diff --git a/.github/DISCUSSION_TEMPLATE/game-bring-up.yml b/.github/DISCUSSION_TEMPLATE/game-bring-up.yml index afb5f3d3d3..ff08c3cca6 100644 --- a/.github/DISCUSSION_TEMPLATE/game-bring-up.yml +++ b/.github/DISCUSSION_TEMPLATE/game-bring-up.yml @@ -6,7 +6,7 @@ body: One thread per title, for every platform: search this category for the title ID first. If a thread exists, reply there, also when your OS or GPU differs from the one it reports. Name the discussion `Game name (title ID)`, for example `DOOM Shareware (PPSA99666)`, without the platform. Bug reports and implementation work belong in issues and pull requests; link them here as the blocker. - Per-game progress is also tracked on the [blocker map](https://alxcbtw.github.io/anyps5-roadmap/), built from public GitHub data. Only share material you have the right to distribute. + Only share material you have the right to distribute. - type: checkboxes id: searched attributes: diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..7dff644e5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,59 @@ +name: Bug report +description: A title, homebrew or test stops, crashes or renders wrongly. +labels: [bug] +body: + - type: markdown + attributes: + value: | + Search open and closed issues and pull requests for the error text first, and comment there if it is already reported. + Game bring-up progress belongs in [Discussions](https://github.com/boykopovar/AnyPS5/discussions/categories/game-bring-up), one thread per title. + A slow scene or a change in speed goes into the [Performance report](https://github.com/boykopovar/AnyPS5/issues/new?template=performance.yml) form, which asks for a [performance trace](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/BUILD.md#performance-trace) and its `tools/trace_digest.py` digest. + - type: input + id: summary + attributes: + label: What happens + description: One sentence, with the exact error text if there is one. + validations: + required: true + - type: input + id: title + attributes: + label: Title, test or build step + description: Title ID and content version (for example PPSAxxxxx 01.000.000), a homebrew with its source, the ctest name, or the build step that fails. + validations: + required: true + - type: input + id: commit + attributes: + label: AnyPS5 commit + description: The `main` commit that was built, or the release that was used, and any pull requests merged on top. + validations: + required: true + - type: input + id: system + attributes: + label: System + description: OS, GPU and driver version, and the compiler if you built it yourself. + placeholder: Arch Linux, GCC 16.2.1, RTX 5080 (NVIDIA 615.78.08) + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: The commands that were run, environment variables included, and how long it takes to stop. + validations: + required: true + - type: textarea + id: log + attributes: + label: Log + description: The lines around the stop. + render: text + validations: + required: true + - type: textarea + id: notes + attributes: + label: What was checked + description: Other devices or commits tried, and whether it is the same every run. Attach the full log and screenshots here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..789cbc7d74 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Game bring-up + url: https://github.com/boykopovar/AnyPS5/discussions/categories/game-bring-up + about: Progress of one title, one thread per title ID + - name: Question + url: https://github.com/boykopovar/AnyPS5/discussions/categories/q-a + about: Building, converting and running diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cab59565e5..69f6ed8c96 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ permissions: concurrency: group: build-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr || github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.event_name != 'push' }} env: CONFIGURE: >- @@ -421,7 +421,7 @@ jobs: linux: needs: eligibility - if: ${{ (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.linux)) && needs.eligibility.outputs.full == 'true' }} + if: ${{ !github.event.repository.fork && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.linux)) && needs.eligibility.outputs.full == 'true' }} runs-on: [self-hosted, linux, x64, anyps5] timeout-minutes: 60 env: @@ -641,7 +641,7 @@ jobs: uses: actions/cache/restore@v6 with: path: configure-seed.cmake - key: configure-seed-${{ env.WINLIBS_ZIP }}-${{ steps.submodules.outputs.key }}-${{ hashFiles('CMakeLists.txt', 'cmake/**', 'core/libs/CMakeLists.txt', '.github/workflows/build.yml') }} + key: configure-seed-${{ env.WINLIBS_ZIP }}-${{ steps.submodules.outputs.key }}-${{ hashFiles('CMakeLists.txt', 'cmake/**', 'core/libs/CMakeLists.txt', 'tools/configure_seed.py', '.github/workflows/build.yml') }} - shell: cmd run: if exist configure-seed.cmake (cmake -C configure-seed.cmake -S . -B build %CONFIGURE% "-DANYPS5_AGC_EXECUTION_TESTS=F16ToF32Unpack;F64FmaSubnormals;RegistrationPrepareShutdown") else (cmake -S . -B build %CONFIGURE% "-DANYPS5_AGC_EXECUTION_TESTS=F16ToF32Unpack;F64FmaSubnormals;RegistrationPrepareShutdown") - if: steps.seed-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/conventions.yml b/.github/workflows/conventions.yml index 2a14c1c2a3..d09210cfd0 100644 --- a/.github/workflows/conventions.yml +++ b/.github/workflows/conventions.yml @@ -18,15 +18,14 @@ jobs: steps: - uses: actions/checkout@v7 with: - ref: ${{ github.event.pull_request.base.sha }} + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 persist-credentials: false - name: Fetch pull request objects without checking out its code env: HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: git fetch --no-tags origin "$HEAD_SHA" - - name: Check conventions with the trusted base script + - name: Check conventions with the current base branch script env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} - run: python3 -I tools/check_conventions.py --base "$BASE_SHA" --head "$HEAD_SHA" + run: python3 -I tools/check_conventions.py --base HEAD --head "$HEAD_SHA" diff --git a/.github/workflows/input-mapper.yml b/.github/workflows/input-mapper.yml new file mode 100644 index 0000000000..55120c5b54 --- /dev/null +++ b/.github/workflows/input-mapper.yml @@ -0,0 +1,29 @@ +name: Input mapper + +on: + push: + branches: [main] + pull_request: + branches: [main] + paths: + - '.github/workflows/input-mapper.yml' + - 'tools/input-mapper/**' + - 'core/libs/prx/libScePad/include/**' + - 'core/libs/prx/libSceVideoOut/src/InputMapping.cpp' + +permissions: + contents: read + +jobs: + input-mapper: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-node@v7 + with: + node-version: '22' + package-manager-cache: false + - run: npm --prefix tools/input-mapper test diff --git a/.github/workflows/pages-progress.yml b/.github/workflows/pages-progress.yml index 88c1f81d5a..5aa5f304a6 100644 --- a/.github/workflows/pages-progress.yml +++ b/.github/workflows/pages-progress.yml @@ -18,6 +18,7 @@ concurrency: jobs: progress: + if: ${{ !github.event.repository.fork }} runs-on: ubuntu-latest timeout-minutes: 5 steps: diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 0dc28284b0..bf8d6a0313 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -33,7 +33,7 @@ concurrency: jobs: pages: - if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' + if: ${{ !github.event.repository.fork && (github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success') }} runs-on: ubuntu-latest environment: name: github-pages diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index 8de6e8d91f..4c42ec63a0 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -36,4 +36,4 @@ jobs: node --check demo/web/output-directory.mjs node --check demo/web/worker.mjs - name: Test browser converter helpers - run: node --test 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/output-directory.test.mjs diff --git a/.github/workflows/windows-msys2.yml b/.github/workflows/windows-msys2.yml new file mode 100644 index 0000000000..def52370d2 --- /dev/null +++ b/.github/workflows/windows-msys2.yml @@ -0,0 +1,117 @@ +name: Windows MSYS2 + +on: + schedule: + - cron: '23 8 * * *' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: windows-msys2-${{ 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 + +jobs: + windows-msys2: + runs-on: windows-latest + timeout-minutes: 45 + defaults: + run: + shell: bash + 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 --force-local -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 --force-local -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: Use the CA bundle of Git for Windows + run: | + for prefix in ucrt64 mingw64; do + bundle="C:/Program Files/Git/$prefix/etc/ssl/certs/ca-bundle.crt" + if [ -f "$bundle" ]; then echo "SSL_CERT_FILE=$bundle" >> "$GITHUB_ENV"; exit 0; fi + done + echo "::error::No CA bundle found in Git for Windows"; exit 1 + - id: msys2 + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: true + install: >- + mingw-w64-ucrt-x86_64-gcc + mingw-w64-ucrt-x86_64-cmake + mingw-w64-ucrt-x86_64-ninja + - shell: pwsh + env: + MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }} + run: Add-Content -LiteralPath $env:GITHUB_PATH -Value "$env:MSYS2_LOCATION\ucrt64\bin" + - uses: hendrikmuhs/ccache-action@v1.2.24 + with: + key: windows-msys2 + save: ${{ github.ref == 'refs/heads/main' }} + evict-old-files: job + - id: ffmpeg-cache + uses: actions/cache/restore@v6 + with: + path: build/externals + key: ffmpeg-${{ runner.os }}-${{ steps.submodules.outputs.ffmpeg }} + - shell: cmd + run: cmake -S . -B build %CONFIGURE% "-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 + shell: cmd + run: python tools/check_test_registration.py build + - shell: cmd + run: cmake --build build + - shell: cmd + run: cmake --build build --target libs + - shell: cmd + working-directory: build + run: ctest --output-on-failure --timeout 120 --parallel 4 --output-log ctest.log --output-junit ctest.xml + - run: python tools/check_skips.py build/ctest.xml --job windows + - name: Report tests slower than 30 s + if: ${{ !cancelled() }} + run: | + [ -f build/ctest.log ] || exit 0 + awk '/Test +#[0-9]+: / && $NF == "sec" { for (i = 1; i <= NF; i++) if ($i ~ /^#[0-9]+:$/) name = $(i + 1); t = $(NF - 1) + 0; if (t > max[name]) max[name] = t } + END { for (name in max) if (max[name] > 30) printf "::warning title=Slow test::%s took %.1f s on %s; tests must finish in 30 s (CONTRIBUTING.md)\n", name, max[name], ENVIRON["RUNNER_OS"] }' build/ctest.log + - uses: actions/upload-artifact@v7 + if: ${{ !cancelled() }} + with: + name: windows-msys2-tests + path: | + build/ctest.log + build/ctest.xml + if-no-files-found: ignore diff --git a/.gitmodules b/.gitmodules index 64a98a2f94..dcde427ae7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,9 @@ [submodule "3rdparty/ffmpeg-core"] path = 3rdparty/ffmpeg-core url = https://github.com/KytyPS5/ext-ffmpeg-core.git +[submodule "3rdparty/opus"] + path = 3rdparty/opus + url = https://github.com/xiph/opus.git [submodule "3rdparty/freetype"] path = 3rdparty/freetype url = https://github.com/freetype/freetype.git @@ -52,3 +55,9 @@ [submodule "3rdparty/vgmstream"] path = 3rdparty/vgmstream url = https://github.com/vgmstream/vgmstream.git +[submodule "3rdparty/ogg"] + path = 3rdparty/ogg + url = https://github.com/xiph/ogg.git +[submodule "3rdparty/vorbis"] + path = 3rdparty/vorbis + url = https://github.com/xiph/vorbis.git diff --git a/3rdparty/ogg b/3rdparty/ogg new file mode 160000 index 0000000000..be05b13e98 --- /dev/null +++ b/3rdparty/ogg @@ -0,0 +1 @@ +Subproject commit be05b13e98b048f0b5a0f5fa8ce514d56db5f822 diff --git a/3rdparty/opus b/3rdparty/opus new file mode 160000 index 0000000000..22244de5a7 --- /dev/null +++ b/3rdparty/opus @@ -0,0 +1 @@ +Subproject commit 22244de5a79bd1d6d623c32e72bf1954b56235be diff --git a/3rdparty/vorbis b/3rdparty/vorbis new file mode 160000 index 0000000000..0657aee69d --- /dev/null +++ b/3rdparty/vorbis @@ -0,0 +1 @@ +Subproject commit 0657aee69dec8508a0011f47f3b69d7538e9d262 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9eaa66c8db..dd86602fde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,13 +6,12 @@ endif() project(Relinker CXX) set(ANYPS5_TEST_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests") -set(ANYPS5_WINDOWS_TEST_EXIT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/WindowsTestExit.cpp") function(add_test_executable target) add_executable(${target} ${ARGN}) set_target_properties(${target} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "$<1:${ANYPS5_TEST_OUTPUT_DIRECTORY}>") if(MINGW) - target_sources(${target} PRIVATE "${ANYPS5_WINDOWS_TEST_EXIT}") + target_sources(${target} PRIVATE $) target_link_options(${target} PRIVATE LINKER:--wrap=main) endif() endfunction() @@ -96,6 +95,10 @@ if(APPLE AND NOT ANYPS5_RELINKER_ONLY) add_link_options(-fexperimental-library) endif() +if(MINGW) + add_library(anyps5_windows_test_exit OBJECT cmake/WindowsTestExit.cpp) +endif() + if(BUILD_TESTING) add_test_executable(shader_cache_file_read_tests ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/CacheFileReadTests.cpp) @@ -107,6 +110,7 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/RequestSerializerTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp) target_include_directories(request_serializer_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/core/libs ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/ControlFlow/include) add_test(NAME request_serializer COMMAND request_serializer_tests) @@ -132,10 +136,17 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/test_shader_cache_native_paths.py ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/ShaderCacheVersion.cmake ${CMAKE_COMMAND}) set_tests_properties(shader_cache_native_paths PROPERTIES TIMEOUT 20) - add_test(NAME shader_cache_runtime_sources COMMAND ${Python3_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_shader_cache_runtime_sources.py - ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler ${CMAKE_COMMAND}) - set_tests_properties(shader_cache_runtime_sources PROPERTIES TIMEOUT 180) + foreach(shard RANGE 0 6) + if(shard EQUAL 0) + set(shader_cache_sources_test_name shader_cache_runtime_sources) + else() + set(shader_cache_sources_test_name shader_cache_runtime_sources_shard_${shard}) + endif() + add_test(NAME ${shader_cache_sources_test_name} COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_shader_cache_runtime_sources.py + ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler ${CMAKE_COMMAND} --shard ${shard}/7) + set_tests_properties(${shader_cache_sources_test_name} PROPERTIES TIMEOUT 180) + endforeach() add_test(NAME pr_overlap_encoding COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_encoding.py) add_test(NAME pr_overlap_dependencies COMMAND ${Python3_EXECUTABLE} @@ -168,7 +179,7 @@ if(BUILD_TESTING) endif() add_test(NAME ${conventions_test_name} COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_comments.py --shard ${shard}/12) - set_tests_properties(${conventions_test_name} PROPERTIES TIMEOUT 30) + set_tests_properties(${conventions_test_name} PROPERTIES TIMEOUT 120) endforeach() add_test(NAME conventions_wrapped_declarations COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_wrapped_declarations.py) @@ -237,27 +248,51 @@ if(BUILD_TESTING) add_test(NAME pad_timeline COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pad_timeline.py) set_tests_properties(pad_timeline PROPERTIES TIMEOUT 20) + set(check_compile_modes_source "${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile_modes.py") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${check_compile_modes_source}") + file(STRINGS "${check_compile_modes_source}" check_compile_modes_cases REGEX "^ def test_") + foreach(test_case IN LISTS check_compile_modes_cases) + string(REGEX REPLACE "^ def (test_[A-Za-z0-9_]+).*" "\\1" test_case "${test_case}") + add_test(NAME check_compile_modes_${test_case} COMMAND ${Python3_EXECUTABLE} + "${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile_modes.py" CheckCompileModeTests.${test_case}) + set_tests_properties(check_compile_modes_${test_case} PROPERTIES TIMEOUT 30) + endforeach() add_test(NAME trace_digest COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_trace_digest.py) + add_test(NAME symbolize_samples COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_symbolize_samples.py) add_test(NAME conventions_bom_shebang COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_bom_shebang.py) set_tests_properties(conventions_bom_shebang PROPERTIES TIMEOUT 20) add_test(NAME env_reference COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_env_reference.py) set_tests_properties(env_reference PROPERTIES TIMEOUT 20) - add_test(NAME conventions_env_reference COMMAND ${Python3_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_env_reference.py) - set_tests_properties(conventions_env_reference PROPERTIES TIMEOUT 60) + set(env_reference_test_source "${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_env_reference.py") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${env_reference_test_source}") + file(STRINGS "${env_reference_test_source}" env_reference_cases REGEX "^ def test_") + foreach(test_case IN LISTS env_reference_cases) + string(REGEX REPLACE "^ def (test_[A-Za-z0-9_]+).*" "\\1" test_case "${test_case}") + add_test(NAME conventions_env_reference_${test_case} COMMAND ${Python3_EXECUTABLE} + "${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_env_reference.py" "EnvReferenceDiffTests.${test_case}") + set_tests_properties(conventions_env_reference_${test_case} PROPERTIES TIMEOUT 30) + endforeach() add_test(NAME conventions_body_bom COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_body_bom.py) add_test(NAME conventions_blob_reads COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_blob_reads.py) add_test(NAME conventions_agent_files COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_agent_files.py) + add_test(NAME conventions_alignment COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_alignment.py) add_test(NAME conventions_pr_template COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_pr_template.py) + add_test(NAME check_compile_preprocessor COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile_preprocessor.py) + set_tests_properties(check_compile_preprocessor PROPERTIES TIMEOUT 20) add_test(NAME check_compile COMMAND ${Python3_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile.py) + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile.py CheckCompileTests) + add_test(NAME check_compile_outputs COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile.py CheckCompileOutputTests) add_test(NAME performance_compare COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_performance_compare.py) set_tests_properties(performance_compare PROPERTIES TIMEOUT 20) @@ -347,6 +382,7 @@ add_subdirectory(3rdparty/libusb-cmake EXCLUDE_FROM_ALL) set_target_properties(usb-1.0 PROPERTIES POSITION_INDEPENDENT_CODE ON) include(cmake/ZArchive.cmake) +include(cmake/Opus.cmake) if(WIN32) set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE) set(MI_BUILD_STATIC OFF CACHE BOOL "" FORCE) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6668eb80b..d96b4d4c11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,7 @@ ## Code - Follow the [coding conventions](docs/dev/CONVENTIONS.md): naming, no comments except [technical debt](docs/dev/TechnicalDebt.md), Conventional Commits. +- **Every exported PS5 function with `APS5_VABI` must have exactly one declaration in a header reachable through the library's `.hpp`. Duplicate declarations in headers, `.cpp` files and tests are forbidden. This is checked by the Python test [`public_headers`](core/libs/tests/test_public_headers.py).** - Every function either does exactly what it is supposed to or throws. Unimplemented exports call `NotImplemented_nid_no_patch(__func__)` (see [libSceAudioIn](core/libs/prx/libSceAudioIn/Export.cpp)). - Pull requests that add or change shader instruction semantics say where they come from: measured on hardware (which GPU and what was checked, e.g. with the [hardware oracle](docs/dev/HW_ORACLE.md)) or the exact source (ISA section, LLVM, ACO or Mesa file). Reviewers check the semantics on hardware. Cases the source doesn't settle throw, and behaviour not verified on hardware is recorded in [technical debt](docs/dev/TechnicalDebt.md). - An export's signature or behaviour taken from a title's call sites or from a library's disassembly follows [reading titles and libraries](docs/dev/REVERSE_ENGINEERING.md): a call site settles the arguments, not what the library does. @@ -11,6 +12,7 @@ - Implement the general behaviour of a function, not what one title happens to need. - Don't add replacements for modules that titles ship themselves in `sce_module/`, `sce_modules/` or `prx/`: engine or middleware modules (Cohtml, FMOD, GOG Galaxy) and SDK libraries that only wrap other system libraries. The relinker converts and loads the title's own module, and a host library with the same name is left out of `DT_NEEDED`. Only system libraries, which reach the kernel or the hardware, are reimplemented in [core/libs/prx](core/libs/prx). A host copy may still exist for a wrapped library that some dumps lack entirely, like libSceNpCppWebApi: the title's own module wins whenever one is shipped, so it is only ever consulted by titles that ship nothing. - Avoid non-standard extensions (`__attribute__`, etc.) where standard C++ is enough. Helper symbols that must not become NIDs use the `_nid_no_patch` or `_nid_no_patch_cut` suffix. +- `alignas` takes at most 8192: MinGW GCC, the Windows compiler, rejects static objects aligned above it. A larger block is aligned at run time inside a bigger buffer. - The relinker uses only the C++20 standard library. - Third-party code is added as a submodule under `3rdparty/` and built from source, not found on the system. With `ANYPS5_USE_SYSTEM_DEPS=ON` the packages packaged by the distributions (SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools) come from the system instead; that resolution lives in [cmake/SystemDeps.cmake](cmake/SystemDeps.cmake) and the default build never reads the system. - Don't add tests that only check that a symbol is exported: a missing export already fails at startup. @@ -42,7 +44,7 @@ This manual run also builds and tests the relinker on Linux, Windows and macOS w With `--field full=false`, the manual run skips the full builds and runs only the relinker matrix on Linux, Windows and macOS, if the pull request changes a file the relinker-only build uses, such as the Python tools and their tests in `tools/`. -The Conventions check runs automatically for every contributor, including first-time contributors. It uses the base branch checker and reads pull request Git objects without executing pull request code. The Conventions check runs on every pull request and fails when a rule on this page is broken. It accepts code comments only when the pull request also changes [technical debt](docs/dev/TechnicalDebt.md), the index or a file in `docs/dev/debt/`, and only UTF-8 text files. Run it locally before pushing: +The Conventions check runs automatically for every contributor, including first-time contributors. It uses the checker from the current tip of the base branch, so rules added after a pull request was opened apply to its next run, and reads pull request Git objects without executing pull request code. The Conventions check runs on every pull request and fails when a rule on this page is broken. It accepts code comments only when the pull request also changes [technical debt](docs/dev/TechnicalDebt.md), the index or a file in `docs/dev/debt/`, and only UTF-8 text files. Run it locally before pushing: ``` python3 tools/check_conventions.py --base origin/main diff --git a/README.md b/README.md index 5777022fb4..1a3a9ac30e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully prod ## Compatibility -Per-game progress (stage reached, open blockers, libraries and GPU coverage) is tracked on the [blocker map](https://alxcbtw.github.io/anyps5-roadmap/), a community project built from public GitHub data ([source](https://github.com/AlxcBTW/anyps5-roadmap)). Test reports go in the [Game bring-up](https://github.com/boykopovar/AnyPS5/discussions/categories/game-bring-up) discussions, one thread per title. +Per-game status and test reports live in the [Game bring-up](https://github.com/boykopovar/AnyPS5/discussions/categories/game-bring-up) discussions, one thread per title. ## Input mapping @@ -38,6 +38,8 @@ SDL-mapped game controllers are supported, including analog sticks and triggers. This project is intended for interoperability, research, preservation, and compatibility purposes. It does not include, distribute, or require copyrighted software, firmware, cryptographic keys, or proprietary libraries. Users are responsible for ensuring that any binaries used with this project are obtained and used in accordance with applicable laws and their respective license terms. +AnyPS5 is an independent project and is not affiliated with, authorized or endorsed by Sony Interactive Entertainment. "PlayStation" and "PS5" are trademarks of Sony Interactive Entertainment Inc. + ## License This project is licensed under the GNU General Public License version 2 only. diff --git a/cmake/Opus.cmake b/cmake/Opus.cmake new file mode 100644 index 0000000000..d6bc3e3f78 --- /dev/null +++ b/cmake/Opus.cmake @@ -0,0 +1,19 @@ +function(add_bundled_opus) + set(BUILD_SHARED_LIBS OFF) + set(BUILD_TESTING OFF) + set(OPUS_BUILD_SHARED_LIBRARY OFF) + set(OPUS_BUILD_TESTING OFF) + set(OPUS_BUILD_PROGRAMS OFF) + set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF) + set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF) + set(OPUS_STACK_PROTECTOR OFF) + set(OPUS_FORTIFY_SOURCE OFF) + add_subdirectory(3rdparty/opus EXCLUDE_FROM_ALL) + set_target_properties(opus PROPERTIES POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden) + target_compile_definitions(opus PRIVATE OPUS_EXPORT=) + if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(opus INTERFACE LINKER:--exclude-libs,libopus.a) + endif() +endfunction() + +add_bundled_opus() diff --git a/core/Decoder/Jpeg/include/Decoder/Jpeg.hpp b/core/Decoder/Jpeg/include/Decoder/Jpeg.hpp index ed59467ac3..62d09b0698 100644 --- a/core/Decoder/Jpeg/include/Decoder/Jpeg.hpp +++ b/core/Decoder/Jpeg/include/Decoder/Jpeg.hpp @@ -19,6 +19,10 @@ struct Header { std::uint32_t width; std::uint32_t height; std::uint32_t channels; + std::uint8_t componentIds[4]{}; + std::uint8_t samplingFactors[4]{}; + bool progressive = false; + bool app0BeforeFrame = false; }; enum class Sampling { diff --git a/core/Decoder/Jpeg/src/Jpeg.cpp b/core/Decoder/Jpeg/src/Jpeg.cpp index e8e6aa19c3..96ace625a4 100644 --- a/core/Decoder/Jpeg/src/Jpeg.cpp +++ b/core/Decoder/Jpeg/src/Jpeg.cpp @@ -126,7 +126,33 @@ std::optional
ParseHeader(std::span jpeg) { int channels = 0; if (!stbi_info_from_memory(jpeg.data(), static_cast(jpeg.size()), &width, &height, &channels)) return std::nullopt; if (width <= 0 || height <= 0 || channels <= 0) return std::nullopt; - return Header{static_cast(width), static_cast(height), static_cast(channels)}; + Header header{static_cast(width), static_cast(height), static_cast(channels)}; + std::size_t offset = 2; + while (offset < jpeg.size()) { + if (jpeg[offset++] != 0xFF) return std::nullopt; + while (offset < jpeg.size() && jpeg[offset] == 0xFF) ++offset; + if (offset == jpeg.size()) return std::nullopt; + const std::uint8_t marker = jpeg[offset++]; + if (marker == 0xD9 || marker == 0xDA) return std::nullopt; + if (marker == 0x01 || (marker >= 0xD0 && marker <= 0xD8)) continue; + if (jpeg.size() - offset < 2) return std::nullopt; + const std::size_t length = (static_cast(jpeg[offset]) << 8) | jpeg[offset + 1]; + if (length < 2 || length > jpeg.size() - offset) return std::nullopt; + if (marker == 0xE0) header.app0BeforeFrame = true; + if (marker == 0xC0 || marker == 0xC1 || marker == 0xC2) { + if (length < 8) return std::nullopt; + header.channels = jpeg[offset + 7]; + if (header.channels == 0 || header.channels > 4 || length != 8 + 3 * header.channels) return std::nullopt; + header.progressive = marker == 0xC2; + for (std::uint32_t i = 0; i < header.channels; ++i) { + header.componentIds[i] = jpeg[offset + 8 + 3 * i]; + header.samplingFactors[i] = jpeg[offset + 9 + 3 * i]; + } + return header; + } + offset += length; + } + return std::nullopt; } std::optional Decode(std::span jpeg) { diff --git a/core/Decoder/Png/src/Png.cpp b/core/Decoder/Png/src/Png.cpp index 434a589d6c..8fb90270e9 100644 --- a/core/Decoder/Png/src/Png.cpp +++ b/core/Decoder/Png/src/Png.cpp @@ -48,6 +48,37 @@ bool isChunkType(const std::uint8_t* bytes, const char* type) { return std::equal(bytes, bytes + 4, type); } +std::optional readGrayscaleTransparencyKey(std::span png) { + std::size_t offset = SIGNATURE.size(); + if (png.size() < offset || !std::equal(SIGNATURE.begin(), SIGNATURE.end(), png.begin())) return std::nullopt; + std::optional key; + while (png.size() - offset >= CHUNK_OVERHEAD) { + const std::uint32_t length = readBigEndian32(&png[offset]); + const std::uint8_t* type = &png[offset + 4]; + if (length > png.size() - offset - CHUNK_OVERHEAD) return std::nullopt; + if (isChunkType(type, "tRNS")) { + if (length != 2) return std::nullopt; + key = static_cast(png[offset + 8] << 8 | png[offset + 9]); + } + if (isChunkType(type, "IDAT") || isChunkType(type, "IEND")) return key; + offset += CHUNK_OVERHEAD + length; + } + return std::nullopt; +} + +void repairGrayscaleTransparency(const Header& header, std::span png, Image& image) { + if (header.colorType != ColorType::Grayscale || !header.hasTransparency) return; + if (header.bitDepth != 1 && header.bitDepth != 2 && header.bitDepth != 4) return; + const std::optional key = readGrayscaleTransparencyKey(png); + if (!key.has_value()) return; + const std::uint16_t mask = static_cast((1u << header.bitDepth) - 1); + if ((*key & 0xFF) == (*key & mask)) return; + const std::uint8_t transparent = static_cast((*key & mask) * (255u / mask)); + for (std::size_t offset = 3; offset < image.pixels.size(); offset += 4) { + image.pixels[offset] = static_cast(image.pixels[offset - 3] == transparent ? 0 : 255); + } +} + bool isValidFormat(std::uint8_t bitDepth, std::uint8_t colorType) { switch (colorType) { case 0: @@ -158,6 +189,8 @@ std::optional Decode(std::span png) { Image image{static_cast(width), static_cast(height), {}}; image.pixels.assign(decoded.get(), decoded.get() + static_cast(image.width) * image.height * 4); + const std::optional
header = ParseHeader(png); + if (header.has_value()) repairGrayscaleTransparency(*header, png, image); return image; } diff --git a/core/Decoder/Png/tests/Png.cpp b/core/Decoder/Png/tests/Png.cpp index 0c1ac81d1e..fbd18d353f 100644 --- a/core/Decoder/Png/tests/Png.cpp +++ b/core/Decoder/Png/tests/Png.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,17 @@ static const std::uint8_t GRAY1[] = { 0x42, 0x60, 0x82, }; +static const std::uint8_t GRAY1_TRNS[] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x59, 0x42, + 0x27, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4E, 0x53, 0x00, 0x02, 0x98, 0x9D, 0xAC, 0x14, 0x00, + 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x70, 0x00, 0x00, 0x00, 0x42, 0x00, + 0x41, 0x84, 0xBF, 0x8E, 0x62, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, + 0x82, +}; + +static const std::uint8_t PNG_SIGNATURE[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; + static void AppendBigEndian32(std::vector& bytes, std::uint32_t value) { for (int shift = 24; shift >= 0; shift -= 8) bytes.push_back(static_cast(value >> shift)); } @@ -64,6 +76,130 @@ static std::vector HeaderOnly(std::uint32_t width, std::uint32_t h return bytes; } +static std::uint32_t Crc32(std::span bytes) { + std::uint32_t crc = 0xFFFFFFFFu; + for (const std::uint8_t byte : bytes) { + crc ^= byte; + for (int bit = 0; bit < 8; ++bit) crc = (crc >> 1) ^ ((crc & 1) != 0 ? 0xEDB88320u : 0u); + } + return ~crc; +} + +static std::uint32_t Adler32(std::span bytes) { + std::uint32_t low = 1; + std::uint32_t high = 0; + for (const std::uint8_t byte : bytes) { + low = (low + byte) % 65521; + high = (high + low) % 65521; + } + return high << 16 | low; +} + +static void AppendTestChunk(std::vector& png, const char* type, std::span data) { + AppendBigEndian32(png, static_cast(data.size())); + const std::size_t start = png.size(); + png.insert(png.end(), type, type + 4); + png.insert(png.end(), data.begin(), data.end()); + AppendBigEndian32(png, Crc32(std::span(png.data() + start, png.size() - start))); +} + +static std::vector ZlibStore(std::span bytes) { + Require(bytes.size() <= 0xFFFF); + const std::uint16_t length = static_cast(bytes.size()); + const std::uint16_t inverse = static_cast(~length); + std::vector output{0x78, 0x01, 0x01, static_cast(length), static_cast(length >> 8), + static_cast(inverse), static_cast(inverse >> 8)}; + output.insert(output.end(), bytes.begin(), bytes.end()); + AppendBigEndian32(output, Adler32(bytes)); + return output; +} + +static std::vector GrayLowBitPng(std::uint8_t bitDepth, std::span samples, + std::optional transparency, + std::optional precedingTransparency = std::nullopt) { + Require(bitDepth == 1 || bitDepth == 2 || bitDepth == 4); + std::vector rows(1 + (samples.size() * bitDepth + 7) / 8, 0); + std::size_t bit = 0; + for (const std::uint8_t sample : samples) { + Require(sample < (1u << bitDepth)); + rows[1 + bit / 8] |= static_cast(sample << (8 - bitDepth - bit % 8)); + bit += bitDepth; + } + + std::vector ihdr; + AppendBigEndian32(ihdr, static_cast(samples.size())); + AppendBigEndian32(ihdr, 1); + ihdr.insert(ihdr.end(), {bitDepth, 0, 0, 0, 0}); + + std::vector png(PNG_SIGNATURE, PNG_SIGNATURE + sizeof(PNG_SIGNATURE)); + AppendTestChunk(png, "IHDR", ihdr); + if (precedingTransparency.has_value()) { + const std::uint8_t key[2] = {static_cast(*precedingTransparency >> 8), static_cast(*precedingTransparency)}; + AppendTestChunk(png, "tRNS", key); + } + if (transparency.has_value()) { + const std::uint8_t key[2] = {static_cast(*transparency >> 8), static_cast(*transparency)}; + AppendTestChunk(png, "tRNS", key); + } + AppendTestChunk(png, "IDAT", ZlibStore(rows)); + AppendTestChunk(png, "IEND", std::span()); + return png; +} + +static std::uint8_t GrayScale(std::uint8_t bitDepth) { + if (bitDepth == 1) return 255; + if (bitDepth == 2) return 85; + return 17; +} + +static void RequireGrayTransparency(std::uint8_t bitDepth, std::span samples, std::uint16_t key, + std::uint8_t transparentSample, std::optional precedingKey = std::nullopt) { + const std::vector png = GrayLowBitPng(bitDepth, samples, key, precedingKey); + const std::vector original = png; + const auto header = Decoder::Png::ParseHeader(png); + Require(header.has_value()); + Require(header->width == static_cast(samples.size()) && header->height == 1 && header->bitDepth == bitDepth); + Require(header->colorType == Decoder::Png::ColorType::Grayscale && header->hasTransparency); + + const auto image = Decoder::Png::Decode(png); + Require(image.has_value() && image->pixels.size() == samples.size() * 4); + const std::uint8_t scale = GrayScale(bitDepth); + for (std::size_t i = 0; i < samples.size(); ++i) { + const std::uint8_t gray = static_cast(samples[i] * scale); + Require(image->pixels[i * 4] == gray && image->pixels[i * 4 + 1] == gray && image->pixels[i * 4 + 2] == gray); + Require(image->pixels[i * 4 + 3] == (samples[i] == transparentSample ? 0 : 255)); + } + Require(png == original); +} + +static void RequireGrayOpaque(std::uint8_t bitDepth, std::span samples) { + const std::vector png = GrayLowBitPng(bitDepth, samples, std::nullopt); + const std::vector original = png; + const auto header = Decoder::Png::ParseHeader(png); + Require(header.has_value() && !header->hasTransparency); + const auto image = Decoder::Png::Decode(png); + Require(image.has_value() && image->pixels.size() == samples.size() * 4); + const std::uint8_t scale = GrayScale(bitDepth); + for (std::size_t i = 0; i < samples.size(); ++i) { + const std::uint8_t gray = static_cast(samples[i] * scale); + Require(image->pixels[i * 4] == gray && image->pixels[i * 4 + 1] == gray && image->pixels[i * 4 + 2] == gray); + Require(image->pixels[i * 4 + 3] == 255); + } + Require(png == original); +} + +static void RequireTruncatedTransparency() { + const std::uint8_t samples[] = {0, 1}; + const std::vector png = GrayLowBitPng(1, samples, 0x0002); + const std::size_t transparencyOffset = sizeof(PNG_SIGNATURE) + 25; + const std::vector truncated(png.begin(), png.begin() + transparencyOffset + 9); + const std::vector original = truncated; + const auto header = Decoder::Png::ParseHeader(truncated); + Require(header.has_value() && !header->hasTransparency); + (void)Decoder::Png::Decode(truncated); + Require(truncated == original); +} + static void RequireRoundTrip(std::uint32_t channels, Decoder::Png::ColorType colorType) { constexpr std::uint32_t width = 20; constexpr std::uint32_t height = 10; @@ -176,6 +312,42 @@ int main() { const std::uint8_t levels[8] = {0, 255, 0, 255, 255, 0, 0, 255}; for (int i = 0; i < 8; ++i) Require(gray1->pixels[i * 4] == levels[i] && gray1->pixels[i * 4 + 3] == 255); + const auto gray1TrnsHeader = Decoder::Png::ParseHeader(GRAY1_TRNS); + Require(gray1TrnsHeader.has_value()); + Require(gray1TrnsHeader->width == 2 && gray1TrnsHeader->height == 1 && gray1TrnsHeader->bitDepth == 1); + Require(gray1TrnsHeader->colorType == Decoder::Png::ColorType::Grayscale && gray1TrnsHeader->hasTransparency); + const auto gray1Trns = Decoder::Png::Decode(GRAY1_TRNS); + Require(gray1Trns.has_value()); + Require(gray1Trns->pixels == std::vector({0, 0, 0, 0, 255, 255, 255, 255})); + + const std::uint8_t gray1Samples[] = {0, 1, 0, 1, 1, 0, 0, 1}; + RequireGrayTransparency(1, gray1Samples, 0x0000, 0); + RequireGrayTransparency(1, gray1Samples, 0x0002, 0); + RequireGrayTransparency(1, gray1Samples, 0x0100, 0); + RequireGrayTransparency(1, gray1Samples, 0xFF02, 0); + RequireGrayTransparency(1, gray1Samples, 0xFFFF, 1); + RequireGrayTransparency(1, gray1Samples, 0x0001, 1, 0x0002); + RequireGrayOpaque(1, gray1Samples); + + const std::uint8_t gray2Samples[] = {0, 1, 2, 3}; + RequireGrayTransparency(2, gray2Samples, 0x0001, 1); + RequireGrayTransparency(2, gray2Samples, 0x0005, 1); + RequireGrayTransparency(2, gray2Samples, 0x0100, 0); + RequireGrayTransparency(2, gray2Samples, 0x0101, 1); + RequireGrayTransparency(2, gray2Samples, 0xFF05, 1); + RequireGrayTransparency(2, gray2Samples, 0xFFFF, 3); + RequireGrayOpaque(2, gray2Samples); + + const std::uint8_t gray4Samples[] = {0, 3, 10, 15, 1}; + RequireGrayTransparency(4, gray4Samples, 0x0003, 3); + RequireGrayTransparency(4, gray4Samples, 0x0013, 3); + RequireGrayTransparency(4, gray4Samples, 0x0100, 0); + RequireGrayTransparency(4, gray4Samples, 0x0103, 3); + RequireGrayTransparency(4, gray4Samples, 0xFF13, 3); + RequireGrayTransparency(4, gray4Samples, 0xFFFF, 15); + RequireGrayOpaque(4, gray4Samples); + RequireTruncatedTransparency(); + const auto interlaced = Decoder::Png::ParseHeader(HeaderOnly(3, 4, 8, 6, 1)); Require(interlaced.has_value() && interlaced->interlaced && !interlaced->hasTransparency); Require(!Decoder::Png::ParseHeader(HeaderOnly(0, 4, 8, 6, 0)).has_value()); diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 737f8fa20f..099467fe17 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -39,9 +39,16 @@ if(MINGW) check_cxx_source_compiles("#ifdef __USING_SJLJ_EXCEPTIONS__\n#error\n#endif\nint main() { return 0; }" ANYPS5_MINGW_DWARF_EXCEPTIONS) unset(CMAKE_REQUIRED_FLAGS) if(NOT ANYPS5_MINGW_DWARF_EXCEPTIONS) - message(FATAL_ERROR "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} selects SjLj exceptions with -fno-asynchronous-unwind-tables; the libraries need DWARF unwinding. Use the MinGW-w64 toolchain listed in docs/dev/BUILD.md.") + check_cxx_source_compiles("#if !defined(__SEH__) || defined(__USING_SJLJ_EXCEPTIONS__)\n#error\n#endif\n[[gnu::sysv_abi, gnu::noinline]] void Throw() { throw 1; }\n[[gnu::sysv_abi, gnu::noinline]] int Catch() { try { Throw(); } catch (int value) { return value; } return 0; }\nint main() { return Catch() != 1; }" ANYPS5_MINGW_SYSV_SEH_EXCEPTIONS) + if(NOT ANYPS5_MINGW_SYSV_SEH_EXCEPTIONS) + message(FATAL_ERROR "The Windows libraries require DWARF exceptions or GCC with System V ABI SEH support. Use the toolchains listed in docs/dev/BUILD.md.") + endif() + add_library(windows_seh_personalities OBJECT ${CMAKE_CURRENT_SOURCE_DIR}/prx/libc/src/specifics/windows/SehPersonalities.cpp) + target_link_options(windows_seh_personalities INTERFACE LINKER:--undefined=LibcSehPersonalities_nid_no_patch) + link_libraries(windows_seh_personalities) + else() + add_compile_options(-fno-asynchronous-unwind-tables) endif() - add_compile_options(-fno-asynchronous-unwind-tables) endif() function(configure_windows_unwind target) @@ -50,7 +57,7 @@ function(configure_windows_unwind target) message(FATAL_ERROR "Windows DWARF unwinding requires objcopy") endif() add_custom_command(TARGET ${target} POST_BUILD - COMMAND "${CMAKE_OBJCOPY}" --rename-section .eh_frame=.ehfram "$" + COMMAND "${CMAKE_OBJCOPY}" --rename-section .eh_frame=.ehfram --rename-section .eh_fram=.ehfram "$" VERBATIM ) endif() @@ -61,6 +68,18 @@ if(MINGW) string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") endforeach() + if(NOT EXISTS "${CMAKE_RANLIB}") + if(EXISTS "${CMAKE_CXX_COMPILER_RANLIB}") + set(CMAKE_RANLIB "${CMAKE_CXX_COMPILER_RANLIB}") + else() + get_filename_component(_compiler_dir "${CMAKE_CXX_COMPILER}" DIRECTORY) + if(EXISTS "${_compiler_dir}/gcc-ranlib${CMAKE_EXECUTABLE_SUFFIX}") + set(CMAKE_RANLIB "${_compiler_dir}/gcc-ranlib${CMAKE_EXECUTABLE_SUFFIX}") + elseif(EXISTS "${_compiler_dir}/ranlib${CMAKE_EXECUTABLE_SUFFIX}") + set(CMAKE_RANLIB "${_compiler_dir}/ranlib${CMAKE_EXECUTABLE_SUFFIX}") + endif() + endif() + endif() if(NOT EXISTS "${CMAKE_RANLIB}") message(FATAL_ERROR "Stable import libraries require ranlib") endif() @@ -186,6 +205,8 @@ foreach(LIB_DIR ${LIBS_DIRS}) endif() endforeach() +target_link_libraries(libSceUsbStorageDialog PRIVATE libSceUserService) + if(APS5_ENABLE_TRACY) set_target_properties(TracyClient PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${UNPATCHED_DIR} @@ -209,7 +230,7 @@ if(TARGET mimalloc) target_compile_features(pe_import_first PRIVATE cxx_std_20) if(MINGW) target_compile_options(pe_import_first PRIVATE -fasynchronous-unwind-tables) - target_link_options(pe_import_first PRIVATE -static -static-libgcc -static-libstdc++) + target_link_options(pe_import_first PRIVATE -municode -static -static-libgcc -static-libstdc++) endif() add_custom_command( OUTPUT ${PATCHED_DIR}/mimalloc.dll @@ -239,6 +260,24 @@ if(BUILD_TESTING) set_tests_properties(guest_posix_scalar_io PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sys_sdk_version_tests tests/GuestKernelSysSdkVersion.cpp) + target_include_directories(guest_kernel_sys_sdk_version_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_sdk_version_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_sdk_version_tests) + add_test(NAME guest_kernel_sys_sdk_version COMMAND guest_kernel_sys_sdk_version_tests) + if(WIN32) + set_tests_properties(guest_kernel_sys_sdk_version PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_kernel_sys_console_identity_tests tests/GuestKernelSysConsoleIdentity.cpp) + target_include_directories(guest_kernel_sys_console_identity_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_console_identity_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_console_identity_tests) + add_test(NAME guest_kernel_sys_console_identity COMMAND guest_kernel_sys_console_identity_tests) + if(WIN32) + set_tests_properties(guest_kernel_sys_console_identity 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) @@ -248,6 +287,32 @@ if(BUILD_TESTING) add_test(NAME nid_buffer_validation COMMAND nid_buffer_validation_tests) set_tests_properties(nid_buffer_validation PROPERTIES TIMEOUT 10) + add_test_executable(guest_dinkumware_fetch_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/GuestDinkumwareFetch.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/NidCompute.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/Sha1.cpp) + target_include_directories(guest_dinkumware_fetch_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/nid/include) + target_link_libraries(guest_dinkumware_fetch_tests PRIVATE ${CMAKE_DL_LIBS}) + configure_windows_unwind(guest_dinkumware_fetch_tests) + if(MINGW) + target_link_libraries(guest_dinkumware_fetch_tests PRIVATE libc) + endif() + add_custom_target(guest_dinkumware_fetch_libraries DEPENDS ${LIBC_PATCHED}) + add_dependencies(guest_dinkumware_fetch_tests guest_dinkumware_fetch_libraries) + foreach(operation IN ITEMS and or xor) + foreach(width IN ITEMS 1 2 4) + add_test(NAME guest_dinkumware_fetch_${operation}_${width} COMMAND guest_dinkumware_fetch_tests + $ unpatched ${operation} ${width}) + add_test(NAME guest_dinkumware_fetch_${operation}_${width}_patched COMMAND guest_dinkumware_fetch_tests + ${PATCHED_DIR} patched ${operation} ${width}) + set_tests_properties(guest_dinkumware_fetch_${operation}_${width} + guest_dinkumware_fetch_${operation}_${width}_patched PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_dinkumware_fetch_${operation}_${width} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_dinkumware_fetch_${operation}_${width}_patched PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:${PATCHED_DIR}") + endif() + endforeach() + endforeach() add_test_executable(pe_import_first_tests allocator/tests/PeImportFirstTests.cpp allocator/PeImportFirst.cpp) target_include_directories(pe_import_first_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_features(pe_import_first_tests PRIVATE cxx_std_20) @@ -259,6 +324,25 @@ if(BUILD_TESTING) find_package(Python3 QUIET COMPONENTS Interpreter) if(Python3_Interpreter_FOUND) + set(public_header_compiler_args) + if(APPLE) + foreach(architecture IN LISTS CMAKE_OSX_ARCHITECTURES) + list(APPEND public_header_compiler_args --compiler-arg=-arch "--compiler-arg=${architecture}") + endforeach() + if(CMAKE_OSX_SYSROOT) + list(APPEND public_header_compiler_args --compiler-arg=-isysroot "--compiler-arg=${CMAKE_OSX_SYSROOT}") + endif() + endif() + set(public_header_manifest "${CMAKE_CURRENT_BINARY_DIR}/public_headers.json") + add_test(NAME public_headers COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_public_headers.py --audit-only --manifest "${public_header_manifest}") + set_tests_properties(public_headers PROPERTIES TIMEOUT 30 RUN_SERIAL TRUE FIXTURES_SETUP public_headers_manifest) + foreach(batch RANGE 0 63) + add_test(NAME public_headers_compile_${batch} COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_public_headers.py --compiler ${CMAKE_CXX_COMPILER} ${public_header_compiler_args} + --manifest "${public_header_manifest}" --batch-index ${batch} --batch-count 64) + set_tests_properties(public_headers_compile_${batch} PROPERTIES TIMEOUT 30 FIXTURES_REQUIRED public_headers_manifest) + endforeach() add_test(NAME nid_gnu_hash COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_gnu_hash.py $) add_test(NAME nid_elf_duplicates COMMAND ${Python3_EXECUTABLE} @@ -300,31 +384,126 @@ if(BUILD_TESTING) target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libkernel libc) configure_windows_unwind(guest_kernel_sys_tests) add_test(NAME guest_kernel_sys COMMAND guest_kernel_sys_tests) + add_test_executable(guest_kernel_sys_thread_defaults_tests tests/GuestKernelSysThreadDefaults.cpp) + target_include_directories(guest_kernel_sys_thread_defaults_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_thread_defaults_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_thread_defaults_tests) + add_test(NAME guest_kernel_sys_thread_defaults COMMAND guest_kernel_sys_thread_defaults_tests) + set_tests_properties(guest_kernel_sys_thread_defaults PROPERTIES TIMEOUT 20) + add_test_executable(guest_kernel_sys_barrier_tests tests/GuestKernelSysBarrier.cpp) + target_include_directories(guest_kernel_sys_barrier_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_barrier_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_barrier_tests) + add_test(NAME guest_kernel_sys_barrier COMMAND guest_kernel_sys_barrier_tests) + set_tests_properties(guest_kernel_sys_barrier PROPERTIES TIMEOUT 20) + add_test_executable(guest_kernel_sys_pthread_tests tests/GuestKernelSysPthread.cpp) + target_include_directories(guest_kernel_sys_pthread_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_pthread_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_pthread_tests) + add_test(NAME guest_kernel_sys_pthread COMMAND guest_kernel_sys_pthread_tests) + add_test_executable(guest_kernel_sys_constants_tests tests/GuestKernelSysConstants.cpp) + target_include_directories(guest_kernel_sys_constants_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_constants_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_constants_tests) + add_test(NAME guest_kernel_sys_constants COMMAND guest_kernel_sys_constants_tests) + set_tests_properties(guest_kernel_sys_constants PROPERTIES TIMEOUT 20) + add_test_executable(guest_firmware_constant_returns_tests tests/GuestFirmwareConstantReturns.cpp) + target_include_directories(guest_firmware_constant_returns_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_firmware_constant_returns_tests PRIVATE libSceComposite libSceFsInternalForVsh libSceHttpCache libScePlayReady libSceSdma libSceSsl libSceVisionManager libmdbg_syscore libkernel libc) + configure_windows_unwind(guest_firmware_constant_returns_tests) + add_test(NAME guest_firmware_constant_returns COMMAND guest_firmware_constant_returns_tests) + set_tests_properties(guest_firmware_constant_returns PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_firmware_constant_returns PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() if(Python3_Interpreter_FOUND) add_test(NAME nid_verneed COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_verneed.py $) endif() if(WIN32) - set_tests_properties(guest_kernel_sys PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_kernel_sys guest_kernel_sys_thread_defaults guest_kernel_sys_barrier guest_kernel_sys_pthread guest_kernel_sys_constants PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sys_threads_tests tests/GuestKernelSysThreads.cpp + nid/src/NidCompute.cpp nid/src/Sha1.cpp) + target_include_directories(guest_kernel_sys_threads_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/nid/include) + target_link_libraries(guest_kernel_sys_threads_tests PRIVATE ${CMAKE_DL_LIBS}) + add_custom_target(guest_kernel_sys_threads_libraries DEPENDS + ${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() + 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) + set_tests_properties(guest_kernel_sys_threads_main_late_load guest_kernel_sys_threads_main_late_load_patched PROPERTIES TIMEOUT 20) + endif() + foreach(case IN ITEMS main) + add_test(NAME guest_kernel_sys_threads_${case} COMMAND guest_kernel_sys_threads_tests ${UNPATCHED_DIR} unpatched ${case}) + add_test(NAME guest_kernel_sys_threads_${case}_patched COMMAND guest_kernel_sys_threads_tests ${PATCHED_DIR} patched ${case}) + set_tests_properties(guest_kernel_sys_threads_${case} guest_kernel_sys_threads_${case}_patched PROPERTIES TIMEOUT 20) + endforeach() + + add_test_executable(guest_gnm_neo_packets_tests tests/GuestGnmNeoPackets.cpp) + target_include_directories(guest_gnm_neo_packets_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_gnm_neo_packets_tests PRIVATE libSceGnmDriverForNeoMode libc) + configure_windows_unwind(guest_gnm_neo_packets_tests) + add_test(NAME guest_gnm_neo_packets COMMAND guest_gnm_neo_packets_tests) + if(WIN32) + set_tests_properties(guest_gnm_neo_packets PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(APPLE) add_subdirectory(tests/write_watch) add_subdirectory(tests/guest_arena) endif() + add_test_executable(guest_kernel_sys_module_internal_tests tests/GuestKernelSysModuleInternal.cpp) + target_include_directories(guest_kernel_sys_module_internal_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_module_internal_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_module_internal_tests) + add_test(NAME guest_kernel_sys_module_internal COMMAND guest_kernel_sys_module_internal_tests) + set_tests_properties(guest_kernel_sys_module_internal PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_module_internal PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(WIN32) add_test_executable(windows_write_watch_tests tests/WindowsWriteWatch.cpp) target_include_directories(windows_write_watch_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_compile_definitions(windows_write_watch_tests PRIVATE _WIN32_WINNT=0x0A00) + target_compile_definitions(windows_write_watch_tests PRIVATE _WIN32_WINNT=0x0A00 APS5_COUNT_MAPPING_QUERIES=1) if(MINGW) target_compile_options(windows_write_watch_tests PRIVATE -fasynchronous-unwind-tables) endif() add_test(NAME windows_write_watch COMMAND windows_write_watch_tests) set_tests_properties(windows_write_watch PROPERTIES TIMEOUT 20) + add_test_executable(windows_shared_map_cost_tests tests/WindowsSharedMapCost.cpp) + target_include_directories(windows_shared_map_cost_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_definitions(windows_shared_map_cost_tests PRIVATE _WIN32_WINNT=0x0A00) + if(MINGW) + target_compile_options(windows_shared_map_cost_tests PRIVATE -fasynchronous-unwind-tables) + endif() + add_test(NAME windows_shared_map_cost COMMAND windows_shared_map_cost_tests) + set_tests_properties(windows_shared_map_cost PROPERTIES TIMEOUT 120) + endif() + add_test_executable(guest_kernel_sys_login_tests tests/GuestKernelSysLogin.cpp) + target_include_directories(guest_kernel_sys_login_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_login_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_login_tests) + add_test(NAME guest_kernel_sys_login COMMAND guest_kernel_sys_login_tests) + if(WIN32) + set_tests_properties(guest_kernel_sys_login PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") endif() if(WIN32) add_test_executable(undeliverable_host_exception_tests tests/UndeliverableHostException.cpp) + if(MINGW) + target_compile_options(undeliverable_host_exception_tests PRIVATE -fno-asynchronous-unwind-tables -fno-exceptions) + endif() add_dependencies(undeliverable_host_exception_tests libc) add_test(NAME undeliverable_host_exception COMMAND undeliverable_host_exception_tests $) set_tests_properties(undeliverable_host_exception PROPERTIES @@ -337,6 +516,16 @@ if(BUILD_TESTING) target_compile_options(crash_text_tests PRIVATE -fasynchronous-unwind-tables) endif() add_test(NAME crash_text COMMAND crash_text_tests) + add_test_executable(guest_kernel_sys_pselect_tests tests/GuestKernelSysPselect.cpp) + target_include_directories(guest_kernel_sys_pselect_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_pselect_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_pselect_tests) + add_test(NAME guest_kernel_sys_pselect COMMAND guest_kernel_sys_pselect_tests) + set_tests_properties(guest_kernel_sys_pselect PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_pselect PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(WIN32) add_test_executable(windows_crash_text_tests tests/WindowsCrashText.cpp) @@ -360,6 +549,26 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_gnm_neo_retail_tests tests/GuestGnmNeoRetail.cpp) + target_include_directories(guest_gnm_neo_retail_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_gnm_neo_retail_tests PRIVATE libSceGnmDriverForNeoMode libc) + configure_windows_unwind(guest_gnm_neo_retail_tests) + add_test(NAME guest_gnm_neo_retail COMMAND guest_gnm_neo_retail_tests) + if(WIN32) + set_tests_properties(guest_gnm_neo_retail PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_vr_tracker_devices_tests tests/GuestVrTrackerDevices.cpp) + target_include_directories(guest_vr_tracker_devices_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_vr_tracker_devices_tests PRIVATE libSceVrTracker libc) + configure_windows_unwind(guest_vr_tracker_devices_tests) + add_test(NAME guest_vr_tracker_devices COMMAND guest_vr_tracker_devices_tests) + if(WIN32) + set_tests_properties(guest_vr_tracker_devices PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_move_tests tests/GuestMove.cpp) target_include_directories(guest_move_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_move_tests PRIVATE libSceMove libc) @@ -370,6 +579,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pthread_create_suspended_tests tests/GuestPthreadCreateSuspended.cpp) + target_include_directories(guest_pthread_create_suspended_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_pthread_create_suspended_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_pthread_create_suspended_tests) + add_test(NAME guest_pthread_create_suspended COMMAND guest_pthread_create_suspended_tests) + set_tests_properties(guest_pthread_create_suspended PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_create_suspended PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_np_party_tests tests/GuestNpParty.cpp) target_include_directories(guest_np_party_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_np_party_tests PRIVATE libSceNpParty libc) @@ -380,9 +600,43 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_np_ps4_imports_tests tests/GuestNpPs4Imports.cpp) + target_include_directories(guest_np_ps4_imports_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_ps4_imports_tests PRIVATE libSceNpMatching2 libSceNpSignaling libSceNpTrophy libc) + configure_windows_unwind(guest_np_ps4_imports_tests) + add_test(NAME guest_np_ps4_imports COMMAND guest_np_ps4_imports_tests) + if(WIN32) + set_tests_properties(guest_np_ps4_imports PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_np_common_tests tests/GuestNpCommon.cpp) + target_include_directories(guest_np_common_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_common_tests PRIVATE libSceNpCommon libkernel libc) + configure_windows_unwind(guest_np_common_tests) + foreach(testCase IN ITEMS ids sync clock) + add_test(NAME guest_np_common_${testCase} COMMAND guest_np_common_tests ${testCase}) + set_tests_properties(guest_np_common_${testCase} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_common_${testCase} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + + add_test_executable(guest_np_common_runtime_tests tests/GuestNpCommonRuntime.cpp) + target_include_directories(guest_np_common_runtime_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_common_runtime_tests PRIVATE libSceNpCommon libkernel libc) + configure_windows_unwind(guest_np_common_runtime_tests) + add_test(NAME guest_np_common_runtime COMMAND guest_np_common_runtime_tests) + set_tests_properties(guest_np_common_runtime PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_common_runtime PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_offline_imports_tests tests/GuestOfflineImports.cpp) target_include_directories(guest_offline_imports_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(guest_offline_imports_tests PRIVATE libSceHttp libSceNpEntitlementAccess libSceRudp libc) + target_link_libraries(guest_offline_imports_tests PRIVATE libSceHttp libSceNpEntitlementAccess libSceRudp libSceVideoRecordingP libc) configure_windows_unwind(guest_offline_imports_tests) add_test(NAME guest_offline_imports COMMAND guest_offline_imports_tests) if(Python3_Interpreter_FOUND) @@ -394,6 +648,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_entitlements_tests tests/GuestEntitlements.cpp) + target_include_directories(guest_entitlements_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_entitlements_tests PRIVATE libSceNpEntitlementAccess libc) + configure_windows_unwind(guest_entitlements_tests) + add_test(NAME guest_entitlements COMMAND guest_entitlements_tests) + add_test(NAME guest_entitlements_missing COMMAND guest_entitlements_tests missing) + if(WIN32) + set_tests_properties(guest_entitlements guest_entitlements_missing PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_signin_dialog_tests tests/GuestSigninDialog.cpp) target_include_directories(guest_signin_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_signin_dialog_tests PRIVATE libSceSigninDialog libc) @@ -406,7 +671,7 @@ if(BUILD_TESTING) add_test_executable(guest_usb_storage_dialog_tests tests/GuestUsbStorageDialog.cpp) target_include_directories(guest_usb_storage_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(guest_usb_storage_dialog_tests PRIVATE libSceUsbStorageDialog libc) + target_link_libraries(guest_usb_storage_dialog_tests PRIVATE libSceUsbStorageDialog libSceUserService libc) configure_windows_unwind(guest_usb_storage_dialog_tests) add_test(NAME guest_usb_storage_dialog COMMAND guest_usb_storage_dialog_tests) if(WIN32) @@ -593,6 +858,26 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_np_cpp_web_api_signed_out_tests tests/GuestNpCppWebApiSignedOut.cpp) + target_include_directories(guest_np_cpp_web_api_signed_out_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_cpp_web_api_signed_out_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_signed_out_tests) + add_test(NAME guest_np_cpp_web_api_signed_out COMMAND guest_np_cpp_web_api_signed_out_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_signed_out PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_np_cpp_web_api_offline_tests tests/GuestNpCppWebApiOffline.cpp) + target_include_directories(guest_np_cpp_web_api_offline_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_cpp_web_api_offline_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_offline_tests) + add_test(NAME guest_np_cpp_web_api_offline COMMAND guest_np_cpp_web_api_offline_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_offline PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(savedata_core_write_replacement_tests tests/SaveDataWriteReplacement.cpp) target_include_directories(savedata_core_write_replacement_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(savedata_core_write_replacement_tests PRIVATE libSceSaveData) @@ -611,6 +896,17 @@ if(BUILD_TESTING) configure_windows_unwind(savedata_replace_file_failure_tests) add_test(NAME savedata_replace_file_failure COMMAND savedata_replace_file_failure_tests) + add_test_executable(guest_kernel_sys_ktimer_tests tests/GuestKernelSysKtimer.cpp) + target_include_directories(guest_kernel_sys_ktimer_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_ktimer_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_ktimer_tests) + add_test(NAME guest_kernel_sys_ktimer COMMAND guest_kernel_sys_ktimer_tests) + set_tests_properties(guest_kernel_sys_ktimer PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_ktimer PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_crt_tests tests/GuestCrt.cpp) target_include_directories(guest_crt_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_crt_tests PRIVATE libSceAmpr libc) @@ -633,6 +929,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sys_aio_tests tests/GuestKernelSysAio.cpp) + target_include_directories(guest_kernel_sys_aio_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_aio_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_aio_tests) + add_test(NAME guest_kernel_sys_aio COMMAND guest_kernel_sys_aio_tests) + set_tests_properties(guest_kernel_sys_aio PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_aio PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_posix_random_tests tests/GuestPosixRandom.cpp) target_include_directories(guest_posix_random_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_posix_random_tests PRIVATE libScePosixForWebKit) @@ -653,6 +960,18 @@ if(BUILD_TESTING) endif() add_test(NAME MemoryPoolTest COMMAND MemoryPoolTest) + add_test_executable(guest_kernel_sys_swapcontext_tests tests/GuestKernelSysSwapcontext.cpp) + target_include_directories(guest_kernel_sys_swapcontext_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_options(guest_kernel_sys_swapcontext_tests PRIVATE -fno-omit-frame-pointer) + target_link_libraries(guest_kernel_sys_swapcontext_tests PRIVATE libkernel_sys libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_kernel_sys_swapcontext_tests) + add_test(NAME guest_kernel_sys_swapcontext COMMAND guest_kernel_sys_swapcontext_tests) + set_tests_properties(guest_kernel_sys_swapcontext PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_swapcontext PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_acm_tests tests/GuestAcm.cpp) target_include_directories(guest_acm_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_acm_tests PRIVATE libSceAcm libc) @@ -673,6 +992,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_gnm_neo_embedded_tests tests/GuestGnmNeoEmbedded.cpp) + target_include_directories(guest_gnm_neo_embedded_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_gnm_neo_embedded_tests PRIVATE libSceGnmDriverForNeoMode libc) + configure_windows_unwind(guest_gnm_neo_embedded_tests) + add_test(NAME guest_gnm_neo_embedded COMMAND guest_gnm_neo_embedded_tests) + if(WIN32) + set_tests_properties(guest_gnm_neo_embedded PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_np_game_intent_tests tests/GuestNpGameIntent.cpp) target_include_directories(guest_np_game_intent_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_np_game_intent_tests PRIVATE libSceNpGameIntent libc) @@ -683,13 +1012,35 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_gnm_neo_shaders_tests tests/GuestGnmNeoShaders.cpp) + target_include_directories(guest_gnm_neo_shaders_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_gnm_neo_shaders_tests PRIVATE libSceGnmDriverForNeoMode libc) + configure_windows_unwind(guest_gnm_neo_shaders_tests) + add_test(NAME guest_gnm_neo_shaders COMMAND guest_gnm_neo_shaders_tests) + if(WIN32) + set_tests_properties(guest_gnm_neo_shaders PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_vorbis_dec_tests tests/GuestVorbisDec.cpp) target_include_directories(guest_vorbis_dec_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(guest_vorbis_dec_tests PRIVATE libSceVorbisDec libc) + target_compile_definitions(guest_vorbis_dec_tests PRIVATE OV_EXCLUDE_STATIC_CALLBACKS) + target_link_libraries(guest_vorbis_dec_tests PRIVATE libSceVorbisDec vorbisenc vorbisfile vorbis ogg libc) configure_windows_unwind(guest_vorbis_dec_tests) add_test(NAME guest_vorbis_dec COMMAND guest_vorbis_dec_tests) + set_tests_properties(guest_vorbis_dec PROPERTIES TIMEOUT 30) if(WIN32) set_tests_properties(guest_vorbis_dec PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_gnm_neo_default_state_tests tests/GuestGnmNeoDefaultState.cpp) + target_include_directories(guest_gnm_neo_default_state_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_gnm_neo_default_state_tests PRIVATE libSceGnmDriverForNeoMode libc) + configure_windows_unwind(guest_gnm_neo_default_state_tests) + add_test(NAME guest_gnm_neo_default_state COMMAND guest_gnm_neo_default_state_tests) + if(WIN32) + set_tests_properties(guest_gnm_neo_default_state PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 4f8d8e8fd7..0eb23cef6d 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -333,7 +333,7 @@ struct VaList { }; #pragma pack(pop) -using FiberEntry = void* (*)(std::uint64_t, void*); +using FiberEntry = void (APS5_VABI*)(std::uint64_t argOnInitialize, std::uint64_t argOnRun); struct FiberOptParam { std::uint32_t magic; @@ -432,6 +432,48 @@ struct AcmBatchError { std::uint32_t reserved[8]; }; +struct AcmReverbBuffer { + void* samples; + const void* blockMetadata; +}; + +struct AcmReverbInput { + std::uint32_t blockSize; + std::uint32_t blockCount; + std::uint32_t channelCount; + std::uint32_t sampleFormat; + std::uint32_t currentBlock; + std::uint32_t availableBlocks; + const AcmReverbBuffer* const* history; + const void* const* inputChannels; + std::uint32_t pendingBlocks; +}; + +struct AcmReverbFilter { + std::uint32_t blockSize; + std::uint32_t blockCount; + std::uint32_t channelCount; + std::uint32_t sampleFormat; + const void* blockMetadata; + const AcmReverbBuffer* const* channels; +}; + +struct AcmReverbOutput { + std::uint32_t blockSize; + std::uint32_t channelCount; + void* const* samples; + void* const* workspace; + float gain; + float previousGain; + void* const* previousSamples; +}; + +static_assert(sizeof(AcmReverbBuffer) == 16); +static_assert(offsetof(AcmReverbInput, history) == 24); +static_assert(offsetof(AcmReverbInput, pendingBlocks) == 40); +static_assert(sizeof(AcmReverbFilter) == 32); +static_assert(offsetof(AcmReverbOutput, previousSamples) == 32); + struct AcmBatchInfo { void* buffer; std::size_t offset; @@ -613,6 +655,13 @@ struct AudioOutPortState { std::uint64_t activeState; }; +struct AudioOutSystemState { + float loudness; + std::uint8_t reserved8[4]; + std::uint64_t reserved[3]; +}; +static_assert(sizeof(AudioOutSystemState) == 32); + struct Audio3dOpenParameters { std::uint64_t size_this; std::uint32_t granularity; @@ -933,13 +982,60 @@ struct AudiodecM4aacInfo { std::int32_t i_result; }; +struct VoiceEvent { + std::int32_t type; + std::uint32_t reserved; + void* userData; + union { + struct { + std::uint16_t count; + std::uint16_t ports[7]; + } dataReady; + struct { + std::uint16_t port; + std::uint8_t state; + std::uint8_t reserved[13]; + } deviceState; + }; +}; + +using VoiceEventHandler = void (APS5_VABI*)(const VoiceEvent* event); + +struct VoiceThreadsParams { + std::uint32_t mode; + std::int32_t firstPriority; + std::uint64_t firstAffinity; + std::int32_t secondPriority; + std::uint32_t reserved20; + std::uint64_t secondAffinity; + std::int32_t thirdPriority; + std::uint32_t reserved36; + std::uint64_t thirdAffinity; +}; + +static_assert(sizeof(VoiceEvent) == 32); +static_assert(offsetof(VoiceEvent, userData) == 8); +static_assert(offsetof(VoiceEvent, dataReady) == 16); +static_assert(offsetof(VoiceEvent, deviceState) == 16); +static_assert(sizeof(VoiceThreadsParams) == 48); +static_assert(offsetof(VoiceThreadsParams, firstAffinity) == 8); +static_assert(offsetof(VoiceThreadsParams, secondPriority) == 16); +static_assert(offsetof(VoiceThreadsParams, secondAffinity) == 24); +static_assert(offsetof(VoiceThreadsParams, thirdPriority) == 32); +static_assert(offsetof(VoiceThreadsParams, thirdAffinity) == 40); + struct VoiceInitParam { std::int32_t app_type; - std::uint64_t on_event; + VoiceEventHandler on_event; void* user_data; - std::uint8_t reserved[32 - sizeof(std::int32_t) - sizeof(std::uint64_t) - sizeof(void*)]; + std::uint8_t reserved[16]; }; +static_assert(sizeof(VoiceInitParam) == 40); +static_assert(offsetof(VoiceInitParam, on_event) == 8); +static_assert(offsetof(VoiceInitParam, user_data) == 16); +static_assert(offsetof(VoiceInitParam, reserved) == 24); + struct VoicePortParam { std::int32_t port_type; std::uint16_t threshold; @@ -962,14 +1058,30 @@ struct VoicePortInfo { std::uint16_t reserved; }; -struct VoiceResourceInfo { - std::uint16_t max_voice_input_ports; - std::uint16_t max_voice_output_ports; - std::uint16_t max_device_input_ports; - std::uint16_t max_device_output_ports; - std::uint16_t max_ports; +struct NpAuthClientId; + +struct NpAuthAuthorizedAppCodeParameter { + std::uint64_t size; + std::int32_t userId; + const NpAuthClientId* clientId; + const char* scope; + std::uint32_t option; }; +static_assert(sizeof(NpAuthAuthorizedAppCodeParameter) == 40); +static_assert(offsetof(NpAuthAuthorizedAppCodeParameter, clientId) == 16); +static_assert(offsetof(NpAuthAuthorizedAppCodeParameter, scope) == 24); +static_assert(offsetof(NpAuthAuthorizedAppCodeParameter, option) == 32); + +struct VoiceResourceInfo { + std::uint16_t maxVoiceInputPorts; + std::uint16_t maxVoiceOutputPorts; + std::uint16_t maxDeviceInputPorts; + std::uint16_t maxDeviceOutputPorts; +}; + +static_assert(sizeof(VoiceResourceInfo) == 8); + struct VoiceStartParam { void* container; std::uint32_t mem_size; @@ -1155,8 +1267,8 @@ struct ImeKeycode { std::uint64_t timestamp; }; -using ImeTextFilter = std::int32_t (APS5_VABI *)(char16_t* out_text, std::uint32_t* out_text_length, const char16_t* source_text, std::uint32_t source_text_length); -using ImeExtKeyboardFilter = int (APS5_VABI *)(const ImeKeycode* source_keycode, std::uint16_t* out_keycode, std::uint32_t* out_status, void* reserved); +using ImeTextFilter = std::int32_t (APS5_VABI*)(char16_t* outText, std::uint32_t* outTextLength, const char16_t* sourceText, std::uint32_t sourceTextLength); +using ImeExtKeyboardFilter = int (APS5_VABI*)(const ImeKeycode* sourceKeycode, std::uint16_t* outKeycode, std::uint32_t* outStatus, void* reserved); struct ImeExtendedParam { std::uint32_t option; @@ -1318,7 +1430,12 @@ struct NetEpollEvent { NetEpollData data; }; -struct NetCtlNatInfo { std::uint8_t opaque[128]; }; +struct NetCtlNatInfo { + std::uint32_t size; + std::uint8_t opaque[12]; +}; + +static_assert(sizeof(NetCtlNatInfo) == 16); union NetCtlInfo { std::uint8_t opaque[256]; }; @@ -1333,10 +1450,10 @@ using NetCtlCallback = void (APS5_VABI *)(int, void*); struct HttpEpoll; using HttpEpollHandle = HttpEpoll*; -using HttpsCallback = int (*)(int, unsigned int, void* const*, int, void*); -using HttpRedirectCallback = int (*)(int, std::int32_t, std::int32_t*, const char*, void*); -using HttpCookieRecvCallback = int (*)(int, const char*, const char*, std::uint64_t, void*); -using HttpAuthInfoCallback = int (*)(int, int, const char*, char*, char*, int, std::uint8_t**, std::uint64_t*, int*, void*); +using HttpsCallback = int (APS5_VABI*)(int, unsigned int, void* const*, int, void*); +using HttpRedirectCallback = int (APS5_VABI*)(int, std::int32_t, std::int32_t*, const char*, void*); +using HttpCookieRecvCallback = int (APS5_VABI*)(int, const char*, const char*, std::uint64_t, void*); +using HttpAuthInfoCallback = int (APS5_VABI*)(int, int, const char*, char*, char*, int, std::uint8_t**, std::uint64_t*, int*, void*); struct HttpNBEvent { std::uint8_t opaque[64]; }; @@ -1385,6 +1502,15 @@ struct SharePlayConnectionInfoA { std::int32_t visitorUserId; }; static_assert(sizeof(SharePlayConnectionInfoA) == 72 && offsetof(SharePlayConnectionInfoA, hostAccountId) == 48 && offsetof(SharePlayConnectionInfoA, hostUserId) == 64); +struct SharePlayConnectionInfo { + std::int32_t status; + std::int32_t mode; + NpOnlineId hostOnlineId; + NpOnlineId visitorOnlineId; + std::int32_t hostUserId; + std::int32_t visitorUserId; +}; +static_assert(sizeof(SharePlayConnectionInfo) == 56 && offsetof(SharePlayConnectionInfo, hostUserId) == 48); struct NpId { NpOnlineId online_id; std::uint8_t opaque[4]; }; struct NpCreateAsyncRequestParameter { std::uint8_t opaque[64]; }; struct NpCheckPremiumParameter { std::uint8_t opaque[64]; }; @@ -1395,6 +1521,24 @@ struct NpUnifiedEntitlementLabel { char padding[3]; }; +struct NpServiceEntitlementLabel { + char data[7]; + std::uint8_t reserved[13]; +}; + +struct NpEntitlementAccessTransactionId { + char data[65]; + std::uint8_t reserved[7]; +}; + +struct NpEntitlementAccessListParameter; +struct NpEntitlementAccessUnifiedInfo; +struct NpEntitlementAccessServiceInfo; + +static_assert(sizeof(NpUnifiedEntitlementLabel) == 20); +static_assert(sizeof(NpServiceEntitlementLabel) == 20); +static_assert(sizeof(NpEntitlementAccessTransactionId) == 72); + struct NpEntitlementAccessInitParam { char reserved[32]; }; struct NpEntitlementAccessBootParam { char reserved[32]; }; @@ -1690,6 +1834,34 @@ struct AppContentBootParam { struct AppContentMountPoint { char data[16]; }; +using ContentExportMallocCallback = void* (APS5_VABI *)(std::size_t size, void* userData); +using ContentExportFreeCallback = void (APS5_VABI *)(void* memory, void* userData); +using ContentExportDataCallback = std::int32_t (APS5_VABI *)(const void** data, std::size_t* size, void* userData); + +struct ContentExportInitParam { + ContentExportMallocCallback mallocFunc; + ContentExportFreeCallback freeFunc; + void* userData; +}; + +static_assert(sizeof(ContentExportInitParam) == 24); +static_assert(offsetof(ContentExportInitParam, freeFunc) == 8); +static_assert(offsetof(ContentExportInitParam, userData) == 16); + +struct ContentExportSfParam { + std::size_t size; + const char* metadata; + const std::int64_t* contentIds; + std::uint32_t contentCount; + std::uint32_t option; +}; + +static_assert(sizeof(ContentExportSfParam) == 32); +static_assert(offsetof(ContentExportSfParam, metadata) == 8); +static_assert(offsetof(ContentExportSfParam, contentIds) == 16); +static_assert(offsetof(ContentExportSfParam, contentCount) == 24); +static_assert(offsetof(ContentExportSfParam, option) == 28); + struct ContentExportInitParam2 { void* malloc_func; void* free_func; @@ -1897,11 +2069,22 @@ union PlayGoOptionalChunk { }; struct PlayGoDialogResult { + std::int32_t errorCode; std::int32_t result; - std::int32_t reserved[9]; + std::uint8_t reserved[32]; }; -using RudpEventHandler = void (*)(int ctx_id, int event_id, int error_code, void* arg); +struct RudpPollEvent { + std::int32_t contextId; + std::uint16_t events; + std::uint16_t returnedEvents; +}; + +static_assert(sizeof(RudpPollEvent) == 8); +static_assert(offsetof(RudpPollEvent, events) == 4); +static_assert(offsetof(RudpPollEvent, returnedEvents) == 6); + +using RudpEventHandler = void (APS5_VABI*)(int ctxId, int eventId, int errorCode, void* arg); struct SystemServiceStatus { std::int32_t event_num = 0; @@ -1998,12 +2181,16 @@ struct UserServiceGamePresets { std::uint32_t audio_language; }; -struct UltMutexOptParam { - std::uint32_t reserved_header[2]; +struct alignas(8) UltMutexOptParam { + std::uint32_t header[3]; + std::uint32_t buildVersion; std::uint32_t attribute; - std::uint32_t reserved0; + std::uint32_t reserved[27]; }; +static_assert(sizeof(UltMutexOptParam) == 128); +static_assert(offsetof(UltMutexOptParam, attribute) == 16); + struct UltUlthreadRuntimeOptParam { std::uint8_t bytes[128]; }; using UltUlthreadEntry = std::int32_t (APS5_VABI *)(std::uint64_t); diff --git a/core/libs/allocator/PeImportFirst.cpp b/core/libs/allocator/PeImportFirst.cpp index 8e1ee93454..aa4b040ba2 100644 --- a/core/libs/allocator/PeImportFirst.cpp +++ b/core/libs/allocator/PeImportFirst.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -78,16 +79,16 @@ void MoveImportFirst(std::vector& bytes, const std::string& dll) for (std::size_t index = 0; index < descriptors.size(); ++index) std::memcpy(bytes.data() + directory + index * DescriptorBytes, descriptors[index].data(), DescriptorBytes); } -void MoveImportFirstInFile(const std::string& path, const std::string& dll) { +void MoveImportFirstInFile(const std::filesystem::path& path, const std::string& dll) { std::vector bytes; { std::ifstream input(path, std::ios::binary); - if (!input) throw std::runtime_error("cannot open " + path); + if (!input) throw std::runtime_error("cannot open " + path.string()); bytes.assign(std::istreambuf_iterator(input), std::istreambuf_iterator()); } MoveImportFirst(bytes, dll); std::ofstream output(path, std::ios::binary | std::ios::trunc); - if (!output || !output.write(reinterpret_cast(bytes.data()), static_cast(bytes.size()))) throw std::runtime_error("cannot write " + path); + if (!output || !output.write(reinterpret_cast(bytes.data()), static_cast(bytes.size()))) throw std::runtime_error("cannot write " + path.string()); } } diff --git a/core/libs/allocator/PeImportFirst.hpp b/core/libs/allocator/PeImportFirst.hpp index 30aa18cf7d..54db5101fc 100644 --- a/core/libs/allocator/PeImportFirst.hpp +++ b/core/libs/allocator/PeImportFirst.hpp @@ -1,13 +1,14 @@ #ifndef CORE_LIBS_ALLOCATOR_PEIMPORTFIRST_HPP #define CORE_LIBS_ALLOCATOR_PEIMPORTFIRST_HPP +#include #include #include namespace Allocator { void MoveImportFirst(std::vector& image, const std::string& dll); -void MoveImportFirstInFile(const std::string& path, const std::string& dll); +void MoveImportFirstInFile(const std::filesystem::path& path, const std::string& dll); } diff --git a/core/libs/allocator/PeImportFirstMain.cpp b/core/libs/allocator/PeImportFirstMain.cpp index 02ace8e22e..359c357674 100644 --- a/core/libs/allocator/PeImportFirstMain.cpp +++ b/core/libs/allocator/PeImportFirstMain.cpp @@ -2,17 +2,37 @@ #include #include +#include +#include -int main(int argc, char** argv) { - if (argc != 3) { - std::fprintf(stderr, "usage: pe_import_first \n"); - return 2; - } +namespace { + +int Usage() { + std::fprintf(stderr, "usage: pe_import_first \n"); + return 2; +} + +int Run(const std::filesystem::path& file, const std::string& dll) { try { - Allocator::MoveImportFirstInFile(argv[1], argv[2]); + Allocator::MoveImportFirstInFile(file, dll); } catch (const std::exception& error) { - std::fprintf(stderr, "pe_import_first: %s: %s\n", argv[1], error.what()); + std::fprintf(stderr, "pe_import_first: %s: %s\n", file.string().c_str(), error.what()); return 1; } return 0; } + +} + +#ifdef _WIN32 +int wmain(int argc, wchar_t** argv) { + if (argc != 3) return Usage(); + const auto dll = std::filesystem::path(argv[2]).u8string(); + return Run(argv[1], std::string(dll.begin(), dll.end())); +} +#else +int main(int argc, char** argv) { + if (argc != 3) return Usage(); + return Run(argv[1], argv[2]); +} +#endif diff --git a/core/libs/allocator/tests/PeImportFirstTests.cpp b/core/libs/allocator/tests/PeImportFirstTests.cpp index 4df8aceed5..5d40857cdc 100644 --- a/core/libs/allocator/tests/PeImportFirstTests.cpp +++ b/core/libs/allocator/tests/PeImportFirstTests.cpp @@ -113,7 +113,7 @@ void movesTheNamedImportFirst() { void movesTheImportInAFile(const std::filesystem::path& directory) { const auto path = directory / "moved.dll"; writeFile(path, image({"kernel32.dll", "mimalloc-redirect.dll"})); - Allocator::MoveImportFirstInFile(path.string(), "mimalloc-redirect.dll"); + Allocator::MoveImportFirstInFile(path, "mimalloc-redirect.dll"); const auto moved = readFile(path); require(importName(moved, 0) == "mimalloc-redirect.dll" && importName(moved, 1) == "kernel32.dll", "the file's import did not move first"); } @@ -122,7 +122,7 @@ void failsWithoutTouchingTheFile(const std::filesystem::path& directory, const s const auto path = directory / (what + ".dll"); writeFile(path, bytes); try { - Allocator::MoveImportFirstInFile(path.string(), "mimalloc-redirect.dll"); + Allocator::MoveImportFirstInFile(path, "mimalloc-redirect.dll"); } catch (const std::exception& error) { require(std::string(error.what()).find(expected) != std::string::npos, what + ": failed with another error: " + error.what()); require(readFile(path) == bytes, what + ": the file changed although the move failed"); @@ -134,7 +134,8 @@ void failsWithoutTouchingTheFile(const std::filesystem::path& directory, const s } int main() { - const auto directory = std::filesystem::temp_directory_path() / ("aps5-pe-import-first-" + std::to_string(std::random_device{}())); + auto directory = std::filesystem::temp_directory_path() / std::filesystem::path(u8"aps5-pe-import-first-a\u00e7\u00e3o-\u65e5\u672c-"); + directory += std::to_string(std::random_device{}()); try { std::filesystem::create_directories(directory); movesTheNamedImportFirst(); diff --git a/core/libs/nid/include/nid/ElfPatcher.hpp b/core/libs/nid/include/nid/ElfPatcher.hpp index 0415b4eb7f..f6887aae64 100644 --- a/core/libs/nid/include/nid/ElfPatcher.hpp +++ b/core/libs/nid/include/nid/ElfPatcher.hpp @@ -22,6 +22,17 @@ struct Elf64_Ehdr { std::uint16_t e_shstrndx; }; +struct Elf64_Phdr { + std::uint32_t p_type; + std::uint32_t p_flags; + std::uint64_t p_offset; + std::uint64_t p_vaddr; + std::uint64_t p_paddr; + std::uint64_t p_filesz; + std::uint64_t p_memsz; + std::uint64_t p_align; +}; + struct Elf64_Shdr { std::uint32_t sh_name; std::uint32_t sh_type; @@ -103,7 +114,7 @@ constexpr std::uint16_t kShnUndef = 0u; class ElfNidPatcher final : public IBinaryPatcher { public: - void PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports) const override; + void PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports, const PatchOptions& options = {}) const override; }; } diff --git a/core/libs/nid/include/nid/IBinaryPatcher.hpp b/core/libs/nid/include/nid/IBinaryPatcher.hpp index 0fbcb88501..4e8cff5cbc 100644 --- a/core/libs/nid/include/nid/IBinaryPatcher.hpp +++ b/core/libs/nid/include/nid/IBinaryPatcher.hpp @@ -8,11 +8,16 @@ namespace Nid { +struct PatchOptions { + bool executable = false; + bool onlySce = false; +}; + class IBinaryPatcher { public: virtual ~IBinaryPatcher() = default; - virtual void PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports) const = 0; + virtual void PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports, const PatchOptions& options = {}) const = 0; }; } diff --git a/core/libs/nid/include/nid/MachONidPatcher.hpp b/core/libs/nid/include/nid/MachONidPatcher.hpp index f49732cd88..f6ae370549 100644 --- a/core/libs/nid/include/nid/MachONidPatcher.hpp +++ b/core/libs/nid/include/nid/MachONidPatcher.hpp @@ -22,7 +22,7 @@ std::vector ReadMachOExports(const std::vector& binar class MachONidPatcher final : public IBinaryPatcher { public: - void PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports) const override; + void PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports, const PatchOptions& options = {}) const override; }; } diff --git a/core/libs/nid/include/nid/NidResolver.hpp b/core/libs/nid/include/nid/NidResolver.hpp index df144b4519..9822939e4e 100644 --- a/core/libs/nid/include/nid/NidResolver.hpp +++ b/core/libs/nid/include/nid/NidResolver.hpp @@ -1,6 +1,7 @@ #ifndef NID_NIDRESOLVER_HPP #define NID_NIDRESOLVER_HPP +#include #include #include #include @@ -8,6 +9,10 @@ namespace Nid { +bool IsSceImport(const std::string& name); + +std::unordered_map ResolveImportNids(const std::vector& names, const std::unordered_set& excluded, const PatchOptions& options); + std::string ResolveOneName(const std::string& funcName); std::unordered_map ResolveNids(const std::vector& exportedNames, const std::string& libraryName, const std::unordered_set& excludedExports); diff --git a/core/libs/nid/include/nid/PeNidPatcher.hpp b/core/libs/nid/include/nid/PeNidPatcher.hpp index c674c3a80b..d9d14bda4b 100644 --- a/core/libs/nid/include/nid/PeNidPatcher.hpp +++ b/core/libs/nid/include/nid/PeNidPatcher.hpp @@ -39,7 +39,7 @@ struct PeSectionHeader { class PeNidPatcher final : public IBinaryPatcher { public: - void PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports) const override; + void PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports, const PatchOptions& options = {}) const override; }; } diff --git a/core/libs/nid/src/ElfNidPatcher.cpp b/core/libs/nid/src/ElfNidPatcher.cpp index 089bcd7107..2e06bc5431 100644 --- a/core/libs/nid/src/ElfNidPatcher.cpp +++ b/core/libs/nid/src/ElfNidPatcher.cpp @@ -354,7 +354,7 @@ std::vector CollectVerneedNameOffsets(const std::vector& elf, const std::string& libraryName, const std::unordered_set& excludedExports) const { +void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& libraryName, const std::unordered_set& excludedExports, const PatchOptions& options) const { using namespace Internal; if (elf.size() < sizeof(Elf64_Ehdr)) throw std::runtime_error("file too small"); @@ -370,11 +370,13 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& std::size_t sysvHashOffset = 0u, sysvHashSize = 0u; std::uint32_t sysvHashLink = 0u; std::size_t versymOffset = 0u; + bool hasDynamic = false; std::uint32_t dynSymSectionIndex = 0u; std::uint32_t gnuHashSymOffset = 0u; for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) { const std::size_t shOffset = static_cast(ehdr.e_shoff) + i * sizeof(Elf64_Shdr); const auto shdr = Read(elf, shOffset); + if (shdr.sh_type == kShtDynamic) hasDynamic = true; if (shdr.sh_type == kShtDynsym) { if (shdr.sh_entsize != sizeof(Elf64_Sym) || shdr.sh_size % sizeof(Elf64_Sym) || shdr.sh_link >= ehdr.e_shnum) throw std::runtime_error("invalid ELF dynsym layout"); @@ -398,6 +400,19 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& throw std::runtime_error("unsupported .gnu.version_d section: symbol version definitions cannot be preserved"); } + if (options.executable && dynSymOffset == 0u && !hasDynamic) { + if (ehdr.e_phnum) { + if (!ehdr.e_phoff || ehdr.e_phentsize != sizeof(Elf64_Phdr) || ehdr.e_phnum == 0xffffu) + throw std::runtime_error("unsupported ELF program header layout"); + RequireElfRange(elf, ehdr.e_phoff, std::uint64_t{ehdr.e_phnum} * sizeof(Elf64_Phdr), "program headers"); + for (std::uint16_t index = 0u; index < ehdr.e_phnum; ++index) { + const auto segment = Read(elf, ehdr.e_phoff + index * sizeof(Elf64_Phdr)); + if (segment.p_type == 2u) + throw std::runtime_error("unsupported ELF PT_DYNAMIC without a .dynsym section"); + } + } + return; + } if (dynSymOffset == 0u) throw std::runtime_error("no .dynsym section"); const std::size_t symCount = dynSymSize / sizeof(Elf64_Sym); @@ -437,19 +452,26 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& elf.begin() + static_cast(dynStrOffset + dynStrSize) ); + std::vector importedNames; std::vector exportedNames; for (std::size_t i = 1u; i < symCount; ++i) { const std::size_t symOffset = dynSymOffset + i * sizeof(Elf64_Sym); const auto sym = Read(elf, symOffset); if (sym.st_name == 0u) continue; const std::uint8_t binding = sym.st_info >> 4u; - if (binding == kStbLocal || sym.st_shndx == kShnUndef) continue; + if (binding == kStbLocal) continue; + if (sym.st_shndx == kShnUndef) { + const auto name = ReadCStr(origDynStr, sym.st_name); + if (!name.empty()) importedNames.push_back(name); + continue; + } const std::string symName = ReadCStr(origDynStr, sym.st_name); if (symName.empty()) continue; exportedNames.push_back(symName); } - const auto nidMap = ResolveNids(exportedNames, libraryName, excludedExports); + const auto nidMap = options.executable ? std::unordered_map{} : ResolveNids(exportedNames, libraryName, excludedExports); + const auto importMap = ResolveImportNids(importedNames, excludedExports, options); struct SymbolNameUse { std::size_t symIndex; @@ -481,14 +503,11 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& } else if (binding != kStbLocal && sym.st_shndx == kShnUndef) { const bool isUnversioned = versymOffset == 0u || (Read(elf, versymOffset + i * sizeof(std::uint16_t)) <= 1u); - const bool hasNidPostfix = symName.size() >= kNidPostfixLen && - symName.compare(symName.size() - kNidPostfixLen, kNidPostfixLen, kNidPostfix) == 0; - const bool hasScePrefix = symName.size() >= 3u && - std::tolower(static_cast(symName[0])) == 's' && - std::tolower(static_cast(symName[1])) == 'c' && - std::tolower(static_cast(symName[2])) == 'e'; - if (isUnversioned && (hasNidPostfix || hasScePrefix)) - newValue = ResolveOneName(symName); + if (options.executable || isUnversioned) { + newValue = importMap.at(symName); + if (options.executable && newValue != symName && versymOffset != 0u) + Write(elf, versymOffset + i * sizeof(std::uint16_t), std::uint16_t{1}); + } } uses.push_back(SymbolNameUse{i, sym.st_name, newValue}); @@ -538,11 +557,69 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& for (const auto offset : preservedOffsets) preservedToNewOffset.emplace_back(offset, valueOffsets.at(ReadCStr(origDynStr, offset))); + if (newDynStr.size() > dynStrSize && options.executable) { + if (ehdr.e_phentsize != sizeof(Elf64_Phdr) || ehdr.e_phnum == 0 || ehdr.e_phnum >= 0xfffdu) + throw std::runtime_error("invalid ELF program header table for string growth"); + RequireElfRange(elf, ehdr.e_phoff, std::uint64_t{ehdr.e_phnum} * sizeof(Elf64_Phdr), "program headers"); + const auto align = [](std::uint64_t value) { + if (value > UINT64_MAX - 4095u) throw std::runtime_error("ELF address overflow"); + return (value + 4095u) & ~std::uint64_t{4095u}; + }; + std::vector segments; + std::uint64_t virtualEnd = 0; + bool hasPhdr = false; + for (std::size_t index = 0; index < ehdr.e_phnum; ++index) { + const auto segment = Read(elf, ehdr.e_phoff + index * sizeof(Elf64_Phdr)); + segments.push_back(segment); + hasPhdr |= segment.p_type == 6; + if (segment.p_type != 1) continue; + if (segment.p_memsz > UINT64_MAX - segment.p_vaddr) throw std::runtime_error("ELF segment address overflow"); + virtualEnd = std::max(virtualEnd, segment.p_vaddr + segment.p_memsz); + } + if (!virtualEnd) throw std::runtime_error("ELF executable has no load segment"); + if (!hasPhdr) segments.insert(segments.begin(), Elf64_Phdr{6, 4, 0, 0, 0, 0, 0, 8}); + const auto fileOffset = align(elf.size()); + const auto address = align(virtualEnd); + const auto tableSize = (segments.size() + 1u) * sizeof(Elf64_Phdr); + const auto segmentSize = tableSize + newDynStr.size(); + if (segmentSize > UINT64_MAX - address || segmentSize > SIZE_MAX - fileOffset) throw std::runtime_error("ELF string segment overflow"); + for (auto& segment : segments) { + if (segment.p_type != 6) continue; + segment = {6, 4, fileOffset, address, address, tableSize, tableSize, 8}; + } + segments.push_back({1, 4, fileOffset, address, address, segmentSize, segmentSize, 4096}); + elf.resize(static_cast(fileOffset + segmentSize), 0); + for (std::size_t index = 0; index < segments.size(); ++index) + Write(elf, fileOffset + index * sizeof(Elf64_Phdr), segments[index]); + Write(elf, offsetof(Elf64_Ehdr, e_phoff), fileOffset); + Write(elf, offsetof(Elf64_Ehdr, e_phnum), static_cast(segments.size())); + dynStrOffset = static_cast(fileOffset + tableSize); + dynStrSize = newDynStr.size(); + const auto stringAddress = address + tableSize; + const auto sectionOffset = ehdr.e_shoff + dynStrSectionLink * sizeof(Elf64_Shdr); + auto strings = Read(elf, sectionOffset); + strings.sh_offset = dynStrOffset; + strings.sh_addr = stringAddress; + strings.sh_size = dynStrSize; + Write(elf, sectionOffset, strings); + bool foundAddress = false; + bool foundSize = false; + for (std::size_t index = 0; index < ehdr.e_shnum; ++index) { + const auto section = Read(elf, ehdr.e_shoff + index * sizeof(Elf64_Shdr)); + if (section.sh_type != kShtDynamic) continue; + RequireElfRange(elf, section.sh_offset, section.sh_size, "dynamic"); + for (std::uint64_t offset = 0; offset + sizeof(Elf64_Dyn) <= section.sh_size; offset += sizeof(Elf64_Dyn)) { + auto entry = Read(elf, section.sh_offset + offset); + if (entry.d_tag == kDtNull) break; + if (entry.d_tag == 5) { entry.d_val = stringAddress; foundAddress = true; } + if (entry.d_tag == 10) { entry.d_val = dynStrSize; foundSize = true; } + Write(elf, section.sh_offset + offset, entry); + } + } + if (!foundAddress || !foundSize) throw std::runtime_error("ELF executable lacks DT_STRTAB or DT_STRSZ"); + } if (newDynStr.size() > dynStrSize) { - throw std::runtime_error( - "rebuilt .dynstr (" + std::to_string(newDynStr.size()) + " bytes) exceeds original section size (" + - std::to_string(dynStrSize) + " bytes) - relinking with a larger section is required" - ); + throw std::runtime_error("rebuilt .dynstr exceeds original section size; relinking with a larger section is required"); } newDynStr.resize(dynStrSize, 0u); diff --git a/core/libs/nid/src/ExportExclusions.cpp b/core/libs/nid/src/ExportExclusions.cpp index 273f517d3b..b02cd0dd24 100644 --- a/core/libs/nid/src/ExportExclusions.cpp +++ b/core/libs/nid/src/ExportExclusions.cpp @@ -175,7 +175,7 @@ std::unordered_set ReadExportExclusions(const std::string& path) { } std::unordered_set ReadExportList(const std::string& path) { - std::ifstream input(path, std::ios::binary); + std::ifstream input(Internal::NativePath(path), std::ios::binary); if (!input) throw std::runtime_error("cannot open export list: " + path); const auto text = ReadText(path, input); if (text.empty() || text.back() != '\n') throw std::runtime_error("malformed export list: " + path); @@ -192,8 +192,8 @@ std::unordered_set ReadExportList(const std::string& path) { bool WriteExportList(const std::string& referencePath, const std::string& path) { const auto text = FormatExportList(ReadExportExclusions(referencePath)); - if (std::ifstream existing(path, std::ios::binary); existing && ReadText(path, existing) == text) return false; - std::ofstream output(path, std::ios::binary | std::ios::trunc); + if (std::ifstream existing(Internal::NativePath(path), std::ios::binary); existing && ReadText(path, existing) == text) return false; + std::ofstream output(Internal::NativePath(path), std::ios::binary | std::ios::trunc); if (!output) throw std::runtime_error("cannot write export list: " + path); output.write(text.data(), static_cast(text.size())); output.close(); diff --git a/core/libs/nid/src/MachONidPatcher.cpp b/core/libs/nid/src/MachONidPatcher.cpp index 45bdee2735..71e8713f27 100644 --- a/core/libs/nid/src/MachONidPatcher.cpp +++ b/core/libs/nid/src/MachONidPatcher.cpp @@ -341,15 +341,6 @@ std::string StripUnderscore(const std::string& name) { return !name.empty() && name[0] == '_' ? name.substr(1) : name; } -bool IsGuestFacingImport(const std::string& name) { - using namespace Internal; - const bool hasNidPostfix = name.size() >= kNidPostfixLen && name.compare(name.size() - kNidPostfixLen, kNidPostfixLen, kNidPostfix) == 0; - const bool hasScePrefix = name.size() >= 3u && - std::tolower(static_cast(name[0])) == 's' && - std::tolower(static_cast(name[1])) == 'c' && - std::tolower(static_cast(name[2])) == 'e'; - return hasNidPostfix || hasScePrefix; -} std::vector DylibOrdinalsArePrx(const std::vector& binary, const Image& image) { std::vector result; @@ -364,11 +355,48 @@ std::vector DylibOrdinalsArePrx(const std::vector& binary, c return result; } -void PatchChainedImports(std::vector& binary, const Image& image) { +void WriteLinkeditData(std::vector& binary, const Image& image, const TrieLocation& location, const std::vector& trie) { + const auto oldOffset = Read(binary, location.OffsetField); + const auto oldSize = Read(binary, location.SizeField); + if (trie.size() <= oldSize) { + std::copy(trie.begin(), trie.end(), binary.begin() + oldOffset); + std::fill(binary.begin() + oldOffset + static_cast(trie.size()), binary.begin() + oldOffset + oldSize, 0); + Write(binary, location.SizeField, static_cast(trie.size())); + return; + } + std::optional linkedit; + for (const auto offset : image.Commands) { + const auto cmd = Read(binary, offset).Cmd; + if (cmd == kLcCodeSignature) throw std::runtime_error("signed Mach-O images cannot grow linkedit data"); + if (cmd != kLcSegment64) continue; + const auto segment = Read(binary, offset); + if (std::strncmp(segment.Name, "__LINKEDIT", sizeof(segment.Name)) == 0) linkedit = offset; + } + if (!linkedit) throw std::runtime_error("Mach-O image has no __LINKEDIT segment"); + auto segment = Read(binary, *linkedit); + if (segment.FileOffset + segment.FileSize != binary.size()) throw std::runtime_error("__LINKEDIT does not end the file"); + const auto newOffset = (binary.size() + 7) & ~std::size_t{7}; + if (newOffset + trie.size() > 0xffffffffu) throw std::runtime_error("linkedit data offset overflow"); + binary.resize(newOffset, 0); + binary.insert(binary.end(), trie.begin(), trie.end()); + std::fill(binary.begin() + oldOffset, binary.begin() + oldOffset + oldSize, 0); + segment.FileSize = binary.size() - segment.FileOffset; + segment.VmSize = std::max(segment.VmSize, (segment.FileSize + kPageSize - 1) & ~(kPageSize - 1)); + Write(binary, *linkedit, segment); + Write(binary, location.OffsetField, static_cast(newOffset)); + Write(binary, location.SizeField, static_cast(trie.size())); +} + +void PatchChainedImports(std::vector& binary, const Image& image, const std::unordered_set& excluded, const PatchOptions& options) { + std::size_t commandOffset = 0; std::optional fixups; for (const auto offset : image.Commands) { const auto cmd = Read(binary, offset).Cmd; - if (cmd == kLcDyldChainedFixups) fixups = Read(binary, offset); + if (cmd == kLcDyldChainedFixups) { + fixups = Read(binary, offset); + commandOffset = offset; + } + if (options.executable && cmd == kLcCodeSignature) throw std::runtime_error("remove the Mach-O code signature before patching and sign again afterwards"); if ((cmd == kLcDyldInfo || cmd == kLcDyldInfoOnly)) { const auto info = Read(binary, offset); if (info.BindSize != 0 || info.LazyBindSize != 0 || info.WeakBindSize != 0) throw std::runtime_error("Mach-O bind opcodes are not supported; link with chained fixups"); @@ -400,61 +428,72 @@ void PatchChainedImports(std::vector& binary, const Image& image) imports.push_back({raw >> 9, raw & 0xff}); } } - std::set nameOffsets; - for (const auto& item : imports) nameOffsets.insert(item.NameOffset); - std::set rewritten; + std::vector names; + std::vector originalNames; + std::vector selected; for (const auto& item : imports) { - const bool special = header.ImportsFormat == kImportFormatAddend64 ? item.Ordinal >= 0xfff0 : item.Ordinal >= 0xf0; - if (item.Ordinal == 0 || special || item.Ordinal > prx.size() || !prx[item.Ordinal - 1]) continue; - if (rewritten.contains(item.NameOffset)) continue; const auto at = symbols + item.NameOffset; if (at >= symbolsEnd) throw std::runtime_error("chained import name out of bounds"); const auto name = Internal::ReadCStr(binary, at); if (at + name.size() >= symbolsEnd) throw std::runtime_error("unterminated chained import name"); const auto bare = StripUnderscore(name); - if (bare == name || !IsGuestFacingImport(bare)) continue; - const auto patched = "_" + ResolveOneName(bare); - if (patched.size() > name.size()) throw std::runtime_error("import NID longer than original name: " + name); - const auto inside = nameOffsets.upper_bound(item.NameOffset); - if (inside != nameOffsets.end() && *inside <= item.NameOffset + name.size()) throw std::runtime_error("chained import names share storage: " + name); - std::memcpy(binary.data() + at, patched.data(), patched.size()); - std::fill(binary.begin() + static_cast(at + patched.size()), binary.begin() + static_cast(at + name.size() + 1), 0); - rewritten.insert(item.NameOffset); + const bool special = header.ImportsFormat == kImportFormatAddend64 ? item.Ordinal >= 0xfff0 : item.Ordinal >= 0xf0; + const bool eligible = options.executable || (bare != name && item.Ordinal != 0 && !special && item.Ordinal <= prx.size() && prx[item.Ordinal - 1]); + originalNames.push_back(name); + selected.push_back(eligible); + if (eligible) names.push_back(bare); } -} - -void WriteTrie(std::vector& binary, const Image& image, const TrieLocation& location, const std::vector& trie) { - const auto oldOffset = Read(binary, location.OffsetField); - const auto oldSize = Read(binary, location.SizeField); - if (trie.size() <= oldSize) { - std::copy(trie.begin(), trie.end(), binary.begin() + oldOffset); - std::fill(binary.begin() + oldOffset + static_cast(trie.size()), binary.begin() + oldOffset + oldSize, 0); - Write(binary, location.SizeField, static_cast(trie.size())); + const auto mapping = ResolveImportNids(names, excluded, options); + if (!options.executable) { + std::set nameOffsets; + for (const auto& item : imports) nameOffsets.insert(item.NameOffset); + std::set rewritten; + for (const auto& item : imports) { + const bool special = header.ImportsFormat == kImportFormatAddend64 ? item.Ordinal >= 0xfff0 : item.Ordinal >= 0xf0; + if (item.Ordinal == 0 || special || item.Ordinal > prx.size() || !prx[item.Ordinal - 1]) continue; + if (rewritten.contains(item.NameOffset)) continue; + const auto at = symbols + item.NameOffset; + if (at >= symbolsEnd) throw std::runtime_error("chained import name out of bounds"); + const auto name = Internal::ReadCStr(binary, at); + if (at + name.size() >= symbolsEnd) throw std::runtime_error("unterminated chained import name"); + const auto bare = StripUnderscore(name); + if (bare == name) continue; + const auto patched = "_" + mapping.at(bare); + if (patched.size() > name.size()) throw std::runtime_error("import NID longer than original name: " + name); + const auto inside = nameOffsets.upper_bound(item.NameOffset); + if (inside != nameOffsets.end() && *inside <= item.NameOffset + name.size()) throw std::runtime_error("chained import names share storage: " + name); + std::memcpy(binary.data() + at, patched.data(), patched.size()); + std::fill(binary.begin() + static_cast(at + patched.size()), binary.begin() + static_cast(at + name.size() + 1), 0); + rewritten.insert(item.NameOffset); + } return; } - std::optional linkedit; - for (const auto offset : image.Commands) { - const auto cmd = Read(binary, offset).Cmd; - if (cmd == kLcCodeSignature) throw std::runtime_error("signed Mach-O images cannot grow their export trie"); - if (cmd != kLcSegment64) continue; - const auto segment = Read(binary, offset); - if (std::strncmp(segment.Name, "__LINKEDIT", sizeof(segment.Name)) == 0) linkedit = offset; + if (imports.empty()) return; + std::vector data(binary.begin() + base, binary.begin() + symbolsEnd); + const auto newSymbols = data.size(); + if (newSymbols > 0xffffffffu) throw std::runtime_error("chained symbol offset overflow"); + Write(data, offsetof(ChainedFixupsHeader, SymbolsOffset), static_cast(newSymbols)); + for (std::size_t index = 0; index < imports.size(); ++index) { + const auto& original = originalNames[index]; + const auto name = selected[index] ? (original.starts_with("_") ? "_" : "") + mapping.at(StripUnderscore(original)) : original; + const auto offset = data.size() - newSymbols; + const auto at = header.ImportsOffset + index * entrySize; + if (header.ImportsFormat == kImportFormatAddend64) { + if (offset > 0xffffffffu) throw std::runtime_error("chained symbol offset overflow"); + const auto raw = Read(data, at); + Write(data, at, (raw & 0xffffffffu) | (std::uint64_t{offset} << 32)); + } else { + if (offset > 0x7fffffu) throw std::runtime_error("chained symbol offset overflow"); + const auto raw = Read(data, at); + Write(data, at, (raw & 0x1ffu) | (static_cast(offset) << 9)); + } + data.insert(data.end(), name.begin(), name.end()); + data.push_back(0); } - if (!linkedit) throw std::runtime_error("Mach-O image has no __LINKEDIT segment"); - auto segment = Read(binary, *linkedit); - if (segment.FileOffset + segment.FileSize != binary.size()) throw std::runtime_error("__LINKEDIT does not end the file"); - const auto newOffset = (binary.size() + 7) & ~std::size_t{7}; - if (newOffset + trie.size() > 0xffffffffu) throw std::runtime_error("export trie offset overflow"); - binary.resize(newOffset, 0); - binary.insert(binary.end(), trie.begin(), trie.end()); - std::fill(binary.begin() + oldOffset, binary.begin() + oldOffset + oldSize, 0); - segment.FileSize = binary.size() - segment.FileOffset; - segment.VmSize = std::max(segment.VmSize, (segment.FileSize + kPageSize - 1) & ~(kPageSize - 1)); - Write(binary, *linkedit, segment); - Write(binary, location.OffsetField, static_cast(newOffset)); - Write(binary, location.SizeField, static_cast(trie.size())); + WriteLinkeditData(binary, image, {commandOffset + 8, commandOffset + 12}, data); } + } std::vector ReadMachOExports(const std::vector& binary) { @@ -464,8 +503,12 @@ std::vector ReadMachOExports(const std::vector& binar return ParseTrie(binary, Read(binary, location->OffsetField), Read(binary, location->SizeField)); } -void MachONidPatcher::PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports) const { +void MachONidPatcher::PatchNids(std::vector& binary, const std::string& libraryName, const std::unordered_set& excludedExports, const PatchOptions& options) const { const auto image = ParseImage(binary); + if (options.executable) { + PatchChainedImports(binary, image, excludedExports, options); + return; + } const auto location = FindTrie(binary, image); if (!location) throw std::runtime_error("no export trie"); auto exports = ParseTrie(binary, Read(binary, location->OffsetField), Read(binary, location->SizeField)); @@ -486,8 +529,8 @@ void MachONidPatcher::PatchNids(std::vector& binary, const std::st if (found == nidMap.end()) throw std::runtime_error("symbol not in nid map: " + name); exports[index].Name = "_" + found->second; } - WriteTrie(binary, image, *location, TrieBuilder(exports).Build()); - PatchChainedImports(binary, ParseImage(binary)); + WriteLinkeditData(binary, image, *location, TrieBuilder(exports).Build()); + PatchChainedImports(binary, ParseImage(binary), excludedExports, options); } } diff --git a/core/libs/nid/src/NidResolver.cpp b/core/libs/nid/src/NidResolver.cpp index 200565a6a7..aefa72ee8c 100644 --- a/core/libs/nid/src/NidResolver.cpp +++ b/core/libs/nid/src/NidResolver.cpp @@ -3,21 +3,32 @@ #include #include #include +#include #include #include #include namespace Nid { +bool IsSceImport(const std::string& name) { + return name.ends_with(Internal::kNidPostfix) || (name.size() >= 3 && + std::tolower(static_cast(name[0])) == 's' && + std::tolower(static_cast(name[1])) == 'c' && + std::tolower(static_cast(name[2])) == 'e'); +} + +std::unordered_map ResolveImportNids(const std::vector& names, const std::unordered_set& excluded, const PatchOptions& options) { + const std::unordered_set unique(names.begin(), names.end()); + auto result = ResolveNids(std::vector(unique.begin(), unique.end()), "", excluded); + for (const auto& name : unique) { + if ((!options.executable || options.onlySce) && !IsSceImport(name)) result.at(name) = name; + else if (!options.executable) result.at(name) = ResolveOneName(name); + } + return result; +} + std::string ResolveOneName(const std::string& funcName) { - using namespace Internal; - - if (IsNidNoPatchCut(funcName)) - return StripNidNoPatchCut(funcName); - if (IsNidNoPatch(funcName)) - return funcName; - - return ComputeNid(StripNidPostfix(funcName), ""); + return ResolveNids({funcName}, "", {}).at(funcName); } std::unordered_map ResolveNids(const std::vector& exportedNames, const std::string& libraryName, const std::unordered_set& excludedExports) { diff --git a/core/libs/nid/src/PeNidPatcher.cpp b/core/libs/nid/src/PeNidPatcher.cpp index 58fb8ade0a..638f3bb29e 100644 --- a/core/libs/nid/src/PeNidPatcher.cpp +++ b/core/libs/nid/src/PeNidPatcher.cpp @@ -36,7 +36,7 @@ std::size_t RvaToOffset(const std::vector& pe, std::uint32_t rva, } -void PeNidPatcher::PatchNids(std::vector& pe, const std::string& libraryName, const std::unordered_set& excludedExports) const { +void PeNidPatcher::PatchNids(std::vector& pe, const std::string& libraryName, const std::unordered_set& excludedExports, const PatchOptions& options) const { using namespace Internal; if (pe.size() < 0x40) throw std::runtime_error("file too small"); @@ -54,7 +54,7 @@ void PeNidPatcher::PatchNids(std::vector& pe, const std::string& l const std::size_t sectionTableOffset = coffHeaderOffset + 20u + sizeOfOptionalHeader; const auto symbolTable = Read(pe, coffHeaderOffset + 8u); const auto symbolCount = Read(pe, coffHeaderOffset + 12u); - for (std::uint16_t i = 0; i < numberOfSections; ++i) { + for (std::uint16_t i = 0; !options.executable && i < numberOfSections; ++i) { const auto offset = sectionTableOffset + i * sizeof(PeSectionHeader); auto section = Read(pe, offset); if (pe[offset] != '/' || !symbolTable) continue; @@ -79,159 +79,162 @@ void PeNidPatcher::PatchNids(std::vector& pe, const std::string& l throw std::runtime_error("unsupported optional header magic"); } - const auto exportDir = Read(pe, dataDirectoryOffset); - if (exportDir.VirtualAddress == 0u) throw std::runtime_error("no export directory"); + if (!options.executable) { + const auto exportDir = Read(pe, dataDirectoryOffset); + if (exportDir.VirtualAddress == 0u) throw std::runtime_error("no export directory"); - const std::size_t exportDirOffset = RvaToOffset(pe, exportDir.VirtualAddress, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); - const auto exportTable = Read(pe, exportDirOffset); + const std::size_t exportDirOffset = RvaToOffset(pe, exportDir.VirtualAddress, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + const auto exportTable = Read(pe, exportDirOffset); - if (exportTable.NumberOfNames == 0u) throw std::runtime_error("no exported names"); + if (exportTable.NumberOfNames == 0u) throw std::runtime_error("no exported names"); - const std::size_t namesArrayOffset = RvaToOffset(pe, exportTable.AddressOfNames, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); - const std::size_t ordinalsArrayOffset = RvaToOffset(pe, exportTable.AddressOfNameOrdinals, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + const std::size_t namesArrayOffset = RvaToOffset(pe, exportTable.AddressOfNames, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + const std::size_t ordinalsArrayOffset = RvaToOffset(pe, exportTable.AddressOfNameOrdinals, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); - std::vector nameRvas(exportTable.NumberOfNames); - std::vector names(exportTable.NumberOfNames); - for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { - const auto nameRva = Read(pe, namesArrayOffset + i * 4u); - const std::size_t nameOffset = RvaToOffset(pe, nameRva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); - const std::string name = ReadCStr(pe, nameOffset); - if (name.empty()) throw std::runtime_error("empty exported name"); - nameRvas[i] = nameRva; - names[i] = name; - } - - const auto nidMap = ResolveNids(names, libraryName, excludedExports); - - const std::size_t edataSectionOffset = FindSectionOffsetByRva(pe, exportDir.VirtualAddress, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); - const auto edataSection = Read(pe, edataSectionOffset); - - const auto fileAlignment = Read(pe, optionalHeaderOffset + 36u); - if (fileAlignment == 0u) throw std::runtime_error("invalid file alignment"); - - std::uint32_t stringsRegionStart = std::numeric_limits::max(); - for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { - const std::uint32_t rvaFromSection = nameRvas[i] - edataSection.VirtualAddress; - if (rvaFromSection < stringsRegionStart) stringsRegionStart = rvaFromSection; - } - if (stringsRegionStart == std::numeric_limits::max()) - throw std::runtime_error("could not determine strings region start"); - - std::uint32_t rawLimit = ((edataSection.SizeOfRawData + fileAlignment - 1u) / fileAlignment) * fileAlignment; - const auto dllName = ReadCStr(pe, RvaToOffset(pe, exportTable.Name, peHeaderOffset, numberOfSections, sizeOfOptionalHeader)); - const bool repackDllName = exportTable.Name >= edataSection.VirtualAddress && - exportTable.Name - edataSection.VirtualAddress < rawLimit; - if (repackDllName) stringsRegionStart = std::min(stringsRegionStart, exportTable.Name - edataSection.VirtualAddress); - struct Forwarder { - std::size_t EntryOffset; - std::string Target; - }; - std::vector forwarders; - std::vector directRvas; - const auto functionsArrayOffset = RvaToOffset(pe, exportTable.AddressOfFunctions, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); - for (std::uint32_t index = 0u; index < exportTable.NumberOfFunctions; ++index) { - const auto entryOffset = functionsArrayOffset + index * sizeof(std::uint32_t); - const auto rva = Read(pe, entryOffset); - if (rva < exportDir.VirtualAddress || std::uint64_t{rva} - exportDir.VirtualAddress >= exportDir.Size) { - directRvas.push_back(rva); - continue; + std::vector nameRvas(exportTable.NumberOfNames); + std::vector names(exportTable.NumberOfNames); + for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { + const auto nameRva = Read(pe, namesArrayOffset + i * 4u); + const std::size_t nameOffset = RvaToOffset(pe, nameRva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + const std::string name = ReadCStr(pe, nameOffset); + if (name.empty()) throw std::runtime_error("empty exported name"); + nameRvas[i] = nameRva; + names[i] = name; } - const auto target = ReadCStr(pe, RvaToOffset(pe, rva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader)); - if (target.size() + 1u > exportDir.Size - (rva - exportDir.VirtualAddress)) - throw std::runtime_error("forwarder string outside export directory"); - forwarders.push_back({entryOffset, target}); - stringsRegionStart = std::min(stringsRegionStart, rva - edataSection.VirtualAddress); - } - for (const auto rva : directRvas) { - if (rva >= edataSection.VirtualAddress && rva - edataSection.VirtualAddress >= stringsRegionStart) - rawLimit = std::min(rawLimit, rva - edataSection.VirtualAddress); - } - const auto reserveTable = [&](std::uint32_t rva, std::uint64_t size) { - if (rva < edataSection.VirtualAddress || rva - edataSection.VirtualAddress >= rawLimit) return; - const auto end = std::uint64_t{rva - edataSection.VirtualAddress} + size; - if (end > rawLimit) throw std::runtime_error("export table outside edata section"); - stringsRegionStart = std::max(stringsRegionStart, static_cast(end)); - }; - reserveTable(exportDir.VirtualAddress, sizeof(PeExportDirectory)); - reserveTable(exportTable.AddressOfFunctions, std::uint64_t{exportTable.NumberOfFunctions} * 4u); - reserveTable(exportTable.AddressOfNames, std::uint64_t{exportTable.NumberOfNames} * 4u); - reserveTable(exportTable.AddressOfNameOrdinals, std::uint64_t{exportTable.NumberOfNames} * 2u); - std::vector finalNames(exportTable.NumberOfNames); - for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { - const auto it = nidMap.find(names[i]); - if (it == nidMap.end()) throw std::runtime_error("symbol not in nid map: " + names[i]); - finalNames[i] = it->second; - } + const auto nidMap = ResolveNids(names, libraryName, excludedExports); - std::vector strings; - std::unordered_map stringRvas; - const auto storeString = [&](const std::string& name) { - const auto existing = stringRvas.find(name); - if (existing != stringRvas.end()) return existing->second; - const auto rva = edataSection.VirtualAddress + stringsRegionStart + static_cast(strings.size()); - stringRvas.emplace(name, rva); - strings.insert(strings.end(), name.begin(), name.end()); - strings.push_back(0u); - return rva; - }; - std::vector forwarderRvas; - for (const auto& forwarder : forwarders) forwarderRvas.push_back(storeString(forwarder.Target)); - if (!forwarders.empty() && (std::uint64_t{edataSection.VirtualAddress} + stringsRegionStart < exportDir.VirtualAddress || - std::uint64_t{edataSection.VirtualAddress} + stringsRegionStart + strings.size() > std::uint64_t{exportDir.VirtualAddress} + exportDir.Size)) - throw std::runtime_error("not enough export directory space to repack forwarders"); - const auto dllNameRva = repackDllName ? storeString(dllName) : exportTable.Name; - std::vector finalNameRvas; - for (const auto& name : finalNames) finalNameRvas.push_back(storeString(name)); - const auto requiredSize = std::uint64_t{stringsRegionStart} + strings.size(); - if (requiredSize > rawLimit) - throw std::runtime_error("not enough raw space in edata section to repack export names"); - const auto directoryEnd = std::max(std::uint64_t{exportDir.VirtualAddress} + exportDir.Size, - std::uint64_t{edataSection.VirtualAddress} + requiredSize); - for (const auto rva : directRvas) { - if (rva != 0u && rva >= exportDir.VirtualAddress && std::uint64_t{rva} < directoryEnd) - throw std::runtime_error("repacked export directory would cover a direct export"); - } - const auto directorySize = directoryEnd - exportDir.VirtualAddress; - if (directorySize > std::numeric_limits::max()) throw std::runtime_error("export directory too large"); - const auto stringFileOffset = static_cast(edataSection.PointerToRawData) + stringsRegionStart; - if (stringFileOffset > pe.size() || strings.size() > pe.size() - stringFileOffset) - throw std::runtime_error("repack write out of file bounds"); - std::memcpy(pe.data() + stringFileOffset, strings.data(), strings.size()); - for (std::size_t index = 0u; index < finalNameRvas.size(); ++index) - Write(pe, namesArrayOffset + index * 4u, finalNameRvas[index]); - for (std::size_t index = 0u; index < forwarders.size(); ++index) - Write(pe, forwarders[index].EntryOffset, forwarderRvas[index]); - Write(pe, exportDirOffset + offsetof(PeExportDirectory, Name), dllNameRva); - Write(pe, dataDirectoryOffset + offsetof(PeDataDirectory, Size), static_cast(directorySize)); + const std::size_t edataSectionOffset = FindSectionOffsetByRva(pe, exportDir.VirtualAddress, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + const auto edataSection = Read(pe, edataSectionOffset); - const std::uint32_t newVirtualSize = std::max(edataSection.VirtualSize, static_cast(requiredSize)); - auto patchedSection = edataSection; - patchedSection.VirtualSize = newVirtualSize; - Write(pe, edataSectionOffset, patchedSection); + const auto fileAlignment = Read(pe, optionalHeaderOffset + 36u); + if (fileAlignment == 0u) throw std::runtime_error("invalid file alignment"); - struct ExportName { - std::string Name; - std::uint32_t Rva; - std::uint16_t Ordinal; - }; + std::uint32_t stringsRegionStart = std::numeric_limits::max(); + for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { + const std::uint32_t rvaFromSection = nameRvas[i] - edataSection.VirtualAddress; + if (rvaFromSection < stringsRegionStart) stringsRegionStart = rvaFromSection; + } + if (stringsRegionStart == std::numeric_limits::max()) + throw std::runtime_error("could not determine strings region start"); + + std::uint32_t rawLimit = ((edataSection.SizeOfRawData + fileAlignment - 1u) / fileAlignment) * fileAlignment; + const auto dllName = ReadCStr(pe, RvaToOffset(pe, exportTable.Name, peHeaderOffset, numberOfSections, sizeOfOptionalHeader)); + const bool repackDllName = exportTable.Name >= edataSection.VirtualAddress && + exportTable.Name - edataSection.VirtualAddress < rawLimit; + if (repackDllName) stringsRegionStart = std::min(stringsRegionStart, exportTable.Name - edataSection.VirtualAddress); + struct Forwarder { + std::size_t EntryOffset; + std::string Target; + }; + std::vector forwarders; + std::vector directRvas; + const auto functionsArrayOffset = RvaToOffset(pe, exportTable.AddressOfFunctions, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + for (std::uint32_t index = 0u; index < exportTable.NumberOfFunctions; ++index) { + const auto entryOffset = functionsArrayOffset + index * sizeof(std::uint32_t); + const auto rva = Read(pe, entryOffset); + if (rva < exportDir.VirtualAddress || std::uint64_t{rva} - exportDir.VirtualAddress >= exportDir.Size) { + directRvas.push_back(rva); + continue; + } + const auto target = ReadCStr(pe, RvaToOffset(pe, rva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader)); + if (target.size() + 1u > exportDir.Size - (rva - exportDir.VirtualAddress)) + throw std::runtime_error("forwarder string outside export directory"); + forwarders.push_back({entryOffset, target}); + stringsRegionStart = std::min(stringsRegionStart, rva - edataSection.VirtualAddress); + } + for (const auto rva : directRvas) { + if (rva >= edataSection.VirtualAddress && rva - edataSection.VirtualAddress >= stringsRegionStart) + rawLimit = std::min(rawLimit, rva - edataSection.VirtualAddress); + } + const auto reserveTable = [&](std::uint32_t rva, std::uint64_t size) { + if (rva < edataSection.VirtualAddress || rva - edataSection.VirtualAddress >= rawLimit) return; + const auto end = std::uint64_t{rva - edataSection.VirtualAddress} + size; + if (end > rawLimit) throw std::runtime_error("export table outside edata section"); + stringsRegionStart = std::max(stringsRegionStart, static_cast(end)); + }; + reserveTable(exportDir.VirtualAddress, sizeof(PeExportDirectory)); + reserveTable(exportTable.AddressOfFunctions, std::uint64_t{exportTable.NumberOfFunctions} * 4u); + reserveTable(exportTable.AddressOfNames, std::uint64_t{exportTable.NumberOfNames} * 4u); + reserveTable(exportTable.AddressOfNameOrdinals, std::uint64_t{exportTable.NumberOfNames} * 2u); + + std::vector finalNames(exportTable.NumberOfNames); + for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { + const auto it = nidMap.find(names[i]); + if (it == nidMap.end()) throw std::runtime_error("symbol not in nid map: " + names[i]); + finalNames[i] = it->second; + } + + std::vector strings; + std::unordered_map stringRvas; + const auto storeString = [&](const std::string& name) { + const auto existing = stringRvas.find(name); + if (existing != stringRvas.end()) return existing->second; + const auto rva = edataSection.VirtualAddress + stringsRegionStart + static_cast(strings.size()); + stringRvas.emplace(name, rva); + strings.insert(strings.end(), name.begin(), name.end()); + strings.push_back(0u); + return rva; + }; + std::vector forwarderRvas; + for (const auto& forwarder : forwarders) forwarderRvas.push_back(storeString(forwarder.Target)); + if (!forwarders.empty() && (std::uint64_t{edataSection.VirtualAddress} + stringsRegionStart < exportDir.VirtualAddress || + std::uint64_t{edataSection.VirtualAddress} + stringsRegionStart + strings.size() > std::uint64_t{exportDir.VirtualAddress} + exportDir.Size)) + throw std::runtime_error("not enough export directory space to repack forwarders"); + const auto dllNameRva = repackDllName ? storeString(dllName) : exportTable.Name; + std::vector finalNameRvas; + for (const auto& name : finalNames) finalNameRvas.push_back(storeString(name)); + const auto requiredSize = std::uint64_t{stringsRegionStart} + strings.size(); + if (requiredSize > rawLimit) + throw std::runtime_error("not enough raw space in edata section to repack export names"); + const auto directoryEnd = std::max(std::uint64_t{exportDir.VirtualAddress} + exportDir.Size, + std::uint64_t{edataSection.VirtualAddress} + requiredSize); + for (const auto rva : directRvas) { + if (rva != 0u && rva >= exportDir.VirtualAddress && std::uint64_t{rva} < directoryEnd) + throw std::runtime_error("repacked export directory would cover a direct export"); + } + const auto directorySize = directoryEnd - exportDir.VirtualAddress; + if (directorySize > std::numeric_limits::max()) throw std::runtime_error("export directory too large"); + const auto stringFileOffset = static_cast(edataSection.PointerToRawData) + stringsRegionStart; + if (stringFileOffset > pe.size() || strings.size() > pe.size() - stringFileOffset) + throw std::runtime_error("repack write out of file bounds"); + std::memcpy(pe.data() + stringFileOffset, strings.data(), strings.size()); + for (std::size_t index = 0u; index < finalNameRvas.size(); ++index) + Write(pe, namesArrayOffset + index * 4u, finalNameRvas[index]); + for (std::size_t index = 0u; index < forwarders.size(); ++index) + Write(pe, forwarders[index].EntryOffset, forwarderRvas[index]); + Write(pe, exportDirOffset + offsetof(PeExportDirectory, Name), dllNameRva); + Write(pe, dataDirectoryOffset + offsetof(PeDataDirectory, Size), static_cast(directorySize)); + + const std::uint32_t newVirtualSize = std::max(edataSection.VirtualSize, static_cast(requiredSize)); + auto patchedSection = edataSection; + patchedSection.VirtualSize = newVirtualSize; + Write(pe, edataSectionOffset, patchedSection); + + struct ExportName { + std::string Name; + std::uint32_t Rva; + std::uint16_t Ordinal; + }; + + std::vector exportNames; + exportNames.reserve(exportTable.NumberOfNames); + for (std::size_t i = 0; i < exportTable.NumberOfNames; ++i) { + const auto nameRva = Read(pe, namesArrayOffset + i * sizeof(std::uint32_t)); + const auto ordinal = Read(pe, ordinalsArrayOffset + i * sizeof(std::uint16_t)); + if (ordinal >= exportTable.NumberOfFunctions) + throw std::runtime_error("export name ordinal out of bounds"); + const auto nameOffset = RvaToOffset(pe, nameRva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + exportNames.push_back({ReadCStr(pe, nameOffset), nameRva, ordinal}); + } + std::sort(exportNames.begin(), exportNames.end(), [](const ExportName& left, const ExportName& right) { return left.Name < right.Name; }); + for (std::size_t i = 0; i < exportNames.size(); ++i) { + if (i != 0 && exportNames[i - 1].Name == exportNames[i].Name) + throw std::runtime_error("duplicate patched export name: " + exportNames[i].Name); + Write(pe, namesArrayOffset + i * sizeof(std::uint32_t), exportNames[i].Rva); + Write(pe, ordinalsArrayOffset + i * sizeof(std::uint16_t), exportNames[i].Ordinal); + } - std::vector exportNames; - exportNames.reserve(exportTable.NumberOfNames); - for (std::size_t i = 0; i < exportTable.NumberOfNames; ++i) { - const auto nameRva = Read(pe, namesArrayOffset + i * sizeof(std::uint32_t)); - const auto ordinal = Read(pe, ordinalsArrayOffset + i * sizeof(std::uint16_t)); - if (ordinal >= exportTable.NumberOfFunctions) - throw std::runtime_error("export name ordinal out of bounds"); - const auto nameOffset = RvaToOffset(pe, nameRva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); - exportNames.push_back({ReadCStr(pe, nameOffset), nameRva, ordinal}); - } - std::sort(exportNames.begin(), exportNames.end(), [](const ExportName& left, const ExportName& right) { return left.Name < right.Name; }); - for (std::size_t i = 0; i < exportNames.size(); ++i) { - if (i != 0 && exportNames[i - 1].Name == exportNames[i].Name) - throw std::runtime_error("duplicate patched export name: " + exportNames[i].Name); - Write(pe, namesArrayOffset + i * sizeof(std::uint32_t), exportNames[i].Rva); - Write(pe, ordinalsArrayOffset + i * sizeof(std::uint16_t), exportNames[i].Ordinal); } constexpr std::size_t kImportDirIndex = 1u; @@ -250,6 +253,20 @@ 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; + std::size_t AddressOffset; + std::string Name; + }; + std::vector imports; + std::vector importNames; std::size_t importDescOffset = RvaToOffset(pe, importDir.VirtualAddress, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); while (true) { @@ -286,19 +303,9 @@ void PeNidPatcher::PatchNids(std::vector& pe, const std::string& l const std::string funcName = ReadCStr(pe, hintNameOffset + 2u); if (!funcName.empty()) { - const bool hasNidPostfix = funcName.size() >= kNidPostfixLen && - funcName.compare(funcName.size() - kNidPostfixLen, kNidPostfixLen, kNidPostfix) == 0; - const bool hasScePrefix = funcName.size() >= 3u && - std::tolower(static_cast(funcName[0])) == 's' && - std::tolower(static_cast(funcName[1])) == 'c' && - std::tolower(static_cast(funcName[2])) == 'e'; - if (hasNidPostfix || hasScePrefix) { - const std::string nid = ResolveOneName(funcName); - if (nid.size() > funcName.size()) - throw std::runtime_error("import NID longer than original name: " + funcName); - std::memcpy(pe.data() + hintNameOffset + 2u, nid.data(), nid.size()); - pe[hintNameOffset + 2u + nid.size()] = 0u; - } + const auto addressOffset = RvaToOffset(pe, desc.FirstThunk, peHeaderOffset, numberOfSections, sizeOfOptionalHeader) + thunkOffset - RvaToOffset(pe, thunkRva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + imports.push_back({hintNameOffset + 2u, thunkOffset, addressOffset, funcName}); + importNames.push_back(funcName); } } @@ -307,6 +314,72 @@ 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; + } + 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 (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())); + strings.insert(strings.end(), {0, 0}); + strings.insert(strings.end(), name.begin(), name.end()); + strings.push_back(0); + } + if (strings.empty()) return; + const auto fileAlignment = Read(pe, optionalHeaderOffset + 36u); + const auto sectionAlignment = Read(pe, optionalHeaderOffset + 32u); + const auto align = [](std::uint64_t value, std::uint32_t alignment) { + if (!alignment || (alignment & (alignment - 1u))) throw std::runtime_error("invalid PE alignment"); + const auto result = (value + alignment - 1u) & ~std::uint64_t{alignment - 1u}; + if (result > 0xffffffffu) throw std::runtime_error("PE section overflow"); + return static_cast(result); + }; + const auto newHeader = sectionTableOffset + numberOfSections * sizeof(PeSectionHeader); + auto headerLimit = std::size_t{Read(pe, optionalHeaderOffset + 60u)}; + std::uint64_t virtualEnd = headerLimit; + for (std::uint16_t index = 0; index < numberOfSections; ++index) { + const auto section = Read(pe, sectionTableOffset + index * sizeof(PeSectionHeader)); + if (section.PointerToRawData) headerLimit = std::min(headerLimit, std::size_t{section.PointerToRawData}); + virtualEnd = std::max(virtualEnd, std::uint64_t{section.VirtualAddress} + std::max(section.VirtualSize, section.SizeOfRawData)); + } + if (numberOfSections == 0xffffu || newHeader + sizeof(PeSectionHeader) > headerLimit || headerLimit > pe.size()) throw std::runtime_error("no room for PE import section header"); + PeSectionHeader section{}; + const char sectionName[] = ".nidimp"; + std::memcpy(section.Name, sectionName, sizeof(sectionName)); + section.VirtualSize = static_cast(strings.size()); + section.VirtualAddress = align(virtualEnd, sectionAlignment); + section.PointerToRawData = align(pe.size(), fileAlignment); + section.SizeOfRawData = align(strings.size(), fileAlignment); + section.Characteristics = 0x40000040u; + const auto imageSize = align(std::uint64_t{section.VirtualAddress} + section.VirtualSize, sectionAlignment); + const auto rawEnd = std::uint64_t{section.PointerToRawData} + section.SizeOfRawData; + if (rawEnd > 0xffffffffu) throw std::runtime_error("PE file size overflow"); + pe.resize(static_cast(rawEnd), 0); + std::copy(strings.begin(), strings.end(), pe.begin() + section.PointerToRawData); + Write(pe, newHeader, section); + Write(pe, coffHeaderOffset + 2u, static_cast(numberOfSections + 1u)); + Write(pe, optionalHeaderOffset + 56u, imageSize); + 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; + 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}); + else Write(pe, offset, rva); + } + } + } } diff --git a/core/libs/nid/src/main.cpp b/core/libs/nid/src/main.cpp index 722c765e7c..776480fca6 100644 --- a/core/libs/nid/src/main.cpp +++ b/core/libs/nid/src/main.cpp @@ -28,6 +28,7 @@ void _writeFile(const std::string& path, const std::vector& data) int Run(int argc, char* argv[]) { if (argc < 3) { std::cerr << "Usage: nid_patcher [--preserve-exports | --preserve-export-list ] [file2 ...]\n" + " nid_patcher --exec [--only-sce] [--preserve-exports | --preserve-export-list ] [file2 ...]\n" " nid_patcher --write-export-list \n"; return 1; } @@ -46,14 +47,20 @@ int Run(int argc, char* argv[]) { return 0; } - const std::string libraryName = argv[1]; + Nid::PatchOptions options; + options.executable = std::string(argv[1]) == "--exec"; + const std::string libraryName = options.executable ? "" : argv[1]; std::unordered_set excludedExports; std::vector paths; try { + if (!options.executable && libraryName.starts_with("--")) throw std::runtime_error("unknown option: " + libraryName); bool hasReference = false; for (int i = 2; i < argc; ++i) { const std::string argument = argv[i]; - if (argument == "--preserve-exports" || argument == "--preserve-export-list") { + if (argument == "--only-sce") { + if (!options.executable || options.onlySce) throw std::runtime_error("--only-sce requires --exec and may appear once"); + options.onlySce = true; + } else if (argument == "--preserve-exports" || argument == "--preserve-export-list") { if (hasReference || i + 1 == argc) throw std::runtime_error(argument + " requires exactly one reference"); hasReference = true; excludedExports = argument == "--preserve-exports" ? Nid::ReadExportExclusions(argv[++i]) : Nid::ReadExportList(argv[++i]); @@ -72,7 +79,7 @@ int Run(int argc, char* argv[]) { try { auto binary = _readFile(path); const auto patcher = Nid::MakePatcher(binary); - patcher->PatchNids(binary, libraryName, excludedExports); + patcher->PatchNids(binary, libraryName, excludedExports, options); _writeFile(path, binary); std::cout << "OK: " << path << "\n"; } catch (const std::exception& e) { diff --git a/core/libs/nid/tests/test_elf_duplicates.py b/core/libs/nid/tests/test_elf_duplicates.py index 504cf33bdc..25f9853da1 100644 --- a/core/libs/nid/tests/test_elf_duplicates.py +++ b/core/libs/nid/tests/test_elf_duplicates.py @@ -4,7 +4,7 @@ import sys import unittest from pathlib import Path -from test_symbol_versions import build_image, patch, read_dynsym, IMPORTS, NID_NAME, SHN_UNDEF, STT_FUNC +from test_symbol_versions import build_image, patch, read_dynsym, IMPORTS, NID_NAME, SHN_UNDEF, STT_FUNC, PATCHER def nid(name): @@ -21,6 +21,7 @@ def image(names, addresses=None): return bytes(build_image(entries, False)) +@unittest.skipUnless(PATCHER is not None and PATCHER.is_file(), "nid_patcher executable required") class ElfDuplicateTests(unittest.TestCase): def test_exports_patched_to_one_name_are_rejected(self): cases = ( @@ -65,5 +66,4 @@ class ElfDuplicateTests(unittest.TestCase): if __name__ == "__main__": - PATCHER = Path(sys.argv.pop(1)).resolve() unittest.main() diff --git a/core/libs/nid/tests/test_elf_layout.py b/core/libs/nid/tests/test_elf_layout.py index 703ac46db3..d4060852f6 100644 --- a/core/libs/nid/tests/test_elf_layout.py +++ b/core/libs/nid/tests/test_elf_layout.py @@ -3,9 +3,10 @@ import sys import unittest from pathlib import Path -from test_symbol_versions import build_image, symbols, patch, SHOFF +from test_symbol_versions import build_image, symbols, patch, SHOFF, PATCHER +@unittest.skipUnless(PATCHER is not None and PATCHER.is_file(), "nid_patcher executable required") class ElfLayoutTests(unittest.TestCase): def test_declared_layout_must_match_supported_tables(self): changes = ((5, "B", 2), (0x3a, "H", 32), @@ -44,5 +45,4 @@ class ElfLayoutTests(unittest.TestCase): if __name__ == "__main__": - PATCHER = Path(sys.argv.pop(1)).resolve() unittest.main() diff --git a/core/libs/nid/tests/test_gnu_hash.py b/core/libs/nid/tests/test_gnu_hash.py index 2e46a70f32..c1ddde43d4 100644 --- a/core/libs/nid/tests/test_gnu_hash.py +++ b/core/libs/nid/tests/test_gnu_hash.py @@ -3,7 +3,7 @@ import sys import unittest from pathlib import Path -from test_symbol_versions import build_image, symbols, patch, HASH_OFFSET, SHOFF, read_dynsym, gnu_hash, sections +from test_symbol_versions import build_image, symbols, patch, HASH_OFFSET, SHOFF, read_dynsym, gnu_hash, sections, PATCHER def sysv_hash(name): @@ -31,6 +31,7 @@ def sysv_lookup(image, name): return None +@unittest.skipUnless(PATCHER is not None and PATCHER.is_file(), "nid_patcher executable required") class GnuHashTests(unittest.TestCase): def image(self, buckets=4, bloom=1, shift=6, symoffset=3): image = build_image(symbols(False), False) @@ -95,6 +96,7 @@ class GnuHashTests(unittest.TestCase): self.assertEqual(struct.unpack_from(" 1: + patcher = Path(sys.argv[1]).resolve() + else: + patcher = PATCHER or find_patcher() + if patcher is None: + print("nid_patcher executable required") + sys.exit(0) + with tempfile.TemporaryDirectory(prefix="anyps5-macho-nid-a\u00e7\u00e3o-\u65e5\u672c-") as directory: check_shrinking(patcher, directory) check_growing(patcher, directory) check_preserved(patcher, directory) diff --git a/core/libs/nid/tests/test_native_paths.py b/core/libs/nid/tests/test_native_paths.py index ccfd9e6b8c..4b5c12193a 100644 --- a/core/libs/nid/tests/test_native_paths.py +++ b/core/libs/nid/tests/test_native_paths.py @@ -5,16 +5,14 @@ import tempfile import unittest from pathlib import Path -from test_macho_nids import build_dylib, exports_of, nid +from test_macho_nids import build_dylib, exports_of, nid, PATCHER try: import resource except ImportError: resource = None -PATCHER = Path(sys.argv.pop(1)).resolve() - - +@unittest.skipUnless(PATCHER is not None and PATCHER.is_file(), "nid_patcher executable required") class NativePathTests(unittest.TestCase): def invoke(self, *arguments): return subprocess.run([str(PATCHER), "libtest", *map(str, arguments)], diff --git a/core/libs/nid/tests/test_pe_references.py b/core/libs/nid/tests/test_pe_references.py index 2e6eed7f40..2992a55093 100644 --- a/core/libs/nid/tests/test_pe_references.py +++ b/core/libs/nid/tests/test_pe_references.py @@ -5,7 +5,7 @@ import tempfile import unittest from pathlib import Path -from test_macho_nids import build_dylib, exports_of, nid +from test_macho_nids import build_dylib, exports_of, nid, PATCHER def reference(*, headers=False, address=0x2000, ordinal=0): @@ -100,6 +100,7 @@ def forwarding_exports(image, bits): return string(table[4]), names, targets, addresses, (export_rva, export_size) +@unittest.skipUnless(PATCHER is not None and PATCHER.is_file(), "nid_patcher executable required") class ReferenceTests(unittest.TestCase): def patch_with_reference(self, data): with tempfile.TemporaryDirectory() as tmp: @@ -135,6 +136,7 @@ class ReferenceTests(unittest.TestCase): self.assertEqual(image, original) +@unittest.skipUnless(PATCHER is not None and PATCHER.is_file(), "nid_patcher executable required") class ForwardingExportTests(unittest.TestCase): def patch(self, image): with tempfile.TemporaryDirectory() as temporary: @@ -241,6 +243,203 @@ class ForwardingExportTests(unittest.TestCase): self.assertEqual(after, image) +class ExecutableImportTests(unittest.TestCase): + names = ["foo", "foo_nid_postfix", "z", "sceExample", "helper_nid_no_patch", "plain_nid_no_patch_cut", "SDL_Init", "scePreserved", "hostFunctionLongName", "alias_nid_disambig12_nid_postfix"] + + def pe(self, bits=64): + image = bytearray(2048) + image[:2] = b"MZ" + struct.pack_into(" 1: + patcher = Path(sys.argv[1]).resolve() + else: + patcher = PATCHER or find_patcher() + if patcher is None: + print("nid_patcher executable required") + sys.exit(0) test_versioned_library_is_rejected(patcher) test_unversioned_library_is_still_patched(patcher) print("NID patcher symbol version tests passed") diff --git a/core/libs/nid/tests/test_verneed.py b/core/libs/nid/tests/test_verneed.py index 58ebc02c6b..79dd7a7789 100644 --- a/core/libs/nid/tests/test_verneed.py +++ b/core/libs/nid/tests/test_verneed.py @@ -3,7 +3,7 @@ import sys import unittest from pathlib import Path -from test_symbol_versions import build_image, symbols, patch, SHOFF, DYNSTR_OFFSET, sections +from test_symbol_versions import build_image, symbols, patch, SHOFF, DYNSTR_OFFSET, sections, PATCHER VERNEED = 0x580 @@ -22,6 +22,7 @@ def image(*, auxiliary=16, count=1, size=32, version=1, auxnext=0, nextrecord=0) return data +@unittest.skipUnless(PATCHER is not None and PATCHER.is_file(), "nid_patcher executable required") class VersionRequirementsTests(unittest.TestCase): def test_auxiliary_record_cannot_leave_declared_section(self): self.reject(image(auxiliary=32)) @@ -72,5 +73,4 @@ class VersionRequirementsTests(unittest.TestCase): if __name__ == "__main__": - PATCHER = Path(sys.argv.pop(1)).resolve() unittest.main() diff --git a/core/libs/prx/libSceAbstractLocal/libSceAbstractLocal.hpp b/core/libs/prx/libSceAbstractLocal/libSceAbstractLocal.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAbstractLocal/libSceAbstractLocal.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAbstractStorage/libSceAbstractStorage.hpp b/core/libs/prx/libSceAbstractStorage/libSceAbstractStorage.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAbstractStorage/libSceAbstractStorage.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAbstractTcs/libSceAbstractTcs.hpp b/core/libs/prx/libSceAbstractTcs/libSceAbstractTcs.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAbstractTcs/libSceAbstractTcs.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAbstractTwitter/libSceAbstractTwitter.hpp b/core/libs/prx/libSceAbstractTwitter/libSceAbstractTwitter.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAbstractTwitter/libSceAbstractTwitter.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAbstractYoutube/libSceAbstractYoutube.hpp b/core/libs/prx/libSceAbstractYoutube/libSceAbstractYoutube.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAbstractYoutube/libSceAbstractYoutube.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAc3Enc/Export.cpp b/core/libs/prx/libSceAc3Enc/Export.cpp index 4ee193119b..13a2ad92a5 100644 --- a/core/libs/prx/libSceAc3Enc/Export.cpp +++ b/core/libs/prx/libSceAc3Enc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAc3Enc/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAc3Enc/include/Api.hpp b/core/libs/prx/libSceAc3Enc/include/Api.hpp new file mode 100644 index 0000000000..02d6fc4467 --- /dev/null +++ b/core/libs/prx/libSceAc3Enc/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAc3EncCreateEncoder(); +int APS5_VABI sceAc3EncDeleteEncoder(); +int APS5_VABI sceAc3EncEncode(); +int APS5_VABI sceAc3EncEncodeAsync(); +int APS5_VABI sceAc3EncFlush(); +int APS5_VABI sceAc3EncIsEncoding(); +int APS5_VABI sceAc3EncWaitEncode(); + +} diff --git a/core/libs/prx/libSceAc3Enc/libSceAc3Enc.hpp b/core/libs/prx/libSceAc3Enc/libSceAc3Enc.hpp new file mode 100644 index 0000000000..7addfec629 --- /dev/null +++ b/core/libs/prx/libSceAc3Enc/libSceAc3Enc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAc3Enc/include/Api.hpp" diff --git a/core/libs/prx/libSceAcm/Export.cpp b/core/libs/prx/libSceAcm/Export.cpp index 325afaa575..ddbe893628 100644 --- a/core/libs/prx/libSceAcm/Export.cpp +++ b/core/libs/prx/libSceAcm/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAcm/include/Api.hpp" #include #include #include @@ -79,19 +80,130 @@ int APS5_VABI sceAcmContextDestroy(AcmContextId context) { return 0; } -int APS5_VABI sceAcm_ConvReverb_SharedInput(void) { +int APS5_VABI sceAcm_ConvReverb_SharedInput(AcmBatchInfo* batch, std::uint32_t mode, AcmReverbInput* input, std::uint32_t outputCount, const AcmReverbFilter* const* filters, const float* gains, const AcmReverbOutput* const* outputs) { return 0; } -int APS5_VABI sceAcm_FFT(void) { +int APS5_VABI sceAcm_FFT(AcmBatchInfo* batch, std::int32_t transformSize, std::uint32_t transformCount, std::int32_t inputFormat, const void* const* inputs, std::int32_t outputFormat, void* const* outputs, std::int32_t flags) { + if (!batch || transformCount == 0 || transformCount > 0x7FFFFFFF || !inputs || !outputs || (flags & 3) == 3 || inputFormat < 0 || inputFormat > 1 || outputFormat < 0 || outputFormat > 1) return SCE_ACM_ERROR_BAD_POINTER; + if (transformSize != 512 && transformSize != 1024 && transformSize != 2048) return SCE_ACM_ERROR_BAD_POINTER; + const bool realInput = (flags & 1) != 0; + if (transformSize == 2048 && !realInput) return SCE_ACM_ERROR_BAD_POINTER; + const std::uint32_t inputBytes = transformSize * (inputFormat == 1 ? 2 : 4); + const std::uint32_t outputBytes = transformSize * (outputFormat == 1 ? 2 : 4); + const std::uint32_t inputBlocks = (inputBytes >> 9) >> (realInput ? 1 : 0); + const std::uint32_t outputBlocks = outputBytes >> 9; + std::uint32_t transformCommand; + std::uint32_t inputCommand; + std::uint32_t outputCommand; + std::uint32_t setupCommand; + std::uint32_t sizeCode; + if (transformSize == 512) { + sizeCode = 4; + transformCommand = realInput ? 7 : 11; + inputCommand = realInput ? 2 * inputFormat : 9 + inputFormat; + outputCommand = 4 | outputFormat; + setupCommand = realInput ? 6 : 12; + } else if (transformSize == 1024) { + sizeCode = 5; + transformCommand = realInput ? 8 : 13; + inputCommand = realInput ? 2 * inputFormat : 14 | inputFormat; + outputCommand = 5 | (2 * outputFormat); + setupCommand = realInput ? 10 : 12; + } else { + sizeCode = 6; + transformCommand = 6; + inputCommand = 2 * inputFormat; + outputCommand = 4 | outputFormat; + setupCommand = 8; + } + auto* buffer = static_cast(batch->buffer); + auto offset = batch->offset; + const auto capacity = batch->buffer_size; + const auto appendWord = [&](std::uint64_t value) { + if (offset > capacity || capacity - offset < sizeof(value)) return false; + std::memcpy(buffer + offset, &value, sizeof(value)); + offset += sizeof(value); + return true; + }; + std::uint64_t header = static_cast(sizeCode) << 8; + if (!(flags & 8)) { + const auto encodedCount = transformCount < 64000 ? transformCount : 64000; + header |= (std::uint64_t{1} << 16) | (static_cast(transformSize) << 8) | (static_cast(flags & 6) << 19) | (static_cast(flags & 1) << 23) | (std::uint64_t{1} << (inputFormat == 1 ? 24 : 26)) | (std::uint64_t{1} << (outputFormat == 1 ? 25 : 27)) | (static_cast(encodedCount) << 28); + } + constexpr int noSpace = static_cast(0x81940009); + constexpr int badAlignment = static_cast(0x8194000A); + if (!appendWord(header) || !appendWord(10 | (setupCommand << 8))) return noSpace; + for (std::uint32_t index = 0; index < transformCount; ++index) { + const auto inputIndex = static_cast(index) * ((flags & 2) ? 2 : 1); + const auto input = reinterpret_cast(inputs[inputIndex]); + const auto secondInput = (flags & 2) ? reinterpret_cast(inputs[inputIndex + 1]) : 0; + if (!(flags & 2) && !input) return SCE_ACM_ERROR_BAD_POINTER; + if ((input | secondInput) & 127) return badAlignment; + const auto inputRecordSize = (flags & 2) ? 24u : 16u; + if (offset > capacity || capacity - offset < inputRecordSize) return noSpace; + appendWord(((flags & 2) ? 7 : 6) | (static_cast(inputBlocks) << 32) | ((flags & 2) ? static_cast(inputBytes / 2) << 48 : 0)); + appendWord(input); + if (flags & 2) appendWord(secondInput); + if (!appendWord(10 | (inputCommand << 8)) || !appendWord(10 | (transformCommand << 8)) || !appendWord(10 | (outputCommand << 8))) return noSpace; + const auto outputIndex = static_cast(index) * ((flags & 4) ? 2 : 1); + const auto output = reinterpret_cast(outputs[outputIndex]); + const auto secondOutput = (flags & 4) ? reinterpret_cast(outputs[outputIndex + 1]) : 0; + if ((output | secondOutput) & 127) return badAlignment; + const auto outputRecordSize = (flags & 4) ? 24u : 16u; + if (offset > capacity || capacity - offset < outputRecordSize) return noSpace; + appendWord(((flags & 4) ? 9 : 8) | (static_cast(outputBlocks) << 32) | ((flags & 4) ? static_cast(outputBytes / 2) << 48 : 0)); + appendWord(output); + if (flags & 4) appendWord(secondOutput); + } + batch->offset = offset; return 0; } -int APS5_VABI sceAcm_Panner(void) { +int APS5_VABI sceAcm_Panner(AcmBatchInfo* batch, std::uint32_t sourceCount, const void* const* sources, std::uint32_t sourceMode, std::uint32_t outputMode, std::uint32_t channelCount, const void* const* sourceParameters, const void* const* sourceMatrices, const void* const* inputChannels, void* const* outputChannels) { + if (!batch || sourceCount == 0 || sourceCount > 65535 || sourceMode > 2 || outputMode > 1 || channelCount == 0 || channelCount > 36 || !sources || !sourceParameters || !sourceMatrices || !outputChannels) return SCE_ACM_ERROR_BAD_POINTER; + std::uintptr_t alignment = reinterpret_cast(sources) | reinterpret_cast(sourceParameters) | reinterpret_cast(sourceMatrices) | reinterpret_cast(inputChannels) | reinterpret_cast(outputChannels); + for (std::uint32_t index = 0; index < sourceCount; ++index) { + if (!sources[index] || !sourceParameters[index] || !sourceMatrices[index]) return SCE_ACM_ERROR_BAD_POINTER; + std::uint32_t parameterMode; + std::memcpy(¶meterMode, sourceParameters[index], sizeof(parameterMode)); + if (parameterMode > 1) return SCE_ACM_ERROR_BAD_POINTER; + alignment |= reinterpret_cast(sources[index]) | reinterpret_cast(sourceParameters[index]) | reinterpret_cast(sourceMatrices[index]); + } + for (std::uint32_t index = 0; index < channelCount; ++index) { + if (!outputChannels[index]) return SCE_ACM_ERROR_BAD_POINTER; + alignment |= reinterpret_cast(outputChannels[index]); + if (inputChannels) alignment |= reinterpret_cast(inputChannels[index]); + } + if (alignment & 127) return static_cast(0x8194000A); + const auto offset = batch->offset; + const auto capacity = batch->buffer_size; + if (offset > capacity || capacity - offset < 8) return static_cast(0x81940009); + auto* buffer = static_cast(batch->buffer); + const auto writeWord = [buffer](std::size_t position, std::uint64_t value) { std::memcpy(buffer + position, &value, sizeof(value)); }; + const auto encodedCount = sourceCount < 255 ? sourceCount : 255; + writeWord(offset, (static_cast(channelCount) << 24) | (static_cast(encodedCount) << 16) | (std::uint64_t{1} << (sourceMode + 32)) | (std::uint64_t{1} << (outputMode + 35)) | 0x300); + if (capacity < 128 || capacity - offset < 152) return static_cast(0x81940009); + const auto descriptorOffset = capacity - 128; + std::memset(buffer + descriptorOffset, 0, 128); + writeWord(descriptorOffset, sourceCount); + writeWord(descriptorOffset + 8, reinterpret_cast(inputChannels)); + writeWord(descriptorOffset + 16, reinterpret_cast(outputChannels)); + writeWord(descriptorOffset + 24, reinterpret_cast(sources)); + writeWord(descriptorOffset + 32, sourceMode | (static_cast(outputMode) << 32)); + writeWord(descriptorOffset + 40, channelCount); + writeWord(descriptorOffset + 48, reinterpret_cast(sourceParameters)); + writeWord(descriptorOffset + 56, reinterpret_cast(sourceMatrices)); + writeWord(offset + 8, 0x8000000000000D); + writeWord(offset + 16, reinterpret_cast(buffer + descriptorOffset)); + if (capacity - offset < 160) return static_cast(0x81940009); + writeWord(offset + 24, 10); + batch->offset = offset + 32; + batch->buffer_size = descriptorOffset; return 0; } -int APS5_VABI sceAcmBatchJobPriority(void) { +int APS5_VABI sceAcmBatchJobPriority(AcmBatchInfo* batch, std::uint32_t priority) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -106,7 +218,6 @@ int APS5_VABI sceAcm_IFFT() { return 0; } - int APS5_VABI sceAcmBatchJobNotification() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceAcm/include/Api.hpp b/core/libs/prx/libSceAcm/include/Api.hpp new file mode 100644 index 0000000000..c4da065395 --- /dev/null +++ b/core/libs/prx/libSceAcm/include/Api.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAcmBatchStartBuffer(AcmContextId context, const void* batchCommands, size_t batchSize, AcmBatchError* batchError, AcmBatchId* batch); +int APS5_VABI sceAcmBatchStartBuffers(AcmContextId context, uint32_t batchInfoCount, const AcmBatchInfo* const batch_info[], AcmBatchError* batchError, AcmBatchId* batch); +int APS5_VABI sceAcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout); +int APS5_VABI sceAcmContextCreate(AcmContextId* context); +int APS5_VABI sceAcmContextDestroy(AcmContextId context); +int APS5_VABI sceAcm_ConvReverb_SharedInput(AcmBatchInfo* batch, std::uint32_t mode, AcmReverbInput* input, std::uint32_t outputCount, const AcmReverbFilter* const* filters, const float* gains, const AcmReverbOutput* const* outputs); +int APS5_VABI sceAcm_FFT(AcmBatchInfo* batch, std::int32_t transformSize, std::uint32_t transformCount, std::int32_t inputFormat, const void* const* inputs, std::int32_t outputFormat, void* const* outputs, std::int32_t flags); +int APS5_VABI sceAcm_Panner(AcmBatchInfo* batch, std::uint32_t sourceCount, const void* const* sources, std::uint32_t sourceMode, std::uint32_t outputMode, std::uint32_t channelCount, const void* const* sourceParameters, const void* const* sourceMatrices, const void* const* inputChannels, void* const* outputChannels); +int APS5_VABI sceAcmBatchJobPriority(AcmBatchInfo* batch, std::uint32_t priority); +int APS5_VABI sceAcm_ConvReverb_SharedIr(); +int APS5_VABI sceAcm_IFFT(); +int APS5_VABI sceAcmBatchJobNotification(); + +} diff --git a/core/libs/prx/libSceAcm/libSceAcm.hpp b/core/libs/prx/libSceAcm/libSceAcm.hpp new file mode 100644 index 0000000000..a8b95eefbf --- /dev/null +++ b/core/libs/prx/libSceAcm/libSceAcm.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAcm/include/Api.hpp" diff --git a/core/libs/prx/libSceAgc/Acb/include/Control.hpp b/core/libs/prx/libSceAgc/Acb/include/Control.hpp index c22dc4e61e..e1862076c3 100644 --- a/core/libs/prx/libSceAgc/Acb/include/Control.hpp +++ b/core/libs/prx/libSceAgc/Acb/include/Control.hpp @@ -1,4 +1,26 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_ACB_INCLUDE_CONTROL_HPP #define CORE_LIBS_PRX_LIBSCEAGC_ACB_INCLUDE_CONTROL_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcAcbAtomicGds_0900(CommandBuffer* buf, std::uint32_t control, std::uint32_t mode, std::uint32_t gdsOp, std::uint32_t src, std::uint16_t offset, std::uint16_t index, std::uint32_t mask, std::uint32_t data, std::uint64_t compareData, std::uint64_t extraData); +std::uint32_t* APS5_VABI sceAgcAcbJump(CommandBuffer* buf, std::uint8_t cachePolicy, const std::uint32_t* target, std::uint32_t sizeInDwords); +uint32_t APS5_VABI sceAgcAcbJumpGetSize(void); +uint32_t* APS5_VABI sceAgcAcbResetQueue(CommandBuffer* buf, uint32_t op, uint32_t value); +std::uint32_t* APS5_VABI sceAgcAcbRewind(CommandBuffer* buf, std::uint32_t initialState); +std::uint32_t APS5_VABI sceAgcAcbRewindGetSize(); +std::uint32_t* APS5_VABI sceAgcAcbWaitUntilSafeForRendering(CommandBuffer* buf, std::uint32_t videoOutHandle, std::uint32_t displayBufferIndex); +std::uint32_t* APS5_VABI sceAgcAcbSetFlip(CommandBuffer* buf, std::uint32_t videoOutHandle, std::int32_t displayBufferIndex, std::uint32_t flipMode, std::int64_t flipArg); +std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadsActive(CommandBuffer* buf, std::uint32_t streamId, const std::uint32_t* workloadIds, std::uint32_t workloadCount); +std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadComplete(CommandBuffer* buf, std::uint32_t streamId, std::uint32_t workloadId); +std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadStreamInactive(CommandBuffer* buf, std::uint32_t streamId); +uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, const char* str, uint32_t color); +uint32_t* APS5_VABI sceAgcAcbPopMarker(CommandBuffer* buf); +uint32_t* APS5_VABI sceAgcAcbSetMarker(CommandBuffer* buf, const char* str, uint32_t color); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Acb/include/Dispatch.hpp b/core/libs/prx/libSceAgc/Acb/include/Dispatch.hpp index e207781ef7..0915d7aad8 100644 --- a/core/libs/prx/libSceAgc/Acb/include/Dispatch.hpp +++ b/core/libs/prx/libSceAgc/Acb/include/Dispatch.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_ACB_INCLUDE_DISPATCH_HPP #define CORE_LIBS_PRX_LIBSCEAGC_ACB_INCLUDE_DISPATCH_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +uint32_t* APS5_VABI sceAgcAcbDispatchIndirect(CommandBuffer* buf, const volatile void* indirectArgs, uint32_t modifier); +std::uint32_t APS5_VABI sceAgcAcbDispatchIndirectGetSize(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Acb/include/Memory.hpp b/core/libs/prx/libSceAgc/Acb/include/Memory.hpp index 78e8668cb0..93949715e6 100644 --- a/core/libs/prx/libSceAgc/Acb/include/Memory.hpp +++ b/core/libs/prx/libSceAgc/Acb/include/Memory.hpp @@ -1,4 +1,24 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_ACB_INCLUDE_MEMORY_HPP #define CORE_LIBS_PRX_LIBSCEAGC_ACB_INCLUDE_MEMORY_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcAcbAcquireMem(CommandBuffer* buf, std::uint32_t gcrControl, const volatile void* base, std::uint64_t sizeBytes, std::uint32_t pollCycles); +uint32_t APS5_VABI sceAgcAcbAcquireMemGetSize(void); +uint32_t* APS5_VABI sceAgcAcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t dstCachePolicy, uint64_t dstAddress, uint8_t src, uint8_t srcCachePolicy, uint64_t srcAddressOrImmediate, uint8_t itemSize, uint8_t writeConfirm); +std::uint64_t APS5_VABI sceAgcAcbCopyDataGetSize(); +std::uint32_t* APS5_VABI sceAgcAcbDmaData(CommandBuffer* buf, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddress, std::uint32_t numBytes, std::uint8_t waitForPrevious, std::uint8_t writeConfirm); +std::uint32_t APS5_VABI sceAgcAcbDmaDataGetSize(); +std::uint32_t* APS5_VABI sceAgcAcbAtomicMem(CommandBuffer* buf, std::uint8_t atomicOp, std::uint8_t command, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t srcData, std::uint64_t compareData, std::uint16_t loopInterval); +std::uint32_t APS5_VABI sceAgcAcbAtomicMemGetSize(); +std::uint32_t APS5_VABI sceAgcAcbAtomicGdsGetSize(); +std::uint32_t* APS5_VABI sceAgcAcbPrimeUtcl2(CommandBuffer* buf, const volatile void* address, std::uint32_t sizeInBytes); +std::uint32_t APS5_VABI sceAgcAcbPrimeUtcl2GetSize(); +std::uint32_t* APS5_VABI sceAgcAcbWriteData(CommandBuffer* buf, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t numDwords, std::uint8_t increment, std::uint8_t writeConfirm); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Acb/include/Sync.hpp b/core/libs/prx/libSceAgc/Acb/include/Sync.hpp index ca3084b8b8..fb7a826139 100644 --- a/core/libs/prx/libSceAgc/Acb/include/Sync.hpp +++ b/core/libs/prx/libSceAgc/Acb/include/Sync.hpp @@ -1,4 +1,20 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_ACB_INCLUDE_SYNC_HPP #define CORE_LIBS_PRX_LIBSCEAGC_ACB_INCLUDE_SYNC_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +uint32_t* APS5_VABI sceAgcAcbCondExec(CommandBuffer* buf, const volatile uint32_t* address, uint32_t numDwords); +uint32_t APS5_VABI sceAgcAcbCondExecGetSize(void); +std::uint32_t* APS5_VABI sceAgcAcbWaitRegMem(CommandBuffer* buf, std::uint8_t size, std::uint8_t compareFunction, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t reference, std::uint64_t mask, std::uint32_t pollCycles); +std::uint64_t APS5_VABI sceAgcAcbWaitOnAddressGetSize(std::uint8_t size); +std::uint32_t* APS5_VABI sceAgcAcbEventWrite(CommandBuffer* buf, std::uint8_t eventType, const volatile void* address); +std::uint64_t APS5_VABI sceAgcAcbEventWriteGetSize(std::uint8_t eventType); +std::uint32_t APS5_VABI sceAgcAcbQueueEndOfShaderActionGetSize(); +std::uint32_t* APS5_VABI sceAgcAcbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint8_t waitForMailbox, std::uint8_t signalType, std::uint8_t operation); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Acb/src/Control.cpp b/core/libs/prx/libSceAgc/Acb/src/Control.cpp index 567bfd5e18..1dd0314800 100644 --- a/core/libs/prx/libSceAgc/Acb/src/Control.cpp +++ b/core/libs/prx/libSceAgc/Acb/src/Control.cpp @@ -102,6 +102,18 @@ uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, const char* str, uin return Agc::Marker::Push(buf, str, __func__); } +uint32_t* APS5_VABI sceAgcAcbPushMarkerSpan(CommandBuffer* buf, const char* str, uint32_t length, uint32_t color) { + (void)color; + return Agc::Marker::PushSpan(buf, str, length, __func__); +} + +uint32_t* APS5_VABI sceAgcAcbSetMarkerSpan(CommandBuffer* buf, const char* str, uint32_t length, uint32_t color) { + (void)color; + auto* packet = Agc::Marker::PushSpan(buf, str, length, __func__); + Agc::Marker::Pop(buf, __func__); + return packet; +} + uint32_t* APS5_VABI sceAgcAcbPopMarker(CommandBuffer* buf) { return Agc::Marker::Pop(buf, __func__); } diff --git a/core/libs/prx/libSceAgc/Acb/src/Sync.cpp b/core/libs/prx/libSceAgc/Acb/src/Sync.cpp index eee05b37c4..cb4ff7f5a6 100644 --- a/core/libs/prx/libSceAgc/Acb/src/Sync.cpp +++ b/core/libs/prx/libSceAgc/Acb/src/Sync.cpp @@ -44,10 +44,7 @@ std::uint32_t APS5_VABI sceAgcAcbQueueEndOfShaderActionGetSize() { return 32; } -std::uint32_t* APS5_VABI sceAgcAcbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address) { - const std::uint8_t operation = action != 0 ? 7u : 6u; - const std::uint8_t signalType = clientCode & 1u; - const std::uint8_t waitForMailbox = (clientCode >> 1u) & 1u; +std::uint32_t* APS5_VABI sceAgcAcbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint8_t waitForMailbox, std::uint8_t signalType, std::uint8_t operation) { return Agc::Command::WriteMemSemaphore(buf, address, waitForMailbox, signalType, operation, __func__); } diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index faf498b571..9c26307068 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -157,7 +157,7 @@ add_test_executable(agc_register_command_tests EXCLUDE_FROM_ALL Patch/src/IndirectRegisters.cpp ) target_include_directories(agc_register_command_tests PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(agc_register_command_tests PRIVATE libc) +target_link_libraries(agc_register_command_tests PRIVATE libSceAgcDriver libc) configure_windows_unwind(agc_register_command_tests) if(BUILD_TESTING) set_target_properties(agc_register_command_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) @@ -176,7 +176,7 @@ add_test_executable(agc_cb_cond_write_tests EXCLUDE_FROM_ALL Cb/src/Branch.cpp ) target_include_directories(agc_cb_cond_write_tests PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(agc_cb_cond_write_tests PRIVATE libc) +target_link_libraries(agc_cb_cond_write_tests PRIVATE libSceAgcDriver libc) configure_windows_unwind(agc_cb_cond_write_tests) if(BUILD_TESTING) set_target_properties(agc_cb_cond_write_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) @@ -220,7 +220,7 @@ add_test_executable(agc_branch_patch_tests EXCLUDE_FROM_ALL Patch/src/Branch.cpp ) target_include_directories(agc_branch_patch_tests PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(agc_branch_patch_tests PRIVATE libc) +target_link_libraries(agc_branch_patch_tests PRIVATE libSceAgcDriver libc) configure_windows_unwind(agc_branch_patch_tests) if(BUILD_TESTING) set_target_properties(agc_branch_patch_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) @@ -240,7 +240,7 @@ add_test_executable(agc_predication_tests EXCLUDE_FROM_ALL Predication/src/Predication.cpp ) target_include_directories(agc_predication_tests PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(agc_predication_tests PRIVATE libc) +target_link_libraries(agc_predication_tests PRIVATE libSceAgcDriver libc) configure_windows_unwind(agc_predication_tests) if(BUILD_TESTING) set_target_properties(agc_predication_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) @@ -267,6 +267,19 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_instrumentation_query_size_tests EXCLUDE_FROM_ALL tests/InstrumentationQuerySize.cpp) +target_include_directories(agc_instrumentation_query_size_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_instrumentation_query_size_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_instrumentation_query_size_tests) +if(BUILD_TESTING) + set_target_properties(agc_instrumentation_query_size_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_instrumentation_query_size_tests COMMAND agc_instrumentation_query_size_tests) + if(WIN32) + set_tests_properties(agc_instrumentation_query_size_tests PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_context_state_size_tests EXCLUDE_FROM_ALL tests/ContextStateSize.cpp) target_include_directories(agc_context_state_size_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_context_state_size_tests PRIVATE libSceAgc libc) @@ -357,7 +370,7 @@ add_test_executable(agc_dma_data_tests EXCLUDE_FROM_ALL DcbFlow/src/Memory.cpp ) target_include_directories(agc_dma_data_tests PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(agc_dma_data_tests PRIVATE libc) +target_link_libraries(agc_dma_data_tests PRIVATE libSceAgcDriver libc) configure_windows_unwind(agc_dma_data_tests) if(BUILD_TESTING) set_target_properties(agc_dma_data_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) @@ -377,7 +390,7 @@ add_test_executable(agc_write_data_patch_tests EXCLUDE_FROM_ALL Patch/src/WaitRegMem.cpp ) target_include_directories(agc_write_data_patch_tests PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(agc_write_data_patch_tests PRIVATE libc) +target_link_libraries(agc_write_data_patch_tests PRIVATE libSceAgcDriver libc) configure_windows_unwind(agc_write_data_patch_tests) if(BUILD_TESTING) set_target_properties(agc_write_data_patch_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) @@ -397,7 +410,7 @@ add_test_executable(agc_prime_utcl2_tests EXCLUDE_FROM_ALL DcbState/src/Display.cpp ) target_include_directories(agc_prime_utcl2_tests PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(agc_prime_utcl2_tests PRIVATE libc) +target_link_libraries(agc_prime_utcl2_tests PRIVATE libSceAgcDriver libc) configure_windows_unwind(agc_prime_utcl2_tests) if(BUILD_TESTING) set_target_properties(agc_prime_utcl2_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) @@ -418,7 +431,7 @@ add_test_executable(agc_cond_exec_patch_tests EXCLUDE_FROM_ALL Patch/src/CondExec.cpp ) target_include_directories(agc_cond_exec_patch_tests PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(agc_cond_exec_patch_tests PRIVATE libc) +target_link_libraries(agc_cond_exec_patch_tests PRIVATE libSceAgcDriver libc) configure_windows_unwind(agc_cond_exec_patch_tests) if(BUILD_TESTING) set_target_properties(agc_cond_exec_patch_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) diff --git a/core/libs/prx/libSceAgc/Cb/include/Branch.hpp b/core/libs/prx/libSceAgc/Cb/include/Branch.hpp index 5c74f02461..855d78d8ee 100644 --- a/core/libs/prx/libSceAgc/Cb/include/Branch.hpp +++ b/core/libs/prx/libSceAgc/Cb/include/Branch.hpp @@ -1,4 +1,16 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_CB_INCLUDE_BRANCH_HPP #define CORE_LIBS_PRX_LIBSCEAGC_CB_INCLUDE_BRANCH_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcCbBranch(CommandBuffer* buf, std::uint8_t mode, std::uint8_t compareFunction, const volatile std::uint64_t* compareAddress, std::uint64_t mask, std::uint64_t reference, std::uint8_t cachePolicy1, const volatile std::uint32_t* buffer1, std::uint32_t sizeInDwords1, std::uint8_t cachePolicy2, const volatile std::uint32_t* buffer2, std::uint32_t sizeInDwords2); +std::uint32_t APS5_VABI sceAgcCbBranchGetSize(); +std::uint32_t* APS5_VABI sceAgcCbCondWrite(CommandBuffer* buf, std::uint32_t compareFunction, std::uint32_t writeSpace, const volatile void* writeAddress, std::uint32_t writeData, const volatile void* pollAddress, std::uint32_t reference, std::uint32_t mask); +std::uint32_t APS5_VABI sceAgcCbCondWriteGetSize(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Cb/include/Dispatch.hpp b/core/libs/prx/libSceAgc/Cb/include/Dispatch.hpp index 5881aaf5a9..9f741d7e2e 100644 --- a/core/libs/prx/libSceAgc/Cb/include/Dispatch.hpp +++ b/core/libs/prx/libSceAgc/Cb/include/Dispatch.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_CB_INCLUDE_DISPATCH_HPP #define CORE_LIBS_PRX_LIBSCEAGC_CB_INCLUDE_DISPATCH_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcCbDispatch(CommandBuffer* buf, std::uint32_t threadGroupX, std::uint32_t threadGroupY, std::uint32_t threadGroupZ, std::uint32_t modifier); +uint32_t APS5_VABI sceAgcCbDispatchGetSize(void); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Cb/include/Registers.hpp b/core/libs/prx/libSceAgc/Cb/include/Registers.hpp index e5634b8ad6..d41e1beb42 100644 --- a/core/libs/prx/libSceAgc/Cb/include/Registers.hpp +++ b/core/libs/prx/libSceAgc/Cb/include/Registers.hpp @@ -1,4 +1,20 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_CB_INCLUDE_REGISTERS_HPP #define CORE_LIBS_PRX_LIBSCEAGC_CB_INCLUDE_REGISTERS_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcCbSetUcRegisterRangeDirect(CommandBuffer* buf, std::uint32_t offset, const std::uint32_t* values, std::uint32_t numValues); +std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuffer* buf, std::uint32_t offset, const std::uint32_t* values, std::uint32_t numValues); +uint32_t APS5_VABI sceAgcCbSetShRegisterRangeDirectGetSize(uint32_t numValues); +std::uint32_t APS5_VABI sceAgcCbSetUcRegisterRangeDirectGetSize(std::uint32_t numValues); +std::uint32_t* APS5_VABI sceAgcCbSetShRegistersDirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs); +std::uint32_t APS5_VABI sceAgcCbSetShRegistersDirectGetSize(std::uint32_t numRegs); +std::uint32_t* APS5_VABI sceAgcCbSetUcRegistersDirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs); +std::uint32_t APS5_VABI sceAgcCbSetUcRegistersDirectGetSize(std::uint32_t numRegs); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Cb/include/Sync.hpp b/core/libs/prx/libSceAgc/Cb/include/Sync.hpp index a070c6a5fc..d3be5f9083 100644 --- a/core/libs/prx/libSceAgc/Cb/include/Sync.hpp +++ b/core/libs/prx/libSceAgc/Cb/include/Sync.hpp @@ -4,10 +4,19 @@ #include "SceTypes.hpp" #include -struct CommandBuffer; - extern "C" { std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint32_t operation, std::uint32_t modifier, std::uint32_t value); } +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcCbNop_nid_postfix(CommandBuffer* buf, std::uint32_t sizeInDwords); +uint32_t APS5_VABI sceAgcCbNopGetSize(uint32_t sizeInDwords); +std::uint32_t APS5_VABI sceAgcCbQueueEndOfPipeActionGetSize(); +std::uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer* buf, std::uint8_t action, std::uint16_t gcrControl, std::uint8_t dst, std::uint8_t cachePolicy, const volatile Label* address, std::uint8_t dataSelect, std::uint64_t data, std::uint16_t gdsOffset, std::uint16_t gdsSize, std::uint8_t interrupt, std::uint32_t interruptContextId); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Command/src/Packet.cpp b/core/libs/prx/libSceAgc/Command/src/Packet.cpp index 6ec0e12165..13036e5d12 100644 --- a/core/libs/prx/libSceAgc/Command/src/Packet.cpp +++ b/core/libs/prx/libSceAgc/Command/src/Packet.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include @@ -67,6 +68,7 @@ void Reserve(CommandBuffer* buffer, std::uint32_t count, const char* function) { std::uint32_t* Allocate(CommandBuffer* buffer, std::uint32_t count, const char* function) { Reserve(buffer, count, function); auto* result = buffer->cursor_up; + AgcDriverPrepareCommandWrite_nid_no_patch(reinterpret_cast(result), static_cast(count) * sizeof(std::uint32_t)); buffer->cursor_up += count; return result; } @@ -113,17 +115,23 @@ std::uint32_t* WriteRegisters(CommandBuffer* buffer, std::uint32_t opcode, const Require(buffer != nullptr, function, "null command buffer"); return buffer->cursor_up; } - CheckAddress(reinterpret_cast(registers), 4, function); + CheckAddress(reinterpret_cast(registers), 1, function); + const auto* bytes = reinterpret_cast(const_cast(registers)); + const auto entryAt = [&](std::uint32_t index) { + ShaderRegister entry; + std::memcpy(&entry, bytes + static_cast(index) * sizeof(ShaderRegister), sizeof(entry)); + return entry; + }; std::vector snapshot; if (snapshotAll) { snapshot.reserve(count); for (std::uint32_t i = 0; i < count; ++i) { - snapshot.push_back({registers[i].offset, registers[i].value}); + snapshot.push_back(entryAt(i)); CheckBits(snapshot.back().offset, 0xffffu, function); } } - const auto offsetAt = [&](std::uint32_t index) { return snapshotAll ? snapshot[index].offset : registers[index].offset; }; - const auto valueAt = [&](std::uint32_t index) { return snapshotAll ? snapshot[index].value : registers[index].value; }; + const auto offsetAt = [&](std::uint32_t index) { return snapshotAll ? snapshot[index].offset : entryAt(index).offset; }; + const auto valueAt = [&](std::uint32_t index) { return snapshotAll ? snapshot[index].value : entryAt(index).value; }; std::uint32_t* first = nullptr; std::uint32_t index = 0; while (index < count) { diff --git a/core/libs/prx/libSceAgc/DcbDraw/include/DrawIndexed.hpp b/core/libs/prx/libSceAgc/DcbDraw/include/DrawIndexed.hpp index e134de925d..1eede737f2 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/include/DrawIndexed.hpp +++ b/core/libs/prx/libSceAgc/DcbDraw/include/DrawIndexed.hpp @@ -1,4 +1,24 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_DRAWINDEXED_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_DRAWINDEXED_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcDcbDrawIndex(CommandBuffer* buf, std::uint32_t indexCount, const volatile void* indexAddress, std::uint64_t modifier); +std::uint32_t APS5_VABI sceAgcDcbDrawIndexGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbDrawIndexAuto(CommandBuffer* buf, std::uint32_t indexCount, std::uint64_t modifier); +std::uint32_t APS5_VABI sceAgcDcbDrawIndexAutoGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbDrawIndexOffset(CommandBuffer* buf, std::uint32_t indexOffset, std::uint32_t indexCount, std::uint64_t modifier); +uint32_t APS5_VABI sceAgcDcbDrawIndexOffsetGetSize(void); +uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirect(CommandBuffer* buf, uint32_t dataOffsetInBytes, uint64_t modifier); +std::uint32_t APS5_VABI sceAgcDcbDrawIndexIndirectGetSize(); +uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirectMulti(CommandBuffer* buf, uint32_t dataOffsetInBytes, uint32_t countIndirect, uint32_t maxCountOrCount, const volatile void* countAddr, uint32_t strideInBytes, uint64_t modifier); +std::uint32_t APS5_VABI sceAgcDcbDrawIndexIndirectMultiGetSize(); +uint32_t* APS5_VABI sceAgcDcbDrawIndexMultiInstanced(CommandBuffer* buf, uint32_t indexCount, const volatile void* indexAddr, const volatile void* objectIds, uint32_t instanceCount, uint64_t modifier); +uint32_t APS5_VABI sceAgcDcbDrawIndexMultiInstancedGetSize(void); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbDraw/include/DrawNonIndexed.hpp b/core/libs/prx/libSceAgc/DcbDraw/include/DrawNonIndexed.hpp index 19705ed1f2..5ee0a26dc5 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/include/DrawNonIndexed.hpp +++ b/core/libs/prx/libSceAgc/DcbDraw/include/DrawNonIndexed.hpp @@ -1,4 +1,16 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_DRAWNONINDEXED_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_DRAWNONINDEXED_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcDcbDrawIndirect(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint64_t modifier); +std::uint32_t APS5_VABI sceAgcDcbDrawIndirectGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbDrawIndirectMulti(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint32_t countIndirect, std::uint32_t maxCountOrCount, const volatile void* countAddress, std::uint32_t strideInBytes, std::uint64_t modifier); +std::uint32_t APS5_VABI sceAgcDcbDrawIndirectMultiGetSize(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbDraw/include/IndexBuffer.hpp b/core/libs/prx/libSceAgc/DcbDraw/include/IndexBuffer.hpp index b90a2a40d6..652629217a 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/include/IndexBuffer.hpp +++ b/core/libs/prx/libSceAgc/DcbDraw/include/IndexBuffer.hpp @@ -1,4 +1,21 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_INDEXBUFFER_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_INDEXBUFFER_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcDcbSetIndexBuffer(CommandBuffer* buf, std::uint64_t indexAddress); +std::uint32_t APS5_VABI sceAgcDcbSetIndexBufferGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbSetIndexCount(CommandBuffer* buf, std::uint32_t indexCount); +std::uint32_t APS5_VABI sceAgcDcbSetIndexCountGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbSetIndexSize(CommandBuffer* buf, std::uint8_t indexSize, std::uint8_t cachePolicy); +std::uint32_t APS5_VABI sceAgcDcbSetIndexSizeGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbSetIndexSize_0600(CommandBuffer* buf, std::uint8_t indexSize, std::uint8_t cachePolicy, std::uint8_t perInstanceObjectId); +std::uint32_t* APS5_VABI sceAgcDcbSetIndexIndirectArgs(CommandBuffer* buf, std::uint64_t attributeAddress, std::uint32_t attributeIndex); +std::uint32_t APS5_VABI sceAgcDcbSetIndexIndirectArgsGetSize(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbDraw/include/Instancing.hpp b/core/libs/prx/libSceAgc/DcbDraw/include/Instancing.hpp index b67b2b5bac..8f74423854 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/include/Instancing.hpp +++ b/core/libs/prx/libSceAgc/DcbDraw/include/Instancing.hpp @@ -1,4 +1,15 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_INSTANCING_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_INSTANCING_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcDcbSetNumInstances(CommandBuffer* buf, std::uint32_t numInstances); +std::uint32_t APS5_VABI sceAgcDcbSetNumInstancesGetSize(); +uint32_t* APS5_VABI sceAgcDcbSetBaseIndirectArgs(CommandBuffer* buf, uint32_t shaderType, const volatile void* indirectBaseAddr); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbDraw/include/Stats.hpp b/core/libs/prx/libSceAgc/DcbDraw/include/Stats.hpp index a2bd44d8bc..de4d9fcf72 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/include/Stats.hpp +++ b/core/libs/prx/libSceAgc/DcbDraw/include/Stats.hpp @@ -1,4 +1,15 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_STATS_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBDRAW_INCLUDE_STATS_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcDcbGetLodStats(CommandBuffer* buf, std::uint8_t cachePolicy, const volatile void* buffer, std::uint32_t bufferSizeInBytes, std::uint32_t resetCount, std::uint8_t forceReset, std::uint8_t reportAndReset, std::uint32_t reportingIntervalIn100kClocks); +std::uint32_t APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize(std::int32_t queryType); +std::uint32_t APS5_VABI sceAgcDcbEndOcclusionQueryGetSize(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbDraw/src/Stats.cpp b/core/libs/prx/libSceAgc/DcbDraw/src/Stats.cpp index b3ae56e199..5a8ebdd44d 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/src/Stats.cpp +++ b/core/libs/prx/libSceAgc/DcbDraw/src/Stats.cpp @@ -21,14 +21,13 @@ std::uint32_t* APS5_VABI sceAgcDcbGetLodStats(CommandBuffer* buf, std::uint8_t c return Agc::Command::Emit(buf, 0x10u, {bufferSizeInBytes, static_cast(address), static_cast(address >> 32u), control}, __func__); } -std::uint32_t APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint32_t APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize(std::int32_t queryType) { + if (queryType == 1) return 0x120u; + return queryType == 0 ? 0x10u : 0u; } std::uint32_t APS5_VABI sceAgcDcbEndOcclusionQueryGetSize() { - NotImplemented_nid_no_patch(__func__); - return 0; + return 0x10u; } } diff --git a/core/libs/prx/libSceAgc/DcbFlow/include/Control.hpp b/core/libs/prx/libSceAgc/DcbFlow/include/Control.hpp index 6ded533662..2890339f5b 100644 --- a/core/libs/prx/libSceAgc/DcbFlow/include/Control.hpp +++ b/core/libs/prx/libSceAgc/DcbFlow/include/Control.hpp @@ -9,4 +9,18 @@ std::uint32_t* APS5_VABI sceAgcDcbAtomicGds(CommandBuffer* buf, std::uint32_t co std::uint32_t* APS5_VABI sceAgcDcbAtomicGds_0900(CommandBuffer* buf, std::uint32_t control, std::uint32_t mode, std::uint32_t gdsOp, std::uint32_t src, std::uint16_t offset, std::uint16_t index, std::uint32_t mask, std::uint32_t data, std::uint64_t compareData, std::uint64_t extraData); } +#include "SceShaders.hpp" + +extern "C" { + +uint32_t* APS5_VABI sceAgcDcbJump(CommandBuffer* buf, uint8_t mode, uint8_t cachePolicy, const uint32_t* target, uint32_t sizeInDwords); +std::uint32_t APS5_VABI sceAgcDcbJumpGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, std::uint32_t op, std::uint32_t state); +std::uint32_t* APS5_VABI sceAgcDcbClearState(CommandBuffer* buf, std::uint32_t command); +uint32_t* APS5_VABI sceAgcDcbRewind(CommandBuffer* buf, uint32_t initialState); +uint32_t APS5_VABI sceAgcDcbRewindGetSize(void); +uint32_t* APS5_VABI sceAgcDcbWaitUntilSafeForRendering(CommandBuffer* buf, uint32_t videoOutHandle, uint32_t displayBufferIndex); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbFlow/include/Dispatch.hpp b/core/libs/prx/libSceAgc/DcbFlow/include/Dispatch.hpp index 025f84ad5c..4266a8cf60 100644 --- a/core/libs/prx/libSceAgc/DcbFlow/include/Dispatch.hpp +++ b/core/libs/prx/libSceAgc/DcbFlow/include/Dispatch.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBFLOW_INCLUDE_DISPATCH_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBFLOW_INCLUDE_DISPATCH_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +uint32_t* APS5_VABI sceAgcDcbDispatchIndirect(CommandBuffer* buf, uint32_t dataOffsetInBytes, uint32_t flags); +std::uint32_t APS5_VABI sceAgcDcbDispatchIndirectGetSize(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbFlow/include/Memory.hpp b/core/libs/prx/libSceAgc/DcbFlow/include/Memory.hpp index f089c9a83e..e9202cd6b5 100644 --- a/core/libs/prx/libSceAgc/DcbFlow/include/Memory.hpp +++ b/core/libs/prx/libSceAgc/DcbFlow/include/Memory.hpp @@ -9,4 +9,20 @@ std::uint32_t* APS5_VABI sceAgcDcbAcquireMem(CommandBuffer* buf, std::uint8_t en std::uint32_t APS5_VABI sceAgcDcbAtomicGdsGetSize(); } +#include "SceShaders.hpp" + +extern "C" { + +uint32_t APS5_VABI sceAgcDcbAcquireMemGetSize(void); +uint32_t* APS5_VABI sceAgcDcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t dstCachePolicy, uint64_t dstAddress, uint8_t src, uint8_t srcCachePolicy, uint64_t srcAddressOrImmediate, uint8_t itemSize, uint8_t writeConfirm); +std::uint64_t APS5_VABI sceAgcDcbCopyDataGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbDmaData(CommandBuffer* buf, std::uint8_t engine, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddress, std::uint32_t numBytes, std::uint8_t waitForPrevious, std::uint8_t writeConfirm, std::uint8_t blockEngine); +std::uint32_t APS5_VABI sceAgcDcbDmaDataGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbAtomicMem(CommandBuffer* buf, std::uint8_t atomicOp, std::uint8_t command, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t srcData, std::uint64_t compareData, std::uint16_t loopInterval); +std::uint32_t APS5_VABI sceAgcDcbAtomicMemGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbWriteData(CommandBuffer* buf, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t numDwords, std::uint8_t increment, std::uint8_t writeConfirm); +uint32_t APS5_VABI sceAgcDcbWriteDataGetSize(uint32_t numDwords); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbFlow/include/Sync.hpp b/core/libs/prx/libSceAgc/DcbFlow/include/Sync.hpp index 6cad00334a..fa07f43e62 100644 --- a/core/libs/prx/libSceAgc/DcbFlow/include/Sync.hpp +++ b/core/libs/prx/libSceAgc/DcbFlow/include/Sync.hpp @@ -1,5 +1,21 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBFLOW_INCLUDE_SYNC_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBFLOW_INCLUDE_SYNC_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +uint32_t* APS5_VABI sceAgcDcbCondExec(CommandBuffer* buf, const volatile uint32_t* address, uint32_t numDwords); +uint32_t APS5_VABI sceAgcDcbCondExecGetSize(void); +std::uint32_t* APS5_VABI sceAgcDcbWaitRegMem(CommandBuffer* buf, std::uint8_t size, std::uint8_t compareFunction, std::uint8_t operation, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t reference, std::uint64_t mask, std::uint32_t pollCycles); +uint32_t APS5_VABI sceAgcDcbWaitOnAddressGetSize(uint32_t size); +std::uint32_t* APS5_VABI sceAgcDcbEventWrite(CommandBuffer* buf, std::uint8_t eventType, const volatile void* address); +std::uint64_t APS5_VABI sceAgcDcbEventWriteGetSize(std::uint8_t eventType); +std::uint32_t* APS5_VABI sceAgcDcbStallCommandBufferParser(CommandBuffer* buf); +std::uint32_t APS5_VABI sceAgcDcbStallCommandBufferParserGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint8_t waitForMailbox, std::uint8_t signalType, std::uint8_t operation); + +} #endif diff --git a/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp b/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp index 96c45f0d3c..d3fb4b534b 100644 --- a/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp +++ b/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp @@ -52,10 +52,7 @@ std::uint32_t APS5_VABI sceAgcDcbStallCommandBufferParserGetSize() { return 8; } -std::uint32_t* APS5_VABI sceAgcDcbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address) { - const std::uint8_t operation = action != 0 ? 7u : 6u; - const std::uint8_t signalType = clientCode & 1u; - const std::uint8_t waitForMailbox = (clientCode >> 1u) & 1u; +std::uint32_t* APS5_VABI sceAgcDcbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint8_t waitForMailbox, std::uint8_t signalType, std::uint8_t operation) { return Agc::Command::WriteMemSemaphore(buf, address, waitForMailbox, signalType, operation, __func__); } diff --git a/core/libs/prx/libSceAgc/DcbState/include/ContextState.hpp b/core/libs/prx/libSceAgc/DcbState/include/ContextState.hpp index 99d37d3c6a..19d2e85ba7 100644 --- a/core/libs/prx/libSceAgc/DcbState/include/ContextState.hpp +++ b/core/libs/prx/libSceAgc/DcbState/include/ContextState.hpp @@ -4,8 +4,6 @@ #include "SceTypes.hpp" #include -struct CommandBuffer; - extern "C" { uint32_t* APS5_VABI sceAgcDcbContextStateOp(CommandBuffer* buf, uint32_t operation); @@ -14,4 +12,15 @@ uint64_t APS5_VABI sceAgcDcbContextStateOpGetSize(uint32_t operation); } +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t APS5_VABI sceAgcDcbSetBaseDispatchIndirectArgsGetSize(); +std::uint32_t APS5_VABI sceAgcDcbSetBaseDrawIndirectArgsGetSize(); +std::uint32_t APS5_VABI sceAgcDcbQueueEndOfShaderActionGetSize(); +std::uint32_t APS5_VABI sceAgcDcbGetLodStatsGetSize(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbState/include/Display.hpp b/core/libs/prx/libSceAgc/DcbState/include/Display.hpp index 74fb8caf0d..e6868d17f4 100644 --- a/core/libs/prx/libSceAgc/DcbState/include/Display.hpp +++ b/core/libs/prx/libSceAgc/DcbState/include/Display.hpp @@ -1,4 +1,15 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_DISPLAY_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_DISPLAY_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +uint32_t* APS5_VABI sceAgcDcbSetFlip(CommandBuffer* buf, uint32_t videoOutHandle, int32_t displayBufferIndex, uint32_t flipMode, int64_t flipArg); +uint32_t* APS5_VABI sceAgcDcbPrimeUtcl2(CommandBuffer* buf, const volatile void* address, uint32_t sizeInBytes); +std::uint32_t APS5_VABI sceAgcDcbPrimeUtcl2GetSize(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbState/include/Init.hpp b/core/libs/prx/libSceAgc/DcbState/include/Init.hpp index de641fea3d..8e278b3fc3 100644 --- a/core/libs/prx/libSceAgc/DcbState/include/Init.hpp +++ b/core/libs/prx/libSceAgc/DcbState/include/Init.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_INIT_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_INIT_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcInit(std::uint32_t version); +int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbState/include/Marker.hpp b/core/libs/prx/libSceAgc/DcbState/include/Marker.hpp index c0ccfdea02..bb626aa02e 100644 --- a/core/libs/prx/libSceAgc/DcbState/include/Marker.hpp +++ b/core/libs/prx/libSceAgc/DcbState/include/Marker.hpp @@ -7,8 +7,19 @@ namespace Agc::Marker { std::uint32_t* Push(CommandBuffer* buf, const char* str, const char* function); +std::uint32_t* PushSpan(CommandBuffer* buf, const char* str, std::uint32_t length, const char* function); std::uint32_t* Pop(CommandBuffer* buf, const char* function); } +#include "SceShaders.hpp" + +extern "C" { + +uint32_t* APS5_VABI sceAgcDcbSetMarker(CommandBuffer* buf, const char* str, uint32_t color); +uint32_t* APS5_VABI sceAgcDcbPopMarker(CommandBuffer* buf); +uint32_t* APS5_VABI sceAgcDcbPushMarker(CommandBuffer* buf, const char* str, uint32_t color); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbState/include/Predication.hpp b/core/libs/prx/libSceAgc/DcbState/include/Predication.hpp index 65f64a14eb..5d56b7066a 100644 --- a/core/libs/prx/libSceAgc/DcbState/include/Predication.hpp +++ b/core/libs/prx/libSceAgc/DcbState/include/Predication.hpp @@ -1,4 +1,16 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_PREDICATION_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_PREDICATION_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +uint32_t* APS5_VABI sceAgcDcbSetPredication(CommandBuffer* buf, uint8_t condition, uint8_t op, uint8_t waitOp, const volatile void* address, uint32_t countInDwords); +std::uint32_t APS5_VABI sceAgcDcbSetZPassPredicationEnableGetSize(); +std::uint32_t APS5_VABI sceAgcDcbSetPredicationDisableGetSize(); +std::uint32_t APS5_VABI sceAgcDcbSetBoolPredicationEnableGetSize(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbState/include/Registers.hpp b/core/libs/prx/libSceAgc/DcbState/include/Registers.hpp index b7beea1499..015d0ed7b2 100644 --- a/core/libs/prx/libSceAgc/DcbState/include/Registers.hpp +++ b/core/libs/prx/libSceAgc/DcbState/include/Registers.hpp @@ -1,4 +1,26 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_REGISTERS_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_REGISTERS_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcDcbSetCfRegisterDirect(CommandBuffer* buf, ShaderRegister reg); +std::uint32_t* APS5_VABI sceAgcDcbSetCfRegisterRangeDirect(CommandBuffer* buf, std::uint32_t offset, const std::uint32_t* values, std::uint32_t numValues); +uint32_t* APS5_VABI sceAgcDcbSetCxRegisterDirect(CommandBuffer* buf, ShaderRegister reg); +uint32_t APS5_VABI sceAgcDcbSetCxRegisterDirectGetSize(void); +std::uint32_t* APS5_VABI sceAgcDcbSetCxRegistersIndirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs); +std::uint32_t APS5_VABI sceAgcDcbSetCxRegistersIndirectGetSize(std::uint32_t numRegs); +uint32_t* APS5_VABI sceAgcDcbSetShRegisterDirect(CommandBuffer* buf, ShaderRegister reg); +std::uint32_t APS5_VABI sceAgcDcbSetShRegisterDirectGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbSetShRegistersIndirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs); +std::uint32_t APS5_VABI sceAgcDcbSetShRegistersIndirectGetSize(std::uint32_t numRegs); +uint32_t* APS5_VABI sceAgcDcbSetUcRegisterDirect(CommandBuffer* buf, ShaderRegister reg); +std::uint32_t APS5_VABI sceAgcDcbSetUcRegisterDirectGetSize(); +std::uint32_t* APS5_VABI sceAgcDcbSetUcRegistersIndirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs); +std::uint32_t APS5_VABI sceAgcDcbSetUcRegistersIndirectGetSize(std::uint32_t numRegs); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbState/include/Workload.hpp b/core/libs/prx/libSceAgc/DcbState/include/Workload.hpp index 3d5d6cac71..562ab7678f 100644 --- a/core/libs/prx/libSceAgc/DcbState/include/Workload.hpp +++ b/core/libs/prx/libSceAgc/DcbState/include/Workload.hpp @@ -1,4 +1,15 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_WORKLOAD_HPP #define CORE_LIBS_PRX_LIBSCEAGC_DCBSTATE_INCLUDE_WORKLOAD_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +uint32_t* APS5_VABI sceAgcDcbSetWorkloadComplete(CommandBuffer* buf, uint32_t streamId, uint32_t workloadId); +uint32_t* APS5_VABI sceAgcDcbSetWorkloadsActive(CommandBuffer* buf, uint32_t streamId, const uint32_t* workloadIds, uint32_t workloadCount); +uint32_t* APS5_VABI sceAgcDcbSetWorkloadStreamInactive(CommandBuffer* buf, uint32_t streamId); + +} + #endif diff --git a/core/libs/prx/libSceAgc/DcbState/src/Marker.cpp b/core/libs/prx/libSceAgc/DcbState/src/Marker.cpp index 7e88856ccc..12304f16a3 100644 --- a/core/libs/prx/libSceAgc/DcbState/src/Marker.cpp +++ b/core/libs/prx/libSceAgc/DcbState/src/Marker.cpp @@ -22,13 +22,21 @@ namespace Agc::Marker { std::uint32_t* Push(CommandBuffer* buf, const char* str, const char* function) { Agc::Command::Require(buf != nullptr, function, "null command buffer"); const char* text = str ? str : ""; - const std::size_t bytes = std::strlen(text) + 1; + const std::size_t length = std::strlen(text); + Agc::Command::Require(length < 0x10000u, function, "marker text too long"); + return PushSpan(buf, text, static_cast(length), function); +} + +std::uint32_t* PushSpan(CommandBuffer* buf, const char* str, std::uint32_t length, const char* function) { + Agc::Command::Require(buf != nullptr, function, "null command buffer"); + Agc::Command::Require(str != nullptr || length == 0, function, "null marker text"); + const std::size_t bytes = std::size_t{length} + 1; const auto payload = static_cast((bytes + 3) / 4); Agc::Command::Require(payload <= 0x4000u, function, "marker text too long"); auto* packet = Agc::Command::Allocate(buf, payload + 1, function); packet[0] = Agc::Command::Header(OpcodeNop, payload + 1, CustomPushMarker << 2); std::memset(packet + 1, 0, payload * sizeof(std::uint32_t)); - std::memcpy(packet + 1, text, bytes); + if (length != 0) std::memcpy(packet + 1, str, length); return packet; } @@ -60,4 +68,16 @@ uint32_t* APS5_VABI sceAgcDcbPushMarker(CommandBuffer* buf, const char* str, uin return Agc::Marker::Push(buf, str, __func__); } +uint32_t* APS5_VABI sceAgcDcbPushMarkerSpan(CommandBuffer* buf, const char* str, uint32_t length, uint32_t color) { + (void)color; + return Agc::Marker::PushSpan(buf, str, length, __func__); +} + +uint32_t* APS5_VABI sceAgcDcbSetMarkerSpan(CommandBuffer* buf, const char* str, uint32_t length, uint32_t color) { + (void)color; + auto* packet = Agc::Marker::PushSpan(buf, str, length, __func__); + Agc::Marker::Pop(buf, __func__); + return packet; +} + } diff --git a/core/libs/prx/libSceAgc/Export.cpp b/core/libs/prx/libSceAgc/Export.cpp index 8e933ec1cf..3bab548078 100644 --- a/core/libs/prx/libSceAgc/Export.cpp +++ b/core/libs/prx/libSceAgc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/include/Agc.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAgc/Misc/include/Debug.hpp b/core/libs/prx/libSceAgc/Misc/include/Debug.hpp index 58286eb85c..6647ed5d6a 100644 --- a/core/libs/prx/libSceAgc/Misc/include/Debug.hpp +++ b/core/libs/prx/libSceAgc/Misc/include/Debug.hpp @@ -1,4 +1,15 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_DEBUG_HPP #define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_DEBUG_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId); +int APS5_VABI sceAgcSetShaderInstrumentation(std::uint32_t flags); +std::uint32_t APS5_VABI sceAgcGetShaderInstrumentation(); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Misc/include/PacketInfo.hpp b/core/libs/prx/libSceAgc/Misc/include/PacketInfo.hpp index 04a3d07eb6..29d7253eee 100644 --- a/core/libs/prx/libSceAgc/Misc/include/PacketInfo.hpp +++ b/core/libs/prx/libSceAgc/Misc/include/PacketInfo.hpp @@ -14,4 +14,14 @@ int APS5_VABI sceAgcGetDataPacketPayloadAddress(uint32_t** addr, uint32_t* cmd, int APS5_VABI sceAgcGetDataPacketPayloadAddress_0090(uint32_t** addr, uint32_t* cmd, int type); } +#include "SceShaders.hpp" + +extern "C" { + +uint32_t APS5_VABI sceAgcGetPacketSize(uint32_t* packet); +int APS5_VABI sceAgcGetDataPacketPayloadRange(SceAgcMemoryRange* range, uint32_t* cmd, int type); +int APS5_VABI sceAgcDcbGetSystemSoftwareVersion(uint32_t* destination, const uint32_t* packet); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Misc/include/Platform.hpp b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp index fd07f976ab..37e71bb0bd 100644 --- a/core/libs/prx/libSceAgc/Misc/include/Platform.hpp +++ b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp @@ -1,4 +1,13 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP #define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Misc/include/RegisterDefaults.hpp b/core/libs/prx/libSceAgc/Misc/include/RegisterDefaults.hpp index c7eb4fea76..f8934d9fb0 100644 --- a/core/libs/prx/libSceAgc/Misc/include/RegisterDefaults.hpp +++ b/core/libs/prx/libSceAgc/Misc/include/RegisterDefaults.hpp @@ -4,6 +4,18 @@ #include extern "C" { -int APS5_VABI sceAgcGetDefaultCxStateFlat(void* out_state, std::uint32_t size); +int APS5_VABI sceAgcGetDefaultCxStateFlat(void* outState, std::uint32_t size); } + +#include "SceShaders.hpp" + +extern "C" { + +void* APS5_VABI sceAgcGetRegisterDefaults(); +void* APS5_VABI sceAgcGetRegisterDefaultsInternal(); +void* APS5_VABI sceAgcGetRegisterDefaults2(std::uint32_t version); +void* APS5_VABI sceAgcGetRegisterDefaults2Internal(std::uint32_t version); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Misc/include/Semaphore.hpp b/core/libs/prx/libSceAgc/Misc/include/Semaphore.hpp index 834dcdd962..3f0602453c 100644 --- a/core/libs/prx/libSceAgc/Misc/include/Semaphore.hpp +++ b/core/libs/prx/libSceAgc/Misc/include/Semaphore.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_SEMAPHORE_HPP #define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_SEMAPHORE_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInBytes); +int APS5_VABI sceAgcGetSemaphoreLabel(std::uint32_t index, void** label); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Misc/include/ShaderFusion.hpp b/core/libs/prx/libSceAgc/Misc/include/ShaderFusion.hpp index 0927dc9e51..d90e4d427e 100644 --- a/core/libs/prx/libSceAgc/Misc/include/ShaderFusion.hpp +++ b/core/libs/prx/libSceAgc/Misc/include/ShaderFusion.hpp @@ -10,7 +10,16 @@ struct SizeAlign { std::size_t m_align; }; -extern "C" int APS5_VABI sceAgcFuseShaderHalves_0200(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem); +extern "C" int APS5_VABI sceAgcFuseShaderHalves_0200(Shader* fusedResult, const Shader* front, const Shader* back, void* scratchMem); extern "C" int APS5_VABI sceAgcGetFusedShaderSize_0080(SizeAlign* dst, const Shader* front, const Shader* back); +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcFuseShaderHalves_nid_postfix(Shader* fusedResult, const Shader* front, const Shader* back, void* scratchMem); +int APS5_VABI sceAgcUpdateInterpolantMapping_0100(ShaderRegister* regs, const Shader* vs, const Shader* ps); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Misc/include/Suspend.hpp b/core/libs/prx/libSceAgc/Misc/include/Suspend.hpp index 25b2dd5bf7..295d3761ce 100644 --- a/core/libs/prx/libSceAgc/Misc/include/Suspend.hpp +++ b/core/libs/prx/libSceAgc/Misc/include/Suspend.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_SUSPEND_HPP #define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_SUSPEND_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcSuspendPoint(void); +int APS5_VABI sceAgcSetSubmitMode(std::uint32_t mode); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Misc/src/Debug.cpp b/core/libs/prx/libSceAgc/Misc/src/Debug.cpp index 8a321110ab..a211959181 100644 --- a/core/libs/prx/libSceAgc/Misc/src/Debug.cpp +++ b/core/libs/prx/libSceAgc/Misc/src/Debug.cpp @@ -1,9 +1,16 @@ #include "prx/libSceAgc/Misc/include/Debug.hpp" +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +std::atomic shaderInstrumentation{0}; + +} + extern "C" { int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId) { @@ -11,4 +18,13 @@ int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId) { return 0; } +int APS5_VABI sceAgcSetShaderInstrumentation(std::uint32_t flags) { + shaderInstrumentation.store(flags, std::memory_order_relaxed); + return 0; +} + +std::uint32_t APS5_VABI sceAgcGetShaderInstrumentation() { + return shaderInstrumentation.load(std::memory_order_relaxed); +} + } diff --git a/core/libs/prx/libSceAgc/Misc/src/Semaphore.cpp b/core/libs/prx/libSceAgc/Misc/src/Semaphore.cpp index 7a6c802690..c7ec65c170 100644 --- a/core/libs/prx/libSceAgc/Misc/src/Semaphore.cpp +++ b/core/libs/prx/libSceAgc/Misc/src/Semaphore.cpp @@ -11,21 +11,37 @@ namespace { +constexpr int GRAPHICS5_ERROR_INVALID_ALIGNMENT = static_cast(0x8a6c0002u); +constexpr int GRAPHICS5_ERROR_OUT_OF_RANGE = static_cast(0x8a6c000bu); +constexpr int GRAPHICS5_ERROR_SEMAPHORE_MEMORY_REGISTERED = static_cast(0x8a6c0048u); +constexpr int GRAPHICS5_ERROR_SEMAPHORE_MEMORY_UNREGISTERED = static_cast(0x8a6c0049u); constexpr std::uint64_t semaphoreMemoryAlignment = 0x4000u; +constexpr std::uint64_t semaphoreMemoryLimit = 0x40000000u; constexpr std::uint64_t semaphoreLabelBytes = 32u; -constexpr std::uint64_t semaphoreCounterBytes = 8u; std::mutex semaphoreMutex; std::uint8_t* semaphoreMemory = nullptr; std::uint64_t semaphoreBytes = 0; -bool hasOnlyZeroLabelTails(const std::uint8_t* memory, std::uint64_t sizeInBytes) { - for (std::uint64_t label = 0; label < sizeInBytes; label += semaphoreLabelBytes) { - for (std::uint64_t byte = semaphoreCounterBytes; byte < semaphoreLabelBytes; ++byte) { - if (memory[label + byte] != 0) return false; - } +bool aligned(std::uint64_t value) { + return (value & (semaphoreMemoryAlignment - 1u)) == 0; +} + +int registerSemaphoreMemory(void* memory, std::uint64_t sizeInBytes, bool limited, const char* name) { + const auto address = reinterpret_cast(memory); + std::lock_guard lock(semaphoreMutex); + if (semaphoreBytes != 0) return GRAPHICS5_ERROR_SEMAPHORE_MEMORY_REGISTERED; + if (!aligned(sizeInBytes)) return GRAPHICS5_ERROR_INVALID_ALIGNMENT; + if (limited && sizeInBytes > semaphoreMemoryLimit) return GRAPHICS5_ERROR_OUT_OF_RANGE; + if (!aligned(address)) return GRAPHICS5_ERROR_INVALID_ALIGNMENT; + if (sizeInBytes != 0) { + Agc::Command::Require(memory != nullptr, name, "null semaphore memory with a non-zero size"); + Agc::Command::Require(sizeInBytes - 1u <= std::numeric_limits::max() - address, name, "semaphore memory wraps the address space"); + std::memset(memory, 0, sizeInBytes); } - return true; + semaphoreMemory = static_cast(memory); + semaphoreBytes = sizeInBytes; + return 0; } } @@ -33,27 +49,18 @@ bool hasOnlyZeroLabelTails(const std::uint8_t* memory, std::uint64_t sizeInBytes extern "C" { int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInBytes) { - const auto address = reinterpret_cast(memory); - std::lock_guard lock(semaphoreMutex); - Agc::Command::Require(semaphoreMemory == nullptr, __func__, "the AMM semaphore memory is already registered; the console's error is unknown"); - Agc::Command::Require(memory != nullptr && sizeInBytes != 0, __func__, "null or empty AMM semaphore memory; the console's error is unknown"); - Agc::Command::Require(((address | sizeInBytes) & (semaphoreMemoryAlignment - 1u)) == 0, __func__, "AMM semaphore memory or size is not 16 KiB aligned; the console's error is unknown"); - Agc::Command::Require(sizeInBytes - 1u <= std::numeric_limits::max() - address, __func__, "AMM semaphore memory wraps the address space"); - auto* labels = static_cast(memory); - Agc::Command::Require(hasOnlyZeroLabelTails(labels, sizeInBytes), __func__, "AMM semaphore labels hold data past their first 8 bytes; whether registration clears it is unknown"); - for (std::uint64_t label = 0; label < sizeInBytes; label += semaphoreLabelBytes) { - std::memset(labels + label, 0, semaphoreCounterBytes); - } - semaphoreMemory = labels; - semaphoreBytes = sizeInBytes; - return 0; + return registerSemaphoreMemory(memory, sizeInBytes, false, __func__); +} + +int APS5_VABI sceAgcSetSemaphoreMemory(void* memory, std::uint64_t sizeInBytes) { + return registerSemaphoreMemory(memory, sizeInBytes, true, __func__); } int APS5_VABI sceAgcGetSemaphoreLabel(std::uint32_t index, void** label) { std::lock_guard lock(semaphoreMutex); - Agc::Command::Require(semaphoreMemory != nullptr, __func__, "no AMM semaphore memory is registered; the console's error is unknown"); - Agc::Command::Require(index < semaphoreBytes / semaphoreLabelBytes, __func__, "the semaphore label lies outside the AMM semaphore memory; the console's error is unknown"); - Agc::Command::Require(label != nullptr, __func__, "null semaphore label output; the console's error is unknown"); + if (semaphoreBytes == 0) return GRAPHICS5_ERROR_SEMAPHORE_MEMORY_UNREGISTERED; + if (index >= semaphoreBytes / semaphoreLabelBytes) return GRAPHICS5_ERROR_OUT_OF_RANGE; + Agc::Command::Require(label != nullptr, __func__, "null semaphore label output"); *label = semaphoreMemory + static_cast(index) * semaphoreLabelBytes; return 0; } diff --git a/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp b/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp index ad00d374ce..a8fdedf664 100644 --- a/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp +++ b/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp @@ -35,7 +35,6 @@ constexpr std::size_t FusedCodeAlignment = 256; constexpr char TrailerMagic[8] = {'b', 'a', 'r', 'e', 'f', 'o', 'o', 't'}; constexpr std::size_t TrailerCodeBytesOffset = sizeof(TrailerMagic) + 3 * sizeof(std::uint32_t); constexpr std::uint32_t Rsrc1VgprMask = 0x3fu; -constexpr std::uint32_t Rsrc1SgprMask = 0xfu << 6u; constexpr std::uint32_t Rsrc2OffchipLdsEnable = 1u << 18u; constexpr std::uint32_t Rsrc2UserSgprMask = (0x1fu << 1u) | (1u << 27u); @@ -121,15 +120,19 @@ std::uint32_t FrontProgramBytes(const char* function, const Shader* front) { return static_cast((end - 1) * 4); } -std::uint32_t MergeRsrc1(std::uint32_t back, std::uint32_t front) { - const auto vgprs = std::max(back & Rsrc1VgprMask, front & Rsrc1VgprMask); - const auto sgprs = std::max(back & Rsrc1SgprMask, front & Rsrc1SgprMask); - return (back & ~(Rsrc1VgprMask | Rsrc1SgprMask)) | vgprs | sgprs; -} - -std::uint32_t MergeRsrc2(const char* function, std::uint32_t back, std::uint32_t front) { - if ((front & ~(Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable) & ~back) != 0) Fail(function, "front half requires hardware state the back half does not enable"); - return (back & ~Rsrc2UserSgprMask) | (front & (Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable)); +std::array mergeGeometryResources(std::uint32_t frontRsrc1, std::uint32_t frontRsrc2, std::uint32_t backRsrc1, std::uint32_t backRsrc2) { + const auto frontVgprs = ((frontRsrc1 & Rsrc1VgprMask) + 1u) * 4u; + const auto backVgprs = ((backRsrc1 & Rsrc1VgprMask) + 1u) * 4u; + const auto frontTotal = frontVgprs + (frontRsrc2 >> 28u) * 8u; + const auto backTotal = backVgprs + (backRsrc2 >> 28u) * 8u; + const auto maxTotal = std::max(frontTotal, backTotal); + const auto shared = std::max(frontVgprs, backVgprs) >= maxTotal ? 0u : (maxTotal - std::min(frontTotal, backTotal) + 7u) / 64u; + const auto vgprs = std::max(frontRsrc1 & Rsrc1VgprMask, backRsrc1 & Rsrc1VgprMask); + const auto rsrc1Field = std::max((frontRsrc1 >> 29u) & 3u, (backRsrc1 >> 29u) & 3u); + const auto rsrc2Field = std::max((frontRsrc2 >> 16u) & 3u, (backRsrc2 >> 16u) & 3u); + const auto rsrc1 = (backRsrc1 & ~(Rsrc1VgprMask | (3u << 29u))) | vgprs | (rsrc1Field << 29u); + const auto rsrc2 = (backRsrc2 & ~(0xf0000000u | (3u << 16u) | Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable)) | ((shared & 15u) << 28u) | (rsrc2Field << 16u) | (frontRsrc2 & (Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable)); + return {rsrc1, rsrc2}; } const ShaderRegister* FindHalfRegister(const Shader* shader, std::uint32_t offset) { @@ -149,10 +152,14 @@ GeometryLayout ComputeGeometryLayout(const char* function, const Shader* front, const auto* frontRsrc1 = FindHalfRegister(front, SPI_SHADER_PGM_RSRC1_GS); const auto* frontRsrc2 = FindHalfRegister(front, SPI_SHADER_PGM_RSRC2_GS); if (frontRsrc1 == nullptr || frontRsrc2 == nullptr) Fail(function, "front half has no GS resource registers"); + const auto* backRsrc1 = FindHalfRegister(back, SPI_SHADER_PGM_RSRC1_GS); + const auto* backRsrc2 = FindHalfRegister(back, SPI_SHADER_PGM_RSRC2_GS); + if (backRsrc1 == nullptr || backRsrc2 == nullptr) Fail(function, "back half has no GS resource registers"); + const auto resources = mergeGeometryResources(frontRsrc1->value, frontRsrc2->value, backRsrc1->value, backRsrc2->value); layout.sh.assign(back->sh_registers, back->sh_registers + back->num_sh_registers); for (auto& reg : layout.sh) { - if (reg.offset == SPI_SHADER_PGM_RSRC1_GS) reg.value = MergeRsrc1(reg.value, frontRsrc1->value); - else if (reg.offset == SPI_SHADER_PGM_RSRC2_GS) reg.value = MergeRsrc2(function, reg.value, frontRsrc2->value); + if (reg.offset == SPI_SHADER_PGM_RSRC1_GS) reg.value = resources[0]; + else if (reg.offset == SPI_SHADER_PGM_RSRC2_GS) reg.value = resources[1]; } for (std::uint32_t i = 0; i < front->num_sh_registers; ++i) { const auto& reg = front->sh_registers[i]; diff --git a/core/libs/prx/libSceAgc/Patch/include/Branch.hpp b/core/libs/prx/libSceAgc/Patch/include/Branch.hpp index f4b7c44624..2e13cbeab4 100644 --- a/core/libs/prx/libSceAgc/Patch/include/Branch.hpp +++ b/core/libs/prx/libSceAgc/Patch/include/Branch.hpp @@ -1,4 +1,17 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_BRANCH_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_BRANCH_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcBranchPatchSetCompareAddress(std::uint32_t* cmd, const volatile std::uint64_t* compareAddress); +int APS5_VABI sceAgcBranchPatchSetThenTarget(std::uint32_t* cmd, const volatile std::uint32_t* target, std::uint32_t sizeInDwords); +int APS5_VABI sceAgcBranchPatchSetElseTarget(std::uint32_t* cmd, const volatile std::uint32_t* target, std::uint32_t sizeInDwords); +int APS5_VABI sceAgcBranchPatchSetThenTarget_0300(std::uint32_t* cmd, std::uint8_t cachePolicy, const volatile std::uint32_t* target, std::uint32_t sizeInDwords); +int APS5_VABI sceAgcBranchPatchSetElseTarget_0300(std::uint32_t* cmd, std::uint8_t cachePolicy, const volatile std::uint32_t* target, std::uint32_t sizeInDwords); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Patch/include/CondExec.hpp b/core/libs/prx/libSceAgc/Patch/include/CondExec.hpp index 8d81efe673..6db4bcedb9 100644 --- a/core/libs/prx/libSceAgc/Patch/include/CondExec.hpp +++ b/core/libs/prx/libSceAgc/Patch/include/CondExec.hpp @@ -1,4 +1,16 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_CONDEXEC_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_CONDEXEC_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcCondExecPatchSetCommandAddress(uint32_t* cmd, const volatile uint32_t* command); +int APS5_VABI sceAgcCondExecPatchSetEnd(uint32_t* cmd, const volatile uint32_t* buffer); +int APS5_VABI sceAgcAsyncCondExecPatchSetCommandAddress(std::uint32_t* cmd, const volatile std::uint32_t* command); +int APS5_VABI sceAgcAsyncCondExecPatchSetEnd(std::uint32_t* cmd, const volatile std::uint32_t* buffer); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Patch/include/Dma.hpp b/core/libs/prx/libSceAgc/Patch/include/Dma.hpp index d28d24df89..50dfc239b6 100644 --- a/core/libs/prx/libSceAgc/Patch/include/Dma.hpp +++ b/core/libs/prx/libSceAgc/Patch/include/Dma.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_DMA_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_DMA_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcDmaDataPatchSetDstAddressOrOffset(std::uint32_t* cmd, std::uint64_t address); +int APS5_VABI sceAgcDmaDataPatchSetSrcAddressOrOffsetOrImmediate(std::uint32_t* cmd, std::uint64_t address); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Patch/include/IndirectRegisters.hpp b/core/libs/prx/libSceAgc/Patch/include/IndirectRegisters.hpp index 72863129b7..9de388c582 100644 --- a/core/libs/prx/libSceAgc/Patch/include/IndirectRegisters.hpp +++ b/core/libs/prx/libSceAgc/Patch/include/IndirectRegisters.hpp @@ -1,4 +1,21 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_INDIRECTREGISTERS_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_INDIRECTREGISTERS_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcSetCxRegIndirectPatchAddRegisters(std::uint32_t* cmd, std::uint32_t numRegs); +int APS5_VABI sceAgcSetCxRegIndirectPatchSetAddress(std::uint32_t* cmd, const volatile ShaderRegister* regs); +int APS5_VABI sceAgcSetCxRegIndirectPatchSetNumRegisters(std::uint32_t* cmd, std::uint32_t numRegs); +int APS5_VABI sceAgcSetShRegIndirectPatchAddRegisters(std::uint32_t* cmd, std::uint32_t numRegs); +int APS5_VABI sceAgcSetShRegIndirectPatchSetAddress(std::uint32_t* cmd, const volatile ShaderRegister* regs); +int APS5_VABI sceAgcSetShRegIndirectPatchSetNumRegisters(std::uint32_t* cmd, std::uint32_t numRegs); +int APS5_VABI sceAgcSetUcRegIndirectPatchAddRegisters(std::uint32_t* cmd, std::uint32_t numRegs); +int APS5_VABI sceAgcSetUcRegIndirectPatchSetAddress(std::uint32_t* cmd, const volatile ShaderRegister* regs); +int APS5_VABI sceAgcSetUcRegIndirectPatchSetNumRegisters(std::uint32_t* cmd, std::uint32_t numRegs); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Patch/include/Jump.hpp b/core/libs/prx/libSceAgc/Patch/include/Jump.hpp index b5820f260d..1d6ba6899e 100644 --- a/core/libs/prx/libSceAgc/Patch/include/Jump.hpp +++ b/core/libs/prx/libSceAgc/Patch/include/Jump.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_JUMP_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_JUMP_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcJumpPatchSetTarget(uint32_t* cmd, const volatile uint32_t* target, uint32_t sizeInDwords); +int APS5_VABI sceAgcJumpPatchSetTarget_0300(uint32_t* cmd, uint64_t cachePolicy, const volatile uint32_t* target, uint32_t sizeInDwords); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Patch/include/Nop.hpp b/core/libs/prx/libSceAgc/Patch/include/Nop.hpp index 9958a2cbc6..390c3de34c 100644 --- a/core/libs/prx/libSceAgc/Patch/include/Nop.hpp +++ b/core/libs/prx/libSceAgc/Patch/include/Nop.hpp @@ -1,4 +1,13 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_NOP_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_NOP_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +std::uint32_t* APS5_VABI sceAgcSetNop(CommandBuffer* buf, std::uint32_t sizeDw); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Patch/include/QueueEndOfPipeAction.hpp b/core/libs/prx/libSceAgc/Patch/include/QueueEndOfPipeAction.hpp index eb06c3a58d..7c7d61b7cc 100644 --- a/core/libs/prx/libSceAgc/Patch/include/QueueEndOfPipeAction.hpp +++ b/core/libs/prx/libSceAgc/Patch/include/QueueEndOfPipeAction.hpp @@ -1,4 +1,16 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_QUEUEENDOFPIPEACTION_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_QUEUEENDOFPIPEACTION_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcQueueEndOfPipeActionPatchAddress(std::uint32_t* cmd, const volatile Label* address); +int APS5_VABI sceAgcQueueEndOfPipeActionPatchData(uint32_t* cmd, uint64_t data); +int APS5_VABI sceAgcQueueEndOfPipeActionPatchGcrCntl(std::uint32_t* cmd, std::uint16_t gcrControl); +int APS5_VABI sceAgcQueueEndOfPipeActionPatchType(std::uint32_t* cmd, std::uint8_t action); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Patch/include/Rewind.hpp b/core/libs/prx/libSceAgc/Patch/include/Rewind.hpp index 52e1bc2625..9752bd43da 100644 --- a/core/libs/prx/libSceAgc/Patch/include/Rewind.hpp +++ b/core/libs/prx/libSceAgc/Patch/include/Rewind.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_REWIND_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_REWIND_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcRewindPatchSetRewindState(uint32_t* cmd, uint8_t state); +int APS5_VABI sceAgcAsyncRewindPatchSetRewindState(std::uint32_t* cmd, std::uint8_t state); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Patch/include/WaitRegMem.hpp b/core/libs/prx/libSceAgc/Patch/include/WaitRegMem.hpp index 8e14191a5e..0bdd3d5fd6 100644 --- a/core/libs/prx/libSceAgc/Patch/include/WaitRegMem.hpp +++ b/core/libs/prx/libSceAgc/Patch/include/WaitRegMem.hpp @@ -1,4 +1,22 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_WAITREGMEM_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PATCH_INCLUDE_WAITREGMEM_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcWaitRegMemPatchAddress(std::uint32_t* cmd, const volatile void* address); +int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint32_t reference); +int APS5_VABI sceAgcWaitRegMemPatchMask(std::uint32_t* cmd, std::uint32_t mask); +int APS5_VABI sceAgcWaitRegMemPatchCompareFunction(std::uint32_t* cmd, std::uint8_t compareFunction); +int APS5_VABI sceAgcWriteDataPatchSetAddressOrOffset(std::uint32_t* cmd, std::uint64_t address); +int APS5_VABI sceAgcAsyncWriteDataPatchSetAddressOrOffset(std::uint32_t* cmd, std::uint64_t address); +int APS5_VABI sceAgcWriteDataPatchSetDst(std::uint32_t* cmd, std::uint8_t dst); +int APS5_VABI sceAgcAsyncWriteDataPatchSetDst(std::uint32_t* cmd, std::uint8_t dst); +int APS5_VABI sceAgcWriteDataPatchSetCachePolicy(std::uint32_t* cmd, std::uint8_t cachePolicy); +int APS5_VABI sceAgcAsyncWriteDataPatchSetCachePolicy(std::uint32_t* cmd, std::uint8_t cachePolicy); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp b/core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp index 4da4af5783..34ee4fd902 100644 --- a/core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp +++ b/core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp @@ -45,34 +45,29 @@ extern "C" { int APS5_VABI sceAgcWaitRegMemPatchAddress(std::uint32_t* cmd, const volatile void* address) { auto* wait = Agc::Command::ValidateWait(cmd, __func__); const auto guestAddress = reinterpret_cast(address); - const auto alignment = ((wait[0] >> 8u) & 0xffu) == 0x3cu ? 4u : 8u; - Agc::Command::CheckAddress(guestAddress, alignment, __func__); - Agc::Command::CheckBits(guestAddress, 0xffffffffffffull, __func__); - cmd[2] = (cmd[2] & 0xffff0000u) | static_cast(guestAddress >> 32u); + const auto lowMask = ((wait[0] >> 8u) & 0xffu) == 0x3cu ? 3u : 7u; + cmd[2] = (cmd[2] & 0xffff0000u) | (static_cast(guestAddress >> 32u) & 0xffffu); cmd[3] = static_cast(guestAddress); - wait[2] = static_cast(guestAddress); - wait[3] = static_cast(guestAddress >> 32u); + wait[2] = (static_cast(guestAddress) & ~lowMask) | (wait[2] & lowMask); + wait[3] = (static_cast(guestAddress >> 32u) & 0x3ffffu) | (wait[3] & 0xfffc0000u); return 0; } -int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint64_t reference) { +int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint32_t reference) { auto* wait = Agc::Command::ValidateWait(cmd, __func__); - Agc::Command::CheckBits(reference, 0xffffffffu, __func__); wait[4] = static_cast(reference); return 0; } -int APS5_VABI sceAgcWaitRegMemPatchMask(std::uint32_t* cmd, std::uint64_t mask) { +int APS5_VABI sceAgcWaitRegMemPatchMask(std::uint32_t* cmd, std::uint32_t mask) { auto* wait = Agc::Command::ValidateWait(cmd, __func__); - Agc::Command::CheckBits(mask, 0xffffffffu, __func__); wait[((wait[0] >> 8u) & 0xffu) == 0x3cu ? 5 : 6] = static_cast(mask); return 0; } int APS5_VABI sceAgcWaitRegMemPatchCompareFunction(std::uint32_t* cmd, std::uint8_t compareFunction) { auto* wait = Agc::Command::ValidateWait(cmd, __func__); - Agc::Command::Require(compareFunction <= 6, __func__, "invalid wait comparison"); - wait[1] = (wait[1] & ~7u) | compareFunction; + wait[1] = (wait[1] & ~7u) | (compareFunction & 7u); return 0; } diff --git a/core/libs/prx/libSceAgc/Predication/include/Predication.hpp b/core/libs/prx/libSceAgc/Predication/include/Predication.hpp index 7ae84c8a76..cad10cd236 100644 --- a/core/libs/prx/libSceAgc/Predication/include/Predication.hpp +++ b/core/libs/prx/libSceAgc/Predication/include/Predication.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_PREDICATION_INCLUDE_PREDICATION_HPP #define CORE_LIBS_PRX_LIBSCEAGC_PREDICATION_INCLUDE_PREDICATION_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcSetPacketPredication(uint32_t* packet, uint32_t predication); +int APS5_VABI sceAgcSetRangePredication(uint32_t* start, const volatile uint32_t* end, uint32_t predication); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Shader/include/CreateShader.hpp b/core/libs/prx/libSceAgc/Shader/include/CreateShader.hpp index 68117f4735..044d288596 100644 --- a/core/libs/prx/libSceAgc/Shader/include/CreateShader.hpp +++ b/core/libs/prx/libSceAgc/Shader/include/CreateShader.hpp @@ -1,4 +1,13 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_SHADER_INCLUDE_CREATESHADER_HPP #define CORE_LIBS_PRX_LIBSCEAGC_SHADER_INCLUDE_CREATESHADER_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcCreateShader(Shader** dst, void* header, const volatile void* code); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Shader/include/GsOversubscription.hpp b/core/libs/prx/libSceAgc/Shader/include/GsOversubscription.hpp new file mode 100644 index 0000000000..b454416920 --- /dev/null +++ b/core/libs/prx/libSceAgc/Shader/include/GsOversubscription.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcGetGsOversubscription(ShaderRegister* regs, const Shader* gs, std::uint32_t budget, float factor); + +} diff --git a/core/libs/prx/libSceAgc/Shader/include/GsPrimPayload.hpp b/core/libs/prx/libSceAgc/Shader/include/GsPrimPayload.hpp new file mode 100644 index 0000000000..d72b485767 --- /dev/null +++ b/core/libs/prx/libSceAgc/Shader/include/GsPrimPayload.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcGetGsPrimPayload(std::uint32_t* payload, const Shader* gs); + +} diff --git a/core/libs/prx/libSceAgc/Shader/include/InterpolantMapping.hpp b/core/libs/prx/libSceAgc/Shader/include/InterpolantMapping.hpp index 91ffb9eeb8..7085d35fe0 100644 --- a/core/libs/prx/libSceAgc/Shader/include/InterpolantMapping.hpp +++ b/core/libs/prx/libSceAgc/Shader/include/InterpolantMapping.hpp @@ -1,4 +1,17 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_SHADER_INCLUDE_INTERPOLANTMAPPING_HPP #define CORE_LIBS_PRX_LIBSCEAGC_SHADER_INCLUDE_INTERPOLANTMAPPING_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps); +int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps); +int APS5_VABI sceAgcUpdateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps); +int APS5_VABI sceAgcCreateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps); +int APS5_VABI sceAgcUpdateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Shader/include/LinkShaders.hpp b/core/libs/prx/libSceAgc/Shader/include/LinkShaders.hpp new file mode 100644 index 0000000000..99d9e328a3 --- /dev/null +++ b/core/libs/prx/libSceAgc/Shader/include/LinkShaders.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcLinkShaders(ShaderRegister* context, ShaderRegister* primitive, const void* reserved, const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType); + +} diff --git a/core/libs/prx/libSceAgc/Shader/include/PrimState.hpp b/core/libs/prx/libSceAgc/Shader/include/PrimState.hpp index 1dda27a825..090a9a78e6 100644 --- a/core/libs/prx/libSceAgc/Shader/include/PrimState.hpp +++ b/core/libs/prx/libSceAgc/Shader/include/PrimState.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGC_SHADER_INCLUDE_PRIMSTATE_HPP #define CORE_LIBS_PRX_LIBSCEAGC_SHADER_INCLUDE_PRIMSTATE_HPP +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcCreatePrimState(ShaderRegister* cxRegs, ShaderRegister* ucRegs, const Shader* hs, const Shader* gs, std::uint32_t primType); +int APS5_VABI sceAgcUpdatePrimState(ShaderRegister* cxRegs, ShaderRegister* ucRegs, std::uint32_t primType); + +} + #endif diff --git a/core/libs/prx/libSceAgc/Shader/src/GsOversubscription.cpp b/core/libs/prx/libSceAgc/Shader/src/GsOversubscription.cpp index 029ec1c099..79209401f3 100644 --- a/core/libs/prx/libSceAgc/Shader/src/GsOversubscription.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/GsOversubscription.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Shader/include/GsOversubscription.hpp" #include #include #include diff --git a/core/libs/prx/libSceAgc/Shader/src/GsPrimPayload.cpp b/core/libs/prx/libSceAgc/Shader/src/GsPrimPayload.cpp index 722ade98c6..2815c5a02b 100644 --- a/core/libs/prx/libSceAgc/Shader/src/GsPrimPayload.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/GsPrimPayload.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Shader/include/GsPrimPayload.hpp" #include #include #include diff --git a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp index 8e56d4c0c8..65b337d335 100644 --- a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp @@ -53,12 +53,9 @@ std::uint32_t CreateInterpolantValueSplitF16(std::uint32_t psWord, const ShaderS : CreateInterpolantValue(psWord, gsSemantic); } -std::uint32_t UpdateInterpolantValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { - const std::uint32_t f16 = (psWord >> 20u) & 0x3u; - if (f16 != 0 && f16 != InterpolantF16HiOnly && ((psWord >> 28u) & 0x3u) != ((psWord >> 30u) & 0x3u)) { - throw std::runtime_error(std::string(__func__) + ": DEFAULT_VAL of an f16 input with different low and high defaults is unknown"); - } - return CreateInterpolantValueSplitF16(psWord, gsSemantic); +std::uint32_t CreateInterpolantValueFlatDefault(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + const std::uint32_t value = CreateInterpolantValue(psWord, gsSemantic); + return ((psWord & 0x00700000u) == 0x00400000u) ? (value | 0x00000020u) : value; } int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue, bool fillIdentityTail) { @@ -132,7 +129,15 @@ int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const S } int APS5_VABI sceAgcUpdateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps) { - return CreateInterpolantMapping(__func__, regs, gs, ps, UpdateInterpolantValue, false); + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16, false); +} + +int APS5_VABI sceAgcCreateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueFlatDefault, true); +} + +int APS5_VABI sceAgcUpdateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueFlatDefault, false); } } diff --git a/core/libs/prx/libSceAgc/Shader/src/LinkShaders.cpp b/core/libs/prx/libSceAgc/Shader/src/LinkShaders.cpp index 59a43354c8..8b6a8ff132 100644 --- a/core/libs/prx/libSceAgc/Shader/src/LinkShaders.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/LinkShaders.cpp @@ -1,3 +1,6 @@ +#include "prx/libSceAgc/Shader/include/PrimState.hpp" +#include "prx/libSceAgc/Shader/include/LinkShaders.hpp" +#include "prx/libSceAgc/Shader/include/InterpolantMapping.hpp" #include "SceShaders.hpp" #include "prx/libSceAgcDriver/Execution/include/ShaderPreparationScope.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" @@ -6,9 +9,6 @@ #include #include -extern "C" int APS5_VABI sceAgcCreatePrimState(ShaderRegister*, ShaderRegister*, const Shader*, const Shader*, std::uint32_t); -extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister*, const Shader*, const Shader*); - extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister* context, ShaderRegister* primitive, const void* reserved, const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) { using namespace ShaderRegs; diff --git a/core/libs/prx/libSceAgc/Unimplemented.cpp b/core/libs/prx/libSceAgc/Unimplemented.cpp index 7a7d7c52cb..919f2d8b77 100644 --- a/core/libs/prx/libSceAgc/Unimplemented.cpp +++ b/core/libs/prx/libSceAgc/Unimplemented.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/include/Agc.hpp" #include #include #include "SceTypes.hpp" @@ -5,49 +6,19 @@ extern "C" { -int APS5_VABI sceAgcAcbPushMarkerSpan() { +int APS5_VABI sceAgcAcquireMemSetEngine(std::uint32_t* packet, std::uint8_t engine) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAgcAcbSetMarkerSpan() { +std::uint32_t* APS5_VABI sceAgcCbMemsetExclusive(CommandBuffer* buffer, void* destination, const void* pattern, std::uint64_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAgcAcquireMemSetEngine() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcDcbPushMarkerSpan() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcDcbSetMarkerSpan() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcSetSemaphoreMemory() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcCbMemsetExclusive() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcSetShaderInstrumentation() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcGetShaderInstrumentation() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAgcAcbInitializeDefaultHardwareState() { + NotImplemented_nid_no_patch(__func__); + return 0; } } diff --git a/core/libs/prx/libSceAgc/include/Agc.hpp b/core/libs/prx/libSceAgc/include/Agc.hpp new file mode 100644 index 0000000000..e600558de3 --- /dev/null +++ b/core/libs/prx/libSceAgc/include/Agc.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "SceTypes.hpp" +#include "SceShaders.hpp" + +extern "C" { + +int APS5_VABI sceAgcSuspendPointAndCheckStatus(void); +std::uint32_t* APS5_VABI sceAgcAcbPushMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color); +std::uint32_t* APS5_VABI sceAgcAcbSetMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color); +int APS5_VABI sceAgcAcquireMemSetEngine(std::uint32_t* packet, std::uint8_t engine); +std::uint32_t* APS5_VABI sceAgcDcbPushMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color); +std::uint32_t* APS5_VABI sceAgcDcbSetMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color); +int APS5_VABI sceAgcSetSemaphoreMemory(void* memory, std::uint64_t size); +std::uint32_t* APS5_VABI sceAgcCbMemsetExclusive(CommandBuffer* buffer, void* destination, const void* pattern, std::uint64_t size); + +int APS5_VABI sceAgcAcbInitializeDefaultHardwareState(); + +} diff --git a/core/libs/prx/libSceAgc/libSceAgc.hpp b/core/libs/prx/libSceAgc/libSceAgc.hpp new file mode 100644 index 0000000000..407e5b809c --- /dev/null +++ b/core/libs/prx/libSceAgc/libSceAgc.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "prx/libSceAgc/Acb/include/Control.hpp" +#include "prx/libSceAgc/Acb/include/Dispatch.hpp" +#include "prx/libSceAgc/Acb/include/Memory.hpp" +#include "prx/libSceAgc/Acb/include/Sync.hpp" +#include "prx/libSceAgc/Cb/include/Branch.hpp" +#include "prx/libSceAgc/Cb/include/Dispatch.hpp" +#include "prx/libSceAgc/Cb/include/Registers.hpp" +#include "prx/libSceAgc/Cb/include/Sync.hpp" +#include "prx/libSceAgc/DcbDraw/include/DrawIndexed.hpp" +#include "prx/libSceAgc/DcbDraw/include/DrawNonIndexed.hpp" +#include "prx/libSceAgc/DcbDraw/include/IndexBuffer.hpp" +#include "prx/libSceAgc/DcbDraw/include/Instancing.hpp" +#include "prx/libSceAgc/DcbDraw/include/Stats.hpp" +#include "prx/libSceAgc/DcbFlow/include/Control.hpp" +#include "prx/libSceAgc/DcbFlow/include/Dispatch.hpp" +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" +#include "prx/libSceAgc/DcbFlow/include/Sync.hpp" +#include "prx/libSceAgc/DcbState/include/ContextState.hpp" +#include "prx/libSceAgc/DcbState/include/Display.hpp" +#include "prx/libSceAgc/DcbState/include/Init.hpp" +#include "prx/libSceAgc/DcbState/include/Marker.hpp" +#include "prx/libSceAgc/DcbState/include/Predication.hpp" +#include "prx/libSceAgc/DcbState/include/Registers.hpp" +#include "prx/libSceAgc/DcbState/include/Workload.hpp" +#include "prx/libSceAgc/include/Agc.hpp" +#include "prx/libSceAgc/Misc/include/Debug.hpp" +#include "prx/libSceAgc/Misc/include/PacketInfo.hpp" +#include "prx/libSceAgc/Misc/include/Platform.hpp" +#include "prx/libSceAgc/Misc/include/RegisterDefaults.hpp" +#include "prx/libSceAgc/Misc/include/Semaphore.hpp" +#include "prx/libSceAgc/Misc/include/ShaderFusion.hpp" +#include "prx/libSceAgc/Misc/include/StaticBuffer.hpp" +#include "prx/libSceAgc/Misc/include/Suspend.hpp" +#include "prx/libSceAgc/Patch/include/Branch.hpp" +#include "prx/libSceAgc/Patch/include/CondExec.hpp" +#include "prx/libSceAgc/Patch/include/Dma.hpp" +#include "prx/libSceAgc/Patch/include/IndirectRegisters.hpp" +#include "prx/libSceAgc/Patch/include/Jump.hpp" +#include "prx/libSceAgc/Patch/include/Nop.hpp" +#include "prx/libSceAgc/Patch/include/QueueEndOfPipeAction.hpp" +#include "prx/libSceAgc/Patch/include/Rewind.hpp" +#include "prx/libSceAgc/Patch/include/WaitRegMem.hpp" +#include "prx/libSceAgc/Predication/include/Predication.hpp" +#include "prx/libSceAgc/Shader/include/CreateShader.hpp" +#include "prx/libSceAgc/Shader/include/GsOversubscription.hpp" +#include "prx/libSceAgc/Shader/include/GsPrimPayload.hpp" +#include "prx/libSceAgc/Shader/include/InterpolantMapping.hpp" +#include "prx/libSceAgc/Shader/include/LinkShaders.hpp" +#include "prx/libSceAgc/Shader/include/PrimState.hpp" diff --git a/core/libs/prx/libSceAgc/tests/AtomicGdsSize.cpp b/core/libs/prx/libSceAgc/tests/AtomicGdsSize.cpp index bbc5416bd3..3315cd1312 100644 --- a/core/libs/prx/libSceAgc/tests/AtomicGdsSize.cpp +++ b/core/libs/prx/libSceAgc/tests/AtomicGdsSize.cpp @@ -1,14 +1,11 @@ +#include "prx/libSceAgc/Acb/include/Memory.hpp" +#include "prx/libSceAgc/Acb/include/Control.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include #include #include -extern "C" std::uint32_t APS5_VABI sceAgcAcbAtomicGdsGetSize(); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbAtomicGds_0900(CommandBuffer* buf, std::uint32_t control, std::uint32_t mode, - std::uint32_t gdsOp, std::uint32_t src, std::uint16_t offset, std::uint16_t index, std::uint32_t mask, - std::uint32_t data, std::uint64_t compareData, std::uint64_t extraData); - int main() { constexpr std::uint32_t expectedSize = 11u * sizeof(std::uint32_t); if (sceAgcAcbAtomicGdsGetSize() != expectedSize) { diff --git a/core/libs/prx/libSceAgc/tests/BranchPatch.cpp b/core/libs/prx/libSceAgc/tests/BranchPatch.cpp index 58afe9b1c9..24c0f213b0 100644 --- a/core/libs/prx/libSceAgc/tests/BranchPatch.cpp +++ b/core/libs/prx/libSceAgc/tests/BranchPatch.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceAgc/Patch/include/Branch.hpp" +#include "prx/libSceAgc/Cb/include/Branch.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include @@ -7,16 +9,6 @@ #include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcCbBranch(CommandBuffer*, std::uint8_t, std::uint8_t, const volatile std::uint64_t*, std::uint64_t, std::uint64_t, std::uint8_t, const volatile std::uint32_t*, std::uint32_t, std::uint8_t, const volatile std::uint32_t*, std::uint32_t); -std::uint32_t APS5_VABI sceAgcCbBranchGetSize(); -int APS5_VABI sceAgcBranchPatchSetCompareAddress(std::uint32_t*, const volatile std::uint64_t*); -int APS5_VABI sceAgcBranchPatchSetThenTarget(std::uint32_t*, const volatile std::uint32_t*, std::uint32_t); -int APS5_VABI sceAgcBranchPatchSetElseTarget(std::uint32_t*, const volatile std::uint32_t*, std::uint32_t); -int APS5_VABI sceAgcBranchPatchSetThenTarget_0300(std::uint32_t*, std::uint8_t, const volatile std::uint32_t*, std::uint32_t); -int APS5_VABI sceAgcBranchPatchSetElseTarget_0300(std::uint32_t*, std::uint8_t, const volatile std::uint32_t*, std::uint32_t); -} - namespace { void check(bool condition, const char* message) { diff --git a/core/libs/prx/libSceAgc/tests/CbCondWrite.cpp b/core/libs/prx/libSceAgc/tests/CbCondWrite.cpp index 69186e10ba..f08ab6e67b 100644 --- a/core/libs/prx/libSceAgc/tests/CbCondWrite.cpp +++ b/core/libs/prx/libSceAgc/tests/CbCondWrite.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Cb/include/Branch.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include @@ -5,11 +6,6 @@ #include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcCbCondWrite(CommandBuffer*, std::uint32_t, std::uint32_t, const volatile void*, std::uint32_t, const volatile void*, std::uint32_t, std::uint32_t); -std::uint32_t APS5_VABI sceAgcCbCondWriteGetSize(); -} - namespace { constexpr std::uint32_t Sentinel = 0xabcdef01u; diff --git a/core/libs/prx/libSceAgc/tests/Commands.cpp b/core/libs/prx/libSceAgc/tests/Commands.cpp index c5ac16fa9c..a9ec29ed76 100644 --- a/core/libs/prx/libSceAgc/tests/Commands.cpp +++ b/core/libs/prx/libSceAgc/tests/Commands.cpp @@ -1,3 +1,23 @@ +#include "prx/libSceAgc/include/Agc.hpp" +#include "prx/libSceAgcDriver/Resource/include/Registration.hpp" +#include "prx/libSceAgc/Patch/include/WaitRegMem.hpp" +#include "prx/libSceAgc/Patch/include/Nop.hpp" +#include "prx/libSceAgc/Misc/include/Suspend.hpp" +#include "prx/libSceAgc/Misc/include/RegisterDefaults.hpp" +#include "prx/libSceAgc/Misc/include/PacketInfo.hpp" +#include "prx/libSceAgc/Misc/include/Debug.hpp" +#include "prx/libSceAgc/DcbState/include/Workload.hpp" +#include "prx/libSceAgc/DcbState/include/Marker.hpp" +#include "prx/libSceAgc/DcbState/include/Init.hpp" +#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/Control.hpp" +#include "prx/libSceAgc/DcbDraw/include/Instancing.hpp" +#include "prx/libSceAgc/DcbDraw/include/IndexBuffer.hpp" +#include "prx/libSceAgc/DcbDraw/include/DrawIndexed.hpp" +#include "prx/libSceAgc/Cb/include/Registers.hpp" +#include "prx/libSceAgc/Acb/include/Control.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include "prx/libSceAgc/Command/include/Memory.hpp" #include "prx/libSceAgc/Command/include/RegisterDefaults.hpp" @@ -10,51 +30,6 @@ #include #include -extern "C" std::uint32_t* APS5_VABI sceAgcDcbEventWrite(CommandBuffer* buf, std::uint8_t eventType, const volatile void* address); -extern "C" std::uint64_t APS5_VABI sceAgcDcbEventWriteGetSize(std::uint8_t eventType); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, std::uint32_t op, std::uint32_t state); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbClearState(CommandBuffer* buf, std::uint32_t command); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetFlip(CommandBuffer* buf, std::uint32_t handle, std::int32_t index, std::uint32_t mode, std::int64_t argument); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbWaitUntilSafeForRendering(CommandBuffer* buf, std::uint32_t handle, std::uint32_t index); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetFlip(CommandBuffer* buf, std::uint32_t handle, std::int32_t index, std::uint32_t mode, std::int64_t argument); -extern "C" int APS5_VABI sceAgcSuspendPoint(); -extern "C" int APS5_VABI sceAgcInit(std::uint32_t version); -extern "C" void* APS5_VABI sceAgcGetRegisterDefaults(); -extern "C" void* APS5_VABI sceAgcGetRegisterDefaults2(std::uint32_t version); -extern "C" void* APS5_VABI sceAgcGetRegisterDefaultsInternal(); -extern "C" void* APS5_VABI sceAgcGetRegisterDefaults2Internal(std::uint32_t version); -extern "C" int APS5_VABI sceAgcGetDefaultCxStateFlat(void* out_state, std::uint32_t size); -extern "C" int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexAuto(CommandBuffer* buf, std::uint32_t indexCount, std::uint64_t modifier); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirect(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint64_t modifier); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirectMulti(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint32_t countIndirect, std::uint32_t maxCountOrCount, const volatile void* countAddress, std::uint32_t strideInBytes, std::uint64_t modifier); -extern "C" int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint64_t reference); -extern "C" int APS5_VABI sceAgcWaitRegMemPatchMask(std::uint32_t* cmd, std::uint64_t mask); -extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddress_0090(std::uint32_t** addr, std::uint32_t* cmd, int type); -extern "C" std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuffer* buf, std::uint32_t offset, const std::uint32_t* values, std::uint32_t numValues); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbContextStateOp_0100(CommandBuffer* buf, std::uint32_t operation); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbPushMarker(CommandBuffer* buf, const char* str, std::uint32_t color); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbPopMarker(CommandBuffer* buf); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetWorkloadComplete(CommandBuffer* buf, std::uint32_t stream_id, std::uint32_t workload_id); -extern "C" int APS5_VABI sceAgcDriverRegisterWorkloadStream(std::uint32_t stream_id, const void* stream); -extern "C" int APS5_VABI sceAgcDriverUnregisterWorkloadStream(std::uint32_t stream_id); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetWorkloadsActive(CommandBuffer* buf, std::uint32_t stream_id, const std::uint32_t* workload_ids, std::uint32_t workload_count); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetWorkloadStreamInactive(CommandBuffer* buf, std::uint32_t streamId); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadsActive(CommandBuffer* buf, std::uint32_t streamId, const std::uint32_t* workloadIds, std::uint32_t workloadCount); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadComplete(CommandBuffer* buf, std::uint32_t streamId, std::uint32_t workloadId); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadStreamInactive(CommandBuffer* buf, std::uint32_t streamId); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetMarker(CommandBuffer* buf, const char* str, std::uint32_t color); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, const char* str, std::uint32_t color); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbPopMarker(CommandBuffer* buf); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetMarker(CommandBuffer* buf, const char* str, std::uint32_t color); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetIndexBuffer(CommandBuffer* buf, std::uint64_t indexAddress); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetIndexIndirectArgs(CommandBuffer* buf, std::uint64_t attributeAddress, std::uint32_t attributeIndex); -extern "C" std::uint32_t APS5_VABI sceAgcDcbSetIndexIndirectArgsGetSize(); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetBaseIndirectArgs(CommandBuffer* buf, std::uint32_t shaderType, const volatile void* indirectBaseAddr); -extern "C" std::uint32_t* APS5_VABI sceAgcSetNop(CommandBuffer* buf, std::uint32_t sizeDw); -extern "C" int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId); -extern "C" std::uint32_t* APS5_VABI sceAgcAcbAtomicGds_0900(CommandBuffer* buf, std::uint32_t control, std::uint32_t mode, std::uint32_t gdsOp, std::uint32_t src, std::uint16_t offset, std::uint16_t index, std::uint32_t mask, std::uint32_t data, std::uint64_t compareData, std::uint64_t extraData); - namespace { void check(bool condition, const char* message) { @@ -223,6 +198,29 @@ void testMarkers() { expectFailure([] { sceAgcAcbSetMarker(nullptr, "frame", 0); }); } +void testMarkerSpans() { + Storage dcb; + Storage acb; + const char text[] = "framebuffer"; + const auto* acbPush = sceAgcAcbPushMarkerSpan(&acb.buffer, text, 5, 0x00ff00u); + const auto* dcbPush = sceAgcDcbPushMarkerSpan(&dcb.buffer, text, 5, 0xff0000u); + check(acbPush == acb.words.data() && acbPush[0] == Agc::Command::Header(0x10, 3, 0x0bu << 2u), "ACB push marker span header mismatch"); + check(std::strcmp(reinterpret_cast(acbPush + 1), "frame") == 0, "ACB push marker span does not stop at its length"); + check(dcbPush == dcb.words.data(), "DCB push marker span did not return its packet"); + const auto* acbSet = sceAgcAcbSetMarkerSpan(&acb.buffer, nullptr, 0, 0); + const auto* dcbSet = sceAgcDcbSetMarkerSpan(&dcb.buffer, nullptr, 0, 0); + check(acbSet == acb.words.data() + 3 && dcbSet == dcb.words.data() + 3, "set marker span did not return its push packet"); + check(acbSet[0] == Agc::Command::Header(0x10, 2, 0x0bu << 2u) && acbSet[1] == 0 && acbSet[2] == Agc::Command::Header(0x10, 2, 0x0cu << 2u), "ACB set marker span is not a push and pop pair"); + check(dcb.words == acb.words && acb.buffer.cursor_up == acb.words.data() + 7, "ACB and DCB marker spans differ"); + Storage plain; + sceAgcAcbPushMarker(&plain.buffer, "frame", 0); + check(std::equal(plain.words.begin(), plain.words.begin() + 3, acb.words.begin()), "marker span differs from the same text pushed whole"); + expectFailure([] { sceAgcAcbPushMarkerSpan(nullptr, "frame", 5, 0); }); + expectFailure([] { sceAgcAcbPushMarker(nullptr, reinterpret_cast(std::uintptr_t{0x10}), 0); }); + expectFailure([] { sceAgcDcbSetMarkerSpan(nullptr, "frame", 5, 0); }); + expectFailure([&] { sceAgcDcbPushMarkerSpan(&dcb.buffer, nullptr, 4, 0); }); +} + void testWorkloads() { Storage dcb; const std::uint32_t ids[2] = {3, 4}; @@ -531,18 +529,28 @@ void testMemory() { check(packet[8] == 0x55667788u && packet[9] == 0x11223344u && packet[12] == 2u, "wait reference or poll interval mismatch"); sceAgcWaitRegMemPatchReference(packet, 7); check(packet[8] == 7 && packet[9] == 0x11223344u, "reference patch changed the high word"); - const auto before = storage.words; - expectFailure([&] { sceAgcWaitRegMemPatchReference(packet, 0x100000000ull); }); - check(storage.words == before, "invalid memory operation modified packet memory"); auto* truncated = Agc::Command::WriteWait(&storage.buffer, 0, 3, 0, 0, &value, 0x100000000ull, 0xffffffff00000001ull, 32, __func__); check(truncated[8] == 0u && truncated[9] == 1u, "32-bit wait did not keep the low halves of the reference and mask"); sceAgcWaitRegMemPatchMask(packet, 0x0f0f0f0fu); check(packet[10] == 0x0f0f0f0fu && packet[11] == 0xffffffffu && packet[8] == 7, "64-bit mask patch changed the wrong word"); sceAgcWaitRegMemPatchMask(truncated, 0xff00u); check(truncated[9] == 0xff00u && truncated[8] == 0u && truncated[10] == 2u, "32-bit mask patch changed the wrong word"); + for (auto* waitPacket : {packet, truncated}) { + const auto lowMask = waitPacket == packet ? 7u : 3u; + waitPacket[6] |= lowMask; + waitPacket[7] = 0xa5fc0000u; + const auto tag = waitPacket[2] & 0xffff0000u; + const std::uintptr_t address = 0x1236abcd12345679ull; + check(sceAgcWaitRegMemPatchAddress(waitPacket, reinterpret_cast(address)) == 0, "wait address patch failed"); + check(waitPacket[2] == (tag | 0xabcdu) && waitPacket[3] == 0x12345679u, "wait wrapper address patch mismatch"); + check(waitPacket[6] == ((0x12345679u & ~lowMask) | lowMask) && waitPacket[7] == 0xa5feabcdu, "wait address patch did not preserve control bits"); + for (std::uint32_t comparison = 0; comparison <= 255; ++comparison) { + const auto control = waitPacket[5] & ~7u; + check(sceAgcWaitRegMemPatchCompareFunction(waitPacket, static_cast(comparison)) == 0, "wait comparison patch failed"); + check(waitPacket[5] == (control | (comparison & 7u)), "wait comparison patch changed control bits"); + } + } const auto beforeMask = storage.words; - expectFailure([&] { sceAgcWaitRegMemPatchMask(packet, 0x100000000ull); }); - expectFailure([&] { sceAgcWaitRegMemPatchMask(truncated, 0x100000000ull); }); expectFailure([&] { sceAgcWaitRegMemPatchMask(packet + 4, 1); }); check(storage.words == beforeMask, "invalid mask patch modified packet memory"); } @@ -691,15 +699,13 @@ void testDefaults() { expectFailure([] { Agc::Command::GetRegisterDefaults(0xffffffffu, true, __func__); }); } -struct DefaultsType { - std::uint32_t key; - std::uint32_t bankIndex; -}; +constexpr std::size_t PairEntryBytes = 8; +constexpr std::size_t CompactEntryBytes = 12; -std::uint32_t colourTargetBlock(const void* root) { +std::uint32_t colourTargetBlock(const void* root, std::size_t entryBytes) { const auto* bytes = static_cast(root); ShaderRegister** contextBlocks = nullptr; - const DefaultsType* types = nullptr; + const unsigned char* types = nullptr; std::uint32_t count = 0; std::memcpy(&contextBlocks, bytes, sizeof(contextBlocks)); std::memcpy(&types, bytes + 0x30, sizeof(types)); @@ -709,12 +715,17 @@ std::uint32_t colourTargetBlock(const void* root) { std::uint32_t matches = 0; std::uint32_t block = 0; for (std::uint32_t entry = 0; entry < count; ++entry) { - if ((types[entry].bankIndex & 3u) == 0) + std::uint32_t key = 0; + std::uint32_t bankIndex = 0; + std::memcpy(&key, types + entry * entryBytes, sizeof(key)); + std::memcpy(&bankIndex, types + entry * entryBytes + 4, sizeof(bankIndex)); + bankIndex &= 0x3ffu; + if ((bankIndex & 3u) == 0) ++contexts; - if (types[entry].key != 0x38e92c91u) + if (key != 0x38e92c91u) continue; - check((types[entry].bankIndex & 3u) == 0, "MRT0 defaults are not in the context bank"); - block = types[entry].bankIndex >> 2; + check((bankIndex & 3u) == 0, "MRT0 defaults are not in the context bank"); + block = bankIndex >> 2; ++matches; } check(matches == 1 && block < contexts && contextBlocks[block] != nullptr, "MRT0 defaults key is missing, repeated or outside the context blocks"); @@ -727,7 +738,11 @@ std::uint32_t colourTargetBlock(const void* root) { void testDefaultsTypes() { const auto* legacy = sceAgcGetRegisterDefaults(); check(legacy != sceAgcGetRegisterDefaults2(13), "legacy register defaults reused the compact versioned table"); - check(colourTargetBlock(legacy) == 78, "MRT0 defaults are not block 78 of the 137-entry table"); + check(colourTargetBlock(legacy, PairEntryBytes) == 78, "MRT0 defaults are not block 78 of the 137-entry table"); + check(colourTargetBlock(sceAgcGetRegisterDefaults2(13), CompactEntryBytes) == 78, "versioned MRT0 defaults are not block 78 of the compact 137-entry table"); + check(colourTargetBlock(sceAgcGetRegisterDefaults2(10), CompactEntryBytes) == 73, "versioned MRT0 defaults are not block 73 of the compact 128-entry table"); + for (std::uint32_t version = 4; version < 14; ++version) + colourTargetBlock(sceAgcGetRegisterDefaults2(version), CompactEntryBytes); const auto checkCompactTypes = [](const void* root, std::uint32_t count, std::uint32_t key, std::uint32_t packedType, std::uint32_t extra) { @@ -803,6 +818,7 @@ int main(int argc, char** argv) { testClearState(); testIndexedIndirectDraws(); testMarkers(); + testMarkerSpans(); testWorkloads(); testWorkloadStreams(); testIndexBuffer(); diff --git a/core/libs/prx/libSceAgc/tests/CondExecPatch.cpp b/core/libs/prx/libSceAgc/tests/CondExecPatch.cpp index 762f40b1a7..b5939e0c46 100644 --- a/core/libs/prx/libSceAgc/tests/CondExecPatch.cpp +++ b/core/libs/prx/libSceAgc/tests/CondExecPatch.cpp @@ -1,3 +1,6 @@ +#include "prx/libSceAgc/Patch/include/CondExec.hpp" +#include "prx/libSceAgc/DcbFlow/include/Sync.hpp" +#include "prx/libSceAgc/Acb/include/Sync.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include @@ -7,15 +10,6 @@ #include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcDcbCondExec(CommandBuffer*, const volatile std::uint32_t*, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcAcbCondExec(CommandBuffer*, const volatile std::uint32_t*, std::uint32_t); -int APS5_VABI sceAgcCondExecPatchSetCommandAddress(std::uint32_t*, const volatile std::uint32_t*); -int APS5_VABI sceAgcCondExecPatchSetEnd(std::uint32_t*, const volatile std::uint32_t*); -int APS5_VABI sceAgcAsyncCondExecPatchSetCommandAddress(std::uint32_t*, const volatile std::uint32_t*); -int APS5_VABI sceAgcAsyncCondExecPatchSetEnd(std::uint32_t*, const volatile std::uint32_t*); -} - namespace { constexpr std::uint32_t Sentinel = 0xabcdef01u; diff --git a/core/libs/prx/libSceAgc/tests/ContextStateSize.cpp b/core/libs/prx/libSceAgc/tests/ContextStateSize.cpp index 3de0a5eda6..6d2e583e47 100644 --- a/core/libs/prx/libSceAgc/tests/ContextStateSize.cpp +++ b/core/libs/prx/libSceAgc/tests/ContextStateSize.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/DcbState/include/ContextState.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -7,10 +8,6 @@ #include #include -extern "C" std::uint64_t APS5_VABI sceAgcDcbContextStateOpGetSize(std::uint32_t operation); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbContextStateOp(CommandBuffer* buf, std::uint32_t operation); -extern "C" std::uint32_t* APS5_VABI sceAgcDcbContextStateOp_0100(CommandBuffer* buf, std::uint32_t operation); - namespace { void check(bool condition, const char* message) { diff --git a/core/libs/prx/libSceAgc/tests/DmaData.cpp b/core/libs/prx/libSceAgc/tests/DmaData.cpp index 77c80988f2..880035d394 100644 --- a/core/libs/prx/libSceAgc/tests/DmaData.cpp +++ b/core/libs/prx/libSceAgc/tests/DmaData.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" +#include "prx/libSceAgc/Acb/include/Memory.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include @@ -7,13 +9,6 @@ #include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcAcbDmaData(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t, std::uint8_t, std::uint64_t, std::uint32_t, std::uint8_t, std::uint8_t); -std::uint32_t APS5_VABI sceAgcAcbDmaDataGetSize(); -std::uint32_t* APS5_VABI sceAgcDcbDmaData(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t, std::uint8_t, std::uint64_t, std::uint32_t, std::uint8_t, std::uint8_t, std::uint8_t); -std::uint32_t APS5_VABI sceAgcDcbDmaDataGetSize(); -} - namespace { constexpr std::uint32_t Sentinel = 0xabcdef01u; diff --git a/core/libs/prx/libSceAgc/tests/EndOfShaderActionSize.cpp b/core/libs/prx/libSceAgc/tests/EndOfShaderActionSize.cpp index cf78ada4a3..5421cb5733 100644 --- a/core/libs/prx/libSceAgc/tests/EndOfShaderActionSize.cpp +++ b/core/libs/prx/libSceAgc/tests/EndOfShaderActionSize.cpp @@ -1,3 +1,8 @@ +#include "prx/libSceAgc/DcbState/include/ContextState.hpp" +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" +#include "prx/libSceAgc/DcbFlow/include/Control.hpp" +#include "prx/libSceAgc/Cb/include/Sync.hpp" +#include "prx/libSceAgc/Acb/include/Sync.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -8,15 +13,6 @@ #include #include -extern "C" { -std::uint32_t APS5_VABI sceAgcDcbQueueEndOfShaderActionGetSize(); -std::uint32_t APS5_VABI sceAgcAcbQueueEndOfShaderActionGetSize(); -std::uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer*, std::uint8_t, std::uint16_t, std::uint8_t, std::uint8_t, const volatile Label*, std::uint8_t, std::uint64_t, std::uint16_t, std::uint16_t, std::uint8_t, std::uint32_t); -std::uint32_t APS5_VABI sceAgcDcbAtomicGdsGetSize(); -std::uint32_t* APS5_VABI sceAgcDcbAtomicGds_0900(CommandBuffer*, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint16_t, std::uint16_t, std::uint32_t, std::uint32_t, std::uint64_t, std::uint64_t); -std::uint32_t* APS5_VABI sceAgcDcbAtomicGds(CommandBuffer*, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint16_t, std::uint16_t, std::uint32_t, std::uint32_t, std::uint64_t, std::uint64_t); -} - namespace { void check(bool condition, const char* message) { diff --git a/core/libs/prx/libSceAgc/tests/GsOversubscription.cpp b/core/libs/prx/libSceAgc/tests/GsOversubscription.cpp index 7c312c879d..3666a2d338 100644 --- a/core/libs/prx/libSceAgc/tests/GsOversubscription.cpp +++ b/core/libs/prx/libSceAgc/tests/GsOversubscription.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Shader/include/GsOversubscription.hpp" #include "SceShaders.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -11,8 +12,6 @@ #include #include -extern "C" int APS5_VABI sceAgcGetGsOversubscription(ShaderRegister* regs, const Shader* gs, std::uint32_t budget, float factor); - namespace { using Registers = std::array; diff --git a/core/libs/prx/libSceAgc/tests/GsPrimPayload.cpp b/core/libs/prx/libSceAgc/tests/GsPrimPayload.cpp index 60f3d29b77..396311e9ee 100644 --- a/core/libs/prx/libSceAgc/tests/GsPrimPayload.cpp +++ b/core/libs/prx/libSceAgc/tests/GsPrimPayload.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Shader/include/GsPrimPayload.hpp" #include "SceShaders.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -9,8 +10,6 @@ #include #include -extern "C" int APS5_VABI sceAgcGetGsPrimPayload(std::uint32_t* payload, const Shader* gs); - namespace { constexpr std::uint32_t Sentinel = 0xccccccccu; diff --git a/core/libs/prx/libSceAgc/tests/InstrumentationQuerySize.cpp b/core/libs/prx/libSceAgc/tests/InstrumentationQuerySize.cpp new file mode 100644 index 0000000000..66e572d0b6 --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/InstrumentationQuerySize.cpp @@ -0,0 +1,43 @@ +#include "prx/libSceAgc/Misc/include/Debug.hpp" +#include "prx/libSceAgc/DcbDraw/include/Stats.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/Shutdown.hpp" + +#include +#include +#include + +namespace { + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +void testShaderInstrumentation() { + check(sceAgcGetShaderInstrumentation() == 0, "shader instrumentation starts cleared"); + check(sceAgcSetShaderInstrumentation(0x80000005u) == 0, "setting shader instrumentation failed"); + check(sceAgcGetShaderInstrumentation() == 0x80000005u, "shader instrumentation flags were not kept"); + check(sceAgcSetShaderInstrumentation(0) == 0 && sceAgcGetShaderInstrumentation() == 0, "shader instrumentation was not cleared"); +} + +void testOcclusionQuerySizes() { + check(sceAgcDcbBeginOcclusionQueryGetSize(0) == 0x10u, "begin occlusion query size for type 0"); + check(sceAgcDcbBeginOcclusionQueryGetSize(1) == 0x120u, "begin occlusion query size for type 1"); + check(sceAgcDcbBeginOcclusionQueryGetSize(2) == 0, "begin occlusion query size for an unknown type"); + check(sceAgcDcbEndOcclusionQueryGetSize() == 0x10u, "end occlusion query size"); +} + +} + +int main() { + try { + testShaderInstrumentation(); + testOcclusionQuerySizes(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC shader instrumentation and occlusion query size 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/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp index 652eacfea7..2fbd372623 100644 --- a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Shader/include/InterpolantMapping.hpp" #include "SceShaders.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -11,10 +12,6 @@ #include #include -extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps); -extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps); -extern "C" int APS5_VABI sceAgcUpdateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps); - namespace { using Registers = std::array; @@ -158,13 +155,46 @@ void testUpdateMapping() { check(std::memcmp(create.data(), update.data(), written) == 0, "update differs from the split f16 mapping"); checkIdentity(create, static_cast(inputs.size()), "unused interpolants are not the identity"); - for (const std::uint32_t index : {1u, 3u}) { - inputs[index].default_value = 0; - update = filled(); - expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(update.data(), &gs, &ps); }); - check(std::memcmp(update.data(), untouched.data(), sizeof(update)) == 0, "rejected update wrote registers"); - inputs[index].default_value = inputs[index].default_value_hi; + inputs[1].default_value = 0; + inputs[3].default_value = 0; + update = filled(); + check(sceAgcUpdateInterpolantMapping_10000(update.data(), &gs, &ps) == 0, "update with split f16 defaults failed"); + check(update[1].value == 0x01380007u && update[3].value == 0x03680409u, "f16 DEFAULT_VAL does not come from the low default"); +} + +void testUnversionedMapping() { + std::array inputs{ + semantic(1, 0, 2), semantic(2, 0, 2), semantic(5, 0, 2), semantic(3, 0, 0), semantic(4, 0, 3)}; + inputs[0].is_flat_shaded = 1; + inputs[0].default_value = 1; + inputs[0].default_value_hi = 3; + inputs[1].default_value_hi = 1; + inputs[2].is_flat_shaded = 1; + inputs[2].default_value_hi = 2; + inputs[3].is_flat_shaded = 1; + inputs[3].default_value = 2; + std::array outputs{semantic(1, 7, 3), semantic(2, 4, 1), semantic(3, 9, 0), semantic(4, 2, 1)}; + Shader gs{}; + gs.output_semantics = outputs.data(); + gs.num_output_semantics = static_cast(outputs.size()); + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + const std::array flatDefault{0x02680527u, 0x02380024u, 0x02580020u, 0x00000629u, 0x03180002u}; + const std::size_t written = inputs.size() * sizeof(ShaderRegister); + const auto untouched = filled(); + + auto create = filled(); + check(sceAgcCreateInterpolantMapping_nid_postfix(create.data(), &gs, &ps) == 0, "unversioned mapping failed"); + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(create[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i && create[i].value == flatDefault[i], "unversioned mapping changed"); } + checkIdentity(create, static_cast(inputs.size()), "unused interpolants are not the identity"); + + auto update = filled(); + check(sceAgcUpdateInterpolantMapping_nid_postfix(update.data(), &gs, &ps) == 0, "unversioned update failed"); + check(std::memcmp(update.data(), create.data(), written) == 0, "unversioned update differs from its mapping"); + check(std::memcmp(update.data() + inputs.size(), untouched.data(), sizeof(update) - written) == 0, "unversioned update wrote past its inputs"); } void testRejections() { @@ -208,8 +238,9 @@ int main() { testIdentity(); testMapping(); testUpdateMapping(); + testUnversionedMapping(); testRejections(); - check(mappings == 7u && links == 5u, "mapping preparation call count changed"); + check(mappings == 10u && links == 8u, "mapping preparation call count changed"); LibcRunShutdown_nid_postfix(); std::puts("AGC interpolant mapping tests passed"); return 0; diff --git a/core/libs/prx/libSceAgc/tests/PacketInfo.cpp b/core/libs/prx/libSceAgc/tests/PacketInfo.cpp index 5e95906b5a..9054c828ba 100644 --- a/core/libs/prx/libSceAgc/tests/PacketInfo.cpp +++ b/core/libs/prx/libSceAgc/tests/PacketInfo.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Misc/include/PacketInfo.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -7,8 +8,6 @@ #include #include -extern "C" std::uint32_t APS5_VABI sceAgcGetPacketSize(std::uint32_t* packet); - namespace { void check(bool condition, const char* message) { diff --git a/core/libs/prx/libSceAgc/tests/PayloadRange.cpp b/core/libs/prx/libSceAgc/tests/PayloadRange.cpp index f920eb0384..d070135e43 100644 --- a/core/libs/prx/libSceAgc/tests/PayloadRange.cpp +++ b/core/libs/prx/libSceAgc/tests/PayloadRange.cpp @@ -8,10 +8,6 @@ #include #include -extern "C" int APS5_VABI sceAgcGetDataPacketPayloadRange(SceAgcMemoryRange* range, std::uint32_t* cmd, int type); -extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddress(std::uint32_t** addr, std::uint32_t* cmd, int type); -extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddress_0090(std::uint32_t** addr, std::uint32_t* cmd, int type); - namespace { void check(bool condition, const char* message) { diff --git a/core/libs/prx/libSceAgc/tests/Platform.cpp b/core/libs/prx/libSceAgc/tests/Platform.cpp index daa0634cf3..5ed2b2aea1 100644 --- a/core/libs/prx/libSceAgc/tests/Platform.cpp +++ b/core/libs/prx/libSceAgc/tests/Platform.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Misc/include/Platform.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -5,8 +6,6 @@ #include #include -extern "C" int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode); - namespace { void check(bool condition, const char* message) { diff --git a/core/libs/prx/libSceAgc/tests/Predication.cpp b/core/libs/prx/libSceAgc/tests/Predication.cpp index 32a6619ed5..7d9511f10f 100644 --- a/core/libs/prx/libSceAgc/tests/Predication.cpp +++ b/core/libs/prx/libSceAgc/tests/Predication.cpp @@ -1,3 +1,6 @@ +#include "prx/libSceAgc/Predication/include/Predication.hpp" +#include "prx/libSceAgc/DcbState/include/Predication.hpp" +#include "prx/libSceAgc/DcbDraw/include/IndexBuffer.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include @@ -5,17 +8,6 @@ #include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcDcbSetPredication(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint8_t, const volatile void*, std::uint32_t); -std::uint32_t APS5_VABI sceAgcDcbSetZPassPredicationEnableGetSize(); -std::uint32_t APS5_VABI sceAgcDcbSetPredicationDisableGetSize(); -std::uint32_t APS5_VABI sceAgcDcbSetBoolPredicationEnableGetSize(); -std::uint32_t* APS5_VABI sceAgcDcbSetIndexCount(CommandBuffer*, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcDcbSetIndexBuffer(CommandBuffer*, std::uint64_t); -int APS5_VABI sceAgcSetPacketPredication(std::uint32_t*, std::uint32_t); -int APS5_VABI sceAgcSetRangePredication(std::uint32_t*, const volatile std::uint32_t*, std::uint32_t); -} - namespace { void check(bool condition, const char* message) { diff --git a/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp b/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp index b1e702d246..4e7791491c 100644 --- a/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp +++ b/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceAgc/DcbState/include/Display.hpp" +#include "prx/libSceAgc/Acb/include/Memory.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include @@ -7,13 +9,6 @@ #include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcAcbPrimeUtcl2(CommandBuffer*, const volatile void*, std::uint32_t); -std::uint32_t APS5_VABI sceAgcAcbPrimeUtcl2GetSize(); -std::uint32_t* APS5_VABI sceAgcDcbPrimeUtcl2(CommandBuffer*, const volatile void*, std::uint32_t); -std::uint32_t APS5_VABI sceAgcDcbPrimeUtcl2GetSize(); -} - namespace { constexpr std::uint32_t Sentinel = 0xabcdef01u; diff --git a/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp b/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp index 5e6be0c709..4c52d26b8a 100644 --- a/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp +++ b/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp @@ -1,39 +1,14 @@ +#include "prx/libSceAgc/Patch/include/IndirectRegisters.hpp" +#include "prx/libSceAgc/DcbState/include/Registers.hpp" +#include "prx/libSceAgc/Cb/include/Registers.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include #include #include +#include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcDcbSetCfRegisterDirect(CommandBuffer*, ShaderRegister); -std::uint32_t* APS5_VABI sceAgcDcbSetCfRegisterRangeDirect(CommandBuffer*, std::uint32_t, const std::uint32_t*, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuffer*, std::uint32_t, const std::uint32_t*, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcCbSetUcRegisterRangeDirect(CommandBuffer*, std::uint32_t, const std::uint32_t*, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcDcbSetCxRegisterDirect(CommandBuffer*, ShaderRegister); -std::uint32_t* APS5_VABI sceAgcDcbSetShRegisterDirect(CommandBuffer*, ShaderRegister); -std::uint32_t* APS5_VABI sceAgcDcbSetUcRegisterDirect(CommandBuffer*, ShaderRegister); -std::uint32_t APS5_VABI sceAgcDcbSetCxRegisterDirectGetSize(); -std::uint32_t APS5_VABI sceAgcDcbSetShRegisterDirectGetSize(); -std::uint32_t APS5_VABI sceAgcDcbSetUcRegisterDirectGetSize(); -std::uint32_t* APS5_VABI sceAgcDcbSetCxRegistersIndirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcDcbSetShRegistersIndirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcDcbSetUcRegistersIndirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); -std::uint32_t APS5_VABI sceAgcDcbSetCxRegistersIndirectGetSize(std::uint32_t); -std::uint32_t APS5_VABI sceAgcDcbSetShRegistersIndirectGetSize(std::uint32_t); -std::uint32_t APS5_VABI sceAgcDcbSetUcRegistersIndirectGetSize(std::uint32_t); -int APS5_VABI sceAgcSetCxRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); -int APS5_VABI sceAgcSetShRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); -int APS5_VABI sceAgcSetUcRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); -int APS5_VABI sceAgcSetCxRegIndirectPatchSetAddress(std::uint32_t*, const volatile ShaderRegister*); -int APS5_VABI sceAgcSetShRegIndirectPatchSetAddress(std::uint32_t*, const volatile ShaderRegister*); -int APS5_VABI sceAgcSetUcRegIndirectPatchSetAddress(std::uint32_t*, const volatile ShaderRegister*); -std::uint32_t* APS5_VABI sceAgcCbSetShRegistersDirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcCbSetUcRegistersDirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); -std::uint32_t APS5_VABI sceAgcCbSetShRegistersDirectGetSize(std::uint32_t); -std::uint32_t APS5_VABI sceAgcCbSetUcRegistersDirectGetSize(std::uint32_t); -} - namespace { void check(bool condition, const char* message) { @@ -125,6 +100,26 @@ void testRangeFromAnyByte() { } } +void testListFromAnyByte() { + const std::array writers{sceAgcCbSetShRegistersDirect, sceAgcCbSetUcRegistersDirect}; + const std::array opcodes{0x7600u, 0x7900u}; + const std::array registers{{{0x10u, 0x11223344u}, {0x11u, 0x55667788u}, {0x30u, 0x99aabbccu}}}; + for (std::size_t i = 0; i < writers.size(); ++i) { + for (const std::size_t offset : {1u, 2u, 3u, 5u}) { + alignas(16) std::array bytes{}; + std::memcpy(bytes.data() + offset, registers.data(), sizeof(registers)); + Storage storage; + storage.words.fill(0xabcdef01u); + const auto* list = reinterpret_cast(bytes.data() + offset); + auto* packet = writers[i](&storage.buffer, list, static_cast(registers.size())); + const std::array expected{0xc0020000u | opcodes[i], 0x10u, 0x11223344u, 0x55667788u, 0xc0010000u | opcodes[i], 0x30u, 0x99aabbccu}; + check(packet == storage.words.data() && storage.buffer.cursor_up == packet + expected.size(), "unaligned register list allocation mismatch"); + check(std::equal(expected.begin(), expected.end(), packet), "unaligned register list was not read byte for byte"); + check(packet[expected.size()] == 0xabcdef01u, "unaligned register list overwrote the following word"); + } + } +} + void testIndirect() { const std::array writers{sceAgcDcbSetCxRegistersIndirect, sceAgcDcbSetShRegistersIndirect, sceAgcDcbSetUcRegistersIndirect}; const std::array sizes{sceAgcDcbSetCxRegistersIndirectGetSize, sceAgcDcbSetShRegistersIndirectGetSize, sceAgcDcbSetUcRegistersIndirectGetSize}; @@ -239,6 +234,7 @@ int main() { testDirect(); testConfig(); testRangeFromAnyByte(); + testListFromAnyByte(); testIndirect(); testIndirectPlaceholder(); testDirectList(); diff --git a/core/libs/prx/libSceAgc/tests/Semaphore.cpp b/core/libs/prx/libSceAgc/tests/Semaphore.cpp index 021eb3e7c2..3d4ff1c16d 100644 --- a/core/libs/prx/libSceAgc/tests/Semaphore.cpp +++ b/core/libs/prx/libSceAgc/tests/Semaphore.cpp @@ -1,3 +1,8 @@ +#include "prx/libSceAgc/include/Agc.hpp" +#include "prx/libSceAgc/Misc/include/Semaphore.hpp" +#include "prx/libSceAgc/DcbFlow/include/Sync.hpp" +#include "prx/libSceAgc/Cb/include/Sync.hpp" +#include "prx/libSceAgc/Acb/include/Sync.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -12,20 +17,19 @@ #include #include -extern "C" int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInBytes); -extern "C" int APS5_VABI sceAgcGetSemaphoreLabel(std::uint32_t index, void** label); -extern "C" std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint32_t operation, std::uint32_t modifier, std::uint32_t value); - namespace { constexpr std::uint64_t blockBytes = 0x4000u; constexpr std::uint64_t labelBytes = 32u; -constexpr std::uint64_t counterBytes = 8u; constexpr std::uint64_t memoryBytes = 2u * blockBytes; constexpr std::uint64_t storageBytes = 4u * blockBytes; constexpr std::uint32_t labelCount = static_cast(memoryBytes / labelBytes); constexpr std::uint8_t guardByte = 0xa5u; -constexpr std::uint8_t counterByte = 0x5au; +constexpr std::uint8_t labelByte = 0x5au; +constexpr int InvalidAlignment = static_cast(0x8a6c0002u); +constexpr int OutOfRange = static_cast(0x8a6c000bu); +constexpr int AlreadyRegistered = static_cast(0x8a6c0048u); +constexpr int NotRegistered = static_cast(0x8a6c0049u); struct AlignedRelease { void operator()(std::uint8_t* memory) const { ::operator delete(memory, std::align_val_t{blockBytes}); } @@ -62,28 +66,28 @@ void checkUnchanged(const std::vector& before, const char* message void fillStorage() { std::fill(storage.get(), storage.get() + storageBytes, guardByte); - for (std::uint64_t label = 0; label < memoryBytes; label += labelBytes) { - std::fill(memory() + label, memory() + label + counterBytes, counterByte); - std::fill(memory() + label + counterBytes, memory() + label + labelBytes, std::uint8_t{0}); - } + std::fill(memory(), memory() + memoryBytes, labelByte); } void testUnregistered() { std::array labels{storage.get(), storage.get(), storage.get()}; - expectFailure([&] { sceAgcGetSemaphoreLabel(0, &labels[1]); }); - expectFailure([] { sceAgcGetSemaphoreLabel(0, nullptr); }); + check(sceAgcGetSemaphoreLabel(0, &labels[1]) == NotRegistered, "a lookup before registration was not rejected as unregistered"); + check(sceAgcGetSemaphoreLabel(0, nullptr) == NotRegistered, "a null lookup before registration was not rejected as unregistered"); check(labels[0] == storage.get() && labels[1] == storage.get() && labels[2] == storage.get(), "a rejected semaphore label changed the output"); } void testRegistrationRejections() { const auto before = snapshot(); expectFailure([] { sceAgcSetAmmSemaphoreMemory(nullptr, memoryBytes); }); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(nullptr, 0); }); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), 0); }); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory() + 1, memoryBytes); }); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory() + labelBytes, blockBytes); }); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), blockBytes - 1u); }); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), blockBytes + labelBytes); }); + check(sceAgcSetAmmSemaphoreMemory(memory() + 1, memoryBytes) == InvalidAlignment, "unaligned memory was not rejected"); + check(sceAgcSetAmmSemaphoreMemory(memory() + labelBytes, blockBytes) == InvalidAlignment, "memory aligned only to a label was not rejected"); + check(sceAgcSetAmmSemaphoreMemory(memory(), blockBytes - 1u) == InvalidAlignment, "an unaligned size was not rejected"); + check(sceAgcSetAmmSemaphoreMemory(memory(), blockBytes + labelBytes) == InvalidAlignment, "a size aligned only to a label was not rejected"); + check(sceAgcSetSemaphoreMemory(memory() + 1, blockBytes) == InvalidAlignment, "unaligned memory was not rejected by the non-AMM call"); + check(sceAgcSetSemaphoreMemory(memory(), 0x40000000u + blockBytes) == OutOfRange, "a size above 1 GiB was not rejected by the non-AMM call"); + check(sceAgcSetSemaphoreMemory(memory() + 1, 0x40000000u + blockBytes) == OutOfRange, "the size limit was not checked before the memory alignment"); + check(sceAgcSetSemaphoreMemory(memory(), blockBytes - 1u) == InvalidAlignment, "the size alignment was not checked first"); + check(sceAgcSetAmmSemaphoreMemory(nullptr, 0) == 0, "an empty registration was rejected"); expectFailure([] { const auto top = std::numeric_limits::max() & ~static_cast(blockBytes - 1u); sceAgcSetAmmSemaphoreMemory(reinterpret_cast(top), memoryBytes); @@ -92,21 +96,11 @@ void testRegistrationRejections() { testUnregistered(); } -void testUnsettledClearing() { - for (const std::uint64_t offset : {5u * labelBytes + counterBytes, memoryBytes - 1u}) { - memory()[offset] = 0x77u; - const auto before = snapshot(); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes); }); - checkUnchanged(before, "a registration with unsettled clearing changed memory"); - testUnregistered(); - memory()[offset] = 0; - } -} - void testRegistration() { + std::fill(memory(), memory() + memoryBytes, std::uint8_t{0x77}); const auto before = snapshot(); check(sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes) == 0, "registering the AMM semaphore memory failed"); - check(std::all_of(memory(), memory() + memoryBytes, [](std::uint8_t byte) { return byte == 0; }), "registration did not clear the semaphore counters"); + check(std::all_of(memory(), memory() + memoryBytes, [](std::uint8_t byte) { return byte == 0; }), "registration did not clear the whole semaphore memory"); check(std::equal(before.begin(), before.begin() + blockBytes, storage.get()), "registration wrote before the semaphore memory"); check(std::equal(before.begin() + blockBytes + memoryBytes, before.end(), memory() + memoryBytes), "registration wrote past the semaphore memory"); } @@ -123,7 +117,7 @@ void testLabels() { void testLabelRejections() { for (const std::uint32_t index : {labelCount, labelCount + 1u, std::numeric_limits::max()}) { void* label = storage.get(); - expectFailure([&] { sceAgcGetSemaphoreLabel(index, &label); }); + check(sceAgcGetSemaphoreLabel(index, &label) == OutOfRange, "a label past the semaphore memory was not rejected"); check(label == storage.get(), "a rejected semaphore label changed the output"); } expectFailure([] { sceAgcGetSemaphoreLabel(0, nullptr); }); @@ -132,9 +126,10 @@ void testLabelRejections() { void testRepeatedRegistration() { std::fill(memory(), memory() + memoryBytes, std::uint8_t{0x3c}); const auto before = snapshot(); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes); }); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(storage.get(), blockBytes); }); - expectFailure([] { sceAgcSetAmmSemaphoreMemory(nullptr, 0); }); + check(sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes) == AlreadyRegistered, "a repeated registration was not rejected"); + check(sceAgcSetAmmSemaphoreMemory(storage.get() + 1, blockBytes) == AlreadyRegistered, "registration was not checked before alignment"); + check(sceAgcSetSemaphoreMemory(storage.get(), blockBytes) == AlreadyRegistered, "the non-AMM call did not share the registration"); + check(sceAgcSetAmmSemaphoreMemory(nullptr, 0) == AlreadyRegistered, "an empty registration was not rejected once memory is registered"); checkUnchanged(before, "a repeated registration changed memory"); void* label = nullptr; check(sceAgcGetSemaphoreLabel(labelCount - 1u, &label) == 0 && label == memory() + memoryBytes - labelBytes, "a repeated registration replaced the semaphore memory"); @@ -190,6 +185,40 @@ void testMemSemaphore() { expectFailure([&] { sceAgcCbMemSemaphore(nullptr, label, 0, 0, 0); }); } +using QueueMemSemaphore = std::uint32_t* APS5_VABI (*)(CommandBuffer*, const volatile void*, std::uint8_t, std::uint8_t, std::uint8_t); + +void testQueueMemSemaphore(QueueMemSemaphore write) { + const auto* semaphore = reinterpret_cast(0x102f221d98ull); + + { + std::array words{}; + words.fill(0xdeadbeefu); + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + const auto* packet = write(&buffer, semaphore, 0, 1, 6); + check(packet == words.data(), "packet address mismatch"); + check(packet[0] == 0xc0023900u, "MEM_SEMAPHORE header opcode mismatch"); + check(packet[1] == 0x2f221d98u, "MEM_SEMAPHORE address low mismatch"); + check(packet[2] == 0x10u, "MEM_SEMAPHORE address high mismatch"); + check(packet[3] == 0xc0100000u, "MEM_SEMAPHORE signal control word mismatch"); + check(buffer.cursor_up == words.data() + 4, "cursor did not advance by 4 words"); + check(words[4] == 0xdeadbeefu, "words past packet modified"); + } + + { + std::array words{}; + words.fill(0xdeadbeefu); + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + const auto* packet = write(&buffer, semaphore, 1, 0, 7); + check(packet[3] == 0xe0010000u, "MEM_SEMAPHORE wait control word mismatch"); + } + + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + expectFailure([&] { write(&buffer, reinterpret_cast(1ull), 0, 1, 6); }); + expectFailure([&] { write(&buffer, semaphore, 0, 1, 5); }); + expectFailure([&] { write(&buffer, semaphore, 2, 0, 7); }); +} + } int main() { @@ -198,10 +227,11 @@ int main() { fillStorage(); testUnregistered(); testRegistrationRejections(); - testUnsettledClearing(); testRegistration(); testLabels(); testMemSemaphore(); + testQueueMemSemaphore(sceAgcAcbMemSemaphore); + testQueueMemSemaphore(sceAgcDcbMemSemaphore); testLabelRejections(); testRepeatedRegistration(); storage.reset(); diff --git a/core/libs/prx/libSceAgc/tests/ShaderFusion.cpp b/core/libs/prx/libSceAgc/tests/ShaderFusion.cpp index 2f451acf50..7b66b5e57c 100644 --- a/core/libs/prx/libSceAgc/tests/ShaderFusion.cpp +++ b/core/libs/prx/libSceAgc/tests/ShaderFusion.cpp @@ -147,7 +147,7 @@ void testFusion(std::size_t misalignment, std::uint32_t terminator = SSetpcS6) { const auto* frontLo = find(fused, 0x0C8u); check(backLo != nullptr && backLo->value == static_cast((base + 256) >> 8u), "back program address was not patched"); check(frontLo != nullptr && frontLo->value == static_cast(base >> 8u), "fused program address was not patched"); - check(find(fused, 0x08Au)->value == ((1u << 24u) | (2u << 6u) | 5u), "RSRC1 register counts were not merged"); + check(find(fused, 0x08Au)->value == ((1u << 24u) | (1u << 6u) | 5u), "RSRC1 register counts were not merged"); check(find(fused, 0x08Bu)->value == ((6u << 1u) | (1u << 7u) | (1u << 15u)), "RSRC2 user SGPRs were not taken from the front half"); check(find(fused, 0x0B2u) != nullptr && find(fused, 0x0B2u)->value == 0x55u, "front-only SH register was lost"); check(find(fused, 0x080u) == nullptr, "front program checksum was kept"); @@ -208,7 +208,7 @@ void testOffchipLdsEnable() { Shader fused{}; check(sceAgcFuseShaderHalves_0200(&fused, &halves.front, &halves.back, aligned(storage, 256)) == 0, "offchip LDS enable rejected by fusion"); const auto* resource = find(fused, 0x08bu); - check(resource != nullptr && resource->value == ((6u << 1u) | (1u << 7u) | (1u << 15u) | frontEnable | backEnable), "fusion lost offchip LDS enable or changed the back-half state or front user SGPRs"); + check(resource != nullptr && resource->value == ((6u << 1u) | (1u << 7u) | (1u << 15u) | frontEnable), "fusion did not take offchip LDS enable and user SGPRs from the front half"); } } Halves reported; @@ -222,6 +222,38 @@ void testOffchipLdsEnable() { check(find(fused, 0x08bu)->value == 0x00af0004u, "reported shader resources did not retain the front offchip LDS enable"); } +void testResourceMerging() { + struct Case { + std::uint32_t front1; + std::uint32_t front2; + std::uint32_t back1; + std::uint32_t back2; + std::uint32_t expected1; + std::uint32_t expected2; + }; + const Case cases[] = { + {0u, 0x80000000u, 0u, 0u, 0u, 0x10000000u}, + {1u, 0xf0000000u, 3u, 0u, 3u, 0x10000000u}, + {0u, 0xf0000000u, 0u, 0xf0000000u, 0u, 0u}, + {0x400003c1u, 0x0006000cu, 0x61000043u, 0x00018001u, 0x61000043u, 0x0006800du}, + {0x60000007u, 0x00010000u, 0x21000041u, 0x00020000u, 0x61000047u, 0x00020000u} + }; + for (const auto& entry : cases) { + Halves halves; + halves.frontSh[1].value = entry.front1; + halves.frontSh[2].value = entry.front2; + halves.backSh[2].value = entry.back1; + halves.backSh[3].value = entry.back2; + SizeAlign size{}; + check(sceAgcGetFusedShaderSize_0080(&size, &halves.front, &halves.back) == 0, "resource merge size query failed"); + std::vector scratch(size.m_size); + Shader fused{}; + check(sceAgcFuseShaderHalves_0200(&fused, &halves.front, &halves.back, scratch.data()) == 0, "resource merge failed"); + check(find(fused, 0x08au)->value == entry.expected1, "resource register 1 merge mismatch"); + check(find(fused, 0x08bu)->value == entry.expected2, "resource register 2 merge mismatch"); + } +} + void testRejections() { Halves halves; SizeAlign size{}; @@ -249,11 +281,6 @@ void testRejections() { noTrailer.frontCode[5] = 0; expectFailure([&] { sceAgcGetFusedShaderSize_0080(&size, &noTrailer.front, &noTrailer.back); }); } - { - Halves hardware; - hardware.frontSh[2].value |= 1u << 16u; - expectFailure([&] { sceAgcGetFusedShaderSize_0080(&size, &hardware.front, &hardware.back); }); - } { Halves constants; constants.back.embedded_constant_buffer_size_dqw = 1; @@ -305,6 +332,7 @@ int main() { testFusion(0, SSwappcNullS6); testScratchReuse(); testOffchipLdsEnable(); + testResourceMerging(); testRejections(); testHullHalves(); LibcRunShutdown_nid_postfix(); diff --git a/core/libs/prx/libSceAgc/tests/ShaderHelpers.cpp b/core/libs/prx/libSceAgc/tests/ShaderHelpers.cpp index 46f7c3c8e8..6c842de199 100644 --- a/core/libs/prx/libSceAgc/tests/ShaderHelpers.cpp +++ b/core/libs/prx/libSceAgc/tests/ShaderHelpers.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Shader/include/LinkShaders.hpp" #include "SceShaders.hpp" #include "prx/libSceAgc/Shader/include/PrimState.hpp" #include "prx/libSceAgc/Shader/include/InterpolantMapping.hpp" @@ -11,10 +12,6 @@ #include #include -extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister*, ShaderRegister*, const void*, const Shader*, const Shader*, std::uint32_t); -extern "C" int APS5_VABI sceAgcCreatePrimState(ShaderRegister*, ShaderRegister*, const Shader*, const Shader*, std::uint32_t); -extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister*, const Shader*, const Shader*); - namespace { std::array preparedContext; std::array preparedPrimitive; diff --git a/core/libs/prx/libSceAgc/tests/WriteDataPatch.cpp b/core/libs/prx/libSceAgc/tests/WriteDataPatch.cpp index 5f543c8707..a8307962a4 100644 --- a/core/libs/prx/libSceAgc/tests/WriteDataPatch.cpp +++ b/core/libs/prx/libSceAgc/tests/WriteDataPatch.cpp @@ -1,3 +1,6 @@ +#include "prx/libSceAgc/Patch/include/WaitRegMem.hpp" +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" +#include "prx/libSceAgc/Acb/include/Memory.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include @@ -7,17 +10,6 @@ #include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcDcbWriteData(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint64_t, const void*, std::uint32_t, std::uint8_t, std::uint8_t); -std::uint32_t* APS5_VABI sceAgcAcbWriteData(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint64_t, const void*, std::uint32_t, std::uint8_t, std::uint8_t); -int APS5_VABI sceAgcWriteDataPatchSetAddressOrOffset(std::uint32_t*, std::uint64_t); -int APS5_VABI sceAgcAsyncWriteDataPatchSetAddressOrOffset(std::uint32_t*, std::uint64_t); -int APS5_VABI sceAgcWriteDataPatchSetDst(std::uint32_t*, std::uint8_t); -int APS5_VABI sceAgcAsyncWriteDataPatchSetDst(std::uint32_t*, std::uint8_t); -int APS5_VABI sceAgcWriteDataPatchSetCachePolicy(std::uint32_t*, std::uint8_t); -int APS5_VABI sceAgcAsyncWriteDataPatchSetCachePolicy(std::uint32_t*, std::uint8_t); -} - namespace { constexpr std::array Payload{0x11111111u, 0x22222222u, 0x33333333u}; diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 177d7b1d5c..521a3f93bd 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -79,6 +79,18 @@ foreach(agcReadbackKind IN ITEMS Uint Depth) list(APPEND agcReadbackHeaders ${agcReadbackHeader}) endforeach() +set(agcMultisampleUploadSpv ${agcTextureDetileGeneratedDir}/MultisampleUpload.spv) +set(agcMultisampleUploadHeader ${agcTextureDetileGeneratedDir}/MultisampleUpload_spv.h) +add_custom_command( + OUTPUT "${agcMultisampleUploadHeader}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}" + COMMAND "$" -V --target-env vulkan1.1 -o "${agcMultisampleUploadSpv}" "${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/MultisampleUpload.comp" + COMMAND ${CMAKE_COMMAND} -DINPUT=${agcMultisampleUploadSpv} -DOUTPUT=${agcMultisampleUploadHeader} + -DSYMBOL=MULTISAMPLE_UPLOAD_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/MultisampleUpload.comp" glslang-standalone + VERBATIM +) + set(agcMeshArgumentsShaderSpv ${agcTextureDetileGeneratedDir}/MeshArguments.spv) set(agcMeshArgumentsShaderHeader ${agcTextureDetileGeneratedDir}/MeshArguments_spv.h) add_custom_command( @@ -130,28 +142,6 @@ add_custom_command( VERBATIM ) -set(agcDepthSampleShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/DepthSample.comp) -foreach(agcDepthSampleKind IN ITEMS Single Multisample) - string(TOUPPER ${agcDepthSampleKind} agcDepthSampleSymbol) - if(agcDepthSampleKind STREQUAL "Multisample") - set(agcDepthSampleMultisampled 1) - else() - set(agcDepthSampleMultisampled 0) - endif() - set(agcDepthSampleShaderSpv ${agcTextureDetileGeneratedDir}/DepthSample${agcDepthSampleKind}.spv) - set(agcDepthSampleShaderHeader ${agcTextureDetileGeneratedDir}/DepthSample${agcDepthSampleKind}_spv.h) - add_custom_command( - OUTPUT "${agcDepthSampleShaderHeader}" - COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}" - COMMAND "$" -V --target-env vulkan1.1 -DMULTISAMPLED=${agcDepthSampleMultisampled} -o "${agcDepthSampleShaderSpv}" "${agcDepthSampleShaderSource}" - COMMAND ${CMAKE_COMMAND} -DINPUT=${agcDepthSampleShaderSpv} -DOUTPUT=${agcDepthSampleShaderHeader} - -DSYMBOL=DEPTH_SAMPLE_${agcDepthSampleSymbol}_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" - DEPENDS "${agcDepthSampleShaderSource}" glslang-standalone - VERBATIM - ) - list(APPEND agcDepthSampleShaderHeaders ${agcDepthSampleShaderHeader}) -endforeach() - set(agcSampleCounterShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/SampleCounter.comp) set(agcSampleCounterShaderSpv ${agcTextureDetileGeneratedDir}/SampleCounter.spv) set(agcSampleCounterShaderHeader ${agcTextureDetileGeneratedDir}/SampleCounter_spv.h) @@ -213,6 +203,22 @@ foreach(agcSampleOrderVariant IN ITEMS "R8;comp;-DTEXEL_FORMAT=r8ui" "R16;comp;- ) list(APPEND agcSampleOrderShaderHeaders ${agcSampleOrderShaderHeader}) endforeach() +foreach(agcFirstSampleClass IN ITEMS Float Uint Sint) + string(TOUPPER "${agcFirstSampleClass}" agcFirstSampleSymbol) + set(agcFirstSampleShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/FirstSample.frag) + set(agcFirstSampleShaderSpv ${agcTextureDetileGeneratedDir}/FirstSample${agcFirstSampleClass}.spv) + set(agcFirstSampleShaderHeader ${agcTextureDetileGeneratedDir}/FirstSample${agcFirstSampleClass}_spv.h) + add_custom_command( + OUTPUT "${agcFirstSampleShaderHeader}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}" + COMMAND "$" -V --target-env vulkan1.1 -D${agcFirstSampleSymbol}_SAMPLES=1 -o "${agcFirstSampleShaderSpv}" "${agcFirstSampleShaderSource}" + COMMAND ${CMAKE_COMMAND} -DINPUT=${agcFirstSampleShaderSpv} -DOUTPUT=${agcFirstSampleShaderHeader} + -DSYMBOL=FIRST_SAMPLE_${agcFirstSampleSymbol}_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" + DEPENDS "${agcFirstSampleShaderSource}" glslang-standalone + VERBATIM + ) + list(APPEND agcPresentationShaderHeaders ${agcFirstSampleShaderHeader}) +endforeach() foreach(agcOverlayStage IN ITEMS Vert Frag) string(TOLOWER "${agcOverlayStage}" agcOverlayExtension) string(TOUPPER "${agcOverlayStage}" agcOverlaySymbol) @@ -301,6 +307,8 @@ set(agcDriverSources ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/ShaderDeviceProfile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Pm4.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/SubmissionCapture.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/FrameCapture.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/FrameRecorder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/VulkanDevice.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/FloatFlushProbe.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/AspectFit.cpp @@ -311,6 +319,7 @@ set(agcDriverSources ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Draw.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/DepthSurface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/MultisampleTarget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/FirstSampleCopy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ColorTargetLayout.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ColorTargetTransfer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Pipeline.cpp @@ -318,6 +327,7 @@ set(agcDriverSources ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PipelineCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PipelineJournal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Resources.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/DeferredDestruction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PlaceholderImages.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Recorder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ParallelCompare.cpp @@ -325,6 +335,7 @@ set(agcDriverSources ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ShaderResources.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ShaderAddressResources.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/GuestBufferMemory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ResidentMirror.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/BdaResources.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/BufferAddress.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ShaderValidation.cpp @@ -334,8 +345,10 @@ set(agcDriverSources ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/TextureFormat.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/DccMetadata.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/TextureTiling.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/MultisampleTextureLayout.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/TextureDetiler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/MultisampleReadback.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/MultisampleUpload.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Texture.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PassHazards.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/SampleOrder.cpp @@ -345,8 +358,8 @@ set(agcDriverSources ${agcSampleOrderShaderHeaders} ${agcTextureDetileShaderHeader} ${agcReadbackHeaders} + ${agcMultisampleUploadHeader} ${agcCmaskClearShaderHeader} - ${agcDepthSampleShaderHeaders} ${agcMeshArgumentsShaderHeader} ${agcTextureDetileImageShaderHeader} ${agcTextureRepackShaderHeader} @@ -408,6 +421,7 @@ target_link_libraries(libSceAgcDriver PRIVATE libc libkernel jpeg_turbo + libzstd_static ${CMAKE_DL_LIBS} ) if(APS5_ENABLE_TRACY) @@ -418,6 +432,16 @@ if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") endif() if(BUILD_TESTING) + add_test_executable(agc_driver_unsupported_diagnostics_tests tests/UnsupportedDiagnostics.cpp) + target_include_directories(agc_driver_unsupported_diagnostics_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_driver_unsupported_diagnostics_tests PRIVATE libSceAgcDriver) + configure_windows_unwind(agc_driver_unsupported_diagnostics_tests) + add_test(NAME agc_driver_unsupported_diagnostics COMMAND agc_driver_unsupported_diagnostics_tests) + if(WIN32) + set_tests_properties(agc_driver_unsupported_diagnostics PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(agc_driver_draw_program_trace_tests tests/DrawProgramTrace.cpp Execution/src/Driver/Draw/DrawProgramTrace.cpp @@ -444,6 +468,16 @@ if(WIN32) endif() if(BUILD_TESTING) + add_test_executable(agc_driver_nop_data_packet_tests tests/NopDataWritePacket.cpp) + target_include_directories(agc_driver_nop_data_packet_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_driver_nop_data_packet_tests PRIVATE libSceAgcDriver) + configure_windows_unwind(agc_driver_nop_data_packet_tests) + add_test(NAME agc_driver_nop_data_packet COMMAND agc_driver_nop_data_packet_tests) + if(WIN32) + set_tests_properties(agc_driver_nop_data_packet PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(agc_frame_dump_io_tests tests/FrameDumpIo.cpp) target_include_directories(agc_frame_dump_io_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_frame_dump_io_tests PRIVATE libc) @@ -455,6 +489,18 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(agc_frame_capture_tests tests/FrameCapture.cpp Execution/src/FrameCapture.cpp) + target_include_directories(agc_frame_capture_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_frame_capture_tests PRIVATE libc libzstd_static) + set_target_properties(agc_frame_capture_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(agc_frame_capture_tests) + add_test(NAME agc_frame_capture COMMAND agc_frame_capture_tests) + set_tests_properties(agc_frame_capture PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_frame_capture PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(agc_write_watch_coverage_tests tests/WriteWatchCoverage.cpp) target_include_directories(agc_write_watch_coverage_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_write_watch_coverage_tests PRIVATE libc) @@ -520,6 +566,42 @@ if(BUILD_TESTING) set_tests_properties(agc_handoff_spin PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(agc_capture_trace_tests tests/CaptureTrace.cpp) + target_include_directories(agc_capture_trace_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_capture_trace_tests PRIVATE libc) + configure_windows_unwind(agc_capture_trace_tests) + set(agcCaptureTraceDirectory ${CMAKE_CURRENT_BINARY_DIR}/capture_trace_tests) + file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/default") + file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/explicit") + file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/failure") + file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/best-effort") + file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/disabled") + set(agcCaptureTraceExplicitPath "${agcCaptureTraceDirectory}/explicit/trace.log") + set(agcCaptureTraceFailurePath "${agcCaptureTraceDirectory}/failure/missing-parent/trace.log") + set(agcCaptureTraceBestEffortPath "${agcCaptureTraceDirectory}/best-effort/trace.log") + add_test(NAME agc_capture_trace_path_default COMMAND agc_capture_trace_tests path-default) + add_test(NAME agc_capture_trace_path_empty COMMAND agc_capture_trace_tests path-empty) + set_tests_properties(agc_capture_trace_path_empty PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE_PATH=") + add_test(NAME agc_capture_trace_path_explicit COMMAND agc_capture_trace_tests path-explicit) + set_tests_properties(agc_capture_trace_path_explicit PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceExplicitPath}") + add_test(NAME agc_capture_trace_explicit_writes COMMAND agc_capture_trace_tests explicit-writes) + set_tests_properties(agc_capture_trace_explicit_writes PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceExplicitPath}") + add_test(NAME agc_capture_trace_default_writes COMMAND agc_capture_trace_tests default-writes) + set_tests_properties(agc_capture_trace_default_writes PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/default") + add_test(NAME agc_capture_trace_open_failure COMMAND agc_capture_trace_tests open-failure) + set_tests_properties(agc_capture_trace_open_failure PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceFailurePath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/failure") + add_test(NAME agc_capture_trace_best_effort_overflow COMMAND agc_capture_trace_tests best-effort-overflow) + set_tests_properties(agc_capture_trace_best_effort_overflow PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceBestEffortPath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/best-effort") + add_test(NAME agc_capture_trace_disabled COMMAND agc_capture_trace_tests disabled) + set_tests_properties(agc_capture_trace_disabled PROPERTIES WORKING_DIRECTORY "${agcCaptureTraceDirectory}/disabled") + if(WIN32) + set_tests_properties(agc_capture_trace_path_default agc_capture_trace_path_empty + agc_capture_trace_path_explicit agc_capture_trace_explicit_writes + agc_capture_trace_default_writes agc_capture_trace_open_failure agc_capture_trace_best_effort_overflow + agc_capture_trace_disabled PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() if(WIN32) @@ -678,6 +760,28 @@ target_link_libraries(agc_shared_write_tests PRIVATE libSceAgcDriver libkernel l set_target_properties(agc_shared_write_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_shared_write_tests) +if(BUILD_TESTING AND WIN32) + add_test_executable(agc_page_state_tests tests/PageStates.cpp) + target_include_directories(agc_page_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_page_state_tests PRIVATE libSceAgcDriver libc) + configure_windows_unwind(agc_page_state_tests) + add_test(NAME agc_page_states COMMAND agc_page_state_tests) + add_test(NAME agc_page_state_metadata COMMAND agc_page_state_tests --metadata) + set_tests_properties(agc_page_states agc_page_state_metadata PROPERTIES + TIMEOUT 30 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") +endif() + +add_test_executable(agc_locks_tests EXCLUDE_FROM_ALL tests/Locks.cpp) +target_include_directories(agc_locks_tests PRIVATE + ${LIBS_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/core/shader/recompiler + ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} + ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR} +) +target_link_libraries(agc_locks_tests PRIVATE libSceAgcDriver libc) +set_target_properties(agc_locks_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_locks_tests) + add_test_executable(agc_write_tracking_tests EXCLUDE_FROM_ALL tests/WriteTracking.cpp Execution/src/GuestMemory.cpp @@ -696,11 +800,32 @@ if(BUILD_TESTING) set_tests_properties(agc_write_tracking_metadata_commit PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) add_test(NAME agc_resident_direct_remap COMMAND agc_write_tracking_tests --resident-direct-remap) set_tests_properties(agc_resident_direct_remap PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 20) - set_tests_properties(agc_write_tracking agc_write_tracking_metadata_commit agc_resident_direct_remap PROPERTIES ENVIRONMENT_MODIFICATION + add_test(NAME agc_rewatched_direct_shared COMMAND agc_write_tracking_tests --rewatched-direct-shared) + set_tests_properties(agc_rewatched_direct_shared PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 20) + add_test(NAME agc_import_alias_shared COMMAND agc_write_tracking_tests --import-alias-shared) + set_tests_properties(agc_import_alias_shared PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 20) + add_test(NAME agc_compared_shared_generations COMMAND agc_write_tracking_tests --compared-shared-generations) + set_tests_properties(agc_compared_shared_generations PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 20) + add_test(NAME agc_write_tracking_rewatch_import_pages COMMAND agc_write_tracking_tests --rewatch-import-pages) + set_tests_properties(agc_write_tracking_rewatch_import_pages PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 20) + set_tests_properties(agc_write_tracking agc_write_tracking_metadata_commit agc_resident_direct_remap agc_rewatched_direct_shared agc_import_alias_shared agc_compared_shared_generations agc_write_tracking_rewatch_import_pages PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() +if(BUILD_TESTING AND NOT WIN32) + add_test_executable(agc_collect_memo_generation_tests + tests/CollectMemoGeneration.cpp + Execution/src/GuestMemory.cpp + Execution/src/PerformanceTraceThread.cpp + ) + target_include_directories(agc_collect_memo_generation_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_collect_memo_generation_tests PRIVATE shader_recompiler_agc_driver libc) + set_target_properties(agc_collect_memo_generation_tests PROPERTIES CXX_EXTENSIONS OFF) + add_test(NAME agc_collect_memo_generation COMMAND agc_collect_memo_generation_tests) + set_tests_properties(agc_collect_memo_generation PROPERTIES TIMEOUT 30) +endif() + add_test_executable(agc_thread_priority_tests EXCLUDE_FROM_ALL tests/ThreadPriority.cpp Execution/src/ThreadPriority.cpp @@ -738,6 +863,15 @@ if(BUILD_TESTING) add_test(NAME agc_fragment_barycentric COMMAND agc_fragment_barycentric_tests) endif() +add_test_executable(agc_tessellation_pipeline_cache_tests EXCLUDE_FROM_ALL tests/TessellationPipelineCache.cpp) +target_include_directories(agc_tessellation_pipeline_cache_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) +set_target_properties(agc_tessellation_pipeline_cache_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_tessellation_pipeline_cache_tests) +if(BUILD_TESTING) + set_target_properties(agc_tessellation_pipeline_cache_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_tessellation_pipeline_cache COMMAND agc_tessellation_pipeline_cache_tests) +endif() + add_test_executable(agc_float32_flush_control_tests EXCLUDE_FROM_ALL tests/Float32FlushControl.cpp) target_include_directories(agc_float32_flush_control_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) set_target_properties(agc_float32_flush_control_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -756,15 +890,6 @@ if(BUILD_TESTING) add_test(NAME agc_pipeline_link_optimization COMMAND agc_pipeline_link_optimization_tests) endif() -add_test_executable(agc_robust_buffer_access_tests EXCLUDE_FROM_ALL tests/RobustBufferAccess.cpp) -target_include_directories(agc_robust_buffer_access_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) -set_target_properties(agc_robust_buffer_access_tests PROPERTIES CXX_EXTENSIONS OFF) -configure_windows_unwind(agc_robust_buffer_access_tests) -if(BUILD_TESTING) - set_target_properties(agc_robust_buffer_access_tests PROPERTIES EXCLUDE_FROM_ALL OFF) - add_test(NAME agc_robust_buffer_access COMMAND agc_robust_buffer_access_tests) -endif() - add_test_executable(agc_draw_inputs_in_place_driver_tests EXCLUDE_FROM_ALL tests/DrawInputsInPlace.cpp) target_include_directories(agc_draw_inputs_in_place_driver_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) set_target_properties(agc_draw_inputs_in_place_driver_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -1124,6 +1249,17 @@ if(BUILD_TESTING) endif() add_test_executable(agc_buffer_pool_tests EXCLUDE_FROM_ALL tests/BufferPool.cpp) + +if(BUILD_TESTING) + add_test_executable(agc_snapshot_copy_failure_tests tests/SnapshotCopyFailure.cpp tests/AllocationFailure.cpp) + target_include_directories(agc_snapshot_copy_failure_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) + if(MINGW) + target_compile_options(agc_snapshot_copy_failure_tests PRIVATE -fasynchronous-unwind-tables) + endif() + configure_windows_unwind(agc_snapshot_copy_failure_tests) + add_test(NAME agc_snapshot_copy_failure COMMAND agc_snapshot_copy_failure_tests) + set_tests_properties(agc_snapshot_copy_failure PROPERTIES TIMEOUT 10) +endif() target_sources(agc_buffer_pool_tests PRIVATE Graphics/src/BufferPool.cpp tests/AllocationFailure.cpp) target_include_directories(agc_buffer_pool_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_buffer_pool_tests PRIVATE libSceAgcDriver libc) @@ -1144,6 +1280,20 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_deferred_destruction_tests EXCLUDE_FROM_ALL tests/DeferredDestruction.cpp) +target_include_directories(agc_deferred_destruction_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) +target_link_libraries(agc_deferred_destruction_tests PRIVATE libSceAgcDriver libc) +set_target_properties(agc_deferred_destruction_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_deferred_destruction_tests) +if(BUILD_TESTING) + set_target_properties(agc_deferred_destruction_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_deferred_destruction COMMAND agc_deferred_destruction_tests) + if(WIN32) + set_tests_properties(agc_deferred_destruction PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_device_memory_fallback_tests EXCLUDE_FROM_ALL tests/DeviceMemoryFallback.cpp) target_include_directories(agc_device_memory_fallback_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_device_memory_fallback_tests PRIVATE libSceAgcDriver libc) @@ -1397,6 +1547,7 @@ target_include_directories(agc_driver_recorder_tests PRIVATE ) target_link_libraries(agc_driver_recorder_tests PRIVATE + libSceAgc libkernel libSceAgcDriver SDL2-static @@ -1416,16 +1567,32 @@ if(BUILD_TESTING) set_tests_properties(agc_recorder_device_selection agc_gpu_timing_complete agc_gpu_timing_incomplete PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_command_memory_reuse COMMAND agc_driver_recorder_tests --command-reuse-only) + set_tests_properties(agc_command_memory_reuse PROPERTIES SKIP_RETURN_CODE 77) + if(WIN32) + set_tests_properties(agc_command_memory_reuse PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_pipeline_cache COMMAND agc_driver_recorder_tests --pipeline-cache-only) add_test(NAME agc_pipeline_cache_file COMMAND agc_driver_recorder_tests --pipeline-cache-file-only) + add_test(NAME agc_pipeline_cache_header COMMAND agc_driver_recorder_tests --pipeline-cache-header-only) + set_tests_properties(agc_pipeline_cache_header PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77) + if(WIN32) + set_tests_properties(agc_pipeline_cache_header PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_pipeline_library COMMAND agc_driver_recorder_tests --pipeline-library-only) add_test(NAME agc_pipeline_library_off COMMAND agc_driver_recorder_tests --pipeline-library-only) set_tests_properties(agc_pipeline_library agc_pipeline_library_off PROPERTIES SKIP_RETURN_CODE 77) set_tests_properties(agc_pipeline_library_off PROPERTIES ENVIRONMENT "APS5_NO_GPL=1") add_test(NAME agc_pipeline_library_fast_only COMMAND agc_driver_recorder_tests --pipeline-library-only) set_tests_properties(agc_pipeline_library_fast_only PROPERTIES ENVIRONMENT "APS5_NO_PIPELINE_LTO=1" SKIP_RETURN_CODE 77) + add_test(NAME agc_pipeline_library_lto_info COMMAND agc_driver_recorder_tests --pipeline-library-lto-info-only) + add_test(NAME agc_pipeline_library_lto_info_fast_only COMMAND agc_driver_recorder_tests --pipeline-library-lto-info-only) + set_tests_properties(agc_pipeline_library_lto_info agc_pipeline_library_lto_info_fast_only PROPERTIES SKIP_RETURN_CODE 77) + set_tests_properties(agc_pipeline_library_lto_info_fast_only PROPERTIES ENVIRONMENT "APS5_NO_PIPELINE_LTO=1") if(WIN32) - set_tests_properties(agc_pipeline_library agc_pipeline_library_off agc_pipeline_library_fast_only PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_pipeline_library agc_pipeline_library_off agc_pipeline_library_fast_only agc_pipeline_library_lto_info agc_pipeline_library_lto_info_fast_only PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() set_tests_properties(agc_pipeline_cache agc_pipeline_cache_file PROPERTIES SKIP_RETURN_CODE 77) @@ -1435,24 +1602,33 @@ if(BUILD_TESTING) endif() add_test(NAME agc_import_watch COMMAND agc_driver_recorder_tests --import-watch-only) set_tests_properties(agc_import_watch PROPERTIES SKIP_RETURN_CODE 77) + add_test(NAME agc_import_settle_initial_dcc COMMAND agc_driver_recorder_tests --settle-initial-dcc-only) + set_tests_properties(agc_import_settle_initial_dcc PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) if(WIN32) add_test(NAME agc_import_watch_other_family COMMAND agc_driver_recorder_tests --import-watch-other-family-only) set_tests_properties(agc_import_watch_other_family PROPERTIES SKIP_RETURN_CODE 77) add_test(NAME agc_import_settle_deferred COMMAND agc_driver_recorder_tests --deferred-settle-only) add_test(NAME agc_import_settle_deferred_other_family COMMAND agc_driver_recorder_tests --deferred-settle-other-family-only) + add_test(NAME agc_import_settle_write_order COMMAND agc_driver_recorder_tests --settle-write-order-only) + add_test(NAME agc_import_settle_write_order_other_family COMMAND agc_driver_recorder_tests --settle-write-order-other-family-only) + add_test(NAME agc_import_settle_wait_failure COMMAND agc_driver_recorder_tests --settle-wait-failure-only) + set_tests_properties(agc_import_settle_write_order agc_import_settle_write_order_other_family agc_import_settle_wait_failure PROPERTIES + SKIP_RETURN_CODE 77 TIMEOUT 30 + FAIL_REGULAR_EXPRESSION "SYNC-HAZARD-|VUID-" + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") add_test(NAME agc_import_settle_first_use COMMAND agc_driver_recorder_tests --settle-first-use-only) add_test(NAME agc_import_settle_earlier_import COMMAND agc_driver_recorder_tests --settle-earlier-import-only) add_test(NAME agc_import_settle_retire COMMAND agc_driver_recorder_tests --settle-retire-only) add_test(NAME agc_import_settle_storage_image COMMAND agc_driver_recorder_tests --settle-storage-image-only) set_tests_properties(agc_import_settle_deferred agc_import_settle_deferred_other_family agc_import_settle_first_use agc_import_settle_earlier_import agc_import_settle_retire agc_import_settle_storage_image PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 60) - set_tests_properties(agc_import_watch agc_import_watch_other_family agc_import_settle_deferred agc_import_settle_deferred_other_family agc_import_settle_first_use agc_import_settle_earlier_import agc_import_settle_retire agc_import_settle_storage_image PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_import_watch agc_import_watch_other_family agc_import_settle_deferred agc_import_settle_deferred_other_family agc_import_settle_first_use agc_import_settle_earlier_import agc_import_settle_retire agc_import_settle_storage_image agc_import_settle_initial_dcc PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() if(WIN32) add_test(NAME agc_reimport_bench COMMAND agc_driver_recorder_tests --reimport-bench) - add_test(NAME agc_trimmed_import COMMAND agc_driver_recorder_tests --trimmed-import-only) - set_tests_properties(agc_reimport_bench agc_trimmed_import PROPERTIES SKIP_RETURN_CODE 77) - set_tests_properties(agc_reimport_bench agc_trimmed_import PROPERTIES ENVIRONMENT_MODIFICATION + add_test(NAME agc_host_import_cut_lifetime COMMAND agc_driver_recorder_tests --host-import-cut-only) + set_tests_properties(agc_reimport_bench agc_host_import_cut_lifetime PROPERTIES SKIP_RETURN_CODE 77) + set_tests_properties(agc_reimport_bench agc_host_import_cut_lifetime PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_unchanged_cpu_stamps COMMAND agc_driver_recorder_tests --unchanged-cpu-stamps-only) @@ -1461,6 +1637,25 @@ if(BUILD_TESTING) set_tests_properties(agc_unchanged_cpu_stamps PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + foreach(mode IN ITEMS map ring shared uncached) + add_test(NAME agc_collect_mapping_history_${mode} COMMAND agc_driver_recorder_tests --collect-mapping-history-only) + set_tests_properties(agc_collect_mapping_history_${mode} PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_collect_mapping_history_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + set_tests_properties(agc_collect_mapping_history_ring PROPERTIES ENVIRONMENT "APS5_NO_EPOCH_COLLECT_MAP=1") + set_tests_properties(agc_collect_mapping_history_shared PROPERTIES ENVIRONMENT "APS5_SHARED_COLLECT_MEMO=1") + set_tests_properties(agc_collect_mapping_history_uncached PROPERTIES ENVIRONMENT "APS5_NO_COLLECT_MEMO=1") + if(WIN32) + foreach(mode IN ITEMS cached uncached) + add_test(NAME agc_hint_mapping_history_${mode} COMMAND agc_driver_recorder_tests --hint-mapping-history-only) + set_tests_properties(agc_hint_mapping_history_${mode} PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endforeach() + set_tests_properties(agc_hint_mapping_history_uncached PROPERTIES ENVIRONMENT "APS5_NO_COLLECT_MEMO=1") + endif() if(WIN32) add_test(NAME agc_partial_watch_restoration COMMAND agc_driver_recorder_tests --partial-watch-restoration-only) set_tests_properties(agc_partial_watch_restoration PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT_MODIFICATION @@ -1503,6 +1698,12 @@ if(BUILD_TESTING) set_tests_properties(agc_storage_baseline_settled PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_bda_staging COMMAND agc_driver_recorder_tests --bda-staging-only) + set_tests_properties(agc_bda_staging PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_bda_staging PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_read_written_staging COMMAND agc_driver_recorder_tests --read-written-staging-only) set_tests_properties(agc_read_written_staging PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) if(WIN32) @@ -1565,6 +1766,22 @@ if(BUILD_TESTING) set_tests_properties(agc_sparse_run_import_copied PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) add_test(NAME agc_unit_shadow COMMAND agc_driver_recorder_tests --unit-shadow-only) set_tests_properties(agc_unit_shadow PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000") + add_test(NAME agc_idle_publish_wait COMMAND agc_driver_recorder_tests --idle-publish-wait-only) + set_tests_properties(agc_idle_publish_wait PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000;APS5_WRITE_WATCH_IMPORTS=watch") + add_test(NAME agc_driver_bda_reads_shadowed_results COMMAND agc_driver_recorder_tests --bda-shadowed-results-only) + add_test(NAME agc_driver_bda_reads_shadowed_results_per_build COMMAND agc_driver_recorder_tests --bda-shadowed-results-only) + set_tests_properties(agc_driver_bda_reads_shadowed_results agc_driver_bda_reads_shadowed_results_per_build PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + set_tests_properties(agc_driver_bda_reads_shadowed_results_per_build PROPERTIES ENVIRONMENT "APS5_NO_ADDRESS_SPACE_CACHE=1") + if(WIN32) + set_tests_properties(agc_driver_bda_reads_shadowed_results agc_driver_bda_reads_shadowed_results_per_build PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_unit_shadow_completion_store COMMAND agc_driver_recorder_tests --completion-store-shadow-only) + set_tests_properties(agc_unit_shadow_completion_store PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_unit_shadow_completion_store PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(WIN32) add_test(NAME agc_storage_cpu_snapshot COMMAND agc_driver_recorder_tests --storage-cpu-snapshot-only) set_tests_properties(agc_storage_cpu_snapshot PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 SKIP_RETURN_CODE 77 @@ -1598,7 +1815,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_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_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 "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 @@ -1610,10 +1827,12 @@ if(BUILD_TESTING) set_tests_properties(agc_shadowed_key_stores PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) add_test(NAME agc_sample_dumps COMMAND agc_driver_recorder_tests --sample-dumps-only) set_tests_properties(agc_sample_dumps PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + add_test(NAME agc_uncounted_passes COMMAND agc_driver_recorder_tests --uncounted-passes-only) + set_tests_properties(agc_uncounted_passes PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) add_test(NAME agc_draw_descriptors COMMAND agc_driver_recorder_tests --draw-descriptors-only) set_tests_properties(agc_draw_descriptors PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 60) if(WIN32) - set_tests_properties(agc_pending_key_stores agc_shadowed_key_stores agc_sample_dumps agc_draw_descriptors PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_pending_key_stores agc_shadowed_key_stores agc_sample_dumps agc_uncounted_passes agc_draw_descriptors PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_driver_host_import_lifetime COMMAND agc_driver_recorder_tests --host-import-lifetime-only) @@ -1626,12 +1845,27 @@ if(BUILD_TESTING) ENVIRONMENT "APS5_PIN_WAIT_MS=2000" SKIP_RETURN_CODE 77 TIMEOUT 30) set_tests_properties(agc_driver_unit_shadow_budget PROPERTIES ENVIRONMENT "APS5_PIN_WAIT_MS=2000;APS5_UNIT_SHADOW_MIB=8" SKIP_RETURN_CODE 77 TIMEOUT 30) + add_test(NAME agc_recorder_completion_labels COMMAND agc_driver_recorder_tests --completion-labels-only) + set_tests_properties(agc_recorder_completion_labels PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + add_test(NAME agc_recorder_cmask COMMAND agc_driver_recorder_tests --cmask-only) + set_tests_properties(agc_recorder_cmask PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + add_test(NAME agc_recorder_cube COMMAND agc_driver_recorder_tests --cube-only) + set_tests_properties(agc_recorder_cube PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) if(WIN32) set_tests_properties(agc_driver_host_import_lifetime agc_driver_host_import_mutation_race agc_driver_unit_shadow agc_driver_unit_shadow_budget PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_recorder_completion_labels agc_recorder_cmask agc_recorder_cube PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") endif() set_property(TEST agc_driver_host_import_lifetime APPEND PROPERTY ENVIRONMENT_MODIFICATION "APS5_RELEASE_UNDER_LOCK=unset:" "APS5_RELEASE_ON_UNLOCK=unset:") + if(WIN32) + add_test(NAME agc_driver_host_import_lifetime_other_family COMMAND agc_driver_recorder_tests --host-import-lifetime-other-family-only) + set_tests_properties(agc_driver_host_import_lifetime_other_family PROPERTIES + ENVIRONMENT "APS5_PIN_WAIT_MS=2000" SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_property(TEST agc_driver_host_import_lifetime_other_family APPEND PROPERTY ENVIRONMENT_MODIFICATION + "APS5_RELEASE_UNDER_LOCK=unset:" "APS5_RELEASE_ON_UNLOCK=unset:") + endif() endif() add_test_executable(agc_recorder_kept_bytes_tests EXCLUDE_FROM_ALL tests/RecorderKeptBytes.cpp) @@ -1798,6 +2032,7 @@ add_test_executable(agc_driver_graphics_tests EXCLUDE_FROM_ALL tests/GuestSamplerResource.cpp tests/TextureDetiler.cpp tests/DepthSurfaceReuse.cpp + tests/LastRecordIndices.cpp ) if(BUILD_TESTING) @@ -1846,6 +2081,28 @@ endif() set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_graphics_tests) +add_test_executable(agc_driver_msaa_layout_tests EXCLUDE_FROM_ALL + tests/MultisampleTextureLayout.cpp + Graphics/src/MultisampleTextureLayout.cpp +) +target_include_directories(agc_driver_msaa_layout_tests PRIVATE + ${LIBS_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/core/shader/recompiler + ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} +) +target_link_libraries(agc_driver_msaa_layout_tests PRIVATE libc) +set_target_properties(agc_driver_msaa_layout_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_driver_msaa_layout_tests) +if(BUILD_TESTING) + set_target_properties(agc_driver_msaa_layout_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_driver_msaa_layout COMMAND agc_driver_msaa_layout_tests) + set_tests_properties(agc_driver_msaa_layout PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(agc_driver_msaa_layout PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + foreach(agcVertexRangeShader IN ITEMS VertexRange.vert VertexRangeProducer.frag) string(REPLACE "." "_" agcVertexRangeName "${agcVertexRangeShader}") string(TOUPPER "${agcVertexRangeName}_SPV" agcVertexRangeSymbol) @@ -1926,6 +2183,7 @@ foreach(agcExecutionTestSource IN LISTS agcExecutionTestSources) add_test_executable(${agcExecutionTest} EXCLUDE_FROM_ALL ${agcExecutionTestSource}) target_include_directories(${agcExecutionTest} PRIVATE ${LIBS_INCLUDE_DIR} + $ ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR} @@ -1942,6 +2200,25 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_address_passes_targets_split PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_address_passes_draw_transitions COMMAND agc_driver_address_passes_tests --targets) + set_tests_properties(agc_driver_address_passes_draw_transitions PROPERTIES ENVIRONMENT "APS5_NO_ADDRESS_READ_PASSES=1;APS5_DRAW_TRANSITIONS=1" SKIP_RETURN_CODE 77 TIMEOUT 60) + if(WIN32) + set_tests_properties(agc_driver_address_passes_draw_transitions PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + set_target_properties(agc_driver_rcp_hardware_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_driver_rcp_hardware_exact COMMAND agc_driver_rcp_hardware_tests) + set_tests_properties(agc_driver_rcp_hardware_exact PROPERTIES ENVIRONMENT "APS5_EXACT_RCP=1;ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 60) + if(WIN32) + set_tests_properties(agc_driver_rcp_hardware_exact PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_driver_depth_plane_sampling_evicting COMMAND agc_driver_depth_plane_sampling_tests --evicting) + set_tests_properties(agc_driver_depth_plane_sampling_evicting PROPERTIES ENVIRONMENT "APS5_DEPTH_PLANE_COPY_MIB=1" SKIP_RETURN_CODE 77 TIMEOUT 120) + if(WIN32) + set_tests_properties(agc_driver_depth_plane_sampling_evicting PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() foreach(agcExecutionTestName IN LISTS ANYPS5_AGC_EXECUTION_TESTS) if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/execution/${agcExecutionTestName}.cpp) @@ -1967,9 +2244,13 @@ if(NOT ANYPS5_AGC_EXECUTION_TESTS OR "PooledBufferRange" IN_LIST ANYPS5_AGC_EXEC endif() target_link_libraries(agc_driver_gpu_map_on_fiber_tests PRIVATE libSceFiber) +if(ANYPS5_ENABLE_SPIRV_TOOLS) + target_link_libraries(agc_driver_optimize_on_fiber_tests PRIVATE libSceFiber shader_recompiler_agc_driver) +endif() target_link_libraries(agc_driver_rect_list_corner_tests PRIVATE shader_recompiler_agc_driver) target_link_libraries(agc_driver_queue_zero_late_interrupt_tests PRIVATE libkernel) +target_link_libraries(agc_driver_linear_target_completion_points_tests PRIVATE libkernel) target_link_libraries(agc_driver_storage_file_write_tests PRIVATE libkernel) @@ -1983,17 +2264,55 @@ target_include_directories(agc_driver_image_table_cache_bounds_tests PRIVATE $) +if(BUILD_TESTING) + add_test(NAME agc_driver_linear_target_completion_points_sync_flip COMMAND agc_driver_linear_target_completion_points_tests --flip) + set_tests_properties(agc_driver_linear_target_completion_points_sync_flip PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_SYNC_FLIP=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_driver_linear_target_completion_points_unpipelined COMMAND agc_driver_linear_target_completion_points_tests --label --interrupt) + set_tests_properties(agc_driver_linear_target_completion_points_unpipelined PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=0" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_linear_target_completion_points_sync_flip agc_driver_linear_target_completion_points_unpipelined PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_submit_progress COMMAND agc_driver_submit_progress_tests) + set_tests_properties(agc_submit_progress PROPERTIES + ENVIRONMENT "APS5_SUBMIT_PROGRESS=1;ANYPS5_REQUIRE_VULKAN=1" TIMEOUT 30 SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_submit_progress PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + add_test(NAME agc_submit_progress_no_timeline COMMAND agc_driver_submit_progress_tests --fallback-only) + set_tests_properties(agc_submit_progress_no_timeline PROPERTIES + ENVIRONMENT "APS5_SUBMIT_PROGRESS=1;APS5_NO_TIMELINE=1;ANYPS5_REQUIRE_VULKAN=1" + TIMEOUT 30 SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_submit_progress_no_timeline PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test(NAME agc_driver_store_pending_identity COMMAND agc_driver_store_over_untracked_tests --pending-identity) set_tests_properties(agc_driver_store_pending_identity PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_driver_store_idle_results_wake COMMAND agc_driver_store_idle_results_tests --idle-epoch-wake) + set_tests_properties(agc_driver_store_idle_results_wake 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_store_pending_identity PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_driver_store_idle_results_wake PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") endif() endif() if(BUILD_TESTING) + add_test(NAME agc_driver_native_fmask COMMAND agc_driver_sample_multisample_target_tests --native-fmask) + set_tests_properties(agc_driver_native_fmask PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_native_fmask PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_native_msaa_vertex_input COMMAND agc_driver_sample_multisample_target_tests --native-vertex-input) set_tests_properties(agc_driver_native_msaa_vertex_input PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") @@ -2031,6 +2350,7 @@ if(BUILD_TESTING) endif() endif() target_include_directories(agc_driver_native_multisample_depth_tests PRIVATE $) +target_include_directories(agc_driver_partially_resident_snapshot_tests PRIVATE $) foreach(agcReadbackTestKind IN ITEMS Vertex Uint Depth) string(TOUPPER ${agcReadbackTestKind} agcReadbackTestSymbol) @@ -2071,12 +2391,22 @@ target_link_libraries(agc_driver_job_manager_compute_tests PRIVATE libSceJobMana target_include_directories(agc_driver_packed_unorm_color_target_tests PRIVATE $) +target_include_directories(agc_driver_packed_unorm4444_color_target_tests PRIVATE $) + +target_include_directories(agc_driver_packed_unorm4444_partial_write_tests PRIVATE $) + +target_include_directories(agc_driver_packed_unorm4444_multisample_partial_write_tests PRIVATE $) + target_include_directories(agc_driver_color1555_target_tests PRIVATE $) +target_include_directories(agc_driver_color_logic_op_tests PRIVATE $) + target_include_directories(agc_driver_color565_target_tests PRIVATE $) target_include_directories(agc_driver_depth_range_remap_draw_tests PRIVATE $) +target_include_directories(agc_driver_depth_bounds_emulation_draw_tests PRIVATE $) + foreach(stage IN ITEMS vert frag) set(agcProvokingShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/ProvokingVertex.${stage}) set(agcProvokingShaderSpv ${agcTestShaderGeneratedRoot}/ProvokingVertex_${stage}.spv) @@ -2142,6 +2472,14 @@ target_include_directories(agc_driver_prepared_shaders_tests PRIVATE if(BUILD_TESTING) set_target_properties(agc_shader_mode_register_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + foreach(packedStorageFormat IN ITEMS 133 134 136) + set(packedStorageTest agc_driver_image_packed16_storage_${packedStorageFormat}) + add_test(NAME ${packedStorageTest} COMMAND agc_driver_image_converted_unorm_tests --packed16 ${packedStorageFormat}) + set_tests_properties(${packedStorageTest} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30) + if(WIN32) + set_tests_properties(${packedStorageTest} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + endforeach() foreach(prebuildPolicyMode IN ITEMS unset 0 1) set(prebuildPolicyTest agc_driver_prepared_graphics_prebuild_${prebuildPolicyMode}) add_test(NAME ${prebuildPolicyTest} COMMAND agc_driver_prepared_graphics_tests --prebuild-${prebuildPolicyMode}) @@ -2187,12 +2525,14 @@ if(BUILD_TESTING) set_tests_properties(agc_shader_srt_read_patching PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_shader_table_snapshot_reuse COMMAND agc_shader_memory_tests --table-snapshot-reuse) set_tests_properties(agc_shader_table_snapshot_reuse PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_shader_table_snapshot_reuse_unmasked COMMAND agc_shader_memory_tests --table-snapshot-reuse) + set_tests_properties(agc_shader_table_snapshot_reuse_unmasked PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_MASKED_RESULT_MEMO=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_driver_image_table_vouched_lookups COMMAND agc_driver_image_table_cache_bounds_tests --vouched-lookups) set_tests_properties(agc_driver_image_table_vouched_lookups PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_WRITE_WATCH_IMPORTS=watch" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 60) add_test(NAME agc_driver_image_table_vouch_queued_draws COMMAND agc_driver_image_table_cache_bounds_tests --queued-draws) set_tests_properties(agc_driver_image_table_vouch_queued_draws PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 60) if(WIN32) - set_tests_properties(agc_shader_table_snapshot_reuse agc_driver_image_table_vouched_lookups agc_driver_image_table_vouch_queued_draws PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_shader_table_snapshot_reuse agc_shader_table_snapshot_reuse_unmasked agc_driver_image_table_vouched_lookups agc_driver_image_table_vouch_queued_draws PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_shader_type_zero_image_materialization COMMAND agc_shader_memory_tests --type-zero-image-materialization) set_tests_properties(agc_shader_type_zero_image_materialization PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") @@ -2214,8 +2554,8 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_shader_bindless agc_shader_selected_records_image_limit agc_shader_selected_records_pair_limit agc_shader_selected_records_direct_image_limit agc_shader_selected_records_direct_pair_limit agc_shader_srt_read_patching agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off agc_shader_entry_lane_proof_budget PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() - foreach(agcTest IN ITEMS agc_vertex_input_tests agc_mesh_output_budget_tests agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_ir_reclaim_tests agc_vector_window_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vertex_input_validation_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_barrier_batch_tests agc_shader_disk_cache_tests agc_screenshot_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests agc_formatted_buffer_byte_stores_tests ${agcExecutionTests} agc_fma_mix_once_tests agc_f16_single_convert_tests) - if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests" OR agcTest STREQUAL "agc_driver_pipeline_optimized_readback_tests") + foreach(agcTest IN ITEMS agc_vertex_input_tests agc_mesh_output_budget_tests agc_shared_write_tests agc_locks_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_ir_reclaim_tests agc_vector_window_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vertex_input_validation_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_barrier_batch_tests agc_shader_disk_cache_tests agc_screenshot_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests agc_formatted_buffer_byte_stores_tests ${agcExecutionTests} agc_fma_mix_once_tests agc_f16_single_convert_tests) + if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests" OR agcTest STREQUAL "agc_driver_pipeline_optimized_readback_tests" OR agcTest STREQUAL "agc_driver_rcp_hardware_tests") continue() endif() string(REGEX REPLACE "_tests$" "" agcTestName "${agcTest}") @@ -2248,15 +2588,33 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_draw_unsupported_buffer_type_skip_refused PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_period_copy_no_hle COMMAND agc_driver_period_copy_tests) + set_tests_properties(agc_driver_period_copy_no_hle PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_COPY_HLE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_period_copy_no_hle PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_sparse_indirect_indices_unreadable COMMAND agc_driver_sparse_indirect_indices_tests --unreadable) - set_tests_properties(agc_driver_sparse_indirect_indices agc_driver_sparse_indirect_indices_unreadable PROPERTIES + add_test(NAME agc_driver_sparse_indirect_indices_no_size COMMAND agc_driver_sparse_indirect_indices_tests --no-size) + add_test(NAME agc_driver_sparse_indirect_indices_no_size_unreadable COMMAND agc_driver_sparse_indirect_indices_tests --no-size-unreadable) + add_test(NAME agc_driver_sparse_indirect_indices_no_size_unreadable_record COMMAND agc_driver_sparse_indirect_indices_tests --no-size-unreadable-record) + set_tests_properties(agc_driver_sparse_indirect_indices 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 "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) - set_tests_properties(agc_driver_sparse_indirect_indices_unreadable PROPERTIES ENVIRONMENT_MODIFICATION + 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_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 "PATH=path_list_prepend:$") endif() set_tests_properties(agc_driver_pixel_implicit_derivatives PROPERTIES TIMEOUT 30) set_tests_properties(agc_driver_storage_file_write PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_SUSPEND_DRAIN=1") + set_tests_properties(agc_driver_clamped_global_fill PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_LABEL_READBACK_STORE=1") set_tests_properties(agc_driver_draw_pipeline_trace PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=8;APS5_TRACE_FRAMES=0:1;APS5_TRACE_EVENTS=64" TIMEOUT 30) set_property(TEST agc_driver_draw_pipeline_trace APPEND PROPERTY ENVIRONMENT_MODIFICATION @@ -2265,14 +2623,41 @@ if(BUILD_TESTING) --unset=APS5_TRACE_FRAMES --unset=APS5_TRACE_SECONDS --unset=APS5_TRACE_SUMMARY_EVERY $ disabled) set_tests_properties(agc_driver_draw_pipeline_trace_disabled PROPERTIES ENVIRONMENT "APS5_PIPELINED_DRAWS=8" SKIP_RETURN_CODE 77 TIMEOUT 30) + add_test(NAME agc_driver_draw_finisher_trace COMMAND agc_driver_draw_pipeline_trace_tests finisher) + set_tests_properties(agc_driver_draw_finisher_trace PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_DRAW_PIPELINE=1;APS5_PIPELINED_DRAWS=8;APS5_TRACE_FRAMES=0:1;APS5_TRACE_EVENTS=64" SKIP_RETURN_CODE 77 TIMEOUT 30) + set_property(TEST agc_driver_draw_finisher_trace APPEND PROPERTY ENVIRONMENT_MODIFICATION + "APS5_TRACE_SECONDS=unset:" "APS5_TRACE_SUMMARY_EVERY=unset:") + add_test(NAME agc_driver_draw_finisher_trace_disabled COMMAND ${CMAKE_COMMAND} -E env + --unset=APS5_TRACE_FRAMES --unset=APS5_TRACE_SECONDS --unset=APS5_TRACE_SUMMARY_EVERY + $ finisher-disabled) + set_tests_properties(agc_driver_draw_finisher_trace_disabled PROPERTIES ENVIRONMENT "APS5_DRAW_PIPELINE=1;APS5_PIPELINED_DRAWS=8" SKIP_RETURN_CODE 77 TIMEOUT 30) if(WIN32) - set_tests_properties(agc_driver_draw_pipeline_trace_disabled PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_driver_draw_pipeline_trace_disabled agc_driver_draw_finisher_trace agc_driver_draw_finisher_trace_disabled PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_copied_writer_label_order_drain COMMAND agc_driver_copied_writer_label_order_tests) + set_tests_properties(agc_driver_copied_writer_label_order_drain PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_DRAIN_COMPLETION_LABELS=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_driver_copied_writer_label_order_drain PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_driver_lazy_resolved_abi_switch_set COMMAND agc_driver_lazy_resolved_abi_tests) + set_tests_properties(agc_driver_lazy_resolved_abi_switch_set PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_LAZY_SHADER_PREPARE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_lazy_resolved_abi_switch_set PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() set_tests_properties(agc_driver_queued_raw_shader PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=8" TIMEOUT 30) + set(agcDrawPipelineTests agc_driver_draw_write_ranges agc_driver_draw_unsupported_buffer_type agc_driver_bda_span_reads agc_driver_copied_writer_copy agc_driver_image_table_reuse agc_driver_depth_storage_reuse agc_driver_staged_clear_sampled_read agc_driver_storage_write_back_overhang agc_driver_write_data_written_ahead agc_driver_gated_dispatch_capture agc_driver_buffer_load_store agc_driver_pipelined_dma agc_driver_pipelined_fill agc_driver_pipelined_predication_read agc_driver_pipelined_predicated_label_order agc_driver_pipelined_metadata_pass agc_driver_draw_cache_reuse agc_driver_draw_cache_relocation agc_driver_draw_data_hit_user_words agc_driver_finisher_label_order agc_driver_same_queue_label_wait agc_driver_unimported_label_order) + foreach(agcTestName IN LISTS agcDrawPipelineTests) + add_test(NAME ${agcTestName}_draw_pipeline COMMAND ${agcTestName}_tests) + set_tests_properties(${agcTestName}_draw_pipeline PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_DRAW_PIPELINE=1;APS5_PIPELINED_DRAWS=32;APS5_PIPELINE_DISPATCH=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(${agcTestName}_draw_pipeline PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + endforeach() add_test(NAME agc_driver_partially_resident_pending_stores COMMAND agc_driver_partially_resident_snapshot_tests --pending-stores) set_tests_properties(agc_driver_multisample_readback PROPERTIES TIMEOUT 20) - foreach(agcReadbackCase IN ITEMS uint8 uint16 uint64 uint128 depth) + foreach(agcReadbackCase IN ITEMS uint8 uint16 uint64 uint128 depth upload retile depth_retile) add_test(NAME agc_driver_multisample_readback_${agcReadbackCase} COMMAND agc_driver_multisample_readback_tests ${agcReadbackCase}) set_tests_properties(agc_driver_multisample_readback_${agcReadbackCase} PROPERTIES TIMEOUT 20 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) @@ -2338,6 +2723,9 @@ if(BUILD_TESTING) add_test(NAME agc_driver_untracked_snapshot_hint COMMAND agc_driver_partially_resident_snapshot_tests --untracked-hint) set_tests_properties(agc_driver_untracked_snapshot_hint PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + add_test(NAME agc_driver_compared_shared_snapshot COMMAND agc_driver_partially_resident_snapshot_tests --compared-shared) + set_tests_properties(agc_driver_compared_shared_snapshot PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30 + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() set_tests_properties(agc_driver_registration_trace PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_TRACE_FRAMES=0:1;APS5_TRACE_EVENTS=64" TIMEOUT 30) @@ -2386,6 +2774,11 @@ 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_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) + set_tests_properties(agc_pipeline_store_growth agc_pipeline_store_fixed_bound agc_pipeline_store_replay_growth PROPERTIES TIMEOUT 60 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + 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}") @@ -2393,7 +2786,7 @@ if(BUILD_TESTING) 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 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:$") endif() set_tests_properties(agc_driver_sparse_buffer_holes PROPERTIES TIMEOUT 30) if(WIN32) @@ -2403,12 +2796,13 @@ if(BUILD_TESTING) add_test(NAME agc_heap_mirror_unwatch_no_space_cache COMMAND agc_driver_graphics_tests --heap-mirror-unwatch) add_test(NAME agc_sparse_heap_mirror_unwatch COMMAND agc_driver_graphics_tests --sparse-heap-mirror-unwatch) add_test(NAME agc_sparse_heap_mirror_unwatch_no_space_cache COMMAND agc_driver_graphics_tests --sparse-heap-mirror-unwatch) + add_test(NAME agc_sparse_heap_mirror_budget COMMAND agc_driver_graphics_tests --sparse-heap-mirror-budget) add_test(NAME agc_heap_mirror_run_unwatch COMMAND agc_driver_graphics_tests --heap-mirror-run-unwatch) add_test(NAME agc_heap_mirror_run_unwatch_no_space_cache COMMAND agc_driver_graphics_tests --heap-mirror-run-unwatch) set_tests_properties(agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch_no_space_cache PROPERTIES ENVIRONMENT "APS5_NO_ADDRESS_SPACE_CACHE=1") - set_tests_properties(agc_heap_mirror_unwatch agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch agc_heap_mirror_run_unwatch_no_space_cache PROPERTIES TIMEOUT 30) + set_tests_properties(agc_heap_mirror_unwatch agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch agc_heap_mirror_run_unwatch_no_space_cache agc_sparse_heap_mirror_budget PROPERTIES TIMEOUT 30) if(WIN32) - set_tests_properties(agc_heap_mirror_unwatch agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch agc_heap_mirror_run_unwatch_no_space_cache PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_heap_mirror_unwatch agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch agc_heap_mirror_run_unwatch_no_space_cache agc_sparse_heap_mirror_budget PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") else() add_test(NAME agc_heap_mirror_unwatched_import COMMAND agc_driver_graphics_tests --heap-mirror-unwatched-import) set_tests_properties(agc_heap_mirror_unwatched_import PROPERTIES TIMEOUT 30) @@ -2420,6 +2814,12 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test(NAME agc_address_copy_cap COMMAND agc_driver_graphics_tests --address-copy-cap) + add_test(NAME agc_address_copy_cap_remembered COMMAND agc_driver_graphics_tests --address-copy-cap-remembered) + set_tests_properties(agc_address_copy_cap agc_address_copy_cap_remembered PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_address_copy_cap agc_address_copy_cap_remembered PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_emulated_color_compare_srgb_decode COMMAND agc_driver_emulated_color_compare_tests) set_tests_properties(agc_driver_emulated_color_compare_srgb_decode PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_SRGB_SHADER_DECODE=1" @@ -2490,6 +2890,28 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_driver_f16_to_f32_unpack_spirv PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + set(agcDrawShaderFailureTraceDirectory ${CMAKE_CURRENT_BINARY_DIR}/draw_shader_failure_tests) + file(MAKE_DIRECTORY "${agcDrawShaderFailureTraceDirectory}/failure") + file(MAKE_DIRECTORY "${agcDrawShaderFailureTraceDirectory}/success") + file(MAKE_DIRECTORY "${agcDrawShaderFailureTraceDirectory}/disabled") + file(MAKE_DIRECTORY "${agcDrawShaderFailureTraceDirectory}/failure_correlation_disabled") + set_tests_properties(agc_driver_draw_shader_failure PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_CAPTURE_TRACE=1;APS5_TRACE_SHADER_CORRELATION=1;APS5_CAPTURE_TRACE_PATH=${agcDrawShaderFailureTraceDirectory}/failure/trace.log" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_driver_draw_shader_failure_successful COMMAND agc_driver_draw_shader_failure_tests successful-draw) + set_tests_properties(agc_driver_draw_shader_failure_successful PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_CAPTURE_TRACE=1;APS5_TRACE_SHADER_CORRELATION=1;APS5_CAPTURE_TRACE_PATH=${agcDrawShaderFailureTraceDirectory}/success/trace.log" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_driver_draw_shader_failure_correlation_disabled COMMAND agc_driver_draw_shader_failure_tests disabled-correlation) + set_tests_properties(agc_driver_draw_shader_failure_correlation_disabled PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcDrawShaderFailureTraceDirectory}/disabled/trace.log" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_driver_draw_shader_failure_request_correlation_disabled COMMAND agc_driver_draw_shader_failure_tests request-failure-correlation-disabled) + set_tests_properties(agc_driver_draw_shader_failure_request_correlation_disabled PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcDrawShaderFailureTraceDirectory}/failure_correlation_disabled/trace.log" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + file(MAKE_DIRECTORY "${agcDrawShaderFailureTraceDirectory}/materialization") + file(MAKE_DIRECTORY "${agcDrawShaderFailureTraceDirectory}/materialization_correlation_disabled") + add_test(NAME agc_driver_draw_shader_failure_materialization COMMAND agc_driver_draw_shader_failure_tests materialization-failure) + set_tests_properties(agc_driver_draw_shader_failure_materialization PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_CAPTURE_TRACE=1;APS5_TRACE_SHADER_CORRELATION=1;APS5_CAPTURE_TRACE_PATH=${agcDrawShaderFailureTraceDirectory}/materialization/trace.log" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_driver_draw_shader_failure_materialization_correlation_disabled COMMAND agc_driver_draw_shader_failure_tests materialization-failure-correlation-disabled) + set_tests_properties(agc_driver_draw_shader_failure_materialization_correlation_disabled PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcDrawShaderFailureTraceDirectory}/materialization_correlation_disabled/trace.log" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_draw_shader_failure agc_driver_draw_shader_failure_successful agc_driver_draw_shader_failure_correlation_disabled agc_driver_draw_shader_failure_request_correlation_disabled agc_driver_draw_shader_failure_materialization agc_driver_draw_shader_failure_materialization_correlation_disabled PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_structurizer_intersection_off COMMAND agc_structurizer_tests) set_tests_properties(agc_structurizer_intersection_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_DOM_INTERSECTION=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_structurizer_iteration_selection_off COMMAND agc_structurizer_tests) @@ -2595,6 +3017,15 @@ if(BUILD_TESTING) set_tests_properties(agc_sampled_cache_device_selection agc_driver_sampled_cache_device_selection PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + foreach(imageRecoveryMode IN ITEMS sampled storage srgb repack) + add_test(NAME agc_driver_recover_${imageRecoveryMode}_allocation COMMAND agc_driver_sampled_cache_budget_tests recover-${imageRecoveryMode}) + set_tests_properties(agc_driver_recover_${imageRecoveryMode}_allocation PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_driver_recover_${imageRecoveryMode}_allocation PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + set_property(TEST agc_driver_recover_srgb_allocation APPEND PROPERTY ENVIRONMENT_MODIFICATION "APS5_SRGB_SHADER_DECODE=set:copy") add_test(NAME agc_driver_resource_cache_storage_admission COMMAND agc_driver_sampled_cache_budget_tests resource-admission) add_test(NAME agc_driver_resource_cache_oversized_storage_admission COMMAND agc_driver_sampled_cache_budget_tests resource-oversized) add_test(NAME agc_driver_resource_cache_storage_clear COMMAND agc_driver_sampled_cache_budget_tests resource-clear) @@ -2664,18 +3095,32 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_flip_compute agc_driver_present_mode agc_driver_worker_failure_log PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_linear_readback_label_off COMMAND agc_driver_linear_readback_label_tests) + set_tests_properties(agc_driver_linear_readback_label_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_LABEL_READBACK_STORE=1" WILL_FAIL TRUE TIMEOUT 60) + if(WIN32) + set_tests_properties(agc_driver_linear_readback_label_off PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_draw_cache_reuse_off COMMAND agc_driver_draw_cache_reuse_tests) set_tests_properties(agc_driver_draw_cache_reuse_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_GPL=1" SKIP_RETURN_CODE 77) add_test(NAME agc_driver_draw_cache_reuse_verify COMMAND agc_driver_draw_cache_reuse_tests) set_tests_properties(agc_driver_draw_cache_reuse_verify PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_VERIFY_DRAW_DATA_HITS=1" SKIP_RETURN_CODE 77) + add_test(NAME agc_driver_draw_cache_reuse_entry_verify COMMAND agc_driver_draw_cache_reuse_tests) + set_tests_properties(agc_driver_draw_cache_reuse_entry_verify PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_VERIFY_DRAW_ENTRIES=1" SKIP_RETURN_CODE 77 FAIL_REGULAR_EXPRESSION "FATAL:") + if(WIN32) + set_tests_properties(agc_driver_draw_cache_reuse_entry_verify PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_draw_cache_reuse_inline COMMAND agc_driver_draw_cache_reuse_tests) set_tests_properties(agc_driver_draw_cache_reuse_inline PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=0;APS5_VERIFY_DRAW_RECIPE=1;APS5_VERIFY_REGISTER_KEYS=1" SKIP_RETURN_CODE 77) set_tests_properties(agc_driver_draw_cache_reuse agc_driver_draw_cache_reuse_off agc_driver_draw_cache_reuse_verify agc_driver_draw_cache_reuse_inline PROPERTIES FAIL_REGULAR_EXPRESSION "FATAL:") set_property(TEST agc_driver_draw_cache_reuse agc_driver_draw_cache_reuse_off APPEND PROPERTY ENVIRONMENT "APS5_VERIFY_DRAW_RECIPE=1" "APS5_VERIFY_REGISTER_KEYS=1") add_test(NAME agc_driver_draw_data_hit_user_words_verify COMMAND agc_driver_draw_data_hit_user_words_tests) set_tests_properties(agc_driver_draw_data_hit_user_words_verify PROPERTIES ENVIRONMENT "APS5_VERIFY_DRAW_DATA_HITS=1" SKIP_RETURN_CODE 77) + add_test(NAME agc_driver_draw_data_hit_user_words_refresh COMMAND agc_driver_draw_data_hit_user_words_tests) + set_tests_properties(agc_driver_draw_data_hit_user_words_refresh PROPERTIES ENVIRONMENT "APS5_DRAW_PIPELINE=1" SKIP_RETURN_CODE 77) if(WIN32) - set_tests_properties(agc_driver_draw_cache_reuse_off agc_driver_draw_cache_reuse_verify agc_driver_draw_cache_reuse_inline agc_driver_draw_data_hit_user_words_verify PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_driver_draw_cache_reuse_off agc_driver_draw_cache_reuse_verify agc_driver_draw_cache_reuse_inline agc_driver_draw_data_hit_user_words_verify agc_driver_draw_data_hit_user_words_refresh PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() @@ -2723,7 +3168,21 @@ if(BUILD_TESTING) endif() endforeach() endforeach() - foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable registered_workgroup registered_workgroup_sync abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state abi_deferred_registration_failure abi_deferred_vertex_inputs abi_deferred_rectangle_pairs lazy lazy_sync) + add_test(NAME agc_driver_depth_plane_partial_store_unimported COMMAND agc_driver_depth_plane_partial_store_tests) + set_tests_properties(agc_driver_depth_plane_partial_store_unimported PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_HOST_IMPORT=1" TIMEOUT 30 SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_depth_plane_partial_store_unimported PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_driver_array_layer_target_cpu COMMAND agc_driver_array_layer_target_tests) + set_tests_properties(agc_driver_array_layer_target_cpu PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_HOST_IMPORT=1" TIMEOUT 30 SKIP_RETURN_CODE 77) + if(WIN32) + set_tests_properties(agc_driver_array_layer_target_cpu PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable registered_workgroup registered_workgroup_sync dispatch_ahead_prewarm abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state abi_deferred_registration_failure abi_deferred_vertex_inputs abi_deferred_rectangle_pairs lazy lazy_sync) set(preparedTest "agc_driver_prepared_shaders_${preparedMode}") if(preparedMode MATCHES "^abi_deferred_") string(REPLACE "_" "-" preparedArgument ${preparedMode}) @@ -2738,6 +3197,8 @@ if(BUILD_TESTING) add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --lazy) elseif(preparedMode STREQUAL "registered_workgroup" OR preparedMode STREQUAL "registered_workgroup_sync") add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --registered-workgroup) + elseif(preparedMode STREQUAL "dispatch_ahead_prewarm") + add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --dispatch-ahead-prewarm) elseif(preparedMode STREQUAL "debug_failure") add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --fail-before-registration) set_tests_properties(${preparedTest} PROPERTIES WILL_FAIL TRUE TIMEOUT 30) @@ -2764,15 +3225,26 @@ if(BUILD_TESTING) endif() endforeach() add_test(NAME agc_driver_shutdown_threads_joined COMMAND agc_driver_shutdown_threads_tests --joined) + add_test(NAME agc_driver_shutdown_threads_queued_prebuilds COMMAND agc_driver_shutdown_threads_tests --queued-prebuilds) add_test(NAME agc_driver_shutdown_threads_compare_helpers COMMAND agc_driver_shutdown_threads_tests --compare-helpers) add_test(NAME agc_driver_shutdown_threads_draw_committer COMMAND agc_driver_shutdown_threads_tests --draw-committer) add_test(NAME agc_driver_shutdown_threads_shader_cache_writer COMMAND agc_driver_shutdown_threads_tests --shader-cache-writer) add_test(NAME agc_driver_shutdown_threads_late_shader_cache COMMAND agc_driver_shutdown_threads_tests --late-shader-cache) add_test(NAME agc_driver_shutdown_threads_async_log_held COMMAND agc_driver_shutdown_threads_tests --async-log-held) add_test(NAME agc_driver_shutdown_threads_exit_after_start COMMAND agc_driver_shutdown_threads_tests --exit-after-start) - set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30) + set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_queued_prebuilds agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30) if(WIN32) - set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_queued_prebuilds agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_driver_prepared_shaders_abi_deferred_concurrent_lazy COMMAND agc_driver_prepared_shaders_tests --abi-deferred-concurrent) + set_tests_properties(agc_driver_prepared_shaders_abi_deferred_concurrent_lazy PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_FAILURE_MEMO=1;APS5_LAZY_SHADER_PREPARE=1;APS5_SUSPEND_DRAIN=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_prepared_shaders_abi_deferred_concurrent_lazy PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_driver_prepared_shaders_abi_deferred_compute_lazy COMMAND agc_driver_prepared_shaders_tests --abi-deferred-compute) + set_tests_properties(agc_driver_prepared_shaders_abi_deferred_compute_lazy PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_FAILURE_MEMO=1;APS5_LAZY_SHADER_PREPARE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_prepared_shaders_abi_deferred_compute_lazy PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_driver_compute_user_data_calls_registered COMMAND agc_driver_compute_user_data_calls_tests --registered) set_tests_properties(agc_driver_compute_user_data_calls_registered PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") @@ -2890,6 +3362,16 @@ set_target_properties(agc_shader_replay PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_shader_replay) if(BUILD_TESTING) + foreach(optimizer IN ITEMS none full) + if(optimizer STREQUAL "full" AND NOT ANYPS5_ENABLE_SPIRV_TOOLS) + continue() + endif() + add_test(NAME agc_shader_specialized_bda_abi_${optimizer} COMMAND agc_shader_memory_tests --specialized-bda-abi) + set_tests_properties(agc_shader_specialized_bda_abi_${optimizer} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_SPIRV_OPT=${optimizer}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_shader_specialized_bda_abi_${optimizer} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + endforeach() add_test_executable(agc_shader_replay_requests_tests tests/ShaderReplayRequests.cpp) target_link_libraries(agc_shader_replay_requests_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_shader_replay_requests_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -2908,6 +3390,18 @@ if(BUILD_TESTING) endif() endif() +add_executable(agc_frame_replay EXCLUDE_FROM_ALL + tools/FrameReplay.cpp + Execution/src/FrameCapture.cpp +) +target_include_directories(agc_frame_replay PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_frame_replay PRIVATE libSceAgcDriver libkernel libc libzstd_static) +if(WIN32) + target_link_options(agc_frame_replay PRIVATE LINKER:--image-base=0x500000000000) +endif() +set_target_properties(agc_frame_replay PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_frame_replay) + add_executable(agc_pm4_replay EXCLUDE_FROM_ALL tools/Pm4Replay.cpp tools/Pm4Analysis.cpp @@ -2954,6 +3448,64 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_pm4_replay agc_driver_pm4_replay_tool PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + if(WIN32) + add_test_executable(agc_driver_frame_capture_eop_tests tests/FrameCaptureEop.cpp Execution/src/FrameCapture.cpp) + target_include_directories(agc_driver_frame_capture_eop_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR}) + target_link_libraries(agc_driver_frame_capture_eop_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static) + set_target_properties(agc_driver_frame_capture_eop_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(agc_driver_frame_capture_eop_tests) + add_test(NAME agc_driver_frame_capture_eop COMMAND agc_driver_frame_capture_eop_tests) + set_tests_properties(agc_driver_frame_capture_eop PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-capture-eop;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=3" + TIMEOUT 120) + set_tests_properties(agc_driver_frame_capture_eop PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(agc_driver_frame_replay_state_tests tests/FrameReplayState.cpp Execution/src/FrameCapture.cpp) + target_include_directories(agc_driver_frame_replay_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_driver_frame_replay_state_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static) + set_target_properties(agc_driver_frame_replay_state_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(agc_driver_frame_replay_state_tests) + set(agcFrameReplayModes order tokens blocking) + if(WIN32) + list(APPEND agcFrameReplayModes zero shader shader_host) + endif() + foreach(mode IN LISTS agcFrameReplayModes) + set(capture "${CMAKE_CURRENT_BINARY_DIR}/frame-replay-${mode}") + add_test(NAME agc_driver_frame_replay_${mode} COMMAND agc_driver_frame_replay_state_tests ${mode} "${capture}") + set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES TIMEOUT 30) + if(mode MATCHES "^shader") + set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT + "APS5_FRAME_CAPTURE=${capture};APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=1") + endif() + if(WIN32) + set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + + if(WIN32) + add_test_executable(agc_driver_frame_replay_tests tests/FrameReplay.cpp Execution/src/FrameCapture.cpp) + target_include_directories(agc_driver_frame_replay_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_driver_frame_replay_tests PRIVATE libSceAgc libSceAgcDriver libkernel libc libzstd_static) + set_target_properties(agc_driver_frame_replay_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(agc_driver_frame_replay_tests) + add_dependencies(agc_driver_frame_replay_tests agc_frame_replay) + add_test(NAME agc_driver_frame_replay COMMAND agc_driver_frame_replay_tests $) + set_tests_properties(agc_driver_frame_replay PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2" + TIMEOUT 120) + set_tests_properties(agc_driver_frame_replay PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + add_test(NAME agc_driver_frame_replay_concurrent COMMAND agc_driver_frame_replay_tests $ concurrent) + set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-concurrent-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=8" + TIMEOUT 120) + set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() add_executable(agc_compute_pipeline_replay EXCLUDE_FROM_ALL tools/ComputePipelineReplay.cpp) @@ -2979,12 +3531,15 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_covered_centroid_state PROPERTIES TIMEOUT 30) add_test(NAME agc_driver_sample_location_grid_fallback COMMAND agc_driver_graphics_tests --sample-location-grid-fallback) set_tests_properties(agc_driver_sample_location_grid_fallback PROPERTIES TIMEOUT 30) + add_test(NAME agc_driver_unobserved_provoking_vertex COMMAND agc_driver_graphics_tests --unobserved-provoking-vertex) + set_tests_properties(agc_driver_unobserved_provoking_vertex PROPERTIES TIMEOUT 30) add_test(NAME agc_driver_sample_order_planning COMMAND agc_driver_graphics_tests --sample-order) set_tests_properties(agc_driver_sample_order_planning PROPERTIES TIMEOUT 30) if(WIN32) set_tests_properties(agc_compute_descriptor_layout_flags PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") set_tests_properties(agc_driver_covered_centroid_state PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") set_tests_properties(agc_driver_sample_location_grid_fallback PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_driver_unobserved_provoking_vertex PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") set_tests_properties(agc_driver_sample_order_planning PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_compute_pipeline_capture COMMAND agc_compute_pipeline_capture_tests) @@ -3201,6 +3756,14 @@ if(BUILD_TESTING) set_tests_properties(scalar_call PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(ir_dump_memory_tests ${LIBS_INCLUDE_DIR}/tests/IrDumpMemory.cpp) + target_link_libraries(ir_dump_memory_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(ir_dump_memory_tests) + add_test(NAME ir_dump_memory COMMAND ir_dump_memory_tests) + if(WIN32) + set_tests_properties(ir_dump_memory PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(image_sample_gather_tests ${LIBS_INCLUDE_DIR}/tests/ImageSampleGather.cpp) target_link_libraries(image_sample_gather_tests PRIVATE shader_recompiler_agc_driver libc) configure_windows_unwind(image_sample_gather_tests) @@ -3302,6 +3865,19 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_gcn_decode_tests EXCLUDE_FROM_ALL tests/GcnDecode.cpp) +target_link_libraries(agc_gcn_decode_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_gcn_decode_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_gcn_decode_tests) +if(BUILD_TESTING) + set_target_properties(agc_gcn_decode_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_gcn_decode COMMAND agc_gcn_decode_tests) + if(WIN32) + set_tests_properties(agc_gcn_decode PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_pixel_position_export_tests EXCLUDE_FROM_ALL tests/PixelPositionExport.cpp) target_link_libraries(agc_pixel_position_export_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_pixel_position_export_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -3427,6 +4003,16 @@ if(BUILD_TESTING) endif() endif() +if(BUILD_TESTING) + add_test_executable(agc_flat_scratch_tests tests/FlatScratch.cpp) + target_link_libraries(agc_flat_scratch_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(agc_flat_scratch_tests) + add_test(NAME agc_flat_scratch COMMAND agc_flat_scratch_tests) + set_tests_properties(agc_flat_scratch PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_flat_scratch PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() +endif() add_test_executable(agc_constant_select_tests EXCLUDE_FROM_ALL tests/ConstantSelect.cpp) target_link_libraries(agc_constant_select_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_constant_select_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -3442,6 +4028,6 @@ endif() if(BUILD_TESTING) get_directory_property(agcRegisteredTests TESTS) - list(FILTER agcRegisteredTests EXCLUDE REGEX "^(agc_pipeline_journal(_long_paths|_extended_paths)?|agc_driver_pipeline_journal)$") + list(FILTER agcRegisteredTests EXCLUDE REGEX "^(agc_pipeline_journal(_long_paths|_extended_paths)?|agc_driver_pipeline_journal|agc_pipeline_store_replay_growth)$") set_property(TEST ${agcRegisteredTests} APPEND PROPERTY ENVIRONMENT "APS5_NO_PIPELINE_JOURNAL=1") endif() diff --git a/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp b/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp index 80774d7216..d7f5463eae 100644 --- a/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp +++ b/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Eq/include/Event.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include @@ -20,7 +21,7 @@ struct Registration { int id; }; -std::mutex g_mutex; +AgcDriver::Mutex g_mutex; std::vector g_registrations; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp index 56dd9215f3..501c86b224 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp @@ -13,6 +13,12 @@ namespace AgcDriver { +inline const char* CaptureTracePath() { + const char* configuredPath = std::getenv("APS5_CAPTURE_TRACE_PATH"); + if (configuredPath != nullptr && configuredPath[0] != '\0') return configuredPath; + return "capture-trace.log"; +} + class CaptureTrace { public: static bool Enabled() { @@ -30,6 +36,14 @@ public: trace.append(std::string(line.data(), static_cast(size))); } + template + static void LogBestEffort(const char* format, TArgs... args) noexcept { + try { + Log(format, args...); + } catch (...) { + } + } + private: static CaptureTrace& instance() { static CaptureTrace trace; @@ -37,8 +51,13 @@ private: } CaptureTrace() { + const char* path = CaptureTracePath(); + output.open(path, std::ios::binary); + if (!output.is_open()) { + std::fprintf(stderr, "Capture trace open failed for %s\n", path); + return; + } output.exceptions(std::ios::badbit | std::ios::failbit); - output.open("capture-trace.log", std::ios::binary); worker = std::thread([this] { run(); }); } @@ -48,10 +67,11 @@ private: stopping = true; } changed.notify_one(); - worker.join(); + if (worker.joinable()) worker.join(); } void append(std::string line) { + if (!output.is_open()) return; const auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); std::lock_guard lock(mutex); if (pending.size() + line.size() + 64 > 16 * 1024 * 1024) throw std::runtime_error("Capture trace queue exceeded 16 MiB"); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/DeviceFault.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/DeviceFault.hpp index 558a25bde7..4646531f5a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/DeviceFault.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/DeviceFault.hpp @@ -6,6 +6,8 @@ #endif #include #include +#include +#include #include #include #include @@ -14,6 +16,17 @@ namespace AgcDriver { +struct DeviceDiagnostics { + bool config = false; + bool checkpoints = false; + VkDeviceDiagnosticsConfigFlagsNV flags = 0; +}; + +inline DeviceDiagnostics ChooseDeviceDiagnostics(bool requested, bool configSupported, bool checkpointsSupported) { + if (!requested || !configSupported) return {}; + return {true, checkpointsSupported, VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_ERROR_REPORTING_BIT_NV}; +} + inline const char* DeviceFaultAddressKind(VkDeviceFaultAddressTypeEXT type) { switch (type) { case VK_DEVICE_FAULT_ADDRESS_TYPE_NONE_EXT: return "none"; @@ -74,6 +87,62 @@ inline std::vector DeviceFaultReport(VkDevice device, PFN_vkGetDevi return lines; } +class BufferMarkerLog { +public: + static constexpr std::uint32_t Capacity = 4096; + static constexpr std::uint32_t Listed = 8; + + std::uint32_t Next(std::uint64_t program) { + std::uint32_t id = 0; + while (id == 0) id = next.fetch_add(1, std::memory_order_relaxed) + 1; + auto& slot = slots[id % Capacity]; + slot.program.store(program, std::memory_order_relaxed); + slot.id.store(id, std::memory_order_release); + return id; + } + + std::vector Report(std::uint32_t reached, std::uint32_t finished) const { + std::vector lines; + if (reached == 0 && finished == 0) { + lines.emplace_back("[device-fault] no buffer marker reached"); + return lines; + } + lines.push_back(Line("last dispatch reached", reached)); + if (finished == 0) lines.emplace_back("[device-fault] no dispatch finished"); + else lines.push_back(Line("last dispatch finished", finished)); + const std::uint32_t pending = reached - finished; + if (pending == 0 || pending > Capacity) return lines; + const auto listed = std::min(pending, Listed); + for (std::uint32_t i = 1; i <= listed; ++i) lines.push_back(Line("unfinished dispatch", finished + i)); + if (pending > listed) { + char line[96]; + std::snprintf(line, sizeof(line), "[device-fault] %u more unfinished dispatches", pending - listed); + lines.emplace_back(line); + } + return lines; + } + +private: + struct Slot { + std::atomic id{0}; + std::atomic program{0}; + }; + + std::string Line(const char* what, std::uint32_t id) const { + char line[128]; + const auto& slot = slots[id % Capacity]; + if (id != 0 && slot.id.load(std::memory_order_acquire) == id) { + std::snprintf(line, sizeof(line), "[device-fault] %s: marker %u, program 0x%llx", what, id, static_cast(slot.program.load(std::memory_order_relaxed))); + } else { + std::snprintf(line, sizeof(line), "[device-fault] %s: marker %u, program not in the log", what, id); + } + return line; + } + + std::atomic next{0}; + std::array slots{}; +}; + } #endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp index b599b88f95..03e68cd1b7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp @@ -1,8 +1,10 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_HPP +#include #include #include +#include #include "SceTypes.hpp" #include "SceShaders.hpp" @@ -10,8 +12,17 @@ namespace AgcDriver { void Submit(const Packet* packet, std::uint32_t queue); +struct AwaitedWait { + std::uint64_t address = 0; + std::uint64_t reference = 0; + std::uint64_t mask = 0; + std::uint32_t function = 0; + std::uint32_t bytes = 0; +}; + } +extern "C" void AgcDriverPrepareCommandWrite_nid_no_patch(std::uint64_t address, std::size_t bytes); extern "C" void AgcDriverWaitIdle_nid_postfix(); extern "C" void AgcDriverRaiseWorkerThreadPriority_nid_postfix(const char* role); extern "C" void AgcDriverLockVulkanLoader_nid_postfix(); @@ -19,7 +30,11 @@ extern "C" void AgcDriverUnlockVulkanLoader_nid_postfix(); extern "C" void AgcDriverShutdown_nid_postfix(); extern "C" void AgcDriverRequestScreenshot_nid_postfix(); extern "C" void AgcDriverSuspendPoint_nid_postfix(); +extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed); +extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* waits, std::size_t capacity); extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix(); +extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix(); +extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)()); extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader); extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span context, std::span primitive); extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span stages, std::span context, std::span primitive); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/DeviceAccess.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/DeviceAccess.hpp index 3bd84a70db..2e33081f07 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/DeviceAccess.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/DeviceAccess.hpp @@ -3,10 +3,9 @@ #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include -#include #include +#include #include -#include #include "prx/libc/include/general/AtomicSharedPtr.hpp" namespace AgcDriver::DriverDetail { @@ -33,10 +32,8 @@ public: void unlock(); private: - std::mutex mutex; - std::condition_variable changed; - std::size_t users = 0; - bool replacing = false; + static constexpr std::uint32_t Replacing = 0x80000000u; + std::atomic state{0}; }; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp index 39bb6c35c9..86505f5a7b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp @@ -10,7 +10,7 @@ namespace AgcDriver::DriverDetail { double TraceMs(); -std::uint64_t SkippedShader(); +bool SkippedShader(std::uint64_t address); class RefusedDraw final : public std::runtime_error { public: 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 3e1f59e9f4..d3f3da21fa 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp @@ -4,6 +4,7 @@ #include "UserDataUse.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include #include "prx/libc/include/general/AtomicSharedPtr.hpp" @@ -45,6 +47,7 @@ struct DrawDecode { void ReadUserWords(const QueueState& queue, DrawProgram& program, bool staticAbi); void InitializeMerged(const QueueState& queue, DrawProgram& program, std::uint32_t pointerBase, bool pointerRequired, bool staticAbi); void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const ShaderRegistry& registry, bool staticAbi, bool includeFragment); +ShaderRecompiler::ShaderVertexStageInfo DecodeOrderedVertexStageInfo(std::span header, std::uint64_t headerAddress, std::span userData, std::vector& reads, std::uint32_t hiddenUserWords); void AppendProgramMemory(std::vector& memory, const std::vector& programs); std::vector> UserDataWords(const std::vector& programs, const std::vector& roles, std::uint32_t location); @@ -88,7 +91,7 @@ struct StageDataHit { enum class DrawMiss : std::size_t { FrontDiffering, FragmentDiffering, OtherDiffering, Layout, Gate, Stages, Count }; struct DrawEntryCounters { - std::uint64_t lookups = 0, absent = 0, hits = 0, stageValidations = 0, stageEqual = 0, variantsCompared = 0, inserts = 0, variantsInserted = 0, variantsEvicted = 0, present = 0, unstable = 0, touches = 0, verifyHits = 0, verifyMismatches = 0; + std::uint64_t lookups = 0, absent = 0, hits = 0, stageValidations = 0, stageEqual = 0, variantsCompared = 0, inserts = 0, shapeUpdates = 0, variantsInserted = 0, variantsEvicted = 0, present = 0, unstable = 0, touches = 0, verifyHits = 0, verifyMismatches = 0; std::array(DrawMiss::Count)> misses{}; std::array variantHitsByRank{}; double validateUs = 0; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp index 9ca8b03dd5..f2bc4960a5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -22,6 +24,11 @@ class DrawPipeline { public: using Commit = std::function; using Range = std::pair; + struct Watch { + std::uint64_t retired = 0; + std::size_t items = 0; + std::vector writes; + }; enum class DrainReason : std::uint8_t { Packet, Flush, Labels, Capture, Indirect, Submission, Count }; static DrawPipeline& Queue0(); @@ -32,17 +39,28 @@ public: static void FollowEpoch(std::uint64_t token); static unsigned Committers(); - void Enqueue(Commit commit, std::vector writes, std::uint64_t labelAddress = 0, std::vector labelBytes = {}, std::vector reads = {}); + void Enqueue(Commit commit, std::vector writes, std::uint64_t labelAddress = 0, std::vector labelBytes = {}, std::vector reads = {}, bool draw = false); void Drain(DrainReason reason, std::uint32_t opcode = 0x100); void Stop(); + void ReleaseRetired(); static std::uint64_t DrainRequestsByOpcode(std::uint32_t opcode); - bool Busy() const { return outstanding.load(std::memory_order_acquire) != 0; } + bool Busy() const; std::size_t Outstanding() const { return outstanding.load(std::memory_order_acquire); } bool Overlaps(std::uint64_t address, std::size_t bytes); + bool OverlapsAny(std::span> ranges); + Watch WatchWrites(); + bool WrittenSince(const Watch& watch, const std::vector& reads); + std::uint64_t Retired() const { return retiredItems.load(std::memory_order_acquire); } + void NoteDecodeDrain() { decodeDrains.fetch_add(1, std::memory_order_relaxed); } + std::uint64_t DecodeDrains() const { return decodeDrains.load(std::memory_order_relaxed); } std::optional PendingLabel(std::uint64_t address, std::size_t bytes); static bool HeldReads(std::uint64_t address, std::size_t bytes); + static std::uint64_t HeldReadQueries(); void ReleaseRunningReads(); + static bool MetadataPassesPipelined(); + static void CountPipelinedMetadataPass(); + static std::uint64_t PipelinedMetadataPasses(); private: struct Item { Commit commit; @@ -50,22 +68,32 @@ private: std::uint64_t labelAddress = 0; std::vector labelBytes; std::vector reads; + bool draw = false; }; DrawPipeline() = default; void run(); void rethrowFailure(); void report(std::chrono::steady_clock::time_point now); + void countWrites(const std::vector& writes, bool added); std::mutex mutex; std::condition_variable wake; std::condition_variable idle; std::deque items; + std::vector> queuedWrites; + std::vector retired; std::exception_ptr failure; std::atomic outstanding{0}; + std::atomic retiredItems{0}; + std::atomic decodeDrains{0}; + std::uint64_t reportedDecodeDrains = 0; std::uint32_t idleWaiters = 0; + std::size_t resumeAt = 0; bool committerWaiting = false; bool stopped = false; bool closing = false; + std::uint64_t wakes = 0; + std::uint64_t lockWaitNs = 0; std::thread thread; std::uint64_t commits = 0; std::uint64_t commitNs = 0; @@ -80,6 +108,54 @@ private: std::chrono::steady_clock::time_point lastReport = std::chrono::steady_clock::now(); }; +class DrawFinisher { +public: + using Job = std::function; + using Range = std::pair; + struct Pending { + std::uint64_t key = 0; + std::uint64_t shape = 0; + bool unknown = true; + std::vector writes; + std::uint64_t labelAddress = 0; + std::vector labelBytes; + }; + enum class LabelLookup : std::uint8_t { NotHeld, Value, Unknown }; + static bool Overlap(); + bool PendingOverlaps(std::uint64_t begin, std::uint64_t end); + LabelLookup PendingLabel(std::uint64_t address, std::size_t bytes, std::uint64_t& value); + bool PendingOverlapsAny(std::span ranges); + bool PendingKey(std::uint64_t key, std::uint64_t shape); + enum class DrainSite : std::uint32_t { Lookup, Reads, Packet, Kept, Count }; + static void NoteDrain(DrainSite site, bool drained); + static std::string DrainText(); + static void NoteDrainedOpcode(std::uint32_t opcode, bool besideCommits); + static DrawFinisher& Get(); + static bool Enabled(); + static bool OnFinisherThread(); + static bool Busy() { return outstanding.load(std::memory_order_acquire) != 0; } + void Enqueue(Job job); + void Enqueue(Job job, Pending held); + void Drain(); + void Settle(); + void ReleaseRan(); + +private: + DrawFinisher() = default; + void run(); + inline static std::atomic outstanding{0}; + std::mutex mutex; + std::condition_variable wake; + std::condition_variable idle; + std::deque jobs; + std::deque pending; + std::vector ran; + std::exception_ptr failure; + bool running = false; + bool sleeping = false; + std::thread thread; +}; + } #endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 3fb9f473b3..e2b2f7be80 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -2,6 +2,7 @@ #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_DRIVER_HPP #include "prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionRing.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/DeviceAccess.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Packets/PacketHistory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp" @@ -17,6 +18,7 @@ #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "prx/libc/include/Shutdown.hpp" #include #include @@ -41,8 +43,14 @@ #include #include +namespace AgcDriver { +class PerformanceTimer; +} + namespace AgcDriver::DriverDetail { +struct DrawTail; + class Driver { public: static Driver& Get(); @@ -51,12 +59,20 @@ public: void Shutdown(); void WaitIdle(); std::uint64_t DispatchesAdoptedAhead() const; + std::uint64_t DispatchesRejectedAhead() const; + void SetAheadAdoptHook(void (*hook)()); void CheckFailure(); void ReportFailure(std::exception_ptr error); void Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip = false); void SubmitFlip(const FlipInfo& info); void SuspendPoint(); std::uint32_t ReserveEopFlip(const FlipInfo& info); + std::uint32_t ReserveEopFlipAt(const FlipInfo& info, std::uint32_t token); + std::vector PendingEopFlipTokens(); + std::pair SerialProgress(); + std::uint64_t FlipSubmissions() const; + std::vector AwaitedWaits(); + bool WaitIdleFor(std::chrono::milliseconds timeout); void RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output); void UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output); void AttachWindow(const PresentationWindow& window); @@ -67,6 +83,17 @@ public: void ResolveGraphicsStagesAbi(std::span stages, std::span context, std::span primitive); void ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType); void SettleShaderPreparations(std::span stages); + [[nodiscard]] std::size_t PreparedEntries(const Shader* shader); + struct DeferredAbiUseCounts { + std::uint64_t firstUses; + std::uint64_t found; + std::uint64_t own; + std::uint64_t entries; + std::uint64_t unmatchedEntries; + }; + [[nodiscard]] static DeferredAbiUseCounts DeferredAbiUses(); + [[nodiscard]] std::vector> PreparedEntryHashes(const Shader* shader); + [[nodiscard]] std::array PrebuiltLibraries(); static void FoldDrawOffsets(const ShaderRecompiler::RecompileResult& result, const DrawProgram& program, Pm4::DrawParameters& parameters); static std::optional ClassifyIndirectDraw(const ShaderRecompiler::RecompileResult& result, const Graphics::State& graphics, const DrawProgram& frontProgram, const std::shared_ptr& localDevice, Pm4::DrawParameters& drawParameters, bool traceIndirect); static void tagVariantRuns(DispatchVariant& variant, std::span regions, const ShaderRecompiler::SrtUserDataUse& use, bool eligible, std::uint32_t hiddenWords, std::span> supplied = {}); @@ -86,10 +113,12 @@ private: friend class SampledReadScope; friend struct PendingView; friend struct PacketTimer; + friend struct DriverTestAccess; void stop(); Driver(); void rethrowFailure() const; void checkStopping() const; + std::uint32_t reserveEopFlip(const FlipInfo& info, std::uint32_t token); static bool& onWorkerThread(); static void copyCommands(Submission& submission, const std::uint32_t* guest, std::size_t words); static bool copySegment(Submission& submission, const std::uint32_t* guest, std::size_t words, std::size_t& budget); @@ -107,9 +136,9 @@ private: static void forgetUnfinishedWrites(QueueWorker& worker, std::span labelWrites); static bool& completionDeferred(); void deferCompletion(const Submission& submission); - void submitOpenWork(); + void submitOpenWork(const std::shared_ptr& progress = {}); void wakeQueueZeroForCompletions(); - void completeSubmission(std::uint64_t serial, std::uint64_t received, std::span labelWrites); + 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 orderReleased(std::uint32_t queue, std::uint64_t received) const; @@ -140,7 +169,7 @@ private: static bool verifyDrawRecipe(); static std::size_t drawCacheEntries(); void accountDrawVariant(const DispatchVariant& variant, bool added); - void insertDrawEntry(std::uint64_t key, std::vector>& fresh, std::shared_ptr decode, std::uint64_t shape = 0); + void insertDrawEntry(std::uint64_t key, std::vector>& fresh, std::shared_ptr decode, std::uint64_t shape = 0, bool replaceShape = false); bool admitDrawKey(std::uint64_t key, std::uint64_t shape); void eraseDrawEntry(std::uint64_t key, const std::shared_ptr& expected); std::shared_ptr findDrawRecipe(std::uint64_t key, const std::vector>& stages); @@ -164,13 +193,14 @@ private: std::shared_ptr decodeDraw(const QueueState& queue, const Submission& submission, const Graphics::Context& context); void resolveDrawDecode(const QueueState& queue, const Submission& submission, const Graphics::Context& context, std::shared_ptr& decode, bool registerKey, std::uint64_t drawKey, bool profile); void lookupDraw(const Submission& submission, const std::shared_ptr& localDevice, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector& programs, const std::vector& roles, const std::vector>& vertexInfos, bool useDrawEntries, bool registerKey, bool profile, std::uint64_t& drawKey, std::shared_ptr& entry, std::vector>& matched, std::vector>& matchedRegions, bool& drawHit, bool& verifyHit, DrawPhaseTiming& phaseTiming, std::array& phaseMs, const std::shared_ptr& dataEntry, DrawDataCandidates& dataCandidates, std::vector& stageData); - std::shared_ptr materializeDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, const std::vector& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector>& vertexInfos, std::vector& memory, const std::vector& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr& localDevice, ShaderMemory& shaderMemory, std::vector& stageCaptures, std::vector& recompiled, bool drawHit, const std::vector>& matched, const std::vector>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array& phaseMs, std::string& rejected, const std::optional& meshBarycentrics); + std::shared_ptr materializeDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, std::size_t packetOffset, std::uint64_t traceToken, const std::vector& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector>& vertexInfos, std::vector& memory, const std::vector& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr& localDevice, ShaderMemory& shaderMemory, std::vector& stageCaptures, std::vector& recompiled, bool drawHit, const std::vector>& matched, const std::vector>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array& phaseMs, std::string& rejected, const std::optional& meshBarycentrics); static void recordStageDataPositions(DispatchVariant& variant, const ShaderRecompiler::ResourceCapture& capture, const std::vector& decodeReads); - void cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawParameters& drawParameters, const std::optional& indirectCpu, const std::vector& programs, const std::vector& stageCaptures, const std::vector>& vertexInfos, const std::vector>& decodeReads, bool verifyHit, const std::vector>& matched, std::vector>& fresh, std::uint64_t drawKey, bool registerKey, const std::shared_ptr& decode, DrawPhaseTiming& phaseTiming, const std::vector& reused, const std::shared_ptr& entry, std::uint64_t shapeKey); + void cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawParameters& drawParameters, const std::optional& indirectCpu, const std::vector& programs, const std::vector& stageCaptures, const std::vector>& vertexInfos, const std::vector>& decodeReads, bool verifyHit, const std::vector>& matched, const std::vector& stageData, std::vector>& fresh, std::uint64_t drawKey, bool registerKey, const std::shared_ptr& decode, DrawPhaseTiming& phaseTiming, const std::vector& reused, const std::shared_ptr& entry, std::uint64_t shapeKey, bool shapeNeedsEntry = false); static bool drawPrecheck(); - std::optional precheckDraw(const QueueState& queue, const Submission& submission, std::span packet, const Pm4::DrawParameters& drawParameters, std::string& rejected, bool& traceIndirect); + std::optional precheckDraw(const QueueState& queue, const Submission& submission, std::size_t packetOffset, std::span packet, const Pm4::DrawParameters& drawParameters, std::string& rejected, bool& traceIndirect); static std::uint32_t drawUserWord(const DrawProgram& program, std::int32_t sgpr); - DrawVerdict draw(QueueState& queue, std::span packet, const Submission& submission, std::string& rejected); + DrawVerdict draw(QueueState& queue, std::span packet, const Submission& submission, std::size_t packetOffset, std::string& rejected); + DrawVerdict finishDraw(DrawTail& tail, PerformanceTimer& timing); void addDriverPhases(DispatchClass which, const std::array& ms, bool hit, bool validated); static PendingDispatchPhases& pendingDispatchPhases(); static std::chrono::steady_clock::time_point& packetStartedAt(); @@ -242,7 +272,8 @@ 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); + 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 = {}); + 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); void commitDmaStore(std::uint32_t queue, std::uint64_t address, std::span bytes); @@ -288,7 +319,7 @@ private: std::chrono::steady_clock::time_point resolvedAt{}; }; struct GroupCaptureStats { - std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0; + std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, memoryMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0; double resolveMs = 0, resolveMaxMs = 0, ageMs = 0, ageMaxMs = 0; std::chrono::steady_clock::time_point reported = std::chrono::steady_clock::now(); }; @@ -338,7 +369,7 @@ private: void run(std::uint32_t id) noexcept; std::mutex mutex; - std::mutex shutdownMutex; + AgcDriver::Mutex shutdownMutex; std::condition_variable changed; std::map workers; @@ -346,6 +377,14 @@ private: std::shared_ptr pendingFrameTiming; std::atomic flipsCounted{0}; + struct AwaitedSlot { + std::atomic address{0}; + std::atomic reference{0}; + std::atomic mask{0}; + std::atomic shape{0}; + }; + std::array awaitedSlots{}; + void noteWaitFields(std::uint32_t queue, std::span packet); std::atomic flipSerial{0}; std::atomic flipBatchesUnsignaled{0}; @@ -361,7 +400,7 @@ private: std::unordered_map> dispatchCache; std::list dispatchOrder; - std::mutex dispatchCacheMutex; + AgcDriver::Mutex dispatchCacheMutex; std::uint64_t dispatchCacheHits = 0; std::uint64_t dispatchCacheEvictions = 0; @@ -379,22 +418,22 @@ private: std::unordered_set traceShapes, traceKeysSeen, traceKeysEvicted; std::list drawOrder; std::unordered_map> drawRelocations; - std::unordered_map> drawAliases; + std::unordered_map drawAliases; DrawRelocationPending drawRelocationPending; std::unordered_map drawRelocationTrials; DrawRelocationCounters drawRelocationCounters; - std::mutex drawCacheMutex; + AgcDriver::Mutex drawCacheMutex; std::uint64_t drawCacheHits = 0, drawCacheEvictions = 0, drawCacheVariants = 0, drawCacheVariantBytes = 0; std::optional drawKeySightings; std::optional drawShapeSightings; DrawEntryCounters drawEntryCounters; - std::mutex driverPhasesMutex; + AgcDriver::Mutex driverPhasesMutex; std::array driverPhaseTotals{}; std::chrono::steady_clock::time_point driverPhasesReport = std::chrono::steady_clock::now(); - std::mutex drawPhasesMutex; + AgcDriver::Mutex drawPhasesMutex; DrawPhaseTotals drawPhaseTotals; std::map queues; @@ -407,7 +446,7 @@ private: std::uint32_t nextEopToken = EopFlipTokenBase; DevicePointer device; - std::mutex labelStoresMutex; + AgcDriver::Mutex labelStoresMutex; std::unordered_map> labelStores; std::vector> replacedDevices; @@ -437,8 +476,10 @@ private: std::atomic evidenceReads{0}; std::atomic evidenceValidations{0}; std::atomic dispatchesAdoptedAhead{0}; + std::atomic dispatchesRejectedAhead{0}; + std::atomic aheadAdoptHook{nullptr}; - std::mutex writtenBuffersMutex; + AgcDriver::Mutex writtenBuffersMutex; std::deque writtenBuffers; std::uint64_t writtenBufferSerial = 0; std::unordered_map dwordEvidence; @@ -448,7 +489,7 @@ private: std::atomic observationsNoWait{0}; std::atomic observationsNotReached{0}; - std::mutex validateMutex; + AgcDriver::Mutex validateMutex; ValidateCounters validateCounters; }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp index 0c72dfd1f1..fa3e284410 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp @@ -28,6 +28,8 @@ struct StreamCopy { bool MatchesStreamCopyKernel(std::span code, const std::vector& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute); std::optional StreamCopyRange(const std::vector& userData, std::uint32_t groups); +bool MatchesIndexedCopyKernel(std::span code, const std::vector& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute); +std::optional IndexedCopyRange(const std::vector& userData, std::uint32_t groups, std::uint32_t count, std::uint32_t sourceOffset, std::uint32_t destinationOffset); } 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 e71457e0f3..e45f794362 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionProgress.hpp" #include #include #include @@ -22,6 +23,7 @@ struct Submission { std::uint64_t serial; std::uint32_t queue; std::vector commands; + std::shared_ptr progress; std::shared_ptr shaders; std::map> flips; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionProgress.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionProgress.hpp new file mode 100644 index 0000000000..5194af7ce4 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionProgress.hpp @@ -0,0 +1,44 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_QUEUES_SUBMISSIONPROGRESS_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_QUEUES_SUBMISSIONPROGRESS_HPP + +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { class VulkanDevice; } + +namespace AgcDriver::DriverDetail { + +class SubmissionProgress { +public: + using Clock = std::chrono::steady_clock; + using Ready = std::pair, std::uint64_t>; + + void Publish(std::shared_ptr device, std::uint64_t epoch) { + { + std::lock_guard lock(mutex); + result = {std::move(device), epoch}; + ready = true; + } + changed.notify_one(); + } + + Ready WaitUntil(Clock::time_point deadline) { + std::unique_lock lock(mutex); + if (!changed.wait_until(lock, deadline, [&] { return ready; })) return {}; + return result; + } + +private: + std::mutex mutex; + std::condition_variable changed; + Ready result; + bool ready = false; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp index 203940d7c3..b1907257c4 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -127,6 +127,8 @@ struct PreparedShaderState { struct Entry { std::size_t codeOffset; std::shared_ptr handle; + bool fromDeferredAbi = false; + bool matched = false; }; struct Rectangle { std::uint64_t vertexId; @@ -153,6 +155,14 @@ struct PreparedShaderState { bool deferred = false; }; struct PreparedShaders : PreparedShaderState { + struct DeferredAbi { + std::vector key; + std::vector> context; + std::vector> primitive; + std::weak_ptr device; + }; + std::vector deferredAbis; + bool deferredPreparing = false; std::mutex mutex; std::condition_variable settled; std::exception_ptr failure; @@ -165,6 +175,8 @@ struct RegisteredShaderState { Registers shader; Registers context; Registers userConfig; + + bool operator==(const RegisteredShaderState&) const = default; }; struct ShaderSnapshot { @@ -177,6 +189,7 @@ struct ShaderSnapshot { std::shared_ptr registeredState; std::shared_ptr> workgroupUse = std::make_shared>(0u); std::shared_ptr> positionVectorUse = std::make_shared>(0u); + std::shared_ptr> memoryWrites = std::make_shared>(0u); std::shared_ptr> rawGraphicsNoted = std::make_shared>(false); std::optional userDataTailRegister; }; @@ -189,6 +202,7 @@ void NoteRawGraphicsDraw(const ShaderSnapshot& snapshot); bool RawGraphicsDrawn(); bool RawGraphicsDrawnIn(std::uint64_t begin, std::uint64_t end); bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset); +bool ProgramWritesMemory(const ShaderSnapshot& snapshot, std::size_t codeOffset); // The PA_CL_VS_OUT_CNTL position export bits a vertex program is compiled with: none for a program // without pos1..pos3 exports, so its artifact does not depend on them. std::uint32_t PositionExportLayout(const ShaderSnapshot* snapshot, std::size_t codeOffset, std::span code, std::uint32_t paClVsOutCntl, const ShaderSnapshot* continuation = nullptr); @@ -198,6 +212,9 @@ std::shared_ptr PrepareShaderWithDiagnosti std::uint64_t NullPixelProgramAddress(); ShaderSnapshot PrepareNullPixelProgram(const VulkanDevice& device); +std::uint64_t DepthCopyPixelProgramAddress(bool multisampled); +std::uint32_t DepthCopyPixelUserWords(bool multisampled); +ShaderSnapshot PrepareDepthCopyPixelProgram(bool multisampled); std::optional RegisteredFloatMode(const ShaderSnapshot& snapshot); std::optional DispatchFloatMode(const ShaderSnapshot& snapshot, const Registers& live); std::shared_ptr RegisteredHeader(const ShaderRegistry& registry, const Shader* shader); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/FrameCapture.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/FrameCapture.hpp new file mode 100644 index 0000000000..b3721a17d5 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/FrameCapture.hpp @@ -0,0 +1,138 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_FRAMECAPTURE_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_FRAMECAPTURE_HPP + +#include "SceShaders.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +enum class FrameCallKind : std::uint32_t { + RegisterShader = 1, + ResolveShaderAbi, + ResolveGraphicsAbi, + ResolveGraphicsStagesAbi, + RegisterVideoOutput, + SetTessellationFactorRing, + Submit, + Flip, + ReserveEopFlip, + Suspend, + Memory, + FrameStart, + End, + DisplayBuffer, + UnregisterVideoOutput, +}; + +struct FrameCall { + FrameCallKind kind = FrameCallKind::End; + std::uint64_t ordinal = 0; + std::uint64_t timeNs = 0; + std::uint64_t thread = 0; + std::uint64_t accepted = 0; + std::uint64_t completed = 0; + std::uint32_t queue = 0; + std::uint32_t flags = 0; + bool cpuFlip = false; + std::uint64_t address = 0; + std::uint64_t size = 0; + std::vector values; + std::vector context; + std::vector primitive; +}; + +enum class FrameMemoryKind : std::uint32_t { Range = 1, Snapshot, Delta, Reference }; + +enum class FrameRangeKind : std::uint32_t { Watched = 1, Unwatched, Section, Alias, File, Host }; + +struct FrameRangeAccess { + static constexpr std::uint32_t Readable = 1; + static constexpr std::uint32_t Writable = 2; + static constexpr std::uint32_t Gpu = 4; +}; + +struct FrameMemoryChunk { + FrameMemoryKind kind = FrameMemoryKind::Range; + FrameRangeKind rangeKind = FrameRangeKind::Watched; + std::uint32_t protection = 0; + std::uint32_t access = 0; + std::uint64_t group = 0; + std::uint64_t completed = 0; + std::uint64_t address = 0; + std::uint64_t bytes = 0; + std::uint64_t compressedBytes = 0; + std::uint64_t offset = 0; +}; + +class FrameCaptureWriter { +public: + static constexpr std::string_view CallsName = "calls.bin"; + static constexpr std::string_view MemoryName = "memory.bin"; + static constexpr std::string_view HeaderName = "capture.json"; + static constexpr std::string_view CallsMagic = "aps5-frame-calls 1\n"; + static constexpr std::string_view MemoryMagic = "aps5-frame-memory 1\n"; + static constexpr std::size_t BlockBytes = 64u * 1024u; + static constexpr std::size_t ChunkBytes = 4u * 1024u * 1024u; + + explicit FrameCaptureWriter(const std::filesystem::path& directory, int level = 1); + FrameCaptureWriter(const FrameCaptureWriter&) = delete; + FrameCaptureWriter& operator=(const FrameCaptureWriter&) = delete; + + void WriteCall(const FrameCall& call); + void WriteRange(std::uint64_t address, std::uint64_t bytes, FrameRangeKind kind, std::uint32_t protection, std::uint32_t access); + std::uint64_t WriteSnapshot(std::uint64_t address, std::span bytes); + void WriteDelta(std::uint64_t group, std::uint64_t completed, std::uint64_t address, std::span bytes); + void WriteReference(std::uint64_t index, std::uint64_t address, std::span pixels); + void Finish(std::string_view header); + + std::uint64_t RawBytes() const; + std::uint64_t StoredBytes() const; + const std::filesystem::path& Directory() const; + +private: + void writeChunk(const FrameMemoryChunk& chunk, std::span payload); + std::vector compress(std::span bytes) const; + + std::filesystem::path directory; + int level; + mutable std::mutex mutex; + std::ofstream calls; + std::ofstream memory; + std::uint64_t rawBytes = 0; + std::uint64_t storedBytes = 0; + bool finished = false; +}; + +class FrameCaptureReader { +public: + explicit FrameCaptureReader(const std::filesystem::path& directory); + + const std::vector& Calls() const; + const std::vector& Chunks() const; + const std::string& Header() const; + bool Truncated() const; + void Read(const FrameMemoryChunk& chunk, std::span destination) const; + std::vector Read(const FrameMemoryChunk& chunk) const; + +private: + std::filesystem::path directory; + std::vector calls; + std::vector chunks; + std::string header; + bool truncated = false; +}; + +std::string_view FrameCallName(FrameCallKind kind); +bool AllZero(std::span bytes); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp new file mode 100644 index 0000000000..e4ed5cf19b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp @@ -0,0 +1,116 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_FRAMERECORDER_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_FRAMERECORDER_HPP + +#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp" +#include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" +#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" +#include "SceShaders.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +class FrameRecorder { +public: + static constexpr std::uint64_t DefaultFrames = 10; + static constexpr std::size_t ReservationHistory = 256; + static constexpr std::size_t SnapshotPieceBytes = 64u * 1024u * 1024u; + static constexpr std::uint64_t MinimumFreeBytes = std::uint64_t{4} << 30; + static constexpr std::size_t HostPageBytes = 4096; + + static FrameRecorder* Active(); + + void RegisterShader(const Shader* shader); + void ResolveShaderAbi(const Shader* shader, std::span context, std::span primitive); + void ResolveGraphicsStagesAbi(std::span stages, std::span context, std::span primitive); + void ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType); + void RegisterVideoOutput(std::uint32_t handle); + void UnregisterVideoOutput(std::uint32_t handle); + void Submit(const Packet& packet, std::uint32_t queue, const std::function& accept); + void Flip(const FlipInfo& info, const std::function& accept); + void ReserveEopFlip(const FlipInfo& info, std::uint32_t token); + void Suspend(const std::function& accept); + void PresentBuffer(const DisplayBuffer& buffer); + void FrameBoundary(); + +private: + enum class State { Waiting, Recording, Done }; + + struct Range { + std::uint64_t address = 0; + std::uint64_t bytes = 0; + FrameRangeKind kind = FrameRangeKind::Watched; + std::uint32_t protection = 0; + std::uint32_t access = 0; + bool readable = false; + }; + + FrameRecorder(std::filesystem::path directory, std::filesystem::path trigger, std::uint64_t atBoundary, std::uint64_t frames); + struct Span { + std::uint64_t address = 0; + std::uint64_t bytes = 0; + std::uint64_t since = 0; + }; + + FrameCall make(FrameCallKind kind); + void record(FrameCall call, bool prologueCall, std::span shaders = {}, const std::function& accept = {}); + void recordLocked(FrameCall call, bool prologueCall, std::span shaders); + void emitDelta(); + void emitShaderDelta(std::span shaders); + void writeDelta(std::span spans, std::span hostPages, std::span added = {}); + bool triggered(); + void start(); + void finish(); + void snapshot(const Range& range); + void writeReferenceFrames(); + bool abandonIfDiskLow(); + void stop(const char* reason); + std::string header() const; + std::vector committedRanges(); + std::vector newRanges(); + + std::filesystem::path directory; + std::filesystem::path trigger; + std::uint64_t atBoundary = 0; + std::uint64_t frames = DefaultFrames; + std::mutex mutex; + State state = State::Waiting; + std::unique_ptr writer; + std::vector prologue; + std::deque reservations; + std::vector ranges; + std::map shaderMarks; + std::map hostPageHashes; + std::mutex displayMutex; + std::map displayBuffers; + std::uint64_t ordinal = 0; + std::uint64_t boundaries = 0; + std::uint64_t startBoundary = 0; + std::uint64_t recordedFrames = 0; + std::uint64_t generation = 0; + std::uint64_t deltaGroups = 0; + std::uint64_t deltaBytes = 0; + std::uint64_t snapshotBytes = 0; + std::uint64_t recordedCalls = 0; + std::uint64_t startAttempts = 0; + std::uint64_t unreadableBytes = 0; + std::uint64_t allocationsGeneration = 0; + std::uint64_t lateRanges = 0; + std::vector warnings; + std::chrono::steady_clock::time_point origin; + std::chrono::steady_clock::time_point startedAt; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp index efa0e9d031..67170acc07 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_GUESTMEMORY_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_GUESTMEMORY_HPP +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -62,7 +63,7 @@ void WriteChanged(std::uint64_t address, std::span current, std bool WriteWatched(); void Unwatch(std::uint64_t address, std::size_t bytes); void Rewatch(std::span> ranges); -bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function& import, bool staysWatched = true); +bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function& import, bool staysWatched = true, bool importMayWriteGuest = true); void SetUnwatchHook(void (*hook)(std::uint64_t address, std::size_t bytes)); #ifdef _WIN32 using FreshCollectHook = bool (*)(std::uintptr_t address, std::size_t bytes, void (*written)(void*, std::uintptr_t, std::uintptr_t), void* context); @@ -80,10 +81,13 @@ struct UnchangedQuery { bool WrittenSince(std::uint64_t address, std::size_t bytes, std::uint64_t generation); bool UnchangedSinceAll(std::span queries); std::uint64_t CollectCommittedWrites(std::uint64_t address, std::size_t bytes, std::vector>& ranges); +std::uint64_t WriteGeneration(); +std::uint64_t CollectWrittenBlocks(std::uint64_t address, std::size_t bytes, std::uint64_t since, std::vector>& written); bool UnchangedCommittedSince(std::span> ranges, std::uint64_t generation); // Returns the generation the blocks were stamped with (0 when the arena is not write-watched): // UnchangedSince(range, it) holds until the next store over the range. std::uint64_t MarkWritten(std::uint64_t address, std::size_t bytes); +std::uint64_t MarkPossiblyWritten(std::uint64_t address, std::size_t bytes); std::uint64_t StoreOwnBytes(std::uint64_t address, std::size_t bytes, const std::function& store); std::uint64_t StoreOwnBytes(std::uint64_t address, std::span source); bool StoredOver(std::uint64_t address, std::size_t bytes, std::uint64_t generation); @@ -105,6 +109,9 @@ bool StoredAllSince_nid_no_patch(std::uint64_t address, std::size_t bytes, std:: // workers bump before every packet as before. CollectEpochBumps counts the bumps ([guestmem] line). void BumpCollectEpoch(); void AdoptLatestCollectEpoch(); +std::uint64_t ProofSerial(); +void BreakProofs(); +void MarkProofThread(); std::uint64_t CollectEpoch(); std::uint64_t CollectEpochBumps(); std::uint64_t ThreadCollectEpoch(); @@ -152,8 +159,10 @@ public: std::uint32_t DepthOnThisThread() const; private: + void enter(); + bool tryEnter(); void acquired(); - std::recursive_mutex mutex; + Mutex mutex; // The holder's thread token and recursion depth, written by the holder only; another thread // reads the owner just to see that it is not itself. std::atomic owner{nullptr}; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Mutex.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Mutex.hpp new file mode 100644 index 0000000000..182fb1e757 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Mutex.hpp @@ -0,0 +1,65 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_MUTEX_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_MUTEX_HPP + +#include +#include + +namespace AgcDriver { + +inline void WaitWhile(const std::atomic& word, std::uint32_t value) noexcept { + word.wait(value, std::memory_order_relaxed); +} + +inline void WakeOne(std::atomic& word) noexcept { + word.notify_one(); +} + +inline void WakeAll(std::atomic& word) noexcept { + word.notify_all(); +} + +class Mutex { +public: + constexpr Mutex() noexcept = default; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + void lock() noexcept { + if (!try_lock()) lockContended(); + } + + bool try_lock() noexcept { + std::uint32_t expected = Unlocked; + return state.compare_exchange_strong(expected, Locked, std::memory_order_acquire, std::memory_order_relaxed); + } + + void unlock() noexcept { + if (state.exchange(Unlocked, std::memory_order_release) == Contended) WakeOne(state); + } + +private: + static constexpr std::uint32_t Unlocked = 0; + static constexpr std::uint32_t Locked = 1; + static constexpr std::uint32_t Contended = 2; + + [[gnu::noinline]] void lockContended() noexcept { + while (state.exchange(Contended, std::memory_order_acquire) != Unlocked) WaitWhile(state, Contended); + } + + std::atomic state{Unlocked}; +}; + +inline const void* CurrentThreadToken() noexcept { +#if defined(_WIN32) && defined(__x86_64__) && defined(__GNUC__) + const void* block; + __asm__("movq %%gs:0x30, %0" : "=r"(block)); + return block; +#else + thread_local char token; + return &token; +#endif +} + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp index 28ccc5c2df..c674c5731e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/TracyZone.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -206,7 +207,7 @@ private: return std::chrono::duration(elapsed).count(); } - std::mutex mutex; + AgcDriver::Mutex mutex; bool localMetrics = false; std::map, Metric> metrics; std::map, Metric> asyncMetrics; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp index e683bc7695..49858aaa31 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp @@ -451,18 +451,30 @@ public: static void AppendString(std::string& output, std::string_view text) { output += '"'; - for (const char character : text) { + std::size_t index = 0; + while (index < text.size()) { + const char character = text[index]; const auto byte = static_cast(character); if (character == '"' || character == '\\') { output += '\\'; output += character; - } else if (byte < 0x20 || byte >= 0x7f) { + } else if (byte >= 0x80) { + if (const auto length = utf8SequenceLength(text, index); length != 0) { + output.append(text.substr(index, length)); + index += length; + continue; + } + char escaped[8]; + std::snprintf(escaped, sizeof escaped, "\\u%04x", static_cast(byte)); + output += escaped; + } else if (byte < 0x20 || byte == 0x7f) { char escaped[8]; std::snprintf(escaped, sizeof escaped, "\\u%04x", static_cast(byte)); output += escaped; } else { output += character; } + ++index; } output += '"'; } @@ -697,6 +709,34 @@ public: } private: + static std::size_t utf8SequenceLength(std::string_view text, std::size_t index) { + const auto lead = static_cast(text[index]); + std::size_t length = 0; + unsigned char low = 0x80; + unsigned char high = 0xbf; + if (lead >= 0xc2 && lead <= 0xdf) { + length = 2; + } else if (lead >= 0xe0 && lead <= 0xef) { + length = 3; + if (lead == 0xe0) low = 0xa0; + if (lead == 0xed) high = 0x9f; + } else if (lead >= 0xf0 && lead <= 0xf4) { + length = 4; + if (lead == 0xf0) low = 0x90; + if (lead == 0xf4) high = 0x8f; + } else { + return 0; + } + if (text.size() - index < length) return 0; + for (std::size_t offset = 1; offset < length; ++offset) { + const auto next = static_cast(text[index + offset]); + if (next < low || next > high) return 0; + low = 0x80; + high = 0xbf; + } + return length; + } + static void AppendMachine(std::string& output, const TraceSummary& summary) { if (summary.host) { output += ",\"cpu\":"; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index 909b403e26..0b9bc1f9f8 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -46,6 +46,7 @@ struct DrawParameters { std::uint32_t drawIndexLocation; bool drawIndexEnabled; std::uint32_t indxOffset; + bool indexLimitKnown = true; enum class Rule : std::uint8_t { InPlace, Constant }; Rule vertexRule = Rule::Constant; Rule instanceRule = Rule::Constant; @@ -154,6 +155,7 @@ inline bool CopyDataImmediate(std::uint32_t source) { return source >= 10 && sou inline bool CopyDataToRegister(std::span packet) { return ((packet[1] >> 8u) & 0xfu) == 0; } inline std::size_t CopyDataBytes(std::span packet) { return (packet[1] & 0x10000u) != 0 ? 8 : 4; } inline constexpr std::uint32_t RewindValid = 0x80000000u; +inline constexpr std::uint32_t RewindOffloadEnable = 0x01000000u; struct WaitFields { std::uint32_t function; std::uint64_t address; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/PresentMode.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/PresentMode.hpp index cb448bbc44..17510d198b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/PresentMode.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/PresentMode.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -22,6 +24,11 @@ inline VkPresentModeKHR SelectPresentMode(const char* requested, std::span SelectPresentQueue(std::uint32_t familyQueues, std::uint32_t queuesTaken) { + if (queuesTaken >= familyQueues) return std::nullopt; + return queuesTaken; +} + } #endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp index b0e88120aa..6507c50a22 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp @@ -46,6 +46,8 @@ private: std::uint32_t sourceHeight = 0; VkImage sourceImage = VK_NULL_HANDLE; VkDeviceMemory sourceMemory = VK_NULL_HANDLE; + VkDeviceSize sourceBytes = 0; + std::uint32_t sourceMemoryType = 0; VkImage blackImage = VK_NULL_HANDLE; VkDeviceMemory blackMemory = VK_NULL_HANDLE; }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index 3954c462a3..7c10d006f5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -221,7 +221,7 @@ inline Registers BuildInitialContextRegisters() { Registers result{ {0x200, 0}, {0x201, 0}, {0x202, 0xcc0010}, {0x203, 0}, {0x204, 0}, {0x205, 0}, {0x206, 1087}, {0x207, 0}, - {0x0, 0}, {0x2, 0}, {0x3, 0}, {0x4, 0}, {0x8, 0}, {0x9, 0x3f800000}, {0xa, 0}, {0xb, 0}, + {0x0, 0}, {0x2, 0}, {0x3, 0}, {0x4, 0}, {0x8, 0}, {0x9, 0x3f800000}, {0xa, 0}, {0xb, 0}, {0x10, 0}, {0x11, 0}, {0x80, 0}, {0x83, 0xffff}, {0x8c, 0xaa99aaaa}, {0x8d, 0}, {0x8e, 0}, {0x8f, 0}, {0xc, 0}, {0xd, 0x40004000}, {0x81, 0x80000000}, {0x82, 0x40004000}, {0x90, 0x80000000}, {0x91, 0x40004000}, @@ -272,7 +272,7 @@ struct QueueState { std::uint64_t indexBase = 0; std::uint64_t drawIndirectBase = 0; std::uint64_t dispatchIndirectBase = 0; - std::uint32_t indexBufferSize = 0; + std::optional indexBufferSize; std::uint32_t indexType = 0; std::uint32_t instanceCount = 1; std::vector markers; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/RobustBufferAccess.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/RobustBufferAccess.hpp deleted file mode 100644 index a180b23104..0000000000 --- a/core/libs/prx/libSceAgcDriver/Execution/include/RobustBufferAccess.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_ROBUSTBUFFERACCESS_HPP -#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_ROBUSTBUFFERACCESS_HPP - -#include - -namespace AgcDriver { - -inline bool RobustBufferAccess2Usable(VkDriverId driver) { - return driver != VK_DRIVER_ID_AMD_PROPRIETARY; -} - -} - -#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp index 98fd3cbadd..70d81c98a5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp @@ -16,6 +16,7 @@ public: ShaderPreparationTransaction& operator=(const ShaderPreparationTransaction&) = delete; PreparedShaderState& Edit(const ShaderSnapshot& snapshot); const PreparedShaderState& Read(const ShaderSnapshot& snapshot) const; + bool Tracks(const ShaderSnapshot& snapshot) const; void Hold(); void Commit(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp index 30f42ba2ab..8675650a20 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp @@ -9,6 +9,10 @@ inline bool SubgroupControlBarrierUsable(VkDriverId driver) { return driver != VK_DRIVER_ID_AMD_PROPRIETARY; } +inline bool WorkgroupScopeFenceUsable(VkDriverId driver) { + return driver != VK_DRIVER_ID_AMD_PROPRIETARY; +} + } #endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/SwapchainImages.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/SwapchainImages.hpp new file mode 100644 index 0000000000..b3411ca19b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/SwapchainImages.hpp @@ -0,0 +1,17 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SWAPCHAINIMAGES_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SWAPCHAINIMAGES_HPP + +#include +#include +#include + +namespace AgcDriver { + +inline std::uint32_t SwapchainImageCount(const VkSurfaceCapabilitiesKHR& surface) { + const std::uint32_t count = std::max(surface.minImageCount, 3u); + return surface.maxImageCount != 0 ? std::min(count, surface.maxImageCount) : count; +} + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/TessellationPipelineCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/TessellationPipelineCache.hpp new file mode 100644 index 0000000000..7605cd2c68 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/TessellationPipelineCache.hpp @@ -0,0 +1,14 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_TESSELLATIONPIPELINECACHE_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_TESSELLATIONPIPELINECACHE_HPP + +#include + +namespace AgcDriver { + +inline bool TessellationPipelineCacheUsable(VkDriverId driver) { + return driver != VK_DRIVER_ID_MOLTENVK; +} + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp index 2c7443e9a5..c642371e8e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp @@ -55,6 +55,7 @@ public: extern "C" std::uint32_t AgcDriverReserveEopFlip_nid_postfix(const AgcDriver::FlipInfo& info); extern "C" void AgcDriverSubmitFlip_nid_postfix(const AgcDriver::FlipInfo& info); +extern "C" std::uint32_t AgcDriverReserveEopFlipAt_nid_postfix(const AgcDriver::FlipInfo& info, std::uint32_t token); extern "C" void AgcDriverRegisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr& output); extern "C" void AgcDriverUnregisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr& output); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index b5b147ac86..dbdcfb88be 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include namespace AgcDriver { @@ -31,9 +33,24 @@ struct ComputePipelineObjects; struct RecipeHit; // The record tail shared by an ordinary dispatch and a recipe hit (VulkanDevice::recordDispatch). struct RecordedDispatch; +struct DispatchCompletion; // The module, layout and pipeline a compute dispatch binds (VulkanDevice::createComputePipeline). struct ComputePipelineObjects; +struct DeviceQueuePlan { + std::uint32_t mainCount = 1; + bool settle = false; + std::uint32_t settleFamily = 0; + std::uint32_t settleIndex = 0; + bool sparseBinding = false; + std::uint32_t sparseFamily = VK_QUEUE_FAMILY_IGNORED; + std::uint32_t sparseIndex = 0; + bool present = false; + std::uint32_t presentIndex = 0; +}; + +DeviceQueuePlan PlanDeviceQueues(std::span families, std::uint32_t family, bool settles, bool sparseBinding, bool presents); + class VulkanDevice { public: explicit VulkanDevice(const PresentationWindow* window = nullptr); @@ -43,6 +60,8 @@ public: // Builds the pipeline of a journaled compute result ahead of its first dispatch (the journal // replay's hook, on its threads): false when the device already holds a pipeline for the module. bool ReplayComputePipeline(const ShaderRecompiler::RecompileResult& shader, bool pushConstants); + bool HasComputePipeline(const ShaderRecompiler::RecompileResult& shader) const; + void PrebuildComputePipeline(const ShaderRecompiler::RecompileResult& shader, std::uint64_t programAddress = 0); // Starts the journal replay if the device has not (a headless device starts it at its first // draw or dispatch) and joins it (tests; the teardown joins it anyway). void WaitForPipelineJournalReplay(); @@ -54,6 +73,7 @@ public: VulkanDevice& operator=(const VulkanDevice&) = delete; std::string DeviceName() const; std::string DriverInfo() const; + bool SupportsImageFormat(VkFormat format, VkImageUsageFlags usage) const; bool CalibrateClock(double& gpuNs, std::chrono::steady_clock::time_point& cpu) const; ShaderRecompiler::SpirvTarget Target() const; Graphics::SampleOrder HostSampleOrder(const Graphics::State& graphics) const; @@ -89,16 +109,18 @@ public: bool CanWaitUnlocked() const; std::uint64_t SubmitAndEpoch(); void WaitRecorded(std::uint64_t serial); + std::uint64_t SubmittedEpoch() const; + bool WaitRecordedFor(std::uint64_t serial, std::uint64_t timeoutNanos); void ReapRecorded(std::uint64_t serial); // Completes the batches that already finished (non-blocking); under the mutex. void ReapRecorded(); // Records a store of `bytes` at a guest address behind the recorded work, so the value appears // once that work completed; the batch is submitted by the queue worker's flush rules (or at once // with APS5_LABEL_SUBMIT_NOW=1). `stamp` is the record-order stamp and `queue` the recording - // queue for the recorder's pending-label table. Returns 0 when recorded on the GPU, 5 when kept - // as a completion action behind pending write-backs, else why the CPU must write it: - // 1 nothing recorded (the write is already ordered), 2 write-backs pending and 3 memory not - // imported, 4 unsuitable size or alignment. + // queue for the recorder's pending-label table. Returns 0 when recorded on the GPU, 5 when stored + // by a completion behind pending CPU write-backs or completion stores, else why the CPU must + // write it: 1 nothing recorded (the write is already ordered), 2 write-backs pending (only with + // APS5_DRAIN_COMPLETION_LABELS=1) and 3 memory not imported, 4 unsuitable size or alignment. // `reapFirst` retires finished batches before the checks; a caller recording a group of labels // under one lock passes it for the first label only. int WriteLabelOnGpu(std::uint64_t address, std::span bytes, std::uint64_t stamp, std::uint32_t queue, bool reapFirst = true); @@ -208,7 +230,7 @@ public: bool ProvokingVertexLast() const; bool GraphicsPipelineLibraries() const; // A presentation is a few steps so the presenter holds GuestMemory::GpuMutex only while it - // touches the queue. Presentations are slots (FlipInFlight() + 1, each with its own command + // touches the main queue. Presentations are slots (FlipInFlight() + 1, each with its own command // buffer, fence, kept resident image and dump buffer): RetirePresents(keep) (no mutex) retires // the slots whose fence signaled, oldest first, and waits for the oldest ones until at most // `keep` are in flight (the only CPU wait of the flip path; it returns the time spent waiting); @@ -217,7 +239,9 @@ public: // PresentClear/PresentDisplayBuffer record and submit the frame into the next slot (under the // mutex; the blit follows the frame's batches in queue order, so the displayed image is always // complete) and return whether one was submitted; QueuePresent hands the image to the swapchain - // (under the mutex). FinishPresent = RetirePresents(0): the render fence wait of the + // (on the device's presentation queue under that queue's mutex, with the GPU mutex released, + // when the presentation family had a free queue; otherwise on the main queue under the GPU + // mutex). FinishPresent = RetirePresents(0): the render fence wait of the // synchronous paths (APS5_FLIP_INFLIGHT=0, APS5_SYNC_FLIP, PresentPixels). AcquireImage returns // false when the swapchain is out of date (the frame is dropped; the next Resize recreates it); // a present without a prior AcquireImage acquires itself, one whose slot is in flight waits for @@ -240,6 +264,7 @@ public: bool PresentDisplayBuffer(const DisplayBuffer& buffer); double FinishPresent(); void QueuePresent(); + bool PresentsOnOwnQueue() const; // At the flip packet (under the mutex): the recorder's submissions so far and, under // APS5_PROFILE_DRAW, how many in flight have not signaled (the frame record's batches_at_flip). void FlipBatches(std::uint64_t& submissions, std::uint64_t& unsignaled) const; @@ -250,8 +275,8 @@ public: // blit-submit times after the flip packet; noted in-place reads checked // (APS5_FLIP_READ_CHECK=1) and found overwritten before the blit's fence. struct PresentStatistics { - std::uint64_t presents, gpuUnread, batchesAheadOfBlit, batchesAfterFlip, readsChecked, readsOverwritten, acquireDrops; - double gpuBusyMs, gpuGapMs, lastSubmitAfterFlipMs, blitSubmitAfterFlipMs; + std::uint64_t presents, gpuUnread, batchesAheadOfBlit, batchesAfterFlip, readsChecked, readsOverwritten, acquireDrops, keyWriterWaits; + double gpuBusyMs, gpuGapMs, lastSubmitAfterFlipMs, blitSubmitAfterFlipMs, keyWriterWaitMs; }; static PresentStatistics PresentCounts(); struct PresyncStatistics { @@ -272,7 +297,7 @@ public: // `recipe`, when given, receives the Recipe a successful call built for its dispatch-cache // variant (design_cpu_final M4): only when the resource cache served or took the object // (reusable, cacheable) and recipes are on (APS5_NO_DISPATCH_RECIPE=1 builds none); else null. - void Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span snapshots = {}, std::uint64_t programAddress = 0, std::shared_ptr prepared = nullptr, std::shared_ptr* recipe = nullptr); + void Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span snapshots = {}, std::uint64_t programAddress = 0, std::shared_ptr prepared = nullptr, std::shared_ptr* recipe = nullptr, std::optional> htileClear = std::nullopt); struct IndirectOutcome { int cpuReason; double argumentReadMs; @@ -316,17 +341,17 @@ public: static void NoteRecipe(RecipeEvent event, RecipeKind kind); enum class DrawRecipePrecheck : std::size_t { NoRecipe, Device }; static void NoteDrawRecipeMiss(DrawRecipePrecheck miss); + 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); + 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); bool DrawInputsInPlace() const; std::optional KnownDrawRejection(const Graphics::State& graphics, std::span shaders) const; void ColorMetadataPass(const Graphics::ColorMetadataPass& pass); - void DepthCopyPass(const Graphics::DepthCopyPass& pass); // A draw-cache hit recorded from its recipe (Graphics::DrawWithRecipe), under the mutex after // the packet's labels; Rebuild when the recipe's device is not this one or DrawWithRecipe // missed (nothing recorded: the caller runs Draw with the hit's stages and re-attaches). - RecipeOutcome DrawFromRecipe(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, const std::shared_ptr& recipe); + RecipeOutcome DrawFromRecipe(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, const std::shared_ptr& recipe, const DrawTraceContext* trace = nullptr); // The indirect draw features the device enabled, for the driver's decision whether an indirect // draw's records can be read by the GPU (Graphics::Draw) or must be resolved on the CPU. struct IndirectDrawSupport { @@ -356,7 +381,8 @@ private: ShaderRecompiler::SpirvTarget buildTarget() const; Graphics::Context buildContext() const; // Body of Dispatch and DispatchIndirect: `arguments` 0 dispatches x, y, z groups. - IndirectOutcome dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span snapshots, std::uint64_t programAddress, std::shared_ptr prepared, std::shared_ptr* recipe, const std::array& threadGroup = {}); + IndirectOutcome dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span snapshots, std::uint64_t programAddress, std::shared_ptr prepared, std::shared_ptr* recipe, const std::array& threadGroup = {}, std::optional> htileClear = std::nullopt); + IndirectOutcome recordCompletedDispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, std::shared_ptr* recipeOut, const std::array& threadGroup, std::optional> htileClear, DispatchCompletion& completion); // The stage-A pre-sync over `surfaces` (see PrepareDispatch): the serial waited for, 0 none. std::uint64_t presync(std::span surfaces); std::shared_ptr computePipeline(const Graphics::Context& context, const ShaderRecompiler::RecompileResult& shader, VkDescriptorSetLayout setLayout, std::span layoutKey, VkShaderStageFlags pushStages, std::uint64_t programAddress); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/DeviceAccess.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/DeviceAccess.cpp index a7a14931a5..15b84d7b96 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/DeviceAccess.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/DeviceAccess.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/DeviceAccess.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" namespace AgcDriver::DriverDetail { @@ -21,27 +22,37 @@ DevicePointer::operator bool() const { return Load() != nullptr; } bool DevicePointer::operator==(std::nullptr_t) const { return Load() == nullptr; } void DeviceUseGate::lock_shared() { - std::unique_lock lock(mutex); - changed.wait(lock, [&] { return !replacing; }); - ++users; + auto value = state.load(std::memory_order_relaxed); + for (;;) { + if ((value & Replacing) != 0) { + WaitWhile(state, value); + value = state.load(std::memory_order_relaxed); + } else if (state.compare_exchange_weak(value, value + 1, std::memory_order_acquire, std::memory_order_relaxed)) { + return; + } + } } void DeviceUseGate::unlock_shared() { - std::lock_guard lock(mutex); - if (--users == 0) changed.notify_all(); + if (state.fetch_sub(1, std::memory_order_release) == (Replacing | 1)) WakeAll(state); } void DeviceUseGate::lock() { - std::unique_lock lock(mutex); - changed.wait(lock, [&] { return !replacing; }); - replacing = true; - changed.wait(lock, [&] { return users == 0; }); + auto value = state.load(std::memory_order_relaxed); + for (;;) { + if ((value & Replacing) != 0) { + WaitWhile(state, value); + value = state.load(std::memory_order_relaxed); + } else if (state.compare_exchange_weak(value, value | Replacing, std::memory_order_acquire, std::memory_order_relaxed)) { + break; + } + } + for (value = state.load(std::memory_order_acquire); value != Replacing; value = state.load(std::memory_order_acquire)) WaitWhile(state, value); } void DeviceUseGate::unlock() { - std::lock_guard lock(mutex); - replacing = false; - changed.notify_all(); + state.fetch_and(~Replacing, std::memory_order_release); + WakeAll(state); } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp index b94858e431..ca92adc259 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp @@ -1,11 +1,19 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" +#include +#include +#include +#include #include #include +#include +#include #include +#include #include #include #include +#include namespace AgcDriver::DriverDetail { @@ -14,9 +22,65 @@ double TraceMs() { return std::chrono::duration(std::chrono::steady_clock::now() - origin).count(); } -std::uint64_t SkippedShader() { - static const std::uint64_t skipped = [] { const char* text = std::getenv("APS5_SKIP_SHADER"); return text ? std::strtoull(text, nullptr, 16) : 0ull; }(); - return skipped; +namespace { + +constexpr std::size_t MaxSkippedShaders = 256; +std::array, MaxSkippedShaders> skippedShaders{}; +std::atomic skippedShaderCount{0}; + +std::optional programAddress(std::string_view word) { + if (word.starts_with("0x") || word.starts_with("0X")) word.remove_prefix(2); + std::uint64_t address = 0; + const auto [end, error] = std::from_chars(word.data(), word.data() + word.size(), address, 16); + if (word.empty() || error != std::errc{} || end != word.data() + word.size()) return std::nullopt; + return address; +} + +void ReloadSkippedShaders(const char* path) { + static std::mutex reload; + static std::filesystem::file_time_type seen{}; + const std::unique_lock lock(reload, std::try_to_lock); + if (!lock.owns_lock()) return; + std::error_code error; + const auto stamp = std::filesystem::last_write_time(path, error); + if (error || stamp == seen) return; + seen = stamp; + std::ifstream file(path); + std::size_t count = 0; + skippedShaderCount.store(0, std::memory_order_release); + for (std::string word; count < MaxSkippedShaders && file >> word;) { + if (word.starts_with('#')) { + std::getline(file, word); + continue; + } + const auto address = programAddress(word); + if (!address) { + std::fprintf(stderr, "[skip-shader] ignoring '%s' in %s: not a hexadecimal program address\n", word.c_str(), path); + continue; + } + if (*address != 0) skippedShaders[count++].store(*address, std::memory_order_relaxed); + } + skippedShaderCount.store(count, std::memory_order_release); + std::fprintf(stderr, "[skip-shader] %zu programs listed in %s\n", count, path); +} + +} + +bool SkippedShader(std::uint64_t address) { + static const std::uint64_t single = [] { const char* text = std::getenv("APS5_SKIP_SHADER"); return text ? std::strtoull(text, nullptr, 16) : 0ull; }(); + static const char* const file = std::getenv("APS5_SKIP_SHADER_FILE"); + if (address == 0) return false; + if (address == single) return true; + if (file == nullptr) return false; + static std::atomic nextCheck{0}; + const auto now = std::chrono::steady_clock::now().time_since_epoch(); + auto due = nextCheck.load(std::memory_order_relaxed); + if (now.count() >= due && nextCheck.compare_exchange_strong(due, std::chrono::duration_cast(now + std::chrono::milliseconds(500)).count(), std::memory_order_relaxed)) ReloadSkippedShaders(file); + const auto count = skippedShaderCount.load(std::memory_order_acquire); + for (std::size_t index = 0; index < count; ++index) { + if (skippedShaders[index].load(std::memory_order_relaxed) == address) return true; + } + return false; } bool SkipRefusedDraws() { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp index ccdc67b4aa..a08d54121c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp @@ -16,7 +16,10 @@ #include "Optimization/ResourceProgram.hpp" #include #include +#include +#include #include +#include namespace AgcDriver::DriverDetail { @@ -55,6 +58,19 @@ void ReportDispatchItems(bool profile) { std::fprintf(stderr, "[dispatch-item] %llu commit items (%llu recipes rebuilt on the committer); drained instead, cumulative:%s\n", static_cast(itemCommits.load(std::memory_order_relaxed)), static_cast(itemRebuilds.load(std::memory_order_relaxed)), drained.c_str()); } +bool CapturedCurrent(std::span captured, const ShaderSnapshot& code) { + for (const auto& region : captured) { + const auto address = region.guestAddress; + const auto bytes = region.bytes.size(); + if (bytes == 0 || (address >= code.codeAddress && address < code.codeAddress + code.code.size() * sizeof(std::uint32_t)) || (!code.header.empty() && address >= code.headerAddress && address < code.headerAddress + code.header.size())) continue; + if (Driver::QueryPendingSpan(address, bytes, {}) != ShaderMemory::PendingWrite::None || !GuestMemory::Accessible(reinterpret_cast(address), bytes)) return false; + GuestMemory::GuardRead(address, bytes); + GuestMemory::FlushGpuWrites(address, bytes); + if (std::memcmp(region.bytes.data(), reinterpret_cast(address), bytes) != 0) return false; + } + return true; +} + } bool Driver::pipelineDispatch() { @@ -64,7 +80,7 @@ bool Driver::pipelineDispatch() { void Driver::dispatch(QueueState& queue, std::span packet, const Submission& submission, std::uint64_t indirectArguments) { PerformanceTimer timing("Driver.Dispatch"); const auto address = (static_cast(readRegister(queue.shader, 0x20c)) << 8u) | (static_cast(readRegister(queue.shader, 0x20d) & 0xffu) << 40u); - if (address == SkippedShader() && address != 0) return; + if (SkippedShader(address)) return; auto registeredShader = RegisteredProgram(*submission.shaders, address, {0}); if (!registeredShader) registeredShader = ReadRawShader(address); const auto& snapshot = *registeredShader; @@ -117,12 +133,13 @@ void Driver::dispatch(QueueState& queue, std::span packet, if (indirectArguments == 0 && (packet[4] & 0x2u) != 0) { partialGroups = Pm4::PartialGroupThreads({packet[1], packet[2], packet[3]}, {readRegister(queue.shader, 0x207), readRegister(queue.shader, 0x208), readRegister(queue.shader, 0x209)}); } - const bool pipelineCandidate = !resolving && !fromAhead && DispatchItemsEnabled() && DrawPipeline::Active() && indirectArguments == 0 && deferredLabels().labels.empty() && !matchesCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute) && !MatchesStreamCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute); + const bool pipelineCandidate = !resolving && !fromAhead && DispatchItemsEnabled() && DrawPipeline::Active() && indirectArguments == 0 && deferredLabels().labels.empty() && !matchesCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute) && !MatchesStreamCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute) && !MatchesIndexedCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute); const bool partialLastGroup = partialGroups != std::array{}; + std::optional> htileClearCompletion; if (resolving) { if (indirectArguments == 0 && Graphics::MatchMultisampledSingleClear(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads)) return; if (!partialLastGroup && matchesFillKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) return; - if (!partialLastGroup && indirectArguments == 0 && (matchesCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchesStreamCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute))) return; + if (!partialLastGroup && indirectArguments == 0 && (matchesCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchesStreamCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchesIndexedCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute))) return; } else { if (indirectArguments == 0 && Graphics::TakeMultisampledSingleClear(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads)) { pendingDispatchPhases().outcome = DispatchOutcome::FillHle; @@ -139,7 +156,8 @@ void Driver::dispatch(QueueState& queue, std::span packet, if (const auto htile = Graphics::HtileDepthClearAddress(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads); htile != 0) { const auto threads = (packet[4] & 0x20u) != 0 ? static_cast(packet[1]) : static_cast(packet[1]) * compute.numThreads[0]; const auto nativeBytes = Graphics::HtileDepthClearBytes_nid_no_patch(std::span(snapshot.code).subspan(codeOffset), userData, compute, packet, indirectArguments); - Graphics::NoteHtileDepthClear(htile, static_cast(threads * 16u), nativeBytes); + const auto bytes = static_cast(threads * 16u); + if (Graphics::NoteHtileDepthClear(htile, bytes, nativeBytes)) htileClearCompletion = std::pair{htile, bytes}; } if (!partialLastGroup && indirectArguments == 0 && copyBuffer(queue, submission.queue, packet, std::span(snapshot.code).subspan(codeOffset), userData, compute, localDevice, address)) { pendingDispatchPhases().outcome = DispatchOutcome::CopyHle; @@ -277,6 +295,14 @@ void Driver::dispatch(QueueState& queue, std::span packet, fromAhead = false; ++groupStats.keyMismatches; } + if (fromAhead) { + if (const auto hook = aheadAdoptHook.load(std::memory_order_acquire)) hook(); + if (!CapturedCurrent(ahead.captured, snapshot)) { + fromAhead = false; + ++dispatchesRejectedAhead; + ++groupStats.memoryMismatches; + } + } phaseTiming.Phase(PhaseKey); timing.Mark("decode_key_device_setup"); bool readsClear = false; @@ -410,6 +436,11 @@ void Driver::dispatch(QueueState& queue, std::span packet, parked.resolvedAt = std::chrono::steady_clock::now(); ++groupStats.resolved; if (cached) ++groupStats.resolvedHits; + if (!cached && compiledResult != nullptr && !localDevice->HasComputePipeline(*compiledResult)) { + SubmitLibraryPrebuild([localDevice, compiledResult, address] { + try { localDevice->PrebuildComputePipeline(*compiledResult, address); } catch (const std::exception&) {} + }); + } return; } if (verifyDataHits() && dataHit) verifyDataHit(snapshot, codeOffset, request, memory, address, *keepVariant, liveWords, *compiledResult); @@ -462,18 +493,18 @@ void Driver::dispatch(QueueState& queue, std::span packet, const auto epoch = DrawPipeline::EpochToken().load(std::memory_order_relaxed); const auto queueId = submission.queue; itemCommits.fetch_add(1, std::memory_order_relaxed); - DrawPipeline::Queue0().Enqueue([this, localDevice = std::move(localDevice), result = compiledResult, snapshots = std::move(snapshots), liveWords = std::move(liveWords), groups, address, queueId, recipe, keepVariant, dataHit, epoch] { + DrawPipeline::Queue0().Enqueue([this, localDevice = std::move(localDevice), result = compiledResult, snapshots = std::move(snapshots), liveWords = std::move(liveWords), groups, address, queueId, recipe, keepVariant, dataHit, epoch, htileClearCompletion] { DrawPipeline::FollowEpoch(epoch); GuestMemory::SetCurrentPacket(0x15, queueId); const auto& compiledCommit = *result; const bool noteWrites = writeEvidenceEnabled() || traceCapSync(); try { - std::shared_ptr recipeHit = localDevice->PrepareRecipe(recipe, false); + std::shared_ptr recipeHit = htileClearCompletion.has_value() ? nullptr : localDevice->PrepareRecipe(recipe, false); std::shared_ptr builtRecipe; bool writersNoted = false; for (;;) { std::shared_ptr prepared; - if (recipeHit == nullptr || VulkanDevice::VerifyRecipes()) prepared = localDevice->PrepareDispatch(compiledCommit, snapshots); + if (recipeHit == nullptr || VulkanDevice::VerifyRecipes() || htileClearCompletion.has_value()) prepared = localDevice->PrepareDispatch(compiledCommit, snapshots); GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Dispatch); std::lock_guard gpuLock(GuestMemory::GpuMutex()); recordLabelsForPacket(localDevice.get(), queueId); @@ -491,7 +522,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, continue; } } else { - localDevice->Dispatch(compiledCommit, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), &builtRecipe); + localDevice->Dispatch(compiledCommit, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), htileClearCompletion.has_value() ? nullptr : &builtRecipe, htileClearCompletion); } break; } @@ -517,7 +548,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, std::shared_ptr recipeHit; if (cached && keepVariant != nullptr && !stampValidate()) { - recipeHit = localDevice->PrepareRecipe(keepVariant->recipe.load(std::memory_order_acquire), indirectArguments != 0); + recipeHit = htileClearCompletion.has_value() ? nullptr : localDevice->PrepareRecipe(keepVariant->recipe.load(std::memory_order_acquire), indirectArguments != 0); phaseTiming.Phase(PhaseRecipePrecheck); } @@ -595,7 +626,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, const auto outcome = localDevice->DispatchIndirect(compiled, indirectArguments, snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr, threadGroup); countIndirect(outcome.cpuReason, outcome.argumentReadMs); } else { - localDevice->Dispatch(compiled, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr); + localDevice->Dispatch(compiled, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), htileClearCompletion.has_value() ? nullptr : (attachTo != nullptr ? &builtRecipe : nullptr), htileClearCompletion); } } catch (const std::exception& error) { if (rethrow(error)) return; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp index 0b6f1f0eb0..50a5eee57b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp @@ -1,9 +1,12 @@ #include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" +#include "Optimization/ResourceProgram.hpp" #include #include #include @@ -39,7 +42,25 @@ std::uint64_t Driver::DispatchesAdoptedAhead() const { return dispatchesAdoptedAhead.load(std::memory_order_relaxed); } +std::uint64_t Driver::DispatchesRejectedAhead() const { + return dispatchesRejectedAhead.load(std::memory_order_relaxed); +} + +void Driver::SetAheadAdoptHook(void (*hook)()) { + aheadAdoptHook.store(hook, std::memory_order_release); +} + +namespace { + +std::size_t& PreviewedUntil() { + static thread_local std::size_t until = 0; + return until; +} + +} + void Driver::clearResolvedAhead() { + PreviewedUntil() = 0; auto& resolved = resolvedAhead(); if (resolved.empty()) return; groupCaptureStats().stale += resolved.size(); @@ -106,7 +127,9 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l static const bool allQueues = std::getenv("APS5_GROUP_CAPTURE_ALL_QUEUES") != nullptr; if (!allQueues && submission.queue == 0) return; auto& resolved = resolvedAhead(); - if (resolved.contains(from) || device.Load() == nullptr) return; + const auto localDevice = device.Load(); + if (resolved.contains(from) || localDevice == nullptr) return; + if (submission.queue == 0 && DrawPipeline::Depth() != 0 && DrawPipeline::Queue0().Busy()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, (submission.commands[from] >> 8u) & 0xffu); auto& stats = groupCaptureStats(); const auto begin = std::chrono::steady_clock::now(); ++stats.runs; @@ -124,6 +147,110 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l } } resolving; const auto& commands = submission.commands; + if (submission.shaders != nullptr && from >= PreviewedUntil()) { + bool hasFurtherDispatch = false; + std::size_t limit = from < commands.size() ? from + Pm4::PacketWords(commands[from]) : commands.size(); + while (limit < commands.size()) { + const auto header = commands[limit]; + if (Pm4::FillerPacket(header)) { + ++limit; + continue; + } + const auto count = Pm4::PacketWords(header); + if (limit + count > commands.size()) break; + const auto opcode = (header >> 8u) & 0xffu; + if (header == FlipPacketHeader || header == RenderingWaitPacketHeader || opcode == 0x22 || opcode == 0x3f || opcode == 0x3c || opcode == 0x93) break; + if (opcode == 0x15 || opcode == 0x16) hasFurtherDispatch = true; + limit += count; + } + PreviewedUntil() = limit; + if (hasFurtherDispatch) { + static thread_local QueueState* previewSlot = nullptr; + auto& preview = ShaderRecompiler::ThreadOwned(previewSlot); + preview.shader = live.shader; + preview.predication = live.predication; + std::vector pendingShaders; + std::vector submittedShaders; + std::size_t scan = from; + while (scan < limit) { + const auto header = commands[scan]; + if (Pm4::FillerPacket(header)) { + ++scan; + continue; + } + const auto count = Pm4::PacketWords(header); + if (scan + count > limit) break; + const auto packet = std::span(commands).subspan(scan, count); + const auto opcode = (header >> 8u) & 0xffu; + const auto next = scan + count; + if (opcode == 0x15 || opcode == 0x16) { + if (count < (opcode == 0x15 ? 5u : 3u)) break; + if (scan > from) { + const auto low = preview.shader.find(0x20c); + const auto high = preview.shader.find(0x20d); + if (low != preview.shader.end() && high != preview.shader.end()) { + const auto address = (static_cast(low->second) << 8u) | (static_cast(high->second & 0xffu) << 40u); + if (auto registered = RegisteredProgram(*submission.shaders, address, {0}); registered != nullptr && registered->type == 0) { + bool isPending = false; + bool isEmpty = false; + { + std::lock_guard lock(registered->prepared->mutex); + isPending = registered->prepared->pending; + isEmpty = registered->prepared->entries.empty(); + } + if (isPending) { + if (std::ranges::find(pendingShaders, registered->prepared.get()) == pendingShaders.end()) { + pendingShaders.push_back(registered->prepared.get()); + } + } else if (isEmpty && std::ranges::find(submittedShaders, registered->prepared.get()) == submittedShaders.end()) { + const auto codeOffset = static_cast((address - registered->codeAddress) / sizeof(std::uint32_t)); + if (codeOffset < registered->code.size()) { + try { + auto compute = Graphics::DecodeComputeStageInfo(preview.shader, registered->header); + submittedShaders.push_back(registered->prepared.get()); + const auto rsrc2 = preview.shader.find(0x213); + const auto userCount = rsrc2 != preview.shader.end() ? ((rsrc2->second >> 1u) & 0x1fu) : 0u; + std::vector userData(userCount, 0u); + for (std::uint32_t i = 0; i < userCount; ++i) { + if (const auto reg = preview.shader.find(0x240u + i); reg != preview.shader.end()) userData[i] = reg->second; + } + const auto wave = (packet.back() & 0x8000u) != 0 ? 32u : 64u; + SubmitRegistrationPreparation([registered, localDevice, address, codeOffset, wave, compute, userData = std::move(userData), state = preview.shader]() mutable { + std::vector memory{{registered->codeAddress, std::as_bytes(std::span(registered->code))}}; + if (!registered->header.empty()) memory.push_back({registered->headerAddress, registered->header}); + ShaderRecompiler::RecompileRequest request{ + {ShaderRecompiler::ShaderStage::Compute, address, std::span(registered->code).subspan(codeOffset), registered->headerAddress, registered->header}, + {wave, 0, userData, compute, std::nullopt, std::nullopt, memory, DispatchFloatMode(*registered, state)}, + localDevice->ComputeTarget(wave), + {0, 0, 0, 128} + }; + try { static_cast(InvocationFor(*registered, codeOffset, request)); } catch (const std::exception&) {} + }, registered->prepared.get()); + } catch (const std::exception&) {} + } + } + } + } + } + Pm4::NoteDispatchRegisters(packet, preview); + } else if (Pm4::IndirectRegisterOpcode(opcode)) { + const auto found = submission.registerLists.find(scan); + if (count != 5 || found == submission.registerLists.end() || found->second.size() != static_cast(packet[4]) * 2 || !Pm4::IndirectRegistersValid(found->second)) break; + Pm4::ExecuteIndirectRegisters(packet, found->second, preview); + } else if (!Pm4::AccessesMemory(header) && opcode != 0x3c && opcode != 0x93 && opcode != 0x37 && opcode != 0x42 && opcode != 0x46 && opcode != 0x49 && opcode != 0x58) { + try { + Pm4::Execute(packet, preview); + } catch (const std::exception&) { + break; + } + } + scan = next; + } + for (auto it = pendingShaders.rbegin(); it != pendingShaders.rend(); ++it) { + PrioritizeRegistrationPreparation(**it); + } + } + } for (std::size_t cursor = from; cursor < commands.size();) { const auto header = commands[cursor]; if (Pm4::FillerPacket(header)) { @@ -139,7 +266,7 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l if (opcode == 0x3c || opcode == 0x93) { if (!Pm4::WaitSatisfiedUnchecked(packet) && !WrittenByRun(packet, labels)) break; } else if (opcode == 0x15 || opcode == 0x16) { - if (resolved.contains(cursor)) break; + if (count < (opcode == 0x15 ? 5u : 3u) || resolved.contains(cursor)) break; if (opcode == 0x16) { std::uint64_t arguments = 0; try { @@ -207,9 +334,9 @@ void Driver::reportGroupCapture(std::uint32_t queue) { auto& stats = groupCaptureStats(); const auto now = std::chrono::steady_clock::now(); if (now - stats.reported < std::chrono::seconds(10)) return; - AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n", + AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu memory %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n", queue, static_cast(stats.runs), static_cast(stats.resolved), static_cast(stats.resolvedHits), stats.resolveMs, stats.resolveMaxMs, - static_cast(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast(stats.adopted) : 0.0, stats.ageMaxMs, static_cast(stats.addressMismatches), static_cast(stats.keyMismatches), + static_cast(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast(stats.adopted) : 0.0, stats.ageMaxMs, static_cast(stats.addressMismatches), static_cast(stats.keyMismatches), static_cast(stats.memoryMismatches), static_cast(stats.failures), static_cast(stats.stale), static_cast(stats.labelOverlaps), static_cast(stats.writtenBefore), static_cast(stats.deviceRejects), static_cast(stats.argumentMismatches)); stats = GroupCaptureStats{}; stats.reported = now; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp index 65057babfa..121a1bedde 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp @@ -3,6 +3,7 @@ #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/Mutex.hpp" #include #include @@ -30,7 +31,7 @@ const char* IndirectPathName(int path) { void Driver::countIndirect(int path, double readMs) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (!profile || path < 0 || path >= IndirectPaths) return; - static std::mutex countsMutex; + static AgcDriver::Mutex countsMutex; static std::uint64_t counts[IndirectPaths] = {}; static double readWaitedMs = 0; static auto lastReport = std::chrono::steady_clock::now(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/VariantValidation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/VariantValidation.cpp index e4136cbebe..99c7b35426 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/VariantValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/VariantValidation.cpp @@ -93,7 +93,7 @@ EntryOutcome Driver::validateVariant(std::uint64_t program, std::uint32_t queue, if (live != nullptr) live->clear(); if (DrawPipeline::Active()) { auto& pipeline = DrawPipeline::Queue0(); - if (pipeline.Busy() && std::any_of(variant.runs.begin(), variant.runs.end(), [&](const auto& run) { return pipeline.Overlaps(run.first, static_cast(run.second - run.first)); })) pipeline.Drain(DrawPipeline::DrainReason::Capture); + if (pipeline.Busy() && pipeline.OverlapsAny(variant.runs)) pipeline.Drain(DrawPipeline::DrainReason::Capture); } const bool masked = live != nullptr && !variant.dataPositions.empty(); const DataMask mask{variant.dataPositions, live}; 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 dd0231145e..4d85df3c00 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -1,28 +1,322 @@ +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/DrawWriteRanges.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawProgramTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Memory/DrawWriteRanges.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "Optimization/ResourceProgram.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" +#include +#include #include #include +#include +#include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include namespace AgcDriver::DriverDetail { -DrawVerdict Driver::draw(QueueState& queue, std::span packet, const Submission& submission, std::string& rejected) { - PerformanceTimer timing("Driver.Draw"); - static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; +namespace { + +bool TraceShaderCorrelation() { + static const bool enabled = std::getenv("APS5_TRACE_SHADER_CORRELATION") != nullptr; + return enabled && AgcDriver::CaptureTrace::Enabled(); +} + +std::atomic& NextDrawTraceToken() { + static std::atomic next{0}; + return next; +} + +std::string ExceptionReason(const std::exception_ptr& failure) { + try { + if (failure != nullptr) std::rethrow_exception(failure); + } catch (const std::exception& error) { + return error.what(); + } catch (...) { + } + return "unknown exception"; +} + +const char* ExceptionType(const std::exception_ptr& failure) { + try { + if (failure != nullptr) std::rethrow_exception(failure); + } catch (const std::exception& error) { + return typeid(error).name(); + } catch (...) { + } + return "unknown"; +} + +void TraceMaterializationFailure(std::uint64_t token, const Submission& submission, std::size_t packetOffset, const char* phase, std::size_t completedSubdraws, const std::exception_ptr& failure) { + if (token == 0) return; + try { + const auto reason = ExceptionReason(failure); + AgcDriver::CaptureTrace::Log("draw-materialization-failed token=%llu submission=%llu queue=%x offset=%zu phase=%s exception=%s reason=%.256s completedSubdraws=%zu", + static_cast(token), static_cast(submission.serial), submission.queue, packetOffset, phase, ExceptionType(failure), reason.c_str(), completedSubdraws); + } catch (...) { + + } +} + +void TraceSubmissionFailure(std::uint64_t token, const Submission& submission, std::size_t packetOffset, std::size_t completedSubdraws, const std::exception_ptr& failure) { + if (token == 0) return; + try { + const auto reason = ExceptionReason(failure); + AgcDriver::CaptureTrace::Log("draw-submission-failed token=%llu submission=%llu queue=%x offset=%zu exception=%s reason=%.256s completedSubdraws=%zu", + static_cast(token), static_cast(submission.serial), submission.queue, packetOffset, ExceptionType(failure), reason.c_str(), completedSubdraws); + } catch (...) { + + } +} + +void TraceDrawAttemptFailure(std::uint64_t token, const char* phase, const char* outcome, std::uint64_t submission, std::uint32_t queue, std::size_t packetOffset, std::string_view reason, std::string_view exceptionType = "none") { + if (token == 0) return; + AgcDriver::CaptureTrace::LogBestEffort("draw-attempt-failed token=%llu phase=%s outcome=%s submission=%llu queue=%x offset=%zu exception=%s reason=%.256s", + static_cast(token), phase, outcome, static_cast(submission), queue, packetOffset, exceptionType.data(), reason.data()); +} + +void TraceDrawAttemptException(std::uint64_t token, const char* phase, std::uint64_t submission, std::uint32_t queue, std::size_t packetOffset, const std::exception_ptr& failure) noexcept { + try { + TraceDrawAttemptFailure(token, phase, "exception", submission, queue, packetOffset, ExceptionReason(failure), ExceptionType(failure)); + } catch (...) { + + } +} + +const char* DrawVerdictName(DrawVerdict verdict) { + switch (verdict) { + case DrawVerdict::Drawn: return "Drawn"; + case DrawVerdict::Nothing: return "Nothing"; + case DrawVerdict::Rejected: return "Rejected"; + } + return "Unknown"; +} + +std::string_view TrimmedReason(std::string_view reason) { + if (reason.size() > 200) reason = reason.substr(0, 200); + return reason; + } + +} + + +struct DrawTail { + using GpuLock = std::unique_lock>; + bool profile = false; std::array phaseMs{}; std::uint64_t captures = 0; - auto phaseLap = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; - DrawPhaseTiming phaseTiming{profile, phaseMs, phaseLap}; + std::chrono::steady_clock::time_point phaseLap{}; + DrawPhaseTiming phaseTiming{false, phaseMs, phaseLap}; + Pm4::DrawParameters drawParameters{}; + bool pipelined = false; + bool traceIndirect = false; + std::uint64_t dumpTarget = 0; + std::uint64_t dumpSlot1 = 0; + std::shared_ptr localDevice; + bool registerKey = false; + std::uint64_t drawKey = 0; + std::shared_ptr entry; + std::shared_ptr decode; + std::shared_ptr relocatedDecode; + std::shared_ptr dataEntry; + std::uint64_t shapeKey = 0; + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + std::vector writablePrograms; + const std::vector* programs = nullptr; + std::vector memory; + std::vector linked; + std::vector> vertexInfos; + std::vector> decodeReads; + std::unique_ptr shaderMemory; + std::vector results; + std::vector> compiledStages; + std::vector stages; + std::uint32_t pushCursorBytes = 0; + std::vector programResults; + std::vector stageCaptures; + std::vector> matched; + std::vector> matchedRegions; + std::vector> fresh; + std::vector recompiled; + DrawDataCandidates dataCandidates; + std::vector reused; + std::vector stageData; + bool drawHit = false; + bool verifyHit = false; + bool stageEntries = false; + bool patchedStages = false; + const Submission* submission = nullptr; + std::uint32_t submissionQueue = 0; + std::uint64_t submissionSerial = 0; + std::size_t packetOffset = 0; + std::uint32_t packetOpcode = 0; + std::uint32_t reg207 = 0; + std::uint64_t epoch = 0; + QueueState* queue = nullptr; + std::span packet; + std::string* rejected = nullptr; + std::uint64_t traceToken = 0; + VulkanDevice::DrawTraceContext trace{}; + std::string ownRejected; + GpuLock* gpuLock = nullptr; + GpuLock ownLock{GuestMemory::GpuMutex(), std::defer_lock}; +}; + +namespace { + +void noteFinisherHandOff(bool handed) { + static std::atomic handedOff{0}, kept{0}; + static std::atomic reportedAt{0}; + (handed ? handedOff : kept).fetch_add(1, std::memory_order_relaxed); + static const bool stats = std::getenv("APS5_PIPELINE_STATS") != nullptr || std::getenv("APS5_PROFILE_DRAW") != nullptr; + if (!stats) return; + const auto tenSeconds = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count() / 10; + if (reportedAt.exchange(tenSeconds, std::memory_order_relaxed) == tenSeconds) return; + AgcDriver::ProfilePrint_nid_no_patch("[finisher] draws (10 s): %llu handed to the draw finisher, %llu finished on the worker%s\n", static_cast(handedOff.exchange(0)), static_cast(kept.exchange(0)), DrawFinisher::DrainText().c_str()); +} + +} + +namespace { + +enum class HeldReason : std::size_t { Known, NoDecode, Size, NullVertex, NullFragment, NullOther, Bda, Buffer, Storage, Count }; +std::array, static_cast(HeldReason::Count)> heldReasons{}; + +void noteHeld(HeldReason reason) { + heldReasons[static_cast(reason)].fetch_add(1, std::memory_order_relaxed); + static const bool profile = std::getenv("APS5_PIPELINE_STATS") != nullptr || std::getenv("APS5_PROFILE_DRAW") != nullptr; + if (!profile) return; + static std::atomic total{0}; + const auto count = ++total; + if (count % 262144u != 0u && (count > 262144u || (count & (count - 1u)) != 0u)) return; + constexpr std::array(HeldReason::Count)> names{"targets only", "no decode", "stage count", "no variant (vertex)", "no variant (fragment)", "no variant (other)", "BDA stores", "written buffer", "storage image"}; + std::string text; + for (std::size_t i = 0; i < names.size(); ++i) { + char item[64]; + std::snprintf(item, sizeof(item), "%s %s %llu", i == 0 ? "" : ",", names[i], static_cast(count >= 262144u ? heldReasons[i].exchange(0) : heldReasons[i].load())); + text += item; + } + std::fprintf(stderr, "[finisher-held] held draws' writes:%s\n", text.c_str()); +} + +std::optional writesBeyondTargets(const ShaderRecompiler::RecompileResult& compiled) { + using Role = ShaderRecompiler::DescriptorRole; + for (const auto& binding : compiled.bindings) { + if (binding.role == Role::BdaPagetable && Graphics::ShaderStoresThroughAddresses(compiled.spirv)) return HeldReason::Bda; + if (binding.role == Role::GuestBuffers) { + for (std::uint32_t element = 0; element < binding.count; ++element) { + if (element >= binding.bufferWritten.size() || binding.bufferWritten[element]) return HeldReason::Buffer; + } + } + if (binding.role == Role::GuestImages && binding.kind == ShaderRecompiler::DescriptorKind::StorageImage) { + for (std::uint32_t element = 0; element < binding.count; ++element) { + if (element >= binding.imageWritten.size() || binding.imageWritten[element]) return HeldReason::Storage; + } + } + } + return std::nullopt; +} + +DrawFinisher::Pending heldWrites(const DrawTail& tail) { + DrawFinisher::Pending held; + held.key = tail.drawKey; + held.shape = tail.shapeKey; + if (tail.decode == nullptr) { + noteHeld(HeldReason::NoDecode); + return held; + } + if (tail.matched.size() != tail.programs->size()) { + noteHeld(HeldReason::Size); + return held; + } + const auto& roles = tail.decode->roles; + for (std::size_t i = 0; i < tail.matched.size(); ++i) { + if (i < roles.size() && roles[i] == ShaderRecompiler::ProgramRole::GeometryBack) continue; + const auto& variant = tail.matched[i]; + if (variant == nullptr || variant->compiled == nullptr) { + const bool candidates = !tail.drawHit && i < tail.dataCandidates.size() && !tail.dataCandidates[i].empty(); + const auto& program = (*tail.programs)[i]; + if (!candidates && program.snapshot != nullptr && !ProgramWritesMemory(*program.snapshot, program.codeOffset) && (program.tailContinuation == nullptr || !ProgramWritesMemory(*program.tailContinuation, 0))) continue; + if (!candidates) { + const auto stage = (*tail.programs)[i].binary.stage; + noteHeld(stage == ShaderRecompiler::ShaderStage::Fragment ? HeldReason::NullFragment : stage == ShaderRecompiler::ShaderStage::Vertex ? HeldReason::NullVertex : HeldReason::NullOther); + return held; + } + for (const auto& [candidate, regions] : tail.dataCandidates[i]) { + if (candidate == nullptr || candidate->compiled == nullptr) { + noteHeld(HeldReason::NullOther); + return held; + } + if (const auto reason = writesBeyondTargets(*candidate->compiled)) { + noteHeld(*reason); + return held; + } + } + continue; + } + if (const auto reason = writesBeyondTargets(*variant->compiled)) { + noteHeld(*reason); + return held; + } + if (i < tail.stageData.size() && tail.stageData[i].compiled != nullptr) { + if (const auto reason = writesBeyondTargets(*tail.stageData[i].compiled)) { + noteHeld(*reason); + return held; + } + } + } + noteHeld(HeldReason::Known); + held.writes = DrawWriteRanges(tail.decode->state, {}, ExactDrawWrites()); + held.unknown = false; + return held; +} + +bool EmptyIndirectSkip() { + static const bool enabled = std::getenv("APS5_NO_EMPTY_INDIRECT_SKIP") == nullptr; + return enabled; +} + +struct CpuIndirectProgramsStorage {}; + +std::unordered_map& CpuIndirectPrograms() { + return HostThreadLocal, CpuIndirectProgramsStorage>(); +} + +} + +DrawVerdict Driver::draw(QueueState& queue, std::span packet, const Submission& submission, std::size_t packetOffset, std::string& rejected) { + PerformanceTimer timing("Driver.Draw"); + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + auto tailHolder = std::make_shared(); + auto& tail = *tailHolder; + auto& phaseMs = tail.phaseMs; + auto& captures = tail.captures; + auto& phaseLap = tail.phaseLap; + phaseLap = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; + tail.profile = profile; + tail.phaseTiming.profile = profile; + auto& phaseTiming = tail.phaseTiming; if (profile && packetStartedAt() != std::chrono::steady_clock::time_point{}) phaseMs[DrawRowPrologue] = std::chrono::duration(phaseLap - packetStartedAt()).count(); const auto drawn = [&] { @@ -35,8 +329,20 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe pending.tailAt = phaseLap; return DrawVerdict::Drawn; }; - auto drawParameters = Pm4::ResolveDraw(packet, queue); - const bool pipelined = DrawPipeline::Active(); + const std::uint64_t traceToken = TraceShaderCorrelation() ? NextDrawTraceToken().fetch_add(1, std::memory_order_relaxed) + 1 : 0; + if (traceToken != 0) { + AgcDriver::CaptureTrace::LogBestEffort("draw-attempt token=%llu submission=%llu queue=%x offset=%zu", + static_cast(traceToken), static_cast(submission.serial), submission.queue, packetOffset); + } + auto& drawParameters = tail.drawParameters; + try { + drawParameters = Pm4::ResolveDraw(packet, queue); + } catch (...) { + TraceDrawAttemptException(traceToken, "resolve-draw", submission.serial, submission.queue, packetOffset, std::current_exception()); + throw; + } + tail.pipelined = DrawPipeline::Active(); + const bool pipelined = tail.pipelined; static const bool pipelinedIndirect = std::getenv("APS5_NO_PIPELINED_INDIRECT") == nullptr; if (pipelined && drawParameters.indirect) { const auto& indirect = *drawParameters.indirect; @@ -44,11 +350,11 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe const bool overlaps = pipeline.Busy() && (pipeline.Overlaps(indirect.arguments, static_cast(std::max(indirect.RangeBytes(), indirect.recordBytes))) || (indirect.countIndirect && pipeline.Overlaps(indirect.countAddress, 4))); if (!pipelinedIndirect || overlaps) pipeline.Drain(DrawPipeline::DrainReason::Indirect); } - const auto drainBeforeInline = [&] { - if (pipelined && DrawPipeline::Queue0().Busy()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Indirect); - }; - bool traceIndirect = false; - if (const auto verdict = precheckDraw(queue, submission, packet, drawParameters, rejected, traceIndirect)) return *verdict; + auto& traceIndirect = tail.traceIndirect; + if (const auto verdict = precheckDraw(queue, submission, packetOffset, packet, drawParameters, rejected, traceIndirect)) { + TraceDrawAttemptFailure(traceToken, "precheck", "rejected", submission.serial, submission.queue, packetOffset, rejected); + return *verdict; + } phaseTiming.Phase(DrawRowPrecheck); using Stage = ShaderRecompiler::ShaderStage; using Role = ShaderRecompiler::ProgramRole; @@ -59,7 +365,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe static const bool lockedPrepare = std::getenv("APS5_LOCKED_DRAW_PREPARE") != nullptr; std::unique_lock gpuLock(GuestMemory::GpuMutex(), std::defer_lock); - std::shared_ptr localDevice; + auto& localDevice = tail.localDevice; if (lockedPrepare) { GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw); @@ -81,13 +387,14 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe phaseTiming.Phase(DrawRowVectors); const bool useDrawEntries = drawEntries() && !ShaderRecompiler::DebugProbeActive() && dumpTarget == 0 && dumpSlot1 == 0; - const bool registerKey = useDrawEntries && registerKeyEnabled(); - std::uint64_t drawKey = 0; + tail.registerKey = useDrawEntries && registerKeyEnabled(); + const bool registerKey = tail.registerKey; + auto& drawKey = tail.drawKey; std::array drawKeyComponents{}; - std::shared_ptr entry; - std::shared_ptr decode; - std::shared_ptr dataEntry; - std::uint64_t shapeKey = 0; + auto& entry = tail.entry; + auto& decode = tail.decode; + auto& dataEntry = tail.dataEntry; + auto& shapeKey = tail.shapeKey; std::uint64_t drawIdentity = 0, drawAlias = 0; std::vector userPatches; if (registerKey) { @@ -95,6 +402,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe std::shared_ptr relocation; bool aliasFirst = false; if (drawRelocationEnabled()) { + if (DrawFinisher::Busy() && drawRelocationPending.key != 0) DrawFinisher::Get().Drain(); drawIdentity = drawIdentityKey(queue, *submission.shaders); std::lock_guard cacheLock(drawCacheMutex); learnPendingRelocation(); @@ -107,6 +415,17 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe } } drawKey = drawRegisterKey(queue, *submission.shaders, localDevice->Serial(), &shapeKey, traceDrawCache() ? &drawKeyComponents : nullptr, aliasFirst ? relocation.get() : nullptr, aliasFirst ? &drawAlias : nullptr); + if (DrawFinisher::Busy()) { + bool shapeMatters = false; + if (DrawFinisher::Overlap() && drawDataHits()) { + std::lock_guard peekLock(drawCacheMutex); + const auto shape = drawShapes.find(shapeKey); + shapeMatters = !drawCache.contains(drawKey) && (shape == drawShapes.end() || !drawCache.contains(shape->second)); + } + const bool drain = !DrawFinisher::Overlap() || DrawFinisher::Get().PendingKey(drawKey, shapeMatters ? shapeKey : 0); + if (drain) DrawFinisher::Get().Drain(); + if (DrawFinisher::Overlap()) DrawFinisher::NoteDrain(DrawFinisher::DrainSite::Lookup, drain); + } std::unique_lock cacheLock(drawCacheMutex); ++drawEntryCounters.lookups; ++drawEntryCounters.registerKeyLookups; @@ -184,15 +503,41 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe } decode = std::move(refreshed); } - resolveDrawDecode(queue, submission, localDevice->graphicsContext(), decode, registerKey, drawKey, profile); + try { + resolveDrawDecode(queue, submission, localDevice->graphicsContext(), decode, registerKey, drawKey, profile); + } catch (...) { + TraceDrawAttemptFailure(traceToken, "decode", "exception", submission.serial, submission.queue, packetOffset, ExceptionReason(std::current_exception())); + throw; + } const auto& graphics = decode->state; - auto pixel = decode->pixel; + auto& pixel = tail.pixel; + pixel = decode->pixel; const bool writesPrograms = graphics.stages.mesh || drawParameters.indirect || !userPatches.empty(); - std::vector writablePrograms; + auto& writablePrograms = tail.writablePrograms; if (writesPrograms) writablePrograms = decode->programs; for (const auto& patch : userPatches) writablePrograms[patch.program].userData[patch.index] = patch.value; - const std::vector& programs = writesPrograms ? writablePrograms : decode->programs; - if (const auto skipped = SkippedShader(); skipped != 0 && std::any_of(programs.begin(), programs.end(), [skipped](const DrawProgram& program) { return program.binary.codeAddress == skipped; })) return drawn(); + tail.programs = writesPrograms ? &writablePrograms : &decode->programs; + const auto& programs = *tail.programs; + if (std::any_of(programs.begin(), programs.end(), [](const DrawProgram& program) { return SkippedShader(program.binary.codeAddress); })) return drawn(); + const bool emptyIndirectSkip = EmptyIndirectSkip(); + auto& cpuIndirectPrograms = CpuIndirectPrograms(); + const auto recordDraws = [&](const Pm4::DrawArguments& arguments) { return arguments.count != 0 && arguments.instances != 0 && (!drawParameters.indexed || !drawParameters.indirect->indexLimitKnown || arguments.firstVertexOrIndex < drawParameters.indexCount); }; + if (emptyIndirectSkip && drawParameters.indirect && !programs.empty()) { + const auto known = cpuIndirectPrograms.find(programs.front().binary.codeAddress); + if (known != cpuIndirectPrograms.end() && known->second) { + const auto& indirect = *drawParameters.indirect; + recordQueuedLabelsBeforeRead(submission.queue); + const auto readStart = std::chrono::steady_clock::now(); + const auto count = std::min(indirect.countIndirect ? Pm4::ReadDrawCount(indirect) : indirect.count, indirect.count); + bool any = false; + for (std::uint32_t record = 0; record < count && !any; ++record) any = recordDraws(Pm4::ReadDrawArguments(indirect, record)); + if (!any) { + Graphics::CountEmptyIndirectDraw(std::chrono::duration(std::chrono::steady_clock::now() - readStart).count()); + timing.Mark("empty_indirect_skipped"); + return drawn(); + } + } + } const auto setMeshIndexBuffer = [&](const Pm4::DrawParameters& parameters) { if (!graphics.stages.mesh) return; auto& words = writablePrograms.front().userData; @@ -231,10 +576,15 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe indirect.startInstanceSgpr = sgprOf(indirect.startInstanceLocation); indirect.drawIndexSgpr = sgprOf(indirect.drawIndexLocation); } - std::vector memory; + if (DrawFinisher::Busy()) { + const bool drain = !DrawFinisher::Overlap(); + if (drain) DrawFinisher::Get().Drain(); + else DrawFinisher::NoteDrain(DrawFinisher::DrainSite::Reads, false); + } + auto& memory = tail.memory; + auto& linked = tail.linked; memory.reserve(2 * programs.size() + 32); AppendProgramMemory(memory, programs); - std::vector linked; linked.reserve(programs.size()); for (std::size_t i = 0; i < programs.size(); ++i) { const auto& program = programs[i]; @@ -243,48 +593,64 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe timing.Mark("prepare"); phaseTiming.Phase(DrawRowProgramPrepare); - std::vector> vertexInfos(programs.size()); - std::vector> decodeReads(programs.size()); + auto& vertexInfos = tail.vertexInfos; + vertexInfos.resize(programs.size()); + auto& decodeReads = tail.decodeReads; + decodeReads.resize(programs.size()); const auto decodeVertexInfo = [&](std::size_t i) { const auto& program = programs[i]; if (program.binary.stage == Stage::Fragment || roles[i] == Role::GeometryBack) return; - decodeReads[i].clear(); - vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr); - if (vertexInfos[i]) vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, readRegister(queue.context, 0x207), program.tailContinuation.get()) | graphics.layerExports; + vertexInfos[i] = DecodeOrderedVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, decodeReads[i], 8u - program.firstUserSgpr); if (vertexInfos[i]) { + vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, readRegister(queue.context, 0x207), program.tailContinuation.get()) | graphics.layerExports; vertexInfos[i]->depthRemapMode = graphics.depthRemap.mode; + Graphics::ApplyPointSize(graphics, *vertexInfos[i]); } }; if (!registerKey) { for (std::size_t i = 0; i < programs.size(); ++i) decodeVertexInfo(i); phaseTiming.Phase(DrawRowDecode); } - auto shaderMemory = std::make_unique(memory, &queryPendingWrite, &observePendingWrite, hookWaitCounter(), &QueryPendingSpan); - std::vector results; - std::vector> compiledStages; + auto& shaderMemory = tail.shaderMemory; + shaderMemory = std::make_unique(memory, &queryPendingWrite, &observePendingWrite, hookWaitCounter(), &QueryPendingSpan); + auto& results = tail.results; + auto& compiledStages = tail.compiledStages; compiledStages.reserve(programs.size()); - std::vector stages; - results.reserve(programs.size() + 2u); + auto& stages = tail.stages; stages.reserve(programs.size()); - std::uint32_t pushCursorBytes = 0; + auto& pushCursorBytes = tail.pushCursorBytes; - std::vector programResults(programs.size(), nullptr); + auto& programResults = tail.programResults; + programResults.assign(programs.size(), nullptr); - std::vector stageCaptures(programs.size()); - std::vector> matched(programs.size()); - std::vector> matchedRegions(programs.size()); + auto& stageCaptures = tail.stageCaptures; + stageCaptures.resize(programs.size()); + auto& matched = tail.matched; + matched.resize(programs.size()); + auto& matchedRegions = tail.matchedRegions; + matchedRegions.resize(programs.size()); - std::vector> fresh(programs.size()); + auto& fresh = tail.fresh; + fresh.resize(programs.size()); - std::vector recompiled(programs.size(), false); - std::vector, std::vector>>> dataCandidates(programs.size()); - std::vector reused(programs.size(), false); - std::vector stageData(programs.size()); - std::uint64_t dataStagesReused = 0, dataStagesCompiled = 0, dataVerified = 0; - bool drawHit = false; - bool verifyHit = false; - const bool stageEntries = useDrawEntries && !(graphics.stages.tessellation && !drawParameters.indirect && drawParameters.instanceCount > 1u); - lookupDraw(submission, localDevice, graphics, pixel, programs, roles, vertexInfos, stageEntries, registerKey, profile, drawKey, entry, matched, matchedRegions, drawHit, verifyHit, phaseTiming, phaseMs, dataEntry, dataCandidates, stageData); + auto& recompiled = tail.recompiled; + recompiled.assign(programs.size(), false); + auto& dataCandidates = tail.dataCandidates; + dataCandidates.resize(programs.size()); + auto& reused = tail.reused; + reused.assign(programs.size(), false); + auto& stageData = tail.stageData; + stageData.resize(programs.size()); + auto& drawHit = tail.drawHit; + auto& verifyHit = tail.verifyHit; + tail.stageEntries = useDrawEntries && !(graphics.stages.tessellation && !drawParameters.indirect && drawParameters.instanceCount > 1u); + const bool stageEntries = tail.stageEntries; + try { + lookupDraw(submission, localDevice, graphics, pixel, programs, roles, vertexInfos, stageEntries, registerKey, profile, drawKey, entry, matched, matchedRegions, drawHit, verifyHit, phaseTiming, phaseMs, dataEntry, dataCandidates, stageData); + } catch (...) { + TraceDrawAttemptException(traceToken, "lookup", submission.serial, submission.queue, packetOffset, std::current_exception()); + throw; + } for (std::size_t i = 0; i < matched.size(); ++i) { if (matched[i] == nullptr || matched[i]->shaderDataPatches.empty()) continue; applyShaderDataPatches(*matched[i]); @@ -296,6 +662,14 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe if (drawHit || verifyHit) { ++drawRelocationCounters.hits; ++drawRelocationTrials[drawIdentity].hits; + if (const auto alias = drawAliases.find(drawAlias); alias != drawAliases.end()) { + const auto donor = drawCache.find(alias->second); + if (donor != drawCache.end() && drawCacheHits - donor->second->touched > drawCacheEntries() / 8) { + drawOrder.splice(drawOrder.begin(), drawOrder, donor->second->order); + donor->second->touched = drawCacheHits; + ++drawEntryCounters.touches; + } + } } if (profile && drawRelocationCounters.lookups >= 100000) reportDrawRelocation(); } @@ -304,8 +678,24 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe for (const auto& patch : userPatches) current->programs[patch.program].userData[patch.index] = patch.value; decode = std::move(current); } - const bool patchedStages = std::any_of(stageData.begin(), stageData.end(), [](const StageDataHit& hit) { return hit.compiled != nullptr; }); + tail.patchedStages = std::any_of(stageData.begin(), stageData.end(), [](const StageDataHit& hit) { return hit.compiled != nullptr; }); + tail.relocatedDecode = relocatedDecode; std::uint64_t stageDataVerified = 0; + const auto target = graphics.hasColorTarget ? graphics.color.address : 0; + const VulkanDevice::DrawTraceContext trace{traceToken, submission.serial, submission.queue, packetOffset, target}; + const auto* tracePtr = trace.token != 0 ? &trace : nullptr; + tail.traceToken = traceToken; + tail.trace = trace; + if (trace.token != 0) { + for (std::size_t i = 0; i < programs.size(); ++i) { + const auto& program = programs[i]; + AgcDriver::CaptureTrace::LogBestEffort("draw-selected token=%llu submission=%llu queue=%x offset=%zu snapshot=0x%llx code=%llx entryOffset=%zu entry=%llx stage=%u role=%u drawCacheHit=%u variantHit=%u target=%llx", + static_cast(trace.token), static_cast(submission.serial), submission.queue, packetOffset, + static_cast(reinterpret_cast(program.snapshot.get())), static_cast(program.snapshot->codeAddress), program.codeOffset, + static_cast(program.snapshot->codeAddress + program.codeOffset), static_cast(program.binary.stage), static_cast(roles[i]), + drawHit ? 1u : 0u, matched[i] != nullptr ? 1u : 0u, static_cast(target)); + } + } if (registerKey) { @@ -330,6 +720,144 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe phaseTiming.Phase(DrawRowDecode); } + tail.submission = &submission; + tail.submissionQueue = submission.queue; + tail.packetOpcode = (packet[0] >> 8u) & 0xffu; + tail.reg207 = readRegister(queue.context, 0x207); + tail.dumpTarget = dumpTarget; + tail.dumpSlot1 = dumpSlot1; + tail.packetOffset = packetOffset; + const bool handOff = pipelined && !drawParameters.indirect && !profile && !lockedPrepare && dumpTarget == 0 && dumpSlot1 == 0 && !tracePrograms && !verifyDrawDataHits() && deferredLabels().labels.empty() && DrawFinisher::Enabled(); + if (handOff) { + tail.epoch = DrawPipeline::EpochToken().load(std::memory_order_relaxed); + tail.submissionSerial = submission.serial; + tail.packetOffset = static_cast(packet.data() - submission.commands.data()); + noteFinisherHandOff(true); + DrawFinisher::Pending held; + if (DrawFinisher::Overlap()) held = heldWrites(tail); + DrawFinisher::Get().Enqueue([this, tailHolder] { + auto& job = *tailHolder; + DrawPipeline::FollowEpoch(job.epoch); + GuestMemory::SetCurrentPacket(job.packetOpcode, job.submissionQueue); + PerformanceTimer finishTiming("Driver.DrawFinish"); + try { + if (finishDraw(job, finishTiming) == DrawVerdict::Rejected) throw std::runtime_error(job.ownRejected); + } catch (const std::exception& error) { + CaptureTrace::Log("draw-error submission=%llu offset=%zu finisher=1 reason=%.256s", static_cast(job.submissionSerial), job.packetOffset, error.what()); + throw std::runtime_error("draw at submission " + std::to_string(job.submissionSerial) + " offset " + std::to_string(job.packetOffset) + " (finished on the draw finisher): " + error.what()); + } + }, std::move(held)); + timing.Mark("draw_handed_off"); + return drawn(); + } + noteFinisherHandOff(false); + if (DrawFinisher::Busy()) { + DrawFinisher::Get().Drain(); + if (DrawFinisher::Overlap()) DrawFinisher::NoteDrain(DrawFinisher::DrainSite::Kept, true); + } + tail.queue = &queue; + tail.packet = packet; + tail.rejected = &rejected; + tail.gpuLock = &gpuLock; + return finishDraw(tail, timing); +} + +DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { + using Stage = ShaderRecompiler::ShaderStage; + using Role = ShaderRecompiler::ProgramRole; + const bool profile = tail.profile; + const auto traceToken = tail.traceToken; + const auto& trace = tail.trace; + const auto* tracePtr = trace.token != 0 ? &trace : nullptr; + const auto packetOffset = tail.packetOffset; + const auto packet = tail.packet; + auto& phaseMs = tail.phaseMs; + auto& phaseLap = tail.phaseLap; + auto& captures = tail.captures; + auto& phaseTiming = tail.phaseTiming; + auto& drawParameters = tail.drawParameters; + const bool pipelined = tail.pipelined; + static const bool pipelinedIndirect = std::getenv("APS5_NO_PIPELINED_INDIRECT") == nullptr; + const auto drainBeforeInline = [&] { + if (pipelined && DrawPipeline::Queue0().Busy()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Indirect); + }; + auto& traceIndirect = tail.traceIndirect; + const auto dumpTarget = tail.dumpTarget; + const auto dumpSlot1 = tail.dumpSlot1; + auto& gpuLock = tail.gpuLock != nullptr ? *tail.gpuLock : tail.ownLock; + auto& localDevice = tail.localDevice; + const bool registerKey = tail.registerKey; + auto& drawKey = tail.drawKey; + auto& entry = tail.entry; + auto& decode = tail.decode; + auto& dataEntry = tail.dataEntry; + auto& shapeKey = tail.shapeKey; + const auto& graphics = decode->state; + auto& pixel = tail.pixel; + auto& writablePrograms = tail.writablePrograms; + const auto& programs = *tail.programs; + const std::vector& roles = decode->roles; + auto& memory = tail.memory; + auto& linked = tail.linked; + auto& vertexInfos = tail.vertexInfos; + auto& decodeReads = tail.decodeReads; + auto& shaderMemory = tail.shaderMemory; + auto& results = tail.results; + auto& compiledStages = tail.compiledStages; + auto& stages = tail.stages; + auto& pushCursorBytes = tail.pushCursorBytes; + auto& programResults = tail.programResults; + auto& stageCaptures = tail.stageCaptures; + auto& matched = tail.matched; + auto& matchedRegions = tail.matchedRegions; + auto& fresh = tail.fresh; + auto& recompiled = tail.recompiled; + auto& dataCandidates = tail.dataCandidates; + auto& reused = tail.reused; + auto& stageData = tail.stageData; + std::uint64_t dataStagesReused = 0, dataStagesCompiled = 0, dataVerified = 0; + auto& drawHit = tail.drawHit; + auto& verifyHit = tail.verifyHit; + const bool stageEntries = tail.stageEntries; + const bool patchedStages = tail.patchedStages; + std::uint64_t stageDataVerified = 0; + const Submission& submission = *tail.submission; + std::string& rejected = tail.rejected != nullptr ? *tail.rejected : tail.ownRejected; + static const QueueState noQueue{}; + const QueueState& queue = tail.queue != nullptr ? *tail.queue : noQueue; + const auto drawn = [&] { + phaseTiming.Phase(DrawRowVectors); + if (!profile) return DrawVerdict::Drawn; + auto& pending = pendingDrawPhases(); + pending.phases = true; + pending.captures = captures; + pending.ms = phaseMs; + pending.tailAt = phaseLap; + return DrawVerdict::Drawn; + }; + const auto setMeshIndexBuffer = [&](const Pm4::DrawParameters& parameters) { + if (!graphics.stages.mesh) return; + auto& words = writablePrograms.front().userData; + require(programs.front().firstUserSgpr == 0 && words.size() >= ShaderRecompiler::MeshIndexBufferUserWord + 4, "mesh program lacks the hidden user words"); + const auto descriptor = Graphics::MeshIndexBufferDescriptor(parameters); + std::copy(descriptor.begin(), descriptor.end(), words.begin() + ShaderRecompiler::MeshIndexBufferUserWord); + }; + const auto locate = [&](std::uint32_t location) -> std::optional> { + if (location == 0x280u) return std::nullopt; + for (std::size_t i = 0; i < programs.size(); ++i) { + if (roles[i] == Role::Fragment || roles[i] == Role::GeometryBack || location < programs[i].userDataBase) continue; + const auto word = location - programs[i].userDataBase + (8u - programs[i].firstUserSgpr); + if (word < programs[i].userData.size()) return std::make_pair(i, static_cast(word)); + } + return std::nullopt; + }; + const auto decodeVertexInfo = [&](std::size_t i) { + const auto& program = programs[i]; + if (program.binary.stage == Stage::Fragment || roles[i] == Role::GeometryBack) return; + decodeReads[i].clear(); + vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr); + if (vertexInfos[i]) vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, tail.reg207, program.tailContinuation.get()) | graphics.layerExports; + }; const auto fold = [&](const ShaderRecompiler::RecompileResult& main, Pm4::DrawParameters& parameters) { FoldDrawOffsets(main, programs.front(), parameters); }; const auto freshRuns = [&](std::size_t i, std::vector>& runs, std::vector& words) { @@ -373,6 +901,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe throw std::runtime_error(message); }; std::optional indirectCpu; + std::size_t completedSubdraws = 0; std::vector pushOffsets(programs.size(), 0); std::optional meshBarycentrics; std::vector resultIndex(programs.size(), 0); @@ -402,7 +931,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe break; } } - compiledStages.push_back(materializeDrawStage(i, pushCursorBytes, queue, submission, programs, graphics, pixel, vertexInfos, memory, linked, drawParameters, localDevice, *shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected, meshBarycentrics)); + compiledStages.push_back(materializeDrawStage(i, pushCursorBytes, queue, submission, packetOffset, trace.token, programs, graphics, pixel, vertexInfos, memory, linked, drawParameters, localDevice, *shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected, meshBarycentrics)); if (!rejected.empty()) return DrawVerdict::Rejected; programResults[i] = compiledStages.back().get(); if (candidate != dataCandidates[i].end()) verifyDataStage(i, *candidate->first); @@ -414,13 +943,19 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe const auto& result = *programResults[i]; if (i == 0 && drawParameters.indirect) { indirectCpu = ClassifyIndirectDraw(result, graphics, programs.front(), localDevice, drawParameters, traceIndirect); + if (EmptyIndirectSkip()) CpuIndirectPrograms()[programs.front().binary.codeAddress] = indirectCpu.has_value(); } else if (i == 0) { fold(result, drawParameters); } - const auto slotEnd = (pushCursorBytes / Graphics::PipelinePushSlotBytes + 1u) * Graphics::PipelinePushSlotBytes; - require(result.pushConstants.size() <= slotEnd - pushCursorBytes, "stage push constants exceed the pipeline push constant block"); - stages.push_back({program.binary.stage, &result, result.pushConstants.empty() ? 0u : pushCursorBytes}); - pushCursorBytes += static_cast(result.pushConstants.size()); + try { + const auto slotEnd = (pushCursorBytes / Graphics::PipelinePushSlotBytes + 1u) * Graphics::PipelinePushSlotBytes; + require(result.pushConstants.size() <= slotEnd - pushCursorBytes, "stage push constants exceed the pipeline push constant block"); + stages.push_back({program.binary.stage, &result, result.pushConstants.empty() ? 0u : pushCursorBytes}); + pushCursorBytes += static_cast(result.pushConstants.size()); + } catch (...) { + TraceMaterializationFailure(trace.token, submission, packetOffset, "push-constant-validation", completedSubdraws, std::current_exception()); + throw; + } } if (stageDataVerified != 0) { @@ -433,37 +968,64 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe drawEntryCounters.dataStagesCompiled += dataStagesCompiled; drawEntryCounters.dataVerified += dataVerified; } - cacheDrawStages(stageEntries, drawHit, drawParameters, indirectCpu, programs, stageCaptures, vertexInfos, decodeReads, verifyHit, matched, fresh, drawKey, registerKey, decode, phaseTiming, reused, entry, shapeKey); + const bool shapeRefresh = Graphics::DrawPipelineEnabled(); + const auto refreshSeenTwice = [&] { + std::uint64_t data = shapeKey; + bool stored = false; + const auto mix = [&data](std::uint64_t value) { + data ^= value + 0x9e3779b97f4a7c15ull + (data << 6u) + (data >> 2u); + }; + for (std::size_t i = 0; i < programs.size(); ++i) { + const auto& capture = stageCaptures[i]; + if (reused[i] || capture.compiled == nullptr || !CacheableResult(*capture.compiled)) continue; + stored = true; + const auto& words = programs[i].userData; + for (std::size_t w = 0; w < words.size(); ++w) { + if (w >= 64u || (capture.userDataMask >> w & 1u) != 0) mix((static_cast(i) << 40u) ^ (static_cast(w) << 32u) ^ words[w]); + } + } + if (!stored) return false; + static std::array, 4096> lastData{}; + return lastData[shapeKey % lastData.size()].exchange(data, std::memory_order_relaxed) == data; + }; + const bool shapeNeedsEntry = shapeRefresh && registerKey && shapeKey != 0 && drawDataHits() && !drawParameters.indirect && dataEntry != nullptr && dataStagesCompiled != 0 && refreshSeenTwice(); + cacheDrawStages(stageEntries, drawHit, drawParameters, indirectCpu, programs, stageCaptures, vertexInfos, decodeReads, verifyHit, matched, stageData, fresh, drawKey, registerKey, decode, phaseTiming, reused, entry, shapeKey, shapeNeedsEntry); timing.Mark("shader_compile_and_link"); for (const auto& reads : decodeReads) { for (const auto& read : reads) memory.push_back({read.address, read.Bytes()}); } - if (pipelined && !deferredLabels().labels.empty()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Labels); - if (recordQueuedLabelsAfterCapture(submission.queue, memory)) return draw(queue, packet, submission, rejected); + if (tail.queue != nullptr && pipelined && !deferredLabels().labels.empty()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Labels); + if (recordQueuedLabelsAfterCapture(submission.queue, memory)) return draw(*tail.queue, packet, submission, packetOffset, rejected); bool rectListBuilt = false; std::size_t rectIndex = 0; const auto buildRectList = [&] { - phaseTiming.Phase(DrawRowVectors); - require(programs.size() == 2 && programResults[0] != nullptr && programResults[1] != nullptr, "rect-list requires vertex and fragment programs"); - auto rectangle = DrawRectangle(*programs[0].snapshot, programs[1].snapshot, programResults[0]->variantId, programResults[1]->variantId, localDevice->Target()); - auto& control = graphics.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT ? rectangle.lastVertexControl : rectangle.control; - if (rectListBuilt) { - results[rectIndex] = std::move(control); - results[rectIndex + 1] = std::move(rectangle.evaluation); + try { + phaseTiming.Phase(DrawRowVectors); + require(programs.size() == 2 && programResults[0] != nullptr && programResults[1] != nullptr, "rect-list requires vertex and fragment programs"); + auto rectangle = DrawRectangle(*programs[0].snapshot, programs[1].snapshot, programResults[0]->variantId, programResults[1]->variantId, localDevice->Target()); + auto& control = graphics.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT ? rectangle.lastVertexControl : rectangle.control; + if (rectListBuilt) { + results[rectIndex] = std::move(control); + results[rectIndex + 1] = std::move(rectangle.evaluation); + phaseTiming.Phase(DrawRowRectList); + return; + } + require(stages.size() == 2, "rect-list requires vertex and fragment programs"); + results.reserve(programs.size() + 2u); + rectIndex = results.size(); + results.push_back(std::move(control)); + results.push_back(std::move(rectangle.evaluation)); + stages.insert(stages.begin() + 1, {{Stage::TessellationControl, &results[rectIndex], 0}, {Stage::TessellationEvaluation, &results[rectIndex + 1], 0}}); + rectListBuilt = true; phaseTiming.Phase(DrawRowRectList); - return; + } catch (...) { + TraceMaterializationFailure(trace.token, submission, packetOffset, "rect-list", completedSubdraws, std::current_exception()); + throw; } - require(stages.size() == 2, "rect-list requires vertex and fragment programs"); - rectIndex = results.size(); - results.push_back(std::move(control)); - results.push_back(std::move(rectangle.evaluation)); - stages.insert(stages.begin() + 1, {{Stage::TessellationControl, &results[rectIndex], 0}, {Stage::TessellationEvaluation, &results[rectIndex + 1], 0}}); - rectListBuilt = true; - phaseTiming.Phase(DrawRowRectList); }; if (graphics.rectList) buildRectList(); if (!programs.empty() && programResults.back() != nullptr && programResults.back()->barycentricEmulation.active && graphics.stages.path == Graphics::ShaderPath::Geometry) { @@ -473,21 +1035,22 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe if (programResults[0]->meshBarycentrics != meshBarycentrics) { const auto pushBytes = programResults[0]->pushConstants.size(); const auto exports = programResults[0]->parameterExports; - compiledStages.push_back(materializeDrawStage(0, pushOffsets[0], queue, submission, programs, graphics, pixel, vertexInfos, memory, linked, drawParameters, localDevice, *shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected, meshBarycentrics)); + compiledStages.push_back(materializeDrawStage(0, pushOffsets[0], queue, submission, packetOffset, traceToken, programs, graphics, pixel, vertexInfos, memory, linked, drawParameters, localDevice, *shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected, meshBarycentrics)); if (!rejected.empty()) return DrawVerdict::Rejected; programResults[0] = compiledStages.back().get(); stages[0].program = programResults[0]; require(programResults[0]->pushConstants.size() == pushBytes && programResults[0]->parameterExports == exports, "pairing the mesh program with its pixel shader changed the push constants or the parameter exports the pixel shader was built for"); } require(programResults[0]->meshBarycentrics == meshBarycentrics, "the mesh program was compiled for a different barycentric layout than its pixel shader"); - } else if (!programs.empty() && programResults.back() != nullptr && programResults.back()->barycentricEmulation.active) { - require(!graphics.rectList && graphics.stages.path == Graphics::ShaderPath::Vertex && programs.size() == 2 && programResults[0] != nullptr && stages.size() == 2, "a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric needs a vertex shader before it"); + } else if (!graphics.rectList && !programs.empty() && programResults.back() != nullptr && programResults.back()->barycentricEmulation.active) { + require(graphics.stages.path == Graphics::ShaderPath::Vertex && programs.size() == 2 && programResults[0] != nullptr && stages.size() == 2, "a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric needs a vertex shader before it"); const auto geometryLimits = localDevice->GeometryLimits(); + results.reserve(programs.size() + 2u); if (geometryLimits || !localDevice->Target().tessellation) { results.push_back(ShaderRecompiler::BuildBarycentricGeometryShader(*programResults[0], *programResults[1], localDevice->Target(), geometryLimits)); stages.insert(stages.begin() + 1, Graphics::CompiledShader{Stage::Geometry, &results.back(), 0}); } else { - require(graphics.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST && !graphics.primitiveRestart, "a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric or geometry shaders needs a triangle list without primitive restart"); + require((graphics.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || graphics.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP) && !graphics.primitiveRestart, "a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric or geometry shaders needs a triangle list or strip without primitive restart"); auto patches = ShaderRecompiler::BuildBarycentricTessellationShaders(*programResults[0], *programResults[1], localDevice->Target(), localDevice->MaxFragmentInputComponents(), graphics.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT ? 2u : 0u); const auto first = results.size(); results.push_back(std::move(patches.control)); @@ -504,6 +1067,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe snapshot(); timing.Mark("post_compile_prepare"); const auto lockForDraw = [&] { + require(tail.queue != nullptr, "the draw finisher reached a draw it cannot commit through the pipeline"); if (gpuLock.owns_lock()) return; phaseTiming.Phase(DrawRowVectors); GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw); @@ -523,6 +1087,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe if (drawParameters.indirect && indirectCpu) { const auto indirect = *drawParameters.indirect; + results.reserve(programs.size() + 2u); for (std::size_t i = 0; i < programs.size(); ++i) { if (programResults[i] == nullptr) continue; resultIndex[i] = results.size(); @@ -541,7 +1106,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe const auto baseVertexWords = UserDataWords(programs, roles, indirect.baseVertexLocation); const auto startInstanceWords = UserDataWords(programs, roles, indirect.startInstanceLocation); const auto drawIndexWords = indirect.drawIndexEnabled ? UserDataWords(programs, roles, indirect.drawIndexLocation) : std::vector>{}; - const auto drawable = [&](const Pm4::DrawArguments& arguments) { return arguments.count != 0 && arguments.instances != 0 && (!drawParameters.indexed || arguments.firstVertexOrIndex < drawParameters.indexCount); }; + const auto drawable = [&](const Pm4::DrawArguments& arguments) { return arguments.count != 0 && arguments.instances != 0 && (!drawParameters.indexed || !indirect.indexLimitKnown || arguments.firstVertexOrIndex < drawParameters.indexCount); }; std::size_t lastDrawable = records.size(); for (std::size_t record = 0; record < records.size(); ++record) { if (drawable(records[record])) lastDrawable = record; @@ -567,10 +1132,16 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe } Pm4::DrawParameters direct{0, arguments.count, 0, arguments.instances, drawParameters.flags, drawParameters.indexed, 0, 0}; if (drawParameters.indexed) { - - if (arguments.firstVertexOrIndex >= drawParameters.indexCount) continue; + if (indirect.indexLimitKnown) { + if (arguments.firstVertexOrIndex >= drawParameters.indexCount) continue; + direct.indexCount = std::min(arguments.count, drawParameters.indexCount - arguments.firstVertexOrIndex); + } else { + const auto end = static_cast(arguments.firstVertexOrIndex) + arguments.count; + require(end <= std::numeric_limits::max(), "indirect draw index range overflow"); + require(end * drawParameters.indexSize <= std::numeric_limits::max() - drawParameters.indexAddress, "indirect draw index range address overflow"); + direct.indexCount = arguments.count; + } direct.indexAddress = drawParameters.indexAddress + static_cast(arguments.firstVertexOrIndex) * drawParameters.indexSize; - direct.indexCount = std::min(arguments.count, drawParameters.indexCount - arguments.firstVertexOrIndex); direct.indexSize = drawParameters.indexSize; } else { direct.firstVertex = indirect.indxOffset; @@ -583,7 +1154,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe auto& result = results[resultIndex[programIndex]]; const auto pushBytes = result.pushConstants.size(); decodeVertexInfo(programIndex); - result = *materializeDrawStage(programIndex, pushOffsets[programIndex], queue, submission, programs, graphics, pixel, vertexInfos, memory, linked, programs[programIndex].binary.stage == Stage::TessellationControl || programs[programIndex].binary.stage == Stage::TessellationEvaluation ? direct : drawParameters, localDevice, *shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected, meshBarycentrics); + result = *materializeDrawStage(programIndex, pushOffsets[programIndex], queue, submission, packetOffset, trace.token, programs, graphics, pixel, vertexInfos, memory, linked, programs[programIndex].binary.stage == Stage::TessellationControl || programs[programIndex].binary.stage == Stage::TessellationEvaluation ? direct : drawParameters, localDevice, *shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected, meshBarycentrics); if (!rejected.empty()) return DrawVerdict::Rejected; require(result.pushConstants.size() == pushBytes, "patched program changed its push constant layout"); } @@ -598,9 +1169,9 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe phaseTiming.Phase(DrawRowVectors); auto writes = drawWriteRanges(graphics, stages); const auto commitQueue = submission.queue; - const auto commitPacket = (packet[0] >> 8u) & 0xffu; + const auto commitPacket = tail.packetOpcode; const auto epoch = DrawPipeline::EpochToken().load(std::memory_order_relaxed); - auto commit = [this, localDevice = std::move(localDevice), decode = std::move(decode), direct, stages = std::move(stages), snapshots = std::move(snapshots), commitQueue, commitPacket, epoch, programs = std::move(programs), 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)] { + auto commit = [this, localDevice = std::move(localDevice), decode = std::move(decode), direct, stages = std::move(stages), snapshots = std::move(snapshots), commitQueue, commitPacket, epoch, 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)] { DrawPipeline::FollowEpoch(epoch); GuestMemory::SetCurrentPacket(commitPacket, commitQueue); commitDraw(localDevice, commitQueue, decode->state, direct, stages, snapshots, nullptr, {}, 0); @@ -615,7 +1186,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe lockForDraw(); noteDrawWriters(stages, submission.queue); phaseTiming.Phase(DrawRowVectors); - localDevice->Draw(graphics, direct, stages, snapshots); + localDevice->Draw(graphics, direct, stages, snapshots, nullptr, tracePtr); phaseTiming.Phase(DrawRowGraphics); } timing.Mark("draw_and_resource_release"); @@ -639,20 +1210,20 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe return DrawVerdict::Rejected; } } - if (pipelined && !drawParameters.indirect && deferredLabels().labels.empty()) { + if (pipelined && !drawParameters.indirect && (tail.queue == nullptr || deferredLabels().labels.empty())) { phaseTiming.Phase(DrawRowVectors); auto writes = drawWriteRanges(graphics, stages); const auto commitQueue = submission.queue; - const auto commitPacket = (packet[0] >> 8u) & 0xffu; - const auto epoch = DrawPipeline::EpochToken().load(std::memory_order_relaxed); + 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, programs = std::move(programs), 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)] { + 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 { DrawPipeline::FollowEpoch(epoch); GuestMemory::SetCurrentPacket(commitPacket, commitQueue); - commitDraw(localDevice, commitQueue, decode->state, drawParameters, stages, snapshots, recipe, recipeStages, drawKey); + commitDraw(localDevice, commitQueue, decode->state, drawParameters, stages, snapshots, recipe, recipeStages, drawKey, trace); }; auto held = std::make_shared(std::move(commit)); - DrawPipeline::Queue0().Enqueue([held] { (*held)(); }, std::move(writes), 0, {}, std::move(reads)); + DrawPipeline::Queue0().Enqueue([held] { (*held)(); }, std::move(writes), 0, {}, std::move(reads), true); phaseTiming.Phase(DrawRowLockWait); timing.Mark("draw_enqueued"); return drawn(); @@ -662,7 +1233,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe noteDrawWriters(stages, submission.queue); phaseTiming.Phase(DrawRowVectors); if (recipe != nullptr) { - if (localDevice->DrawFromRecipe(graphics, drawParameters, stages, snapshots, recipe) == RecipeOutcome::Recorded) { + if (localDevice->DrawFromRecipe(graphics, drawParameters, stages, snapshots, recipe, tracePtr) == RecipeOutcome::Recorded) { phaseTiming.Phase(DrawRowGraphics); timing.Mark("draw_and_resource_release"); return drawn(); @@ -671,14 +1242,15 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe VulkanDevice::NoteRecipe(VulkanDevice::RecipeEvent::Restart, VulkanDevice::RecipeKind::Draw); } std::shared_ptr built; - localDevice->Draw(graphics, drawParameters, stages, snapshots, recipeStages.empty() ? nullptr : &built); + localDevice->Draw(graphics, drawParameters, stages, snapshots, recipeStages.empty() ? nullptr : &built, tracePtr); 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) { +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) { + const auto* tracePtr = trace.token != 0 ? &trace : nullptr; GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw); std::lock_guard gpuLock(GuestMemory::GpuMutex()); if (auto current = device.Load(); current != nullptr && current != localDevice) localDevice = std::move(current); @@ -692,11 +1264,11 @@ void Driver::commitDraw(std::shared_ptr localDevice, std::uint32_t recordLabelsForPacket(localDevice.get(), queue); noteDrawWriters(stages, queue); if (recipe != nullptr) { - if (localDevice->DrawFromRecipe(graphics, drawParameters, stages, snapshots, recipe) == RecipeOutcome::Recorded) return; + if (localDevice->DrawFromRecipe(graphics, drawParameters, stages, snapshots, recipe, tracePtr) == RecipeOutcome::Recorded) return; VulkanDevice::NoteRecipe(VulkanDevice::RecipeEvent::Restart, VulkanDevice::RecipeKind::Draw); } std::shared_ptr built; - localDevice->Draw(graphics, drawParameters, stages, snapshots, recipeStages.empty() ? nullptr : &built); + localDevice->Draw(graphics, drawParameters, stages, snapshots, recipeStages.empty() ? nullptr : &built, tracePtr); 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 1d76a6a338..8d05f4693f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp @@ -123,10 +123,24 @@ bool Driver::admitDrawKey(std::uint64_t key, std::uint64_t shape) { return drawShapeSightings->SeenBefore(shape); } -void Driver::insertDrawEntry(std::uint64_t key, std::vector>& fresh, std::shared_ptr decode, std::uint64_t shape) { +void Driver::insertDrawEntry(std::uint64_t key, std::vector>& fresh, std::shared_ptr decode, std::uint64_t shape, bool replaceShape) { std::lock_guard cacheLock(drawCacheMutex); auto& counters = drawEntryCounters; - ++counters.inserts; + if (replaceShape && shape != 0) { + ++counters.shapeUpdates; + if (const auto shapeOf = drawShapes.find(shape); shapeOf != drawShapes.end() && shapeOf->second != key) { + if (const auto previous = drawCache.find(shapeOf->second); previous != drawCache.end()) { + for (const auto& variants : previous->second->stages) { + for (const auto& variant : variants) accountDrawVariant(*variant, false); + } + drawOrder.erase(previous->second->order); + drawCache.erase(previous); + } + drawShapes.erase(shapeOf); + } + } else { + ++counters.inserts; + } const auto found = drawCache.find(key); auto replacement = std::make_shared(); replacement->stages.resize(fresh.size()); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCacheStatistics.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCacheStatistics.cpp index 3e1094adfb..b1f3dc2a02 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCacheStatistics.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCacheStatistics.cpp @@ -62,7 +62,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 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.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), %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] 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 c13d57f4a8..a6f6b5ebd9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" @@ -11,10 +12,55 @@ #include #include #include +#include +#include namespace AgcDriver::DriverDetail { -std::shared_ptr Driver::materializeDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, const std::vector& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector>& vertexInfos, std::vector& memory, const std::vector& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr& localDevice, ShaderMemory& shaderMemory, std::vector& stageCaptures, std::vector& recompiled, bool drawHit, const std::vector>& matched, const std::vector>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array& phaseMs, std::string& rejected, const std::optional& meshBarycentrics) try { +namespace { + +void TraceMaterializationFailure(std::uint64_t token, const Submission& submission, std::size_t packetOffset, std::size_t stage, const char* phase, const std::exception_ptr& failure) { + if (token == 0) return; + const char* type = "unknown"; + std::string reason = "unknown exception"; + try { + if (failure != nullptr) std::rethrow_exception(failure); + } catch (const std::exception& error) { + type = typeid(error).name(); + reason = error.what(); + } catch (...) { + } + try { + AgcDriver::CaptureTrace::Log("draw-materialization-failed token=%llu submission=%llu queue=%x offset=%zu stage=%zu phase=%s exception=%s reason=%.256s", + static_cast(token), static_cast(submission.serial), submission.queue, packetOffset, stage, phase, type, reason.c_str()); + } catch (...) { + } +} + +void TraceShaderRequestFailure(std::uint64_t token, const Submission& submission, std::size_t packetOffset, const DrawProgram& program, const std::exception_ptr& failure) noexcept { + if (token == 0) return; + try { + std::string reason = "unknown exception"; + const char* type = "unknown"; + try { + if (failure != nullptr) std::rethrow_exception(failure); + } catch (const std::exception& error) { + type = typeid(error).name(); + reason = error.what(); + } catch (...) { + } + AgcDriver::CaptureTrace::LogBestEffort("draw-shader-request-failed token=%llu submission=%llu queue=%x offset=%zu snapshot=0x%llx code=%llx codeOffset=%zu stage=%u type=%u reason=%.256s", + static_cast(token), static_cast(submission.serial), submission.queue, packetOffset, + static_cast(reinterpret_cast(program.snapshot.get())), + static_cast(program.snapshot->codeAddress), program.codeOffset, + static_cast(program.binary.stage), static_cast(program.snapshot->type), reason.c_str()); + } catch (...) { + } +} + +} + +std::shared_ptr Driver::materializeDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, std::size_t packetOffset, std::uint64_t traceToken, const std::vector& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector>& vertexInfos, std::vector& memory, const std::vector& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr& localDevice, ShaderMemory& shaderMemory, std::vector& stageCaptures, std::vector& recompiled, bool drawHit, const std::vector>& matched, const std::vector>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array& phaseMs, std::string& rejected, const std::optional& meshBarycentrics) try { PerformanceTimer timing("Shader.DrawStage"); using Stage = ShaderRecompiler::ShaderStage; phaseTiming.Phase(DrawRowVectors); @@ -28,7 +74,14 @@ std::shared_ptr Driver::materializeDraw ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}, program.binary.stage == Stage::Mesh ? meshBarycentrics : std::nullopt} }; const auto waitedBefore = traceCapSync() || profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0; - auto invocation = InvocationFor(*program.snapshot, program.codeOffset, request); + auto invocation = [&] { + try { + return InvocationFor(*program.snapshot, program.codeOffset, request); + } catch (...) { + TraceShaderRequestFailure(traceToken, submission, packetOffset, program, std::current_exception()); + throw; + } + }(); timing.Mark("prepared_invocation"); auto& stageCapture = stageCaptures[i]; stageCapture.forgetSerial = GuestMemory::ForgetSerial(); @@ -73,16 +126,16 @@ std::shared_ptr Driver::materializeDraw ++captures; phaseTiming.Phase(DrawRowCapture); - const auto waited = std::min(Graphics::Recorder::ThreadWaitedMs() - waitedBefore, phaseMs[DrawRowCapture]); - phaseMs[DrawRowCapture] -= waited; - phaseMs[DrawRowCaptureHookWaits] += waited; - } - if (dumpTarget != 0) { + const auto waited = std::min(Graphics::Recorder::ThreadWaitedMs() - waitedBefore, phaseMs[DrawRowCapture]); + phaseMs[DrawRowCapture] -= waited; + phaseMs[DrawRowCaptureHookWaits] += waited; + } + if (dumpTarget != 0) { const auto slot0 = (static_cast(readRegister(queue.context, 0x390)) << 40u) | (static_cast(readRegister(queue.context, 0x318)) << 8u); if ((graphics.hasColorTarget && graphics.color.address == dumpTarget) || slot0 == dumpTarget) static_cast(dumpRequest(program.binary.codeAddress, request)); - } - if (dumpSlot1 != 0) { + } + if (dumpSlot1 != 0) { const auto value = [&](std::uint32_t offset) -> std::uint64_t { const auto it = queue.context.find(offset); return it == queue.context.end() ? 0u : it->second; }; const auto slot1 = (value(0x391) << 40u) | (value(0x327) << 8u); if (slot1 == dumpSlot1) { @@ -94,17 +147,18 @@ std::shared_ptr Driver::materializeDraw std::fclose(file); } } - } + } - phaseTiming.Phase(DrawRowCapture); + phaseTiming.Phase(DrawRowCapture); timing.Mark("capture_resources"); try { stageCapture.compiled = invocation.Materialize(*capture); + stageCapture.capture = capture; } catch (const std::exception& error) { + TraceMaterializationFailure(traceToken, submission, packetOffset, i, "capture-or-materialize", std::current_exception()); throw named(error, request.context.memory); } - stageCapture.capture = capture; timing.Mark("materialize"); const auto walked = capture->userDataUse.asValue | capture->userDataUse.asBase; auto mask = walked == 0 ? ~std::uint64_t{0} : walked | stageCapture.compiled->userDataRegisterReads; @@ -152,8 +206,11 @@ void Driver::recordStageDataPositions(DispatchVariant& variant, const ShaderReco variant.dataSlots.resize(kept); } -void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawParameters& drawParameters, const std::optional& indirectCpu, const std::vector& programs, const std::vector& stageCaptures, const std::vector>& vertexInfos, const std::vector>& decodeReads, bool verifyHit, const std::vector>& matched, std::vector>& fresh, std::uint64_t drawKey, bool registerKey, const std::shared_ptr& decode, DrawPhaseTiming& phaseTiming, const std::vector& reused, const std::shared_ptr& entry, std::uint64_t shapeKey) { - if (useDrawEntries && !drawHit && !(drawParameters.indirect && indirectCpu) && (verifyHit || admitDrawKey(drawKey, registerKey && drawDataHits() ? shapeKey : 0))) { +void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawParameters& drawParameters, const std::optional& indirectCpu, const std::vector& programs, const std::vector& stageCaptures, const std::vector>& vertexInfos, const std::vector>& decodeReads, bool verifyHit, const std::vector>& matched, const std::vector& stageData, std::vector>& fresh, std::uint64_t drawKey, bool registerKey, const std::shared_ptr& decode, DrawPhaseTiming& phaseTiming, const std::vector& reused, const std::shared_ptr& entry, std::uint64_t shapeKey, bool shapeNeedsEntry) { + const bool cacheable = useDrawEntries && !drawHit && !(drawParameters.indirect && indirectCpu); + const bool admitted = cacheable && (verifyHit || admitDrawKey(drawKey, registerKey && drawDataHits() ? shapeKey : 0)); + const bool replaceShape = cacheable && !admitted && shapeNeedsEntry; + if (admitted || replaceShape) { phaseTiming.Phase(DrawRowVectors); std::uint64_t unstable = 0, mismatches = 0, differingSameRuns = 0, differingWords = 0, differingRunsChanged = 0; for (std::size_t i = 0; i < programs.size(); ++i) { @@ -189,17 +246,17 @@ void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawP ++differingRunsChanged; } } - const auto sameContent = [](const DispatchVariant& a, const DispatchVariant& b) { - const auto flatten = [](const DispatchVariant& source) { + const auto sameContent = [](const DispatchVariant& a, const std::vector& words, const DispatchVariant& b) { + const auto flatten = [](const DispatchVariant& source, const std::vector& values) { std::vector> flat; std::size_t word = 0; for (const auto& [begin, end] : source.runs) { - for (auto address = begin; address < end && word < source.words.size(); address += sizeof(std::uint32_t)) flat.emplace_back(address, source.words[word++]); + for (auto address = begin; address < end && word < values.size(); address += sizeof(std::uint32_t)) flat.emplace_back(address, values[word++]); } std::sort(flat.begin(), flat.end()); return flat; }; - return (a.runs == b.runs && a.words == b.words) || flatten(a) == flatten(b); + return (a.runs == b.runs && words == b.words) || flatten(a, words) == flatten(b, b.words); }; const auto sameForwardedWords = [](const ShaderRecompiler::RecompileResult& a, const ShaderRecompiler::RecompileResult& b, std::uint32_t hidden) { if (a.pushConstants.size() != b.pushConstants.size() || a.shaderDataUserWords != b.shaderDataUserWords) return false; @@ -208,10 +265,14 @@ void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawP } return true; }; - if (verifyHit && matched[i] != nullptr && (!sameContent(*matched[i], *variant) || !sameForwardedWords(*matched[i]->compiled, *variant->compiled, programs[i].hiddenUserWords))) { - ++mismatches; - static std::atomic reports{0}; - if (reports.fetch_add(1) < 20) std::fprintf(stderr, "[draw-cache] verify: stage %zu (program 0x%llx) of a hit captured differently: %zu runs / %zu words matched, %zu / %zu fresh\n", i, static_cast(programs[i].binary.codeAddress), matched[i]->runs.size(), matched[i]->words.size(), variant->runs.size(), variant->words.size()); + if (verifyHit && matched[i] != nullptr) { + const auto& words = stageData[i].compiled != nullptr ? stageData[i].words : matched[i]->words; + const auto& compiled = stageData[i].compiled != nullptr ? *stageData[i].compiled : *matched[i]->compiled; + if (!sameContent(*matched[i], words, *variant) || !sameForwardedWords(compiled, *variant->compiled, programs[i].hiddenUserWords)) { + ++mismatches; + static std::atomic reports{0}; + if (reports.fetch_add(1) < 20) std::fprintf(stderr, "[draw-cache] verify: stage %zu (program 0x%llx) of a hit captured differently: %zu runs / %zu words matched, %zu / %zu fresh\n", i, static_cast(programs[i].binary.codeAddress), matched[i]->runs.size(), words.size(), variant->runs.size(), variant->words.size()); + } } if (insertCompare()) { const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::DrawCache); @@ -222,7 +283,7 @@ void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawP } fresh[i] = std::move(variant); } - insertDrawEntry(drawKey, fresh, registerKey ? decode : nullptr, registerKey && drawDataHits() ? shapeKey : 0); + insertDrawEntry(drawKey, fresh, registerKey ? decode : nullptr, registerKey && drawDataHits() ? shapeKey : 0, replaceShape); std::uint64_t dataInserts = 0, dataPositions = 0; for (std::size_t i = 0; i < programs.size(); ++i) { if (fresh[i] == nullptr || reused[i] || fresh[i]->dataPositions.empty()) continue; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index 8208e0805d..05c6b4dfa2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include #include @@ -30,6 +31,25 @@ void ReadUserWords(const QueueState& queue, DrawProgram& program, bool staticAbi if ((program.binary.stage == ShaderRecompiler::ShaderStage::Vertex || program.binary.stage == ShaderRecompiler::ShaderStage::Fragment) && program.snapshot->header.empty() && program.snapshot->userDataTailRegister) program.tailContinuation = ReadUserDataTailContinuation(program.binary.code, program.firstUserSgpr, program.userData); } +ShaderRecompiler::ShaderVertexStageInfo DecodeOrderedVertexStageInfo(std::span header, std::uint64_t headerAddress, std::span userData, std::vector& reads, std::uint32_t hiddenUserWords) { + const auto decode = [&] { + reads.clear(); + return Graphics::DecodeVertexStageInfo(header, headerAddress, userData, &reads, false, hiddenUserWords); + }; + if (!DrawPipeline::Active()) return decode(); + auto& queue = DrawPipeline::Queue0(); + const auto watch = queue.WatchWrites(); + auto info = decode(); + if (watch.items == 0 && !queue.Busy() && queue.Retired() == watch.retired) return info; + std::vector ranges; + ranges.reserve(reads.size()); + for (const auto& read : reads) ranges.emplace_back(read.address, read.address + read.size); + if (!queue.WrittenSince(watch, ranges)) return info; + queue.NoteDecodeDrain(); + queue.Drain(DrawPipeline::DrainReason::Capture); + return decode(); +} + void InitializeMerged(const QueueState& queue, DrawProgram& program, std::uint32_t pointerBase, bool pointerRequired, bool staticAbi) { program.merged = true; program.mergedPointer = pointerBase; @@ -159,12 +179,15 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su auto product = std::make_shared(); product->state = Graphics::DecodeState(queue, VertexWorkgroupRequired(queue, *submission.shaders)); DecodeGraphicsPrograms(*product, queue, *submission.shaders, false, true); + product->state.depthCopySource = std::ranges::any_of(product->programs, [](const DrawProgram& program) { return program.binary.codeAddress == DepthCopyPixelProgramAddress(true); }); product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product->state), Graphics::PixelProgramSkipped(queue)); Graphics::NormalizeUnusedViewportDepth(product->state, product->pixel); Graphics::ApplyDepthRemap(context, product->state); + Graphics::ApplyDepthBoundsEmulation(context, product->state, product->pixel); product->pixel.targetExportPacking = Graphics::ExportPackings(product->state); product->pixel.dualSourceBlend = product->state.dualSourceBlend; product->pixel.reversedBlend = product->state.reversedBlend; + product->pixel.targetMerge = Graphics::ExportTargetMerge(product->state); product->pixel.alphaToCoverage = product->state.alphaToCoverage; product->pixel.quadPixelMask = Graphics::QuadPixelMask(product->state); if (product->state.sampleLocationGrid) { 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 6bfa9b1fb4..ec65e9734e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include +#include namespace AgcDriver::DriverDetail { @@ -51,6 +52,10 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr(vertex.pointSize)); + mix(std::bit_cast(vertex.pointSizeMin)); + mix(std::bit_cast(vertex.pointSizeMax)); for (std::uint32_t r = 0; r < vertex.resourcesNum; ++r) { for (const auto field : vertex.resources[r].fields) mix(field); const auto& destination = vertex.resourcesDst[r]; @@ -74,6 +79,12 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr(value)); mix(pixel.dualSourceBlend); mix(pixel.reversedBlend); + mix(pixel.targetMerge.target); + mix(pixel.targetMerge.mask); + mix(pixel.targetMerge.samples); + for (const auto word : pixel.targetMerge.descriptor) mix(word); + mix(pixel.targetMerge.depthStencilWrites); + mix(pixel.targetMerge.depthStencilTests); mix(pixel.alphaToCoverage); mix(static_cast(pixel.rasterPrimitive)); mix(pixel.ditheredAlphaToCoverage); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp index 9ba7b4a1db..a34ffbd972 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp @@ -3,8 +3,13 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp" +#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" #include +#include +#include +#include #include #include #include @@ -29,6 +34,212 @@ std::atomic heldReadHits{0}; } +namespace { +thread_local bool finisherThread = false; +thread_local bool finisherProducer = false; +} + +DrawFinisher& DrawFinisher::Get() { + static DrawFinisher* finisher = new DrawFinisher(); + return *finisher; +} + +bool DrawFinisher::Enabled() { + return Graphics::DrawPipelineEnabled(); +} + +bool DrawPipeline::MetadataPassesPipelined() { + return Graphics::DrawPipelineEnabled(); +} + +namespace { +std::atomic pipelinedMetadataPasses{0}; +} + +std::uint64_t DrawPipeline::PipelinedMetadataPasses() { + return pipelinedMetadataPasses.load(std::memory_order_relaxed); +} + +void DrawPipeline::CountPipelinedMetadataPass() { + pipelinedMetadataPasses.fetch_add(1, std::memory_order_relaxed); +} + +bool DrawFinisher::Overlap() { + return Graphics::DrawPipelineEnabled(); +} + +bool DrawFinisher::PendingOverlaps(std::uint64_t begin, std::uint64_t end) { + if (!Busy() || !finisherProducer) return false; + std::lock_guard lock(mutex); + for (const auto& held : pending) { + if (held.unknown) return true; + for (const auto& [first, last] : held.writes) { + if (begin < last && first < end) return true; + } + } + return false; +} + +DrawFinisher::LabelLookup DrawFinisher::PendingLabel(std::uint64_t address, std::size_t bytes, std::uint64_t& value) { + if (!Busy() || !finisherProducer) return LabelLookup::NotHeld; + const auto end = address + bytes; + std::lock_guard lock(mutex); + for (auto held = pending.rbegin(); held != pending.rend(); ++held) { + if (held->unknown) return LabelLookup::Unknown; + const bool overlaps = std::any_of(held->writes.begin(), held->writes.end(), [&](const Range& range) { return address < range.second && range.first < end; }); + if (!overlaps) continue; + if (held->labelBytes.empty() || address < held->labelAddress || end > held->labelAddress + held->labelBytes.size()) return LabelLookup::Unknown; + value = 0; + std::memcpy(&value, held->labelBytes.data() + (address - held->labelAddress), bytes); + return LabelLookup::Value; + } + return LabelLookup::NotHeld; +} + +bool DrawFinisher::PendingOverlapsAny(std::span ranges) { + if (!Busy() || !finisherProducer) return false; + std::lock_guard lock(mutex); + for (const auto& held : pending) { + if (held.unknown) return true; + for (const auto& [first, last] : held.writes) { + for (const auto& [begin, end] : ranges) { + if (begin < last && first < end) return true; + } + } + } + return false; +} + +bool DrawFinisher::PendingKey(std::uint64_t key, std::uint64_t shape) { + if (!Busy() || !finisherProducer) return false; + std::lock_guard lock(mutex); + return std::any_of(pending.begin(), pending.end(), [&](const Pending& held) { return held.key == key || (shape != 0 && held.shape == shape); }); +} + +namespace { +std::array, static_cast(DrawFinisher::DrainSite::Count)> finisherDrains{}, finisherSkips{}; +} + +void DrawFinisher::NoteDrain(DrainSite site, bool drained) { + (drained ? finisherDrains : finisherSkips)[static_cast(site)].fetch_add(1, std::memory_order_relaxed); +} + +namespace { +std::array, 257> drainedOpcodes{}, drainedBeside{}; +} + +void DrawFinisher::NoteDrainedOpcode(std::uint32_t opcode, bool besideCommits) { + drainedOpcodes[std::min(opcode, 256u)].fetch_add(1, std::memory_order_relaxed); + if (besideCommits) drainedBeside[std::min(opcode, 256u)].fetch_add(1, std::memory_order_relaxed); +} + +std::string DrawFinisher::DrainText() { + constexpr std::array(DrainSite::Count)> names{"lookup", "reads", "packet", "kept draw"}; + std::string text = "; worker drains of the finisher (drained/skipped):"; + for (std::size_t i = 0; i < names.size(); ++i) { + char item[64]; + std::snprintf(item, sizeof(item), " %s %llu/%llu", names[i], static_cast(finisherDrains[i].exchange(0)), static_cast(finisherSkips[i].exchange(0))); + text += item; + } + text += "; drained by opcode (of them beside commits):"; + for (int shown = 0; shown < 6; ++shown) { + std::size_t top = 0; + for (std::size_t i = 1; i < drainedOpcodes.size(); ++i) { + if (drainedOpcodes[i].load() > drainedOpcodes[top].load()) top = i; + } + const auto count = drainedOpcodes[top].exchange(0); + if (count == 0) break; + char item[48]; + std::snprintf(item, sizeof(item), " 0x%02zx x%llu (%llu)", top, static_cast(count), static_cast(drainedBeside[top].exchange(0))); + text += item; + } + return text; +} + +bool DrawFinisher::OnFinisherThread() { + return finisherThread; +} + +void DrawFinisher::Enqueue(Job job) { + Enqueue(std::move(job), Pending{}); +} + +void DrawFinisher::Enqueue(Job job, Pending held) { + finisherProducer = true; + constexpr std::size_t depth = 16; + struct ReclaimStorage {}; + auto& reclaim = HostThreadLocal, ReclaimStorage>(); + reclaim.clear(); + std::unique_lock lock(mutex); + if (!ran.empty()) std::swap(ran, reclaim); + if (failure != nullptr) std::rethrow_exception(std::exchange(failure, nullptr)); + if (jobs.size() >= depth && GuestMemory::GpuMutex().HeldByThisThread()) throw std::runtime_error("draw finisher full while the enqueuing thread holds the GPU lock"); + idle.wait(lock, [&] { return jobs.size() < depth; }); + jobs.push_back(std::move(job)); + pending.push_back(std::move(held)); + outstanding.fetch_add(1, std::memory_order_release); + if (!running) { + running = true; + thread = std::thread([this] { + finisherThread = true; + run(); + }); + } + if (sleeping) wake.notify_one(); +} + +void DrawFinisher::run() { + PerformanceTrace::BindThread("draw finisher"); + for (;;) { + Job* job = nullptr; + bool failed = false; + SpinUntil(HandoffSpinWindow(), [&] { return outstanding.load(std::memory_order_acquire) != 0; }); + { + std::unique_lock lock(mutex); + sleeping = true; + wake.wait(lock, [&] { return !jobs.empty(); }); + sleeping = false; + job = &jobs.front(); + failed = failure != nullptr; + } + std::exception_ptr error; + try { + if (!failed) (*job)(); + } catch (...) { + error = std::current_exception(); + } + std::lock_guard lock(mutex); + if (error != nullptr && failure == nullptr) failure = error; + ran.push_back(std::move(jobs.front())); + jobs.pop_front(); + pending.pop_front(); + outstanding.fetch_sub(1, std::memory_order_release); + idle.notify_all(); + } +} + +void DrawFinisher::Settle() { + if (!Busy() || OnFinisherThread()) return; + if (GuestMemory::GpuMutex().HeldByThisThread()) throw std::runtime_error("draw finisher settle under the GPU lock"); + std::unique_lock lock(mutex); + idle.wait(lock, [&] { return jobs.empty(); }); +} + +void DrawFinisher::Drain() { + if (OnFinisherThread()) return; + if (Busy() && GuestMemory::GpuMutex().HeldByThisThread()) throw std::runtime_error("draw finisher drain under the GPU lock"); + SpinUntil(HandoffSpinWindow() * 2, [&] { return outstanding.load(std::memory_order_acquire) == 0; }); + std::unique_lock lock(mutex); + if (!jobs.empty()) idle.wait(lock, [&] { return jobs.empty(); }); + if (failure != nullptr) std::rethrow_exception(std::exchange(failure, nullptr)); +} + +void DrawFinisher::ReleaseRan() { + std::vector done; + std::lock_guard lock(mutex); + std::swap(done, ran); +} + DrawPipeline& DrawPipeline::Queue0() { static DrawPipeline* pipeline = new DrawPipeline(); return *pipeline; @@ -78,9 +289,16 @@ void DrawPipeline::rethrowFailure() { std::rethrow_exception(error); } -void DrawPipeline::Enqueue(Commit commit, std::vector writes, std::uint64_t labelAddress, std::vector labelBytes, std::vector reads) { +void DrawPipeline::Enqueue(Commit commit, std::vector writes, std::uint64_t labelAddress, std::vector labelBytes, std::vector reads, bool draw) { + if (DrawFinisher::Busy() && !DrawFinisher::OnFinisherThread()) DrawFinisher::Get().Drain(); static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + struct ReclaimStorage {}; + auto& reclaim = HostThreadLocal, ReclaimStorage>(); + reclaim.clear(); + const auto lockStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; std::unique_lock lock(mutex); + if (!retired.empty()) std::swap(retired, reclaim); + if (profile) lockWaitNs += elapsedNs(lockStart); rethrowFailure(); if (closing) { lock.unlock(); @@ -90,23 +308,33 @@ void DrawPipeline::Enqueue(Commit commit, std::vector writes, std::uint64 if (items.size() >= Depth()) { if (GuestMemory::GpuMutex().HeldByThisThread()) throw std::runtime_error("draw pipeline full while the enqueuing thread holds the GPU lock"); const auto start = std::chrono::steady_clock::now(); + resumeAt = Depth() - 1; ++idleWaiters; - idle.wait(lock, [&] { return items.size() < Depth(); }); + idle.wait(lock, [&] { return items.size() <= resumeAt; }); --idleWaiters; + resumeAt = 0; fullWaitNs += elapsedNs(start); rethrowFailure(); } depthSum += items.size(); ++enqueued; heldReadRanges.fetch_add(reads.size(), std::memory_order_relaxed); - items.push_back({std::move(commit), std::move(writes), labelAddress, std::move(labelBytes), std::move(reads)}); + countWrites(writes, true); + items.push_back({std::move(commit), std::move(writes), labelAddress, std::move(labelBytes), std::move(reads), draw}); outstanding.fetch_add(1, std::memory_order_release); if (!thread.joinable()) { - thread = std::thread([this] { run(); }); + thread = std::thread([this] { + GuestMemory::MarkProofThread(); + run(); + }); runningCommitters.fetch_add(1, std::memory_order_acq_rel); } - if (committerWaiting) wake.notify_one(); - if (!profile) return; + if (committerWaiting) { + ++wakes; + wake.notify_one(); + } + static const bool stats = std::getenv("APS5_PIPELINE_STATS") != nullptr; + if (!profile && !stats) return; const auto now = std::chrono::steady_clock::now(); if (now - lastReport >= std::chrono::seconds(10)) report(now); } @@ -132,20 +360,24 @@ void DrawPipeline::report(std::chrono::steady_clock::time_point now) { const auto perCommit = commits == 0 ? 0.0 : static_cast(commitNs) / 1e3 / static_cast(commits); const auto perDraw = enqueued == 0 ? 0.0 : static_cast(fullWaitNs) / 1e3 / static_cast(enqueued); const auto queued = enqueued == 0 ? 0.0 : static_cast(depthSum) / static_cast(enqueued); - char reads[64]; - std::snprintf(reads, sizeof(reads), " %llu read ranges, %llu stores held", static_cast(heldReadRanges.load(std::memory_order_relaxed)), static_cast(heldReadHits.exchange(0, std::memory_order_relaxed))); + const auto decodeDrainCount = decodeDrains.load(std::memory_order_relaxed); + char reads[96]; + std::snprintf(reads, sizeof(reads), " %llu read ranges, %llu stores held, %llu decode drains", static_cast(heldReadRanges.load(std::memory_order_relaxed)), static_cast(heldReadHits.exchange(0, std::memory_order_relaxed)), static_cast(decodeDrainCount - reportedDecodeDrains)); + reportedDecodeDrains = decodeDrainCount; drainText += reads; - std::fprintf(stderr, "[draw] pipelined (10 s, depth %zu): %llu commits at %.1f us each on the committer, %llu failed; %llu enqueued behind %.2f on average, waiting %.1f us per draw for room;%s; drains:%s\n", Depth(), static_cast(commits), perCommit, static_cast(commitErrors), static_cast(enqueued), queued, perDraw, reads, drainText.c_str()); - commits = commitNs = commitErrors = enqueued = fullWaitNs = depthSum = 0; + AgcDriver::ProfilePrint_nid_no_patch("[draw] pipelined (10 s, depth %zu): %llu commits at %.1f us each on the committer, %llu failed; %llu enqueued behind %.2f on average, waiting %.1f us per draw for room, %.2f us for the lock; %llu committer wakes;%s; drains:%s\n", Depth(), static_cast(commits), perCommit, static_cast(commitErrors), static_cast(enqueued), queued, perDraw, enqueued == 0 ? 0.0 : static_cast(lockWaitNs) / 1e3 / static_cast(enqueued), static_cast(wakes), reads, drainText.c_str()); + commits = commitNs = commitErrors = enqueued = fullWaitNs = depthSum = wakes = lockWaitNs = 0; drains = {}; drainWaitNs = {}; } + void DrawPipeline::run() { PerformanceTrace::BindThread("draw committer"); StartCommitterSampler(); for (;;) { Commit* commit = nullptr; + bool isDraw = false; SpinUntil(HandoffSpinWindow(), [&] { return outstanding.load(std::memory_order_acquire) != 0; }); { std::unique_lock lock(mutex); @@ -154,6 +386,7 @@ void DrawPipeline::run() { committerWaiting = false; if (items.empty()) break; commit = stopped ? nullptr : &items.front().commit; + isDraw = items.front().draw; } const auto start = std::chrono::steady_clock::now(); std::exception_ptr fatal; @@ -161,6 +394,7 @@ void DrawPipeline::run() { bool shutdown = false; try { if (commit != nullptr) (*commit)(); + if (!isDraw) GuestMemory::BreakProofs(); } catch (const std::exception&) { failed = true; fatal = std::current_exception(); @@ -176,13 +410,16 @@ void DrawPipeline::run() { if (!shutdown) Driver::Get().ReportFailure(fatal); } std::lock_guard lock(mutex); + countWrites(items.front().writes, false); + retired.push_back(std::move(items.front())); items.pop_front(); + retiredItems.fetch_add(1, std::memory_order_release); ++commits; commitNs += spent; if (failed) ++commitErrors; if (fatal != nullptr && failure == nullptr) failure = fatal; outstanding.fetch_sub(1, std::memory_order_release); - if (idleWaiters != 0) idle.notify_all(); + if (idleWaiters != 0 && (items.empty() || items.size() <= resumeAt)) idle.notify_all(); } runningCommitters.fetch_sub(1, std::memory_order_acq_rel); } @@ -200,10 +437,18 @@ void DrawPipeline::Stop() { else committer.join(); } +void DrawPipeline::ReleaseRetired() { + std::vector done; + std::lock_guard lock(mutex); + std::swap(done, retired); +} + void DrawPipeline::Drain(DrainReason reason, std::uint32_t opcode) { drainRequests[std::min(opcode, 0x100)].fetch_add(1, std::memory_order_relaxed); + const bool finisherBusy = DrawFinisher::Busy() && !DrawFinisher::OnFinisherThread(); + if ((finisherBusy || Busy()) && GuestMemory::GpuMutex().HeldByThisThread()) throw std::runtime_error("draw pipeline drain under the GPU lock (reason " + std::to_string(static_cast(reason)) + ", opcode " + std::to_string(opcode) + ")"); + if (finisherBusy) DrawFinisher::Get().Drain(); if (!Busy()) return; - if (GuestMemory::GpuMutex().HeldByThisThread()) throw std::runtime_error("draw pipeline drain under the GPU lock (reason " + std::to_string(static_cast(reason)) + ", opcode " + std::to_string(opcode) + ")"); const auto start = std::chrono::steady_clock::now(); SpinUntil(HandoffSpinWindow() * 2, [&] { return !Busy(); }); std::unique_lock lock(mutex); @@ -237,6 +482,10 @@ bool DrawPipeline::HeldReads(std::uint64_t address, std::size_t bytes) { return false; } +std::uint64_t DrawPipeline::HeldReadQueries() { + return heldReadQueries.load(std::memory_order_relaxed); +} + void DrawPipeline::ReleaseRunningReads() { std::lock_guard lock(mutex); if (!items.empty()) items.front().reads.clear(); @@ -244,6 +493,12 @@ void DrawPipeline::ReleaseRunningReads() { std::optional DrawPipeline::PendingLabel(std::uint64_t address, std::size_t bytes) { if (bytes == 0 || bytes > 8) return std::nullopt; + if (DrawFinisher::Overlap()) { + std::uint64_t held = 0; + const auto lookup = DrawFinisher::Get().PendingLabel(address, bytes, held); + if (lookup == DrawFinisher::LabelLookup::Value) return held; + if (lookup == DrawFinisher::LabelLookup::Unknown) return std::nullopt; + } const auto end = address + bytes; std::lock_guard lock(mutex); for (auto item = items.rbegin(); item != items.rend(); ++item) { @@ -257,15 +512,64 @@ std::optional DrawPipeline::PendingLabel(std::uint64_t address, s return std::nullopt; } -bool DrawPipeline::Overlaps(std::uint64_t address, std::size_t bytes) { - const auto end = address + bytes; +void DrawPipeline::countWrites(const std::vector& writes, bool added) { + for (const auto& range : writes) { + const auto found = std::find_if(queuedWrites.begin(), queuedWrites.end(), [&](const auto& entry) { return entry.first == range; }); + if (added) { + if (found != queuedWrites.end()) ++found->second; + else queuedWrites.emplace_back(range, 1u); + } else if (found != queuedWrites.end() && --found->second == 0) { + *found = queuedWrites.back(); + queuedWrites.pop_back(); + } + } +} + +DrawPipeline::Watch DrawPipeline::WatchWrites() { + if (!Busy()) return {Retired(), 0, {}}; std::lock_guard lock(mutex); - for (const auto& item : items) { - for (const auto& [begin, limit] : item.writes) { - if (address < limit && begin < end) return true; + Watch watch{retiredItems.load(std::memory_order_relaxed), items.size(), {}}; + for (const auto& item : items) watch.writes.insert(watch.writes.end(), item.writes.begin(), item.writes.end()); + return watch; +} + +bool DrawPipeline::WrittenSince(const Watch& watch, const std::vector& reads) { + const auto overlaps = [&reads](const std::vector& writes) { + return std::any_of(reads.begin(), reads.end(), [&writes](const Range& read) { + return std::any_of(writes.begin(), writes.end(), [&read](const Range& write) { return read.first < write.second && write.first < read.second; }); + }); + }; + if (DrawFinisher::Overlap() && DrawFinisher::Get().PendingOverlapsAny(reads)) return true; + std::lock_guard lock(mutex); + if (std::any_of(items.begin(), items.end(), [&](const Item& item) { return overlaps(item.writes); })) return true; + const auto retiredNow = retiredItems.load(std::memory_order_relaxed); + if (retiredNow == watch.retired) return false; + return retiredNow - watch.retired > watch.items || overlaps(watch.writes); +} + +bool DrawPipeline::Busy() const { + return outstanding.load(std::memory_order_acquire) != 0 || (finisherProducer && DrawFinisher::Busy() && DrawFinisher::Overlap()); +} + +bool DrawPipeline::OverlapsAny(std::span> ranges) { + if (DrawFinisher::Overlap() && DrawFinisher::Get().PendingOverlapsAny(ranges)) return true; + std::lock_guard lock(mutex); + for (const auto& [range, count] : queuedWrites) { + for (const auto& [address, end] : ranges) { + if (address < range.second && range.first < end) return true; } } return false; } +bool DrawPipeline::Overlaps(std::uint64_t address, std::size_t bytes) { + const auto end = address + bytes; + if (DrawFinisher::Overlap() && DrawFinisher::Get().PendingOverlaps(address, end)) return true; + std::lock_guard lock(mutex); + for (const auto& [range, count] : queuedWrites) { + if (address < range.second && range.first < end) return true; + } + return false; +} + } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp index 6311d6461f..033f0313a8 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp @@ -1,21 +1,70 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp" #include namespace AgcDriver::DriverDetail { +namespace { + +QueueState DepthCopyDraw(const QueueState& queue, const Graphics::DepthCopyPass& copy) { + const bool multisampled = copy.source.samples != 1u; + const auto address = DepthCopyPixelProgramAddress(multisampled); + const auto words = Graphics::DepthCopySourceWords(copy.source); + QueueState draw = queue; + draw.context.insert_or_assign(0x000u, 0u); + draw.shader.insert_or_assign(0x008u, static_cast(address >> 8u)); + draw.shader.insert_or_assign(0x009u, static_cast(address >> 40u)); + draw.shader.insert_or_assign(0x00au, 0u); + draw.shader.insert_or_assign(0x00bu, DepthCopyPixelUserWords(multisampled) << 1u); + for (std::uint32_t index = 0; index < words.size(); ++index) draw.shader.insert_or_assign(0x00cu + index, words[index]); + if (multisampled) draw.shader.insert_or_assign(0x014u, copy.sample); + draw.context.insert_or_assign(0x1b3u, 0x8002u); + draw.context.insert_or_assign(0x1b4u, 0x8002u); + draw.context.insert_or_assign(0x1b6u, 0u); + draw.context.insert_or_assign(0x1c4u, 0u); + draw.context.insert_or_assign(0x1c5u, 1u); + draw.context.insert_or_assign(0x08fu, 1u); + draw.context.insert_or_assign(0x203u, 0u); + if (const auto alpha = draw.context.find(0x2dcu); alpha != draw.context.end()) draw.context.insert_or_assign(0x2dcu, alpha->second & ~1u); + return draw; +} + +} + bool Driver::drawPrecheck() { static const bool precheck = std::getenv("APS5_NO_DRAW_PRECHECK") == nullptr; return precheck; } -std::optional Driver::precheckDraw(const QueueState& queue, const Submission& submission, std::span packet, const Pm4::DrawParameters& drawParameters, std::string& rejected, bool& traceIndirect) { +std::optional Driver::precheckDraw(const QueueState& queue, const Submission& submission, std::size_t packetOffset, std::span packet, const Pm4::DrawParameters& drawParameters, std::string& rejected, bool& traceIndirect) { if (!drawParameters.indirect && !drawParameters.indexed && (drawParameters.indexCount == 0 || drawParameters.instanceCount == 0)) return DrawVerdict::Nothing; if (const auto pass = Graphics::DecodeColorMetadataPass(queue)) { require(!drawParameters.indirect, "indirect CB metadata passes are unsupported"); + if (DrawPipeline::Active() && DrawPipeline::MetadataPassesPipelined() && deferredLabels().labels.empty()) { + auto writes = Graphics::PipelinedColorMetadataPassWrites(*pass); + const auto localDevice = device.Load(); + if (!writes.empty() && localDevice != nullptr) { + const auto queueId = submission.queue; + const auto opcode = (packet[0] >> 8u) & 0xffu; + DrawPipeline::Queue0().Enqueue([this, localDevice, queueId, opcode, pass = *pass] { + GuestMemory::SetCurrentPacket(opcode, queueId); + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw); + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + auto current = device.Load(); + const auto& target = current != nullptr ? current : localDevice; + recordLabelsForPacket(target.get(), queueId); + target->ColorMetadataPass(pass); + }, std::move(writes)); + DrawPipeline::CountPipelinedMetadataPass(); + return DrawVerdict::Drawn; + } + } if (DrawPipeline::Active()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, (packet[0] >> 8u) & 0xffu); GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw); std::lock_guard gpuLock(GuestMemory::GpuMutex()); @@ -27,14 +76,8 @@ std::optional Driver::precheckDraw(const QueueState& queue, const S } if (const auto copy = Graphics::DecodeDepthCopyPass(queue)) { require(!drawParameters.indirect, "indirect DB depth copies are unsupported"); - if (DrawPipeline::Active()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, (packet[0] >> 8u) & 0xffu); - GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw); - std::lock_guard gpuLock(GuestMemory::GpuMutex()); - if (device == nullptr) device = std::make_shared(); - const std::shared_ptr localDevice = device; - recordLabelsForPacket(localDevice.get(), submission.queue); - localDevice->DepthCopyPass(*copy); - return DrawVerdict::Drawn; + auto copyDraw = DepthCopyDraw(queue, *copy); + return draw(copyDraw, packet, submission, packetOffset, rejected); } if (Graphics::DepthResummarizeDrawsNothing(queue)) return DrawVerdict::Nothing; rejected = Graphics::DepthMaintenanceRejection(queue); 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 dd66e54ebf..7acaf98715 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp @@ -213,7 +213,7 @@ void Driver::traceDrawKeyLookup(const std::array& components, } bool Driver::sameVertexInfo(const ShaderRecompiler::ShaderVertexStageInfo& a, const ShaderRecompiler::ShaderVertexStageInfo& b) { - if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded || a.paClVsOutCntl != b.paClVsOutCntl) return false; + if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded || a.paClVsOutCntl != b.paClVsOutCntl || a.pointList != b.pointList || a.pointSize != b.pointSize || a.pointSizeMin != b.pointSizeMin || a.pointSizeMax != b.pointSizeMax) return false; if (a.depthRemapMode != b.depthRemapMode) return false; for (std::uint32_t i = 0; i < a.resourcesNum && i < a.resources.size(); ++i) { if (a.resources[i].fields != b.resources[i].fields) return false; @@ -228,7 +228,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) { const auto& s = a.state; const auto& t = b.state; const auto sameColor = [](const Graphics::ColorTarget& x, const Graphics::ColorTarget& y) { - return x.address == y.address && x.extent.width == y.extent.width && x.extent.height == y.extent.height && x.format == y.format && x.bytes == y.bytes && x.componentMapping == y.componentMapping && x.tileMode == y.tileMode && x.elementBytes == y.elementBytes && x.dccAddress == y.dccAddress && x.dccAlphaOnMsb == y.dccAlphaOnMsb && x.slot == y.slot && x.exportIndex == y.exportIndex; + return x.address == y.address && x.extent.width == y.extent.width && x.extent.height == y.extent.height && x.format == y.format && x.bytes == y.bytes && x.componentMapping == y.componentMapping && x.tileMode == y.tileMode && x.elementBytes == y.elementBytes && x.dccAddress == y.dccAddress && x.dccAlphaOnMsb == y.dccAlphaOnMsb && x.slot == y.slot && x.exportIndex == y.exportIndex && x.partialWriteMask == y.partialWriteMask; }; const auto sameBlend = [](const VkPipelineColorBlendAttachmentState& x, const VkPipelineColorBlendAttachmentState& y) { return x.blendEnable == y.blendEnable && x.srcColorBlendFactor == y.srcColorBlendFactor && x.dstColorBlendFactor == y.dstColorBlendFactor && x.colorBlendOp == y.colorBlendOp && x.srcAlphaBlendFactor == y.srcAlphaBlendFactor && x.dstAlphaBlendFactor == y.dstAlphaBlendFactor && x.alphaBlendOp == y.alphaBlendOp && x.colorWriteMask == y.colorWriteMask; @@ -251,7 +251,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) { for (std::size_t i = 0; i < s.blends.size(); ++i) { if (!sameBlend(s.blends[i], t.blends[i])) return false; } - if (s.hasColorTarget != t.hasColorTarget || s.rectList != t.rectList || s.renderExtent.width != t.renderExtent.width || s.renderExtent.height != t.renderExtent.height || s.topology != t.topology || s.negativeOneToOne != t.negativeOneToOne || s.depthClamp != t.depthClamp || s.conservativeRasterization != t.conservativeRasterization || s.perSampleShading != t.perSampleShading || s.cullMode != t.cullMode || s.polygonMode != t.polygonMode || s.frontFace != t.frontFace || !sameBlend(s.blend, t.blend) || s.blendConstants != t.blendConstants) return false; + if (s.colorRop != t.colorRop || s.hasColorTarget != t.hasColorTarget || s.rectList != t.rectList || s.renderExtent.width != t.renderExtent.width || s.renderExtent.height != t.renderExtent.height || s.topology != t.topology || s.negativeOneToOne != t.negativeOneToOne || s.depthClamp != t.depthClamp || s.conservativeRasterization != t.conservativeRasterization || s.perSampleShading != t.perSampleShading || s.cullMode != t.cullMode || s.polygonMode != t.polygonMode || s.frontFace != t.frontFace || !sameBlend(s.blend, t.blend) || s.blendConstants != t.blendConstants) return false; 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; @@ -262,8 +262,8 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) { const auto& y = b.programs[i]; if (x.binary.stage != y.binary.stage || x.binary.codeAddress != y.binary.codeAddress || x.userDataBase != y.userDataBase || x.firstUserSgpr != y.firstUserSgpr || x.userData != y.userData || x.snapshot != y.snapshot || x.codeOffset != y.codeOffset || x.tailContinuation != y.tailContinuation) return false; } - if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.reversedBlend != q.reversedBlend || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive || p.ditheredAlphaToCoverage != q.ditheredAlphaToCoverage || p.quadPixelMask != q.quadPixelMask || p.quadSampleMask != q.quadSampleMask || p.iteratesSamples != q.iteratesSamples || p.positionLocation != q.positionLocation || p.sampleLocations != q.sampleLocations || p.sampleOrder != q.sampleOrder) return false; - if (s.samples != t.samples || s.mergedCoverage != t.mergedCoverage || s.quadSampleMask != t.quadSampleMask || s.pixelEarlyZ != t.pixelEarlyZ) return false; + if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.reversedBlend != q.reversedBlend || p.targetMerge != q.targetMerge || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive || p.ditheredAlphaToCoverage != q.ditheredAlphaToCoverage || p.quadPixelMask != q.quadPixelMask || p.quadSampleMask != q.quadSampleMask || p.iteratesSamples != q.iteratesSamples || p.positionLocation != q.positionLocation || p.sampleLocations != q.sampleLocations || p.sampleOrder != q.sampleOrder) return false; + if (s.samples != t.samples || s.mergedCoverage != t.mergedCoverage || s.truncatedCoverage != t.truncatedCoverage || s.quadSampleMask != t.quadSampleMask || s.pixelEarlyZ != t.pixelEarlyZ || s.pixelKill != t.pixelKill) return false; return true; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRelocate.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRelocate.cpp index 33665a380c..c03e05baea 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRelocate.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRelocate.cpp @@ -173,15 +173,21 @@ void Driver::learnPendingRelocation() { } if (pending.alias == 0) return; if (drawAliases.size() >= MaxAliases) drawRelocationCounters.evicted += dropEveryOther(drawAliases); - drawAliases[pending.alias] = inserted->second; + drawAliases[pending.alias] = pending.key; } void Driver::relocateDrawEntry(const QueueState& queue, std::uint64_t alias, std::shared_ptr& entry, std::shared_ptr& decode, std::vector& patches) { auto& counters = drawRelocationCounters; ++counters.lookups; const auto found = drawAliases.find(alias); - if (found == drawAliases.end() || found->second->decode == nullptr) return; - const auto donor = found->second; + if (found == drawAliases.end()) return; + const auto cached = drawCache.find(found->second); + if (cached == drawCache.end()) { + drawAliases.erase(found); + return; + } + if (cached->second->decode == nullptr) return; + const auto donor = cached->second; const auto& programs = donor->decode->programs; if (donor->stages.size() != programs.size()) return; struct Move { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp index d3049765aa..fa1676e68c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp @@ -29,7 +29,8 @@ std::optional Driver::ClassifyIndirectDraw(const Sha using Path = Graphics::IndirectDrawPath; const auto support = localDevice->DrawIndirectSupport(); const auto fail = [&](Path reason) { if (!indirectCpu) indirectCpu = reason; }; - if (drawParameters.indexed && !GuestMemory::Accessible(reinterpret_cast(drawParameters.indexAddress), static_cast(drawParameters.indexCount) * drawParameters.indexSize)) fail(Path::IndexRange); + if (drawParameters.indexed && !indirect.indexLimitKnown) fail(Path::IndexLimitUnknown); + else if (drawParameters.indexed && !GuestMemory::Accessible(reinterpret_cast(drawParameters.indexAddress), static_cast(drawParameters.indexCount) * drawParameters.indexSize)) fail(Path::IndexRange); constexpr std::uint32_t NoLocation = 0x280u; const bool meshPath = graphics.stages.mesh.has_value() && !graphics.stages.tessellation && !graphics.rectList; const bool meshGpuSide = meshPath && drawParameters.indexed && indirect.count == 1 && !indirect.countIndirect && indirect.baseVertexLocation == NoLocation && indirect.startInstanceLocation == NoLocation && (!indirect.drawIndexEnabled || indirect.drawIndexLocation == NoLocation); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp index 04a1f72a02..e7d9bcc79e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp @@ -1,14 +1,27 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/ThreadPriority.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Screenshot.hpp" #include "prx/libkernel/Time/include/SleepWake.hpp" +#include #include namespace AgcDriver { void Submit(const Packet* packet, std::uint32_t queue) try { KernelNoteGpuSubmit_nid_no_patch(); - DriverDetail::Driver::Get().Submit(packet, queue); + auto& driver = DriverDetail::Driver::Get(); + auto* const recorder = FrameRecorder::Active(); + if (recorder == nullptr) { + driver.Submit(packet, queue); + return; + } + GuestMemory::CheckRange(packet, sizeof(Packet), alignof(Packet)); + const auto flips = driver.FlipSubmissions(); + recorder->Submit(*packet, queue, [&] { driver.Submit(packet, queue); }); + if (driver.FlipSubmissions() != flips) recorder->FrameBoundary(); } catch (const ProcessShutdown&) { LibcAwaitExit_nid_postfix(); } @@ -22,18 +35,26 @@ void Shutdown() { } void RegisterShader(const Shader* shader) { + if (auto* const recorder = FrameRecorder::Active()) recorder->RegisterShader(shader); DriverDetail::Driver::Get().RegisterShader(shader); } void SuspendPoint() { - DriverDetail::Driver::Get().SuspendPoint(); + auto* const recorder = FrameRecorder::Active(); + if (recorder == nullptr) { + DriverDetail::Driver::Get().SuspendPoint(); + return; + } + recorder->Suspend([] { DriverDetail::Driver::Get().SuspendPoint(); }); } void RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output) { + if (auto* const recorder = FrameRecorder::Active()) recorder->RegisterVideoOutput(handle); DriverDetail::Driver::Get().RegisterVideoOutput(handle, output); } void UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output) { + if (auto* const recorder = FrameRecorder::Active()) recorder->UnregisterVideoOutput(handle); DriverDetail::Driver::Get().UnregisterVideoOutput(handle, output); } @@ -46,6 +67,7 @@ void PresentClear(const PresentationWindow& window, bool opaque, void (*gpuReady } void PresentBuffer(const PresentationWindow& window, const DisplayBuffer& buffer, void (*gpuReady)(void*), void* context) { + if (auto* const recorder = FrameRecorder::Active()) recorder->PresentBuffer(buffer); DriverDetail::Driver::Get().Present(window, &buffer, true, gpuReady, context); } @@ -103,11 +125,36 @@ extern "C" void AgcDriverSuspendPoint_nid_postfix() try { } extern "C" std::uint32_t AgcDriverReserveEopFlip_nid_postfix(const AgcDriver::FlipInfo& info) { - return AgcDriver::DriverDetail::Driver::Get().ReserveEopFlip(info); + const auto token = AgcDriver::DriverDetail::Driver::Get().ReserveEopFlip(info); + if (auto* const recorder = AgcDriver::FrameRecorder::Active(); recorder != nullptr && token != 0) recorder->ReserveEopFlip(info, token); + return token; +} + +extern "C" std::uint32_t AgcDriverReserveEopFlipAt_nid_postfix(const AgcDriver::FlipInfo& info, std::uint32_t token) { + return AgcDriver::DriverDetail::Driver::Get().ReserveEopFlipAt(info, token); +} + +extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* waits, std::size_t capacity) { + const auto awaited = AgcDriver::DriverDetail::Driver::Get().AwaitedWaits(); + const auto count = std::min(capacity, awaited.size()); + std::copy_n(awaited.begin(), count, waits); + return count; +} + +extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed) { + const auto [submitted, done] = AgcDriver::DriverDetail::Driver::Get().SerialProgress(); + *accepted = submitted; + *completed = done; } extern "C" void AgcDriverSubmitFlip_nid_postfix(const AgcDriver::FlipInfo& info) try { - AgcDriver::DriverDetail::Driver::Get().SubmitFlip(info); + auto* const recorder = AgcDriver::FrameRecorder::Active(); + if (recorder == nullptr) { + AgcDriver::DriverDetail::Driver::Get().SubmitFlip(info); + return; + } + recorder->Flip(info, [&] { AgcDriver::DriverDetail::Driver::Get().SubmitFlip(info); }); + recorder->FrameBoundary(); } catch (const ProcessShutdown&) { LibcAwaitExit_nid_postfix(); } @@ -116,6 +163,14 @@ extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix() { return AgcDriver::DriverDetail::Driver::Get().DispatchesAdoptedAhead(); } +extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix() { + return AgcDriver::DriverDetail::Driver::Get().DispatchesRejectedAhead(); +} + +extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)()) { + AgcDriver::DriverDetail::Driver::Get().SetAheadAdoptHook(hook); +} + extern "C" void AgcDriverRegisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr& output) { AgcDriver::RegisterVideoOutput(handle, output); } @@ -145,18 +200,26 @@ extern "C" void AgcDriverReportFailure_nid_postfix(std::exception_ptr error) { } extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span context, std::span primitive) try { + if (auto* const recorder = AgcDriver::FrameRecorder::Active()) recorder->ResolveShaderAbi(shader, context, primitive); AgcDriver::DriverDetail::Driver::Get().ResolveShaderAbi(shader, context, primitive); } catch (const ProcessShutdown&) { LibcAwaitExit_nid_postfix(); } extern "C" void AgcDriverResolveGraphicsAbi_nid_postfix(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) try { + if (auto* const recorder = AgcDriver::FrameRecorder::Active()) recorder->ResolveGraphicsAbi(vertex, pixel, primitiveType); AgcDriver::DriverDetail::Driver::Get().ResolveGraphicsAbi(vertex, pixel, primitiveType); } catch (const ProcessShutdown&) { LibcAwaitExit_nid_postfix(); } extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span stages, std::span context, std::span primitive) try { + if (auto* const recorder = AgcDriver::FrameRecorder::Active()) recorder->ResolveGraphicsStagesAbi(stages, context, primitive); AgcDriver::DriverDetail::Driver::Get().ResolveGraphicsStagesAbi(stages, context, primitive); } catch (const ProcessShutdown&) { LibcAwaitExit_nid_postfix(); } + +extern "C" void AgcDriverPrepareCommandWrite_nid_no_patch(std::uint64_t address, std::size_t bytes) { + if (AgcDriver::Graphics::Recorder::SnapshotWriteOverlaps(address, bytes)) + AgcDriver::GuestMemory::FlushGpuWrites(address, bytes); +} diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp index abad85855e..ba19bb4a60 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp @@ -14,6 +14,7 @@ #include "prx/libc/include/GuestAllocations.hpp" #include "ShaderDiskCache.hpp" #include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" #include #include #include @@ -87,6 +88,9 @@ void MapDriverMemory() { auto* slot = reinterpret_cast(FsTableAddress); slot[0] = (FsRoutineTableAddress & 0xffffffffffffull) | 16ull << 48u; slot[1] = FsTableDword3 << 32u | FsRoutineRecords; +#ifndef _WIN32 + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(mapped, DriverMemoryBytes); +#endif GuestAllocations::Mutation mutation; mutation.Add(mapped, DriverMemoryBytes, true, true, true); } @@ -161,7 +165,13 @@ void Driver::stop() { for (auto& [queue, worker] : workers) { if (worker.thread.joinable()) worker.thread.join(); } + try { + DrawFinisher::Get().Drain(); + } catch (...) { + } DrawPipeline::Queue0().Stop(); + DrawPipeline::Queue0().ReleaseRetired(); + DrawFinisher::Get().ReleaseRan(); StopWorkerSampler(); CloseRegistrationPreparation(); Graphics::StopPipelineLibraryWorker(); @@ -223,14 +233,52 @@ Driver::Driver() { } void Driver::WaitIdle() { + require(!onWorkerThread(), "worker cannot wait for itself"); + { + std::unique_lock lock(mutex); + const auto target = accepted; + ++idleWaiters; + changed.wait(lock, [&] { return failure != nullptr || stopping || completed >= target; }); + --idleWaiters; + rethrowFailure(); + checkStopping(); + } + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + if (const auto localDevice = device.Load()) localDevice->WaitIdle(); +} + +bool Driver::WaitIdleFor(std::chrono::milliseconds timeout) { require(!onWorkerThread(), "worker cannot wait for itself"); std::unique_lock lock(mutex); const auto target = accepted; ++idleWaiters; - changed.wait(lock, [&] { return failure != nullptr || stopping || completed >= target; }); + const bool idle = changed.wait_for(lock, timeout, [&] { return failure != nullptr || stopping || completed >= target; }); --idleWaiters; rethrowFailure(); checkStopping(); + return idle; +} + +std::vector Driver::AwaitedWaits() { + std::vector result; + for (const auto& slot : awaitedSlots) { + const auto address = slot.address.load(std::memory_order_acquire); + if (address == 0) continue; + AwaitedWait wait; + wait.address = address; + wait.reference = slot.reference.load(std::memory_order_relaxed); + wait.mask = slot.mask.load(std::memory_order_relaxed); + const auto shape = slot.shape.load(std::memory_order_relaxed); + wait.function = static_cast(shape); + wait.bytes = static_cast(shape >> 32u); + if (slot.address.load(std::memory_order_acquire) == address) result.push_back(wait); + } + return result; +} + +std::pair Driver::SerialProgress() { + std::lock_guard lock(mutex); + return {accepted, completed}; } void Driver::CheckFailure() { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp index 6a76867635..9e8ca4fe94 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp @@ -2,6 +2,8 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -16,14 +18,14 @@ bool Driver::matchesCopyKernel(std::span code, const std::v static const bool enabled = std::getenv("APS5_NO_COPY_HLE") == nullptr; if (!enabled || userData.size() != 12 || compute.numThreads[0] != 64 || compute.numThreads[1] != 1 || compute.numThreads[2] != 1 || !compute.groupIdEnable[0]) return false; static constexpr std::size_t copyKernelWords = 38; - static constexpr std::uint64_t copyKernelHash = 0x6ec00fe8aa95f99aull; + static constexpr std::array copyKernelHashes{0x6ec00fe8aa95f99aull, 0xc771c42e4d069d62ull}; if (code.size() < copyKernelWords || code[0] != 0xbfa00002u || code[copyKernelWords - 1] != 0xbf810000u) return false; std::uint64_t hash = 0xcbf29ce484222325ull; for (const auto word : code.first(copyKernelWords)) { hash ^= word; hash *= 0x100000001b3ull; } - if (hash != copyKernelHash) return false; + if (std::find(copyKernelHashes.begin(), copyKernelHashes.end(), hash) == copyKernelHashes.end()) return false; const auto stride = [&](std::size_t word) { return (userData[word] >> 16u) & 0x3fffu; }; const auto swizzled = [&](std::size_t word) { return ((userData[word] >> 31u) & 1u) != 0; }; return userData[3] == 0x00014004u && userData[7] == 0x00014004u && userData[11] == 0x0004dfacu && stride(1) == 4 && stride(5) == 4 && stride(9) == 16 && !swizzled(1) && !swizzled(5) && !swizzled(9) && userData[10] >= 1; @@ -52,13 +54,35 @@ std::optional StreamCopyRange(const std::vector& user return copy; } +bool MatchesIndexedCopyKernel(std::span code, const std::vector& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute) { + static const bool enabled = std::getenv("APS5_NO_COPY_HLE") == nullptr; + if (!enabled || userData.size() != 12 || compute.numThreads[0] != 64 || compute.numThreads[1] != 1 || compute.numThreads[2] != 1 || !compute.groupIdEnable[0]) return false; + static constexpr std::array kernel{0xbfa00001u, 0xd7460000u, 0x04010c0cu, 0xf4201a84u, 0xfa000000u, 0xbf8cc07fu, 0x7da8006au, 0xbf88000au, 0xf4241a84u, 0xfa000004u, 0xbf8cc07fu, 0x4a08006bu, 0x4a00006au, 0xe0382000u, 0x80000000u, 0xbf8c3f70u, 0xe0782000u, 0x80010004u, 0xbf810000u}; + if (code.size() < kernel.size() || !std::equal(kernel.begin(), kernel.end(), code.begin())) return false; + const auto stride = [&](std::size_t word) { return (userData[word] >> 16u) & 0x3fffu; }; + const auto swizzled = [&](std::size_t word) { return ((userData[word] >> 31u) & 1u) != 0; }; + const bool addTid = ((userData[3] >> 23u) & 1u) != 0; + const auto outOfBounds = (userData[3] >> 28u) & 3u; + const auto type = userData[3] >> 30u; + return stride(1) == 16 && stride(5) == 16 && stride(9) == 16 && !swizzled(1) && !swizzled(5) && !swizzled(9) && userData[3] == userData[7] && type == 0 && !addTid && outOfBounds == 0 && userData[10] >= 1; +} + +std::optional IndexedCopyRange(const std::vector& userData, std::uint32_t groups, std::uint32_t count, std::uint32_t sourceOffset, std::uint32_t destinationOffset) { + const auto base = [&](std::size_t word) { return userData[word] | (static_cast(userData[word + 1] & 0xffffu) << 32u); }; + const auto elements = std::min(std::uint64_t{groups} * 64u, count); + if (elements == 0 || std::uint64_t{sourceOffset} + elements > userData[2] || std::uint64_t{destinationOffset} + elements > userData[6]) return std::nullopt; + const StreamCopy copy{base(0) + std::uint64_t{sourceOffset} * 16u, base(4) + std::uint64_t{destinationOffset} * 16u, static_cast(elements) * 16u}; + if (copy.bytes > (16u << 20u) || copy.source % 16 != 0 || copy.destination % 16 != 0) return std::nullopt; + return copy; +} + void Driver::countCopy(int path, std::size_t bytes, std::chrono::steady_clock::time_point started, std::chrono::steady_clock::time_point locked, const VulkanDevice::CopyOutcome* outcome) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (path < 0 || path >= CopyPaths) return; copyPathCounts[path].fetch_add(1, std::memory_order_relaxed); if (!profile) return; using Outcome = VulkanDevice::CopyOutcome; - static std::mutex countsMutex; + static AgcDriver::Mutex countsMutex; static std::uint64_t counts[CopyPaths] = {}; static std::uint64_t refused[Outcome::Refusals] = {}; static std::uint64_t cpuBytes = 0, gpuBytes = 0, aliasedBytes = 0, syncs = 0, settledBySignal = 0, sourceWaits = 0; @@ -119,7 +143,9 @@ bool Driver::knownValueVerify() { } bool Driver::copyBuffer(QueueState& queue, std::uint32_t queueId, std::span packet, std::span code, const std::vector& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute, const std::shared_ptr& localDevice, std::uint64_t programAddress) { - const bool stream = !matchesCopyKernel(code, userData, compute); + const bool hashed = matchesCopyKernel(code, userData, compute); + const bool indexed = !hashed && MatchesIndexedCopyKernel(code, userData, compute); + const bool stream = !hashed && !indexed; if (stream && !MatchesStreamCopyKernel(code, userData, compute)) return false; const auto started = std::chrono::steady_clock::now(); static const std::size_t cpuMax = [] { @@ -132,8 +158,8 @@ bool Driver::copyBuffer(QueueState& queue, std::uint32_t queueId, std::span(std::strtoull(text, nullptr, 10)) : std::numeric_limits::max(); }(); const auto base = [&](std::size_t word) { return userData[word] | (static_cast(userData[word + 1] & 0xffffu) << 32u); }; - const auto source = base(0); - const auto destination = base(4); + auto source = base(0); + auto destination = base(4); const auto record = stream ? std::uint64_t{0} : base(8); std::array groups{packet[1], packet[2], packet[3]}; if ((packet[4] & 0x20u) != 0) { @@ -168,6 +194,28 @@ bool Driver::copyBuffer(QueueState& queue, std::uint32_t queueId, std::spanbytes; + } else if (indexed) { + Graphics::FinishLeaseWork(); + const bool recordAccessible = GuestMemory::Accessible(reinterpret_cast(record), 12); + + bool published = false; + if (!recordAccessible || Graphics::Recorder::SnapshotWriteOverlaps(record, 12) || Graphics::StorageTexture::FlushPending(record, 12, nullptr, "indexed copy record", Graphics::PublishScope::Whole, &published) || published || labelPending(record, 12)) { + if (traceCopy()) traceCopyPending(recordAccessible ? "copy record" : "inaccessible copy record", record, 12, source, destination, 0, localDevice); + countCopy(CopyRecordPending, 0, started, locked); + return false; + } + std::uint32_t count = 0, sourceOffset = 0, destinationOffset = 0; + std::memcpy(&count, reinterpret_cast(record), 4); + std::memcpy(&sourceOffset, reinterpret_cast(record + 4), 4); + std::memcpy(&destinationOffset, reinterpret_cast(record + 8), 4); + const auto range = IndexedCopyRange(userData, groups[0], count, sourceOffset, destinationOffset); + if (!range || (record < range->destination + range->bytes && range->destination < record + 12)) { + countCopy(CopyShape, range ? range->bytes : 0, started, locked); + return false; + } + source = range->source; + destination = range->destination; + bytes = range->bytes; } else { const bool recordAccessible = GuestMemory::Accessible(reinterpret_cast(record), 8); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp index e410cb88d3..bd1b6112de 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferFill.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Memory/DwordPatternFill.hpp" @@ -186,7 +187,7 @@ void Driver::fillClearCount(const Graphics::StorageTexture::FillCoverage& covera if (!profile) return; using FillCover = Graphics::StorageTexture::FillCover; static constexpr std::array names{"none", "exact", "inside", "around", "straddle", "several", "keys", "layer"}; - static std::mutex countsMutex; + static AgcDriver::Mutex countsMutex; static std::array counts{}, coverBytes{}; static std::map coveredFormats; static std::map refusals; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp index 8b0687ac4f..58fa8648a3 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp @@ -181,10 +181,11 @@ std::vector> QueuedDrawReadRanges(const } if (stages.empty() || stages.front().program == nullptr) return ranges; for (const auto& attribute : stages.front().program->vertexAttributes) { - if (Graphics::NullVertexDescriptor(attribute) || Graphics::VertexFetchOutOfRange(attribute)) continue; + if (Graphics::NullVertexDescriptor(attribute)) continue; const auto address = attribute.resource.fields[0] | (static_cast(attribute.resource.fields[1] & 0xffffu) << 32u); std::uint64_t bytes = 0; try { + if (Graphics::VertexFetchOutOfRange(attribute)) continue; bytes = Graphics::VertexBufferExtent(attribute); } catch (const std::runtime_error&) { continue; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index ce9a656cb7..5972698258 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/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Eq/include/Event.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include #include #include @@ -29,9 +30,22 @@ bool orderedFills() { return ordered; } -bool pipelineDma() { - static const bool enabled = std::getenv("APS5_PIPELINE_DMA") != nullptr; - return enabled; +bool keepsDrawFinisher(std::uint32_t header, std::uint32_t opcode) { + if (header == FlipPacketHeader || header == RenderingWaitPacketHeader) return false; + if (Pm4::DrawOpcode(opcode)) return true; + switch (opcode) { + case 0x13: case 0x26: case 0x2a: case 0x2f: case 0x69: case 0x76: case 0x79: return true; + case 0x10: return ((header >> 2u) & 0x3fu) == 0; + default: return false; + } +} + +bool waitTakesPendingLabel(std::span packet) { + if (packet.size() < 5) return false; + const auto awaitedBytes = Pm4::WaitAwaitedBytes(packet); + const std::uint64_t awaited = (packet[2] & (awaitedBytes == 8 ? ~7u : ~3u)) | (static_cast(packet[3]) << 32u); + const auto value = DrawPipeline::Queue0().PendingLabel(awaited, awaitedBytes); + return value.has_value() && Pm4::WaitComparesValue(packet, *value); } bool keepsDrawPipeline(std::span packet, std::uint32_t header, std::uint32_t opcode) { @@ -47,7 +61,6 @@ bool keepsDrawPipeline(std::span packet, std::uint32_t head if (packet.size() < 7) return false; const auto destination = Pm4::DmaDestination(packet); if (destination == Pm4::DmaDestinationNowhere) return true; - if (!pipelineDma()) return false; if (destination == Pm4::DmaSelectGds) return true; const auto bytes = Pm4::DmaBytes(packet); if (bytes == 0) return true; @@ -108,13 +121,17 @@ void Driver::deliverFlipEopInterrupt(std::uint32_t queue) { template void Driver::timed(double WorkerProfile::*bucket, TWork&& work) { + static const bool report = std::getenv("APS5_PROFILE_DRAW") != nullptr; + if (!report) { + work(); + return; + } static thread_local WorkerProfile profile; const auto begin = std::chrono::steady_clock::now(); work(); const auto end = std::chrono::steady_clock::now(); profile.*bucket += std::chrono::duration(end - begin).count(); - static const bool report = std::getenv("APS5_PROFILE_DRAW") != nullptr; - if (report && end - profile.reported > std::chrono::seconds(10)) { + if (end - profile.reported > std::chrono::seconds(10)) { profile.reported = end; AgcDriver::ProfilePrint_nid_no_patch( "[gpu] worker at %.0f s: dispatch %.1f s, draw %.1f s, wait %.1f s\n", std::chrono::duration(end - profile.start).count(), profile.dispatchMs / 1000, profile.drawMs / 1000, profile.waitMs / 1000); } @@ -226,6 +243,12 @@ void Driver::execute(const Submission& submission) { struct ClearAhead { ~ClearAhead() { clearResolvedAhead(); } } clearAhead; + struct SettleFinisher { + bool queue0; + ~SettleFinisher() { + if (queue0) DrawFinisher::Get().Settle(); + } + } settleFinisher{submission.queue == 0}; for (std::size_t cursor = 0; cursor < submission.commands.size();) { if (packetEpoch()) bumpEpoch(&EpochBumps::packets); CheckFailure(); @@ -235,10 +258,20 @@ void Driver::execute(const Submission& submission) { const auto packet = std::span(submission.commands).subspan(cursor, count); const auto opcode = (header >> 8u) & 0xffu; auto nextCursor = cursor + count; + const bool predicated = Pm4::Predicated(header) && queue.predication.operation != 0; + if (submission.queue == 0 && DrawFinisher::Busy() && !keepsDrawFinisher(header, opcode)) { + const bool queuesLabel = pipelined && !predicated && header != FlipPacketHeader && header != RenderingWaitPacketHeader && !submission.flips.contains(cursor) && labelPacketQueues(packet, opcode); + const bool heldLabelWait = pipelined && !predicated && orderedWaits && (opcode == 0x3c || opcode == 0x93) && header != RenderingWaitPacketHeader && waitTakesPendingLabel(packet); + const bool beside = DrawFinisher::Overlap() && opcode != 0x15 && (queuesLabel || heldLabelWait || keepsDrawPipeline(packet, header, opcode)); + if (!beside) DrawFinisher::Get().Drain(); + if (DrawFinisher::Overlap()) { + DrawFinisher::NoteDrain(DrawFinisher::DrainSite::Packet, !beside); + if (!beside) DrawFinisher::NoteDrainedOpcode(header == FlipPacketHeader ? 0xffu : opcode, keepsDrawPipeline(packet, header, opcode)); + } + } if (APS5_ENABLE_TIMING_LOG && submission.queue == 0 && pendingFrameTiming == nullptr) includeTimingSubmission(submission, false); PerformanceContext timingContext(submission.queue == 0 ? pendingFrameTiming.get() : FrameTiming::Async()); PerformanceTimer timing("Driver.Packet"); - const bool predicated = Pm4::Predicated(header) && queue.predication.operation != 0; if (pipelined && !predicated && header != FlipPacketHeader && header != RenderingWaitPacketHeader && !submission.flips.contains(cursor) && (enqueueLabelPacket(packet, opcode, submission.queue) || enqueueDmaPacket(packet, opcode, submission.queue, queue))) { traceLabel(packet, submission.queue); recent.Record(cursor); @@ -265,8 +298,10 @@ void Driver::execute(const Submission& submission) { continue; } } + timing.Mark("pipeline_prepare"); if (pipelined && DrawPipeline::Queue0().Busy() && !keepsDrawPipeline(packet, header, opcode)) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, header == FlipPacketHeader ? 0xffu : opcode); if (pipelined && predicated && DrawPipeline::Queue0().Busy() && DrawPipeline::Queue0().Overlaps(queue.predication.address, queue.predication.operation == 3 ? 8 : 4)) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, opcode); + timing.Mark("pipeline_drain"); std::shared_lock deviceUse(deviceReplacement, std::defer_lock); if (opcode != 0x3c && opcode != 0x93 && header != RenderingWaitPacketHeader && header != FlipPacketHeader) deviceUse.lock(); @@ -289,7 +324,7 @@ void Driver::execute(const Submission& submission) { const auto flushStart = profilePackets ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; flushBetweenPackets(submission.queue, header, opcode == 0x49 || opcode == 0x37); timing.Mark("flush_between_packets"); - PacketTimer packetTimer{profilePackets, header == FlipPacketHeader ? 0xffffu : opcode, submission.queue, packetProfile, std::chrono::steady_clock::now()}; + PacketTimer packetTimer{profilePackets, header == FlipPacketHeader ? 0xffffu : opcode, submission.queue, packetProfile, profilePackets ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}}; if (profilePackets) { packetStartedAt() = packetTimer.start; @@ -360,6 +395,7 @@ void Driver::execute(const Submission& submission) { GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush); std::lock_guard gpuLock(GuestMemory::GpuMutex()); const auto localDevice = device.Load(); + if (localDevice != nullptr) static_cast(Graphics::StorageTexture::StoreDrawnLinear("flip")); if (!recordLabelsForPacket(localDevice.get(), submission.queue) && localDevice != nullptr) localDevice->SubmitRecorded(submission.queue == 0); if (localDevice != nullptr) { @@ -404,7 +440,7 @@ void Driver::execute(const Submission& submission) { finishDispatchPacket(opcode == 0x16); } else if (opcode == 0x3c || opcode == 0x93) { static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr; - const auto waitStart = std::chrono::steady_clock::now(); + const auto waitStart = traceGpu ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; timed(&WorkerProfile::waitMs, [&] { waitMemory(packet, submission.queue, recent, submission.received, submission.heldAtSubmit.contains(cursor)); }); gateOpened() = true; if (traceGpu && std::chrono::steady_clock::now() - waitStart > std::chrono::milliseconds(200)) { @@ -435,7 +471,7 @@ void Driver::execute(const Submission& submission) { try { std::string rejected; - const auto verdict = draw(queue, packet, submission, rejected); + const auto verdict = draw(queue, packet, submission, cursor, rejected); drawn = verdict == DrawVerdict::Drawn; CaptureTrace::Log("draw submission=%llu queue=%x offset=%zu target=%llx mask=%x verdict=%d reason=%.256s", static_cast(submission.serial), submission.queue, cursor, static_cast(color), readRegister(queue.context, 0x8e), static_cast(verdict), rejected.c_str()); if (verdict == DrawVerdict::Rejected) { @@ -519,6 +555,7 @@ void Driver::execute(const Submission& submission) { cursor = nextCursor; } + if (submission.queue == 0) DrawFinisher::Get().Drain(); PerformanceContext endContext(submission.queue == 0 ? frameTiming() : FrameTiming::Async()); PerformanceTimer endTiming("Driver.EndSubmission"); static const bool submitAtEnd = std::getenv("APS5_SUBMIT_AT_END") != nullptr; @@ -528,8 +565,8 @@ void Driver::execute(const Submission& submission) { return; } if ((std::atomic_ref(*const_cast(submission.rewindTail - 1)).load(std::memory_order_acquire) & 0x80000000u) != 0) { - DrawPipeline::Queue0().Enqueue([this] { - submitOpenWork(); + DrawPipeline::Queue0().Enqueue([this, progress = submission.progress] { + submitOpenWork(progress); wakeQueueZeroForCompletions(); }, {}); endTiming.Finish(); @@ -553,7 +590,10 @@ void Driver::execute(const Submission& submission) { if (localDevice != nullptr) localDevice->SubmitRecorded(submission.queue == 0); } endTiming.Finish(); - if (submission.rewindTail != nullptr) executeRewindTail(submission); + if (submission.rewindTail != nullptr) { + if (submission.progress) submitOpenWork(submission.progress); + executeRewindTail(submission); + } } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp index d65d4bf795..2e9ce460eb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp @@ -38,11 +38,12 @@ bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue std::lock_guard gpuLock(GuestMemory::GpuMutex()); const auto localDevice = device.Load(); recordDeferredLabels(localDevice.get(), submission.queue); - const bool workOpen = Graphics::Recorder::RecordedWorkSinceSubmit() != 0; + bool workOpen = Graphics::Recorder::RecordedWorkSinceSubmit() != 0; int reason = localDevice != nullptr ? 0 : 1; if (label.has_value()) { const auto bytes = label->Bytes(); const auto stamp = ++eventSerial; + if (localDevice != nullptr && submission.queue == 0 && Graphics::StorageTexture::StoreDrawnLinear("label") != 0) workOpen = true; reason = localDevice != nullptr ? localDevice->WriteLabelOnGpu(label->address, bytes, stamp, submission.queue) : 4; if (reason == 1) GuestMemory::Write(label->address, bytes, 4); if (reason == 0 || reason == 1 || reason == 5) { @@ -52,6 +53,8 @@ bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue countLabelOutcome(reason); ++immediateLabels; } else { + if (localDevice != nullptr && submission.queue == 0 && Graphics::StorageTexture::StoreDrawnLinear("interrupt") != 0) workOpen = true; + if (localDevice != nullptr) Graphics::StorageTexture::StoreReadbackBeforeLabel("readback before interrupt"); ++noOpLabels; } if (reason == 0 || reason == 5) { @@ -248,6 +251,7 @@ bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue recordDeferredLabels(draining.get(), submission.queue); if (draining != nullptr) { + static_cast(Graphics::StorageTexture::StoreDrawnLinear("drain")); if (unlockedDrain && draining->CanWaitUnlocked()) epoch = draining->SubmitAndEpoch(); else draining->WaitIdle(); } @@ -291,8 +295,7 @@ void Driver::dumpSampleCounters(std::uint64_t address) { } bool Driver::enqueueDmaPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue, const QueueState& state) { - static const bool enabled = std::getenv("APS5_PIPELINE_DMA") != nullptr; - if (!enabled || opcode != 0x50 || queue != 0 || packet.size() < 7 || !DrawPipeline::Active() || !deferredLabels().labels.empty()) return false; + if (opcode != 0x50 || queue != 0 || packet.size() < 7 || !DrawPipeline::Active() || !deferredLabels().labels.empty()) return false; const auto source = ((packet[1] >> 29u) & 3u) | ((packet[6] >> 24u) & 4u) | ((packet[6] >> 25u) & 8u); constexpr std::size_t immediateLimit = std::size_t{16} << 20u; constexpr std::size_t gpuStoreLimit = 65536; @@ -380,9 +383,15 @@ void Driver::commitDmaStore(std::uint32_t queue, std::uint64_t address, std::spa Graphics::Recorder::CloseLabelGroup(GuestMemory::TrackerGeneration()); } -bool Driver::enqueueLabelPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue) { +bool Driver::labelPacketQueues(std::span packet, std::uint32_t opcode) { static const bool ordered = std::getenv("APS5_PIPELINE_DRAIN_LABELS") == nullptr; if (!ordered || !DeferLabels() || (opcode != 0x49 && opcode != 0x37) || !deferredLabels().labels.empty()) return false; + if (Pm4::DecodeLabelWrite(packet).has_value()) return true; + return opcode == 0x49 && ((packet[2] >> 29u) == 0 || (packet[3] | (static_cast(packet[4]) << 32u)) == 0); +} + +bool Driver::enqueueLabelPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue) { + if (!labelPacketQueues(packet, opcode)) return false; const auto interruptSelect = opcode == 0x49 ? (packet[2] >> 24u) & 7u : 0u; const bool endOfPipeInterrupt = interruptSelect != 0 && interruptSelect != 3; const auto label = Pm4::DecodeLabelWrite(packet); @@ -406,10 +415,23 @@ bool Driver::enqueueLabelPacket(std::span packet, std::uint } if (endOfPipeInterrupt) bumpEpoch(&EpochBumps::drains); auto stored = bytes; - DrawPipeline::Queue0().Enqueue([this, queue, opcode, address, bytes = std::move(bytes), endOfPipeInterrupt] { - GuestMemory::SetCurrentPacket(opcode, queue); - commitLabel(queue, address, bytes, endOfPipeInterrupt); - }, std::move(writes), address, std::move(stored)); + auto labelBytes = bytes; + auto enqueue =[this, queue, opcode, address, bytes = std::move(bytes), endOfPipeInterrupt, writes, stored = std::move(stored)]() mutable { + DrawPipeline::Queue0().Enqueue([this, queue, opcode, address, bytes = std::move(bytes), endOfPipeInterrupt] { + GuestMemory::SetCurrentPacket(opcode, queue); + commitLabel(queue, address, bytes, endOfPipeInterrupt); + }, std::move(writes), address, std::move(stored)); + }; + if (queue == 0 && DrawFinisher::Overlap() && DrawFinisher::Busy()) { + DrawFinisher::Pending held; + held.unknown = false; + held.writes = std::move(writes); + held.labelAddress = address; + held.labelBytes = std::move(labelBytes); + DrawFinisher::Get().Enqueue(std::move(enqueue), std::move(held)); + return true; + } + enqueue(); return true; } @@ -418,10 +440,11 @@ void Driver::commitLabel(std::uint32_t queue, std::uint64_t address, std::spanWriteLabelOnGpu(address, bytes, stamp, queue) : 4; if (reason != 0 && reason != 5 && reason != 6) { if (reason != 1 && localDevice != nullptr) localDevice->WaitIdle(); @@ -436,6 +459,7 @@ void Driver::commitLabel(std::uint32_t queue, std::uint64_t address, std::spanAfterRecordedWork([this, queue] { deliverGuestEopInterrupt(queue); }, queue == 0); if (deferred && workOpen) localDevice->SubmitRecorded(queue == 0); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp index d071d049fc..4f3261986a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp @@ -12,19 +12,35 @@ namespace AgcDriver::DriverDetail { std::uint32_t Driver::ReserveEopFlip(const FlipInfo& info) { std::lock_guard lock(mutex); + require(nextEopToken <= EopFlipTokenLimit, "EOP flip token space exhausted"); + return reserveEopFlip(info, nextEopToken); +} + +std::uint32_t Driver::ReserveEopFlipAt(const FlipInfo& info, std::uint32_t token) { + std::lock_guard lock(mutex); + return reserveEopFlip(info, token); +} + +std::uint32_t Driver::reserveEopFlip(const FlipInfo& info, std::uint32_t token) { rethrowFailure(); checkStopping(); - require(nextEopToken <= EopFlipTokenLimit, "EOP flip token space exhausted"); + require(token >= EopFlipTokenBase && token <= EopFlipTokenLimit && !eopFlips.contains(token), "EOP flip token is not free"); const auto output = outputs.find(info.handle); require(output != outputs.end(), "EOP flip references an unregistered video output"); auto request = output->second->TryReserve(info); if (!request) return 0; - const auto token = nextEopToken; eopFlips.emplace(token, EopFlip{info.handle, std::move(request)}); - ++nextEopToken; + nextEopToken = std::max(nextEopToken, token + 1); return token; } +std::vector Driver::PendingEopFlipTokens() { + std::lock_guard lock(mutex); + std::vector tokens; + for (const auto& [token, flip] : eopFlips) tokens.push_back(token); + return tokens; +} + void Driver::RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output) { require(output != nullptr, "null video output"); std::lock_guard lock(mutex); @@ -77,6 +93,7 @@ void Driver::Present(const PresentationWindow& window, const DisplayBuffer* buff bool submitted = false; bool presentable = false; bool trailing = false; + bool queueUnlocked = false; double waitedMs = 0; { GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Present); @@ -125,12 +142,20 @@ void Driver::Present(const PresentationWindow& window, const DisplayBuffer* buff } if (submitted && (syncFlip || inFlight != 0)) { - presenting->QueuePresent(); - timing.Mark("queue_present"); + if (presenting->PresentsOnOwnQueue()) { + queueUnlocked = true; + } else { + presenting->QueuePresent(); + timing.Mark("queue_present"); + } trailing = !syncFlip; submitted = false; } } + if (queueUnlocked) { + presenting->QueuePresent(); + timing.Mark("queue_present"); + } if (trailing) { waitedMs += presenting->FinishPresent(); timing.Mark("render_fence_wait"); @@ -139,9 +164,13 @@ void Driver::Present(const PresentationWindow& window, const DisplayBuffer* buff waitedMs = presenting->FinishPresent(); timing.Mark("render_fence_wait"); - GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Present); - std::lock_guard lock(GuestMemory::GpuMutex()); - presenting->QueuePresent(); + if (presenting->PresentsOnOwnQueue()) { + presenting->QueuePresent(); + } else { + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Present); + std::lock_guard lock(GuestMemory::GpuMutex()); + presenting->QueuePresent(); + } timing.Mark("queue_present"); } gpuReady(context); @@ -177,7 +206,7 @@ void Driver::reportPresents(double waitedMs, std::size_t inFlight) { const auto flips = flipsCounted.load() - previousFlips; const double perFlip = flips != 0 ? 1.0 / static_cast(flips) : 0.0; const auto serial = flipSerial.load(); - AgcDriver::ProfilePrint_nid_no_patch( "[present] %llu presents over 10 s (%llu dropped: no free swapchain image; %zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per frame, presented or dropped: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked\n", static_cast(presents), static_cast(dropped), inFlight, inFlight != 0 ? "inflight_wait" : "render_fence_wait", percentile(0.5), percentile(0.9), waits.empty() ? 0.0 : waits.back(), waits.size(), (counts.gpuBusyMs - previous.gpuBusyMs) * perFrame, (counts.gpuGapMs - previous.gpuGapMs) * perFrame, static_cast(counts.gpuUnread - previous.gpuUnread), static_cast(serial - windowSerial) * perFlip, static_cast(flipBatchesUnsignaled.load() - previousUnsignaled) * perFlip, static_cast(counts.batchesAheadOfBlit - previous.batchesAheadOfBlit) * perPresent, static_cast(counts.batchesAfterFlip - previous.batchesAfterFlip) * perPresent, (counts.lastSubmitAfterFlipMs - previous.lastSubmitAfterFlipMs) * perPresent, (counts.blitSubmitAfterFlipMs - previous.blitSubmitAfterFlipMs) * perPresent, static_cast(counts.readsOverwritten - previous.readsOverwritten), static_cast(counts.readsChecked - previous.readsChecked)); + AgcDriver::ProfilePrint_nid_no_patch( "[present] %llu presents over 10 s (%llu dropped: no free swapchain image; %zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per frame, presented or dropped: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked; waits for the display keys' writer outside the GPU mutex: %llu, %.1f ms\n", static_cast(presents), static_cast(dropped), inFlight, inFlight != 0 ? "inflight_wait" : "render_fence_wait", percentile(0.5), percentile(0.9), waits.empty() ? 0.0 : waits.back(), waits.size(), (counts.gpuBusyMs - previous.gpuBusyMs) * perFrame, (counts.gpuGapMs - previous.gpuGapMs) * perFrame, static_cast(counts.gpuUnread - previous.gpuUnread), static_cast(serial - windowSerial) * perFlip, static_cast(flipBatchesUnsignaled.load() - previousUnsignaled) * perFlip, static_cast(counts.batchesAheadOfBlit - previous.batchesAheadOfBlit) * perPresent, static_cast(counts.batchesAfterFlip - previous.batchesAfterFlip) * perPresent, (counts.lastSubmitAfterFlipMs - previous.lastSubmitAfterFlipMs) * perPresent, (counts.blitSubmitAfterFlipMs - previous.blitSubmitAfterFlipMs) * perPresent, static_cast(counts.readsOverwritten - previous.readsOverwritten), static_cast(counts.readsChecked - previous.readsChecked), static_cast(counts.keyWriterWaits - previous.keyWriterWaits), counts.keyWriterWaitMs - previous.keyWriterWaitMs); previous = counts; previousFlips = flipsCounted.load(); previousUnsignaled = flipBatchesUnsignaled.load(); 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 ed1cb37640..546be83644 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp @@ -70,19 +70,23 @@ void Driver::deferCompletion(const Submission& submission) { queue0Uncommitted.push_back(received); } completionDeferred() = true; - DrawPipeline::Queue0().Enqueue([this, serial = submission.serial, received, labelWrites = submission.labelWrites] { - completeSubmission(serial, received, labelWrites); + DrawPipeline::Queue0().Enqueue([this, serial = submission.serial, received, labelWrites = submission.labelWrites, progress = submission.progress] { + completeSubmission(serial, received, labelWrites, progress); }, {}); } -void Driver::submitOpenWork() { - if (deferredLabels().labels.empty() && !Graphics::Recorder::PendingLabelSince().has_value() && Graphics::Recorder::RecordedWorkSinceSubmit() == 0) return; +void Driver::submitOpenWork(const std::shared_ptr& progress) { + const bool work = !deferredLabels().labels.empty() || Graphics::Recorder::PendingLabelSince().has_value() || Graphics::Recorder::RecordedWorkSinceSubmit() != 0; + if (!work && !progress) return; ++submissionCosts(0).endSubmits; GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::End); std::lock_guard gpuLock(GuestMemory::GpuMutex()); const auto localDevice = device.Load(); - recordDeferredLabels(localDevice.get(), 0); - if (localDevice != nullptr) localDevice->SubmitRecorded(true); + if (work) { + recordDeferredLabels(localDevice.get(), 0); + if (localDevice != nullptr) localDevice->SubmitRecorded(true); + } + if (progress) progress->Publish(localDevice, localDevice ? localDevice->SubmittedEpoch() : 0); } void Driver::wakeQueueZeroForCompletions() { @@ -91,7 +95,7 @@ void Driver::wakeQueueZeroForCompletions() { changed.notify_all(); } -void Driver::completeSubmission(std::uint64_t serial, std::uint64_t received, std::span labelWrites) { +void Driver::completeSubmission(std::uint64_t serial, std::uint64_t received, std::span labelWrites, const std::shared_ptr& progress) { struct Complete { Driver& driver; std::uint64_t serial, received; @@ -109,7 +113,7 @@ void Driver::completeSubmission(std::uint64_t serial, std::uint64_t received, st if (notify) driver.changed.notify_all(); } } complete{*this, serial, received, labelWrites}; - submitOpenWork(); + submitOpenWork(progress); } const std::atomic*& Driver::workerQueued() { @@ -261,6 +265,7 @@ void Driver::run(std::uint32_t id) noexcept { if (profile && submission.enqueuedAt != std::chrono::steady_clock::time_point{}) costs.dequeueNs += static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - submission.enqueuedAt).count()); } execute(submission); + if (submission.progress && submission.rewindTail == nullptr && !completionDeferred()) submitOpenWork(submission.progress); if (traceGpu) std::fprintf(stderr, "[gpu] %.1f done serial=%llu queue=0x%x\n", TraceMs(), static_cast(submission.serial), id); const auto completeStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; PerformanceContext completionContext(id == 0 ? frameTiming() : FrameTiming::Async()); 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 13d45c0f36..eeaea3e126 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp" @@ -21,6 +22,61 @@ namespace AgcDriver::DriverDetail { namespace { +bool& submitsQueue0() { + struct SubmitsQueue0Storage {}; + return HostThreadLocal(); +} + +std::uint64_t& flipSubmissionsOfThread() { + struct FlipSubmissionsStorage {}; + return HostThreadLocal(); +} + +bool progressEligible(const Submission& submission) { + if (submission.queue != 0 || !submission.flips.empty() + || !submission.renderingWaits.empty() || !submission.conditionalEnds.empty()) return false; + bool terminalRelease = false; + std::optional knownWrite; + for (std::size_t at = 0; at < submission.commands.size();) { + const auto packet = std::span(submission.commands).subspan(at); + const auto count = Pm4::PacketWords(packet[0]); + if (count == 0 || count > packet.size()) return false; + const auto opcode = (packet[0] >> 8u) & 0xffu; + const bool type3 = (packet[0] >> 30u) == 3; + if (type3 && (opcode == 0x3c || opcode == 0x93)) { + if (!knownWrite || Pm4::Predicated(packet[0]) || ((packet[1] >> 4u) & 3u) != 1u) return false; + const auto wait = Pm4::DecodeWait(packet.first(count)); + const auto bytes = knownWrite->Bytes(); + if (wait.address < knownWrite->address) return false; + const auto offset = wait.address - knownWrite->address; + if (offset > bytes.size() || wait.bytes > bytes.size() - offset) return false; + std::uint64_t value = 0; + std::memcpy(&value, bytes.data() + offset, wait.bytes); + if (!Pm4::WaitComparesValue(packet.first(count), value)) return false; + terminalRelease = false; + at += count; + continue; + } + if (type3 && opcode == 0x59 && submission.rewindTail != nullptr && at + count == submission.commands.size()) { + at += count; + continue; + } + if (!Pm4::FillerPacket(packet[0]) && !(type3 && opcode == 0x10) && !Pm4::IsTagMarker(packet.first(count))) { + if (type3 && (opcode == 0x49 || opcode == 0x37)) { + const auto write = Pm4::DecodeLabelWrite(packet.first(count)); + terminalRelease = opcode == 0x49 && write.has_value(); + if (write && !Pm4::Predicated(packet[0]) && (opcode == 0x37 || (packet[2] >> 29u) < 3)) knownWrite = *write; + else if (opcode != 0x49 || (packet[2] >> 29u) != 0) knownWrite.reset(); + } else { + terminalRelease = false; + if (!type3 || (opcode != 0x42 && opcode != 0x58)) knownWrite.reset(); + } + } + at += count; + } + return terminalRelease; +} + std::uint64_t captureTime() { return static_cast(std::llround(TraceMs() * 1000.0)); } @@ -38,11 +94,10 @@ bool EopRelease(std::span words) { return words.size() >= 8 && (words[0] >> 30u) == 3u && Pm4::PacketWords(words[0]) == 8u && ((words[0] >> 8u) & 0xffu) == 0x49u && ((words[2] >> 24u) & 7u) == 2u; } -bool& submitsQueue0() { - struct SubmitsQueue0Storage {}; - return HostThreadLocal(); } +std::uint64_t Driver::FlipSubmissions() const { + return flipSubmissionsOfThread(); } std::deque& Driver::releasedTails() { @@ -282,6 +337,7 @@ void Driver::executeRewindTail(const Submission& stalled) { checkStopping(); noteAwaitingTitle(stalled.queue, reinterpret_cast(stalled.rewindTail - 1)); stall.Check("REWIND at", reinterpret_cast(stalled.rewindTail - 1)); + if (stalled.queue == 0 && completionsPending()) reapCompletionLabels(); PollSleep(); } } @@ -366,6 +422,10 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) { const auto validated = profile ? std::chrono::steady_clock::now() : start; auto* const capture = SubmissionCapture::Active(); auto captured = capture != nullptr ? SubmissionCapture::Prepare(queue, SubmissionKind::Submit, submission.commands, submission.conditionalEnds) : CapturedSubmission{}; + static const bool progressEnabled = std::getenv("APS5_SUBMIT_PROGRESS") != nullptr; + const auto progress = progressEnabled && progressEligible(submission) ? std::make_shared() : nullptr; + submission.progress = progress; + const auto progressDeadline = progress ? std::chrono::steady_clock::now() + std::chrono::milliseconds(2) : std::chrono::steady_clock::time_point{}; { std::unique_lock lock(mutex); rethrowFailure(); @@ -401,11 +461,31 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) { submission.holdsFlip = true; flipsAhead.fetch_add(1, std::memory_order_acq_rel); } + if (!submission.flips.empty()) ++flipSubmissionsOfThread(); enqueue(std::move(submission)); ++accepted; } changed.notify_all(); if (capture != nullptr) writeCaptured(*this, *capture, captured); + if (progress) { + PerformanceTimer timing("Driver.SubmitProgress"); + const auto [localDevice, epoch] = progress->WaitUntil(progressDeadline); + const auto now = std::chrono::steady_clock::now(); + bool gpuReady = false; + if (localDevice && epoch != 0 && now < progressDeadline) { + const auto remaining = std::chrono::duration_cast(progressDeadline - now).count(); + gpuReady = localDevice->WaitRecordedFor(epoch, static_cast(remaining)); + } + if (profile) { + static std::atomic eligible{0}, ready{0}, expired{0}; + if (gpuReady) ready.fetch_add(1, std::memory_order_relaxed); + else expired.fetch_add(1, std::memory_order_relaxed); + const auto count = eligible.fetch_add(1, std::memory_order_relaxed) + 1; + if (count % 1000 == 0) AgcDriver::ProfilePrint_nid_no_patch("[submit-progress] %llu eligible terminal releases, %llu GPU-ready returns, %llu expired or unsupported\n", + static_cast(count), static_cast(ready.load()), static_cast(expired.load())); + } + CheckFailure(); + } } void Driver::SuspendPoint() { @@ -465,7 +545,18 @@ bool Driver::orderReleased(std::uint32_t queue, std::uint64_t received) const { return runningWorkers.load(std::memory_order_acquire) == 0 && !completionsPending() && !Graphics::Recorder::SnapshotWriteOverlaps(awaited, 4); } +void Driver::noteWaitFields(std::uint32_t queue, std::span packet) { + const auto fields = Pm4::DecodeWait(packet); + auto& slot = awaitedSlots[queue == 0 ? 0 : queue - 0x20 + 1]; + slot.reference.store(fields.reference, std::memory_order_relaxed); + slot.mask.store(fields.mask, std::memory_order_relaxed); + slot.shape.store(static_cast(fields.function) | static_cast(fields.bytes) << 32u, std::memory_order_relaxed); +} + void Driver::noteWaitBlocked(std::uint32_t queue, std::uint64_t awaited, bool blocked) { + auto& slot = awaitedSlots[queue == 0 ? 0 : queue - 0x20 + 1]; + if (!blocked) slot.shape.store(0, std::memory_order_relaxed); + slot.address.store(blocked ? awaited : 0, std::memory_order_release); if (blocked) { if (runningWorkers.fetch_sub(1, std::memory_order_acq_rel) == 1) { idleEpoch.fetch_add(1, std::memory_order_release); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp index 52f65cc2d0..91b97871d7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "ControlFlow/RequestSerializer.hpp" #include #include @@ -7,7 +8,7 @@ namespace AgcDriver::DriverDetail { void Driver::reportSkip(const char* kind, const std::string& what) { - static std::mutex reportedMutex; + static AgcDriver::Mutex reportedMutex; static std::set reported; static std::map requestDumps; @@ -50,7 +51,7 @@ void Driver::reportSkip(const char* kind, const std::string& what) { } std::string Driver::dumpRequest(std::uint64_t address, const ShaderRecompiler::RecompileRequest& request, std::string_view suffix) { - static std::mutex dumpMutex; + static AgcDriver::Mutex dumpMutex; static std::set dumped; char hex[32]; std::snprintf(hex, sizeof(hex), "%llx", static_cast(address)); 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 b69bccd8fe..312b71958b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -1,9 +1,11 @@ #include "prx/libc/include/general/LogMacros.hpp" #include "ControlFlow/GraphBuilder.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" +#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" +#include "prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "CacheKey.hpp" @@ -136,7 +138,7 @@ std::shared_ptr ReadRawShader(std::uint64_t address) { cache.erase(found); } const auto bytes = result->code.size() * sizeof(std::uint32_t); - while (!cache.empty() && (cache.size() >= 64 || cacheBytes + bytes > 8 * 1024 * 1024)) { + while (!cache.empty() && (cache.size() >= 4096 || cacheBytes + bytes > 64 * 1024 * 1024)) { cacheBytes -= cache.back()->code.size() * sizeof(std::uint32_t); cache.pop_back(); } @@ -220,6 +222,24 @@ bool CommittedAbi(const ShaderSnapshot& snapshot, std::span return std::ranges::any_of(snapshot.prepared->registeredAbis, [&](const auto& abi) { return std::ranges::equal(abi, key); }); } +std::optional>> CommittedGraphicsAbi(const ShaderSnapshot& owner, std::span key, const std::shared_ptr& registry) { + std::unique_lock lock(owner.prepared->mutex); + AwaitRegisteredPreparation(*owner.prepared, lock); + const auto& abis = owner.prepared->graphicsAbis; + const auto found = std::ranges::find_if(abis, [&](const auto& abi) { return std::ranges::equal(abi.key, key) && abi.registry.lock() == registry; }); + if (found == abis.end()) return std::nullopt; + return found->stages; +} + +bool CommittedLink(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType) { + if (primitiveType == 7 || primitiveType == 17) return false; + std::unique_lock lock(front.prepared->mutex); + AwaitRegisteredPreparation(*front.prepared, lock); + const auto& fragments = front.prepared->fragments; + if (front.prepared->rectangleRequested || std::ranges::any_of(fragments, [](const auto& entry) { return entry.expired(); })) return false; + return fragment == nullptr || std::ranges::any_of(fragments, [&](const auto& entry) { return entry.lock() == fragment; }); +} + } bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset) { @@ -236,6 +256,216 @@ bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset return used; } +namespace { + +bool readOnlyMemoryOp(ShaderRecompiler::RdnaOpcode op) { + using Op = ShaderRecompiler::RdnaOpcode; + switch (op) { + case Op::SLoadDword: + case Op::SLoadDwordx2: + case Op::SLoadDwordx4: + case Op::SLoadDwordx8: + case Op::SLoadDwordx16: + case Op::BufferLoadFormatX: + case Op::BufferLoadFormatXy: + case Op::BufferLoadFormatXyz: + case Op::BufferLoadFormatXyzw: + case Op::BufferLoadDword: + case Op::BufferLoadDwordx2: + case Op::BufferLoadDwordx4: + case Op::ImageSample: + case Op::ImageSampleLz: + case Op::ImageSampleBCl: + case Op::ImageSampleBClO: + case Op::ImageSampleBO: + case Op::ImageSampleC: + case Op::ImageSampleCB: + case Op::ImageSampleCBCl: + case Op::ImageSampleCBClO: + case Op::ImageSampleCBO: + case Op::ImageSampleCCl: + case Op::ImageSampleCClO: + case Op::ImageSampleCD: + case Op::ImageSampleCDCl: + case Op::ImageSampleCDClO: + case Op::ImageSampleCDO: + case Op::ImageSampleCL: + case Op::ImageSampleCLzO: + case Op::ImageSampleCLO: + case Op::ImageSampleCO: + case Op::ImageSampleCl: + case Op::ImageSampleClO: + case Op::ImageSampleD: + case Op::ImageSampleDCl: + case Op::ImageSampleDO: + case Op::ImageSampleLzO: + case Op::ImageSampleO: + case Op::ImageSampleCd: + case Op::ImageSampleCdCl: + case Op::ImageSampleCCd: + case Op::ImageSampleCCdCl: + case Op::ImageSampleCdO: + case Op::ImageSampleCdClO: + case Op::ImageSampleCCdO: + case Op::ImageSampleCCdClO: + case Op::ImageSampleDG16: + case Op::ImageSampleDClG16: + case Op::ImageSampleCDG16: + case Op::ImageSampleCDClG16: + case Op::ImageSampleDOG16: + case Op::ImageSampleDClOG16: + case Op::ImageSampleCDOG16: + case Op::ImageSampleCDClOG16: + case Op::ImageSampleCdG16: + case Op::ImageSampleCdClG16: + case Op::ImageSampleCCdG16: + case Op::ImageSampleCCdClG16: + case Op::ImageSampleCdOG16: + case Op::ImageSampleCdClOG16: + case Op::ImageSampleCCdOG16: + case Op::ImageSampleCCdClOG16: + case Op::ImageLoad: + case Op::ImageGetResinfo: + case Op::SBufferLoadDword: + case Op::SBufferLoadDwordx2: + case Op::SBufferLoadDwordx4: + case Op::SBufferLoadDwordx8: + case Op::SBufferLoadDwordx16: + case Op::SScratchLoadDword: + case Op::SScratchLoadDwordx2: + case Op::SScratchLoadDwordx4: + case Op::BufferLoadUbyte: + case Op::BufferLoadSbyte: + case Op::BufferLoadUshort: + case Op::BufferLoadSshort: + case Op::BufferLoadDwordx3: + case Op::BufferLoadUbyteD16: + case Op::BufferLoadUbyteD16Hi: + case Op::BufferLoadSbyteD16: + case Op::BufferLoadSbyteD16Hi: + case Op::BufferLoadShortD16: + case Op::BufferLoadShortD16Hi: + case Op::TbufferLoadFormatX: + case Op::TbufferLoadFormatXy: + case Op::TbufferLoadFormatXyz: + case Op::TbufferLoadFormatXyzw: + case Op::TbufferLoadFormatD16X: + case Op::TbufferLoadFormatD16Xy: + case Op::TbufferLoadFormatD16Xyz: + case Op::TbufferLoadFormatD16Xyzw: + case Op::BufferLoadFormatD16X: + case Op::BufferLoadFormatD16Xy: + case Op::BufferLoadFormatD16Xyz: + case Op::BufferLoadFormatD16Xyzw: + case Op::BufferLoadFormatD16HiX: + case Op::FlatLoadUbyte: + case Op::FlatLoadSbyte: + case Op::FlatLoadUshort: + case Op::FlatLoadSshort: + case Op::FlatLoadDword: + case Op::FlatLoadDwordx2: + case Op::FlatLoadDwordx3: + case Op::FlatLoadDwordx4: + case Op::GlobalLoadDwordAddtid: + case Op::FlatLoadUbyteD16: + case Op::FlatLoadUbyteD16Hi: + case Op::FlatLoadSbyteD16: + case Op::FlatLoadSbyteD16Hi: + case Op::FlatLoadShortD16: + case Op::FlatLoadShortD16Hi: + case Op::ImageGetLod: + case Op::ImageLoadMip: + case Op::ImageLoadPck: + case Op::ImageLoadPckSgn: + case Op::ImageLoadMipPck: + case Op::ImageLoadMipPckSgn: + case Op::ImageLoadBy2: + case Op::ImageLoadBy4: + case Op::ImageLoadMipBy2: + case Op::ImageLoadMipBy4: + case Op::ImageLoadPck2: + case Op::ImageLoadPck4: + case Op::ImageLoadMipPck2: + case Op::ImageLoadMipPck4: + case Op::ImageMsaaLoad: + case Op::ImageGather4Lz: + case Op::ImageGather4C: + case Op::ImageGather4CLz: + case Op::ImageGather4LzO: + case Op::ImageGather4CO: + case Op::ImageGather4CLzO: + case Op::ImageGather4h: + case Op::ImageGather4hPck: + case Op::ImageGather4: + case Op::ImageGather4B: + case Op::ImageGather4BCl: + case Op::ImageGather4BClO: + case Op::ImageGather4BO: + case Op::ImageGather4Cl: + case Op::ImageGather4ClO: + case Op::ImageGather4CB: + case Op::ImageGather4CBCl: + case Op::ImageGather4CBClO: + case Op::ImageGather4CBO: + case Op::ImageGather4CCl: + case Op::ImageGather4CClO: + case Op::ImageGather4CL: + case Op::ImageGather4CLO: + case Op::ImageGather4L: + case Op::ImageGather4LO: + case Op::ImageGather4O: + case Op::ImageBvhIntersectRay: + case Op::SGl1Inv: + case Op::SDcacheInv: + case Op::SDcacheWb: + case Op::SMemtime: + case Op::SMemrealtime: + case Op::SDcacheDiscard: + case Op::SDcacheDiscardX2: + case Op::BufferGl0Inv: + case Op::BufferGl1Inv: + return true; + default: + return false; + } +} + +bool decodedWritesMemory(std::span code) { + using Family = ShaderRecompiler::RdnaInstructionFamily; + using Op = ShaderRecompiler::RdnaOpcode; + try { + const auto decoded = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(code); + return std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) { + switch (instruction.family) { + case Family::MUBUF: case Family::MTBUF: case Family::FLAT: case Family::MIMG: case Family::SMEM: + return !readOnlyMemoryOp(instruction.op); + case Family::DS: + return instruction.gds || instruction.op == Op::DsGwsInit || instruction.op == Op::DsGwsBarrier || instruction.op == Op::DsGwsSemaV || instruction.op == Op::DsGwsSemaP || instruction.op == Op::DsGwsSemaBr || instruction.op == Op::DsGwsSemaReleaseAll || instruction.op == Op::DsOrderedCount || instruction.op == Op::DsAppend || instruction.op == Op::DsConsume; + case Family::Unknown: + return true; + default: + return instruction.op == Op::SSetpcB64 || instruction.op == Op::SSwappcB64 || instruction.op == Op::SRfeB64; + } + }); + } catch (const std::exception&) { + return true; + } +} + +} + +bool ProgramWritesMemory(const ShaderSnapshot& snapshot, std::size_t codeOffset) { + constexpr std::uint8_t Unknown = 0, Reads = 1, Writes = 2; + if (codeOffset == 0) { + const auto cached = snapshot.memoryWrites->load(std::memory_order_acquire); + if (cached != Unknown) return cached == Writes; + } + if (codeOffset > snapshot.code.size()) return true; + const bool writes = decodedWritesMemory(std::span(snapshot.code).subspan(codeOffset)); + if (codeOffset == 0) snapshot.memoryWrites->store(writes ? Writes : Reads, std::memory_order_release); + return writes; +} + std::uint32_t PositionExportLayout(const ShaderSnapshot* snapshot, std::size_t codeOffset, std::span code, std::uint32_t paClVsOutCntl, const ShaderSnapshot* continuation) { constexpr std::uint8_t Unknown = 0, Unused = 1, Used = 2; const auto bits = paClVsOutCntl & Graphics::PositionExportLayoutBits; @@ -351,6 +581,10 @@ const PreparedShaderState& ShaderPreparationTransaction::Read(const ShaderSnapsh return changes.emplace(snapshot.prepared.get(), State::Change{snapshot.prepared, *snapshot.prepared, false, snapshot.prepared->entries.size(), snapshot.prepared->registeredAbis.size()}).first->second.prepared; } +bool ShaderPreparationTransaction::Tracks(const ShaderSnapshot& snapshot) const { + return root->changes.contains(snapshot.prepared.get()); +} + void ShaderPreparationTransaction::Hold() { require(!committed, "shader preparation transaction is already committed"); root->Hold(); @@ -591,6 +825,65 @@ void PublishRegisteredShader(std::shared_ptr& registry, const st namespace { +bool LazyShaderPreparation() { + static const bool lazy = std::getenv("APS5_LAZY_SHADER_PREPARE") != nullptr; + return lazy; +} + +using PreparedAtUse = std::optional>; + +PreparedAtUse PrepareDeferredAbis(const ShaderSnapshot& snapshot, std::unique_lock& lock); + +struct DeferredAbiUseCounters { + std::atomic firstUses{0}; + std::atomic found{0}; + std::atomic own{0}; + std::atomic entries{0}; + std::atomic matchedEntries{0}; + std::atomic lastReport{0}; +}; + +DeferredAbiUseCounters& DeferredAbiCounters() { + static DeferredAbiUseCounters counters; + return counters; +} + +void ReportDeferredAbiUses() { + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + if (!profile) return; + auto& counters = DeferredAbiCounters(); + const auto now = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + auto previous = counters.lastReport.load(std::memory_order_relaxed); + if ((previous != 0 && now - previous < 60000) || !counters.lastReport.compare_exchange_strong(previous, now, std::memory_order_relaxed)) return; + const auto entries = counters.entries.load(std::memory_order_relaxed); + const auto matched = counters.matchedEntries.load(std::memory_order_relaxed); + AgcDriver::ProfilePrint_nid_no_patch("[shaders] deferred ABIs: %llu prepared at first use, %llu found their artifact there, %llu prepared their own; %llu of the %llu entries prepared from them never matched a use\n", static_cast(counters.firstUses.load(std::memory_order_relaxed)), static_cast(counters.found.load(std::memory_order_relaxed)), static_cast(counters.own.load(std::memory_order_relaxed)), static_cast(entries - std::min(entries, matched)), static_cast(entries)); +} + +void NoteMatchedEntry(PreparedShaders::Entry& entry, const PreparedAtUse& preparedAtUse) { + auto& counters = DeferredAbiCounters(); + bool changed = false; + if (entry.fromDeferredAbi && !entry.matched) { + entry.matched = true; + counters.matchedEntries.fetch_add(1, std::memory_order_relaxed); + changed = true; + } + if (preparedAtUse) { + counters.firstUses.fetch_add(1, std::memory_order_relaxed); + if (std::ranges::find(*preparedAtUse, entry.handle.get()) != preparedAtUse->end()) counters.found.fetch_add(1, std::memory_order_relaxed); + changed = true; + } + if (changed) ReportDeferredAbiUses(); +} + +void NoteOwnPreparation(const PreparedAtUse& preparedAtUse) { + if (!preparedAtUse) return; + auto& counters = DeferredAbiCounters(); + counters.firstUses.fetch_add(1, std::memory_order_relaxed); + counters.own.fetch_add(1, std::memory_order_relaxed); + ReportDeferredAbiUses(); +} + void ReportPreparedAtUse(const ShaderSnapshot& snapshot, const ShaderRecompiler::RecompileRequest& request) { if (request.context.convertedSamples) { APS5_LOG_OUT_DEBUG("Shader 0x%llx samples a converted image; preparing its converted-sample variant at use", static_cast(request.shader.codeAddress)); @@ -626,9 +919,15 @@ std::shared_ptr SourceHandleFor(const Shad ShaderRecompiler::BuildPreparedShaderKey(request, key); std::unique_lock lock(snapshot.prepared->mutex); AwaitRegisteredPreparation(*snapshot.prepared, lock); - for (const auto& entry : snapshot.prepared->entries) { - if (entry.codeOffset == codeOffset && ShaderRecompiler::MatchesPreparedShader(request, *entry.handle, key)) return entry.handle; + PreparedAtUse preparedAtUse; + if (!snapshot.prepared->deferredAbis.empty() || snapshot.prepared->deferredPreparing) preparedAtUse = PrepareDeferredAbis(snapshot, lock); + for (auto& entry : snapshot.prepared->entries) { + if (entry.codeOffset == codeOffset && ShaderRecompiler::MatchesPreparedShader(request, *entry.handle, key)) { + NoteMatchedEntry(entry, preparedAtUse); + return entry.handle; + } } + NoteOwnPreparation(preparedAtUse); ReportPreparedAtUse(snapshot, request); auto handle = PrepareShaderWithDiagnostics(request); snapshot.prepared->entries.push_back({codeOffset, handle}); @@ -779,12 +1078,18 @@ ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& s struct PreparedKeyStorage {}; auto& key = HostThreadLocal, PreparedKeyStorage>(); ShaderRecompiler::BuildPreparedShaderKey(source, key); - for (const auto& entry : snapshot.prepared->entries) { + PreparedAtUse preparedAtUse; + if (!snapshot.prepared->deferredAbis.empty() || snapshot.prepared->deferredPreparing) preparedAtUse = PrepareDeferredAbis(snapshot, lock); + for (auto& entry : snapshot.prepared->entries) { if (entry.codeOffset != codeOffset) continue; invocationRequest.shader.code = ShaderRecompiler::GetPreparedCode(*entry.handle); if (!linked.code.empty() && !std::ranges::equal(invocationRequest.shader.code, linked.code)) continue; - if (auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(invocationRequest, entry.handle, key)) return std::move(*invocation); + if (auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(invocationRequest, entry.handle, key)) { + NoteMatchedEntry(entry, preparedAtUse); + return std::move(*invocation); + } } + NoteOwnPreparation(preparedAtUse); ReportPreparedAtUse(snapshot, source); auto handle = PrepareShaderWithDiagnostics(source); invocationRequest = source; @@ -1071,6 +1376,60 @@ std::vector PrepareRegistered(const ShaderSnapshot& snap return plan != nullptr ? PrepareRegistered(*plan) : std::vector{}; } +PreparedAtUse PrepareDeferredAbis(const ShaderSnapshot& snapshot, std::unique_lock& lock) { + auto& prepared = *snapshot.prepared; + if (prepared.deferredPreparing) { + prepared.settled.wait(lock, [&] { return !prepared.deferredPreparing; }); + return std::nullopt; + } + if (prepared.deferredAbis.empty()) return std::nullopt; + std::vector deferred; + deferred.swap(prepared.deferredAbis); + prepared.deferredPreparing = true; + struct Preparing { + PreparedShaders& prepared; + std::unique_lock& lock; + ~Preparing() { + if (!lock.owns_lock()) lock.lock(); + prepared.deferredPreparing = false; + prepared.settled.notify_all(); + } + } preparing{prepared, lock}; + lock.unlock(); + std::vector entries; + for (const auto& abi : deferred) { + const auto device = abi.device.lock(); + if (device == nullptr || snapshot.registeredState == nullptr) continue; + QueueState state{}; + state.shader = snapshot.registeredState->shader; + state.context = snapshot.registeredState->context; + state.userConfig = snapshot.registeredState->userConfig; + for (const auto& [offset, value] : abi.context) state.context[offset] = value; + for (const auto& [offset, value] : abi.primitive) state.userConfig[offset] = value; + try { + const auto plan = PlanRegistered(snapshot, *device, state, false); + if (plan == nullptr) continue; + plan->prebuildDevice = device; + auto made = PrepareRegistered(*plan); + std::ranges::move(made, std::back_inserter(entries)); + } catch (const std::exception& error) { + APS5_LOG_ERR("Shader 0x%llx could not be prepared at its first use for its resolved ABI (%s); preparing it for the use instead", static_cast(snapshot.codeAddress), error.what()); + continue; + } + } + lock.lock(); + std::vector added; + for (auto& entry : entries) { + const auto duplicate = std::ranges::any_of(prepared.entries, [&](const auto& existing) { return existing.codeOffset == entry.codeOffset && existing.handle->artifact == entry.handle->artifact; }); + if (duplicate) continue; + entry.fromDeferredAbi = true; + added.push_back(entry.handle.get()); + prepared.entries.push_back(std::move(entry)); + } + DeferredAbiCounters().entries.fetch_add(added.size(), std::memory_order_relaxed); + return added; +} + void DeferPipelineStage(PreparedShaderState& prepared, const ShaderSnapshot& snapshot, const char* reason) { if (!prepared.deferred) APS5_LOG_ERR("Shader 0x%llx could not be prepared when the title created its pipeline (%s); preparing it when it is used", static_cast(snapshot.codeAddress), reason); prepared.deferred = true; @@ -1146,9 +1505,8 @@ public: Closing() = true; pools = {Existing(), ExistingPrebuild()}; } - for (auto* pool : pools) { - if (pool != nullptr) pool->Close(); - } + if (pools[0] != nullptr) pools[0]->Close(false); + if (pools[1] != nullptr) pools[1]->Close(true); } private: @@ -1204,11 +1562,17 @@ public: idle.wait(lock, [&] { return unfinished.empty() || *unfinished.begin() > ticket; }); } - void Close() { + void Close(bool dropQueued) { std::vector joined; + std::deque dropped; { std::unique_lock lock(mutex); closed = true; + if (dropQueued) { + dropped.swap(tasks); + for (const auto& task : dropped) unfinished.erase(task.sequence); + idle.notify_all(); + } idle.wait(lock, [&] { return tasks.empty() && running == 0; }); joined.swap(workers); } @@ -1338,7 +1702,7 @@ void PrepareRegisteredInBackground(std::shared_ptr snapsho try { entries = PrepareRegistered(*plan); } catch (const std::exception& error) { - APS5_LOG_ERR("Shader 0x%llx could not be prepared after registration: %s", static_cast(snapshot->codeAddress), error.what()); + APS5_LOG_ERR("Shader 0x%llx snapshot=0x%llx could not be prepared after registration: %s", static_cast(snapshot->codeAddress), static_cast(reinterpret_cast(snapshot.get())), error.what()); failure = std::current_exception(); } catch (...) { failure = std::current_exception(); @@ -1435,7 +1799,10 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr}); continue; } - if (vertex) vertex->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, decoded.state.paClVsOutCntl, program.tailContinuation.get()) | decoded.state.layerExports; + if (vertex) { + vertex->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, decoded.state.paClVsOutCntl, program.tailContinuation.get()) | decoded.state.layerExports; + 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, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}}; std::vector key; ShaderRecompiler::BuildPreparedShaderKey(request, key); @@ -1531,14 +1898,23 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std auto& key = HostThreadLocal, GraphicsAbiKeyStorage>(); BuildRegisteredAbiKey(state, *localDevice, key); const auto primitiveType = state.userConfig.find(0x242u); - for (const auto& abi : transaction.Read(*owner).graphicsAbis) { - if (abi.key != key || abi.registry.lock() != registry) continue; - for (const auto& stage : abi.stages) { + const auto resolveStages = [&](const std::vector>& stages) { + for (const auto& stage : stages) { const auto snapshot = stage.lock(); require(snapshot != nullptr, "prepared graphics ABI lost a registered stage"); if (snapshot->type != 1) ResolvePreparedGraphics(*snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target(), true); } transaction.Commit(); + }; + if (!transaction.Tracks(*owner)) { + if (const auto committed = CommittedGraphicsAbi(*owner, key, registry)) { + resolveStages(*committed); + return; + } + } + for (const auto& abi : transaction.Read(*owner).graphicsAbis) { + if (abi.key != key || abi.registry.lock() != registry) continue; + resolveStages(abi.stages); return; } DrawDecode decoded{}; @@ -1574,6 +1950,55 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std transaction.Commit(); } +std::size_t Driver::PreparedEntries(const Shader* shader) { + std::shared_ptr snapshot; + { + std::lock_guard lock(mutex); + if (shaders == nullptr) return 0; + snapshot = RegisteredHeader(*shaders, shader); + } + if (snapshot == nullptr) return 0; + std::unique_lock lock(snapshot->prepared->mutex); + AwaitRegisteredPreparation(*snapshot->prepared, lock); + return snapshot->prepared->entries.size(); +} + +Driver::DeferredAbiUseCounts Driver::DeferredAbiUses() { + auto& counters = DeferredAbiCounters(); + const auto entries = counters.entries.load(std::memory_order_relaxed); + const auto matched = counters.matchedEntries.load(std::memory_order_relaxed); + return {counters.firstUses.load(std::memory_order_relaxed), counters.found.load(std::memory_order_relaxed), counters.own.load(std::memory_order_relaxed), entries, entries - std::min(entries, matched)}; +} + +std::vector> Driver::PreparedEntryHashes(const Shader* shader) { + std::shared_ptr snapshot; + { + std::lock_guard lock(mutex); + if (shaders == nullptr) return {}; + snapshot = RegisteredHeader(*shaders, shader); + } + if (snapshot == nullptr) return {}; + std::unique_lock lock(snapshot->prepared->mutex); + AwaitRegisteredPreparation(*snapshot->prepared, lock); + std::vector> hashes; + for (const auto& entry : snapshot->prepared->entries) { + if (entry.handle == nullptr || entry.handle->artifact == nullptr) continue; + const auto hash = entry.handle->artifact->artifact.spirv.ContentHash(); + std::uint64_t key = 0xcbf29ce484222325ull; + for (const auto word : entry.handle->staticKey) key = (key ^ word) * 0x100000001b3ull; + hashes.push_back({hash.low, hash.high ^ key}); + } + std::ranges::sort(hashes); + return hashes; +} + +std::array Driver::PrebuiltLibraries() { + if (auto* pool = RegistrationPreparePool::ExistingPrebuild()) pool->AwaitSubmitted(); + const auto localDevice = device.Load(); + if (localDevice == nullptr) return {}; + return Graphics::PipelineLibraryCountersOf(localDevice->Device()).built; +} + void Driver::ResolveShaderAbi(const Shader* shader, std::span context, std::span primitive) { BindTitleThread(); PerformanceContext timingContext(FrameTiming::Preparation()); @@ -1611,6 +2036,31 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span, RegisteredAbiKeyStorage>(); BuildRegisteredAbiKey(state, *localDevice, key); + if (LazyShaderPreparation()) { + const auto& resolved = transaction.Read(*snapshot).registeredAbis; + if (std::ranges::find(resolved, key) != resolved.end()) { + transaction.Commit(); + return; + } + std::optional invalid; + try { + static_cast(PlanRegistered(*snapshot, *localDevice, state, false)); + } catch (const std::exception& error) { + invalid = error.what(); + } + auto& prepared = transaction.Edit(*snapshot); + if (invalid) DeferPipelineStage(prepared, *snapshot, invalid->c_str()); + prepared.registeredAbis.push_back(key); + prepared.deferred = true; + transaction.Commit(); + if (invalid) return; + PreparedShaders::DeferredAbi deferred{key, {}, {}, localDevice}; + for (const auto reg : context) deferred.context.emplace_back(reg.offset, reg.value); + for (const auto reg : primitive) deferred.primitive.emplace_back(reg.offset, reg.value); + std::lock_guard lock(snapshot->prepared->mutex); + snapshot->prepared->deferredAbis.push_back(std::move(deferred)); + return; + } std::optional deferral; const auto prepare = [&] { try { @@ -1624,6 +2074,10 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span{} : prepare(); const auto& registeredAbis = transaction.Read(*snapshot).registeredAbis; if (std::ranges::find(registeredAbis, key) != registeredAbis.end()) { @@ -1657,6 +2111,37 @@ std::uint64_t NullPixelProgramAddress() { return reinterpret_cast(NullPixelCode); } +alignas(256) static const std::uint32_t DepthCopyMultisamplePixelCode[64] = {0x360604ffu, 0x0000ffffu, 0x2c080490u, 0x7e0a0208u, 0xf0000130u, 0x00000603u, 0xbf8c3f70u, 0xf8001801u, 0x00000006u, 0xbf810000u}; +alignas(256) static const std::uint32_t DepthCopySinglePixelCode[64] = {0x360604ffu, 0x0000ffffu, 0x2c080490u, 0xf0000108u, 0x00000603u, 0xbf8c3f70u, 0xf8001801u, 0x00000006u, 0xbf810000u}; +static const Shader DepthCopyMultisamplePixelShader = [] { + Shader shader{}; + shader.file_header = 0x34333231u; + shader.version = 0x18u; + shader.code = DepthCopyMultisamplePixelCode; + shader.header_size = sizeof(Shader); + shader.shader_size = sizeof(DepthCopyMultisamplePixelCode); + shader.type = 1; + return shader; +}(); +static const Shader DepthCopySinglePixelShader = [] { + Shader shader{}; + shader.file_header = 0x34333231u; + shader.version = 0x18u; + shader.code = DepthCopySinglePixelCode; + shader.header_size = sizeof(Shader); + shader.shader_size = sizeof(DepthCopySinglePixelCode); + shader.type = 1; + return shader; +}(); + +std::uint64_t DepthCopyPixelProgramAddress(bool multisampled) { + return reinterpret_cast(multisampled ? DepthCopyMultisamplePixelCode : DepthCopySinglePixelCode); +} + +std::uint32_t DepthCopyPixelUserWords(bool multisampled) { + return multisampled ? 9u : 8u; +} + void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) { BindTitleThread(); PerformanceContext timingContext(FrameTiming::Preparation()); @@ -1709,6 +2194,10 @@ void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr< return; } } + if (!transaction.Tracks(front) && CommittedLink(front, fragment, primitiveType)) { + transaction.Commit(); + return; + } std::vector> fragments; { const auto& current = transaction.Read(front); @@ -1793,6 +2282,25 @@ ShaderSnapshot PrepareNullPixelProgram(const VulkanDevice& device) { return null; } +ShaderSnapshot PrepareDepthCopyPixelProgram(bool multisampled) { + const auto& shader = multisampled ? DepthCopyMultisamplePixelShader : DepthCopySinglePixelShader; + const auto* code = multisampled ? DepthCopyMultisamplePixelCode : DepthCopySinglePixelCode; + ShaderSnapshot copy{DepthCopyPixelProgramAddress(multisampled), reinterpret_cast(&shader), shader.type, {}, {}}; + copy.code.assign(code, code + 64); + copy.header.resize(sizeof(Shader)); + std::memcpy(copy.header.data(), &shader, sizeof(Shader)); + auto state = DecodeRegisteredState(copy); + state.shader.insert_or_assign(0x008u, static_cast(copy.codeAddress >> 8u)); + state.shader.insert_or_assign(0x009u, static_cast(copy.codeAddress >> 40u)); + state.shader.insert_or_assign(0x00bu, DepthCopyPixelUserWords(multisampled) << 1u); + state.context.insert_or_assign(0x1b3u, 0x8002u); + state.context.insert_or_assign(0x1b4u, 0x8002u); + state.context.insert_or_assign(0x1c5u, 1u); + copy.registeredState = std::make_shared(std::move(state)); + copy.prepared->deferred = true; + return copy; +} + std::shared_ptr Driver::RegisteredShaders() { std::lock_guard lock(mutex); return shaders; @@ -1846,13 +2354,24 @@ void Driver::RegisterShader(const Shader* shader) { localDevice = device; } snapshot.registeredState = std::make_shared(DecodeRegisteredState(snapshot)); + { + std::lock_guard lock(mutex); + if (shaders != nullptr) { + if (const auto found = shaders->find(snapshot.codeAddress); found != shaders->end()) { + for (const auto& current : found->second) { + if (current->registeredState == nullptr || current->type != snapshot.type || current->code != snapshot.code || *current->registeredState != *snapshot.registeredState) continue; + snapshot.registeredState = current->registeredState; + break; + } + } + } + } QueueState registered{}; registered.shader = snapshot.registeredState->shader; registered.context = snapshot.registeredState->context; registered.userConfig = snapshot.registeredState->userConfig; std::shared_ptr preparation; - static const bool lazy = std::getenv("APS5_LAZY_SHADER_PREPARE") != nullptr; - if (lazy) { + if (LazyShaderPreparation()) { static_cast(PlanRegistered(snapshot, *localDevice, registered, true, &snapshot.prepared->deferred)); snapshot.prepared->deferred = true; } else if (AsyncRegistrationPrepare()) { @@ -1890,6 +2409,9 @@ void Driver::RegisterShader(const Shader* shader) { if (shaders->find(NullPixelProgramAddress()) == shaders->end()) { PublishRegisteredShader(shaders, std::make_shared(PrepareNullPixelProgram(*localDevice))); } + for (const bool multisampled : {false, true}) { + if (shaders->find(DepthCopyPixelProgramAddress(multisampled)) == shaders->end()) PublishRegisteredShader(shaders, std::make_shared(PrepareDepthCopyPixelProgram(multisampled))); + } transaction.Commit(); if (preparation != nullptr) PrepareRegisteredInBackground(published, std::move(preparation)); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp index 858cada5e4..b43a5c49b2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp @@ -2,14 +2,20 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/HostThreadSlot.hpp" #include "ThreadOwned.hpp" #include namespace AgcDriver::DriverDetail { DeferredLabels& deferredLabels() { - static thread_local DeferredLabels* deferred = nullptr; - return ShaderRecompiler::ThreadOwned(deferred); + struct Slot {}; + auto* deferred = HostThreadSlot::Get(); + if (deferred == nullptr) { + ShaderRecompiler::ThreadOwned(deferred); + HostThreadSlot::Set(deferred); + } + return *deferred; } bool DeferLabels() { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp index 2720b91265..fe15539222 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -60,7 +61,7 @@ void Driver::traceLabel(std::span packet, std::uint32_t que } if (kind == nullptr) return; if (std::string_view(kind) != "WAIT_REG_MEM") { - static std::mutex historyMutex; + static AgcDriver::Mutex historyMutex; std::lock_guard lock(historyMutex); auto& entry = writeHistory()[writeCursor()++ % writeHistory().size()]; entry = {target, length, queue, opcode}; 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 6aead1a2cc..f1b7df1757 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp @@ -322,6 +322,7 @@ void Driver::waitMemory(std::span packet, std::uint32_t que if (entryTryFailed && pollService(false)) return; + noteWaitFields(queue, packet); noteWaitBlocked(queue, awaited, true); struct BlockedWait { Driver& driver; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/FrameCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/FrameCapture.cpp new file mode 100644 index 0000000000..7505860321 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/src/FrameCapture.cpp @@ -0,0 +1,455 @@ +#include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { +namespace { + +static_assert(std::endian::native == std::endian::little, "frame captures are stored in little-endian order"); + +constexpr std::size_t ChunkHeaderBytes = 64; + +void requireCapture(bool condition, const std::string& reason) { + if (!condition) throw std::runtime_error("frame capture: " + reason); +} + +class Encoder { +public: + template + void Put(TValue value) { + const auto at = bytes.size(); + bytes.resize(at + sizeof(TValue)); + std::memcpy(bytes.data() + at, &value, sizeof(TValue)); + } + + std::vector bytes; +}; + +class Decoder { +public: + explicit Decoder(std::span bytes) : bytes(bytes) {} + + template + TValue Take(const char* what) { + requireCapture(sizeof(TValue) <= bytes.size() - at, std::string("record ends inside ") + what); + TValue value{}; + std::memcpy(&value, bytes.data() + at, sizeof(TValue)); + at += sizeof(TValue); + return value; + } + + std::size_t Count(std::size_t elementBytes, const char* what) { + const auto count = Take(what); + requireCapture(count <= (bytes.size() - at) / elementBytes, std::string("count of ") + what + " exceeds its record"); + return count; + } + + bool Done() const { return at == bytes.size(); } + +private: + std::span bytes; + std::size_t at = 0; +}; + +void putRegisters(Encoder& encoder, const std::vector& registers) { + encoder.Put(static_cast(registers.size())); + for (const auto& value : registers) { + encoder.Put(value.offset); + encoder.Put(value.value); + } +} + +std::vector takeRegisters(Decoder& decoder) { + std::vector registers(decoder.Count(8, "registers")); + for (auto& value : registers) { + value.offset = decoder.Take("register offset"); + value.value = decoder.Take("register value"); + } + return registers; +} + +std::vector encodeCall(const FrameCall& call) { + Encoder encoder; + encoder.Put(call.ordinal); + encoder.Put(call.timeNs); + encoder.Put(call.thread); + encoder.Put(call.accepted); + encoder.Put(call.completed); + encoder.Put(call.queue); + encoder.Put(call.flags); + encoder.Put(static_cast(call.cpuFlip ? 1u : 0u)); + encoder.Put(call.address); + encoder.Put(call.size); + encoder.Put(static_cast(call.values.size())); + for (const auto value : call.values) encoder.Put(value); + putRegisters(encoder, call.context); + putRegisters(encoder, call.primitive); + return std::move(encoder.bytes); +} + +FrameCall decodeCall(FrameCallKind kind, std::span payload) { + Decoder decoder(payload); + FrameCall call; + call.kind = kind; + call.ordinal = decoder.Take("ordinal"); + call.timeNs = decoder.Take("time"); + call.thread = decoder.Take("thread"); + call.accepted = decoder.Take("accepted serial"); + call.completed = decoder.Take("completed serial"); + call.queue = decoder.Take("queue"); + call.flags = decoder.Take("flags"); + const auto cpuFlip = decoder.Take("cpu flip"); + requireCapture(cpuFlip <= 1, "cpu flip is not 0 or 1"); + call.cpuFlip = cpuFlip == 1; + call.address = decoder.Take("address"); + call.size = decoder.Take("size"); + call.values.resize(decoder.Count(8, "values")); + for (auto& value : call.values) value = decoder.Take("value"); + call.context = takeRegisters(decoder); + call.primitive = takeRegisters(decoder); + requireCapture(decoder.Done(), "call record has trailing bytes"); + return call; +} + +bool knownCall(std::uint32_t kind) { + return kind >= static_cast(FrameCallKind::RegisterShader) && kind <= static_cast(FrameCallKind::UnregisterVideoOutput); +} + +std::vector readFile(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + requireCapture(file.is_open(), "cannot open " + path.string()); + std::vector bytes; + file.seekg(0, std::ios::end); + bytes.resize(static_cast(file.tellg())); + file.seekg(0); + file.read(reinterpret_cast(bytes.data()), static_cast(bytes.size())); + requireCapture(file.good() || bytes.empty(), "cannot read " + path.string()); + return bytes; +} + +void requireMagic(std::span bytes, std::string_view magic, const std::filesystem::path& path) { + requireCapture(bytes.size() >= magic.size() && std::memcmp(bytes.data(), magic.data(), magic.size()) == 0, path.string() + " is not a frame capture file"); +} + +struct Run { + std::uint64_t address; + std::span bytes; +}; + +} + +bool AllZero(std::span bytes) { + constexpr std::size_t Word = sizeof(std::uint64_t); + std::size_t at = 0; + for (; at + Word <= bytes.size(); at += Word) { + std::uint64_t value = 0; + std::memcpy(&value, bytes.data() + at, Word); + if (value != 0) return false; + } + return std::all_of(bytes.begin() + static_cast(at), bytes.end(), [](std::byte value) { return value == std::byte{0}; }); +} + +std::string_view FrameCallName(FrameCallKind kind) { + switch (kind) { + case FrameCallKind::RegisterShader: return "register-shader"; + case FrameCallKind::ResolveShaderAbi: return "resolve-shader-abi"; + case FrameCallKind::ResolveGraphicsAbi: return "resolve-graphics-abi"; + case FrameCallKind::ResolveGraphicsStagesAbi: return "resolve-graphics-stages-abi"; + case FrameCallKind::RegisterVideoOutput: return "register-video-output"; + case FrameCallKind::UnregisterVideoOutput: return "unregister-video-output"; + case FrameCallKind::SetTessellationFactorRing: return "set-tf-ring"; + case FrameCallKind::Submit: return "submit"; + case FrameCallKind::Flip: return "flip"; + case FrameCallKind::ReserveEopFlip: return "reserve-eop-flip"; + case FrameCallKind::Suspend: return "suspend"; + case FrameCallKind::Memory: return "memory"; + case FrameCallKind::FrameStart: return "frame-start"; + case FrameCallKind::End: return "end"; + case FrameCallKind::DisplayBuffer: return "display-buffer"; + } + throw std::runtime_error("frame capture: unknown call kind " + std::to_string(static_cast(kind))); +} + +FrameCaptureWriter::FrameCaptureWriter(const std::filesystem::path& directory, int level) : directory(directory), level(level) { + std::error_code error; + std::filesystem::create_directories(directory, error); + requireCapture(!error && std::filesystem::is_directory(directory), "cannot create the directory " + directory.string()); + for (const auto name : {CallsName, MemoryName, HeaderName}) std::filesystem::remove(directory / name, error); + calls.open(directory / CallsName, std::ios::binary | std::ios::trunc); + memory.open(directory / MemoryName, std::ios::binary | std::ios::trunc); + requireCapture(calls.is_open() && memory.is_open(), "cannot create the capture files in " + directory.string()); + calls.write(CallsMagic.data(), static_cast(CallsMagic.size())); + memory.write(MemoryMagic.data(), static_cast(MemoryMagic.size())); + requireCapture(calls.good() && memory.good(), "cannot write the capture files in " + directory.string()); +} + +void FrameCaptureWriter::WriteCall(const FrameCall& call) { + FrameCallName(call.kind); + const auto payload = encodeCall(call); + requireCapture(payload.size() <= UINT32_MAX, "call record too large"); + std::lock_guard lock(mutex); + requireCapture(!finished, "call written after the capture finished"); + const auto kind = static_cast(call.kind); + const auto bytes = static_cast(payload.size()); + calls.write(reinterpret_cast(&kind), sizeof(kind)); + calls.write(reinterpret_cast(&bytes), sizeof(bytes)); + calls.write(reinterpret_cast(payload.data()), static_cast(payload.size())); + requireCapture(calls.good(), "cannot write " + (directory / CallsName).string()); +} + +void FrameCaptureWriter::WriteRange(std::uint64_t address, std::uint64_t bytes, FrameRangeKind kind, std::uint32_t protection, std::uint32_t access) { + FrameMemoryChunk chunk; + chunk.kind = FrameMemoryKind::Range; + chunk.rangeKind = kind; + chunk.protection = protection; + chunk.access = access; + chunk.address = address; + chunk.bytes = bytes; + std::lock_guard lock(mutex); + writeChunk(chunk, {}); +} + +std::uint64_t FrameCaptureWriter::WriteSnapshot(std::uint64_t address, std::span bytes) { + std::vector runs; + for (std::size_t at = 0; at < bytes.size();) { + const auto block = bytes.subspan(at, std::min(BlockBytes, bytes.size() - at)); + if (AllZero(block)) { + at += block.size(); + continue; + } + if (!runs.empty() && runs.back().address + runs.back().bytes.size() == address + at && runs.back().bytes.size() + block.size() <= ChunkBytes) { + runs.back().bytes = bytes.subspan(runs.back().address - address, runs.back().bytes.size() + block.size()); + } else { + runs.push_back({address + at, block}); + } + at += block.size(); + } + const auto workers = std::max(1, std::thread::hardware_concurrency() / 2); + std::uint64_t stored = 0; + for (std::size_t first = 0; first < runs.size(); first += workers) { + const auto last = std::min(runs.size(), first + workers); + std::vector>> compressed; + for (auto index = first; index < last; ++index) { + compressed.push_back(std::async(std::launch::async, [this, run = runs[index]] { return compress(run.bytes); })); + } + std::lock_guard lock(mutex); + for (auto index = first; index < last; ++index) { + const auto payload = compressed[index - first].get(); + FrameMemoryChunk chunk; + chunk.kind = FrameMemoryKind::Snapshot; + chunk.address = runs[index].address; + chunk.bytes = runs[index].bytes.size(); + chunk.compressedBytes = payload.size(); + writeChunk(chunk, payload); + stored += chunk.bytes; + } + } + return stored; +} + +void FrameCaptureWriter::WriteDelta(std::uint64_t group, std::uint64_t completed, std::uint64_t address, std::span bytes) { + for (std::size_t at = 0; at < bytes.size(); at += ChunkBytes) { + const auto part = bytes.subspan(at, std::min(ChunkBytes, bytes.size() - at)); + const auto payload = compress(part); + FrameMemoryChunk chunk; + chunk.kind = FrameMemoryKind::Delta; + chunk.group = group; + chunk.completed = completed; + chunk.address = address + at; + chunk.bytes = part.size(); + chunk.compressedBytes = payload.size(); + std::lock_guard lock(mutex); + writeChunk(chunk, payload); + } +} + +void FrameCaptureWriter::WriteReference(std::uint64_t index, std::uint64_t address, std::span pixels) { + const auto payload = compress(pixels); + FrameMemoryChunk chunk; + chunk.kind = FrameMemoryKind::Reference; + chunk.group = index; + chunk.address = address; + chunk.bytes = pixels.size(); + chunk.compressedBytes = payload.size(); + std::lock_guard lock(mutex); + writeChunk(chunk, payload); +} + +void FrameCaptureWriter::Finish(std::string_view header) { + std::lock_guard lock(mutex); + requireCapture(!finished, "capture finished twice"); + finished = true; + calls.flush(); + memory.flush(); + requireCapture(calls.good() && memory.good(), "cannot flush the capture files in " + directory.string()); + calls.close(); + memory.close(); + std::ofstream file(directory / HeaderName, std::ios::binary | std::ios::trunc); + file.write(header.data(), static_cast(header.size())); + file.flush(); + requireCapture(file.good(), "cannot write " + (directory / HeaderName).string()); +} + +std::uint64_t FrameCaptureWriter::RawBytes() const { + std::lock_guard lock(mutex); + return rawBytes; +} + +std::uint64_t FrameCaptureWriter::StoredBytes() const { + std::lock_guard lock(mutex); + return storedBytes; +} + +const std::filesystem::path& FrameCaptureWriter::Directory() const { + return directory; +} + +void FrameCaptureWriter::writeChunk(const FrameMemoryChunk& chunk, std::span payload) { + requireCapture(!finished, "memory written after the capture finished"); + Encoder encoder; + encoder.Put(static_cast(chunk.kind)); + encoder.Put(static_cast(chunk.rangeKind)); + encoder.Put(chunk.protection); + encoder.Put(chunk.access); + encoder.Put(chunk.group); + encoder.Put(chunk.completed); + encoder.Put(chunk.address); + encoder.Put(chunk.bytes); + encoder.Put(static_cast(payload.size())); + encoder.Put(std::uint64_t{0}); + memory.write(reinterpret_cast(encoder.bytes.data()), static_cast(encoder.bytes.size())); + memory.write(reinterpret_cast(payload.data()), static_cast(payload.size())); + requireCapture(memory.good(), "cannot write " + (directory / MemoryName).string()); + if (chunk.kind != FrameMemoryKind::Range) rawBytes += chunk.bytes; + storedBytes += payload.size(); +} + +std::vector FrameCaptureWriter::compress(std::span bytes) const { + std::vector output(ZSTD_compressBound(bytes.size())); + const auto written = ZSTD_compress(output.data(), output.size(), bytes.data(), bytes.size(), level); + requireCapture(ZSTD_isError(written) == 0, std::string("zstd: ") + ZSTD_getErrorName(written)); + output.resize(written); + return output; +} + +FrameCaptureReader::FrameCaptureReader(const std::filesystem::path& directory) : directory(directory) { + const auto callsPath = directory / FrameCaptureWriter::CallsName; + const auto callBytes = readFile(callsPath); + requireMagic(callBytes, FrameCaptureWriter::CallsMagic, callsPath); + std::size_t at = FrameCaptureWriter::CallsMagic.size(); + while (at < callBytes.size()) { + if (callBytes.size() - at < 8) { + truncated = true; + break; + } + std::uint32_t kind = 0; + std::uint32_t bytes = 0; + std::memcpy(&kind, callBytes.data() + at, 4); + std::memcpy(&bytes, callBytes.data() + at + 4, 4); + requireCapture(knownCall(kind), callsPath.string() + ": unknown call kind " + std::to_string(kind) + " at byte " + std::to_string(at)); + if (callBytes.size() - at - 8 < bytes) { + truncated = true; + break; + } + calls.push_back(decodeCall(static_cast(kind), std::span(callBytes).subspan(at + 8, bytes))); + at += 8 + static_cast(bytes); + } + + const auto memoryPath = directory / FrameCaptureWriter::MemoryName; + std::ifstream memory(memoryPath, std::ios::binary); + requireCapture(memory.is_open(), "cannot open " + memoryPath.string()); + memory.seekg(0, std::ios::end); + const auto size = static_cast(memory.tellg()); + std::string magic(FrameCaptureWriter::MemoryMagic.size(), '\0'); + memory.seekg(0); + memory.read(magic.data(), static_cast(magic.size())); + requireCapture(memory.good() && magic == FrameCaptureWriter::MemoryMagic, memoryPath.string() + " is not a frame capture file"); + std::uint64_t offset = magic.size(); + while (offset < size) { + if (size - offset < ChunkHeaderBytes) { + truncated = true; + break; + } + std::array raw{}; + memory.seekg(static_cast(offset)); + memory.read(reinterpret_cast(raw.data()), static_cast(raw.size())); + requireCapture(memory.good(), "cannot read " + memoryPath.string()); + Decoder decoder(raw); + FrameMemoryChunk chunk; + const auto kind = decoder.Take("memory kind"); + const auto rangeKind = decoder.Take("range kind"); + requireCapture(kind >= 1 && kind <= 4 && rangeKind >= 1 && rangeKind <= 6, memoryPath.string() + ": corrupt chunk header at byte " + std::to_string(offset)); + chunk.kind = static_cast(kind); + chunk.rangeKind = static_cast(rangeKind); + chunk.protection = decoder.Take("protection"); + chunk.access = decoder.Take("access"); + chunk.group = decoder.Take("group"); + chunk.completed = decoder.Take("completed serial"); + chunk.address = decoder.Take("address"); + chunk.bytes = decoder.Take("bytes"); + chunk.compressedBytes = decoder.Take("compressed bytes"); + requireCapture((chunk.kind == FrameMemoryKind::Range) == (chunk.compressedBytes == 0), memoryPath.string() + ": chunk payload does not match its kind at byte " + std::to_string(offset)); + chunk.offset = offset + ChunkHeaderBytes; + if (size - chunk.offset < chunk.compressedBytes) { + truncated = true; + break; + } + chunks.push_back(chunk); + offset = chunk.offset + chunk.compressedBytes; + } + + const auto headerPath = directory / FrameCaptureWriter::HeaderName; + if (std::filesystem::exists(headerPath)) { + const auto bytes = readFile(headerPath); + header.assign(reinterpret_cast(bytes.data()), bytes.size()); + } else { + truncated = true; + } +} + +const std::vector& FrameCaptureReader::Calls() const { + return calls; +} + +const std::vector& FrameCaptureReader::Chunks() const { + return chunks; +} + +const std::string& FrameCaptureReader::Header() const { + return header; +} + +bool FrameCaptureReader::Truncated() const { + return truncated; +} + +void FrameCaptureReader::Read(const FrameMemoryChunk& chunk, std::span destination) const { + requireCapture(chunk.kind != FrameMemoryKind::Range, "a range chunk has no bytes"); + requireCapture(destination.size() == chunk.bytes, "destination size does not match the chunk"); + const auto path = directory / FrameCaptureWriter::MemoryName; + std::ifstream memory(path, std::ios::binary); + requireCapture(memory.is_open(), "cannot open " + path.string()); + std::vector compressed(chunk.compressedBytes); + memory.seekg(static_cast(chunk.offset)); + memory.read(reinterpret_cast(compressed.data()), static_cast(compressed.size())); + requireCapture(memory.good(), "cannot read " + path.string()); + const auto written = ZSTD_decompress(destination.data(), destination.size(), compressed.data(), compressed.size()); + requireCapture(ZSTD_isError(written) == 0, std::string("zstd: ") + ZSTD_getErrorName(written)); + requireCapture(written == chunk.bytes, "chunk at " + std::to_string(chunk.address) + " decompressed to the wrong size"); +} + +std::vector FrameCaptureReader::Read(const FrameMemoryChunk& chunk) const { + std::vector bytes(chunk.bytes); + Read(chunk, bytes); + return bytes; +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp new file mode 100644 index 0000000000..cdfdc9afbe --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp @@ -0,0 +1,605 @@ +#include "prx/libSceAgcDriver/State/include/Configuration.hpp" +#include "prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/TraceRevision.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace AgcDriver { +namespace { + +std::uint64_t readNumber(const char* text, std::uint64_t fallback) { + if (text == nullptr || *text == '\0') return fallback; + std::uint64_t value = 0; + const std::string_view view(text); + const auto [last, error] = std::from_chars(view.data(), view.data() + view.size(), value); + if (error != std::errc{} || last != view.data() + view.size()) throw std::runtime_error("frame capture: '" + std::string(view) + "' is not a number"); + return value; +} + +std::string jsonString(std::string_view text) { + std::string result = "\""; + for (const char character : text) { + if (character == '"' || character == '\\') { + result += '\\'; + result += character; + } else if (static_cast(character) < 0x20) { + char escaped[8]; + std::snprintf(escaped, sizeof(escaped), "\\u%04x", static_cast(static_cast(character))); + result += escaped; + } else { + result += character; + } + } + return result + "\""; +} + +std::vector> captureEnvironment() { +#ifdef _WIN32 + char** entries = _environ; +#else + char** entries = environ; +#endif + std::vector> result; + for (; entries != nullptr && *entries != nullptr; ++entries) { + const std::string_view entry = *entries; + const auto equals = entry.find('='); + if (!entry.starts_with("APS5_") || equals == std::string_view::npos) continue; + result.emplace_back(std::string(entry.substr(0, equals)), std::string(entry.substr(equals + 1))); + } + std::sort(result.begin(), result.end()); + return result; +} + +std::uint64_t threadId() { + return static_cast(std::hash{}(std::this_thread::get_id())); +} + +std::vector copyRegisters(std::span registers) { + return {registers.begin(), registers.end()}; +} + +std::uint64_t pointer(const void* value) { + return static_cast(reinterpret_cast(value)); +} + +std::uint64_t flipValue(std::int64_t value) { + return static_cast(value); +} + +std::uint64_t contentHash(std::span bytes) { + std::uint64_t hash = 0xcbf29ce484222325ull; + for (const auto value : bytes) hash = (hash ^ static_cast(value)) * 0x100000001b3ull; + return hash == 0 ? 1 : hash; +} + +} + +FrameRecorder* FrameRecorder::Active() { + static FrameRecorder* const recorder = []() -> FrameRecorder* { + const char* directory = std::getenv("APS5_FRAME_CAPTURE"); + if (directory == nullptr || *directory == '\0') return nullptr; + const char* trigger = std::getenv("APS5_FRAME_CAPTURE_TRIGGER"); + const auto atBoundary = readNumber(std::getenv("APS5_FRAME_CAPTURE_AT_FLIP"), 0); + const auto frames = readNumber(std::getenv("APS5_FRAME_CAPTURE_FRAMES"), DefaultFrames); + if (frames == 0) throw std::runtime_error("frame capture: APS5_FRAME_CAPTURE_FRAMES must be at least 1"); + return new FrameRecorder(directory, trigger != nullptr ? trigger : "", atBoundary, frames); + }(); + return recorder; +} + +FrameRecorder::FrameRecorder(std::filesystem::path directory, std::filesystem::path trigger, std::uint64_t atBoundary, std::uint64_t frames) + : directory(std::move(directory)), trigger(std::move(trigger)), atBoundary(atBoundary), frames(frames), origin(std::chrono::steady_clock::now()) {} + +FrameCall FrameRecorder::make(FrameCallKind kind) { + FrameCall call; + call.kind = kind; + call.timeNs = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - origin).count()); + call.thread = threadId(); + return call; +} + +void FrameRecorder::RegisterShader(const Shader* shader) { + auto call = make(FrameCallKind::RegisterShader); + call.address = pointer(shader); + const std::array shaders{shader}; + record(std::move(call), true, shaders); +} + +void FrameRecorder::ResolveShaderAbi(const Shader* shader, std::span context, std::span primitive) { + auto call = make(FrameCallKind::ResolveShaderAbi); + call.address = pointer(shader); + call.context = copyRegisters(context); + call.primitive = copyRegisters(primitive); + const std::array shaders{shader}; + record(std::move(call), true, shaders); +} + +void FrameRecorder::ResolveGraphicsStagesAbi(std::span stages, std::span context, std::span primitive) { + auto call = make(FrameCallKind::ResolveGraphicsStagesAbi); + for (const auto* stage : stages) call.values.push_back(pointer(stage)); + call.context = copyRegisters(context); + call.primitive = copyRegisters(primitive); + record(std::move(call), true, stages); +} + +void FrameRecorder::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) { + auto call = make(FrameCallKind::ResolveGraphicsAbi); + call.values = {pointer(vertex), pointer(pixel), primitiveType}; + const std::array shaders{vertex, pixel}; + record(std::move(call), true, shaders); +} + +void FrameRecorder::RegisterVideoOutput(std::uint32_t handle) { + auto call = make(FrameCallKind::RegisterVideoOutput); + call.values = {handle}; + record(std::move(call), true); +} + +void FrameRecorder::UnregisterVideoOutput(std::uint32_t handle) { + auto call = make(FrameCallKind::UnregisterVideoOutput); + call.values = {handle}; + record(std::move(call), true); +} + +void FrameRecorder::Submit(const Packet& packet, std::uint32_t queue, const std::function& accept) { + auto call = make(FrameCallKind::Submit); + call.queue = queue; + call.address = pointer(packet.addr); + call.size = packet.dw_num; + call.flags = packet.flags; + record(std::move(call), false, {}, accept); +} + +void FrameRecorder::Flip(const FlipInfo& info, const std::function& accept) { + auto call = make(FrameCallKind::Flip); + call.cpuFlip = true; + call.values = {info.handle, flipValue(info.index), info.mode, flipValue(info.argument)}; + record(std::move(call), false, {}, accept); +} + +void FrameRecorder::ReserveEopFlip(const FlipInfo& info, std::uint32_t token) { + auto call = make(FrameCallKind::ReserveEopFlip); + call.values = {info.handle, flipValue(info.index), info.mode, flipValue(info.argument), token}; + std::lock_guard lock(mutex); + if (state == State::Waiting) { + call.ordinal = ++ordinal; + reservations.push_back(std::move(call)); + if (reservations.size() > ReservationHistory) reservations.pop_front(); + return; + } + if (state != State::Recording) return; + emitDelta(); + if (state != State::Recording) return; + call.ordinal = ++ordinal; + std::tie(call.accepted, call.completed) = DriverDetail::Driver::Get().SerialProgress(); + writer->WriteCall(call); + ++recordedCalls; +} + +void FrameRecorder::Suspend(const std::function& accept) { + record(make(FrameCallKind::Suspend), false, {}, accept); +} + +void FrameRecorder::PresentBuffer(const DisplayBuffer& buffer) { + std::lock_guard lock(displayMutex); + displayBuffers[buffer.address] = buffer; +} + +void FrameRecorder::writeReferenceFrames() { + std::map presented; + { + std::lock_guard lock(displayMutex); + presented = displayBuffers; + } + if (presented.empty()) return; + if (!DriverDetail::Driver::Get().WaitIdleFor(std::chrono::seconds(10))) { + warnings.push_back("no reference frames: the driver did not go idle after the last frame"); + return; + } + std::uint64_t index = 0; + for (const auto& [address, buffer] : presented) { + try { + const auto pixels = ReadDisplayBuffer(buffer); + writer->WriteReference(index, address, pixels); + } catch (const std::exception& failure) { + warnings.push_back(std::string("no reference frame for a display buffer: ") + failure.what()); + continue; + } + auto call = make(FrameCallKind::DisplayBuffer); + call.ordinal = ++ordinal; + call.address = buffer.address; + call.values = {index, buffer.pixelFormat, buffer.width, buffer.height, buffer.tilingMode, buffer.pitchInPixel, buffer.dccAddress, buffer.dccClearColor, buffer.alignment}; + writer->WriteCall(call); + ++index; + } +} + +void FrameRecorder::record(FrameCall call, bool prologueCall, std::span shaders, const std::function& accept) { + std::lock_guard lock(mutex); + recordLocked(std::move(call), prologueCall, shaders); + if (accept) accept(); +} + +void FrameRecorder::recordLocked(FrameCall call, bool prologueCall, std::span shaders) { + if (state == State::Waiting) { + if (!prologueCall) return; + call.ordinal = ++ordinal; + prologue.push_back(std::move(call)); + return; + } + if (state != State::Recording) return; + if (prologueCall) emitShaderDelta(shaders); + else emitDelta(); + if (state != State::Recording) return; + call.ordinal = ++ordinal; + std::tie(call.accepted, call.completed) = DriverDetail::Driver::Get().SerialProgress(); + writer->WriteCall(call); + ++recordedCalls; +} + +void FrameRecorder::FrameBoundary() { + std::lock_guard lock(mutex); + ++boundaries; + if (state == State::Recording) { + if (++recordedFrames >= frames) finish(); + return; + } + if (state == State::Waiting && triggered()) start(); +} + +bool FrameRecorder::triggered() { + if (!trigger.empty()) { + std::error_code error; + if (!std::filesystem::exists(trigger, error)) return false; + std::ifstream file(trigger); + std::uint64_t requested = 0; + if (file >> requested && requested != 0) frames = requested; + return true; + } + return boundaries >= std::max(atBoundary, 1); +} + +void FrameRecorder::emitDelta() { + std::vector spans; + for (const auto& range : ranges) { + if (range.kind == FrameRangeKind::Watched || range.kind == FrameRangeKind::Unwatched) spans.push_back({range.address, range.bytes, generation}); + } + const auto mark = GuestMemory::WriteGeneration(); + const auto added = newRanges(); + writeDelta(spans, {}, added); + generation = mark; + shaderMarks.clear(); +} + +std::vector FrameRecorder::newRanges() { + const auto current = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + if (current == allocationsGeneration) return {}; + allocationsGeneration = current; + std::map covered; + for (const auto& range : ranges) { + auto& end = covered[range.address]; + end = std::max(end, range.address + range.bytes); + } + std::vector added; + for (const auto& range : committedRanges()) { + const auto end = range.address + range.bytes; + for (auto at = range.address; at < end;) { + const auto next = covered.upper_bound(at); + if (next != covered.begin() && std::prev(next)->second > at) { + at = std::prev(next)->second; + continue; + } + auto piece = range; + piece.address = at; + piece.bytes = (next == covered.end() ? end : std::min(end, next->first)) - at; + added.push_back(piece); + at += piece.bytes; + } + } + return added; +} + +void FrameRecorder::emitShaderDelta(std::span shaders) { + std::set blocks; + std::set hostPages; + std::uintptr_t arenaBase = 0; + std::size_t arenaBytes = 0; + GuestArena::GuestArenaRange_nid_postfix(&arenaBase, &arenaBytes); + const auto note = [&](const void* address, std::uint64_t bytes = 0) { + if (address == nullptr) return; + const auto at = pointer(address); + const bool arena = at >= arenaBase && at - arenaBase < arenaBytes; + const auto blockBytes = arena ? FrameCaptureWriter::BlockBytes : HostPageBytes; + const auto begin = at & ~std::uint64_t{blockBytes - 1}; + const auto count = bytes != 0 ? (at - begin + bytes + blockBytes - 1) / blockBytes : 2; + auto& pages = arena ? blocks : hostPages; + for (std::uint64_t index = 0; index < count; ++index) pages.insert(begin + index * blockBytes); + }; + for (const auto* shader : shaders) { + if (shader == nullptr || !GuestMemory::Accessible(shader, sizeof(Shader))) continue; + note(shader); + note(const_cast(shader->code), shader->shader_size); + note(shader->user_data); + note(shader->cx_registers); + note(shader->sh_registers); + note(shader->specials); + note(shader->input_semantics); + note(shader->output_semantics); + if (shader->user_data == nullptr || !GuestMemory::Accessible(shader->user_data, sizeof(ShaderUserData))) continue; + note(shader->user_data->direct_resource_offset); + for (const auto* sharps : shader->user_data->sharp_resource_offset) note(sharps); + } + if (blocks.empty() && hostPages.empty()) return; + const auto mark = GuestMemory::WriteGeneration(); + std::vector spans; + for (const auto block : blocks) { + const auto found = shaderMarks.find(block); + spans.push_back({block, FrameCaptureWriter::BlockBytes, found != shaderMarks.end() ? std::max(found->second, generation) : generation}); + } + const std::vector pages(hostPages.begin(), hostPages.end()); + writeDelta(spans, pages); + for (const auto block : blocks) shaderMarks[block] = mark; +} + +void FrameRecorder::writeDelta(std::span spans, std::span hostPages, std::span added) { + if (abandonIfDiskLow()) return; + std::vector> blocks; + std::vector> written; + for (const auto& span : spans) { + GuestMemory::CollectWrittenBlocks(span.address, static_cast(span.bytes), span.since, written); + blocks.insert(blocks.end(), written.begin(), written.end()); + } + auto& driver = DriverDetail::Driver::Get(); + auto [accepted, completed] = driver.SerialProgress(); + if (completed < accepted && (!added.empty() || !blocks.empty())) { + if (!driver.WaitIdleFor(std::chrono::seconds(10))) { + stop("stopped: the GPU work submitted before a memory delta did not finish within 10 s"); + return; + } + std::tie(accepted, completed) = driver.SerialProgress(); + } + const auto group = deltaGroups + 1; + bool any = false; + std::vector bytes; + for (const auto& range : added) { + writer->WriteRange(range.address, range.bytes, range.kind, range.protection, range.access); + ranges.push_back(range); + ++lateRanges; + if (!range.readable) { + unreadableBytes += range.bytes; + continue; + } + for (std::uint64_t at = 0; at < range.bytes;) { + bytes.resize(static_cast(std::min(FrameCaptureWriter::BlockBytes, range.bytes - at))); + GuestMemory::ReadCommittedUnsynced(range.address + at, bytes); + const bool zero = std::all_of(bytes.begin(), bytes.end(), [](std::byte value) { return value == std::byte{0}; }); + if (!zero) { + writer->WriteDelta(group, completed, range.address + at, bytes); + deltaBytes += bytes.size(); + any = true; + } + at += bytes.size(); + } + } + for (const auto page : hostPages) { + if (!GuestMemory::Accessible(reinterpret_cast(static_cast(page)), HostPageBytes)) continue; + bytes.resize(HostPageBytes); + std::memcpy(bytes.data(), reinterpret_cast(static_cast(page)), HostPageBytes); + const auto hash = contentHash(bytes); + auto& known = hostPageHashes[page]; + if (known == hash) continue; + known = hash; + writer->WriteDelta(group, completed, page, bytes); + deltaBytes += bytes.size(); + any = true; + } + for (const auto& [begin, end] : blocks) { + bytes.resize(static_cast(end - begin)); + GuestMemory::ReadCommittedUnsynced(begin, bytes); + writer->WriteDelta(group, completed, begin, bytes); + deltaBytes += bytes.size(); + any = true; + } + if (!any) return; + deltaGroups = group; + auto call = make(FrameCallKind::Memory); + call.ordinal = ++ordinal; + call.accepted = accepted; + call.completed = completed; + call.values = {group}; + writer->WriteCall(call); +} + +void FrameRecorder::snapshot(const Range& range) { + writer->WriteRange(range.address, range.bytes, range.kind, range.protection, range.access); + if (!range.readable) { + unreadableBytes += range.bytes; + return; + } + std::vector bytes; + for (std::uint64_t at = 0; at < range.bytes; at += SnapshotPieceBytes) { + if (abandonIfDiskLow()) return; + bytes.resize(static_cast(std::min(SnapshotPieceBytes, range.bytes - at))); + GuestMemory::ReadCommitted(range.address + at, bytes); + snapshotBytes += writer->WriteSnapshot(range.address + at, bytes); + } +} + +bool FrameRecorder::abandonIfDiskLow() { + std::error_code error; + const auto space = std::filesystem::space(directory, error); + if (error || space.available >= MinimumFreeBytes) return false; + stop("stopped: less than 4 GiB free on the capture volume"); + return true; +} + +void FrameRecorder::stop(const char* reason) { + warnings.push_back(reason); + std::fprintf(stderr, "[frame-capture] %s\n", reason); + try { + writer->Finish(header()); + } catch (const std::exception& failure) { + std::fprintf(stderr, "[frame-capture] %s\n", failure.what()); + } + writer.reset(); + state = State::Done; +} + +void FrameRecorder::start() { + ++startAttempts; + auto& driver = DriverDetail::Driver::Get(); + if (!driver.WaitIdleFor(std::chrono::seconds(10))) { + std::fprintf(stderr, "[frame-capture] the driver did not go idle within 10 s at frame boundary %llu (attempt %llu)\n", static_cast(boundaries), static_cast(startAttempts)); + if (startAttempts >= 5) { + std::fprintf(stderr, "[frame-capture] giving up: no idle frame boundary\n"); + state = State::Done; + } + return; + } + startedAt = std::chrono::steady_clock::now(); + startBoundary = boundaries; + allocationsGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + ranges = committedRanges(); + writer = std::make_unique(directory); + std::vector> written; + for (const auto& range : ranges) { + if (range.kind == FrameRangeKind::Watched || range.kind == FrameRangeKind::Unwatched) GuestMemory::CollectWrittenBlocks(range.address, static_cast(range.bytes), std::numeric_limits::max(), written); + } + generation = GuestMemory::WriteGeneration(); + for (const auto& range : ranges) { + snapshot(range); + if (!writer) return; + } + for (const auto& call : prologue) writer->WriteCall(call); + std::uintptr_t ringBase = 0; + std::uint32_t ringSize = 0; + sceAgcDriverGetTFRing(&ringBase, &ringSize); + auto ring = make(FrameCallKind::SetTessellationFactorRing); + ring.ordinal = ++ordinal; + ring.address = ringBase; + ring.size = ringSize; + writer->WriteCall(ring); + const auto pending = driver.PendingEopFlipTokens(); + const std::set open(pending.begin(), pending.end()); + for (const auto& call : reservations) { + if (open.contains(call.values.back())) writer->WriteCall(call); + } + auto begin = make(FrameCallKind::FrameStart); + begin.ordinal = ++ordinal; + std::tie(begin.accepted, begin.completed) = driver.SerialProgress(); + begin.values = {boundaries}; + writer->WriteCall(begin); + state = State::Recording; + recordedFrames = 0; + std::fprintf(stderr, "[frame-capture] started at frame boundary %llu: %zu ranges, %.1f MiB snapshot in %.1f s\n", static_cast(boundaries), ranges.size(), + static_cast(snapshotBytes) / (1024.0 * 1024.0), std::chrono::duration(std::chrono::steady_clock::now() - startedAt).count()); +} + +void FrameRecorder::finish() { + emitDelta(); + if (!writer) return; + auto end = make(FrameCallKind::End); + end.ordinal = ++ordinal; + std::tie(end.accepted, end.completed) = DriverDetail::Driver::Get().SerialProgress(); + writer->WriteCall(end); + writeReferenceFrames(); + writer->Finish(header()); + std::fprintf(stderr, "[frame-capture] %llu frames, %llu calls, %llu memory deltas (%.1f MiB), %.1f MiB raw, %.1f MiB stored in %s\n", static_cast(recordedFrames), + static_cast(recordedCalls), static_cast(deltaGroups), static_cast(deltaBytes) / (1024.0 * 1024.0), + static_cast(writer->RawBytes()) / (1024.0 * 1024.0), static_cast(writer->StoredBytes()) / (1024.0 * 1024.0), directory.string().c_str()); + writer.reset(); + state = State::Done; +} + +std::string FrameRecorder::header() const { + std::uintptr_t arenaBase = 0; + std::size_t arenaBytes = 0; + GuestArena::GuestArenaRange_nid_postfix(&arenaBase, &arenaBytes); + std::string text = "{\"magic\":\"aps5-frame-capture 1\""; + text += ",\"commit\":" + jsonString(APS5_TRACE_COMMIT); + text += std::string(",\"dirty\":") + (APS5_TRACE_DIRTY < 0 ? "null" : APS5_TRACE_DIRTY != 0 ? "true" : "false"); + text += ",\"arena_base\":" + std::to_string(arenaBase) + ",\"arena_bytes\":" + std::to_string(arenaBytes); + text += ",\"start_boundary\":" + std::to_string(startBoundary) + ",\"frames\":" + std::to_string(recordedFrames); + text += ",\"calls\":" + std::to_string(recordedCalls) + ",\"prologue_calls\":" + std::to_string(prologue.size()); + text += ",\"ranges\":" + std::to_string(ranges.size()) + ",\"late_ranges\":" + std::to_string(lateRanges) + ",\"snapshot_bytes\":" + std::to_string(snapshotBytes) + ",\"unreadable_bytes\":" + std::to_string(unreadableBytes); + text += ",\"memory_groups\":" + std::to_string(deltaGroups) + ",\"delta_bytes\":" + std::to_string(deltaBytes); + text += ",\"capture_seconds\":" + std::to_string(std::chrono::duration(std::chrono::steady_clock::now() - startedAt).count()); + text += ",\"env\":{"; + bool first = true; + for (const auto& [name, value] : captureEnvironment()) { + text += (first ? "" : ",") + jsonString(name) + ":" + jsonString(value); + first = false; + } + text += "},\"warnings\":["; + first = true; + for (const auto& warning : warnings) { + text += (first ? "" : ",") + jsonString(warning); + first = false; + } + return text + "]}\n"; +} + +std::vector FrameRecorder::committedRanges() { +#ifdef _WIN32 + std::uintptr_t base = 0; + std::size_t size = 0; + GuestArena::GuestArenaRange_nid_postfix(&base, &size); + if (size == 0) throw std::runtime_error("frame capture: the guest arena is not reserved"); + std::vector result; + for (const auto& allocation : GuestAllocations::GuestAllocationsAcquireAll_nid_postfix()) { + if (!allocation->readable) continue; + const auto first = allocation->address; + const auto end = first + allocation->bytes; + const bool host = first < base || end > base + size; + const auto access = FrameRangeAccess::Readable | (allocation->writable ? FrameRangeAccess::Writable : 0u) | (allocation->gpu ? FrameRangeAccess::Gpu : 0u); + for (auto address = first; address < end;) { + MEMORY_BASIC_INFORMATION info{}; + if (VirtualQuery(reinterpret_cast(static_cast(address)), &info, sizeof(info)) == 0) throw std::runtime_error("frame capture: VirtualQuery failed inside the guest arena"); + const auto regionEnd = std::min(end, reinterpret_cast(info.BaseAddress) + info.RegionSize); + if (info.State == MEM_COMMIT) { + Range range; + range.address = address; + range.bytes = regionEnd - address; + range.protection = info.Protect; + range.access = access; + const auto page = info.Protect & 0xffu; + range.readable = (info.Protect & PAGE_GUARD) == 0 && (page == PAGE_READONLY || page == PAGE_READWRITE || page == PAGE_WRITECOPY || page == PAGE_EXECUTE_READ || page == PAGE_EXECUTE_READWRITE || page == PAGE_EXECUTE_WRITECOPY); + if (host) range.kind = FrameRangeKind::Host; + else if (info.Type == MEM_PRIVATE) range.kind = GuestMemory::Watched(range.address, static_cast(range.bytes)) ? FrameRangeKind::Watched : FrameRangeKind::Unwatched; + else range.kind = FrameRangeKind::Section; + auto& last = result.empty() ? range : result.back(); + if (!result.empty() && last.address + last.bytes == range.address && last.kind == range.kind && last.protection == range.protection && last.readable == range.readable && last.access == range.access) last.bytes += range.bytes; + else result.push_back(range); + } + address = regionEnd; + } + } + return result; +#else + throw std::runtime_error("frame capture: guest memory capture is implemented on Windows only"); +#endif +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 6e7a43a575..e72fabda25 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -1,12 +1,14 @@ #include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/WriteWatchCoverage.hpp" #include "ThreadOwned.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" +#include "prx/libc/include/HostThreadSlot.hpp" #include #include #include @@ -19,7 +21,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -152,13 +157,13 @@ struct MemoryCounters { }; struct MemoryProfile { - std::mutex threadsMutex; + AgcDriver::Mutex threadsMutex; std::vector threads; MemoryCounters retired; MemoryCounters shared; std::atomic lastReport{0}; // Sampled callers of Read and Write (module offsets), to name what touches guest memory most. - std::mutex callersMutex; + AgcDriver::Mutex callersMutex; std::array, 16> callers{}; std::array, 16> writeCallers{}; }; @@ -222,19 +227,19 @@ void CountCaller(std::array, 16>& c } } -// Attribution tags (see the header): the packet a queue worker executes and the read site the -// driver code named. Plain thread-locals; a store per packet costs nothing measurable. -thread_local PacketTag currentPacket{NoPacket, 0xffffffffu}; -thread_local ReadSite currentReadSite = ReadSite::Unknown; +struct CurrentPacketSlot {}; +struct CurrentReadSiteSlot {}; +constexpr std::uint64_t NoPacketBits = NoPacket | (std::uint64_t{0xffffffffu} << 32u); // Sampled reads by site (every 256th Read/ReadCommitted, like the callers), for the [guestmem] line. std::atomic readSiteSamples[static_cast(ReadSite::Count)] = {}; void SetCurrentPacket(std::uint32_t opcode, std::uint32_t queue) { - currentPacket = {opcode, queue}; + HostThreadSlot::Set((opcode | (std::uint64_t{queue} << 32u)) ^ NoPacketBits); } PacketTag CurrentPacket() { - return currentPacket; + const auto bits = HostThreadSlot::Get() ^ NoPacketBits; + return {static_cast(bits), static_cast(bits >> 32u)}; } const char* ReadSiteName(ReadSite site) { @@ -244,11 +249,13 @@ const char* ReadSiteName(ReadSite site) { } ReadSite SetReadSite(ReadSite site) { - return std::exchange(currentReadSite, site); + const auto previous = HostThreadSlot::Get(); + HostThreadSlot::Set(site); + return previous; } ReadSite CurrentReadSite() { - return currentReadSite; + return HostThreadSlot::Get(); } std::size_t CaptureCallerOffsets(std::span frames, unsigned skip) { @@ -289,7 +296,7 @@ std::size_t CaptureCallerOffsets(std::span frames, unsigned void CountReadCaller(const void* returnAddress) { thread_local std::uint32_t sampled = 0; // Sampled at the same rate as the callers, in step with them (CountCaller advances the counter). - if ((sampled + 1) % 256 == 0) readSiteSamples[static_cast(currentReadSite)].fetch_add(256, std::memory_order_relaxed); + if ((sampled + 1) % 256 == 0) readSiteSamples[static_cast(CurrentReadSite())].fetch_add(256, std::memory_order_relaxed); CountCaller(Profile().callers, sampled, 256, returnAddress); } @@ -368,18 +375,38 @@ struct PageSpan { std::size_t size = 0; // Per page: access flags, zero while unknown. std::uint8_t* pages = nullptr; + static constexpr std::size_t ChunkEntries = 65536; + struct alignas(8) Chunk { std::array entries{}; }; + std::atomic* chunks = nullptr; + + std::uint8_t* chunkData(std::size_t index, bool create) const { + auto* chunk = chunks[index].load(std::memory_order_acquire); + if (chunk == nullptr && create) { + auto fresh = std::unique_ptr(new (std::nothrow) Chunk{}); + if (fresh != nullptr && chunks[index].compare_exchange_strong(chunk, fresh.get(), std::memory_order_acq_rel, std::memory_order_acquire)) chunk = fresh.release(); + } + return chunk == nullptr ? nullptr : chunk->entries.data(); + } + + std::uint8_t* entry(std::size_t index, bool create) const { + if (chunks == nullptr) return pages + index; + auto* data = chunkData(index / ChunkEntries, create); + return data == nullptr ? nullptr : data + index % ChunkEntries; + } - // Reserves and commits the table; untouched table pages cost nothing until a guest page is recorded. bool allocate() { if (size == 0) return false; const auto count = (size / PageBytes + 8) & ~static_cast(7); #ifdef _WIN32 - pages = static_cast(VirtualAlloc(nullptr, count, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + const auto chunkCount = (count + ChunkEntries - 1) / ChunkEntries; + chunks = new (std::nothrow) std::atomic[chunkCount]{}; + if (chunks == nullptr) size = 0; + return chunks != nullptr; #else pages = static_cast(std::calloc(count, 1)); -#endif if (pages == nullptr) size = 0; return pages != nullptr; +#endif } bool covers(std::uintptr_t address) const { @@ -387,11 +414,23 @@ struct PageSpan { } std::uint8_t load(std::uintptr_t address) const { - return std::atomic_ref(const_cast(pages[(address - base) / PageBytes])).load(std::memory_order_relaxed); + auto* value = entry((address - base) / PageBytes, false); + return value == nullptr ? 0 : std::atomic_ref(*value).load(std::memory_order_relaxed); } void store(std::uintptr_t address, std::uint8_t value) { - std::atomic_ref(pages[(address - base) / PageBytes]).store(value, std::memory_order_relaxed); + if (auto* at = entry((address - base) / PageBytes, value != 0 && value != PageInaccessible)) std::atomic_ref(*at).store(value, std::memory_order_relaxed); + } + + void store(std::uintptr_t first, std::uintptr_t last, std::uint8_t value) { + auto index = (first - base) / PageBytes; + const auto stop = (last - 1 - base) / PageBytes + 1; + while (index < stop) { + const auto next = std::min(stop, (index / ChunkEntries + 1) * ChunkEntries); + if (auto* at = entry(index, value != 0 && value != PageInaccessible)) { + for (; index < next; ++index, ++at) std::atomic_ref(*at).store(value, std::memory_order_relaxed); + } else index = next; + } } // End of the run of pages equal to `value` that starts at `address`, at most `limit`. @@ -400,11 +439,17 @@ struct PageSpan { const auto stop = std::min((std::min(limit, base + size) - base + PageBytes - 1) / PageBytes, size / PageBytes); const std::uint64_t wide = 0x0101010101010101ull * value; while (index < stop) { - if (index % 8 == 0 && index + 8 <= stop && std::atomic_ref(*reinterpret_cast(const_cast(pages + index))).load(std::memory_order_relaxed) == wide) { + auto* at = entry(index, false); + if (at == nullptr) { + if (value != 0) break; + index = std::min(stop, (index / ChunkEntries + 1) * ChunkEntries); + continue; + } + if (index % 8 == 0 && index + 8 <= stop && std::atomic_ref(*reinterpret_cast(at)).load(std::memory_order_relaxed) == wide) { index += 8; continue; } - if (std::atomic_ref(const_cast(pages[index])).load(std::memory_order_relaxed) != value) break; + if (std::atomic_ref(*at).load(std::memory_order_relaxed) != value) break; ++index; } return std::min(limit, base + index * PageBytes); @@ -414,7 +459,7 @@ struct PageSpan { if (size == 0 || bytes == 0 || address >= base + size || address + bytes <= base) return; const auto first = (std::max(address, base) - base) / PageBytes; const auto last = (std::min(address + bytes, base + size) - 1 - base) / PageBytes; - for (auto page = first; page <= last; ++page) std::atomic_ref(pages[page]).store(0, std::memory_order_relaxed); + store(base + first * PageBytes, base + (last + 1) * PageBytes, 0); } }; @@ -445,7 +490,7 @@ struct PageStates { const std::uint8_t value = readable ? static_cast(PageReadable | (writable ? PageWritable : 0u)) : PageInaccessible; const auto first = std::max(base, span->base); const auto last = std::min(regionEnd, span->base + span->size); - for (auto at = first; at < last; at += PageBytes) span->store(at, value); + span->store(first, last, value); if (GuestAllocations::GuestAllocationsGeneration_nid_postfix() != generation || accessSerial.load(std::memory_order_acquire) != access) span->forget(first, static_cast(last - first)); } } @@ -813,18 +858,33 @@ constexpr std::size_t WritePagesPerBlock = WriteBlockBytes / WritePageBytes; enum class StampKind : std::uint8_t { Cpu, Driver, ImportWindow, Fresh }; -constexpr std::array ZeroPage{}; +std::uint64_t HashPage(const std::byte* page) { + constexpr std::uint64_t prime1 = 0x9E3779B185EBCA87ull; + constexpr std::uint64_t prime2 = 0xC2B2AE3D27D4EB4Full; + constexpr std::uint64_t prime3 = 0x165667B19E3779F9ull; + constexpr std::uint64_t prime4 = 0x85EBCA77C2B2AE63ull; + const auto round = [](std::uint64_t accumulator, std::uint64_t word) { return std::rotl(accumulator + word * prime2, 31) * prime1; }; + std::array lanes{prime1 + prime2, prime2, 0, 0 - prime1}; + for (std::size_t offset = 0; offset < WritePageBytes; offset += 4 * sizeof(std::uint64_t)) { + std::array words; + std::memcpy(words.data(), page + offset, sizeof(words)); + for (std::size_t lane = 0; lane < 4; ++lane) lanes[lane] = round(lanes[lane], words[lane]); + } + std::uint64_t hash = std::rotl(lanes[0], 1) + std::rotl(lanes[1], 7) + std::rotl(lanes[2], 12) + std::rotl(lanes[3], 18); + for (const auto lane : lanes) hash = (hash ^ round(0, lane)) * prime1 + prime4; + hash ^= hash >> 33; + hash *= prime2; + hash ^= hash >> 29; + hash *= prime3; + return hash ^ (hash >> 32); +} struct WindowCopy { - static constexpr std::uint32_t Zero = std::numeric_limits::max(); std::uint64_t first; - std::span bytes; - std::span slots; + std::span hashes; bool Holds(std::uintptr_t page) const { - const auto slot = slots[(page - first) / WritePageBytes]; - const auto* expected = slot == Zero ? ZeroPage.data() : bytes.data() + static_cast(slot) * WritePageBytes; - return std::memcmp(reinterpret_cast(page), expected, WritePageBytes) == 0; + return HashPage(reinterpret_cast(page)) == hashes[(page - first) / WritePageBytes]; } }; @@ -833,7 +893,7 @@ void watchPrivateMapping(std::uintptr_t address, std::size_t bytes, std::uint64_ #endif struct WriteTracker { - std::mutex mutex; + AgcDriver::Mutex mutex; bool initialized = false; bool watched = false; static constexpr std::size_t CoarseBlocks = 64; @@ -859,6 +919,7 @@ struct WriteTracker { // bumps, or an image refreshed after a stamp would keep failing UnchangedSince until the next // walk of its range and re-upload at every use of the epoch. std::atomic generation{1}; + std::atomic stable{1}; std::vector pages; // Collect memo: page-rounded ranges walked during the current epoch (see BumpCollectEpoch). A // collect fully inside one skips GetWriteWatch, whose cost is proportional to the range's pages, @@ -873,6 +934,7 @@ struct WriteTracker { std::uintptr_t end = 0; std::uint64_t epoch = 0; std::uint64_t unwatched = 0; + std::uint64_t mapping = 0; }; std::array memo{}; std::size_t nextMemo = 0; @@ -955,8 +1017,10 @@ struct WriteTracker { struct DriverPieces { std::uint32_t whole = 0; std::uint32_t dropped = 0; - std::uint32_t next = 0; std::array pieces{}; + std::uint32_t possibleWhole = 0; + std::uint32_t possibleDropped = 0; + std::array possiblePieces{}; bool covers(std::uint32_t begin, std::uint32_t end, std::uint64_t generation) const { if (generation == 0 || begin >= end || dropped > generation) return false; @@ -979,6 +1043,7 @@ struct WriteTracker { }; std::unordered_map cpuPages; std::unordered_map walkPages; + std::map refused; void noteCpuStore(std::uint64_t begin, std::uint64_t end, std::uint32_t stampGeneration) { notePages(cpuPages, begin, end, stampGeneration); @@ -1009,7 +1074,7 @@ struct WriteTracker { #endif } - void stampWatchedPart(std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration) { + void stampWatchedPart(std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration, bool complete) { #ifdef _WIN32 address = std::max(address, base); end = std::min(end, base + size); @@ -1023,24 +1088,45 @@ struct WriteTracker { const auto stop = std::min(end, blockBegin(block) + WriteBlockBytes); if (!blockPartWatched(first, stop)) continue; stamp(block, stampGeneration, StampKind::Driver); - noteDriverStore(block, address, end, stampGeneration); + noteDriverStore(block, address, end, stampGeneration, complete); } } - void noteDriverStore(std::uint64_t block, std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration) { + void noteDriverStore(std::uint64_t block, std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration, bool complete) { const auto begin = blockBegin(block); const auto from = static_cast(std::max(address, begin) - begin); const auto to = static_cast(std::min(end, begin + WriteBlockBytes) - begin); if (from == 0 && to == WriteBlockBytes) { auto& entry = driverPieces[block]; - entry = DriverPieces{}; - entry.whole = stampGeneration; + if (complete) { + entry = DriverPieces{}; + entry.whole = stampGeneration; + } else { + entry.possibleWhole = stampGeneration; + entry.possibleDropped = 0; + entry.possiblePieces = {}; + } return; } auto& entry = driverPieces[block]; - auto& slot = entry.pieces[entry.next++ % entry.pieces.size()]; - entry.dropped = std::max(entry.dropped, slot.generation); - slot = {stampGeneration, from, to}; + auto& pieces = complete ? entry.pieces : entry.possiblePieces; + auto& dropped = complete ? entry.dropped : entry.possibleDropped; + DriverPiece* contained = nullptr; + for (auto& piece : pieces) { + if (piece.generation == 0 || piece.begin < from || piece.end > to) continue; + if (contained == nullptr) contained = &piece; + else piece = {}; + } + if (contained != nullptr) { + *contained = {stampGeneration, from, to}; + return; + } + auto* oldest = &pieces.front(); + for (auto& piece : pieces) { + if (piece.generation < oldest->generation) oldest = &piece; + } + dropped = std::max(dropped, oldest->generation); + *oldest = {stampGeneration, from, to}; } Leaf& leafFor(std::uint64_t block) { @@ -1151,15 +1237,27 @@ struct WriteTracker { continue; } const auto& entry = found->second; - if (entry.whole > generation || entry.dropped > generation) return true; + if (entry.whole > generation || entry.dropped > generation || entry.possibleWhole > generation || entry.possibleDropped > generation) return true; for (const auto& piece : entry.pieces) { if (piece.generation > generation && piece.begin < to && from < piece.end) return true; } + for (const auto& piece : entry.possiblePieces) { + if (piece.generation > generation && piece.begin < to && from < piece.end) return true; + } + } + return false; + } + + bool refusedSince(std::uint64_t address, std::uint64_t end, std::uint64_t since) const { + const auto last = blockOf(end - 1); + for (auto it = refused.lower_bound(blockOf(address)); it != refused.end() && it->first <= last; ++it) { + if (it->second > since) return true; } return false; } bool rangeUnchanged(std::uint64_t address, std::size_t bytes, std::uint64_t since) const { + if (refusedSince(address, address + bytes, since)) return false; return unchanged(blockOf(address), blockOf(address + bytes - 1), since) || !storedOver(address, address + bytes, since, true); } @@ -1183,16 +1281,51 @@ WriteTracker& Tracker() { // Takes the tracker mutex; under APS5_PROFILE_DRAW an acquisition that found it held is counted // ('tracker waits N / M' in [guestmem]), which says whether walking outside the lock would pay. -std::unique_lock lockTracker(WriteTracker& tracker) { - static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; - if (!profile) return std::unique_lock(tracker.mutex); - std::unique_lock lock(tracker.mutex, std::try_to_lock); - if (!lock.owns_lock()) { - trackerWaits.fetch_add(1, std::memory_order_relaxed); +class TrackerLock { +public: + explicit TrackerLock(WriteTracker& tracker) : tracker(tracker), lock(tracker.mutex, std::defer_lock) { + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + if (!profile) { + lock.lock(); + return; + } + if (!lock.try_lock()) { + trackerWaits.fetch_add(1, std::memory_order_relaxed); + lock.lock(); + } + trackerAcquisitions.fetch_add(1, std::memory_order_relaxed); + } + ~TrackerLock() { + if (lock.owns_lock()) publish(); + } + TrackerLock(const TrackerLock&) = delete; + TrackerLock& operator=(const TrackerLock&) = delete; + + void Unlock() { + publish(); + lock.unlock(); + } + + void Suspend() { + lock.unlock(); + } + + void Resume() { lock.lock(); } - trackerAcquisitions.fetch_add(1, std::memory_order_relaxed); - return lock; + +private: + void publish() { + const auto current = tracker.generation.load(std::memory_order_relaxed); + if (tracker.stable.load(std::memory_order_relaxed) != current) tracker.stable.store(current, std::memory_order_release); + } + + WriteTracker& tracker; + std::unique_lock lock; +}; + +TrackerLock lockTracker(WriteTracker& tracker) { + return TrackerLock(tracker); } #ifdef _WIN32 @@ -1218,11 +1351,11 @@ void watchPrivateMapping(std::uintptr_t address, std::size_t bytes, std::uint64_ // thread that never bumped is given a new epoch for every collect: its walks are never reused (the // presenter, game threads inside the flush hook), as nothing orders the CPU's writes for it. std::atomic nextCollectEpoch{1}; -thread_local std::uint64_t threadCollectEpoch = 0; +struct CollectEpochSlot {}; std::atomic lastBumpedEpoch{0}; std::uint64_t currentCollectEpoch() { - if (threadCollectEpoch != 0) return threadCollectEpoch; + if (const auto epoch = HostThreadSlot::Get(); epoch != 0) return epoch; return nextCollectEpoch.fetch_add(1, std::memory_order_relaxed) + 1; } @@ -1239,7 +1372,15 @@ struct ThreadCollectMemo { std::array entries{}; std::size_t next = 0; }; -thread_local ThreadCollectMemo threadCollectMemo; +ThreadCollectMemo& threadCollectMemo() { + struct Slot {}; + auto* memo = HostThreadSlot::Get(); + if (memo == nullptr) { + ShaderRecompiler::ThreadOwned(memo); + HostThreadSlot::Set(memo); + } + return *memo; +} struct EpochCollectMap { std::uint64_t epoch = 0; @@ -1321,13 +1462,32 @@ bool collectWindowsWrites(StampRuns& runs, std::uint64_t first, std::uint64_t st if (runs.stamped != 0) collectDirty.fetch_add(1, std::memory_order_relaxed); return fresh || singlePass || runs.stamped == 0 || walk(true); } + +void noteRefusedWalk(WriteTracker& tracker, std::uint64_t first, std::uint64_t stop) { + const auto pagesOf = [&](auto list, auto&& each) { + auto cursor = first; + while (cursor < stop) { + std::size_t count = tracker.pages.size(); + list(static_cast(cursor), static_cast(stop - cursor), tracker.pages.data(), &count); + for (std::size_t i = 0; i < count; ++i) each(reinterpret_cast(tracker.pages[i])); + if (count < tracker.pages.size()) break; + cursor = reinterpret_cast(tracker.pages[count - 1]) + WritePageBytes; + } + }; + StampRuns runs{tracker, StampKind::Cpu}; + pagesOf(&GuestArena::GuestArenaTakeUnreportedPages_nid_no_patch, [&](std::uintptr_t page) { stampWrittenRun(&runs, page, page + WritePageBytes); }); + const auto generation = tracker.generation.load(std::memory_order_relaxed); + pagesOf(&GuestArena::GuestArenaComparedPages_nid_no_patch, [&](std::uintptr_t page) { tracker.refused[tracker.blockOf(page)] = generation; }); +} #endif bool walkWrites(WriteTracker& tracker, std::uint64_t first, std::uint64_t stop, StampKind kind, bool armedOnly = false, const WindowCopy* window = nullptr) { ++tracker.generation; StampRuns runs{tracker, kind, 0, window}; #ifdef _WIN32 - return collectWindowsWrites(runs, first, stop, armedOnly); + if (collectWindowsWrites(runs, first, stop, armedOnly)) return true; + noteRefusedWalk(tracker, first, stop); + return false; #else const auto dirtyRuns = collectDirtyRuns.load(std::memory_order_relaxed); const auto collect = armedOnly ? &GuestWriteWatch::GuestWriteWatchCollectArmed_nid_postfix : &GuestWriteWatch::GuestWriteWatchCollect_nid_postfix; @@ -1348,24 +1508,25 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi // must not differ between them. const auto epoch = currentCollectEpoch(); const auto unwatched = unwatchSerial.load(std::memory_order_acquire); + const auto mapping = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); const bool useMemo = memoized && collectMemoEnabled() && bytes != 0; if (useMemo && !sharedCollectMemo()) { // An entry exists only for a completed walk of an in-arena range, so the tracker is // initialized and watched; nothing below the lock needs asking. if (epochCollectMapEnabled()) { const auto& map = epochCollectMap(epoch); - if (const auto found = map.ranges.find(first); found != map.ranges.end() && found->second.unwatched == unwatched && stop <= found->second.end) { + if (const auto found = map.ranges.find(first); found != map.ranges.end() && found->second.unwatched == unwatched && found->second.mapping == mapping && stop <= found->second.end) { collectMemoHits.fetch_add(1, std::memory_order_relaxed); - return tracker.generation.load(std::memory_order_relaxed); + return tracker.stable.load(std::memory_order_acquire); } } - for (const auto& entry : threadCollectMemo.entries) { - if (entry.epoch == epoch && entry.unwatched == unwatched && entry.begin <= first && stop <= entry.end) { + for (const auto& entry : threadCollectMemo().entries) { + if (entry.epoch == epoch && entry.unwatched == unwatched && entry.mapping == mapping && entry.begin <= first && stop <= entry.end) { // The current generation, not the memoized one: blocks stamped since (MarkWritten, an // overlapping collect) were written before this caller reads, and an older value would // make every later UnchangedSince fail until the epoch ends. collectMemoHits.fetch_add(1, std::memory_order_relaxed); - return tracker.generation.load(std::memory_order_relaxed); + return tracker.stable.load(std::memory_order_acquire); } } } @@ -1375,7 +1536,7 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi auto cursor = first; if (useMemo && sharedCollectMemo()) { for (const auto& entry : tracker.memo) { - if (entry.epoch == epoch && entry.unwatched == unwatchSerial.load(std::memory_order_relaxed) && entry.begin <= cursor && stop <= entry.end) { + if (entry.epoch == epoch && entry.unwatched == unwatchSerial.load(std::memory_order_relaxed) && entry.mapping == mapping && entry.begin <= cursor && stop <= entry.end) { collectMemoHits.fetch_add(1, std::memory_order_relaxed); return tracker.generation; } @@ -1383,7 +1544,7 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi } const TimedAccess timed(CounterCollect, bytes); #ifndef _WIN32 - if (useMemo && threadCollectEpoch != 0 && bytes <= WriteBlockBytes) { + if (useMemo && HostThreadSlot::Get() != 0 && bytes <= WriteBlockBytes) { const auto begin = first & ~(WriteBlockBytes - 1); const auto end = (stop + WriteBlockBytes - 1) & ~(WriteBlockBytes - 1); if (end > begin && tracker.covers(begin, end - begin)) { @@ -1396,12 +1557,13 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi // Only a completed walk is remembered; a failed one (uncommitted pages) returned 0 above. if (collectMemoEnabled()) { const auto serial = unwatchSerial.load(std::memory_order_relaxed); - if (sharedCollectMemo()) tracker.memo[tracker.nextMemo++ % tracker.memo.size()] = {first, stop, epoch, serial}; + if (sharedCollectMemo()) tracker.memo[tracker.nextMemo++ % tracker.memo.size()] = {first, stop, epoch, serial, mapping}; else { - threadCollectMemo.entries[threadCollectMemo.next++ % threadCollectMemo.entries.size()] = {first, stop, epoch, serial}; + auto& memo = threadCollectMemo(); + memo.entries[memo.next++ % memo.entries.size()] = {first, stop, epoch, serial, mapping}; if (epochCollectMapEnabled()) { auto& entry = epochCollectMap(epoch).ranges[first]; - if (entry.unwatched != serial || entry.end < stop) entry = {first, stop, epoch, serial}; + if (entry.unwatched != serial || entry.mapping != mapping || entry.end < stop) entry = {first, stop, epoch, serial, mapping}; } } } @@ -1418,15 +1580,81 @@ std::uint64_t CollectWritesUncached(std::uint64_t address, std::size_t bytes) { return collectWrites(address, bytes, false); } +std::uint64_t WriteGeneration() { + return Tracker().generation.load(std::memory_order_acquire); +} + +std::uint64_t CollectWrittenBlocks(std::uint64_t address, std::size_t bytes, std::uint64_t since, std::vector>& written) { + written.clear(); + if (bytes == 0 || bytes > std::numeric_limits::max() - address) return 0; + auto& tracker = Tracker(); + const auto lock = lockTracker(tracker); + tracker.initialize(); + if (!tracker.watched) return 0; +#ifdef _WIN32 + if (address < tracker.base || bytes > tracker.size || address - tracker.base > tracker.size - bytes) return 0; +#endif + const auto end = address + bytes; + const auto first = tracker.blockOf(address); + const auto last = tracker.blockOf(end - 1); + const auto clip = [&](std::uint64_t block) { + const auto begin = tracker.blockBegin(block); + return std::pair{std::max(address, begin), std::min(end, begin + WriteBlockBytes)}; + }; + std::uint64_t spanBegin = 0; + std::uint64_t spanEnd = 0; + const auto walkSpan = [&] { + if (spanEnd > spanBegin) walkWrites(tracker, spanBegin & ~std::uint64_t{WritePageBytes - 1}, (spanEnd + WritePageBytes - 1) & ~std::uint64_t{WritePageBytes - 1}, StampKind::Cpu); + spanBegin = spanEnd = 0; + }; + for (auto block = first; block <= last; ++block) { + const auto [begin, stop] = clip(block); + if (!tracker.covers(begin, stop - begin)) { + walkSpan(); + continue; + } + if (spanEnd != begin) { + walkSpan(); + spanBegin = begin; + } + spanEnd = stop; + } + walkSpan(); + const auto append = [&](std::uint64_t begin, std::uint64_t stop) { + if (!written.empty() && written.back().second == begin) written.back().second = stop; + else written.emplace_back(begin, stop); + }; + for (auto block = first; block <= last; ++block) { + if (tracker.cpuStampOf(block) <= since) continue; + const auto [begin, stop] = clip(block); + const auto pages = tracker.cpuPages.find(block); + bool any = false; + if (pages != tracker.cpuPages.end()) { + const auto blockStart = tracker.blockBegin(block); + for (std::size_t page = 0; page < WritePagesPerBlock; ++page) { + if (pages->second.pages[page] <= since) continue; + const auto pageBegin = std::max(begin, blockStart + page * WritePageBytes); + const auto pageEnd = std::min(stop, blockStart + (page + 1) * WritePageBytes); + if (pageBegin >= pageEnd) continue; + append(pageBegin, pageEnd); + any = true; + } + } + if (!any) append(begin, stop); + } + return tracker.generation.load(std::memory_order_relaxed); +} + void BumpCollectEpoch() { - threadCollectEpoch = nextCollectEpoch.fetch_add(1, std::memory_order_relaxed) + 1; + const auto epoch = nextCollectEpoch.fetch_add(1, std::memory_order_relaxed) + 1; + HostThreadSlot::Set(epoch); auto last = lastBumpedEpoch.load(std::memory_order_relaxed); - while (last < threadCollectEpoch && !lastBumpedEpoch.compare_exchange_weak(last, threadCollectEpoch, std::memory_order_release, std::memory_order_relaxed)) {} + while (last < epoch && !lastBumpedEpoch.compare_exchange_weak(last, epoch, std::memory_order_release, std::memory_order_relaxed)) {} collectEpochBumps.fetch_add(1, std::memory_order_relaxed); } std::uint64_t ThreadCollectEpoch() { - return collectMemoEnabled() ? threadCollectEpoch : 0; + return collectMemoEnabled() ? HostThreadSlot::Get() : 0; } std::uint64_t UnwatchSerial() { @@ -1435,15 +1663,32 @@ std::uint64_t UnwatchSerial() { void AdoptLatestCollectEpoch() { const auto latest = lastBumpedEpoch.load(std::memory_order_acquire); - if (latest > threadCollectEpoch) threadCollectEpoch = latest; + if (latest > HostThreadSlot::Get()) HostThreadSlot::Set(latest); } std::uint64_t CollectEpochBumps() { return collectEpochBumps.load(std::memory_order_relaxed); } +namespace { +std::atomic proofSerial{1}; +thread_local bool proofThread = false; +} + +std::uint64_t ProofSerial() { + return proofSerial.load(std::memory_order_acquire); +} + +void BreakProofs() { + proofSerial.fetch_add(1, std::memory_order_acq_rel); +} + +void MarkProofThread() { + proofThread = true; +} + std::uint64_t CollectEpoch() { - return collectMemoEnabled() ? threadCollectEpoch : 0; + return collectMemoEnabled() ? HostThreadSlot::Get() : 0; } namespace { @@ -1507,9 +1752,11 @@ void Rewatch(std::span> ranges) { if (!tracker.watched) return; bool restored = false; for (const auto& [begin, end] : ranges) { - if (end <= begin || begin < tracker.base || end - tracker.base > tracker.size || (begin - tracker.base) % WriteBlockBytes != 0 || (end - tracker.base) % WriteBlockBytes != 0) continue; + if (end <= begin || begin < tracker.base || end - tracker.base > tracker.size || (begin - tracker.base) % WritePageBytes != 0 || (end - tracker.base) % WritePageBytes != 0) continue; const auto bytes = static_cast(end - begin); - if (tracker.coverage.Covers(begin, bytes) || !walkWrites(tracker, begin, end, StampKind::Cpu) || !tracker.coverage.Restore(begin, bytes)) continue; + if (tracker.coverage.Covers(begin, bytes)) continue; + GuestArena::GuestArenaCompareWhenShared_nid_no_patch(static_cast(begin), bytes); + if (!walkWrites(tracker, begin, end, StampKind::Cpu) || !tracker.coverage.Restore(begin, bytes)) continue; ++tracker.generation; tracker.noteCpuStore(begin, end, tracker.generation); for (auto block = tracker.blockOf(begin); block <= tracker.blockOf(end - 1); ++block) { @@ -1524,7 +1771,7 @@ void Rewatch(std::span> ranges) { #endif } -bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function& import, bool staysWatched) { +bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function& import, bool staysWatched, bool importMayWriteGuest) { if (bytes == 0 || bytes > std::numeric_limits::max() - address) return import(); if (!staysWatched) { constexpr std::uint64_t page = 4096; @@ -1535,7 +1782,7 @@ bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function auto lock = lockTracker(tracker); tracker.initialize(); if (!tracker.watched) { - lock.unlock(); + lock.Unlock(); return import(); } constexpr std::uint64_t page = 4096; @@ -1544,7 +1791,11 @@ bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function const bool covered = tracker.covers(first, static_cast(stop - first)); const auto runImport = [&] { #ifdef __APPLE__ - const GuestArena::HostWrite open(reinterpret_cast(first), static_cast(stop - first)); + std::optional open; + if (importMayWriteGuest) open.emplace(reinterpret_cast(first), static_cast(stop - first)); +#endif +#ifndef __APPLE__ + static_cast(importMayWriteGuest); #endif return import(); }; @@ -1567,26 +1818,36 @@ bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function begin = end; } } - std::vector slots(static_cast((stop - first) / page), WindowCopy::Zero); - std::size_t kept = 0; + std::vector hashes(static_cast((stop - first) / page), 0); for (const auto& [begin, end] : spans) { CheckRange(reinterpret_cast(begin), static_cast(end - begin), 1, false); for (auto cursor = begin; cursor < end; cursor += page) { - if (std::memcmp(reinterpret_cast(cursor), ZeroPage.data(), page) != 0) slots[static_cast((cursor - first) / page)] = static_cast(kept++); + hashes[static_cast((cursor - first) / page)] = HashPage(reinterpret_cast(cursor)); } } - std::vector saved; - saved.reserve(kept * page); - const auto* source = reinterpret_cast(first); - for (std::size_t index = 0; index < slots.size(); ++index) { - if (slots[index] != WindowCopy::Zero) saved.insert(saved.end(), source + index * page, source + (index + 1) * page); - } - std::erase_if(spans, [&](const auto& span) { return !walkWrites(tracker, span.first, span.second, StampKind::Cpu, true); }); + const auto saveBytes = [&](std::uint64_t begin, std::uint64_t end) { + lock.Suspend(); + beforeUnwatch(begin, static_cast(end - begin)); + lock.Resume(); + }; + std::vector> leaving; + std::erase_if(spans, [&](const auto& span) { + if (walkWrites(tracker, span.first, span.second, StampKind::Cpu, true)) return false; + leaving.push_back(span); + return true; + }); + for (const auto& [begin, end] : leaving) saveBytes(begin, end); if (!runImport()) return false; - const WindowCopy window{first, saved, slots}; + const WindowCopy window{first, hashes}; auto unwatchedFrom = first; for (const auto& [begin, end] : spans) { - if (!walkWrites(tracker, begin, end, StampKind::Cpu, true, &window)) continue; + if (!walkWrites(tracker, begin, end, StampKind::Cpu, true, &window)) { + saveBytes(begin, end); + continue; + } +#ifdef _WIN32 + GuestArena::GuestArenaCompareResidentWhenShared_nid_no_patch(static_cast(begin), static_cast(end - begin)); +#endif if (unwatchedFrom < begin) unwatchLocked(tracker, unwatchedFrom, static_cast(begin - unwatchedFrom)); unwatchedFrom = end; } @@ -1649,24 +1910,25 @@ bool UnchangedCommittedSince(std::span(end - begin)) || tracker.newestStamp(begin, end) > generation) return false; + if (end <= begin || !tracker.covers(begin, static_cast(end - begin)) || tracker.newestStamp(begin, end) > generation || tracker.refusedSince(begin, end, generation)) return false; } return true; } std::uint64_t storeOwn(std::uint64_t address, std::size_t bytes, const std::function()>& store) { if (bytes == 0) return 0; + BreakProofs(); const auto stampStored = [](WriteTracker& tracker, std::pair stored) -> std::uint64_t { if (stored.second <= stored.first) return 0; ++tracker.generation; if (!tracker.watched || !tracker.covers(stored.first, static_cast(stored.second - stored.first))) { tracker.noteUnstampedStore(stored.first, stored.second); - if (tracker.watched) tracker.stampWatchedPart(stored.first, stored.second, tracker.generation); + if (tracker.watched) tracker.stampWatchedPart(stored.first, stored.second, tracker.generation, true); return 0; } for (auto block = tracker.blockOf(stored.first); block <= tracker.blockOf(stored.second - 1); ++block) { tracker.stamp(block, tracker.generation, StampKind::Driver); - tracker.noteDriverStore(block, stored.first, stored.second, tracker.generation); + tracker.noteDriverStore(block, stored.first, stored.second, tracker.generation, true); } return tracker.generation; }; @@ -1699,7 +1961,8 @@ std::uint64_t StoreOwnBytes(std::uint64_t address, std::span so }); } -std::uint64_t MarkWritten(std::uint64_t address, std::size_t bytes) { +static std::uint64_t markWritten(std::uint64_t address, std::size_t bytes, bool complete) { + BreakProofs(); auto& tracker = Tracker(); auto lock = lockTracker(tracker); tracker.initialize(); @@ -1707,7 +1970,7 @@ std::uint64_t MarkWritten(std::uint64_t address, std::size_t bytes) { ++tracker.generation; if (!tracker.watched || !tracker.covers(address, bytes)) { tracker.noteUnstampedStore(address, address + bytes); - if (tracker.watched) tracker.stampWatchedPart(address, address + bytes, tracker.generation); + if (tracker.watched) tracker.stampWatchedPart(address, address + bytes, tracker.generation, complete); return 0; } { @@ -1715,14 +1978,14 @@ std::uint64_t MarkWritten(std::uint64_t address, std::size_t bytes) { const auto blockStop = (address + bytes + WriteBlockBytes - 1) & ~(std::uint64_t{WriteBlockBytes} - 1); StampRuns runs{tracker, StampKind::Fresh}; #ifdef _WIN32 - const bool complete = collectWindowsWrites(runs, blockFirst, blockStop, false); + const bool collected = collectWindowsWrites(runs, blockFirst, blockStop, false); #else - const bool complete = GuestWriteWatch::GuestWriteWatchCollectFresh_nid_postfix(static_cast(blockFirst), static_cast(blockStop - blockFirst), &stampWrittenRun, &runs); + const bool collected = GuestWriteWatch::GuestWriteWatchCollectFresh_nid_postfix(static_cast(blockFirst), static_cast(blockStop - blockFirst), &stampWrittenRun, &runs); #endif - if (!complete) { - lock.unlock(); + if (!collected) { + lock.Suspend(); beforeUnwatch(blockFirst, static_cast(blockStop - blockFirst)); - lock.lock(); + lock.Resume(); unwatchLocked(tracker, blockFirst, static_cast(blockStop - blockFirst)); tracker.noteUnstampedStore(address, address + bytes); return 0; @@ -1733,11 +1996,19 @@ std::uint64_t MarkWritten(std::uint64_t address, std::size_t bytes) { const auto last = tracker.blockOf(address + bytes - 1); for (auto block = first; block <= last; ++block) { tracker.stamp(block, tracker.generation, StampKind::Driver); - tracker.noteDriverStore(block, address, address + bytes, tracker.generation); + tracker.noteDriverStore(block, address, address + bytes, tracker.generation, complete); } return tracker.generation; } +std::uint64_t MarkWritten(std::uint64_t address, std::size_t bytes) { + return markWritten(address, bytes, true); +} + +std::uint64_t MarkPossiblyWritten(std::uint64_t address, std::size_t bytes) { + return markWritten(address, bytes, false); +} + bool StoredOver(std::uint64_t address, std::size_t bytes, std::uint64_t generation) { auto& tracker = Tracker(); const auto lock = lockTracker(tracker); @@ -1827,15 +2098,16 @@ std::uint64_t UntrackedHint(std::uint64_t address, std::size_t bytes) { tracker.initialize(); if (!tracker.watched || !tracker.hinted(first, stop)) return 0; const auto unwatched = unwatchSerial.load(std::memory_order_relaxed); + const auto mapping = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); const bool memoized = collectMemoEnabled() && std::any_of(threadHintMemo.entries.begin(), threadHintMemo.entries.end(), [&](const WriteTracker::Memo& entry) { - return entry.epoch == epoch && entry.unwatched == unwatched && entry.begin <= first && stop <= entry.end; + return entry.epoch == epoch && entry.unwatched == unwatched && entry.mapping == mapping && entry.begin <= first && stop <= entry.end; }); if (memoized) { collectMemoHits.fetch_add(1, std::memory_order_relaxed); } else { const TimedAccess timed(CounterCollect, bytes); if (!walkHints(tracker, first, stop)) return 0; - if (collectMemoEnabled()) threadHintMemo.entries[threadHintMemo.next++ % threadHintMemo.entries.size()] = {first, stop, epoch, unwatched}; + if (collectMemoEnabled()) threadHintMemo.entries[threadHintMemo.next++ % threadHintMemo.entries.size()] = {first, stop, epoch, unwatched, mapping}; } return tracker.newestHint(first, stop); #else @@ -2042,24 +2314,29 @@ std::string WaitedBehindReport(const HolderMatrixValues& waited) { } -// The owner token is a thread-local's address: unique per live thread and free to read. namespace { -const void* ThreadToken() { - thread_local char token; - return &token; -} - std::atomic gpuHoldSerial{0}; } +void GpuMutexType::enter() { + if (!HeldByThisThread()) LockSpinning(mutex, HandoffSpinWindow()); +} + +bool GpuMutexType::tryEnter() { + return HeldByThisThread() || mutex.try_lock(); +} + void GpuMutexType::acquired() { - owner.store(ThreadToken(), std::memory_order_relaxed); - if (++depth == 1) gpuHoldSerial.fetch_add(1, std::memory_order_relaxed); + owner.store(CurrentThreadToken(), std::memory_order_relaxed); + if (++depth == 1) { + gpuHoldSerial.fetch_add(1, std::memory_order_relaxed); + if (!proofThread) BreakProofs(); + } } bool GpuMutexType::try_lock() { if (!GpuLockProfiled()) { - if (!mutex.try_lock()) return false; + if (!tryEnter()) return false; acquired(); return true; } @@ -2068,7 +2345,7 @@ bool GpuMutexType::try_lock() { auto& stats = LockStats(); const auto tagged = std::exchange(stats.nextSite, GpuLockSite::Other); ++stats.tries; - if (!mutex.try_lock()) { + if (!tryEnter()) { ++stats.triesFailed; return false; } @@ -2095,8 +2372,8 @@ void GpuMutexType::unlock() { owner.store(nullptr, std::memory_order_relaxed); // Still the holder here: the hold ends when the mutex is given up, not before. if (GpuHoldProfiled()) EndHold(); + mutex.unlock(); } - mutex.unlock(); // After the release, so what the hook does (destroying a batch's kept objects) is neither part // of the hold nor waited for by the next holder. if (outermost) { @@ -2105,7 +2382,7 @@ void GpuMutexType::unlock() { } bool GpuMutexType::HeldByThisThread() const { - return owner.load(std::memory_order_relaxed) == ThreadToken(); + return owner.load(std::memory_order_relaxed) == CurrentThreadToken(); } std::uint32_t GpuMutexType::DepthOnThisThread() const { @@ -2124,10 +2401,10 @@ void GpuMutexType::lock() { if (!GpuLockProfiled()) { auto* trace = PerformanceTrace::Recording() ? PerformanceTrace::Thread() : nullptr; if (trace == nullptr) { - LockSpinning(mutex, HandoffSpinWindow()); + enter(); } else { const auto site = static_cast(std::exchange(LockStats().nextSite, GpuLockSite::Other)); - if (!mutex.try_lock()) { + if (!tryEnter()) { const auto start = std::chrono::steady_clock::now(); LockSpinning(mutex, HandoffSpinWindow()); trace->Add("GpuMutex.wait", GpuLockSiteNames[site], start, std::chrono::steady_clock::now()); @@ -2142,7 +2419,7 @@ void GpuMutexType::lock() { ++stats.siteAcquisitions[site]; // An uncontended (or recursive) acquisition costs no clock reads for the wait; only a wait is // timed. The hold timer (outermost acquisitions only) starts once the mutex is held. - if (!mutex.try_lock()) { + if (!tryEnter()) { const bool holders = GpuHoldProfiled(); HolderMatrixValues before{}; if (holders) Holders().Snapshot(before); @@ -2409,6 +2686,7 @@ void WriteChangedCommitted(std::uint64_t address, std::span cur void WriteChanged(std::uint64_t address, std::span current, std::span original) { require(current.size() == original.size(), "write-back snapshot sizes differ"); + BreakProofs(); if (current.empty()) return; // A store: the hook sync it makes orders this write after a pending GPU write, whatever a read // site above it was doing (the [hooksync] line counts stores apart). diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/HostAllocator.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/HostAllocator.cpp index cbbb9c549d..d33999f9e3 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/HostAllocator.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/HostAllocator.cpp @@ -24,6 +24,41 @@ struct Cache { std::uint16_t counts[Classes]; }; +constexpr std::uint16_t KeptLists = 8; + +struct Depot { + SRWLOCK lock = SRWLOCK_INIT; + void* lists = nullptr; + std::uint16_t count = 0; +}; + +Depot depots[Classes]; + +bool KeepList(std::size_t sizeClass, void* list) noexcept { + auto& depot = depots[sizeClass]; + AcquireSRWLockExclusive(&depot.lock); + const bool kept = depot.count < KeptLists; + if (kept) { + static_cast(list)[1] = depot.lists; + depot.lists = list; + ++depot.count; + } + ReleaseSRWLockExclusive(&depot.lock); + return kept; +} + +void* TakeList(std::size_t sizeClass) noexcept { + auto& depot = depots[sizeClass]; + AcquireSRWLockExclusive(&depot.lock); + void* list = depot.lists; + if (list != nullptr) { + depot.lists = static_cast(list)[1]; + --depot.count; + } + ReleaseSRWLockExclusive(&depot.lock); + return list; +} + Cache* const Closed = reinterpret_cast(std::uintptr_t{1}); constexpr unsigned long TlsSlotsOffset = 0x1480; @@ -114,6 +149,12 @@ void* TryAllocate(std::size_t bytes) noexcept { if (bytes <= LargestCached) { const auto sizeClass = (bytes - 1) / Granule; if (auto* cache = CurrentCache()) { + if (cache->heads[sizeClass] == nullptr) { + if (void* list = TakeList(sizeClass)) { + cache->heads[sizeClass] = list; + cache->counts[sizeClass] = Depth(sizeClass); + } + } if (void* block = cache->heads[sizeClass]) { cache->heads[sizeClass] = *static_cast(block); --cache->counts[sizeClass]; @@ -140,6 +181,10 @@ void Release(void* block, std::size_t bytes = 0) noexcept { const auto usable = bytes != 0 && bytes % Granule == 0 ? bytes : _msize(block); if (usable >= Granule && usable < LargestCached + Granule) { const auto sizeClass = usable / Granule - 1; + if (cache->counts[sizeClass] == Depth(sizeClass) && KeepList(sizeClass, cache->heads[sizeClass])) { + cache->heads[sizeClass] = nullptr; + cache->counts[sizeClass] = 0; + } if (cache->counts[sizeClass] < Depth(sizeClass)) { *static_cast(block) = cache->heads[sizeClass]; cache->heads[sizeClass] = block; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 20b73f581a..09dda98153 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -1,8 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/PreciseWait.hpp" #include +#include #include #include #include @@ -12,8 +14,6 @@ #include #include -extern "C" void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protection, int flags, int descriptor, std::int64_t offset) noexcept; - namespace AgcDriver::Pm4 { std::uint64_t GdsAddress() { @@ -114,7 +114,6 @@ std::vector copySource(std::uint64_t source, std::size_t bytes, bool return data; } - bool gdsRange(std::uint64_t offset, std::size_t bytes) { return offset <= GdsBytes && bytes <= GdsBytes - offset; } @@ -226,8 +225,7 @@ std::string_view UnsupportedReason(std::uint32_t header) { return "graphics draw, shader stages and guest render-target materialization are not implemented"; case 0x33: case 0x3f: return "nested command buffers, branching and nested flip reservation are not implemented"; case 0x3c: case 0x93: return {}; - case 0x39: - return "cooperative command-queue waits are not implemented"; + case 0x39: return {}; case 0x59: return {}; case 0x84: case 0x85: case 0x86: case 0x88: return "separate CE/DE execution and counter synchronization are not implemented"; @@ -450,7 +448,7 @@ void Validate(std::span packet, std::uint32_t queue) { } case 0x59: size(2); - require((packet[1] & 0x7fffffffu) == 0, "unsupported REWIND payload bits"); + require((packet[1] & ~(RewindValid | RewindOffloadEnable)) == 0, "unsupported REWIND payload bits"); break; case 0x3c: case 0x93: { size(opcode == 0x3c ? 7 : 9); @@ -458,6 +456,12 @@ void Validate(std::span packet, std::uint32_t queue) { require((packet[1] & 7u) <= 6u, "invalid WAIT_REG_MEM compare function"); break; } + case 0x39: + size(4); + require((packet[3] >> 29u) == 6u || (packet[3] >> 29u) == 7u, "invalid MEM_SEMAPHORE operation"); + require(address(packet[1], packet[2]) != 0 && (packet[1] & 7u) == 0, "null or misaligned MEM_SEMAPHORE address"); + require((packet[3] & 0x10000u) == 0, "MEM_SEMAPHORE mailbox waits are not implemented"); + break; case 0x45: { size(9); require((packet[1] & ~0x317u) == 0, "COND_WRITE reserved fields are not implemented"); @@ -716,6 +720,7 @@ std::vector DecodeStores(std::span packet) { if (!label.has_value()) return {}; return {known(label->address, label->Bytes())}; } + case 0x39: return {runTime(address(packet[1], packet[2]), 8)}; case 0x40: { require(packet.size() == 6, "invalid COPY_DATA size"); if (CopyDataToRegister(packet)) return {}; @@ -771,7 +776,7 @@ bool UsesGpuCacheBarrier(std::span packet) { bool AccessesMemory(std::uint32_t header) { switch ((header >> 8u) & 0xffu) { - case 0x22: case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x45: case 0x50: case 0x63: case 0x64: case 0x83: case 0x91: case 0x9f: return true; + case 0x22: case 0x27: case 0x39: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x45: case 0x50: case 0x63: case 0x64: case 0x83: case 0x91: case 0x9f: return true; default: return false; } } @@ -846,7 +851,7 @@ DrawParameters resolveIndirectDraw(std::span packet, const indirect.drawIndexLocation = multi ? location(packet[4] & 0xffffu) : 0x280u; indirect.drawIndexEnabled = multi && (packet[4] >> 31u) != 0; indirect.indxOffset = 0; - require(indirect.count <= (std::numeric_limits::max() - indirect.arguments - indirect.recordBytes) / indirect.stride, "indirect draw record range overflow"); + require(indirect.RangeBytes() <= std::numeric_limits::max() - indirect.arguments, "indirect draw record range overflow"); // The counts are unknown here (the CP takes them from the record; NUM_INSTANCES is ignored). DrawParameters draw{0, 0, 0, 0, packet.back() & 0x20u, indexed, 0, 0}; if (!indexed) { @@ -856,12 +861,17 @@ DrawParameters resolveIndirectDraw(std::span packet, const indirect.indxOffset = offset->second; } else { const auto indexSize = indexSizeOf(queue); - require(queue.indexBufferSize != 0, "INDEX_BUFFER_SIZE has not been set"); - const auto bytes = static_cast(queue.indexBufferSize) * indexSize; - require(bytes <= std::numeric_limits::max() && bytes <= std::numeric_limits::max() - queue.indexBase, "index range size overflow"); draw.indexAddress = queue.indexBase; - draw.indexCount = queue.indexBufferSize; draw.indexSize = indexSize; + if (queue.indexBufferSize) { + require(*queue.indexBufferSize != 0, "INDEX_BUFFER_SIZE is zero"); + const auto bytes = static_cast(*queue.indexBufferSize) * indexSize; + require(bytes <= std::numeric_limits::max() && bytes <= std::numeric_limits::max() - queue.indexBase, "index range size overflow"); + draw.indexCount = *queue.indexBufferSize; + } else { + draw.indexCount = 0; + indirect.indexLimitKnown = false; + } } draw.indirect = indirect; return draw; @@ -1072,6 +1082,47 @@ void Execute(std::span packet, QueueState& queue) { } return; } + case 0x39: { + const auto semaphore = address(packet[1], packet[2]); + auto* word = reinterpret_cast(semaphore); + std::atomic_ref shared(*word); + const auto prepare = [&] { + GuestMemory::GuardRead(semaphore, sizeof(*word)); + GuestMemory::FlushGpuWrites(semaphore, sizeof(*word)); + GuestMemory::CheckRange(word, sizeof(*word), std::atomic_ref::required_alignment, true); + }; + if ((packet[3] >> 29u) == 6u) { + prepare(); + const bool set = (packet[3] & 0x100000u) != 0; + GuestMemory::StoreOwnBytes(semaphore, sizeof(*word), [&] { + if (set) shared.store(1, std::memory_order_release); + else shared.fetch_add(1, std::memory_order_acq_rel); + }); + return; + } + const auto start = std::chrono::steady_clock::now(); + bool warned = false; + for (;;) { + { + const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait); + prepare(); + } + auto value = shared.load(std::memory_order_acquire); + if (value != 0) { + bool taken = false; + GuestMemory::StoreOwnBytes(semaphore, sizeof(*word), [&] { + taken = shared.compare_exchange_strong(value, value - 1, std::memory_order_acq_rel); + }); + if (taken) return; + continue; + } + PreciseSleepUs(50); + if (!warned && std::chrono::steady_clock::now() - start > std::chrono::seconds(5)) { + warned = true; + std::fprintf(stderr, "[gpu] MEM_SEMAPHORE wait at 0x%llx still waiting after 5s\n", static_cast(semaphore)); + } + } + } case 0x49: { // Earlier work has drained by the time the driver reaches this packet, so the end-of-pipe // write can happen immediately. diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/PresentationScaler.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/PresentationScaler.cpp index d7c88d7b7d..bc07a8d8db 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/PresentationScaler.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/PresentationScaler.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp" #include "prx/libSceAgcDriver/Execution/include/AspectFit.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" namespace AgcDriver { @@ -23,7 +24,7 @@ PresentationScaler::~PresentationScaler() { void PresentationScaler::release() noexcept { if (sourceImage != VK_NULL_HANDLE) context.Function("vkDestroyImage")(context.device, sourceImage, nullptr); - if (sourceMemory != VK_NULL_HANDLE) context.Function("vkFreeMemory")(context.device, sourceMemory, nullptr); + if (sourceMemory != VK_NULL_HANDLE) Graphics::ReleaseImageMemory(context, sourceMemory, sourceBytes, sourceMemoryType); sourceImage = VK_NULL_HANDLE; sourceMemory = VK_NULL_HANDLE; sourceWidth = 0; @@ -50,8 +51,9 @@ void PresentationScaler::EnsureSourceImage(std::uint32_t width, std::uint32_t he context.Function("vkGetImageMemoryRequirements")(context.device, sourceImage, &requirements); VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; allocation.allocationSize = requirements.size; - allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - Graphics::Check(context.Function("vkAllocateMemory")(context.device, &allocation, nullptr, &sourceMemory), "vkAllocateMemory presentation source"); + allocation.memoryTypeIndex = context.DeviceMemoryType(requirements.memoryTypeBits, requirements.size, "presentation source"); + sourceMemory = Graphics::AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory presentation source", &sourceMemoryType, true); + sourceBytes = requirements.size; Graphics::Check(context.Function("vkBindImageMemory")(context.device, sourceImage, sourceMemory, 0), "vkBindImageMemory presentation source"); sourceWidth = width; sourceHeight = height; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp index 6a930d4e30..9e60cbac8c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp @@ -16,8 +16,12 @@ namespace { std::atomic requested{false}; std::filesystem::path directory() { +#ifdef _WIN32 + const wchar_t* configured = _wgetenv(L"ANYPS5_SCREENSHOTS"); +#else const char* configured = std::getenv("ANYPS5_SCREENSHOTS"); - if (configured != nullptr && configured[0] != '\0') return configured; +#endif + if (configured != nullptr && configured[0] != 0) return configured; const std::unique_ptr basePath(SDL_GetBasePath(), SDL_free); if (!basePath) throw std::runtime_error(std::string("Screenshot: cannot locate the executable: ") + SDL_GetError()); return std::filesystem::path(reinterpret_cast(basePath.get())) / "anyps5-screenshots"; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp index 253d832b7c..4fc0fb89bc 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include namespace AgcDriver { namespace { @@ -130,6 +132,23 @@ std::vector>& pagePool() { constexpr std::size_t PagePoolLimit = 64; +bool sharedPagePools() { + static const bool enabled = std::getenv("APS5_DRAW_PIPELINE") != nullptr; + return enabled; +} + +template +struct SharedPagePool { + std::mutex mutex; + std::vector> pages; +}; + +template +SharedPagePool& sharedPagePool() { + static auto* pool = new SharedPagePool(); + return *pool; +} + } void ShaderMemory::SetWaitedMsProvider(WaitedMsProvider provider) { @@ -144,6 +163,15 @@ ShaderMemory::ShaderMemory(std::span regio } ShaderMemory::~ShaderMemory() { + if (sharedPagePools() && !pages.empty()) { + auto& shared = sharedPagePool(); + std::lock_guard lock(shared.mutex); + for (const auto& [base, page] : pages) { + if (shared.pages.size() < 1024) shared.pages.emplace_back(page); + else delete page; + } + return; + } auto& pool = pagePool(); for (const auto& [base, page] : pages) { if (pool.size() < PagePoolLimit) pool.emplace_back(page); @@ -191,6 +219,14 @@ std::size_t ShaderMemory::pageAtOrAfter(std::uint64_t base) const { ShaderMemory::Page& ShaderMemory::addPage(std::size_t position, std::uint64_t base) { auto& pool = pagePool(); + if (pool.empty() && sharedPagePools()) { + auto& shared = sharedPagePool(); + std::lock_guard lock(shared.mutex); + for (std::size_t i = 0; i < 32 && !shared.pages.empty(); ++i) { + pool.push_back(std::move(shared.pages.back())); + shared.pages.pop_back(); + } + } std::unique_ptr owned; if (pool.empty()) { owned = std::make_unique(); @@ -356,7 +392,7 @@ bool ShaderMemory::readWords(void* context, std::uint64_t address, std::uint32_t } auto last = first + BlockWords; while (last < end && !page->valid.TestBlock(last)) last += BlockWords; - std::memcpy(&page->words[first], reinterpret_cast(base + first * sizeof(std::uint32_t)), (last - first) * sizeof(std::uint32_t)); + 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; } @@ -459,8 +495,8 @@ bool ShaderMemory::place(std::uint64_t address, const std::uint32_t* values, std if (!page.valid.Test(index + word)) page.words[index + word] = values[word]; } } - if (headOpen && head < index) std::memcpy(&page.words[head], reinterpret_cast(base + head * sizeof(std::uint32_t)), (index - head) * sizeof(std::uint32_t)); - if (tailOpen && index + words < tail) std::memcpy(&page.words[index + words], reinterpret_cast(base + (index + words) * sizeof(std::uint32_t)), (tail - index - words) * sizeof(std::uint32_t)); + if (headOpen && head < index) GuestMemory::CopyGuestBytes(&page.words[head], reinterpret_cast(base + head * sizeof(std::uint32_t)), (index - head) * sizeof(std::uint32_t)); + if (tailOpen && index + words < tail) GuestMemory::CopyGuestBytes(&page.words[index + words], reinterpret_cast(base + (index + words) * sizeof(std::uint32_t)), (tail - index - words) * sizeof(std::uint32_t)); page.valid.SetRun(page.lazy ? head : index, page.lazy ? tail - head : words); page.read.SetRun(index, words); if (recent) page.recent.SetRun(index, words); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp index 217e5b0c81..9caddde7bd 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp @@ -101,8 +101,12 @@ SubmissionCapture::SubmissionCapture(const std::filesystem::path& directory) : d SubmissionCapture* SubmissionCapture::Active() { static SubmissionCapture* const capture = [] { +#ifdef _WIN32 + const wchar_t* directory = _wgetenv(L"APS5_DUMP_SUBMITS"); +#else const char* directory = std::getenv("APS5_DUMP_SUBMITS"); - return directory != nullptr && *directory != '\0' ? new SubmissionCapture(directory) : nullptr; +#endif + return directory != nullptr && *directory != 0 ? new SubmissionCapture(directory) : nullptr; }(); return capture; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp index 2db74fc43c..e88d6475f4 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp @@ -12,6 +12,12 @@ #ifdef _WIN32 #include +#elif defined(__APPLE__) +#include +#include +#include +#include +#include #else #include #include @@ -42,7 +48,30 @@ void LogOnce(const char* role, const std::string& text) { std::fprintf(stderr, "[thread-priority] %s: %s\n", role, text.c_str()); } -#ifndef _WIN32 +#if defined(__APPLE__) + +void Raise(const char* role) { + if (RequestedMode() == ThreadPriorityMode::Realtime) { + mach_timebase_info_data_t timebase{}; + mach_timebase_info(&timebase); + const auto ticks = [&](std::uint64_t nanos) { + return static_cast(nanos * timebase.denom / timebase.numer); + }; + thread_time_constraint_policy_data_t policy{ticks(1000000), ticks(500000), ticks(1000000), TRUE}; + const auto status = thread_policy_set(pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY, + reinterpret_cast(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (status != KERN_SUCCESS) + throw std::runtime_error(std::string("APS5_THREAD_PRIORITY=rt: ") + role + " time-constraint policy refused (kern_return " + std::to_string(status) + ")"); + LogOnce(role, "Mach time-constraint policy"); + return; + } + const int error = pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0); + if (error != 0) + throw std::runtime_error(std::string("APS5_THREAD_PRIORITY=high: ") + role + " user-interactive QoS refused: " + std::strerror(error)); + LogOnce(role, "user-interactive QoS"); +} + +#elif !defined(_WIN32) struct DBusConnection; struct DBusMessage; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 286da2ec1f..2306dbe33b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2,11 +2,13 @@ #include "prx/libSceAgcDriver/Execution/include/FrameDumpIo.hpp" #include "prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp" #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "BdaAbi.hpp" #if ANYPS5_ENABLE_SPIRV_TOOLS #include "SpirvBackend/SpirvOptimizer.hpp" #endif #include "prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Execution/include/FloatFlushProbe.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" @@ -18,17 +20,19 @@ #include "prx/libSceAgcDriver/Execution/include/Float32FlushControl.hpp" #include "prx/libSceAgcDriver/Execution/include/PipelineLinkOptimization.hpp" #include "prx/libSceAgcDriver/Execution/include/DrawInputsInPlace.hpp" -#include "prx/libSceAgcDriver/Execution/include/RobustBufferAccess.hpp" #include "prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp" +#include "prx/libSceAgcDriver/Execution/include/TessellationPipelineCache.hpp" #include "prx/libSceAgcDriver/Execution/include/SubgroupClock.hpp" #include "prx/libSceAgcDriver/Execution/include/ShaderDeviceProfile.hpp" #include "prx/libSceAgcDriver/Execution/include/PresentMode.hpp" +#include "prx/libSceAgcDriver/Execution/include/SwapchainImages.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceOverlay.hpp" #include "prx/libSceAgcDriver/Execution/include/PresentationPass.hpp" #include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp" #include "prx/libSceAgcDriver/Execution/include/Screenshot.hpp" #include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/PlaceholderImages.hpp" #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" #include "prx/libSceAgcDriver/Execution/include/DisplayFormat.hpp" @@ -81,6 +85,51 @@ #include namespace AgcDriver { + +DeviceQueuePlan PlanDeviceQueues(std::span families, std::uint32_t family, bool settles, bool sparseBinding, bool presents) { + DeviceQueuePlan plan; + const auto count = static_cast(families.size()); + if (settles) { + if (family < count && families[family].queueCount >= 2) { + plan.mainCount = 2; + plan.settle = true; + plan.settleFamily = family; + plan.settleIndex = 1; + } else { + const auto copies = [&](std::uint32_t index) { return index != family && families[index].queueCount != 0 && (families[index].queueFlags & (VK_QUEUE_TRANSFER_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT)) != 0; }; + for (std::uint32_t i = 0; i < count && !plan.settle; ++i) { + if (copies(i) && (families[i].queueFlags & (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT)) == 0) plan.settle = true, plan.settleFamily = i; + } + for (std::uint32_t i = 0; i < count && !plan.settle; ++i) { + if (copies(i)) plan.settle = true, plan.settleFamily = i; + } + } + } + if (presents && family < count) { + if (const auto index = SelectPresentQueue(families[family].queueCount, plan.mainCount)) { + plan.present = true; + plan.presentIndex = *index; + plan.mainCount = *index + 1; + } + } + if (!sparseBinding) return plan; + const auto taken = [&](std::uint32_t index) { return plan.settle && index == plan.settleFamily ? plan.settleIndex + 1 : 0u; }; + const auto binds = [&](std::uint32_t index) { return index != family && families[index].queueCount > taken(index) && (families[index].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; }; + for (std::uint32_t i = 0; i < count && plan.sparseFamily == VK_QUEUE_FAMILY_IGNORED; ++i) { + if (binds(i) && families[i].queueFlags == VK_QUEUE_SPARSE_BINDING_BIT) plan.sparseFamily = i; + } + for (std::uint32_t i = 0; i < count && plan.sparseFamily == VK_QUEUE_FAMILY_IGNORED; ++i) { + if (binds(i)) plan.sparseFamily = i; + } + if (plan.sparseFamily != VK_QUEUE_FAMILY_IGNORED) { + plan.sparseIndex = taken(plan.sparseFamily); + plan.sparseBinding = true; + } else { + plan.sparseBinding = family < count && (families[family].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; + } + return plan; +} + namespace { void check(VkResult result, const char* operation) { @@ -130,6 +179,12 @@ void CreateComputePipeline(VkDevice device, PFN_vkCreateComputePipelines create, PerformanceCounters::CountPipeline(); } +#ifdef _WIN32 +const wchar_t* ComputePipelineDumpDirectory() { return _wgetenv(L"APS5_DUMP_COMPUTE_PIPELINES"); } +#else +const char* ComputePipelineDumpDirectory() { return std::getenv("APS5_DUMP_COMPUTE_PIPELINES"); } +#endif + bool CaptureComputePipeline(std::size_t words) { const char* minimum = std::getenv("APS5_DUMP_COMPUTE_MIN_WORDS"); if (minimum == nullptr) return true; @@ -157,20 +212,22 @@ public: std::lock_guard lock(mutex); stopping = true; } - changed.notify_one(); + changed.notify_all(); if (worker.joinable()) worker.join(); } void Enqueue(int index, std::uint32_t width, std::uint32_t height, std::uint32_t scale, bool screenshot, std::vector pixels) { - std::lock_guard lock(mutex); + std::unique_lock lock(mutex); require(!stopping, "frame dump writer has stopped"); - require(pixels.size() <= maxBytes - queuedBytes, "frame dump queue exceeded 64 MiB"); if (!screenshot && !frameDumpOutput.Enabled_nid_no_patch()) return; + require(pixels.size() <= maxBytes, "a frame dump exceeds the 64 MiB queue"); + changed.wait(lock, [&] { return stopping || pixels.size() <= maxBytes - queuedBytes; }); + require(!stopping, "frame dump writer has stopped"); if (!worker.joinable()) worker = std::thread([this] { run(); }); const auto bytes = pixels.size(); pending.push_back({index, width, height, scale, screenshot, std::move(pixels)}); queuedBytes += bytes; - changed.notify_one(); + changed.notify_all(); } bool Idle() { @@ -203,6 +260,7 @@ private: else WriteFrameBmp(frame.index, frame.width, frame.height, frame.pixels, frame.scale); std::lock_guard lock(mutex); queuedBytes -= frame.pixels.size(); + changed.notify_all(); } } } catch (const std::exception& error) { @@ -300,10 +358,20 @@ struct VulkanDevice::State { VkDeviceSize uploadSize = 0; VkPhysicalDeviceProperties properties{}; std::string driverInfo; + VkDriverId driverId = VK_DRIVER_ID_MAX_ENUM; + std::uint32_t pixelFunctionStorageDwords = 4096u; bool individuallyFreeDescriptorSets = false; + bool tessellationPipelineCache = true; PFN_vkGetCalibratedTimestampsKHR calibratedTimestamps = nullptr; PFN_vkCmdSetCheckpointNV checkpointMarker = nullptr; + PFN_vkCmdWriteBufferMarkerAMD bufferMarker = nullptr; + VkBuffer markerBuffer = VK_NULL_HANDLE; + VkDeviceMemory markerMemory = VK_NULL_HANDLE; + void* markerMapping = nullptr; + BufferMarkerLog markerLog; bool deviceLostHook = false; + bool deferDestruction = false; + PFN_vkGetDeviceProcAddr contextDeviceProc = nullptr; VkTimeDomainKHR hostTimeDomain = VK_TIME_DOMAIN_DEVICE_KHR; VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}; std::vector capabilities{1}; @@ -313,9 +381,12 @@ struct VulkanDevice::State { bool meshShader = false; bool fragmentShaderBarycentric = false; bool shaderTerminateInvocation = false; + bool fusedExactArithmetic = false; bool geometryShader = false; + bool largePoints = false; bool sampleRateShading = false; bool storageImageMultisample = false; + bool storageImageWriteWithoutFormat = false; bool shaderClipDistance = false; bool shaderCullDistance = false; bool shaderClock = false; @@ -330,6 +401,7 @@ struct VulkanDevice::State { bool earlyFragmentSampleMaskTestBeforeSampleCounting = false; bool robustBufferAccess2 = false; bool subgroupControlBarriers = true; + bool workgroupScopeFences = true; // 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; @@ -342,6 +414,7 @@ struct VulkanDevice::State { bool imageInt64Atomics = false; bool bufferInt64Atomics = false; bool viewportIndexLayer = false; + bool imageFormatList = false; bool primitiveListRestart = false; bool depthClipControl = false; bool graphicsPipelineLibrary = false; @@ -362,19 +435,25 @@ struct VulkanDevice::State { bool depthBounds = false; bool depthBiasClamp = false; bool dualSrcBlend = false; + bool logicOp = false; bool occlusionQueryPrecise = false; VkDeviceSize hostImportAlignment = 0; bool drawInputsInPlace = true; bool dmaBufImport = false; bool sparseBinding = false; + bool sparseResidencyBuffer = false; + bool sparseResidencyAliased = false; std::uint32_t queueFamily = 0; std::uint32_t sparseFamily = VK_QUEUE_FAMILY_IGNORED; + std::uint32_t sparseIndex = 0; VkQueue sparseQueue = VK_NULL_HANDLE; std::mutex sparseQueueMutex; VkQueue settleQueue = VK_NULL_HANDLE; std::uint32_t settleFamily = 0; VkCommandPool settlePool = VK_NULL_HANDLE; std::mutex settleQueueMutex; + VkQueue presentQueue = VK_NULL_HANDLE; + std::mutex presentQueueMutex; bool memoryBudget = false; bool depthRangeUnrestricted = false; bool samplerAnisotropy = false; @@ -382,6 +461,8 @@ struct VulkanDevice::State { bool samplerFilterMinmax = false; bool nonSeamlessCubeMap = false; VkSampleCountFlags mergedCoverageSampleCounts = 0; + VkSampleCountFlags truncatedCoverageSampleCounts = 0; + bool mixedAttachmentSamples = false; bool fragmentShaderPixelInterlock = false; bool conservativeRasterization = false; bool provokingVertexLast = false; @@ -414,7 +495,7 @@ struct VulkanDevice::State { // Compute pipeline objects by variant (and push-constant use), under their own mutex: the // dispatch's find-or-insert and the verify switch's lookups touch the map, recipes hold weak // references to its objects. - std::mutex computePipelinesMutex; + AgcDriver::Mutex computePipelinesMutex; std::map> computePipelines; std::map>> computePipelinesBuilding; // The journal replay building this device's pipelines on its own threads; drained first at teardown. @@ -495,7 +576,7 @@ struct VulkanDevice::State { std::uint64_t throughSerial; }; std::deque retiredRanges; - std::mutex presentMutex; + AgcDriver::Mutex presentMutex; // The presentation present() submitted and QueuePresent has not handed to the swapchain. bool queuePending = false; std::uint32_t queueIndex = 0; @@ -570,6 +651,33 @@ struct VulkanDevice::State { return workgroupMemory; } + void CreateMarkerBuffer() { + bufferMarker = DeviceFunction("vkCmdWriteBufferMarkerAMD"); + VkBufferCreateInfo buffer{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + buffer.size = 2 * sizeof(std::uint32_t); + buffer.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + buffer.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + check(DeviceFunction("vkCreateBuffer")(device, &buffer, nullptr, &markerBuffer), "vkCreateBuffer buffer markers"); + VkMemoryRequirements requirements{}; + DeviceFunction("vkGetBufferMemoryRequirements")(device, markerBuffer, &requirements); + std::uint32_t memoryType = memoryProperties.memoryTypeCount; + const auto flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + for (std::uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i) { + if ((requirements.memoryTypeBits & (1u << i)) != 0 && (memoryProperties.memoryTypes[i].propertyFlags & flags) == flags) { + memoryType = i; + break; + } + } + require(memoryType < memoryProperties.memoryTypeCount, "coherent host memory for buffer markers is unavailable"); + VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocation.allocationSize = requirements.size; + allocation.memoryTypeIndex = memoryType; + check(DeviceFunction("vkAllocateMemory")(device, &allocation, nullptr, &markerMemory), "vkAllocateMemory buffer markers"); + check(DeviceFunction("vkBindBufferMemory")(device, markerBuffer, markerMemory, 0), "vkBindBufferMemory buffer markers"); + check(DeviceFunction("vkMapMemory")(device, markerMemory, 0, buffer.size, 0, &markerMapping), "vkMapMemory buffer markers"); + std::memset(markerMapping, 0, buffer.size); + } + void Upload(std::span pixels) { if (uploadSize < pixels.size()) { if (uploadMapping) DeviceFunction("vkUnmapMemory")(device, uploadMemory); @@ -713,7 +821,7 @@ struct VulkanDevice::State { for (std::uint32_t x = 0; x < width; ++x) std::memcpy(pixels.data() + (static_cast(y) * width + x) * 4, full.data() + (static_cast(y) * scale * slot.dumpWidth + static_cast(x) * scale) * 4, 4); } static const auto start = std::chrono::steady_clock::now(); - std::fprintf(stderr, "[gpu] frame_%03d.bmp: %ux%u display read back on the GPU at %.1f s\n", index, slot.dumpWidth, slot.dumpHeight, std::chrono::duration(std::chrono::steady_clock::now() - start).count()); + AgcDriver::ProfilePrint_nid_no_patch("[gpu] frame_%03d.bmp: %ux%u display read back on the GPU at %.1f s\n", index, slot.dumpWidth, slot.dumpHeight, std::chrono::duration(std::chrono::steady_clock::now() - start).count()); dumpWriter.Enqueue(index, width, height, 1, false, std::move(pixels)); } @@ -830,6 +938,9 @@ struct VulkanDevice::State { if (uploadMapping) reinterpret_cast(deviceProc(device, "vkUnmapMemory"))(device, uploadMemory); if (uploadBuffer) reinterpret_cast(deviceProc(device, "vkDestroyBuffer"))(device, uploadBuffer, nullptr); if (uploadMemory) reinterpret_cast(deviceProc(device, "vkFreeMemory"))(device, uploadMemory, nullptr); + if (markerMapping) reinterpret_cast(deviceProc(device, "vkUnmapMemory"))(device, markerMemory); + if (markerBuffer) reinterpret_cast(deviceProc(device, "vkDestroyBuffer"))(device, markerBuffer, nullptr); + if (markerMemory) reinterpret_cast(deviceProc(device, "vkFreeMemory"))(device, markerMemory, nullptr); if (swapchain) reinterpret_cast(deviceProc(device, "vkDestroySwapchainKHR"))(device, swapchain, nullptr); const auto destroyPool = reinterpret_cast(deviceProc(device, "vkDestroyCommandPool")); const auto destroyDevice = reinterpret_cast(deviceProc(device, "vkDestroyDevice")); @@ -837,6 +948,7 @@ struct VulkanDevice::State { destroyPool(device, pool, nullptr); } if (settlePool != VK_NULL_HANDLE) destroyPool(device, settlePool, nullptr); + Graphics::DestroyDeferredObjects(device); destroyDevice(device, nullptr); } if (instance != VK_NULL_HANDLE) { @@ -1175,9 +1287,25 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver}; state->InstanceFunction("vkGetPhysicalDeviceProperties2")(selected, &driverProperties); driverId = driver.driverID; + state->deferDestruction = driver.driverID == VK_DRIVER_ID_MOLTENVK && std::getenv("APS5_NO_DEFERRED_DESTRUCTION") == nullptr; state->driverInfo = std::string(driver.driverName) + " " + driver.driverInfo; + state->driverId = driver.driverID; state->individuallyFreeDescriptorSets = driver.driverID == VK_DRIVER_ID_MESA_LLVMPIPE; + state->fusedExactArithmetic = driver.driverID == VK_DRIVER_ID_MOLTENVK; + state->tessellationPipelineCache = TessellationPipelineCacheUsable(driver.driverID); state->subgroupControlBarriers = SubgroupControlBarrierUsable(driver.driverID); + state->workgroupScopeFences = WorkgroupScopeFenceUsable(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}; + VkPhysicalDeviceProperties2 smProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &smBuiltins}; + state->InstanceFunction("vkGetPhysicalDeviceProperties2")(selected, &smProperties); + const auto subgroup = state->subgroup.subgroupSize; + if (smBuiltins.shaderSMCount != 0u && smBuiltins.shaderWarpsPerSM != 0u && subgroup != 0u) { + const auto invocations = static_cast(smBuiltins.shaderSMCount) * smBuiltins.shaderWarpsPerSM * subgroup; + const auto budgetDwords = (4ull << 30) / (invocations * 4ull); + if (budgetDwords < state->pixelFunctionStorageDwords) state->pixelFunctionStorageDwords = static_cast(budgetDwords); + } } auto byteFeatures = QueryBdaByteFeatures(selected, state->InstanceFunction("vkGetPhysicalDeviceFeatures2"), availableExtensions); auto bdaFeatures = QueryBdaFeatures(selected, state->InstanceFunction("vkGetPhysicalDeviceFeatures2"), availableExtensions); @@ -1313,6 +1441,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : state->spirvExtensions.push_back("SPV_EXT_shader_viewport_index_layer"); state->viewportIndexLayer = true; } + if (hasExtension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME)) { + deviceExtensions.push_back(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME); + state->imageFormatList = true; + } if (state->demoteToHelperInvocation) { deviceExtensions.push_back(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); state->capabilities.push_back(spv::CapabilityDemoteToHelperInvocation); @@ -1409,7 +1541,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : // Guest memory is host memory: importing it lets address-based shaders use it in place instead of // copying every registered allocation per draw. if (hasExtension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME) && std::getenv("APS5_NO_HOST_IMPORT") == nullptr) { - VkPhysicalDeviceDriverProperties driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES}; + VkPhysicalDeviceMaintenance3Properties maintenance3{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES}; + VkPhysicalDeviceDriverProperties driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES, &maintenance3}; VkPhysicalDeviceExternalMemoryHostPropertiesEXT hostProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT, &driverProperties}; VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &hostProperties}; state->InstanceFunction("vkGetPhysicalDeviceProperties2")(selected, &properties); @@ -1421,6 +1554,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : #endif state->drawInputsInPlace = std::getenv("APS5_NO_DRAW_INPUTS_IN_PLACE") == nullptr && (std::getenv("APS5_DRAW_INPUTS_IN_PLACE") != nullptr || DrawInputsInPlaceUsable(driverProperties.driverID, windows)); if (!compileOnly) Graphics::SetHostImportWindow(8ull << 20u); + if (!compileOnly) Graphics::SetHostImportMaxBytes(maintenance3.maxMemoryAllocationSize); #ifdef _WIN32 // Imported pages are locked; locking is bounded by the process working-set minimum. if (const char* value = std::getenv("APS5_WORKING_SET_MIB"); value != nullptr && !compileOnly) { @@ -1507,12 +1641,17 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : check(combinations(selected, &count, supported.data()), "vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV"); for (const auto& combination : supported) { if (combination.coverageReductionMode == VK_COVERAGE_REDUCTION_MODE_MERGE_NV && combination.colorSamples == VK_SAMPLE_COUNT_1_BIT && combination.depthStencilSamples == 0) state->mergedCoverageSampleCounts |= combination.rasterizationSamples; + if (combination.coverageReductionMode == VK_COVERAGE_REDUCTION_MODE_TRUNCATE_NV && combination.colorSamples == VK_SAMPLE_COUNT_1_BIT && combination.depthStencilSamples == combination.rasterizationSamples) state->truncatedCoverageSampleCounts |= combination.rasterizationSamples; } } - if (state->mergedCoverageSampleCounts != 0) { + if (state->mergedCoverageSampleCounts != 0 || state->truncatedCoverageSampleCounts != 0) { deviceExtensions.push_back(VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME); deviceExtensions.push_back(VK_NV_COVERAGE_REDUCTION_MODE_EXTENSION_NAME); } + } else if (hasExtension(VK_AMD_MIXED_ATTACHMENT_SAMPLES_EXTENSION_NAME)) { + state->mixedAttachmentSamples = true; + state->truncatedCoverageSampleCounts = state->properties.limits.framebufferDepthSampleCounts & state->properties.limits.framebufferColorSampleCounts & ~static_cast(VK_SAMPLE_COUNT_1_BIT); + deviceExtensions.push_back(VK_AMD_MIXED_ATTACHMENT_SAMPLES_EXTENSION_NAME); } coverageReductionFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV}; coverageReductionFeatures.coverageReductionMode = VK_TRUE; @@ -1561,36 +1700,19 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : queueInfo.queueFamilyIndex = family; queueInfo.queueCount = 1; queueInfo.pQueuePriorities = &priority; - const float priorities[2]{1.0f, 1.0f}; - bool settleQueue = false; - std::uint32_t settleFamily = family; - std::uint32_t settleIndex = 0; - std::vector queues; + const float priorities[3]{1.0f, 1.0f, 1.0f}; #ifdef _WIN32 const bool settles = true; #else const bool settles = state->dmaBufImport; #endif - if (settles) { + std::vector queues; + { std::uint32_t families = 0; auto getFamilies = state->InstanceFunction("vkGetPhysicalDeviceQueueFamilyProperties"); getFamilies(selected, &families, nullptr); queues.resize(families); getFamilies(selected, &families, queues.data()); - if (family < families && queues[family].queueCount >= 2) { - queueInfo.queueCount = 2; - queueInfo.pQueuePriorities = priorities; - settleQueue = true; - settleIndex = 1; - } else { - const auto copies = [&](std::uint32_t index) { return index != family && queues[index].queueCount != 0 && (queues[index].queueFlags & (VK_QUEUE_TRANSFER_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT)) != 0; }; - for (std::uint32_t i = 0; i < families && !settleQueue; ++i) { - if (copies(i) && (queues[i].queueFlags & (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT)) == 0) settleFamily = i, settleQueue = true; - } - for (std::uint32_t i = 0; i < families && !settleQueue; ++i) { - if (copies(i)) settleFamily = i, settleQueue = true; - } - } } VkDeviceCreateInfo deviceInfo{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; deviceInfo.queueCreateInfoCount = 1; @@ -1625,50 +1747,38 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : // Guest shaders routinely read past descriptor ranges; robust access turns that into zeros // instead of a GPU fault that loses the device. enabled.robustBufferAccess = available.robustBufferAccess; + const auto plan = PlanDeviceQueues(queues, family, settles, available.sparseBinding == VK_TRUE, window != nullptr); + const bool settleQueue = plan.settle; + const auto settleFamily = plan.settleFamily; + const auto settleIndex = plan.settleIndex; VkDeviceQueueCreateInfo queueInfos[3]{queueInfo, {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}, {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}}; - if (available.sparseBinding && state->dmaBufImport) { - std::uint32_t families = 0; - auto getFamilies = state->InstanceFunction("vkGetPhysicalDeviceQueueFamilyProperties"); - getFamilies(selected, &families, nullptr); - std::vector queues(families); - getFamilies(selected, &families, queues.data()); - const auto binds = [&](std::uint32_t index) { return index != family && queues[index].queueCount != 0 && (queues[index].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; }; - std::uint32_t sparseFamily = families; - for (std::uint32_t i = 0; i < families && sparseFamily == families; ++i) { - if (binds(i) && queues[i].queueFlags == VK_QUEUE_SPARSE_BINDING_BIT) sparseFamily = i; + queueInfos[0].queueCount = plan.mainCount; + queueInfos[0].pQueuePriorities = plan.mainCount > 1 ? priorities : &priority; + deviceInfo.queueCreateInfoCount = 1; + const auto addQueue = [&](std::uint32_t queueFamily, std::uint32_t index) { + for (std::uint32_t info = 1; info < deviceInfo.queueCreateInfoCount; ++info) { + if (queueInfos[info].queueFamilyIndex != queueFamily) continue; + queueInfos[info].queueCount = std::max(queueInfos[info].queueCount, index + 1); + queueInfos[info].pQueuePriorities = queueInfos[info].queueCount == 2 ? priorities : &priority; + return; } - for (std::uint32_t i = 0; i < families && sparseFamily == families; ++i) { - if (binds(i)) sparseFamily = i; - } - if (sparseFamily != families) { - queueInfos[1].queueFamilyIndex = sparseFamily; - queueInfos[1].queueCount = 1; - queueInfos[1].pQueuePriorities = &priority; - deviceInfo.queueCreateInfoCount = 2; - state->sparseFamily = sparseFamily; - state->sparseBinding = true; - } else { - state->sparseBinding = family < families && (queues[family].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; - } - enabled.sparseBinding = state->sparseBinding ? VK_TRUE : VK_FALSE; - } + auto& added = queueInfos[deviceInfo.queueCreateInfoCount++]; + added = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; + added.queueFamilyIndex = queueFamily; + added.queueCount = index + 1; + added.pQueuePriorities = added.queueCount == 2 ? priorities : &priority; + }; + if (plan.settle && plan.settleFamily != family) addQueue(plan.settleFamily, plan.settleIndex); + if (plan.sparseFamily != VK_QUEUE_FAMILY_IGNORED) addQueue(plan.sparseFamily, plan.sparseIndex); deviceInfo.pQueueCreateInfos = queueInfos; - if (settleQueue && settleFamily != family) { - if (deviceInfo.queueCreateInfoCount == 2 && queueInfos[1].queueFamilyIndex == settleFamily) { - settleQueue = queues[settleFamily].queueCount >= 2; - if (settleQueue) { - queueInfos[1].queueCount = 2; - queueInfos[1].pQueuePriorities = priorities; - settleIndex = 1; - } - } else { - auto& settleInfo = queueInfos[deviceInfo.queueCreateInfoCount++]; - settleInfo = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; - settleInfo.queueFamilyIndex = settleFamily; - settleInfo.queueCount = 1; - settleInfo.pQueuePriorities = &priority; - } - } + state->sparseFamily = plan.sparseFamily; + state->sparseIndex = plan.sparseIndex; + state->sparseBinding = plan.sparseBinding; + enabled.sparseBinding = state->sparseBinding ? VK_TRUE : VK_FALSE; + enabled.sparseResidencyBuffer = state->sparseBinding ? available.sparseResidencyBuffer : VK_FALSE; + enabled.sparseResidencyAliased = state->sparseBinding ? available.sparseResidencyAliased : VK_FALSE; + state->sparseResidencyBuffer = enabled.sparseResidencyBuffer == VK_TRUE; + state->sparseResidencyAliased = enabled.sparseResidencyAliased == VK_TRUE; // Indirect draw records with a non-zero first instance, and several records per call. enabled.drawIndirectFirstInstance = available.drawIndirectFirstInstance; enabled.multiDrawIndirect = available.multiDrawIndirect; @@ -1684,6 +1794,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : enabled.depthBiasClamp = available.depthBiasClamp; state->depthBiasClamp = enabled.depthBiasClamp == VK_TRUE; enabled.dualSrcBlend = available.dualSrcBlend; + enabled.logicOp = available.logicOp; + state->logicOp = enabled.logicOp == VK_TRUE; state->dualSrcBlend = enabled.dualSrcBlend == VK_TRUE; enabled.occlusionQueryPrecise = available.occlusionQueryPrecise; state->occlusionQueryPrecise = enabled.occlusionQueryPrecise == VK_TRUE; @@ -1693,6 +1805,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : 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; // Gathers with non-constant offsets (ImageGatherExtended). enabled.shaderImageGatherExtended = available.shaderImageGatherExtended; @@ -1702,6 +1815,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : enabled.sampleRateShading = available.sampleRateShading; enabled.geometryShader = available.geometryShader; state->geometryShader = enabled.geometryShader == VK_TRUE; + enabled.largePoints = available.largePoints; + state->largePoints = enabled.largePoints == VK_TRUE; state->sampleRateShading = enabled.sampleRateShading == VK_TRUE; if (enabled.sampleRateShading) state->capabilities.push_back(spv::CapabilityInterpolationFunction); if (enabled.geometryShader) state->capabilities.push_back(spv::CapabilityGeometry); @@ -1802,7 +1917,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : nonSeamlessCubeFeatures.pNext = const_cast(deviceInfo.pNext); deviceInfo.pNext = &nonSeamlessCubeFeatures; } - if (state->mergedCoverageSampleCounts != 0) { + if (!state->mixedAttachmentSamples && (state->mergedCoverageSampleCounts != 0 || state->truncatedCoverageSampleCounts != 0)) { coverageReductionFeatures.pNext = const_cast(deviceInfo.pNext); deviceInfo.pNext = &coverageReductionFeatures; } @@ -1931,26 +2046,44 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : faultFeatures.pNext = byteFeatures.pNext; byteFeatures.pNext = &faultFeatures; } - const bool checkpoints = std::getenv("APS5_GPU_CHECKPOINTS") != nullptr && hasExtension(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME); - if (checkpoints) { - deviceExtensions.push_back(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME); + const bool checkpointsRequested = std::getenv("APS5_GPU_CHECKPOINTS") != nullptr; + const bool checkpoints = checkpointsRequested && hasExtension(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME); + const bool bufferMarkers = checkpointsRequested && !checkpoints && hasExtension(VK_AMD_BUFFER_MARKER_EXTENSION_NAME); + if (checkpoints || bufferMarkers) { + deviceExtensions.push_back(checkpoints ? VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME : VK_AMD_BUFFER_MARKER_EXTENSION_NAME); deviceInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); deviceInfo.ppEnabledExtensionNames = deviceExtensions.data(); } + const bool diagnosticsRequested = std::getenv("APS5_NV_DIAGNOSTICS") != nullptr; + VkPhysicalDeviceDiagnosticsConfigFeaturesNV diagnosticsFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DIAGNOSTICS_CONFIG_FEATURES_NV}; + if (diagnosticsRequested && hasExtension(VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME)) { + VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &diagnosticsFeatures}; + state->InstanceFunction("vkGetPhysicalDeviceFeatures2")(selected, &features); + } + const auto diagnostics = ChooseDeviceDiagnostics(diagnosticsRequested, diagnosticsFeatures.diagnosticsConfig == VK_TRUE, hasExtension(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME)); + VkDeviceDiagnosticsConfigCreateInfoNV diagnosticsConfig{VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV}; + if (diagnostics.config) { + diagnosticsFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DIAGNOSTICS_CONFIG_FEATURES_NV}; + diagnosticsFeatures.diagnosticsConfig = VK_TRUE; + diagnosticsConfig.flags = diagnostics.flags; + deviceExtensions.push_back(VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME); + if (diagnostics.checkpoints && !checkpoints) deviceExtensions.push_back(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME); + deviceInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + deviceInfo.ppEnabledExtensionNames = deviceExtensions.data(); + diagnosticsConfig.pNext = byteFeatures.pNext; + diagnosticsFeatures.pNext = &diagnosticsConfig; + byteFeatures.pNext = &diagnosticsFeatures; + std::fprintf(stderr, "[gpu] APS5_NV_DIAGNOSTICS: VK_NV_device_diagnostics_config enabled (shader debug info, resource tracking, automatic checkpoints, shader error reporting)\n"); + } else if (diagnosticsRequested) { + std::fprintf(stderr, "[gpu] APS5_NV_DIAGNOSTICS: the device does not support VK_NV_device_diagnostics_config\n"); + } bdaFeatures.pNext = &byteFeatures; VkPhysicalDeviceRobustness2FeaturesEXT robustness2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT}; if (hasExtension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)) { VkPhysicalDeviceFeatures2 queried{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &robustness2}; state->InstanceFunction("vkGetPhysicalDeviceFeatures2")(selected, &queried); } - VkDriverId robustnessDriver = VK_DRIVER_ID_MAX_ENUM; - if (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}; - state->InstanceFunction("vkGetPhysicalDeviceProperties2")(selected, &driverProperties); - robustnessDriver = driver.driverID; - } - state->robustBufferAccess2 = robustness2.robustBufferAccess2 == VK_TRUE && enabled.robustBufferAccess == VK_TRUE && RobustBufferAccess2Usable(robustnessDriver); + state->robustBufferAccess2 = robustness2.robustBufferAccess2 == VK_TRUE && enabled.robustBufferAccess == VK_TRUE; if (robustness2.nullDescriptor == VK_TRUE || state->robustBufferAccess2) { const bool nullDescriptor = robustness2.nullDescriptor == VK_TRUE; robustness2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT}; @@ -1967,6 +2100,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : check(state->InstanceFunction("vkCreateDevice")(selected, &deviceInfo, nullptr, &state->device), "vkCreateDevice"); state->shaderProfile = std::move(shaderProfile); if (compileOnly) return; + if (state->deferDestruction) state->contextDeviceProc = Graphics::DeferDestructionToIdle(state->device, state->deviceProc); if (calibratedTimestamps != nullptr) { const bool khr = std::strcmp(calibratedTimestamps, VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME) == 0; const auto domains = reinterpret_cast(state->instanceProc(state->instance, khr ? "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR" : "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT")); @@ -1988,16 +2122,26 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : } state->DeviceFunction("vkGetDeviceQueue")(state->device, family, 0, &state->queue); state->queueFamily = family; + if (plan.present) { + state->DeviceFunction("vkGetDeviceQueue")(state->device, family, plan.presentIndex, &state->presentQueue); + APS5_LOG_OUT("Presentation queue family=%u index=%u", family, plan.presentIndex); + } if (settleQueue) { state->settleFamily = settleFamily; state->DeviceFunction("vkGetDeviceQueue")(state->device, settleFamily, settleIndex, &state->settleQueue); } - if (state->sparseFamily != VK_QUEUE_FAMILY_IGNORED) state->DeviceFunction("vkGetDeviceQueue")(state->device, state->sparseFamily, 0, &state->sparseQueue); + if (state->sparseFamily != VK_QUEUE_FAMILY_IGNORED) state->DeviceFunction("vkGetDeviceQueue")(state->device, state->sparseFamily, state->sparseIndex, &state->sparseQueue); if (checkpoints) state->checkpointMarker = state->DeviceFunction("vkCmdSetCheckpointNV"); - if (deviceFault || checkpoints) { + if (bufferMarkers) state->CreateMarkerBuffer(); + if (deviceFault || checkpoints || bufferMarkers) { const auto faultInfo = deviceFault ? state->DeviceFunction("vkGetDeviceFaultInfoEXT") : nullptr; const auto checkpointData = checkpoints ? state->DeviceFunction("vkGetQueueCheckpointDataNV") : nullptr; - Graphics::DeviceLostHook() = [device = state->device, queue = state->queue, faultInfo, checkpointData] { + Graphics::DeviceLostHook() = [device = state->device, queue = state->queue, faultInfo, checkpointData, owner = state.get()] { + if (owner->markerMapping != nullptr) { + std::uint32_t words[2]; + std::memcpy(words, owner->markerMapping, sizeof(words)); + for (const auto& line : owner->markerLog.Report(words[0], words[1])) std::fprintf(stderr, "%s\n", line.c_str()); + } for (const auto& line : DeviceFaultReport(device, faultInfo, queue, checkpointData)) std::fprintf(stderr, "%s\n", line.c_str()); }; Graphics::DeviceLostOwner() = state.get(); @@ -2072,7 +2216,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : require((surface.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0, "surface does not support color attachment images"); VkSwapchainCreateInfoKHR swapchain{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR}; swapchain.surface = state->surface; - swapchain.minImageCount = surface.minImageCount; + swapchain.minImageCount = AgcDriver::SwapchainImageCount(surface); swapchain.imageFormat = state->surfaceFormat; swapchain.imageColorSpace = state->surfaceColorSpace; swapchain.imageExtent = state->extent; @@ -2176,6 +2320,26 @@ bool VulkanDevice::ReplayComputePipeline(const ShaderRecompiler::RecompileResult return state->computePipelines.try_emplace(key, std::move(objects)).second; } +bool VulkanDevice::HasComputePipeline(const ShaderRecompiler::RecompileResult& shader) const { + const std::array shaders{{{ShaderRecompiler::ShaderStage::Compute, &shader, 0}}}; + const auto key = ComputePipelineKey::Of(shader, Graphics::PushConstantStages(shaders) != 0); + std::lock_guard pipelines(state->computePipelinesMutex); + return !key.identity.Empty() && (state->computePipelines.contains(key) || state->computePipelinesBuilding.contains(key)); +} + +void VulkanDevice::PrebuildComputePipeline(const ShaderRecompiler::RecompileResult& shader, std::uint64_t programAddress) { + if (shader.spirv.size() < 5 || shader.spirv[0] != 0x07230203u) return; + ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion); + const auto context = graphicsContext(); + if (context.descriptorCache == nullptr) return; + const std::array shaders{{{ShaderRecompiler::ShaderStage::Compute, &shader, 0}}}; + const auto pushStages = Graphics::PushConstantStages(shaders); + if (pushStages != 0 && state->properties.limits.maxPushConstantsSize < Graphics::ComputePushConstantBytes) return; + if (HasComputePipeline(shader)) return; + const auto layouts = Graphics::DescribeSetLayouts(context, shaders); + static_cast(computePipeline(context, shader, layouts.layouts.main, layouts.mainKey, pushStages, programAddress)); +} + bool VulkanDevice::ImportGuestMemory(const GuestAllocations::Mapped& ranges, std::uint64_t generation, bool adoptDevice) { if (adoptDevice) GuestMemory::AssertGpuLockHeld("VulkanDevice::ImportGuestMemory"); return Graphics::ImportMappedRanges(graphicsContext(), ranges, generation, adoptDevice); @@ -2204,6 +2368,7 @@ void VulkanDevice::PrepareForReplacement() { void VulkanDevice::WaitIdle() { APS5_LOG_CHARS_OUT_DEBUG("VulkanDevice::WaitIdle begin"); + static_cast(Graphics::StorageTexture::StoreDrawnLinear("wait idle")); RetirePresents(0); if (state->recorder) { // Announced with this call's return address: the [recorder] site table then names the @@ -2211,7 +2376,10 @@ void VulkanDevice::WaitIdle() { if (!state->recorder->Idle()) Graphics::Recorder::CountSync(0, __builtin_return_address(0)); state->recorder->Sync(); } + std::unique_lock presentQueueLock(state->presentQueueMutex, std::defer_lock); + if (state->presentQueue != VK_NULL_HANDLE) presentQueueLock.lock(); check(state->DeviceFunction("vkDeviceWaitIdle")(state->device), "vkDeviceWaitIdle"); + Graphics::CheckPixelFunctionLds(graphicsContext()); APS5_LOG_CHARS_OUT_DEBUG("VulkanDevice::WaitIdle complete"); } @@ -2258,6 +2426,14 @@ void VulkanDevice::WaitRecorded(std::uint64_t serial) { if (state->recorder) state->recorder->WaitSerial(serial); } +std::uint64_t VulkanDevice::SubmittedEpoch() const { + return state->recorder ? state->recorder->NewestSubmitted() : 0; +} + +bool VulkanDevice::WaitRecordedFor(std::uint64_t serial, std::uint64_t timeoutNanos) { + return state->recorder && state->recorder->WaitSerialFor(serial, timeoutNanos); +} + void VulkanDevice::ReapRecorded(std::uint64_t serial) { if (!state->recorder) return; // The drain's site in the [recorder] table is this call's caller (Driver::execute's drain, @@ -2299,6 +2475,8 @@ bool VulkanDevice::StoresPendingOver(std::uint64_t address, std::size_t bytes) c int VulkanDevice::WriteLabelOnGpu(std::uint64_t address, std::span bytes, std::uint64_t stamp, std::uint32_t queue, bool reapFirst) { static const bool storeAtLabel = Graphics::StorageTexture::StoreAtLabelRequested(std::getenv("APS5_STORE_AT_LABEL")); if (storeAtLabel) Graphics::StorageTexture::FlushAllPending("label"); + if (queue == 0) static_cast(Graphics::StorageTexture::StoreDrawnLinear("label")); + Graphics::StorageTexture::StoreReadbackBeforeLabel("readback before label"); if (!state->recorder || bytes.empty() || bytes.size() > 65536 || address % 4 != 0 || bytes.size() % 4 != 0) return 4; auto& recorder = *state->recorder; // Batches that already finished are retired before the checks: their completions ran, so they @@ -2324,10 +2502,37 @@ int VulkanDevice::WriteLabelOnGpu(std::uint64_t address, std::spanbuffer, Graphics::HostImportOffset(*import, address), bytes, address); }; - // Only a write-back that can land over these bytes (a listed copied writer of the range, as - // FillBuffer tests) puts the label behind the completions; any other pending completion - // stores elsewhere. Debug aid: APS5_LABEL_GATE_ALL=1 gates on any completion, as before (a - // completion label there registered its own completion, so every later label followed). + // On the console every store a queue made before a label is in memory once the label is + // visible. A listed copied writer (a dispatch or draw whose written buffer the device holds as a + // CPU copy) is written back by the CPU when its batch is reaped, after the GPU already ran any + // store recorded here, so a title polling the label could rewrite the written region and have + // the late write-back land over its bytes (a title that rebuilds its command buffer there sees + // a stale packet). While one is listed, a label of 8 bytes or less (every RELEASE_MEM, the fence + // WRITE_DATAs) is therefore not recorded on the GPU at all: a completion action of the open (or + // newest in-flight) batch stores it when that batch is reaped. Completions run in push order and + // batches finish front-first, so it lands after every earlier write-back, whatever range that + // one wrote. The GPU store plus completion re-store below would not do: its GPU store is + // visible before the write-back, and its re-store runs only over a write-back that overlapped + // the label. While such a completion store is pending (PendingCompletionStores: no end-of-pipe + // delivery and no write-back counts there), later small labels follow it even after every + // writer was written back, or a GPU store would become visible before the earlier completion + // store lands (#1458), or land under it at the reap when both are at one address. Larger stores + // (DUMP_CONST_RAM, COPY_DATA and DMA_DATA payloads that later GPU work reads through the import) + // stay on the GPU and follow only a pending completion store over their own range. Readers of a + // range stored this way sync through its batch first (the label is a pending write of its batch + // and counted in PendingCompletionLabels). Debug aid: APS5_DRAIN_COMPLETION_LABELS=1 drains + // (reason 2) instead and stores the label on the CPU. + const bool listed = !state->copiedWriters->empty() || !Graphics::DrawCopiedWriters()->empty(); + if (drain) { + if (listed) return 2; + } else if (const bool storesPending = Graphics::Recorder::PendingCompletionStores() != 0; bytes.size() <= 8 ? (listed || storesPending) : (storesPending && recorder.CompletionLabelIn(address, bytes.size()))) { + recorder.AfterCompletions(address, bytes, stamp, queue, false); + return 5; + } + // A larger store over a listed copied writer's range (as FillBuffer tests it) goes behind the + // completions too, below; any other one stores on the GPU. Debug aid: APS5_LABEL_GATE_ALL=1 + // gates on any completion, as before (a completion label there registered its own completion, + // so every later label followed). static const bool gateAll = std::getenv("APS5_LABEL_GATE_ALL") != nullptr; if (gateAll ? recorder.HasCompletions() : state->CopiedWriterOverlaps(address, bytes.size())) { // A copied buffer's write-back is still to run: the store must land after it. It is stored @@ -2886,7 +3091,7 @@ void VulkanDevice::recreateSwapchain(std::uint32_t width, std::uint32_t height, require((surface.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 && (surface.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) != 0 && (surface.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) != 0, "resized surface capabilities are unsupported"); VkSwapchainCreateInfoKHR create{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR}; create.surface = state->surface; - create.minImageCount = surface.minImageCount; + create.minImageCount = AgcDriver::SwapchainImageCount(surface); create.imageFormat = state->surfaceFormat; create.imageColorSpace = state->surfaceColorSpace; create.imageExtent = {width, height}; @@ -3158,6 +3363,15 @@ FrameDumps& Dumps() { } bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) { + if (buffer.dccAddress != 0 && buffer.tilingMode == 0) { + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + const auto before = profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0; + state->recorder->SyncThrough(buffer.dccAddress, DisplayBufferKeyBytes(buffer), true); + if (const auto waited = profile ? Graphics::Recorder::ThreadWaitedMs() - before : 0.0; waited > 0.0) { + ++presentCounters.keyWriterWaits; + presentCounters.keyWriterWaitMs += waited; + } + } static const bool storeAtFlip = Graphics::StorageTexture::StoreAtFlipRequested(std::getenv("APS5_STORE_AT_FLIP")); if (storeAtFlip) Graphics::StorageTexture::FlushAllPending("flip"); else Graphics::StorageTexture::FlushDumpSelected("flip"); @@ -3220,8 +3434,9 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) { static const bool noAdopt = std::getenv("APS5_NO_PRESENT_EPOCH") != nullptr; if (!noAdopt) GuestMemory::AdoptLatestCollectEpoch(); GuestMemory::CollectWritesUncached(buffer.address, bytes); - if (!GuestMemory::UnchangedSince(buffer.address, bytes, resident->Generation()) || (buffer.dccAddress != 0 && ResidentKeysMoved(*resident))) { - resident->Refresh(); + if (!GuestMemory::UnchangedSince(buffer.address, bytes, resident->GenerationFor(buffer.address, bytes)) || (buffer.dccAddress != 0 && ResidentKeysMoved(*resident))) { + if (resident->ImageLayers() > 1) resident->RefreshLayers(layer, 1); + else resident->Refresh(); ++refreshedPresents; } ++residentPresents; @@ -3578,6 +3793,7 @@ double VulkanDevice::RetirePresents(std::size_t keepInFlight) { const auto status = state->DeviceFunction("vkGetFenceStatus"); auto inFlight = static_cast(std::count_if(slots.begin(), slots.end(), [](const auto& slot) { return slot.inFlight; })); double waitedMs = 0; + bool retired = false; // Oldest first from the cursor; the fences of one queue signal in submission order, so the // first unsignaled one ends the scan, and the ones beyond the bound are the oldest. for (std::size_t i = 0; i < slots.size() && inFlight != 0; ++i) { @@ -3596,9 +3812,11 @@ double VulkanDevice::RetirePresents(std::size_t keepInFlight) { } const bool dump = slot.dumpRecorded; state->RetireSlot(slot); + retired = true; if (dump) timing.Mark("dump"); --inFlight; } + if (retired) Graphics::CheckPixelFunctionLds(graphicsContext()); return waitedMs; } @@ -3618,13 +3836,19 @@ void VulkanDevice::QueuePresent() { present.swapchainCount = 1; present.pSwapchains = &state->swapchain; present.pImageIndices = &index; - const auto presented = state->DeviceFunction("vkQueuePresentKHR")(state->queue, &present); + std::unique_lock presentQueueLock(state->presentQueueMutex, std::defer_lock); + if (state->presentQueue != VK_NULL_HANDLE) presentQueueLock.lock(); + const auto presented = state->DeviceFunction("vkQueuePresentKHR")(state->presentQueue != VK_NULL_HANDLE ? state->presentQueue : state->queue, &present); if (presented == VK_ERROR_OUT_OF_DATE_KHR || presented == VK_SUBOPTIMAL_KHR) state->extent = {0, 0}; else check(presented, "vkQueuePresentKHR"); timing.Mark("queue_present"); APS5_LOG_OUT_DEBUG("vkQueuePresentKHR queued imageIndex=%u", index); } +bool VulkanDevice::PresentsOnOwnQueue() const { + return state->presentQueue != VK_NULL_HANDLE; +} + ShaderRecompiler::SpirvTarget VulkanDevice::ComputeTarget(std::uint32_t waveSize) const { auto target = Target(); if (waveSize == 32u && state->computeWave32) target.subgroupSize = 32u; @@ -3671,10 +3895,17 @@ std::uint32_t VulkanDevice::VendorId() const { return state->properties.vendorID; } +bool VulkanDevice::SupportsImageFormat(VkFormat format, VkImageUsageFlags usage) const { + VkImageFormatProperties properties{}; + const auto query = state->InstanceFunction("vkGetPhysicalDeviceImageFormatProperties"); + return query(state->physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage, 0, &properties) == VK_SUCCESS; +} + ShaderRecompiler::SpirvTarget VulkanDevice::Target() const { require(state->shaderProfile != nullptr, "shader device profile is unavailable"); auto target = state->shaderProfile->Target(); target.float32FlushToZero = state->float32FlushToZero; + target.fusedExactArithmetic = state->fusedExactArithmetic; target.float32DenormPreserve = state->float32DenormPreserve; return target; } @@ -3698,6 +3929,9 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const { target.registrationModules = state->graphicsPipelineLibrary; target.robustBufferAccess2 = state->robustBufferAccess2; target.waveLdsMemoryBarriers = !state->subgroupControlBarriers; + target.largePixelFunctionLdsBuffer = state->driverId == VK_DRIVER_ID_NVIDIA_PROPRIETARY; + target.pixelFunctionStorageDwords = state->pixelFunctionStorageDwords; + target.deviceScopeLdsMemoryBarriers = !state->workgroupScopeFences; target.srgbDecodeFormats = state->srgbDecodeFormats & ~state->srgbCopyFormats; target.vertexSubgroups = (state->subgroup.supportedStages & VK_SHADER_STAGE_VERTEX_BIT) != 0; if (state->meshShader) { @@ -3758,7 +3992,7 @@ Graphics::Context VulkanDevice::buildContext() const { state->physical, state->queue, state->pool, - state->deviceProc, + state->contextDeviceProc != nullptr ? state->contextDeviceProc : state->deviceProc, state->InstanceFunction("vkGetPhysicalDeviceFormatProperties"), state->InstanceFunction("vkGetPhysicalDeviceImageFormatProperties"), state->memoryProperties, @@ -3783,8 +4017,11 @@ Graphics::Context VulkanDevice::buildContext() const { }; context.pipelineCacheSynchronization = state->pipelineCache ? &state->pipelineCache->Synchronization() : nullptr; context.individuallyFreeDescriptorSets = state->individuallyFreeDescriptorSets; + context.tessellationPipelineCache = state->tessellationPipelineCache; context.dmaBufImport = state->dmaBufImport; context.sparseBinding = state->sparseBinding; + context.sparseResidencyBuffer = state->sparseResidencyBuffer; + context.sparseResidencyAliased = state->sparseResidencyAliased; context.queueFamily = state->queueFamily; context.sparseFamily = state->sparseFamily; context.sparseQueue = state->sparseQueue; @@ -3800,17 +4037,21 @@ Graphics::Context VulkanDevice::buildContext() const { context.drawIndirectFirstInstance = state->drawIndirectFirstInstance; context.multiDrawIndirect = state->multiDrawIndirect; context.dualSrcBlend = state->dualSrcBlend; + context.logicOp = state->logicOp; context.depthBounds = state->depthBounds; context.depthBiasClamp = state->depthBiasClamp; context.samplerFilterMinmax = state->samplerFilterMinmax; context.nonSeamlessCubeMap = state->nonSeamlessCubeMap; context.mergedCoverageSampleCounts = state->mergedCoverageSampleCounts; + context.truncatedCoverageSampleCounts = state->truncatedCoverageSampleCounts; + context.mixedAttachmentSamples = state->mixedAttachmentSamples; context.conservativeRasterization = state->conservativeRasterization; context.meshWave32 = state->meshWave32; context.provokingVertexLast = state->provokingVertexLast; context.graphicsPipelineLibrary = state->graphicsPipelineLibrary; context.pipelineLinkTimeOptimization = state->pipelineLinkTimeOptimization; context.drawInputsInPlace = state->drawInputsInPlace; + context.discreteGpu = state->properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; context.provokingVertexModePerPipeline = state->provokingVertexModePerPipeline; context.drawIndirectCount = state->drawIndirectCount; context.occlusionQueryPrecise = state->occlusionQueryPrecise; @@ -3829,10 +4070,12 @@ Graphics::Context VulkanDevice::buildContext() const { context.imageTableUpdateAfterBind = ImageTableUpdateAfterBind(context); context.samplerTableUpdateAfterBind = SamplerTableUpdateAfterBind(context); context.geometryShader = state->geometryShader; + context.largePoints = state->largePoints; context.sampleRateShading = state->sampleRateShading; context.fillModeNonSolid = state->fillModeNonSolid; context.maintenance5 = state->maintenance5; context.storageImageMultisample = state->storageImageMultisample; + context.storageImageWriteWithoutFormat = state->storageImageWriteWithoutFormat; context.shaderClipDistance = state->shaderClipDistance; context.shaderCullDistance = state->shaderCullDistance; context.shaderTerminateInvocation = state->shaderTerminateInvocation; @@ -3840,6 +4083,7 @@ Graphics::Context VulkanDevice::buildContext() const { context.bufferInt64Atomics = state->bufferInt64Atomics; context.placeholderImages = state->placeholderImages.get(); context.viewportIndexLayer = state->viewportIndexLayer; + context.imageFormatList = state->imageFormatList; context.demoteToHelperInvocation = state->demoteToHelperInvocation; context.earlyFragmentSampleMaskTestBeforeSampleCounting = state->earlyFragmentSampleMaskTestBeforeSampleCounting; context.primitiveListRestart = state->primitiveListRestart; @@ -3865,10 +4109,6 @@ std::optional VulkanDevice::KnownDrawRejection(const Graphics::Stat return Graphics::KnownValidationFailure(graphicsContext(), shaders, graphics); } -void VulkanDevice::DepthCopyPass(const Graphics::DepthCopyPass& pass) { - Graphics::RunDepthCopyPass(graphicsContext(), pass); -} - void VulkanDevice::ColorMetadataPass(const Graphics::ColorMetadataPass& pass) { Graphics::RunColorMetadataPass(graphicsContext(), pass); } @@ -3911,7 +4151,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) { +void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, std::shared_ptr* recipe, const DrawTraceContext* trace) { startJournalReplay(); for (const auto& shader : shaders) { require(shader.program != nullptr, "missing compiled shader"); @@ -3919,11 +4159,11 @@ void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParamete } // Two stdout lines per draw cost ~1.3 ms per frame of the queue-0 worker (part of it under the // GPU mutex); APS5_TRACE_DRAWS=1 restores them. - static const bool trace = std::getenv("APS5_TRACE_DRAWS") != nullptr; - if (trace) 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)); + 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); - if (trace) APS5_LOG_CHARS_OUT("VulkanDevice::Draw complete"); + Graphics::Draw(context, graphics, draw, shaders, snapshots, recipe, trace); + if (traceDraws) APS5_LOG_CHARS_OUT("VulkanDevice::Draw complete"); } bool VulkanDevice::DrawInputsInPlace() const { @@ -4088,7 +4328,7 @@ void WatchMemory(std::uint64_t programAddress) { return parsed; }(); if (ranges.empty()) return; - static std::mutex mutex; + static AgcDriver::Mutex mutex; static std::deque>> history; static bool written = false; std::lock_guard lock(mutex); @@ -4155,7 +4395,7 @@ struct DispatchCounters { std::array phaseTotals{}; std::uint64_t profiledDispatches = 0; IndirectHold indirectHold; - std::uint64_t cacheHits = 0, cacheMisses = 0, cacheInvalidated = 0, templateRefreshed = 0, templateSameWords = 0; + std::uint64_t cacheHits = 0, cacheMisses = 0, cacheInvalidated = 0, templateRefreshed = 0, templateSameWords = 0, templateByNumber = 0; double templateRevalidateMs = 0; std::uint64_t procLookups = 0, preBarriersRecorded = 0, preBarriersSkipped = 0; std::chrono::steady_clock::time_point cacheReport = std::chrono::steady_clock::now(); @@ -4401,6 +4641,7 @@ struct RecordedDispatch { // A template hit's revalidate time, charged to the [rescache] refresh accounting. double revalidateMs; DispatchTimer* timer; + std::optional> htileClear; // Whether the data refresh recorded anything. bool refreshed = false; bool resourceKeyMatched = false; @@ -4411,6 +4652,24 @@ struct RecordedDispatch { std::array threadLimit{}; }; +struct DispatchCompletion { + const Graphics::Context& context; + const std::array& shaders; + VkShaderStageFlags pushStages; + const std::array& pushBytes; + const std::shared_ptr& objects; + const std::shared_ptr& resources; + const ResourceCache::Key& contentKey; + bool cacheable; + bool fromCache; + double revalidateMs; + const std::shared_ptr& overlay; + std::uint64_t lookupsBefore; + DispatchTimer& timer; + PerformanceTimer& timing; + char* groupsText; +}; + VkDevice VulkanDevice::Device() const { return state->device; } @@ -4453,7 +4712,7 @@ void VulkanDevice::NoteDrawRecipeMiss(DrawRecipePrecheck miss) { counters.precheckMisses[miss == DrawRecipePrecheck::NoRecipe ? MissNoRecipe : MissDevice].fetch_add(1, std::memory_order_relaxed); } -RecipeOutcome VulkanDevice::DrawFromRecipe(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, const std::shared_ptr& recipe) { +RecipeOutcome VulkanDevice::DrawFromRecipe(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, const std::shared_ptr& recipe, const DrawTraceContext* trace) { PerformanceTimer timing("Vulkan.DrawFromRecipe"); static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; auto& counters = Recipes().kinds[static_cast(RecipeKind::Draw)]; @@ -4464,7 +4723,7 @@ RecipeOutcome VulkanDevice::DrawFromRecipe(const Graphics::State& graphics, cons return RecipeOutcome::Rebuild; } const auto context = graphicsContext(); - const auto outcome = Graphics::DrawWithRecipe(context, graphics, draw, shaders, snapshots, *recipe); + const auto outcome = Graphics::DrawWithRecipe(context, graphics, draw, shaders, snapshots, *recipe, trace); if (!outcome.recorded) { switch (outcome.miss) { case Graphics::DrawRecipeMiss::NotRecordable: counters.precheckMisses[MissNotRecordable].fetch_add(1, std::memory_order_relaxed); break; @@ -4661,8 +4920,8 @@ std::shared_ptr VulkanDevice::PrepareRecipe(const std::shared_ptr snapshots, std::uint64_t programAddress, std::shared_ptr prepared, std::shared_ptr* recipe) { - static_cast(dispatch(shader, x, y, z, 0, snapshots, programAddress, std::move(prepared), recipe)); +void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span snapshots, std::uint64_t programAddress, std::shared_ptr prepared, std::shared_ptr* recipe, std::optional> htileClear) { + static_cast(dispatch(shader, x, y, z, 0, snapshots, programAddress, std::move(prepared), recipe, {}, htileClear)); } VulkanDevice::IndirectOutcome VulkanDevice::DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span snapshots, std::uint64_t programAddress, std::shared_ptr prepared, std::shared_ptr* recipe, const std::array& threadGroup) { @@ -4745,6 +5004,14 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { auto& resources = *record.resources; const auto arguments = record.arguments; const auto* argumentImport = record.argumentImport; + const auto* limit = state->properties.limits.maxComputeWorkGroupCount; + if (argumentImport == nullptr && record.shader->program->workgroupMemoryDwords != 0 && (record.x > limit[0] || record.y > limit[1] || record.z > limit[2])) { + char counted[128] = ""; + if (record.cpuArguments != 0) std::snprintf(counted, sizeof(counted), " (read on the CPU from the indirect arguments at 0x%llx, cpuReason %d)", static_cast(record.cpuArguments), record.cpuReason); + char text[384]; + std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]); + throw std::runtime_error(text); + } // The record phase split (APS5_PROFILE_DRAW, rows "record: ..." of the [dispatch] totals): // opening the batch, the keeps, the data refresh, the barriers with the bind and the dispatch // itself, the pending-write notes and marks (MarkGpuWrites), and the completion registration. @@ -4771,6 +5038,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { }; if (recorder.HasQueuedKeyStores() && (resources.HoldsLease() || recorder.AnyQueuedKeyStore(touches))) recorder.FlushKeyStores(); if (recorder.HasQueuedStores() && (resources.HoldsLease() || recorder.AnyQueuedStore(touches))) recorder.FlushStores(); + static_cast(Graphics::StorageTexture::StoreDrawnLinearTouching("dispatch access", touches, resources.HoldsLease())); VkAccessFlags covered = 0; const auto commands = recorder.Commands(&covered); recordStep(PhaseRecordCommands); @@ -4789,6 +5057,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { // after an earlier dispatch's reads of the buffers by that dispatch's trailing barrier. A // recipe compares the two 64-bit hashes first: equal hashes mean the buffers hold the words. const bool differs = record.dataRefresh == RecordedDispatch::DataRefresh::Words || resources.DataWordsHash() != record.dataWordsHash; + if (timer.profile && record.shader->program != nullptr && record.shader->program->bindings.size() != resources.BindingCount()) ++d.templateByNumber; if (differs && (record.resourceKeyMatched ? resources.refreshData(commands, *record.shader, &recorder) : resources.RefreshData(commands, *record.shader, &recorder))) { covered = 0; ++d.templateRefreshed; @@ -4876,20 +5145,16 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { } const auto gpuTiming = recorder.BeginGpuTiming(record.programAddress != 0 ? record.programAddress : record.shader->program->variantId); if (state->checkpointMarker != nullptr) state->checkpointMarker(commands, reinterpret_cast(static_cast(record.programAddress))); + const auto marker = state->bufferMarker != nullptr ? state->markerLog.Next(record.programAddress) : 0u; + if (marker != 0) state->bufferMarker(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, state->markerBuffer, 0, marker); if (argumentImport != nullptr) { context.Resolved(&Graphics::DeviceFunctions::cmdDispatchIndirect, "vkCmdDispatchIndirect")(commands, indirectBuffer, indirectOffset); } else { - const auto* limit = state->properties.limits.maxComputeWorkGroupCount; if (record.x <= limit[0] && record.y <= limit[1] && record.z <= limit[2]) { context.Resolved(&Graphics::DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, record.x, record.y, record.z); } else { char counted[128] = ""; if (record.cpuArguments != 0) std::snprintf(counted, sizeof(counted), " (read on the CPU from the indirect arguments at 0x%llx, cpuReason %d)", static_cast(record.cpuArguments), record.cpuReason); - if (record.shader->program->workgroupMemoryDwords != 0) { - char text[384]; - std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]); - throw std::runtime_error(text); - } const auto offsets = std::ranges::count_if(record.shader->program->specialization, [](const auto& constant) { return ShaderRecompiler::PipelineSpecialization::IsWorkgroupOffset(constant.id); }); if (offsets != ShaderRecompiler::PipelineSpecialization::WorkgroupOffsetWords) { char text[320]; @@ -4914,6 +5179,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { } } recorder.EndGpuTiming(gpuTiming); + if (marker != 0) state->bufferMarker(commands, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, state->markerBuffer, sizeof(std::uint32_t), marker); const auto trailingTiming = recorder.BeginGpuTiming(CommandClass::DispatchTrailing); constexpr VkAccessFlags dispatchedAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT; Graphics::RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, dispatchedAccess); @@ -4921,21 +5187,31 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { recorder.EndGpuTiming(trailingTiming); recorder.MarkCovered(dispatchedAccess); recordStep(PhaseRecordBind); + if (record.htileClear.has_value()) Graphics::ArmHtileDepthClear(record.htileClear->first, record.htileClear->second); resources.MarkGpuWrites(recorder); + if (record.htileClear.has_value()) { + Graphics::QueueHtileDepthClear(record.htileClear->first, record.htileClear->second); + recorder.NotePendingWrite(record.htileClear->first, record.htileClear->second); + } recordStep(PhaseRecordMarks); // Only copied written buffers (and BDA fault checks) need work once the GPU is done; without them // the batch can signal its labels from the GPU. - if (resources.NeedsCompletion() && resources.Reusable()) { + const bool completionNeeded = resources.NeedsCompletion() || record.htileClear.has_value(); + if (completionNeeded && resources.Reusable()) { auto kept = record.resources; - recorder.OnComplete([kept] { kept->CheckFaults(); }); - } else if (resources.NeedsCompletion()) { + recorder.OnComplete([kept, htileClear = record.htileClear] { + kept->CheckFaults(); + if (htileClear.has_value()) Graphics::CompleteHtileDepthClear(htileClear->first, htileClear->second); + }); + } else if (completionNeeded) { // Listed for DispatchIndirect until the write-back ran (a non-reusable object, so once). auto writers = state->copiedWriters; auto kept = record.resources; - recorder.OnComplete([kept, writers] { + recorder.OnComplete([kept, writers, htileClear = record.htileClear] { // Delisted before the write-back: one that fails must not keep indirect dispatches on the CPU. writers->erase(std::remove(writers->begin(), writers->end(), kept), writers->end()); kept->WriteBackBuffers(); + if (htileClear.has_value()) Graphics::CompleteHtileDepthClear(htileClear->first, htileClear->second); }); // Listed after the registration: a throw there leaves nothing that would pin the CPU path forever. if (resources.HasCopiedWrites()) writers->push_back(kept); @@ -4969,7 +5245,7 @@ std::shared_ptr VulkanDevice::createComputePipeline(cons const Graphics::PipelineSpecialization specialization(shader); const auto subgroupSize = computeSubgroupSize(shader); const auto stageFlags = computeStageFlags(shader); - if (const char* directory = std::getenv("APS5_DUMP_COMPUTE_PIPELINES"); directory != nullptr && *directory != '\0' && ComputePipelineCapture::OptionalEnabled() && CaptureComputePipeline(shader.spirv.size())) { + if (const auto* directory = ComputePipelineDumpDirectory(); directory != nullptr && *directory != 0 && !state->probingFloatControls && ComputePipelineCapture::OptionalEnabled() && CaptureComputePipeline(shader.spirv.size())) { ComputePipelineCapture capture; capture.spirv = shader.spirv.Copy(); ComputeDescriptorSetLayout resourcesLayout; @@ -5066,7 +5342,7 @@ std::shared_ptr VulkanDevice::computePipeline(const Grap return finish(std::move(objects), nullptr); } -VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span snapshots, std::uint64_t programAddress, std::shared_ptr prepared, std::shared_ptr* recipeOut, const std::array& threadGroup) { +VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span snapshots, std::uint64_t programAddress, std::shared_ptr prepared, std::shared_ptr* recipeOut, const std::array& threadGroup, std::optional> htileClear) { startJournalReplay(); for (const auto& binding : shader.bindings) { if (binding.role != ShaderRecompiler::DescriptorRole::GuestBuffers) continue; @@ -5295,7 +5571,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec } if (profile && std::chrono::steady_clock::now() - d.cacheReport > std::chrono::seconds(10)) { d.cacheReport = std::chrono::steady_clock::now(); - AgcDriver::ProfilePrint_nid_no_patch("[rescache] (cumulative): %llu hits, %llu misses, %llu invalidated, %zu entries (dispatch + draw); template hits: %llu refreshed the data buffers (revalidate %.1f ms), %llu had the same words; Find calls %llu, Touch calls %llu\n", static_cast(d.cacheHits), static_cast(d.cacheMisses), static_cast(d.cacheInvalidated), state->resourceCache.Size(), static_cast(d.templateRefreshed), d.templateRevalidateMs, static_cast(d.templateSameWords), static_cast(ResourceCache::Finds()), static_cast(ResourceCache::Touches())); + AgcDriver::ProfilePrint_nid_no_patch("[rescache] (cumulative): %llu hits, %llu misses, %llu invalidated, %zu entries (dispatch + draw); template hits: %llu refreshed the data buffers (revalidate %.1f ms), %llu had the same words, %llu paired their data bindings by number (no image table binding of their own); Find calls %llu, Touch calls %llu\n", static_cast(d.cacheHits), static_cast(d.cacheMisses), static_cast(d.cacheInvalidated), state->resourceCache.Size(), static_cast(d.templateRefreshed), d.templateRevalidateMs, static_cast(d.templateSameWords), static_cast(d.templateByNumber), static_cast(ResourceCache::Finds()), static_cast(ResourceCache::Touches())); AgcDriver::ProfilePrint_nid_no_patch("[vk] deviceProc lookups inside the device call (cumulative): %llu (%.2f per dispatch); pre-dispatch barriers recorded %llu, skipped %llu; compute pipelines created %llu (%llu under the GPU mutex)\n", static_cast(d.procLookups), d.profiledDispatches != 0 ? static_cast(d.procLookups) / static_cast(d.profiledDispatches) : 0.0, static_cast(d.preBarriersRecorded), static_cast(d.preBarriersSkipped), static_cast(computePipelinesCreated.load(std::memory_order_relaxed)), static_cast(computePipelinesCreatedLocked.load(std::memory_order_relaxed))); reportRecipes(); reportDispatchMove(); @@ -5314,11 +5590,32 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec timing.Mark("pipeline_create"); timer.phase(PhasePipeline); } + DispatchCompletion completion{context, shaders, pushStages, pushBytes, objects, resources, contentKey, cacheable, fromCache, revalidateMs, overlay, lookupsBefore, timer, timing, groupsText}; + return recordCompletedDispatch(shader, x, y, z, arguments, programAddress, recipeOut, threadGroup, htileClear, completion); +} + +VulkanDevice::IndirectOutcome VulkanDevice::recordCompletedDispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, std::shared_ptr* recipeOut, const std::array& threadGroup, std::optional> htileClear, DispatchCompletion& completion) { + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + const auto& context = completion.context; + const auto& shaders = completion.shaders; + const auto pushStages = completion.pushStages; + const auto& pushBytes = completion.pushBytes; + const auto& objects = completion.objects; + const auto& resources = completion.resources; + const auto& contentKey = completion.contentKey; + const bool cacheable = completion.cacheable; + const bool fromCache = completion.fromCache; + const double revalidateMs = completion.revalidateMs; + const auto& overlay = completion.overlay; + const auto lookupsBefore = completion.lookupsBefore; + auto& timer = completion.timer; + auto& timing = completion.timing; + auto* groupsText = completion.groupsText; // Recorded into the device's open batch: the CPU moves on to the next command while the GPU // works; the batch is waited for where the guest expects results (labels, flips, CPU reads). auto& recorder = *state->recorder; IndirectOutcome outcome{0, 0}; - RecordedDispatch record{&context, &shaders[0], resources, objects, pushStages, &pushBytes, x, y, z, arguments, nullptr, programAddress, fromCache && TemplateDataRefresh() ? RecordedDispatch::DataRefresh::Words : RecordedDispatch::DataRefresh::None, 0, revalidateMs, &timer}; + RecordedDispatch record{&context, &shaders[0], resources, objects, pushStages, &pushBytes, x, y, z, arguments, nullptr, programAddress, fromCache && TemplateDataRefresh() ? RecordedDispatch::DataRefresh::Words : RecordedDispatch::DataRefresh::None, 0, revalidateMs, &timer, htileClear}; record.resourceKeyMatched = fromCache; record.bindings = overlay; record.threadGroup = threadGroup; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp index 7bbca06017..3bdac581be 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -43,7 +44,7 @@ struct Sampler { std::uint64_t stackHigh = 0; std::vector modules; std::uint64_t pendingModule = 0; - std::mutex mutex; + AgcDriver::Mutex mutex; std::map> counts; CollapsedStacks stacks; std::uint64_t samples = 0; @@ -162,18 +163,29 @@ struct Sampler { return frame; } + static void writeModules(std::FILE* file, const std::map& loaded) { + for (const auto& [base, name] : loaded) { + const auto* dos = reinterpret_cast(base); + const auto* nt = reinterpret_cast(base + dos->e_lfanew); + std::fprintf(file, "# module %s 0x%lx 0x%lx\n", name.c_str(), static_cast(nt->OptionalHeader.SizeOfImage), static_cast(nt->FileHeader.TimeDateStamp)); + } + } + void write() { std::lock_guard lock(mutex); std::FILE* file = std::fopen(path.c_str(), "w"); if (file == nullptr) return; std::fprintf(file, "# %llu samples\n", static_cast(samples)); + std::map loaded; for (const auto& [address, count] : counts) { HMODULE module = nullptr; char name[MAX_PATH] = "?"; if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(address), &module)) GetModuleFileNameA(module, name, sizeof(name)); const char* base = std::strrchr(name, '\\'); + if (module != nullptr) loaded.emplace(reinterpret_cast(module), base ? base + 1 : name); std::fprintf(file, "%s 0x%llx %llu %llu\n", base ? base + 1 : name, static_cast(address - reinterpret_cast(module)), static_cast(count.first), static_cast(count.second)); } + writeModules(file, loaded); std::fclose(file); if (keepStacks) writeStacks(); } @@ -194,11 +206,7 @@ struct Sampler { const double tickUs = 1e6 / static_cast(frequency.QuadPart); std::fprintf(file, "# samples=%llu depth=%d truncated=%llu suspended_us_mean=%.1f suspended_us_max=%.1f\n", static_cast(samples), depth, static_cast(stacks.Truncated()), samples != 0 ? static_cast(suspendedTicks) * tickUs / static_cast(samples) : 0.0, static_cast(longestSuspendedTicks) * tickUs); - for (const auto& [base, name] : loaded) { - const auto* dos = reinterpret_cast(base); - const auto* nt = reinterpret_cast(base + dos->e_lfanew); - std::fprintf(file, "# module %s 0x%lx 0x%lx\n", name.c_str(), static_cast(nt->OptionalHeader.SizeOfImage), static_cast(nt->FileHeader.TimeDateStamp)); - } + writeModules(file, loaded); const auto text = stacks.Format([&](std::uint64_t address) { return names.at(address); }); const bool written = std::fwrite(text.data(), 1, text.size(), file) == text.size(); if (std::fclose(file) != 0 || !written) return; @@ -245,6 +253,7 @@ struct ProcessSampler { std::FILE* file = std::fopen(path.c_str(), "w"); if (file == nullptr) return; std::fprintf(file, "# %llu rounds\n", static_cast(rounds)); + std::map loaded; for (auto& [id, thread] : threads) { std::lock_guard lock(thread.mutex); std::vector>> hot(thread.counts.begin(), thread.counts.end()); @@ -259,9 +268,11 @@ struct ProcessSampler { char name[MAX_PATH] = "?"; if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(address), &module)) GetModuleFileNameA(module, name, sizeof(name)); const char* base = std::strrchr(name, '\\'); + if (module != nullptr) loaded.emplace(reinterpret_cast(module), base ? base + 1 : name); std::fprintf(file, " %s 0x%llx %llu %llu\n", base ? base + 1 : name, static_cast(address - reinterpret_cast(module)), static_cast(hot[i].second.first), static_cast(hot[i].second.second)); } } + Sampler::writeModules(file, loaded); std::fclose(file); } }; diff --git a/core/libs/prx/libSceAgcDriver/Export.cpp b/core/libs/prx/libSceAgcDriver/Export.cpp index 18cd1da5d8..fd98ebbdb5 100644 --- a/core/libs/prx/libSceAgcDriver/Export.cpp +++ b/core/libs/prx/libSceAgcDriver/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/include/AgcDriver.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp index 657c5351c4..01a9c04191 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" #include +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -65,13 +66,14 @@ public: // 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 VkBufferUsageFlags Usage(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties); - VkDeviceMemory AllocateMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional hostType = std::nullopt, std::uint32_t* allocatedType = nullptr); + 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); - VkDeviceMemory TakeImageMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional hostType = std::nullopt, std::uint32_t* allocatedType = nullptr); + VkDeviceMemory TakeImageMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional hostType = std::nullopt, std::uint32_t* allocatedType = nullptr, bool newImage = false); void PutImageMemory(VkDeviceMemory memory, VkDeviceSize bytes, std::uint32_t memoryType) noexcept; std::size_t RetainedImages(); std::optional Take(std::size_t bytes, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); void Put(const BufferAllocation& allocation) noexcept; + VkDeviceSize ReclaimIdleDevice(VkDeviceSize bytes, std::chrono::steady_clock::time_point now); static bool SlabEligible(std::size_t capacity, VkDeviceSize alignment, VkDeviceSize size, VkDeviceSize atom); static VkDeviceSize SlabBlockBytes(std::size_t capacity); SlabSlot TakeSlot(const Context& context, std::uint32_t memoryType, std::size_t capacity, bool addressable); @@ -142,7 +144,7 @@ private: // The retained-slot bound of each tier (APS5_BUFFER_POOL_SLOTS, default `defaultSlots`), read once. static std::size_t MaxSlots(); std::size_t maxSlots; - VkDeviceSize (*cacheReclaim)(VkDeviceSize bytes); + VkDeviceSize (*cacheReclaim)(VkDeviceSize bytes, bool newImage); VkDevice device; PFN_vkUnmapMemory unmap; PFN_vkDestroyBuffer destroyBuffer; @@ -152,7 +154,7 @@ private: VkPhysicalDevice physical = VK_NULL_HANDLE; VkPhysicalDeviceMemoryProperties memoryProperties{}; PFN_vkGetPhysicalDeviceMemoryProperties2 memoryProperties2 = nullptr; - std::mutex mutex; + AgcDriver::Mutex mutex; // Not `small`/`large`: (via ) defines `small` as a macro. Tier smallTier; Tier largeTier; @@ -167,7 +169,11 @@ private: std::uint64_t imageEvictions = 0; std::list imageOrder; std::uint64_t clock = 0; - std::mutex slabMutex; + std::optional idleRead; + std::uint64_t idleClock = 0; + std::uint64_t reclaimBefore = 0; + std::uint64_t scannedIdleClock = 0; + AgcDriver::Mutex slabMutex; std::unordered_map slabs; std::unordered_map> slabBlocks; static constexpr VkDeviceSize budget = 512ull * 1024 * 1024; @@ -181,7 +187,7 @@ private: }; std::shared_ptr GetBufferPool(const Context& context); -VkDeviceMemory AllocateImageMemory(const Context& context, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation, std::uint32_t* allocatedType = nullptr); +VkDeviceMemory AllocateImageMemory(const Context& context, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation, std::uint32_t* allocatedType = nullptr, bool newImage = false); void ReleaseImageMemory(const Context& context, VkDeviceMemory memory, VkDeviceSize bytes, std::uint32_t memoryType) noexcept; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index afaa005b1d..dc2e00475a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -180,8 +180,11 @@ struct Context { // Nonzero when VK_EXT_external_memory_host is enabled: the required host pointer alignment. VkDeviceSize hostImportAlignment = 0; bool drawInputsInPlace = true; + bool discreteGpu = false; bool dmaBufImport = false; bool sparseBinding = false; + bool sparseResidencyBuffer = false; + bool sparseResidencyAliased = false; std::uint32_t queueFamily = 0; std::uint32_t sparseFamily = VK_QUEUE_FAMILY_IGNORED; VkQueue sparseQueue = VK_NULL_HANDLE; @@ -191,7 +194,7 @@ struct Context { std::uint32_t settleFamily = 0; std::mutex* settleQueueMutex = nullptr; PFN_vkGetPhysicalDeviceMemoryProperties2 memoryProperties2 = nullptr; - VkDeviceSize (*cacheReclaim)(VkDeviceSize bytes) = nullptr; + VkDeviceSize (*cacheReclaim)(VkDeviceSize bytes, bool newImage) = nullptr; RenderCache* renderCache = nullptr; DrawQueue* drawQueue = nullptr; GraphicsPipelineCache* graphicsPipelines = nullptr; @@ -210,9 +213,12 @@ struct Context { bool depthBounds = false; bool depthBiasClamp = false; bool dualSrcBlend = false; + bool logicOp = false; bool samplerFilterMinmax = false; bool nonSeamlessCubeMap = false; VkSampleCountFlags mergedCoverageSampleCounts = 0; + VkSampleCountFlags truncatedCoverageSampleCounts = 0; + bool mixedAttachmentSamples = false; bool conservativeRasterization = false; bool meshWave32 = false; VkBuffer emptyBuffer = VK_NULL_HANDLE; @@ -234,13 +240,17 @@ struct Context { std::uint32_t imageTableCapacity = 0; std::uint32_t samplerTableCapacity = 0; bool individuallyFreeDescriptorSets = false; + bool tessellationPipelineCache = true; bool imageTableUpdateAfterBind = false; bool samplerTableUpdateAfterBind = false; bool geometryShader = false; + bool largePoints = false; bool sampleRateShading = false; bool fillModeNonSolid = false; bool maintenance5 = false; bool storageImageMultisample = false; + bool storageImageWriteWithoutFormat = false; + bool imageFormatList = false; bool shaderClipDistance = false; bool shaderCullDistance = false; bool nullDescriptors = false; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp index 7f05d121a2..b0c9a79d39 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp @@ -81,6 +81,16 @@ struct DccKeyProof { // APS5_NO_KEY_FAST_PATH=1 scans on every call and stores nothing. DccKeys ProvedClearKeys(const GuestTextureResource& resource, std::uint64_t guestBytes, DccKeyProof& proof); bool KeyFastPath(); +bool SingleKeysReadable(const GuestTextureResource& resource); +bool ReadSingleElement(const GuestTextureResource& resource, std::span element); +struct DccSingleProof { + std::uint64_t address = 0; + std::uint64_t bytes = 0; + std::uint64_t generation = 0; + std::uint32_t format = 0; + bool uniform = false; +}; +bool ProvedSingleUniform(const GuestTextureResource& resource, std::uint64_t guestBytes, std::uint64_t collected, DccSingleProof& proof); // Cumulative outcomes of ProvedClearKeys: calls answered by a proof, scans made, and scans not kept. struct DccKeyProofCounts { std::uint64_t proved; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp new file mode 100644 index 0000000000..70fb764122 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp @@ -0,0 +1,17 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DEFERREDDESTRUCTION_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DEFERREDDESTRUCTION_HPP + +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include +#include + +namespace AgcDriver::Graphics { + +PFN_vkGetDeviceProcAddr DeferDestructionToIdle(VkDevice device, PFN_vkGetDeviceProcAddr resolver); +void DestroyDeferredAtIdle(const Context& context, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); +void DestroyDeferredObjects(VkDevice device) noexcept; +std::size_t DeferredObjects(VkDevice device); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp index ee9304bcad..ef9852b1e0 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp @@ -20,8 +20,16 @@ class StorageTexture; VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten = true); SampleOrderTarget DepthSurfaceOrderTarget(const Context& context, const DepthTarget& target); DepthSampleLocations DepthSurfaceSampleLocations(const Context& context, const DepthTarget& target); -std::vector> FlushDepthPlanes(std::uint64_t address, std::size_t bytes); +enum class DepthPlaneScope { Overlapped, Whole }; +std::vector> FlushDepthPlanes(std::uint64_t address, std::size_t bytes, DepthPlaneScope scope = DepthPlaneScope::Overlapped); bool DepthPlanesStale(); +struct DepthPlaneStoreStatistics { + std::uint64_t stores = 0; + std::uint64_t units = 0; + std::uint64_t bytes = 0; +}; +DepthPlaneStoreStatistics DepthPlaneStoreCounts(); +void NoteDepthSurfaceWrite(const Context& context, const DepthTarget& target); bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std::uint32_t samples); std::uint64_t DepthSurfacesSerial(); std::uint64_t DepthSurfacesReleased(); @@ -47,7 +55,10 @@ bool DepthSurfaceOverlaps(VkDevice device, std::uint64_t address, std::uint64_t bool DepthSurfaceAt(const Context& context, std::span words, const GuestTextureResource& resource); std::uint64_t HtileDepthClearAddress(std::span code, std::span userData, const std::array& numThreads); std::size_t HtileDepthClearBytes_nid_no_patch(std::span code, std::span userData, const ShaderRecompiler::ShaderComputeStageInfo& compute, std::span packet, std::uint64_t indirectArguments); -void NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::size_t nativeBytes = 0u); +bool NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::size_t nativeBytes = 0u); +void ArmHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes); +void QueueHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes); +void CompleteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes); struct MemoryValueFill { std::uint64_t address; std::size_t bytes; @@ -61,11 +72,22 @@ VkImageAspectFlags HtileFillClears(std::uint32_t pattern, bool stencilInHtile); 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(); 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); bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage); -void CopyDepthSample(const Context& context, std::uint64_t depthAddress, std::uint32_t samples, std::uint32_t sample, StorageTexture& destination, VkImageView destinationView, VkExtent2D extent); +std::array DepthCopySourceWords(const DepthTarget& target); +class DepthCopySourceScope { +public: + explicit DepthCopySourceScope(bool active); + ~DepthCopySourceScope(); + DepthCopySourceScope(const DepthCopySourceScope&) = delete; + DepthCopySourceScope& operator=(const DepthCopySourceScope&) = delete; + +private: + bool previous; +}; struct StorageImageClear { std::array image{}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index b79528bf72..ea02167400 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -14,10 +14,29 @@ namespace AgcDriver::Graphics { struct HostImport; +struct DrawTraceContext { + std::uint64_t token = 0; + std::uint64_t submission = 0; + std::uint32_t queue = 0; + std::size_t offset = 0; + std::uint64_t target = 0; +}; + // `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); +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); + +struct PixelFunctionLdsLayouts { + VkDescriptorSetLayout empty = VK_NULL_HANDLE; + VkDescriptorSetLayout buffer = VK_NULL_HANDLE; +}; + +PixelFunctionLdsLayouts PixelFunctionLdsSetLayouts(const Context& context); +void PreparePixelFunctionLds(const Context& context, VkCommandBuffer commands, std::uint32_t subgroupSize); +void BindPixelFunctionLds(const Context& context, VkCommandBuffer commands, VkPipelineLayout layout); +void CheckPixelFunctionLds(const Context& context); +void ReleasePixelFunctionLds(VkDevice device); std::optional KnownValidationFailure(const Context& context, std::span shaders, const State& state); std::uint64_t DrawRenderPassKey(const Context& context, const State& state, std::span targetViews); @@ -81,7 +100,7 @@ struct DrawRecipeOutcome { // the template's proof (ProveCurrent) with the alias checks repeated, the pipeline and framebuffer // from the recipe, then the record as Draw's (pass continued or begun, Kept, MarkGpuWrites). A // miss records nothing. -DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, const DrawRecipe& recipe); +DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, const DrawRecipe& recipe, const DrawTraceContext* trace = nullptr); // Whether recorded draws build recipes and draw-cache hits use them (APS5_NO_DRAW_RECIPE unset). bool DrawRecipes(); @@ -91,9 +110,11 @@ bool DrawRecipes(); // draw index, the vertex ranges, the device features, APS5_NO_GPU_INDIRECT_DRAW) or under the device // lock in Draw (the memory state of the records). `readMs` is the CPU read's time (its sync); // `rewritten` counts a GPU-side draw whose records were copied and patched with a constant. -enum class IndirectDrawPath : std::uint8_t { Gpu, NotFolded, FetchUnknown, NonVertexPath, IndxOffset, DrawIndex, VertexRange, FeatureGap, PendingImage, PendingLabelOrCopy, NotImported, Disabled, IndexRange, Count }; +enum class IndirectDrawPath : std::uint8_t { Gpu, NotFolded, FetchUnknown, NonVertexPath, IndxOffset, DrawIndex, VertexRange, FeatureGap, PendingImage, PendingLabelOrCopy, NotImported, Disabled, IndexRange, IndexLimitUnknown, Count }; void CountIndirectDraw(IndirectDrawPath path, double readMs, bool rewritten = false); std::array(IndirectDrawPath::Count)> IndirectDrawCounts(); +void CountEmptyIndirectDraw(double readMs); +std::uint64_t EmptyIndirectDrawCount(); struct DrawPassCounts { std::uint64_t draws = 0; @@ -127,7 +148,9 @@ void CountDrawSkip(DrawSkip kind, double us); std::shared_ptr>> DrawCopiedWriters(); void RunColorMetadataPass(const Context& context, const ColorMetadataPass& pass); -void RunDepthCopyPass(const Context& context, const DepthCopyPass& pass); +std::vector> PipelinedColorMetadataPassWrites(const ColorMetadataPass& pass); +bool DrawPipelineEnabled(); +bool CommitTrims(); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/FirstSampleCopy.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/FirstSampleCopy.hpp new file mode 100644 index 0000000000..4f973ff75e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/FirstSampleCopy.hpp @@ -0,0 +1,40 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_FIRSTSAMPLECOPY_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_FIRSTSAMPLECOPY_HPP + +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" + +namespace AgcDriver::Graphics { + +class Recorder; + +class FirstSampleCopy { +public: + FirstSampleCopy(const Context& context, VkFormat format, VkExtent2D extent); + ~FirstSampleCopy(); + FirstSampleCopy(const FirstSampleCopy&) = delete; + FirstSampleCopy& operator=(const FirstSampleCopy&) = delete; + + void Record(Recorder& recorder, VkImageView source, VkImageLayout layout); + VkImage Image() const { return image; } + +private: + void release() noexcept; + + Context context; + VkExtent2D extent; + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + VkSampler sampler = VK_NULL_HANDLE; + VkDescriptorSetLayout descriptorLayout = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkRenderPass renderPass = VK_NULL_HANDLE; + VkFramebuffer framebuffer = VK_NULL_HANDLE; + VkShaderModule vertex = VK_NULL_HANDLE; + VkShaderModule fragment = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index ceec04cf3e..bc6afff426 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -38,6 +38,7 @@ struct HostImport { // Identity for the life of this import (see HostImportSerial); 0 until first asked for. std::uint64_t serial = 0; bool unwatched = false; + bool settleOnDemand = false; bool dmaBuf = false; bool joined = false; std::shared_ptr chunk; @@ -102,6 +103,7 @@ void ReportHostImports(); // address)` in the import's buffer. const HostImport* HostImportFor(const Context& context, std::uint64_t address, std::size_t bytes, ImportRequester requester = ImportRequester::Access); const HostImport* ExistingHostImportFor(const Context& context, std::uint64_t address, std::size_t bytes); +bool WatchedAfterSettle(const Context& context, std::uint64_t address, std::size_t bytes); void SetHostImportWindow(std::uint64_t bytes); // Tests: the window of one large registered range (64 MiB) and the import budget (0: the configured one). void SetHostImportRangeWindow(std::uint64_t bytes); @@ -113,7 +115,9 @@ void SetHostImportDeferredSettles(bool enabled); // The larger of 6 GiB and half the physical memory, at most the largest host heap less the larger of 2 GiB and half // that heap: the other half stays for the buffers, mirrors and textures that share it. std::uint64_t HostImportBudget(const VkPhysicalDeviceMemoryProperties& memory, std::uint64_t physicalBytes); +void SetHostImportMaxBytes(std::uint64_t bytes); std::uint64_t HostImportLiveBytes(); +std::uint64_t HostImportBudgetRemaining(const Context& context); struct HostImportSettleCounts { std::uint64_t submitted = 0; std::uint64_t waited = 0; @@ -247,6 +251,11 @@ struct MirrorStats { }; MirrorStats MirrorCounters(); std::uint64_t SnapshotMismatches(); +struct HeapMirrorHold { + std::uint64_t mirrors = 0; + std::uint64_t bytes = 0; +}; +HeapMirrorHold HeapMirrorsWithin(std::uint64_t begin, std::uint64_t end); void ClearSnapshotCopies(VkDevice device); std::vector AddressTableKey(std::span> writes, std::span own); void ClearImageMirrors(VkDevice device); @@ -257,6 +266,8 @@ struct AddressCopy { std::uint64_t end; std::uint64_t committed; const char* reason; + bool heap = false; + bool overBudget = false; }; std::string AddressCopyOverflow(std::vector copies, std::uint64_t limit); @@ -284,6 +295,7 @@ public: // calls RecordCopyBacks (a dispatch), since a staged region's results reach guest memory by // that copy alone. Call before Upload. void AllowDeviceStaging() { stagingAllowed = true; } + void DisallowAddressStaging() { addressStaging = false; } void AllowAdjustedRegions() { adjustedRegions = true; } // Records the copy-in of every staged region anew for another use of this upload (a resource // cache hit, from ShaderResources::Revalidate, under GuestMemory::GpuMutex, once the imports @@ -420,6 +432,7 @@ private: // The device refused the shadow (memory or allocations exhausted): the region takes the // path it would take without staging, in both upload stages. bool unstaged = false; + bool overlay = false; }; // How [begin, end) lies against the space's base regions. @@ -461,10 +474,12 @@ private: // APS5_GPU_COPY_MAX_KIB allows, and APS5_CPU_COPIES unset. bool gpuCopyEligible(const Region& region) const; // Whether a region inside a host import is staged in device memory instead of bound in place - // (see GuestBufferMemory.cpp): staging allowed, not address-based, a written element inside, - // and an atomic element or a size within the written-shadow window. Independent of the import, - // so UploadPrepare and UploadFinish decide alike. + // (see GuestBufferMemory.cpp): staging allowed, not address-based unless an overlay region, a + // written element inside, and an atomic element or a size within the written-shadow window. + // Independent of the import, so UploadPrepare and UploadFinish decide alike. bool stagingEligible(const Region& region, bool addressable) const; + bool overlayCandidate(const Region& inside, bool readOnly) const; + void resolveOverlays(); bool bindableInPlace(std::uint64_t offset, bool addressable) const; // Records the import-to-buffer copies of the given gpuCopy regions into the open batch, with // the barriers that order them after earlier recorded writes and before the shaders reading them. @@ -472,6 +487,7 @@ private: void takeHeapReferences(); Context context; bool stagingAllowed = false; + bool addressStaging = true; bool adjustedRegions = false; GuestAllocations::Lease lease; // The cached address space this build maps through (its lease pins the ranges); `regions` then @@ -490,11 +506,14 @@ private: bool regionsOverlap = false; std::vector> writes; std::vector> readsOverBase; + std::vector> servedInsideBase; std::vector>> heapReferences; + std::vector>> heapReadbacks; // UploadPrepare ran (regions are frozen); `uploaded` once UploadFinish ran. bool prepared = false; bool uploaded = false; bool committed = false; + mutable std::uint64_t directWritesGeneration = 0; }; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp index 6f40129b56..17b5f7cb3b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp @@ -90,12 +90,14 @@ struct GuestTextureResource { std::uint32_t minLod = 0; std::uint32_t allocatedMipCount = 0; std::uint32_t samples = 1; + std::uint64_t fmaskCmaskAddress = 0; }; float EffectiveMinLod(const GuestTextureResource& resource); bool IsNullTextureResource(std::span words); GuestTextureResource DecodeTextureResource(std::span words); +std::array EncodeTextureResource(const GuestTextureResource& resource); bool DescriptorSingleLevel(std::span words); bool FirstLayerDescriptor(std::span words, std::array& narrowed); bool MatchesGuestDimension(ShaderRecompiler::DescriptorImageShape shape, TextureDimension dimension); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleSwizzleEquations.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleSwizzleEquations.hpp new file mode 100644 index 0000000000..358d893eb8 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleSwizzleEquations.hpp @@ -0,0 +1,53 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MULTISAMPLESWIZZLEEQUATIONS_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MULTISAMPLESWIZZLEEQUATIONS_HPP + +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include +#include + +namespace AgcDriver::Graphics { + +struct MultisampleSwizzleEquation { + TextureTileMode tileMode; + std::uint32_t elementBytes; + std::uint32_t fragments; + std::array bits; +}; + +inline constexpr MultisampleSwizzleEquation kMultisampleSwizzleEquations[] = { + {TextureTileMode::kZ64KBX, 1u, 2u, {0x0001000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000100000ull, 0x0000000000400000ull, 0x0000000000000040ull, 0x0000000000800000ull}}, + {TextureTileMode::kZ64KBX, 2u, 2u, {0x0000000000000000ull, 0x0001000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000000040000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0000000000000040ull}}, + {TextureTileMode::kZ64KBX, 4u, 2u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0001000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0000000000840000ull}}, + {TextureTileMode::kZ64KBX, 1u, 4u, {0x0001000000000000ull, 0x0002000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000000040000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0000000000000040ull}}, + {TextureTileMode::kZ64KBX, 2u, 4u, {0x0000000000000000ull, 0x0001000000000000ull, 0x0002000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000000000040ull, 0x0000000000440000ull}}, + {TextureTileMode::kZ64KBX, 4u, 4u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0001000000000000ull, 0x0002000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000440000ull, 0x0000000000800004ull}}, + {TextureTileMode::kZ64KBX, 1u, 8u, {0x0001000000000000ull, 0x0002000000000000ull, 0x0004000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000800400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0000000000840000ull}}, + {TextureTileMode::kZ64KBX, 2u, 8u, {0x0000000000000000ull, 0x0001000000000000ull, 0x0002000000000000ull, 0x0004000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000800400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000440000ull, 0x0000000000800004ull}}, + {TextureTileMode::kZ64KBX, 4u, 8u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0001000000000000ull, 0x0002000000000000ull, 0x0004000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000000440020ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000000400004ull, 0x0000000000820000ull}}, + {TextureTileMode::kR64KBX, 1u, 2u, {0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000000010000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000400000ull, 0x0000000000000040ull, 0x0000000000800000ull, 0x0001000001000000ull}}, + {TextureTileMode::kR64KBX, 2u, 2u, {0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000100000ull, 0x0000000000000040ull, 0x0000000000400000ull, 0x0001000000800000ull}}, + {TextureTileMode::kR64KBX, 4u, 2u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0001000000800000ull}}, + {TextureTileMode::kR64KBX, 8u, 2u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0001000000400000ull}}, + {TextureTileMode::kR64KBX, 16u, 2u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0001000000400020ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000000080000ull, 0x0000000000000010ull}}, + {TextureTileMode::kR64KBX, 1u, 4u, {0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000000010000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000400000ull, 0x0000000000000040ull, 0x0001000000800000ull, 0x0002000001000000ull}}, + {TextureTileMode::kR64KBX, 2u, 4u, {0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000100000ull, 0x0000000000000040ull, 0x0001000000400000ull, 0x0002000000800000ull}}, + {TextureTileMode::kR64KBX, 4u, 4u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0001000000400000ull, 0x0002000000800000ull}}, + {TextureTileMode::kR64KBX, 8u, 4u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0002000000200040ull, 0x0000000100400020ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0001000000400000ull}}, + {TextureTileMode::kR64KBX, 16u, 4u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000200080008ull, 0x0000000100100010ull, 0x0002000000200040ull, 0x0001000000400020ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000000080000ull, 0x0000000000000010ull}}, + {TextureTileMode::kR64KBX, 1u, 8u, {0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000000010000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000800400020ull, 0x0000000000400000ull, 0x0001000000800000ull, 0x0002000001000000ull, 0x0004000002000000ull}}, + {TextureTileMode::kR64KBX, 2u, 8u, {0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000800400020ull, 0x0000000000100000ull, 0x0001000000400000ull, 0x0002000000800000ull, 0x0004000001000000ull}}, + {TextureTileMode::kR64KBX, 4u, 8u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0004000000400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0001000000400000ull, 0x0002000000800000ull}}, + {TextureTileMode::kR64KBX, 8u, 8u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000200080008ull, 0x0000000100100010ull, 0x0004000000200040ull, 0x0002000000400020ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0001000000400000ull}}, + {TextureTileMode::kR64KBX, 16u, 8u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000200080008ull, 0x0000000100100010ull, 0x0004000000200040ull, 0x0002000000400020ull, 0x0000000000000004ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0001000000400000ull}}, +}; + +constexpr const MultisampleSwizzleEquation* FindMultisampleSwizzleEquation(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t fragments) { + for (const auto& equation : kMultisampleSwizzleEquations) { + if (equation.tileMode == tileMode && equation.elementBytes == elementBytes && equation.fragments == fragments) return &equation; + } + return nullptr; +} + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp index c8a8800703..04f7e5ccbd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp @@ -67,6 +67,7 @@ bool TakeMultisampledSingleClear(std::span code, std::span< bool TakeMultisampledClear(MultisampledCmask& cmask, MultisampledColorFill& colorFill, const ColorTarget& use, VkClearColorValue& clear); SampleOrderTarget MultisampleTargetOrderTarget(const Context& context, const ColorTarget& target); std::shared_ptr MultisampleTargetTexture(const Context& context, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D); +std::shared_ptr MultisampleTargetFirstSample(const Context& context, const GuestTextureResource& resource, VkComponentMapping components); VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, VkImage* image = nullptr); VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, std::shared_ptr& owner, VkImage* image = nullptr); void KeepMultisampleTarget_nid_no_patch(const std::shared_ptr& owner); @@ -76,9 +77,11 @@ bool MultisampleTargetStorable(const Context& context, const ColorTarget& target void ResolveMultisampleTarget(const Context& context, const ColorTarget& source, StorageTexture& destination, VkImageView destinationView); bool MultisampledColorFillAbsorbs(std::uint64_t address, std::size_t bytes, std::uint32_t pattern); void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern); +std::shared_ptr MultisampleTargetFmaskTexture_nid_no_patch(const Context& context, const GuestTextureResource& resource, VkComponentMapping components); void ClearMultisampleTargets(VkDevice device); void InstallResidentImageReadGuard_nid_no_patch(); void UpdateResidentImageUnmapObserver_nid_no_patch(); +void GuardOneFragmentFmask(const Context& context, const ColorTarget& target); void RequireNoMultisampleTarget(std::uint64_t address, std::size_t bytes, const char* access); void RequireNoMultisampleColorTarget(std::uint64_t address, std::size_t bytes, const char* access); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp new file mode 100644 index 0000000000..a7960c43dc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp @@ -0,0 +1,54 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MULTISAMPLETEXTURELAYOUT_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MULTISAMPLETEXTURELAYOUT_HPP + +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include +#include +#include +#include +#include + +namespace AgcDriver::Graphics { + +class MultisampleTextureLayout { +public: + static constexpr std::uint32_t Alignment = 65536u; + + MultisampleTextureLayout(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t fragments, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t pipeBankXor = 0); + std::uint64_t Bytes() const { return sliceBytes * layers; } + std::uint64_t SliceBytes() const { return sliceBytes; } + std::uint64_t LinearBytes() const { return static_cast(width) * height * layers * fragments * elementBytes; } + std::uint32_t Pitch() const { return pitch; } + std::uint32_t PaddedHeight() const { return paddedHeight; } + std::uint32_t BlockWidth() const { return blockWidth; } + std::uint32_t BlockHeight() const { return blockHeight; } + const std::array& InverseBlockEquation() const { return inverseBlockEquation; } + std::uint64_t Offset(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t fragment) const; + void Detile(std::span source, std::span destination) const; + void Tile(std::span source, std::span destination) const; + +private: + std::uint64_t offset(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t fragment) const; + void transfer(std::span source, std::span destination, bool retile) const; + + std::uint32_t elementBytes; + std::uint32_t fragments; + std::uint32_t width; + std::uint32_t height; + std::uint32_t layers; + std::uint32_t pipeBankXor; + std::uint32_t blockWidth; + std::uint32_t blockHeight; + std::uint32_t pitch; + std::uint32_t paddedHeight; + std::uint64_t sliceBytes; + std::array inverseBlockEquation; + std::vector xOffsets; + std::vector yOffsets; + std::vector layerOffsets; + std::vector fragmentOffsets; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp index 1ee631c131..b9cea62862 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp @@ -87,6 +87,8 @@ private: VkPipeline secondChannel = VK_NULL_HANDLE; std::size_t attachments = 0; std::size_t colorAttachments = 0; + std::vector boundAttachments; + std::uint32_t boundColorAttachments = 0; bool depthBounds = false; bool depthBias = false; bool libraries = false; @@ -107,8 +109,10 @@ private: // stages and every pipeline state field. A configuration whose stage has no module gets a private // pipeline. A miss builds the pipeline outside the store's mutex (a concurrent build of the same // key keeps the first inserted) and records its recipe in the pipeline journal. Entries are -// LRU-bounded (APS5_PIPELINE_STORE_ENTRIES, 1024) and only evicted once no recorded draw holds -// them. Debug aid: APS5_NO_PIPELINE_CACHE=1 builds one per draw as before. +// LRU-bounded and only evicted once no recorded draw holds them. The bound starts at 1024 and +// doubles up to 4096 for a journal replay that does not fit and while pipelines it evicted keep +// coming back; it never shrinks during a run. APS5_PIPELINE_STORE_ENTRIES fixes it. Debug aid: +// 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); @@ -122,6 +126,8 @@ struct PipelineStoreCounters { std::uint64_t bound = 0; }; PipelineStoreCounters PipelineStoreTotals(); +// Whether the store may still raise its bound (not fixed by APS5_PIPELINE_STORE_ENTRIES, below 4096). +bool PipelineStoreCanGrow(); // The pipeline store's key of a configuration (empty when the configuration gets a private pipeline). void BuildPipelineStoreKey(std::vector& key, const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders, VkImageLayout attachmentLayout); struct DescribedSetLayouts { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp index c2cf90476e..d905e217cf 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp @@ -61,11 +61,14 @@ struct PipelineJournalCounters { std::uint64_t replayFailed = 0; double replayMs = 0; std::uint32_t replayThreads = 0; + std::uint64_t replayHeldPeakBytes = 0; std::uint64_t recordedGraphics = 0; std::uint64_t recordedCompute = 0; std::uint64_t recordedModules = 0; std::uint64_t writtenBytes = 0; std::uint64_t writeFailures = 0; + std::uint64_t evictedRecords = 0; + std::uint64_t evictedBytes = 0; }; // A decimal switch of the pipeline store or journal, or its default when unset; anything else throws. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index 6ef4b5cf08..f76e061983 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -4,6 +4,7 @@ #include "prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp" #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" #include "prx/libSceAgcDriver/Graphics/include/PassHazards.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -106,7 +107,8 @@ public: static constexpr std::size_t DrawInputEntries = 16384; std::shared_ptr UnwrittenDrawSnapshot(std::uint64_t address, std::size_t bytes, const std::array& stamp, VkDeviceSize* offset = nullptr); void NoteUnwrittenDrawSnapshot(std::uint64_t address, std::size_t bytes, const std::array& stamp, std::shared_ptr buffer, VkDeviceSize offset = 0); - std::shared_ptr ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use = SnapshotUse::Storage, std::uint32_t* derived = nullptr, std::uint64_t generation = 0); + std::shared_ptr ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use = SnapshotUse::Storage, std::uint32_t* derived = nullptr, std::uint64_t generation = 0, VkDeviceSize* offset = nullptr); + static constexpr std::size_t ContainingSnapshotProbes = 16; void KeepDrawSnapshot(std::uint64_t address, std::size_t bytes, std::uint64_t generation, std::uint64_t registryGeneration, std::shared_ptr buffer, SnapshotUse use = SnapshotUse::Storage, std::uint32_t derived = 0); static constexpr std::size_t DrawUploadChunk = std::size_t{1} << 20u; struct UploadSlice { @@ -181,6 +183,7 @@ public: // Whether the OPEN (unsubmitted) batch writes the range: a wait on such a range must submit it. bool OpenWriteOverlaps(std::uint64_t address, std::size_t bytes) const; std::uint64_t PendingWriterSerial(std::uint64_t address, std::size_t bytes) const; + std::uint64_t PendingWriterSerial(std::uint64_t address, std::size_t bytes, std::span exceptNotes) const; // What SyncThrough(address, bytes) would wait for, for the [hooksync] attribution: the newest // batch whose noted range overlaps the access (the open batch counts first, with the serial it // will get), whether that batch's fence has already signaled (its GPU work is done, so the @@ -201,25 +204,30 @@ public: // Submits the open batch and returns the serial of the newest submitted batch (0 when nothing was // ever submitted); WaitSerial(serial) then covers all recorded work. std::uint64_t SubmitAndEpoch(); + std::uint64_t OpenBatchSerial(); bool HasTimeline() const { return timeline != VK_NULL_HANDLE; } bool RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress source, VkDeviceAddress arguments, const std::array* threadGroup = nullptr); // Waits until every batch up to `serial` completed on the GPU. Called WITHOUT GuestMemory::GpuMutex: // it touches only the immutable timeline semaphore (the caller keeps the device alive). Requires // HasTimeline(); the debug switch APS5_NO_TIMELINE makes the device create the recorder without one. void WaitSerial(std::uint64_t serial); + bool WaitSerialFor(std::uint64_t serial, std::uint64_t timeoutNanos); // Under the mutex: finishes (completions, release) the in-flight batches up to `serial`, from the // front only; tolerates batches another thread finished meanwhile. Later batches stay in flight. void FinishUpTo(std::uint64_t serial); // Submits and waits for every batch, running completions in order. void Sync(); void CountSamples(); + bool CountingSamples() const { return countingSamples; } bool RecordMeshArguments(VkCommandBuffer commands, VkDeviceAddress record, VkDeviceAddress arguments, std::span rules); std::uint64_t SamplesTotal(); bool DumpSamples(VkDeviceAddress target, std::uint64_t address = 0); void NoteSampledDraw(VkCommandBuffer commands); + void NoteDrawTraceToken(std::uint64_t token) noexcept; bool QueuesSampleDumps() const; void PrepareSampleSlot(); void EndPassSamples(); + void RecordUncounted(const std::function& record); struct SampleDumpStatistics { std::uint64_t queued; std::uint64_t queuedInPass; @@ -402,6 +410,7 @@ public: static std::uint64_t PublishGeneration(); // labels waiting in completion actions (the CPU must reap their batch for them to land); static std::uint64_t PendingCompletionLabels(); + static std::uint64_t PendingCompletionStores(); // write-back completions (OnComplete) of unfinished batches: a reaper retires them when every // worker sleeps, as for the labels above; static std::uint64_t PendingWriteBackCompletions(); @@ -578,6 +587,7 @@ private: VkQueryPool queries = VK_NULL_HANDLE; VkQueryPool samples = VK_NULL_HANDLE; std::shared_ptr samplePool; + std::vector endedSamples; bool sampleActive = false; bool samplesDrawn = false; VkQueryPool segments = VK_NULL_HANDLE; @@ -599,6 +609,7 @@ private: // Labels stored by this batch's completion actions (see AfterCompletions); subtracted from // the lock-free pending count when the batch finishes, whether or not its completions ran. std::uint32_t completionLabelCount = 0; + std::uint32_t completionStoreCount = 0; // Write-back completions (OnComplete), likewise. std::uint32_t writeBackCompletionCount = 0; // The ranges of the completion labels also stored on the GPU, and whether each entered the @@ -671,6 +682,8 @@ private: std::uint64_t end; }; std::vector keyStores; + std::vector drawTraceTokens; + std::unordered_set drawTraceTokenSet; }; struct LabelEntry { std::uint32_t value; @@ -796,7 +809,7 @@ private: // Whether a write-back noted after `sequence` overlapped [begin, end); true when the ring no // longer reaches back to `sequence` (conservative: the store runs as before). bool writtenBackSince(std::uint64_t sequence, std::uint64_t begin, std::uint64_t end); - std::mutex writtenBackMutex; + AgcDriver::Mutex writtenBackMutex; std::deque> writtenBack; std::uint64_t writtenBackSequence = 0; // PendingLabel without the table mutex (the caller holds it, or the GPU mutex). @@ -844,7 +857,7 @@ private: // frame would otherwise allocate and free their objects each time). std::vector> spare; std::vector sparePools; - mutable std::mutex completedMutex; + mutable AgcDriver::Mutex completedMutex; std::array completed; std::uint64_t newestSubmitted = 0; std::chrono::steady_clock::time_point newestSubmittedAt{}; @@ -881,6 +894,7 @@ private: std::map indexRanges; std::list indexRangeRecency; bool refreshDrawSnapshot(DrawSnapshot& entry, std::uint64_t address, std::size_t bytes); + std::shared_ptr containingDrawSnapshot(std::uint64_t address, std::size_t bytes, VkDeviceSize* offset); mutable std::weak_ptr publishedWriteRanges; mutable bool publishedWriteRangesValid = false; }; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ResidentMirror.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ResidentMirror.hpp new file mode 100644 index 0000000000..f6c10579b9 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ResidentMirror.hpp @@ -0,0 +1,77 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_RESIDENTMIRROR_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_RESIDENTMIRROR_HPP + +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver::Graphics { + +class Buffer; +class ResidentMemory; + +using GuestRuns = std::vector>; + +GuestRuns GuestNonzeroBlocks(const GuestRuns& candidates); +bool GuestBytesZero(std::uint64_t begin, std::uint64_t end); +void SetResidentAddressSpaceLimit(std::uint64_t bytes); + +class ResidentBuffer : public std::enable_shared_from_this { +public: + static constexpr std::uint64_t BlockBytes = 65536; + + static std::shared_ptr Create(const Context& context, std::uint64_t base, std::uint64_t bytes, const GuestRuns& backed); + ~ResidentBuffer(); + ResidentBuffer(const ResidentBuffer&) = delete; + ResidentBuffer& operator=(const ResidentBuffer&) = delete; + + VkBuffer Handle() const { return buffer; } + VkDeviceAddress DeviceAddress() const { return address + lead; } + std::uint64_t Origin() const { return origin; } + std::uint64_t BoundBytes() const { return bound * BlockBytes; } + std::uint32_t MemoryType() const { return type; } + std::byte* Block(std::uint64_t guest) const; + std::uint64_t UnboundBytes(const GuestRuns& runs) const; + void Bind(const GuestRuns& runs); + void MarkChanged(std::uint64_t begin, std::uint64_t end); + void Read(std::uint64_t guest, std::span out) const; + void Store(std::uint64_t guest, std::span bytes); + std::shared_ptr RecordReadback(VkCommandBuffer commands, std::uint64_t guest, std::uint64_t bytes) const; + void ReadDevice(std::uint64_t guest, std::span out); + static void FlushChanged(const Context& context); + +private: + struct Slot { + void* slab = nullptr; + std::uint32_t index = 0; + std::byte* host = nullptr; + }; + ResidentBuffer(const Context& context, std::uint64_t base, std::uint64_t bytes, const GuestRuns& backed); + std::size_t blockIndex(std::uint64_t guest) const; + void submit(const std::vector& binds); + void flush(); + std::shared_ptr readbackBatch(std::uint64_t guest, std::uint64_t bytes); + void release() noexcept; + + Context context; + std::shared_ptr memory; + VkBuffer buffer = VK_NULL_HANDLE; + VkDeviceAddress address = 0; + std::uint32_t type = 0; + std::uint64_t origin = 0; + std::uint64_t lead = 0; + std::uint64_t size = 0; + std::uint64_t bound = 0; + std::vector slots; + std::mutex changedMutex; + GuestRuns changed; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp index ad2f347b21..7212fb40c6 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -59,7 +60,7 @@ private: std::shared_ptr sampler; std::uint64_t lastUse; }; - std::mutex mutex; + AgcDriver::Mutex mutex; std::map, Entry> entries; std::uint64_t clock = 0; std::size_t capacity; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index c0d65d1f74..2438745430 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -5,6 +5,7 @@ #include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "Recompiler.hpp" #include "prx/libSceAgcDriver/Graphics/include/Shaders.hpp" #include @@ -29,16 +30,20 @@ class Recorder; void FlushCachedTextures(VkDevice device); void ClearCachedTextures(VkDevice device); -std::uint64_t ReleaseTextureCaches(std::uint64_t bytes); +std::uint64_t ReleaseTextureCaches(std::uint64_t bytes, bool newImage = false); std::uint64_t TextureCacheBudget(const VkPhysicalDeviceMemoryProperties& memory); +std::uint64_t StorageTextureCeiling(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported); std::uint64_t StorageTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t sampledBytes, std::uint64_t storageBytes); -std::uint64_t SampledTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t textureBytes); +std::uint64_t SampledTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t textureBytes, std::uint64_t storageBytes = 0); bool SampledBudgetReportDue(std::uint64_t reported, std::uint64_t budget, std::chrono::steady_clock::duration sinceReport); std::optional LargestDeviceLocalHeap(const VkPhysicalDeviceMemoryProperties& memory); std::uint64_t StorageTextureCacheBudget(const Context& context); +inline constexpr std::uint64_t SpareSnapshotPresents = 16; struct TextureCacheUse { std::size_t sampledEntries = 0; + std::size_t spareEntries = 0; std::uint64_t sampledBytes = 0; + std::uint64_t spareBytes = 0; std::uint64_t storageBytes = 0; }; TextureCacheUse TextureCacheUsage(); @@ -46,12 +51,16 @@ void OverrideCacheBudgetForTest(std::uint64_t bytes); std::uint64_t SampledTextureCacheBudget(const Context& context); std::shared_ptr CachedSampledTexture(const Context& context, std::span words, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D); bool SampledTexturesShareEntry(std::span first, std::span second); +bool AnyPendingCpuWrites(const Context& context); bool CompletePendingCpuWrites(const Context& context, std::uint64_t address, std::size_t bytes); bool SpirvStoresThroughAddresses(std::span words); +bool ShaderStoresThroughAddresses(const ShaderRecompiler::SharedSpirv& spirv); +void LastRecordIndices(std::span logged, std::span objects, std::vector& indices); // The cached storage image of a surface (render targets use it as their resident image); brought up // to date with guest memory before it is returned. std::shared_ptr CachedStorageSurface(const Context& context, const GuestTextureResource& resource); +std::shared_ptr CachedStorageLayers(const Context& context, const GuestTextureResource& resource, std::uint32_t firstLayer, std::uint32_t layerCount); // Whether `image` is still the storage cache's image of its surface (what CachedStorageSurface // 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); @@ -118,7 +127,7 @@ private: PFN_vkDestroyDescriptorPool destroyPool; PFN_vkResetDescriptorPool resetPool; PFN_vkFreeDescriptorSets freeDescriptorSets = nullptr; - mutable std::mutex mutex; + mutable AgcDriver::Mutex mutex; std::map, VkDescriptorSetLayout> layouts; struct Pool { VkDescriptorPool handle = VK_NULL_HANDLE; @@ -145,6 +154,7 @@ struct ImageTableReuseCounts { std::uint64_t writesMade = 0; }; ImageTableReuseCounts ImageTableReuses(); +std::uint64_t SampledLookupKeyScans(); struct PresyncSurface { std::uint64_t address; @@ -291,6 +301,7 @@ public: ProofFailure failure = ProofFailure::None; }; bool Revalidate(std::span shaders, ProofReport* report = nullptr); + static bool EpochProofs(); bool Revalidate(const CompiledShader& shader, ProofReport* report = nullptr) { return Revalidate(std::span(&shader, 1), report); } // The proof a recipe hit runs on its template (design_cpu_final M4): Revalidate by that name. bool ProveCurrent(std::span shaders, ProofReport* report = nullptr) { return Revalidate(shaders, report); } @@ -305,6 +316,7 @@ public: } // Whether RefreshData would record anything for `shader` (the per-word compare; verification). bool DataWordsDiffer(const CompiledShader& shader) const; + std::size_t BindingCount() const { return bindings.size(); } // Why the fast proof of a Revalidate left the object to the full walk (the [rescache] // revalidate line's reasons); Count: it did not. enum class FastFail : std::size_t { NoRecord, Collect, Pending, Evicted, Changed, Keys, ClearedView, StorageKeys, Depth, Replaced, Audit, Count }; @@ -471,6 +483,7 @@ private: std::array words{}; bool depthCompare = false; VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D; + bool firstSample = false; std::shared_ptr texture; }; PreviousSampled previousSampled; @@ -631,6 +644,10 @@ private: // no image other than the object's own sources has results pending over its surfaces and // regions, so those checks are skipped (see fastRevalidate). std::uint64_t pendingSerialSeen = 0; + std::uint64_t provedEpoch = 0; + std::uint64_t provedProof = 0; + std::uint64_t provedPending = 0; + bool revalidateFull(std::span shaders, ProofReport* report); // The import table's identity when the direct regions' serials were last proved. HostImportsProof importsProof; // FNV-1a offset basis: the hash of no data buffers (DataWordsHash). @@ -696,6 +713,7 @@ public: // meanwhile by another worker stays). void Remove(const Key& key, const ShaderResources* object = nullptr); void RemoveImage(const StorageTexture* image, std::vector>* evicted = nullptr); + bool TryRemoveImage(const StorageTexture* image, std::vector>* evicted = nullptr); // Moves the entry to the front of the LRU (a recipe hit uses its template without Find, and a // hot template must not age out under the recipes that depend on it); false when no entry. bool Touch(const Key& key); @@ -718,14 +736,32 @@ private: void noteEvicted(const Key& key); void noteReturn(const Key& key); void noteUse(const Key& key); + void removeImage(const StorageTexture* image, std::vector>* evicted); struct KeyHash { std::size_t operator()(const Key& key) const noexcept { - std::uint64_t hash = 14695981039346656037ull; - for (const auto word : key) hash = (hash ^ word) * 1099511628211ull; + constexpr std::uint64_t Prime = 0x9e3779b97f4a7c15ull; + std::uint64_t lanes[4] = {0x243f6a8885a308d3ull, 0x13198a2e03707344ull, 0xa4093822299f31d0ull, 0x082efa98ec4e6c89ull}; + const auto* words = key.data(); + const auto count = key.size(); + std::size_t at = 0; + for (; at + 8u <= count; at += 8u) { + for (std::size_t lane = 0; lane < 4u; ++lane) { + const auto value = static_cast(words[at + lane * 2u]) | static_cast(words[at + lane * 2u + 1u]) << 32u; + lanes[lane] = (lanes[lane] ^ value) * Prime; + lanes[lane] ^= lanes[lane] >> 29u; + } + } + std::uint64_t hash = lanes[0] ^ (lanes[1] << 17u | lanes[1] >> 47u) ^ (lanes[2] << 31u | lanes[2] >> 33u) ^ (lanes[3] << 47u | lanes[3] >> 17u) ^ count; + for (; at < count; ++at) hash = (hash ^ words[at]) * Prime; + hash ^= hash >> 33u; + hash *= 0xff51afd7ed558ccdull; + hash ^= hash >> 33u; + hash *= 0xc4ceb9fe1a85ec53ull; + hash ^= hash >> 33u; return static_cast(hash); } }; - mutable std::mutex mutex; + mutable AgcDriver::Mutex mutex; std::list>> entries; std::unordered_map index; std::size_t capacity = 0; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/SnapshotCopies.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/SnapshotCopies.hpp new file mode 100644 index 0000000000..3b0b3b94f4 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/SnapshotCopies.hpp @@ -0,0 +1,56 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_SNAPSHOTCOPIES_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_SNAPSHOTCOPIES_HPP + +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver::Graphics { +class Buffer; + +namespace Detail { +struct SnapshotCopies { + using Key = std::tuple; + static constexpr std::size_t Budget = std::size_t{16} << 20u; + static constexpr std::size_t MaxEntries = 1024; + AgcDriver::Mutex mutex; + VkDevice device = VK_NULL_HANDLE; + struct Entry { + std::shared_ptr buffer; + std::vector bytes; + std::list::iterator recent; + }; + std::map entries; + std::list recency; + std::size_t bytes = 0; + + void Insert(const Key& key, const std::shared_ptr& buffer, std::span data) { + recency.push_back(key); + try { + entries.emplace(key, Entry{buffer, {data.begin(), data.end()}, std::prev(recency.end())}); + } catch (...) { + recency.pop_back(); + throw; + } + bytes += data.size(); + } + + void Clear() { + entries.clear(); + recency.clear(); + bytes = 0; + } +}; +} +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index f8b9d16afe..13d227b8ea 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -45,6 +45,7 @@ struct ColorTarget { ShaderRecompiler::ColorExportPacking packing = ShaderRecompiler::ColorExportPacking::None; // DCC metadata of a compressed target (CB_COLOR_INFO DCC_ENABLE), or 0 (see DccMetadata.hpp). std::uint64_t dccAddress = 0; + std::uint64_t chainDccAddress = 0; bool dccAlphaOnMsb = false; bool dccPipeAligned = false; std::uint64_t cmaskAddress = 0; @@ -67,7 +68,11 @@ struct ColorTarget { bool uintExport = false; std::uint32_t pipeBankXor = 0; std::uint32_t samples = 1; + std::uint32_t coverageSamples = 1; std::uint32_t dccControl = 0; + std::uint64_t fmaskAddress = 0; + std::uint32_t fmaskPipeBankXor = 0; + std::uint16_t partialWriteMask = 0; }; struct DepthTarget { @@ -95,6 +100,7 @@ struct State { std::optional depth; bool depthTest = false; bool depthWrite = false; + bool depthCopySource = false; VkCompareOp depthCompare = VK_COMPARE_OP_ALWAYS; bool depthBoundsTest = false; float minDepthBounds = 0.0f; @@ -140,6 +146,7 @@ struct State { bool ditheredAlphaToCoverage = false; bool perSampleShading = false; bool mergedCoverage = false; + bool truncatedCoverage = false; VkConservativeRasterizationModeEXT conservativeRasterization = VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT; VkRect2D scissor; VkCullModeFlags cullMode; @@ -148,22 +155,33 @@ struct State { VkProvokingVertexModeEXT provokingVertexMode = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT; VkPipelineColorBlendAttachmentState blend; std::array blendConstants; + std::uint8_t colorRop = 0xccu; std::uint32_t samples = 1; std::array sampleLocations{}; bool customSampleLocations = false; bool sampleLocationGrid = false; std::uint32_t paClVsOutCntl = 0; + bool vertexPoints = false; + float pointSize = 1.0f; + float pointSizeMin = 0.0f; + float pointSizeMax = 0.0f; std::array quadSampleMask{0xffffu, 0xffffu, 0xffffu, 0xffffu}; bool pixelEarlyZ = false; + bool pixelKill = false; }; // PA_CL_VS_OUT_CNTL bits that place position exports (clip and cull distances, the misc and the two // distance vectors); a vertex program that exports pos1..pos3 depends on them. inline constexpr std::uint32_t PositionExportLayoutBits = 0xffffu | (1u << 21u) | (1u << 22u) | (1u << 23u); +inline constexpr std::uint8_t CopyRop = 0xccu; +VkLogicOp LogicOpFromRop(std::uint8_t rop); + ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup = false); +void ApplyPointSize(const State& state, ShaderRecompiler::ShaderVertexStageInfo& vertex); State DecodeState(const QueueState& queue, bool vertexWorkgroup = false); void ApplyDepthRemap(const Context& context, State& state); +void ApplyDepthBoundsEmulation(const Context& context, State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel); void NormalizeUnusedViewportDepth(State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel); std::array SampleLocations(const State& state); std::array PixelSampleLocations(const State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel); @@ -178,6 +196,11 @@ bool SampleMaskFollowsOrder(const State& state, const SampleOrder& order); std::array QuadSampleMasks(const State& state, const SampleOrder& order); std::array ExportMappings(const State& state); std::array ExportPackings(const State& state); +std::uint16_t PackedWriteMask(std::uint8_t componentMapping, std::uint32_t exportedMask); +GuestTextureResource TargetMergeSurface(const ColorTarget& color); +bool DepthStencilWrites(const State& state); +bool DepthStencilTests(const State& state); +ShaderRecompiler::TargetMerge ExportTargetMerge(const State& state); ShaderRecompiler::RasterPrimitive RasterPrimitiveOf(const State& state); bool PartialSampleMask(const State& state); bool UniformSampleMask(const State& state); @@ -202,9 +225,7 @@ struct ColorMetadataPass { }; std::optional DecodeColorMetadataPass(const QueueState& queue); struct DepthCopyPass { - std::uint64_t depthAddress; - VkExtent2D extent; - std::uint32_t samples; + DepthTarget source; std::uint32_t sample; ColorTarget color; }; @@ -278,7 +299,7 @@ inline constexpr std::array DrawKeyRegisters{{ // PA_SU_VTX_CNTL, the sample masks, PA_SC_CONSERVATIVE_RASTERIZATION_CNTL. {RegisterBank::Context, 0x200, 8}, {RegisterBank::Context, 0x286, 2}, {RegisterBank::Context, 0x292, 2}, {RegisterBank::Context, 0x29b, 1}, {RegisterBank::Context, 0x2ab, 1}, {RegisterBank::Context, 0x2af, 1}, {RegisterBank::Context, 0x2ce, 1}, {RegisterBank::Context, 0x2d5, 2}, {RegisterBank::Context, 0x2db, 2}, {RegisterBank::Context, 0x2de, 6}, {RegisterBank::Context, 0x2f8, 2}, {RegisterBank::Context, 0x2fe, 16}, {RegisterBank::Context, 0x30e, 2}, {RegisterBank::Context, 0x313, 1}, // CB_COLOR0..7_BASE .. DCC_BASE (15 words a slot), CB_COLOR0..7_BASE_EXT, DCC_BASE_EXT, ATTRIB2, ATTRIB3. - {RegisterBank::Context, 0x318, 0x78}, {RegisterBank::Context, 0x390, 8}, {RegisterBank::Context, 0x398, 8}, {RegisterBank::Context, 0x3a8, 0x18}, + {RegisterBank::Context, 0x318, 0x78}, {RegisterBank::Context, 0x390, 8}, {RegisterBank::Context, 0x398, 0x10}, {RegisterBank::Context, 0x3a8, 0x18}, // The pixel program address, RSRC2 and user words; the geometry-back user pointer and program // address; the vertex/geometry-front RSRC1/RSRC2 and user words; the vertex program address; // the hull user pointer, program address, RSRC2 and user words; the local program address. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index ae4f6dcaec..85a45ae60e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -47,7 +49,7 @@ struct OwnedImage { OwnedImage(const OwnedImage&) = delete; OwnedImage& operator=(const OwnedImage&) = delete; ~OwnedImage(); - void Allocate(const Context& device, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation); + void Allocate(const Context& device, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation, bool newImage = false); Context context; VkImage image; VkDeviceMemory memory; @@ -198,7 +200,12 @@ public: // Deferred write-back (APS5_EAGER_WRITEBACK=1 stores at once instead). void MarkDirty(); void MarkWholeWritten(); + void MarkUnitsWritten(const std::vector& units); + std::vector UnitRects(const std::vector& units) const; + bool BlockUnits() const { return blockUnits; } + std::uint32_t TrackedUnits() const { return trackedLayers; } void MarkSliceDirty(std::uint32_t slice, std::uint32_t count = 1); + bool MarkDrawn(std::uint32_t slice, std::uint32_t count, VkRect2D scissor); void Flush(); bool Dirty() const; // Stores every pending image overlapping the range, except `except`; returns whether any was. @@ -208,12 +215,15 @@ public: // any unit was: a reader of the import must sync for a publish as for a store. Only images // with a pending unit inside the range are stored (APS5_NO_FLUSH_PRETEST=1 lists every // overlapping pending image, as before). - static bool FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except = nullptr, const char* reason = "memory access", PublishScope scope = PublishScope::Whole, bool* published = nullptr); + static bool FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except = nullptr, const char* reason = "memory access", PublishScope scope = PublishScope::Whole, bool* published = nullptr, bool waited = false); static void FlushAllPending(const char* reason); static bool WaitForFlushing(std::uint64_t address, std::size_t bytes, const StorageTexture* except = nullptr); static void SaveBeforeUnwatch(std::uint64_t address, std::size_t bytes); static constexpr std::uint64_t SmallStoreBytes = 64 * 1024; static std::size_t StoreSmallPending(const char* reason); + static void StoreReadbackBeforeLabel(const char* reason); + static std::size_t StoreDrawnLinear(const char* reason); + static std::size_t StoreDrawnLinearTouching(const char* reason, const std::function& touches, bool all); static bool AnyPending(); static void FlushDumpSelected(const char* reason); static bool StoreAtFlipRequested(const char* value); @@ -225,6 +235,7 @@ public: // any of the [begin, end) ranges: one acquisition of the registry's mutex. static bool AnyPendingOverlaps(std::span> ranges); static bool AnyLiveOverlaps(std::span> ranges); + static std::uint64_t StampKeepingNewerResults(std::uint64_t address, std::size_t bytes, std::uint64_t recorded, const std::function& stamp); // Moves with every change of the pending registry (an image marked dirty, stored, flushed or // dropped; after the store for a flush), bumped under the registry's mutex with the change, so // a reader that loads it before and after its own reads knows the registry it consulted stayed @@ -273,7 +284,7 @@ public: // FlushPending's publish of the unit shadows over the range without the image stores (what it // does when it lists no image, and all an access AccessKeptByCpu approved needs): whether any // unit was published. - static bool PublishShadowsOnly(std::uint64_t address, std::size_t bytes, PublishScope scope, const char* reason = "memory access"); + static bool PublishShadowsOnly(std::uint64_t address, std::size_t bytes, PublishScope scope, const char* reason = "memory access", bool waited = false); // For the [hooksync] recorded-store line: images a FlushPending stored after a hook skip of // theirs (AccessKeptByCpu), of which by the hook for the same read site as the last skip; 10 s deltas. struct HookSkipCounts { @@ -350,6 +361,7 @@ public: // validates textures copied from this image. std::uint64_t Version() const { return version; } std::uint64_t Generation() const { return generation; } + std::uint64_t GenerationFor(std::uint64_t address, std::uint64_t bytes) const; // The DCC keys the content was uploaded under and the last scan of the surface's keys // (ProvedClearKeys): Refresh's own key rule for the fast revalidation. Under // GuestMemory::GpuMutex only, as Refresh is; never from a build's stage A. @@ -364,6 +376,8 @@ public: // was still current (nothing uploaded). // Keeps the image current with guest memory (see GuestMemory::CollectWrites). bool Refresh(); + bool RefreshLayers(std::uint32_t first, std::uint32_t count); + inline static std::atomic epochRefreshes{0}; static std::uint64_t SinglePassMoves(); static std::uint64_t SlabRetiles(); static std::uint64_t RefreshesProved(); @@ -377,12 +391,14 @@ public: VkDeviceSize AllocationBytes() const { return memoryBytes; } private: + std::atomic provedEpoch{0}; + std::atomic provedProof{0}; + std::atomic provedPending{0}; StorageTexture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, std::uint32_t mipLevel); // The regions of every array layer, or of the tracked layers `layers` selects. std::vector CopyRegions(const std::vector* layers = nullptr) const; - // Uploads the surface, or only the tracked layers `layers` selects (the direct path; the others - // upload everything). void upload(const std::vector* layers = nullptr); + std::vector cpuUploadLayers(const std::vector& units) const; void captureGuestBytes(const std::vector* layers); bool originalHolds(std::uint64_t address, std::size_t bytes) const; bool compareUntracked(std::uint64_t address, std::size_t bytes, std::span changed, bool memoize = false) const; @@ -470,6 +486,27 @@ private: // (`shadowed` receives those guest ranges, `imported` the pieces written to the import). std::uint64_t uploadWindows(const HostImport& import, std::span> runs, bool discard = false); std::uint64_t writeBackWindows(std::span segments, std::span> keep, std::uint64_t firstStored, std::uint64_t lastStored, std::vector& shadowed, std::vector>& imported, Buffer* save = nullptr); + struct DrawnRect { + std::uint32_t left = 0; + std::uint32_t top = 0; + std::uint32_t right = 0; + std::uint32_t bottom = 0; + }; + struct DrawnArea { + bool drawn = false; + bool rect = false; + std::uint32_t left = 0; + std::uint32_t top = 0; + std::uint32_t right = 0; + std::uint32_t bottom = 0; + std::vector rects; + }; + DrawnArea drawnArea() const; + std::vector> drawnSpans(const DrawnArea& area, std::span> keep) const; + std::uint64_t storeDrawnRect(const DrawnArea& area, std::span segments, std::span> stores, const std::vector& saveUnits); + void addDrawnRect(DrawnRect rect); + void forgetDrawn(); + void noteStore(std::uint64_t note); // Per-layer validity (thin array surfaces whose layers are 64 KiB-aligned guest slices; every // other surface is one tracked layer): a layer's generation is the write generation its guest // bytes were last known to match the image at, and a pending layer holds results (a clear, a @@ -477,7 +514,10 @@ private: // layer generation (the whole-surface fast check), `dirty` whether any layer is pending. std::uint64_t layerBegin(std::uint32_t layer) const { return descriptor.baseAddress + static_cast(layer) * trackedLayerBytes; } bool anyLayerPending() const; + bool refresh(std::uint32_t firstLayer, std::uint32_t lastLayer); + void noteLayersBehind(std::uint32_t firstLayer, std::uint32_t lastLayer, const std::vector& outside); void refreshGeneration(); + std::uint64_t collectedLayerGeneration(std::uint32_t layer, std::uint64_t whole, bool uncached = false) const; bool refreshProved(); void takeRefreshProof(bool aliased); bool otherPendingOverlaps() const; @@ -503,6 +543,7 @@ private: // Per 64 KiB tracker block of the surface (block 0 holds the base address): the generation of // the tracked layer it belongs to, for GuestMemory::ChangedBlocks. void blockGenerations(std::vector& generations) const; + std::uint64_t unitBlockGeneration(std::uint32_t unit, std::uint64_t at) const; // Another image of exactly this surface under another storage format with results pending // (block units, uncompressed, still cached), if any: Refresh takes its pending units from its // image on the device (borrowUnits) instead of having them stored and re-read, and the alias @@ -555,13 +596,20 @@ private: std::uint64_t sliceLinearBytes = 0; SurfaceGeometry geometry; std::vector original; - mutable std::array comparedGuestBytes{}; + struct ComparedRange { + std::uint64_t address = 0; + std::size_t bytes = 0; + std::array stamp{}; + }; + mutable std::array comparedRanges{}; + mutable std::uint32_t nextComparedRange = 0; std::vector generationBaseline; // DCC keys the image content was uploaded under: a fast-cleared surface starts as its clear value. DccKeys uploadedKeys = DccKeys::Uncompressed; mutable DccKeys filledKeys = DccKeys::Uncompressed; mutable DccKeyProof keyProof; mutable DccRangeProof targetKeyProof; + DccSingleProof singleProof; struct RefreshProof { std::uint64_t generation = 0; std::uint64_t pendingSerial = 0; @@ -580,7 +628,9 @@ private: std::uint32_t trackedLayers = 1; std::uint64_t trackedLayerBytes = 0; std::vector layerGeneration; + std::vector completionKept; std::vector layerPending; + std::vector layerBehind; bool blockUnits = false; // Write-back hysteresis (block units, see writeBack): partial stores in the current window of // presents, its first present, and the present until which every pending unit is stored. @@ -618,6 +668,12 @@ private: VkFormat storageFormat = VK_FORMAT_UNDEFINED; // Results are on the GPU only (guarded by the pending-write registry lock). bool dirty = false; + bool drawnListed = false; + bool drawnWhole = false; + bool drawingTarget = false; + bool pendingFromOthers = false; + std::vector drawnRects; + std::vector storeNotes; std::uint64_t version = 0; std::uint64_t writes = 0; // `original` holds the guest bytes; false after a GPU-side clear, which never read them. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp index 316ae0cb63..d0b15ac51c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp @@ -56,10 +56,11 @@ namespace AgcDriver::Graphics { void DispatchCmaskClear(VkCommandBuffer commands, VkBuffer cmask, std::uint64_t cmaskOffset, std::size_t cmaskBytes, VkImageView view, std::uint32_t width, std::uint32_t height, std::uint32_t elementBytes, const std::array& clearWords, bool write); std::uint32_t CmaskErrors(); void DispatchMultisampleReadback(VkCommandBuffer commands, VkFormat format, VkBuffer destination, std::uint64_t offset, std::uint64_t bytes, VkImageView arrayView, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t fragments, std::uint32_t firstElement, std::uint32_t elements); - void DispatchDepthSample(VkCommandBuffer commands, VkImageView depth, bool multisampled, VkImageView target, std::uint32_t width, std::uint32_t height, std::uint32_t sample); + void DispatchMultisampleImage(VkCommandBuffer commands, std::uint32_t elementBytes, VkBuffer source, std::uint64_t offset, std::uint64_t bytes, VkImageView arrayView, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t fragments, std::uint32_t firstElement, std::uint32_t elements); private: VkPipeline multisampleReadbackPipeline(std::uint32_t elementBytes, bool depth); + VkPipeline multisampleUploadPipeline(std::uint32_t elementBytes); VkPipeline pipeline(TextureTileMode tileMode, std::uint32_t elementBytes, bool retile, bool thick, bool image = false); void release() noexcept; struct DescriptorRing { @@ -83,11 +84,6 @@ namespace AgcDriver::Graphics { VkPipelineLayout cmaskPipelineLayout = VK_NULL_HANDLE; VkPipeline cmaskPipeline = VK_NULL_HANDLE; DescriptorRing cmaskDescriptorSets; - VkDescriptorSetLayout depthSampleLayout = VK_NULL_HANDLE; - VkPipelineLayout depthSamplePipelineLayout = VK_NULL_HANDLE; - std::array depthSamplePipelines{}; - VkSampler depthSampler = VK_NULL_HANDLE; - DescriptorRing depthSampleSets; std::unique_ptr cmaskErrors; VkDescriptorSetLayout imageDescriptorLayout = VK_NULL_HANDLE; VkPipelineLayout imagePipelineLayout = VK_NULL_HANDLE; @@ -100,6 +96,7 @@ namespace AgcDriver::Graphics { std::array readbackModules{}; std::vector> readbackPipelines; DescriptorRing readbackDescriptorSets; + VkShaderModule multisampleUploadModule = VK_NULL_HANDLE; }; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp index 5699bf78e1..dafa52d5f5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp @@ -75,7 +75,7 @@ struct SurfaceGeometry { }; SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor); bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t levels); -GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed); +GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed, bool keysReadAsStored); GuestTextureResource ArrayLayerWindow(const GuestTextureResource& viewed, std::uint32_t maxArrayLayers); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp index 4c8124f513..6d69206ae4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp @@ -48,7 +48,7 @@ bool UnitShadowEnabled(); // byte of the range; PartialUnits: a writer of the range (only units it covers partly are published, // so the neighbours' results in the rest of the unit reach the import before the write stamps it). enum class PublishScope : std::uint8_t { None, Whole, PartialUnits }; -enum class PublishReason : std::uint8_t { Hook, Region, Indirect, CopySource, CopyDestination, Fill, FillClear, Label, Keys, Scanout, Validate, Upload, TailMip, Evict, Retire, Teardown, Count }; +enum class PublishReason : std::uint8_t { Hook, Region, Indirect, CopySource, CopyDestination, Fill, FillClear, Label, Keys, Scanout, Validate, Upload, TailMip, Evict, Retire, Teardown, Address, Count }; // The reason for a FlushPending reason string ("memory access" -> Hook, ...). PublishReason PublishReasonFor(const char* flushReason); @@ -90,7 +90,7 @@ struct ShadowedRange { // published, clears stale ones. Returns the units copied. Inside a completion action a store's // publish records nothing (the store's own stamp makes the unit stale; a recorded publish would land // over the store later). -std::size_t PublishShadow(std::uint64_t address, std::size_t bytes, PublishScope scope, PublishReason reason); +std::size_t PublishShadow(std::uint64_t address, std::size_t bytes, PublishScope scope, PublishReason reason, bool waited = false); // No GpuMutex, no tracker: conservative (a unit with a generation and no publish counts), for // hook-free readers and a build's stage A. bool AnyShadowedOverlaps(std::uint64_t address, std::size_t bytes); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp index a8d93a9a85..85f0dbd804 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -29,13 +30,13 @@ inline bool NullVertexDescriptor(const ShaderRecompiler::VertexAttribute& attrib return fields[0] == 0 && fields[1] == 0 && fields[2] == 0 && fields[3] == 0; } -inline VertexFormat DecodeVertexFormat(const ShaderRecompiler::VertexAttribute& attribute) { +inline VertexFormat DecodeVertexFormat(const ShaderRecompiler::VertexAttribute& attribute, std::uint32_t minimumComponents = 0u) { Require(attribute.components >= 1 && attribute.components <= 4, "invalid vertex attribute component count"); if (NullVertexDescriptor(attribute)) { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT}; return {formats[attribute.components - 1], attribute.components * 4u, 4u, "f32"}; } - const auto components = attribute.formatComponents == 0u ? attribute.components : attribute.formatComponents; + const auto components = std::max(attribute.formatComponents == 0u ? attribute.components : attribute.formatComponents, minimumComponents); Require(components >= 1u && components <= 4u, "invalid vertex format component count"); const auto format = (attribute.resource.fields[3] >> 12u) & 0x7fu; switch (format) { @@ -106,11 +107,27 @@ inline std::string VertexAttributeSignature(const ShaderRecompiler::VertexAttrib return std::string(format.scalar) + (attribute.components == 1 ? "" : "x" + std::to_string(attribute.components)); } +inline std::uint32_t VertexReadBytes(const ShaderRecompiler::VertexAttribute& attribute, VkFormat host) { + for (auto components = 1u; components <= 4u; ++components) { + const auto format = DecodeVertexFormat(attribute, components); + if (format.format == host) return format.bytes; + } + throw std::runtime_error("AGC graphics: vertex format " + std::to_string(host) + " is not a form of the attribute's buffer format"); +} + struct VertexInputLayout { std::vector bindings; std::vector attributes; }; +inline std::array VertexDescriptorFlagsMessage(const ShaderRecompiler::VertexAttribute& attribute) { + std::array message{}; + const auto& fields = attribute.resource.fields; + std::snprintf(message.data(), message.size(), "unsupported vertex buffer descriptor flags at location %u (V#=[%08x %08x %08x %08x])", + attribute.location, fields[0], fields[1], fields[2], fields[3]); + return message; +} + inline VertexInputLayout BuildVertexInputLayout(const Context& context, std::span attributes) { Require(attributes.size() <= context.limits.maxVertexInputBindings && attributes.size() <= context.limits.maxVertexInputAttributes, "vertex input count exceeds device limits"); VertexInputLayout result; @@ -120,18 +137,29 @@ inline VertexInputLayout BuildVertexInputLayout(const Context& context, std::spa const auto format = DecodeVertexFormat(attribute); Require(attribute.location < context.limits.maxVertexInputAttributes && locations.insert(attribute.location).second, "invalid or duplicate vertex attribute location"); Require(attribute.fetchIndex <= 1, "unsupported vertex fetch index"); - Require((fields[1] & 0x80000000u) == 0 && (fields[3] & 0x00800000u) == 0 && (fields[3] >> 30u) == 0, "unsupported vertex buffer descriptor flags"); + if ((fields[1] & 0x80000000u) != 0 || (fields[3] & 0x00800000u) != 0 || (fields[3] >> 30u) != 0) { + Require(false, VertexDescriptorFlagsMessage(attribute).data()); + } const auto stride = (fields[1] >> 16u) & 0x3fffu; const auto address = fields[0] | (static_cast(fields[1] & 0xffffu) << 32u); Require(NullVertexDescriptor(attribute) || (address != 0 && address % format.alignment == 0 && stride % format.alignment == 0), "unaligned vertex buffer"); Require(stride <= context.limits.maxVertexInputBindingStride, "vertex stride exceeds device limits"); Require(context.formatProperties != nullptr, "missing vertex format property query"); - VkFormatProperties properties{}; - context.formatProperties(context.physical, format.format, &properties); - Require((properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0, "device does not support vertex format " + std::to_string(format.format)); + const auto supported = [&](VkFormat candidate) { + VkFormatProperties properties{}; + context.formatProperties(context.physical, candidate, &properties); + return (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0; + }; + auto host = format.format; + auto hostSupported = supported(host); + for (auto components = 1u; !hostSupported && components <= 4u; ++components) { + host = DecodeVertexFormat(attribute, components).format; + hostSupported = supported(host); + } + Require(hostSupported, "device does not support vertex format " + std::to_string(format.format)); const auto binding = static_cast(result.bindings.size()); result.bindings.push_back({binding, stride, attribute.fetchIndex == 0 ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE}); - result.attributes.push_back({attribute.location, binding, format.format, 0}); + result.attributes.push_back({attribute.location, binding, host, 0}); } return result; } @@ -230,14 +258,16 @@ inline std::uint32_t VertexBufferOutOfBoundsSelect(const ShaderRecompiler::Verte inline bool VertexFetchOutOfRange(const ShaderRecompiler::VertexAttribute& attribute) { const auto stride = (attribute.resource.fields[1] >> 16u) & 0x3fffu; + if (stride != 0 || NullVertexDescriptor(attribute)) return false; const auto select = VertexBufferOutOfBoundsSelect(attribute); - return stride == 0 && (select == 2u || select == 3u) && attribute.resource.fields[2] == 0; + const auto records = attribute.resource.fields[2]; + if (select == 0u) return true; + if (select == 3u) return records < DecodeVertexFormat(attribute).bytes; + return records == 0; } inline std::uint64_t ZeroStrideAvailableBytes(const ShaderRecompiler::VertexAttribute& attribute) { - const auto records = attribute.resource.fields[2]; - if (VertexBufferOutOfBoundsSelect(attribute) == 2u && records != 0) return std::max(records, DecodeVertexFormat(attribute).bytes); - return records; + return VertexFetchOutOfRange(attribute) ? 0u : DecodeVertexFormat(attribute).bytes; } inline std::size_t VertexBufferExtent(const ShaderRecompiler::VertexAttribute& attribute) { @@ -261,7 +291,9 @@ inline std::size_t VertexBufferReadSize(const ShaderRecompiler::VertexAttribute& Require(attribute.fetchIndex <= 1, "unsupported vertex fetch index"); const auto index = attribute.fetchIndex == 0 ? maxIndex : firstInstance + instances - 1u; const auto bytes = DecodeVertexFormat(attribute).bytes; - Require(stride == 0 || VertexBufferOutOfBoundsSelect(attribute) <= 1u || index < records, "vertex fetch exceeds descriptor record count"); + const auto select = VertexBufferOutOfBoundsSelect(attribute); + const auto recordsUnchecked = stride == 0 ? select != 1u : select <= 1u; + Require(recordsUnchecked || index < records, "vertex fetch exceeds descriptor record count"); const auto required = static_cast(stride) * index + bytes; Require(stride != 0 || required <= ZeroStrideAvailableBytes(attribute), "vertex fetch exceeds descriptor byte range"); Require(required <= std::numeric_limits::max(), "vertex fetch exceeds addressable byte range"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/DepthSample.comp b/core/libs/prx/libSceAgcDriver/Graphics/shaders/DepthSample.comp deleted file mode 100644 index 36f1883556..0000000000 --- a/core/libs/prx/libSceAgcDriver/Graphics/shaders/DepthSample.comp +++ /dev/null @@ -1,27 +0,0 @@ -#version 450 - -layout(local_size_x = 8, local_size_y = 8) in; - -#if MULTISAMPLED -layout(set = 0, binding = 0) uniform sampler2DMS depth; -#else -layout(set = 0, binding = 0) uniform sampler2D depth; -#endif -layout(set = 0, binding = 1, r32f) writeonly uniform image2D target; - -layout(push_constant) uniform Push { - uint width; - uint height; - int sampleIndex; -} push; - -void main() { - uvec2 pixel = gl_GlobalInvocationID.xy; - if (pixel.x >= push.width || pixel.y >= push.height) return; -#if MULTISAMPLED - float value = texelFetch(depth, ivec2(pixel), push.sampleIndex).r; -#else - float value = texelFetch(depth, ivec2(pixel), 0).r; -#endif - imageStore(target, ivec2(pixel), vec4(value, 0.0, 0.0, 0.0)); -} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/FirstSample.frag b/core/libs/prx/libSceAgcDriver/Graphics/shaders/FirstSample.frag new file mode 100644 index 0000000000..287527d25a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/shaders/FirstSample.frag @@ -0,0 +1,16 @@ +#version 450 + +#if defined(UINT_SAMPLES) +layout(set = 0, binding = 0) uniform usampler2DMS source; +layout(location = 0) out uvec4 color; +#elif defined(SINT_SAMPLES) +layout(set = 0, binding = 0) uniform isampler2DMS source; +layout(location = 0) out ivec4 color; +#else +layout(set = 0, binding = 0) uniform sampler2DMS source; +layout(location = 0) out vec4 color; +#endif + +void main() { + color = texelFetch(source, ivec2(gl_FragCoord.xy), 0); +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/MultisampleUpload.comp b/core/libs/prx/libSceAgcDriver/Graphics/shaders/MultisampleUpload.comp new file mode 100644 index 0000000000..4aa972fc04 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/shaders/MultisampleUpload.comp @@ -0,0 +1,32 @@ +#version 450 +#extension GL_EXT_shader_image_load_formatted : require + +layout(constant_id = 0) const uint ELEMENT_BYTES = 4u; +layout(local_size_x = 64) in; +layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } sourceBuffer; +layout(set = 0, binding = 1) uniform uimage2DMSArray destinationImage; +layout(push_constant) uniform Push { + uint width; + uint height; + uint layers; + uint fragments; + uint firstElement; + uint elements; + uint rowThreads; +} params; + +void main() { + uint index = gl_GlobalInvocationID.y * params.rowThreads + gl_GlobalInvocationID.x; + if (index >= params.elements) return; + uint element = params.firstElement + index; + uint pixel = element / params.fragments; + ivec3 coordinate = ivec3(pixel % params.width, (pixel / params.width) % params.height, pixel / (params.width * params.height)); + uint address = index * ELEMENT_BYTES; + uvec4 value = uvec4(0u); + if (ELEMENT_BYTES < 4u) { + value.x = (sourceBuffer.data[address >> 2u] >> ((address & 3u) * 8u)) & (ELEMENT_BYTES == 1u ? 255u : 65535u); + } else { + for (uint component = 0u; component < ELEMENT_BYTES / 4u; ++component) value[component] = sourceBuffer.data[(address >> 2u) + component]; + } + imageStore(destinationImage, coordinate, int(element % params.fragments), value); +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp index 1da043f87f..aa267a27f6 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "ImageTableAbi.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include @@ -43,7 +44,7 @@ struct TableEntry { }; struct TableCache { - std::mutex mutex; + AgcDriver::Mutex mutex; VkDevice device = VK_NULL_HANDLE; std::list entries; std::uint64_t hits = 0; @@ -282,6 +283,10 @@ void BdaResources::CheckFault() const { message << "shader stage " << report.stage << " called address 0x" << std::hex << report.address << " at program counter 0x" << report.instruction << ", which no linked callee of that call matches"; throw std::runtime_error(message.str()); } + if (report.reason == ShaderRecompiler::BdaAbi::FaultReason::WindowLaneSpill) { + message << "shader stage " << report.stage << ": a relative write stored " << report.bytes << " in lane " << (report.address >> 32u) << " of vector window slot " << (report.address & 0xffffffffu) << ", which holds a scalar spilled with v_writelane_b32 that a later v_readlane_b32 was replaced with"; + throw std::runtime_error(message.str()); + } message << "BDA access failed: address=0x" << std::hex << report.address << " instruction=0x" << report.instruction << std::dec << " bytes=" << report.bytes << " stage=" << report.stage << " reason=" << static_cast(report.reason); if (report.reason == ShaderRecompiler::BdaAbi::FaultReason::Permission && table != nullptr) { const auto bytes = table->Bytes(); @@ -347,7 +352,7 @@ void BdaResources::markWrittenPages() const { Require(false, message.str()); } } - GuestMemory::MarkWritten(begin, bytes); + GuestMemory::MarkPossiblyWritten(begin, bytes); }), "invalid BDA written-page record"); for (std::uint32_t claim = 0; claim < count; ++claim) { const auto slot = words[Abi::WrittenListWord + claim]; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp index 128970a898..9b22e6a5b5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp @@ -57,7 +57,7 @@ BufferPool::~BufferPool() { for (auto& [memory, block] : slabBlocks) freeBlock(*block); } -VkDeviceMemory BufferPool::AllocateMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional hostType, std::uint32_t* allocatedType) { +VkDeviceMemory BufferPool::AllocateMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional hostType, std::uint32_t* allocatedType, bool newImage) { if (allocatedType != nullptr) *allocatedType = allocation.memoryTypeIndex; VkDeviceMemory memory = VK_NULL_HANDLE; auto result = allocateMemory(device, &allocation, nullptr, &memory); @@ -75,7 +75,7 @@ VkDeviceMemory BufferPool::AllocateMemory(const VkMemoryAllocateInfo& allocation } } if (exhausted(result)) { - if (cacheReclaim != nullptr) released = cacheReclaim(ReclaimTarget(allocation.allocationSize)); + if (cacheReclaim != nullptr) released = cacheReclaim(ReclaimTarget(allocation.allocationSize), newImage); if (released != 0) { reclaimed += trim(); memory = VK_NULL_HANDLE; @@ -146,7 +146,7 @@ bool BufferPool::heapOverBudget() { return heapOver; } -VkDeviceMemory BufferPool::TakeImageMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional hostType, std::uint32_t* allocatedType) { +VkDeviceMemory BufferPool::TakeImageMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional hostType, std::uint32_t* allocatedType, bool newImage) { const auto type = allocation.memoryTypeIndex; if (imagePooled(allocation.allocationSize, type)) { std::lock_guard lock(mutex); @@ -168,7 +168,7 @@ VkDeviceMemory BufferPool::TakeImageMemory(const VkMemoryAllocateInfo& allocatio } ++imageMisses; } - return AllocateMemory(allocation, operation, hostType, allocatedType); + return AllocateMemory(allocation, operation, hostType, allocatedType, newImage); } void BufferPool::PutImageMemory(VkDeviceMemory memory, VkDeviceSize bytes, std::uint32_t memoryType) noexcept { @@ -217,7 +217,9 @@ std::size_t BufferPool::RetainedImages() { } VkDeviceSize BufferPool::ReclaimTarget(VkDeviceSize bytes) { - return bytes + std::max(bytes, VkDeviceSize{256} << 20u); + const auto reserve = std::max(bytes, VkDeviceSize{256} << 20u); + const auto limit = std::numeric_limits::max(); + return bytes > limit - reserve ? limit : bytes + reserve; } bool BufferPool::SlabEligible(std::size_t capacity, VkDeviceSize alignment, VkDeviceSize size, VkDeviceSize atom) { @@ -454,6 +456,56 @@ bool BufferPool::evictOldestImage(std::vector& evicted) { return true; } +VkDeviceSize BufferPool::ReclaimIdleDevice(VkDeviceSize bytes, std::chrono::steady_clock::time_point now) { + if (bytes == 0) return 0; + const auto heaps = std::min(memoryProperties.memoryHeapCount, VK_MAX_MEMORY_HEAPS); + if (std::count_if(memoryProperties.memoryHeaps, memoryProperties.memoryHeaps + heaps, [](const auto& heap) { return (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0; }) != 1) return 0; + std::vector evicted; + { + std::lock_guard lock(mutex); + if (!idleRead) { + idleRead = now; + idleClock = clock; + return 0; + } + if (now < *idleRead) return 0; + if (now - *idleRead >= std::chrono::seconds(1)) { + reclaimBefore = idleClock; + idleClock = clock; + idleRead = now; + } + if (reclaimBefore <= scannedIdleClock) return 0; + VkDeviceSize selected = 0; + while (selected < bytes) { + auto oldest = deviceTier.free.end(); + for (auto it = deviceTier.free.begin(); it != deviceTier.free.end(); ++it) { + if (it->second.empty()) continue; + const auto& slot = it->second.front(); + if (slot.allocation.buffer == VK_NULL_HANDLE || slot.allocation.slab || slot.lastUse > reclaimBefore) continue; + if (oldest == deviceTier.free.end() || slot.lastUse < oldest->second.front().lastUse) oldest = it; + } + if (oldest == deviceTier.free.end()) { + scannedIdleClock = reclaimBefore; + break; + } + if (evicted.empty()) evicted.reserve(deviceTier.slots); + const auto allocation = oldest->second.front().allocation; + evicted.push_back(allocation); + selected += allocation.allocationBytes; + deviceTier.retainedBytes -= allocation.allocationBytes; + oldest->second.pop_front(); + if (oldest->second.empty()) deviceTier.free.erase(oldest); + --deviceTier.slots; + ++deviceTier.evictions; + } + } + VkDeviceSize released = 0; + for (const auto& allocation : evicted) released += destroy(allocation); + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + if (profile && released != 0) AgcDriver::ProfilePrint_nid_no_patch("[bufferpool] idle device reclaim: %llu bytes released for %llu bytes of storage-cache pressure\n", static_cast(released), static_cast(bytes)); + return released; +} + std::size_t BufferPool::MaxSlots() { // APS5_BUFFER_POOL_SLOTS= bounds the retained allocations; 64 is the capacity the pool had // before least-recently-used retention, for A/B runs. @@ -533,8 +585,8 @@ std::shared_ptr GetBufferPool(const Context& context) { return context.bufferPool; } -VkDeviceMemory AllocateImageMemory(const Context& context, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation, std::uint32_t* allocatedType) { - return GetBufferPool(context)->TakeImageMemory(allocation, operation, context.RefusedDeviceMemoryFallback(mask, allocation.memoryTypeIndex), allocatedType); +VkDeviceMemory AllocateImageMemory(const Context& context, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation, std::uint32_t* allocatedType, bool newImage) { + return GetBufferPool(context)->TakeImageMemory(allocation, operation, context.RefusedDeviceMemoryFallback(mask, allocation.memoryTypeIndex), allocatedType, newImage); } void ReleaseImageMemory(const Context& context, VkDeviceMemory memory, VkDeviceSize bytes, std::uint32_t memoryType) noexcept { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp index eee078cba1..963da7f4ef 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp @@ -5,6 +5,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -57,10 +58,6 @@ DccKeys ByteKeys(std::uint8_t key) { } } -bool SingleKeysReadable(const GuestTextureResource& resource) { - return KeyBytes % BytesPerElement(resource.format) == 0; -} - void ExpandSingleBlocks(std::span bytes, std::size_t elementBytes) { for (std::size_t block = 0; block < bytes.size(); block += KeyBytes) { const auto blockBytes = std::min(KeyBytes, bytes.size() - block); @@ -162,7 +159,7 @@ struct GpuKeyStore { }; struct KeyStoreMemo { - std::mutex mutex; + AgcDriver::Mutex mutex; std::vector entries; const Recorder* recorder = nullptr; }; @@ -527,7 +524,7 @@ DccKeys textureClearKeys(const GuestTextureResource& resource, std::uint64_t gue if (IsConvertedTextureFormat(resource.format) && (keys == DccKeys::Clear0001 || keys == DccKeys::Clear1110)) throw std::runtime_error(std::string("AGC graphics: DCC clear code ") + DccKeysName(keys) + " of converted texture format " + std::to_string(resource.format) + " is not implemented"); std::byte probe[16]{}; if (!IsDccClear(keys) || !FillDccClear(ResolveTextureFormat(resource.format), keys, resource.dccAlphaOnMsb, std::span(probe, std::min(sizeof(probe), BytesPerElement(resource.format))))) { - static std::mutex reportedMutex; + static AgcDriver::Mutex reportedMutex; static std::set> reported; std::lock_guard lock(reportedMutex); if (reported.size() < 32 && reported.insert({resource.baseAddress, static_cast(keys)}).second) std::fprintf(stderr, "[gpu] texture 0x%llx (format %u) has %s DCC keys at 0x%llx; its texels are read as stored\n", static_cast(resource.baseAddress), resource.format, DccKeysName(keys), static_cast(resource.dccAddress)); @@ -668,7 +665,10 @@ bool FillDccClear(VkFormat format, DccKeys keys, bool alphaOnMsb, std::span(layout.channels) - 1 : 0; @@ -683,7 +683,9 @@ bool FillDccClear(VkFormat format, DccKeys keys, bool alphaOnMsb, std::span> i) & 1u) != 0) element[bit / 8u] |= static_cast(1u << (bit % 8u)); } } - for (std::size_t offset = 0; offset + element.size() <= bytes.size(); offset += element.size()) std::memcpy(bytes.data() + offset, element.data(), element.size()); + std::size_t offset = 0; + for (; offset + element.size() <= bytes.size(); offset += element.size()) std::memcpy(bytes.data() + offset, element.data(), element.size()); + std::fill(bytes.begin() + offset, bytes.end(), std::byte{0}); return true; } @@ -778,6 +780,44 @@ void ReadTextureSurface(const GuestTextureResource& resource, DccKeys keys, std: if (keys == DccKeys::ClearSingle) ExpandSingleBlocks(bytes, BytesPerElement(resource.format)); } +bool SingleKeysReadable(const GuestTextureResource& resource) { + return KeyBytes % BytesPerElement(resource.format) == 0; +} + +bool ReadSingleElement(const GuestTextureResource& resource, std::span element) { + if (!SingleKeysReadable(resource)) return false; + const auto bytes = BytesPerElement(resource.format); + if (element.size() < bytes) return false; + const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::TextureRead); + GuestMemory::ReadCommitted(resource.baseAddress, element.subspan(0, bytes)); + return true; +} + +bool ProvedSingleUniform(const GuestTextureResource& resource, std::uint64_t guestBytes, std::uint64_t collected, DccSingleProof& proof) { + if (!SingleKeysReadable(resource) || guestBytes < KeyBytes) return false; + const auto bytes = BytesPerElement(resource.format); + if (bytes == 0 || bytes > 16) return false; + const auto blocks = static_cast(guestBytes / KeyBytes); + if (proof.generation != 0 && proof.address == resource.baseAddress && proof.bytes == guestBytes && proof.format == resource.format && collected != 0 && GuestMemory::UnchangedSince(resource.baseAddress, static_cast(guestBytes), proof.generation)) return proof.uniform; + std::byte first[16]{}; + bool uniform = true; + std::vector buffer(std::min(guestBytes, std::uint64_t(1) << 20)); + { + const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::TextureRead); + GuestMemory::ReadCommitted(resource.baseAddress, std::span(first, bytes)); + for (std::size_t block = 1; block < blocks && uniform;) { + const auto offset = block * KeyBytes; + const auto size = static_cast(std::min(buffer.size(), guestBytes - offset)); + GuestMemory::ReadCommitted(resource.baseAddress + offset, std::span(buffer).first(size)); + const auto covered = size / KeyBytes; + for (std::size_t index = 0; index < covered && uniform; ++index) uniform = std::equal(first, first + bytes, buffer.begin() + index * KeyBytes); + block += covered; + } + } + proof = collected != 0 ? DccSingleProof{resource.baseAddress, guestBytes, collected, resource.format, uniform} : DccSingleProof{}; + return uniform; +} + namespace { DccKeys EncodedKeys(const GuestTextureResource& resource, DccKeys keys) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DeferredDestruction.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DeferredDestruction.cpp new file mode 100644 index 0000000000..1fb049628a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DeferredDestruction.cpp @@ -0,0 +1,134 @@ +#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver::Graphics { + +namespace { + +constexpr std::size_t DrainObjects = 512; +constexpr auto DrainAge = std::chrono::milliseconds(100); + +struct Deferred { + PFN_vkDestroyBuffer destroyBuffer = nullptr; + PFN_vkFreeMemory freeMemory = nullptr; + std::vector buffers; + std::vector memory; + std::chrono::steady_clock::time_point oldest{}; +}; + +struct Registry { + std::mutex mutex; + std::unordered_map devices; + std::atomic resolver{nullptr}; +}; + +Registry& registry() { + static auto& state = *new Registry(); + return state; +} + +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); +} + +template +bool defer(VkDevice device, THandle handle, std::vector Deferred::*list) noexcept { + try { + auto& state = registry(); + std::lock_guard lock(state.mutex); + const auto found = state.devices.find(device); + if (found == state.devices.end()) return false; + auto& deferred = found->second; + if (deferred.buffers.empty() && deferred.memory.empty()) deferred.oldest = std::chrono::steady_clock::now(); + (deferred.*list).push_back(handle); + return true; + } catch (...) { + return false; + } +} + +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); +} + +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); +} + +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); +} + +} + +PFN_vkGetDeviceProcAddr DeferDestructionToIdle(VkDevice device, PFN_vkGetDeviceProcAddr resolver) { + if (device == VK_NULL_HANDLE || resolver == nullptr) throw std::invalid_argument("AGC graphics: deferred destruction needs a device and its resolver"); + Deferred deferred; + deferred.destroyBuffer = reinterpret_cast(resolver(device, "vkDestroyBuffer")); + deferred.freeMemory = reinterpret_cast(resolver(device, "vkFreeMemory")); + 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.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); + return &deferringDeviceProc; +} + +void DestroyDeferredAtIdle(const Context& context, std::chrono::steady_clock::time_point now) { + auto& state = registry(); + { + std::lock_guard lock(state.mutex); + const auto found = state.devices.find(context.device); + if (found == state.devices.end()) return; + const auto objects = found->second.buffers.size() + found->second.memory.size(); + if (objects == 0 || (objects < DrainObjects && now - found->second.oldest < DrainAge)) return; + } + const auto idle = context.Function("vkQueueWaitIdle")(context.queue); + if (idle != VK_SUCCESS && idle != VK_ERROR_DEVICE_LOST) throw std::runtime_error("AGC graphics: vkQueueWaitIdle before destroying deferred objects: Vulkan result " + std::to_string(idle)); + Deferred taken; + { + std::lock_guard lock(state.mutex); + const auto found = state.devices.find(context.device); + if (found == state.devices.end()) return; + taken.destroyBuffer = found->second.destroyBuffer; + taken.freeMemory = found->second.freeMemory; + taken.buffers.swap(found->second.buffers); + taken.memory.swap(found->second.memory); + } + destroyAll(context.device, taken); +} + +void DestroyDeferredObjects(VkDevice device) noexcept { + Deferred taken; + { + auto& state = registry(); + std::lock_guard lock(state.mutex); + const auto found = state.devices.find(device); + if (found == state.devices.end()) return; + taken = std::move(found->second); + state.devices.erase(found); + } + destroyAll(device, taken); +} + +std::size_t DeferredObjects(VkDevice device) { + auto& state = registry(); + std::lock_guard lock(state.mutex); + const auto found = state.devices.find(device); + return found == state.devices.end() ? 0 : found->second.buffers.size() + found->second.memory.size(); +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 40f1132662..f498c73896 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -10,6 +10,7 @@ #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp" #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +59,7 @@ struct DepthGuard { std::size_t bytes; std::shared_ptr> active; std::vector live; + std::shared_ptr> hostWritten; }; std::shared_mutex& depthGuardMutex() { @@ -150,6 +153,21 @@ DepthClearCounters& depthClearCounters() { return counters; } +struct DepthPlaneStoreCounters { + std::atomic stores{0}; + std::atomic units{0}; + std::atomic bytes{0}; +}; + +DepthPlaneStoreCounters& depthPlaneStoreCounters() { + static DepthPlaneStoreCounters counters; + return counters; +} + +bool anyUnit(const std::vector& units) { + return std::find(units.begin(), units.end(), true) != units.end(); +} + thread_local DepthPlaneOverwrite* currentOverwrite = nullptr; std::optional decodeOverwrite(const StorageImageClear& clear) { @@ -322,6 +340,7 @@ public: const auto clearLease = clearGeneration == 0u ? GuestAllocations::Lease{} : GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); Require(active->load(std::memory_order_acquire), "the native multisample depth owner was released"); Require(sampling.refusal.empty(), sampling.refusal); + Require(!clearPending, "native multisample depth clear has not been recorded"); for (const auto& range : sampling.initial) { Require(GuestMemory::Accessible(reinterpret_cast(range.address), range.bytes) && GuestMemory::Watched(range.address, range.bytes), "native multisample depth requires watched accessible guest memory"); if (const auto* recorder = RecorderFor(context)) Require(!recorder->PendingWriteOverlaps(range.address, range.bytes), "external GPU writes over native multisample depth are not implemented"); @@ -408,8 +427,8 @@ public: return (ResolveTextureFormat(resource.format) == expected || depthBits || UnormStencilView(resource)) && oneSlice && resource.width == target.extent.width && resource.height == target.extent.height && resource.baseLevel == 0 && resource.lastLevel == 0 && resource.baseArray == 0; } - std::shared_ptr Sampled(std::span words, const GuestTextureResource& resource, VkComponentMapping components, bool drawn = false, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D) { - CheckCurrent(); + std::shared_ptr Sampled(std::span words, const GuestTextureResource& resource, VkComponentMapping components, bool drawn = false, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D, bool copySource = false) { + if (!copySource) CheckCurrent(); CheckResource(resource); std::array key{}; std::copy_n(words.begin(), std::min(words.size(), 8), key.begin()); @@ -449,7 +468,7 @@ public: const auto sampledImage = useSampleShadow ? sampleShadow : owned; auto texture = std::make_shared(context, useSampleShadow ? sampleShadow->image : image, hostFormat, stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, components, viewType, 0, sampledImage); if (multisampled) { - Require(samplingActive, "native multisample depth sampling requires its creation-time owner"); + Require(copySource || samplingActive, "native multisample depth sampling requires its creation-time owner"); texture->MarkRefreshedPerUse(); } textures.emplace(key, texture); @@ -475,6 +494,7 @@ public: void CopyAspectFrom(const DepthSurface& source, VkImageAspectFlagBits aspect) { ForgetClear(aspect); + NoteHostWrite(); auto* recorder = RecorderFor(context); std::unique_ptr batch; if (recorder == nullptr) batch = std::make_unique(context); @@ -491,30 +511,6 @@ public: else Recorder::CountBarriers(Recorder::CommandClass::Draw, 2); } - void CopySample(std::uint32_t sample, StorageTexture& destination, VkImageView destinationView) { - auto* recorder = Recorder::Active(); - Require(recorder != nullptr, "depth sample copies need an active recorder"); - Require(sampleable, "DB depth copy from a depth surface the device cannot sample at its sample count"); - ApplyFastClear(); - if (useSampleShadow) { - if (sampleShadow == nullptr) createSampleShadow(); - refreshSampleShadow(false); - recorder->Keep(sampleShadow); - } - if (depthView == VK_NULL_HANDLE) { - VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; - viewInfo.image = useSampleShadow ? sampleShadow->image : image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = hostFormat; - viewInfo.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}; - Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &depthView), "vkCreateImageView depth sample copy"); - } - Require(context.detiler != nullptr, "depth sample copies need the texture detiler"); - context.detiler->DispatchDepthSample(recorder->Commands(), depthView, target.samples > 1, destinationView, target.extent.width, target.extent.height, sample); - Recorder::CountBarriers(Recorder::CommandClass::Copy, 2); - destination.MarkDirty(); - } - bool Holds(const Texture* texture) const { const auto held = [&](const auto& entry) { return entry.second.get() == texture; }; return std::any_of(textures.begin(), textures.end(), held) || std::any_of(stencilCopy.textures.begin(), stencilCopy.textures.end(), held); @@ -524,7 +520,12 @@ public: return std::any_of(textures.begin(), textures.end(), [](const auto& entry) { return entry.second.use_count() > 1; }); } + void NoteHostWrite() { + hostWritten->store(true, std::memory_order_release); + } + std::uint64_t written = ++depthWrites; + std::shared_ptr> hostWritten = std::make_shared>(false); bool retired = false; bool Overlaps(std::uint64_t address, std::uint64_t bytes) const { @@ -674,6 +675,7 @@ public: if (plane.storage.get() == &candidate) { if (stencil || !StoreOverwrite(candidate)) copyPlane(plane, plane.storage, stencil, false); written = ++depthWrites; + NoteHostWrite(); return true; } if (std::none_of(plane.replaced.begin(), plane.replaced.end(), [&](const auto& old) { return old.lock().get() == &candidate; })) continue; @@ -691,6 +693,7 @@ public: const auto source = std::const_pointer_cast(candidate.shared_from_this()); copyPlane(plane, source, stencil, false); written = ++depthWrites; + NoteHostWrite(); return true; } return false; @@ -716,61 +719,206 @@ public: RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); context.Function("vkCmdCopyImageToBuffer")(commands, storage->Image(), VK_IMAGE_LAYOUT_GENERAL, depthPlane.staging->Handle(), 1, &colorRegion); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); - context.Function("vkCmdCopyBufferToImage")(commands, depthPlane.staging->Handle(), image, VK_IMAGE_LAYOUT_GENERAL, 1, &depthRegion); - RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); + if (target.stencilAddress == 0) { + VkImageMemoryBarrier discard{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + discard.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + discard.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + discard.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + discard.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + discard.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + discard.image = image; + discard.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}; + const auto barrier = context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier"); + barrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &discard); + context.Function("vkCmdCopyBufferToImage")(commands, depthPlane.staging->Handle(), image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &depthRegion); + VkImageMemoryBarrier toGeneral = discard; + toGeneral.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toGeneral.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + toGeneral.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toGeneral.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral); + } else { + context.Function("vkCmdCopyBufferToImage")(commands, depthPlane.staging->Handle(), image, VK_IMAGE_LAYOUT_GENERAL, 1, &depthRegion); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); + } if (batch) batch->SubmitAndWait(); - else Recorder::CountBarriers(Recorder::CommandClass::Copy, 3); + else Recorder::CountBarriers(Recorder::CommandClass::Copy, target.stencilAddress == 0 ? 4 : 3); } - std::shared_ptr StoreDepthPlane(const GuestTextureResource& resource) { - ApplyFastClear(); + std::shared_ptr Mirror(const GuestTextureResource& resource) { if (mirrorPlane.storage == nullptr) { mirrorPlane.storage = StorageTexture::Create(context, *context.detiler, resource, 0); ++mirrorEpoch; } - copyPlane(mirrorPlane, mirrorPlane.storage, false, true, true); return mirrorPlane.storage; } - bool SyncFromMemory(const GuestTextureResource& resource) { + bool StoresUnits(const StorageTexture& mirror) const { + return mirror.BlockUnits() && mirror.TrackedUnits() == staleUnits.size(); + } + + std::shared_ptr StoreDepthPlane(const GuestTextureResource& resource, std::vector& units) { + ApplyFastClear(); + auto mirror = Mirror(resource); + auto& counters = depthPlaneStoreCounters(); + ++counters.stores; + const auto planeBytes = mirror->GuestBytes(); + if (!StoresUnits(*mirror)) { + units.assign(units.size(), true); + copyPlane(mirrorPlane, mirror, false, true, true); + mirror->MarkWholeWritten(); + counters.units += units.size(); + counters.bytes += planeBytes; + return mirror; + } + const auto rects = mirror->UnitRects(units); + copyPlane(mirrorPlane, mirror, false, true, true, &rects); + mirror->MarkUnitsWritten(units); + for (std::size_t unit = 0; unit < units.size(); ++unit) { + if (!units[unit]) continue; + ++counters.units; + counters.bytes += std::min(unitBytes, planeBytes - unit * unitBytes); + } + return mirror; + } + + void SizeUnits(std::uint64_t planeBytes) { + static const bool whole = std::getenv("APS5_DEPTH_PLANE_WHOLE_STORE") != nullptr; + const std::uint64_t bytes = !whole && target.address % 65536u == 0 ? std::uint64_t{65536} : std::max(planeBytes, 1u); + const auto count = static_cast(std::max((planeBytes + bytes - 1u) / bytes, 1u)); + if (unitBytes == bytes && staleUnits.size() == count) return; + unitBytes = bytes; + staleUnits.assign(count, false); + staleCount = 0; + unitGeneration.assign(count, 0); + } + + bool AnyStale() const { + return staleCount != 0; + } + + void MarkAllStale(std::uint64_t planeBytes) { + SizeUnits(planeBytes); + staleUnits.assign(staleUnits.size(), true); + staleCount = staleUnits.size(); + } + + void ClearStale() { + staleUnits.assign(staleUnits.size(), false); + staleCount = 0; + } + + void ClearStale(const std::vector& units) { + for (std::size_t unit = 0; unit < units.size() && unit < staleUnits.size(); ++unit) { + if (!units[unit] || !staleUnits[unit]) continue; + staleUnits[unit] = false; + --staleCount; + } + } + + std::vector StaleUnitsIn(std::uint64_t planeBytes, std::uint64_t begin, std::uint64_t end) const { + std::vector units(staleUnits.size(), false); + const auto plane = target.address; + if (units.empty() || unitBytes == 0 || begin >= end || begin >= plane + planeBytes || plane >= end) return units; + const auto from = std::max(begin, plane) - plane; + const auto to = std::min(end, plane + planeBytes) - plane; + const auto last = std::min((to - 1u) / unitBytes, units.size() - 1u); + for (auto unit = from / unitBytes; unit <= last; ++unit) units[static_cast(unit)] = staleUnits[static_cast(unit)]; + return units; + } + + std::pair UnitHull(const std::vector& units, std::uint64_t planeBytes) const { + std::uint64_t first = planeBytes; + std::uint64_t last = 0; + for (std::size_t unit = 0; unit < units.size(); ++unit) { + if (!units[unit]) continue; + first = std::min(first, unit * unitBytes); + last = std::max(last, std::min(planeBytes, (unit + 1u) * unitBytes)); + } + return {target.address + first, target.address + std::max(first, last)}; + } + + void NoteStored(const std::vector& units, std::uint64_t generation) { + for (std::size_t unit = 0; unit < units.size() && unit < unitGeneration.size(); ++unit) { + if (units[unit]) unitGeneration[unit] = generation; + } + } + + std::vector WrittenUnits(const GuestTextureResource& resource, std::uint64_t& generation) { const auto bytes = static_cast(DescribeSurface(resource).guestBytes); + SizeUnits(bytes); GuestMemory::CollectWrites(resource.baseAddress, bytes); - if (syncedGeneration == 0 || (pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 || !GuestMemory::WrittenSince(resource.baseAddress, bytes, syncedGeneration)) { - syncedGeneration = GuestMemory::TrackerGeneration(); - return false; + generation = GuestMemory::TrackerGeneration(); + std::vector changed(unitGeneration.size(), false); + if (unitGeneration.size() == 1u) { + changed[0] = GuestMemory::WrittenSince(resource.baseAddress, bytes, unitGeneration[0]); + return changed; } - memoryStale = false; - if (mirrorPlane.storage == nullptr) { - mirrorPlane.storage = StorageTexture::Create(context, *context.detiler, resource, 0); - ++mirrorEpoch; + const auto oldest = *std::min_element(unitGeneration.begin(), unitGeneration.end()); + if (oldest != 0u && !GuestMemory::WrittenSince(resource.baseAddress, bytes, oldest)) return changed; + std::vector stamped(unitGeneration.size(), GuestMemory::BlockUnchanged); + if (!GuestMemory::ChangedBlocks(resource.baseAddress, bytes, unitGeneration, stamped)) return changed; + for (std::size_t unit = 0; unit < changed.size(); ++unit) changed[unit] = unitGeneration[unit] != 0u && stamped[unit] == GuestMemory::BlockWritten; + return changed; + } + + void SyncFromMemory(const GuestTextureResource& resource) { + std::uint64_t generation = 0; + const auto changed = WrittenUnits(resource, generation); + if ((pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 || !anyUnit(changed)) { + unitGeneration.assign(unitGeneration.size(), generation); + return; + } + ClearStale(changed); + { + struct Syncing { + bool& flag; + ~Syncing() { flag = false; } + } guard{syncing}; + syncing = true; + if (mirrorPlane.storage == nullptr) { + mirrorPlane.storage = StorageTexture::Create(context, *context.detiler, resource, 0); + ++mirrorEpoch; + } else { + static_cast(mirrorPlane.storage->Refresh()); + } + } + unitGeneration.assign(unitGeneration.size(), generation); + const auto mirror = mirrorPlane.storage; + if (StoresUnits(*mirror)) { + const auto rects = mirror->UnitRects(changed); + copyPlane(mirrorPlane, mirror, false, false, true, &rects); + } else { + ClearStale(); + copyPlane(mirrorPlane, mirror, false, false, true); } - else static_cast(mirrorPlane.storage->Refresh()); - syncedGeneration = GuestMemory::TrackerGeneration(); - copyPlane(mirrorPlane, mirrorPlane.storage, false, false, true); written = ++depthWrites; - return true; } void DropMirror() { mirrorPlane = {}; } - bool memoryStale = false; + std::vector staleUnits; + std::size_t staleCount = 0; + std::vector unitGeneration; + std::uint64_t unitBytes = 0; + bool syncing = false; std::uint64_t scannedEpoch = 0; - std::uint64_t syncedGeneration = 0; const Context context; const DepthTarget target; const VkFormat hostFormat; VkImage image = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE; - VkImageView depthView = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; std::shared_ptr> active = std::make_shared>(true); bool sampleable = true; bool samplingActive = false; DepthSamplingState sampling; + bool clearPending = false; std::uint64_t clearGeneration = 0u; + bool clearCompletionQueued = false; std::shared_ptr owned; std::shared_ptr sampleShadow; bool useSampleShadow = false; @@ -968,33 +1116,52 @@ private: return ResolveTextureFormat(resource.format) == (stencil ? VK_FORMAT_R8_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT); } - void copyPlane(PlaneStorage& plane, const std::shared_ptr& storage, bool stencil, bool toStorage, bool ownBatch = false) { + void copyPlane(PlaneStorage& plane, const std::shared_ptr& storage, bool stencil, bool toStorage, bool ownBatch = false, const std::vector* rects = nullptr) { + if (rects != nullptr && rects->empty()) return; auto* recorder = Recorder::Active(); Require(recorder != nullptr || ownBatch, "depth surface storage copies need an active recorder"); std::unique_ptr batch; if (recorder == nullptr) batch = std::make_unique(context); const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; - const auto bytes = static_cast(target.extent.width) * target.extent.height * (stencil ? 1u : d16 ? 2u : 4u); + const std::uint32_t texelBytes = stencil ? 1u : d16 ? 2u : 4u; + const auto bytes = static_cast(target.extent.width) * target.extent.height * texelBytes; if (plane.staging == nullptr) plane.staging = std::make_unique(context, bytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); const auto commands = recorder != nullptr ? recorder->Commands() : batch->Handle(); if (recorder != nullptr) recorder->Keep(storage); const std::uint32_t timing = recorder != nullptr ? recorder->BeginGpuTiming(Recorder::CommandClass::Copy) : 0u; - VkBufferImageCopy planeRegion{0, 0, 0, {stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}, {0, 0, 0}, {target.extent.width, target.extent.height, 1}}; - VkBufferImageCopy colorRegion = planeRegion; - colorRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + const VkBufferImageCopy planeRegion{0, 0, 0, {stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}, {0, 0, 0}, {target.extent.width, target.extent.height, 1}}; + std::vector planeRegions; + std::size_t moved = bytes; + if (rects == nullptr) { + planeRegions.push_back(planeRegion); + } else { + moved = 0; + for (const auto& rect : *rects) { + auto region = planeRegion; + region.bufferOffset = (static_cast(rect.offset.y) * target.extent.width + static_cast(rect.offset.x)) * texelBytes; + region.bufferRowLength = target.extent.width; + region.imageOffset = {rect.offset.x, rect.offset.y, 0}; + region.imageExtent = {rect.extent.width, rect.extent.height, 1}; + planeRegions.push_back(region); + moved += static_cast(rect.extent.width) * rect.extent.height * texelBytes; + } + } + auto colorRegions = planeRegions; + for (auto& region : colorRegions) region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + const auto count = static_cast(planeRegions.size()); const auto sourceImage = toStorage ? image : storage->Image(); const auto destinationImage = toStorage ? storage->Image() : image; RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); - context.Function("vkCmdCopyImageToBuffer")(commands, sourceImage, VK_IMAGE_LAYOUT_GENERAL, plane.staging->Handle(), 1, toStorage ? &planeRegion : &colorRegion); + context.Function("vkCmdCopyImageToBuffer")(commands, sourceImage, VK_IMAGE_LAYOUT_GENERAL, plane.staging->Handle(), count, toStorage ? planeRegions.data() : colorRegions.data()); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); - context.Function("vkCmdCopyBufferToImage")(commands, plane.staging->Handle(), destinationImage, VK_IMAGE_LAYOUT_GENERAL, 1, toStorage ? &colorRegion : &planeRegion); + context.Function("vkCmdCopyBufferToImage")(commands, plane.staging->Handle(), destinationImage, VK_IMAGE_LAYOUT_GENERAL, count, toStorage ? colorRegions.data() : planeRegions.data()); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); if (batch) { batch->SubmitAndWait(); return; } Recorder::CountBarriers(Recorder::CommandClass::Copy, 3); - recorder->EndGpuTiming(timing, bytes); + recorder->EndGpuTiming(timing, moved); } void release() noexcept { @@ -1003,7 +1170,6 @@ private: releaseStencilCopy(); mirrorPlane = {}; textures.clear(); - if (depthView) context.Function("vkDestroyImageView")(context.device, depthView, nullptr); if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); if (owned != nullptr) owned.reset(); else { @@ -1046,8 +1212,8 @@ public: allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory depth plane copy"); Check(context.Function("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory depth plane copy"); - stagingSize = static_cast(sliceBytes()) * layers; - staging = std::make_unique(context, stagingSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + imageBytes = requirements.size; + owned = std::make_shared(context, image, memory); } catch (...) { release(); throw; @@ -1066,12 +1232,7 @@ public: std::snprintf(text, sizeof(text), "AGC graphics: depth plane copy geometry does not match its layers (copy %ux%u, %u layers, %llu bytes a slice; texture %ux%u guest format %u, dimension %d, tile mode %u, %u layers, %llu bytes a slice, %zu mips)", extent.width, extent.height, layers, static_cast(sliceBytes()), resource.width, resource.height, resource.format, static_cast(resource.dimension), resource.tileMode, geometry.layers, static_cast(geometry.sliceLinearBytes), geometry.mips.size()); throw std::runtime_error(text); } - const auto stagingBytes = static_cast(geometry.sliceLinearBytes) * layers; - if (stagingBytes > stagingSize) { - retiredStaging.push_back(std::move(staging)); - staging = std::make_unique(context, stagingBytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); - stagingSize = stagingBytes; - } + const auto stagingBytes = std::max(static_cast(geometry.sliceLinearBytes), static_cast(sliceBytes())) * layers; std::vector> uploads; std::vector uploadLayers; std::vector copiedLayers; @@ -1086,8 +1247,10 @@ public: continue; } surface = {}; - const GuestLayer current{resource.baseAddress + geometry.GuestLayerOffset(layer), geometry.layerBytes, resource.tileMode, geometry.thick, GuestMemory::CollectWrites(resource.baseAddress + geometry.GuestLayerOffset(layer), static_cast(geometry.layerBytes))}; - if (held.generation != 0 && held.address == current.address && held.bytes == current.bytes && held.tileMode == current.tileMode && held.thick == current.thick && !Recorder::SnapshotWriteOverlaps(current.address, static_cast(current.bytes)) && GuestMemory::UnchangedSince(current.address, static_cast(current.bytes), held.generation)) { + GuestLayer current{resource.baseAddress + geometry.GuestLayerOffset(layer), geometry.layerBytes, resource.tileMode, geometry.thick, GuestMemory::CollectWrites(resource.baseAddress + geometry.GuestLayerOffset(layer), static_cast(geometry.layerBytes))}; + current.uncommitted = current.generation == 0 && GuestMemory::CommittedRanges(current.address, static_cast(current.bytes)).empty(); + const bool unchanged = current.uncommitted ? held.uncommitted : held.generation != 0 && GuestMemory::UnchangedSince(current.address, static_cast(current.bytes), held.generation); + if (held.address == current.address && held.bytes == current.bytes && held.tileMode == current.tileMode && held.thick == current.thick && !Recorder::SnapshotWriteOverlaps(current.address, static_cast(current.bytes)) && unchanged) { held.generation = current.generation; continue; } @@ -1097,11 +1260,14 @@ public: uploadLayers.push_back(layer); held = current; } - if (!copiedLayers.empty() || !uploads.empty()) record(slices, resource, geometry, copiedLayers, uploads, uploadLayers); + if (!copiedLayers.empty() || !uploads.empty()) { + if (staging == nullptr || stagingBytes > staging->Size()) staging = std::make_shared(context, stagingBytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + record(slices, resource, geometry, copiedLayers, uploads, uploadLayers, staging); + } const std::array key{static_cast(components.r), static_cast(components.g), static_cast(components.b), static_cast(components.a), static_cast(viewType), baseLayer}; auto& texture = textures[key]; if (texture == nullptr) { - texture = std::make_shared(context, image, format, aspect(), components, viewType, baseLayer); + texture = std::make_shared(context, image, format, aspect(), components, viewType, baseLayer, owned); texture->MarkRefreshedPerUse(); } return texture; @@ -1111,6 +1277,9 @@ public: const VkExtent2D extent; const VkFormat format; const std::uint32_t layers; + VkDeviceSize imageBytes = 0; + std::uint64_t lastUse = 0; + VkDeviceSize HeldBytes() const { return imageBytes + (staging != nullptr ? staging->Size() : 0u); } private: struct GuestLayer { @@ -1119,19 +1288,19 @@ private: TextureTileMode tileMode = TextureTileMode::kLinear; bool thick = false; std::uint64_t generation = 0; + bool uncommitted = false; }; VkImage image = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; - std::unique_ptr staging; - std::size_t stagingSize = 0; - std::vector> retiredStaging; + std::shared_ptr owned; + std::shared_ptr staging; std::map, std::shared_ptr> textures; std::vector guestLayers; std::vector surfaceLayers; bool filled = false; - void record(std::span slices, const GuestTextureResource& resource, const SurfaceGeometry& geometry, std::span copiedLayers, std::span> uploads, std::span uploadLayers) { + void record(std::span slices, const GuestTextureResource& resource, const SurfaceGeometry& geometry, std::span copiedLayers, std::span> uploads, std::span uploadLayers, const std::shared_ptr& staging) { if (!uploads.empty()) { Require(context.detiler != nullptr, "depth plane copy requires a texture detiler"); context.detiler->BeginBatch(); @@ -1188,7 +1357,11 @@ private: barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral); filled = true; if (batch) batch->SubmitAndWait(); - else Recorder::CountBarriers(Recorder::CommandClass::Draw, 3); + else { + Recorder::CountBarriers(Recorder::CommandClass::Draw, 3); + recorder->Keep(staging, staging->Size()); + if (owned != nullptr) recorder->Keep(owned); + } } VkDeviceSize sliceBytes() const { @@ -1202,8 +1375,11 @@ private: void release() noexcept { textures.clear(); staging.reset(); - if (image) context.Function("vkDestroyImage")(context.device, image, nullptr); - if (memory) context.Function("vkFreeMemory")(context.device, memory, nullptr); + if (owned != nullptr) owned.reset(); + else { + if (image) context.Function("vkDestroyImage")(context.device, image, nullptr); + if (memory) context.Function("vkFreeMemory")(context.device, memory, nullptr); + } image = VK_NULL_HANDLE; memory = VK_NULL_HANDLE; } @@ -1214,6 +1390,32 @@ std::map& keep) { + static const VkDeviceSize budget = [] { + const char* text = std::getenv("APS5_DEPTH_PLANE_COPY_MIB"); + return static_cast(text != nullptr ? std::strtoull(text, nullptr, 10) : 256ull) << 20u; + }(); + if (budget == 0) return; + auto& copies = planeCopies(); + VkDeviceSize total = 0; + for (const auto& entry : copies) total += entry.second->HeldBytes(); + while (total > budget) { + auto oldest = copies.end(); + for (auto it = copies.begin(); it != copies.end(); ++it) { + if (it->first == keep) continue; + if (oldest == copies.end() || it->second->lastUse < oldest->second->lastUse) oldest = it; + } + if (oldest == copies.end()) break; + total -= oldest->second->HeldBytes(); + copies.erase(oldest); + } +} + struct LayeredSlice { DepthSurface* surface; std::uint32_t layer; @@ -1223,6 +1425,7 @@ class LayeredDepthStorage { public: LayeredDepthStorage(const Context& context, const GuestTextureResource& resource, const DepthTarget& base, std::uint32_t bytesPerTexel, std::uint32_t firstLayer, std::uint32_t layers) : context(context), base(base), extent(base.extent), bytesPerTexel(bytesPerTexel), firstLayer(firstLayer), layers(layers) { const auto bytes = DescribeSurface(resource).guestBytes; + layerPitchBytes = DescribeSurface(resource).GuestLayerOffset(1); StorageTexture::FlushPending(resource.baseAddress, static_cast(bytes), nullptr, "storage image creation", PublishScope::None); storage = StorageTexture::Create(context, *context.detiler, resource, 0); Require(storage->ImageLayers() == layers, "a layered depth storage view's image does not hold its layers"); @@ -1276,6 +1479,7 @@ public: const std::uint32_t bytesPerTexel; const std::uint32_t firstLayer; const std::uint32_t layers; + std::uint64_t layerPitchBytes = 0; std::shared_ptr storage; private: @@ -1368,6 +1572,21 @@ DepthSurface* planeSurface(VkDevice device, std::uint64_t address) { return found == list.rend() ? nullptr : found->get(); } +void settlePendingHtileClear(const Context& context, std::uint64_t address) { + if (Recorder::InCompletion()) return; + auto* recorder = RecorderFor(context); + if (recorder == nullptr) return; + std::pair metadata; + { + std::lock_guard lock(surfacesMutex()); + const auto* surface = planeSurface(context.device, address); + if (surface == nullptr || !surface->samplingActive || surface->clearGeneration == 0u || !surface->clearCompletionQueued || !surface->sampling.refusal.empty()) return; + const auto& range = surface->sampling.initial[1]; + metadata = {range.address, range.bytes}; + } + if (recorder->PendingWriteOverlaps(metadata.first, metadata.second)) recorder->SyncThrough(metadata.first, metadata.second); +} + std::map& clearedHtiles() { static auto* cleared = new std::map(); return *cleared; @@ -1604,15 +1823,36 @@ void retire(DepthSurface& surface) { for (auto* owners : {&depthOwners(), &stencilOwners()}) { std::erase_if(*owners, [&](const auto& entry) { return entry.second.surface == &surface; }); } - if (!surface.memoryStale) return; - surface.memoryStale = false; + if (!surface.AnyStale()) return; + surface.ClearStale(); const auto plane = stalePlaneRange(surface); - if (std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& other) { return other->memoryStale && stalePlaneRange(*other) == plane; })) return; + if (std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& other) { return other->AnyStale() && stalePlaneRange(*other) == plane; })) return; auto& planes = stalePlanes(); std::lock_guard lock(planes.mutex); std::erase(planes.ranges, plane); publishStaleHull(planes); } + +void retireStoringStale(DepthSurface& surface) { + if (!surface.AnyStale() || !mirrorable(surface.target)) { + retire(surface); + return; + } + const auto resource = mirrorResource(surface.target); + const auto planeBytes = DescribeSurface(resource).guestBytes; + std::uint64_t generation = 0; + const auto written = surface.WrittenUnits(resource, generation); + auto units = surface.staleUnits; + for (std::size_t unit = 0; unit < units.size() && unit < written.size(); ++unit) { + if (written[unit]) units[unit] = false; + } + std::shared_ptr mirror; + if (anyUnit(units) && (!anyUnit(written) || surface.StoresUnits(*surface.Mirror(resource)))) mirror = surface.StoreDepthPlane(resource, units); + retire(surface); + if (mirror == nullptr) return; + const auto [begin, end] = surface.UnitHull(units, planeBytes); + if (StorageTexture::FlushPending(begin, static_cast(end - begin), nullptr, "depth plane store")) surface.NoteStored(units, GuestMemory::TrackerGeneration()); +} } std::uint64_t DepthSurfacesSerial() { @@ -1646,8 +1886,28 @@ std::uint64_t MultisampledDepthBytes_nid_no_patch(VkExtent2D extent, std::uint32 void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes) { if (bytes == 0u || !anyDepthGuard().load(std::memory_order_acquire)) return; Require(address <= std::numeric_limits::max() - bytes, "native depth read range overflows"); - std::shared_lock lock(depthGuardMutex()); - for (const auto& range : depthGuards()) Require(address >= range.address + range.bytes || range.address >= address + bytes, "sample-preserving guest publication of native multisample depth is not implemented"); + const auto overlaps = [&](const DepthGuard& range) { return address < range.address + range.bytes && range.address < address + bytes; }; + { + std::shared_lock lock(depthGuardMutex()); + if (std::ranges::none_of(depthGuards(), overlaps)) return; + } + std::unique_lock lock(depthGuardMutex()); + for (const auto& range : depthGuards()) { + if (overlaps(range)) Require(!range.hostWritten->load(std::memory_order_acquire), "sample-preserving guest publication of native multisample depth is not implemented"); + } + for (const auto& range : depthGuards()) { + if (overlaps(range)) range.active->store(false, std::memory_order_release); + } + std::erase_if(depthGuards(), [](const auto& range) { return !range.active->load(std::memory_order_acquire); }); + anyDepthGuard().store(!depthGuards().empty(), std::memory_order_release); +} + +void NoteDepthSurfaceWrite(const Context& context, const DepthTarget& target) { + if (target.samples == 1u || !anyDepthGuard().load(std::memory_order_acquire)) return; + std::lock_guard lock(surfacesMutex()); + for (const auto& surface : surfaces()) { + if (surface->active->load(std::memory_order_acquire) && surface->context.device == context.device && sameSurface(surface->target, target)) surface->NoteHostWrite(); + } } bool HasResidentDepthGuards_nid_no_patch() { @@ -1693,7 +1953,7 @@ bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std VkFormat HostDepthFormat(const Context& context, VkFormat format) { if (format != VK_FORMAT_D16_UNORM_S8_UINT) return format; - static std::mutex mutex; + static AgcDriver::Mutex mutex; static std::map chosen; std::lock_guard lock(mutex); if (const auto found = chosen.find(context.physical); found != chosen.end()) return found->second; @@ -1720,6 +1980,7 @@ float HostDepthBiasConstant(const Context& context, VkFormat format, float const VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten) { const auto htileCleared = RoutesToNativeDepthOwner(target) ? VkImageAspectFlags{0} : storeBackClearedHtile(context, target); std::lock_guard gpu(GuestMemory::GpuMutex()); + settlePendingHtileClear(context, target.address); std::unique_lock lock(surfacesMutex()); const bool cleared = takeClearedHtile(target); DepthSurface* surface = nullptr; @@ -1732,20 +1993,15 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, const bool created = surface == nullptr; GuestAllocations::Lease creationLease; if (created) { - for (const auto& surface : surfaces()) { - if (!surface->active->load(std::memory_order_acquire) || !surface->samplingActive || surface->context.device != context.device) continue; - const auto bytes = target.samples == 1u ? DepthSliceBytes(target.extent, target.format == VK_FORMAT_D16_UNORM ? 2u : 4u) : MultisampledDepthBytes_nid_no_patch(target.extent, target.format == VK_FORMAT_D16_UNORM ? 2u : 4u, target.samples); - Require(target.address <= std::numeric_limits::max() - bytes && target.htileAddress <= std::numeric_limits::max() - HtileMetadataBytes(target.extent), "native depth attachment range overflows"); - for (const auto& range : surface->sampling.initial) { - Require(target.address >= range.address + range.bytes || range.address >= target.address + bytes, "native multisample depth attachment layout or metadata changed"); - if (target.stencilAddress != 0u) { - const auto stencilBytes = target.samples == 1u ? DepthSliceBytes(target.extent, 1u) : MultisampledDepthBytes_nid_no_patch(target.extent, 1u, target.samples); - Require(target.stencilAddress <= std::numeric_limits::max() - stencilBytes && (target.stencilAddress >= range.address + range.bytes || range.address >= target.stencilAddress + stencilBytes), "native multisample stencil attachment aliases a resident depth surface"); - } - if (target.htileAddress != 0u) Require(target.htileAddress >= range.address + range.bytes || range.address >= target.htileAddress + HtileMetadataBytes(target.extent), "native multisample depth attachment layout or metadata changed"); - } - } + const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; + const auto depthBytes = target.samples == 1u ? DepthSliceBytes(target.extent, d16 ? 2u : 4u) : MultisampledDepthBytes_nid_no_patch(target.extent, d16 ? 2u : 4u, target.samples); + const auto stencilBytes = target.stencilAddress == 0u ? 0u : target.samples == 1u ? DepthSliceBytes(target.extent, 1u) : MultisampledDepthBytes_nid_no_patch(target.extent, 1u, target.samples); + const auto metadataBytes = target.htileAddress == 0u ? 0u : HtileMetadataBytes(target.extent); + Require(target.address <= std::numeric_limits::max() - depthBytes && target.stencilAddress <= std::numeric_limits::max() - stencilBytes && target.htileAddress <= std::numeric_limits::max() - metadataBytes, "native depth attachment range overflows"); lock.unlock(); + RequireNoResidentDepthRead_nid_no_patch(target.address, static_cast(depthBytes)); + if (stencilBytes != 0u) RequireNoResidentDepthRead_nid_no_patch(target.stencilAddress, static_cast(stencilBytes)); + if (metadataBytes != 0u) RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, static_cast(metadataBytes)); auto sampling = target.samples == 1u ? DepthSamplingState{} : prepareDepthSampling(context, target); creationLease = target.samples == 1u || !sampling.refusal.empty() ? GuestAllocations::Lease{} : GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); lock.lock(); @@ -1756,7 +2012,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, auto& fresh = *surfaces().back(); if (target.samples != 1u && fresh.sampleable && fresh.sampling.refusal.empty()) { std::unique_lock guardLock(depthGuardMutex()); - for (const auto& range : fresh.sampling.initial) depthGuards().push_back({context.device, range.address, range.bytes, fresh.active, {{range.address, range.bytes, 0u}}}); + for (const auto& range : fresh.sampling.initial) depthGuards().push_back({context.device, range.address, range.bytes, fresh.active, {{range.address, range.bytes, 0u}}, fresh.hostWritten}); anyDepthGuard().store(true, std::memory_order_release); InstallResidentImageReadGuard_nid_no_patch(); fresh.samplingActive = true; @@ -1775,7 +2031,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, if (!created) surface->NoteHtileClear(VK_IMAGE_ASPECT_DEPTH_BIT); } if (!created) surface->NoteHtileClear(htileCleared); - if (mirrorable(target)) static_cast(surface->SyncFromMemory(mirrorResource(target))); + if (mirrorable(target)) surface->SyncFromMemory(mirrorResource(target)); surface->ApplyFastClear(); if (drawnDepth != nullptr && drawnDepth != surface) drawnDepth->written = ++depthWrites; surface->written = ++depthWrites; @@ -1787,14 +2043,15 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, if (other.get() == surface || other->context.device != context.device || other->written >= surface->written || !mirrorable(other->target)) continue; const auto otherPlane = stalePlaneRange(*other); if (otherPlane == plane || otherPlane.first >= plane.second || plane.first >= otherPlane.second) continue; - other->memoryStale = false; + other->ClearStale(); other->DropMirror(); } } if ((depthWritten || cleared) && mirrorable(target)) { - surface->memoryStale = true; const auto plane = target.address; - noteStalePlane(plane, plane + DescribeSurface(mirrorResource(target)).guestBytes); + const auto planeBytes = DescribeSurface(mirrorResource(target)).guestBytes; + surface->MarkAllStale(planeBytes); + noteStalePlane(plane, plane + planeBytes); } drawnDepth = surface; return surface->view; @@ -1831,52 +2088,84 @@ bool DepthPlanesStale() { return planes.begin.load(std::memory_order_acquire) < planes.end.load(std::memory_order_acquire); } -std::vector> FlushDepthPlanes(std::uint64_t address, std::size_t bytes) { +std::vector> FlushDepthPlanes(std::uint64_t address, std::size_t bytes, DepthPlaneScope scope) { static thread_local bool flushing = false; const auto end = bytes > std::numeric_limits::max() - address ? std::numeric_limits::max() : address + bytes; if (flushing || !overlapsStalePlane(address, end)) return {}; + if (scope == DepthPlaneScope::Overlapped) { + std::lock_guard lock(surfacesMutex()); + const auto staleInRange = [&](const auto& surface) { return surface->AnyStale() && !surface->syncing && ((surface->pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 || anyUnit(surface->StaleUnitsIn(DescribeSurface(mirrorResource(surface->target)).guestBytes, address, end))); }; + if (std::none_of(surfaces().begin(), surfaces().end(), staleInRange)) return {}; + } flushing = true; struct Reset { ~Reset() { flushing = false; } } reset; - std::vector>> stored; + struct Stored { + DepthSurface* surface; + std::shared_ptr mirror; + std::vector units; + std::uint64_t begin; + std::uint64_t end; + bool flushed; + }; + std::vector stored; std::vector> ranges; { std::lock_guard gpu(GuestMemory::GpuMutex()); std::lock_guard lock(surfacesMutex()); std::vector> remaining; for (const auto& surface : surfaces()) { - if (!surface->memoryStale) continue; + if (!surface->AnyStale()) continue; const auto resource = mirrorResource(surface->target); const auto plane = surface->target.address; const auto planeBytes = DescribeSurface(resource).guestBytes; - if (plane >= end || address >= plane + planeBytes) { - remaining.emplace_back(plane, plane + planeBytes); - continue; + const bool overlapped = !surface->syncing && plane < end && address < plane + planeBytes; + if (overlapped && (surface->pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) surface->MarkAllStale(planeBytes); + if (overlapped && (scope == DepthPlaneScope::Whole || anyUnit(surface->StaleUnitsIn(planeBytes, address, end)))) { + if (GuestMemory::CommittedRanges(plane, static_cast(planeBytes)).empty()) { + static std::atomic reported{false}; + if (!reported.exchange(true)) std::fprintf(stderr, "[gpu] depth plane 0x%llx+0x%llx was unmapped with GPU results pending; they are dropped\n", static_cast(plane), static_cast(planeBytes)); + surface->ClearStale(); + continue; + } + surface->SyncFromMemory(resource); + auto units = scope == DepthPlaneScope::Whole ? surface->staleUnits : surface->StaleUnitsIn(planeBytes, address, end); + if (anyUnit(units)) { + auto mirror = surface->StoreDepthPlane(resource, units); + surface->ClearStale(units); + const auto hull = surface->UnitHull(units, planeBytes); + ranges.emplace_back(hull.first, hull.second - hull.first); + stored.push_back({surface.get(), std::move(mirror), std::move(units), hull.first, hull.second, false}); + } } - surface->memoryStale = false; - if (surface->SyncFromMemory(resource)) continue; - stored.emplace_back(surface.get(), surface->StoreDepthPlane(resource)); - ranges.emplace_back(plane, planeBytes); + if (surface->AnyStale()) remaining.emplace_back(plane, plane + planeBytes); } auto& planes = stalePlanes(); std::lock_guard staleLock(planes.mutex); planes.ranges = std::move(remaining); publishStaleHull(planes); } - for (const auto& entry : stored) entry.second->MarkWholeWritten(); - for (const auto& [plane, planeBytes] : ranges) StorageTexture::FlushPending(plane, static_cast(planeBytes), nullptr, "depth plane store"); + for (auto& entry : stored) entry.flushed = StorageTexture::FlushPending(entry.begin, static_cast(entry.end - entry.begin), nullptr, "depth plane store"); const auto generation = GuestMemory::TrackerGeneration(); std::lock_guard lock(surfacesMutex()); for (const auto& entry : stored) { - if (std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& candidate) { return candidate.get() == entry.first; })) { - entry.first->syncedGeneration = generation; - entry.first->NoteMemory(); - } + if (!entry.flushed || std::none_of(surfaces().begin(), surfaces().end(), [&](const auto& candidate) { return candidate.get() == entry.surface; })) continue; + entry.surface->NoteStored(entry.units, generation); + entry.surface->NoteMemory(); } return ranges; } +DepthPlaneStoreStatistics DepthPlaneStoreCounts() { + const auto& counters = depthPlaneStoreCounters(); + DepthPlaneStoreStatistics statistics; + statistics.stores = counters.stores.load(); + statistics.units = counters.units.load(); + statistics.bytes = counters.bytes.load(); + return statistics; +} + std::uint64_t HtileDepthClearAddress(std::span code, std::span userData, const std::array& numThreads) { static constexpr std::array htileMaskKernel{0xd7460000u, 0x04010c06u, 0x34000084u, 0xdc388000u, 0x04020000u, 0xbf8c3f70u, 0xd7710008u, 0x00120805u, 0xd7710009u, 0x00120a05u, 0xd771000au, 0x00120c05u, 0xd771000bu, 0x00120e05u, 0xdc788000u, 0x00020800u, 0xbf810000u}; if (userData.size() < 6 || numThreads[0] != 64 || numThreads[1] != 1 || numThreads[2] != 1) return 0; @@ -1977,17 +2266,22 @@ std::size_t HtileDepthClearBytes_nid_no_patch(std::span cod return address <= std::numeric_limits::max() - bytes ? static_cast(bytes) : 0u; } -void NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::size_t nativeBytes) { +bool NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::size_t nativeBytes) { std::lock_guard lock(surfacesMutex()); forgetHtileReads(htileAddress, bytes); bool existing = false; + bool completionCandidate = false; for (const auto& surface : surfaces()) { if (!surface->active->load(std::memory_order_acquire)) continue; if (surface->samplingActive && surface->target.htileAddress == htileAddress) { const auto& metadata = surface->sampling.initial[1]; if (nativeBytes != bytes || nativeBytes < metadata.bytes || !GuestMemory::Accessible(reinterpret_cast(metadata.address), metadata.bytes) || !GuestMemory::Watched(metadata.address, metadata.bytes)) surface->sampling.Refuse("native multisample depth incomplete or unproven fast-clear transition is not implemented"); - else if (surface->clearGeneration != 0u) surface->sampling.Refuse("overlapping native multisample depth clear transitions are not implemented"); - else surface->clearGeneration = GuestMemory::CollectWritesUncached(metadata.address, metadata.bytes); + else if (surface->clearPending || surface->clearGeneration != 0u) surface->sampling.Refuse("overlapping native multisample depth clear transitions are not implemented"); + else { + surface->clearPending = true; + surface->clearCompletionQueued = false; + completionCandidate = true; + } existing = true; continue; } @@ -1997,6 +2291,39 @@ void NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::siz existing = true; } if (!existing) clearedHtiles()[htileAddress] = bytes; + return completionCandidate; +} + +void ArmHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) { + std::lock_guard lock(surfacesMutex()); + for (const auto& surface : surfaces()) { + if (!surface->active->load(std::memory_order_acquire) || !surface->samplingActive || surface->target.htileAddress != htileAddress || !surface->clearPending) continue; + const auto& metadata = surface->sampling.initial[1]; + if (bytes < metadata.bytes) continue; + surface->clearGeneration = GuestMemory::CollectWritesUncached(metadata.address, metadata.bytes); + surface->clearPending = false; + if (surface->clearGeneration == 0u) surface->sampling.Refuse("native multisample depth clear requires tracked HTILE generations"); + } +} + +void QueueHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) { + std::lock_guard lock(surfacesMutex()); + for (const auto& surface : surfaces()) { + if (!surface->active->load(std::memory_order_acquire) || !surface->samplingActive || surface->target.htileAddress != htileAddress || surface->clearGeneration == 0u || !surface->sampling.refusal.empty()) continue; + if (bytes >= surface->sampling.initial[1].bytes) surface->clearCompletionQueued = true; + } +} + +void CompleteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) { + std::lock_guard lock(surfacesMutex()); + for (const auto& surface : surfaces()) { + if (!surface->active->load(std::memory_order_acquire) || !surface->samplingActive || surface->target.htileAddress != htileAddress || surface->clearGeneration == 0u) continue; + const auto& metadata = surface->sampling.initial[1]; + if (bytes < metadata.bytes) continue; + Require(GuestMemory::UnchangedSinceCollected(metadata.address, metadata.bytes, surface->clearGeneration), "CPU writes raced native multisample depth clear"); + GuestMemory::MarkWritten(metadata.address, metadata.bytes); + surface->clearCompletionQueued = false; + } } void ClearDepthSurfaces(VkDevice device) { @@ -2015,6 +2342,16 @@ void ClearDepthSurfaces(VkDevice device) { UpdateResidentImageUnmapObserver_nid_no_patch(); } +std::size_t DepthPlaneCopyCount() { + return planeCopies().size(); +} + +namespace { + +thread_local bool depthCopySourceActive = false; + +} + std::shared_ptr DepthSurfaceTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType multisampleViewType) { const auto held = [&](const auto& surface) { return surface->active->load(std::memory_order_acquire) && surface->context.device == context.device && !surface->retired && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress)); @@ -2024,6 +2361,7 @@ std::shared_ptr DepthSurfaceTexture(const Context& context, std::span DepthSurfaceTexture(const Context& context, std::span 1u, "multisample reader overlaps a single-sample depth attachment"); auto* recorder = RecorderFor(context); Require(recorder != nullptr, "a native multisampled depth target is sampled only by recorded draws and dispatches"); - found->CheckCurrent(); - found->CheckResource(resource); - Require(found->pendingClear == 0u, "native multisample depth fast-clear transition is not implemented"); + if (depthCopySourceActive) { + found->CheckResource(resource); + found->ApplyFastClear(); + } else { + found->CheckCurrent(); + found->CheckResource(resource); + Require(found->pendingClear == 0u, "native multisample depth fast-clear transition is not implemented"); + } RecordMemoryBarrier(context, recorder->Commands(), VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); Recorder::CountBarriers(Recorder::CommandClass::Draw, 1); - return found->Sampled(words, resource, components, found == drawnDepth, multisampleViewType); + return found->Sampled(words, resource, components, found == drawnDepth, multisampleViewType, depthCopySourceActive); } if (resource.width != base.extent.width || resource.height != base.extent.height || resource.baseLevel != 0) return nullptr; const bool d16 = base.format == VK_FORMAT_D16_UNORM || base.format == VK_FORMAT_D16_UNORM_S8_UINT; @@ -2053,7 +2396,7 @@ std::shared_ptr DepthSurfaceTexture(const Context& context, std::span resource.depthOrLastArray)) && !(cube && resource.depthOrLastArray != 0 && resource.depthOrLastArray != 5) && (resource.dimension == TextureDimension::k2D || resource.dimension == TextureDimension::k2DArray || cube); if (!(sampled ? found->SampledAccepts(words, resource) : planes) && found->OverwrittenInMemory()) { - retire(*found); + retireStoringStale(*found); return nullptr; } found->ApplyFastClear(); @@ -2075,21 +2418,25 @@ std::shared_ptr DepthSurfaceTexture(const Context& context, std::spanimage, slice == drawnDepth ? 0u : slice->written}); } const auto imageFormat = integer ? VK_FORMAT_R16_UINT : d16 ? VK_FORMAT_D16_UNORM : VK_FORMAT_D32_SFLOAT; - auto& copy = planeCopies()[{context.device, base.address, base.extent.width, base.extent.height, imageFormat, layers}]; - if (copy == nullptr) { - copy = std::make_unique(context, base.extent, imageFormat, layers); - } - return 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); + auto& copies = planeCopies(); + const std::tuple copyKey{context.device, base.address, base.extent.width, base.extent.height, imageFormat, layers}; + auto& copy = copies[copyKey]; + if (copy == nullptr) copy = std::make_unique(context, base.extent, imageFormat, layers); + copy->lastUse = ++planeCopyClock(); + 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; } bool DepthSurfaceHolds(const Context& context, const GuestTextureResource& resource, const Texture* texture) { if ((resource.dimension != TextureDimension::k2D && resource.dimension != TextureDimension::k2DMsaa) || resource.baseArray != 0) return false; std::lock_guard gpu(GuestMemory::GpuMutex()); const auto address = resource.baseAddress; + settlePendingHtileClear(context, address); std::lock_guard lock(surfacesMutex()); auto* found = planeSurface(context.device, address); if (found == nullptr || !found->Holds(texture)) return false; - found->CheckCurrent(); + if (!depthCopySourceActive) found->CheckCurrent(); found->CheckResource(resource); return true; } @@ -2185,7 +2532,7 @@ std::vector layeredSlices(const Context& context, const LayeredDep std::vector slices; const auto& list = surfaces(); for (std::uint32_t layer = storage.firstLayer; layer < storage.layers; ++layer) { - const auto address = base.address + static_cast(layer) * DepthSliceBytes(base.extent, storage.bytesPerTexel); + const auto address = base.address + static_cast(layer) * storage.layerPitchBytes; const auto slice = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { return surface->context.device == context.device && !surface->retired && surface->target.address == address && surface->target.extent.width == base.extent.width && surface->target.extent.height == base.extent.height && surface->target.format == base.format && surface->target.samples == 1; }); if (slice != list.rend()) slices.push_back({slice->get(), layer}); } @@ -2198,12 +2545,6 @@ std::shared_ptr layeredDepthStorage(const Context& context, Dept const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; const auto bytesPerTexel = d16 ? 2u : 4u; surface.RequireStorable(resource); - const auto geometry = DescribeSurface(resource); - if (layers > 1 && geometry.GuestLayerOffset(1) != DepthSliceBytes(target.extent, bytesPerTexel)) { - char text[256]; - std::snprintf(text, sizeof(text), "AGC graphics: storage image access to the depth planes of depth surface 0x%llx with a layer pitch of 0x%llx bytes, not the depth slice size 0x%llx, is not implemented", static_cast(target.address), static_cast(geometry.GuestLayerOffset(1)), static_cast(DepthSliceBytes(target.extent, bytesPerTexel))); - throw std::runtime_error(text); - } auto& entry = layeredStorages()[{context.device, resource.baseAddress, resource.width, resource.height, layers, resource.format, static_cast(resource.tileMode), static_cast(resource.dimension), resource.baseArray}]; if (entry == nullptr) entry = std::make_unique(context, resource, target, bytesPerTexel, resource.baseArray, layers); else static_cast(entry->storage->Refresh()); @@ -2230,15 +2571,23 @@ std::shared_ptr DepthSurfaceStorage(const Context& context, std: return layeredDepthStorage(context, surface, resource, mip); } -void CopyDepthSample(const Context& context, std::uint64_t depthAddress, std::uint32_t samples, std::uint32_t sample, StorageTexture& destination, VkImageView destinationView, VkExtent2D extent) { - std::lock_guard lock(surfacesMutex()); - auto* found = planeSurface(context.device, depthAddress); - Require(found != nullptr && found->target.address == depthAddress, "DB depth copy from a depth plane no draw rendered"); - Require(found->target.format == VK_FORMAT_D32_SFLOAT || found->target.format == VK_FORMAT_D32_SFLOAT_S8_UINT, "DB depth copy from a depth surface that is not 32-bit float"); - Require(found->target.samples == samples, "DB depth copy from a depth surface of another sample count"); - Require(found->target.extent.width == extent.width && found->target.extent.height == extent.height, "DB depth copy from a depth surface of another size"); - Require(sample < found->target.samples, "DB depth copy of a sample the depth surface does not have"); - found->CopySample(sample, destination, destinationView); +DepthCopySourceScope::DepthCopySourceScope(bool active) : previous(depthCopySourceActive) { + depthCopySourceActive = active; +} + +DepthCopySourceScope::~DepthCopySourceScope() { + depthCopySourceActive = previous; +} + +std::array DepthCopySourceWords(const DepthTarget& target) { + const bool multisampled = target.samples != 1u; + const auto base = (target.address | target.pipeBankXor) >> 8u; + const auto metadata = multisampled ? target.htileAddress >> 8u : 0u; + const auto count = static_cast(std::countr_zero(target.samples)); + const auto width = target.extent.width - 1u, height = target.extent.height - 1u; + const auto type = multisampled ? 14u : 9u; + const auto word6 = (multisampled && target.iterate256 ? 1u << 10u : 0u) | (multisampled && target.htilePipeAligned ? 1u << 19u : 0u) | (metadata != 0 ? 1u << 21u : 0u) | (static_cast(metadata & 255u) << 24u); + return {static_cast(base), static_cast((base >> 32u) & 255u) | (22u << 20u) | ((width & 3u) << 30u), (width >> 2u) | (height << 14u), (type << 28u) | (target.depthSwizzle << 20u) | (count << 16u) | 0x204u, 0u, count << 4u, word6, static_cast(metadata >> 8u)}; } bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage) { @@ -2247,7 +2596,10 @@ bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage if (std::get<0>(key) != context.device || entry->storage.get() != &storage) continue; const auto slices = layeredSlices(context, *entry); entry->Copy(slices, false); - for (const auto& slice : slices) slice.surface->written = ++depthWrites; + for (const auto& slice : slices) { + slice.surface->written = ++depthWrites; + slice.surface->NoteHostWrite(); + } return true; } for (const auto& surface : surfaces()) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 8e0ecd6ea6..f3dd7f8885 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -15,11 +15,15 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" +#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "ThreadOwned.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "Optimization/include/Optimization/ShaderStageInputInfo.hpp" +#include "SpirvBackend/SpirvAnalysis.hpp" #include "ThreadOwned.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" #include #include #include @@ -40,6 +44,184 @@ namespace AgcDriver::Graphics { +namespace { + +struct PixelFunctionLdsPool { + Context context{}; + VkBuffer buffer = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + VkDescriptorSet set = VK_NULL_HANDLE; + VkDescriptorSetLayout empty = VK_NULL_HANDLE; + VkDescriptorSetLayout bufferLayout = VK_NULL_HANDLE; + std::uint32_t* header = nullptr; + std::uint32_t subgroupSize = 0; + std::uint32_t slots = 0; + bool announced = false; +}; + +std::mutex& PixelLdsMutex() { + static std::mutex mutex; + return mutex; +} + +std::unordered_map& PixelLdsPools() { + static std::unordered_map pools; + return pools; +} + +void DestroyPixelFunctionLds(PixelFunctionLdsPool& pool) { + if (pool.header != nullptr) pool.context.Function("vkUnmapMemory")(pool.context.device, pool.memory); + if (pool.descriptorPool != VK_NULL_HANDLE) pool.context.Function("vkDestroyDescriptorPool")(pool.context.device, pool.descriptorPool, nullptr); + if (pool.buffer != VK_NULL_HANDLE) pool.context.Function("vkDestroyBuffer")(pool.context.device, pool.buffer, nullptr); + if (pool.memory != VK_NULL_HANDLE) pool.context.Function("vkFreeMemory")(pool.context.device, pool.memory, nullptr); + if (pool.bufferLayout != VK_NULL_HANDLE) pool.context.Function("vkDestroyDescriptorSetLayout")(pool.context.device, pool.bufferLayout, nullptr); + if (pool.empty != VK_NULL_HANDLE) pool.context.Function("vkDestroyDescriptorSetLayout")(pool.context.device, pool.empty, nullptr); + pool.header = nullptr; + pool.descriptorPool = VK_NULL_HANDLE; + pool.set = VK_NULL_HANDLE; + pool.buffer = VK_NULL_HANDLE; + pool.memory = VK_NULL_HANDLE; + pool.bufferLayout = VK_NULL_HANDLE; + pool.empty = VK_NULL_HANDLE; +} + +} + +PixelFunctionLdsLayouts PixelFunctionLdsSetLayouts(const Context& context) { + std::lock_guard lock(PixelLdsMutex()); + auto& pool = PixelLdsPools()[context.device]; + if (pool.bufferLayout == VK_NULL_HANDLE) { + pool.context = context; + VkDescriptorSetLayoutCreateInfo emptyInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + Check(context.Function("vkCreateDescriptorSetLayout")(context.device, &emptyInfo, nullptr, &pool.empty), "vkCreateDescriptorSetLayout pixel function LDS empty"); + const VkDescriptorSetLayoutBinding binding{0u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}; + VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + info.bindingCount = 1u; + info.pBindings = &binding; + Check(context.Function("vkCreateDescriptorSetLayout")(context.device, &info, nullptr, &pool.bufferLayout), "vkCreateDescriptorSetLayout pixel function LDS"); + } + return {pool.empty, pool.bufferLayout}; +} + +void PreparePixelFunctionLds(const Context& context, VkCommandBuffer commands, std::uint32_t subgroupSize) { + const auto operations = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_BALLOT_BIT; + Require((context.subgroup.supportedStages & VK_SHADER_STAGE_FRAGMENT_BIT) != 0 && (context.subgroup.supportedOperations & operations) == operations, "pixel function LDS needs fragment subgroup elect, ballot and broadcast"); + Require(subgroupSize != 0u && subgroupSize == context.subgroup.subgroupSize, "pixel function LDS subgroup size does not match the device"); + std::lock_guard lock(PixelLdsMutex()); + auto& pool = PixelLdsPools()[context.device]; + Require(pool.bufferLayout != VK_NULL_HANDLE, "pixel function LDS descriptor set layout was not created"); + if (pool.buffer == VK_NULL_HANDLE) { + const auto stride = static_cast(subgroupSize) * ShaderRecompiler::FunctionLdsDwordLimit * 4ull; + const auto budget = std::min(context.limits.maxStorageBufferRange, 2ull << 30); + Require(budget > 16ull + stride, "pixel function LDS does not fit in the device storage buffer range"); + pool.slots = static_cast((budget - 16ull) / stride); + pool.subgroupSize = subgroupSize; + const auto bytes = 16ull + stride * pool.slots; + VkBufferCreateInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bufferInfo.size = bytes; + bufferInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + Check(context.Function("vkCreateBuffer")(context.device, &bufferInfo, nullptr, &pool.buffer), "vkCreateBuffer pixel function LDS"); + VkMemoryRequirements requirements{}; + context.Function("vkGetBufferMemoryRequirements")(context.device, pool.buffer, &requirements); + VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocation.allocationSize = requirements.size; + constexpr auto hostCoherent = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + constexpr auto deviceLocalHost = hostCoherent | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + std::optional deviceLocalType; + std::optional hostOnlyType; + for (std::uint32_t i = 0; i < context.memory.memoryTypeCount; ++i) { + if ((requirements.memoryTypeBits & (1u << i)) == 0) continue; + const auto flags = context.memory.memoryTypes[i].propertyFlags; + const auto heap = context.memory.memoryTypes[i].heapIndex; + if (!deviceLocalType && (flags & deviceLocalHost) == deviceLocalHost && context.memory.memoryHeaps[heap].size >= requirements.size) { + deviceLocalType = i; + } + if (!hostOnlyType && (flags & hostCoherent) == hostCoherent && (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == 0) { + hostOnlyType = i; + } + } + const auto hostType = hostOnlyType.value_or(context.MemoryType(requirements.memoryTypeBits, hostCoherent)); + allocation.memoryTypeIndex = deviceLocalType.value_or(hostType); + auto allocated = context.Function("vkAllocateMemory")(context.device, &allocation, nullptr, &pool.memory); + if (allocated != VK_SUCCESS && deviceLocalType && allocation.memoryTypeIndex == *deviceLocalType && hostType != *deviceLocalType) { + allocation.memoryTypeIndex = hostType; + allocated = context.Function("vkAllocateMemory")(context.device, &allocation, nullptr, &pool.memory); + } + if (allocated != VK_SUCCESS) { + context.Function("vkDestroyBuffer")(context.device, pool.buffer, nullptr); + pool.buffer = VK_NULL_HANDLE; + pool.memory = VK_NULL_HANDLE; + Check(allocated, "vkAllocateMemory pixel function LDS"); + } + Check(context.Function("vkBindBufferMemory")(context.device, pool.buffer, pool.memory, 0), "vkBindBufferMemory pixel function LDS"); + Check(context.Function("vkMapMemory")(context.device, pool.memory, 0, 16, 0, reinterpret_cast(&pool.header)), "vkMapMemory pixel function LDS"); + pool.header[0] = 0u; + pool.header[1] = 0u; + pool.header[2] = pool.slots; + const VkDescriptorPoolSize size{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u}; + VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + poolInfo.maxSets = 1u; + poolInfo.poolSizeCount = 1u; + poolInfo.pPoolSizes = &size; + Check(context.Function("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &pool.descriptorPool), "vkCreateDescriptorPool pixel function LDS"); + VkDescriptorSetAllocateInfo sets{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + sets.descriptorPool = pool.descriptorPool; + sets.descriptorSetCount = 1u; + sets.pSetLayouts = &pool.bufferLayout; + Check(context.Function("vkAllocateDescriptorSets")(context.device, &sets, &pool.set), "vkAllocateDescriptorSets pixel function LDS"); + const VkDescriptorBufferInfo buffer{pool.buffer, 0, bytes}; + VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + write.dstSet = pool.set; + write.dstBinding = 0u; + write.descriptorCount = 1u; + write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + write.pBufferInfo = &buffer; + context.Function("vkUpdateDescriptorSets")(context.device, 1u, &write, 0u, nullptr); + std::fprintf(stderr, "[gpu] pixel function LDS pool: %u subgroups, %llu MiB, stride %u lanes\n", pool.slots, static_cast(bytes >> 20u), subgroupSize); + pool.announced = true; + } + Require(pool.subgroupSize == subgroupSize, "pixel function LDS subgroup size does not match the pool"); + const VkMemoryBarrier beforeFill{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT}; + context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &beforeFill, 0, nullptr, 0, nullptr); + context.Resolved(&DeviceFunctions::cmdFillBuffer, "vkCmdFillBuffer")(commands, pool.buffer, 0, 4, 0u); + const VkMemoryBarrier afterFill{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT}; + context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 1, &afterFill, 0, nullptr, 0, nullptr); +} + +std::uint32_t PixelFunctionLdsSubgroup(std::span shaders) { + std::uint32_t subgroup = 0u; + for (const auto& shader : shaders) { + if (shader.program != nullptr && shader.program->functionLdsBuffer) subgroup = shader.program->hostSubgroupSize; + } + return subgroup; +} + +void BindPixelFunctionLds(const Context& context, VkCommandBuffer commands, VkPipelineLayout layout) { + std::lock_guard lock(PixelLdsMutex()); + const auto& pool = PixelLdsPools().at(context.device); + Require(pool.set != VK_NULL_HANDLE, "pixel function LDS buffer was not prepared"); + context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, ShaderRecompiler::PixelFunctionLdsDescriptorSet, 1, &pool.set, 0, nullptr); +} + +void CheckPixelFunctionLds(const Context& context) { + std::lock_guard lock(PixelLdsMutex()); + const auto found = PixelLdsPools().find(context.device); + if (found == PixelLdsPools().end() || found->second.header == nullptr) return; + const auto exhausted = found->second.header[1] != 0u; + found->second.header[1] = 0u; + if (exhausted) throw std::runtime_error("AGC graphics: pixel function LDS subgroup slots exhausted"); +} + +void ReleasePixelFunctionLds(VkDevice device) { + std::lock_guard lock(PixelLdsMutex()); + const auto found = PixelLdsPools().find(device); + if (found == PixelLdsPools().end()) return; + DestroyPixelFunctionLds(found->second); + PixelLdsPools().erase(found); +} + std::uint64_t DrawRenderPassKey(const Context& context, const State& state, std::span targetViews) { std::uint64_t key = 14695981039346656037ull; const auto mix = [&](std::uint64_t value) { @@ -51,10 +233,14 @@ std::uint64_t DrawRenderPassKey(const Context& context, const State& state, std: mix(state.blends.size()); for (const auto& color : state.colors) mix(color.exportIndex); } + for (const auto& color : state.colors) { + if (color.partialWriteMask != 0u) mix(0x6d65726765000000ull | color.exportIndex); + } mix(state.renderExtent.width); mix(state.renderExtent.height); mix(state.samples); mix(state.mergedCoverage); + mix(state.truncatedCoverage); mix(state.customSampleLocations); if (state.customSampleLocations) { mix(state.sampleLocationGrid); @@ -81,6 +267,10 @@ GuestTextureResource ArraySurfaceForTarget(const ColorTarget& color) { namespace { +bool writesDepthTarget(const State& state) { + return state.depthWrite || (state.stencilTest && (state.stencilFront.writeMask != 0u || state.stencilBack.writeMask != 0u)); +} + std::size_t colorKeyCount(const ColorTarget& color, std::size_t bytes) { if (color.mipCount != 1 || color.depth != 1 || !color.dccPipeAligned) return DccKeyBytes(bytes); return DccKeyCount(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, bytes); @@ -172,8 +362,8 @@ struct CmaskProof { std::uint64_t generation = 0; }; -std::mutex& cmaskProofMutex() { - static std::mutex mutex; +AgcDriver::Mutex& cmaskProofMutex() { + static AgcDriver::Mutex mutex; return mutex; } @@ -219,6 +409,19 @@ CmaskState readCmask(const ColorTarget& color, const CmaskLayout& layout, std::v std::vector mask(bytes); GuestMemory::Read(address, mask, CmaskLayout::Alignment); const auto uniform = [&](std::byte value) { return std::all_of(mask.begin(), mask.end(), [&](std::byte entry) { return entry == value; }); }; + if (color.coverageSamples > color.samples) { + for (std::uint32_t tileY = 0; tileY < layout.TilesY(); ++tileY) { + for (std::uint32_t tileX = 0; tileX < layout.TilesX(); ++tileX) { + const auto nibble = layout.Nibble(tileX, tileY); + const auto code = (std::to_integer(mask[nibble / 2u]) >> ((nibble % 2u) * 4u)) & 0xfu; + if (code != 0u && (code >> 2u) != 3u) { + char text[128]; + std::snprintf(text, sizeof(text), "AGC graphics: CMASK code 0x%x of a one-fragment color target is not implemented", code); + throw std::runtime_error(text); + } + } + } + } if (uniform(std::byte{0xff})) return CmaskState::Expanded; if (uniform(std::byte{0})) return CmaskState::Cleared; for (std::uint32_t tileY = 0; tileY < layout.TilesY(); ++tileY) { @@ -275,6 +478,10 @@ bool materializeCmaskClear(const Context& context, const ColorTarget& color, con const auto bytes = layout.Bytes(); Require(color.cmaskBytes == bytes, "CMASK size differs from the color target's CMASK layout"); if (cmaskProved(color.cmaskAddress, bytes)) return false; + if (color.coverageSamples > color.samples) { + std::vector codes; + static_cast(readCmask(color, layout, codes)); + } auto* recorder = Recorder::Active(); if (resident != nullptr && context.detiler != nullptr && recorder != nullptr && GuestMemory::GpuMutex().HeldByThisThread()) { const auto* import = HostImportFor(context, color.cmaskAddress, bytes); @@ -347,7 +554,7 @@ constexpr std::array DrawPhaseNames{"validate", "vertex enum SyncReason : std::size_t { SyncNone, SyncNotResident, SyncNoRecorder, SyncDisabled, SyncCopiedWrites, SyncLease, SyncCount }; constexpr std::array SyncReasonNames{"none", "non-resident target", "no recorder", "disabled", "copied writes", "lease"}; constexpr std::size_t IndirectPathCount = static_cast(IndirectDrawPath::Count); -constexpr std::array IndirectDrawPathNames{"gpu-side", "patched SGPR not folded", "fetch offset unknown", "non-vertex path", "GE_INDX_OFFSET", "draw index", "vertex range too large", "feature gap", "pending image results", "pending label or copied write", "not imported", "disabled", "index range not fully readable"}; +constexpr std::array IndirectDrawPathNames{"gpu-side", "patched SGPR not folded", "fetch offset unknown", "non-vertex path", "GE_INDX_OFFSET", "draw index", "vertex range too large", "feature gap", "pending image results", "pending label or copied write", "not imported", "disabled", "index range not fully readable", "index limit unknown"}; // What became of one draw, for the totals. struct DrawOutcome { @@ -399,7 +606,7 @@ enum DrawKind : std::size_t { KindRecipeHit, KindTemplateHit, KindBuild, KindBda constexpr std::array DrawKindNames{"recipe hit", "template hit", "build", "BDA"}; struct DrawProfile { - std::mutex mutex; + AgcDriver::Mutex mutex; std::array kindUs{}; std::array kindCounts{}; std::uint64_t fullScissorLookups = 0; @@ -461,6 +668,7 @@ struct DrawProfile { // with a constant, and the time of the CPU record reads (their syncs). std::array indirect{}; std::uint64_t indirectRewritten = 0; + std::uint64_t indirectEmpty = 0; double indirectReadUs = 0; // Draw packets that drew nothing, by DrawSkip, and their time. std::array(DrawSkip::Count)> skips{}; @@ -558,12 +766,12 @@ void reportDraw(const std::array& us, const ShaderResources: if (room()) n += std::snprintf(line + n, sizeof(line) - static_cast(n), "; hook waits inside draws %.1f ms (max %.1f; the draws' own syncs, %.1f ms, not counted)", profile.hookWaitUs / 1000.0, profile.maxHookWaitUs / 1000.0, profile.ownSyncUs / 1000.0); std::uint64_t indirectCpu = 0; for (std::size_t i = 1; i < IndirectPathCount; ++i) indirectCpu += profile.indirect[i]; - if ((profile.indirect[0] != 0 || indirectCpu != 0) && room()) { + if ((profile.indirect[0] != 0 || indirectCpu != 0 || profile.indirectEmpty != 0) && room()) { n += std::snprintf(line + n, sizeof(line) - static_cast(n), "; indirect: gpu-side %llu (%llu rewritten), cpu-side %llu (", static_cast(profile.indirect[0]), static_cast(profile.indirectRewritten), static_cast(indirectCpu)); for (std::size_t i = 1; i < IndirectPathCount && room(); ++i) { if (profile.indirect[i] != 0) n += std::snprintf(line + n, sizeof(line) - static_cast(n), " %s %llu", IndirectDrawPathNames[i], static_cast(profile.indirect[i])); } - if (room()) n += std::snprintf(line + n, sizeof(line) - static_cast(n), "), argument reads %.1f ms", profile.indirectReadUs / 1000.0); + if (room()) n += std::snprintf(line + n, sizeof(line) - static_cast(n), "), empty skipped before their stages %llu, argument reads %.1f ms", static_cast(profile.indirectEmpty), profile.indirectReadUs / 1000.0); } if (room()) n += std::snprintf(line + n, sizeof(line) - static_cast(n), "; skipped packets:"); for (std::size_t i = 0; i < profile.skips.size() && room(); ++i) { @@ -573,8 +781,8 @@ void reportDraw(const std::array& us, const ShaderResources: for (std::size_t i = 1; i < profile.recipeMisses.size() && room(); ++i) { n += std::snprintf(line + n, sizeof(line) - static_cast(n), " %s %llu", DrawRecipeMissName(static_cast(i)), static_cast(profile.recipeMisses[i])); } - std::fprintf(stderr, "%s\n", line); - std::fprintf(stderr, "[rescache] draws: %llu hits, %llu misses, %llu invalidated, %llu uncacheable; validation memo %llu hits / %llu misses (which key words the misses differ in: the miss churn line)\n", static_cast(profile.cacheHits), static_cast(profile.cacheMisses), static_cast(profile.cacheInvalidated), static_cast(profile.uncacheable), static_cast(profile.validateHits), static_cast(profile.validateMisses)); + AgcDriver::ProfilePrint_nid_no_patch("%s\n", line); + AgcDriver::ProfilePrint_nid_no_patch("[rescache] draws: %llu hits, %llu misses, %llu invalidated, %llu uncacheable; validation memo %llu hits / %llu misses (which key words the misses differ in: the miss churn line)\n", static_cast(profile.cacheHits), static_cast(profile.cacheMisses), static_cast(profile.cacheInvalidated), static_cast(profile.uncacheable), static_cast(profile.validateHits), static_cast(profile.validateMisses)); profile.totalsUs.fill(0); profile.maxUs.fill(0); profile.maxDrawUs = profile.hookWaitUs = profile.maxHookWaitUs = profile.ownSyncUs = 0; @@ -666,6 +874,7 @@ bool ValidationKey(const Context& context, std::span shade add(context.viewportIndexLayer); add(context.shaderTerminateInvocation); add(state.paClVsOutCntl); + add(state.vertexPoints); add(state.stages.mesh.has_value()); if (state.stages.mesh) { const auto& mesh = *state.stages.mesh; @@ -695,8 +904,8 @@ bool ValidationKey(const Context& context, std::span shade return keyed; } -std::mutex& validationMutex() { - static std::mutex mutex; +AgcDriver::Mutex& validationMutex() { + static AgcDriver::Mutex mutex; return mutex; } @@ -718,11 +927,26 @@ std::uint32_t CachedFragmentOutputs(const Context& context, std::span, std::uint32_t> memo; + struct LastKeyStorage {}; + auto& lastKey = HostThreadLocal, LastKeyStorage>(); + thread_local std::uint32_t lastOutputs = 0; + thread_local bool lastValid = false; + const bool trims = keyed && CommitTrims(); + if (trims && lastValid && key == lastKey) { + memoized = true; + hit = true; + return lastOutputs; + } if (keyed) { memoized = true; std::lock_guard lock(validationMutex()); if (const auto found = memo.find(key); found != memo.end()) { hit = true; + if (trims) { + lastKey = key; + lastOutputs = found->second; + lastValid = true; + } return found->second; } } @@ -777,10 +1001,16 @@ ResourceCache::Key DrawResourceKey(const Context& context, std::span(context.device)); key.push_back(static_cast(shaders.size())); for (const auto& shader : shaders) { - const auto at = key.size(); - key.push_back(0); - ShaderResources::AppendContentKey(shader, true, MovableBuffers(), key); - key[at] = static_cast(key.size() - at - 1); + const auto sizeAt = key.size(); + key.push_back(0u); + auto& memo = shader.program->drawKeyMemo.words; + if (auto words = memo.load(std::memory_order_acquire); words != nullptr) { + key.insert(key.end(), words->begin(), words->end()); + } else { + ShaderResources::AppendContentKey(shader, true, MovableBuffers(), key); + memo.store(std::make_shared>(key.begin() + static_cast(sizeAt + 1u), key.end()), std::memory_order_release); + } + key[sizeAt] = static_cast(key.size() - sizeAt - 1u); } key.push_back(colorAttachments); if (ranges) { @@ -872,6 +1102,21 @@ std::array IndirectDrawCounts() { return stats.indirect; } +void CountEmptyIndirectDraw(double readMs) { + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + if (!profile) return; + auto& stats = Profile(); + std::lock_guard lock(stats.mutex); + ++stats.indirectEmpty; + stats.indirectReadUs += readMs * 1000.0; +} + +std::uint64_t EmptyIndirectDrawCount() { + auto& stats = Profile(); + std::lock_guard lock(stats.mutex); + return stats.indirectEmpty; +} + std::array DrawPassTotals() { auto& stats = Profile(); std::lock_guard lock(stats.mutex); @@ -1043,11 +1288,16 @@ struct DrawInputs { VkPipelineStageFlags shaderStages = 0; std::uint32_t meshGroups = 0; std::vector restartTable; + std::uint32_t expandedIndexCount = 0; const VertexInputLayout& VertexLayout() const { return retainedVertexInput != nullptr ? *retainedVertexInput : vertexInput; } }; -std::shared_ptr zeroVertexBuffer(const Context& context, const ShaderRecompiler::VertexAttribute& attribute) { - auto zero = std::make_shared(context, DecodeVertexFormat(attribute).bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); +bool StripPatches(const State& state, std::span shaders) { + return state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP && BarycentricPatches(state, shaders); +} + +std::shared_ptr zeroVertexBuffer(const Context& context, std::size_t bytes) { + auto zero = std::make_shared(context, bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); std::fill(zero->Bytes().begin(), zero->Bytes().end(), std::byte{0}); return zero; } @@ -1093,6 +1343,10 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P APS5_LOG_CHARS_OUT_DEBUG("Index buffer range OK"); Require(!draw.indexed || !state.hasColorTarget || draw.indexAddress + indexBytes <= state.color.address || state.color.address + state.color.bytes <= draw.indexAddress, "index buffer aliases the render target"); if (draw.indexed) RequireNoMultisampleTarget(draw.indexAddress, static_cast(indexBytes), "an index buffer read"); + if (state.pixelKill && !state.pixelEarlyZ && std::ranges::any_of(state.colors, [](const ColorTarget& color) { return color.partialWriteMask != 0; })) { + const auto* recorder = Recorder::Active(); + Require(recorder == nullptr || !recorder->CountingSamples(), "a partial write of a packed color target merges inside the pixel shader after early depth and stencil tests, which a shader that kills pixels under late tests cannot use while occlusion queries count samples: the early tests would count the pixels it kills"); + } APS5_LOG_CHARS_OUT_DEBUG("ValidateShaders"); if (recipe != nullptr) { inputs.fragmentOutputs = recipe->fragmentOutputs; @@ -1129,19 +1383,58 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P ValidateSampleMask(context, state, shaders); timer.phase(PhaseValidate); inputs.maxIndex = draw.indexed ? 0u : draw.firstVertex + draw.indexCount - 1u; - if (state.quadList) { - Require(!draw.indirect, "indirect quad-list draw is unsupported"); - const std::uint32_t quadCount = draw.indexCount / 4u; - if (quadCount == 0u || draw.instanceCount == 0u) { + if (state.quadList || StripPatches(state, shaders)) { + Require(!draw.indirect, state.quadList ? "indirect quad-list draw is unsupported" : "an indirect triangle strip through emulated barycentric patches is unsupported"); + const std::uint32_t primitives = state.quadList ? draw.indexCount / 4u : draw.indexCount >= 3u ? draw.indexCount - 2u : 0u; + if (primitives == 0u || draw.instanceCount == 0u) { inputs.nothing = true; return inputs; } - const std::uint32_t outputIndexCount = quadCount * 6u; - const std::size_t quadIndexBytes = static_cast(outputIndexCount) * sizeof(std::uint32_t); - auto indexCopy = std::make_shared(context, quadIndexBytes, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); + const std::uint32_t corners = state.quadList ? 6u : 3u; + const std::uint32_t outputIndexCount = primitives * corners; + const std::size_t expandedIndexBytes = static_cast(outputIndexCount) * sizeof(std::uint32_t); + auto indexCopy = std::make_shared(context, expandedIndexBytes, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); auto* outIndices = reinterpret_cast(indexCopy->Bytes().data()); std::uint32_t maxIdx = 0; const bool lastProvoking = state.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT; + const auto expand = [&](const auto& source) { + for (std::uint32_t primitive = 0; primitive < primitives; ++primitive) { + auto* out = outIndices + static_cast(primitive) * corners; + if (state.quadList) { + const auto i0 = source(primitive * 4u + 0u); + const auto i1 = source(primitive * 4u + 1u); + const auto i2 = source(primitive * 4u + 2u); + const auto i3 = source(primitive * 4u + 3u); + if (lastProvoking) { + out[0] = i0; + out[1] = i1; + out[2] = i3; + out[3] = i1; + out[4] = i2; + out[5] = i3; + } else { + out[0] = i0; + out[1] = i1; + out[2] = i2; + out[3] = i0; + out[4] = i2; + out[5] = i3; + } + } else { + const std::uint32_t odd = primitive & 1u; + if (lastProvoking) { + out[0] = source(primitive + odd); + out[1] = source(primitive + 1u - odd); + out[2] = source(primitive + 2u); + } else { + out[0] = source(primitive); + out[1] = source(primitive + 1u + odd); + out[2] = source(primitive + 2u - odd); + } + } + for (std::uint32_t corner = 0; corner < corners; ++corner) maxIdx = std::max(maxIdx, out[corner]); + } + }; if (draw.indexed) { GuestMemory::FlushGpuWrites(draw.indexAddress, static_cast(indexBytes)); GuestMemory::CheckRange(reinterpret_cast(draw.indexAddress), static_cast(indexBytes), draw.indexSize); @@ -1156,59 +1449,18 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P return v; } }; - for (std::uint32_t q = 0; q < quadCount; ++q) { - const auto i0 = readIndex(q * 4u + 0u); - const auto i1 = readIndex(q * 4u + 1u); - const auto i2 = readIndex(q * 4u + 2u); - const auto i3 = readIndex(q * 4u + 3u); - maxIdx = std::max({maxIdx, i0, i1, i2, i3}); - if (lastProvoking) { - outIndices[q * 6u + 0u] = i0; - outIndices[q * 6u + 1u] = i1; - outIndices[q * 6u + 2u] = i3; - outIndices[q * 6u + 3u] = i1; - outIndices[q * 6u + 4u] = i2; - outIndices[q * 6u + 5u] = i3; - } else { - outIndices[q * 6u + 0u] = i0; - outIndices[q * 6u + 1u] = i1; - outIndices[q * 6u + 2u] = i2; - outIndices[q * 6u + 3u] = i0; - outIndices[q * 6u + 4u] = i2; - outIndices[q * 6u + 5u] = i3; - } - } + expand(readIndex); Require(draw.firstVertex <= std::numeric_limits::max() - maxIdx, "indexed draw vertex range overflow"); - inputs.maxIndex = maxIdx + draw.firstVertex; } else { - maxIdx = quadCount * 4u - 1u; - Require(draw.firstVertex <= std::numeric_limits::max() - maxIdx, "auto draw vertex range overflow"); - for (std::uint32_t q = 0; q < quadCount; ++q) { - const auto i0 = q * 4u + 0u; - const auto i1 = q * 4u + 1u; - const auto i2 = q * 4u + 2u; - const auto i3 = q * 4u + 3u; - if (lastProvoking) { - outIndices[q * 6u + 0u] = i0; - outIndices[q * 6u + 1u] = i1; - outIndices[q * 6u + 2u] = i3; - outIndices[q * 6u + 3u] = i1; - outIndices[q * 6u + 4u] = i2; - outIndices[q * 6u + 5u] = i3; - } else { - outIndices[q * 6u + 0u] = i0; - outIndices[q * 6u + 1u] = i1; - outIndices[q * 6u + 2u] = i2; - outIndices[q * 6u + 3u] = i0; - outIndices[q * 6u + 4u] = i2; - outIndices[q * 6u + 5u] = i3; - } - } - inputs.maxIndex = maxIdx + draw.firstVertex; + const std::uint32_t lastVertex = state.quadList ? primitives * 4u - 1u : primitives + 1u; + Require(draw.firstVertex <= std::numeric_limits::max() - lastVertex, "auto draw vertex range overflow"); + expand([](std::uint32_t idx) { return idx; }); } + inputs.maxIndex = maxIdx + draw.firstVertex; Require(inputs.maxIndex <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit"); inputs.indexHandle = indexCopy->Handle(); inputs.indexOffset = 0; + inputs.expandedIndexCount = outputIndexCount; inputs.indices = std::move(indexCopy); } else if (draw.indexed) { const bool listTopology = state.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; @@ -1290,14 +1542,19 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P fetchValid.reserve(attributes.size()); std::vector fetchOf(attributes.size(), 0); std::vector> zeroed(attributes.size()); + const auto& layout = inputs.VertexLayout(); for (std::size_t i = 0; i < attributes.size(); ++i) { const auto& attribute = attributes[i]; + const auto described = std::find_if(layout.attributes.begin(), layout.attributes.end(), [&](const auto& entry) { return entry.location == attribute.location; }); + Require(described != layout.attributes.end(), "vertex attribute missing from its input layout"); + const auto narrow = DecodeVertexFormat(attribute); + const std::size_t widened = described->format == narrow.format ? 0u : VertexReadBytes(attribute, described->format) - narrow.bytes; if (NullVertexDescriptor(attribute) || VertexFetchOutOfRange(attribute)) { - zeroed[i] = zeroVertexBuffer(context, attribute); + zeroed[i] = zeroVertexBuffer(context, narrow.bytes + widened); continue; } // An indirect draw's counts are unknown here: the descriptor's whole range is copied. - const auto bytes = args != nullptr ? VertexBufferExtent(attribute) : VertexBufferReadSize(attribute, inputs.maxIndex, draw.instanceCount, draw.firstInstance); + const auto bytes = (args != nullptr ? VertexBufferExtent(attribute) : VertexBufferReadSize(attribute, inputs.maxIndex, draw.instanceCount, draw.firstInstance)) + widened; const auto& fields = attribute.resource.fields; const auto address = fields[0] | (static_cast(fields[1] & 0xffffu) << 32u); const auto firstByte = args == nullptr ? VertexBufferReadOffset(attribute, draw.firstVertex, draw.firstInstance) : 0; @@ -1382,7 +1639,7 @@ std::shared_ptr refreshResidentTarget(const Context& context, co resident = lookup(); if (resident != nullptr) materializeRegisterClear(context, color, *resident); } catch (const std::exception& error) { - static std::mutex reportMutex; + static AgcDriver::Mutex reportMutex; static std::set reported; std::lock_guard lock(reportMutex); if (reported.insert(color.address).second) std::fprintf(stderr, "[gpu] color target 0x%llx stays non-resident: %s\n", static_cast(color.address), error.what()); @@ -1478,28 +1735,41 @@ struct IndirectRecord { // The draw commands of one draw: the vertex and index buffer binds, then the direct draw, the // GPU-side indirect draw from `argumentBuffer` or the CPU-read records with the driver's rules. -void recordDrawCommands(const Context& context, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, const DrawInputs& inputs, const IndirectRecord* indirect, VkBuffer argumentBuffer, VkDeviceSize argumentOffset, std::uint32_t firstIndex = 0) { +void recordDrawCommands(const Context& context, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, const DrawInputs& inputs, const IndirectRecord* indirect, VkBuffer argumentBuffer, VkDeviceSize argumentOffset, const DrawTraceContext* trace, std::uint32_t& subdraw, std::uint32_t firstIndex = 0) { if (context.recorder != nullptr) context.recorder->NoteSampledDraw(commands); + const auto recorded = [&](std::uint32_t index) { + if (trace != nullptr && context.recorder != nullptr) { + context.recorder->NoteDrawTraceToken(trace->token); + CaptureTrace::Log("draw-recorded token=%llu batch=%llu submission=%llu queue=%u offset=%zu target=%llx subdraw=%u", static_cast(trace->token), static_cast(context.recorder->OpenBatchSerial()), static_cast(trace->submission), trace->queue, trace->offset, static_cast(trace->target), index); + } + }; const auto* args = indirect != nullptr ? indirect->args : nullptr; if (state.stages.mesh && args != nullptr) { context.Function("vkCmdDrawMeshTasksIndirectEXT")(commands, argumentBuffer, argumentOffset, 1, sizeof(VkDrawMeshTasksIndirectCommandEXT)); + recorded(subdraw++); return; } if (state.stages.mesh) { APS5_LOG_OUT_DEBUG("vkCmdDrawMeshTasksEXT groups=%u instances=%u", inputs.meshGroups, draw.instanceCount); context.Function("vkCmdDrawMeshTasksEXT")(commands, inputs.meshGroups, draw.instanceCount, 1); + recorded(subdraw++); return; } if (!inputs.vertexHandles.empty()) context.Resolved(&DeviceFunctions::cmdBindVertexBuffers, "vkCmdBindVertexBuffers")(commands, 0, static_cast(inputs.vertexHandles.size()), inputs.vertexHandles.data(), inputs.vertexOffsets.data()); - if (state.quadList) { + if (inputs.expandedIndexCount != 0u) { context.Resolved(&DeviceFunctions::cmdBindIndexBuffer, "vkCmdBindIndexBuffer")(commands, inputs.indexHandle, inputs.indexOffset, VK_INDEX_TYPE_UINT32); - context.Resolved(&DeviceFunctions::cmdDrawIndexed, "vkCmdDrawIndexed")(commands, (draw.indexCount / 4u) * 6u, draw.instanceCount, 0, static_cast(draw.firstVertex), draw.firstInstance); + context.Resolved(&DeviceFunctions::cmdDrawIndexed, "vkCmdDrawIndexed")(commands, inputs.expandedIndexCount, draw.instanceCount, 0, static_cast(draw.firstVertex), draw.firstInstance); return; } if (draw.indexed) context.Resolved(&DeviceFunctions::cmdBindIndexBuffer, "vkCmdBindIndexBuffer")(commands, inputs.indexHandle, inputs.indexOffset, draw.indexSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32); if (args == nullptr) { - if (draw.indexed) context.Resolved(&DeviceFunctions::cmdDrawIndexed, "vkCmdDrawIndexed")(commands, draw.indexCount, draw.instanceCount, firstIndex, static_cast(draw.firstVertex), draw.firstInstance); - else context.Resolved(&DeviceFunctions::cmdDraw, "vkCmdDraw")(commands, draw.indexCount, draw.instanceCount, draw.firstVertex, draw.firstInstance); + if (draw.indexed) { + context.Resolved(&DeviceFunctions::cmdDrawIndexed, "vkCmdDrawIndexed")(commands, draw.indexCount, draw.instanceCount, firstIndex, static_cast(draw.firstVertex), draw.firstInstance); + recorded(subdraw++); + } else { + context.Resolved(&DeviceFunctions::cmdDraw, "vkCmdDraw")(commands, draw.indexCount, draw.instanceCount, draw.firstVertex, draw.firstInstance); + recorded(subdraw++); + } } else if (indirect->path == IndirectDrawPath::Gpu) { if (args->countIndirect) { if (draw.indexed) context.Function("vkCmdDrawIndexedIndirectCountKHR")(commands, argumentBuffer, argumentOffset, indirect->countImport->buffer, HostImportOffset(*indirect->countImport, args->countAddress), args->count, args->stride); @@ -1651,7 +1921,7 @@ struct Kept { // The completion side of a recorded draw: the kept objects, the record check, the lease outcome, // the GPU write notes, the copied-buffer write-back (listed in DrawCopiedWriters or run as a // completion action) and the targets marked dirty. -void keepRecordedDraw(Recorder& recorder, const std::shared_ptr& resources, std::shared_ptr pipeline, std::shared_ptr framebuffer, DrawInputs& inputs, std::vector> targets, std::vector> slices, std::unique_ptr scratch, std::function checkRecords, bool listed, bool completion, const DrawOutcome& outcome) { +void keepRecordedDraw(Recorder& recorder, const std::shared_ptr& resources, std::shared_ptr pipeline, std::shared_ptr framebuffer, DrawInputs& inputs, std::vector> targets, std::vector> slices, VkRect2D scissor, std::unique_ptr scratch, std::function checkRecords, bool listed, bool completion, const DrawOutcome& outcome) { auto kept = std::make_shared(); kept->resources = resources; kept->pipeline = std::move(pipeline); @@ -1686,18 +1956,20 @@ void keepRecordedDraw(Recorder& recorder, const std::shared_ptr resources->WriteBackBuffers(); }); writers->push_back(resources); + GuestMemory::BreakProofs(); } else if (completion) { if (resources->Reusable()) recorder.OnComplete([resources] { resources->CheckFaults(); }); else recorder.OnComplete([resources] { resources->WriteBackBuffers(); }); } + std::vector storeNow(residents.size(), false); for (std::size_t index = 0; index < residents.size(); ++index) { if (residents[index] == nullptr) continue; - if (index < slices.size()) residents[index]->MarkSliceDirty(slices[index].first, slices[index].second); + if (residents[index]->Descriptor().tileMode == TextureTileMode::kLinear) storeNow[index] = !residents[index]->MarkDrawn(index < slices.size() ? slices[index].first : 0u, index < slices.size() ? slices[index].second : 0u, scissor) || !residents[index]->Cached(); + else if (index < slices.size()) residents[index]->MarkSliceDirty(slices[index].first, slices[index].second); else residents[index]->MarkDirty(); } - for (const auto& resident : residents) { - if (resident == nullptr || resident->Descriptor().tileMode != TextureTileMode::kLinear) continue; - StorageTexture::FlushPending(resident->Descriptor().baseAddress, static_cast(resident->GuestBytes()), nullptr, "linear target"); + for (std::size_t index = 0; index < residents.size(); ++index) { + if (storeNow[index]) StorageTexture::FlushPending(residents[index]->Descriptor().baseAddress, static_cast(residents[index]->GuestBytes()), nullptr, "linear target"); } } @@ -1751,6 +2023,7 @@ void recordStencilReplay(const Context& context, VkCommandBuffer commands, const const auto referenceOffset = fragment->pushConstantOffset + fragment->program->stencilReferenceDword * sizeof(std::uint32_t); Require(referenceOffset <= block.size() - sizeof(std::uint32_t), "stencil replay reference exceeds the push block"); const auto run = [&](const Pm4::DrawParameters& piece, std::uint32_t firstIndex) { + std::uint32_t unusedSubdraw = 0; for (const auto& [reference, pipeline] : replay) { selected.stencilReplayReference = reference; selected.stencilFront.reference = reference; @@ -1758,7 +2031,7 @@ void recordStencilReplay(const Context& context, VkCommandBuffer commands, const context.Resolved(&DeviceFunctions::cmdSetStencilReference, "vkCmdSetStencilReference")(commands, VK_STENCIL_FACE_FRONT_AND_BACK, reference); std::memcpy(block.data() + referenceOffset, &reference, sizeof(reference)); pipeline->PushConstants(commands, stages, block); - recordDrawCommands(context, commands, selected, piece, inputs, nullptr, VK_NULL_HANDLE, 0, firstIndex); + recordDrawCommands(context, commands, selected, piece, inputs, nullptr, VK_NULL_HANDLE, 0, nullptr, unusedSubdraw, firstIndex); } }; if (replay.size() == 1u) { @@ -1891,7 +2164,8 @@ bool WriteOnlyPasses() { return enabled; } -void recordDraw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, DrawInputs& inputs, RecordedDraw& record, DrawOutcome& outcome, DrawTimer& timer, double& ownWaitedMs) { +void recordDraw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, DrawInputs& inputs, RecordedDraw& record, DrawOutcome& outcome, DrawTimer& timer, double& ownWaitedMs, const DrawTraceContext* trace = nullptr) { + std::uint32_t subdraw = 0; auto* recorder = record.recorder; auto& resources = *record.resources; const auto* args = record.indirect != nullptr ? record.indirect->args : nullptr; @@ -1922,7 +2196,6 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam recorder->FlushStores(); outcome.storesFlushed = true; } - const auto drawBindings = resources.PrepareDrawBindings(*recorder, record.moved); const bool capture = CaptureInputsEnabled(); const bool meshIndirect = state.stages.mesh && args != nullptr; const bool addressBased = resources.HoldsLease(); @@ -1944,6 +2217,8 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam } for (const auto image : record.multisampleImages) passAttachments.emplace_back(image, true); const bool unknownMemory = addressBased || resources.UsesAddressTable() || resources.HasCopiedWrites(); + static_cast(StorageTexture::StoreDrawnLinearTouching("draw access", [&](std::uint64_t begin, std::uint64_t end) { return std::any_of(passReads.begin(), passReads.end(), [&](const auto& read) { return read.first < end && begin < read.second; }) || resources.WritesOverlap(begin, static_cast(end - begin)); }, unknownMemory)); + const auto drawBindings = resources.PrepareDrawBindings(*recorder, record.moved); static const bool addressReadPasses = std::getenv("APS5_NO_ADDRESS_READ_PASSES") == nullptr; const bool readOnlyBda = addressReadPasses && resources.BdaReadOnly(); outcome.addressDraw = addressBased; @@ -1972,7 +2247,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam } const PassAccess passAccess{passReads, passWrites, passImages, passAttachments, unknownMemory, unknownWrites, passWriteOnly}; const bool fenced = state.depth.has_value() || !record.proxies.empty(); - const auto start = recorder->StartDrawPass(passKey, capture || readsTarget || gpuIndirect || meshIndirect, fenced, passAccess); + const auto start = recorder->StartDrawPass(passKey, capture || readsTarget || gpuIndirect || meshIndirect || PixelFunctionLdsSubgroup(shaders) != 0u, fenced, passAccess); for (const auto& target : record.multisampleTargets) KeepMultisampleTarget_nid_no_patch(target); const bool continued = start.continued; outcome.passContinued = continued; @@ -2019,6 +2294,8 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT); countBarrier(1); } + const auto pixelLdsSubgroup = PixelFunctionLdsSubgroup(shaders); + if (pixelLdsSubgroup != 0u) PreparePixelFunctionLds(context, commands, pixelLdsSubgroup); if (meshIndirect) { meshArguments = recordMeshArguments(context, commands, recorder, true, state, draw, *record.indirect, countBarrier, inputs.restartTable); argumentBuffer = meshArguments->Handle(); @@ -2044,21 +2321,22 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam } else { resources.Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout()); } + if (std::ranges::any_of(shaders, [](const CompiledShader& shader) { return shader.program != nullptr && shader.program->functionLdsBuffer; })) BindPixelFunctionLds(context, commands, record.pipeline->Layout()); APS5_LOG_CHARS_OUT_DEBUG("Resources bound"); pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0, !inputs.restartTable.empty()); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); - if (record.stencilReplay.empty()) recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset); + if (record.stencilReplay.empty()) recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset, trace, subdraw); else { recordStencilReplay(context, commands, state, draw, inputs, record.stencilReplay, shaders, resources, record.pushBytes, record.pushStages); for (const auto& [reference, pipeline] : record.stencilReplay) recorder->Keep(pipeline); } if (record.pipeline->SplitsFaces()) { record.pipeline->ContinueBackFaces(commands, state); - recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset); + recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset, trace, subdraw); } if (record.pipeline->SplitsSecondChannel()) { record.pipeline->ContinueSecondChannel(commands); - recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset); + recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset, trace, subdraw); } if (meshArguments != nullptr) recorder->Keep(meshArguments); if (args != nullptr) CountIndirectDraw(record.indirect->path, record.indirect->readMs, rewritten); @@ -2085,7 +2363,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam } recorder->LeaveRenderPassOpen(passKey, drawTiming, fenced, passAccess, std::move(storeProxies)); timer.phase(PhaseRecord); - keepRecordedDraw(*recorder, record.resources, std::move(record.pipeline), std::move(record.framebuffer), inputs, std::move(record.targets), std::move(record.targetSlices), std::move(scratch), std::move(checkRecords), record.listed, record.completion, outcome); + keepRecordedDraw(*recorder, record.resources, std::move(record.pipeline), std::move(record.framebuffer), inputs, std::move(record.targets), std::move(record.targetSlices), state.scissor, std::move(scratch), std::move(checkRecords), record.listed, record.completion, outcome); timer.phase(PhaseKeep); if (record.waited) { // The wait the dispatch path makes for such work (source 3, "address-based"): the @@ -2148,8 +2426,67 @@ std::optional KnownValidationFailure(const Context& context, std::s return std::nullopt; } -void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, std::shared_ptr* recipeOut) { +namespace { + +struct TargetBinding { + ColorTarget color; + bool gpuTiling = false; + std::shared_ptr resident; + bool proxied = false; + std::unique_ptr transfer; + std::unique_ptr tiled; + std::unique_ptr tiledDevice; + std::unique_ptr linearDevice; + std::vector original; + TileMipLayout mip{}; + std::unique_ptr target; + bool multisampled = false; + VkImage multisampleImage = VK_NULL_HANDLE; + std::shared_ptr multisampleOwner; + std::unique_ptr dump; +}; + +struct PreparedDraw { + explicit PreparedDraw(bool profile) : timer(profile) {} + PreparedDraw(const PreparedDraw&) = delete; + PreparedDraw& operator=(const PreparedDraw&) = delete; + std::shared_ptr* recipeOut = nullptr; + const DrawTraceContext* trace = nullptr; std::vector stencilReferences; + std::optional timing; + DrawTimer timer; + DrawOutcome outcome; + const ShaderResources::BuildTiming* built = nullptr; + double waitedBefore = 0; + double ownWaitedMs = 0; + DrawInputs inputs; + const Pm4::DrawParameters::IndirectDraw* args = nullptr; + int dumpLimit = 0; + std::vector targets; + std::vector targetViews; + Recorder* recorder = nullptr; + ResolvedResources resolved; + IndirectRecord indirect; + std::vector records; + bool completion = false; + bool listed = false; + bool recorded = false; + bool lean = false; + bool multisampled = false; + std::optional masked; + std::vector>> stencilReplay; + std::shared_ptr pipeline; + std::vector> owners; + std::vector> framebufferOwners; + std::shared_ptr framebuffer; + std::vector reordered; + SampleOrderPass reorderPass; + std::optional depthLocations; +}; + +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; for (const auto& shader : shaders) { if (shader.program != nullptr && !shader.program->stencilReferences.empty()) { Require(shader.stage == ShaderRecompiler::ShaderStage::Fragment && stencilReferences.empty(), "stencil replay requires exactly one fragment stage"); @@ -2172,60 +2509,37 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& Require(state.samples == 1u && (!state.depth || HasStencil(state.depth->format)), "stencil replay requires single-sample rendering and an eight-bit stencil format"); recipeOut = nullptr; } - if (state.quadList) recipeOut = nullptr; + if (state.quadList || StripPatches(state, shaders)) recipeOut = nullptr; if (context.recorder != nullptr) context.recorder->BoundKeptBytes(); - PerformanceTimer timing("Graphics.Draw"); + auto& timing = prepared.timing.emplace("Graphics.Draw"); // APS5_PROFILE_DRAW prints the time of each phase of the draw (microseconds) and the [draws] totals. static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; - DrawTimer timer(profile); + auto& timer = prepared.timer; + timer = DrawTimer(profile); // APS5_TRACE_DRAWS (together with APS5_PROFILE_DRAW; alone it only enables Driver.cpp's own // [draw] lines) additionally prints one line per draw with its phases and, for synchronous // draws, their inputs: per-draw string building at ~1400 draws per 10 s is itself a cost the // totals must not carry. static const bool traceDraws = std::getenv("APS5_TRACE_DRAWS") != nullptr; - DrawOutcome outcome; - const ShaderResources::BuildTiming* built = nullptr; + auto& outcome = prepared.outcome; + auto& built = prepared.built; // The thread's GPU waits so far: the difference at the report, less the draw's own syncs // (`ownWaitedMs`, sampled around them), is what this draw waited for through the flush hook // (nested in the draw's hold, see DrawOutcome::hookWaitUs). - const auto waitedBefore = profile ? Recorder::ThreadWaitedMs() : 0.0; - double ownWaitedMs = 0; + const auto waitedBefore = prepared.waitedBefore = profile ? Recorder::ThreadWaitedMs() : 0.0; + auto& ownWaitedMs = prepared.ownWaitedMs; const auto report = [&](const char* suffix) { reportDrawEnd(state, timer, built, outcome, waitedBefore, ownWaitedMs, suffix); }; - auto inputs = prepareDrawInputs(context, state, draw, shaders, outcome, timer, nullptr); - if (inputs.nothing) return; - const auto* args = draw.indirect ? &*draw.indirect : nullptr; + auto& inputs = prepared.inputs; + inputs = prepareDrawInputs(context, state, draw, shaders, outcome, timer, nullptr); + if (inputs.nothing) return false; + const auto* args = prepared.args = draw.indirect ? &*draw.indirect : nullptr; const auto indexBytes = inputs.indexBytes; timing.Mark("validate"); timing.Mark("index_upload"); - // One binding per color attachment. Tiled targets are detiled and retiled on the GPU in video memory; - // only changed guest blocks are stored back. - struct TargetBinding { - ColorTarget color; - bool gpuTiling = false; - // Resident target: the surface's cached storage image is attached directly and marked dirty - // afterwards, so nothing is copied in or out per draw. - std::shared_ptr resident; - bool proxied = false; - // Linear pixels converted on the CPU, for targets the GPU detiler does not handle. - std::unique_ptr transfer; - // Guest bytes in host memory, and their device-local copies for the detiler. - std::unique_ptr tiled; - std::unique_ptr tiledDevice; - std::unique_ptr linearDevice; - std::vector original; - TileMipLayout mip{}; - std::unique_ptr target; - bool multisampled = false; - VkImage multisampleImage = VK_NULL_HANDLE; - std::shared_ptr multisampleOwner; - // APS5_DUMP_TARGETS: the rendered linear pixels, read back for target_
_.raw. - std::unique_ptr dump; - }; - const int dumpLimit = DumpTargetLimit(); - static std::mutex dumpMutex; - static std::map dumped; - std::vector targets(state.colors.size()); - std::vector targetViews; + const int dumpLimit = prepared.dumpLimit = DumpTargetLimit(); + auto& targets = prepared.targets; + targets.resize(state.colors.size()); + auto& targetViews = prepared.targetViews; for (std::size_t index = 0; index < state.colors.size(); ++index) { auto& binding = targets[index]; binding.color = state.colors[index]; @@ -2234,14 +2548,19 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& if (color.samples > 1) { binding.multisampled = true; timer.phase(PhaseSetup); + if (color.partialWriteMask != 0) { + static_cast(MultisampleTargetView(context, color)); + static_cast(MultisampleTargetStorageView(context, TargetMergeSurface(color))); + } targetViews.push_back(MultisampleTargetView(context, color, binding.multisampleOwner, &binding.multisampleImage)); timer.phase(PhaseReadTarget); continue; } APS5_LOG_OUT_DEBUG("Creating color target %zu address=0x%llx bytes=%llu extent=%ux%u", index, static_cast(color.address), static_cast(color.bytes), color.extent.width, color.extent.height); const ColorTargetLayout colorLayout(color.extent.width, color.extent.height, color.tileMode, color.elementBytes); - static_cast(FlushDepthPlanes(color.address, colorLayout.Bytes())); + static_cast(FlushDepthPlanes(color.address, colorLayout.Bytes(), DepthPlaneScope::Whole)); RetireDepthSurfaces(context.device, color.address, colorLayout.Bytes()); + if (color.fmaskAddress != 0) GuardOneFragmentFmask(context, color); constexpr VkBufferUsageFlags copies = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; timer.phase(PhaseSetup); // Debug aid: APS5_NO_RESIDENT_TARGETS=1 copies every target in and out again. @@ -2264,7 +2583,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& const auto geometry = DescribeSurface(array); for (std::uint32_t slice = 0; slice <= color.arrayLastSlice; ++slice) Require(geometry.GuestLayerOffset(slice) == slice * sliceBytes, "the array image's layer pitch differs from the color view's slice offset"); Require(color.surfaceAddress == color.arrayAddress + color.arraySlice * sliceBytes, "the array image's layer pitch differs from the color view's slice offset"); - auto resident = CachedStorageSurface(context, array); + auto resident = CachedStorageLayers(context, array, color.arraySlice, color.layers); Require(resident->Attachable(), "storage format cannot be a color attachment"); return resident; } @@ -2317,12 +2636,16 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& binding.target = std::make_unique(context, color, state.blends.at(color.exportIndex).blendEnable != 0); targetViews.push_back(binding.target->View()); } - if (state.depth) targetViews.push_back(DepthSurfaceView(context, *state.depth, state.depthWrite)); + if (state.depth) { + targetViews.push_back(DepthSurfaceView(context, *state.depth, state.depthWrite)); + if (writesDepthTarget(state)) NoteDepthSurfaceWrite(context, *state.depth); + } timer.phase(PhasePrepare); const bool recordDraws = RecordDraws(); - auto* recorder = Recorder::Active(); + auto* recorder = prepared.recorder = Recorder::Active(); bool recordable = recordDraws && recorder != nullptr && dumpLimit == 0 && std::all_of(targets.begin(), targets.end(), [](const TargetBinding& binding) { return binding.resident != nullptr || binding.multisampled; }); - auto resolved = resolveDrawResources(context, state, draw, shaders, snapshots, indexBytes, recordable, outcome, timer); + auto& resolved = prepared.resolved; + resolved = resolveDrawResources(context, state, draw, shaders, snapshots, indexBytes, recordable, outcome, timer); auto& resources = resolved.resources; if (!stencilReferences.empty() && resources->ChecksFaults()) recordable = false; if (!stencilReferences.empty()) { @@ -2362,9 +2685,9 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& // and records plain draws with the same per-dimension rules. The import pointer stays valid: // refreshImports runs only under GuestMemory::GpuMutex, held here, and a retired import's // buffer is kept while the recorder is busy. - IndirectRecord indirect; + auto& indirect = prepared.indirect; indirect.args = args; - std::vector records; + auto& records = prepared.records; if (args != nullptr) { static const bool gpuIndirectDraws = std::getenv("APS5_NO_GPU_INDIRECT_DRAW") == nullptr; const auto decide = [&](std::uint64_t address, std::size_t bytes, const HostImport*& import) { @@ -2385,7 +2708,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& const auto rangeBytes = args->RangeBytes(); if (rangeBytes == 0) { report(" indirect draw without records"); - return; + return false; } Require(rangeBytes <= std::numeric_limits::max(), "indirect draw record range overflow"); if (!gpuIndirectDraws) indirect.path = IndirectDrawPath::Disabled; @@ -2406,7 +2729,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& if (std::none_of(records.begin(), records.end(), [](const Pm4::DrawArguments& record) { return record.count != 0 && record.instances != 0; })) { CountIndirectDraw(indirect.path, indirect.readMs); report(" indirect draw with empty records"); - return; + return false; } } } @@ -2431,14 +2754,14 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& static const bool syncCompletionDraws = std::getenv("APS5_SYNC_COMPLETION_DRAWS") != nullptr; static const bool recorderSyncDraws = std::getenv("APS5_NO_RECORDER_SYNC_DRAWS") == nullptr; static const bool recordCopiedDraws = std::getenv("APS5_NO_RECORD_COPIED_DRAWS") == nullptr; - const bool completion = resources->NeedsCompletion(); + const bool completion = prepared.completion = resources->NeedsCompletion(); const bool copiedWrites = completion && resources->HasCopiedWrites(); // A lease only forces the wait when deferred release is off. const bool lease = resources->HoldsLease() && SyncLeaseWork(); outcome.completion = completion; outcome.recorded = recordable; // Whether the copied write-back is listed in DrawCopiedWriters instead of waited for. - bool listed = false; + auto& listed = prepared.listed; if (!recordable) { outcome.reason = !recordDraws || dumpLimit != 0 ? SyncDisabled : recorder == nullptr ? SyncNoRecorder : SyncNotResident; } else if (completion && (syncCompletionDraws || copiedWrites || lease)) { @@ -2448,7 +2771,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& else if (recorderSyncDraws) outcome.waited = true; else outcome.recorded = false; } - const bool recorded = outcome.recorded; + const bool recorded = prepared.recorded = outcome.recorded; // A build this recorded draw can share with later identical ones goes into the cache (a cache // hit is reusable by construction, so `recorded` holds for it; one with completion work is // never reusable). @@ -2457,11 +2780,22 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& // aid: APS5_DRAW_TRANSITIONS=1 keeps the per-draw upload and download barriers, the layout // transitions of every target and one pass per draw, as before. static const bool drawTransitions = std::getenv("APS5_DRAW_TRANSITIONS") != nullptr; - const bool lean = recorded && !drawTransitions; - const bool multisampled = std::any_of(targets.begin(), targets.end(), [](const TargetBinding& binding) { return binding.multisampled; }); + const bool lean = prepared.lean = recorded && !drawTransitions; + const bool multisampled = prepared.multisampled = std::any_of(targets.begin(), targets.end(), [](const TargetBinding& binding) { return binding.multisampled; }); Require(lean || !multisampled, "multisampled draws are only rendered as recorded draws"); for (const auto& binding : targets) { - if (binding.multisampled) Require(std::find(resources->MultisampleSources().begin(), resources->MultisampleSources().end(), binding.color.surfaceAddress) == resources->MultisampleSources().end(), "sampling a multisampled color target the draw renders to is not implemented"); + if (!binding.multisampled) continue; + const auto& sources = resources->MultisampleSources(); + const auto reads = std::count(sources.begin(), sources.end(), binding.color.surfaceAddress); + Require(reads == 0 || (reads == 1 && binding.color.partialWriteMask != 0), "sampling a multisampled color target the draw renders to is not implemented"); + Require(binding.color.partialWriteMask == 0 || (reads == 1 && MultisampleTargetStorable(context, binding.color)), "the partial write of a multisampled packed color target does not store into the target's image"); + if (binding.color.partialWriteMask != 0) static_cast(MultisampleTargetStorageView(context, TargetMergeSurface(binding.color))); + } + for (const auto& binding : targets) { + if (binding.color.partialWriteMask == 0 || binding.multisampled) continue; + Require(lean && binding.resident != nullptr && !binding.proxied, "a partial write of a packed color target is merged into its resident image by a recorded draw only"); + const auto images = resources->StorageImages(); + Require(std::ranges::any_of(images, [&](const std::pair& image) { return image.first == binding.resident->Image() && image.second; }), "the partial write of a packed color target does not store into the target's resident image"); } if (!lean && !resolved.moved.empty()) { resolved.resources = std::make_shared(context, shaders, state.color, static_cast(state.colors.size()), draw.indexAddress, static_cast(indexBytes), snapshots); @@ -2470,7 +2804,8 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& if (state.depth) RequireNoNativeDepthFeedback_nid_no_patch(context, *state.depth, resources->SampledTextures()); APS5_LOG_CHARS_OUT_DEBUG("Creating Pipeline"); // The state is copied only when a mask must change. - auto masked = maskedState(state, inputs.fragmentOutputs); + auto& masked = prepared.masked; + masked = maskedState(state, inputs.fragmentOutputs); if (std::any_of(targets.begin(), targets.end(), [](const TargetBinding& binding) { return binding.proxied; })) recipeOut = nullptr; for (std::size_t index = 0; index < targets.size(); ++index) { if (!targets[index].proxied) continue; @@ -2485,7 +2820,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& const auto built = resources->SetLayouts(); Require(described.layouts.main == built.main && described.layouts.fragment == built.fragment && described.layouts.split == built.split && std::ranges::equal(described.mainKey, built.mainKey) && std::ranges::equal(described.fragmentKey, built.fragmentKey), "set layouts described from the shaders differ from the draw's"); } - std::vector>> stencilReplay; + auto& stencilReplay = prepared.stencilReplay; for (const auto reference : stencilReferences) { auto selected = pipelineState; selected.stencilReplayReference = reference; @@ -2495,20 +2830,22 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& Require(!selectedPipeline->SplitsFaces() && !selectedPipeline->SplitsSecondChannel(), "stencil replay refuses pipeline face or channel splits"); stencilReplay.emplace_back(reference, std::move(selectedPipeline)); } - auto pipeline = stencilReplay.empty() ? CachedPipeline(context, pipelineState, inputs.VertexLayout(), *resources, shaders, lean ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) : stencilReplay.front().second; + 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; // 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. - std::vector> owners; + auto& owners = prepared.owners; owners.reserve(targets.size()); - std::vector> framebufferOwners; + auto& framebufferOwners = prepared.framebufferOwners; framebufferOwners.reserve(targets.size()); for (const auto& binding : targets) { owners.push_back(binding.resident); framebufferOwners.push_back(binding.multisampled ? binding.multisampleOwner : binding.resident); } - auto framebuffer = pipeline->AcquireFramebuffer(targetViews, framebufferOwners, state.renderExtent, state.renderLayers); - std::vector reordered; - SampleOrderPass reorderPass; + auto& framebuffer = prepared.framebuffer; + framebuffer = pipeline->AcquireFramebuffer(targetViews, framebufferOwners, state.renderExtent, state.renderLayers); + auto& reordered = prepared.reordered; + auto& reorderPass = prepared.reorderPass; if (pipeline->ReordersSamples()) { reorderPass = SampleOrderPassFor(context, pipelineState, pipeline->HostSampleOrder()); for (const auto& step : PlanSampleOrder(context, state, pipeline->HostSampleOrder())) { @@ -2517,9 +2854,50 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& Require(reordered.back().method == step.method, "a reordered attachment's surface disagrees with the planned way of putting its samples in order"); } } - const auto depthLocations = passDepthLocations(context, pipelineState, *pipeline); + prepared.depthLocations = passDepthLocations(context, pipelineState, *pipeline); timer.phase(PhasePipeline); APS5_LOG_CHARS_OUT_DEBUG("Pipeline created"); + return true; +} + +void recordPreparedDraw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, PreparedDraw& prepared) { + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + static const bool traceDraws = std::getenv("APS5_TRACE_DRAWS") != nullptr; + static AgcDriver::Mutex dumpMutex; + static std::map dumped; + auto& timing = *prepared.timing; + auto& timer = prepared.timer; + auto& outcome = prepared.outcome; + const auto* built = prepared.built; + const auto waitedBefore = prepared.waitedBefore; + auto& ownWaitedMs = prepared.ownWaitedMs; + const auto report = [&](const char* suffix) { reportDrawEnd(state, timer, built, outcome, waitedBefore, ownWaitedMs, suffix); }; + auto* recipeOut = prepared.recipeOut; + const auto& stencilReferences = prepared.stencilReferences; + auto& inputs = prepared.inputs; + const auto* args = prepared.args; + const int dumpLimit = prepared.dumpLimit; + auto& targets = prepared.targets; + const auto& targetViews = prepared.targetViews; + auto* recorder = prepared.recorder; + auto& resolved = prepared.resolved; + auto& resources = resolved.resources; + const auto& contentKey = resolved.contentKey; + const bool cacheable = resolved.cacheable; + auto& indirect = prepared.indirect; + const bool completion = prepared.completion; + const bool listed = prepared.listed; + const bool recorded = prepared.recorded; + const bool lean = prepared.lean; + const bool multisampled = prepared.multisampled; + const auto& masked = prepared.masked; + const auto& stencilReplay = prepared.stencilReplay; + const auto& pipeline = prepared.pipeline; + const auto& owners = prepared.owners; + const auto& framebuffer = prepared.framebuffer; + const auto& reordered = prepared.reordered; + const auto& reorderPass = prepared.reorderPass; + const auto& depthLocations = prepared.depthLocations; if (lean) { RecordedDraw record; record.recorder = recorder; @@ -2545,7 +2923,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& record.reordered = reordered; record.reorderPass = reorderPass; record.depthLocations = depthLocations; - recordDraw(context, state, draw, shaders, inputs, record, outcome, timer, ownWaitedMs); + recordDraw(context, state, draw, shaders, inputs, record, outcome, timer, ownWaitedMs, prepared.trace); // The recipe for the caller's draw-cache entry (design_cpu_final M8, rule R3 for draws): // only a template the resource cache serves under this content key (reusable: no lease, // no copied writes, every direct region import- or mirror-served) of a direct draw, so a @@ -2576,6 +2954,14 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& report(outcome.waited ? (outcome.reason == SyncLease ? " recorded then waited (lease)" : " recorded then waited (copied writes)") : completion ? " recorded with completion" : " recorded"); return; } + const bool gpuIndirect = args != nullptr && indirect.path == IndirectDrawPath::Gpu; + const auto touches = [&](std::uint64_t begin, std::uint64_t end) { + const auto bytes = static_cast(end - begin); + if (resources->WritesOverlap(begin, bytes) || resources->ReadsOverlap(begin, bytes)) return true; + if (!gpuIndirect) return false; + return (begin < args->arguments + args->RangeBytes() && args->arguments < end) || (args->countIndirect && begin < args->countAddress + 4 && args->countAddress < end); + }; + static_cast(StorageTexture::StoreDrawnLinearTouching("draw access", [&](std::uint64_t begin, std::uint64_t end) { return touches(begin, end) || std::any_of(inputs.inPlaceReads.begin(), inputs.inPlaceReads.end(), [&](const auto& read) { return read.first < end && begin < read.second; }); }, resources->HoldsLease() || resources->UsesAddressTable() || resources->HasCopiedWrites())); APS5_LOG_CHARS_OUT_DEBUG("Creating CommandBatch"); std::optional batch; if (!recorded) { @@ -2585,16 +2971,9 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& } using CommandClass = Recorder::CommandClass; const auto countBarrier = [&](std::uint32_t count = 1) { if (recorded) Recorder::CountBarriers(CommandClass::Draw, count); }; - const bool gpuIndirect = args != nullptr && indirect.path == IndirectDrawPath::Gpu; // A queued DCC key store over memory the draw writes or reads in place (unknown for an // address-based build), or over its GPU-side records, lands before it, as before a dispatch // (VulkanDevice::dispatch). - const auto touches = [&](std::uint64_t begin, std::uint64_t end) { - const auto bytes = static_cast(end - begin); - if (resources->WritesOverlap(begin, bytes) || resources->ReadsOverlap(begin, bytes)) return true; - if (!gpuIndirect) return false; - return (begin < args->arguments + args->RangeBytes() && args->arguments < end) || (args->countIndirect && begin < args->countAddress + 4 && args->countAddress < end); - }; if (recorded && recorder->HasQueuedKeyStores() && (resources->HoldsLease() || recorder->AnyQueuedKeyStore(touches))) recorder->FlushKeyStores(); // A queued label store over such memory likewise (Recorder::RecordStore). if (recorded && recorder->HasQueuedStores() && (resources->HoldsLease() || recorder->AnyQueuedStore(touches))) recorder->FlushStores(); @@ -2634,6 +3013,8 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT); countBarrier(); } + const auto pixelLdsSubgroup = PixelFunctionLdsSubgroup(shaders); + if (pixelLdsSubgroup != 0u) PreparePixelFunctionLds(context, commands, pixelLdsSubgroup); std::shared_ptr meshArguments; if (state.stages.mesh && args != nullptr) { meshArguments = recordMeshArguments(context, commands, recorder, recorded, state, draw, indirect, countBarrier, inputs.restartTable); @@ -2675,21 +3056,23 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& pipeline->Begin(commands, *framebuffer, state.renderExtent, state); APS5_LOG_CHARS_OUT_DEBUG("Pipeline Begin OK"); resources->Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->Layout()); + if (pixelLdsSubgroup != 0u) BindPixelFunctionLds(context, commands, pipeline->Layout()); APS5_LOG_CHARS_OUT_DEBUG("Resources bound"); pushDrawConstants(*pipeline, commands, state, draw, shaders, *resources, nullptr, 0, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0, !inputs.restartTable.empty()); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); - if (stencilReplay.empty()) recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset); + std::uint32_t subdraw = 0; + if (stencilReplay.empty()) recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset, prepared.trace, subdraw); else { recordStencilReplay(context, commands, state, draw, inputs, stencilReplay, shaders, *resources); if (recorded) for (const auto& [reference, selectedPipeline] : stencilReplay) recorder->Keep(selectedPipeline); } if (pipeline->SplitsFaces()) { pipeline->ContinueBackFaces(commands, state); - recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset); + recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset, prepared.trace, subdraw); } if (pipeline->SplitsSecondChannel()) { pipeline->ContinueSecondChannel(commands); - recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset); + recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset, prepared.trace, subdraw); } if (meshArguments != nullptr && recorded) recorder->Keep(meshArguments); if (args != nullptr) CountIndirectDraw(indirect.path, indirect.readMs, rewritten); @@ -2757,7 +3140,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& APS5_LOG_CHARS_OUT_DEBUG("SubmitAndWait"); timer.phase(PhaseRecord); if (recorded) { - keepRecordedDraw(*recorder, resources, pipeline, framebuffer, inputs, owners, [&] { std::vector> slices; for (const auto& binding : targets) slices.emplace_back(binding.color.arraySlice, binding.color.arrayLastSlice != 0 ? binding.color.layers : 1u); return slices; }(), std::move(scratch), std::move(checkRecords), listed, completion, outcome); + keepRecordedDraw(*recorder, resources, pipeline, framebuffer, inputs, owners, [&] { std::vector> slices; for (const auto& binding : targets) slices.emplace_back(binding.color.arraySlice, binding.color.arrayLastSlice != 0 ? binding.color.layers : 1u); return slices; }(), state.scissor, std::move(scratch), std::move(checkRecords), listed, completion, outcome); timer.phase(PhaseKeep); if (outcome.waited) { // The wait the dispatch path makes for such work (source 3, "address-based"): the @@ -2844,7 +3227,19 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& APS5_LOG_CHARS_OUT_DEBUG("Draw finished"); } -DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, const DrawRecipe& recipe) { +} + +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) { + const DepthCopySourceScope depthCopySource(state.depthCopySource); + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + PreparedDraw prepared(profile); + prepared.recipeOut = recipeOut; + prepared.trace = trace; + if (prepareDraw(context, state, draw, shaders, snapshots, prepared)) recordPreparedDraw(context, state, draw, shaders, prepared); +} + +DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, const DrawRecipe& recipe, const DrawTraceContext* trace) { + const DepthCopySourceScope depthCopySource(state.depthCopySource); if (context.recorder != nullptr) context.recorder->BoundKeptBytes(); static_cast(snapshots); PerformanceTimer timing("Graphics.DrawWithRecipe"); @@ -2866,7 +3261,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con }; Require(!draw.indirect, "a draw recipe covers direct draws only"); auto* recorder = Recorder::Active(); - if (state.quadList || !RecordDraws() || recorder == nullptr || DumpTargetLimit() != 0) return miss(DrawRecipeMiss::NotRecordable); + if (state.quadList || StripPatches(state, shaders) || !RecordDraws() || recorder == nullptr || DumpTargetLimit() != 0) return miss(DrawRecipeMiss::NotRecordable); if (recipe.hasDepth != state.depth.has_value()) return miss(DrawRecipeMiss::TargetGone); const std::size_t expectedViews = state.colors.size() + (recipe.hasDepth ? 1u : 0u); Require(recipe.targets.size() == state.colors.size() && recipe.targetViews.size() == expectedViews, "draw recipe targets do not match the state"); @@ -2885,10 +3280,12 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con targets.reserve(recipe.targets.size()); for (std::size_t index = 0; index < recipe.targets.size(); ++index) { timer.phase(PhaseSetup); + if (state.colors[index].fmaskAddress != 0) GuardOneFragmentFmask(context, state.colors[index]); auto stored = recipe.targets[index].lock(); - if (stored == nullptr || !StorageImageCached(context, stored.get()) || !StorageImageServesKeys(*stored, state.colors[index].dccAddress)) return miss(DrawRecipeMiss::TargetGone); + if (stored == nullptr || !StorageImageCached(context, stored.get()) || !StorageImageServesKeys(*stored, SurfaceForTarget(state.colors[index]).dccAddress)) return miss(DrawRecipeMiss::TargetGone); auto resident = refreshResidentTarget(context, state, state.colors[index], outcome, profile, [&] { - stored->Refresh(); + if (state.colors[index].arrayLastSlice != 0) stored->RefreshLayers(state.colors[index].arraySlice, 1); + else stored->Refresh(); return stored; }); timer.phase(PhaseReadTarget); @@ -2898,6 +3295,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con if (recipe.hasDepth) { timer.phase(PhaseSetup); const auto depthView = DepthSurfaceView(context, *state.depth, state.depthWrite); + if (writesDepthTarget(state)) NoteDepthSurfaceWrite(context, *state.depth); timer.phase(PhaseReadTarget); if (recipe.targetViews.back() != depthView) return miss(DrawRecipeMiss::TargetGone); } @@ -2950,7 +3348,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con record.pushBytes = &recipe.pushBytes; record.pushStages = recipe.pushStages; record.depthLocations = passDepthLocations(context, state, *record.pipeline); - recordDraw(context, state, draw, shaders, inputs, record, outcome, timer, ownWaitedMs); + recordDraw(context, state, draw, shaders, inputs, record, outcome, timer, ownWaitedMs, trace); if (profile) { result.recordUs = std::chrono::duration(std::chrono::steady_clock::now() - recordStart).count(); auto& stats = Profile(); @@ -2973,10 +3371,24 @@ std::shared_ptr metadataPassResident(const Context& context, con } -void RunDepthCopyPass(const Context& context, const DepthCopyPass& pass) { - Require(pass.color.tileMode != ColorTileMode::Linear && context.detiler != nullptr, "DB depth copy into a target without a resident image is not implemented"); - const auto resident = CachedStorageSurface(context, SurfaceForTarget(pass.color)); - CopyDepthSample(context, pass.depthAddress, pass.samples, pass.sample, *resident, resident->StorageView(0, false), pass.extent); +std::vector> PipelinedColorMetadataPassWrites(const ColorMetadataPass& pass) { + std::vector> writes; + if (pass.mode == ColorMetadataPass::Mode::Resolve) { + if (!pass.source.has_value() || pass.targets.size() != 1) return writes; + const auto& destination = pass.targets.front(); + if (destination.samples != 1u || destination.bytes == 0 || destination.cmaskAddress != 0) return writes; + writes.emplace_back(destination.address, destination.address + destination.bytes); + if (destination.dccAddress != 0) writes.emplace_back(destination.dccAddress, destination.dccAddress + std::max(colorKeyCount(destination, destination.bytes), 1u)); + return writes; + } + if (pass.mode != ColorMetadataPass::Mode::EliminateFastClear && pass.mode != ColorMetadataPass::Mode::DccDecompress) return writes; + for (const auto& color : pass.targets) { + if (color.samples != 1u || color.bytes == 0) return {}; + writes.emplace_back(color.address, color.address + color.bytes); + if (color.cmaskAddress != 0) writes.emplace_back(color.cmaskAddress, color.cmaskAddress + std::max(color.cmaskBytes, 1u)); + if (color.dccAddress != 0) writes.emplace_back(color.dccAddress, color.dccAddress + std::max(colorKeyCount(color, color.bytes), 1u)); + } + return writes; } void RunColorMetadataPass(const Context& context, const ColorMetadataPass& pass) { @@ -3023,4 +3435,13 @@ void RunColorMetadataPass(const Context& context, const ColorMetadataPass& pass) } } +bool DrawPipelineEnabled() { + static const bool enabled = std::getenv("APS5_DRAW_PIPELINE") != nullptr; + return enabled; +} + +bool CommitTrims() { + return DrawPipelineEnabled(); +} + } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/FirstSampleCopy.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/FirstSampleCopy.cpp new file mode 100644 index 0000000000..3a33f8bc0d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/FirstSampleCopy.cpp @@ -0,0 +1,252 @@ +#include "prx/libSceAgcDriver/Graphics/include/FirstSampleCopy.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/shaders/FirstSampleFloat_spv.h" +#include "prx/libSceAgcDriver/Graphics/shaders/FirstSampleSint_spv.h" +#include "prx/libSceAgcDriver/Graphics/shaders/FirstSampleUint_spv.h" +#include "prx/libSceAgcDriver/Graphics/shaders/PresentationPassVert_spv.h" +#include +#include + +namespace AgcDriver::Graphics { +namespace { + +bool sintFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_R8_SINT: case VK_FORMAT_R8G8_SINT: case VK_FORMAT_R8G8B8A8_SINT: case VK_FORMAT_B8G8R8A8_SINT: + case VK_FORMAT_A8B8G8R8_SINT_PACK32: case VK_FORMAT_A2B10G10R10_SINT_PACK32: case VK_FORMAT_R16_SINT: case VK_FORMAT_R16G16_SINT: + case VK_FORMAT_R16G16B16A16_SINT: case VK_FORMAT_R32_SINT: case VK_FORMAT_R32G32_SINT: case VK_FORMAT_R32G32B32A32_SINT: + return true; + default: return false; + } +} + +bool uintFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_R8_UINT: case VK_FORMAT_R8G8_UINT: case VK_FORMAT_R8G8B8A8_UINT: case VK_FORMAT_B8G8R8A8_UINT: + case VK_FORMAT_A8B8G8R8_UINT_PACK32: case VK_FORMAT_A2R10G10B10_UINT_PACK32: case VK_FORMAT_A2B10G10R10_UINT_PACK32: case VK_FORMAT_R16_UINT: + case VK_FORMAT_R16G16_UINT: case VK_FORMAT_R16G16B16A16_UINT: case VK_FORMAT_R32_UINT: case VK_FORMAT_R32G32_UINT: case VK_FORMAT_R32G32B32A32_UINT: + return true; + default: return false; + } +} + +std::span fragmentCode(VkFormat format) { + if (sintFormat(format)) return FIRST_SAMPLE_SINT_SPV; + if (uintFormat(format)) return FIRST_SAMPLE_UINT_SPV; + return FIRST_SAMPLE_FLOAT_SPV; +} + +struct DescriptorPool { + DescriptorPool(const Context& context, VkDescriptorPool pool) : context(context), pool(pool) {} + ~DescriptorPool() { context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); } + DescriptorPool(const DescriptorPool&) = delete; + DescriptorPool& operator=(const DescriptorPool&) = delete; + Context context; + VkDescriptorPool pool; +}; + +} + +FirstSampleCopy::FirstSampleCopy(const Context& context, VkFormat format, VkExtent2D extent) : context(context), extent(extent) { + try { + VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = format; + imageInfo.extent = {extent.width, extent.height, 1}; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + Check(context.Function("vkCreateImage")(context.device, &imageInfo, nullptr, &image), "vkCreateImage first sample"); + VkMemoryRequirements requirements{}; + context.Function("vkGetImageMemoryRequirements")(context.device, image, &requirements); + VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocation.allocationSize = requirements.size; + allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory first sample"); + Check(context.Function("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory first sample"); + VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView first sample"); + + VkSamplerCreateInfo samplerInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + samplerInfo.magFilter = VK_FILTER_NEAREST; + samplerInfo.minFilter = VK_FILTER_NEAREST; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + Check(context.Function("vkCreateSampler")(context.device, &samplerInfo, nullptr, &sampler), "vkCreateSampler first sample"); + VkDescriptorSetLayoutBinding binding{}; + binding.binding = 0; + binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + binding.descriptorCount = 1; + binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + binding.pImmutableSamplers = &sampler; + VkDescriptorSetLayoutCreateInfo descriptorInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + descriptorInfo.bindingCount = 1; + descriptorInfo.pBindings = &binding; + Check(context.Function("vkCreateDescriptorSetLayout")(context.device, &descriptorInfo, nullptr, &descriptorLayout), "vkCreateDescriptorSetLayout first sample"); + VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + layoutInfo.setLayoutCount = 1; + layoutInfo.pSetLayouts = &descriptorLayout; + Check(context.Function("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &pipelineLayout), "vkCreatePipelineLayout first sample"); + + VkAttachmentDescription attachment{}; + attachment.format = format; + attachment.samples = VK_SAMPLE_COUNT_1_BIT; + attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachment.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + const VkAttachmentReference reference{0, VK_IMAGE_LAYOUT_GENERAL}; + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &reference; + const VkSubpassDependency dependencies[2] = { + {VK_SUBPASS_EXTERNAL, 0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0}, + {0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, 0}, + }; + VkRenderPassCreateInfo renderPassInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &attachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 2; + renderPassInfo.pDependencies = dependencies; + Check(context.Function("vkCreateRenderPass")(context.device, &renderPassInfo, nullptr, &renderPass), "vkCreateRenderPass first sample"); + VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = &view; + framebufferInfo.width = extent.width; + framebufferInfo.height = extent.height; + framebufferInfo.layers = 1; + Check(context.Function("vkCreateFramebuffer")(context.device, &framebufferInfo, nullptr, &framebuffer), "vkCreateFramebuffer first sample"); + + vertex = CreateShaderModule(context, PRESENTATION_PASS_VERT_SPV, sizeof(PRESENTATION_PASS_VERT_SPV), "vkCreateShaderModule first sample vertex"); + const auto code = fragmentCode(format); + fragment = CreateShaderModule(context, code.data(), code.size_bytes(), "vkCreateShaderModule first sample fragment"); + VkPipelineShaderStageCreateInfo stages[2]{{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}, {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}}; + stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + stages[0].module = vertex; + stages[0].pName = "main"; + stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stages[1].module = fragment; + stages[1].pName = "main"; + VkPipelineVertexInputStateCreateInfo vertexInput{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + VkPipelineInputAssemblyStateCreateInfo assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + const VkViewport viewportRect{0.0f, 0.0f, static_cast(extent.width), static_cast(extent.height), 0.0f, 1.0f}; + const VkRect2D scissor{{0, 0}, extent}; + VkPipelineViewportStateCreateInfo viewport{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; + viewport.viewportCount = 1; + viewport.pViewports = &viewportRect; + viewport.scissorCount = 1; + viewport.pScissors = &scissor; + VkPipelineRasterizationStateCreateInfo rasterization{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; + rasterization.polygonMode = VK_POLYGON_MODE_FILL; + rasterization.cullMode = VK_CULL_MODE_NONE; + rasterization.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterization.lineWidth = 1.0f; + VkPipelineMultisampleStateCreateInfo multisample{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + VkPipelineColorBlendAttachmentState blendAttachment{}; + blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + blend.attachmentCount = 1; + blend.pAttachments = &blendAttachment; + VkGraphicsPipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = stages; + pipelineInfo.pVertexInputState = &vertexInput; + pipelineInfo.pInputAssemblyState = &assembly; + pipelineInfo.pViewportState = &viewport; + pipelineInfo.pRasterizationState = &rasterization; + pipelineInfo.pMultisampleState = &multisample; + pipelineInfo.pColorBlendState = &blend; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + Check(context.Function("vkCreateGraphicsPipelines")(context.device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines first sample"); + } catch (...) { + release(); + throw; + } +} + +FirstSampleCopy::~FirstSampleCopy() { + release(); +} + +void FirstSampleCopy::release() noexcept { + if (pipeline) context.Function("vkDestroyPipeline")(context.device, pipeline, nullptr); + if (fragment) context.Function("vkDestroyShaderModule")(context.device, fragment, nullptr); + if (vertex) context.Function("vkDestroyShaderModule")(context.device, vertex, nullptr); + if (framebuffer) context.Function("vkDestroyFramebuffer")(context.device, framebuffer, nullptr); + if (renderPass) context.Function("vkDestroyRenderPass")(context.device, renderPass, nullptr); + if (pipelineLayout) context.Function("vkDestroyPipelineLayout")(context.device, pipelineLayout, nullptr); + if (descriptorLayout) context.Function("vkDestroyDescriptorSetLayout")(context.device, descriptorLayout, nullptr); + if (sampler) context.Function("vkDestroySampler")(context.device, sampler, nullptr); + if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); + if (image) context.Function("vkDestroyImage")(context.device, image, nullptr); + if (memory) context.Function("vkFreeMemory")(context.device, memory, nullptr); + pipeline = VK_NULL_HANDLE; + fragment = vertex = VK_NULL_HANDLE; + framebuffer = VK_NULL_HANDLE; + renderPass = VK_NULL_HANDLE; + pipelineLayout = VK_NULL_HANDLE; + descriptorLayout = VK_NULL_HANDLE; + sampler = VK_NULL_HANDLE; + view = VK_NULL_HANDLE; + image = VK_NULL_HANDLE; + memory = VK_NULL_HANDLE; +} + +void FirstSampleCopy::Record(Recorder& recorder, VkImageView source, VkImageLayout layout) { + const VkDescriptorPoolSize size{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1}; + VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + poolInfo.maxSets = 1; + poolInfo.poolSizeCount = 1; + poolInfo.pPoolSizes = &size; + VkDescriptorPool created = VK_NULL_HANDLE; + Check(context.Function("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &created), "vkCreateDescriptorPool first sample"); + const auto pool = std::make_shared(context, created); + VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + allocation.descriptorPool = pool->pool; + allocation.descriptorSetCount = 1; + allocation.pSetLayouts = &descriptorLayout; + VkDescriptorSet set = VK_NULL_HANDLE; + Check(context.Function("vkAllocateDescriptorSets")(context.device, &allocation, &set), "vkAllocateDescriptorSets first sample"); + const VkDescriptorImageInfo imageInfo{VK_NULL_HANDLE, source, layout}; + VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + write.dstSet = set; + write.dstBinding = 0; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write.pImageInfo = &imageInfo; + context.Function("vkUpdateDescriptorSets")(context.device, 1, &write, 0, nullptr); + recorder.Keep(pool); + recorder.RecordUncounted([&](VkCommandBuffer commands) { + VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + begin.renderPass = renderPass; + begin.framebuffer = framebuffer; + begin.renderArea = {{0, 0}, extent}; + context.Resolved(&DeviceFunctions::cmdBeginRenderPass, "vkCmdBeginRenderPass")(commands, &begin, VK_SUBPASS_CONTENTS_INLINE); + context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &set, 0, nullptr); + context.Function("vkCmdDraw")(commands, 3, 1, 0, 0); + context.Resolved(&DeviceFunctions::cmdEndRenderPass, "vkCmdEndRenderPass")(commands); + }); +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 610c437369..24f1fc3e13 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -1,10 +1,13 @@ #include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "ThreadOwned.hpp" +#include "prx/libSceAgcDriver/Graphics/include/SnapshotCopies.hpp" #include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ResidentMirror.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -12,6 +15,7 @@ #include "prx/libc/include/CpuTopology.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" #include "prx/libc/include/general/AtomicSharedPtr.hpp" #ifdef _WIN32 #include @@ -65,12 +69,16 @@ struct ImageMirror { std::uint64_t bytes = 0; bool writable = false; bool heap = false; + bool joinedHeap = false; bool sparse = false; std::uint64_t reserved = 0; std::vector> committed; std::vector generations; std::weak_ptr range; std::shared_ptr buffer; + std::shared_ptr resident; + std::uint64_t accounted = 0; + bool listed = false; // Writable ranges only: the guest bytes the mirror holds, compared on refresh and at write-back. std::vector shadow; // Identity for the life of this mirror (see ImageMirrorSerial); the top bit keeps mirror serials @@ -116,7 +124,7 @@ struct GuestBufferMemory::AddressSpace { // The BDA table entries of `base`, in its order. std::vector ranges; std::shared_ptr>> directReads; - mutable std::mutex tableMutex; + mutable AgcDriver::Mutex tableMutex; struct Table { std::uint64_t serial; std::shared_ptr> ranges; @@ -227,18 +235,18 @@ struct ImportSettle { // Imports persist across draws, keyed by allocation base, and are dropped when their allocation leaves // the registered set; a dropped import is destroyed once the recorded work that may read it completed. struct HostImports { - std::mutex mutex; + AgcDriver::Mutex mutex; VkDevice device = VK_NULL_HANDLE; PFN_vkDestroyBuffer destroyBuffer = nullptr; PFN_vkFreeMemory freeMemory = nullptr; std::map imports; - std::map trimmed; std::map>> parked; std::uint64_t liveBytes = 0; std::map failed; std::map failedJoins; std::map sparseRunEnds; std::map sparseRunJoins; + std::set> importedAtMap; std::map, ImportFailure> failedBlocks; std::map, ImportRefusal> refusals; std::map, EvictingRefusal> evictingRefusals; @@ -278,6 +286,7 @@ struct HostImports { bool allocationFailed = false; std::uint8_t reportNoted = 0; std::map spaceUses; + std::uint64_t budgetRefusals = 0; }; HostImports& Imports() { @@ -287,7 +296,7 @@ HostImports& Imports() { #ifdef _WIN32 void waitSettleBatch(const ImportSettle& settle, VkDevice device, const SettleBatch& batch) { - if (batch.busy && settle.waitForFences != nullptr) static_cast(settle.waitForFences(device, 1, &batch.fence, VK_TRUE, 10'000'000'000ull)); + if (batch.busy && settle.waitForFences != nullptr) Check(settle.waitForFences(device, 1, &batch.fence, VK_TRUE, 60'000'000'000ull), "vkWaitForFences (settle batch)"); } bool settleBatchRunning(const ImportSettle& settle, VkDevice device, const SettleBatch& batch) { @@ -300,7 +309,7 @@ SettleBatch* forgetSettle(HostImports& state, std::uint64_t base) { SettleBatch* running = nullptr; if (const auto settle = state.settles.find(found->second.device); found->second.phase == SettlePhase::Submitted && settle != state.settles.end()) { auto& batch = settle->second.batches[found->second.batch]; - if (settleBatchRunning(settle->second, settle->first, batch)) running = &batch; + if (batch.busy) running = &batch; } state.pendingSettles.erase(found); return running; @@ -427,6 +436,8 @@ bool releaseSettleRetired(HostImports& state, std::uint64_t begin, std::uint64_t ++state.settleUnmapWaits; waitSettleBatch(settle, device, batch); } + for (const auto alias : batch.aliases) settle.settleDestroyBuffer(device, alias, nullptr); + batch.aliases.clear(); for (auto& holder : batch.retired) released.push_back(std::move(holder)); batch.retired.clear(); } @@ -458,64 +469,6 @@ void shareDmaBufImport(const Context& context, VkBufferCreateInfo& info, ImportF } #endif -void releaseTrimmed(HostImports& state, ImportNode node) { - state.liveBytes -= node.mapped().pinnedBytes; - auto holder = std::make_shared(state.device, state.destroyBuffer, state.freeMemory, std::move(node)); - auto* recorder = Recorder::Active(); - if (recorder != nullptr) recorder->KeepThroughHold(std::move(holder)); -} - -void dropTrimmed(HostImports& state, std::uint64_t begin, std::uint64_t end) { - for (auto it = state.trimmed.begin(); it != state.trimmed.end();) { - const auto next = std::next(it); - if (it->first < end && begin < it->first + it->second.bytes) releaseTrimmed(state, state.trimmed.extract(it)); - it = next; - } -} - -void retireOrTrim(VkDevice device, HostImports& state, std::map::iterator it, const GuestAllocations::Lease& lease, std::uint64_t begin, std::uint64_t end) { - const auto& entry = it->second; - const auto entryEnd = entry.base + entry.bytes; - std::uint64_t first = 0; - std::uint64_t last = 0; - if (begin <= entry.base && end > entry.base && end < entryEnd) { - first = end; - last = entryEnd; - } else if (begin > entry.base && begin < entryEnd && end >= entryEnd) { - first = entry.base; - last = begin; - } else if (begin > entry.base && begin < end && end < entryEnd) { - const bool tail = entryEnd - end >= begin - entry.base; - first = tail ? end : entry.base; - last = tail ? entryEnd : begin; - } - const bool plain = entry.alias == nullptr && !entry.dmaBuf && entry.chunk == nullptr && !entry.joined && entry.ranges.size() == 1 && entry.backingSerial != 0 && entry.memoryType != std::numeric_limits::max() && entry.base == entry.range->address && entry.bytes == entry.range->bytes; -#ifdef _WIN32 - const bool settling = state.pendingSettles.contains(it->first); -#else - const bool settling = false; -#endif - if (first == last || !plain || settling || state.trimmed.contains(first)) { - retireImport(device, state, it, lease, false); - return; - } - RetireShadow(device, entry, [&lease](std::uint64_t from, std::uint64_t to) { return runCovers(lease, from, to); }); - ++state.epoch; - auto node = state.imports.extract(it); - auto& kept = node.mapped(); - std::shared_ptr holder(nullptr, [device = state.device, destroy = state.destroyBuffer, buffer = std::exchange(kept.buffer, VK_NULL_HANDLE)](void*) { destroy(device, buffer, nullptr); }); - kept.range.reset(); - kept.ranges.clear(); - kept.base = first; - kept.bytes = last - first; - kept.address = 0; - kept.serial = 0; - node.key() = first; - state.trimmed.insert(std::move(node)); - auto* recorder = Recorder::Active(); - if (recorder != nullptr && !recorder->Idle()) recorder->Keep(std::move(holder)); -} - VkResult createImportBuffer(const Context& context, VkExternalMemoryHandleTypeFlagBits handleType, VkDeviceSize size, VkBuffer* buffer) { const VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, nullptr, static_cast(handleType)}; VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external}; @@ -639,7 +592,7 @@ struct ImportChunk { }; struct ImportChunks { - std::mutex mutex; + AgcDriver::Mutex mutex; VkDevice device = VK_NULL_HANDLE; std::map, std::weak_ptr> chunks; }; @@ -917,6 +870,7 @@ bool importsStale(const Context& context, const HostImports& state); constexpr std::uint64_t settleFloorBytes = 0x100000; #ifdef _WIN32 constexpr std::uint64_t settleBlockBytes = 0x10000; +constexpr std::uint64_t settlePageBytes = 0x1000; constexpr auto settlePollPeriod = std::chrono::microseconds(100); std::atomic deferredSettles{std::getenv("APS5_NO_DEFERRED_SETTLES") == nullptr}; #endif @@ -1037,7 +991,7 @@ const char* prepareSettleBatch(const Context& context, ImportSettle& settle, Set } if (batch.commands == VK_NULL_HANDLE) { VkCommandBufferAllocateInfo allocate{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; - allocate.commandPool = context.settlePool; + allocate.commandPool = context.pool; allocate.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocate.commandBufferCount = 1; if ((result = context.Function("vkAllocateCommandBuffers")(context.device, &allocate, &batch.commands)) != VK_SUCCESS) { @@ -1087,9 +1041,7 @@ void submitSettleBatch(const Context& context, HostImports& state) { begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; if ((result = context.Function("vkBeginCommandBuffer")(batch.commands, &begin)) != VK_SUCCESS) return fail("vkBeginCommandBuffer (settle batch)"); const auto copy = context.Resolved(&DeviceFunctions::cmdCopyBuffer, "vkCmdCopyBuffer"); - const bool otherFamily = context.settleFamily != context.queueFamily; - const auto createBuffer = otherFamily ? context.Function("vkCreateBuffer") : nullptr; - const auto bindBufferMemory = otherFamily ? context.Function("vkBindBufferMemory") : nullptr; + RecordMemoryBarrier(context, batch.commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); std::size_t consumed = 0; for (; consumed < state.settleWanted.size() && batch.bases.size() < settleBatchCopies; ++consumed) { const auto base = state.settleWanted[consumed]; @@ -1100,38 +1052,19 @@ void submitSettleBatch(const Context& context, HostImports& state) { state.pendingSettles.erase(settle); continue; } - VkBuffer source = import->second.buffer; - if (otherFamily) { - VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO}; - external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT; - VkBufferCreateInfo aliasInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external}; - aliasInfo.size = std::min(import->second.bytes, 65536); - aliasInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - VkBuffer alias = VK_NULL_HANDLE; - if (createBuffer(context.device, &aliasInfo, nullptr, &alias) != VK_SUCCESS) { - state.pendingSettles.erase(settle); - continue; - } - if (bindBufferMemory(context.device, alias, import->second.memory, 0) != VK_SUCCESS) { - destroyBuffer(context.device, alias, nullptr); - state.pendingSettles.erase(settle); - continue; - } - batch.aliases.push_back(alias); - source = alias; - } const VkBufferCopy region{0, (VkDeviceSize{index} * settleBatchCopies + batch.bases.size()) * 4u, 4}; - copy(batch.commands, source, deviceSettle.batchTarget, 1, ®ion); + copy(batch.commands, import->second.buffer, deviceSettle.batchTarget, 1, ®ion); settle->second = PendingSettle{SettlePhase::Submitted, index, context.device}; batch.bases.push_back(base); } state.settleWanted.erase(state.settleWanted.begin(), state.settleWanted.begin() + static_cast(consumed)); + RecordMemoryBarrier(context, batch.commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_WRITE_BIT); if ((result = context.Function("vkEndCommandBuffer")(batch.commands)) != VK_SUCCESS) return fail("vkEndCommandBuffer (settle batch)"); if (batch.bases.empty()) return; VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO}; submit.commandBufferCount = 1; submit.pCommandBuffers = &batch.commands; - if ((result = context.Function("vkQueueSubmit")(context.settleQueue, 1, &submit, batch.fence)) != VK_SUCCESS) return fail("vkQueueSubmit (settle batch)"); + if ((result = context.Function("vkQueueSubmit")(context.queue, 1, &submit, batch.fence)) != VK_SUCCESS) return fail("vkQueueSubmit (settle batch)"); batch.busy = true; state.settlesSubmitted += batch.bases.size(); } @@ -1164,8 +1097,22 @@ void reapSettleBatches(const Context& context, HostImports& state) { } } +bool comparedImportShares(const HostImports& state, std::uint64_t own, std::uint64_t begin, std::uint64_t end) { + const auto shares = [&](const HostImport& entry) { return entry.unwatched && entry.memoryBase < end && begin < entry.memoryBase + entry.pinnedBytes; }; + for (const auto& [base, entry] : state.imports) { + if (base != own && shares(entry)) return true; + } + for (const auto& [device, list] : state.parked) { + for (const auto& retired : list) { + if (shares(retired->node.mapped())) return true; + } + } + return false; +} + void rewatchSettledImports(HostImports& state) { if (state.settleReady.empty()) return; + std::vector bases; std::vector> ranges; for (const auto base : state.settleReady) { const auto settle = state.pendingSettles.find(base); @@ -1173,19 +1120,23 @@ void rewatchSettledImports(HostImports& state) { state.pendingSettles.erase(settle); const auto import = state.imports.find(base); if (import == state.imports.end() || !import->second.unwatched) continue; - const std::array, 1> span{{{base, base + import->second.bytes}}}; + const auto first = base & ~(settleBlockBytes - 1); + const auto stop = (base + import->second.bytes + settleBlockBytes - 1) & ~(settleBlockBytes - 1); + if ((first != base || stop != base + import->second.bytes) && comparedImportShares(state, base, first, stop)) continue; + const std::array, 1> span{{{first, stop}}}; if (StorageTexture::AnyLiveOverlaps(span)) { ++state.settlesKeptCompared; continue; } - ranges.push_back(span.front()); + bases.push_back(base); + ranges.emplace_back(base & ~(settlePageBytes - 1), (base + import->second.bytes + settlePageBytes - 1) & ~(settlePageBytes - 1)); } state.settleReady.clear(); if (ranges.empty()) return; GuestMemory::Rewatch(ranges); - for (const auto& [begin, end] : ranges) { - const auto import = state.imports.find(begin); - if (import == state.imports.end() || !GuestMemory::Watched(begin, static_cast(end - begin))) continue; + for (const auto base : bases) { + const auto import = state.imports.find(base); + if (import == state.imports.end() || !GuestMemory::Watched(base, static_cast(import->second.bytes))) continue; import->second.unwatched = false; ++state.settlesRewatched; } @@ -1200,6 +1151,49 @@ void settleUnwatchedImports(const Context& context, HostImports& state) { rewatchSettledImports(state); submitSettleBatch(context, state); } + +void settleImportsNow(const Context& context, HostImports& state, std::uint64_t begin, std::uint64_t end) { + if (context.settleQueue == VK_NULL_HANDLE || context.settleQueueMutex == nullptr || !GuestMemory::GpuMutex().HeldByThisThread()) return; + std::vector needed; + auto it = state.imports.lower_bound(begin); + if (it != state.imports.begin()) --it; + for (; it != state.imports.end() && it->first < end; ++it) { + auto& entry = it->second; + if (entry.base + entry.bytes <= begin || !entry.unwatched) continue; + auto settle = state.pendingSettles.find(it->first); + if (settle == state.pendingSettles.end()) { + if (!entry.settleOnDemand || !deferredSettles.load(std::memory_order_relaxed)) continue; + entry.settleOnDemand = false; + settle = state.pendingSettles.insert_or_assign(it->first, PendingSettle{}).first; + } + if (settle->second.phase == SettlePhase::Wanted) { + std::erase(state.settleWanted, it->first); + state.settleWanted.insert(state.settleWanted.begin(), it->first); + } + needed.push_back(it->first); + } + if (needed.empty()) return; + auto& deviceSettle = settleFor(context, state); + const auto wanted = [&] { + return std::any_of(needed.begin(), needed.end(), [&](std::uint64_t base) { + const auto settle = state.pendingSettles.find(base); + return settle != state.pendingSettles.end() && settle->second.phase == SettlePhase::Wanted; + }); + }; + while (wanted()) { + if (std::all_of(deviceSettle.batches.begin(), deviceSettle.batches.end(), [](const SettleBatch& batch) { return batch.busy; })) { + for (const auto& batch : deviceSettle.batches) waitSettleBatch(deviceSettle, context.device, batch); + reapSettleBatches(context, state); + } + submitSettleBatch(context, state); + } + for (const auto base : needed) { + const auto settle = state.pendingSettles.find(base); + if (settle != state.pendingSettles.end() && settle->second.phase == SettlePhase::Submitted && settle->second.device == context.device) waitSettleBatch(deviceSettle, context.device, deviceSettle.batches[settle->second.batch]); + } + reapSettleBatches(context, state); + rewatchSettledImports(state); +} #endif void decideImportWatch(const Context& context, HostImports& state) { @@ -1312,9 +1306,45 @@ bool stillRefused(const HostImports& state, const ImportRefusal* refusal, std::u return refusal != nullptr && budget != 0 && budget <= refusal->budget && state.liveBytes >= refusal->liveBytes; } -ImportRun importRun(const GuestAllocations::Lease& lease, std::uint64_t address, std::uint64_t largest = std::numeric_limits::max(), std::uint64_t budget = 0) { +struct RunBounds { + std::size_t first = 0; + std::size_t last = 0; + bool found = false; +}; + +std::pair walkRun(const GuestAllocations::Lease& lease, std::size_t index, std::uint64_t low, std::uint64_t high) { + auto first = index; + while (first > 0 && lease[first - 1]->address >= low && continuesRun(*lease[first - 1], *lease[first])) --first; + auto last = index; + while (last + 1 < lease.size() && lease[last + 1]->address + lease[last + 1]->bytes <= high && continuesRun(*lease[last], *lease[last + 1])) ++last; + return {first, last}; +} + +std::pair continuedRun(const GuestAllocations::Lease& lease, std::size_t index, std::uint64_t window, std::uint64_t low, std::uint64_t high) { + static const bool memo = std::getenv("APS5_NO_RUN_MEMO") == nullptr; + if (!memo) return walkRun(lease, index, low, high); + struct Memo { + std::weak_ptr snapshot; + std::size_t index = 0; + std::uint64_t window = 0; + std::uint64_t low = 0; + std::pair run; + }; + struct MemoStorage {}; + auto& memos = HostThreadLocal, MemoStorage>(); + const auto& snapshot = lease.Snapshot(); + const auto block = window != 0 ? low / window : 0; + const auto slot = (reinterpret_cast(snapshot.get()) / 16u ^ index * 0x9e3779b1u ^ block * 0x85ebca6bu) % memos.size(); + auto& entry = memos[slot]; + if (!entry.snapshot.owner_before(snapshot) && !snapshot.owner_before(entry.snapshot) && entry.index == index && entry.window == window && entry.low == low) return entry.run; + entry = Memo{snapshot, index, window, low, walkRun(lease, index, low, high)}; + return entry.run; +} + +RunBounds importRunBounds(const GuestAllocations::Lease& lease, std::uint64_t address, std::uint64_t largest = std::numeric_limits::max(), std::uint64_t budget = 0) { const auto index = leasedIndexContaining(lease, address); if (index == lease.size()) return {}; + const RunBounds single{index, index, true}; const auto& containing = *lease[index]; const auto& state = Imports(); const auto window = importWindow.load(std::memory_order_relaxed); @@ -1323,14 +1353,11 @@ ImportRun importRun(const GuestAllocations::Lease& lease, std::uint64_t address, if (window != 0) { low = address / window * window; high = low + window; - if (containing.address < low || containing.address + containing.bytes > high) return leasedRun(lease, index, index); + if (containing.address < low || containing.address + containing.bytes > high) return single; } - auto first = index; - while (first > 0 && lease[first - 1]->address >= low && continuesRun(*lease[first - 1], *lease[first])) --first; - auto last = index; - while (last + 1 < lease.size() && lease[last + 1]->address + lease[last + 1]->bytes <= high && continuesRun(*lease[last], *lease[last + 1])) ++last; + const auto [first, last] = continuedRun(lease, index, window, low, high); const auto skipped = [&](std::size_t from, std::size_t to) { return rememberedRun(state.failedBlocks, lease, from, to) != nullptr || stillRefused(state, rememberedRun(state.refusals, lease, from, to), budget); }; - if (window == 0) return first == last || lease[last]->address + lease[last]->bytes - lease[first]->address > largest || skipped(first, last) ? leasedRun(lease, index, index) : leasedRun(lease, first, last); + if (window == 0) return first == last || lease[last]->address + lease[last]->bytes - lease[first]->address > largest || skipped(first, last) ? single : RunBounds{first, last, true}; const auto bounding = [&](std::size_t from, std::size_t to, std::uint64_t value, bool start) { const auto found = std::lower_bound(lease.begin() + static_cast(from), lease.begin() + static_cast(to) + 1, value, [start](const auto& range, std::uint64_t wanted) { return (start ? range->address : range->address + range->bytes) < wanted; }); const auto position = static_cast(found - lease.begin()); @@ -1347,9 +1374,23 @@ ImportRun importRun(const GuestAllocations::Lease& lease, std::uint64_t address, if (blockFirst == lease.size() || blockLast == lease.size()) continue; if (blockFirst == blockLast) break; if (skipped(blockFirst, blockLast)) continue; - return leasedRun(lease, blockFirst, blockLast); + return {blockFirst, blockLast, true}; } - return leasedRun(lease, index, index); + return single; +} + +ImportRun importRun(const GuestAllocations::Lease& lease, std::uint64_t address, std::uint64_t largest = std::numeric_limits::max(), std::uint64_t budget = 0) { + const auto bounds = importRunBounds(lease, address, largest, budget); + return bounds.found ? leasedRun(lease, bounds.first, bounds.last) : ImportRun{}; +} + +template +bool sameRun(const Entry& entry, const GuestAllocations::Lease& lease, const RunBounds& bounds) { + if (!bounds.found || lease[bounds.first]->address != entry.base || lease[bounds.last]->address + lease[bounds.last]->bytes - entry.base != entry.bytes || bounds.last - bounds.first + 1 != entry.ranges.size()) return false; + for (std::size_t index = 0; index < entry.ranges.size(); ++index) { + if (entry.ranges[index].owner_before(lease[bounds.first + index]) || lease[bounds.first + index].owner_before(entry.ranges[index])) return false; + } + return true; } ImportRun exactRun(const GuestAllocations::Lease& lease, std::uint64_t base, std::uint64_t bytes) { @@ -1364,8 +1405,8 @@ ImportRun exactRun(const GuestAllocations::Lease& lease, std::uint64_t base, std } bool runCovers(const GuestAllocations::Lease& lease, std::uint64_t begin, std::uint64_t end) { - const auto run = importRun(lease, begin); - return !run.ranges.empty() && end <= run.base + run.bytes; + const auto bounds = importRunBounds(lease, begin); + return bounds.found && end <= lease[bounds.last]->address + lease[bounds.last]->bytes; } ImportRun crossedRun(const GuestAllocations::Lease& lease, std::uint64_t begin, std::uint64_t end) { @@ -1388,7 +1429,7 @@ ImportRun joinedRun(const GuestAllocations::Lease& lease, std::uint64_t begin, s auto run = crossedRun(lease, begin, end); if (run.ranges.empty()) return run; if (std::all_of(run.ranges.begin(), run.ranges.end(), [window](const auto& range) { return range->bytes > window; })) return run; - if (begin == run.base && end == run.base + run.bytes) return run; + if (begin == run.base) return run; return {}; } @@ -1407,9 +1448,6 @@ bool sameRun(const Entry& entry, const ImportRun& run) { bool wouldRetireImport(const HostImports& state, const ImportRun& run) { if (run.ranges.empty()) return false; - for (const auto& [base, entry] : state.trimmed) { - if (base < run.base + run.bytes && run.base < base + entry.bytes) return true; - } const auto next = state.imports.lower_bound(run.base); if (next != state.imports.end()) { if (next->first == run.base && sameRun(next->second, run)) return false; @@ -1423,7 +1461,7 @@ bool wouldRetireImport(const HostImports& state, const ImportRun& run) { bool sameRange(const ImportFailure& entry, const GuestAllocations::Lease& lease, std::uint64_t budget = 0) { const auto current = entry.ranges.size() == 1 ? leasedRangeOwner(lease, entry.base, entry.base + entry.bytes) : nullptr; if (current != nullptr && entry.bytes < current->bytes) return !entry.ranges.front().owner_before(current) && !current.owner_before(entry.ranges.front()); - return sameRun(entry, importRun(lease, entry.base, std::numeric_limits::max(), budget)); + return sameRun(entry, lease, importRunBounds(lease, entry.base, std::numeric_limits::max(), budget)); } bool sameRange(const HostImport& entry, const GuestAllocations::Lease& lease, std::uint64_t budget = 0) { @@ -1431,7 +1469,8 @@ bool sameRange(const HostImport& entry, const GuestAllocations::Lease& lease, st const auto current = leasedRangeOwner(lease, entry.base, entry.base + entry.bytes); return current != nullptr && !entry.ranges.front().owner_before(current) && !current.owner_before(entry.ranges.front()); } - return sameRun(entry, entry.joined ? joinedRun(lease, entry.base, entry.base + entry.bytes) : importRun(lease, entry.base, std::numeric_limits::max(), budget)); + if (entry.joined) return sameRun(entry, joinedRun(lease, entry.base, entry.base + entry.bytes)); + return sameRun(entry, lease, importRunBounds(lease, entry.base, std::numeric_limits::max(), budget)); } std::uint64_t effectiveUse(const HostImports& state, const HostImport& entry) { @@ -1454,8 +1493,32 @@ bool evictingRefusalCurrent(const HostImports& state, const EvictingRefusal& ref 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); } +void noteRetiredUnwatched(std::vector>& spans, const HostImport& entry, const GuestAllocations::Lease& lease) { + if (!entry.unwatched) return; + for (const auto& range : entry.ranges) { + if (leasedRangeOwner(lease, range->address) != range) continue; + const auto begin = std::max(range->address, entry.base); + const auto end = std::min(range->address + range->bytes, entry.base + entry.bytes); + if (begin < end) spans.emplace_back(begin, end); + } +} + +void rewatchRetired(const HostImports& state, std::vector> spans) { + std::erase_if(spans, [&](const auto& span) { + auto it = state.imports.upper_bound(span.first); + if (it != state.imports.begin()) --it; + for (; it != state.imports.end() && it->first < span.second; ++it) { + if (it->second.unwatched && span.first < it->first + it->second.bytes) return true; + } + const std::array, 1> one{{span}}; + return StorageTexture::AnyLiveOverlaps(one); + }); + if (!spans.empty()) GuestMemory::Rewatch(spans); +} + std::uint64_t retireLeastRecent(const Context& context, HostImports& state, const GuestAllocations::Lease& lease, std::uint64_t wanted) { std::uint64_t freed = 0; + std::vector> unwatched; while (freed < wanted) { auto oldest = state.imports.end(); for (auto it = state.imports.begin(); it != state.imports.end(); ++it) { @@ -1463,8 +1526,10 @@ std::uint64_t retireLeastRecent(const Context& context, HostImports& state, cons } if (oldest == state.imports.end()) break; freed += oldest->second.pinnedBytes; + noteRetiredUnwatched(unwatched, oldest->second, lease); retireImport(context.device, state, oldest, lease); } + rewatchRetired(state, std::move(unwatched)); return freed; } @@ -1551,20 +1616,29 @@ std::string budgetSource(const Context& context, const HostImports& state, std:: } std::atomic rangeImportWindow{64ull << 20u}; +std::atomic importMaxBytes{0}; // The import of an access to a registered range larger than the range window: the cells the access touches, grown to // the union of the range's imports it overlaps, so accesses across neighbouring cell edges share one import instead of // replacing each other's. -ImportRun windowRun(const HostImports& state, ImportRun run, std::uint64_t begin, std::uint64_t end, std::uint64_t alignment) { +std::optional> importWindowBounds(const GuestAllocations::Range& range, std::uint64_t begin, std::uint64_t end, std::uint64_t alignment) { const auto window = rangeImportWindow.load(std::memory_order_relaxed); - if (run.ranges.size() != 1 || window == 0 || alignment == 0 || window % alignment != 0) return run; - const auto& range = *run.ranges.front(); - if (range.bytes <= window || begin < range.address || end > range.address + range.bytes || end <= begin) return run; + if (window == 0 || alignment == 0 || window % alignment != 0 || range.bytes <= window || begin < range.address || end > range.address + range.bytes || end <= begin) return std::nullopt; const auto lastCell = range.bytes / window - 1; const auto cell = [&](std::uint64_t address) { return std::min((address - range.address) / window, lastCell); }; - auto windowBegin = range.address + cell(begin) * window; + const auto windowBegin = range.address + cell(begin) * window; const auto endCell = cell(end - 1); - auto windowEnd = endCell == lastCell ? range.address + range.bytes : range.address + (endCell + 1) * window; + const auto windowEnd = endCell == lastCell ? range.address + range.bytes : range.address + (endCell + 1) * window; + if (windowBegin % alignment != 0 || (windowEnd - windowBegin) % alignment != 0) return std::nullopt; + return std::pair{windowBegin, windowEnd}; +} + +ImportRun windowRun(const HostImports& state, ImportRun run, std::uint64_t begin, std::uint64_t end, std::uint64_t alignment) { + if (run.ranges.size() != 1) return run; + const auto& range = *run.ranges.front(); + const auto bounds = importWindowBounds(range, begin, end, alignment); + if (!bounds) return run; + auto [windowBegin, windowEnd] = *bounds; for (bool grown = true; grown;) { grown = false; auto it = state.imports.upper_bound(windowBegin); @@ -1639,31 +1713,100 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s } } const auto budget = evict ? 0 : effectiveBudget(context, state); - if (const auto index = leasedIndexContaining(lease, address); budget != 0 && index != lease.size() && stillRefused(state, rememberedRun(state.refusals, lease, index, index), budget) && findImport(state, address, address + 1) == nullptr) { + const auto index = leasedIndexContaining(lease, address); + const auto windowBounds = index != lease.size() && end != 0 ? importWindowBounds(*lease[index], address, end, context.hostImportAlignment) : std::nullopt; + const bool complete = !windowBounds || (windowBounds->first == lease[index]->address && windowBounds->second == lease[index]->address + lease[index]->bytes); + if (budget != 0 && index != lease.size() && complete && stillRefused(state, rememberedRun(state.refusals, lease, index, index), budget) && findImport(state, address, address + 1) == nullptr) { auto& counts = state.counts[static_cast(requester)]; ++counts.refused; counts.refusedBytes += lease[index]->bytes; ++counts.remembered; + ++state.budgetRefusals; return nullptr; } - auto run = importRun(lease, address, largest, budget); + const auto bounds = importRunBounds(lease, address, largest, budget); // Without an end the caller views the whole run through the import (AcquireRegistered): a window that holds the // first byte is shorter than that, so only an import of the whole run is a hit; a window is replaced below. if (fresh && end == 0) { - if (const auto* current = findImport(state, address, run.ranges.empty() ? address + 1 : run.base + run.bytes)) return current; + if (const auto* current = findImport(state, address, bounds.found ? lease[bounds.last]->address + lease[bounds.last]->bytes : address + 1)) return current; } + auto run = bounds.found ? leasedRun(lease, bounds.first, bounds.last) : ImportRun{}; if (end != 0) run = windowRun(state, std::move(run), address, end, context.hostImportAlignment); if (run.ranges.empty()) return nullptr; return importRunAt(context, state, std::move(run), false, address, lease, end, evict, requester); } +struct ImportPiece { + std::uint64_t begin; + std::uint64_t end; + const HostImport* entry; +}; + +std::vector importPieces(const Context& context, HostImports& state, const GuestAllocations::Range& range, const GuestAllocations::Lease& lease) { + const auto limit = importMaxBytes.load(std::memory_order_relaxed); + const auto window = rangeImportWindow.load(std::memory_order_relaxed); + const auto alignment = context.hostImportAlignment; + if (limit < 2 * window || window == 0 || alignment == 0 || window % alignment != 0 || range.address % alignment != 0) return {}; + const auto step = (limit / window - 1) * window; + const auto end = range.address + range.bytes; + std::vector pieces; + for (auto cursor = range.address; cursor < end;) { + const auto wanted = end - cursor < step + window ? end : cursor + step; + const auto* entry = importAllocation(context, state, cursor, lease, ImportRequester::Build, wanted, false); + if (entry == nullptr || entry->base > cursor || entry->bytes > limit) return {}; + const auto to = std::min(end, entry->base + entry->bytes); + if (to <= cursor) return {}; + pieces.push_back({cursor, to, entry}); + cursor = to; + } + return pieces; +} + +std::vector budgetedImportPieces(const Context& context, HostImports& state, const GuestAllocations::Range& range, const GuestAllocations::Lease& lease) { + const auto window = rangeImportWindow.load(std::memory_order_relaxed); + const auto alignment = context.hostImportAlignment; + if (!range.releasable || !GuestMemory::Watched(range.address, range.bytes) || window == 0 || alignment == 0 || window % alignment != 0 || range.address % alignment != 0 || range.bytes <= window) return {}; + const auto end = range.address + range.bytes; + std::vector pieces; + for (auto cursor = range.address; cursor < end;) { + const HostImport* entry = nullptr; + if (const auto* previous = peekImport(state, cursor, cursor + 1); previous != nullptr && previous->base >= range.address && previous->base + previous->bytes <= end) { + entry = importAllocation(context, state, cursor, lease, ImportRequester::Build, std::min(end, previous->base + previous->bytes), false); + } + if (entry == nullptr) { + const auto budget = effectiveBudget(context, state); + auto room = budget - std::min(budget, state.liveBytes); + if (const auto limit = importMaxBytes.load(std::memory_order_relaxed); limit != 0) room = std::min(room, limit); + if (room < window) break; + auto wanted = cursor + std::min(end - cursor, window); + if (const auto bounds = importWindowBounds(range, cursor, wanted, alignment); bounds && bounds->second - bounds->first > room) { + wanted = range.address + (range.bytes / window - 1) * window; + if (wanted <= cursor) break; + } + entry = importAllocation(context, state, cursor, lease, ImportRequester::Build, wanted, false); + } + if (entry == nullptr || entry->base > cursor) break; + const auto to = std::min(end, entry->base + entry->bytes); + if (to <= cursor) break; + pieces.push_back({cursor, to, entry}); + cursor = to; + } + if (pieces.empty()) return {}; + if (pieces.back().end != end) pieces.push_back({pieces.back().end, end, nullptr}); + return pieces; +} + const HostImport* importJoined(const Context& context, HostImports& state, std::uint64_t begin, std::uint64_t end, const GuestAllocations::Lease& lease) { if (const auto* low = peekImport(state, begin, begin + 1); low != nullptr && low->joined) begin = low->base; if (const auto* high = peekImport(state, end - 1, end); high != nullptr && high->joined) end = high->base + high->bytes; auto run = joinedRun(lease, begin, end); if (run.ranges.empty()) return nullptr; if (const auto failed = state.failedJoins.find(run.base); failed != state.failedJoins.end() && sameRun(failed->second, run)) return nullptr; - return importRunAt(context, state, std::move(run), true, begin, lease, end, true, ImportRequester::Join); + const auto existing = state.imports.find(run.base); + const bool existed = existing != state.imports.end() && sameRun(existing->second, run); + const auto* entry = importRunAt(context, state, std::move(run), true, begin, lease, end, true, ImportRequester::Join); + if (entry != nullptr && !existed) ++state.epoch; + return entry; } 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) { @@ -1682,11 +1825,11 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import return &found->second; } if (alignment == 0 || base % alignment != 0 || bytes % alignment != 0) return nullptr; + if (const auto limit = importMaxBytes.load(std::memory_order_relaxed); limit != 0 && bytes > limit) return nullptr; if (const auto failed = failures.find(base); !stepDown && failed != failures.end()) { if (sameRun(failed->second, run)) return nullptr; failures.erase(failed); } - dropTrimmed(state, base, base + bytes); const auto budget = effectiveBudget(context, state); const auto containing = std::find_if(run.ranges.begin(), run.ranges.end(), [address](const auto& range) { return address >= range->address && address < range->address + range->bytes; }); if (const auto remembered = state.evictingRefusals.find({base, bytes}); evict && remembered != state.evictingRefusals.end()) { @@ -1744,6 +1887,7 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import 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 (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; return nullptr; } for (auto it = first; it != state.imports.end() && it->first < base + bytes;) { @@ -1832,7 +1976,7 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import GuestMemory::ImportWatched(base, bytes, [&] { step = createBackingImport(context, entry, backing, result); return step == nullptr; - }, watchedDmaBuf); + }, watchedDmaBuf, false); } if (step == nullptr && entry.dmaBuf && bytes >= settleFloorBytes && context.settleQueue != VK_NULL_HANDLE && context.settleQueueMutex != nullptr) { static const bool traceSettle = std::getenv("APS5_TRACE_HOST_IMPORT") != nullptr; @@ -1896,8 +2040,10 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import entry.joined = joined; entry.lastUse = ++state.useCounter; #ifdef _WIN32 - if (deferredSettles.load(std::memory_order_relaxed) && entry.alias == nullptr && state.watchPrivateImports && writable && base % settleBlockBytes == 0 && bytes % settleBlockBytes == 0) { - if (entry.unwatched) { + if (deferredSettles.load(std::memory_order_relaxed) && entry.alias == nullptr && state.watchPrivateImports && writable) { + if (base % settleBlockBytes != 0 || bytes % settleBlockBytes != 0) { + entry.settleOnDemand = entry.unwatched; + } else if (entry.unwatched) { state.pendingSettles.insert_or_assign(base, PendingSettle{}); state.settleWanted.push_back(base); } else if (settled && !GuestMemory::Watched(base, static_cast(bytes))) { @@ -1920,46 +2066,6 @@ const GuestAllocations::Range* containingRange(const GuestAllocations::Lease& le return range->readable && begin >= range->address && end <= range->address + range->bytes ? range.get() : nullptr; } -bool reattachTrimmed(const Context& context, HostImports& state, HostImport& entry, const GuestAllocations::Lease& lease) { - const auto first = entry.base; - const auto last = entry.base + entry.bytes; - std::shared_ptr kept; - for (auto it = std::lower_bound(lease.begin(), lease.end(), first, [](const auto& range, std::uint64_t value) { return range->address < value; }); it != lease.end() && (*it)->address < last; ++it) { - if ((*it)->backingSerial == entry.backingSerial && (*it)->address + (*it)->bytes <= last && (kept == nullptr || (*it)->bytes > kept->bytes)) kept = *it; - } - if (kept == nullptr) return false; - const auto next = state.imports.lower_bound(kept->address); - if (next != state.imports.end() && next->first < kept->address + kept->bytes) return false; - if (next != state.imports.begin() && std::prev(next)->second.base + std::prev(next)->second.bytes > kept->address) return false; - HostImport candidate{kept->address, kept->bytes, VK_NULL_HANDLE, VK_NULL_HANDLE, 0}; - candidate.range = kept; - candidate.ranges = {kept}; - if (!sameRange(candidate, lease)) return false; - const auto offset = kept->address - entry.memoryBase; - VkBuffer buffer = VK_NULL_HANDLE; - if (createImportBuffer(context, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, kept->bytes, &buffer) != VK_SUCCESS) return false; - const auto refuse = [&] { - context.Function("vkDestroyBuffer")(context.device, buffer, nullptr); - return false; - }; - VkMemoryRequirements requirements{}; - context.Function("vkGetBufferMemoryRequirements")(context.device, buffer, &requirements); - if (((requirements.memoryTypeBits >> entry.memoryType) & 1u) == 0 || requirements.alignment == 0 || offset % requirements.alignment != 0 || requirements.size > entry.pinnedBytes - offset) return refuse(); - if (context.Function("vkBindBufferMemory")(context.device, buffer, entry.memory, offset) != VK_SUCCESS) return refuse(); - const VkBufferDeviceAddressInfo addressInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, buffer}; - const auto address = context.Function("vkGetBufferDeviceAddressKHR")(context.device, &addressInfo); - if (address == 0) return refuse(); - entry.base = kept->address; - entry.bytes = kept->bytes; - entry.buffer = buffer; - entry.address = address; - entry.range = kept; - entry.ranges = {kept}; - entry.serial = 0; - entry.lastUse = ++state.useCounter; - return true; -} - // Drops imports whose registered range changed or disappeared: their pages may no longer back the // guest addresses. Walks the imports only when the registry changed since the last walk. void refreshImports(const Context& context, HostImports& state, const GuestAllocations::Lease& lease) { @@ -1967,17 +2073,15 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc #ifdef _WIN32 drainSettles(state); #endif - for (auto* held : {&state.imports, &state.trimmed}) { - while (!held->empty()) { - auto node = held->extract(held->begin()); - if (state.device != VK_NULL_HANDLE && state.destroyBuffer != nullptr && state.freeMemory != nullptr) { - state.parked[state.device].push_back(std::make_shared(state.device, state.destroyBuffer, state.freeMemory, std::move(node))); - continue; - } -#ifdef _WIN32 - GuestArena::GuestArenaUnmapAlias_nid_postfix(node.mapped().alias); -#endif + while (!state.imports.empty()) { + auto node = state.imports.extract(state.imports.begin()); + if (state.device != VK_NULL_HANDLE && state.destroyBuffer != nullptr && state.freeMemory != nullptr) { + state.parked[state.device].push_back(std::make_shared(state.device, state.destroyBuffer, state.freeMemory, std::move(node))); + continue; } +#ifdef _WIN32 + GuestArena::GuestArenaUnmapAlias_nid_postfix(node.mapped().alias); +#endif } state.liveBytes = 0; state.failed.clear(); @@ -1991,6 +2095,7 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc state.reportNoted = 0; state.sparseRunEnds.clear(); state.sparseRunJoins.clear(); + state.importedAtMap.clear(); state.device = context.device; state.destroyBuffer = context.Function("vkDestroyBuffer"); state.freeMemory = context.Function("vkFreeMemory"); @@ -2008,17 +2113,8 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc }; std::erase_if(state.sparseRunEnds, [&](const auto& entry) { return !starts(entry.first); }); std::erase_if(state.sparseRunJoins, [&](const auto& entry) { return !starts(entry.first); }); - while (!state.trimmed.empty()) { - auto node = state.trimmed.extract(state.trimmed.begin()); - if (!reattachTrimmed(context, state, node.mapped(), lease)) { - releaseTrimmed(state, std::move(node)); - continue; - } - node.key() = node.mapped().base; - ++state.epoch; - state.imports.insert(std::move(node)); - } const auto budget = effectiveBudget(context, state); + std::vector> unwatched; for (auto it = state.imports.begin(); it != state.imports.end();) { if (sameRange(it->second, lease, budget)) { if (it->second.unwatched) GuestMemory::Unwatch(it->first, it->second.bytes); @@ -2028,9 +2124,11 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc static const bool trace = std::getenv("APS5_TRACE_HOST_IMPORT") != nullptr; if (trace && leasedRangeOwner(lease, it->first, it->first + it->second.bytes) != nullptr) std::fprintf(stderr, "[gpu] host import of 0x%llx+0x%llx retired: the range was mapped again\n", static_cast(it->first), static_cast(it->second.bytes)); const auto next = std::next(it); + noteRetiredUnwatched(unwatched, it->second, lease); retireImport(context.device, state, it, lease); it = next; } + rewatchRetired(state, std::move(unwatched)); for (auto it = state.failed.begin(); it != state.failed.end();) it = sameRange(it->second, lease, budget) ? std::next(it) : state.failed.erase(it); for (auto it = state.failedJoins.begin(); it != state.failedJoins.end();) it = sameRun(it->second, joinedRun(lease, it->second.base, it->second.base + it->second.bytes)) ? std::next(it) : state.failedJoins.erase(it); for (auto it = state.failedBlocks.begin(); it != state.failedBlocks.end();) it = sameRun(it->second, exactRun(lease, it->first.first, it->first.second)) ? std::next(it) : state.failedBlocks.erase(it); @@ -2068,7 +2166,7 @@ bool importsStale(const Context& context, const HostImports& state) { // GuestMemory::GpuMutex only (every completion and every stage B holds it). APS5_NO_LEASE_MIRROR=1 // restores copies. struct ImageMirrors { - std::mutex mutex; + AgcDriver::Mutex mutex; VkDevice device = VK_NULL_HANDLE; std::map> entries; std::set failed; @@ -2114,7 +2212,7 @@ AddressBuildTiming& ThreadAddressTiming() { } struct AddressBuildTotals { - std::mutex mutex; + AgcDriver::Mutex mutex; std::uint64_t builds = 0; AddressBuildTiming sums; double snapshotsUs = 0; @@ -2133,26 +2231,7 @@ ImageMirrors& Mirrors() { return mirrors; } -struct SnapshotCopies { - using Key = std::tuple; - static constexpr std::size_t Budget = std::size_t{16} << 20u; - static constexpr std::size_t MaxEntries = 1024; - std::mutex mutex; - VkDevice device = VK_NULL_HANDLE; - struct Entry { - std::shared_ptr buffer; - std::vector bytes; - std::list::iterator recent; - }; - std::map entries; - std::list recency; - std::size_t bytes = 0; - void clear() { - entries.clear(); - recency.clear(); - bytes = 0; - } -}; +using SnapshotCopies = Detail::SnapshotCopies; SnapshotCopies& SnapshotCopyCache() { static SnapshotCopies copies; @@ -2163,7 +2242,7 @@ std::shared_ptr snapshotCopy(const Context& context, std::uint64_t addre auto& copies = SnapshotCopyCache(); std::lock_guard lock(copies.mutex); if (copies.device != context.device) { - copies.clear(); + copies.Clear(); copies.device = context.device; } const SnapshotCopies::Key key{address, bytes.size(), usage}; @@ -2185,9 +2264,7 @@ std::shared_ptr snapshotCopy(const Context& context, std::uint64_t addre copies.entries.erase(oldest); copies.recency.pop_front(); } - copies.recency.push_back(key); - copies.entries.emplace(key, SnapshotCopies::Entry{buffer, {bytes.begin(), bytes.end()}, std::prev(copies.recency.end())}); - copies.bytes += bytes.size(); + copies.Insert(key, buffer, bytes); return buffer; } @@ -2204,25 +2281,137 @@ std::uint64_t heapMirrorBudget() { return bytes; } -std::string heapMirrorError(const GuestAllocations::Range& range, std::uint64_t held, std::string_view reason) { +std::string heapMirrorError(std::uint64_t address, std::uint64_t bytes, std::uint64_t held, std::string_view reason) { constexpr std::string_view prefix = "AGC graphics: "; if (reason.starts_with(prefix)) reason.remove_prefix(prefix.size()); char where[64]; - std::snprintf(where, sizeof(where), "heap mirror of 0x%llx+0x%llx: ", static_cast(range.address), static_cast(range.bytes)); + std::snprintf(where, sizeof(where), "heap mirror of 0x%llx+0x%llx: ", static_cast(address), static_cast(bytes)); char holding[96]; std::snprintf(holding, sizeof(holding), "; heap mirrors hold %llu MiB of %llu MiB (APS5_HEAP_MIRROR_MIB)", static_cast(held >> 20u), static_cast(heapMirrorBudget() >> 20u)); return std::string(prefix) + where + std::string(reason) + holding; } -std::uint64_t heapMirrorsBeside(const GuestAllocations::Range& range) { - auto& state = Mirrors(); - std::lock_guard lock(state.mutex); - std::uint64_t held = state.heapBytes; - if (const auto found = state.entries.find(range.address); found != state.entries.end() && found->second->heap) held -= found->second->bytes; - return held; +void sweepMirrors(); + +void unlistMirror(ImageMirrors& state, ImageMirror& mirror) { + if (!mirror.listed) return; + state.heapBytes -= mirror.accounted; + mirror.listed = false; } -void sweepMirrors(); +struct JoinedHeapRange { + std::vector> members; + std::shared_ptr range; +}; + +struct JoinedHeapRanges { + std::mutex mutex; + std::map entries; +}; + +JoinedHeapRanges& JoinedHeaps() { + static JoinedHeapRanges ranges; + return ranges; +} + +bool releaseExpiredJoinedHeaps() { + std::vector> released; + { + auto& joined = JoinedHeaps(); + std::lock_guard lock(joined.mutex); + for (auto it = joined.entries.begin(); it != joined.entries.end();) { + const auto& members = it->second.members; + if (std::none_of(members.begin(), members.end(), [](const auto& member) { return member.expired(); })) { + ++it; + continue; + } + released.push_back(std::move(it->second.range)); + it = joined.entries.erase(it); + } + } + return !released.empty(); +} + +std::uint64_t blockBytes(const GuestRuns& runs) { + constexpr std::uint64_t block = ResidentBuffer::BlockBytes; + std::uint64_t blocks = 0; + std::uint64_t last = 0; + bool any = false; + for (const auto& [first, end] : runs) { + if (first >= end) continue; + auto index = first / block; + if (any && index == last) ++index; + if (index <= (end - 1) / block) blocks += (end - 1) / block - index + 1; + last = (end - 1) / block; + any = true; + } + return blocks * block; +} + +void reserveHeapBytes(std::uint64_t address, std::uint64_t bytes, std::uint64_t need) { + auto& state = Mirrors(); + for (int attempt = 0;; ++attempt) { + { + std::lock_guard lock(state.mutex); + std::uint64_t held = state.heapBytes; + if (const auto found = state.entries.find(address); found != state.entries.end() && found->second->heap && found->second->listed) held -= found->second->accounted; + if (held + need <= heapMirrorBudget()) { + state.heapBytes += need; + return; + } + if (attempt != 0) throw std::runtime_error(heapMirrorError(address, bytes, held, "past the budget")); + } + sweepMirrors(); + } +} + +void growResident(ImageMirror& mirror, const GuestRuns& runs) { + if (mirror.resident == nullptr) return; + const auto need = mirror.resident->UnboundBytes(runs); + if (need == 0) return; + auto& state = Mirrors(); + bool counted = false; + for (int attempt = 0; !counted; ++attempt) { + { + std::lock_guard lock(state.mutex); + if (!mirror.listed) break; + if (state.heapBytes + need <= heapMirrorBudget()) { + state.heapBytes += need; + mirror.accounted += need; + counted = true; + break; + } + if (attempt != 0) throw std::runtime_error(heapMirrorError(mirror.base, mirror.reserved, state.heapBytes, "past the budget")); + } + sweepMirrors(); + } + try { + mirror.resident->Bind(runs); + } catch (const std::runtime_error& error) { + std::uint64_t held = 0; + { + std::lock_guard lock(state.mutex); + if (counted && mirror.listed) { + state.heapBytes -= need; + mirror.accounted -= need; + } + held = state.heapBytes; + } + throw std::runtime_error(heapMirrorError(mirror.base, mirror.reserved, held, error.what())); + } +} + +std::uint64_t mirrorStart(const ImageMirror& mirror) { + return mirror.resident != nullptr ? mirror.resident->Origin() : mirror.base; +} + +VkBuffer mirrorHandle(const ImageMirror& mirror) { + return mirror.resident != nullptr ? mirror.resident->Handle() : mirror.buffer->Handle(); +} + +VkDeviceAddress mirrorAddress(const ImageMirror& mirror) { + return mirror.resident != nullptr ? mirror.resident->DeviceAddress() : mirror.buffer->DeviceAddress(); +} bool sameRange(const std::weak_ptr& mirrored, const std::shared_ptr& range) { return !mirrored.owner_before(range) && !range.owner_before(mirrored); @@ -2244,6 +2433,16 @@ constexpr std::uint8_t SparseZero = 2; bool compareBlock(const RefreshBlock& block) { const auto offset = static_cast(block.address - block.mirror->base); const auto* guest = reinterpret_cast(block.address); + if (block.mirror->resident != nullptr) { + auto* target = block.mirror->resident->Block(block.address); + if (block.fill == SparseZero) { + if (target != nullptr) std::memset(target, 0, block.length); + return true; + } + if (target != nullptr) std::memcpy(target, guest, block.length); + if (block.fill == 0) block.mirror->generations[static_cast(block.address / 65536 - block.mirror->base / 65536)] = block.generation; + return target != nullptr; + } if (block.fill != 0) { auto* target = block.mirror->buffer->Bytes().data() + offset; if (block.fill == SparseZero) std::memset(target, 0, block.length); @@ -2341,7 +2540,7 @@ private: inline static RefreshPool* instance = nullptr; unsigned helpers = 0; - std::mutex runMutex; + AgcDriver::Mutex runMutex; std::mutex mutex; std::condition_variable_any wake; std::condition_variable done; @@ -2395,9 +2594,33 @@ void appendBlocks(std::vector& blocks, ImageMirror& mirror, std::u } // Compares the gathered blocks (of one or many mirrors) and counts them. +void bindHeapBlocks(std::span blocks) { + std::vector> wanted; + for (const auto& block : blocks) { + if (block.mirror->resident == nullptr || block.fill == SparseZero) continue; + if (block.mirror->resident->Block(block.address) == nullptr && GuestBytesZero(block.address, block.address + block.length)) continue; + if (wanted.empty() || wanted.back().first != block.mirror) { + const auto found = std::find_if(wanted.begin(), wanted.end(), [&](const auto& entry) { return entry.first == block.mirror; }); + if (found == wanted.end()) wanted.emplace_back(block.mirror, GuestRuns{}); + else std::rotate(found, std::next(found), wanted.end()); + } + auto& runs = wanted.back().second; + if (!runs.empty() && runs.back().second == block.address) runs.back().second = block.address + block.length; + else runs.emplace_back(block.address, block.address + block.length); + } + for (auto& [mirror, runs] : wanted) { + std::sort(runs.begin(), runs.end()); + growResident(*mirror, runs); + } +} + void compareBlocks(std::span blocks) { if (blocks.empty()) return; + bindHeapBlocks(blocks); const auto copied = RefreshPool::Get().Run(blocks); + for (const auto& block : blocks) { + if (block.mirror->resident != nullptr && block.mirror->resident->Block(block.address) != nullptr) block.mirror->resident->MarkChanged(block.address, block.address + block.length); + } auto& state = Mirrors(); state.blocksCompared += blocks.size(); state.blocksCopied += copied; @@ -2615,47 +2838,61 @@ bool importsNow(const Context& context, HostImports& state, const GuestAllocatio return context.hostImportAlignment != 0 && (importsStale(context, state) || importAllocation(context, state, address, lease, ImportRequester::Build, 0, false) != nullptr); } +const char* heapCopyReason(bool overBudget, bool watched) { + if (overBudget) return watched ? "import over the host import budget, heap mirror refused" : "import over the host import budget, heap not fully write-watched"; + return watched ? "heap mirror refused" : "heap not fully write-watched"; +} + // The mirror for a leased image range: the existing one, or a new one when none exists or the // registered Range object changed (a protection change). Null when the range cannot be mirrored. An // existing writable mirror's blocks are appended to `blocks` for the caller's one compare of every -std::shared_ptr acquireMirror(const Context& context, const std::shared_ptr& range, std::vector& blocks, bool heap) { +std::shared_ptr acquireMirror(const Context& context, const std::shared_ptr& range, std::vector& blocks, bool heap, std::uint64_t begin = 0, std::uint64_t bytes = 0, bool joinedHeap = false) { + auto selected = *range; + if (bytes != 0) { + Require(begin >= range->address && bytes <= range->bytes && begin - range->address <= range->bytes - bytes, "heap mirror selection exceeds its registered range"); + selected.address = begin; + selected.bytes = static_cast(bytes); + } auto& state = Mirrors(); std::shared_ptr mirror; { std::lock_guard lock(state.mutex); if (state.device != context.device) { + for (auto& [base, entry] : state.entries) entry->listed = false; state.entries.clear(); state.failed.clear(); state.heapBytes = 0; state.device = context.device; ++state.version; } - if (state.failed.contains(range->address)) return nullptr; - const auto found = state.entries.find(range->address); - if (found != state.entries.end() && sameRange(found->second->range, range) && found->second->reserved == range->bytes && found->second->writable == range->writable && found->second->heap == heap) mirror = found->second; + if (state.failed.contains(selected.address)) return nullptr; + const auto found = state.entries.find(selected.address); + if (found != state.entries.end() && sameRange(found->second->range, range) && found->second->reserved == selected.bytes && found->second->writable == range->writable && found->second->heap == heap && found->second->joinedHeap == joinedHeap) mirror = found->second; } if (mirror != nullptr) { if (!mirror->sparse) { - if (!heap && prepareRefresh(*mirror, range->address, range->bytes)) appendBlocks(blocks, *mirror, range->address, range->bytes); + if (!heap && prepareRefresh(*mirror, selected.address, selected.bytes)) appendBlocks(blocks, *mirror, selected.address, selected.bytes); return mirror; } if (refreshSparseMirror(*mirror, blocks)) return mirror; } Pieces pieces; - if (!GuestMemory::Accessible(reinterpret_cast(range->address), range->bytes, range->writable && !heap)) { + if (!GuestMemory::Accessible(reinterpret_cast(selected.address), selected.bytes, range->writable && !heap)) { if (!heap || !sparseMirrorsEnabled()) return nullptr; - pieces = GuestMemory::DescribeCommitted(range->address, range->bytes).ranges; + pieces = GuestMemory::DescribeCommitted(selected.address, selected.bytes).ranges; if (pieces.empty()) return nullptr; } const bool sparse = !pieces.empty(); mirror = std::make_shared(); - mirror->base = range->address; - mirror->bytes = sparse ? sparseExtent(range->address, range->bytes, pieces) : range->bytes; - mirror->reserved = range->bytes; + mirror->base = selected.address; + mirror->bytes = sparse ? sparseExtent(selected.address, selected.bytes, pieces) : selected.bytes; + mirror->reserved = selected.bytes; mirror->sparse = sparse; mirror->writable = range->writable; mirror->heap = heap; + mirror->joinedHeap = joinedHeap; mirror->range = range; + GuestRuns backed; if (heap) { std::uint64_t generation = 0; const auto serial = GuestMemory::UnwatchSerial(); @@ -2663,8 +2900,8 @@ std::shared_ptr acquireMirror(const Context& context, const std::sh syncRange(mirror->base, mirror->bytes); if (GuestMemory::Watched(mirror->base, static_cast(mirror->bytes))) generation = collectPieces(pieces); } else { - prepareRefresh(*mirror, range->address, range->bytes); - generation = GuestMemory::CollectWrites(range->address, range->bytes); + prepareRefresh(*mirror, selected.address, selected.bytes); + generation = GuestMemory::CollectWrites(selected.address, selected.bytes); } constexpr std::uint64_t block = 65536; mirror->generations.assign(static_cast(((mirror->base + mirror->bytes + block - 1) / block) - mirror->base / block), generation); @@ -2672,8 +2909,8 @@ std::shared_ptr acquireMirror(const Context& context, const std::sh std::shared_ptr stale; std::lock_guard lock(state.mutex); ++state.heapUnwatched; - if (const auto found = state.entries.find(range->address); found != state.entries.end() && found->second->heap) { - state.heapBytes -= found->second->bytes; + if (const auto found = state.entries.find(selected.address); found != state.entries.end() && found->second->heap) { + unlistMirror(state, *found->second); stale = std::move(found->second); state.entries.erase(found); ++state.version; @@ -2681,18 +2918,67 @@ std::shared_ptr acquireMirror(const Context& context, const std::sh return nullptr; } mirror->watchedSerial.store(serial, std::memory_order_relaxed); - if (heapMirrorsBeside(*range) + mirror->bytes > heapMirrorBudget()) { - sweepMirrors(); - if (const auto held = heapMirrorsBeside(*range); held + mirror->bytes > heapMirrorBudget()) throw std::runtime_error(heapMirrorError(*range, held, "past the budget")); + if (context.sparseBinding && context.sparseResidencyBuffer && context.sparseResidencyAliased) backed = GuestNonzeroBlocks(sparse ? clipPieces(pieces, mirror->base, mirror->base + mirror->bytes) : GuestRuns{{mirror->base, mirror->base + mirror->bytes}}); + auto need = blockBytes(backed); + reserveHeapBytes(selected.address, selected.bytes, need); + try { + mirror->resident = ResidentBuffer::Create(context, mirror->base, mirror->bytes, backed); + } catch (const std::runtime_error& error) { + std::uint64_t held = 0; + { + std::lock_guard lock(state.mutex); + state.heapBytes -= need; + held = state.heapBytes; + } + throw std::runtime_error(heapMirrorError(selected.address, selected.bytes, held, error.what())); } + if (mirror->resident == nullptr) { + { + std::lock_guard lock(state.mutex); + state.heapBytes -= need; + } + need = mirror->bytes; + reserveHeapBytes(selected.address, selected.bytes, need); + } + mirror->accounted = need; + } + if (mirror->resident != nullptr) { + { + const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::MirrorRefresh); + for (const auto& [first, last] : backed) { + for (auto at = first; at < last;) { + const auto next = std::min(last, (at / ResidentBuffer::BlockBytes + 1) * ResidentBuffer::BlockBytes); + GuestMemory::Read(at, std::span(mirror->resident->Block(at), static_cast(next - at))); + at = next; + } + mirror->resident->MarkChanged(first / ResidentBuffer::BlockBytes * ResidentBuffer::BlockBytes, (last + ResidentBuffer::BlockBytes - 1) / ResidentBuffer::BlockBytes * ResidentBuffer::BlockBytes); + } + } + mirror->committed = std::move(pieces); + std::lock_guard lock(state.mutex); + ++state.rebuilds; + mirror->serial = (1ull << 63u) | ++state.serials; + if (const auto found = state.entries.find(selected.address); found != state.entries.end() && found->second->heap) unlistMirror(state, *found->second); + mirror->listed = true; + state.entries[selected.address] = mirror; + ++state.version; + return mirror; } try { mirror->buffer = std::make_shared(context, static_cast(mirror->bytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); } catch (const std::runtime_error& error) { - if (heap) throw std::runtime_error(heapMirrorError(*range, heapMirrorsBeside(*range), error.what())); - std::fprintf(stderr, "[gpu] image mirror of 0x%llx+0x%llx failed: %s; falling back to copies\n", static_cast(range->address), static_cast(range->bytes), error.what()); + if (heap) { + std::uint64_t held = 0; + { + std::lock_guard lock(state.mutex); + state.heapBytes -= mirror->accounted; + held = state.heapBytes; + } + throw std::runtime_error(heapMirrorError(selected.address, selected.bytes, held, error.what())); + } + std::fprintf(stderr, "[gpu] image mirror of 0x%llx+0x%llx failed: %s; falling back to copies\n", static_cast(selected.address), static_cast(selected.bytes), error.what()); std::lock_guard lock(state.mutex); - state.failed.insert(range->address); + state.failed.insert(selected.address); return nullptr; } // Read stores GPU results pending in the range first (write-backs of earlier copies of it); @@ -2700,7 +2986,7 @@ std::shared_ptr acquireMirror(const Context& context, const std::sh { const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::MirrorRefresh); const auto target = mirror->buffer->Bytes().first(static_cast(mirror->bytes)); - if (!sparse) GuestMemory::Read(range->address, target); + if (!sparse) GuestMemory::Read(selected.address, target); std::size_t cursor = 0; for (const auto& [first, last] : pieces) { const auto at = static_cast(first - mirror->base); @@ -2716,9 +3002,9 @@ std::shared_ptr acquireMirror(const Context& context, const std::sh ++state.rebuilds; mirror->serial = (1ull << 63u) | ++state.serials; // A replaced mirror lives on in the regions of recorded work that bound it. - if (const auto found = state.entries.find(range->address); found != state.entries.end() && found->second->heap) state.heapBytes -= found->second->bytes; - if (heap) state.heapBytes += mirror->bytes; - state.entries[range->address] = mirror; + if (const auto found = state.entries.find(selected.address); found != state.entries.end() && found->second->heap) unlistMirror(state, *found->second); + mirror->listed = heap; + state.entries[selected.address] = mirror; ++state.version; return mirror; } @@ -2737,9 +3023,11 @@ std::shared_ptr findMirror(const Context& context, std::uint64_t be // Drops mirrors whose registered range is gone (a later build made a new one for the replacement). void sweepMirrors() { + const bool released = releaseExpiredJoinedHeaps(); const auto space = Spaces().current.load(); auto& state = Mirrors(); std::lock_guard lock(state.mutex); + if (released) ++state.version; if (space != nullptr && state.pinnedSpace == space->serial && state.pinnedVersion == state.version) return; ++state.sweeps; bool pinned = space != nullptr; @@ -2753,7 +3041,7 @@ void sweepMirrors() { ++it; continue; } - if (it->second->heap) state.heapBytes -= it->second->bytes; + if (it->second->heap) unlistMirror(state, *it->second); it = state.entries.erase(it); ++state.version; } @@ -2761,20 +3049,43 @@ void sweepMirrors() { state.pinnedVersion = state.version; } -void releaseImportedMirrors(const Context& context, const std::vector& imported) { +void releaseImportedMirrors(const Context& context, std::span> imported) { if (imported.empty()) return; + std::vector> coverage(imported.begin(), imported.end()); + std::sort(coverage.begin(), coverage.end()); + std::size_t count = 0; + for (const auto& interval : coverage) { + if (count != 0 && interval.first <= coverage[count - 1].second) coverage[count - 1].second = std::max(coverage[count - 1].second, interval.second); + else coverage[count++] = interval; + } + coverage.resize(count); std::vector> released; auto& state = Mirrors(); std::lock_guard lock(state.mutex); if (state.device != context.device || state.entries.empty()) return; - for (const auto base : imported) { - const auto found = state.entries.find(base); - if (found == state.entries.end() || !found->second->heap) continue; - state.heapBytes -= found->second->bytes; - ++state.heapImported; - state.heapImportedBytes += found->second->bytes; - released.push_back(std::move(found->second)); - state.entries.erase(found); + for (const auto& [begin, end] : imported) { + auto it = state.entries.upper_bound(begin); + if (it != state.entries.begin()) --it; + while (it != state.entries.end() && it->first < end) { + const auto& mirror = it->second; + if (!mirror->heap || mirror->base + mirror->bytes <= begin) { + ++it; + continue; + } + if (mirror->joinedHeap) { + const auto covered = std::upper_bound(coverage.begin(), coverage.end(), mirror->base, [](std::uint64_t address, const auto& interval) { return address < interval.first; }); + if (covered == coverage.begin() || std::prev(covered)->second < mirror->base + mirror->reserved) { + ++it; + continue; + } + } + ++state.heapImported; + state.heapImportedBytes += mirror->accounted; + unlistMirror(state, *mirror); + released.push_back(std::move(it->second)); + it = state.entries.erase(it); + ++state.version; + } } } @@ -2822,7 +3133,7 @@ void reportMirrors() { // waiter yields instead when its own thread holds the device lock (a driver thread mutating the // registry mid-packet, as the plain spin did) and reports whether it finished any work. struct LeaseState { - std::mutex mutex; + AgcDriver::Mutex mutex; LeaseStats stats; std::chrono::steady_clock::time_point lastReport = std::chrono::steady_clock::now(); // Under GuestMemory::GpuMutex: the recorder whose batches hold leases (serials restart with a @@ -2857,6 +3168,7 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) { bool synced = false; bool drained = false; bool retiredImport = false; + bool droppedAddressSpace = false; // A range pinned only by the cached address space is released by dropping the cache's // reference: no GPU wait, no device lock (the mutating thread may be a driver thread holding // it). The registry rescans; a range still pinned by a build or a batch holding the space comes @@ -2865,12 +3177,24 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) { Spaces().droppedByWaiter.store(true, std::memory_order_relaxed); Spaces().waiterDrops.fetch_add(1, std::memory_order_relaxed); dropped.reset(); - auto& state = Leases(); - std::lock_guard lock(state.mutex); - ++state.stats.contentionWaits; - ++state.stats.cacheDrops; - state.stats.contentionMs += std::chrono::duration(std::chrono::steady_clock::now() - start).count(); - return true; + droppedAddressSpace = true; + auto& imports = Imports(); + const auto end = static_cast(address) + bytes; + bool overlapsImport = false; + { + std::lock_guard importsLock(imports.mutex); + const auto next = imports.imports.lower_bound(address); + const auto previous = next == imports.imports.begin() ? imports.imports.end() : std::prev(next); + overlapsImport = (next != imports.imports.end() && next->first < end) || (previous != imports.imports.end() && previous->first + previous->second.bytes > address); + } + if (!overlapsImport) { + auto& state = Leases(); + std::lock_guard lock(state.mutex); + ++state.stats.contentionWaits; + ++state.stats.cacheDrops; + state.stats.contentionMs += std::chrono::duration(std::chrono::steady_clock::now() - start).count(); + return true; + } } if (Recorder::InCompletion()) throw std::runtime_error("guest allocation mutation cannot wait for an active GPU completion"); if (GuestMemory::GpuMutex().HeldByThisThread()) { @@ -2895,7 +3219,7 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) { continue; } const auto next = std::next(it); - retireOrTrim(imports.device, imports, it, lease, address, end); + retireImport(imports.device, imports, it, lease, false); it = next; retiredImport = true; } @@ -2961,7 +3285,7 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) { continue; } const auto next = std::next(it); - retireOrTrim(imports.device, imports, it, lease, address, end); + retireImport(imports.device, imports, it, lease, false); it = next; retiredImport = true; } @@ -2980,6 +3304,7 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) { auto& state = Leases(); std::lock_guard lock(state.mutex); ++state.stats.contentionWaits; + if (droppedAddressSpace) ++state.stats.cacheDrops; if (synced) ++state.stats.contentionSyncs; if (drained) ++state.stats.contentionDrains; state.stats.contentionMs += std::chrono::duration(std::chrono::steady_clock::now() - start).count(); @@ -3063,7 +3388,8 @@ std::string AddressCopyOverflow(std::vector copies, std::uint64_t l std::string message = text; for (std::size_t index = 0; index < copies.size() && index < 8; ++index) { const auto& copy = copies[index]; - std::snprintf(text, sizeof(text), " 0x%llx+0x%llx (%.1f MiB committed, %s)", static_cast(copy.begin), static_cast(copy.end - copy.begin), copy.committed / 1048576.0, copy.reason); + const char* reason = copy.heap ? heapCopyReason(copy.overBudget, GuestMemory::Watched(copy.begin, copy.end - copy.begin)) : copy.reason; + std::snprintf(text, sizeof(text), " 0x%llx+0x%llx (%.1f MiB committed, %s)", static_cast(copy.begin), static_cast(copy.end - copy.begin), copy.committed / 1048576.0, reason); message += text; } return message; @@ -3081,6 +3407,18 @@ std::uint64_t SnapshotMismatches() { return Snapshots().mismatched.load(std::memory_order_relaxed); } +HeapMirrorHold HeapMirrorsWithin(std::uint64_t begin, std::uint64_t end) { + auto& state = Mirrors(); + std::lock_guard lock(state.mutex); + HeapMirrorHold hold; + for (auto it = state.entries.lower_bound(begin); it != state.entries.end() && it->first < end; ++it) { + if (!it->second->heap || it->second->range.expired()) continue; + ++hold.mirrors; + if (it->second->listed) hold.bytes += it->second->accounted; + } + return hold; +} + std::vector AddressTableKey(std::span> writes, std::span own) { std::vector key; key.reserve(writes.size() * 2u + 1u + own.size() * 4u); @@ -3094,7 +3432,7 @@ void ClearSnapshotCopies(VkDevice device) { auto& copies = SnapshotCopyCache(); std::lock_guard lock(copies.mutex); if (copies.device != device) return; - copies.clear(); + copies.Clear(); copies.device = VK_NULL_HANDLE; } @@ -3109,6 +3447,7 @@ void ClearImageMirrors(VkDevice device) { std::lock_guard lock(state.mutex); if (state.device != device) return; entries.swap(state.entries); + for (auto& [base, entry] : entries) entry->listed = false; state.failed.clear(); state.heapBytes = 0; state.device = VK_NULL_HANDLE; @@ -3119,6 +3458,7 @@ void ClearHostImports(VkDevice device) { auto& state = Imports(); std::lock_guard lock(state.mutex); state.parked.erase(device); + state.importedAtMap.clear(); #ifdef _WIN32 if (device == state.device) drainSettles(state); #endif @@ -3141,9 +3481,7 @@ void ClearHostImports(VkDevice device) { GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias); #endif } - for (const auto& [address, entry] : state.trimmed) state.freeMemory(state.device, entry.memory, nullptr); state.imports.clear(); - state.trimmed.clear(); state.liveBytes = 0; state.failed.clear(); state.failedJoins.clear(); @@ -3383,6 +3721,10 @@ void SetHostImportRangeWindow(std::uint64_t bytes) { rangeImportWindow.store(bytes, std::memory_order_relaxed); } +void SetHostImportMaxBytes(std::uint64_t bytes) { + importMaxBytes.store(bytes, std::memory_order_relaxed); +} + void SetHostImportBudget(std::uint64_t bytes) { budgetOverride.store(bytes, std::memory_order_relaxed); auto& state = Imports(); @@ -3436,6 +3778,13 @@ std::uint64_t HostImportLiveBytes() { return state.liveBytes; } +std::uint64_t HostImportBudgetRemaining(const Context& context) { + auto& state = Imports(); + std::lock_guard lock(state.mutex); + const auto budget = effectiveBudget(context, state); + return budget > state.liveBytes ? budget - state.liveBytes : 0; +} + const char* ImportRequesterName(ImportRequester requester) { switch (requester) { case ImportRequester::Map: return "map"; @@ -3521,6 +3870,22 @@ const HostImport* ExistingHostImportFor(const Context& context, std::uint64_t ad return findImport(state, address, address + bytes); } +bool WatchedAfterSettle(const Context& context, std::uint64_t address, std::size_t bytes) { +#ifdef _WIN32 + if (context.hostImportAlignment != 0 && bytes != 0 && bytes <= std::numeric_limits::max() - address && GuestMemory::GpuMutex().HeldByThisThread()) { + ensurePinWaiter(); + auto& state = Imports(); + std::lock_guard lock(state.mutex); + if (importsStale(context, state)) { + const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); + refreshImports(context, state, lease); + } + settleImportsNow(context, state, address, address + bytes); + } +#endif + return GuestMemory::Watched(address, bytes); +} + bool ImportMappedRanges(const Context& context, const GuestAllocations::Mapped& ranges, std::uint64_t generation, bool adoptDevice) { if (context.hostImportAlignment == 0) return true; ensurePinWaiter(); @@ -3535,16 +3900,18 @@ bool ImportMappedRanges(const Context& context, const GuestAllocations::Mapped& } else if (state.refreshedGeneration > generation) { return true; } - if (!adopting) current = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); for (const auto& mapped : ranges) { const auto range = mapped.lock(); - if (range == nullptr || leasedRangeOwner(current, range->address) != range) continue; + if (range == nullptr || range->bytes <= 0x10000) continue; + if (current.empty()) current = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); + if (leasedRangeOwner(current, range->address) != range) continue; try { if (!GuestMemory::GpuMutex().HeldByThisThread()) { const auto run = importRun(current, range->address, std::numeric_limits::max(), effectiveBudget(context, state)); const std::array, 1> span{{{run.base, run.base + run.bytes}}}; if (wouldRetireImport(state, run) || StorageTexture::AnyPendingOverlaps(span)) return false; } + if (!state.importedAtMap.emplace(range->address, range->bytes).second && findImport(state, range->address, range->address + range->bytes) == nullptr) continue; importAllocation(context, state, range->address, current, ImportRequester::Map, 0, false); } catch (const std::exception& error) { std::fprintf(stderr, "[gpu] import of GPU memory mapped at 0x%llx+0x%zx failed: %s\n", static_cast(range->address), range->bytes, error.what()); @@ -3728,35 +4095,79 @@ void GuestBufferMemory::AcquireRegistered() { // The import lock is taken per range: a mirror's preparation may wait for recorded work, which // must not happen under it. regions.reserve(lease.size()); + static const std::uint64_t copyLimit = [] { + const char* value = std::getenv("APS5_ADDRESS_COPY_MAX_MIB"); + return (value ? std::strtoull(value, nullptr, 10) : 64ull) << 20u; + }(); + const auto copyOverride = addressCopyLimitOverride.load(std::memory_order_relaxed); + const auto cap = copyOverride != 0 ? copyOverride : copyLimit; std::vector copies; - std::vector imported; + std::vector> imported; const HostImport* covering = nullptr; std::uint64_t coveredBegin = 0; std::uint64_t coveredEnd = 0; + std::uint64_t copiedBytes = 0; for (const auto& range : lease) { if (!range->readable || !range->gpu) continue; validate(range->address, range->bytes); Region region{range->address, range->address + range->bytes, range->writable, {}, nullptr}; const HostImport* entry = nullptr; + bool overBudget = false; + std::vector pieces; + const auto limit = importMaxBytes.load(std::memory_order_relaxed); if (importable && covering != nullptr && range->address >= coveredBegin && range->address + range->bytes <= coveredEnd) { entry = covering; + } else if (importable && limit != 0 && range->bytes > limit) { + auto& state = Imports(); + std::lock_guard lock(state.mutex); + pieces = importPieces(context, state, *range, lease); } else if (importable) { auto& state = Imports(); std::lock_guard lock(state.mutex); + const auto refusals = state.budgetRefusals; entry = importAllocation(context, state, range->address, lease, ImportRequester::Build, 0, false); + overBudget = state.budgetRefusals != refusals; covering = entry; if (entry != nullptr) { coveredBegin = entry->base; coveredEnd = entry->base + entry->bytes; } } + if (importable && mirrorsEnabled() && entry == nullptr && pieces.empty()) { + auto& state = Imports(); + std::lock_guard lock(state.mutex); + pieces = budgetedImportPieces(context, state, *range, lease); + } lap(timing.importsUs, at); + if (!pieces.empty()) { + const auto firstImported = imported.size(); + for (const auto& piece : pieces) if (piece.entry != nullptr) imported.emplace_back(piece.begin, piece.end); + releaseImportedMirrors(context, std::span(imported).subspan(firstImported)); + for (const auto& piece : pieces) { + Region part{piece.begin, piece.end, range->writable, {}, nullptr}; + if (piece.entry != nullptr) { + part.hostBacked = true; + part.direct = piece.entry; + } else { + part.mirror = acquireMirror(context, range, blocks, true, piece.begin, piece.end - piece.begin); + lap(timing.mirrorsUs, at); + Require(part.mirror != nullptr, "a partial heap import has no mirror for its remaining bytes"); + mirrored = true; + if (part.mirror->sparse) { + part.sparse = true; + part.backed = part.mirror->committed; + } else heaps.push_back(part.mirror.get()); + } + regions.push_back(std::move(part)); + } + continue; + } if (entry != nullptr) { region.hostBacked = true; // Reused by Upload while no import was dropped since (see importsEpoch). region.direct = entry; regions.push_back(std::move(region)); - imported.push_back(range->address); + imported.emplace_back(range->address, range->address + range->bytes); continue; } if (mirrorsEnabled()) { @@ -3781,17 +4192,36 @@ void GuestBufferMemory::AcquireRegistered() { committed = 0; for (const auto& [first, last] : copied.backed) committed += last - first; } - const char* reason = !mirrorsEnabled() ? "mirrors disabled by APS5_NO_LEASE_MIRROR" : copied.sparse ? "unreadable pages" : !range->releasable ? "image mirror refused" : "outside the write-watched arena"; - copies.push_back({copied.begin, copied.end, committed, reason}); + if (overBudget && copiedBytes + committed > cap) { + auto& state = Imports(); + std::lock_guard lock(state.mutex); + entry = importAllocation(context, state, range->address, lease, ImportRequester::Build, 0, true); + if (entry != nullptr) { + covering = entry; + coveredBegin = entry->base; + coveredEnd = entry->base + entry->bytes; + } + static auto lastReport = std::chrono::steady_clock::now() - std::chrono::seconds(60); + if (entry != nullptr && std::chrono::steady_clock::now() - lastReport > std::chrono::seconds(10)) { + lastReport = std::chrono::steady_clock::now(); + std::fprintf(stderr, "[gpu] host import budget: 0x%llx+0x%llx imported by evicting idle imports, as copying it would pass APS5_ADDRESS_COPY_MAX_MIB\n", static_cast(range->address), static_cast(range->bytes)); + } + } + if (entry != nullptr) { + regions.pop_back(); + region.hostBacked = true; + region.direct = entry; + regions.push_back(std::move(region)); + imported.emplace_back(range->address, range->address + range->bytes); + continue; + } + copiedBytes += committed; + const char* reason = !mirrorsEnabled() ? "mirrors disabled by APS5_NO_LEASE_MIRROR" : copied.sparse ? "unreadable pages" : !range->releasable ? "image mirror refused" : nullptr; + copies.push_back({copied.begin, copied.end, committed, reason, reason == nullptr, overBudget}); } releaseImportedMirrors(context, imported); regionsSorted = sortedLookup; - static const std::uint64_t copyLimit = [] { - const char* value = std::getenv("APS5_ADDRESS_COPY_MAX_MIB"); - return (value ? std::strtoull(value, nullptr, 10) : 64ull) << 20u; - }(); - const auto limit = addressCopyLimitOverride.load(std::memory_order_relaxed); - if (const auto overflow = AddressCopyOverflow(std::move(copies), limit != 0 ? limit : copyLimit); !overflow.empty()) { + if (const auto overflow = AddressCopyOverflow(std::move(copies), cap); !overflow.empty()) { std::fprintf(stderr, "FATAL: %s\n", overflow.c_str()); std::fflush(stderr); std::abort(); @@ -3967,6 +4397,11 @@ const GuestBufferMemory::Region* GuestBufferMemory::owner(std::uint64_t address, namespace { +bool bdaStagingEnabled() { + static const bool disabled = std::getenv("APS5_NO_BDA_STAGING") != nullptr; + return !disabled; +} + template bool mirrorOverlaps(const Regions& base, std::uint64_t begin, std::uint64_t end) { auto found = std::upper_bound(base.begin(), base.end(), begin, [](std::uint64_t value, const auto& region) { return value < region.begin; }); @@ -3984,12 +4419,18 @@ void GuestBufferMemory::addDescriptorRegion(std::uint64_t address, std::size_t b const auto begin = address & ~std::uint64_t{3}; const auto end = begin == address ? address + bytes : (address + bytes + 3) & ~std::uint64_t{3}; const Region* inside = nullptr; + bool overlay = false; switch (baseOverlap(begin, end, &inside)) { case BaseOverlap::Inside: if (!servesView(*inside, begin)) { dissolveSpace(false); break; } + if (overlayCandidate(*inside, readOnly) && GuestMemory::DescribeCommitted(begin, static_cast(end - begin)).whole) { + overlay = true; + break; + } + if (bdaStagingEnabled() && stagingAllowed) servedInsideBase.emplace_back(begin, end); return; case BaseOverlap::Partial: if (readOnly && !mirrorOverlaps(space->base, begin, end) && !WritesOverlap(begin, static_cast(end - begin))) { @@ -4008,6 +4449,7 @@ void GuestBufferMemory::addDescriptorRegion(std::uint64_t address, std::size_t b region.atomic = atomic; region.swept = swept; region.lastView = begin; + region.overlay = overlay; auto committed = GuestMemory::DescribeCommitted(begin, static_cast(end - begin)); if (!committed.whole) { // A GPU heap bound whole while the guest commits its pages on demand, or a descriptor left @@ -4027,15 +4469,40 @@ bool GuestBufferMemory::sparseHeapCandidate(const Region& region) { return region.sparseHeap && region.sparse && region.mirror == nullptr && region.direct == nullptr && (region.hostBacked || region.writable) && mirrorsEnabled() && sparseMirrorsEnabled(); } +std::shared_ptr joinedHeapRange(const GuestAllocations::Lease& lease, std::uint64_t begin, std::uint64_t end) { + const auto run = crossedRun(lease, begin, end); + if (run.ranges.size() < 2 || std::any_of(run.ranges.begin(), run.ranges.end(), [](const auto& range) { return !range->releasable; })) return nullptr; + releaseExpiredJoinedHeaps(); + auto& joined = JoinedHeaps(); + std::lock_guard lock(joined.mutex); + auto& entry = joined.entries[run.base]; + bool same = entry.range != nullptr && entry.members.size() == run.ranges.size(); + for (std::size_t index = 0; same && index < run.ranges.size(); ++index) same = sameRange(entry.members[index], run.ranges[index]); + if (!same) { + const auto& first = *run.ranges.front(); + entry.members.assign(run.ranges.begin(), run.ranges.end()); + entry.range = std::make_shared(GuestAllocations::Range{run.base, static_cast(run.bytes), true, first.writable, first.allocationAddress, first.allocationBytes, true, first.gpu, first.backingSerial}); + } + return entry.range; +} + bool GuestBufferMemory::bindSparseMirror(Region& region, const GuestAllocations::Lease& lease) { const auto backedEnd = region.backed.empty() ? region.begin : region.backed.back().second; const auto neededEnd = std::min(region.end, std::max(backedEnd, std::max(region.lastView, region.begin) + 4u)); - const auto* range = containingRange(lease, region.begin, neededEnd); - if (range == nullptr || !range->releasable) return false; + std::shared_ptr owner; + bool joined = false; + if (const auto* range = containingRange(lease, region.begin, neededEnd); range != nullptr) { + if (!range->releasable) return false; + owner = leasedRangeOwner(lease, range->address); + } else { + joined = true; + owner = joinedHeapRange(lease, region.begin, neededEnd); + if (owner == nullptr) return false; + } std::vector blocks; std::shared_ptr mirror; try { - mirror = acquireMirror(context, leasedRangeOwner(lease, range->address), blocks, true); + mirror = acquireMirror(context, owner, blocks, true, 0, 0, joined); } catch (const std::runtime_error& error) { static std::atomic reported{0}; if (reported.fetch_add(1, std::memory_order_relaxed) < 4) std::fprintf(stderr, "[gpu] %s; copying the sparse heap instead\n", error.what()); @@ -4046,7 +4513,7 @@ bool GuestBufferMemory::bindSparseMirror(Region& region, const GuestAllocations: refreshHeapMirrors(whole, blocks); } compareBlocks(blocks); - if (mirror == nullptr || !mirror->heap || region.begin < mirror->base || (region.begin - mirror->base) % context.limits.minStorageBufferOffsetAlignment != 0) return false; + if (mirror == nullptr || !mirror->heap || region.begin < mirror->base || (region.begin - mirrorStart(*mirror)) % context.limits.minStorageBufferOffsetAlignment != 0) return false; region.mirror = std::move(mirror); if (!servesView(region, std::max(region.lastView, region.begin))) { region.mirror = nullptr; @@ -4129,7 +4596,7 @@ void GuestBufferMemory::AddSnapshot(const GuestMemorySnapshot& snapshot) { std::snprintf(text, sizeof(text), "guest snapshot differs from registered memory: snapshot 0x%llx+0x%zx (%s region 0x%llx+0x%llx%s%s%s) differs first at +0x%zx", static_cast(snapshot.address), snapshot.bytes.size(), live ? "live" : "snapshot-backed", static_cast(owner->begin), static_cast(owner->end - owner->begin), owner->writable ? ", writable" : "", owner->hostBacked ? ", host-backed" : "", owner->mirror != nullptr ? ", mirrored" : "", first); static const bool fatal = std::getenv("APS5_SNAPSHOT_CHECK_FATAL") != nullptr; if (!live || fatal) throw std::runtime_error(text); - static std::mutex reportMutex; + static AgcDriver::Mutex reportMutex; static auto lastReport = std::chrono::steady_clock::now() - std::chrono::seconds(60); std::lock_guard lock(reportMutex); const auto mismatches = Snapshots().mismatched.fetch_add(1, std::memory_order_relaxed) + 1; @@ -4202,18 +4669,24 @@ VkBufferUsageFlags gpuCopyUsage(bool addressable) { // copied back by RecordCopyBacks after the work, with the pending-write note and the direct-write // marks of an in-place write. Only builds whose every use records the work and calls // RecordCopyBacks take it (dispatches, see AllowDeviceStaging; a synchronous draw would store the -// staging bytes from the CPU, which a device-local buffer cannot serve), and never address-based -// ones (their pointers read the import in place, and AddressRanges needs a device address). The +// staging bytes from the CPU, which a device-local buffer cannot serve). In an address-based build +// a written element inside one direct import region of the space becomes an overlay region +// (resolveOverlays), and AddressRanges cuts the import's range around it so that its pointers reach +// the shadow as its descriptor does. The // shadow is per use, not resident: a reused build records its copy-in anew (RecordStagingCopies). // Accepted trade-off, as for the misaligned copies above: a CPU write into a staged range between // the copy-in and the copy-back is rolled back by the copy-back (the whole written element range, // since store extents are not known), for the batch's latency; the staging window keeps the // exposure to the elements named on the [buffers] staging line and APS5_TRACE_STAGING. // APS5_NO_WRITTEN_SHADOW=1 and APS5_NO_ATOMIC_STAGING=1 bind those elements in place as before; -// APS5_WRITTEN_SHADOW_MIN_KIB / APS5_WRITTEN_SHADOW_MAX_KIB (16 / 2048) bound the written window -// (a kernel streaming once through a large buffer, the engine's memcpy kernel over 4-8 MiB video -// frames, would only gain the two copies), APS5_ATOMIC_STAGE_MAX_KIB (1024) the atomic one. +// APS5_WRITTEN_SHADOW_MIN_KIB / APS5_WRITTEN_SHADOW_MAX_KIB (16 / 2048, 8192 on a discrete GPU) bound +// the written window: on an integrated GPU a kernel streaming once through a large buffer (the +// engine's memcpy kernel over 4-8 MiB video frames) would only gain the two copies, while on a +// discrete GPU its stores into the host import cross the bus and cost more than the copies, so the +// window takes those elements there. APS5_ATOMIC_STAGE_MAX_KIB (1024) bounds the atomic one. constexpr std::uint64_t SweptShadowMax = std::uint64_t{16} << 20u; +constexpr std::uint64_t DiscreteWrittenShadowMax = std::uint64_t{8} << 20u; +constexpr std::uint64_t DiscreteSweptShadowMax = std::uint64_t{256} << 20u; bool writtenShadowEnabled() { static const bool disabled = std::getenv("APS5_NO_WRITTEN_SHADOW") != nullptr; @@ -4235,9 +4708,10 @@ std::uint64_t writtenShadowMin() { return bytes; } -std::uint64_t writtenShadowMax() { - static const std::uint64_t bytes = kibSetting("APS5_WRITTEN_SHADOW_MAX_KIB", 2048); - return bytes; +std::uint64_t writtenShadowMax(bool discreteGpu) { + static const std::uint64_t setting = kibSetting("APS5_WRITTEN_SHADOW_MAX_KIB", 0); + if (setting != 0) return setting; + return discreteGpu ? DiscreteWrittenShadowMax : std::uint64_t{2048} << 10u; } std::uint64_t atomicStageMax() { @@ -4301,7 +4775,7 @@ std::shared_ptr stagingBuffer(const Context& context, std::size_t bytes, void traceStaged(std::uint64_t begin, std::uint64_t end, bool atomic) { static const bool trace = std::getenv("APS5_TRACE_STAGING") != nullptr; if (!trace) return; - static std::mutex mutex; + static AgcDriver::Mutex mutex; static std::set> seen; std::lock_guard lock(mutex); if (!seen.insert({begin, end}).second) return; @@ -4351,16 +4825,56 @@ bool GuestBufferMemory::bindableInPlace(std::uint64_t offset, bool addressable) } bool GuestBufferMemory::stagingEligible(const Region& region, bool addressable) const { - if (!stagingAllowed || addressable || region.unstaged || !gpuCopiesEnabled() || region.sparse || region.mirror != nullptr) return false; + if (!stagingAllowed || (addressable && !region.overlay) || region.unstaged || !gpuCopiesEnabled() || region.sparse || region.mirror != nullptr) return false; const auto bytes = region.end - region.begin; if (!WritesOverlap(region.begin, static_cast(bytes))) return false; if (region.atomic && atomicStagingEnabled() && bytes <= atomicStageMax()) return true; - return writtenShadowEnabled() && bytes >= writtenShadowMin() && bytes <= (region.swept ? std::max(writtenShadowMax(), SweptShadowMax) : writtenShadowMax()); + return writtenShadowEnabled() && bytes >= writtenShadowMin() && bytes <= (region.swept ? std::max(writtenShadowMax(context.discreteGpu), context.discreteGpu ? DiscreteSweptShadowMax : SweptShadowMax) : writtenShadowMax(context.discreteGpu)); +} + +bool GuestBufferMemory::overlayCandidate(const Region& inside, bool readOnly) const { + return !readOnly && bdaStagingEnabled() && stagingAllowed && addressStaging && gpuCopiesEnabled() && writtenShadowEnabled() && inside.direct != nullptr && inside.mirror == nullptr && !inside.sparse && Recorder::Active() != nullptr; +} + +void GuestBufferMemory::resolveOverlays() { + std::vector members; + for (std::size_t index = 0; index < regions.size(); ++index) { + if (regions[index].overlay) members.push_back(index); + } + if (members.empty()) return; + std::vector drop(regions.size(), space == nullptr); + std::sort(members.begin(), members.end(), [&](std::size_t left, std::size_t right) { return regions[left].begin < regions[right].begin; }); + for (std::size_t first = 0; space != nullptr && first < members.size();) { + Region cluster{regions[members[first]].begin, regions[members[first]].end, true, {}, nullptr}; + cluster.overlay = true; + auto last = first; + for (; last < members.size() && (last == first || regions[members[last]].begin < cluster.end); ++last) { + cluster.end = std::max(cluster.end, regions[members[last]].end); + cluster.atomic = cluster.atomic || regions[members[last]].atomic; + cluster.swept = cluster.swept || regions[members[last]].swept; + } + const auto crosses = [&](std::uint64_t begin, std::uint64_t end) { return begin < cluster.end && cluster.begin < end && (begin < cluster.begin || end > cluster.end); }; + const Region* inside = nullptr; + bool keep = baseOverlap(cluster.begin, cluster.end, &inside) == BaseOverlap::Inside && inside != nullptr && inside->direct != nullptr && inside->mirror == nullptr && stagingEligible(cluster, true); + keep = keep && std::none_of(servedInsideBase.begin(), servedInsideBase.end(), [&](const auto& range) { return crosses(range.first, range.second); }); + keep = keep && std::none_of(regions.begin(), regions.end(), [&](const Region& region) { return !region.overlay && region.begin < cluster.end && cluster.begin < region.end; }); + for (auto member = first; member < last; ++member) drop[members[member]] = !keep; + first = last; + } + std::size_t kept = 0; + for (std::size_t index = 0; index < regions.size(); ++index) { + if (drop[index]) continue; + if (kept != index) regions[kept] = std::move(regions[index]); + ++kept; + } + regions.erase(regions.begin() + static_cast(kept), regions.end()); } void GuestBufferMemory::UploadPrepare(bool addressable) { Require(!prepared && !uploaded, "guest memory was already uploaded"); prepared = true; + resolveOverlays(); + servedInsideBase.clear(); std::vector order(regions.size()); std::iota(order.begin(), order.end(), 0u); std::sort(order.begin(), order.end(), [&](std::uint32_t left, std::uint32_t right) { return regions[left].begin < regions[right].begin; }); @@ -4529,7 +5043,7 @@ void GuestBufferMemory::UploadPrepare(bool addressable) { void GuestBufferMemory::completeSpaceWrites() const { auto* recorder = Recorder::Active(); - if (recorder == nullptr || Recorder::InCompletion()) return; + if (recorder == nullptr || Recorder::InCompletion() || !AnyPendingCpuWrites(context)) return; const auto& base = space->base; std::vector> writes; std::vector candidates; @@ -4623,6 +5137,12 @@ void GuestBufferMemory::UploadFinish(bool addressable) { const auto loopStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; std::uint64_t refreshUs = 0; std::uint64_t copyUs = 0; + std::uint64_t heapsBegin = std::numeric_limits::max(); + std::uint64_t heapsEnd = 0; + if (addressable && space != nullptr && !space->base.empty()) { + heapsBegin = space->base.front().begin; + heapsEnd = space->base.back().end; + } for (auto& region : regions) { if (!region.pending) continue; region.pending = false; @@ -4636,12 +5156,6 @@ void GuestBufferMemory::UploadFinish(bool addressable) { if (profile) refreshUs += microsecondsSince(refreshStart); continue; } - if (sparseHeapCandidate(region)) { - const auto refreshStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; - const bool bound = bindSparseMirror(region, importRanges()); - if (profile) refreshUs += microsecondsSince(refreshStart); - if (bound) continue; - } bool copyOnGpu = false; if (context.hostImportAlignment != 0) { auto& state = Imports(); @@ -4713,7 +5227,12 @@ void GuestBufferMemory::UploadFinish(bool addressable) { // an address-based shader maps are exempt (they hold every pending image, and such shaders // do not read texture memory through pointers); APS5_BDA_FLUSH=1 flushes for them too. static const bool bdaFlush = std::getenv("APS5_BDA_FLUSH") != nullptr; - if (!(addressable && region.hostBacked) || bdaFlush) StorageTexture::FlushPending(region.begin, static_cast(bytes), nullptr, "imported buffer region"); + if (!(addressable && region.hostBacked) || bdaFlush) { + StorageTexture::FlushPending(region.begin, static_cast(bytes), nullptr, "imported buffer region"); + } else { + heapsBegin = std::min(heapsBegin, region.begin); + heapsEnd = std::max(heapsEnd, region.end); + } continue; } if (copyOnGpu) { @@ -4728,6 +5247,12 @@ void GuestBufferMemory::UploadFinish(bool addressable) { gpuCopies.push_back(®ion); continue; } + if (sparseHeapCandidate(region)) { + const auto refreshStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; + const bool bound = bindSparseMirror(region, importRanges()); + if (profile) refreshUs += microsecondsSince(refreshStart); + if (bound) continue; + } if (region.deviceLocal) { // The staging shadow UploadPrepare made cannot take the CPU copy (no host mapping): the // region falls back to a host buffer of its own. @@ -4738,8 +5263,10 @@ void GuestBufferMemory::UploadFinish(bool addressable) { copyRegion(region, addressable); if (profile) copyUs += microsecondsSince(copyStart); } + if (heapsEnd > heapsBegin) StorageTexture::PublishShadowsOnly(heapsBegin, static_cast(heapsEnd - heapsBegin), PublishScope::Whole, "address-based heaps"); if (!gpuCopies.empty()) recordGpuCopies(gpuCopies, addressable); takeHeapReferences(); + ResidentBuffer::FlushChanged(context); if (!profile) return; const auto loopUs = microsecondsSince(loopStart); readUs.fetch_add(refreshUs, std::memory_order_relaxed); @@ -4773,7 +5300,7 @@ void GuestBufferMemory::UploadFinish(bool addressable) { void GuestBufferMemory::copyRegion(Region& region, bool addressable) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; const auto bytes = region.end - region.begin; - const auto usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | (addressable ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT : 0u); + const auto usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | (addressable ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT : 0u); std::uint64_t readBytes = 0; bool inImport = false; if (profile && context.hostImportAlignment != 0) { @@ -4830,7 +5357,7 @@ void GuestBufferMemory::copyRegion(Region& region, bool addressable) { region.snapshot.clear(); if (profile) readUs.fetch_add(microsecondsSince(readStart), std::memory_order_relaxed); if (profile) { - static std::mutex reasonsMutex; + static AgcDriver::Mutex reasonsMutex; static std::uint64_t uploads = 0, noImport = 0, noImportBytes = 0, misaligned = 0, misalignedBytes = 0, snapshotOnly = 0, snapshotBytes = 0; static std::uint64_t logicalBytes = 0, readTotalBytes = 0; static std::map> outside; @@ -4974,16 +5501,36 @@ void GuestBufferMemory::RecordCopyBacks(Recorder& recorder) { VkCommandBuffer commands = VK_NULL_HANDLE; auto timing = Recorder::NoTiming; std::uint64_t copiedBytes = 0; + if (!writes.empty()) { + auto sorted = writes; + std::sort(sorted.begin(), sorted.end()); + for (const auto& range : sorted) { + if (!merged.empty() && range.first < merged.back().second) merged.back().second = std::max(merged.back().second, range.second); + else merged.push_back(range); + } + } + for (const auto& [begin, end] : merged) { + const auto* found = owner(begin, end); + if (found == nullptr || found->mirror == nullptr || !found->mirror->heap || !found->mirror->writable || found->mirror->resident == nullptr) continue; + const auto& region = *found; + { + const auto from = begin; + const auto to = std::min(end, servedEnd(region)); + if (from >= to || std::any_of(heapReadbacks.begin(), heapReadbacks.end(), [&](const auto& entry) { return entry.first == from; })) continue; + if (!recording) { + commands = recorder.Commands(); + timing = recorder.BeginGpuTiming(StagingCopyBackKey); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + Recorder::CountBarriers(Recorder::CommandClass::StagingOut); + recording = true; + } + heapReadbacks.emplace_back(from, region.mirror->resident->RecordReadback(commands, from, to - from)); + recorder.Keep(heapReadbacks.back().second); + copiedBytes += to - from; + } + } for (auto& region : regions) { if (!region.gpuCopy || region.copiedBack) continue; - if (merged.empty() && !writes.empty()) { - auto sorted = writes; - std::sort(sorted.begin(), sorted.end()); - for (const auto& range : sorted) { - if (!merged.empty() && range.first < merged.back().second) merged.back().second = std::max(merged.back().second, range.second); - else merged.push_back(range); - } - } for (const auto& [begin, end] : merged) { const auto from = std::max(begin, region.begin); const auto to = std::min(end, region.end); @@ -5040,7 +5587,7 @@ VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std: return std::string(text); }; if (!(address >= region.begin && address + bytes <= region.end && (region.buffer != nullptr || region.direct != nullptr || region.mirror != nullptr))) Require(false, describe("exceeds")); - const auto base = region.direct != nullptr ? region.direct->base - region.direct->lead : region.mirror != nullptr ? region.mirror->base : region.begin; + const auto base = region.direct != nullptr ? region.direct->base - region.direct->lead : region.mirror != nullptr ? mirrorStart(*region.mirror) : region.begin; const auto offset = address - base; Require(context.limits.minStorageBufferOffsetAlignment != 0, "no storage buffer offset alignment"); Require(base % 4 == 0, "a guest buffer view in a GPU owner that does not start at a DWORD boundary is not implemented"); @@ -5054,7 +5601,7 @@ VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std: } if (address - adjustment + range > end) Require(false, describe("rounded up to its view range exceeds")); Require(range <= context.limits.maxStorageBufferRange, "guest buffer view exceeds descriptor range limit"); - const auto handle = region.direct != nullptr ? region.direct->buffer : region.mirror != nullptr ? region.mirror->buffer->Handle() : region.buffer->Handle(); + const auto handle = region.direct != nullptr ? region.direct->buffer : region.mirror != nullptr ? mirrorHandle(*region.mirror) : region.buffer->Handle(); return {handle, offset - adjustment, range}; } @@ -5067,7 +5614,7 @@ std::uint64_t GuestBufferMemory::ViewBytes(std::uint64_t bytes, std::uint32_t ad ShaderRecompiler::BdaAbi::Range GuestBufferMemory::addressRange(const Region& region) { Require(region.buffer != nullptr || region.direct != nullptr || region.mirror != nullptr, "incomplete guest GPU upload"); - const auto address = region.direct != nullptr ? region.direct->address + (region.begin - region.direct->base) : region.mirror != nullptr ? region.mirror->buffer->DeviceAddress() + (region.begin - region.mirror->base) : region.buffer->DeviceAddress(); + const auto address = region.direct != nullptr ? region.direct->address + (region.begin - region.direct->base) : region.mirror != nullptr ? mirrorAddress(*region.mirror) + (region.begin - region.mirror->base) : region.buffer->DeviceAddress(); Require(region.end - region.begin <= std::numeric_limits::max() - address, "GPU address range overflow"); const auto permissions = ShaderRecompiler::BdaAbi::Read | (region.direct != nullptr && region.writable ? ShaderRecompiler::BdaAbi::Write : 0u); return {region.begin, region.direct != nullptr ? inPlaceEnd(region) : servedEnd(region), address, permissions, 0}; @@ -5125,12 +5672,20 @@ std::vector GuestBufferMemory::AddressRanges() std::vector result; if (space != nullptr) { std::vector own; + std::vector overlays; own.reserve(regions.size()); - for (const auto& region : regions) own.push_back(addressRange(region)); + for (const auto& region : regions) (region.overlay ? overlays : own).push_back(addressRange(region)); std::vector uncovered; uncovered.reserve(own.size()); AppendUncovered(own, space->ranges, uncovered); result = MergeByBegin(space->ranges, uncovered); + if (!overlays.empty()) { + std::sort(overlays.begin(), overlays.end(), [](const auto& left, const auto& right) { return left.begin < right.begin; }); + std::vector cut; + cut.reserve(result.size() + overlays.size()); + AppendUncovered(result, overlays, cut); + result = MergeByBegin(cut, overlays); + } } else if (regionsOverlap) { std::vector live; std::vector copies; @@ -5267,7 +5822,10 @@ void GuestBufferMemory::MarkDirectWrites() const { const auto* found = owner(begin, end); if (found == nullptr) continue; const auto& region = *found; - if (region.direct != nullptr || (region.gpuCopy && region.copiedBack)) GuestMemory::MarkWritten(begin, static_cast(end - begin)); + if (region.direct != nullptr || (region.gpuCopy && region.copiedBack)) { + const auto generation = GuestMemory::MarkPossiblyWritten(begin, static_cast(end - begin)); + if (generation != 0) directWritesGeneration = std::max(directWritesGeneration, generation); + } } } @@ -5330,15 +5888,19 @@ std::vector> mergeWrites(std::vectormirror == nullptr || !found->mirror->heap || !found->mirror->writable) continue; Require(end <= found->end, "written range exceeds its heap mirror"); - const auto& mirror = *found->mirror; + auto& mirror = *found->mirror; const auto served = std::min(end, servedEnd(*found)); if (begin >= served) continue; - const auto bytes = mirror.buffer->Bytes().subspan(static_cast(begin - mirror.base), static_cast(served - begin)); - heapReferences.emplace_back(begin, std::vector(bytes.begin(), bytes.end())); + growResident(mirror, {{begin, served}}); + std::vector bytes(static_cast(served - begin)); + if (mirror.resident != nullptr) mirror.resident->Read(begin, bytes); + else std::memcpy(bytes.data(), mirror.buffer->Bytes().data() + (begin - mirror.base), bytes.size()); + heapReferences.emplace_back(begin, std::move(bytes)); } } @@ -5355,7 +5917,7 @@ void GuestBufferMemory::WriteBack() { if (region.direct == nullptr && !(region.gpuCopy && (region.copiedBack || region.deviceLocal)) && !(region.mirror != nullptr && !region.mirror->writable)) Recorder::NoteWrittenBack(begin, static_cast(end - begin)); if (region.direct != nullptr) { // The GPU wrote imported guest memory directly; page write watching did not see it. - GuestMemory::MarkWritten(begin, static_cast(end - begin)); + StorageTexture::StampKeepingNewerResults(begin, static_cast(end - begin), directWritesGeneration, [&] { return GuestMemory::MarkPossiblyWritten(begin, static_cast(end - begin)); }); continue; } if (region.mirror != nullptr) { @@ -5368,14 +5930,28 @@ void GuestBufferMemory::WriteBack() { const auto served = std::min(end, servedEnd(region)); if (mirror.heap && begin >= served) continue; const auto length = static_cast((mirror.heap ? served : end) - begin); - const auto current = mirror.buffer->Bytes().subspan(offset, length); if (mirror.heap) { const auto reference = std::find_if(heapReferences.begin(), heapReferences.end(), [&](const auto& entry) { return entry.first == begin; }); Require(reference != heapReferences.end() && reference->second.size() == length, "heap mirror write-back has no reference"); + if (mirror.resident == nullptr) { + const auto current = mirror.buffer->Bytes().subspan(offset, length); + if (mirror.sparse) storeSparse(mirror, begin, current, reference->second); + else storeChanged(begin, current, reference->second); + continue; + } + std::vector current(length); + if (const auto readback = std::find_if(heapReadbacks.begin(), heapReadbacks.end(), [&](const auto& entry) { return entry.first == begin; }); readback != heapReadbacks.end()) { + Require(readback->second->Bytes().size() == length, "heap mirror readback does not match its write-back"); + std::memcpy(current.data(), readback->second->Bytes().data(), length); + } else { + mirror.resident->ReadDevice(begin, current); + } + mirror.resident->Store(begin, current); if (mirror.sparse) storeSparse(mirror, begin, current, reference->second); else storeChanged(begin, current, reference->second); continue; } + const auto current = mirror.buffer->Bytes().subspan(offset, length); GuestMemory::WriteChanged(begin, current, std::span(mirror.shadow).subspan(offset, length)); std::memcpy(mirror.shadow.data() + offset, current.data(), length); continue; @@ -5385,7 +5961,7 @@ void GuestBufferMemory::WriteBack() { if (region.copiedBack) { // The GPU copied the written sub-range back into the import; page write watching // did not see it. - GuestMemory::MarkWritten(begin, static_cast(end - begin)); + StorageTexture::StampKeepingNewerResults(begin, static_cast(end - begin), directWritesGeneration, [&] { return GuestMemory::MarkPossiblyWritten(begin, static_cast(end - begin)); }); continue; } static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; @@ -5426,6 +6002,7 @@ void GuestBufferMemory::WriteBack() { lease.clear(); space.reset(); heapReferences.clear(); + heapReadbacks.clear(); } std::optional>> GuestBufferMemory::DirectRegions() const { @@ -5522,6 +6099,7 @@ std::vector> GuestBufferMemory::DeviceRe } void GuestBufferMemory::RecordStagingCopies(Recorder& recorder) { + ResidentBuffer::FlushChanged(context); if (!uploaded || committed) return; static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; std::vector copies; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index 3c81c9c97e..7a7788c964 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -5,6 +5,9 @@ #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "RdnaDecoder/RdnaDescriptorFormat.hpp" #include +#include +#include +#include #include #include @@ -50,6 +53,45 @@ TextureDimension resolveDimension(std::uint32_t raw) { } } +bool ZeroTail(std::span words) { + return words[4] == 0u && words[5] == 0u && words[6] == 0u && words[7] == 0u; +} + +bool SameLevels(const TileMipLayout& a, const TileMipLayout& b) { + return a.tiledOffset == b.tiledOffset && a.tiledSize == b.tiledSize && a.width == b.width && a.height == b.height && a.blocksPerRow == b.blocksPerRow && a.pitchBytes == b.pitchBytes && a.tail == b.tail && a.tailX == b.tailX && a.tailY == b.tailY; +} + +std::optional> R128ResidentLevelWords(std::span words) { + const auto baseLevel = (words[3] >> 12u) & 0xfu; + const auto lastLevel = (words[3] >> 16u) & 0xfu; + const auto typeRaw = (words[3] >> 28u) & 0xfu; + if (baseLevel == 0u || baseLevel > lastLevel || (typeRaw != 8u && typeRaw != 9u)) return std::nullopt; + const std::array chainWords{words[0], words[1], words[2], words[3], 0u, lastLevel << 4u, 0u, 0u}; + const auto width = (((words[1] >> 30u) & 0x3u) | ((words[2] & 0xfffu) << 2u)) + 1u; + const auto height = ((words[2] >> 14u) & 0x3fffu) + 1u; + const auto minLod = (words[1] >> 8u) & 0xfffu; + const auto widthMinus1 = std::max(width >> baseLevel, 1u) - 1u; + const auto heightMinus1 = std::max(height >> baseLevel, 1u) - 1u; + const auto rebasedMinLod = minLod > baseLevel * 256u ? minLod - baseLevel * 256u : 0u; + const auto levels = lastLevel - baseLevel; + auto rebased = chainWords; + rebased[1] = (words[1] & ~((0xfffu << 8u) | (0x3u << 30u))) | (rebasedMinLod << 8u) | ((widthMinus1 & 0x3u) << 30u); + rebased[2] = (words[2] & ~(0xfffu | (0x3fffu << 14u))) | ((widthMinus1 >> 2u) & 0xfffu) | ((heightMinus1 & 0x3fffu) << 14u); + rebased[3] = (words[3] & ~(0xffu << 12u)) | (levels << 16u); + rebased[5] = levels << 4u; + try { + const auto chain = DescribeSurface(DecodeTextureResource(chainWords)); + const auto view = DescribeSurface(DecodeTextureResource(rebased)); + if (chain.mips.size() != lastLevel + 1u || view.mips.size() != levels + 1u || chain.layers != view.layers) return std::nullopt; + for (std::uint32_t level = baseLevel; level <= lastLevel; ++level) { + if (!SameLevels(chain.mips[level], view.mips[level - baseLevel])) return std::nullopt; + } + } catch (const std::exception&) { + return std::nullopt; + } + return rebased; +} + } bool IsNullTextureResource(std::span words) { @@ -138,11 +180,15 @@ GuestTextureResource DecodeTextureResource(std::span words) Require(baseLevel <= lastLevel, "guest texture descriptor has a base mip level past its last mip level"); const bool fmask = ShaderRecompiler::IsFmaskTextureFormat(static_cast(format)); + const auto fmaskLayout = ShaderRecompiler::FmaskLayoutOf(static_cast(format)); if (fmask) { - Require(format == static_cast(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8), "guest FMASK format is not implemented"); - Require(!metaCompress && !writeCompress, "FMASK CMASK compression is not implemented"); + Require(fmaskLayout.has_value(), "guest FMASK format is not implemented"); + Require(!writeCompress && (!metaCompress || metaAddr != 0u), "FMASK CMASK compression is not implemented without readable metadata"); + Require(!metaCompress || metaPipeAligned, "FMASK CMASK metadata must be pipe aligned"); + Require(!metaCompress || format == static_cast(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8), "FMASK CMASK compression is implemented only for 8-sample, 8-fragment FMASK"); Require((typeRaw == 9u || typeRaw == 13u) && tileModeRaw == 24u && baseLevel == 0u && lastLevel == 0u && maxMip == 0u, "FMASK reads require a single-mip 2D or 2D-array Z_64KB_X surface"); - for (const auto selector : {dstSelX, dstSelY, dstSelZ, dstSelW}) Require(selector == 0u || selector == 1u || selector == 4u, "FMASK destination channel selector is not implemented"); + const bool identity = format != static_cast(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8) && dstSelX == 4u && dstSelY == 5u && dstSelZ == 6u && dstSelW == 7u; + for (const auto selector : {dstSelX, dstSelY, dstSelZ, dstSelW}) Require(identity || selector == 0u || selector == 1u || selector == 4u, "FMASK destination channel selector is not implemented"); } const auto describe = [&] { return " (format " + std::to_string(format) + ", " + std::to_string(width) + "x" + std::to_string(height) + "x" + std::to_string(depth + 1) + ", type " + std::to_string(typeRaw) + ", tile " + std::to_string(tileModeRaw) + ", mips " + std::to_string(baseLevel) + ".." + std::to_string(lastLevel) + " of " + std::to_string(maxMip + 1) + ")"; }; @@ -223,25 +269,67 @@ GuestTextureResource DecodeTextureResource(std::span words) result.lastLevel = lastLevel; result.tileMode = tileMode; result.dimension = dimension; - result.format = fmask ? static_cast(ShaderRecompiler::IrBufferFormat::Format32UInt) : format; + result.format = !fmask ? format : static_cast(fmaskLayout->storageBits == 8u ? ShaderRecompiler::IrBufferFormat::Format8UInt : fmaskLayout->storageBits == 16u ? ShaderRecompiler::IrBufferFormat::Format16UInt : ShaderRecompiler::IrBufferFormat::Format32UInt); result.dstSelX = static_cast(dstSelX); result.dstSelY = static_cast(dstSelY); result.dstSelZ = static_cast(dstSelZ); result.dstSelW = static_cast(dstSelW); result.bcSwizzle = static_cast(bcSwizzle); - result.dccAddress = metaCompress ? metaAddr << 8u : 0u; + result.dccAddress = metaCompress && !fmask ? metaAddr << 8u : 0u; + result.fmaskCmaskAddress = metaCompress && fmask ? metaAddr << 8u : 0u; result.dccAlphaOnMsb = dccAlphaPos; result.dccPipeAligned = metaPipeAligned; result.iterate256 = iterate256; result.minLod = minLod; result.samples = samples; if (baseLevel > maxMip) { - Require(LevelsFitAllocation(result, lastLevel + 1u), "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe()); + const bool fits = LevelsFitAllocation(result, lastLevel + 1u); + if (!fits && ZeroTail(words)) { + if (const auto resident = R128ResidentLevelWords(words)) return DecodeTextureResource(*resident); + } + Require(fits, "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe()); result.mipCount = lastLevel + 1u; } return result; } +std::array EncodeTextureResource(const GuestTextureResource& resource) { + const bool multisampled = resource.dimension == TextureDimension::k2DMsaa; + Require((resource.dimension == TextureDimension::k2D && resource.samples == 1u) || (multisampled && resource.samples >= 2u && resource.samples <= 16u && std::has_single_bit(resource.samples)), "only 2D and multisampled 2D surfaces have a texture descriptor encoding"); + Require(resource.mipCount == 1u && resource.allocatedMipCount <= 1u && resource.baseLevel == 0u && resource.lastLevel == 0u && resource.depthOrLastArray == 0u && resource.baseArray == 0u && resource.dccAddress == 0u && resource.minLod == 0u && !resource.iterate256 && resource.bcSwizzle == 0u, "only single-level, single-layer surfaces without metadata have a texture descriptor encoding"); + Require(resource.width >= 1u && resource.width <= 0x4000u && resource.height >= 1u && resource.height <= 0x4000u && resource.format < 0x200u, "the surface's extent or format has no texture descriptor encoding"); + const auto xorMask = resource.tileMode == TextureTileMode::kS4KBX || resource.tileMode == TextureTileMode::kD4KBX ? 0xfffull : 0xffffull; + Require(XorSwizzleMode(resource.tileMode) != 0 ? (resource.baseAddress & xorMask) == 0 && (resource.pipeBankXor & ~xorMask) == 0 : resource.pipeBankXor == 0, "the surface's pipe and bank XOR has no texture descriptor encoding"); + const auto base = (resource.baseAddress | resource.pipeBankXor) >> 8u; + Require(base != 0 && (resource.baseAddress & 0xffu) == 0 && base <= 0xffffffffffull, "the surface's address has no texture descriptor encoding"); + std::uint32_t tileMode = 0; + switch (resource.tileMode) { + case TextureTileMode::kLinear: tileMode = 0x00; break; + case TextureTileMode::kStandard256B: tileMode = 0x01; break; + case TextureTileMode::kD256B: tileMode = 0x02; break; + case TextureTileMode::kStandard4KB: tileMode = 0x05; break; + case TextureTileMode::kD4KB: tileMode = 0x06; break; + case TextureTileMode::kStandard64KB: tileMode = 0x09; break; + case TextureTileMode::kD64KB: tileMode = 0x0a; break; + case TextureTileMode::kS64KBT: tileMode = 0x11; break; + case TextureTileMode::kD64KBT: tileMode = 0x12; break; + case TextureTileMode::kS4KBX: tileMode = 0x15; break; + case TextureTileMode::kD4KBX: tileMode = 0x16; break; + case TextureTileMode::kZ64KBX: tileMode = 0x18; break; + case TextureTileMode::kS64KBX: tileMode = 0x19; break; + case TextureTileMode::kD64KBX: tileMode = 0x1a; break; + case TextureTileMode::kR64KBX: tileMode = 0x1b; break; + } + const auto sampleLevel = static_cast(std::countr_zero(resource.samples)); + std::array words{}; + words[0] = static_cast(base); + words[1] = static_cast(base >> 32u) | (resource.format << 20u) | (((resource.width - 1u) & 3u) << 30u); + words[2] = ((resource.width - 1u) >> 2u) | ((resource.height - 1u) << 14u); + words[3] = resource.dstSelX | (resource.dstSelY << 3u) | (resource.dstSelZ << 6u) | (resource.dstSelW << 9u) | (sampleLevel << 16u) | (tileMode << 20u) | ((multisampled ? 14u : 9u) << 28u); + words[5] = sampleLevel << 4u; + return words; +} + float EffectiveMinLod(const GuestTextureResource& resource) { if (resource.minLod <= resource.baseLevel * 256u) return 0.0f; return std::min(static_cast(resource.minLod) / 256.0f, static_cast(resource.lastLevel)); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp index 8911499a06..b4759d8ee0 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp @@ -2,13 +2,17 @@ #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" #include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" +#include "prx/libSceAgcDriver/Graphics/include/FirstSampleCopy.hpp" #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/SampleOrder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" +#include "IntermediateRepresentation/IrMetadata/BufferFormat.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include @@ -45,10 +49,57 @@ std::uint64_t collectBlocks(std::uint64_t address, std::size_t bytes) { return generation != 0 ? generation : GuestMemory::CollectWritesUncached(address, bytes); } +GuestTextureResource fmaskSnapshotResource(const GuestTextureResource& resource) { + Require(resource.format == static_cast(ShaderRecompiler::IrBufferFormat::Format32UInt) && resource.fmaskCmaskAddress != 0u, "FMASK image format read semantics are not implemented"); + Require(resource.dimension == TextureDimension::k2D || resource.dimension == TextureDimension::k2DArray, "FMASK image dimension is not implemented"); + Require(resource.tileMode == TextureTileMode::kZ64KBX && (resource.pipeBankXor & ~0xcf00u) == 0u, "FMASK image swizzle or pipe/bank XOR is not implemented"); + Require(resource.width != 0u && resource.width <= 16384u && resource.height != 0u && resource.height <= 16384u && resource.depthOrLastArray < 8192u && resource.baseArray <= resource.depthOrLastArray, "invalid FMASK image extent or layer range"); + Require(resource.dimension == TextureDimension::k2DArray || (resource.depthOrLastArray == 0u && resource.baseArray == 0u), "FMASK 2D image has an array range"); + Require(resource.mipCount == 1u && resource.allocatedMipCount == 1u && resource.baseLevel == 0u && resource.lastLevel == 0u && resource.minLod == 0u, "FMASK image mip fields are not implemented"); + Require(resource.fmaskCmaskAddress == 0u || resource.dccPipeAligned, "FMASK image CMASK must be pipe aligned"); + for (const auto selector : {resource.dstSelX, resource.dstSelY, resource.dstSelZ, resource.dstSelW}) Require(selector == 0u || selector == 1u || selector == 4u, "FMASK image selects an unverified channel"); + auto snapshot = resource; + snapshot.format = static_cast(ShaderRecompiler::IrBufferFormat::Format32UInt); + snapshot.tileMode = TextureTileMode::kLinear; + snapshot.pipeBankXor = 0u; + snapshot.dccAddress = 0u; + snapshot.fmaskCmaskAddress = 0u; + snapshot.dccAlphaOnMsb = false; + snapshot.dccPipeAligned = false; + return snapshot; +} + +GuestTextureResource fmaskResource(const ColorTarget& target) { + GuestTextureResource resource{}; + resource.baseAddress = target.fmaskAddress; + resource.width = target.extent.width; + resource.height = target.extent.height; + resource.mipCount = 1u; + resource.allocatedMipCount = 1u; + resource.tileMode = TextureTileMode::kZ64KBX; + resource.dimension = TextureDimension::k2D; + resource.format = static_cast(ShaderRecompiler::IrBufferFormat::Format32UInt); + resource.dstSelX = 4u; + resource.pipeBankXor = target.fmaskPipeBankXor; + resource.fmaskCmaskAddress = target.cmaskAddress; + resource.dccPipeAligned = true; + return resource; +} + bool sameTarget(const ColorTarget& a, const ColorTarget& b) { return a.surfaceAddress == b.surfaceAddress && a.format == b.format && a.extent.width == b.extent.width && a.extent.height == b.extent.height && a.samples == b.samples; } +constexpr VkImageUsageFlags twinViewUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + +VkFormat colorTwinFormat(const Context& context, VkFormat format) { + if (const auto srgb = SrgbTwinFormat(context, format); srgb != VK_FORMAT_UNDEFINED) return srgb; + for (const auto unorm : {VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_A8B8G8R8_UNORM_PACK32}) { + if (SrgbTwinFormat(context, unorm) == format) return unorm; + } + return VK_FORMAT_UNDEFINED; +} + bool recreated(const ColorTarget& resident, const ColorTarget& use) { return use.dccAddress != 0 && use.pipeBankXor != resident.pipeBankXor && use.address == resident.address && use.dccAddress == resident.dccAddress && use.dccControl == resident.dccControl && use.dccAlphaOnMsb == resident.dccAlphaOnMsb && use.dccPipeAligned == resident.dccPipeAligned && use.cmaskAddress == resident.cmaskAddress && use.cmaskBytes == resident.cmaskBytes && use.tileMode == resident.tileMode && use.elementBytes == resident.elementBytes; } @@ -101,6 +152,13 @@ public: return context.imageFormatProperties(context.physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, formatUsage, flags, &properties) == VK_SUCCESS && (properties.sampleCounts & samples) != 0; }; if (storageFormat != VK_FORMAT_UNDEFINED && (!context.storageImageMultisample || !storageSupports(storageFormat, VK_IMAGE_USAGE_STORAGE_BIT, 0u) || !storageSupports(target.format, usage | VK_IMAGE_USAGE_STORAGE_BIT, storageFlags))) storageFormat = VK_FORMAT_UNDEFINED; + twinFormat = colorTwinFormat(context, target.format); + if (twinFormat != VK_FORMAT_UNDEFINED) { + VkFormatProperties twinProperties{}; + context.formatProperties(context.physical, twinFormat, &twinProperties); + const VkImageCreateFlags twinFlags = orderFlags | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + if ((twinProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) == 0 || !storageSupports(target.format, usage, twinFlags) || !storageSupports(twinFormat, twinViewUsage, twinFlags)) twinFormat = VK_FORMAT_UNDEFINED; + } try { const auto created = allocate(usage, orderFlags); image = created.image; @@ -135,6 +193,16 @@ public: try { VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; info.flags = flags; + const std::array viewFormats{target.format, twinFormat}; + VkImageFormatListCreateInfo formatList{VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO}; + if (twinFormat != VK_FORMAT_UNDEFINED) { + if ((flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0 && context.imageFormatList) { + formatList.viewFormatCount = static_cast(viewFormats.size()); + formatList.pViewFormats = viewFormats.data(); + info.pNext = &formatList; + } + info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + } info.imageType = VK_IMAGE_TYPE_2D; info.format = target.format; info.extent = {target.extent.width, target.extent.height, 1}; @@ -161,7 +229,7 @@ public: viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; VkImageViewUsageCreateInfo viewUsage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; viewUsage.usage = imageUsage & ~VK_IMAGE_USAGE_STORAGE_BIT; - if (orderFormat != VK_FORMAT_UNDEFINED) viewInfo.pNext = &viewUsage; + if ((imageUsage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) viewInfo.pNext = &viewUsage; Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &created.view), "vkCreateImageView multisampled target"); } catch (...) { destroy(created); @@ -203,6 +271,7 @@ public: Recorder::CountBarriers(Recorder::CommandClass::Draw, 3); for (auto& [key, texture] : textures) retiredTextures.push_back(std::move(texture)); textures.clear(); + retired.push_back({VK_NULL_HANDLE, VK_NULL_HANDLE, std::exchange(twinView, VK_NULL_HANDLE)}); retired.push_back({image, memory, view}); image = storable.image; memory = storable.memory; @@ -212,6 +281,28 @@ public: recorder.Keep(shared_from_this(), static_cast(storable.bytes)); } + std::shared_ptr FmaskSampled(const GuestTextureResource& resource, VkComponentMapping components) { + const auto snapshot = fmaskSnapshotResource(resource); + Require(target.samples == 8u && target.dccAddress != 0u && fmaskRange.has_value(), "native FMASK sampling requires a tracked eight-fragment DCC target"); + Require(target.tileMode == ColorTileMode::RenderTarget && target.depth == 1u && target.depthSlice == 0u && target.layers == 1u && target.arrayLastSlice == 0u && target.arraySlice == 0u && target.mip == 0u && target.mipCount == 1u, "native FMASK target layout is not implemented"); + Require(target.fmaskAddress == resource.baseAddress && target.fmaskPipeBankXor == resource.pipeBankXor && target.cmaskAddress == resource.fmaskCmaskAddress, "native FMASK descriptor does not match its resident target"); + Require(target.extent.width == resource.width && target.extent.height == resource.height && resource.baseArray == 0u && resource.depthOrLastArray == 0u, "native FMASK extent or subresource does not match its resident target"); + auto* recorder = Recorder::Active(); + Require(recorder != nullptr && recorder->Device() == context.device && !Recorder::InCompletion(), "native FMASK sampling requires its device's active recorder outside completion"); + ApplyFastClear(target); + Keep(); + const std::array key{static_cast(components.r), static_cast(components.g), static_cast(components.b), static_cast(components.a), static_cast(resource.dimension)}; + auto& texture = fmaskTextures[key]; + if (texture == nullptr) { + const auto geometry = DescribeSurface(snapshot); + std::vector bytes(geometry.guestBytes); + constexpr std::uint32_t expanded = 0x76543210u; + for (std::size_t offset = 0; offset < bytes.size(); offset += sizeof(expanded)) std::memcpy(bytes.data() + offset, &expanded, sizeof(expanded)); + texture = std::make_unique(context, *context.detiler, snapshot, components, bytes); + } + return {shared_from_this(), texture.get()}; + } + void Keep() { if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->Device() == context.device) { recorder->Keep(shared_from_this()); @@ -241,6 +332,7 @@ public: colorFill = MultisampledColorFill(use); fromTexture = false; dccRanges.clear(); + fmaskRange.reset(); metadataValid = std::make_shared>(true); const auto initial = initialDccClear(use); recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) { @@ -336,14 +428,52 @@ public: return {shared_from_this(), texture.get()}; } - VkRenderPass ResolvePass() { + std::shared_ptr FirstSample(VkComponentMapping components) { + const auto source = Sampled({}); + if (firstSampleCopy == nullptr) firstSampleCopy = std::make_unique(context, target.format, target.extent); + firstSampleCopy->Record(*Recorder::Active(), source->View(), source->Layout()); + const std::array key{static_cast(components.r), static_cast(components.g), static_cast(components.b), static_cast(components.a)}; + auto& texture = firstSampleTextures[key]; + if (texture == nullptr) { + texture = std::make_shared(context, firstSampleCopy->Image(), target.format, VK_IMAGE_ASPECT_COLOR_BIT, components); + texture->MarkRefreshedPerUse(); + } + return {shared_from_this(), texture.get()}; + } + + VkImageView View(VkFormat format) { + if (format == target.format) return view; + Require(twinFormat != VK_FORMAT_UNDEFINED && format == twinFormat, "a multisampled color target is only viewed in its own format or its sRGB twin"); + if (twinView == VK_NULL_HANDLE) { + VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = twinFormat; + viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + VkImageViewUsageCreateInfo viewUsage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; + viewUsage.usage = twinViewUsage; + viewInfo.pNext = &viewUsage; + Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &twinView), "vkCreateImageView multisampled target twin"); + } + return twinView; + } + + bool Serves(const ColorTarget& use) const { + if (use.format == target.format) return sameTarget(target, use); + auto twin = use; + twin.format = target.format; + return twinFormat != VK_FORMAT_UNDEFINED && use.format == twinFormat && sameTarget(target, twin); + } + + VkRenderPass ResolvePass(VkFormat format) { + auto& resolvePass = resolvePasses[format]; if (resolvePass != VK_NULL_HANDLE) return resolvePass; std::array attachments{}; - attachments[0].format = target.format; + attachments[0].format = format; attachments[0].samples = static_cast(target.samples); attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; - attachments[1].format = target.format; + attachments[1].format = format; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; @@ -371,12 +501,13 @@ public: Context context; std::shared_ptr> active = std::make_shared>(true); + std::shared_ptr> hostOnly = std::make_shared>(true); std::shared_ptr> metadataValid; ColorTarget target; VkImage image = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE; - VkRenderPass resolvePass = VK_NULL_HANDLE; + std::map resolvePasses; MultisampledCmask cmask; MultisampledColorFill colorFill; bool fromTexture = false; @@ -391,12 +522,18 @@ private: VkImageUsageFlags usage = 0; VkFormat storageFormat = VK_FORMAT_UNDEFINED; VkImageCreateFlags storageFlags = 0; + VkFormat twinFormat = VK_FORMAT_UNDEFINED; + VkImageView twinView = VK_NULL_HANDLE; std::vector retired; VkImageView storageView = VK_NULL_HANDLE; bool storageWritten = false; VkDeviceSize allocationBytes = 0; + std::optional fmaskRange; + std::map, std::unique_ptr> fmaskTextures; std::map, std::shared_ptr> textures; std::vector> retiredTextures; + std::unique_ptr firstSampleCopy; + std::map, std::shared_ptr> firstSampleTextures; std::vector dccRanges; std::optional dccFill; std::optional cmaskFill; @@ -439,7 +576,7 @@ private: if (use.cmaskAddress != 0) dccRanges.push_back({use.cmaskAddress, use.cmaskBytes, 0}); for (auto& range : dccRanges) { GuestMemory::CheckRange(reinterpret_cast(range.address), range.bytes, 256); - Require(GuestMemory::Watched(range.address, range.bytes), "initial multisampled DCC contents require watched guest memory"); + Require(WatchedAfterSettle(context, range.address, range.bytes), "initial multisampled DCC contents require watched guest memory"); range.generation = collectBlocks(range.address, range.bytes); Require(range.generation != 0, "initial multisampled DCC contents have no write generation"); } @@ -450,18 +587,29 @@ private: Require(a.address >= b.address + b.bytes || b.address >= a.address + a.bytes, "overlapping multisampled DCC color or metadata ranges are not implemented"); } } + if (use.samples == 8u && use.fmaskAddress != 0u && use.cmaskAddress != 0u) { + const auto resource = fmaskResource(use); + static_cast(fmaskSnapshotResource(resource)); + fmaskRange = GuestMemory::UnchangedQuery{use.fmaskAddress, static_cast(DescribeSurface(resource).guestBytes), 0}; + auto& range = *fmaskRange; + GuestMemory::CheckRange(reinterpret_cast(range.address), range.bytes, 65536u); + Require(WatchedAfterSettle(context, range.address, range.bytes), "native FMASK contents require watched guest memory"); + range.generation = GuestMemory::CollectWritesUncached(range.address, range.bytes); + Require(range.generation != 0, "native FMASK contents have no write generation"); + for (const auto& other : dccRanges) Require(range.address >= other.address + other.bytes || other.address >= range.address + range.bytes, "native FMASK overlaps its color or metadata planes"); + } std::vector dcc(dccRanges[1].bytes); GuestMemory::Read(use.dccAddress, dcc); std::vector cmask(use.cmaskBytes); if (!cmask.empty()) GuestMemory::Read(use.cmaskAddress, cmask); const auto clear = MultisampledDccClear(use, dcc, cmask); - Require(GuestMemory::UnchangedSinceAll(dccRanges), "initial multisampled DCC contents changed during import"); + Require(GuestMemory::UnchangedSinceAll(dccRanges) && (!fmaskRange || GuestMemory::UnchangedSinceAll(std::span(&*fmaskRange, 1u))), "initial multisampled DCC contents changed during import"); return clear; } void requireDccRange(bool valid, const GuestMemory::UnchangedQuery& range, const char* reason) const { if (valid) return; - const auto* plane = range.address == target.address ? "color" : range.address == target.dccAddress ? "DCC" : "CMASK"; + const auto* plane = range.address == target.address ? "color" : range.address == target.dccAddress ? "DCC" : range.address == target.fmaskAddress ? "FMASK" : "CMASK"; char text[384]; std::snprintf(text, sizeof(text), "%s (%s range 0x%llx+0x%zx, %u samples)", reason, plane, static_cast(range.address), range.bytes, target.samples); Require(false, text); @@ -482,13 +630,25 @@ private: requireDccRange(GuestMemory::UnchangedSince(range.address, range.bytes, range.generation) || !GuestMemory::StoredOver(range.address, range.bytes, range.generation), range, "writes to a resident multisampled DCC target or its metadata are not implemented"); if (auto* recorder = Recorder::Active(); recorder != nullptr) requireDccRange(!recorder->PendingWriteOverlaps(range.address, range.bytes), range, "pending writes to a resident multisampled DCC target or its metadata are not implemented"); } + if (fmaskRange) { + Require(use.fmaskAddress == target.fmaskAddress && use.fmaskPipeBankXor == target.fmaskPipeBankXor, "native FMASK target layout changed"); + const auto& range = *fmaskRange; + requireDccRange(WatchedAfterSettle(context, range.address, range.bytes), range, "native FMASK sampling requires watched guest memory"); + GuestMemory::CollectWritesUncached(range.address, range.bytes); + requireDccRange(GuestMemory::UnchangedSince(range.address, range.bytes, range.generation) || !GuestMemory::StoredOver(range.address, range.bytes, range.generation), range, "writes to a resident native FMASK are not implemented"); + if (auto* recorder = Recorder::Active(); recorder != nullptr) requireDccRange(!recorder->PendingWriteOverlaps(range.address, range.bytes), range, "pending writes to a resident native FMASK are not implemented"); + } } void release() noexcept { + fmaskTextures.clear(); textures.clear(); retiredTextures.clear(); - if (resolvePass) context.Function("vkDestroyRenderPass")(context.device, resolvePass, nullptr); + firstSampleTextures.clear(); + firstSampleCopy.reset(); + for (const auto& [format, resolvePass] : resolvePasses) context.Function("vkDestroyRenderPass")(context.device, resolvePass, nullptr); if (orderView) context.Function("vkDestroyImageView")(context.device, orderView, nullptr); + if (twinView) context.Function("vkDestroyImageView")(context.device, twinView, nullptr); if (storageView) context.Function("vkDestroyImageView")(context.device, storageView, nullptr); destroy({image, memory, view}); for (const auto& allocation : retired) destroy(allocation); @@ -502,6 +662,8 @@ struct GuardedRange { std::uint32_t samples; std::shared_ptr> active; std::shared_ptr> metadataValid; + std::shared_ptr> hostOnly; + bool fmask = false; }; class PrimedSharedMutex : public std::shared_mutex { @@ -575,7 +737,7 @@ void retireUnmapped(std::uintptr_t address, std::size_t bytes) { anyGuarded().store(!guarded().empty(), std::memory_order_release); } -void guard(const Context& context, const ColorTarget& target, const std::shared_ptr>& active, const std::shared_ptr>& metadataValid) { +void guard(const Context& context, const ColorTarget& target, const std::shared_ptr>& active, const std::shared_ptr>& metadataValid, const std::shared_ptr>& hostOnly) { auto bytes = std::max(target.bytes * target.samples, static_cast(target.extent.width) * target.extent.height * target.elementBytes * target.samples); if (MultisampledBlockLog2(target.tileMode) == 16u && target.tileMode != ColorTileMode::Standard64KB && std::has_single_bit(target.elementBytes) && std::has_single_bit(target.samples)) { const auto log2Samples = std::countr_zero(target.samples); @@ -587,10 +749,11 @@ void guard(const Context& context, const ColorTarget& target, const std::shared_ bytes = align(target.extent.width, blockWidth) * align(target.extent.height, blockHeight) * target.elementBytes * target.samples; } std::unique_lock lock(guardedMutex()); - guarded().push_back({context.device, target.address, bytes, target.samples, active}); + guarded().push_back({context.device, target.address, bytes, target.samples, active, nullptr, hostOnly}); if (target.dccAddress != 0) { - guarded().push_back({context.device, target.dccAddress, MultisampledDccBytes(target), target.samples, active, metadataValid}); - if (target.cmaskAddress != 0) guarded().push_back({context.device, target.cmaskAddress, target.cmaskBytes, target.samples, active, metadataValid}); + guarded().push_back({context.device, target.dccAddress, MultisampledDccBytes(target), target.samples, active, metadataValid, hostOnly}); + if (target.cmaskAddress != 0) guarded().push_back({context.device, target.cmaskAddress, target.cmaskBytes, target.samples, active, metadataValid, hostOnly}); + if (target.samples == 8u && target.fmaskAddress != 0u && target.cmaskAddress != 0u) guarded().push_back({context.device, target.fmaskAddress, DescribeSurface(fmaskResource(target)).guestBytes, target.samples, active, metadataValid, hostOnly}); } anyGuarded().store(true, std::memory_order_release); InstallResidentImageReadGuard_nid_no_patch(); @@ -610,22 +773,38 @@ void discardRetiredTargets() { std::erase_if(targets(), [](const auto& target) { return !target->active->load(std::memory_order_acquire); }); } -MultisampleTarget& acquire(const Context& context, const ColorTarget& use) { +bool holdHostOnly(const MultisampleTarget& target) { + std::unique_lock lock(guardedMutex()); + if (!target.active->load(std::memory_order_acquire)) return false; + target.hostOnly->store(true, std::memory_order_release); + return true; +} + +MultisampleTarget& acquire(const Context& context, const ColorTarget& requested) { discardRetiredTargets(); for (const auto& target : targets()) { if (!target->active->load(std::memory_order_acquire)) continue; - if (target->context.device != context.device || !sameTarget(target->target, use)) continue; + if (target->context.device != context.device || !target->Serves(requested)) continue; + auto use = requested; + use.format = target->target.format; if (recreated(target->target, use)) { { std::unique_lock lock(guardedMutex()); + if (!target->active->load(std::memory_order_acquire)) continue; std::erase_if(guarded(), [&](const auto& range) { return range.active == target->active; }); anyGuarded().store(!guarded().empty(), std::memory_order_release); } target->Recreate(use); - guard(context, use, target->active, target->metadataValid); + target->hostOnly->store(true, std::memory_order_release); + guard(context, use, target->active, target->metadataValid, target->hostOnly); target->ApplyFastClear(use); return *target; } + { + std::unique_lock lock(guardedMutex()); + if (!target->active->load(std::memory_order_acquire)) continue; + target->hostOnly->store(true, std::memory_order_release); + } if (target->fromTexture && use.cmaskAddress != 0) { target->fromTexture = false; target->cmask = MultisampledCmask(use.cmaskAddress, use.cmaskBytes); @@ -636,18 +815,19 @@ MultisampleTarget& acquire(const Context& context, const ColorTarget& use) { target->ApplyFastClear(use); return *target; } + const auto& use = requested; auto target = std::make_shared(context, use); target->Keep(); targets().push_back(std::move(target)); auto& created = *targets().back(); - guard(context, use, created.active, created.metadataValid); + guard(context, use, created.active, created.metadataValid, created.hostOnly); if (use.cmaskFastClear && use.dccAddress == 0) created.cmask.SeedCode(CurrentUniformMetadataByte(use.cmaskAddress, static_cast(use.cmaskBytes))); created.ApplyFastClear(use); created.Keep(); return created; } -MultisampleTarget& createFilled(const Context& context, const GuestTextureResource& resource, VkFormat format, const char* access) { +MultisampleTarget& createFilled(const Context& context, const GuestTextureResource& resource, VkFormat format, const char* access, bool written) { const auto refuse = [&](const std::string& reason) { char text[192]; std::snprintf(text, sizeof(text), "AGC graphics: %s the %ux%u multisampled texture 0x%llx of %u samples, which no draw rendered to and ", access, resource.width, resource.height, static_cast(resource.baseAddress), resource.samples); @@ -714,7 +894,8 @@ MultisampleTarget& createFilled(const Context& context, const GuestTextureResour target->Keep(); targets().push_back(std::move(target)); auto& created = *targets().back(); - guard(context, use, created.active, created.metadataValid); + created.hostOnly->store(written, std::memory_order_release); + guard(context, use, created.active, created.metadataValid, created.hostOnly); created.colorFill.NoteFill(use.address, static_cast(bytes), value); created.fromTexture = true; created.htileAddress = resource.dccAddress; @@ -871,7 +1052,7 @@ VkClearColorValue MultisampledDccClear(const ColorTarget& target, std::span(cmask.front()); const auto expanded = target.samples == 2 ? 0xddu : target.samples == 4 ? 0xeeu : 0xffu; Require(std::ranges::all_of(cmask, [&](std::byte key) { return key == cmask.front(); }) && (code == 0xccu || code == expanded), "multisampled DCC CMASK compression or register clear is not implemented"); @@ -956,9 +1137,9 @@ SampleOrderTarget MultisampleTargetOrderTarget(const Context& context, const Col VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, VkImage* image) { std::lock_guard lock(targetsMutex()); - const auto& acquired = acquire(context, target); + auto& acquired = acquire(context, target); if (image != nullptr) *image = acquired.image; - return acquired.view; + return acquired.View(target.format); } VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, std::shared_ptr& owner, VkImage* image) { @@ -966,7 +1147,7 @@ VkImageView MultisampleTargetView(const Context& context, const ColorTarget& tar auto& acquired = acquire(context, target); owner = acquired.shared_from_this(); if (image != nullptr) *image = acquired.image; - return acquired.view; + return acquired.View(target.format); } void KeepMultisampleTarget_nid_no_patch(const std::shared_ptr& owner) { @@ -990,6 +1171,12 @@ VkComponentMapping swapRedBlue(VkComponentMapping components) { return {swapped(components.r, VK_COMPONENT_SWIZZLE_R), swapped(components.g, VK_COMPONENT_SWIZZLE_G), swapped(components.b, VK_COMPONENT_SWIZZLE_B), swapped(components.a, VK_COMPONENT_SWIZZLE_A)}; } +bool sameStorageForm(const Context& context, VkFormat rendered, VkFormat requested) { + if (rendered == requested) return true; + const auto storage = HostStorageFormat(context, rendered); + return storage != VK_FORMAT_UNDEFINED && storage == HostStorageFormat(context, requested); +} + } std::shared_ptr MultisampleTargetTexture(const Context& context, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType viewType) { @@ -1008,13 +1195,36 @@ std::shared_ptr MultisampleTargetTexture(const Context& context, const } if (other == nullptr) { Require(Recorder::Active() != nullptr, "a multisampled texture is only sampled by recorded draws and dispatches"); - return createFilled(context, resource, format, "sampling").Sampled(components, viewType); + return createFilled(context, resource, format, "sampling", false).Sampled(components, viewType); } char text[320]; std::snprintf(text, sizeof(text), "AGC graphics: sampling multisampled color target 0x%llx (%ux%u, %u samples, vk format %d) as a %ux%u texture of %u samples and guest format %u (vk %d) is not implemented", static_cast(resource.baseAddress), other->target.extent.width, other->target.extent.height, other->target.samples, static_cast(other->target.format), resource.width, resource.height, resource.samples, resource.format, static_cast(format)); throw std::runtime_error(text); } +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 MultisampleTarget* other = nullptr; + for (const auto& target : targets()) { + if (!target->active->load(std::memory_order_acquire)) continue; + if (target->context.device != context.device || target->target.surfaceAddress != resource.baseAddress) continue; + const auto& rendered = target->target; + const bool sameShape = rendered.extent.width == resource.width && rendered.extent.height == resource.height && rendered.samples == resource.samples && rendered.mip == 0; + if (sameShape && rendered.format == format) return target->FirstSample(components); + if (sameShape && redBlueSwapped(rendered.format, format)) return target->FirstSample(swapRedBlue(components)); + other = target.get(); + } + if (other == nullptr) { + Require(Recorder::Active() != nullptr, "a multisampled texture is only sampled by recorded draws and dispatches"); + return createFilled(context, resource, format, "sampling", false).FirstSample(components); + } + char text[320]; + std::snprintf(text, sizeof(text), "AGC graphics: reading sample 0 of multisampled color target 0x%llx (%ux%u, %u samples, vk format %d) as a %ux%u texture of %u samples and guest format %u (vk %d) is not implemented", static_cast(resource.baseAddress), other->target.extent.width, other->target.extent.height, other->target.samples, static_cast(other->target.format), resource.width, resource.height, resource.samples, resource.format, static_cast(format)); + throw std::runtime_error(text); +} + void MaterializeMultisampleTargetClear(const Context& context, const ColorTarget& target) { std::lock_guard lock(targetsMutex()); acquire(context, target); @@ -1029,12 +1239,15 @@ VkImageView MultisampleTargetStorageView(const Context& context, const GuestText if (!target->active->load(std::memory_order_acquire)) continue; if (target->context.device != context.device || target->target.surfaceAddress != resource.baseAddress) continue; const auto& rendered = target->target; - if (rendered.format == format && rendered.extent.width == resource.width && rendered.extent.height == resource.height && rendered.samples == resource.samples && rendered.mip == 0) return target->Storage(StorageFormatForGuest(context, resource.format)); + if (sameStorageForm(context, rendered.format, format) && rendered.extent.width == resource.width && rendered.extent.height == resource.height && rendered.samples == resource.samples && rendered.mip == 0) { + if (!holdHostOnly(*target)) continue; + return target->Storage(StorageFormatForGuest(context, resource.format)); + } other = target.get(); } if (other == nullptr) { Require(Recorder::Active() != nullptr, "a multisampled color target is only written by recorded dispatches"); - return createFilled(context, resource, format, "writing").Storage(StorageFormatForGuest(context, resource.format)); + return createFilled(context, resource, format, "writing", true).Storage(StorageFormatForGuest(context, resource.format)); } char text[320]; std::snprintf(text, sizeof(text), "AGC graphics: storage image access to multisampled color target 0x%llx (%ux%u, %u samples, vk format %d) as a %ux%u texture of %u samples and guest format %u (vk %d) is not implemented", static_cast(resource.baseAddress), other->target.extent.width, other->target.extent.height, other->target.samples, static_cast(other->target.format), resource.width, resource.height, resource.samples, resource.format, static_cast(format)); @@ -1052,12 +1265,12 @@ void ResolveMultisampleTarget(const Context& context, const ColorTarget& source, std::lock_guard lock(targetsMutex()); auto& target = acquire(context, source); target.Keep(); - const std::array views{target.view, destinationView}; - auto framebuffer = std::make_shared(context, target.ResolvePass(), views, source.extent); + const std::array views{target.View(source.format), destinationView}; + auto framebuffer = std::make_shared(context, target.ResolvePass(source.format), views, source.extent); recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) { RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; - begin.renderPass = target.ResolvePass(); + begin.renderPass = target.ResolvePass(source.format); begin.framebuffer = framebuffer->Handle(); begin.renderArea = {{0, 0}, source.extent}; context.Resolved(&DeviceFunctions::cmdBeginRenderPass, "vkCmdBeginRenderPass")(commands, &begin, VK_SUBPASS_CONTENTS_INLINE); @@ -1074,10 +1287,13 @@ void ResolveMultisampleTarget(const Context& context, const ColorTarget& source, bool MultisampledColorFillAbsorbs(std::uint64_t address, std::size_t bytes, std::uint32_t pattern) { if (bytes == 0) return false; std::lock_guard lock(targetsMutex()); - return std::any_of(targets().begin(), targets().end(), [&](const auto& target) { + bool absorbed = false; + for (const auto& target : targets()) { VkClearColorValue clear{}; - return target->active->load(std::memory_order_acquire) && target->target.address == address && MultisampledColorBytes(target->target) == bytes && MultisampledFillClearColor(target->target, pattern, clear); - }); + if (!target->active->load(std::memory_order_acquire) || target->target.address != address || MultisampledColorBytes(target->target) != bytes || !MultisampledFillClearColor(target->target, pattern, clear)) continue; + absorbed = holdHostOnly(*target) || absorbed; + } + return absorbed; } void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern) { @@ -1107,6 +1323,20 @@ void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32 } } +std::shared_ptr MultisampleTargetFmaskTexture_nid_no_patch(const Context& context, const GuestTextureResource& resource, VkComponentMapping components) { + std::lock_guard lock(targetsMutex()); + discardRetiredTargets(); + std::shared_ptr owner; + for (const auto& candidate : targets()) { + if (!candidate->active->load(std::memory_order_acquire) || candidate->context.device != context.device) continue; + const auto& target = candidate->target; + if (target.fmaskAddress != resource.baseAddress && (resource.fmaskCmaskAddress == 0u || target.cmaskAddress != resource.fmaskCmaskAddress)) continue; + Require(owner == nullptr, "FMASK descriptor refers to more than one resident target"); + owner = candidate; + } + return owner != nullptr ? owner->FmaskSampled(resource, components) : nullptr; +} + void ClearMultisampleTargets(VkDevice device) { std::lock_guard lock(targetsMutex()); std::erase_if(targets(), [&](const auto& target) { return target->context.device == device; }); @@ -1116,6 +1346,17 @@ void ClearMultisampleTargets(VkDevice device) { UpdateResidentImageUnmapObserver_nid_no_patch(); } +void GuardOneFragmentFmask(const Context& context, const ColorTarget& target) { + const auto bits = static_cast(target.extent.width) * target.extent.height * target.coverageSamples; + const auto bytes = std::max((bits / 8u + 0xffffu) & ~0xffffull, 0x10000u); + std::unique_lock lock(guardedMutex()); + const bool known = std::ranges::any_of(guarded(), [&](const auto& range) { return range.fmask && range.device == context.device && range.address == target.fmaskAddress && range.bytes == bytes; }); + if (!known) guarded().push_back({context.device, target.fmaskAddress, bytes, target.coverageSamples, std::make_shared>(true), nullptr, std::make_shared>(true), true}); + anyGuarded().store(true, std::memory_order_release); + lock.unlock(); + InstallResidentImageReadGuard_nid_no_patch(); +} + void RequireNoMultisampleTarget(std::uint64_t address, std::size_t bytes, const char* access) { RequireNoResidentDepthRead_nid_no_patch(address, bytes); RequireNoMultisampleColorTarget(address, bytes, access); @@ -1123,13 +1364,36 @@ void RequireNoMultisampleTarget(std::uint64_t address, std::size_t bytes, const void RequireNoMultisampleColorTarget(std::uint64_t address, std::size_t bytes, const char* access) { if (bytes == 0 || !anyGuarded().load(std::memory_order_acquire)) return; - std::shared_lock lock(guardedMutex()); - for (const auto& range : guarded()) { - if (address >= range.address + range.bytes || range.address >= address + bytes) continue; + const auto overlaps = [&](const GuardedRange& range) { return address < range.address + range.bytes && range.address < address + bytes; }; + const auto refuse = [&](const GuardedRange& range) { char text[256]; + if (range.fmask) { + std::snprintf(text, sizeof(text), "AGC graphics: %s of 0x%llx+0x%zx overlaps the FMASK 0x%llx+0x%llx of a one-fragment color target of %u samples drawn on the host, which does not write it: guest access to it is not implemented", access, static_cast(address), bytes, static_cast(range.address), static_cast(range.bytes), range.samples); + throw std::runtime_error(text); + } std::snprintf(text, sizeof(text), "AGC graphics: %s of 0x%llx+0x%zx overlaps multisampled color target 0x%llx+0x%llx (%u samples), whose samples are only on the host: guest access to it is not implemented", access, static_cast(address), bytes, static_cast(range.address), static_cast(range.bytes), range.samples); throw std::runtime_error(text); + }; + { + std::shared_lock lock(guardedMutex()); + bool retirable = false; + for (const auto& range : guarded()) { + if (!overlaps(range)) continue; + if (range.hostOnly->load(std::memory_order_acquire)) refuse(range); + retirable = true; + } + if (!retirable) return; } + std::unique_lock lock(guardedMutex()); + std::vector>> retired; + for (const auto& range : guarded()) { + if (!overlaps(range)) continue; + if (range.hostOnly->load(std::memory_order_acquire)) refuse(range); + retired.push_back(range.active); + } + for (const auto& active : retired) active->store(false, std::memory_order_release); + std::erase_if(guarded(), [&](const auto& range) { return std::find(retired.begin(), retired.end(), range.active) != retired.end(); }); + anyGuarded().store(!guarded().empty(), std::memory_order_release); } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTextureLayout.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTextureLayout.cpp new file mode 100644 index 0000000000..96a5cbc02a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTextureLayout.cpp @@ -0,0 +1,120 @@ +#include "prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/MultisampleSwizzleEquations.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include +#include +#include +#include + +namespace AgcDriver::Graphics { +namespace { + +std::array inverseBlock(const MultisampleSwizzleEquation& equation, std::uint32_t byteBits, std::uint32_t fragmentBits, std::uint32_t widthBits) { + std::array rows{}, inverse{}; + for (std::uint32_t input = 0; input < 16u; ++input) { + std::uint32_t address = 0u; + if (input < byteBits) address = 1u << input; + else { + const auto coordinateBit = input < byteBits + fragmentBits ? 48u + input - byteBits : input < byteBits + fragmentBits + widthBits ? input - byteBits - fragmentBits : 16u + input - byteBits - fragmentBits - widthBits; + const auto coordinate = std::uint64_t{1} << coordinateBit; + for (std::uint32_t output = 0; output < 16u; ++output) address |= static_cast(std::popcount(coordinate & equation.bits[output]) & 1u) << output; + } + for (std::uint32_t output = 0; output < 16u; ++output) rows[output] |= ((address >> output) & 1u) << input; + inverse[input] = 1u << input; + } + for (std::uint32_t input = 0; input < 16u; ++input) { + auto pivot = input; + while (pivot < 16u && (rows[pivot] & (1u << input)) == 0u) ++pivot; + Require(pivot < 16u, "multisample block address equation is not invertible"); + std::swap(rows[input], rows[pivot]); + std::swap(inverse[input], inverse[pivot]); + for (std::uint32_t row = 0; row < 16u; ++row) { + if (row == input || (rows[row] & (1u << input)) == 0u) continue; + rows[row] ^= rows[input]; + inverse[row] ^= inverse[input]; + } + } + return inverse; +} + +std::vector axisOffsets(const MultisampleSwizzleEquation& equation, std::uint32_t count, std::uint32_t shift) { + std::vector offsets(count); + for (std::uint32_t value = 0; value < count; ++value) { + const auto coordinate = static_cast(value) << shift; + for (std::uint32_t bit = 0; bit < equation.bits.size(); ++bit) { + offsets[value] |= static_cast(std::popcount(coordinate & equation.bits[bit]) & 1u) << bit; + } + } + return offsets; +} + +} + +MultisampleTextureLayout::MultisampleTextureLayout(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t fragments, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t pipeBankXor) : elementBytes(elementBytes), fragments(fragments), width(width), height(height), layers(layers), pipeBankXor(pipeBankXor) { + Require(width != 0 && height != 0 && width <= 16384u && height <= 16384u && layers != 0 && layers <= 8192u, "invalid multisampled surface extent or layer count"); + Require(std::has_single_bit(elementBytes) && elementBytes <= 16u, "unsupported multisampled surface element size"); + Require(fragments == 2u || fragments == 4u || fragments == 8u, "unsupported multisampled surface fragment count"); + Require((pipeBankXor & ~0xcf00u) == 0, "multisampled surface pipe/bank XOR uses bits outside its 16-pipe swizzle"); + Require(tileMode != TextureTileMode::kZ64KBX || elementBytes <= 4u, "multisampled Z surface element size exceeds 32 bits"); + const auto* equation = FindMultisampleSwizzleEquation(tileMode, elementBytes, fragments); + Require(equation != nullptr, "unsupported multisampled surface tile mode"); + + const auto fragmentBits = static_cast(std::countr_zero(fragments)); + const auto elementBits = 16u - static_cast(std::countr_zero(elementBytes)) - fragmentBits; + const auto widthBits = (elementBits + ((fragmentBits & 1u) == 0 ? 1u : 0u)) / 2u; + blockWidth = 1u << widthBits; + blockHeight = 1u << (elementBits - widthBits); + inverseBlockEquation = inverseBlock(*equation, static_cast(std::countr_zero(elementBytes)), fragmentBits, widthBits); + pitch = (width + blockWidth - 1u) / blockWidth * blockWidth; + paddedHeight = (height + blockHeight - 1u) / blockHeight * blockHeight; + sliceBytes = static_cast(pitch) * paddedHeight * fragments * elementBytes; + Require(sliceBytes <= std::numeric_limits::max() / layers, "multisampled surface size overflows"); + xOffsets = axisOffsets(*equation, width, 0u); + yOffsets = axisOffsets(*equation, height, 16u); + layerOffsets = axisOffsets(*equation, layers, 32u); + fragmentOffsets = axisOffsets(*equation, fragments, 48u); +} + +std::uint64_t MultisampleTextureLayout::offset(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t fragment) const { + const auto block = static_cast(y / blockHeight) * (pitch / blockWidth) + x / blockWidth; + const auto withinBlock = xOffsets[x] ^ yOffsets[y] ^ layerOffsets[layer] ^ fragmentOffsets[fragment] ^ pipeBankXor; + return static_cast(layer) * sliceBytes + block * Alignment + withinBlock; +} + +std::uint64_t MultisampleTextureLayout::Offset(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t fragment) const { + Require(x < width && y < height && layer < layers && fragment < fragments, "multisampled surface coordinate is outside its extent"); + return offset(x, y, layer, fragment); +} + +void MultisampleTextureLayout::transfer(std::span source, std::span destination, bool retile) const { + const auto sourceBytes = retile ? LinearBytes() : Bytes(); + const auto destinationBytes = retile ? Bytes() : LinearBytes(); + Require(source.size() >= sourceBytes && destination.size() >= destinationBytes, "multisampled surface transfer buffer is too small"); + const auto sourceAddress = reinterpret_cast(source.data()); + const auto destinationAddress = reinterpret_cast(destination.data()); + Require(sourceAddress <= std::numeric_limits::max() - sourceBytes && destinationAddress <= std::numeric_limits::max() - destinationBytes, "multisampled surface transfer range overflows"); + Require(sourceAddress + sourceBytes <= destinationAddress || destinationAddress + destinationBytes <= sourceAddress, "multisampled surface transfer buffers overlap"); + + std::uint64_t linear = 0; + for (std::uint32_t layer = 0; layer < layers; ++layer) { + for (std::uint32_t y = 0; y < height; ++y) { + for (std::uint32_t x = 0; x < width; ++x) { + for (std::uint32_t fragment = 0; fragment < fragments; ++fragment) { + const auto tiled = offset(x, y, layer, fragment); + std::memcpy(destination.data() + (retile ? tiled : linear), source.data() + (retile ? linear : tiled), elementBytes); + linear += elementBytes; + } + } + } + } +} + +void MultisampleTextureLayout::Detile(std::span source, std::span destination) const { + transfer(source, destination, false); +} + +void MultisampleTextureLayout::Tile(std::span source, std::span destination) const { + transfer(source, destination, true); +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleUpload.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleUpload.cpp new file mode 100644 index 0000000000..419ed7648e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleUpload.cpp @@ -0,0 +1,68 @@ +#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" +#include "prx/libSceAgcDriver/Graphics/shaders/MultisampleUpload_spv.h" +#include +#include +#include +#include + +namespace AgcDriver::Graphics { + +VkPipeline TextureDetiler::multisampleUploadPipeline(std::uint32_t elementBytes) { + const auto key = (1u << 19u) | elementBytes; + for (const auto& entry : pipelines) if (entry.first == key) return entry.second; + if (multisampleUploadModule == VK_NULL_HANDLE) { + VkShaderModuleCreateInfo info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + info.codeSize = sizeof(MULTISAMPLE_UPLOAD_SPV); + info.pCode = MULTISAMPLE_UPLOAD_SPV; + Check(context.Function("vkCreateShaderModule")(context.device, &info, nullptr, &multisampleUploadModule), "vkCreateShaderModule multisample upload"); + } + const VkSpecializationMapEntry entry{0u, 0u, sizeof(elementBytes)}; + const VkSpecializationInfo specialization{1u, &entry, sizeof(elementBytes), &elementBytes}; + VkComputePipelineCreateInfo info{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + info.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + info.stage.module = multisampleUploadModule; + info.stage.pName = "main"; + info.stage.pSpecializationInfo = &specialization; + info.layout = imagePipelineLayout; + pipelines.reserve(pipelines.size() + 1u); + VkPipeline result = VK_NULL_HANDLE; + { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1u, &info, nullptr, &result), "vkCreateComputePipelines multisample upload"); + } + pipelines.emplace_back(key, result); + return result; +} + +void TextureDetiler::DispatchMultisampleImage(VkCommandBuffer commands, std::uint32_t elementBytes, VkBuffer source, std::uint64_t offset, std::uint64_t bytes, VkImageView arrayView, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t fragments, std::uint32_t firstElement, std::uint32_t elements) { + Require(context.storageImageMultisample && context.storageImageWriteWithoutFormat, "device does not enable multisampled storage image writes without format"); + Require(commands != VK_NULL_HANDLE && source != VK_NULL_HANDLE && arrayView != VK_NULL_HANDLE, "multisample upload requires commands, buffer and array view"); + Require(std::has_single_bit(elementBytes) && elementBytes <= 16u, "multisample upload element size is unsupported"); + Require(width != 0u && width <= 16384u && height != 0u && height <= 16384u && layers != 0u && layers <= 8192u, "multisample upload extent is unsupported"); + Require(fragments == 2u || fragments == 4u || fragments == 8u, "multisample upload fragment count is unsupported"); + const auto total = static_cast(width) * height * layers * fragments; + Require(total <= std::numeric_limits::max() && elements != 0u && static_cast(firstElement) + elements <= total, "multisample upload element range is invalid"); + const auto required = (static_cast(elements) * elementBytes + 3u) & ~3ull; + const auto alignment = std::max(4u, context.limits.minStorageBufferOffsetAlignment); + Require(offset % alignment == 0u && bytes >= required && bytes <= context.limits.maxStorageBufferRange && bytes % 4u == 0u && offset <= std::numeric_limits::max() - bytes, "multisample upload buffer range is invalid"); + Require(context.limits.maxComputeWorkGroupInvocations >= 64u && context.limits.maxComputeWorkGroupSize[0] >= 64u && context.limits.maxComputeWorkGroupSize[1] >= 1u && context.limits.maxComputeWorkGroupSize[2] >= 1u && context.limits.maxComputeWorkGroupCount[2] >= 1u, "device does not support the multisample upload workgroup"); + const auto groups = (static_cast(elements) + 63u) / 64u; + const auto groupsX = std::min(groups, context.limits.maxComputeWorkGroupCount[0]); + Require(groupsX != 0u, "device has no multisample upload workgroups"); + const auto groupsY = (groups + groupsX - 1u) / groupsX; + Require(groupsY <= context.limits.maxComputeWorkGroupCount[1] && groupsX * groupsY * 64u <= std::numeric_limits::max(), "multisample upload dispatch exceeds the device limits"); + const auto target = multisampleUploadPipeline(elementBytes); + const auto set = allocateSet(true); + const VkDescriptorBufferInfo buffer{source, offset, bytes}; + const VkDescriptorImageInfo image{VK_NULL_HANDLE, arrayView, VK_IMAGE_LAYOUT_GENERAL}; + const std::array writes{{{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, 0u, 0u, 1u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nullptr, &buffer, nullptr}, {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, 1u, 0u, 1u, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, &image, nullptr, nullptr}}}; + context.Function("vkUpdateDescriptorSets")(context.device, 2u, writes.data(), 0u, nullptr); + context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target); + context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, imagePipelineLayout, 0u, 1u, &set, 0u, nullptr); + const std::array push{width, height, layers, fragments, firstElement, elements, static_cast(groupsX * 64u)}; + context.Function("vkCmdPushConstants")(commands, imagePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0u, sizeof(push), push.data()); + context.Function("vkCmdDispatch")(commands, static_cast(groupsX), static_cast(groupsY), 1u); +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 20ce2e4e8b..1dd1e6c450 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Execution/include/PerformanceOverlay.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp" #include "prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" @@ -9,6 +10,7 @@ #include "prx/libSceAgcDriver/Graphics/include/SampleOrder.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" #include "prx/libc/include/HostThreadLocal.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include #include #include @@ -18,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +28,34 @@ namespace AgcDriver::Graphics { +namespace { + +bool LogicOpFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_R8_UNORM: + case VK_FORMAT_R8G8_UNORM: + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: + case VK_FORMAT_R16_UNORM: + case VK_FORMAT_R16G16_UNORM: + case VK_FORMAT_R16G16B16A16_UNORM: + case VK_FORMAT_R8_UINT: + case VK_FORMAT_R8G8_UINT: + case VK_FORMAT_R8G8B8A8_UINT: + case VK_FORMAT_R16_UINT: + case VK_FORMAT_R16G16_UINT: + case VK_FORMAT_R16G16B16A16_UINT: + case VK_FORMAT_R32_UINT: + case VK_FORMAT_R32G32_UINT: + case VK_FORMAT_R32G32B32A32_UINT: return true; + default: return false; + } +} + +} + void LogPipelineStatistics_nid_no_patch(const Context& context, VkPipeline pipeline) { if (!context.pipelineExecutableInfo) return; const auto getProperties = context.Function("vkGetPipelineExecutablePropertiesKHR"); @@ -103,6 +134,18 @@ bool BarycentricPatches(const State& state, std::span shad return !state.rectList && state.stages.path == ShaderPath::Vertex && std::any_of(shaders.begin(), shaders.end(), [](const CompiledShader& shader) { return shader.stage == ShaderRecompiler::ShaderStage::TessellationControl; }); } +bool FragmentInterlock(std::span shaders) { + return std::any_of(shaders.begin(), shaders.end(), [](const CompiledShader& shader) { + if (shader.stage != ShaderRecompiler::ShaderStage::Fragment || shader.program == nullptr) return false; + const auto words = shader.program->spirv.Words(); + for (std::size_t cursor = 5; cursor + 1 < words.size() && (words[cursor] & 0xffffu) == spv::OpCapability && (words[cursor] >> 16u) == 2u; cursor += 2) { + const auto capability = words[cursor + 1]; + if (capability == spv::CapabilityFragmentShaderPixelInterlockEXT || capability == spv::CapabilityFragmentShaderSampleInterlockEXT || capability == spv::CapabilityFragmentShaderShadingRateInterlockEXT) return true; + } + return false; + }); +} + void ValidateProvokingVertex(const Context& context, const State& state, std::span shaders) { Require(state.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT || state.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT, "invalid provoking vertex mode"); if (state.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT) { @@ -112,6 +155,10 @@ void ValidateProvokingVertex(const Context& context, const State& state, std::sp } return; } + for (const auto& shader : shaders) { + if (shader.stage != ShaderRecompiler::ShaderStage::Fragment || shader.program == nullptr) continue; + Require(std::none_of(shader.program->fragmentParameters.begin(), shader.program->fragmentParameters.end(), [](const ShaderRecompiler::FragmentParameter& parameter) { return parameter.flatP20; }), "v_interp_mov p20 of a flat input with the last provoking vertex is not measured"); + } if (BarycentricPatches(state, shaders)) { for (const auto& shader : shaders) { if (shader.stage != ShaderRecompiler::ShaderStage::Fragment || shader.program == nullptr) continue; @@ -119,10 +166,74 @@ void ValidateProvokingVertex(const Context& context, const State& state, std::sp } return; } + struct Decorations { + std::unordered_map builtIns; + std::vector perVertexValues; + std::vector perPrimitiveValues; + }; + const auto readDecorations = [](const ShaderRecompiler::RecompileResult& program, Decorations& decorations) { + const auto words = program.spirv.Words(); + if (words.size() < 5) return false; + constexpr std::uint32_t NoMember = 0xffffffffu; + const auto key = [](std::uint32_t target, std::uint32_t member) { return (static_cast(target) << 32u) | member; }; + for (std::size_t cursor = 5; cursor < words.size();) { + const auto count = words[cursor] >> 16u; + if (count == 0 || count > words.size() - cursor) return false; + const auto op = static_cast(words[cursor] & 0xffffu); + const std::size_t decoration = op == spv::OpDecorate ? 2 : op == spv::OpMemberDecorate ? 3 : 0; + if (decoration != 0 && count > decoration) { + const auto target = key(words[cursor + 1], op == spv::OpMemberDecorate ? words[cursor + 2] : NoMember); + if (words[cursor + decoration] == spv::DecorationFlat || words[cursor + decoration] == spv::DecorationPerVertexKHR) decorations.perVertexValues.push_back(target); + if (words[cursor + decoration] == spv::DecorationPerPrimitiveEXT) decorations.perPrimitiveValues.push_back(target); + if (words[cursor + decoration] == spv::DecorationBuiltIn && count > decoration + 1) decorations.builtIns[target] = words[cursor + decoration + 1]; + } + cursor += count; + } + return true; + }; + const auto readsProvokingVertex = [&](const ShaderRecompiler::RecompileResult& program) { + if (std::any_of(program.fragmentParameters.begin(), program.fragmentParameters.end(), [](const ShaderRecompiler::FragmentParameter& parameter) { + return parameter.flat || parameter.perVertex || parameter.custom || parameter.flatP10; + })) return true; + Decorations decorations; + if (!readDecorations(program, decorations)) return true; + const auto& builtIns = decorations.builtIns; + return std::any_of(decorations.perVertexValues.begin(), decorations.perVertexValues.end(), [&](std::uint64_t target) { + const auto builtIn = builtIns.find(target); + if (builtIn == builtIns.end()) return true; + switch (builtIn->second) { + case spv::BuiltInFragCoord: + case spv::BuiltInPointCoord: + case spv::BuiltInFrontFacing: + case spv::BuiltInSampleId: + case spv::BuiltInSamplePosition: + case spv::BuiltInSampleMask: + case spv::BuiltInHelperInvocation: + case spv::BuiltInSubgroupSize: + case spv::BuiltInSubgroupLocalInvocationId: + return false; + default: + return true; + } + }); + }; + const auto writesProvokingVertexBuiltIn = [&](const ShaderRecompiler::RecompileResult& program) { + Decorations decorations; + if (!readDecorations(program, decorations)) return true; + return std::any_of(decorations.builtIns.begin(), decorations.builtIns.end(), [&](const auto& builtIn) { + return (builtIn.second == spv::BuiltInLayer || builtIn.second == spv::BuiltInViewportIndex) && + std::find(decorations.perPrimitiveValues.begin(), decorations.perPrimitiveValues.end(), builtIn.first) == decorations.perPrimitiveValues.end(); + }); + }; + const bool observed = std::any_of(shaders.begin(), shaders.end(), [&](const CompiledShader& shader) { + if (shader.stage == ShaderRecompiler::ShaderStage::Fragment) return shader.program == nullptr || readsProvokingVertex(*shader.program); + return shader.program != nullptr && writesProvokingVertexBuiltIn(*shader.program); + }); + if (!context.provokingVertexLast && !observed) return; Require(context.provokingVertexLast, "last provoking vertex requires VK_EXT_provoking_vertex with provokingVertexLast enabled"); const bool meshPrimitives = state.stages.path == ShaderPath::Geometry && state.stages.mesh; Require(!state.stages.tessellation && (meshPrimitives || (state.stages.path == ShaderPath::Vertex && !state.stages.mesh)), "last provoking vertex is unsupported for generated primitive pipelines"); - Require(!meshPrimitives || (state.topology != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP && state.topology != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN), "last provoking vertex is unsupported for triangle strips or fans assembled for a geometry program"); + Require(!meshPrimitives || state.topology != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, "last provoking vertex is unsupported for triangle fans assembled for a geometry program"); Require(state.rectList || state.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, "last provoking vertex is unsupported for this primitive topology"); for (const auto& shader : shaders) { if (shader.stage != ShaderRecompiler::ShaderStage::Fragment) continue; @@ -162,8 +273,10 @@ bool ShadersWriteMemory(std::span shaders) { if (binding.kind == ShaderRecompiler::DescriptorKind::StorageImage && any(binding.imageWritten, binding.count)) return true; break; case ShaderRecompiler::DescriptorRole::Gds: - case ShaderRecompiler::DescriptorRole::BdaPagetable: return true; + case ShaderRecompiler::DescriptorRole::BdaPagetable: + if (shader.program->bdaWrites) return true; + break; case ShaderRecompiler::DescriptorRole::GuestSamplers: case ShaderRecompiler::DescriptorRole::FaultBuffer: case ShaderRecompiler::DescriptorRole::FlattenedSrt: @@ -208,7 +321,7 @@ void appendKey(std::vector& key, const TValue& value) { bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State& state, const VertexInputLayout& input, const PipelineSetLayouts& resources, std::span shaders) { using Stage = ShaderRecompiler::ShaderStage; - if (!context.graphicsPipelineLibrary || context.pipelineExecutableInfo || state.depthBiasPerFace || state.rectList || BarycentricPatches(state, shaders) || SplitsAlternateAlpha(state)) return false; + if (!context.graphicsPipelineLibrary || context.pipelineExecutableInfo || (state.stages.tessellation && !context.tessellationPipelineCache) || state.depthBiasPerFace || state.rectList || BarycentricPatches(state, shaders) || SplitsAlternateAlpha(state) || std::ranges::any_of(shaders, [](const CompiledShader& shader) { return shader.program != nullptr && shader.program->functionLdsBuffer; }) || FragmentInterlock(shaders)) return false; keys = {}; for (const auto& shader : shaders) { const auto identity = shader.program->Identity(); @@ -259,6 +372,8 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State& appendKey(keys.fragmentOutput, state.perSampleShading); appendKey(keys.fragmentShader, state.mergedCoverage); appendKey(keys.fragmentOutput, state.mergedCoverage); + appendKey(keys.fragmentShader, state.truncatedCoverage); + appendKey(keys.fragmentOutput, state.truncatedCoverage); const auto sampleLocationsEnabled = ValidateSampleLocations(context, state); appendKey(keys.fragmentShader, sampleLocationsEnabled); appendKey(keys.fragmentOutput, sampleLocationsEnabled); @@ -276,11 +391,13 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State& } } for (const auto& blend : state.blends) appendKey(keys.fragmentOutput, blend); + appendKey(keys.fragmentOutput, state.colorRop); for (const auto value : state.blendConstants) appendKey(keys.fragmentOutput, value); appendKey(keys.renderPass, state.blends.size()); for (const auto& color : state.colors) { appendKey(keys.renderPass, color.format); appendKey(keys.renderPass, color.exportIndex); + appendKey(keys.renderPass, color.partialWriteMask != 0u); } appendKey(keys.renderPass, state.depth.has_value()); if (state.depth) appendKey(keys.renderPass, state.depth->format); @@ -323,6 +440,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput Pipeline::Pipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders, VkImageLayout attachmentLayout) : context(context), _modules(shaders.size()), attachments(state.colors.size() + (state.depth ? 1u : 0u)), colorAttachments(state.colors.size()), depthBounds(state.depth.has_value() && state.depthBoundsTest), depthBias(state.depth.has_value() && state.depthBias), dynamicRendering(context.graphicsPipelineLibrary) { PerformanceTimer timing("Vulkan.GraphicsPipeline"); + for (const auto& color : state.colors) boundAttachments.push_back(color.partialWriteMask == 0u); + if (state.depth) boundAttachments.push_back(true); sampleLocationsEnabled = ValidateSampleLocations(context, state); const auto host = HostSampleLocationsFor(context, state); sampleLocationGrid = host.grid; @@ -332,20 +451,25 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput this->context.bufferPool.reset(); Require(state.blends.size() == (state.colors.empty() ? 0u : state.colors.back().exportIndex + 1u) && state.colors.size() <= state.blends.size(), "blend states do not match decoded color state"); Require(state.blends.size() <= context.limits.maxColorAttachments, "color targets exceed device attachment limits"); - Require(state.hasColorTarget || (context.limits.framebufferNoAttachmentsSampleCounts & static_cast(state.samples)) != 0, "device does not support rendering without attachments at the draw's sample count"); + const bool boundColor = std::ranges::any_of(state.colors, [](const ColorTarget& color) { return color.partialWriteMask == 0u; }); + Require((state.hasColorTarget && (boundColor || state.colors.empty() || state.depth.has_value())) || (context.limits.framebufferNoAttachmentsSampleCounts & static_cast(state.samples)) != 0, "device does not support rendering without attachments at the draw's sample count"); Require(state.samples == 1 || !state.depth.has_value() || (context.limits.framebufferDepthSampleCounts & static_cast(state.samples)) != 0, "device does not support depth targets with the draw's sample count"); Require(!depthBounds || context.depthBounds, "device does not support the depth bounds test"); Require(!depthBias || state.depthBiasClamp == 0.0f || context.depthBiasClamp, "device does not support depth bias clamping"); Require(!state.dualSourceBlend || (context.dualSrcBlend && state.blends.size() <= context.limits.maxFragmentDualSrcAttachments), "device does not support dual-source blending into this many attachments"); Require(state.reversedBlend == 0 || (context.dualSrcBlend && state.blends.size() <= context.limits.maxFragmentDualSrcAttachments), "blending into a color target with a reversed component order through a second source color needs dual-source blending, which the device does not support"); + Require(LogicOpFromRop(state.colorRop) == VK_LOGIC_OP_COPY || context.logicOp, "device does not support color logic operations"); + Require(LogicOpFromRop(state.colorRop) == VK_LOGIC_OP_COPY || std::ranges::all_of(state.colors, [](const ColorTarget& color) { return LogicOpFormat(color.format); }), "color logic operations on a color target that is not a normalized or integer format are not implemented"); Require(!state.negativeOneToOne || context.depthClipControl, "negative-one-to-one depth clipping requires VK_EXT_depth_clip_control with depthClipControl enabled"); Require(!state.perSampleShading || context.sampleRateShading, "per-sample pixel shading requires the sampleRateShading device feature"); Require(!state.mergedCoverage || (context.mergedCoverageSampleCounts & state.samples) != 0, "an overrasterized draw (a multisampled rasterizer into one-sample color targets) requires VK_NV_framebuffer_mixed_samples with merged coverage reduction"); + Require(!(state.vertexPoints || (state.stages.mesh.has_value() && state.stages.mesh->outputPrimitiveSize == 1u)) || context.largePoints || (state.pointSize == 1.0f && ((state.paClVsOutCntl & (1u << 16u)) == 0 || (state.pointSizeMin == 1.0f && state.pointSizeMax == 1.0f))), "points of a size other than one pixel require the largePoints device feature"); + Require(!state.truncatedCoverage || (context.truncatedCoverageSampleCounts & state.samples) != 0, "one-sample color targets beside a multisampled depth target require VK_AMD_mixed_attachment_samples or VK_NV_framebuffer_mixed_samples with truncated coverage reduction"); Require(state.conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT || context.conservativeRasterization, "conservative rasterization requires VK_EXT_conservative_rasterization with at most 1/256 pixel of overestimation and degenerate triangles rasterized"); ValidateProvokingVertex(context, state, shaders); if (state.rectList) Require(context.tessellationShader && context.limits.maxTessellationPatchSize >= 4, "rect-list requires tessellation with four output control points"); const bool barycentricPatches = BarycentricPatches(state, shaders); - if (barycentricPatches) Require(context.tessellationShader && state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST && !state.primitiveRestart, "emulated barycentrics through tessellation require a triangle list without primitive restart"); + if (barycentricPatches) Require(context.tessellationShader && (state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP) && !state.primitiveRestart, "emulated barycentrics through tessellation require a triangle list or strip without primitive restart"); if (std::any_of(shaders.begin(), shaders.end(), [](const CompiledShader& shader) { return shader.stage == ShaderRecompiler::ShaderStage::Geometry; })) Require(context.geometryShader, "device does not support geometry shaders"); if (state.stages.tessellation) { Require(context.tessellationShader, "device does not support tessellation shaders"); @@ -388,12 +512,14 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput } // A descriptor set layout with the same bindings as this one is compatible with the pipeline // layout, so later draws bind their own ShaderResources' set under it. - const std::array setLayouts{resources.main, resources.fragment}; + const bool pixelLds = std::ranges::any_of(shaders, [](const CompiledShader& shader) { return shader.program != nullptr && shader.program->functionLdsBuffer; }); + const auto pixelLdsLayouts = pixelLds ? PixelFunctionLdsSetLayouts(context) : PixelFunctionLdsLayouts{}; + const std::array setLayouts{resources.main, pixelLds && !resources.split ? pixelLdsLayouts.empty : resources.fragment, pixelLdsLayouts.buffer}; const VkPushConstantRange push{pushStages, 0, PipelinePushSlotBytes}; const std::array slots{{{pushStagesOf(context), 0, PipelinePushSlotBytes}, {VK_SHADER_STAGE_FRAGMENT_BIT, PipelinePushSlotBytes, PipelinePushSlotBytes}}}; VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; - layoutInfo.flags = resources.split ? VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT : 0; - layoutInfo.setLayoutCount = resources.split ? 2u : 1u; + layoutInfo.flags = pixelLds || !resources.split ? 0 : VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT; + layoutInfo.setLayoutCount = pixelLds ? 3u : resources.split ? 2u : 1u; layoutInfo.pSetLayouts = setLayouts.data(); layoutInfo.pushConstantRangeCount = dynamicRendering ? static_cast(slots.size()) : pushStages != 0 ? 1 : 0; layoutInfo.pPushConstantRanges = dynamicRendering ? slots.data() : pushStages != 0 ? &push : nullptr; @@ -401,18 +527,20 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput std::vector colors; std::vector references(state.blends.size(), VkAttachmentReference{VK_ATTACHMENT_UNUSED, attachmentLayout}); for (std::uint32_t index = 0; index < state.colors.size(); ++index) { + if (state.colors[index].partialWriteMask != 0u) continue; VkAttachmentDescription color{}; color.format = state.colors[index].format; - color.samples = state.mergedCoverage ? VK_SAMPLE_COUNT_1_BIT : static_cast(state.samples); + color.samples = state.mergedCoverage || state.truncatedCoverage ? VK_SAMPLE_COUNT_1_BIT : static_cast(state.samples); color.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; color.storeOp = VK_ATTACHMENT_STORE_OP_STORE; color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; color.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; color.initialLayout = attachmentLayout; color.finalLayout = attachmentLayout; + references.at(state.colors[index].exportIndex) = {static_cast(colors.size()), attachmentLayout}; colors.push_back(color); - references.at(state.colors[index].exportIndex) = {index, attachmentLayout}; } + boundColorAttachments = static_cast(colors.size()); VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = static_cast(references.size()); @@ -432,7 +560,9 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput subpass.pDepthStencilAttachment = &depthReference; } std::vector renderingColors(state.blends.size(), VK_FORMAT_UNDEFINED); - for (const auto& color : state.colors) renderingColors.at(color.exportIndex) = color.format; + for (const auto& color : state.colors) { + if (color.partialWriteMask == 0u) renderingColors.at(color.exportIndex) = color.format; + } VkPipelineRenderingCreateInfoKHR rendering{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR}; rendering.colorAttachmentCount = static_cast(renderingColors.size()); rendering.pColorAttachmentFormats = renderingColors.empty() ? nullptr : renderingColors.data(); @@ -440,7 +570,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput VkAttachmentSampleCountInfoNV attachmentSamples{VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_NV}; attachmentSamples.colorAttachmentCount = static_cast(mergedColorSamples.size()); attachmentSamples.pColorAttachmentSamples = mergedColorSamples.empty() ? nullptr : mergedColorSamples.data(); - if (state.mergedCoverage) rendering.pNext = &attachmentSamples; + if (state.truncatedCoverage) attachmentSamples.depthStencilAttachmentSamples = static_cast(state.samples); + if (state.mergedCoverage || state.truncatedCoverage) rendering.pNext = &attachmentSamples; if (state.depth) { rendering.depthAttachmentFormat = HostDepthFormat(context, state.depth->format); if (HasStencil(state.depth->format)) rendering.stencilAttachmentFormat = rendering.depthAttachmentFormat; @@ -448,7 +579,9 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput if (dynamicRendering) { depthFormat = state.depth ? HostDepthFormat(context, state.depth->format) : VK_FORMAT_UNDEFINED; colorSlots.assign(state.blends.size(), ~0u); - for (std::uint32_t index = 0; index < state.colors.size(); ++index) colorSlots.at(state.colors[index].exportIndex) = index; + for (std::uint32_t index = 0; index < state.colors.size(); ++index) { + if (state.colors[index].partialWriteMask == 0u) colorSlots.at(state.colors[index].exportIndex) = index; + } this->attachmentLayout = attachmentLayout; } VkRenderPassCreateInfo passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; @@ -514,8 +647,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput sampleLocations.sampleLocationsInfo = {VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT, nullptr, samples.rasterizationSamples, grid, state.samples * grid.width * grid.height, locations.data()}; if (sampleLocationsEnabled) samples.pNext = &sampleLocations; VkPipelineCoverageReductionStateCreateInfoNV coverageReduction{VK_STRUCTURE_TYPE_PIPELINE_COVERAGE_REDUCTION_STATE_CREATE_INFO_NV}; - coverageReduction.coverageReductionMode = VK_COVERAGE_REDUCTION_MODE_MERGE_NV; - if (state.mergedCoverage) { + coverageReduction.coverageReductionMode = state.truncatedCoverage ? VK_COVERAGE_REDUCTION_MODE_TRUNCATE_NV : VK_COVERAGE_REDUCTION_MODE_MERGE_NV; + if (state.mergedCoverage || (state.truncatedCoverage && !context.mixedAttachmentSamples)) { coverageReduction.pNext = samples.pNext; samples.pNext = &coverageReduction; } @@ -550,6 +683,10 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput secondChannels[index].colorBlendOp = state.blends[index].alphaBlendOp; } } + if (const auto logicOp = LogicOpFromRop(state.colorRop); logicOp != VK_LOGIC_OP_COPY) { + blend.logicOpEnable = VK_TRUE; + blend.logicOp = logicOp; + } blend.attachmentCount = static_cast(firstChannels.size()); blend.pAttachments = firstChannels.empty() ? nullptr : firstChannels.data(); std::copy(state.blendConstants.begin(), state.blendConstants.end(), blend.blendConstants); @@ -586,20 +723,21 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput prebuilt = true; return; } + const VkPipelineCache pipelineCache = state.stages.tessellation && !context.tessellationPipelineCache ? VK_NULL_HANDLE : context.pipelineCache; if (libraries) pipeline = LinkPipelineFromLibraries(context, pipelineInfo, rendering, layoutInfo, keys, &optimized, false, _modules); else { auto pipelineCacheLock = context.LockPipelineCacheForUse(); - Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines"); + Check(context.Function("vkCreateGraphicsPipelines")(context.device, pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines"); } if (alternateAlpha) { blend.pAttachments = secondChannels.data(); auto pipelineCacheLock = context.LockPipelineCacheForUse(); - Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &secondChannel), "vkCreateGraphicsPipelines"); + Check(context.Function("vkCreateGraphicsPipelines")(context.device, pipelineCache, 1, &pipelineInfo, nullptr, &secondChannel), "vkCreateGraphicsPipelines"); } if (state.depthBiasPerFace) { raster.cullMode = VK_CULL_MODE_FRONT_BIT; auto pipelineCacheLock = context.LockPipelineCacheForUse(); - Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &backFaces), "vkCreateGraphicsPipelines"); + Check(context.Function("vkCreateGraphicsPipelines")(context.device, pipelineCache, 1, &pipelineInfo, nullptr, &backFaces), "vkCreateGraphicsPipelines"); } for (const auto module : _modules) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); _modules.clear(); @@ -694,7 +832,11 @@ std::shared_ptr Pipeline::AcquireFramebuffer(std::span(context, renderPass, targets, extent, layers); + std::vector boundViews; + for (std::size_t i = 0; i < targets.size(); ++i) { + if (boundAttachments[i]) boundViews.push_back(targets[i]); + } + auto framebuffer = std::make_shared(context, renderPass, renderPass == VK_NULL_HANDLE ? targets : std::span(boundViews), extent, layers); if (!resident) return framebuffer; // Beyond the bound the least recently used unreferenced entry goes. constexpr std::size_t bound = 8; @@ -752,7 +894,7 @@ void Pipeline::Begin(VkCommandBuffer commands, const Framebuffer& framebuffer, V const auto locations = SampleLocations(state); const auto grid = sampleLocationGrid; const VkSampleLocationsInfoEXT info{VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT, nullptr, static_cast(state.samples), grid, state.samples * grid.width * grid.height, locations.data()}; - const VkAttachmentSampleLocationsEXT initial{static_cast(state.colors.size()), info}; + const VkAttachmentSampleLocationsEXT initial{boundColorAttachments, info}; const VkSubpassSampleLocationsEXT post{0, info}; VkRenderPassSampleLocationsBeginInfoEXT sampleLocations{VK_STRUCTURE_TYPE_RENDER_PASS_SAMPLE_LOCATIONS_BEGIN_INFO_EXT}; sampleLocations.attachmentInitialSampleLocationsCount = state.depth ? 1 : 0; @@ -889,10 +1031,12 @@ void pipelineKey(std::vector& key, const Context& context, const Stat append(key, state.conservativeRasterization); append(key, state.blends.size()); for (const auto& blend : state.blends) append(key, blend); + append(key, state.colorRop); for (const auto value : state.blendConstants) append(key, value); append(key, state.samples); append(key, state.perSampleShading); append(key, state.mergedCoverage); + append(key, state.truncatedCoverage); append(key, state.customSampleLocations); if (state.customSampleLocations) { append(key, state.sampleLocationGrid); @@ -902,6 +1046,7 @@ void pipelineKey(std::vector& key, const Context& context, const Stat for (const auto& color : state.colors) { append(key, color.format); append(key, color.componentMapping); + append(key, color.partialWriteMask != 0u); } if (state.blends.size() != state.colors.size()) { for (const auto& color : state.colors) append(key, color.exportIndex); @@ -972,7 +1117,7 @@ struct PipelineStore { std::vector key; std::shared_ptr pipeline; }; - std::mutex mutex; + AgcDriver::Mutex mutex; // Least recently used first. std::list entries; std::unordered_map::iterator> index; @@ -982,6 +1127,14 @@ struct PipelineStore { std::uint64_t evicted = 0; PipelineStoreCounters totals; std::chrono::steady_clock::time_point lastReport = std::chrono::steady_clock::now(); + std::size_t capacity = 0; + bool fixedCapacity = false; + std::list evictedOrder; + std::unordered_map::iterator> evictedKeys; + std::size_t evictionsSinceCheck = 0; + std::size_t returnsSinceCheck = 0; + static constexpr std::size_t BaseEntries = 1024; + static constexpr std::size_t MaximumEntries = 4096; }; // Never destroyed: the pipelines belong to a device that may already be gone when statics die, and @@ -1010,9 +1163,41 @@ std::list::iterator abandon(PipelineStore& store, std::lis return store.entries.erase(it); } -std::size_t storeBound() { - static const auto bound = static_cast(std::max(1u, PipelineSwitch("APS5_PIPELINE_STORE_ENTRIES", 1024u))); - return bound; +std::size_t storeBound(PipelineStore& store) { + if (store.capacity == 0) { + const auto set = PipelineSwitch("APS5_PIPELINE_STORE_ENTRIES", 0u); + store.fixedCapacity = set != 0; + store.capacity = store.fixedCapacity ? static_cast(set) : PipelineStore::BaseEntries; + } + return store.capacity; +} + +void noteStoreEviction(PipelineStore& store, std::uint64_t hash) { + if (store.fixedCapacity) return; + if (const auto found = store.evictedKeys.find(hash); found != store.evictedKeys.end()) store.evictedOrder.erase(found->second); + store.evictedOrder.push_front(hash); + store.evictedKeys[hash] = store.evictedOrder.begin(); + while (store.evictedOrder.size() > store.capacity) { + store.evictedKeys.erase(store.evictedOrder.back()); + store.evictedOrder.pop_back(); + } + if (++store.evictionsSinceCheck < store.capacity / 32u) return; + const bool churning = store.returnsSinceCheck * 2u > store.evictionsSinceCheck; + store.evictionsSinceCheck = 0; + store.returnsSinceCheck = 0; + if (!churning || store.capacity >= PipelineStore::MaximumEntries) return; + const auto previous = store.capacity; + store.capacity = std::min(store.capacity * 2u, PipelineStore::MaximumEntries); + std::fprintf(stderr, "[pipecache] bound %zu -> %zu pipelines: pipelines it evicted keep coming back\n", previous, store.capacity); +} + +void noteStoreReturn(PipelineStore& store, std::uint64_t hash) { + if (store.fixedCapacity) return; + const auto found = store.evictedKeys.find(hash); + if (found == store.evictedKeys.end()) return; + store.evictedOrder.erase(found->second); + store.evictedKeys.erase(found); + ++store.returnsSinceCheck; } void reportPipelines(PipelineStore& store) { @@ -1084,12 +1269,12 @@ std::shared_ptr CachedPipeline(const Context& context, const State& st } ++store.misses; ++store.totals.misses; + noteStoreReturn(store, hash); } // Built outside the mutex: a draw that finds its pipeline resident is not held behind another // thread's build (the journal replay builds here too), and a device's driver compiles in // parallel when several threads build at once. auto pipeline = std::make_shared(context, state, vertexInput, resources, shaders, attachmentLayout); - const auto bound = storeBound(); const bool replaying = PipelineJournalReplaying(); { std::lock_guard lock(store.mutex); @@ -1107,7 +1292,10 @@ std::shared_ptr CachedPipeline(const Context& context, const State& st } // A replayed pipeline enters at the least recently used end and never displaces a draw's: // the title's own pipelines evict it first, and a full store takes none. - if (replaying && store.entries.size() >= bound) return pipeline; + if (replaying && store.entries.size() >= storeBound(store)) { + if (store.fixedCapacity || store.capacity >= PipelineStore::MaximumEntries) return pipeline; + store.capacity = std::min(store.capacity * 2u, PipelineStore::MaximumEntries); + } if (replaying) { store.entries.push_front({context.device, context.bufferPool, context.bufferPool != nullptr, hash, std::vector(key), pipeline}); store.index[hash] = store.entries.begin(); @@ -1115,12 +1303,14 @@ std::shared_ptr CachedPipeline(const Context& context, const State& st store.entries.push_back({context.device, context.bufferPool, context.bufferPool != nullptr, hash, std::vector(key), pipeline}); store.index[hash] = std::prev(store.entries.end()); } - while (store.entries.size() > bound) { + while (store.entries.size() > storeBound(store)) { // Only an entry no recorded draw still holds may go (Kept keeps its shared_ptr until the fence). const auto victim = std::find_if(store.entries.begin(), store.entries.end(), [](const PipelineStore::Entry& entry) { return entry.pipeline.use_count() == 1; }); if (victim == store.entries.end()) break; + const auto evictedHash = victim->hash; store.index.erase(victim->hash); store.entries.erase(victim); + noteStoreEviction(store, evictedHash); ++store.evicted; ++store.totals.evicted; } @@ -1139,10 +1329,16 @@ PipelineStoreCounters PipelineStoreTotals() { std::lock_guard lock(store.mutex); auto totals = store.totals; totals.cached = store.entries.size(); - totals.bound = storeBound(); + totals.bound = storeBound(store); return totals; } +bool PipelineStoreCanGrow() { + auto& store = Pipelines(); + std::lock_guard lock(store.mutex); + return storeBound(store) < PipelineStore::MaximumEntries && !store.fixedCapacity; +} + void ClearCachedPipelines(VkDevice device) { auto& store = Pipelines(); std::lock_guard lock(store.mutex); @@ -1154,7 +1350,15 @@ void ClearCachedPipelines(VkDevice device) { store.index.erase(it->hash); it = store.entries.erase(it); } + if (store.entries.empty() && !store.fixedCapacity) { + store.capacity = 0; + store.evictedOrder.clear(); + store.evictedKeys.clear(); + store.evictionsSinceCheck = 0; + store.returnsSinceCheck = 0; + } ClearPipelineLibraries(device); + ReleasePixelFunctionLds(device); } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp index 73fac4402d..28df53956c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp @@ -197,7 +197,7 @@ void PipelineCache::save(bool final, bool waitForPipelines) { settledBytes = liveBytes; settledFile = file; }; - const auto disk = fileUnchanged ? File{} : readFile(); + 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()); @@ -206,12 +206,16 @@ void PipelineCache::save(bool final, bool waitForPipelines) { if (disk.state == FileState::Invalid) std::fprintf(stderr, "[pipeline-cache] replacing %s: %s\n", path.string().c_str(), disk.invalidReason); std::size_t floor = fileUnchanged ? std::max(loadedBytes, settledFile->dataBytes) : 0; VkPipelineCache merged = VK_NULL_HANDLE; + bool mergedWithFile = false; struct DestroyMerged { const Context& context; - const VkPipelineCache& cache; - ~DestroyMerged() { - if (cache != VK_NULL_HANDLE) context.Function("vkDestroyPipelineCache")(context.device, cache, nullptr); + VkPipelineCache& cache; + void Reset() { + if (cache == VK_NULL_HANDLE) return; + context.Function("vkDestroyPipelineCache")(context.device, cache, nullptr); + cache = VK_NULL_HANDLE; } + ~DestroyMerged() { Reset(); } } destroyMerged{context, merged}; if (disk.state == FileState::Valid) { const auto data = disk.Payload(); @@ -221,11 +225,13 @@ void PipelineCache::save(bool final, bool waitForPipelines) { const auto result = context.Function("vkCreatePipelineCache")(context.device, &info, nullptr, &merged); if (result == VK_SUCCESS) { floor = std::max(loadedBytes, data.size()); + mergedWithFile = true; } else { merged = VK_NULL_HANDLE; std::fprintf(stderr, "[pipeline-cache] replacing %s: the driver refused it (Vulkan result %d)\n", path.string().c_str(), static_cast(result)); } } + std::vector().swap(disk.contents); if (merged != VK_NULL_HANDLE) { auto synchronizationLock = snapshot(); if (!synchronizationLock.owns_lock()) return; @@ -251,6 +257,7 @@ void PipelineCache::save(bool final, bool waitForPipelines) { if (result != VK_SUCCESS && result != VK_INCOMPLETE) return; file.resize(sizeof(FileHeader) + size); } + destroyMerged.Reset(); if (size < floor) { std::fprintf(stderr, "[pipeline-cache] keeping %s: the merged cache has %.1f KiB, less than the %.1f KiB on disk or loaded at start\n", path.string().c_str(), static_cast(size) / 1024.0, static_cast(floor) / 1024.0); settle(fileUnchanged ? *settledFile : disk.header); @@ -263,7 +270,7 @@ void PipelineCache::save(bool final, bool waitForPipelines) { return; } 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(), merged != VK_NULL_HANDLE ? " (merged with the file)" : "", final ? " (teardown)" : ""); + 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)" : ""); } void PipelineCache::SaveNow() { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp index d0669ed238..1745c0ed96 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp @@ -1,11 +1,13 @@ #include "prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp" #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" #include "ShaderCacheDirectory.hpp" #include "ShaderDiskCache.hpp" #include #include #include +#include #include #include #include @@ -23,6 +25,7 @@ #else #include #include +#include #include #endif @@ -35,7 +38,7 @@ static_assert(sizeof(VkStencilOpState) == 28, "VkStencilOpState changed: update static_assert(sizeof(VkVertexInputBindingDescription) == 12, "VkVertexInputBindingDescription changed: update the recipe codec"); static_assert(sizeof(VkVertexInputAttributeDescription) == 16, "VkVertexInputAttributeDescription changed: update the recipe codec"); #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) -static_assert(sizeof(State) == 784, "State changed: update CanonicalPipelineState and the recipe codec"); +static_assert(sizeof(State) == 840, "State changed: update CanonicalPipelineState and the recipe codec"); static_assert(sizeof(ShaderStages) == 112, "ShaderStages changed: update the recipe codec"); static_assert(sizeof(ShaderRecompiler::MeshConfiguration) == 40, "MeshConfiguration changed: update the recipe codec"); static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "TessellationConfiguration changed: update the recipe codec"); @@ -44,7 +47,7 @@ static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "Tessel constexpr std::uint32_t FileMagic = 0x4c4a5041u; // Bumped when the recipe codec, CanonicalPipelineState or the store key change in a way the // layout guards above do not catch (a field renumbered or reinterpreted at the same size). -constexpr std::uint32_t FileFormat = 6u; +constexpr std::uint32_t FileFormat = 10u; constexpr std::uint32_t ModuleRecord = 1u; constexpr std::uint32_t GraphicsRecord = 2u; constexpr std::uint32_t ComputeRecord = 3u; @@ -195,6 +198,7 @@ void encodeState(Writer& writer, const State& state) { writer.U32(static_cast(color.format)); writer.U32(color.exportIndex); writer.Raw(color.componentMapping); + writer.Raw(color.partialWriteMask); } writer.Raws(std::span(state.blends)); for (const auto value : state.blendConstants) writer.Raw(value); @@ -202,6 +206,7 @@ void encodeState(Writer& writer, const State& state) { writer.Bool(state.rectList); writer.Bool(state.dualSourceBlend); writer.U32(state.reversedBlend); + writer.U32(state.colorRop); writer.U32(static_cast(state.topology)); writer.Bool(state.primitiveRestart); writer.Bool(state.negativeOneToOne); @@ -217,8 +222,13 @@ void encodeState(Writer& writer, const State& state) { writer.Bool(state.sampleLocationGrid); writer.Bool(state.perSampleShading); writer.Bool(state.mergedCoverage); + writer.Bool(state.truncatedCoverage); for (const auto mask : state.quadSampleMask) writer.Raw(mask); writer.U32(state.depthRemap.mode); + writer.Bool(state.vertexPoints); + writer.Raw(state.pointSize); + writer.Raw(state.pointSizeMin); + writer.Raw(state.pointSizeMax); } void decodeState(Reader& reader, State& state) { @@ -255,6 +265,7 @@ void decodeState(Reader& reader, State& state) { color.format = static_cast(reader.U32()); color.exportIndex = reader.U32(); color.componentMapping = reader.Raw(); + color.partialWriteMask = reader.Raw(); state.colors.push_back(color); } reader.Raws(state.blends, 8u); @@ -263,6 +274,7 @@ void decodeState(Reader& reader, State& state) { state.rectList = reader.Bool(); state.dualSourceBlend = reader.Bool(); state.reversedBlend = reader.U32(); + state.colorRop = static_cast(reader.U32()); state.topology = static_cast(reader.U32()); state.primitiveRestart = reader.Bool(); state.negativeOneToOne = reader.Bool(); @@ -278,8 +290,13 @@ void decodeState(Reader& reader, State& state) { state.sampleLocationGrid = reader.Bool(); state.perSampleShading = reader.Bool(); state.mergedCoverage = reader.Bool(); + state.truncatedCoverage = reader.Bool(); for (auto& mask : state.quadSampleMask) mask = reader.Raw(); state.depthRemap.mode = reader.U32(); + state.vertexPoints = reader.Bool(); + state.pointSize = reader.Raw(); + state.pointSizeMin = reader.Raw(); + state.pointSizeMax = reader.Raw(); } void encodeRecipe(Writer& writer, const GraphicsPipelineRecipe& recipe) { @@ -327,6 +344,10 @@ std::uint64_t padded(std::uint64_t bytes) { return (bytes + RecordAlignment - 1u) / RecordAlignment * RecordAlignment; } +std::uint64_t recordBytes(std::uint64_t payloadBytes) { + return padded(sizeof(RecordHeader) + payloadBytes); +} + bool profiling() { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; return profile; @@ -347,6 +368,16 @@ std::uint64_t configured(const char* name, std::uint64_t fallback) { // second run of the same install) takes the same lock file, so its appends and this run's never // interleave and neither cuts a record the other is writing. Taken before the journal's mutex, // never while holding it. +bool replaceFile(const std::filesystem::path& temporary, const std::filesystem::path& target) { +#ifdef _WIN32 + return ::MoveFileExW(temporary.c_str(), target.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0; +#else + std::error_code error; + std::filesystem::rename(temporary, target, error); + return !error; +#endif +} + class FileLock { public: explicit FileLock(const std::filesystem::path& path) { @@ -397,13 +428,100 @@ private: #endif }; +class JournalFile { +public: + explicit JournalFile(const std::filesystem::path& path) { +#ifdef _WIN32 + handle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr); + LARGE_INTEGER length{}; + if (handle != INVALID_HANDLE_VALUE && GetFileSizeEx(handle, &length) && length.QuadPart >= 0) { + size = static_cast(length.QuadPart); + } else if (handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; + } +#else + descriptor = ::open(path.c_str(), O_RDONLY | O_CLOEXEC); + struct stat status{}; + if (descriptor >= 0 && ::fstat(descriptor, &status) == 0 && status.st_size >= 0) { + size = static_cast(status.st_size); + } else if (descriptor >= 0) { + ::close(descriptor); + descriptor = -1; + } +#endif + } + + ~JournalFile() { +#ifdef _WIN32 + if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle); +#else + if (descriptor >= 0) ::close(descriptor); +#endif + } + + JournalFile(const JournalFile&) = delete; + JournalFile& operator=(const JournalFile&) = delete; + +#ifdef _WIN32 + [[nodiscard]] bool Open() const { return handle != INVALID_HANDLE_VALUE; } +#else + [[nodiscard]] bool Open() const { return descriptor >= 0; } +#endif + [[nodiscard]] std::uint64_t Size() const { return size; } + + bool Read(std::uint64_t offset, std::span bytes) const { + for (std::size_t done = 0; done < bytes.size();) { + const auto position = offset + done; +#ifdef _WIN32 + struct ReadEvent { + HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr); + ~ReadEvent() { + if (event != nullptr) CloseHandle(event); + } + }; + auto& completion = HostThreadLocal(); + if (completion.event == nullptr) return false; + OVERLAPPED at{}; + at.Offset = static_cast(position); + at.OffsetHigh = static_cast(position >> 32u); + at.hEvent = completion.event; + DWORD count = 0; + if (!ReadFile(handle, bytes.data() + done, static_cast(std::min(bytes.size() - done, MAXDWORD)), nullptr, &at) && GetLastError() != ERROR_IO_PENDING) return false; + if (!GetOverlappedResult(handle, &at, &count, TRUE) || count == 0) return false; +#else + const auto count = ::pread(descriptor, bytes.data() + done, bytes.size() - done, static_cast(position)); + if (count < 0 && errno == EINTR) continue; + if (count <= 0) return false; +#endif + done += static_cast(count); + } + return true; + } + + template + bool ReadValue(std::uint64_t offset, TValue& value) const requires std::is_trivially_copyable_v { + return Read(offset, std::as_writable_bytes(std::span(&value, 1))); + } + +private: +#ifdef _WIN32 + HANDLE handle = INVALID_HANDLE_VALUE; +#else + int descriptor = -1; +#endif + std::uint64_t size = 0; +}; + // The end of the last whole record in [from, size) of the file: records another process appended // after this one loaded the file are kept, the torn tail of a record a crashed run left is cut. std::uint64_t validTail(const std::filesystem::path& path, std::uint64_t from, std::uint64_t size); struct LoadedModule { ShaderRecompiler::ShaderStage stage; - std::shared_ptr result; + std::uint64_t payloadOffset = 0; + std::uint64_t payloadBytes = 0; + std::uint64_t payloadHash = 0; }; struct LoadedEntry { @@ -414,6 +532,7 @@ struct LoadedEntry { }; struct LoadedJournal { + std::shared_ptr file; std::map modules; std::vector entries; std::set indexModules; @@ -424,6 +543,178 @@ struct LoadedJournal { bool headerValid = false; }; +struct RecordSpan { + std::uint64_t offset = 0; + std::uint64_t bytes = 0; + std::uint32_t kind = 0; + std::uint64_t hash = 0; + ShaderRecompiler::PipelineIdentity identity{}; + std::vector needs; + bool decodable = false; +}; + +class PayloadHash { +public: + explicit PayloadHash(std::uint64_t bytes) : hash(bytes * Multiplier) {} + + void Update(std::span bytes) { + while (!bytes.empty()) { + const auto take = std::min(bytes.size(), carry.size() - carryBytes); + std::memcpy(carry.data() + carryBytes, bytes.data(), take); + carryBytes += take; + bytes = bytes.subspan(take); + if (carryBytes == carry.size()) { + std::uint64_t chunk = 0; + std::memcpy(&chunk, carry.data(), sizeof(chunk)); + Mix(chunk); + carryBytes = 0; + } + } + } + + [[nodiscard]] std::uint64_t Finish() { + if (carryBytes != 0) { + std::uint64_t chunk = 0; + std::memcpy(&chunk, carry.data(), carryBytes); + Mix(chunk ^ 0x8000000000000000ull); + carryBytes = 0; + } + hash ^= hash >> 33u; + hash *= 0xff51afd7ed558ccdull; + hash ^= hash >> 33u; + hash *= 0xc4ceb9fe1a85ec53ull; + hash ^= hash >> 33u; + return hash; + } + +private: + static constexpr std::uint64_t Multiplier = 0x9e3779b97f4a7c15ull; + + void Mix(std::uint64_t chunk) { + chunk *= 0xff51afd7ed558ccdull; + chunk ^= chunk >> 32u; + hash = (hash ^ chunk) * Multiplier; + hash = (hash << 27u) | (hash >> 37u); + } + + std::uint64_t hash; + std::array carry{}; + std::size_t carryBytes = 0; +}; + +struct SpanScan { + bool headerValid = false; + std::vector spans; +}; + +bool readSpanPayload(std::ifstream& file, const RecordHeader& record, std::array& prefix, std::vector& graphics, std::uint64_t& hash) { + constexpr std::uint64_t MaxGraphicsBytes = 16u * 1024u * 1024u; + PayloadHash hasher(record.payloadBytes); + std::array chunk{}; + const bool capture = record.kind == GraphicsRecord && record.payloadBytes <= MaxGraphicsBytes; + if (capture) graphics.resize(static_cast(record.payloadBytes)); + std::size_t copied = 0; + std::uint64_t left = record.payloadBytes; + while (left != 0) { + const auto take = static_cast(std::min(left, chunk.size())); + file.read(reinterpret_cast(chunk.data()), static_cast(take)); + if (file.gcount() != static_cast(take)) return false; + const auto bytes = std::span(chunk.data(), take); + hasher.Update(bytes); + const auto prefixBytes = std::min(take, prefix.size() - copied); + std::memcpy(prefix.data() + copied, bytes.data(), prefixBytes); + if (prefixBytes < take) copied = prefix.size(); + else copied += take; + if (capture) std::memcpy(graphics.data() + (static_cast(record.payloadBytes) - static_cast(left)), bytes.data(), take); + left -= take; + } + hash = hasher.Finish(); + return true; +} + +bool scanSpans(const std::filesystem::path& path, std::uint64_t device, SpanScan& scan) { + std::error_code error; + const auto fileSize = std::filesystem::file_size(path, error); + if (error) return false; + std::ifstream file(path, std::ios::binary); + if (!file) return false; + FileHeader header{}; + file.read(reinterpret_cast(&header), sizeof(header)); + if (file.gcount() != sizeof(header)) return false; + if (header.magic != FileMagic || header.format != FileFormat || header.runtimeAbi != ShaderRecompiler::RuntimeAbi::Version || + header.sourceVersion != ShaderRecompiler::ShaderDiskCache::SourceVersion() || header.device != device) return false; + scan.headerValid = true; + auto cursor = padded(sizeof(FileHeader)); + while (cursor + sizeof(RecordHeader) <= fileSize) { + file.clear(); + file.seekg(static_cast(cursor), std::ios::beg); + RecordHeader record{}; + file.read(reinterpret_cast(&record), sizeof(record)); + if (file.gcount() != sizeof(record) || record.kind == 0 || record.kind > ComputeRecord) break; + const auto payloadOffset = cursor + sizeof(RecordHeader); + if (record.payloadBytes > fileSize - payloadOffset) break; + const auto end = padded(payloadOffset + record.payloadBytes); + if (end > fileSize) break; + std::array prefix{}; + std::vector graphics; + std::uint64_t hash = 0; + if (!readSpanPayload(file, record, prefix, graphics, hash) || hash != record.payloadHash) break; + RecordSpan span; + span.offset = cursor; + span.bytes = end - cursor; + span.kind = record.kind; + span.hash = record.payloadHash; + if (record.kind == ModuleRecord) { + if (record.payloadBytes < 20u) break; + std::memcpy(&span.identity.low, prefix.data(), sizeof(span.identity.low)); + std::memcpy(&span.identity.high, prefix.data() + 8, sizeof(span.identity.high)); + } else if (record.kind == ComputeRecord) { + if (record.payloadBytes >= 16u) { + std::memcpy(&span.identity.low, prefix.data(), sizeof(span.identity.low)); + std::memcpy(&span.identity.high, prefix.data() + 8, sizeof(span.identity.high)); + span.needs.push_back(span.identity); + span.decodable = true; + } + } else { + GraphicsPipelineRecipe recipe; + if (!graphics.empty() && DecodeGraphicsPipelineRecipe(graphics, recipe)) { + for (const auto& stage : recipe.stages) span.needs.push_back(stage.identity); + span.decodable = true; + } + } + scan.spans.push_back(std::move(span)); + cursor = end; + } + return true; +} + +bool copySpans(const std::filesystem::path& path, const SpanScan& scan, const std::set& kept, const std::filesystem::path& temporary) { + std::ifstream source(path, std::ios::binary); + if (!source) return false; + std::ofstream out(temporary, std::ios::binary | std::ios::trunc); + if (!out) return false; + std::array chunk{}; + source.read(reinterpret_cast(chunk.data()), static_cast(padded(sizeof(FileHeader)))); + if (source.gcount() != static_cast(padded(sizeof(FileHeader)))) return false; + out.write(reinterpret_cast(chunk.data()), static_cast(padded(sizeof(FileHeader)))); + for (std::size_t position = 0; position < scan.spans.size(); ++position) { + if (!kept.contains(position)) continue; + const auto& span = scan.spans[position]; + source.clear(); + source.seekg(static_cast(span.offset), std::ios::beg); + auto left = span.bytes; + while (left != 0) { + const auto take = static_cast(std::min(left, chunk.size())); + source.read(reinterpret_cast(chunk.data()), static_cast(take)); + if (source.gcount() != static_cast(take)) return false; + out.write(reinterpret_cast(chunk.data()), static_cast(take)); + left -= take; + } + } + out.flush(); + return !out.fail(); +} + class Journal { public: static Journal& Instance() { @@ -452,20 +743,19 @@ public: return false; } - // Reads the whole file (modules keep views into the buffer, which lives as long as they do), - // fills the dedupe index, and remembers how many bytes parsed so a torn tail is cut before the - // next append. + // Parses the file a record at a time and keeps it open for the replay, fills the dedupe index, + // and remembers how many bytes parsed so a torn tail is cut before the next append. LoadedJournal Load() { LoadedJournal loaded; if (!Enabled() || !bound.load(std::memory_order_acquire)) return loaded; const auto started = std::chrono::steady_clock::now(); - auto file = std::make_shared>(); bool read = false; { FileLock fileLock(lockPath); - read = ShaderRecompiler::ReadWholeFile(path, *file); + auto file = std::make_shared(path); + read = file->Open() && parse(*file, loaded, true); + if (read) loaded.file = std::move(file); } - if (read) parse(*file, file, loaded, true); const auto ms = std::chrono::duration(std::chrono::steady_clock::now() - started).count(); { std::lock_guard lock(mutex); @@ -531,12 +821,15 @@ public: totals.recordedModules = counters.recordedModules.load(std::memory_order_relaxed); totals.writtenBytes = counters.writtenBytes.load(std::memory_order_relaxed); totals.writeFailures = counters.writeFailures.load(std::memory_order_relaxed); + totals.evictedRecords = counters.evictedRecords.load(std::memory_order_relaxed); + totals.evictedBytes = counters.evictedBytes.load(std::memory_order_relaxed); return totals; } struct Counters { std::atomic loadedRecipes{0}, loadedModules{0}, loadedBytes{0}, droppedRecords{0}; std::atomic recordedGraphics{0}, recordedCompute{0}, recordedModules{0}, writtenBytes{0}, writeFailures{0}; + std::atomic evictedRecords{0}, evictedBytes{0}; }; private: @@ -582,27 +875,30 @@ private: // Parses the file: with `loaded` set every record is decoded, otherwise only the index (the // module identities and record hashes) is built. Stops at the first record that does not // parse; the bytes before it stay valid. - void parse(const std::vector& file, const std::shared_ptr>& owner, LoadedJournal& loaded, bool decode) { + bool parse(const JournalFile& file, LoadedJournal& loaded, bool decode, std::vector* spans = nullptr) { std::uint64_t valid = 0; bool header = false; std::uint64_t dropped = 0; - const auto bytes = std::span(file); - if (bytes.size() >= sizeof(FileHeader)) { + const auto size = file.Size(); + if (size >= sizeof(FileHeader)) { FileHeader fileHeader; - std::memcpy(&fileHeader, bytes.data(), sizeof(fileHeader)); + if (!file.ReadValue(0, fileHeader)) return false; header = fileHeader.magic == FileMagic && fileHeader.format == FileFormat && fileHeader.runtimeAbi == ShaderRecompiler::RuntimeAbi::Version && fileHeader.sourceVersion == ShaderRecompiler::ShaderDiskCache::SourceVersion() && fileHeader.device == device.load(std::memory_order_relaxed); } if (header) { valid = padded(sizeof(FileHeader)); std::uint64_t cursor = valid; - while (cursor + sizeof(RecordHeader) <= bytes.size()) { + std::vector bytes; + while (cursor + sizeof(RecordHeader) <= size) { RecordHeader record; - std::memcpy(&record, bytes.data() + cursor, sizeof(record)); + if (!file.ReadValue(cursor, record)) return false; const auto payloadOffset = cursor + sizeof(RecordHeader); - if (record.payloadBytes > bytes.size() - payloadOffset) break; + if (record.payloadBytes > size - payloadOffset) break; const auto end = padded(payloadOffset + record.payloadBytes); - if (end > bytes.size()) break; - const auto payload = bytes.subspan(static_cast(payloadOffset), static_cast(record.payloadBytes)); + if (end > size) break; + bytes.resize(static_cast(record.payloadBytes)); + if (!file.Read(payloadOffset, bytes)) return false; + const auto payload = std::span(bytes); if (ShaderRecompiler::HashBytes(payload) != record.payloadHash) break; if (record.kind == ModuleRecord) { if (payload.size() < 20u) break; @@ -613,15 +909,24 @@ private: std::memcpy(&stage, payload.data() + 16, sizeof(stage)); bool decoded = !decode; if (decode && !loaded.indexModules.contains(identity)) { - auto result = std::make_shared(); - decoded = ShaderRecompiler::ShaderDiskCache::DecodeResult(payload.subspan(20), *result, owner) && result->Identity() == identity; - if (decoded) loaded.modules.emplace(identity, LoadedModule{static_cast(stage), std::move(result)}); + ShaderRecompiler::RecompileResult result; + decoded = ShaderRecompiler::ShaderDiskCache::DecodeResult(payload.subspan(20), result) && result.Identity() == identity; + if (decoded) loaded.modules.emplace(identity, LoadedModule{static_cast(stage), payloadOffset, record.payloadBytes, record.payloadHash}); else ++dropped; } if (decoded) loaded.indexModules.insert(identity); + if (spans != nullptr) { + RecordSpan span; + span.offset = cursor; + span.bytes = end - cursor; + span.kind = record.kind; + span.hash = record.payloadHash; + span.identity = identity; + spans->push_back(std::move(span)); + } } else if (record.kind == GraphicsRecord || record.kind == ComputeRecord) { if (decode && !loaded.indexRecords.contains(record.payloadHash)) { - LoadedEntry entry; + LoadedEntry entry{}; entry.kind = record.kind; bool decoded = false; if (record.kind == GraphicsRecord) { @@ -638,6 +943,28 @@ private: else ++dropped; } loaded.indexRecords.insert(record.payloadHash); + if (spans != nullptr) { + RecordSpan span; + span.offset = cursor; + span.bytes = end - cursor; + span.kind = record.kind; + span.hash = record.payloadHash; + if (record.kind == ComputeRecord) { + if (payload.size() >= 16u) { + std::memcpy(&span.identity.low, payload.data(), sizeof(span.identity.low)); + std::memcpy(&span.identity.high, payload.data() + 8, sizeof(span.identity.high)); + span.needs.push_back(span.identity); + span.decodable = true; + } + } else { + GraphicsPipelineRecipe recipe; + if (DecodeGraphicsPipelineRecipe(payload, recipe)) { + for (const auto& stage : recipe.stages) span.needs.push_back(stage.identity); + span.decodable = true; + } + } + spans->push_back(std::move(span)); + } } else { break; } @@ -645,11 +972,12 @@ private: cursor = end; } } - if (valid < bytes.size()) ++dropped; + if (valid < size) ++dropped; loaded.validBytes = valid; - loaded.bytes = bytes.size(); + loaded.bytes = size; loaded.dropped = dropped; loaded.headerValid = header; + return true; } // The index the writer dedupes against, replaced from the file: what it holds and where it @@ -657,12 +985,12 @@ private: // written until it can be (the next record tries again) and the file is never started over for // a read failure. bool indexLocked() { - std::vector file; LoadedJournal index; std::error_code error; - const bool exists = std::filesystem::exists(path, error); - if (exists && !ShaderRecompiler::ReadWholeFile(path, file)) return false; - if (exists) parse(file, nullptr, index, false); + if (std::filesystem::exists(path, error)) { + const JournalFile file(path); + if (!file.Open() || !parse(file, index, false)) return false; + } std::lock_guard lock(mutex); indexed = true; validBytes = index.validBytes; @@ -723,43 +1051,119 @@ private: // tail of a crashed run (bytes past the last parsed record) was cut. void write(const Pending& pending) { if (!ensureIndexed()) throw std::runtime_error("the journal could not be read"); - for (unsigned attempt = 0; attempt < 2; ++attempt) { + for (unsigned attempt = 0; attempt < 3; ++attempt) { if (append(pending)) return; } throw std::runtime_error("the journal keeps changing under this run"); } + bool evictUnderLock(std::uint64_t reserve) { + SpanScan scan; + if (!scanSpans(path, device.load(std::memory_order_relaxed), scan) || !scan.headerValid) return false; + const auto& spans = scan.spans; + std::map moduleSpans; + for (std::size_t position = 0; position < spans.size(); ++position) { + if (spans[position].kind == ModuleRecord) moduleSpans[spans[position].identity] = position; + } + const auto headerBytes = padded(sizeof(FileHeader)); + const auto target = maxBytes - maxBytes / 4u; + auto budget = target > reserve ? target - reserve : headerBytes; + if (budget < headerBytes) budget = headerBytes; + std::set keptModules; + std::set kept; + std::uint64_t used = headerBytes; + for (std::size_t position = spans.size(); position-- > 0;) { + const auto& span = spans[position]; + if (span.kind == ModuleRecord) continue; + if (!span.decodable) continue; + std::uint64_t added = span.bytes; + std::vector modules; + bool complete = true; + for (const auto& identity : span.needs) { + if (keptModules.contains(identity)) continue; + const auto found = moduleSpans.find(identity); + if (found == moduleSpans.end()) { + complete = false; + break; + } + modules.push_back(found->second); + added += spans[found->second].bytes; + } + if (!complete) continue; + if (used + added > budget) break; + used += added; + for (const auto module : modules) keptModules.insert(spans[module].identity); + kept.insert(position); + } + for (const auto& identity : keptModules) kept.insert(moduleSpans[identity]); + const auto temporary = path.string() + ".evict"; + if (!copySpans(path, scan, kept, temporary)) { + std::error_code error; + std::filesystem::remove(temporary, error); + return false; + } + if (!replaceFile(temporary, path)) { + std::error_code error; + std::filesystem::remove(temporary, error); + return false; + } + std::uint64_t evicted = 0; + std::uint64_t evictedBytes = 0; + { + std::lock_guard lock(mutex); + for (std::size_t position = 0; position < spans.size(); ++position) { + if (kept.contains(position)) continue; + ++evicted; + evictedBytes += spans[position].bytes; + } + writtenModules.clear(); + writtenRecords.clear(); + for (const auto& identity : keptModules) writtenModules.insert(identity); + for (const auto position : kept) { + if (spans[position].kind != ModuleRecord) writtenRecords.insert(spans[position].hash); + } + validBytes = used; + headerValid = true; + } + counters.evictedRecords.fetch_add(evicted, std::memory_order_relaxed); + counters.evictedBytes.fetch_add(evictedBytes, std::memory_order_relaxed); + std::fprintf(stderr, "[pipeline-journal] %s reached its size cap; %llu records (%.1f MiB) were dropped and the file was rewritten to %.1f MiB\n", path.string().c_str(), static_cast(evicted), static_cast(evictedBytes) / (1024.0 * 1024.0), static_cast(used) / (1024.0 * 1024.0)); + return true; + } + + std::vector EncodeModule(const PendingModule& module) { + std::vector payload; + Writer writer(payload); + writer.U64(module.identity.low); + writer.U64(module.identity.high); + writer.U32(static_cast(module.stage)); + ShaderRecompiler::ShaderDiskCache::EncodeResult(*module.result, payload); + return payload; + } + // One attempt: false when the file was cut or replaced by someone else since it was indexed - // (it is indexed again, and the batch is rebuilt against that index). + // (it is indexed again, and the batch is rebuilt against that index), or when the file was + // rewritten to make room for the record (the batch is rebuilt against that file). bool append(const Pending& pending) { const auto recordHash = ShaderRecompiler::HashBytes(pending.payload); - std::vector batch; - std::vector newModules; + std::vector missing; { std::lock_guard lock(mutex); if (writtenRecords.contains(recordHash)) return true; for (const auto& module : pending.modules) { - if (!writtenModules.contains(module.identity) && std::ranges::find(newModules, module.identity) == newModules.end()) newModules.push_back(module.identity); + if (writtenModules.contains(module.identity)) continue; + if (std::ranges::find(missing, module.identity) != missing.end()) continue; + missing.push_back(module.identity); } } - const auto appendRecord = [&](std::uint32_t kind, std::span payload) { - const RecordHeader header{kind, 0u, payload.size(), ShaderRecompiler::HashBytes(payload)}; - Writer writer(batch); - writer.Raw(header); - batch.insert(batch.end(), payload.begin(), payload.end()); - batch.resize(static_cast(padded(batch.size()))); - }; - for (const auto& identity : newModules) { + std::vector> missingPayloads; + missingPayloads.reserve(missing.size()); + for (const auto& identity : missing) { const auto module = std::ranges::find_if(pending.modules, [&](const PendingModule& candidate) { return candidate.identity == identity; }); - std::vector payload; - Writer writer(payload); - writer.U64(identity.low); - writer.U64(identity.high); - writer.U32(static_cast(module->stage)); - ShaderRecompiler::ShaderDiskCache::EncodeResult(*module->result, payload); - appendRecord(ModuleRecord, payload); + missingPayloads.push_back(EncodeModule(*module)); } - appendRecord(pending.kind, pending.payload); + std::uint64_t batchSize = recordBytes(pending.payload.size()); + for (const auto& payload : missingPayloads) batchSize += recordBytes(payload.size()); std::error_code error; std::filesystem::create_directories(path.parent_path(), error); FileLock fileLock(lockPath); @@ -770,6 +1174,7 @@ private: return true; } bool fresh = false; + bool overCap = false; { std::lock_guard lock(mutex); fresh = !headerValid; @@ -783,13 +1188,41 @@ private: else validBytes = tail; } } - if (!fresh && validBytes + batch.size() > maxBytes) { + overCap = !fresh && validBytes + batchSize > maxBytes; + } + if (overCap) { + std::uint64_t needed = batchSize; + std::vector held; + for (const auto& module : pending.modules) { + if (std::ranges::find(missing, module.identity) != missing.end()) continue; + if (std::ranges::find(held, module.identity) != held.end()) continue; + held.push_back(module.identity); + needed += recordBytes(EncodeModule(module).size()); + } + if (needed + padded(sizeof(FileHeader)) > maxBytes) { + static std::atomic reported{false}; + if (!reported.exchange(true)) std::fprintf(stderr, "[pipeline-journal] %s cannot hold a record of %llu bytes under APS5_PIPELINE_JOURNAL_MAX_MIB; it is not recorded\n", path.string().c_str(), static_cast(needed)); + counters.writeFailures.fetch_add(1, std::memory_order_relaxed); + return true; + } + if (!evictUnderLock(needed)) { static std::atomic reported{false}; if (!reported.exchange(true)) std::fprintf(stderr, "[pipeline-journal] %s reached APS5_PIPELINE_JOURNAL_MAX_MIB; later pipelines are not recorded\n", path.string().c_str()); counters.writeFailures.fetch_add(1, std::memory_order_relaxed); return true; } + return false; } + std::vector batch; + const auto appendRecord = [&](std::uint32_t kind, std::span payload) { + const RecordHeader header{kind, 0u, payload.size(), ShaderRecompiler::HashBytes(payload)}; + Writer writer(batch); + writer.Raw(header); + batch.insert(batch.end(), payload.begin(), payload.end()); + batch.resize(static_cast(padded(batch.size()))); + }; + for (const auto& payload : missingPayloads) appendRecord(ModuleRecord, payload); + appendRecord(pending.kind, pending.payload); if (!fresh) { const auto size = std::filesystem::file_size(path, error); if (!error && size < validBytes) { @@ -813,9 +1246,9 @@ private: std::lock_guard lock(mutex); headerValid = true; validBytes = (fresh ? padded(sizeof(FileHeader)) : validBytes) + batch.size(); - for (const auto& identity : newModules) writtenModules.insert(identity); + for (const auto& identity : missing) writtenModules.insert(identity); writtenRecords.insert(recordHash); - counters.recordedModules.fetch_add(newModules.size(), std::memory_order_relaxed); + counters.recordedModules.fetch_add(missing.size(), std::memory_order_relaxed); if (pending.kind == GraphicsRecord) counters.recordedGraphics.fetch_add(1, std::memory_order_relaxed); else counters.recordedCompute.fetch_add(1, std::memory_order_relaxed); counters.writtenBytes.fetch_add(prefix.size() + batch.size(), std::memory_order_relaxed); @@ -869,6 +1302,7 @@ std::uint64_t validTail(const std::filesystem::path& path, std::uint64_t from, s struct ReplayCounters { std::atomic graphics{0}, compute{0}, present{0}, skipped{0}, rejected{0}, failed{0}; + std::atomic held{0}, heldPeak{0}; std::atomic threads{0}; std::atomic ms{0}; }; @@ -889,6 +1323,7 @@ public: Replay(Context context, PipelineJournalHooks hooks, unsigned threads) : context(std::move(context)), hooks(std::move(hooks)), active(threads) { started = std::chrono::steady_clock::now(); replayCounters().threads.store(threads, std::memory_order_relaxed); + replayCounters().heldPeak.store(0, std::memory_order_relaxed); workers.reserve(threads); for (unsigned index = 0; index < threads; ++index) workers.emplace_back([this] { run(); }); } @@ -911,11 +1346,14 @@ public: private: void run() { +#ifdef _WIN32 + if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL)) std::fprintf(stderr, "[pipeline-journal] replay thread priority not lowered (error %lu)\n", GetLastError()); +#endif std::call_once(loadOnce, [&] { try { auto journal = Journal::Instance().Load(); - const auto limit = configured("APS5_PIPELINE_JOURNAL_ENTRIES", 1024u); - if (journal.entries.size() > limit) journal.entries.resize(static_cast(limit)); + const auto limit = configured("APS5_PIPELINE_JOURNAL_ENTRIES", 4096u); + if (journal.entries.size() > limit) journal.entries.erase(journal.entries.begin(), journal.entries.end() - static_cast(limit)); loaded = std::move(journal); } catch (const std::exception& error) { std::fprintf(stderr, "[pipeline-journal] cannot load %s: %s\n", Journal::Instance().Path().string().c_str(), error.what()); @@ -942,6 +1380,27 @@ private: } } + std::shared_ptr readModule(const ShaderRecompiler::PipelineIdentity& identity, const LoadedModule& module) const { + auto bytes = std::make_shared>(static_cast(module.payloadBytes)); + if (loaded.file == nullptr || !loaded.file->Read(module.payloadOffset, *bytes) || ShaderRecompiler::HashBytes(*bytes) != module.payloadHash) return nullptr; + auto result = std::make_shared(); + if (!ShaderRecompiler::ShaderDiskCache::DecodeResult(std::span(*bytes).subspan(20), *result, bytes) || !(result->Identity() == identity)) return nullptr; + return result; + } + + struct Held { + explicit Held(std::uint64_t bytes) : bytes(bytes) { + auto& counters = replayCounters(); + const auto now = counters.held.fetch_add(bytes, std::memory_order_relaxed) + bytes; + auto peak = counters.heldPeak.load(std::memory_order_relaxed); + while (peak < now && !counters.heldPeak.compare_exchange_weak(peak, now, std::memory_order_relaxed)) {} + } + ~Held() { replayCounters().held.fetch_sub(bytes, std::memory_order_relaxed); } + Held(const Held&) = delete; + Held& operator=(const Held&) = delete; + std::uint64_t bytes; + }; + void replay(const LoadedEntry& entry) { struct Replaying { Replaying() { replayingHere = true; } @@ -954,27 +1413,47 @@ private: counters.rejected.fetch_add(1, std::memory_order_relaxed); return; } - (hooks.compute(*module->second.result, entry.pushConstants) ? counters.compute : counters.present).fetch_add(1, std::memory_order_relaxed); + const Held held(module->second.payloadBytes); + const auto result = readModule(module->first, module->second); + if (result == nullptr) { + counters.rejected.fetch_add(1, std::memory_order_relaxed); + return; + } + (hooks.compute(*result, entry.pushConstants) ? counters.compute : counters.present).fetch_add(1, std::memory_order_relaxed); return; } - std::vector shaders; + std::vector*> modules; + std::uint64_t bytes = 0; for (const auto& stage : entry.graphics.stages) { const auto module = loaded.modules.find(stage.identity); if (module == loaded.modules.end() || module->second.stage != stage.stage) { counters.rejected.fetch_add(1, std::memory_order_relaxed); return; } - shaders.push_back({stage.stage, module->second.result.get(), stage.pushConstantOffset}); + modules.push_back(&*module); + bytes += module->second.payloadBytes; } - if (shaders.empty() || context.descriptorCache == nullptr) { + if (modules.empty() || context.descriptorCache == nullptr) { counters.rejected.fetch_add(1, std::memory_order_relaxed); return; } const auto store = PipelineStoreTotals(); - if (store.cached >= store.bound) { + if (store.cached >= store.bound && !PipelineStoreCanGrow()) { counters.skipped.fetch_add(1, std::memory_order_relaxed); return; } + const Held held(bytes); + std::vector> results; + std::vector shaders; + for (std::size_t index = 0; index < modules.size(); ++index) { + auto result = readModule(modules[index]->first, modules[index]->second); + if (result == nullptr) { + counters.rejected.fetch_add(1, std::memory_order_relaxed); + return; + } + shaders.push_back({entry.graphics.stages[index].stage, result.get(), entry.graphics.stages[index].pushConstantOffset}); + results.push_back(std::move(result)); + } const auto layouts = DescribeSetLayouts(context, shaders); if (!std::ranges::equal(layouts.mainKey, entry.graphics.mainLayoutKey) || !std::ranges::equal(layouts.fragmentKey, entry.graphics.fragmentLayoutKey) || layouts.layouts.split != entry.graphics.splitSets) { counters.rejected.fetch_add(1, std::memory_order_relaxed); @@ -1028,6 +1507,7 @@ State CanonicalPipelineState(const State& state) { canonicalColor.format = color.format; canonicalColor.exportIndex = color.exportIndex; canonicalColor.componentMapping = color.componentMapping; + canonicalColor.partialWriteMask = color.partialWriteMask; canonical.colors.push_back(canonicalColor); } canonical.blends = state.blends; @@ -1036,6 +1516,7 @@ State CanonicalPipelineState(const State& state) { canonical.rectList = state.rectList; canonical.dualSourceBlend = state.dualSourceBlend; canonical.reversedBlend = state.reversedBlend; + canonical.colorRop = state.colorRop; canonical.topology = state.topology; canonical.primitiveRestart = state.primitiveRestart; canonical.negativeOneToOne = state.negativeOneToOne; @@ -1051,8 +1532,13 @@ State CanonicalPipelineState(const State& state) { canonical.sampleLocationGrid = state.sampleLocationGrid; canonical.perSampleShading = state.perSampleShading; canonical.mergedCoverage = state.mergedCoverage; + canonical.truncatedCoverage = state.truncatedCoverage; canonical.quadSampleMask = state.quadSampleMask; canonical.depthRemap.mode = state.depthRemap.mode; + canonical.vertexPoints = state.vertexPoints; + canonical.pointSize = state.pointSize; + canonical.pointSizeMin = state.pointSizeMin; + canonical.pointSizeMax = state.pointSizeMax; return canonical; } @@ -1115,6 +1601,7 @@ PipelineJournalCounters PipelineJournalTotals() { totals.replayFailed = replay.failed.load(std::memory_order_relaxed); totals.replayMs = replay.ms.load(std::memory_order_relaxed); totals.replayThreads = replay.threads.load(std::memory_order_relaxed); + totals.replayHeldPeakBytes = replay.heldPeak.load(std::memory_order_relaxed); return totals; } @@ -1134,10 +1621,10 @@ void FlushPipelineJournal() { void ReportPipelineJournal() { if (!profiling() || !Journal::Instance().Enabled()) return; const auto totals = PipelineJournalTotals(); - AgcDriver::ProfilePrint_nid_no_patch("[pipeline-journal] loaded %llu pipelines, %llu modules (%.1f MiB, %.0f ms); replay on %u threads: %llu graphics and %llu compute built, %llu present, %llu skipped (store full), %llu rejected, %llu failed in %.0f ms; recorded this run: %llu graphics, %llu compute, %llu modules (%.1f MiB), %llu write failures\n", + AgcDriver::ProfilePrint_nid_no_patch("[pipeline-journal] loaded %llu pipelines, %llu modules (%.1f MiB, %.0f ms); replay on %u threads: %llu graphics and %llu compute built, %llu present, %llu skipped (store full), %llu rejected, %llu failed in %.0f ms, holding at most %.1f MiB of modules; recorded this run: %llu graphics, %llu compute, %llu modules (%.1f MiB), %llu write failures, %llu records evicted (%.1f MiB)\n", static_cast(totals.loadedRecipes), static_cast(totals.loadedModules), static_cast(totals.loadedBytes) / (1024.0 * 1024.0), totals.loadMs, totals.replayThreads, - static_cast(totals.replayedGraphics), static_cast(totals.replayedCompute), static_cast(totals.replayPresent), static_cast(totals.replaySkipped), static_cast(totals.replayRejected), static_cast(totals.replayFailed), totals.replayMs, - static_cast(totals.recordedGraphics), static_cast(totals.recordedCompute), static_cast(totals.recordedModules), static_cast(totals.writtenBytes) / (1024.0 * 1024.0), static_cast(totals.writeFailures)); + static_cast(totals.replayedGraphics), static_cast(totals.replayedCompute), static_cast(totals.replayPresent), static_cast(totals.replaySkipped), static_cast(totals.replayRejected), static_cast(totals.replayFailed), totals.replayMs, static_cast(totals.replayHeldPeakBytes) / (1024.0 * 1024.0), + static_cast(totals.recordedGraphics), static_cast(totals.recordedCompute), static_cast(totals.recordedModules), static_cast(totals.writtenBytes) / (1024.0 * 1024.0), static_cast(totals.writeFailures), static_cast(totals.evictedRecords), static_cast(totals.evictedBytes) / (1024.0 * 1024.0)); } std::uint64_t PipelineJournalDeviceFingerprint(const VkPhysicalDeviceProperties& properties) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp index 7670cde694..0ccd993ac2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp @@ -185,7 +185,8 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip std::vector libraries; for (std::size_t part = 0; part < flags.size(); ++part) { if (part == 0 && mesh) continue; - const auto key = Combined(*partKeys[part], *contextKeys[part]); + auto key = Combined(*partKeys[part], *contextKeys[part]); + key.push_back(static_cast(optimize)); auto& cache = device.parts[part]; if (const auto found = cache.find(key); found != cache.end()) { libraries.push_back(found->second); @@ -195,7 +196,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip VkGraphicsPipelineLibraryCreateInfoEXT library{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, flags[part] == VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT ? &rendering : &shaderRendering}; library.flags = flags[part]; VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &library}; - create.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT; + create.flags = optimize ? VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT : VK_PIPELINE_CREATE_LIBRARY_BIT_KHR; create.pDynamicState = info.pDynamicState; create.layout = partLayouts[part]; switch (flags[part]) { @@ -255,10 +256,24 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip } if (partsOnly) return VK_NULL_HANDLE; PerformanceTimer timing("Vulkan.GraphicsPipelineLink"); + std::optional, VkSampleCountFlagBits>> sampleCounts; + for (auto* next = static_cast(rendering.pNext); next != nullptr; next = next->pNext) { + if (next->sType != VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_NV) continue; + const auto& counts = *reinterpret_cast(next); + sampleCounts.emplace(std::vector(counts.pColorAttachmentSamples, counts.pColorAttachmentSamples + counts.colorAttachmentCount), counts.depthStencilAttachmentSamples); + } + const auto attachmentSamples = [](const std::pair, VkSampleCountFlagBits>& counts) { + return VkAttachmentSampleCountInfoNV{VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_NV, nullptr, static_cast(counts.first.size()), counts.first.empty() ? nullptr : counts.first.data(), counts.second}; + }; + auto linkedSamples = sampleCounts ? attachmentSamples(*sampleCounts) : VkAttachmentSampleCountInfoNV{}; VkPipelineLibraryCreateInfoKHR linked{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR}; linked.libraryCount = static_cast(libraries.size()); linked.pLibraries = libraries.data(); VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &linked}; + if (sampleCounts) { + linkedSamples.pNext = create.pNext; + create.pNext = &linkedSamples; + } create.layout = info.layout; VkPipeline pipeline = VK_NULL_HANDLE; lock.unlock(); @@ -277,12 +292,17 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip const auto cache = context.pipelineCache; const auto cacheSynchronization = context.pipelineCacheSynchronization; const auto libraryLayout = fullLayout; - std::function relink = [slot, createPipelines, cache, cacheSynchronization, libraryLayout, libraries, &store, devicePointer = &device] { + std::function relink = [slot, createPipelines, cache, cacheSynchronization, libraryLayout, libraries, sampleCounts, attachmentSamples, &store, devicePointer = &device] { if (slot->released.load()) return; + auto linkedSamples = sampleCounts ? attachmentSamples(*sampleCounts) : VkAttachmentSampleCountInfoNV{}; VkPipelineLibraryCreateInfoKHR linked{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR}; linked.libraryCount = static_cast(libraries.size()); linked.pLibraries = libraries.data(); VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &linked}; + if (sampleCounts) { + linkedSamples.pNext = create.pNext; + create.pNext = &linkedSamples; + } create.flags = VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT; create.layout = libraryLayout; VkPipeline result = VK_NULL_HANDLE; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 9f03343a67..3abc0aec26 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -1,12 +1,14 @@ #include "prx/libSceAgcDriver/Execution/include/AsyncLog.hpp" #include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "prx/libc/include/HostThreadLocal.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libkernel/Time/include/SleepWake.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp" #include "prx/libSceAgcDriver/Graphics/shaders/MeshArguments_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/SampleCounter_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/SampleDumps_spv.h" @@ -31,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -128,7 +131,7 @@ struct ThreadSyncs { std::array waitedMs; }; std::vector threadSyncs; -std::mutex threadSyncsMutex; +AgcDriver::Mutex threadSyncsMutex; // This thread's fence and timeline waits in total (Recorder::ThreadWaitedMs): a caller times a span // of its own work and reads the difference to learn how much of it was waiting for the GPU. @@ -176,7 +179,7 @@ std::atomic unlockedWaiters[WaiterSlots]{}; std::atomic& WaitersOf(std::uint64_t id) { return unlockedWaiters[id % WaiterSlots]; } // Recorders alive, by id (own mutex, taken under the GpuMutex or with nothing held): a thread that // released the GpuMutex around a wait learns whether its recorder still exists before touching it. -std::mutex liveRecordersMutex; +AgcDriver::Mutex liveRecordersMutex; std::vector liveRecorders; std::atomic nextRecorderId{1}; std::atomic samplesPassed{0}; @@ -193,7 +196,7 @@ std::atomic dumpsQueued{0}, dumpsQueuedInPass{0}, dumpsAtOnce{0}, struct SamplePoolCache { VkDevice device; PFN_vkDestroyQueryPool destroy; - std::mutex mutex; + AgcDriver::Mutex mutex; std::vector free; bool closed = false; void put(VkQueryPool pool) { @@ -416,7 +419,7 @@ struct ReleaseQueue { // Set by a joiner under `mutex` until the thread was joined: the thread leaves once the queue // is empty, and hand-offs meanwhile destroy inline (nothing may be queued without a taker). bool stop = false; - std::mutex joinMutex; + AgcDriver::Mutex joinMutex; }; ReleaseQueue& ReleaseThreadQueue() { @@ -515,6 +518,7 @@ std::atomic pendingLabelSince{NoPendingLabel}; std::atomic writeGeneration{0}; std::atomic publishGeneration{0}; std::atomic completionLabels{0}; +std::atomic completionStores{0}; std::atomic writeBackCompletions{0}; std::atomic completionStoresSkipped{0}; std::atomic completionStoresRun{0}; @@ -541,7 +545,7 @@ bool ProcTable() { // instead of queueing behind a dispatch. `labelTableOwner` is the active recorder while it lives, // set and cleared under this mutex, so a lookup never touches a table being destroyed. Nothing // under this mutex takes the GPU mutex (no lock-order cycle). -std::mutex labelTableMutex; +AgcDriver::Mutex labelTableMutex; Recorder* labelTableOwner = nullptr; // The ranges of the calling worker's queued labels (NoteQueuedLabel, cleared by // ForgetQueuedLabels): the flush hook, on the same thread, has them recorded before an access that @@ -898,7 +902,7 @@ struct RecordedStoreTotals { }; struct RecordedStoreStats { - std::mutex mutex; + AgcDriver::Mutex mutex; std::map byKey; RecordedStoreTotals totals; std::chrono::steady_clock::time_point lastReport = std::chrono::steady_clock::now(); @@ -1040,11 +1044,9 @@ void FlushForAccess(std::uint64_t address, std::size_t bytes) { recorder->SyncThrough(address, bytes, true); } } - // A reader takes every shadowed unit of the range; a store only the units it covers partly - // (its own stamp makes the others stale). Inside a completion action a store has no publish - // recorded: the batch is not waited for, and the copy would land over the store later. const auto site = GuestMemory::CurrentReadSite(); - const auto scope = site != GuestMemory::ReadSite::Store ? PublishScope::Whole : completionDepth != 0 ? PublishScope::None : PublishScope::PartialUnits; + const auto scope = site != GuestMemory::ReadSite::Store ? PublishScope::Whole : PublishScope::PartialUnits; + const bool waited = completionDepth != 0; bool published = false; // The classification is made before the store (the store changes what it measures); profile only. StorageTexture::AccessClassification classification{}; @@ -1059,12 +1061,12 @@ void FlushForAccess(std::uint64_t address, std::size_t bytes) { const bool kept = !HookFlushCpuBlocks() && StorageTexture::AccessKeptByCpu(address, bytes, &images, &evicted); if (kept || (images == 0 && !HookFlushCpuBlocks())) { stored = StorageTexture::WaitForFlushing(address, bytes); - published = StorageTexture::PublishShadowsOnly(address, bytes, scope); + published = StorageTexture::PublishShadowsOnly(address, bytes, scope, "memory access", waited); if (kept && HookSyncProfiled()) CountRecordedStoreSkip(RecordedStoreKey(), evicted); } else { - stored = StorageTexture::FlushPending(address, bytes, nullptr, "memory access", scope, &published); + stored = StorageTexture::FlushPending(address, bytes, nullptr, "memory access", scope, &published, waited); } - if (stored || published) { + if (stored || published || (completionDepth == 0 && SnapshotOverlaps(address, bytes))) { // Stores into imported memory were only recorded; the CPU is about to read them. The wait // targets the batch holding them (the open one: SyncThrough submits it) and runs with the // mutex released when this acquisition is the outermost; a nested hook waits locked through @@ -1192,6 +1194,7 @@ Recorder::~Recorder() { publishGeneration.fetch_add(1, std::memory_order_release); pendingLabelSince.store(NoPendingLabel, std::memory_order_release); completionLabels.store(0, std::memory_order_release); + completionStores.store(0, std::memory_order_release); writeBackCompletions.store(0, std::memory_order_release); workSinceSubmit.store(0, std::memory_order_release); } @@ -1267,6 +1270,10 @@ std::uint64_t Recorder::PendingCompletionLabels() { return completionLabels.load(std::memory_order_acquire); } +std::uint64_t Recorder::PendingCompletionStores() { + return completionStores.load(std::memory_order_acquire); +} + std::uint64_t Recorder::PendingWriteBackCompletions() { return writeBackCompletions.load(std::memory_order_acquire); } @@ -1846,7 +1853,7 @@ std::atomic timingPresents{0}; std::atomic presentSerial{0}; std::atomic timingDropped{0}; struct TimingTotals { std::uint64_t count = 0; double ms = 0; std::uint64_t bytes = 0; }; -std::mutex timingMutex; +AgcDriver::Mutex timingMutex; std::map timingByKey; double timingProgramMs = 0, timingClassMs = 0, timingUnionMs = 0, timingBatchMs = 0; std::uint64_t timingBatches = 0; @@ -2129,6 +2136,22 @@ void Recorder::NoteSampledDraw(VkCommandBuffer commands) { if (open->sampleActive) open->samplesDrawn = true; } +void Recorder::NoteDrawTraceToken(std::uint64_t token) noexcept { + if (token == 0 || open == nullptr) return; + try { + auto inserted = open->drawTraceTokenSet.insert(token); + if (!inserted.second) return; + try { + open->drawTraceTokens.push_back(token); + } catch (...) { + open->drawTraceTokenSet.erase(inserted.first); + throw; + } + } catch (...) { + AgcDriver::CaptureTrace::Log("draw-token-note-failed token=%llu", static_cast(token)); + } +} + bool Recorder::gpuSampleCounter() { if (sampleCounterState != 0) return sampleCounterState > 0; sampleCounterState = -1; @@ -2282,6 +2305,20 @@ void Recorder::EndPassSamples() { if (open != nullptr && open->segmentActive) endSegment(*open); } +void Recorder::RecordUncounted(const std::function& record) { + const auto commands = Commands(); + if (open->segmentActive) endSegment(*open); + const bool counted = open->sampleActive; + if (counted) { + open->endedSamples.reserve(open->endedSamples.size() + 1); + endSamples(*open); + open->endedSamples.push_back(open->samples); + open->samples = VK_NULL_HANDLE; + } + record(commands); + if (counted) beginSamples(*open); +} + void Recorder::takeSegmentPool(Batch& batch) { auto cache = std::static_pointer_cast(segmentPools); if (cache == nullptr) { @@ -2572,9 +2609,15 @@ void Recorder::beginSamples(Batch& batch) { void Recorder::readSamples(Batch& batch) { if (sampleCounterState > 0) return; if (batch.samples == VK_NULL_HANDLE || !batch.submitted) return; + std::uint64_t total = 0; + for (const auto pool : batch.endedSamples) { + std::uint64_t samples = 0; + Check(context.Function("vkGetQueryPoolResults")(context.device, pool, 0, 1, sizeof(samples), &samples, sizeof(samples), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT), "vkGetQueryPoolResults occlusion"); + total += samples; + } std::uint64_t samples = 0; Check(context.Function("vkGetQueryPoolResults")(context.device, batch.samples, 0, 1, sizeof(samples), &samples, sizeof(samples), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT), "vkGetQueryPoolResults occlusion"); - samplesPassed.fetch_add(samples, std::memory_order_acq_rel); + samplesPassed.fetch_add(total + samples, std::memory_order_acq_rel); } void Recorder::readGpuTiming(Batch& batch) { @@ -2769,9 +2812,13 @@ void Recorder::NoteUnwrittenDrawSnapshot(std::uint64_t address, std::size_t byte unwrittenBytes += bytes; } -std::shared_ptr Recorder::ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use, std::uint32_t* derived, std::uint64_t generation) { +std::shared_ptr Recorder::ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use, std::uint32_t* derived, std::uint64_t generation, VkDeviceSize* offset) { auto found = use == SnapshotUse::Vertex ? drawSnapshots.lower_bound({address, use, bytes}) : drawSnapshots.find({address, use, bytes}); - if (found == drawSnapshots.end() || std::get<0>(found->first) != address || std::get<1>(found->first) != use) return {}; + if (found == drawSnapshots.end() || std::get<0>(found->first) != address || std::get<1>(found->first) != use) { + if (use != SnapshotUse::Storage || offset == nullptr) return {}; + return containingDrawSnapshot(address, bytes, offset); + } + if (offset != nullptr) *offset = 0; const bool sameRegistry = found->second.registryGeneration == GuestAllocations::GuestAllocationsGeneration_nid_postfix(); if (!sameRegistry || !GuestMemory::UnchangedSince(address, bytes, found->second.generation)) { if (!sameRegistry || generation == 0 || use != SnapshotUse::Storage || !refreshDrawSnapshot(found->second, address, bytes)) { @@ -2786,6 +2833,23 @@ std::shared_ptr Recorder::ReusableDrawSnapshot(std::uint64_t address, st return found->second.buffer; } +std::shared_ptr Recorder::containingDrawSnapshot(std::uint64_t address, std::size_t bytes, VkDeviceSize* offset) { + const VkDeviceSize alignment = std::max(context.limits.minStorageBufferOffsetAlignment, 4); + const auto registry = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + auto entry = drawSnapshots.upper_bound({address, SnapshotUse::Storage, std::numeric_limits::max()}); + for (std::size_t probe = 0; probe < ContainingSnapshotProbes && entry != drawSnapshots.begin(); ++probe) { + --entry; + const auto& [start, use, held] = entry->first; + if (use != SnapshotUse::Storage || start + held < address + bytes || (address - start) % alignment != 0) continue; + if (entry->second.registryGeneration != registry || !GuestMemory::UnchangedSince(address, bytes, entry->second.generation)) continue; + auto& recency = drawSnapshotPools[SnapshotPool(use)].recency; + recency.splice(recency.end(), recency, entry->second.recent); + *offset = address - start; + return entry->second.buffer; + } + return {}; +} + bool Recorder::refreshDrawSnapshot(DrawSnapshot& entry, std::uint64_t address, std::size_t bytes) { const auto held = entry.buffer->Bytes(); if (held.size() != bytes || bytes == 0) return false; @@ -3071,6 +3135,26 @@ std::uint64_t Recorder::PendingWriterSerial(std::uint64_t address, std::size_t b return 0; } +std::uint64_t Recorder::PendingWriterSerial(std::uint64_t address, std::size_t bytes, std::span exceptNotes) const { + if (exceptNotes.empty()) return PendingWriterSerial(address, bytes); + if (bytes == 0) return 0; + const auto end = address + bytes; + const auto writes = [&](const Batch& batch) { + for (std::size_t index = 0; index < batch.writes.size(); ++index) { + const auto& [begin, finish] = batch.writes[index]; + if (address >= finish || begin >= end) continue; + if (index < batch.writeNotes.size() && std::find(exceptNotes.begin(), exceptNotes.end(), batch.writeNotes[index]) != exceptNotes.end()) continue; + return true; + } + return readOverlap(batch, address, end, ReadKind::AddressBased).has_value(); + }; + if (open != nullptr && writes(*open)) return OpenSerial(); + for (auto it = inFlight.rbegin(); it != inFlight.rend(); ++it) { + if (writes(**it)) return (*it)->serial; + } + return 0; +} + bool Recorder::signaled(const Batch& batch) const { return batch.submitted && fenceStatus(batch.fence) == VK_SUCCESS; } @@ -3451,6 +3535,9 @@ void Recorder::AfterCompletions(std::uint64_t address, std::span action) { batch.completions.push_back(std::move(action)); ++batch.completionLabelCount; completionLabels.fetch_add(1, std::memory_order_acq_rel); + GuestMemory::BreakProofs(); if (&batch == open.get() && activeRecorder == this && pendingLabelSince.load(std::memory_order_relaxed) == NoPendingLabel) { pendingLabelSince.store(std::chrono::steady_clock::now().time_since_epoch().count(), std::memory_order_release); } @@ -3494,7 +3582,10 @@ void Recorder::NoteWrittenBack(std::uint64_t address, std::size_t bytes) { label.counted = true; ++batch.completionLabelCount; completionLabels.fetch_add(1, std::memory_order_acq_rel); + GuestMemory::BreakProofs(); completionLabelsCountedLate.fetch_add(1, std::memory_order_relaxed); + ++batch.completionStoreCount; + completionStores.fetch_add(1, std::memory_order_acq_rel); recorder->markBehindCompletion(batch, label.begin, label.end); } }; @@ -3564,7 +3655,13 @@ void Recorder::Submit() { submission.pCommandBuffers = &batch->commands; // The timeline reaches this batch's serial when it completes (see WaitSerial). const std::uint64_t serial = submissions + 1; - CaptureTrace::Log("submit batch=%llu reads=%zu writes=%zu", static_cast(serial), batch->reads.size(), batch->writes.size()); + std::string tokensStr = "["; + for (std::size_t i = 0; i < batch->drawTraceTokens.size(); ++i) { + if (i > 0) tokensStr += ","; + tokensStr += std::to_string(batch->drawTraceTokens[i]); + } + tokensStr += "]"; + CaptureTrace::Log("submit batch=%llu tokens=%s", static_cast(serial), tokensStr.c_str()); VkTimelineSemaphoreSubmitInfoKHR timelineInfo{VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR}; const VkSemaphore hold = std::exchange(submitHold, VK_NULL_HANDLE); const std::uint64_t holdValue = submitHoldValue; @@ -3583,6 +3680,7 @@ void Recorder::Submit() { submission.pSignalSemaphores = &timeline; } if (hold != VK_NULL_HANDLE || timeline != VK_NULL_HANDLE) submission.pNext = &timelineInfo; + DestroyDeferredAtIdle(context); const auto submitStart = std::chrono::steady_clock::now(); const auto result = function(queueSubmit, "vkQueueSubmit")(context.queue, 1, &submission, batch->fence); if (result == VK_ERROR_DEVICE_LOST) { @@ -3593,6 +3691,7 @@ void Recorder::Submit() { batch->submitted = true; batch->serial = ++submissions; batch->submittedAt = std::chrono::steady_clock::now(); + CaptureTrace::Log("submit-confirmed batch=%llu tokens=%s", static_cast(batch->serial), tokensStr.c_str()); if (DrawProfiled()) { const auto us = std::chrono::duration(batch->submittedAt - submitStart).count(); ++submitCount; @@ -3622,6 +3721,10 @@ std::uint64_t Recorder::SubmitAndEpoch() { return inFlight.empty() ? 0 : submissions; } +std::uint64_t Recorder::OpenBatchSerial() { + return open != nullptr ? submissions + 1 : 0; +} + namespace { // One thread's registration as a timeline waiter (see `unlockedWaiters`): constructed while the @@ -3675,6 +3778,20 @@ void Recorder::WaitSerial(std::uint64_t serial) { Check(result, "vkWaitSemaphoresKHR recorder"); } +bool Recorder::WaitSerialFor(std::uint64_t serial, std::uint64_t timeoutNanos) { + PerformanceTimer frameTiming("Recorder.WaitSerialFor"); + if (timeline == VK_NULL_HANDLE || serial == 0) return false; + const UnlockedWaiter waiter{id}; + VkSemaphoreWaitInfoKHR wait{VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR}; + wait.semaphoreCount = 1; + wait.pSemaphores = &timeline; + wait.pValues = &serial; + const auto result = context.Function("vkWaitSemaphoresKHR")(context.device, &wait, timeoutNanos); + if (result == VK_TIMEOUT) return false; + Check(result, "vkWaitSemaphoresKHR submission progress"); + return true; +} + void Recorder::FinishUpTo(std::uint64_t serial) { // A drain: the wait already happened (unlocked), the fences are signaled, so the source-0 wait // recorded here is the bookkeeping only (a caller that did not wait first, WaitForLeases, shows @@ -3999,6 +4116,7 @@ void Recorder::finish(std::unique_ptr batch, bool wait, int source, bool list.erase(std::remove(list.begin(), list.end(), batch), list.end()); // The batch's completion labels have landed, or never will (fence failure below). if (batch->completionLabelCount != 0) completionLabels.fetch_sub(batch->completionLabelCount, std::memory_order_acq_rel); + if (batch->completionStoreCount != 0) completionStores.fetch_sub(batch->completionStoreCount, std::memory_order_acq_rel); if (batch->writeBackCompletionCount != 0) writeBackCompletions.fetch_sub(batch->writeBackCompletionCount, std::memory_order_acq_rel); // This also runs while the fence-failure throw unwinds: an allocation failure in the // rebuild keeps the previous snapshot, which is a superset (a stale entry only causes a @@ -4143,6 +4261,7 @@ void Recorder::finish(std::unique_ptr batch, bool wait, int source, bool void Recorder::discard(std::unique_ptr batch) noexcept { removeLabels(*batch); if (batch->completionLabelCount != 0) completionLabels.fetch_sub(batch->completionLabelCount, std::memory_order_acq_rel); + if (batch->completionStoreCount != 0) completionStores.fetch_sub(batch->completionStoreCount, std::memory_order_acq_rel); if (batch->writeBackCompletionCount != 0) writeBackCompletions.fetch_sub(batch->writeBackCompletionCount, std::memory_order_acq_rel); if (activeRecorder == this) pendingLabelSince.store(NoPendingLabel, std::memory_order_release); release(*batch); @@ -4181,6 +4300,8 @@ void Recorder::release(Batch& batch) noexcept { if (batch.samples != VK_NULL_HANDLE && batch.samplePool == nullptr) context.Function("vkDestroyQueryPool")(context.device, batch.samples, nullptr); batch.samples = VK_NULL_HANDLE; batch.samplePool.reset(); + for (const auto pool : batch.endedSamples) context.Function("vkDestroyQueryPool")(context.device, pool, nullptr); + batch.endedSamples.clear(); batch.segments = VK_NULL_HANDLE; batch.segmentPool.reset(); // A completed (or never submitted) batch's objects are kept for reuse: the fence is signaled or diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ResidentMirror.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ResidentMirror.cpp new file mode 100644 index 0000000000..3913660efc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ResidentMirror.cpp @@ -0,0 +1,659 @@ +#include "prx/libSceAgcDriver/Graphics/include/ResidentMirror.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/GuestArena.hpp" +#ifndef _WIN32 +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver::Graphics { + +namespace { + +constexpr std::uint64_t BlockSize = ResidentBuffer::BlockBytes; +constexpr std::uint64_t SlabBytes = std::uint64_t{16} << 20u; +constexpr std::uint32_t SlabBlocks = static_cast(SlabBytes / BlockSize); +constexpr std::uint64_t ZeroBytes = SlabBytes; +constexpr VkAccessFlags TransferredAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT; + +[[noreturn]] void fail(const char* step, VkResult result) { + char text[160]; + std::snprintf(text, sizeof(text), "%s failed (%d)", step, static_cast(result)); + throw std::runtime_error(text); +} + +struct TransientBuffer { + TransientBuffer(const Context& context, VkBuffer buffer) : context(context), buffer(buffer) {} + ~TransientBuffer() { context.Function("vkDestroyBuffer")(context.device, buffer, nullptr); } + TransientBuffer(const TransientBuffer&) = delete; + TransientBuffer& operator=(const TransientBuffer&) = delete; + Context context; + VkBuffer buffer; +}; + +template +void recordTransfer(const Context& context, Work&& work) { + GuestMemory::AssertGpuLockHeld("heap mirror transfer"); + std::vector> kept; + if (auto* recorder = Recorder::Active(); recorder != nullptr) { + const auto commands = recorder->Commands(); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + work(commands, kept); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, TransferredAccess); + for (auto& object : kept) recorder->Keep(std::move(object)); + recorder->MarkCovered(TransferredAccess); + return; + } + CommandBatch batch(context); + RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + work(batch.Handle(), kept); + RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, TransferredAccess); + batch.SubmitAndWait(); +} + +} + +class ResidentMemory { +public: + struct Slab { + VkDeviceMemory memory = VK_NULL_HANDLE; + std::unique_ptr host; + std::vector free; + }; + + ResidentMemory(const Context& context, std::uint32_t type) : context(context), type(type) { + zero = allocate(ZeroBytes, "vkAllocateMemory of the heap mirrors' zero block"); + } + + ~ResidentMemory() { + const auto freeMemory = context.Function("vkFreeMemory"); + for (auto& slab : slabs) freeMemory(context.device, slab->memory, nullptr); + freeMemory(context.device, zero, nullptr); + } + + ResidentMemory(const ResidentMemory&) = delete; + ResidentMemory& operator=(const ResidentMemory&) = delete; + + VkDeviceMemory Zero() const { return zero; } + bool Zeroed() const { return zeroed.load(std::memory_order_acquire); } + + void FillZero(VkCommandBuffer commands, std::vector>& kept) { + if (Zeroed()) return; + VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + info.size = ZeroBytes; + info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + VkBuffer buffer = VK_NULL_HANDLE; + if (const auto result = context.Function("vkCreateBuffer")(context.device, &info, nullptr, &buffer); result != VK_SUCCESS) fail("vkCreateBuffer of the heap mirrors' zero fill", result); + auto transient = std::make_shared(context, buffer); + VkMemoryRequirements requirements{}; + context.Function("vkGetBufferMemoryRequirements")(context.device, buffer, &requirements); + if ((requirements.memoryTypeBits & (1u << type)) == 0 || requirements.size > ZeroBytes) throw std::runtime_error("the heap mirrors' zero block memory cannot back a transfer buffer to fill it"); + if (const auto result = context.Function("vkBindBufferMemory")(context.device, buffer, zero, 0); result != VK_SUCCESS) fail("vkBindBufferMemory of the heap mirrors' zero fill", result); + context.Function("vkCmdFillBuffer")(commands, buffer, 0, VK_WHOLE_SIZE, 0); + kept.push_back(std::move(transient)); + zeroed.store(true, std::memory_order_release); + } + + std::tuple Take() { + std::lock_guard lock(mutex); + for (auto& slab : slabs) { + if (slab->free.empty()) continue; + const auto index = slab->free.back(); + slab->free.pop_back(); + return {slab.get(), index}; + } + auto slab = std::make_unique(); + slab->memory = allocate(SlabBytes, "vkAllocateMemory of a heap mirror slab"); + slab->host = std::unique_ptr(new std::byte[SlabBytes]); + for (std::uint32_t index = SlabBlocks; index-- > 1;) slab->free.push_back(index); + slabs.push_back(std::move(slab)); + return {slabs.back().get(), 0u}; + } + + void Put(Slab* slab, std::uint32_t index) noexcept { + std::lock_guard lock(mutex); + slab->free.push_back(index); + if (slab->free.size() != SlabBlocks) return; + const auto found = std::find_if(slabs.begin(), slabs.end(), [&](const auto& held) { return held.get() == slab; }); + context.Function("vkFreeMemory")(context.device, slab->memory, nullptr); + slabs.erase(found); + } + +private: + VkDeviceMemory allocate(std::uint64_t bytes, const char* operation) { + const VkMemoryAllocateFlagsInfo flags{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, nullptr, VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, 0}; + VkMemoryAllocateInfo info{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, context.bufferDeviceAddress ? &flags : nullptr}; + info.allocationSize = bytes; + info.memoryTypeIndex = type; + if (context.bufferPool != nullptr) return context.bufferPool->AllocateMemory(info, operation); + VkDeviceMemory memory = VK_NULL_HANDLE; + if (const auto result = context.Function("vkAllocateMemory")(context.device, &info, nullptr, &memory); result != VK_SUCCESS) fail(operation, result); + return memory; + } + + Context context; + std::uint32_t type; + VkDeviceMemory zero = VK_NULL_HANDLE; + std::atomic zeroed{false}; + std::mutex mutex; + std::vector> slabs; +}; + +namespace { + +struct ResidentDevices { + std::mutex mutex; + std::map, std::weak_ptr> memories; + std::map addressSpace; + std::uint64_t limit = 0; + std::mutex pendingMutex; + std::vector> pending; +}; + +ResidentDevices& Devices() { + static ResidentDevices devices; + return devices; +} + +std::shared_ptr residentMemory(const Context& context, std::uint32_t type) { + auto& devices = Devices(); + std::lock_guard lock(devices.mutex); + auto& slot = devices.memories[{context.device, type}]; + if (auto held = slot.lock()) return held; + auto made = std::make_shared(context, type); + slot = made; + return made; +} + +struct Candidate { + std::uint64_t begin = 0; + std::uint64_t end = 0; + bool readable = true; +}; + +bool allZero(std::uint64_t begin, std::uint64_t end) { + const auto* at = reinterpret_cast(begin); + const auto* stop = reinterpret_cast(end); + while (at < stop && reinterpret_cast(at) % sizeof(std::uint64_t) != 0) { + if (*at++ != 0) return false; + } + while (stop - at >= static_cast(sizeof(std::uint64_t))) { + std::uint64_t word = 0; + std::memcpy(&word, at, sizeof(word)); + if (word != 0) return false; + at += sizeof(word); + } + while (at < stop) { + if (*at++ != 0) return false; + } + return true; +} + +#ifndef _WIN32 +struct Mapping { + std::uint64_t begin = 0; + std::uint64_t end = 0; + bool readable = false; + bool privateAnonymous = false; +}; + +std::vector mappings(std::uint64_t begin, std::uint64_t end) { + std::ifstream maps("/proc/self/maps"); + Require(static_cast(maps), "cannot read /proc/self/maps for a heap mirror's pages"); + std::vector found; + std::string line; + while (std::getline(maps, line)) { + std::istringstream fields(line); + std::string span, permissions, offset, device; + unsigned long long inode = 0; + fields >> span >> permissions >> offset >> device >> inode; + const auto dash = span.find('-'); + Require(dash != std::string::npos && permissions.size() == 4, "unexpected /proc/self/maps line: " + line); + const auto first = std::stoull(span.substr(0, dash), nullptr, 16); + const auto last = std::stoull(span.substr(dash + 1), nullptr, 16); + if (last <= begin || first >= end) continue; + found.push_back({std::max(first, begin), std::min(last, end), permissions[0] == 'r', inode == 0 && permissions[3] == 'p'}); + } + return found; +} + +void fileData(std::vector& maybe, const Mapping& mapping, int file, std::uint64_t offset) { + const auto end = offset + (mapping.end - mapping.begin); + auto at = static_cast(offset); + while (static_cast(at) < end) { + const auto data = lseek(file, at, SEEK_DATA); + if (data < 0) { + Require(errno == ENXIO, "SEEK_DATA on a heap mirror's shared backing failed"); + return; + } + if (static_cast(data) >= end) return; + const auto hole = lseek(file, data, SEEK_HOLE); + Require(hole >= 0, "SEEK_HOLE on a heap mirror's shared backing failed"); + const auto stop = std::min(static_cast(hole), end); + maybe.push_back({mapping.begin + (static_cast(data) - offset), mapping.begin + (stop - offset), mapping.readable}); + at = static_cast(stop); + } +} + +void populatedPages(std::vector& maybe, const Mapping& mapping) { + static const auto page = static_cast(sysconf(_SC_PAGESIZE)); + const int file = open("/proc/self/pagemap", O_RDONLY | O_CLOEXEC); + Require(file >= 0, "cannot open /proc/self/pagemap for a heap mirror's pages"); + constexpr std::size_t Entries = 4096; + std::array entries{}; + const auto first = mapping.begin / page; + const auto last = (mapping.end + page - 1) / page; + for (auto at = first; at < last;) { + const auto count = static_cast(std::min(Entries, last - at)); + const auto read = pread(file, entries.data(), count * sizeof(std::uint64_t), static_cast(at * sizeof(std::uint64_t))); + if (read != static_cast(count * sizeof(std::uint64_t))) { + close(file); + throw std::runtime_error("reading /proc/self/pagemap for a heap mirror's pages failed"); + } + for (std::size_t index = 0; index < count; ++index) { + constexpr std::uint64_t present = std::uint64_t{1} << 63u; + constexpr std::uint64_t swapped = std::uint64_t{1} << 62u; + if ((entries[index] & (present | swapped)) == 0) continue; + const auto address = (at + index) * page; + const auto from = std::max(address, mapping.begin); + const auto to = std::min(address + page, mapping.end); + if (!maybe.empty() && maybe.back().end == from && maybe.back().readable == mapping.readable) maybe.back().end = to; + else maybe.push_back({from, to, mapping.readable}); + } + at += count; + } + close(file); +} +#endif + +std::vector possiblyNonzero(std::uint64_t begin, std::uint64_t end) { + std::vector maybe; +#ifdef _WIN32 + maybe.push_back({begin, end, true}); +#else + for (const auto& mapping : mappings(begin, end)) { + int file = -1; + std::uint64_t offset = 0; + if (GuestArena::GuestArenaSharedBacking_nid_postfix(static_cast(mapping.begin), static_cast(mapping.end - mapping.begin), &file, &offset)) { + try { + fileData(maybe, mapping, file, offset); + } catch (...) { + close(file); + throw; + } + close(file); + } else if (mapping.privateAnonymous) { + populatedPages(maybe, mapping); + } else { + maybe.push_back({mapping.begin, mapping.end, mapping.readable}); + } + } +#endif + return maybe; +} + +} + +bool GuestBytesZero(std::uint64_t begin, std::uint64_t end) { + return allZero(begin, end); +} + +GuestRuns GuestNonzeroBlocks(const GuestRuns& candidates) { + GuestRuns runs; + for (const auto& [first, last] : candidates) { + if (first >= last) continue; + const auto maybe = possiblyNonzero(first, last); + std::size_t at = 0; + for (auto block = first / BlockSize * BlockSize; block < last; block += BlockSize) { + const auto from = std::max(first, block); + const auto to = std::min(last, block + BlockSize); + while (at < maybe.size() && maybe[at].end <= from) ++at; + bool nonzero = false; + for (auto next = at; !nonzero && next < maybe.size() && maybe[next].begin < to; ++next) { + const auto low = std::max(from, maybe[next].begin); + const auto high = std::min(to, maybe[next].end); + if (low < high) nonzero = !maybe[next].readable || !allZero(low, high); + } + if (!nonzero) continue; + if (!runs.empty() && runs.back().second == from) runs.back().second = to; + else runs.emplace_back(from, to); + } + } + return runs; +} + +void SetResidentAddressSpaceLimit(std::uint64_t bytes) { + auto& devices = Devices(); + std::lock_guard lock(devices.mutex); + devices.limit = bytes; +} + +std::shared_ptr ResidentBuffer::Create(const Context& context, std::uint64_t base, std::uint64_t bytes, const GuestRuns& backed) { + if (!context.sparseBinding || !context.sparseResidencyBuffer || !context.sparseResidencyAliased) return nullptr; + const auto first = base / BlockSize * BlockSize; + const auto size = (base - first + bytes + BlockSize - 1) / BlockSize * BlockSize; + { + auto& devices = Devices(); + std::lock_guard lock(devices.mutex); + const auto limit = devices.limit != 0 ? std::min(devices.limit, context.limits.sparseAddressSpaceSize) : context.limits.sparseAddressSpaceSize; + auto& used = devices.addressSpace[context.device]; + if (size > limit || used > limit - size) return nullptr; + used += size; + } + return std::shared_ptr(new ResidentBuffer(context, base, bytes, backed)); +} + +ResidentBuffer::ResidentBuffer(const Context& context, std::uint64_t base, std::uint64_t bytes, const GuestRuns& backed) : context(context) { + origin = base / BlockSize * BlockSize; + lead = base - origin; + size = (lead + bytes + BlockSize - 1) / BlockSize * BlockSize; + try { + VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + info.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT; + info.size = size; + info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + const std::array families{context.queueFamily, context.sparseFamily}; + if (context.sparseQueue != VK_NULL_HANDLE && context.sparseFamily != context.queueFamily) { + info.sharingMode = VK_SHARING_MODE_CONCURRENT; + info.queueFamilyIndexCount = static_cast(families.size()); + info.pQueueFamilyIndices = families.data(); + } + if (const auto result = context.Function("vkCreateBuffer")(context.device, &info, nullptr, &buffer); result != VK_SUCCESS) fail("vkCreateBuffer of a resident heap mirror", result); + VkMemoryRequirements requirements{}; + context.Function("vkGetBufferMemoryRequirements")(context.device, buffer, &requirements); + if (requirements.alignment == 0 || requirements.alignment > BlockSize || BlockSize % requirements.alignment != 0) { + char text[160]; + std::snprintf(text, sizeof(text), "the device's sparse buffer block of %llu bytes does not divide a heap mirror's 64 KiB block", static_cast(requirements.alignment)); + throw std::runtime_error(text); + } + type = context.DeviceMemoryType(requirements.memoryTypeBits, SlabBytes, "a resident heap mirror slab"); + memory = residentMemory(context, type); + slots.resize(static_cast(size / BlockSize)); + std::vector wanted(slots.size(), false); + for (const auto& [first, last] : backed) { + if (first >= last) continue; + for (auto index = blockIndex(first); index <= blockIndex(last - 1); ++index) wanted[index] = true; + } + std::vector binds; + for (std::size_t index = 0; index < slots.size();) { + if (wanted[index]) { + auto [slab, at] = memory->Take(); + slots[index] = {slab, at, slab->host.get() + at * BlockSize}; + std::memset(slots[index].host, 0, static_cast(BlockSize)); + binds.push_back({index * BlockSize, BlockSize, slab->memory, at * BlockSize, 0}); + ++bound; + ++index; + continue; + } + auto last = index; + while (last < slots.size() && !wanted[last] && (last - index + 1) * BlockSize <= ZeroBytes) ++last; + binds.push_back({index * BlockSize, (last - index) * BlockSize, memory->Zero(), 0, 0}); + index = last; + } + submit(binds); + const VkBufferDeviceAddressInfo addressInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, buffer}; + address = context.Function("vkGetBufferDeviceAddressKHR")(context.device, &addressInfo); + if (address == 0) fail("vkGetBufferDeviceAddressKHR of a resident heap mirror", VK_ERROR_UNKNOWN); + } catch (...) { + release(); + throw; + } +} + +ResidentBuffer::~ResidentBuffer() { + release(); +} + +void ResidentBuffer::release() noexcept { + if (buffer != VK_NULL_HANDLE) context.Function("vkDestroyBuffer")(context.device, buffer, nullptr); + buffer = VK_NULL_HANDLE; + for (auto& slot : slots) { + if (slot.host != nullptr) memory->Put(static_cast(slot.slab), slot.index); + slot = {}; + } + auto& devices = Devices(); + std::lock_guard lock(devices.mutex); + if (const auto found = devices.addressSpace.find(context.device); found != devices.addressSpace.end()) found->second -= std::min(found->second, size); + size = 0; +} + +std::size_t ResidentBuffer::blockIndex(std::uint64_t guest) const { + Require(guest >= origin && guest - origin < size, "guest address outside its resident heap mirror"); + return static_cast((guest - origin) / BlockSize); +} + +std::byte* ResidentBuffer::Block(std::uint64_t guest) const { + const auto& slot = slots[blockIndex(guest)]; + return slot.host == nullptr ? nullptr : slot.host + (guest - origin) % BlockSize; +} + +std::uint64_t ResidentBuffer::UnboundBytes(const GuestRuns& runs) const { + std::vector seen; + for (const auto& [first, last] : runs) { + if (first >= last) continue; + for (auto index = blockIndex(first); index <= blockIndex(last - 1); ++index) { + if (slots[index].host == nullptr) seen.push_back(index); + } + } + std::sort(seen.begin(), seen.end()); + return static_cast(std::unique(seen.begin(), seen.end()) - seen.begin()) * BlockSize; +} + +void ResidentBuffer::Bind(const GuestRuns& runs) { + std::vector binds; + std::vector made; + for (const auto& [first, last] : runs) { + if (first >= last) continue; + for (auto index = blockIndex(first); index <= blockIndex(last - 1); ++index) { + if (slots[index].host != nullptr) continue; + auto [slab, at] = memory->Take(); + slots[index] = {slab, at, slab->host.get() + at * BlockSize}; + std::memset(slots[index].host, 0, static_cast(BlockSize)); + binds.push_back({index * BlockSize, BlockSize, slab->memory, at * BlockSize, 0}); + made.push_back(index); + } + } + if (binds.empty()) return; + if (auto* recorder = Recorder::Active(); recorder != nullptr) recorder->Sync(); + try { + submit(binds); + } catch (...) { + for (const auto index : made) { + memory->Put(static_cast(slots[index].slab), slots[index].index); + slots[index] = {}; + } + throw; + } + bound += made.size(); + for (const auto index : made) MarkChanged(origin + index * BlockSize, origin + (index + 1) * BlockSize); +} + +void ResidentBuffer::MarkChanged(std::uint64_t begin, std::uint64_t end) { + if (begin >= end) return; + bool first = false; + { + std::lock_guard lock(changedMutex); + first = changed.empty(); + if (!changed.empty() && changed.back().second == begin) changed.back().second = end; + else changed.emplace_back(begin, end); + } + if (!first) return; + auto& devices = Devices(); + std::lock_guard lock(devices.pendingMutex); + devices.pending.push_back(weak_from_this()); +} + +void ResidentBuffer::Read(std::uint64_t guest, std::span out) const { + std::size_t done = 0; + while (done < out.size()) { + const auto at = guest + done; + const auto within = static_cast((at - origin) % BlockSize); + const auto length = std::min(out.size() - done, static_cast(BlockSize) - within); + if (const auto* source = Block(at); source != nullptr) std::memcpy(out.data() + done, source, length); + else std::memset(out.data() + done, 0, length); + done += length; + } +} + +void ResidentBuffer::Store(std::uint64_t guest, std::span bytes) { + std::size_t done = 0; + while (done < bytes.size()) { + const auto at = guest + done; + const auto within = static_cast((at - origin) % BlockSize); + const auto length = std::min(bytes.size() - done, static_cast(BlockSize) - within); + if (auto* target = Block(at); target != nullptr) std::memcpy(target, bytes.data() + done, length); + else Require(GuestBytesZero(reinterpret_cast(bytes.data() + done), reinterpret_cast(bytes.data() + done + length)), "the GPU wrote a heap mirror block that holds no memory"); + done += length; + } +} + +std::shared_ptr ResidentBuffer::RecordReadback(VkCommandBuffer commands, std::uint64_t guest, std::uint64_t bytes) const { + Require(guest >= origin && bytes <= size && guest - origin <= size - bytes, "heap mirror readback outside its buffer"); + auto staging = std::make_shared(context, static_cast(bytes), VK_BUFFER_USAGE_TRANSFER_DST_BIT); + CopyBuffer(context, commands, buffer, guest - origin, staging->Handle(), 0, bytes); + return staging; +} + +void ResidentBuffer::ReadDevice(std::uint64_t guest, std::span out) { + GuestMemory::AssertGpuLockHeld("heap mirror device read"); + Require(!Recorder::InCompletion(), "heap mirror device read inside a GPU completion: its write-back needs a recorded readback"); + auto staging = readbackBatch(guest, out.size()); + std::memcpy(out.data(), staging->Bytes().data(), out.size()); +} + +std::shared_ptr ResidentBuffer::readbackBatch(std::uint64_t guest, std::uint64_t bytes) { + CommandBatch batch(context); + RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + auto staging = RecordReadback(batch.Handle(), guest, bytes); + RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + batch.SubmitAndWait(); + return staging; +} + +void ResidentBuffer::flush() { + GuestRuns runs; + { + std::lock_guard lock(changedMutex); + runs.swap(changed); + } + if (runs.empty()) return; + try { + std::sort(runs.begin(), runs.end()); + std::vector regions; + std::uint64_t total = 0; + std::uint64_t cursor = 0; + for (const auto& [first, last] : runs) { + for (auto at = std::max(first, cursor); at < last;) { + const auto next = std::min(last, (at / BlockSize + 1) * BlockSize); + if (slots[blockIndex(at)].host != nullptr) { + if (!regions.empty() && regions.back().dstOffset + regions.back().size == at - origin) regions.back().size += next - at; + else regions.push_back({total, at - origin, next - at}); + total += next - at; + } + at = next; + } + cursor = std::max(cursor, last); + } + if (regions.empty()) return; + auto staging = std::make_shared(context, static_cast(total), VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + for (const auto& region : regions) { + for (VkDeviceSize done = 0; done < region.size;) { + const auto at = origin + region.dstOffset + done; + const auto length = std::min(region.size - done, BlockSize - (at - origin) % BlockSize); + std::memcpy(staging->Bytes().data() + region.srcOffset + done, Block(at), static_cast(length)); + done += length; + } + } + recordTransfer(context, [&](VkCommandBuffer commands, std::vector>& kept) { + context.Function("vkCmdCopyBuffer")(commands, staging->Handle(), buffer, static_cast(regions.size()), regions.data()); + kept.push_back(staging); + kept.push_back(shared_from_this()); + }); + } catch (...) { + { + std::lock_guard lock(changedMutex); + changed.insert(changed.begin(), runs.begin(), runs.end()); + } + auto& devices = Devices(); + std::lock_guard lock(devices.pendingMutex); + devices.pending.push_back(weak_from_this()); + throw; + } +} + +void ResidentBuffer::FlushChanged(const Context& context) { + auto& devices = Devices(); + std::vector> unzeroed; + { + std::lock_guard lock(devices.mutex); + for (const auto& [key, held] : devices.memories) { + if (std::get<0>(key) != context.device) continue; + if (auto memory = held.lock(); memory != nullptr && !memory->Zeroed()) unzeroed.push_back(std::move(memory)); + } + } + for (auto& memory : unzeroed) recordTransfer(context, [&](VkCommandBuffer commands, std::vector>& kept) { memory->FillZero(commands, kept); }); + std::vector> waiting; + { + std::lock_guard lock(devices.pendingMutex); + waiting.swap(devices.pending); + } + for (std::size_t index = 0; index < waiting.size(); ++index) { + const auto held = waiting[index].lock(); + if (held == nullptr) continue; + if (held->context.device != context.device) { + std::lock_guard lock(devices.pendingMutex); + devices.pending.push_back(waiting[index]); + continue; + } + try { + held->flush(); + } catch (...) { + std::lock_guard lock(devices.pendingMutex); + devices.pending.insert(devices.pending.end(), waiting.begin() + static_cast(index) + 1, waiting.end()); + throw; + } + } +} + +void ResidentBuffer::submit(const std::vector& binds) { + const bool ownQueue = context.sparseQueue != VK_NULL_HANDLE && context.sparseQueueMutex != nullptr; + const VkSparseBufferMemoryBindInfo bufferBinds{buffer, static_cast(binds.size()), binds.data()}; + VkBindSparseInfo bind{VK_STRUCTURE_TYPE_BIND_SPARSE_INFO}; + bind.bufferBindCount = 1; + bind.pBufferBinds = &bufferBinds; + const VkFenceCreateInfo fenceInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + VkFence fence = VK_NULL_HANDLE; + if (const auto result = context.Function("vkCreateFence")(context.device, &fenceInfo, nullptr, &fence); result != VK_SUCCESS) fail("vkCreateFence of a resident heap mirror bind", result); + VkResult submitted = VK_SUCCESS; + VkResult waited = VK_SUCCESS; + { + std::unique_lock queueLock; + std::unique_lock gpuLock; + if (ownQueue) queueLock = std::unique_lock(*context.sparseQueueMutex); + else gpuLock = std::unique_lock(GuestMemory::GpuMutex()); + submitted = context.Function("vkQueueBindSparse")(ownQueue ? context.sparseQueue : context.queue, 1, &bind, fence); + if (submitted == VK_SUCCESS) waited = context.Function("vkWaitForFences")(context.device, 1, &fence, VK_TRUE, UINT64_MAX); + } + context.Function("vkDestroyFence")(context.device, fence, nullptr); + if (submitted != VK_SUCCESS) fail("vkQueueBindSparse of a resident heap mirror", submitted); + if (waited != VK_SUCCESS) fail("vkWaitForFences of a resident heap mirror bind", waited); +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp index 4f30207def..ea37fed7ef 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -381,6 +382,7 @@ void CommandBatch::Submit() { submission.commandBufferCount = 1; submission.pCommandBuffers = &commands; timing.Mark("command_end"); + DestroyDeferredAtIdle(context); Check(context.Function("vkQueueSubmit")(context.queue, 1, &submission, fence), "vkQueueSubmit graphics"); pending = true; submitted = true; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/SampleOrder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/SampleOrder.cpp index dfcef91efc..647b38274c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/SampleOrder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/SampleOrder.cpp @@ -185,6 +185,7 @@ public: auto copy = copyFor(target.format, target.samples, target.extent, stencil); kept.push_back(copy); const VkImageAspectFlags aspects = VK_IMAGE_ASPECT_DEPTH_BIT | (stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u); + const VkImageAspectFlags layoutAspects = VK_IMAGE_ASPECT_DEPTH_BIT | (HasStencil(target.format) ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u); if (!copy->general) { VkImageMemoryBarrier toGeneral{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; toGeneral.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; @@ -193,7 +194,7 @@ public: toGeneral.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toGeneral.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toGeneral.image = copy->image; - toGeneral.subresourceRange = {aspects, 0, 1, 0, 1}; + toGeneral.subresourceRange = {layoutAspects, 0, 1, 0, 1}; context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral); copy->general = true; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp index 2915b76a5a..5bfb9d604e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp @@ -93,6 +93,10 @@ bool storesThroughAddressesCached(const ShaderRecompiler::SharedSpirv& spirv) { } +bool ShaderStoresThroughAddresses(const ShaderRecompiler::SharedSpirv& spirv) { + return storesThroughAddressesCached(spirv); +} + bool SpirvStoresThroughAddresses(std::span words) { if (words.size() < 5 || words[0] != spv::MagicNumber) return true; std::unordered_set pointerTypes; @@ -134,7 +138,7 @@ void ShaderResources::prepareAddressBindings(std::span sha if (tables != 0 && storesThroughAddressesCached(shader.program->spirv)) bdaReadOnly = false; usesFaultBuffer = usesFaultBuffer || faults != 0; const bool tableFaultsOnly = std::any_of(shader.program->bindings.begin(), shader.program->bindings.end(), [](const ShaderRecompiler::DescriptorBinding& binding) { return binding.role == ShaderRecompiler::DescriptorRole::ImageTableMap; }); - faultChecks = faultChecks || tables != 0 || (faults != 0 && (!tableFaultsOnly || shader.program->imageTableFaults != 0 || shader.program->poisonedSrtReads != 0 || loopGuarded)); + faultChecks = faultChecks || tables != 0 || (faults != 0 && (!tableFaultsOnly || shader.program->imageTableFaults != 0 || shader.program->poisonedSrtReads != 0 || shader.program->windowLaneGuards != 0 || loopGuarded)); } if (usesBda) { Require(context.bufferDeviceAddress, "buffer device address is not enabled"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp index 3859fc17d0..30ea580803 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp @@ -3,6 +3,7 @@ #include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" #include "prx/libSceAgcDriver/Graphics/include/State.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" #include #include #include @@ -244,6 +245,48 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span(bytes.size()); }; if (reads != nullptr) reads->reserve(reads->size() + 2u * shader.num_input_semantics); + std::array attribWords{}; + std::array, ShaderRecompiler::ShaderVertexStageInfo::MaxResources> sharpWords{}; + if (!staticAbi) { + std::uint32_t firstSemantic = ~0u, lastSemantic = 0; + for (std::uint32_t i = 0; i < shader.num_input_semantics; ++i) { + firstSemantic = std::min(firstSemantic, semantics[i].semantic); + lastSemantic = std::max(lastSemantic, semantics[i].semantic); + } + const auto readWords = [](std::uint64_t base, std::uint32_t stride, std::uint32_t first, std::uint32_t last, std::vector& words, auto&& wanted) { + const auto wordsPer = stride / 4u; + words.assign(static_cast(last - first + 1u) * wordsPer, 0u); + const auto begin = base + static_cast(first) * stride; + if (AgcDriver::GuestMemory::Accessible(reinterpret_cast(begin), words.size() * 4u)) { + AgcDriver::GuestMemory::Read(begin, std::as_writable_bytes(std::span(words)), 4); + return; + } + for (std::uint32_t entry = first; entry <= last; ++entry) { + if (!wanted(entry)) continue; + AgcDriver::GuestMemory::Read(base + static_cast(entry) * stride, std::as_writable_bytes(std::span(words).subspan(static_cast(entry - first) * wordsPer, wordsPer)), 4); + } + }; + struct TableStorage {}; + auto& table = HostThreadLocal, TableStorage>(); + readWords(attribTableAddr, 4u, firstSemantic, lastSemantic, table, [&](std::uint32_t semantic) { return std::any_of(semantics.begin(), semantics.begin() + shader.num_input_semantics, [&](const ShaderSemantic& candidate) { return candidate.semantic == semantic; }); }); + std::uint32_t firstIndex = ~0u, lastIndex = 0; + for (std::uint32_t i = 0; i < shader.num_input_semantics; ++i) { + attribWords[i] = table[semantics[i].semantic - firstSemantic]; + const auto index = attribWords[i] & 0x1fu; + if (index >= ShaderRecompiler::ShaderVertexStageInfo::MaxResources) throw std::runtime_error("AGC graphics: vertex buffer index exceeds the supported domain"); + firstIndex = std::min(firstIndex, index); + lastIndex = std::max(lastIndex, index); + } + if (shader.num_input_semantics != 0) { + struct SharpsStorage {}; + auto& sharps = HostThreadLocal, SharpsStorage>(); + readWords(bufferTableAddr, 16u, firstIndex, lastIndex, sharps, [&](std::uint32_t index) { return std::any_of(attribWords.begin(), attribWords.begin() + shader.num_input_semantics, [&](std::uint32_t word) { return (word & 0x1fu) == index; }); }); + for (std::uint32_t i = 0; i < shader.num_input_semantics; ++i) { + const auto index = attribWords[i] & 0x1fu; + std::copy_n(sharps.begin() + static_cast((index - firstIndex) * 4u), 4, sharpWords[i].begin()); + } + } + } for (std::uint32_t i = 0; i < shader.num_input_semantics; ++i) { const auto& semantic = semantics[i]; if (semantic.static_vb_index == 1 || semantic.static_attribute == 1) throw std::runtime_error("AGC graphics: statically bound vertex attributes are not implemented"); @@ -256,10 +299,9 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span attribWordBytes{}; const auto attribWordAddress = attribTableAddr + static_cast(semantic.semantic) * 4u; - AgcDriver::GuestMemory::Read(attribWordAddress, attribWordBytes, 4); + std::memcpy(attribWordBytes.data(), &attribWords[i], 4); record(attribWordAddress, attribWordBytes); - std::uint32_t attribWord; - std::memcpy(&attribWord, attribWordBytes.data(), 4); + const std::uint32_t attribWord = attribWords[i]; const auto index = attribWord & 0x1fu; const auto format = (attribWord >> 5u) & 0x1ffu; const auto offset = (attribWord >> 14u) & 0xfffu; @@ -267,7 +309,7 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span= ShaderRecompiler::ShaderVertexStageInfo::MaxResources) throw std::runtime_error("AGC graphics: vertex buffer index exceeds the supported domain"); std::array sharpBytes{}; const auto sharpAddress = bufferTableAddr + static_cast(index) * 16u; - AgcDriver::GuestMemory::Read(sharpAddress, sharpBytes, 4); + std::memcpy(sharpBytes.data(), sharpWords[i].data(), 16); record(sharpAddress, sharpBytes); std::array sharp{}; std::memcpy(sharp.data(), sharpBytes.data(), 16); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 277eea93c5..e67349e436 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1,12 +1,15 @@ #include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "ThreadOwned.hpp" #include "prx/libc/include/HostThreadLocal.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/PlaceholderImages.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" @@ -14,12 +17,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" @@ -127,10 +130,12 @@ struct CachedTexture { // eviction takes the back. APS5_NO_TEXTURE_HASH=1 finds entries by scanning the list (the index is // still kept), the linear lookup of before. struct TextureCache { - std::mutex mutex; + AgcDriver::Mutex mutex; std::list entries; std::unordered_map::iterator, TextureKeyHash> index; std::unordered_multimap::iterator> views; + std::vector, std::uint64_t>> spare; + std::uint64_t spareBytes = 0; std::uint64_t bytes = 0; std::size_t passed = 0; std::chrono::steady_clock::time_point passedSince{}; @@ -160,6 +165,8 @@ TextureCache& Textures() { thread_local unsigned sampledCacheHeld = 0; thread_local unsigned storageCacheHeld = 0; +thread_local unsigned sampledCacheReclaimable = 0; +thread_local unsigned storageCacheReclaimable = 0; struct CacheHold { unsigned& depth; @@ -169,17 +176,8 @@ struct CacheHold { CacheHold& operator=(const CacheHold&) = delete; }; -bool AcquireCache(std::mutex& mutex) { - const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(20); - do { - if (mutex.try_lock()) return true; - std::this_thread::yield(); - } while (std::chrono::steady_clock::now() < deadline); - return false; -} - struct UncachedImages { - std::mutex mutex; + AgcDriver::Mutex mutex; std::vector> images; std::atomic any{false}; }; @@ -265,6 +263,39 @@ void touchTexture(TextureCache& cache, std::list::iterator it) { it->present = Recorder::Presents(); } +constexpr std::uint64_t SpareSnapshotBytes = 256ull << 20; + +void parkSnapshot(TextureCache& cache, std::vector& bytes) { + const auto size = bytes.size(); + if (size == 0) return; + const auto present = Recorder::Presents(); + while (!cache.spare.empty() && (present - cache.spare.front().second > SpareSnapshotPresents || cache.spareBytes + size > SpareSnapshotBytes)) { + cache.spareBytes -= cache.spare.front().first.size(); + cache.spare.erase(cache.spare.begin()); + } + if (cache.spareBytes + size <= SpareSnapshotBytes) { + cache.spareBytes += size; + cache.spare.emplace_back(std::move(bytes), present); + } + bytes = std::vector{}; +} + +std::vector spareSnapshot(TextureCache& cache, std::size_t bytes) { + const auto present = Recorder::Presents(); + while (!cache.spare.empty() && present - cache.spare.front().second > SpareSnapshotPresents) { + cache.spareBytes -= cache.spare.front().first.size(); + cache.spare.erase(cache.spare.begin()); + } + for (auto spare = cache.spare.begin(); spare != cache.spare.end(); ++spare) { + if (spare->first.size() != bytes) continue; + cache.spareBytes -= bytes; + std::vector taken = std::move(spare->first); + cache.spare.erase(spare); + return taken; + } + return {}; +} + bool admitTexture(TextureCache& cache, const TextureKey& key, std::uint64_t incoming, std::uint64_t budget) { if (cache.bytes + incoming <= budget) return true; if (const auto now = std::chrono::steady_clock::now(); now - cache.passedSince >= std::chrono::seconds(1)) { @@ -394,6 +425,8 @@ TextureCounters& TextureCounts() { return counters; } +thread_local std::uint64_t sampledLookupKeyScans = 0; + void reportTextureCounters() { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (!profile) return; @@ -461,7 +494,12 @@ private: bool outermost; }; -std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes = 0, bool written = false); +void refreshForView(StorageTexture& source, const GuestTextureResource& resource) { + if (resource.dimension == TextureDimension::k2D && source.ImageLayers() > 1) source.RefreshLayers(resource.baseArray, 1); + else source.Refresh(); +} + +std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes = 0, bool written = false, std::optional> layers = std::nullopt); std::uint64_t heldBytes(const StorageTexture& image) { return image.BudgetBytes(); @@ -508,7 +546,7 @@ std::uint64_t DepthCopyMinBytes() { // they were when it failed (the allocation generation): pages committed later, or another surface // at the address, get a fresh attempt. struct StorageFailures { - std::mutex mutex; + AgcDriver::Mutex mutex; std::map, std::uint64_t> generations; }; @@ -565,26 +603,32 @@ std::uint64_t forcedCacheBudget() { return forced; } -std::uint64_t storageBudget(const Context& context) { +std::uint64_t storageBudget(const Context& context, std::uint64_t required = 0) { const auto forced = forcedCacheBudget(); if (forced != 0) return forced; if (context.memoryProperties2 == nullptr || context.physical == VK_NULL_HANDLE) return TextureCacheBudget(context.memory); static struct { - std::mutex mutex; + AgcDriver::Mutex mutex; VkPhysicalDevice device{VK_NULL_HANDLE}; std::chrono::steady_clock::time_point read{}; std::uint64_t budget{0}; + std::uint64_t ceiling{0}; } cached; const auto now = std::chrono::steady_clock::now(); std::lock_guard lock(cached.mutex); - if (cached.device == context.physical && now - cached.read < std::chrono::seconds(1)) return cached.budget; - VkPhysicalDeviceMemoryBudgetPropertiesEXT reported{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; - VkPhysicalDeviceMemoryProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2}; - properties.pNext = &reported; - context.memoryProperties2(context.physical, &properties); - cached.device = context.physical; - cached.read = now; - cached.budget = StorageTextureBudget(context.memory, &reported, SampledTextureMemory(), storageCacheBytes()); + const auto read = [&] { + VkPhysicalDeviceMemoryBudgetPropertiesEXT reported{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; + VkPhysicalDeviceMemoryProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2}; + properties.pNext = &reported; + context.memoryProperties2(context.physical, &properties); + cached.device = context.physical; + cached.read = now; + cached.budget = StorageTextureBudget(context.memory, &reported, SampledTextureMemory(), storageCacheBytes()); + cached.ceiling = StorageTextureCeiling(context.memory, &reported); + }; + if (cached.device != context.physical || now - cached.read >= std::chrono::seconds(1)) read(); + const auto needed = std::min(required, cached.ceiling); + if (needed > cached.budget && context.bufferPool != nullptr && context.bufferPool->ReclaimIdleDevice(needed - cached.budget, now) != 0) read(); return cached.budget; } @@ -599,7 +643,7 @@ std::uint64_t sampledBudget(const Context& context, TextureCache& cache) { context.memoryProperties2(context.physical, &properties); const auto sampled = SampledTextureMemory(); const auto storage = storageCacheBytes(); - const auto budget = SampledTextureBudget(context.memory, &reported, sampled + storage); + const auto budget = SampledTextureBudget(context.memory, &reported, sampled + storage, storage); const bool fresh = cache.budgetDevice != context.physical; const auto storageLimit = storageBudget(context); if (const auto heap = largestDeviceLocalHeap(context.memory); heap && (fresh || SampledBudgetReportDue(cache.reportedBudget, budget, now - cache.reportedAt) || SampledBudgetReportDue(cache.reportedStorage, storageLimit, now - cache.reportedAt))) { @@ -677,6 +721,14 @@ std::shared_ptr cachedTexture(const Context& context, std::span(resource.baseAddress), resource.width, resource.height, static_cast(resource.dccAddress)); RequireNoMultisampleTarget(resource.baseAddress, guestBytes != 0 ? static_cast(guestBytes) : 1u, "a texture read"); if (auto* recorder = Recorder::Active(); recorder != nullptr) recorder->BoundKeptBytes(); @@ -697,7 +749,14 @@ std::shared_ptr cachedTexture(const Context& context, std::span windows{0}; const auto count = windows.fetch_add(1) + 1; if (count <= 5 || (count & (count - 1)) == 0) std::fprintf(stderr, "[gpu] sampled array view 0x%llx (%ux%u format %u, dimension %d, slices %u..%u) served from its viewed slices at 0x%llx; %llu such views\n", static_cast(resource.baseAddress), resource.width, resource.height, resource.format, static_cast(resource.dimension), resource.baseArray, resource.depthOrLastArray, static_cast(window.baseAddress), static_cast(count)); @@ -715,11 +774,14 @@ std::shared_ptr cachedTexture(const Context& context, std::span scanned; const auto scanKeys = [&] { + if (scanned.has_value()) return *scanned; const auto scanStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; - const auto keys = TextureClearKeys(resource, guestBytes); + scanned = TextureClearKeys(resource, guestBytes); + if (resource.dccAddress != 0) ++sampledLookupKeyScans; if (profile && resource.dccAddress != 0) LookupOutcomes::Add(LookupOutcomes::DccScan, scanStart); - return keys; + return *scanned; }; auto& counters = TextureCounts(); const auto address = resource.baseAddress; @@ -775,6 +837,7 @@ std::shared_ptr cachedTexture(const Context& context, std::spanDescriptor().dccAddress == resource.dccAddress) source = std::move(candidate); } + scanned.reset(); // The lookup's Refresh may have flushed another image's results over the memory (which // marks the keys uncompressed): a surface that stays a snapshot re-reads them behind that // flush, as it does behind its own. APS5_NO_KEYS_RESCAN=1 keeps the keys scanned above. @@ -787,18 +850,22 @@ std::shared_ptr cachedTexture(const Context& context, std::spanGeneration())) { + if (source != nullptr && !GuestMemory::UnchangedSince(address, bytes, source->GenerationFor(address, bytes))) { // The CPU wrote the memory while results were pending: Refresh merges them the usual way. - source->Refresh(); + refreshForView(*source, resource); + scanned.reset(); } // Results stored to guest memory just now are stamped newer than `generation`; a snapshot read // after them is current at the generation of a second (memoized) collect. The store marks the // surface's DCC keys uncompressed, so the keys are read after it. const auto flushStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; - const auto observedKeys = keys.has_value() ? *keys : scanKeys(); - const bool stagedClear = source != nullptr && resource.dccAddress != 0 && (IsDccClear(observedKeys) || observedKeys == DccKeys::ClearSingle); + const bool stagedClear = source != nullptr && resource.dccAddress != 0 && [&] { + const auto observedKeys = keys.has_value() ? *keys : scanKeys(); + return IsDccClear(observedKeys) || observedKeys == DccKeys::ClearSingle; + }(); bool stagedFlushed = false; - if ((source == nullptr || stagedClear) && !clearThroughKeys && StorageTexture::FlushPending(address, bytes, nullptr, "sampled texture")) { + const bool flushAsked = (source == nullptr || stagedClear) && !clearThroughKeys; + if (flushAsked && StorageTexture::FlushPending(address, bytes, nullptr, "sampled texture")) { if (profile) LookupOutcomes::Add(LookupOutcomes::PendingFlush, flushStart); generation = GuestMemory::CollectWrites(address, bytes); if (stagedClear) { @@ -809,6 +876,7 @@ std::shared_ptr cachedTexture(const Context& context, std::span cachedTexture(const Context& context, std::span recycled; if (auto it = findTexture(cache, key); it != cache.entries.end()) { if (it->source != nullptr) { // The view follows the storage image, whatever the GPU wrote to it since; guest memory // written meanwhile is taken in by refreshing the image. A fast clear the image cannot // see (keys, and no pending results to prefer) ends the view: a snapshot holds the clear. if (!stagedFlushed && (source == nullptr || source == it->source) && (source != nullptr || *keys == DccKeys::Uncompressed) && StorageImageServesKeys(*it->source, resource.dccAddress)) { - if (!GuestMemory::UnchangedSince(address, bytes, it->source->Generation())) it->source->Refresh(); + if (!GuestMemory::UnchangedSince(address, bytes, it->source->GenerationFor(address, bytes))) refreshForView(*it->source, resource); if (it->texture->IsDepthCopy()) it->texture->RefreshDepthCopy(); it->keys = *keys; touchTexture(cache, it); @@ -897,29 +966,37 @@ std::shared_ptr cachedTexture(const Context& context, std::spantexture; } } + if (it->bytes.size() == bytes) recycled = std::move(it->bytes); + else parkSnapshot(cache, it->bytes); eraseTexture(cache, it); ++cache.counts.stale; } - CachedTexture entry{key, address, std::vector(source != nullptr ? 0u : bytes), nullptr, *keys, snapshotGeneration}; + if (source != nullptr) parkSnapshot(cache, recycled); + else if (recycled.empty()) recycled = spareSnapshot(cache, bytes); + CachedTexture entry{key, address, source != nullptr ? std::vector{} : (recycled.empty() ? std::vector(bytes) : std::move(recycled)), nullptr, *keys, snapshotGeneration}; entry.committed = std::move(committed); entry.surfaceBytes = bytes; + const auto createTexture = [&](auto&&... args) { + const CacheHold reclaimable(sampledCacheReclaimable); + return std::make_shared(context, std::forward(args)...); + }; if (source != nullptr) { entry.source = source; entry.sourceVersion = source->Version(); if (depthCompare) { - entry.texture = std::make_shared(context, source, resource, components, Texture::DepthCopyTag{}); + entry.texture = createTexture(source, resource, components, Texture::DepthCopyTag{}); entry.texture->RefreshDepthCopy(); } else if (blockCopy) { - entry.texture = std::make_shared(context, source, resource, components, Texture::BlockCopyTag{}); + entry.texture = createTexture(source, resource, components, Texture::BlockCopyTag{}); entry.texture->RefreshDepthCopy(); } else if (IsRepackedTextureFormat(resource.format)) { - entry.texture = std::make_shared(context, source, resource, components, Texture::RepackCopyTag{}); + entry.texture = createTexture(source, resource, components, Texture::RepackCopyTag{}); entry.texture->RefreshDepthCopy(); } else if (srgbExpand) { - entry.texture = std::make_shared(context, source, resource, components, Texture::SrgbExpandTag{}); + entry.texture = createTexture(source, resource, components, Texture::SrgbExpandTag{}); entry.texture->RefreshDepthCopy(); } else { - entry.texture = std::make_shared(context, source, resource, components); + entry.texture = createTexture(source, resource, components); } entry.accounted = source->Cached() ? 0u : heldBytes(*source); counters.fromStorage.fetch_add(1, std::memory_order_relaxed); @@ -935,10 +1012,10 @@ std::shared_ptr cachedTexture(const Context& context, std::span(resource.baseAddress), resource.width, resource.height, resource.format, static_cast(resource.tileMode), nonzero, entry.bytes.size() / 64); } - entry.texture = std::make_shared(context, *context.detiler, resource, components, entry.bytes, depthCompare); + entry.texture = createTexture(*context.detiler, resource, components, entry.bytes, depthCompare); entry.accounted = entry.texture->AllocationBytes(); constexpr std::size_t SnapshotCompareBytes = 256u << 10u; - if (entry.bytes.size() > SnapshotCompareBytes && (entry.generation != 0 || entry.untracked[0] != 0)) std::vector().swap(entry.bytes); + if (entry.bytes.size() > SnapshotCompareBytes && (entry.generation != 0 || entry.untracked[0] != 0)) parkSnapshot(cache, entry.bytes); counters.snapshots.fetch_add(1, std::memory_order_relaxed); } const auto budget = sampledBudget(context, cache); @@ -987,7 +1064,7 @@ struct CachedStorageTexture { // As TextureCache: use order with a hash index by key, plus one by image for StorageImageCached. struct StorageTextureCache { - std::mutex mutex; + AgcDriver::Mutex mutex; std::list entries; std::unordered_map::iterator, StorageKeyHash> index; std::unordered_map::iterator> byImage; @@ -1046,7 +1123,7 @@ std::array SurfaceKey(const Context& context, const GuestTextu } struct ExtendedSurfaces { - std::mutex mutex; + AgcDriver::Mutex mutex; std::map, std::uint32_t> levels; std::atomic any{false}; }; @@ -1108,13 +1185,13 @@ static WrittenStorage WrittenStorageBytes(const GuestTextureResource& viewed, st } // `guestBytes` is the surface size when the caller described the surface already (0: described here). -std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& requested, std::uint32_t mip, std::uint64_t guestBytes, bool written) { +std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& requested, std::uint32_t mip, std::uint64_t guestBytes, bool written, std::optional> layers) { const auto viewed = ArrayLayerWindow(requested, context.limits.maxImageArrayLayers); if (viewed.baseAddress != requested.baseAddress) guestBytes = 0; Require(viewed.dimension != TextureDimension::k2DMsaa, "storage image access to a multisampled texture is not implemented"); - RequireNoMultisampleTarget(viewed.baseAddress, guestBytes != 0 ? static_cast(guestBytes) : 1u, "a storage image access"); - if (auto depth = DepthSurfaceStorage(context, words, viewed, mip)) return depth; const auto viewedBytes = guestBytes != 0 ? guestBytes : DescribeSurface(viewed).guestBytes; + RequireNoMultisampleTarget(viewed.baseAddress, static_cast(viewedBytes), "a storage image access"); + if (auto depth = DepthSurfaceStorage(context, words, viewed, mip)) return depth; if (viewedBytes >= (1ull << 30u)) { static std::atomic large{0}; const auto count = large.fetch_add(1) + 1; @@ -1123,12 +1200,12 @@ std::shared_ptr cachedStorageTexture(const Context& context, std reportGuestAllocation(viewed, viewedBytes); } } - if (DepthPlanesStale() || written) static_cast(FlushDepthPlanes(viewed.baseAddress, static_cast(viewedBytes))); + static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; + if ((DepthPlanesStale() || written) && (written || !layers || disabled)) static_cast(FlushDepthPlanes(viewed.baseAddress, static_cast(viewedBytes), written ? DepthPlaneScope::Whole : DepthPlaneScope::Overlapped)); if (written && DepthSurfaceOverlaps(context.device, viewed.baseAddress, viewedBytes)) { const auto storage = WrittenStorageBytes(viewed, mip); RetireCoveredDepthSurfaces(context.device, storage.bytes, storage.extents); } - static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; if (disabled) return StorageTexture::Create(context, *context.detiler, viewed, mip); static_cast(words); if (auto* recorder = Recorder::Active(); recorder != nullptr) recorder->BoundKeptBytes(); @@ -1154,7 +1231,8 @@ std::shared_ptr cachedStorageTexture(const Context& context, std it = cache.entries.end(); } if (it != cache.entries.end()) { - it->texture->Refresh(); + if (layers) it->texture->RefreshLayers(layers->first, layers->second); + else it->texture->Refresh(); cache.entries.splice(cache.entries.begin(), cache.entries, it); texture = it->texture; counters.storageHits.fetch_add(1, std::memory_order_relaxed); @@ -1162,11 +1240,16 @@ std::shared_ptr cachedStorageTexture(const Context& context, std } else { if (guestBytes == 0) guestBytes = DescribeSurface(resource).guestBytes; if (StorageTexture::FlushPending(resource.baseAddress, static_cast(guestBytes), nullptr, "storage image creation", PublishScope::None) && profile) start = LookupOutcomes::Add(LookupOutcomes::PendingFlush, start); - CachedStorageTexture entry{key, mip, StorageTexture::Create(context, *context.detiler, resource, mip)}; + std::shared_ptr created; + { + const CacheHold reclaimable(storageCacheReclaimable); + created = StorageTexture::Create(context, *context.detiler, resource, mip); + } + CachedStorageTexture entry{key, mip, std::move(created)}; static const bool keepNew = std::getenv("APS5_NO_KEEP_NEW_STORAGE") == nullptr; if (auto* recorder = Recorder::Active(); keepNew && recorder != nullptr && GuestMemory::GpuMutex().HeldByThisThread() && recorder->Recording()) recorder->Keep(entry.texture); entry.accounted = heldBytes(*entry.texture); - const auto budget = storageBudget(context); + const auto budget = storageBudget(context, cache.bytes + entry.accounted); while (!cache.entries.empty() && cache.bytes + entry.accounted > budget) evicted.push_back(evictStorage(cache, std::prev(cache.entries.end()))); cache.bytes += entry.accounted; texture = entry.texture; @@ -1204,22 +1287,27 @@ std::uint64_t TextureCacheBudget(const VkPhysicalDeviceMemoryProperties& memory) return std::max(2048ull << 20u, heap ? memory.memoryHeaps[*heap].size / 4u : 0u); } +std::uint64_t StorageTextureCeiling(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported) { + const auto heap = largestDeviceLocalHeap(memory); + if (reported == nullptr || !heap || reported->heapBudget[*heap] == 0) return TextureCacheBudget(memory); + return std::max(512ull << 20u, std::min(memory.memoryHeaps[*heap].size / 2u, reported->heapBudget[*heap] / 2u)); +} + std::uint64_t StorageTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t sampledBytes, std::uint64_t storageBytes) { const auto heap = largestDeviceLocalHeap(memory); if (reported == nullptr || !heap || reported->heapBudget[*heap] == 0) return TextureCacheBudget(memory); const auto budget = reported->heapBudget[*heap]; const auto usage = reported->heapUsage[*heap]; const auto reserved = usage - std::min(usage, sampledBytes + storageBytes) + sampledBytes + budget / 8u; - const auto cap = std::min(TextureCacheBudget(memory), budget / 4u); - return std::max(512ull << 20u, std::min(cap, budget > reserved ? budget - reserved : 0u)); + return std::min(StorageTextureCeiling(memory, reported), std::max(512ull << 20u, budget > reserved ? budget - reserved : 0u)); } -std::uint64_t SampledTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t textureBytes) { +std::uint64_t SampledTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t textureBytes, std::uint64_t storageBytes) { const auto heap = largestDeviceLocalHeap(memory); if (reported == nullptr || !heap || reported->heapBudget[*heap] == 0) return TextureCacheBudget(memory); const auto budget = reported->heapBudget[*heap]; const auto usage = reported->heapUsage[*heap]; - const auto reserved = usage - std::min(usage, textureBytes) + TextureCacheBudget(memory) + budget / 8u; + const auto reserved = usage - std::min(usage, textureBytes) + std::max(TextureCacheBudget(memory), storageBytes) + budget / 8u; return std::max(2048ull << 20u, budget > reserved ? budget - reserved : 0u); } @@ -1248,6 +1336,8 @@ TextureCacheUse TextureCacheUsage() { dropUncachedViews(sampled); use.sampledEntries = sampled.entries.size(); use.sampledBytes = sampled.bytes; + use.spareEntries = sampled.spare.size(); + use.spareBytes = sampled.spareBytes; } use.storageBytes = storageCacheBytes(); return use; @@ -1277,6 +1367,7 @@ void FlushCachedTextures(VkDevice device) { GuestMemory::AssertGpuLockHeld("FlushCachedTextures"); auto& cache = StorageTextures(); std::lock_guard lock(cache.mutex); + const CacheHold held(storageCacheHeld); for (const auto& entry : cache.entries) { if (entry.key.device == device) entry.texture->Flush(); } @@ -1287,15 +1378,19 @@ void ClearCachedTextures(VkDevice device) { auto& sampled = Textures(); { std::lock_guard lock(sampled.mutex); + const CacheHold held(sampledCacheHeld); for (auto it = sampled.entries.begin(); it != sampled.entries.end();) { if (it->key.device == device) eraseTexture(sampled, it++); else ++it; } + sampled.spare.clear(); + sampled.spareBytes = 0; } auto& storage = StorageTextures(); std::vector> cleared; { std::lock_guard lock(storage.mutex); + const CacheHold held(storageCacheHeld); for (auto it = storage.entries.begin(); it != storage.entries.end();) { if (it->key.device != device) { ++it; @@ -1322,7 +1417,7 @@ void ClearCachedTextures(VkDevice device) { } } -std::uint64_t ReleaseTextureCaches(std::uint64_t bytes) { +std::uint64_t ReleaseTextureCaches(std::uint64_t bytes, bool newImage) { thread_local bool releasing = false; if (releasing) return 0; releasing = true; @@ -1338,63 +1433,67 @@ std::uint64_t ReleaseTextureCaches(std::uint64_t bytes) { bool storageSkipped = false; { auto& cache = Textures(); - if (sampledCacheHeld != 0 || !AcquireCache(cache.mutex)) { + std::unique_lock lock(cache.mutex, std::defer_lock); + const bool inherited = sampledCacheHeld != 0; + if ((inherited && (!newImage || sampledCacheReclaimable == 0)) || (!inherited && !lock.try_lock())) { sampledSkipped = true; } else { - std::lock_guard lock(cache.mutex, std::adopt_lock); - dropUncachedViews(cache); - while (freed < bytes && !cache.entries.empty()) { - const auto back = std::prev(cache.entries.end()); - const auto accounted = back->accounted; - const bool last = back->texture.use_count() == 1; - eraseTexture(cache, back); + for (auto it = cache.entries.end(); it != cache.entries.begin() && freed < bytes;) { + --it; + if (it->texture.use_count() != 1 || (it->source != nullptr && it->source->Dirty())) continue; + const auto allocated = it->texture->AllocationBytes(); + const auto sourceBytes = it->source != nullptr ? it->source->AllocationBytes() : 0u; + const std::weak_ptr source = it->source; + const auto next = std::next(it); + eraseTexture(cache, it); ++cache.counts.evicted; ++sampledEntries; - if (last) freed += accounted; + freed += allocated; + if (source.expired()) freed += sourceBytes; + it = next; } } } if (freed < bytes) { auto& cache = StorageTextures(); std::unique_lock gpu(GuestMemory::GpuMutex(), std::defer_lock); - if (storageCacheHeld != 0 || !gpu.try_lock()) { + const bool inherited = storageCacheHeld != 0; + if ((inherited && (!newImage || storageCacheReclaimable == 0)) || !gpu.try_lock()) { storageSkipped = true; } else { try { while (freed < bytes) { - std::vector, std::uint64_t>> evicted; + std::shared_ptr image; { - if (!AcquireCache(cache.mutex)) { + std::unique_lock lock(cache.mutex, std::defer_lock); + if (!inherited && !lock.try_lock()) { storageSkipped = true; break; } - std::lock_guard lock(cache.mutex, std::adopt_lock); - std::uint64_t batch = 0; storageKept = 0; - for (auto it = cache.entries.end(); it != cache.entries.begin() && batch + freed < bytes;) { + for (auto it = cache.entries.end(); it != cache.entries.begin();) { --it; if (it->texture->Dirty()) { ++storageKept; continue; } - const auto accounted = it->accounted; - const auto next = std::next(it); - evicted.emplace_back(evictStorage(cache, it), accounted); - batch += accounted; - it = next; + image = evictStorage(cache, it); + break; } - if (evicted.empty()) break; + if (image == nullptr) break; } - for (auto& [image, accounted] : evicted) { - std::vector> resources; - SharedResourceCache().RemoveImage(image.get(), &resources); - if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->Recording()) { - for (auto& resource : resources) recorder->Keep(std::move(resource)); - } - resources.clear(); - ++storageEntries; - if (image.use_count() == 1) freed += accounted; - image.reset(); + std::vector> resources; + const bool removed = SharedResourceCache().TryRemoveImage(image.get(), &resources); + if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->Recording()) { + for (auto& resource : resources) recorder->Keep(std::move(resource)); + } + resources.clear(); + ++storageEntries; + if (image.use_count() == 1) freed += image->AllocationBytes(); + image.reset(); + if (!removed) { + storageSkipped = true; + break; } } } catch (const std::exception& error) { @@ -1436,6 +1535,10 @@ std::shared_ptr CachedStorageSurface(const Context& context, con return cachedStorageTexture(context, {}, resource, 0); } +std::shared_ptr CachedStorageLayers(const Context& context, const GuestTextureResource& resource, std::uint32_t firstLayer, std::uint32_t layerCount) { + return cachedStorageTexture(context, {}, resource, 0, 0, false, std::pair{firstLayer, layerCount}); +} + bool StorageImageServesKeys(const StorageTexture& image, std::uint64_t dccAddress) { GuestTextureResource resource{}; resource.dccAddress = dccAddress; @@ -1527,6 +1630,11 @@ ShaderResources::ShaderResources(const Context& context, const CompiledShader& c // Every use of a compute build is a recorded dispatch that calls MarkGpuWrites, which staged // buffers need (a synchronous draw's use would not). guestMemory.AllowDeviceStaging(); + static const std::uint64_t addressStagingMinThreads = [] { + const char* value = std::getenv("APS5_BDA_STAGING_MIN_THREADS"); + return value != nullptr ? std::strtoull(value, nullptr, 10) : 512ull; + }(); + if (dispatchThreads != 0 && dispatchThreads < addressStagingMinThreads) guestMemory.DisallowAddressStaging(); const std::span shaders(&deferredCompute, 1); if (!deferred) { prepareAddressBindings(shaders, snapshots); @@ -1613,7 +1721,7 @@ constexpr std::size_t BuildPhaseCount = static_cast(ShaderResources constexpr std::array BuildPhaseNames{"bindings", "precollect", "guest memory upload", "descriptors", "stage A", "images", "bda", "stage B"}; struct BuildProfile { - std::mutex mutex; + AgcDriver::Mutex mutex; std::array ms{}; std::uint64_t builds = 0; }; @@ -1976,9 +2084,10 @@ void ShaderResources::buildPrepare(std::span shaders, cons const bool classified = element < binding.bufferWritten.size() && element < binding.bufferRead.size() && element < binding.bufferAtomic.size(); allocations[index].writeOnly = classified && allocations[index].guest && allocations[index].written && binding.bufferWritten[element] && !read && !atomic; const auto& push = shader.program->pushConstants; - if (!push.empty()) { + const auto pushDataBytes = shader.program->stencilReferences.empty() ? push.size() : shader.program->stencilReferenceDword * sizeof(std::uint32_t); + if (pushDataBytes != 0u) { const auto position = shader.program->memoryOffsetDword * 4u + element; - Require(position < push.size(), "guest buffer offset lies outside the shader's push constants"); + Require(position < pushDataBytes, "guest buffer offset lies outside the shader's push constants"); allocations[index].pushByte = static_cast(shader.pushConstantOffset + position); } else { allocations[index].dataByte = shader.program->memoryOffsetDword * 4u + element; @@ -2346,7 +2455,7 @@ void ShaderResources::buildComplete() { for (auto element = first; element < last; ++element) { const auto index = binding.imageAllocations[element]; if (binding.layout.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER) images.push_back({samplers[index]->Handle(), VK_NULL_HANDLE, VK_IMAGE_LAYOUT_UNDEFINED}); - else if (binding.layout.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) images.push_back({VK_NULL_HANDLE, storageTextures[index]->StorageView(storageMips[index], storageFirstLayer[index]), VK_IMAGE_LAYOUT_GENERAL}); + else if (binding.layout.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) images.push_back({VK_NULL_HANDLE, storageAtomic64[index] ? storageTextures[index]->Atomic64View(storageMips[index], storageFirstLayer[index]) : storageAtomic[index] ? storageTextures[index]->AtomicView(storageMips[index], storageFirstLayer[index]) : storageTextures[index]->StorageView(storageMips[index], storageFirstLayer[index]), VK_IMAGE_LAYOUT_GENERAL}); else images.push_back({VK_NULL_HANDLE, textureFirstLayer[index] ? textures[index]->FirstLayerView() : textures[index]->View(), textures[index]->Layout()}); } if (recorded) runs.emplace_back(write.dstArrayElement, write.descriptorCount); @@ -2534,7 +2643,7 @@ bool ShaderResources::NeverReusable(std::span shaders) { // APS5_PROFILE_DRAW: the per-device descriptor caches' counters, every 10 s. void ShaderResources::reportDescriptorCaches() const { - static std::mutex reportMutex; + static AgcDriver::Mutex reportMutex; static auto lastReport = std::chrono::steady_clock::now(); const auto now = std::chrono::steady_clock::now(); std::lock_guard lock(reportMutex); @@ -2886,21 +2995,57 @@ bool TemplateAuditDue(std::chrono::steady_clock::time_point last) { } +void LastRecordIndices(std::span logged, std::span objects, std::vector& indices) { + indices.assign(objects.size(), logged.size()); + thread_local std::vector>* wantedSlot = nullptr; + auto& wanted = ShaderRecompiler::ThreadOwned(wantedSlot); + wanted.clear(); + for (std::size_t i = 0; i < objects.size(); ++i) wanted.emplace_back(objects[i], i); + const auto byObject = [](const std::pair& left, const std::pair& right) { return std::less{}(left.first, right.first); }; + std::sort(wanted.begin(), wanted.end(), byObject); + std::size_t open = wanted.size(); + for (auto at = logged.size(); at-- > 0 && open != 0;) { + const auto [first, last] = std::equal_range(wanted.begin(), wanted.end(), std::pair{logged[at], 0}, byObject); + if (first == last || indices[first->second] != logged.size()) continue; + for (auto it = first; it != last; ++it) indices[it->second] = at; + open -= static_cast(last - first); + } + if (wanted.capacity() > LookupLogBound + LookupLogBound / 2) { + wanted.clear(); + wanted.shrink_to_fit(); + } +} + // Turns the lookups' records into this object's per-texture validation records (see // ValidatedSurface); the last record of an object is the freshest. void ShaderResources::captureValidation() { - const auto record = [](const void* object) -> const LookupRecord* { - const auto& log = lookupLog(); - for (auto it = log.rbegin(); it != log.rend(); ++it) { - if (it->object == object) return &*it; - } - return nullptr; - }; + const auto& log = lookupLog(); + thread_local std::vector* loggedSlot = nullptr; + auto& logged = ShaderRecompiler::ThreadOwned(loggedSlot); + thread_local std::vector* objectsSlot = nullptr; + auto& objects = ShaderRecompiler::ThreadOwned(objectsSlot); + thread_local std::vector* indicesSlot = nullptr; + auto& indices = ShaderRecompiler::ThreadOwned(indicesSlot); + logged.clear(); + for (const auto& entry : log) logged.push_back(entry.object); + objects.clear(); + for (const auto& texture : textures) objects.push_back(texture.get()); + LastRecordIndices(logged, objects, indices); validatedTextures.assign(textures.size(), {}); for (std::size_t i = 0; i < textures.size(); ++i) { - if (const auto* found = record(textures[i].get())) validatedTextures[i] = {found->resource, found->bytes, found->keys, found->generation, 0, found->source, found->depth, true}; + if (indices[i] == log.size()) continue; + const auto& found = log[indices[i]]; + validatedTextures[i] = {found.resource, found.bytes, found.keys, found.generation, 0, found.source, found.depth, true}; } lookupLog().clear(); + const auto trim = [](auto& scratch) { + if (scratch.capacity() <= LookupLogBound + LookupLogBound / 2) return; + scratch.clear(); + scratch.shrink_to_fit(); + }; + trim(logged); + trim(objects); + trim(indices); } std::size_t ShaderResources::LookupLogCapacity() { @@ -3005,7 +3150,7 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::spanCached()) return fail(FastFail::Evicted); - queries.push_back({address, bytes, source->Generation()}); + queries.push_back({address, bytes, source->GenerationFor(address, bytes)}); images.push_back(source); } else { queries.push_back({address, bytes, surface.generation}); @@ -3033,7 +3178,7 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::spanCached()) return fail(FastFail::Evicted); - queries.push_back({address, bytes, image->Generation()}); + queries.push_back({address, bytes, image->GenerationFor(address, bytes)}); images.push_back(image); } if (!pending.empty()) { @@ -3101,7 +3246,7 @@ bool ShaderResources::fastRevalidateEach() { if (surface.source != nullptr) { // The view follows the image: it needs the image current with guest memory, as the // lookup's Refresh would make it. - if (!StorageImageCached(context, surface.source) || !GuestMemory::UnchangedSince(address, bytes, surface.source->Generation())) return false; + if (!StorageImageCached(context, surface.source) || !GuestMemory::UnchangedSince(address, bytes, surface.source->GenerationFor(address, bytes))) return false; } else if (!GuestMemory::UnchangedSince(address, bytes, surface.generation)) { return false; } @@ -3135,7 +3280,7 @@ bool ShaderResources::fastRevalidateEach() { if (GuestMemory::CollectWrites(address, bytes) == 0) return false; if (PendingStorageOverlaps(address, bytes, image)) return false; if (!StorageImageCached(context, image)) return false; - if (!GuestMemory::UnchangedSince(address, bytes, image->Generation())) return false; + if (!GuestMemory::UnchangedSince(address, bytes, image->GenerationFor(address, bytes))) return false; } // Every check passed: snapshot textures are current at the collects issued before them. Storage // images keep their own generation: nothing was stamped above it, so the blocks a later write @@ -3169,9 +3314,9 @@ ShaderResources::OwnRefreshFallback ShaderResources::refreshOwnObjects(std::span if (found != nullptr && !Texture::CanCopyFrom(*found, surface.resource)) found.reset(); if (found != nullptr) { if (found != source) return OwnRefreshFallback::ForeignView; - if (!GuestMemory::UnchangedSince(address, bytes, source->Generation())) { + if (!GuestMemory::UnchangedSince(address, bytes, source->GenerationFor(address, bytes))) { countOwnRefresh(false); - source->Refresh(); + refreshForView(*source, surface.resource); } return OwnRefreshFallback::Count; } @@ -3233,6 +3378,11 @@ ShaderResources::OwnRefreshFallback ShaderResources::refreshOwnObjects(std::span return OwnRefreshFallback::Count; } +bool AnyPendingCpuWrites(const Context& context) { + if (Recorder::InCompletion() || Recorder::Active() == nullptr) return false; + return (context.copiedWriters != nullptr && !context.copiedWriters->empty()) || !DrawCopiedWriters()->empty() || Recorder::PendingCompletionLabels() != 0; +} + bool CompletePendingCpuWrites(const Context& context, std::uint64_t address, std::size_t bytes) { if (bytes == 0 || Recorder::InCompletion()) return false; auto* recorder = Recorder::Active(); @@ -3262,7 +3412,54 @@ const ShaderRecompiler::DescriptorBinding* ShaderResources::tableImages(std::spa return nullptr; } +bool ShaderResources::EpochProofs() { + return DrawPipelineEnabled(); +} + +namespace { +void noteEpochProof(bool reused, bool verified, bool failed) { + static std::atomic reuses{0}, proofs{0}, checks{0}, failures{0}; + if (verified) checks.fetch_add(1, std::memory_order_relaxed); + if (failed) failures.fetch_add(1, std::memory_order_relaxed); + const auto count = (reused ? reuses : proofs).fetch_add(1, std::memory_order_relaxed) + 1u; + static const bool report = std::getenv("APS5_PROFILE_DRAW") != nullptr || std::getenv("APS5_VERIFY_DRAW_PIPELINE") != nullptr; + if (!report) return; + if (count % 262144u == 0u || (count & (count - 1u)) == 0u) std::fprintf(stderr, "[epoch-proofs] resource proofs: %llu reused the epoch's, %llu proved; verify: %llu checked, %llu failed; storage refreshes reused %llu\n", static_cast(reuses.load()), static_cast(proofs.load()), static_cast(checks.load()), static_cast(failures.load()), static_cast(StorageTexture::epochRefreshes.load())); +} +} + bool ShaderResources::Revalidate(std::span shaders, ProofReport* report) { + if (EpochProofs()) { + const auto epoch = GuestMemory::CollectEpoch(); + const auto proof = GuestMemory::ProofSerial(); + const auto pending = StorageTexture::PendingSerial(); + if (epoch != 0 && provedEpoch == epoch && provedProof == proof && provedPending == pending) { + static const bool verify = std::getenv("APS5_VERIFY_DRAW_PIPELINE") != nullptr; + thread_local std::uint64_t reuses = 0; + if (verify && ++reuses % 64u == 0u) { + const bool ok = revalidateFull(shaders, report); + noteEpochProof(true, true, !ok); + return ok; + } + if (report != nullptr) *report = {ProofPath::Full, ProofFailure::None}; + noteEpochProof(true, false, false); + return true; + } + const bool ok = revalidateFull(shaders, report); + noteEpochProof(false, false, false); + if (ok) { + provedEpoch = epoch; + provedProof = proof; + provedPending = pending; + } else { + provedEpoch = 0; + } + return ok; + } + return revalidateFull(shaders, report); +} + +bool ShaderResources::revalidateFull(std::span shaders, ProofReport* report) { if (report != nullptr) *report = {ProofPath::Full, ProofFailure::Other}; if (!reusable || shaders.empty()) return false; static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; @@ -3533,7 +3730,7 @@ struct ChurnCounts { // Dispatch keys and draw keys are counted apart: what churns a draw's key decides the draw steps. struct ChurnProfile { - std::mutex mutex; + AgcDriver::Mutex mutex; std::unordered_map lastByVariant; ChurnCounts dispatch; ChurnCounts draws; @@ -3834,6 +4031,17 @@ void ResourceCache::Remove(const Key& key, const ShaderResources* object) { void ResourceCache::RemoveImage(const StorageTexture* image, std::vector>* evicted) { std::lock_guard lock(mutex); + removeImage(image, evicted); +} + +bool ResourceCache::TryRemoveImage(const StorageTexture* image, std::vector>* evicted) { + std::unique_lock lock(mutex, std::try_to_lock); + if (!lock.owns_lock()) return false; + removeImage(image, evicted); + return true; +} + +void ResourceCache::removeImage(const StorageTexture* image, std::vector>* evicted) { for (auto it = entries.begin(); it != entries.end();) { if (!it->second->ReadsImage(image)) { ++it; @@ -4186,7 +4394,8 @@ std::size_t ShaderResources::addGuestBuffer(std::span words Require(!written || !overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer"); written = written || allWritten; const std::uint64_t recordBytes = descriptor.Stride() != 0 ? descriptor.Stride() : extent; - const bool swept = read && dispatchThreads != 0 && recordBytes != 0 && dispatchThreads * recordBytes * 2u >= byteSize; + constexpr std::uint64_t WidestBufferStoreBytes = 16; + const bool swept = (read || (context.discreteGpu && descriptor.Stride() > WidestBufferStoreBytes)) && dispatchThreads != 0 && recordBytes != 0 && dispatchThreads * recordBytes * 2u >= byteSize; if (written) guestMemory.AddWritable(address, size, atomic, swept); else { guestMemory.AddReadable(address, size); @@ -4294,12 +4503,11 @@ std::uint64_t ShaderResources::DataWordsHash(const CompiledShader& shader) { bool ShaderResources::DataWordsDiffer(const CompiledShader& shader) const { Require(shader.program != nullptr, "missing compiled shader"); const auto& program = *shader.program; - if (program.bindings.size() != bindings.size()) return true; - for (std::size_t index = 0; index < program.bindings.size(); ++index) { - const auto& binding = program.bindings[index]; + for (const auto& binding : program.bindings) { if (!DataRole(binding.role)) continue; - if (bindings[index].allocations.size() != 1) return true; - const auto& allocation = allocations[bindings[index].allocations.front()]; + const auto held = std::ranges::find_if(bindings, [&](const Binding& candidate) { return candidate.layout.binding == binding.binding; }); + if (held == bindings.end() || held->allocations.size() != 1) return true; + const auto& allocation = allocations[held->allocations.front()]; if (allocation.dataWords.size() != binding.guestDescriptor.size() || !std::equal(allocation.dataWords.begin(), allocation.dataWords.end(), binding.guestDescriptor.begin())) return true; } return false; @@ -4314,23 +4522,23 @@ bool ShaderResources::RefreshData(VkCommandBuffer commands, const CompiledShader bool ShaderResources::refreshData(VkCommandBuffer commands, const CompiledShader& shader, Recorder* recorder) { Require(shader.program != nullptr, "missing compiled shader"); const auto& program = *shader.program; - Require(program.bindings.size() == bindings.size(), "template bindings disagree with the shader"); bool recorded = false; // One [gputime] range of class TemplateDataRefresh around the updates, begun at the first one // (a refresh that finds every word equal records nothing and times nothing). auto timing = Recorder::NoTiming; std::uint64_t refreshedBytes = 0; - for (std::size_t index = 0; index < program.bindings.size(); ++index) { - const auto& binding = program.bindings[index]; + for (const auto& binding : program.bindings) { if (!DataRole(binding.role)) continue; - Require(bindings[index].allocations.size() == 1, "data binding without its buffer"); - auto& allocation = allocations[bindings[index].allocations.front()]; + const auto held = std::ranges::find_if(bindings, [&](const Binding& candidate) { return candidate.layout.binding == binding.binding; }); + Require(held != bindings.end(), "template bindings disagree with the shader"); + Require(held->allocations.size() == 1, "data binding without its buffer"); + auto& allocation = allocations[held->allocations.front()]; const auto size = binding.guestDescriptor.size() * sizeof(std::uint32_t); Require(allocation.buffer != nullptr && !allocation.guest && allocation.size == size && size <= MaxRefreshBytes, "template data buffer cannot take the dispatch's words"); if (allocation.dataWords.size() == binding.guestDescriptor.size() && std::equal(allocation.dataWords.begin(), allocation.dataWords.end(), binding.guestDescriptor.begin())) continue; if (binding.role == ShaderRecompiler::DescriptorRole::ShaderData) ValidateRuntimeResources(shader, binding.guestDescriptor); if (recorder != nullptr && timing == Recorder::NoTiming) timing = recorder->BeginGpuTiming(Recorder::CommandClass::TemplateDataRefresh); - writeDataWords(commands, bindings[index].allocations.front(), binding.guestDescriptor); + writeDataWords(commands, held->allocations.front(), binding.guestDescriptor); allocation.dataWords.assign(binding.guestDescriptor.begin(), binding.guestDescriptor.end()); refreshedBytes += size; recorded = true; @@ -4579,7 +4787,7 @@ std::shared_ptr ShaderResources::fastTexture(const ImageRecord& record) // keys and stays valid while they are unchanged); a change is the full lookup's to judge. if (keys != record.entryKeys) return nullptr; if (record.source != nullptr) { - if (!StorageImageCached(context, record.source.get()) || !GuestMemory::UnchangedSince(address, bytes, record.source->Generation())) return nullptr; + if (!StorageImageCached(context, record.source.get()) || !GuestMemory::UnchangedSince(address, bytes, record.source->GenerationFor(address, bytes))) return nullptr; if (record.resource.dccAddress != 0 && IsDccClear(record.source->FilledKeys())) return nullptr; // Under fast-clear keys the view holds only while its image's results are still pending over // the surface; flushed, the clear the image cannot see makes the lookup take a snapshot. @@ -4639,18 +4847,23 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind const bool depthCompare = !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element); const bool firstLayer = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && (resource.dimension == TextureDimension::k2DArray || (depthCompare && resource.dimension == TextureDimension::k1DArray)); const bool depth1D = depthCompare && ((binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k1D) || (binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2DArray && resource.dimension == TextureDimension::k1DArray)); - if (!firstLayer && !depth1D && !MatchesGuestDimension(*binding.imageShape, resource.dimension)) throw std::runtime_error("AGC graphics: guest texture dimension disagrees with the shader's declared image shape (shape " + std::to_string(static_cast(*binding.imageShape)) + ", dimension " + std::to_string(static_cast(resource.dimension)) + ")"); + const bool firstSample = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k2DMsaa; + if (!firstLayer && !depth1D && !firstSample && !MatchesGuestDimension(*binding.imageShape, resource.dimension)) throw std::runtime_error("AGC graphics: guest texture dimension disagrees with the shader's declared image shape (shape " + std::to_string(static_cast(*binding.imageShape)) + ", dimension " + std::to_string(static_cast(resource.dimension)) + ")"); const VkComponentMapping components = ViewComponents(resource); const auto guestBytes = record != nullptr && record->decoded ? record->guestBytes : DescribeSurface(resource).guestBytes; CompletePendingCpuWrites(context, resource.baseAddress, static_cast(guestBytes)); if (resource.dccAddress != 0) CompletePendingCpuWrites(context, resource.dccAddress, DccKeyBytes(guestBytes)); std::shared_ptr texture; const VkImageViewType multisampleViewType = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2DMultisampleArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; - if (previousSampled.texture != nullptr && previousSampled.depthCompare == depthCompare && previousSampled.multisampleViewType == multisampleViewType && words.size() == 8 && std::equal(words.begin(), words.end(), previousSampled.words.begin())) texture = previousSampled.texture; - if (texture == nullptr && record != nullptr && record->texture != nullptr) { + if (previousSampled.texture != nullptr && previousSampled.depthCompare == depthCompare && previousSampled.multisampleViewType == multisampleViewType && previousSampled.firstSample == firstSample && words.size() == 8 && std::equal(words.begin(), words.end(), previousSampled.words.begin())) texture = previousSampled.texture; + if (texture == nullptr && !firstSample && record != nullptr && record->texture != nullptr) { texture = fastTexture(*record); (texture != nullptr ? counters.fastHits : counters.fastMisses).fetch_add(1, std::memory_order_relaxed); } + if (texture == nullptr && firstSample) { + Require(!depthCompare && !DepthSurfaceAt(resource.baseAddress), "reading sample 0 of a multisampled depth surface through a 2D image is not implemented"); + texture = MultisampleTargetFirstSample(context, resource, components); + } if (texture == nullptr) texture = cachedTexture(context, words, resource, components, guestBytes, depthCompare, multisampleViewType); if (resource.dimension == TextureDimension::k2DMsaa) multisampleSources.push_back(resource.baseAddress); bool baseLevel = false; @@ -4678,6 +4891,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind std::copy(words.begin(), words.end(), previousSampled.words.begin()); previousSampled.depthCompare = depthCompare; previousSampled.multisampleViewType = multisampleViewType; + previousSampled.firstSample = firstSample; previousSampled.texture = texture; } textures.push_back(std::move(texture)); @@ -5022,8 +5236,8 @@ void ShaderResources::resolveImageTables() { storageKeys.push_back(resource.dccAddress); storageFirstLayer.push_back(firstLayer); storageWritten.push_back(true); - storageAtomic.push_back(false); - storageAtomic64.push_back(false); + storageAtomic.push_back(element < binding.imageAtomic.size() && binding.imageAtomic[element]); + storageAtomic64.push_back(element < binding.imageAtomic64.size() && binding.imageAtomic64[element]); describedRanges.push_back({"storage", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast(resource.tileMode), resource.dccAddress, true}); bindings[storageBinding].imageAllocations[absolute] = storageTextures.size() - 1u; continue; @@ -5081,6 +5295,10 @@ void ShaderResources::resolveImageTables() { reportTextureCounters(); } +std::uint64_t SampledLookupKeyScans() { + return sampledLookupKeyScans; +} + ImageTableReuseCounts ImageTableReuses() { const auto& counters = TextureCounts(); return {counters.tablesReused.load(std::memory_order_relaxed) + counters.tablesReplayed.load(std::memory_order_relaxed), counters.tablesResolved.load(std::memory_order_relaxed), counters.tablesReplayed.load(std::memory_order_relaxed), counters.tableWritesKept.load(std::memory_order_relaxed), counters.tableWritesMade.load(std::memory_order_relaxed)}; @@ -5463,8 +5681,9 @@ std::shared_ptr ShaderResources::PrepareDrawBindi if (verifySnapshots && buffer != nullptr) { static std::atomic checked{0}, differing{0}; const auto fast = buffer->Bytes().subspan(static_cast(offset), bytes); - const auto slow = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, GuestMemory::CollectWrites(begin, bytes)); - const bool same = slow == buffer || (slow != nullptr && slow->Bytes().size() == fast.size() && std::memcmp(slow->Bytes().data(), fast.data(), fast.size()) == 0) || (slow == nullptr && std::memcmp(fast.data(), reinterpret_cast(begin), bytes) == 0); + VkDeviceSize slowOffset = 0; + const auto slow = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, GuestMemory::CollectWrites(begin, bytes), &slowOffset); + const bool same = (slow == buffer && slowOffset == offset) || (slow != nullptr && slow->Bytes().size() >= slowOffset + fast.size() && std::memcmp(slow->Bytes().data() + slowOffset, fast.data(), fast.size()) == 0) || (slow == nullptr && std::memcmp(fast.data(), reinterpret_cast(begin), bytes) == 0); if (!same) differing.fetch_add(1); if (checked.fetch_add(1) % 200000 == 199999) std::fprintf(stderr, "[draw-snapshots] verify: %llu fast answers checked, %llu differing%c", static_cast(checked.load()), static_cast(differing.load()), 10); } @@ -5477,7 +5696,7 @@ std::shared_ptr ShaderResources::PrepareDrawBindi offset = slice.offset; } else { const auto generation = GuestMemory::CollectWrites(begin, bytes); - buffer = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, generation); + buffer = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, generation, &offset); if (buffer == nullptr) { buffer = std::make_shared(context, bytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); std::memcpy(buffer->Bytes().data(), reinterpret_cast(begin), bytes); @@ -5576,6 +5795,7 @@ void ShaderResources::WriteBack() { } catch (const BdaUnalignedAccess&) { unaligned = std::current_exception(); } + if (!guestMemory.Writes().empty() || std::find(storageWritten.begin(), storageWritten.end(), true) != storageWritten.end() || (usesBda && !bdaReadOnly)) GuestMemory::BreakProofs(); if (!SkipWriteBack()) { for (std::size_t index = 0; index < storageTextures.size(); ++index) { if (!storageWritten[index] || storageTextures[index] == nullptr) continue; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index f9912af9f9..7dd97e4370 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -1,4 +1,5 @@ #include "BdaAbi.hpp" +#include "SpirvBackend/SpirvAnalysis.hpp" #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" @@ -53,6 +54,8 @@ struct Module { bool layerOutput = false; bool meshCapability = false; std::uint32_t primitiveInputs = 0; + bool pointSizeOutput = false; + bool pointSize = false; std::map> modes; const std::vector& Type(std::uint32_t id) const { @@ -138,6 +141,10 @@ struct Module { Require(input && (clipInput || (value == spv::BuiltInPosition && signature == "f32x4") || (value == spv::BuiltInTessCoord && stage == Stage::TessellationEvaluation && signature == "f32x3") || ((value == spv::BuiltInInvocationId || value == spv::BuiltInPrimitiveId || value == spv::BuiltInPatchVertices) && (signature == "i32" || signature == "u32"))), "unsupported tessellation built-in"); return; } + if (!input && value == spv::BuiltInPointSize && pointSizeOutput && signature == "f32" && !pointSize) { + pointSize = true; + return; + } if (!input && value == spv::BuiltInPrimitivePointIndicesEXT) primitiveIndices = 1u; if (!input && value == spv::BuiltInPrimitiveLineIndicesEXT) primitiveIndices = 2u; if (!input && value == spv::BuiltInPrimitiveTriangleIndicesEXT) primitiveIndices = 3u; @@ -149,6 +156,10 @@ struct Module { Require((value == spv::BuiltInVertexIndex || value == spv::BuiltInInstanceIndex || value == spv::BuiltInBaseInstance) && signature == "i32", "unsupported vertex built-in input"); } else if (vertex && storage == spv::StorageClassOutput) { if (value == spv::BuiltInLayer && layerOutput && (signature == "u32" || signature == "i32")) return; + if (value == spv::BuiltInPointSize && pointSizeOutput && signature == "f32" && !pointSize) { + pointSize = true; + return; + } Require(value == spv::BuiltInPosition && signature == "f32x4" && !position, "unsupported or duplicate vertex built-in output"); position = true; } else { @@ -186,6 +197,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi Require(words[4] == 0, "unsupported SPIR-V schema: " + std::to_string(words[4])); Module module; + module.pointSizeOutput = (state.vertexPoints && stage == Stage::Vertex) || (mesh && state.stages.mesh.has_value() && state.stages.mesh->outputPrimitiveSize == 1u); std::uint32_t entries = 0; std::uint32_t memoryModels = 0; std::uint32_t entryPoint = 0; @@ -583,6 +595,10 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi } else { Require(variable.storage == spv::StorageClassStorageBuffer && decoration.set && decoration.binding, "unsupported or unbound shader resource"); const auto key = std::make_pair(*decoration.set, *decoration.binding); + if (*decoration.set == ShaderRecompiler::PixelFunctionLdsDescriptorSet) { + Require(fragment && *decoration.binding == 0u, "pixel function LDS buffer binding is wrong"); + continue; + } const auto binding = std::find_if(shader.bindings.begin(), shader.bindings.end(), [&](const auto& item) { return item.descriptorSet == key.first && item.binding == key.second; }); const bool array = binding != shader.bindings.end() && binding->role == ShaderRecompiler::DescriptorRole::GuestBuffers; const auto [view, first] = bufferViews.emplace(key, decoration.aliased); @@ -659,7 +675,7 @@ std::set ValidateShaders(std::span shaders, const bool geometry = state.stages.path == ShaderPath::Vertex && !state.rectList && shaders.size() == 3u; const bool patches = BarycentricPatches(state, shaders) && shaders.back().program != nullptr && shaders.back().program->barycentricEmulation.active; Require(geometry || shaders.size() == (tessellation || state.rectList || patches ? 4u : 2u), "incorrect graphics stage count"); - if (patches) Require(state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, "emulated barycentrics through tessellation require a triangle list"); + if (patches) Require(state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, "emulated barycentrics through tessellation require a triangle list or strip"); if (geometry) { Require(shaders.back().program != nullptr && shaders.back().program->barycentricEmulation.active, "a geometry stage is generated only for emulated barycentrics"); Require(state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, "emulated barycentrics require triangle primitives"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index d8a2554f0d..5c920b2440 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -6,6 +6,7 @@ #include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/General.hpp" #include @@ -190,6 +191,37 @@ std::uint32_t inactiveVertexPointSize(const QueueState& queue) { } } +bool pointOutput(const QueueState& queue) { + const auto stages = find(queue.context, 0x2d5); + const auto primitive = find(queue.userConfig, 0x242, RegisterBank::UserConfig); + if (stages == queue.context.end()) return false; + if ((stages->second & 0x24u) == 0) return primitive != queue.userConfig.end() && primitive->second == 1u; + const auto output = find(queue.context, 0x29b); + return (stages->second & 0x24u) == 0x20u && output != queue.context.end() && output->second == 0u; +} + +void decodePointSize(const QueueState& queue, State& result) { + const auto& cx = queue.context; + const auto size = read(cx, 0x280); + const auto limits = read(cx, 0x281); + const bool fromVertex = (result.paClVsOutCntl & (1u << 16u)) != 0; + Require(!fromVertex || (result.paClVsOutCntl & (1u << 21u)) != 0, "USE_VTX_POINT_SIZE without VS_OUT_MISC_VEC_ENA is unsupported"); + Require(fromVertex || (size & 0xffffu) == size >> 16u, "points whose PA_SU_POINT_SIZE width and height differ are unsupported"); + Require(fromVertex || (size & 0xffffu) != 0, "points of size 0 (PA_SU_POINT_SIZE 0) are unsupported"); + Require((limits & 0xffffu) <= limits >> 16u, "PA_SU_POINT_MINMAX with a minimum above its maximum is unsupported"); + Require(!fromVertex || (limits >> 16u) != 0, "a vertex point size clamped to 0 (PA_SU_POINT_MINMAX maximum 0) is unsupported"); + result.pointSize = static_cast(size & 0xffffu) / 8.0f; + result.pointSizeMin = static_cast(limits & 0xffffu) / 8.0f; + result.pointSizeMax = static_cast(limits >> 16u) / 8.0f; + const auto interpolation = find(cx, 0x1b5); + const auto inputs = find(cx, 0x1b6); + if (interpolation == cx.end() || (interpolation->second & 2u) == 0 || inputs == cx.end()) return; + for (std::uint32_t input = 0; input < (inputs->second & 0x3fu); ++input) { + const auto control = find(cx, 0x191 + input); + Require(control == cx.end() || (control->second & 0x820000u) == 0, "point sprite texture coordinates (SPI_PS_INPUT_CNTL PT_SPRITE_TEX) are not implemented"); + } +} + constexpr std::uint32_t DepthControlMask = ~0x007007f0u; // EXEC_ON_HIER_FAIL / EXEC_ON_NOOP / EXEC_IF_OVERLAPPED (bits 9, 10, 17) only force the pixel shader // to run, which it always does here. @@ -281,13 +313,35 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint const auto opValue = refMask >> 24u; auto reference = test; auto fixed = compare == 0 || compare == 7 ? 0u : face.compareMask; - const auto convert = [&](std::uint32_t op) { + std::uint32_t invertMask = 0; + std::uint32_t inverted = 0; + std::uint32_t logicMask = 0; + const auto logic = [](std::uint32_t op) { return op == 10 || op == 11 || op == 12 || op == 15; }; + const auto convert = [&](std::uint32_t op, bool testFailed) { if (face.writeMask == 0) return VK_STENCIL_OP_KEEP; char detail[112]; - if (opValExport && ((op == 4 && !opValReference) || op == 5 || op == 6 || op == 8 || op == 9)) { + if (opValExport && ((op == 4 && !opValReference) || (op >= 5 && op != 7))) { std::snprintf(detail, sizeof(detail), "AGC graphics: stencil operation %u with a pixel shader that exports the stencil operation value is unsupported", op); throw std::runtime_error(detail); } + if (logic(op)) { + const auto bits = (op == 11 || op == 12 ? opValue : ~opValue) & face.writeMask; + if (bits == 0) return VK_STENCIL_OP_KEEP; + if (logicMask != 0 && logicMask != bits) { + std::snprintf(detail, sizeof(detail), "AGC graphics: stencil logic operations on write masks 0x%02x and 0x%02x are unsupported", logicMask, bits); + throw std::runtime_error(detail); + } + logicMask = bits; + if (op != 11) return op == 10 ? VK_STENCIL_OP_ZERO : VK_STENCIL_OP_INVERT; + if (testValExport) throw std::runtime_error("AGC graphics: stencil OR is unsupported with a pixel shader that exports the stencil test value"); + if ((~reference & fixed & bits) != 0) { + std::snprintf(detail, sizeof(detail), "AGC graphics: stencil OR of 0x%02x against test value 0x%02x (masks 0x%02x/0x%02x) is unsupported", opValue, test, face.compareMask, face.writeMask); + throw std::runtime_error(detail); + } + reference |= bits; + fixed |= bits; + return VK_STENCIL_OP_REPLACE; + } switch (op) { case 0: return VK_STENCIL_OP_KEEP; case 1: return VK_STENCIL_OP_ZERO; @@ -304,6 +358,16 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint } const auto value = op == 2 ? 0xffu : op == 3 ? test : opValue; if (((reference ^ value) & fixed & face.writeMask) != 0) { + const auto compared = test & face.compareMask; + const bool passKnown = compare == 2 || (compare == 3 && compared == face.compareMask) || (compare == 6 && compared == 0u); + const bool failKnown = compare == 5 || (compare == 1 && compared == 0u) || (compare == 4 && compared == face.compareMask); + const bool testValueKnown = testFailed ? failKnown : passKnown; + const auto flipped = (test ^ value) & face.writeMask; + if (testValueKnown && (face.writeMask & ~face.compareMask) == 0 && (invertMask == 0 || invertMask == flipped)) { + invertMask = flipped; + ++inverted; + return VK_STENCIL_OP_INVERT; + } std::snprintf(detail, sizeof(detail), "AGC graphics: stencil replacement 0x%02x against test value 0x%02x (masks 0x%02x/0x%02x) is unsupported", value, test, face.compareMask, face.writeMask); throw std::runtime_error(detail); } @@ -326,37 +390,41 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint throw std::runtime_error(detail); } }; - face.failOp = convert(ops & 0xfu); - face.passOp = convert((ops >> 4u) & 0xfu); - face.depthFailOp = convert((ops >> 8u) & 0xfu); + face.failOp = convert(ops & 0xfu, true); + face.passOp = convert((ops >> 4u) & 0xfu, false); + face.depthFailOp = convert((ops >> 8u) & 0xfu, false); + if (invertMask != 0) { + const auto active = ((ops & 0xfu) != 0u) + (((ops >> 4u) & 0xfu) != 0u) + (((ops >> 8u) & 0xfu) != 0u); + if (inverted != active) throw std::runtime_error("AGC graphics: a stencil replacement drawn as an inversion beside another stencil operation is unsupported"); + face.writeMask = invertMask; + } + if (logicMask != 0) { + const auto other = [&](std::uint32_t op) { return op != 0 && !logic(op); }; + if ((other(ops & 0xfu) || other((ops >> 4u) & 0xfu) || other((ops >> 8u) & 0xfu)) && logicMask != face.writeMask) { + char detail[128]; + std::snprintf(detail, sizeof(detail), "AGC graphics: stencil operations 0x%03x combine a logic operation on write mask 0x%02x with write mask 0x%02x", ops & 0xfffu, logicMask, face.writeMask); + throw std::runtime_error(detail); + } + face.writeMask = logicMask; + } face.reference = reference; Require(!opValReference || compare == 0 || compare == 7 || face.compareMask == 0, "a stencil compare against the test value is unsupported with a pixel shader that exports the stencil operation value"); return face; } -void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValExport, bool opValExport, State& result) { - const bool opValReference = opValExport && StencilOperationValueReplaces(cx); - zero(cx, 0x000, 0x0000101cu, "depth copy, resummarize or decompress draws (DB_RENDER_CONTROL)"); - const bool depthClear = (read(cx, 0x000) & 1u) != 0; - const bool stencilClear = (read(cx, 0x000) & 2u) != 0; - const auto view = read(cx, 0x002); - zero(cx, 0x002, 0x3c000000u, "depth mips (DB_DEPTH_VIEW MIP_LEVEL)"); - zero(cx, 0x010, 0x000f1000u, "partially resident or mipmapped depth (DB_Z_INFO)"); - zero(cx, 0x011, 0x00001000u, "partially resident stencil (DB_STENCIL_INFO)"); +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 zFormat = read(cx, 0x010) & 3u; - const bool stencil = (read(cx, 0x011) & 1u) != 0; - Require(zFormat != 2, "Z_24 depth is unsupported"); - if (zFormat == 0) depthControl &= ~6u; - if (!stencil) depthControl &= ~1u; + const auto stencilRegister = find(cx, 0x011); + const auto stencilInfo = stencilRegister == cx.end() ? 0u : stencilRegister->second; + const bool stencil = (stencilInfo & 1u) != 0; const auto base = [&](std::uint32_t low, std::uint32_t highOffset) { const auto high = find(cx, highOffset); return (high == cx.end() ? 0ull : static_cast(high->second & 0xffu) << 40u) | (static_cast(read(cx, low)) << 8u); }; - const bool depthReadOnly = (view & 0x01000000u) != 0; - const bool stencilReadOnly = (view & 0x02000000u) != 0; - Require(!stencilClear || (stencil && !stencilReadOnly), "stencil clear requires a writable stencil plane"); - Require(!stencilClear || !testValExport, "stencil clear with a pixel shader that exports the stencil test value is unsupported"); - Require(!depthClear || (zFormat != 0 && !depthReadOnly), "depth clear requires a writable depth plane"); DepthTarget depth{}; depth.samples = 1u << ((read(cx, 0x010) >> 2u) & 3u); depth.depthSwizzle = (read(cx, 0x010) >> 4u) & 31u; @@ -365,9 +433,6 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValEx depth.lastSlice = (view >> 13u) & 0x7ffu; depth.address = zFormat != 0 ? base(0x012, 0x01a) : 0; depth.stencilAddress = stencil ? base(0x013, 0x01b) : 0; - const bool depthWritten = zFormat != 0 && !depthReadOnly && (depthClear || (depthControl & 6u) == 6u || (read(cx, 0x000) & 0x60u) != 0); - Require(!depthWritten || base(0x014, 0x01c) == depth.address, "depth read and written at different addresses is unsupported"); - Require(!stencil || stencilReadOnly || base(0x015, 0x01d) == depth.stencilAddress, "stencil read and written at different addresses is unsupported"); if (depth.samples != 1u) { depth.pipeBankXor = static_cast(depth.address & 65535u); depth.address &= ~std::uint64_t{65535u}; @@ -379,18 +444,69 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValEx } const auto size = read(cx, 0x007); depth.extent = {(size & 0x3fffu) + 1u, ((size >> 16u) & 0x3fffu) + 1u}; - if (const auto slice = view & 0x1fffu; slice != 0) { + 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); + const std::string where(message); + Require(viewMip <= maxMip, "the " + where + " is beyond the surface's mips"); + Require(depth.samples == 1u, "multisampled " + where + " is unsupported"); + Require(depth.depthSwizzle == XorSwizzleMode(TextureTileMode::kZ64KBX), "the " + where + " uses a swizzle other than 64 KiB Z_X"); + const auto depthMips = ComputeElementMipLayout(TextureTileMode::kZ64KBX, zFormat == 1 ? 2u : 4u, depth.extent.width, depth.extent.height, maxMip + 1u); + const auto stencilMips = ComputeElementMipLayout(TextureTileMode::kZ64KBX, 1u, depth.extent.width, depth.extent.height, maxMip + 1u); + Require(zFormat == 0 || !depthMips.at(viewMip).tail, "rendering the " + where + " into the packed mip tail is unsupported"); + Require(!stencil || !stencilMips.at(viewMip).tail, "rendering the stencil of the " + where + " into the packed mip tail is unsupported"); + Require((read(cx, 0x010) & (1u << 29u)) == 0, "HTILE for the " + where + " is unsupported"); + if (const auto slice = view & 0x1fffu; slice != 0) { + if (depth.address != 0) depth.address += static_cast(slice) * ComputeSurfaceSize(depthMips, 1u); + if (depth.stencilAddress != 0) depth.stencilAddress += static_cast(slice) * ComputeSurfaceSize(stencilMips, 1u); + } + if (depth.address != 0) depth.address += depthMips.at(viewMip).tiledOffset; + if (depth.stencilAddress != 0) depth.stencilAddress += stencilMips.at(viewMip).tiledOffset; + depth.extent = {depthMips.at(viewMip).width, depthMips.at(viewMip).height}; + } else if (const auto slice = view & 0x1fffu; slice != 0) { if (depth.address != 0) depth.address += static_cast(slice) * DepthSliceBytes(depth.extent, zFormat == 1 ? 2u : 4u); if (depth.stencilAddress != 0) depth.stencilAddress += static_cast(slice) * DepthSliceBytes(depth.extent, 1u); } depth.format = zFormat == 1 ? (stencil ? VK_FORMAT_D16_UNORM_S8_UINT : VK_FORMAT_D16_UNORM) : (stencil ? VK_FORMAT_D32_SFLOAT_S8_UINT : VK_FORMAT_D32_SFLOAT); - depth.clearDepth = readFloat(cx, 0x00b); - depth.clearStencil = static_cast(read(cx, 0x00a) & 0xffu); - depth.htileDepthOnly = (read(cx, 0x011) & (1u << 29u)) != 0u; + depth.htileDepthOnly = (stencilInfo & (1u << 29u)) != 0u; if (const auto htile = find(cx, 0x005); zFormat != 0 && htile != cx.end() && htile->second != 0 && (read(cx, 0x010) & (1u << 29u)) != 0) { depth.htileAddress = base(0x005, 0x01e) + static_cast(view & 0x1fffu) * HtileMetadataBytes(depth.extent); depth.htileStencil = stencil && !depth.htileDepthOnly; } + return depth; +} + +void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValExport, bool opValExport, State& result) { + const bool opValReference = opValExport && StencilOperationValueReplaces(cx); + zero(cx, 0x000, 0x0000101cu, "depth copy, resummarize or decompress draws (DB_RENDER_CONTROL)"); + const bool depthClear = (read(cx, 0x000) & 1u) != 0; + const auto view = read(cx, 0x002); + zero(cx, 0x010, 0x00001000u, "partially resident depth (DB_Z_INFO)"); + zero(cx, 0x011, 0x00001000u, "partially resident stencil (DB_STENCIL_INFO)"); + const auto zFormat = read(cx, 0x010) & 3u; + const bool stencil = (read(cx, 0x011) & 1u) != 0; + const bool stencilClear = stencil && (read(cx, 0x000) & 2u) != 0; + Require(zFormat != 2, "Z_24 depth is unsupported"); + if (zFormat == 0) depthControl &= ~6u; + if (!stencil) depthControl &= ~1u; + const auto base = [&](std::uint32_t low, std::uint32_t highOffset) { + const auto high = find(cx, highOffset); + return (high == cx.end() ? 0ull : static_cast(high->second & 0xffu) << 40u) | (static_cast(read(cx, low)) << 8u); + }; + const bool depthReadOnly = (view & 0x01000000u) != 0; + const bool stencilReadOnly = (view & 0x02000000u) != 0; + Require(!stencilClear || !stencilReadOnly, "stencil clear requires a writable stencil plane"); + Require(!stencilClear || !testValExport, "stencil clear with a pixel shader that exports the stencil test value is unsupported"); + Require(!depthClear || (zFormat != 0 && !depthReadOnly), "depth clear requires a writable depth plane"); + const auto readAddress = zFormat != 0 ? base(0x012, 0x01a) : 0; + const auto stencilReadAddress = stencil ? base(0x013, 0x01b) : 0; + const bool depthWritten = zFormat != 0 && !depthReadOnly && (depthClear || (depthControl & 6u) == 6u || (read(cx, 0x000) & 0x60u) != 0); + Require(!depthWritten || base(0x014, 0x01c) == readAddress, "depth read and written at different addresses is unsupported"); + Require(!stencil || stencilReadOnly || base(0x015, 0x01d) == stencilReadAddress, "stencil read and written at different addresses is unsupported"); + auto depth = decodeDepthPlane(cx); + depth.clearDepth = readFloat(cx, 0x00b); + depth.clearStencil = static_cast(read(cx, 0x00a) & 0xffu); result.depth = depth; result.depthTest = (depthControl & 2u) != 0; result.depthWrite = result.depthTest && (depthControl & 4u) != 0 && !depthReadOnly; @@ -521,6 +637,11 @@ constexpr std::uint32_t CoveredCentroidIsCenter = 1u << 29u; constexpr std::uint32_t SampleZeroRefusedInputs = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCentroid) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::LinearCentroid) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage); constexpr std::uint32_t CentroidInputs = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCentroid) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::LinearCentroid); +bool staticAnchors(const Registers& cx, std::uint32_t depthSamples) { + const auto eqaa = find(cx, 0x201); + return eqaa != cx.end() && (eqaa->second & 7u) == static_cast(std::countr_zero(depthSamples)) && (eqaa->second & 0x100000u) != 0; +} + bool overrasterized(const Registers& cx, std::uint32_t samples) { const auto eqaa = find(cx, 0x201); const auto mode = find(cx, 0x292); @@ -536,6 +657,25 @@ bool sampleCoverageSupported(const QueueState& queue, std::uint32_t samples, boo return (inputs & PixelSampleCoverageInput) == 0u || (!perSampleShading && (inputs & PixelSampleRateInputs) == 0u); } +bool pixelRateInputsKept(const QueueState& queue, const State& state) { + using ShaderRecompiler::PixelInput; + using ShaderRecompiler::PixelInputBit; + constexpr std::uint32_t numbered = PixelInputBit(PixelInput::Ancillary) | PixelInputBit(PixelInput::SampleCoverage); + constexpr std::uint32_t located = PixelInputBit(PixelInput::PerspectiveSample) | PixelInputBit(PixelInput::PerspectiveCentroid) | PixelInputBit(PixelInput::PerspectivePullModel) | PixelInputBit(PixelInput::LinearSample) | PixelInputBit(PixelInput::LinearCentroid) | PixelInputBit(PixelInput::PositionZ) | PixelInputBit(PixelInput::PositionW); + const bool centred = state.customSampleLocations && std::ranges::all_of(state.sampleLocations, [](std::uint8_t location) { return location == 0u; }); + const auto moving = numbered | (centred ? 0u : located); + if (PixelProgramSkipped(queue)) return true; + const auto ena = find(queue.context, 0x1b3); + const auto addr = find(queue.context, 0x1b4); + if (ena == queue.context.end() || addr == queue.context.end()) return true; + const auto inputs = ena->second & addr->second; + if ((inputs & moving) != 0u) return false; + if (centred || (inputs & (PixelInputBit(PixelInput::PositionX) | PixelInputBit(PixelInput::PositionY))) == 0u) return true; + const auto position = find(queue.context, 0x1b8); + const auto control = position == queue.context.end() ? 0u : position->second; + return (control & 0x100000u) != 0u || ((control >> 16u) & 3u) == 0u; +} + bool coveredCentroidSupported(const QueueState& queue) { const auto config = find(queue.context, 0x2f8); if (config == queue.context.end() || (config->second & CoveredCentroidIsCenter) == 0 || PixelProgramSkipped(queue)) return true; @@ -625,15 +765,19 @@ void rasterizeSampleZero(State& state) { state.sampleLocationGrid = sampleZero[8] != sampleZero[0] || sampleZero[16] != sampleZero[0] || sampleZero[24] != sampleZero[0]; } +bool documentedRop(std::uint32_t rop) { + return (rop >> 4u) == (rop & 0xfu) || ((rop >> 2u) & 0x33u) == (rop & 0x33u); +} + bool colorControlSupported(std::uint32_t colorControl, bool hasColorTarget) { colorControl &= ~1u; - return colorControl == 0xcc0010u || ((colorControl >> 4u) & 7u) == 0u || (!hasColorTarget && (colorControl & ~0x70u) == 0xcc0000u); + return ((colorControl & ~0xff0000u) == 0x10u && documentedRop((colorControl >> 16u) & 0xffu)) || ((colorControl >> 4u) & 7u) == 0u || (!hasColorTarget && (colorControl & ~0x70u) == 0xcc0000u); } std::string colorControlMessage(std::uint32_t colorControl) { static constexpr const char* modes[8] = {"disable", "normal", "eliminate fast clear", "resolve", "decompress", "FMASK decompress", "DCC decompress", "reserved"}; char text[160]; - std::snprintf(text, sizeof(text), "AGC graphics: only normal color rendering with copy ROP is supported (CB_COLOR_CONTROL 0x%08x, mode %s)", colorControl, modes[(colorControl >> 4u) & 7u]); + std::snprintf(text, sizeof(text), "AGC graphics: only normal color rendering with a ROP3 code of the source or brush form is supported (CB_COLOR_CONTROL 0x%08x, mode %s)", colorControl, modes[(colorControl >> 4u) & 7u]); return text; } @@ -838,6 +982,7 @@ struct DecodedColorFormat { }; const char* PackedColorTargetName(ShaderRecompiler::ColorExportPacking packing) { + if (packing == ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4) return "4_4_4_4 unorm"; return packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 ? "11_11_10 float" : "10_11_11 unorm"; } @@ -851,7 +996,7 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number, }; const bool alternate = swap == 1; const auto single = [&](VkFormat vkFormat, std::uint32_t bytes) { return DecodedColorFormat{vkFormat, bytes, static_cast((0xe4u & ~3u) | swap)}; }; - if (swap > 1 && format != 1 && format != 2 && format != 4 && format != 7 && format != 10 && format != 12 && format != 16) return fail(); + if (swap > 1 && format != 1 && format != 2 && format != 4 && format != 7 && format != 10 && format != 12 && format != 16 && format != 19) return fail(); switch (format) { case 1: if (number == unorm) return single(VK_FORMAT_R8_UNORM, 1); @@ -891,8 +1036,8 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number, if (number == unorm) return {VK_FORMAT_R32_UINT, 4, 0xe4u, ShaderRecompiler::ColorExportPacking::Unorm10_11_11}; return {VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4}; case 7: - if (swap != 2 || number != floating) return fail(); - return {VK_FORMAT_R32_UINT, 4, 0xc6u, ShaderRecompiler::ColorExportPacking::Float11_11_10}; + if ((swap != 0 && swap != 2) || number != floating) return fail(); + return {VK_FORMAT_R32_UINT, 4, static_cast(swap == 2 ? 0xc6u : 0xe4u), ShaderRecompiler::ColorExportPacking::Float11_11_10}; case 9: // COLOR_2_10_10_10 keeps red in the low bits, the Vulkan A2B10G10R10 packing. if (number != unorm) return fail(); @@ -929,6 +1074,9 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number, case 17: if (swap != 0 || number != unorm) return fail(); return {VK_FORMAT_A1R5G5B5_UNORM_PACK16, 2, 0xc6u}; + case 19: + if (swap != 2 || number != unorm) return fail(); + return {VK_FORMAT_R16_UINT, 2, 0x1bu, ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4}; default: return fail(); } @@ -1003,6 +1151,21 @@ std::array QuadSampleMasks(const State& state, const SampleOrd return {mask, mask, mask, mask}; } +VkLogicOp LogicOpFromRop(std::uint8_t rop) { + if (!documentedRop(rop)) { + char text[96]; + std::snprintf(text, sizeof(text), "AGC graphics: ROP3 code 0x%02x combines source and brush, which is not implemented", rop); + throw std::runtime_error(text); + } + static constexpr std::array operations{ + VK_LOGIC_OP_CLEAR, VK_LOGIC_OP_NOR, VK_LOGIC_OP_AND_INVERTED, VK_LOGIC_OP_COPY_INVERTED, + VK_LOGIC_OP_AND_REVERSE, VK_LOGIC_OP_INVERT, VK_LOGIC_OP_XOR, VK_LOGIC_OP_NAND, + VK_LOGIC_OP_AND, VK_LOGIC_OP_EQUIVALENT, VK_LOGIC_OP_NO_OP, VK_LOGIC_OP_OR_INVERTED, + VK_LOGIC_OP_COPY, VK_LOGIC_OP_OR_REVERSE, VK_LOGIC_OP_OR, VK_LOGIC_OP_SET}; + const std::uint32_t index = (rop & 3u) | ((rop >> 4u) & 4u) | ((rop >> 4u) & 8u); + return operations[index]; +} + std::array SampleLocations(const State& state) { Require(state.samples == 1 || state.samples == 2 || state.samples == 4 || state.samples == 8, "invalid sample count"); std::array locations{}; @@ -1029,7 +1192,8 @@ std::array PixelSampleLocations(const State& state, const Shad std::array locations{}; const bool sampleBarycentrics = (pixel.inputAddr & (PixelInputBit(PixelInput::PerspectiveSample) | PixelInputBit(PixelInput::LinearSample))) != 0u; const bool samplePosition = !state.perSampleShading && (pixel.posX || pixel.posY) && pixel.positionLocation == ShaderRecompiler::PixelPositionLocation::Sample; - if (!sampleBarycentrics && !samplePosition) return locations; + const bool mergedCentres = pixel.targetMerge.samples > 1u && (pixel.inputAddr & (PixelInputBit(PixelInput::PerspectiveCenter) | PixelInputBit(PixelInput::LinearCenter))) != 0u; + if (!sampleBarycentrics && !samplePosition && !mergedCentres) return locations; const auto samples = state.perSampleShading ? state.samples : 1u; for (std::uint32_t pixelIndex = 0; pixelIndex < 4u; ++pixelIndex) { for (std::uint32_t sample = 0; sample < samples; ++sample) { @@ -1044,6 +1208,15 @@ std::array PixelSampleLocations(const State& state, const Shad return locations; } +void ApplyPointSize(const State& state, ShaderRecompiler::ShaderVertexStageInfo& vertex) { + if (!state.vertexPoints && !(state.stages.mesh.has_value() && state.stages.mesh->outputPrimitiveSize == 1u)) return; + vertex.paClVsOutCntl |= state.paClVsOutCntl & (1u << 16u); + vertex.pointList = true; + vertex.pointSize = state.pointSize; + vertex.pointSizeMin = state.pointSizeMin; + vertex.pointSizeMax = state.pointSizeMax; +} + ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) { const auto value = read(queue.context, 0x2d5); const auto validate = [&](bool condition, const char* reason) { @@ -1183,6 +1356,27 @@ ReversedBlend TranslateReversedBlend(std::uint32_t blendControl, std::uint8_t co return {blend, routing->code}; } +void ApplyDepthBoundsEmulation(const Context& context, State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel) { + if (!state.depthBoundsTest || !state.depth || context.depthBounds) return; + ValidateDepthBounds(context, state); + Require(!context.depthRangeUnrestricted, "the depth bounds test without the depthBounds feature is emulated only where stored depth stays in [0, 1]"); + Require(state.depth->format == VK_FORMAT_D32_SFLOAT || state.depth->format == VK_FORMAT_D32_SFLOAT_S8_UINT, "the depth bounds test on a normalized depth target needs the depthBounds feature"); + Require(!state.depthTest && !state.depthWrite, "the depth bounds test with depth testing needs the depthBounds feature"); + Require(!pixel.posZ && !pixel.depthExportEnable, "the depth bounds test from a pixel program that reads or exports depth needs the depthBounds feature"); + const auto keepsOnFail = [](const VkStencilOpState& face) { return (face.writeMask & 0xffu) == 0 || (face.failOp == VK_STENCIL_OP_KEEP && face.depthFailOp == VK_STENCIL_OP_KEEP); }; + Require(!state.stencilTest || (keepsOnFail(state.stencilFront) && keepsOnFail(state.stencilBack)), "the depth bounds test with a stencil test that writes on a failed test needs the depthBounds feature"); + const bool upper = state.minDepthBounds <= 0.0f; + Require(upper || state.maxDepthBounds >= 1.0f, "the depth bounds test with both bounds inside (0, 1) needs the depthBounds feature"); + const float bound = upper ? state.maxDepthBounds : state.minDepthBounds; + state.depthBoundsTest = false; + state.depthTest = true; + state.depthCompare = upper ? VK_COMPARE_OP_GREATER_OR_EQUAL : VK_COMPARE_OP_LESS_OR_EQUAL; + state.depthBias = false; + state.depthBiasPerFace = false; + state.viewport.minDepth = bound; + state.viewport.maxDepth = bound; +} + 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; @@ -1218,7 +1412,11 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig); APS5_LOG_OUT_DEBUG("DecodeState primitive=%u path=%u vertexWave=%u", primitive, static_cast(result.stages.path), result.stages.vertexWaveSize); switch (primitive) { - case 1: Require(result.stages.mesh.has_value(), "point-list vertex rendering requires point-size output support"); result.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; break; + case 1: + Require(result.stages.mesh.has_value() || result.stages.path == ShaderPath::Vertex, "point lists are only drawn through the vertex and geometry paths"); + result.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + result.vertexPoints = result.stages.path == ShaderPath::Vertex; + break; case 2: result.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; break; case 3: result.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; break; case 7: @@ -1243,9 +1441,10 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { } APS5_LOG_OUT_DEBUG("Topology=%u", static_cast(result.topology)); result.primitiveRestart = read(queue.userConfig, 0x24b, RegisterBank::UserConfig) != 0 && !result.rectList && !result.quadList && result.topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; - zero(cx, 0x207, ~(LayerExports | ClipCullExports | inactiveVertexPointSize(queue)), "layer, viewport or auxiliary vertex exports"); + zero(cx, 0x207, ~(LayerExports | ClipCullExports | ((result.vertexPoints || (result.stages.mesh.has_value() && result.stages.mesh->outputPrimitiveSize == 1u)) ? 1u << 16u : inactiveVertexPointSize(queue))), "layer, viewport or auxiliary vertex exports"); Require(std::popcount(read(cx, 0x207) & 0xffffu) <= 8, "more than eight clip and cull distances are unsupported"); result.paClVsOutCntl = read(cx, 0x207); + if (result.vertexPoints || (result.stages.mesh.has_value() && result.stages.mesh->outputPrimitiveSize == 1u)) decodePointSize(queue, result); result.samples = decodeSamples(cx); Require(coveredCentroidSupported(queue), "COVERED_CENTROID_IS_CENTER with active or unspecified centroid inputs is not implemented"); decodeSampleLocations(cx, result); @@ -1288,16 +1487,16 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { zero(cx, 0x292, ScanModeMask & ~1u, "scan conversion mode"); if (depthKeepsSampleZero && (read(cx, 0x292) & 1u) == 0) { const bool filled = result.topology != VK_PRIMITIVE_TOPOLOGY_POINT_LIST && result.topology != VK_PRIMITIVE_TOPOLOGY_LINE_LIST && result.topology != VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; - const auto sampleInputs = read(cx, 0x1b3) & read(cx, 0x1b4) & (ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage)); - Require(filled && !result.alphaToCoverage && (PixelProgramSkipped(queue) || sampleInputs == 0) && (read(cx, 0x203) & 0x100u) == 0 && (read(cx, 0x201) & 0x0f0c0000u) == 0, "a one-sample depth target under a multisampled rasterizer without MSAA_ENABLE is unsupported for points or lines and with alpha-to-coverage, a sample index or coverage input, a sample-mask export, or DB_EQAA Z interpolation or overrasterization"); + const auto sampleIndex = (read(cx, 0x293) & PixelShaderIteratesSamples) != 0u ? ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary) : 0u; + const auto sampleInputs = read(cx, 0x1b3) & read(cx, 0x1b4) & (sampleIndex | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage)); + Require(filled && !result.alphaToCoverage && (PixelProgramSkipped(queue) || sampleInputs == 0) && (read(cx, 0x203) & 0x100u) == 0 && (read(cx, 0x201) & 0x0f080000u) == 0, "a one-sample depth target under a multisampled rasterizer without MSAA_ENABLE is unsupported for points or lines and with alpha-to-coverage, a sample index input under PS_ITER_SAMPLE, a coverage input, a sample-mask export, or DB_EQAA source Z interpolation or overrasterization"); result.samples = 1; result.customSampleLocations = false; result.sampleLocations.fill(0); result.sampleLocationGrid = false; } if (result.samples > 1 && (read(cx, 0x292) & 1u) == 0) { - const bool filled = result.topology != VK_PRIMITIVE_TOPOLOGY_POINT_LIST && result.topology != VK_PRIMITIVE_TOPOLOGY_LINE_LIST && result.topology != VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; - Require(filled && !result.alphaToCoverage, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points, lines or alpha-to-coverage"); + Require(RasterPrimitiveOf(result) != ShaderRecompiler::RasterPrimitive::Point && !result.alphaToCoverage, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points or alpha-to-coverage"); if (const auto eqaa = find(cx, 0x201); result.depth && eqaa != cx.end() && (eqaa->second & 0x0f0c0000u) != 0) throw std::runtime_error(zeroMessage(0x201, eqaa->second, "a multisampled depth target rasterized without MSAA_ENABLE with Z interpolation or overrasterization (DB_EQAA)")); if (const auto control = find(cx, 0x203); result.depth && control != cx.end() && (control->second & 0x100u) != 0) throw std::runtime_error(zeroMessage(0x203, control->second, "a multisampled depth target rasterized without MSAA_ENABLE with a sample-mask export")); result.customSampleLocations = true; @@ -1331,7 +1530,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { if (auto reason = sampleMaskRejection(queue, read(cx, 0x2f8), read(cx, 0x30e), read(cx, 0x30f)); !reason.empty()) throw std::runtime_error(reason); const auto viewportControl = read(cx, 0x206); if (viewportControl != 0x43fu) throw std::runtime_error(vteMessage(viewportControl)); - zero(cx, 0x204, ClipControlMask, "unsupported PA_CL_CLIP_CNTL flags"); + zero(cx, 0x204, ClipControlMask, "a PA_CL_CLIP_CNTL flag"); result.depthClamp = (read(cx, 0x204) & 0x0c000000u) != 0; result.negativeOneToOne = (read(cx, 0x204) & 0x80000u) == 0; const auto raster = read(cx, 0x205); @@ -1368,6 +1567,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { // CB_COLOR_CONTROL mode 0 disables color writes, which only matters when a target is written. if (const auto colorControl = read(cx, 0x202); !colorControlSupported(colorControl, result.hasColorTarget)) throw std::runtime_error(colorControlMessage(colorControl)); + if (const auto colorControl = read(cx, 0x202); ((colorControl >> 4u) & 7u) == 1u) result.colorRop = static_cast(colorControl >> 16u); zero(cx, 0x1c4, zFormatSupported(read(cx, 0x1c4)) ? 0u : ~0u, "depth or sample-mask export"); const auto exportFormat = result.hasColorTarget ? read(cx, 0x1c5) : 0u; APS5_LOG_OUT_DEBUG("Export format=%u", exportFormat); @@ -1386,9 +1586,10 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { color.uintExport = slotExport == 7; if (color.samples == 1 && result.samples > 1 && result.colors.empty() && !result.depth && (read(cx, 0x292) & 1u) == 0) { const auto sampleInputs = read(cx, 0x1b3) & read(cx, 0x1b4) & (ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage)); - Require(!result.perSampleShading && sampleInputs == 0 && (read(cx, 0x203) & 0x100u) == 0, "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with PS_ITER_SAMPLE, a sample index or coverage input, or a sample-mask export"); + Require(sampleInputs == 0 && (read(cx, 0x203) & 0x100u) == 0, "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with a sample index or coverage input, or a sample-mask export"); result.samples = 1; result.customSampleLocations = false; + result.perSampleShading = false; } if (color.samples > 1 && result.samples == 1 && result.colors.empty() && !result.depth && !result.alphaToCoverage && (read(cx, 0x292) & 1u) == 0) { Require(result.conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT || (read(cx, 0x1b3) & 0x44u) == 0, "conservative rasterization with centroid interpolation of a draw raised to a multisampled target is unsupported"); @@ -1396,10 +1597,14 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { result.customSampleLocations = true; result.sampleLocations.fill(0); } - const bool merged = color.samples == 1u && !result.depth && !result.perSampleShading && (result.colors.empty() || result.mergedCoverage) && overrasterized(cx, result.samples); - if (!merged && color.samples == 1 && result.colors.empty() && !result.depth && rasterizesSampleZeroOnly(cx, result)) rasterizeSampleZero(result); - Require((color.samples == result.samples && !result.mergedCoverage) || merged, "a color target's sample count differs from the rasterizer's"); + const bool merged = color.samples == 1u && color.coverageSamples == 1u && !result.depth && !result.perSampleShading && (result.colors.empty() || result.mergedCoverage) && overrasterized(cx, result.samples); + if (!merged && color.samples == 1 && color.coverageSamples == 1u && result.colors.empty() && !result.depth && rasterizesSampleZeroOnly(cx, result)) rasterizeSampleZero(result); + Require(color.coverageSamples == color.samples || color.coverageSamples == result.samples || (result.samples == 1u && (read(cx, 0x292) & 1u) == 0), "a one-fragment color target's sample count differs from the rasterizer's"); + const bool truncated = color.samples == 1u && result.samples > 1u && result.depth && result.depth->samples == result.samples && (result.colors.empty() || result.truncatedCoverage) && !overrasterized(cx, result.samples); + Require((color.samples == result.samples && !result.mergedCoverage && !result.truncatedCoverage) || merged || truncated, "a color target's sample count differs from the rasterizer's"); + Require(color.coverageSamples == color.samples || result.samples == 1u || staticAnchors(cx, result.depth->samples), "a one-fragment color target is supported only with static DB_EQAA anchors at the depth target's sample count"); result.mergedCoverage = merged; + result.truncatedCoverage = truncated; APS5_LOG_OUT_DEBUG("Color %u address=0x%llx extent=%ux%u bytes=%llu VkFormat=%u", slot, static_cast(color.address), color.extent.width, color.extent.height, static_cast(color.bytes), static_cast(color.format)); if (result.colors.empty()) { result.renderExtent = color.extent; @@ -1427,6 +1632,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { } result.quadSampleMask = decodeQuadSampleMask(cx, result.samples); result.pixelEarlyZ = !PixelProgramSkipped(queue) && PixelEarlyZ(read(cx, 0x203)); + result.pixelKill = !PixelProgramSkipped(queue) && (read(cx, 0x203) & 0x40u) != 0; if ((read(cx, 0x207) & LayerExports & ~result.layerExports & ~(1u << 21u)) != 0) { static bool reported = false; if (!reported) { @@ -1472,7 +1678,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { result.blends.assign(exportCount, VkPipelineColorBlendAttachmentState{}); std::array requiredBlendConstants{}; std::uint32_t requiredBlendMask = 0; - for (const auto& color : result.colors) { + for (auto& color : result.colors) { const auto slot = color.slot; const auto blend = read(cx, 0x1e0 + slot); APS5_LOG_OUT_DEBUG("Blend %u control=0x%x", slot, blend); @@ -1540,7 +1746,25 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { const auto slotExport = (exportFormat >> (4u * color.exportIndex)) & 0xfu; if (slotExport == 5 || slotExport == 6) throw std::runtime_error("AGC graphics: color export format " + std::to_string(slotExport) + " into " + packed + " target is unsupported"); Require(!state.blendEnable, "blending into " + packed + " color target is unsupported"); - Require((exportedMask & 7u) == 7u, "partial writes of " + packed + " color target are unsupported"); + const bool nibbles = color.packing == ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4; + const std::uint32_t packedMask = nibbles ? 0xfu : 7u; + Require(nibbles || (exportedMask & packedMask) == packedMask, "partial writes of " + packed + " color target are unsupported"); + if ((exportedMask & packedMask) != packedMask) { + Require(color.mipCount == 1u && color.depth == 1u && color.layers == 1u && color.arrayLastSlice == 0u, "partial writes of a mipmapped, volume or array " + std::string(PackedColorTargetName(color.packing)) + " color target are not implemented"); + Require(std::ranges::none_of(result.colors, [](const ColorTarget& other) { return other.partialWriteMask != 0; }), "partial writes of more than one packed color target are not implemented"); + color.partialWriteMask = PackedWriteMask(mapping, exportedMask); + state.colorWriteMask = 0; + if (color.samples > 1u) { + Require(sampleCoverageSupported(queue, result.samples, true), "SAMPLE_COVERAGE in a multisampled pixel shader that runs per sample, or reads sample-rate or ancillary inputs, is unsupported (the input holds the whole pixel's coverage, gl_SampleMaskIn only the invocation's samples)"); + if (!result.perSampleShading && !pixelRateInputsKept(queue, result)) { + const auto value = [&](std::uint32_t offset) { const auto it = queue.context.find(offset); return it == queue.context.end() ? 0u : it->second; }; + char detail[160]; + std::snprintf(detail, sizeof(detail), " (SPI_PS_INPUT_ENA 0x%x, SPI_PS_INPUT_ADDR 0x%x, SPI_BARYC_CNTL 0x%x, sample 1 location 0x%02x)", value(0x1b3), value(0x1b4), value(0x1b8), result.sampleLocations[1]); + Require(false, std::string("partial writes of a multisampled 4_4_4_4 unorm color target run the pixel shader once per sample, so a shader the guest runs once per pixel cannot read the ancillary word or, unless every sample sits at the pixel centre, centroid, sample-located or pull-model barycentrics, Z, 1/W or a position at the centroid or a sample: they would follow the invocation's sample") + detail); + } + result.perSampleShading = true; + } + } } if (state.blendEnable && !reversed && (secondSource(state.srcColorBlendFactor) || secondSource(state.dstColorBlendFactor) || secondSource(state.srcAlphaBlendFactor) || secondSource(state.dstAlphaBlendFactor))) { const auto addition = [](VkBlendOp op) { return op == VK_BLEND_OP_ADD || op == VK_BLEND_OP_SUBTRACT || op == VK_BLEND_OP_REVERSE_SUBTRACT; }; @@ -1557,7 +1781,14 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { for (std::uint32_t i = 0; i < 3; ++i) { if ((requiredBlendMask & (1u << i)) != 0) result.blendConstants[i] = requiredBlendConstants[i]; } - if (!result.colors.empty()) result.blend = result.blends[result.colors.front().exportIndex]; + Require((!result.mergedCoverage && !result.truncatedCoverage) || std::ranges::none_of(result.colors, [](const ColorTarget& color) { return color.partialWriteMask != 0; }), "partial writes of a 4_4_4_4 unorm color target under merged or truncated coverage (a one-sample target under a multisampled rasterizer) are not implemented"); + if (!result.colors.empty()) { + result.color = result.colors.front(); + result.blend = result.blends[result.colors.front().exportIndex]; + } + if (LogicOpFromRop(result.colorRop) != VK_LOGIC_OP_COPY) { + for (const auto& blend : result.blends) Require(blend.blendEnable == VK_FALSE, "a color logic operation together with blending is not implemented"); + } Require(result.reversedBlend == 0 || (exportSlots.size() == 1 && exportCount == 1), "blending into a color target with a reversed component order through a second source color needs that target to be the shader's only color export"); if (result.dualSourceBlend) { Require(exportSlots.size() == 2 && exportSlots[0] == 0 && exportSlots[1] == 1 && exportCount == 1, "dual-source blending needs pixel exports to MRT slots 0 and 1 and color writes to slot 0 alone"); @@ -1584,6 +1815,50 @@ std::array ExportPackings(const State& return packings; } +std::uint16_t PackedWriteMask(std::uint8_t componentMapping, std::uint32_t exportedMask) { + std::uint16_t mask = 0; + for (std::uint32_t component = 0; component < 4; ++component) { + if (((exportedMask >> ((componentMapping >> (2u * component)) & 3u)) & 1u) != 0) mask = static_cast(mask | (0xfu << (4u * component))); + } + return mask; +} + +GuestTextureResource TargetMergeSurface(const ColorTarget& color) { + auto surface = SurfaceForTarget(color); + if (color.samples > 1u) { + Require(surface.baseAddress == color.surfaceAddress, "a merged multisampled color target does not start at its surface"); + surface.dimension = TextureDimension::k2DMsaa; + surface.samples = color.samples; + } + return surface; +} + +bool DepthStencilWrites(const State& state) { + if (!state.depth) return false; + const auto writes = [](const VkStencilOpState& face) { return (face.writeMask & 0xffu) != 0 && (face.failOp != VK_STENCIL_OP_KEEP || face.passOp != VK_STENCIL_OP_KEEP || face.depthFailOp != VK_STENCIL_OP_KEEP); }; + return state.depthWrite || (state.stencilTest && (writes(state.stencilFront) || writes(state.stencilBack))); +} + +bool DepthStencilTests(const State& state) { + return state.depth.has_value() && (state.depthTest || state.stencilTest || state.depthBoundsTest); +} + +ShaderRecompiler::TargetMerge ExportTargetMerge(const State& state) { + ShaderRecompiler::TargetMerge merge; + for (const auto& color : state.colors) { + if (color.partialWriteMask == 0) continue; + Require(merge.mask == 0 && color.exportIndex < 8u && color.samples <= 8u, "a draw merges into one packed color target at most"); + Require(color.samples == 1u || state.perSampleShading, "a merged multisampled color target needs per-sample shading"); + merge.target = static_cast(color.exportIndex); + merge.mask = color.partialWriteMask; + merge.samples = static_cast(color.samples); + merge.descriptor = EncodeTextureResource(TargetMergeSurface(color)); + merge.depthStencilWrites = DepthStencilWrites(state); + merge.depthStencilTests = DepthStencilTests(state); + } + return merge; +} + bool PartialSampleMask(const State& state) { const auto full = (1u << state.samples) - 1u; return std::ranges::any_of(state.quadSampleMask, [&](std::uint16_t mask) { return (mask & full) != full; }); @@ -1688,7 +1963,7 @@ GuestTextureResource SurfaceForTarget(const ColorTarget& color) { surface.dstSelY = 5; surface.dstSelZ = 6; surface.dstSelW = 7; - surface.dccAddress = color.dccAddress; + surface.dccAddress = color.dccAddress != 0 ? color.dccAddress : color.chainDccAddress; surface.dccAlphaOnMsb = color.dccAlphaOnMsb; surface.dccPipeAligned = color.dccPipeAligned; return surface; @@ -1706,10 +1981,12 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo const auto decoded = DecodeColorFormat(format, number, swap); const auto attrib = read(cx, 0x31d + stride); zero(cx, 0x31d + stride, ~0x3f000u, "color destination alpha override or other CB_COLOR_ATTRIB modes"); - color.samples = 1u << ((attrib >> 12u) & 7u); - Require(color.samples <= 8u, "color targets with more than eight samples are unsupported"); - Require((1u << ((attrib >> 15u) & 3u)) == color.samples, "color targets with fewer fragments than samples (EQAA) are unsupported"); - const auto multisampleInfo = color.samples > 1 ? 0x0c006000u : 0u; + color.coverageSamples = 1u << ((attrib >> 12u) & 7u); + Require(color.coverageSamples <= 8u, "color targets with more than eight samples are unsupported"); + color.samples = 1u << ((attrib >> 15u) & 3u); + Require(color.samples <= color.coverageSamples, "color targets with more fragments than samples are unsupported"); + Require(color.samples == color.coverageSamples || color.samples == 1u, "color targets with more than one but fewer fragments than samples (EQAA) are unsupported"); + const auto multisampleInfo = color.coverageSamples > 1 ? 0x0c006000u : 0u; // ROUND_MODE (bit 18) only affects unorm rounding and packed float truncation. With DCC_ENABLE (bit 28) the target is written if ((info & ~(0x00039f7cu | 0x00040000u | 0x10000000u | 0x00002000u | multisampleInfo)) != 0) throw std::runtime_error("AGC graphics: color compression, DCC, endian conversion, nonstandard rounding or color optimization is unsupported (CB_COLOR_INFO 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%08x", info); return std::string(text); }() + ")"); Require((info & 0x8000u) != 0 || number <= 1u || number == 7 || number == 4 || number == 5, "unclamped normalized color is unsupported"); @@ -1801,6 +2078,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo color.packing = decoded.packing; if (color.packing != ShaderRecompiler::ColorExportPacking::None && (info & 0x10000000u) != 0) throw std::runtime_error("AGC graphics: DCC-compressed " + std::string(PackedColorTargetName(color.packing)) + " color targets are unsupported"); Require(color.packing != ShaderRecompiler::ColorExportPacking::Unorm10_11_11 || (info & 0x00040000u) == 0, "truncating (ROUND_MODE) 10_11_11 unorm color targets are unsupported"); + Require(color.packing != ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4 || (info & 0x00040000u) == 0, "truncating (ROUND_MODE) 4_4_4_4 unorm color targets are unsupported"); Require(color.packing != ShaderRecompiler::ColorExportPacking::Float11_11_10 || (info & 0x00040000u) != 0, "rounding (ROUND_MODE 0) 11_11_10 float color targets are unsupported"); for (std::uint32_t word = 0; word < 2; ++word) { const auto clear = find(cx, 0x323 + word + stride); @@ -1839,11 +2117,25 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo if (color.samples == 1) GuestMemory::CheckRange(reinterpret_cast(cmask), totalCmaskBytes, CmaskLayout::Alignment, true); } color.cmaskFastClear = (info & 0x2000u) != 0; + if (const auto fmask = find(cx, 0x321 + stride); color.samples > 1u && fmask != cx.end()) { + const auto high = find(cx, 0x3a0 + slot); + const auto fmaskHigh = high == cx.end() ? 0u : high->second; + Require((fmaskHigh & ~0xffu) == 0u, "invalid FMASK address extension"); + const auto address = (static_cast(fmaskHigh) << 40u) | (static_cast(fmask->second) << 8u); + color.fmaskPipeBankXor = static_cast(address & 0xffffu); + color.fmaskAddress = address - color.fmaskPipeBankXor; + } + } + if (color.coverageSamples > color.samples && (info & 0x4000u) != 0) { + const auto fmaskHigh = find(cx, 0x3a0 + slot); + color.fmaskAddress = (((fmaskHigh == cx.end() ? 0ull : static_cast(fmaskHigh->second & 0xffu)) << 40u) | (static_cast(read(cx, 0x321 + stride)) << 8u)) & ~0xffffull; + Require(color.fmaskAddress != 0, "a compressed one-fragment color target without an FMASK address is unsupported"); } if ((info & 0x10000000u) != 0) { + const auto dccHigh = find(cx, 0x3a8 + slot); + const auto dccBase = ((dccHigh == cx.end() ? 0ull : static_cast(dccHigh->second & 0xffu)) << 40u) | (static_cast(read(cx, 0x325 + stride)) << 8u); if (maxMip == 0) { - const auto dccHigh = find(cx, 0x3a8 + slot); - color.dccAddress = ((dccHigh == cx.end() ? 0ull : static_cast(dccHigh->second & 0xffu)) << 40u) | (static_cast(read(cx, 0x325 + stride)) << 8u); + color.dccAddress = dccBase; color.dccAlphaOnMsb = DccAlphaOnMsb(color.format, swap); color.dccPipeAligned = ((attrib3 >> 30u) & 1u) != 0; if (color.samples > 1) { @@ -1851,11 +2143,15 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo color.dccControl = read(cx, 0x31e + stride); if (!allowMultisampleDcc) Require((color.dccControl & 0x3ff80u) == 0, "multisampled DCC color transforms or lossy modes are not implemented"); } + } else if (!volume && color.arrayLastSlice == 0 && slice == 0) { + color.chainDccAddress = dccBase; + color.dccAlphaOnMsb = DccAlphaOnMsb(color.format, swap); + color.dccPipeAligned = ((attrib3 >> 30u) & 1u) != 0; } else { static bool reported = false; if (!reported) { reported = true; - std::fprintf(stderr, "[gpu] DCC keys of mipmapped color targets are ignored\n"); + std::fprintf(stderr, "[gpu] DCC keys of mipmapped array and 3D color targets are ignored\n"); } } } @@ -1896,28 +2192,20 @@ std::optional DecodeDepthCopyPass(const QueueState& queue) { Require((control->second & ~0xf84u) == 0, "DB depth copy combined with a stencil copy, clear, resummarize or decompress"); Require((read(cx, 0x200) & 0xfu) == 0, "DB depth copy with depth or stencil tests"); Require((read(cx, 0x010) & 3u) == 3u, "DB depth copy from a depth plane that is not Z_32_FLOAT"); - Require(ColorWriteMask(cx) == 1u, "DB depth copy into anything but the red channel of color target 0"); const auto viewRegister = find(cx, 0x002); - const auto view = viewRegister == cx.end() ? 0u : viewRegister->second; - Require((read(cx, 0x010) & 0x000f1000u) == 0 && (view & 0x3c000000u) == 0, "DB depth copy from a mipmapped or partially resident depth plane"); + Require((read(cx, 0x010) & 0x000f1000u) == 0 && ((viewRegister == cx.end() ? 0u : viewRegister->second) & 0x3c000000u) == 0, "DB depth copy from a mipmapped or partially resident depth plane"); + Require(ColorWriteMask(cx) == 1u, "DB depth copy into anything but the red channel of color target 0"); + Require((read(cx, 0x1e0) & (1u << 30u)) == 0, "DB depth copy with color target 0 blending is unsupported"); DepthCopyPass pass{}; - const auto high = find(cx, 0x01a); - pass.depthAddress = (high == cx.end() ? 0ull : static_cast(high->second & 0xffu) << 40u) | (static_cast(read(cx, 0x012)) << 8u); - const auto size = read(cx, 0x007); - pass.extent = {(size & 0x3fffu) + 1u, ((size >> 16u) & 0x3fffu) + 1u}; - pass.samples = 1u << ((read(cx, 0x010) >> 2u) & 3u); - if (pass.samples != 1u) pass.depthAddress &= ~std::uint64_t{65535u}; - if (const auto slice = view & 0x1fffu; slice != 0) pass.depthAddress += static_cast(slice) * DepthSliceBytes(pass.extent, 4u); + pass.source = decodeDepthPlane(cx); const auto maskLow = find(cx, 0x30e); const auto maskHigh = find(cx, 0x30f); Require((maskLow == cx.end() || maskLow->second == 0xffffffffu) && (maskHigh == cx.end() || maskHigh->second == 0xffffffffu), "DB depth copy with a sample mask is unsupported"); pass.sample = (control->second & 0x80u) != 0 ? 0u : (control->second >> 8u) & 0xfu; - Require(pass.sample < pass.samples, "DB depth copy of a sample the depth plane does not have"); + Require(pass.sample < pass.source.samples, "DB depth copy of a sample the depth plane does not have"); pass.color = DecodeColorBuffer(cx, 0); Require(pass.color.format == VK_FORMAT_R32_SFLOAT && pass.color.samples == 1 && pass.color.mip == 0 && pass.color.layers == 1, "DB depth copy into a color target that is not a single-sample R32 float"); - Require(pass.color.extent.width == pass.extent.width && pass.color.extent.height == pass.extent.height, "DB depth copy into a color target of another size"); - Require(pass.color.cmaskAddress == 0, "DB depth copy into a color target with CMASK fast clears is unsupported"); - Require(drawCovers(cx, pass.extent), "DB depth copy over part of the depth surface"); + Require(pass.color.extent.width == pass.source.extent.width && pass.color.extent.height == pass.source.extent.height, "DB depth copy into a color target of another size"); return pass; } @@ -2006,15 +2294,16 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { if (value(queue.userConfig, 0x242, primitive) && (primitive & 0x3fu) != 1 && (primitive & 0x3fu) != 2 && (primitive & 0x3fu) != 3 && (primitive & 0x3fu) != 4 && (primitive & 0x3fu) != 5 && (primitive & 0x3fu) != 6 && (primitive & 0x3fu) != 21) return "AGC graphics: primitive restart is only supported for point, line and triangle topologies"; if (value(cx, 0x103, resetIndex) && (resetIndex & 0xffffu) != 0xffffu) return "AGC graphics: primitive restart index other than all ones is unsupported"; } - if (auto reason = nonzero(cx, 0x207, ~(LayerExports | ClipCullExports | inactiveVertexPointSize(queue)), "layer, viewport or auxiliary vertex exports"); !reason.empty()) return reason; + if (auto reason = nonzero(cx, 0x207, ~(LayerExports | ClipCullExports | (pointOutput(queue) ? 1u << 16u : inactiveVertexPointSize(queue))), "layer, viewport or auxiliary vertex exports"); !reason.empty()) return reason; if (value(cx, 0x207, word) && std::popcount(word & 0xffffu) > 8) return require(false, "more than eight clip and cull distances are unsupported"); if (value(cx, 0x200, word)) { const auto renderControl = find(cx, 0x000); const bool depthClear = renderControl != cx.end() && (renderControl->second & 1u) != 0; - const bool stencilClear = renderControl != cx.end() && (renderControl->second & 2u) != 0; + std::uint32_t stencilInfo = 0; + const bool stencilClear = renderControl != cx.end() && (renderControl->second & 2u) != 0 && !(value(cx, 0x011, stencilInfo) && (stencilInfo & 1u) == 0); if (stencilClear && !depthPlanesAbsent(cx)) { - std::uint32_t stencil = 0, view = 0; - if ((value(cx, 0x011, stencil) && (stencil & 1u) == 0) || (value(cx, 0x002, view) && (view & 0x02000000u) != 0)) return require(false, "stencil clear requires a writable stencil plane"); + std::uint32_t view = 0; + if (value(cx, 0x002, view) && (view & 0x02000000u) != 0) return require(false, "stencil clear requires a writable stencil plane"); } if (depthClear && !depthPlanesAbsent(cx)) { std::uint32_t z = 0, view = 0; @@ -2064,7 +2353,7 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { if (auto reason = sampleMaskRejection(queue, config, word, other); !reason.empty()) return reason; } if (value(cx, 0x206, word) && word != 0x43fu) return vteMessage(word); - if (auto reason = nonzero(cx, 0x204, ClipControlMask, "unsupported PA_CL_CLIP_CNTL flags"); !reason.empty()) return reason; + if (auto reason = nonzero(cx, 0x204, ClipControlMask, "a PA_CL_CLIP_CNTL flag"); !reason.empty()) return reason; std::uint32_t targetMask = 0, shaderMask = 0; if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, ColorWriteMask(cx) != 0)) return colorControlMessage(word); if (auto reason = nonzero(cx, 0x1c4, zFormatSupported(zFormat) ? 0u : ~0u, "depth or sample-mask export"); !reason.empty()) return reason; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index a70da26def..ada06eab1e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -5,10 +5,12 @@ #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/SlimMutex.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" // For the declaration of PendingStorageOverlaps, defined below. #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" @@ -46,8 +48,8 @@ OwnedImage::~OwnedImage() { ReleaseImageMemory(context, memory, memoryBytes, memoryType); } -void OwnedImage::Allocate(const Context& device, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation) { - memory = AllocateImageMemory(device, allocation, mask, operation, &memoryType); +void OwnedImage::Allocate(const Context& device, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation, bool newImage) { + memory = AllocateImageMemory(device, allocation, mask, operation, &memoryType, newImage); memoryBytes = allocation.allocationSize; context.bufferPool = device.bufferPool; } @@ -117,10 +119,11 @@ std::atomic refreshesProved{0}; std::atomic untrackedCompares{0}; struct StorageTraffic { - std::mutex mutex; + AgcDriver::Mutex mutex; std::map> writeBacks; std::array, 4> uploads{}; std::map> uploadReasons; + std::map> drawnStores; std::uint64_t directWriteBacks = 0; std::chrono::steady_clock::time_point lastReport = std::chrono::steady_clock::now(); }; @@ -130,6 +133,14 @@ StorageTraffic& Traffic() { return traffic; } +struct DrawnPending { + std::size_t images = 0; + std::uint64_t bytes = 0; + double oldestMs = 0; +}; + +DrawnPending drawnPending(); + void reportStorageTraffic(StorageTraffic& traffic) { const auto now = std::chrono::steady_clock::now(); if (now - traffic.lastReport < std::chrono::seconds(10)) return; @@ -152,12 +163,19 @@ void reportStorageTraffic(StorageTraffic& traffic) { std::snprintf(text, sizeof(text), " %llu/%.1f", static_cast(totals.first), totals.second / 1048576.0); line += " " + reason + text; } + std::string drawnLine; + for (const auto& [reason, totals] : traffic.drawnStores) { + std::snprintf(text, sizeof(text), " %llu/%.1f", static_cast(totals.first), totals.second / 1048576.0); + drawnLine += " " + reason + text; + } + const auto drawn = drawnPending(); const auto take = [](std::atomic& counter) { return static_cast(counter.exchange(0, std::memory_order_relaxed)); }; const auto partialUploadCount = take(partialUploads), partialWriteBackCount = take(partialWriteBacks); const auto uploadMiB = take(partialUploadBytes) / 1048576.0, writeBackMiB = take(partialWriteBackBytes) / 1048576.0; - AgcDriver::ProfilePrint_nid_no_patch( "[storage] uploads by path (count/MiB, 10 s):%s; %llu write-backs GPU-direct, %llu stored nothing; block units: partial uploads %llu/%.1f, partial write-backs %llu/%.1f, %llu widened to every pending unit, units dropped %llu, superseded %llu, handed over at %llu clear-key refreshes (%llu units superseded), dropped unregistered %llu, kept over a key flip %llu%s, pretest skipped %llu, held for a queued draw read %llu, evicted stale %llu\n", line.c_str(), static_cast(traffic.directWriteBacks), take(emptyWriteBacks), partialUploadCount, uploadMiB, partialWriteBackCount, writeBackMiB, take(coalescedWriteBacks), take(unitsDropped), take(unitsSuperseded), take(clearHandOvers), take(handedOverSuperseded), take(unregisteredDropped), take(keyFlipKept), ShadowReport().c_str(), take(pretestSkipped), take(queuedReadHeld), take(staleEvicted)); + AgcDriver::ProfilePrint_nid_no_patch( "[storage] uploads by path (count/MiB, 10 s):%s; %llu write-backs GPU-direct, %llu stored nothing; block units: partial uploads %llu/%.1f, partial write-backs %llu/%.1f, %llu widened to every pending unit, units dropped %llu, superseded %llu, handed over at %llu clear-key refreshes (%llu units superseded), dropped unregistered %llu, kept over a key flip %llu%s, pretest skipped %llu, held for a queued draw read %llu, evicted stale %llu; drawn linear targets: stores by reason (count/MiB):%s, pending %zu images / %.1f MiB, oldest %.1f ms\n", line.c_str(), static_cast(traffic.directWriteBacks), take(emptyWriteBacks), partialUploadCount, uploadMiB, partialWriteBackCount, writeBackMiB, take(coalescedWriteBacks), take(unitsDropped), take(unitsSuperseded), take(clearHandOvers), take(handedOverSuperseded), take(unregisteredDropped), take(keyFlipKept), ShadowReport().c_str(), take(pretestSkipped), take(queuedReadHeld), take(staleEvicted), drawnLine.c_str(), drawn.images, drawn.bytes / 1048576.0, drawn.oldestMs); traffic.writeBacks.clear(); traffic.uploadReasons.clear(); + traffic.drawnStores.clear(); traffic.directWriteBacks = 0; } @@ -173,7 +191,7 @@ void countStorageUpload(std::size_t path, std::uint64_t bytes) { reportStorageTraffic(traffic); } -void countStorageWriteBack(std::uint64_t bytes, bool direct) { +void countStorageWriteBack(std::uint64_t bytes, bool direct, bool drawn = false) { if (!LookupOutcomes::Profiled()) return; auto& traffic = Traffic(); std::lock_guard lock(traffic.mutex); @@ -181,13 +199,18 @@ void countStorageWriteBack(std::uint64_t bytes, bool direct) { ++totals.first; totals.second += bytes; if (direct) ++traffic.directWriteBacks; + if (drawn) { + auto& stores = traffic.drawnStores[flushReason != nullptr ? flushReason : "other"]; + ++stores.first; + stores.second += bytes; + } reportStorageTraffic(traffic); } // Every storage image alive, for the fill HLE's cover check (StorageTexture::ClassifyFill): the // storage cache indexes surfaces by key, not by address range. Its mutex is a leaf. struct LiveImages { - std::mutex mutex; + AgcDriver::Mutex mutex; std::vector textures; }; @@ -280,7 +303,7 @@ ExpandImage CreateExpandImage(const Context& context, const GuestTextureResource VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = context.DeviceMemoryType(requirements.memoryTypeBits, requirements.size, "texture"); - result.owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory sRGB copy"); + result.owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory sRGB copy", true); Check(context.Function("vkBindImageMemory")(context.device, result.image, result.owned->memory, 0), "vkBindImageMemory sRGB copy"); result.bytes = requirements.size; return result; @@ -364,7 +387,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex allocation.allocationSize = requirements.size; allocationBytes = requirements.size; allocation.memoryTypeIndex = context.DeviceMemoryType(requirements.memoryTypeBits, requirements.size, "texture"); - owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory texture"); + owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory texture", true); Check(context.Function("vkBindImageMemory")(context.device, image, owned->memory, 0), "vkBindImageMemory"); const VkImage uploadImage = image; @@ -518,7 +541,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex else ++Profile().recordedUploads; if (profile) Profile().gpu += timer.lap(); if (dump) { - static std::mutex dumpMutex; + static AgcDriver::Mutex dumpMutex; static std::map dumped; std::lock_guard lock(dumpMutex); auto& count = dumped[descriptor.baseAddress]; @@ -640,7 +663,7 @@ Texture::Texture(const Context& context, const std::shared_ptr& allocation.allocationSize = requirements.size; allocationBytes = requirements.size; allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory repacked copy"); + owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory repacked copy", true); Check(context.Function("vkBindImageMemory")(context.device, image, owned->memory, 0), "vkBindImageMemory repacked copy"); const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u; @@ -687,6 +710,7 @@ Texture::Texture(const Context& context, const std::shared_ptr& imageInfo.arrayLayers = copyLayers; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.flags = SrgbTwinFormat(context, blockFormat) != VK_FORMAT_UNDEFINED ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0u; imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -698,7 +722,7 @@ Texture::Texture(const Context& context, const std::shared_ptr& allocation.allocationSize = requirements.size; allocationBytes = requirements.size; allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory block copy"); + owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory block copy", true); Check(context.Function("vkBindImageMemory")(context.device, image, owned->memory, 0), "vkBindImageMemory block copy"); const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u; @@ -716,6 +740,7 @@ Texture::Texture(const Context& context, const std::shared_ptr& viewRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount}; createFirstLayerView(descriptor, viewInfo); prepareBaseLevelView(descriptor, viewInfo); + prepareSrgbView(descriptor, viewInfo); } catch (...) { release(); throw; @@ -756,7 +781,7 @@ Texture::Texture(const Context& context, const std::shared_ptr& allocation.allocationSize = requirements.size; allocationBytes = requirements.size; allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory depth copy"); + owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory depth copy", true); Check(context.Function("vkBindImageMemory")(context.device, image, owned->memory, 0), "vkBindImageMemory depth copy"); const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u; @@ -1035,6 +1060,9 @@ Texture::Texture(const Context& context, const std::shared_ptr& } VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; viewInfo.image = source->Image(); + VkImageViewUsageCreateInfo usage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; + usage.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + viewInfo.pNext = &usage; viewInfo.viewType = ViewTypeFor(descriptor.dimension, viewLayerCount); viewInfo.format = vkFormat; viewFormat = vkFormat; @@ -1192,13 +1220,24 @@ VkBufferMemoryBarrier WholeBufferBarrier(VkBuffer buffer, VkAccessFlags from, Vk return barrier; } +VkFormat PackedStorageViewFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_R5G6B5_UNORM_PACK16: + case VK_FORMAT_A1R5G5B5_UNORM_PACK16: + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: + return VK_FORMAT_R16_UINT; + default: + return VK_FORMAT_UNDEFINED; + } +} + // Storage images cannot use sRGB formats; the shader works on the raw encoded values either way. // The answer is a property of the physical device, so it is computed once per format (every storage // image lookup asks: a live vkGetPhysicalDeviceFormatProperties call each time was measurable) and // remembered as VK_FORMAT_UNDEFINED when the format has no storage form. VkFormat StorageFormatOrUndefined(const Context& context, VkFormat format) { struct Table { - std::mutex mutex; + AgcDriver::Mutex mutex; std::unordered_map formats; }; static Table table; @@ -1215,7 +1254,10 @@ VkFormat StorageFormatOrUndefined(const Context& context, VkFormat format) { return (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; }; VkFormat storage = VK_FORMAT_UNDEFINED; - if (supports(format)) { + const auto packed = PackedStorageViewFormat(format); + if (packed != VK_FORMAT_UNDEFINED) { + if (supports(packed)) storage = format; + } else if (supports(format)) { storage = format; } else { VkFormat linear = VK_FORMAT_UNDEFINED; @@ -1312,7 +1354,10 @@ VkFormat SrgbTwinFormat(const Context& context, VkFormat format) { switch (format) { case VK_FORMAT_R8_UNORM: return decoded(ShaderRecompiler::IrBufferFormat::Format8Srgb) ? VK_FORMAT_UNDEFINED : VK_FORMAT_R8_SRGB; case VK_FORMAT_R8G8_UNORM: return decoded(ShaderRecompiler::IrBufferFormat::Format8_8Srgb) ? VK_FORMAT_UNDEFINED : VK_FORMAT_R8G8_SRGB; - case VK_FORMAT_R8G8B8A8_UNORM: return VK_FORMAT_R8G8B8A8_SRGB; + case VK_FORMAT_R8_SNORM: return decoded(ShaderRecompiler::IrBufferFormat::Format8Srgb) ? VK_FORMAT_UNDEFINED : VK_FORMAT_R8_SRGB; + case VK_FORMAT_R8G8_SNORM: return decoded(ShaderRecompiler::IrBufferFormat::Format8_8Srgb) ? VK_FORMAT_UNDEFINED : VK_FORMAT_R8G8_SRGB; + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_R8G8B8A8_SNORM: return VK_FORMAT_R8G8B8A8_SRGB; case VK_FORMAT_B8G8R8A8_UNORM: return VK_FORMAT_B8G8R8A8_SRGB; case VK_FORMAT_A8B8G8R8_UNORM_PACK32: return VK_FORMAT_A8B8G8R8_SRGB_PACK32; case VK_FORMAT_BC1_RGB_UNORM_BLOCK: return VK_FORMAT_BC1_RGB_SRGB_BLOCK; @@ -1327,7 +1372,7 @@ VkFormat SrgbTwinFormat(const Context& context, VkFormat format) { VkFormat AttachmentProxyFormat(const Context& context, VkFormat format) { if (format != VK_FORMAT_R8_SRGB) return VK_FORMAT_UNDEFINED; struct Table { - std::mutex mutex; + AgcDriver::Mutex mutex; std::unordered_map formats; }; static Table table; @@ -1431,7 +1476,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler, allocation.allocationSize = requirements.size; memoryBytes = requirements.size; allocation.memoryTypeIndex = context.DeviceMemoryType(requirements.memoryTypeBits, requirements.size, "storage texture"); - memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory storage texture", &memoryType); + memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory storage texture", &memoryType, true); Check(context.Function("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory storage"); } catch (...) { release(); @@ -1514,6 +1559,13 @@ struct PendingWrites { // Images taken out of `textures` by a FlushPending still storing them (see adjacentPendingUnchanged). std::vector flushing; std::atomic flushingCount{0}; + struct Drawn { + StorageTexture* texture; + std::uint64_t bytes; + std::chrono::steady_clock::time_point since; + }; + std::vector drawn; + std::atomic drawnCount{0}; }; PendingWrites& Pending() { @@ -1521,6 +1573,20 @@ PendingWrites& Pending() { return pending; } +DrawnPending drawnPending() { + DrawnPending result; + auto& pending = Pending(); + if (pending.drawnCount.load(std::memory_order_acquire) == 0) return result; + const auto now = std::chrono::steady_clock::now(); + std::lock_guard lock(pending.mutex); + result.images = pending.drawn.size(); + for (const auto& entry : pending.drawn) { + result.bytes += entry.bytes; + result.oldestMs = std::max(result.oldestMs, std::chrono::duration(now - entry.since).count()); + } + return result; +} + // See StorageTexture::PendingSerial: bumped after every mutation of the registry above. std::atomic pendingSerial{0}; @@ -1698,7 +1764,7 @@ bool AdjacentGenerationEnabled() { } bool ClearKeepsDenormals(const Context& context, VkFormat format) { - static std::mutex mutex; + static AgcDriver::Mutex mutex; static std::map, bool> known; std::lock_guard lock(mutex); if (const auto found = known.find({context.device, format}); found != known.end()) return found->second; @@ -1793,9 +1859,10 @@ VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer, VkFor Require(!firstLayer || descriptor.dimension == TextureDimension::k2DArray, "a first-layer storage view needs a 2D array surface"); const auto viewLayerCount = firstLayer ? 1u : geometry.imageLayers - descriptor.baseArray; VkImageViewUsageCreateInfo usage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; - usage.usage = VK_IMAGE_USAGE_STORAGE_BIT; + const bool packed = PackedStorageViewFormat(storageFormat) != VK_FORMAT_UNDEFINED; + usage.usage = packed && format == storageFormat ? VK_IMAGE_USAGE_SAMPLED_BIT | (attachable ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0u) : VK_IMAGE_USAGE_STORAGE_BIT; VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; - viewInfo.pNext = format == storageFormat ? nullptr : &usage; + viewInfo.pNext = format == storageFormat && !packed ? nullptr : &usage; viewInfo.image = image; // Storage views address one mip; cube faces are written as array layers. viewInfo.viewType = firstLayer ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k1D ? VK_IMAGE_VIEW_TYPE_1D : descriptor.dimension == TextureDimension::k2D ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k3D ? VK_IMAGE_VIEW_TYPE_3D : descriptor.dimension == TextureDimension::k1DArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_2D_ARRAY; @@ -1971,7 +2038,8 @@ VkImageView StorageTexture::FirstLayerView(std::uint32_t mip) { } VkImageView StorageTexture::StorageView(std::uint32_t mip, bool firstLayer) { - const auto format = UintFormatOfSint(storageFormat); + const auto packed = PackedStorageViewFormat(storageFormat); + const auto format = packed != VK_FORMAT_UNDEFINED ? packed : UintFormatOfSint(storageFormat); if (format == VK_FORMAT_UNDEFINED) return firstLayer ? FirstLayerView(mip) : View(mip); if (format == VK_FORMAT_R32_UINT) return AtomicView(mip, firstLayer); const auto found = uintViews.find({mip, firstLayer}); @@ -2036,11 +2104,52 @@ LookupOutcomes& ThreadLookupOutcomes() { } bool StorageTexture::Refresh() { + return refresh(0, trackedLayers); +} + +bool StorageTexture::RefreshLayers(std::uint32_t first, std::uint32_t count) { + const auto whole = [this] { + static_cast(FlushDepthPlanes(descriptor.baseAddress, static_cast(guestBytes))); + return Refresh(); + }; + if (count == 0 || geometry.thick || geometry.imageLayers <= 1 || first >= geometry.imageLayers || count > geometry.imageLayers - first || geometry.layerBytes == 0 || trackedLayerBytes == 0 || descriptor.dccAddress != 0) return whole(); + const auto begin = geometry.GuestLayerOffset(first); + const auto end = geometry.GuestLayerOffset(first + count - 1) + geometry.layerBytes; + const auto firstLayer = static_cast(begin / trackedLayerBytes); + const auto lastLayer = static_cast(std::min((end + trackedLayerBytes - 1) / trackedLayerBytes, trackedLayers)); + if (firstLayer >= lastLayer || (firstLayer == 0 && lastLayer == trackedLayers)) return whole(); + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { + if (layerPending[layer] && (layer < firstLayer || layer >= lastLayer)) return whole(); + } + if (pendingAlias() != nullptr) return whole(); + const auto scopeBegin = layerBegin(firstLayer); + static_cast(FlushDepthPlanes(scopeBegin, static_cast(layerBegin(lastLayer - 1) + layerBytes(lastLayer - 1) - scopeBegin))); + return refresh(firstLayer, lastLayer); +} + +bool StorageTexture::refresh(std::uint32_t firstLayer, std::uint32_t lastLayer) { const bool profile = LookupOutcomes::Profiled(); auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; + const bool scoped = firstLayer != 0 || lastLayer != trackedLayers; + const bool epochProofs = ShaderResources::EpochProofs(); + const auto epoch = epochProofs ? GuestMemory::CollectEpoch() : 0; + if (epoch != 0 && provedEpoch.load(std::memory_order_relaxed) == epoch && provedProof.load(std::memory_order_relaxed) == GuestMemory::ProofSerial() && provedPending.load(std::memory_order_relaxed) == PendingSerial()) { + NoteProved(); + refreshesProved.fetch_add(1, std::memory_order_relaxed); + epochRefreshes.fetch_add(1, std::memory_order_relaxed); + return true; + } + const auto proofBefore = GuestMemory::ProofSerial(); + const auto pendingBefore = PendingSerial(); CompletePendingCpuWrites(context, descriptor.baseAddress, static_cast(guestBytes)); if (descriptor.dccAddress != 0) CompletePendingCpuWrites(context, descriptor.dccAddress, DccKeyBytes(guestBytes)); if (refreshProved()) { + layerBehind.clear(); + if (epoch != 0) { + provedEpoch.store(epoch, std::memory_order_relaxed); + provedProof.store(proofBefore, std::memory_order_relaxed); + provedPending.store(pendingBefore, std::memory_order_relaxed); + } NoteProved(); refreshesProved.fetch_add(1, std::memory_order_relaxed); if (profile) LookupOutcomes::Add(LookupOutcomes::RefreshProved, start); @@ -2062,7 +2171,7 @@ bool StorageTexture::Refresh() { // generation, no results of this image's own there) or dead to its own write-back (tracked and // stamped); an untracked unit is stored whole by that write-back, and a unit pending in both // images takes the old order (store, then this image re-uploads it). - auto alias = pendingAlias(); + auto alias = scoped ? std::shared_ptr{} : pendingAlias(); if (alias != nullptr && !(ProvedClearKeys(descriptor, guestBytes, keyProof) == DccKeys::Uncompressed && HostImportFor(context, descriptor.baseAddress, static_cast(guestBytes)) != nullptr)) alias = nullptr; if (alias != nullptr) { std::vector aliasStamped(trackedLayers); @@ -2075,22 +2184,35 @@ bool StorageTexture::Refresh() { } } } + const auto scopeBegin = scoped ? layerBegin(firstLayer) : descriptor.baseAddress; + const auto scopeBytes = scoped ? static_cast(layerBegin(lastLayer - 1) + layerBytes(lastLayer - 1) - scopeBegin) : static_cast(guestBytes); // No publish: the upload below reads the stored units from the unit shadow itself. handedOver.clear(); handedUnits.clear(); - const auto handOverKeys = alias == nullptr ? clearHandOver() : std::nullopt; - const bool flushed = !handOverKeys.has_value() && FlushPending(descriptor.baseAddress, static_cast(guestBytes), alias.get(), "storage refresh", PublishScope::None); + const auto handOverKeys = alias == nullptr && !scoped ? clearHandOver() : std::nullopt; + const bool flushed = !handOverKeys.has_value() && FlushPending(scopeBegin, scopeBytes, alias.get(), "storage refresh", PublishScope::None); if (profile && flushed) start = LookupOutcomes::Add(LookupOutcomes::PendingFlush, start); // `original` holds the guest bytes the image was last uploaded from or written back as; while the // guest memory and the DCC keys still match, the image content is current. Pages nobody wrote // since `generation` need no comparison. const auto current = GuestMemory::CollectWrites(descriptor.baseAddress, static_cast(guestBytes)); + const auto scopedCurrent = current != 0 || !scoped ? current : GuestMemory::CollectWrites(scopeBegin, scopeBytes); + std::vector collected; + if (scopedCurrent == 0) { + collected.resize(trackedLayers); + for (auto layer = firstLayer; layer < lastLayer; ++layer) collected[layer] = collectedLayerGeneration(layer, 0); + } + const auto currentFor = [&](std::uint32_t layer) { + if (scoped && (layer < firstLayer || layer >= lastLayer)) return current; + return scopedCurrent != 0 ? scopedCurrent : collected[layer]; + }; // Per tracked layer: changed since its generation (a keys change makes every layer stale). std::vector changed(trackedLayers, false); // Per 64 KiB block of the surface (blockGenerations): stamped since its layer's generation, and // by a CPU store; scanned once when anything changed. std::vector generations; std::vector stampedBlocks, cpuBlocks; + std::vector outside; // Units taken from the alias's image instead of guest memory. std::vector borrowed; bool anyBorrowed = false; @@ -2106,6 +2228,7 @@ bool StorageTexture::Refresh() { const auto equalsOriginal = [&] { // Named for the [hooksync] attribution: the compare goes through the flush hook. const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::TextureCompare); + if (scoped) return GuestMemory::EqualsCommitted(scopeBegin, std::span(original).subspan(static_cast(scopeBegin - descriptor.baseAddress), scopeBytes)); return GuestMemory::EqualsCommitted(descriptor.baseAddress, original); }; const auto scanBlocks = [&] { @@ -2114,13 +2237,19 @@ bool StorageTexture::Refresh() { cpuBlocks.assign(generations.size(), 0); tracked = GuestMemory::ChangedBlocks(descriptor.baseAddress, static_cast(guestBytes), generations, stampedBlocks, cpuBlocks); if (!tracked) awaitStoredSave(nullptr); - compared = !tracked && compareUntracked(descriptor.baseAddress, static_cast(guestBytes), stampedBlocks, true); + const auto firstBlock = static_cast(scopeBegin / 65536 - descriptor.baseAddress / 65536); + const auto blocks = static_cast((scopeBegin + scopeBytes - 1) / 65536 - scopeBegin / 65536 + 1); + compared = !tracked && (scoped ? compareUntracked(scopeBegin, scopeBytes, std::span(stampedBlocks).subspan(firstBlock, blocks), true) : compareUntracked(descriptor.baseAddress, static_cast(guestBytes), stampedBlocks, true)); if (compared) { for (const auto& [from, unitGeneration] : keptAtUnwatch) { const auto unit = static_cast((from - descriptor.baseAddress) / trackedLayerBytes); if (keptAtUnwatchHolds(from, unit)) stampedBlocks[static_cast(from / 65536 - descriptor.baseAddress / 65536)] = GuestMemory::BlockWritten; } cpuBlocks = stampedBlocks; + if (scoped) { + std::fill(stampedBlocks.begin(), stampedBlocks.begin() + static_cast(firstBlock), GuestMemory::BlockWritten); + std::fill(stampedBlocks.begin() + static_cast(firstBlock + blocks), stampedBlocks.end(), GuestMemory::BlockWritten); + } } }; const auto keysStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; @@ -2139,7 +2268,7 @@ bool StorageTexture::Refresh() { scanBlocks(); for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) { if (stampedBlocks[unit] == 0) { - layerGeneration[unit] = current; + layerGeneration[unit] = currentFor(unit); continue; } changed[unit] = true; @@ -2165,6 +2294,15 @@ bool StorageTexture::Refresh() { } unchanged = std::none_of(changed.begin(), changed.end(), [](bool value) { return value; }); } + if (scoped && !keysChanged) { + outside.assign(trackedLayers, false); + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { + if ((layer >= firstLayer && layer < lastLayer) || (!changed[layer] && !compared)) continue; + outside[layer] = true; + changed[layer] = false; + } + unchanged = std::none_of(changed.begin(), changed.end(), [](bool value) { return value; }); + } for (std::size_t k = 0; k < cpuBlocks.size(); ++k) { if (cpuBlocks[k] != 0 && (compared || (tracked && generations[k] != 0))) cpuWrote = true; } @@ -2175,12 +2313,11 @@ bool StorageTexture::Refresh() { for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { if (changed[layer] && layerPending[layer]) pendingChanged = true; } - if (!unchanged && !keysChanged && !pendingChanged && originalHolds(descriptor.baseAddress, static_cast(guestBytes)) && equalsOriginal()) { + if (!unchanged && !keysChanged && !pendingChanged && (scoped ? originalHolds(scopeBegin, scopeBytes) : originalHolds(descriptor.baseAddress, static_cast(guestBytes))) && equalsOriginal()) { unchanged = true; stamped = false; + if (!scoped) outside.clear(); } - // Only the direct path uploads the selected layers alone (see upload); the others replace - // the whole image, so every pending layer's results are stored first. direct = keys == DccKeys::Uncompressed && HostImportFor(context, descriptor.baseAddress, static_cast(guestBytes)) != nullptr; if (alias != nullptr && direct) { // The alias's pending units that nothing stamped since its generation (a CPU store or a @@ -2205,11 +2342,16 @@ bool StorageTexture::Refresh() { } } } + const bool outsideChanged = std::any_of(outside.begin(), outside.end(), [](bool value) { return value; }); + if (scoped) noteLayersBehind(firstLayer, lastLayer, outside); + else layerBehind.clear(); if (unchanged) { ++Profile().storageReused; - layerGeneration.assign(trackedLayers, current); + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { + if (!outsideChanged || !outside[layer]) layerGeneration[layer] = currentFor(layer); + } refreshGeneration(); - takeRefreshProof(alias != nullptr); + if (layerBehind.empty()) takeRefreshProof(alias != nullptr); if (profile) LookupOutcomes::Add(stamped ? LookupOutcomes::RefreshUnchanged : LookupOutcomes::RefreshCompared, start); return true; } @@ -2259,6 +2401,7 @@ bool StorageTexture::Refresh() { } std::fprintf(stderr, "[dcc-keys] refresh 0x%llx+0x%llx keys %s -> %s (dirty %d, %zu pending units, %zu cpu-stamped units%s)\n", static_cast(descriptor.baseAddress), static_cast(guestBytes), DccKeysName(uploadedKeys), DccKeysName(keys), dirty ? 1 : 0, pendingUnits, stampedUnits, keyFlip ? ", flip keeps unstamped results" : ""); } + const bool selectedCpu = !direct && keys == DccKeys::Uncompressed && !cpuUploadLayers(changed).empty(); for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { if (!layerPending[layer]) continue; if (keyFlip && layerGeneration[layer] != 0 && stampedBlocks[layer] != GuestMemory::BlockWritten) { @@ -2277,7 +2420,7 @@ bool StorageTexture::Refresh() { dropped = true; continue; } - } else if (direct) { + } else if (direct || selectedCpu) { continue; } stored[layer] = true; @@ -2299,7 +2442,7 @@ bool StorageTexture::Refresh() { uploadReason = "alias"; borrowUnits(*alias, borrowed); for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) { - if (borrowed[unit]) layerGeneration[unit] = current; + if (borrowed[unit]) layerGeneration[unit] = currentFor(unit); } refreshGeneration(); } @@ -2412,7 +2555,7 @@ void traceKeyStore(const char* path, const GuestTextureResource& descriptor, std void StorageTexture::captureGuestBytes(const std::vector* layers) { original.resize(static_cast(guestBytes)); - comparedGuestBytes = {}; + comparedRanges = {}; std::vector selected(trackedLayers, true); if (layers != nullptr) selected = *layers; for (const auto& [begin, end] : unitRuns(selected)) GuestMemory::FlushGpuWrites(descriptor.baseAddress + begin, static_cast(end - begin)); @@ -2449,11 +2592,10 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes, Require(changed.size() == (end - 1) / blockBytes - first + 1, "texture comparison block count differs"); const auto* recorder = Recorder::Active(); const auto epoch = GuestMemory::CollectEpoch(); - const bool whole = address == descriptor.baseAddress && bytes == guestBytes; - const auto hint = memoize && epoch != 0 && whole ? GuestMemory::UntrackedHint(address, bytes) : 0; + const auto hint = memoize && epoch != 0 ? GuestMemory::UntrackedHint(address, bytes) : 0; const std::array stamp{hint != 0, hint != 0 ? hint : epoch, GuestMemory::NewestStamp(address, bytes), GuestMemory::ForgetSerial(), recorder != nullptr ? recorder->NewestWriteNote(address, bytes) : 0}; - const bool cacheable = memoize && epoch != 0 && (stamp[3] & 1u) == 0 && whole; - if (cacheable && comparedGuestBytes == stamp) { + const bool cacheable = memoize && epoch != 0 && (stamp[3] & 1u) == 0; + if (cacheable && std::any_of(comparedRanges.begin(), comparedRanges.end(), [&](const ComparedRange& entry) { return entry.address == address && entry.bytes == bytes && entry.stamp == stamp; })) { std::fill(changed.begin(), changed.end(), GuestMemory::BlockUnchanged); return true; } @@ -2461,10 +2603,14 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes, const auto saved = std::span(original).subspan(static_cast(address - descriptor.baseAddress), bytes); if (GuestMemory::EqualsCommittedUnsynced(address, saved)) { std::fill(changed.begin(), changed.end(), GuestMemory::BlockUnchanged); - if (cacheable) comparedGuestBytes = stamp; + if (cacheable) { + auto found = std::find_if(comparedRanges.begin(), comparedRanges.end(), [&](const ComparedRange& entry) { return entry.address == address && entry.bytes == bytes; }); + if (found == comparedRanges.end()) found = comparedRanges.begin() + nextComparedRange++ % comparedRanges.size(); + *found = {address, bytes, stamp}; + } return true; } - comparedGuestBytes = {}; + comparedRanges = {}; for (std::size_t index = 0; index < changed.size(); ++index) { const auto begin = std::max(address, (first + index) * blockBytes); const auto stop = std::min(end, (first + index + 1) * blockBytes); @@ -2476,6 +2622,36 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes, constexpr std::uint64_t GenerationBaselineBytes = 1ull << 20; +bool SingleKeyColorFor(const GuestTextureResource& resource, std::uint64_t guestBytes, std::uint64_t collected, DccSingleProof& proof, VkFormat format, DccKeys keys, VkClearColorValue& clear) { + if (keys != DccKeys::ClearSingle || !ProvedSingleUniform(resource, guestBytes, collected, proof)) return false; + const auto elementBytes = BytesPerElement(resource.format); + if (elementBytes == 0 || elementBytes > sizeof(std::uint32_t) * 4) return false; + std::uint32_t pattern[4]{}; + auto* bytes = reinterpret_cast(pattern); + if (!ReadSingleElement(resource, std::span(bytes, elementBytes))) return false; + for (std::size_t at = elementBytes; at < sizeof(pattern); at += elementBytes) std::memcpy(bytes + at, bytes, elementBytes); + return FillClearColor(format, elementBytes, pattern, clear); +} + +std::vector StorageTexture::cpuUploadLayers(const std::vector& units) const { + if (descriptor.dimension != TextureDimension::k2DArray || descriptor.mipCount != 1 || descriptor.dccAddress != 0 || IsBlockCompressed(descriptor.format) || geometry.thick || arrayLayers <= 1 || geometry.imageLayers != arrayLayers || trackedLayerBytes == 0 || units.size() != trackedLayers) return {}; + std::vector selected(arrayLayers, false); + for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) { + const auto begin = geometry.GuestLayerOffset(layer); + const auto end = begin + geometry.layerBytes; + if (end <= begin || end > guestBytes || begin % trackedLayerBytes != 0 || (end % trackedLayerBytes != 0 && end != guestBytes)) return {}; + const auto first = static_cast(begin / trackedLayerBytes); + const auto last = static_cast((end + trackedLayerBytes - 1) / trackedLayerBytes); + if (first >= last || last > trackedLayers) return {}; + selected[layer] = units[first]; + for (auto unit = first + 1; unit < last; ++unit) { + if (units[unit] != selected[layer]) return {}; + } + } + if (std::none_of(selected.begin(), selected.end(), [](bool value) { return value; }) || std::all_of(selected.begin(), selected.end(), [](bool value) { return value; })) return {}; + return selected; +} + void StorageTexture::upload(const std::vector* layers) { const bool profile = LookupOutcomes::Profiled(); const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; @@ -2484,8 +2660,6 @@ void StorageTexture::upload(const std::vector* layers) { CaptureTrace::Log("upload image=%llx bytes=%llu generation=%llu reason=%s partial=%d", static_cast(descriptor.baseAddress), static_cast(guestBytes), static_cast(generation), uploadReason, layers != nullptr); const auto elementBytes = BytesPerElement(descriptor.format); const auto linearBytes = sliceLinearBytes * arrayLayers; - // A layer selection applies to the direct path alone (the others upload everything); it names - // array layers, which the tracked layers are when there are several. if (layers != nullptr && ((!blockUnits && trackedLayers != arrayLayers) || std::all_of(layers->begin(), layers->end(), [](bool selected) { return selected; }))) layers = nullptr; const auto now = GuestMemory::CollectWrites(descriptor.baseAddress, static_cast(guestBytes)); const auto stampLayers = [&](bool selectedOnly) { @@ -2497,7 +2671,7 @@ void StorageTexture::upload(const std::vector* layers) { } for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { if (!whole && !(*layers)[layer]) continue; - layerGeneration[layer] = now; + layerGeneration[layer] = collectedLayerGeneration(layer, now); if (whole || generationBaseline.size() != guestBytes) continue; const auto offset = static_cast(layerBegin(layer) - descriptor.baseAddress); GuestMemory::ReadCommitted(layerBegin(layer), std::span(generationBaseline).subspan(offset, static_cast(layerBytes(layer)))); @@ -2507,7 +2681,8 @@ void StorageTexture::upload(const std::vector* layers) { uploadedKeys = ProvedClearKeys(descriptor, guestBytes, keyProof); filledKeys = DccKeys::Uncompressed; VkClearColorValue clearValue{}; - if (IsDccClear(uploadedKeys) && ClearColorFor(storageFormat, uploadedKeys, clearValue)) { + const bool fastClear = IsDccClear(uploadedKeys) || uploadedKeys == DccKeys::ClearSingle; + if (fastClear && (ClearColorFor(storageFormat, uploadedKeys, clearValue) || SingleKeyColorFor(descriptor, guestBytes, now, singleProof, storageFormat, uploadedKeys, clearValue))) { // A fast-cleared surface is cleared on the GPU; its texel memory is neither read nor filled. // The clear is recorded into the open batch like a direct upload (the image kept by it): a // batch of its own submitted the recorder's work first and waited for all of it, 25-40 ms @@ -2692,15 +2867,36 @@ void StorageTexture::upload(const std::vector* layers) { if (profile) LookupOutcomes::Add(LookupOutcomes::UploadDirect, start); return; } - layerOriginal.assign(trackedLayers, true); - cancelStoredSaves(); - forgetBorrowed(0, trackedLayers); - stampLayers(false); - original.resize(static_cast(guestBytes)); - GuestMemory::ReadCommitted(descriptor.baseAddress, original); + const auto cpuLayers = uploadedKeys == DccKeys::Uncompressed && layers != nullptr ? cpuUploadLayers(*layers) : std::vector{}; + const bool selectedCpu = !cpuLayers.empty(); + std::uint64_t uploadedBytes = guestBytes; + if (selectedCpu) { + captureGuestBytes(layers); + for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) { + if ((*layers)[unit]) forgetBorrowed(unit, 1); + } + stampLayers(true); + uploadedBytes = 0; + for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) { + if (cpuLayers[layer]) uploadedBytes += geometry.layerBytes; + } + } else { + layerOriginal.assign(trackedLayers, true); + cancelStoredSaves(); + forgetBorrowed(0, trackedLayers); + stampLayers(false); + original.resize(static_cast(guestBytes)); + GuestMemory::ReadCommitted(descriptor.baseAddress, original); + } { Buffer staging(context, original.size(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT); - if (uploadedKeys == DccKeys::Uncompressed) std::memcpy(staging.Bytes().data(), original.data(), original.size()); + if (selectedCpu) { + for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) { + if (!cpuLayers[layer]) continue; + const auto offset = geometry.GuestLayerOffset(layer); + std::memcpy(staging.Bytes().data() + offset, original.data() + offset, static_cast(geometry.layerBytes)); + } + } else if (uploadedKeys == DccKeys::Uncompressed) std::memcpy(staging.Bytes().data(), original.data(), original.size()); else ReadTextureSurface(descriptor, uploadedKeys, staging.Bytes().first(original.size())); DeviceBuffer tiled(context, original.size(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); DeviceBuffer linear(context, static_cast(linearBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); @@ -2708,9 +2904,16 @@ void StorageTexture::upload(const std::vector* layers) { CommandBatch batch(context); const auto commands = batch.Handle(); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); - CopyBuffer(context, commands, staging.Handle(), 0, tiled.Handle(), 0, original.size()); + if (selectedCpu) { + for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) { + if (!cpuLayers[layer]) continue; + const auto offset = geometry.GuestLayerOffset(layer); + CopyBuffer(context, commands, staging.Handle(), offset, tiled.Handle(), offset, geometry.layerBytes); + } + } else CopyBuffer(context, commands, staging.Handle(), 0, tiled.Handle(), 0, original.size()); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) { + if (selectedCpu && !cpuLayers[layer]) continue; for (std::uint32_t level = 0; level < mips.size(); ++level) { if (!geometry.HasLayer(level, layer)) continue; const auto& mip = mips[level]; @@ -2719,15 +2922,16 @@ void StorageTexture::upload(const std::vector* layers) { } const auto linearRead = WholeBufferBarrier(linear.Handle(), VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); VkImageMemoryBarrier toTransfer{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + toTransfer.srcAccessMask = selectedCpu ? VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT : 0u; toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - toTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + toTransfer.oldLayout = selectedCpu ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED; toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; toTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toTransfer.image = image; toTransfer.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, descriptor.mipCount, 0, geometry.imageLayers}; - context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &linearRead, 1, &toTransfer); - const auto regions = CopyRegions(); + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | (selectedCpu ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT : 0u), VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &linearRead, 1, &toTransfer); + const auto regions = CopyRegions(selectedCpu ? &cpuLayers : nullptr); context.Function("vkCmdCopyBufferToImage")(commands, linear.Handle(), image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(regions.size()), regions.data()); VkImageMemoryBarrier toGeneral = toTransfer; toGeneral.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; @@ -2739,7 +2943,7 @@ void StorageTexture::upload(const std::vector* layers) { batch.SubmitAndWait(); APS5_LOG_CHARS_OUT("StorageTexture upload done"); } - countStorageUpload(2, guestBytes); + countStorageUpload(2, uploadedBytes); ++version; if (profile) LookupOutcomes::Add(LookupOutcomes::UploadCpu, start); } @@ -2837,6 +3041,16 @@ std::vector StorageTexture::sliceWindows(std::span< return windows; } +std::vector StorageTexture::UnitRects(const std::vector& units) const { + std::vector rects; + if (units.size() != trackedLayers) return rects; + const auto runs = unitRuns(units); + for (const auto& window : sliceWindows(runs)) { + for (const auto& region : window.regions) rects.push_back({{region.imageOffset.x, region.imageOffset.y}, {region.imageExtent.width, region.imageExtent.height}}); + } + return rects; +} + std::vector> StorageTexture::uncoveredBytes(std::span> runs) const { std::vector> covered; if (!geometry.thick) { @@ -3422,7 +3636,7 @@ std::uint64_t StorageTexture::writeBackWindows(std::span se toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; toRead.newLayout = VK_IMAGE_LAYOUT_GENERAL; const BarrierMask ready{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, importReady.srcAccessMask | toRead.srcAccessMask, importReady.dstAccessMask | toRead.dstAccessMask, toRead.oldLayout != toRead.newLayout, toRead.srcQueueFamilyIndex != toRead.dstQueueFamilyIndex}; - const bool ordered = recorder != nullptr && seeds.empty() && padding.copies.empty() && Recorder::ElideCovered(Recorder::CommandClass::StorageWriteBack, ready, covered); + const bool ordered = recorder != nullptr && seeds.empty() && padding.copies.empty() && holeCopies.empty() && Recorder::ElideCovered(Recorder::CommandClass::StorageWriteBack, ready, covered); if (!ordered) context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &importReady, 0, nullptr, 1, &toRead); for (std::size_t i = 0; i < windows.size(); ++i) { const auto& window = windows[i]; @@ -3568,18 +3782,77 @@ void StorageTexture::refreshGeneration() { generation = *std::min_element(layerGeneration.begin(), layerGeneration.end()); } +std::uint64_t StorageTexture::GenerationFor(std::uint64_t address, std::uint64_t bytes) const { + if (layerBehind.empty() && generation != 0) return generation; + std::optional oldest; + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { + const auto begin = layerBegin(layer); + if (address >= begin + layerBytes(layer) || begin >= address + bytes) continue; + if (!layerBehind.empty() && layerBehind[layer]) return 0; + oldest = std::min(oldest.value_or(layerGeneration[layer]), layerGeneration[layer]); + } + return oldest.value_or(generation); +} + +std::uint64_t StorageTexture::collectedLayerGeneration(std::uint32_t layer, std::uint64_t whole, bool uncached) const { + if (whole != 0 || trackedLayers <= 1) return whole; + const auto begin = layerBegin(layer); + const auto bytes = static_cast(layerBytes(layer)); + return uncached ? GuestMemory::CollectWritesUncached(begin, bytes) : GuestMemory::CollectWrites(begin, bytes); +} + +void StorageTexture::noteLayersBehind(std::uint32_t firstLayer, std::uint32_t lastLayer, const std::vector& outside) { + if (layerBehind.size() != trackedLayers) layerBehind.assign(trackedLayers, false); + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { + if (layer >= firstLayer && layer < lastLayer) layerBehind[layer] = false; + else if (layer < outside.size() && outside[layer]) layerBehind[layer] = true; + } + const auto layersEnd = layerBegin(lastLayer - 1) + layerBytes(lastLayer - 1); + const std::array, 2> ranges{{{descriptor.baseAddress, layerBegin(firstLayer)}, {layersEnd, descriptor.baseAddress + guestBytes}}}; + for (const auto& [from, to] : ranges) { + if (to <= from) continue; + for (const auto& other : overlappingPending(from, static_cast(to - from))) { + const bool whole = other->descriptor.dccAddress != 0 && other->uploadedKeys != DccKeys::Uncompressed; + for (std::uint32_t unit = 0; unit < other->trackedLayers; ++unit) { + if (!whole && !other->layerPending[unit]) continue; + const auto begin = std::max(from, other->layerBegin(unit)); + const auto end = std::min(to, other->layerBegin(unit) + other->layerBytes(unit)); + if (end <= begin) continue; + const auto first = static_cast((begin - descriptor.baseAddress) / trackedLayerBytes); + const auto last = static_cast(std::min((end - descriptor.baseAddress + trackedLayerBytes - 1) / trackedLayerBytes, trackedLayers)); + for (auto layer = first; layer < last; ++layer) layerBehind[layer] = true; + } + } + } + if (std::none_of(layerBehind.begin(), layerBehind.end(), [](bool behind) { return behind; })) layerBehind.clear(); +} + void StorageTexture::markLayersPending(std::uint32_t first, std::uint32_t count) { CaptureTrace::Log("image-write image=%llx first=%u count=%u generation=%llu", static_cast(descriptor.baseAddress), first, count, static_cast(generation)); ++writes; static const bool eager = std::getenv("APS5_EAGER_WRITEBACK") != nullptr || std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; - std::vector unsaved(trackedLayers, false); + std::vector unsaved; bool anyUnsaved = false; - for (std::uint32_t layer = first; layer < first + count; ++layer) { - unsaved[layer] = !layerPending[layer] && !layerOriginal[layer] && layerGeneration[layer] == 0; - anyUnsaved = anyUnsaved || unsaved[layer]; + const bool trims = CommitTrims(); + if (trims) { + for (std::uint32_t layer = first; layer < first + count && !anyUnsaved; ++layer) anyUnsaved = !layerPending[layer] && !layerOriginal[layer] && layerGeneration[layer] == 0; + } + if (!trims || anyUnsaved) { + unsaved.assign(trackedLayers, false); + anyUnsaved = false; + for (std::uint32_t layer = first; layer < first + count; ++layer) { + unsaved[layer] = !layerPending[layer] && !layerOriginal[layer] && layerGeneration[layer] == 0; + anyUnsaved = anyUnsaved || unsaved[layer]; + } } if (anyUnsaved && !GuestMemory::Watched(descriptor.baseAddress, static_cast(guestBytes))) captureGuestBytes(&unsaved); - for (std::uint32_t layer = first; layer < first + count; ++layer) layerPending[layer] = true; + for (std::uint32_t layer = first; layer < first + count; ++layer) { + if (!layerPending[layer] && !completionKept.empty()) { + const auto end = layerBegin(layer) + layerBytes(layer); + for (auto at = layerBegin(layer) & ~std::uint64_t{65535}; at < end; at += 65536) completionKept[static_cast(at / 65536 - descriptor.baseAddress / 65536)] = 0; + } + layerPending[layer] = true; + } // Results of this image now cover the alias's results borrowed into these units: this image // stores them, the alias no longer has to (its results, unchanged since the borrow, are in // this image's content). An alias written since keeps its own newer results pending. @@ -3602,6 +3875,10 @@ void StorageTexture::markLayersPending(std::uint32_t first, std::uint32_t count) } auto& pending = Pending(); std::lock_guard lock(pending.mutex); + if (!drawingTarget) { + pendingFromOthers = true; + if (drawnListed) drawnWhole = true; + } // The active recorder installs a hook that also waits for recorded work; without one (tests), // pending storage results alone are flushed. if (Recorder::Active() == nullptr) { @@ -3626,6 +3903,7 @@ void StorageTexture::reconcilePending() { auto& pending = Pending(); std::lock_guard lock(pending.mutex); const bool any = anyLayerPending(); + if (!any) forgetDrawn(); if (any == dirty) return; dirty = any; if (any) pending.textures.push_back(this); @@ -3639,6 +3917,215 @@ bool StorageTexture::Dirty() const { return dirty; } +constexpr std::size_t MaxDrawnRects = 16; + +bool StorageTexture::MarkDrawn(std::uint32_t slice, std::uint32_t count, VkRect2D scissor) { + struct Drawing { + bool& flag; + bool previous; + ~Drawing() { flag = previous; } + } drawing{drawingTarget, std::exchange(drawingTarget, true)}; + MarkSliceDirty(slice, count); + const auto clip = [](std::int64_t value, std::uint32_t limit) { return static_cast(std::clamp(value, 0, limit)); }; + const auto left = clip(scissor.offset.x, descriptor.width); + const auto top = clip(scissor.offset.y, descriptor.height); + const auto right = clip(std::int64_t{scissor.offset.x} + scissor.extent.width, descriptor.width); + const auto bottom = clip(std::int64_t{scissor.offset.y} + scissor.extent.height, descriptor.height); + const bool rectangle = proxyImage == VK_NULL_HANDLE && descriptor.tileMode == TextureTileMode::kLinear && !blockUnits && trackedLayers == 1 && descriptor.mipCount == 1 && arrayLayers == 1 && geometry.imageLayers == 1 && geometry.imageDepth == 1 && !geometry.thick && descriptor.dccAddress == 0; + auto& pending = Pending(); + std::lock_guard lock(pending.mutex); + if (!anyLayerPending()) return true; + if (!drawnListed) { + drawnListed = true; + drawnWhole = false; + drawnRects.clear(); + pending.drawn.push_back({this, guestBytes, std::chrono::steady_clock::now()}); + pending.drawnCount.store(pending.drawn.size(), std::memory_order_release); + } + if (!rectangle || pendingFromOthers) drawnWhole = true; + if (!drawnWhole && right > left && bottom > top) addDrawnRect({left, top, right, bottom}); + return !drawnWhole && drawnRects.size() <= MaxDrawnRects; +} + +void StorageTexture::addDrawnRect(DrawnRect rect) { + const auto inside = [](const DrawnRect& outer, const DrawnRect& inner) { return outer.left <= inner.left && outer.top <= inner.top && inner.right <= outer.right && inner.bottom <= outer.bottom; }; + if (std::any_of(drawnRects.begin(), drawnRects.end(), [&](const DrawnRect& kept) { return inside(kept, rect); })) return; + for (auto kept = drawnRects.begin(); kept != drawnRects.end();) { + const bool columns = kept->left == rect.left && kept->right == rect.right && kept->top <= rect.bottom && rect.top <= kept->bottom; + const bool rows = kept->top == rect.top && kept->bottom == rect.bottom && kept->left <= rect.right && rect.left <= kept->right; + if (!columns && !rows && !inside(rect, *kept)) { + ++kept; + continue; + } + rect = {std::min(kept->left, rect.left), std::min(kept->top, rect.top), std::max(kept->right, rect.right), std::max(kept->bottom, rect.bottom)}; + drawnRects.erase(kept); + kept = drawnRects.begin(); + } + drawnRects.push_back(rect); +} + +void StorageTexture::forgetDrawn() { + pendingFromOthers = false; + if (!drawnListed) return; + drawnListed = false; + drawnWhole = false; + drawnRects.clear(); + auto& pending = Pending(); + std::erase_if(pending.drawn, [this](const PendingWrites::Drawn& entry) { return entry.texture == this; }); + pending.drawnCount.store(pending.drawn.size(), std::memory_order_release); +} + +void StorageTexture::noteStore(std::uint64_t note) { + if (note == 0) return; + auto& pending = Pending(); + std::lock_guard lock(pending.mutex); + storeNotes.push_back(note); + if (storeNotes.size() > 8) storeNotes.erase(storeNotes.begin()); +} + +StorageTexture::DrawnArea StorageTexture::drawnArea() const { + auto& pending = Pending(); + std::lock_guard lock(pending.mutex); + if (!drawnListed) return {}; + DrawnArea area{true, !drawnWhole && !pendingFromOthers}; + if (!area.rect || drawnRects.empty()) return area; + area.left = area.top = std::numeric_limits::max(); + for (const auto& rect : drawnRects) { + area.left = std::min(area.left, rect.left); + area.top = std::min(area.top, rect.top); + area.right = std::max(area.right, rect.right); + area.bottom = std::max(area.bottom, rect.bottom); + } + area.rects = drawnRects; + return area; +} + +std::vector> StorageTexture::drawnSpans(const DrawnArea& area, std::span> keep) const { + std::vector> spans; + if (area.rects.empty()) return spans; + const auto& mip = mips.front(); + const auto elementBytes = static_cast(BytesPerElement(descriptor.format)); + const auto rowBase = descriptor.baseAddress + mip.tiledOffset; + std::vector> columns; + std::size_t next = 0; + for (auto row = area.top; row < area.bottom; ++row) { + columns.clear(); + for (const auto& rect : area.rects) { + if (rect.top <= row && row < rect.bottom) columns.emplace_back(rect.left, rect.right); + } + std::sort(columns.begin(), columns.end()); + const auto rowStart = rowBase + std::uint64_t{row} * mip.pitchBytes; + for (std::size_t first = 0; first < columns.size();) { + auto right = columns[first].second; + auto last = first + 1; + for (; last < columns.size() && columns[last].first <= right; ++last) right = std::max(right, columns[last].second); + const auto rowBegin = rowStart + columns[first].first * elementBytes; + const auto rowEnd = rowStart + right * elementBytes; + first = last; + while (next < keep.size() && keep[next].second <= rowBegin) ++next; + for (auto index = next; index < keep.size() && keep[index].first < rowEnd; ++index) { + const auto from = std::max(rowBegin, keep[index].first); + const auto to = std::min(rowEnd, keep[index].second); + if (from < to) spans.emplace_back(from, to); + } + } + } + return spans; +} + +std::uint64_t StorageTexture::storeDrawnRect(const DrawnArea& area, std::span segments, std::span> stores, const std::vector& saveUnits) { + const auto& mip = mips.front(); + const auto elementBytes = static_cast(BytesPerElement(descriptor.format)); + const auto rowBase = descriptor.baseAddress + mip.tiledOffset; + const auto width = area.right - area.left; + const auto height = area.bottom - area.top; + const auto rowBytes = std::uint64_t{width} * elementBytes; + const auto hullBegin = stores.front().first; + const auto hullBytes = static_cast(stores.back().second - hullBegin); + std::uint64_t storedBytes = 0; + for (const auto& [from, to] : stores) storedBytes += to - from; + auto scratch = std::make_shared(context, static_cast(rowBytes * height), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + auto* recorder = Recorder::Active(); + std::unique_ptr batch; + VkCommandBuffer commands = VK_NULL_HANDLE; + auto timing = Recorder::NoTiming; + if (recorder != nullptr) { + recorder->FlushKeyStoresOverlapping(hullBegin, hullBytes); + recorder->FlushStoresOverlapping(hullBegin, hullBytes); + commands = recorder->Commands(); + timing = recorder->BeginGpuTiming(Recorder::CommandClass::StorageWriteBack); + recorder->Keep(scratch, scratch->Size()); + if (auto self = weak_from_this().lock()) keepStorageTexture(*recorder, std::move(self)); + Recorder::CountBarriers(Recorder::CommandClass::StorageWriteBack, 3); + if (Recorder::BarrierValidate()) { + const std::pair read{image, false}; + recorder->NoteAccess(Recorder::CommandClass::StorageWriteBack, Recorder::Access{{}, stores, std::span(&read, 1), VK_PIPELINE_STAGE_TRANSFER_BIT}); + } + } else { + batch = std::make_unique(context); + commands = batch->Handle(); + } + VkImageMemoryBarrier toSource{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + toSource.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + toSource.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + toSource.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + toSource.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + toSource.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toSource.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toSource.image = image; + toSource.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, descriptor.mipCount, 0, geometry.imageLayers}; + const VkMemoryBarrier importReady{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT}; + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &importReady, 0, nullptr, 1, &toSource); + VkBufferImageCopy region{}; + region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + region.imageOffset = {static_cast(area.left), static_cast(area.top), 0}; + region.imageExtent = {width, height, 1u}; + context.Function("vkCmdCopyImageToBuffer")(commands, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, scratch->Handle(), 1, ®ion); + const auto scratchRead = WholeBufferBarrier(scratch->Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &scratchRead, 0, nullptr); + const auto scratchOffset = [&](std::uint64_t address) { + const auto offset = address - rowBase; + const auto row = offset / mip.pitchBytes; + return (row - area.top) * rowBytes + offset - row * mip.pitchBytes - area.left * elementBytes; + }; + const auto copyBuffer = context.Function("vkCmdCopyBuffer"); + for (const auto& segment : segments) { + std::vector copies; + for (const auto& [from, to] : stores) { + const auto begin = std::max(from, segment.begin); + const auto end = std::min(to, segment.end); + if (begin < end) copies.push_back({scratchOffset(begin), HostImportOffset(*segment.import, begin), end - begin}); + } + if (!copies.empty()) copyBuffer(commands, scratch->Handle(), segment.import->buffer, static_cast(copies.size()), copies.data()); + } + if (!saveUnits.empty()) { + const auto saved = savedBytes->Bytes(); + for (const auto unit : saveUnits) { + const auto offset = static_cast(layerBegin(unit) - descriptor.baseAddress); + std::memcpy(saved.data() + offset, original.data() + offset, static_cast(layerBytes(unit))); + } + std::vector saves; + for (const auto& [from, to] : stores) saves.push_back({scratchOffset(from), from - descriptor.baseAddress, to - from}); + copyBuffer(commands, scratch->Handle(), savedBytes->Handle(), static_cast(saves.size()), saves.data()); + } + VkImageMemoryBarrier backToGeneral = toSource; + backToGeneral.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + backToGeneral.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + backToGeneral.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + backToGeneral.newLayout = VK_IMAGE_LAYOUT_GENERAL; + const VkMemoryBarrier stored{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | 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, 1, &stored, 0, nullptr, 1, &backToGeneral); + if (batch) { + batch->SubmitAndWait(); + return storedBytes; + } + recorder->EndGpuTiming(timing, storedBytes); + recorder->MarkShaderReadsCovered(); + recorder->NotePendingWrite(hullBegin, hullBytes); + noteStore(recorder->LastWriteNote(hullBegin, hullBytes)); + return storedBytes; +} + void StorageTexture::Flush() { { auto& live = Live(); @@ -3674,16 +4161,53 @@ bool StorageTexture::pendingUnitInside(std::uint64_t address, std::size_t bytes) return false; } -bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except, const char* reason, PublishScope scope, bool* published) { +namespace { + +void waitForIdlePublish(const char* reason) { + if (reason == nullptr || std::strcmp(reason, "idle") != 0 || Recorder::InCompletion()) return; + if (auto* recorder = Recorder::Active()) { + Recorder::CountSync(0); + recorder->Sync(); + } +} + +} + +bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except, const char* reason, PublishScope scope, bool* published, bool waited) { static_cast(FlushDepthPlanes(address, bytes)); if (published != nullptr) *published = false; + const auto nothingToStore = [&] { + auto& pending = Pending(); + if (pending.flushingCount.load(std::memory_order_acquire) != 0) return false; + if (!pending.textures.SurelyClear(address, bytes)) { + std::lock_guard lock(pending.mutex); + const auto* exempt = refreshing; + if (pending.textures.MayOverlap(address, bytes) && std::any_of(pending.textures.begin(), pending.textures.end(), [&](const StorageTexture* texture) { return texture != except && texture != exempt && texture->overlaps(address, bytes) && texture->pendingUnitInside(address, bytes); })) return false; + if (pending.flushingCount.load(std::memory_order_acquire) != 0) return false; + } + return scope == PublishScope::None || !AnyShadowedOverlaps(address, bytes); + }; + if (nothingToStore()) return false; if (AgcDriver::DriverDetail::DrawPipeline::HeldReads(address, bytes)) { queuedReadHeld.fetch_add(1, std::memory_order_relaxed); return false; } + std::unique_lock drawnHold; + if (auto& drawnPending = Pending(); drawnPending.drawnCount.load(std::memory_order_acquire) != 0 && !GuestMemory::GpuMutex().HeldByThisThread()) { + bool overlap = false; + { + std::lock_guard lock(drawnPending.mutex); + overlap = std::any_of(drawnPending.drawn.begin(), drawnPending.drawn.end(), [&](const PendingWrites::Drawn& entry) { return entry.texture->dirty && entry.texture->overlaps(address, bytes); }); + } + if (overlap) { + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush); + drawnHold = std::unique_lock(GuestMemory::GpuMutex()); + } + } // The images stay alive across the scan: their last owner may be a batch's kept list, which // another thread releases outside the GPU mutex once the batch completed. std::vector> flush; + std::vector ownNotes; { auto& pending = Pending(); std::lock_guard lock(pending.mutex); @@ -3711,14 +4235,18 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons for (const auto& texture : flush) pending.flushing.push_back(texture.get()); pending.flushingCount.store(pending.flushing.size(), std::memory_order_release); if (!flush.empty()) BumpPendingSerial(); + for (const auto& texture : flush) { + if (texture->drawnListed) ownNotes.insert(ownNotes.end(), texture->storeNotes.begin(), texture->storeNotes.end()); + } } if (flush.empty()) { - const bool waited = WaitForFlushing(address, bytes, except); + if (drawnHold.owns_lock()) drawnHold.unlock(); + const bool stored = WaitForFlushing(address, bytes, except); // Units shadowed over the range (another image's results retiled into the import's unit // shadow, or an earlier flush's) still reach the import as the scope asks. - const bool units = PublishShadowsOnly(address, bytes, scope, reason); + const bool units = PublishShadowsOnly(address, bytes, scope, reason, waited || stored); if (published != nullptr) *published = units; - return waited; + return stored; } struct Unregister { const std::vector>& flush; @@ -3736,10 +4264,11 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons for (const auto& texture : flush) std::fprintf(stderr, "[flush] image 0x%llx+0x%llx for %s 0x%llx+0x%zx\n", static_cast(texture->descriptor.baseAddress), static_cast(texture->guestBytes), reason, static_cast(address), bytes); } std::lock_guard gpu(GuestMemory::GpuMutex()); - if (auto* recorder = Recorder::Active(); recorder != nullptr && !Recorder::InCompletion() && recorder->PendingWriterSerial(address, bytes) != 0) { + if (!ownNotes.empty() && (flush.size() != 1 || std::any_of(flush.begin(), flush.end(), [](const std::shared_ptr& texture) { return ExistingHostImportFor(texture->context, texture->descriptor.baseAddress, static_cast(texture->guestBytes)) == nullptr; }))) ownNotes.clear(); + if (auto* recorder = Recorder::Active(); recorder != nullptr && !Recorder::InCompletion() && recorder->PendingWriterSerial(address, bytes, ownNotes) != 0) { try { recorder->Reap(); - if (const auto writer = recorder->PendingWriterSerial(address, bytes); writer == recorder->OpenSerial()) { + if (const auto writer = recorder->PendingWriterSerial(address, bytes, ownNotes); writer == recorder->OpenSerial()) { Recorder::CountSync(recorder->OpenWriteOverlaps(address, bytes) ? 1 : 3); recorder->Sync(); } else if (writer != 0) { @@ -3766,7 +4295,8 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons // The stores above went into the unit shadows where slabs took them: the consumer reads the // import, so they are published under the same hold, after every store. if (scope != PublishScope::None) { - const auto units = PublishShadow(address, bytes, scope, PublishReasonFor(reason)); + const auto units = PublishShadow(address, bytes, scope, PublishReasonFor(reason), waited); + if (units != 0) waitForIdlePublish(reason); if (published != nullptr) *published = units != 0; } return true; @@ -3831,7 +4361,7 @@ void StorageTexture::saveBeforeUnwatch() { const auto runs = unitRuns(units); if (runs.empty()) return; original.resize(static_cast(guestBytes)); - comparedGuestBytes = {}; + comparedRanges = {}; for (const auto& [begin, end] : runs) GuestMemory::ReadCommittedUnsynced(descriptor.baseAddress + begin, std::span(original).subspan(static_cast(begin), static_cast(end - begin))); for (const auto& [begin, end] : runs) GuestMemory::CollectWritesUncached(descriptor.baseAddress + begin, static_cast(end - begin)); constexpr std::uint64_t block = 65536; @@ -3848,7 +4378,8 @@ void StorageTexture::saveBeforeUnwatch() { const auto end = begin + layerBytes(unit); const auto spanBegin = begin & ~(block - 1); const auto spanBlocks = static_cast((end - spanBegin + block - 1) / block); - const std::vector generations(spanBlocks, layerGeneration[unit]); + std::vector generations(spanBlocks); + for (std::size_t k = 0; k < spanBlocks; ++k) generations[k] = unitBlockGeneration(unit, spanBegin + k * block); std::vector changedBlocks(spanBlocks); if (!GuestMemory::ChangedBlocks(begin, static_cast(end - begin), generations, changedBlocks)) continue; forgetKeptAtUnwatch(unit); @@ -3856,7 +4387,7 @@ void StorageTexture::saveBeforeUnwatch() { const auto from = std::max(at, begin); const auto to = std::min(at + block, end); const bool edge = from != at || to != at + block; - if (changedBlocks[static_cast((at - spanBegin) / block)] == GuestMemory::BlockWritten && (!edge || GuestMemory::StoredOver(from, static_cast(to - from), layerGeneration[unit])) && !unchangedSinceBaseline(from, to)) keptAtUnwatch.emplace(from, layerGeneration[unit]); + if (changedBlocks[static_cast((at - spanBegin) / block)] == GuestMemory::BlockWritten && (!edge || GuestMemory::StoredOver(from, static_cast(to - from), unitBlockGeneration(unit, at))) && !unchangedSinceBaseline(from, to)) keptAtUnwatch.emplace(from, layerGeneration[unit]); } layerOriginal[unit] = true; cancelStoredSave(unit); @@ -3892,7 +4423,7 @@ void StorageTexture::applyStoredSave(std::uint64_t ticket, const std::vector(guestBytes)); - comparedGuestBytes = {}; + comparedRanges = {}; const auto offset = static_cast(layerBegin(unit) - descriptor.baseAddress); std::memcpy(original.data() + offset, stored.data() + offset, static_cast(layerBytes(unit))); layerOriginal[unit] = true; @@ -3929,10 +4460,12 @@ void StorageTexture::awaitStoredSave(const std::vector* layers) { if (outstanding()) recorder->Sync(); } -bool StorageTexture::PublishShadowsOnly(std::uint64_t address, std::size_t bytes, PublishScope scope, const char* reason) { +bool StorageTexture::PublishShadowsOnly(std::uint64_t address, std::size_t bytes, PublishScope scope, const char* reason, bool waited) { if (scope == PublishScope::None || !AnyShadowedOverlaps(address, bytes)) return false; std::lock_guard gpu(GuestMemory::GpuMutex()); - return PublishShadow(address, bytes, scope, PublishReasonFor(reason)) != 0; + if (PublishShadow(address, bytes, scope, PublishReasonFor(reason), waited) == 0) return false; + waitForIdlePublish(reason); + return true; } namespace { @@ -4004,6 +4537,63 @@ std::size_t StorageTexture::StoreSmallPending(const char* reason) { return stored; } +void StorageTexture::StoreReadbackBeforeLabel(const char* reason) { + static const bool disabled = std::getenv("APS5_NO_LABEL_READBACK_STORE") != nullptr; + if (disabled) return; + std::vector> ranges; + { + auto& pending = Pending(); + std::lock_guard lock(pending.mutex); + for (const auto* texture : pending.textures) { + if (texture->descriptor.tileMode == TextureTileMode::kLinear || texture->guestBytes <= SmallStoreBytes) ranges.emplace_back(texture->descriptor.baseAddress, static_cast(texture->guestBytes)); + } + } + for (const auto& [address, bytes] : ranges) static_cast(FlushPending(address, bytes, nullptr, reason)); +} + +std::size_t StorageTexture::StoreDrawnLinear(const char* reason) { + auto& pending = Pending(); + if (pending.drawnCount.load(std::memory_order_acquire) == 0 || Recorder::InCompletion()) return 0; + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush); + std::lock_guard gpu(GuestMemory::GpuMutex()); + std::vector> ranges; + { + std::lock_guard lock(pending.mutex); + for (const auto& entry : pending.drawn) { + if (entry.texture->dirty) ranges.emplace_back(entry.texture->descriptor.baseAddress, static_cast(entry.texture->guestBytes)); + } + } + std::size_t stored = 0; + for (const auto& [address, bytes] : ranges) { + if (FlushPending(address, bytes, nullptr, reason)) ++stored; + } + return stored; +} + +std::size_t StorageTexture::StoreDrawnLinearTouching(const char* reason, const std::function& touches, bool all) { + auto& pending = Pending(); + if (pending.drawnCount.load(std::memory_order_acquire) == 0 || Recorder::InCompletion()) return 0; + std::vector> ranges; + { + std::lock_guard lock(pending.mutex); + for (const auto& entry : pending.drawn) { + if (!entry.texture->dirty) continue; + const auto begin = entry.texture->descriptor.baseAddress; + const auto end = begin + entry.texture->guestBytes; + if (!all && !touches(begin, end)) continue; + ranges.emplace_back(begin, static_cast(entry.texture->guestBytes)); + } + } + if (ranges.empty()) return 0; + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush); + std::lock_guard gpu(GuestMemory::GpuMutex()); + std::size_t stored = 0; + for (const auto& [address, bytes] : ranges) { + if (FlushPending(address, bytes, nullptr, reason)) ++stored; + } + return stored; +} + bool StorageTexture::AnyPending() { auto& pending = Pending(); std::lock_guard lock(pending.mutex); @@ -4069,6 +4659,101 @@ bool PendingStorageOverlaps(std::uint64_t address, std::size_t bytes, const Stor return false; } +std::uint64_t StorageTexture::StampKeepingNewerResults(std::uint64_t address, std::size_t bytes, std::uint64_t recorded, const std::function& stamp) { + struct Candidate { + std::shared_ptr texture; + std::uint32_t unit; + bool drawn; + }; + struct Kept { + std::shared_ptr texture; + std::uint32_t unit; + std::uint64_t from; + std::uint64_t to; + std::uint64_t since; + bool whole; + bool drawn; + }; + std::vector candidates; + if (recorded != 0 && bytes != 0) { + auto& pending = Pending(); + std::lock_guard lock(pending.mutex); + const auto consider = [&](StorageTexture* texture) { + if ((!texture->blockUnits && !texture->drawnListed) || !texture->overlaps(address, bytes)) return; + auto alive = texture->weak_from_this().lock(); + if (alive == nullptr) return; + for (std::uint32_t unit = 0; unit < texture->trackedLayers; ++unit) { + const auto begin = texture->layerBegin(unit); + const auto unitBytes = texture->layerBytes(unit); + if (!texture->layerPending[unit] || begin >= address + bytes || address >= begin + unitBytes) continue; + if (texture->layerGeneration[unit] >= recorded) candidates.push_back({alive, unit, texture->drawnListed}); + } + }; + for (auto* texture : pending.textures) consider(texture); + for (auto* texture : pending.flushing) consider(texture); + } + constexpr std::uint64_t block = 65536; + std::vector kept; + for (const auto& [texture, unit, drawn] : candidates) { + const auto begin = texture->layerBegin(unit); + const auto end = begin + texture->layerBytes(unit); + GuestMemory::CollectWritesUncached(begin, static_cast(end - begin)); + if (GuestMemory::UnchangedSince(begin, static_cast(end - begin), texture->layerGeneration[unit])) { + kept.push_back({texture, unit, begin, end, texture->layerGeneration[unit], true, drawn}); + continue; + } + if (!drawn) continue; + const auto first = std::max(begin, address) / block; + const auto last = (std::min(end, address + bytes) - 1) / block; + const auto from = std::max(begin, first * block); + const auto to = std::min(end, (last + 1) * block); + std::vector generations(static_cast(last - first + 1)); + for (std::size_t k = 0; k < generations.size(); ++k) generations[k] = texture->unitBlockGeneration(unit, (first + k) * block); + std::vector changed(generations.size()); + if (!GuestMemory::ChangedBlocks(from, static_cast(to - from), generations, changed)) continue; + for (std::size_t k = 0; k < generations.size(); ++k) { + const auto at = (first + k) * block; + const auto blockFrom = std::max(at, begin); + const auto blockTo = std::min(at + block, end); + const bool edge = blockFrom != at || blockTo != at + block; + if (changed[k] == GuestMemory::BlockWritten && (!edge || !GuestMemory::UnchangedSince(blockFrom, static_cast(blockTo - blockFrom), generations[k]))) continue; + if (!kept.empty() && !kept.back().whole && kept.back().texture == texture && kept.back().unit == unit && kept.back().since == generations[k] && kept.back().to == blockFrom) kept.back().to = blockTo; + else kept.push_back({texture, unit, blockFrom, blockTo, generations[k], false, true}); + } + } + const auto generation = stamp(); + if (generation == 0 || kept.empty()) return generation; + std::vector confirmed; + for (const auto& entry : kept) { + if ((entry.whole && !entry.drawn) || GuestMemory::UnchangedSinceCollected(entry.from, static_cast(entry.to - entry.from), entry.since)) { + confirmed.push_back(entry); + continue; + } + if (entry.from / block == (entry.to - 1) / block) continue; + const auto last = (std::min(entry.to, address + bytes) - 1) / block; + for (auto at = std::max(entry.from, address) / block * block; at <= last * block; at += block) { + const auto from = std::max(at, entry.from); + const auto to = std::min(at + block, entry.to); + if (GuestMemory::UnchangedSinceCollected(from, static_cast(to - from), entry.since)) confirmed.push_back({entry.texture, entry.unit, from, to, entry.since, false, true}); + } + } + auto& pending = Pending(); + std::lock_guard lock(pending.mutex); + for (const auto& entry : confirmed) { + auto& texture = *entry.texture; + if (!texture.layerPending[entry.unit]) continue; + if (entry.whole) { + texture.layerGeneration[entry.unit] = generation; + texture.refreshGeneration(); + continue; + } + const auto spanBegin = texture.descriptor.baseAddress / block; + if (texture.completionKept.empty()) texture.completionKept.assign(static_cast((texture.descriptor.baseAddress + texture.guestBytes - 1) / block - spanBegin + 1), 0); + for (auto at = entry.from / block; at <= (entry.to - 1) / block; ++at) texture.completionKept[static_cast(at - spanBegin)] = generation; + } + return generation; +} + bool StorageTexture::AnyPendingOverlaps(std::span> ranges) { if (ranges.empty()) return false; auto& pending = Pending(); @@ -4079,8 +4764,10 @@ bool StorageTexture::AnyPendingOverlaps(std::span range.first && pending.textures.MayOverlap(range.first, static_cast(range.second - range.first)); })) { + for (const auto* texture : pending.textures) { + if (overlapsAny(texture)) return true; + } } for (const auto* texture : pending.flushing) { if (overlapsAny(texture)) return true; @@ -4178,6 +4865,7 @@ bool StorageTexture::AccessKeptByCpu(std::uint64_t address, std::size_t bytes, s // store there would see, and the stamps only grow. GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Hook); std::lock_guard gpu(GuestMemory::GpuMutex()); + if (std::any_of(overlapping.begin(), overlapping.end(), [](const std::shared_ptr& texture) { return texture->drawnArea().drawn; })) return false; if (!blocksKept(overlapping, address, bytes)) return false; const auto site = static_cast(GuestMemory::CurrentReadSite()); std::size_t dropped = 0; @@ -4309,6 +4997,7 @@ std::size_t StorageTexture::DiscardPendingInside(std::uint64_t address, std::siz texture->layerPending.assign(texture->trackedLayers, false); texture->layerOriginal.assign(texture->trackedLayers, false); texture->cancelStoredSaves(); + texture->forgetDrawn(); it = pending.textures.erase(it); ++discarded; } else if (texture != exempt && texture->blockUnits && texture->overlaps(address, bytes)) { @@ -4529,7 +5218,8 @@ bool StorageTexture::clearByKeysFill(DccKeys keys, std::uint8_t key) { filledKeys = DccKeys::Uncompressed; keyProof = {}; forgetBorrowed(0, trackedLayers); - layerGeneration.assign(trackedLayers, GuestMemory::CollectWrites(descriptor.baseAddress, static_cast(guestBytes))); + const auto now = GuestMemory::CollectWrites(descriptor.baseAddress, static_cast(guestBytes)); + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) layerGeneration[layer] = collectedLayerGeneration(layer, now); refreshGeneration(); ++version; if (traceKeys) std::fprintf(stderr, "[dcc-keys] key fill %s clears 0x%llx+0x%llx at once (keys 0x%llx, %zu pending units dropped, dirty %d)\n", DccKeysName(keys), static_cast(descriptor.baseAddress), static_cast(guestBytes), static_cast(descriptor.dccAddress), droppedUnits, wasDirty ? 1 : 0); @@ -4690,7 +5380,8 @@ bool StorageTexture::FillClear(std::span pattern, std::u return true; } if (!watched) captureGuestBytes(nullptr); - layerGeneration.assign(trackedLayers, GuestMemory::CollectWritesUncached(descriptor.baseAddress, static_cast(guestBytes))); + const auto now = GuestMemory::CollectWritesUncached(descriptor.baseAddress, static_cast(guestBytes)); + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) layerGeneration[layer] = collectedLayerGeneration(layer, now, true); refreshGeneration(); forgetBorrowed(0, trackedLayers); MarkDirty(); @@ -4808,12 +5499,45 @@ void StorageTexture::MarkWholeWritten() { captureGuestBytes(nullptr); } uploadedKeys = DccKeys::Uncompressed; - layerGeneration.assign(trackedLayers, GuestMemory::CollectWritesUncached(descriptor.baseAddress, static_cast(guestBytes))); + const auto now = GuestMemory::CollectWritesUncached(descriptor.baseAddress, static_cast(guestBytes)); + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) layerGeneration[layer] = collectedLayerGeneration(layer, now, true); refreshGeneration(); forgetBorrowed(0, trackedLayers); MarkDirty(); } +void StorageTexture::MarkUnitsWritten(const std::vector& units) { + if (!blockUnits || descriptor.dccAddress != 0 || units.size() != trackedLayers || std::all_of(units.begin(), units.end(), [](bool unit) { return unit; })) { + MarkWholeWritten(); + return; + } + const auto runs = unitRuns(units); + if (runs.empty()) return; + if (GuestMemory::Watched(descriptor.baseAddress, static_cast(guestBytes))) { + for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) { + if (!units[unit]) continue; + layerOriginal[unit] = false; + cancelStoredSave(unit); + } + } else { + captureGuestBytes(&units); + } + uploadedKeys = DccKeys::Uncompressed; + for (const auto& [begin, end] : runs) { + const auto first = static_cast(begin / trackedLayerBytes); + const auto count = static_cast((end - begin + trackedLayerBytes - 1) / trackedLayerBytes); + const auto collected = GuestMemory::CollectWritesUncached(descriptor.baseAddress + begin, static_cast(end - begin)); + for (auto unit = first; unit < first + count; ++unit) layerGeneration[unit] = collected; + } + refreshGeneration(); + for (const auto& [begin, end] : runs) { + const auto first = static_cast(begin / trackedLayerBytes); + const auto count = static_cast((end - begin + trackedLayerBytes - 1) / trackedLayerBytes); + forgetBorrowed(first, count); + markLayersPending(first, count); + } +} + // See AdjacentGenerationEnabled above. std::vector StorageTexture::adjacentPendingUnchanged(std::uint64_t firstBlock, std::uint64_t lastBlock) const { std::vector adjacent; @@ -4883,6 +5607,12 @@ void StorageTexture::blockGenerations(std::vector& generations) c } } +std::uint64_t StorageTexture::unitBlockGeneration(std::uint32_t unit, std::uint64_t at) const { + const auto unitGeneration = layerGeneration[unit]; + if (unitGeneration == 0 || !layerPending[unit] || completionKept.empty()) return unitGeneration; + return std::max(unitGeneration, completionKept[static_cast(at / 65536 - descriptor.baseAddress / 65536)]); +} + namespace { std::atomic& ClearHandOverOn() { @@ -5179,10 +5909,12 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { if (!layers[layer]) continue; const auto end = layerBegin(layer) + layerBytes(layer); - for (auto at = layerBegin(layer) & ~(block - 1); at < end; at += block) generations[static_cast((at - spanBegin) / block)] = layerGeneration[layer]; + for (auto at = layerBegin(layer) & ~(block - 1); at < end; at += block) generations[static_cast((at - spanBegin) / block)] = unitBlockGeneration(layer, at); } + const auto drawn = drawnArea(); std::vector changedBlocks(spanBlocks); - const bool tracked = GuestMemory::ChangedBlocks(firstStored, static_cast(lastStored - firstStored), generations, changedBlocks); + std::vector cpuBlocks(drawn.drawn ? spanBlocks : 0); + const bool tracked = GuestMemory::ChangedBlocks(firstStored, static_cast(lastStored - firstStored), generations, changedBlocks, cpuBlocks); std::vector compared(trackedLayers, false); if (!tracked) { awaitStoredSave(&layers); @@ -5207,7 +5939,8 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { if (!layers[layer]) continue; const auto begin = layerBegin(layer); const auto end = begin + layerBytes(layer); - if (tracked ? layerGeneration[layer] == 0 : !compared[layer]) { + const bool unknown = tracked ? layerGeneration[layer] == 0 : !compared[layer]; + if (unknown && !drawn.rect) { unsavedOutsideWatch = unsavedOutsideWatch || !tracked; keep.emplace_back(begin, end); continue; @@ -5217,12 +5950,16 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { const auto to = std::min(at + block, end); if (from >= to) continue; const bool edge = from != at || to != at + block; - if (keptAtUnwatchHolds(from, layer) || (changedBlocks[static_cast((at - spanBegin) / block)] == GuestMemory::BlockWritten && (compared[layer] || !edge || GuestMemory::StoredOver(from, static_cast(to - from), layerGeneration[layer])) && !unchangedSinceBaseline(from, to))) { + const auto index = static_cast((at - spanBegin) / block); + const bool kept = keptAtUnwatchHolds(from, layer); + const bool written = kept || unknown || (changedBlocks[index] == GuestMemory::BlockWritten && (compared[layer] || !edge || GuestMemory::StoredOver(from, static_cast(to - from), unitBlockGeneration(layer, at))) && !unchangedSinceBaseline(from, to)); + if (written && (!drawn.rect || (tracked && !kept && !unknown && cpuBlocks[index] == 0))) { skippedAny = true; skippedLayer[layer] = true; ++skipped; continue; } + if (written && drawn.rect) skippedLayer[layer] = true; if (!keep.empty() && keep.back().second == from) keep.back().second = to; else keep.emplace_back(from, to); } @@ -5236,10 +5973,15 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { const auto first = keep.empty() ? descriptor.baseAddress + guestBytes : keep.front().first; std::fprintf(stderr, "[flush] image 0x%llx+0x%llx keeps %zu CPU-written 64 KiB blocks (first stored byte at +0x%llx, %zu ranges stored, generation %llu)\n", static_cast(descriptor.baseAddress), static_cast(guestBytes), skipped, static_cast(first - descriptor.baseAddress), keep.size(), static_cast(generation)); } + std::vector> spans; + if (drawn.rect) spans = drawnSpans(drawn, keep); + const auto& stores = drawn.rect ? spans : keep; + const auto stampBegin = drawn.rect && !stores.empty() ? stores.front().first : firstStored; + const auto stampEnd = drawn.rect && !stores.empty() ? stores.back().second : lastStored; // Taken before this write-back stamps anything (see advanceAdjacent), with the 64 KiB block span // its stamps cover. - const auto firstBlock = firstStored / block; - const auto lastBlock = (lastStored - 1) / block; + const auto firstBlock = stampBegin / block; + const auto lastBlock = (stampEnd - 1) / block; const auto adjacent = adjacentPendingUnchanged(firstBlock, lastBlock); // After the store: the stored layers are in sync at a fresh generation unless blocks were kept // for the CPU (then the layer is stale there, keeps its old generation and re-uploads at its @@ -5270,13 +6012,13 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { if (!advances && adjacent.empty() && shadowed.empty()) return; const auto now = memoizedCollect ? GuestMemory::CollectWrites(firstStored, static_cast(lastStored - firstStored)) : GuestMemory::CollectWritesUncached(firstStored, static_cast(lastStored - firstStored)); for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { - if (layers[layer] && !skippedLayer[layer]) layerGeneration[layer] = now; + if (layers[layer] && !skippedLayer[layer]) layerGeneration[layer] = collectedLayerGeneration(layer, now, !memoizedCollect); } refreshGeneration(); advanceAdjacent(adjacent, now, firstBlock, lastBlock); if (!shadowed.empty()) markShadowed(now); }; - if (keep.empty()) { + if (stores.empty()) { // Every selected block was written by the CPU since the layer's generation: nothing to // store, the keys stay as they are, and the image is stale there (re-uploaded at its next // use, never from a matching `original`). @@ -5306,6 +6048,24 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { ++Profile().storageDirectWriteBacks; return; } + if (drawn.rect) { + PublishShadow(stampBegin, static_cast(stampEnd - stampBegin), PublishScope::PartialUnits, PublishReason::Upload); + const auto storedBytes = storeDrawnRect(drawn, segments, stores, saveUnits); + countStorageWriteBack(storedBytes, true, true); + if (profile) Profile().storageGpu += timer.lap(); + forgetStored(); + uploadedKeys = DccKeys::Uncompressed; + std::vector> stamps; + for (const auto& [from, to] : stores) { + if (!stamps.empty() && from / block <= (stamps.back().second - 1) / block + 1) stamps.back().second = to; + else stamps.emplace_back(from, to); + } + for (const auto& [from, to] : stamps) GuestMemory::MarkWritten(from, static_cast(to - from)); + settle(true); + commitStoredSave(saveUnits); + ++Profile().storageDirectWriteBacks; + return; + } // The whole-layer copies below overwrite the interior units of the span; the edge units // keep a neighbour's shadowed results in the rest of their bytes, which reach the import // first (this write-back's stamps then make those units stale). @@ -5408,8 +6168,9 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { recorder->EndGpuTiming(timing, storedBytes); recorder->MarkShaderReadsCovered(); recorder->NotePendingWrite(firstStored, static_cast(lastStored - firstStored)); + if (drawn.drawn) noteStore(recorder->LastWriteNote(firstStored, static_cast(lastStored - firstStored))); } - countStorageWriteBack(storedBytes, true); + countStorageWriteBack(storedBytes, true, drawn.drawn); if (profile) Profile().storageGpu += timer.lap(); // The guest bytes now differ from `original`; other caches of the range see the write. forgetStored(); @@ -5450,10 +6211,10 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { overhang = covered >= end; } } - const bool registered = std::all_of(keep.begin(), keep.end(), [](const auto& range) { return RegisteredReadableCovers(range.first, static_cast(range.second - range.first)); }); + const bool registered = std::all_of(stores.begin(), stores.end(), [](const auto& range) { return RegisteredReadableCovers(range.first, static_cast(range.second - range.first)); }); if (unregisteredDrop && !overhang && !registered) { static std::atomic reports{0}; - if (reports.fetch_add(1) < 4) std::fprintf(stderr, "[gpu] storage image 0x%llx+0x%llx: memory no longer registered; %zu pending ranges dropped\n", static_cast(descriptor.baseAddress), static_cast(guestBytes), keep.size()); + if (reports.fetch_add(1) < 4) std::fprintf(stderr, "[gpu] storage image 0x%llx+0x%llx: memory no longer registered; %zu pending ranges dropped\n", static_cast(descriptor.baseAddress), static_cast(guestBytes), stores.size()); unregisteredDropped.fetch_add(1, std::memory_order_relaxed); forgetStored(); for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { @@ -5470,6 +6231,10 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { std::vector unsaved = layerOriginal; unsaved.flip(); for (const auto& [begin, end] : unitRuns(unsaved)) { + if (drawn.rect) { + GuestMemory::ReadCommitted(descriptor.baseAddress + begin, std::span(original).subspan(static_cast(begin), static_cast(end - begin))); + continue; + } for (const auto& [from, to] : keep) { const auto first = std::max(descriptor.baseAddress + begin, from); const auto last = std::min(descriptor.baseAddress + end, to); @@ -5531,7 +6296,9 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { APS5_LOG_CHARS_OUT("StorageTexture writeback submit"); batch.SubmitAndWait(); APS5_LOG_CHARS_OUT("StorageTexture writeback done"); - countStorageWriteBack(guestBytes, false); + std::uint64_t storedBytes = 0; + for (const auto& [from, to] : stores) storedBytes += to - from; + countStorageWriteBack(drawn.rect ? storedBytes : guestBytes, false, drawn.drawn); if (profile) Profile().storageGpu += timer.lap(); if (dump) { char name[64]; @@ -5546,7 +6313,7 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { // Store only the kept ranges' blocks that changed, so concurrent CPU writes to untouched texels // survive; `original` follows for the stored layers (the others' guest bytes are unchanged). const auto current = host.Bytes(); - for (const auto& [from, to] : keep) { + for (const auto& [from, to] : stores) { const auto offset = static_cast(from - descriptor.baseAddress); const auto length = static_cast(to - from); GuestMemory::WriteChangedCommitted(from, current.subspan(offset, length), std::span(original).subspan(offset, length)); @@ -5582,6 +6349,7 @@ StorageTexture::~StorageTexture() { { auto& pending = Pending(); std::lock_guard lock(pending.mutex); + forgetDrawn(); if (dirty) { dirty = false; pending.textures.remove(this); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp index d2a6964f6b..344d80c594 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp @@ -4,8 +4,6 @@ #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include "prx/libSceAgcDriver/Graphics/shaders/TextureDetile_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/CmaskClear_spv.h" -#include "prx/libSceAgcDriver/Graphics/shaders/DepthSampleMultisample_spv.h" -#include "prx/libSceAgcDriver/Graphics/shaders/DepthSampleSingle_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/TextureDetileImage_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/TextureRepack_spv.h" #include @@ -110,6 +108,7 @@ void TextureDetiler::release() noexcept { for (const auto pool : readbackDescriptorSets.pools) context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); for (const auto& entry : readbackPipelines) context.Function("vkDestroyPipeline")(context.device, entry.second, nullptr); for (const auto shader : readbackModules) if (shader) context.Function("vkDestroyShaderModule")(context.device, shader, nullptr); + if (multisampleUploadModule) context.Function("vkDestroyShaderModule")(context.device, multisampleUploadModule, nullptr); if (readbackPipelineLayout) context.Function("vkDestroyPipelineLayout")(context.device, readbackPipelineLayout, nullptr); if (readbackDescriptorLayout) context.Function("vkDestroyDescriptorSetLayout")(context.device, readbackDescriptorLayout, nullptr); for (const auto pool : cmaskDescriptorSets.pools) context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); @@ -119,11 +118,6 @@ void TextureDetiler::release() noexcept { cmaskErrors.reset(); for (const auto pool : descriptorSets.pools) context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); for (const auto pool : imageDescriptorSets.pools) context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); - for (const auto pool : depthSampleSets.pools) context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); - for (const auto pipeline : depthSamplePipelines) if (pipeline) context.Function("vkDestroyPipeline")(context.device, pipeline, nullptr); - if (depthSamplePipelineLayout) context.Function("vkDestroyPipelineLayout")(context.device, depthSamplePipelineLayout, nullptr); - if (depthSampleLayout) context.Function("vkDestroyDescriptorSetLayout")(context.device, depthSampleLayout, nullptr); - if (depthSampler) context.Function("vkDestroySampler")(context.device, depthSampler, nullptr); if (imageModule) context.Function("vkDestroyShaderModule")(context.device, imageModule, nullptr); if (repackPipeline) context.Function("vkDestroyPipeline")(context.device, repackPipeline, nullptr); if (repackModule) context.Function("vkDestroyShaderModule")(context.device, repackModule, nullptr); @@ -401,79 +395,6 @@ void TextureDetiler::DispatchCmaskClear(VkCommandBuffer commands, VkBuffer cmask barrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, anyAccess); } -void TextureDetiler::DispatchDepthSample(VkCommandBuffer commands, VkImageView depth, bool multisampled, VkImageView target, std::uint32_t width, std::uint32_t height, std::uint32_t sample) { - Require(commands != VK_NULL_HANDLE && depth != VK_NULL_HANDLE && target != VK_NULL_HANDLE, "a depth sample copy needs a command buffer, the depth view and the target view"); - if (depthSamplePipelineLayout == VK_NULL_HANDLE) { - VkSamplerCreateInfo samplerInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; - samplerInfo.magFilter = VK_FILTER_NEAREST; - samplerInfo.minFilter = VK_FILTER_NEAREST; - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - Check(context.Function("vkCreateSampler")(context.device, &samplerInfo, nullptr, &depthSampler), "vkCreateSampler depth sample copy"); - std::array bindings{}; - bindings[0] = {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT, &depthSampler}; - bindings[1] = {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}; - VkDescriptorSetLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; - layoutInfo.bindingCount = static_cast(bindings.size()); - layoutInfo.pBindings = bindings.data(); - Check(context.Function("vkCreateDescriptorSetLayout")(context.device, &layoutInfo, nullptr, &depthSampleLayout), "vkCreateDescriptorSetLayout depth sample copy"); - const VkPushConstantRange pushRange{VK_SHADER_STAGE_COMPUTE_BIT, 0, 3 * sizeof(std::uint32_t)}; - VkPipelineLayoutCreateInfo pipelineLayoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; - pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = &depthSampleLayout; - pipelineLayoutInfo.pushConstantRangeCount = 1; - pipelineLayoutInfo.pPushConstantRanges = &pushRange; - Check(context.Function("vkCreatePipelineLayout")(context.device, &pipelineLayoutInfo, nullptr, &depthSamplePipelineLayout), "vkCreatePipelineLayout depth sample copy"); - } - auto& depthSamplePipeline = depthSamplePipelines[multisampled ? 1u : 0u]; - if (depthSamplePipeline == VK_NULL_HANDLE) { - VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; - moduleInfo.codeSize = multisampled ? sizeof(DEPTH_SAMPLE_MULTISAMPLE_SPV) : sizeof(DEPTH_SAMPLE_SINGLE_SPV); - moduleInfo.pCode = multisampled ? DEPTH_SAMPLE_MULTISAMPLE_SPV : DEPTH_SAMPLE_SINGLE_SPV; - VkShaderModule depthModule = VK_NULL_HANDLE; - Check(context.Function("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &depthModule), "vkCreateShaderModule depth sample copy"); - VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; - pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; - pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; - pipelineInfo.stage.module = depthModule; - pipelineInfo.stage.pName = "main"; - pipelineInfo.layout = depthSamplePipelineLayout; - const auto created = context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &depthSamplePipeline); - context.Function("vkDestroyShaderModule")(context.device, depthModule, nullptr); - Check(created, "vkCreateComputePipelines depth sample copy"); - } - const auto set = allocateFrom(depthSampleSets, depthSampleLayout, {{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 64}, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 64}}, "vkCreateDescriptorPool depth sample copy", "vkAllocateDescriptorSets depth sample copy", "vkResetDescriptorPool depth sample copy"); - const VkDescriptorImageInfo depthInfo{VK_NULL_HANDLE, depth, VK_IMAGE_LAYOUT_GENERAL}; - const VkDescriptorImageInfo targetInfo{VK_NULL_HANDLE, target, VK_IMAGE_LAYOUT_GENERAL}; - std::array writes{}; - for (std::uint32_t index = 0; index < writes.size(); ++index) { - writes[index].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writes[index].dstSet = set; - writes[index].dstBinding = index; - writes[index].descriptorCount = 1; - writes[index].descriptorType = index == 0 ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; - } - writes[0].pImageInfo = &depthInfo; - writes[1].pImageInfo = &targetInfo; - context.Function("vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); - const auto barrier = [&](VkPipelineStageFlags source, VkPipelineStageFlags destination, VkAccessFlags sourceAccess, VkAccessFlags destinationAccess) { - VkMemoryBarrier memory{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; - memory.srcAccessMask = sourceAccess; - memory.dstAccessMask = destinationAccess; - context.Function("vkCmdPipelineBarrier")(commands, source, destination, 0, 1, &memory, 0, nullptr, 0, nullptr); - }; - constexpr VkAccessFlags anyWrite = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT; - constexpr VkAccessFlags anyAccess = anyWrite | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_HOST_READ_BIT; - barrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, anyWrite, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); - context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, depthSamplePipeline); - context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, depthSamplePipelineLayout, 0, 1, &set, 0, nullptr); - const std::array push{width, height, sample}; - context.Function("vkCmdPushConstants")(commands, depthSamplePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data()); - context.Function("vkCmdDispatch")(commands, (width + 7u) / 8u, (height + 7u) / 8u, 1); - barrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, anyAccess); -} - void TextureDetiler::DispatchImage(VkCommandBuffer commands, TextureTileMode tileMode, std::uint32_t elementBytes, VkBuffer tiled, std::uint64_t tiledOffset, VkImageView view, const TileMipLayout& layout, bool retile, std::uint32_t slice, const DetileWindow& window) { Require(commands != VK_NULL_HANDLE, "texture detiling requires an active command buffer"); Require(window.rangeBegin < window.rangeEnd && window.tiledBase <= window.rangeBegin, "texture detiling window is empty"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp index ff1bed0787..494c38359e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp @@ -466,23 +466,26 @@ bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t leve return true; } -GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed) { +GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed, bool keysReadAsStored) { const bool layered = viewed.dimension == TextureDimension::k2DArray || viewed.dimension == TextureDimension::k1DArray || viewed.dimension == TextureDimension::kCube; - if (!layered || viewed.baseArray == 0 || viewed.baseArray > viewed.depthOrLastArray || viewed.dccAddress != 0) return viewed; + if (!layered || viewed.baseArray == 0 || viewed.baseArray > viewed.depthOrLastArray || (viewed.dccAddress != 0 && !keysReadAsStored)) return viewed; + std::uint32_t sliceMask = 0; if (const auto xorMode = XorSwizzleMode(viewed.tileMode); xorMode != 0u) { const auto* equation = FindTextureSwizzleEquation(xorMode, BytesPerElement(viewed.format)); if (equation == nullptr) return viewed; std::uint32_t sliceBits = 0; for (const auto bit : equation->bits) sliceBits |= bit >> 24u; - const auto sliceMask = sliceBits == 0 ? 0u : (1u << std::bit_width(sliceBits)) - 1u; - if ((viewed.baseArray & sliceMask) != 0) return viewed; + sliceMask = sliceBits == 0 ? 0u : (1u << std::bit_width(sliceBits)) - 1u; } + const auto first = viewed.baseArray & ~sliceMask; + if (first == 0) return viewed; const auto geometry = DescribeSurface(viewed); if (geometry.thick || geometry.layers != viewed.depthOrLastArray + 1u) return viewed; auto window = viewed; - window.baseAddress = viewed.baseAddress + geometry.GuestLayerOffset(viewed.baseArray); - window.depthOrLastArray = viewed.depthOrLastArray - viewed.baseArray; - window.baseArray = 0; + window.baseAddress = viewed.baseAddress + geometry.GuestLayerOffset(first); + window.depthOrLastArray = viewed.depthOrLastArray - first; + window.baseArray = viewed.baseArray - first; + window.dccAddress = 0; return window; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp index cd2998c06b..94b0655305 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp @@ -22,7 +22,7 @@ namespace { constexpr std::uint64_t UnitBytes = 65536; constexpr std::size_t ReasonCount = static_cast(PublishReason::Count); -constexpr const char* ReasonNames[ReasonCount] = {"hook", "region", "indirect", "copy-src", "copy-dst", "fill", "fill-clear", "label", "keys", "scanout", "validate", "upload", "tail", "evict", "retire", "teardown"}; +constexpr const char* ReasonNames[ReasonCount] = {"hook", "region", "indirect", "copy-src", "copy-dst", "fill", "fill-clear", "label", "keys", "scanout", "validate", "upload", "tail", "evict", "retire", "teardown", "address"}; std::uint64_t BudgetBytes() { static const std::uint64_t budget = [] { @@ -454,6 +454,7 @@ PublishReason PublishReasonFor(const char* flushReason) { if (is("packet store")) return PublishReason::Label; if (is("buffer copy count") || is("dispatch-cache entry")) return PublishReason::Validate; if (is("storage image creation") || is("storage refresh")) return PublishReason::Upload; + if (is("address-based heaps")) return PublishReason::Address; return PublishReason::Hook; } @@ -472,9 +473,9 @@ ShadowSlabPin::~ShadowSlabPin() { slab->pins.fetch_sub(1, std::memory_order_relaxed); } -std::size_t PublishShadow(std::uint64_t address, std::size_t bytes, PublishScope scope, PublishReason reason) { +std::size_t PublishShadow(std::uint64_t address, std::size_t bytes, PublishScope scope, PublishReason reason, bool waited) { if (!UnitShadowEnabled() || scope == PublishScope::None || bytes == 0) return 0; - if (Recorder::InCompletion() && GuestMemory::CurrentReadSite() == GuestMemory::ReadSite::Store) { + if (!waited && Recorder::InCompletion() && GuestMemory::CurrentReadSite() == GuestMemory::ReadSite::Store) { // The store's own stamp makes the unit stale; a publish recorded now would land over the // store when the batch runs (the completion is not waited for). Stats().skippedInCompletion.fetch_add(1, std::memory_order_relaxed); @@ -492,11 +493,11 @@ std::size_t PublishShadow(std::uint64_t address, std::size_t bytes, PublishScope return units; } -bool AnyShadowedOverlaps(std::uint64_t address, std::size_t bytes) { - if (!UnitShadowEnabled() || bytes == 0) return false; +namespace { + +bool anyShadowedLocked(const Shadows& registry, std::uint64_t address, std::size_t bytes) { + if (bytes == 0) return false; const auto end = bytes > std::numeric_limits::max() - address ? std::numeric_limits::max() : address + bytes; - auto& registry = Registry(); - std::lock_guard lock(registry.mutex); return anyOverlapping(registry, address, end, [&](const std::shared_ptr& shadow) { if (shadow->liveUnits == 0) return false; const auto begin = std::max(address, shadow->importBase); @@ -508,10 +509,21 @@ bool AnyShadowedOverlaps(std::uint64_t address, std::size_t bytes) { }); } +} + +bool AnyShadowedOverlaps(std::uint64_t address, std::size_t bytes) { + if (!UnitShadowEnabled() || bytes == 0) return false; + auto& registry = Registry(); + std::lock_guard lock(registry.mutex); + return anyShadowedLocked(registry, address, bytes); +} + bool AnyShadowedOverlaps(std::span> ranges) { - if (!UnitShadowEnabled()) return false; + if (!UnitShadowEnabled() || ranges.empty()) return false; + auto& registry = Registry(); + std::lock_guard lock(registry.mutex); for (const auto& [begin, end] : ranges) { - if (end > begin && AnyShadowedOverlaps(begin, static_cast(end - begin))) return true; + if (end > begin && anyShadowedLocked(registry, begin, static_cast(end - begin))) return true; } return false; } diff --git a/core/libs/prx/libSceAgcDriver/Resource/include/Lifecycle.hpp b/core/libs/prx/libSceAgcDriver/Resource/include/Lifecycle.hpp index d2485e8727..8d49017a87 100644 --- a/core/libs/prx/libSceAgcDriver/Resource/include/Lifecycle.hpp +++ b/core/libs/prx/libSceAgcDriver/Resource/include/Lifecycle.hpp @@ -1,4 +1,13 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_RESOURCE_INCLUDE_LIFECYCLE_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_RESOURCE_INCLUDE_LIFECYCLE_HPP +#include "SceTypes.hpp" + +extern "C" { + +uint32_t APS5_VABI sceAgcDriverInitResourceRegistration(void); +uint32_t APS5_VABI sceAgcDriverQueryResourceRegistrationUserMemoryRequirements(uint64_t* sizeInBytes); + +} + #endif diff --git a/core/libs/prx/libSceAgcDriver/Resource/include/Registration.hpp b/core/libs/prx/libSceAgcDriver/Resource/include/Registration.hpp index d30f27839a..6cbdcce863 100644 --- a/core/libs/prx/libSceAgcDriver/Resource/include/Registration.hpp +++ b/core/libs/prx/libSceAgcDriver/Resource/include/Registration.hpp @@ -5,4 +5,33 @@ extern "C" bool AgcDriverWorkloadStreamRegistered_nid_postfix(std::uint32_t streamId); +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAgcDriverRegisterOwner(uint32_t* ownerHandle, const char* name); +int APS5_VABI sceAgcDriverRegisterResource(uint32_t* resourceHandle, uint32_t ownerHandle, const void* memory, size_t size, const char* name, uint32_t type, uint64_t userData); +int APS5_VABI sceAgcDriverRegisterMultipleResources(uint32_t* resourceHandle, uint32_t ownerHandle, const void* resources, uint32_t resourceCount); +int APS5_VABI sceAgcDriverRegisterWorkloadStream(uint32_t streamId, const void* stream); +int APS5_VABI sceAgcDriverUnregisterOwnerAndResources(uint32_t ownerHandle); +int APS5_VABI sceAgcDriverUnregisterResource(uint32_t resourceHandle); +int APS5_VABI sceAgcDriverRegisterGdsResource(uint32_t* resourceHandle, uint32_t ownerHandle, uint32_t gdsOffset, uint32_t gdsSize, const char* name, uint32_t type, uint64_t userData); +int APS5_VABI sceAgcDriverUnregisterAllResourcesForOwner(uint32_t ownerHandle); +int APS5_VABI sceAgcDriverUnregisterWorkloadStream(uint32_t streamId); +int APS5_VABI sceAgcDriverGetDefaultOwner(uint32_t* ownerHandle); +int APS5_VABI sceAgcDriverGetResourceRegistrationMaxNameLength(uint32_t* maxLength); +int APS5_VABI sceAgcDriverGetOwnerName(uint32_t ownerHandle, const char** name); +int APS5_VABI sceAgcDriverGetResourceName(uint32_t resourceHandle, const char** name); +int APS5_VABI sceAgcDriverGetResourceType(uint32_t resourceHandle, uint32_t* type); +int APS5_VABI sceAgcDriverGetResourceUserData(uint32_t resourceHandle, uint64_t* userData); +int APS5_VABI sceAgcDriverSetResourceUserData(uint32_t resourceHandle, uint64_t userData); +int APS5_VABI sceAgcDriverGetResourceBaseAddressAndSizeInBytes(uint32_t resourceHandle, void** memory, size_t* size); +int APS5_VABI sceAgcDriverGetResourceShaderGuid(uint32_t resourceHandle, void* guid); +int APS5_VABI sceAgcDriverFindResourcesPublic(const void* callback, void* userData); +int APS5_VABI sceAgcDriverRegisterOwnerForSystem(); +int APS5_VABI sceAgcDriverInitResourceRegistrationInterface(); +int APS5_VABI sceAgcDriverSetResourceRegistrationInfo(); + +} + #endif diff --git a/core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp b/core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp index f3fbd0ba67..7cae8c3afb 100644 --- a/core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp +++ b/core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp @@ -9,6 +9,9 @@ // Resource registration feeds GPU debugging tools, none of which are attached; the game treats this code as benign. static constexpr int SCE_AGC_ERROR_RESOURCE_REGISTRATION_UNAVAILABLE = static_cast(0x8A6C9018); static constexpr uint32_t RESOURCE_REGISTRATION_MAX_NAME_LENGTH = 0xfc; +static constexpr int SCE_AGC_ERROR_WORKLOAD_STREAM_INVALID_ID = static_cast(0x8A6C0033); +static constexpr int SCE_AGC_ERROR_WORKLOAD_STREAM_NULL = static_cast(0x8A6C0035); +static constexpr int SCE_AGC_ERROR_WORKLOAD_STREAM_NOT_REGISTERED = static_cast(0x8A6C003A); static constexpr uint32_t WORKLOAD_STREAM_MIN_ID = 1; static constexpr uint32_t WORKLOAD_STREAM_MAX_ID = 31; @@ -49,10 +52,11 @@ int APS5_VABI sceAgcDriverRegisterMultipleResources(uint32_t* resourceHandle, ui } int APS5_VABI sceAgcDriverRegisterWorkloadStream(uint32_t stream_id, const void* stream) { - if (stream != nullptr && stream_id >= WORKLOAD_STREAM_MIN_ID && stream_id <= WORKLOAD_STREAM_MAX_ID) { - std::lock_guard lock(g_workloadStreamLock); - g_workloadStreams |= 1u << stream_id; - } + if (stream_id < WORKLOAD_STREAM_MIN_ID || stream_id > WORKLOAD_STREAM_MAX_ID) return SCE_AGC_ERROR_WORKLOAD_STREAM_INVALID_ID; + std::lock_guard lock(g_workloadStreamLock); + if ((g_workloadStreams & (1u << stream_id)) != 0) return SCE_AGC_ERROR_WORKLOAD_STREAM_INVALID_ID; + if (stream == nullptr) return SCE_AGC_ERROR_WORKLOAD_STREAM_NULL; + g_workloadStreams |= 1u << stream_id; return 0; } @@ -83,10 +87,10 @@ int APS5_VABI sceAgcDriverUnregisterAllResourcesForOwner(uint32_t owner_handle) } int APS5_VABI sceAgcDriverUnregisterWorkloadStream(uint32_t stream_id) { - if (stream_id >= WORKLOAD_STREAM_MIN_ID && stream_id <= WORKLOAD_STREAM_MAX_ID) { - std::lock_guard lock(g_workloadStreamLock); - g_workloadStreams &= ~(1u << stream_id); - } + if (stream_id < WORKLOAD_STREAM_MIN_ID || stream_id > WORKLOAD_STREAM_MAX_ID) return SCE_AGC_ERROR_WORKLOAD_STREAM_INVALID_ID; + std::lock_guard lock(g_workloadStreamLock); + if ((g_workloadStreams & (1u << stream_id)) == 0) return SCE_AGC_ERROR_WORKLOAD_STREAM_NOT_REGISTERED; + g_workloadStreams &= ~(1u << stream_id); return 0; } diff --git a/core/libs/prx/libSceAgcDriver/State/include/Configuration.hpp b/core/libs/prx/libSceAgcDriver/State/include/Configuration.hpp index aadd076ce7..54c8fc2cca 100644 --- a/core/libs/prx/libSceAgcDriver/State/include/Configuration.hpp +++ b/core/libs/prx/libSceAgcDriver/State/include/Configuration.hpp @@ -1,4 +1,14 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_STATE_INCLUDE_CONFIGURATION_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_STATE_INCLUDE_CONFIGURATION_HPP +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAgcDriverSetHsOffchipParam(uint64_t value0, uint64_t value1, uint64_t value2); +int APS5_VABI sceAgcDriverSetTFRing(const volatile void* base, uint32_t size); +int APS5_VABI sceAgcDriverGetTFRing(uintptr_t* base, uint32_t* size); + +} + #endif diff --git a/core/libs/prx/libSceAgcDriver/State/include/Status.hpp b/core/libs/prx/libSceAgcDriver/State/include/Status.hpp index d7184e323f..822d5016ab 100644 --- a/core/libs/prx/libSceAgcDriver/State/include/Status.hpp +++ b/core/libs/prx/libSceAgcDriver/State/include/Status.hpp @@ -1,4 +1,17 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_STATE_INCLUDE_STATUS_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_STATE_INCLUDE_STATUS_HPP +#include "SceTypes.hpp" + +extern "C" { + +bool APS5_VABI sceAgcDriverIsCaptureInProgress(void); +bool APS5_VABI sceAgcDriverIsTraceInProgress(void); +bool APS5_VABI sceAgcDriverIsSubmitValidationEnabled(void); +int APS5_VABI sceAgcDriverRequestCaptureStart(const char* path); +int APS5_VABI sceAgcDriverRequestCaptureStop(); +int APS5_VABI sceAgcDriverTriggerCapture(); + +} + #endif diff --git a/core/libs/prx/libSceAgcDriver/State/src/Status.cpp b/core/libs/prx/libSceAgcDriver/State/src/Status.cpp index ae1a8543f7..825a9e9001 100644 --- a/core/libs/prx/libSceAgcDriver/State/src/Status.cpp +++ b/core/libs/prx/libSceAgcDriver/State/src/Status.cpp @@ -5,6 +5,12 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int CAPTURE_UNAVAILABLE = static_cast(0x8A6C1000); + +} + extern "C" { bool APS5_VABI sceAgcDriverIsCaptureInProgress(void) { @@ -21,18 +27,15 @@ bool APS5_VABI sceAgcDriverIsSubmitValidationEnabled(void) { int APS5_VABI sceAgcDriverRequestCaptureStart(const char* path) { (void)path; - NotImplemented_nid_no_patch(__func__); - return 0; + return CAPTURE_UNAVAILABLE; } int APS5_VABI sceAgcDriverRequestCaptureStop() { - NotImplemented_nid_no_patch(__func__); - return 0; + return CAPTURE_UNAVAILABLE; } int APS5_VABI sceAgcDriverTriggerCapture() { - NotImplemented_nid_no_patch(__func__); - return 0; + return CAPTURE_UNAVAILABLE; } } diff --git a/core/libs/prx/libSceAgcDriver/Submit/include/Acb.hpp b/core/libs/prx/libSceAgcDriver/Submit/include/Acb.hpp index d6d7244c58..d39b6ce6e9 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/include/Acb.hpp +++ b/core/libs/prx/libSceAgcDriver/Submit/include/Acb.hpp @@ -4,6 +4,6 @@ #include "SceTypes.hpp" extern "C" int APS5_VABI sceAgcDriverSubmitAcb(std::uint32_t queue, const Packet* packet); -extern "C" int APS5_VABI sceAgcDriverSubmitMultiAcbs(std::uint32_t queue, std::uint32_t* const* acbs, const std::uint32_t* sizes_in_dwords, std::uint32_t count); +extern "C" int APS5_VABI sceAgcDriverSubmitMultiAcbs(std::uint32_t queue, std::uint32_t* const* acbs, const std::uint32_t* sizesInDwords, std::uint32_t count); #endif diff --git a/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp b/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp index 52fb4cb31d..bf355fbe65 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp +++ b/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp @@ -4,12 +4,10 @@ #include "SceTypes.hpp" #include -struct Packet; - extern "C" { -int APS5_VABI sceAgcDriverSubmitCommandBuffer(void* queue_context, const Packet* packet); -int APS5_VABI sceAgcDriverSubmitMultiCommandBuffers(void* queue_context, uint32_t* const* command_buffers, const uint32_t* sizes_in_dwords, uint32_t count); +int APS5_VABI sceAgcDriverSubmitCommandBuffer(void* queueContext, const Packet* packet); +int APS5_VABI sceAgcDriverSubmitMultiCommandBuffers(void* queueContext, uint32_t* const* commandBuffers, const uint32_t* sizesInDwords, uint32_t count); } diff --git a/core/libs/prx/libSceAgcDriver/Submit/include/Dcb.hpp b/core/libs/prx/libSceAgcDriver/Submit/include/Dcb.hpp index 9e8fe9fdf7..78cfda269b 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/include/Dcb.hpp +++ b/core/libs/prx/libSceAgcDriver/Submit/include/Dcb.hpp @@ -18,4 +18,15 @@ extern "C" int APS5_VABI sceAgcDriverAgrSubmitDcb(const Packet* packet); extern "C" int APS5_VABI sceAgcDriverSubmitMultiDcbs(std::uint32_t* const* dcbGpuAddrs, const std::uint32_t* dcbSizesInDwords, std::uint32_t count); extern "C" int APS5_VABI sceAgcDriverAgrSubmitMultiDcbs(std::uint32_t* const* dcbGpuAddrs, const std::uint32_t* dcbSizesInDwords, std::uint32_t count); +extern "C" { + +std::uint32_t APS5_VABI sceAgcDriverGetSetWorkloadsActivePacketSize(); +std::uint32_t APS5_VABI sceAgcDriverGetSetWorkloadCompletePacketSize(); +std::uint32_t APS5_VABI sceAgcDriverGetSetWorkloadStreamInactivePacketSize(); +std::uint32_t APS5_VABI sceAgcDriverNopDataGetPacketSize(std::uint32_t bytes); +std::uint32_t APS5_VABI sceAgcDriverUserDataGetPacketSize(std::uint32_t bytes); +std::uint32_t APS5_VABI sceAgcDriverWaitUntilSafeForRendering(std::uint32_t** command, std::uint32_t capacity, std::uint32_t mode, std::uint32_t handle, int index); + +} + #endif diff --git a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp index be639faf70..f361aacbe8 100644 --- a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp +++ b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/include/AgcDriver.hpp" #include #include #include @@ -57,6 +58,16 @@ int APS5_VABI sceAgcDriverGetHsOffchipParam(uint16_t* first, uint16_t* second) { return 0; } +int APS5_VABI sceAgcDriverNopDataWritePacket(void* destination, uint32_t address, const void* data, uint32_t bytes) { + const auto alignedBytes = (static_cast(bytes) + 3) & ~std::uint64_t{3}; + const auto totalBytes = alignedBytes + 8; + std::memset(destination, 0, static_cast(totalBytes)); + const auto header = (static_cast(totalBytes) << 14) + 0x3ffe0000U | 0xc0001000U; + std::memcpy(destination, &header, sizeof(header)); + std::memcpy(static_cast(destination) + 4, &address, sizeof(address)); + if (data != nullptr && bytes != 0) std::memcpy(static_cast(destination) + 8, data, bytes); + return data == nullptr || bytes == 0 ? 2 : static_cast(totalBytes / 4); +} int APS5_VABI sceAgcDriverSetValidationDisabled() { return DEBUG_UNAVAILABLE; @@ -154,9 +165,8 @@ int APS5_VABI sceAgcDriverAcquireHDRScopesACQ() { return 0; } -int APS5_VABI sceAgcDriverGetCoredumpAddress() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI sceAgcDriverGetCoredumpAddress() { + return nullptr; } int APS5_VABI sceAgcDriverHp3dLockEnd() { @@ -174,19 +184,13 @@ int APS5_VABI sceAgcDriverIsEsvmProcess() { return 0; } -int APS5_VABI sceAgcDriverNopDataWritePacket() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceAgcDriverReleaseHDRScopesACQ() { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceAgcDriverSetTargetRingForDiag() { - NotImplemented_nid_no_patch(__func__); - return 0; + return DRIVER_FEATURE_UNAVAILABLE; } int APS5_VABI sceAgcDriverSetValueToSharedGds() { diff --git a/core/libs/prx/libSceAgcDriver/include/AgcDriver.hpp b/core/libs/prx/libSceAgcDriver/include/AgcDriver.hpp new file mode 100644 index 0000000000..6c6e8fea9b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/include/AgcDriver.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAgcDriverAcquireRazorACQ(void); +int APS5_VABI sceAgcDriverCreateQueue(void); +int APS5_VABI sceAgcDriverCwsrResumeAcq(void); +int APS5_VABI sceAgcDriverCwsrSuspendAcq(void); +int APS5_VABI sceAgcDriverDebugHardwareStatus(void); +int APS5_VABI sceAgcDriverDestroyQueue(void); +int APS5_VABI sceAgcDriverGetPaDebugInterfaceVersion(void); +int APS5_VABI sceAgcDriverGetRegShadowInfo(void); +int APS5_VABI sceAgcDriverGetRegShadowInfoAgr(void); +int APS5_VABI sceAgcDriverGetReservedDmemForAgc(void); +int APS5_VABI sceAgcDriverGetWorkloadStreamInfo(void); +int APS5_VABI sceAgcDriverIDHSSubmit(void); +int APS5_VABI sceAgcDriverModuleRegistration(void); +int APS5_VABI sceAgcDriverNotifyDefaultStates(void); +int APS5_VABI sceAgcDriverPassInfoDownward(void); +int APS5_VABI sceAgcDriverPatchClearState(void); +int APS5_VABI sceAgcDriverSetFlip(void); +int APS5_VABI sceAgcDriverSetHsOffchipParamDirect(void); +int APS5_VABI sceAgcDriverSetWorkloadComplete(void); +int APS5_VABI sceAgcDriverSetWorkloadsActive(void); +int APS5_VABI sceAgcDriverSetupAsyncGraphics(void); +int APS5_VABI sceAgcDriverSetupRegisterShadow(void); +int APS5_VABI sceAgcDriverSubmitMultiCommandBuffersDirect(void); +int APS5_VABI sceAgcDriverSubmitToHDRScopesACQ(void); +int APS5_VABI sceAgcDriverSubmitToRazorACQ(void); +int APS5_VABI sceAgcDriverSuspendPointSubmit(void); +int APS5_VABI sceAgcDriverSuspendPointSubmitDirect(void); +int APS5_VABI sceAgcDriverSysEnableSubmitDone45Exception(void); +int APS5_VABI sceAgcDriverSysGetClientNumber(void); +int APS5_VABI sceAgcDriverSysIsGameClosed(void); +int APS5_VABI sceAgcDriverTmpInitIdhs(void); +int APS5_VABI sceAgcDriverUserDataWritePacket(void); +int APS5_VABI sceAgcDriverUserDataWritePopMarker(void); +int APS5_VABI sceAgcDriverUserDataWritePushMarker(void); +int APS5_VABI sceAgcDriverUserDataWriteSetMarker(void); +int APS5_VABI sceAgcDriverGetShaderDebuggingStatus(); +int APS5_VABI sceAgcDriverRegisterDefaultOwner(uint32_t owner); +int APS5_VABI sceAgcDriverSetValidationErrorOutputFrequency(uint32_t frequency); +int APS5_VABI sceAgcDriverSetSubmitValidationMode(uint32_t mode); +int APS5_VABI sceAgcDriverGetSubmitValidationConfig(void* config); +int APS5_VABI sceAgcDriverGetSubmitValidationMode(uint32_t* mode); +int APS5_VABI sceAgcDriverSetSubmitValidationConfig(const void* config); +int APS5_VABI sceAgcDriverGetHsOffchipParam(uint16_t* first, uint16_t* second); +int APS5_VABI sceAgcDriverNopDataWritePacket(void* destination, uint32_t address, const void* data, uint32_t bytes); +int APS5_VABI sceAgcDriverSetValidationDisabled(); +int APS5_VABI sceAgcDriverGetCaptureConfig(); +int APS5_VABI sceAgcDriverSetCaptureConfig(); +int APS5_VABI sceAgcDriverGetGpuPrintfWorkArea(); +int APS5_VABI sceAgcDriverSetGpuPrintfEnabled(); +int APS5_VABI sceAgcDriverDbgReadRegister(); +int APS5_VABI sceAgcDriverDbgWriteRegister(); +int APS5_VABI sceAgcDriverGetGpuRefClks(); +int APS5_VABI sceAgcDriverIsSuspendPointInFlight(); +int APS5_VABI sceAgcDriverIsPaDebug(); +int APS5_VABI sceAgcDriverGetExceptionStatus(); +int APS5_VABI sceAgcDriverGetTraceInitiator(); +int APS5_VABI sceAgcDriverRegisterGpuResetCallbacks(); +int APS5_VABI sceAgcDriverUnregisterGpuResetCallbacks(); +int APS5_VABI sceAgdResetGpu(); +int APS5_VABI sceAgcDriverSysSubmitFlipHandleProxy(); +int APS5_VABI sceAgcDriverUserDataImmediateWrite(); +int APS5_VABI sceAgcDriverUserDataImmediateWritePacket(); +int APS5_VABI sceAgcDriverGetCoredumpMode(uint32_t* mode); +int APS5_VABI sceAgcDriverAllocateToolMemoryForGpuReset(uint64_t, uint64_t, uint64_t, void* range); +int APS5_VABI sceAgcDriverGetAllocatedToolMemoryForGpuReset(uint64_t, void* range); +int APS5_VABI sceAgcDriverAcquireHDRScopesACQ(); +void* APS5_VABI sceAgcDriverGetCoredumpAddress(); +int APS5_VABI sceAgcDriverHp3dLockEnd(); +int APS5_VABI sceAgcDriverHp3dLockStart(); +int APS5_VABI sceAgcDriverIsEsvmProcess(); +int APS5_VABI sceAgcDriverReleaseHDRScopesACQ(); +int APS5_VABI sceAgcDriverSetTargetRingForDiag(); +int APS5_VABI sceAgcDriverSetValueToSharedGds(); +int APS5_VABI sceAgcDriverSetWorkloadStreamInactive(); +int APS5_VABI sceAgcDriverSubmitReprojectionAcb(); +int APS5_VABI sceAgcDriverSuspendPointSubmitCdbg(); +int APS5_VABI sceAgcDriverSysGetGfxAppGpuResetStatus(); + +} diff --git a/core/libs/prx/libSceAgcDriver/libSceAgcDriver.hpp b/core/libs/prx/libSceAgcDriver/libSceAgcDriver.hpp new file mode 100644 index 0000000000..342610270a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/libSceAgcDriver.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "prx/libSceAgcDriver/Eq/include/Event.hpp" +#include "prx/libSceAgcDriver/Eq/include/Query.hpp" +#include "prx/libSceAgcDriver/include/AgcDriver.hpp" +#include "prx/libSceAgcDriver/Resource/include/Lifecycle.hpp" +#include "prx/libSceAgcDriver/Resource/include/Registration.hpp" +#include "prx/libSceAgcDriver/State/include/Configuration.hpp" +#include "prx/libSceAgcDriver/State/include/Status.hpp" +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" +#include "prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" diff --git a/core/libs/prx/libSceAgcDriver/tests/AsyncLog.cpp b/core/libs/prx/libSceAgcDriver/tests/AsyncLog.cpp index f334f4b036..a370fe2802 100644 --- a/core/libs/prx/libSceAgcDriver/tests/AsyncLog.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/AsyncLog.cpp @@ -243,7 +243,12 @@ void FullQueueBlocksCaller() { constexpr int ChildLines = 50; [[noreturn]] void EndChild(const std::string& mode, const char* output) { - std::FILE* file = std::fopen(output, "wb"); + const auto path = std::filesystem::temp_directory_path() / output; +#ifdef _WIN32 + std::FILE* file = _wfopen(path.c_str(), L"wb"); +#else + std::FILE* file = std::fopen(path.c_str(), "wb"); +#endif Check(file != nullptr, "child output file"); #ifdef _WIN32 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); @@ -288,12 +293,18 @@ std::string Quote(const std::string& text) { void LinesSurviveEnd(const std::string& self, const std::string& mode) { const auto output = std::filesystem::temp_directory_path() / ("aps5-async-log-" + mode + "-" + std::to_string(Clock::now().time_since_epoch().count()) + ".txt"); - std::string command = Quote(self) + " child " + mode + " " + Quote(output.string()); #ifdef _WIN32 - command = Quote(command); -#endif + (void)self; + std::wstring executable(32768, L'\0'); + executable.resize(GetModuleFileNameW(nullptr, executable.data(), static_cast(executable.size()))); + const std::wstring command = L"\"\"" + executable + L"\" child " + std::filesystem::path(mode).wstring() + L" \"" + output.filename().wstring() + L"\"\""; + const auto start = Clock::now(); + const int status = _wsystem(command.c_str()); +#else + const std::string command = Quote(self) + " child " + mode + " " + Quote(output.filename().string()); const auto start = Clock::now(); const int status = std::system(command.c_str()); +#endif const auto lived = Clock::now() - start; std::error_code error; const bool started = std::filesystem::exists(output, error); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index ceacffde72..37e518eb51 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -237,6 +237,77 @@ void importSpanTests(const Context& context) { ::operator delete(block, std::align_val_t{piece}); } +void importRunGrowthTests(const Context& context) { + constexpr std::size_t piece = 65536; + void* block = ::operator new(2 * piece, std::align_val_t{piece}); + auto* guest = static_cast(block); + const auto first = reinterpret_cast(block); + std::memset(block, 0x3c, 2 * piece); + auto importing = context; + importing.hostImportAlignment = piece; + const auto change = [&](void* range, std::size_t bytes, bool add) { + GuestAllocations::Mutation mutation; + if (add) mutation.Add(range, bytes, true, false, true); + else mutation.Remove(range); + }; + const auto build = [&](std::size_t pieces, const std::string& step) { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + leased.AddReadable(first + 64, 16); + leased.Upload(true); + const auto ranges = leased.AddressRanges(); + leased.WriteBack(); + Require(std::any_of(ranges.begin(), ranges.end(), [&](const auto& range) { return range.begin <= first && first + pieces * piece <= range.end; }), "the import of a run does not cover its " + std::to_string(pieces) + " registered pieces " + step); + }; + change(guest, piece, true); + build(1, "before its neighbour was registered"); + change(guest + piece, piece, true); + build(2, "after its neighbour was registered"); + change(guest, 0, false); + change(guest + piece, 0, false); + ::operator delete(block, std::align_val_t{piece}); +} + +void importRunReplacedTests(const Context& context) { + constexpr std::size_t piece = 65536; + void* block = ::operator new(3 * piece, std::align_val_t{piece}); + auto* guest = static_cast(block); + const auto first = reinterpret_cast(block); + std::memset(block, 0x3c, 3 * piece); + auto importing = context; + importing.hostImportAlignment = piece; + const auto add = [&](void* range, bool writable) { + GuestAllocations::Mutation mutation; + mutation.Add(range, piece, true, writable, true); + }; + const auto remove = [&](void* range) { + GuestAllocations::Mutation mutation; + mutation.Remove(range); + }; + const auto build = [&](std::size_t pieces, const std::string& step) { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + leased.AddReadable(first + 64, 16); + leased.Upload(true); + const auto ranges = leased.AddressRanges(); + leased.WriteBack(); + Require(std::any_of(ranges.begin(), ranges.end(), [&](const auto& range) { return range.begin <= first && first + pieces * piece <= range.end; }), "the import of a run does not cover its " + std::to_string(pieces) + " registered pieces " + step); + }; + add(guest, true); + add(guest + piece, false); + add(guest + 2 * piece, true); + build(1, "while the read-only middle piece ends it"); + for (int round = 0; round < 4; ++round) { + remove(guest + piece); + add(guest + piece, round % 2 == 0); + build(round % 2 == 0 ? 3 : 1, "after the middle piece was replaced by one that " + std::string(round % 2 == 0 ? "continues" : "ends") + " the run, with as many ranges registered as before"); + } + remove(guest); + remove(guest + piece); + remove(guest + 2 * piece); + ::operator delete(block, std::align_val_t{piece}); +} + void spaceWritableWritesTests(const Context& context) { if (!AddressSpaceCounters().enabled) return; constexpr std::size_t piece = 65536; @@ -531,6 +602,9 @@ void importJoinTests(const Context& context) { Require(pieces != nullptr && pieces->base == heapBase && pieces->bytes == 2 * piece && piecesBuffer == pieces->buffer && piecesCopied == 0, "a buffer bound exactly over two pieces no larger than the import window was not imported as one"); change(heap + 2 * piece, piece, true); static_cast(bind(heapBase + 2 * piece - 16)); + const auto [prefixBuffer, prefixCopied] = bind(heapBase, 2 * piece + 16); + const auto* prefix = HostImportFor(importing, heapBase, 2 * piece + 16); + Require(prefix != nullptr && prefix->base == heapBase && prefix->bytes == 3 * piece && prefixBuffer == prefix->buffer && prefixCopied == 0, "a buffer bound from the first of three pieces into the last was not imported over the pieces it crosses"); const auto* kept = HostImportFor(importing, first, 16); Require(kept != nullptr && kept->buffer == joinedBuffer && kept->bytes == 2 * large, "a registry change elsewhere re-imported the joined ranges"); change(guest + 2 * large, large, true); @@ -809,7 +883,7 @@ private: void importMappedFailureGenerationTests(const Context& context) { ClearHostImports(context.device); - WindowedRange range(context, WindowedRange::piece, 1); + WindowedRange range(context, 2 * WindowedRange::piece, 1); ImportAllocationProbe probe(range.importing); probe.failure = VK_ERROR_INVALID_EXTERNAL_HANDLE; GuestAllocations::Mapped mapped; @@ -840,8 +914,10 @@ void importMappedFailureGenerationTests(const Context& context) { { std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); Require(ImportMappedRanges(range.importing, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true), "the replacement owner map import was deferred"); - Require(probe.calls.size() == 2u, "a replacement owner did not retry its predecessor's failed import"); + Require(probe.calls.size() == 1u, "a replacement owner was imported again at map instead of on first use"); } + HostImportFor(range.importing, range.First(), range.bytes); + Require(probe.calls.size() == 2u, "a replacement owner did not retry its predecessor's failed import on first use"); ClearHostImports(context.device); } @@ -950,6 +1026,34 @@ void importRefusedWholeRangeTests(const Context& context) { Require(kept != nullptr && kept->buffer == buffer, "the range's window was imported again after a refused whole-range import"); } +void importPiecesOverAllocationLimitTests(const Context& context) { + WindowedRange range(context, 2 * WindowedRange::piece, 5); + const auto first = range.First(); + const auto limit = 3 * range.window; + SetHostImportMaxBytes(limit); + { + GuestBufferMemory leased(range.importing); + leased.AcquireRegistered(); + leased.Upload(true); + std::uint64_t covered = first; + for (const auto& entry : leased.AddressRanges()) { + if (entry.end <= first || entry.begin >= first + range.bytes) continue; + Require(entry.begin == covered, "the pieces of a range over the allocation limit leave a gap or overlap in the BDA table"); + Require(entry.end - entry.begin <= limit, "a BDA table entry of a range over the allocation limit is larger than the limit"); + const auto* piece = ExistingHostImportFor(range.importing, entry.begin, static_cast(entry.end - entry.begin)); + Require(piece != nullptr && piece->bytes <= limit && entry.deviceAddress == piece->address + (entry.begin - piece->base), "a BDA table entry of a range over the allocation limit is not served in place by an import within the limit"); + covered = entry.end; + } + Require(covered == first + range.bytes, "the pieces of a range over the allocation limit do not cover it"); + for (std::uint64_t at = first; at < first + range.bytes; at += range.window) { + const auto* piece = ExistingHostImportFor(range.importing, at, 16); + Require(piece != nullptr && piece->bytes <= limit, "a range over the allocation limit has an import larger than the limit"); + } + leased.WriteBack(); + } + SetHostImportMaxBytes(0); +} + // Two uses across neighbouring cell edges of one range, each in a build of its own, share one import: the second grows // the first's window to their union, and later rounds of both keep it. Before, each replaced the other's window at // every use. @@ -979,7 +1083,8 @@ void importNeighbouringEdgesTests(const Context& context) { // A range mapped while the imports are at the budget is refused at map time without retiring an import: the map // observer holds no GPU lock, so it must not evict (before, it evicted the least recently used import to fit). void importAtMapOverBudgetTests(const Context& context) { - constexpr std::size_t bytes = 65536; + ClearHostImports(context.device); + constexpr std::size_t bytes = 2 * 65536; auto importing = context; importing.hostImportAlignment = bytes; void* kept = ::operator new(bytes, std::align_val_t{bytes}); @@ -1023,6 +1128,7 @@ void importAtMapOverBudgetTests(const Context& context) { } void importRequesterTests(const Context& context, const BdaTestAccess& access) { + ClearHostImports(context.device); constexpr std::size_t piece = 65536; constexpr std::size_t pieces = 8; auto* block = static_cast(::operator new(pieces * piece, std::align_val_t{piece})); @@ -1039,17 +1145,32 @@ void importRequesterTests(const Context& context, const BdaTestAccess& access) { const auto counted = [](ImportRequester requester) { return HostImportCounters().requesters[static_cast(requester)]; }; const auto made = [&](ImportRequester requester) { return counted(requester).made; }; Require(std::string(ImportRequesterName(ImportRequester::Map)) == "map" && std::string(ImportRequesterName(ImportRequester::Build)) == "build" && std::string(ImportRequesterName(ImportRequester::Access)) == "access" && std::string(ImportRequesterName(ImportRequester::Join)) == "join" && std::string(ImportRequesterName(ImportRequester::Segment)) == "segment", "an import requester has the wrong name"); - change(0, true); - { + const auto mappedRange = [&](std::size_t index) { GuestAllocations::Mapped ranges; const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); for (const auto& range : lease) { - if (range->address == address(0)) ranges.push_back(range); + if (range->address == address(index)) ranges.push_back(range); } Require(ranges.size() == 1, "the requester test's mapped range is not registered"); + return ranges; + }; + { + GuestAllocations::Mutation mutation; + mutation.Add(block + 6 * piece, 2 * piece, true, true, true); + } + { const auto before = made(ImportRequester::Map); - static_cast(ImportMappedRanges(importing, ranges, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true)); - Require(made(ImportRequester::Map) == before + 1 && HostImportCovers(importing, address(0), piece), "a range imported at map time was not counted for the map requester"); + static_cast(ImportMappedRanges(importing, mappedRange(6), GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true)); + Require(made(ImportRequester::Map) == before + 1 && HostImportCovers(importing, address(6), 2 * piece), "a range imported at map time was not counted for the map requester"); + } + change(0, true); + { + const auto map = made(ImportRequester::Map); + const auto access = made(ImportRequester::Access); + static_cast(ImportMappedRanges(importing, mappedRange(0), GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true)); + Require(made(ImportRequester::Map) == map && !HostImportCovers(importing, address(0), 16), "a range of 64 KiB was imported at map time"); + Require(HostImportFor(importing, address(0), 16) != nullptr && HostImportCovers(importing, address(0), piece) && made(ImportRequester::Access) == access + 1 && made(ImportRequester::Map) == map, + "a range of 64 KiB left to its first GPU use was not imported there, or not counted for that use's requester"); } change(1, true); { @@ -1127,6 +1248,10 @@ void importRequesterTests(const Context& context, const BdaTestAccess& access) { const auto counts = HostImportCounters(); Require(counts.live >= 4 && counts.liveBytes == HostImportLiveBytes(), "the live import counts disagree with the live bytes"); for (std::size_t index = 0; index < 6; ++index) change(index, false); + { + GuestAllocations::Mutation mutation; + mutation.Remove(block + 6 * piece); + } ClearHostImports(importing.device); ::operator delete(block, std::align_val_t{piece}); } @@ -1148,7 +1273,7 @@ public: static constexpr std::size_t Window = std::size_t{8} << 20u; explicit PieceWindow(const Context& context, bool views = false) : importing(context) { -#ifndef _WIN32 +#ifdef __linux__ if (views) { reserved = mmap(nullptr, 2 * Window, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); Require(reserved != MAP_FAILED, "cannot reserve the block of 16 KiB views"); @@ -1331,6 +1456,18 @@ void importBlockFailureTests(const Context& context, const BdaTestAccess& access Require(window.Imports(0, PieceWindow::Window) == 1 && window.Block(0, PieceWindow::Window), "a block that failed was not tried again after one of its ranges was mapped again: " + blockText(window, 0, PieceWindow::Window)); } +void importRunFailureShrinkTests(const Context& context, const BdaTestAccess& access) { + PieceWindow window(context); + constexpr auto half = PieceWindow::Window / 2; + window.AddPieces(0, PieceWindow::Window); + Require(HostImportFor(window.importing, window.Address(0), 16) != nullptr && window.Block(0, PieceWindow::Window), "a window of registered pieces was not imported as one 8 MiB block: " + blockText(window, 0, PieceWindow::Window)); + ClearHostImports(window.importing.device); + access.limitImports(half); + Require(HostImportFor(window.importing, window.Address(0), 16) != nullptr && window.Block(0, half), "a lookup on an unchanged registry whose 8 MiB block failed after an earlier lookup did not shrink to the 4 MiB block: " + blockText(window, 0, PieceWindow::Window)); + access.limitImports(std::nullopt); + Require(HostImportFor(window.importing, window.Address(half), 16) != nullptr && window.Block(0, half) && window.Block(half, half), "a lookup on an unchanged registry after its 8 MiB block failed imported the failed block again: " + blockText(window, 0, PieceWindow::Window)); +} + void importBlockBudgetTests(const Context& context) { PieceWindow window(context); constexpr std::size_t MiB = std::size_t{1} << 20u; @@ -2055,6 +2192,119 @@ ShaderRecompiler::DescriptorBinding binding(Role role, std::uint32_t slot) { return {ShaderRecompiler::DescriptorKind::StorageBuffer, role, 0, slot, 1, {}, false}; } +void partialHeapImportBudgetCase(const Context& context, const BdaTestAccess& access, std::size_t bytes, std::size_t budgetBytes, std::size_t prefixBytes) { + if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) return; + constexpr std::size_t window = 2 * 65536; + const auto allocated = bytes + 65536; + void* block = GuestArena::GuestArenaAllocate_nid_postfix(allocated, 65536); +#ifdef _WIN32 + GuestArena::GuestArenaCommit_nid_postfix(block, allocated, PAGE_READWRITE, allocated); +#endif + auto* guest = static_cast(block); + std::memset(block, 0x39, bytes); + std::memset(guest + bytes, 0xa7, allocated - bytes); + const auto first = reinterpret_cast(block); + auto importing = context; + importing.hostImportAlignment = 65536; + const auto live = HostImportLiveBytes(); + const auto before = MirrorCounters(); + SetHostImportRangeWindow(window); + SetHostImportBudget(live + budgetBytes); + const auto registry = [&](bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, block, bytes, true, true, true); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, block); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + registry(true); + { + auto withoutImports = context; + withoutImports.hostImportAlignment = 0; + GuestBufferMemory leased(withoutImports); + leased.AcquireRegistered(); + leased.AddWritable(first + bytes - 16, 4); + leased.Upload(true); + std::uint32_t adjustment = 0; + const auto view = leased.Descriptor(first + bytes - 16, 4, adjustment); + access.bytes(view.buffer)[view.offset + adjustment] = std::byte{0x43}; + leased.WriteBack(); + } + Require(MirrorCounters().heapBytes == before.heapBytes + bytes && guest[bytes - 16] == 0x43, "the partial-import fixture did not start with a valid full heap mirror"); + GuestAllocations::GuestAllocationsEnd_nid_postfix(GuestAllocations::GuestAllocationsBegin_nid_postfix()); + const auto build = [&](bool write) { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + if (write) { + leased.AddWritable(first + 16, 32); + leased.AddWritable(first + bytes - 48, 32); + } + leased.Upload(true); + const auto* prefix = ExistingHostImportFor(importing, first, window); + Require(prefix != nullptr, "a heap exceeding the import budget did not import its fitting prefix"); + Require(HostImportLiveBytes() <= live + budgetBytes, "partial heap imports exceeded the configured budget"); + Require(MirrorCounters().heapBytes == before.heapBytes + bytes - prefixBytes, "a partially imported heap mirrored bytes already imported in place"); + auto covered = first; + for (const auto& entry : leased.AddressRanges()) { + if (entry.end <= first || entry.begin >= first + bytes) continue; + Require(entry.begin == covered && entry.end <= first + bytes, "partial heap BDA ranges have a gap or overlap"); + if (entry.end <= first + prefixBytes) { + const auto* imported = ExistingHostImportFor(importing, entry.begin, static_cast(entry.end - entry.begin)); + Require(imported != nullptr && imported->bytes <= window && entry.deviceAddress == imported->address + entry.begin - imported->base, "the partial heap prefix is not served by bounded imports"); + } + if (entry.begin >= first + prefixBytes) { + std::uint32_t adjustment = 0; + const auto view = leased.Descriptor(entry.begin, static_cast(entry.end - entry.begin), adjustment); + const auto data = access.bytes(view.buffer).subspan(view.offset + adjustment); + Require(data[0] == static_cast(guest[entry.begin - first]) && data[entry.end - entry.begin - 1] == static_cast(guest[entry.end - first - 1]), "a partial heap mirror lost its first or last bytes"); + } + covered = entry.end; + } + Require(covered == first + bytes, "a partially imported heap does not cover its registered range"); + if (write) { + for (const auto at : {first + 16, first + bytes - 48}) { + if (at < first + prefixBytes) guest[at - first] = 0x71; + else { + std::uint32_t adjustment = 0; + const auto view = leased.Descriptor(at, 32, adjustment); + access.bytes(view.buffer)[view.offset + adjustment] = std::byte{0x71}; + } + guest[at - first + 8] = 0x52; + } + } + leased.WriteBack(); + }; + build(true); + Require(guest[16] == 0x71 && guest[24] == 0x52 && guest[bytes - 48] == 0x71 && guest[bytes - 40] == 0x52, "partial heap write-back lost GPU or genuine CPU stores"); + guest[bytes - 1] = 0x67; + build(false); + const auto quiet = MirrorCounters(); + guest[bytes + 9] = 0x8a; + build(false); + Require(MirrorCounters().blocksCopied == quiet.blocksCopied, "a neighboring CPU write refreshed an unrelated partial heap mirror"); + Require(guest[bytes + 9] == 0x8a && guest[bytes] == 0xa7 && guest[allocated - 1] == 0xa7, "partial heap uses changed unregistered neighboring memory"); + registry(false); + { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + leased.Upload(true); + leased.WriteBack(); + } + Require(MirrorCounters().heapBytes == before.heapBytes, "a partial heap mirror outlived its registered range"); + SetHostImportBudget(0); + SetHostImportRangeWindow(std::uint64_t{64} << 20u); + GuestArena::GuestArenaReset_nid_postfix(block, allocated); + GuestArena::GuestArenaRelease_nid_postfix(block, allocated); +} + +void partialHeapImportBudgetTests(const Context& context, const BdaTestAccess& access) { + constexpr std::size_t window = 2 * 65536; + partialHeapImportBudgetCase(context, access, 3 * window, 2 * window, 2 * window); + partialHeapImportBudgetCase(context, access, 3 * window + 65536, 3 * window, 2 * window); + SetHostImportMaxBytes(window); + partialHeapImportBudgetCase(context, access, 3 * window, 2 * window, 2 * window); + SetHostImportMaxBytes(0); +} + void importedHeapMirrorTests(const Context& context, const BdaTestAccess& access) { if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { std::cout << "guest arena unavailable or not write-watched: heap mirrors of imported ranges not tested\n"; @@ -2110,6 +2360,64 @@ void importedHeapMirrorTests(const Context& context, const BdaTestAccess& access GuestArena::GuestArenaRelease_nid_postfix(block, bytes); } +void retiredImportWatchTests(const Context& context) { +#ifdef _WIN32 + if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { + std::cout << "guest arena unavailable or not write-watched: the write watch of retired imports not tested\n"; + return; + } + constexpr std::size_t piece = 65536; + constexpr std::size_t bytes = 2 * piece; + void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, bytes); + GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes); + std::memset(block, 0x5a, bytes); + auto* guest = static_cast(block); + const auto address = reinterpret_cast(block); + auto importing = context; + importing.hostImportAlignment = piece; + SetHostImportWindow(bytes); + SetHostImportDeferredSettles(false); + ClearHostImports(importing.device); + const auto decided = PrepareImportWatch(importing); + SetImportWatch(importing, ImportWatch::Unwatch); + const auto registry = [&](std::size_t offset, bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest + offset, piece, true, true, true); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest + offset); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + const auto build = [&] { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + leased.Upload(true); + leased.WriteBack(); + }; + registry(0, true); + build(); + Require(HostImportCovers(importing, address, piece), "the first 64 KiB piece was not imported on its own"); + Require(!AgcDriver::GuestMemory::Watched(address, piece), "an unwatched 64 KiB import kept its piece in the write watch"); + registry(piece, true); + SetHostImportBudget(4096); + const auto before = MirrorCounters(); + build(); + Require(!HostImportCovers(importing, address, piece), "the piece's import outlived the run that grew past it, or a 128 KiB block fit a 4 KiB budget"); + Require(AgcDriver::GuestMemory::Watched(address, piece), "a 64 KiB piece whose import retired when its run grew into a block stayed outside the write watch"); + Require(MirrorCounters().heapMirrors == before.heapMirrors + 2, "a 64 KiB piece whose import retired when its run grew into a block was copied, not heap mirrored, once no import fit"); + registry(piece, false); + registry(0, false); + build(); + SetImportWatch(importing, decided); + ClearHostImports(importing.device); + SetHostImportBudget(0); + SetHostImportWindow(0); + SetHostImportDeferredSettles(true); + GuestArena::GuestArenaReset_nid_postfix(block, bytes); + GuestArena::GuestArenaRelease_nid_postfix(block, bytes); +#else + static_cast(context); +#endif +} + void importedFreshTests(const Context& context) { #ifndef _WIN32 if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { @@ -2227,6 +2535,95 @@ void importWatchTests() { #endif } +void sparseImportedHeapTests(const Context& context, const BdaTestAccess& access) { +#ifndef _WIN32 + if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { + std::cout << "write watch unavailable: sparse imported heaps not tested\n"; + return; + } + constexpr std::size_t bytes = 65536; + void* raw = mmap(nullptr, 4 * bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot reserve the sparse imported heap fixture"); + const auto base = (reinterpret_cast(raw) + bytes - 1) & ~(bytes - 1); + auto* guest = reinterpret_cast(base); + Require(mprotect(guest, bytes, PROT_READ | PROT_WRITE) == 0, "cannot commit the sparse imported heap prefix"); + std::memset(guest, 0x57, bytes); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(guest, bytes); + auto importing = context; + importing.hostImportAlignment = bytes; + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest, bytes, true, true, true); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + const auto* imported = HostImportFor(importing, base, bytes); + Require(imported != nullptr, "the sparse heap prefix was not imported"); + const auto before = MirrorCounters(); + for (unsigned index = 0; index < 3; ++index) { + { + GuestBufferMemory described(importing); + described.AddReadable(base, 2 * bytes); + described.Upload(false); + std::uint32_t adjustment = 0; + const auto view = described.Descriptor(base, 2 * bytes, adjustment); + Require(view.buffer == imported->buffer && view.range == bytes && adjustment == 0, "the sparse descriptor did not bind its imported prefix"); + Require(described.CopiedBytes() == 0 && MirrorCounters().rebuilds == before.rebuilds, "the sparse imported descriptor rebuilt or copied its heap mirror"); + reject([&] { static_cast(described.Descriptor(base + bytes + 16, 4, adjustment)); }, "starts past its copy"); + described.WriteBack(); + } + { + GuestBufferMemory addressed(importing); + addressed.AcquireRegistered(); + addressed.Upload(true); + Require(MirrorCounters().rebuilds == before.rebuilds && MirrorCounters().heapMirrors == before.heapMirrors, "alternating descriptor and address builds rebuilt the imported heap mirror"); + addressed.WriteBack(); + } + } + { + auto misaligned = importing; + misaligned.limits.minStorageBufferOffsetAlignment = 256; + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Recorder recorder(misaligned); + recorder.Activate(); + GuestBufferMemory memory(misaligned); + memory.AddReadable(base + 16, 2 * bytes - 16); + memory.Upload(false); + std::uint32_t adjustment = 0; + const auto view = memory.Descriptor(base + 16, 2 * bytes - 16, adjustment); + Require(view.buffer != imported->buffer, "a misaligned sparse descriptor bound its import in place"); + Require(view.range != 0 && view.range <= bytes && memory.CopiedBytes() <= bytes && access.bytes(view.buffer)[view.offset + adjustment] == std::byte{0x57}, "a misaligned sparse descriptor does not serve its committed prefix alone"); + memory.WriteBack(); + } + { + GuestBufferMemory written(importing); + written.AddWritable(base, 2 * bytes); + written.Upload(false); + std::uint32_t adjustment = 0; + const auto view = written.Descriptor(base, 2 * bytes, adjustment); + Require(view.buffer != imported->buffer && view.range == bytes && MirrorCounters().rebuilds == before.rebuilds + 1, "a sparse writable descriptor bound a shortened import"); + access.bytes(view.buffer)[view.offset] = std::byte{0x68}; + guest[8] = 0x79; + written.WriteBack(); + Require(guest[0] == 0x68 && guest[8] == 0x79, "the sparse writable fallback lost a GPU store or rolled back a CPU store"); + } + mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + Require(HostImportFor(importing, base, bytes) == nullptr, "the sparse heap import outlived its range"); + { + GuestBufferMemory sweep(importing); + sweep.AcquireRegistered(); + sweep.Upload(true); + sweep.WriteBack(); + } + Require(MirrorCounters().heapMirrors == before.heapMirrors, "the sparse imported heap fixture leaked its mirror"); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(guest, bytes); + munmap(raw, 4 * bytes); + std::cout << "Sparse imported heap tests passed\n"; +#else + static_cast(context); + static_cast(access); +#endif +} + void heapMirrorTests(const Context& context, const BdaTestAccess& access) { if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { std::cout << "guest arena unavailable or not write-watched: heap mirrors not tested\n"; @@ -2724,7 +3121,7 @@ bool RunSparseBufferHoleTests(const Context& context, const BdaTestAccess& acces std::memset(reservation.data + 2 * unit, 0x72, unit); VkBuffer seeded = VK_NULL_HANDLE; { - Buffer seed(context, copied, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + Buffer seed(context, copied, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); std::fill(seed.Bytes().begin(), seed.Bytes().end(), std::byte{0xa5}); seeded = seed.Handle(); } @@ -2959,6 +3356,63 @@ void RunSparseHeapMirrorUnwatchTests(const Context& context, const BdaTestAccess std::cout << "an address-based build keeps and refreshes a sparse heap's mirror once its range has left the write watch\n"; } +void RunSparseHeapMirrorBudgetTests(const Context& context, const BdaTestAccess& access) { + constexpr std::size_t block = 65536; + constexpr std::size_t reserved = 128 * block; + constexpr std::size_t committed = 80 * block; +#ifdef _WIN32 + if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { + std::cout << "guest arena unavailable or not write-watched: sparse heaps past the heap mirror budget not tested\n"; + return; + } + auto* guest = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(reserved, block)); + Require(guest != nullptr, "cannot allocate the sparse heap in the guest arena"); + GuestArena::GuestArenaCommit_nid_postfix(guest, committed, PAGE_READWRITE, block); +#else + if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { + std::cout << "write watch unavailable: sparse heaps past the heap mirror budget not tested\n"; + return; + } + auto* guest = reserveSparse(reserved, committed); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(guest, reserved); +#endif + std::memset(guest, 0x5a, committed); + const auto base = reinterpret_cast(guest); + const auto registry = [&](bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest, reserved, true, true, true); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + registry(true); + const auto before = MirrorCounters(); + { + GuestBufferMemory memory(context); + memory.AddWritable(base, reserved); + memory.Upload(false); + std::uint32_t adjustment = 0; + const auto view = memory.Descriptor(base, reserved, adjustment); + const auto bytes = access.bytes(view.buffer); + Require(adjustment == 0 && view.range >= committed && bytes[view.offset + committed - 1] == std::byte{0x5a}, "a sparse heap past the heap mirror budget was not copied up to its last committed byte"); + bytes[view.offset + 8] = std::byte{0x77}; + memory.WriteBack(); + } + Require(guest[8] == std::byte{0x77}, "a store to a sparse heap copied past the heap mirror budget was not written back"); + const auto after = MirrorCounters(); + Require(after.sparseBinds == before.sparseBinds && after.heapMirrors == before.heapMirrors && after.heapBytes == before.heapBytes, "a sparse heap past the heap mirror budget was mirrored or left heap mirror bytes held"); + registry(false); + ClearImageMirrors(context.device); + ClearSnapshotCopies(context.device); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(guest, reserved); + GuestArena::GuestArenaRelease_nid_postfix(guest, reserved); +#else + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(guest, reserved); + releaseSparse(guest, reserved); +#endif + std::cout << "a sparse heap whose mirror would pass the heap mirror budget is copied\n"; +} + void RunHeapMirrorRunUnwatchTests(const Context& context, const BdaTestAccess& access) { constexpr std::size_t block = 65536; constexpr std::size_t heapBytes = 32 * block; @@ -3034,6 +3488,137 @@ 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) { + constexpr std::size_t block = 65536; + constexpr std::size_t heapBytes = 32 * block; + constexpr std::size_t idleBytes = 16 * block; + constexpr std::size_t busyBytes = block; +#ifdef _WIN32 + if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { + std::cout << "guest arena unavailable or not write-watched: the address copy cap not tested\n"; + return; + } +#else + if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { + std::cout << "write watch unavailable: the address copy cap not tested\n"; + return; + } +#endif + struct Piece { + void* pointer = nullptr; + void* mapping = nullptr; + std::size_t allocated = 0; + std::size_t bytes = 0; + }; + std::array pieces{}; + const std::array, 4> sizes{{{heapBytes + block, heapBytes}, {heapBytes + block, idleBytes}, {busyBytes + block, busyBytes}, {busyBytes + block, busyBytes}}}; + for (std::size_t index = 0; index < pieces.size(); ++index) { + auto& piece = pieces[index]; + piece.allocated = sizes[index].first; + piece.bytes = sizes[index].second; +#ifdef _WIN32 + piece.pointer = GuestArena::GuestArenaAllocate_nid_postfix(piece.allocated, block); + Require(piece.pointer != nullptr, "cannot allocate the address copy cap ranges in the guest arena"); + GuestArena::GuestArenaCommit_nid_postfix(piece.pointer, piece.allocated, PAGE_READWRITE, piece.allocated); + piece.mapping = piece.pointer; +#else + piece.mapping = mmap(nullptr, piece.allocated + block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(piece.mapping != MAP_FAILED, "cannot map the address copy cap ranges"); + piece.pointer = reinterpret_cast((reinterpret_cast(piece.mapping) + block - 1) & ~std::uintptr_t{block - 1}); +#endif + std::memset(piece.pointer, 0x5a, piece.allocated); + } + if (pieces[1].pointer < pieces[0].pointer) { + std::swap(pieces[0].pointer, pieces[1].pointer); + std::swap(pieces[0].mapping, pieces[1].mapping); + } + const auto heap = reinterpret_cast(pieces[0].pointer); + const auto idle = reinterpret_cast(pieces[1].pointer); + const auto busy = reinterpret_cast(pieces[2].pointer); + Require(heap < idle, "the idle range does not follow the heap in the registry"); +#ifndef _WIN32 + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(pieces[0].pointer, heapBytes); +#endif + const auto registry = [&](bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + for (std::size_t index = 0; index < 3; ++index) { + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, pieces[index].pointer, pieces[index].bytes, true, false, true); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, pieces[index].pointer); + } + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + const auto unrelated = [&](bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, pieces[3].pointer, pieces[3].bytes, true, false, false); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, pieces[3].pointer); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + registry(true); +#ifdef _WIN32 + AgcDriver::GuestMemory::Unwatch(idle, idleBytes); + AgcDriver::GuestMemory::Unwatch(busy, busyBytes); +#endif + auto importing = context; + importing.hostImportAlignment = block; + SetHostImportBudget(heapBytes + idleBytes + busyBytes - block); + Require(HostImportFor(importing, idle, 64) != nullptr && HostImportFor(importing, busy, 64) != nullptr, "the idle and busy ranges were not imported"); + Require(HostImportLiveBytes() == idleBytes + busyBytes && !HostImportCovers(importing, heap, heapBytes), "the imports before the build are not the idle and busy ranges"); + Require(AgcDriver::GuestMemory::Watched(heap, heapBytes), "the heap is not write-watched"); + const auto before = MirrorCounters(); + const auto builds = [] { return HostImportCounters().requesters[static_cast(ImportRequester::Build)]; }; + const auto unwatch = [&] { + AgcDriver::GuestMemory::Unwatch(heap + heapBytes / 2, block); + Require(AgcDriver::GuestMemory::Watched(heap, heapBytes / 2) && !AgcDriver::GuestMemory::Watched(heap, heapBytes), "the heap is not partly outside the write watch"); + }; + if (remembered) { + unwatch(); + SetAddressCopyLimit(2 * heapBytes); + { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + leased.Upload(true); + Require(MirrorCounters().heapMirrors == before.heapMirrors && !HostImportCovers(importing, heap, heapBytes), "the first build did not copy the heap whose import the budget refused"); + leased.WriteBack(); + } + SetAddressCopyLimit(0); + unrelated(true); + } + 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(); + { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + leased.Upload(true); + const bool imported = HostImportCovers(importing, heap, heapBytes); + Require(imported || (!remembered && MirrorCounters().heapMirrors == before.heapMirrors + 1), "the heap is neither imported nor mirrored"); + const auto ranges = leased.AddressRanges(); + const auto found = std::find_if(ranges.begin(), ranges.end(), [&](const auto& range) { return range.begin == heap; }); + Require(found != ranges.end() && found->deviceAddress != 0u, "the heap is missing from the BDA table"); + Require(imported || access.addressBytes(found->deviceAddress)[heapBytes / 2 + 3] == std::byte{0x5a}, "the heap mirror does not hold the guest bytes"); + leased.WriteBack(); + } + Require(!remembered || builds().remembered == refused.remembered + 1, "the build under the copy cap was not answered from the refusal the first build remembered"); + SetHostImportBudget(0); + if (remembered) unrelated(false); + registry(false); + ClearImageMirrors(importing.device); + ClearHostImports(importing.device); + ClearSnapshotCopies(importing.device); +#ifndef _WIN32 + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(pieces[0].pointer, heapBytes); +#endif + for (const auto& piece : pieces) { +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(piece.pointer, piece.allocated); + GuestArena::GuestArenaRelease_nid_postfix(piece.pointer, piece.allocated); +#else + munmap(piece.mapping, piece.allocated + block); +#endif + } + std::cout << (remembered ? "an address-based build answered from a remembered budget refusal evicts instead of copying a partly unwatched heap past the cap\n" : "an address-based build serves a partly unwatched heap whose import is over the budget\n"); +} + void hostWriteWatchExclusionTests() { #ifdef _WIN32 std::vector host(64, 0x31); @@ -3047,6 +3632,200 @@ void hostWriteWatchExclusionTests() { #endif } +void sparseJoinedHeapMirrorTests(const Context& context, const BdaTestAccess& access) { + constexpr std::size_t half = 1u << 20u; + constexpr std::size_t reserved = 2 * half; + constexpr std::size_t page = 65536; +#ifdef _WIN32 + if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { + std::cout << "guest arena unavailable or not write-watched: sparse heap mirrors over adjacent ranges not tested\n"; + return; + } + void* block = GuestArena::GuestArenaAllocate_nid_postfix(reserved, page); + auto* guest = static_cast(block); + GuestArena::GuestArenaCommit_nid_postfix(guest, page, PAGE_READWRITE, page); + GuestArena::GuestArenaCommit_nid_postfix(guest + half, page, PAGE_READWRITE, page); +#else + if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { + std::cout << "write watch unavailable: sparse heap mirrors over adjacent ranges not tested\n"; + return; + } + void* mapping = mmap(nullptr, reserved + page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(mapping != MAP_FAILED, "cannot reserve the sparse heap over adjacent ranges"); + auto* guest = reinterpret_cast((reinterpret_cast(mapping) + page - 1) & ~std::uintptr_t{page - 1}); + Require(mprotect(guest, page, PROT_READ | PROT_WRITE) == 0 && mprotect(guest + half, page, PROT_READ | PROT_WRITE) == 0, "cannot commit the sparse heap pages"); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(guest, reserved); + void* block = guest; +#endif + auto* second = guest + half; + std::memset(guest, 0x5a, page); + std::memset(second, 0x6b, page); + const auto base = reinterpret_cast(block); + const auto registry = [&](bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) { + GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest, half, true, true, true); + GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, second, half, true, true, true); + } else { + GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest); + GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, second); + } + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + const auto sweep = [&] { + GuestBufferMemory memory(context); + memory.AcquireRegistered(); + memory.Upload(true); + memory.WriteBack(); + }; + sweep(); + registry(true); + const auto bind = [&] { + GuestBufferMemory memory(context); + memory.AddWritable(base, reserved); + memory.Upload(false); + std::uint32_t adjustment = 0; + const auto view = memory.Descriptor(base, reserved, adjustment); + Require(adjustment == 0, "a sparse heap view over adjacent ranges is adjusted"); + const auto bytes = access.bytes(view.buffer); + Require(view.range >= half + page && bytes[view.offset + page - 1] == std::byte{0x5a} && bytes[view.offset + page] == std::byte{} && bytes[view.offset + half] == std::byte{0x6b}, "a sparse heap over adjacent ranges is not bound to its committed extent"); + bytes[view.offset + half + 8] = std::byte{0x77}; + memory.WriteBack(); + return view; + }; + const auto before = MirrorCounters(); + const auto first = bind(); + const auto made = MirrorCounters(); + Require(made.sparseBinds == before.sparseBinds + 1 && made.heapMirrors == before.heapMirrors + 1, "a sparse heap whose committed span crosses an adjacent range was not mirrored"); + Require(second[8] == 0x77, "a store to the adjacent range of a mirrored sparse heap was not written back"); + second[8] = 0x6b; + const auto again = bind(); + Require(again.buffer == first.buffer && MirrorCounters().rebuilds == made.rebuilds && MirrorCounters().sparseBinds == made.sparseBinds + 1, "a sparse heap over unchanged adjacent ranges did not reuse its mirror"); + registry(false); + sweep(); + Require(MirrorCounters().heapMirrors == before.heapMirrors && MirrorCounters().heapBytes == before.heapBytes, "the sparse heap mirror over adjacent ranges outlived its ranges"); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(block, reserved); + GuestArena::GuestArenaRelease_nid_postfix(block, reserved); +#else + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(guest, reserved); + munmap(mapping, reserved + page); +#endif +} + +void joinedHeapMirrorPartialImportTests(const Context& context, const BdaTestAccess& access) { + constexpr std::size_t half = 1u << 20u; + constexpr std::size_t committed = 2 * half; + constexpr std::size_t reserved = 4 * half; + constexpr std::size_t page = 65536; +#ifdef _WIN32 + if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { + std::cout << "guest arena unavailable or not write-watched: joined heap mirrors under a partial import not tested\n"; + return; + } + void* block = GuestArena::GuestArenaAllocate_nid_postfix(reserved, half); + auto* guest = static_cast(block); + GuestArena::GuestArenaCommit_nid_postfix(guest, committed, PAGE_READWRITE, page); +#else + if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { + std::cout << "write watch unavailable: joined heap mirrors under a partial import not tested\n"; + return; + } + void* mapping = mmap(nullptr, reserved + half, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(mapping != MAP_FAILED, "cannot reserve the joined heap"); + auto* guest = reinterpret_cast((reinterpret_cast(mapping) + half - 1) & ~std::uintptr_t{half - 1}); + Require(mprotect(guest, committed, PROT_READ | PROT_WRITE) == 0, "cannot commit the joined heap ranges"); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(guest, reserved); + void* block = guest; +#endif + std::memset(guest, 0x3c, committed); + const auto base = reinterpret_cast(block); + const auto registry = [&](bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) { + GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest, half, true, true, true); + GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest + half, half, true, true, true); + } else { + GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest); + GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest + half); + } + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + const auto sweep = [&](const Context& with) { + GuestBufferMemory memory(with); + memory.AcquireRegistered(); + memory.Upload(true); + memory.WriteBack(); + }; + const auto bind = [&] { + GuestBufferMemory memory(context); + memory.AddWritable(base, reserved); + memory.Upload(false); + std::uint32_t adjustment = 0; + const auto view = memory.Descriptor(base, reserved, adjustment); + Require(adjustment == 0 && access.bytes(view.buffer)[view.offset + half + 32] == std::byte{0x3c}, "a heap bound across two committed ranges did not read its second range"); + memory.WriteBack(); + return view; + }; + sweep(context); + registry(true); + const auto before = MirrorCounters(); + const auto first = bind(); + const auto made = MirrorCounters(); + Require(made.heapMirrors == before.heapMirrors + 1 && made.sparseBinds == before.sparseBinds + 1, "a heap bound across two committed ranges was not mirrored over both"); + auto importing = context; + importing.hostImportAlignment = page; + SetHostImportWindow(half); + SetHostImportRangeWindow(half); + SetHostImportBudget(HostImportLiveBytes() + half); + { + GuestBufferMemory memory(importing); + memory.AcquireRegistered(); + Require(ExistingHostImportFor(importing, base, half) != nullptr && ExistingHostImportFor(importing, base + half, half) == nullptr, "the joined heap partial-import fixture did not import exactly its first range"); + Require(HeapMirrorsWithin(base, base + half).mirrors == 1, "an address build that imported one range of a joined heap mirror released the mirror"); + memory.Upload(true); + memory.WriteBack(); + } + const auto partial = MirrorCounters(); + const auto again = bind(); + Require(again.buffer == first.buffer && MirrorCounters().rebuilds == partial.rebuilds, "a joined heap mirror was rebuilt after an import of one of its ranges"); + { + GuestBufferMemory memory(importing); + memory.AcquireRegistered(); + memory.AddWritable(base + half, 32); + memory.Upload(true); + if (ExistingHostImportFor(importing, base + half, 32) != nullptr) guest[half] = 0x71; + else { + std::uint32_t adjustment = 0; + const auto view = memory.Descriptor(base + half, 32, adjustment); + access.bytes(view.buffer)[view.offset + adjustment] = std::byte{0x71}; + } + guest[half + 8] = 0x52; + memory.WriteBack(); + } + Require(guest[half] == 0x71 && guest[half + 8] == 0x52, "a joined heap address build lost GPU or genuine CPU stores"); + const auto refreshed = bind(); + Require(refreshed.buffer == first.buffer && access.bytes(refreshed.buffer)[refreshed.offset + half] == std::byte{0x71} && access.bytes(refreshed.buffer)[refreshed.offset + half + 8] == std::byte{0x52}, "the retained joined mirror did not refresh stores from an address build"); + SetHostImportBudget(HostImportLiveBytes() + committed); + GuestAllocations::GuestAllocationsEnd_nid_postfix(GuestAllocations::GuestAllocationsBegin_nid_postfix()); + sweep(importing); + Require(ExistingHostImportFor(importing, base, half) != nullptr && ExistingHostImportFor(importing, base + half, half) != nullptr, "the joined heap full-import fixture did not import both ranges"); + Require(HeapMirrorsWithin(base, base + committed).mirrors == 0, "imports covering every joined heap range left a heap mirror allocated"); + SetHostImportBudget(0); + SetHostImportWindow(0); + SetHostImportRangeWindow(std::uint64_t{64} << 20u); + ClearHostImports(context.device); + registry(false); + sweep(context); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(block, reserved); + GuestArena::GuestArenaRelease_nid_postfix(block, reserved); +#else + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(guest, reserved); + munmap(mapping, reserved + half); +#endif +} + void sparseHeapMirrorTests(const Context& context, const BdaTestAccess& access) { #ifdef _WIN32 if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { @@ -3393,6 +4172,8 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { addressTableKeyTests(); snapshotTableTests(context, access); importSpanTests(context); + importRunGrowthTests(context); + importRunReplacedTests(context); failedImportRemapTests(context, access); spaceImportRunTests(context); spaceWritableWritesTests(context); @@ -3410,12 +4191,13 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { importMappedFailureGenerationTests(context); importStaleProbeTests(context, access); importRefusedWholeRangeTests(context); + importPiecesOverAllocationLimitTests(context); importNeighbouringEdgesTests(context); importAtMapOverBudgetTests(context); importRequesterTests(context, access); hostImportReportTests(); importBlockMapTests(context, false); -#ifndef _WIN32 +#ifdef __linux__ importBlockMapTests(context, true); #endif importBlockBuildTests(context); @@ -3424,6 +4206,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { importBlockFlagTests(context); importBlockRemapTests(context); importBlockFailureTests(context, access); + importRunFailureShrinkTests(context, access); importBlockBudgetTests(context); importRefusalMemoryTests(context); evictingRefusalMemoryTests(context); @@ -3439,8 +4222,11 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { importReportFloorTests(context, access, true); importReportFloorTests(context, access, false); importReportRefusalMemoryTests(context, access); + sparseImportedHeapTests(context, access); heapMirrorTests(context, access); + partialHeapImportBudgetTests(context, access); importedHeapMirrorTests(context, access); + retiredImportWatchTests(context); importWatchTests(); importedFreshTests(context); gpuMappingTests(context); @@ -3449,8 +4235,15 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { spaceCommittedPagesTests(context, access); hostWriteWatchExclusionTests(); sparseHeapMirrorTests(context, access); + sparseJoinedHeapMirrorTests(context, access); + joinedHeapMirrorPartialImportTests(context, access); Require(AddressCopyOverflow({{0x1000, 0x3000, 0x2000, "uncommitted pages"}}, 0x2000).empty(), "copies within the limit were refused"); const auto copies = AddressCopyOverflow({{0x1000, 0x2000, 0x1000, "not mirrored"}, {0x10000, 0x30000, 0x18000, "uncommitted pages"}}, 0x2000); Require(!copies.empty() && copies.find("0x10000+0x20000 (0.1 MiB committed, uncommitted pages)") < copies.find("0x1000+0x1000"), "the copy limit does not name the largest copy first"); + AgcDriver::GuestMemory::Unwatch(0x10000, 0x20000); + AgcDriver::GuestMemory::Unwatch(0x40000, 0x10000); + Require(!AgcDriver::GuestMemory::Watched(0x10000, 0x20000), "the unregistered heap copy range is write-watched"); + const auto heapCopies = AddressCopyOverflow({{0x10000, 0x30000, 0x18000, nullptr, true, true}, {0x40000, 0x50000, 0x10000, nullptr, true, false}}, 0x2000); + Require(heapCopies.find("0x10000+0x20000 (0.1 MiB committed, import over the host import budget, heap not fully write-watched)") != std::string::npos && heapCopies.find("0x40000+0x10000 (0.1 MiB committed, heap not fully write-watched)") != std::string::npos, "the copy limit does not name a copied heap's reason"); ClearSnapshotCopies(context.device); } diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp index 6aea082fff..7479c1f4b1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp @@ -19,7 +19,9 @@ void RunBdaResourceTests(const AgcDriver::Graphics::Context& context, const BdaT bool RunSparseBufferHoleTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access); void RunHeapMirrorUnwatchTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access, bool throughImport); 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 RunGuestAllocationTests(); void RunGuestLeaseWaitTests(); void RunUnmappedGapTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp index abdf8f0166..66ec1aa305 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -675,6 +676,7 @@ struct ReclaimMock { VkDeviceSize externalBytes = 0; std::uint64_t requests = 0; VkDeviceSize asked = 0; + bool newImage = false; }; ReclaimMock reclaimMock; @@ -689,7 +691,8 @@ VkDeviceMemory ExternalAllocation(VkDeviceSize bytes) { return memory; } -VkDeviceSize mockCacheReclaim(VkDeviceSize bytes) { +VkDeviceSize mockCacheReclaim(VkDeviceSize bytes, bool newImage) { + Expect(newImage == reclaimMock.newImage, "an allocation used the wrong image-creation cache access"); ++reclaimMock.requests; reclaimMock.asked = bytes; if (reclaimMock.external == VK_NULL_HANDLE) return 0; @@ -701,6 +704,10 @@ VkDeviceSize mockCacheReclaim(VkDeviceSize bytes) { void CacheReclaimRecovery() { Expect(BufferPool::ReclaimTarget(4 * MiB) == 260 * MiB, "a 4 MiB request does not ask the caches for 256 MiB on top of itself"); Expect(BufferPool::ReclaimTarget(512 * MiB) == 1024 * MiB, "a 512 MiB request does not ask the caches for twice its size"); + const auto limit = std::numeric_limits::max(); + Expect(BufferPool::ReclaimTarget(limit / 2u) == limit - 1u, "the largest doubled request without overflow lost its reserve"); + Expect(BufferPool::ReclaimTarget(limit / 2u + 1u) == limit, "a reclaim target wrapped when twice the request overflowed"); + Expect(BufferPool::ReclaimTarget(limit) == limit, "the largest reclaim request wrapped to a smaller target"); constexpr auto usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; mock = MockDevice{}; reclaimMock = ReclaimMock{}; @@ -775,18 +782,23 @@ void ImageCacheReclaim() { VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; allocation.allocationSize = 8 * MiB; allocation.memoryTypeIndex = 0; - const auto refuse = [&](std::initializer_list results) { + const auto refuse = [&](std::initializer_list results, bool newImage = false) { ExternalAllocation(4 * MiB); + reclaimMock.newImage = newImage; mock.results = results; const auto requests = reclaimMock.requests; - const auto memory = AllocateImageMemory(context, allocation, 3, "vkAllocateMemory texture"); + const auto before = mock.attempts; + const auto memory = AllocateImageMemory(context, allocation, 3, "vkAllocateMemory texture", nullptr, newImage); Expect(reclaimMock.requests == requests + 1 && reclaimMock.external == VK_NULL_HANDLE, "a texture refused device memory did not release the texture caches before retrying"); + Expect(mock.attempts == before + results.size() + 1, "image recovery or host fallback exceeded its bounded attempts"); const auto type = mock.memoryTypes.at(memory); mockFreeMemory(VK_NULL_HANDLE, memory, nullptr); return type; }; Expect(refuse({VK_ERROR_OUT_OF_DEVICE_MEMORY}) == 0, "a texture was placed in host memory although releasing the texture caches made room in device memory"); Expect(refuse({VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY}) == 1, "a texture still refused device memory after releasing the texture caches was not placed in host memory"); + Expect(refuse({VK_ERROR_OUT_OF_DEVICE_MEMORY}, true) == 0, "a new image was placed in host memory before retrying after cache release"); + Expect(refuse({VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY}, true) == 1, "a new image refused device memory after cache release lost its host fallback"); Expect(mock.liveBytes == 0, "a texture allocation leaked memory"); } @@ -832,6 +844,103 @@ void AllocationFailureReportsHeapBudgets() { Expect(diagnosticBudgetQueries == 0, "missing-extension fallback attempted a memory budget query"); } +Context reclaimContext() { + auto context = mockContext(); + context.memory.memoryHeapCount = 2; + context.memory.memoryHeaps[0] = {4096 * MiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}; + context.memory.memoryHeaps[1] = {8192 * MiB, 0}; + context.memory.memoryTypes[1].heapIndex = 1; + return context; +} + +void IdleDeviceReclaim() { + mock = MockDevice{}; + { + auto context = reclaimContext(); + VkBuffer cold; + VkBuffer nextCold; + VkBuffer hot; + VkBuffer host; + VkBuffer small; + { DeviceBuffer buffer(context, 2 * MiB, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cold = buffer.Handle(); } + { DeviceBuffer buffer(context, 3 * MiB, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); nextCold = buffer.Handle(); } + { DeviceBuffer buffer(context, 4 * MiB, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); hot = buffer.Handle(); } + { Buffer buffer(context, 8 * MiB, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); host = buffer.Handle(); } + { Buffer buffer(context, 1024, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); small = buffer.Handle(); } + DeviceBuffer active(context, 16 * MiB, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + auto& pool = *context.bufferPool; + const auto start = std::chrono::steady_clock::time_point{std::chrono::seconds(1)}; + Expect(pool.ReclaimIdleDevice(MiB, start) == 0, "the first pressure observation reclaimed a buffer without an idle interval"); + Expect(pool.ReclaimIdleDevice(MiB, start + std::chrono::milliseconds(999)) == 0, "a partial idle interval reclaimed a buffer"); + { + DeviceBuffer reused(context, 4 * MiB, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + Expect(reused.Handle() == hot, "the warm device buffer was not reused"); + } + const auto next = start + std::chrono::seconds(1); + SetAllocationFailureCountdown(0); + bool threw = false; + try { + pool.ReclaimIdleDevice(MiB, next); + } catch (const std::bad_alloc&) { + threw = true; + } + SetAllocationFailureCountdown(-1); + Expect(threw && mock.liveBuffers.contains(cold) && mock.liveBuffers.contains(nextCold), "a failed reclaim collection lost ownership of an allocation"); + Expect(pool.ReclaimIdleDevice(MiB, next) == 2 * MiB, "reclaim did not release exactly the oldest complete idle allocation"); + Expect(!mock.liveBuffers.contains(cold) && mock.liveBuffers.contains(nextCold), "bounded reclaim released the wrong allocation or exceeded its request"); + Expect(pool.ReclaimIdleDevice(0, next) == 0, "zero pressure released a buffer"); + Expect(pool.ReclaimIdleDevice(32 * MiB, next) == 3 * MiB, "reclaim counted a hot, live, host or slab allocation as idle device memory"); + for (const auto buffer : {hot, host, small, active.Handle()}) Expect(mock.liveBuffers.contains(buffer), "reclaim destroyed a protected buffer"); + SetAllocationFailureCountdown(0); + const auto empty = pool.ReclaimIdleDevice(32 * MiB, next); + SetAllocationFailureCountdown(-1); + Expect(empty == 0, "an unchanged empty eligible set did not stay empty"); + Expect(pool.ReclaimIdleDevice(32 * MiB, start) == 0, "a backwards time observation reclaimed a buffer"); + Expect(pool.ReclaimIdleDevice(32 * MiB, next + std::chrono::seconds(1)) == 4 * MiB, "a device buffer did not become reclaimable after a complete idle interval"); + Expect(mock.liveBuffers.contains(active.Handle()), "reclaim touched an allocation still in use"); + Expect(pool.ReclaimIdleDevice(32 * MiB, next + std::chrono::seconds(2)) == 0, "an empty device tier reported reclaimed memory"); + } + Expect(mock.liveBytes == 0 && mock.liveBuffers.empty(), "idle device reclaim leaked an allocation"); +} + +void IdleReclaimPreservesWorkingSet() { + mock = MockDevice{}; + { + auto context = reclaimContext(); + const auto useWorkingSet = [&] { + std::vector> buffers; + for (unsigned i = 0; i < 4; ++i) buffers.push_back(std::make_unique(context, 96 * MiB, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT)); + }; + useWorkingSet(); + const auto allocations = mock.allocations; + const auto start = std::chrono::steady_clock::time_point{std::chrono::seconds(1)}; + auto& pool = *context.bufferPool; + Expect(pool.ReclaimIdleDevice(1024 * MiB, start) == 0, "initial pressure discarded the working set"); + for (unsigned second = 1; second <= 4; ++second) { + useWorkingSet(); + Expect(pool.ReclaimIdleDevice(1024 * MiB, start + std::chrono::seconds(second)) == 0, "pressure discarded buffers reused during the observation interval"); + Expect(mock.allocations == allocations && mock.liveBytes == 384 * MiB, "a reusable working set larger than 128 MiB was recreated or shrunk"); + } + Expect(pool.ReclaimIdleDevice(1024 * MiB, start + std::chrono::seconds(5)) == 384 * MiB, "the former working set remained retained after becoming idle"); + Expect(mock.liveBytes == 0, "reclaim did not return the idle working set to the device"); + } + Expect(mock.liveBuffers.empty(), "working-set reclaim leaked a buffer"); + for (const auto heaps : {0u, 2u}) { + mock = MockDevice{}; + { + auto context = reclaimContext(); + context.memory.memoryHeapCount = heaps; + context.memory.memoryHeaps[1].flags = VK_MEMORY_HEAP_DEVICE_LOCAL_BIT; + { DeviceBuffer buffer(context, 2 * MiB, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); } + auto& pool = *context.bufferPool; + const auto start = std::chrono::steady_clock::time_point{std::chrono::seconds(1)}; + Expect(pool.ReclaimIdleDevice(4 * MiB, start) == 0 && pool.ReclaimIdleDevice(4 * MiB, start + std::chrono::seconds(1)) == 0, "reclaim assumed a device heap without unambiguous ownership"); + Expect(mock.liveBytes == 2 * MiB, "reclaim released memory from an unknown or different local heap"); + } + Expect(mock.liveBytes == 0 && mock.liveBuffers.empty(), "unsupported-heap reclaim leaked an allocation"); + } +} + void SuccessfulAllocationKeepsPool() { mock = MockDevice{}; { @@ -900,7 +1009,7 @@ void DeviceBindingFailures() { VkDeviceMemory reclaimImage = VK_NULL_HANDLE; const Context* reclaimImageContext = nullptr; -VkDeviceSize mockImageReclaim(VkDeviceSize) { +VkDeviceSize mockImageReclaim(VkDeviceSize, bool) { if (reclaimImage == VK_NULL_HANDLE) return 0; ReleaseImageMemory(*reclaimImageContext, reclaimImage, 10 * MiB, 0); reclaimImage = VK_NULL_HANDLE; @@ -1001,10 +1110,50 @@ void ImageMemoryReuse() { Expect(mock.frees == 35 && context.bufferPool->RetainedImages() == 1, "the pool retains image memory again after a trim"); } +void IdleReclaimPreservesImages() { + mock = MockDevice{}; + { + auto context = reclaimContext(); + VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocation.allocationSize = 4 * MiB; + allocation.memoryTypeIndex = 0; + std::uint32_t type = 99; + const auto image = AllocateImageMemory(context, allocation, 1u, "test idle image", &type); + ReleaseImageMemory(context, image, 4 * MiB, type); + VkBuffer buffer; + { DeviceBuffer idle(context, 2 * MiB, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); buffer = idle.Handle(); } + auto& pool = *context.bufferPool; + const auto start = std::chrono::steady_clock::time_point{std::chrono::seconds(1)}; + Expect(pool.RetainedImages() == 1 && mock.liveBytes == 6 * MiB, "the mixed pool fixture was not retained"); + Expect(pool.ReclaimIdleDevice(MiB, start) == 0, "initial pressure reclaimed mixed pool memory"); + const auto next = start + std::chrono::seconds(1); + const auto released = pool.ReclaimIdleDevice(MiB, next); + Expect(released == 2 * MiB, "idle buffer reclaim selected image memory instead of the oldest buffer"); + if (released != 2 * MiB) return; + Expect(!mock.liveBuffers.contains(buffer) && mock.liveBytes == 4 * MiB && pool.RetainedImages() == 1, "buffer reclaim changed image ownership or accounting"); + Expect(pool.ReclaimIdleDevice(32 * MiB, next) == 0, "an image-only pool was reclaimed as idle buffers"); + SetAllocationFailureCountdown(0); + const auto empty = pool.ReclaimIdleDevice(32 * MiB, next); + SetAllocationFailureCountdown(-1); + Expect(empty == 0, "a repeated image-only scan allocated or reclaimed memory"); + const auto allocations = mock.allocations; + const auto reused = AllocateImageMemory(context, allocation, 1u, "test idle image", &type); + Expect(reused == image && mock.allocations == allocations && pool.RetainedImages() == 0, "the retained image was not reusable after buffer reclaim"); + ReleaseImageMemory(context, reused, 4 * MiB, type); + Expect(pool.RetainedImages() == 1 && mock.liveBytes == 4 * MiB, "the reused image was not retained exactly once"); + } + Expect(mock.liveBytes == 0 && mock.liveBuffers.empty() && mock.frees == 2, "mixed pool cleanup leaked or freed an allocation twice"); +} + + int main(int argc, char** argv) { try { if (argc == 2 && std::string_view(argv[1]) == "--small-device-retention") { SmallDeviceRetention(); + if (std::getenv("APS5_STAGING_POOL_MIB") == nullptr) { + IdleDeviceReclaim(); + IdleReclaimPreservesWorkingSet(); + } return failures == 0 ? 0 : 1; } if (argc == 2) { @@ -1040,6 +1189,9 @@ int main(int argc, char** argv) { AllocationFailure(VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_DEVICE_LOST, true); AllocationFailureReportsHeapBudgets(); SuccessfulAllocationKeepsPool(); + IdleDeviceReclaim(); + IdleReclaimPreservesWorkingSet(); + IdleReclaimPreservesImages(); PartialSlabDoesNotRetry(); CacheReclaimRecovery(); ImageCacheReclaim(); diff --git a/core/libs/prx/libSceAgcDriver/tests/CaptureTrace.cpp b/core/libs/prx/libSceAgcDriver/tests/CaptureTrace.cpp new file mode 100644 index 0000000000..b263f9e7b1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/CaptureTrace.cpp @@ -0,0 +1,160 @@ +#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE) +#define _POSIX_C_SOURCE 200809L +#endif + +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +void RemoveEnvironmentVariable(const char* name) { +#ifdef _WIN32 + _putenv_s(name, ""); +#else + unsetenv(name); +#endif +} + +bool FileExists(const char* path) { + std::error_code error; + return std::filesystem::exists(path, error) && !error; +} + +std::string ReadFile(const char* path) { + std::ifstream stream(path, std::ios::binary); + Check(stream.is_open(), "the trace file was not created at the expected path"); + std::ostringstream buffer; + buffer << stream.rdbuf(); + return buffer.str(); +} + +bool WaitForRecord(const char* path, const std::string& record) { + for (int attempt = 0; attempt < 100; ++attempt) { + if (FileExists(path) && ReadFile(path).find(record) != std::string::npos) return true; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + return false; +} + +const char* RequiredPathVariable() { + const char* configured = std::getenv("APS5_CAPTURE_TRACE_PATH"); + Check(configured != nullptr && configured[0] != '\0', + "this case requires a non-empty APS5_CAPTURE_TRACE_PATH"); + return configured; +} + +void PathDefault() { + RemoveEnvironmentVariable("APS5_CAPTURE_TRACE_PATH"); + Check(std::string(AgcDriver::CaptureTracePath()) == "capture-trace.log", + "an absent APS5_CAPTURE_TRACE_PATH must select capture-trace.log"); +} + +void PathEmpty() { + const char* configured = std::getenv("APS5_CAPTURE_TRACE_PATH"); + Check(configured != nullptr && configured[0] == '\0', + "this case requires an empty APS5_CAPTURE_TRACE_PATH"); + Check(std::string(AgcDriver::CaptureTracePath()) == "capture-trace.log", + "an empty APS5_CAPTURE_TRACE_PATH must select capture-trace.log"); +} + +void PathExplicit() { + const char* configured = RequiredPathVariable(); + Check(std::string(AgcDriver::CaptureTracePath()) == std::string(configured), + "a non-empty APS5_CAPTURE_TRACE_PATH must be selected verbatim"); +} + +void ExplicitWrites() { + const char* configured = RequiredPathVariable(); + Check(AgcDriver::CaptureTrace::Enabled(), "APS5_CAPTURE_TRACE must enable capture tracing"); + AgcDriver::CaptureTrace::Log("capture-trace-explicit label=%d", 7); + Check(WaitForRecord(configured, "capture-trace-explicit label=7"), + "the configured trace path did not receive the capture record"); + std::remove(configured); +} + +void DefaultWrites() { + RemoveEnvironmentVariable("APS5_CAPTURE_TRACE_PATH"); + Check(AgcDriver::CaptureTrace::Enabled(), "APS5_CAPTURE_TRACE must enable capture tracing"); + Check(std::string(AgcDriver::CaptureTracePath()) == "capture-trace.log", + "an absent APS5_CAPTURE_TRACE_PATH must keep the default path"); + AgcDriver::CaptureTrace::Log("capture-trace-default label=%d", 5); + Check(WaitForRecord("capture-trace.log", "capture-trace-default label=5"), + "the default trace path did not receive the capture record"); + std::remove("capture-trace.log"); +} + +void OpenFailure() { + const char* configured = RequiredPathVariable(); + Check(!FileExists(configured), "the configured trace path must not already exist"); + Check(AgcDriver::CaptureTrace::Enabled(), "APS5_CAPTURE_TRACE must enable capture tracing"); + bool threw = false; + try { + AgcDriver::CaptureTrace::Log("capture-trace-failure label=%d", 9); + } catch (const std::exception& error) { + threw = true; + std::cerr << "unexpected exception: " << error.what() << '\n'; + } + Check(!threw, "an unopenable trace path must not throw from Log"); + Check(!FileExists(configured), "an unopenable trace path must not create the requested file"); + Check(!FileExists("capture-trace.log"), + "an unopenable trace path must not fall back to capture-trace.log"); +} + +void BestEffortOverflow() { + const char* configured = RequiredPathVariable(); + Check(AgcDriver::CaptureTrace::Enabled(), "APS5_CAPTURE_TRACE must enable capture tracing"); + const std::string oversized(4096, 'x'); + bool threw = false; + try { + AgcDriver::CaptureTrace::LogBestEffort("capture-trace-best-effort payload=%s", oversized.c_str()); + } catch (...) { + threw = true; + } + Check(!threw, "best-effort capture logging must not propagate formatting failures"); + std::remove(configured); +} + +void Disabled() { + RemoveEnvironmentVariable("APS5_CAPTURE_TRACE"); + Check(!AgcDriver::CaptureTrace::Enabled(), + "capture tracing must stay disabled when APS5_CAPTURE_TRACE is absent"); + AgcDriver::CaptureTrace::Log("capture-trace-disabled label=%d", 3); + Check(!FileExists("capture-trace.log"), + "disabled tracing must not initialize or write capture-trace.log"); +} + +} + +int main(int argc, char** argv) { + try { + if (argc != 2) throw std::runtime_error("expected exactly one test mode argument"); + const std::string mode = argv[1]; + if (mode == "path-default") PathDefault(); + else if (mode == "path-empty") PathEmpty(); + else if (mode == "path-explicit") PathExplicit(); + else if (mode == "explicit-writes") ExplicitWrites(); + else if (mode == "default-writes") DefaultWrites(); + else if (mode == "open-failure") OpenFailure(); + else if (mode == "best-effort-overflow") BestEffortOverflow(); + else if (mode == "disabled") Disabled(); + else throw std::runtime_error("unknown capture trace test mode: " + mode); + std::cout << "capture trace tests passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/CmaskFastClear.cpp b/core/libs/prx/libSceAgcDriver/tests/CmaskFastClear.cpp index dfadd4aabe..5cf4e302e4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/CmaskFastClear.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/CmaskFastClear.cpp @@ -155,6 +155,32 @@ void unknownCode(AgcDriver::VulkanDevice& device) { expect(device, color, [](std::uint32_t, std::uint32_t) { return Clear; }, "fast-cleared target eliminated after a CMASK code 3"); } +void expectRefused(const std::function& action, const std::string& message, const std::string& what) { + try { + action(); + } catch (const std::exception& error) { + Require(std::string(error.what()).find(message) != std::string::npos, what + ": refused with " + error.what()); + return; + } + throw std::runtime_error(what + ": was not refused"); +} + +void oneFragment(AgcDriver::VulkanDevice& device) { + auto color = target(ColorTileMode::RenderTarget); + color.coverageSamples = 4; + prepare(color, [](std::uint32_t tileX, std::uint32_t tileY) { return tileX == 2 && tileY == 0; }, 3); + expectRefused([&] { eliminate(device, color); }, "CMASK code 0x3 of a one-fragment color target is not implemented", "a one-fragment target with CMASK code 3"); + prepare(color, [](std::uint32_t, std::uint32_t) { return true; }); + eliminate(device, color); + expect(device, color, [](std::uint32_t, std::uint32_t) { return Clear; }, "a fast-cleared one-fragment target eliminated"); + prepare(color, [](std::uint32_t tileX, std::uint32_t tileY) { return tileX == 7 && tileY == 1; }); + eliminate(device, color); + expect(device, color, [](std::uint32_t x, std::uint32_t y) { return x >= 56 && y >= 8 ? Clear : Stale; }, "a one-fragment target with one fast-cleared tile eliminated"); + prepare(color, [](std::uint32_t tileX, std::uint32_t) { return tileX < 4; }, 0xc); + eliminate(device, color); + expect(device, color, [](std::uint32_t, std::uint32_t) { return Stale; }, "a one-fragment target with CMASK codes 0xc and 0xf eliminated"); +} + } int main() { @@ -175,6 +201,7 @@ int main() { run(*device, ColorTileMode::Linear, "linear"); run(*device, ColorTileMode::RenderTarget, "SW_64KB_R_X"); unknownCode(*device); + oneFragment(*device); device->WaitIdle(); device.reset(); { diff --git a/core/libs/prx/libSceAgcDriver/tests/CollectMemoGeneration.cpp b/core/libs/prx/libSceAgcDriver/tests/CollectMemoGeneration.cpp new file mode 100644 index 0000000000..90d5e8a215 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/CollectMemoGeneration.cpp @@ -0,0 +1,127 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint64_t Block = 65536; +constexpr std::uint64_t Page = 4096; +constexpr std::uint64_t Base = std::uint64_t{1} << 32; +constexpr std::chrono::seconds Patience{10}; + +struct FakeWatch { + std::mutex mutex; + std::condition_variable changed; + std::vector> written; + bool holdNextWalk = false; + bool holding = false; + bool released = false; + bool timedOut = false; +}; + +FakeWatch& Fake() { + static FakeWatch fake; + return fake; +} + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +} + +namespace GuestWriteWatch { + +extern "C" { + +bool GuestWriteWatchAvailable_nid_postfix() { + return true; +} + +bool GuestWriteWatchCovers_nid_postfix(std::uintptr_t address, std::size_t bytes) { + return address >= Base && bytes <= 2 * Block && address - Base <= 2 * Block - bytes; +} + +bool GuestWriteWatchCollect_nid_postfix(std::uintptr_t address, std::size_t bytes, void (*written)(void* context, std::uintptr_t begin, std::uintptr_t end), void* context) { + auto& fake = Fake(); + const auto end = address + bytes; + std::vector> runs; + { + std::unique_lock lock(fake.mutex); + if (fake.holdNextWalk) { + fake.holdNextWalk = false; + fake.holding = true; + fake.changed.notify_all(); + fake.timedOut = !fake.changed.wait_for(lock, Patience, [&] { return fake.released; }); + } + for (auto it = fake.written.begin(); it != fake.written.end();) { + if (it->first < end && address < it->second) { + runs.emplace_back(std::max(it->first, address), std::min(it->second, end)); + it = fake.written.erase(it); + } else ++it; + } + } + for (const auto& [from, to] : runs) written(context, from, to); + return true; +} + +} + +} + +namespace { + +using namespace AgcDriver::GuestMemory; + +void CheckMemoHitDuringWalk() { + auto& fake = Fake(); + BumpCollectEpoch(); + const auto collected = CollectWrites(Base, Block); + Require(collected != 0 && UnchangedSince(Base, Block, collected), "the block was not collected"); + { + const std::lock_guard lock(fake.mutex); + fake.written.emplace_back(Base + Page, Base + 2 * Page); + fake.holdNextWalk = true; + } + std::thread walker([] { CollectWritesUncached(Base, 2 * Block); }); + bool holding = false; + { + std::unique_lock lock(fake.mutex); + holding = fake.changed.wait_for(lock, Patience, [&] { return fake.holding; }); + } + const auto memoized = holding ? CollectWrites(Base, Block) : 0; + { + const std::lock_guard lock(fake.mutex); + fake.released = true; + } + fake.changed.notify_all(); + walker.join(); + Require(holding, "the second walk did not reach the write watch"); + Require(!fake.timedOut, "the memoized collect waited for the walk in progress"); + Require(!UnchangedSince(Base + Page, Page, memoized), "a memoized collect returned the generation a walk in progress then stamped a write with"); + BumpCollectEpoch(); + Require(CollectWrites(Base, Block) > memoized && !UnchangedSince(Base, Block, memoized), "a later epoch trusts the write stamped by the walk in progress"); +} + +} + +int main() { + try { + CheckMemoHitDuringWalk(); + } catch (const std::exception& error) { + std::cerr << "collect memo generation test failed: " << error.what() << "\n"; + return 1; + } + std::cout << "collect memo generation test passed\n"; + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp b/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp index 06d73e6578..d7bac76983 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp @@ -1,6 +1,7 @@ #include "IntermediateRepresentation/IrBuilder.hpp" #include "Optimization/ConstantFolder.hpp" #include "Optimization/DeadCodeEliminator.hpp" +#include #include #include #include @@ -203,6 +204,71 @@ bool vertexProjection(std::uint32_t low, std::uint32_t hostSubgroup, std::uint32 return retainedLane; } +enum class QuadMask { + Expanded, + Intersected, + Nested, + SeparateHalves, + RealOnly, + Partial, + WrongHalf, + Divergent, +}; + +bool quadProjection(std::uint32_t wave, QuadMask shape, bool expected, IrShaderStage stage = IrShaderStage::Pixel) { + IrProgram program; + program.Resources().stage = stage; + program.SetWaveSize(wave); + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + program.BlockOrder().push_back(&block); + IrBuilder builder(program); + builder.SetInsertionPoint(block); + auto& lane = builder.Emit(IrOpcode::LaneId, IrType::U32, {}); + if (shape == QuadMask::Divergent) { + auto& taken = program.CreateBlock(); + auto& other = program.CreateBlock(); + program.BlockOrder().push_back(&taken); + program.BlockOrder().push_back(&other); + builder.BranchConditional(builder.ULessThan(lane, builder.Constant(16u)), taken, other); + builder.SetInsertionPoint(other); + static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); + builder.SetInsertionPoint(taken); + } + const auto ballot = [&](IrValue& condition) { + auto& mask = builder.Emit(IrOpcode::Ballot, IrType::U32x4, {&condition}); + return std::array{&builder.CompositeExtract(mask, 0u), wave == 64u ? &builder.CompositeExtract(mask, 1u) : &builder.Constant(0u)}; + }; + const auto expand = [&](const std::array& words) { + auto& wide = builder.ConstructU64(*words[0], *words[1]); + auto& mask = builder.Emit(IrOpcode::WqmU64, IrType::U64, {&wide}); + 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))); + 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) { + const auto highHost = shape == QuadMask::SeparateHalves ? expand(ballot(builder.ConstantBool(true))) : host; + const auto source = shape == QuadMask::Nested || shape == QuadMask::RealOnly ? 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) { + words = {&builder.BitwiseAnd(*words[0], builder.Constant(0x0000ffffu)), &builder.Constant(0u)}; + } + if (shape == QuadMask::WrongHalf) words[1] = words[0]; + IrValue& word = wave == 64u ? builder.Select(builder.ULessThan(lane, builder.Constant(32u)), *words[0], *words[1]) : *words[0]; + auto& shifted = builder.ShiftRightLogical(word, builder.BitwiseAnd(lane, builder.Constant(31u))); + auto& test = builder.INotEqual(builder.BitwiseAnd(shifted, builder.Constant(1u)), builder.Constant(0u)); + auto& user = builder.Emit(IrOpcode::Reference, IrType::Void, {&test}); + 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(); +} + bool run(const char* name, bool passed) { if (!passed) std::fprintf(stderr, "%s\n", name); return passed; @@ -213,6 +279,17 @@ bool run(const char* name, bool passed) { int main() { try { bool passed = true; + for (const auto wave : {32u, 64u}) { + passed &= run("a whole-quad entry mask covers pixel helpers", quadProjection(wave, QuadMask::Expanded, true)); + 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("a partial whole-quad mask must keep the lane bit test", quadProjection(wave, QuadMask::Partial, 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)); + } + passed &= run("a low word cannot prove the high-half helper bits", quadProjection(64u, QuadMask::WrongHalf, false)); passed &= run("an all-zero word must fold the lane bit test to false", foldsTo(0u, false, true)); passed &= run("an all-one word must fold the lane bit test to true", foldsTo(0xffffffffu, true, true)); passed &= run("a mixed word must keep the lane bit test", foldsTo(0x0000ffffu, false, true)); diff --git a/core/libs/prx/libSceAgcDriver/tests/ConstantPhi.cpp b/core/libs/prx/libSceAgcDriver/tests/ConstantPhi.cpp index 2cc4a8b370..09ef9b6dac 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ConstantPhi.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ConstantPhi.cpp @@ -79,6 +79,83 @@ void CheckDiamond(unsigned mode) { } } +void CheckUnobservedPhiCycles() { + IrProgram empty; + DeadCodeEliminator{}.EliminateUnobservedPhis(empty); + IrProgram program; + for (std::uint32_t id = 0; id < 4u; ++id) { + auto& block = program.CreateBlock(); + program.BlockOrder().push_back(&block); + BlockInfo info; + info.id = id; + info.terminator.kind = TerminatorKind::Return; + program.Metadata().blockInfo.push_back(info); + } + auto& entry = *program.BlockOrder()[0]; + auto& header = *program.BlockOrder()[1]; + auto& latch = *program.BlockOrder()[2]; + auto& exit = *program.BlockOrder()[3]; + program.SetEntryBlock(entry); + entry.AddBranch(&header); + header.AddBranch(&latch); + header.AddBranch(&exit); + latch.AddBranch(&header); + program.Metadata().blockInfo[0].terminator.kind = TerminatorKind::Branch; + program.Metadata().blockInfo[0].terminator.trueBlock = 1u; + auto& headerInfo = program.Metadata().blockInfo[1]; + headerInfo.terminator.kind = TerminatorKind::ConditionalBranch; + headerInfo.terminator.trueBlock = 2u; + headerInfo.terminator.falseBlock = 3u; + program.Metadata().blockInfo[2].terminator.kind = TerminatorKind::Branch; + program.Metadata().blockInfo[2].terminator.trueBlock = 1u; + IrBuilder builder(program); + builder.SetInsertionPoint(entry); + auto& zero = builder.Constant(0u); + auto& producer = builder.Emit(IrOpcode::LaneId, IrType::U32, {}); + const auto phi = [&](IrBlock& block, IrType type = IrType::U32) -> IrValue& { + auto& value = program.CreateValue(IrOpcode::Phi, type); + block.AppendInstruction(&value); + return value; + }; + auto& first = phi(header); + auto& second = phi(latch); + auto& tail = phi(header); + auto& self = phi(header); + auto& liveFirst = phi(header); + auto& liveSecond = phi(latch); + auto& condition = phi(header, IrType::Bool); + first.AddPhiOperand(&entry, &producer); + first.AddPhiOperand(&latch, &second); + second.AddPhiOperand(&header, &first); + tail.AddPhiOperand(&entry, &zero); + tail.AddPhiOperand(&latch, &second); + self.AddPhiOperand(&entry, &zero); + self.AddPhiOperand(&latch, &self); + liveFirst.AddPhiOperand(&entry, &zero); + liveFirst.AddPhiOperand(&latch, &liveSecond); + liveSecond.AddPhiOperand(&header, &liveFirst); + condition.AddPhiOperand(&entry, &builder.ConstantBool(true)); + condition.AddPhiOperand(&latch, &condition); + headerInfo.condition = &condition; + builder.SetInsertionPoint(header); + static_cast(builder.Emit(IrOpcode::Reference, IrType::Void, {&condition})); + builder.SetInsertionPoint(latch); + auto& store = builder.Emit(IrOpcode::StoreVectorWindow, IrType::Void, {&zero, &liveSecond}); + ValidateProgram(program, true); + DeadCodeEliminator{}.EliminateUnobservedPhis(program); + ValidateProgram(program, true); + Require(first.Parent() == nullptr && second.Parent() == nullptr && tail.Parent() == nullptr && self.Parent() == nullptr, "phi cycles: an unobserved cycle or its tail survived"); + Require(!first.HasUses() && !second.HasUses() && !tail.HasUses() && !self.HasUses(), "phi cycles: a removed cycle retained uses"); + Require(liveFirst.Parent() == &header && liveSecond.Parent() == &latch && liveFirst.Argument(1) == &liveSecond && liveSecond.Argument(0) == &liveFirst, "phi cycles: an observed cycle was changed"); + Require(store.Parent() == &latch && store.Argument(1) == &liveSecond, "phi cycles: an observable store was changed"); + Require(condition.Parent() == &header && headerInfo.condition == &condition && condition.Argument(1) == &condition, "phi cycles: a branch condition was changed"); + DeadCodeEliminator{}.Eliminate(program); + Require(producer.Parent() == nullptr && !producer.HasUses(), "phi cycles: ordinary DCE retained a released producer"); + DeadCodeEliminator{}.EliminateUnobservedPhis(program); + ValidateProgram(program, true); + Require(entry.Instructions().empty() && header.Instructions().size() == 3u && latch.Instructions().size() == 2u, "phi cycles: repeated elimination changed the live graph"); +} + void CheckLongAncillaryForwarding() { IrProgram program; auto& block = program.CreateBlock(); @@ -127,6 +204,7 @@ int main() { try { for (unsigned mode = 0; mode < 5u; ++mode) CheckDiamond(mode); CheckLongAncillaryForwarding(); + CheckUnobservedPhiCycles(); std::puts("constant phi dominance tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/ControlFlow.cpp b/core/libs/prx/libSceAgcDriver/tests/ControlFlow.cpp index 3febba4f40..b59161ab49 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ControlFlow.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ControlFlow.cpp @@ -445,6 +445,26 @@ void verifyProgram(const Program& program) { } } +const std::vector TestedHeaderLatchStore{ + 0xbe880380u, 0x7e020280u, 0xbf0a8408u, 0xbf840004u, 0xe0700000u, 0x80000100u, 0x80088108u, 0xbf82fffau, 0xbf810000u}; +const std::vector SelfLoopLatchStore{ + 0xbe880380u, 0x7e020280u, 0xe0700000u, 0x80000100u, 0x80088108u, 0xbf0a8408u, 0xbf85fffbu, 0xbf810000u}; + +void verifyEmptyLoopContinues() { + for (const auto& [name, code] : {std::pair{"a loop whose latch stores behind a tested header", &TestedHeaderLatchStore}, std::pair{"a single-block loop that stores", &SelfLoopLatchStore}}) { + auto graph = GraphBuilder{}.Build(RdnaInstructionDecoder{}.Decode(*code)); + Structurizer{}.Structurize(graph); + bool loops = false; + for (const auto& block : graph.blocks) { + if (!block.terminator.loopHeader) continue; + loops = true; + const auto& continueTarget = graph.FindBlock(block.terminator.continueBlock); + require(continueTarget.instructionBegin == continueTarget.instructionEnd, std::string(name) + ": the continue block " + std::to_string(continueTarget.id) + " of the loop at block " + std::to_string(block.id) + " holds instructions"); + } + require(loops, std::string(name) + ": no loop was structured"); + } +} + const std::vector DwordTableGetpcFirst{ 0xbe8c1f00u, 0x800cff0cu, 0x00000058u, 0x820d800du, 0x83928202u, 0xd4c40016u, 0x02000500u, 0x8f128212u, 0xf4000386u, 0x24000000u, 0xbf8cc07fu, 0x7d820003u, 0x808c0e0cu, 0x828d800du, 0xbefd210cu, 0x7e020281u, 0xbf820003u, 0x7e020282u, 0xbf820001u, 0x7e020283u, @@ -711,6 +731,18 @@ loop: s_cbranch_scc1 loop s_endpgm)", {0xbe880380u, 0x7e020280u, 0xe0700000u, 0x80000100u, 0x80088108u, 0xbf0a8408u, 0xbf85fffbu, 0xbf810000u}, Split::None}, + {"memory access in a loop latch behind a tested header", R"( + s_mov_b32 s8, 0 + v_mov_b32 v1, 0 +loop: + s_cmp_lt_u32 s8, 4 + s_cbranch_scc0 done + buffer_store_dword v1, off, s[0:3], 0 + s_add_u32 s8, s8, 1 + s_branch loop +done: + s_endpgm)", + TestedHeaderLatchStore, Split::None}, {"shared tail in a loop", R"( s_mov_b32 s8, 0 v_mov_b32 v1, 0 @@ -1421,6 +1453,7 @@ done: verifyCallLinkMaskReadByScalarInstruction(); verifyLargeLoopExitFallback(); verifyNestedExitForwarders(); + verifyEmptyLoopContinues(); verifyIrreducibleLoopEntry(); } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); diff --git a/core/libs/prx/libSceAgcDriver/tests/DeferredDestruction.cpp b/core/libs/prx/libSceAgcDriver/tests/DeferredDestruction.cpp new file mode 100644 index 0000000000..7873086b80 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/DeferredDestruction.cpp @@ -0,0 +1,131 @@ +#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace AgcDriver::Graphics; + +namespace { + +int failures = 0; + +void Expect(bool condition, const std::string& what) { + if (condition) return; + std::fprintf(stderr, "FAIL: %s\n", what.c_str()); + ++failures; +} + +std::vector events; +VkResult idleResult = VK_SUCCESS; + +template +THandle Handle(std::uintptr_t value) { + return reinterpret_cast(value); +} + +std::string Name(const char* kind, const void* handle) { + return std::string(kind) + std::to_string(reinterpret_cast(handle)); +} + +VKAPI_ATTR void VKAPI_CALL mockDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks*) { + events.push_back(Name("buffer ", reinterpret_cast(buffer)) + Name(" on ", device)); +} + +VKAPI_ATTR void VKAPI_CALL mockFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks*) { + events.push_back(Name("memory ", reinterpret_cast(memory)) + Name(" on ", device)); +} + +VKAPI_ATTR VkResult VKAPI_CALL mockQueueWaitIdle(VkQueue queue) { + events.push_back(Name("idle ", queue)); + return idleResult; +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) { + if (std::strcmp(name, "vkDestroyBuffer") == 0) return reinterpret_cast(&mockDestroyBuffer); + if (std::strcmp(name, "vkFreeMemory") == 0) return reinterpret_cast(&mockFreeMemory); + if (std::strcmp(name, "vkQueueWaitIdle") == 0) return reinterpret_cast(&mockQueueWaitIdle); + return nullptr; +} + +Context ContextFor(VkDevice device, PFN_vkGetDeviceProcAddr proc) { + Context context{}; + context.device = device; + context.queue = Handle(0x900); + context.deviceProc = proc; + return context; +} + +void DeferredUntilTheQueueIsIdle() { + events.clear(); + const auto device = Handle(0x100); + const auto proc = DeferDestructionToIdle(device, mockProc); + const auto context = ContextFor(device, proc); + Expect(context.Function("vkQueueWaitIdle") == &mockQueueWaitIdle, "a function other than vkDestroyBuffer and vkFreeMemory was not passed through"); + context.Function("vkFreeMemory")(device, Handle(0x21), nullptr); + context.Function("vkDestroyBuffer")(device, Handle(0x11), nullptr); + context.Function("vkDestroyBuffer")(device, VK_NULL_HANDLE, nullptr); + Expect(events.empty() && DeferredObjects(device) == 2, "a destroy or free was not deferred, or a null handle was queued"); + const auto start = std::chrono::steady_clock::now(); + DestroyDeferredAtIdle(context, start); + Expect(events.empty() && DeferredObjects(device) == 2, "deferred objects younger than the drain age were destroyed, or the queue was waited for"); + DestroyDeferredAtIdle(context, start + std::chrono::seconds(1)); + const std::vector drained{"idle 2304", "buffer 17 on 256", "memory 33 on 256"}; + Expect(events == drained && DeferredObjects(device) == 0, "old deferred objects were not destroyed after a queue wait, buffers before memory"); + events.clear(); + for (std::uintptr_t index = 0; index < 512; ++index) context.Function("vkDestroyBuffer")(device, Handle(0x1000 + index), nullptr); + DestroyDeferredAtIdle(context, std::chrono::steady_clock::now()); + Expect(events.size() == 513 && events.front() == "idle 2304" && DeferredObjects(device) == 0, "512 young deferred objects were not destroyed after a queue wait"); + events.clear(); + idleResult = VK_ERROR_OUT_OF_HOST_MEMORY; + context.Function("vkFreeMemory")(device, Handle(0x22), nullptr); + bool threw = false; + try { + DestroyDeferredAtIdle(context, std::chrono::steady_clock::now() + std::chrono::seconds(1)); + } catch (const std::exception&) { + threw = true; + } + Expect(threw && DeferredObjects(device) == 1 && events.size() == 1, "a failed queue wait did not throw, or destroyed the deferred objects"); + idleResult = VK_SUCCESS; + events.clear(); + DestroyDeferredObjects(device); + Expect(events == std::vector{"memory 34 on 256"} && DeferredObjects(device) == 0, "the deferred objects were not destroyed when the device went, or the queue was waited for"); + events.clear(); + context.Function("vkDestroyBuffer")(device, Handle(0x12), nullptr); + Expect(events == std::vector{"buffer 18 on 256"}, "a destroy after the device went was deferred"); +} + +void OtherDevicesDestroyAtOnce() { + events.clear(); + const auto device = Handle(0x101); + const auto proc = DeferDestructionToIdle(device, mockProc); + const auto other = Handle(0x102); + reinterpret_cast(proc(other, "vkFreeMemory"))(other, Handle(0x23), nullptr); + Expect(events == std::vector{"memory 35 on 258"} && DeferredObjects(other) == 0, "a free on a device without deferred destruction was deferred"); + bool threw = false; + try { + DeferDestructionToIdle(device, mockProc); + } catch (const std::exception&) { + threw = true; + } + Expect(threw, "deferred destruction was set up twice for one device"); + DestroyDeferredObjects(device); +} + +} + +int main() { + try { + DeferredUntilTheQueueIsIdle(); + OtherDevicesDestroyAtOnce(); + } catch (const std::exception& error) { + std::fprintf(stderr, "FAIL: %s\n", error.what()); + return 1; + } + if (failures != 0) return 1; + std::puts("deferred destruction tests passed"); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/DenormalFlushElimination.cpp b/core/libs/prx/libSceAgcDriver/tests/DenormalFlushElimination.cpp index cc952f5ea9..5dd5cfc4ba 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DenormalFlushElimination.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DenormalFlushElimination.cpp @@ -110,7 +110,8 @@ std::vector Recompiled(std::span code) { request.target.maxWorkgroupSharedMemoryBytes = 49152; request.layout = {0, 0, 0, 128}; request.useCache = false; - const auto& words = Recompile(request).spirv.Words(); + const auto result = Recompile(request); + const auto& words = result.spirv.Words(); return {words.begin(), words.end()}; } diff --git a/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp b/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp index 79878c2766..cd876ad472 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp @@ -1,4 +1,5 @@ #include "GraphicsTests.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" @@ -13,6 +14,14 @@ #include #include #include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif namespace { @@ -417,6 +426,62 @@ void TestRetiredSurfaceRelease(const Context& context) { ClearDepthSurfaces(context.device); } +void TestUnmappedStalePlaneFlush(const Context& context) { + ClearDepthSurfaces(context.device); + constexpr VkExtent2D extent{256, 256}; + constexpr std::uint64_t unmapped = 0xa0000000; + DepthTarget target{unmapped, 0, extent, VK_FORMAT_D32_SFLOAT, 1.0f, 0}; + target.depthSwizzle = XorSwizzleMode(TextureTileMode::kZ64KBX); + Require(DepthSurfaceView(context, target, true) != VK_NULL_HANDLE, "a mirrorable depth surface must render"); + Require(DepthPlanesStale(), "a written mirrorable depth surface must leave its plane stale"); + std::vector> stored; + try { + stored = FlushDepthPlanes(unmapped, DepthSliceBytes(extent, 4u)); + } catch (const std::exception& error) { + Require(false, std::string("flushing a depth plane whose guest memory is not mapped must drop its results: ") + error.what()); + } + Require(stored.empty(), "a depth plane without mapped guest memory must not be stored"); + Require(!DepthPlanesStale(), "a dropped depth plane must not stay stale"); + ClearDepthSurfaces(context.device); +} + +void TestReadOnlyStalePlaneFlush(const Context& context) { + ClearDepthSurfaces(context.device); + constexpr std::size_t bytes = 1024 * 1024; +#ifdef _WIN32 + const auto release = [](void* pointer) { VirtualFree(pointer, 0, MEM_RELEASE); }; + std::unique_ptr mapping(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READONLY), release); + Require(mapping != nullptr, "cannot map a read-only depth plane"); +#else + const auto release = [](void* pointer) { munmap(pointer, bytes); }; + std::unique_ptr mapping(mmap(nullptr, bytes, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), release); + Require(mapping.get() != MAP_FAILED, "cannot map a read-only depth plane"); +#endif + const auto address = (reinterpret_cast(mapping.get()) + 65535u) & ~std::uintptr_t{65535u}; + constexpr VkExtent2D extent{256, 256}; + const auto planeBytes = DepthSliceBytes(extent, 4u); + Require(!AgcDriver::GuestMemory::CommittedRanges(address, planeBytes).empty(), "a read-only depth plane must remain readable"); + Require(AgcDriver::GuestMemory::CommittedRanges(address, planeBytes, true).empty(), "a read-only depth plane must not be writable"); + auto storageContext = context; + storageContext.formatProperties = [](VkPhysicalDevice, VkFormat, VkFormatProperties* properties) { + *properties = {}; + properties->optimalTilingFeatures = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT; + }; + 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"); + Require(DepthPlanesStale(), "the read-only depth plane must have pending results"); + bool rejected = false; + try { + static_cast(FlushDepthPlanes(address, planeBytes)); + } catch (const std::runtime_error& error) { + rejected = std::string_view(error.what()).find("storage texture has no committed guest pages") != std::string_view::npos; + } + Require(rejected, "a mapped read-only depth plane must retain the unsupported-store rejection"); + Require(DepthPlanesStale(), "a mapped read-only depth plane must retain its pending results"); + ClearDepthSurfaces(context.device); +} + void RunDepthSurfaceReuseTests() { static int deviceTag = 0; Context context{}; @@ -432,6 +497,8 @@ void RunDepthSurfaceReuseTests() { TestMultisampleDepthOwnership(context); TestMultisampleStencilAliases(context); TestStencilUnormView(context); + TestUnmappedStalePlaneFlush(context); + TestReadOnlyStalePlaneFlush(context); constexpr std::uint64_t depth32 = 0x40000000; constexpr std::uint64_t depth16 = 0x50000000; constexpr std::uint64_t depthStencil = 0x60000000; diff --git a/core/libs/prx/libSceAgcDriver/tests/DeviceFault.cpp b/core/libs/prx/libSceAgcDriver/tests/DeviceFault.cpp index 67475a41de..b2df523ef9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DeviceFault.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DeviceFault.cpp @@ -60,6 +60,25 @@ VkQueue FakeQueue() { return reinterpret_cast(static_cast(0x2000)); } +void Diagnostics() { + using AgcDriver::ChooseDeviceDiagnostics; + constexpr VkDeviceDiagnosticsConfigFlagsNV all = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_ERROR_REPORTING_BIT_NV; + for (const bool configSupported : {false, true}) { + for (const bool checkpointsSupported : {false, true}) { + const auto off = ChooseDeviceDiagnostics(false, configSupported, checkpointsSupported); + Require(!off.config && !off.checkpoints && off.flags == 0, "diagnostics were chosen without APS5_NV_DIAGNOSTICS"); + } + } + for (const bool checkpointsSupported : {false, true}) { + const auto unsupported = ChooseDeviceDiagnostics(true, false, checkpointsSupported); + Require(!unsupported.config && !unsupported.checkpoints && unsupported.flags == 0, "diagnostics were chosen on a device without VK_NV_device_diagnostics_config"); + } + const auto alone = ChooseDeviceDiagnostics(true, true, false); + Require(alone.config && !alone.checkpoints && alone.flags == all, "the diagnostics config without checkpoint support did not enable every crash-dump flag alone"); + const auto both = ChooseDeviceDiagnostics(true, true, true); + Require(both.config && both.checkpoints && both.flags == all, "the diagnostics config did not add the diagnostic checkpoints the device supports"); +} + void FaultInfo() { faultCalls = 0; faultFails = false; @@ -97,6 +116,35 @@ void Nothing() { Require(faultCalls == 0, "the fault info was queried without the extension"); } +void BufferMarkers() { + AgcDriver::BufferMarkerLog log; + Require(log.Report(0, 0) == std::vector{"[device-fault] no buffer marker reached"}, "an untouched marker buffer was not reported"); + Require(log.Next(0x10000) == 1 && log.Next(0x10100) == 2 && log.Next(0x10200) == 3, "markers are not numbered from 1"); + const auto lines = log.Report(3, 1); + Require(lines.size() == 4, "a hang two markers past the last finished one does not get four lines"); + Require(lines[0] == "[device-fault] last dispatch reached: marker 3, program 0x10200", "the reached line changed"); + Require(lines[1] == "[device-fault] last dispatch finished: marker 1, program 0x10000", "the finished line changed"); + Require(lines[2] == "[device-fault] unfinished dispatch: marker 2, program 0x10100", "the first unfinished line changed"); + Require(lines[3] == "[device-fault] unfinished dispatch: marker 3, program 0x10200", "the second unfinished line changed"); + Require(log.Report(3, 3).size() == 2, "a finished queue listed unfinished dispatches"); + const auto first = log.Report(1, 0); + Require(first.size() == 3 && first[1] == "[device-fault] no dispatch finished" && first[2] == "[device-fault] unfinished dispatch: marker 1, program 0x10000", "a hang in the first dispatch was not reported as nothing finished"); + Require(log.Report(2, 3).size() == 2, "markers finished past the reached one listed unfinished dispatches"); +} + +void BufferMarkersWrap() { + AgcDriver::BufferMarkerLog log; + for (std::uint32_t i = 0; i <= AgcDriver::BufferMarkerLog::Capacity; ++i) static_cast(log.Next(0x20000 + i)); + const auto lines = log.Report(AgcDriver::BufferMarkerLog::Capacity + 1, 1); + Require(lines[0] == "[device-fault] last dispatch reached: marker 4097, program 0x21000", "the newest marker was not kept"); + Require(lines[1] == "[device-fault] last dispatch finished: marker 1, program not in the log", "an overwritten marker kept a program"); + Require(lines.size() == 2 + AgcDriver::BufferMarkerLog::Listed + 1 && lines.back() == "[device-fault] 4088 more unfinished dispatches", "a full log of unfinished markers was not listed and counted"); + Require(log.Report(AgcDriver::BufferMarkerLog::Capacity + 1, 0).size() == 2, "more unfinished markers than the log holds were listed"); + const auto pending = log.Report(AgcDriver::BufferMarkerLog::Capacity + 1, AgcDriver::BufferMarkerLog::Capacity - 19); + Require(pending.size() == 2 + AgcDriver::BufferMarkerLog::Listed + 1, "a long unfinished run was not cut to the listed count"); + Require(pending.back() == "[device-fault] 12 more unfinished dispatches", "the count of unlisted unfinished dispatches changed"); +} + void AddressKinds() { Require(std::strcmp(AgcDriver::DeviceFaultAddressKind(VK_DEVICE_FAULT_ADDRESS_TYPE_NONE_EXT), "none") == 0, "none"); Require(std::strcmp(AgcDriver::DeviceFaultAddressKind(VK_DEVICE_FAULT_ADDRESS_TYPE_WRITE_INVALID_EXT), "write invalid") == 0, "write invalid"); @@ -110,11 +158,14 @@ void AddressKinds() { int main() { try { + Diagnostics(); FaultInfo(); FaultInfoFails(); Checkpoints(); Nothing(); AddressKinds(); + BufferMarkers(); + BufferMarkersWrap(); } catch (const std::exception& error) { std::fprintf(stderr, "agc_device_fault: %s\n", error.what()); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/F32DenormPreserve.cpp b/core/libs/prx/libSceAgcDriver/tests/F32DenormPreserve.cpp index 0f3fe85da7..f47e2d0ebc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/F32DenormPreserve.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/F32DenormPreserve.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -19,17 +20,19 @@ void require(bool condition, const std::string& message) { } constexpr std::array CvtF16F32Store{0x7e021500u, 0xe0700000u, 0x80000100u, 0xbf810000u}; +constexpr std::array MovStore{0x7e020300u, 0xe0700000u, 0x80000100u, 0xbf810000u}; -std::vector recompile(bool preserve, bool flush, bool flushCapability = true) { +std::vector recompile(bool preserve, bool flush, bool flushCapability = true, std::optional floatMode = std::nullopt, std::span code = CvtF16F32Store) { const std::array capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityDenormPreserve, spv::CapabilityDenormFlushToZero}; const std::array userData{0x10000000u, 0x00100000u, 0x40u, 0x00027facu}; const std::array extensions{"SPV_KHR_storage_buffer_storage_class", "SPV_KHR_float_controls"}; RecompileRequest request{}; - request.shader = {ShaderStage::Compute, 0x20000u, CvtF16F32Store, 0, {}}; + request.shader = {ShaderStage::Compute, 0x20000u, code, 0, {}}; request.context.waveSize = 64; request.context.userDataBaseRegister = 0; request.context.userData = userData; request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.context.floatMode = floatMode; request.target.vulkanVersion = 0x00403000u; request.target.spirvVersion = 0x00010600u; request.target.subgroupSize = 64; @@ -79,6 +82,16 @@ void verify() { require(flushed.flush32 && !flushed.preserve32, "the hardware 32-bit flush did not take precedence over the preserve flag"); const auto nativeFlush = scan(recompile(true, true, false)); require(!nativeFlush.preserve32 && !nativeFlush.flush32, "a hardware flush without the DenormFlushToZero capability declared a 32-bit denormal execution mode"); + const auto flushingMode = scan(recompile(true, true, true, ShaderFloatMode{0xc0u, true, false, false}, MovStore)); + require(flushingMode.flush32 && !flushingMode.preserve32, "FLOAT_MODE 0xc0 flushes f32 denormals but the module did not take the hardware flush"); + for (const std::uint32_t mode : {0xd0u, 0xe0u, 0xf0u}) { + char name[64]; + std::snprintf(name, sizeof(name), "FLOAT_MODE 0x%02x keeps f32 denormals", mode); + const auto kept = scan(recompile(true, true, true, ShaderFloatMode{mode, true, false, false}, MovStore)); + require(kept.preserve32 && kept.preserveCapability && !kept.flush32, std::string(name) + ", but the module did not declare DenormPreserve 32 instead of the hardware flush"); + const auto unpreserved = scan(recompile(false, true, true, ShaderFloatMode{mode, true, false, false}, MovStore)); + require(!unpreserved.flush32 && !unpreserved.preserve32, std::string(name) + ", but the module took the hardware flush on a device that cannot preserve them"); + } } } @@ -90,6 +103,6 @@ int main() { std::fprintf(stderr, "F32DenormPreserve: %s\n", error.what()); return 1; } - std::puts("F32DenormPreserve: the 32-bit DenormPreserve execution mode follows the target flag and yields to the hardware flush"); + std::puts("F32DenormPreserve: the 32-bit DenormPreserve execution mode follows the target flag and yields to the hardware flush in shaders that flush f32 denormals"); return 0; } diff --git a/core/libs/prx/libSceAgcDriver/tests/FlatScratch.cpp b/core/libs/prx/libSceAgcDriver/tests/FlatScratch.cpp new file mode 100644 index 0000000000..73aef4bdf0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/FlatScratch.cpp @@ -0,0 +1,118 @@ +#include "RdnaDecoder/RdnaMemoryOpDecoder.hpp" +#include "Translation/TranslationContext.hpp" +#include +#include +#include +using namespace ShaderRecompiler; +static unsigned tests = 0; +#define Require(value) \ + do { \ + if (!(value)) { \ + std::fprintf(stderr, "flat scratch assertion line %u: %s\n", __LINE__, \ + #value); \ + throw std::runtime_error("flat scratch regression"); \ + } \ + } while (false) +static RdnaInstruction Decode(unsigned seg, unsigned saddr, unsigned addr, + unsigned offset = 0, unsigned opcode = 12) { + const std::array code{(0x37u << 26) | (opcode << 18) | + (seg << 14) | offset, + (2u << 24) | (saddr << 16) | addr}; + return DecodeRdnaFlat(0x154cu, code, 0); +} +static void Reject(unsigned seg, unsigned saddr, unsigned addr) { + try { + (void)Decode(seg, saddr, addr); + } catch (const std::runtime_error &) { + ++tests; + return; + } + throw std::runtime_error("invalid FLAT accepted"); +} +static void Scratch(unsigned saddr, unsigned addr, unsigned expectedVector, + unsigned expectedScalar) { + auto op = Decode(1, saddr, addr, 0xffcu); + Require(op.memorySegment == 1 && op.memoryOffset == -4 && + op.sourceCount == 2); + Require(op.source0.kind == (expectedVector + ? RdnaOperandKind::VectorRegister + : RdnaOperandKind::IntegerInlineConstant)); + Require(op.source1.kind == (expectedScalar + ? RdnaOperandKind::ScalarRegister + : RdnaOperandKind::IntegerInlineConstant)); + if (expectedVector) + Require(op.source0.reg == addr); + else + Require(op.source0.value == 0); + if (expectedScalar) + Require(op.source1.reg == saddr); + else + Require(op.source1.value == 0); + IrProgram program; + auto &block = program.CreateBlock(); + program.SetEntryBlock(block); + TranslationContext context(program, block, 256); + context.TranslateInstruction(op); + unsigned vectors = 0, addressReads = 0, destinationReads = 0, scalars = 0, + loads = 0, scratch = 0; + for (auto *inst : block.Instructions()) { + if (inst->Opcode() == IrOpcode::GetVectorRegister) { + ++vectors; + auto reg = inst->Arguments().at(0)->Register().index; + Require(reg < 256); + if (reg == addr) + ++addressReads; + if (reg == 2) + ++destinationReads; + } + if (inst->Opcode() == IrOpcode::GetScalarRegister) + ++scalars; + if (inst->Opcode() == IrOpcode::LoadAddressU32) + ++loads; + if (inst->Opcode() == IrOpcode::GetScratchResource) + ++scratch; + } + std::printf( + "SV/SS/ST saddr=%u addr=%u vector=%u scalar=%u loads=%u scratch=%u\n", + saddr, addr, vectors, scalars, loads, scratch); + std::fflush(stdout); + Require(vectors == expectedVector + 1 && addressReads == expectedVector && + destinationReads == 1 && scalars == expectedScalar && loads == 1 && + scratch == 1); + ++tests; +} +int main() { + + const std::array code{0xdc304000u, 0x027d00ffu}; + auto actual = DecodeRdnaFlat(0x154cu, code, 0); + Require(actual.destination.reg == 2 && actual.source0.reg == 255 && + actual.source1.value == 0); + ++tests; + Scratch(125, 255, 1, 0); + Scratch(125, 254, 1, 0); + Scratch(0, 255, 0, 1); + Scratch(105, 255, 0, 1); + Scratch(127, 255, 0, 0); + Reject(0, 125, 255); + Reject(2, 125, 255); + Reject(2, 127, 255); + Reject(1, 108, 0); + Reject(3, 0, 0); + auto flat = Decode(0, 125, 254); + Require(flat.source1.kind == RdnaOperandKind::VectorRegister && + flat.source1.reg == 255); + ++tests; + auto global = Decode(2, 0, 255); + Require(global.source0.reg == 255 && + global.source1.kind == RdnaOperandKind::ScalarRegister); + ++tests; + try { + const std::array truncated{0xdc304000u}; + (void)DecodeRdnaFlat(0, truncated, 0); + throw std::logic_error("truncated accepted"); + } catch (const std::out_of_range &) { + ++tests; + } + std::printf("PASS %u flat scratch decode/IR and invalid boundary cases\n", + tests); +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp index ec73dea01a..849703ad95 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/PresentMode.hpp" +#include "prx/libSceAgcDriver/Execution/include/SwapchainImages.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" @@ -355,7 +356,11 @@ void testFailureLog() { std::fflush(stderr); const int saved = APS5_DUP(APS5_FILENO(stderr)); check(saved >= 0, "stderr could not be saved"); - check(std::freopen(path.string().c_str(), "wb", stderr) != nullptr, "stderr could not be redirected"); +#ifdef _WIN32 + check(_wfreopen(path.wstring().c_str(), L"wb", stderr) != nullptr, "stderr could not be redirected"); +#else + check(std::freopen(path.c_str(), "wb", stderr) != nullptr, "stderr could not be redirected"); +#endif std::string message; std::string error; try { @@ -402,6 +407,19 @@ void testReset(bool compute) { void testPresentMode() { using AgcDriver::SelectPresentMode; + VkSurfaceCapabilitiesKHR surface{}; + surface.minImageCount = 2; + surface.maxImageCount = 8; + check(AgcDriver::SwapchainImageCount(surface) == 3, "the swapchain has no image beyond the surface minimum"); + surface.minImageCount = 3; + check(AgcDriver::SwapchainImageCount(surface) == 3, "a three-image surface minimum gained an image"); + surface.minImageCount = 5; + check(AgcDriver::SwapchainImageCount(surface) == 5, "the swapchain does not follow a larger surface minimum"); + surface.minImageCount = 2; + surface.maxImageCount = 2; + check(AgcDriver::SwapchainImageCount(surface) == 2, "the swapchain exceeds the surface maximum"); + surface.maxImageCount = 0; + check(AgcDriver::SwapchainImageCount(surface) == 3, "an unlimited surface maximum capped the swapchain"); const std::array wayland{VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR}; const std::array all{VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR}; check(SelectPresentMode(nullptr, wayland) == VK_PRESENT_MODE_FIFO_KHR, "the default present mode is not FIFO"); @@ -417,6 +435,11 @@ void testPresentMode() { check(expectFailure([&] { SelectPresentMode("", all); }).find("must be fifo, fifo_latest_ready, mailbox or immediate") != std::string::npos, "an empty present mode name was accepted"); const std::array none{}; check(expectFailure([&] { SelectPresentMode(nullptr, none); }).find("does not support") != std::string::npos, "FIFO was selected on a surface without it"); + using AgcDriver::SelectPresentQueue; + check(SelectPresentQueue(16, 2) == 2u, "the presentation queue does not follow the main and settle queues"); + check(SelectPresentQueue(16, 1) == 1u, "the presentation queue does not follow the main queue alone"); + check(!SelectPresentQueue(2, 2).has_value(), "a presentation queue was taken from a family without a free queue"); + check(!SelectPresentQueue(1, 1).has_value(), "a presentation queue was taken from a family with one queue"); } int main(int argc, char** argv) { diff --git a/core/libs/prx/libSceAgcDriver/tests/FormattedBufferOnce.cpp b/core/libs/prx/libSceAgcDriver/tests/FormattedBufferOnce.cpp index 0eba15d628..86b372cf72 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FormattedBufferOnce.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FormattedBufferOnce.cpp @@ -19,8 +19,8 @@ void Check(bool condition, const std::string& message) { } SpirvTarget Target() { - static constexpr std::array capabilities{ - spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance, + static constexpr std::array capabilities{ + spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance, spv::CapabilityInt64Atomics, }; static constexpr std::array extensions{ "SPV_EXT_descriptor_indexing", "SPV_KHR_8bit_storage", "SPV_KHR_float_controls", "SPV_KHR_physical_storage_buffer", @@ -31,6 +31,7 @@ SpirvTarget Target() { struct Counts { std::size_t words = 0; + std::size_t arrayLengths = 0; std::size_t switches = 0; std::size_t calls = 0; std::size_t functions = 0; @@ -46,6 +47,7 @@ Counts Count(std::span words) { Check(count != 0 && count <= words.size() - offset, "formatted buffer once: malformed SPIR-V instruction"); const auto opcode = words[offset] & spv::OpCodeMask; if (opcode == spv::OpSwitch) ++counts.switches; + if (opcode == spv::OpArrayLength) ++counts.arrayLengths; if (opcode == spv::OpFunctionCall) ++counts.calls; if (opcode == spv::OpAtomicCompareExchange) ++counts.compareExchanges; if (opcode == spv::OpFunction) { @@ -57,9 +59,10 @@ Counts Count(std::span words) { return counts; } -Counts Compile(std::size_t loads, bool formattedStores = false, bool gpu = false, bool vertex = false, bool registration = false) { +Counts Compile(std::size_t loads, bool formattedStores = false, bool gpu = false, bool vertex = false, bool registration = false, bool atomicBeforeLoads = false) { std::vector code; if (gpu) code.push_back(0x7e160500u); + if (atomicBeforeLoads) code.insert(code.end(), {0xe1705000u, 0x80012803u, 0xbf8c3f70u}); for (std::uint32_t load = 0; load < loads; ++load) code.insert(code.end(), {0xe0000000u | (load * 4u), 0x80020000u | ((10u + load) << 8u)}); code.push_back(0xbf8c3f70u); for (std::uint32_t load = 0; load < loads; ++load) code.insert(code.end(), {(formattedStores ? 0xe0100000u : 0xe0700000u) | (load * 4u), 0x80010000u | ((10u + load) << 8u)}); @@ -83,6 +86,32 @@ Counts Compile(std::size_t loads, bool formattedStores = false, bool gpu = false return Count(Recompile(request).spirv.Words()); } +Counts CompileScalarReads(std::size_t loads) { + std::array userData{}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0u, {}, 0, {}}; + request.target = Target(); + request.layout = {0, 0, 0, 128}; + request.context.waveSize = 32; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.useCache = false; + std::vector code; + for (std::uint32_t load = 0; load < loads; ++load) { + code.insert(code.end(), {0xf4000200u | (load * 4u), 0xfa000004u, 0x7e040208u | (load << 8u)}); + } + code.push_back(0xbf8c3f70u); + for (std::uint32_t load = 0; load < loads; ++load) code.insert(code.end(), {0xe0701000u | (load * 4u), 0x80010000u | ((10u + load) << 8u)}); + code.push_back(0xbf810000u); + const std::span words(code); + const std::array memory{{{reinterpret_cast(words.data()), std::as_bytes(words)}}}; + request.shader.codeAddress = reinterpret_cast(words.data()); + request.shader.code = words; + request.context.memory = memory; + return Count(Recompile(request).spirv.Words()); +} + } int main() { @@ -90,6 +119,9 @@ int main() { const auto none = Compile(0); const auto one = Compile(1); const auto three = Compile(3); + const auto scalarOne = CompileScalarReads(1); + const auto scalarThree = CompileScalarReads(3); + const auto atomicThenLoad = Compile(1, false, false, false, false, true); const auto gpuOne = Compile(1, false, true); const auto vertexSpecialized = Compile(1, false, false, true, false); const auto vertexRegistration = Compile(1, false, false, true, true); @@ -105,6 +137,12 @@ int main() { Check(storeOne.functions == none.functions + 2 && storeOne.calls == none.calls + 2, "formatted buffer once: a formatted load and a formatted store to another resource emitted " + std::to_string(storeOne.functions - none.functions) + " functions and " + std::to_string(storeOne.calls - none.calls) + " calls more than none, expected one function and one call each"); Check(storeThree.functions == storeOne.functions && storeThree.calls == storeOne.calls + 4 && storeThree.switches == storeOne.switches, "formatted buffer once: two more formatted loads and stores emitted " + std::to_string(storeThree.functions - storeOne.functions) + " functions, " + std::to_string(storeThree.calls - storeOne.calls) + " calls and " + std::to_string(storeThree.switches - storeOne.switches) + " switches more, expected four calls only"); Check(three.words - one.words < (one.words - none.words) / 2u, "formatted buffer once: 2 more formatted loads added " + std::to_string(three.words - one.words) + " SPIR-V words, as many as the first one with its shared function (" + std::to_string(one.words - none.words) + ")"); +#if ANYPS5_ENABLE_SPIRV_TOOLS + Check(atomicThenLoad.words > 5u, "a 64-bit buffer atomic followed by a buffer read did not produce a SPIR-V module"); +#else + Check(scalarOne.arrayLengths == 1u && scalarThree.arrayLengths == 3u, "scalar buffer reads across generated labels emitted " + std::to_string(scalarThree.arrayLengths) + " OpArrayLength instructions for 3 reads (expected one per label)"); + Check(atomicThenLoad.arrayLengths > 1u, "a buffer query inside the 64-bit atomic path was reused after its merge (expected at least 2 OpArrayLength instructions, got " + std::to_string(atomicThenLoad.arrayLengths) + ")"); +#endif const auto gpuThree = Compile(3, false, true); Check(gpuThree.functions == gpuOne.functions && gpuThree.calls > gpuOne.calls, "formatted buffer once: two more formatted loads from a GPU-selected descriptor emitted " + std::to_string(gpuThree.functions - gpuOne.functions) + " more functions, expected calls to the shared one"); Check(gpuThree.switches == gpuOne.switches, "formatted buffer once: two more formatted loads from a GPU-selected descriptor emitted " + std::to_string(gpuThree.switches - gpuOne.switches) + " more format switches"); diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameCapture.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameCapture.cpp new file mode 100644 index 0000000000..63bfa42046 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/FrameCapture.cpp @@ -0,0 +1,238 @@ +#include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#define getpid _getpid +#else +#include +#endif + +namespace { + +using AgcDriver::FrameCall; +using AgcDriver::FrameCallKind; +using AgcDriver::FrameCaptureReader; +using AgcDriver::FrameCaptureWriter; +using AgcDriver::FrameMemoryChunk; +using AgcDriver::FrameMemoryKind; +using AgcDriver::FrameRangeKind; + +void check(bool condition, const std::string& message) { + if (!condition) { + std::fprintf(stderr, "FAILED: %s\n", message.c_str()); + std::exit(1); + } +} + +template +void checkThrows(TFunction&& function, const std::string& message) { + try { + function(); + } catch (const std::runtime_error&) { + return; + } + check(false, message); +} + +bool sameRegisters(const std::vector& left, const std::vector& right) { + if (left.size() != right.size()) return false; + for (std::size_t index = 0; index < left.size(); ++index) { + if (left[index].offset != right[index].offset || left[index].value != right[index].value) return false; + } + return true; +} + +bool sameCall(const FrameCall& left, const FrameCall& right) { + return left.kind == right.kind && left.ordinal == right.ordinal && left.timeNs == right.timeNs && left.thread == right.thread && left.accepted == right.accepted && left.completed == right.completed && left.queue == right.queue && + left.flags == right.flags && left.cpuFlip == right.cpuFlip && left.address == right.address && left.size == right.size && left.values == right.values && sameRegisters(left.context, right.context) && + sameRegisters(left.primitive, right.primitive); +} + +std::vector sampleCalls() { + std::vector calls; + FrameCall shader; + shader.kind = FrameCallKind::RegisterShader; + shader.ordinal = 1; + shader.address = 0x2000010000; + calls.push_back(shader); + FrameCall abi; + abi.kind = FrameCallKind::ResolveGraphicsStagesAbi; + abi.ordinal = 2; + abi.values = {0x2000010000, 0x2000020000, 0}; + abi.context = {{0x1a0, 7}, {0x1a1, 0xffffffffu}}; + abi.primitive = {{0x242, 3}}; + calls.push_back(abi); + FrameCall submit; + submit.kind = FrameCallKind::Submit; + submit.ordinal = 3; + submit.timeNs = 123456789; + submit.thread = 4242; + submit.accepted = 20; + submit.completed = 17; + submit.queue = 0x21; + submit.address = 0x2000030000; + submit.size = 4096; + calls.push_back(submit); + FrameCall flip; + flip.kind = FrameCallKind::Flip; + flip.ordinal = 4; + flip.cpuFlip = true; + flip.values = {1, 2, 0, static_cast(-5)}; + calls.push_back(flip); + FrameCall unregister; + unregister.kind = FrameCallKind::UnregisterVideoOutput; + unregister.ordinal = 5; + unregister.values = {1}; + calls.push_back(unregister); + FrameCall end; + end.kind = FrameCallKind::End; + end.ordinal = 6; + calls.push_back(end); + return calls; +} + +std::vector sampleMemory() { + constexpr std::size_t Block = FrameCaptureWriter::BlockBytes; + std::vector memory(Block * 150 + 4096); + for (std::size_t block = 0; block < 150; ++block) { + if (block % 3 == 1 || (block >= 10 && block < 90)) { + for (std::size_t at = 0; at < Block; at += 97) memory[block * Block + at] = static_cast((block * 131 + at) & 0xff); + } + } + memory[memory.size() - 1] = std::byte{0x5a}; + return memory; +} + +void truncateFile(const std::filesystem::path& path, std::uintmax_t removed) { + const auto size = std::filesystem::file_size(path); + std::filesystem::resize_file(path, size - removed); +} + +void testRoundTrip(const std::filesystem::path& directory) { + constexpr std::uint64_t Base = 0x2000000000; + const auto calls = sampleCalls(); + const auto memory = sampleMemory(); + std::vector delta(8192); + for (std::size_t at = 0; at < delta.size(); ++at) delta[at] = static_cast(at * 7); + std::uint64_t stored = 0; + { + FrameCaptureWriter writer(directory); + writer.WriteRange(Base, memory.size(), FrameRangeKind::Watched, 3, AgcDriver::FrameRangeAccess::Readable | AgcDriver::FrameRangeAccess::Gpu); + stored = writer.WriteSnapshot(Base, memory); + for (const auto& call : calls) writer.WriteCall(call); + writer.WriteDelta(1, 17, Base + 0x3000, delta); + writer.Finish("{\"magic\":\"aps5-frame-capture 1\"}\n"); + check(writer.RawBytes() == stored + delta.size(), "raw byte count does not add the snapshot and the delta"); + } + std::size_t nonzero = 0; + for (std::size_t at = 0; at < memory.size(); at += FrameCaptureWriter::BlockBytes) { + const auto bytes = std::min(FrameCaptureWriter::BlockBytes, memory.size() - at); + if (!AgcDriver::AllZero(std::span(memory).subspan(at, bytes))) nonzero += bytes; + } + check(stored == nonzero, "the snapshot stored zero blocks or lost data blocks"); + + const FrameCaptureReader reader(directory); + check(!reader.Truncated(), "a finished capture reads as truncated"); + check(reader.Header() == "{\"magic\":\"aps5-frame-capture 1\"}\n", "header does not round-trip"); + check(reader.Calls().size() == calls.size(), "call count does not round-trip"); + for (std::size_t index = 0; index < calls.size(); ++index) check(sameCall(reader.Calls()[index], calls[index]), "call " + std::to_string(index) + " does not round-trip"); + + std::vector restored(memory.size()); + std::size_t ranges = 0, snapshots = 0, deltas = 0; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Range) { + ++ranges; + check(chunk.address == Base && chunk.bytes == memory.size() && chunk.rangeKind == FrameRangeKind::Watched && chunk.protection == 3 && + chunk.access == (AgcDriver::FrameRangeAccess::Readable | AgcDriver::FrameRangeAccess::Gpu), "range does not round-trip"); + continue; + } + if (chunk.kind == FrameMemoryKind::Snapshot) { + ++snapshots; + check(chunk.bytes <= FrameCaptureWriter::ChunkBytes, "snapshot chunk is larger than the chunk limit"); + check(chunk.address >= Base && chunk.address - Base + chunk.bytes <= restored.size(), "snapshot chunk outside its range"); + reader.Read(chunk, std::span(restored).subspan(chunk.address - Base, chunk.bytes)); + continue; + } + ++deltas; + check(chunk.group == 1 && chunk.completed == 17 && chunk.address == Base + 0x3000, "delta header does not round-trip"); + check(reader.Read(chunk) == delta, "delta bytes do not round-trip"); + } + check(ranges == 1 && deltas == 1 && snapshots >= 2, "unexpected chunk counts"); + check(restored == memory, "snapshot does not restore the memory"); + std::vector wrong(1); + checkThrows([&] { reader.Read(reader.Chunks()[1], wrong); }, "a read into a wrongly sized destination does not throw"); +} + +void testTruncated(const std::filesystem::path& source, const std::filesystem::path& directory) { + std::filesystem::remove_all(directory); + std::filesystem::copy(source, directory, std::filesystem::copy_options::recursive); + truncateFile(directory / FrameCaptureWriter::CallsName, 3); + truncateFile(directory / FrameCaptureWriter::MemoryName, 10); + std::filesystem::remove(directory / FrameCaptureWriter::HeaderName); + const FrameCaptureReader reader(directory); + const auto calls = sampleCalls(); + check(reader.Truncated(), "a cut capture does not read as truncated"); + check(reader.Calls().size() == calls.size() - 1, "a cut call record is not dropped"); + check(reader.Chunks().back().kind != FrameMemoryKind::Delta, "a cut memory chunk is not dropped"); +} + +void testCorrupt(const std::filesystem::path& source, const std::filesystem::path& directory) { + std::filesystem::remove_all(directory); + std::filesystem::copy(source, directory, std::filesystem::copy_options::recursive); + { + std::fstream file(directory / FrameCaptureWriter::CallsName, std::ios::binary | std::ios::in | std::ios::out); + file.seekp(static_cast(FrameCaptureWriter::CallsMagic.size())); + const std::uint32_t kind = 99; + file.write(reinterpret_cast(&kind), sizeof(kind)); + } + checkThrows([&] { FrameCaptureReader reader(directory); }, "an unknown call kind does not throw"); + std::ofstream(directory / FrameCaptureWriter::CallsName, std::ios::binary | std::ios::trunc) << "not a capture"; + checkThrows([&] { FrameCaptureReader reader(directory); }, "a file without the magic does not throw"); +} + +void testWriter(const std::filesystem::path& directory) { + FrameCaptureWriter writer(directory); + writer.Finish("{}"); + FrameCall call; + call.kind = FrameCallKind::Suspend; + checkThrows([&] { writer.WriteCall(call); }, "a call after Finish does not throw"); + checkThrows([&] { writer.Finish("{}"); }, "a second Finish does not throw"); + FrameCall unknown; + unknown.kind = static_cast(77); + FrameCaptureWriter other(directory / "other"); + checkThrows([&] { other.WriteCall(unknown); }, "an unknown call kind is written"); +} + +void testAllZero() { + std::vector bytes(37); + check(AgcDriver::AllZero(bytes), "zero bytes are not all zero"); + check(AgcDriver::AllZero({}), "an empty span is not all zero"); + bytes[36] = std::byte{1}; + check(!AgcDriver::AllZero(bytes), "a nonzero tail byte is missed"); + bytes[36] = std::byte{0}; + bytes[3] = std::byte{1}; + check(!AgcDriver::AllZero(bytes), "a nonzero word is missed"); +} + +} + +int main() { + const auto root = std::filesystem::temp_directory_path() / ("aps5-frame-capture-test-" + std::to_string(getpid())); + std::filesystem::remove_all(root); + testAllZero(); + testRoundTrip(root / "capture"); + testTruncated(root / "capture", root / "truncated"); + testCorrupt(root / "capture", root / "corrupt"); + testWriter(root / "writer"); + std::filesystem::remove_all(root); + std::puts("frame capture tests passed"); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameCaptureEop.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameCaptureEop.cpp new file mode 100644 index 0000000000..88d8cb144e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/FrameCaptureEop.cpp @@ -0,0 +1,143 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" +#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +namespace { + +using AgcDriver::FrameCallKind; +using AgcDriver::FrameCaptureReader; + +constexpr std::uint32_t Handle = 7; +constexpr std::size_t BlockBytes = 1u << 20; +constexpr std::size_t Frames = 6; +constexpr std::size_t CapturedFrames = 3; + +void check(bool condition, const std::string& reason) { + if (!condition) throw std::runtime_error(reason); +} + +class Request final : public AgcDriver::IFlipRequest { +public: + explicit Request(std::atomic& ready) : ready(ready) {} + void GpuReady(const std::shared_ptr&) override { ++ready; } + void Fail(std::exception_ptr error) noexcept override { + if (!error) std::terminate(); + } + +private: + std::atomic& ready; +}; + +class Output final : public AgcDriver::IVideoOutput { +public: + std::shared_ptr Reserve(const AgcDriver::FlipInfo&) override { return std::make_shared(ready); } + void Fail(std::exception_ptr error) noexcept override { + if (!error) std::terminate(); + } + std::atomic ready = 0; +}; + +std::byte* allocate() { + auto* const block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(BlockBytes, 65536)); + check(block != nullptr, "cannot allocate guest memory"); + GuestArena::GuestArenaCommit_nid_postfix(block, BlockBytes, 0x04, BlockBytes); + GuestAllocations::Mutation mutation; + mutation.Add(block, BlockBytes, true, true, true); + return block; +} + +void submitEopFrame(std::byte* block, std::size_t frame) { + const auto token = AgcDriverReserveEopFlip_nid_postfix(AgcDriver::FlipInfo{Handle, 0, 1, static_cast(frame)}); + check(token != 0, "the EOP flip of frame " + std::to_string(frame) + " was not reserved"); + auto* const words = reinterpret_cast(block + frame * 256); + const auto label = reinterpret_cast(block + BlockBytes / 2 + frame * 8); + const std::array release{0xc0064900u, 0x06200504u, 0x42010000u, static_cast(label), static_cast(label >> 32u), 1u, 0u, token}; + std::memcpy(words, release.data(), sizeof(release)); + Packet packet{words, static_cast(release.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "the EOP submission of frame " + std::to_string(frame) + " failed"); +} + +void testEopFramesCountOnce() { + const char* directory = std::getenv("APS5_FRAME_CAPTURE"); + check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set"); + auto* const block = allocate(); + auto output = std::make_shared(); + AgcDriverRegisterVideoOutput_nid_postfix(Handle, output); + for (std::size_t frame = 0; frame < Frames; ++frame) submitEopFrame(block, frame); + AgcDriverWaitIdle_nid_postfix(); + check(output->ready.load() == static_cast(Frames), "the driver presented " + std::to_string(output->ready.load()) + " of the EOP flips"); + + const FrameCaptureReader reader{std::filesystem::path(directory)}; + check(!reader.Truncated(), "the capture is truncated"); + check(reader.Header().find("\"frames\":" + std::to_string(CapturedFrames)) != std::string::npos, "the capture header does not record " + std::to_string(CapturedFrames) + " frames: " + reader.Header()); + const auto& calls = reader.Calls(); + const auto start = std::find_if(calls.begin(), calls.end(), [](const auto& call) { return call.kind == FrameCallKind::FrameStart; }); + check(start != calls.end(), "the capture has no frame start"); + std::size_t submits = 0, reservations = 0; + FrameCallKind first = FrameCallKind::End; + for (auto call = start + 1; call != calls.end(); ++call) { + if (call->kind == FrameCallKind::Submit || call->kind == FrameCallKind::ReserveEopFlip) { + if (first == FrameCallKind::End) first = call->kind; + submits += call->kind == FrameCallKind::Submit; + reservations += call->kind == FrameCallKind::ReserveEopFlip; + } + } + check(first == FrameCallKind::ReserveEopFlip, "the capture started between a reservation and the submission that flips it"); + check(submits == CapturedFrames && reservations == CapturedFrames, + "the captured frames hold " + std::to_string(submits) + " submissions and " + std::to_string(reservations) + " reservations, expected " + std::to_string(CapturedFrames) + " of each"); +} + +void testFlipsCountPerThread() { + auto* const block = allocate(); + const auto& driver = AgcDriver::DriverDetail::Driver::Get(); + const auto before = driver.FlipSubmissions(); + std::uint64_t otherBefore = 0, otherAfter = 0; + std::exception_ptr failure; + std::thread submitter([&] { + try { + otherBefore = driver.FlipSubmissions(); + submitEopFrame(block, 0); + otherAfter = driver.FlipSubmissions(); + } catch (...) { + failure = std::current_exception(); + } + }); + submitter.join(); + if (failure) std::rethrow_exception(failure); + AgcDriverWaitIdle_nid_postfix(); + check(otherAfter == otherBefore + 1, "the flipping submission did not count on the thread that made it"); + check(driver.FlipSubmissions() == before, "a flip submitted on another thread changed this thread's flip count, so a submission of this thread that overlaps it would end a captured frame"); +} + +} + +int main() { + try { + testEopFramesCountOnce(); + testFlipsCountPerThread(); + } catch (const std::exception& error) { + std::fprintf(stderr, "FAILED: %s\n", error.what()); + return 1; + } + std::puts("frame capture EOP tests passed"); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameDumpIo.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameDumpIo.cpp index ec52e5f1d9..f6b12a62e6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameDumpIo.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameDumpIo.cpp @@ -120,13 +120,15 @@ void DisableAfterFailure() { AgcDriver::FrameDumpOutput output; const std::array pixels{}; Check(output.Enabled_nid_no_patch(), "BMP output was disabled before any failure"); - const auto blocked = directory.string(); + const auto previous = std::filesystem::current_path(); + std::filesystem::current_path(directory.parent_path()); + const auto blocked = directory.filename().string(); Check(!output.Write_nid_no_patch(blocked.c_str(), 2, 2, pixels), "BMP output accepted a directory as a file"); Check(!output.Enabled_nid_no_patch(), "failed BMP output remained enabled"); - const auto skipped = directory / "must-not-exist.bmp"; - const auto path = skipped.string(); + const auto path = (directory.filename() / "must-not-exist.bmp").string(); for (unsigned i = 0; i < 64; ++i) Check(!output.Write_nid_no_patch(path.c_str(), 2, 2, pixels), "disabled BMP output wrote another file"); - Check(!std::filesystem::exists(skipped), "disabled BMP output created a file"); + std::filesystem::current_path(previous); + Check(!std::filesystem::exists(directory / "must-not-exist.bmp"), "disabled BMP output created a file"); Check(std::filesystem::remove(directory), "cannot remove empty fixture directory"); } diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp new file mode 100644 index 0000000000..23e6c21513 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp @@ -0,0 +1,270 @@ +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" +#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +namespace { + +using AgcDriver::FrameCallKind; +using AgcDriver::FrameCaptureReader; +using AgcDriver::FrameMemoryKind; + +constexpr std::uint32_t Handle = 7; +constexpr std::uint8_t GraphicsMemory = 0x0a; +constexpr std::uint8_t DmaMemory = 0; +constexpr std::size_t BlockBytes = 1u << 20; +constexpr std::size_t CommandBytes = 64u * 1024u; +constexpr std::size_t DataBytes = 4096; +constexpr std::uint32_t Done = 1; + +void check(bool condition, const std::string& reason) { + if (!condition) throw std::runtime_error(reason); +} + +std::uint64_t at(const volatile void* pointer) { + return static_cast(reinterpret_cast(pointer)); +} + +class Request final : public AgcDriver::IFlipRequest { +public: + explicit Request(std::atomic& ready) : ready(ready) {} + void GpuReady(const std::shared_ptr&) override { ++ready; } + void Fail(std::exception_ptr error) noexcept override { + if (!error) std::terminate(); + } + +private: + std::atomic& ready; +}; + +class Output final : public AgcDriver::IVideoOutput { +public: + std::shared_ptr Reserve(const AgcDriver::FlipInfo&) override { return std::make_shared(ready); } + void Fail(std::exception_ptr error) noexcept override { + if (!error) std::terminate(); + } + std::atomic ready = 0; +}; + +struct Layout { + std::byte* base = nullptr; + std::uint32_t* firstCommands = nullptr; + std::uint32_t* secondCommands = nullptr; + std::byte* source = nullptr; + std::byte* firstTarget = nullptr; + std::byte* secondTarget = nullptr; + std::uint32_t* label = nullptr; +}; + +Layout allocate() { + auto* const block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(BlockBytes, 65536)); + check(block != nullptr, "cannot allocate guest memory"); + GuestArena::GuestArenaCommit_nid_postfix(block, BlockBytes, 0x04, BlockBytes); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, BlockBytes, true, true, true); + } + Layout layout; + layout.base = block; + layout.firstCommands = reinterpret_cast(block); + layout.secondCommands = reinterpret_cast(block + CommandBytes); + layout.source = block + 2 * CommandBytes; + layout.firstTarget = layout.source + DataBytes; + layout.secondTarget = layout.firstTarget + DataBytes; + layout.label = reinterpret_cast(layout.secondTarget + DataBytes); + return layout; +} + +std::vector pattern(std::uint8_t seed) { + std::vector bytes(DataBytes); + for (std::size_t index = 0; index < bytes.size(); ++index) bytes[index] = static_cast((index * 31 + seed) & 0xff); + return bytes; +} + +void copy(std::uint32_t* words, std::size_t commandBytes, const std::byte* source, std::byte* target, std::uint32_t* label) { + CommandBuffer buffer{words, words + commandBytes / sizeof(std::uint32_t), words, words + commandBytes / sizeof(std::uint32_t), nullptr, nullptr, 0}; + sceAgcDcbDmaData(&buffer, 0, DmaMemory, 0, at(target), DmaMemory, 0, at(source), DataBytes, 0, 1, 0); + sceAgcDcbWriteData(&buffer, GraphicsMemory, 0, at(label), &Done, 1, 0, 1); + Packet packet{words, static_cast(buffer.cursor_up - words), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "submission failed"); +} + +void copyFrame(std::uint32_t* words, const Layout& layout, std::byte* target) { + copy(words, CommandBytes, layout.source, target, layout.label); +} + +void flip() { + AgcDriverSubmitFlip_nid_postfix(AgcDriver::FlipInfo{Handle, -1, 1, 0}); +} + +std::vector readFile(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + check(file.is_open(), "cannot open " + path.string()); + std::vector bytes((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + std::vector result(bytes.size()); + std::memcpy(result.data(), bytes.data(), bytes.size()); + return result; +} + +bool equal(const std::byte* bytes, const std::vector& expected) { + return std::memcmp(bytes, expected.data(), expected.size()) == 0; +} + +void testCaptureAndReplay(const std::filesystem::path& tool) { + const char* directory = std::getenv("APS5_FRAME_CAPTURE"); + check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set"); + const std::filesystem::path capture(directory); + const auto layout = allocate(); + const auto first = pattern(3); + const auto second = pattern(101); + auto output = std::make_shared(); + AgcDriverRegisterVideoOutput_nid_postfix(Handle, output); + AgcDriverUnregisterVideoOutput_nid_postfix(Handle, output); + output = std::make_shared(); + AgcDriverRegisterVideoOutput_nid_postfix(Handle, output); + + flip(); + std::memcpy(layout.source, first.data(), first.size()); + copyFrame(layout.firstCommands, layout, layout.firstTarget); + flip(); + AgcDriverWaitIdle_nid_postfix(); + AgcDriverUnregisterVideoOutput_nid_postfix(Handle, output); + output = std::make_shared(); + AgcDriverRegisterVideoOutput_nid_postfix(Handle, output); + const auto late = allocate(); + std::memcpy(late.source, second.data(), second.size()); + copyFrame(late.firstCommands, late, late.firstTarget); + flip(); + AgcDriverWaitIdle_nid_postfix(); + check(equal(layout.firstTarget, first) && equal(late.firstTarget, second), "the driver did not run the copies while capturing"); + + const FrameCaptureReader reader(capture); + check(!reader.Truncated(), "the capture is truncated"); + check(reader.Header().find("\"frames\":2") != std::string::npos, "the capture header does not record two frames: " + reader.Header()); + std::size_t submits = 0, flips = 0, memory = 0, starts = 0, registrations = 0, unregistrations = 0; + for (const auto& call : reader.Calls()) { + submits += call.kind == FrameCallKind::Submit; + flips += call.kind == FrameCallKind::Flip; + memory += call.kind == FrameCallKind::Memory; + starts += call.kind == FrameCallKind::FrameStart; + registrations += call.kind == FrameCallKind::RegisterVideoOutput; + unregistrations += call.kind == FrameCallKind::UnregisterVideoOutput; + } + check(starts == 1 && submits == 2 && flips == 2, "the capture holds " + std::to_string(submits) + " submits and " + std::to_string(flips) + " flips after " + std::to_string(starts) + " frame starts"); + check(memory >= 2, "the CPU writes before each copy were not recorded as memory deltas"); + check(registrations == 3 && unregistrations == 2, "video output close and reopen was not recorded before and during capture"); + const auto recorded = [&](const std::byte* base) { + return std::any_of(reader.Chunks().begin(), reader.Chunks().end(), [&](const auto& chunk) { + return chunk.kind == FrameMemoryKind::Range && chunk.address <= at(base) && at(base) + BlockBytes <= chunk.address + chunk.bytes; + }); + }; + check(recorded(layout.base), "the guest block is not in the snapshot ranges"); + check(recorded(late.base), "the block committed during the capture is not in its ranges"); + check(reader.Header().find("\"late_ranges\":1") != std::string::npos, "the capture header does not count one range committed during the capture: " + reader.Header()); + + const auto firstDump = capture / "first.bin"; + const auto secondDump = capture / "second.bin"; + const auto command = "\"\"" + tool.string() + "\" \"" + capture.string() + "\" --strict --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + firstDump.string() + + "\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\"\""; + const auto code = std::system(command.c_str()); + check(code == 0, "agc_frame_replay exited with " + std::to_string(code)); + check(readFile(firstDump) == first, "the replay did not reproduce the first copy"); + check(readFile(secondDump) == second, "the replay did not reproduce the second copy"); +} + +void testConcurrentCopies(const std::filesystem::path& tool) { + constexpr std::size_t Copies = 64; + constexpr std::size_t Frames = 8; + constexpr std::size_t CopyCommandBytes = 1024; + const char* directory = std::getenv("APS5_FRAME_CAPTURE"); + check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set"); + const std::filesystem::path capture(directory); + const auto layout = allocate(); + const auto other = allocate(); + auto* const otherSource = other.base + 128 * 1024; + auto* const targets = otherSource + DataBytes; + auto* const otherLabel = reinterpret_cast(other.base + 512 * 1024); + const auto copied = pattern(57); + std::memcpy(otherSource, copied.data(), copied.size()); + auto output = std::make_shared(); + AgcDriverRegisterVideoOutput_nid_postfix(Handle, output); + + flip(); + std::thread copier([&] { + for (std::size_t index = 0; index < Copies; ++index) { + copy(reinterpret_cast(other.base + index * CopyCommandBytes), CopyCommandBytes, otherSource, targets + index * DataBytes, otherLabel); + } + }); + for (std::size_t frame = 0; frame + 1 < Frames; ++frame) { + const auto bytes = pattern(static_cast(frame)); + std::memcpy(layout.source, bytes.data(), bytes.size()); + copyFrame(layout.firstCommands, layout, layout.firstTarget); + flip(); + } + copier.join(); + flip(); + AgcDriverWaitIdle_nid_postfix(); + + const FrameCaptureReader reader(capture); + check(!reader.Truncated(), "the capture is truncated"); + std::uint64_t base = 0; + std::size_t submits = 0, memory = 0; + for (const auto& call : reader.Calls()) { + if (call.kind == FrameCallKind::FrameStart) base = call.completed; + if (call.kind == FrameCallKind::Memory) { + ++memory; + check(call.completed >= base + submits, "a memory delta was read with " + std::to_string(base + submits - call.completed) + " of the " + std::to_string(submits) + " submissions recorded before it unfinished"); + } + submits += call.kind == FrameCallKind::Submit; + } + check(submits == Copies + Frames - 1, "the capture holds " + std::to_string(submits) + " submits, not " + std::to_string(Copies + Frames - 1)); + check(memory != 0, "the capture holds no memory deltas"); + + const auto dump = capture / "targets.bin"; + const auto command = "\"\"" + tool.string() + "\" \"" + capture.string() + "\" --strict --dump " + std::to_string(at(targets)) + ":" + std::to_string(Copies * DataBytes) + ":\"" + dump.string() + "\"\""; + const auto code = std::system(command.c_str()); + check(code == 0, "agc_frame_replay exited with " + std::to_string(code)); + const auto replayed = readFile(dump); + check(replayed.size() == Copies * DataBytes, "the replay dumped " + std::to_string(replayed.size()) + " bytes of the copy targets"); + for (std::size_t index = 0; index < Copies; ++index) { + check(std::memcmp(replayed.data() + index * DataBytes, copied.data(), DataBytes) == 0, "the replay did not reproduce copy " + std::to_string(index) + " of the second thread"); + } +} + +} + +int main(int argc, char** argv) { + if (argc != 2 && !(argc == 3 && std::string(argv[2]) == "concurrent")) { + std::fprintf(stderr, "usage: agc_driver_frame_replay_tests [concurrent]\n"); + return 2; + } + try { + if (argc == 3) testConcurrentCopies(argv[1]); + else testCaptureAndReplay(argv[1]); + } catch (const std::exception& error) { + std::fprintf(stderr, "FAILED: %s\n", error.what()); + return 1; + } + std::puts("frame replay tests passed"); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp new file mode 100644 index 0000000000..c54b01466a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp @@ -0,0 +1,241 @@ +#define main frameReplayToolMain +#include "../tools/FrameReplay.cpp" +#undef main +#include "prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp" +#include + +namespace { + +using AgcDriver::FrameCaptureWriter; + +void check(bool condition, const char* reason) { + if (!condition) throw std::runtime_error(reason); +} + +std::uint64_t address(const void* pointer) { + return reinterpret_cast(pointer); +} + +std::byte* allocate(std::size_t size, bool gpu = true) { + auto* bytes = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(size, 65536)); + check(bytes != nullptr, "guest allocation failed"); + GuestArena::GuestArenaCommit_nid_postfix(bytes, size, 0x04, size); + GuestAllocations::Mutation mutation; + mutation.Add(bytes, size, true, true, gpu); + return bytes; +} + +void collectGroups(Replay& replay) { + for (const auto& chunk : replay.reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Delta) replay.groups[chunk.group].push_back(&chunk); + } +} + +FrameCall memoryCall(std::uint64_t group, std::uint64_t thread) { + FrameCall call; + call.kind = FrameCallKind::Memory; + call.values = {group}; + call.thread = thread; + return call; +} + +void testMemoryOrder(const std::filesystem::path& directory) { + constexpr std::size_t Size = 64u * 1024u * 1024u; + auto* bytes = allocate(Size); + const std::vector first(Size, std::byte{0x11}); + const std::vector second(4096, std::byte{0x22}); + FrameCaptureWriter writer(directory); + writer.WriteDelta(1, 0, address(bytes), first); + writer.WriteDelta(2, 0, address(bytes + Size - second.size()), second); + writer.Finish("{}"); + const FrameCaptureReader reader(directory); + Options options; + options.release = false; + Replay replay{options, reader}; + replay.recording = true; + collectGroups(replay); + const std::array calls{memoryCall(1, 1), memoryCall(2, 2)}; + replay.runWindow(calls); + check(std::all_of(bytes, bytes + Size - second.size(), [](std::byte value) { return value == std::byte{0x11}; }), "first delta was not restored"); + check(std::equal(second.begin(), second.end(), bytes + Size - second.size()), "an earlier delta overwrote the later thread's delta"); +} + +void testReusedMemory(const std::filesystem::path& directory) { +#ifdef _WIN32 + constexpr std::size_t Size = 2 * FrameCaptureWriter::BlockBytes; + auto* bytes = allocate(Size); + auto* cpu = allocate(FrameCaptureWriter::BlockBytes, false); + std::fill_n(bytes, Size, std::byte{0x7f}); + std::fill_n(cpu, FrameCaptureWriter::BlockBytes, std::byte{0x55}); + std::vector expected(Size); + std::fill(expected.begin() + FrameCaptureWriter::BlockBytes, expected.end(), std::byte{0x22}); + std::uintptr_t base = 0; + std::size_t arenaSize = 0; + GuestArena::GuestArenaRange_nid_postfix(&base, &arenaSize); + FrameCaptureWriter writer(directory); + writer.WriteRange(address(bytes), Size, FrameRangeKind::Watched, PAGE_READWRITE, FrameRangeAccess::Readable | FrameRangeAccess::Writable | FrameRangeAccess::Gpu); + writer.WriteRange(address(cpu), FrameCaptureWriter::BlockBytes, FrameRangeKind::Unwatched, PAGE_READWRITE, FrameRangeAccess::Readable | FrameRangeAccess::Writable); + check(writer.WriteSnapshot(address(bytes), expected) == FrameCaptureWriter::BlockBytes, "zero snapshot block was not elided"); + writer.Finish("{\"arena_base\":" + std::to_string(base) + "}"); + const FrameCaptureReader reader(directory); + const auto skipped = restoreMemory(reader); + check(std::equal(expected.begin(), expected.end(), bytes), "reused GPU memory did not restore zero and nonzero snapshot blocks"); + check(skipped.bytes == FrameCaptureWriter::BlockBytes, "unexpected skipped memory"); + check(std::all_of(cpu, cpu + FrameCaptureWriter::BlockBytes, [](std::byte value) { return value == std::byte{0x55}; }), "reused CPU memory was overwritten"); +#endif +} + +void testEopTokens() { + Frames frames; + auto output = std::make_shared(frames); + AgcDriverRegisterVideoOutput_nid_postfix(7, output); + std::barrier together(2); + std::atomic wrong{0}; + const auto worker = [&](int lane) { + for (int round = 0; round < 2000; ++round) { + const auto requested = AgcDriver::EopFlipTokenBase + round * 8 + lane * 2; + together.arrive_and_wait(); + try { + if (AgcDriverReserveEopFlipAt_nid_postfix({7, -1, 1, 0}, requested) != requested) ++wrong; + } catch (const std::exception&) { ++wrong; } + } + }; + std::thread first(worker, 0), second(worker, 1); + first.join(); + second.join(); + check(wrong.load() == 0, "concurrent explicit EOP reservations returned the wrong token"); + const auto high = AgcDriver::EopFlipTokenBase + 20000; + check(AgcDriverReserveEopFlipAt_nid_postfix({7, -1, 1, 0}, high) == high, "explicit high token was not reserved"); + check(AgcDriverReserveEopFlipAt_nid_postfix({7, -1, 1, 0}, high - 1) == high - 1, "explicit lower token was not reserved"); + check(AgcDriverReserveEopFlip_nid_postfix({7, -1, 1, 0}) == high + 1, "ordinary reservation reused an explicit token"); + bool rejected = false; + try { AgcDriverReserveEopFlipAt_nid_postfix({7, -1, 1, 0}, high); } + catch (const std::runtime_error&) { rejected = true; } + check(rejected, "duplicate explicit token was not rejected"); + AgcDriverUnregisterVideoOutput_nid_postfix(7, output); +} + +class WaitingOutput final : public AgcDriver::IVideoOutput { +public: + WaitingOutput(Frames& frames, const std::uint32_t* label) : frames(frames), label(label) {} + std::shared_ptr Reserve(const AgcDriver::FlipInfo&) override { + const auto deadline = Clock::now() + std::chrono::seconds(2); + while (*static_cast(label) == 0) { + check(Clock::now() < deadline, "a blocking driver call prevented the later memory update"); + std::this_thread::yield(); + } + return std::make_shared(frames); + } + void Fail(std::exception_ptr error) noexcept override { frames.Failed(error); } + +private: + Frames& frames; + const std::uint32_t* label; +}; + +void testBlockingSubmission(const std::filesystem::path& directory) { + auto* bytes = allocate(FrameCaptureWriter::BlockBytes); + auto* label = reinterpret_cast(bytes); + const std::uint32_t ready = 1; + FrameCaptureWriter writer(directory); + writer.WriteDelta(1, 0, address(label), std::as_bytes(std::span(&ready, 1))); + writer.Finish("{}"); + const FrameCaptureReader reader(directory); + Options options; + options.pace = false; + options.release = false; + Replay replay{options, reader}; + collectGroups(replay); + auto output = std::make_shared(replay.frames, label); + AgcDriverRegisterVideoOutput_nid_postfix(7, output); + std::array words{AgcDriver::FlipPacketHeader, 7, 0xffffffffu, 1, 0, 0}; + for (const auto kind : {FrameCallKind::Submit, FrameCallKind::Flip, FrameCallKind::ReserveEopFlip}) { + *label = 0; + FrameCall submit; + submit.kind = kind; + submit.thread = 1; + submit.address = address(words.data()); + submit.size = words.size(); + submit.values = {7, static_cast(-1), 1, 0, AgcDriver::EopFlipTokenBase}; + const std::array calls{submit, memoryCall(1, 2)}; + replay.runWindow(calls); + check(*label == ready && replay.unfinished == 0, "blocking submission did not complete"); + } + check(replay.frames.Times().size() == 2, "blocking submissions lost a flip"); + AgcDriverUnregisterVideoOutput_nid_postfix(7, output); +} + +void testShaderCode(const std::filesystem::path& directory, bool host) { + auto* bytes = allocate(1u << 20); + auto* shader = reinterpret_cast(bytes); + auto* code = bytes + (1u << 19) + 256; + constexpr std::size_t CodeBytes = 3 * FrameCaptureWriter::BlockBytes; +#ifdef _WIN32 + if (host) { + auto* memory = static_cast(VirtualAlloc(nullptr, CodeBytes + 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + check(memory != nullptr, "host shader allocation failed"); + GuestAllocations::Mutation mutation; + mutation.Add(memory, CodeBytes + 4096, true, true, true); + code = memory + 256; + } +#endif + *shader = {}; + shader->file_header = 0x34333231u; + shader->version = 0x18u; + shader->header_size = sizeof(Shader); + shader->shader_size = CodeBytes; + shader->code = code; + auto* recorder = AgcDriver::FrameRecorder::Active(); + check(recorder != nullptr, "frame recorder is disabled"); + recorder->FrameBoundary(); + std::vector expected(CodeBytes); + for (std::size_t i = 0; i < expected.size(); ++i) expected[i] = static_cast(i * 31 + 17); + std::memcpy(code, expected.data(), expected.size()); + recorder->RegisterShader(shader); + recorder->FrameBoundary(); + const FrameCaptureReader reader(directory); + std::vector captured(CodeBytes); + const auto apply = [&](const FrameMemoryChunk& chunk) { + const auto begin = std::max(address(code), chunk.address); + const auto end = std::min(address(code) + CodeBytes, chunk.address + chunk.bytes); + if (begin >= end) return; + const auto data = reader.Read(chunk); + std::memcpy(captured.data() + begin - address(code), data.data() + begin - chunk.address, end - begin); + }; + for (const auto& chunk : reader.Chunks()) if (chunk.kind == FrameMemoryKind::Snapshot) apply(chunk); + bool registered = false; + for (const auto& call : reader.Calls()) { + if (call.kind == FrameCallKind::RegisterShader) { + registered = true; + break; + } + if (call.kind != FrameCallKind::Memory) continue; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Delta && chunk.group == call.values.at(0)) apply(chunk); + } + } + check(registered && captured == expected, "the complete separate shader code was not captured before registration"); +} + +} + +int main(int argc, char** argv) { + try { + check(argc == 3, "expected a test name and capture directory"); + const std::string mode = argv[1]; + const std::filesystem::path directory = argv[2]; + if (mode == "order") testMemoryOrder(directory); + else if (mode == "zero") testReusedMemory(directory); + else if (mode == "tokens") testEopTokens(); + else if (mode == "blocking") testBlockingSubmission(directory); + else if (mode == "shader" || mode == "shader_host") testShaderCode(directory, mode == "shader_host"); + else throw std::runtime_error("unknown test"); + AgcDriverShutdown_nid_postfix(); + std::puts("frame replay state test passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { AgcDriverShutdown_nid_postfix(); } catch (...) {} + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/GcnDecode.cpp b/core/libs/prx/libSceAgcDriver/tests/GcnDecode.cpp new file mode 100644 index 0000000000..855c0aecdb --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/GcnDecode.cpp @@ -0,0 +1,45 @@ +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" +#include +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +int failures = 0; + +void Check(bool condition, const char* what) { + if (condition) return; + std::fprintf(stderr, "%s\n", what); + ++failures; +} + +RdnaInstruction Decode(std::span words) { + return DecodeGcnInstruction(0u, words, 0u); +} + +void SetregImmKeepsItsLiteral() { + const std::array setreg{0xba80f801u, 0x12345678u, 0xbf810000u}; + const auto instruction = Decode(setreg); + Check(instruction.op == RdnaOpcode::SSetregImm32B32, "s_setreg_imm32_b32 did not decode as its RDNA opcode"); + Check(instruction.wordCount == 2u, "a GCN s_setreg_imm32_b32 is not two words"); + Check(instruction.source0.value == 0x12345678u, "s_setreg_imm32_b32 lost its literal"); + const std::array movk{0xb0001234u, 0xbf810000u}; + Check(Decode(movk).wordCount == 1u, "s_movk_i32 took a literal word"); +} + +} + +int main() { + try { + SetregImmKeepsItsLiteral(); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } + if (failures == 0) std::puts("GCN decode tests passed"); + return failures == 0 ? 0 : 1; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 12b11af2df..335a88d0dd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "BdaTests.hpp" #include "GraphicsTests.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -10,7 +11,6 @@ #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" -#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "ShaderDiskCache.hpp" #include "ShaderCacheDirectory.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" @@ -19,6 +19,7 @@ #include "prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include "SceShaders.hpp" #include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" #include "ControlFlow/RequestSerializer.hpp" @@ -64,8 +65,6 @@ #include #include -extern "C" std::terminate_handler APS5_VABI _ZSt13set_terminatePFvvE_nid_postfix(std::terminate_handler handler); - namespace { using AgcDriver::Graphics::Require; @@ -162,9 +161,15 @@ void inactiveVertexPointSizeTests() { } throw std::runtime_error("potentially active point size passed the state decoder"); }; - for (const auto primitive : {1u, 9u}) { + { auto queue = pointSizeState(); - queue.userConfig[0x242] = primitive; + queue.userConfig[0x242] = 9; + rejected(queue); + } + { + auto queue = pointSizeState(); + queue.userConfig[0x242] = 1; + queue.context[0x2d5] = 0x2020; rejected(queue); } for (const auto stages : {0x2004u, 0x2020u}) { @@ -221,6 +226,73 @@ void PolygonPrimitiveTests() { Require(indexedState.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN && indexedState.primitiveRestart, "indexed polygon decoding did not keep primitive restart"); } +void vertexPointListTests() { + const auto pointState = [](std::uint32_t control, std::uint32_t size, std::uint32_t limits) { + auto queue = makeState(); + queue.userConfig[0x242] = 1; + queue.context[0x1b3] = queue.context[0x1b4] = 2; + queue.context[0x207] = control; + queue.context[0x280] = size; + queue.context[0x281] = limits; + return queue; + }; + { + const auto queue = pointState(0x01210000u, 0x00080008u, 0x00080008u); + const auto rejection = AgcDriver::Graphics::DrawRejection(queue, false); + Require(rejection.empty(), "a vertex-path point list with a vertex point size failed the draw precheck: " + rejection); + const auto state = AgcDriver::Graphics::DecodeState(queue); + Require(state.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST && state.paClVsOutCntl == 0x01210000u, "a vertex-path point list with a vertex point size did not decode as a point list"); + Require(AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Point, "a vertex-path point list is not rasterized as points"); + Require(state.vertexPoints && state.pointSize == 1.0f && state.pointSizeMin == 1.0f && state.pointSizeMax == 1.0f, "PA_SU_POINT_SIZE and PA_SU_POINT_MINMAX of 8 did not decode as one-pixel points"); + ShaderRecompiler::ShaderVertexStageInfo vertex{}; + vertex.paClVsOutCntl = 1u << 21u; + AgcDriver::Graphics::ApplyPointSize(state, vertex); + Require(vertex.pointList && vertex.paClVsOutCntl == ((1u << 16u) | (1u << 21u)) && vertex.pointSize == 1.0f && vertex.pointSizeMin == 1.0f && vertex.pointSizeMax == 1.0f, "the vertex stage did not receive the vertex point size"); + } + { + const auto state = AgcDriver::Graphics::DecodeState(pointState(0, 0x00200020u, 0x00100004u)); + Require(state.vertexPoints && state.pointSize == 4.0f && state.pointSizeMin == 0.5f && state.pointSizeMax == 2.0f, "a register point size did not decode as diameters"); + ShaderRecompiler::ShaderVertexStageInfo vertex{}; + vertex.paClVsOutCntl = 1u << 21u; + AgcDriver::Graphics::ApplyPointSize(state, vertex); + Require(vertex.pointList && vertex.paClVsOutCntl == (1u << 21u) && vertex.pointSize == 4.0f, "a register point size turned on the vertex point size"); + } + { + const auto state = AgcDriver::Graphics::DecodeState(pointState(0x01210000u, 0x00200010u, 0x00400000u)); + Require(state.pointSizeMin == 0.0f && state.pointSizeMax == 8.0f, "a vertex point size refused a register size whose width and height differ"); + } + { + auto queue = makeState(); + queue.context[0x1b3] = queue.context[0x1b4] = 2; + queue.context[0x207] = 0x01210000u; + const auto state = AgcDriver::Graphics::DecodeState(queue); + Require(!state.vertexPoints, "a triangle draw decoded as vertex points"); + ShaderRecompiler::ShaderVertexStageInfo vertex{}; + AgcDriver::Graphics::ApplyPointSize(state, vertex); + Require(!vertex.pointList && vertex.paClVsOutCntl == 0, "a triangle draw received a point size"); + } + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(0, 0x00200010u, 0x00080008u)); }, "points whose PA_SU_POINT_SIZE width and height differ are unsupported"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(0, 0x00080008u, 0x00080010u)); }, "PA_SU_POINT_MINMAX with a minimum above its maximum is unsupported"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(1u << 16u, 0x00080008u, 0x00080008u)); }, "USE_VTX_POINT_SIZE without VS_OUT_MISC_VEC_ENA is unsupported"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(0, 0, 0x00080008u)); }, "points of size 0 (PA_SU_POINT_SIZE 0) are unsupported"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(0x01210000u, 0x00080008u, 0)); }, "a vertex point size clamped to 0 (PA_SU_POINT_MINMAX maximum 0) is unsupported"); + { + const auto state = AgcDriver::Graphics::DecodeState(pointState(0x01210000u, 0, 0x00200000u)); + Require(state.pointSize == 0.0f && state.pointSizeMin == 0.0f && state.pointSizeMax == 4.0f, "a vertex point size refused a zero register size or a zero clamp minimum"); + static_cast(AgcDriver::Graphics::DecodeState(pointState(0, 0x00080008u, 0))); + } + for (const auto sprite : {1u << 17u, 1u << 23u}) { + auto queue = pointState(0x01210000u, 0x00080008u, 0x00080008u); + queue.context[0x1b5] = 0x486bu; + queue.context[0x1b6] = 2; + queue.context[0x191] = 0; + queue.context[0x192] = sprite; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "point sprite texture coordinates (SPI_PS_INPUT_CNTL PT_SPRITE_TEX) are not implemented"); + queue.context[0x1b5] = 0x4869u; + Require(AgcDriver::Graphics::DecodeState(queue).vertexPoints, "PT_SPRITE_TEX was refused with point sprites off"); + } +} + void retainedPixelStageTests() { using namespace AgcDriver::Graphics; auto queue = makeState(); @@ -750,7 +822,7 @@ void msaaEnableClearOneSampleTargetTests() { queue = drawC(); queue.context[0x1b3] |= ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary); requireOneSample(queue, "draw C with an enabled but unaddressed ANCILLARY input"); - constexpr std::string_view refused = "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with PS_ITER_SAMPLE, a sample index or coverage input, or a sample-mask export"; + constexpr std::string_view refused = "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with a sample index or coverage input, or a sample-mask export"; for (const auto input : {ShaderRecompiler::PixelInput::Ancillary, ShaderRecompiler::PixelInput::SampleCoverage}) { queue = drawC(); queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(input); @@ -758,6 +830,10 @@ void msaaEnableClearOneSampleTargetTests() { } queue = drawC(); queue.context[0x293] = 0x10000u; + queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveSample); + requireOneSample(queue, "draw C with PS_ITER_SAMPLE and a sample-interpolated input"); + Require(!DecodeState(queue).perSampleShading, "PS_ITER_SAMPLE into a one-sample color target without MSAA_ENABLE asked for sample shading"); + queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary); expectFailure([&] { DecodeState(queue); }, refused); queue = drawC(); queue.context[0x1c4] = 9; @@ -765,10 +841,12 @@ void msaaEnableClearOneSampleTargetTests() { expectFailure([&] { DecodeState(queue); }, refused); queue = drawC(); queue.context[0x2dc] = 0x0000aa01u; - expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points, lines"); - queue = drawC(); - queue.userConfig[0x242] = 2; - expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points, lines"); + expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points or alpha-to-coverage"); + for (const auto primitive : {2u, 3u}) { + queue = drawC(); + queue.userConfig[0x242] = primitive; + requireOneSample(queue, "a line list or strip into PPSA18216's draw C target"); + } const auto twoTargets = [&](std::uint32_t first, std::uint32_t second) { auto pair = drawC(); for (const auto offset : {0x318u, 0x31bu, 0x31cu}) pair.context[offset + 0xfu] = pair.context.at(offset); @@ -830,36 +908,24 @@ void msaaEnableClearOneSampleTargetTests() { queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage); Require(AgcDriver::Graphics::PixelProgramSkipped(queue), "the depth-only draw did not skip its pixel program"); requireOneSampleDepth(queue, 0, "a one-sample depth target whose skipped pixel program has PS_ITER_SAMPLE and sample index and coverage inputs"); - constexpr std::string_view depthRefused = "a one-sample depth target under a multisampled rasterizer without MSAA_ENABLE is unsupported for points or lines and with alpha-to-coverage, a sample index or coverage input, a sample-mask export, or DB_EQAA Z interpolation or overrasterization"; for (const bool color : {false, true}) { - for (const auto input : {ShaderRecompiler::PixelInput::Ancillary, ShaderRecompiler::PixelInput::SampleCoverage}) { - queue = depthTarget(color); - queue.context[0x203] |= 0x40u; - queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(input); - expectFailure([&] { DecodeState(queue); }, depthRefused); - } + queue = depthTarget(color); + queue.context[0x203] |= 0x40u; + queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary); + Require(!AgcDriver::Graphics::PixelProgramSkipped(queue), "the ANCILLARY draw skipped its pixel program"); + requireOneSampleDepth(queue, color ? 1u : 0u, "a one-sample depth target whose running pixel program reads ANCILLARY without PS_ITER_SAMPLE"); + queue.context[0x1b3] = queue.context[0x1b4] = 0x2b01u; + requireOneSampleDepth(queue, color ? 1u : 0u, "a one-sample depth target whose running pixel program reads PERSP_SAMPLE, the position and ANCILLARY without PS_ITER_SAMPLE"); queue = depthTarget(color); queue.context[0x203] |= 0x40u; queue.context[0x293] = 0x10000u; Require(!AgcDriver::Graphics::PixelProgramSkipped(queue), "the PS_ITER_SAMPLE draw skipped its pixel program"); requireOneSampleDepth(queue, color ? 1u : 0u, "a one-sample depth target whose running pixel program has PS_ITER_SAMPLE and no sample index or coverage input"); Require(!DecodeState(queue).perSampleShading, "a PS_ITER_SAMPLE program drawn as one sample still shades per sample"); - queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary); - expectFailure([&] { DecodeState(queue); }, depthRefused); - queue = depthTarget(color); - queue.context[0x1c4] = 9; - queue.context[0x203] |= 0x100u; - expectFailure([&] { DecodeState(queue); }, depthRefused); - queue = depthTarget(color); - queue.context[0x2dc] = 0x0000aa01u; - expectFailure([&] { DecodeState(queue); }, depthRefused); - queue = depthTarget(color); - queue.userConfig[0x242] = 2; - expectFailure([&] { DecodeState(queue); }, depthRefused); - for (const auto eqaa : {0x00050002u, 0x00090002u, 0x01010002u, 0x08010002u}) { + for (const auto eqaa : {0x00040000u, 0x00050000u}) { queue = depthTarget(color); queue.context[0x201] = eqaa; - expectFailure([&] { DecodeState(queue); }, depthRefused); + requireOneSampleDepth(queue, color ? 1u : 0u, "a one-sample depth target under a 4x PA_SC_AA_CONFIG without MSAA_ENABLE with DB_EQAA INTERPOLATE_COMP_Z"); } } queue = depthTarget(true); @@ -867,6 +933,57 @@ void msaaEnableClearOneSampleTargetTests() { expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's"); } +void UnwrittenDepthPlaneTests() { + using AgcDriver::Graphics::DecodeState; + using AgcDriver::Graphics::DrawRejection; + const auto fresh = [](std::uint32_t depthControl) { + auto written = makeState(); + written.context[0x200] = depthControl; + written.context[0x1b3] = 2; + written.context[0x1b4] = 2; + AgcDriver::QueueState queue; + queue.shader = written.shader; + queue.userConfig = written.userConfig; + written.context.ForEach([&](std::uint32_t offset, std::uint32_t value) { queue.context[offset] = value; }); + return queue; + }; + auto queue = fresh(0x777); + auto state = DecodeState(queue); + Require(!state.depth && !state.depthTest && !state.depthWrite && !state.stencilTest, "depth and stencil tests on unwritten depth and stencil planes were kept (PPSA24040's DB_DEPTH_CONTROL)"); + auto rejection = DrawRejection(queue, false); + Require(rejection.empty(), "the precheck refused depth and stencil tests on unwritten depth and stencil planes: " + rejection); + queue = fresh(0x77f); + state = DecodeState(queue); + Require(!state.depth && !state.depthBoundsTest, "a depth bounds test on unwritten depth and stencil planes was kept"); + rejection = DrawRejection(queue, false); + Require(rejection.empty(), "the precheck refused a depth bounds test on unwritten depth and stencil planes: " + rejection); + queue = fresh(0x007007b3); + queue.context[0x010] = 0x80000183; + queue.context[0x012] = 0x00001000; + queue.context[0x014] = 0x00001000; + queue.context[0x007] = 0x0001001f; + queue.context[0x00b] = 0x3f800000; + state = DecodeState(queue); + Require(state.depth && state.depthTest && !state.stencilTest && state.renderExtent.width == 32 && state.renderExtent.height == 2, "a depth plane beside an unwritten DB_STENCIL_INFO did not decode as depth only"); + rejection = DrawRejection(queue, false); + Require(rejection.empty(), "the precheck refused a depth plane beside an unwritten DB_STENCIL_INFO: " + rejection); + queue = fresh(0x007007b3); + queue.context[0x011] = 0x20000181; + queue.context[0x013] = 0x00002000; + queue.context[0x015] = 0x00002000; + queue.context[0x007] = 0x003f003f; + queue.context[0x00b] = 0x3f800000; + queue.context[0x10c] = 0x01ffff00; + queue.context[0x10d] = 0x01ffff00; + state = DecodeState(queue); + Require(!state.depthTest && state.stencilTest, "a stencil plane beside an unwritten DB_Z_INFO kept the depth test or lost the stencil test"); + rejection = DrawRejection(queue, false); + Require(rejection.empty(), "the precheck refused a stencil plane beside an unwritten DB_Z_INFO: " + rejection); + queue.context[0x010] = 0x80000183; + queue.ClearContext(); + for (const auto offset : {0x010u, 0x011u}) Require(queue.context.contains(offset) && queue.context.at(offset) == 0, "CLEAR_STATE did not reset DB_Z_INFO and DB_STENCIL_INFO to their clear-state value 0"); +} + void MsaaEnableSingleSampleTests() { using AgcDriver::Graphics::DecodeState; using AgcDriver::Graphics::DrawRejection; @@ -1075,11 +1192,30 @@ void ShaderStageTests() { lines.context[0x29b] = 1; Require(AgcDriver::Graphics::DecodeState(lines).stages.mesh->provokingVertex == 1, "a last-vertex geometry draw does not take its flat inputs and layer from the second vertex of each output line"); lines.context[0x29b] = 0; + expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "missing register in context bank at DWORD 0x280"); + lines.context[0x280] = 0x00080008u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "missing register in context bank at DWORD 0x281"); + lines.context[0x281] = 0xffff0000u; state = AgcDriver::Graphics::DecodeState(lines); mesh = *state.stages.mesh; Require(mesh.outputPrimitiveSize == 1 && mesh.maxPrimitives == 255 && AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Point, "a point-list subgroup did not decode as points"); Require(mesh.provokingVertex == 0, "a point draw has provoking vertex 0"); + Require(state.pointSize == 1.0f && state.pointSizeMin == 0.0f && state.pointSizeMax == 8191.875f, "geometry points did not decode the queue's default point size and limits"); lines.context[0x205] = raster; + auto points = lines; + points.userConfig[0x242] = 6; + points.userConfig[0x25b] = 0x5c2e; + points.context[0x1ff] = 0x8a; + points.context[0x2ce] = 3; + points.context[0x29b] = 0; + points.context[0x280] = 0x00180018u; + points.context[0x281] = 0x00400008u; + const auto pointState = AgcDriver::Graphics::DecodeState(points); + const auto& pointMesh = *pointState.stages.mesh; + ShaderRecompiler::ShaderVertexStageInfo pointInfo{}; + AgcDriver::Graphics::ApplyPointSize(pointState, pointInfo); + Require(pointInfo.pointList && pointInfo.pointSize == 3.0f && pointInfo.pointSizeMin == 1.0f && pointInfo.pointSizeMax == 8.0f, "geometry points lost their register sizes"); + Require(pointMesh.outputPrimitiveSize == 1u && pointMesh.primitivesPerGroup == 44u && pointMesh.verticesPerGroup == 46u && pointMesh.maxVertices == 138u && pointMesh.maxPrimitives == 138u && pointMesh.threadsPerGroup == 192u && AgcDriver::Graphics::RasterPrimitiveOf(pointState) == ShaderRecompiler::RasterPrimitive::Point, "triangle-strip input with point output did not decode the reported subgroup"); lines.context[0x29b] = 1; lines.context[0x2ce] = 1; expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "unsupported geometry input or output assembly"); @@ -1729,7 +1865,39 @@ void DisabledColorTests() { queue.context[0x002] = 3; 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"); queue.context[0x002] = 0; + { + using AgcDriver::Graphics::ComputeElementMipLayout; + using AgcDriver::Graphics::TextureTileMode; + auto mipped = queue; + mipped.context[0x007] = 0x03ff03ff; + mipped.context[0x010] = 0x80000181u | (2u << 16u); + const auto mips = ComputeElementMipLayout(TextureTileMode::kZ64KBX, 2u, 1024u, 1024u, 3u); + Require(!mips.at(1).tail && mips.at(1).width == 512u && mips.at(1).height == 512u, "the 1024x1024 Z_X layout does not give a 512x512 second mip outside the tail"); + const auto first = AgcDriver::Graphics::DecodeState(mipped); + Require(first.depth && first.depth->address == depthOnly.depth->address + mips.at(0).tiledOffset && first.depth->extent.width == 1024u && first.depth->extent.height == 1024u, "view mip 0 of a mip chain is not its first level"); + mipped.context[0x002] = 1u << 26u; + const auto second = AgcDriver::Graphics::DecodeState(mipped); + Require(second.depth && second.depth->address == depthOnly.depth->address + mips.at(1).tiledOffset, "view mip 1 is not at the second level's offset in the Z_X mip chain"); + Require(second.depth->extent.width == 512u && second.depth->extent.height == 512u, "view mip 1 does not render at the second level's extent"); + mipped.context[0x002] = (1u << 26u) | 1u | (1u << 13u); + const auto sliced = AgcDriver::Graphics::DecodeState(mipped); + Require(sliced.depth && sliced.depth->address == second.depth->address + AgcDriver::Graphics::ComputeSurfaceSize(mips, 1u), "slice 1 of a mip chain does not step by the whole chain"); + mipped.context[0x002] = 3u << 26u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(mipped); }, "is beyond the surface's mips"); + mipped.context[0x002] = 2u << 26u; + mipped.context[0x007] = 0x003f003f; + expectFailure([&] { AgcDriver::Graphics::DecodeState(mipped); }, "into the packed mip tail is unsupported"); + mipped.context[0x007] = 0x03ff03ff; + mipped.context[0x002] = 1u << 26u; + mipped.context[0x010] |= 1u << 29u; + mipped.context[0x005] = 0x300; + expectFailure([&] { AgcDriver::Graphics::DecodeState(mipped); }, "HTILE for the depth view mip"); + mipped.context[0x010] = (0x80000181u | (2u << 16u)) & ~(31u << 4u); + expectFailure([&] { AgcDriver::Graphics::DecodeState(mipped); }, "uses a swizzle other than 64 KiB Z_X"); + } for (const auto colorControl : {0x0u, 0xcc0000u}) { queue = makeState(); queue.context[0x202] = colorControl; @@ -1756,8 +1924,35 @@ void TuningFieldTests() { Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "DISABLE_DUAL_QUAD was rejected"); Require(state.hasColorTarget && state.blend.colorWriteMask == baseline.blend.colorWriteMask, "DISABLE_DUAL_QUAD changed color output"); queue.context[0x202] = 0xcc0013; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "copy ROP"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "source or brush form"); + queue.context[0x202] = 0x5b0010; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "source or brush form"); + queue.context[0x202] = 0x5a0010; + state = AgcDriver::Graphics::DecodeState(queue); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "an XOR ROP was rejected"); + Require(state.hasColorTarget && state.colorRop == 0x5a && AgcDriver::Graphics::LogicOpFromRop(state.colorRop) == VK_LOGIC_OP_XOR, "an XOR ROP did not decode"); queue.context[0x202] = 0xcc0010; + Require(AgcDriver::Graphics::DecodeState(queue).colorRop == AgcDriver::Graphics::CopyRop, "the copy ROP did not decode as the copy operation"); + const std::array, 14> ropOperations{{ + {0x0f, VK_LOGIC_OP_COPY_INVERTED}, {0xa0, VK_LOGIC_OP_AND}, {0xf0, VK_LOGIC_OP_COPY}, {0xfa, VK_LOGIC_OP_OR}, + {0x00, VK_LOGIC_OP_CLEAR}, {0x11, VK_LOGIC_OP_NOR}, {0x33, VK_LOGIC_OP_COPY_INVERTED}, {0x55, VK_LOGIC_OP_INVERT}, {0x66, VK_LOGIC_OP_XOR}, + {0x88, VK_LOGIC_OP_AND}, {0xaa, VK_LOGIC_OP_NO_OP}, {0xcc, VK_LOGIC_OP_COPY}, {0xee, VK_LOGIC_OP_OR}, {0xff, VK_LOGIC_OP_SET}}}; + for (const auto& [rop, operation] : ropOperations) Require(AgcDriver::Graphics::LogicOpFromRop(rop) == operation, "a ROP3 code mapped to the wrong logic operation"); + std::uint32_t documentedRops = 0; + for (std::uint32_t rop = 0; rop < 256u; ++rop) { + try { + static_cast(AgcDriver::Graphics::LogicOpFromRop(static_cast(rop))); + ++documentedRops; + } catch (const std::runtime_error&) { + } + } + Require(documentedRops == 28u, "a ROP3 code outside the source and brush forms was accepted, or one inside them was refused"); + auto blendedLogic = queue; + blendedLogic.context[0x202] = 0x5a0010; + blendedLogic.context[0x1e0] = 0x40000101u; + for (std::uint32_t index = 0; index < 4u; ++index) blendedLogic.context[0x105 + index] = 0u; + blendedLogic.context[0x31c] |= 0x8000u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(blendedLogic); }, "logic operation together with blending"); for (const auto groups : {1u, 2u, 15u}) { queue.context[0x2d5] = 0x2000u | (groups << 15u); state = AgcDriver::Graphics::DecodeState(queue); @@ -2262,7 +2457,41 @@ void DepthStencilTests() { queue.context[0x10b] = 0x40; queue.context[0x10c] = 0x05ffff02; queue.context[0x200] = 0x00200211; + state = AgcDriver::Graphics::DecodeState(queue); + Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.failOp == VK_STENCIL_OP_KEEP && state.stencilFront.depthFailOp == VK_STENCIL_OP_KEEP && state.stencilFront.compareOp == VK_COMPARE_OP_EQUAL, "an equal test replaced by another value is not drawn as an inversion"); + Require(state.stencilFront.reference == 2 && state.stencilFront.compareMask == 0xff && state.stencilFront.writeMask == 7, "an inverted replacement does not keep the test value and write only the bits that differ from it"); + queue.context[0x10b] = 0x440; + state = AgcDriver::Graphics::DecodeState(queue); + Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.depthFailOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 7, "a replacement on pass and depth fail is not drawn as one inversion"); + queue.context[0x10b] = 0x4; + queue.context[0x200] = 0x00200511; + state = AgcDriver::Graphics::DecodeState(queue); + Require(state.stencilFront.failOp == VK_STENCIL_OP_INVERT && state.stencilFront.passOp == VK_STENCIL_OP_KEEP && state.stencilFront.writeMask == 7, "a not-equal test that fails is not replaced as an inversion"); + queue.context[0x10c] = 0x40ffffff; + queue.context[0x10b] = 0x40; + queue.context[0x200] = 0x00200311; + state = AgcDriver::Graphics::DecodeState(queue); + Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 0xbf && state.stencilFront.reference == 0xff, "a less-or-equal test against 0xff, which passes only on 0xff, is not replaced as an inversion"); + queue.context[0x10b] = 0x4; + queue.context[0x200] = 0x00200411; + state = AgcDriver::Graphics::DecodeState(queue); + Require(state.stencilFront.failOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 0xbf, "a greater test against 0xff, which fails only on 0xff, is not replaced as an inversion"); + queue.context[0x10c] = 0x40ffff7f; + queue.context[0x10b] = 0x40; + queue.context[0x200] = 0x00200311; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil replacement"); + queue.context[0x10c] = 0x05ffff02; + queue.context[0x200] = 0x00200511; + queue.context[0x10b] = 0x40; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil replacement"); + queue.context[0x200] = 0x00200111; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil replacement"); + queue.context[0x200] = 0x00200211; + queue.context[0x10c] = 0x05ff0f02; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil replacement"); + queue.context[0x10c] = 0x05ffff02; + queue.context[0x10b] = 0x41; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "beside another stencil operation"); queue.context[0x10b] = 0x50; queue.context[0x200] = 0x00700711; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil add/subtract"); @@ -2329,10 +2558,12 @@ void DepthStencilTests() { queue.context[0x000] = 0x22u | control; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "DB_RENDER_CONTROL"); } - queue.context[0x000] = 0x22; + queue.context[0x000] = 0x23; queue.context[0x011] = 0; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "writable stencil plane"); - Require(AgcDriver::Graphics::DrawRejection(queue, false).find("writable stencil plane") != std::string::npos, "precheck accepted clear without a stencil plane"); + state = AgcDriver::Graphics::DecodeState(queue); + Require(state.depthWrite && state.depthCompare == VK_COMPARE_OP_ALWAYS && !state.stencilTest && state.colors.size() == 2, "a depth and stencil clear without a stencil plane does not clear depth alone"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "precheck rejected a stencil clear without a stencil plane"); + queue.context[0x000] = 0x22; queue.context[0x010] = 0; state = AgcDriver::Graphics::DecodeState(queue); Require(!state.depth && !state.depthTest && !state.depthWrite && !state.stencilTest && state.colors.size() == 2, "clear flags without depth/stencil planes changed color-only rendering"); @@ -2761,7 +2992,12 @@ void depthCopyPassTests() { queue.context[0x31c] = 0x40710; queue.context[0x8e] = 1; auto pass = DecodeDepthCopyPass(queue); - Require(pass.has_value() && pass->depthAddress == 0x100000 && pass->samples == 2 && pass->sample == 0 && pass->extent.width == 64 && pass->extent.height == 4, "a centroid DB depth copy of a two-sample depth plane decoded differently"); + Require(pass.has_value() && pass->source.address == 0x100000 && pass->source.samples == 2 && pass->sample == 0 && pass->source.extent.width == 64 && pass->source.extent.height == 4, "a centroid DB depth copy of a two-sample depth plane decoded differently"); + { + const auto words = AgcDriver::Graphics::DepthCopySourceWords(pass->source); + const auto resource = AgcDriver::Graphics::DecodeTextureResource(words); + Require(resource.baseAddress == pass->source.address && resource.dimension == AgcDriver::Graphics::TextureDimension::k2DMsaa && resource.samples == 2 && resource.width == 64 && resource.height == 4 && resource.tileMode == AgcDriver::Graphics::TextureTileMode::kZ64KBX && resource.pipeBankXor == pass->source.pipeBankXor && resource.dccAddress == pass->source.htileAddress, "the DB depth copy source descriptor of a two-sample plane decoded differently"); + } Require(pass->color.format == VK_FORMAT_R32_SFLOAT && pass->color.samples == 1 && pass->color.address == reinterpret_cast(colorMemory.data()), "the DB depth copy target decoded differently"); queue.context[0x000] = 0x104; Require(DecodeDepthCopyPass(queue)->sample == 1, "COPY_SAMPLE without COPY_CENTROID did not select the sample"); @@ -2786,23 +3022,31 @@ void depthCopyPassTests() { expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "single-sample R32 float"); queue.context[0x31c] = 0x40710; queue.context[0x10f] = std::bit_cast(16.0f); - expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "over part of the depth surface"); + Require(DecodeDepthCopyPass(queue).has_value(), "a DB depth copy whose viewport covers part of the depth surface was refused"); queue.context[0x10f] = std::bit_cast(32.0f); + queue.context[0x1e0] = 1u << 30u; + expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "blending"); + queue.context[0x1e0] = 0; queue.context[0x010] = 0x80001187; expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "mipmapped or partially resident"); queue.context[0x010] = 0x80000187; queue.context[0x012] = 0x00001003; pass = DecodeDepthCopyPass(queue); - Require(pass.has_value() && pass->depthAddress == 0x100000, "a DB depth copy kept the pipe-bank XOR of a two-sample depth plane in its address"); + Require(pass.has_value() && pass->source.address == 0x100000 && pass->source.pipeBankXor == 0x300, "a DB depth copy kept the pipe-bank XOR of a two-sample depth plane in its address"); queue.context[0x012] = 0x00001000; queue.context[0x002] = 1u | (1u << 13u); pass = DecodeDepthCopyPass(queue); - Require(pass.has_value() && pass->depthAddress == 0x100000 + AgcDriver::Graphics::DepthSliceBytes({64, 4}, 4u), "a DB depth copy of slice 1 did not address the slice the depth surface registers"); + Require(pass.has_value() && pass->source.address == 0x100000 + AgcDriver::Graphics::DepthSliceBytes({64, 4}, 4u), "a DB depth copy of slice 1 did not address the slice the depth surface registers"); queue.context[0x002] = 0; Require(DecodeDepthCopyPass(queue).has_value(), "the restored DB depth copy was refused"); queue.context[0x010] = 0x80000183; pass = DecodeDepthCopyPass(queue); - Require(pass.has_value() && pass->samples == 1 && pass->sample == 0, "a centroid DB depth copy of a one-sample depth plane decoded differently"); + Require(pass.has_value() && pass->source.samples == 1 && pass->sample == 0, "a centroid DB depth copy of a one-sample depth plane decoded differently"); + { + const auto words = AgcDriver::Graphics::DepthCopySourceWords(pass->source); + const auto resource = AgcDriver::Graphics::DecodeTextureResource(words); + Require(resource.baseAddress == pass->source.address && resource.dimension == AgcDriver::Graphics::TextureDimension::k2D && resource.samples == 1 && resource.width == 64 && resource.height == 4 && resource.dccAddress == 0, "the DB depth copy source descriptor of a one-sample plane decoded differently"); + } queue.context[0x000] = 0x104; expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "a sample the depth plane does not have"); } @@ -2890,6 +3134,13 @@ void metadataPassTests() { auto mipmapped = queue; mipmapped.context[0x3b0] |= 1u << 28u; expectFailure([&] { DecodeColorMetadataPass(mipmapped); }, "mipmapped DCC"); + const auto chain = AgcDriver::Graphics::DecodeColorBuffer(mipmapped.context, 0); + const auto chainSurface = AgcDriver::Graphics::SurfaceForTarget(chain); + Require(chain.dccAddress == 0 && chainSurface.mipCount == 2 && chainSurface.dccAddress == keysAddress, "the surface of a mipmapped DCC color target lost its keys"); + auto mipmappedArray = mipmapped; + mipmappedArray.context[0x3b8] |= 1u; + mipmappedArray.context[0x31b] = 1u << 13u; + Require(AgcDriver::Graphics::SurfaceForTarget(AgcDriver::Graphics::DecodeColorBuffer(mipmappedArray.context, 0)).dccAddress == 0, "a mipmapped DCC array color target took keys whose layers are not modeled"); const AgcDriver::Graphics::Context context{}; const auto texels = [&](std::uint32_t value) { for (std::size_t offset = 0; offset < colorMemory.size(); offset += 4) { @@ -3282,6 +3533,43 @@ void msaaEnableClearDepthTests() { state = DecodeState(queue); Require(state.colors.empty(), "a depth-only 4x draw without MSAA_ENABLE decoded a color target"); requireCentered(state, "a 4x depth target rasterized without MSAA_ENABLE"); + for (const auto primitive : {2u, 3u}) { + queue = drawB(); + queue.userConfig[0x242] = primitive; + state = DecodeState(queue); + Require(state.topology == (primitive == 2u ? VK_PRIMITIVE_TOPOLOGY_LINE_LIST : VK_PRIMITIVE_TOPOLOGY_LINE_STRIP) && state.colors.size() == 1 && state.colors[0].samples == 4, "PPSA18216's line list or strip into its 4x depth and color pair lost its topology or color target"); + requireCentered(state, "a line list or strip into PPSA18216's 4x depth and color pair without MSAA_ENABLE"); + Require(!state.sampleLocationGrid && !state.perSampleShading, "a line list or strip without MSAA_ENABLE kept a sample location grid or sample shading"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck refused a line list or strip into PPSA18216's 4x depth and color pair"); + } + queue = drawB(); + queue.userConfig[0x242] = 1; + queue.context[0x2d5] = 0x2030; + queue.userConfig[0x25b] = 0x6633; + queue.context[0x1ff] = 255; + queue.context[0x2ce] = 5; + queue.context[0x29b] = 2; + queue.context[0x2ab] = 4; + queue.shader[0x8a] = 3u << 29u; + queue.shader[0x8b] = 3u << 16u; + state = DecodeState(queue); + Require(state.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST && state.stages.mesh && state.stages.mesh->outputPrimitiveSize == 3u && AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Triangle && state.colors.size() == 1 && state.colors[0].samples == 4, "a point list into a geometry program that outputs triangle strips into PPSA18216's 4x depth and color pair did not decode as triangles"); + requireCentered(state, "triangles a geometry program makes from a point list into PPSA18216's 4x depth and color pair without MSAA_ENABLE"); + for (const auto primitive : {4u, 2u, 1u}) { + queue = drawB(); + queue.userConfig[0x242] = primitive; + queue.context[0x2d5] = 0x2030; + queue.userConfig[0x25b] = 0x6633; + queue.context[0x1ff] = 255; + queue.context[0x2ce] = 5; + queue.context[0x29b] = 0; + queue.context[0x2ab] = 4; + queue.context[0x280] = 0x00080008u; // point-output geometry programs decode PA_SU_POINT_SIZE / PA_SU_POINT_MINMAX before this rejection + queue.context[0x281] = 0x00080008u; + queue.shader[0x8a] = 3u << 29u; + queue.shader[0x8b] = 3u << 16u; + expectFailure([&] { DecodeState(queue); }, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points or alpha-to-coverage"); + } for (const auto eqaa : {0x00050002u, 0x00090002u, 0x01010002u, 0x08010002u}) { queue = drawB(); queue.context[0x201] = eqaa; @@ -3293,7 +3581,7 @@ void msaaEnableClearDepthTests() { expectFailure([&] { DecodeState(queue); }, "a multisampled depth target rasterized without MSAA_ENABLE with a sample-mask export"); queue = drawB(); queue.context[0x2dc] = 0x0000aa01u; - expectFailure([&] { DecodeState(queue); }, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points, lines or alpha-to-coverage"); + expectFailure([&] { DecodeState(queue); }, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points or alpha-to-coverage"); queue = drawB(); queue.context[0x010] = 0xa2900987; expectFailure([&] { DecodeState(queue); }, "the depth target's sample count differs from the rasterizer's"); @@ -3589,7 +3877,7 @@ void multisampleTests() { } queue = multisampled(); queue.context[0x31d] = 0x1000; - expectFailure([&] { DecodeState(queue); }, "fewer fragments than samples"); + expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's"); queue.context[0x31d] = 0; queue.context[0x293] = 0x10000u; Require(DecodeState(queue).samples == 1 && !DecodeState(queue).perSampleShading, "PS_ITER_SAMPLE into a one-sample target did not rasterize sample 0 alone"); @@ -3600,7 +3888,7 @@ void multisampleTests() { const auto aliased = AgcDriver::Graphics::SampleLocations(state); for (std::uint32_t sample = 0; sample < state.samples; ++sample) Require(aliased[sample].x == 0.5f && aliased[sample].y == 0.5f, "a 2x target rasterized without MSAA_ENABLE did not place every sample at the pixel center"); queue.context[0x2dc] = 0x0000aa01u; - expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points, lines or alpha-to-coverage"); + expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points or alpha-to-coverage"); queue = multisampled(); queue.context[0x292] = 2; queue.context[0x302] = 0x44cc; @@ -3978,6 +4266,24 @@ void multisampleDccStateTests() { changed.context[0x3b0] |= 1u << 28u; expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "multisampled color mips"); } + for (const auto samples : {2u, 4u, 8u}) { + AgcDriver::Graphics::ColorTarget target{}; + target.extent = {64, 64}; + target.format = VK_FORMAT_R8G8B8A8_UNORM; + target.tileMode = AgcDriver::Graphics::ColorTileMode::RenderTarget; + target.samples = samples; + target.dccAddress = 0x200000u; + target.dccPipeAligned = true; + target.cmaskAddress = 0x300000u; + target.cmaskBytes = AgcDriver::Graphics::CmaskBytes(64, 64); + const std::vector dcc(AgcDriver::Graphics::MultisampledDccBytes(target), std::byte{0x00}); + std::vector cmask(target.cmaskBytes); + for (std::size_t index = 0; index < cmask.size(); ++index) cmask[index] = std::byte{static_cast(index * 37u)}; + const auto clear = AgcDriver::Graphics::MultisampledDccClear(target, dcc, cmask); + Require(clear.float32[0] == 0.0f && clear.float32[1] == 0.0f && clear.float32[2] == 0.0f && clear.float32[3] == 0.0f, "a uniform 0000 DCC clear of a multisampled target without CMASK fast clears did not import as 0000"); + target.cmaskFastClear = true; + expectFailure([&] { static_cast(AgcDriver::Graphics::MultisampledDccClear(target, dcc, cmask)); }, "multisampled DCC CMASK compression or register clear"); + } } void multisampledDepthTests() { @@ -4043,6 +4349,76 @@ void multisampledDepthTests() { Require(!DecodeState(queue).perSampleShading, "PS_ITER_SAMPLE requested sample shading for a single-sample scan"); queue = singleSampleScan(); queue.context[0x31d] = 0; + state = DecodeState(queue); + Require(state.truncatedCoverage && !state.mergedCoverage && state.colors.size() == 1 && state.colors[0].samples == 1, "a single-sample scan into a 4x depth target and a one-sample color target did not take the color from sample 0"); + requireCentered(state, "a single-sample scan into a 4x depth target and a one-sample color target"); + const auto truncatedTwoSample = [&] { + auto queue = singleSampleScan(); + queue.context[0x2f8] = 0x00100001; + queue.context[0x292] = 3; + for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0xcc44; + queue.context[0x010] = 0x22900987; + queue.context[0x31d] = 0; + return queue; + }; + queue = truncatedTwoSample(); + state = DecodeState(queue); + Require(state.samples == 2 && state.depth && state.depth->samples == 2 && state.colors.size() == 1 && state.colors[0].samples == 1 && state.truncatedCoverage && !state.mergedCoverage && !state.perSampleShading, "a one-sample color target beside a 2x depth target under a 2x rasterizer with MSAA_ENABLE (PPSA02225's registers) did not take the color from sample 0"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck refused a one-sample color target beside a 2x depth target"); + queue = truncatedTwoSample(); + queue.context[0x293] = 0x10000u; + queue.context[0x1b3] = queue.context[0x1b4] = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveSample) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::FrontFace); + state = DecodeState(queue); + Require(state.truncatedCoverage && state.perSampleShading && state.samples == 2 && state.colors[0].samples == 1, "PS_ITER_SAMPLE into a one-sample color target beside a 2x depth target (PPSA02225's registers) did not run per sample with sample 0's color"); + queue = truncatedTwoSample(); + queue.context[0x31d] = 0x12000; + expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's"); + const auto oneFragment = [&] { + auto queue = singleSampleScan(); + queue.context[0x2f8] = 0x00210002; + queue.context[0x292] = 3; + for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0x622ae6ae; + queue.context[0x31d] = 0x2000; + queue.context[0x201] = 0x112202; + return queue; + }; + queue = oneFragment(); + state = DecodeState(queue); + Require(state.samples == 4 && !state.customSampleLocations && state.depth && state.depth->samples == 4 && state.colors.size() == 1 && state.colors[0].samples == 1 && state.truncatedCoverage && !state.mergedCoverage && !state.perSampleShading, "a 4-sample color target with one fragment beside a 4x depth target (PPSA02015's registers) did not keep sample 0's color in its one fragment"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck refused a 4-sample color target with one fragment beside a 4x depth target"); + queue = oneFragment(); + queue.context[0x31c] |= 0x4000u; + queue.context[0x321] = 0x00c3a1b2u; + queue.context[0x3a0] = 0x01u; + state = DecodeState(queue); + Require(state.colors.size() == 1 && state.colors[0].fmaskAddress == ((1ull << 40u) | 0xc3a10000ull), "a compressed one-fragment color target did not decode its FMASK address"); + queue.context[0x321] = 0u; + queue.context[0x3a0] = 0u; + expectFailure([&] { DecodeState(queue); }, "a compressed one-fragment color target without an FMASK address is unsupported"); + queue.context[0x321] = 0xa5u; + expectFailure([&] { DecodeState(queue); }, "a compressed one-fragment color target without an FMASK address is unsupported"); + queue = oneFragment(); + Require(DecodeState(queue).colors[0].fmaskAddress == 0u, "an uncompressed one-fragment color target noted an FMASK address"); + queue = oneFragment(); + queue.context[0x31d] = 0xa000; + expectFailure([&] { DecodeState(queue); }, "color targets with more than one but fewer fragments than samples (EQAA) are unsupported"); + queue = oneFragment(); + queue.context[0x31d] = 0x8000; + expectFailure([&] { DecodeState(queue); }, "color targets with more fragments than samples are unsupported"); + queue = oneFragment(); + queue.context[0x201] = 0x012202; + expectFailure([&] { DecodeState(queue); }, "a one-fragment color target is supported only with static DB_EQAA anchors at the depth target's sample count"); + queue = oneFragment(); + queue.context[0x201] = 0x112201; + expectFailure([&] { DecodeState(queue); }, "a one-fragment color target is supported only with static DB_EQAA anchors at the depth target's sample count"); + queue = oneFragment(); + queue.context[0x2f8] = 0x00100001; + queue.context[0x010] = 0x22900987; + for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0xcc44; + expectFailure([&] { DecodeState(queue); }, "a one-fragment color target's sample count differs from the rasterizer's"); + queue = oneFragment(); + queue.context[0x010] = 0; + queue.context[0x011] = 0; expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's"); queue = singleSampleScan(); queue.context[0x010] = 0x22900983; @@ -4298,6 +4674,72 @@ void DepthRemapTests() { Require(floating.depthRemap.mode == 1 && floating.viewport.minDepth == 0 && floating.viewport.maxDepth == 1 && floating.depthClamp, "a written float depth target refused clipped clamp bounds"); } +void DepthBoundsEmulationTests() { + auto queue = makeState(); + queue.context[0x204] = 0; + queue.context[0x113] = std::bit_cast(0.5f); + queue.context[0x114] = std::bit_cast(0.5f); + auto base = AgcDriver::Graphics::DecodeState(queue); + base.depth = AgcDriver::Graphics::DepthTarget{0x10000, 0, {64, 64}, VK_FORMAT_D32_SFLOAT_S8_UINT, 0.0f, 0}; + base.depthTest = base.depthWrite = base.stencilTest = false; + base.depthBoundsTest = true; + base.depthBias = true; + base.depthBiasConstant = 4.0f; + const ShaderRecompiler::ShaderPixelStageInfo pixel{}; + const AgcDriver::Graphics::Context emulating{}; + const auto emulated = [&](float minBound, float maxBound) { + auto state = base; + state.minDepthBounds = minBound; + state.maxDepthBounds = maxBound; + AgcDriver::Graphics::ApplyDepthBoundsEmulation(emulating, state, pixel); + Require(!state.depthBoundsTest && state.depthTest && !state.depthWrite && !state.depthBias && state.viewport.x == base.viewport.x && state.viewport.width == base.viewport.width && state.viewport.height == base.viewport.height, "an emulated depth bounds test did not become a read-only depth test over the same coverage"); + return state; + }; + const auto upper = emulated(0.0f, 0.000125f); + Require(upper.depthCompare == VK_COMPARE_OP_GREATER_OR_EQUAL && upper.viewport.minDepth == 0.000125f && upper.viewport.maxDepth == 0.000125f, "an upper depth bound was not tested as the bound at or above the stored depth"); + const auto lower = emulated(0.000125f, 1.0f); + Require(lower.depthCompare == VK_COMPARE_OP_LESS_OR_EQUAL && lower.viewport.minDepth == 0.000125f && lower.viewport.maxDepth == 0.000125f, "a lower depth bound was not tested as the bound at or below the stored depth"); + const auto whole = emulated(0.0f, 1.0f); + Require(whole.depthCompare == VK_COMPARE_OP_GREATER_OR_EQUAL && whole.viewport.minDepth == 1.0f && whole.viewport.maxDepth == 1.0f, "bounds covering [0, 1] were not tested against the upper bound"); + AgcDriver::Graphics::Context native{}; + native.depthBounds = true; + auto kept = base; + AgcDriver::Graphics::ApplyDepthBoundsEmulation(native, kept, pixel); + Require(kept.depthBoundsTest && !kept.depthTest && kept.depthBias && kept.viewport.minDepth == base.viewport.minDepth, "a device with the depth bounds test had it emulated"); + auto absent = base; + absent.depth.reset(); + AgcDriver::Graphics::ApplyDepthBoundsEmulation(emulating, absent, pixel); + Require(absent.depthBoundsTest && !absent.depthTest, "a draw without a depth target had its depth bounds emulated"); + const auto refuses = [&](const char* expected, auto change) { + auto state = base; + state.minDepthBounds = 0.0f; + state.maxDepthBounds = 0.5f; + auto context = emulating; + auto program = pixel; + change(state, context, program); + expectFailure([&] { AgcDriver::Graphics::ApplyDepthBoundsEmulation(context, state, program); }, expected); + }; + refuses("both bounds inside", [](auto& state, auto&, auto&) { state.minDepthBounds = 0.25f; }); + refuses("with depth testing", [](auto& state, auto&, auto&) { state.depthTest = true; }); + refuses("reads or exports depth", [](auto&, auto&, auto& program) { program.posZ = true; }); + refuses("reads or exports depth", [](auto&, auto&, auto& program) { program.depthExportEnable = true; }); + refuses("normalized depth target", [](auto& state, auto&, auto&) { state.depth->format = VK_FORMAT_D16_UNORM; }); + refuses("stays in [0, 1]", [](auto&, auto& context, auto&) { context.depthRangeUnrestricted = true; }); + refuses("depth bounds outside [0, 1]", [](auto& state, auto&, auto&) { state.maxDepthBounds = 1.5f; }); + refuses("writes on a failed test", [](auto& state, auto&, auto&) { + state.stencilTest = true; + state.stencilFront.writeMask = 0xffu; + state.stencilFront.failOp = VK_STENCIL_OP_REPLACE; + }); + auto stencil = base; + stencil.stencilTest = true; + stencil.stencilFront.writeMask = stencil.stencilBack.writeMask = 0xffu; + stencil.stencilFront.passOp = stencil.stencilBack.passOp = VK_STENCIL_OP_REPLACE; + stencil.maxDepthBounds = 0.5f; + AgcDriver::Graphics::ApplyDepthBoundsEmulation(emulating, stencil, pixel); + Require(stencil.depthTest && stencil.stencilTest, "a stencil test that writes only on a pass refused the depth bounds emulation"); +} + void UnusedViewportDepthTests() { auto queue = makeState(); queue.context[0x204] = 0; @@ -5610,6 +6052,7 @@ void descriptorPoolResetFailureTest() { }); cache.Free(allocation); _ZSt13set_terminatePFvvE_nid_postfix(previous); + } void pushConstantTests() { @@ -6620,8 +7063,37 @@ void sampledTextureBudgetTests() { Require(SampledTextureBudget(memory, &reported, 5 * GiB) == 5 * GiB + 128 * MiB, "a 15 GiB budget with 4 GiB used outside the texture caches does not leave 5 GiB 128 MiB after the 4 GiB storage cache and 1 GiB 896 MiB of headroom"); reported.heapUsage[2] = 3 * GiB; Require(SampledTextureBudget(memory, &reported, 5 * GiB) == 9 * GiB + 128 * MiB, "usage below the cached bytes is not read as no other device memory in use"); + reported.heapUsage[2] = 9 * GiB; + Require(SampledTextureBudget(memory, &reported, 5 * GiB, 4 * GiB + 512 * MiB) == 4 * GiB + 640 * MiB, "a storage image cache larger than TextureCacheBudget was not reserved in full"); + Require(SampledTextureBudget(memory, &reported, 5 * GiB, 1 * GiB) == 5 * GiB + 128 * MiB, "a storage image cache smaller than TextureCacheBudget did not keep the TextureCacheBudget reservation"); reported.heapUsage[2] = 12 * GiB; Require(SampledTextureBudget(memory, &reported, 1 * GiB) == 2 * GiB, "a heap whose other users leave no room does not keep the 2 GiB floor"); + { + using AgcDriver::Graphics::StorageTextureBudget; + auto small = memory; + small.memoryHeaps[2].size = 8 * GiB; + VkPhysicalDeviceMemoryBudgetPropertiesEXT example{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; + example.heapBudget[2] = 7373 * MiB; + example.heapUsage[2] = 2 * GiB + 3686 * MiB; + Require(SampledTextureBudget(small, &example, 3686 * MiB, 3686 * MiB) == 2 * GiB, "3686 MiB of storage images on an 8 GiB heap with a 7373 MiB budget and 2 GiB of other use left the sampled cache above its floor"); + for (const auto heapGiB : {8u, 16u}) { + small.memoryHeaps[2].size = heapGiB * GiB; + for (std::uint64_t budget = 4 * GiB; budget <= heapGiB * GiB; budget += GiB) { + example.heapBudget[2] = budget; + for (std::uint64_t other = 0; other <= budget; other += GiB) { + for (std::uint64_t sampled = 0; sampled <= 4 * GiB; sampled += 512 * MiB) { + for (std::uint64_t storage = 0; storage <= heapGiB * GiB / 2u; storage += 512 * MiB) { + example.heapUsage[2] = other + sampled + storage; + const auto sampledLimit = SampledTextureBudget(small, &example, sampled + storage, storage); + const auto storageLimit = StorageTextureBudget(small, &example, sampled, storage); + if (sampledLimit > 2 * GiB) Require(other + storage + sampledLimit + budget / 8u <= budget, "the sampled cache budget and the storage images held exceed the reported budget less its eighth"); + if (storageLimit > 512 * MiB) Require(other + sampled + storageLimit + budget / 8u <= budget, "the storage cache budget and the sampled textures held exceed the reported budget less its eighth"); + } + } + } + } + } + } memory.memoryHeaps[0].flags = 0; memory.memoryHeaps[2].flags = 0; Require(SampledTextureBudget(memory, &reported, 0) == 2 * GiB, "a device without a device-local heap does not keep 2 GiB of sampled textures"); @@ -6642,29 +7114,29 @@ void storageTextureBudgetTests() { reported.heapBudget[1] = 100 * GiB; Require(StorageTextureBudget(memory, &reported, 0, 0) == 4 * GiB, "a zero budget for the largest device-local heap did not fall back to a quarter of the heap"); reported.heapBudget[2] = 10 * GiB; - Require(StorageTextureBudget(memory, &reported, 0, 0) == 2560 * MiB, "a 10 GiB budget on the 16 GiB heap does not limit the storage image cache to a quarter of the budget"); + Require(StorageTextureBudget(memory, &reported, 0, 0) == 5 * GiB, "a 10 GiB budget on the 16 GiB heap does not limit the storage image cache to half of the budget"); reported.heapUsage[2] = 5 * GiB; - Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 2560 * MiB, "2 GiB used outside the texture caches on a 10 GiB budget lowered the storage image cache below its quarter"); + Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 5 * GiB, "2 GiB used outside the texture caches on a 10 GiB budget lowered the storage image cache below its half"); reported.heapUsage[2] = 8 * GiB + 512 * MiB; Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 2304 * MiB, "a 10 GiB budget with 5632 MiB used outside the texture caches does not leave 2304 MiB after 1 GiB of sampled textures and 1280 MiB of headroom"); reported.heapUsage[2] = 9 * GiB; Require(StorageTextureBudget(memory, &reported, 3 * GiB, 2 * GiB) == 1792 * MiB, "3 GiB of sampled textures were not reserved in full"); reported.heapUsage[2] = 1 * GiB; - Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 2560 * MiB, "usage below the cached bytes is not read as no other device memory in use"); + Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 5 * GiB, "usage below the cached bytes is not read as no other device memory in use"); Require(StorageTextureBudget(memory, &reported, 7 * GiB, 1 * GiB) == 1792 * MiB, "7 GiB of sampled textures with the usage below the cached bytes do not leave 1792 MiB after their reservation and 1280 MiB of headroom"); reported.heapUsage[2] = 9 * GiB + 512 * MiB; Require(StorageTextureBudget(memory, &reported, 2 * GiB, 512 * MiB) == 512 * MiB, "a heap whose other users leave no room does not keep the 512 MiB floor"); reported.heapUsage[2] = 0; reported.heapBudget[2] = 6 * GiB; - Require(StorageTextureBudget(memory, &reported, 0, 0) == 1536 * MiB, "a 6 GiB budget on the 16 GiB heap does not limit the storage image cache to a quarter of the budget"); + Require(StorageTextureBudget(memory, &reported, 0, 0) == 3 * GiB, "a 6 GiB budget on the 16 GiB heap does not limit the storage image cache to half of the budget"); reported.heapBudget[2] = 1 * GiB; Require(StorageTextureBudget(memory, &reported, 0, 0) == 512 * MiB, "a 1 GiB budget does not keep the 512 MiB floor"); reported.heapBudget[2] = 40 * GiB; - Require(StorageTextureBudget(memory, &reported, 0, 0) == 4 * GiB, "a budget above the heap size raised the storage image cache past a quarter of the heap"); + Require(StorageTextureBudget(memory, &reported, 0, 0) == 8 * GiB, "a budget above the heap size raised the storage image cache past half of the heap"); memory.memoryHeaps[2].size = 8 * GiB; reported.heapBudget[2] = 7189 * MiB; reported.heapUsage[2] = 4491 * MiB; - Require(StorageTextureBudget(memory, &reported, 49 * MiB, 506 * MiB) == 7189 * MiB / 4u, "49 MiB of sampled textures on an 8 GiB heap with a 7189 MiB budget and 3936 MiB used outside the texture caches held the storage image cache below a quarter of the budget"); + Require(StorageTextureBudget(memory, &reported, 49 * MiB, 506 * MiB) == 7189 * MiB - 3936 * MiB - 49 * MiB - 7189 * MiB / 8u, "49 MiB of sampled textures on an 8 GiB heap with a 7189 MiB budget and 3936 MiB used outside the texture caches did not leave the storage image cache what the budget has left after them and its eighth"); reported.heapUsage[2] = 6490 * MiB; Require(StorageTextureBudget(memory, &reported, 2 * GiB, 506 * MiB) == 512 * MiB, "2 GiB of sampled textures on an 8 GiB heap with a 7189 MiB budget and 3936 MiB used outside the texture caches did not take the storage image cache back to its 512 MiB floor"); memory.memoryHeaps[2].size = 10 * GiB; @@ -6681,8 +7153,8 @@ void storageTextureBudgetTests() { reported.heapUsage[2] = outside + sampled + 512 * MiB; const auto limit = StorageTextureBudget(memory, &reported, sampled, 512 * MiB); Require(limit >= 512 * MiB, "pressure sweep lost the storage cache floor"); - Require(limit <= std::max(512 * MiB, budget / 4u), "pressure sweep exceeded a quarter of the reported budget above the floor"); - Require(limit <= std::max(2 * GiB, heapGiB * GiB / 4u), "pressure sweep exceeded the heap storage cache cap"); + Require(limit <= std::max(512 * MiB, budget / 2u), "pressure sweep exceeded half of the reported budget above the floor"); + Require(limit <= std::max(512 * MiB, heapGiB * GiB / 2u), "pressure sweep exceeded half of the heap"); if (outside != 0) Require(limit <= previous, "additional non-cache memory usage grew the storage cache budget"); if (limit > 512 * MiB) Require(outside + sampled + budget / 8u + limit <= budget, "storage cache budget consumed live sampled memory or pressure headroom"); previous = limit; @@ -6693,6 +7165,27 @@ void storageTextureBudgetTests() { memory.memoryHeaps[0].flags = 0; memory.memoryHeaps[2].flags = 0; Require(StorageTextureBudget(memory, &reported, 0, 0) == 2 * GiB, "a device without a device-local heap does not keep 2 GiB of storage images"); + using AgcDriver::Graphics::StorageTextureCeiling; + memory.memoryHeaps[0] = {256 * MiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}; + memory.memoryHeaps[2] = {16 * GiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}; + reported = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; + Require(StorageTextureCeiling(memory, nullptr) == 4 * GiB, "without VK_EXT_memory_budget the storage image cache ceiling is not a quarter of the 16 GiB heap"); + reported.heapBudget[2] = 6 * GiB; + Require(StorageTextureCeiling(memory, &reported) == 3 * GiB, "the storage image cache ceiling on a 6 GiB budget is not half of the budget, the cap the cache may reach"); + reported.heapBudget[2] = 40 * GiB; + Require(StorageTextureCeiling(memory, &reported) == 8 * GiB, "the storage image cache ceiling on a 40 GiB budget is not half of the 16 GiB heap"); + reported.heapBudget[2] = 768 * MiB; + Require(StorageTextureCeiling(memory, &reported) == 512 * MiB, "the storage image cache ceiling on a 768 MiB budget does not keep the 512 MiB floor"); + for (std::uint64_t budget = 1 * GiB; budget <= 24 * GiB; budget += 512 * MiB) { + reported.heapBudget[2] = budget; + const auto ceiling = StorageTextureCeiling(memory, &reported); + for (std::uint64_t used = 0; used <= budget; used += 256 * MiB) { + reported.heapUsage[2] = used; + Require(StorageTextureBudget(memory, &reported, 256 * MiB, 512 * MiB) <= ceiling, "the storage image cache budget exceeds the ceiling idle device buffers are reclaimed up to"); + } + reported.heapUsage[2] = 0; + Require(StorageTextureBudget(memory, &reported, 0, 0) == ceiling, "an idle heap does not give the storage image cache its full ceiling"); + } } void sampledBudgetReportTests() { @@ -6739,6 +7232,41 @@ void misalignedShaderDataTests() { Require(mock.live == 0, "misaligned shader data resources leaked Vulkan objects"); } +void imageTableTemplateRefreshTests() { + namespace Abi = ShaderRecompiler::ImageTableAbi; + mock = MockVulkan{}; + auto context = mockContext(); + context.imageTableCapacity = 2; + context.limits.maxDescriptorSetSampledImages = 2; + context.limits.maxPerStageDescriptorSampledImages = 2; + const auto commands = reinterpret_cast(std::uintptr_t{1}); + { + ShaderRecompiler::RecompileResult compute; + auto map = makeBinding(Role::ImageTableMap, static_cast(ShaderRecompiler::RuntimeAbi::Binding::ImageTableMap), 1, std::vector(Abi::HeaderWords)); + map.guestDescriptor[Abi::HeaderVersion] = Abi::Version; + map.guestDescriptor[Abi::HeaderElementFaults] = Abi::HeaderWords; + map.guestDescriptor[Abi::HeaderSamplerFaults] = Abi::HeaderWords; + compute.bindings.push_back(map); + auto images = makeBinding(Role::ImageTable, ShaderRecompiler::RuntimeAbi::ImageTableBinding, 0, {}); + images.kind = Kind::SampledImage; + compute.bindings.push_back(images); + compute.bindings.push_back(makeBinding(Role::ShaderData, 1, 1, ShaderDataWords({0x11, 0x22}))); + compute.shaderDataDwords = ShaderRecompiler::RuntimeAbi::ShaderDataDwords; + auto live = compute; + live.bindings[2].guestDescriptor = ShaderDataWords({0x33, 0x44}); + const AgcDriver::Graphics::CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &compute, 0}; + const AgcDriver::Graphics::CompiledShader liveShader{ShaderRecompiler::ShaderStage::Compute, &live, 0}; + AgcDriver::Graphics::ShaderResources resources(context, shader); + const auto data = findWrite(1).buffers.at(0).buffer; + Require(!resources.DataWordsDiffer(shader), "a template's own words differ from the shader that built it"); + Require(resources.DataWordsDiffer(liveShader), "a template with an image table missed the dispatch's new words"); + Require(resources.RefreshData(commands, liveShader), "a refresh of a template with an image table recorded nothing"); + Require(sameBytes(bufferBytes(data), live.bindings[2].guestDescriptor.data(), 3u * sizeof(std::uint32_t)), "a refresh of a template with an image table did not write the dispatch's words"); + Require(!resources.DataWordsDiffer(liveShader), "the refreshed template's words are not the dispatch's"); + } + Require(mock.live == 0, "image table template refresh resources leaked Vulkan objects"); +} + struct ModuleShape { bool fragment = false; std::optional fragmentMode; @@ -7677,6 +8205,38 @@ void pipelineLibraryClearWaitsForBuildTests() { Require(mock.live == 0, "clearing during a library build leaked Vulkan objects"); } +void tessellationPipelineLibraryCacheTests() { + using namespace AgcDriver::Graphics; + for (const auto shared : {true, false}) { + mock = MockVulkan{}; + auto context = mockContext(); + context.graphicsPipelineLibrary = true; + context.tessellationShader = true; + context.tessellationPipelineCache = shared; + context.limits.maxTessellationPatchSize = 32; + context.limits.maxPushConstantsSize = 256; + context.limits.framebufferNoAttachmentsSampleCounts = VK_SAMPLE_COUNT_1_BIT; + State state{}; + state.stages.path = ShaderPath::Tessellation; + state.stages.tessellation = ShaderRecompiler::TessellationConfiguration{3, 3, 2, 1, 4, 128, 16, false}; + ShaderRecompiler::RecompileResult vertex, control, evaluation, fragment; + vertex.spirv = makeModule({}); + control.spirv = makeModule({}); + evaluation.spirv = makeModule({}); + fragment.spirv = makeModule({.fragment = true}); + vertex.variantId = 201; + control.variantId = 202; + evaluation.variantId = 203; + fragment.variantId = 204; + const std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::TessellationControl, &control, 0}, {ShaderRecompiler::ShaderStage::TessellationEvaluation, &evaluation, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, PipelinePushSlotBytes}}}; + std::array mainKey{31}; + const PipelineSetLayouts resources{makeHandle(), VK_NULL_HANDLE, false, mainKey, {}}; + Require(Pipeline::Prebuild(context, state, VertexInputLayout{}, resources, shaders) == shared, shared ? "tessellation pipelines skipped graphics pipeline libraries with a shared pipeline cache" : "tessellation pipelines without the pipeline cache were built from graphics pipeline libraries"); + ClearPipelineLibraries(context.device); + Require(mock.live == 0, "tessellation pipeline library tests leaked Vulkan objects"); + } +} + void framebufferOwnerTests() { using namespace AgcDriver::Graphics; mock = MockVulkan{}; @@ -7744,6 +8304,9 @@ void barycentricEmulationTests() { const std::array parameters{{{0u, 0u, false, false}, {1u, 1u, false, true}, {2u, 2u, true, false}}}; const auto layout = LayoutBarycentricEmulation(parameters, {true, true, true}); Require(layout.smoothLocation == 1u && layout.linearLocation == 3u && layout.perVertexLocations == std::vector>{{1u, 4u}}, "emulated barycentrics did not take the free locations in order"); + const auto offsetLayout = LayoutBarycentricEmulation(parameters, {true, true, false, true}); + Require(offsetLayout.smoothLocation == 1u && offsetLayout.reciprocalWLocation == 3u && offsetLayout.perVertexLocations == std::vector>{{1u, 4u}}, "perspective offset emulation did not reserve reciprocal-w inputs"); + Require(LayoutBarycentricEmulation(parameters, {true, true, false}).reciprocalWLocation == BarycentricEmulationLayout::NoLocation, "non-offset perspective barycentrics reserved reciprocal-w inputs"); Require(LayoutBarycentricEmulation(parameters, {}).perVertexLocations.empty() && LayoutBarycentricEmulation(parameters, {}).smoothLocation == BarycentricEmulationLayout::NoLocation, "an inactive emulation was given locations"); std::vector crowded; for (std::uint32_t location = 0; location < 11u; ++location) crowded.push_back({location, location, false, true}); @@ -7860,9 +8423,11 @@ void barycentricEmulationTests() { Require(BarycentricPatches(state, patchShaders) && !BarycentricPatches(state, shaders), "generated tessellation stages were not recognised as barycentric patches"); state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; ValidateShaders(patchShaders, state, subgroup, false); - for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, VK_PRIMITIVE_TOPOLOGY_POINT_LIST}) { + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + ValidateShaders(patchShaders, state, subgroup, false); + for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, VK_PRIMITIVE_TOPOLOGY_POINT_LIST}) { state.topology = topology; - expectFailure([&] { ValidateShaders(patchShaders, state, subgroup, false); }, "require a triangle list"); + expectFailure([&] { ValidateShaders(patchShaders, state, subgroup, false); }, "require a triangle list or strip"); } state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; const std::array plainPatchShaders{{{ShaderStage::Vertex, &vertex, 0}, {ShaderStage::TessellationControl, &patches.control, 0}, {ShaderStage::TessellationEvaluation, &patches.evaluation, 0}, {ShaderStage::Fragment, &plain, 0}}}; @@ -8410,6 +8975,24 @@ std::string interlockShape(std::span words) { return shape; } +bool coherentImageRead(std::span words) { + std::map sources; + std::set coherent; + std::uint32_t image = 0; + 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::OpDecorate && words[at + 2] == spv::DecorationCoherent) coherent.insert(words[at + 1]); + if (op == spv::OpLoad || op == spv::OpAccessChain || op == spv::OpInBoundsAccessChain) sources[words[at + 2]] = words[at + 3]; + if (op == spv::OpImageRead) image = words[at + 3]; + } + for (std::size_t step = 0; image != 0 && step < 8u; ++step) { + if (coherent.contains(image)) return true; + const auto source = sources.find(image); + image = source == sources.end() ? 0u : source->second; + } + return false; +} + std::string interlockExits(std::span words, std::size_t& kills, std::size_t& terminates) { struct Block { std::vector ops; @@ -8652,7 +9235,7 @@ void stencilRefExportTests() { Require(AgcDriver::Graphics::DrawRejection(unused, false).empty(), "the precheck rejected STENCIL_OP_VAL_EXPORT_ENABLE without a stencil test in DB_SHADER_CONTROL " + std::to_string(shaderControl)); Require(AgcDriver::Graphics::DecodePixelStageInfo(unused.context, IdentityExports).stencilRefExportEnable == ((shaderControl & 2u) != 0), "STENCIL_OP_VAL_EXPORT_ENABLE without a stencil test exported the stencil reference"); const bool replaces = (shaderControl & 6u) == 4u; - for (const auto [ops, writeMask, compare, accepted] : {std::tuple{0x000u, 0xffu, 7u, true}, std::tuple{0x737u, 0xffu, 7u, true}, std::tuple{0x110u, 0xffu, 7u, true}, std::tuple{0x050u, 0x00u, 7u, true}, std::tuple{0x040u, 0xffu, 7u, replaces}, std::tuple{0x40040u, 0xffu, 7u, replaces}, std::tuple{0x404u, 0x0fu, 0u, replaces}, std::tuple{0x040u, 0xffu, 2u, false}, std::tuple{0x340u, 0xffu, 7u, false}, std::tuple{0x042u, 0xffu, 7u, false}, std::tuple{0x050u, 0xffu, 7u, false}, std::tuple{0x600u, 0xffu, 7u, false}, std::tuple{0x008u, 0xffu, 7u, false}, std::tuple{0x900u, 0xffu, 7u, false}, std::tuple{0x5000u, 0xffu, 7u, false}}) { + for (const auto [ops, writeMask, compare, accepted] : {std::tuple{0x000u, 0xffu, 7u, true}, std::tuple{0x737u, 0xffu, 7u, true}, std::tuple{0x110u, 0xffu, 7u, true}, std::tuple{0x050u, 0x00u, 7u, true}, std::tuple{0x040u, 0xffu, 7u, replaces}, std::tuple{0x40040u, 0xffu, 7u, replaces}, std::tuple{0x404u, 0x0fu, 0u, replaces}, std::tuple{0x040u, 0xffu, 2u, false}, std::tuple{0x340u, 0xffu, 7u, false}, std::tuple{0x042u, 0xffu, 7u, false}, std::tuple{0x050u, 0xffu, 7u, false}, std::tuple{0x600u, 0xffu, 7u, false}, std::tuple{0x008u, 0xffu, 7u, false}, std::tuple{0x900u, 0xffu, 7u, false}, std::tuple{0x5000u, 0xffu, 7u, false}, std::tuple{0x0c0u, 0xffu, 7u, false}, std::tuple{0x00bu, 0xffu, 7u, false}}) { auto stenciled = stencilRefExportQueue(shaderControl, zFormat); for (const auto [offset, word] : {std::pair{0x010u, 0x80000180u}, std::pair{0x011u, 0x20000181u}, std::pair{0x012u, 0x00001000u}, std::pair{0x013u, 0x00002000u}, std::pair{0x015u, 0x00002000u}, std::pair{0x007u, 0x003f003fu}, std::pair{0x000u, 0u}, std::pair{0x002u, 0u}, std::pair{0x00au, 0u}, std::pair{0x00bu, 0x3f800000u}, std::pair{0x200u, 0x007007b3u}}) stenciled.context[offset] = word; stenciled.context[0x10b] = ops; @@ -8962,6 +9545,99 @@ void stencilReferenceUnusedTests() { std::cout << "Unused stencil reference sample equivalence passed: " << accepted << " accepted states, " << countermodels << " dependent countermodels\n"; } +void stencilLogicOperationTests() { + const auto queueFor = [](std::uint32_t ops, std::uint32_t front, std::uint32_t back, std::uint32_t depthControl) { + auto queue = makeState(); + for (const auto [offset, word] : {std::pair{0x000u, 0u}, std::pair{0x002u, 0u}, std::pair{0x007u, (1u << 16u) | 3u}, std::pair{0x00au, 0u}, std::pair{0x00bu, 0u}, std::pair{0x010u, 0x22900983u}, std::pair{0x011u, 0x20000181u}, std::pair{0x012u, 0x100u}, std::pair{0x014u, 0x100u}, std::pair{0x013u, 0x200u}, std::pair{0x015u, 0x200u}, std::pair{0x005u, 0x300u}, std::pair{0x01eu, 0x1u}}) queue.context[offset] = word; + queue.context[0x10b] = ops; + queue.context[0x10c] = front; + queue.context[0x10d] = back; + queue.context[0x200] = depthControl; + return queue; + }; + const auto refMaskOf = [](std::uint32_t test, std::uint32_t writeMask, std::uint32_t value) { return test | 0xff00u | (writeMask << 16u) | (value << 24u); }; + const auto apply = [](std::uint32_t op, std::uint32_t stencil, std::uint32_t value) { + switch (op) { + case 1: return 0u; + case 10: return stencil & value; + case 11: return stencil | value; + case 12: return stencil ^ value; + case 15: return ~(stencil ^ value); + default: return stencil; + } + }; + const auto matches = [&](const AgcDriver::Graphics::State& state, const VkStencilOpState& face, std::uint32_t ops, std::uint32_t compare, std::uint32_t refMask, const std::string& name) { + const auto test = refMask & 0xffu; + const auto compareMask = (refMask >> 8u) & 0xffu; + const auto writeMask = (refMask >> 16u) & 0xffu; + const auto value = refMask >> 24u; + for (std::uint32_t stencil = 0; stencil < 256u; ++stencil) { + for (const bool depthPass : {false, true}) { + const bool stencilPass = stencilComparison(static_cast(compare), test & compareMask, stencil & compareMask); + const auto op = (ops >> (!stencilPass ? 0u : !depthPass ? 8u : 4u)) & 0xfu; + const auto expected = ((stencil & ~writeMask) | (apply(op, stencil, value) & writeMask)) & 0xffu; + const auto [actual, passed] = stencilReferenceOutcome(state, face, face.reference, stencil, depthPass, true); + Require(actual == expected && passed == (stencilPass && depthPass), name + ": stencil " + std::to_string(stencil) + (depthPass ? " passing" : " failing") + " the depth test became " + std::to_string(actual) + ", expected " + std::to_string(expected)); + } + } + }; + std::size_t accepted = 0; + for (const auto op : {10u, 11u, 12u, 15u}) { + for (const auto placement : {0x010u, 0x001u, 0x100u, 0x111u}) { + for (const auto compare : {7u, 0u, 2u, 3u}) { + for (const auto [test, writeMask, value] : {std::tuple{0x00u, 0xffu, 0x5au}, std::tuple{0xa5u, 0xffu, 0x21u}, std::tuple{0xa5u, 0x3cu, 0xffu}, std::tuple{0xffu, 0xf0u, 0x0fu}, std::tuple{0x5au, 0x81u, 0x00u}}) { + const auto ops = op * placement; + const auto refMask = refMaskOf(test, writeMask, value); + const auto queue = queueFor(ops, refMask, refMask, 0x13u | (compare << 8u)); + const auto name = "stencil operation " + std::to_string(op) + " in DB_STENCIL_CONTROL " + std::to_string(ops) + " with compare " + std::to_string(compare) + " and DB_STENCILREFMASK " + std::to_string(refMask); + const auto bits = (op == 11u || op == 12u ? value : ~value) & writeMask; + if (op == 11u && compare != 0u && compare != 7u && (~test & bits) != 0u) { + expectFailure([&] { static_cast(AgcDriver::Graphics::DecodeState(queue)); }, "stencil OR of"); + continue; + } + const auto state = AgcDriver::Graphics::DecodeState(queue); + Require(state.stencilTest && state.depthTest && (state.stencilFront.writeMask & 0xffu) == (bits != 0u ? bits : writeMask), "the write mask is not narrowed to the bits changed by " + name); + matches(state, state.stencilFront, ops, compare, refMask, name); + ++accepted; + } + } + } + } + for (const auto [ops, refMask, name] : {std::tuple{0x0c1u, refMaskOf(0x80u, 0xffu, 0xffu), "XOR of every written bit beside ZERO"}, std::tuple{0x0cbu, refMaskOf(0x8fu, 0xffu, 0x0fu), "OR and XOR of the same bits"}, std::tuple{0x0fau, refMaskOf(0x80u, 0xffu, 0xf0u), "AND and XNOR of the same bits"}, std::tuple{0xb0bu, refMaskOf(0x30u, 0x3cu, 0xf0u), "OR on stencil fail and depth fail"}}) { + const auto queue = queueFor(ops, refMask, refMask, 0x13u | (3u << 8u)); + const auto state = AgcDriver::Graphics::DecodeState(queue); + matches(state, state.stencilFront, ops, 3u, refMask, name); + ++accepted; + } + { + const auto front = refMaskOf(0x11u, 0xffu, 0x0fu); + const auto back = refMaskOf(0x22u, 0x3cu, 0x30u); + const auto queue = queueFor(0x0c0u | (0x0a0u << 12u), front, back, 0x93u | (7u << 8u) | (5u << 20u)); + const auto state = AgcDriver::Graphics::DecodeState(queue); + Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 0x0fu && state.stencilBack.passOp == VK_STENCIL_OP_ZERO && state.stencilBack.writeMask == 0x0cu, "front XOR and back AND did not keep their own write masks"); + matches(state, state.stencilFront, 0x0c0u, 7u, front, "front XOR beside a back AND"); + matches(state, state.stencilBack, 0x0a0u, 5u, back, "back AND beside a front XOR"); + accepted += 2; + } + for (const auto [ops, refMask, reason] : {std::tuple{0x0d0u, refMaskOf(0u, 0xffu, 0x0fu), "stencil operation 13 is unsupported"}, std::tuple{0x0e0u, refMaskOf(0u, 0xffu, 0x0fu), "stencil operation 14 is unsupported"}, std::tuple{0x0cau, refMaskOf(0u, 0xffu, 0x0fu), "stencil logic operations on write masks"}, std::tuple{0x0fcu, refMaskOf(0u, 0xffu, 0x0fu), "stencil logic operations on write masks"},std::tuple{0x0c1u, refMaskOf(0u, 0xffu, 0x0fu), "combine a logic operation"}, std::tuple{0x5c0u, refMaskOf(0u, 0xffu, 0x01u), "combine a logic operation"}}) { + const auto queue = queueFor(ops, refMask, refMask, 0x13u | (7u << 8u)); + expectFailure([&] { static_cast(AgcDriver::Graphics::DecodeState(queue)); }, reason); + } + for (const auto ops : {0x0c0u, 0x0b0u}) { + auto exported = stencilRefExportQueue(0x12u, 9u); + for (const auto [offset, word] : {std::pair{0x010u, 0x80000180u}, std::pair{0x011u, 0x20000181u}, std::pair{0x012u, 0x00001000u}, std::pair{0x013u, 0x00002000u}, std::pair{0x015u, 0x00002000u}, std::pair{0x007u, 0x003f003fu}, std::pair{0x000u, 0u}, std::pair{0x002u, 0u}, std::pair{0x00au, 0u}, std::pair{0x00bu, 0x3f800000u}, std::pair{0x200u, 0x007007b3u}}) exported.context[offset] = word; + exported.context[0x10b] = ops; + exported.context[0x10c] = exported.context[0x10d] = refMaskOf(0u, 0xffu, 0x0fu); + if (ops == 0x0c0u) { + const auto state = AgcDriver::Graphics::DecodeState(exported); + Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 0x0fu, "a stencil test value export changed a stencil XOR"); + } else { + expectFailure([&] { static_cast(AgcDriver::Graphics::DecodeState(exported)); }, "stencil OR is unsupported with a pixel shader that exports the stencil test value"); + } + } + std::cout << "Stencil logic operations matched AND, OR, XOR and XNOR under the write mask for " << accepted << " register states\n"; +} + std::set bufferViews(std::span words) { std::set views; for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) { @@ -9073,7 +9749,7 @@ void ProvokingVertexTests() { ValidateProvokingVertex(unsupported, first); auto last = first; last.provokingVertexMode = VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT; - expectFailure([&] { ValidateProvokingVertex(unsupported, last); }, "last provoking vertex requires VK_EXT_provoking_vertex with provokingVertexLast enabled"); + ValidateProvokingVertex(unsupported, last); auto supported = unsupported; supported.provokingVertexLast = true; ValidateProvokingVertex(supported, last); @@ -9102,6 +9778,14 @@ void ProvokingVertexTests() { const std::array flatP10Shaders{{{ShaderRecompiler::ShaderStage::Fragment, &flatP10Pixel, 0}}}; ValidateProvokingVertex(supported, last, flatP10Shaders); expectFailure([&] { ValidateProvokingVertex(supported, first, flatP10Shaders); }, "v_interp_mov p10 of a flat input with the first provoking vertex is not measured"); + const std::array flatP20Color{0xc8120001u, 0xc8160101u, 0xc81a0201u, 0xc81e0301u, 0xf800180fu, 0x07060504u, 0xbf810000u}; + const auto flatP20Pixel = recompilePixel({0x400u}, flatP20Color); + const auto p20Inputs = locatedInputs(flatP20Pixel.spirv.Words()); + Require(p20Inputs.size() == 1 && p20Inputs[0].location == 0 && p20Inputs[0].flat && !p20Inputs[0].perVertex, "flat v_interp_mov P20 did not compile to a flat input"); + Require(flatP20Pixel.fragmentParameters.size() == 1 && flatP20Pixel.fragmentParameters[0].flatP20 && !flatP20Pixel.fragmentParameters[0].flatP10 && !flatP20Pixel.fragmentParameters[0].perVertex && !flatP10Pixel.fragmentParameters[0].flatP20, "flat v_interp_mov P20 was not recorded apart from P10"); + const std::array flatP20Shaders{{{ShaderRecompiler::ShaderStage::Fragment, &flatP20Pixel, 0}}}; + ValidateProvokingVertex(supported, first, flatP20Shaders); + expectFailure([&] { ValidateProvokingVertex(supported, last, flatP20Shaders); }, "v_interp_mov p20 of a flat input with the last provoking vertex is not measured"); auto lastStrip = last; lastStrip.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; auto lastList = last; @@ -9161,10 +9845,16 @@ void ProvokingVertexTests() { meshDraw.topology = topology; for (const auto& drawn : {std::span{}, std::span(flatShaders), std::span(flatP10Shaders), std::span(smoothShaders), std::span(slotShaders)}) ValidateProvokingVertex(supported, meshDraw, drawn); } + meshDraw.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + for (const auto& drawn : {std::span{}, std::span(flatShaders), std::span(flatP10Shaders), std::span(smoothShaders)}) ValidateProvokingVertex(supported, meshDraw, drawn); + fragment.fragmentParameters = {{0, 0, false, true, false, false, false}, {1, 1, false, true, false, false, false}, {2, 2, false, true, false, false, false}, {3, 3, false, true, false, false, false}}; + ValidateProvokingVertex(supported, meshDraw, shaders); + expectFailure([&] { ValidateProvokingVertex(supported, meshDraw, slotShaders); }, "last provoking vertex with explicit per-vertex triangle-strip interpolation is unsupported"); + meshDraw.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; + expectFailure([&] { ValidateProvokingVertex(supported, meshDraw, flatShaders); }, "last provoking vertex is unsupported for triangle fans assembled for a geometry program"); for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN}) { - meshDraw.topology = topology; - expectFailure([&] { ValidateProvokingVertex(supported, meshDraw, flatShaders); }, "last provoking vertex is unsupported for triangle strips or fans assembled for a geometry program"); auto firstDraw = meshDraw; + firstDraw.topology = topology; firstDraw.provokingVertexMode = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT; ValidateProvokingVertex(supported, firstDraw, flatShaders); } @@ -9182,6 +9872,105 @@ void ProvokingVertexTests() { expectFailure([&] { ValidateProvokingVertex(supported, last); }, "invalid provoking vertex mode"); } +void UnobservedProvokingVertexTests() { + using namespace AgcDriver::Graphics; + auto last = DecodeState(makeState()); + last.provokingVertexMode = VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT; + const Context unsupported{}; + const auto module = [](std::initializer_list instructions) { + std::vector words{spv::MagicNumber, 0x10300u, 0u, 4u, 0u}; + words.insert(words.end(), instructions); + return words; + }; + ShaderRecompiler::RecompileResult fragment{}; + const std::array shaders{{{ShaderRecompiler::ShaderStage::Fragment, &fragment, 0}}}; + const ShaderRecompiler::FragmentParameter smooth{0, 0, false, false}; + const auto refused = [&](const State& state) { + expectFailure([&] { ValidateProvokingVertex(unsupported, state, shaders); }, "last provoking vertex requires VK_EXT_provoking_vertex with provokingVertexLast enabled"); + }; + for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN}) { + auto state = last; + state.topology = topology; + ValidateProvokingVertex(unsupported, state); + fragment.fragmentParameters.clear(); + fragment.spirv = module({}); + ValidateProvokingVertex(unsupported, state, shaders); + fragment.fragmentParameters = {smooth}; + fragment.spirv = module({(4u << 16u) | spv::OpDecorate, 1u, spv::DecorationLocation, 0u}); + ValidateProvokingVertex(unsupported, state, shaders); + for (std::uint32_t read = 0; read < 4; ++read) { + const ShaderRecompiler::FragmentParameter observed{1, 1, read == 0, read == 1, read == 2, read == 3, false}; + fragment.fragmentParameters = {smooth, observed}; + refused(state); + } + fragment.fragmentParameters = {smooth}; + for (const std::uint32_t decoration : {static_cast(spv::DecorationFlat), static_cast(spv::DecorationPerVertexKHR)}) { + fragment.spirv = module({(3u << 16u) | spv::OpDecorate, 1u, decoration}); + refused(state); + fragment.spirv = module({(4u << 16u) | spv::OpMemberDecorate, 1u, 0u, decoration}); + refused(state); + } + fragment.spirv = module({(5u << 16u) | spv::OpDecorate, 1u, spv::DecorationLocation}); + refused(state); + fragment.spirv = std::vector{}; + refused(state); + } + fragment.fragmentParameters = {smooth}; + for (const std::uint32_t generated : {static_cast(spv::BuiltInSampleId), static_cast(spv::BuiltInFrontFacing), static_cast(spv::BuiltInSubgroupLocalInvocationId)}) { + fragment.spirv = module({(4u << 16u) | spv::OpDecorate, 1u, spv::DecorationBuiltIn, generated, (3u << 16u) | spv::OpDecorate, 1u, spv::DecorationFlat}); + ValidateProvokingVertex(unsupported, last, shaders); + fragment.spirv = module({(3u << 16u) | spv::OpDecorate, 1u, spv::DecorationFlat, (4u << 16u) | spv::OpDecorate, 1u, spv::DecorationBuiltIn, generated}); + ValidateProvokingVertex(unsupported, last, shaders); + fragment.spirv = module({(5u << 16u) | spv::OpMemberDecorate, 1u, 0u, spv::DecorationBuiltIn, generated, (4u << 16u) | spv::OpMemberDecorate, 1u, 0u, spv::DecorationFlat}); + ValidateProvokingVertex(unsupported, last, shaders); + fragment.spirv = module({(4u << 16u) | spv::OpDecorate, 1u, spv::DecorationBuiltIn, generated, (3u << 16u) | spv::OpDecorate, 2u, spv::DecorationFlat}); + refused(last); + fragment.spirv = module({(5u << 16u) | spv::OpMemberDecorate, 1u, 0u, spv::DecorationBuiltIn, generated, (4u << 16u) | spv::OpMemberDecorate, 1u, 1u, spv::DecorationFlat}); + refused(last); + } + for (const std::uint32_t passed : {static_cast(spv::BuiltInLayer), static_cast(spv::BuiltInViewportIndex), static_cast(spv::BuiltInPrimitiveId)}) { + fragment.spirv = module({(4u << 16u) | spv::OpDecorate, 1u, spv::DecorationBuiltIn, passed, (3u << 16u) | spv::OpDecorate, 1u, spv::DecorationFlat}); + refused(last); + fragment.spirv = module({(5u << 16u) | spv::OpMemberDecorate, 1u, 0u, spv::DecorationBuiltIn, passed, (4u << 16u) | spv::OpMemberDecorate, 1u, 0u, spv::DecorationFlat}); + refused(last); + } + ShaderRecompiler::RecompileResult vertex{}; + vertex.fragmentParameters = {{0, 0, true, true, true, true, true}}; + vertex.spirv = module({(3u << 16u) | spv::OpDecorate, 1u, spv::DecorationFlat}); + fragment.fragmentParameters = {smooth}; + fragment.spirv = module({}); + const std::array stages{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, 0}}}; + ValidateProvokingVertex(unsupported, last, stages); + Context supported{}; + supported.provokingVertexLast = true; + for (const auto stage : {ShaderRecompiler::ShaderStage::Vertex, ShaderRecompiler::ShaderStage::TessellationEvaluation, ShaderRecompiler::ShaderStage::Geometry, ShaderRecompiler::ShaderStage::Mesh}) { + ShaderRecompiler::RecompileResult writer{}; + const std::array written{{{stage, &writer, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, 0}}}; + const auto refusedWrite = [&] { + expectFailure([&] { ValidateProvokingVertex(unsupported, last, written); }, "last provoking vertex requires VK_EXT_provoking_vertex with provokingVertexLast enabled"); + ValidateProvokingVertex(supported, last, written); + }; + for (const std::uint32_t selected : {static_cast(spv::BuiltInLayer), static_cast(spv::BuiltInViewportIndex)}) { + writer.spirv = module({(4u << 16u) | spv::OpDecorate, 1u, spv::DecorationBuiltIn, selected}); + refusedWrite(); + writer.spirv = module({(5u << 16u) | spv::OpMemberDecorate, 1u, 0u, spv::DecorationBuiltIn, selected}); + refusedWrite(); + writer.spirv = module({(5u << 16u) | spv::OpMemberDecorate, 1u, 0u, spv::DecorationBuiltIn, selected, (4u << 16u) | spv::OpMemberDecorate, 1u, 1u, spv::DecorationPerPrimitiveEXT}); + refusedWrite(); + writer.spirv = module({(4u << 16u) | spv::OpMemberDecorate, 1u, 0u, spv::DecorationPerPrimitiveEXT, (5u << 16u) | spv::OpMemberDecorate, 1u, 0u, spv::DecorationBuiltIn, selected}); + ValidateProvokingVertex(unsupported, last, written); + writer.spirv = module({(4u << 16u) | spv::OpDecorate, 1u, spv::DecorationBuiltIn, selected, (3u << 16u) | spv::OpDecorate, 1u, spv::DecorationPerPrimitiveEXT}); + ValidateProvokingVertex(unsupported, last, written); + } + writer.spirv = module({(4u << 16u) | spv::OpDecorate, 1u, spv::DecorationBuiltIn, spv::BuiltInPosition, (5u << 16u) | spv::OpMemberDecorate, 2u, 0u, spv::DecorationBuiltIn, spv::BuiltInPointSize}); + ValidateProvokingVertex(unsupported, last, written); + writer.spirv = std::vector{}; + refusedWrite(); + } + const std::array missing{{{ShaderRecompiler::ShaderStage::Fragment, nullptr, 0}}}; + expectFailure([&] { ValidateProvokingVertex(unsupported, last, missing); }, "last provoking vertex requires VK_EXT_provoking_vertex with provokingVertexLast enabled"); +} + void ProvokingVertexRenderPassTests() { using namespace AgcDriver::Graphics; const auto first = DecodeState(makeState()); @@ -9240,6 +10029,8 @@ void ConservativeRasterizationTests() { geometry.context[0x2ce] = 3; geometry.context[0x29b] = 2; geometry.context[0x2ab] = 4; + geometry.context[0x280] = 0x00080008u; // PA_SU_POINT_SIZE and PA_SU_POINT_MINMAX: a point-output geometry program decodes them + geometry.context[0x281] = 0x00080008u; geometry.shader[0x8a] = 3u << 29u; geometry.shader[0x8b] = 3u << 16u; for (const auto input : {1u, 2u, 4u}) { @@ -9446,6 +10237,332 @@ void unorm10_11_11TargetTests() { expectFailure([&] { static_cast(ShaderRecompiler::Recompile(uintExport)); }, "fragment output 0 has no color export packing path"); } +void unorm4_4_4_4TargetTests() { + using ShaderRecompiler::ColorExportPacking; + auto queue = makeState(); + queue.context[0x31c] = (queue.context.at(0x31c) & ~((0x1fu << 2u) | (7u << 8u) | (3u << 11u))) | (19u << 2u) | (2u << 11u); + queue.context[0x1b3] = 2; + queue.context[0x1b4] = 2; + const auto state = AgcDriver::Graphics::DecodeState(queue); + Require(state.color.format == VK_FORMAT_R16_UINT && state.color.elementBytes == 2 && state.color.componentMapping == 0x1bu && state.color.packing == ColorExportPacking::Unorm4_4_4_4, "a 4_4_4_4 unorm target did not decode to an R16_UINT attachment with reversed exports"); + const auto packings = AgcDriver::Graphics::ExportPackings(state); + Require(packings[0] == ColorExportPacking::Unorm4_4_4_4 && std::all_of(packings.begin() + 1, packings.end(), [](ColorExportPacking packing) { return packing == ColorExportPacking::None; }), "export 0 did not carry the 4_4_4_4 unorm packing"); + Require(state.color.partialWriteMask == 0u && state.blends[0].colorWriteMask == 0xfu && AgcDriver::Graphics::ExportTargetMerge(state).mask == 0u, "a full write of a 4_4_4_4 unorm target merged into the target"); + auto changed = queue; + for (const auto& [targetMask, wordMask] : {std::pair{1u, 0xf000u}, std::pair{7u, 0xfff0u}, std::pair{8u, 0x000fu}, std::pair{0xau, 0x0f0fu}}) { + changed.context[0x8e] = targetMask; + Require(AgcDriver::Graphics::DrawRejection(changed, false).empty(), "the draw precheck refused CB_TARGET_MASK " + std::to_string(targetMask) + " of a 4_4_4_4 unorm target"); + const auto partial = AgcDriver::Graphics::DecodeState(changed); + Require(partial.color.partialWriteMask == wordMask && partial.colors.front().partialWriteMask == wordMask && partial.blends[0].colorWriteMask == 0u && partial.blend.colorWriteMask == 0u, "CB_TARGET_MASK " + std::to_string(targetMask) + " of a 4_4_4_4 unorm target did not merge its nibbles behind a closed write mask"); + const auto merge = AgcDriver::Graphics::ExportTargetMerge(partial); + const auto expected = AgcDriver::Graphics::SurfaceForTarget(partial.color); + const auto surface = AgcDriver::Graphics::DecodeTextureResource(merge.descriptor); + Require(merge.target == 0u && merge.mask == wordMask && merge.samples == 1u && !merge.depthStencilWrites && surface.baseAddress == expected.baseAddress && surface.pipeBankXor == expected.pipeBankXor && surface.width == expected.width && surface.height == expected.height && surface.format == expected.format && surface.tileMode == expected.tileMode && surface.dimension == AgcDriver::Graphics::TextureDimension::k2D && surface.mipCount == 1u && surface.samples == 1u, "the merge descriptor of CB_TARGET_MASK " + std::to_string(targetMask) + " does not decode to the target's surface"); + } + { + auto depthQueue = queue; + depthQueue.context[0x8e] = 1; + depthQueue.context[0x000] = 0; + depthQueue.context[0x002] = 0; + depthQueue.context[0x007] = (1u << 16u) | 3u; + depthQueue.context[0x00a] = 7; + depthQueue.context[0x00b] = 0; + depthQueue.context[0x010] = 0x22900983; + depthQueue.context[0x011] = 0x20000181; + for (const auto offset : {0x012u, 0x014u}) depthQueue.context[offset] = 0x100; + for (const auto offset : {0x013u, 0x015u}) depthQueue.context[offset] = 0x200; + depthQueue.context[0x005] = 0x300; + depthQueue.context[0x01e] = 0x1; + depthQueue.context[0x008] = std::bit_cast(0.25f); + depthQueue.context[0x009] = std::bit_cast(0.75f); + struct DepthStencilCase { + std::uint32_t control; + std::uint32_t ops; + std::uint32_t masks; + bool writes; + }; + for (const auto& [control, ops, masks, writes] : {DepthStencilCase{0x00700710u, 0x00050050u, 0x01ffff01u, false}, DepthStencilCase{0x00700712u, 0x00050050u, 0x01ffff01u, false}, DepthStencilCase{0x00700714u, 0x00050050u, 0x01ffff01u, false}, DepthStencilCase{0x00700716u, 0x00050050u, 0x01ffff01u, true}, DepthStencilCase{0x00700711u, 0x00050050u, 0x01ffff01u, true}, DepthStencilCase{0x00700711u, 0u, 0x01ffff01u, false}, DepthStencilCase{0x00700711u, 0x00050050u, 0x0100ff01u, false}, DepthStencilCase{0x00700713u, 0u, 0x01ffff01u, false}, DepthStencilCase{0x00700718u, 0x00050050u, 0x01ffff01u, false}}) { + depthQueue.context[0x200] = control; + depthQueue.context[0x10b] = ops; + depthQueue.context[0x10c] = depthQueue.context[0x10d] = masks; + const auto what = "DB_DEPTH_CONTROL " + std::to_string(control) + " with stencil operations " + std::to_string(ops) + " and masks " + std::to_string(masks); + Require(AgcDriver::Graphics::DrawRejection(depthQueue, false).empty(), "the draw precheck refused a partial 4_4_4_4 write under " + what); + const auto partial = AgcDriver::Graphics::DecodeState(depthQueue); + const auto merge = AgcDriver::Graphics::ExportTargetMerge(partial); + Require(partial.depth.has_value() == ((control & 0xbu) != 0u) && partial.depthBoundsTest == ((control & 8u) != 0u) && merge.mask == 0xf000u && AgcDriver::Graphics::DepthStencilWrites(partial) == writes && merge.depthStencilWrites == writes && merge.depthStencilTests == ((control & 0xbu) != 0u), "a partial 4_4_4_4 write under " + what + (writes ? " did not report" : " reported") + " depth or stencil writes"); + } + auto truncated = depthQueue; + truncated.context[0x200] = 0x00700712u; + truncated.context[0x010] = 0x22900987u; + truncated.context[0x2f8] = 0x00100001u; + truncated.context[0x292] = 3; + for (std::uint32_t pixel = 0; pixel < 4; ++pixel) truncated.context[0x2fe + pixel * 4u] = 0xcc44; + truncated.context[0x31d] = truncated.context.at(0x31d) & ~0x3f000u; + truncated.context[0x8e] = 0xf; + const auto full = AgcDriver::Graphics::DecodeState(truncated); + Require(full.samples == 2 && full.truncatedCoverage && !full.mergedCoverage && full.color.samples == 1 && full.color.partialWriteMask == 0u, "a full write of a one-sample 4_4_4_4 unorm target beside a 2x depth target did not decode as truncated coverage"); + truncated.context[0x8e] = 1; + expectFailure([&] { AgcDriver::Graphics::DecodeState(truncated); }, "under merged or truncated coverage"); + } + { + auto smoothed = queue; + smoothed.context[0x2f8] = 0x0020c002u; + smoothed.context[0x292] = 3; + smoothed.context[0x3b8] = 0x0dc00000u; + for (std::uint32_t pixel = 0; pixel < 4; ++pixel) smoothed.context[0x2fe + pixel * 4u] = 0xcc44; + smoothed.context[0x31d] = smoothed.context.at(0x31d) & ~0x3f000u; + smoothed.context[0x201] = 0x02130000u; + const auto merged = AgcDriver::Graphics::DecodeState(smoothed); + Require(merged.samples == 4 && merged.mergedCoverage && merged.color.samples == 1 && merged.color.partialWriteMask == 0u, "a full write of a one-sample 4_4_4_4 unorm target under OVERRASTERIZATION_AMOUNT 2 did not decode as merged coverage"); + smoothed.context[0x8e] = 1; + expectFailure([&] { AgcDriver::Graphics::DecodeState(smoothed); }, "under merged or truncated coverage"); + } + { + changed.context[0x8e] = 1; + const auto partial = AgcDriver::Graphics::DecodeState(changed); + auto killQueue = changed; + killQueue.context[0x203] = 0x40u; + const auto killState = AgcDriver::Graphics::DecodeState(killQueue); + killQueue.context[0x203] = 0x10u; + const auto earlyState = AgcDriver::Graphics::DecodeState(killQueue); + Require(killState.pixelKill && !killState.pixelEarlyZ && !earlyState.pixelKill && earlyState.pixelEarlyZ, "DB_SHADER_CONTROL.KILL_ENABLE of a 4_4_4_4 merge did not decode as a kill under late tests"); + constexpr std::array mergeCode{0xf800180fu, 0x07060504u, 0xbf810000u}; + auto merging = packedExportRequest(changed, partial, mergeCode); + merging.context.pixel->targetMerge = AgcDriver::Graphics::ExportTargetMerge(partial); + const std::array capabilities{spv::CapabilityShader, spv::CapabilityFragmentShaderPixelInterlockEXT, spv::CapabilityStorageImageReadWithoutFormat}; + merging.target.supportedCapabilities = capabilities; + merging.target.registrationWordLimit = 1u << 20u; + const ShaderRecompiler::RequestSerializer mergeSerializer; + Require(mergeSerializer.Deserialize(mergeSerializer.Serialize(merging)).request.context.pixel->targetMerge == merging.context.pixel->targetMerge, "the 4_4_4_4 target merge was lost in serialization"); + auto unmerged = merging; + unmerged.context.pixel->targetMerge = {}; + auto moved = merging; + moved.context.pixel->targetMerge.descriptor[0] += 0x100u; + std::vector mergedKey, unmergedKey, movedKey; + ShaderRecompiler::RecompileCacheKey::BuildInterface(merging, mergedKey); + ShaderRecompiler::RecompileCacheKey::BuildInterface(unmerged, unmergedKey); + ShaderRecompiler::RecompileCacheKey::BuildInterface(moved, movedKey); + Require(mergedKey != unmergedKey && mergedKey == movedKey, "the static shader key does not separate merging from plain writes, or depends on the merged target's address"); + for (const bool registration : {false, true}) { + merging.target.registrationModules = registration; + const auto merged = ShaderRecompiler::Recompile(merging); + const auto words = merged.spirv.Copy(); + std::size_t reads = 0, writes = 0; + for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) { + reads += static_cast(words[at] & 0xffffu) == spv::OpImageRead ? 1u : 0u; + writes += static_cast(words[at] & 0xffffu) == spv::OpImageWrite ? 1u : 0u; + } + const auto what = std::string(registration ? "a registration" : "a specialized") + " 4_4_4_4 merge module"; + Require(merged.registrationModule == registration && reads == 1u && writes == 1u && interlockShape(words) == "capability extension ordered begin end " && declaresEarlyFragmentTests(merging), what + " did not read and write the target once inside a pixel interlock after early tests: " + interlockShape(words)); + Require(coherentImageRead(words), what + " does not read the target through a Coherent image variable"); + const auto bound = std::ranges::any_of(merged.bindings, [&](const ShaderRecompiler::DescriptorBinding& binding) { + if (binding.role != ShaderRecompiler::DescriptorRole::GuestImages || binding.kind != ShaderRecompiler::DescriptorKind::StorageImage) return false; + for (std::size_t offset = 0; offset + 8u <= binding.guestDescriptor.size(); offset += 8u) { + if (std::equal(merging.context.pixel->targetMerge.descriptor.begin(), merging.context.pixel->targetMerge.descriptor.end(), binding.guestDescriptor.begin() + static_cast(offset))) return true; + } + return false; + }); + Require(bound, what + " does not bind the merged target as a storage image"); + ShaderRecompiler::RecompileResult vertex; + vertex.spirv = makeModule({}); + const VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}; + const std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &merged, 0}}}; + Require(AgcDriver::Graphics::ValidateShaders(shaders, partial, subgroup, false) == std::set{0u}, what + " lost its export to attachment 0"); + } + auto lacking = merging; + const std::array noInterlock{spv::CapabilityShader, spv::CapabilityStorageImageReadWithoutFormat}; + lacking.target.supportedCapabilities = noInterlock; + expectFailure([&] { static_cast(ShaderRecompiler::Recompile(lacking)); }, "fragmentShaderPixelInterlock"); + auto unformatted = merging; + const std::array noFormatlessReads{spv::CapabilityShader, spv::CapabilityFragmentShaderPixelInterlockEXT}; + unformatted.target.supportedCapabilities = noFormatlessReads; + expectFailure([&] { static_cast(ShaderRecompiler::Recompile(unformatted)); }, "storage image reads without a format"); + auto killing = merging; + killing.context.pixel->pixelKillEnable = true; + killing.context.pixel->earlyZ = false; + for (const bool registration : {false, true}) { + killing.target.registrationModules = registration; + const auto words = ShaderRecompiler::Recompile(killing).spirv.Copy(); + std::size_t reads = 0, writes = 0; + for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) { + reads += static_cast(words[at] & 0xffffu) == spv::OpImageRead ? 1u : 0u; + writes += static_cast(words[at] & 0xffffu) == spv::OpImageWrite ? 1u : 0u; + } + Require(reads == 1u && writes == 1u && interlockShape(words) == "capability extension ordered begin end " && declaresEarlyFragmentTests(killing), "a killing 4_4_4_4 merge module under late tests of a draw without depth or stencil writes did not merge inside a pixel interlock after early tests"); + } + killing.context.pixel->targetMerge.depthStencilWrites = true; + expectFailure([&] { static_cast(ShaderRecompiler::Recompile(killing)); }, "kills pixels under late tests of a draw writing depth or stencil"); + auto earlyKilling = killing; + earlyKilling.context.pixel->earlyZ = true; + static_cast(ShaderRecompiler::Recompile(earlyKilling)); + auto exporting = merging; + exporting.context.pixel->depthExportEnable = true; + expectFailure([&] { static_cast(ShaderRecompiler::Recompile(exporting)); }, "exports depth"); + alignas(16) static std::array counted{}; + static const std::array counter{static_cast(reinterpret_cast(counted.data())), static_cast(reinterpret_cast(counted.data()) >> 32u) & 0xffffu, 16u, 0x31016facu}; + constexpr std::array countingCode{0x7e000281u, 0xe0c80000u, 0x80000000u, 0xf800180fu, 0x07060504u, 0xbf810000u}; + auto counting = packedExportRequest(changed, partial, countingCode); + counting.context.userData = counter; + counting.context.pixel->targetMerge = merging.context.pixel->targetMerge; + counting.target = merging.target; + counting.target.registrationModules = false; + counting.target.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version; + static const std::array atomicCapabilities{spv::CapabilityShader, spv::CapabilityFragmentShaderPixelInterlockEXT, spv::CapabilityStorageImageReadWithoutFormat, spv::CapabilityInt64}; + counting.target.supportedCapabilities = atomicCapabilities; + counting.context.pixel->earlyZ = false; + static_cast(ShaderRecompiler::Recompile(counting)); + counting.context.pixel->targetMerge.depthStencilTests = true; + expectFailure([&] { static_cast(ShaderRecompiler::Recompile(counting)); }, "writes guest memory (stores, atomics or GDS) under late tests"); + counting.context.pixel->earlyZ = true; + const auto earlyWords = ShaderRecompiler::Recompile(counting).spirv.Copy(); + std::size_t atomics = 0; + for (std::size_t at = 5; at < earlyWords.size() && (earlyWords[at] >> 16u) != 0; at += earlyWords[at] >> 16u) atomics += static_cast(earlyWords[at] & 0xffffu) == spv::OpAtomicIAdd ? 1u : 0u; + Require(atomics == 1u && interlockShape(earlyWords) == "capability extension ordered begin end " && declaresEarlyFragmentTests(counting), "a 4_4_4_4 merge whose shader adds to a counter under early tests did not keep its one atomic add beside the merge"); + constexpr std::array gdsReadCode{0xbefc03ffu, 0x00000100u, 0x7e000280u, 0xd8da0000u, 0x04000000u, 0xbf8c0000u, 0xf800180fu, 0x07060504u, 0xbf810000u}; + auto gdsReading = packedExportRequest(changed, partial, gdsReadCode); + gdsReading.context.pixel->targetMerge = counting.context.pixel->targetMerge; + gdsReading.context.pixel->earlyZ = false; + gdsReading.target = counting.target; + static_cast(ShaderRecompiler::Recompile(gdsReading)); + constexpr std::array gdsAddCode{0xbefc03ffu, 0x00000100u, 0x7e000280u, 0xd8020000u, 0x00000000u, 0xbf8c0000u, 0xf800180fu, 0x07060504u, 0xbf810000u}; + auto gdsAdding = gdsReading; + gdsAdding.shader.code = gdsAddCode; + expectFailure([&] { static_cast(ShaderRecompiler::Recompile(gdsAdding)); }, "writes guest memory (stores, atomics or GDS) under late tests"); + auto lateMerge = counting; + lateMerge.context.pixel->earlyZ = false; + lateMerge.context.pixel->targetMerge = {}; + static_cast(ShaderRecompiler::Recompile(lateMerge)); + } + changed = queue; + changed.context[0x8e] = 1u; + changed.context[0x31d] = (changed.context.at(0x31d) & ~0x3f000u) | (1u << 12u) | (1u << 15u); + { + const auto multisampled = AgcDriver::Graphics::DecodeState(changed); + Require(multisampled.samples == 2u && multisampled.color.samples == 2u && multisampled.color.partialWriteMask == 0xf000u && multisampled.blends[0].colorWriteMask == 0u && multisampled.perSampleShading, "a partial write of a 2-sample 4_4_4_4 unorm target did not merge per sample behind a closed write mask"); + auto coverage = changed; + coverage.context[0x1b3] = 0x4002u; + coverage.context[0x1b4] = 0x4002u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(coverage); }, "SAMPLE_COVERAGE in a multisampled pixel shader that runs per sample"); + coverage.context[0x8e] = 0xfu; + Require(!AgcDriver::Graphics::DecodeState(coverage).perSampleShading, "a full write of a 2-sample 4_4_4_4 unorm target with a SAMPLE_COVERAGE input was refused or ran per sample"); + struct InputCase { + std::uint32_t inputs; + std::uint32_t position; + const char* what; + }; + auto rasterized = changed; + rasterized.context[0x2f8] = 0x00100001u; + rasterized.context[0x292] = 3; + rasterized.context[0x3b8] = 0x0dc00000u; + for (std::uint32_t pixel = 0; pixel < 4; ++pixel) rasterized.context[0x2fe + pixel * 4u] = 0xcc44; + const auto inputState = [](AgcDriver::QueueState queue, std::uint32_t inputs, std::uint32_t position) { + queue.context[0x1b3] = inputs; + queue.context[0x1b4] = inputs; + queue.context[0x1b8] = position; + return queue; + }; + for (const auto& [inputs, position, what] : {InputCase{0x0004u, 0u, "PERSP_CENTROID"}, InputCase{0x0040u, 0u, "LINEAR_CENTROID"}, InputCase{0x0001u, 0u, "PERSP_SAMPLE"}, InputCase{0x0008u, 0u, "PERSP_PULL_MODEL"}, InputCase{0x2002u, 0u, "ANCILLARY"}, InputCase{0x0402u, 0u, "POS_Z_FLOAT"}, InputCase{0x0802u, 0u, "POS_W_FLOAT"}, InputCase{0x0302u, 0x20000u, "POS_X_FLOAT and POS_Y_FLOAT at a sample"}, InputCase{0x0102u, 0x10000u, "POS_X_FLOAT at the centroid"}}) { + auto moved = inputState(rasterized, inputs, position); + expectFailure([&] { AgcDriver::Graphics::DecodeState(moved); }, "cannot read the ancillary word or, unless every sample sits at the pixel centre"); + moved.context[0x293] = moved.context.at(0x293) | 0x10000u; + Require(AgcDriver::Graphics::DecodeState(moved).perSampleShading, std::string("a partial write of a 2-sample 4_4_4_4 unorm target whose shader the guest runs per sample was refused for reading ") + what); + if (inputs == 0x2002u) { + expectFailure([&] { AgcDriver::Graphics::DecodeState(inputState(changed, inputs, position)); }, "cannot read the ancillary word"); + continue; + } + Require(AgcDriver::Graphics::DecodeState(inputState(changed, inputs, position)).perSampleShading, std::string("a partial write of a 2-sample 4_4_4_4 unorm target raised from a one-sample rasterizer, every sample at the pixel centre, was refused for reading ") + what); + } + for (const auto& [inputs, position, what] : {InputCase{0x0302u, 0u, "POS_X_FLOAT and POS_Y_FLOAT at the centre"}, InputCase{0x0302u, 0x100000u, "POS_X_FLOAT and POS_Y_FLOAT at the upper-left corner"}, InputCase{0x9022u, 0u, "PERSP_CENTER, LINEAR_CENTER, FRONT_FACE and POS_FIXED_PT"}}) { + Require(AgcDriver::Graphics::DecodeState(inputState(rasterized, inputs, position)).perSampleShading, std::string("a partial write of a 2-sample 4_4_4_4 unorm target whose pixel-rate shader reads ") + what + " was refused"); + } + const auto merge = AgcDriver::Graphics::ExportTargetMerge(multisampled); + const auto surface = AgcDriver::Graphics::DecodeTextureResource(merge.descriptor); + Require(merge.samples == 2u && merge.mask == 0xf000u && surface.dimension == AgcDriver::Graphics::TextureDimension::k2DMsaa && surface.samples == 2u && surface.baseAddress == multisampled.color.surfaceAddress && surface.width == multisampled.color.extent.width && surface.height == multisampled.color.extent.height && surface.format == AgcDriver::Graphics::TargetMergeSurface(multisampled.color).format, "the merge descriptor of a 2-sample 4_4_4_4 unorm target does not decode to its multisampled surface"); + auto unshaded = multisampled; + unshaded.perSampleShading = false; + expectFailure([&] { static_cast(AgcDriver::Graphics::ExportTargetMerge(unshaded)); }, "needs per-sample shading"); + constexpr std::array mergeCode{0xf800180fu, 0x07060504u, 0xbf810000u}; + auto merging = packedExportRequest(changed, multisampled, mergeCode); + merging.context.pixel->targetMerge = merge; + const std::array capabilities{spv::CapabilityShader, spv::CapabilityFragmentShaderPixelInterlockEXT, spv::CapabilityStorageImageReadWithoutFormat, spv::CapabilityStorageImageMultisample, spv::CapabilityInterpolationFunction}; + merging.target.supportedCapabilities = capabilities; + merging.target.fragmentShaderBarycentricEnabled = true; + const auto words = ShaderRecompiler::Recompile(merging).spirv.Copy(); + std::size_t sampled = 0; + bool sampleRate = 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::OpCapability && words[at + 1] == spv::CapabilitySampleRateShading) sampleRate = true; + if (op == spv::OpImageRead && (words[at] >> 16u) == 7u && words[at + 5] == spv::ImageOperandsSampleMask) ++sampled; + if (op == spv::OpImageWrite && (words[at] >> 16u) == 6u && words[at + 4] == spv::ImageOperandsSampleMask) ++sampled; + } + Require(sampleRate && sampled == 2u && interlockShape(words) == "capability extension ordered begin end ", "a 2-sample 4_4_4_4 merge module does not read and write the shaded sample inside a pixel interlock"); + auto single = merging; + const std::array noMultisampleStorage{spv::CapabilityShader, spv::CapabilityFragmentShaderPixelInterlockEXT, spv::CapabilityStorageImageReadWithoutFormat, spv::CapabilityInterpolationFunction}; + single.target.supportedCapabilities = noMultisampleStorage; + expectFailure([&] { static_cast(ShaderRecompiler::Recompile(single)); }, "storage image multisampling"); + auto centred = merging; + centred.target.fragmentShaderBarycentricEnabled = false; + centred.context.pixel->sampleLocations = AgcDriver::Graphics::PixelSampleLocations(multisampled, *centred.context.pixel); + Require(std::ranges::all_of(centred.context.pixel->sampleLocations, [](std::uint8_t location) { return location == 0u; }), "a merge raised from a one-sample rasterizer did not keep every sample at the pixel centre"); + static_cast(ShaderRecompiler::Recompile(centred)); + const auto rasterizedState = AgcDriver::Graphics::DecodeState(rasterized); + auto offCentre = packedExportRequest(rasterized, rasterizedState, mergeCode); + offCentre.context.pixel->targetMerge = AgcDriver::Graphics::ExportTargetMerge(rasterizedState); + offCentre.context.pixel->iteratesSamples = rasterizedState.perSampleShading; + offCentre.context.pixel->sampleLocations = AgcDriver::Graphics::PixelSampleLocations(rasterizedState, *offCentre.context.pixel); + Require(std::ranges::any_of(offCentre.context.pixel->sampleLocations, [](std::uint8_t location) { return location != 0u; }), "a 2-sample rasterizer's merge did not pass its off-centre sample locations"); + offCentre.target.supportedCapabilities = capabilities; + offCentre.target.fragmentShaderBarycentricEnabled = false; + expectFailure([&] { static_cast(ShaderRecompiler::Recompile(offCentre)); }, "its centre-located barycentrics would follow the sample"); + offCentre.target.fragmentShaderBarycentricEnabled = true; + static_cast(ShaderRecompiler::Recompile(offCentre)); + alignas(16) static std::array counted{}; + static const std::array counter{static_cast(reinterpret_cast(counted.data())), static_cast(reinterpret_cast(counted.data()) >> 32u) & 0xffffu, 16u, 0x31016facu}; + constexpr std::array countingCode{0x7e000281u, 0xe0c80000u, 0x80000000u, 0xf800180fu, 0x07060504u, 0xbf810000u}; + auto counting = packedExportRequest(changed, multisampled, countingCode); + counting.context.userData = counter; + counting.context.pixel->targetMerge = merge; + counting.context.pixel->iteratesSamples = true; + counting.context.pixel->earlyZ = true; + counting.target = merging.target; + counting.target.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version; + static const std::array atomicCapabilities{spv::CapabilityShader, spv::CapabilityFragmentShaderPixelInterlockEXT, spv::CapabilityStorageImageReadWithoutFormat, spv::CapabilityStorageImageMultisample, spv::CapabilityInterpolationFunction, spv::CapabilityInt64}; + counting.target.supportedCapabilities = atomicCapabilities; + expectFailure([&] { static_cast(ShaderRecompiler::Recompile(counting)); }, "each covered sample would repeat its writes"); + auto unmergedCounting = counting; + unmergedCounting.context.pixel->targetMerge = {}; + static_cast(ShaderRecompiler::Recompile(unmergedCounting)); + } + for (const auto& [number, swap] : {std::pair{1u, 2u}, std::pair{4u, 2u}, std::pair{0u, 0u}, std::pair{0u, 1u}, std::pair{0u, 3u}}) { + changed = queue; + changed.context[0x31c] = (changed.context[0x31c] & ~((7u << 8u) | (3u << 11u))) | (number << 8u) | (swap << 11u); + expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "unsupported color format 19 number type " + std::to_string(number) + " component swap " + std::to_string(swap)); + } + changed = queue; + changed.context[0x31c] |= 0x10000000u; + changed.context[0x325] = 0x1234; + expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "DCC-compressed 4_4_4_4 unorm color targets"); + changed = queue; + changed.context[0x31c] |= 0x40000u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "truncating (ROUND_MODE) 4_4_4_4 unorm color targets"); + + constexpr std::array code{0xf800180fu, 0x07060504u, 0xbf810000u}; + const auto request = packedExportRequest(queue, state, code); + auto plain = request; + plain.context.pixel->targetExportPacking.fill(ColorExportPacking::None); + const ShaderRecompiler::RequestSerializer serializer; + Require(serializer.Deserialize(serializer.Serialize(request)).request.context.pixel->targetExportPacking == request.context.pixel->targetExportPacking, "the 4_4_4_4 unorm packing was lost in serialization"); + const auto packed = ShaderRecompiler::Recompile(request); + const auto unpacked = ShaderRecompiler::Recompile(plain); + Require(packed.PipelineVariantId() != unpacked.PipelineVariantId() && !(packed.spirv == unpacked.spirv), "the 4_4_4_4 export packing did not specialize the module"); + ShaderRecompiler::RecompileResult vertex; + vertex.spirv = makeModule({}); + const VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}; + const std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &packed, 0}}}; + Require(AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false) == std::set{0u}, "the 4_4_4_4 packed export did not reach attachment 0"); +} + void float11_11_10TargetTests() { using ShaderRecompiler::ColorExportPacking; auto queue = makeState(); @@ -9460,7 +10577,12 @@ void float11_11_10TargetTests() { auto changed = queue; changed.context[0x31c] &= ~0x40000u; expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "rounding (ROUND_MODE 0) 11_11_10 float color targets"); - for (const auto swap : {0u, 1u, 3u}) { + changed = queue; + changed.context[0x31c] &= ~0x1800u; + const auto standard = AgcDriver::Graphics::DecodeState(changed); + Require(standard.color.format == VK_FORMAT_R32_UINT && standard.color.componentMapping == 0xe4u && standard.color.packing == ColorExportPacking::Float11_11_10, "an 11_11_10 float target with the standard component order did not decode"); + Require(AgcDriver::Graphics::ExportMappings(standard)[0] == 0xe4u, "export 0 reordered its components into the standard-order 11_11_10 float target"); + for (const auto swap : {1u, 3u}) { changed = queue; changed.context[0x31c] = (changed.context[0x31c] & ~0x1800u) | (swap << 11u); expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "unsupported color format 7 number type 7 component swap " + std::to_string(swap)); @@ -9507,7 +10629,7 @@ void float11_11_10TargetTests() { const std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &packed, 0}}}; Require(AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false) == std::set{0u}, "the packed float export did not reach attachment 0"); auto invalid = request; - invalid.context.pixel->targetExportPacking[0] = static_cast(3); + invalid.context.pixel->targetExportPacking[0] = static_cast(4); expectFailure([&] { static_cast(ShaderRecompiler::Recompile(invalid)); }, "invalid prepared fragment export packing"); } @@ -9850,26 +10972,63 @@ void validationTests() { attribute.resource.fields[3] = 77u << 12u; attribute.fetchIndex = 1; attribute.resource.fields[1] = 0; - attribute.resource.fields[2] = 16; + const auto zeroStride = [&](std::uint32_t select, std::uint32_t records) { + attribute.resource.fields[3] = (77u << 12u) | (select << 28u); + attribute.resource.fields[2] = records; + }; + for (const auto records : {0u, 1u, 16u, 0xffffffffu}) { + zeroStride(0, records); + Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 0 must read every zero-stride fetch as zero"); + Require(AgcDriver::Graphics::ZeroStrideAvailableBytes(attribute) == 0, "OOB_SELECT 0 zero-stride fetch has bytes to read"); + expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1); }, "byte range"); + expectFailure([&] { AgcDriver::Graphics::VertexBufferExtent(attribute); }, "empty or oversized"); + } + zeroStride(1, 16); Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2) == 16, "zero stride must repeat one value"); Require(AgcDriver::Graphics::VertexBufferReadOffset(attribute, 100, 7) == 0, "zero-stride input skipped its repeated value"); - attribute.resource.fields[2] = 8; - expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1); }, "byte range"); - Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "a structured zero-stride fetch was treated as out of range"); - attribute.resource.fields[3] = (77u << 12u) | (2u << 28u); - attribute.resource.fields[2] = 1; + Require(AgcDriver::Graphics::VertexBufferExtent(attribute) == 16, "OOB_SELECT 1 extent must cover the whole fetch"); + zeroStride(1, 0); + Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 1 without records must read zero"); + zeroStride(1, 8); + Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 1 counted NUM_RECORDS in bytes"); + Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 8) == 16, "OOB_SELECT 1 must read the whole element for every index below NUM_RECORDS"); + Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1, 7) == 16, "OOB_SELECT 1 must read the whole element for the last index below NUM_RECORDS"); + expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 9); }, "record count"); + expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1, 8); }, "record count"); + zeroStride(1, 1); + Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1) == 16, "OOB_SELECT 1 with one record must read the element at index 0"); + expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 2); }, "record count"); + attribute.fetchIndex = 0; + zeroStride(1, 3); + Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 2, 1) == 16, "OOB_SELECT 1 must read the element for index 2 of 3 records"); + expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 3, 1); }, "record count"); + attribute.fetchIndex = 1; + zeroStride(2, 1); Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2) == 16, "OOB_SELECT 2 must read a zero-stride fetch whole while NUM_RECORDS is nonzero"); Require(AgcDriver::Graphics::VertexBufferExtent(attribute) == 16, "OOB_SELECT 2 extent must cover the whole fetch"); Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 2 with records was treated as out of range"); - attribute.resource.fields[2] = 0; + zeroStride(2, 0); Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 2 without records must read zero"); - attribute.resource.fields[3] = (77u << 12u) | (3u << 28u); + zeroStride(3, 0); Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 without records must read zero"); - attribute.resource.fields[2] = 8; - Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 with records was treated as out of range"); - expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1); }, "byte range"); - attribute.resource.fields[2] = 16; - Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1) == 16, "OOB_SELECT 3 must read a fetch inside the range"); + for (const auto records : {1u, 8u, 15u}) { + zeroStride(3, records); + Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 must read a fetch that only partly fits as zero"); + expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1); }, "byte range"); + } + for (const auto records : {16u, 17u, 0xffffffffu}) { + zeroStride(3, records); + Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 with the whole fetch in range was treated as out of range"); + Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2) == 16, "OOB_SELECT 3 must read a fetch inside the range, whatever the index"); + Require(AgcDriver::Graphics::VertexBufferExtent(attribute) == 16, "OOB_SELECT 3 extent must cover only the fetch"); + } + attribute.resource.fields[3] = (22u << 12u) | (3u << 28u); + attribute.resource.fields[2] = 4; + Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 compared NUM_RECORDS with the wrong fetch size"); + attribute.resource.fields[2] = 3; + Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 read a 4-byte fetch past 3 bytes"); + attribute.resource.fields[3] = (113u << 12u) | (3u << 28u); + expectFailure([&] { AgcDriver::Graphics::VertexFetchOutOfRange(attribute); }, "unsupported vertex format"); attribute.resource.fields[3] = 113u << 12u; expectFailure([&] { AgcDriver::Graphics::DecodeVertexFormat(attribute); }, "unsupported vertex format"); attribute.resource.fields[3] = 50u << 12u; @@ -9909,6 +11068,21 @@ void validationTests() { attribute.components = 2; attribute.resource.fields[1] |= 0x80000000u; expectFailure([&] { AgcDriver::Graphics::BuildVertexInputLayout(context, std::span(&attribute, 1)); }, "descriptor flags"); + attribute.resource.fields = {0x1000, 8u << 16u, 3, 71u << 12u}; + attribute.components = 3; + context.formatProperties = [](VkPhysicalDevice, VkFormat format, VkFormatProperties* properties) { + *properties = {}; + if (format == VK_FORMAT_R16G16B16A16_SFLOAT || format == VK_FORMAT_R8G8B8A8_UNORM) properties->bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT; + }; + Require(AgcDriver::Graphics::DecodeVertexFormat(attribute).format == VK_FORMAT_R16G16B16_SFLOAT && AgcDriver::Graphics::DecodeVertexFormat(attribute).bytes == 6, "a 3-component 16_16_16_16 float attribute was not decoded as 3 components"); + const auto widened = AgcDriver::Graphics::BuildVertexInputLayout(context, std::span(&attribute, 1)); + Require(widened.attributes[0].format == VK_FORMAT_R16G16B16A16_SFLOAT && widened.bindings[0].stride == 8 && AgcDriver::Graphics::VertexReadBytes(attribute, widened.attributes[0].format) == 8, "a 3-component 16_16_16_16 float attribute was not widened to the format the device supports"); + Require(AgcDriver::Graphics::VertexAttributeSignature(attribute) == "f32x3", "widening the vertex format changed the shader input"); + attribute.resource.fields = {0x1000, 4u << 16u, 3, 56u << 12u}; + Require(AgcDriver::Graphics::BuildVertexInputLayout(context, std::span(&attribute, 1)).attributes[0].format == VK_FORMAT_R8G8B8A8_UNORM, "a 3-component 8_8_8_8 unorm attribute was not widened to the format the device supports"); + attribute.resource.fields = {0x1000, 12u << 16u, 3, 74u << 12u}; + expectFailure([&] { AgcDriver::Graphics::BuildVertexInputLayout(context, std::span(&attribute, 1)); }, "device does not support vertex format 106"); + expectFailure([&] { AgcDriver::Graphics::VertexReadBytes(attribute, VK_FORMAT_R32G32B32A32_SFLOAT); }, "is not a form of"); } for (const auto capability : {spv::CapabilityInt64Atomics, spv::CapabilityInt64ImageEXT}) { ShaderRecompiler::RecompileResult vertex; @@ -10042,7 +11216,6 @@ void validationTests() { } } - } bool recompilesDebugBranch(std::uint32_t opcode) { @@ -10459,6 +11632,117 @@ void pipelineStoreLazyBufferPoolTests() { Require(mock.live == 0, "lazy buffer pool pipeline tests leaked Vulkan objects"); } +// The pipeline store's bound without a GPU: 1536 configurations cycled through the default 1024 +// entries keep coming back after their eviction, so the bound doubles and a later pass finds every +// pipeline resident; a bound set with APS5_PIPELINE_STORE_ENTRIES never moves. +void pipelineStoreGrowthTests(bool fixed) { + 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 = 301; + fragment.variantId = 302; + const std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, PipelinePushSlotBytes}}}; + std::array mainKey{41}, fragmentKey{42}; + const PipelineSetLayouts resources{makeHandle(), makeHandle(), true, mainKey, fragmentKey}; + constexpr std::uint32_t configurations = 1536; + const auto pass = [&](std::uint32_t count) { + std::uint32_t built = 0; + for (std::uint32_t configuration = 0; configuration < count; ++configuration) { + VertexInputLayout input; + input.bindings.push_back({0, 4u * (configuration + 1u), VK_VERTEX_INPUT_RATE_VERTEX}); + bool created = false; + static_cast(CachedPipeline(context, State{}, input, resources, shaders, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, &created)); + built += created ? 1u : 0u; + } + return built; + }; + std::uint32_t lastBuilt = 0; + for (int round = 0; round < 6; ++round) lastBuilt = pass(configurations); + const auto grown = PipelineStoreTotals(); + if (fixed) { + Require(grown.bound == 1024, "a bound set with APS5_PIPELINE_STORE_ENTRIES changed"); + Require(lastBuilt == configurations, "a fixed 1024-entry bound kept pipelines of a 1536-configuration cycle"); + } else { + Require(grown.bound == 2048, "the bound did not grow to 2048 while evicted pipelines kept coming back"); + Require(lastBuilt == 0, "a pass over 1536 configurations still rebuilt pipelines after the bound grew"); + } + ClearCachedPipelines(context.device); + Require(mock.live == 0, "pipeline store growth 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() { + using namespace AgcDriver::Graphics; + const auto root = std::filesystem::temp_directory_path() / ("aps5-pipeline-store-replay-" + std::to_string(std::random_device{}())); + std::filesystem::create_directories(root); + struct Cleanup { + std::filesystem::path directory; + ~Cleanup() { + ShaderRecompiler::ShaderDiskCache::Shutdown(); + std::error_code error; + std::filesystem::remove_all(directory, error); + } + } cleanup{root}; +#ifdef _WIN32 + _putenv_s("ANYPS5_NO_SHADER_CACHE", "0"); + _wputenv_s(L"ANYPS5_SHADER_CACHE_DIR", root.c_str()); + _putenv_s("APS5_PIPELINE_JOURNAL_THREADS", "1"); +#else + setenv("ANYPS5_NO_SHADER_CACHE", "0", 1); + setenv("ANYPS5_SHADER_CACHE_DIR", root.string().c_str(), 1); + setenv("APS5_PIPELINE_JOURNAL_THREADS", "1", 1); +#endif + Require(PipelineJournalEnabled(), "the journal is disabled with a shader cache directory set"); + 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}}}; + auto cache = std::make_unique(context); + context.descriptorCache = cache.get(); + const auto layouts = DescribeSetLayouts(context, shaders); + const auto resources = layouts.View(); + constexpr std::uint64_t fingerprint = 0x5eed'0000'0000'0002ull; + { + auto absent = StartPipelineJournalReplay(context, fingerprint, {}); + Require(absent != nullptr, "the first replay did not start"); + absent->Wait(); + } + constexpr std::uint32_t configurations = 1032; + for (std::uint32_t configuration = 0; configuration < configurations; ++configuration) { + VertexInputLayout input; + input.bindings.push_back({0, 4u * (configuration + 1u), VK_VERTEX_INPUT_RATE_VERTEX}); + static_cast(CachedPipeline(context, State{}, input, resources, shaders)); + } + FlushPipelineJournal(); + ClearCachedPipelines(context.device); + Require(PipelineStoreTotals().cached == 0, "clearing the device left pipelines in the store"); + auto replay = StartPipelineJournalReplay(context, fingerprint, {}); + Require(replay != nullptr, "the replay did not start"); + replay->Wait(); + const auto totals = PipelineStoreTotals(); + Require(totals.cached == configurations, "a replay of " + std::to_string(configurations) + " recorded pipelines kept " + std::to_string(totals.cached) + " in the store (bound " + std::to_string(totals.bound) + ")"); + ClearCachedPipelines(context.device); + context.descriptorCache = nullptr; + cache.reset(); + Require(mock.live == 0, "pipeline store replay growth tests leaked Vulkan objects"); +} + void pipelineJournalTests(bool longPath = false, bool extended = false) { using namespace AgcDriver::Graphics; using Stage = ShaderRecompiler::ShaderStage; @@ -10609,6 +11893,39 @@ void pipelineJournalTests(bool longPath = false, bool extended = false) { const PipelineSetLayouts decodedLayouts{layouts.layouts.main, layouts.layouts.fragment, decoded.splitSets, decoded.mainLayoutKey, decoded.fragmentLayoutKey}; BuildPipelineStoreKey(replayed, context, decoded.state, decoded.vertexInput, decodedLayouts, shaders, decoded.attachmentLayout); Require(!original.empty() && original == canonical && original == replayed, "the canonical or the decoded state keys another pipeline"); + auto fmaskState = state; + fmaskState.samples = 8; + fmaskState.color.samples = 8; + fmaskState.color.fmaskAddress = 0x30000; + fmaskState.color.fmaskPipeBankXor = 0x1200; + for (auto& target : fmaskState.colors) { + target.samples = 8; + target.fmaskAddress = 0x40000; + target.fmaskPipeBankXor = 0x2300; + } + const auto fmaskRecipe = MakeGraphicsPipelineRecipe(fmaskState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL); + Require(fmaskRecipe.state.color.fmaskAddress == 0 && fmaskRecipe.state.color.fmaskPipeBankXor == 0, "the canonical state kept the primary target's FMASK location"); + for (const auto& canonicalColor : fmaskRecipe.state.colors) { + Require(canonicalColor.fmaskAddress == 0 && canonicalColor.fmaskPipeBankXor == 0, "the canonical state kept an attachment's FMASK location"); + } + const auto fmaskBytes = EncodeGraphicsPipelineRecipe(fmaskRecipe); + GraphicsPipelineRecipe fmaskDecoded; + Require(DecodeGraphicsPipelineRecipe(fmaskBytes, fmaskDecoded) && EncodeGraphicsPipelineRecipe(fmaskDecoded) == fmaskBytes, "the FMASK pipeline recipe did not round-trip byte for byte"); + std::vector fmaskKey, fmaskReplayedKey; + BuildPipelineStoreKey(fmaskKey, context, fmaskState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL); + BuildPipelineStoreKey(fmaskReplayedKey, context, fmaskDecoded.state, fmaskDecoded.vertexInput, layouts.View(), shaders, fmaskDecoded.attachmentLayout); + Require(fmaskKey != original && fmaskKey == fmaskReplayedKey, "the decoded FMASK state keys another pipeline"); + auto relocatedFmask = fmaskState; + relocatedFmask.color.fmaskAddress += 0x10000; + relocatedFmask.color.fmaskPipeBankXor ^= 0x100; + for (auto& target : relocatedFmask.colors) { + target.fmaskAddress += 0x20000; + target.fmaskPipeBankXor ^= 0x200; + } + std::vector relocatedFmaskKey; + BuildPipelineStoreKey(relocatedFmaskKey, context, relocatedFmask, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL); + Require(relocatedFmaskKey == fmaskKey, "the FMASK resource location changed the pipeline key"); + Require(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(relocatedFmask, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)) == fmaskBytes, "the FMASK resource location changed the journal recipe"); auto gridState = state; gridState.samples = 2; gridState.customSampleLocations = true; @@ -10627,6 +11944,22 @@ void pipelineJournalTests(bool longPath = false, bool extended = false) { GraphicsPipelineRecipe lineDecoded; Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(lineState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), lineDecoded), "the line strip mesh recipe does not decode"); Require(lineDecoded.state.stages.mesh && lineDecoded.state.stages.mesh->outputPrimitiveSize == 2u && lineDecoded.state.stages.mesh->esgsItemSize == 4u, "the recipe dropped the mesh output primitive size"); + auto pointState = state; + pointState.vertexPoints = true; + pointState.pointSize = 4.0f; + pointState.pointSizeMin = 0.5f; + pointState.pointSizeMax = 8.0f; + GraphicsPipelineRecipe pointDecoded; + Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(pointState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), pointDecoded), "the point list recipe does not decode"); + Require(pointDecoded.state.vertexPoints && pointDecoded.state.pointSize == 4.0f && pointDecoded.state.pointSizeMin == 0.5f && pointDecoded.state.pointSizeMax == 8.0f, "the recipe dropped the vertex point list or its point sizes"); + auto meshPointState = pointState; + meshPointState.vertexPoints = false; + meshPointState.stages.mesh = *lineState.stages.mesh; + meshPointState.stages.mesh->outputPrimitiveSize = 1u; + meshPointState.stages.mesh->provokingVertex = 0u; + GraphicsPipelineRecipe meshPointDecoded; + Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(meshPointState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), meshPointDecoded), "the mesh point recipe does not decode"); + Require(!meshPointDecoded.state.vertexPoints && meshPointDecoded.state.stages.mesh && meshPointDecoded.state.stages.mesh->outputPrimitiveSize == 1u && meshPointDecoded.state.pointSize == 4.0f && meshPointDecoded.state.pointSizeMin == 0.5f && meshPointDecoded.state.pointSizeMax == 8.0f, "the recipe dropped mesh points or their sizes without the vertex point flag"); auto fourSampleState = state; fourSampleState.samples = 4; auto maskState = fourSampleState; @@ -10650,6 +11983,15 @@ void pipelineJournalTests(bool longPath = false, bool extended = false) { BuildPipelineStoreKey(mergedKey, context, mergedState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL); BuildPipelineStoreKey(mergedReplayed, context, mergedDecoded.state, mergedDecoded.vertexInput, layouts.View(), shaders, mergedDecoded.attachmentLayout); Require(mergedKey != fourSamples && mergedKey == mergedReplayed, "the decoded merged coverage state keys another pipeline"); + auto partialState = state; + partialState.colors[0].partialWriteMask = 0xf000u; + GraphicsPipelineRecipe partialDecoded; + Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(partialState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), partialDecoded), "the partial write recipe does not decode"); + Require(partialDecoded.state.colors.size() == partialState.colors.size() && partialDecoded.state.colors[0].partialWriteMask == 0xf000u, "the recipe dropped the merged target's partial write mask"); + std::vector partialKey, partialReplayed; + BuildPipelineStoreKey(partialKey, context, partialState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL); + BuildPipelineStoreKey(partialReplayed, context, partialDecoded.state, partialDecoded.vertexInput, layouts.View(), shaders, partialDecoded.attachmentLayout); + Require(partialKey != original && partialKey == partialReplayed, "the decoded partial write state keys another pipeline"); auto movedState = state; movedState.viewport.x = 5; movedState.colors[0].address = 0x9000; @@ -10832,6 +12174,16 @@ int main(int argc, char** argv) { std::cout << "pipeline library clear waits for active part builds\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--pipeline-store-replay-growth") { + pipelineStoreReplayGrowthTests(); + std::cout << "a replayed journal longer than the store bound stays resident\n"; + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--pipeline-store-growth") { + pipelineStoreGrowthTests(std::getenv("APS5_PIPELINE_STORE_ENTRIES") != nullptr); + 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-lazy-buffer-pool") { pipelineStoreLazyBufferPoolTests(); std::cout << "cached pipelines survive lazy buffer pool creation\n"; @@ -10902,6 +12254,23 @@ int main(int argc, char** argv) { Require(mock.live == 0, "the sparse heap mirror unwatch test leaked Vulkan objects"); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--sparse-heap-mirror-budget") { + { + mock = MockVulkan{}; + auto context = mockContext(); + context.bufferDeviceAddress = true; + context.limits.maxStorageBufferRange = 1u << 27; + RunSparseHeapMirrorBudgetTests(context, { + [](VkBuffer buffer) -> std::span { return mockBufferMemory(buffer); }, + {}, + {}, + {}, + {} + }); + } + Require(mock.live == 0, "the sparse heap mirror budget test leaked Vulkan objects"); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--heap-mirror-run-unwatch") { { mock = MockVulkan{}; @@ -10923,6 +12292,32 @@ int main(int argc, char** argv) { Require(mock.live == 0, "the heap mirror run unwatch test leaked Vulkan objects"); return 0; } + if (argc == 2 && (std::string_view(argv[1]) == "--address-copy-cap" || std::string_view(argv[1]) == "--address-copy-cap-remembered")) { +#ifdef _WIN32 + _putenv_s("APS5_ADDRESS_COPY_MAX_MIB", "1"); +#else + setenv("APS5_ADDRESS_COPY_MAX_MIB", "1", 1); +#endif + { + mock = MockVulkan{}; + auto context = mockContext(); + context.bufferDeviceAddress = true; + context.limits.maxStorageBufferRange = 1u << 27; + RunAddressCopyCapTests(context, { + [](VkBuffer buffer) -> std::span { return mockBufferMemory(buffer); }, + {}, + [](VkDeviceAddress address) { + const auto offset = address - 0x100000000000ULL; + const auto buffer = reinterpret_cast(offset / 0x10000); + return mockBufferMemory(buffer).subspan(offset % 0x10000); + }, + {}, + {} + }, std::string_view(argv[1]) == "--address-copy-cap-remembered"); + } + 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") { addressSpaceTeardownTests(); std::cout << "address-space device teardown releases cached guest allocations\n"; @@ -10992,6 +12387,12 @@ int main(int argc, char** argv) { std::cerr << "descriptor pool reset failure did not terminate\n"; return 1; } + if (argc == 2 && std::string_view(argv[1]) == "--unobserved-provoking-vertex") { + UnobservedProvokingVertexTests(); + ProvokingVertexTests(); + std::cout << "Unobserved provoking vertex checks passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--covered-centroid-state") { multisampleTests(); std::cout << "Covered-centroid and multisample state checks passed\n"; @@ -11044,6 +12445,7 @@ int main(int argc, char** argv) { retainedPixelStageTests(); pipelineLibrarySetTests(); pipelineLibraryClearWaitsForBuildTests(); + tessellationPipelineLibraryCacheTests(); framebufferOwnerTests(); hdrComponentSwapTests(); depthMaintenanceTests(); @@ -11051,6 +12453,7 @@ int main(int argc, char** argv) { srgb8TargetTests(); DepthClipTests(); DepthRemapTests(); + DepthBoundsEmulationTests(); UnusedViewportDepthTests(); DepthStencilTests(); OneDimensionalColorTests(); @@ -11060,12 +12463,14 @@ int main(int argc, char** argv) { ZExportTests(); unorm10_11_11TargetTests(); float11_11_10TargetTests(); + unorm4_4_4_4TargetTests(); DepthBoundsBiasTests(); conservativeZExportTests(); earlyFragmentTestsTests(); orderedPixelShaderTests(); stencilRefExportTests(); stencilReferenceUnusedTests(); + stencilLogicOperationTests(); aliasedBufferViewTests(); LineStripTests(); ShaderModuleKeyTests(); @@ -11101,6 +12506,7 @@ int main(int argc, char** argv) { UnwrittenSampleLocationTests(); sampleLocationGridFallbackTests(); sampleOrderTests(); + UnwrittenDepthPlaneTests(); depthCopyFormatTests(); quadPixelMaskTests(); ShaderStageTests(); @@ -11108,6 +12514,7 @@ int main(int argc, char** argv) { NggVertexWorkgroupTests(); inactiveVertexPointSizeTests(); PolygonPrimitiveTests(); + vertexPointListTests(); TuningFieldTests(); PixelInputLayoutTests(); UnexportedPixelInputTests(); @@ -11133,6 +12540,7 @@ int main(int argc, char** argv) { samplerTableDeviceLimitTests(); tableUpdateAfterBindTests(); misalignedShaderDataTests(); + imageTableTemplateRefreshTests(); debugBranchTests(); hostImportBudgetTests(); textureCacheBudgetTests(); @@ -11192,6 +12600,7 @@ int main(int argc, char** argv) { RunGuestTextureResourceTests(); RunDepthSurfaceReuseTests(); RunGuestSamplerResourceTests(); + RunLastRecordIndicesTests(); mock = MockVulkan{}; auto textureDetilerContext = mockContext(); textureDetilerContext.limits.minStorageBufferOffsetAlignment = 16; diff --git a/core/libs/prx/libSceAgcDriver/tests/GraphicsTests.hpp b/core/libs/prx/libSceAgcDriver/tests/GraphicsTests.hpp index 28a442b75d..95dcc30770 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GraphicsTests.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/GraphicsTests.hpp @@ -36,6 +36,7 @@ void RunTextureTilingTests(); void RunGuestTextureResourceTests(); void RunDepthSurfaceReuseTests(); void RunGuestSamplerResourceTests(); +void RunLastRecordIndicesTests(); void RunTextureDetilerTests(const AgcDriver::Graphics::Context& context, const TextureDetilerTestAccess& access); #endif diff --git a/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp index e20b23d8d5..452bac82c4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp @@ -32,7 +32,7 @@ struct LoadCase { std::uint32_t components = 1; }; -std::vector emitLoad(const LoadCase& test, bool active, std::uint32_t offset, std::uint32_t records, bool robust) { +std::vector emitLoad(const LoadCase& test, bool active, std::uint32_t offset, std::uint32_t records, bool robust, bool valid = true) { IrProgram program; program.SetWaveSize(32); program.Resources().stage = IrShaderStage::Compute; @@ -76,7 +76,8 @@ std::vector emitLoad(const LoadCase& test, bool active, std::uint program.Metadata().bindings.pushDataStartDword = 0; program.Metadata().bindings.memoryOffsetCount = 1; auto& size = constant(records); - auto& handle = emit(IrOpcode::GetBufferResource, IrType::BufferResource, {&zero, &zero, &size, &zero}); + auto& base = constant(valid ? 0x100u : 0u); + auto& handle = emit(IrOpcode::GetBufferResource, IrType::BufferResource, {&base, &zero, &size, &zero}); emit(test.opcode, test.type, {&handle, &zero, &address, &zero, &enabled}); } else { if (test.kind == ResourceKind::Gds) program.Metadata().bindings.descriptors.push_back({DescriptorBindingKind::Gds, {0}}); @@ -104,8 +105,8 @@ std::vector emitLoad(const LoadCase& test, bool active, std::uint class AccessTrace { public: - AccessTrace(const std::vector& module, std::uint32_t misalignment = 0, bool valid = true) - : words(module), misalignment(misalignment), valid(valid) { + AccessTrace(const std::vector& module, std::uint32_t misalignment = 0) + : words(module), misalignment(misalignment) { require(words.size() >= 5, "SPIR-V module header is missing"); for (std::size_t offset = 5; offset < words.size();) { const auto count = words[offset] >> 16; @@ -206,7 +207,6 @@ private: const auto spec = specializations.at(id); if (spec == PipelineSpecialization::BufferBase) return 0; if (spec == PipelineSpecialization::BufferBase + 1) return 0x30001000; - if (spec == PipelineSpecialization::BufferBase + 2) return valid; if (spec == PipelineSpecialization::BufferBase + 3) return misalignment; if (spec == PipelineSpecialization::PushDataOffset) return 0; throw std::runtime_error("unexpected specialization in access fixture"); @@ -250,7 +250,6 @@ private: const std::vector& words; std::uint32_t misalignment; - bool valid; std::uint32_t entryFunction = 0; std::map> definitions; std::map names; @@ -281,8 +280,8 @@ std::uint32_t ldsLength(const std::vector& words) { } void expectReads(const LoadCase& test, bool robust, bool active, std::uint32_t offset, std::vector expected, std::uint32_t records = 64, std::uint32_t misalignment = 0, bool valid = true) { - const auto module = emitLoad(test, active, offset, records, robust); - const auto actual = AccessTrace(module, misalignment, valid).Run(); + const auto module = emitLoad(test, active, offset, records, robust, valid); + const auto actual = AccessTrace(module, misalignment).Run(); if (actual != expected) { std::string detail = std::string(robust ? "robust" : "plain") + " guest reads:"; for (const auto index : actual) detail += " " + std::to_string(index); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index db38d183f5..ab7db93b7a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -95,6 +95,52 @@ void rejectFields(const Fields& f, std::string_view reason) { } +static void RunR128TextureTests() { + constexpr std::array streamed{0xa0c18800u, 0xcad00000u, 0x01ffc1ffu, 0x905b2facu, 0u, 0u, 0u, 0u}; + const auto view = DecodeTextureResource(streamed); + Require(view.baseAddress == 0xa0c1880000ull && view.format == 173u && view.tileMode == TextureTileMode::kStandard4KB && view.width == 512u && view.height == 512u && view.baseLevel == 0u && view.lastLevel == 9u && view.mipCount == 10u && view.allocatedMipCount == 10u && view.minLod == 0u, + "a 128-bit BC3 T# (2048x2048, levels 2..11, words 4-7 zero) must read as the 512x512 chain of its 10 resident levels, got " + std::to_string(view.width) + "x" + std::to_string(view.height) + " levels " + std::to_string(view.baseLevel) + ".." + std::to_string(view.lastLevel) + " of " + std::to_string(view.mipCount)); + auto chainWords = streamed; + chainWords[3] &= ~(0xfu << 12u); + chainWords[5] = 11u << 4u; + const auto full = DecodeTextureResource(chainWords); + Require(full.width == 2048u && full.mipCount == 12u, "the same surface with MAX_MIP 11 must be the 12-level 2048x2048 chain"); + const auto chain = DescribeSurface(full); + const auto resident = DescribeSurface(view); + Require(chain.mips.size() == 12u && resident.mips.size() == 10u, "the chain and the resident levels must have 12 and 10 levels"); + for (std::uint32_t level = 0; level < 10u; ++level) { + const auto& a = chain.mips[level + 2u]; + const auto& b = resident.mips[level]; + Require(a.tiledOffset == b.tiledOffset && a.tiledSize == b.tiledSize && a.width == b.width && a.height == b.height && a.blocksPerRow == b.blocksPerRow && a.tail == b.tail && a.tailX == b.tailX && a.tailY == b.tailY, "resident level " + std::to_string(level) + " must be level " + std::to_string(level + 2u) + " of the 12-level chain"); + } + Require(resident.guestBytes <= chain.mips[1].tiledOffset && chain.mips[0].tiledOffset > chain.mips[1].tiledOffset, "the resident levels must end before the chain's levels 1 and 0, which come last in a GFX10 swizzled chain"); + auto levelZero = streamed; + levelZero[3] &= ~(0xfu << 12u); + const auto single = DecodeTextureResource(levelZero); + Require(single.mipCount == 1u && single.lastLevel == 0u, "a T# with words 4-7 zero that starts at level 0 must stay one level"); + auto tailed = streamed; + tailed[5] = 4u << 20u; + reject([&] { DecodeTextureResource(tailed); }, "would move the surface's own"); + + constexpr std::array tall{0xa0c1de00u, 0xcad00000u, 0x01ffc0ffu, 0x905b4facu, 0u, 0u, 0u, 0u}; + const auto tallView = DecodeTextureResource(tall); + Require(tallView.width == 64u && tallView.height == 128u && tallView.lastLevel == 7u && tallView.mipCount == 8u, "a 128-bit 1024x2048 T# of levels 4..11 must read as its 64x128 chain of 8 levels"); + + Fields clamped; + clamped.base40 = 0x55ea000ull; + clamped.format = 20; + clamped.width = 1024; + clamped.height = 1024; + clamped.tileModeRaw = 0x1b; + clamped.baseLevel = 2; + clamped.lastLevel = 10; + clamped.minLod = 2u * 256u + 64u; + const auto clampedView = DecodeTextureResource(pack(clamped)); + Require(clampedView.width == 256u && clampedView.mipCount == 9u && clampedView.minLod == 64u, "reading a 128-bit T# from its resident levels must move its MIN_LOD clamp with the base level"); + clamped.minLod = 256u; + Require(DecodeTextureResource(pack(clamped)).minLod == 0u, "a MIN_LOD clamp below the base level must vanish when read from the resident levels"); +} + void RunGuestTextureResourceTests() { Fields base; const auto baseWords = pack(base); @@ -123,14 +169,44 @@ void RunGuestTextureResourceTests() { fmask.dstSelY = fmask.dstSelZ = fmask.dstSelW = 0u; const auto fmaskResource = DecodeTextureResource(pack(fmask)); Require(fmaskResource.format == 20u && fmaskResource.dimension == TextureDimension::k2D && fmaskResource.tileMode == TextureTileMode::kZ64KBX, "uncompressed 32-bit FMASK did not use an integer Z surface"); + auto compressedFmask = fmask; + compressedFmask.metaCompress = compressedFmask.metaPipeAligned = true; + compressedFmask.metaAddr = 0x123456789aull; + const auto compressedResource = DecodeTextureResource(pack(compressedFmask)); + Require(compressedResource.fmaskCmaskAddress == compressedFmask.metaAddr << 8u && compressedResource.dccAddress == 0u && compressedResource.dccPipeAligned, "compressed FMASK CMASK was interpreted as color DCC"); + compressedFmask.metaPipeAligned = false; + rejectFields(compressedFmask, "FMASK CMASK metadata must be pipe aligned"); + compressedFmask.metaPipeAligned = compressedFmask.writeCompress = true; + rejectFields(compressedFmask, "FMASK CMASK compression is not implemented"); Fields fmaskArray = fmask; fmaskArray.typeRaw = 13u; fmaskArray.depth = 2u; fmaskArray.baseArray = 1u; const auto fmaskArrayResource = DecodeTextureResource(pack(fmaskArray)); Require(fmaskArrayResource.format == 20u && fmaskArrayResource.baseArray == 1u && fmaskArrayResource.depthOrLastArray == 2u, "FMASK normalization changed the array view"); - for (std::uint32_t format = 156u; format <= 168u; ++format) { - if (format == 166u) continue; + constexpr std::array, 8> narrowFormats{{{156u, 5u}, {157u, 5u}, {158u, 5u}, {159u, 5u}, {160u, 5u}, {161u, 5u}, {163u, 11u}, {165u, 20u}}}; + for (const auto& [format, surface] : narrowFormats) { + auto narrow = fmask; + narrow.format = format; + const auto narrowResource = DecodeTextureResource(pack(narrow)); + Require(narrowResource.format == surface && narrowResource.tileMode == TextureTileMode::kZ64KBX, "FMASK " + std::to_string(format) + " did not use an integer Z surface of its storage width"); + narrow.dstSelX = 4u; + narrow.dstSelY = 5u; + narrow.dstSelZ = 6u; + narrow.dstSelW = 7u; + Require(DecodeTextureResource(pack(narrow)).format == surface, "an expanded FMASK " + std::to_string(format) + " view with identity selectors was refused"); + auto compressedNarrow = narrow; + compressedNarrow.metaCompress = compressedNarrow.metaPipeAligned = true; + compressedNarrow.metaAddr = 0x123456789aull; + rejectFields(compressedNarrow, "FMASK CMASK compression is implemented only for 8-sample, 8-fragment FMASK"); + } + auto identityWord = fmask; + identityWord.dstSelX = 4u; + identityWord.dstSelY = 5u; + identityWord.dstSelZ = 6u; + identityWord.dstSelW = 7u; + rejectFields(identityWord, "FMASK destination channel selector is not implemented"); + for (const auto format : {162u, 164u, 167u, 168u}) { auto unsupported = fmask; unsupported.format = format; rejectFields(unsupported, "FMASK format is not implemented"); @@ -620,6 +696,7 @@ void RunGuestTextureResourceTests() { pastUntailed.maxMip = 0; pastUntailed.baseLevel = 1; pastUntailed.lastLevel = 1; + pastUntailed.perfMod = 4; rejectFields(pastUntailed, "would move the surface's own"); auto untailedChain = tailView; untailedChain.mipCount = 1; @@ -627,6 +704,8 @@ void RunGuestTextureResourceTests() { untailedChain.mipCount = 2; Require(untailedAllocated == 0xff0000u && DescribeSurface(untailedChain).guestBytes == 0x1470000u, "a second level must grow a 1920x1080 64 bpp SW_64KB_R_X surface from addrlib's 0xff0000 to 0x1470000 bytes"); + RunR128TextureTests(); + std::array shortWords{}; reject([&] { DecodeTextureResource(shortWords); }, "8 dwords"); diff --git a/core/libs/prx/libSceAgcDriver/tests/HostAllocator.cpp b/core/libs/prx/libSceAgcDriver/tests/HostAllocator.cpp index e6b6ff60c5..43c38ce262 100644 --- a/core/libs/prx/libSceAgcDriver/tests/HostAllocator.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/HostAllocator.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -112,6 +113,50 @@ std::size_t PrivateBytes() { #endif } +void BlocksFlowToAnotherThread() { + constexpr int rounds = 12; + constexpr int perRound = 200000; + std::size_t afterEarlyRounds = 0; + for (int round = 0; round < rounds; ++round) { + std::mutex lock; + std::vector handed; + std::atomic done{false}; + std::thread freer([&] { + std::vector taken; + for (;;) { + const bool finished = done.load(std::memory_order_acquire); + { + std::lock_guard guard(lock); + taken.swap(handed); + } + for (auto* block : taken) { + const auto size = static_cast(block[0]) * 4 + 1; + Check(block[size - 1] == block[0], "a block freed on another thread keeps its bytes until then"); + ::operator delete(block); + } + taken.clear(); + if (finished) break; + std::this_thread::yield(); + } + }); + for (int i = 0; i < perRound; ++i) { + const auto seed = static_cast(i % 256); + const auto size = static_cast(seed) * 4 + 1; + auto* block = static_cast(::operator new(size)); + block[0] = seed; + block[size - 1] = seed; + std::lock_guard guard(lock); + handed.push_back(block); + } + done.store(true, std::memory_order_release); + freer.join(); + if (round == 3) afterEarlyRounds = PrivateBytes(); + } + const auto afterLastRound = PrivateBytes(); + std::printf("private bytes after 4 and after %d rounds of blocks freed on another thread: %zu, %zu\n", rounds, afterEarlyRounds, afterLastRound); + Check(afterLastRound < afterEarlyRounds + (64u << 20), "blocks freed on another thread do not pile up"); +} + void ThreadsEndWithCachedBlocks() { constexpr int waves = 24; std::size_t afterEarlyWaves = 0; @@ -144,6 +189,7 @@ int main() { SizedDeletesKeepTheirClass(); BlocksCrossIntoLibstdcxx(); BlocksMoveBetweenThreads(); + BlocksFlowToAnotherThread(); ThreadsEndWithCachedBlocks(); if (failures != 0) { std::fprintf(stderr, "%d host allocator checks failed\n", failures); diff --git a/core/libs/prx/libSceAgcDriver/tests/HostImportLifetime.cpp b/core/libs/prx/libSceAgcDriver/tests/HostImportLifetime.cpp index 36d9aba45e..603d4bb0a2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/HostImportLifetime.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/HostImportLifetime.cpp @@ -517,7 +517,7 @@ void partialUnmap(Fixture& fixture) { { GuestAllocations::Mutation mutation; mutation.Unmap(middle, part, [&](const void* piece, std::size_t bytes, const void* original, bool last) { - Require(!imported->freed.load(), "an unmap inside a host import freed the memory of the side it keeps"); + requireFreed(imported); Require(!other->freed.load(), "partial unmap retired a disjoint host import"); Require(piece == middle && bytes == part && original == allocation.data && !last, "partial unmap changed the wrong guest extent"); }); @@ -525,9 +525,8 @@ void partialUnmap(Fixture& fixture) { Require(!RegisteredReadableCovers(reinterpret_cast(middle), part), "the middle of a partial unmap remains registered"); const auto low = fixture.Import(allocation.Address(), part); const auto high = fixture.Import(allocation.Address() + 2 * part, part); - Require(high->memory == imported->memory && !imported->freed.load(), "the side kept from an unmap inside the host import was imported again"); + Require(imported->freed.load(), "the side kept from an unmap inside the host import reused memory imported across the unmapped range"); Require(high->address == allocation.Address() + 2 * part && high->bytes == part, "the kept side of the host import does not cover its remaining range"); - Require(low->memory != imported->memory, "the smaller side of an unmap inside the host import was not imported again"); Require(!low->freed.load() && !high->freed.load() && !other->freed.load(), "remaining partial mappings could not be imported"); { GuestAllocations::Mutation mutation; diff --git a/core/libs/prx/libSceAgcDriver/tests/LastRecordIndices.cpp b/core/libs/prx/libSceAgcDriver/tests/LastRecordIndices.cpp new file mode 100644 index 0000000000..75b7a3ee21 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/LastRecordIndices.cpp @@ -0,0 +1,64 @@ +#include "GraphicsTests.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" +#include +#include +#include +#include +#include + +namespace { + +using namespace AgcDriver::Graphics; + +std::vector BackwardScan(const std::vector& logged, const std::vector& objects) { + std::vector indices(objects.size(), logged.size()); + for (std::size_t i = 0; i < objects.size(); ++i) { + for (auto at = logged.size(); at-- > 0;) { + if (logged[at] == objects[i]) { + indices[i] = at; + break; + } + } + } + return indices; +} + +void CheckIndices(const std::vector& logged, const std::vector& objects, const std::string& what) { + std::vector indices; + LastRecordIndices(logged, objects, indices); + Require(indices == BackwardScan(logged, objects), "last record indices, " + what + ": an object did not get the index of its last record"); +} + +} + +void RunLastRecordIndicesTests() { + std::array pool{}; + const auto object = [&pool](std::size_t index) -> const void* { return &pool[index]; }; + + CheckIndices({object(0), object(1), object(0), object(2)}, {object(0), object(1), object(2), object(3), object(0), nullptr}, "repeated records and objects"); + CheckIndices({}, {object(0), nullptr}, "an empty log"); + CheckIndices({object(0), nullptr, object(1)}, {}, "no objects"); + CheckIndices({nullptr, object(0)}, {nullptr, object(0)}, "a null object"); + + std::vector table; + for (std::size_t texture = 0; texture < 1500; ++texture) table.push_back(object(texture)); + CheckIndices(table, table, "a table of 1500 textures over its own records in table order"); + std::vector repeated = table; + repeated.insert(repeated.end(), table.begin(), table.begin() + 548); + CheckIndices(repeated, table, "a table of 1500 textures whose first 548 were looked up twice"); + std::vector missing(table.begin() + 476, table.end()); + CheckIndices(missing, table, "a table of 1500 textures over a log without its first 476 records"); + + std::mt19937 generator(0x5eed); + for (const std::size_t records : {std::size_t{1}, std::size_t{7}, std::size_t{64}, std::size_t{1023}, std::size_t{1024}, std::size_t{1500}, std::size_t{2048}}) { + for (const std::size_t textures : {std::size_t{1}, std::size_t{5}, std::size_t{300}, std::size_t{1500}}) { + for (const std::size_t distinct : {std::size_t{8}, pool.size()}) { + std::vector logged(records); + for (auto& entry : logged) entry = object(generator() % distinct); + std::vector objects(textures); + for (auto& entry : objects) entry = object(generator() % distinct); + CheckIndices(logged, objects, std::to_string(records) + " records, " + std::to_string(textures) + " objects of " + std::to_string(distinct)); + } + } + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/LdsLockBudget.cpp b/core/libs/prx/libSceAgcDriver/tests/LdsLockBudget.cpp index d409fc1d07..55c6fc91b1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/LdsLockBudget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/LdsLockBudget.cpp @@ -114,7 +114,7 @@ void verify(std::uint32_t waveSize, std::uint32_t limit, bool int64Atomics) { const bool native = int64Atomics && limit < 68u; require(widths.contains(64u) == native, native ? "device-memory LDS did not use a 64-bit compare exchange" : "a 64-bit compare exchange appeared without device-memory LDS and shaderBufferInt64Atomics"); require(widths.contains(32u) != native, native ? "device-memory LDS with shaderBufferInt64Atomics kept the workgroup lock" : "the 64-bit LDS atomic lost its workgroup lock"); - require(result.bdaAbiVersion == (limit < 68u ? 1u : 0u), "the fault buffer for a misaligned 64-bit LDS atomic does not follow device-memory LDS at limit " + std::to_string(limit)); + require(result.bdaAbiVersion == 1u, "a 64-bit LDS atomic did not get the fault buffer for a misaligned address at limit " + std::to_string(limit)); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/Locks.cpp b/core/libs/prx/libSceAgcDriver/tests/Locks.cpp new file mode 100644 index 0000000000..6c660949ac --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/Locks.cpp @@ -0,0 +1,221 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/DeviceAccess.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" +#include "prx/libc/include/HostThreadSlot.hpp" +#ifdef _WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +int failures = 0; + +void Check(bool condition, const char* what) { + if (condition) return; + std::fprintf(stderr, "FAIL: %s\n", what); + ++failures; +} + +void MutexCounts(int threads, int iterations, int holdSpins) { + AgcDriver::Mutex mutex; + std::uint64_t counter = 0; + std::vector workers; + for (int t = 0; t < threads; ++t) { + workers.emplace_back([&] { + for (int i = 0; i < iterations; ++i) { + std::lock_guard lock(mutex); + const auto seen = counter; + for (volatile int spin = 0; spin < holdSpins; spin = spin + 1) { + } + counter = seen + 1; + } + }); + } + for (auto& worker : workers) worker.join(); + Check(counter == static_cast(threads) * static_cast(iterations), "every increment under the mutex counted"); +} + +void MutexTryLock() { + AgcDriver::Mutex mutex; + Check(mutex.try_lock(), "try_lock takes a free mutex"); + bool taken = true; + std::thread other([&] { taken = mutex.try_lock(); }); + other.join(); + Check(!taken, "try_lock fails while another thread holds the mutex"); + mutex.unlock(); + std::thread again([&] { + taken = mutex.try_lock(); + if (taken) mutex.unlock(); + }); + again.join(); + Check(taken, "try_lock takes the mutex once released"); +} + +void MutexWakesSleeper() { + AgcDriver::Mutex mutex; + mutex.lock(); + std::atomic acquired{false}; + std::thread waiter([&] { + std::lock_guard lock(mutex); + acquired = true; + }); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Check(!acquired, "lock() waits while the mutex is held"); + mutex.unlock(); + waiter.join(); + Check(acquired, "the waiting thread took the mutex after the release"); +} + +void DeviceGate() { + AgcDriver::DriverDetail::DeviceUseGate gate; + std::atomic users{0}; + std::atomic replacements{0}; + std::atomic overlap{false}; + std::atomic stop{false}; + std::atomic uses{0}; + std::vector threads; + for (int t = 0; t < 6; ++t) { + threads.emplace_back([&] { + while (!stop.load(std::memory_order_relaxed)) { + std::shared_lock use(gate); + users.fetch_add(1); + if (replacements.load() != 0) overlap = true; + users.fetch_sub(1); + uses.fetch_add(1, std::memory_order_relaxed); + } + }); + } + for (int r = 0; r < 2; ++r) { + threads.emplace_back([&] { + for (int i = 0; i < 300; ++i) { + std::unique_lock replacing(gate); + if (replacements.fetch_add(1) != 0 || users.load() != 0) overlap = true; + std::this_thread::yield(); + if (users.load() != 0) overlap = true; + replacements.fetch_sub(1); + } + }); + } + threads[6].join(); + threads[7].join(); + stop = true; + for (int t = 0; t < 6; ++t) threads[static_cast(t)].join(); + Check(!overlap, "no user inside the gate during a replacement, one replacement at a time"); + Check(uses.load() != 0, "users got through between replacements"); +} + +void GpuMutexRecursion() { + auto& mutex = AgcDriver::GuestMemory::GpuMutex(); + Check(!mutex.HeldByThisThread(), "the GPU mutex starts free"); + mutex.lock(); + mutex.lock(); + Check(mutex.HeldByThisThread() && mutex.DepthOnThisThread() == 2, "a nested lock counts the depth"); + bool taken = true; + std::thread other([&] { + taken = mutex.try_lock(); + if (taken) mutex.unlock(); + }); + other.join(); + Check(!taken, "another thread cannot take the held GPU mutex"); + Check(mutex.try_lock() && mutex.DepthOnThisThread() == 3, "the holder's try_lock nests"); + mutex.unlock(); + mutex.unlock(); + other = std::thread([&] { + taken = mutex.try_lock(); + if (taken) mutex.unlock(); + }); + other.join(); + Check(!taken, "the GPU mutex stays held until the outermost unlock"); + mutex.unlock(); + Check(!mutex.HeldByThisThread() && mutex.DepthOnThisThread() == 0, "the outermost unlock releases the GPU mutex"); + std::uint64_t counter = 0; + std::vector workers; + for (int t = 0; t < 4; ++t) { + workers.emplace_back([&] { + for (int i = 0; i < 20000; ++i) { + std::lock_guard outer(mutex); + std::lock_guard inner(mutex); + ++counter; + } + }); + } + for (auto& worker : workers) worker.join(); + Check(counter == 80000, "every nested increment under the GPU mutex counted"); +} + +template +void SlotIsolation(const char* what) { + using Slot = HostThreadSlot; + bool ok = Slot::Get() == 0; + Slot::Set(0x1234567890abcdefull); + std::uint64_t seen = 1; + std::thread other([&] { + seen = Slot::Get(); + Slot::Set(99); + }); + other.join(); + ok = ok && seen == 0 && Slot::Get() == 0x1234567890abcdefull; + Slot::Set(0); + Check(ok, what); +} + +struct DirectSlot {}; +struct ExpansionSlot {}; +struct PointerSlot {}; + +void ThreadSlots() { + SlotIsolation("a thread slot starts at zero on every thread and keeps its own value"); + int object = 0; + HostThreadSlot::Set(&object); + Check(HostThreadSlot::Get() == &object, "a pointer round-trips through a thread slot"); +#ifdef _WIN32 + std::vector held; + for (DWORD index = TlsAlloc(); index != TLS_OUT_OF_INDEXES; index = TlsAlloc()) { + held.push_back(index); + if (index >= 64) break; + } + SlotIsolation("an expansion thread slot starts at zero on every thread and keeps its own value"); + for (const auto index : held) TlsFree(index); +#endif + AgcDriver::GuestMemory::PacketTag fresh{}; + AgcDriver::GuestMemory::ReadSite freshSite = AgcDriver::GuestMemory::ReadSite::Count; + std::thread other([&] { + fresh = AgcDriver::GuestMemory::CurrentPacket(); + freshSite = AgcDriver::GuestMemory::CurrentReadSite(); + }); + other.join(); + Check(fresh.opcode == AgcDriver::GuestMemory::NoPacket && fresh.queue == 0xffffffffu, "a thread that ran no packet reads NoPacket"); + Check(freshSite == AgcDriver::GuestMemory::ReadSite::Unknown, "a thread that named no read site reads Unknown"); + AgcDriver::GuestMemory::SetCurrentPacket(0xffffu, 0x2au); + const auto packet = AgcDriver::GuestMemory::CurrentPacket(); + Check(packet.opcode == 0xffffu && packet.queue == 0x2au, "the packet tag reads back what was set"); + const auto previous = AgcDriver::GuestMemory::SetReadSite(AgcDriver::GuestMemory::ReadSite::Scanout); + Check(previous == AgcDriver::GuestMemory::ReadSite::Unknown && AgcDriver::GuestMemory::CurrentReadSite() == AgcDriver::GuestMemory::ReadSite::Scanout, "SetReadSite returns the previous site and sets the new one"); + AgcDriver::GuestMemory::SetReadSite(previous); +} + +} + +int main() { + MutexCounts(8, 100000, 0); + MutexCounts(4, 2000, 20000); + MutexTryLock(); + MutexWakesSleeper(); + DeviceGate(); + GpuMutexRecursion(); + ThreadSlots(); + if (failures != 0) { + std::fprintf(stderr, "%d lock check(s) failed\n", failures); + return 1; + } + std::printf("lock tests passed\n"); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp index ff2a3e40a7..a77f88d58f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp @@ -1066,6 +1066,38 @@ int main() { return StencilReplaySafe(b.program); }); + passed &= run("read-only BDA reads can be replayed and BDA writes cannot", [] { + Builder b; + b.program.Info().usesDma = true; + if (!StencilReplaySafe(b.program)) return false; + b.program.Info().bdaWrites = true; + return !StencilReplaySafe(b.program); + }); + + const auto execCopy = [](Builder& b, IrValue& low, IrValue& high) -> IrValue& { + auto& active = b.emit(IrOpcode::Ballot, IrType::U32x4, {&b.truth()}); + auto& lanes = b.emit(IrOpcode::CompositeConstructU64, IrType::U64, {&b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&active, &b.constant(0u)}), &b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&active, &b.constant(1u)})}); + auto& wqm = b.emit(IrOpcode::WqmU64, IrType::U64, {&lanes}); + return b.threadBit(b.wordAnd(low, b.emit(IrOpcode::CompositeExtractU64, IrType::U32, {&wqm, &b.constant(0u)})), b.wordAnd(high, b.emit(IrOpcode::CompositeExtractU64, IrType::U32, {&wqm, &b.constant(1u)}))); + }; + + passed &= run("an exec copy masked by the whole-quad active lanes is a stable export guard", [&] { + Builder b; + b.program.Resources().stage = IrShaderStage::Pixel; + const auto [low, high] = b.ballotWords(pixelPredicate(b)); + exportUnder(b, execCopy(b, *low, *high), true, true); + return StencilReplaySafe(b.program); + }); + + passed &= run("an exec copy does not hide helper participation in a whole-quad expansion", [&] { + Builder b; + b.program.Resources().stage = IrShaderStage::Pixel; + const auto [low, high] = b.ballotWords(helperPredicate(b, true)); + auto& wqm = b.emit(IrOpcode::WqmU64, IrType::U64, {&b.emit(IrOpcode::CompositeConstructU64, IrType::U64, {low, high})}); + stencilByMask(b, execCopy(b, b.emit(IrOpcode::CompositeExtractU64, IrType::U32, {&wqm, &b.constant(0u)}), b.emit(IrOpcode::CompositeExtractU64, IrType::U32, {&wqm, &b.constant(1u)}))); + return !StencilReplaySafe(b.program); + }); + passed &= run("only guarded read-only SRT diagnostics can be replayed", [] { Builder b; b.program.Info().usesFaultBuffer = true; diff --git a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp index a3c2a13217..ce6f2d65f3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp @@ -5,6 +5,7 @@ #include "prx/libc/include/GuestAllocations.hpp" #include #include +#include #include #include #include @@ -144,6 +145,7 @@ struct MeshDraw { VkCullModeFlags cull = VK_CULL_MODE_NONE; std::span code = {}; std::uint32_t stagesRegister = 0x20u; + ShaderRecompiler::ShaderVertexStageInfo vertex{}; }; bool ReadsNumWorkgroups(std::span words) { @@ -176,7 +178,7 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) { const std::array geometryMemory{{{reinterpret_cast(code.data()), std::as_bytes(std::span(code))}}}; ShaderRecompiler::RecompileRequest geometry{ {ShaderStage::Mesh, reinterpret_cast(code.data()), code, 0, {}}, - {setup.waveSize, 0, userData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, geometryMemory}, + {setup.waveSize, 0, userData, std::nullopt, std::nullopt, setup.vertex, geometryMemory}, target, {0, 0, 0, setup.geometryPushBytes}, ShaderRecompiler::GraphicsCompileContext{0, {}, setup.mesh, std::nullopt, {setup.draw.indexAddress, setup.draw.indexCount, setup.draw.indexSize, setup.draw.instanceCount}} @@ -207,6 +209,10 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) { AgcDriver::Graphics::State state{}; state.stages = {AgcDriver::Graphics::ShaderPath::Geometry, setup.stagesRegister, setup.waveSize, 64, setup.mesh, std::nullopt}; + state.pointSize = setup.vertex.pointSize; + state.pointSizeMin = setup.vertex.pointSizeMin; + state.pointSizeMax = setup.vertex.pointSizeMax; + state.paClVsOutCntl = setup.vertex.paClVsOutCntl; state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; state.colors = {state.color}; state.hasColorTarget = true; @@ -394,6 +400,63 @@ int main() { } } + ClearPixels(); + auto pointSubgroup = SmallSubgroup; + pointSubgroup.outputPrimitiveSize = 1u; + for (const auto pointWave : {32u, 64u}) { + ClearPixels(); + DrawMesh(device, {pointSubgroup, {0, static_cast(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast(Ordered.size())), ShaderRecompiler::MeshDrawPushOffsetBytes, false, pointWave}); + for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) { + const auto& position = Ordered[3u * triangle].position; + const auto x = static_cast((position[0] + 1.0f) * 0.5f * Width); + const auto y = static_cast((1.0f - position[1]) * 0.5f * Height); + const auto offset = (static_cast(y) * Width + x) * 4u; + Require(PixelIs(offset, TriangleColor(triangle)), "mesh point missing at first exported index for primitive " + std::to_string(triangle)); + Require(PixelIs(CellPixel(triangle), Background), "mesh point output filled the interior of its packed triangle"); + } + } + + for (const auto pointWave : {32u, 64u}) { + for (const auto size : {3.0f, 4.0f}) { + ClearPixels(); + MeshDraw points{pointSubgroup, {0, static_cast(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast(Ordered.size()))}; + points.waveSize = pointWave; + points.vertex.pointSize = size; + DrawMesh(device, points); + for (std::uint32_t primitive = 0; primitive < Triangles; ++primitive) { + std::uint32_t covered = 0; + for (std::size_t pixel = 0; pixel < Pixels.size(); pixel += 4u) covered += PixelIs(pixel, TriangleColor(primitive)); + Require(covered == static_cast(size * size), "mesh point did not cover its register-sized square"); + } + } + for (const auto exportedSize : {6.0f, -3.0f}) { + ClearPixels(); + const auto& baseCode = pointWave == 32u ? Wave32GeometryCode : GeometryCode; + std::array sizedCode{}; + std::copy_n(baseCode.begin(), 101u, sizedCode.begin()); + sizedCode[92] = 0xbf88000eu; + sizedCode[99] = 0xf80000cfu; + sizedCode[101] = 0x7e6802ffu; + sizedCode[102] = std::bit_cast(exportedSize); + sizedCode[103] = 0xf80008d1u; + sizedCode[104] = 0x00000034u; + std::copy(baseCode.begin() + 101u, baseCode.end(), sizedCode.begin() + 105u); + MeshDraw exported{pointSubgroup, {0, static_cast(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast(Ordered.size()))}; + exported.waveSize = pointWave; + exported.code = sizedCode; + exported.vertex.paClVsOutCntl = (1u << 16u) | (1u << 21u); + exported.vertex.pointSizeMin = 3.0f; + exported.vertex.pointSizeMax = 4.0f; + DrawMesh(device, exported); + for (std::uint32_t primitive = 0; primitive < Triangles; ++primitive) { + std::uint32_t covered = 0; + for (std::size_t pixel = 0; pixel < Pixels.size(); pixel += 4u) covered += PixelIs(pixel, TriangleColor(primitive)); + const auto side = exportedSize < 3.0f ? 3u : 4u; + Require(covered == side * side, "mesh point ignored its exported size or clamp"); + } + } + } + ClearPixels(); auto lineSubgroup = FastLaunchSubgroup; lineSubgroup.maxPrimitives = 2u; diff --git a/core/libs/prx/libSceAgcDriver/tests/MultisampleTextureLayout.cpp b/core/libs/prx/libSceAgcDriver/tests/MultisampleTextureLayout.cpp new file mode 100644 index 0000000000..2986c79da0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/MultisampleTextureLayout.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace AgcDriver::Graphics; + +namespace { + +struct ReferenceCase { + TextureTileMode tileMode; + std::uint32_t elementBytes; + std::uint32_t fragments; + std::uint32_t blockWidth; + std::uint32_t blockHeight; + std::uint32_t pitch; + std::uint32_t paddedHeight; + std::uint64_t sliceBytes; + std::array addresses; +}; + +constexpr std::array, 7> kCoordinates{{ + {0u, 0u, 0u}, {128u, 66u, 16u}, {64u, 32u, 1u}, {32u, 64u, 2u}, + {63u, 31u, 7u}, {127u, 65u, 15u}, {1u, 1u, 3u} +}}; + +constexpr ReferenceCase kReferences[] = { + {TextureTileMode::kR64KBX, 1u, 2u, 128u, 256u, 256u, 256u, 131072ull, {{0ull, 2201608ull, 141312ull, 300032ull, 919295ull, 2013271ull, 396305ull}}}, + {TextureTileMode::kR64KBX, 1u, 4u, 128u, 128u, 256u, 128u, 131072ull, {{0ull, 2185224ull, 174080ull, 316416ull, 919295ull, 1996887ull, 429073ull}}}, + {TextureTileMode::kR64KBX, 1u, 8u, 64u, 128u, 192u, 128u, 196608ull, {{0ull, 3291144ull, 279552ull, 422400ull, 1413119ull, 3061847ull, 640529ull}}}, + {TextureTileMode::kR64KBX, 2u, 2u, 128u, 128u, 256u, 128u, 131072ull, {{0ull, 2213920ull, 141312ull, 312320ull, 923390ull, 2025630ull, 396306ull}}}, + {TextureTileMode::kR64KBX, 2u, 4u, 128u, 64u, 256u, 128u, 262144ull, {{0ull, 4392992ull, 305152ull, 689152ull, 1840894ull, 4073630ull, 822290ull}}}, + {TextureTileMode::kR64KBX, 2u, 8u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6621216ull, 476160ull, 999936ull, 2793470ull, 6195358ull, 1230354ull}}}, + {TextureTileMode::kR64KBX, 4u, 2u, 64u, 128u, 192u, 128u, 196608ull, {{0ull, 3328032ull, 264192ull, 443392ull, 1390332ull, 3074204ull, 592916ull}}}, + {TextureTileMode::kR64KBX, 4u, 4u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6621216ull, 493568ull, 1016832ull, 2766588ull, 6170780ull, 1215508ull}}}, + {TextureTileMode::kR64KBX, 4u, 8u, 32u, 64u, 160u, 128u, 655360ull, {{0ull, 11077664ull, 820224ull, 1737216ull, 4667388ull, 10365084ull, 2002452ull}}}, + {TextureTileMode::kR64KBX, 8u, 2u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6621312ull, 460800ull, 984064ull, 2782968ull, 6170744ull, 1182744ull}}}, + {TextureTileMode::kR64KBX, 8u, 4u, 64u, 32u, 192u, 96u, 589824ull, {{0ull, 9963648ull, 855040ull, 1574400ull, 4158456ull, 9317496ull, 1773080ull}}}, + {TextureTileMode::kR64KBX, 8u, 8u, 32u, 32u, 160u, 96u, 983040ull, {{0ull, 16648320ull, 1444352ull, 2689280ull, 6979576ull, 15605880ull, 2952984ull}}}, + {TextureTileMode::kR64KBX, 16u, 2u, 32u, 64u, 160u, 128u, 655360ull, {{0ull, 11075712ull, 787456ull, 1706496ull, 4718576ull, 10397808ull, 1967664ull}}}, + {TextureTileMode::kR64KBX, 16u, 4u, 32u, 32u, 160u, 96u, 983040ull, {{0ull, 16646272ull, 1443328ull, 2690304ull, 7011312ull, 15641712ull, 2950960ull}}}, + {TextureTileMode::kR64KBX, 16u, 8u, 16u, 32u, 144u, 96u, 1769472ull, {{0ull, 30017664ull, 2624000ull, 4851968ull, 12615664ull, 28201072ull, 5312304ull}}}, + {TextureTileMode::kZ64KBX, 1u, 2u, 128u, 256u, 256u, 256u, 131072ull, {{0ull, 2172945ull, 149504ull, 271361ull, 923390ull, 1992879ull, 396294ull}}}, + {TextureTileMode::kZ64KBX, 1u, 4u, 128u, 128u, 256u, 128u, 131072ull, {{0ull, 2181153ull, 165890ull, 279555ull, 931580ull, 2025565ull, 396302ull}}}, + {TextureTileMode::kZ64KBX, 1u, 8u, 64u, 128u, 192u, 128u, 196608ull, {{0ull, 3295297ull, 263170ull, 410115ull, 1425404ull, 3041469ull, 591390ull}}}, + {TextureTileMode::kZ64KBX, 2u, 2u, 128u, 128u, 256u, 128u, 131072ull, {{0ull, 2181154ull, 165888ull, 279554ull, 931580ull, 2025566ull, 396300ull}}}, + {TextureTileMode::kZ64KBX, 2u, 4u, 128u, 64u, 256u, 128u, 262144ull, {{0ull, 4425794ull, 280580ull, 689158ull, 1881848ull, 4118714ull, 789532ull}}}, + {TextureTileMode::kZ64KBX, 2u, 8u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6637698ull, 459780ull, 999942ull, 2818040ull, 6219898ull, 1181244ull}}}, + {TextureTileMode::kZ64KBX, 4u, 2u, 64u, 128u, 192u, 128u, 196608ull, {{0ull, 3295300ull, 264192ull, 410628ull, 1423096ull, 3041468ull, 592920ull}}}, + {TextureTileMode::kZ64KBX, 4u, 4u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6637700ull, 460808ull, 1000460ull, 2815728ull, 6219892ull, 1182776ull}}}, + {TextureTileMode::kZ64KBX, 4u, 8u, 32u, 64u, 160u, 128u, 655360ull, {{0ull, 11126788ull, 787464ull, 1720844ull, 4716528ull, 10359028ull, 1967736ull}}}, +}; + +template +void requireRefusal(TFunction action) { + bool refused = false; + try { action(); } catch (const std::exception&) { refused = true; } + Require(refused, "multisampled layout accepted an unsupported operation"); +} + +void checkReference(const ReferenceCase& reference) { + MultisampleTextureLayout layout(reference.tileMode, reference.elementBytes, reference.fragments, 129u, 67u, 17u); + Require(layout.BlockWidth() == reference.blockWidth && layout.BlockHeight() == reference.blockHeight, "multisampled block extent differs from AMD reference"); + Require(layout.Pitch() == reference.pitch && layout.PaddedHeight() == reference.paddedHeight, "multisampled padded extent differs from AMD reference"); + Require(layout.SliceBytes() == reference.sliceBytes && layout.Bytes() == reference.sliceBytes * 17u, "multisampled allocation differs from AMD reference"); + Require(layout.LinearBytes() == 129ull * 67u * 17u * reference.fragments * reference.elementBytes, "multisampled linear region omits fragments or layers"); + for (std::uint32_t n = 0; n < kCoordinates.size(); ++n) { + const auto& coordinate = kCoordinates[n]; + const auto address = layout.Offset(coordinate[0], coordinate[1], coordinate[2], n % reference.fragments); + Require(address == reference.addresses[n], "multisampled fragment address differs from AMD reference"); + for (auto xorBytes : {0x100u, 0x800u, 0x4000u, 0x8000u, 0xcf00u}) { + MultisampleTextureLayout swizzled(reference.tileMode, reference.elementBytes, reference.fragments, 129u, 67u, 17u, xorBytes); + const auto expected = (address & ~65535ull) | ((address & 65535ull) ^ xorBytes); + Require(swizzled.Offset(coordinate[0], coordinate[1], coordinate[2], n % reference.fragments) == expected, "multisampled pipe/bank XOR moves a fragment outside its tile"); + } + } +} + +void checkTransfer(const ReferenceCase& reference) { + MultisampleTextureLayout layout(reference.tileMode, reference.elementBytes, reference.fragments, 129u, 67u, 3u, 0x8100u); + std::vector source(layout.LinearBytes() + 32u, std::byte{0xa5}); + auto linear = std::span(source).subspan(16u, layout.LinearBytes()); + for (std::size_t n = 0; n < linear.size(); ++n) linear[n] = static_cast((n * 13u + n / 251u) & 255u); + const auto savedSource = source; + std::vector tiled(layout.Bytes() + 32u, std::byte{0xa5}); + auto guest = std::span(tiled).subspan(16u, layout.Bytes()); + layout.Tile(linear, guest); + Require(source == savedSource, "multisampled retile changed its source"); + std::vector covered(guest.size()); + std::size_t position = 0; + for (std::uint32_t layer = 0; layer < 3u; ++layer) { + for (std::uint32_t y = 0; y < 67u; ++y) { + for (std::uint32_t x = 0; x < 129u; ++x) { + for (std::uint32_t fragment = 0; fragment < reference.fragments; ++fragment) { + const auto address = layout.Offset(x, y, layer, fragment); + for (std::uint32_t channel = 0; channel < reference.elementBytes; ++channel) { + Require(address + channel < guest.size() && !covered[address + channel], "multisampled element addresses overlap or exceed the allocation"); + covered[address + channel] = true; + Require(guest[address + channel] == linear[position++], "multisampled retile changed element bytes"); + } + } + } + } + } + for (std::size_t n = 0; n < guest.size(); ++n) Require(covered[n] || guest[n] == std::byte{0xa5}, "multisampled retile changed tile padding"); + Require(std::all_of(tiled.begin(), tiled.begin() + 16u, [](auto value) { return value == std::byte{0xa5}; }) && std::all_of(tiled.end() - 16u, tiled.end(), [](auto value) { return value == std::byte{0xa5}; }), "multisampled retile changed guard bytes"); + const auto savedTiled = tiled; + std::vector restored(source.size(), std::byte{0xa5}); + layout.Detile(guest, std::span(restored).subspan(16u, layout.LinearBytes())); + Require(restored == source && tiled == savedTiled, "multisampled detile changed element, source or guard bytes"); +} + +void checkInverse(const ReferenceCase& reference) { + const auto byteBits = static_cast(std::countr_zero(reference.elementBytes)); + const auto fragmentBits = static_cast(std::countr_zero(reference.fragments)); + const auto widthBits = static_cast(std::countr_zero(reference.blockWidth)); + for (const auto xorBytes : {0u, 0x4000u, 0x8000u, 0xcf00u}) { + const MultisampleTextureLayout layout(reference.tileMode, reference.elementBytes, reference.fragments, reference.blockWidth * 4u, reference.blockHeight * 4u, 8u, xorBytes); + for (const auto origin : {std::array{0u, 0u, 0u}, std::array{reference.blockWidth * 2u, reference.blockHeight * 3u, 7u}}) { + const auto base = static_cast(layout.Offset(origin[0], origin[1], origin[2], 0u) & 65535u); + const auto step = xorBytes == 0u && origin[2] == 0u ? 1u : 257u; + for (std::uint32_t packed = 0u; packed < 65536u; packed += step) { + const auto byte = packed & (reference.elementBytes - 1u); + const auto fragment = (packed >> byteBits) & (reference.fragments - 1u); + const auto x = (packed >> (byteBits + fragmentBits)) & (reference.blockWidth - 1u); + const auto y = packed >> (byteBits + fragmentBits + widthBits); + const auto physical = static_cast((layout.Offset(origin[0] + x, origin[1] + y, origin[2], fragment) + byte) & 65535u) ^ base; + std::uint32_t restored = 0u; + for (std::uint32_t bit = 0; bit < 16u; ++bit) restored |= static_cast(std::popcount(physical & layout.InverseBlockEquation()[bit]) & 1u) << bit; + Require(restored == packed, "multisampled inverse equation changed a byte, fragment or pixel coordinate"); + } + } + } +} + +void checkRefusals() { + for (const auto& extent : {std::array{0u, 3u, 1u}, std::array{5u, 0u, 1u}, std::array{16385u, 3u, 1u}, std::array{5u, 16385u, 1u}, std::array{5u, 3u, 0u}, std::array{5u, 3u, 8193u}}) { + requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kR64KBX, 8u, 8u, extent[0], extent[1], extent[2]); }); + } + for (auto elementBytes : {0u, 3u, 32u}) requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kR64KBX, elementBytes, 8u, 5u, 3u, 1u); }); + for (auto fragments : {0u, 1u, 3u, 16u}) requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kR64KBX, 8u, fragments, 5u, 3u, 1u); }); + for (auto tileMode : {TextureTileMode::kLinear, TextureTileMode::kStandard4KB, TextureTileMode::kStandard64KB, TextureTileMode::kS64KBX, TextureTileMode::kD64KBX}) requireRefusal([&] { MultisampleTextureLayout invalid(tileMode, 8u, 8u, 5u, 3u, 1u); }); + for (auto elementBytes : {8u, 16u}) requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kZ64KBX, elementBytes, 8u, 5u, 3u, 1u); }); + for (auto xorBytes : {1u, 255u, 0x1000u, 0x2000u, 0x10000u}) requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kR64KBX, 8u, 8u, 5u, 3u, 1u, xorBytes); }); + + MultisampleTextureLayout layout(TextureTileMode::kR64KBX, 8u, 8u, 5u, 3u, 2u); + for (const auto& coordinate : {std::array{5u, 0u, 0u, 0u}, std::array{0u, 3u, 0u, 0u}, std::array{0u, 0u, 2u, 0u}, std::array{0u, 0u, 0u, 8u}}) requireRefusal([&] { layout.Offset(coordinate[0], coordinate[1], coordinate[2], coordinate[3]); }); + std::vector source(layout.Bytes(), std::byte{0xa5}), destination(layout.Bytes(), std::byte{0x5a}); + const auto savedSource = source, savedDestination = destination; + requireRefusal([&] { layout.Detile(std::span(source).first(layout.Bytes() - 1u), destination); }); + requireRefusal([&] { layout.Detile(source, std::span(destination).first(layout.LinearBytes() - 1u)); }); + requireRefusal([&] { layout.Tile(std::span(source).first(layout.LinearBytes() - 1u), destination); }); + requireRefusal([&] { layout.Tile(source, std::span(destination).first(layout.Bytes() - 1u)); }); + requireRefusal([&] { layout.Detile(source, source); }); + requireRefusal([&] { layout.Tile(source, source); }); + requireRefusal([&] { layout.Detile(source, std::span(source).subspan(8u)); }); + Require(source == savedSource && destination == savedDestination, "refused multisampled transfer changed a buffer"); +} + +} + +int main() { + try { + for (const auto& reference : kReferences) { + checkReference(reference); + checkTransfer(reference); + checkInverse(reference); + } + checkRefusals(); + std::cout << "Multisampled texture layout reference, fragment, layer, padding and refusal tests passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/NopDataWritePacket.cpp b/core/libs/prx/libSceAgcDriver/tests/NopDataWritePacket.cpp new file mode 100644 index 0000000000..6be7c6011a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/NopDataWritePacket.cpp @@ -0,0 +1,50 @@ +#include "prx/libSceAgcDriver/include/AgcDriver.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +int main() { + std::array storage; + storage.fill(std::byte{0xaa}); + const std::array data{std::byte{0x11}, std::byte{0x22}, std::byte{0x33}, std::byte{0x44}, std::byte{0x55}}; + const auto words = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0x12345678, data.data(), static_cast(data.size())); + std::uint32_t header = 0; + std::uint32_t address = 0; + std::memcpy(&header, storage.data() + 4, sizeof(header)); + std::memcpy(&address, storage.data() + 8, sizeof(address)); + if (words != 4 || header != 0xc0021000U || address != 0x12345678U) return 1; + if (!std::equal(data.begin(), data.end(), storage.begin() + 12)) return 2; + if (storage[17] != std::byte{0} || storage[18] != std::byte{0} || storage[19] != std::byte{0}) return 3; + if (storage[3] != std::byte{0xaa} || storage[20] != std::byte{0xaa}) return 4; + + storage.fill(std::byte{0xaa}); + const auto emptyWords = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0x87654321, nullptr, 5); + std::memcpy(&header, storage.data() + 4, sizeof(header)); + std::memcpy(&address, storage.data() + 8, sizeof(address)); + if (emptyWords != 2 || header != 0xc0021000U || address != 0x87654321U) return 5; + if (storage[12] != std::byte{0} || storage[19] != std::byte{0} || storage[20] != std::byte{0xaa}) return 6; + + storage.fill(std::byte{0xaa}); + const auto zeroBytesWords = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0x01020304, data.data(), 0); + std::memcpy(&header, storage.data() + 4, sizeof(header)); + std::memcpy(&address, storage.data() + 8, sizeof(address)); + if (zeroBytesWords != 2 || header != 0xc0001000U || address != 0x01020304U) return 7; + if (storage[12] != std::byte{0xaa}) return 8; + + for (std::uint32_t len = 1; len <= 4; ++len) { + storage.fill(std::byte{0xaa}); + const auto lenWords = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0xaabbccddU, data.data(), len); + std::memcpy(&header, storage.data() + 4, sizeof(header)); + std::memcpy(&address, storage.data() + 8, sizeof(address)); + if (lenWords != 3 || header != 0xc0011000U || address != 0xaabbccddU) return 9 + static_cast(len); + if (!std::equal(data.begin(), data.begin() + len, storage.begin() + 12)) return 14 + static_cast(len); + for (std::size_t pad = 12 + len; pad < 16; ++pad) { + if (storage[pad] != std::byte{0}) return 19 + static_cast(len); + } + if (storage[3] != std::byte{0xaa} || storage[16] != std::byte{0xaa}) return 24 + static_cast(len); + } + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/PageStates.cpp b/core/libs/prx/libSceAgcDriver/tests/PageStates.cpp new file mode 100644 index 0000000000..fb92db485c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/PageStates.cpp @@ -0,0 +1,145 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include + +namespace { +using namespace AgcDriver::GuestMemory; +constexpr std::size_t Unit = 65536; +constexpr std::size_t ChunkGuestBytes = 256ull << 20u; + +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +class Range { +public: + explicit Range(std::size_t bytes) : bytes(bytes) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, ChunkGuestBytes)); + Require(data != nullptr, "cannot reserve page-state fixture"); + } + + ~Range() { + { + GuestAllocations::Mutation mutation; + for (const auto offset : committed) mutation.Remove(data + offset); + } + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } + + std::byte* Commit(std::size_t offset) { + auto* at = data + offset; + GuestArena::GuestArenaCommit_nid_postfix(at, Unit, PAGE_READWRITE, Unit); + std::memset(at, 0x5b, Unit); + GuestAllocations::Mutation mutation; + mutation.Add(at, Unit, true, true, true); + committed.push_back(offset); + return at; + } + + std::byte* Data() const { return data; } + +private: + std::byte* data = nullptr; + std::size_t bytes; + std::vector committed; +}; + +std::size_t PrivateBytes() { + PROCESS_MEMORY_COUNTERS_EX counters{}; + Require(K32GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&counters), sizeof(counters)), "cannot query page-state fixture private commit"); + return counters.PrivateUsage; +} + +void CheckMetadata() { + Range range(3 * ChunkGuestBytes); + auto* data = range.Commit(0); + const auto before = PrivateBytes(); + Require(Accessible(data, Unit, true), "committed fixture is inaccessible"); + const auto after = PrivateBytes(); + const auto added = after > before ? after - before : 0; + std::printf("page-permission initialization: %zu bytes of private commit\n", added); + Require(added < (16ull << 20u), "page permissions committed a dense arena table"); + const auto queried = PrivateBytes(); + Require(!Accessible(range.Data() + 2 * ChunkGuestBytes, Unit), "reserved fixture tail is accessible"); + Require(PrivateBytes() < queried + (4ull << 20u), "querying reserved pages allocated untouched metadata chunks"); +} + +void CheckBoundaries() { + constexpr auto span = 3 * ChunkGuestBytes; + Range range(span); + const std::array offsets{0, ChunkGuestBytes - Unit, ChunkGuestBytes, 2 * ChunkGuestBytes + Unit}; + for (const auto offset : offsets) range.Commit(offset); + auto* block = range.Data(); + std::atomic begin{false}; + std::atomic errors{0}; + std::vector readers; + for (unsigned reader = 0; reader < 8; ++reader) { + readers.emplace_back([&] { + while (!begin.load(std::memory_order_acquire)) std::this_thread::yield(); + for (unsigned round = 0; round < 500; ++round) { + if (!Accessible(block + offsets.back(), Unit, true)) errors.fetch_add(1, std::memory_order_relaxed); + } + }); + } + begin.store(true, std::memory_order_release); + for (auto& reader : readers) reader.join(); + Require(errors.load() == 0, "concurrent page-state publication changed access"); + for (const auto offset : offsets) { + std::array copy{}; + Read(reinterpret_cast(block + offset), copy); + Require(std::all_of(copy.begin(), copy.end(), [](std::byte value) { return value == std::byte{0x5b}; }), "page cache changed guest bytes"); + } + Require(Accessible(block + ChunkGuestBytes - Unit, 2 * Unit, true), "adjacent chunks lost their boundary pages"); + const auto described = DescribeCommitted(reinterpret_cast(block), span, true); + std::uint64_t total = 0; + for (const auto& [first, last] : described.ranges) total += last - first; + Require(!described.whole && total == offsets.size() * Unit, "reserved holes were reported committed"); + auto* changed = block + offsets[1]; + const auto protect = [&](bool writable) { + GuestAllocations::Mutation mutation; + mutation.Protect(changed, Unit, true, writable, true, [&] { + const DWORD protection = writable ? PAGE_READWRITE : PAGE_READONLY; + DWORD previous = 0; + Require(VirtualProtect(changed, Unit, protection, &previous) != 0, "cannot protect page-state fixture"); + GuestArena::GuestArenaSetProtection_nid_postfix(reinterpret_cast(changed), Unit, protection); + }); + }; + protect(false); + Require(Accessible(changed, Unit) && !Accessible(changed, Unit, true), "protection invalidation retained writable state"); + protect(true); + Require(Accessible(changed, Unit, true), "restored writable state was not discovered"); + GuestArena::GuestArenaReset_nid_postfix(block, Unit); + GuestAllocations::GuestAllocationsInvalidate_nid_postfix(reinterpret_cast(block), Unit); + Require(!Accessible(block, Unit), "decommitted pages remained readable"); + GuestArena::GuestArenaCommit_nid_postfix(block, Unit, PAGE_READWRITE, Unit); + Require(Accessible(block, Unit, true), "access observer did not invalidate inaccessible state after commit"); + std::puts("page-state boundaries, publication, holes and invalidation passed"); +} +} + +int main(int argc, char** argv) { + try { + Require(GuestArena::GuestArenaAvailable_nid_postfix(), "page-state fixture needs the guest arena"); + if (argc == 2 && std::string_view(argv[1]) == "--metadata") CheckMetadata(); + else CheckBoundaries(); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/PerformanceOverlay.cpp b/core/libs/prx/libSceAgcDriver/tests/PerformanceOverlay.cpp index b5db144ac8..2c138fecf3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/PerformanceOverlay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/PerformanceOverlay.cpp @@ -105,6 +105,8 @@ public: Device() { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + library = SDL_LoadObject("libvulkan.1.dylib"); #else library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -116,6 +118,12 @@ public: application.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &application; +#ifdef __APPLE__ + const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + info.enabledExtensionCount = 1; + info.ppEnabledExtensionNames = &portability; +#endif Check(function("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); std::uint32_t count = 0; const auto enumerate = function("vkEnumeratePhysicalDevices"); diff --git a/core/libs/prx/libSceAgcDriver/tests/PerformanceTrace.cpp b/core/libs/prx/libSceAgcDriver/tests/PerformanceTrace.cpp index efad88d641..a6272db6c6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/PerformanceTrace.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/PerformanceTrace.cpp @@ -134,6 +134,20 @@ void EscapeStrings() { Check(output == "\"a\\\"b\\\\c\\u000a\\u00e9\"", "string escaping is not valid JSON"); } +void Utf8Strings() { + std::string output; + TraceFormat::AppendString(output, "Ghost of Y\xc5\x8dtei \xe2\x84\xa2 \xf0\x9f\x8e\xae"); + Check(output == "\"Ghost of Y\xc5\x8dtei \xe2\x84\xa2 \xf0\x9f\x8e\xae\"", "a UTF-8 title was not written as its own characters"); + output.clear(); + TraceFormat::AppendString(output, "\xc0\xaf\xed\xa0\x80\xf4\x90\x80\x80\xe2\x84\x7f\xc5"); + Check(output == "\"\\u00c0\\u00af\\u00ed\\u00a0\\u0080\\u00f4\\u0090\\u0080\\u0080\\u00e2\\u0084\\u007f\\u00c5\"", "a byte outside a valid UTF-8 sequence was not escaped"); + AgcDriver::TraceSummary summary; + summary.title = AgcDriver::TraceTitle{"PPSA26344", "Ghost of Y\xc5\x8dtei", "01.000.000"}; + const auto origin = Clock::now(); + const std::vector presents{origin, origin + milliseconds(16)}; + Check(TraceFormat::Summary(summary, {}, presents).find("\"title\":\"Ghost of Y\xc5\x8dtei\"") != std::string::npos, "the summary line did not keep a UTF-8 title"); +} + void EventsShape() { const auto origin = Clock::now(); const std::vector slices{ @@ -576,6 +590,7 @@ int main() { FullBufferEvents(); FoldShortSlices(); EscapeStrings(); + Utf8Strings(); EventsShape(); BatchIds(); SummaryFields(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index fd239f7c00..a95329f86d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" @@ -40,8 +41,6 @@ #include #endif -extern "C" int APS5_VABI sceKernelAvailableFlexibleMemorySize(size_t* size); - namespace { void check(bool condition, const char* reason) { @@ -192,9 +191,9 @@ void testContextAndBases() { check(state.context.contains(0x280) && state.context.at(0x280) == 0x00080008u && state.context.contains(0x281) && state.context.at(0x281) == 0xffff0000u, "initial point context differs from the SDK defaults"); execute(state, makePacket(0x69, {0x280, 0x00180018u, 0x00400008u})); check(state.context.at(0x280) == 0x00180018u && state.context.at(0x281) == 0x00400008u, "explicit point size and limits were not stored"); - for (const auto offset : {0x10bu, 0x10cu, 0x10du}) execute(state, makePacket(0x69, {offset, 0xffffffffu})); + for (const auto offset : {0x010u, 0x011u, 0x10bu, 0x10cu, 0x10du}) execute(state, makePacket(0x69, {offset, 0xffffffffu})); execute(state, makePacket(0x12, {0})); - for (const auto offset : {0x10bu, 0x10cu, 0x10du}) check(state.context.at(offset) == 0, "CLEAR_STATE did not restore stencil defaults"); + for (const auto offset : {0x010u, 0x011u, 0x10bu, 0x10cu, 0x10du}) check(state.context.at(offset) == 0, "CLEAR_STATE did not restore depth and stencil defaults"); check(state.context.at(0x280) == 0x00080008u && state.context.at(0x281) == 0xffff0000u, "CLEAR_STATE did not restore point size and limits"); execute(state, makePacket(0x69, {0x10, 17})); execute(state, makePacket(0x76, {0x20c, 2})); @@ -322,6 +321,45 @@ void testIndexedDraw() { expectFailure([&] { AgcDriver::Pm4::ResolveDraw(packet, state); }, "guest"); } +void testIndirectDrawRange() { + AgcDriver::QueueState state; + alignas(4) std::array indices{}; + state.indexBase = reinterpret_cast(indices.data()); + state.indexBufferSize = 1; + state.indexType = 1; + state.userConfig[0x24a] = 0; + for (const auto opcode : {0x24u, 0x25u, 0x2cu, 0x38u}) { + const bool indexed = opcode == 0x25 || opcode == 0x38; + const bool multi = opcode == 0x2c || opcode == 0x38; + const std::uint32_t recordBytes = indexed ? 20u : 16u; + const std::uint32_t initiator = indexed ? 0u : 2u; + state.drawIndirectBase = std::numeric_limits::max() - 3; + const auto wrapping = multi ? makePacket(opcode, {0, 0x280, 0x280, 0x280, 1, 0, 0, recordBytes, initiator}) + : makePacket(opcode, {0, 0x280, 0x280, initiator}); + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(wrapping, state); }, "record range overflow"); + for (const auto count : {1u, 3u, std::numeric_limits::max()}) { + if (!multi && count != 1) continue; + for (const auto stride : {recordBytes, 64u, 0xfffffffcu}) { + if (!multi && stride != recordBytes) continue; + const auto packet = multi ? makePacket(opcode, {0, 0x280, 0x280, 0x280, count, 0, 0, stride, initiator}) + : makePacket(opcode, {0, 0x280, 0x280, initiator}); + const auto bytes = static_cast(count - 1) * stride + recordBytes; + state.drawIndirectBase = (std::numeric_limits::max() - bytes) & ~std::uint64_t{3}; + const auto draw = AgcDriver::Pm4::ResolveDraw(packet, state); + check(draw.indirect.has_value() && draw.indirect->RangeBytes() == bytes, "indirect draw rejected or changed a fitting record range"); + state.drawIndirectBase += 4; + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(packet, state); }, "record range overflow"); + } + } + state.drawIndirectBase = std::numeric_limits::max() - 3; + if (multi) { + const auto empty = makePacket(opcode, {0, 0x280, 0x280, 0x280, 0, 0, 0, recordBytes, initiator}); + const auto draw = AgcDriver::Pm4::ResolveDraw(empty, state); + check(draw.indirect.has_value() && draw.indirect->RangeBytes() == 0, "empty indirect draw consumed a record"); + } + } +} + void testIndirectDraw() { check(AgcDriver::Pm4::AccessesMemory(0xc0032400u) && AgcDriver::Pm4::AccessesMemory(0xc0083800u), "indirect draws must synchronize guest memory"); check(AgcDriver::Pm4::UnsupportedReason(0xc0032400u).empty() && AgcDriver::Pm4::UnsupportedReason(0xc0082c00u).empty(), "indirect draws are rejected"); @@ -363,8 +401,17 @@ void testIndirectDraw() { alignas(4) std::array indices{}; state.indexBase = reinterpret_cast(indices.data()); state.indexType = 0; + state.indexBufferSize.reset(); + const auto unlimited = AgcDriver::Pm4::ResolveDraw(makePacket(0x25, {0, 0x8c, 0x280, 0}), state); + check(unlimited.indexed && unlimited.indirect && !unlimited.indirect->indexLimitKnown && unlimited.indexCount == 0 && unlimited.indexAddress == state.indexBase && unlimited.indexSize == 2 && unlimited.indirect->recordBytes == 20, "an indexed indirect draw without INDEX_BUFFER_SIZE did not resolve with an unknown limit"); + const auto unlimitedMulti = AgcDriver::Pm4::ResolveDraw(makePacket(0x38, {0, 0x8c, 0x280, 0x280, 3, 0, 0, 20, 0}), state); + check(unlimitedMulti.indirect && !unlimitedMulti.indirect->indexLimitKnown && unlimitedMulti.indirect->count == 3 && unlimitedMulti.indirect->stride == 20, "a multi indexed indirect draw without INDEX_BUFFER_SIZE did not resolve with an unknown limit"); + execute(state, makePacket(0x13, {0})); + check(state.indexBufferSize.has_value() && *state.indexBufferSize == 0, "INDEX_BUFFER_SIZE of zero was not recorded as written"); expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x25, {0, 0x8c, 0x280, 0}), state); }, "INDEX_BUFFER_SIZE"); + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x38, {0, 0x8c, 0x280, 0x280, 3, 0, 0, 20, 0}), state); }, "INDEX_BUFFER_SIZE"); state.indexBufferSize = 8; + check(AgcDriver::Pm4::ResolveDraw(makePacket(0x25, {0, 0x8c, 0x280, 0}), state).indirect->indexLimitKnown, "a written INDEX_BUFFER_SIZE resolved with an unknown limit"); const auto indexed = AgcDriver::Pm4::ResolveDraw(makePacket(0x25, {0, 0x8c, 0x280, 0}), state); check(indexed.indexed && indexed.indexAddress == state.indexBase && indexed.indexCount == 8 && indexed.indexSize == 2 && indexed.indirect->recordBytes == 20 && indexed.indirect->stride == 20 && indexed.firstVertex == 0 && indexed.indirect->indxOffset == 0, "indexed indirect draw mismatch"); check(indexed.indirect->VertexDwordOffset() == 12 && indexed.indirect->InstanceDwordOffset() == 16, "indexed record geometry mismatch"); @@ -1021,8 +1068,9 @@ void testInvalidRegisterListEntries() { check(!AgcDriver::Pm4::IndirectRegistersValid(std::array{0xffffffffu, 0}), "the sentinel was taken as a register offset"); check(AgcDriver::Pm4::IndirectRegistersValid(std::array{0x10, 1, 0x70000011u, 2}), "a valid register list was rejected"); AgcDriver::QueueState state; + const auto initial = state.context; expectFailure([&] { AgcDriver::Pm4::ExecuteIndirectRegisters(packet, pairs, state); }, "extended register"); - check(!state.context.contains(0x10) && !state.context.contains(0x11), "a rejected register list partially changed state"); + check(state.context == initial, "a rejected register list partially changed state"); AgcDriver::Pm4::ExecuteIndirectRegisters(packet, pairs, state, true); check(state.context.at(0x10) == 41 && state.context.at(0x11) == 42 && !state.context.contains(0x38f), "skipping an invalid entry lost the valid ones or wrote the invalid one"); } @@ -1088,6 +1136,63 @@ void testReleaseMemDataSelects() { for (const auto select : {6u, 7u}) expectFailure([&] { AgcDriver::Pm4::Validate(release(select), 0); }, "reserved RELEASE_MEM DATA_SEL"); } +void testMemSemaphore() { + alignas(8) static std::uint64_t semaphore = 0; + alignas(8) static std::uint32_t marker = 0; + const auto packet = [](std::uint32_t control) { return makePacket(0x39, {low(&semaphore), high(&semaphore), control}); }; + check(AgcDriver::Pm4::AccessesMemory(packet(0xc0100000u)[0]), "MEM_SEMAPHORE must synchronize guest memory"); + for (const auto queue : {0u, 0x20u}) { + AgcDriver::Pm4::Validate(packet(0xc0100000u), queue); + AgcDriver::Pm4::Validate(packet(0xe0000000u), queue); + } + expectFailure([&] { AgcDriver::Pm4::Validate(packet(0xe0010000u), 0); }, "mailbox"); + expectFailure([&] { AgcDriver::Pm4::Validate(packet(0xa0000000u), 0); }, "MEM_SEMAPHORE operation"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x39, {low(&semaphore), high(&semaphore), 0xc0100000u, 0}), 0); }, "packet size"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x39, {0, 0, 0xc0100000u}), 0); }, "MEM_SEMAPHORE address"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x39, {low(&semaphore) + 4, high(&semaphore), 0xc0100000u}), 0); }, "MEM_SEMAPHORE address"); + + semaphore = 0x500000000ull; + auto words = packet(0xc0100000u); + submitWords(words); + AgcDriverWaitIdle_nid_postfix(); + check(std::atomic_ref(semaphore).load() == 1, "a MEM_SEMAPHORE signal of type 1 did not write 1"); + + semaphore = 5; + words = packet(0xc0000000u); + submitWords(words); + AgcDriverWaitIdle_nid_postfix(); + check(std::atomic_ref(semaphore).load() == 6, "a MEM_SEMAPHORE signal of type 0 did not increment"); + + constexpr std::uint64_t queueSignals = 2000, cpuSignals = 200000; + semaphore = 0; + std::vector graphicsWords, computeWords; + for (std::uint64_t index = 0; index < queueSignals; ++index) { + const auto signal = packet(0xc0000000u); + graphicsWords.insert(graphicsWords.end(), signal.begin(), signal.end()); + computeWords.insert(computeWords.end(), signal.begin(), signal.end()); + } + std::thread cpu([] { + for (std::uint64_t index = 0; index < cpuSignals; ++index) std::atomic_ref(semaphore).fetch_add(1); + }); + submitWords(graphicsWords); + submitWords(computeWords, 0x20); + cpu.join(); + AgcDriverWaitIdle_nid_postfix(); + check(std::atomic_ref(semaphore).load() == 2 * queueSignals + cpuSignals, "competing MEM_SEMAPHORE signals of type 0 lost an increment"); + + semaphore = 0; + marker = 0; + words = joinPackets({packet(0xe0000000u), makePacket(0x49, {0x0030c514, 0x20000000, low(&marker), high(&marker), 1, 0, 0})}); + submitWords(words); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + check(std::atomic_ref(marker).load() == 0, "a MEM_SEMAPHORE wait passed a zero semaphore"); + std::atomic_ref(semaphore).store(2); + for (int waited = 0; waited < 2000 && std::atomic_ref(marker).load() == 0; ++waited) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + AgcDriverWaitIdle_nid_postfix(); + check(std::atomic_ref(marker).load() == 1, "work after a signalled MEM_SEMAPHORE wait never ran"); + check(std::atomic_ref(semaphore).load() == 1, "a MEM_SEMAPHORE wait did not take one from the semaphore"); +} + void testSuspendPointWritesQueuedLabels() { alignas(8) static std::uint32_t gate = 0, marker = 0; gate = 0; @@ -1170,6 +1275,11 @@ void testConditionalSubmission() { check(results[13] == 0, "a rejected conditional submission executed a guarded packet"); } +void testRewindPayloadBits() { + for (const auto payload : {0x00000000u, 0x80000000u, 0x01000000u, 0x81000000u}) AgcDriver::Pm4::Validate(makePacket(0x59, {payload}), 0); + for (const auto payload : {0x82000000u, 0x80000001u, 0x00800000u}) expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x59, {payload}), 0); }, "unsupported REWIND payload bits"); +} + void testNopPadSubmission() { alignas(8) static std::uint32_t zero = 0; static std::array results{}; @@ -1936,6 +2046,7 @@ int main(int argc, char** argv) { } testCatalog(); testNopPad(); + testRewindPayloadBits(); testWriteChangedKeepsUntouchedBytes(); testWriteChangedStoresExactlyTheChangedRuns(); testRegisters(); @@ -1943,6 +2054,7 @@ int main(int argc, char** argv) { testContextAndBases(); testIndexedDraw(); testAutoDraw(); + testIndirectDrawRange(); testIndirectDraw(); testIndexAttributesIndirect(); testMemory(); @@ -1968,6 +2080,7 @@ int main(int argc, char** argv) { testPredicatedSubmission(); testConditionalSubmission(); testSuspendPointWritesQueuedLabels(); + testMemSemaphore(); testNopPadSubmission(); testBranchSubmission(); testWaitRegMemMasking(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4Replay.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4Replay.cpp index 98cf833fd3..7359bc4598 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4Replay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4Replay.cpp @@ -1,3 +1,10 @@ +#include "prx/libSceAgc/Patch/include/Rewind.hpp" +#include "prx/libSceAgc/DcbFlow/include/Sync.hpp" +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" +#include "prx/libSceAgc/Cb/include/Sync.hpp" +#include "prx/libSceAgc/Acb/include/Sync.hpp" +#include "prx/libSceAgc/Acb/include/Memory.hpp" +#include "prx/libSceAgc/Acb/include/Control.hpp" #include "../tools/Pm4Analysis.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/LabelStores.hpp" @@ -21,18 +28,6 @@ #include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcDcbWriteData(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint64_t, const void*, std::uint32_t, std::uint8_t, std::uint8_t); -std::uint32_t* APS5_VABI sceAgcAcbWriteData(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint64_t, const void*, std::uint32_t, std::uint8_t, std::uint8_t); -std::uint32_t* APS5_VABI sceAgcDcbWaitRegMem(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint8_t, std::uint8_t, const volatile void*, std::uint64_t, std::uint64_t, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcAcbWaitRegMem(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint8_t, const volatile void*, std::uint64_t, std::uint64_t, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer*, std::uint8_t, std::uint16_t, std::uint8_t, std::uint8_t, const volatile Label*, std::uint8_t, std::uint64_t, std::uint16_t, std::uint16_t, std::uint8_t, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcDcbDmaData(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t, std::uint8_t, std::uint64_t, std::uint32_t, std::uint8_t, std::uint8_t, std::uint8_t); -std::uint32_t* APS5_VABI sceAgcDcbCondExec(CommandBuffer*, const volatile std::uint32_t*, std::uint32_t); -std::uint32_t* APS5_VABI sceAgcAcbRewind(CommandBuffer*, std::uint32_t); -int APS5_VABI sceAgcAsyncRewindPatchSetRewindState(std::uint32_t*, std::uint8_t); -} - namespace { using AgcDriver::CapturedSubmission; @@ -693,8 +688,12 @@ void testCorrupt(const std::filesystem::path& directory) { int main() { std::filesystem::path scratch; try { +#ifdef _WIN32 + const wchar_t* captured = _wgetenv(L"APS5_DUMP_SUBMITS"); +#else const char* captured = std::getenv("APS5_DUMP_SUBMITS"); - check(captured != nullptr && *captured != '\0', "set APS5_DUMP_SUBMITS to the capture directory of this test"); +#endif + check(captured != nullptr && *captured != 0, "set APS5_DUMP_SUBMITS to the capture directory of this test"); const std::filesystem::path directory = captured; std::filesystem::remove_all(directory); scratch = std::filesystem::temp_directory_path() / ("anyps5-pm4-replay-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); diff --git a/core/libs/prx/libSceAgcDriver/tests/PresentationPass.cpp b/core/libs/prx/libSceAgcDriver/tests/PresentationPass.cpp index f55704d524..c8aae68a7a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/PresentationPass.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/PresentationPass.cpp @@ -7,6 +7,7 @@ #include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" +#include "execution/FixtureDeviceSelection.hpp" #include #include #include @@ -38,6 +39,8 @@ public: Device() { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + library = SDL_LoadObject("libvulkan.1.dylib"); #else library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -49,6 +52,12 @@ public: application.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &application; +#ifdef __APPLE__ + const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + info.enabledExtensionCount = 1; + info.ppEnabledExtensionNames = &portability; +#endif Check(function("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); std::uint32_t count = 0; const auto enumerate = function("vkEnumeratePhysicalDevices"); @@ -56,7 +65,7 @@ public: Require(count != 0, "no Vulkan device"); std::vector devices(count); Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); - context.physical = devices.front(); + context.physical = SelectFixturePhysicalDevice(devices, function("vkGetPhysicalDeviceProperties")); const auto queues = function("vkGetPhysicalDeviceQueueFamilyProperties"); queues(context.physical, &count, nullptr); std::vector families(count); @@ -559,6 +568,64 @@ void residentPresentTests(const Context& context) { blitLetterboxMatchesClear(context); } +PFN_vkGetDeviceProcAddr realDeviceProc = nullptr; +PFN_vkAllocateMemory realAllocateMemory = nullptr; +const VkPhysicalDeviceMemoryProperties* refusingMemory = nullptr; +std::uint32_t refusedAllocations = 0; +std::uint32_t lastAllocatedType = UINT32_MAX; + +VKAPI_ATTR VkResult VKAPI_CALL refuseDeviceLocalAllocation(VkDevice device, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks* callbacks, VkDeviceMemory* memory) { + const auto heap = refusingMemory->memoryTypes[info->memoryTypeIndex].heapIndex; + if ((refusingMemory->memoryHeaps[heap].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) { + ++refusedAllocations; + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + lastAllocatedType = info->memoryTypeIndex; + return realAllocateMemory(device, info, callbacks, memory); +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL refusingDeviceProc(VkDevice device, const char* name) { + if (std::strcmp(name, "vkAllocateMemory") == 0) return reinterpret_cast(&refuseDeviceLocalAllocation); + return realDeviceProc(device, name); +} + +void sourceImageOutsideDeviceMemoryTests(const Context& base) { + VkImageCreateInfo probeInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + probeInfo.imageType = VK_IMAGE_TYPE_2D; + probeInfo.format = VK_FORMAT_B8G8R8A8_UNORM; + probeInfo.extent = {64u, 64u, 1u}; + probeInfo.mipLevels = 1; + probeInfo.arrayLayers = 1; + probeInfo.samples = VK_SAMPLE_COUNT_1_BIT; + probeInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + probeInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + VkImage probe = VK_NULL_HANDLE; + Check(base.Function("vkCreateImage")(base.device, &probeInfo, nullptr, &probe), "vkCreateImage probe"); + VkMemoryRequirements requirements{}; + base.Function("vkGetImageMemoryRequirements")(base.device, probe, &requirements); + base.Function("vkDestroyImage")(base.device, probe, nullptr); + if (!base.HostMemoryType(requirements.memoryTypeBits)) { + std::printf("presentation source in host memory: skipped, the device offers no host memory type for the image\n"); + return; + } + auto context = base; + context.bufferPool.reset(); + realDeviceProc = base.deviceProc; + realAllocateMemory = base.Function("vkAllocateMemory"); + refusingMemory = &context.memory; + refusedAllocations = 0; + lastAllocatedType = UINT32_MAX; + context.deviceProc = refusingDeviceProc; + { + PresentationScaler scaler(context, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM); + scaler.EnsureSourceImage(64u, 64u); + Require(refusedAllocations != 0, "the presentation source never asked for device-local memory"); + Require(lastAllocatedType != UINT32_MAX && (context.memory.memoryHeaps[context.memory.memoryTypes[lastAllocatedType].heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0, "the presentation source was not placed in host memory when device-local memory was refused"); + Require(scaler.SourceWidth() == 64u && scaler.SourceHeight() == 64u, "the presentation source in host memory lost its extent"); + } + context.bufferPool.reset(); +} + } int main() { @@ -573,6 +640,7 @@ int main() { return VulkanTestSkipped; } residentPresentTests(device->GetContext()); + sourceImageOutsideDeviceMemoryTests(device->GetContext()); std::cout << "resident present format, presentation pass and letterbox tests passed\n"; return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 497adb234f..412903f163 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceAgc/Command/include/Packet.hpp" #include "prx/libSceAgcDriver/Execution/include/PipelineLinkOptimization.hpp" #include "Triangle_frag_spv.h" #include "Triangle_vert_spv.h" @@ -30,6 +32,7 @@ #include "ResidentPresent.hpp" #include "ImageTableAbi.hpp" #include "HostImportLifetime.hpp" +#include "ShaderCacheDirectory.hpp" #include "SampleLod_spv.h" #include "BufferUpdate_spv.h" #include "SampleArray_spv.h" @@ -47,6 +50,7 @@ #endif #include #include +#include #else #include #include @@ -64,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -77,14 +82,6 @@ namespace { -extern "C" { -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); -} - using namespace AgcDriver::Graphics; using AgcDriver::GuestMemory::GpuMutex; @@ -117,6 +114,32 @@ void ReleaseWatched(void* block, std::size_t bytes) { #endif } +bool collectMappingHistoryTests() { + constexpr std::size_t bytes = 131072; + auto* memory = static_cast(AllocateWatched(bytes, 65536)); + if (memory == nullptr) return false; + GuestAllocations::Mutation().Add(memory, bytes, true, true, false); + struct Cleanup { + std::uint8_t* memory; + ~Cleanup() { + GuestAllocations::Mutation().Remove(memory); + ReleaseWatched(memory, bytes); + } + } cleanup{memory}; + const auto address = reinterpret_cast(memory); + std::memset(memory, 0x11, bytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + const auto before = AgcDriver::GuestMemory::CollectWrites(address, bytes); + Require(before != 0, "the mapping fixture has no complete write history"); + memory[4096] = 0x22; + GuestAllocations::Mutation().Remove(memory); + GuestAllocations::Mutation().Add(memory, bytes, true, true, false); + Require(AgcDriver::GuestMemory::CollectWrites(address, bytes) != 0, "the replacement mapping lost write tracking"); + Require(!AgcDriver::GuestMemory::UnchangedSince(address, bytes, before), "a cached collect hid a CPU store across allocation replacement"); + Require(memory[4096] == 0x22, "collecting the replacement mapping changed its CPU bytes"); + return true; +} + bool partialWatchRestorationTests() { #ifdef _WIN32 constexpr std::size_t bytes = 131072; @@ -191,6 +214,41 @@ std::optional selectDevice(std::span(AllocateWatched(bytes, 65536)); + if (memory == nullptr) return false; + GuestAllocations::Mutation().Add(memory, bytes, true, true, false); + struct Cleanup { + std::uint8_t* memory; + ~Cleanup() { + GuestAllocations::Mutation().Remove(memory); + ReleaseWatched(memory, bytes); + } + } cleanup{memory}; + const auto address = reinterpret_cast(memory); + std::memset(memory, 0x11, bytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + AgcDriver::GuestMemory::Unwatch(address, bytes); + const auto before = AgcDriver::GuestMemory::UntrackedHint(address, bytes); + Require(before != 0, "the hint mapping fixture has no write hint"); + Require(AgcDriver::GuestMemory::UntrackedHint(address, bytes) == before, "an unchanged hint range advanced its history"); + memory[4096] = 0x22; + GuestAllocations::Mutation().Remove(memory); + GuestAllocations::Mutation().Add(memory, bytes, true, true, false); + const auto after = AgcDriver::GuestMemory::UntrackedHint(address, bytes); + Require(after > before, "a cached hint walk hid a CPU store across allocation replacement"); + Require(memory[4096] == 0x22 && memory[bytes - 4096] == 0x11, "collecting replacement hints changed guest bytes"); + GuestAllocations::Mutation().Remove(memory); + GuestAllocations::Mutation().Add(memory, bytes, true, true, false); + Require(AgcDriver::GuestMemory::UntrackedHint(address, bytes) == after, "allocation replacement invented a CPU write hint"); + return true; +#else + return false; +#endif +} + void deviceSelectionTests() { const auto properties = [](const char* name, VkPhysicalDeviceType type) { VkPhysicalDeviceProperties result{}; @@ -266,6 +324,7 @@ public: } return false; }; + memoryBudget = hasExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); auto bytes = AgcDriver::QueryBdaByteFeatures(context.physical, function("vkGetPhysicalDeviceFeatures2"), available); auto address = AgcDriver::QueryBdaFeatures(context.physical, function("vkGetPhysicalDeviceFeatures2"), available); const auto queues = function("vkGetPhysicalDeviceQueueFamilyProperties"); @@ -323,6 +382,7 @@ public: VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &hostProperties}; function("vkGetPhysicalDeviceProperties2")(context.physical, &properties); context.hostImportAlignment = hostProperties.minImportedHostPointerAlignment; + context.discreteGpu = properties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; } if (driverProperties.driverID != VK_DRIVER_ID_NVIDIA_PROPRIETARY && hasExtension(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME) && hasExtension(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME)) { extensionsEnabled.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME); @@ -459,6 +519,21 @@ public: return properties; } void WaitQueue() const { Check(context.Function("vkQueueWaitIdle")(context.queue), "vkQueueWaitIdle"); } + struct HeapUsage { + std::uint64_t deviceLocal = 0; + std::uint64_t host = 0; + }; + std::optional MemoryUsage() const { + if (!memoryBudget) return std::nullopt; + VkPhysicalDeviceMemoryBudgetPropertiesEXT budget{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; + VkPhysicalDeviceMemoryProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2, &budget}; + function("vkGetPhysicalDeviceMemoryProperties2")(context.physical, &properties); + HeapUsage usage; + for (std::uint32_t heap = 0; heap < properties.memoryProperties.memoryHeapCount; ++heap) { + ((properties.memoryProperties.memoryHeaps[heap].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0 ? usage.deviceLocal : usage.host) += budget.heapUsage[heap]; + } + return usage; + } private: template @@ -488,6 +563,7 @@ private: bool graphics = false; std::mutex settleMutex; bool nullDescriptors = false; + bool memoryBudget = false; }; using Kind = Recorder::ReadKind; @@ -1160,8 +1236,10 @@ void completionCountTests(const Device& device, Recorder& recorder) { recorder.AfterCompletions(base, value, 3, 0, true); recorder.AfterCompletions(base + 8, value, 4, 0, true); Require(Recorder::PendingCompletionLabels() == registered, "a GPU-stored completion label counts before any write-back"); + Require(Recorder::PendingCompletionStores() == registered, "a GPU-stored completion label counts as a completion store before any write-back"); recorder.AfterCompletions(base + 0x40, value, 5, 0, false); Require(Recorder::PendingCompletionLabels() == registered + 1, "a label the GPU has no view of is not pending"); + Require(Recorder::PendingCompletionStores() == registered + 1, "a label the GPU has no view of is not a pending completion store"); // A late lookup (stamp <= afterStamp) of a label whose value reaches memory only through the // batch's completion action is refused as BehindCompletion (a poller reaps for it); a GPU-stored // one keeps the ordinary reason (unclosed group, or trust off) until a write-back overlaps it. @@ -1177,16 +1255,20 @@ void completionCountTests(const Device& device, Recorder& recorder) { Recorder::NoteWrittenBack(base + 2, 4); const std::uint64_t counted = countAll ? 3 : 2; Require(Recorder::PendingCompletionLabels() == counted, "a write-back over a GPU-stored label did not count it exactly once"); + Require(Recorder::PendingCompletionStores() == counted, "a write-back over a GPU-stored label did not make it a completion store exactly once"); + int delivered = 0; + Require(recorder.AfterRecordedWork([&] { ++delivered; }), "an action after recorded work was refused"); + Require(Recorder::PendingCompletionLabels() == counted + 1 && Recorder::PendingCompletionStores() == counted, "an action after recorded work counted as a completion store"); Require(!Recorder::LookupLabel(base, 4, 3, &refusal).has_value() && refusal == Refusal::BehindCompletion, "a written-back GPU-stored label is not refused as behind a completion"); Require(!Recorder::LookupLabel(base + 8, 4, 4, &refusal).has_value() && refusal == (countAll ? Refusal::BehindCompletion : ordinary), "an untouched GPU-stored label became behind a completion"); recorder.Submit(); device.WaitQueue(); - Require(Recorder::PendingCompletionLabels() == counted && Recorder::PendingWriteBackCompletions() == 1, "counts dropped before the batch finished"); + Require(Recorder::PendingCompletionLabels() == counted + 1 && Recorder::PendingCompletionStores() == counted && Recorder::PendingWriteBackCompletions() == 1, "counts dropped before the batch finished"); memory[0] = 7; memory[2] = 7; memory[16] = 7; recorder.Sync(); - Require(ran == 1 && Recorder::PendingCompletionLabels() == 0 && Recorder::PendingWriteBackCompletions() == 0 && recorder.Idle(), "counts did not return to 0 at finish"); + Require(ran == 1 && delivered == 1 && Recorder::PendingCompletionLabels() == 0 && Recorder::PendingCompletionStores() == 0 && Recorder::PendingWriteBackCompletions() == 0 && recorder.Idle(), "counts did not return to 0 at finish"); const bool storeAlways = std::getenv("APS5_LABEL_STORE_ALWAYS") != nullptr; Require(memory[0] == 1 && memory[2] == (storeAlways ? 1u : 7u) && memory[16] == 1, "completion stores ran for the wrong labels (overlapped and not-imported ones store, the untouched GPU-stored one skips)"); } @@ -1261,7 +1343,7 @@ void directCompletionLabelTests(Recorder& recorder) { std::int64_t physical = -1; ~Release() { recorder.Sync(); - if (mapping != nullptr) sceKernelMunmap(mapping, size); + if (mapping != nullptr) sceKernelMunmap(reinterpret_cast(mapping), size); if (physical >= 0) sceKernelReleaseDirectMemory(physical, size); } } release{recorder}; @@ -1777,6 +1859,16 @@ void storeRunTests(const Device& device, Recorder& recorder) { Require(words[0] == 1 && words[1] == 2 && words[2] == 5 && words[4] == 4 && words[3] == 0, "the stores did not land (or the later store of a dword lost)"); const auto after = Recorder::StoreCounts(); Require(after.stores == before.stores + 1 && after.replaced == before.replaced + 0 && after.joined == before.joined + 0, "store counters are off"); + store(8, 1); + recorder.NotePendingWrite(address + 32, 4); + auto* commandWords = static_cast(block); + CommandBuffer disjoint{commandWords + 12, commandWords + 16, commandWords + 12, nullptr, nullptr, nullptr, 0}; + Agc::Command::Emit(&disjoint, 0x7a, {0x20000243, 0x400}, __func__); + Require(recorder.Recording(), "a disjoint command allocation drained the GPU store"); + CommandBuffer reused{commandWords + 6, commandWords + 10, commandWords + 6, nullptr, nullptr, nullptr, 0}; + Agc::Command::Emit(&reused, 0x7a, {0x20000243, 0x400}, __func__); + recorder.Sync(); + Require(words[8] == 0x400, "a pending GPU label overwrote a reused command word"); // The covered-access mask: reported once by the next Commands() call, then cleared. VkAccessFlags covered = 0xffffffffu; recorder.Commands(&covered); @@ -1966,7 +2058,10 @@ bool settleFirstUseRaced(const Context& context, int copies) { const auto* import = HostImportFor(context, address, unit); Require(import != nullptr && import->unwatched && !Watched(address, unit), "(f) a 64 KiB import was not compared at first"); const auto commands = recorder.Commands(); - for (int copy = 0; copy < copies; ++copy) CopyBuffer(context, commands, import->buffer, 0, scratch.Handle(), 0, unit); + for (int copy = 0; copy < copies; ++copy) { + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + CopyBuffer(context, commands, import->buffer, 0, scratch.Handle(), 0, unit); + } recorder.Submit(); const auto submittedAt = std::chrono::steady_clock::now(); const auto deadline = submittedAt + std::chrono::seconds(5); @@ -2005,6 +2100,177 @@ bool settleFirstUseRaced(const Context& context, int copies) { return racing; } +bool settleWriteOrderTests(const Device& device) { + using namespace AgcDriver::GuestMemory; + const auto& context = device.GetContext(); + constexpr std::size_t unit = 65536; + if (context.hostImportAlignment == 0 || context.hostImportAlignment > unit || context.settleQueue == VK_NULL_HANDLE || !WriteWatched()) { + std::cout << "host imports within 64 KiB, a settle queue or write watching unavailable: settle write order not tested\n"; + return false; + } + std::unique_lock gpu(GpuMutex()); + PrepareImportWatch(context); + if (!settledImportsWatchPrivate(context)) { + std::cout << "this device compares every Windows import: settle write order not tested\n"; + return false; + } + void* block = AllocateWatched(unit, unit); + struct Release { + void* block; + ~Release() { ReleaseWatched(block, unit); } + } release{block}; + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, unit, true, true, true); + } + Recorder recorder(context); + recorder.Activate(); + const auto* import = HostImportFor(context, address, unit); + Require(import != nullptr && import->unwatched, "the write-order import was not initially compared"); + const auto fill = [&](std::uint32_t value) { + const auto commands = recorder.Commands(); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + context.Function("vkCmdFillBuffer")(commands, import->buffer, HostImportOffset(*import, address), unit, value); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); + recorder.NotePendingWrite(address, unit); + recorder.Submit(); + }; + const auto before = HostImportSettles().submitted; + fill(0x22222222u); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (HostImportSettles().submitted == before && std::chrono::steady_clock::now() < deadline) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(HostImportFor(context, address, unit) == import, "the write-order import changed while settling"); + } + Require(HostImportSettles().submitted > before, "the write-order import did not submit a settle"); + fill(0x33333333u); + recorder.Sync(); + while (!Watched(address, unit) && std::chrono::steady_clock::now() < deadline) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + HostImportFor(context, address, unit); + } + Require(Watched(address, unit), "the write-order import did not return to the write watch"); + const auto* bytes = static_cast(block); + Require(std::all_of(bytes, bytes + unit, [](std::uint8_t value) { return value == 0x33; }), "the GPU store after the settle did not preserve its bytes"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, unit); + std::cout << "settle read between main-queue GPU writes passed\n"; + return true; +} + +struct SettleWaitFailure { + PFN_vkGetDeviceProcAddr resolve; + VkBuffer buffer = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkResult result = VK_SUCCESS; + std::uint64_t timeout = 0; + unsigned waits = 0; + unsigned destroys = 0; + unsigned frees = 0; +}; + +SettleWaitFailure* settleWaitFailure = nullptr; + +VKAPI_ATTR VkResult VKAPI_CALL settleWaitForFences(VkDevice device, std::uint32_t count, const VkFence* fences, VkBool32 all, std::uint64_t timeout) { + auto& failure = *settleWaitFailure; + if (failure.result != VK_SUCCESS) { + failure.timeout = timeout; + ++failure.waits; + return failure.result; + } + return reinterpret_cast(failure.resolve(device, "vkWaitForFences"))(device, count, fences, all, timeout); +} + +VKAPI_ATTR void VKAPI_CALL settleDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* allocator) { + auto& failure = *settleWaitFailure; + if (buffer == failure.buffer) ++failure.destroys; + reinterpret_cast(failure.resolve(device, "vkDestroyBuffer"))(device, buffer, allocator); +} + +VKAPI_ATTR void VKAPI_CALL settleFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* allocator) { + auto& failure = *settleWaitFailure; + if (memory == failure.memory) ++failure.frees; + reinterpret_cast(failure.resolve(device, "vkFreeMemory"))(device, memory, allocator); +} + +PFN_vkVoidFunction VKAPI_CALL settleWaitDeviceProc(VkDevice device, const char* name) { + if (std::strcmp(name, "vkWaitForFences") == 0) return reinterpret_cast(settleWaitForFences); + if (std::strcmp(name, "vkDestroyBuffer") == 0) return reinterpret_cast(settleDestroyBuffer); + if (std::strcmp(name, "vkFreeMemory") == 0) return reinterpret_cast(settleFreeMemory); + return settleWaitFailure->resolve(device, name); +} + +bool settleWaitFailureTests(const Device& device) { + using namespace AgcDriver::GuestMemory; + auto context = device.GetContext(); + constexpr std::size_t unit = 65536; + if (context.hostImportAlignment == 0 || context.hostImportAlignment > unit || context.settleQueue == VK_NULL_HANDLE || !WriteWatched()) return false; + std::lock_guard gpu(GpuMutex()); + PrepareImportWatch(context); + if (!settledImportsWatchPrivate(context)) return false; + ClearHostImports(context.device); + SettleWaitFailure failure{context.deviceProc}; + settleWaitFailure = &failure; + context.deviceProc = settleWaitDeviceProc; + context.functions = nullptr; + void* block = AllocateWatched(unit, unit); + Require(block != nullptr, "no watched block for settle wait failure"); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, unit, true, true, true); + } + struct Release { + Context& context; + SettleWaitFailure& failure; + void* block; + ~Release() { + failure.result = VK_SUCCESS; + ClearHostImports(context.device); + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + ReleaseWatched(block, unit); + settleWaitFailure = nullptr; + } + } release{context, failure, block}; + const auto address = reinterpret_cast(block); + const auto* import = HostImportFor(context, address, unit); + Require(import != nullptr && import->unwatched, "wait failure import was not initially compared"); + failure.buffer = import->buffer; + failure.memory = import->memory; + const auto before = HostImportSettles().submitted; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (HostImportSettles().submitted == before && std::chrono::steady_clock::now() < deadline) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + HostImportFor(context, address, unit); + } + Require(HostImportSettles().submitted > before, "wait failure did not submit a settle"); + Check(context.Function("vkDeviceWaitIdle")(context.device), "vkDeviceWaitIdle before settle failure"); + for (const auto result : {VK_TIMEOUT, VK_ERROR_DEVICE_LOST}) { + failure.result = result; + const auto waits = failure.waits; + std::string error; + try { + ClearHostImports(context.device); + } catch (const std::runtime_error& caught) { + error = caught.what(); + } + failure.result = VK_SUCCESS; + Require(failure.waits == waits + 1 && failure.timeout > 0 && failure.timeout <= 60'000'000'000ull, "settle retirement did not use a bounded fence wait"); + Require(error == "AGC graphics: vkWaitForFences (settle batch): Vulkan result " + std::to_string(result), "settle retirement did not propagate its Vulkan failure"); + Require(failure.destroys == 0 && failure.frees == 0 && HostImportCovers(context, address, unit), "failed settle wait released its host import"); + } + ClearHostImports(context.device); + Require(failure.destroys == 1 && failure.frees == 1, "successful settle retirement did not release the retained import exactly once"); + std::cout << "bounded settle wait preserves imports on timeout and device loss; successful retry releases once\n"; + return true; +} + bool settleFirstUseTests(const Device& device) { using namespace AgcDriver::GuestMemory; const auto& context = device.GetContext(); @@ -2019,15 +2285,9 @@ bool settleFirstUseTests(const Device& device) { std::cout << "this device compares every Windows import: the first use of a settled import not tested\n"; return false; } - for (int copies = 4096; copies <= 65536; copies *= 4) { - if (settleFirstUseRaced(context, copies)) { - std::cout << "settled import first use tests passed\n"; - return true; - } - std::cout << "(f) the main-queue copies had finished before the import returned to the write watch, so its read did not race them\n"; - } - std::cout << "(f) the main-queue copies finished first in every round: the first use of a settled import not tested\n"; - return false; + Require(!settleFirstUseRaced(context, 4096), "(f) the import returned to the write watch while its earlier main-queue reads were still in flight"); + std::cout << "settled import first use ordering tests passed\n"; + return true; } bool settleEarlierImportTests(const Device& device) { @@ -2066,7 +2326,10 @@ bool settleEarlierImportTests(const Device& device) { const auto* earlier = HostImportFor(context, address, 16); Require(earlier != nullptr && earlier->base == address && earlier->bytes == unit && earlier->unwatched, "(e) the first window of the range was not imported compared"); const auto commands = recorder.Commands(); - for (int copy = 0; copy < copies; ++copy) CopyBuffer(context, commands, earlier->buffer, 0, scratch.Handle(), 0, unit); + for (int copy = 0; copy < copies; ++copy) { + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + CopyBuffer(context, commands, earlier->buffer, 0, scratch.Handle(), 0, unit); + } const auto* whole = HostImportFor(context, address + unit - 16, 32); Require(whole != nullptr && whole->base == address && whole->bytes == 2 * unit && whole->unwatched, "(e) an access across the windows did not replace the first window's import"); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); @@ -2267,6 +2530,297 @@ bool settleStorageImageTests(const Device& device) { std::cout << "a settled import under a storage image tests passed\n"; return true; } + +bool settleInitialDccTests(const Device& device) { + using namespace AgcDriver::GuestMemory; + const auto& base = device.GetContext(); + constexpr std::size_t unit = 65536; + constexpr std::size_t tail = 0x4000; + if ((base.limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_4_BIT) == 0 || !device.Graphics() || base.hostImportAlignment == 0 || base.hostImportAlignment > tail || base.settleQueue == VK_NULL_HANDLE || !WriteWatched()) { + std::cout << "4x color targets, graphics, host imports within 16 KiB, a settle queue or write watching unavailable: an initial multisampled DCC import over a pending settle not tested\n"; + return false; + } + { + std::lock_guard gpu(GpuMutex()); + PrepareImportWatch(base); + if (!settledImportsWatchPrivate(base)) { + std::cout << "this device compares every Windows import: an initial multisampled DCC import over a pending settle not tested\n"; + return false; + } + } + std::cout << "write watch: Windows private imports stay watched after a settle read; imports under 1 MiB wait for a deferred settle\n"; + for (const std::size_t count : {256u, 257u, 1025u}) { + const auto bytes = count * unit; + auto* block = static_cast(AllocateWatched(bytes, unit)); + Require(block != nullptr, "(m) cannot allocate adjacent pending imports"); + const auto address = reinterpret_cast(block); + struct Release { + const Context& context; + std::uint8_t* block; + std::size_t bytes; + std::size_t registered = 0; + ~Release() { + { + GuestAllocations::Mutation mutation; + for (std::size_t index = 0; index < registered; ++index) mutation.Remove(block + index * unit); + } + HostImportFor(context, reinterpret_cast(block), unit); + SetHostImportWindow(0); + ReleaseWatched(block, bytes); + } + } release{base, block, bytes}; + std::memset(block, 0x11, bytes); + SetHostImportWindow(unit); + { + GuestAllocations::Mutation mutation; + for (std::size_t index = 0; index < count; ++index) { + mutation.Add(block + index * unit, unit, true, true, true); + ++release.registered; + } + } + for (std::size_t index = 0; index < count; ++index) { + const auto at = address + index * unit; + const auto* import = HostImportFor(base, at, unit); + Require(import != nullptr && import->base == at && import->bytes == unit && import->unwatched, + "(m) an adjacent 64 KiB import did not start with its settle pending"); + } + { + std::lock_guard gpu(GpuMutex()); + Require(!Watched(address, bytes), "(m) adjacent pending imports were already watched"); + const auto before = HostImportSettles(); + Require(WatchedAfterSettle(base, address, bytes), + "(m) one synchronous settle left part of " + std::to_string(count) + " adjacent imports unwatched"); + const auto after = HostImportSettles(); + BumpCollectEpoch(); + const auto collected = CollectWrites(address, bytes); + Require(collected != 0, "(m) adjacent settled imports were not collected"); + static_cast(block)[bytes - 1] = 0x22; + CollectWritesUncached(address, bytes); + Require(!UnchangedSince(address, bytes, collected), "(m) a CPU write after the final import settled was missed"); + std::cout << "(m) " << count << " adjacent pending imports settled: " << after.submitted - before.submitted << " reads submitted\n"; + } + } + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + Recorder recorder(context); + recorder.Activate(); + enum class Case { Pending, PartialBlock, ComparedNeighbour, DeferredOff, UnwatchedKeys, PendingFmask, PendingFmaskAlone }; + const auto run = [&](Case kind, std::uint32_t height) { + const bool fmaskAlone = kind == Case::PendingFmaskAlone; + const bool eightSamples = kind == Case::PendingFmask || fmaskAlone; + if (eightSamples && (!base.storageImageMultisample || (base.limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_8_BIT) == 0)) { + std::cout << "(m) 8x color or multisampled storage images unavailable: an initial FMASK import over a pending settle not tested\n"; + return; + } + const bool partial = kind == Case::PartialBlock || kind == Case::ComparedNeighbour; + ColorTarget target{}; + target.extent = {128, height}; + target.surfaceExtent = target.extent; + target.format = VK_FORMAT_R8G8B8A8_UNORM; + target.elementBytes = 4; + target.samples = eightSamples ? 8 : 4; + target.tileMode = ColorTileMode::RenderTarget; + target.dccPipeAligned = true; + const auto colorBytes = static_cast(MultisampledColorBytes(target)); + const auto dccBytes = MultisampledDccBytes(target); + const auto importBytes = colorBytes + (partial ? tail : unit); + const auto reserved = (importBytes + unit - 1) & ~(unit - 1); + const auto fmaskBytes = static_cast(target.extent.width) * target.extent.height * 4u; + const auto fmaskReserved = (fmaskBytes + unit - 1) & ~(unit - 1); + const auto regionBytes = unit + reserved + unit + (kind == Case::UnwatchedKeys ? unit : eightSamples ? fmaskReserved + unit : 0); + auto* region = static_cast(AllocateWatched(regionBytes, unit)); + void* block = region != nullptr ? region + unit : nullptr; + void* keys = region != nullptr && kind == Case::UnwatchedKeys ? region + unit + reserved + unit : nullptr; + void* fmask = region != nullptr && eightSamples ? region + unit + reserved + unit : nullptr; + const auto destBytes = static_cast(target.extent.width) * target.extent.height * 4u; + void* dest = VirtualAlloc(nullptr, destBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + Require(block != nullptr && dest != nullptr && (kind != Case::UnwatchedKeys || keys != nullptr) && (!eightSamples || fmask != nullptr), "(m) cannot allocate the target"); + const auto address = reinterpret_cast(block); + const auto neighbour = address + importBytes + tail; + const auto describe = [](const HostImport* found, std::uint64_t at, std::size_t bytes) { + char text[256]; + if (found == nullptr) std::snprintf(text, sizeof(text), ": no import of 0x%llx+0x%zx, watched %d", static_cast(at), bytes, Watched(at, bytes) ? 1 : 0); + else std::snprintf(text, sizeof(text), ": import 0x%llx+0x%llx, unwatched %d, settled on demand %d, for 0x%llx+0x%zx, watched %d", static_cast(found->base), static_cast(found->bytes), found->unwatched ? 1 : 0, found->settleOnDemand ? 1 : 0, static_cast(at), bytes, Watched(at, bytes) ? 1 : 0); + return std::string(text); + }; + struct Release { + const Context& context; + Recorder& recorder; + void* region; + std::size_t regionBytes; + void* block; + void* keys; + void* fmask; + void* dest; + std::size_t reserved; + std::uint64_t neighbour; + ~Release() { + { + std::lock_guard gpu(GpuMutex()); + recorder.Sync(); + ClearMultisampleTargets(context.device); + ClearCachedTextures(context.device); + recorder.FlushDeferredReleases(); + } + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + mutation.Remove(dest); + if (keys != nullptr) mutation.Remove(keys); + if (fmask != nullptr) mutation.Remove(fmask); + if (neighbour < reinterpret_cast(block) + reserved) mutation.Remove(reinterpret_cast(neighbour)); + } + HostImportFor(context, reinterpret_cast(block), 16); + SetHostImportDeferredSettles(true); + ReleaseWatched(region, regionBytes); + VirtualFree(dest, 0, MEM_RELEASE); + } + } release{context, recorder, region, regionBytes, block, keys, fmask, dest, reserved, kind == Case::ComparedNeighbour ? neighbour : std::numeric_limits::max()}; + target.address = address; + target.surfaceAddress = address; + target.bytes = colorBytes; + target.dccAddress = kind == Case::UnwatchedKeys ? reinterpret_cast(keys) : address + colorBytes; + std::memset(block, 0, importBytes); + std::memset(reinterpret_cast(target.dccAddress), 0xc0, dccBytes); + std::memset(dest, 0x5a, destBytes); + if (fmask != nullptr) { + target.cmaskAddress = target.dccAddress + ((dccBytes + 4095) & ~std::size_t{4095}); + target.cmaskBytes = CmaskBytes(target.extent.width, target.extent.height); + target.cmaskFastClear = true; + target.fmaskAddress = reinterpret_cast(fmask); + Require(target.cmaskAddress + target.cmaskBytes <= address + importBytes, "(m) the CMASK does not fit the target's import"); + std::memset(reinterpret_cast(target.cmaskAddress), 0xcc, static_cast(target.cmaskBytes)); + std::memset(fmask, 0x11, fmaskBytes); + } + { + GuestAllocations::Mutation mutation; + mutation.Add(block, importBytes, true, true, true); + mutation.Add(dest, destBytes, true, true, true); + if (keys != nullptr) mutation.Add(keys, unit, true, true, true); + if (fmask != nullptr) mutation.Add(fmask, fmaskBytes, true, true, true); + if (kind == Case::ComparedNeighbour) mutation.Add(reinterpret_cast(neighbour), reserved - (importBytes + tail), true, true, true); + } + if (kind == Case::UnwatchedKeys) Unwatch(target.dccAddress, dccBytes); + if (kind == Case::DeferredOff) SetHostImportDeferredSettles(false); + if (kind == Case::ComparedNeighbour) { + const auto* other = HostImportFor(context, neighbour, reserved - (importBytes + tail)); + Require(other != nullptr && other->unwatched && !Watched(neighbour, reserved - (importBytes + tail)), "(m) the import sharing the target's last 64 KiB block was not compared" + describe(other, neighbour, reserved - (importBytes + tail))); + } + if (fmask != nullptr && !fmaskAlone) { + const auto* fmaskImport = HostImportFor(context, target.fmaskAddress, fmaskBytes); + Require(fmaskImport != nullptr && fmaskImport->base == target.fmaskAddress && fmaskImport->bytes == fmaskBytes && fmaskImport->unwatched && !Watched(target.fmaskAddress, fmaskBytes), "(m) the FMASK's import was not compared at first" + describe(fmaskImport, target.fmaskAddress, fmaskBytes)); + } + const auto* import = HostImportFor(context, address, importBytes); + Require(import != nullptr && import->base == address && import->bytes == importBytes && import->unwatched && !Watched(address, importBytes), "(m) the target's import of " + std::to_string(importBytes) + " bytes was not compared at first" + describe(import, address, importBytes)); + HostImportSettleCounts quiet{}; + if (fmaskAlone) { + { + std::lock_guard gpu(GpuMutex()); + const auto start = HostImportSettles(); + Require(WatchedAfterSettle(context, address, importBytes), "(m) the target's import did not settle before the FMASK's import" + describe(ExistingHostImportFor(context, address, importBytes), address, importBytes)); + const auto settledTarget = HostImportSettles(); + Require(settledTarget.submitted > start.submitted && settledTarget.rewatched > start.rewatched, "(m) the target's import returned to the write watch without a settle read"); + } + const std::array, 3> planes{{{address, colorBytes}, {target.dccAddress, dccBytes}, {target.cmaskAddress, static_cast(target.cmaskBytes)}}}; + for (const auto& [at, bytes] : planes) { + const auto* covering = ExistingHostImportFor(context, at, bytes); + Require(Watched(at, bytes) && (covering == nullptr || !covering->unwatched), "(m) a color, DCC or CMASK plane is not watched before the FMASK's import" + describe(covering, at, bytes)); + } + quiet = HostImportSettles(); + const auto* fmaskImport = HostImportFor(context, target.fmaskAddress, fmaskBytes); + Require(fmaskImport != nullptr && fmaskImport->base == target.fmaskAddress && fmaskImport->bytes == fmaskBytes && fmaskImport->unwatched && !fmaskImport->settleOnDemand && !Watched(target.fmaskAddress, fmaskBytes), "(m) the FMASK's import is not compared with its deferred settle wanted" + describe(fmaskImport, target.fmaskAddress, fmaskBytes)); + } + std::unique_lock gpu(GpuMutex()); + if (partial) { + for (int round = 0; round < 20; ++round) { + gpu.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + gpu.lock(); + HostImportFor(context, address, importBytes); + } + Require(!Watched(address, importBytes), "(m) a deferred settle returned an import that is not a whole number of 64 KiB blocks to the write watch"); + } + const auto before = HostImportSettles(); + Require(!fmaskAlone || before.submitted == quiet.submitted, "(m) a settle read was submitted between the FMASK's import and the target's first use"); + std::string refusal; + try { + Require(MultisampleTargetView(context, target) != VK_NULL_HANDLE, "(m) the multisampled DCC target has no view"); + } catch (const std::exception& error) { + refusal = error.what(); + } + const auto after = HostImportSettles(); + const auto* settled = ExistingHostImportFor(context, address, importBytes); + const char* name = kind == Case::Pending ? "a pending settle" : kind == Case::PartialBlock ? "an import of whole and partial 64 KiB blocks" : kind == Case::ComparedNeighbour ? "a compared import in its last 64 KiB block" : kind == Case::DeferredOff ? "deferred settles off" : kind == Case::UnwatchedKeys ? "explicitly unwatched DCC keys" : kind == Case::PendingFmask ? "an eight-sample target whose color and FMASK imports both have pending settles" : "an eight-sample target whose FMASK alone has a pending settle"; + std::cout << "(m) initial DCC over " << name << " (import " << importBytes << " bytes): " << (refusal.empty() ? "accepted" : refusal) << "; " << after.submitted - before.submitted << " settle reads submitted, " << after.rewatched - before.rewatched << " back in the write watch\n"; + if (kind == Case::ComparedNeighbour || kind == Case::DeferredOff || kind == Case::UnwatchedKeys) { + Require(refusal.find("initial multisampled DCC contents require watched guest memory") != std::string::npos, std::string("(m) an initial multisampled DCC import over ") + name + " was not refused: " + refusal); + Require(kind == Case::UnwatchedKeys || !Watched(address, importBytes), std::string("(m) ") + name + " returned the target's import to the write watch"); + Require(kind != Case::UnwatchedKeys || (Watched(address, importBytes) && !Watched(target.dccAddress, dccBytes)), "(m) the refusal over explicitly unwatched DCC keys did not come from the keys" + describe(settled, address, importBytes)); + return; + } + Require(refusal.empty(), std::string("(m) an initial multisampled DCC import over ") + name + " was refused: " + refusal); + Require(!fmaskAlone || (after.submitted - before.submitted == 1 && after.rewatched - before.rewatched == 1), "(m) the first use over a pending FMASK settle alone submitted " + std::to_string(after.submitted - before.submitted) + " settle reads, not the FMASK's alone"); + Require(after.submitted > before.submitted && after.rewatched > before.rewatched && Watched(address, importBytes) && settled != nullptr && !settled->unwatched, std::string("(m) the initial DCC import over ") + name + " did not settle the import and return it to the write watch"); + if (fmask != nullptr) { + const auto* settledFmask = ExistingHostImportFor(context, target.fmaskAddress, fmaskBytes); + Require(Watched(target.fmaskAddress, fmaskBytes) && settledFmask != nullptr && !settledFmask->unwatched, "(m) the FMASK's import did not settle back into the write watch" + describe(settledFmask, target.fmaskAddress, fmaskBytes)); + } + ColorTarget destination{}; + destination.address = reinterpret_cast(dest); + destination.surfaceAddress = destination.address; + destination.extent = target.extent; + destination.surfaceExtent = target.extent; + destination.format = VK_FORMAT_R8G8B8A8_UNORM; + destination.elementBytes = 4; + destination.bytes = destBytes; + destination.samples = 1; + destination.tileMode = ColorTileMode::RenderTarget; + RunColorMetadataPass(context, {ColorMetadataPass::Mode::Resolve, {destination}, target}); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + std::vector resolved(destBytes / 4); + AgcDriver::GuestMemory::Read(destination.address, std::as_writable_bytes(std::span(resolved))); + Require(std::ranges::all_of(resolved, [](std::uint32_t word) { return word == 0xffffffffu; }), std::string("(m) the target made over ") + name + " did not start from its DCC fast clear"); + BumpCollectEpoch(); + if (fmask != nullptr) { + const auto collectedFmask = CollectWrites(target.fmaskAddress, fmaskBytes); + Require(collectedFmask != 0, "(m) the settled FMASK import is not collected"); + static_cast(fmask)[0] = 0x22; + std::string fmaskReuse; + try { + MultisampleTargetView(context, target); + } catch (const std::exception& error) { + fmaskReuse = error.what(); + } + Require(fmaskReuse.find("writes to a resident native FMASK are not implemented") != std::string::npos, "(m) the resident target missed the CPU store to its FMASK: " + fmaskReuse); + return; + } + const auto collected = CollectWrites(address, importBytes); + Require(collected != 0, "(m) the settled import is not collected"); + static_cast(block)[importBytes - 1] = 0x77; + static_cast(block)[colorBytes] = 0x00; + CollectWritesUncached(address, importBytes); + Require(!UnchangedSince(address + importBytes - 1, 1, collected) && !UnchangedSince(address + colorBytes, 1, collected), "(m) a CPU store after the initial DCC import was missed"); + std::string reuse; + try { + MultisampleTargetView(context, target); + } catch (const std::exception& error) { + reuse = error.what(); + } + Require(reuse.find("writes to a resident multisampled DCC target or its metadata are not implemented") != std::string::npos, "(m) the resident target missed the CPU store to its DCC keys: " + reuse); + }; + run(Case::Pending, 128); + run(Case::PartialBlock, 384); + run(Case::ComparedNeighbour, 384); + run(Case::DeferredOff, 128); + run(Case::UnwatchedKeys, 128); + run(Case::PendingFmask, 128); + run(Case::PendingFmaskAlone, 128); + std::cout << "initial multisampled DCC over a pending settle tests passed\n"; + return true; +} #endif void remappedImportTests(const Device& device) { @@ -2310,7 +2864,7 @@ void remappedImportTests(const Device& device) { void hostImportRetirementTests(const Device& device) { const auto& context = device.GetContext(); - constexpr std::size_t UnitBytes = 65536; + constexpr std::size_t UnitBytes = 2 * 65536; constexpr std::size_t AllocationBytes = 4 * UnitBytes; constexpr std::size_t CopyBytes = 256; #ifdef _WIN32 @@ -2443,8 +2997,8 @@ void secondMappingImportTests(const Device& device) { void* alias = nullptr; std::int64_t physical = -1; ~Release() { - if (alias != nullptr) sceKernelMunmap(alias, page); - if (mapping != nullptr) sceKernelMunmap(mapping, bytes); + if (alias != nullptr) sceKernelMunmap(reinterpret_cast(alias), page); + if (mapping != nullptr) sceKernelMunmap(reinterpret_cast(mapping), bytes); if (physical >= 0) sceKernelReleaseDirectMemory(physical, bytes); } } release; @@ -2505,11 +3059,17 @@ int reimportBench(const Device& device) { constexpr std::size_t moves = 5; constexpr std::size_t page = 4096; constexpr std::size_t alignment = 2ull << 20u; + constexpr std::size_t requiredImportBytes = bytes + inside; + const auto availableImportBytes = HostImportBudgetRemaining(context); + if (availableImportBytes < requiredImportBytes) { + std::cout << "skipped, the re-import bench needs " << (requiredImportBytes >> 20u) << " MiB of simultaneous host imports; the current budget has " << (availableImportBytes >> 20u) << " MiB available\n"; + return 77; + } struct Release { void* mapping = nullptr; std::int64_t physical = -1; ~Release() { - if (mapping != nullptr) sceKernelMunmap(mapping, bytes); + if (mapping != nullptr) sceKernelMunmap(reinterpret_cast(mapping), bytes); if (physical >= 0) sceKernelReleaseDirectMemory(physical, bytes); } }; @@ -2619,15 +3179,15 @@ int reimportBench(const Device& device) { throw; } gpu.lock(); - Require(importedAgain.empty(), "the kept side was imported again instead of trimmed after the " + importedAgain + " cuts"); - std::printf("re-import bench: every cut at the head, at the tail and inside the range kept the larger side by trimming the first import\n"); + Require(importedAgain == "head, tail, inside", "a cut kept memory imported across the changed host range instead of importing the remaining side again: " + importedAgain); + std::printf("re-import bench: every cut at the head, at the tail and inside the range retired the original import before importing the remaining side\n"); return 0; } -int trimmedImportTests(const Device& device) { +int hostImportCutTests(const Device& device) { const auto& context = device.GetContext(); if (context.hostImportAlignment == 0 || context.settleQueue == VK_NULL_HANDLE || !AgcDriver::GuestMemory::WriteWatched()) { - std::cout << "skipped, the trimmed import tests require host imports, a settle queue and write watching\n"; + std::cout << "skipped, the host import cut tests require host imports, a settle queue and write watching\n"; return 77; } constexpr std::size_t bytes = 32ull << 20u; @@ -2644,14 +3204,13 @@ int trimmedImportTests(const Device& device) { std::size_t keptEnd; std::size_t smaller; std::size_t smallerEnd; - bool trimmed; }; constexpr std::array cases{{ - {"a page made inaccessible inside the import, nearer its start", inside + guard, guard, false, inside + 2 * guard, bytes, 0, inside + guard, true}, - {"a page made inaccessible inside the import, nearer its end", bytes - inside - 2 * guard, guard, false, 0, bytes - inside - 2 * guard, bytes - inside - guard, bytes, true}, - {"an unmap of a 2 MiB run inside the import", inside, run, true, inside + run, bytes, 0, inside, true}, - {"an unmap of the import's first 2 MiB run", 0, run, true, run, bytes, 0, 0, true}, - {"an unmap of 1440 KiB at the start of the import's first 2 MiB run", 0, 1440ull << 10u, true, 1440ull << 10u, bytes, 0, 0, false}, + {"a page made inaccessible inside the import, nearer its start", inside + guard, guard, false, inside + 2 * guard, bytes, 0, inside + guard}, + {"a page made inaccessible inside the import, nearer its end", bytes - inside - 2 * guard, guard, false, 0, bytes - inside - 2 * guard, bytes - inside - guard, bytes}, + {"an unmap of a 2 MiB run inside the import", inside, run, true, inside + run, bytes, 0, inside}, + {"an unmap of the import's first 2 MiB run", 0, run, true, run, bytes, 0, 0}, + {"an unmap of 1440 KiB at the start of the import's first 2 MiB run", 0, 1440ull << 10u, true, 1440ull << 10u, bytes, 0, 0}, }}; struct Backing { void* mapping = nullptr; @@ -2660,8 +3219,8 @@ int trimmedImportTests(const Device& device) { std::size_t holeBytes = 0; ~Backing() { auto* memory = static_cast(mapping); - if (memory != nullptr && hole != 0) sceKernelMunmap(memory, hole); - if (memory != nullptr && hole + holeBytes < bytes) sceKernelMunmap(memory + hole + holeBytes, bytes - hole - holeBytes); + if (memory != nullptr && hole != 0) sceKernelMunmap(reinterpret_cast(memory), hole); + if (memory != nullptr && hole + holeBytes < bytes) sceKernelMunmap(reinterpret_cast(memory + hole + holeBytes), bytes - hole - holeBytes); if (physical >= 0) sceKernelReleaseDirectMemory(physical, bytes); } }; @@ -2679,19 +3238,18 @@ int trimmedImportTests(const Device& device) { const auto check = [&](const Case& test) { const std::string name = test.name; Backing backing; - Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, run, 0, &backing.physical) == 0, "allocate the trimmed import test backing"); - Require(sceKernelMapDirectMemory(&backing.mapping, bytes, 0x33, 0, backing.physical, run) == 0, "map the trimmed import test backing"); + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, run, 0, &backing.physical) == 0, "allocate the host import cut test backing"); + Require(sceKernelMapDirectMemory(&backing.mapping, bytes, 0x33, 0, backing.physical, run) == 0, "map the host import cut test backing"); const auto address = reinterpret_cast(backing.mapping); auto* memory = static_cast(backing.mapping); for (std::size_t offset = 0; offset < bytes; offset += page) std::memset(memory + offset, static_cast(0x40u + (offset / page) % 64u), 64); gpu.lock(); const auto* whole = HostImportFor(context, address, bytes); Require(whole != nullptr && whole->base == address && whole->bytes == bytes && whole->alias == nullptr && !whole->unwatched, name + ": the range was not imported whole, settled and watched"); - const auto original = whole->memory; auto* cut = memory + test.offset; gpu.unlock(); if (test.unmap) { - Require(sceKernelMunmap(cut, test.length) == 0, name + ": the unmap failed"); + Require(sceKernelMunmap(reinterpret_cast(cut), test.length) == 0, name + ": the unmap failed"); backing.hole = test.offset; backing.holeBytes = test.length; } else { @@ -2702,15 +3260,13 @@ int trimmedImportTests(const Device& device) { const auto end = address + test.keptEnd; const auto* kept = HostImportFor(context, begin, static_cast(end - begin)); Require(kept != nullptr && kept->base == begin && kept->base + kept->bytes == end, name + ": the larger side is not imported whole"); - const auto keptMemory = kept->memory; - if (test.trimmed) Require(kept->memory == original, "the larger side of " + name + " was imported again instead of kept"); - else Require(kept->memory != original, "the side of " + name + " whose 2 MiB run the unmap replaced is served from the import that pinned the run's old pages"); + Require(kept->memoryBase == begin && kept->pinnedBytes == end - begin, "the side of " + name + " is served from the import that pinned the changed host range"); requireImportServesGuest(context, recorder, *kept, begin, end, ++salt, "the larger side of " + name); if (test.smallerEnd != test.smaller) { const auto* smaller = HostImportFor(context, address + test.smaller, test.smallerEnd - test.smaller); - Require(smaller != nullptr && smaller->base == address + test.smaller && smaller->bytes == test.smallerEnd - test.smaller && smaller->memory != original, "the smaller side of " + name + " is not imported apart from the larger one"); + Require(smaller != nullptr && smaller->base == address + test.smaller && smaller->bytes == test.smallerEnd - test.smaller && smaller->memoryBase == address + test.smaller && smaller->pinnedBytes == test.smallerEnd - test.smaller, "the smaller side of " + name + " is not imported apart from the larger one"); requireImportServesGuest(context, recorder, *smaller, address + test.smaller, address + test.smallerEnd, ++salt, "the smaller side of " + name); - Require(HostImportFor(context, begin, static_cast(end - begin)) == kept && kept->memory == original, "importing the smaller side of " + name + " dropped the larger one"); + Require(HostImportFor(context, begin, static_cast(end - begin)) == kept && kept->memoryBase == begin && kept->pinnedBytes == end - begin, "importing the smaller side of " + name + " replaced the safe import of the larger one"); } if (test.unmap) { gpu.unlock(); @@ -2722,29 +3278,28 @@ int trimmedImportTests(const Device& device) { const auto from = reinterpret_cast(cut); const auto* remapped = HostImportFor(context, address, bytes); Require(remapped != nullptr && remapped->base == address && remapped->bytes == bytes, name + ": the range mapped again is not imported whole"); - Require(remapped->memory != keptMemory, "after " + name + " the pages mapped again are served from the import made before they were mapped again"); + Require(remapped->memoryBase == address && remapped->pinnedBytes == bytes, "after " + name + " the pages mapped again are served from the import that pinned the old ones"); requireImportServesGuest(context, recorder, *remapped, from, from + test.length, ++salt, "the pages mapped again after " + name); requireImportServesGuest(context, recorder, *remapped, begin, end, ++salt, "the larger side after " + name + " and a new mapping"); } cleanup(); gpu.unlock(); - std::cout << "trimmed import: " << name << ": the larger side was " << (test.trimmed ? "kept" : "imported again") << " and the GPU and the write watch see the guest's bytes" << (test.unmap ? ", also after the pages were mapped again" : "") << '\n'; + std::cout << "host import after cut: " << name << ": the changed import was retired before the remaining range was imported, and the GPU and write watch see the guest's bytes" << (test.unmap ? ", also after the pages were mapped again" : "") << '\n'; }; const auto remapWhileTrimmed = [&] { - const std::string name = "an unmap and a new mapping of the kept side while the import waits trimmed"; + const std::string name = "an unmap and a new mapping of the retained side while the old import is retired"; Backing backing; - Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, run, 0, &backing.physical) == 0, "allocate the trimmed import test backing"); - Require(sceKernelMapDirectMemory(&backing.mapping, bytes, 0x33, 0, backing.physical, run) == 0, "map the trimmed import test backing"); + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, run, 0, &backing.physical) == 0, "allocate the host import cut test backing"); + Require(sceKernelMapDirectMemory(&backing.mapping, bytes, 0x33, 0, backing.physical, run) == 0, "map the host import cut test backing"); const auto address = reinterpret_cast(backing.mapping); auto* memory = static_cast(backing.mapping); for (std::size_t offset = 0; offset < bytes; offset += page) std::memset(memory + offset, static_cast(0x40u + (offset / page) % 64u), 64); gpu.lock(); const auto* whole = HostImportFor(context, address, bytes); Require(whole != nullptr && whole->base == address && whole->bytes == bytes && whole->alias == nullptr && !whole->unwatched, name + ": the range was not imported whole, settled and watched"); - const auto original = whole->memory; gpu.unlock(); Require(sceKernelMprotect(memory, run, 0x03) == 0, name + ": the protection of the head failed"); - Require(sceKernelMunmap(memory + run, bytes - run) == 0, name + ": the unmap of the kept side failed"); + Require(sceKernelMunmap(reinterpret_cast(memory + run), bytes - run) == 0, name + ": the unmap of the kept side failed"); backing.hole = run; backing.holeBytes = bytes - run; void* again = memory + run; @@ -2757,10 +3312,10 @@ int trimmedImportTests(const Device& device) { const auto* remapped = HostImportFor(context, begin, static_cast(end - begin)); Require(remapped != nullptr && remapped->base == begin && remapped->base + remapped->bytes == end, name + ": the pages mapped again are not imported whole"); requireImportServesGuest(context, recorder, *remapped, begin, end, ++salt, "the pages mapped again after " + name); - Require(remapped->memory != original, "after " + name + " the pages mapped again are served from the import that pinned the old ones"); + Require(remapped->memoryBase == begin && remapped->pinnedBytes == end - begin, "after " + name + " the pages mapped again are served from the import that pinned the old ones"); cleanup(); gpu.unlock(); - std::cout << "trimmed import: " << name << ": the pages mapped again were imported again and the GPU and the write watch see the guest's bytes\n"; + std::cout << "host import cut: " << name << ": the pages mapped again were imported again and the GPU and the write watch see the guest's bytes\n"; }; gpu.unlock(); try { @@ -2771,7 +3326,7 @@ int trimmedImportTests(const Device& device) { throw; } gpu.lock(); - std::cout << "Trimmed host import tests passed\n"; + std::cout << "Host import cut lifetime tests passed\n"; return 0; } #endif @@ -3064,13 +3619,121 @@ void resourceReadTests(const Device& device, Recorder& recorder) { HostImportFor(context, address, bytes); } +bool bdaStagingTests(const Device& device, Recorder& recorder) { + const auto& context = device.GetContext(); + if (context.hostImportAlignment == 0 || !context.bufferDeviceAddress || !AddressSpaceCounters().enabled || std::getenv("APS5_NO_BDA_STAGING") != nullptr) { + std::cout << "skipped, host imports are unavailable (no device addresses or address space cache)\n"; + return false; + } + constexpr std::size_t bytes = 4u << 20u; +#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 test block"); + std::memset(block, 0x11, bytes); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true, true); + } + const auto* import = HostImportFor(context, address, bytes); + if (import == nullptr || import->address == 0) { + std::cout << "skipped, host imports are unavailable: the test block's import was refused\n"; + return false; + } + const auto importBase = import->base; + const auto importAddress = import->address; + const auto importBuffer = import->buffer; + auto* words = static_cast(block); + const auto fill = context.Function("vkCmdFillBuffer"); + const auto element = address + 8192; + constexpr std::size_t elementBytes = 64u << 10u; + constexpr std::size_t first = 8192 / 4; + const auto checkTable = [&](const std::vector& ranges, bool staged) { + for (std::size_t index = 1; index < ranges.size(); ++index) Require(ranges[index - 1].end <= ranges[index].begin, "the BDA table of a staged build has overlapping or unsorted ranges"); + bool covered = false; + std::uint64_t reached = address; + for (const auto& range : ranges) { + if (range.end <= address || range.begin >= address + bytes) continue; + Require(range.begin == reached, "the BDA table leaves a hole in the registered block"); + reached = range.end; + const bool inImport = range.deviceAddress == importAddress + (range.begin - importBase); + const bool overElement = range.begin < element + elementBytes && element < range.end; + covered = covered || overElement; + if (overElement && staged) { + Require(range.begin == element && range.end == element + elementBytes && !inImport, "the staged element's BDA range does not send its addresses to the shadow"); + Require((range.permissions & ShaderRecompiler::BdaAbi::Write) != 0u, "the staged element's BDA range is not writable"); + } else { + Require(inImport, "a BDA range outside the staged element left the import"); + } + } + Require(covered, "no BDA range covers the written element"); + Require(reached == address + bytes, "the BDA table does not reach the end of the registered block"); + }; + { + GuestBufferMemory memory(context); + memory.AllowDeviceStaging(); + memory.AcquireRegistered(); + memory.AddWritable(element, elementBytes, false, false); + memory.AddReadable(element + 16, 64); + memory.AddReadable(address + (1u << 20u), 64); + memory.Upload(true); + checkTable(memory.AddressRanges(), true); + const auto cached = memory.CachedAddressTable(); + Require(cached.has_value() && cached->ranges != nullptr && cached->ranges->size() == memory.AddressRanges().size(), "the cached BDA table of a staged build differs from its ranges"); + std::uint32_t adjustment = 0; + const auto view = memory.Descriptor(element, elementBytes, adjustment); + Require(view.buffer != importBuffer, "the written element of an address-based build is bound in place"); + std::uint32_t innerAdjustment = 0; + Require(memory.Descriptor(element + 16, 64, innerAdjustment).buffer == view.buffer, "a read inside the staged element does not read its shadow"); + recorder.Sync(); + auto commands = recorder.Commands(); + fill(commands, view.buffer, view.offset + adjustment + 64u * 4u, 64, 0x22222222u); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT); + memory.RecordCopyBacks(recorder); + recorder.Sync(); + memory.MarkDirectWrites(); + std::size_t wrong = 0; + for (std::size_t index = 0; index < bytes / 4u; ++index) { + const auto at = index >= first ? index - first : bytes; + const auto expected = at >= 64 && at < 80 ? 0x22222222u : 0x11111111u; + if (words[index] != expected && wrong++ < 8) std::cout << "address-based staging copy-back dword " << index << " holds 0x" << std::hex << words[index] << ", expected 0x" << expected << std::dec << "\n"; + } + Require(wrong == 0, "an address-based staged copy-back left the wrong guest bytes"); + memory.WriteBack(); + } + std::memset(block, 0x11, bytes); + { + GuestBufferMemory memory(context); + memory.AllowDeviceStaging(); + memory.AcquireRegistered(); + memory.AddReadable(element - 16, 64); + memory.AddWritable(element, elementBytes, false, false); + memory.Upload(true); + checkTable(memory.AddressRanges(), false); + std::uint32_t adjustment = 0; + Require(memory.Descriptor(element, elementBytes, adjustment).buffer == importBuffer, "an element crossed by an in-place read was staged"); + memory.RecordCopyBacks(recorder); + recorder.Sync(); + memory.WriteBack(); + } + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + return true; +} + bool readWrittenStagingTests(const Device& device, Recorder& recorder) { const auto& context = device.GetContext(); if (context.hostImportAlignment == 0) { std::cout << "host imports unavailable: read and written staging not tested\n"; return false; } - constexpr std::size_t bytes = 8u << 20u; + constexpr std::size_t bytes = 40u << 20u; #ifdef _WIN32 void* block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); #else @@ -3100,11 +3763,11 @@ bool readWrittenStagingTests(const Device& device, Recorder& recorder) { return direct; }; Require(!boundInPlace(address + 4096, 1u << 20u, false), "a written 1 MiB element was not staged"); - Require(boundInPlace(address + 4096, 4u << 20u, false), "a written-only 4 MiB element was staged past the written window"); + Require(boundInPlace(address + 4096, 4u << 20u, false) != context.discreteGpu, context.discreteGpu ? "a written-only 4 MiB element was bound in place on a discrete GPU" : "a written-only 4 MiB element was staged past the written window"); + Require(boundInPlace(address + 4096, 12u << 20u, false), "a written-only 12 MiB element was staged past the discrete written window"); Require(!boundInPlace(address + 4096, 4u << 20u, true), "a read and written 4 MiB element was bound in place"); - const auto buildInPlace = [&](std::uint64_t dispatchThreads, bool read, std::uint32_t stride, std::uint32_t extent) { + const auto buildInPlace = [&](std::uint64_t dispatchThreads, bool read, std::uint32_t stride, std::uint32_t extent, std::uint32_t elementBytes = 12u << 20u) { const auto element = address + 4096; - constexpr std::uint32_t elementBytes = 4u << 20u; const std::uint32_t records = stride != 0 ? elementBytes / stride : elementBytes; ShaderRecompiler::RecompileResult program; ShaderRecompiler::DescriptorBinding binding; @@ -3126,13 +3789,18 @@ bool readWrittenStagingTests(const Device& device, Recorder& recorder) { recorder.Sync(); return direct; }; - Require(!buildInPlace((4u << 20u) / 16u, true, 16, 0), "a dispatch sweeping a read and written 4 MiB element bound it in place"); - Require(buildInPlace(1024, true, 16, 0), "a dispatch touching 16 KiB of a read and written 4 MiB element staged all of it"); - Require(buildInPlace(0, true, 16, 0), "a dispatch of unknown size staged a read and written 4 MiB element"); - Require(buildInPlace((4u << 20u) / 16u, false, 16, 0), "a dispatch sweeping a written-only 4 MiB element staged it"); - Require(!buildInPlace((4u << 20u) / 32u, true, 0, 32), "a dispatch sweeping a read and written 4 MiB raw element bound it in place"); - Require(buildInPlace(1024, true, 0, 32), "a dispatch touching 32 KiB of a read and written 4 MiB raw element staged all of it"); - Require(buildInPlace((4u << 20u) / 32u, true, 0, 0), "a raw element without an access extent was staged"); + Require(!buildInPlace((12u << 20u) / 16u, true, 16, 0), "a dispatch sweeping a read and written 12 MiB element bound it in place"); + Require(buildInPlace(1024, true, 16, 0), "a dispatch touching 16 KiB of a read and written 12 MiB element staged all of it"); + Require(buildInPlace(0, true, 16, 0), "a dispatch of unknown size staged a read and written 12 MiB element"); + Require(buildInPlace((12u << 20u) / 32u, false, 32, 0) != context.discreteGpu, context.discreteGpu ? "a dispatch sweeping a written-only 12 MiB element of 32-byte records bound it in place on a discrete GPU" : "a dispatch sweeping a written-only 12 MiB element of 32-byte records staged it on a GPU without separate VRAM"); + Require(buildInPlace((12u << 20u) / 16u, false, 16, 0), "a dispatch sweeping a written-only 12 MiB element of 16-byte records, which its stores write contiguously, staged it"); + Require(buildInPlace(1024, false, 32, 0), "a dispatch touching 32 KiB of a written-only 12 MiB element staged all of it"); + Require(buildInPlace((12u << 20u) / 32u, false, 0, 32), "a dispatch writing a written-only 12 MiB raw element staged it"); + Require(buildInPlace(204800, false, 160, 0, 204800u * 160u) != context.discreteGpu, context.discreteGpu ? "a dispatch sweeping a written-only 31.25 MiB element of 160-byte records bound it in place on a discrete GPU" : "a dispatch sweeping a written-only 31.25 MiB element of 160-byte records staged it on a GPU without separate VRAM"); + Require(buildInPlace(204800, true, 160, 0, 204800u * 160u) != context.discreteGpu, context.discreteGpu ? "a dispatch sweeping a read and written 31.25 MiB element bound it in place on a discrete GPU" : "a dispatch sweeping a read and written 31.25 MiB element staged it past the 16 MiB cap on a GPU without separate VRAM"); + Require(!buildInPlace((12u << 20u) / 32u, true, 0, 32), "a dispatch sweeping a read and written 12 MiB raw element bound it in place"); + Require(buildInPlace(1024, true, 0, 32), "a dispatch touching 32 KiB of a read and written 12 MiB raw element staged all of it"); + Require(buildInPlace((12u << 20u) / 32u, true, 0, 0), "a raw element without an access extent was staged"); Require(std::all_of(static_cast(block), static_cast(block) + bytes, [](std::byte value) { return value == std::byte{0x11}; }), "a staged copy-back changed the guest bytes"); { GuestAllocations::Mutation mutation; @@ -3594,10 +4262,11 @@ void drawSnapshotTableSetTests(const Device& device, Recorder& recorder) { } constexpr std::uint32_t width = 4; constexpr std::uint32_t height = 4; - alignas(256) static std::array texels{}; + alignas(256) static std::array texels{}; texels.fill(1.0f); const auto address = static_cast(reinterpret_cast(texels.data())); const std::array texture{static_cast(address >> 8u), static_cast((address >> 40u) & 0xffu) | (22u << 20u) | (((width - 1u) & 3u) << 30u), ((width - 1u) >> 2u) | ((height - 1u) << 14u), 0xfacu | (9u << 28u), 0u, 0u, 0u, 0u}; + Require(DescribeSurface(DecodeTextureResource(texture)).guestBytes <= sizeof(texels), "the draw snapshot table set test T# reads past its texels"); constexpr auto bindingCount = static_cast(ShaderRecompiler::RuntimeAbi::Binding::Count); const auto addTable = [&](ShaderRecompiler::RecompileResult& program, ShaderRecompiler::RuntimeAbi::Stage stage, std::uint32_t set, std::uint32_t elements) { ShaderRecompiler::DescriptorBinding map{}; @@ -4191,6 +4860,46 @@ void drawSnapshotEvictionTests(const Device& device) { Require(cache.ReusableDrawSnapshot(address, 16) == nullptr && cache.ReusableDrawSnapshot(address, 16) == nullptr, "a snapshot outlived a CPU store"); } +void containingSnapshotTests(const Device& device) { + using namespace AgcDriver::GuestMemory; + constexpr std::size_t bytes = 131072; + void* block = WriteWatched() ? AllocateWatched(bytes, 65536) : nullptr; + if (block == nullptr) { + std::cout << "write watching unavailable: containing draw snapshots not tested\n"; + return; + } + struct Release { + void* block; + ~Release() { ReleaseWatched(block, bytes); } + } release{block}; + const auto address = reinterpret_cast(block); + const VkDeviceSize alignment = std::max(device.GetContext().limits.minStorageBufferOffsetAlignment, 4); + Recorder cache(device.GetContext()); + const auto whole = std::make_shared(device.GetContext(), bytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const auto exact = std::make_shared(device.GetContext(), 4096, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const auto registry = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + const auto generation = CollectWrites(address, bytes); + Require(generation != 0, "the watched block has no generation"); + cache.KeepDrawSnapshot(address, bytes, generation, registry, whole); + const auto inner = address + 65536 + 16 * alignment; + VkDeviceSize offset = 1; + Require(cache.ReusableDrawSnapshot(inner, 4096) == nullptr, "a lookup without an offset took a containing snapshot"); + Require(cache.ReusableDrawSnapshot(inner, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == whole && offset == inner - address, "an unchanged range inside a kept snapshot was not taken from it at its offset"); + Require(cache.ReusableDrawSnapshot(inner, address + bytes - inner + 4, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == nullptr, "a range past the end of a kept snapshot was taken from it"); + Require(cache.ReusableDrawSnapshot(inner + alignment / 2, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == nullptr, "a misaligned range was taken from a kept snapshot"); + Require(cache.ReusableDrawSnapshot(inner, 4096, Recorder::SnapshotUse::Vertex, nullptr, 0, &offset) == nullptr, "a vertex lookup took a storage snapshot"); + cache.KeepDrawSnapshot(inner, 4096, generation, registry, exact); + offset = 1; + Require(cache.ReusableDrawSnapshot(inner, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == exact && offset == 0, "an exact snapshot lost to a containing one"); + std::memset(block, 0x5a, 16); + CollectWrites(address, 16); + Require(cache.ReusableDrawSnapshot(inner + 4096, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == whole && offset == inner + 4096 - address, "a store outside the range stopped it from being taken from a containing snapshot"); + Require(cache.ReusableDrawSnapshot(address, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == nullptr, "a range over a CPU store was taken from a containing snapshot"); + std::memset(reinterpret_cast(inner + 4096), 0x5a, 16); + CollectWrites(inner + 4096, 16); + Require(cache.ReusableDrawSnapshot(inner + 4096, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == nullptr, "a containing snapshot outlived a CPU store into the range"); +} + void unwrittenSnapshotTests(const Device& device) { Recorder cache(device.GetContext()); const auto buffer = std::make_shared(device.GetContext(), 16, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); @@ -4580,6 +5289,9 @@ bool unitShadowTests(const Device& device, Recorder& recorder) { retile(*half, unit3, unit / 2, 0x22, true); Require(AnyShadowedOverlaps(unit3, 1) && AnyShadowedOverlaps(unit4 - 1, 1) && AnyShadowedOverlaps(unit3 + 1000, 64), "a fresh unit is not seen as shadowed"); Require(!AnyShadowedOverlaps(unit3 - 1, 1) && !AnyShadowedOverlaps(unit4, unit) && !AnyShadowedOverlaps(address, 3 * unit), "a range outside the unit is seen as shadowed"); + const std::array, 2> outside{{{unit3 - 8, unit3}, {unit4, unit4 + unit}}}; + const std::array, 2> reaching{{{unit3 - 8, unit3}, {unit4 - 4, unit4}}}; + Require(!AnyShadowedOverlaps(outside) && AnyShadowedOverlaps(reaching), "a set of ranges around the fresh unit was answered wrongly"); { // An upload's runs over units 2..4 split at unit 3's freshness; the fresh piece reads the slab. const std::pair run{2 * unit, 5 * unit}; @@ -4677,6 +5389,82 @@ bool unitShadowTests(const Device& device, Recorder& recorder) { return true; } +void idlePublishWaitTests(const Device& device, Recorder& recorder) { + using namespace AgcDriver::GuestMemory; + const auto& context = device.GetContext(); + constexpr std::uint64_t unit = 65536; + constexpr std::size_t bytes = 8 * unit; + void* block = AllocateWatched(bytes, 65536); + Require(block != nullptr, "unit shadows are on without write watching"); + struct Release { + void* block; + ~Release() { ReleaseWatched(block, bytes); } + } release{block}; + std::memset(block, 0x11, bytes); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + ~Unregister() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + } + } unregister{context, block, address}; + const auto* import = HostImportFor(context, address, bytes); + if (import == nullptr || !Watched(address, bytes)) { + std::cout << "host import of the idle publish test block refused or not watched: idle publish wait not tested\n"; + return; + } + recorder.Sync(); + CollectWritesUncached(address, bytes); + auto* words = static_cast(block); + const auto copyBuffer = context.Function("vkCmdCopyBuffer"); + const auto retile = [&](std::uint64_t begin, std::uint8_t value) { + auto destination = ShadowDestinationFor(context, *import, begin, begin + unit); + Require(destination.has_value() && destination->seedUnits.empty(), "a whole-unit retile piece has no destination"); + auto pattern = std::make_shared(context, static_cast(unit), VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + std::memset(pattern->Bytes().data(), value, pattern->Bytes().size()); + const auto commands = recorder.Commands(); + recorder.Keep(pattern); + recorder.Keep(destination->slab); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + const VkBufferCopy piece{0, destination->offset, unit}; + copyBuffer(commands, pattern->Handle(), destination->buffer, 1, &piece); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + recorder.MarkCovered(VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + const ShadowedRange range{begin, begin + unit, destination->slab}; + MarkShadowed(*import, std::span(&range, 1), TrackerGeneration()); + Require(AnyShadowedOverlaps(begin, unit), "the retiled unit is not shadowed"); + }; + retile(address + 2 * unit, 0x22); + retile(address + 3 * unit, 0x33); + StorageTexture::FlushAllPending("idle"); + Require(!AnyShadowedOverlaps(address + 2 * unit, static_cast(2 * unit)), "the idle flush did not publish the shadowed units"); + Require(!recorder.PendingWriteOverlaps(address + 2 * unit, static_cast(2 * unit)), "the idle flush returned before its publish's copies completed"); + words[2 * unit] = 0x44; + words[4 * unit - 1] = 0x45; + recorder.Sync(); + Require(words[2 * unit] == 0x44 && words[4 * unit - 1] == 0x45, "a CPU store made after the idle flush returned was overwritten by its publish"); + Require(words[2 * unit + 1] == 0x22 && words[3 * unit - 1] == 0x22 && words[3 * unit] == 0x33 && words[1 * unit] == 0x11 && words[4 * unit] == 0x11, "the idle publish's bytes are wrong"); + words[5 * unit] = 0x11; + CollectWritesUncached(address, bytes); + retile(address + 5 * unit, 0x55); + StorageTexture::FlushAllPending("label"); + const bool pending = recorder.PendingWriteOverlaps(address + 5 * unit, unit); + recorder.Sync(); + Require(words[5 * unit] == 0x55 && words[6 * unit - 1] == 0x55, "the label flush's publish did not land: the unit read as written after its retile, so the publish dropped it as stale"); + Require(pending, "a publish of a flush other than the idle one was waited"); + std::cout << "idle publish wait verified\n"; +} + void storageRefreshTests(const Device& device, Recorder& recorder, bool watched) { const auto& base = device.GetContext(); if (base.hostImportAlignment == 0) { @@ -4847,6 +5635,271 @@ void storageRefreshTests(const Device& device, Recorder& recorder, bool watched) recorder.Sync(); } +bool addressShadowTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (!UnitShadowEnabled() || base.hostImportAlignment == 0) { + std::cout << "skipped, unit shadows or host imports are unavailable\n"; + return false; + } + const auto decided = PrepareImportWatch(base); +#ifndef _WIN32 + const auto probe = ProbeImportWriteProtection(base); + if (probe.failure != nullptr || probe.writtenAtImport != 0 || probe.writtenAfterSubmit != 0) { + std::cout << "skipped, the device reports watched host memory written when it imports or reads it\n"; + return false; + } + SetImportWatch(base, ImportWatch::Watch); +#endif + struct Restore { + const Context& context; + ImportWatch decided; + ~Restore() { SetImportWatch(context, decided); } + } restore{base, decided}; + constexpr std::uint32_t width = 1024; + constexpr std::uint32_t height = 512; + constexpr std::size_t bytes = width * height * 4; + constexpr std::size_t allocated = bytes + 65536; + void* block = AllocateWatched(allocated, 65536); + if (block == nullptr) { + std::cout << "skipped, no write watching\n"; + return false; + } + struct Release { + void* block; + ~Release() { ReleaseWatched(block, allocated); } + } release{block}; + std::memset(block, 0x55, bytes); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + ~Unregister() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + } + } unregister{base, block, address}; + if (HostImportFor(base, address, bytes) == nullptr || !AgcDriver::GuestMemory::Watched(address, bytes)) { + std::cout << "skipped, the heap is not imported or not watched\n"; + return false; + } + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = width; + resource.height = height; + 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 == bytes, "the heap's surface has an unexpected size"); + const auto* words = static_cast(block); + const auto holds = [&](std::array texel) { + for (std::size_t i = 0; i < bytes; ++i) { + if (words[i] != texel[i % 4]) return false; + } + return true; + }; + const auto settle = [&] { + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + }; + struct Build { + bool copied; + bool current; + }; + const auto build = [&](std::array texel) { + GuestBufferMemory leased(context); + leased.AcquireRegistered(); + leased.Upload(true); + Build result{recorder.PendingWriteOverlaps(address, bytes), false}; + recorder.Submit(); + device.WaitQueue(); + result.current = holds(texel); + leased.WriteBack(); + recorder.Sync(); + return result; + }; + const auto none = build({0x55, 0x55, 0x55, 0x55}); + Require(!none.copied && none.current, "an address-based build over a heap with no shadowed units recorded a copy"); + { + auto image = StorageTexture::Create(context, detiler, resource, 0); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + const auto draw = [&](VkClearColorValue value) { + const auto commands = recorder.Commands(); + recorder.Keep(image); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + context.Function("vkCmdClearColorImage")(commands, image->Image(), VK_IMAGE_LAYOUT_GENERAL, &value, 1, &range); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + image->MarkDirty(); + }; + draw({{1.0f, 0.0f, 0.0f, 1.0f}}); + StorageTexture::FlushPending(address, bytes, nullptr, "storage refresh", PublishScope::None); + settle(); + if (!AnyShadowedOverlaps(address, bytes)) { + std::cout << "skipped, the image's write-back did not retile into the unit shadow\n"; + return false; + } + Require(holds({0x55, 0x55, 0x55, 0x55}), "a write-back into the unit shadow reached the import"); + static_cast(ShadowReport()); + const auto shadowed = build({255, 0, 0, 255}); + const auto report = ShadowReport(); + Require(shadowed.current, "an address-based build over a heap left the unit shadow's stored results unpublished: the import held the earlier bytes at its submission"); + Require(shadowed.copied && !AnyShadowedOverlaps(address, bytes), "an address-based build published the heap's shadowed units without noting the copy, or left some unpublished"); + Require(report.find(" address 1/") != std::string::npos, "the address-based build's publish was not counted under its own reason:" + report); + const auto again = build({255, 0, 0, 255}); + Require(!again.copied && again.current, "a second address-based build with nothing newly retiled recorded another copy"); + draw({{0.0f, 1.0f, 0.0f, 1.0f}}); + const auto pending = build({255, 0, 0, 255}); + Require(StorageTexture::FindPending(address, bytes) == image, "an address-based build stored the pending results of an image over its heap"); + Require(!pending.copied && pending.current, "an address-based build changed the heap while an image's results were pending"); + StorageTexture::FlushPending(address, bytes, nullptr, "memory access"); + settle(); + } + recorder.Sync(); + std::cout << "address-based builds read the unit shadow's stored results (address space cache " << (std::getenv("APS5_NO_ADDRESS_SPACE_CACHE") != nullptr ? "off" : "on") << ")\n"; + return true; +} + +bool completionStoreShadowTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (!UnitShadowEnabled() || base.hostImportAlignment == 0) { + std::cout << "skipped, unit shadows or host imports are unavailable\n"; + return false; + } + const auto decided = PrepareImportWatch(base); +#ifndef _WIN32 + const auto probe = ProbeImportWriteProtection(base); + if (probe.failure != nullptr || probe.writtenAtImport != 0 || probe.writtenAfterSubmit != 0) { + std::cout << "skipped, the device reports watched host memory written when it imports or reads it\n"; + return false; + } + SetImportWatch(base, ImportWatch::Watch); +#endif + struct Restore { + const Context& context; + ImportWatch decided; + ~Restore() { SetImportWatch(context, decided); } + } restore{base, decided}; + constexpr std::uint32_t width = 1024; + constexpr std::uint32_t height = 512; + constexpr std::size_t bytes = width * height * 4; + constexpr std::size_t allocated = bytes + 65536; + constexpr std::size_t storedOffset = 16; + void* block = AllocateWatched(allocated, 65536); + if (block == nullptr) { + std::cout << "skipped, no write watching\n"; + return false; + } + struct Release { + void* block; + ~Release() { ReleaseWatched(block, allocated); } + } release{block}; + std::memset(block, 0x55, bytes); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + ~Unregister() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + } + } unregister{base, block, address}; + if (HostImportFor(base, address, bytes) == nullptr || !AgcDriver::GuestMemory::Watched(address, bytes)) { + std::cout << "skipped, the heap is not imported or not watched\n"; + return false; + } + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = width; + resource.height = height; + 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 == bytes, "the heap's surface has an unexpected size"); + const auto settle = [&] { + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + }; + constexpr std::array stored{std::byte{0x77}, std::byte{0x77}, std::byte{0x77}, std::byte{0x77}}; + std::vector read(bytes); + const auto run = [&](bool shadowed) { + std::memset(block, 0x55, bytes); + { + auto image = StorageTexture::Create(context, detiler, resource, 0); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + const auto commands = recorder.Commands(); + recorder.Keep(image); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + const VkClearColorValue red{{1.0f, 0.0f, 0.0f, 1.0f}}; + context.Function("vkCmdClearColorImage")(commands, image->Image(), VK_IMAGE_LAYOUT_GENERAL, &red, 1, &range); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + image->MarkDirty(); + settle(); + Require(StorageTexture::FindPending(address, bytes) == image, "the cleared image's results are not pending"); + if (shadowed) { + StorageTexture::FlushPending(address, bytes, nullptr, "storage refresh", PublishScope::None); + settle(); + if (!AnyShadowedOverlaps(address, bytes)) return false; + Require(StorageTexture::FindPending(address, bytes) == nullptr, "the image's results stayed pending after their write-back into the unit shadow"); + } + recorder.OnComplete([&] { AgcDriver::GuestMemory::Write(address + storedOffset, stored, 4); }); + settle(); + AgcDriver::GuestMemory::Read(address, read, 4); + settle(); + } + recorder.Sync(); + const std::string over = shadowed ? "a shadowed unit" : "a pending image"; + Require(std::equal(stored.begin(), stored.end(), read.begin() + storedOffset), "a CPU store inside a completion did not reach guest memory over " + over); + std::size_t lost = 0; + for (std::size_t i = 0; i < bytes; ++i) { + if (i >= storedOffset && i < storedOffset + stored.size()) continue; + const auto expected = std::array{255, 0, 0, 255}[i % 4]; + if (static_cast(read[i]) != expected) ++lost; + } + Require(lost == 0, "a CPU store inside a completion over " + over + " lost " + std::to_string(lost) + " bytes of the image's results around it"); + return true; + }; + run(false); + if (!run(true)) { + std::cout << "skipped, the image's write-back did not retile into the unit shadow\n"; + return false; + } + std::cout << "a CPU store inside a completion keeps the results of a pending image and of a shadowed unit around it\n"; + return true; +} + bool writtenBufferOrderTests(const Device& device, Recorder& recorder) { const auto& base = device.GetContext(); if (base.hostImportAlignment == 0) { @@ -6198,7 +7251,6 @@ void refreshProofTests(const Device& device, Recorder& recorder) { recorder.Sync(); } - void unchangedCpuStampTests(const Device& device, Recorder& recorder) { const auto& base = device.GetContext(); constexpr std::uint32_t side = 256; @@ -6282,6 +7334,9 @@ void unchangedCpuStampTests(const Device& device, Recorder& recorder) { context.Function("vkCmdClearColorImage")(commands, image->Image(), VK_IMAGE_LAYOUT_GENERAL, &value, 1, &range); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); image->MarkDirty(); + const std::array, 2> beside{{{address - 64, address - 16}, {address + surfaceBytes, address + surfaceBytes + 64}}}; + const std::array, 2> touching{{{address - 64, address - 16}, {address + surfaceBytes - 4, address + surfaceBytes}}}; + Require(!StorageTexture::AnyPendingOverlaps(beside) && StorageTexture::AnyPendingOverlaps(touching), "(c) " + what + ": a set of ranges beside the pending image or touching its last word was answered wrongly"); for (std::size_t i = 0; i < 4096; ++i) memory[offset + i] = pattern(offset + i); if (changed) memory[offset + 16] = static_cast(~pattern(offset + 16)); AgcDriver::GuestMemory::CollectWritesUncached(blockAddress, bytes); @@ -6345,7 +7400,7 @@ void keptUnitSettleTests(const Device& device, Recorder& recorder) { return; } if (direct) { - if (block != nullptr) sceKernelMunmap(block, surfaceBytes); + if (block != nullptr) sceKernelMunmap(reinterpret_cast(block), surfaceBytes); if (physical >= 0) sceKernelReleaseDirectMemory(physical, surfaceBytes); return; } @@ -6894,35 +7949,7 @@ public: const auto commands = start.commands; if (!continued) { recorder.PrepareSampleSlot(); - if (context.graphicsPipelineLibrary) { - if (!transitioned) { - transitioned = true; - VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; - barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - barrier.image = target.Image(); - barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); - } - VkRenderingAttachmentInfoKHR attachment{VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR}; - attachment.imageView = target.View(); - attachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL; - attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - VkRenderingInfoKHR rendering{VK_STRUCTURE_TYPE_RENDERING_INFO_KHR}; - rendering.renderArea = {{0, 0}, {8, 8}}; - rendering.layerCount = 1; - rendering.colorAttachmentCount = 1; - rendering.pColorAttachments = &attachment; - context.Resolved(&DeviceFunctions::cmdBeginRendering, "vkCmdBeginRenderingKHR")(commands, &rendering); - } else { - VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; - begin.renderPass = renderPass; - begin.framebuffer = framebuffer; - begin.renderArea = {{0, 0}, {8, 8}}; - context.Function("vkCmdBeginRenderPass")(commands, &begin, VK_SUBPASS_CONTENTS_INLINE); - } + begin(commands); } context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); recorder.NoteSampledDraw(commands); @@ -6931,10 +7958,48 @@ public: return continued; } + void Record(VkCommandBuffer commands) const { + begin(commands); + context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + context.Function("vkCmdDraw")(commands, 3, 1, 0, 0); + EndRenderPass(context, commands); + } + static constexpr std::uint64_t Samples = 64; private: static constexpr std::uint64_t PassKey = 0xc0de; + void begin(VkCommandBuffer commands) const { + if (context.graphicsPipelineLibrary) { + if (!transitioned) { + transitioned = true; + VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.image = target.Image(); + barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + VkRenderingAttachmentInfoKHR attachment{VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR}; + attachment.imageView = target.View(); + attachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + VkRenderingInfoKHR rendering{VK_STRUCTURE_TYPE_RENDERING_INFO_KHR}; + rendering.renderArea = {{0, 0}, {8, 8}}; + rendering.layerCount = 1; + rendering.colorAttachmentCount = 1; + rendering.pColorAttachments = &attachment; + context.Resolved(&DeviceFunctions::cmdBeginRendering, "vkCmdBeginRenderingKHR")(commands, &rendering); + } else { + VkRenderPassBeginInfo info{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + info.renderPass = renderPass; + info.framebuffer = framebuffer; + info.renderArea = {{0, 0}, {8, 8}}; + context.Function("vkCmdBeginRenderPass")(commands, &info, VK_SUBPASS_CONTENTS_INLINE); + } + } void release() noexcept { const auto device = context.device; if (pipeline != VK_NULL_HANDLE) context.Function("vkDestroyPipeline")(device, pipeline, nullptr); @@ -7282,6 +8347,24 @@ void sampleDumpTests(const Device& device, Recorder& recorder) { inPassSampleDumpTests(recorder, context, cover, words, address, target); } +void uncountedPassTests(const Device& device) { + auto context = device.GetContext(); + context.bufferDeviceAddress = false; + Recorder recorder(context); + CoverPass cover(context); + recorder.CountSamples(); + Require(!recorder.QueuesSampleDumps(), "a recorder without buffer device addresses counted samples in segments"); + const auto base = recorder.SamplesTotal(); + cover.Draw(recorder); + recorder.RecordUncounted([&](VkCommandBuffer commands) { cover.Record(commands); }); + cover.Draw(recorder); + recorder.Submit(); + recorder.Sync(); + const auto counted = recorder.SamplesTotal() - base; + Require(counted != 0, "the draws around an uncounted pass were not counted"); + if (context.occlusionQueryPrecise) Require(counted == 2 * CoverPass::Samples, "the batch-wide occlusion query counted " + std::to_string(counted) + " samples, not the " + std::to_string(2 * CoverPass::Samples) + " of the two draws around an uncounted pass"); +} + void cmaskPassTests(const Device& device, Recorder& recorder) { namespace GuestMemory = AgcDriver::GuestMemory; auto context = device.GetContext(); @@ -7975,6 +9058,106 @@ void multisampleGuestAccessTests(const Device& device, Recorder& recorder) { std::cout << "Multisampled target guest access tests passed\n"; } +void oneFragmentFmaskGuardTests(const Device& device) { + const auto& base = device.GetContext(); + if (!device.NullDescriptors()) { + std::cout << "nullDescriptor unavailable: one-fragment FMASK guard not tested\n"; + return; + } + constexpr std::uint32_t side = 64; + constexpr std::size_t bytes = 3u * 65536u; +#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 one-fragment FMASK 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; + const auto refused = [](const std::function& access, const std::string& what) { + std::string message; + try { + access(); + } catch (const std::exception& error) { + message = error.what(); + } + Require(message.find("FMASK") != std::string::npos && message.find("one-fragment color target") != std::string::npos, what + " of a host-drawn one-fragment target's FMASK did not throw: " + message); + }; + ColorTarget target{}; + target.extent = {128, 128}; + target.samples = 1; + target.coverageSamples = 4; + target.fmaskAddress = address; + GuardOneFragmentFmask(context, target); + GuardOneFragmentFmask(context, target); + std::array words{}; + refused([&] { AgcDriver::GuestMemory::Read(address + 256u, words); }, "a CPU read"); + refused([&] { RequireNoMultisampleTarget(address + 65535u, 1u, "a DMA copy"); }, "a DMA copy of the last guarded byte"); + RequireNoMultisampleTarget(address + 65536u, 64u, "a DMA copy"); + ShaderRecompiler::RecompileResult program; + ShaderRecompiler::DescriptorBinding binding; + binding.kind = ShaderRecompiler::DescriptorKind::SampledImage; + binding.role = ShaderRecompiler::DescriptorRole::GuestImages; + binding.descriptorSet = 0; + binding.binding = ShaderRecompiler::RuntimeAbi::FirstImageBinding; + binding.count = 1; + binding.imageShape = ShaderRecompiler::DescriptorImageShape::Image2D; + binding.imageSamplers = {0}; + binding.guestDescriptor = { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (56u << 20u) | (((side - 1u) & 3u) << 30u), + ((side - 1u) >> 2u) | ((side - 1u) << 14u), + 0xfacu | (9u << 28u), + 0u, + 0u, + 0u, + 0u, + }; + program.bindings.push_back(binding); + const CompiledShader compute{ShaderRecompiler::ShaderStage::Compute, &program, 0}; + for (const char* what : {"a texture read", "a repeated texture read"}) { + refused([&] { + std::lock_guard gpu(GpuMutex()); + ShaderResources resources(context, compute); + }, what); + } + { + GuestAllocations::Mutation mutation; + mutation.Unmap(block, bytes, [](const void*, std::size_t, const void*, bool) {}); + mutation.Add(block, bytes, true, true, true); + } + AgcDriver::GuestMemory::Read(address + 256u, words); + RequireNoMultisampleTarget(address, 65536u, "a DMA copy"); + std::cout << "One-fragment FMASK guard tests passed\n"; +} + void fmaskDecompressMsaaTests(const Device& device, Recorder& recorder) { const auto& base = device.GetContext(); if ((base.limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_4_BIT) == 0 || base.hostImportAlignment == 0) { @@ -8866,8 +10049,8 @@ bool vertexTargetRangeTests(const Device& device, Recorder& recorder, bool impor const std::array range{std::pair{address + unit, address + 2 * unit}}; Require(StorageTexture::AnyPendingOverlaps(range), "the color producer did not leave GPU results pending"); }; - const auto attributeFor = [](std::uint64_t base, std::uint32_t stride, std::uint32_t fetchIndex) { - return ShaderRecompiler::VertexAttribute{0, 4, {{static_cast(base), static_cast(base >> 32u) | (stride << 16u), static_cast(bytes / 16), 77u << 12u}}, fetchIndex}; + const auto attributeFor = [](std::uint64_t base, std::uint32_t stride, std::uint32_t fetchIndex, std::uint32_t outOfBoundsSelect = 0) { + return ShaderRecompiler::VertexAttribute{0, 4, {{static_cast(base), static_cast(base >> 32u) | (stride << 16u), static_cast(bytes / 16), (77u << 12u) | (outOfBoundsSelect << 28u)}}, fetchIndex}; }; const auto check = [&](std::uint32_t edge) { std::vector texels(64 * 64 * 4); @@ -8938,13 +10121,108 @@ bool vertexTargetRangeTests(const Device& device, Recorder& recorder, bool impor reject([&] { Draw(context, consumer, indexed, shaders); }, "index buffer aliases the render target"); } reject([&] { Draw(context, consumer, triangle, shaders); }, "vertex buffer aliases the render target"); - vertex.vertexAttributes = {attributeFor(address, 0, fetchIndex)}; + vertex.vertexAttributes = {attributeFor(address, 0, fetchIndex, 3)}; reject([&] { Draw(context, consumer, draw, shaders); }, "vertex buffer aliases the render target"); } + vertex.vertexAttributes = {attributeFor(address, 0, 0)}; + Draw(context, consumer, triangle, shaders); + WaitForOptimizedPipelines(context.device); std::cout << "Vertex range and pending color producer tests passed (" << (imported ? "imported" : "copied") << ")\n"; return true; } +std::uint64_t processPrivateBytes() { +#ifdef _WIN32 + PROCESS_MEMORY_COUNTERS_EX counters{}; + counters.cb = sizeof(counters); + if (!K32GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&counters), sizeof(counters))) return 0; + return counters.PrivateUsage; +#else + std::ifstream status("/proc/self/status"); + std::string line; + while (std::getline(status, line)) { + if (line.rfind("RssAnon:", 0) == 0) return std::stoull(line.substr(8)) * 1024u; + } + return 0; +#endif +} + +// What one graphics pipeline the store keeps costs: `count` pipelines, each with its own fragment +// module (so its own fragment shader library), built through CachedPipeline and held, after their +// optimized relinks; process private bytes and VK_EXT_memory_budget heap usage before and after, +// and after the store is cleared. +int pipelineStoreMemoryBench(const Device& device, std::uint32_t count) { + if (!device.Graphics() || count == 0) { + std::cout << "skipped, the pipeline store memory bench requires a graphics queue and at least one pipeline\n"; + return 77; + } + std::lock_guard gpu(GpuMutex()); + const auto& context = device.GetContext(); + struct Cleanup { + VkDevice device; + ~Cleanup() { ClearCachedPipelines(device); } + } cleanup{context.device}; + ClearCachedPipelines(context.device); + ShaderRecompiler::RecompileResult vertex; + vertex.variantId = 21; + vertex.spirv = std::vector(std::begin(TRIANGLE_vert_SPV), std::end(TRIANGLE_vert_SPV)); + std::vector fragments(count + 1u); + for (std::uint32_t index = 0; index <= count; ++index) { + fragments[index].variantId = 1000u + index; + fragments[index].spirv = std::vector(std::begin(TRIANGLE_frag_SPV), std::end(TRIANGLE_frag_SPV)); + fragments[index].spirv[2] = 0x50535430u + index; + } + State state{}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.hasColorTarget = true; + state.color.format = VK_FORMAT_R8G8B8A8_UNORM; + state.colors.push_back(state.color); + state.blend.colorWriteMask = 15; + state.blends.push_back(state.blend); + const std::array first{CompiledShader{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, CompiledShader{ShaderRecompiler::ShaderStage::Fragment, &fragments[count], 0}}; + ShaderResources resources(context, first, state.color, static_cast(state.colors.size()), 0, 0); + const VertexInputLayout input; + std::vector> held; + held.reserve(count + 1u); + const auto build = [&](std::uint32_t index) { + const std::array shaders{CompiledShader{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, CompiledShader{ShaderRecompiler::ShaderStage::Fragment, &fragments[index], 0}}; + held.push_back(CachedPipeline(context, state, input, resources, shaders, VK_IMAGE_LAYOUT_GENERAL)); + }; + struct Sample { + std::uint64_t privateBytes; + std::optional heaps; + }; + const auto sample = [&] { + WaitForOptimizedPipelines(context.device); + return Sample{processPrivateBytes(), device.MemoryUsage()}; + }; + build(count); + const auto before = sample(); + const auto started = std::chrono::steady_clock::now(); + for (std::uint32_t index = 0; index < count; ++index) build(index); + const auto built = std::chrono::steady_clock::now(); + const auto after = sample(); + const auto linked = std::chrono::steady_clock::now(); + const auto totals = PipelineStoreTotals(); + held.clear(); + ClearCachedPipelines(context.device); + const auto cleared = sample(); + const auto mib = [](std::uint64_t later, std::uint64_t earlier) { return (static_cast(later) - static_cast(earlier)) / (1024.0 * 1024.0); }; + const auto perPipeline = [&](std::uint64_t later, std::uint64_t earlier) { return (static_cast(later) - static_cast(earlier)) / 1024.0 / count; }; + const char* linking = !context.graphicsPipelineLibrary ? "whole pipelines" : context.pipelineLinkTimeOptimization ? "libraries and optimized relinks" : "libraries, fast links only"; + std::cout << std::fixed << std::setprecision(1); + std::cout << "Pipeline store memory: " << count << " graphics pipelines (one fragment module each, " << linking << "), " << totals.cached << " in the store, built in " + << std::chrono::duration(built - started).count() << " s, relinked in " << std::chrono::duration(linked - built).count() << " s\n"; + std::cout << " process private bytes: +" << mib(after.privateBytes, before.privateBytes) << " MiB = " << perPipeline(after.privateBytes, before.privateBytes) << " KiB per pipeline; after clearing the store " << mib(cleared.privateBytes, before.privateBytes) << " MiB above the start\n"; + if (before.heaps && after.heaps && cleared.heaps) { + std::cout << " VK_EXT_memory_budget heap usage: device-local +" << mib(after.heaps->deviceLocal, before.heaps->deviceLocal) << " MiB = " << perPipeline(after.heaps->deviceLocal, before.heaps->deviceLocal) << " KiB per pipeline, host +" << mib(after.heaps->host, before.heaps->host) << " MiB = " << perPipeline(after.heaps->host, before.heaps->host) + << " KiB per pipeline; after clearing the store device-local " << mib(cleared.heaps->deviceLocal, before.heaps->deviceLocal) << " MiB, host " << mib(cleared.heaps->host, before.heaps->host) << " MiB above the start\n"; + } else { + std::cout << " VK_EXT_memory_budget unavailable: no device heap usage\n"; + } + return 0; +} + void pipelineCacheTests(const Device& device, bool benchmark) { std::thread establishThreadedProcess([] {}); establishThreadedProcess.join(); @@ -9013,6 +10291,109 @@ void pipelineCacheTests(const Device& device, bool benchmark) { std::cout << "Pipeline cache key and retained lifetime tests passed\n"; } +struct PipelineCacheFileProbe { + Context& context; + PFN_vkGetDeviceProcAddr resolver; + std::atomic creations{0}; + static inline PipelineCacheFileProbe* active = nullptr; + explicit PipelineCacheFileProbe(Context& context) : context(context), resolver(context.deviceProc) { + Require(active == nullptr, "pipeline cache file probe is already active"); + active = this; + context.deviceProc = resolve; + } + ~PipelineCacheFileProbe() { + context.deviceProc = resolver; + active = nullptr; + } + static VKAPI_ATTR VkResult VKAPI_CALL createCache(VkDevice device, const VkPipelineCacheCreateInfo* info, const VkAllocationCallbacks* callbacks, VkPipelineCache* created) { + ++active->creations; + return reinterpret_cast(active->resolver(device, "vkCreatePipelineCache"))(device, info, callbacks, created); + } + static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL resolve(VkDevice device, const char* name) { + if (std::strcmp(name, "vkCreatePipelineCache") == 0) return reinterpret_cast(createCache); + return active->resolver(device, name); + } +}; + +void pipelineCacheHeaderTests(const Device& device) { + const auto root = std::filesystem::temp_directory_path() / ("aps5-pipeline-header-test-" + std::to_string(std::random_device{}())); + Require(root.native().size() < 225, "the pipeline cache header test requires a temporary root shorter than 225 characters"); +#ifdef _WIN32 + std::error_code cleanupError; + const auto cleanupRoot = ShaderRecompiler::NativeCachePath_nid_no_patch(root, cleanupError); + Require(!cleanupError, "the pipeline cache header test could not resolve its cleanup directory"); +#else + const auto& cleanupRoot = root; +#endif + struct RemoveDirectory { + std::filesystem::path directory; + ~RemoveDirectory() { + std::error_code error; + std::filesystem::remove_all(directory, error); + } + } removeDirectory{cleanupRoot}; + auto directory = root; + char version[17]; + std::snprintf(version, sizeof(version), "%016llx", static_cast(ShaderRecompiler::ShaderDiskCache::Generated::SourceVersion)); + while ((directory / version).native().size() < 220) { + const auto length = (directory / version).native().size(); + const auto remaining = 245 > length ? 245 - length : 1; + const auto chunk = std::min(remaining, 16); + directory /= std::string(chunk, 'd'); + } + const auto versionDirectory = directory / version; + Require(versionDirectory.native().size() >= 220 && versionDirectory.native().size() <= 249, "the pipeline cache directory is not between 220 and 249 characters"); +#ifdef _WIN32 + _putenv_s("ANYPS5_NO_SHADER_CACHE", "0"); + _putenv_s("ANYPS5_SHADER_CACHE_DIR", directory.string().c_str()); +#else + setenv("ANYPS5_NO_SHADER_CACHE", "0", 1); + setenv("ANYPS5_SHADER_CACHE_DIR", directory.string().c_str(), 1); +#endif + auto context = device.GetContext(); + PipelineCacheFileProbe probe(context); + PipelineCache cache(context, device.Properties()); + cache.SaveNow(); + std::vector files; + for (const auto& entry : std::filesystem::directory_iterator(versionDirectory)) files.push_back(entry.path()); + Require(files.size() == 1 && files.front().filename().string().starts_with("vk-pipelines-"), "the long pipeline cache directory does not hold exactly one cache file"); + Require(files.front().native().size() > 260, "the pipeline cache header test did not exceed MAX_PATH"); + std::vector stored; + Require(ShaderRecompiler::ReadWholeFile(files.front(), stored) && !stored.empty(), "the long pipeline cache file was not persisted"); + const auto creations = probe.creations.load(); + for (int repeat = 0; repeat < 3; ++repeat) cache.SaveNow(); + Require(probe.creations.load() == creations, "an unchanged long-path pipeline cache was reloaded for an idle save"); + std::vector after; + Require(ShaderRecompiler::ReadWholeFile(files.front(), after) && after == stored, "an idle save changed the long pipeline cache file"); +#ifdef _WIN32 + std::error_code lockError; + const auto lockNative = ShaderRecompiler::NativeCachePath_nid_no_patch(files.front(), lockError); + Require(!lockError, "could not resolve long cache path"); + struct FileLockGuard { + HANDLE handle = INVALID_HANDLE_VALUE; + ~FileLockGuard() { + if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle); + } + } lockGuard{CreateFileW(lockNative.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)}; + Require(lockGuard.handle != INVALID_HANDLE_VALUE, "could not lock pipeline cache file"); + std::string capturedStderr; + { + StderrCapture capture; + PipelineCache lockedCache(context, device.Properties()); + lockedCache.SaveNow(); + capturedStderr = capture.Finish(); + } + Require(capturedStderr.find("cannot read") != std::string::npos, "an unreadable long pipeline cache file did not report the cannot read diagnostic"); + if (lockGuard.handle != INVALID_HANDLE_VALUE) { + CloseHandle(lockGuard.handle); + lockGuard.handle = INVALID_HANDLE_VALUE; + } + std::vector retained; + Require(ShaderRecompiler::ReadWholeFile(files.front(), retained) && retained == stored, "an unreadable long pipeline cache file was overwritten"); +#endif + std::cout << "long-path pipeline cache header reused without reloading\n"; +} + int pipelineCacheFileTests(const Device& device) { const auto& context = device.GetContext(); const auto directory = std::filesystem::temp_directory_path() / ("aps5-pipeline-cache-file-test-" + std::to_string(std::random_device{}())); @@ -9025,7 +10406,7 @@ int pipelineCacheFileTests(const Device& device) { } removeDirectory{directory}; #ifdef _WIN32 _putenv_s("ANYPS5_NO_SHADER_CACHE", "0"); - _putenv_s("ANYPS5_SHADER_CACHE_DIR", directory.string().c_str()); + _wputenv_s(L"ANYPS5_SHADER_CACHE_DIR", directory.wstring().c_str()); #else setenv("ANYPS5_NO_SHADER_CACHE", "0", 1); setenv("ANYPS5_SHADER_CACHE_DIR", directory.string().c_str(), 1); @@ -9677,6 +11058,97 @@ void denormalClearTests(const Device& device, Recorder& recorder) { recorder.Sync(); } +class LibraryCreateFlags { +public: + explicit LibraryCreateFlags(const Context& context) : resolver(context.deviceProc), device(context.device) { + create = function("vkCreateGraphicsPipelines"); + active = this; + } + ~LibraryCreateFlags() { active = nullptr; } + LibraryCreateFlags(const LibraryCreateFlags&) = delete; + LibraryCreateFlags& operator=(const LibraryCreateFlags&) = delete; + PFN_vkGetDeviceProcAddr Resolver() const { return resolve; } + std::uint32_t Libraries() const { return libraries.load(); } + std::uint32_t Retaining() const { return retaining.load(); } + +private: + template + TFunction function(const char* name) const { return reinterpret_cast(resolver(device, name)); } + static VKAPI_ATTR VkResult VKAPI_CALL createPipelines(VkDevice device, VkPipelineCache cache, std::uint32_t count, const VkGraphicsPipelineCreateInfo* infos, const VkAllocationCallbacks* callbacks, VkPipeline* pipelines) { + const auto result = active->create(device, cache, count, infos, callbacks, pipelines); + for (std::uint32_t index = 0; index < count; ++index) { + if (pipelines[index] == VK_NULL_HANDLE || (infos[index].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) == 0) continue; + active->libraries.fetch_add(1); + if ((infos[index].flags & VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT) != 0) active->retaining.fetch_add(1); + } + return result; + } + static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL resolve(VkDevice device, const char* name) { + if (std::strcmp(name, "vkCreateGraphicsPipelines") == 0) return reinterpret_cast(createPipelines); + return active->resolver(device, name); + } + PFN_vkGetDeviceProcAddr resolver; + VkDevice device; + PFN_vkCreateGraphicsPipelines create = nullptr; + std::atomic libraries{0}; + std::atomic retaining{0}; + static inline LibraryCreateFlags* active = nullptr; +}; + +void pipelineLibraryLtoInfoTests(const Device& device) { + std::lock_guard gpu(GpuMutex()); + const auto& context = device.GetContext(); + LibraryCreateFlags created(context); + struct Cleanup { + VkDevice device; + ~Cleanup() { ClearCachedPipelines(device); } + } cleanup{context.device}; + ClearCachedPipelines(context.device); + if (!context.graphicsPipelineLibrary) { + std::cout << "graphics pipeline libraries unavailable or disabled: no library retains link-time optimization info\n"; + return; + } + ShaderRecompiler::RecompileResult vertex, fragment; + vertex.variantId = 41; + fragment.variantId = 42; + vertex.spirv = std::vector(std::begin(TRIANGLE_vert_SPV), std::end(TRIANGLE_vert_SPV)); + fragment.spirv = std::vector(std::begin(TRIANGLE_frag_SPV), std::end(TRIANGLE_frag_SPV)); + const std::array shaders{CompiledShader{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, CompiledShader{ShaderRecompiler::ShaderStage::Fragment, &fragment, 0}}; + State state{}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.hasColorTarget = true; + state.color.format = VK_FORMAT_R8G8B8A8_UNORM; + state.colors.push_back(state.color); + state.blend.colorWriteMask = 15; + state.blends.push_back(state.blend); + ShaderResources resources(context, shaders, state.color, static_cast(state.colors.size()), 0, 0); + VertexInputLayout input; + auto fast = context; + fast.deviceProc = created.Resolver(); + fast.pipelineLinkTimeOptimization = false; + auto optimizing = fast; + optimizing.pipelineLinkTimeOptimization = true; + const bool lto = std::getenv("APS5_NO_PIPELINE_LTO") == nullptr; + const auto fastPipeline = CachedPipeline(fast, state, input, resources, shaders, VK_IMAGE_LAYOUT_GENERAL); + const auto fastLibraries = created.Libraries(); + Require(fastLibraries != 0, "the pipeline was not linked from libraries created through the traced device"); + Require(created.Retaining() == 0, std::to_string(created.Retaining()) + " of the " + std::to_string(fastLibraries) + " libraries made for a context without optimized links were created to retain link-time optimization info"); + WaitForOptimizedPipelines(context.device); + Require(!fastPipeline->Optimized(), "a pipeline of a context without optimized links was optimized in the background"); + const auto optimizedPipeline = CachedPipeline(optimizing, state, input, resources, shaders, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + Require(optimizedPipeline != fastPipeline, "the two attachment layouts shared one cached pipeline"); + const auto libraries = created.Libraries() - fastLibraries; + if (lto) { + Require(libraries == fastLibraries, "a link that will be optimized made " + std::to_string(libraries) + " libraries, expected " + std::to_string(fastLibraries) + ": it reused libraries made without link-time optimization info"); + Require(created.Retaining() == libraries, std::to_string(created.Retaining()) + " of the " + std::to_string(libraries) + " libraries of a link that will be optimized were created to retain link-time optimization info"); + } else { + Require(libraries == 0 && created.Retaining() == 0, "with APS5_NO_PIPELINE_LTO set, forcing pipelineLinkTimeOptimization changed the libraries or their create flags"); + } + WaitForOptimizedPipelines(context.device); + Require(optimizedPipeline->Optimized() == lto, lto ? "the pipeline linked from libraries that retain link-time optimization info was not optimized in the background" : "a pipeline was optimized with APS5_NO_PIPELINE_LTO set"); + std::cout << "Pipeline library link-time optimization info tests passed: " << fastLibraries << " libraries without the info, " << created.Retaining() << " with it\n"; +} + void pipelineLibraryTests(const Device& device) { std::lock_guard gpu(GpuMutex()); const auto& context = device.GetContext(); @@ -9945,6 +11417,16 @@ void Run(Context context) { int main(int argc, char** argv) { try { + if (argc == 2 && std::string_view(argv[1]) == "--collect-mapping-history-only") { + if (!collectMappingHistoryTests()) return 77; + std::cout << "collect mapping history tests passed\n"; + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--hint-mapping-history-only") { + if (!hintMappingHistoryTests()) return 77; + std::cout << "hint mapping history tests passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--partial-watch-restoration-only") { if (!partialWatchRestorationTests()) return 77; std::cout << "partial private write-watch restoration tests passed\n"; @@ -9962,7 +11444,7 @@ int main(int argc, char** argv) { } std::unique_ptr created; try { - created = std::make_unique(argc == 2 && (std::string_view(argv[1]) == "--import-watch-other-family-only" || std::string_view(argv[1]) == "--deferred-settle-other-family-only")); + created = std::make_unique(argc == 2 && (std::string_view(argv[1]) == "--import-watch-other-family-only" || std::string_view(argv[1]) == "--deferred-settle-other-family-only" || std::string_view(argv[1]) == "--host-import-lifetime-other-family-only" || std::string_view(argv[1]) == "--settle-write-order-other-family-only")); } catch (const std::exception& error) { if (std::getenv("ANYPS5_REQUIRE_VULKAN") != nullptr) throw; std::cout << "skipped, no usable Vulkan device: " << error.what() << '\n'; @@ -9986,7 +11468,22 @@ int main(int argc, char** argv) { importWatchTests(device); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--settle-initial-dcc-only") { #ifdef _WIN32 + return settleInitialDccTests(device) ? 0 : 77; +#else + std::cout << "skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows\n"; + return 77; +#endif + } +#ifdef _WIN32 + if (argc == 2 && std::string_view(argv[1]) == "--host-import-lifetime-other-family-only") { + if (device.GetContext().settleFamily == device.GetContext().queueFamily) { + std::cout << "skipped, no other queue family to settle imports on\n"; + return 77; + } + return RunHostImportLifetimeTests(device.GetContext()); + } if (argc == 2 && (std::string_view(argv[1]) == "--deferred-settle-only" || std::string_view(argv[1]) == "--deferred-settle-other-family-only")) { if (std::string_view(argv[1]) == "--deferred-settle-other-family-only" && device.GetContext().settleFamily == device.GetContext().queueFamily) { std::cout << "skipped, no other queue family to settle imports on\n"; @@ -9994,12 +11491,20 @@ int main(int argc, char** argv) { } return deferredSettleTests(device) ? 0 : 77; } + if (argc == 2 && (std::string_view(argv[1]) == "--settle-write-order-only" || std::string_view(argv[1]) == "--settle-write-order-other-family-only")) { + if (std::string_view(argv[1]) == "--settle-write-order-other-family-only" && device.GetContext().settleFamily == device.GetContext().queueFamily) { + std::cout << "skipped, no other queue family to settle imports on\n"; + return 77; + } + return settleWriteOrderTests(device) ? 0 : 77; + } + if (argc == 2 && std::string_view(argv[1]) == "--settle-wait-failure-only") return settleWaitFailureTests(device) ? 0 : 77; if (argc == 2 && std::string_view(argv[1]) == "--settle-first-use-only") return settleFirstUseTests(device) ? 0 : 77; if (argc == 2 && std::string_view(argv[1]) == "--settle-earlier-import-only") return settleEarlierImportTests(device) ? 0 : 77; if (argc == 2 && std::string_view(argv[1]) == "--settle-retire-only") return settleRetireTests(device) ? 0 : 77; if (argc == 2 && std::string_view(argv[1]) == "--settle-storage-image-only") return settleStorageImageTests(device) ? 0 : 77; if (argc == 2 && std::string_view(argv[1]) == "--reimport-bench") return reimportBench(device); - if (argc == 2 && std::string_view(argv[1]) == "--trimmed-import-only") return trimmedImportTests(device); + if (argc == 2 && std::string_view(argv[1]) == "--host-import-cut-only") return hostImportCutTests(device); #endif if (argc == 2 && std::string_view(argv[1]) == "--host-import-retirement-only") { const auto alignment = device.GetContext().hostImportAlignment; @@ -10024,10 +11529,15 @@ int main(int argc, char** argv) { } return sparseRunImportTests(device, true) ? 0 : 77; } + if (argc == 2 && std::string_view(argv[1]) == "--pipeline-library-lto-info-only") { + pipelineLibraryLtoInfoTests(device); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--pipeline-library-only") { pipelineLibraryTests(device); return 0; } + if ((argc == 2 || argc == 3) && std::string_view(argv[1]) == "--benchmark-pipeline-store-memory") return pipelineStoreMemoryBench(device, argc == 3 ? static_cast(std::stoul(argv[2])) : 4096u); if (argc == 2 && (std::string_view(argv[1]) == "--benchmark-pipeline-cache" || std::string_view(argv[1]) == "--pipeline-cache-only")) { pipelineCacheTests(device, std::string_view(argv[1]) == "--benchmark-pipeline-cache"); return 0; @@ -10037,6 +11547,11 @@ int main(int argc, char** argv) { return pipelineCacheFileTests(device); } + if (argc == 2 && std::string_view(argv[1]) == "--pipeline-cache-header-only") { + pipelineCacheHeaderTests(device); + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--descriptor-pool-only") { descriptorPoolTests(device); return 0; @@ -10057,6 +11572,12 @@ int main(int argc, char** argv) { std::cout << "host imports " << (PrepareImportWatch(device.GetContext()) == ImportWatch::Unwatch ? "are compared" : "stay watched") << '\n'; Recorder recorder(device.GetContext()); recorder.Activate(); + if (argc == 2 && std::string_view(argv[1]) == "--command-reuse-only") { + if (device.GetContext().hostImportAlignment == 0) return 77; + const auto before = Recorder::StoreCounts().stores; + storeRunTests(device, recorder); + return Recorder::StoreCounts().stores != before ? 0 : 77; + } if (argc == 2 && std::string_view(argv[1]) == "--copied-direct-only") { copiedDirectOrderingTests(device, recorder); return 0; @@ -10126,6 +11647,11 @@ int main(int argc, char** argv) { std::cout << "Completion label storage and ordering tests passed\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--bda-staging-only") { + if (!bdaStagingTests(device, recorder)) return 77; + std::cout << "address-based staging tests passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--read-written-staging-only") { if (!readWrittenStagingTests(device, recorder)) return 77; std::cout << "read and written staging tests passed\n"; @@ -10164,6 +11690,14 @@ int main(int argc, char** argv) { parkedImportUnmapTests(device); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--idle-publish-wait-only") { + if (!AgcDriver::Graphics::UnitShadowEnabled() || device.GetContext().hostImportAlignment == 0) { + std::cout << "skipped, unit shadows or host imports are unavailable\n"; + return 77; + } + idlePublishWaitTests(device, recorder); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--unit-shadow-only") { if (!AgcDriver::Graphics::UnitShadowEnabled() || device.GetContext().hostImportAlignment == 0) { std::cout << "skipped, unit shadows or host imports are unavailable\n"; @@ -10175,6 +11709,12 @@ int main(int argc, char** argv) { } return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--bda-shadowed-results-only") { + return addressShadowTests(device, recorder) ? 0 : 77; + } + if (argc == 2 && std::string_view(argv[1]) == "--completion-store-shadow-only") { + return completionStoreShadowTests(device, recorder) ? 0 : 77; + } if (argc == 2 && std::string_view(argv[1]) == "--split-writeback-only") { if (device.GetContext().hostImportAlignment == 0) { std::cout << "skipped, host imports are unavailable\n"; @@ -10239,6 +11779,15 @@ int main(int argc, char** argv) { std::cout << "Occlusion counter dump tests passed\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--uncounted-passes-only") { + if (!device.Graphics()) { + std::cout << "skipped, the test queue has no graphics\n"; + return 77; + } + uncountedPassTests(device); + std::cout << "Uncounted pass tests passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--shadowed-key-stores-only") { if (!shadowedKeyStoreTests(device, recorder)) { std::cout << "skipped, the device imports no watched host memory under a unit shadow\n"; @@ -10259,6 +11808,7 @@ int main(int argc, char** argv) { } if (argc == 2 && std::string_view(argv[1]) == "--multisample-only") { multisampleGuestAccessTests(device, recorder); + oneFragmentFmaskGuardTests(device); fmaskDecompressMsaaTests(device, recorder); return 0; } @@ -10277,6 +11827,7 @@ int main(int argc, char** argv) { } if (argc == 2 && std::string_view(argv[1]) == "--draw-snapshot-cap-only") { drawSnapshotCapTests(device, recorder); + containingSnapshotTests(device); unwrittenSnapshotTests(device); drawSnapshotUploadTests(device, recorder); std::cout << "Draw snapshot cap tests passed\n"; @@ -10316,8 +11867,10 @@ int main(int argc, char** argv) { refreshProofTests(device, recorder); resourceReadTests(device, recorder); readWrittenStagingTests(device, recorder); + bdaStagingTests(device, recorder); depthSurfaceProofTests(device, recorder); multisampleGuestAccessTests(device, recorder); + oneFragmentFmaskGuardTests(device); tiledResolveTests(device, recorder); fmaskDecompressMsaaTests(device, recorder); misalignedSnapshotTests(device, recorder); @@ -10328,6 +11881,7 @@ int main(int argc, char** argv) { imageTableFirstLayerRevalidateTests(device, recorder); drawSnapshotCapTests(device, recorder); drawSnapshotEvictionTests(device); + containingSnapshotTests(device); unwrittenSnapshotTests(device); drawSnapshotUploadTests(device, recorder); drawInputReuseTests(device, recorder); diff --git a/core/libs/prx/libSceAgcDriver/tests/RedundantValueElimination.cpp b/core/libs/prx/libSceAgcDriver/tests/RedundantValueElimination.cpp index a17b7cf87d..202aa559ae 100644 --- a/core/libs/prx/libSceAgcDriver/tests/RedundantValueElimination.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/RedundantValueElimination.cpp @@ -1,4 +1,5 @@ #include "IntermediateRepresentation/IrProgram.hpp" +#include "Optimization/DeadCodeEliminator.hpp" #include "Optimization/RedundantValueEliminator.hpp" #include "Recompiler.hpp" #include @@ -208,7 +209,8 @@ std::vector Recompiled(std::span code) { request.target.maxWorkgroupSharedMemoryBytes = 49152; request.layout = {0, 0, 0, 128}; request.useCache = false; - const auto& words = Recompile(request).spirv.Words(); + const auto result = Recompile(request); + const auto& words = result.spirv.Words(); return {words.begin(), words.end()}; } @@ -324,6 +326,149 @@ int main() { return b.eliminate() == 1u && !dominated.HasUses() && first.UseCount() == 2u && AllUsed({&thenOnly, &sibling, &merged}); }); + passed &= run("GetSrtResource and ReadConst with the same operands merge in a dominated block, while another slot or a sibling block stays", [] { + Builder b; + IrBlock& head = *b.block; + IrBlock& then = b.newBlock(); + IrBlock& otherwise = b.newBlock(); + IrBlock& merge = b.newBlock(); + head.AddBranch(&then); + head.AddBranch(&otherwise); + then.AddBranch(&merge); + otherwise.AddBranch(&merge); + auto& srtHead = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {&b.constant(0u)}); + auto& readHead = b.emit(IrOpcode::ReadConst, IrType::U32, {&srtHead, &b.constant(2u)}); + b.keep(readHead); + b.block = &then; + auto& srtDominated = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {&b.constant(0u)}); + auto& readDominated = b.emit(IrOpcode::ReadConst, IrType::U32, {&srtDominated, &b.constant(2u)}); + auto& readOtherSlot = b.emit(IrOpcode::ReadConst, IrType::U32, {&srtDominated, &b.constant(3u)}); + auto& srtThenOnly = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {&b.constant(1u)}); + auto& readThenOnly = b.emit(IrOpcode::ReadConst, IrType::U32, {&srtThenOnly, &b.constant(4u)}); + b.keep(readDominated); + b.keep(readOtherSlot); + b.keep(readThenOnly); + b.block = &otherwise; + auto& srtSibling = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {&b.constant(1u)}); + auto& readSibling = b.emit(IrOpcode::ReadConst, IrType::U32, {&srtSibling, &b.constant(4u)}); + b.keep(readSibling); + b.block = &merge; + auto& srtMerge = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {&b.constant(0u)}); + auto& readMerge = b.emit(IrOpcode::ReadConst, IrType::U32, {&srtMerge, &b.constant(2u)}); + b.keep(readMerge); + return b.eliminate() == 4u && !srtDominated.HasUses() && !readDominated.HasUses() && !srtMerge.HasUses() && !readMerge.HasUses() && srtHead.UseCount() == 4u && readHead.UseCount() == 3u && AllUsed({&readOtherSlot, &srtThenOnly, &readThenOnly, &srtSibling, &readSibling}); + }); + + passed &= run("two ReadConst instructions of the same slot separated by a store to the table address stay", [] { + Builder b; + auto& tableAddress = b.emit(IrOpcode::GetAddressResource, IrType::AddressResource, {&b.constant(0x1000u), &b.constant(0u)}); + auto& tableRead = b.emit(IrOpcode::LoadAddressU32, IrType::U32, {&tableAddress, &b.constant(8u), &b.constant(0u), &b.truth()}); + b.program.Resources().srtReads.push_back({&tableRead, 0u}); + auto& srt = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {}); + auto& first = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + static_cast(b.emit(IrOpcode::StoreAddressU32, IrType::Void, {&tableAddress, &b.constant(8u), &b.constant(0u), &b.constant(7u), &b.truth()})); + auto& second = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(first); + b.keep(second); + return b.eliminate() == 0u && AllUsed({&first, &second}); + }); + + passed &= run("a store on one branch keeps ReadConst in the merge block while the clean sibling branch reuses the head read", [] { + Builder b; + IrBlock& head = *b.block; + IrBlock& then = b.newBlock(); + IrBlock& otherwise = b.newBlock(); + IrBlock& merge = b.newBlock(); + head.AddBranch(&then); + head.AddBranch(&otherwise); + then.AddBranch(&merge); + otherwise.AddBranch(&merge); + auto& tableAddress = b.emit(IrOpcode::GetAddressResource, IrType::AddressResource, {&b.constant(0x1000u), &b.constant(0u)}); + auto& tableRead = b.emit(IrOpcode::LoadAddressU32, IrType::U32, {&tableAddress, &b.constant(8u), &b.constant(0u), &b.truth()}); + b.program.Resources().srtReads.push_back({&tableRead, 0u}); + auto& srt = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {}); + auto& readHead = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(readHead); + b.block = &then; + static_cast(b.emit(IrOpcode::StoreAddressU32, IrType::Void, {&tableAddress, &b.constant(8u), &b.constant(0u), &b.constant(7u), &b.truth()})); + auto& readThenAfterStore = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(readThenAfterStore); + b.block = &otherwise; + auto& readOtherwise = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(readOtherwise); + b.block = &merge; + auto& readMerge = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(readMerge); + return b.eliminate() == 1u && !readOtherwise.HasUses() && readHead.UseCount() == 2u && AllUsed({&readHead, &readThenAfterStore, &readMerge}); + }); + + passed &= run("a store on a loop backedge prevents reusing a pre-loop ReadConst in the loop header", [] { + Builder b; + IrBlock& header = b.newBlock(); + IrBlock& body = b.newBlock(); + IrBlock& exit = b.newBlock(); + b.block->AddBranch(&header); + header.AddBranch(&body); + header.AddBranch(&exit); + body.AddBranch(&header); + auto& tableAddress = b.emit(IrOpcode::GetAddressResource, IrType::AddressResource, {&b.constant(0x1000u), &b.constant(0u)}); + auto& tableRead = b.emit(IrOpcode::LoadAddressU32, IrType::U32, {&tableAddress, &b.constant(8u), &b.constant(0u), &b.truth()}); + b.program.Resources().srtReads.push_back({&tableRead, 0u}); + auto& srt = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {}); + auto& beforeLoop = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(beforeLoop); + b.block = &header; + auto& inHeader = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(inHeader); + b.block = &body; + auto& inBodyBeforeStore = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + static_cast(b.emit(IrOpcode::StoreAddressU32, IrType::Void, {&tableAddress, &b.constant(8u), &b.constant(0u), &b.constant(7u), &b.truth()})); + auto& inBodyAfterStore = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(inBodyBeforeStore); + b.keep(inBodyAfterStore); + b.block = &exit; + auto& afterLoop = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(afterLoop); + return b.eliminate() == 2u && !inBodyBeforeStore.HasUses() && !afterLoop.HasUses() && beforeLoop.UseCount() == 1u && inHeader.UseCount() == 3u && AllUsed({&beforeLoop, &inHeader, &inBodyAfterStore}); + }); + + passed &= run("a store in an inner loop propagates to the outer loop header across fixed-point iterations", [] { + Builder b; + IrBlock& outerHeader = b.newBlock(); + IrBlock& innerHeader = b.newBlock(); + IrBlock& innerBody = b.newBlock(); + IrBlock& outerLatch = b.newBlock(); + IrBlock& exit = b.newBlock(); + b.block->AddBranch(&outerHeader); + outerHeader.AddBranch(&innerHeader); + outerHeader.AddBranch(&exit); + innerHeader.AddBranch(&innerBody); + innerHeader.AddBranch(&outerLatch); + innerBody.AddBranch(&innerHeader); + outerLatch.AddBranch(&outerHeader); + auto& tableAddress = b.emit(IrOpcode::GetAddressResource, IrType::AddressResource, {&b.constant(0x1000u), &b.constant(0u)}); + auto& tableRead = b.emit(IrOpcode::LoadAddressU32, IrType::U32, {&tableAddress, &b.constant(8u), &b.constant(0u), &b.truth()}); + b.program.Resources().srtReads.push_back({&tableRead, 0u}); + auto& srt = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {}); + auto& beforeOuter = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(beforeOuter); + b.block = &outerHeader; + auto& inOuterHeader = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(inOuterHeader); + b.block = &innerHeader; + auto& inInnerHeader = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(inInnerHeader); + b.block = &innerBody; + static_cast(b.emit(IrOpcode::StoreAddressU32, IrType::Void, {&tableAddress, &b.constant(8u), &b.constant(0u), &b.constant(7u), &b.truth()})); + b.block = &outerLatch; + auto& inOuterLatch = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(inOuterLatch); + b.block = &exit; + auto& inExit = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)}); + b.keep(inExit); + return b.eliminate() == 2u && !inOuterLatch.HasUses() && !inExit.HasUses() && beforeOuter.UseCount() == 1u && inOuterHeader.UseCount() == 2u && inInnerHeader.UseCount() == 2u && AllUsed({&beforeOuter, &inOuterHeader, &inInnerHeader}); + }); + passed &= run("a loop body reuses a value of its header, the loop exit does not reuse a value of the body", [] { Builder b; IrBlock& header = b.newBlock(); @@ -362,6 +507,55 @@ int main() { return b.eliminate() == 1u && b.program.Metadata().blockInfo.front().condition == &first; }); + passed &= run("descriptor dwords name the surviving values after redundancy and dead code elimination", [] { + Builder b; + auto& input = b.unknown(); + DescriptorSource descriptor; + descriptor.dwordCount = 8u; + std::array kept{}; + std::array removed{}; + for (std::uint32_t index = 0; index < 8u; ++index) { + auto& first = b.emit(IrOpcode::IAdd32, IrType::U32, {&input, &b.constant(index + 1u)}); + auto& second = b.emit(IrOpcode::IAdd32, IrType::U32, {&input, &b.constant(index + 1u)}); + b.keep(first); + b.keep(second); + descriptor.dwords[index] = &second; + kept[index] = &first; + removed[index] = &second; + } + b.program.Resources().descriptorSources.push_back(descriptor); + b.program.Resources().descriptorSources.emplace_back(); + if (b.eliminate() != 8u) return false; + DeadCodeEliminator{}.Eliminate(b.program); + const auto& descriptors = b.program.Resources().descriptorSources; + for (std::size_t index = 0; index < kept.size(); ++index) { + if (descriptors[0].dwords[index] != kept[index] || kept[index]->Parent() != b.block || removed[index]->Parent() != nullptr || descriptors[1].dwords[index] != nullptr) return false; + } + return true; + }); + + passed &= run("a selected table column keeps its surviving runtime offset", [] { + Builder b; + auto& input = b.unknown(); + auto& first = b.emit(IrOpcode::IMul32, IrType::U32, {&input, &b.constant(48u)}); + auto& second = b.emit(IrOpcode::IMul32, IrType::U32, {&input, &b.constant(48u)}); + b.keep(first); + b.keep(second); + DescriptorSource selection; + selection.dwordCount = 1u; + selection.dwords[0] = &second; + DescriptorSource image; + image.tableColumn.emplace(); + image.tableColumn->selected = true; + image.tableColumn->selection = 0u; + b.program.Resources().descriptorSources = {selection, image}; + if (b.eliminate() != 1u) return false; + DeadCodeEliminator{}.Eliminate(b.program); + const auto& descriptors = b.program.Resources().descriptorSources; + const auto selected = descriptors[1].tableColumn->selection; + return descriptors[selected].dwords[0] == &first && first.Parent() == b.block && second.Parent() == nullptr; + }); + passed &= run("lane reads are not merged", [] { Builder b; auto& first = b.unknown(); diff --git a/core/libs/prx/libSceAgcDriver/tests/RobustBufferAccess.cpp b/core/libs/prx/libSceAgcDriver/tests/RobustBufferAccess.cpp deleted file mode 100644 index 6b0331109f..0000000000 --- a/core/libs/prx/libSceAgcDriver/tests/RobustBufferAccess.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "prx/libSceAgcDriver/Execution/include/RobustBufferAccess.hpp" -#include - -namespace { - -struct Case { - VkDriverId driver; - bool usable; -}; - -constexpr Case Cases[]{ - {VK_DRIVER_ID_AMD_PROPRIETARY, false}, - {VK_DRIVER_ID_AMD_OPEN_SOURCE, true}, - {VK_DRIVER_ID_MESA_RADV, true}, - {VK_DRIVER_ID_NVIDIA_PROPRIETARY, true}, - {VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, true}, - {VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA, true}, - {VK_DRIVER_ID_MESA_LLVMPIPE, true}, - {VK_DRIVER_ID_MOLTENVK, true}, -}; - -} - -int main() { - int failures = 0; - for (const auto& item : Cases) { - if (AgcDriver::RobustBufferAccess2Usable(item.driver) != item.usable) { - std::fprintf(stderr, "driver %d: expected robustBufferAccess2 to be %s\n", static_cast(item.driver), item.usable ? "usable" : "unusable"); - ++failures; - } - } - return failures == 0 ? 0 : 1; -} diff --git a/core/libs/prx/libSceAgcDriver/tests/SavedExecMaskLoop.hpp b/core/libs/prx/libSceAgcDriver/tests/SavedExecMaskLoop.hpp new file mode 100644 index 0000000000..d6aa73bc18 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/SavedExecMaskLoop.hpp @@ -0,0 +1,19 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP + +#include +#include + +namespace AgcTest { + +inline std::vector SavedExecMaskLoop(std::uint32_t rounds) { + std::vector code{0xbea2047eu, 0xbea40380u}; + for (std::uint32_t step = 0; step < rounds; ++step) code.insert(code.end(), {0xbefe0422u, 0x7c080080u, 0x8aa26a7eu}); + const auto branch = 0xbf850000u | static_cast(2 - static_cast(code.size() + 3)); + code.insert(code.end(), {0x80248124u, 0xbf0a8424u, branch, 0xbefe0422u}); + return code; +} + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp b/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp index e6202ceb1a..4adf0c824d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp @@ -21,9 +21,9 @@ void require(bool condition, const char* message) { void setScreenshotDirectory(const std::filesystem::path& directory) { #ifdef _WIN32 - require(_putenv_s("ANYPS5_SCREENSHOTS", directory.string().c_str()) == 0, "set ANYPS5_SCREENSHOTS"); + require(_wputenv_s(L"ANYPS5_SCREENSHOTS", directory.wstring().c_str()) == 0, "set ANYPS5_SCREENSHOTS"); #else - require(::setenv("ANYPS5_SCREENSHOTS", directory.string().c_str(), 1) == 0, "set ANYPS5_SCREENSHOTS"); + require(::setenv("ANYPS5_SCREENSHOTS", directory.c_str(), 1) == 0, "set ANYPS5_SCREENSHOTS"); #endif } @@ -73,7 +73,7 @@ void rejectsInvalidFrames() { int main() try { const auto root = std::filesystem::temp_directory_path() / ("anyps5_screenshot-" + std::to_string(std::random_device{}())); - const auto directory = root / "nested"; + const auto directory = root / std::filesystem::path(u8"nested-a\u00e7\u00e3o-\u65e5\u672c"); std::filesystem::remove_all(root); setScreenshotDirectory(directory); requestsMerge(); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 8ef8dfde37..cc86441030 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -62,11 +62,19 @@ void setEnvironment(const char* name, const std::string& value) { #endif } +void setPathEnvironment(const char* name, const std::filesystem::path& value) { +#ifdef _WIN32 + _wputenv_s(std::filesystem::path(name).wstring().c_str(), value.wstring().c_str()); +#else + setenv(name, value.c_str(), 1); +#endif +} + #ifdef _WIN32 void verifyHostStandardError() { const auto directory = std::filesystem::temp_directory_path() / ("aps5-shader-host-stderr-" + std::to_string(std::random_device{}())); setEnvironment("ANYPS5_NO_SHADER_CACHE", "0"); - setEnvironment("ANYPS5_SHADER_CACHE_DIR", directory.string()); + setPathEnvironment("ANYPS5_SHADER_CACHE_DIR", directory); int capture[2], guest[2]; require(::_pipe(capture, 65536, _O_BINARY) == 0, "cannot create host capture"); require(::_pipe(guest, 65536, _O_BINARY) == 0, "cannot create guest capture"); @@ -148,6 +156,7 @@ void requireSameArtifact(const CompiledShaderArtifact& left, const CompiledShade require(left.parameterExports == right.parameterExports, prefix + "parameter exports differ"); require(left.stencilReferences == right.stencilReferences, prefix + "stencil reference set differs"); require(left.stencilReplaySafe == right.stencilReplaySafe, prefix + "stencil replay proof differs"); + require(left.bdaWrites == right.bdaWrites, prefix + "BDA write flag differs"); require(left.stencilReferenceDword == right.stencilReferenceDword, prefix + "stencil replay push offset differs"); require(left.fragmentParameters.size() == right.fragmentParameters.size(), prefix + "fragment parameter count differs"); for (std::size_t i = 0; i < left.fragmentParameters.size(); ++i) { @@ -259,6 +268,7 @@ RecompileResult sampleResult() { result.fragmentParameters = {{0, 1, true, false, true}, {2, 3, false, true}}; result.stencilReferences = {0u, 17u, 255u}; result.stencilReplaySafe = true; + result.bdaWrites = true; result.stencilReferenceDword = 23u; result.poisonedSrtReads = 3; result.shaderDataUserWords = {2u, 0xffffffffu, 9u}; @@ -322,6 +332,7 @@ CompiledVariant sampleVariant() { image.volumeArraySampleCompatible = true; image.flatLineCompatible = false; image.flatVolumeLineCompatible = false; + image.dimensionQueryOnly = false; image.foldTexelOffsetsOnly = true; image.remappedDepth1D = true; image.byElements = 4; @@ -748,6 +759,7 @@ void verifyKeySensitivity() { changes("spanned waves", [](SampleRequest& sample) { sample.request.target.spannedWaves = true; }); changes("robust buffer access 2", [](SampleRequest& sample) { sample.request.target.robustBufferAccess2 = 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("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; }); @@ -1070,7 +1082,7 @@ void verifyLongHostPaths() { const auto extended = [](const auto& path) { return std::filesystem::path(L"\\\\?\\" + path.native()); }; std::filesystem::create_directories(extended(directory)); setEnvironment("ANYPS5_NO_SHADER_CACHE", "0"); - setEnvironment("ANYPS5_SHADER_CACHE_DIR", directory.string()); + setPathEnvironment("ANYPS5_SHADER_CACHE_DIR", directory); std::vector files; std::filesystem::path entries; struct Cleanup { @@ -2117,6 +2129,36 @@ void verifyMalformedPlans() { require(!ShaderDiskCache::EncodePlan(unblocked, bytes), "a phi without predecessor blocks was encoded"); } +void verifySharedPlans() { + auto fixture = planFixtures().front(); + const auto request = fixture.Request(false); + const auto plan = GetResourcePlan(request); + const std::vector first{std::byte{0x5a}, std::byte{0x01}, std::byte{0x02}}; + const std::vector second{std::byte{0x5a}, std::byte{0x01}, std::byte{0x03}}; + for (const auto* key : {&first, &second}) { + const auto entry = ShaderDiskCache::EncodePlanEntry(*key, *plan); + require(WriteFileAtomically(ShaderDiskCache::EntryDirectory() / ShaderDiskCache::EntryName(*key), entry), "cannot write the shared plan entries"); + } + constexpr std::uint64_t address = 0x7000u; + IrResourcePlan expected; + require(ShaderDiskCache::DecodePlan(encodedPlan(*plan), expected), "the shared plan fixture does not decode"); + expected.shaderHash = address; + const auto hits = ShaderDiskCache::Totals().planHits; + auto a = ShaderDiskCache::LoadSharedPlan(first, address); + auto b = ShaderDiskCache::LoadSharedPlan(second, address); + require(a != nullptr && b != nullptr && ShaderDiskCache::Totals().planHits == hits + 2u, "the shared plan entries did not load"); + require(a.get() == b.get(), "two plan entries with the same payload for one shader decoded into separate plans"); + require(samePlan(*a, expected), "a shared plan differs from the stored plan"); + const auto other = ShaderDiskCache::LoadSharedPlan(second, address + 0x100u); + require(other != nullptr && other.get() != a.get() && other->shaderHash == address + 0x100u && a->shaderHash == address, "a plan of another shader address was shared"); + const std::weak_ptr released = a; + a.reset(); + b.reset(); + require(released.expired(), "the shared plan table kept a plan no source uses"); + const auto again = ShaderDiskCache::LoadSharedPlan(first, address); + require(again != nullptr && samePlan(*again, expected), "a released shared plan did not load again"); +} + void verifyPlanRoundTrip() { const auto handBuilt = handBuiltPlan(); const auto handBytes = encodedPlan(handBuilt); @@ -2496,7 +2538,7 @@ int main(int argc, char** argv) { std::filesystem::remove_all(directory); verifyDefaultDirectory(argv[0]); setEnvironment("ANYPS5_NO_SHADER_CACHE", "0"); - setEnvironment("ANYPS5_SHADER_CACHE_DIR", directory.string()); + setPathEnvironment("ANYPS5_SHADER_CACHE_DIR", directory); verifyAtomicReplacement(directory); verifyResultRoundTrip(); verifyEntryRoundTrip(); @@ -2507,6 +2549,7 @@ int main(int argc, char** argv) { verifyFloatModeSerialization(); verifyPlanRoundTrip(); verifyPlanKey(); + verifySharedPlans(); verifyStore(); verifyPersist(); verifyAcrossProcesses(argv[0]); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index a754f42271..6ee1fdace0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -20,6 +20,7 @@ #include "IntermediateRepresentation/IrBuilder.hpp" #include "IntermediateRepresentation/IrProgram.hpp" #include "SpirvBackend/SpirvAnalysis.hpp" +#include "SavedExecMaskLoop.hpp" #if ANYPS5_ENABLE_SPIRV_TOOLS #include "SpirvBackend/SpirvOptimizer.hpp" #endif @@ -30,6 +31,7 @@ #include "ShaderDiskCache.hpp" #include #include +#include #include #include #include @@ -168,7 +170,6 @@ void verifyFrontendIdentity() { require((preparation.AcquireFrontend(partial) == first) == reuse, "frontend pair: partial-thread state changed the frontend identity"); auto target = request; target.target.subgroupSize = 64u; - target.context.waveSize = 32u; target.context.compute->numThreads = {32u, 2u, 1u}; require((preparation.AcquireFrontend(target) == first) == reuse, "frontend pair: translation-only state changed the frontend identity"); const std::array changedUserData{1u, 2u}; @@ -197,6 +198,10 @@ void verifyFrontendIdentity() { changed.context.userDataBaseRegister = 4u; verifyMiss(changed, "frontend pair: changed user-data base reused the frontend"); changed = request; + changed.context.waveSize = 32u; + changed.context.compute->numThreads = {32u, 2u, 1u}; + verifyMiss(changed, "frontend pair: a different wave size reused the frontend"); + changed = request; changed.context.userData = std::span(userData).first(1u); verifyMiss(changed, "frontend pair: changed user-data count reused the frontend"); changed = request; @@ -222,6 +227,25 @@ void verifyFrontendIdentity() { require((preparation.AcquireFrontend(request) == first) == reuse, "frontend pair: a failed miss replaced the prior frontend"); } +void verifyFrontendWaveSizeCalls() { + using namespace ShaderRecompiler; + const std::vector code{ + 0xbb0e0005u, + 0xd4c2000eu, 0x00020300u, + 0xd5010002u, 0x003a0300u, + 0xbf820001u, + 0xbe80200eu, + 0xbf810000u}; + const std::array userData{}; + for (const std::uint32_t waveSize : {32u, 64u}) { + auto request = FrontendRequest(code, userData); + request.context.waveSize = waveSize; + request.context.compute->numThreads = {waveSize, 1u, 1u}; + ShaderPreparationContext preparation; + require(preparation.AcquireFrontend(request) != nullptr, "frontend wave size: a mask write that reuses the link pair and its v_cndmask read were refused"); + } +} + void verifyFrontendProgram(const ShaderRecompiler::IrProgram& reused, const ShaderRecompiler::IrProgram& fresh) { using namespace ShaderRecompiler; require(ProgramToString(reused) == ProgramToString(fresh), "frontend pair: reused frontend changed the IR"); @@ -256,6 +280,7 @@ void verifyFrontendProgram(const ShaderRecompiler::IrProgram& reused, const Shad void verifyFrontendPair() { using namespace ShaderRecompiler; verifyFrontendIdentity(); + verifyFrontendWaveSizeCalls(); const bool reuse = std::getenv("APS5_NO_PERF_FRONTEND_PAIR") == nullptr; const std::vector code{0xe0700000u, 0x80000000u, 0xbf810000u}; const std::array buffer{0x10000000u, 0u, 0x40u, 0x00027facu}; @@ -318,7 +343,7 @@ void verifyFrontendPair() { const auto freshChanged = PrepareResourceProgram(translated); verifyFrontendProgram(changed, freshChanged); require(changed.WaveSize() != freshFull.WaveSize(), "frontend pair: changed translation inputs were ignored"); - require((preparation.AcquireFrontend(request) == retained) == reuse, "frontend pair: translating a variant replaced the frontend"); + require(preparation.AcquireFrontend(request) != retained, "frontend pair: a variant with another wave size reused the frontend"); } for (const bool partialFirst : {false, true}) { @@ -381,6 +406,36 @@ void verifyFrontendPair() { } } +void verifySpecializedModuleRelease() { + using namespace ShaderRecompiler; + const std::array color{0x3f800000u}; + const std::vector code{0xbf800003u, 0x7e000200u, 0xf800180fu, 0u, 0xbf810000u}; + auto pixel = FrontendRequest(code, color); + pixel.shader.stage = ShaderStage::Fragment; + pixel.context.compute.reset(); + pixel.context.pixel = ShaderPixelStageInfo{}; + pixel.context.pixel->wave32 = true; + pixel.context.pixel->targetOutputMode[0] = 9u; + pixel.context.waveSize = 32u; + pixel.useCache = true; + SharedSpirv::Weak released; + std::uint64_t specializationId = 0; + std::vector words; + { + const auto first = Recompile(pixel); + require(first.specializationId != 0u, "specialized module release: the pixel fixture was not specialized"); + released = first.spirv.Weaken(); + specializationId = first.specializationId; + words = first.spirv.Copy(); + } + require(released.Expired(), "specialized module release: the module caches kept the words of a module no result uses"); + const auto again = Recompile(pixel); + require(again.specializationId == specializationId, "specialized module release: a module built again from its key got another specialization ID"); + require(std::ranges::equal(again.spirv.Words(), words), "specialized module release: a module built again from its key has other words"); + const auto shared = Recompile(pixel); + require(shared.spirv.data() == again.spirv.data(), "specialized module release: a second result of a live module copied its words"); +} + ShaderRecompiler::RecompileRequest LayoutPairRequest(std::span code, std::span userData, bool registration, std::uint32_t wordLimit) { using namespace ShaderRecompiler; auto request = FrontendRequest(code, userData); @@ -542,6 +597,297 @@ void verifyRegisterSources() { require(!EquivalentValue(plan, &samplerRegister, &firstVector), "different register types were merged"); } +void verifyPlanConditionPruning() { + using namespace ShaderRecompiler; + std::vector> values; + std::uint32_t ids = 0; + const auto make = [&](IrOpcode opcode, IrType type) -> IrValue& { + values.push_back(std::make_unique(opcode, type, ids++)); + return *values.back(); + }; + const auto constant = [&](std::uint32_t value) -> IrValue& { + auto& immediate = make(IrOpcode::Void, IrType::U32); + immediate.SetImmediateU32(value); + return immediate; + }; + const auto userData = [&](std::uint32_t index) -> IrValue& { + auto& reg = make(IrOpcode::Void, IrType::ScalarReg); + reg.SetRegister({RegisterBank::Scalar, index}); + auto& read = make(IrOpcode::GetUserData, IrType::U32); + read.AddArgument(®); + return read; + }; + const auto equal = [&](IrValue& left, IrValue& right) -> IrValue& { + auto& compare = make(IrOpcode::IEqual32, IrType::Bool); + compare.AddArgument(&left); + compare.AddArgument(&right); + return compare; + }; + constexpr std::uint32_t Additions = 64; + IrValue* address = &userData(0); + for (std::uint32_t index = 0; index < Additions; ++index) { + auto& sum = make(IrOpcode::IAdd32, IrType::U32); + sum.AddArgument(address); + sum.AddArgument(&constant(4)); + address = ∑ + } + auto& load = make(IrOpcode::LoadBufferU32, IrType::U32); + load.AddArgument(address); + auto& pair = make(IrOpcode::CompositeConstructU32x2, IrType::U32x2); + pair.AddArgument(&userData(1)); + pair.AddArgument(&load); + auto& low = make(IrOpcode::CompositeExtractU32x2, IrType::U32); + low.AddArgument(&pair); + low.AddArgument(&constant(0)); + static std::uint32_t word = 5; + const auto wordAddress = static_cast(reinterpret_cast(&word)); + auto& handle = make(IrOpcode::CompositeConstructU64, IrType::U64); + handle.AddArgument(&userData(2)); + handle.AddArgument(&userData(3)); + auto& read = make(IrOpcode::LoadAddressU32, IrType::U32); + read.SetFlags(MemoryFlags{0, 0}); + read.AddArgument(&handle); + read.AddArgument(&constant(0)); + IrProgram program; + auto& resources = program.Resources(); + resources.srtPlanComplete = true; + resources.resourceTrackingComplete = true; + MemoryInfo memory; + memory.kind = ResourceKind::ScalarAddress; + resources.memoryInfo.push_back(memory); + resources.controlFlow.push_back({&equal(load, constant(0)), {1, 2}, {}, {}}); + resources.controlFlow.push_back({&equal(low, constant(7)), {3, 4}, {}, {}}); + resources.controlFlow.push_back({&equal(read, constant(5)), {5, 6}, {}, {}}); + auto& choice = make(IrOpcode::SelectU32, IrType::U32); + choice.AddArgument(&equal(userData(4), constant(0))); + choice.AddArgument(&load); + choice.AddArgument(&constant(1)); + resources.controlFlow.push_back({&equal(choice, constant(1)), {7, 8}, {}, {}}); + const auto plan = ResourceMaterializer{}.ExtractPlan(program); + const auto copied = std::ranges::count_if(plan.valueStorage, [](const IrValue& value) { return value.Opcode() == IrOpcode::IAdd32; }); + require(copied == 0, "plan pruning: the address computation under a load the evaluator cannot read was copied"); + require(plan.controlFlow[0].condition == nullptr && plan.controlFlow[3].condition == nullptr, "plan pruning: a condition the evaluator can never read was kept"); + require(plan.controlFlow[1].condition != nullptr && plan.controlFlow[2].condition != nullptr, "plan pruning: a condition the evaluator can read was dropped"); + const auto copiedLoad = std::ranges::find_if(plan.valueStorage, [](const IrValue& value) { return value.Opcode() == IrOpcode::LoadBufferU32; }); + require(copiedLoad != plan.valueStorage.end() && copiedLoad->ArgumentCount() == 0u, "plan pruning: the unevaluable load was not kept as a leaf"); + require(std::ranges::all_of(plan.valueStorage, [](const IrValue& value) { return !value.HasUses() && value.OperandUses().empty() && value.Arguments().capacity() == value.ArgumentCount(); }), "plan compaction: a plan value keeps use lists or spare operand capacity"); + const std::array data{0x100u, 7u, static_cast(wordAddress), static_cast(wordAddress >> 32u)}; + SrtRuntime runtime{data}; + Detail::Evaluator evaluator(plan, runtime); + std::uint32_t result = 0; + require(evaluator.Evaluate(Detail::IrNode(plan.controlFlow[1].condition), result) && result == 1u, "plan pruning: a condition read through an extracted pair was not evaluated"); + require(evaluator.Evaluate(Detail::IrNode(plan.controlFlow[2].condition), result) && result == 1u, "plan pruning: a condition on a raw read lost its handle"); +} + +struct PlanWalk { + std::vector results; + std::vector flat; + std::vector active; +}; + +PlanWalk walkPlan(const ShaderRecompiler::IrResourcePlan& plan, std::span sources, std::span cleanFlatSlots, const ShaderRecompiler::SrtRuntime& runtime) { + PlanWalk walk; + ShaderRecompiler::SrtWalker{}.EvaluateRuntimeSources(plan, sources, runtime, walk.results, walk.flat, cleanFlatSlots, walk.active); + return walk; +} + +bool readFive(void*, std::uint64_t, std::uint32_t* value) { + *value = 5u; + return true; +} + +void verifyPlanRetention() { + using namespace ShaderRecompiler; + std::vector> values; + std::uint32_t ids = 0; + const auto make = [&](IrOpcode opcode, IrType type) -> IrValue& { + values.push_back(std::make_unique(opcode, type, ids++)); + return *values.back(); + }; + const auto constant = [&](std::uint32_t value) -> IrValue& { + auto& immediate = make(IrOpcode::Void, IrType::U32); + immediate.SetImmediateU32(value); + return immediate; + }; + const auto userData = [&](std::uint32_t index) -> IrValue& { + auto& reg = make(IrOpcode::Void, IrType::ScalarReg); + reg.SetRegister({RegisterBank::Scalar, index}); + auto& read = make(IrOpcode::GetUserData, IrType::U32); + read.AddArgument(®); + return read; + }; + const auto binary = [&](IrOpcode opcode, IrType type, IrValue& left, IrValue& right) -> IrValue& { + auto& value = make(opcode, type); + value.AddArgument(&left); + value.AddArgument(&right); + return value; + }; + constexpr std::uint32_t Chain = 64; + IrValue* address = &userData(4); + for (std::uint32_t step = 0; step < Chain; ++step) address = &binary(IrOpcode::IAdd32, IrType::U32, *address, constant(1)); + auto& load = make(IrOpcode::LoadBufferU32, IrType::U32); + load.AddArgument(address); + auto& low = binary(IrOpcode::CompositeExtractU32x2, IrType::U32, binary(IrOpcode::CompositeConstructU32x2, IrType::U32x2, userData(1), load), constant(0)); + auto& mask = binary(IrOpcode::IEqual32, IrType::Bool, userData(5), constant(0)); + auto& choice = make(IrOpcode::SelectU32, IrType::U32); + choice.AddArgument(&mask); + choice.AddArgument(&userData(6)); + choice.AddArgument(&load); + auto& offset = binary(IrOpcode::ReadFirstLane, IrType::U32, choice, mask); + auto& read = binary(IrOpcode::LoadAddressU32, IrType::U32, binary(IrOpcode::CompositeConstructU64, IrType::U64, userData(2), userData(3)), offset); + read.SetFlags(MemoryFlags{0, 0}); + IrProgram program; + auto& resources = program.Resources(); + resources.srtPlanComplete = true; + resources.resourceTrackingComplete = true; + MemoryInfo memory; + memory.kind = ResourceKind::ScalarAddress; + resources.memoryInfo.push_back(memory); + for (std::uint32_t source = 0; source < 2u; ++source) { + DescriptorSource descriptor; + descriptor.dwordCount = 4; + for (std::uint32_t dword = 0; dword < 4u; ++dword) descriptor.dwords[dword] = &userData(10u + source * 4u + dword); + resources.descriptorSources.push_back(descriptor); + BufferResource buffer; + buffer.source = source; + resources.info.buffers.push_back(buffer); + } + resources.controlFlow.push_back({&binary(IrOpcode::IEqual32, IrType::Bool, low, constant(7)), {1, 2}, {0}, {}}); + resources.controlFlow.push_back({&binary(IrOpcode::IEqual32, IrType::Bool, read, constant(5)), {3, 4}, {}, {}}); + resources.controlFlow.push_back({nullptr, {}, {}, {}}); + resources.controlFlow.push_back({nullptr, {}, {}, {}}); + resources.controlFlow.push_back({nullptr, {}, {1}, {}}); + const auto plan = ResourceMaterializer{}.ExtractPlan(program); + constexpr std::size_t Read = 40; + require(plan.valueStorage.Size() == Read, ("plan retention: the plan keeps " + std::to_string(plan.valueStorage.Size()) + " values; its conditions and descriptor sources read " + std::to_string(Read)).c_str()); + require(std::ranges::none_of(plan.valueStorage, [](const IrValue& value) { return value.Opcode() == IrOpcode::IAdd32; }), "plan retention: the address under a vector load was copied"); + const auto copiedLoad = std::ranges::find_if(plan.valueStorage, [](const IrValue& value) { return value.Opcode() == IrOpcode::LoadBufferU32; }); + require(copiedLoad != plan.valueStorage.end() && copiedLoad->ArgumentCount() == 0u, "plan retention: the vector load was not kept as a leaf"); + std::array userWords{}; + userWords[1] = 7u; + userWords[2] = 0x1000u; + userWords[6] = 8u; + for (std::uint32_t word = 10; word < userWords.size(); ++word) userWords[word] = 0x100u + word; + SrtRuntime runtime{userWords}; + runtime.readMemory = readFive; + runtime.readSpecializationMemory = readFive; + const auto copiedWalk = walkPlan(plan, plan.materializationSources, plan.cleanFlatSlots, runtime); + const std::array sources{0u, 1u}; + const auto wholeWalk = walkPlan(program.Resources(), sources, {}, runtime); + require(wholeWalk.active == std::vector{1u, 0u}, "plan retention: the program's walk did not decide both conditions"); + require(copiedWalk.active == wholeWalk.active && copiedWalk.results == wholeWalk.results && copiedWalk.flat == wholeWalk.flat, "plan retention: the plan's walk differs from the program's"); +} + +void verifyPlanRetentionChains() { + using namespace ShaderRecompiler; + std::vector kept; + std::vector conditions; + for (const std::uint32_t chain : {1u, 16u, 64u}) { + std::vector code{0xf4080600u, 0xfa000060u, 0xbf8cc07fu, 0x7e140300u}; + code.insert(code.end(), chain, 0x4a141481u); + code.insert(code.end(), {0x34141482u, 0xe0301000u, 0x80060b0au, 0xbf8c0000u, 0x7e28050bu, 0xf4000540u, 0x28000000u, 0xf4080700u, 0xfa000020u, 0xbf8c0000u, 0xbf078015u, 0xbf840002u, 0xe0700000u, 0x80070000u, 0xbf810000u}); + const std::array userData{0x10000u, 0u}; + const auto plan = GetResourcePlan(FrontendRequest(code, userData)); + kept.push_back(plan->valueStorage.Size()); + conditions.push_back(static_cast(std::ranges::count_if(plan->controlFlow, [](const ResourceBlock& block) { return block.condition != nullptr; }))); + } + require(conditions.front() != 0u, "plan retention: the program's plan keeps no branch condition, so the test does not reach the condition's values"); + require(kept[0] == kept[1] && kept[1] == kept[2], ("plan retention: the plan keeps " + std::to_string(kept[0]) + ", " + std::to_string(kept[1]) + " and " + std::to_string(kept[2]) + " values for vector chains of 1, 16 and 64 steps before the scalar load its branch condition reads; it must keep only what the condition's evaluation can reach").c_str()); +} + +void verifyPlanCopyAnswers() { + using namespace ShaderRecompiler; + std::vector> values; + std::vector> blocks; + std::uint32_t ids = 0; + const auto make = [&](IrOpcode opcode, IrType type) -> IrValue& { + values.push_back(std::make_unique(opcode, type, ids++)); + return *values.back(); + }; + const auto constant = [&](std::uint32_t value) -> IrValue& { + auto& immediate = make(IrOpcode::Void, IrType::U32); + immediate.SetImmediateU32(value); + return immediate; + }; + const auto userData = [&](std::uint32_t index) -> IrValue& { + auto& reg = make(IrOpcode::Void, IrType::ScalarReg); + reg.SetRegister({RegisterBank::Scalar, index}); + auto& read = make(IrOpcode::GetUserData, IrType::U32); + read.AddArgument(®); + return read; + }; + const auto binary = [&](IrOpcode opcode, IrType type, IrValue& left, IrValue& right) -> IrValue& { + auto& value = make(opcode, type); + value.AddArgument(&left); + value.AddArgument(&right); + return value; + }; + const auto vectorLoad = [&](IrValue& address) -> IrValue& { + auto& load = make(IrOpcode::LoadBufferU32, IrType::U32); + load.AddArgument(&address); + return load; + }; + const auto rawRead = [&](IrValue& low, IrValue& high, IrValue& offset) -> IrValue& { + auto& read = binary(IrOpcode::LoadAddressU32, IrType::U32, binary(IrOpcode::CompositeConstructU64, IrType::U64, low, high), offset); + read.SetFlags(MemoryFlags{0, 0}); + return read; + }; + const auto walks = [&](IrProgram& program, IrValue& read, const std::array& userWords, std::uint8_t expected, const char* what) { + auto& resources = program.Resources(); + resources.srtPlanComplete = true; + resources.resourceTrackingComplete = true; + MemoryInfo memory; + memory.kind = ResourceKind::ScalarAddress; + resources.memoryInfo.push_back(memory); + DescriptorSource descriptor; + descriptor.dwordCount = 4; + for (std::uint32_t dword = 0; dword < 4u; ++dword) descriptor.dwords[dword] = &userData(10u + dword); + resources.descriptorSources.push_back(descriptor); + BufferResource buffer; + resources.info.buffers.push_back(buffer); + resources.controlFlow.push_back({&binary(IrOpcode::IEqual32, IrType::Bool, read, constant(5)), {1, 2}, {}, {}}); + resources.controlFlow.push_back({nullptr, {}, {}, {}}); + resources.controlFlow.push_back({nullptr, {}, {0}, {}}); + const auto plan = ResourceMaterializer{}.ExtractPlan(program); + SrtRuntime runtime{userWords}; + runtime.readMemory = readFive; + runtime.readSpecializationMemory = readFive; + const std::array sources{0u}; + const auto whole = walkPlan(resources, sources, {}, runtime); + const auto taped = walkPlan(plan, plan.materializationSources, plan.cleanFlatSlots, runtime); + const auto interpreted = walkPlan(plan, sources, {}, runtime); + require(whole.active == std::vector{expected}, what); + require(taped.active == whole.active && taped.results == whole.results, what); + require(interpreted.active == whole.active && interpreted.results == whole.results, what); + }; + std::array userWords{}; + for (std::uint32_t word = 10; word < userWords.size(); ++word) userWords[word] = 0x100u + word; + + IrProgram phiProgram; + auto& shared = userData(1); + const auto low = [&](IrValue& hidden) -> IrValue& { + return binary(IrOpcode::CompositeExtractU32x2, IrType::U32, binary(IrOpcode::CompositeConstructU32x2, IrType::U32x2, shared, hidden), constant(0)); + }; + auto& left = *blocks.emplace_back(std::make_unique(1u)); + auto& right = *blocks.emplace_back(std::make_unique(2u)); + auto& phi = make(IrOpcode::Phi, IrType::U32); + phi.AddPhiOperand(&left, &low(vectorLoad(userData(4)))); + phi.AddPhiOperand(&right, &low(vectorLoad(userData(5)))); + userWords[1] = 8u; + userWords[2] = 0x1000u; + walks(phiProgram, rawRead(userData(2), userData(3), phi), userWords, 1u, "plan copy: a phi whose incoming values differ below a vector load resolved in the plan, not in the program"); + + IrProgram rootProgram; + auto& mask = binary(IrOpcode::IEqual32, IrType::Bool, userData(5), constant(0)); + auto& choice = make(IrOpcode::SelectU32, IrType::U32); + choice.AddArgument(&mask); + choice.AddArgument(&userData(2)); + choice.AddArgument(&vectorLoad(rawRead(userData(8), userData(9), constant(0)))); + userWords[1] = 0u; + userWords[2] = 0u; + walks(rootProgram, rawRead(binary(IrOpcode::ReadFirstLane, IrType::U32, choice, mask), userData(3), constant(0)), userWords, 0u, "plan copy: a read from a null base loaded from memory only below a vector load counted as a null root in the plan, not in the program"); +} + void verifySignedMinimum() { using namespace ShaderRecompiler; IrResourcePlan plan; @@ -945,6 +1291,135 @@ void verifyPureFlatSlots() { require(Detail::ComputePureFlatSlots(plan) == Pure{0, 0, 0, 0}, "pure flat slots: an incomplete plan was classified"); } +void verifyLaneMaskGuardedDescriptorSources() { + using namespace ShaderRecompiler; + constexpr std::uint32_t Format8888UNorm = 56; + constexpr std::uint32_t Type2D = 9; + constexpr std::uint32_t Zero = 0x00000000u; + constexpr std::uint32_t NegativeZero = 0x80000000u; + constexpr std::uint32_t One = 0x3f800000u; + struct alignas(256) Texture { std::array bytes{}; }; + static Texture texture; + const auto base = static_cast(reinterpret_cast(texture.bytes.data())); + const std::array valid{static_cast(base >> 8u), static_cast((base >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (3u << 30u), 3u << 14u, 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u}; + const std::array invalid{0x43700000u, 0x43070000u, 0x3b888889u, 0x3bf2b9d6u, 0x3b088889u, 0x3b72b9d6u, 0x3f7f7777u, 0x3f7f0d46u}; + const std::array sampler{0u, 0u, 0u, 0u}; + std::array output{}; + const auto outputAddress = static_cast(reinterpret_cast(output.data())); + const std::array outputV{static_cast(outputAddress), static_cast((outputAddress >> 32u) & 0xffffu), 16u, 0xfacu}; + std::array srt{}; + const auto fill = [&](const std::array& image, std::uint32_t mode) { + std::copy(image.begin(), image.end(), srt.begin()); + std::copy(sampler.begin(), sampler.end(), srt.begin() + 8); + std::copy(outputV.begin(), outputV.end(), srt.begin() + 16); + srt[20] = mode; + }; + const auto srtAddress = static_cast(reinterpret_cast(srt.data())); + 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{}; + const std::vector code{0xf4000400u, 0xfa000050u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xbf8cc07fu, 0xd40d006au, 0x00002080u, 0xbf860006u, 0xf40c0200u, 0xfa000000u, 0xbf8cc07fu, 0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xbf810000u}; + request.shader = {ShaderStage::Compute, 0x24000u, code, 0, {}}; + request.context.waveSize = 32; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.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; + + fill(invalid, Zero); + const auto plan = GetResourcePlan(request); + require(plan->info.images.size() == 1u && plan->info.samplers.size() == 1u, "lane-mask guarded sources: the image and its sampler were not planned"); + require(!plan->controlFlow.empty() && std::ranges::any_of(plan->controlFlow, [](const ResourceBlock& block) { return block.condition != nullptr; }), "lane-mask guarded sources: the VCC branch around the image kept no condition"); + for (const auto mode : {Zero, NegativeZero}) { + fill(invalid, mode); + AgcDriver::ShaderMemory memory({}); + const auto capture = memory.Capture(request); + require(capture->snapshot.images.size() == 1u && std::ranges::all_of(capture->snapshot.images[0].dwords, [](std::uint32_t word) { return word == 0u; }), "lane-mask guarded sources: the T# behind a branch no lane takes was not left null"); + } + fill(invalid, One); + { + AgcDriver::ShaderMemory memory({}); + expectFailure([&] { static_cast(memory.Capture(request)); }, "runtime image descriptor is invalid", "lane-mask guarded sources: an invalid T# behind a branch the lanes may take was accepted"); + } + fill(valid, One); + { + AgcDriver::ShaderMemory memory({}); + const auto capture = memory.Capture(request); + require(capture->snapshot.images.size() == 1u && std::equal(valid.begin(), valid.end(), capture->snapshot.images[0].dwords.begin()), "lane-mask guarded sources: the T# behind a branch the lanes may take was not read"); + } +} + +void verifyExecGuardedDescriptorSources() { + using namespace ShaderRecompiler; + constexpr std::uint32_t Format8888UNorm = 56; + constexpr std::uint32_t Type2D = 9; + struct alignas(256) Texture { std::array bytes{}; }; + static Texture texture; + const auto base = static_cast(reinterpret_cast(texture.bytes.data())); + const std::array valid{static_cast(base >> 8u), static_cast((base >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (3u << 30u), 3u << 14u, 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u}; + const std::array invalid{0x43700000u, 0x43070000u, 0x3b888889u, 0x3bf2b9d6u, 0x3b088889u, 0x3b72b9d6u, 0x3f7f7777u, 0x3f7f0d46u}; + const std::array sampler{0u, 0u, 0u, 0u}; + std::array output{}; + const auto outputAddress = static_cast(reinterpret_cast(output.data())); + const std::array outputV{static_cast(outputAddress), static_cast((outputAddress >> 32u) & 0xffffu), 16u, 0xfacu}; + std::array srt{}; + const auto fill = [&](const std::array& image, std::uint32_t mode) { + std::copy(image.begin(), image.end(), srt.begin()); + std::copy(sampler.begin(), sampler.end(), srt.begin() + 8); + std::copy(outputV.begin(), outputV.end(), srt.begin() + 16); + srt[20] = mode; + }; + const auto srtAddress = static_cast(reinterpret_cast(srt.data())); + 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{}; + const std::vector code{0xf4000400u, 0xfa000050u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xbf8cc07fu, 0x7da80010u, 0xbf880006u, 0xf40c0200u, 0xfa000000u, 0xbf8cc07fu, 0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xbf810000u}; + request.shader = {ShaderStage::Compute, 0x24000u, code, 0, {}}; + request.context.waveSize = 32; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.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; + + fill(invalid, 0u); + const auto plan = GetResourcePlan(request); + require(plan->info.images.size() == 1u && plan->info.samplers.size() == 1u, "exec guarded sources: the image and its sampler were not planned"); + require(!plan->controlFlow.empty() && std::ranges::any_of(plan->controlFlow, [](const ResourceBlock& block) { return block.condition != nullptr; }), "exec guarded sources: the EXEC branch around the image kept no condition"); + { + fill(invalid, 0u); + AgcDriver::ShaderMemory memory({}); + const auto capture = memory.Capture(request); + require(capture->snapshot.images.size() == 1u && std::ranges::all_of(capture->snapshot.images[0].dwords, [](std::uint32_t word) { return word == 0u; }), "exec guarded sources: the T# behind a branch no lane takes was not left null"); + } + fill(invalid, 1u); + { + AgcDriver::ShaderMemory memory({}); + expectFailure([&] { static_cast(memory.Capture(request)); }, "runtime image descriptor is invalid", "exec guarded sources: an invalid T# behind a branch the lanes may take was accepted"); + } + fill(valid, 1u); + { + AgcDriver::ShaderMemory memory({}); + const auto capture = memory.Capture(request); + require(capture->snapshot.images.size() == 1u && std::equal(valid.begin(), valid.end(), capture->snapshot.images[0].dwords.begin()), "exec guarded sources: the T# behind a branch the lanes may take was not read"); + } +} + void verifyGuardedDescriptorSources() { using namespace ShaderRecompiler; constexpr std::uint32_t Format8888UNorm = 56; @@ -1248,6 +1723,7 @@ constexpr std::uint32_t TableFormat8888UNorm = 56; constexpr std::uint32_t TableFormat32UInt = 20; constexpr std::uint32_t TableFormat16SInt = 12; constexpr std::uint32_t TableFormat11_11_10UInt = 34; +constexpr std::uint32_t TableFormat32_32UInt = 62; constexpr std::uint32_t TableType2D = 9; constexpr std::uint32_t TableType3D = 10; @@ -1330,6 +1806,41 @@ std::array TableSelected(const ShaderRecompiler::ImageTableSna return column.words.at(column.codes[0]).dwords; } +void verifyTargetFeatureKey() { + using namespace ShaderRecompiler; + static constexpr std::array code{0xbf810000u}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0x10000u, code, 0, {}}; + request.context.waveSize = 64; + const std::vector capabilities{1u, 4448u, 5347u}; + const std::vector extensions{"SPV_KHR_8bit_storage", "SPV_KHR_physical_storage_buffer"}; + const auto build = [&](std::span capabilityList, std::span extensionList) { + auto keyed = request; + keyed.target.supportedCapabilities = capabilityList; + keyed.target.supportedExtensions = extensionList; + std::vector key; + RecompileCacheKey::Build(keyed, key); + return key; + }; + const auto base = build(capabilities, extensions); + const std::vector capabilityCopy = capabilities; + const std::vector extensionStorage{"SPV_KHR_8bit_storage", "SPV_KHR_physical_storage_buffer"}; + const std::vector extensionCopy{extensionStorage[0], extensionStorage[1]}; + require(build(capabilityCopy, extensionCopy) == base, "target feature key: equal capability and extension lists in other storage gave another key"); + auto renamed = extensionStorage; + renamed[1].back() = 'X'; + const std::vector renamedViews{renamed[0], renamed[1]}; + require(build(capabilities, renamedViews) != base, "target feature key: an extension name that differs in its last byte gave the same key"); + const std::vector moved{"SPV_KHR_8bit_storageS", "PV_KHR_physical_storage_buffer"}; + require(build(capabilities, moved) != base, "target feature key: a byte moved between two extension names gave the same key"); + const std::vector more{1u, 4448u, 5347u, 11u}; + require(build(more, extensions) != base, "target feature key: an added capability gave the same key"); + std::vector many; + for (std::uint32_t index = 0; index < 40u; ++index) many.push_back("SPV_EXT_descriptor_indexing_" + std::to_string(index)); + const std::vector manyViews(many.begin(), many.end()); + require(build(capabilities, manyViews).size() == base.size(), "target feature key: the key grows with the device's extension names"); +} + void verifyImageTableKeys() { using namespace ShaderRecompiler; namespace Abi = ImageTableAbi; @@ -1858,7 +2369,10 @@ void verifyTableSnapshotReuse() { const auto otherBuffer = CompileTable(request); std::copy(outputV.begin(), outputV.end(), guest.srt.begin() + 12); const auto resolvedShared = ResourceMaterializer::TableReuse(); - require(otherBuffer.result != compiled.result && otherBuffer.result->variantId == compiled.result->variantId && resolvedShared.resolutionsShared - resolvedBefore.resolutionsShared == 1u && resolvedShared.resolutions == resolvedBefore.resolutions && TableMap(*otherBuffer.result) == map && TableBinding(*otherBuffer.result, DescriptorRole::ImageTable)->guestDescriptor == images->guestDescriptor, "table snapshot reuse: a result for another buffer did not reuse the unchanged table's resolution"); + const bool resolves = std::getenv("APS5_NO_MASKED_RESULT_MEMO") != nullptr || std::getenv("APS5_VERIFY_MASKED_RESULT_MEMO") != nullptr; + require(otherBuffer.result != compiled.result && otherBuffer.result->variantId == compiled.result->variantId && resolvedShared.resolutionsShared - resolvedBefore.resolutionsShared == (resolves ? 1u : 0u) && resolvedShared.resolutions == resolvedBefore.resolutions && TableMap(*otherBuffer.result) == map && TableBinding(*otherBuffer.result, DescriptorRole::ImageTable)->guestDescriptor == images->guestDescriptor, "table snapshot reuse: a result for another buffer did not reuse the unchanged table's resolution"); + const auto* otherBuffers = TableBinding(*otherBuffer.result, DescriptorRole::GuestBuffers); + require(otherBuffers != nullptr && std::search(otherBuffers->guestDescriptor.begin(), otherBuffers->guestDescriptor.end(), otherOutputV.begin(), otherOutputV.end()) != otherBuffers->guestDescriptor.end(), "table snapshot reuse: a result for another buffer does not hold that buffer's descriptor"); const auto storedWord = heap[5u * 8u]; heap[5u * 8u] = heap[6u * 8u]; const auto restoredMap = map; @@ -2873,7 +3387,30 @@ void verifyImageTableRegistration() { const auto* storeTable = TableBinding(*store.result, DescriptorRole::ImageStorageTable); require((storeFault & Abi::PoisonFlag) == 0u && storeTable != nullptr && storeTable->count != 0u && std::all_of(storeTable->imageWritten.begin(), storeTable->imageWritten.end(), [](bool written) { return written; }), "image table registration: the store's table was not bound as written storage elements"); require(TableBinding(*store.result, DescriptorRole::ImageTable) == nullptr || TableBinding(*store.result, DescriptorRole::ImageTable)->count == 0u, "image table registration: a store-only table bound sampled elements"); - const std::array, 3> unsupportedStores{{{0xf0240f08u, "image_store_mip"}, {0xf0280f08u, "image_store_pck"}, {0xf0440108u, "image_atomic_add"}}}; + auto atomicCode = storeCode; + atomicCode[8] = 0xf0440108u; + const auto atomic = CompileTable(TableRequest(atomicCode, userData)); + const auto& atomicPlan = *atomic.capture->plan; + require(atomicPlan.info.images.size() == 1u && atomicPlan.info.images[0].table == 0u && atomicPlan.info.images[0].tableOperation == TableOperation::Supported, "image table registration: a table T# feeding image_atomic_add was not a supported table operation"); + const auto& atomicMap = TableMap(*atomic.result); + const auto atomicFault = atomicMap[Abi::TableHeader(0) + Abi::TableFault]; + const auto* atomicTable = TableBinding(*atomic.result, DescriptorRole::ImageStorageTable); + require((atomicFault & Abi::PoisonFlag) == 0u && atomicTable != nullptr && atomicTable->count != 0u && atomicTable->imageAtomic.size() == atomicTable->count && std::all_of(atomicTable->imageWritten.begin(), atomicTable->imageWritten.end(), [](bool written) { return written; }) && std::all_of(atomicTable->imageAtomic.begin(), atomicTable->imageAtomic.end(), [](bool atomic) { return atomic; }), "image table registration: the image_atomic_add table was not bound as atomic storage elements"); + require(TableBinding(*atomic.result, DescriptorRole::ImageTable) == nullptr || TableBinding(*atomic.result, DescriptorRole::ImageTable)->count == 0u, "image table registration: an atomic-only table bound sampled elements"); + auto atomic64Code = storeCode; + atomic64Code[8] = 0xf0440308u; + guest.heap[0] = TableT(texture.bytes.data(), TableFormat32_32UInt, TableType2D); + static constexpr std::array int64ImageCapabilities{29u, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityInt64ImageEXT}; + auto atomic64Request = TableRequest(atomic64Code, userData); + atomic64Request.target.supportedCapabilities = int64ImageCapabilities; + const auto atomic64 = CompileTable(atomic64Request); + const auto& atomic64Plan = *atomic64.capture->plan; + require(atomic64Plan.info.images.size() == 1u && atomic64Plan.info.images[0].table == 0u && atomic64Plan.info.images[0].tableOperation == TableOperation::Supported, "image table registration: a table T# feeding image_atomic64_add was not a supported table operation"); + const auto& atomic64Map = TableMap(*atomic64.result); + const auto atomic64Fault = atomic64Map[Abi::TableHeader(0) + Abi::TableFault]; + const auto* atomic64Table = TableBinding(*atomic64.result, DescriptorRole::ImageStorageTable); + require((atomic64Fault & Abi::PoisonFlag) == 0u && atomic64Table != nullptr && atomic64Table->count != 0u && atomic64Table->imageAtomic64.size() == atomic64Table->count && std::all_of(atomic64Table->imageAtomic64.begin(), atomic64Table->imageAtomic64.end(), [](bool atomic) { return atomic; }), "image table registration: the image_atomic64_add table was not bound as 64-bit atomic storage elements"); + const std::array, 2> unsupportedStores{{{0xf0240f08u, "image_store_mip"}, {0xf0280f08u, "image_store_pck"}}}; for (const auto& [instruction, name] : unsupportedStores) { auto code = storeCode; code[8] = instruction; @@ -3062,6 +3599,18 @@ void verifyPointerImageTable() { require(!TableTouched(narrowTable.regions, root + (Palette + 32u) / 4u, 32u) && !TableTouched(narrowTable.regions, root + Committed / 4u, Reserved - Committed), "pointer image table: a record outside the key domain was read"); const std::vector> narrowReads{{base + Palette, 32u}, {base + Palette + 2u * 32u, 32u}, {base + Palette + 255u * 32u, 32u}, {base + Palette + 0x102u * 32u, 32u}}; require(narrowSnapshot.readRanges == narrowReads && narrowTable.result->imageTableReadRanges.size() == 1u && narrowTable.result->imageTableReadRanges[0] == narrowReads, "pointer image table: consumed ranges include skipped keys or the key-domain buffer"); + + auto phiCode = maskedCode; + phiCode.erase(phiCode.begin() + 8, phiCode.begin() + 10); + phiCode.insert(phiCode.begin() + 8, { + 0xbf068010u, 0xbf850003u, 0xf4200444u, 0x20000000u, 0xbf820002u, 0xf4200444u, 0x20000000u + }); + const auto phiRequest = request(phiCode, 0x36000u); + const auto phiPlan = GetResourcePlan(phiRequest); + require(phiPlan->info.images.size() == 1u && phiPlan->info.images[0].table == 0u, "pointer image table: a pointer read through a phi was not planned as a table"); + const auto phiTable = CompileTable(phiRequest); + require(phiTable.capture->snapshot.tables.tables.at(0).codes == snapshot.codes, "pointer image table: a pointer read through a phi captured different records"); + keys = {}; #ifdef _WIN32 VirtualFree(block, 0, MEM_RELEASE); @@ -3279,7 +3828,7 @@ void verifySelectedRecordDirectOperations() { FillTableSrt(guest, heap.data(), 32u, 4u); guest.srt[16] = 2u; const auto userData = TableUserData(guest); - const std::array, 4> operations{{{"image_store", 0xf0200f08u, TableFormat32UInt}, {"image_atomic_add", 0xf0440108u, TableFormat32UInt}, {"image_load_pck", 0xf0080f08u, TableFormat32UInt}, {"image_load_by2", 0xf1080308u, TableFormat16UNorm}}}; + const std::array, 5> operations{{{"image_store", 0xf0200f08u, TableFormat32UInt}, {"image_atomic_add", 0xf0440108u, TableFormat32UInt}, {"image_atomic64_add", 0xf0440308u, TableFormat32_32UInt}, {"image_load_pck", 0xf0080f08u, TableFormat32UInt}, {"image_load_by2", 0xf1080308u, TableFormat16UNorm}}}; for (const auto& [name, word0, format] : operations) { const auto what = std::string("selected record direct operations: ") + name; heap[2] = TableT(textures[1].bytes.data(), format, TableType2D); @@ -3287,11 +3836,11 @@ void verifySelectedRecordDirectOperations() { const auto request = TableRequest(code, userData); const auto plan = GetResourcePlan(request); require(plan->info.images.size() == 1u && plan->info.images[0].table == NoTable && !plan->descriptorSources.at(plan->info.images[0].source).tableColumn.has_value(), (what + " through a T# whose record the CPU evaluates was planned through an image table").c_str()); - const auto compiled = CompileTable(request); - require(TableBinding(*compiled.result, DescriptorRole::ImageTable) == nullptr && compiled.result->imageTableFaults == 0u && TableCovered(compiled.regions, &heap[2], sizeof(heap[2])), (what + " through a T# whose record the CPU evaluates was not bound from its record").c_str()); + const auto compiled = word0 == 0xf0440308u ? TableCompiled{} : CompileTable(request); + require(word0 == 0xf0440308u || (TableBinding(*compiled.result, DescriptorRole::ImageTable) == nullptr && compiled.result->imageTableFaults == 0u && TableCovered(compiled.regions, &heap[2], sizeof(heap[2]))), (what + " through a T# whose record the CPU evaluates was not bound from its record").c_str()); const auto wholeCode = WithImageOperation(TableWholeCode, word0, false); const auto whole = GetResourcePlan(TableRequest(wholeCode, userData)); - require(whole->info.images.size() == 1u && whole->info.images[0].table == 0u && whole->info.images[0].tableOperation == (word0 == 0xf0200f08u ? TableOperation::Supported : TableOperation::Unsupported), (what + " through a T# keyed per lane left the image table or has the wrong table operation").c_str()); + require(whole->info.images.size() == 1u && whole->info.images[0].table == 0u && whole->info.images[0].tableOperation == (word0 == 0xf0200f08u || word0 == 0xf0440108u || word0 == 0xf0440308u ? TableOperation::Supported : TableOperation::Unsupported), (what + " through a T# keyed per lane left the image table or has the wrong table operation").c_str()); } const auto mixedCode = WithImageOperation(TableSrtKeyCode, 0xf0200f08u, true); const auto mixed = GetResourcePlan(TableRequest(mixedCode, userData)); @@ -3465,6 +4014,45 @@ void verifyPointerChainImageTable() { static_cast(ValidateAndOptimizeSpirv(compiled.result->spirv, request.target.vulkanVersion, request.target.spirvVersion)); #endif store(links[1].data() + 4u, address(descriptors[1].data())); + + auto phiCode = code; + phiCode.erase(phiCode.begin() + 15, phiCode.begin() + 17); + phiCode.insert(phiCode.begin() + 15, { + 0xbf068010u, 0xbf850003u, 0xf4240b02u, 0x24000008u, 0xbf820002u, 0xf4240b02u, 0x24000008u + }); + auto phiRequest = TableRequest(phiCode, userData); + phiRequest.shader.codeAddress = 0x38000u; + const auto phiPlan = GetResourcePlan(phiRequest); + require(phiPlan->info.images.size() == 1u && phiPlan->info.images[0].table == 0u, "pointer chain image table: a pointer read through a phi was not planned as a table"); + const auto phiTable = CompileTable(phiRequest); + require(phiTable.capture->snapshot.tables.tables.at(0).codes == snapshot.codes, "pointer chain image table: a pointer read through a phi captured different records"); + + auto divergentCode = code; + divergentCode.erase(divergentCode.begin() + 15, divergentCode.begin() + 17); + divergentCode.insert(divergentCode.begin() + 15, { + 0xbf068010u, 0xbf850003u, 0xf4240b02u, 0x24000008u, 0xbf820002u, 0xf4240b02u, 0x24000018u + }); + auto divergentRequest = TableRequest(divergentCode, userData); + divergentRequest.shader.codeAddress = 0x39000u; + expectFailure([&] { static_cast(GetResourcePlan(divergentRequest)); }, "GetImageResource dword 0 is not a valid runtime value", "pointer chain image table: a phi reading different table offsets was planned as a table"); + + auto nonTableCode = code; + nonTableCode.erase(nonTableCode.begin() + 15, nonTableCode.begin() + 17); + nonTableCode.insert(nonTableCode.begin() + 15, { + 0xbf068010u, 0xbf850003u, 0xf4240b02u, 0x24000008u, 0xbf820002u, 0xbe960380u, 0xbe970380u + }); + auto nonTableRequest = TableRequest(nonTableCode, userData); + nonTableRequest.shader.codeAddress = 0x3a000u; + expectFailure([&] { static_cast(GetResourcePlan(nonTableRequest)); }, "GetImageResource dword 0 is not a valid runtime value", "pointer chain image table: a phi with a non-table branch was planned as a table"); + + auto armOffsetCode = code; + armOffsetCode.erase(armOffsetCode.begin() + 14, armOffsetCode.begin() + 17); + armOffsetCode.insert(armOffsetCode.begin() + 14, { + 0xbf068010u, 0xbf850004u, 0x8f128411u, 0xf4240b02u, 0x24000008u, 0xbf820003u, 0x8f128411u, 0xf4240b02u, 0x24000008u + }); + auto armOffsetRequest = TableRequest(armOffsetCode, userData); + armOffsetRequest.shader.codeAddress = 0x3b000u; + expectFailure([&] { static_cast(GetResourcePlan(armOffsetRequest)); }, "GetImageResource dword 0 is not a valid runtime value", "pointer chain image table: a phi whose arms compute their own table offset was planned as a table"); } void verifyWaterfallImageTable() { @@ -3551,6 +4139,40 @@ void verifyWaterfallImageTable() { require(!SrtWalker().ValidateRuntimeValue(planA, &firstOther), "waterfall image table: non-implied condition was accepted by validator"); } +void verifySplitWaterfallImageTable() { + using namespace ShaderRecompiler; + namespace Abi = ImageTableAbi; + static TableTexture texture; + alignas(256) static std::array root{}; + static std::array output{}; + const auto outputV = TableV(output.data(), 0u, static_cast(sizeof(output))); + std::copy(outputV.begin(), outputV.end(), root.begin() + 4); + const auto descriptor = TableT(texture.bytes.data(), TableFormat8888UNorm, TableType2D); + std::copy(descriptor.begin(), descriptor.end(), root.begin() + 0x40u / 4u); + const auto base = static_cast(reinterpret_cast(root.data())); + const std::array userData{static_cast(base), static_cast(base >> 32u)}; + const auto code = [](std::uint32_t addend) { + return std::vector{0xf4080700u, 0xfa000010u, 0xf4080800u, 0xfa000020u, 0x7da80086u, 0x7e220500u, 0xd7460001u, 0x00010b00u | (addend << 18u), 0x7e260501u, 0x8f128511u, + 0xf4080900u, 0x24000040u, 0xf4080a00u, 0x26000040u, 0xf09c0f08u, 0x01090000u, 0xe0700000u, 0x80070000u, 0xbf810000u}; + }; + const auto split = code(0x90u); + auto request = TableRequest(split, userData); + request.shader.codeAddress = 0x3a000u; + const auto plan = GetResourcePlan(request); + require(plan->info.images.size() == 1u && plan->info.images[0].table == 0u, "split waterfall image table: the T# was not planned as a table"); + const auto& column = *plan->descriptorSources.at(plan->info.images[0].source).tableColumn; + require(column.address && column.stride == 32u && column.offset == 0x40u && column.dwordCount == 8u && column.foldedCarry == 16u, "split waterfall image table: the column is wrong"); + const auto compiled = CompileTable(request); + const auto& snapshot = compiled.capture->snapshot.tables.tables.at(0); + require((snapshot.codes[0] & Abi::PoisonFlag) == 0u && snapshot.words.at(snapshot.codes[0]).dwords == descriptor, "split waterfall image table: record 0 was not read whole"); + for (const std::uint32_t addend : {0xa0u, 0x88u}) { + const auto otherCode = code(addend); + auto other = TableRequest(otherCode, userData); + other.shader.codeAddress = 0x3b000u + addend * 0x100u; + expectFailure([&] { static_cast(GetResourcePlan(other)); }, "GetImageResource dword 0 is not a valid runtime value", "split waterfall image table: a second half outside the record was accepted"); + } +} + void verifyDescriptorPhis() { using namespace ShaderRecompiler; constexpr std::uint32_t Format8888UNorm = 56; @@ -3968,6 +4590,51 @@ void verifyLanesOutsideHostSubgroup() { require(pixel(permlanex16, 32u) && pixel(permlane16, 8u), "host lanes: a v_permlane16_b32 row that the host subgroup may hold did not build"); } +void verifyFusedExactArithmetic() { + using namespace ShaderRecompiler; + const auto pixel = [](bool fused) { + static const std::vector code{0x7e000200u, 0x10020100u, 0x06040101u, 0x08060302u, 0x7e000303u, 0xf800180fu, 0u, 0xbf810000u}; + ShaderPixelStageInfo info{}; + info.inputAddr = PixelInputBit(PixelInput::PositionX); + info.posX = true; + info.targetOutputMode[0] = 9u; + info.targetExportMapping.fill(0xe4u); + RecompileRequest request{}; + request.shader = {ShaderStage::Fragment, 0x30000u, code, 0, {}}; + request.context.waveSize = 64; + request.context.pixel = info; + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 64; + request.target.fusedExactArithmetic = fused; + request.layout.pushConstantSizeBytes = 128; + request.useCache = false; + return Recompile(request).spirv; + }; + struct Counts { + std::uint32_t exactArithmetic = 0; + std::uint32_t fmas = 0; + }; + const auto count = [](std::span spirv) { + std::set exact; + std::vector arithmetic; + Counts result; + for (std::size_t cursor = 5; cursor < spirv.size() && (spirv[cursor] >> 16u) != 0u; cursor += spirv[cursor] >> 16u) { + const auto op = spirv[cursor] & 0xffffu; + const auto words = spirv[cursor] >> 16u; + if (op == spv::OpDecorate && words == 3u && spirv[cursor + 2] == spv::DecorationNoContraction) exact.insert(spirv[cursor + 1]); + if (op == spv::OpFAdd || op == spv::OpFSub || op == spv::OpFMul) arithmetic.push_back(spirv[cursor + 2]); + if (op == spv::OpExtInst && words >= 5u && spirv[cursor + 4] == 50u) ++result.fmas; + } + result.exactArithmetic = static_cast(std::count_if(arithmetic.begin(), arithmetic.end(), [&](std::uint32_t id) { return exact.contains(id); })); + return result; + }; + const auto plain = count(pixel(false)); + const auto fused = count(pixel(true)); + require(plain.exactArithmetic >= 3u, "fused exact arithmetic: the f32 multiply, add and subtract were not exact operations without the flag"); + require(fused.exactArithmetic == 0u && fused.fmas >= plain.fmas + 3u, "fused exact arithmetic: the f32 multiply, add and subtract did not become fused multiply-adds with the flag"); +} + void verifyHalfWaveReduction() { using namespace ShaderRecompiler; static constexpr std::array capabilities{spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityGroupNonUniformArithmetic, spv::CapabilityClipDistance}; @@ -4054,6 +4721,18 @@ void verifyHalfWaveReduction() { {0u, 0x38000000u, 0x88000000u, spv::OpGroupNonUniformBitwiseOr, "half-wave reduction: the wave64 OR scan did not read lane 31 as a subgroup OR on 32 lanes"}, }}; for (const auto& reduction : scans) require(reductions(pixel(scan(reduction.identity, reduction.vector, reduction.scalar), 32u, true), reduction.reduce) == 1u, reduction.message); + auto loopScan = scan(0xffffffffu, 0x26000000u, 0x83800000u); + const auto savedMaskLoop = AgcTest::SavedExecMaskLoop(8u); + loopScan.insert(loopScan.begin(), savedMaskLoop.begin(), savedMaskLoop.end()); + auto helperLoop = loopScan; + helperLoop[0] = 0xbea204c1u; + expectFailure([&] { static_cast(pixel(helperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: a loop initialized with helper lanes was proven to exclude them"); + auto upperHelperLoop = loopScan; + upperHelperLoop[0] = 0xbea2037eu; + upperHelperLoop.insert(upperHelperLoop.begin() + 1, 0xbea303c1u); + expectFailure([&] { static_cast(pixel(upperHelperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: the low-word loop proof was reused for helper-inclusive upper lanes"); + require(reductions(pixel(loopScan, 32u, true), spv::OpGroupNonUniformUMin) == 1u, "half-wave reduction: a loop-carried saved EXEC mask exhausted the entry-lane proof on 32 lanes"); + require(halves(pixel(loopScan, 64u, true), spv::OpGroupNonUniformUMin, 0xffffffffu), "half-wave reduction: a loop-carried saved EXEC mask did not preserve the two half-wave UMin reductions"); auto comparedScan = scan(0u, 0x38000000u, 0x88000000u); *std::find(comparedScan.begin(), comparedScan.end(), 0xbe98047eu) = 0x8a986a7eu; comparedScan.insert(comparedScan.begin(), 0x7c080080u); @@ -4466,13 +5145,15 @@ void verifyMeshConfiguration() { RecompileCacheKey::Build(other, key); require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh configuration"); require(mesh.outputPrimitiveSize == 3u && replay.request.graphics->mesh->outputPrimitiveSize == 3u, "a mesh configuration does not output triangles by default"); - otherMesh = mesh; - otherMesh.outputPrimitiveSize = 2u; - other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}}; - const auto lineReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(other)); - require(lineReplay.request.graphics.has_value() && lineReplay.request.graphics->mesh.has_value() && lineReplay.request.graphics->mesh->outputPrimitiveSize == 2u, "the mesh output primitive size was lost in serialization"); - RecompileCacheKey::Build(other, key); - require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh output primitive size"); + for (const auto primitiveSize : {1u, 2u}) { + otherMesh = mesh; + otherMesh.outputPrimitiveSize = primitiveSize; + other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}}; + const auto primitiveReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(other)); + require(primitiveReplay.request.graphics.has_value() && primitiveReplay.request.graphics->mesh.has_value() && primitiveReplay.request.graphics->mesh->outputPrimitiveSize == primitiveSize, "the mesh output primitive size was lost in serialization"); + RecompileCacheKey::Build(other, key); + require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh output primitive size"); + } RecompileRequest hull{}; hull.shader = {ShaderStage::TessellationControl, 0x10000u, code, 0, {}}; hull.context.waveSize = 64; @@ -4747,7 +5428,7 @@ void verifyPixelRequestSerialization() { value.posZ, value.posW, value.frontFace, value.frontFaceAllBits, value.ancillary, value.sampleShading, value.noPerspective, value.linearCentroid, value.pixelKillEnable, value.depthExportEnable, value.sampleMaskExportEnable, value.earlyZ, value.executeOnNoop, value.conservativeZExport, value.orderedPixelShader, - value.targetOutputMode, value.targetExportMapping, value.alphaToCoverage, value.stencilRefExportEnable, value.rasterPrimitive, value.layerExported, value.ditheredAlphaToCoverage, value.reversedBlend); + value.targetOutputMode, value.targetExportMapping, value.alphaToCoverage, value.stencilRefExportEnable, value.rasterPrimitive, value.layerExported, value.ditheredAlphaToCoverage, value.reversedBlend, value.targetMerge); }; const std::array code{0xbf810000u}; const std::array userData{0x12345678u, 0xabcdef01u, 0x87654321u}; @@ -4795,6 +5476,7 @@ void verifyPixelRequestSerialization() { pixel.stencilRefExportEnable = true; pixel.ditheredAlphaToCoverage = true; pixel.reversedBlend = 0x570001u; + pixel.targetMerge = {1u, 0x0ff0u, 1u, {0x100u, 0x2u, 0x3u, 0x4u, 0x5u, 0x6u, 0x7u, 0x8u}}; const std::array, 3> mappings{{ {0x00u, 0xe4u, 0xc6u, 0x1bu, 0xffu, 0x80u, 0x55u, 0xaau}, {0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u}, @@ -4827,6 +5509,22 @@ void verifyPixelRequestSerialization() { withoutReversedBlend.context.pixel->reversedBlend = 0u; RecompileCacheKey::Build(withoutReversedBlend, replayKey); require(key == replayKey && RecompileCacheKey::ContextHash(request) == RecompileCacheKey::ContextHash(withoutReversedBlend), "the reversed-order blend code changed the static shader identity"); + auto movedMerge = request; + movedMerge.context.pixel->targetMerge.descriptor[0] = 0x200u; + RecompileCacheKey::Build(movedMerge, replayKey); + require(key == replayKey, "the merged target's descriptor changed the static shader identity"); + auto depthWritingMerge = request; + depthWritingMerge.context.pixel->targetMerge.depthStencilWrites = true; + RecompileCacheKey::Build(depthWritingMerge, replayKey); + require(key != replayKey, "the merge draw's depth or stencil writes did not change the static shader identity"); + auto depthTestingMerge = request; + depthTestingMerge.context.pixel->targetMerge.depthStencilTests = true; + RecompileCacheKey::Build(depthTestingMerge, replayKey); + require(key != replayKey, "the merge draw's depth or stencil tests did not change the static shader identity"); + auto withoutMerge = request; + withoutMerge.context.pixel->targetMerge = {}; + RecompileCacheKey::Build(withoutMerge, replayKey); + require(key != replayKey, "a color target merge did not change the static shader identity"); auto withoutCoverage = request; withoutCoverage.context.pixel->alphaToCoverage = false; RecompileCacheKey::Build(withoutCoverage, replayKey); @@ -4849,7 +5547,7 @@ void verifyPixelRequestSerialization() { minimal.context.pixel->layerExported = true; minimal.context.pixel->ditheredAlphaToCoverage = true; const auto encoded = serializer.Serialize(minimal); - require(requestPrefix(encoded, 8u) == "NVNQQSoAAAA=", "new requests did not use serialization version 42"); + require(requestPrefix(encoded, 8u) == "NVNQQS4AAAA=", "new requests did not use serialization version 46"); const std::array fallbacks{0x40u, 0x1ac4u}; auto withFallbacks = minimal; withFallbacks.context.linkedCallFallbacks = fallbacks; @@ -4967,7 +5665,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=", "NVNQQSsAAAA="}) { + for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQS8AAAA="}) { expectFailure([&] { static_cast(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted"); } static constexpr std::string_view version18Request{ @@ -5058,6 +5756,8 @@ void verifyVertexInfoVersion19() { require(offset + 4u <= plain.size() && flagged[offset] == 0xefu && flagged[offset + 1u] == 0xcdu && flagged[offset + 2u] == 0xabu && flagged[offset + 3u] == 0x89u, "PA_CL_VS_OUT_CNTL is not the four bytes the version 20 vertex info adds"); require(std::equal(plain.begin() + offset + 4u, plain.end(), flagged.begin() + offset + 4u), "PA_CL_VS_OUT_CNTL moved the fields after it"); auto version20Vertex = flagged; + version20Vertex.resize(version20Vertex.size() - sizeof(std::uint32_t)); + version20Vertex.pop_back(); version20Vertex.pop_back(); version20Vertex.pop_back(); version20Vertex.pop_back(); @@ -5069,6 +5769,8 @@ void verifyVertexInfoVersion19() { const auto layered = serializer.Deserialize(serializer.Serialize(vertex)); require(layered.request.context.vertex.has_value() && layered.request.context.vertex->paClVsOutCntl == vertex.context.vertex->paClVsOutCntl, "a layered vertex info lost its clip distances or render target index"); auto legacy = plain; + legacy.resize(legacy.size() - sizeof(std::uint32_t)); + legacy.pop_back(); legacy.pop_back(); legacy.pop_back(); legacy.pop_back(); @@ -5107,15 +5809,39 @@ void verifyVertexInfoVersion19() { while (allBitsOffset < plainPixel.size() && plainPixel[allBitsOffset] == allBitsBytes[allBitsOffset]) ++allBitsOffset; require(allBitsOffset == flagOffset + 2u, "the front-face all-bits flag does not follow the dithered coverage flag"); require(blendOffset == allBitsOffset + 18u, "the reversed-order blend code is not serialized after the front-face all-bits flag, quad pixel mask and quad sample masks"); - constexpr std::size_t sampleMaskBytes = 16u; constexpr std::size_t iterationBytes = 35u; constexpr std::size_t sampleOrderBytes = 16u; + constexpr std::size_t mergeBytes = 40u; + constexpr std::size_t sampleMaskBytes = 16u; const auto orderOffset = blendOffset + 4u + iterationBytes; + const auto mergeOffset = static_cast(orderOffset + sampleOrderBytes); + auto merging = pixel; + merging.context.pixel->targetMerge = {3u, 0xf00fu, 2u, {1u, 2u, 3u, 4u, 5u, 6u, 7u, 0x89abcdefu}, true, true}; + 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. + 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); + return bytes; + }; + auto version42 = version43Bytes(plainPixel); + version42[4] = 42u; + const auto replay42 = serializer.Deserialize(encodeBase64(version42)); + require(replay42.request.context.pixel.has_value() && replay42.request.context.pixel->targetMerge == TargetMerge{} && serializer.Serialize(replay42.request) == encodedPixel, "a version 42 request read a color target merge or misread the fields after it"); + auto version39 = version42; + version39.pop_back(); + version39.erase(version39.begin() + static_cast(orderOffset), version39.begin() + mergeOffset); + version39[4] = 39u; + const auto replay39 = serializer.Deserialize(encodeBase64(version39)); + require(replay39.request.context.pixel.has_value() && replay39.request.context.pixel->targetMerge == TargetMerge{} && replay39.request.context.pixel->sampleOrder == std::array{} && serializer.Serialize(replay39.request) == encodedPixel, "a version 39 request read a sample order or color target merge, or misread the fields after them"); auto ordered = pixel; ordered.context.pixel->sampleOrder = {0x76543210u, 0x76543210u, 0x76543201u, 0x76543201u}; const auto orderedBytes = decodeBase64(serializer.Serialize(ordered)); require(orderedBytes.size() == plainPixel.size() && std::equal(plainPixel.begin(), plainPixel.begin() + static_cast(orderOffset), orderedBytes.begin()) && orderedBytes[orderOffset] == 0x10u && orderedBytes[orderOffset + 11u] == 0x76u && orderedBytes[orderOffset + 12u] == 0x01u && std::equal(plainPixel.begin() + static_cast(orderOffset + sampleOrderBytes), plainPixel.end(), orderedBytes.begin() + static_cast(orderOffset + sampleOrderBytes)), "the sample order is not the four words the version 42 pixel info adds after the stencil operation value export flag"); - auto orderedVersion34 = orderedBytes; + auto orderedVersion34 = version43Bytes(orderedBytes); orderedVersion34.pop_back(); orderedVersion34.pop_back(); orderedVersion34.pop_back(); @@ -5123,7 +5849,7 @@ void verifyVertexInfoVersion19() { orderedVersion34[4] = 34u; const auto orderedReplay34 = serializer.Deserialize(encodeBase64(orderedVersion34)); require(orderedReplay34.request.context.pixel.has_value() && orderedReplay34.request.context.pixel->sampleOrder == std::array{} && serializer.Serialize(orderedReplay34.request) == encodedPixel, "a version 34 request read a sample order, or misread the fields after its pixel iteration fields"); - auto orderedVersion30 = orderedBytes; + auto orderedVersion30 = version43Bytes(orderedBytes); orderedVersion30.resize(orderedVersion30.size() - 4u); orderedVersion30.erase(orderedVersion30.begin() + static_cast(blendOffset + 4u), orderedVersion30.begin() + static_cast(orderOffset + sampleOrderBytes)); orderedVersion30[4] = 30u; @@ -5131,7 +5857,7 @@ void verifyVertexInfoVersion19() { require(orderedReplay30.request.context.pixel.has_value() && orderedReplay30.request.context.pixel->sampleOrder == std::array{} && serializer.Serialize(orderedReplay30.request) == encodedPixel, "a version 30 request read a sample order, or misread the fields after its reversed-order blend code"); auto orderedSpanned = ordered; orderedSpanned.target.spannedWaves = true; - auto orderedVersion32 = decodeBase64(serializer.Serialize(orderedSpanned)); + auto orderedVersion32 = version43Bytes(decodeBase64(serializer.Serialize(orderedSpanned))); orderedVersion32.pop_back(); require(orderedVersion32.size() > 2u && orderedVersion32.back() == 0u && orderedVersion32[orderedVersion32.size() - 2u] == 0u && orderedVersion32[orderedVersion32.size() - 3u] == 1u, "the spanned-waves flag is not the byte before the depth remap flag"); orderedVersion32.pop_back(); @@ -5152,7 +5878,7 @@ void verifyVertexInfoVersion19() { sampledPixel.context.pixel->quadSampleMask = {0x1u, 0x2u, 0x4u, 0x8u}; const auto sampledBytes = decodeBase64(serializer.Serialize(sampledPixel)); require(std::equal(maskedBytes.begin(), maskedBytes.begin() + static_cast(maskOffset + 1u), sampledBytes.begin()) && sampledBytes[maskOffset + 1u] == 0x1u && sampledBytes[maskOffset + 5u] == 0x2u && sampledBytes[maskOffset + 9u] == 0x4u && sampledBytes[maskOffset + 13u] == 0x8u, "the quad sample masks are not the four words after the quad pixel mask"); - auto version30 = sampledBytes; + auto version30 = version43Bytes(sampledBytes); version30.pop_back(); version30.pop_back(); version30.pop_back(); @@ -5163,7 +5889,7 @@ void verifyVertexInfoVersion19() { require(replay30.request.context.pixel.has_value() && !replay30.request.context.pixel->iteratesSamples && replay30.request.context.pixel->positionLocation == PixelPositionLocation::Center && replay30.request.context.pixel->sampleLocations == std::array{} && decodeBase64(serializer.Serialize(replay30.request)) == sampledBytes, "a version 30 request read pixel iteration, a position location or sample locations, or misread the fields after its reversed-order blend code"); auto spanned = sampledPixel; spanned.target.spannedWaves = true; - auto version32 = decodeBase64(serializer.Serialize(spanned)); + auto version32 = version43Bytes(decodeBase64(serializer.Serialize(spanned))); version32.pop_back(); require(version32.size() > 2u && version32.back() == 0u && version32[version32.size() - 2u] == 0u && version32[version32.size() - 3u] == 1u, "the spanned-waves flag is not the byte before the depth remap flag"); version32.pop_back(); @@ -5172,13 +5898,13 @@ void verifyVertexInfoVersion19() { version32[4] = 32u; const auto replay32 = serializer.Deserialize(encodeBase64(version32)); require(replay32.request.context.pixel.has_value() && !replay32.request.context.pixel->iteratesSamples && replay32.request.context.pixel->sampleLocations == std::array{} && replay32.request.target.spannedWaves && serializer.Serialize(replay32.request) == serializer.Serialize(spanned), "a version 32 request read pixel iteration or sample locations, or lost its spanned-waves flag"); - auto version29 = sampledBytes; + auto version29 = version43Bytes(sampledBytes); version29.pop_back(); version29.pop_back(); version29.pop_back(); version29.pop_back(); version29.erase(version29.begin() + static_cast(maskOffset + 1u + sampleMaskBytes), version29.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes + sampleOrderBytes)); - auto version36 = sampledBytes; + auto version36 = version43Bytes(sampledBytes); version36.pop_back(); version36.erase(version36.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes - 1u), version36.begin() + static_cast(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes + sampleOrderBytes)); version36[4] = 36u; @@ -5187,7 +5913,7 @@ void verifyVertexInfoVersion19() { version29[4] = 29u; const auto replay29 = serializer.Deserialize(encodeBase64(version29)); require(replay29.request.context.pixel.has_value() && replay29.request.context.pixel->quadSampleMask == std::array{0x1u, 0x2u, 0x4u, 0x8u} && replay29.request.context.pixel->reversedBlend == 0u && decodeBase64(serializer.Serialize(replay29.request)) == sampledBytes, "a version 29 request read a reversed-order blend code or misread the fields after the quad sample masks"); - auto version28 = sampledBytes; + auto version28 = version43Bytes(sampledBytes); version28.pop_back(); version28.pop_back(); version28.pop_back(); @@ -5196,7 +5922,7 @@ void verifyVertexInfoVersion19() { version28[4] = 28u; const auto replay28 = serializer.Deserialize(encodeBase64(version28)); require(replay28.request.context.pixel.has_value() && replay28.request.context.pixel->quadPixelMask == 0x2u && replay28.request.context.pixel->quadSampleMask == std::array{0xffffu, 0xffffu, 0xffffu, 0xffffu} && replay28.request.context.pixel->reversedBlend == 0u && decodeBase64(serializer.Serialize(replay28.request)) == maskedBytes, "a version 28 request read quad sample masks or a reversed-order blend code, or misread the fields after them"); - auto version27 = maskedBytes; + auto version27 = version43Bytes(maskedBytes); version27.pop_back(); version27.pop_back(); version27.pop_back(); @@ -5205,7 +5931,7 @@ void verifyVertexInfoVersion19() { version27[4] = 27u; const auto replay27 = serializer.Deserialize(encodeBase64(version27)); require(replay27.request.context.pixel.has_value() && replay27.request.context.pixel->quadPixelMask == 0xfu && replay27.request.context.pixel->reversedBlend == 0u && serializer.Serialize(replay27.request) == encodedPixel, "a version 27 request read a quad pixel mask or reversed-order blend code, or misread the fields after it"); - auto legacyPixel = plainPixel; + auto legacyPixel = version43Bytes(plainPixel); legacyPixel.pop_back(); legacyPixel.pop_back(); legacyPixel.pop_back(); @@ -5256,26 +5982,33 @@ void verifyDepthRemapRequests() { const auto replay = serializer.Deserialize(remapped); require(replay.request.target.depthRangeRemap && replay.request.context.vertex.has_value() && replay.request.context.vertex->paClVsOutCntl == 0x89abcdefu, "the depth remap target flag or the vertex info did not round-trip"); require(replay.request.context.vertex->depthRemapMode == 2u, "the depth remap mode did not round-trip"); - require(serializer.Serialize(replay.request) == remapped, "a version 35 depth remap request changed on re-serialization"); + require(serializer.Serialize(replay.request) == remapped, "the depth remap request changed on re-serialization"); const auto plainBytes = decodeBase64(plain); const auto remappedBytes = decodeBase64(remapped); - require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 5u && std::equal(plainBytes.begin(), plainBytes.end() - 7, remappedBytes.begin()), "the depth remap flag and mode are not the 6 bytes before the f32 flush and denormal preserve flags of a vertex request"); + 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"); 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[4] = legacyVersion; - legacyBytes.resize(legacyBytes.size() - 6u); + legacyBytes.resize(legacyBytes.size() - 6u - pointTailBytes); const auto legacy = serializer.Deserialize(encodeBase64(legacyBytes)); require(!legacy.request.target.depthRangeRemap && legacy.request.context.vertex.has_value() && legacy.request.context.vertex->depthRemapMode == 0u, ("a version " + std::to_string(legacyVersion) + " vertex request read depth remap fields").c_str()); require(serializer.Serialize(legacy.request) == plain, ("a version " + std::to_string(legacyVersion) + " vertex request misread the fields before the depth remap").c_str()); } auto version35 = remappedBytes; + version35.resize(version35.size() - sizeof(std::uint32_t)); + version35.pop_back(); + version35.pop_back(); version35.pop_back(); version35[4] = 35u; - version35.pop_back(); + version35.resize(version35.size() - 1u - pointTailBytes); const auto replay35 = serializer.Deserialize(encodeBase64(version35)); require(replay35.request.target.depthRangeRemap && replay35.request.context.vertex->depthRemapMode == 2u && !replay35.request.target.float32FlushToZero && serializer.Serialize(replay35.request) == remapped, "a version 35 request misread the depth remap fields or read an f32 flush flag"); - auto truncated = remappedBytes; + auto truncated = version35; truncated.resize(truncated.size() - 2u); expectFailure([&] { static_cast(serializer.Deserialize(encodeBase64(truncated))); }, "truncated data", "a truncated depth remap mode was accepted"); auto trailing = remappedBytes; @@ -5316,9 +6049,12 @@ void verifyMergedHullConfiguration() { const auto replay = serializer.Deserialize(merged); require(replay.request.graphics.has_value() && replay.request.graphics->tessellation.has_value() && replay.request.graphics->tessellation->mergedHull, "a merged hull configuration was lost in serialization"); 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] == 46u, "a merged hull request did not use serialization version 46"); + legacy.resize(legacy.size() - sizeof(std::uint32_t) - 2u - pointTailBytes); legacy.pop_back(); - require(legacy.size() > 52u && legacy[legacy.size() - 52u] == 1u, "the merged hull flag is not the last byte of the tessellation configuration"); + require(legacy[legacy.size() - 52u] == 1u, "the merged hull flag is not the last byte of the tessellation configuration"); legacy.erase(legacy.end() - 52); for (const auto version : {30u, 31u, 32u, 34u, 35u, 36u, 37u, 38u}) { auto older = legacy; @@ -5786,10 +6522,10 @@ void verifyInt64AtomicCapabilities() { require(declares(compile(imageAtomic, spv::CapabilityInt64ImageEXT), spv::CapabilityInt64ImageEXT), "64-bit atomics: an image_atomic_swap on a 32_32 image does not declare Int64ImageEXT"); } -void verifySpecializedBufferAtomicBindings() { +void verifySpecializedBufferAtomicBindings(bool finalAbiOnly = false) { using namespace ShaderRecompiler; const std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; - const std::array capabilities{spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess}; + const std::array capabilities{spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityInt64Atomics}; alignas(256) static std::array buffer{}; const auto bufferAddress = static_cast(reinterpret_cast(buffer.data())); const std::array userData{ @@ -5797,8 +6533,8 @@ void verifySpecializedBufferAtomicBindings() { static_cast(bufferAddress), static_cast((bufferAddress >> 32u) & 0xffffu), 256u, 0x31016facu, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u}; - const std::vector code{0xe0f05000u, 0x80012803u, 0xbf810000u}; - const auto compile = [&](std::span supported) { + const auto compile = [&](std::span supported, std::uint32_t opcode = 0xe0f05000u) { + const std::array code{opcode, 0x80012803u, 0xbf810000u}; RecompileRequest request{}; request.shader = {ShaderStage::Compute, 0x58800u, code, 0, {}}; request.context.waveSize = 32; @@ -5816,6 +6552,16 @@ void verifySpecializedBufferAtomicBindings() { request.useCache = false; return Recompile(request); }; + if (finalAbiOnly) { + for (const auto opcode : {0xe0f05000u, 0xe1705000u}) { + const auto result = compile(capabilities, opcode); + const auto faults = std::ranges::count_if(result.bindings, [](const DescriptorBinding& binding) { return binding.role == DescriptorRole::FaultBuffer; }); + require(faults <= 1, "specialized buffer atomic: duplicate fault buffers"); + require(result.bdaAbiVersion == (faults == 0 ? 0u : BdaAbi::Version), "specialized buffer atomic: final fault binding and BDA ABI disagree"); + if (opcode == 0xe1705000u) require(faults == 1, "specialized buffer atomic: a 64-bit atomic lost its live fault buffer"); + } + return; + } const auto result = compile(capabilities); std::size_t tables = 0; std::size_t faults = 0; @@ -6087,6 +6833,12 @@ void verifyUnnormalizedSamplers() { const auto adjusted = recompile(reservedDword3(0xf0800f09u), userData(normalized)); require(proven(adjusted, false), "sampler dword 3: an image_sample_a whose S# dword 3 holds only a ballot in its reserved bits was not compiled"); expectFailure([&] { static_cast(recompile(reservedDword3(0xf0800f08u), userData(normalized))); }, "GetSamplerResource dword 3 is not a valid runtime value", "sampler dword 3: an image_sample whose S# dword 3 holds a ballot was accepted"); + for (const auto& [adjust, plain, compiled, accepted] : { + std::tuple{0xf1200109u, 0xf1200108u, "sampler dword 3: an image_gather4_c_a whose S# dword 3 holds only a ballot in its reserved bits was not compiled", "sampler dword 3: an image_gather4_c with a computed S# dword 3 was accepted"}, + std::tuple{0xf1600109u, 0xf1600108u, "sampler dword 3: an image_gather4_c_o_a whose S# dword 3 holds only a ballot in its reserved bits was not compiled", "sampler dword 3: an image_gather4_c_o with a computed S# dword 3 was accepted"}}) { + require(proven(recompile(reservedDword3(adjust), imageData(normalized, Type2D, Format32Float, 0u)), false), compiled); + expectFailure([&] { static_cast(recompile(reservedDword3(plain), imageData(normalized, Type2D, Format32Float, 0u))); }, "GetSamplerResource dword 3 is not a valid runtime value", accepted); + } reject(0xf09c0f18u, imageData(unnormalized, TypeCube, Format8888UNorm, 5u), "unnormalized guest sampler samples a 1D-array, 2D-array, 3D, cube or multisampled image", "unnormalized samplers: a cube T# was accepted"); reject(0xf09c0f10u, imageData(unnormalized, Type3D, Format8888UNorm, 3u), "unnormalized guest sampler samples a 1D-array, 2D-array, 3D, cube or multisampled image", "unnormalized samplers: a 3D T# was accepted"); reject(0xf09c0f28u, imageData(unnormalized, Type2DArray, Format8888UNorm, 5u), "unnormalized guest sampler samples a 1D-array, 2D-array, 3D, cube or multisampled image", "unnormalized samplers: a 2D-array sample was accepted"); @@ -7432,8 +8184,19 @@ void verifyFmaskImageMode() { const auto modes = ResourceMaterializer::RuntimeImageModes(image); const auto selected = ResourceMaterializer::RuntimeImageMode(image, descriptor, modes); require(modes[selected].numericClass == IrTextureNumericClass::Uint && modes[selected].packedFormat == IrBufferFormat::Invalid && !modes[selected].constantSwizzle, "FMASK did not select a bound integer image mode"); - for (std::uint32_t format = 156u; format <= 168u; ++format) { - if (format == 166u) continue; + for (const auto format : {156u, 157u, 158u, 159u, 160u, 161u, 163u, 165u}) { + descriptor.dwords[1] = format << 20u; + const auto expanded = ResourceMaterializer::RuntimeImageMode(image, descriptor, modes); + require(expanded != selected && modes[expanded].numericClass == IrTextureNumericClass::Uint && modes[expanded].conversionFormat == FmaskExpansion && modes[expanded].dimension == RdnaImageDimension::Dim2D, "an FMASK format of up to 8 samples did not select the FMASK expansion mode"); + } + require(std::ranges::count_if(modes, [](const ImageResource& mode) { return mode.conversionFormat == FmaskExpansion; }) == 1, "FMASK expansion needs exactly one runtime image mode"); + for (const auto format : {161u, 166u}) { + descriptor.dwords[1] = format << 20u; + descriptor.dwords[3] = 0x91800005u; + expectFailure([&] { static_cast(ResourceMaterializer::RuntimeImageMode(image, descriptor, modes)); }, "FMASK destination channel selector is not implemented", "an FMASK load selecting Y was accepted"); + } + descriptor.dwords[3] = 0x91800004u; + for (const auto format : {162u, 164u, 167u, 168u}) { descriptor.dwords[1] = format << 20u; expectFailure([&] { static_cast(ResourceMaterializer::RuntimeImageMode(image, descriptor, modes)); }, "FMASK format is not implemented", "an unsupported FMASK format was accepted"); } @@ -7491,6 +8254,37 @@ void verifyVolumeArrayLoadMode() { expectFailure([&] { static_cast(ResourceMaterializer::RuntimeImageMode(store, descriptor, ResourceMaterializer::RuntimeImageModes(store))); }, "incompatible with the static runtime image interface", "2D-array image atomic on a 3D T# was accepted"); } +void verifyMultisamplePlaneReadMode() { + using namespace ShaderRecompiler; + const DescriptorValue descriptor{{0x02754d00u, 0xc3800000u, 0x00cbc167u, 0xe1b20facu, 0u, 0x00700020u, 0u, 0u}, 8u}; + ImageResource image{}; + image.resourceClass = ImageResourceClass::Sampled; + image.dimension = RdnaImageDimension::Dim2D; + const auto modes = ResourceMaterializer::RuntimeImageModes(image); + const auto selected = ResourceMaterializer::RuntimeImageMode(image, descriptor, modes); + require(modes[selected].dimension == RdnaImageDimension::Dim2D && !modes[selected].cube && modes[selected].numericClass == IrTextureNumericClass::Float, "a 2D read of a 2D MSAA T# (PPSA18216's) did not select a 2D runtime mode"); + for (const auto& [word1, word3, word5, numericClass] : std::array, 4>{{{0xc0100000u, 0xe1b20facu, 0x00700020u, IrTextureNumericClass::Float}, {0xc0500000u, 0xe1810facu, 0x00700010u, IrTextureNumericClass::Uint}, {0xc0c00000u, 0xe1b20facu, 0x00700020u, IrTextureNumericClass::Sint}, {0xc0d00000u, 0xe1830facu, 0x00700030u, IrTextureNumericClass::Float}}}) { + const DescriptorValue narrow{{0x02754d00u, word1, 0x00cbc167u, word3, 0u, word5, 0u, 0u}, 8u}; + const auto mode = ResourceMaterializer::RuntimeImageMode(image, narrow, modes); + require(modes[mode].dimension == RdnaImageDimension::Dim2D && !modes[mode].cube && modes[mode].numericClass == numericClass, "a 2D read of a 2D MSAA T# with 8-bit or 16-bit texels did not select a 2D runtime mode"); + } + const DescriptorValue depthBits{{0x02754d00u, 0xc0b00000u, 0x00cbc167u, 0xe1820facu, 0u, 0x00700020u, 0u, 0u}, 8u}; + expectFailure([&] { static_cast(ResourceMaterializer::RuntimeImageMode(image, depthBits, modes)); }, "incompatible with the static runtime image interface", "a 2D read of a 16_UINT SW_64KB_Z_X 2D MSAA T#, read as depth bits, was accepted"); + const auto refused = [&](const ImageResource& access, const char* what) { + expectFailure([&] { static_cast(ResourceMaterializer::RuntimeImageMode(access, descriptor, ResourceMaterializer::RuntimeImageModes(access))); }, "incompatible with the static runtime image interface", what); + }; + auto other = image; + other.cubeFaceCompatible = false; + refused(other, "a 2D read of a 2D MSAA T# through a mip, gather, bias, gradient or offset access was accepted"); + other = image; + other.depthCompare = true; + refused(other, "a 2D depth comparison of a 2D MSAA T# was accepted"); + other = image; + other.resourceClass = ImageResourceClass::Storage; + other.written = true; + refused(other, "a 2D storage access to a 2D MSAA T# was accepted"); +} + void verifyVolumeBiasTableRejection() { using namespace ShaderRecompiler; namespace Abi = ImageTableAbi; @@ -7534,6 +8328,85 @@ void verifyVolumeBiasTableRejection() { } } +void verifySamplerTableDeduplication() { + using namespace ShaderRecompiler; + namespace Abi = ImageTableAbi; + SamplerResource sampler0{}; + sampler0.table = 0u; + SamplerResource sampler1{}; + sampler1.table = 1u; + ShaderInfo info{}; + info.samplers.push_back(sampler0); + info.samplers.push_back(sampler1); + ResourceSnapshot snapshot{}; + snapshot.tables.tables.resize(2u); + const DescriptorValue s0{{0x00000600u, 0x00fff000u, 0x06f03fa0u, 0u}, 4u}; + const DescriptorValue s1{{0x00000700u, 0x00fff000u, 0x06f03fa0u, 0u}, 4u}; + const DescriptorValue s2{{0x00000800u, 0x00fff000u, 0x06f03fa0u, 0u}, 4u}; + std::vector codes(300u); + for (std::size_t i = 0; i < 300u; ++i) codes[i] = static_cast(i % 3u); + for (std::uint32_t table = 0u; table < 2u; ++table) { + auto& column = snapshot.tables.tables[table]; + column.records = 300u; + column.keys = 300u; + column.codes = SharedValues(codes); + column.words = SharedValues(std::vector{s0, s1, s2}); + } + const auto resolved = ResourceMaterializer::ResolveImageTables(info, snapshot); + require(resolved.samplers.size() == (3u + 1u) * Abi::SamplerEntryElements, "sampler table deduplication: duplicate sampler entries across tables were not deduplicated"); + for (std::uint32_t table = 0u; table < 2u; ++table) { + const auto mapStart = resolved.map.at(Abi::TableHeader(table) + Abi::TableMapStart); + for (std::uint32_t key = 0; key < 300u; ++key) { + const auto record = resolved.map.at(mapStart + key); + const auto element = resolved.map.at(record + Abi::SamplerRecordElement); + require(element == (key % 3u) * Abi::SamplerEntryElements, "sampler table deduplication: record points to the wrong deduplicated sampler element"); + require((resolved.map.at(record + Abi::SamplerRecordFlags) & Abi::PoisonFlag) == 0u, "sampler table deduplication: deduplicated sampler was poisoned"); + } + } + + constexpr std::uint32_t MaxUnique = Abi::SamplerElementBudget / Abi::SamplerEntryElements; + std::vector budgetWords; + budgetWords.reserve(MaxUnique + 1u); + for (std::uint32_t i = 0; i < MaxUnique + 1u; ++i) { + budgetWords.push_back(DescriptorValue{{0x00000600u, 0x00fff000u, 0x06f03fa0u, i + 1u}, 4u}); + } + std::vector budgetCodes(MaxUnique + 2u); + for (std::uint32_t i = 0; i < MaxUnique; ++i) budgetCodes[i] = i; + budgetCodes[MaxUnique] = 0u; + budgetCodes[MaxUnique + 1u] = MaxUnique; + + ShaderInfo budgetInfo{}; + SamplerResource budgetSampler{}; + budgetSampler.table = 0u; + budgetInfo.samplers.push_back(budgetSampler); + ResourceSnapshot budgetSnapshot{}; + budgetSnapshot.tables.tables.resize(1u); + auto& budgetColumn = budgetSnapshot.tables.tables[0]; + budgetColumn.records = static_cast(budgetCodes.size()); + budgetColumn.keys = static_cast(budgetCodes.size()); + budgetColumn.codes = SharedValues(budgetCodes); + budgetColumn.words = SharedValues(budgetWords); + + const auto budgetResolved = ResourceMaterializer::ResolveImageTables(budgetInfo, budgetSnapshot); + require(budgetResolved.samplers.size() == Abi::SamplerElementBudget, "sampler table budget: valid unique samplers did not fill budget"); + const auto budgetMapStart = budgetResolved.map.at(Abi::TableHeader(0) + Abi::TableMapStart); + + const auto dupRecord = budgetResolved.map.at(budgetMapStart + MaxUnique); + const auto dupFlags = budgetResolved.map.at(dupRecord + Abi::SamplerRecordFlags); + require((dupFlags & Abi::PoisonFlag) == 0u, "sampler table budget: duplicate of existing sampler was poisoned when budget is full"); + require(budgetResolved.map.at(dupRecord + Abi::SamplerRecordElement) == 0u, "sampler table budget: duplicate sampler did not reuse existing element"); + + const auto overflowRecord = budgetResolved.map.at(budgetMapStart + MaxUnique + 1u); + const auto overflowFlags = budgetResolved.map.at(overflowRecord + Abi::SamplerRecordFlags); + require((overflowFlags & Abi::PoisonFlag) != 0u, "sampler table budget: exceeding sampler budget did not poison record"); + require(budgetResolved.poison.at(overflowFlags & ~Abi::PoisonFlag).reason == Abi::PoisonReason::Budget, "sampler table budget: poisoned reason is not Budget"); + + const auto outsideRecord = budgetResolved.map.at(Abi::TableHeader(0) + Abi::TableOutsideCode); + const auto outsideFlags = budgetResolved.map.at(outsideRecord + Abi::SamplerRecordFlags); + require((outsideFlags & Abi::PoisonFlag) != 0u, "sampler table budget: TableOutsideCode did not poison when budget is full"); + require(budgetResolved.poison.at(outsideFlags & ~Abi::PoisonFlag).reason == Abi::PoisonReason::Budget, "sampler table budget: TableOutsideCode poisoned reason is not Budget"); +} + void verifyDynamicStorageMipChain() { using namespace ShaderRecompiler; ImageResource image{}; @@ -7553,6 +8426,24 @@ void verifyDynamicStorageMipChain() { require(ResourceMaterializer::TryRuntimeImageMode(image, allLevels, modes).mode.has_value(), "a dynamic-mip storage image with sixteen levels was rejected"); } +void verifyArrayVolumeStoreMode() { + using namespace ShaderRecompiler; + const DescriptorValue descriptor{{0x11800100u, 0xc3800000u, 0x0003c003u, 0xd0900facu, 0u, 0x00700000u, 0u, 0u}, 8u}; + ImageResource store{}; + store.resourceClass = ImageResourceClass::Storage; + store.dimension = RdnaImageDimension::Dim3D; + store.written = true; + store.fmaskCompatible = false; + const auto modes = ResourceMaterializer::RuntimeImageModes(store); + require(modes[ResourceMaterializer::RuntimeImageMode(store, descriptor, modes)].dimension == RdnaImageDimension::Dim2DArray, "3D image store on a 2D-array T# did not select an array runtime mode"); + store.read = true; + expectFailure([&] { static_cast(ResourceMaterializer::RuntimeImageMode(store, descriptor, ResourceMaterializer::RuntimeImageModes(store))); }, "incompatible with the static runtime image interface", "3D image load and store on a 2D-array T# was accepted"); + store.read = false; + store.atomic = true; + const DescriptorValue uintDescriptor{{0x11800100u, 0xc1400000u, 0x0003c003u, 0xd0900facu, 0u, 0x00700000u, 0u, 0u}, 8u}; + expectFailure([&] { static_cast(ResourceMaterializer::RuntimeImageMode(store, uintDescriptor, ResourceMaterializer::RuntimeImageModes(store))); }, "incompatible with the static runtime image interface", "3D image atomic on a 2D-array T# was accepted"); +} + void verifySrgbDepthCompareModes() { using namespace ShaderRecompiler; for (const auto decodeFormats : {0u, 1u, 2u, 3u}) { @@ -7579,6 +8470,11 @@ int main(int argc, char** argv) { setenv("ANYPS5_NO_SHADER_CACHE", "1", 1); #endif using namespace ShaderRecompiler; + if (argc == 2 && std::string_view(argv[1]) == "--specialized-bda-abi") { + verifySpecializedBufferAtomicBindings(true); + std::cout << "Specialized programs retain the BDA ABI only while a fault buffer remains\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--quad-mask-no-demote") { verifyQuadMaskNeighbourReads(); std::cout << "Quad masks preserve neighbour-lane helpers or refuse without demote\n"; @@ -7622,6 +8518,7 @@ int main(int argc, char** argv) { verifyChainedTableOffset(); verifyPointerChainImageTable(); verifyWaterfallImageTable(); + verifySplitWaterfallImageTable(); std::cout << "Image table keys, maps, records and registration passed\n"; return 0; } @@ -7674,10 +8571,18 @@ int main(int argc, char** argv) { verifySignedMinimum(); verifyMayEvaluate(); verifyPureFlatSlots(); + verifyPlanConditionPruning(); + verifyPlanRetention(); + verifyPlanRetentionChains(); + verifyPlanCopyAnswers(); verifyGuardedDescriptorSources(); + verifyLaneMaskGuardedDescriptorSources(); + verifyExecGuardedDescriptorSources(); verifySrtTape(); + verifyTargetFeatureKey(); verifyImageTableKeys(); verifyImageTables(); + verifySpecializedModuleRelease(); verifyImageTableRegistration(); verifyScalarAddressRule(); verifyPointerImageTable(); @@ -7685,12 +8590,14 @@ int main(int argc, char** argv) { verifyChainedTableOffset(); verifyPointerChainImageTable(); verifyWaterfallImageTable(); + verifySplitWaterfallImageTable(); verifyDescriptorPhis(); verifyUniformTableImage(); verifyLaneSelectedDescriptors(); verifyProgramCounterRelativeData(); verifyLanesOutsideHostSubgroup(); verifyHalfWaveReduction(); + verifyFusedExactArithmetic(); verifyNarrowVertexWave(); verifySingleLaneVertexWave(); verifyMeshConfiguration(); @@ -7725,7 +8632,10 @@ int main(int argc, char** argv) { verifyFmaskImageMode(); verifyCubeDepthCompareMode(); verifyVolumeArrayLoadMode(); + verifyArrayVolumeStoreMode(); + verifyMultisamplePlaneReadMode(); verifyVolumeBiasTableRejection(); + verifySamplerTableDeduplication(); verifyDynamicStorageMipChain(); verifySrgbDepthCompareModes(); verifyQuadMaskNeighbourReads(); diff --git a/core/libs/prx/libSceAgcDriver/tests/SharedWrites.cpp b/core/libs/prx/libSceAgcDriver/tests/SharedWrites.cpp index f1fe06c422..496fb288c5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SharedWrites.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SharedWrites.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" #include "prx/libc/include/GuestArena.hpp" @@ -17,14 +18,6 @@ #include #endif -extern "C" { -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); -} - namespace { using namespace AgcDriver::GuestMemory; void require(bool condition, const char* reason) { if (!condition) throw std::runtime_error(reason); } @@ -36,13 +29,14 @@ struct Mapping { require(sceKernelAllocateDirectMemory(0, 0x10000000000ll, size, 65536, 3, &physical) == 0, "allocate direct memory failed"); if (sceKernelMapDirectMemory(&data, size, 3, 0, physical, 65536) != 0) { sceKernelReleaseDirectMemory(physical, size); + throw std::runtime_error("map direct memory failed"); } } Mapping(const Mapping&) = delete; Mapping& operator=(const Mapping&) = delete; ~Mapping() { - if (data != nullptr) sceKernelMunmap(data, size); + if (data != nullptr) sceKernelMunmap(reinterpret_cast(data), size); if (physical >= 0) sceKernelReleaseDirectMemory(physical, size); } std::uint64_t Address() const { return reinterpret_cast(data); } @@ -180,7 +174,7 @@ void partialCollect() { require(collect(alias, 0, mapping.size, 4, true) == std::vector{page, page * 2}, "writes to two pages were not reported apart"); require(collect(mapping.data, 0, mapping.size, 4, true) == std::vector{page, page * 2}, "writes to two pages were not reported for their own view"); require(collect(mapping.data, 0, mapping.size, 4, false).empty() && collect(alias, 0, mapping.size, 4, false).empty(), "page tracking did not settle"); - require(sceKernelMunmap(alias, mapping.size) == 0, "partial collect alias unmap failed"); + require(sceKernelMunmap(reinterpret_cast(alias), mapping.size) == 0, "partial collect alias unmap failed"); #endif std::cout << "Partial shared write collection tests passed\n"; } @@ -253,14 +247,14 @@ void run() { require(std::memcmp(replacement.data, across.data() + part, part) == 0, "physical alias did not see the write"); require(std::all_of(static_cast(originalAlias), static_cast(originalAlias) + part, [](auto x) { return x == std::byte{29}; }), "replaced virtual range wrote the old backing"); #ifndef _WIN32 - require(sceKernelMunmap(middle, part) == 0, "partial unmap failed"); + require(sceKernelMunmap(reinterpret_cast(middle), part) == 0, "partial unmap failed"); const auto saved = bytes[0]; across[0] = std::byte{99}; require(!GuestArena::GuestArenaWriteSharedBacking_nid_no_patch(mapping.Address(), across.data(), across.size()), "writer accepted an unmapped gap"); require(bytes[0] == saved, "failed shared write changed its prefix"); #endif require(sceKernelMapDirectMemory(&middle, part, 3, 0x10, mapping.physical + part, part) == 0, "original backing restore failed"); - require(sceKernelMunmap(originalAlias, part) == 0, "alias unmap failed"); + require(sceKernelMunmap(reinterpret_cast(originalAlias), part) == 0, "alias unmap failed"); } std::vector threads; for (unsigned i = 0; i < 8; ++i) threads.emplace_back([&, i] { diff --git a/core/libs/prx/libSceAgcDriver/tests/SnapshotCopyFailure.cpp b/core/libs/prx/libSceAgcDriver/tests/SnapshotCopyFailure.cpp new file mode 100644 index 0000000000..f302ebf9af --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/SnapshotCopyFailure.cpp @@ -0,0 +1,74 @@ +#include "prx/libSceAgcDriver/Graphics/include/SnapshotCopies.hpp" +#include "AllocationFailure.hpp" +#include +#include +#include + +namespace { +using AgcDriver::Graphics::Detail::SnapshotCopies; +using AgcDriver::Tests::SetAllocationFailureCountdown; + +bool Consistent(const SnapshotCopies& copies) { + if (copies.entries.size() != copies.recency.size()) return false; + std::size_t bytes = 0; + for (auto it = copies.recency.begin(); it != copies.recency.end(); ++it) { + const auto found = copies.entries.find(*it); + if (found == copies.entries.end() || found->second.recent != it) return false; + bytes += found->second.bytes.size(); + } + return bytes == copies.bytes; +} + +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +void CheckInsertionFailures() { + const std::array data{std::byte{0x37}}; + unsigned failures = 0; + for (int point = 0; point < 4; ++point) { + SnapshotCopies copies; + const SnapshotCopies::Key first{0x1000, data.size(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT}; + const SnapshotCopies::Key second{0x2000, data.size(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT}; + auto owner = std::make_shared(7); + const std::shared_ptr buffer(owner, nullptr); + copies.Insert(first, buffer, data); + bool failed = false; + SetAllocationFailureCountdown(point); + try { + copies.Insert(second, buffer, data); + } catch (const std::bad_alloc&) { + failed = true; + ++failures; + } + SetAllocationFailureCountdown(-1); + Require(Consistent(copies), "failed snapshot insertion left inconsistent eviction metadata"); + Require(copies.entries.at(first).bytes == std::vector(data.begin(), data.end()), "failed insertion changed an existing snapshot"); + Require(owner.use_count() == (failed ? 3 : 4), "snapshot insertion leaked or lost a buffer reference"); + if (failed) { + Require(copies.entries.size() == 1 && copies.bytes == data.size(), "failed insertion changed cache accounting"); + copies.Insert(second, buffer, data); + } + Require(Consistent(copies) && copies.entries.size() == 2, "cache could not recover after allocation failure"); + const auto oldest = copies.entries.find(copies.recency.front()); + copies.bytes -= oldest->second.bytes.size(); + copies.entries.erase(oldest); + copies.recency.pop_front(); + Require(Consistent(copies) && copies.entries.contains(second), "eviction after insertion failure corrupted another snapshot"); + copies.Clear(); + Require(Consistent(copies) && copies.bytes == 0 && owner.use_count() == 2, "snapshot teardown retained buffers or stale accounting"); + } + Require(failures == 3, "did not exercise every snapshot insertion allocation"); +} +} + +int main() { + try { + CheckInsertionFailures(); + std::puts("Snapshot insertion allocation failures preserve eviction metadata and buffer ownership"); + } catch (const std::exception& error) { + SetAllocationFailureCountdown(-1); + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvLdsAtomicLock.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvLdsAtomicLock.cpp index 8155564e0b..2bb13e5283 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvLdsAtomicLock.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvLdsAtomicLock.cpp @@ -77,10 +77,18 @@ Usage Scan(std::span words) { std::uint32_t laneZeroBody = 0u; std::uint32_t lockPointer = 0u; std::map constants; + std::uint32_t faultFunction = 0u; + bool inFaultFunction = false; for (std::size_t cursor = 5; cursor < words.size();) { const auto count = words[cursor] >> spv::WordCountShift; const auto op = static_cast(words[cursor] & spv::OpCodeMask); Require(count != 0 && cursor + count <= words.size(), "truncated SPIR-V instruction"); + if (op == spv::OpName && count >= 3u && std::string_view(reinterpret_cast(words.data() + cursor + 2u)) == "record_bda_fault") faultFunction = words[cursor + 1u]; + if (op == spv::OpFunction && count >= 3u) inFaultFunction = words[cursor + 2u] == faultFunction; + if (inFaultFunction) { + cursor += count; + continue; + } if (op == spv::OpDecorate && count >= 4u && words[cursor + 2u] == spv::DecorationBuiltIn && words[cursor + 3u] == spv::BuiltInLocalInvocationIndex) { localIndexVariable = words[cursor + 1u]; } diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp index 1be75623e3..956897cb51 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp @@ -1,5 +1,7 @@ #include "SpirvBackend/SpirvModule.hpp" #include "SpirvBackend/SpirvSpecialization.hpp" +#include "Recompiler.hpp" +#include #include #include #include @@ -298,6 +300,92 @@ void testIdsBeyondTheBoundAreRejected() { check(rejected, "a module whose ids reach its bound was specialized"); } +void testPrivateCompositeTableLoadFolding() { + for (const auto storageClass : {spv::StorageClassPrivate, spv::StorageClassFunction}) { + for (const auto accessOp : {spv::OpAccessChain, spv::OpInBoundsAccessChain}) { + const auto build = [&](bool dynamicIndex, bool storeThroughChain) { + SpirvModule module; + module.EmitCapability(spv::CapabilityShader); + const auto typeVoid = module.Type(spv::OpTypeVoid); + const auto typeBool = module.Type(spv::OpTypeBool); + const auto typeU32 = module.Type(spv::OpTypeInt, 32u, 0u); + const auto falseVal = module.Constant(spv::OpConstantFalse, typeBool); + const auto zero = module.Constant(spv::OpConstant, typeU32, 0u); + const auto one = module.Constant(spv::OpConstant, typeU32, 1u); + const auto three = module.Constant(spv::OpConstant, typeU32, 3u); + const auto sixteen = module.Constant(spv::OpConstant, typeU32, 16u); + const auto twenty = module.Constant(spv::OpConstant, typeU32, 20u); + const auto thirty = module.Constant(spv::OpConstant, typeU32, 30u); + const auto typeArray = module.Type(spv::OpTypeArray, typeU32, three); + const auto initial = module.Constant(spv::OpConstantComposite, typeArray, std::vector{sixteen, twenty, thirty}); + const auto typeInputPtr = module.Type(spv::OpTypePointer, spv::StorageClassInput, typeU32); + const auto typeTablePtr = module.Type(spv::OpTypePointer, storageClass, typeArray); + const auto typeElementPtr = module.Type(spv::OpTypePointer, storageClass, typeU32); + const auto typePrivatePtr = module.Type(spv::OpTypePointer, spv::StorageClassPrivate, typeU32); + const auto typeFunction = module.Type(spv::OpTypeFunction, typeVoid); + const auto input = module.DefineGlobalVariable(typeInputPtr, spv::StorageClassInput); + const auto table = storageClass == spv::StorageClassPrivate + ? module.DefineInitializedGlobalVariable(typeTablePtr, spv::StorageClassPrivate, initial) + : module.AllocateId(); + const auto output = module.DefineGlobalVariable(typePrivatePtr, spv::StorageClassPrivate); + const auto mainId = module.AllocateId(); + const auto lane = module.AllocateId(); + const auto rawCond = module.AllocateId(); + const auto cond = module.AllocateId(); + const auto baseIndex = module.AllocateId(); + const auto index = module.AllocateId(); + const auto element = module.AllocateId(); + const auto loaded = module.AllocateId(); + const auto sum = module.AllocateId(); + module.AddFunction(spv::OpFunction, typeVoid, mainId, spv::FunctionControlMaskNone, typeFunction); + module.AddFunction(spv::OpLabel, module.AllocateId()); + if (storageClass == spv::StorageClassFunction) module.AddFunction(spv::OpVariable, typeTablePtr, table, spv::StorageClassFunction, initial); + module.AddFunction(spv::OpLoad, typeU32, lane, input); + if (storeThroughChain) { + const auto writePtr = module.AllocateId(); + module.AddFunction(accessOp, typeElementPtr, writePtr, table, zero); + module.AddFunction(spv::OpStore, writePtr, lane); + } + module.AddFunction(spv::OpUGreaterThan, typeBool, rawCond, lane, zero); + module.AddFunction(spv::OpLogicalOr, typeBool, cond, rawCond, falseVal); + module.AddFunction(spv::OpIAdd, typeU32, baseIndex, one, one); + module.AddFunction(spv::OpSelect, typeU32, index, cond, baseIndex, baseIndex); + module.AddFunction(accessOp, typeElementPtr, element, table, dynamicIndex ? lane : index); + module.AddFunction(spv::OpLoad, typeU32, loaded, element); + module.AddFunction(spv::OpIAdd, typeU32, sum, lane, loaded); + module.AddFunction(spv::OpStore, output, sum); + module.AddFunction(spv::OpReturn); + module.AddFunction(spv::OpFunctionEnd); + return std::pair{ShaderRecompiler::SpecializeSpirv(module.Finalize()), thirty}; + }; + const auto [folded, expectedConstant] = build(false, false); + const auto foldedCounts = opcodeCounts(folded); + check(!foldedCounts.contains(accessOp), "a constant-index composite table access chain was not removed"); + check(!foldedCounts.contains(spv::OpLogicalOr), "a right-false OpLogicalOr was not simplified"); + check(!foldedCounts.contains(spv::OpSelect), "an identical-arm OpSelect was not simplified"); + check(foldedCounts.contains(spv::OpLoad) && foldedCounts.at(spv::OpLoad) == 1u, "a constant-index composite table load was not folded"); + bool addedThirty = false; + for (std::size_t cursor = 5; cursor < folded.size(); cursor += folded[cursor] >> 16u) { + if ((folded[cursor] >> 16u) == 0u) break; + if ((folded[cursor] & 0xffffu) == spv::OpIAdd && folded[cursor + 4u] == expectedConstant) addedThirty = true; + } + check(addedThirty, "a constant-index composite table load did not fold to its element constant"); + + const auto [dynamic, unusedDynamic] = build(true, false); + static_cast(unusedDynamic); + const auto dynamicCounts = opcodeCounts(dynamic); + check(dynamicCounts.contains(accessOp) && dynamicCounts.at(accessOp) == 1u, "a dynamic-index composite table access chain was removed"); + check(dynamicCounts.contains(spv::OpLoad) && dynamicCounts.at(spv::OpLoad) == 2u, "a dynamic-index composite table load was folded"); + + const auto [mutated, unusedMutated] = build(false, true); + static_cast(unusedMutated); + const auto mutatedCounts = opcodeCounts(mutated); + check(mutatedCounts.contains(accessOp) && mutatedCounts.at(accessOp) == 2u, "a mutated composite table access chain was removed"); + check(mutatedCounts.contains(spv::OpLoad) && mutatedCounts.at(spv::OpLoad) == 2u, "a mutated composite table load was folded despite a store through an access chain"); + } + } +} + void testBlockBallotsOfOnePredicateShareOneBallot() { SpirvModule module; const auto typeBool = module.Type(spv::OpTypeBool); @@ -356,10 +444,41 @@ void testBlockBallotsOfOnePredicateShareOneBallot() { check(ballotSources.contains(afterLabel), "a ballot in a later block reused one from an earlier block"); } +void testInternedSpirvSharesEqualWords() { + const std::vector words{spv::MagicNumber, 0x00010400u, 0u, 7u, 0u, (2u << 16u) | spv::OpCapability, spv::CapabilityShader}; + auto changed = words; + changed.back() = spv::CapabilityInt64; + const auto first = InternSpirv(words); + const auto second = InternSpirv(words); + const auto other = InternSpirv(changed); + check(std::ranges::equal(first.Words(), words) && std::ranges::equal(other.Words(), changed), "interned SPIR-V changed its words"); + check(first.data() == second.data(), "two equal specialized modules kept separate copies of their words"); + check(first.data() != other.data(), "different SPIR-V was interned into one copy"); + auto edited = second; + edited[6] = spv::CapabilityFloat64; + const auto again = InternSpirv(words); + check(edited[6] == spv::CapabilityFloat64 && first[6] == spv::CapabilityShader && again.data() == first.data(), "a write to one holder of interned SPIR-V changed the others"); +} + +void testInternedSpirvReleasesUnusedWords() { + const std::vector words{spv::MagicNumber, 0x00010400u, 0u, 7u, 0u, (2u << 16u) | spv::OpCapability, spv::CapabilityGeometry}; + SharedSpirv::Weak released; + { + const auto first = InternSpirv(words); + const auto second = InternSpirv(words); + check(first.data() == second.data(), "two equal specialized modules kept separate copies of their words"); + released = first.Weaken(); + } + check(released.Expired(), "the SPIR-V interning table kept words no holder uses"); + const auto again = InternSpirv(words); + check(std::ranges::equal(again.Words(), words) && released.Lock().empty(), "SPIR-V interned again after its release changed its words"); +} + int main() { testFoldingLeavesNoDeadBlocksOrConstants(); testIdsBeyondTheBoundAreRejected(); testStructExtractsAreNotShuffled(); + testPrivateCompositeTableLoadFolding(); testRepeatedTypesShareOneId(); testOperandShapesDoNotCollide(); testOperandKindsAgree(); @@ -371,6 +490,8 @@ int main() { testIdsStayUniqueBeyondCacheCapacity(); testEmittedWordsAreStable(); testBlockBallotsOfOnePredicateShareOneBallot(); + testInternedSpirvSharesEqualWords(); + testInternedSpirvReleasesUnusedWords(); if (failures != 0) { std::fprintf(stderr, "%d check(s) failed\n", failures); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp index eb5a967d31..b50010c578 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp @@ -396,6 +396,7 @@ std::optional> storedLaneFunction(const switch (opcode) { case spv::OpFunction: case spv::OpLabel: + case spv::OpVariable: case spv::OpReturn: case spv::OpFunctionEnd: break; @@ -936,6 +937,95 @@ OpStore %slot2 %second check(opcodeCount(acrossCall, spv::OpGroupNonUniformBallot) == 2, "a ballot was shared across a function call"); } +std::string privateTableModuleSource(const std::string& body, bool functionTable = false) { + const std::string storage = functionTable ? "Function" : "Private"; + const std::string globalTable = functionTable ? "" : "%table = OpVariable %tableArray Private %initial\n"; + const std::string localTable = functionTable ? "%table = OpVariable %tableArray Function %initial\n" : ""; + return R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" %index +OpExecutionMode %main LocalSize 8 1 1 +OpDecorate %index BuiltIn LocalInvocationIndex +%void = OpTypeVoid +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%false = OpConstantFalse %bool +%zero = OpConstant %uint 0 +%one = OpConstant %uint 1 +%three = OpConstant %uint 3 +%sixteen = OpConstant %uint 16 +%twenty = OpConstant %uint 20 +%thirty = OpConstant %uint 30 +%array = OpTypeArray %uint %three +%initial = OpConstantComposite %array %sixteen %twenty %thirty +%input = OpTypePointer Input %uint +%tableArray = OpTypePointer )" + storage + R"( %array +%tableElement = OpTypePointer )" + storage + R"( %uint +%private = OpTypePointer Private %uint +%function = OpTypeFunction %void +%index = OpVariable %input Input +)" + globalTable + R"(%output = OpVariable %private Private +%main = OpFunction %void None %function +%entry = OpLabel +)" + localTable + R"(%lane = OpLoad %uint %index +)" + body + R"(OpReturn +OpFunctionEnd +)"; +} + +void testSpecializedPrivateCompositeTableLoad() { + for (const bool functionTable : {false, true}) { + for (const bool inBounds : {false, true}) { + const auto accessName = inBounds ? "OpInBoundsAccessChain" : "OpAccessChain"; + const auto accessOp = inBounds ? spv::OpInBoundsAccessChain : spv::OpAccessChain; + const std::string label = std::string(accessName) + (functionTable ? " (Function)" : " (Private)"); + try { + const auto folded = assemble(privateTableModuleSource( + "%rawCond = OpUGreaterThan %bool %lane %zero\n" + "%cond = OpLogicalOr %bool %rawCond %false\n" + "%baseOffset = OpIAdd %uint %one %one\n" + "%offset = OpSelect %uint %cond %baseOffset %baseOffset\n" + "%element = " + std::string(accessName) + " %tableElement %table %offset\n" + "%loaded = OpLoad %uint %element\n" + "%sum = OpIAdd %uint %lane %loaded\n" + "OpStore %output %sum\n", functionTable)); + static_cast(ShaderRecompiler::ValidateAndOptimizeSpirv(folded, Vulkan11, Spirv13, false, false)); + const auto specializedFolded = ShaderRecompiler::SpecializeSpirv(folded); + static_cast(ShaderRecompiler::ValidateAndOptimizeSpirv(specializedFolded, Vulkan11, Spirv13, false, false)); + check(opcodeCount(specializedFolded, accessOp) == 0u, label + ": constant-index table access chain was not removed"); + check(opcodeCount(specializedFolded, spv::OpLogicalOr) == 0u, label + ": right-false OpLogicalOr was not simplified"); + check(opcodeCount(specializedFolded, spv::OpSelect) == 0u, label + ": identical-arm OpSelect was not simplified"); + check(opcodeCount(specializedFolded, spv::OpLoad) == 1u, label + ": constant-index table load was not folded"); + check(storedLaneFunction(specializedFolded) == std::optional>({1u, 30u}), label + ": constant-index table load folded to the wrong value"); + + const auto dynamic = assemble(privateTableModuleSource( + "%element = " + std::string(accessName) + " %tableElement %table %lane\n" + "%loaded = OpLoad %uint %element\n" + "OpStore %output %loaded\n", functionTable)); + static_cast(ShaderRecompiler::ValidateAndOptimizeSpirv(dynamic, Vulkan11, Spirv13, false, false)); + const auto specializedDynamic = ShaderRecompiler::SpecializeSpirv(dynamic); + static_cast(ShaderRecompiler::ValidateAndOptimizeSpirv(specializedDynamic, Vulkan11, Spirv13, false, false)); + check(opcodeCount(specializedDynamic, accessOp) == 1u, label + ": dynamic-index table access chain was removed"); + check(opcodeCount(specializedDynamic, spv::OpLoad) == 2u, label + ": dynamic-index table load was folded"); + + const auto mutated = assemble(privateTableModuleSource( + "%writeElement = " + std::string(accessName) + " %tableElement %table %zero\n" + "OpStore %writeElement %lane\n" + "%readElement = " + std::string(accessName) + " %tableElement %table %one\n" + "%loaded = OpLoad %uint %readElement\n" + "OpStore %output %loaded\n", functionTable)); + static_cast(ShaderRecompiler::ValidateAndOptimizeSpirv(mutated, Vulkan11, Spirv13, false, false)); + const auto specializedMutated = ShaderRecompiler::SpecializeSpirv(mutated); + static_cast(ShaderRecompiler::ValidateAndOptimizeSpirv(specializedMutated, Vulkan11, Spirv13, false, false)); + check(opcodeCount(specializedMutated, accessOp) == 2u, label + ": mutated table access chain was removed"); + check(opcodeCount(specializedMutated, spv::OpLoad) == 2u, label + ": mutated table load was folded despite a store through an access chain"); + } catch (const std::exception& error) { + check(false, label + " table specialization: " + error.what()); + } + } + } +} + } int main() { @@ -953,6 +1043,7 @@ int main() { testFoldedConstantUsedByUndecodedInstructionIsKept(); testSharedConstantsAreNotCopiedForUndecodedUsers(); testSharedBooleanConstantsKeepTheirValues(); + testSpecializedPrivateCompositeTableLoad(); testBallotsMergedIntoOneBlockShareOneBallot(optimizationEnabled); if (failures != 0) { std::fprintf(stderr, "%d optimizer check(s) failed\n", failures); @@ -961,3 +1052,4 @@ int main() { std::puts("SPIR-V optimizer checks passed"); return 0; } + diff --git a/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp b/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp index 272e5ed914..0578ee283d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include #include #include +#include namespace { @@ -34,6 +36,10 @@ constexpr std::array BranchedLdsStep{ 0xbf8cc07fu, 0xbf8a0000u, 0xbf810000u, }; +constexpr std::array LdsHalfStore{0x34040082u, 0xd87c0400u, 0x00000102u, 0xbf8cc07fu, 0xbf810000u}; +constexpr std::array LdsAtomicAdd{0x34040082u, 0xd8000400u, 0x00000102u, 0xbf8cc07fu, 0xbf810000u}; +constexpr std::array LdsAtomicMax{0x34040082u, 0xd84c0400u, 0x00000102u, 0xbf8cc07fu, 0xbf810000u}; + struct Barriers { std::uint32_t subgroupControl = 0; std::uint32_t subgroupMemory = 0; @@ -43,14 +49,19 @@ void require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } -Barriers recompile(std::uint32_t threads, bool waveLdsMemoryBarriers) { +struct MemoryBarriers { + std::uint32_t workgroup = 0; + std::uint32_t device = 0; +}; + +ShaderRecompiler::RecompileResult recompileProgram(std::span code, std::uint32_t threads, bool waveLdsMemoryBarriers, bool deviceScopeLdsMemoryBarriers) { using namespace ShaderRecompiler; const std::array capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform}; const std::array extensions{"SPV_KHR_storage_buffer_storage_class"}; RecompileRequest request{}; - request.shader = {ShaderStage::Compute, 0x20000u, BranchedLdsStep, 0, {}}; + request.shader = {ShaderStage::Compute, 0x20000u, code, 0, {}}; request.context.waveSize = 32; - request.context.compute = ShaderComputeStageInfo{{threads, 1u, 1u}, 256u, {false, false, false}, false, 1u}; + request.context.compute = ShaderComputeStageInfo{{threads, 1u, 1u}, 512u, {false, false, false}, false, 1u}; request.target.vulkanVersion = 0x00403000u; request.target.spirvVersion = 0x00010600u; request.target.subgroupSize = 32; @@ -61,15 +72,42 @@ Barriers recompile(std::uint32_t threads, bool waveLdsMemoryBarriers) { request.target.maxWorkgroupInvocations = 1024; request.target.maxWorkgroupSharedMemoryBytes = 49152; request.target.waveLdsMemoryBarriers = waveLdsMemoryBarriers; + request.target.deviceScopeLdsMemoryBarriers = deviceScopeLdsMemoryBarriers; request.layout = {0, 0, 0, 128}; request.useCache = false; - const auto result = Recompile(request); - const auto& words = result.spirv.Words(); + return Recompile(request); +} + +std::unordered_map constantsOf(std::span words) { std::unordered_map constants; + for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) { + if (static_cast(words[at] & 0xffffu) == spv::OpConstant && (words[at] >> 16u) == 4u) constants[words[at + 2]] = words[at + 3]; + } + return constants; +} + +MemoryBarriers memoryBarriers(std::span code, bool deviceScopeLdsMemoryBarriers) { + const auto result = recompileProgram(code, 128, false, deviceScopeLdsMemoryBarriers); + const auto& words = result.spirv.Words(); + const auto constants = constantsOf(words); + MemoryBarriers barriers; + for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) { + if (static_cast(words[at] & 0xffffu) != spv::OpMemoryBarrier) continue; + const auto scope = constants.find(words[at + 1]); + require(scope != constants.end(), "a memory barrier scope is not a 32-bit constant"); + if (scope->second == spv::ScopeWorkgroup) ++barriers.workgroup; + if (scope->second == spv::ScopeDevice) ++barriers.device; + } + return barriers; +} + +Barriers recompile(std::uint32_t threads, bool waveLdsMemoryBarriers) { + const auto result = recompileProgram(BranchedLdsStep, threads, waveLdsMemoryBarriers, false); + const auto& words = result.spirv.Words(); + const auto constants = constantsOf(words); Barriers barriers; 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) constants[words[at + 2]] = words[at + 3]; if (op != spv::OpControlBarrier && op != spv::OpMemoryBarrier) continue; const auto scope = constants.find(words[at + 1]); require(scope != constants.end(), "a barrier scope is not a 32-bit constant"); @@ -83,6 +121,17 @@ Barriers recompile(std::uint32_t threads, bool waveLdsMemoryBarriers) { void verifyDriverQuirk() { for (const auto& item : Cases) { require(AgcDriver::SubgroupControlBarrierUsable(item.driver) == item.usable, "driver " + std::to_string(static_cast(item.driver)) + ": expected subgroup control barriers to be " + (item.usable ? "usable" : "unusable")); + require(AgcDriver::WorkgroupScopeFenceUsable(item.driver) == item.usable, "driver " + std::to_string(static_cast(item.driver)) + ": expected workgroup-scope fences to be " + (item.usable ? "usable" : "unusable")); + } +} + +void verifyLdsMemoryBarrierScope() { + const std::array>, 3> programs{{{"ds_write_b16", LdsHalfStore}, {"ds_add_u32", LdsAtomicAdd}, {"ds_max_f32", LdsAtomicMax}}}; + for (const auto& [name, code] : programs) { + const auto workgroup = memoryBarriers(code, false); + require(workgroup.workgroup == 1 && workgroup.device == 0, std::string(name) + " did not order its LDS update with one workgroup-scope OpMemoryBarrier"); + const auto device = memoryBarriers(code, true); + require(device.workgroup == 0 && device.device == 1, std::string(name) + " with device-scope LDS memory barriers did not order its LDS update with one device-scope OpMemoryBarrier"); } } @@ -101,6 +150,7 @@ int main() { try { verifyDriverQuirk(); verifyEmittedBarriers(); + verifyLdsMemoryBarrierScope(); std::puts("subgroup barrier tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index d97862941b..fa67366d65 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "execution/VulkanTestDevice.hpp" #include "prx/libSceAgcDriver/Execution/include/QueueState.hpp" @@ -31,9 +32,6 @@ #include #endif -extern "C" int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -extern "C" int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); - static_assert(sizeof(Packet) == 16); static_assert(offsetof(Packet, addr) == 0); static_assert(offsetof(Packet, dw_num) == 8); @@ -593,8 +591,7 @@ void testSmallCopyBehindPendingSource() { waitFor(&copied, 1, "the label after a small copy behind a pending source never landed"); AgcDriverWaitIdle_nid_postfix(); check(std::equal(destination, destination + smallWords, produced), "a small copy did not see the GPU copy into its source before it"); - if (std::getenv("APS5_COPY_SYNC") != nullptr) check(AgcDriver::DriverDetail::copiesBehindPendingSources.load() == before, "a small copy that has to wait for a completion store over its source went through the GPU copy"); - else check(AgcDriver::DriverDetail::copiesBehindPendingSources.load() == before + 1, "a small copy whose source the GPU still stores into was not recorded on the GPU"); + check(AgcDriver::DriverDetail::copiesBehindPendingSources.load() == before, "a small copy queued behind the pipeline write into its source was recorded inline"); std::fill(destination, destination + smallWords, 0u); const auto settled = AgcDriver::DriverDetail::copiesBehindPendingSources.load(); submit(0, commands(memoryCopy(destination, produced, smallBytes), writeData(&settledCopied, 1))); @@ -955,7 +952,8 @@ int main(int argc, char** argv) { }); for (auto& reader : readers) reader.join(); for (const auto& snapshot : concurrent) check(snapshot == branched, "concurrent raw compute readers lost snapshot reuse"); - alignas(256) std::array, 65> programs{}; + struct alignas(256) RawProgram : std::array {}; + std::vector programs(4097); for (auto& program : programs) program[0] = 0xbf810000; const auto firstAddress = reinterpret_cast(programs.front().data()); readers.clear(); @@ -965,6 +963,9 @@ int main(int argc, char** argv) { for (auto& reader : readers) reader.join(); for (const auto& snapshot : concurrent) check(snapshot == concurrent.front(), "concurrent raw compute misses duplicated snapshots"); const auto evicted = AgcDriver::DriverDetail::ReadRawShader(firstAddress); + for (std::size_t i = 1; i <= 400; ++i) + AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast(programs[i].data())); + check(AgcDriver::DriverDetail::ReadRawShader(firstAddress) == evicted, "raw shader cache evicted a program while a title used only 401"); for (std::size_t i = 1; i < programs.size(); ++i) AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast(programs[i].data())); check(AgcDriver::DriverDetail::ReadRawShader(firstAddress) != evicted && evicted->code[0] == 0xbf810000, diff --git a/core/libs/prx/libSceAgcDriver/tests/TessellationFactorRing.cpp b/core/libs/prx/libSceAgcDriver/tests/TessellationFactorRing.cpp index f302a0d119..c6bbb71920 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TessellationFactorRing.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TessellationFactorRing.cpp @@ -1,14 +1,10 @@ +#include "prx/libSceAgcDriver/State/include/Configuration.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI sceAgcDriverSetTFRing(const volatile void* base, uint32_t size); -int APS5_VABI sceAgcDriverGetTFRing(uintptr_t* base, uint32_t* size); -} - namespace { constexpr uint32_t Canary = 0xa5a5a5a5; diff --git a/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp b/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp index 1b1ec2d0da..2ccb9129ac 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp @@ -1,11 +1,16 @@ #include "IntermediateRepresentation/IrBuilder.hpp" #include "IntermediateRepresentation/IrProgram.hpp" +#include "Optimization/ShaderStageInputInfo.hpp" #include "Optimization/TessellationLowering.hpp" +#include "SpirvBackend/SpirvEmitter.hpp" +#include #include #include #include #include +#include #include +#include using namespace ShaderRecompiler; @@ -135,6 +140,52 @@ bool RefusedBecause(IrProgram& program, const char* reason) { return !outcome.lowered && outcome.reason.find(reason) != std::string::npos; } +std::uint32_t EmittedEvaluationControlPoint(std::uint32_t address) { + constexpr std::uint32_t ControlStride = 16u; + IrProgram program; + IrBuilder builder{program}; + program.SetWaveSize(64); + program.Resources().stage = IrShaderStage::TessellationEvaluation; + program.Resources().srtPlanComplete = true; + program.Resources().resourceTrackingComplete = true; + program.Metadata().shaderInfoComplete = true; + program.Metadata().bindingLayoutComplete = true; + program.Metadata().tessellationControlStride = ControlStride; + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + program.BlockOrder().push_back(&block); + BlockInfo info; + info.id = block.Id(); + program.Metadata().blockInfo.push_back(info); + builder.SetInsertionPoint(block); + auto& read = builder.Emit(IrOpcode::GetTessellationAttribute, IrType::U32, {&builder.Constant(static_cast(TessellationAttribute::EvaluationInput)), &builder.Constant(address), &builder.ConstantBool(true)}); + static_cast(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&read})); + ShaderVertexInputInfo vertex; + vertex.tess.inputControlPoints = InputControlPoints; + vertex.tess.outputControlPoints = InputControlPoints; + vertex.tess.domain = 1u; + vertex.tess.partitioning = 2u; + vertex.tess.outputTopology = 2u; + ShaderStageInputInfo inputs; + inputs.vertex = &vertex; + CompiledBindingLayout bindings; + bindings.layout = program.Metadata().bindings; + SpirvTargetOptions target{}; + target.vulkanVersion = 0x00401000; + target.spirvVersion = 0x00010300; + target.subgroupSize = 64; + const auto words = SpirvEmitter().Emit(program, inputs, bindings, target); + std::unordered_map constants; + for (std::size_t at = 5; at < words.size(); at += words[at] >> 16u) { + const auto count = words[at] >> 16u; + if (count == 0u || at + count > words.size()) throw std::runtime_error("the domain program compiled to a malformed SPIR-V instruction"); + const auto opcode = words[at] & 0xffffu; + if (opcode == spv::OpConstant && count == 4u) constants[words[at + 2u]] = words[at + 3u]; + if (opcode == spv::OpAccessChain && count == 7u) return constants.at(words[at + 4u]); + } + throw std::runtime_error("the domain program reads no control point attribute"); +} + bool run(const char* name, const std::function& body) { bool passed = false; try { @@ -206,5 +257,9 @@ int main() { return patch->Opcode() == IrOpcode::IMul32 && patch->Argument(1)->Resolve()->ImmediateU32() == PatchStride; }); + passed &= run("a domain read at an immediate address past the last control point reads the last control point", [] { + return EmittedEvaluationControlPoint(InputControlPoints * 16u) == InputControlPoints - 1u; + }); + return passed ? 0 : 1; } diff --git a/core/libs/prx/libSceAgcDriver/tests/TessellationPipelineCache.cpp b/core/libs/prx/libSceAgcDriver/tests/TessellationPipelineCache.cpp new file mode 100644 index 0000000000..6c922e0bc9 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/TessellationPipelineCache.cpp @@ -0,0 +1,31 @@ +#include "prx/libSceAgcDriver/Execution/include/TessellationPipelineCache.hpp" +#include + +namespace { + +struct Case { + VkDriverId driver; + bool usable; +}; + +constexpr Case Cases[]{ + {VK_DRIVER_ID_MOLTENVK, false}, + {VK_DRIVER_ID_MESA_RADV, true}, + {VK_DRIVER_ID_MESA_LLVMPIPE, true}, + {VK_DRIVER_ID_NVIDIA_PROPRIETARY, true}, + {VK_DRIVER_ID_AMD_PROPRIETARY, true}, + {VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, true}, +}; + +} + +int main() { + int failures = 0; + for (const auto& item : Cases) { + if (AgcDriver::TessellationPipelineCacheUsable(item.driver) != item.usable) { + std::fprintf(stderr, "driver %d: expected tessellation pipelines %s the pipeline cache\n", static_cast(item.driver), item.usable ? "to use" : "not to use"); + ++failures; + } + } + return failures == 0 ? 0 : 1; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/TextureFormat.cpp b/core/libs/prx/libSceAgcDriver/tests/TextureFormat.cpp index 31ff43033e..3a13ee2d9e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TextureFormat.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TextureFormat.cpp @@ -63,6 +63,7 @@ void srgbTwinTests() { Require(SrgbTwinFormat(context, VK_FORMAT_R8_UNORM) == VK_FORMAT_UNDEFINED, "a device that decodes 8_SRGB in the shader must not give R8 an sRGB twin"); Require(SrgbTwinFormat(context, VK_FORMAT_R8G8B8A8_UNORM) == VK_FORMAT_R8G8B8A8_SRGB && SrgbTwinFormat(context, VK_FORMAT_BC7_UNORM_BLOCK) == VK_FORMAT_BC7_SRGB_BLOCK, "formats without a shader decode must keep their sRGB twins"); Require(SrgbTwinFormat(context, VK_FORMAT_R32_SFLOAT) == VK_FORMAT_UNDEFINED, "formats without an sRGB counterpart have no twin"); + Require(SrgbTwinFormat(context, VK_FORMAT_R8G8B8A8_SNORM) == VK_FORMAT_R8G8B8A8_SRGB, "8_8_8_8 SNORM must read its bytes through the sRGB twin"); } template @@ -143,6 +144,25 @@ void repackedFormatTests() { Require(TextureComponentChannel(guest, VK_COMPONENT_SWIZZLE_A) == VK_COMPONENT_SWIZZLE_A && TextureComponentChannel(guest, VK_COMPONENT_SWIZZLE_ZERO) == VK_COMPONENT_SWIZZLE_ZERO, "format 43 keeps alpha and constant selectors"); } +void gcnBufferFormatTests() { + using ShaderRecompiler::IrBufferFormat; + const auto& formats = ShaderRecompiler::GcnBufferFormats(); + const auto gcn = [&](std::uint32_t data, std::uint32_t number) { return static_cast(formats[(data << 3u) | number]); }; + std::array seen{}; + for (const auto format : formats) ++seen[format]; + for (std::uint32_t format = 1; format < seen.size(); ++format) Require(seen[format] == 1u, "unified buffer format " + std::to_string(format) + " must come from exactly one GCN format pair"); + Require(gcn(0u, 4u) == IrBufferFormat::Invalid && gcn(15u, 7u) == IrBufferFormat::Invalid, "GCN DATA_FORMAT 0 and 15 have no buffer form"); + Require(gcn(1u, 7u) == IrBufferFormat::Invalid && gcn(4u, 0u) == IrBufferFormat::Invalid, "8-bit float and 32-bit UNORM have no buffer form"); + Require(gcn(1u, 0u) == IrBufferFormat::Format8UNorm && gcn(2u, 7u) == IrBufferFormat::Format16Float, "8 and 16-bit pairs must map"); + Require(gcn(4u, 4u) == IrBufferFormat::Format32UInt && gcn(4u, 7u) == IrBufferFormat::Format32Float, "32-bit pairs must map"); + Require(gcn(6u, 7u) == IrBufferFormat::Format11_11_10Float && gcn(7u, 7u) == IrBufferFormat::Format10_11_11Float, "GCN 10_11_11 and 11_11_10 must map by bit layout"); + Require(gcn(8u, 0u) == IrBufferFormat::Format2_10_10_10UNorm && gcn(9u, 0u) == IrBufferFormat::Format10_10_10_2UNorm, "GCN 10_10_10_2 and 2_10_10_10 must map by bit layout"); + Require(gcn(10u, 0u) == IrBufferFormat::Format8_8_8_8UNorm && gcn(14u, 7u) == IrBufferFormat::Format32_32_32_32Float, "four-channel pairs must map"); + const std::uint32_t word3 = 0x00000facu | (7u << 12u) | (14u << 15u) | (7u << 27u); + Require(ShaderRecompiler::GcnBufferWord3(16u << 16u, word3) == (0x00000facu | (77u << 12u) | (1u << 28u)), "a strided GCN V# must check its index range"); + Require(ShaderRecompiler::GcnBufferWord3(0u, word3) == (0x00000facu | (77u << 12u) | (3u << 28u)), "a GCN V# without a stride must check its byte range"); +} + void convertedDccClearTests() { alignas(64) std::array keys{}; GuestTextureResource resource{}; @@ -204,6 +224,22 @@ void packedFloatDccClearTests() { Require(TextureClearKeys(resource, 1024) == DccKeys::Clear1111, "packed float texture discarded its white fast-clear keys"); } +void fillContractTests() { + std::array texels; + texels.fill(0xdeadbeefu); + auto span = std::as_writable_bytes(std::span(texels)); + Require(!FillDccClear(VK_FORMAT_R8G8B8A8_UNORM, DccKeys::ClearRegister, false, span), "a register clear encoded a texel"); + for (const auto texel : texels) Require(texel == 0u, "a declined clear left its span untouched"); + texels.fill(0xdeadbeefu); + Require(!FillDccClear(VK_FORMAT_R8G8B8A8_UNORM, DccKeys::Uncompressed, false, span), "an uncompressed code encoded a texel"); + for (const auto texel : texels) Require(texel == 0u, "a declined clear left its span untouched"); + std::array partial; + partial.fill(std::byte{0x5a}); + Require(FillDccClear(VK_FORMAT_B10G11R11_UFLOAT_PACK32, DccKeys::Clear1111, false, partial), "packed float fast clear was not encoded"); + const std::array white{std::byte{0xc0}, std::byte{0x03}, std::byte{0x1e}, std::byte{0x78}}; + for (std::size_t index = 0; index < partial.size(); ++index) Require(partial[index] == (index < 8u ? white[index % 4u] : std::byte{0}), "a clear fill over a partial trailing element did not zero the tail"); +} + } void RunTextureFormatTests() { @@ -268,4 +304,6 @@ void RunTextureFormatTests() { convertedDccClearTests(); singleDccKeysTests(); packedFloatDccClearTests(); + fillContractTests(); + gcnBufferFormatTests(); } diff --git a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp index cb439c6ffe..fce3aa0322 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp @@ -1,4 +1,6 @@ #include "GraphicsTests.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" @@ -386,6 +388,29 @@ void RunTextureTilingTests() { Require(dense.pitchBytes == 1024 && CoveredMipBytes(TextureTileMode::kLinear, 4, dense) == std::vector>{{0, 3072}}, "linear rows without padding are not one range"); } + { + const auto* equation = FindTextureSwizzleEquation(XorSwizzleMode(TextureTileMode::kZ64KBX), 4u); + Require(equation != nullptr, "missing the 64KB Z_X swizzle equation of 4-byte elements"); + for (const auto mask : equation->bits) Require((mask & 0x00f80f80u) == 0u, "a 64KB Z_X address bit of a 4-byte element reads x or y bits past one 128x128 tile"); + GuestTextureResource plane{}; + plane.width = 3840; + plane.height = 2160; + plane.mipCount = 1; + plane.tileMode = TextureTileMode::kZ64KBX; + plane.dimension = TextureDimension::k2D; + plane.format = 22; + const auto geometry = DescribeSurface(plane); + const auto& mip = geometry.mips.front(); + const auto block = ThinBlockLayout(TextureTileMode::kZ64KBX, 4u); + Require(block[0] == 65536u && block[1] == 128u && block[2] == 128u, "a 64KB Z_X tile of 4-byte elements is not 128x128"); + Require(!mip.tail && mip.tiledOffset == 0u && mip.blocksPerRow == 30u && geometry.guestBytes == 510ull * 65536ull, "a 3840x2160 D32 Z_X plane is not 30x17 tiles"); + const std::array, 8> samples{{{0, 0}, {127, 127}, {128, 0}, {3839, 0}, {0, 128}, {1000, 700}, {2047, 1151}, {3839, 2159}}}; + for (const auto& [x, y] : samples) { + const auto offset = mip.tiledOffset + (static_cast(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + equationOffset(*equation, x, y, 0); + Require(offset / 65536u == static_cast(y >> 7u) * 30u + (x >> 7u), "element (" + std::to_string(x) + ", " + std::to_string(y) + ") of a 3840x2160 D32 Z_X plane lies outside the 64 KiB unit of its 128x128 tile"); + } + } + reject([] { ComputeSurfaceSize({}, 1); }, "empty mip chain"); reject([] { ComputeSurfaceSize(ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 1), 0); }, "zero array layers"); @@ -401,26 +426,35 @@ void RunTextureTilingTests() { array.depthOrLastArray = 1536; array.baseArray = 1536; const auto layerBytes = DescribeSurface(array).layerBytes; - const auto window = ViewedArrayWindow(array); + const auto window = ViewedArrayWindow(array, false); Require(window.baseAddress == array.baseAddress + 1536ull * layerBytes && window.depthOrLastArray == 0 && window.baseArray == 0, "a sampled view of one slice at 1536 of an R_X array must start at that slice"); Require(DescribeSurface(window).layerBytes == layerBytes && DescribeSurface(window).layers == 1, "a sampled array window must keep the slice layout"); auto unaligned = array; unaligned.baseArray = 1530; - Require(ViewedArrayWindow(unaligned).baseAddress == array.baseAddress, "an R_X window at a slice the swizzle mixes in must keep the whole array"); + const auto mixed = ViewedArrayWindow(unaligned, false); + Require(mixed.baseAddress == array.baseAddress + 1520ull * layerBytes && mixed.baseArray == 10 && mixed.depthOrLastArray == 16, "an R_X view at a slice the swizzle mixes in must start its window at the last slice before it with those bits clear"); + auto low = array; + low.baseArray = 9; + Require(ViewedArrayWindow(low, false).baseAddress == array.baseAddress, "an R_X view below the first slice with those bits clear keeps the whole array"); auto plain = array; plain.tileMode = TextureTileMode::kStandard64KB; plain.baseArray = 1530; - Require(ViewedArrayWindow(plain).baseAddress == array.baseAddress + 1530ull * DescribeSurface(plain).layerBytes && ViewedArrayWindow(plain).depthOrLastArray == 6, "a non-XOR window may start at any slice"); + Require(ViewedArrayWindow(plain, false).baseAddress == array.baseAddress + 1530ull * DescribeSurface(plain).layerBytes && ViewedArrayWindow(plain, false).depthOrLastArray == 6, "a non-XOR window may start at any slice"); auto fromZero = array; fromZero.baseArray = 0; - Require(ViewedArrayWindow(fromZero).baseAddress == array.baseAddress && ViewedArrayWindow(fromZero).depthOrLastArray == 1536, "a view from slice 0 keeps its descriptor"); + Require(ViewedArrayWindow(fromZero, false).baseAddress == array.baseAddress && ViewedArrayWindow(fromZero, false).depthOrLastArray == 1536, "a view from slice 0 keeps its descriptor"); auto compressed = array; compressed.dccAddress = 0x20000000ull; - Require(ViewedArrayWindow(compressed).baseAddress == array.baseAddress, "a view with DCC metadata keeps its descriptor"); + Require(ViewedArrayWindow(compressed, false).baseAddress == array.baseAddress, "a view with DCC metadata keeps its descriptor"); + const auto stored = ViewedArrayWindow(compressed, true); + Require(stored.baseAddress == window.baseAddress && stored.depthOrLastArray == 0 && stored.baseArray == 0 && stored.dccAddress == 0, "a view whose DCC keys read as stored is cut to its slices without metadata"); + auto compressedFromZero = compressed; + compressedFromZero.baseArray = 0; + Require(ViewedArrayWindow(compressedFromZero, true).baseAddress == array.baseAddress && ViewedArrayWindow(compressedFromZero, true).dccAddress == compressed.dccAddress, "a view with DCC metadata from slice 0 keeps its descriptor"); GuestTextureResource cube{}; cube.baseAddress = 0x30000000ull; @@ -432,13 +466,45 @@ void RunTextureTilingTests() { cube.tileMode = TextureTileMode::kStandard4KB; cube.depthOrLastArray = 7232; cube.baseArray = 7227; - const auto faces = ViewedArrayWindow(cube); + const auto faces = ViewedArrayWindow(cube, false); Require(faces.baseAddress == cube.baseAddress + 7227ull * DescribeSurface(cube).layerBytes && faces.depthOrLastArray == 5 && faces.baseArray == 0 && faces.dimension == TextureDimension::kCube, "a cube view of six faces at 7227 must start at its first face"); + auto compressedVolume = cube; + compressedVolume.dimension = TextureDimension::k3D; + compressedVolume.baseArray = 0; + compressedVolume.dccAddress = 0x20000000ull; + Require(ViewedArrayWindow(compressedVolume, true).dccAddress == compressedVolume.dccAddress, "a volume with DCC metadata keeps its descriptor"); + auto volume = cube; volume.dimension = TextureDimension::k3D; volume.baseArray = 0; - Require(ViewedArrayWindow(volume).baseAddress == cube.baseAddress, "a volume keeps its descriptor"); + Require(ViewedArrayWindow(volume, false).baseAddress == cube.baseAddress, "a volume keeps its descriptor"); + } + + { + const std::array words{0x027c0000u, 0xc1600000u, 0x00ffc0ffu, 0xd1800924u, 0x027cc27cu, 0xc1400000u, 0x00704018u, 0x91b00204u}; + const auto view = DecodeTextureResource(words); + Require(view.baseAddress == 0x27c000000ull && view.dccAddress == 0x91b002040000ull && view.dimension == TextureDimension::k2DArray && view.baseArray == 636 && view.depthOrLastArray == 636 && view.width == 1024 && view.height == 1024 && view.format == 22, "the title's DCC array view decodes to slice 636 of 637"); + const auto whole = DescribeSurface(view); + Require(whole.guestBytes == 2671771648ull, "the uncut DCC array view covers all 637 slices (" + std::to_string(whole.guestBytes) + " bytes)"); + Require(TextureClearKeys(view, whole.guestBytes) == DccKeys::Uncompressed, "unreadable DCC keys of the uncut view read the texels as stored"); + Require(ViewedArrayWindow(view, false).baseAddress == view.baseAddress, "a DCC view whose keys are not known to read as stored keeps its descriptor"); + const auto window = ViewedArrayWindow(view, true); + const auto cut = DescribeSurface(window); + Require(window.baseAddress == view.baseAddress + whole.GuestLayerOffset(624) && window.dccAddress == 0 && window.baseArray == 12 && window.depthOrLastArray == 12, "the window of a stored-keys DCC view starts at the slice-swizzle boundary below its slice without metadata"); + Require(cut.layers == 13 && cut.layerBytes == whole.layerBytes && window.baseAddress + cut.guestBytes == view.baseAddress + whole.guestBytes, "the window covers slices 624..636 (" + std::to_string(cut.guestBytes) + " bytes)"); + for (std::uint32_t slice = 0; slice < cut.layers; ++slice) Require(window.baseAddress + cut.GuestLayerOffset(slice) == view.baseAddress + whole.GuestLayerOffset(624 + slice), "a window slice lies where the uncut surface keeps it"); + const auto* equation = FindTextureSwizzleEquation(XorSwizzleMode(view.tileMode), 4); + const auto block = ThinBlockLayout(view.tileMode, 4); + Require(equation != nullptr && whole.mips.size() == 1 && !whole.mips[0].tail, "the title's DCC array is one untailed Z_X level"); + const auto texel = [&](const GuestTextureResource& surface, const SurfaceGeometry& geometry, std::uint32_t x, std::uint32_t y, std::uint32_t slice) { + const auto& mip = geometry.mips[0]; + return surface.baseAddress + geometry.GuestLayerOffset(slice) + mip.tiledOffset + (static_cast(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + equationOffset(*equation, x, y, slice); + }; + for (std::uint32_t y = 0; y < 1024; y += 7) { + for (std::uint32_t x = 0; x < 1024; x += 5) Require(texel(window, cut, x, y, window.baseArray) == texel(view, whole, x, y, view.baseArray), "a texel of the viewed slice detiles from the same guest byte in the window as in the uncut surface"); + } + Require(TextureClearKeys(window, cut.guestBytes) == DccKeys::Uncompressed, "the window reads its texels as stored, as the uncut view does"); } { diff --git a/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp b/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp new file mode 100644 index 0000000000..3217dc0eb1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp @@ -0,0 +1,8 @@ +#include "prx/libSceAgcDriver/include/AgcDriver.hpp" +#include "SceTypes.hpp" + +int main() { + if (sceAgcDriverGetCoredumpAddress() != nullptr) return 1; + if (sceAgcDriverSetTargetRingForDiag() != static_cast(0x8A6D0001)) return 2; + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/VectorWindow.cpp b/core/libs/prx/libSceAgcDriver/tests/VectorWindow.cpp index a69e325898..0efed550e8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/VectorWindow.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/VectorWindow.cpp @@ -40,6 +40,13 @@ constexpr std::uint32_t SampleV8V2S0S8 = 0x00400802u; constexpr std::uint32_t AddV8V1V8 = 0x4a101101u; constexpr std::uint32_t BufferStoreDword = 0xe0700000u; constexpr std::uint32_t StoreV8WithS12 = 0x80030800u; +constexpr std::uint32_t AndM0GroupXOne = 0x877c8104u; +constexpr std::uint32_t MovreldV2V0 = 0x7e048500u; +constexpr std::uint32_t MoveS16Two = 0xbe900382u; +constexpr std::uint32_t BranchForwardTwo = 0xbf820002u; +constexpr std::uint32_t SubtractS16One = 0x80908110u; +constexpr std::uint32_t CompareS16NotZero = 0xbf078010u; +constexpr std::uint32_t BranchBackFourOnScc = 0xbf85fffcu; struct Shape { std::size_t functionArrays = 0; @@ -137,6 +144,57 @@ std::size_t WindowStores(std::span words) { return stores; } +constexpr std::array WindowInit{ + 0x34020086, 0x34060088, 0x4a140680, 0x4a160681, 0x4a180682, 0x4a1a0683, 0x4a1c0684, 0x4a1e0685, + 0x4a200686, 0x4a220687, 0x4a240688, 0x4a260689, 0x4a28068a, 0x4a2a068b, 0x4a2c068c, 0x4a2e068d, + 0x4a30068e, 0x4a32068f, 0x4a340690, 0x4a360691, 0x4a380692, 0x4a3a0693, 0x4a3c0694, 0x4a3e0695, + 0x4a400696, 0x4a420697, 0x4a440698, 0x4a460699, 0x4a48069a, 0x4a4a069b, 0x4a4c069c, 0x4a4e069d, + 0x4a50069e, 0x4a52069f, +}; +constexpr std::uint32_t MoveM0BoundedS8 = 0x877c8308u; +constexpr std::uint32_t MoveM0S9 = 0xbefc0309u; +constexpr std::uint32_t MovrelsV4V10 = 0x7e08870au; + +std::vector NarrowingProgram(std::uint32_t unbounded) { + std::vector code(WindowInit.begin(), WindowInit.end()); + code.push_back(MoveM0BoundedS8); + code.push_back(MovrelsV4V10); + code.push_back(0xe0701ffcu); + code.push_back(0x80010401u); + code.push_back(MoveM0S9); + for (std::uint32_t read = 0; read < unbounded; ++read) { + const std::uint32_t destination = 42u + read % 100u; + code.push_back((MovrelsV4V10 & ~(0xffu << 17u)) | (destination << 17u)); + code.push_back(0xe0701000u | ((read * 4u) & 0xfffu)); + code.push_back(0x80010001u | destination << 8u); + } + code.push_back(EndProgram); + return code; +} + +std::vector RecompileNarrowing(std::uint32_t unbounded) { + std::array userData{0u, 0u, 0u, 0u, 0x10000000u, 0u, 0x40u, 0x31016facu, 3u, 5u}; + return RecompileProgram(NarrowingProgram(unbounded), userData); +} + +struct Narrowing { + std::uint32_t functionVariables = 0; + std::uint32_t switchCases = 0; +}; + +Narrowing ScanNarrowing(std::span words) { + Narrowing result; + for (std::size_t cursor = 5; cursor < words.size();) { + const auto count = words[cursor] >> spv::WordCountShift; + const auto op = static_cast(words[cursor] & spv::OpCodeMask); + Require(count != 0 && cursor + count <= words.size(), "truncated SPIR-V instruction"); + if (op == spv::OpVariable && words[cursor + 3] == spv::StorageClassFunction) ++result.functionVariables; + if (op == spv::OpSwitch) result.switchCases += (count - 3u) / 2u; + cursor += count; + } + return result; +} + IrProgram TranslateStage(ShaderStageKind stage, std::uint32_t waveSize, std::uint32_t base = 0u, std::uint32_t fetchStart = 0u, std::uint32_t fetchCount = 0u) { ShaderComputeInputInfo compute{}; compute.threadIdsNum = 3; @@ -306,6 +364,35 @@ int main(int argc, char** argv) { return shape.functionArrays == 0 && shape.accessChains == 0; }); + passed &= run("a sampler dword spilled into a window register with ordinary writes keeps its definition past a relative write", [] { + const std::array userData{}; + const std::array spilled{AndM0GroupXOne, MoveV2Zero, MoveV3Zero, MoveV4One, WritelaneV4, FromS8Lane0, MovreldV2V0, ReadlaneS8, FromV4Lane0, ImageSampleLz2d, SampleV8V2S0S8, BufferStoreDword, StoreV8WithS12, EndProgram}; + return Scan(RecompileProgram(spilled, userData)).functionArrays == 1; + }); + + passed &= run("a sampler dword spilled into a window register and overwritten by an ordinary write before its v_readlane stays a run-time value", [] { + const std::array userData{}; + const std::array overwritten{AndM0GroupXOne, MoveV2Zero, MoveV3Zero, WritelaneV4, FromS8Lane0, MovreldV2V0, MoveV4One, ReadlaneS8, FromV4Lane0, ImageSampleLz2d, SampleV8V2S0S8, BufferStoreDword, StoreV8WithS12, EndProgram}; + try { + RecompileProgram(overwritten, userData); + } catch (const std::exception& error) { + return std::string_view(error.what()).find("GetSamplerResource dword 0 is not a valid runtime value; chain: ReadLane(LoadVectorWindow(Immediate), Immediate)") != std::string_view::npos; + } + return false; + }); + + passed &= run("a sampler dword spilled into a window register before a loop entered at its condition keeps its definition past the loop's relative write", [] { + const std::array userData{}; + const std::array code{AndM0GroupXOne, MoveV2Zero, MoveV3Zero, WritelaneV4, FromS8Lane0, MoveS16Two, BranchForwardTwo, MovreldV2V0, SubtractS16One, CompareS16NotZero, BranchBackFourOnScc, ReadlaneS8, FromV4Lane0, ImageSampleLz2d, SampleV8V2S0S8, BufferStoreDword, StoreV8WithS12, EndProgram}; + return Scan(RecompileProgram(code, userData)).functionArrays == 1; + }); + + passed &= run("a sampler dword spilled into a window register with ordinary writes before a loop entered at its condition keeps its definition past the loop's relative write", [] { + const std::array userData{}; + const std::array code{AndM0GroupXOne, MoveV2Zero, MoveV3Zero, MoveV4One, WritelaneV4, FromS8Lane0, MoveS16Two, BranchForwardTwo, MovreldV2V0, SubtractS16One, CompareS16NotZero, BranchBackFourOnScc, ReadlaneS8, FromV4Lane0, ImageSampleLz2d, SampleV8V2S0S8, BufferStoreDword, StoreV8WithS12, EndProgram}; + return Scan(RecompileProgram(code, userData)).functionArrays == 1; + }); + passed &= run("a sampler dword spilled into a relatively read register keeps its definition through v_readlane", [] { const std::array code{MoveM0GroupX, MoveV2Zero, MoveV3Zero, WritelaneV4, FromS8Lane0, MovrelsV1V4, ReadlaneS8, FromV4Lane0, ImageSampleLz2d, SampleV8V2S0S8, AddV8V1V8, BufferStoreDword, StoreV8WithS12, EndProgram}; const std::array userData{}; @@ -322,6 +409,19 @@ int main(int argc, char** argv) { return twice == single && single >= 3u; }); + passed &= run("a window with a few unproven relative reads keeps its unindexed slots out of the array", [] { + const auto few = ScanNarrowing(RecompileNarrowing(2)); + if (few.functionVariables <= 8u || few.switchCases == 0u) std::fprintf(stderr, " two unproven reads: %u function variables, %u switch cases\n", few.functionVariables, few.switchCases); + return few.functionVariables > 8u && few.switchCases != 0u; + }); + + passed &= run("a window with hundreds of unproven relative reads does not grow a switch for each of them", [] { + const auto many = ScanNarrowing(RecompileNarrowing(300)); + const auto few = ScanNarrowing(RecompileNarrowing(2)); + if (many.switchCases > 4096u || many.functionVariables >= few.functionVariables) std::fprintf(stderr, " 300 unproven reads: %u function variables, %u switch cases (2 reads: %u, %u)\n", many.functionVariables, many.switchCases, few.functionVariables, few.switchCases); + return many.switchCases <= 4096u && many.functionVariables < few.functionVariables; + }); + if (passed) std::puts("Vector window shape and SSA control tests passed"); return passed ? 0 : 1; } diff --git a/core/libs/prx/libSceAgcDriver/tests/VertexInput.cpp b/core/libs/prx/libSceAgcDriver/tests/VertexInput.cpp index f0f999a04c..48d26f71df 100644 --- a/core/libs/prx/libSceAgcDriver/tests/VertexInput.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/VertexInput.cpp @@ -58,8 +58,36 @@ template void fails(TAction&& fn) { Require(failed, "invalid vertex input was accepted"); } +void descriptorDiagnostics(const Context& ctx) { + auto input = attribute(7); + input.resource.fields = {0x89abcde0u, 0x0010abcdu, 0x12345678u, 0x0104dfacu}; + Require(BuildVertexInputLayout(ctx, std::span(&input, 1)).attributes.size() == 1, "valid diagnostic control was rejected"); + struct Case { + unsigned field; + std::uint32_t bits; + const char* words; + }; + constexpr std::array cases{ + Case{1, 0x80000000u, "V#=[89abcde0 8010abcd 12345678 0104dfac]"}, + Case{3, 0x00800000u, "V#=[89abcde0 0010abcd 12345678 0184dfac]"}, + Case{3, 0x40000000u, "V#=[89abcde0 0010abcd 12345678 4104dfac]"}, + Case{3, 0x80000000u, "V#=[89abcde0 0010abcd 12345678 8104dfac]"}, + Case{3, 0xc0800000u, "V#=[89abcde0 0010abcd 12345678 c184dfac]"} + }; + for (const auto& value : cases) { + auto described = input; + described.resource.fields[value.field] |= value.bits; + const auto before = described.resource.fields; + const std::string message(VertexDescriptorFlagsMessage(described).data()); + Require(message.find("unsupported vertex buffer descriptor flags at location 7") != std::string::npos && message.find(value.words) != std::string::npos, + "vertex flag message did not identify the attribute and all four descriptor words: " + message); + Require(described.resource.fields == before, "formatting changed the descriptor"); + } +} + void run() { auto ctx = context(); + descriptorDiagnostics(ctx); VertexInputCache cache(ctx, 2); std::array attributes{attribute(0), attribute(1)}; auto first = get(cache, ctx, 1, attributes); @@ -151,10 +179,25 @@ void run() { const auto zeroStride = live.TryGet(101, input); Require(VertexFetchOutOfRange(input[0]), "zero records did not select zero fill"); input[0].resource.fields[2] = 64; - Require(live.TryGet(101, input) == zeroStride && !VertexFetchOutOfRange(input[0]) && VertexBufferExtent(input[0]) == 64, "cached zero-stride layout froze the live zero-fill decision"); + Require(live.TryGet(101, input) == zeroStride && !VertexFetchOutOfRange(input[0]) && VertexBufferExtent(input[0]) == 16, "cached zero-stride layout froze the live zero-fill decision"); input[0].resource.fields[2] = 0; Require(live.TryGet(101, input) == zeroStride && VertexFetchOutOfRange(input[0]), "record count zero reused a live fetch"); input[0] = attribute(); + input[0].resource.fields[1] = 0; + input[0].resource.fields[3] |= 3u << 28u; + input[0].resource.fields[2] = 16; + const auto wholeFetch = live.TryGet(105, input); + for (const auto records : {0u, 8u, 15u}) { + input[0].resource.fields[2] = records; + Require(live.TryGet(105, input) == wholeFetch && VertexFetchOutOfRange(input[0]), "a fetch that only partly fits was not read as zero"); + } + for (const auto records : {16u, 17u, 0xffffffffu}) { + input[0].resource.fields[2] = records; + Require(live.TryGet(105, input) == wholeFetch && !VertexFetchOutOfRange(input[0]) && VertexBufferExtent(input[0]) == 16, "a fetch inside the byte range was read as zero or sized by NUM_RECORDS"); + } + input[0].resource.fields[3] &= ~(3u << 28u); + Require(VertexFetchOutOfRange(input[0]), "OOB_SELECT 0 read a zero-stride fetch"); + input[0] = attribute(); input[0].formatComponents = 1; const auto narrow = live.TryGet(102, input); input[0].formatComponents = 2; diff --git a/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp b/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp index 700455ddd0..ddc92eaab0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp @@ -12,6 +12,10 @@ #include #include #endif +#ifdef __GLIBC__ +#include +#endif +#include #include #include #include @@ -32,6 +36,10 @@ constexpr std::size_t Block = 65536; #ifdef _WIN32 constexpr wchar_t HostHoleMode[] = L"--host-hole"; constexpr wchar_t ResidentRemapMode[] = L"--resident-direct-remap"; +constexpr wchar_t RewatchImportPagesMode[] = L"--rewatch-import-pages"; +constexpr wchar_t RewatchedSharedMode[] = L"--rewatched-direct-shared"; +constexpr wchar_t ImportAliasSharedMode[] = L"--import-alias-shared"; +constexpr wchar_t SharedGenerationsMode[] = L"--compared-shared-generations"; #endif void Require(bool condition, const std::string& message) { @@ -171,6 +179,21 @@ void CheckSharedBlock() { if (!Watched(outside, unwatched.size())) Require(StoredOver(outside, unwatched.size(), TrackerGeneration()), "an unwatched range reads as not stored over"); } +void CheckStoredOverUsesPageEvidence() { + constexpr std::size_t Page = 4096; + auto* memory = static_cast(AllocateWatched(2 * Block)); + const auto base = reinterpret_cast(memory); + std::memset(memory, 0x11, 2 * Block); + const auto before = CollectWritesUncached(base, 2 * Block); + Require(before != 0, "the stored-over page fixture was not collected"); + + static_cast(memory)[0] = 0x22; + const auto after = CollectWritesUncached(base, Block); + Require(after > before, "the page-local CPU write was not collected"); + Require(StoredOver(base, Page, before), "a collected CPU write on a page was not reported as stored over"); + Require(!StoredOver(base + Page, Page, before), "a page-local CPU write made an untouched page in the same block count as stored over"); +} + void CheckStoredAllSince() { constexpr std::size_t bytes = 4 * Block; auto* memory = static_cast(AllocateWatched(bytes)); @@ -214,6 +237,39 @@ void CheckStoredAllSince() { MarkWritten(base, 64); Require(StoredAllSince_nid_no_patch(base, 64, before), "a complete non-block-aligned range store is absent"); + before = TrackerGeneration(); + MarkPossiblyWritten(base + 2048, 16); + Require(StoredOver(base + 2048, 16, before), "a possible driver write did not invalidate its exact range"); + Require(!StoredOver(base + 2064, 16, before), "a possible driver write invalidated an adjacent range"); + Require(!UnchangedSince(base + 2048, 16, before), "a possible driver write left its exact range unchanged"); + Require(UnchangedSince(base + 2064, 16, before), "a possible driver write changed an adjacent range"); + Require(!StoredAllSince_nid_no_patch(base + 2048, 16, before), "a possible driver write proved complete byte coverage"); + for (unsigned repeat = 0; repeat < 6; ++repeat) MarkPossiblyWritten(base + 2048, 16); + Require(StoredOver(base + 2048, 16, before), "repeated possible writes lost their overlap evidence"); + Require(!StoredOver(base + 2064, 16, before), "repeated possible writes invalidated an adjacent range"); + Require(!StoredAllSince_nid_no_patch(base + 2048, 16, before), "repeated possible writes proved complete byte coverage"); + + before = TrackerGeneration(); + constexpr unsigned PossibleIntervals = 8; + auto beforeNewest = before; + for (unsigned interval = 0; interval < PossibleIntervals; ++interval) { + beforeNewest = TrackerGeneration(); + MarkPossiblyWritten(base + 4096 + static_cast(interval) * 32, 16); + } + Require(StoredOver(base + 4096, 16, before), "an evicted possible-write interval was forgotten instead of marking its block"); + Require(StoredOver(base + 4096 + 16, 16, before), "an evicted possible write did not mark the rest of its block"); + Require(StoredOver(base + 4096 + (PossibleIntervals - 1) * 32, 16, beforeNewest), "the newest possible-write interval was evicted"); + const auto beforeRecord = TrackerGeneration(); + MarkPossiblyWritten(base + 4096, 16); + Require(StoredOver(base + 4096, 16, beforeRecord), "a possible-write interval was not retained after being re-recorded"); + Require(!StoredAllSince_nid_no_patch(base + 4096, 16, before), "possible-write eviction or re-recording proved complete byte coverage"); + + before = TrackerGeneration(); + MarkWritten(base + 2560, 16); + MarkPossiblyWritten(base, Block); + Require(StoredAllSince_nid_no_patch(base + 2560, 16, before), "a possible whole-block range erased an exact partial-store proof"); + Require(!StoredAllSince_nid_no_patch(base, Block, before), "a possible whole-block range proved complete block coverage"); + before = TrackerGeneration(); MarkWritten(base + 512, 16); MarkWritten(base + 544, 16); @@ -345,6 +401,42 @@ void CheckSharedBlockProofs() { Require(!UnchangedSince(base + Block + 14 * page + 0x800, 16, beforeNeighbour), "a range in the page an own store dirtied reads as unchanged"); } +void CheckRepeatedPossibleStores() { + void* memory = AllocateWatched(3 * Block); + const auto base = reinterpret_cast(memory); + const auto boundary = base + 2 * Block - 0x9000; + std::memset(memory, 0x11, 3 * Block); + + const auto synced = CollectWrites(base, 3 * Block); + Require(synced != 0, "the test block is not collected"); + + const auto exact = TrackerGeneration(); + MarkWritten(base + Block, 0x100); + for (std::uint64_t store = 0; store < 5; ++store) MarkPossiblyWritten(boundary + store * 0x1000, 0x100); + Require(StoredAllSince_nid_no_patch(base + Block, 0x100, exact), "possible writes beyond the ring forgot an exact store's coverage"); + + void* fresh = AllocateWatched(2 * Block); + const auto freshBase = reinterpret_cast(fresh); + std::memset(fresh, 0x11, 2 * Block); + const auto freshSynced = CollectWrites(freshBase, 2 * Block); + const auto neighbour = freshBase + Block - 0x9000; + MarkPossiblyWritten(neighbour, 0x100); + const auto secondStored = MarkPossiblyWritten(neighbour + 0x1000, 0x100); + const auto thirdStored = MarkPossiblyWritten(neighbour + 0x2000, 0x100); + MarkPossiblyWritten(neighbour + 0x3000, 0x100); + const auto settled = TrackerGeneration(); + MarkPossiblyWritten(neighbour, 0x100); + MarkPossiblyWritten(neighbour, 0x100); + MarkPossiblyWritten(neighbour + 0x5000, 0x100); + Require(!StoredOver(freshBase, static_cast(neighbour - freshBase), secondStored), "a full possible ring evicted a newer piece instead of its oldest"); + Require(StoredOver(freshBase, static_cast(neighbour - freshBase), freshSynced), "an evicted possible write is not taken as a store over the rest of the block"); + + const auto partial = TrackerGeneration(); + MarkPossiblyWritten(neighbour + 0x5080, 0x100); + Require(StoredOver(neighbour + 0x5000, 0x10, settled) && StoredOver(neighbour + 0x5100, 0x10, partial), "a possible write that overlaps an earlier piece without containing it replaced it"); + Require(!StoredOver(freshBase, static_cast(neighbour - freshBase), thirdStored), "a possible write that overlaps without containing evicted a newer piece instead of the oldest"); +} + void CheckOwnStore() { void* memory = AllocateWatched(2 * Block); const auto base = reinterpret_cast(memory); @@ -621,13 +713,18 @@ void CheckRewatch() { const auto unwatched = UnwatchSerial(); Require(!Watched(imported, Block) && UntrackedHint(imported, Block) != 0, "the unwatched block does not read as untracked"); bytes[Block + 8] = 0x22; + const std::array, 1> unaligned{{{imported + 1, imported + Block}}}; + Rewatch(unaligned); + Require(!Watched(imported + 4096, Block - 4096) && UnwatchSerial() == unwatched, "a range that is not whole pages returned to the write watch"); const std::array, 1> partial{{{imported + 4096, imported + Block}}}; Rewatch(partial); - Require(!Watched(imported, Block) && UnwatchSerial() == unwatched, "part of a block returned to the write watch"); + Require(Watched(imported + 4096, Block - 4096) && !Watched(imported, 4096) && !Watched(imported, Block), "rewatching part of a block did not return exactly its pages to the write watch"); + const auto partly = UnwatchSerial(); + Require(partly != unwatched, "rewatching part of a block did not invalidate what was cached while it was unwatched"); const std::array, 1> first{{{imported, imported + Block}}}; Rewatch(first); Require(Watched(imported, Block) && !Watched(imported + Block, Block), "rewatching did not return exactly its block to the write watch"); - Require(UnwatchSerial() != unwatched, "rewatching did not invalidate what was cached while the block was unwatched"); + Require(UnwatchSerial() != partly, "rewatching did not invalidate what was cached while the block was unwatched"); Require(UntrackedHint(imported, Block) == 0, "a rewatched block still reads as untracked"); Require(!UnchangedSince(imported, Block, before) && StoredOver(imported, Block, before), "a block returned to the write watch reads as unchanged since before it left"); Require(!UnchangedSinceCollected(imported, Block, before), "a CPU store made while the block was unwatched reads as no CPU store"); @@ -649,6 +746,362 @@ void CheckRewatch() { GuestArena::GuestArenaRelease_nid_postfix(memory, 4 * Block); } +using RewatchSpan = std::array, 1>; + +RewatchSpan BlockSpan(std::uint64_t address, std::size_t bytes) { + return {{{address & ~std::uint64_t{Block - 1}, (address + bytes + Block - 1) & ~std::uint64_t{Block - 1}}}}; +} + +RewatchSpan PageSpan(std::uint64_t address, std::size_t bytes) { + return {{{address & ~std::uint64_t{4095}, (address + bytes + 4095) & ~std::uint64_t{4095}}}}; +} + +void PrintPageState(const char* page, const void* address) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "cannot query a page next to an import"); + std::cout << page << ": State 0x" << std::hex << info.State << " Type 0x" << info.Type << " Protect 0x" << info.Protect << std::dec << '\n'; +} + +struct ImportFixture { + std::uint8_t* memory; + std::size_t reserved; + ImportFixture(std::size_t reservedBytes, std::size_t committed) : memory(static_cast(GuestArena::GuestArenaAllocate_nid_postfix(reservedBytes, Block))), reserved(reservedBytes) { + Require(memory != nullptr, "cannot reserve an import fixture"); + GuestArena::GuestArenaCommit_nid_postfix(memory, committed, PAGE_READWRITE, committed); + std::memset(memory, 0x11, committed); + BumpCollectEpoch(); + Require(CollectWrites(Base(), committed) != 0, "an import fixture was not collected"); + } + ImportFixture(const ImportFixture&) = delete; + ImportFixture& operator=(const ImportFixture&) = delete; + ~ImportFixture() { + GuestArena::GuestArenaReset_nid_postfix(memory, reserved); + GuestArena::GuestArenaRelease_nid_postfix(memory, reserved); + } + std::uint64_t Base() const { + return reinterpret_cast(memory); + } +}; + +void UnwatchImport(std::uint64_t address, std::size_t bytes) { + Require(ImportWatched(address, bytes, [] { return true; }, false) && !Watched(address, bytes), "an import that leaves the watch stayed watched"); +} + +void RequireRewatchedStores(std::uint64_t address, std::size_t bytes) { + BumpCollectEpoch(); + const auto collected = CollectWrites(address, bytes); + Require(collected != 0 && UnchangedSince(address, bytes, collected), "a rewatched import is not collected"); + *reinterpret_cast(address + bytes - 1) = 0x22; + CollectWritesUncached(address, bytes); + Require(!UnchangedSince(address + bytes - 1, 1, collected), "a CPU store into a rewatched import's last page was missed"); +} + +void CheckRewatchWholeBlockImport() { + constexpr std::size_t bytes = 4 * Block; + ImportFixture fixture(6 * Block, 6 * Block); + const auto import = fixture.Base() + Block; + const auto before = CollectWrites(fixture.Base(), 6 * Block); + UnwatchImport(import, bytes); + Require(PageSpan(import, bytes) == BlockSpan(import, bytes), "a whole-block import has a page span unlike its block span"); + Rewatch(PageSpan(import, bytes)); + Require(Watched(import, bytes), "a settled whole-block import did not return to the write watch"); + Require(!UnchangedSince(import, bytes, before) && UnchangedSince(fixture.Base(), Block, before) && UnchangedSince(import + bytes, Block, before), "rewatching a whole-block import did not stamp exactly its blocks"); + RequireRewatchedStores(import, bytes); +} + +void CheckRewatchImportTail() { + constexpr std::size_t bytes = 0xc4000; + ImportFixture fixture(13 * Block, bytes); + const auto import = fixture.Base(); + UnwatchImport(import, bytes); + PrintPageState("page past a 0xc4000-byte import", fixture.memory + bytes); + const auto unwatched = UnwatchSerial(); + Rewatch(BlockSpan(import, bytes)); + Require(!Watched(import, bytes) && UnwatchSerial() == unwatched, "the 64 KiB span past the import's committed pages returned to the write watch"); + Rewatch(PageSpan(import, bytes)); + Require(Watched(import, bytes), "a settled import whose size is not a whole block did not return to the write watch"); + Require(Watched(import + 12 * Block, bytes - 12 * Block) && !Watched(import, bytes + 1) && !Watched(import + bytes - 1, 2) && !Watched(import + bytes, 4096), "rewatching an import ending inside a block did not return exactly its pages"); + Require(UnwatchSerial() != unwatched && UntrackedHint(import, bytes) == 0, "a rewatched import still reads as unwatched"); + RequireRewatchedStores(import, bytes); +} + +void CheckRewatchUnalignedImport() { + constexpr std::size_t offset = 0x1000; + constexpr std::size_t bytes = 0xc4000; + constexpr std::size_t committed = 0xc8000; + ImportFixture fixture(13 * Block, committed); + const auto import = fixture.Base() + offset; + UnwatchImport(import, bytes); + PrintPageState("page past an import at block offset 0x1000", fixture.memory + committed); + Rewatch(BlockSpan(import, bytes)); + Require(!Watched(import, bytes), "the 64 KiB span past the committed pages around an import returned to the write watch"); + Rewatch(PageSpan(import, bytes)); + Require(Watched(import, bytes), "a settled import that does not start on a block did not return to the write watch"); + Require(!Watched(fixture.Base(), offset) && !Watched(import + bytes, 4096), "rewatching an import returned pages outside it"); + RequireRewatchedStores(import, bytes); +} + +void CheckRewatchImportBesideUnwatchedImport() { + constexpr std::size_t bytes = 0xc4000; + constexpr std::size_t otherBytes = 0x1c000; + ImportFixture fixture(14 * Block, 14 * Block); + const auto import = fixture.Base(); + const auto other = import + bytes; + UnwatchImport(import, bytes); + UnwatchImport(other, otherBytes); + Rewatch(PageSpan(import, bytes)); + Require(Watched(import, bytes), "an import sharing its last block with another unwatched import did not return to the write watch"); + Require(!Watched(other, 4096) && !Watched(import + 13 * Block - 4096, 4096) && !Watched(other + otherBytes - 4096, 4096), "rewatching an import returned another unwatched import's pages in its last block"); + Require(UntrackedHint(other, otherBytes) != 0, "an unwatched import beside a rewatched one does not read as untracked"); + RequireRewatchedStores(import, bytes); + Rewatch(PageSpan(other, otherBytes)); + Require(Watched(import, bytes + otherBytes), "rewatching the second import did not return both to the write watch"); +} + +void CheckRewatchImportPages() { + CheckRewatchWholeBlockImport(); + CheckRewatchImportTail(); + CheckRewatchUnalignedImport(); + CheckRewatchImportBesideUnwatchedImport(); +} + +void CheckRewatchedDirectMemoryComparedWhenShared() { + constexpr std::size_t bytes = 2 * Block; + auto* memory = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(2 * bytes, Block)); + Require(memory != nullptr, "cannot reserve the compared sharing fixture"); + struct Cleanup { + std::uint8_t* memory; + HANDLE section = nullptr; + ~Cleanup() { + GuestArena::GuestArenaReset_nid_postfix(memory, 2 * bytes); + GuestArena::GuestArenaRelease_nid_postfix(memory, 2 * bytes); + if (section == nullptr) return; + GuestArena::GuestArenaForgetSection_nid_postfix(section); + CloseHandle(section); + } + } cleanup{memory}; + cleanup.section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, 0, bytes, nullptr); + Require(cleanup.section != nullptr, "cannot create the compared sharing backing"); + const auto base = reinterpret_cast(memory); + const auto native = [](const std::uint8_t* address) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "cannot query the compared sharing fixture"); + return info; + }; + GuestArena::GuestArenaMapDirect_nid_postfix(memory, bytes, cleanup.section, 0, PAGE_READWRITE); + Require(native(memory).Type == MEM_PRIVATE && Watched(base, bytes), "the direct memory is not resident and watched"); + Unwatch(base, Block); + const std::array, 1> imported{{{base, base + Block}}}; + Rewatch(imported); + Require(Watched(base, Block), "the rewatched direct memory is not watched"); + BumpCollectEpoch(); + Require(CollectWritesUncached(base, Block) != 0, "rewatched resident direct memory is not collected"); + GuestArena::GuestArenaMapDirect_nid_postfix(memory + bytes, bytes, cleanup.section, 0, PAGE_READWRITE); + Require(native(memory).Type == MEM_MAPPED && native(memory + bytes).Type == MEM_MAPPED, "a second mapping kept the direct memory resident"); + BumpCollectEpoch(); + Require(CollectWritesUncached(base, Block) == 0 && CollectWritesUncached(base + bytes, Block) == 0, "rewatched direct memory was collected once shared"); + Require(native(memory).Protect == PAGE_READWRITE && native(memory + bytes).Protect == PAGE_READWRITE, "rewatched direct memory was armed once shared"); + memory[8] = 0x5a; + Require(memory[bytes + 8] == 0x5a, "the second mapping does not see a store into the first"); + Require(CollectWritesUncached(base + Block, Block) != 0, "shared direct memory that never left the watch stopped being collected"); + std::cout << "rewatched direct memory: compared once shared, not armed; other shared direct memory still collected\n"; +} + +struct ComparedSharing { + static constexpr std::size_t bytes = 2 * Block; + std::uint8_t* memory = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(2 * bytes, Block)); + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, 0, bytes, nullptr); + std::uint64_t base = reinterpret_cast(memory); + + ComparedSharing() { + Require(memory != nullptr && section != nullptr, "cannot create the compared sharing generation fixture"); + GuestArena::GuestArenaForgetSection_nid_postfix(section); + GuestArena::GuestArenaMapDirect_nid_postfix(memory, bytes, section, 0, PAGE_READWRITE); + Require(Watched(base, bytes), "the compared sharing generation fixture is not watched"); + Unwatch(base, Block); + const std::array, 1> imported{{{base, base + Block}}}; + Rewatch(imported); + Require(Watched(base, Block), "the rewatched direct memory of the generation fixture is not watched"); + BumpCollectEpoch(); + Require(CollectWritesUncached(base, Block) != 0, "rewatched resident direct memory of the generation fixture is not collected"); + } + ~ComparedSharing() { + GuestArena::GuestArenaReset_nid_postfix(memory, 2 * bytes); + GuestArena::GuestArenaRelease_nid_postfix(memory, 2 * bytes); + GuestArena::GuestArenaForgetSection_nid_postfix(section); + CloseHandle(section); + } + ComparedSharing(const ComparedSharing&) = delete; + ComparedSharing& operator=(const ComparedSharing&) = delete; + + void Share() { + GuestArena::GuestArenaMapDirect_nid_postfix(memory + bytes, bytes, section, 0, PAGE_READWRITE); + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(memory, &info, sizeof(info)) == sizeof(info) && info.Type == MEM_MAPPED, "a second mapping kept the generation fixture resident"); + } + std::uint64_t Collect(std::uint64_t address) { + BumpCollectEpoch(); + return CollectWrites(address, Block); + } +}; + +void CheckComparedSharingGenerations() { + { + ComparedSharing fixture; + const auto g0 = TrackerGeneration(); + Require(UnchangedSince(fixture.base, Block, g0) && !WrittenSince(fixture.base, Block, g0), "rewatched resident direct memory reads as written before any store"); + fixture.memory[16] = 0x21; + Require(fixture.Collect(fixture.base) != 0 && !UnchangedSince(fixture.base, Block, g0) && WrittenSince(fixture.base, Block, g0), "a store into rewatched resident direct memory was missed before it was shared"); + const auto cached = TrackerGeneration(); + Require(UnchangedSince(fixture.base, Block, cached), "rewatched resident direct memory reads as changed after its collect"); + fixture.Share(); + fixture.memory[8] = 0x5a; + Require(fixture.memory[ComparedSharing::bytes + 8] == 0x5a, "the second mapping does not see the store into the generation fixture"); + Require(fixture.Collect(fixture.base) == 0, "rewatched direct memory was collected once shared"); + Require(!UnchangedSince(fixture.base, Block, cached), "a store into rewatched direct memory once shared reads as unchanged since a generation before the sharing"); + Require(WrittenSince(fixture.base, Block, cached), "a store into rewatched direct memory once shared reads as not written since a generation before the sharing"); + } + { + ComparedSharing fixture; + const auto g0 = TrackerGeneration(); + fixture.Share(); + Require(fixture.Collect(fixture.base) == 0, "rewatched direct memory was collected once shared, before any store"); + Require(!UnchangedSince(fixture.base, Block, g0) && WrittenSince(fixture.base, Block, g0), "sharing rewatched direct memory did not report it written once"); + const auto g1 = TrackerGeneration(); + Require(fixture.Collect(fixture.base) == 0 && fixture.Collect(fixture.base) == 0, "rewatched direct memory was collected again once shared"); + std::array generations{g1}; + std::array changed{}; + std::array cpu{}; + Require(!WrittenSince(fixture.base, Block, g1) && ChangedBlocks(fixture.base, Block, generations, changed, cpu) && changed[0] == BlockUnchanged && cpu[0] == 0, "refused collects of shared compared memory reported it written again without a store"); + Require(!UnchangedSince(fixture.base, Block, g1), "shared compared memory read as unchanged since a generation taken after a refused collect"); + const auto neighbour = fixture.Collect(fixture.base + Block); + const auto spanned = std::max(fixture.Collect(fixture.base), neighbour); + Require(spanned != 0 && UnchangedSince(fixture.base + Block, Block, spanned), "shared direct memory that never left the watch is not collected beside compared memory"); + fixture.memory[24] = 0x3c; + Require(fixture.Collect(fixture.base) == 0, "rewatched direct memory was collected once shared, after a store"); + Require(!UnchangedSince(fixture.base, Block, g1), "a store into shared compared memory reads as unchanged since a generation taken after a refused collect"); + Require(!UnchangedSince(fixture.base, Block, spanned), "a store into shared compared memory reads as unchanged since the newest generation of a collect over several ranges"); + Require(fixture.Collect(fixture.base + Block) != 0 && UnchangedSince(fixture.base + Block, Block, spanned), "a refused collect of compared memory made its never-compared neighbour read as changed"); + const std::array queries{{{fixture.base + Block, Block, spanned}, {fixture.base, Block, spanned}}}; + Require(!UnchangedSinceAll(queries), "a batch of queries over shared compared memory read as unchanged"); + const std::array, 1> committed{{{fixture.base, fixture.base + Block}}}; + Require(!UnchangedCommittedSince(committed, spanned), "committed ranges over shared compared memory read as unchanged"); + } + std::cout << "shared compared memory: reported written once at the sharing, never unchanged since an older generation, its neighbour still trusted\n"; +} + +void CheckImportedDirectMemoryComparedWhenShared() { + constexpr std::size_t bytes = 2 * Block; + auto* memory = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(2 * bytes, Block)); + Require(memory != nullptr, "cannot reserve the imported sharing fixture"); + struct Cleanup { + std::uint8_t* memory; + HANDLE section = nullptr; + ~Cleanup() { + GuestArena::GuestArenaReset_nid_postfix(memory, 2 * bytes); + GuestArena::GuestArenaRelease_nid_postfix(memory, 2 * bytes); + if (section == nullptr) return; + GuestArena::GuestArenaForgetSection_nid_postfix(section); + CloseHandle(section); + } + } cleanup{memory}; + cleanup.section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, 0, bytes, nullptr); + Require(cleanup.section != nullptr, "cannot create the imported sharing backing"); + const auto base = reinterpret_cast(memory); + const auto native = [](const std::uint8_t* address) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "cannot query the imported sharing fixture"); + return info; + }; + GuestArena::GuestArenaMapDirect_nid_postfix(memory, bytes, cleanup.section, 0, PAGE_READWRITE); + Require(native(memory).Type == MEM_PRIVATE && Watched(base, bytes), "the imported direct memory is not resident and watched"); + Require(ImportWatched(base, Block, [] { return true; }), "the watched import of resident direct memory failed"); + Require(Watched(base, Block), "a watched import of resident direct memory left the write watch"); + BumpCollectEpoch(); + Require(CollectWritesUncached(base, Block) != 0, "imported resident direct memory is not collected"); + GuestArena::GuestArenaMapDirect_nid_postfix(memory + bytes, bytes, cleanup.section, 0, PAGE_READWRITE); + Require(native(memory).Type == MEM_MAPPED && native(memory + bytes).Type == MEM_MAPPED, "a second mapping kept the imported direct memory resident"); + BumpCollectEpoch(); + Require(CollectWritesUncached(base, Block) == 0 && CollectWritesUncached(base + bytes, Block) == 0, "imported direct memory was collected once shared"); + Require(native(memory).Protect == PAGE_READWRITE && native(memory + bytes).Protect == PAGE_READWRITE, "imported direct memory was armed once shared"); + memory[8] = 0x5a; + Require(memory[bytes + 8] == 0x5a, "the second mapping does not see a store into the imported first"); + Require(CollectWritesUncached(base + Block, Block) != 0, "shared direct memory that was never imported stopped being collected"); + std::cout << "imported direct memory: compared once shared, not armed; unimported shared direct memory still collected\n"; +} + +void CheckImportAliasDemotedComparedWhenShared() { + constexpr std::size_t page = 0x4000; + auto* memory = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(5 * Block, Block)); + Require(memory != nullptr, "cannot reserve the import alias fixture"); + struct Cleanup { + std::uint8_t* memory; + std::array sections{}; + std::array aliases{}; + ~Cleanup() { + for (const auto alias : aliases) GuestArena::GuestArenaUnmapAlias_nid_postfix(alias); + GuestArena::GuestArenaReset_nid_postfix(memory, 5 * Block); + GuestArena::GuestArenaRelease_nid_postfix(memory, 5 * Block); + for (const auto section : sections) { + if (section == nullptr) continue; + GuestArena::GuestArenaForgetSection_nid_postfix(section); + CloseHandle(section); + } + } + } cleanup{memory}; + for (auto& section : cleanup.sections) { + section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, 0, Block, nullptr); + Require(section != nullptr, "cannot create the import alias backing"); + GuestArena::GuestArenaForgetSection_nid_postfix(section); + } + const auto base = reinterpret_cast(memory); + const auto native = [](const std::uint8_t* address) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "cannot query the import alias fixture"); + return info; + }; + const auto clearingCollect = [](std::uint64_t address) { + std::array pages{}; + std::size_t count = pages.size(); + return GuestArena::GuestArenaCollectWrites_nid_postfix(static_cast(address), page, pages.data(), &count, true); + }; + GuestArena::GuestArenaMapDirect_nid_postfix(memory, page, cleanup.sections[0], 0, PAGE_READWRITE); + Require(native(memory).Type == MEM_PRIVATE && Watched(base, page), "the aliased direct memory is not resident and watched"); + BumpCollectEpoch(); + Require(CollectWritesUncached(base, page) != 0, "resident direct memory is not collected before its import"); + cleanup.aliases[0] = GuestArena::GuestArenaMapAlias_nid_postfix(static_cast(base), page); + Require(cleanup.aliases[0] != nullptr && native(memory).Type == MEM_MAPPED, "the import alias did not demote the resident run"); + Require(ImportWatched(base, page, [] { return true; }) && Watched(base, page), "the import of aliased direct memory did not stay watched"); + Require(native(memory).Protect == PAGE_READWRITE, "memory an import's alias demoted was armed by the import"); + Require(!clearingCollect(base), "a clearing collect over memory an import's alias demoted was not refused"); + Require(native(memory).Protect == PAGE_READWRITE, "memory an import's alias demoted was armed by a clearing collect"); + BumpCollectEpoch(); + Require(CollectWritesUncached(base, page) == 0, "memory an import's alias demoted was collected once shared"); + memory[8] = 0x5a; + Require(static_cast(cleanup.aliases[0])[8] == 0x5a, "the import alias does not see a store into the guest view"); + + GuestArena::GuestArenaMapDirect_nid_postfix(memory + Block, page, cleanup.sections[1], 0, PAGE_READWRITE); + GuestArena::GuestArenaMapDirect_nid_postfix(memory + 2 * Block, page, cleanup.sections[1], 0, PAGE_READWRITE); + Require(native(memory + Block).Type == MEM_MAPPED && native(memory + 2 * Block).Type == MEM_MAPPED, "a second mapping kept the control direct memory resident"); + cleanup.aliases[1] = GuestArena::GuestArenaMapAlias_nid_postfix(static_cast(base + Block), page); + Require(cleanup.aliases[1] != nullptr, "the control import alias was refused"); + Require(ImportWatched(base + Block, page, [] { return true; }) && Watched(base + Block, page), "the control import did not stay watched"); + Require(clearingCollect(base + Block), "memory shared by a second mapping before its import alias stopped being collected"); + Require(native(memory + Block).Protect == PAGE_READONLY && native(memory + 2 * Block).Protect == PAGE_READONLY, "memory shared by a second mapping before its import alias was not armed"); + + GuestArena::GuestArenaMapDirect_nid_postfix(memory + 3 * Block, page, cleanup.sections[2], 0, PAGE_READWRITE); + auto* failed = GuestArena::GuestArenaMapAlias_nid_postfix(static_cast(base + 3 * Block), page); + Require(failed != nullptr && native(memory + 3 * Block).Type == MEM_MAPPED, "the alias of a failed import did not demote the resident run"); + GuestArena::GuestArenaUnmapAlias_nid_postfix(failed); + GuestArena::GuestArenaMapDirect_nid_postfix(memory + 4 * Block, page, cleanup.sections[2], 0, PAGE_READWRITE); + cleanup.aliases[2] = GuestArena::GuestArenaMapAlias_nid_postfix(static_cast(base + 3 * Block), page); + Require(cleanup.aliases[2] != nullptr, "the import alias after a second mapping was refused"); + Require(ImportWatched(base + 3 * Block, page, [] { return true; }) && Watched(base + 3 * Block, page), "the import after a second mapping did not stay watched"); + Require(clearingCollect(base + 3 * Block), "memory a second mapping shared after an earlier alias demoted it stopped being collected"); + Require(native(memory + 3 * Block).Protect == PAGE_READONLY && native(memory + 4 * Block).Protect == PAGE_READONLY, "memory a second mapping shared after an earlier alias demoted it was not armed"); + std::cout << "direct memory an import's alias demoted: compared once shared, not armed; memory shared before the alias still collected\n"; +} + void CheckResidentDirectMappingReuse() { constexpr std::size_t bytes = 5 * Block; auto* memory = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block)); @@ -1053,7 +1506,7 @@ void CheckImportWindow() { bytes[0x100] = 0x11; bytes[Block + 0x2000] = 0x22; return true; - }), "the import window import failed"); + }, true, false), "the import window import failed"); Require(Watched(base, 3 * Block), "an import window left its range unwatched"); const std::array generations{synced, synced, synced}; std::array changed{}; @@ -1153,6 +1606,38 @@ void CheckImportWindowOverUnwatchedBlock() { Require(after != 0 && !UnchangedSince(base, Block, after), "a store after an import window over an unwatched block was missed"); } +void CheckImportWindowFootprint() { +#ifdef __GLIBC__ + constexpr std::size_t bytes = std::size_t{64} << 20; + void* memory = AllocateWatched(bytes); + const auto base = reinterpret_cast(memory); + std::memset(memory, 0x5a, bytes); + Require(CollectWritesUncached(base, bytes) != 0, "the footprint import window block is not collected"); + const auto heapBytes = [] { + const auto info = mallinfo2(); + return info.uordblks + info.hblkhd; + }; + const auto before = heapBytes(); + std::size_t during = 0; + Require(ImportWatched(base, bytes, [&] { + during = heapBytes(); + return true; + }), "the footprint import window import failed"); + Require(Watched(base, bytes), "the footprint import window left its range unwatched"); + const auto held = during > before ? during - before : 0; + Require(held <= bytes / 64, "an import window over " + std::to_string(bytes >> 20) + " MiB held " + std::to_string(held >> 20) + " MiB of host memory during the import: it copies the range instead of hashing its pages"); + auto* plain = static_cast(memory); + const auto synced = CollectWritesUncached(base, bytes); + Require(ImportWatched(base, bytes, [&] { + plain[0x100] = 0x5a; + plain[Block + 0x100] = 0x5b; + return true; + }), "the footprint import window re-import failed"); + Require(UnchangedSince(base, Block, synced), "a page a hashed import window resolved without changing its bytes reads as written"); + Require(WrittenSince(base + Block, Block, synced), "a store of new bytes within a hashed import window was missed"); +#endif +} + void ProtectPage(std::uint64_t address, bool readable) { #ifdef _WIN32 DWORD previous = 0; @@ -1233,6 +1718,80 @@ void CheckUnwatchedImport() { #endif } +struct HookCall { + std::uint64_t address; + std::size_t bytes; + bool watched; +}; + +std::vector hookCalls; +std::uint64_t hookProbe = 0; +std::size_t hookProbeBytes = 0; + +void RecordHook(std::uint64_t address, std::size_t bytes) { + hookCalls.push_back({address, bytes, Watched(hookProbe, hookProbeBytes)}); +} + +struct RecordedHook { + RecordedHook() { + hookCalls.clear(); + SetUnwatchHook(&RecordHook); + } + ~RecordedHook() { SetUnwatchHook(nullptr); } + RecordedHook(const RecordedHook&) = delete; + RecordedHook& operator=(const RecordedHook&) = delete; +}; + +void RequireHookOver(std::uint64_t address, std::size_t bytes, const std::string& path) { + Require(hookCalls.size() == 1, path + " did not run the unwatch hook once"); + const auto& call = hookCalls.front(); + Require(call.address <= address && address + bytes <= call.address + call.bytes, path + " gave the unwatch hook a range that misses its own"); + Require(call.watched, path + " ran the unwatch hook after its range left the write watch"); +} + +void CheckUnwatchHooks() { + void* memory = AllocateWatched(8 * Block); + const auto base = reinterpret_cast(memory); + std::memset(memory, 0x11, 8 * Block); + Require(CollectWritesUncached(base, 8 * Block) != 0, "the hooked block is not collected"); + const RecordedHook recorded; + hookProbe = base; + hookProbeBytes = Block; + Unwatch(base, Block); + RequireHookOver(base, Block, "Unwatch"); + Require(!Watched(base, Block), "Unwatch left its range watched"); + + hookCalls.clear(); + hookProbe = base + Block; + bool hookedBeforeImport = false; + Require(ImportWatched(base + Block, Block, [&] { + hookedBeforeImport = hookCalls.size() == 1; + return true; + }, false), "an import that does not stay watched failed"); + RequireHookOver(base + Block, Block, "an import that does not stay watched"); + Require(hookedBeforeImport, "an import that does not stay watched ran the unwatch hook after the import"); + Require(!Watched(base + Block, Block), "an import that does not stay watched left its range watched"); + + hookCalls.clear(); + Require(ImportWatched(base + 2 * Block, Block, [] { return true; }), "an import that stays watched failed"); + Require(hookCalls.empty() && Watched(base + 2 * Block, Block), "an import whose page walk vouched for its pages ran the unwatch hook or gave up its range"); + +#ifndef _WIN32 + hookCalls.clear(); + const auto late = base + 4 * Block; + hookProbe = late; + bool hookedBeforeLoss = false; + Require(ImportWatched(late, 3 * Block, [&] { + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(reinterpret_cast(late + 2 * Block), Block); + hookedBeforeLoss = !hookCalls.empty(); + return true; + }), "an import whose range lost a block during the import failed"); + RequireHookOver(late, 3 * Block, "an import whose page walk failed after the import"); + Require(!hookedBeforeLoss, "an import whose page walk failed after the import ran the unwatch hook before it"); + Require(!Watched(late, Block), "an import whose page walk failed after the import left its range watched"); +#endif +} + class PartlyCommitted { public: static constexpr std::size_t Bytes = 4 * Block; @@ -1413,10 +1972,29 @@ int main() { CheckResidentDirectMappingReuse(); return 0; } + if (std::wstring_view(GetCommandLineW()).ends_with(RewatchImportPagesMode)) { + CheckRewatchImportPages(); + return 0; + } + if (std::wstring_view(GetCommandLineW()).ends_with(RewatchedSharedMode)) { + CheckRewatchedDirectMemoryComparedWhenShared(); + CheckImportedDirectMemoryComparedWhenShared(); + return 0; + } + if (std::wstring_view(GetCommandLineW()).ends_with(ImportAliasSharedMode)) { + CheckImportAliasDemotedComparedWhenShared(); + return 0; + } + if (std::wstring_view(GetCommandLineW()).ends_with(SharedGenerationsMode)) { + CheckComparedSharingGenerations(); + return 0; + } #endif CheckSharedBlock(); + CheckStoredOverUsesPageEvidence(); CheckStoredAllSince(); CheckSharedBlockProofs(); + CheckRepeatedPossibleStores(); CheckOwnStore(); CheckUnwatch(); CheckNewestStamp(); @@ -1442,7 +2020,9 @@ int main() { CheckImportWindowOtherThread(); CheckImportWindowOverUnwatchedBlock(); CheckStoreAcrossImports(); + CheckImportWindowFootprint(); CheckUnwatchedImport(); + CheckUnwatchHooks(); CheckCommittedRanges(); CheckAdoptLatestEpoch(); } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/WriteWatchCoverage.cpp b/core/libs/prx/libSceAgcDriver/tests/WriteWatchCoverage.cpp index 62e56cfe52..121205b1a2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/WriteWatchCoverage.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/WriteWatchCoverage.cpp @@ -99,6 +99,17 @@ int main() { coverage.Exclude(base + block, block, 43); require(coverage.Restore(base + block - page, 2 * page, 42), "a newer boundary page was not restored"); require(coverage.Covers(base + block - page, page) && !coverage.Covers(base + block, page), "a stale boundary mapping erased a newer neighbouring exclusion"); + constexpr std::size_t tail = 0xc4000; + coverage.Initialize(base, 14 * block); + coverage.Exclude(base, tail, 50); + require(coverage.Restore(base, tail, 51), "an import ending inside a block did not restore its pages"); + require(coverage.Covers(base, tail) && coverage.Covers(base + 12 * block, tail - 12 * block), "an import ending inside a block is not watched"); + require(!coverage.Covers(base, tail + 1) && !coverage.Covers(base + tail - 1, 2) && !coverage.Covers(base + tail, page) && !coverage.Covers(base, 13 * block), "a range past an import ending inside a block is watched"); + require(coverage.Covers(base + tail - page, page) && !coverage.Covers(base + 13 * block - page, page) && coverage.Covers(base + 13 * block, block), "an import ending inside a block changed the pages after it"); + coverage.Initialize(base, 14 * block); + coverage.Exclude(base + page, tail, 60); + require(coverage.Restore(base + page, tail, 61) && coverage.Covers(base + page, tail), "an import starting and ending inside blocks is not watched"); + require(!coverage.Covers(base, page) && !coverage.Covers(base, tail) && !coverage.Covers(base + page + tail, page) && !coverage.Covers(base + page, tail + 1), "an import starting and ending inside blocks watched pages outside it"); std::cout << "write watch coverage passed\n"; return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/AddressPasses.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/AddressPasses.cpp index 9278d67787..bc4e3ffab8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/AddressPasses.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/AddressPasses.cpp @@ -141,6 +141,7 @@ struct Step { const std::uint32_t* pixel; std::uint64_t pointer; std::array descriptor{}; + std::uint64_t color = 0; }; void Submit(const GuestBlock& vertices, const GuestBlock& target, std::initializer_list steps, bool linear = false) { @@ -179,6 +180,10 @@ void Submit(const GuestBlock& vertices, const GuestBlock& target, std::initializ Append(commands, 0x76, {0x008, static_cast(pixel >> 8u), static_cast(pixel >> 40u)}); Append(commands, 0x76, {0x00e, static_cast(step.pointer), static_cast(step.pointer >> 32u)}); Append(commands, 0x76, {0x010, step.descriptor[0], step.descriptor[1], step.descriptor[2], step.descriptor[3]}); + if (step.color != 0) { + Append(commands, 0x69, {0x318, static_cast(step.color >> 8u)}); + Append(commands, 0x69, {0x390, static_cast(step.color >> 40u)}); + } Append(commands, 0x2d, {3, 2}); } Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; @@ -285,6 +290,26 @@ void TargetReadAsWhenSplit(const GuestBlock& vertices, const GuestBlock& target, if (!linear && !split) Expect(after.passesContinued - before.passesContinued == 1, what + ": the reading draw did not continue the rendering draw's pass, so the case does not test it: " + Counts(before, after)); } +void TargetReadSynchronously(const GuestBlock& vertices, const GuestBlock& target, const GuestBlock& unmapped) { + Submit(vertices, target, {{PixelRead.data(), target.At(ColorOffset)}}, true); + std::memset(target.Bytes(ColorOffset), 0, ColorBytes); + std::memset(unmapped.Bytes(ColorOffset), 0, ColorBytes); + const auto before = AddressDraws(); + const auto otherBefore = OtherDraws(); + Submit(vertices, target, {{PixelColorWord.data(), 0}, {PixelRead.data(), target.At(ColorOffset), {}, unmapped.At(ColorOffset)}}, true); + const auto after = AddressDraws(); + const auto otherAfter = OtherDraws(); + const unsigned red = unmapped.Bytes(ColorOffset)[0]; + std::printf("a synchronous draw reading through the table the linear target the previous draw rendered read %s (red byte %u); recorded: %s, %s\n", red == 255u ? "that draw's color" : "the bytes before it", red, Counts(before, after).c_str(), Counts(otherBefore, otherAfter, "other draws").c_str()); + if (otherAfter.draws == otherBefore.draws) { + std::puts("a synchronous draw reading through the table the linear target the previous draw rendered: skipped, that draw was not recorded because the linear target is not resident on this device, so the target was stored right after it"); + return; + } + Expect(otherAfter.draws - otherBefore.draws == 1, "the draw rendering the linear target was not recorded as one draw: " + Counts(otherBefore, otherAfter, "other draws")); + Expect(after.draws == before.draws, "the draw reading through the table into an unregistered target was recorded, so the case does not test a synchronous draw: " + Counts(before, after)); + Expect(red == 255u, "a synchronous draw reading through the table the linear target the previous draw rendered read the bytes before it (red byte " + std::to_string(red) + "), not that draw's color"); +} + } int main(int argc, char** argv) { @@ -306,6 +331,7 @@ int main(int argc, char** argv) { GuestBlock readOnlyVertices; GuestBlock data; GuestBlock target; + GuestBlock unmapped; FillVertices(readOnlyVertices); readOnlyVertices.Map(false); data.Map(true); @@ -320,6 +346,7 @@ int main(int argc, char** argv) { TargetReadAsWhenSplit(readOnlyVertices, target, false); TargetReadAsWhenSplit(readOnlyVertices, target, true); } + if (only.empty() || only == "--synchronous-target-read") TargetReadSynchronously(readOnlyVertices, target, unmapped); AgcDriverShutdown_nid_postfix(); std::puts("address passes: draws reading through the BDA table share a render pass, and a store through it still begins one"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp index f9e388295e..aed0ffc37a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp @@ -68,6 +68,8 @@ alignas(256) std::array PixelCode{}; alignas(256) std::array Output{}; void SampleSliceZero(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode); +void OverlappingArrays(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode, bool cpuWrite = false); +void OverlappingArrayLastSlice(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode); std::uint64_t AddressOf(const void* data) { return static_cast(reinterpret_cast(data)); @@ -81,22 +83,24 @@ std::string Hex(std::uint32_t value) { class GuestBlock { public: - explicit GuestBlock(std::size_t registeredBytes = SurfaceBytes, std::size_t registeredOffset = 0) : registeredOffset(registeredOffset) { + explicit GuestBlock(std::size_t registeredBytes = SurfaceBytes, std::size_t registeredOffset = 0, bool commitAll = true) : registeredOffset(registeredOffset) { #ifdef _WIN32 block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, LayerBytes)); - if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, SurfaceBytes, PAGE_READWRITE, SurfaceBytes); + if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block + (commitAll ? 0 : registeredOffset), commitAll ? SurfaceBytes : registeredBytes, PAGE_READWRITE, commitAll ? SurfaceBytes : registeredBytes); #else - void* mapped = mmap(nullptr, SurfaceBytes + LayerBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + void* mapped = mmap(nullptr, SurfaceBytes + LayerBytes, commitAll ? PROT_READ | PROT_WRITE : PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mapped != MAP_FAILED) { const auto begin = reinterpret_cast(mapped); const auto aligned = (begin + LayerBytes - 1) & ~(static_cast(LayerBytes) - 1); if (aligned != begin) munmap(mapped, aligned - begin); if (aligned + SurfaceBytes != begin + SurfaceBytes + LayerBytes) munmap(reinterpret_cast(aligned + SurfaceBytes), begin + LayerBytes - aligned); block = reinterpret_cast(aligned); - if (AgcDriver::GuestMemory::WriteWatched()) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, SurfaceBytes); + if (!commitAll) Require(mprotect(block + registeredOffset, registeredBytes, PROT_READ | PROT_WRITE) == 0, "cannot commit the active array slice"); + if (AgcDriver::GuestMemory::WriteWatched()) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block + (commitAll ? 0 : registeredOffset), commitAll ? SurfaceBytes : registeredBytes); } #endif Require(block != nullptr, "array layer target: cannot allocate the guest block"); + if (!commitAll) GuestAllocations::GuestAllocationsInvalidate_nid_postfix(reinterpret_cast(block), SurfaceBytes); GuestAllocations::Mutation().Add(block + registeredOffset, registeredBytes, true, true, true); } @@ -170,10 +174,10 @@ void Load(AgcDriver::VulkanDevice& device, std::span code, Dispatch(device, code, userData); } -void LoadLayer(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t layer, std::uint32_t swizzleMode) { +void LoadLayer(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t layer, std::uint32_t swizzleMode, std::uint32_t lastLayer = Layers - 1u) { auto& code = LoadCodes[layer]; code = {0x7e020280, 0x7e040280u | layer, 0xf0001128, 0x00010300, 0x34080082, 0xbf8c3f70, 0xe0701000, 0x80000304, 0xbf810000}; - Load(device, code, SurfaceDescriptor(surface, swizzleMode, Type2DArray, Layers - 1u)); + Load(device, code, SurfaceDescriptor(surface, swizzleMode, Type2DArray, lastLayer)); } void Sample2D(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t swizzleMode) { @@ -187,23 +191,23 @@ void RequireOutput(std::uint32_t inside, std::uint32_t outside, const std::strin } } -AgcDriver::Graphics::ColorTarget DecodeTarget(std::uint64_t address, std::uint32_t slice, std::uint32_t swizzleMode, bool collapsed) { +AgcDriver::Graphics::ColorTarget DecodeTarget(std::uint64_t address, std::uint32_t slice, std::uint32_t swizzleMode, bool collapsed, std::uint32_t lastSlice) { AgcDriver::Registers cx; cx[0x318] = static_cast(address >> 8u); cx[0x31b] = slice | (slice << 13u); cx[0x31c] = Color32FloatInfo; cx[0x31d] = 0; cx[0x3b0] = ((Side - 1u) << 14u) | (Side - 1u); - cx[0x3b8] = (TiledAttrib3 & ~(0x1fu << 14u)) | (swizzleMode << 14u) | (collapsed ? 0u : Layers - 1u); + cx[0x3b8] = (TiledAttrib3 & ~(0x1fu << 14u)) | (swizzleMode << 14u) | (collapsed ? 0u : lastSlice); cx[0x390] = static_cast(address >> 40u); const auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); Require(color.format == VK_FORMAT_R32_SFLOAT && color.bytes == LayerBytes && color.address == address + slice * LayerBytes, "the COLOR_32 float view did not decode as a 128x128 R32 slice of the array"); if (collapsed) Require(color.arrayAddress == 0 && color.arrayLastSlice == 0, "a collapsed slice view unexpectedly describes a whole array"); - else Require(color.arrayAddress == address && color.arrayLastSlice == Layers - 1u && color.arraySlice == slice, "the color view did not keep its array and slice"); + else Require(color.arrayAddress == address && color.arrayLastSlice == lastSlice && color.arraySlice == slice, "the color view did not keep its array and slice"); return color; } -void DrawSlice(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t slice, std::uint32_t swizzleMode, bool collapsed) { +void DrawSlice(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t slice, std::uint32_t swizzleMode, bool collapsed, std::uint32_t lastSlice = Layers - 1u, std::uint32_t width = ScissorWidth) { const auto target = device.Target(); constexpr std::uint32_t waveSize = 64; std::size_t at = 0; @@ -230,7 +234,7 @@ void DrawSlice(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint const auto vertexResult = ShaderRecompiler::Recompile(vertex); const auto vertexPush = static_cast(vertexResult.pushConstants.size()); - const auto color = DecodeTarget(surface, slice, swizzleMode, collapsed); + const auto color = DecodeTarget(surface, slice, swizzleMode, collapsed, lastSlice); VkPipelineColorBlendAttachmentState blend{}; blend.colorWriteMask = 0xf; AgcDriver::Graphics::State state{}; @@ -242,7 +246,7 @@ void DrawSlice(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; state.viewport = {0, static_cast(Side), static_cast(Side), -static_cast(Side), 0, 1}; state.negativeOneToOne = false; - state.scissor = {{0, 0}, {ScissorWidth, Side}}; + state.scissor = {{0, 0}, {width, Side}}; state.cullMode = VK_CULL_MODE_NONE; state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; state.blends = {blend}; @@ -296,6 +300,9 @@ void Run(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t sw RequireOutput(drawn, Initial, "slice " + std::to_string(slice) + " uploaded again from guest memory"); AgcDriver::Graphics::ClearCachedTextures(device.Device()); SampleSliceZero(device, texels, swizzleMode); + OverlappingArrays(device, texels, swizzleMode); + OverlappingArrays(device, texels, swizzleMode, true); + OverlappingArrayLastSlice(device, texels, swizzleMode); } void SampleSliceZero(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode) { @@ -315,6 +322,32 @@ void SampleSliceZero(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std: AgcDriver::Graphics::ClearCachedTextures(device.Device()); } +void OverlappingArrayLastSlice(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode) { + const auto neighbourAddress = AddressOf(texels); + const auto surface = neighbourAddress + LayerBytes; + const auto cpuValue = std::bit_cast(0.125f); + const auto drawn = std::bit_cast(Drawn); + std::fill_n(reinterpret_cast(texels), SurfaceBytes / sizeof(std::uint32_t), Initial); + AgcDriver::GuestMemory::CollectWrites(neighbourAddress, SurfaceBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, 1u, swizzleMode, false, 1u); + DrawSlice(device, neighbourAddress, 1u, swizzleMode, false, 1u, Side); + const auto neighbour = StorageTexture::FindPending(neighbourAddress, 2u * LayerBytes); + Require(neighbour != nullptr && neighbour->Descriptor().baseAddress == neighbourAddress, "the preceding target has no pending results"); + std::fill_n(reinterpret_cast(texels + 2u * LayerBytes), LayerBytes / sizeof(std::uint32_t), cpuValue); + AgcDriver::GuestMemory::CollectWrites(surface + LayerBytes, LayerBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, 1u, swizzleMode, false, 1u); + Require(StorageTexture::FindPending(neighbourAddress, 2u * LayerBytes) == neighbour, "CPU refresh of the last slice stored the preceding target's results"); + LoadLayer(device, surface, 1u, swizzleMode, 1u); + RequireOutput(drawn, cpuValue, "the refreshed last slice keeps the CPU's untouched pixels"); + LoadLayer(device, surface, 0u, swizzleMode, 1u); + RequireOutput(drawn, drawn, "the first slice sees the preceding target's pending results"); + StorageTexture::FlushPending(neighbourAddress, SurfaceBytes, nullptr, "test"); + device.WaitIdle(); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); +} + void UnregisteredUnusedLayer(AgcDriver::VulkanDevice& device, std::uint32_t slice) { GuestBlock block(LayerBytes, slice * LayerBytes); auto* texels = block.Data(); @@ -354,6 +387,75 @@ void RetiredArrayLayer(AgcDriver::VulkanDevice& device) { Require(untouched, "CPU write-back stored results over a retired allocation"); } +void UncommittedUnusedLayers(AgcDriver::VulkanDevice& device, std::uint32_t slice) { + if (!AgcDriver::GuestMemory::WriteWatched()) return; + GuestBlock block(LayerBytes, slice * LayerBytes, false); + const auto surface = AddressOf(block.Data()); + const auto active = surface + slice * LayerBytes; + auto* texels = reinterpret_cast(active); + std::fill_n(texels, LayerBytes / sizeof(std::uint32_t), Initial); + Require(AgcDriver::GuestMemory::CollectWrites(active, LayerBytes) != 0, "the committed array slice has no write history"); + Require(AgcDriver::GuestMemory::CollectWritesUncached(surface, SurfaceBytes) == 0, "the uncommitted array fixture unexpectedly has complete backend write history"); + Require(AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes) == 0, "the unused array slices unexpectedly have complete write history"); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, slice, TileR64KBX, false); + const auto pending = StorageTexture::FindPending(surface, SurfaceBytes); + Require(pending != nullptr, "the committed array slice has no pending draw results"); + Require(pending->GenerationFor(active, LayerBytes) != 0, "an uncommitted unused slice erased the active slice's write history"); + const auto version = pending->Version(); + for (unsigned repeat = 0; repeat < 3; ++repeat) { + AgcDriver::GuestMemory::BumpCollectEpoch(); + Require(pending->RefreshLayers(slice, 1), "an unchanged committed slice was uploaded again"); + Require(pending->GenerationFor(active, LayerBytes) != 0 && pending->Version() == version, "repeated refresh erased the committed slice's write history"); + Require(StorageTexture::FindPending(surface, SurfaceBytes) == pending, "refresh settled unchanged GPU results for an uncommitted unused slice"); + } + const auto cpu = std::bit_cast(0.125f); + std::fill_n(texels, LayerBytes / sizeof(std::uint32_t), cpu); + AgcDriver::GuestMemory::BumpCollectEpoch(); + Require(!pending->RefreshLayers(slice, 1), "the active array slice ignored a genuine CPU write"); + LoadLayer(device, surface, slice, TileR64KBX); + RequireOutput(cpu, cpu, "a committed array slice after a genuine CPU write"); + DrawSlice(device, surface, slice, TileR64KBX, false); + StorageTexture::FlushPending(active, LayerBytes, nullptr, "test"); + device.WaitIdle(); + Require(std::count(texels, texels + LayerBytes / sizeof(std::uint32_t), std::bit_cast(Drawn)) == ScissorWidth * Side, "write-back lost results in the committed array slice"); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); +} + +void OverlappingArrays(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode, bool cpuWrite) { + const auto surface = AddressOf(texels); + const auto next = surface + LayerBytes; + const auto drawn = std::bit_cast(Drawn); + std::fill_n(reinterpret_cast(texels), SurfaceBytes / 4u, Initial); + AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, 0, swizzleMode, false, 1u); + DrawSlice(device, next, 0, swizzleMode, false, 1u, Side); + const auto neighbour = StorageTexture::FindPending(next, 2u * LayerBytes); + Require(neighbour != nullptr && neighbour->Descriptor().baseAddress == next && neighbour->Descriptor().dimension == TextureDimension::k2DArray, "the next target did not render into the image of its own array"); + if (cpuWrite) { + std::fill_n(reinterpret_cast(texels), LayerBytes / sizeof(std::uint32_t), std::bit_cast(0.125f)); + AgcDriver::GuestMemory::CollectWrites(surface, LayerBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + } + DrawSlice(device, surface, 0, swizzleMode, false, 1u); + Require(StorageTexture::FindPending(next, 2u * LayerBytes) == neighbour, "a draw into slice 0 of the first array stored the next target's results, which only its unused second slice overlaps"); + Sample2D(device, surface, swizzleMode); + RequireOutput(drawn, cpuWrite ? std::bit_cast(0.125f) : Initial, "slice 0 of the first array sampled as a 2D texture after both draws"); + Require(StorageTexture::FindPending(next, 2u * LayerBytes) == neighbour, "a 2D texture over slice 0 of the first array stored the next target's results, which only its unused second slice overlaps"); + LoadLayer(device, next, 0, swizzleMode, 1u); + RequireOutput(drawn, drawn, "slice 0 of the next array after the draw into the first"); + LoadLayer(device, surface, 1, swizzleMode, 1u); + RequireOutput(drawn, drawn, "slice 1 of the first array (the next target's slice 0) read while the next target's results were pending"); + LoadLayer(device, surface, 0, swizzleMode, 1u); + RequireOutput(drawn, cpuWrite ? std::bit_cast(0.125f) : Initial, "slice 0 of the first array after both draws"); + LoadLayer(device, next, 1, swizzleMode, 1u); + RequireOutput(Initial, Initial, "slice 1 of the next array, which nothing drew"); + StorageTexture::FlushPending(surface, SurfaceBytes, nullptr, "test"); + device.WaitIdle(); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); +} + } int main() { @@ -367,6 +469,8 @@ int main() { UnregisteredUnusedLayer(*device, 0u); UnregisteredUnusedLayer(*device, Layers - 1u); RetiredArrayLayer(*device); + UncommittedUnusedLayers(*device, 0u); + UncommittedUnusedLayers(*device, Layers - 1u); std::puts("array layer target tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BcBlockCopy.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BcBlockCopy.cpp index 23eeb6dcf7..216bfbc31d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/BcBlockCopy.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BcBlockCopy.cpp @@ -35,6 +35,7 @@ constexpr std::uint32_t Format32x2Uint = 62; constexpr std::uint32_t Format32x4Uint = 75; constexpr std::uint32_t FormatBc1Unorm = 169; constexpr std::uint32_t FormatBc7Unorm = 181; +constexpr std::uint32_t ForceDegamma = 1u << 20u; constexpr std::uint32_t Type2D = 9; constexpr std::uint32_t ClampEdge = 2; constexpr std::size_t BlockBytes = 1u << 20u; @@ -130,8 +131,8 @@ std::array TextureDescriptor(const void* data, const Shape& sh }; } -std::array SamplerDescriptor() { - return {ClampEdge | (ClampEdge << 3u) | (ClampEdge << 6u), 0u, 0u, 0u}; +std::array SamplerDescriptor(std::uint32_t forced) { + return {ClampEdge | (ClampEdge << 3u) | (ClampEdge << 6u) | forced, 0u, 0u, 0u}; } ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code, std::span userData) { @@ -167,12 +168,12 @@ void Settle(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint64_t AgcDriver::GuestMemory::CollectWritesUncached(reinterpret_cast(texels), bytes); } -std::vector Sample(AgcDriver::VulkanDevice& device, std::uint8_t* texels, const Shape& shape) { +std::vector Sample(AgcDriver::VulkanDevice& device, std::uint8_t* texels, const Shape& shape, std::uint32_t forced = 0u) { std::vector userData(24, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size_bytes())); const auto output = BufferDescriptor(Output.data(), static_cast(Output.size_bytes())); const auto texture = TextureDescriptor(texels, shape); - const auto sampler = SamplerDescriptor(); + const auto sampler = SamplerDescriptor(forced); std::copy(input.begin(), input.end(), userData.begin()); std::copy(output.begin(), output.end(), userData.begin() + 4); std::copy(texture.begin(), texture.end(), userData.begin() + 8); @@ -189,12 +190,12 @@ std::vector Sample(AgcDriver::VulkanDevice& device, std::uint8_t* return {Output.begin(), Output.end()}; } -std::vector Reference(AgcDriver::VulkanDevice& device, const Case& test, std::uint32_t value) { +std::vector Reference(AgcDriver::VulkanDevice& device, const Case& test, std::uint32_t value, std::uint32_t forced = 0u) { GuestBlock block; auto* texels = block.Data(); Store(device, texels, test, value); Settle(device, texels, SurfaceBytes(texels, test.blocks)); - return Sample(device, texels, test.compressed); + return Sample(device, texels, test.compressed, forced); } void RequireSame(const std::vector& sampled, const std::vector& expected, const std::string& what) { @@ -208,7 +209,11 @@ void PendingBlocksCase(AgcDriver::VulkanDevice& device, const Case& test) { constexpr std::uint32_t Second = 0x9bdf6840u; const auto first = Reference(device, test, First); const auto second = Reference(device, test, Second); + const auto forcedFirst = Reference(device, test, First, ForceDegamma); + const auto forcedSecond = Reference(device, test, Second, ForceDegamma); Require(first != second, test.name + ": the two block patterns sample alike"); + Require(forcedFirst != first && forcedSecond != second, test.name + ": a sampler that forces sRGB decoding samples the stored blocks like a plain one"); + Require(forcedFirst != forcedSecond, test.name + ": the two block patterns sample alike through a sampler that forces sRGB decoding"); GuestBlock block; auto* texels = block.Data(); const auto bytes = SurfaceBytes(texels, test.compressed); @@ -222,6 +227,9 @@ void PendingBlocksCase(AgcDriver::VulkanDevice& device, const Case& test) { RequireSame(pending, second, test.name + ": sample of pending blocks"); Store(device, texels, test, First); RequireSame(Sample(device, texels, test.compressed), first, test.name + ": sample of blocks stored again"); + Store(device, texels, test, Second); + RequireSame(Sample(device, texels, test.compressed, ForceDegamma), forcedSecond, test.name + ": sRGB-forced sample of pending blocks"); + Require(AgcDriver::Graphics::StorageTexture::FindPending(reinterpret_cast(texels), bytes) != nullptr, test.name + ": the sRGB-forced sample stored the pending blocks to guest memory"); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsFloat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsFloat.cpp index 99a286d6ac..01b015a01b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsFloat.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsFloat.cpp @@ -149,6 +149,7 @@ void Run(AgcDriver::VulkanDevice& device) { {0, 0, 0, 128} }; request.useCache = false; + request.context.floatMode = ShaderRecompiler::ShaderFloatMode{0xf0u, true, false, false}; const auto result = ShaderRecompiler::Recompile(request); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferFormat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferFormat.cpp index 7ba31cd239..7394269e39 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/BufferFormat.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferFormat.cpp @@ -171,6 +171,96 @@ void Check() { } } +constexpr std::array SubwordWord3{0x31005facu, 0x31006facu, 0x3100bfacu, 0x3100cfacu, 0x31012facu}; + +constexpr const char* SubwordNames[16] = { + "buffer_load_format_x 8_UINT +1", + "buffer_load_format_x 8_UINT +2", + "buffer_load_format_x 8_UINT +3", + "buffer_load_format_x 8_SINT +1", + "buffer_load_format_x 8_SINT +2", + "buffer_load_format_x 8_SINT +3", + "buffer_load_format_x 16_UINT +1", + "buffer_load_format_x 16_UINT +2", + "buffer_load_format_x 16_UINT +3", + "buffer_load_format_x 16_SINT +1", + "buffer_load_format_x 16_SINT +2", + "buffer_load_format_x 16_SINT +3", + "buffer_load_format_xy 8_8_UINT +1 x", + "buffer_load_format_xy 8_8_UINT +1 y", + "buffer_load_format_xy 8_8_UINT +2 x", + "buffer_load_format_xy 8_8_UINT +2 y", +}; + +std::vector SubwordCode() { + std::vector code{0x34060087u, 0x34500085u}; + std::uint32_t vdata = 10u; + const auto emitLoad = [&](std::uint32_t op, std::uint32_t srsrc, std::uint32_t offset, std::uint32_t words) { + code.push_back(0xe0001000u | (op << 18u) | offset); + code.push_back(0x80000028u | (srsrc << 16u) | (vdata << 8u)); + vdata += words; + }; + for (std::uint32_t offset = 1u; offset <= 3u; ++offset) emitLoad(0u, 2u, offset, 1u); + for (std::uint32_t offset = 1u; offset <= 3u; ++offset) emitLoad(0u, 3u, offset, 1u); + for (std::uint32_t offset = 1u; offset <= 3u; ++offset) emitLoad(0u, 4u, offset, 1u); + for (std::uint32_t offset = 1u; offset <= 3u; ++offset) emitLoad(0u, 5u, offset, 1u); + for (std::uint32_t offset = 1u; offset <= 2u; ++offset) emitLoad(1u, 6u, offset, 2u); + code.push_back(0xbf8c3f70u); + for (std::uint32_t index = 0u; index < 16u; ++index) { + code.push_back(0xe0701000u | (index * 4u)); + code.push_back(0x80010003u | ((10u + index) << 8u)); + } + code.push_back(0xbf810000u); + return code; +} + +void RunSubwordOffsets(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]); + Output.fill(0xdeadbeefu); + std::vector userData(8u + 4u * SubwordWord3.size(), 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u), 0x31016facu); + std::copy(output.begin(), output.end(), userData.begin() + 4); + for (std::size_t index = 0; index < SubwordWord3.size(); ++index) { + const auto descriptor = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u), SubwordWord3[index]); + std::copy(descriptor.begin(), descriptor.end(), userData.begin() + 8 + 4 * index); + } + static const auto program = SubwordCode(); + const std::span code(program); + 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 CheckSubwordOffsets() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t word = Rows[tid][0]; + const auto u8 = [&](std::uint32_t offset) { return (word >> (offset * 8u)) & 0xffu; }; + const auto s8 = [&](std::uint32_t offset) { return static_cast(static_cast(static_cast(u8(offset)))); }; + const auto u16 = [&](std::uint32_t offset) { return (word >> (offset * 8u)) & 0xffffu; }; + const auto s16 = [&](std::uint32_t offset) { return static_cast(static_cast(static_cast(u16(offset)))); }; + const std::array expected{ + u8(1u), u8(2u), u8(3u), + s8(1u), s8(2u), s8(3u), + u16(1u), u16(2u), u16(3u), + s16(1u), s16(2u), s16(3u), + u8(1u), u8(2u), + u8(2u), u8(3u), + }; + for (std::uint32_t index = 0; index < expected.size(); ++index) { + Expect(tid, Output[tid * Results + index], expected[index], SubwordNames[index]); + } + } +} + } int main() { @@ -179,6 +269,8 @@ int main() { if (!device) return VulkanTestSkipped; Run(*device); Check(); + RunSubwordOffsets(*device); + CheckSubwordOffsets(); std::puts("buffer format tests passed"); return 0; } catch (const std::exception& error) { @@ -186,3 +278,4 @@ int main() { return 1; } } + diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ColorLogicOp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ColorLogicOp.cpp new file mode 100644 index 0000000000..89963aafb1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ColorLogicOp.cpp @@ -0,0 +1,155 @@ +#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 Width = 64; +constexpr std::uint32_t Height = 16; +constexpr std::uint32_t WaveSize = 64; +alignas(256) std::array Pixels{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +alignas(256) constexpr std::array PixelCode{ + 0x7e0802ff, 0x3e4ccccd, 0x7e0a02ff, 0x3ecccccd, 0x7e0c02ff, 0x3f19999a, 0x7e0e02ff, 0x3f4ccccd, + 0xf800180f, 0x07060504, 0xbf810000, +}; + +constexpr std::array Source{0x33, 0x66, 0x99, 0xcc}; + +constexpr std::array, 3> Vertices{{ + {-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f} +}}; +alignas(256) std::array, 3> VertexStorage = Vertices; +std::array Before{}; + + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +void Draw(AgcDriver::VulkanDevice& device, std::uint8_t rop) { + for (std::size_t index = 0; index < Pixels.size(); ++index) Before[index] = Pixels[index] = static_cast((index * 37u + 11u) & 0xffu); + const auto target = device.Target(); + + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(VertexStorage.data(), 16u, static_cast(VertexStorage.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {WaveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.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, {}}, + {WaveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, WaveSize, WaveSize, 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.colorRop = rop; + 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(VertexStorage.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +std::uint8_t Combine(std::uint8_t rop, std::uint8_t destination, std::uint8_t source) { + switch (rop) { + case 0xcc: return source; + case 0x5a: return static_cast(destination ^ source); + case 0x88: return static_cast(destination & source); + case 0xee: return static_cast(destination | source); + default: throw std::runtime_error("the test has no expectation for this ROP"); + } +} + +void Check(std::uint8_t rop, std::string_view what) { + 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) { + const auto index = (y * Width + x) * 4u + channel; + const auto expected = Combine(rop, static_cast(Before[index]), Source[channel]); + const auto actual = static_cast(Pixels[index]); + Require(actual == expected, std::string(what) + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected)); + } + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (const std::uint8_t rop : {0xcc, 0x5a, 0x88, 0xee, 0xcc}) { + try { + Draw(*device, rop); + } catch (const std::exception& error) { + Require(std::string_view(error.what()).find("device does not support color logic operations") != std::string_view::npos, std::string("unexpected rejection: ") + error.what()); + std::puts("skipped, the device has no logicOp feature"); + return VulkanTestSkipped; + } + char what[48]; + std::snprintf(what, sizeof(what), "ROP3 0x%02x into an 8_8_8_8 unorm target", rop); + Check(rop, what); + } + std::puts("color logic operation 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/ComputeFsTableCalls.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ComputeFsTableCalls.cpp index a5395d5661..d2013c8b53 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ComputeFsTableCalls.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ComputeFsTableCalls.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" #include "SceShaders.hpp" #include "VulkanTestDevice.hpp" #include @@ -21,6 +22,8 @@ constexpr std::uint32_t Threads = 64; constexpr std::uint32_t Untouched = 0xcafebabeu; constexpr std::uint32_t Marker = 0x55u; constexpr std::uint64_t FsTable = 0xfe0040000ull; +constexpr std::uint64_t DriverMemory = 0xfe0000000ull; +constexpr std::size_t DriverMemoryBytes = 2u << 20u; alignas(256) constexpr std::array Caller{ 0x34040082u, 0x7e0202ffu, 0x00000055u, 0xbeea03ffu, 0xe0040000u, 0xbeeb038fu, 0xf4080235u, 0xfa000000u, 0xbf8cc07fu, 0xf4240384u, @@ -84,6 +87,9 @@ void CheckTable() { Require(AgcDriver::GuestMemory::Accessible(reinterpret_cast(table[2]), sizeof(routine)), "the FS routine is not readable"); std::memcpy(routine.data(), reinterpret_cast(table[2]), sizeof(routine)); Require(routine[0] == 0xbe80200eu, "the FS routine does not start with s_setpc_b64 s[14:15]"); +#ifndef _WIN32 + if (GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) Require(AgcDriver::GuestMemory::Watched(DriverMemory, DriverMemoryBytes), "the AGC driver memory at 0xfe0000000 is not write-watched, so a refused import copies it on every address-based build"); +#endif } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ComputeTableCalls.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ComputeTableCalls.cpp index d9a6a8aa1d..f85c5c7d8a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ComputeTableCalls.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ComputeTableCalls.cpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "SceShaders.hpp" #include "VulkanTestDevice.hpp" +#include #include #include #include @@ -32,10 +33,14 @@ alignas(256) constexpr std::array HoistedCaller{ 0x7e020280u, 0xf4080400u, 0xfa000000u, 0xbf8cc07fu, 0xbf068203u, 0xbf850001u, 0x7e020280u, 0x8f148402u, 0xf4280208u, 0x28000000u, 0xbf8cc07fu, 0xbe8e0308u, 0xbe8f0309u, 0xbf068103u, 0xbe8e210eu, 0x34040082u, 0xe0701000u, 0x80010102u, 0xbf810000u, 0xbf9f0000u, 0x30306c73u, 0x00000040u, 0x00000008u}; +alignas(256) constexpr std::array SliceCaller{ + 0x7e020280u, 0xf40c0600u, 0xfa000010u, 0x8f148402u, 0xbf8cc07fu, 0xf428020eu, 0x28000000u, 0xbf8cc07fu, + 0xbe8e0308u, 0xbe8f0309u, 0xbf068103u, 0xbe8e210eu, 0x34040082u, 0xe0701000u, 0x80010102u, 0xbf810000u, + 0xbf9f0000u, 0x30306c73u, 0x00000040u, 0x00000008u}; alignas(256) constexpr std::array SelectSixteen{0x85159091u, 0x7e020215u, 0xbe80200eu}; alignas(256) constexpr std::array SelectThirtyTwo{0x8515a0a1u, 0x7e020215u, 0xbe80200eu}; alignas(16) std::array Table{}; -alignas(16) std::array Descriptors{}; +alignas(16) std::array Descriptors{}; alignas(256) std::array Output{}; void Fill() { @@ -43,7 +48,10 @@ void Fill() { const auto second = reinterpret_cast(SelectThirtyTwo.data()); Table = {static_cast(first), static_cast(first >> 32u), 0u, 0u, static_cast(second), static_cast(second >> 32u), 0u, 0u}; const auto table = reinterpret_cast(Table.data()); - Descriptors = {static_cast(table), static_cast((table >> 32u) & 0xffffu) | (16u << 16u), 2u, 0x31016facu}; + const std::array descriptor{static_cast(table), static_cast((table >> 32u) & 0xffffu) | (16u << 16u), 2u, 0x31016facu}; + Descriptors = {}; + std::copy(descriptor.begin(), descriptor.end(), Descriptors.begin()); + std::copy(descriptor.begin(), descriptor.end(), Descriptors.begin() + 8); } std::array UserData(std::uint32_t index, bool scc) { @@ -149,6 +157,8 @@ int main() { Check(1u, true, wave32, 33u, "the second record's callee with SCC 1"); Check(0u, false, wave32, 16u, "the first record's callee through a descriptor loaded before a branch", HoistedCaller.data()); Check(1u, true, wave32, 33u, "the second record's callee through a descriptor loaded before a branch", HoistedCaller.data()); + Check(0u, false, wave32, 16u, "the first record's callee through the second half of an s_load_dwordx8", SliceCaller.data()); + Check(1u, true, wave32, 33u, "the second record's callee through the second half of an s_load_dwordx8", SliceCaller.data()); const auto log = Dispatch(2u, false, wave32); Require(log.find("called address 0x0 at program counter") != std::string::npos && log.find("which no linked callee of that call matches") != std::string::npos, std::string("a record past the table did not report an unlinked call") + (wave32 ? " (wave32)" : " (wave64)") + ": " + log); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/CopiedWriterLabelOrder.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/CopiedWriterLabelOrder.cpp new file mode 100644 index 0000000000..f143769c82 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/CopiedWriterLabelOrder.cpp @@ -0,0 +1,270 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Recorder; +using AgcDriver::Graphics::Require; + +constexpr std::uint32_t Threads = 32; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t RecordBytes = 16; +constexpr std::size_t StoredOffset = 8; +constexpr std::size_t CopiedRecordOffset = 0x1004; +constexpr std::size_t DirectRecordOffset = 0x1000; +constexpr std::uint32_t CpuValue = 0xc0001000u; +constexpr std::uint32_t GpuValue = 1; +constexpr std::uint32_t LabelValue = 1; + +alignas(256) constexpr std::array StoreCode{ + 0x7e000281u, 0x7e020288u, 0xe0701000u, 0x80000001u, 0xbf810000u, 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, "copied writer label order: cannot allocate a guest block"); + std::memset(block, 0, BlockBytes); + 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; + + volatile std::uint32_t* Word(std::size_t offset) const { return reinterpret_cast(block + offset); } + std::uint64_t Address() const { return static_cast(reinterpret_cast(block)); } + +private: + std::uint8_t* block = nullptr; +}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void RecordDispatch(AgcDriver::VulkanDevice& device, std::uint64_t record) { + std::vector userData(16, 0u); + const std::array descriptor{static_cast(record), static_cast((record >> 32u) & 0xffffu), RecordBytes, 0x31016facu}; + std::copy(descriptor.begin(), descriptor.end(), userData.begin()); + const std::span code(StoreCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderRecompiler::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())); +} + +bool Drains() { + return std::getenv("APS5_DRAIN_COMPLETION_LABELS") != nullptr; +} + +VkDeviceSize StorageAlignment(AgcDriver::VulkanDevice& device) { + return device.graphicsContext().limits.minStorageBufferOffsetAlignment; +} + +std::array LabelBytes(std::uint32_t value) { + std::array bytes{}; + std::memcpy(bytes.data(), &value, sizeof(value)); + return bytes; +} + +void WaitForGpu(AgcDriver::VulkanDevice& device, std::uint64_t epoch) { + if (device.CanWaitUnlocked()) device.WaitRecorded(epoch); + else std::this_thread::sleep_for(std::chrono::milliseconds(50)); +} + +std::uint64_t SubmitLocked(AgcDriver::VulkanDevice& device) { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + return device.SubmitAndEpoch(); +} + +int LabelPass(AgcDriver::VulkanDevice& device, const GuestBlock& records, std::size_t recordOffset, const GuestBlock& labels, std::size_t labelOffset, std::uint64_t stamp, std::uint64_t writeBacks, int expectedReason, const std::string& what) { + volatile std::uint32_t* stored = records.Word(recordOffset + StoredOffset); + volatile std::uint32_t* label = labels.Word(labelOffset); + *stored = CpuValue; + *label = 0; + RecordDispatch(device, records.Address() + recordOffset); + const auto pending = Recorder::PendingWriteBackCompletions(); + if (writeBacks != 0 && pending == 0 && recordOffset % StorageAlignment(device) == 0) { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + device.WaitIdle(); + return VulkanTestSkipped; + } + Require(pending == writeBacks, what + ": " + std::to_string(pending) + " write-back completions are pending after the dispatch, expected " + std::to_string(writeBacks)); + const auto bytes = LabelBytes(LabelValue); + int reason = 0; + std::uint64_t epoch = 0; + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + reason = device.WriteLabelOnGpu(labels.Address() + labelOffset, bytes, stamp, 0, false); + if (reason == 3) { + device.WaitIdle(); + return 1; + } + Require(reason == expectedReason, what + ": the label after the dispatch was handled with reason " + std::to_string(reason) + ", expected " + std::to_string(expectedReason)); + if (reason >= 1 && reason <= 4) { + if (reason != 1) device.WaitIdle(); + AgcDriver::GuestMemory::Write(labels.Address() + labelOffset, bytes, 4); + } else if (reason == 5) { + Require(Recorder::PendingWriteBackCompletions() == 1 && Recorder::PendingCompletionStores() == 1, what + ": the label behind the write-back drained the device or is not a pending completion store"); + const std::array wide{}; + Require(device.WriteLabelOnGpu(labels.Address() + labelOffset + 0x100, wide, stamp, 0, false) == 0, what + ": a 16-byte store away from the copied writer and the pending completion store was not recorded on the GPU"); + epoch = device.SubmitAndEpoch(); + } else { + device.SubmitRecorded(true); + } + } + if (reason == 5) { + WaitForGpu(device, epoch); + Require(*label == 0, what + ": the label is visible before the batch that writes back the dispatch's record was reaped"); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + device.ReapRecorded(epoch); + Require(Recorder::PendingCompletionStores() == 0 && Recorder::PendingWriteBackCompletions() == 0, what + ": completions outlived the reap of their batch"); + } + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (*label != LabelValue) { + Require(std::chrono::steady_clock::now() < deadline, what + ": the label never landed"); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + const std::uint32_t seen = *stored; + Require(seen == GpuValue, what + ": the label is visible but the record holds " + Hex(seen) + ", the dispatch stored " + Hex(GpuValue)); + *stored = CpuValue; + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + device.WaitIdle(); + } + const std::uint32_t kept = *stored; + Require(kept == CpuValue, what + ": the record holds " + Hex(kept) + " after the drain, a write-back landed over the value the CPU wrote after the label (" + Hex(CpuValue) + ")"); + Require(Recorder::PendingWriteBackCompletions() == 0, what + ": write-back completions outlived the drain"); + return 0; +} + +void StreamOrderPass(AgcDriver::VulkanDevice& device, const GuestBlock& copied, const GuestBlock& direct, const GuestBlock& labels, std::uint64_t stamp) { + constexpr std::uint32_t First = 0x11u; + constexpr std::uint32_t Second = 0x22u; + const std::uint64_t address = labels.Address() + 0x4000; + volatile std::uint32_t* label = labels.Word(0x4000); + *label = 0; + *copied.Word(CopiedRecordOffset + StoredOffset) = CpuValue; + RecordDispatch(device, copied.Address() + CopiedRecordOffset); + Require(Recorder::PendingWriteBackCompletions() == 1, "stream order: the copied dispatch has no pending write-back"); + const auto copiedEpoch = SubmitLocked(device); + RecordDispatch(device, direct.Address() + DirectRecordOffset); + std::uint64_t firstEpoch = 0; + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Require(Recorder::PendingWriteBackCompletions() == 1, "stream order: the copied writer was written back before the first label"); + const int reason = device.WriteLabelOnGpu(address, LabelBytes(First), stamp, 0, false); + Require(reason == 5, "stream order: the first label behind the copied writer was handled with reason " + std::to_string(reason) + ", expected 5"); + firstEpoch = device.SubmitAndEpoch(); + } + WaitForGpu(device, firstEpoch); + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + device.ReapRecorded(copiedEpoch); + Require(Recorder::PendingWriteBackCompletions() == 0, "stream order: the copied writer was not written back by the reap of its batch"); + Require(Recorder::PendingCompletionStores() == 1, "stream order: the first label's completion store is not pending after the copied writer's reap"); + Require(*label == 0, "stream order: the first label landed before its batch was reaped"); + } + RecordDispatch(device, direct.Address() + DirectRecordOffset); + std::uint64_t secondEpoch = 0; + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const int reason = device.WriteLabelOnGpu(address, LabelBytes(Second), stamp + 1, 0, false); + Require(reason == 5, "stream order: a later label to the address of a pending completion store was handled with reason " + std::to_string(reason) + ", expected 5"); + secondEpoch = device.SubmitAndEpoch(); + Require(secondEpoch > firstEpoch, "stream order: the second label went into the first label's batch"); + } + WaitForGpu(device, secondEpoch); + const std::uint32_t early = *label; + Require(early != Second, "stream order: the later label is visible while the earlier completion store to its address has not landed"); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + device.ReapRecorded(firstEpoch); + Require(*label == First, "stream order: the label holds " + Hex(*label) + " after the first label's batch was reaped, expected " + Hex(First)); + device.ReapRecorded(secondEpoch); + const std::uint32_t last = *label; + Require(last == Second, "stream order: the label holds " + Hex(last) + " after the reap, the later label stored " + Hex(Second)); + Require(Recorder::PendingCompletionStores() == 0 && Recorder::PendingCompletionLabels() == 0, "stream order: completion stores outlived the reap of their batches"); + device.WaitIdle(); +} + +} + +int main() { + try { +#ifdef _WIN32 + _putenv_s("APS5_CPU_COPIES", "1"); + _putenv_s("APS5_NO_OPPORTUNISTIC_REAP", "1"); +#else + setenv("APS5_CPU_COPIES", "1", 1); + setenv("APS5_NO_OPPORTUNISTIC_REAP", "1", 1); +#endif + const GuestBlock copied; + const GuestBlock direct; + const GuestBlock labels; + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const int copiedPass = LabelPass(*device, copied, CopiedRecordOffset, labels, 0x2000, 1, 1, Drains() ? 2 : 5, "copied writer"); + if (copiedPass == VulkanTestSkipped) { + std::printf("skipped, the device reports a minStorageBufferOffsetAlignment of %llu, so the dispatch binds its record at +0x%zx in place and nothing is written back; the test needs an alignment above 4\n", + static_cast(StorageAlignment(*device)), CopiedRecordOffset); + return VulkanTestSkipped; + } + if (copiedPass != 0 || LabelPass(*device, direct, DirectRecordOffset, labels, 0x3000, 2, 0, 0, "direct writer") != 0) { + std::puts("skipped, the device imports no host memory"); + return VulkanTestSkipped; + } + if (!Drains()) StreamOrderPass(*device, copied, direct, labels, 3); + std::puts("copied writer label order 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/DccKeyPresync.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeyPresync.cpp index bed49aed8f..6fcb25ce6f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DccKeyPresync.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeyPresync.cpp @@ -234,6 +234,22 @@ void RunKnownKeys(AgcDriver::VulkanDevice& device, const Scenario& scenario) { RequireSampledClear(scenario, "a dispatch behind a pending fill of the keys"); } +void RunOneKeyReadPerLookup(AgcDriver::VulkanDevice& device, const Scenario& scenario, const char* what) { + Reset(scenario); + const auto sample = Compile(device, SampleSecondTexelCode, scenario.sampleData); + auto prepared = device.PrepareDispatch(sample, {}, Threads, reinterpret_cast(SampleSecondTexelCode.data())); + const auto before = AgcDriver::Graphics::SampledLookupKeyScans(); + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + device.Dispatch(sample, 1, 1, 1, {}, reinterpret_cast(SampleSecondTexelCode.data()), std::move(prepared)); + device.WaitIdle(); + } + const auto scans = AgcDriver::Graphics::SampledLookupKeyScans() - before; + char message[192]; + std::snprintf(message, sizeof(message), "%s scanned the keys of its sampled texture %llu times with nothing written between the scans, expected at most once", what, static_cast(scans)); + Require(scans <= 1u, message); +} + void RunNoWriter(AgcDriver::VulkanDevice& device, const Scenario& scenario) { const auto sample = Compile(device, SampleSecondTexelCode, scenario.sampleData); const auto before = AgcDriver::VulkanDevice::PresyncCounts(); @@ -273,6 +289,11 @@ int main() { RunLockedSync(*device, control); RunPresync(*device, changed); RunNoWriter(*device, changed); + GuestBlock single; + GuestBlock singleOutput; + const auto reads = Prepare(single.Data(), singleOutput.Data()); + RunOneKeyReadPerLookup(*device, reads, "a dispatch's first lookup"); + RunOneKeyReadPerLookup(*device, reads, "a dispatch's repeated lookup"); RunKnownKeys(*device, fills); std::puts("DCC key pre-sync tests passed"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp index 9f844dd5cc..5dc96d71c0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" @@ -7,7 +8,9 @@ #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" @@ -47,6 +50,7 @@ struct VulkanDeviceTestAccess { namespace { using AgcDriver::Graphics::DccKeys; +using AgcDriver::Graphics::LookupOutcomes; using AgcDriver::Graphics::Require; using AgcDriver::Graphics::StorageTexture; using ShaderRecompiler::ShaderStage; @@ -451,6 +455,46 @@ void RunSingleKeys(AgcDriver::VulkanDevice& device, std::uint8_t* block) { Require(wrong == 0, message); } +void RunUniformSingleClear(AgcDriver::VulkanDevice& device, std::uint8_t* block) { + auto* texels = block; + auto* keys = block + KeysOffset; + const auto surface = AddressOf(texels); + const std::uint32_t color = 0x11223344u; + std::memset(texels, 0x55, SurfaceBytes); + for (std::size_t offset = 0; offset < SurfaceBytes; offset += 256) std::memcpy(texels + offset, &color, 4); + std::memset(keys, 0x10, KeyBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + const auto texture = TextureDescriptor(texels, keys); + std::vector writeData(16, 0u); + std::copy(texture.begin(), texture.end(), writeData.begin() + 4); + const auto uploads = AgcDriver::Graphics::ThreadLookupOutcomes().counts; + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Dispatch(device, WriteCode, writeData); + const auto image = StorageTexture::FindPending(surface, SurfaceBytes); + Require(image != nullptr && image->UploadedKeys() == DccKeys::Uncompressed, "uniform single-key clear: the first write did not store the keys uncompressed"); + const auto& uploaded = AgcDriver::Graphics::ThreadLookupOutcomes().counts; + Require(uploaded[LookupOutcomes::UploadClear] == uploads[LookupOutcomes::UploadClear] + 1 && uploaded[LookupOutcomes::UploadCpu] == uploads[LookupOutcomes::UploadCpu], "uniform single-key clear: the first write read the surface and uploaded it instead of clearing it on the GPU"); + const bool stillWatched = AgcDriver::GuestMemory::Watched(surface, SurfaceBytes); + Require(image->GuestSnapshotValid() == !stillWatched, stillWatched ? "uniform single-key clear: the upload of write-watched memory kept a guest snapshot, so the surface was read rather than cleared" : "uniform single-key clear: the upload of untracked memory kept no guest bytes to compare"); + StorageTexture::FlushPending(surface, SurfaceBytes, nullptr, "test uniform single-key clear"); + device.WaitIdle(); + RequireKeys(keys, 0xff, "uniform single-key clear: the keys after the write-back"); + const AgcDriver::Graphics::ColorTargetLayout layout(Side, Side, AgcDriver::Graphics::ColorTileMode::RenderTarget, 4); + std::size_t wrong = 0; + for (std::uint32_t y = 0; y < Side; ++y) { + for (std::uint32_t x = 0; x < Side; ++x) { + const auto offset = layout.Offset(x, y); + std::uint32_t texel = 0; + std::memcpy(&texel, texels + offset, 4); + const auto expected = y == 0 && x < Threads ? 0xffffffffu : color; + if (texel != expected) ++wrong; + } + } + char message[256]; + std::snprintf(message, sizeof(message), "uniform single-key clear: %zu of %u written-back texels are neither the kernel's write nor the proved clear color 0x%08x", wrong, Side * Side, color); + Require(wrong == 0, message); +} + void RunSingleKeysSampled(AgcDriver::VulkanDevice& device, std::uint8_t* block, std::uint8_t* outputBlock, const char* memory) { auto* texels = block; auto* keys = block + KeysOffset; @@ -785,8 +829,52 @@ void RunSharedImport(AgcDriver::VulkanDevice& device, std::uint8_t* block) { } +void RunMipmappedTarget(AgcDriver::VulkanDevice& device, std::uint8_t* block) { +#ifndef _WIN32 + constexpr std::uint32_t ChainSide = 64; + constexpr std::uint32_t ChainLevels = 7; + auto* texels = block; + auto* keys = block + KeysOffset; + const auto surface = AddressOf(texels); + const auto meta = AddressOf(keys); + std::memset(texels, 0x55, SurfaceBytes); + std::memset(keys, 0x00, KeyBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + AgcDriver::Registers cx; + cx[0x318] = static_cast(surface >> 8u); + cx[0x390] = static_cast(surface >> 40u); + cx[0x31b] = 0; + cx[0x31c] = 0x10028028u; + cx[0x31d] = 0; + cx[0x3b0] = ((ChainLevels - 1u) << 28u) | ((ChainSide - 1u) << 14u) | (ChainSide - 1u); + cx[0x3b8] = 0x9000000u | (TileRenderTarget64KB << 14u) | (1u << 30u); + cx[0x325] = static_cast(meta >> 8u); + cx[0x3a8] = static_cast(meta >> 40u); + const auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + const auto resource = AgcDriver::Graphics::SurfaceForTarget(color); + Require(color.mipCount == ChainLevels && color.dccAddress == 0 && resource.dccAddress == meta, "the resident surface of a mipmapped DCC color target has no keys"); + const auto bytes = AgcDriver::Graphics::DescribeSurface(resource).guestBytes; + Require(bytes <= SurfaceBytes, "the mip chain does not fit the test surface"); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); + const auto image = AgcDriver::Graphics::CachedStorageSurface(context, resource); + Require(image->UploadedKeys() == DccKeys::Clear0000, "the resident image of a 0000-cleared mipmapped target was not cleared from its keys"); + image->MarkDirty(); + Require(AgcDriver::Graphics::CurrentDccKeys(meta, bytes) == DccKeys::Uncompressed, "the first write into a mipmapped DCC target left its keys cleared"); + Require(AgcDriver::Graphics::TextureClearKeys(resource, bytes) == DccKeys::Uncompressed, "a sampled view of a rendered mipmapped DCC target still reads its clear"); +#else + static_cast(device); + static_cast(block); +#endif +} + int main() { try { +#ifdef _WIN32 + _putenv_s("APS5_PROFILE_DRAW", "1"); +#else + setenv("APS5_PROFILE_DRAW", "1", 1); +#endif std::optional block; std::optional watched; std::optional untracked; @@ -806,6 +894,9 @@ int main() { std::optional single; single.emplace(false); RunSingleKeys(*device, single->Data()); + std::optional uniformClear; + uniformClear.emplace(false); + RunUniformSingleClear(*device, uniformClear->Data()); std::optional sampled; sampled.emplace(false); std::optional sampledOutput; @@ -817,6 +908,9 @@ int main() { std::optional uniform; uniform.emplace(false); RunUniformKeysBehindKernel(*device, uniform->Data()); + std::optional chain; + chain.emplace(false); + RunMipmappedTarget(*device, chain->Data()); #ifdef _WIN32 shared.emplace(true, true); RunSharedImport(*device, shared->Data()); @@ -828,6 +922,9 @@ int main() { std::optional sampledWatched; sampledWatched.emplace(true); RunSingleKeysSampled(*device, sampledWatched->Data(), sampledOutput->Data(), "write-watched"); + std::optional uniformClearWatched; + uniformClearWatched.emplace(true); + RunUniformSingleClear(*device, uniformClearWatched->Data()); #ifdef _WIN32 pendingSingle.emplace(true, true); #else diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthBoundsEmulationDraw.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthBoundsEmulationDraw.cpp new file mode 100644 index 0000000000..eaddbfcf25 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthBoundsEmulationDraw.cpp @@ -0,0 +1,173 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "Recompiler.hpp" +#include "DepthFastClearHarness.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +namespace Harness = DepthFastClearHarness; +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +struct Bounds { + float min; + float max; +}; + +struct Case { + float stored; + Bounds bounds; + bool covered; + const char* what; +}; + +alignas(4096) std::array, 16> Depths{}; +alignas(256) std::array, 3>, 64> VertexSets{}; +std::size_t vertexSets = 0; + +std::uint32_t Draw(AgcDriver::VulkanDevice& device, std::uint64_t depth, float z, std::optional bounds, bool emulate) { + Require(vertexSets < VertexSets.size(), "the test ran out of vertex sets"); + auto& vertices = VertexSets[vertexSets++]; + vertices = {{{-1.0f, -1.0f, z, 1.0f}, {3.0f, -1.0f, z, 1.0f}, {-1.0f, 3.0f, z, 1.0f}}}; + Harness::Pixels.fill(std::byte{0}); + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Harness::Pixels.data()), {Harness::Width, Harness::Height}, VK_FORMAT_R8G8B8A8_UNORM, Harness::Pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.depth = AgcDriver::Graphics::DepthTarget{depth, 0, {Harness::Width, Harness::Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0}; + state.depthTest = !bounds.has_value(); + state.depthWrite = !bounds.has_value(); + state.depthCompare = VK_COMPARE_OP_ALWAYS; + if (bounds) { + state.depthBoundsTest = true; + state.minDepthBounds = bounds->min; + state.maxDepthBounds = bounds->max; + } + state.renderExtent = {Harness::Width, Harness::Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Harness::Height), static_cast(Harness::Width), -static_cast(Harness::Height), 0.0f, 1.0f}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Harness::Width, Harness::Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + + 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); + if (bounds && emulate) { + auto context = device.graphicsContext(); + context.depthBounds = false; + context.depthRangeUnrestricted = false; + AgcDriver::Graphics::ApplyDepthBoundsEmulation(context, state, pixel); + Require(!state.depthBoundsTest && state.depthTest && !state.depthWrite, "the depth bounds test was not emulated"); + } + + auto target = device.Target(); + target.depthRangeRemap = false; + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = Harness::BufferDescriptor(vertices.data(), 16u, static_cast(vertices.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(Harness::VertexCode.data()), std::as_bytes(std::span(Harness::VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(Harness::VertexCode.data()), Harness::VertexCode, 0, {}}, + {32, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + std::vector pixelUserData(4, 0u); + const std::array pixelMemory{{{reinterpret_cast(Harness::PixelCode.data()), std::as_bytes(std::span(Harness::PixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(Harness::PixelCode.data()), Harness::PixelCode, 0, {}}, + {32, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(vertices.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); + std::uint32_t covered = 0; + for (std::size_t index = 0; index < Harness::Pixels.size() / 4u; ++index) { + if (std::to_integer(Harness::Pixels[index * 4u]) == 255u) ++covered; + } + return covered; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto context = device->graphicsContext(); + const float below = std::nextafter(0.25f, 0.0f); + const float above = std::nextafter(0.25f, 1.0f); + const std::array cases{{ + {0.25f, {0.0f, 0.25f}, true, "stored 0.25, upper bound equal to it"}, + {0.25f, {0.0f, below}, false, "stored 0.25, upper bound one step below it"}, + {0.25f, {0.25f, 1.0f}, true, "stored 0.25, lower bound equal to it"}, + {0.25f, {above, 1.0f}, false, "stored 0.25, lower bound one step above it"}, + {0.000125f, {0.0f, 0.000125f}, true, "stored 0.000125 inside [0, 0.000125]"}, + {0.5f, {0.0f, 0.000125f}, false, "stored 0.5 outside [0, 0.000125]"}, + {0.5f, {0.000125f, 1.0f}, true, "stored 0.5 inside [0.000125, 1]"}, + {0.0f, {0.000125f, 1.0f}, false, "stored 0 outside [0.000125, 1]"}, + {0.0f, {0.0f, 1.0f}, true, "stored 0 inside [0, 1]"}, + {1.0f, {0.0f, 1.0f}, true, "stored 1 inside [0, 1]"}, + {1.0f, {0.0f, 0.5f}, false, "stored 1 outside [0, 0.5]"}, + {0.75f, {0.5f, 1.0f}, true, "stored 0.75 inside [0.5, 1]"}, + }}; + std::size_t compared = 0; + for (std::size_t index = 0; index < cases.size(); ++index) { + const auto& test = cases[index]; + const auto depth = static_cast(reinterpret_cast(Depths[index].data())); + Require(Draw(*device, depth, test.stored, std::nullopt, false) == Harness::Covered, "the draw that stores the depth did not cover the target"); + const auto expected = test.covered ? Harness::Covered : 0u; + for (const bool emulate : {true, false}) { + if (!emulate && !context.depthBounds) continue; + const auto passed = Draw(*device, depth, 0.5f, test.bounds, emulate); + if (passed != expected) { + char detail[256]; + std::snprintf(detail, sizeof(detail), "%s (%s): %u of %u pixels passed, expected %s", test.what, emulate ? "emulated" : "native", passed, Harness::Covered, test.covered ? "all" : "none"); + throw std::runtime_error(detail); + } + if (!emulate) ++compared; + } + } + std::printf("depth bounds emulation draw tests passed (%zu cases, %zu compared with the native test)\n", cases.size(), compared); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthCopyDraw.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthCopyDraw.cpp new file mode 100644 index 0000000000..b7c8199b67 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthCopyDraw.cpp @@ -0,0 +1,166 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 4; +constexpr float DrawnDepth = 0.25f; +constexpr float MemoryDepth = 0.75f; +constexpr float Untouched = -1.0f; + +alignas(256) constexpr std::array VertexCode{ + 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e060280u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u, +}; +alignas(256) constexpr std::array WhitePixelCode{0x7e0e02f2u, 0xf800180fu, 0x07070707u, 0xbf810000u}; +alignas(256) std::array CopyTarget{}; +alignas(256) std::array ReferenceTarget{}; +constexpr std::size_t DepthFloats = 16384; +constexpr std::size_t DepthAlignment = 65536; +std::array DepthStorage{}; +const std::span DepthMemory{reinterpret_cast((reinterpret_cast(DepthStorage.data()) + DepthAlignment - 1) & ~(DepthAlignment - 1)), DepthFloats}; + +struct Header { + Shader shader{}; + ShaderUserData userData{}; + std::array registers{}; +}; + +alignas(8) Header VertexHeader{}; +alignas(8) Header PixelHeader{}; + +void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(bytes); + header.shader.code = code; + header.shader.type = type; + header.shader.user_data = &header.userData; + if (type == 1) { + const auto address = reinterpret_cast(code); + header.registers = {{{0x008, static_cast(address >> 8u)}, {0x009, static_cast(address >> 40u)}, {0x00b, 0}, {0x00a, 0}}}; + header.shader.sh_registers = header.registers.data(); + header.shader.num_sh_registers = header.registers.size(); + } + AgcDriverRegisterShader_nid_postfix(&header.shader); +} + +void Append(std::vector& commands, std::uint32_t opcode, std::initializer_list payload) { + commands.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + commands.insert(commands.end(), payload); +} + +struct DrawOptions { + float* color = nullptr; + std::uint32_t targetMask = 1; + std::uint32_t renderControl = 0; + std::uint32_t depthControl = 0; +}; + +void AppendDraw(std::vector& commands, const DrawOptions& options) { + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto pixel = reinterpret_cast(WhitePixelCode.data()); + const auto color = reinterpret_cast(options.color); + const auto depth = reinterpret_cast(DepthMemory.data()); + Require(depth % DepthAlignment == 0, "the test depth plane is not 64 KiB aligned"); + const std::vector> context{ + {0x2d5, 0x2000}, + {0x1b3, 0x2}, {0x1b4, 0x2}, {0x1b6, 0}, {0x207, 0}, {0x200, options.depthControl}, {0x203, 0}, + {0x2dc, 0xaa00}, {0x2f8, 0}, {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, options.targetMask}, {0x8f, 0xf}, {0x202, 0xcc0010}, + {0x1c4, 0}, {0x1c5, 1}, {0x1c3, 4}, {0x31c, 0x8710}, + {0x31b, 0}, {0x31d, 0}, {0x3b0, ((Width - 1u) << 14u) | (Height - 1u)}, + {0x3b8, 0x9000000}, {0x1e0, 0}, + {0xc, 0}, {0xd, (Height << 16u) | Width}, + {0x81, 0x80000000}, {0x82, (Height << 16u) | Width}, + {0x90, 0x80000000}, {0x91, (Height << 16u) | Width}, + {0x94, 0x80000000}, {0x95, (Height << 16u) | Width}, + {0x10f, std::bit_cast(Width / 2.0f)}, {0x110, std::bit_cast(Width / 2.0f)}, + {0x111, std::bit_cast(-(Height / 2.0f))}, {0x112, std::bit_cast(Height / 2.0f)}, + {0x113, std::bit_cast(1.0f)}, {0x114, std::bit_cast(DrawnDepth)}, + {0xb4, 0}, {0xb5, std::bit_cast(1.0f)}, + {0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0}, + {0x318, static_cast(color >> 8u)}, {0x390, static_cast(color >> 40u)}, {0x292, 2}, + {0x000, options.renderControl}, {0x002, 0}, {0x007, ((Height - 1u) << 16u) | (Width - 1u)}, + {0x008, 0}, {0x009, std::bit_cast(1.0f)}, {0x00a, 0}, {0x00b, 0}, + {0x010, 3}, {0x011, 0}, + {0x012, static_cast(depth >> 8u)}, {0x014, static_cast(depth >> 8u)}, + {0x01a, static_cast(depth >> 40u)}, {0x01c, static_cast(depth >> 40u)}, + }; + const std::vector> shader{ + {0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, 1u << 1u}, {0x8c, 0}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00a, 0}, {0x00b, 0}, + }; + Append(commands, 0x79, {0x242, 4}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value}); + Append(commands, 0x2d, {3, 2}); +} + +void CopyCoversTheDraw() { + CopyTarget.fill(Untouched); + ReferenceTarget.fill(Untouched); + std::ranges::fill(DepthMemory, MemoryDepth); + Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Registered(PixelHeader, WhitePixelCode.data(), sizeof(WhitePixelCode), 1); + std::vector commands; + AppendDraw(commands, {.color = CopyTarget.data(), .targetMask = 0, .depthControl = 0x76}); + AppendDraw(commands, {.color = CopyTarget.data(), .renderControl = 0x84}); + AppendDraw(commands, {.color = ReferenceTarget.data()}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the depth write, DB depth copy and reference draws were not submitted"); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(CopyTarget.data()), sizeof(CopyTarget)); + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(ReferenceTarget.data()), sizeof(ReferenceTarget)); + std::size_t covered = 0; + for (std::size_t index = 0; index < CopyTarget.size(); ++index) { + const bool drawn = ReferenceTarget[index] == 1.0f; + Require(drawn || ReferenceTarget[index] == Untouched, "the reference draw wrote " + std::to_string(ReferenceTarget[index]) + " at texel " + std::to_string(index)); + covered += drawn; + const auto expected = drawn ? DrawnDepth : Untouched; + Require(CopyTarget[index] == expected, "the DB depth copy holds " + std::to_string(CopyTarget[index]) + " at texel " + std::to_string(index) + ", expected " + std::to_string(expected) + (drawn ? " (the triangle covers it)" : " (outside the triangle)")); + } + Require(covered != 0 && covered != CopyTarget.size(), "the triangle covered " + std::to_string(covered) + " of " + std::to_string(CopyTarget.size()) + " texels, expected an edge through the target"); +} + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + CopyCoversTheDraw(); + AgcDriverShutdown_nid_postfix(); + std::puts("the DB depth copy wrote the drawn depth exactly where its draw rasterized"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + AgcDriverShutdown_nid_postfix(); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp index cdb94c5363..4d791ac7f7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp @@ -7,6 +7,7 @@ #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" +#include "FixtureDeviceSelection.hpp" #include #ifdef _WIN32 #ifndef NOMINMAX @@ -75,6 +76,8 @@ public: Device() { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + library = SDL_LoadObject("libvulkan.1.dylib"); #else library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -86,6 +89,12 @@ public: application.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &application; +#ifdef __APPLE__ + const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + info.enabledExtensionCount = 1; + info.ppEnabledExtensionNames = &portability; +#endif Check(function("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); std::uint32_t count = 0; const auto enumerate = function("vkEnumeratePhysicalDevices"); @@ -93,7 +102,7 @@ public: Require(count != 0, "no Vulkan device"); std::vector devices(count); Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); - context.physical = devices.front(); + context.physical = SelectFixturePhysicalDevice(devices, function("vkGetPhysicalDeviceProperties")); const auto extensions = function("vkEnumerateDeviceExtensionProperties"); Check(extensions(context.physical, nullptr, &count, nullptr), "vkEnumerateDeviceExtensionProperties"); std::vector available(count); @@ -214,6 +223,65 @@ private: std::uint8_t* block = nullptr; }; +class SparseLayers { +public: + SparseLayers() { +#ifdef _WIN32 + memory = static_cast(GuestArena::GuestArenaAllocateAtOrAbove_nid_postfix(GuestArena::GuestArenaHighestFree_nid_no_patch(BlockBytes, 65536), BlockBytes, 65536)); +#else + constexpr std::uintptr_t alignment = 65536; + void* raw = mmap(nullptr, BlockBytes + alignment, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot reserve sparse depth layers"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + alignment - 1) & ~(alignment - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + BlockBytes != begin + BlockBytes + alignment) munmap(reinterpret_cast(aligned + BlockBytes), begin + alignment - aligned); + memory = reinterpret_cast(aligned); +#endif + Require(memory != nullptr, "cannot reserve sparse depth layers"); + } + + ~SparseLayers() { +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(memory, BlockBytes); + GuestAllocations::GuestAllocationsInvalidate_nid_postfix(Address(), BlockBytes); + GuestArena::GuestArenaRelease_nid_postfix(memory, BlockBytes); +#else + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(memory, BlockBytes); + munmap(memory, BlockBytes); + GuestAllocations::GuestAllocationsInvalidate_nid_postfix(Address(), BlockBytes); +#endif + } + + void Commit(std::size_t offset, std::size_t bytes) { +#ifdef _WIN32 + GuestArena::GuestArenaCommit_nid_postfix(memory + offset, bytes, PAGE_READWRITE, 65536); +#else + Require(mmap(memory + offset, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED, "cannot commit sparse depth layer pages"); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(memory + offset, bytes); +#endif + GuestAllocations::GuestAllocationsInvalidate_nid_postfix(Address() + offset, bytes); + } + + void DecommitGuestLayer() { +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(memory + LayerBytes, LayerBytes); +#else + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(memory + LayerBytes, LayerBytes); + Require(mmap(memory + LayerBytes, LayerBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED, "cannot decommit sparse depth layer pages"); +#endif + GuestAllocations::GuestAllocationsInvalidate_nid_postfix(Address() + LayerBytes, LayerBytes); + } + + std::uint8_t* Data() const { return memory; } + std::uint64_t Address() const { return reinterpret_cast(memory); } + SparseLayers(const SparseLayers&) = delete; + SparseLayers& operator=(const SparseLayers&) = delete; + +private: + std::uint8_t* memory = nullptr; +}; + std::atomic watchedBegin{0}; std::atomic watchedEnd{0}; std::atomic watchedReads{0}; @@ -299,6 +367,59 @@ void Run(const Context& context, const WatchedBlock& block) { AgcDriver::GuestMemory::SetFlushHook(nullptr); } +void SparseGuestLayers(const Context& context) { + ClearDepthSurfaces(context.device); + SparseLayers block; + block.Commit(0, LayerBytes); + FillLayer(block.Data(), 1.0f); + const auto address = block.Address(); + DepthSurfaceView(context, DepthTarget{address, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}); + const auto resource = ArrayOver(address); + watchedBegin = address + LayerBytes; + watchedEnd = address + BlockBytes; + AgcDriver::GuestMemory::SetFlushHook(&CountReads); + const auto sample = [&](const char* when, bool changed) { + AgcDriver::GuestMemory::BumpCollectEpoch(); + watchedReads = 0; + Require(DepthSurfaceTexture(context, words, resource, components) != nullptr, "sparse depth layers were not sampled"); + Require((watchedReads.load() != 0) == changed, std::string("sparse depth layer read mismatch ") + when); + }; + try { + sample("at first use", true); + sample("with the guest layer still uncommitted", false); + block.Commit(LayerBytes, 65536); + const float value = 0.25f; + std::memcpy(block.Data() + LayerBytes, &value, sizeof(value)); + sample("after committing part of the guest layer", true); + block.Data()[LayerBytes + 64] = 1; + sample("after a CPU write to the committed part", true); + AgcDriver::GuestMemory::MarkWritten(address + LayerBytes + 128, 4); + sample("after a driver store to the committed part", true); + block.DecommitGuestLayer(); + sample("after decommitting the guest layer", true); + sample("with the guest layer still decommitted", false); + block.Commit(LayerBytes, 65536); + sample("after committing the same pages again", true); + block.DecommitGuestLayer(); + block.Commit(LayerBytes, 65536); + sample("after replacing a mapping with the same committed range", true); + block.DecommitGuestLayer(); + block.Commit(LayerBytes, LayerBytes); + FillLayer(block.Data() + LayerBytes, 0.5f); + sample("after fully committing the guest layer", true); + sample("with the fully committed layer unchanged", false); + block.DecommitGuestLayer(); + sample("after decommitting a fully committed guest layer", true); + sample("with the fully committed layer now empty", false); + } catch (...) { + AgcDriver::GuestMemory::SetFlushHook(nullptr); + ClearDepthSurfaces(context.device); + throw; + } + AgcDriver::GuestMemory::SetFlushHook(nullptr); + ClearDepthSurfaces(context.device); +} + void RecordedUploads(const Context& context, const WatchedBlock& block) { const auto resource = ArrayOver(block.Address()); Recorder recorder(context); @@ -409,6 +530,7 @@ int main() { RecordedUploads(context, block); RunCubeGather(context, block); RunCubeTiledSlice(context, block); + SparseGuestLayers(context); } device.reset(); std::puts("depth plane guest layer tests passed"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlanePartialStore.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlanePartialStore.cpp new file mode 100644 index 0000000000..8bcdfb60dc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlanePartialStore.cpp @@ -0,0 +1,293 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#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/ShaderResources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "DepthFastClearHarness.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#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 AgcDriver::Graphics::DepthPlaneStoreStatistics; +using DepthFastClearHarness::Compile; +using DepthFastClearHarness::Draw; +using DepthFastClearHarness::DrawOptions; +using DepthFastClearHarness::Require; +using DepthFastClearHarness::Surface; + +constexpr std::uint32_t PlaneWidth = 384; +constexpr std::uint32_t PlaneHeight = 200; +constexpr VkExtent2D PlaneExtent{PlaneWidth, PlaneHeight}; +constexpr std::size_t Block = 65536; +constexpr std::size_t Units = 6; +constexpr std::size_t PlaneBytes = Units * Block; +constexpr std::size_t UnitTexels = Block / sizeof(float); +constexpr std::size_t EdgeTexels = 128u * (PlaneHeight - 128u); +constexpr std::size_t PlaneTexels = static_cast(PlaneWidth) * PlaneHeight; +constexpr VkExtent2D SmallExtent{64, 32}; +constexpr std::size_t SmallColorBytes = static_cast(SmallExtent.width) * SmallExtent.height * 4u; +constexpr std::uint64_t ReadBefore = 4096; +constexpr std::size_t ReadBytes = 7u * 1024u; +alignas(256) std::array Colors{}; + +void* AllocateBlocks(std::size_t bytes, bool watched) { +#ifdef _WIN32 + void* block = nullptr; + if (watched) { + block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block); + if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes); + } else { + block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + } + Require(block != nullptr, "cannot allocate the depth plane blocks"); +#else + void* raw = mmap(nullptr, bytes + Block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the depth plane blocks"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + Block - 1) & ~static_cast(Block - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + Block) munmap(reinterpret_cast(aligned + bytes), begin + Block - aligned); + void* block = reinterpret_cast(aligned); + if (watched) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes); +#endif + GuestAllocations::Mutation().Add(block, bytes, true, true, true); + return block; +} + +void Fill(float* plane, float value) { + std::fill(plane, plane + PlaneBytes / sizeof(float), value); + AgcDriver::GuestMemory::BumpCollectEpoch(); +} + +void FillUnit(float* plane, std::size_t unit, float value) { + std::fill(plane + unit * UnitTexels, plane + (unit + 1u) * UnitTexels, value); + AgcDriver::GuestMemory::BumpCollectEpoch(); +} + +std::size_t Texels(std::size_t unit) { + return unit < 3u ? UnitTexels : EdgeTexels; +} + +void RequireUnit(const float* plane, std::size_t unit, float depth, std::size_t count, const std::string& what) { + const auto* first = plane + unit * UnitTexels; + const auto held = static_cast(std::count(first, first + UnitTexels, depth)); + Require(held == count, what + ": unit " + std::to_string(unit) + " holds " + std::to_string(held) + " texels of " + std::to_string(depth) + " instead of " + std::to_string(count)); +} + +DepthPlaneStoreStatistics Since(const DepthPlaneStoreStatistics& before) { + const auto now = AgcDriver::Graphics::DepthPlaneStoreCounts(); + DepthPlaneStoreStatistics difference; + difference.stores = now.stores - before.stores; + difference.units = now.units - before.units; + difference.bytes = now.bytes - before.bytes; + return difference; +} + +std::vector> ReadStart(AgcDriver::VulkanDevice& device, std::uint64_t base) { + auto stored = AgcDriver::Graphics::FlushDepthPlanes(base - ReadBefore, ReadBytes); + device.WaitIdle(); + return stored; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Require(AgcDriver::Graphics::DepthSliceBytes(PlaneExtent, 4u) == PlaneBytes, "the 384x200 depth plane is not six 64 KiB tiles"); + auto* blocks = static_cast(AllocateBlocks(Block + PlaneBytes, true)); + auto* plane = reinterpret_cast(blocks + Block); + const auto base = reinterpret_cast(plane); + const auto shaders = Compile(*device); + const auto swizzle = AgcDriver::Graphics::XorSwizzleMode(AgcDriver::Graphics::TextureTileMode::kZ64KBX); + const Surface depth{base, 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, swizzle}; + 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}; + + 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"); + auto before = AgcDriver::Graphics::DepthPlaneStoreCounts(); + const auto first = ReadStart(*device, base); + auto stored = Since(before); + Require(first.size() == 1 && first[0].first == base && first[0].second == Block, "a read ending 3 KiB into the stale plane did not store exactly its first 64 KiB unit"); + Require(stored.stores == 1 && stored.units == 1 && stored.bytes == Block, "a read ending 3 KiB into the stale plane did not count one store of one unit"); + RequireUnit(plane, 0, 0.5f, UnitTexels, "after a read of the first unit"); + for (std::size_t unit = 1; unit < Units; ++unit) RequireUnit(plane, unit, 1.0f, UnitTexels, "after a read of the first unit, a unit it does not overlap"); + Require(AgcDriver::Graphics::DepthPlanesStale(), "a store of one unit left no stale plane"); + + before = AgcDriver::Graphics::DepthPlaneStoreCounts(); + Require(ReadStart(*device, base).empty(), "the same read again stored the first unit again"); + stored = Since(before); + Require(stored.stores == 0 && stored.units == 0 && stored.bytes == 0, "the same read again counted a store"); + + before = AgcDriver::Graphics::DepthPlaneStoreCounts(); + const auto rest = AgcDriver::Graphics::FlushDepthPlanes(base, PlaneBytes); + device->WaitIdle(); + stored = Since(before); + Require(rest.size() == 1 && rest[0].first == base + Block && rest[0].second == PlaneBytes - Block, "a read of the whole plane did not store the five units still stale"); + Require(stored.stores == 1 && stored.units == Units - 1u && stored.bytes == PlaneBytes - Block, "a read of the whole plane did not count one store of five units"); + for (std::size_t unit = 0; unit < Units; ++unit) RequireUnit(plane, unit, 0.5f, Texels(unit), "after a read of the whole plane"); + for (std::size_t unit = 3; unit < Units; ++unit) RequireUnit(plane, unit, 1.0f, UnitTexels - EdgeTexels, "after a read of the whole plane, the padding below the last row"); + Require(!AgcDriver::Graphics::DepthPlanesStale(), "a read of the whole plane left it stale"); + + if (!AgcDriver::GuestMemory::WriteWatched() || !AgcDriver::GuestMemory::Watched(base, PlaneBytes)) { + std::puts("guest memory is not write-watched here: guest writes into a partly stored plane are not exercised"); + } else { + Fill(plane, 1.0f); + Require(draw(depth, writeHalf) == PlaneTexels, "the draw writing depth 0.5 over the reloaded plane did not cover it"); + Require(!ReadStart(*device, base).empty(), "a read of the first unit of the drawn plane stored nothing"); + FillUnit(plane, 4, 0.75f); + draw(depth, {.depthTest = false}); + before = AgcDriver::Graphics::DepthPlaneStoreCounts(); + Require(!AgcDriver::Graphics::FlushDepthPlanes(base, PlaneBytes).empty(), "a read of the whole plane after a guest write into unit 4 stored nothing"); + device->WaitIdle(); + stored = Since(before); + Require(stored.units == 4 && stored.bytes == 4u * Block, "a read of the whole plane after a guest write into unit 4 did not store the four units still stale"); + for (const std::size_t unit : {0u, 1u, 2u, 3u, 5u}) RequireUnit(plane, unit, 0.5f, Texels(unit), "a guest write into unit 4 after unit 0 was stored, then a read of the whole plane"); + RequireUnit(plane, 4, 0.75f, UnitTexels, "a guest write into unit 4 after unit 0 was stored, then a read of the whole plane, the written unit"); + Require(draw(depth, {.minDepth = 0.6f, .maxDepth = 0.6f}) == EdgeTexels, "a draw at depth 0.6 did not pass LESS exactly over the unit the guest wrote 0.75 into"); + + Fill(plane, 1.0f); + Require(draw(depth, writeHalf) == PlaneTexels, "the draw writing depth 0.5 before a guest write and an unmatched view did not cover the plane"); + Require(!ReadStart(*device, base).empty(), "a read of the first unit before a guest write and an unmatched view stored nothing"); + FillUnit(plane, 2, 0.75f); + const std::array words{0, 0, 0, 0, PlaneWidth, PlaneHeight, 1, 0}; + AgcDriver::Graphics::GuestTextureResource levels{}; + levels.baseAddress = base; + levels.width = PlaneWidth; + levels.height = PlaneHeight; + levels.mipCount = 2; + levels.lastLevel = 1; + levels.dimension = AgcDriver::Graphics::TextureDimension::k2D; + levels.tileMode = AgcDriver::Graphics::TextureTileMode::kZ64KBX; + levels.format = 22; + Require(AgcDriver::Graphics::DepthSurfaceTexture(AgcDriver::VulkanDeviceTestAccess::ContextOf(*device), words, levels, {}) == nullptr, "a view with two levels of a plane the guest wrote was served by the depth surface"); + device->WaitIdle(); + Require(!AgcDriver::Graphics::DepthSurfaceAt(base), "a view with two levels of a plane the guest wrote left the depth surface live"); + for (const std::size_t unit : {0u, 1u, 3u, 4u, 5u}) RequireUnit(plane, unit, 0.5f, Texels(unit), "the retire of a partly stored plane the guest wrote into"); + RequireUnit(plane, 2, 0.75f, UnitTexels, "the retire of a partly stored plane the guest wrote into, the written unit"); + } + + Fill(plane, 1.0f); + Require(draw(depth, writeHalf) == PlaneTexels, "the draw writing depth 0.5 under a later color target did not cover the plane"); + Require(!ReadStart(*device, base).empty(), "a read of the first unit before a color target stored nothing"); + auto* smallPlane = AllocateBlocks(Block, true); + const Surface smallDepth{reinterpret_cast(smallPlane), 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, swizzle}; + const std::span overLastUnit(reinterpret_cast(plane + 5u * UnitTexels), SmallColorBytes); + Draw(*device, shaders, smallDepth, {.depthTest = false}, SmallExtent, overLastUnit); + Require(!AgcDriver::Graphics::DepthSurfaceAt(base), "a color target over the last unit left the depth surface live"); + for (std::size_t unit = 0; unit < 5u; ++unit) RequireUnit(plane, unit, 0.5f, Texels(unit), "a color target over the last unit of a partly stored plane"); + + auto* looseBlocks = static_cast(AllocateBlocks(Block + PlaneBytes, false)); + auto* loose = reinterpret_cast(looseBlocks + Block); + const auto looseBase = reinterpret_cast(loose); + if (AgcDriver::GuestMemory::Watched(looseBase, PlaneBytes)) { + std::puts("memory outside the guest arena is write-watched here: a partial store over unwatched memory is not exercised"); + } else { + const Surface unwatched{looseBase, 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, swizzle}; + Fill(loose, 1.0f); + Require(draw(unwatched, writeHalf) == PlaneTexels, "the draw writing depth 0.5 into an unwatched plane did not cover it"); + before = AgcDriver::Graphics::DepthPlaneStoreCounts(); + Require(ReadStart(*device, looseBase).size() == 1, "a read of the first unit of an unwatched plane did not store it"); + stored = Since(before); + Require(stored.units == 1 && stored.bytes == Block, "a read of the first unit of an unwatched plane did not count one unit"); + RequireUnit(loose, 0, 0.5f, UnitTexels, "unwatched plane after a read of the first unit"); + for (std::size_t unit = 1; unit < Units; ++unit) RequireUnit(loose, unit, 1.0f, UnitTexels, "unwatched plane after a read of the first unit, a unit it does not overlap"); + Require(draw(unwatched, {.minDepth = 0.6f, .maxDepth = 0.6f}) == 0u, "a draw at depth 0.6 passed LESS over unwatched units still holding depth 0.5 on the GPU"); + } + + auto* arrayBlocks = static_cast(AllocateBlocks(3u * PlaneBytes, true)); + auto* layerPlane = reinterpret_cast(arrayBlocks + PlaneBytes); + AgcDriver::Graphics::GuestTextureResource array{}; + array.baseAddress = reinterpret_cast(arrayBlocks); + array.width = PlaneWidth; + array.height = PlaneHeight; + array.depthOrLastArray = 2; + array.mipCount = 1; + array.tileMode = AgcDriver::Graphics::TextureTileMode::kR64KBX; + array.dimension = AgcDriver::Graphics::TextureDimension::k2DArray; + array.format = 22; + array.dstSelX = 4; + array.dstSelY = 5; + array.dstSelZ = 6; + array.dstSelW = 7; + Require(AgcDriver::Graphics::DescribeSurface(array).layerBytes == PlaneBytes, "a layer of the 384x200 R32 array is not six 64 KiB tiles"); + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device); + const auto lookup = [&](std::uint32_t first, std::uint32_t count) { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + return AgcDriver::Graphics::CachedStorageLayers(context, array, first, count); + }; + Fill(layerPlane, 1.0f); + const auto arrayImage = lookup(0, 1); + device->WaitIdle(); + const auto provedBefore = AgcDriver::Graphics::StorageTexture::RefreshesProved(); + Require(lookup(0, 1) == arrayImage && lookup(0, 1) == arrayImage, "a lookup of the first layer of an unchanged array replaced its cached image"); + if (!AgcDriver::GuestMemory::WriteWatched() || !AgcDriver::GuestMemory::Watched(array.baseAddress, 3u * PlaneBytes)) { + std::puts("guest memory is not write-watched here: lookups of array layers through a refresh proof are not exercised"); + } else { + Require(AgcDriver::Graphics::StorageTexture::RefreshesProved() != provedBefore, "repeated lookups of the first layer of an unchanged array proved no refresh"); + } + const Surface layerDepth{reinterpret_cast(layerPlane), 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, swizzle}; + Require(draw(layerDepth, writeHalf) == PlaneTexels, "the draw writing depth 0.5 over the second layer of an array did not cover the plane"); + before = AgcDriver::Graphics::DepthPlaneStoreCounts(); + Require(lookup(0, 1) == arrayImage, "a depth draw over the second layer of an array replaced the array's cached image"); + device->WaitIdle(); + stored = Since(before); + Require(stored.stores == 0, "a lookup of the first layer of an array stored the depth plane over its second layer"); + for (std::size_t unit = 0; unit < Units; ++unit) RequireUnit(layerPlane, unit, 1.0f, UnitTexels, "a lookup of the first layer of an array, the depth plane over its second layer"); + Require(AgcDriver::Graphics::DepthPlanesStale(), "a lookup of the first layer of an array left no stale plane"); + + before = AgcDriver::Graphics::DepthPlaneStoreCounts(); + static_cast(lookup(0, 2)); + device->WaitIdle(); + stored = Since(before); + Require(stored.stores == 1 && stored.units == Units, "a lookup of the first two layers of a three-layer array did not store the depth plane over its second layer"); + for (std::size_t unit = 0; unit < Units; ++unit) RequireUnit(layerPlane, unit, 0.5f, Texels(unit), "a lookup of the first two layers of a three-layer array, the depth plane over its second layer"); + + Require(draw(layerDepth, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS, .minDepth = 0.25f, .maxDepth = 0.25f}) == PlaneTexels, "the draw writing depth 0.25 over the second layer of an array did not cover the plane"); + before = AgcDriver::Graphics::DepthPlaneStoreCounts(); + static_cast(lookup(0, 3)); + device->WaitIdle(); + stored = Since(before); + Require(stored.stores == 1 && stored.units == Units, "a lookup of every layer of an array did not store the depth plane over its second layer"); + for (std::size_t unit = 0; unit < Units; ++unit) RequireUnit(layerPlane, unit, 0.25f, Texels(unit), "a lookup of every layer of an array, the depth plane over its second layer"); + std::puts("depth plane partial store 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/DepthPlaneSampling.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp index 33c6cfdb6b..f77432e092 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp @@ -434,8 +434,9 @@ void RefusalCase(AgcDriver::VulkanDevice& device, GuestBlock& block) { } } -int main() { +int main(int argc, char** argv) { try { + const bool evicting = argc > 1 && std::string(argv[1]) == "--evicting"; GuestBlock block; const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; @@ -452,6 +453,10 @@ int main() { PaddedD16CubeCase(*device, block, output); 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"); + } std::puts("depth plane sampling tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorage.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorage.cpp index 27236f4199..0c706f407d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorage.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorage.cpp @@ -1,15 +1,13 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" -#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" -#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" -#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "VulkanTestDevice.hpp" +#include "FixtureDeviceSelection.hpp" #include #include #include @@ -32,7 +30,6 @@ using namespace AgcDriver::Graphics; constexpr std::uint32_t Side = 64; constexpr std::size_t BlockBytes = 65536; alignas(4096) std::array Storage{}; -alignas(4096) std::array CopyStorage{}; constexpr std::uint32_t Format32Float = 22; constexpr std::uint32_t Format8Uint = 5; @@ -41,6 +38,8 @@ public: Device() { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + library = SDL_LoadObject("libvulkan.1.dylib"); #else library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -52,6 +51,12 @@ public: application.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &application; +#ifdef __APPLE__ + const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + info.enabledExtensionCount = 1; + info.ppEnabledExtensionNames = &portability; +#endif Check(function("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); std::uint32_t count = 0; const auto enumerate = function("vkEnumeratePhysicalDevices"); @@ -59,7 +64,7 @@ public: Require(count != 0, "no Vulkan device"); std::vector devices(count); Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); - context.physical = devices.front(); + context.physical = SelectFixturePhysicalDevice(devices, function("vkGetPhysicalDeviceProperties")); const auto extensions = function("vkEnumerateDeviceExtensionProperties"); Check(extensions(context.physical, nullptr, &count, nullptr), "vkEnumerateDeviceExtensionProperties"); std::vector available(count); @@ -254,9 +259,17 @@ void Run(Context context, std::uint8_t* block) { layeredStencil.depthOrLastArray = 1; ExpectFailure([&] { static_cast(DepthSurfaceStorage(context, {}, layeredStencil, 0)); }, "storage image access to depth/stencil surface", "a two-layer 2D array view of the stencil plane was accepted"); layeredDepth.depthOrLastArray = 1; - ExpectFailure([&] { static_cast(DepthSurfaceStorage(context, {}, layeredDepth, 0)); }, "with a layer pitch of", "a two-layer 2D array view of the depth plane with a layer pitch other than the depth slice size was accepted"); + const auto layerPitch = static_cast(DescribeSurface(layeredDepth).GuestLayerOffset(1)); + const auto sliceBytes = DepthSliceBytes({Side, Side}, 4u); + Require(layerPitch != sliceBytes, "the test's layered depth view has the depth slice size as its layer pitch"); + const auto layered = DepthSurfaceStorage(context, {}, layeredDepth, 0); + Require(layered != nullptr && layered->ImageLayers() == 2, "a two-layer 2D array view over the depth plane's guest layer pitch was not taken as a storage image"); + submit(recorder); + readBack(*layered); + Require(CountFloats(block + layerPitch, 0.f) == Side * Side, "the two-layer 2D array view did not take the layer that only overlaps the depth slice from guest memory"); + Require(CountFloats(block, 0.5f) == Side * Side, "the two-layer 2D array view did not receive the layer the depth surface holds"); layeredDepth.baseArray = 1; - ExpectFailure([&] { static_cast(DepthSurfaceStorage(context, {}, layeredDepth, 0)); }, "with a layer pitch of", "a 2D array view of layer 1 of the depth plane with a layer pitch other than the depth slice size was accepted"); + Require(DepthSurfaceStorage(context, {}, layeredDepth, 0) != nullptr, "a 2D array view from layer 1 of the depth plane was not taken as a storage image"); const auto own = StorageTexture::Create(context, *context.detiler, PlaneResource(address, Format32Float), 0); const std::array eighth{std::bit_cast(0.125f), std::bit_cast(0.125f), std::bit_cast(0.125f), std::bit_cast(0.125f)}; Require(own->FillClear(eighth, StorageTexture::WholeImage, refusal), refusal != nullptr ? refusal : "the storage image could not be filled"); @@ -267,6 +280,24 @@ void Run(Context context, std::uint8_t* block) { submit(recorder); readBack(*reread); Require(CountFloats(block, 0.125f) == Side * Side, "a storage image written over the depth plane did not reach the depth surface"); + const float overlapping = 0.375f; + for (std::size_t i = 0; i < Side * Side; ++i) std::memcpy(block + layerPitch + i * sizeof(overlapping), &overlapping, sizeof(overlapping)); + auto swizzled = PlaneResource(address, Format32Float); + swizzled.tileMode = TextureTileMode::kStandard64KB; + const auto tiled = DepthSurfaceStorage(context, {}, swizzled, 0); + Require(tiled != nullptr, "a 64 KiB depth plane has no storage image"); + const std::array pendingFill{std::bit_cast(0.75f), std::bit_cast(0.75f), std::bit_cast(0.75f), std::bit_cast(0.75f)}; + Require(tiled->FillClear(pendingFill, StorageTexture::WholeImage, refusal), refusal != nullptr ? refusal : "the 64 KiB depth plane's storage image could not be filled"); + submit(recorder); + auto spanning = layeredDepth; + spanning.baseArray = 0; + spanning.depthOrLastArray = 3; + const auto spanningView = DepthSurfaceStorage(context, {}, spanning, 0); + Require(spanningView != nullptr && spanningView->ImageLayers() == 4, "a four-layer 2D array view over a 64 KiB depth plane was not taken as a storage image"); + Require(CountFloats(block, 0.75f) == Side * Side, "the pending depth result was not stored into guest memory before the layered view was created"); + submit(recorder); + readBack(*spanningView); + Require(CountFloats(block + layerPitch, overlapping) == Side * Side, "the layer that only overlaps the depth surface did not hold the guest bytes that were there"); Require(!DepthSurfaceStoreBack(context, *StorageTexture::Create(context, *context.detiler, PlaneResource(address, Format32Float, Side / 2), 0)), "a storage image of another extent over the depth memory was stored into the depth surface"); } @@ -305,69 +336,6 @@ void RunPairings(Context context, std::uint8_t* block) { ExpectFailure([&] { static_cast(DepthSurfaceStoreBack(context, *depth)); }, "through a view that another view of the plane", "a store back through a replaced view was taken by an older pairing that covers the plane"); } -void DepthCopyCase(Context context, std::uint8_t* block) { - const auto colorAddress = reinterpret_cast(block); - const auto depthAddress = colorAddress + BlockBytes / 4; - Recorder recorder(context); - recorder.Activate(); - const auto submit = [&] { - recorder.Submit(); - Check(context.Function("vkQueueWaitIdle")(context.queue), "vkQueueWaitIdle"); - }; - ColorTarget color{}; - color.address = colorAddress; - color.surfaceAddress = colorAddress; - color.extent = {Side, Side}; - color.surfaceExtent = color.extent; - color.format = VK_FORMAT_R32_SFLOAT; - color.elementBytes = 4; - color.tileMode = ColorTileMode::RenderTarget; - color.bytes = ColorTargetLayout(Side, Side, ColorTileMode::RenderTarget, 4).Bytes(); - const auto copy = [&](std::uint32_t samples, std::uint32_t sample, VkExtent2D extent) { - RunDepthCopyPass(context, DepthCopyPass{depthAddress, extent, samples, sample, color}); - submit(); - const auto resident = CachedStorageSurface(context, SurfaceForTarget(color)); - resident->MarkDirty(); - resident->WriteBack(); - submit(); - }; - const auto countBlock = [&](float value) { - std::size_t count = 0; - for (std::size_t offset = 0; offset < BlockBytes; offset += sizeof(float)) { - float texel = 0; - std::memcpy(&texel, block + offset, sizeof(texel)); - if (texel == value) ++count; - } - return count; - }; - std::fill(block, block + BlockBytes, std::uint8_t{0}); - ExpectFailure([&] { copy(1, 0, {Side, Side}); }, "no draw rendered", "a DB depth copy from memory without a depth surface was accepted"); - const auto sampledCounts = context.limits.sampledImageDepthSampleCounts & context.limits.framebufferDepthSampleCounts; - const std::uint32_t samples = (sampledCounts & VK_SAMPLE_COUNT_2_BIT) != 0 ? 2u : (sampledCounts & VK_SAMPLE_COUNT_4_BIT) != 0 ? 4u : 0u; - if (samples != 0) { - Require(DepthSurfaceView(context, DepthTarget{depthAddress, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 0.375f, 0, 0, false, samples}) != VK_NULL_HANDLE, "the multisample depth surface was not created"); - for (std::uint32_t sample = 0; sample < samples; ++sample) { - std::fill(block, block + BlockBytes, std::uint8_t{0}); - copy(samples, sample, {Side, Side}); - Require(countBlock(0.375f) == Side * Side, "the DB depth copy of sample " + std::to_string(sample) + " of " + std::to_string(samples) + " did not store the cleared depth in every texel of the R32 float target"); - } - ExpectFailure([&] { copy(samples, samples, {Side, Side}); }, "does not have", "a DB depth copy of a sample past the last of a multisample surface was accepted"); - ExpectFailure([&] { copy(samples, 0, {Side / 2, Side / 2}); }, "of another size", "a DB depth copy of another extent than the depth surface was accepted"); - ClearDepthSurfaces(context.device); - } - if ((context.limits.sampledImageDepthSampleCounts & VK_SAMPLE_COUNT_2_BIT) == 0) { - Require(DepthSurfaceView(context, DepthTarget{depthAddress, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 0.375f, 0, 0, false, 2}) != VK_NULL_HANDLE, "the unsampleable two-sample depth surface was not created"); - ExpectFailure([&] { copy(2, 0, {Side, Side}); }, "cannot sample at its sample count", "a DB depth copy from an unsampleable two-sample depth surface was accepted"); - ClearDepthSurfaces(context.device); - std::puts("DB depth copy: unsupported two-sample source refused cleanly"); - } - Require(DepthSurfaceView(context, DepthTarget{depthAddress, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 0.625f, 0, 0, false, 1}) != VK_NULL_HANDLE, "the one-sample depth surface was not created"); - std::fill(block, block + BlockBytes, std::uint8_t{0}); - copy(1, 0, {Side, Side}); - Require(countBlock(0.625f) == Side * Side, "the DB depth copy of a one-sample depth surface did not store the cleared depth in every texel of the R32 float target"); - ExpectFailure([&] { copy(1, 1, {Side, Side}); }, "does not have", "a DB depth copy of a second sample of a one-sample surface was accepted"); -} - } int main() { @@ -388,11 +356,6 @@ int main() { { std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); Run(context, block); - auto* copyBlock = CopyStorage.data() + (BlockBytes - reinterpret_cast(CopyStorage.data()) % BlockBytes) % BlockBytes; - GuestAllocations::Mutation().Add(copyBlock, BlockBytes, true, true, true); - DepthCopyCase(context, copyBlock); - ClearCachedTextures(context.device); - GuestAllocations::Mutation().Remove(copyBlock); ClearDepthSurfaces(context.device); RunPairings(context, block); ClearDepthSurfaces(context.device); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthSurfaceLastWriter.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthSurfaceLastWriter.cpp index b7f809f6ee..cdb726b3bd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthSurfaceLastWriter.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthSurfaceLastWriter.cpp @@ -4,6 +4,7 @@ #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" #include "VulkanTestDevice.hpp" +#include "FixtureDeviceSelection.hpp" #include #ifdef _WIN32 #ifndef NOMINMAX @@ -36,6 +37,8 @@ public: Device() { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + library = SDL_LoadObject("libvulkan.1.dylib"); #else library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -47,6 +50,12 @@ public: application.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &application; +#ifdef __APPLE__ + const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + info.enabledExtensionCount = 1; + info.ppEnabledExtensionNames = &portability; +#endif Check(function("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); std::uint32_t count = 0; const auto enumerate = function("vkEnumeratePhysicalDevices"); @@ -54,7 +63,7 @@ public: Require(count != 0, "no Vulkan device"); std::vector devices(count); Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); - context.physical = devices.front(); + context.physical = SelectFixturePhysicalDevice(devices, function("vkGetPhysicalDeviceProperties")); const auto queues = function("vkGetPhysicalDeviceQueueFamilyProperties"); queues(context.physical, &count, nullptr); std::vector families(count); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DeviceQueuePlan.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DeviceQueuePlan.cpp new file mode 100644 index 0000000000..032a78ccc8 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DeviceQueuePlan.cpp @@ -0,0 +1,104 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr VkQueueFlags Graphics = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT; +constexpr VkQueueFlags Sparse = VK_QUEUE_SPARSE_BINDING_BIT; +constexpr std::uint32_t Own = VK_QUEUE_FAMILY_IGNORED; + +struct Case { + const char* name; + std::vector families; + bool settles; + std::uint32_t mainCount; + bool settle; + std::uint32_t settleFamily; + std::uint32_t settleIndex; + bool sparseBinding; + std::uint32_t sparseFamily; + std::uint32_t sparseIndex; +}; + +VkQueueFamilyProperties family(VkQueueFlags flags, std::uint32_t queues) { + VkQueueFamilyProperties properties{}; + properties.queueFlags = flags; + properties.queueCount = queues; + return properties; +} + +std::string describe(const AgcDriver::DeviceQueuePlan& plan) { + return "main " + std::to_string(plan.mainCount) + " queues, present " + (plan.present ? std::to_string(plan.presentIndex) : std::string("none")) + ", settle " + (plan.settle ? std::to_string(plan.settleFamily) + ":" + std::to_string(plan.settleIndex) : std::string("none")) + ", sparse " + (!plan.sparseBinding ? std::string("none") : plan.sparseFamily == Own ? std::string("on the main queue") : std::to_string(plan.sparseFamily) + ":" + std::to_string(plan.sparseIndex)); +} + +void check(const Case& test) { + const auto plan = AgcDriver::PlanDeviceQueues(test.families, 0, test.settles, true, false); + const auto dense = AgcDriver::PlanDeviceQueues(test.families, 0, test.settles, false, false); + Require(plan.mainCount == dense.mainCount && plan.settle == dense.settle && plan.settleFamily == dense.settleFamily && plan.settleIndex == dense.settleIndex, std::string("device queue plan, ") + test.name + ": sparse binding changed the queues: " + describe(dense) + " without it, " + describe(plan) + " with it"); + Require(!dense.sparseBinding && dense.sparseFamily == Own, std::string("device queue plan, ") + test.name + ": a device without sparse binding got a sparse queue: " + describe(dense)); + const bool expected = plan.mainCount == test.mainCount && plan.settle == test.settle && (!test.settle || (plan.settleFamily == test.settleFamily && plan.settleIndex == test.settleIndex)) && plan.sparseBinding == test.sparseBinding && plan.sparseFamily == test.sparseFamily && (test.sparseFamily == Own || plan.sparseIndex == test.sparseIndex); + Require(expected && !plan.present, std::string("device queue plan, ") + test.name + ": got " + describe(plan)); + const auto windowed = AgcDriver::PlanDeviceQueues(test.families, 0, test.settles, true, true); + const bool kept = windowed.settle == plan.settle && windowed.settleFamily == plan.settleFamily && windowed.settleIndex == plan.settleIndex && windowed.sparseBinding == plan.sparseBinding && windowed.sparseFamily == plan.sparseFamily && windowed.sparseIndex == plan.sparseIndex; + const bool counted = windowed.present ? windowed.presentIndex == plan.mainCount && windowed.mainCount == plan.mainCount + 1 : windowed.mainCount == plan.mainCount && plan.mainCount >= test.families[0].queueCount; + Require(kept && counted, std::string("device queue plan, ") + test.name + ": a window changed more than the presentation queue: " + describe(plan) + " without it, " + describe(windowed) + " with it"); +} + +struct WindowCase { + const char* name; + std::vector families; + bool settles; + std::uint32_t mainCount; + bool present; + std::uint32_t presentIndex; + bool settle; + std::uint32_t settleFamily; + std::uint32_t settleIndex; + std::uint32_t sparseFamily; + std::uint32_t sparseIndex; +}; + +void check(const WindowCase& test) { + const auto plan = AgcDriver::PlanDeviceQueues(test.families, 0, test.settles, true, true); + const bool expected = plan.mainCount == test.mainCount && plan.present == test.present && (!test.present || plan.presentIndex == test.presentIndex) && plan.settle == test.settle && (!test.settle || (plan.settleFamily == test.settleFamily && plan.settleIndex == test.settleIndex)) && plan.sparseFamily == test.sparseFamily && (test.sparseFamily == Own || plan.sparseIndex == test.sparseIndex); + Require(expected, std::string("device queue plan with a window, ") + test.name + ": got " + describe(plan)); +} + +} + +int main() { + try { + const std::vector cases{ + {"one family with one queue", {family(Graphics | Sparse, 1)}, true, 1, false, 0, 0, true, Own, 0}, + {"one family with one queue, no settles", {family(Graphics | Sparse, 1)}, false, 1, false, 0, 0, true, Own, 0}, + {"settle family with one queue that also binds", {family(Graphics | Sparse, 1), family(VK_QUEUE_TRANSFER_BIT | Sparse, 1)}, true, 1, true, 1, 0, true, Own, 0}, + {"settle family with two queues that also binds", {family(Graphics, 1), family(VK_QUEUE_TRANSFER_BIT | Sparse, 2)}, true, 1, true, 1, 0, true, 1, 1}, + {"settle on the main family", {family(Graphics | Sparse, 2), family(VK_QUEUE_TRANSFER_BIT | Sparse, 1)}, true, 2, true, 0, 1, true, 1, 0}, + {"sparse-only family preferred", {family(Graphics | Sparse, 16), family(VK_QUEUE_TRANSFER_BIT | Sparse, 2), family(Sparse, 1)}, true, 2, true, 0, 1, true, 2, 0}, + {"no settles, a second family binds", {family(Graphics | Sparse, 1), family(VK_QUEUE_TRANSFER_BIT | Sparse, 1)}, false, 1, false, 0, 0, true, 1, 0}, + {"no queue binds", {family(Graphics, 1), family(VK_QUEUE_TRANSFER_BIT, 1)}, true, 1, true, 1, 0, false, Own, 0}, + {"only the settle queue binds", {family(Graphics, 1), family(VK_QUEUE_TRANSFER_BIT | Sparse, 1)}, true, 1, true, 1, 0, false, Own, 0}, + }; + for (const auto& test : cases) check(test); + const std::vector windowCases{ + {"one queue with a window", {family(Graphics | Sparse, 1)}, true, 1, false, 0, false, 0, 0, Own, 0}, + {"two queues, no settles, with a window", {family(Graphics, 2)}, false, 2, true, 1, false, 0, 0, Own, 0}, + {"two queues taken by the main and settle queues, with a window", {family(Graphics, 2)}, true, 2, false, 0, true, 0, 1, Own, 0}, + {"settle on the main family, three queues, with a window", {family(Graphics | Sparse, 3), family(VK_QUEUE_TRANSFER_BIT | Sparse, 1)}, true, 3, true, 2, true, 0, 1, 1, 0}, + {"one main queue and settle on another family, with a window", {family(Graphics, 1), family(VK_QUEUE_TRANSFER_BIT, 1)}, true, 1, false, 0, true, 1, 0, Own, 0}, + {"sparse-only family preferred, with a window", {family(Graphics | Sparse, 16), family(VK_QUEUE_TRANSFER_BIT | Sparse, 2), family(Sparse, 1)}, true, 3, true, 2, true, 0, 1, 2, 0}, + }; + for (const auto& test : windowCases) check(test); + std::puts("device queue plan 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/DirectWriteCompletion.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DirectWriteCompletion.cpp new file mode 100644 index 0000000000..889e5f46cb --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DirectWriteCompletion.cpp @@ -0,0 +1,266 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using AgcDriver::Graphics::StorageTexture; +constexpr std::uint32_t Side = 256; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t TileR64KBX = 0x1b; +constexpr std::uint32_t Type2D = 9; +constexpr std::size_t Block = 0x10000; +constexpr std::size_t SurfaceBytes = 4 * Block; +constexpr std::size_t SettledImportBytes = 64 * Block; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::uint32_t Stored = 0x12345678u; +constexpr std::uint32_t Filled = 0x9abcdef0u; +constexpr std::uint32_t Written = 0x0badf00du; + +alignas(256) constexpr std::array StoreCode{ + 0x7e040300, 0x7e060280, 0x7e0202ff, Stored, 0xf0200108, 0x00020102, 0xbf810000, +}; + +alignas(256) constexpr std::array FillCode{ + 0xd7460004, 0x04010c08, 0x7e000204, 0x7e020205, 0x7e040206, 0x7e060207, 0xe01c2000, 0x80000004, 0xe01c2000, 0x80020004, 0xbf810000, +}; + +std::uint64_t AddressOf(const void* data) { + return static_cast(reinterpret_cast(data)); +} + +std::array TextureDescriptor(const std::uint32_t* texels) { + const auto address = AddressOf(texels); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Side - 1u) & 3u) << 30u), + ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), + 0xfacu | (TileR64KBX << 20u) | (Type2D << 28u), + 0u, + 0u, + 0u, + 0u, + }; +} + +void Dispatch(std::vector& words, std::span code, std::uint32_t threads, std::uint32_t resource, std::span userData, std::uint32_t groups) { + const auto registers = [&](const std::uint32_t first, const std::span values) { + words.push_back(0xc0007600u | (static_cast(values.size()) << 16u)); + words.push_back(first); + words.insert(words.end(), values.begin(), values.end()); + }; + const std::array shape{threads, 1, 1}; + registers(0x207, shape); + const auto address = AddressOf(code.data()); + const std::array program{static_cast(address >> 8u), static_cast(address >> 40u)}; + registers(0x20c, program); + const std::array layout{resource}; + registers(0x213, layout); + registers(0x240, userData); + words.insert(words.end(), {0xc0031500u, groups, 1, 1, 0x41u}); +} + +void Store(std::vector& words, const std::uint32_t* texels) { + std::array userData{}; + const auto texture = TextureDescriptor(texels); + std::copy(texture.begin(), texture.end(), userData.begin() + 8); + Dispatch(words, StoreCode, 8, static_cast(userData.size()) << 1u, userData, 1); +} + +void Fill(std::vector& words, const std::uint32_t* texels, const std::uint32_t* copied) { + const auto address = AddressOf(texels); + const auto other = AddressOf(copied); + const std::array userData{ + static_cast(address), + static_cast((address >> 32u) & 0xffffu) | (16u << 16u), + static_cast(SurfaceBytes / 16u), + 0x4bfacu, + Filled, + Filled, + Filled, + Filled, + static_cast(other), + static_cast((other >> 32u) & 0xffffu) | (16u << 16u), + static_cast(SurfaceBytes / 16u), + 0x4bfacu, + }; + Dispatch(words, FillCode, 64, (static_cast(userData.size()) << 1u) | 0x80u, userData, static_cast(SurfaceBytes / 1024u)); +} + +class Surface { +public: + Surface() { +#ifdef _WIN32 + region = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, Block)); + if (region != nullptr) GuestArena::GuestArenaCommit_nid_postfix(region, SurfaceBytes, PAGE_READWRITE, SurfaceBytes); +#else + void* mapped = mmap(nullptr, SurfaceBytes + Block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped != MAP_FAILED) { + const auto begin = reinterpret_cast(mapped); + const auto aligned = (begin + Block - 1) & ~std::uintptr_t{Block - 1}; + if (aligned != begin) munmap(mapped, aligned - begin); + if (aligned != begin + Block) munmap(reinterpret_cast(aligned + SurfaceBytes), begin + Block - aligned); + region = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(region, SurfaceBytes); + } +#endif + Require(region != nullptr, "direct write completion: cannot allocate the guest surface"); + texels = reinterpret_cast(region); + std::fill(texels, texels + SurfaceBytes / 4u, Untouched); + GuestAllocations::Mutation().Add(region, SurfaceBytes, true, true, true); + } + + ~Surface() { + GuestAllocations::Mutation().Remove(region); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(region, SurfaceBytes); + GuestArena::GuestArenaRelease_nid_postfix(region, SurfaceBytes); +#else + munmap(region, SurfaceBytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(region, SurfaceBytes); +#endif + } + + Surface(const Surface&) = delete; + Surface& operator=(const Surface&) = delete; + + std::uint32_t* Texels() const { return texels; } + +private: + std::uint8_t* region = nullptr; + std::uint32_t* texels = nullptr; +}; + +enum class Outcome { Kept, Dropped, Missed }; + +void WriteOnceRecorded(const Surface& surface) { + const auto address = AddressOf(surface.Texels()); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + for (;;) { + { + std::unique_lock hold(AgcDriver::GuestMemory::GpuMutex()); + if (StorageTexture::FindPending(address, SurfaceBytes) != nullptr) { + surface.Texels()[0] = Written; + return; + } + } + Require(std::chrono::steady_clock::now() < deadline, "direct write completion: the image store was never recorded"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +Outcome StoreAfterFill(const Surface& surface, bool cpuWrite) { + std::vector copied(SurfaceBytes / 4u, Untouched); + GuestAllocations::Mutation().Add(copied.data(), SurfaceBytes, true, true, true); + struct Unregister { + void* data; + ~Unregister() { GuestAllocations::Mutation().Remove(data); } + } unregister{copied.data()}; + std::vector words; + Fill(words, surface.Texels(), copied.data()); + Store(words, surface.Texels()); + alignas(8) static std::uint32_t gate = 0; + gate = 0; + if (cpuWrite) { + const auto at = AddressOf(&gate); + words.insert(words.end(), {0xc0053c00u, 0x13u, static_cast(at), static_cast(at >> 32u), 1u, 0xffffffffu, 0x19u}); + } + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + AgcDriver::Submit(&packet, 0); + if (cpuWrite) { + WriteOnceRecorded(surface); + std::atomic_ref(gate).store(1); + } + AgcDriverWaitIdle_nid_postfix(); + std::vector landed(SurfaceBytes); + AgcDriver::GuestMemory::Read(AddressOf(copied.data()), landed); + const auto address = AddressOf(surface.Texels()); + std::unique_lock hold(AgcDriver::GuestMemory::GpuMutex()); + const auto image = StorageTexture::FindPending(address, SurfaceBytes); + if (image == nullptr) return Outcome::Missed; + const bool current = image->Refresh(); + const bool pending = StorageTexture::FindPending(address, SurfaceBytes) == image; + StorageTexture::FlushPending(address, SurfaceBytes, nullptr, "direct write completion fixture"); + return current && pending ? Outcome::Kept : Outcome::Dropped; +} + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + struct ShutdownGuard { + ~ShutdownGuard() { try { AgcDriverShutdown_nid_postfix(); } catch (...) {} } + } shutdown; + { + Surface probe; + Require(AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor(probe.Texels()))).guestBytes == SurfaceBytes, "direct write completion: unexpected surface size"); + } + { + constexpr std::size_t Runs = SettledImportBytes / SurfaceBytes - 1; + std::vector> surfaces; + for (std::size_t index = 0; index <= 2 * Runs + 1; ++index) surfaces.push_back(std::make_unique()); + static_cast(StoreAfterFill(*surfaces[0], false)); + std::size_t kept = 0; + std::size_t dropped = 0; + for (std::size_t run = 1; run <= Runs; ++run) { + const auto outcome = StoreAfterFill(*surfaces[run], false); + kept += outcome == Outcome::Kept; + dropped += outcome == Outcome::Dropped; + } + AgcDriverWaitIdle_nid_postfix(); + std::printf("[direct write completion] %zu of %zu runs held the image results pending, %zu dropped them\n", kept + dropped, Runs, dropped); + Require(kept + dropped != 0, "direct write completion: no run held the image results pending"); + Require(dropped == 0, "direct write completion: the completion of a buffer write dropped image results recorded after it"); + static_cast(StoreAfterFill(*surfaces[Runs + 1], false)); + std::size_t keptOverWrite = 0; + std::size_t lostWrites = 0; + for (std::size_t run = Runs + 2; run <= 2 * Runs + 1; ++run) { + keptOverWrite += StoreAfterFill(*surfaces[run], true) == Outcome::Kept; + lostWrites += surfaces[run]->Texels()[0] != Written; + } + AgcDriverWaitIdle_nid_postfix(); + std::printf("[direct write completion] %zu of %zu runs with a CPU write before the completion kept the image results, %zu lost the write\n", keptOverWrite, Runs, lostWrites); + Require(keptOverWrite == 0, "direct write completion: image results were kept over a CPU write made before the completion"); + Require(lostWrites == 0, "direct write completion: a CPU write made before the completion was lost"); + } + AgcDriverShutdown_nid_postfix(); + std::puts("direct write completion 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/DispatchAheadAdoptCheck.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DispatchAheadAdoptCheck.cpp new file mode 100644 index 0000000000..e2453a66f0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DispatchAheadAdoptCheck.cpp @@ -0,0 +1,208 @@ +#include "VulkanTestDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +alignas(256) std::array CounterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000281u, 0xe0c80000u, 0x80020000u, 0xbf810000u}; +alignas(256) std::array StoreCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000202u, 0xe0700000u, 0x80020000u, 0xbf810000u}; +alignas(256) std::array SpinCode{0xf4080200u, 0xfa000000u, 0xbe840381u, 0x9304ff04u, 0x0019660du, 0x8004ff04u, 0x3c6ef35fu, 0x80828102u, 0xbf078002u, 0xbf85fff9u, 0xbf8cc07fu, 0x7e000204u, 0xe0700000u, 0x80020000u, 0xbf810000u}; + +constexpr std::uint32_t Compute = 0x20u; +constexpr std::uint32_t OtherCompute = 0x28u; +constexpr std::uint32_t SpinIterations = 1u << 20u; + +alignas(256) std::array Stale{}; +alignas(256) std::array Fresh{}; +alignas(256) std::array Target{}; +alignas(256) std::array TargetDescriptor{}; +alignas(256) std::array Spun{}; +alignas(256) std::array SpunDescriptor{}; +alignas(256) volatile std::uint32_t Gate = 1; +alignas(256) volatile std::uint32_t Done = 0; +alignas(256) volatile std::uint32_t OtherDone = 0; + +enum class Window { None, CpuStore, QueuedGpuStore }; + +std::atomic Armed{Window::None}; +std::uint32_t SpinDispatches = 16; +std::vector OtherWords; +bool SawPending = false; + +std::uint32_t Low(const volatile void* pointer) { + return static_cast(reinterpret_cast(pointer)); +} + +std::uint32_t High(const volatile void* pointer) { + return static_cast(static_cast(reinterpret_cast(pointer)) >> 32u); +} + +std::array Descriptor(const void* pointer, std::uint32_t bytes) { + return {Low(pointer), High(pointer) & 0xffffu, bytes, 0x31016facu}; +} + +void SetShaderRegister(std::vector& words, std::uint32_t offset, std::uint32_t value) { + words.insert(words.end(), {0xc0017600u, offset, value}); +} + +void Program(std::vector& words, const void* code, std::initializer_list userData) { + const auto address = static_cast(reinterpret_cast(code)); + SetShaderRegister(words, 0x20c, static_cast(address >> 8u)); + SetShaderRegister(words, 0x20d, static_cast(address >> 40u) & 0xffu); + for (const std::uint32_t offset : {0x207u, 0x208u, 0x209u}) SetShaderRegister(words, offset, 1); + SetShaderRegister(words, 0x212, 0); + SetShaderRegister(words, 0x213, static_cast(userData.size()) << 1u); + std::uint32_t offset = 0x240; + for (const auto value : userData) SetShaderRegister(words, offset++, value); +} + +void Dispatch(std::vector& words, const void* code, std::initializer_list userData) { + Program(words, code, userData); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); +} + +void WriteData(std::vector& words, const volatile std::uint32_t* target, std::uint32_t value) { + words.insert(words.end(), {0xc0033700u, 0x00100200u, Low(target), High(target), value}); +} + +void Submit(std::uint32_t queue, std::vector& words) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + Require(sceAgcDriverSubmitAcb(queue, &packet) == 0, "compute submit failed"); +} + +void QueueStoreAhead() { + OtherWords.clear(); + for (std::uint32_t index = 0; index < SpinDispatches; ++index) Dispatch(OtherWords, SpinCode.data(), {Low(SpunDescriptor.data()), High(SpunDescriptor.data()), SpinIterations}); + Dispatch(OtherWords, StoreCode.data(), {Low(TargetDescriptor.data()), High(TargetDescriptor.data()), Low(Fresh.data())}); + WriteData(OtherWords, &OtherDone, 1); + Submit(OtherCompute, OtherWords); + const auto start = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5) && OtherDone == 0) { + if (AgcDriver::DriverDetail::Driver::QueryPendingSpan(reinterpret_cast(Target.data()), sizeof(Target), {}) != AgcDriver::ShaderMemory::PendingWrite::None) { + SawPending = true; + return; + } + std::this_thread::yield(); + } +} + +void BeforeAdoption() { + const auto window = Armed.exchange(Window::None); + if (window == Window::CpuStore) reinterpret_cast(Target[0]) = Low(Fresh.data()); + else if (window == Window::QueuedGpuStore) QueueStoreAhead(); +} + +void WaitIdle() { + AgcDriverWaitIdle_nid_postfix(); + for (const auto* buffer : {&Stale, &Fresh, &Target}) AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(buffer->data()), sizeof(*buffer)); +} + +void Reset() { + WaitIdle(); + Stale = {}; + Fresh = {}; + Target = Descriptor(Stale.data(), sizeof(Stale)); + Done = 0; + OtherDone = 0; + SawPending = false; +} + +void Gated(Window window) { + Reset(); + std::vector words; + words.insert(words.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u}); + Dispatch(words, CounterCode.data(), {Low(Target.data()), High(Target.data())}); + WriteData(words, &Done, 1); + Armed = window; + Submit(Compute, words); + WaitIdle(); + Armed = Window::None; + Require(Done == 1, "the gated submission did not finish"); +} + +std::string Counts() { + return "(stale " + std::to_string(Stale[0]) + ", fresh " + std::to_string(Fresh[0]) + ")"; +} + +} + +int main() { + try { + auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + device.reset(); + Require(High(Stale.data()) == High(Fresh.data()), "the stale and fresh buffers differ in their high address word"); + TargetDescriptor = Descriptor(Target.data(), sizeof(Target)); + SpunDescriptor = Descriptor(Spun.data(), sizeof(Spun)); + AgcDriverSetAheadAdoptHook_nid_postfix(&BeforeAdoption); + Reset(); + std::vector warmUp; + Dispatch(warmUp, CounterCode.data(), {Low(Target.data()), High(Target.data())}); + WriteData(warmUp, &Done, 1); + Submit(Compute, warmUp); + std::vector otherWarmUp; + Dispatch(otherWarmUp, SpinCode.data(), {Low(SpunDescriptor.data()), High(SpunDescriptor.data()), SpinIterations}); + Dispatch(otherWarmUp, StoreCode.data(), {Low(TargetDescriptor.data()), High(TargetDescriptor.data()), Low(Fresh.data())}); + Submit(OtherCompute, otherWarmUp); + WaitIdle(); + Require(Done == 1 && Target[0] == Low(Fresh.data()), "the warm-up dispatches did not run"); + + Gated(Window::None); + Require(Stale[0] == 1 && Fresh[0] == 0, "the gated dispatch without a store in the window did not count into the stale buffer " + Counts()); + const auto adopted = AgcDriverDispatchesAdoptedAhead_nid_postfix(); + Require(adopted != 0, "the gated dispatch was not resolved ahead and adopted"); + + auto rejected = AgcDriverDispatchesRejectedAhead_nid_postfix(); + Gated(Window::CpuStore); + Require(Fresh[0] == 1 && Stale[0] == 0, "a dispatch resolved ahead whose descriptor the CPU replaced before its adoption used the old descriptor " + Counts()); + Require(AgcDriverDispatchesRejectedAhead_nid_postfix() == rejected + 1, "the CPU store in the adoption window was not caught by the adoption check"); + std::cout << "cpu store before adoption: passed\n"; + + bool conclusive = false; + for (int attempt = 0; attempt < 4 && !conclusive; ++attempt, SpinDispatches *= 4) { + rejected = AgcDriverDispatchesRejectedAhead_nid_postfix(); + Gated(Window::QueuedGpuStore); + conclusive = SawPending; + if (!conclusive) { + std::cout << "queued gpu store before adoption: the store finished before the adoption with " << SpinDispatches << " spin dispatches, retrying\n"; + continue; + } + Require(Fresh[0] == 1 && Stale[0] == 0, "a dispatch resolved ahead whose descriptor a GPU store queued on another queue replaces before its adoption used the old descriptor " + Counts()); + Require(AgcDriverDispatchesRejectedAhead_nid_postfix() == rejected + 1, "the queued GPU store in the adoption window was not caught by the adoption check"); + } + AgcDriverSetAheadAdoptHook_nid_postfix(nullptr); + if (!conclusive) { + std::cout << "queued gpu store before adoption: never observed the store pending at the adoption\n"; + AgcDriverShutdown_nid_postfix(); + return VulkanTestSkipped; + } + std::cout << "queued gpu store before adoption: passed\n"; + std::cout << "dispatch ahead adopt check test passed\n"; + AgcDriverShutdown_nid_postfix(); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { + AgcDriverShutdown_nid_postfix(); + } catch (const std::exception& shutdownError) { + std::cerr << shutdownError.what() << '\n'; + } + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheRelocation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheRelocation.cpp index a82e34a36a..45f1e60352 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheRelocation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheRelocation.cpp @@ -135,6 +135,69 @@ void MovedDataHitsTheCache(bool libraries) { Require(again.lookups == 10 && again.hits == 5, mode + ": a draw at an address seen before did not reuse the cached draw (" + std::to_string(again.hits) + " hits)"); } +void EvictedEntriesServeNoRelocation(bool libraries) { + Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + Registered(SecondVertexHeader, SecondVertexCode.data(), sizeof(SecondVertexCode), 2); + const auto mode = std::string(libraries ? "with" : "without") + " graphics pipeline libraries"; + const auto draw = [&](const float* source, const std::uint32_t* vertexCode) { + auto commands = DrawFrom({source}, vertexCode); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); + return AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters(); + }; + static std::array, 6> same{}; + for (auto& entry : same) entry = {1.0f, 0.0f, 0.0f, 0.0f}; + alignas(16) static std::array, 3> others{}; + for (std::size_t i = 0; i < others.size(); ++i) others[i] = {0.25f + 0.03125f * static_cast(i), 0.0f, 0.0f, 0.0f}; + AgcDriver::DriverDetail::DrawEntryCounters counters{}; + for (std::size_t i = 0; i < 4; ++i) counters = draw(same[i].data(), VertexCode.data()); + Require(counters.lookups == 4 && counters.hits == 0 && counters.inserts == 2, mode + ": the draws that teach the moved pointer and its contents made " + std::to_string(counters.hits) + " hits and " + std::to_string(counters.inserts) + " inserts, expected 0 and 2"); + const auto relocated = draw(same[4].data(), VertexCode.data()); + Require(relocated.hits == 1, mode + ": a draw whose data moved after the teaching draws did not reuse the cached draw (" + std::to_string(relocated.hits) + " hits)"); + auto evicted = relocated; + for (const auto& entry : others) { + draw(entry.data(), SecondVertexCode.data()); + evicted = draw(entry.data(), SecondVertexCode.data()); + } + Require(evicted.evictions - relocated.evictions >= 2 && evicted.inserts - relocated.inserts >= 2, mode + ": draws of another vertex shader did not evict the two taught entries from the draw cache (" + std::to_string(evicted.inserts - relocated.inserts) + " inserts, " + std::to_string(evicted.evictions - relocated.evictions) + " evictions; run with APS5_DRAW_CACHE_ENTRIES=2)"); + const auto after = draw(same[5].data(), VertexCode.data()); + Require(after.lookups == evicted.lookups + 1 && after.hits == evicted.hits, mode + ": a draw whose data moved reused a draw the cache had evicted, through its relocation alias (" + std::to_string(after.hits - evicted.hits) + " hits)"); +} + +void RelocatingDrawsKeepTheirDonor(bool libraries) { + Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + Registered(SecondVertexHeader, SecondVertexCode.data(), sizeof(SecondVertexCode), 2); + const auto mode = std::string(libraries ? "with" : "without") + " graphics pipeline libraries"; + const auto draw = [&](const float* source, const std::uint32_t* vertexCode) { + auto commands = DrawFrom({source}, vertexCode); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); + return AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters(); + }; + constexpr std::size_t Rounds = 3; + alignas(16) static std::array, Rounds> others{}; + for (std::size_t i = 0; i < others.size(); ++i) others[i] = {0.25f + 0.03125f * static_cast(i), 0.0f, 0.0f, 0.0f}; + static std::array, 5 + Rounds> same{}; + for (auto& entry : same) entry = {1.0f, 0.0f, 0.0f, 0.0f}; + AgcDriver::DriverDetail::DrawEntryCounters counters{}; + for (std::size_t i = 0; i < 4; ++i) counters = draw(same[i].data(), VertexCode.data()); + Require(counters.lookups == 4 && counters.hits == 0 && counters.inserts == 2, mode + ": the draws that teach the moved pointer and its contents made " + std::to_string(counters.hits) + " hits and " + std::to_string(counters.inserts) + " inserts, expected 0 and 2"); + const auto start = draw(same[4].data(), VertexCode.data()); + Require(start.hits == 1, mode + ": a draw whose data moved after the teaching draws did not reuse the cached draw (" + std::to_string(start.hits) + " hits)"); + auto relocated = start; + for (std::size_t i = 0; i < others.size(); ++i) { + draw(others[i].data(), SecondVertexCode.data()); + const auto inserted = draw(others[i].data(), SecondVertexCode.data()); + relocated = draw(same[5 + i].data(), VertexCode.data()); + Require(relocated.hits == inserted.hits + 1, mode + ": a draw whose data moved again lost the cached draw it reused before each of " + std::to_string(i + 1) + " rounds of another vertex shader's draws (" + std::to_string(inserted.inserts - start.inserts) + " inserts, " + std::to_string(inserted.evictions - start.evictions) + " evictions)"); + } + Require(relocated.evictions - start.evictions >= 2, mode + ": draws of another vertex shader evicted " + std::to_string(relocated.evictions - start.evictions) + " entries, expected at least 2 (run with APS5_DRAW_CACHE_ENTRIES=2)"); +} + void SplitRunsNameTheirSharedWords() { alignas(16) static std::array data{1u, 2u, 3u, 4u}; auto table = std::make_shared(); @@ -228,9 +291,11 @@ void MovedForwardedWordReachesStageDataHit(bool libraries) { } -int main() { +int main(int argc, char** argv) { try { - SplitRunsNameTheirSharedWords(); + const bool evicted = argc == 2 && std::string(argv[1]) == "--evicted"; + const bool kept = argc == 2 && std::string(argv[1]) == "--kept"; + if (!evicted && !kept) SplitRunsNameTheirSharedWords(); bool libraries = false; { const auto device = OpenVulkanTestDevice(); @@ -238,6 +303,18 @@ int main() { libraries = device->GraphicsPipelineLibraries(); } if (!libraries && std::getenv("APS5_NO_GPL") == nullptr) return VulkanTestSkipped; + if (evicted) { + EvictedEntriesServeNoRelocation(libraries); + AgcDriverShutdown_nid_postfix(); + std::cout << "An evicted draw served no relocation " << (libraries ? "with" : "without") << " graphics pipeline libraries\n"; + return 0; + } + if (kept) { + RelocatingDrawsKeepTheirDonor(libraries); + AgcDriverShutdown_nid_postfix(); + std::cout << "A draw relocated from a cached draw kept it cached " << (libraries ? "with" : "without") << " graphics pipeline libraries\n"; + return 0; + } MovedDataHitsTheCache(libraries); UnrelocatedAliasesStayOut(libraries); MovedForwardedWordReachesStageDataHit(libraries); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheReuse.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheReuse.cpp index ab9818c6ba..41ff2d9a50 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheReuse.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheReuse.cpp @@ -183,6 +183,20 @@ void StageDataHitsRefreshTheirWords(bool libraries) { Require(BrightestColorByte() == 255, mode + ": the draw of constant 1.0 wrote " + std::to_string(BrightestColorByte()) + ", expected 255"); } +void RefreshedStageDataVerifies() { + Registered(TriangleVertexHeader, TriangleVertexCode.data(), sizeof(TriangleVertexCode), 2); + Registered(ConstantPixelHeader, ConstantPixelCode.data(), sizeof(ConstantPixelCode), 1); + ColorMemory.fill(std::byte{0}); + PixelConstant[0] = 0.25f; + const auto cached = SubmitConstantDraws(3); + Require(BrightestColorByte() == 64, "draw-entry verification: the initial constant did not render"); + PixelConstant[0] = 1.0f; + const auto refreshed = SubmitConstantDraws(1); + Require(refreshed.verifyHits - cached.verifyHits == 1 && refreshed.stageDataHits - cached.stageDataHits == 1, "draw-entry verification: the refreshed constant did not exercise a verified stage data hit"); + Require(refreshed.verifyMismatches == cached.verifyMismatches, "draw-entry verification: refreshed stage data differed from a fresh capture"); + Require(BrightestColorByte() == 255, "draw-entry verification: the refreshed constant did not render"); +} + void RetainedPixelCacheTransitions() { auto commands = Draws(32.0f, VertexCode.data(), 1, PixelCode.data(), 1, {}); Append(commands, 0x76, {0x00c, std::bit_cast(1.0f)}); @@ -239,10 +253,14 @@ int main() { libraries = device->GraphicsPipelineLibraries(); } if (!libraries && std::getenv("APS5_NO_GPL") == nullptr) return VulkanTestSkipped; - ThirdDrawHitsTheCache(libraries); - UserDataDrawsReuseTheirShape(libraries); - StageDataHitsRefreshTheirWords(libraries); - RetainedPixelCacheTransitions(); + if (std::getenv("APS5_VERIFY_DRAW_ENTRIES") != nullptr) { + RefreshedStageDataVerifies(); + } else { + ThirdDrawHitsTheCache(libraries); + UserDataDrawsReuseTheirShape(libraries); + StageDataHitsRefreshTheirWords(libraries); + RetainedPixelCacheTransitions(); + } AgcDriverShutdown_nid_postfix(); Require(AgcDriver::Graphics::PipelineLibraryWorkers() == 0, "the driver's shutdown left the pipeline library worker running"); std::cout << "A repeated draw reused the cached draw " << (libraries ? "with" : "without") << " graphics pipeline libraries\n"; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawDataHitUserWords.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawDataHitUserWords.cpp index 13d67731a9..ed4b5280c1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawDataHitUserWords.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawDataHitUserWords.cpp @@ -34,6 +34,7 @@ alignas(256) constexpr std::array TriangleVertexCode{ 0x7e060280u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u, }; alignas(256) constexpr std::array ArithmeticPixelCode{0xf4000100u, 0xfa000000u, 0xbf8cc07fu, 0x7e000204u, 0x06000002u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u}; +alignas(256) constexpr std::array DirectBufferPixelCode{0xe0300000u, 0x80000000u, 0xbf8c3f70u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u}; alignas(256) std::array PixelConstant{}; alignas(256) std::array ColorMemory{}; @@ -47,6 +48,7 @@ struct Header { alignas(8) Header VertexHeader{}; alignas(8) Header PixelHeader{}; alignas(8) Header ArithmeticPixelHeader{}; +alignas(8) Header DirectBufferPixelHeader{}; void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { header.shader.file_header = 0x34333231u; @@ -83,10 +85,10 @@ void PixelUserData(std::vector& commands, const std::uint32_t* po Append(commands, 0x76, {0x00e, unread}); } -std::vector Setup(bool triangle = false) { +std::vector Setup(bool triangle = false, const std::uint32_t* pixelCode = nullptr, std::uint32_t pixelUserSgprs = 3) { const auto color = reinterpret_cast(ColorMemory.data()); const auto vertex = reinterpret_cast(triangle ? TriangleVertexCode.data() : VertexCode.data()); - const auto pixel = reinterpret_cast(triangle ? ArithmeticPixelCode.data() : PixelCode.data()); + const auto pixel = reinterpret_cast(pixelCode ? pixelCode : triangle ? ArithmeticPixelCode.data() : PixelCode.data()); std::vector> context{ {0x2d5, 0x2000}, {0x1b6, 0}, {0x207, 0}, {0x200, 0}, {0x203, 0x800}, @@ -110,7 +112,7 @@ std::vector Setup(bool triangle = false) { }; const std::vector> shader{ {0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, triangle ? 0u : 1u << 1u}, {0x8c, 0}, - {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, 3u << 1u}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, pixelUserSgprs << 1u}, }; if (triangle) context.insert(context.end(), {{0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0}}); std::vector commands; @@ -177,6 +179,21 @@ void UnreadUserWordsKeepTheStage(bool verify) { Require(counters.dataHits == moved.dataHits + 1, "a draw that pointed the pixel stage at another buffer descriptor was not a data-only hit: " + Counts(counters)); Require(counters.dataStagesCompiled - moved.dataStagesCompiled >= 1, "a draw that pointed the pixel stage at another buffer descriptor reused the pixel stage: " + Counts(counters)); if (!verify) Require(counters.dataStagesReused - moved.dataStagesReused == 1, "a draw that changed only the pixel stage's descriptor pointer did not reuse the vertex stage: " + Counts(counters)); + + const bool refresh = std::getenv("APS5_DRAW_PIPELINE") != nullptr; + for (const std::uint32_t unread : {0x33u, 0x44u}) { + commands.clear(); + PixelUserData(commands, second, unread); + Append(commands, 0x2d, {3, 2}); + const auto last = counters; + counters = Submit(std::move(commands)); + Require(counters.dataHits == last.dataHits + 1 && counters.inserts == last.inserts, "a draw at the moved pointer with a new unread word: " + Counts(counters)); + if (verify) continue; + const auto compiled = counters.dataStagesCompiled - last.dataStagesCompiled; + const bool expectReuse = refresh && unread == 0x44u; + Require(compiled == (expectReuse ? 0u : 1u), std::string(expectReuse ? "after a refresh, the draw at the moved pointer compiled its pixel stage again: " : "the draw at the moved pointer did not compile its pixel stage: ") + Counts(counters)); + Require(counters.shapeUpdates - last.shapeUpdates == (refresh && unread == 0x33u ? 1u : 0u), "shape refreshes: " + std::to_string(counters.shapeUpdates - last.shapeUpdates) + " (" + Counts(counters) + ")"); + } } unsigned BrightestColorByte() { @@ -208,6 +225,39 @@ void ReadUserWordsChangeTheOutput() { Require(closeTo(second, 191), "a pixel stage adding user word 2 changed to 0.5 wrote " + std::to_string(second) + ", expected 191 (" + Counts(after) + ", before: " + Counts(before) + ")"); } +void DirectBufferBaseMovesWithItsPayload() { + Registered(VertexHeader, TriangleVertexCode.data(), sizeof(TriangleVertexCode), 2); + Registered(DirectBufferPixelHeader, DirectBufferPixelCode.data(), sizeof(DirectBufferPixelCode), 1); + auto* ring = GuestBlock(); + ColorMemory.fill(std::byte{0}); + const auto slot = [&](std::size_t index, float value) { + auto* data = ring + 1024 + index * 64; + data[0] = std::bit_cast(value); + return data; + }; + const auto draw = [&](const std::uint32_t* data, bool setup) { + auto commands = setup ? Setup(true, DirectBufferPixelCode.data(), 4) : std::vector{}; + const auto address = reinterpret_cast(data); + Append(commands, 0x76, {0x00c, static_cast(address)}); + Append(commands, 0x76, {0x00d, static_cast(address >> 32u) & 0xffffu}); + Append(commands, 0x76, {0x00e, 16}); + Append(commands, 0x76, {0x00f, 0x3004bfacu}); + Append(commands, 0x2d, {3, 2}); + Submit(std::move(commands)); + return BrightestColorByte(); + }; + const auto first = slot(0, 0.25f); + const auto second = slot(1, 1.0f); + draw(first, true); + const auto repeated = draw(first, false); + const auto before = AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters(); + const auto moved = draw(second, false); + const auto after = AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters(); + Require(moved > repeated, "a draw whose descriptor base and payload both moved read the old payload: " + std::to_string(repeated) + " then " + std::to_string(moved) + " (" + Counts(after) + ", before: " + Counts(before) + ")"); + Require(after.dataHits == before.dataHits + 1, "a draw whose descriptor base and payload moved was not a data-only hit (" + Counts(after) + ", before: " + Counts(before) + ")"); + Require(after.dataStagesCompiled - before.dataStagesCompiled >= 1, "a draw whose descriptor base moved reused the pixel stage built for the old base: the rebuild this test documents is gone, flip it to a reuse check (" + Counts(after) + ", before: " + Counts(before) + ")"); +} + } int main() { @@ -222,6 +272,7 @@ int main() { const bool verify = std::getenv("APS5_VERIFY_DRAW_DATA_HITS") != nullptr; UnreadUserWordsKeepTheStage(verify); ReadUserWordsChangeTheOutput(); + DirectBufferBaseMovesWithItsPayload(); AgcDriverShutdown_nid_postfix(); std::cout << "Data-only hits " << (verify ? "verified" : "reused") << " the stages whose read user words did not change\n"; return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawPipelineTrace.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawPipelineTrace.cpp index 31456ae51f..e3a7f4cc80 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawPipelineTrace.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawPipelineTrace.cpp @@ -11,35 +11,46 @@ int main(int argc, char** argv) { try { using namespace AgcDriver; using DriverDetail::DrawPipeline; + using DriverDetail::DrawFinisher; if (DrawPipeline::Depth() == 0) return 77; - const bool disabled = argc > 1 && std::string_view(argv[1]) == "disabled"; + const auto mode = argc > 1 ? std::string_view(argv[1]) : std::string_view{}; + const bool disabled = mode == "disabled" || mode == "finisher-disabled"; + const bool finisher = mode.starts_with("finisher"); + const char* worker = finisher ? "draw finisher" : "draw committer"; + if (finisher && !DrawFinisher::Enabled()) throw std::runtime_error("draw finisher is disabled"); if (PerformanceTrace::Configured() == disabled) throw std::runtime_error("unexpected trace configuration"); if (!disabled) PerformanceTrace::Present(1, 1, nullptr); bool checked = false; const char* failure = nullptr; - DrawPipeline::Queue0().Enqueue([&] { + const auto checkWorker = [&] { auto* trace = PerformanceTrace::Thread(); if (disabled) { - if (trace != nullptr) { failure = "unconfigured draw committer allocated a trace buffer"; return; } + if (trace != nullptr) { failure = "unconfigured draw worker allocated a trace buffer"; return; } checked = true; return; } - if (trace == nullptr) { failure = "draw committer CPU spans are absent from the trace"; return; } + if (trace == nullptr) { failure = "draw worker CPU spans are absent from the trace"; return; } { PerformanceTimer timer("DrawPipeline.TraceRegression"); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } const auto snapshot = trace->Collect(); - if (snapshot.name != "draw committer" || snapshot.dropped) { failure = "draw committer trace identity or capacity is incorrect"; return; } + if (snapshot.name != worker || snapshot.dropped) { failure = "draw worker trace identity or capacity is incorrect"; return; } const auto found = [](const auto& event) { return event.scope != nullptr && std::string_view(event.scope) == "DrawPipeline.TraceRegression" && event.stage == nullptr && event.length > std::chrono::steady_clock::duration::zero(); }; if (std::none_of(snapshot.slices.begin(), snapshot.slices.end(), found) || std::none_of(snapshot.totals.begin(), snapshot.totals.end(), found)) - { failure = "committed work was missing from trace slices or totals"; return; } + { failure = "draw work was missing from trace slices or totals"; return; } checked = true; - }, {}); - DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Submission); + }; + if (finisher) { + DrawFinisher::Get().Enqueue(checkWorker); + DrawFinisher::Get().Drain(); + } else { + DrawPipeline::Queue0().Enqueue(checkWorker, {}); + DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Submission); + } if (failure != nullptr) throw std::runtime_error(failure); - if (!checked) throw std::runtime_error("draw committer did not run the trace check"); - std::cout << "Draw committer tracing checked\n"; + if (!checked) throw std::runtime_error("draw worker did not run the trace check"); + std::cout << worker << " tracing checked\n"; return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawShaderFailure.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawShaderFailure.cpp new file mode 100644 index 0000000000..2051585c2d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawShaderFailure.cpp @@ -0,0 +1,355 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif + +namespace { + +using AgcDriver::Graphics::Require; + +alignas(256) constexpr std::array FailingVertexCode{0xbe800200u, 0xbf810000u}; +alignas(256) constexpr std::array RequestFailureVertexCode{0xbe901f00u, 0x809010c0u, 0xbe802010u, 0xbf810000u}; +alignas(256) constexpr std::array ValidVertexCode{0x7e000208u, 0x7e020280u, 0x7e040280u, 0x7e0602f2u, 0xf80008cfu, 0x03020100u, 0xbf810000u, 0xbf810000u}; +alignas(256) constexpr std::array PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u}; + +struct ColorBuffer { + static constexpr std::size_t Size = 65536; + ColorBuffer() { +#ifdef _WIN32 + data = static_cast(VirtualAlloc(nullptr, Size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(data != nullptr, "cannot allocate the color target"); +#else + void* raw = mmap(nullptr, 2 * Size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the color target"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + Size - 1) & ~(static_cast(Size) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + Size != begin + 2 * Size) munmap(reinterpret_cast(aligned + Size), begin + 2 * Size - (aligned + Size)); + data = reinterpret_cast(aligned); +#endif + std::memset(data, 0, Size); + GuestAllocations::Mutation().Add(data, Size, true, true, true); + } + ~ColorBuffer() { + if (data != nullptr) { + GuestAllocations::Mutation().Remove(data); +#ifdef _WIN32 + VirtualFree(data, 0, MEM_RELEASE); +#else + munmap(data, Size); +#endif + data = nullptr; + } + } + ColorBuffer(const ColorBuffer&) = delete; + ColorBuffer& operator=(const ColorBuffer&) = delete; + + std::byte* data = nullptr; +}; + +struct Header { + Shader shader{}; + ShaderUserData userData{}; +}; + +alignas(8) Header VertexHeader{}; +alignas(8) Header PixelHeader{}; + +const Shader* Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(bytes); + header.shader.code = code; + header.shader.type = type; + header.shader.user_data = &header.userData; + AgcDriverRegisterShader_nid_postfix(&header.shader); + return &header.shader; +} + +void Append(std::vector& commands, std::uint32_t opcode, std::initializer_list payload) { + commands.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + commands.insert(commands.end(), payload); +} + +std::vector DrawPacket(std::uintptr_t color, const std::uint32_t* vertex, const std::uint32_t* pixel) { + const auto vertexAddress = reinterpret_cast(vertex); + const auto pixelAddress = reinterpret_cast(pixel); + 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, 0x4dc6c000}, {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)}, + }; + const std::vector> shader{ + {0xc8, static_cast(vertexAddress >> 8u)}, {0xc9, static_cast(vertexAddress >> 40u)}, {0x8b, 1u << 1u}, {0x8c, 0}, + {0x008, static_cast(pixelAddress >> 8u)}, {0x009, static_cast(pixelAddress >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast(1.0f)}, + }; + std::vector commands; + Append(commands, 0x79, {0x242, 4}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value}); + Append(commands, 0x2d, {3, 2}); + return commands; +} + +std::string ReadTraceFile(const char* path) { + std::ifstream stream(path, std::ios::binary); + if (!stream.is_open()) return {}; + std::ostringstream buffer; + buffer << stream.rdbuf(); + return buffer.str(); +} + +bool WaitForTraceRecord(const char* path, const std::string& record) { + for (int attempt = 0; attempt < 200; ++attempt) { + if (ReadTraceFile(path).find(record) != std::string::npos) return true; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + return false; +} + +std::string TraceLine(const std::string& trace, const std::string& record) { + const auto position = trace.find(record); + if (position == std::string::npos) return {}; + const auto end = trace.find('\n', position); + return trace.substr(position, end == std::string::npos ? std::string::npos : end - position); +} + +std::string TraceField(const std::string& line, const char* name) { + const auto field = line.find(std::string(name) + "="); + if (field == std::string::npos) return {}; + const auto start = field + std::strlen(name) + 1; + const auto end = line.find(' ', start); + return line.substr(start, end == std::string::npos ? std::string::npos : end - start); +} + +std::string SubmissionSerialOf(const std::string& trace, const std::string& record) { + const auto position = trace.find(record); + if (position == std::string::npos) return {}; + const auto field = trace.find("submission=", position); + if (field == std::string::npos) return {}; + const auto start = field + std::string("submission=").size(); + const auto end = trace.find(' ', start); + return trace.substr(start, end == std::string::npos ? std::string::npos : end - start); +} + +void SubmitDraw(std::uintptr_t color, const std::uint32_t* vertex, const std::uint32_t* pixel) { + auto commands = DrawPacket(color, vertex, pixel); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission was not accepted"); +} + +void FailingShaderDecodeRecordsTheDraw(const char* tracePath, bool correlationExpected = true) { + ColorBuffer color; + Registered(VertexHeader, FailingVertexCode.data(), FailingVertexCode.size() * sizeof(std::uint32_t), 2); + Registered(PixelHeader, PixelCode.data(), PixelCode.size() * sizeof(std::uint32_t), 1); + SubmitDraw(reinterpret_cast(color.data), FailingVertexCode.data(), PixelCode.data()); + std::string failure; + try { + AgcDriverWaitIdle_nid_postfix(); + Require(false, "the failing shader request did not fail the submission"); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find("unsupported SOP1 opcode 2") != std::string::npos, "the submission failure lost the original shader-request error: " + failure); + if (!correlationExpected) { + const auto trace = ReadTraceFile(tracePath); + Require(trace.find("draw-attempt") == std::string::npos, "correlation-disabled failure emitted draw-attempt"); + Require(trace.find("draw-shader-request-failed") == std::string::npos, "correlation-disabled failure emitted draw-shader-request-failed"); + Require(trace.find("draw-materialization-failed") == std::string::npos, "correlation-disabled failure emitted draw-materialization-failed"); + Require(trace.find("draw-submission-failed") == std::string::npos, "correlation-disabled failure emitted draw-submission-failed"); + Require(trace.find("draw-selected") == std::string::npos, "correlation-disabled failure emitted draw-selected"); + Require(trace.find("draw-recorded") == std::string::npos, "correlation-disabled failure emitted draw-recorded"); + return; + } + Require(WaitForTraceRecord(tracePath, "draw-attempt-failed"), "the capture trace received no draw-attempt-failed record"); + const auto trace = ReadTraceFile(tracePath); + const auto attempt = TraceLine(trace, "draw-attempt "); + Require(!attempt.empty(), "the failed draw emitted no draw-attempt record"); + const auto token = TraceField(attempt, "token"); + Require(!token.empty() && token != "0", "the draw-attempt record carries no draw token"); + const auto failed = TraceLine(trace, "draw-attempt-failed"); + Require(!failed.empty(), "the failed draw emitted no draw-attempt-failed record"); + Require(failed.find("token=" + token) != std::string::npos, "the decode failure lost the draw token"); + Require(failed.find("phase=decode") != std::string::npos, "the SOP1 failure was not classified at the decode stage"); + Require(failed.find("offset=" + TraceField(attempt, "offset")) != std::string::npos, "the decode failure lost the packet offset"); + Require(failed.find("unsupported SOP1 opcode 2") != std::string::npos, "the decode failure lost the shader-decoder reason"); + Require(trace.find("draw-selected") == std::string::npos, "a pre-selection decode failure emitted draw-selected"); + Require(trace.find("draw-shader-request-failed") == std::string::npos, "a pre-request decode failure emitted draw-shader-request-failed"); + Require(trace.find("draw-recorded token=" + token) == std::string::npos, "a failed draw emitted draw-recorded"); + Require(trace.find("tokens=[" + token + "]") == std::string::npos, "a failed draw token was attributed to a submitted batch"); + const auto errorSerial = SubmissionSerialOf(trace, "draw-error"); + Require(!errorSerial.empty(), "the draw-error record carries no submission serial"); + Require(errorSerial == SubmissionSerialOf(trace, "draw-attempt"), "the draw-error record (submission " + errorSerial + ") does not correlate with the draw attempt"); + Require(trace.find("draw-error submission=" + errorSerial + " offset=" + TraceField(attempt, "offset")) != std::string::npos, "the draw-error record lost the attempted packet offset"); + Require(trace.find("unsupported SOP1 opcode 2") != std::string::npos, "the trace lost the shader-decoder reason"); +} + +void FailingShaderMaterializationRecordsTheDraw(const char* tracePath, bool correlationExpected = true) { + ColorBuffer color; + Registered(VertexHeader, RequestFailureVertexCode.data(), RequestFailureVertexCode.size() * sizeof(std::uint32_t), 2); + Registered(PixelHeader, PixelCode.data(), PixelCode.size() * sizeof(std::uint32_t), 1); + SubmitDraw(reinterpret_cast(color.data), RequestFailureVertexCode.data(), PixelCode.data()); + std::string failure; + try { + AgcDriverWaitIdle_nid_postfix(); + Require(false, "the failing shader request did not fail the submission"); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find("unsupported dynamic s_setpc_b64") != std::string::npos, "the submission failure lost the original shader-request error: " + failure); + if (!correlationExpected) { + const auto trace = ReadTraceFile(tracePath); + Require(trace.find("draw-attempt") == std::string::npos, "correlation-disabled failure emitted draw-attempt"); + Require(trace.find("draw-shader-request-failed") == std::string::npos, "correlation-disabled failure emitted draw-shader-request-failed"); + Require(trace.find("draw-materialization-failed") == std::string::npos, "correlation-disabled failure emitted draw-materialization-failed"); + Require(trace.find("draw-submission-failed") == std::string::npos, "correlation-disabled failure emitted draw-submission-failed"); + Require(trace.find("draw-selected") == std::string::npos, "correlation-disabled failure emitted draw-selected"); + Require(trace.find("draw-recorded") == std::string::npos, "correlation-disabled failure emitted draw-recorded"); + return; + } + Require(WaitForTraceRecord(tracePath, "draw-shader-request-failed"), "the capture trace received no draw-shader-request-failed record"); + const auto trace = ReadTraceFile(tracePath); + const auto attempt = TraceLine(trace, "draw-attempt "); + Require(!attempt.empty(), "the failed draw emitted no draw-attempt record"); + const auto token = TraceField(attempt, "token"); + Require(!token.empty() && token != "0", "the draw-attempt record carries no draw token"); + const auto request = TraceLine(trace, "draw-shader-request-failed"); + Require(!request.empty(), "the failed draw emitted no request-failure line"); + Require(request.find("token=" + token) != std::string::npos, "the request failure lost the draw token"); + Require(request.find("offset=" + TraceField(attempt, "offset")) != std::string::npos, "the request failure lost the packet offset"); + const auto snapshot = TraceField(request, "snapshot"); + Require(!snapshot.empty() && snapshot != "0x0", "the request failure carries no snapshot identity"); + const auto selected = TraceLine(trace, "draw-selected token=" + token); + Require(!selected.empty(), "the failed draw emitted no draw-selected record for its token"); + Require(selected.find("stage=1") != std::string::npos, "the first selected program is not the failing vertex stage"); + Require(selected.find("snapshot=" + snapshot) != std::string::npos, "the request failure lost the selected snapshot identity"); + Require(trace.find("draw-recorded token=" + token) == std::string::npos, "a failed draw emitted draw-recorded"); + char codeAddress[19]; + std::snprintf(codeAddress, sizeof(codeAddress), "%llx", static_cast(reinterpret_cast(RequestFailureVertexCode.data()))); + Require(request.find(std::string("code=") + codeAddress + " codeOffset=0 stage=1 type=2") != std::string::npos, "the draw-shader-request-failed record does not identify the failing vertex shader"); + Require(trace.find("tokens=[" + token + "]") == std::string::npos, "a failed draw token was attributed to a submitted batch"); + const auto requestSerial = SubmissionSerialOf(trace, "draw-shader-request-failed"); + Require(!requestSerial.empty(), "the draw-shader-request-failed record carries no submission serial"); + const auto errorSerial = SubmissionSerialOf(trace, "draw-error"); + Require(requestSerial == errorSerial, "the draw-shader-request-failed record (submission " + requestSerial + ") does not correlate with the draw-error record (submission " + errorSerial + ")"); + Require(trace.find("draw-error submission=" + requestSerial + " offset=" + TraceField(attempt, "offset")) != std::string::npos, "the draw-error record lost the attempted packet offset"); + Require(trace.find("unsupported dynamic s_setpc_b64") != std::string::npos, "the draw-shader-request-failed record lost the shader-request reason"); +} + +void SuccessfulDrawEmitsNoFailureRecord(const char* tracePath) { + ColorBuffer color; + Registered(VertexHeader, ValidVertexCode.data(), ValidVertexCode.size() * sizeof(std::uint32_t), 2); + Registered(PixelHeader, PixelCode.data(), PixelCode.size() * sizeof(std::uint32_t), 1); + SubmitDraw(reinterpret_cast(color.data), ValidVertexCode.data(), PixelCode.data()); + AgcDriverWaitIdle_nid_postfix(); + Require(WaitForTraceRecord(tracePath, "draw submission="), "the capture trace received no draw submission record"); + const auto trace = ReadTraceFile(tracePath); + const auto selected = TraceLine(trace, "draw-selected"); + const auto attempt = TraceLine(trace, "draw-attempt"); + Require(!selected.empty(), "the successful draw emitted no draw-selected record"); + Require(!attempt.empty(), "the successful draw emitted no draw-attempt record"); + const auto token = TraceField(selected, "token"); + Require(!token.empty() && token != "0", "the successful selection record carries no draw token"); + Require(attempt.find("token=" + token) != std::string::npos, "the successful draw-attempt record lost the draw token"); + Require(WaitForTraceRecord(tracePath, "draw-recorded token=" + token), "the selected draw emitted no draw-recorded record"); + 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(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"); +} + +void DisabledCorrelationTraceLeavesDrawBehaviorUnchanged(const char* tracePath) { + ColorBuffer color; + Registered(VertexHeader, ValidVertexCode.data(), ValidVertexCode.size() * sizeof(std::uint32_t), 2); + Registered(PixelHeader, PixelCode.data(), PixelCode.size() * sizeof(std::uint32_t), 1); + SubmitDraw(reinterpret_cast(color.data), ValidVertexCode.data(), PixelCode.data()); + AgcDriverWaitIdle_nid_postfix(); + Require(WaitForTraceRecord(tracePath, "draw submission="), "the disabled-trace draw did not complete"); + const auto trace = ReadTraceFile(tracePath); + Require(trace.find("draw-attempt") == std::string::npos, "the disabled trace emitted draw-attempt"); + Require(trace.find("draw-materialization-failed") == std::string::npos, "the disabled trace emitted draw-materialization-failed"); + Require(trace.find("draw-submission-failed") == std::string::npos, "the disabled trace emitted draw-submission-failed"); + Require(trace.find("draw-selected") == std::string::npos, "the disabled trace emitted draw-selected"); + Require(trace.find("draw-recorded") == std::string::npos, "the disabled trace emitted draw-recorded"); +} + +} + +int main(int argc, char** argv) { + const std::string mode = argc > 1 ? argv[1] : "request-failure"; + const bool expectFailure = mode != "successful-draw" && mode != "disabled-correlation"; + const char* tracePath = std::getenv("APS5_CAPTURE_TRACE_PATH"); + try { + if (!tracePath || tracePath[0] == '\0') throw std::runtime_error("this case requires a non-empty APS5_CAPTURE_TRACE_PATH"); + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (mode == "request-failure") FailingShaderDecodeRecordsTheDraw(tracePath); + else if (mode == "request-failure-correlation-disabled") FailingShaderDecodeRecordsTheDraw(tracePath, false); + else if (mode == "materialization-failure") FailingShaderMaterializationRecordsTheDraw(tracePath); + else if (mode == "materialization-failure-correlation-disabled") FailingShaderMaterializationRecordsTheDraw(tracePath, false); + else if (mode == "successful-draw") SuccessfulDrawEmitsNoFailureRecord(tracePath); + else if (mode == "disabled-correlation") DisabledCorrelationTraceLeavesDrawBehaviorUnchanged(tracePath); + else throw std::runtime_error("unknown draw shader failure test mode: " + mode); + try { + AgcDriverShutdown_nid_postfix(); + Require(!expectFailure, "the failed draw was not rethrown at driver shutdown"); + } catch (const std::exception&) { + Require(expectFailure, "driver shutdown failed unexpectedly"); + } + std::cout << "draw shader failure tests passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { + AgcDriverShutdown_nid_postfix(); + } catch (...) { + } + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawUnsupportedBufferType.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawUnsupportedBufferType.cpp index c0a50d0476..61a0e679ee 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawUnsupportedBufferType.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawUnsupportedBufferType.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,7 @@ void NamesShaderAndDescriptor() { Require(message.find("buffer descriptor uses an unsupported type") != std::string::npos, "draw failed for another reason: " + message); Require(message.find(shader) != std::string::npos, std::string("draw error does not name ") + shader + ": " + message); Require(message.find(words) != std::string::npos, std::string("draw error does not name ") + words + ": " + message); + if (std::getenv("APS5_DRAW_PIPELINE") != nullptr) Require(message.find("draw at submission ") != std::string::npos && message.find("(finished on the draw finisher)") != std::string::npos, "a draw finished on the draw finisher does not name its submission and offset: " + message); return; } throw std::runtime_error("a pixel shader read a buffer through a descriptor of type 2"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/EmptyIndirectSkip.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/EmptyIndirectSkip.cpp new file mode 100644 index 0000000000..6a85e68f65 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/EmptyIndirectSkip.cpp @@ -0,0 +1,162 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::byte Kept{0x40}; +constexpr std::uint32_t BaseVertexLocation = 0x8c; + +alignas(256) constexpr std::array VertexCode{ + 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e060208u, 0x7e0802f2u, 0x7e0a02ffu, 0x44800000u, 0xf80000d1u, 0x00000005u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u, +}; +alignas(256) constexpr std::array PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u}; +alignas(256) std::array FirstMemory{}; +alignas(256) std::array NoInstancesMemory{}; +alignas(256) std::array NoVerticesMemory{}; +alignas(256) std::array LastMemory{}; +alignas(256) std::array Records{3, 1, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 3, 1, 0, 0}; + +struct Header { + Shader shader{}; + ShaderUserData userData{}; +}; + +alignas(8) Header VertexHeader{}; +alignas(8) Header PixelHeader{}; + +void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(bytes); + header.shader.code = code; + header.shader.type = type; + header.shader.user_data = &header.userData; + AgcDriverRegisterShader_nid_postfix(&header.shader); +} + +void Append(std::vector& commands, std::uint32_t opcode, std::initializer_list payload) { + commands.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + commands.insert(commands.end(), payload); +} + +std::vector DrawFour() { + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto pixel = reinterpret_cast(PixelCode.data()); + const auto records = reinterpret_cast(Records.data()); + const std::vector> context{ + {0x2d5, 0x2000}, + {0x1b6, 0}, {0x207, 0x01200000}, {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}, + {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, 1u << 1u}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast(1.0f)}, + }; + std::vector commands; + Append(commands, 0x79, {0x242, 4}); + Append(commands, 0x79, {0x24a, 0}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value}); + Append(commands, 0x11, {1, static_cast(records), static_cast(records >> 32u)}); + const auto draw = [&](std::array& memory, std::uint32_t recordOffset) { + const auto color = reinterpret_cast(memory.data()); + Append(commands, 0x69, {0x318, static_cast(color >> 8u)}); + Append(commands, 0x69, {0x390, static_cast(color >> 40u)}); + Append(commands, 0x76, {BaseVertexLocation, 0}); + Append(commands, 0x24, {recordOffset, BaseVertexLocation, 0, 2}); + }; + draw(FirstMemory, 0); + draw(NoInstancesMemory, 16); + draw(NoVerticesMemory, 32); + draw(LastMemory, 48); + return commands; +} + +std::size_t Covered(const std::array& memory) { + return static_cast(std::count_if(memory.begin(), memory.end(), [](std::byte value) { return value != Kept; })); +} + +void EmptyIndirectDrawsSkipTheirStages() { + FirstMemory.fill(Kept); + NoInstancesMemory.fill(Kept); + NoVerticesMemory.fill(Kept); + LastMemory.fill(Kept); + Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + auto commands = DrawFour(); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); + const auto first = Covered(FirstMemory); + Require(first != 0, "the first indirect draw, whose record draws 3 vertices, covered nothing"); + Require(Covered(NoInstancesMemory) == 0, "the indirect draw whose record has no instances covered " + std::to_string(Covered(NoInstancesMemory)) + " bytes, expected none"); + Require(Covered(NoVerticesMemory) == 0, "the indirect draw whose record has no vertices covered " + std::to_string(Covered(NoVerticesMemory)) + " bytes, expected none"); + for (std::size_t index = 0; index < FirstMemory.size(); ++index) { + Require(LastMemory[index] == FirstMemory[index], "byte " + std::to_string(index) + " of the indirect draw after the empty ones is " + std::to_string(static_cast(LastMemory[index])) + ", the first indirect draw wrote " + std::to_string(static_cast(FirstMemory[index]))); + } + const auto empty = AgcDriver::Graphics::EmptyIndirectDrawCount(); + Require(empty == 2, std::to_string(empty) + " of the 2 indirect draws whose records draw nothing were skipped before their stages were built"); + const auto counts = AgcDriver::Graphics::IndirectDrawCounts(); + std::uint64_t cpu = 0; + for (std::size_t path = 1; path < counts.size(); ++path) cpu += counts[path]; + Require(counts[0] == 0 && cpu == 2, "the drawing indirect draws took the GPU-side path " + std::to_string(counts[0]) + " times and the CPU path " + std::to_string(cpu) + " times, expected the CPU path twice"); +} + +} + +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; + } + EmptyIndirectDrawsSkipTheirStages(); + AgcDriverShutdown_nid_postfix(); + std::puts("CPU-path indirect draws whose records draw nothing were skipped before their stages were built"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + AgcDriverShutdown_nid_postfix(); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp index 1d96a16670..b656cca503 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp @@ -27,6 +27,7 @@ constexpr std::uint32_t Side = 2; constexpr std::uint32_t Format8888UNorm = 56; constexpr std::uint32_t Format8UNorm = 1; constexpr std::uint32_t Format8_8UNorm = 14; +constexpr std::uint32_t Format16UNorm = 7; constexpr std::uint32_t Format1616UNorm = 23; constexpr std::uint32_t Format9995Float = 132; constexpr std::uint32_t Format8Srgb = 128; @@ -178,45 +179,6 @@ void FillInput() { Extra[0] = 0x00001f20u; } -std::int32_t OffsetComponent(std::uint32_t tid, std::uint32_t component) { - const auto field = (Extra[tid * 3u] >> (component * 8u)) & 0x3fu; - return static_cast(field ^ 0x20u) - 0x20; -} - -float ReferenceTexel(int x, int y, float reference, const Sampler& sampler) { - const std::uint32_t clamp = sampler.clamp; - const int size = static_cast(Side); - if (clamp == ClampBorder && (x < 0 || x >= size || y < 0 || y >= size)) { - const float border = sampler.border == BorderWhite ? 1.0f : 0.0f; - return reference <= border ? 1.0f : 0.0f; - } - const auto address = [&](int value) { - if (clamp == ClampEdge) return std::clamp(value, 0, static_cast(Side) - 1); - const int size = static_cast(Side); - return ((value % size) + size) % size; - }; - const float red = static_cast(Red[address(y) * Side + address(x)]) / 255.0f; - return reference <= red ? 1.0f : 0.0f; -} - -float Expected(std::uint32_t tid, const Sampler& sampler, bool offsets) { - const float u = Input[tid * 3u + 1u] * static_cast(Side); - const float v = Input[tid * 3u + 2u] * static_cast(Side); - const float reference = std::clamp(Input[tid * 3u + 0u], 0.0f, 1.0f); - const int offsetX = offsets ? OffsetComponent(tid, 0u) : 0; - const int offsetY = offsets ? OffsetComponent(tid, 1u) : 0; - if (sampler.filter == FilterPoint) return ReferenceTexel(static_cast(std::floor(u)) + offsetX, static_cast(std::floor(v)) + offsetY, reference, sampler); - const float cu = u - 0.5f; - const float cv = v - 0.5f; - const int x = static_cast(std::floor(cu)) + offsetX; - const int y = static_cast(std::floor(cv)) + offsetY; - const float a = cu - std::floor(cu); - const float b = cv - std::floor(cv); - const float top = ReferenceTexel(x, y, reference, sampler) * (1.0f - a) + ReferenceTexel(x + 1, y, reference, sampler) * a; - const float bottom = ReferenceTexel(x, y + 1, reference, sampler) * (1.0f - a) + ReferenceTexel(x + 1, y + 1, reference, sampler) * a; - return top * (1.0f - b) + bottom * b; -} - ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, std::span code = Code, bool useCache = false, bool nativeSampleOffsets = true, bool cube = false) { std::vector userData(24, 0u); const auto input = cube ? BufferDescriptor(CubeInput.data(), static_cast(sizeof(CubeInput))) : BufferDescriptor(Input.data(), static_cast(sizeof(Input))); @@ -254,9 +216,7 @@ std::uint64_t Run(AgcDriver::VulkanDevice& device, const Sampler& sampler, const device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const float expected = Expected(tid, sampler, offsets); - const float tolerance = sampler.filter == FilterPoint ? 0.0f : 1e-4f; - Require(std::fabs(Output[tid] - expected) <= tolerance, std::string(name) + ": thread " + std::to_string(tid) + " compared to " + std::to_string(Output[tid]) + ", expected " + std::to_string(expected)); + Require(Output[tid] == 0.0f, std::string(name) + ": thread " + std::to_string(tid) + " compared to " + std::to_string(Output[tid]) + ", expected 0"); } return result.variantId; } @@ -274,12 +234,11 @@ void RunChannels(AgcDriver::VulkanDevice& device, std::span device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const float compared = Expected(tid, sampler, false); for (std::uint32_t reg = 0; reg < 4u; ++reg) { float expected = -1.0f; if (reg < registers) { const auto select = channels[firstChannel + reg]; - expected = select == 0u ? 0.0f : select == 1u ? 1.0f : compared; + expected = select == 0u ? 0.0f : select == 1u ? 1.0f : 0.0f; } const float actual = Output[tid * 4u + reg]; Require(std::fabs(actual - expected) <= 1e-4f, std::string(name) + ": thread " + std::to_string(tid) + " register " + std::to_string(reg) + " returned " + std::to_string(actual) + ", expected " + std::to_string(expected)); @@ -304,7 +263,7 @@ void RunCube(AgcDriver::VulkanDevice& device, const Sampler& sampler, const char device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(CubeCode.data())); device.WaitIdle(); for (std::uint32_t tid = 0; tid < Threads; ++tid) { - Require(Output[tid] == 1.0f, std::string(name) + ": +X cube face comparison returned " + std::to_string(Output[tid]) + ", expected 1"); + Require(Output[tid] == 0.0f, std::string(name) + ": +X cube face comparison returned " + std::to_string(Output[tid]) + ", expected 0"); } } @@ -314,7 +273,7 @@ bool BindsDepthCompare(const ShaderRecompiler::RecompileResult& result) { }); } -void RunSrgbCubeZero(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name) { +void RunZeroCube(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name) { FillCubeTexels(); for (std::uint32_t tid = 0; tid < Threads; ++tid) { CubeInput[tid * 4u + 0u] = 0.5f; @@ -332,7 +291,7 @@ void RunSrgbCubeZero(AgcDriver::VulkanDevice& device, std::uint32_t format, cons } } -void RunSrgbZero(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name, bool useCache, std::uint64_t expectedVariant) { +void RunZero(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name, bool useCache, std::uint64_t expectedVariant) { Output.fill(-1.0f); const auto result = Compile(device, format, sampler, Code, useCache); if (useCache) Require(result.cacheHit && result.variantId == expectedVariant, std::string(name) + " did not reuse the compiled comparison artifact"); @@ -366,25 +325,38 @@ int main() { FillTexels(); FillInput(); RunCube(*device, {ClampEdge, FilterPoint}, "point cube, clamp to edge"); - Reject(*device, Format1616UNorm, {ClampEdge, FilterBilinear}, "bilinear cube comparison requires DISABLE_CUBE_WRAP", CubeCode, true, true); + RunCube(*device, {ClampEdge, FilterBilinear}, "bilinear cube without DISABLE_CUBE_WRAP"); for (std::uint32_t function = 0; function < 8u; ++function) { const auto name = "bilinear 8_8_8_8 srgb cube function " + std::to_string(function); - RunSrgbCubeZero(*device, Format8888Srgb, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, false, function}, name.c_str()); + RunZeroCube(*device, Format8888Srgb, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, false, function}, name.c_str()); } - RunSrgbCubeZero(*device, Format8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8 srgb cube without DISABLE_CUBE_WRAP"); - RunSrgbCubeZero(*device, Format8_8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8_8 srgb cube without DISABLE_CUBE_WRAP"); + RunZeroCube(*device, Format8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8 srgb cube without DISABLE_CUBE_WRAP"); + RunZeroCube(*device, Format8_8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8_8 srgb cube without DISABLE_CUBE_WRAP"); RunCube(*device, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, true}, "bilinear cube with DISABLE_CUBE_WRAP"); - Reject(*device, Format9995Float, {ClampEdge, FilterPoint}, "implemented only for float, unorm and snorm formats"); + Reject(*device, Format9995Float, {ClampEdge, FilterPoint}, "implemented only for floating-point, UNORM and SNORM formats"); for (std::uint32_t function = 0; function < 8u; ++function) { const auto name = "bilinear 8_8_8_8 unorm cube function " + std::to_string(function); - RunSrgbCubeZero(*device, Format8888UNorm, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, false, function}, name.c_str()); + RunZeroCube(*device, Format8888UNorm, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, false, function}, name.c_str()); } - RunSrgbCubeZero(*device, Format8888UNorm, {ClampEdge, FilterAnisoBilinear, BorderBlack, 0u, 0u, false, GreaterEqual}, "anisotropic bilinear 8_8_8_8 unorm cube without DISABLE_CUBE_WRAP"); - Reject(*device, Format1616UNorm, {ClampEdge, FilterAnisoBilinear}, "point or bilinear"); - Reject(*device, Format1616UNorm, {ClampEdge, FilterAnisoPoint}, "point or bilinear"); + RunZeroCube(*device, Format8888UNorm, {ClampEdge, FilterAnisoBilinear, BorderBlack, 0u, 0u, false, GreaterEqual}, "anisotropic bilinear 8_8_8_8 unorm cube without DISABLE_CUBE_WRAP"); + RunZero(*device, Format1616UNorm, {ClampEdge, FilterAnisoBilinear}, "anisotropic bilinear 16_16 unorm", false, 0u); + RunZero(*device, Format1616UNorm, {ClampEdge, FilterAnisoPoint}, "anisotropic point 16_16 unorm", false, 0u); FillTexels(); + Require(BindsDepthCompare(Compile(*device, Format16UNorm, {ClampEdge, FilterPoint})), "an R16 unorm texture left the native comparison path"); Require(BindsDepthCompare(Compile(*device, Format32Float, {ClampEdge, FilterBilinear})), "an R32 float texture left the native comparison path"); Require(!BindsDepthCompare(Compile(*device, Format8888UNorm, {ClampEdge, FilterBilinear})), "a color texture kept a depth-compare binding"); + for (const auto format : {1u, 2u, 8u, 13u, 14u, 15u, 23u, 24u, 29u, 36u, 50u, 51u, 56u, 57u, 65u, 66u, 71u, 77u}) { + if (format == 51u && !device->SupportsImageFormat(VK_FORMAT_A2B10G10R10_SNORM_PACK32, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)) { + std::puts("measured GFX10 color format 51 returns zero: skipped, Vulkan device does not support the image format"); + continue; + } + const auto name = "measured GFX10 color format " + std::to_string(format) + " returns zero"; + RunZero(*device, format, {ClampEdge, FilterPoint}, name.c_str(), false, 0u); + } + for (std::uint32_t function = 0; function < 8u; ++function) { + const auto name = "16_16 unorm compare function " + std::to_string(function) + " returns zero"; + RunZero(*device, Format1616UNorm, {ClampEdge, FilterPoint, BorderBlack, 0u, 0u, false, function}, name.c_str(), false, 0u); + } for (const bool useCache : {false, true}) { const auto variant = useCache ? Run(*device, {ClampEdge, FilterPoint}, "cached UNORM before sRGB", false, true) : 0u; if (useCache) Require(variant != 0, "the cached comparison has no compiled artifact identity"); @@ -392,7 +364,7 @@ int main() { for (std::uint32_t function = 0; function < 8u; ++function) { for (const auto filter : {FilterPoint, FilterBilinear}) { const auto name = std::string(useCache ? "cached" : "uncached") + " sRGB format " + std::to_string(format) + " function " + std::to_string(function) + (filter == FilterPoint ? " point" : " bilinear"); - RunSrgbZero(*device, format, {ClampEdge, filter, BorderBlack, 0u, 0u, false, function}, name.c_str(), useCache, variant); + RunZero(*device, format, {ClampEdge, filter, BorderBlack, 0u, 0u, false, function}, name.c_str(), useCache, variant); } } } @@ -403,10 +375,11 @@ int main() { for (const auto filter : {FilterPoint, FilterBilinear, FilterAnisoBilinear}) { if (format != Format8888UNorm && function != LessEqual) continue; const auto name = "8-bit unorm format " + std::to_string(format) + " function " + std::to_string(function) + " filter " + std::to_string(filter); - RunSrgbZero(*device, format, {ClampEdge, filter, BorderBlack, 0u, 0u, false, function}, name.c_str(), false, 0u); + RunZero(*device, format, {ClampEdge, filter, BorderBlack, 0u, 0u, false, function}, name.c_str(), false, 0u); } } } + RunZero(*device, Format1616UNorm, {ClampEdge, FilterPoint}, "16_16 unorm comparison returns zero", false, 0u); Run(*device, {ClampEdge, FilterPoint}, "point, clamp to edge"); Run(*device, {ClampWrap, FilterPoint}, "point, wrap"); if (lodClamp) Run(*device, {ClampWrap, FilterPoint}, "point, wrap with negative offsets", true); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32AddMulDenormalModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32AddMulDenormalModes.cpp new file mode 100644 index 0000000000..7b5780067d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32AddMulDenormalModes.cpp @@ -0,0 +1,244 @@ +#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 = 96; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Instructions = 7; +constexpr std::uint32_t FirstMultiply = 3; +constexpr std::uint32_t FirstConstantFactor = 5; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x06140b04u, 0x08160b04u, 0x0a180b04u, + 0x101a0b04u, 0x0e1c0b04u, 0x101e08ffu, 0x3fc00000u, 0xd5070010u, 0x0001ff04u, 0x3fc00000u, 0xe0781000u, + 0x80010a02u, 0xe07c1010u, 0x80010e02u, 0xbf810000u, +}; + +constexpr const char* Names[Instructions] = {"v_add_f32", "v_sub_f32", "v_subrev_f32", "v_mul_f32", "v_mul_legacy_f32", "v_mul_f32 1.5", "v_mul_legacy_f32 1.5"}; + +template +struct Vector { + std::uint32_t operands[2]; + std::uint32_t outputsFlushed[Count]; + std::uint32_t inputsFlushed[Count]; +}; + +constexpr std::array, 40> ArithmeticVectors{{ + {{0x00000000u, 0x00000001u}, {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {{0x00000001u, 0x00800002u}, {0x00800003u, 0x80800001u, 0x00800001u, 0x00000000u, 0x00000000u}, {0x00800002u, 0x80800002u, 0x00800002u, 0x00000000u, 0x00000000u}}, + {{0x00000001u, 0x81000001u}, {0x81000000u, 0x01000002u, 0x81000002u, 0x80000000u, 0x80000000u}, {0x81000001u, 0x01000001u, 0x81000001u, 0x80000000u, 0x00000000u}}, + {{0x00000002u, 0x3f800000u}, {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}}, + {{0x00000002u, 0xff7fffffu}, {0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0xb57fffffu, 0xb57fffffu}, {0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x80000000u, 0x00000000u}}, + {{0x00000003u, 0x80000001u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}}, + {{0x00400000u, 0x00000002u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {{0x00400000u, 0x807fffffu}, {0x80000000u, 0x00bfffffu, 0x80bfffffu, 0x80000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}}, + {{0x007fffffu, 0x00800000u}, {0x00ffffffu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00800000u, 0x80800000u, 0x00800000u, 0x00000000u, 0x00000000u}}, + {{0x007fffffu, 0x80800002u}, {0x80000000u, 0x01000000u, 0x81000000u, 0x80000000u, 0x80000000u}, {0x80800002u, 0x00800002u, 0x80800002u, 0x80000000u, 0x00000000u}}, + {{0x00800000u, 0x80000001u}, {0x00000000u, 0x00800001u, 0x80800001u, 0x80000000u, 0x80000000u}, {0x00800000u, 0x00800000u, 0x80800000u, 0x80000000u, 0x00000000u}}, + {{0x00800001u, 0x33800000u}, {0x33800000u, 0xb3800000u, 0x33800000u, 0x00000000u, 0x00000000u}, {0x33800000u, 0xb3800000u, 0x33800000u, 0x00000001u, 0x00000001u}}, + {{0x00800002u, 0x00800000u}, {0x01000001u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, {0x01000001u, 0x00000002u, 0x80000002u, 0x00000000u, 0x00000000u}}, + {{0x00ffffffu, 0x00000002u}, {0x01000000u, 0x00fffffdu, 0x80fffffdu, 0x00000000u, 0x00000000u}, {0x00ffffffu, 0x00ffffffu, 0x80ffffffu, 0x00000000u, 0x00000000u}}, + {{0x00ffffffu, 0x80800002u}, {0x00000000u, 0x01400000u, 0x81400000u, 0x80000000u, 0x80000000u}, {0x007ffffdu, 0x01400000u, 0x81400000u, 0x80000000u, 0x80000000u}}, + {{0x01000000u, 0x80000003u}, {0x00fffffdu, 0x01000002u, 0x81000002u, 0x80000000u, 0x80000000u}, {0x01000000u, 0x01000000u, 0x81000000u, 0x80000000u, 0x00000000u}}, + {{0x01000001u, 0x80000001u}, {0x01000000u, 0x01000002u, 0x81000002u, 0x80000000u, 0x80000000u}, {0x01000001u, 0x01000001u, 0x81000001u, 0x80000000u, 0x00000000u}}, + {{0x3f800000u, 0x807fffffu}, {0x3f800000u, 0x3f800000u, 0xbf800000u, 0x80000000u, 0x80000000u}, {0x3f800000u, 0x3f800000u, 0xbf800000u, 0x80000000u, 0x00000000u}}, + {{0x34000000u, 0x00800001u}, {0x34000000u, 0x34000000u, 0xb4000000u, 0x00000000u, 0x00000000u}, {0x34000000u, 0x34000000u, 0xb4000000u, 0x00000001u, 0x00000001u}}, + {{0x7f7fffffu, 0x00000003u}, {0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x35bfffffu, 0x35bfffffu}, {0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}}, + {{0x80000000u, 0x00000001u}, {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u}, {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u}}, + {{0x80000001u, 0x00800002u}, {0x00800001u, 0x80800003u, 0x00800003u, 0x80000000u, 0x80000000u}, {0x00800002u, 0x80800002u, 0x00800002u, 0x80000000u, 0x00000000u}}, + {{0x80000001u, 0x80ffffffu}, {0x81000000u, 0x00fffffeu, 0x80fffffeu, 0x00000000u, 0x00000000u}, {0x80ffffffu, 0x00ffffffu, 0x80ffffffu, 0x00000000u, 0x00000000u}}, + {{0x80000002u, 0x3f800000u}, {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x80000000u, 0x80000000u}, {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x80000000u, 0x00000000u}}, + {{0x80000002u, 0xff7fffffu}, {0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x357fffffu, 0x357fffffu}, {0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}}, + {{0x80000003u, 0x7f800000u}, {0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xff800000u}, {0x7f800000u, 0xff800000u, 0x7f800000u, 0xffc00000u, 0x00000000u}}, + {{0x80400000u, 0x00000002u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u}, {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u}}, + {{0x80400000u, 0x80000003u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {{0x807fffffu, 0x00800000u}, {0x00000000u, 0x80ffffffu, 0x00ffffffu, 0x80000000u, 0x80000000u}, {0x00800000u, 0x80800000u, 0x00800000u, 0x80000000u, 0x00000000u}}, + {{0x807fffffu, 0x80800001u}, {0x81000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, {0x80800001u, 0x00800001u, 0x80800001u, 0x00000000u, 0x00000000u}}, + {{0x80800000u, 0x80000001u}, {0x80800001u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80800000u, 0x80800000u, 0x00800000u, 0x00000000u, 0x00000000u}}, + {{0x80800001u, 0x33800000u}, {0x33800000u, 0xb3800000u, 0x33800000u, 0x80000000u, 0x80000000u}, {0x33800000u, 0xb3800000u, 0x33800000u, 0x80000001u, 0x80000001u}}, + {{0x80800002u, 0x00800000u}, {0x80000000u, 0x81000001u, 0x01000001u, 0x80000000u, 0x80000000u}, {0x80000002u, 0x81000001u, 0x01000001u, 0x80000000u, 0x80000000u}}, + {{0x80ffffffu, 0x00000002u}, {0x80fffffdu, 0x81000000u, 0x01000000u, 0x80000000u, 0x80000000u}, {0x80ffffffu, 0x80ffffffu, 0x00ffffffu, 0x80000000u, 0x00000000u}}, + {{0x80ffffffu, 0x80800002u}, {0x81400000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x81400000u, 0x807ffffdu, 0x007ffffdu, 0x00000000u, 0x00000000u}}, + {{0x81000000u, 0x80000003u}, {0x81000002u, 0x80fffffdu, 0x00fffffdu, 0x00000000u, 0x00000000u}, {0x81000000u, 0x81000000u, 0x01000000u, 0x00000000u, 0x00000000u}}, + {{0x81000001u, 0x80000001u}, {0x81000002u, 0x81000000u, 0x01000000u, 0x00000000u, 0x00000000u}, {0x81000001u, 0x81000001u, 0x01000001u, 0x00000000u, 0x00000000u}}, + {{0xbf800000u, 0x807fffffu}, {0xbf800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, {0xbf800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}}, + {{0xb4000000u, 0x00000003u}, {0xb4000000u, 0xb4000000u, 0x34000000u, 0x80000000u, 0x80000000u}, {0xb4000000u, 0xb4000000u, 0x34000000u, 0x80000000u, 0x00000000u}}, + {{0xff7fffffu, 0x00000003u}, {0xff7fffffu, 0xff7fffffu, 0x7f7fffffu, 0xb5bfffffu, 0xb5bfffffu}, {0xff7fffffu, 0xff7fffffu, 0x7f7fffffu, 0x80000000u, 0x00000000u}}, +}}; + +constexpr std::array, 24> TinyProductVectors{{ + {{0x3f7ffffeu, 0x00800001u}, {0x00800000u, 0x00800000u}, {0x00800000u, 0x00800000u}}, + {{0x3f7ffffeu, 0x80800001u}, {0x80800000u, 0x80800000u}, {0x80800000u, 0x80800000u}}, + {{0xbf7ffffeu, 0x00800001u}, {0x80800000u, 0x80800000u}, {0x80800000u, 0x80800000u}}, + {{0xbf7ffffeu, 0x80800001u}, {0x00800000u, 0x00800000u}, {0x00800000u, 0x00800000u}}, + {{0x3f800001u, 0x007fffffu}, {0x00800000u, 0x00800000u}, {0x00000000u, 0x00000000u}}, + {{0x3f800001u, 0x807fffffu}, {0x80800000u, 0x80800000u}, {0x80000000u, 0x00000000u}}, + {{0xbf800001u, 0x007fffffu}, {0x80800000u, 0x80800000u}, {0x80000000u, 0x00000000u}}, + {{0xbf800001u, 0x807fffffu}, {0x00800000u, 0x00800000u}, {0x00000000u, 0x00000000u}}, + {{0x3f800002u, 0x007ffffeu}, {0x00800000u, 0x00800000u}, {0x00000000u, 0x00000000u}}, + {{0x3f800002u, 0x807ffffeu}, {0x80800000u, 0x80800000u}, {0x80000000u, 0x00000000u}}, + {{0xbf800002u, 0x007ffffeu}, {0x80800000u, 0x80800000u}, {0x80000000u, 0x00000000u}}, + {{0xbf800002u, 0x807ffffeu}, {0x00800000u, 0x00800000u}, {0x00000000u, 0x00000000u}}, + {{0x3f7ffffeu, 0x00800000u}, {0x00000000u, 0x00000000u}, {0x007fffffu, 0x007fffffu}}, + {{0xbf7fffffu, 0x00400000u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x3f800001u, 0x80000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0xbfffffffu, 0x00400000u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x3f400000u, 0x00800000u}, {0x00000000u, 0x00000000u}, {0x00600000u, 0x00600000u}}, + {{0x40000000u, 0x807fffffu}, {0x80fffffeu, 0x80fffffeu}, {0x80000000u, 0x00000000u}}, + {{0xcb000000u, 0x007ffffeu}, {0x8bfffffcu, 0x8bfffffcu}, {0x80000000u, 0x00000000u}}, + {{0xcb7fffffu, 0x807ffffeu}, {0x0c7ffffbu, 0x0c7ffffbu}, {0x00000000u, 0x00000000u}}, + {{0x5f800000u, 0x00400000u}, {0x20000000u, 0x20000000u}, {0x00000000u, 0x00000000u}}, + {{0x7f000000u, 0x80400000u}, {0xbf800000u, 0xbf800000u}, {0x80000000u, 0x00000000u}}, + {{0xb3800000u, 0x00ffffffu}, {0x80000000u, 0x80000000u}, {0x80000001u, 0x80000001u}}, + {{0xb4000000u, 0x80800000u}, {0x00000000u, 0x00000000u}, {0x00000001u, 0x00000001u}}, +}}; + +constexpr std::array, 32> ConstantFactorVectors{{ + {{0x00000001u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}}, + {{0x00000002u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}}, + {{0x00000003u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}}, + {{0x00155555u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}}, + {{0x003fffffu, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}}, + {{0x00400000u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}}, + {{0x00400001u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}}, + {{0x00555555u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}}, + {{0x007ffffeu, 0x00000001u}, {0x00bffffdu, 0x00bffffdu}, {0x00000000u, 0x00000000u}}, + {{0x007fffffu, 0x00000001u}, {0x00bffffeu, 0x00bffffeu}, {0x00000000u, 0x00000000u}}, + {{0x00800000u, 0x00000001u}, {0x00c00000u, 0x00c00000u}, {0x00c00000u, 0x00c00000u}}, + {{0x00800001u, 0x00000001u}, {0x00c00002u, 0x00c00002u}, {0x00c00002u, 0x00c00002u}}, + {{0x00aaaaabu, 0x00000001u}, {0x01000000u, 0x01000000u}, {0x01000000u, 0x01000000u}}, + {{0x00ffffffu, 0x00000001u}, {0x013fffffu, 0x013fffffu}, {0x013fffffu, 0x013fffffu}}, + {{0x01000000u, 0x00000001u}, {0x01400000u, 0x01400000u}, {0x01400000u, 0x01400000u}}, + {{0x3f800000u, 0x00000001u}, {0x3fc00000u, 0x3fc00000u}, {0x3fc00000u, 0x3fc00000u}}, + {{0x00000000u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}}, + {{0x80000001u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x80000002u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x80000003u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x80155555u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x803fffffu, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x80400000u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x80400001u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x80555555u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}}, + {{0x807ffffeu, 0x00000001u}, {0x80bffffdu, 0x80bffffdu}, {0x80000000u, 0x00000000u}}, + {{0x807fffffu, 0x00000001u}, {0x80bffffeu, 0x80bffffeu}, {0x80000000u, 0x00000000u}}, + {{0x80800000u, 0x00000001u}, {0x80c00000u, 0x80c00000u}, {0x80c00000u, 0x80c00000u}}, + {{0x80800001u, 0x00000001u}, {0x80c00002u, 0x80c00002u}, {0x80c00002u, 0x80c00002u}}, + {{0x80aaaaabu, 0x00000001u}, {0x81000000u, 0x81000000u}, {0x81000000u, 0x81000000u}}, + {{0x80ffffffu, 0x00000001u}, {0x813fffffu, 0x813fffffu}, {0x813fffffu, 0x813fffffu}}, + {{0x81000000u, 0x00000001u}, {0x81400000u, 0x81400000u}, {0x81400000u, 0x81400000u}}, +}}; + +constexpr std::uint32_t FirstTinyProductLane = static_cast(ArithmeticVectors.size()); +constexpr std::uint32_t FirstConstantFactorLane = FirstTinyProductLane + static_cast(TinyProductVectors.size()); +static_assert(FirstConstantFactorLane + ConstantFactorVectors.size() <= Threads); + +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; +} + +template +void Fill(const std::array, Size>& vectors, std::uint32_t firstLane) { + for (std::uint32_t row = 0; row < Size; ++row) { + Input[(firstLane + row) * Inputs] = vectors[row].operands[0]; + Input[(firstLane + row) * Inputs + 1u] = vectors[row].operands[1]; + } +} + +bool Run(AgcDriver::VulkanDevice& device, ShaderRecompiler::ShaderFloatMode floatMode) { + Input.fill(0u); + Fill(ArithmeticVectors, 0u); + Fill(TinyProductVectors, FirstTinyProductLane); + Fill(ConstantFactorVectors, FirstConstantFactorLane); + 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) { + const std::string message = error.what(); + const bool keepsDenormals = request.target.float32DenormPreserve && !request.target.float32FlushToZero; + Require(!keepsDenormals && message.find("needs DenormPreserve 32") != std::string::npos, "f32 add and multiply FLOAT_MODE " + Hex(floatMode.floatMode) + ": " + message); + return false; + } + device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + return true; +} + +template +void Check(const std::array, Size>& vectors, std::uint32_t firstLane, std::uint32_t firstInstruction, std::uint32_t denormals, bool ieee) { + for (std::uint32_t row = 0; row < Size; ++row) { + const auto& vector = vectors[row]; + for (std::uint32_t index = 0; index < Count; ++index) { + const std::uint32_t instruction = firstInstruction + index; + const auto actual = Output[(firstLane + row) * Results + instruction]; + const auto expected = denormals == 1u ? vector.outputsFlushed[index] : vector.inputsFlushed[index]; + Require(actual == expected, std::string(Names[instruction]) + " f32 denormal mode " + std::to_string(denormals) + (ieee ? ", IEEE=1" : ", IEEE=0") + " of " + + Hex(vector.operands[0]) + ", " + Hex(vector.operands[1]) + " is " + Hex(actual) + ", expected " + Hex(expected)); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + std::uint32_t checked = 0u; + for (const std::uint32_t denormals : {1u, 2u}) { + for (const bool ieee : {false, true}) { + if (!Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u | denormals << 4u, true, ieee, false})) continue; + ++checked; + Check(ArithmeticVectors, 0u, 0u, denormals, ieee); + Check(TinyProductVectors, FirstTinyProductLane, FirstMultiply, denormals, ieee); + Check(ConstantFactorVectors, FirstConstantFactorLane, FirstConstantFactor, denormals, ieee); + } + } + std::printf("f32 add and multiply 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/F32ArithmeticNan.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp index c78f91002f..a4b48e2c5e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp @@ -23,6 +23,7 @@ constexpr std::uint32_t Inputs = 4; constexpr std::uint32_t Results = 16; alignas(256) std::array Input{}; alignas(256) std::array Output{}; +bool FusedExactArithmetic = false; alignas(256) constexpr std::array Code{ 0x34020084u, 0x34060086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e140280u, 0x7e160280u, 0x7e180280u, @@ -172,7 +173,7 @@ std::string Hex(std::uint32_t value) { } void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { - Require(actual == expected, std::string("f32 arithmetic nan: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); + Require(actual == expected, std::string("f32 arithmetic nan: ") + (FusedExactArithmetic ? "fused " : "") + "lane " + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); } bool IsNan32(std::uint32_t value) { @@ -202,6 +203,7 @@ void Run(AgcDriver::VulkanDevice& device, const std::optional(code.data())); device.WaitIdle(); @@ -223,12 +225,16 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - Run(*device, std::nullopt); - Check(ExpectedNoIeee, "no float mode"); - Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}); - Check(ExpectedNoIeee, "IEEE=0"); - Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}); - Check(ExpectedIeee, "IEEE=1"); + for (const bool fused : {false, true}) { + if (!fused && device->Target().fusedExactArithmetic) continue; + FusedExactArithmetic = fused; + Run(*device, std::nullopt); + Check(ExpectedNoIeee, "no float mode"); + Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}); + Check(ExpectedNoIeee, "IEEE=0"); + Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}); + Check(ExpectedIeee, "IEEE=1"); + } std::puts("f32 arithmetic nan tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32ConvertDenormalModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32ConvertDenormalModes.cpp new file mode 100644 index 0000000000..9dcc8b2bf9 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32ConvertDenormalModes.cpp @@ -0,0 +1,212 @@ +#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 + +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 = 16; +constexpr std::uint32_t Columns = 12; +constexpr std::uint32_t Float64Low = 5; +constexpr std::uint32_t Float64High = 6; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e141104u, 0x7e160f04u, 0x7e181904u, + 0x7e1a1b04u, 0x7e1c7f04u, 0xd7680011u, 0x00020b04u, 0xd7690012u, 0x00020b04u, 0xd55e0013u, 0x02010104u, + 0x5e280b04u, 0x7e2a1504u, 0xe0781000u, 0x80010a02u, 0xe0781010u, 0x80010e02u, 0xe0781020u, 0x80011202u, + 0xbf810000u, +}; + +alignas(256) constexpr std::array Float64Code{ + 0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e1e2104u, 0xe0781000u, 0x80010a02u, + 0xe0781010u, 0x80010e02u, 0xe0781020u, 0x80011202u, 0xbf810000u, +}; + +constexpr const char* Names[Columns] = { + "v_cvt_i32_f32 v10, v4", + "v_cvt_u32_f32 v11, v4", + "v_cvt_rpi_i32_f32 v12, v4", + "v_cvt_flr_i32_f32 v13, v4", + "v_frexp_exp_i32_f32 v14, v4", + "v_cvt_f64_f32 v[15:16], v4 (low)", + "v_cvt_f64_f32 v[15:16], v4 (high)", + "v_cvt_pknorm_i16_f32 v17, v4, v5", + "v_cvt_pknorm_u16_f32 v18, v4, v5", + "v_cvt_pk_u8_f32 v19, v4, 0, 0", + "v_cvt_pkrtz_f16_f32 v20, v4, v5", + "v_cvt_f16_f32 v21, v4", +}; + +struct ConvertVector { + std::uint32_t operands[2]; + std::uint32_t denormalsFlushed[Columns]; + std::uint32_t denormalsKept[Columns]; +}; + +constexpr std::array Vectors{{ + {{0x00000001u, 0x00400000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffff6cu, 0x00000000u, 0x36a00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {{0x007fffffu, 0x80000001u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffff82u, 0xc0000000u, 0x380fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}}, + {{0x00400000u, 0x3f800000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fff0000u, 0xffff0000u, 0x00000000u, 0x3c000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffff82u, 0x00000000u, 0x38000000u, 0x7fff0000u, 0xffff0000u, 0x00000000u, 0x3c000000u, 0x00000000u}}, + {{0x80000001u, 0x007fffffu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffff6cu, 0x00000000u, 0xb6a00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}}, + {{0x807fffffu, 0x00000001u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffff82u, 0xc0000000u, 0xb80fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}}, + {{0x80400000u, 0xbf000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xc0000000u, 0x00000000u, 0x00000000u, 0xb8008000u, 0x00008000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffff82u, 0x00000000u, 0xb8000000u, 0xc0000000u, 0x00000000u, 0x00000000u, 0xb8008000u, 0x00008000u}}, + {{0x00800000u, 0x80400000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffff83u, 0x00000000u, 0x38100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffff83u, 0x00000000u, 0x38100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}}, + {{0x80800000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffff83u, 0x00000000u, 0xb8100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffff83u, 0x00000000u, 0xb8100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}}, + {{0x3f000000u, 0x00000001u}, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u, 0x00004000u, 0x00008000u, 0x00000000u, 0x00003800u, 0x00003800u}, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u, 0x00004000u, 0x00008000u, 0x00000000u, 0x00003800u, 0x00003800u}}, + {{0xbfc00000u, 0x807fffffu}, {0xffffffffu, 0x00000000u, 0xffffffffu, 0xfffffffeu, 0x00000001u, 0x00000000u, 0xbff80000u, 0x00008001u, 0x00000000u, 0x00000000u, 0x8000be00u, 0x0000be00u}, {0xffffffffu, 0x00000000u, 0xffffffffu, 0xfffffffeu, 0x00000001u, 0x00000000u, 0xbff80000u, 0x00008001u, 0x00000000u, 0x00000000u, 0x8000be00u, 0x0000be00u}}, + {{0x3f800000u, 0x80000002u}, {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000000u, 0x3ff00000u, 0x00007fffu, 0x0000ffffu, 0x00000001u, 0x80003c00u, 0x00003c00u}, {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000000u, 0x3ff00000u, 0x00007fffu, 0x0000ffffu, 0x00000001u, 0x80003c00u, 0x00003c00u}}, + {{0x00000000u, 0x00000002u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {{0x80000000u, 0x3f800000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x7fff0000u, 0xffff0000u, 0x00000000u, 0x3c008000u, 0x00008000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x7fff0000u, 0xffff0000u, 0x00000000u, 0x3c008000u, 0x00008000u}}, + {{0x33800000u, 0x00000001u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffe9u, 0x00000000u, 0x3e700000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffe9u, 0x00000000u, 0x3e700000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u}}, + {{0x477fe000u, 0x80000001u}, {0x0000ffe0u, 0x0000ffe0u, 0x0000ffe0u, 0x0000ffe0u, 0x00000010u, 0x00000000u, 0x40effc00u, 0x00007fffu, 0x0000ffffu, 0x000000ffu, 0x80007bffu, 0x00007bffu}, {0x0000ffe0u, 0x0000ffe0u, 0x0000ffe0u, 0x0000ffe0u, 0x00000010u, 0x00000000u, 0x40effc00u, 0x00007fffu, 0x0000ffffu, 0x000000ffu, 0x80007bffu, 0x00007bffu}}, + {{0x501502f9u, 0x00800000u}, {0x7fffffffu, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x00000022u, 0x20000000u, 0x4202a05fu, 0x00007fffu, 0x0000ffffu, 0x000000ffu, 0x00007bffu, 0x00007c00u}, {0x7fffffffu, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x00000022u, 0x20000000u, 0x4202a05fu, 0x00007fffu, 0x0000ffffu, 0x000000ffu, 0x00007bffu, 0x00007c00u}}, + {{0xd01502f9u, 0x007fffffu}, {0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000022u, 0x20000000u, 0xc202a05fu, 0x00008001u, 0x00000000u, 0x00000000u, 0x0000fbffu, 0x0000fc00u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000022u, 0x20000000u, 0xc202a05fu, 0x00008001u, 0x00000000u, 0x00000000u, 0x0000fbffu, 0x0000fc00u}}, + {{0x7f800000u, 0xbfc00000u}, {0x7fffffffu, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x80017fffu, 0x0000ffffu, 0x000000ffu, 0xbe007c00u, 0x00007c00u}, {0x7fffffffu, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x80017fffu, 0x0000ffffu, 0x000000ffu, 0xbe007c00u, 0x00007c00u}}, + {{0xff800000u, 0x00000001u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00008001u, 0x00000000u, 0x00000000u, 0x0000fc00u, 0x0000fc00u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00008001u, 0x00000000u, 0x00000000u, 0x0000fc00u, 0x0000fc00u}}, + {{0x7fc00000u, 0x80400000u}, {0x00000000u, 0x00000000u, 0x7fffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80007e00u, 0x00007e00u}, {0x00000000u, 0x00000000u, 0x7fffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80007e00u, 0x00007e00u}}, + {{0xffc00000u, 0x00400000u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fe00u, 0x0000fe00u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fe00u, 0x0000fe00u}}, + {{0x4f800000u, 0x80000000u}, {0x7fffffffu, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x00000021u, 0x00000000u, 0x41f00000u, 0x00007fffu, 0x0000ffffu, 0x000000ffu, 0x80007bffu, 0x00007c00u}, {0x7fffffffu, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x00000021u, 0x00000000u, 0x41f00000u, 0x00007fffu, 0x0000ffffu, 0x000000ffu, 0x80007bffu, 0x00007c00u}}, + {{0xcf000000u, 0x007fffffu}, {0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000020u, 0x00000000u, 0xc1e00000u, 0x00008001u, 0x00000000u, 0x00000000u, 0x0000fbffu, 0x0000fc00u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000020u, 0x00000000u, 0xc1e00000u, 0x00008001u, 0x00000000u, 0x00000000u, 0x0000fbffu, 0x0000fc00u}}, + {{0x3f7fffffu, 0x80000001u}, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xe0000000u, 0x3fefffffu, 0x00007fffu, 0x0000ffffu, 0x00000001u, 0x80003bffu, 0x00003c00u}, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xe0000000u, 0x3fefffffu, 0x00007fffu, 0x0000ffffu, 0x00000001u, 0x80003bffu, 0x00003c00u}}, + {{0x00000002u, 0x3f800000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fff0000u, 0xffff0000u, 0x00000000u, 0x3c000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffff6du, 0x00000000u, 0x36b00000u, 0x7fff0000u, 0xffff0000u, 0x00000000u, 0x3c000000u, 0x00000000u}}, + {{0x80000002u, 0x00000003u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffff6du, 0x00000000u, 0xb6b00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}}, + {{0x00000001u, 0x00000001u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffff6cu, 0x00000000u, 0x36a00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {{0x007fffffu, 0x807fffffu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffff82u, 0xc0000000u, 0x380fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}}, + {{0x3f000000u, 0x80000001u}, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u, 0x00004000u, 0x00008000u, 0x00000000u, 0x80003800u, 0x00003800u}, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u, 0x00004000u, 0x00008000u, 0x00000000u, 0x80003800u, 0x00003800u}}, + {{0x80000001u, 0x00400000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffff6cu, 0x00000000u, 0xb6a00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00008000u}}, + {{0x3e800000u, 0x80800000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x3fd00000u, 0x00002000u, 0x00004000u, 0x00000000u, 0x80003400u, 0x00003400u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x3fd00000u, 0x00002000u, 0x00004000u, 0x00000000u, 0x80003400u, 0x00003400u}}, + {{0xbf800000u, 0x00000001u}, {0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u, 0x00000000u, 0xbff00000u, 0x00008001u, 0x00000000u, 0x00000000u, 0x0000bc00u, 0x0000bc00u}, {0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u, 0x00000000u, 0xbff00000u, 0x00008001u, 0x00000000u, 0x00000000u, 0x0000bc00u, 0x0000bc00u}}, +}}; + +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; +} + +bool InPass(std::uint32_t column, bool float64) { + return (column == Float64Low || column == Float64High) == float64; +} + +std::vector Run(AgcDriver::VulkanDevice& device, bool float64, ShaderRecompiler::ShaderFloatMode floatMode) { + Input.fill(0u); + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) { + Input[lane * Inputs] = Vectors[lane].operands[0]; + Input[lane * Inputs + 1u] = Vectors[lane].operands[1]; + } + 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 auto code = float64 ? std::span(Float64Code) : std::span(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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + return {Output.begin(), Output.end()}; +} + +std::string Where(std::uint32_t lane, std::uint32_t column, std::uint32_t denormals, bool ieee) { + return std::string(Names[column]) + " of " + Hex(Vectors[lane].operands[0]) + ", " + Hex(Vectors[lane].operands[1]) + " in f32 denormal mode " + + std::to_string(denormals) + (ieee ? ", IEEE=1" : ", IEEE=0"); +} + +void CheckFlushed(const std::vector& output, bool float64, std::uint32_t denormals, bool ieee) { + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) { + for (std::uint32_t column = 0; column < Columns; ++column) { + if (!InPass(column, float64)) continue; + const auto value = output[lane * Results + column]; + const auto expected = Vectors[lane].denormalsFlushed[column]; + Require(value == expected, Where(lane, column, denormals, ieee) + " is " + Hex(value) + ", expected " + Hex(expected)); + } + } +} + +void CheckKept(const std::vector& output, bool float64, bool ieee) { + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) { + for (std::uint32_t column = 0; column < Columns; ++column) { + if (!InPass(column, float64)) continue; + const auto value = output[lane * Results + column]; + const auto& vector = Vectors[lane]; + Require(value == vector.denormalsKept[column] || value == vector.denormalsFlushed[column], + Where(lane, column, 3u, ieee) + " is " + Hex(value) + ", expected " + Hex(vector.denormalsKept[column]) + " or, on a driver that flushes f32 denormals, " + + Hex(vector.denormalsFlushed[column])); + } + } +} + +void CheckSame(const std::vector& output, const std::vector& reference, bool float64, std::uint32_t denormals, std::uint32_t like, bool ieee) { + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) { + for (std::uint32_t column = 0; column < Columns; ++column) { + if (!InPass(column, float64)) continue; + const auto value = output[lane * Results + column]; + const auto expected = reference[lane * Results + column]; + Require(value == expected, Where(lane, column, denormals, ieee) + " is " + Hex(value) + ", but mode " + std::to_string(like) + " gave " + Hex(expected)); + } + } +} + +ShaderRecompiler::ShaderFloatMode Mode(std::uint32_t denormals, bool ieee) { + return ShaderRecompiler::ShaderFloatMode{0xc0u | denormals << 4u, true, ieee, false}; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool hasFloat64 = TargetHasCapability(device->Target(), spv::CapabilityFloat64); + if (!hasFloat64) std::puts("v_cvt_f64_f32 skipped, the device has no shaderFloat64"); + for (const bool float64 : {false, true}) { + if (float64 && !hasFloat64) continue; + for (const bool ieee : {false, true}) { + const auto flushed = Run(*device, float64, Mode(0u, ieee)); + CheckFlushed(flushed, float64, 0u, ieee); + const auto inputsFlushed = Run(*device, float64, Mode(2u, ieee)); + CheckFlushed(inputsFlushed, float64, 2u, ieee); + CheckSame(inputsFlushed, flushed, float64, 2u, 0u, ieee); + const auto kept = Run(*device, float64, Mode(3u, ieee)); + CheckKept(kept, float64, ieee); + const auto outputsFlushed = Run(*device, float64, Mode(1u, ieee)); + CheckSame(outputsFlushed, kept, float64, 1u, 3u, ieee); + } + } + std::puts("f32 conversion denormal mode 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/F32DenormalFlush.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32DenormalFlush.cpp index 19beb4db4c..bb4aa369e0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/F32DenormalFlush.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32DenormalFlush.cpp @@ -382,8 +382,10 @@ std::string Hex(std::uint32_t value) { return text; } +bool FusedExactArithmetic = false; + void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { - Require(actual == expected, std::string("f32 denormal flush: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); + Require(actual == expected, std::string("f32 denormal flush: ") + (FusedExactArithmetic ? "fused " : "") + "lane " + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); } bool IsNan(std::uint32_t value) { @@ -434,6 +436,7 @@ void Run(AgcDriver::VulkanDevice& device, const std::optional(code.data())); device.WaitIdle(); @@ -451,7 +454,7 @@ void Check(const char* mode) { if (hostSplits && tid == TinyProductLane && (i == 4u || i == 5u)) continue; const bool floatResult = i < 12u || i >= 15u; if (floatResult && IsNan(Expected[tid][i])) { - Require(IsNan(out[i]), std::string("f32 denormal flush: lane ") + std::to_string(tid) + " " + mode + " " + Names[i] + " is " + Hex(out[i]) + ", expected a NaN"); + Require(IsNan(out[i]), std::string("f32 denormal flush: ") + (FusedExactArithmetic ? "fused " : "") + "lane " + std::to_string(tid) + " " + mode + " " + Names[i] + " is " + Hex(out[i]) + ", expected a NaN"); continue; } Expect(tid, out[i], Expected[tid][i], (std::string(mode) + " " + Names[i]).c_str()); @@ -516,9 +519,12 @@ int main() { Require(!softwareDeclares && hardwareDeclares == declared, declared ? "f32 denormal flush: only the hardware-flush module declares DenormFlushToZero 32" : "f32 denormal flush: a module flushed natively declares DenormFlushToZero 32 without the device capability"); Require(hardwareWords < software, "f32 denormal flush: the hardware-flush module is " + std::to_string(hardwareWords) + " words, the emulated one " + std::to_string(software)); } - for (const bool flush : {false, true}) { + for (const std::uint32_t variant : {0u, 1u, 2u, 3u}) { + const bool flush = (variant & 1u) != 0u; if (flush && !hardware) continue; + if ((variant & 2u) == 0u && device->Target().fusedExactArithmetic) continue; HardwareFlush = flush; + FusedExactArithmetic = (variant & 2u) != 0u; Run(*device, std::nullopt); Check("no float mode"); Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxDenormalModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxDenormalModes.cpp new file mode 100644 index 0000000000..a9d0238d0b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxDenormalModes.cpp @@ -0,0 +1,181 @@ +#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 = 16; +constexpr std::uint32_t Instructions = 5; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x1e140b04u, 0x20160b04u, 0xd551000cu, + 0x041a0b04u, 0xd554000du, 0x041a0b04u, 0xd557000eu, 0x041a0b04u, 0xe0781000u, 0x80010a02u, 0xe0701010u, + 0x80010e02u, 0xbf810000u, +}; + +constexpr const char* Names[Instructions] = {"v_min_f32", "v_max_f32", "v_min3_f32", "v_max3_f32", "v_med3_f32"}; + +struct MinMaxVector { + std::uint32_t operands[3]; + std::uint32_t inputsKept[Instructions]; + std::uint32_t inputsFlushed[Instructions]; +}; + +constexpr std::array Vectors{{ + {0x00000000u, 0x00000000u, 0x00000001u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {0x00000000u, 0x80000001u, 0x80000000u, {0x80000001u, 0x00000000u, 0x80000001u, 0x00000000u, 0x80000000u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x00000000u, 0x007fffffu, 0x7f800000u, {0x00000000u, 0x007fffffu, 0x00000000u, 0x7f800000u, 0x007fffffu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u}}, + {0x00000000u, 0x80800000u, 0x807fffffu, {0x80800000u, 0x00000000u, 0x80800000u, 0x00000000u, 0x807fffffu}, {0x80800000u, 0x00000000u, 0x80800000u, 0x00000000u, 0x80000000u}}, + {0x80000000u, 0x80000000u, 0x00000001u, {0x80000000u, 0x80000000u, 0x80000000u, 0x00000001u, 0x80000000u}, {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x80000000u, 0x80000001u, 0x807fffffu, {0x80000001u, 0x80000000u, 0x807fffffu, 0x80000000u, 0x80000001u}, {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}}, + {0x80000000u, 0x807fffffu, 0x00000001u, {0x807fffffu, 0x80000000u, 0x807fffffu, 0x00000001u, 0x80000000u}, {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x80000000u, 0x3f800000u, 0x807fffffu, {0x80000000u, 0x3f800000u, 0x807fffffu, 0x3f800000u, 0x80000000u}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u}}, + {0x00000001u, 0x00000000u, 0x3f800000u, {0x00000000u, 0x00000001u, 0x00000000u, 0x3f800000u, 0x00000001u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}}, + {0x00000001u, 0x00000001u, 0x807fffffu, {0x00000001u, 0x00000001u, 0x807fffffu, 0x00000001u, 0x00000001u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}}, + {0x00000001u, 0x007fffffu, 0x00000001u, {0x00000001u, 0x007fffffu, 0x00000001u, 0x007fffffu, 0x00000001u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {0x00000001u, 0x807fffffu, 0xff800000u, {0x807fffffu, 0x00000001u, 0xff800000u, 0x00000001u, 0x807fffffu}, {0x80000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000000u}}, + {0x00000001u, 0x80800000u, 0x3f800000u, {0x80800000u, 0x00000001u, 0x80800000u, 0x3f800000u, 0x00000001u}, {0x80800000u, 0x00000000u, 0x80800000u, 0x3f800000u, 0x00000000u}}, + {0x00000001u, 0xbf800000u, 0x807fffffu, {0xbf800000u, 0x00000001u, 0xbf800000u, 0x00000001u, 0x807fffffu}, {0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u}}, + {0x00000001u, 0xff800000u, 0x00000001u, {0xff800000u, 0x00000001u, 0xff800000u, 0x00000001u, 0x00000001u}, {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u}}, + {0x80000001u, 0x00000000u, 0xff800000u, {0x80000001u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000001u}, {0x80000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000000u}}, + {0x80000001u, 0x00000001u, 0x3f800000u, {0x80000001u, 0x00000001u, 0x80000001u, 0x3f800000u, 0x00000001u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x3f800000u, 0x00000000u}}, + {0x80000001u, 0x007fffffu, 0x807fffffu, {0x80000001u, 0x007fffffu, 0x807fffffu, 0x007fffffu, 0x80000001u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x80000001u, 0x00800000u, 0x00000001u, {0x80000001u, 0x00800000u, 0x80000001u, 0x00800000u, 0x00000001u}, {0x80000000u, 0x00800000u, 0x80000000u, 0x00800000u, 0x00000000u}}, + {0x80000001u, 0x80800000u, 0xff800000u, {0x80800000u, 0x80000001u, 0xff800000u, 0x80000001u, 0x80800000u}, {0x80800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0x80800000u}}, + {0x80000001u, 0xbf800000u, 0x3f800000u, {0xbf800000u, 0x80000001u, 0xbf800000u, 0x3f800000u, 0x80000001u}, {0xbf800000u, 0x80000000u, 0xbf800000u, 0x3f800000u, 0x80000000u}}, + {0x80000001u, 0xff800000u, 0x807fffffu, {0xff800000u, 0x80000001u, 0xff800000u, 0x80000001u, 0x807fffffu}, {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0x80000000u}}, + {0x007fffffu, 0x80000000u, 0x00000001u, {0x80000000u, 0x007fffffu, 0x80000000u, 0x007fffffu, 0x00000001u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}}, + {0x007fffffu, 0x00000001u, 0xff800000u, {0x00000001u, 0x007fffffu, 0xff800000u, 0x007fffffu, 0x00000001u}, {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u}}, + {0x007fffffu, 0x007fffffu, 0x3f800000u, {0x007fffffu, 0x007fffffu, 0x007fffffu, 0x3f800000u, 0x007fffffu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}}, + {0x007fffffu, 0x00800000u, 0x807fffffu, {0x007fffffu, 0x00800000u, 0x807fffffu, 0x00800000u, 0x007fffffu}, {0x00000000u, 0x00800000u, 0x80000000u, 0x00800000u, 0x00000000u}}, + {0x007fffffu, 0x3f800000u, 0x00000001u, {0x007fffffu, 0x3f800000u, 0x00000001u, 0x3f800000u, 0x007fffffu}, {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u}}, + {0x007fffffu, 0xbf800000u, 0xff800000u, {0xbf800000u, 0x007fffffu, 0xff800000u, 0x007fffffu, 0xbf800000u}, {0xbf800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xbf800000u}}, + {0x007fffffu, 0xff800000u, 0x3f800000u, {0xff800000u, 0x007fffffu, 0xff800000u, 0x3f800000u, 0x007fffffu}, {0xff800000u, 0x00000000u, 0xff800000u, 0x3f800000u, 0x00000000u}}, + {0x807fffffu, 0x80000000u, 0x807fffffu, {0x807fffffu, 0x80000000u, 0x807fffffu, 0x80000000u, 0x807fffffu}, {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}}, + {0x807fffffu, 0x80000001u, 0x00000001u, {0x807fffffu, 0x80000001u, 0x807fffffu, 0x00000001u, 0x80000001u}, {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x807fffffu, 0x007fffffu, 0xff800000u, {0x807fffffu, 0x007fffffu, 0xff800000u, 0x007fffffu, 0x807fffffu}, {0x80000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000000u}}, + {0x807fffffu, 0x00800000u, 0x3f800000u, {0x807fffffu, 0x00800000u, 0x807fffffu, 0x3f800000u, 0x00800000u}, {0x80000000u, 0x00800000u, 0x80000000u, 0x3f800000u, 0x00800000u}}, + {0x807fffffu, 0x3f800000u, 0x807fffffu, {0x807fffffu, 0x3f800000u, 0x807fffffu, 0x3f800000u, 0x807fffffu}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u}}, + {0x807fffffu, 0x7f800000u, 0x00000001u, {0x807fffffu, 0x7f800000u, 0x807fffffu, 0x7f800000u, 0x00000001u}, {0x80000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x00000000u}}, + {0x807fffffu, 0xff800000u, 0xff800000u, {0xff800000u, 0x807fffffu, 0xff800000u, 0x807fffffu, 0xff800000u}, {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u}}, + {0x00800000u, 0x80000001u, 0x00000000u, {0x80000001u, 0x00800000u, 0x80000001u, 0x00800000u, 0x00000000u}, {0x80000000u, 0x00800000u, 0x80000000u, 0x00800000u, 0x00000000u}}, + {0x00800000u, 0x007fffffu, 0xbf800000u, {0x007fffffu, 0x00800000u, 0xbf800000u, 0x00800000u, 0x007fffffu}, {0x00000000u, 0x00800000u, 0xbf800000u, 0x00800000u, 0x00000000u}}, + {0x00800000u, 0x80800000u, 0x007fffffu, {0x80800000u, 0x00800000u, 0x80800000u, 0x00800000u, 0x007fffffu}, {0x80800000u, 0x00800000u, 0x80800000u, 0x00800000u, 0x00000000u}}, + {0x80800000u, 0x00000000u, 0x807fffffu, {0x80800000u, 0x00000000u, 0x80800000u, 0x00000000u, 0x807fffffu}, {0x80800000u, 0x00000000u, 0x80800000u, 0x00000000u, 0x80000000u}}, + {0x80800000u, 0x80000001u, 0x007fffffu, {0x80800000u, 0x80000001u, 0x80800000u, 0x007fffffu, 0x80000001u}, {0x80800000u, 0x80000000u, 0x80800000u, 0x00000000u, 0x80000000u}}, + {0x80800000u, 0x807fffffu, 0x80000000u, {0x80800000u, 0x807fffffu, 0x80800000u, 0x80000000u, 0x807fffffu}, {0x80800000u, 0x80000000u, 0x80800000u, 0x80000000u, 0x80000000u}}, + {0x80800000u, 0x3f800000u, 0x007fffffu, {0x80800000u, 0x3f800000u, 0x80800000u, 0x3f800000u, 0x007fffffu}, {0x80800000u, 0x3f800000u, 0x80800000u, 0x3f800000u, 0x00000000u}}, + {0x3f800000u, 0x80000000u, 0x807fffffu, {0x80000000u, 0x3f800000u, 0x807fffffu, 0x3f800000u, 0x80000000u}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u}}, + {0x3f800000u, 0x80000001u, 0x3f800000u, {0x80000001u, 0x3f800000u, 0x80000001u, 0x3f800000u, 0x3f800000u}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x3f800000u}}, + {0x3f800000u, 0x807fffffu, 0x807fffffu, {0x807fffffu, 0x3f800000u, 0x807fffffu, 0x3f800000u, 0x807fffffu}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u}}, + {0x3f800000u, 0xbf800000u, 0x007fffffu, {0xbf800000u, 0x3f800000u, 0xbf800000u, 0x3f800000u, 0x007fffffu}, {0xbf800000u, 0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u}}, + {0xbf800000u, 0x00000001u, 0x80000001u, {0xbf800000u, 0x00000001u, 0xbf800000u, 0x00000001u, 0x80000001u}, {0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u}}, + {0xbf800000u, 0x007fffffu, 0x00000000u, {0xbf800000u, 0x007fffffu, 0xbf800000u, 0x007fffffu, 0x00000000u}, {0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x00000000u}}, + {0xbf800000u, 0x807fffffu, 0xbf800000u, {0xbf800000u, 0x807fffffu, 0xbf800000u, 0x807fffffu, 0xbf800000u}, {0xbf800000u, 0x80000000u, 0xbf800000u, 0x80000000u, 0xbf800000u}}, + {0xbf800000u, 0x7f800000u, 0x007fffffu, {0xbf800000u, 0x7f800000u, 0xbf800000u, 0x7f800000u, 0x007fffffu}, {0xbf800000u, 0x7f800000u, 0xbf800000u, 0x7f800000u, 0x00000000u}}, + {0x7f800000u, 0x00000001u, 0x80800000u, {0x00000001u, 0x7f800000u, 0x80800000u, 0x7f800000u, 0x00000001u}, {0x00000000u, 0x7f800000u, 0x80800000u, 0x7f800000u, 0x00000000u}}, + {0x7f800000u, 0x007fffffu, 0x007fffffu, {0x007fffffu, 0x7f800000u, 0x007fffffu, 0x7f800000u, 0x007fffffu}, {0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u}}, + {0x7f800000u, 0x00800000u, 0x80000001u, {0x00800000u, 0x7f800000u, 0x80000001u, 0x7f800000u, 0x00800000u}, {0x00800000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x00800000u}}, + {0x7f800000u, 0xff800000u, 0x007fffffu, {0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x007fffffu}, {0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x00000000u}}, + {0xff800000u, 0x00000001u, 0xff800000u, {0xff800000u, 0x00000001u, 0xff800000u, 0x00000001u, 0xff800000u}, {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u}}, +}}; + +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; +} + +bool Run(AgcDriver::VulkanDevice& device, ShaderRecompiler::ShaderFloatMode floatMode) { + Input.fill(0u); + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) { + std::copy(std::begin(Vectors[lane].operands), std::end(Vectors[lane].operands), Input.begin() + lane * 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) { + const std::string message = error.what(); + const bool keepsDenormals = request.target.float32DenormPreserve && !request.target.float32FlushToZero; + Require(!keepsDenormals && ((floatMode.floatMode >> 4u) & 3u) == 1u && message.find("needs DenormPreserve 32") != std::string::npos, + "f32 min/max FLOAT_MODE " + Hex(floatMode.floatMode) + ": " + message); + return false; + } + device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + return true; +} + +void Check(std::uint32_t denormals, bool ieee) { + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) { + const auto& vector = Vectors[lane]; + for (std::uint32_t instruction = 0; instruction < Instructions; ++instruction) { + const auto actual = Output[lane * Results + instruction]; + const auto expected = denormals == 1u ? vector.inputsKept[instruction] : vector.inputsFlushed[instruction]; + Require(actual == expected, std::string(Names[instruction]) + " f32 denormal mode " + std::to_string(denormals) + (ieee ? ", IEEE=1" : ", IEEE=0") + " of " + + Hex(vector.operands[0]) + ", " + Hex(vector.operands[1]) + ", " + Hex(vector.operands[2]) + " is " + Hex(actual) + + ", expected " + Hex(expected)); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + std::uint32_t checked = 0u; + for (const std::uint32_t denormals : {1u, 2u}) { + for (const bool ieee : {false, true}) { + if (!Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u | denormals << 4u, true, ieee, false})) continue; + ++checked; + Check(denormals, ieee); + } + } + std::printf("f32 min/max denormal mode 1 and 2 tests passed (%u of 4 modes run; mode 1 needs 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/F32SeparateRounding.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32SeparateRounding.cpp new file mode 100644 index 0000000000..3556b0538b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32SeparateRounding.cpp @@ -0,0 +1,177 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 8; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; +bool FusedExactArithmetic = false; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0x34060085u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x10140b04u, 0x06160d0au, 0x08180d0au, + 0x0a1a0d0au, 0x061c1506u, 0x081e1506u, 0x06200d04u, 0x10220b10u, 0xe0781000u, 0x80010a03u, 0xe0781010u, + 0x80010e03u, 0xbf810000u, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x3f800800u, 0x3f800c00u, 0xbf801401u, 0x00000000u}, + {0x3f801800u, 0x3f801400u, 0xbf802c04u, 0x00000000u}, + {0x3f802800u, 0x3f801c00u, 0xbf804409u, 0x00000000u}, + {0x3f803800u, 0x3f802400u, 0xbf805c10u, 0x00000000u}, + {0x3f804800u, 0x3f802c00u, 0xbf807419u, 0x00000000u}, + {0x3f805800u, 0x3f803400u, 0xbf808c24u, 0x00000000u}, + {0x3f806800u, 0x3f803c00u, 0xbf80a431u, 0x00000000u}, + {0x3f807800u, 0x3f804400u, 0xbf80bc40u, 0x00000000u}, + {0x3f808800u, 0x3f804c00u, 0xbf80d451u, 0x00000000u}, + {0x3f809800u, 0x3f805400u, 0xbf80ec64u, 0x00000000u}, + {0x3f80a800u, 0x3f805c00u, 0xbf810479u, 0x00000000u}, + {0x3f80b800u, 0x3f806400u, 0xbf811c90u, 0x00000000u}, + {0x3d801000u, 0xc1001800u, 0x3f002804u, 0x00000000u}, + {0x3e005000u, 0xc0803800u, 0x3f008822u, 0x00000000u}, + {0x3e809000u, 0xc0005800u, 0x3f00e864u, 0x00000000u}, + {0x3f00d000u, 0xbf807800u, 0x3f0148c2u, 0x00000000u}, + {0x3f811000u, 0xbf009800u, 0x3f01a944u, 0x00000000u}, + {0x40015000u, 0xbe80b800u, 0x3f0209e2u, 0x00000000u}, + {0x40819000u, 0xbe00d800u, 0x3f026aa4u, 0x00000000u}, + {0x4101d000u, 0xbd80f800u, 0x3f02cb82u, 0x00000000u}, + {0x40400000u, 0x3eaaaaabu, 0xbf800000u, 0x00000000u}, + {0x40540000u, 0x3eabaaabu, 0xc0000000u, 0x00000000u}, + {0x40680000u, 0x3eacaaabu, 0xc0400000u, 0x00000000u}, + {0x407c0000u, 0x3eadaaabu, 0xc0800000u, 0x00000000u}, + {0x40880000u, 0x3eaeaaabu, 0xc0a00000u, 0x00000000u}, + {0x40920000u, 0x3eafaaabu, 0xc0c00000u, 0x00000000u}, + {0x00000000u, 0xbf800000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x3f800000u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0xbf800000u, 0x00000000u}, + {0x4e800001u, 0x4e7ffffeu, 0xdd800000u, 0x00000000u}, + {0x3f800001u, 0x3f7fffffu, 0xbf800000u, 0x00000000u}, + {0xbfc00000u, 0x35800000u, 0x35000000u, 0x00000000u}, +}; +constexpr std::uint32_t Expected[32][8] = { + {0x3f801401u, 0x00000000u, 0x40001401u, 0xc0001401u, 0x00000000u, 0xc0001401u, 0xb9c01000u, 0xb9c02202u}, + {0x3f802c04u, 0x00000000u, 0x40002c04u, 0xc0002c04u, 0x00000000u, 0xc0002c04u, 0xba202000u, 0xba203905u}, + {0x3f804409u, 0x00000000u, 0x40004409u, 0xc0004409u, 0x00000000u, 0xc0004409u, 0xba604800u, 0xba607910u}, + {0x3f805c10u, 0x00000000u, 0x40005c10u, 0xc0005c10u, 0x00000000u, 0xc0005c10u, 0xba904000u, 0xba906892u}, + {0x3f807419u, 0x00000000u, 0x40007419u, 0xc0007419u, 0x00000000u, 0xc0007419u, 0xbab06400u, 0xbab0a0a2u}, + {0x3f808c24u, 0x00000000u, 0x40008c24u, 0xc0008c24u, 0x00000000u, 0xc0008c24u, 0xbad09000u, 0xbad0e4bau}, + {0x3f80a431u, 0x00000000u, 0x4000a431u, 0xc000a431u, 0x00000000u, 0xc000a431u, 0xbaf0c400u, 0xbaf134dcu}, + {0x3f80bc40u, 0x00000000u, 0x4000bc40u, 0xc000bc40u, 0x00000000u, 0xc000bc40u, 0xbb088000u, 0xbb08c884u}, + {0x3f80d451u, 0x00000000u, 0x4000d451u, 0xc000d451u, 0x00000000u, 0xc000d451u, 0xbb18a200u, 0xbb18fca0u}, + {0x3f80ec64u, 0x00000000u, 0x4000ec64u, 0xc000ec64u, 0x00000000u, 0xc000ec64u, 0xbb28c800u, 0xbb2936c3u}, + {0x3f810479u, 0x00000000u, 0x40010479u, 0xc0010479u, 0x00000000u, 0xc0010479u, 0xbb38f200u, 0xbb3976eeu}, + {0x3f811c90u, 0x00000000u, 0x40011c90u, 0xc0011c90u, 0x00000000u, 0xc0011c90u, 0xbb492000u, 0xbb49bd21u}, + {0xbf002803u, 0x33800000u, 0xbf802804u, 0x3f802804u, 0x33800000u, 0x3f802804u, 0x3f102a04u, 0xc090450cu}, + {0xbf008823u, 0xb3800000u, 0xbf808822u, 0x3f808822u, 0xb3800000u, 0x3f808822u, 0x3f209c22u, 0xc020e266u}, + {0xbf00e863u, 0x33800000u, 0xbf80e864u, 0x3f80e864u, 0x33800000u, 0x3f80e864u, 0x3f413064u, 0xbfc1b535u}, + {0xbf0148c3u, 0xb3800000u, 0xbf8148c2u, 0x3f8148c2u, 0xb3800000u, 0x3f8148c2u, 0x3f810c61u, 0xbf81855du}, + {0xbf01a943u, 0x33800000u, 0xbf81a944u, 0x3f81a944u, 0x33800000u, 0x3f81a944u, 0x3fc1e4a2u, 0xbf42cae2u}, + {0xbf0209e3u, 0xb3800000u, 0xbf8209e2u, 0x3f8209e2u, 0xb3800000u, 0x3f8209e2u, 0x4021d278u, 0xbf22bb17u}, + {0xbf026aa3u, 0x33800000u, 0xbf826aa4u, 0x3f826aa4u, 0x33800000u, 0x3f826aa4u, 0x4091dd54u, 0xbf12d379u}, + {0xbf02cb83u, 0xb3800000u, 0xbf82cb82u, 0x3f82cb82u, 0xb3800000u, 0x3f82cb82u, 0x4109fcb8u, 0xbf0b0812u}, + {0x3f800000u, 0x00000000u, 0x40000000u, 0xc0000000u, 0x00000000u, 0xc0000000u, 0x40000000u, 0x3f2aaaabu}, + {0x3f8e2956u, 0xbf63ad54u, 0x404714abu, 0xc04714abu, 0xbf63ad54u, 0xc04714abu, 0x3fa80000u, 0x3ee15000u}, + {0x3f9c7aabu, 0xbfe38555u, 0x40871eabu, 0xc0871eabu, 0xbfe38555u, 0xc0871eabu, 0x3f200000u, 0x3e57d556u}, + {0x3faaf400u, 0xc02a8600u, 0x40aabd00u, 0xc0aabd00u, 0xc02a8600u, 0xc0aabd00u, 0xbd800000u, 0xbcadaaabu}, + {0x3fb99556u, 0xc0633555u, 0x40ce6556u, 0xc0ce6556u, 0xc0633555u, 0xc0ce6556u, 0xbf400000u, 0xbe830000u}, + {0x3fc85eabu, 0xc08de855u, 0x40f217abu, 0xc0f217abu, 0xc08de855u, 0xc0f217abu, 0xbfb80000u, 0xbefc8556u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x00000000u, 0x40000000u, 0xc0000000u, 0x00000000u, 0xc0000000u, 0x00000000u, 0x00000000u}, + {0x5d800000u, 0x00000000u, 0x5e000000u, 0xde000000u, 0x00000000u, 0xde000000u, 0xdd800000u, 0xec7ffffeu}, + {0x3f800000u, 0x00000000u, 0x40000000u, 0xc0000000u, 0x00000000u, 0xc0000000u, 0x34000000u, 0x33ffffffu}, + {0xb5c00000u, 0xb5800000u, 0xb6000000u, 0x36000000u, 0xb5800000u, 0x36000000u, 0xbfbffffcu, 0xb5bffffcu}, +}; + +constexpr const char* Names[Results] = { + "v_mul_f32 v10, v4, v5", + "v_add_f32 v11, v10, v6", + "v_sub_f32 v12, v10, v6", + "v_subrev_f32 v13, v10, v6", + "v_add_f32 v14, v6, v10", + "v_sub_f32 v15, v6, v10", + "v_add_f32 v16, v4, v6", + "v_mul_f32 v17, v16, v5", +}; + +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, const std::optional& floatMode) { + 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}, 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; + request.context.floatMode = floatMode; + request.target.fusedExactArithmetic = FusedExactArithmetic; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(const char* mode) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t i = 0; i < Results; ++i) { + const auto actual = Output[tid * Results + i]; + Require(actual == Expected[tid][i], std::string("f32 separate rounding: ") + (FusedExactArithmetic ? "fused " : "") + "lane " + std::to_string(tid) + " " + mode + " " + Names[i] + " is " + Hex(actual) + ", expected " + Hex(Expected[tid][i])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (const bool fused : {false, true}) { + if (!fused && device->Target().fusedExactArithmetic) continue; + FusedExactArithmetic = fused; + Run(*device, std::nullopt); + Check("no float mode"); + Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}); + Check("IEEE=1"); + } + std::puts("f32 separate rounding 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/FinisherLabelOrder.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FinisherLabelOrder.cpp new file mode 100644 index 0000000000..80e9c72dba --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FinisherLabelOrder.cpp @@ -0,0 +1,153 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +alignas(256) constexpr std::array VertexCode{ + 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e060280u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u, +}; +alignas(256) constexpr std::array PixelCode{0xf4000100u, 0xfa000000u, 0xbf8cc07fu, 0x7e000204u, 0x06000002u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u}; +alignas(256) std::array PixelConstant{}; +alignas(256) std::array ColorMemory{}; +constexpr std::uint32_t Labels = 8; +alignas(256) std::array LabelMemory{}; + +struct Header { + Shader shader{}; + ShaderUserData userData{}; +}; + +alignas(8) Header VertexHeader{}; +alignas(8) Header PixelHeader{}; + +void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(bytes); + header.shader.code = code; + header.shader.type = type; + header.shader.user_data = &header.userData; + AgcDriverRegisterShader_nid_postfix(&header.shader); +} + +void Append(std::vector& commands, std::uint32_t opcode, std::initializer_list payload) { + commands.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + commands.insert(commands.end(), payload); +} + +std::vector Setup() { + const auto color = reinterpret_cast(ColorMemory.data()); + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto pixel = reinterpret_cast(PixelCode.data()); + 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, 3u << 1u}, + }; + std::vector commands; + Append(commands, 0x79, {0x242, 4}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value}); + const auto table = reinterpret_cast(PixelConstant.data()); + Append(commands, 0x76, {0x00c, static_cast(table)}); + Append(commands, 0x76, {0x00d, static_cast(table >> 32u)}); + Append(commands, 0x76, {0x00e, std::bit_cast(0.25f)}); + return commands; +} + +void WriteLabel(std::vector& commands, std::uint32_t index, std::uint32_t value) { + const auto address = reinterpret_cast(&LabelMemory[index]); + commands.insert(commands.end(), {0xc0033700u, 0x00100200u, static_cast(address), static_cast(address >> 32u), value}); +} + +void WaitLabel(std::vector& commands, std::uint32_t index, std::uint32_t value) { + const auto address = reinterpret_cast(&LabelMemory[index]); + commands.insert(commands.end(), {0xc0053c00u, 0x13u, static_cast(address), static_cast(address >> 32u), value, 0xffffffffu, 0x19u}); +} + +} + +int main() { + try { + bool libraries = false; + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + libraries = device->GraphicsPipelineLibraries(); + } + if (!libraries && std::getenv("APS5_NO_GPL") == nullptr) return VulkanTestSkipped; + using AgcDriver::DriverDetail::DrawFinisher; + const bool finisher = DrawFinisher::Enabled() && DrawFinisher::Overlap(); + Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + PixelConstant[0] = 0.25f; + static_cast(DrawFinisher::DrainText()); + auto commands = Setup(); + for (std::uint32_t index = 0; index < Labels; ++index) { + Append(commands, 0x2d, {3, 2}); + WriteLabel(commands, index, 0x100u + index); + WaitLabel(commands, index, 0x100u + index); + } + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); + const auto drains = DrawFinisher::DrainText(); + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(LabelMemory.data()), sizeof(LabelMemory)); + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(ColorMemory.data()), ColorMemory.size()); + for (std::uint32_t index = 0; index < Labels; ++index) { + Require(LabelMemory[index] == 0x100u + index, "label " + std::to_string(index) + " is " + std::to_string(LabelMemory[index]) + " after the draws before it, expected " + std::to_string(0x100u + index)); + } + unsigned brightest = 0; + for (const auto value : ColorMemory) brightest = std::max(brightest, static_cast(std::to_integer(value))); + Require(brightest + 2 >= 128 && brightest <= 130, "the draws before the labels wrote " + std::to_string(brightest) + ", expected 128"); + if (finisher) Require(drains.find(" 0x37 x") == std::string::npos, "a WRITE_DATA label after a draw handed to the finisher drained the finisher:" + drains); + if (finisher) Require(drains.find(" 0x3c x") == std::string::npos, "a WAIT_REG_MEM on a label the finisher holds drained the finisher:" + drains); + AgcDriverShutdown_nid_postfix(); + std::cout << "labels after finisher draws " << (finisher ? "queued through the finisher" : "written in order") << "\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + AgcDriverShutdown_nid_postfix(); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FixtureDeviceSelection.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FixtureDeviceSelection.cpp new file mode 100644 index 0000000000..5d3e6ebf24 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FixtureDeviceSelection.cpp @@ -0,0 +1,106 @@ +#include "FixtureDeviceSelection.hpp" +#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); +} + +void SetRequest(const char* value) { +#ifdef _WIN32 + if (_putenv_s("ANYPS5_GPU", value) != 0) throw std::runtime_error("cannot set ANYPS5_GPU"); +#else + if (setenv("ANYPS5_GPU", value, 1) != 0) throw std::runtime_error("cannot set ANYPS5_GPU"); +#endif +} + +VkPhysicalDeviceProperties Properties(const char* name, VkPhysicalDeviceType type) { + VkPhysicalDeviceProperties result{}; + result.apiVersion = VK_API_VERSION_1_1; + result.deviceType = type; + std::memcpy(result.deviceName, name, std::strlen(name) + 1u); + return result; +} + +std::array described{ + Properties("llvmpipe (LLVM 20.1.2, 256 bits)", VK_PHYSICAL_DEVICE_TYPE_CPU), + Properties("AMD Radeon(TM) Graphics", VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU), + Properties("NVIDIA GeForce RTX 5080", VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU), +}; + +VkPhysicalDevice Handle(std::size_t index) { + return reinterpret_cast(static_cast(index + 1u)); +} + +void VKAPI_CALL Describe(VkPhysicalDevice device, VkPhysicalDeviceProperties* properties) { + *properties = described[reinterpret_cast(device) - 1u]; +} + +std::string SelectionError(const char* request) { + SetRequest(request); + try { + static_cast(SelectFixturePhysicalDevice(std::array{Handle(0), Handle(1), Handle(2)}, Describe)); + } catch (const std::runtime_error& error) { + return error.what(); + } + throw std::runtime_error(std::string("ANYPS5_GPU=\"") + request + "\" selected a device"); +} + +void SelectionTests() { + std::array devices = described; + Require(!SelectFixtureDevice({}, {}).has_value(), "an empty device list selected a device"); + Require(SelectFixtureDevice(devices, {}) == 2u, "default selection did not prefer the discrete GPU"); + Require(SelectFixtureDevice(std::span(devices).first(2), {}) == 1u, "default selection did not prefer the integrated GPU to the CPU device"); + Require(SelectFixtureDevice(devices, "AMD") == 1u, "an AMD request lost to the discrete GPU"); + Require(SelectFixtureDevice(devices, "aMd rAdEoN") == 1u, "GPU name matching was not case-insensitive"); + Require(SelectFixtureDevice(devices, "RTX 5080") == 2u, "partial GPU name matching failed"); + Require(SelectFixtureDevice(devices, "llvmpipe") == 0u, "a requested CPU device was not selected"); + Require(!SelectFixtureDevice(devices, "missing device").has_value(), "an unmatched GPU request selected a device"); + Require(!SelectFixtureDevice(devices, "NVIDIA GeForce RTX 5080 with a longer name").has_value(), "a request longer than the device name matched"); + const std::array radeons{ + Properties("AMD Radeon(TM) Graphics", VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU), + Properties("AMD Radeon RX 7900 XTX", VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU), + }; + Require(SelectFixtureDevice(radeons, {}) == 1u, "default selection did not prefer the discrete Radeon"); + Require(SelectFixtureDevice(radeons, "radeon") == 0u, "a request matching an integrated and a later discrete GPU did not take the first match"); + devices[0] = devices[1]; + Require(SelectFixtureDevice(devices, "AMD") == 0u, "equally ranked matches did not keep enumeration order"); + devices[1].apiVersion = VK_API_VERSION_1_0; + devices[2].apiVersion = VK_API_VERSION_1_0; + Require(!SelectFixtureDevice(devices, "NVIDIA").has_value(), "a requested device without Vulkan 1.1 was selected"); +} + +void EnvironmentTests() { + const std::array handles{Handle(0), Handle(1), Handle(2)}; + SetRequest(""); + Require(SelectFixturePhysicalDevice(handles, Describe) == Handle(2), "an empty ANYPS5_GPU did not select the discrete GPU"); + SetRequest("radeon"); + Require(SelectFixturePhysicalDevice(handles, Describe) == Handle(1), "ANYPS5_GPU=radeon did not select the integrated GPU"); + SetRequest("LLVMPIPE"); + Require(SelectFixturePhysicalDevice(handles, Describe) == Handle(0), "ANYPS5_GPU=LLVMPIPE did not select the CPU device"); + const std::string error = SelectionError("no such device 1d3d154f"); + Require(error == "no Vulkan 1.1 device whose name contains ANYPS5_GPU=\"no such device 1d3d154f\"; devices: llvmpipe (LLVM 20.1.2, 256 bits), AMD Radeon(TM) Graphics, NVIDIA GeForce RTX 5080", "unexpected error: " + error); +} + +} + +int main() { + try { + SelectionTests(); + EnvironmentTests(); + std::puts("fixture device selection 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/FixtureDeviceSelection.hpp b/core/libs/prx/libSceAgcDriver/tests/execution/FixtureDeviceSelection.hpp new file mode 100644 index 0000000000..7bc494e0ec --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FixtureDeviceSelection.hpp @@ -0,0 +1,57 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FIXTUREDEVICESELECTION_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FIXTUREDEVICESELECTION_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +inline std::optional SelectFixtureDevice(std::span devices, std::string_view request) { + const auto rankDeviceType = [](VkPhysicalDeviceType type) { + switch (type) { + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return 3; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return 2; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return 1; + default: return 0; + } + }; + std::optional selected; + int selectedRank = -1; + for (std::size_t index = 0; index < devices.size(); ++index) { + const auto& candidate = devices[index]; + if (candidate.apiVersion < VK_API_VERSION_1_1) continue; + const std::string_view name(candidate.deviceName); + if (!request.empty() && std::search(name.begin(), name.end(), request.begin(), request.end(), [](char left, char right) { + return std::tolower(static_cast(left)) == std::tolower(static_cast(right)); + }) == name.end()) continue; + const int rank = request.empty() ? rankDeviceType(candidate.deviceType) : 4; + if (rank <= selectedRank) continue; + selected = index; + selectedRank = rank; + } + return selected; +} + +inline VkPhysicalDevice SelectFixturePhysicalDevice(std::span devices, PFN_vkGetPhysicalDeviceProperties physicalProperties) { + std::vector properties(devices.size()); + for (std::size_t index = 0; index < devices.size(); ++index) physicalProperties(devices[index], &properties[index]); + const char* requestedName = std::getenv("ANYPS5_GPU"); + const std::string_view request = requestedName != nullptr ? requestedName : ""; + if (const auto selected = SelectFixtureDevice(properties, request)) return devices[*selected]; + if (request.empty()) throw std::runtime_error("no Vulkan 1.1 device"); + std::string names; + for (const auto& candidate : properties) { + if (!names.empty()) names += ", "; + names += candidate.deviceName; + } + throw std::runtime_error("no Vulkan 1.1 device whose name contains ANYPS5_GPU=\"" + std::string(request) + "\"; devices: " + names); +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp index 961afeb7c9..0a96011da8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp @@ -31,6 +31,12 @@ alignas(256) constexpr std::array Code{ 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, }; +alignas(256) constexpr std::array AccumulatorCode{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xcc13600a, 0x1c1a0b04, 0xcc13000b, + 0x1c1a0b04, 0xcc13440c, 0x1c1a0b04, 0xcc13400d, 0x9c1a0b04, 0xcc13440e, 0x9c1a0b04, 0xe0781000, + 0x80010a03, 0xe0701010, 0x80010e03, 0xbf810000, +}; + constexpr std::uint32_t Rows[32][4] = { {0x0001bc00u, 0x81bfacbau, 0xc541aa2fu, 0x00000000u}, {0x01f61a79u, 0x3c000381u, 0xc1729fffu, 0x00000000u}, @@ -101,6 +107,43 @@ constexpr std::uint32_t Expected[32][10] = { {0xbaf43c38u, 0xbaf43c38u, 0x3af43c38u, 0x3af43c38u, 0x3abe63ecu, 0xbaf43c38u, 0xc021e000u, 0x3fb50000u, 0xba90cd60u, 0x3abe63ecu} }; +constexpr std::uint32_t AccumulatorExpected[32][5] = { + {0xc541a901u, 0xc541a901u, 0x4541ab5du, 0x4541ab5du, 0xc541a901u}, + {0xc1729fdfu, 0xc1729fdfu, 0x4172a01fu, 0x4172a01fu, 0xc1729fdfu}, + {0x4187e315u, 0x4187e315u, 0x4187e315u, 0xc1882737u, 0xc1882737u}, + {0x3f7fdb0fu, 0x3f7fdb0fu, 0x3f7fdb0fu, 0xbf801279u, 0xbf801279u}, + {0xbe511687u, 0xbe511687u, 0xbe511687u, 0xbe5166f4u, 0xbe5166f4u}, + {0xc1e56f13u, 0xc1e56f13u, 0x41e55ba5u, 0x41e55ba5u, 0xc1e56f13u}, + {0x439a1228u, 0x439a1228u, 0x439a1228u, 0xc3a5d2acu, 0xc3a5d2acu}, + {0x37f5fb14u, 0x37f5fb14u, 0x37f5fc0cu, 0x37f5fc0cu, 0x37f5fb14u}, + {0x7fd0a000u, 0x7fd0a000u, 0x7fd0a000u, 0x7fd0a000u, 0x7fd0a000u}, + {0xffc8ab86u, 0xffc8ab86u, 0x7fc8ab86u, 0x7fc8ab86u, 0xffc8ab86u}, + {0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u}, + {0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u}, + {0xb974e480u, 0xb974e480u, 0xb974e480u, 0xb974e480u, 0xb974e480u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u}, + {0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u}, + {0x7fc42000u, 0x7fc42000u, 0x7fc42000u, 0x7fc42000u, 0x7fc42000u}, + {0xbf052ae0u, 0xbf052ae0u, 0xbf052ae0u, 0xbf052ae0u, 0xbf052ae0u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5f834da7u, 0x5f834da7u, 0x5f834da7u, 0xdf834da7u, 0xdf834da7u}, + {0x7f7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0xff7fffffu}, + {0x42750eeeu, 0x42750eeeu, 0x42750eeeu, 0xc2750eeeu, 0xc2750eeeu}, + {0x43380748u, 0x43380748u, 0x43380748u, 0xc3380746u, 0xc3380746u}, + {0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u}, + {0xf540cddbu, 0xf540cddbu, 0x7540cddbu, 0x7540cddbu, 0xf540cddbu}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u}, + {0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0xffe00000u, 0xffe00000u}, + {0x3cb7c720u, 0x3cb7c720u, 0x3cb7c720u, 0x3cb7c720u, 0x3cb7c720u}, + {0xbf9bd158u, 0xbf9bd158u, 0xbf9bd158u, 0xbf9bd158u, 0xbf9bd158u}, + {0x565f49ebu, 0x565f49ebu, 0x565f49ebu, 0xd65f49ebu, 0xd65f49ebu}, + {0xbaf43c38u, 0xbaf43c38u, 0xbaf43c38u, 0xbaf43c38u, 0xbaf43c38u} +}; + +constexpr const char* AccumulatorNames[5] = {"v_dot2 op_sel src2", "v_dot2 op_sel_hi src2 cleared", "v_dot2 neg_hi src2", "v_dot2 neg_lo src2", "v_dot2 neg_lo and neg_hi src2"}; + constexpr const char* Names[10] = {"v_dot2c", "v_dot2", "v_dot2 neg_lo src0/src2 neg_hi src1", "v_dot2 neg_lo src1 neg_hi src0", "v_dot2 op_sel src0", "v_dot2 clamp", "v_dot2c inline 1.0", "v_dot2c literal", "v_dot2c dpp quad_perm", "v_dot2 op_sel src1"}; void Fill(std::uint32_t tid, std::uint32_t* words) { @@ -140,21 +183,9 @@ auto Compile(AgcDriver::VulkanDevice& device, std::span cod return ShaderRecompiler::Recompile(request); } -void CheckRefused(AgcDriver::VulkanDevice& device, std::uint32_t word0, const std::string& what) { - alignas(256) const std::array code{word0, 0x1c1a0b04u, 0xbf810000u}; - std::string refusal; - try { - static_cast(Compile(device, code)); - } catch (const std::exception& error) { - refusal = error.what(); - } - Require(refusal.find("v_dot2_f32_f16 accumulator op_sel, op_sel_hi and neg_hi are not implemented") != std::string::npos, what + " was not refused"); -} - -void Run(AgcDriver::VulkanDevice& device) { +void Run(AgcDriver::VulkanDevice& device, std::span code) { for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); Output.fill(0xdeadbeefu); - const std::span code(Code); const auto result = Compile(device, code); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); @@ -167,6 +198,13 @@ void Check() { } } +void CheckAccumulator() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 5; ++i) Expect(tid, out[i], AccumulatorExpected[tid][i], AccumulatorNames[i]); + } +} + } int main() { @@ -177,11 +215,10 @@ int main() { std::puts("skipped, v_dot2_f32_f16 is computed in f64 and the device has no shaderFloat64"); return VulkanTestSkipped; } - Run(*device); + Run(*device, Code); Check(); - CheckRefused(*device, 0xcc13600au, "op_sel on the accumulator"); - CheckRefused(*device, 0xcc13440au, "neg_hi on the accumulator"); - CheckRefused(*device, 0xcc13000au, "op_sel_hi cleared on the accumulator"); + Run(*device, AccumulatorCode); + CheckAccumulator(); std::puts("v_dot2_f32_f16 tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp index b76ffe2a6d..e4b54fc027 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp @@ -32,9 +32,9 @@ std::array Buffer(const void* data, std::uint32_t bytes) { return {static_cast(address), static_cast(address >> 32u) & 0xffffu, bytes, 0x31016facu}; } -std::array Image(bool layered, std::uint32_t pipeXor, std::uint32_t swizzle = 4u) { +std::array Image(bool layered, std::uint32_t pipeXor, std::uint32_t swizzle = 4u, std::uint32_t format = 166u) { const auto address = reinterpret_cast(Texels.data()) + pipeXor; - return {static_cast(address >> 8u), static_cast(address >> 40u) | (166u << 20u) | (3u << 30u), 31u | ((Height - 1u) << 14u), swizzle | (24u << 20u) | ((layered ? 13u : 9u) << 28u), layered ? 2u | (1u << 16u) : 0u, 0u, 0u, 0u}; + return {static_cast(address >> 8u), static_cast(address >> 40u) | (format << 20u) | (3u << 30u), 31u | ((Height - 1u) << 14u), swizzle | (24u << 20u) | ((layered ? 13u : 9u) << 28u), layered ? 2u | (1u << 16u) : 0u, 0u, 0u, 0u}; } std::uint32_t Mapping(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t round) { @@ -56,15 +56,16 @@ std::uint64_t EquationOffset(const TextureSwizzleEquation& equation, std::uint32 return offset; } -void Fill(bool layered, std::uint32_t pipeXor, std::uint32_t round) { +template +void FillWith(bool layered, std::uint32_t pipeXor, std::uint32_t surfaceFormat, std::uint32_t bytes, TValue valueAt) { auto words = Image(layered, pipeXor); - words[1] = (words[1] & ~(0x1ffu << 20u)) | (20u << 20u); + words[1] = (words[1] & ~(0x1ffu << 20u)) | (surfaceFormat << 20u); const auto resource = DecodeTextureResource(words); const auto geometry = DescribeSurface(resource); Require(geometry.guestBytes <= Texels.size(), "FMASK fixture exceeds its allocation"); const auto& mip = geometry.mips.at(0); - const auto block = ThinBlockLayout(resource.tileMode, 4u); - const auto* equation = FindTextureSwizzleEquation(24u, 4u); + const auto block = ThinBlockLayout(resource.tileMode, bytes); + const auto* equation = FindTextureSwizzleEquation(24u, bytes); Require(equation != nullptr, "FMASK fixture has no Z swizzle equation"); std::ranges::fill(Texels, std::byte{0}); for (std::uint32_t layer = 0; layer < geometry.layers; ++layer) { @@ -73,14 +74,18 @@ void Fill(bool layered, std::uint32_t pipeXor, std::uint32_t round) { auto offset = geometry.GuestLayerOffset(layer) + mip.tiledOffset; if (mip.tail) offset += EquationOffset(*equation, x + mip.tailX, y + mip.tailY, layer) ^ pipeXor; else offset += (static_cast(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + (EquationOffset(*equation, x, y, layer) ^ pipeXor); - const auto value = Mapping(x, y, layer, round); - Require(offset + sizeof(value) <= Texels.size(), "FMASK fixture addresses outside its allocation"); - std::memcpy(Texels.data() + offset, &value, sizeof(value)); + const std::uint32_t value = valueAt(x, y, layer); + Require(offset + bytes <= Texels.size(), "FMASK fixture addresses outside its allocation"); + std::memcpy(Texels.data() + offset, &value, bytes); } } } } +void Fill(bool layered, std::uint32_t pipeXor, std::uint32_t round) { + FillWith(layered, pipeXor, 20u, 4u, [round](std::uint32_t x, std::uint32_t y, std::uint32_t layer) { return Mapping(x, y, layer, round); }); +} + ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code, std::span image, bool registration) { std::vector userData(16u); const auto input = Buffer(Input.data(), sizeof(Input)); @@ -140,6 +145,68 @@ void Run(AgcDriver::VulkanDevice& device, bool layered, bool registration, std:: } } +struct NarrowFmask { + std::uint32_t format; + std::uint32_t surfaceFormat; + std::uint32_t bytes; + std::uint32_t samples; + std::uint32_t fragments; + std::uint32_t sampleBits; + std::uint32_t identity; + std::uint32_t expandedIdentity; +}; + +constexpr std::array NarrowFormats{{ + {156u, 5u, 1u, 2u, 1u, 1u, 0x02u, 0x80u}, + {157u, 5u, 1u, 4u, 1u, 1u, 0x0eu, 0x8880u}, + {158u, 5u, 1u, 8u, 1u, 1u, 0xfeu, 0x88888880u}, + {159u, 5u, 1u, 2u, 2u, 1u, 0x02u, 0x10u}, + {160u, 5u, 1u, 4u, 2u, 2u, 0xa4u, 0x8810u}, + {161u, 5u, 1u, 4u, 4u, 2u, 0xe4u, 0x3210u}, + {163u, 11u, 2u, 8u, 2u, 2u, 0xaaa4u, 0x88888810u}, + {165u, 20u, 4u, 8u, 4u, 4u, 0x44443210u, 0x88883210u}, +}}; + +std::uint32_t Stored(const NarrowFmask& fmask, std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t round) { + if (round == 0u) return fmask.identity; + const auto value = (x * 0x9e3779b1u) ^ (y * 0x85ebca77u) ^ (layer * 0xc2b2ae3du) ^ (fmask.format * 0x27d4eb2fu); + return fmask.bytes == 4u ? value : value & ((1u << (fmask.bytes * 8u)) - 1u); +} + +std::uint32_t Expanded(const NarrowFmask& fmask, std::uint32_t stored) { + std::uint32_t expanded = 0; + for (std::uint32_t sample = 0; sample < fmask.samples; ++sample) { + const auto fragment = (stored >> (sample * fmask.sampleBits)) & ((1u << fmask.sampleBits) - 1u); + expanded |= (fragment >= fmask.fragments ? 8u : fragment) << (sample * 4u); + } + return expanded; +} + +void RunNarrow(AgcDriver::VulkanDevice& device, const NarrowFmask& fmask, bool layered, bool registration) { + auto code = Code; + if (layered) code[4] = 0xf0001f28u; + Require(Expanded(fmask, fmask.identity) == fmask.expandedIdentity, "FMASK " + std::to_string(fmask.format) + " reference does not expand PAL's identity value"); + for (std::uint32_t round = 0; round < 2u; ++round) { + FillWith(layered, 0u, fmask.surfaceFormat, fmask.bytes, [&](std::uint32_t x, std::uint32_t y, std::uint32_t layer) { return Stored(fmask, x, y, layer, round); }); + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + Input[lane * 4u] = (lane * 37u + 11u) % Width; + Input[lane * 4u + 1u] = (lane * 23u + 5u) % Height; + Input[lane * 4u + 2u] = layered ? lane & 1u : 0u; + Input[lane * 4u + 3u] = 0u; + } + Output.fill(Sentinel); + const auto shader = Compile(device, code, Image(layered, 0u, 4u, fmask.format), registration); + device.Dispatch(shader, 1u, 1u, 1u); + device.WaitIdle(); + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + const auto stored = Stored(fmask, Input[lane * 4u], Input[lane * 4u + 1u], layered ? Input[lane * 4u + 2u] + 1u : 0u, round); + const std::array expected{Expanded(fmask, stored), 0u, 0u, 0u}; + for (std::uint32_t component = 0; component < 4u; ++component) Require(Output[lane * 4u + component] == expected[component], "FMASK " + std::to_string(fmask.format) + " load did not expand its mapping: round " + std::to_string(round) + ", lane " + std::to_string(lane) + ", component " + std::to_string(component) + ", read " + std::to_string(Output[lane * 4u + component]) + ", expected " + std::to_string(expected[component])); + } + Require(std::all_of(Output.begin() + Threads * 4u, Output.end(), [](auto value) { return value == Sentinel; }), "FMASK load wrote outside its output"); + } +} + } int main() { @@ -149,9 +216,10 @@ int main() { for (const bool registration : {false, true}) { for (const bool layered : {false, true}) { for (const auto pipeXor : {0u, 0xf00u}) Run(*device, layered, registration, pipeXor); + for (const auto& fmask : NarrowFormats) RunNarrow(*device, fmask, layered, registration); } } - std::cout << "FMASK image reads passed: 40 dispatches, 5120 component checks, 8 compression refusals\n"; + std::cout << "FMASK image reads passed: 104 dispatches, 13312 component checks, 8 compression refusals\n"; return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp index 503ccf662f..b4827a3b53 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp @@ -684,6 +684,7 @@ void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std {0, 0, 0, 128} }; request.useCache = false; + request.context.floatMode = ShaderRecompiler::ShaderFloatMode{0xf0u, true, false, false}; const auto result = ShaderRecompiler::Recompile(request); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GpuMapOnFiber.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GpuMapOnFiber.cpp index 5a4da94b89..6137424eba 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/GpuMapOnFiber.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GpuMapOnFiber.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFiber/include/Api.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -17,15 +18,6 @@ #include #include -using GuestFiberEntry = void (APS5_VABI*)(std::uint64_t, std::uint64_t); - -extern "C" { -std::int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject*, const char*, GuestFiberEntry, std::uint64_t, void*, std::uint64_t, const void*, std::uint32_t); -std::int32_t APS5_VABI sceFiberFinalize(FiberObject*); -std::int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject*, std::uint64_t, std::uint64_t*); -std::int32_t APS5_VABI sceFiberReturnToThread(std::uint64_t, std::uint64_t*); -} - namespace { using AgcDriver::DriverDetail::Driver; @@ -77,6 +69,7 @@ void MapForGpu() { void APS5_VABI MapOnFiber(std::uint64_t, std::uint64_t) { MapForGpu(); sceFiberReturnToThread(0, nullptr); + } void ReleaseMapping() { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GuardedNullPointer.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GuardedNullPointer.cpp index 172c6a26aa..d05f480585 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/GuardedNullPointer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GuardedNullPointer.cpp @@ -270,7 +270,7 @@ void RunTests(AgcDriver::VulkanDevice& device) { Require(!Faulted(someLanes), "guarded pointer: a mapped pointer under EXEC faulted:\n" + someLanes.log); RequireWords(someLanes, 5u, Payload, "guarded pointer: a mapped pointer read under EXEC"); const auto noLanes = Run(device, ExecCode, 0u, 0u); - Require(!Faulted(noLanes) && noLanes.poisoned == 1u, "guarded pointer: a null pointer read skipped by s_cbranch_execz faulted:\n" + noLanes.log); + Require(!Faulted(noLanes) && noLanes.poisoned == 0u && noLanes.cacheable, "guarded pointer: a null pointer read skipped by s_cbranch_execz under an EXEC decided empty at capture faulted, was poisoned or left the caches:\n" + noLanes.log); RequireWords(noLanes, 0u, 0u, "guarded pointer: a read skipped by s_cbranch_execz"); const auto oneLane = Run(device, ExecCode, 0u, 1u); Require(Faulted(oneLane) && oneLane.log.find(Fault(0u, 0x34u)) != std::string::npos, "guarded pointer: a null pointer read with one EXEC lane did not fault:\n" + oneLane.log); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GuestMemoryLifetime.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GuestMemoryLifetime.cpp index 6c92e2171d..7d6a257711 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/GuestMemoryLifetime.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GuestMemoryLifetime.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" @@ -17,19 +19,6 @@ #include #include -extern "C" { -void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; -int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; -int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int) noexcept; -void* APS5_VABI memalign_nid_postfix(std::size_t, std::size_t); -void* APS5_VABI realloc_nid_postfix(void*, std::size_t); -void APS5_VABI free_nid_postfix(void*); -int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); -} - namespace { constexpr std::size_t BlockBytes = 65536; @@ -63,16 +52,16 @@ void* Map(std::size_t bytes = BlockBytes) { return result; } -void CheckPattern(const void* pointer) { +void CheckPattern(const void* pointer, std::size_t bytes = FillBytes) { const auto* data = static_cast(pointer); - for (std::size_t offset = 0; offset < FillBytes; offset += sizeof(Pattern)) + for (std::size_t offset = 0; offset < bytes; offset += sizeof(Pattern)) Require(std::memcmp(data + offset, Pattern.data(), sizeof(Pattern)) == 0, "guest mutation lost the pending GPU fill"); } -void Fill(AgcDriver::VulkanDevice& device, void* pointer) { +void Fill(AgcDriver::VulkanDevice& device, void* pointer, std::size_t bytes = FillBytes) { std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); - Require(device.FillBuffer(Address(pointer), FillBytes, Pattern), "Vulkan device did not import the guest allocation"); - Require(Imported(device, pointer), "GPU fill did not create a host import"); + Require(device.FillBuffer(Address(pointer), bytes, Pattern), "Vulkan device did not import the guest allocation"); + Require(Imported(device, pointer, bytes), "GPU fill did not create a host import"); } void CheckMappings(AgcDriver::VulkanDevice& device) { @@ -105,14 +94,25 @@ void CheckMappings(AgcDriver::VulkanDevice& device) { Require(!Imported(device, mapping), "host import survived guest munmap"); mapping = Map(3 * BlockBytes); - Fill(device, mapping); + Fill(device, mapping, 3 * BlockBytes); auto* middle = static_cast(mapping) + BlockBytes; Require(mprotect_nid_postfix(middle, BlockBytes, 1) == 0, "guest partial protection failed"); - Require(!Imported(device, mapping), "whole host import survived partial protection change"); - CheckPattern(mapping); - Fill(device, mapping); + Require(!Imported(device, mapping, 3 * BlockBytes), "whole host import survived partial protection change"); + CheckPattern(mapping, 3 * BlockBytes); + Require(munmap_nid_postfix(middle, BlockBytes) == 0, "guest middle unmap after protection change failed"); + Require(!Imported(device, mapping, 3 * BlockBytes), "host import still covered the protected and unmapped middle range"); + Require(munmap_nid_postfix(mapping, BlockBytes) == 0, "guest prefix unmap after protection change failed"); + Require(munmap_nid_postfix(middle + BlockBytes, BlockBytes) == 0, "guest suffix unmap after protection change failed"); + + mapping = Map(3 * BlockBytes); + Fill(device, mapping, 3 * BlockBytes); + Require(Imported(device, mapping, 3 * BlockBytes), "whole host import did not cover the three-block mapping before middle unmap"); + middle = static_cast(mapping) + BlockBytes; Require(munmap_nid_postfix(middle, BlockBytes) == 0, "guest middle unmap failed"); - Require(Imported(device, mapping), "unrelated prefix import was retired by middle unmap"); + Require(!Imported(device, mapping, 3 * BlockBytes), "middle unmap left a host import spanning the unmapped range"); + Require(!Imported(device, mapping), "middle unmap retained the prefix of the retired host import"); + Fill(device, mapping); + Require(Imported(device, mapping), "the retained prefix was not imported again after middle unmap"); Require(munmap_nid_postfix(mapping, BlockBytes) == 0, "guest prefix unmap failed"); Require(!Imported(device, mapping), "prefix host import survived guest munmap"); Require(munmap_nid_postfix(middle + BlockBytes, BlockBytes) == 0, "guest suffix unmap failed"); @@ -130,12 +130,14 @@ void CheckHeap(AgcDriver::VulkanDevice& device) { Require(!Imported(device, allocation), "host import survived guest realloc"); CheckPattern(replacement); free_nid_postfix(replacement); + for (int iteration = 0; iteration < 3; ++iteration) { allocation = memalign_nid_postfix(BlockBytes, BlockBytes); Require(allocation != nullptr, "guest heap reuse allocation failed"); std::memset(allocation, 0, BlockBytes); Fill(device, allocation); free_nid_postfix(allocation); + Require(!Imported(device, allocation), "host import survived guest free or heap cache eviction"); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/HostImportAtMap.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/HostImportAtMap.cpp index 00a4f23f96..6bc76a11e1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/HostImportAtMap.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/HostImportAtMap.cpp @@ -147,7 +147,13 @@ int main() { remapped.Unregister(); remapped.Register(); Import(*device, remapped.Ranges()); - Require(Imported(handle, remapped.Address(), AllocationBytes), "host import at map: a range mapped again was not imported"); + Require(!Imported(handle, remapped.Address(), AllocationBytes), "host import at map: a range mapped again was imported at map instead of on first use"); + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + const std::array pattern{9u, 10u, 11u, 12u}; + Require(device->FillBuffer(remapped.Address(), BlockBytes, pattern), "host import at map: the first use of a range mapped again was not imported"); + device->WaitIdle(); + } GuestAllocation gone; const auto goneRanges = gone.Ranges(); gone.Unregister(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/HostImportWindows.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/HostImportWindows.cpp index e6d9bd6b30..35e4f777cd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/HostImportWindows.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/HostImportWindows.cpp @@ -21,6 +21,14 @@ #include #include +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + namespace { namespace Graphics = AgcDriver::Graphics; @@ -163,6 +171,41 @@ Outcome ResidentMappingsImportAfterCollection() { return Outcome::Passed; } +Outcome SmallMappingsImportOnFirstUse() { + GuestBlock small(GuestAlignment); + GuestBlock large(2 * GuestAlignment); + auto device = OpenVulkanTestDevice(); + if (!device) return Outcome::NoDevice; + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device); + if (context.hostImportAlignment == 0) return Outcome::NoImport; + for (auto* guest : {&small, &large}) { + Graphics::ClearHostImports(device->Device()); + GuestAllocations::Mapped ranges; + for (const auto& range : GuestAllocations::GuestAllocationsAcquire_nid_postfix()) { + if (range->address == guest->Address()) ranges.push_back(range); + } + Require(ranges.size() == 1, "mapped import test lost its registered range"); + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + Require(Graphics::ImportMappedRanges(context, ranges, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true), "mapped import test could not adopt the device"); + } + Require(Graphics::HostImportCovers(context, guest->Address(), ProbeBytes) == (guest == &large), "mapping did not defer exactly the small import"); + Require(Fill(*device, guest->Address()), "first GPU use could not import the mapped buffer"); + Require(Graphics::HostImportCovers(context, guest->Address(), ProbeBytes), "first GPU use left the mapped buffer unimported"); + constexpr std::array pattern{0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u}; + for (std::size_t offset = 0; offset < ProbeBytes; offset += sizeof(std::uint32_t)) { + std::uint32_t actual = 0; + std::memcpy(&actual, guest->Data() + offset, sizeof(actual)); + Require(actual == pattern[(offset / sizeof(actual)) % pattern.size()], "first GPU use changed the fill result"); + } + for (std::size_t offset = ProbeBytes; offset < GuestAlignment; ++offset) { + Require(guest->Data()[offset] == 0, "first GPU use overwrote bytes outside the fill"); + } + } + Graphics::ClearHostImports(device->Device()); + return Outcome::Passed; +} + } int main() { @@ -176,6 +219,7 @@ int main() { const auto windows = WindowsFollowTheirRange(); Require(windows == Outcome::Passed, "host import windows: the device stopped importing guest memory between two scenarios"); Require(ResidentMappingsImportAfterCollection() == Outcome::Passed, "resident import test failed"); + Require(SmallMappingsImportOnFirstUse() == Outcome::Passed, "mapped import test lost its Vulkan device or host import support"); std::puts("host import windows tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp index 0d29ae08e4..d6a5e73941 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp @@ -30,7 +30,10 @@ constexpr std::uint32_t ClampEdge = 2; alignas(256) std::array Input{}; alignas(256) std::array Output{}; -alignas(65536) std::array Texels{}; +constexpr std::size_t TexelBytes = 4u << 20u; +constexpr std::size_t TexelAlignment = 65536; +std::array TexelStorage{}; +const std::span Texels{reinterpret_cast((reinterpret_cast(TexelStorage.data()) + TexelAlignment - 1) & ~(TexelAlignment - 1)), TexelBytes}; alignas(256) constexpr std::array Code{ 0x34020084, 0xe0381000, 0x80000201, 0xbf8c3f70, 0xf09c0f20, 0x00820602, 0xf0001f20, 0x00020a04, @@ -62,7 +65,7 @@ std::array Texel(std::uint32_t x, std::uint32_t guestLayer) { } void FillTexels() { - Texels.fill(0); + std::ranges::fill(Texels, std::uint8_t{0}); const auto surface = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor())); const auto& mip = surface.mips.at(0); Require(surface.layers == LastArray + 1u && surface.guestBytes <= Texels.size(), "the test array does not fit its storage"); @@ -117,7 +120,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t baseArray = BaseArray) { device.WaitIdle(); for (std::uint32_t tid = 0; tid < Threads; ++tid) { const float u = std::bit_cast(Input[tid * 4u + 0u]); - const float layer = std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(Layers - 1u)); + const float layer = std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(LastArray - baseArray)); const auto x = static_cast(std::clamp(static_cast(std::floor(u * static_cast(Width))), 0, static_cast(Width) - 1)); Expect(tid, "image_sample_lz on a window of a 1D array past maxImageArrayLayers", &Output[tid * 8u], Texel(x, baseArray + static_cast(layer))); Expect(tid, "image_load on a window of a 1D array past maxImageArrayLayers", &Output[tid * 8u + 4u], Texel(Input[tid * 4u + 2u], baseArray + Input[tid * 4u + 3u])); @@ -128,6 +131,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t baseArray = BaseArray) { int main() { try { + Require(reinterpret_cast(Texels.data()) % TexelAlignment == 0, "the test array is not 64 KiB aligned"); const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; FillTexels(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp index a7b492f8e8..78b36356c7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp @@ -504,6 +504,7 @@ ShaderRecompiler::RecompileResult Compile(std::uint32_t waveSize, const ShaderRe {0, 0, 0, 128} }; request.useCache = false; + request.context.floatMode = ShaderRecompiler::ShaderFloatMode{0xf0u, true, false, false}; return ShaderRecompiler::Recompile(request); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageConvertedUnorm.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageConvertedUnorm.cpp index 94de1a8025..f840ade501 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageConvertedUnorm.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageConvertedUnorm.cpp @@ -442,14 +442,92 @@ void RequireRefused(AgcDriver::VulkanDevice& device, std::span widths = format == 133u ? std::array{5u, 6u, 5u, 0u} : format == 134u ? std::array{5u, 5u, 5u, 1u} : std::array{4u, 4u, 4u, 4u}; + const std::array offsets = format == 133u ? std::array{0u, 5u, 11u, 0u} : format == 134u ? std::array{0u, 5u, 10u, 15u} : std::array{0u, 4u, 8u, 12u}; + for (std::uint32_t index = 0; index < LoadWidth; ++index) { + const auto value = static_cast(index * 4051u + 0x39d7u); + std::memcpy(texels + index * 2u, &value, 2u); + } + Run(device, LoadXyzw, TextureDescriptor(texels, format, SwizzleXYZW, LoadWidth), {}, LoadGroups); + for (std::uint32_t index = 0; index < LoadWidth; ++index) { + const auto value = static_cast(index * 4051u + 0x39d7u); + for (std::uint32_t component = 0; component < 4u; ++component) { + const auto bits = widths[component] == 0u ? widths[0] : widths[component]; + const auto maximum = (1u << bits) - 1u; + const auto actual = Output[index * 4u + component]; + Require(std::isfinite(std::bit_cast(actual)) && StoreField(actual, maximum) == ((value >> offsets[component]) & maximum), "packed image_load has the wrong channel for format " + std::to_string(format)); + } + } + auto* target = texels + RoundTripOffset; + alignas(256) static constexpr std::array roundTrip{ + 0x7e080218u, 0x343c0885u, 0x4a3c3d00u, 0x7e3e0280u, 0xf0001f08u, 0x00010a1eu, 0xbf8c3f70u, 0xf0201f08u, + 0x00040a1eu, 0xbf810000u, + }; + Run(device, roundTrip, TextureDescriptor(texels, format, SwizzleXYZW, LoadWidth), {}, LoadGroups, TextureDescriptor(target, format, SwizzleXYZW, LoadWidth)); + AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast(target), LoadWidth * 2u, nullptr, "test"); + device.WaitIdle(); + Require(std::memcmp(texels, target, LoadWidth * 2u) == 0, "packed image load/store changed guest bytes for format " + std::to_string(format)); + alignas(256) static constexpr auto store = StoreCode(0xfu, 0u); + auto* written = texels + StoreOffset; + alignas(256) static constexpr auto storeHalf = StoreCode(0xfu, D16); + constexpr std::array halfValues{0x0000u, 0x3c00u, 0x3800u, 0x3400u, 0xbc00u, 0x4000u, 0x7c00u, 0x7e00u}; + constexpr std::array halfFloats{0.0f, 1.0f, 0.5f, 0.25f, -1.0f, 2.0f, INFINITY, NAN}; + for (std::uint32_t pass = 0; pass < 5u; ++pass) { + const auto swizzle = pass == 3u ? SwizzleZYX1 : SwizzleXYZW; + const auto dmask = pass == 4u ? 0x5u : 0xfu; + std::array expected{}; + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::uint32_t slot = 0; + for (std::uint32_t component = 0; component < 4u; ++component) { + if (((dmask >> component) & 1u) == 0u) continue; + auto value = StoreValues[(tid + component * 3u + pass * 4u) % StoreValues.size()]; + if (tid == 0u) value = std::bit_cast(pass == 0u ? std::array{1.0f, 0.5f, 0.25f, 1.0f}[component] : std::array{0.25f, 1.0f, 0.5f, 0.0f}[component]); + if (pass == 2u) { + const auto index = (tid + component * 3u) % halfValues.size(); + value = std::bit_cast(halfFloats[index]); + Input[tid * 4u + slot / 2u] |= halfValues[index] << ((slot % 2u) * 16u); + } else { + Input[tid * 4u + slot] = value; + } + ++slot; + const auto selected = (swizzle >> (component * 3u)) & 7u; + if (selected >= 4u && widths[selected - 4u] != 0u) expected[tid] |= static_cast(StoreField(value, (1u << widths[selected - 4u]) - 1u) << offsets[selected - 4u]); + } + } + auto* reference = texels + FloatOffset; + std::memcpy(reference, expected.data(), Threads * 2u); + Run(device, SampleLz, TextureDescriptor(reference, format, SwizzleXYZW, Threads), PointSampler, 1); + std::array sampled{}; + std::copy_n(Output.begin(), 4u, sampled.begin()); + const auto descriptor = TextureDescriptor(written, format, SwizzleXYZW, Threads); + Run(device, pass == 2u ? storeHalf : pass == 4u ? StoreXz : store, TextureDescriptor(written, format, swizzle, Threads), BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)), 1); + Run(device, SampleLz, descriptor, PointSampler, 1); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t component = 0; component < 4u; ++component) Require(Output[tid * 4u + component] == sampled[component], "sample after packed store differs for format " + std::to_string(format)); + } + AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast(written), Threads * 2u, nullptr, "test"); + device.WaitIdle(); + Require(std::memcmp(written, expected.data(), Threads * 2u) == 0, "packed image_store write-back differs for format " + std::to_string(format)); + } } -int main() { +} + +int main(int argc, char** argv) { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; GuestBlock block; auto* texels = block.Data(); + if (argc == 3 && std::string(argv[1]) == "--packed16") { + const auto format = static_cast(std::stoul(argv[2])); + Require(format == 133u || format == 134u || format == 136u, "unsupported packed storage test format"); + CheckPacked16(*device, texels, format); + std::cout << "Packed 16-bit storage image tests passed\n"; + return 0; + } for (std::uint32_t index = 0; index < LoadWidth; ++index) { const auto texel = LoadTexel(index); std::memcpy(texels + index * 4u, &texel, 4u); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageResinfoDimension.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageResinfoDimension.cpp new file mode 100644 index 0000000000..01fd310101 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageResinfoDimension.cpp @@ -0,0 +1,108 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Size = 8; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Type3D = 10; +constexpr std::uint32_t TypeCube = 11; +constexpr std::uint32_t Type2DArray = 13; +alignas(256) std::array Output{}; +alignas(256) std::array Texels{}; + +constexpr std::array Code(std::uint32_t dim) { + return {0x34060084u, 0x7e020280u, 0xf0380f00u | (dim << 3u), 0x00010a01u, 0xbf8c3f70u, 0xe0781000u, 0x80000a03u, 0xbf810000u}; +} + +alignas(256) constexpr auto Query1D = Code(0u); +alignas(256) constexpr auto Query3D = Code(2u); +alignas(256) constexpr auto QueryCube = Code(3u); +alignas(256) constexpr auto Query2DArray = Code(5u); + +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 Descriptor(std::uint32_t type, 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 | (type << 28u), + depth - 1u, + 0u, + 0u, + 0u, + }; +} + +std::array Query(AgcDriver::VulkanDevice& device, std::span code, const std::array& texture) { + Output.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + 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(); + for (std::uint32_t tid = 1; tid < Threads; ++tid) { + Require(std::equal(Output.begin(), Output.begin() + 4, Output.begin() + tid * 4u), "image_get_resinfo: lanes disagree"); + } + return {Output[0], Output[1], Output[2], Output[3]}; +} + +std::string Text(const std::array& value) { + return std::to_string(value[0]) + "x" + std::to_string(value[1]) + "x" + std::to_string(value[2]) + " levels " + std::to_string(value[3]); +} + +using Result = std::array; + +void Check(AgcDriver::VulkanDevice& device, std::span native, const std::array& texture, const char* what, const Result& own, const Result& oneDimensional) { + const auto nativeResult = Query(device, native, texture); + Require(nativeResult == own, std::string("image_get_resinfo: the ") + what + " query returned " + Text(nativeResult) + ", RDNA 2 returns " + Text(own)); + const auto lineResult = Query(device, Query1D, texture); + Require(lineResult == oneDimensional, std::string("image_get_resinfo: DIM 1D on a ") + what + " T# returned " + Text(lineResult) + ", RDNA 2 returns " + Text(oneDimensional)); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Check(*device, QueryCube, Descriptor(TypeCube, 6u), "cube", {Size, Size, 6u, 1u}, {Size, Size, 0u, 1u}); + Check(*device, Query3D, Descriptor(Type3D, 4u), "3D", {Size, Size, 4u, 1u}, {Size, Size, 4u, 1u}); + Check(*device, Query2DArray, Descriptor(Type2DArray, 3u), "2D array", {Size, Size, 3u, 1u}, {Size, Size, 0u, 1u}); + std::puts("image resinfo dimension 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/ImageSampleForceDegamma.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleForceDegamma.cpp index 331882d14a..0178717e94 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleForceDegamma.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleForceDegamma.cpp @@ -27,6 +27,7 @@ constexpr std::uint32_t Format8888Srgb = 130; constexpr std::uint32_t Format32Float = 22; constexpr std::uint32_t Format8888SNorm = 57; constexpr std::uint32_t FormatBc4UNorm = 175; +constexpr std::uint32_t Format32UInt = 20; constexpr std::uint32_t Type2D = 9; constexpr std::uint32_t FilterPoint = 0; constexpr std::uint32_t FilterBilinear = 1; @@ -177,7 +178,11 @@ int main() { RequireSame(Sample(*device, Format8888UNorm, FilterBilinear, ForceDegamma), forcedBilinear, "8_8_8_8_UNORM, bilinear", "through FORCE_DEGAMMA", "as 8_8_8_8_SRGB"); RequireSame(Sample(*device, Format32Float, FilterPoint, ForceDegamma), Sample(*device, Format32Float, FilterPoint, 0u), "32_FLOAT, point"); RequireSame(Sample(*device, FormatBc4UNorm, FilterPoint, ForceDegamma), Sample(*device, FormatBc4UNorm, FilterPoint, 0u), "BC4_UNORM, point"); - Reject(*device, Format8888SNorm, "guest format 57, which is not sRGB and has no sRGB view, is sampled through a sampler that forces sRGB decoding"); + FillInput(true); + RequireSame(Sample(*device, Format8888SNorm, FilterPoint, ForceDegamma), forcedPoint, "8_8_8_8_SNORM, point", "through FORCE_DEGAMMA", "as 8_8_8_8_SRGB"); + FillInput(false); + RequireSame(Sample(*device, Format8888SNorm, FilterBilinear, ForceDegamma), forcedBilinear, "8_8_8_8_SNORM, bilinear", "through FORCE_DEGAMMA", "as 8_8_8_8_SRGB"); + Reject(*device, Format32UInt, "guest format 20, which is not sRGB and has no sRGB view, is sampled through a sampler that forces sRGB decoding"); std::puts("image sample force degamma tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageStoreLineThroughPlaneArray.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageStoreLineThroughPlaneArray.cpp new file mode 100644 index 0000000000..26d3e4d1ff --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageStoreLineThroughPlaneArray.cpp @@ -0,0 +1,185 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Width = 32; +constexpr std::uint32_t Type1D = 8; +constexpr std::uint32_t IdentitySwizzle = 0xfacu; +constexpr std::size_t TexelBytes = 4096; +constexpr std::size_t BlockBytes = 65536; + +alignas(256) std::array Input{}; + +alignas(256) constexpr std::array StoreCode{ + 0x34020084, 0xe0381000, 0x80001401, 0xbf8c3f70, 0x7e3c0300, 0x7e3e0281, 0x7e400280, 0xf0201f28, 0x0001141e, + 0x7e3e0280, 0x7e4002c1, 0xf0201528, 0x0001141e, 0x7e400281, 0xf0201528, 0x0001141e, 0xbf810000, +}; + +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, "image store line through plane array: 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; +}; + +struct Format { + const char* name; + std::uint32_t format; + std::uint32_t components; +}; + +constexpr std::array Formats{{ + {"32_SINT", 21u, 1u}, + {"32_32_32_32_SINT", 76u, 4u}, +}}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::uint32_t Value(std::uint32_t tid, std::uint32_t index) { + return (tid * 4u + index + 1u) * 0x9e3779b1u; +} + +std::uint64_t TexelOffset(const Format& format, std::uint32_t x) { + const auto mip = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, format.format, Width, 1u, 1u).at(0); + return mip.tiledOffset + static_cast(x) * format.components * 4u; +} + +std::vector Initial() { + std::vector bytes(TexelBytes); + for (std::size_t index = 0; index < bytes.size(); ++index) bytes[index] = static_cast(0x80u + 37u * index + 11u * (index >> 4u)); + return bytes; +} + +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 TextureDescriptor(const void* data, std::uint32_t format) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((Width - 1u) & 3u) << 30u), + (Width - 1u) >> 2u, + IdentitySwizzle | (Type1D << 28u), + 0u, + 0u, + 0u, + 0u, + }; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::span code, std::uint32_t format) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t index = 0; index < 4u; ++index) Input[tid * 4u + index] = Value(tid, index); + } + const auto initial = Initial(); + std::copy(initial.begin(), initial.end(), texels); + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto texture = TextureDescriptor(texels, format); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + 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(); + AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast(texels), TexelBytes, nullptr, "test"); + device.WaitIdle(); +} + +void CheckTexels(const std::uint8_t* texels, const Format& format) { + const auto initial = Initial(); + for (std::uint32_t x = 0; x < Width; ++x) { + const auto offset = TexelOffset(format, x); + for (std::uint32_t component = 0; component < format.components; ++component) { + std::uint32_t actual = 0; + std::memcpy(&actual, texels + offset + component * 4u, 4u); + const auto wanted = Value(x, component); + Require(actual == wanted, std::string("image_store 2d_array through a 1D ") + format.name + " T#: texel " + std::to_string(x) + " component " + std::to_string(component) + " is " + Hex(actual) + ", expected the y = 1, slice = 0 store " + Hex(wanted) + " and the stores to slices 0xffffffff and 1 dropped"); + } + } + const auto end = TexelOffset(format, Width); + Require(std::equal(texels + end, texels + TexelBytes, initial.begin() + static_cast(end)), std::string("image_store 2d_array through a 1D ") + format.name + " T#: bytes past the texture changed"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock block; + auto* texels = block.Data(); + for (const auto& format : Formats) { + Dispatch(*device, texels, StoreCode, format.format); + CheckTexels(texels, format); + } + std::puts("image store line through plane array 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/ImageStoreVolumeThroughArray.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageStoreVolumeThroughArray.cpp new file mode 100644 index 0000000000..b82fa4ba07 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageStoreVolumeThroughArray.cpp @@ -0,0 +1,173 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Size = 4; +constexpr std::uint32_t Depth = 2; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Type2DArray = 13; +constexpr std::uint32_t IdentitySwizzle = 0xfacu; +constexpr std::size_t TexelBytes = 16384; +constexpr std::size_t BlockBytes = 65536; + +alignas(256) std::array Input{}; + +alignas(256) constexpr std::array StoreCode{ + 0x34020084, 0xe0381000, 0x80001401, 0xbf8c3f70, 0xf0201110, 0x00011714, 0x7e2c0282, 0xf0201110, + 0x00011714, 0xbf810000, +}; + +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, "image store volume through array: 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; +}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::uint32_t Value(std::uint32_t tid) { + return (tid + 1u) * 0x9e3779b1u; +} + +std::vector Initial() { + std::vector bytes(TexelBytes); + for (std::size_t index = 0; index < bytes.size(); ++index) bytes[index] = static_cast(0x80u + 37u * index + 11u * (index >> 4u)); + return bytes; +} + +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 ArrayDescriptor(const void* data) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + IdentitySwizzle | (Type2DArray << 28u), + Depth - 1u, + 0u, + 0u, + 0u, + }; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::size_t bytes) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::array words{tid % Size, (tid / Size) % Size, tid / (Size * Size), Value(tid)}; + std::copy(words.begin(), words.end(), Input.begin() + tid * 4u); + } + const auto initial = Initial(); + std::copy(initial.begin(), initial.end(), texels); + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto texture = ArrayDescriptor(texels); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + const std::span code(StoreCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 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(); + AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast(texels), bytes, nullptr, "test"); + device.WaitIdle(); +} + +void Check(const std::uint8_t* texels) { + const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(ArrayDescriptor(texels))); + const auto& mip = geometry.mips.at(0); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto x = tid % Size; + const auto y = (tid / Size) % Size; + const auto z = tid / (Size * Size); + std::uint32_t actual = 0; + std::memcpy(&actual, texels + geometry.GuestLayerOffset(z) + mip.tiledOffset + static_cast(y) * mip.pitchBytes + x * 4u, 4u); + Require(actual == Value(tid), "image_store 3d through a 2D-array T#: texel (" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ") is " + Hex(actual) + ", expected " + Hex(Value(tid))); + } + const auto initial = Initial(); + Require(std::equal(texels + geometry.guestBytes, texels + TexelBytes, initial.begin() + static_cast(geometry.guestBytes)), "image_store 3d through a 2D-array T#: a slice past the array changed bytes after it"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock block; + auto* texels = block.Data(); + const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(ArrayDescriptor(texels))); + Require(geometry.guestBytes <= TexelBytes, "image store volume through array: the array does not fit the texel storage"); + Dispatch(*device, texels, geometry.guestBytes); + Check(texels); + std::puts("image store volume through array 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 d353e82b93..5ee8abca21 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTable.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTable.cpp @@ -47,6 +47,7 @@ constexpr std::uint32_t MaxGroups = 8; constexpr std::uint32_t Format32UInt = 20; constexpr std::uint32_t Format32SInt = 21; constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Format32_32UInt = 62; constexpr std::uint32_t Format11_11_10UInt = 34; constexpr std::uint32_t Format8_8Srgb = 129; constexpr std::uint32_t Type2D = 9; @@ -56,6 +57,7 @@ constexpr std::uint32_t DepthSwizzleMode = 0x18; alignas(256) std::array FloatTexels{}; alignas(256) std::array OtherFloatTexels{}; alignas(256) std::array UintTexels{}; +alignas(256) std::array Uint64Texels{}; alignas(256) std::array SintTexels{}; alignas(4096) std::array DepthStorage{}; alignas(256) std::array Table{}; @@ -203,6 +205,28 @@ alignas(256) constexpr std::array StoreLaneTableCode{ 0xf42c0802u, 0x24000000u, 0x360200bfu, 0x7e040280u, 0x7e080301u, 0xf0208f08u, 0x00090401u, 0xbf810000u, }; +alignas(256) constexpr std::array AtomicAddLaneTableCode{ + 0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0x8f108202u, 0xf4200444u, 0x20000000u, 0x9312b011u, + 0xf42c0802u, 0x24000000u, 0x360200bfu, 0x7e040280u, 0x7e080301u, 0xf0448108u, 0x00090401u, 0xbf810000u, +}; + +alignas(256) constexpr std::array AtomicAdd64LaneTableCode{ + 0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0x8f108202u, 0xf4200444u, 0x20000000u, 0x9312b011u, + 0xf42c0802u, 0x24000000u, 0x360200bfu, 0x7e040280u, 0x7e080301u, 0x7e0a02ffu, 0x00000000u, 0xf0448308u, + 0x00090401u, 0xbf810000u, +}; + +alignas(256) constexpr std::array AtomicSwapLaneTableCode{ + 0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0x8f108202u, 0xf4200444u, 0x20000000u, 0x9312b011u, + 0xf42c0802u, 0x24000000u, 0x360200bfu, 0x7e040280u, 0x7e080301u, 0xf03c8108u, 0x00090401u, 0xbf810000u, +}; + +alignas(256) constexpr std::array AtomicCmpSwapLaneTableCode{ + 0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0x8f108202u, 0xf4200444u, 0x20000000u, 0x9312b011u, + 0xf42c0802u, 0x24000000u, 0x360200bfu, 0x7e040280u, 0x7e080301u, 0x7e0a0304u, 0x3a080881u, 0xf0408308u, + 0x00090401u, 0xbf810000u, +}; + alignas(256) constexpr std::array StoreTableCutCode{ 0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0x8f108202u, 0xf4200444u, 0x20000000u, 0x9312b011u, 0xf42c0802u, 0x24000000u, 0x360200bfu, 0x7e040280u, 0x7da400ffu, 0x00001000u, 0xf0208f08u, 0x00090401u, @@ -1117,6 +1141,67 @@ void RunStoreTests(AgcDriver::VulkanDevice& device) { Require(untouched(), "image table store: a store through a null entry reached an image:\n" + null.log); } +void RunAtomicTests(AgcDriver::VulkanDevice& device) { + struct Registration { + void* data; + Registration(void* data, std::size_t bytes) : data(data) { GuestAllocations::Mutation().Add(data, bytes, true, true, true); } + ~Registration() { GuestAllocations::Mutation().Remove(data); } + } registeredUint(UintTexels.data(), sizeof(UintTexels)), registeredFloat(FloatTexels.data(), sizeof(FloatTexels)), registeredSint(SintTexels.data(), sizeof(SintTexels)); + SetClassRecords(); + for (std::uint32_t x = 0; x < Width; ++x) UintTexels[x] = 0x5000u + x; + for (std::uint32_t x = 0; x < Width; ++x) FloatTexels[x] = 1.0f; + Keys = {}; + Keys[0] = 1; + const auto added = Run(device, AtomicAddLaneTableCode, 1, Lanes, 64); + AgcDriver::Graphics::StorageTexture::FlushAllPending("image table atomic test"); + Require(!Faulted(added), "image table atomic: an add through a table faulted:\n" + added.log); + Require(added.imageTableCount != 0u, "image table atomic: the add bound no storage table elements"); + for (std::uint32_t x = 0; x < Width; ++x) Require(UintTexels[x] == 0x5000u + x + x, "image table atomic: texel " + std::to_string(x) + " of the selected image is " + Hex(UintTexels[x]) + ", expected " + Hex(0x5000u + x + x)); + const auto swapped = Run(device, AtomicSwapLaneTableCode, 1, Lanes, 64); + AgcDriver::Graphics::StorageTexture::FlushAllPending("image table atomic test"); + Require(!Faulted(swapped), "image table atomic: an exchange through a table faulted:\n" + swapped.log); + for (std::uint32_t x = 0; x < Width; ++x) Require(UintTexels[x] == x, "image table atomic: texel " + std::to_string(x) + " of the selected image is " + Hex(UintTexels[x]) + ", expected " + Hex(x)); + const auto compared = Run(device, AtomicCmpSwapLaneTableCode, 1, Lanes, 64); + AgcDriver::Graphics::StorageTexture::FlushAllPending("image table atomic test"); + Require(!Faulted(compared), "image table atomic: a compare exchange through a table faulted:\n" + compared.log); + for (std::uint32_t x = 0; x < Width; ++x) Require(UintTexels[x] == (x ^ 1u), "image table atomic: texel " + std::to_string(x) + " of the selected image is " + Hex(UintTexels[x]) + ", expected " + Hex(x ^ 1u)); + Keys[0] = 8; + const auto addedFloat = Run(device, AtomicAddLaneTableCode, 1, Lanes, 64); + AgcDriver::Graphics::StorageTexture::FlushAllPending("image table atomic test"); + Require(!Faulted(addedFloat), "image table atomic: an add on a float record through a table faulted:\n" + addedFloat.log); + for (std::uint32_t x = 0; x < Width; ++x) Require(std::bit_cast(FloatTexels[x]) == 0x3f800000u + x, "image table atomic: float texel " + std::to_string(x) + " is " + Hex(std::bit_cast(FloatTexels[x])) + ", expected " + Hex(0x3f800000u + x)); + Keys[0] = 6; + const auto null = Run(device, AtomicAddLaneTableCode, 1, Lanes, 64); + AgcDriver::Graphics::StorageTexture::FlushAllPending("image table atomic test"); + for (std::uint32_t x = 0; x < Width; ++x) Require(UintTexels[x] == (x ^ 1u) && std::bit_cast(FloatTexels[x]) == 0x3f800000u + x, "image table atomic: an add through a null entry reached an image:\n" + null.log); +} + +void RunAtomic64Tests(AgcDriver::VulkanDevice& device) { + if (!TargetHasCapability(device.Target(), spv::CapabilityInt64ImageEXT)) { + std::puts("skipped the 64-bit table atomic rows, the device has no shaderImageInt64Atomics"); + return; + } + struct Registration { + void* data; + Registration(void* data, std::size_t bytes) : data(data) { GuestAllocations::Mutation().Add(data, bytes, true, true, true); } + ~Registration() { GuestAllocations::Mutation().Remove(data); } + } registered(Uint64Texels.data(), sizeof(Uint64Texels)); + SetClassRecords(); + for (std::uint32_t x = 0; x < Width; ++x) Uint64Texels[x] = 0x5000000000000000ull + x; + SetRecord(9, PointClamp, Image(Uint64Texels.data(), Format32_32UInt)); + Keys = {}; + Keys[0] = 9; + const auto added = Run(device, AtomicAdd64LaneTableCode, 1, Lanes, 64); + AgcDriver::Graphics::StorageTexture::FlushAllPending("image table atomic64 test"); + Require(!Faulted(added), "image table atomic64: an add through a table faulted:\n" + added.log); + Require(added.imageTableCount != 0u, "image table atomic64: the add bound no storage table elements"); + for (std::uint32_t x = 0; x < Width; ++x) Require(Uint64Texels[x] == 0x5000000000000000ull + x + x, "image table atomic64: texel " + std::to_string(x) + " is wrong"); + Keys[0] = 6; + const auto null = Run(device, AtomicAdd64LaneTableCode, 1, Lanes, 64); + AgcDriver::Graphics::StorageTexture::FlushAllPending("image table atomic64 test"); + for (std::uint32_t x = 0; x < Width; ++x) Require(Uint64Texels[x] == 0x5000000000000000ull + x + x, "image table atomic64: an add through a null entry reached an image:\n" + null.log); +} + void RunSelectedStoreTests(AgcDriver::VulkanDevice& device) { alignas(256) static std::array storeTexels{}; struct Registration { @@ -1509,6 +1594,8 @@ int main() { RunSelectedRecordTests(*device); RunModeTests(*device); RunStoreTests(*device); + RunAtomicTests(*device); + RunAtomic64Tests(*device); RunSelectedStoreTests(*device); RunPointerTests(*device); RunPointerRecordTests(*device); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/IndexedCopy.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/IndexedCopy.cpp new file mode 100644 index 0000000000..43966bcf5b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/IndexedCopy.cpp @@ -0,0 +1,187 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using AgcDriver::DriverDetail::IndexedCopyRange; +using AgcDriver::DriverDetail::MatchesIndexedCopyKernel; + +alignas(256) constexpr std::array Code{0xbfa00001u, 0xd7460000u, 0x04010c0cu, 0xf4201a84u, 0xfa000000u, 0xbf8cc07fu, 0x7da8006au, 0xbf88000au, 0xf4241a84u, 0xfa000004u, 0xbf8cc07fu, 0x4a08006bu, 0x4a00006au, 0xe0382000u, 0x80000000u, 0xbf8c3f70u, 0xe0782000u, 0x80010004u, 0xbf810000u}; +constexpr std::uint32_t Untouched = 0xcafebabeu; +constexpr std::uint32_t Format = 0x00005204u; +constexpr std::uint32_t RecordFormat = 0x0004dfacu; +constexpr std::uint32_t LargeGroups = 16385; +constexpr std::size_t Words = (std::size_t{LargeGroups} * 64u + 64u) * 4u; +alignas(256) std::array Source{}; +alignas(256) std::array Destination{}; +alignas(256) std::array Record{}; + +std::vector UserData(const std::uint32_t* source, std::uint32_t sourceRecords, const std::uint32_t* destination, std::uint32_t destinationRecords, const std::uint32_t* record = Record.data(), std::uint32_t sourceStride = 16, std::uint32_t destinationStride = 16, std::uint32_t recordStride = 16) { + const auto from = reinterpret_cast(source); + const auto to = reinterpret_cast(destination); + const auto at = reinterpret_cast(record); + return {static_cast(from), static_cast((from >> 32u) & 0xffffu) | (sourceStride << 16u), sourceRecords, Format, + static_cast(to), static_cast((to >> 32u) & 0xffffu) | (destinationStride << 16u), destinationRecords, Format, + static_cast(at), static_cast((at >> 32u) & 0xffffu) | (recordStride << 16u), 1u, RecordFormat}; +} + +void MatchTests() { + const ShaderRecompiler::ShaderComputeStageInfo compute{{64, 1, 1}, 0, {true, false, false}, false, 1}; + const auto data = UserData(Source.data(), 512, Destination.data(), 512); + Require(MatchesIndexedCopyKernel(Code, data, compute), "the indexed copy kernel was not recognized"); + for (std::size_t word = 0; word < Code.size(); ++word) { + auto altered = Code; + altered[word] ^= 1u; + Require(!MatchesIndexedCopyKernel(altered, data, compute), "a different shader matched the indexed copy kernel (word " + std::to_string(word) + ")"); + } + Require(!MatchesIndexedCopyKernel(std::span(Code).first(18), data, compute), "a truncated shader matched"); + Require(!MatchesIndexedCopyKernel(Code, UserData(Source.data(), 512, Destination.data(), 512, Record.data(), 4), compute), "a source stride other than 16 matched"); + Require(!MatchesIndexedCopyKernel(Code, UserData(Source.data(), 512, Destination.data(), 512, Record.data(), 16, 4), compute), "a destination stride other than 16 matched"); + Require(!MatchesIndexedCopyKernel(Code, UserData(Source.data(), 512, Destination.data(), 512, Record.data(), 16, 16, 4), compute), "a record stride other than 16 matched"); + auto swizzled = data; + swizzled[5] |= 0x80000000u; + Require(!MatchesIndexedCopyKernel(Code, swizzled, compute), "a swizzled destination matched"); + auto formats = data; + formats[7] ^= 0x1000u; + Require(!MatchesIndexedCopyKernel(Code, formats, compute), "descriptors of different formats matched"); + const auto both = [&](std::uint32_t word3) { + auto changed = data; + changed[3] = changed[7] = word3; + return MatchesIndexedCopyKernel(Code, changed, compute); + }; + Require(!both(Format | 0x00800000u), "an add-tid descriptor matched"); + Require(!both(Format | 0x30000000u), "a raw out-of-bounds mode matched"); + Require(!both(Format | 0x40000000u), "a descriptor of another type matched"); + auto noRecords = data; + noRecords[10] = 0; + Require(!MatchesIndexedCopyKernel(Code, noRecords, compute), "a record descriptor without records matched"); + auto extra = data; + extra.push_back(0); + Require(!MatchesIndexedCopyKernel(Code, extra, compute), "a different user SGPR count matched (the group id moves)"); + auto wave32 = compute; + wave32.numThreads[0] = 32; + Require(!MatchesIndexedCopyKernel(Code, data, wave32), "a group of 32 threads matched"); + auto noGroup = compute; + noGroup.groupIdEnable[0] = false; + Require(!MatchesIndexedCopyKernel(Code, data, noGroup), "a dispatch without the group id matched"); + + const auto source = reinterpret_cast(Source.data()); + const auto destination = reinterpret_cast(Destination.data()); + const auto copy = IndexedCopyRange(data, 4, 256, 0, 0); + Require(copy && copy->source == source && copy->destination == destination && copy->bytes == 4096, "4 groups were not taken as a copy of 256 elements"); + const auto offset = IndexedCopyRange(data, 4, 200, 16, 300); + Require(offset && offset->source == source + 256 && offset->destination == destination + 4800 && offset->bytes == 3200, "the offsets were not applied in elements"); + const auto launched = IndexedCopyRange(data, 4, 300, 0, 0); + Require(launched && launched->bytes == 4096, "the copy exceeded the launched threads"); + const auto fewer = IndexedCopyRange(data, 4, 100, 0, 0); + Require(fewer && fewer->bytes == 1600, "a count below the launch was not taken as the copy"); + Require(!IndexedCopyRange(data, 4, 256, 300, 0), "a source window past the records was taken"); + Require(!IndexedCopyRange(data, 4, 256, 0, 300), "a destination window past the records was taken"); + Require(!IndexedCopyRange(data, 4, 0, 0, 0), "a copy of 0 elements was taken"); + Require(!IndexedCopyRange(data, 0, 256, 0, 0), "a copy of 0 groups was taken"); + Require(IndexedCopyRange(UserData(Source.data(), 16384u * 64u, Destination.data(), 16384u * 64u), 16384, 16384u * 64u, 0, 0).has_value(), "a copy of 16 MiB was refused"); + Require(!IndexedCopyRange(UserData(Source.data(), LargeGroups * 64u, Destination.data(), LargeGroups * 64u), LargeGroups, LargeGroups * 64u, 0, 0), "a copy above 16 MiB was taken"); + Require(!IndexedCopyRange(UserData(Source.data() + 1, 512, Destination.data(), 512), 4, 256, 0, 0), "a source not aligned to 16 bytes was taken"); + Require(!IndexedCopyRange(UserData(Source.data(), 512, Destination.data() + 1, 512), 4, 256, 0, 0), "a destination not aligned to 16 bytes was taken"); +} + +std::vector Commands(const std::vector& data, std::uint32_t groups) { + std::vector words; + const auto registers = [&](std::uint32_t first, std::span values) { + words.push_back(0xc0007600u | (static_cast(values.size()) << 16u)); + words.push_back(first); + words.insert(words.end(), values.begin(), values.end()); + }; + const std::array shape{64, 1, 1}; + registers(0x207, shape); + const auto address = reinterpret_cast(Code.data()); + const std::array program{static_cast(address >> 8u), static_cast(address >> 40u)}; + registers(0x20c, program); + const std::array resource{(static_cast(data.size()) << 1u) | (1u << 7u)}; + registers(0x213, resource); + registers(0x240, data); + words.insert(words.end(), {0xc0031500u, groups, 1, 1, 0x41u}); + return words; +} + +std::uint64_t Taken() { + using namespace AgcDriver::DriverDetail; + return copyPathCounts[CopyCpu].load() + copyPathCounts[CopyGpu].load() + copyPathCounts[CopyAliased].load(); +} + +void ExecuteTests() { + using AgcDriver::DriverDetail::copyPathCounts; + using AgcDriver::DriverDetail::CopyShape; + struct Case { + const char* name; + std::uint32_t groups, count, sourceOffset, destinationOffset, sourceRecords, destinationRecords; + bool hle; + }; + constexpr std::array cases{{ + {"4 groups", 4, 256, 0, 0, 512, 512, true}, + {"16 groups", 16, 1024, 0, 0, 2048, 2048, true}, + {"offsets", 4, 200, 16, 300, 512, 512, true}, + {"count below the launch", 4, 100, 0, 0, 512, 512, true}, + {"count above the launch", 4, 300, 0, 0, 512, 512, true}, + {"source window past the records", 4, 256, 300, 0, 512, 512, false}, + {"destination window past the records", 4, 256, 0, 300, 512, 512, false}, + {"above 16 MiB", LargeGroups, LargeGroups * 64u, 0, 0, LargeGroups * 64u, LargeGroups * 64u, false}, + }}; + const bool hleEnabled = std::getenv("APS5_NO_COPY_HLE") == nullptr; + for (const auto& item : cases) { + const bool hle = item.hle && hleEnabled; + for (std::size_t i = 0; i < Source.size(); ++i) Source[i] = static_cast(i) * 2654435761u + 1u; + Destination.fill(Untouched); + Record = {item.count, item.sourceOffset, item.destinationOffset, 0u}; + const auto taken = Taken(); + const auto refused = copyPathCounts[CopyShape].load(); + const auto words = Commands(UserData(Source.data(), item.sourceRecords, Destination.data(), item.destinationRecords), item.groups); + Packet packet{const_cast(words.data()), static_cast(words.size()), 0, {}}; + AgcDriver::Submit(&packet, 0); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(Destination.data()), sizeof(Destination)); + const std::string where = std::string(" (") + item.name + ")"; + Require(Taken() - taken == (hle ? 1u : 0u), (hle ? "the copy did not take the HLE path" : "the copy took the HLE path") + where); + Require(copyPathCounts[CopyShape].load() - refused == (hle || !hleEnabled ? 0u : 1u), (hle ? "the copy was refused for its shape" : "the copy was not refused for its shape") + where); + const auto elements = std::min(item.groups * 64u, item.count); + for (std::size_t i = 0; i < Destination.size(); ++i) { + const auto element = i / 4u; + const bool written = element >= item.destinationOffset && element < std::uint64_t{item.destinationOffset} + elements && element < item.destinationRecords; + const auto sourceElement = element - item.destinationOffset + item.sourceOffset; + const auto expected = !written ? Untouched : sourceElement < item.sourceRecords ? Source[sourceElement * 4u + i % 4u] : 0u; + Require(Destination[i] == expected, "indexed copy mismatch at word " + std::to_string(i) + where + ": got " + std::to_string(Destination[i]) + ", expected " + std::to_string(expected)); + } + } +} + +} + +int main() { + try { + if (std::getenv("APS5_NO_COPY_HLE") == nullptr) MatchTests(); + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + ExecuteTests(); + AgcDriverShutdown_nid_postfix(); + std::puts("indexed copy tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + AgcDriverShutdown_nid_postfix(); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/IndexedCopyGpuParameters.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/IndexedCopyGpuParameters.cpp new file mode 100644 index 0000000000..b2878fc79a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/IndexedCopyGpuParameters.cpp @@ -0,0 +1,150 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +namespace { + +using AgcDriver::Graphics::Require; + +alignas(256) constexpr std::array Code{ + 0xbfa00001u, 0xd7460000u, 0x04010c0cu, 0xf4201a84u, 0xfa000000u, + 0xbf8cc07fu, 0x7da8006au, 0xbf88000au, 0xf4241a84u, 0xfa000004u, + 0xbf8cc07fu, 0x4a08006bu, 0x4a00006au, 0xe0382000u, 0x80000000u, + 0xbf8c3f70u, 0xe0782000u, 0x80010004u, 0xbf810000u}; +constexpr std::uint32_t Untouched = 0xcafebabeu; +constexpr std::size_t Prefix = 16; +constexpr std::size_t Records = 256; +alignas(256) std::array Source{}, Destination{}; +alignas(16) std::array Parameters{}; +alignas(256) constexpr std::array AddressWriterCode{0x7e000202u, 0x7e020203u, 0x7e040204u, 0x7e060280u, 0xdc7c8000u, 0x00000003u, 0xbf810000u}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + data = static_cast(VirtualAlloc(nullptr, Bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + data = static_cast(std::aligned_alloc(Bytes, Bytes)); +#endif + Require(data != nullptr, "cannot allocate parameters"); + std::fill_n(data, Bytes / 4, 0); + GuestAllocations::Mutation().Add(data, Bytes, true, true, true); + } + ~GuestBlock() { + GuestAllocations::Mutation().Remove(data); +#ifdef _WIN32 + VirtualFree(data, 0, MEM_RELEASE); +#else + std::free(data); +#endif + } + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + std::uint32_t* Data() const { return data; } + +private: + static constexpr std::size_t Bytes = 65536; + std::uint32_t* data = nullptr; +}; + +std::vector UserData(std::uint32_t sourceRecords, std::uint32_t destinationRecords, const void* parameters = Parameters.data()) { + std::vector words; + const auto descriptor = [&](const void* pointer, std::uint32_t records, std::uint32_t format) { + const auto address = reinterpret_cast(pointer); + words.insert(words.end(), {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), records, format}); + }; + descriptor(Source.data() + Prefix, sourceRecords, 0x00005204u); + descriptor(Destination.data() + Prefix, destinationRecords, 0x00005204u); + descriptor(parameters, 1, 0x0004dfacu); + return words; +} + +std::uint64_t Taken() { + using namespace AgcDriver::DriverDetail; + return copyPathCounts[CopyCpu].load() + copyPathCounts[CopyGpu].load() + copyPathCounts[CopyAliased].load(); +} + +std::vector Commands(const std::vector& data, std::uint32_t groups, const void* code = Code.data(), std::uint32_t threads = 64, bool groupId = true) { + std::vector words; + const auto registers = [&](std::uint32_t first, std::span values) { + words.push_back(0xc0007600u | (static_cast(values.size()) << 16u)); + words.push_back(first); + words.insert(words.end(), values.begin(), values.end()); + }; + const std::array shape{threads, 1, 1}; + registers(0x207, shape); + const auto address = reinterpret_cast(code); + const std::array program{static_cast(address >> 8u), static_cast(address >> 40u)}; + registers(0x20c, program); + const std::array resource{(static_cast(data.size()) << 1u) | (static_cast(groupId) << 7u)}; + registers(0x213, resource); + registers(0x240, data); + words.insert(words.end(), {0xc0031500u, groups, 1, 1, 0x41u}); + return words; +} + +void GpuParameters() { + GuestBlock parameters; + parameters.Data()[0] = 1; + Destination.fill(Untouched); + for (std::size_t i = 0; i < Source.size(); ++i) Source[i] = static_cast(i) * 2654435761u + 1u; + const auto address = reinterpret_cast(parameters.Data()); + auto words = Commands({static_cast(address), static_cast(address >> 32u), 23, 17, 29}, 1, AddressWriterCode.data(), 1, false); + const auto copy = Commands(UserData(Records, Records, parameters.Data()), 1); + words.insert(words.end(), copy.begin(), copy.end()); + const auto taken = Taken(); + const auto deferred = AgcDriver::Graphics::LeaseCounters().deferred; + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + AgcDriver::Submit(&packet, 0); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(Destination.data()), sizeof(Destination)); + const auto deferredAfter = AgcDriver::Graphics::LeaseCounters().deferred; + const auto takenAfter = Taken(); + AgcDriverShutdown_nid_postfix(); + Require(deferredAfter > deferred, "parameter producer was not deferred"); + Require(parameters.Data()[0] == 23 && parameters.Data()[1] == 17 && parameters.Data()[2] == 29, "GPU parameter store differs"); + Require(takenAfter - taken == (std::getenv("APS5_NO_COPY_HLE") == nullptr ? 1u : 0u), "GPU parameter copy did not take the expected path"); + for (std::size_t i = 0; i < Destination.size(); ++i) { + const bool copied = i >= Prefix + 29 * 4 && i < Prefix + (29 + 23) * 4; + const auto expected = copied ? Source[i - (29 - 17) * 4] : Untouched; + Require(Destination[i] == expected, "GPU parameter copy differs at word " + std::to_string(i)); + } + std::puts("PASS: copy consumes parameters written by a preceding address-based GPU store"); +} + + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + GpuParameters(); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + AgcDriverShutdown_nid_postfix(); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp index 9c332e8aaa..913426408a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp @@ -262,6 +262,7 @@ struct Generator { if (half) return bits; const auto value = std::bit_cast(bits); if (!std::isfinite(value) || std::fabs(value) > 65504.0f) return Half(); + if (value == 0.0f) return bits; const auto exponent = std::max(std::ilogb(value) - 10, -24); return std::bit_cast(static_cast(std::ldexp(std::nearbyint(std::ldexp(value, -exponent)), exponent))); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/JobManagerCompute.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/JobManagerCompute.cpp index c149aa41a9..0e677dd20c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/JobManagerCompute.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/JobManagerCompute.cpp @@ -1,5 +1,12 @@ +#include "prx/libSceAgcDriver/libSceAgcDriver.hpp" +#include "prx/libSceAgc/libSceAgc.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceJobManager/libSceJobManager.hpp" #include "VulkanTestDevice.hpp" #include "SceTypes.hpp" +#include +#include "prx/libc/include/GuestArena.hpp" #include #include #include @@ -11,61 +18,8 @@ #include #include #include +#include -extern "C" void AgcDriverShutdown_nid_postfix(); - -struct MemorySizeQueryParams { - std::uint32_t version; - std::uint32_t workers; - std::uint32_t sequences; - std::uint32_t invocations; - std::uint32_t dispatches; - std::uint32_t syncPoints; - std::uint32_t waits; - std::uint32_t computeSequences; - std::uint64_t computeQueueMask; -}; - -struct ConfigParams { - std::uint32_t version; - std::uint32_t threadFlags; - std::uint32_t stackSize; - std::uint32_t priority; - const char* threadName; - std::uint32_t spinMicroseconds; - std::uint32_t computeAffinity; - std::uint32_t computePriority; - std::uint32_t reserved; -}; - -using JobEntry = void (APS5_VABI*)(void*, std::uint32_t); - -struct Sequence { - APS5_VABI virtual int Dispatch(JobEntry, void*, std::uint32_t, const char*) = 0; - APS5_VABI virtual int SyncPoint() = 0; - APS5_VABI virtual int Wait() = 0; - APS5_VABI virtual void CompleteDestructor() = 0; - APS5_VABI virtual void DeletingDestructor() = 0; - APS5_VABI virtual int DispatchCompute(const void*, void*, std::int32_t, std::int32_t, std::int32_t, const char*) = 0; - APS5_VABI virtual int DispatchComputeIndirect(const void*, void*, const void*, const char*) = 0; -}; - -struct SequenceFactory { - APS5_VABI virtual int Create(Sequence**, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, const char*) = 0; - APS5_VABI virtual int Destroy(Sequence*) = 0; - APS5_VABI virtual void CompleteDestructor() = 0; - APS5_VABI virtual void DeletingDestructor() = 0; - APS5_VABI virtual int CreateCompute(Sequence**, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, const char*) = 0; - APS5_VABI virtual int DestroyCompute(Sequence*) = 0; -}; - -extern "C" { -void APS5_VABI _ZN3sce3Job10JobManagerC1Ev(void*); -std::uint64_t APS5_VABI _ZN3sce3Job10JobManager27calculateRequiredMemorySizeEPKNS1_21MemorySizeQueryParamsE(const MemorySizeQueryParams*); -int APS5_VABI _ZN3sce3Job10JobManager10initializeEPvmjjjmPKNS1_12ConfigParamsE(void*, void*, std::uint64_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint64_t, const ConfigParams*); -int APS5_VABI _ZN3sce3Job10JobManager8shutdownEv(void*); -SequenceFactory* APS5_VABI _ZN3sce3Job10JobManager34getUnifiedSequenceFactoryInterfaceENS1_8PriorityE(void*, std::uint32_t); -} #define Construct _ZN3sce3Job10JobManagerC1Ev #define RequiredSize _ZN3sce3Job10JobManager27calculateRequiredMemorySizeEPKNS1_21MemorySizeQueryParamsE @@ -94,10 +48,10 @@ alignas(256) volatile std::uint32_t Output[4]{}; alignas(256) std::array OutputDescriptor{}; alignas(32) std::array IndirectArguments{1, 1, 1, 0, 0, 0, 0, 0}; alignas(16) std::array Specials{}; -alignas(16) std::array SpecialsWithoutDispatch{}; +alignas(16) std::array SpecialsDefaultModifier{}; std::array Registers{}; alignas(16) std::array Shader{}; -alignas(16) std::array ShaderWithoutDispatch{}; +alignas(16) std::array ShaderDefaultModifier{}; std::uint32_t Low(const volatile void* pointer) { return static_cast(reinterpret_cast(pointer)); @@ -115,8 +69,8 @@ void BuildShader() { {0x242, Low(OutputDescriptor.data())}, {0x243, High(OutputDescriptor.data())}}}; Specials[4] = 1; const ShaderRegister* registers = Registers.data(); - for (auto* shader : {&Shader, &ShaderWithoutDispatch}) { - const std::uint32_t* specials = shader == &Shader ? Specials.data() : SpecialsWithoutDispatch.data(); + for (auto* shader : {&Shader, &ShaderDefaultModifier}) { + const std::uint32_t* specials = shader == &Shader ? Specials.data() : SpecialsDefaultModifier.data(); std::memcpy(shader->data() + 0x20, ®isters, sizeof(registers)); std::memcpy(shader->data() + 0x28, &specials, sizeof(specials)); (*shader)[0x5c] = static_cast(Registers.size()); @@ -130,6 +84,7 @@ std::uint64_t SizeFor(std::uint32_t computeSequences, std::uint64_t mask, bool l std::atomic Released{false}; std::atomic SeenByJob{0}; +std::atomic SecondSeenByJob{0}; void APS5_VABI BlockingJob(void*, std::uint32_t) { while (!Released) std::this_thread::yield(); @@ -139,15 +94,21 @@ void APS5_VABI ReadOutputJob(void*, std::uint32_t) { SeenByJob = Output[0]; } +void APS5_VABI ReadBothOutputsJob(void* argument, std::uint32_t) { + SeenByJob = Output[0]; + SecondSeenByJob = Output[1]; + std::atomic_ref(*static_cast(argument)).store(1); +} + void APS5_VABI Nothing(void*, std::uint32_t) { } void SizeCase() { Require(SizeFor(0, 0, false) == 0x10d0 && SizeFor(0, 0, true) == 0x14c0, "size without compute sequences"); - Require(SizeFor(1, 1, false) == 0x24fd8 && SizeFor(1, 1, true) == 0x253c8, "size with one compute sequence"); - Require(SizeFor(2, 3, false) == 0x48e20 && SizeFor(2, 3, true) == 0x49210, "size with two compute sequences"); - Require(SizeFor(3, 7, false) == 0x6cb68, "size with three compute sequences"); - Require(SizeFor(1, 3, true) == 0x491c8, "size with two queues for one compute sequence"); + Require(SizeFor(1, 1, false) == 0x29fd8 && SizeFor(1, 1, true) == 0x2a3c8, "size with one compute sequence"); + Require(SizeFor(2, 3, false) == 0x52de0 && SizeFor(2, 3, true) == 0x531d0, "size with two compute sequences"); + Require(SizeFor(3, 7, false) == 0x7bb28, "size with three compute sequences"); + Require(SizeFor(1, 3, true) == 0x53188, "size with two queues for one compute sequence"); Require(SizeFor(2, 1, true) == 0, "size with fewer queues than compute sequences"); } @@ -165,9 +126,9 @@ void RunCase() { Require(Initialize(manager, memory, size, 2, 1, 1, 2, &wideAffinity) == InvalidArgument, "a handler affinity beyond 13 cores was accepted"); Require(Initialize(manager, memory, size, 2, 1, 0xffffffffu, 2, nullptr) == InvalidArgument, "a sequence count that wraps was accepted"); Require(Initialize(manager, memory, size, 2, 1, 1, 2, nullptr) == 0, "initialize with one compute sequence failed"); - SequenceFactory* factory = Factories(manager, 0); - Sequence* sequence = nullptr; - Sequence* other = nullptr; + ISequenceFactory* factory = Factories(manager, 0); + ISequence* sequence = nullptr; + ISequence* other = nullptr; Require(factory->CreateCompute(&sequence, 100, 4, 4, 2, "compute") == 0, "the compute sequence was not created"); Require(factory->CreateCompute(&other, 1, 1, 1, 1, "second") == NoneFree, "a second compute sequence was created"); Require(factory->Create(&other, 1, 1, 1, 1, "cpu") == 0, "the CPU sequence was not created"); @@ -192,24 +153,30 @@ void RunCase() { Require(Output[0] == 0x22220002u, "the indirect compute dispatch had not run when the wait returned: " + std::to_string(Output[0])); Value[0] = 0x33330003u; - Require(sequence->DispatchCompute(ShaderWithoutDispatch.data(), Table.data(), 1, 1, 1, nullptr) == 0, "the dispatch of a shader without the dispatch bit failed"); - Require(sequence->Wait() == 0, "the wait after a shader without the dispatch bit failed"); - Require(Output[0] == 0x22220002u, "a shader without the dispatch bit ran"); + Require(sequence->DispatchCompute(ShaderDefaultModifier.data(), Table.data(), 1, 1, 1, nullptr) == 0, "the dispatch with a default modifier failed"); + Require(sequence->Wait() == 0, "the wait after a default modifier dispatch failed"); + Require(Output[0] == 0x33330003u, "a default modifier dispatch did not run"); + Value[0] = 0x33330004u; + Require(sequence->DispatchComputeIndirect(ShaderDefaultModifier.data(), Table.data(), IndirectArguments.data(), nullptr) == 0, "the indirect dispatch with a default modifier failed"); + Require(sequence->Wait() == 0, "the wait after an indirect default modifier dispatch failed"); + Require(Output[0] == 0x33330004u, "an indirect default modifier dispatch did not run"); + + Value[0] = 0x33330005u; Require(sequence->Dispatch(BlockingJob, nullptr, 1, "blocking") == 0, "the blocking job was not dispatched"); Require(sequence->SyncPoint() == 0, "the sync point failed"); Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "the compute dispatch behind a sync point failed"); std::this_thread::sleep_for(std::chrono::milliseconds(200)); - Require(Output[0] == 0x22220002u, "a compute dispatch ran before the CPU job ahead of its sync point finished"); + Require(Output[0] == 0x33330004u, "a compute dispatch ran before the CPU job ahead of its sync point finished"); Released = true; Require(sequence->SyncPoint() == 0, "the second sync point failed"); Require(sequence->Dispatch(ReadOutputJob, nullptr, 1, "reader") == 0, "the reading job was not dispatched"); Require(sequence->Wait() == 0, "the wait after the ordered dispatches failed"); - Require(Output[0] == 0x33330003u, "the compute dispatch behind a sync point did not run"); - Require(SeenByJob == 0x33330003u, "a CPU job behind a sync point ran before the compute dispatch ahead of it finished"); + Require(Output[0] == 0x33330005u, "the compute dispatch behind a sync point did not run"); + Require(SeenByJob == 0x33330005u, "a CPU job behind a sync point ran before the compute dispatch ahead of it finished"); Value[0] = 0x44440004u; - Require(sequence->DispatchCompute(ShaderWithoutDispatch.data(), Table.data(), 1, 1, 1, nullptr) == 0, "the leading dispatch failed"); + Require(sequence->DispatchCompute(ShaderDefaultModifier.data(), Table.data(), 1, 1, 1, nullptr) == 0, "the leading dispatch failed"); Require(sequence->SyncPoint() == 0, "the sync point between compute dispatches failed"); Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, "behind") == 0, "the compute dispatch behind a compute dispatch failed"); Require(sequence->Wait() == 0, "the wait after chained compute dispatches failed"); @@ -229,16 +196,395 @@ void RunCase() { ::operator delete(memory, std::align_val_t{256}); } +void CapacityCase() { + std::array registers; + registers.fill(Registers[2]); + std::copy(Registers.begin(), Registers.end(), registers.begin()); + auto shader = Shader; + const ShaderRegister* data = registers.data(); + std::memcpy(shader.data() + 0x20, &data, sizeof(data)); + shader[0x5c] = static_cast(registers.size()); + const std::string marker(65535, 'm'); + const MemorySizeQueryParams query{1, 1, 0, 0, 2, 0, 1, 1, 1}; + const auto size = RequiredSize(&query); + const auto allocationSize = (size + 255) & ~std::uint64_t{255}; + auto* memory = static_cast(::operator new(allocationSize, std::align_val_t{256})); + std::memset(memory, 0, allocationSize); + std::memset(memory + size, 0xa5, allocationSize - size); + alignas(64) unsigned char manager[0x100]{}; + Construct(manager); + Require(Initialize(manager, memory, size, 1, 0, 1, 1, nullptr) == 0, "capacity manager initialization failed"); + auto* factory = Factories(manager, 0); + ISequence* sequence = nullptr; + Require(factory->CreateCompute(&sequence, 0, 2, 0, 1, nullptr) == 0, "capacity sequence creation failed"); + for (std::uint32_t round = 0; round < 2; ++round) { + Value[0] = 0xabcd0000u + round; + for (int dispatch = 0; dispatch < 2; ++dispatch) + Require(sequence->DispatchCompute(shader.data(), Table.data(), 1, 1, 1, marker.c_str()) == 0, "maximum register and marker dispatch failed"); + Require(sequence->Wait() == 0, "maximum packet wait failed"); + Require(Output[0] == Value[0], "maximum packet dispatch did not finish"); + const auto* words = reinterpret_cast(memory); + std::uint32_t markers = 0; + for (std::size_t offset = 0; offset + 0x4001 <= size / 4; ++offset) { + if (words[offset] != 0xffff102cu) continue; + Require(std::memcmp(words + offset + 1, marker.c_str(), marker.size() + 1) == 0, "maximum marker was truncated"); + ++markers; + } + Require(markers == 2, "the maximum markers were not stored in the command area"); + } + Require(factory->DestroyCompute(sequence) == 0, "capacity sequence destruction failed"); + Require(Shutdown(manager) == 0, "capacity manager shutdown failed"); + for (auto offset = size; offset < allocationSize; ++offset) + Require(memory[offset] == 0xa5, "maximum packets escaped the supplied work area"); + ::operator delete(memory, std::align_val_t{256}); } -int main() { +void DeferredInputsCase(bool mixed) { + for (const bool indirect : {false, true}) { + const auto size = SizeFor(1, 1, true); + auto* memory = static_cast(::operator new(size, std::align_val_t{256})); + std::memset(memory, 0, size); + alignas(64) unsigned char manager[0x100]{}; + Construct(manager); + Require(Initialize(manager, memory, size, 2, 1, 1, 1, nullptr) == 0, "snapshot manager initialization failed"); + auto* factory = Factories(manager, 0); + ISequence* sequence = nullptr; + Require(factory->CreateCompute(&sequence, 1, 3, 1, 1, nullptr) == 0, "snapshot sequence creation failed"); + auto registers = Registers; + auto shader = Shader; + const ShaderRegister* pointer = registers.data(); + std::memcpy(shader.data() + 0x20, &pointer, sizeof(pointer)); + alignas(16) const std::array redirected{Low(Output + 1), High(Output + 1) & 0xffffu, sizeof(Output[1]), 0x31016facu}; + const std::string expected = indirect ? "snapshot indirect" : "snapshot direct"; + std::array marker{}; + std::memcpy(marker.data(), expected.c_str(), expected.size() + 1); + Released = false; + Output[0] = 0; + Output[1] = 0; + Value[0] = indirect ? 0x51a70002u : 0x51a70001u; + if (mixed) Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "snapshot parallel compute dispatch failed"); + Require(sequence->Dispatch(BlockingJob, nullptr, 1, nullptr) == 0, "snapshot blocking job failed"); + Require(sequence->SyncPoint() == 0, "snapshot sync point failed"); + const int result = indirect ? sequence->DispatchComputeIndirect(shader.data(), Table.data(), IndirectArguments.data(), marker.data()) + : sequence->DispatchCompute(shader.data(), Table.data(), 1, 1, 1, marker.data()); + Require(result == 0, "snapshot dispatch failed"); + const bool encoded = std::search(memory, memory + size, expected.begin(), expected.end()) != memory + size; + registers[7].value = Low(redirected.data()); + registers[8].value = High(redirected.data()); + std::memset(marker.data(), 'x', expected.size()); + Released = true; + Require(sequence->Wait() == 0, "snapshot wait failed"); + const bool updated = Output[0] == (mixed ? Value[0] : 0) && Output[1] == Value[0]; + const bool markerUpdated = std::search(memory, memory + size, marker.begin(), marker.begin() + expected.size()) != memory + size; + Require(factory->DestroyCompute(sequence) == 0, "snapshot destruction failed"); + Require(Shutdown(manager) == 0, "snapshot shutdown failed"); + ::operator delete(memory, std::align_val_t{256}); + Require(!encoded, "compute commands were encoded before their CPU barrier retired"); + Require(updated, "queued compute commands did not read their deferred shader registers"); + Require(markerUpdated, "queued compute commands did not read their deferred marker"); + } +} + +void GpuBarriersCase() { + for (const bool indirect : {false, true}) { + const auto size = SizeFor(1, 2, true); + auto* memory = static_cast(::operator new(size, std::align_val_t{256})); + std::memset(memory, 0, size); + alignas(64) unsigned char manager[0x100]{}; + Construct(manager); + Require(Initialize(manager, memory, size, 2, 1, 1, 2, nullptr) == 0, "GPU barrier initialization failed"); + auto* factory = Factories(manager, 0); + ISequence* sequence = nullptr; + Require(factory->CreateCompute(&sequence, 1, 8, 4, 2, nullptr) == 0, "GPU barrier sequence creation failed"); + auto registers = Registers; + auto shader = Shader; + const ShaderRegister* pointer = registers.data(); + std::memcpy(shader.data() + 0x20, &pointer, sizeof(pointer)); + alignas(16) const std::array redirected{Low(Output + 1), High(Output + 1) & 0xffffu, sizeof(Output[1]), 0x31016facu}; + alignas(64) std::uint32_t gate = 0; + alignas(256) std::array commands{}; + CommandBuffer buffer{commands.data(), commands.data() + commands.size(), commands.data(), commands.data() + commands.size(), nullptr, nullptr, 0}; + Require(sceAgcAcbWaitRegMem(&buffer, 0, 3, 3, &gate, 1, 0xffffffffu, 0x190) != nullptr, "GPU barrier gate encoding failed"); + const Packet packet{commands.data(), static_cast(buffer.cursor_up - commands.data()), 0, {}}; + Require(sceAgcDriverSubmitAcb(0x21, &packet) == 0, "GPU barrier gate submission failed"); + Output[0] = 0; + Output[1] = 0; + Value[0] = indirect ? 0x51a80002u : 0x51a80001u; + Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, "first") == 0, "GPU barrier first dispatch failed"); + Require(sequence->SyncPoint() == 0, "GPU barrier sync point failed"); + const std::string expected = indirect ? "GPU barrier indirect" : "GPU barrier direct"; + std::array marker{}; + std::memcpy(marker.data(), expected.c_str(), expected.size() + 1); + const int result = indirect ? sequence->DispatchComputeIndirect(shader.data(), Table.data(), IndirectArguments.data(), marker.data()) + : sequence->DispatchCompute(shader.data(), Table.data(), 1, 1, 1, marker.data()); + Require(result == 0, "GPU barrier second dispatch failed"); + const bool encoded = std::search(memory, memory + size, expected.begin(), expected.end()) != memory + size; + registers[7].value = Low(redirected.data()); + registers[8].value = High(redirected.data()); + std::memset(marker.data(), 'x', expected.size()); + std::atomic_ref(gate).store(1); + Require(sequence->Wait() == 0, "GPU barrier wait failed"); + Require(encoded, "GPU-only sync deferred the following dispatch inputs"); + Require(Output[0] == Value[0] && Output[1] == 0, "GPU-only sync did not capture shader registers before dispatch returned"); + alignas(16) const std::array dependentTable{Low(Output), High(Output)}; + for (std::uint32_t round = 0; round < 3; ++round) { + Output[0] = 0; + Output[1] = 0; + Value[0] = 0x52a80000u + round; + std::atomic_ref(gate).store(0); + Require(sceAgcDriverSubmitAcb(0x21, &packet) == 0, "GPU dependency gate submission failed"); + Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "GPU dependency producer failed"); + Require(sequence->SyncPoint() == 0, "GPU dependency sync failed"); + Require(sequence->DispatchCompute(shader.data(), const_cast(dependentTable.data()), 1, 1, 1, nullptr) == 0, "GPU dependency consumer failed"); + std::atomic_ref(gate).store(1); + Require(sequence->Wait() == 0, "GPU dependency wait failed"); + Require(Output[0] == Value[0] && Output[1] == Value[0], "GPU sync did not make producer writes visible to its consumer"); + } + if (!indirect) { + alignas(64) std::uint32_t overlapGate = 0; + alignas(256) std::array overlapCommands{}; + CommandBuffer overlapBuffer{overlapCommands.data(), overlapCommands.data() + overlapCommands.size(), overlapCommands.data(), overlapCommands.data() + overlapCommands.size(), nullptr, nullptr, 0}; + Require(sceAgcAcbWaitRegMem(&overlapBuffer, 0, 3, 3, &overlapGate, 1, 0xffffffffu, 0x190) != nullptr, "GPU overlap gate encoding failed"); + const Packet overlapPacket{overlapCommands.data(), static_cast(overlapBuffer.cursor_up - overlapCommands.data()), 0, {}}; + Output[0] = 0; + Output[1] = 0; + std::atomic_ref(gate).store(0); + Require(sceAgcDriverSubmitAcb(0x21, &packet) == 0, "GPU overlap gate submission failed"); + Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "GPU overlap producer failed"); + Require(sequence->SyncPoint() == 0, "GPU overlap sync failed"); + Require(sceAgcDriverSubmitAcb(0x21, &overlapPacket) == 0, "GPU overlap gate submission failed"); + Require(sequence->DispatchCompute(shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "GPU overlap consumer failed"); + Require(sequence->Dispatch(ReadBothOutputsJob, &overlapGate, 1, nullptr) == 0, "GPU overlap CPU callback failed"); + std::atomic missedDeadline{false}; + std::jthread rescue([&](std::stop_token stop) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + const std::atomic_ref signal(overlapGate); + while (!stop.stop_requested() && signal.load() == 0 && std::chrono::steady_clock::now() < deadline) std::this_thread::yield(); + if (signal.load() == 0) { + missedDeadline = true; + signal.store(1); + } + }); + std::atomic_ref(gate).store(1); + Require(sequence->Wait() == 0, "GPU overlap wait failed"); + rescue.request_stop(); + rescue.join(); + Require(!missedDeadline, "CPU work did not release GPU work beyond its preceding sync"); + Require(SeenByJob == Value[0] && SecondSeenByJob == 0, "CPU work waited for GPU work beyond its preceding sync"); + Require(Output[1] != 0, "GPU overlap consumer did not finish before Wait returned"); + alignas(16) const std::array originalTarget{Low(Output + 2), High(Output + 2) & 0xffffu, sizeof(Output[2]), 0x31016facu}; + alignas(16) const std::array changedTarget{Low(Output + 3), High(Output + 3) & 0xffffu, sizeof(Output[3]), 0x31016facu}; + Output[2] = 0; + Output[3] = 0; + registers[7].value = Low(originalTarget.data()); + registers[8].value = High(originalTarget.data()); + const std::string afterWait = "after waiting caller"; + std::memcpy(marker.data(), afterWait.c_str(), afterWait.size() + 1); + std::atomic_ref(gate).store(0); + Require(sceAgcDriverSubmitAcb(0x21, &packet) == 0, "GPU wait-boundary gate submission failed"); + Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "GPU wait-boundary producer failed"); + Require(sequence->SyncPoint() == 0, "GPU wait-boundary sync failed"); + Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "GPU wait-boundary second dispatch failed"); + std::atomic waiting{false}; + std::thread waiter([&] { + waiting.store(true); + Require(sequence->Wait() == 0, "GPU wait-boundary first wait failed"); + }); + while (!waiting.load()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(sequence->DispatchCompute(shader.data(), Table.data(), 1, 1, 1, marker.data()) == 0, "GPU dispatch after a queued wait failed"); + const bool crossedWait = std::search(memory, memory + size, afterWait.begin(), afterWait.end()) != memory + size; + registers[7].value = Low(changedTarget.data()); + registers[8].value = High(changedTarget.data()); + std::atomic_ref(gate).store(1); + waiter.join(); + Require(sequence->Wait() == 0, "GPU wait-boundary final wait failed"); + Require(!crossedWait && Output[2] == 0 && Output[3] == Value[0], "compute lookahead crossed a waiting caller's boundary"); + } + Require(factory->DestroyCompute(sequence) == 0, "GPU barrier destruction failed"); + Require(Shutdown(manager) == 0, "GPU barrier shutdown failed"); + ::operator delete(memory, std::align_val_t{256}); + } +} + +void ComputeShutdownCase() { + for (const std::uint32_t mode : {0u, 1u, 2u}) { + const bool waiting = mode == 1; + const bool queuedCpu = mode == 2; + const auto size = SizeFor(1, 2, true); + const auto allocationSize = (size + 255) & ~std::uint64_t{255}; + auto* memory = static_cast(::operator new(allocationSize, std::align_val_t{256})); + std::memset(memory, 0, size); + std::memset(memory + size, 0xa5, allocationSize - size); + alignas(64) unsigned char manager[0x100]{}; + Construct(manager); + Require(Initialize(manager, memory, size, 2, 1, 1, 2, nullptr) == 0, "compute shutdown initialization failed"); + auto* factory = Factories(manager, 0); + ISequence* sequence = nullptr; + Require(factory->CreateCompute(&sequence, 1, 4, 2, 2, nullptr) == 0, "compute shutdown sequence creation failed"); + auto registers = Registers; + auto shader = Shader; + alignas(16) const std::array descriptor{Low(Output + 1), High(Output + 1) & 0xffffu, sizeof(Output[1]), 0x31016facu}; + registers[7].value = Low(descriptor.data()); + registers[8].value = High(descriptor.data()); + const ShaderRegister* registerPointer = registers.data(); + std::memcpy(shader.data() + 0x20, ®isterPointer, sizeof(registerPointer)); + alignas(64) std::uint32_t gate = 0; + alignas(256) std::array commands{}; + CommandBuffer buffer{commands.data(), commands.data() + commands.size(), commands.data(), commands.data() + commands.size(), nullptr, nullptr, 0}; + Require(sceAgcAcbWaitRegMem(&buffer, 0, 3, 3, &gate, 1, 0xffffffffu, 0x190) != nullptr, "compute shutdown gate encoding failed"); + const Packet packet{commands.data(), static_cast(buffer.cursor_up - commands.data()), 0, {}}; + Require(sceAgcDriverSubmitAcb(0x21, &packet) == 0, "compute shutdown gate submission failed"); + Output[0] = 0; + Output[1] = 0; + SeenByJob = 0; + Value[0] = 0x5a170000u + mode; + Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "compute shutdown first dispatch failed"); + Require(sequence->SyncPoint() == 0, "compute shutdown first sync failed"); + if (queuedCpu) { + Require(sequence->Dispatch(ReadOutputJob, nullptr, 1, nullptr) == 0, "compute shutdown queued CPU dispatch failed"); + Require(sequence->SyncPoint() == 0, "compute shutdown second sync failed"); + } + Require(sequence->DispatchCompute(shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "compute shutdown second dispatch failed"); + std::atomic waitingStarted{false}; + std::atomic waitingReturned{false}; + std::thread waiter; + if (waiting) { + waiter = std::thread([&] { + waitingStarted.store(true); + Require(sequence->Wait() == 0, "compute shutdown canceled wait failed"); + waitingReturned.store(true); + }); + while (!waitingStarted.load()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(!waitingReturned.load(), "compute shutdown wait passed the held GPU gate"); + } + std::atomic stopped{false}; + std::thread stopper([&] { + Require(Shutdown(manager) == 0, "compute shutdown failed"); + stopped.store(true); + }); + const auto stopDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + while (!stopped.load() && std::chrono::steady_clock::now() < stopDeadline) std::this_thread::yield(); + const bool stoppedBeforeGpu = stopped.load(); + while (waiting && !waitingReturned.load() && std::chrono::steady_clock::now() < stopDeadline) std::this_thread::yield(); + const bool waiterBeforeGpu = !waiting || waitingReturned.load(); + std::atomic_ref(gate).store(1); + stopper.join(); + if (waiting) waiter.join(); + alignas(256) Label fence{}; + alignas(256) std::array tailWords{}; + CommandBuffer tail{tailWords.data(), tailWords.data() + tailWords.size(), tailWords.data(), tailWords.data() + tailWords.size(), nullptr, nullptr, 0}; + Require(sceAgcCbReleaseMem(&tail, 0x2f, 0x200, 1, 3, &fence, 1, 1, 0, 0, 0, 0) != nullptr, "compute shutdown tail fence encoding failed"); + const Packet tailPacket{tailWords.data(), static_cast(tail.cursor_up - tailWords.data()), 0, {}}; + Require(sceAgcDriverSubmitAcb(0x21, &tailPacket) == 0, "compute shutdown tail fence submission failed"); + const auto gpuDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + while (fence.value != 1 && std::chrono::steady_clock::now() < gpuDeadline) std::this_thread::yield(); + Require(fence.value == 1, "submitted compute work did not survive manager shutdown"); + Require(stoppedBeforeGpu && waiterBeforeGpu, "shutdown waited for GPU completion"); + Require(Output[0] == Value[0] && Output[1] == (queuedCpu ? 0 : Value[0]), "shutdown changed submitted or deferred compute work"); + Require(SeenByJob == 0, "shutdown published queued CPU work after GPU completion"); + for (auto offset = size; offset < allocationSize; ++offset) Require(memory[offset] == 0xa5, "compute shutdown wrote past the caller work area"); + ::operator delete(memory, std::align_val_t{256}); + } +} + +void StorageCase(bool directMemory) { + constexpr std::uint64_t mask = (1u << 1) | (1u << 5) | (1u << 8); + const MemorySizeQueryParams query{1, 2, 0, 0, 32, 8, 4, 2, mask}; + const auto size = RequiredSize(&query); + const auto allocationSize = (size + 0xffff) & ~std::uint64_t{0xffff}; + std::int64_t physical = 0; + void* memory = nullptr; + if (directMemory) { + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, allocationSize, 0x10000, 0, &physical) == 0, "direct work area allocation failed"); + Require(sceKernelMapDirectMemory(&memory, allocationSize, 0x33, 0, physical, 0x10000) == 0, "direct work area mapping failed"); + } else { + memory = ::operator new(allocationSize, std::align_val_t{256}); + } + { + const GuestArena::HostWrite destination(memory, allocationSize); + std::memset(memory, 0, allocationSize); + std::memset(static_cast(memory) + size, 0xa5, allocationSize - size); + } + alignas(64) unsigned char manager[0x100]{}; + Construct(manager); + Require(Initialize(manager, memory, size, 2, 0, 2, mask, nullptr) == 0, "storage manager initialization failed"); + const auto inside = [memory, size](const void* pointer) { + const auto base = reinterpret_cast(memory); + const auto address = reinterpret_cast(pointer); + return address >= base && address < base + size; + }; + void* implementation = nullptr; + std::memcpy(&implementation, manager, sizeof(implementation)); + Require(inside(implementation), "manager state is outside the supplied work area"); + auto* factory = Factories(manager, 0); + Require(inside(factory), "compute factory is outside the supplied work area"); + std::array sequences{}; + for (auto& sequence : sequences) { + Require(factory->CreateCompute(&sequence, 0, 16, 4, 2, nullptr) == 0, "storage sequence creation failed"); + Require(inside(sequence), "compute sequence is outside the supplied work area"); + } + for (std::uint32_t round = 0; round < 3; ++round) { + for (auto* sequence : sequences) { + Value[0] = 0x56780000u + round; + for (int dispatch = 0; dispatch < 8; ++dispatch) + Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, "batch") == 0, "batched dispatch failed"); + Require(sequence->Wait() == 0, "batched wait failed"); + Require(Output[0] == Value[0], "batched work was not visible after wait"); + Require(sequence->Wait() == 0, "empty wait after a batch failed"); + } + const auto* words = static_cast(memory); + const auto base = reinterpret_cast(memory); + std::vector labels; + for (std::size_t index = 0; index + 8 <= size / 4; ++index) { + if (words[index] != 0xc0064900u || (words[index + 2] & 0xe7000000u) != 0x22000000u) continue; + const auto address = words[index + 3] | (std::uint64_t{words[index + 4]} << 32); + Require(address >= base && address + 8 <= base + size && (address & 7) == 0, "a completion label is outside the caller's work area"); + Require(words[index + 5] == 8 * (round + 1) && words[index + 6] == 0, "a completion did not publish the batched 32-bit count"); + Require(*reinterpret_cast(address) == words[index + 5], "a published batch was not retired"); + labels.push_back(address); + } + Require(labels.size() == 2 && labels[0] != labels[1], "each queue must store one batch completion in the caller's work area"); + } + for (auto* sequence : sequences) Require(factory->DestroyCompute(sequence) == 0, "storage sequence destruction failed"); + for (auto& sequence : sequences) { + Require(factory->CreateCompute(&sequence, 0, 16, 4, 2, nullptr) == 0, "storage sequence reuse failed"); + Require(sequence->DispatchCompute(Shader.data(), Table.data(), 1, 1, 1, nullptr) == 0, "dispatch after sequence reuse failed"); + Require(factory->DestroyCompute(sequence) == 0, "destroy did not wait for reused sequence work"); + } + Require(Shutdown(manager) == 0, "storage manager shutdown failed"); + const auto* bytes = static_cast(memory); + for (auto offset = size; offset < allocationSize; ++offset) + Require(bytes[offset] == 0xa5, "the manager wrote beyond its supplied work area"); + if (directMemory) Require(sceKernelReleaseDirectMemory(physical, allocationSize) == 0, "direct work area release failed"); + else ::operator delete(memory, std::align_val_t{256}); +} + +} + +int RunJobManagerCase(const char* name) { try { + Require(std::strcmp(name, "compute") == 0 || std::strcmp(name, "size") == 0 || std::strcmp(name, "execution") == 0 || std::strcmp(name, "storage") == 0 || std::strcmp(name, "capacity") == 0 || std::strcmp(name, "deferred_inputs") == 0 || std::strcmp(name, "gpu_barriers") == 0 || std::strcmp(name, "compute_shutdown") == 0, "unknown compute fixture"); +#ifndef ANYPS5_JOB_MANAGER_REFERENCE auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; device.reset(); +#endif BuildShader(); - SizeCase(); - RunCase(); + if (std::strcmp(name, "compute") == 0 || std::strcmp(name, "size") == 0) SizeCase(); + if (std::strcmp(name, "compute") == 0 || std::strcmp(name, "execution") == 0) RunCase(); + if (std::strcmp(name, "compute") == 0 || std::strcmp(name, "capacity") == 0) CapacityCase(); + if (std::strcmp(name, "compute") == 0 || std::strcmp(name, "deferred_inputs") == 0) { + DeferredInputsCase(false); + DeferredInputsCase(true); + } + if (std::strcmp(name, "compute") == 0 || std::strcmp(name, "gpu_barriers") == 0) GpuBarriersCase(); + if (std::strcmp(name, "compute") == 0 || std::strcmp(name, "compute_shutdown") == 0) ComputeShutdownCase(); + if (std::strcmp(name, "compute") == 0 || std::strcmp(name, "storage") == 0) { + StorageCase(false); + StorageCase(true); + } AgcDriverShutdown_nid_postfix(); std::cout << "job manager compute test passed\n"; return 0; @@ -252,3 +598,10 @@ int main() { return 1; } } + +#ifndef ANYPS5_JOB_MANAGER_REFERENCE +int main(int argc, char** argv) { + if (argc > 2) return 2; + return RunJobManagerCase(argc == 2 ? argv[1] : "compute"); +} +#endif diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LazyResolvedAbi.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LazyResolvedAbi.cpp new file mode 100644 index 0000000000..aad7c87ddb --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LazyResolvedAbi.cpp @@ -0,0 +1,636 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/ShaderPreparationScope.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::uint32_t Textures = 16; +constexpr std::uint32_t VertexBuffers = 3; +constexpr std::uint32_t MaxDraws = 2048; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t ClampEdge = 2; +constexpr std::uint32_t TexelByte = 4; +constexpr std::size_t SrtOffset = 0; +constexpr std::size_t SamplerOffset = 0x200; +constexpr std::size_t FrameConstantsOffset = 0x210; +constexpr std::size_t VertexTableOffset = 0x300; +constexpr std::size_t FrameDataOffset = 0x400; +constexpr std::size_t VertexDataOffset = 0x800; +constexpr std::size_t TextureOffset = 0x1000; +constexpr std::size_t TextureBytes = 4096; +constexpr std::size_t ObjectOffset = TextureOffset + Textures * TextureBytes; +constexpr std::size_t ObjectBytes = 256; +constexpr std::size_t ColorOffset = (ObjectOffset + MaxDraws * ObjectBytes + 65535) & ~static_cast(65535); +constexpr std::size_t ColorBytes = 65536; +constexpr std::uint32_t RenderTargetTileMode = 0x1b; +constexpr std::size_t BlockBytes = (ColorOffset + ColorBytes + 65535) & ~static_cast(65535); + +alignas(256) constexpr std::array VertexCode{ + 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e060280u, 0x7e0802f2u, + 0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, + 0xbf8cc07fu, + 0xe00c2000u, 0x80010a05u, 0xe00c2000u, 0x80020e05u, 0xe00c2000u, 0x80031205u, + 0xbf8c3f70u, + 0x0602030au, 0x0604050bu, 0x0606070cu, 0x0608090du, + 0x0602030eu, 0x0604050fu, 0x06060710u, 0x06080911u, + 0x06020312u, 0x06040513u, 0x06060714u, 0x06080915u, + 0xf80008cfu, 0x04030201u, + 0xbf810000u, 0xbf810000u, +}; + +constexpr std::size_t PixelWords = 10 + Textures * 10 + 5 + 8 + 3; + +constexpr std::array MakePixelCode() { + std::array code{}; + std::size_t n = 0; + for (const auto word : {0xf4080200u, 0xfa000200u, 0xf4080300u, 0xfa000210u, 0x7e0002f0u, 0x7e0202f0u, 0x7e100280u, 0x7e120280u, 0x7e140280u, 0x7e160280u}) code[n++] = word; + for (std::uint32_t texture = 0; texture < Textures; ++texture) { + for (const auto word : {0xf40c0400u, 0xfa000000u | (texture * 32u), 0xbf8cc07fu, 0xf09c0f08u, 0x00440400u, 0xbf8c3f70u, 0x06101104u, 0x06121305u, 0x06141506u, 0x06161707u}) code[n++] = word; + } + for (const auto word : {0xf4200606u, 0xfa000000u, 0xf4200642u, 0xfa000000u, 0xbf8cc07fu}) code[n++] = word; + for (const auto word : {0x10101018u, 0x10121218u, 0x10141418u, 0x10161618u, 0x10101019u, 0x10121219u, 0x10141419u, 0x10161619u}) code[n++] = word; + for (const auto word : {0xf800180fu, 0x0b0a0908u, 0xbf810000u}) code[n++] = word; + return code; +} + +alignas(256) constexpr std::array PixelCode = MakePixelCode(); + +struct Header { + Shader shader{}; + ShaderUserData userData{}; +}; + +alignas(8) Header VertexHeader{}; +alignas(8) Header PixelHeader{}; + +void Register(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(bytes); + header.shader.code = code; + header.shader.type = type; + header.shader.user_data = &header.userData; + AgcDriverRegisterShader_nid_postfix(&header.shader); +} + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(BlockBytes, 65536)); + if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, BlockBytes, PAGE_READWRITE, BlockBytes); +#else + constexpr std::uintptr_t alignment = 65536; + void* mapped = mmap(nullptr, BlockBytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped != MAP_FAILED) { + const auto begin = reinterpret_cast(mapped); + const auto aligned = (begin + alignment - 1) & ~(alignment - 1); + if (aligned != begin) munmap(mapped, aligned - begin); + if (aligned + BlockBytes != begin + BlockBytes + alignment) munmap(reinterpret_cast(aligned + BlockBytes), begin + alignment - aligned); + block = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, BlockBytes); + } +#endif + Require(block != nullptr, "lazy resolved ABI: cannot allocate the guest block"); + std::memset(block, 0, BlockBytes); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint64_t At(std::size_t offset) const { return static_cast(reinterpret_cast(block + offset)); } + std::uint8_t* Bytes(std::size_t offset) const { return block + offset; } + +private: + std::uint8_t* block = nullptr; +}; + +struct Layout { + const GuestBlock& data; + const GuestBlock& textures; + const GuestBlock& target; +}; + +std::array BufferDescriptor(std::uint64_t address, std::uint32_t stride, std::uint32_t records, std::uint32_t word3) { + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), records, word3}; +} + +void Prepare(const Layout& layout) { + const auto& block = layout.data; + for (std::uint32_t texture = 0; texture < Textures; ++texture) { + const auto address = layout.textures.At(TextureOffset + texture * TextureBytes); + std::memset(layout.textures.Bytes(TextureOffset + texture * TextureBytes), static_cast(TexelByte), TextureBytes); + const std::array words{static_cast(address >> 8u), static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (3u << 30u), 7u << 14u, 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u}; + std::memcpy(block.Bytes(SrtOffset + texture * 32u), words.data(), sizeof(words)); + } + const std::array sampler{ClampEdge | (ClampEdge << 3u) | (ClampEdge << 6u), 0u, 0u, 0u}; + std::memcpy(block.Bytes(SamplerOffset), sampler.data(), sizeof(sampler)); + const auto frame = BufferDescriptor(block.At(FrameDataOffset), 0, 256, 0x31016facu); + std::memcpy(block.Bytes(FrameConstantsOffset), frame.data(), sizeof(frame)); + const float one = 1.0f; + std::memcpy(block.Bytes(FrameDataOffset), &one, sizeof(one)); + for (std::uint32_t buffer = 0; buffer < VertexBuffers; ++buffer) { + const auto vertices = BufferDescriptor(block.At(VertexDataOffset + buffer * 0x100u), 16, 3, 0x0104dfacu); + std::memcpy(block.Bytes(VertexTableOffset + buffer * 16u), vertices.data(), sizeof(vertices)); + } + for (std::uint32_t draw = 0; draw < MaxDraws; ++draw) std::memcpy(block.Bytes(ObjectOffset + draw * ObjectBytes), &one, sizeof(one)); +} + +void Append(std::vector& commands, std::uint32_t opcode, std::initializer_list payload) { + commands.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + commands.insert(commands.end(), payload); +} + +constexpr std::pair PrimitiveType{0x242, 4}; + +std::vector> DrawContext(const Layout& layout, std::uint32_t blend = 0) { + const auto color = layout.target.At(ColorOffset); + const float scale = 32.0f; + return { + {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, 0x9000000u | (RenderTargetTileMode << 14u)}, {0x1e0, blend}, + {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(scale)}, {0x110, std::bit_cast(scale)}, + {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}, + }; +} + +std::vector Commands(const Layout& layout, std::uint32_t draws, std::uintptr_t pixel = reinterpret_cast(PixelCode.data()), std::uint32_t blend = 0) { + const auto& block = layout.data; + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto srt = block.At(SrtOffset); + const auto table = block.At(VertexTableOffset); + const auto context = DrawContext(layout, blend); + const std::vector> shader{ + {0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, 2u << 1u}, + {0x8c, static_cast(table)}, {0x8d, static_cast(table >> 32u)}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, 8u << 1u}, + {0x00c, static_cast(srt)}, {0x00d, static_cast(srt >> 32u)}, {0x00e, 0}, {0x00f, 0}, + }; + std::vector commands; + Append(commands, 0x79, {PrimitiveType.first, PrimitiveType.second}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value}); + for (std::uint32_t draw = 0; draw < draws; ++draw) { + const auto object = BufferDescriptor(block.At(ObjectOffset + draw * ObjectBytes), 0, 16, 0x31016facu); + Append(commands, 0x76, {0x010, object[0], object[1], object[2], object[3]}); + Append(commands, 0x2d, {3, 2}); + } + return commands; +} + +double Submit(std::vector& commands) { + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + const auto start = std::chrono::steady_clock::now(); + Require(sceAgcDriverSubmitDcb(&packet) == 0, "lazy resolved ABI: the draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); + return std::chrono::duration(std::chrono::steady_clock::now() - start).count(); +} + +unsigned ColorByte(const GuestBlock& block) { + AgcDriver::GuestMemory::FlushGpuWrites(static_cast(block.At(ColorOffset)), ColorBytes); + unsigned brightest = 0; + for (std::size_t i = 0; i < ColorBytes; ++i) brightest = std::max(brightest, static_cast(block.Bytes(ColorOffset)[i])); + return brightest; +} + +void Check(const Layout& layout) { + std::memset(layout.target.Bytes(ColorOffset), 0, ColorBytes); + auto commands = Commands(layout, 16); + Submit(commands); + const auto byte = ColorByte(layout.target); + Require(byte == Textures * TexelByte, "lazy resolved ABI: 16 draws sampling 16 textures of byte " + std::to_string(TexelByte) + " wrote " + std::to_string(byte) + ", expected " + std::to_string(Textures * TexelByte)); +} + + +std::vector SyntheticPixelCode(std::uint32_t index, std::uint32_t blocks) { + std::vector code; + code.reserve(PixelCode.size() + 2 + blocks * 8); + code.push_back(0x7e1802ffu); + code.push_back(0x3f800000u + index); + for (const auto word : PixelCode) { + if (word == 0xf800180fu) { + for (std::uint32_t block = 0; block < blocks; ++block) { + for (const auto multiply : {0x10101018u, 0x10121218u, 0x10141418u, 0x10161618u, 0x10101019u, 0x10121219u, 0x10141419u, 0x10161619u}) code.push_back(multiply); + } + } + code.push_back(word); + } + return code; +} + +struct SyntheticHeader { + Shader shader{}; + ShaderUserData userData{}; + std::array registers{}; + std::array context{{{0x1b6, 0}}}; +}; + +struct Synthetic { + SyntheticHeader header; + std::uint32_t* code = nullptr; +}; + +class ProgramArena { +public: + explicit ProgramArena(std::size_t size) : bytes((size + 65535u) & ~std::size_t{65535}) { +#ifdef _WIN32 + base = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, 65536)); + if (base != nullptr) GuestArena::GuestArenaCommit_nid_postfix(base, bytes, PAGE_READWRITE, bytes); +#else + void* mapped = mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + base = mapped != MAP_FAILED ? static_cast(mapped) : nullptr; +#endif + Require(base != nullptr, "lazy resolved ABI: cannot allocate the program arena"); + GuestAllocations::Mutation().Add(base, bytes, true, true, true); + } + ProgramArena(const ProgramArena&) = delete; + ProgramArena& operator=(const ProgramArena&) = delete; + std::uint32_t* Place(std::span code) { + const auto aligned = (used + 255u) & ~std::size_t{255}; + Require(aligned + code.size_bytes() <= bytes, "lazy resolved ABI: the program arena is full"); + auto* destination = reinterpret_cast(base + aligned); + std::memcpy(destination, code.data(), code.size_bytes()); + used = aligned + code.size_bytes(); + return destination; + } + +private: + std::size_t bytes; + std::size_t used = 0; + std::uint8_t* base = nullptr; +}; + +void RegisterHeader(Synthetic& program, std::uint32_t* code, std::size_t words) { + program.code = code; + const auto address = reinterpret_cast(program.code); + auto& header = program.header; + header.registers = {{{0x008, static_cast(address >> 8u)}, {0x009, static_cast(address >> 40u)}, {0x00b, 8u << 1u}, {0x00a, 0}}}; + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(SyntheticHeader); + header.shader.shader_size = static_cast(words * sizeof(std::uint32_t)); + header.shader.code = program.code; + header.shader.type = 1; + header.shader.user_data = &header.userData; + header.shader.sh_registers = header.registers.data(); + header.shader.num_sh_registers = static_cast(header.registers.size()); + header.shader.cx_registers = header.context.data(); + header.shader.num_cx_registers = static_cast(header.context.size()); + AgcDriverRegisterShader_nid_postfix(&header.shader); +} + +void RegisterSynthetic(Synthetic& program, ProgramArena& arena, const std::vector& code) { + RegisterHeader(program, arena.Place(code), code.size()); +} + +constexpr std::uint32_t Programs = 4; +constexpr std::uint32_t Drawn = 2; + +void SetEnvironment(const char* name, const std::string& value) { +#ifdef _WIN32 + _putenv_s(name, value.c_str()); +#else + setenv(name, value.c_str(), 1); +#endif +} + +void ClearEnvironment(const char* name) { +#ifdef _WIN32 + _putenv_s(name, ""); +#else + unsetenv(name); +#endif +} + +void Settle(std::span programs) { + std::vector registered; + for (const auto& program : programs) registered.push_back(&program.header.shader); + AgcDriverEndShaderPreparation_nid_postfix(AgcDriverBeginShaderPreparation_nid_postfix(registered.data(), registered.size())); + AgcDriverWaitIdle_nid_postfix(); +} + +std::vector Draw(const Layout& layout, const Synthetic& program) { + std::memset(layout.target.Bytes(ColorOffset), 0, ColorBytes); + auto commands = Commands(layout, 1, reinterpret_cast(program.code)); + Submit(commands); + AgcDriver::GuestMemory::FlushGpuWrites(static_cast(layout.target.At(ColorOffset)), ColorBytes); + return {layout.target.Bytes(ColorOffset), layout.target.Bytes(ColorOffset) + ColorBytes}; +} + +std::size_t Entries(const Synthetic& program) { + return AgcDriver::DriverDetail::Driver::Get().PreparedEntries(&program.header.shader); +} + +std::string Uses(const AgcDriver::DriverDetail::Driver::DeferredAbiUseCounts& uses) { + return std::to_string(uses.firstUses) + " first uses, " + std::to_string(uses.found) + " found, " + std::to_string(uses.own) + " own, " + std::to_string(uses.unmatchedEntries) + " of " + std::to_string(uses.entries) + " entries unmatched"; +} + +constexpr std::uint32_t Matching = Programs; +constexpr std::uint32_t Twice = Programs + 1; +constexpr std::uint32_t Replaced = Programs + 2; +constexpr std::uint32_t Replacement = Programs + 3; +constexpr std::array DrawnPrograms{0, 1, Matching, Twice, Replaced, Replacement}; + +bool RegistrationAbi(std::uint32_t index) { + return index < Programs || index == Twice; +} + +using Report = std::map; + +std::string Text(std::span> hashes) { + std::string text = std::to_string(hashes.size()); + char word[40]; + for (const auto& hash : hashes) { + std::snprintf(word, sizeof(word), " %016llx%016llx", static_cast(hash[1]), static_cast(hash[0])); + text += word; + } + return text; +} + +std::string Text(const std::array& libraries) { + std::string text; + for (const auto count : libraries) text += (text.empty() ? "" : " ") + std::to_string(count); + return text; +} + +std::size_t Count(const std::string& text) { + return static_cast(std::stoull(text.substr(0, text.find(' ')))); +} + +std::vector Prints(const std::string& text) { + std::vector prints; + std::istringstream stream(text); + std::string word; + stream >> word; + while (stream >> word) prints.push_back(word); + std::ranges::sort(prints); + return prints; +} + +std::string Joined(std::span prints) { + std::string text = std::to_string(prints.size()); + for (const auto& print : prints) text += " " + print; + return text; +} + +std::vector EagerResolutionEntries(const Report& eager, std::uint32_t index) { + const auto id = std::to_string(index); + const auto registered = Prints(eager.at("registered " + id)); + const auto drawn = Prints(eager.at("drawn " + id)); + if (RegistrationAbi(index)) return drawn; + std::vector made; + std::ranges::set_difference(drawn, registered, std::back_inserter(made)); + return made; +} + +Report Scenario(const Layout& layout, std::vector& first, std::array* uses = nullptr) { + auto& driver = AgcDriver::DriverDetail::Driver::Get(); + Report report; + report["libraries before"] = Text(driver.PrebuiltLibraries()); + ProgramArena arena(1u << 20u); + std::vector programs(Replacement + 1); + const auto record = [&](const char* phase, std::uint32_t index) { + report[std::string(phase) + " " + std::to_string(index)] = Text(driver.PreparedEntryHashes(&programs[index].header.shader)); + }; + const auto resolved = std::span(programs).first(Replaced + 1); + for (std::uint32_t index = 0; index <= Replaced; ++index) RegisterSynthetic(programs[index], arena, SyntheticPixelCode(index + 1, 0)); + Settle(resolved); + for (std::uint32_t index = 0; index <= Replaced; ++index) record("registered", index); + std::vector context; + for (const auto& [offset, value] : DrawContext(layout)) context.push_back({offset, value}); + std::array primitive{{{PrimitiveType.first, PrimitiveType.second}}}; + for (std::uint32_t index = 0; index < Programs; ++index) AgcDriverResolveShaderAbi_nid_postfix(&programs[index].header.shader, {}, {}); + AgcDriverResolveShaderAbi_nid_postfix(&programs[Matching].header.shader, context, primitive); + AgcDriverResolveShaderAbi_nid_postfix(&programs[Twice].header.shader, {}, {}); + AgcDriverResolveShaderAbi_nid_postfix(&programs[Twice].header.shader, context, primitive); + AgcDriverResolveShaderAbi_nid_postfix(&programs[Replaced].header.shader, context, primitive); + for (auto& reg : context) reg.value = ~reg.value; + primitive.front().value = ~primitive.front().value; + Settle(resolved); + for (std::uint32_t index = 0; index <= Replaced; ++index) record("resolved", index); + report["libraries resolved"] = Text(driver.PrebuiltLibraries()); + RegisterHeader(programs[Replacement], programs[Replaced].code, programs[Replaced].header.shader.shader_size / sizeof(std::uint32_t)); + Settle(std::span(programs).subspan(Replacement, 1)); + record("registered", Replacement); + const auto draw = [&](std::uint32_t index, const Synthetic& program) { + auto bytes = Draw(layout, program); + Require(std::ranges::any_of(bytes, [](std::uint8_t byte) { return byte != 0; }), "lazy resolved ABI: drawn shader " + std::to_string(index) + " wrote nothing"); + if (index == 0) first = std::move(bytes); + }; + const auto count = [&](std::size_t slot) { + if (uses != nullptr) (*uses)[slot] = AgcDriver::DriverDetail::Driver::DeferredAbiUses(); + }; + count(0); + for (const auto index : {0u, 1u}) draw(index, programs[index]); + count(1); + draw(Matching, programs[Matching]); + count(2); + draw(Twice, programs[Twice]); + draw(Replacement, programs[Replacement]); + AgcDriverRegisterShader_nid_postfix(&programs[Replaced].header.shader); + draw(Replaced, programs[Replaced]); + for (std::uint32_t index = 0; index <= Replacement; ++index) record("drawn", index); + report["libraries drawn"] = Text(driver.PrebuiltLibraries()); + return report; +} + +std::filesystem::path ReportPath(const std::filesystem::path& reference) { + auto path = reference; + path += ".report"; + return path; +} + +std::set CacheFiles(const std::filesystem::path& cache) { + std::set files; + for (const auto& entry : std::filesystem::recursive_directory_iterator(cache)) { + if (entry.is_regular_file() && entry.path().filename().string().rfind("vk-pipelines-", 0) != 0) files.insert(std::filesystem::relative(entry.path(), cache).generic_string()); + } + return files; +} + +void EagerReference(const Layout& layout, const std::filesystem::path& output) { + std::vector bytes; + const auto report = Scenario(layout, bytes); + for (std::uint32_t index = 0; index <= Replaced; ++index) Require(Count(report.at("resolved " + std::to_string(index))) != 0, "lazy resolved ABI: eager preparation left resolved shader " + std::to_string(index) + " unprepared"); + std::ofstream file(output, std::ios::binary | std::ios::trunc); + file.write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); + Require(file.good(), "lazy resolved ABI: cannot write the eager reference"); + std::ofstream text(ReportPath(output), std::ios::trunc); + for (const auto& [key, value] : report) text << key << '=' << value << '\n'; + Require(text.good(), "lazy resolved ABI: cannot write the eager report"); +} + +void LazyResolution(const Layout& layout, const std::filesystem::path& reference, const std::set& cached, const std::filesystem::path& cache) { + std::vector expected(ColorBytes); + { + std::ifstream file(reference, std::ios::binary); + file.read(reinterpret_cast(expected.data()), static_cast(expected.size())); + Require(file.gcount() == static_cast(expected.size()), "lazy resolved ABI: the eager reference is missing or short"); + } + Report eager; + { + std::ifstream text(ReportPath(reference)); + for (std::string line; std::getline(text, line);) { + const auto split = line.find('='); + if (split != std::string::npos) eager[line.substr(0, split)] = line.substr(split + 1); + } + Require(eager.contains("libraries drawn"), "lazy resolved ABI: the eager report is missing or short"); + } + std::vector first; + std::array uses{}; + const auto lazy = Scenario(layout, first, &uses); + for (std::uint32_t index = 0; index <= Replaced; ++index) { + const auto key = "resolved " + std::to_string(index); + Require(Count(lazy.at(key)) == 0, "lazy resolved ABI: ResolveShaderAbi prepared " + std::to_string(Count(lazy.at(key))) + " entries for shader " + std::to_string(index) + " under APS5_LAZY_SHADER_PREPARE"); + } + Require(lazy.at("libraries resolved") == lazy.at("libraries before"), "lazy resolved ABI: ResolveShaderAbi built pipeline libraries under APS5_LAZY_SHADER_PREPARE (" + lazy.at("libraries before") + " before, " + lazy.at("libraries resolved") + " after)"); + for (std::uint32_t index = 0; index <= Replacement; ++index) Require(Count(lazy.at("registered " + std::to_string(index))) == 0, "lazy resolved ABI: registration prepared shader " + std::to_string(index) + " under APS5_LAZY_SHADER_PREPARE"); + for (const auto index : DrawnPrograms) { + const auto expected = EagerResolutionEntries(eager, index); + const auto held = Prints(lazy.at("drawn " + std::to_string(index))); + Require(held == expected, "lazy resolved ABI: drawn shader " + std::to_string(index) + " holds other prepared entries than eager resolution and its draw made (eager " + Joined(expected) + "; lazy " + Joined(held) + ")"); + } + for (std::uint32_t index = Drawn; index < Programs; ++index) { + const auto key = "drawn " + std::to_string(index); + Require(Count(lazy.at(key)) == 0, "lazy resolved ABI: shader " + std::to_string(index) + ", which never drew, holds " + std::to_string(Count(lazy.at(key))) + " prepared entries"); + } + const auto& [start, unmatched, matched] = uses; + Require(unmatched.firstUses - start.firstUses == Drawn && unmatched.own - start.own == Drawn && unmatched.found == start.found, "lazy resolved ABI: draws whose state differs from the resolved ABI did not count as preparing their own artifact (before: " + Uses(start) + "; after: " + Uses(unmatched) + ")"); + Require(unmatched.entries > start.entries && unmatched.unmatchedEntries - start.unmatchedEntries == unmatched.entries - start.entries, "lazy resolved ABI: entries prepared for an ABI no draw used do not count as unmatched (before: " + Uses(start) + "; after: " + Uses(unmatched) + ")"); + Require(matched.firstUses == unmatched.firstUses + 1 && matched.found == unmatched.found + 1 && matched.own == unmatched.own, "lazy resolved ABI: a draw whose state matches the resolved ABI did not count as finding its artifact (before: " + Uses(unmatched) + "; after: " + Uses(matched) + ")"); + Require(matched.entries > unmatched.entries && matched.unmatchedEntries == unmatched.unmatchedEntries + (matched.entries - unmatched.entries) - 1, "lazy resolved ABI: the entry a matching draw used still counts as unmatched (before: " + Uses(unmatched) + "; after: " + Uses(matched) + ")"); + for (const auto index : {Matching, Twice, Replaced}) Require(eager.at("drawn " + std::to_string(index)) == eager.at("resolved " + std::to_string(index)), "lazy resolved ABI: shader " + std::to_string(index) + ", drawn with its resolved ABI's state, did not find the ABI's entries with eager preparation"); + for (std::uint32_t index = 0; index < Drawn; ++index) Require(Count(eager.at("drawn " + std::to_string(index))) == Count(eager.at("resolved " + std::to_string(index))) + 1, "lazy resolved ABI: a draw whose state differs from its resolved ABI did not prepare its own entry with eager preparation"); + Require(Count(eager.at("drawn " + std::to_string(Replacement))) == Count(eager.at("registered " + std::to_string(Replacement))) + 1, "lazy resolved ABI: the header registered over a resolved one did not prepare its own entry at its draw with eager preparation"); + Require(lazy.at("libraries drawn") == eager.at("libraries drawn"), "lazy resolved ABI: the pipeline libraries built differ from eager preparation (eager " + eager.at("libraries drawn") + "; lazy " + lazy.at("libraries drawn") + ")"); + Require(first == expected, "lazy resolved ABI: the shader prepared at its first draw drew other bytes than with eager preparation"); + AgcDriverWaitIdle_nid_postfix(); + const auto files = CacheFiles(cache); + std::vector added; + std::ranges::set_difference(files, cached, std::back_inserter(added)); + Require(added.empty(), "lazy resolved ABI: preparation at first use wrote " + std::to_string(added.size()) + " shader cache files eager preparation had not, the first " + (added.empty() ? std::string() : added.front())); + std::printf("lazy resolved ABI: %u shaders drew, %u of them ABI-resolved; the %u resolved ones that never drew hold no prepared entries; the drawn ones hold the entries eager resolution and their draws made: drawn with their ABI's state they found them, with the ABI register arrays overwritten after resolution (one resolved with two ABIs, one whose header was registered over and again before its draw), drawn with another state they prepared their own; the pipeline libraries built match eager's (%s), a drawn one drew the same %zu bytes, and the lazy run wrote none of the %zu shader cache files eager wrote again\n", static_cast(DrawnPrograms.size()), static_cast(DrawnPrograms.size() - 1), Programs - Drawn, lazy.at("libraries drawn").c_str(), first.size(), cached.size()); +} + +} + +int main(int argc, char** argv) { + const bool eager = argc == 2 && std::string(argv[1]) == "--eager-reference"; + std::filesystem::path reference; + std::filesystem::path cache; + std::set cached; + const auto clean = [&] { + if (eager) return; + std::error_code ignored; + if (!reference.empty()) { + std::filesystem::remove(reference, ignored); + std::filesystem::remove(ReportPath(reference), ignored); + } + if (!cache.empty()) std::filesystem::remove_all(cache, ignored); + }; + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + if (eager) { + const char* path = std::getenv("LAZY_RESOLVED_ABI_REFERENCE"); + Require(path != nullptr, "lazy resolved ABI: the eager child has no reference path"); + reference = path; + } else { + const auto run = std::to_string(std::random_device{}()); + reference = std::filesystem::temp_directory_path() / ("aps5-lazy-resolved-abi-" + run + ".bin"); + cache = std::filesystem::temp_directory_path() / ("aps5-lazy-resolved-abi-cache-" + run); + std::filesystem::create_directories(cache); + SetEnvironment("LAZY_RESOLVED_ABI_REFERENCE", reference.string()); + SetEnvironment("ANYPS5_NO_SHADER_CACHE", "0"); + SetEnvironment("ANYPS5_SHADER_CACHE_DIR", cache.string()); + SetEnvironment("APS5_NO_PIPELINE_JOURNAL", "1"); + ClearEnvironment("APS5_LAZY_SHADER_PREPARE"); + const std::string command = "\"" + std::string(argv[0]) + "\" --eager-reference"; + Require(std::system(command.c_str()) == 0, "lazy resolved ABI: the eager reference process failed"); + cached = CacheFiles(cache); + Require(!cached.empty(), "lazy resolved ABI: the eager reference process wrote no shader cache files"); + SetEnvironment("APS5_LAZY_SHADER_PREPARE", "1"); + } + Register(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Register(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + GuestBlock data; + GuestBlock textures; + GuestBlock target; + const Layout layout{data, textures, target}; + Prepare(layout); + Check(layout); + if (eager) EagerReference(layout, reference); + else LazyResolution(layout, reference, cached, cache); + AgcDriverShutdown_nid_postfix(); + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { + AgcDriverShutdown_nid_postfix(); + } catch (...) { + } + clean(); + return 1; + } + clean(); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LdsMixedWidthAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LdsMixedWidthAtomics.cpp index 1f8032e93c..250f80264a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/LdsMixedWidthAtomics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LdsMixedWidthAtomics.cpp @@ -325,8 +325,9 @@ void Run(AgcDriver::VulkanDevice& device, std::span code, c if (expectedDeviceDwords) std::printf("lds lock budget: %s GPU readback verified across 256 lanes\n", name); } -void Misaligned(AgcDriver::VulkanDevice& device, std::uint32_t ldsDwords) { +void Misaligned(AgcDriver::VulkanDevice& device, std::uint32_t ldsDwords, const char* lds) { const auto result = Compile(device, MisalignedCode, ldsDwords); + Output.fill(0xdeadbeefu); StderrCapture capture; try { device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(MisalignedCode.data())); @@ -336,8 +337,9 @@ void Misaligned(AgcDriver::VulkanDevice& device, std::uint32_t ldsDwords) { throw; } const auto log = capture.Finish(); - Require(log.find("BDA access failed: address=0x4 instruction=0x2c bytes=8 ") != std::string::npos && log.find("reason=7") != std::string::npos, "lds lock budget: misaligned ds_add_u64 on device-backed LDS did not fault"); - std::puts("lds lock budget: misaligned ds_add_u64 faulted"); + Require(log.find("BDA access failed: address=0x4 instruction=0x2c bytes=8 ") != std::string::npos && log.find("reason=7") != std::string::npos, std::string("lds lock budget: misaligned ds_add_u64 on ") + lds + " LDS did not fault"); + for (std::uint32_t tid = 0; tid < Threads; ++tid) Require(Output[tid * 2u] == 0u && Output[tid * 2u + 1u] == 0u, std::string("lds lock budget: misaligned ds_add_u64 on ") + lds + " LDS changed the slot: lane " + std::to_string(tid) + " read " + Hex(Output[tid * 2u] | (static_cast(Output[tid * 2u + 1u]) << 32u))); + std::printf("lds lock budget: misaligned ds_add_u64 on %s LDS faulted\n", lds); } void LockBudget(AgcDriver::VulkanDevice& device) { @@ -349,7 +351,8 @@ void LockBudget(AgcDriver::VulkanDevice& device) { const auto operations = static_cast(Threads) * Iterations; Run(device, UniformCode, "limit", operations, 1, limit / 4u, (limit / 4u + 2u) & ~1u); const auto& capabilities = target.supportedCapabilities; - if (std::find(capabilities.begin(), capabilities.end(), static_cast(spv::CapabilityInt64Atomics)) != capabilities.end()) Misaligned(device, limit / 4u); + if (std::find(capabilities.begin(), capabilities.end(), static_cast(spv::CapabilityInt64Atomics)) != capabilities.end()) Misaligned(device, limit / 4u, "device-backed"); + Misaligned(device, LdsDwords, "workgroup"); Run(device, UniformCode, "limit-4", operations, 1, (limit - 4u) / 4u, 0u); std::puts("lds lock budget tests passed"); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LdsSubwordStoreBranch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LdsSubwordStoreBranch.cpp new file mode 100644 index 0000000000..ae9d55e2c0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LdsSubwordStoreBranch.cpp @@ -0,0 +1,92 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 128; +constexpr std::uint32_t BranchThreads = 64; +constexpr std::uint32_t Iterations = 0x1000; +constexpr std::uint32_t Rounds = 16; +constexpr std::uint32_t LdsDwords = 512; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array BranchCode{ + 0x34040082, 0xe0301000, 0x80000102, 0xbf8c3f70, 0xbe86037e, 0x7da800c0, 0xbf880002, 0xd87c0400, + 0x00000102, 0xbefe0306, 0x7e0e0500, 0xbf0ac007, 0x850580ff, 0x00001000, 0x7e0c0300, 0xbf068005, + 0xbf850005, 0xd5430006, 0x02050706, 0x80858105, 0xbf078005, 0xbf85fffb, 0xe0701200, 0x80000602, + 0xd8340000, 0x00000102, 0xbf8cc07f, 0xbf8a0000, 0x3a0604ff, 0x00000100, 0xd8d80000, 0x04000003, + 0xbf8cc07f, 0xe0701000, 0x80000402, 0xbf810000, +}; + +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::uint32_t BranchResult(std::uint32_t thread) { + std::uint32_t value = thread; + for (std::uint32_t iteration = 0; iteration < Iterations; ++iteration) value = (value & 0xffffffu) * 3u + 1u; + return value; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto target = device->ComputeTarget(32); + if (target.subgroupSize != 32u) { + std::printf("skipped, wave32 programs do not run on 32-lane subgroups (%u lanes)\n", target.subgroupSize); + return VulkanTestSkipped; + } + std::vector userData(4, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin()); + const std::span code(BranchCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, LdsDwords, {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}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + std::array input{}; + for (std::uint32_t round = 0; round < Rounds; ++round) { + for (std::uint32_t thread = 0; thread < Threads; ++thread) input[thread] = (round + 1u) * 0x10000u + thread; + Output.fill(0xdeadbeefu); + std::copy(input.begin(), input.end(), Output.begin()); + device->Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device->WaitIdle(); + for (std::uint32_t thread = 0; thread < Threads; ++thread) { + const auto expected = input[thread ^ BranchThreads]; + Require(Output[thread] == expected, "LDS exchange after a 16-bit LDS store in a branch that waves 0-1 take, round " + std::to_string(round) + ": thread " + std::to_string(thread) + " (wave " + std::to_string(thread / 32u) + ") read " + std::to_string(Output[thread]) + ", expected " + std::to_string(expected)); + } + for (std::uint32_t thread = 0; thread < Threads; ++thread) { + const auto expected = thread < BranchThreads ? BranchResult(thread) : thread; + Require(Output[Threads + thread] == expected, "loop result of thread " + std::to_string(thread) + " in round " + std::to_string(round)); + } + } + std::puts("LDS subword store 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/LinearReadbackLabel.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearReadbackLabel.cpp new file mode 100644 index 0000000000..36105a9e93 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearReadbackLabel.cpp @@ -0,0 +1,238 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Width = 32; +constexpr std::uint32_t Height = 8; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t Format8888Uint = 60; +constexpr std::uint32_t TexelBytes = 4; +constexpr std::uint32_t IdentitySwizzle = 0xfacu; +constexpr std::size_t BlockBytes = 0x20000; +constexpr std::size_t LabelOffset = 0x8000; +constexpr std::uint32_t LabelValue = 0x5eedf00du; +constexpr std::uint32_t TiledThreads = 8; +constexpr std::uint32_t TiledSide = 16; +constexpr std::uint32_t Format32Uint = 20; +constexpr std::uint32_t TileR64KBX = 0x1b; +constexpr std::size_t TiledOffset = 0x10000; +constexpr std::size_t TiledLabelOffset = 0x8100; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::uint32_t Written = 0x12345678u; + +alignas(256) constexpr std::array StoreCode{ + 0x4a500081, 0xd5690014, 0x0201ff28, 0x9e3779b1, 0x4a2a28ff, 0x7f4a7c15, 0x4a2c28ff, 0xfe94f82a, + 0x4a2e28ff, 0x7ddf743f, 0x7e3c0300, 0x7e3e0280, 0xf0281f08, 0x0001141e, 0x7e3e0281, 0xf0281508, + 0x0001141e, 0x7e3e0282, 0xf0281a08, 0x0001141e, 0x7e3e0283, 0xf0281808, 0x0001141e, 0xbf810000, +}; + +alignas(256) constexpr std::array TiledStoreCode{ + 0x7e040300, 0x7e060280, 0x7e0202ff, Written, 0xf0200108, 0x00020102, 0xbf810000, +}; + +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, "linear readback label: cannot allocate the guest block"); + std::memset(block, 0, BlockBytes); + 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; +}; + +std::uint32_t Data(std::uint32_t tid, std::uint32_t index) { + return (tid + 1u) * 0x9e3779b1u + index * 0x7f4a7c15u; +} + +AgcDriver::Graphics::TileMipLayout Layout() { + return AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888Uint, Width, Height, 1).front(); +} + +std::vector Expected(const std::vector& initial) { + const auto mip = Layout(); + auto bytes = initial; + constexpr std::array Dmasks{0xfu, 0x5u, 0xau, 0x8u}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t row = 0; row < Dmasks.size(); ++row) { + std::array words{}; + std::uint32_t next = 0; + for (std::uint32_t word = 0; word < 4u; ++word) { + if (((Dmasks[row] >> word) & 1u) != 0u) words[word] = Data(tid, next++); + } + std::memcpy(bytes.data() + mip.tiledOffset + static_cast(row) * mip.pitchBytes + static_cast(tid) * TexelBytes, words.data(), TexelBytes); + } + } + return bytes; +} + +std::array UserData(const void* texels) { + const auto address = static_cast(reinterpret_cast(texels)); + std::array data{}; + data[4] = static_cast(address >> 8u); + data[5] = static_cast((address >> 40u) & 0xffu) | (Format8888Uint << 20u) | (((Width - 1u) & 3u) << 30u); + data[6] = ((Width - 1u) >> 2u) | ((Height - 1u) << 14u); + data[7] = IdentitySwizzle | (Type2D << 28u); + return data; +} + +std::array TiledDescriptor(const void* texels) { + const auto address = static_cast(reinterpret_cast(texels)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32Uint << 20u) | (((TiledSide - 1u) & 3u) << 30u), + ((TiledSide - 1u) >> 2u) | ((TiledSide - 1u) << 14u), + IdentitySwizzle | (TileR64KBX << 20u) | (Type2D << 28u), + 0u, + 0u, + 0u, + 0u, + }; +} + +std::array TiledUserData(const void* texels) { + std::array data{}; + const auto texture = TiledDescriptor(texels); + std::copy(texture.begin(), texture.end(), data.begin() + 8); + return data; +} + +std::vector Commands(std::span code, std::uint32_t threads, std::span userData, const void* label) { + std::vector words; + const auto registers = [&](std::uint32_t first, std::span values) { + words.push_back(0xc0007600u | (static_cast(values.size()) << 16u)); + words.push_back(first); + words.insert(words.end(), values.begin(), values.end()); + }; + const std::array shape{threads, 1, 1}; + registers(0x207, shape); + const auto address = reinterpret_cast(code.data()); + const std::array program{static_cast(address >> 8u), static_cast(address >> 40u)}; + registers(0x20c, program); + const std::array resource{static_cast(userData.size()) << 1u}; + registers(0x213, resource); + registers(0x240, userData); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x41u | 0x8000u}); + const auto at = reinterpret_cast(label); + words.insert(words.end(), {0xc0064900u, 0x514u, (1u << 29u) | (2u << 24u), static_cast(at), static_cast(at >> 32u), LabelValue, 0, 0}); + return words; +} + +void SubmitAndWait(std::vector words, std::uint32_t* label, const char* what) { + *label = 0; + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + AgcDriver::Submit(&packet, 0); + const auto start = std::chrono::steady_clock::now(); + while (std::atomic_ref(*label).load(std::memory_order_acquire) != LabelValue) { + Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), std::string(what) + ": the label after the dispatch never landed"); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } +} + +void ReadAfterLabel(std::uint8_t* block) { + auto* texels = block; + auto* label = reinterpret_cast(block + LabelOffset); + const auto mip = Layout(); + const auto bytes = static_cast(mip.tiledOffset + mip.pitchBytes * Height); + std::vector initial(bytes); + for (std::size_t index = 0; index < initial.size(); ++index) initial[index] = static_cast(0x80u + 37u * index + 11u * (index >> 4u)); + std::memcpy(texels, initial.data(), initial.size()); + const auto userData = UserData(texels); + SubmitAndWait(Commands(StoreCode, Threads, userData, label), label, "linear readback label"); + const auto expected = Expected(initial); + for (std::uint32_t row = 0; row < Height; ++row) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto offset = mip.tiledOffset + static_cast(row) * mip.pitchBytes + static_cast(x) * TexelBytes; + std::uint32_t actual = 0; + std::uint32_t wanted = 0; + std::memcpy(&actual, texels + offset, TexelBytes); + std::memcpy(&wanted, expected.data() + offset, TexelBytes); + Require(actual == wanted, "linear readback label: texel (" + std::to_string(x) + ", " + std::to_string(row) + ") read after the label is 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%08x", actual); return std::string(text); }() + ", the dispatch before the label stored a different value"); + } + } +} + +void ReadTiledAfterLabel(std::uint8_t* block) { + auto* texels = reinterpret_cast(block + TiledOffset); + auto* label = reinterpret_cast(block + TiledLabelOffset); + const auto bytes = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TiledDescriptor(texels))).guestBytes; + Require(bytes <= AgcDriver::Graphics::StorageTexture::SmallStoreBytes && bytes <= BlockBytes - TiledOffset, "tiled readback label: the test image is larger than a small image"); + std::fill(texels, texels + bytes / 4u, Untouched); + const auto userData = TiledUserData(texels); + SubmitAndWait(Commands(TiledStoreCode, TiledThreads, userData, label), label, "tiled readback label"); + const volatile std::uint32_t* cpu = texels; + std::uint32_t written = 0; + for (std::size_t index = 0; index < bytes / 4u; ++index) written += cpu[index] == Written ? 1u : 0u; + Require(written == TiledThreads, "tiled readback label: " + std::to_string(written) + " texels of a small tiled image read after the label hold the value the dispatch before it stored, expected " + std::to_string(TiledThreads)); +} + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + GuestBlock block; + ReadAfterLabel(block.Data()); + ReadTiledAfterLabel(block.Data()); + AgcDriverWaitIdle_nid_postfix(); + AgcDriverShutdown_nid_postfix(); + std::puts("linear readback label tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + AgcDriverShutdown_nid_postfix(); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCompletionPoints.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCompletionPoints.cpp new file mode 100644 index 0000000000..8c8bd50ef9 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCompletionPoints.cpp @@ -0,0 +1,395 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" +#include "prx/libSceAgcDriver/Eq/include/Event.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "prx/libkernel/Equeue/Equeue.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 AgcDriver::Graphics::Require; + +constexpr std::uint32_t Width = 256; +constexpr std::uint32_t Height = 128; +constexpr std::size_t Pitch = std::size_t{Width} * 4u; +constexpr std::size_t TargetBytes = Pitch * Height; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t LabelOffset = 3 * BlockBytes; +constexpr std::uint32_t Untouched = 0x5a5a5a5au; +constexpr std::uint32_t Drawn = 0xff0000ffu; +constexpr std::uint32_t LabelValue = 0x600dcafeu; +constexpr std::uint32_t OutputHandle = 9; +constexpr std::uint32_t Rgba8UnormInfo = 0x28028u; +constexpr std::uint32_t LinearAttrib3 = 0x09000000u; +constexpr std::array Scissor{32, 16, 160, 80}; + +alignas(256) constexpr std::array VertexCode{ + 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e060208u, 0x7e0802f2u, 0x7e0a02ffu, 0x44800000u, 0xf80000d1u, 0x00000005u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u, +}; +alignas(256) constexpr std::array PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u}; +alignas(256) constexpr std::array FillCode{ + 0xd7460004, 0x04010c08, 0x7e000204, 0x7e020205, 0x7e040206, 0x7e060207, 0xe01c2000, 0x80000004, 0xbf810000, +}; + +alignas(4) std::atomic Gate = 0; + +struct Header { + Shader shader{}; + ShaderUserData userData{}; +}; + +alignas(8) Header VertexHeader{}; +alignas(8) Header PixelHeader{}; + +void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(bytes); + header.shader.code = code; + header.shader.type = type; + header.shader.user_data = &header.userData; + AgcDriverRegisterShader_nid_postfix(&header.shader); +} + +void Append(std::vector& commands, std::uint32_t opcode, std::initializer_list payload) { + commands.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + commands.insert(commands.end(), payload); +} + +void AppendDraw(std::vector& commands, std::uint64_t target) { + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto pixel = reinterpret_cast(PixelCode.data()); + const auto extent = (Height << 16u) | Width; + const std::vector> context{ + {0x2d5, 0x2000}, + {0x1b6, 0}, {0x207, 0x01200000}, {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, Rgba8UnormInfo}, + {0x31b, 0}, {0x31d, 0}, {0x3b0, ((Width - 1u) << 14u) | (Height - 1u)}, + {0x3b8, LinearAttrib3}, {0x1e0, 0}, + {0xc, 0}, {0xd, extent}, + {0x81, 0x80000000}, {0x82, extent}, + {0x90, 0x80000000u | (Scissor[1] << 16u) | Scissor[0]}, {0x91, (Scissor[3] << 16u) | Scissor[2]}, + {0x94, 0x80000000}, {0x95, extent}, + {0x10f, std::bit_cast(2.0f * Width)}, {0x110, std::bit_cast(2.0f * Width)}, + {0x111, std::bit_cast(2.0f * Height)}, {0x112, std::bit_cast(2.0f * Height)}, + {0x113, std::bit_cast(1.0f)}, {0x114, 0}, + {0xb4, 0}, {0xb5, std::bit_cast(1.0f)}, + {0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0}, + {0x318, static_cast(target >> 8u)}, {0x390, static_cast(target >> 40u)}, + }; + const std::vector> shader{ + {0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, 1u << 1u}, {0x8c, 0}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast(1.0f)}, + }; + Append(commands, 0x79, {0x242, 4}); + Append(commands, 0x79, {0x24a, 0}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value}); + Append(commands, 0x2d, {3, 2}); +} + +void AppendGate(std::vector& commands) { + const auto gate = reinterpret_cast(&Gate); + commands.insert(commands.end(), {0xc0053c00u, 0x13u, static_cast(gate), static_cast(gate >> 32u), 1u, 0xffffffffu, 0x19u}); +} + +void Submit(std::vector& commands) { + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + AgcDriver::Submit(&packet, 0); +} + +class Region { +public: + Region(std::size_t bytes, bool watched) : bytes(bytes), arena(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (arena) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + if (data != nullptr) GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (arena) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "linear target completion points: cannot map the guest region"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "linear target completion points: cannot allocate the guest region"); + auto* words = reinterpret_cast(data); + std::fill(words, words + bytes / 4u, Untouched); + if (arena && !watched) { + AgcDriver::GuestMemory::Unwatch(Address(), bytes); + Require(!AgcDriver::GuestMemory::Watched(Address(), bytes), "linear target completion points: the region is still write-watched"); + } + GuestAllocations::Mutation().Add(data, bytes, true, true, true); + } + ~Region() { + GuestAllocations::Mutation().Remove(data); +#ifdef _WIN32 + if (arena) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (arena) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Region(const Region&) = delete; + Region& operator=(const Region&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::uint32_t Word(std::size_t offset) const { return std::atomic_ref(*reinterpret_cast(data + offset)).load(std::memory_order_acquire); } + std::size_t bytes; + +private: + bool arena; + std::uint8_t* data = nullptr; +}; + +void Import(const Region& region) { + const auto address = region.Address(); + const std::vector userData{ + static_cast(address), + static_cast((address >> 32u) & 0xffffu) | (16u << 16u), + static_cast(region.bytes / 16u), + 0x4bfacu, + Untouched, + Untouched, + Untouched, + Untouched, + }; + std::vector commands; + const auto code = reinterpret_cast(FillCode.data()); + Append(commands, 0x76, {0x207, 64, 1, 1}); + Append(commands, 0x76, {0x20c, static_cast(code >> 8u), static_cast(code >> 40u)}); + Append(commands, 0x76, {0x213, (static_cast(userData.size()) << 1u) | 0x80u}); + commands.push_back(0xc0007600u | (static_cast(userData.size()) << 16u)); + commands.push_back(0x240u); + commands.insert(commands.end(), userData.begin(), userData.end()); + commands.insert(commands.end(), {0xc0031500u, static_cast(region.bytes / 1024u), 1, 1, 0x41u}); + Submit(commands); + AgcDriverWaitIdle_nid_postfix(); +} + +bool InsideScissor(std::uint32_t x, std::uint32_t y) { + return x >= Scissor[0] && x < Scissor[2] && y >= Scissor[1] && y < Scissor[3]; +} + +std::string Mismatches(const Region& region) { + std::size_t drawn = 0, outside = 0; + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto value = region.Word(y * Pitch + x * 4u); + if (InsideScissor(x, y)) drawn += value == Drawn ? 0u : 1u; + else outside += value == Untouched ? 0u : 1u; + } + } + if (drawn == 0 && outside == 0) return {}; + return std::to_string(drawn) + " of the " + std::to_string((Scissor[2] - Scissor[0]) * (Scissor[3] - Scissor[1])) + " pixels inside the scissor do not hold the draw's color and " + std::to_string(outside) + " pixels outside it changed"; +} + +bool Await(const std::function& done) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!done()) { + if (std::chrono::steady_clock::now() >= deadline) return false; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + return true; +} + +class Request final : public AgcDriver::IFlipRequest { +public: + explicit Request(std::atomic& ready) : ready(ready) {} + void GpuReady(const std::shared_ptr&) override { ++ready; } + void Fail(std::exception_ptr) noexcept override {} +private: + std::atomic& ready; +}; + +class Output final : public AgcDriver::IVideoOutput { +public: + std::shared_ptr Reserve(const AgcDriver::FlipInfo&) override { return std::make_shared(ready); } + void Fail(std::exception_ptr) noexcept override {} + std::atomic ready = 0; +}; + +enum class Point { Flip, Label, Interrupt }; + +struct Events { + KernelEqueue queue = 0; + KernelEqueueRef owner; + int tag = 0; +}; + +std::string Run(Point point, bool watched, const Region& region, Output& output, Events& events) { + const char* name = point == Point::Flip ? "flip" : point == Point::Label ? "label" : "end-of-pipe interrupt"; + const auto target = region.Address(); + Import(region); + Gate.store(0, std::memory_order_release); + std::vector commands; + AppendDraw(commands, target); + if (point == Point::Flip) { + commands.insert(commands.end(), {AgcDriver::FlipPacketHeader, OutputHandle, 0xfffffffeu, 1, 0, 0}); + } else if (point == Point::Label) { + const auto label = target + LabelOffset; + commands.insert(commands.end(), {0xc0064900u, 0x514u, 1u << 29u, static_cast(label), static_cast(label >> 32u), LabelValue, 0, 0}); + } else { + commands.insert(commands.end(), {0xc0064900u, 0x06000528u, 2u << 24u, 0, 0, 0, 0, 0}); + } + AppendGate(commands); + const auto readyBefore = output.ready.load(); + if (point == Point::Interrupt) { + std::array stale{}; + while (events.owner->GetTriggeredEvents(stale.data(), static_cast(stale.size())) > 0) {} + } + Submit(commands); + const bool watchedAtStore = AgcDriver::GuestMemory::Watched(target, TargetBytes); + std::string failure; + if (point == Point::Flip) { + const bool flipped = Await([&] { return output.ready.load() != readyBefore; }); + if (!flipped) failure = "the flip did not reach the GPU"; + else if (!Await([&] { return Mismatches(region).empty(); })) failure = "after the flip, while the queue waited: " + Mismatches(region); + } else if (point == Point::Label) { + if (!Await([&] { return region.Word(LabelOffset) == LabelValue; })) failure = "the label after the draw never landed"; + else if (const auto mismatches = Mismatches(region); !mismatches.empty()) failure = "when the label after the draw landed: " + mismatches; + } else { + std::array delivered{}; + if (!Await([&] { return events.owner->GetTriggeredEvents(delivered.data(), 1) == 1; })) failure = "the end-of-pipe interrupt after the draw was not delivered"; + else if (delivered[0].udata != &events.tag) failure = "an unexpected event was delivered"; + else if (const auto mismatches = Mismatches(region); !mismatches.empty()) failure = "when the end-of-pipe interrupt after the draw was delivered: " + mismatches; + } + Gate.store(1, std::memory_order_release); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::Graphics::StorageTexture::FlushPending(target, region.bytes, nullptr, "test release"); + std::printf("linear target completion point: %s, %s memory (watched at the store: %d): %s\n", name, watched ? "write-watched" : "unwatched", watchedAtStore ? 1 : 0, failure.empty() ? "stored" : failure.c_str()); + if (failure.empty()) return {}; + return std::string(name) + (watched ? " (write-watched memory): " : " (unwatched memory): ") + failure; +} + +} + +int main(int argc, char** argv) { + try { + bool flips = argc == 1, labels = argc == 1, interrupts = argc == 1, watchedFlip = argc == 1; + for (int index = 1; index < argc; ++index) { + const std::string_view argument(argv[index]); + if (argument == "--flip") flips = true; + else if (argument == "--label") labels = true; + else if (argument == "--interrupt") interrupts = true; + else Require(false, "linear target completion points: unknown argument " + std::string(argument)); + } + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (AgcDriver::VulkanDeviceTestAccess::ContextOf(*device).hostImportAlignment == 0) { + std::puts("linear target completion point tests skipped: no host import on this device, so the target is never resident"); + return VulkanTestSkipped; + } + } + std::vector> regions; + struct ShutdownGuard { + ~ShutdownGuard() { try { AgcDriverShutdown_nid_postfix(); } catch (...) {} } + } shutdown; + Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + auto output = std::make_shared(); + AgcDriverRegisterVideoOutput_nid_postfix(OutputHandle, output); + Events events; + if (interrupts) { + Require(sceKernelCreateEqueue(&events.queue, "AGC linear target") == 0, "linear target completion points: event queue creation failed"); + events.owner = EqueuePin_nid_postfix(events.queue); + Require(sceAgcDriverAddEqEvent(events.queue, 0, &events.tag) == 0, "linear target completion points: graphics event registration failed"); + } + std::vector failures; + const auto run = [&](Point point, bool watched) { + regions.push_back(std::make_unique(watched ? 16 * BlockBytes : 4 * BlockBytes, watched)); + if (auto failure = Run(point, watched, *regions.back(), *output, events); !failure.empty()) failures.push_back(std::move(failure)); + }; + if (flips) run(Point::Flip, false); + if (watchedFlip) run(Point::Flip, true); + if (labels) run(Point::Label, false); + if (interrupts) run(Point::Interrupt, false); + if (interrupts) { + Require(sceAgcDriverDeleteEqEvent(events.queue, 0) == 0, "linear target completion points: graphics event deletion failed"); + events.owner.reset(); + Require(sceKernelDeleteEqueue(events.queue) == 0, "linear target completion points: event queue deletion failed"); + } + AgcDriverUnregisterVideoOutput_nid_postfix(OutputHandle, output); + if (!failures.empty()) { + std::string message = "a CPU read after a point where the guest can observe a draw's completion did not see the drawn linear target:"; + for (const auto& failure : failures) message += "\n " + failure; + Require(false, message); + } + AgcDriverShutdown_nid_postfix(); + std::puts("linear target completion point 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/LinearTargetCompletionStores.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCompletionStores.cpp new file mode 100644 index 0000000000..e6abff0879 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCompletionStores.cpp @@ -0,0 +1,304 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using AgcDriver::Graphics::Require; +using AgcDriver::Graphics::StorageTexture; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 16; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t TargetBytes = std::size_t{Width} * Height * 16u; +constexpr std::size_t LabelOffset = BlockBytes; +constexpr std::uint32_t Rgba32FloatInfo = 0x8738u; +constexpr std::uint32_t LinearAttrib3 = 0x09000000u; +constexpr std::uint32_t Float32AbgrExport = 9; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +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} +}}; + +alignas(256) std::array, 2> Programs{}; + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +std::span Program(std::size_t index, bool blue) { + auto& code = Programs.at(index); + std::size_t at = 0; + code[at++] = 0x36000000u | (20u << 17u) | 0xffu; + code[at++] = 0x0000ffffu; + code[at++] = 0x2c000000u | (21u << 17u) | 0x90u; + code[at++] = 0x7e000c00u | (20u << 17u) | (256u + 20u); + code[at++] = 0x7e000c00u | (21u << 17u) | (256u + 21u); + code[at++] = (blue ? 0x7e0002f2u : 0x7e000280u) | (22u << 17u); + code[at++] = 0x7e0002f2u | (23u << 17u); + code[at++] = 0xf800180fu; + code[at++] = 0x17161514u; + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (watched) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (watched) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the color target"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "cannot allocate the color target"); + Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched"); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + StorageTexture::FlushPending(Address(), bytes, nullptr, "test release"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + if (watched) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (watched) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + bool watched; + std::uint8_t* data = nullptr; +}; + +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(block.Address() >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Rgba32FloatInfo; + cx[0x31d] = 0; + cx[0x323] = 0; + cx[0x324] = 0; + cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); + cx[0x3b8] = LinearAttrib3; + cx[0x390] = static_cast(block.Address() >> 40u); + auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + color.exportIndex = 0; + return color; +} + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::span pixelCode, VkRect2D scissor) { + const auto target = device.Target(); + constexpr std::uint32_t waveSize = 64; + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast(Triangle.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionFixedPoint); + pixel.targetOutputMode.at(0) = Float32AbgrExport; + pixel.targetExportMapping.fill(0xe4u); + const std::vector pixelUserData(8, 0u); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + + VkPipelineColorBlendAttachmentState blend{}; + blend.blendEnable = VK_FALSE; + blend.colorWriteMask = 0xf; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.colors = {color}; + state.color = 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 = scissor; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blends = {blend}; + state.blend = blend; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); +} + +std::array Texel(const std::uint8_t* target, std::uint32_t x, std::uint32_t y) { + std::array texel{}; + std::memcpy(texel.data(), target + (static_cast(y) * Width + x) * 16u, sizeof(texel)); + return texel; +} + +void Label(AgcDriver::VulkanDevice& device, const Block& block, std::uint32_t value, std::uint64_t submissions, bool watched, const char* what) { + const auto bytes = std::as_bytes(std::span(&value, 1)); + const auto reason = device.WriteLabelOnGpu(block.Address() + LabelOffset, bytes, value, 0); + Require(reason == 0, std::string(what) + ": the label was not recorded on the GPU (outcome " + std::to_string(reason) + ")"); + Require(StorageTexture::FindPending(block.Address(), TargetBytes) == nullptr, std::string(what) + ": the drawn linear target was still pending after a label the GPU writes"); + const auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(!watched || (recorder != nullptr && recorder->Submissions() == submissions), std::string(what) + ": storing the drawn linear target before the label waited for recorded work (" + std::to_string(recorder != nullptr ? recorder->Submissions() - submissions : 0) + " submissions)"); +} + +bool Expect(AgcDriver::VulkanDevice& device) { + Block block(16 * BlockBytes); + std::fill(block.data, block.data + block.bytes, std::uint8_t{0xff}); + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); + if (AgcDriver::Graphics::HostImportFor(context, block.Address(), block.bytes) == nullptr) return false; + const auto color = DecodeTarget(block); + Require(color.tileMode == AgcDriver::Graphics::ColorTileMode::Linear && color.bytes == TargetBytes, "the target is not a linear R32G32B32A32 target"); + const auto red = Program(0, false); + const auto blue = Program(1, true); + const auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(recorder != nullptr, "the device has no recorder"); + const bool watched = AgcDriver::GuestMemory::Watched(block.Address(), TargetBytes); + if (!watched) std::puts("linear target completion stores: the target is outside the write watch, so a store may wait for the previous store's saved bytes; the submission checks are skipped"); + const auto pending = [&](const char* what) { + Require(StorageTexture::FindPending(block.Address(), TargetBytes) != nullptr, std::string(what) + ": the drawn linear target was stored before any point where its completion is observable"); + }; + Draw(device, color, red, {{0, 0}, {Width, Height}}); + pending("after the first draw"); + const auto submissions = recorder->Submissions(); + Draw(device, color, blue, {{0, 0}, {Width / 2u, Height}}); + pending("after the second draw"); + Require(!watched || recorder->Submissions() == submissions, "the second draw into the linear target submitted recorded work"); + Draw(device, color, red, {{Width / 4u, Height / 4u}, {Width / 4u, Height / 2u}}); + pending("after the third draw"); + Require(!watched || recorder->Submissions() == submissions, "the third draw into the linear target submitted recorded work"); + Label(device, block, 1u, submissions, watched, "the first label"); + Draw(device, color, blue, {{Width / 2u, 0}, {Width / 2u, Height}}); + pending("after the fourth draw"); + Label(device, block, 2u, submissions, watched, "the second label"); + Draw(device, color, red, {{0, Height / 2u}, {Width, Height / 2u}}); + pending("after the fifth draw"); + Require(!watched || recorder->Submissions() == submissions, "the fifth draw into the linear target submitted recorded work"); + device.WaitIdle(); + Require(StorageTexture::FindPending(block.Address(), TargetBytes) == nullptr, "the drawn linear target was still pending after the device wait"); + std::vector stored(TargetBytes); + std::memcpy(stored.data(), block.data, TargetBytes); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const bool lastRed = y >= Height / 2u; + const bool lastBlue = !lastRed && x >= Width / 2u; + const bool thirdRed = !lastRed && !lastBlue && x >= Width / 4u && x < Width / 2u && y >= Height / 4u; + const bool secondBlue = !lastRed && !lastBlue && !thirdRed; + const float z = lastRed || thirdRed ? 0.0f : secondBlue || lastBlue ? 1.0f : -1.0f; + const std::array expected{static_cast(x), static_cast(y), z, 1.0f}; + const auto texel = Texel(stored.data(), x, y); + Require(texel == expected, "after the device wait, raw guest read: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(texel[0]) + ", " + std::to_string(texel[1]) + ", " + std::to_string(texel[2]) + ", " + std::to_string(texel[3]) + ")"); + } + } + std::uint32_t label = 0; + std::memcpy(&label, block.data + LabelOffset, sizeof(label)); + Require(label == 2u, "the second label did not land: it holds " + std::to_string(label)); + return true; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (!Expect(*device)) { + std::puts("linear target completion store test skipped: no host import on this device"); + return VulkanTestSkipped; + } + std::puts("linear target completion store 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/LinearTargetCpuWrites.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCpuWrites.cpp new file mode 100644 index 0000000000..cbd2e525e4 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCpuWrites.cpp @@ -0,0 +1,310 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 16; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t TargetBytes = std::size_t{Width} * Height * 16u; +constexpr std::size_t RowBytes = std::size_t{Width} * 16u; +constexpr std::uint32_t Drawn = Width / 2u; +constexpr std::uint32_t Rgba32FloatInfo = 0x8738u; +constexpr std::uint32_t LinearAttrib3 = 0x09000000u; +constexpr std::uint32_t Float32AbgrExport = 9; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +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} +}}; + +alignas(256) std::array, 2> Programs{}; + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +std::span Program(std::size_t index, bool blue) { + auto& code = Programs.at(index); + std::size_t at = 0; + code[at++] = 0x36000000u | (20u << 17u) | 0xffu; + code[at++] = 0x0000ffffu; + code[at++] = 0x2c000000u | (21u << 17u) | 0x90u; + code[at++] = 0x7e000c00u | (20u << 17u) | (256u + 20u); + code[at++] = 0x7e000c00u | (21u << 17u) | (256u + 21u); + code[at++] = (blue ? 0x7e0002f2u : 0x7e000280u) | (22u << 17u); + code[at++] = 0x7e0002f2u | (23u << 17u); + code[at++] = 0xf800180fu; + code[at++] = 0x17161514u; + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + +struct Block { + Block(std::size_t bytes, bool stayWatched) : bytes(bytes), arena(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (arena) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (arena) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the color target"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "cannot allocate the color target"); + Require(!arena || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched"); + if (arena && !stayWatched) { + AgcDriver::GuestMemory::Unwatch(Address(), bytes); + Require(!AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is still write-watched"); + } + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + AgcDriver::Graphics::StorageTexture::FlushPending(Address(), bytes, nullptr, "test release"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + if (arena) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (arena) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + bool arena; + std::uint8_t* data = nullptr; +}; + +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(block.Address() >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Rgba32FloatInfo; + cx[0x31d] = 0; + cx[0x323] = 0; + cx[0x324] = 0; + cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); + cx[0x3b8] = LinearAttrib3; + cx[0x390] = static_cast(block.Address() >> 40u); + auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + color.exportIndex = 0; + return color; +} + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::span pixelCode, std::uint32_t scissorWidth) { + const auto target = device.Target(); + constexpr std::uint32_t waveSize = 64; + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast(Triangle.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionFixedPoint); + pixel.targetOutputMode.at(0) = Float32AbgrExport; + pixel.targetExportMapping.fill(0xe4u); + const std::vector pixelUserData(8, 0u); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + + VkPipelineColorBlendAttachmentState blend{}; + blend.blendEnable = VK_FALSE; + blend.colorWriteMask = 0xf; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.colors = {color}; + state.color = 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}, {scissorWidth, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blends = {blend}; + state.blend = blend; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); +} + +std::array Texel(const std::uint8_t* target, std::uint32_t x, std::uint32_t y) { + std::array texel{}; + std::memcpy(texel.data(), target + static_cast(y) * RowBytes + static_cast(x) * 16u, sizeof(texel)); + return texel; +} + +enum class Outcome { Kept, Lost, Skipped }; + +Outcome Run(AgcDriver::VulkanDevice& device, bool watched, bool seeded, bool hookRead) { + const auto name = std::string(seeded ? "seeded" : "unseeded") + ", " + (watched ? "write-watched" : "unwatched") + " memory" + (hookRead ? ", driver read before the store" : ""); + Block block(watched ? std::size_t{16} * BlockBytes : BlockBytes, watched); + std::fill(block.data, block.data + block.bytes, std::uint8_t{0xff}); + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); + if (AgcDriver::Graphics::HostImportFor(context, block.Address(), block.bytes) == nullptr) return Outcome::Skipped; + const auto color = DecodeTarget(block); + Require(color.tileMode == AgcDriver::Graphics::ColorTileMode::Linear && color.bytes == TargetBytes, "the target is not a linear R32G32B32A32 target"); + Draw(device, color, Program(0, false), Width); + const auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(recorder != nullptr && !recorder->Idle(), "the draw into an imported linear target waited for the GPU instead of being recorded"); + device.WaitIdle(); + std::vector stored(TargetBytes); + std::memcpy(stored.data(), block.data, TargetBytes); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const std::array expected{static_cast(x), static_cast(y), 0.0f, 1.0f}; + const auto texel = Texel(stored.data(), x, y); + Require(texel == expected, name + ": after the first draw, raw guest read: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(texel[0]) + ", " + std::to_string(texel[1]) + ", " + std::to_string(texel[2]) + ", " + std::to_string(texel[3]) + ")"); + } + } + if (seeded) { + for (std::uint32_t y = 0; y < Height; ++y) std::memset(block.data + y * RowBytes, 0x5a, Drawn * 16u); + } + Draw(device, color, Program(1, true), Drawn); + for (std::uint32_t y = 0; y < Height; ++y) std::memset(block.data + y * RowBytes + Drawn * 16u, 0x42, RowBytes - Drawn * 16u); + const bool watchedAtStore = AgcDriver::GuestMemory::Watched(block.Address(), TargetBytes); + if (hookRead) { + std::vector row(RowBytes); + AgcDriver::GuestMemory::Read(block.Address(), std::as_writable_bytes(std::span(row)), 1); + for (std::uint32_t x = 0; x < Drawn; ++x) { + const std::array expected{static_cast(x), 0.0f, 1.0f, 1.0f}; + const auto texel = Texel(row.data(), x, 0); + Require(texel == expected, name + ": a driver read of row 0 before the store: pixel (" + std::to_string(x) + ", 0) holds (" + std::to_string(texel[0]) + ", " + std::to_string(texel[1]) + ", " + std::to_string(texel[2]) + ", " + std::to_string(texel[3]) + "), not the second draw's"); + } + } + device.WaitIdle(); + std::size_t wrongGpu = 0; + std::size_t wrongCpu = 0; + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Drawn; ++x) { + const std::array expected{static_cast(x), static_cast(y), 1.0f, 1.0f}; + if (Texel(block.data, x, y) != expected) ++wrongGpu; + } + auto* begin = block.data + y * RowBytes + Drawn * 16u; + wrongCpu += static_cast(std::count_if(begin, block.data + (y + 1u) * RowBytes, [](std::uint8_t byte) { return byte != 0x42u; })); + } + std::printf("linear target CPU writes, %s (watched at the store: %d): wrong GPU pixels %zu/%u, wrong CPU bytes %zu/%zu\n", name.c_str(), watchedAtStore ? 1 : 0, wrongGpu, Drawn * Height, wrongCpu, (RowBytes - Drawn * 16u) * Height); + return wrongGpu == 0 && wrongCpu == 0 ? Outcome::Kept : Outcome::Lost; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + bool kept = true; + bool ran = false; + for (const bool watched : {true, false}) { + for (const bool seeded : {false, true}) { + for (const bool hookRead : {false, true}) { + const auto outcome = Run(*device, watched, seeded, hookRead); + ran = ran || outcome != Outcome::Skipped; + kept = kept && outcome != Outcome::Lost; + } + } + } + if (!ran) { + std::puts("linear target CPU write test skipped: no host import on this device"); + return VulkanTestSkipped; + } + Require(kept, "a drawn linear target's store lost the drawn half or the CPU's later stores to the other half"); + std::puts("linear target CPU write 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/LinearTargetDirectWriteCompletion.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDirectWriteCompletion.cpp new file mode 100644 index 0000000000..4a10c86def --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDirectWriteCompletion.cpp @@ -0,0 +1,452 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#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 AgcDriver::Graphics::Require; + +constexpr std::uint32_t Width = 256; +constexpr std::uint32_t Height = 128; +constexpr std::size_t Pitch = std::size_t{Width} * 4u; +constexpr std::size_t TargetBytes = Pitch * Height; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionBytes = 16 * BlockBytes; +constexpr std::size_t LabelOffset = 3 * BlockBytes; +constexpr std::uint32_t Untouched = 0x5a5a5a5au; +constexpr std::uint32_t Filled = 0x9abcdef0u; +constexpr std::uint32_t Drawn = 0xff0000ffu; +constexpr std::uint32_t LabelValue = 0x600dcafeu; +constexpr std::uint32_t CpuStored = 0x0badf00du; +constexpr std::uint32_t Rgba8UnormInfo = 0x28028u; +constexpr std::uint32_t LinearAttrib3 = 0x09000000u; +constexpr std::uint32_t WaitEqual = 3; +constexpr std::uint32_t WaitAtLeast = 5; +constexpr std::array, 2> Scissors{{{16, 8, 128, 96}, {128, 32, 240, 120}}}; +constexpr std::array BlockZeroScissor{24, 8, 232, 56}; +constexpr std::size_t CpuStoreOffset = BlockBytes + 32 * Pitch + 64 * 4; +static_assert(BlockZeroScissor[3] * Pitch <= BlockBytes); +static_assert(CpuStoreOffset >= BlockBytes && CpuStoreOffset < TargetBytes); + +alignas(256) constexpr std::array VertexCode{ + 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e060208u, 0x7e0802f2u, 0x7e0a02ffu, 0x44800000u, 0xf80000d1u, 0x00000005u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u, +}; +alignas(256) constexpr std::array PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u}; +alignas(256) constexpr std::array FillCode{ + 0xd7460004, 0x04010c08, 0x7e000204, 0x7e020205, 0x7e040206, 0x7e060207, 0xe01c2000, 0x80000004, 0xbf810000, +}; +alignas(256) constexpr std::array PairFillCode{ + 0xd7460004, 0x04010c0c, 0x7e000204, 0x7e020205, 0x7e040206, 0x7e060207, 0xe01c2000, 0x80000004, 0xe01c2000, 0x80020004, 0xbf810000, +}; + +alignas(4) std::atomic Gate = 0; + +struct Header { + Shader shader{}; + ShaderUserData userData{}; +}; + +alignas(8) Header VertexHeader{}; +alignas(8) Header PixelHeader{}; + +void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(bytes); + header.shader.code = code; + header.shader.type = type; + header.shader.user_data = &header.userData; + AgcDriverRegisterShader_nid_postfix(&header.shader); +} + +void Append(std::vector& commands, std::uint32_t opcode, std::initializer_list payload) { + commands.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + commands.insert(commands.end(), payload); +} + +void AppendDraw(std::vector& commands, std::uint64_t target, const std::array& scissor) { + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto pixel = reinterpret_cast(PixelCode.data()); + const auto extent = (Height << 16u) | Width; + const std::vector> context{ + {0x2d5, 0x2000}, + {0x1b6, 0}, {0x207, 0x01200000}, {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, Rgba8UnormInfo}, + {0x31b, 0}, {0x31d, 0}, {0x3b0, ((Width - 1u) << 14u) | (Height - 1u)}, + {0x3b8, LinearAttrib3}, {0x1e0, 0}, + {0xc, 0}, {0xd, extent}, + {0x81, 0x80000000}, {0x82, extent}, + {0x90, 0x80000000u | (scissor[1] << 16u) | scissor[0]}, {0x91, (scissor[3] << 16u) | scissor[2]}, + {0x94, 0x80000000}, {0x95, extent}, + {0x10f, std::bit_cast(2.0f * Width)}, {0x110, std::bit_cast(2.0f * Width)}, + {0x111, std::bit_cast(2.0f * Height)}, {0x112, std::bit_cast(2.0f * Height)}, + {0x113, std::bit_cast(1.0f)}, {0x114, 0}, + {0xb4, 0}, {0xb5, std::bit_cast(1.0f)}, + {0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0}, + {0x318, static_cast(target >> 8u)}, {0x390, static_cast(target >> 40u)}, + }; + const std::vector> shader{ + {0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, 1u << 1u}, {0x8c, 0}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast(1.0f)}, + }; + Append(commands, 0x79, {0x242, 4}); + Append(commands, 0x79, {0x24a, 0}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value}); + Append(commands, 0x2d, {3, 2}); +} + +std::array BufferDescriptor(std::uint64_t address, std::size_t bytes) { + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), static_cast(bytes / 16u), 0x4bfacu}; +} + +void AppendDispatch(std::vector& commands, const std::uint32_t* program, const std::vector& userData, std::uint32_t groups) { + const auto code = reinterpret_cast(program); + Append(commands, 0x76, {0x207, 64, 1, 1}); + Append(commands, 0x76, {0x20c, static_cast(code >> 8u), static_cast(code >> 40u)}); + Append(commands, 0x76, {0x213, (static_cast(userData.size()) << 1u) | 0x80u}); + commands.push_back(0xc0007600u | (static_cast(userData.size()) << 16u)); + commands.push_back(0x240u); + commands.insert(commands.end(), userData.begin(), userData.end()); + commands.insert(commands.end(), {0xc0031500u, groups, 1, 1, 0x41u}); +} + +void AppendFill(std::vector& commands, std::uint64_t target, const std::uint32_t* copied, std::size_t bytes) { + const auto direct = BufferDescriptor(target, bytes); + const auto other = BufferDescriptor(reinterpret_cast(copied), bytes); + std::vector userData(direct.begin(), direct.end()); + userData.insert(userData.end(), {Filled, Filled, Filled, Filled}); + userData.insert(userData.end(), other.begin(), other.end()); + AppendDispatch(commands, PairFillCode.data(), userData, static_cast(bytes / 1024u)); +} + +void AppendLabel(std::vector& commands, std::uint64_t label) { + commands.insert(commands.end(), {0xc0064900u, 0x514u, 1u << 29u, static_cast(label), static_cast(label >> 32u), LabelValue, 0, 0}); +} + +void AppendGate(std::vector& commands, std::uint32_t function, std::uint32_t reference) { + const auto gate = reinterpret_cast(&Gate); + commands.insert(commands.end(), {0xc0053c00u, 0x10u | function, static_cast(gate), static_cast(gate >> 32u), reference, 0xffffffffu, 0x19u}); +} + +void Submit(std::vector& commands) { + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + AgcDriver::Submit(&packet, 0); +} + +class Region { +public: + explicit Region(std::size_t bytes) : bytes(bytes), arena(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (arena) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + if (data != nullptr) GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (arena) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "linear target direct write completion: cannot map the guest region"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "linear target direct write completion: cannot allocate the guest region"); + auto* words = reinterpret_cast(data); + std::fill(words, words + bytes / 4u, Untouched); + GuestAllocations::Mutation().Add(data, bytes, true, true, true); + } + ~Region() { + GuestAllocations::Mutation().Remove(data); +#ifdef _WIN32 + if (arena) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (arena) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Region(const Region&) = delete; + Region& operator=(const Region&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::uint32_t Word(std::size_t offset) const { return std::atomic_ref(*reinterpret_cast(data + offset)).load(std::memory_order_acquire); } + void Put(std::size_t offset, std::uint32_t value) const { std::atomic_ref(*reinterpret_cast(data + offset)).store(value, std::memory_order_release); } + std::size_t bytes; + +private: + bool arena; + std::uint8_t* data = nullptr; +}; + +void Import(const Region& region) { + const auto buffer = BufferDescriptor(region.Address(), region.bytes); + std::vector userData(buffer.begin(), buffer.end()); + userData.insert(userData.end(), {Untouched, Untouched, Untouched, Untouched}); + std::vector commands; + AppendDispatch(commands, FillCode.data(), userData, static_cast(region.bytes / 1024u)); + Submit(commands); + AgcDriverWaitIdle_nid_postfix(); +} + +bool Covered(std::uint32_t x, std::uint32_t y) { + return std::any_of(Scissors.begin(), Scissors.end(), [&](const std::array& scissor) { return x >= scissor[0] && x < scissor[2] && y >= scissor[1] && y < scissor[3]; }); +} + +std::string Mismatches(const Region& region, bool drawsLast) { + std::size_t covered = 0, wrongCovered = 0, filledCovered = 0, wrongElsewhere = 0, drawnElsewhere = 0; + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto value = region.Word(y * Pitch + x * 4u); + if (drawsLast && Covered(x, y)) { + ++covered; + wrongCovered += value == Drawn ? 0u : 1u; + filledCovered += value == Filled ? 1u : 0u; + } else { + wrongElsewhere += value == Filled ? 0u : 1u; + drawnElsewhere += value == Drawn ? 1u : 0u; + } + } + } + if (wrongCovered == 0 && wrongElsewhere == 0) return {}; + const auto elsewhere = std::to_string(std::size_t{Width} * Height - covered); + if (!drawsLast) return std::to_string(wrongElsewhere) + " of the " + elsewhere + " pixels do not hold the fill's pattern (" + std::to_string(drawnElsewhere) + " hold the draws' color)"; + return std::to_string(wrongCovered) + " of the " + std::to_string(covered) + " pixels the draws covered do not hold their color (" + std::to_string(filledCovered) + " hold the fill's pattern) and " + std::to_string(wrongElsewhere) + " of the " + elsewhere + " pixels outside them do not hold the fill's pattern"; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::string CpuStoreMismatches(const Region& region) { + std::size_t covered = 0, wrongCovered = 0, filledCovered = 0, others = 0, wrongOthers = 0; + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto offset = y * Pitch + x * 4u; + if (offset == CpuStoreOffset) continue; + const auto value = region.Word(offset); + if (x >= BlockZeroScissor[0] && x < BlockZeroScissor[2] && y >= BlockZeroScissor[1] && y < BlockZeroScissor[3]) { + ++covered; + wrongCovered += value == Drawn ? 0u : 1u; + filledCovered += value == Filled ? 1u : 0u; + } else { + ++others; + wrongOthers += value == (offset < BlockBytes ? Filled : Untouched) ? 0u : 1u; + } + } + } + std::vector parts; + if (wrongCovered != 0) parts.push_back(std::to_string(wrongCovered) + " of the " + std::to_string(covered) + " pixels the draw covered in block 0 do not hold its color (" + std::to_string(filledCovered) + " hold the fill's pattern)"); + if (const auto stored = region.Word(CpuStoreOffset); stored != CpuStored) parts.push_back("the CPU store in block 1 holds " + Hex(stored) + ", not " + Hex(CpuStored)); + if (wrongOthers != 0) parts.push_back(std::to_string(wrongOthers) + " of the " + std::to_string(others) + " other pixels do not hold the fill's pattern in block 0 or the untouched pattern in block 1"); + std::string text; + for (const auto& part : parts) text += (text.empty() ? "" : "; ") + part; + return text; +} + +bool Await(const std::function& done) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!done()) { + if (std::chrono::steady_clock::now() >= deadline) return false; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + return true; +} + +enum class Order { FillFirst, DrawsFirst }; + +bool Run(Order order, const Region& region, std::vector& failures) { + const bool fillFirst = order == Order::FillFirst; + const char* name = fillFirst ? "fill, then draws" : "draws, then fill"; + const auto target = region.Address(); + Import(region); + if (!AgcDriver::GuestMemory::Watched(target, TargetBytes)) return false; + std::vector copied(TargetBytes / 4u + 4u, Untouched); + GuestAllocations::Mutation().Add(copied.data(), TargetBytes + 16u, true, true, true); + struct Unregister { + void* data; + ~Unregister() { GuestAllocations::Mutation().Remove(data); } + } unregister{copied.data()}; + Gate.store(0, std::memory_order_release); + std::vector commands; + if (fillFirst) AppendFill(commands, target, copied.data(), TargetBytes); + for (const auto& scissor : Scissors) AppendDraw(commands, target, scissor); + if (!fillFirst) AppendFill(commands, target, copied.data(), TargetBytes); + AppendLabel(commands, target + LabelOffset); + if (fillFirst) AppendGate(commands, WaitEqual, 1); + Submit(commands); + std::string failure; + if (fillFirst) { + if (!Await([&] { return region.Word(LabelOffset) == LabelValue; })) failure = "the label after the draws never landed"; + else if (const auto mismatches = Mismatches(region, true); !mismatches.empty()) failure = "when the label after the draws landed: " + mismatches; + Gate.store(1, std::memory_order_release); + AgcDriverWaitIdle_nid_postfix(); + } else { + AgcDriverWaitIdle_nid_postfix(); + if (region.Word(LabelOffset) != LabelValue) failure = "the label after the fill had not landed by the device wait"; + else if (const auto mismatches = Mismatches(region, false); !mismatches.empty()) failure = "after the label and the device wait: " + mismatches; + } + AgcDriver::Graphics::StorageTexture::FlushPending(target, region.bytes, nullptr, "test release"); + std::printf("linear target direct write completion, %s: %s\n", name, failure.empty() ? "the later writer's values" : failure.c_str()); + if (!failure.empty()) failures.push_back(std::string(name) + ": " + failure); + return true; +} + +bool RunCpuStore(bool watched, const Region& region, std::vector& failures) { + const std::string name = std::string("fill, draw in block 0, CPU store in block 1, ") + (watched ? "write-watched" : "unwatched") + " memory"; + const auto target = region.Address(); + Import(region); + if (watched && !AgcDriver::GuestMemory::Watched(target, TargetBytes)) return false; + if (!watched) { + AgcDriver::GuestMemory::Unwatch(target, region.bytes); + Require(!AgcDriver::GuestMemory::Watched(target, region.bytes), "linear target direct write completion: the region is still write-watched"); + } + std::vector copied(TargetBytes / 4u + 4u, Untouched); + GuestAllocations::Mutation().Add(copied.data(), TargetBytes + 16u, true, true, true); + struct Unregister { + void* data; + ~Unregister() { GuestAllocations::Mutation().Remove(data); } + } unregister{copied.data()}; + Gate.store(0, std::memory_order_release); + std::vector commands; + AppendFill(commands, target, copied.data(), BlockBytes); + AppendDraw(commands, target, BlockZeroScissor); + AppendGate(commands, WaitAtLeast, 1); + AppendLabel(commands, target + LabelOffset); + AppendGate(commands, WaitAtLeast, 2); + Submit(commands); + const auto completed = [&] { return std::atomic_ref(copied[0]).load(std::memory_order_acquire) == Filled; }; + bool pendingAtStore = false; + const bool recorded = Await([&] { + std::unique_lock hold(AgcDriver::GuestMemory::GpuMutex()); + if (AgcDriver::Graphics::StorageTexture::FindLive(target, TargetBytes) == nullptr) return false; + pendingAtStore = !completed(); + region.Put(CpuStoreOffset, CpuStored); + return true; + }); + Gate.store(1, std::memory_order_release); + std::string failure; + bool completedAtLabel = false; + if (!recorded) failure = "the draw into block 0 never made the target's image"; + else if (!Await([&] { return region.Word(LabelOffset) == LabelValue; })) failure = "the label after the draw never landed"; + else { + completedAtLabel = completed(); + failure = CpuStoreMismatches(region); + if (!failure.empty()) failure = "when the label after the draw landed: " + failure; + if (watched && (!pendingAtStore || !completedAtLabel)) failure += std::string(failure.empty() ? "" : "; ") + (pendingAtStore ? "the fill's completion had not run when the label landed" : "the fill's completion had run before the CPU store") + ", so the ordering under test did not occur"; + } + const bool watchedAtLabel = AgcDriver::GuestMemory::Watched(target, TargetBytes); + Gate.store(2, std::memory_order_release); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::Graphics::StorageTexture::FlushPending(target, region.bytes, nullptr, "test release"); + std::printf("linear target direct write completion, %s (watched at the label: %d, completion pending at the CPU store: %d, run by the label: %d): %s\n", name.c_str(), watchedAtLabel ? 1 : 0, pendingAtStore ? 1 : 0, completedAtLabel ? 1 : 0, failure.empty() ? "the draw's color and the CPU store" : failure.c_str()); + if (!failure.empty()) failures.push_back(name + ": " + failure); + return true; +} + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (AgcDriver::VulkanDeviceTestAccess::ContextOf(*device).hostImportAlignment == 0) { + std::puts("linear target direct write completion tests skipped, host imports are unavailable on this device, so the target is never resident"); + return VulkanTestSkipped; + } + } + std::vector> regions; + struct ShutdownGuard { + ~ShutdownGuard() { try { AgcDriverShutdown_nid_postfix(); } catch (...) {} } + } shutdown; + Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + const auto unwatchedImports = [] { + std::puts("linear target direct write completion tests skipped, the device does not keep host imports write-watched, so no completion stamp over the target is tracked"); + return VulkanTestSkipped; + }; + std::vector failures; + for (const auto order : {Order::FillFirst, Order::DrawsFirst}) { + regions.push_back(std::make_unique(RegionBytes)); + if (!Run(order, *regions.back(), failures)) return unwatchedImports(); + } + for (const bool watched : {true, false}) { + regions.push_back(std::make_unique(RegionBytes)); + if (!RunCpuStore(watched, *regions.back(), failures)) return unwatchedImports(); + } + if (!failures.empty()) { + std::string message = "linear target direct write completion: at a completion point, the memory of a resident linear target did not hold the later of a direct buffer write with completion work and draws into it, or lost a CPU store made outside the draws:"; + for (const auto& failure : failures) message += "\n " + failure; + Require(false, message); + } + AgcDriverShutdown_nid_postfix(); + std::puts("linear target direct write completion 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/LinearTargetDispatchAccess.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDispatchAccess.cpp new file mode 100644 index 0000000000..809ffd4d91 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDispatchAccess.cpp @@ -0,0 +1,300 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using AgcDriver::Graphics::Require; +using AgcDriver::Graphics::StorageTexture; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 16; +constexpr std::uint32_t Threads = 64; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t TargetBytes = std::size_t{Width} * Height * 16u; +constexpr std::uint32_t Rgba32FloatInfo = 0x8738u; +constexpr std::uint32_t LinearAttrib3 = 0x09000000u; +constexpr std::uint32_t Float32AbgrExport = 9; +constexpr std::uint32_t ReadX = 10; +constexpr std::uint32_t ReadY = 6; +constexpr std::uint32_t Drawn = 0x3f800000u; +constexpr std::uint32_t Unwritten = 0xdeadbeefu; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +alignas(256) constexpr std::array CopyCode{0x7e000280u, 0xdc308000u, 0x01000000u, 0xbf8c3f70u, 0xdc708000u, 0x00020100u, 0xbf810000u}; + +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} +}}; + +alignas(256) std::array, 2> Programs{}; + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +std::span Program(std::size_t index, bool blue) { + auto& code = Programs.at(index); + std::size_t at = 0; + code[at++] = 0x36000000u | (20u << 17u) | 0xffu; + code[at++] = 0x0000ffffu; + code[at++] = 0x2c000000u | (21u << 17u) | 0x90u; + code[at++] = 0x7e000c00u | (20u << 17u) | (256u + 20u); + code[at++] = 0x7e000c00u | (21u << 17u) | (256u + 21u); + code[at++] = (blue ? 0x7e0002f2u : 0x7e000280u) | (22u << 17u); + code[at++] = 0x7e0002f2u | (23u << 17u); + code[at++] = 0xf800180fu; + code[at++] = 0x17161514u; + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (watched) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (watched) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map a guest block"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "cannot allocate a guest block"); + Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "a guest block is not write-watched"); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + StorageTexture::FlushPending(Address(), bytes, nullptr, "test release"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + if (watched) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (watched) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + bool watched; + std::uint8_t* data = nullptr; +}; + +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(block.Address() >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Rgba32FloatInfo; + cx[0x31d] = 0; + cx[0x323] = 0; + cx[0x324] = 0; + cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); + cx[0x3b8] = LinearAttrib3; + cx[0x390] = static_cast(block.Address() >> 40u); + auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + color.exportIndex = 0; + return color; +} + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::span pixelCode, VkRect2D scissor) { + const auto target = device.Target(); + constexpr std::uint32_t waveSize = 64; + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast(Triangle.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionFixedPoint); + pixel.targetOutputMode.at(0) = Float32AbgrExport; + pixel.targetExportMapping.fill(0xe4u); + const std::vector pixelUserData(8, 0u); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + + VkPipelineColorBlendAttachmentState blend{}; + blend.blendEnable = VK_FALSE; + blend.colorWriteMask = 0xf; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.colors = {color}; + state.color = 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 = scissor; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blends = {blend}; + state.blend = blend; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint64_t source, std::uint64_t destination) { + constexpr std::uint32_t waveSize = 64; + const std::array userData{static_cast(source), static_cast(source >> 32u), static_cast(destination), static_cast(destination >> 32u)}; + const std::span code(CopyCode); + 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, {}}, + {waveSize, 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())); +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::uint32_t Dword(const std::uint8_t* bytes) { + std::uint32_t value = 0; + std::memcpy(&value, bytes, sizeof(value)); + return value; +} + +bool Expect(AgcDriver::VulkanDevice& device) { + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); + if (!context.bufferDeviceAddress) { + std::puts("skipped, the device lacks bufferDeviceAddress"); + return false; + } + Block block(16 * BlockBytes); + std::fill(block.data, block.data + block.bytes, std::uint8_t{0}); + Block output(BlockBytes); + for (std::size_t offset = 0; offset < output.bytes; offset += sizeof(Unwritten)) std::memcpy(output.data + offset, &Unwritten, sizeof(Unwritten)); + if (AgcDriver::Graphics::HostImportFor(context, block.Address(), block.bytes) == nullptr) { + std::puts("skipped, host imports are unavailable"); + return false; + } + const auto color = DecodeTarget(block); + Require(color.tileMode == AgcDriver::Graphics::ColorTileMode::Linear && color.bytes == TargetBytes, "the target is not a linear R32G32B32A32 target"); + const auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(recorder != nullptr, "the device has no recorder"); + Draw(device, color, Program(0, false), {{0, 0}, {Width, Height}}); + device.WaitIdle(); + Require(StorageTexture::FindPending(block.Address(), TargetBytes) == nullptr, "the linear target was still pending after the device wait"); + const auto blueOffset = (static_cast(ReadY) * Width + ReadX) * 16u + 8u; + Require(Dword(block.data + blueOffset) == 0u, "the first draw left " + Hex(Dword(block.data + blueOffset)) + " in the blue channel of pixel (" + std::to_string(ReadX) + ", " + std::to_string(ReadY) + ")"); + Draw(device, color, Program(1, true), {{8, 4}, {16, 8}}); + Require(recorder->Recording(), "the draw into the linear target was not left in the open batch"); + Dispatch(device, block.Address() + blueOffset, output.Address()); + device.WaitIdle(); + const auto read = Dword(output.data); + Require(read == Drawn, "the address-based dispatch recorded after the draw read " + Hex(read) + " from the blue channel of pixel (" + std::to_string(ReadX) + ", " + std::to_string(ReadY) + "), not the drawn " + Hex(Drawn)); + Require(Dword(block.data + blueOffset) == Drawn, "the second draw left " + Hex(Dword(block.data + blueOffset)) + " in the blue channel of pixel (" + std::to_string(ReadX) + ", " + std::to_string(ReadY) + ")"); + return true; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (!Expect(*device)) return VulkanTestSkipped; + std::puts("linear target dispatch access 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/LinearTargetDrawBindings.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDrawBindings.cpp new file mode 100644 index 0000000000..4335e86e2c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDrawBindings.cpp @@ -0,0 +1,334 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using AgcDriver::Graphics::Require; +using AgcDriver::Graphics::StorageTexture; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 16; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t TargetBytes = std::size_t{Width} * Height * 16u; +constexpr std::size_t CopyTargetOffset = 0x40000; +constexpr std::size_t FirstSourceOffset = 0x80000; +constexpr std::size_t SecondSourceOffset = 0x90000; +constexpr std::size_t PointedOffset = 0xa0000; +constexpr std::uint32_t Rgba32FloatInfo = 0x8738u; +constexpr std::uint32_t LinearAttrib3 = 0x09000000u; +constexpr std::uint32_t Float32AbgrExport = 9; +constexpr std::uint32_t FirstSource = 0x3e800000u; +constexpr std::uint32_t SecondSource = 0x3f400000u; +constexpr std::uint32_t Pointed = 0x3f800000u; +constexpr std::uint32_t Unwritten = 0xdeadbeefu; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +alignas(256) constexpr std::array PixelOpaque{0x7e100280u, 0x7e120280u, 0x7e140280u, 0x7e1602f2u, 0xf800180fu, 0x0b0a0908u, 0xbf810000u}; +alignas(256) constexpr std::array PixelRead{0x7e1a0280u, 0xdc308000u, 0x0c02000du, 0xbf8c3f70u, 0x7e10030cu, 0x7e120280u, 0x7e140280u, 0x7e160280u, 0xf800180fu, 0x0b0a0908u, 0xbf810000u}; +alignas(256) constexpr std::array PixelCopy{0x7e040280u, 0xe0301000u, 0x80010102u, 0xbf8c3f70u, 0xe0701000u, 0x80020102u, 0x7e100301u, 0x7e120280u, 0x7e140280u, 0x7e1602f2u, 0xf800180fu, 0x0b0a0908u, 0xbf810000u}; + +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} +}}; + +alignas(256) std::array FirstOutput{}; +alignas(256) std::array SecondOutput{}; + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +std::array RawBuffer(std::uint64_t address, std::uint32_t bytes) { + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x31016facu}; +} + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (watched) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (watched) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the color targets"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "cannot allocate the color targets"); + Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color targets are not write-watched"); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + StorageTexture::FlushPending(Address(), bytes, nullptr, "test release"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + if (watched) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (watched) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + bool watched; + std::uint8_t* data = nullptr; +}; + +AgcDriver::Graphics::ColorTarget DecodeTarget(std::uint64_t address) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(address >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Rgba32FloatInfo; + cx[0x31d] = 0; + cx[0x323] = 0; + cx[0x324] = 0; + cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); + cx[0x3b8] = LinearAttrib3; + cx[0x390] = static_cast(address >> 40u); + auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + color.exportIndex = 0; + return color; +} + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::span pixelCode, std::span pixelUserData, VkRect2D scissor) { + const auto target = device.Target(); + constexpr std::uint32_t waveSize = 64; + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast(Triangle.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionFixedPoint); + pixel.targetOutputMode.at(0) = Float32AbgrExport; + pixel.targetExportMapping.fill(0xe4u); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + + VkPipelineColorBlendAttachmentState blend{}; + blend.blendEnable = VK_FALSE; + blend.colorWriteMask = 0xf; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.colors = {color}; + state.color = 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 = scissor; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blends = {blend}; + state.blend = blend; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); +} + +std::array CopyUserData(std::uint64_t source, const std::array& output) { + std::array userData{}; + const auto read = RawBuffer(source, 16u); + const auto written = RawBuffer(reinterpret_cast(output.data()), 16u); + std::copy(read.begin(), read.end(), userData.begin() + 4); + std::copy(written.begin(), written.end(), userData.begin() + 8); + return userData; +} + +std::array Texel(const std::uint8_t* target, std::uint32_t x, std::uint32_t y) { + std::array texel{}; + std::memcpy(texel.data(), target + (static_cast(y) * Width + x) * 16u, sizeof(texel)); + return texel; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::string Describe(const std::array& texel) { + return "(" + std::to_string(texel[0]) + ", " + std::to_string(texel[1]) + ", " + std::to_string(texel[2]) + ", " + std::to_string(texel[3]) + ")"; +} + +void ExpectTexels(const std::uint8_t* target, const char* what, std::uint32_t firstX, std::uint32_t lastX, const std::array& expected) { + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = firstX; x < lastX; ++x) { + const auto texel = Texel(target, x, y); + Require(texel == expected, std::string(what) + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds " + Describe(texel) + ", expected " + Describe(expected)); + } + } +} + +bool Expect(AgcDriver::VulkanDevice& device, bool unwatch) { + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); + if (!context.bufferDeviceAddress) { + std::puts("skipped, the device lacks bufferDeviceAddress"); + return false; + } + Block block(16 * BlockBytes); + std::fill(block.data, block.data + block.bytes, std::uint8_t{0}); + const auto put = [&](std::size_t offset, std::uint32_t value, std::size_t dwords) { + for (std::size_t dword = 0; dword < dwords; ++dword) std::memcpy(block.data + offset + dword * 4u, &value, sizeof(value)); + }; + put(FirstSourceOffset, FirstSource, 4); + put(SecondSourceOffset, SecondSource, 4); + put(PointedOffset, Pointed, 1); + FirstOutput.fill(Unwritten); + SecondOutput.fill(Unwritten); + if (AgcDriver::Graphics::HostImportFor(context, block.Address(), block.bytes) == nullptr) { + std::puts("skipped, host imports are unavailable"); + return false; + } + const auto drawn = DecodeTarget(block.Address()); + const auto copied = DecodeTarget(block.Address() + CopyTargetOffset); + Require(drawn.tileMode == AgcDriver::Graphics::ColorTileMode::Linear && drawn.bytes == TargetBytes && copied.tileMode == AgcDriver::Graphics::ColorTileMode::Linear && copied.bytes == TargetBytes, "the targets are not linear R32G32B32A32 targets"); + auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(recorder != nullptr && context.descriptorCache != nullptr, "the device has no recorder or descriptor cache"); + const auto pending = [](std::uint64_t address) { return StorageTexture::FindPending(address, TargetBytes) != nullptr; }; + const VkRect2D whole{{0, 0}, {Width, Height}}; + Draw(device, drawn, PixelOpaque, {}, whole); + Draw(device, copied, PixelOpaque, {}, whole); + device.WaitIdle(); + Require(!pending(drawn.address) && !pending(copied.address), "a linear target was still pending after the device wait"); + if (unwatch) AgcDriver::GuestMemory::Unwatch(block.Address(), block.bytes); + const bool watched = AgcDriver::GuestMemory::Watched(copied.address, TargetBytes); + Require(!unwatch || !watched, "the copied target is still write-watched after its block was taken out of the write watch"); + const auto submitted = recorder->Submissions(); + const auto pointed = block.Address() + PointedOffset; + const std::array pointer{0u, 0u, static_cast(pointed), static_cast(pointed >> 32u)}; + Draw(device, drawn, PixelRead, pointer, whole); + Require(pending(drawn.address), "the address-based draw did not leave its linear target pending"); + Require(recorder->Recording() && recorder->Submissions() == submitted, "the address-based draw did not stay in the open batch"); + const auto setsBefore = context.descriptorCache->Counters().sets; + const auto firstCopy = CopyUserData(block.Address() + FirstSourceOffset, FirstOutput); + Draw(device, copied, PixelCopy, firstCopy, {{0, 0}, {Width / 2u, Height}}); + Require(!pending(drawn.address), "the draw writing a copied buffer did not store the drawn linear target first"); + Require(pending(copied.address), "the first copying draw did not leave its linear target pending"); + Require(recorder->Recording() && recorder->Submissions() == submitted + 1, "storing the drawn linear target did not submit the batch the address-based draw read it in (" + std::to_string(recorder->Submissions() - submitted) + " submissions)"); + const auto setsBetween = context.descriptorCache->Counters().sets; + const auto secondCopy = CopyUserData(block.Address() + SecondSourceOffset, SecondOutput); + Draw(device, copied, PixelCopy, secondCopy, {{static_cast(Width / 2u), 0}, {Width / 2u, Height}}); + if (watched) Require(recorder->Recording() && recorder->Submissions() == submitted + 1, "the second copying draw submitted recorded work"); + else Require(recorder->Submissions() == submitted + 2, "the second copying draw into a stored target outside the write watch did not submit once to capture its bytes (" + std::to_string(recorder->Submissions() - submitted - 1) + " submissions)"); + const auto setsAfter = context.descriptorCache->Counters().sets; + device.WaitIdle(); + Require(FirstOutput[0] == FirstSource, "the first copying draw's output holds " + Hex(FirstOutput[0]) + ", not its source's " + Hex(FirstSource)); + Require(SecondOutput[0] == SecondSource, "the second copying draw's output holds " + Hex(SecondOutput[0]) + ", not its source's " + Hex(SecondSource)); + ExpectTexels(block.data + CopyTargetOffset, "the first copying draw's half", 0, Width / 2u, {0.25f, 0.0f, 0.0f, 1.0f}); + ExpectTexels(block.data + CopyTargetOffset, "the second copying draw's half", Width / 2u, Width, {0.75f, 0.0f, 0.0f, 1.0f}); + ExpectTexels(block.data, "the address-based draw's target", 0, Width, {1.0f, 0.0f, 0.0f, 0.0f}); + Require(setsAfter - setsBetween == setsBetween - setsBefore, "the second copying draw allocated " + std::to_string(setsAfter - setsBetween) + " descriptor sets, the first " + std::to_string(setsBetween - setsBefore)); + return true; +} + +} + +int main() { +#ifdef _WIN32 + _putenv_s("APS5_RELEASE_ON_UNLOCK", "1"); +#else + setenv("APS5_RELEASE_ON_UNLOCK", "1", 1); +#endif + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (!Expect(*device, false)) return VulkanTestSkipped; + if (!Expect(*device, true)) return VulkanTestSkipped; + std::puts("linear target draw binding 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/LinearTargetRectangleBound.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetRectangleBound.cpp new file mode 100644 index 0000000000..c19854ea05 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetRectangleBound.cpp @@ -0,0 +1,344 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionBytes = 8 * BlockBytes; +constexpr std::uint32_t Rgba32FloatInfo = 0x8738u; +constexpr std::uint32_t LinearAttrib3 = 0x09000000u; +constexpr std::uint32_t Float32AbgrExport = 9; +constexpr std::uint8_t Initial = 0x3f; +constexpr std::uint8_t Sentinel = 0x37; +constexpr std::uint8_t Gap = 0x29; +constexpr std::size_t GapBytes = 0x100; + +struct Surface { + std::size_t offset; + std::uint32_t width; + std::uint32_t height; + std::size_t RowBytes() const { return std::size_t{width} * 16u; } + std::size_t Bytes() const { return RowBytes() * height; } +}; + +constexpr Surface Tall{0, 64, 256}; +constexpr Surface Shifted{6 * BlockBytes + GapBytes, 64, 16}; +constexpr VkRect2D Seed{{63, 255}, {1, 1}}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +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} +}}; + +alignas(256) std::array, 2> Programs{}; + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +std::span Program(std::size_t index, bool blue) { + auto& code = Programs.at(index); + std::size_t at = 0; + code[at++] = 0x36000000u | (20u << 17u) | 0xffu; + code[at++] = 0x0000ffffu; + code[at++] = 0x2c000000u | (21u << 17u) | 0x90u; + code[at++] = 0x7e000c00u | (20u << 17u) | (256u + 20u); + code[at++] = 0x7e000c00u | (21u << 17u) | (256u + 21u); + code[at++] = (blue ? 0x7e0002f2u : 0x7e000280u) | (22u << 17u); + code[at++] = 0x7e0002f2u | (23u << 17u); + code[at++] = 0xf800180fu; + code[at++] = 0x17161514u; + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes), arena(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (arena) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (arena) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the color targets"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "cannot allocate the color targets"); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + AgcDriver::Graphics::StorageTexture::FlushPending(Address(), bytes, nullptr, "test release"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + if (arena) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (arena) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + bool arena; + std::uint8_t* data = nullptr; +}; + +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, const Surface& surface) { + const auto address = block.Address() + surface.offset; + AgcDriver::Registers cx; + cx[0x318] = static_cast(address >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Rgba32FloatInfo; + cx[0x31d] = 0; + cx[0x323] = 0; + cx[0x324] = 0; + cx[0x3b0] = ((surface.width - 1u) << 14u) | (surface.height - 1u); + cx[0x3b8] = LinearAttrib3; + cx[0x390] = static_cast(address >> 40u); + auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + color.exportIndex = 0; + return color; +} + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, const Surface& surface, std::span pixelCode, VkRect2D scissor) { + const auto target = device.Target(); + constexpr std::uint32_t waveSize = 64; + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast(Triangle.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionFixedPoint); + pixel.targetOutputMode.at(0) = Float32AbgrExport; + pixel.targetExportMapping.fill(0xe4u); + const std::vector pixelUserData(8, 0u); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + + VkPipelineColorBlendAttachmentState blend{}; + blend.blendEnable = VK_FALSE; + blend.colorWriteMask = 0xf; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.colors = {color}; + state.color = color; + state.hasColorTarget = true; + state.renderExtent = {surface.width, surface.height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(surface.height), static_cast(surface.width), -static_cast(surface.height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = scissor; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blends = {blend}; + state.blend = blend; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); +} + +bool Inside(const VkRect2D& rect, std::uint32_t x, std::uint32_t y) { + return x >= static_cast(rect.offset.x) && x < static_cast(rect.offset.x) + rect.extent.width && y >= static_cast(rect.offset.y) && y < static_cast(rect.offset.y) + rect.extent.height; +} + +std::uint8_t* Pixel(const Block& block, const Surface& surface, std::uint32_t x, std::uint32_t y) { + return block.data + surface.offset + static_cast(y) * surface.RowBytes() + static_cast(x) * 16u; +} + +bool Holds(const std::uint8_t* pixel, std::uint8_t value) { + return std::all_of(pixel, pixel + 16, [&](std::uint8_t byte) { return byte == value; }); +} + +bool HoldsOutput(const std::uint8_t* pixel, std::uint32_t x, std::uint32_t y, float z) { + std::array texel{}; + std::memcpy(texel.data(), pixel, sizeof(texel)); + return texel == std::array{static_cast(x), static_cast(y), z, 1.0f}; +} + +bool Expect(AgcDriver::VulkanDevice& device) { + Block block(RegionBytes); + std::fill(block.data, block.data + block.bytes, Initial); + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); + if (AgcDriver::Graphics::HostImportFor(context, block.Address(), block.bytes) == nullptr) return false; + const auto tall = DecodeTarget(block, Tall); + const auto shifted = DecodeTarget(block, Shifted); + Require(tall.tileMode == AgcDriver::Graphics::ColorTileMode::Linear && tall.bytes == Tall.Bytes() && shifted.bytes == Shifted.Bytes(), "the targets are not linear R32G32B32A32 targets"); + constexpr VkRect2D first{{8, 4}, {16, 16}}; + constexpr VkRect2D second{{16, 12}, {24, 16}}; + constexpr VkRect2D whole{{0, 0}, {Shifted.width, Shifted.height}}; + Draw(device, tall, Tall, Program(0, false), Seed); + device.WaitIdle(); + Draw(device, tall, Tall, Program(0, false), first); + Draw(device, tall, Tall, Program(1, true), second); + Draw(device, shifted, Shifted, Program(0, false), whole); + const std::array, 3> sentinels{{{50, 10}, {20, 40}, {30, 200}}}; + const std::array, 2> between{{{30, 6}, {10, 25}}}; + for (const auto& [x, y] : sentinels) std::memset(Pixel(block, Tall, x, y), Sentinel, 16); + for (const auto& [x, y] : between) std::memset(Pixel(block, Tall, x, y), Sentinel, 16); + Require((block.Address() + Shifted.offset) / BlockBytes * BlockBytes == block.Address() + Shifted.offset - GapBytes, "the shifted target does not start past a 64 KiB boundary"); + std::memset(block.data + Shifted.offset - GapBytes, Gap, GapBytes); + device.WaitIdle(); + std::size_t wrongDrawn = 0, wrongSentinels = 0, wrongBetween = 0, wrongUntouched = 0, wrongShifted = 0; + for (std::uint32_t y = 0; y < Tall.height; ++y) { + for (std::uint32_t x = 0; x < Tall.width; ++x) { + const auto* pixel = Pixel(block, Tall, x, y); + const auto at = [&](const auto& point) { return point[0] == x && point[1] == y; }; + if (std::any_of(sentinels.begin(), sentinels.end(), at)) wrongSentinels += Holds(pixel, Sentinel) ? 0u : 1u; + else if (std::any_of(between.begin(), between.end(), at)) wrongBetween += Holds(pixel, Sentinel) ? 0u : 1u; + else if (Inside(second, x, y)) wrongDrawn += HoldsOutput(pixel, x, y, 1.0f) ? 0u : 1u; + else if (Inside(first, x, y) || Inside(Seed, x, y)) wrongDrawn += HoldsOutput(pixel, x, y, 0.0f) ? 0u : 1u; + else wrongUntouched += Holds(pixel, Initial) ? 0u : 1u; + } + } + for (std::uint32_t y = 0; y < Shifted.height; ++y) { + for (std::uint32_t x = 0; x < Shifted.width; ++x) wrongShifted += HoldsOutput(Pixel(block, Shifted, x, y), x, y, 0.0f) ? 0u : 1u; + } + const auto gapLost = static_cast(std::count_if(block.data + Shifted.offset - GapBytes, block.data + Shifted.offset, [](std::uint8_t byte) { return byte != Gap; })); + std::printf("linear target rectangle bound: wrong drawn pixels %zu, lost sentinels %zu/%zu, lost sentinels between the rectangles %zu/%zu, changed untouched pixels %zu, wrong shifted-target pixels %zu, lost gap bytes %zu/%zu\n", wrongDrawn, wrongSentinels, sentinels.size(), wrongBetween, between.size(), wrongUntouched, wrongShifted, gapLost, GapBytes); + Require(wrongDrawn == 0 && wrongShifted == 0, "a drawn linear target's store lost pixels its draws wrote"); + Require(wrongSentinels == 0 && gapLost == 0, "a drawn linear target's store overwrote CPU stores outside the drawn rectangles"); + Require(wrongBetween == 0, "a drawn linear target's store overwrote CPU stores between two draws' scissor rectangles"); + Require(wrongUntouched == 0, "a drawn linear target's store changed pixels no draw and no CPU store wrote"); + return true; +} + +bool ExpectManyRows(AgcDriver::VulkanDevice& device) { + Block block(RegionBytes); + std::fill(block.data, block.data + block.bytes, Initial); + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); + if (AgcDriver::Graphics::HostImportFor(context, block.Address(), block.bytes) == nullptr) return false; + const auto tall = DecodeTarget(block, Tall); + constexpr std::uint32_t Rows = 17; + constexpr std::uint32_t Spacing = 15; + Draw(device, tall, Tall, Program(0, false), Seed); + device.WaitIdle(); + for (std::uint32_t row = 0; row < Rows; ++row) Draw(device, tall, Tall, Program(0, false), VkRect2D{{8, static_cast(row * Spacing)}, {16, 1}}); + constexpr std::array between{10, 70}; + std::memset(Pixel(block, Tall, between[0], between[1]), Sentinel, 16); + device.WaitIdle(); + std::size_t wrongDrawn = 0, wrongUntouched = 0; + for (std::uint32_t y = 0; y < Tall.height; ++y) { + const bool drawnRow = y % Spacing == 0 && y / Spacing < Rows; + for (std::uint32_t x = 0; x < Tall.width; ++x) { + if (x == between[0] && y == between[1]) continue; + const auto* pixel = Pixel(block, Tall, x, y); + if ((drawnRow && x >= 8 && x < 24) || Inside(Seed, x, y)) wrongDrawn += HoldsOutput(pixel, x, y, 0.0f) ? 0u : 1u; + else wrongUntouched += Holds(pixel, Initial) ? 0u : 1u; + } + } + const bool kept = Holds(Pixel(block, Tall, between[0], between[1]), Sentinel); + std::printf("linear target rectangle bound, %u one-row scissors: wrong drawn pixels %zu, sentinel between the rows %s, changed untouched pixels %zu\n", Rows, wrongDrawn, kept ? "kept" : "lost", wrongUntouched); + Require(wrongDrawn == 0, "a drawn linear target's store lost pixels its one-row draws wrote"); + Require(kept, "a drawn linear target's store overwrote a CPU store between two one-row scissors"); + Require(wrongUntouched == 0, "a drawn linear target's store changed pixels no one-row draw and no CPU store wrote"); + return true; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (!Expect(*device) || !ExpectManyRows(*device)) { + std::puts("linear target rectangle bound test skipped: no host import on this device"); + return VulkanTestSkipped; + } + std::puts("linear target rectangle bound 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/MemoryFloatAtomicModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MemoryFloatAtomicModes.cpp new file mode 100644 index 0000000000..572e59d66f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MemoryFloatAtomicModes.cpp @@ -0,0 +1,447 @@ +#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 +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 12; +constexpr std::uint32_t Results = 32; +constexpr std::uint32_t TargetDwords = 512; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Type2D = 9; +constexpr std::size_t BlockBytes = 4096; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; +alignas(4096) std::array Texels{}; + +alignas(256) constexpr std::array BufferCode{ + 0x160200b0u, 0x34180087u, 0xe0381000u, 0x80000201u, 0xe0381010u, 0x80000601u, 0xe0341020u, 0x80000a01u, + 0xbf8c3f70u, 0x341a0082u, 0x4a1a1affu, 0x00001000u, 0x341c0083u, 0x4a1c1cffu, 0x00001200u, 0xe0701000u, + 0x8001020du, 0xbbfd0000u, 0x7e200303u, 0x7e220304u, 0xe0fc5000u, 0x8001100du, 0xbf8c3f70u, 0xe0305000u, + 0x8001120du, 0xbf8c3f70u, 0xe0701000u, 0x8001100cu, 0xe0701004u, 0x8001120cu, 0xe0701080u, 0x8001020du, + 0xbbfd0000u, 0x7e200303u, 0x7e220304u, 0xe1005080u, 0x8001100du, 0xbf8c3f70u, 0xe0305080u, 0x8001120du, + 0xbf8c3f70u, 0xe0701008u, 0x8001100cu, 0xe070100cu, 0x8001120cu, 0xe0701100u, 0x8001020du, 0xbbfd0000u, + 0x7e200303u, 0x7e220304u, 0xe0f85100u, 0x8001100du, 0xbf8c3f70u, 0xe0305100u, 0x8001120du, 0xbf8c3f70u, + 0xe0701010u, 0x8001100cu, 0xe0701014u, 0x8001120cu, 0xe0741000u, 0x8001060eu, 0xbbfd0000u, 0x7e280308u, + 0x7e2a0309u, 0x7e2c030au, 0x7e2e030bu, 0xe17c5000u, 0x8001140eu, 0xbf8c3f70u, 0xe0345000u, 0x8001180eu, + 0xbf8c3f70u, 0xe0741018u, 0x8001140cu, 0xe0741020u, 0x8001180cu, 0xe0741100u, 0x8001060eu, 0xbbfd0000u, + 0x7e280308u, 0x7e2a0309u, 0x7e2c030au, 0x7e2e030bu, 0xe1805100u, 0x8001140eu, 0xbf8c3f70u, 0xe0345100u, + 0x8001180eu, 0xbf8c3f70u, 0xe0741028u, 0x8001140cu, 0xe0741030u, 0x8001180cu, 0xe0741200u, 0x8001060eu, + 0xbbfd0000u, 0x7e280308u, 0x7e2a0309u, 0x7e2c030au, 0x7e2e030bu, 0xe1785200u, 0x8001140eu, 0xbf8c3f70u, + 0xe0345200u, 0x8001180eu, 0xbf8c3f70u, 0xe0741038u, 0x8001140cu, 0xe0741040u, 0x8001180cu, 0xbf810000u, +}; + +alignas(256) constexpr std::array ImageCode{ + 0x160200b0u, 0x34180087u, 0xe0381000u, 0x80000201u, 0xe0381010u, 0x80000601u, 0xe0341020u, 0x80000a01u, + 0xbf8c3f70u, 0x7e3c0300u, 0x7e3e0280u, 0x7e260302u, 0xf03c0108u, 0x0002131eu, 0x7e200303u, 0x7e220304u, + 0xf0782108u, 0x0002101eu, 0x7e240280u, 0xf0442108u, 0x0002121eu, 0xbf8c3f70u, 0xe0701000u, 0x8001100cu, + 0xe0701004u, 0x8001120cu, 0x7e3e0281u, 0x7e260302u, 0xf03c0108u, 0x0002131eu, 0x7e200303u, 0x7e220304u, + 0xf07c2108u, 0x0002101eu, 0x7e240280u, 0xf0442108u, 0x0002121eu, 0xbf8c3f70u, 0xe0701008u, 0x8001100cu, + 0xe070100cu, 0x8001120cu, 0x7e3e0282u, 0x7e260302u, 0xf03c0108u, 0x0002131eu, 0x7e200303u, 0x7e220304u, + 0xf0742308u, 0x0002101eu, 0x7e240280u, 0xf0442108u, 0x0002121eu, 0xbf8c3f70u, 0xe0701010u, 0x8001100cu, + 0xe0701014u, 0x8001120cu, 0xbf810000u, +}; + +alignas(256) constexpr std::array GlobalCode{ + 0x160200b0u, 0x34180087u, 0xe0381000u, 0x80000201u, 0xe0381010u, 0x80000601u, 0xe0341020u, 0x80000a01u, + 0xbf8c3f70u, 0x34340082u, 0xd70f6a1au, 0x00023408u, 0x7e360209u, 0x50363680u, 0x34380083u, 0xd70f6a1cu, + 0x00023808u, 0x7e3a0209u, 0x503a3a80u, 0xdc708000u, 0x007d021au, 0xbbfd0000u, 0x7e200303u, 0x7e220304u, + 0xdcfd8000u, 0x137d101au, 0xbf8c3f70u, 0xdc318000u, 0x127d001au, 0xbf8c3f70u, 0xe0701000u, 0x8001130cu, + 0xe0701004u, 0x8001120cu, 0xdc748200u, 0x007d061cu, 0xbbfd0000u, 0x7e280308u, 0x7e2a0309u, 0x7e2c030au, + 0x7e2e030bu, 0xdd7d8200u, 0x207d141cu, 0xbf8c3f70u, 0xdc358200u, 0x187d001cu, 0xbf8c3f70u, 0xe0741018u, + 0x8001200cu, 0xe0741020u, 0x8001180cu, 0xbf810000u, +}; + +constexpr std::uint32_t Rows32[Threads][3] = { + {0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x00000001u, 0x00000000u}, + {0x00000000u, 0x80000001u, 0x80000001u}, + {0x00000001u, 0x00000000u, 0x00000001u}, + {0x00000001u, 0x3f800000u, 0x3f800000u}, + {0x80000001u, 0x80000000u, 0x80000000u}, + {0x00000001u, 0x80000001u, 0x80000001u}, + {0x007fffffu, 0x00800000u, 0x007fffffu}, + {0x807fffffu, 0xbf800000u, 0x807fffffu}, + {0x7fc00000u, 0x00000001u, 0x7fc00000u}, + {0x00000001u, 0x7fc00000u, 0x00000001u}, + {0x7f800001u, 0x00000001u, 0x7f800001u}, + {0x00000001u, 0x7f800001u, 0x00000001u}, + {0x3f800000u, 0xbf800000u, 0x3f800000u}, + {0x7f800000u, 0xff800000u, 0xff800000u}, + {0x80000000u, 0x00000001u, 0x80000000u}, + {0x7fc12345u, 0x7fc00000u, 0x7fc12345u}, + {0xffc00000u, 0x3f800000u, 0x3f800000u}, + {0x7fa00000u, 0x3f800000u, 0x3f800000u}, + {0x40490fdbu, 0x007fffffu, 0x40490fdbu}, + {0x807fffffu, 0x80000001u, 0x80000001u}, + {0x00800000u, 0x007fffffu, 0x00800000u}, + {0x00000000u, 0xff800001u, 0xff800001u}, + {0x80000001u, 0x00000001u, 0x80000001u}, + {0x7f7fffffu, 0xff7fffffu, 0xff7fffffu}, + {0x00000001u, 0x00000001u, 0x00000001u}, + {0x007fffffu, 0x80000000u, 0x80000000u}, + {0xff800000u, 0x80000001u, 0x80000001u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u}, + {0x3f800000u, 0x00000001u, 0x3f800000u} +}; + +constexpr std::uint64_t Rows64[Threads][3] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull}, + {0x0000000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x8000000000000000ull}, + {0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000000ull}, + {0x0000000000000000ull, 0x8000000000000001ull, 0x8000000000000001ull}, + {0x0000000000000001ull, 0x0000000000000000ull, 0x0000000000000001ull}, + {0x0000000000000001ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {0x8000000000000001ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {0x0000000000000001ull, 0x8000000000000001ull, 0x8000000000000001ull}, + {0x000fffffffffffffull, 0x0010000000000000ull, 0x000fffffffffffffull}, + {0x800fffffffffffffull, 0xbff0000000000000ull, 0x800fffffffffffffull}, + {0x7ff8000000000000ull, 0x0000000000000001ull, 0x7ff8000000000000ull}, + {0x0000000000000001ull, 0x7ff8000000000000ull, 0x7ff8000000000000ull}, + {0x7ff0000000000001ull, 0x0000000000000001ull, 0x7ff0000000000001ull}, + {0x0000000000000001ull, 0x7ff0000000000001ull, 0x7ff0000000000001ull}, + {0x3ff0000000000000ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {0x7ff0000000000000ull, 0xfff0000000000000ull, 0x7ff0000000000000ull}, + {0x8000000000000000ull, 0x0000000000000001ull, 0x8000000000000000ull}, + {0x7ff8000000012345ull, 0x7ff8000000000000ull, 0x7ff8000000000000ull}, + {0xfff8000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {0xfff0000000000001ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {0x400921fb54442d18ull, 0x000fffffffffffffull, 0x400921fb54442d18ull}, + {0x800fffffffffffffull, 0x8000000000000001ull, 0x8000000000000001ull}, + {0x0010000000000000ull, 0x000fffffffffffffull, 0x0010000000000000ull}, + {0x0000000000000000ull, 0xfff0000000000001ull, 0x0000000000000000ull}, + {0x8000000000000001ull, 0x0000000000000001ull, 0x8000000000000001ull}, + {0x7fefffffffffffffull, 0xfff0000000000000ull, 0x7fefffffffffffffull}, + {0x0000000000000001ull, 0x0000000000000001ull, 0x0000000000000001ull}, + {0x000fffffffffffffull, 0x8000000000000000ull, 0x8000000000000000ull}, + {0xfff0000000000000ull, 0x8000000000000001ull, 0x8000000000000001ull}, + {0x7ff8000000000000ull, 0x7ff8000000000000ull, 0x7ff8000000000000ull}, + {0x3ff0000000000000ull, 0x0000000000000001ull, 0x3ff0000000000000ull} +}; + +constexpr std::uint32_t Flushed32[Threads][3] = { + {0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u}, + {0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000001u}, + {0x80000000u, 0x00000000u, 0x80000001u}, + {0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000001u}, + {0x80000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x00000000u, 0x80000001u}, + {0x00000000u, 0x00800000u, 0x00800000u}, + {0xbf800000u, 0x80000000u, 0xbf800000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x7fc00001u, 0x7fc00001u, 0x7f800001u}, + {0x7fc00001u, 0x7fc00001u, 0x7f800001u}, + {0xbf800000u, 0x3f800000u, 0xbf800000u}, + {0xff800000u, 0x7f800000u, 0x7f800000u}, + {0x80000000u, 0x00000000u, 0x00000001u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc12345u}, + {0x3f800000u, 0x3f800000u, 0xffc00000u}, + {0x7fe00000u, 0x7fe00000u, 0x7fa00000u}, + {0x00000000u, 0x40490fdbu, 0x007fffffu}, + {0x80000000u, 0x80000000u, 0x80000001u}, + {0x00000000u, 0x00800000u, 0x007fffffu}, + {0xffc00001u, 0xffc00001u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x00000001u}, + {0xff7fffffu, 0x7f7fffffu, 0x7f7fffffu}, + {0x00000000u, 0x00000000u, 0x00000001u}, + {0x80000000u, 0x00000000u, 0x80000000u}, + {0xff800000u, 0x80000000u, 0xff800000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u}, + {0x00000000u, 0x3f800000u, 0x00000001u} +}; + +constexpr std::uint32_t Kept32[Threads][3] = { + {0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u}, + {0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000001u, 0x00000001u}, + {0x80000001u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000001u, 0x00000000u}, + {0x00000001u, 0x3f800000u, 0x00000001u}, + {0x80000001u, 0x80000000u, 0x80000001u}, + {0x80000001u, 0x00000001u, 0x00000001u}, + {0x007fffffu, 0x00800000u, 0x00800000u}, + {0xbf800000u, 0x807fffffu, 0xbf800000u}, + {0x00000001u, 0x00000001u, 0x7fc00000u}, + {0x00000001u, 0x00000001u, 0x7fc00000u}, + {0x7fc00001u, 0x7fc00001u, 0x7f800001u}, + {0x7fc00001u, 0x7fc00001u, 0x7f800001u}, + {0xbf800000u, 0x3f800000u, 0xbf800000u}, + {0xff800000u, 0x7f800000u, 0x7f800000u}, + {0x80000000u, 0x00000001u, 0x00000001u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc12345u}, + {0x3f800000u, 0x3f800000u, 0xffc00000u}, + {0x7fe00000u, 0x7fe00000u, 0x7fa00000u}, + {0x007fffffu, 0x40490fdbu, 0x007fffffu}, + {0x807fffffu, 0x80000001u, 0x807fffffu}, + {0x007fffffu, 0x00800000u, 0x007fffffu}, + {0xffc00001u, 0xffc00001u, 0x00000000u}, + {0x80000001u, 0x00000001u, 0x00000001u}, + {0xff7fffffu, 0x7f7fffffu, 0x7f7fffffu}, + {0x00000001u, 0x00000001u, 0x00000001u}, + {0x80000000u, 0x007fffffu, 0x007fffffu}, + {0xff800000u, 0x80000001u, 0xff800000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u}, + {0x00000001u, 0x3f800000u, 0x00000001u} +}; + +constexpr std::uint64_t Flushed64[Threads][3] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x8000000000000000ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull}, + {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x8000000000000001ull}, + {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull}, + {0x0000000000000000ull, 0x3ff0000000000000ull, 0x0000000000000001ull}, + {0x8000000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x8000000000000001ull}, + {0x0000000000000000ull, 0x0010000000000000ull, 0x0010000000000000ull}, + {0xbff0000000000000ull, 0x8000000000000000ull, 0xbff0000000000000ull}, + {0x0000000000000000ull, 0x0000000000000000ull, 0x7ff8000000000000ull}, + {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0x7ff0000000000001ull}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0x0000000000000001ull}, + {0xbff0000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {0xfff0000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000000ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull}, + {0x7ff8000000000000ull, 0x7ff8000000000000ull, 0x7ff8000000012345ull}, + {0x3ff0000000000000ull, 0x3ff0000000000000ull, 0xfff8000000000000ull}, + {0xfff8000000000001ull, 0xfff8000000000001ull, 0xfff0000000000001ull}, + {0x0000000000000000ull, 0x400921fb54442d18ull, 0x000fffffffffffffull}, + {0x8000000000000000ull, 0x8000000000000000ull, 0x8000000000000001ull}, + {0x0000000000000000ull, 0x0010000000000000ull, 0x000fffffffffffffull}, + {0xfff8000000000001ull, 0xfff8000000000001ull, 0xfff0000000000001ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull}, + {0xfff0000000000000ull, 0x7fefffffffffffffull, 0xfff0000000000000ull}, + {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x8000000000000000ull}, + {0xfff0000000000000ull, 0x8000000000000000ull, 0xfff0000000000000ull}, + {0x7ff8000000000000ull, 0x7ff8000000000000ull, 0x7ff8000000000000ull}, + {0x0000000000000000ull, 0x3ff0000000000000ull, 0x0000000000000001ull} +}; + +constexpr std::uint64_t Kept64[Threads][3] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x8000000000000000ull}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull}, + {0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000001ull}, + {0x8000000000000001ull, 0x0000000000000000ull, 0x0000000000000000ull}, + {0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000000ull}, + {0x0000000000000001ull, 0x3ff0000000000000ull, 0x0000000000000001ull}, + {0x8000000000000001ull, 0x8000000000000000ull, 0x8000000000000001ull}, + {0x8000000000000001ull, 0x0000000000000001ull, 0x0000000000000001ull}, + {0x000fffffffffffffull, 0x0010000000000000ull, 0x0010000000000000ull}, + {0xbff0000000000000ull, 0x800fffffffffffffull, 0xbff0000000000000ull}, + {0x0000000000000001ull, 0x0000000000000001ull, 0x7ff8000000000000ull}, + {0x0000000000000001ull, 0x0000000000000001ull, 0x0000000000000001ull}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0x7ff0000000000001ull}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0x0000000000000001ull}, + {0xbff0000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {0xfff0000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000000ull}, + {0x8000000000000000ull, 0x0000000000000001ull, 0x0000000000000001ull}, + {0x7ff8000000000000ull, 0x7ff8000000000000ull, 0x7ff8000000012345ull}, + {0x3ff0000000000000ull, 0x3ff0000000000000ull, 0xfff8000000000000ull}, + {0xfff8000000000001ull, 0xfff8000000000001ull, 0xfff0000000000001ull}, + {0x000fffffffffffffull, 0x400921fb54442d18ull, 0x000fffffffffffffull}, + {0x800fffffffffffffull, 0x8000000000000001ull, 0x800fffffffffffffull}, + {0x000fffffffffffffull, 0x0010000000000000ull, 0x000fffffffffffffull}, + {0xfff8000000000001ull, 0xfff8000000000001ull, 0xfff0000000000001ull}, + {0x8000000000000001ull, 0x0000000000000001ull, 0x0000000000000001ull}, + {0xfff0000000000000ull, 0x7fefffffffffffffull, 0xfff0000000000000ull}, + {0x0000000000000001ull, 0x0000000000000001ull, 0x0000000000000001ull}, + {0x8000000000000000ull, 0x000fffffffffffffull, 0x000fffffffffffffull}, + {0xfff0000000000000ull, 0x8000000000000001ull, 0xfff0000000000000ull}, + {0x7ff8000000000000ull, 0x7ff8000000000000ull, 0x7ff8000000000000ull}, + {0x0000000000000001ull, 0x3ff0000000000000ull, 0x0000000000000001ull} +}; + +constexpr std::array Names{"fmin", "fmax", "fcmpswap"}; + +enum class Kind { Buffer, Image, Global }; + +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, "memory float atomic modes: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + } + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + 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::array TextureDescriptor(const void* data) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32Float << 20u) | (((Threads - 1u) & 3u) << 30u), + ((Threads - 1u) >> 2u) | (2u << 14u), + 0xfacu | (Type2D << 28u), + 0u, 0u, 0u, 0u, + }; +} + +std::string Hex(std::uint64_t value, int digits) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%0*llx", digits, static_cast(value)); + return text; +} + +const char* KindName(Kind kind) { + return kind == Kind::Buffer ? "buffer" : kind == Kind::Image ? "image" : "global"; +} + +void FillInput() { + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto* row = &Input[tid * Inputs]; + row[0] = Rows32[tid][0]; + row[1] = Rows32[tid][1]; + row[2] = Rows32[tid][2]; + for (std::uint32_t i = 0; i < 3u; ++i) { + row[4u + 2u * i] = static_cast(Rows64[tid][i]); + row[5u + 2u * i] = static_cast(Rows64[tid][i] >> 32u); + } + } +} + +void Run(AgcDriver::VulkanDevice& device, Kind kind, GuestBlock& guest, const std::optional& floatMode) { + Output.fill(0xdeadbeefu); + Texels.fill(0xdeadbeefu); + std::memset(guest.Data(), 0xcd, BlockBytes); + std::vector userData(16, 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); + if (kind == Kind::Image) { + const auto texture = TextureDescriptor(Texels.data()); + std::copy(texture.begin(), texture.end(), userData.begin() + 8); + } else if (kind == Kind::Global) { + const auto address = static_cast(reinterpret_cast(guest.Data())); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + } + const std::span code = kind == Kind::Buffer ? std::span(BufferCode) : kind == Kind::Image ? std::span(ImageCode) : std::span(GlobalCode); + 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; + request.context.floatMode = floatMode; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(Kind kind, const char* mode, bool flush32, bool flush64) { + const auto where = std::string("memory float atomic modes: ") + KindName(kind) + ", " + mode + ", lane "; + const std::uint32_t ops = kind == Kind::Global ? 1u : 3u; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t op = 0; op < ops; ++op) { + const std::uint32_t expected = flush32 ? Flushed32[tid][op] : Kept32[tid][op]; + Require(out[2u * op] == Rows32[tid][0], where + std::to_string(tid) + " " + Names[op] + " returned " + Hex(out[2u * op], 8) + ", expected " + Hex(Rows32[tid][0], 8)); + Require(out[2u * op + 1u] == expected, where + std::to_string(tid) + " " + Names[op] + " stored " + Hex(out[2u * op + 1u], 8) + ", expected " + Hex(expected, 8)); + } + if (kind == Kind::Image) continue; + for (std::uint32_t op = 0; op < ops; ++op) { + const std::uint64_t returned = out[6u + 4u * op] | (static_cast(out[7u + 4u * op]) << 32u); + const std::uint64_t stored = out[8u + 4u * op] | (static_cast(out[9u + 4u * op]) << 32u); + const std::uint64_t expected = flush64 ? Flushed64[tid][op] : Kept64[tid][op]; + Require(returned == Rows64[tid][0], where + std::to_string(tid) + " " + Names[op] + "_x2 returned " + Hex(returned, 16) + ", expected " + Hex(Rows64[tid][0], 16)); + Require(stored == expected, where + std::to_string(tid) + " " + Names[op] + "_x2 stored " + Hex(stored, 16) + ", expected " + Hex(expected, 16)); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (!TargetHasCapability(device->Target(), spv::CapabilityInt64Atomics)) { + std::puts("skipped, the device has no shaderBufferInt64Atomics"); + return VulkanTestSkipped; + } + GuestBlock guest; + FillInput(); + struct Mode { + std::optional floatMode; + const char* name; + bool flush32; + bool flush64; + bool global; + }; + const std::array modes{{ + {std::nullopt, "no float mode", true, false, false}, + {ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}, "FLOAT_MODE 0xc0", true, false, false}, + {ShaderRecompiler::ShaderFloatMode{0xf0u, true, false, false}, "FLOAT_MODE 0xf0", false, false, false}, + {ShaderRecompiler::ShaderFloatMode{0x00u, true, false, false}, "FLOAT_MODE 0x00", true, true, true}, + {ShaderRecompiler::ShaderFloatMode{0x50u, true, false, false}, "FLOAT_MODE 0x50", false, false, false}, + {ShaderRecompiler::ShaderFloatMode{0xa0u, true, false, false}, "FLOAT_MODE 0xa0", true, true, false}, + }}; + for (const auto kind : {Kind::Buffer, Kind::Image, Kind::Global}) { + for (const auto& mode : modes) { + if (kind == Kind::Global && !mode.global) continue; + Run(*device, kind, guest, mode.floatMode); + Check(kind, mode.name, mode.flush32, mode.flush64); + } + } + std::puts("memory float atomic modes 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/MeshProvokingVertex.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingVertex.cpp index 3c7b2a9333..1cb6bea34a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingVertex.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingVertex.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -23,9 +24,9 @@ constexpr std::uint32_t Width = 64; constexpr std::uint32_t Height = 64; using Image = std::array; alignas(256) Image Pixels{}; -alignas(256) constexpr std::array Indices{0, 1, 2, 0}; -alignas(256) constexpr std::array, 3> Vertices{{ - {-0.9f, -0.8f, 0, 1, 1, 0, 0, 1}, {-0.1f, -0.8f, 0, 1, 0, 1, 0, 1}, {-0.5f, 0.8f, 0, 1, 0, 0, 1, 1} +alignas(256) constexpr std::array Indices{0, 1, 2, 3}; +alignas(256) constexpr std::array, 4> Vertices{{ + {-0.9f, -0.8f, 0, 1, 1, 0, 0, 1}, {-0.1f, -0.8f, 0, 1, 0, 1, 0, 1}, {-0.5f, 0.8f, 0, 1, 0, 0, 1, 1}, {0.3f, 0.8f, 0, 1, 1, 1, 0, 1} }}; alignas(256) constexpr std::array FlatPixelCode{0xc8120002u, 0xc8160102u, 0xc81a0202u, 0xc81e0302u, 0xf800180fu, 0x07060504u, 0xbf810000u}; alignas(256) constexpr std::array SmoothPixelCode{0xc8100000u, 0xc8110001u, 0xc8140100u, 0xc8150101u, 0xc8180200u, 0xc8190201u, 0xc81c0300u, 0xc81d0301u, 0xf800180fu, 0x07060504u, 0xbf810000u}; @@ -64,7 +65,7 @@ void Write(std::vector& commands, std::uint32_t opcode, std::uint commands.insert(commands.end(), {0xc0010000u | (opcode << 8u), offset, value}); } -Image Draw(const std::uint32_t* pixelCode, bool flat, bool last) { +Image Draw(const std::uint32_t* pixelCode, bool flat, bool last, bool strip = false) { Pixels.fill(std::byte{0}); const auto color = reinterpret_cast(Pixels.data()); const auto geometry = reinterpret_cast(GeometryCode.data()); @@ -98,13 +99,14 @@ Image Draw(const std::uint32_t* pixelCode, bool flat, bool last) { {0x8, static_cast(pixel >> 8u)}, {0x9, static_cast(pixel >> 40u)}, {0xb, 0} }; std::vector commands; - Write(commands, 0x79, 0x242, 4); + Write(commands, 0x79, 0x242, strip ? 6u : 4u); Write(commands, 0x79, 0x25b, (12u << 9u) | 4u); for (const auto& [offset, value] : context) Write(commands, 0x69, offset, value); for (const auto& [offset, value] : shaders) Write(commands, 0x76, offset, value); commands.insert(commands.end(), {0xc0002a00u, 0u}); const auto address = reinterpret_cast(Indices.data()); - commands.insert(commands.end(), {0xc0042700u, 3u, static_cast(address), static_cast(address >> 32u), 3u, 0u}); + const std::uint32_t count = strip ? 4u : 3u; + commands.insert(commands.end(), {0xc0042700u, count, static_cast(address), static_cast(address >> 32u), count, 0u}); Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; Require(sceAgcDriverSubmitDcb(&packet) == 0, "mesh submission failed"); AgcDriverWaitIdle_nid_postfix(); @@ -132,6 +134,25 @@ void FlatInputs() { } } +constexpr std::array, 4> SecondInside{{{26, 48}, {32, 48}, {29, 44}, {29, 52}}}; + +void CheckFlat(const Image& image, std::span> pixels, const std::array& expected, const std::string& what) { + for (const auto& [x, y] : pixels) { + for (std::uint32_t channel = 0; channel < 4; ++channel) { + Require(Channel(image, x, y, channel) == expected[channel], what + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(Channel(image, x, y, channel)) + ", expected " + std::to_string(expected[channel])); + } + } +} + +void StripFlatInputs() { + const auto last = Draw(FlatPixelCode.data(), true, true, true); + CheckFlat(last, Inside, {0u, 0u, 255u, 255u}, "triangle strip, last provoking vertex, triangle 0 (the color of vertex 2)"); + CheckFlat(last, SecondInside, {255u, 255u, 0u, 255u}, "triangle strip, last provoking vertex, triangle 1 (the color of vertex 3)"); + Require(Channel(last, 48, 48, 3) == 0, "triangle strip, last provoking vertex: the draw wrote outside its triangles"); + const auto first = Draw(FlatPixelCode.data(), true, false, true); + CheckFlat(first, Inside, {255u, 0u, 0u, 255u}, "triangle strip, first provoking vertex, triangle 0 (the color of vertex 0)"); +} + void SmoothInputs() { const auto first = Draw(SmoothPixelCode.data(), false, false); for (const auto& [x, y] : Inside) { @@ -169,9 +190,10 @@ int main() { Register(FlatPixelHeader, FlatPixelCode.data(), sizeof(FlatPixelCode), 1); Register(SmoothPixelHeader, SmoothPixelCode.data(), sizeof(SmoothPixelCode), 1); FlatInputs(); + StripFlatInputs(); if (smooth) SmoothInputs(); AgcDriverShutdown_nid_postfix(); - std::cout << "geometry-program triangles take flat inputs from vertex 0 with the first provoking vertex and from vertex 2 with the last" << (smooth ? ", and interpolate smooth inputs the same in both modes\n" : "; smooth inputs skipped without fragment shader barycentrics\n"); + std::cout << "geometry-program triangles take flat inputs from vertex 0 with the first provoking vertex and from vertex 2 with the last, and the second triangle of a strip takes them from vertex 3 with the last" << (smooth ? ", and interpolate smooth inputs the same in both modes\n" : "; smooth inputs skipped without fragment shader barycentrics\n"); return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MullitF32DenormalModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MullitF32DenormalModes.cpp new file mode 100644 index 0000000000..2dd9886984 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MullitF32DenormalModes.cpp @@ -0,0 +1,226 @@ +#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 ConstantForms = 4; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array MullitCode{ + 0x34020084u, 0x34040082u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e140280u, 0xd550000au, 0x041a0b04u, + 0xe0701000u, 0x80010a02u, 0xbf810000u, +}; + +alignas(256) constexpr std::array ConstantMullitCode{ + 0x34020084u, 0x34040084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xd550000au, 0x041a08f2u, 0xd550000bu, + 0x0419e904u, 0xd550000cu, 0x041a0a81u, 0xd550000du, 0x041a08ffu, 0x3fc00000u, 0xe0781000u, 0x80010a02u, + 0xbf810000u, +}; + +constexpr const char* ConstantNames[ConstantForms] = {"v_mullit_f32 1.0, v4, v6", "v_mullit_f32 v4, 2.0, v6", "v_mullit_f32 0x1, v5, v6", "v_mullit_f32 0x3fc00000, v4, v6"}; + +struct Vector { + std::uint32_t lhs; + std::uint32_t rhs; + std::uint32_t limit; + std::uint32_t expected[8]; +}; + +constexpr Vector Vectors[] = { + {0x00000000u, 0x00000000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}}, + {0x00000000u, 0x00800000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}}, + {0x00000001u, 0x80000001u, 0x00800000u, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u}}, + {0x00000001u, 0x3f800000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000001u, 0x00000001u}}, + {0x00000001u, 0x3f800000u, 0x00800000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u}}, + {0x00000001u, 0xbf800000u, 0x00800000u, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000001u, 0x80000001u}}, + {0x00000001u, 0x7f7fffffu, 0x00800000u, {0x00000000u, 0x00000000u, 0x34ffffffu, 0x34ffffffu, 0x00000000u, 0x00000000u, 0x34ffffffu, 0x34ffffffu}}, + {0x00800000u, 0x00000000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}}, + {0x00800000u, 0x80000001u, 0x00800000u, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u}}, + {0x00800000u, 0x00800000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}}, + {0x00800000u, 0x3f000000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00400000u, 0x00400000u}}, + {0x00800000u, 0x3f000000u, 0x00800000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00400000u, 0x00400000u, 0x00400000u, 0x00400000u}}, + {0x3f000000u, 0x007fffffu, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00400000u, 0x00400000u}}, + {0x3f000000u, 0x007fffffu, 0x00800000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00400000u, 0x00400000u}}, + {0x3f000000u, 0x807fffffu, 0x00800000u, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80400000u, 0x80400000u}}, + {0x7f800001u, 0x00000001u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7fc00001u, 0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7fc00001u}}, + {0x7f800001u, 0x00000001u, 0x00800000u, {0x00000000u, 0x00000000u, 0x7f800001u, 0x7fc00001u, 0x00000000u, 0x00000000u, 0x7f800001u, 0x7fc00001u}}, + {0x7f800001u, 0x00800000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7fc00001u, 0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7fc00001u}}, + {0x7f800001u, 0x00800000u, 0x00800000u, {0x7f800001u, 0x7fc00001u, 0x7f800001u, 0x7fc00001u, 0x7f800001u, 0x7fc00001u, 0x7f800001u, 0x7fc00001u}}, + {0x3f800000u, 0x40000000u, 0x3f800000u, {0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u}}, + {0x3f800000u, 0xff7fffffu, 0x3f800000u, {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu}}, + {0x3f800000u, 0x3f800000u, 0x80000000u, {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu}}, + {0x7f7fffffu, 0x7f7fffffu, 0x3f800000u, {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}}, +}; + +struct ConstantVector { + std::uint32_t inputs[3]; + std::uint32_t expected[4][ConstantForms]; +}; + +constexpr ConstantVector ConstantVectors[] = { + {{0x00000001u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000001u, 0x00000002u, 0x00000000u, 0x00000002u}}}, + {{0x00000001u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000001u, 0x00000002u, 0x00000000u, 0x00000002u}}}, + {{0x00000002u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u}}}, + {{0x00000002u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u}}}, + {{0x00000003u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000003u, 0x00000006u, 0x00000000u, 0x00000004u}}}, + {{0x00000003u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000003u, 0x00000006u, 0x00000000u, 0x00000004u}}}, + {{0x00155555u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00155555u, 0x002aaaaau, 0x00000000u, 0x00200000u}}}, + {{0x00155555u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00155555u, 0x002aaaaau, 0x00000000u, 0x00200000u}}}, + {{0x003fffffu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x003fffffu, 0x007ffffeu, 0x00000000u, 0x005ffffeu}}}, + {{0x003fffffu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x003fffffu, 0x007ffffeu, 0x00000000u, 0x005ffffeu}}}, + {{0x00400000u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00800000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00400000u, 0x00800000u, 0x00000000u, 0x00600000u}}}, + {{0x00400000u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00800000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00400000u, 0x00800000u, 0x00000000u, 0x00600000u}}}, + {{0x00400001u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00800002u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00400001u, 0x00800002u, 0x00000000u, 0x00600002u}}}, + {{0x00400001u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00800002u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00400001u, 0x00800002u, 0x00000000u, 0x00600002u}}}, + {{0x00555555u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00aaaaaau, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00555555u, 0x00aaaaaau, 0x00000000u, 0x00800000u}}}, + {{0x00555555u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00aaaaaau, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00555555u, 0x00aaaaaau, 0x00000000u, 0x00800000u}}}, + {{0x007ffffeu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00fffffcu, 0x00000000u, 0x00bffffdu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x007ffffeu, 0x00fffffcu, 0x00000000u, 0x00bffffdu}}}, + {{0x007ffffeu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00fffffcu, 0x00000000u, 0x00bffffdu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x007ffffeu, 0x00fffffcu, 0x00000000u, 0x00bffffdu}}}, + {{0x007fffffu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00fffffeu, 0x00000000u, 0x00bffffeu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x007fffffu, 0x00fffffeu, 0x00000000u, 0x00bffffeu}}}, + {{0x007fffffu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00fffffeu, 0x00000000u, 0x00bffffeu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x007fffffu, 0x00fffffeu, 0x00000000u, 0x00bffffeu}}}, + {{0x80000001u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000001u, 0x80000002u, 0x00000000u, 0x80000002u}}}, + {{0x80000001u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000001u, 0x80000002u, 0x00000000u, 0x80000002u}}}, + {{0x80000002u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000002u, 0x80000004u, 0x00000000u, 0x80000003u}}}, + {{0x80000002u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000002u, 0x80000004u, 0x00000000u, 0x80000003u}}}, + {{0x80000003u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000003u, 0x80000006u, 0x00000000u, 0x80000004u}}}, + {{0x80000003u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000003u, 0x80000006u, 0x00000000u, 0x80000004u}}}, + {{0x80155555u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80155555u, 0x802aaaaau, 0x00000000u, 0x80200000u}}}, + {{0x80155555u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80155555u, 0x802aaaaau, 0x00000000u, 0x80200000u}}}, + {{0x803fffffu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x803fffffu, 0x807ffffeu, 0x00000000u, 0x805ffffeu}}}, + {{0x803fffffu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x803fffffu, 0x807ffffeu, 0x00000000u, 0x805ffffeu}}}, + {{0x80400000u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80800000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80400000u, 0x80800000u, 0x00000000u, 0x80600000u}}}, + {{0x80400000u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80800000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80400000u, 0x80800000u, 0x00000000u, 0x80600000u}}}, + {{0x80400001u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80800002u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80400001u, 0x80800002u, 0x00000000u, 0x80600002u}}}, + {{0x80400001u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80800002u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80400001u, 0x80800002u, 0x00000000u, 0x80600002u}}}, + {{0x80555555u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80aaaaaau, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80555555u, 0x80aaaaaau, 0x00000000u, 0x80800000u}}}, + {{0x80555555u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80aaaaaau, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80555555u, 0x80aaaaaau, 0x00000000u, 0x80800000u}}}, + {{0x807ffffeu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80fffffcu, 0x00000000u, 0x80bffffdu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x807ffffeu, 0x80fffffcu, 0x00000000u, 0x80bffffdu}}}, + {{0x807ffffeu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80fffffcu, 0x00000000u, 0x80bffffdu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x807ffffeu, 0x80fffffcu, 0x00000000u, 0x80bffffdu}}}, + {{0x807fffffu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80fffffeu, 0x00000000u, 0x80bffffeu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x807fffffu, 0x80fffffeu, 0x00000000u, 0x80bffffeu}}}, + {{0x807fffffu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80fffffeu, 0x00000000u, 0x80bffffeu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x807fffffu, 0x80fffffeu, 0x00000000u, 0x80bffffeu}}}, +}; + +static_assert(std::size(Vectors) <= Threads && std::size(ConstantVectors) <= Threads); + +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; +} + +bool KeepsDenormals(const AgcDriver::VulkanDevice& device) { + const auto target = device.Target(); + return target.float32DenormPreserve && !target.float32FlushToZero; +} + +bool Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t denorm32, bool ieee) { + 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::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; + request.context.floatMode = ShaderRecompiler::ShaderFloatMode{(denorm32 << 4u) | (3u << 6u), true, ieee, false}; + std::optional result; + try { + result.emplace(ShaderRecompiler::Recompile(request)); + } catch (const std::exception& error) { + const std::string message = error.what(); + Require(!KeepsDenormals(device) && (denorm32 == 1u || denorm32 == 2u) && message.find("needs DenormPreserve 32") != std::string::npos, + "v_mullit_f32 denorm32 " + std::to_string(denorm32) + ": " + message); + return false; + } + device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + return true; +} + +void CheckRegisters(std::uint32_t denorm32, bool ieee) { + for (std::uint32_t row = 0; row < std::size(Vectors); ++row) { + const auto& vector = Vectors[row]; + const auto actual = Output[row]; + const auto expected = vector.expected[denorm32 * 2u + (ieee ? 1u : 0u)]; + Require(actual == expected, "v_mullit_f32 denorm32 " + std::to_string(denorm32) + (ieee ? " ieee" : " no ieee") + " inputs " + Hex(vector.lhs) + " " + Hex(vector.rhs) + " " + + Hex(vector.limit) + " is " + Hex(actual) + ", expected " + Hex(expected)); + } +} + +void CheckConstants(std::uint32_t denorm32, bool ieee) { + for (std::uint32_t row = 0; row < std::size(ConstantVectors); ++row) { + const auto& vector = ConstantVectors[row]; + for (std::uint32_t form = 0; form < ConstantForms; ++form) { + const auto actual = Output[row * ConstantForms + form]; + const auto expected = vector.expected[denorm32][form]; + Require(actual == expected, std::string(ConstantNames[form]) + " denorm32 " + std::to_string(denorm32) + (ieee ? " ieee" : " no ieee") + " inputs " + Hex(vector.inputs[0]) + " " + + Hex(vector.inputs[1]) + " " + Hex(vector.inputs[2]) + " is " + Hex(actual) + ", expected " + Hex(expected)); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool keeps = KeepsDenormals(*device); + std::uint32_t checked = 0u; + for (std::uint32_t denorm32 = 0u; denorm32 < 4u; ++denorm32) { + if (denorm32 == 3u && !keeps) continue; + for (const bool ieee : {false, true}) { + Input.fill(0u); + for (std::uint32_t row = 0; row < std::size(Vectors); ++row) { + Input[row * Inputs] = Vectors[row].lhs; + Input[row * Inputs + 1] = Vectors[row].rhs; + Input[row * Inputs + 2] = Vectors[row].limit; + } + if (Run(*device, MullitCode, denorm32, ieee)) { + CheckRegisters(denorm32, ieee); + ++checked; + } + Input.fill(0u); + for (std::uint32_t row = 0; row < std::size(ConstantVectors); ++row) std::copy(std::begin(ConstantVectors[row].inputs), std::end(ConstantVectors[row].inputs), Input.begin() + row * Inputs); + if (Run(*device, ConstantMullitCode, denorm32, ieee)) { + CheckConstants(denorm32, ieee); + ++checked; + } + } + } + std::printf("v_mullit_f32 denormal mode tests passed (%u of 16 runs checked; denormal modes 1 and 2 need DenormPreserve 32, mode 3 is checked only where the device keeps f32 denormals)\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/MultisampleCenteredLines.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleCenteredLines.cpp new file mode 100644 index 0000000000..a5e2dfc7f6 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleCenteredLines.cpp @@ -0,0 +1,330 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Side = 16; +constexpr std::uint32_t Samples = 4; +constexpr std::uint32_t SampleShift = 2; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t WaveSize = 64; +constexpr std::uint32_t Float16x4Info = 0x0730u; +constexpr std::uint32_t MultisampledAttrib = (SampleShift << 12u) | (SampleShift << 15u); +constexpr std::uint32_t TiledAttrib3 = 0x4dc6c000u; +constexpr std::uint32_t AaConfig4x = 0x0020c002u; +constexpr std::uint32_t MsaaEnableClear = 2; +constexpr std::uint32_t StandardLocations4x = 0x622ae6aeu; +constexpr std::uint32_t Format16161616Float = 71; +constexpr std::uint32_t Type2DMsaa = 14; +constexpr std::uint32_t TileRenderTarget = 0x1b; +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Words = 32; +constexpr std::uint32_t LoadResult = 12; +constexpr std::array Primitives{2u, 3u}; +constexpr std::array Drawn{0.25f, 0.5f, 0.125f, 1.0f}; +constexpr std::array DrawnBytes{std::byte{64}, std::byte{128}, std::byte{32}, std::byte{255}}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +alignas(256) constexpr std::array PixelCode{ + 0x7e0802ff, 0x3e800000, 0x7e0a02ff, 0x3f000000, 0x7e0c02ff, 0x3e000000, 0x7e0e02ff, 0x3f800000, 0xf800180f, 0x07060504, 0xbf810000, +}; + +alignas(256) constexpr std::array Unorm8PixelCode{ + 0x7e0802ff, std::bit_cast(64.0f / 255.0f), 0x7e0a02ff, std::bit_cast(128.0f / 255.0f), 0x7e0c02ff, std::bit_cast(32.0f / 255.0f), 0x7e0e02ff, std::bit_cast(1.0f), 0xf800180f, 0x07060504, 0xbf810000, +}; + +alignas(256) constexpr std::array LoadCode{ + 0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c, + 0x80000501, 0xe0301010, 0x80000601, 0xe0301014, 0x80000701, 0xbf8c3f70, 0xf0001f30, 0x00011005, + 0xbf8c3f70, 0xe0701030, 0x80001001, 0xe0701034, 0x80001101, 0xe0701038, 0x80001201, 0xe070103c, + 0x80001301, 0xbf810000, +}; + +alignas(256) constexpr std::array, 4> Lines{{ + {-0.828125f, -0.4375f, 0.5f, 1.0f}, {0.90625f, 0.140625f, 0.5f, 1.0f}, + {-0.265625f, 0.90625f, 0.5f, 1.0f}, {0.34375f, -0.921875f, 0.5f, 1.0f}, +}}; + +alignas(256) std::array Buffer{}; +alignas(256) std::array, Primitives.size()> LinePixels{}; + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes) { +#ifdef _WIN32 + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); +#endif + Require(data != nullptr, "cannot allocate the multisampled color target"); + std::memset(data, 0, bytes); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + VirtualFree(data, 0, MEM_RELEASE); +#else + std::free(data); +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + std::uint8_t* data = nullptr; +}; + +AgcDriver::QueueState LineQueue(const Block& block, std::uint32_t primitive) { + const auto half = std::bit_cast(static_cast(Side) / 2.0f); + const auto extent = (Side << 16u) | Side; + AgcDriver::QueueState queue; + queue.userConfig[0x242] = primitive; + queue.context = { + {0x2d5, 0x2000}, {0x1b6, 0}, {0x207, 0}, {0x200, 0}, {0x203, 0x800}, + {0x2dc, 0xaa00}, {0x2f8, AaConfig4x}, {0x292, MsaaEnableClear}, {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}, + {0x318, static_cast(block.Address() >> 8u)}, {0x390, static_cast(block.Address() >> 40u)}, + {0x31b, 0}, {0x31c, Float16x4Info}, {0x31d, MultisampledAttrib}, + {0x3b0, ((Side - 1u) << 14u) | (Side - 1u)}, {0x3b8, TiledAttrib3}, {0x1e0, 0}, + {0xc, 0}, {0xd, extent}, {0x81, 0x80000000}, {0x82, extent}, + {0x90, 0x80000000}, {0x91, extent}, {0x94, 0x80000000}, {0x95, extent}, + {0x10f, half}, {0x110, half}, {0x111, std::bit_cast(-static_cast(Side) / 2.0f)}, {0x112, half}, + {0x113, std::bit_cast(1.0f)}, {0x114, 0}, {0xb4, 0}, {0xb5, std::bit_cast(1.0f)} + }; + for (std::uint32_t offset = 0x2fe; offset <= 0x30d; ++offset) queue.context[offset] = (offset - 0x2feu) % 4u == 0 ? StandardLocations4x : 0u; + queue.shader[0x008] = 0x100; + queue.shader[0x009] = 0; + return queue; +} + +AgcDriver::Graphics::State DecodeLines(const Block& block, std::uint32_t primitive) { + const auto state = AgcDriver::Graphics::DecodeState(LineQueue(block, primitive)); + const auto& color = state.color; + Require(state.topology == (primitive == 2u ? VK_PRIMITIVE_TOPOLOGY_LINE_LIST : VK_PRIMITIVE_TOPOLOGY_LINE_STRIP), "VGT_PRIMITIVE_TYPE " + std::to_string(primitive) + " did not decode as a line list or strip"); + Require(state.colors.size() == 1 && color.address == block.Address() && color.format == VK_FORMAT_R16G16B16A16_SFLOAT && color.samples == Samples && color.extent.width == Side && color.extent.height == Side, "the target did not decode as a 16x16 RGBA16F surface of 4 samples"); + Require(state.samples == Samples && !state.depth && !state.perSampleShading && !state.alphaToCoverage, "the lines did not decode as a 4x draw without depth, sample shading or alpha-to-coverage"); + const auto locations = AgcDriver::Graphics::SampleLocations(state); + for (std::uint32_t sample = 0; sample < Samples; ++sample) Require(locations[sample].x == 0.5f && locations[sample].y == 0.5f, "the decoded 4x line state does not put every sample at the pixel center"); + Require(state.viewport.x == 0.0f && state.viewport.y == static_cast(Side) && state.viewport.width == static_cast(Side) && state.viewport.height == -static_cast(Side) && state.scissor.offset.x == 0 && state.scissor.offset.y == 0 && state.scissor.extent.width == Side && state.scissor.extent.height == Side, "the decoded viewport or scissor does not cover the 16x16 target"); + return state; +} + +AgcDriver::Graphics::State OneSample(AgcDriver::Graphics::State state, std::span pixels, const AgcDriver::Graphics::ColorTargetLayout& linear) { + state.color = {reinterpret_cast(pixels.data()), {Side, Side}, VK_FORMAT_R8G8B8A8_UNORM, linear.Bytes(), 0xe4u}; + state.colors = {state.color}; + state.samples = 1; + state.customSampleLocations = false; + state.sampleLocations.fill(0); + state.sampleLocationGrid = false; + return state; +} + +void DrawLines(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::State& state, std::span pixelCode) { + const auto target = device.Target(); + std::vector vertexUserData(4, 0u); + const auto address = reinterpret_cast(Lines.data()); + const std::array vertexBuffer{static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), static_cast(Lines.size()), 0x01016facu}; + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {WaveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.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 auto codeAddress = reinterpret_cast(pixelCode.data()); + const std::array pixelMemory{{{codeAddress, std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, codeAddress, pixelCode, 0, {}}, + {WaveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Lines.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +std::vector> LoadSamples(AgcDriver::VulkanDevice& device, const Block& block) { + const auto address = block.Address(); + const std::array texture{ + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format16161616Float << 20u) | (((Side - 1u) & 3u) << 30u), + ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), + 0xfacu | (SampleShift << 16u) | (TileRenderTarget << 20u) | (Type2DMsaa << 28u), + 0u, + SampleShift << 4u, + 0u, + 0u, + }; + const auto bufferAddress = reinterpret_cast(Buffer.data()); + const std::array buffer{static_cast(bufferAddress), static_cast((bufferAddress >> 32u) & 0xffffu), static_cast(Buffer.size() * 4u), 0x31016facu}; + std::vector userData(16, 0u); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(LoadCode.data()), std::as_bytes(std::span(LoadCode))}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(LoadCode.data()), LoadCode, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto program = ShaderRecompiler::Recompile(request); + std::vector> loaded(Side * Side * Samples); + for (std::uint32_t first = 0; first < loaded.size(); first += Threads) { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto index = first + tid; + const auto pixel = index / Samples; + const std::array words{0u, 0u, 0u, pixel % Side, pixel / Side, index % Samples}; + std::copy(words.begin(), words.end(), &Buffer[tid * Words]); + } + device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast(LoadCode.data())); + device.WaitIdle(); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t component = 0; component < 4u; ++component) loaded[first + tid][component] = std::bit_cast(Buffer[tid * Words + LoadResult + component]); + } + } + return loaded; +} + +bool NearEndpoint(std::uint32_t x, std::uint32_t y) { + return std::any_of(Lines.begin(), Lines.end(), [&](const std::array& vertex) { + const double px = (static_cast(vertex[0]) + 1.0) * 0.5 * Side; + const double py = Side - (static_cast(vertex[1]) + 1.0) * 0.5 * Side; + return std::fabs(x + 0.5 - px) < 1.5 && std::fabs(y + 0.5 - py) < 1.5; + }); +} + +void CompareLines(AgcDriver::VulkanDevice& device, const Block& block, std::uint32_t primitive, std::span pixels) { + const auto decoded = DecodeLines(block, primitive); + DrawLines(device, decoded, PixelCode); + const auto loaded = LoadSamples(device, block); + const AgcDriver::Graphics::ColorTargetLayout linear(Side, Side, AgcDriver::Graphics::ColorTileMode::Linear, 4u); + Require(linear.Bytes() <= pixels.size(), "the one-sample line target does not fit its memory"); + std::fill(pixels.begin(), pixels.end(), std::byte{0}); + DrawLines(device, OneSample(decoded, pixels, linear), Unorm8PixelCode); + std::uint32_t covered = 0; + std::uint32_t single = 0; + std::uint32_t compared = 0; + std::uint32_t differing = 0; + for (std::uint32_t y = 0; y < Side; ++y) { + for (std::uint32_t x = 0; x < Side; ++x) { + char where[80]; + std::snprintf(where, sizeof(where), "VGT_PRIMITIVE_TYPE %u: pixel (%u, %u)", primitive, x, y); + std::uint32_t drawn = 0; + for (std::uint32_t sample = 0; sample < Samples; ++sample) { + const auto& texel = loaded[(y * Side + x) * Samples + sample]; + Require(texel == Drawn || texel == std::array{}, std::string(where) + " of the 4x target holds neither the line color nor the cleared value"); + drawn += texel == Drawn ? 1u : 0u; + } + Require(drawn == 0u || drawn == Samples, std::string(where) + " has " + std::to_string(drawn) + " of its 4 samples covered by a line with every sample at the pixel center"); + const auto bytes = pixels.begin() + static_cast(linear.Offset(x, y)); + const bool oneSample = std::equal(DrawnBytes.begin(), DrawnBytes.end(), bytes); + Require(oneSample || std::all_of(bytes, bytes + 4, [](std::byte value) { return value == std::byte{0}; }), std::string(where) + " of the one-sample target holds neither the line color nor zero"); + covered += drawn == Samples ? 1u : 0u; + single += oneSample ? 1u : 0u; + if (NearEndpoint(x, y)) continue; + ++compared; + differing += (drawn == Samples) != oneSample ? 1u : 0u; + Require(!oneSample || drawn == Samples, std::string(where) + " is drawn by the one-sample line but not covered in the 4x target"); + } + } + Require(covered >= 20u && covered <= 2u * single && compared >= 200u, "VGT_PRIMITIVE_TYPE " + std::to_string(primitive) + ": the 4x lines covered " + std::to_string(covered) + " pixels and the one-sample lines " + std::to_string(single) + ", with " + std::to_string(compared) + " pixels compared"); + std::printf("VGT_PRIMITIVE_TYPE %u: 4x lines with every sample at the pixel center covered %u pixels in full and none in part; the one-sample lines drew %u; %u of the %u pixels away from the endpoints differ\n", primitive, covered, single, differing, compared); +} + +} + +int main() { + try { + auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (!AgcDriver::VulkanDeviceTestAccess::ContextOf(*device).storageImageMultisample) { + std::puts("skipped, the device has no shaderStorageImageMultisample to load the samples of a 4x target"); + return VulkanTestSkipped; + } + if ((AgcDriver::VulkanDeviceTestAccess::ContextOf(*device).sampleLocationSampleCounts & Samples) == 0u) { + std::puts("skipped, the device has no VK_EXT_sample_locations at 4 samples to put every sample at the pixel center"); + return VulkanTestSkipped; + } + Block listTarget(4u * BlockBytes); + Block stripTarget(4u * BlockBytes); + struct ReleaseDevice { + std::unique_ptr& value; + ~ReleaseDevice() { value.reset(); } + } releaseDevice{device}; + const std::array targets{&listTarget, &stripTarget}; + for (std::size_t index = 0; index < Primitives.size(); ++index) CompareLines(*device, *targets[index], Primitives[index], LinePixels[index]); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleReadback.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleReadback.cpp index 8b6cc1b256..a6c84ad27f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleReadback.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleReadback.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" +#include "prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp" #include "VulkanTestDevice.hpp" #include "MsaaReadbackVertex_spv.h" #include "MsaaReadbackUint_spv.h" @@ -69,8 +70,8 @@ void CheckSupportSelection() { static_assert(!SurfaceFits(VkImageFormatProperties{}, 4u)); } -bool SurfaceSupported(const Context& context, VkFormat format, std::uint32_t samples, std::uint32_t width = 17u, std::uint32_t height = 5u, std::uint32_t layers = 2u) { - const VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | (format == VK_FORMAT_D32_SFLOAT ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); +bool SurfaceSupported(const Context& context, VkFormat format, std::uint32_t samples, std::uint32_t width = 17u, std::uint32_t height = 5u, std::uint32_t layers = 2u, bool writable = false) { + const VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | (format == VK_FORMAT_D32_SFLOAT ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) | (writable ? VK_IMAGE_USAGE_STORAGE_BIT : 0u); VkImageFormatProperties supported{}; const auto result = context.imageFormatProperties(context.physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage, 0u, &supported); if (result != VK_ERROR_FORMAT_NOT_SUPPORTED) Check(result, "readback fixture format query"); @@ -97,11 +98,11 @@ struct Surface { std::vector views; std::vector framebuffers; - Surface(const Context& context, VkFormat format, std::uint32_t elementBytes, std::uint32_t samples, std::uint32_t width = 17u, std::uint32_t height = 5u, std::uint32_t layers = 2u) : context(context), format(format), elementBytes(elementBytes), samples(samples), width(width), height(height), layers(layers) { + Surface(const Context& context, VkFormat format, std::uint32_t elementBytes, std::uint32_t samples, std::uint32_t width = 17u, std::uint32_t height = 5u, std::uint32_t layers = 2u, bool writable = false) : context(context), format(format), elementBytes(elementBytes), samples(samples), width(width), height(height), layers(layers) { try { const bool depth = format == VK_FORMAT_D32_SFLOAT; const auto aspect = depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; - const VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | (depth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + const VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | (depth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) | (writable ? VK_IMAGE_USAGE_STORAGE_BIT : 0u); VkImageFormatProperties supported{}; Check(context.imageFormatProperties(context.physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage, 0u, &supported), "readback fixture format query"); Require(SurfaceFits(supported, samples, width, height, layers), "readback fixture format does not support its sample count or extent"); @@ -275,15 +276,15 @@ struct Surface { const auto layer = pixel / (width * height); const auto value = x + y * width + layer * width * height + sample * 31u; std::array words{0x91352700u ^ value, 0x45219b00u ^ (value * 3u), 0xc7391d00u ^ (value * 5u), 0x6b43e100u ^ (value * 7u)}; - if (format == VK_FORMAT_D32_SFLOAT) words[0] = std::bit_cast(0.125f + static_cast(element + 1u) / 65536.0f); + if (format == VK_FORMAT_D32_SFLOAT) words[0] = std::bit_cast(0.125f + static_cast(element + 1u) / 1048576.0f); std::memcpy(result.data() + static_cast(index) * elementBytes, words.data(), elementBytes); } return result; } }; -void CheckReadback(TextureDetiler& reader, const Surface& surface, std::uint32_t first, std::uint32_t elements, std::uint32_t repetitions, std::size_t extraBytes = 0u) { - const auto expected = surface.Expected(first, elements); +std::vector CheckReadback(TextureDetiler& reader, const Surface& surface, std::uint32_t first, std::uint32_t elements, std::uint32_t repetitions, std::size_t extraBytes = 0u, std::span supplied = {}) { + const auto expected = supplied.empty() ? surface.Expected(first, elements) : std::vector(supplied.begin(), supplied.end()); const auto bytes = ((expected.size() + 3u) & ~std::size_t{3u}) + extraBytes; const auto alignment = std::max(4u, surface.context.limits.minStorageBufferOffsetAlignment); const auto offset = (16u + alignment - 1u) / alignment * alignment; @@ -291,7 +292,7 @@ void CheckReadback(TextureDetiler& reader, const Surface& surface, std::uint32_t std::fill(output.Bytes().begin(), output.Bytes().end(), std::byte{0xa5}); reader.BeginBatch(); CommandBatch batch(surface.context); - RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); for (std::uint32_t repeat = 0u; repeat < repetitions; ++repeat) { reader.DispatchMultisampleReadback(batch.Handle(), surface.format, output.Handle(), offset, bytes, surface.arrayView, surface.width, surface.height, surface.layers, surface.samples, first, elements); if (repeat + 1u < repetitions) RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); @@ -304,6 +305,61 @@ void CheckReadback(TextureDetiler& reader, const Surface& surface, std::uint32_t if (mismatch.first != expected.end()) std::printf("readback mismatch: format=%u first=%u byte=%zu expected=0x%02x actual=0x%02x\n", static_cast(surface.format), first, static_cast(mismatch.first - expected.begin()), std::to_integer(*mismatch.first), std::to_integer(*mismatch.second)); Require(std::equal(expected.begin(), expected.end(), data.begin() + offset), "multisample readback changed a rendered sample, component, layer or pixel"); for (std::size_t index = 0u; index < data.size(); ++index) Require((index >= offset && index < offset + expected.size()) || data[index] == std::byte{0xa5}, "multisample readback changed a buffer sentinel or packed tail"); + return {data.begin() + offset, data.begin() + offset + expected.size()}; +} + +void CheckRetile(TextureDetiler& reader, const Surface& surface) { + const auto elements = surface.width * surface.height * surface.layers * surface.samples; + const auto linear = CheckReadback(reader, surface, 0u, elements, 1u); + const auto expected = surface.Expected(0u, elements); + constexpr std::array, 7> coordinates{{ + {0u, 0u, 0u}, {128u, 66u, 16u}, {64u, 32u, 1u}, {32u, 64u, 2u}, + {63u, 31u, 7u}, {127u, 65u, 15u}, {1u, 1u, 3u} + }}; + constexpr std::array modes{TextureTileMode::kR64KBX, TextureTileMode::kZ64KBX}; + constexpr std::array, 2> references{{ + {0ull, 6621216ull, 493568ull, 1016832ull, 2766588ull, 6170780ull, 1215508ull}, + {0ull, 6637700ull, 460808ull, 1000460ull, 2815728ull, 6219892ull, 1182776ull} + }}; + for (std::size_t mode = 0u; mode < modes.size(); ++mode) { + for (const auto pipeBankXor : {0u, 0x8100u}) { + const MultisampleTextureLayout layout(modes[mode], surface.elementBytes, surface.samples, surface.width, surface.height, surface.layers, pipeBankXor); + std::vector tiled(static_cast(layout.Bytes()) + 32u, std::byte{0xa5}); + auto payload = std::span(tiled).subspan(16u, layout.Bytes()); + layout.Tile(linear, payload); + std::vector covered(payload.size()); + std::size_t position = 0u; + for (std::uint32_t layer = 0u; layer < surface.layers; ++layer) { + for (std::uint32_t y = 0u; y < surface.height; ++y) { + for (std::uint32_t x = 0u; x < surface.width; ++x) { + for (std::uint32_t fragment = 0u; fragment < surface.samples; ++fragment) { + const auto address = layout.Offset(x, y, layer, fragment); + Require(address + surface.elementBytes <= payload.size(), "readback retile exceeds its allocation"); + for (std::uint32_t byte = 0u; byte < surface.elementBytes; ++byte) { + Require(!covered[address + byte], "readback retile elements overlap"); + covered[address + byte] = true; + Require(payload[address + byte] == expected[position++], "readback and retile disagree on layer, pixel or fragment order"); + } + } + } + } + } + for (std::size_t index = 0u; index < covered.size(); ++index) Require(covered[index] || payload[index] == std::byte{0xa5}, "readback retile changed tile padding"); + for (std::size_t index = 0u; index < coordinates.size(); ++index) { + const auto& coordinate = coordinates[index]; + const auto fragment = static_cast(index) % surface.samples; + const auto source = (((coordinate[2] * surface.height + coordinate[1]) * surface.width + coordinate[0]) * surface.samples + fragment) * surface.elementBytes; + const auto reference = references[mode][index]; + const auto address = (reference & ~65535ull) | ((reference & 65535ull) ^ pipeBankXor); + Require(address + surface.elementBytes <= payload.size() && std::equal(expected.begin() + source, expected.begin() + source + surface.elementBytes, payload.begin() + address), "rendered readback differs at an AMD reference fragment address"); + } + std::vector restored(expected.size()); + layout.Detile(payload, restored); + Require(restored == expected && linear == expected, "readback retile roundtrip changed rendered element bits"); + for (std::size_t index = 0u; index < tiled.size(); ++index) Require((index >= 16u && index < 16u + payload.size()) || tiled[index] == std::byte{0xa5}, "readback retile changed an allocation sentinel"); + std::printf("multisample readback retile: format=%u mode=%s xor=0x%x samples=%u layers=%u elements=%u reference-points=7 padding/sentinels/roundtrip=passed\n", static_cast(surface.format), mode == 0u ? "R_X" : "Z_X", pipeBankXor, surface.samples, surface.layers, elements); + } + } } template @@ -331,6 +387,106 @@ void CheckRefusals(TextureDetiler& reader, const Surface& surface) { std::puts("multisample readback invalid-input refusals passed"); } +std::vector Upload(TextureDetiler& transfer, TextureDetiler& reader, const Surface& surface, std::vector& expected, std::uint32_t first, std::uint32_t elements, std::uint32_t repetitions = 1u, std::span supplied = {}) { + const auto bytes = static_cast(elements) * surface.elementBytes; + const auto padded = (bytes + 3u) & ~std::size_t{3}; + const auto alignment = std::max({4u, surface.elementBytes, surface.context.limits.minStorageBufferOffsetAlignment}); + const auto offset = (16u + alignment - 1u) / alignment * alignment; + Buffer source(surface.context, offset + padded + 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + std::fill(source.Bytes().begin(), source.Bytes().end(), std::byte{0x5a}); + Require(supplied.empty() || supplied.size() == bytes, "multisample upload fixture source size mismatch"); + for (std::size_t index = 0; index < bytes; ++index) source.Bytes()[offset + index] = supplied.empty() ? static_cast((index * 29u + first * 17u + elements * 7u) & 255u) : supplied[index]; + const std::vector retained(source.Bytes().begin(), source.Bytes().end()); + std::copy_n(retained.begin() + offset, bytes, expected.begin() + static_cast(first) * surface.elementBytes); + const auto stride = 256u / alignment * alignment; + Require(stride != 0u, "upload fixture cannot hold an aligned chunk"); + transfer.BeginBatch(); + CommandBatch batch(surface.context); + RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + for (std::uint32_t repeat = 0; repeat < repetitions; ++repeat) { + for (std::size_t done = 0; done < bytes; done += stride) { + const auto count = std::min(stride, bytes - done); + transfer.DispatchMultisampleImage(batch.Handle(), surface.elementBytes, source.Handle(), offset + done, (count + 3u) & ~std::size_t{3}, surface.arrayView, surface.width, surface.height, surface.layers, surface.samples, first + static_cast(done / surface.elementBytes), static_cast(count / surface.elementBytes)); + } + RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + } + batch.SubmitAndWait(); + source.Invalidate(); + Require(std::equal(retained.begin(), retained.end(), source.Bytes().begin()), "multisample upload changed source bytes or sentinels"); + return CheckReadback(reader, surface, 0u, surface.width * surface.height * surface.layers * surface.samples, 1u, 0u, expected); +} + +void CheckUploadRefusals(TextureDetiler& transfer, const Surface& surface) { + Buffer source(surface.context, 256u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + CommandBatch batch(surface.context); + const auto call = [&](std::uint32_t bytes, std::uint32_t fragments, std::uint32_t first, std::uint32_t elements, std::uint64_t offset = 0u, std::uint64_t range = 4u) { + transfer.DispatchMultisampleImage(batch.Handle(), bytes, source.Handle(), offset, range, surface.arrayView, 1u, 1u, 1u, fragments, first, elements); + }; + for (const auto bytes : {0u, 3u, 32u}) Refused([&] { call(bytes, 4u, 0u, 1u); }, "element size is unsupported"); + for (const auto fragments : {0u, 1u, 3u, 16u}) Refused([&] { call(4u, fragments, 0u, 1u); }, "fragment count is unsupported"); + for (const auto range : {std::array{0u, 0u}, std::array{4u, 1u}, std::array{3u, 2u}, std::array{0xffffffffu, 2u}}) Refused([&] { call(4u, 4u, range[0], range[1]); }, "element range is invalid"); + for (const auto range : {std::array{1u, 4u}, std::array{0u, 0u}, std::array{0u, 3u}, std::array{0u, 260u}, std::array{~std::uint64_t{0} - 3u, 8u}}) Refused([&] { call(4u, 4u, 0u, 1u, range[0], range[1]); }, "buffer range is invalid"); + Refused([&] { transfer.DispatchMultisampleImage(VK_NULL_HANDLE, 4u, source.Handle(), 0u, 4u, surface.arrayView, 1u, 1u, 1u, 4u, 0u, 1u); }, "requires commands, buffer and array view"); + Refused([&] { transfer.DispatchMultisampleImage(batch.Handle(), 4u, VK_NULL_HANDLE, 0u, 4u, surface.arrayView, 1u, 1u, 1u, 4u, 0u, 1u); }, "requires commands, buffer and array view"); + Refused([&] { transfer.DispatchMultisampleImage(batch.Handle(), 4u, source.Handle(), 0u, 4u, VK_NULL_HANDLE, 1u, 1u, 1u, 4u, 0u, 1u); }, "requires commands, buffer and array view"); + for (const auto extent : {std::array{0u, 1u, 1u}, std::array{1u, 0u, 1u}, std::array{1u, 1u, 0u}, std::array{16385u, 1u, 1u}, std::array{1u, 16385u, 1u}, std::array{1u, 1u, 8193u}}) Refused([&] { transfer.DispatchMultisampleImage(batch.Handle(), 4u, source.Handle(), 0u, 4u, surface.arrayView, extent[0], extent[1], extent[2], 4u, 0u, 1u); }, "extent is unsupported"); + Refused([&] { transfer.DispatchMultisampleImage(batch.Handle(), 4u, source.Handle(), 0u, 4u, surface.arrayView, 16384u, 16384u, 8192u, 8u, 0u, 1u); }, "element range is invalid"); + for (const bool multisample : {false, true}) { + auto unsupported = surface.context; + if (multisample) unsupported.storageImageMultisample = false; + else unsupported.storageImageWriteWithoutFormat = false; + TextureDetiler unavailable(unsupported); + Refused([&] { unavailable.DispatchMultisampleImage(batch.Handle(), 4u, source.Handle(), 0u, 4u, surface.arrayView, 1u, 1u, 1u, 4u, 0u, 1u); }, "does not enable multisampled storage image writes without format"); + } +} + +void CheckUpload(const Context& context) { + Require(context.storageImageMultisample && context.storageImageWriteWithoutFormat, "upload fixture requires enabled multisampled formatless storage writes"); + auto limited = context; + limited.limits.maxComputeWorkGroupCount[0] = 1u; + limited.limits.maxStorageBufferRange = 256u; + TextureDetiler transfer(limited); + TextureDetiler reader(context); + std::uint32_t exercised = 0u; + for (const auto bytes : {1u, 2u, 4u, 8u, 16u}) { + const auto format = TextureDetiler::ImageElementFormat(bytes); + for (const auto samples : {2u, 4u, 8u}) { + if (!SurfaceSupported(context, format, samples, 17u, 5u, 2u, true)) continue; + Surface surface(context, format, bytes, samples, 17u, 5u, 2u, true); + surface.Render(); + const auto elements = surface.width * surface.height * surface.layers * samples; + auto expected = surface.Expected(0u, elements); + Upload(transfer, reader, surface, expected, 3u, 1u); + Upload(transfer, reader, surface, expected, 1u, elements - 3u); + Upload(transfer, reader, surface, expected, 0u, elements); + Upload(transfer, reader, surface, expected, elements - 1u, 1u, samples == 4u ? 65u : 1u); + if (bytes == 4u && samples == 4u) CheckUploadRefusals(transfer, surface); + std::printf("multisample upload: bytes=%u samples=%u layers=2 full/partial/packed/source-sentinels/chunks/reuse=passed\n", bytes, samples); + ++exercised; + } + } + Require(exercised != 0u, "no supported multisample upload subcase was exercised"); + const MultisampleTextureLayout active(TextureTileMode::kR64KBX, 8u, 4u, 17u, 5u, 2u); + if (SurfaceSupported(context, VK_FORMAT_R32G32_UINT, 4u, active.Pitch(), active.PaddedHeight(), 2u, true)) { + for (const auto xorValue : {0u, 0x8100u}) { + const MultisampleTextureLayout layout(TextureTileMode::kR64KBX, 8u, 4u, active.Pitch(), active.PaddedHeight(), 2u, xorValue); + std::vector tiled(static_cast(layout.Bytes())); + for (std::size_t byte = 0; byte < tiled.size(); ++byte) tiled[byte] = static_cast((byte * 37u + (byte >> 8u) * 13u + (byte >> 15u) * 71u + (byte >> 16u) * 101u) & 255u); + std::vector linear(static_cast(layout.LinearBytes())); + layout.Detile(tiled, linear); + Surface surface(context, VK_FORMAT_R32G32_UINT, 8u, 4u, layout.Pitch(), layout.PaddedHeight(), 2u, true); + surface.Render(); + const auto elements = surface.width * surface.height * surface.layers * surface.samples; + auto expected = surface.Expected(0u, elements); + const auto actual = Upload(transfer, reader, surface, expected, 0u, elements, 1u, linear); + std::vector retiled(tiled.size(), std::byte{0xa5}); + layout.Tile(actual, retiled); + Require(retiled == tiled, "multisample padded initialization changed color bytes, fragments, layers, XOR or padding"); + std::printf("multisample padded upload: active=17x5 backing=%ux%u layers=2 fragments=4 bytes=%zu xor=0x%x exact roundtrip passed\n", surface.width, surface.height, retiled.size(), xorValue); + } + } +} + } int main(int argc, char** argv) { @@ -343,8 +499,8 @@ int main(int argc, char** argv) { else if (mode == "uint16") { format = VK_FORMAT_R16_UINT; bytes = 2u; } else if (mode == "uint64") { format = VK_FORMAT_R32G32_UINT; bytes = 8u; } else if (mode == "uint128") { format = VK_FORMAT_R32G32B32A32_UINT; bytes = 16u; } - else Require(mode == "uint32" || mode == "depth", "unknown multisample readback test mode"); - if (mode == "depth") format = VK_FORMAT_D32_SFLOAT; + else Require(mode == "uint32" || mode == "depth" || mode == "upload" || mode == "retile" || mode == "depth_retile", "unknown multisample readback test mode"); + if (mode == "depth" || mode == "depth_retile") format = VK_FORMAT_D32_SFLOAT; const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); @@ -357,9 +513,28 @@ int main(int argc, char** argv) { std::puts("skipped, the device does not support per-sample shading"); return VulkanTestSkipped; } + if (mode == "upload") { + if (!context.storageImageMultisample || !context.storageImageWriteWithoutFormat) { + std::puts("skipped, the device does not enable multisampled formatless storage writes"); + return VulkanTestSkipped; + } + CheckUpload(context); + Require(checkedPipelineCacheUses != 0u, "upload fixture did not check pipeline cache use"); + std::printf("multisample upload guarded pipeline creation checks=%u passed\n", checkedPipelineCacheUses); + return 0; + } auto limited = context; limited.limits.maxComputeWorkGroupCount[0] = 1u; TextureDetiler reader(limited); + if (mode == "retile" || mode == "depth_retile") { + if (!SurfaceSupported(context, format, 4u, 129u, 67u, 17u)) return VulkanTestSkipped; + Surface surface(context, format, bytes, 4u, 129u, 67u, 17u); + surface.Render(); + CheckRetile(reader, surface); + Require(checkedPipelineCacheUses != 0u, "retile fixture did not check pipeline cache use"); + std::printf("multisample retile guarded pipeline creation checks=%u passed\n", checkedPipelineCacheUses); + return 0; + } std::uint32_t checked = 0u; for (const auto samples : {2u, 4u, 8u}) { if (!SurfaceSupported(context, format, samples)) continue; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleRetirement.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleRetirement.cpp index b603cc7326..5cc437a819 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleRetirement.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleRetirement.cpp @@ -14,6 +14,7 @@ #else #include #endif +#include #include #include #include @@ -66,6 +67,15 @@ PFN_vkDestroyImageView realDestroyImageView = nullptr; PFN_vkCmdClearColorImage realClearColorImage = nullptr; PFN_vkCmdCopyImage realCopyImage = nullptr; +[[noreturn]] void FailViewOutlivesImage() { + std::fputs("an MSAA image would be destroyed before one of its views; stopping\n", stderr); + std::fflush(stderr); +#ifdef _WIN32 + TerminateProcess(GetCurrentProcess(), 1); +#endif + std::_Exit(1); +} + [[noreturn]] void FailPendingImage() { std::fputs("an unsubmitted MSAA image would be destroyed; stopping without submitting it\n", stderr); std::fflush(stderr); @@ -104,6 +114,7 @@ VKAPI_ATTR void VKAPI_CALL CountDestroyImage(VkDevice device, VkImage image, con for (const auto& [pending, generation] : pendingImages) { if (pending == image && images.at(image).generation == generation) FailPendingImage(); } + if (std::any_of(views.begin(), views.end(), [&](const auto& entry) { return entry.second == image; })) FailViewOutlivesImage(); realDestroyImage(device, image, callbacks); images.erase(image); ++destroyed; @@ -254,11 +265,11 @@ private: bool mapped = false; }; -ColorTarget Target(const GuestBlock& block) { +ColorTarget Target(const GuestBlock& block, VkFormat format = VK_FORMAT_R8G8B8A8_UNORM) { ColorTarget target{}; target.address = target.surfaceAddress = block.Address(); target.extent = target.surfaceExtent = {Side, Side}; - target.format = VK_FORMAT_R8G8B8A8_UNORM; + target.format = format; target.elementBytes = 4; target.samples = 4; target.tileMode = ColorTileMode::RenderTarget; @@ -514,6 +525,67 @@ public: AwaitImages(0, "promoted MSAA targets were not fully released"); } + void StorageTwin() { + GuestBlock block; + const auto target = Target(block); + auto twin = target; + twin.format = VK_FORMAT_R8G8B8A8_SRGB; + 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{}; + VkImageView twinView = VK_NULL_HANDLE; + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + rendered = ImageFor(MultisampleTargetView(context, target), &renderedImage); + twinView = MultisampleTargetView(context, twin); + ImageFor(twinView, &twinImage); + Require(twinImage == renderedImage, "an sRGB use of an MSAA surface rendered as UNORM does not reach its samples"); + } + Sync(); + { + 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"); + 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"); + } + Sync(); + block.Unmap(); + Clear(); + AwaitImages(0, "MSAA targets with twin views were not fully released"); + } + + void StorageSrgbTwin() { + if (!context.storageImageMultisample || HostStorageFormat(context, VK_FORMAT_R8G8B8A8_SRGB) != VK_FORMAT_R8G8B8A8_UNORM) { + std::puts("the device has no UNORM storage form for a multisampled sRGB target; its storage twin case is skipped"); + return; + } + GuestBlock block; + const auto target = Target(block, VK_FORMAT_R8G8B8A8_SRGB); + auto resource = Resource(block); + VkImageView storageView = VK_NULL_HANDLE; + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + MultisampleTargetView(context, target); + storageView = MultisampleTargetStorageView(context, resource); + Require(storageView != VK_NULL_HANDLE, "a UNORM storage access to a multisampled sRGB target has no view"); + Require(MultisampleTargetStorageView(context, resource) == storageView, "a repeated UNORM storage access to a multisampled sRGB target changed its view"); + resource.format = 60; + bool refused = false; + try { + MultisampleTargetStorageView(context, resource); + } catch (const std::exception&) { + refused = true; + } + Require(refused, "a storage access in an unrelated format reached a multisampled sRGB target"); + } + Sync(); + block.Unmap(); + Clear(); + AwaitImages(0, "the sRGB storage twin targets were not fully released"); + } + private: AgcDriver::VulkanDevice& device; Context context; @@ -532,7 +604,9 @@ int main() { run.RecordedRemap(); run.SampledRemap(); run.StorageRemap(); + run.StorageSrgbTwin(); run.OrderRemap(); + if (context.storageImageMultisample) run.StorageTwin(); std::puts("retired MSAA allocations stay bounded, recorded clears and storage promotions retain their images, sampled aliases release their parents after the last batch, and sample-order targets keep their retired images until their batch completes"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleSrgbTwin.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleSrgbTwin.cpp new file mode 100644 index 0000000000..a28966b839 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleSrgbTwin.cpp @@ -0,0 +1,247 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#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 AgcDriver::Graphics::ColorTarget; +using AgcDriver::Graphics::Require; + +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Side = 64; + +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, "multisampled sRGB twin: cannot allocate a guest block"); + std::memset(block, 0, BlockBytes); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint64_t Address() const { return static_cast(reinterpret_cast(block)); } + +private: + std::byte* block = nullptr; +}; + +GuestBlock& NewBlock() { + static auto* blocks = new std::deque(); + return blocks->emplace_back(); +} + +ColorTarget MultisampledTarget(const GuestBlock& color, VkFormat format) { + ColorTarget target{}; + target.address = color.Address(); + target.surfaceAddress = target.address; + target.extent = {Side, Side}; + target.surfaceExtent = target.extent; + target.format = format; + target.tileMode = AgcDriver::Graphics::ColorTileMode::RenderTarget; + target.elementBytes = 4; + target.bytes = BlockBytes / 4; + target.samples = 4; + return target; +} + +struct Acquired { + VkImageView view = VK_NULL_HANDLE; + VkImage image = VK_NULL_HANDLE; + std::shared_ptr owner; +}; + +Acquired Acquire(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::Context& context, const ColorTarget& target) { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + Acquired acquired; + acquired.view = AgcDriver::Graphics::MultisampleTargetView(context, target, acquired.owner, &acquired.image); + Require(acquired.view != VK_NULL_HANDLE && acquired.image != VK_NULL_HANDLE && acquired.owner != nullptr, "the multisampled target has no view"); + device.SubmitRecorded(true); + device.WaitIdle(); + return acquired; +} + +std::array UniformTexel(const AgcDriver::Graphics::Context& context, VkImage multisampled, VkFormat format) { + VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = format; + info.extent = {Side, Side, 1}; + info.mipLevels = 1; + info.arrayLayers = 1; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImage resolved = VK_NULL_HANDLE; + AgcDriver::Graphics::Check(context.Function("vkCreateImage")(context.device, &info, nullptr, &resolved), "vkCreateImage resolved texels"); + VkMemoryRequirements requirements{}; + context.Function("vkGetImageMemoryRequirements")(context.device, resolved, &requirements); + VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocation.allocationSize = requirements.size; + allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VkDeviceMemory memory = VK_NULL_HANDLE; + AgcDriver::Graphics::Check(context.Function("vkAllocateMemory")(context.device, &allocation, nullptr, &memory), "vkAllocateMemory resolved texels"); + AgcDriver::Graphics::Check(context.Function("vkBindImageMemory")(context.device, resolved, memory, 0), "vkBindImageMemory resolved texels"); + AgcDriver::Graphics::Buffer readback(context, static_cast(Side) * Side * 4u, VK_BUFFER_USAGE_TRANSFER_DST_BIT); + { + AgcDriver::Graphics::CommandBatch batch(context); + const auto commands = batch.Handle(); + std::array barriers{}; + for (auto& barrier : barriers) { + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + } + barriers[0].image = multisampled; + barriers[0].srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; + barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barriers[0].oldLayout = barriers[0].newLayout = VK_IMAGE_LAYOUT_GENERAL; + barriers[1].image = resolved; + barriers[1].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barriers[1].newLayout = VK_IMAGE_LAYOUT_GENERAL; + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, static_cast(barriers.size()), barriers.data()); + VkImageResolve region{}; + region.srcSubresource = region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + region.extent = {Side, Side, 1}; + context.Function("vkCmdResolveImage")(commands, multisampled, VK_IMAGE_LAYOUT_GENERAL, resolved, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); + VkMemoryBarrier toCopy{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; + toCopy.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toCopy.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &toCopy, 0, nullptr, 0, nullptr); + VkBufferImageCopy copy{}; + copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + copy.imageExtent = {Side, Side, 1}; + context.Function("vkCmdCopyImageToBuffer")(commands, resolved, VK_IMAGE_LAYOUT_GENERAL, readback.Handle(), 1, ©); + VkMemoryBarrier toHost{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; + toHost.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toHost.dstAccessMask = VK_ACCESS_HOST_READ_BIT; + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &toHost, 0, nullptr, 0, nullptr); + batch.SubmitAndWait(); + } + readback.Invalidate(); + const auto bytes = readback.Bytes(); + std::array texel{}; + std::copy_n(bytes.begin(), texel.size(), texel.begin()); + for (std::size_t offset = 0; offset < bytes.size(); offset += texel.size()) Require(std::equal(texel.begin(), texel.end(), bytes.begin() + static_cast(offset)), "a cleared multisampled target holds different texels"); + context.Function("vkDestroyImage")(context.device, resolved, nullptr); + context.Function("vkFreeMemory")(context.device, memory, nullptr); + return texel; +} + +std::array Bytes(std::uint32_t pattern) { + std::array bytes{}; + std::memcpy(bytes.data(), &pattern, bytes.size()); + return bytes; +} + +void FillTakenThroughTwin(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::Context& context, VkFormat created, VkFormat used, std::uint32_t pattern) { + auto& color = NewBlock(); + const auto target = MultisampledTarget(color, created); + const auto rendered = Acquire(device, context, target); + const auto bytes = static_cast(AgcDriver::Graphics::MultisampledColorBytes(target)); + Require(AgcDriver::Graphics::MultisampledColorFillAbsorbs(color.Address(), bytes, pattern), "the color fill is not taken as the target's clear in its creation format"); + AgcDriver::Graphics::NoteColorMetadataFill(color.Address(), bytes, pattern); + const auto twin = Acquire(device, context, MultisampledTarget(color, used)); + Require(twin.image == rendered.image, "the twin use of the filled target does not reach its samples"); + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + Require(UniformTexel(context, twin.image, created) == Bytes(pattern), "a fill taken as a clear through the twin format does not store the guest's bytes"); +} + +bool TwinFormatsShareTheirSamples(AgcDriver::VulkanDevice& device) { + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); + if ((context.limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_4_BIT) == 0) { + std::cout << "skipped, the device has no 4x color targets\n"; + return false; + } + struct ClearTargets { + AgcDriver::VulkanDevice& device; + VkDevice handle; + ~ClearTargets() { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + device.SubmitRecorded(true); + device.WaitIdle(); + AgcDriver::Graphics::ClearMultisampleTargets(handle); + } + } clearTargets{device, context.device}; + for (const auto [first, second] : {std::pair{VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM}, std::pair{VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB}}) { + if (AgcDriver::Graphics::SrgbTwinFormat(context, first) != second && AgcDriver::Graphics::SrgbTwinFormat(context, second) != first) continue; + auto& color = NewBlock(); + const auto rendered = Acquire(device, context, MultisampledTarget(color, first)); + const auto twin = Acquire(device, context, MultisampledTarget(color, second)); + Require(twin.image == rendered.image && twin.owner == rendered.owner, "a use of a multisampled surface in its sRGB twin format does not reach the samples rendered in the other format"); + Require(twin.view != rendered.view, "a use of a multisampled surface in its sRGB twin format is given the view of the other format"); + const auto again = Acquire(device, context, MultisampledTarget(color, first)); + Require(again.image == rendered.image && again.view == rendered.view, "returning to the first format does not give back the first format's view"); + } + if (AgcDriver::Graphics::SrgbTwinFormat(context, VK_FORMAT_R8G8B8A8_UNORM) == VK_FORMAT_R8G8B8A8_SRGB) { + FillTakenThroughTwin(device, context, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB, 0x80808080u); + FillTakenThroughTwin(device, context, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_R8G8B8A8_UNORM, 0xff0000ffu); + } + auto& other = NewBlock(); + const auto unorm = Acquire(device, context, MultisampledTarget(other, VK_FORMAT_R8G8B8A8_UNORM)); + const auto floating = Acquire(device, context, MultisampledTarget(other, VK_FORMAT_R16G16_SFLOAT)); + Require(floating.image != unorm.image, "a multisampled surface used in an unrelated format of the same size shares the other format's samples"); + return true; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool tested = TwinFormatsShareTheirSamples(*device); + LibcRunShutdown_nid_postfix(); + if (!tested) return VulkanTestSkipped; + std::cout << "multisampled sRGB twin tests passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { + LibcRunShutdown_nid_postfix(); + } catch (...) { + } + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/NativeDepthBdaClear.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/NativeDepthBdaClear.cpp index d1564e3562..6df1dfa361 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/NativeDepthBdaClear.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/NativeDepthBdaClear.cpp @@ -25,7 +25,9 @@ #include #include #include +#include #include +#include namespace AgcDriver { @@ -93,7 +95,9 @@ private: #endif }; -int RunNativeClear(AgcDriver::VulkanDevice& device) { +enum class ClearConsumer { Owner, Texture, Attachment }; + +int RunNativeClear(AgcDriver::VulkanDevice& device, ClearConsumer consumer) { using namespace ShaderRecompiler; using namespace AgcDriver::Graphics; const std::lock_guard lock(Memory::GpuMutex()); @@ -153,10 +157,28 @@ int RunNativeClear(AgcDriver::VulkanDevice& device) { device.WaitIdle(); target.clearDepth = 1.0f; Require(DepthSurfaceView(context, target) == view, "changing the native clear value replaced its attachment"); - NoteHtileDepthClear(target.htileAddress, metadataBytes, metadataBytes); + std::optional> htileClear; + if (NoteHtileDepthClear(target.htileAddress, metadataBytes, metadataBytes)) htileClear.emplace(target.htileAddress, metadataBytes); + Require(htileClear.has_value(), "the full BDA clear was not registered for completion"); const auto generation = Memory::TrackerGeneration(); userData[4] = 0xfffffff0u; - device.Dispatch(compile(), groups, 1u, 1u, {}, reinterpret_cast(code.data())); + device.Dispatch(compile(), groups, 1u, 1u, {}, reinterpret_cast(code.data()), nullptr, nullptr, htileClear); + std::puts("Native full BDA clear: checking sampled owner before explicit WaitIdle"); + Require(context.recorder->PendingWriteOverlaps(target.htileAddress, metadataBytes), "the recognized HTILE clear is missing its pending write range"); + switch (consumer) { + case ClearConsumer::Owner: + Require(DepthSurfaceHolds(context, DecodeTextureResource(words), sampled.get()), "a pending full BDA clear lost its native sampled owner"); + break; + case ClearConsumer::Texture: + Require(CachedSampledTexture(context, words) == sampled, "a pending full BDA clear lost its native sampled view"); + break; + case ClearConsumer::Attachment: + Require(DepthSurfaceView(context, target) == view, "a pending full BDA clear lost its native attachment"); + break; + } + std::printf("Native full BDA clear: consumer=%u settled the recognized clear before WaitIdle\n", static_cast(consumer)); + Require(!context.recorder->PendingWriteOverlaps(target.htileAddress, metadataBytes), "the native HTILE consumer did not settle its clear's pending write"); + Require(Memory::StoredAllSince_nid_no_patch(target.htileAddress, metadataBytes, generation), "the native HTILE consumer did not complete its recognized clear"); device.WaitIdle(); for (std::size_t offset = 0; offset < BlockBytes; offset += 4u) { std::uint32_t value = 0; @@ -164,6 +186,7 @@ int RunNativeClear(AgcDriver::VulkanDevice& device) { Require(value == (offset < metadataBytes ? 0xfffffff0u : 0u), "the native GPU clear did not write its exact metadata range"); } std::printf("Native full BDA clear: exact %zu-byte metadata readback passed; complete=%d\n", metadataBytes, Memory::StoredAllSince_nid_no_patch(target.htileAddress, metadataBytes, generation)); + Require(Memory::StoredAllSince_nid_no_patch(target.htileAddress, metadataBytes, generation), "the completed recognized native clear did not record exact coverage"); Require(CachedSampledTexture(context, words) == sampled, "a completed full BDA clear lost its native sampled view"); Require(DepthSurfaceView(context, target) == view, "a completed full BDA clear replaced its native attachment"); device.WaitIdle(); @@ -178,7 +201,11 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - return RunNativeClear(*device); + for (const auto consumer : {ClearConsumer::Owner, ClearConsumer::Texture, ClearConsumer::Attachment}) { + const auto result = RunNativeClear(*device, consumer); + if (result != 0) return result; + } + return 0; } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp index cda5b9280a..3d3531a577 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp @@ -7,11 +7,15 @@ #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" #include "SpirvBackend/SpirvModule.hpp" #include "VulkanTestDevice.hpp" +#include "FixtureDeviceSelection.hpp" #include #include #include @@ -21,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +40,12 @@ #include #endif +namespace AgcDriver::DriverDetail { +struct DriverTestAccess { + static std::shared_ptr GetDevice() { return Driver::Get().device.Load(); } +}; +} + namespace { using namespace AgcDriver::Graphics; @@ -44,6 +55,8 @@ public: Device() { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + library = SDL_LoadObject("libvulkan.1.dylib"); #else library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -55,26 +68,22 @@ public: app.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &app; +#ifdef __APPLE__ + const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + info.enabledExtensionCount = 1; + info.ppEnabledExtensionNames = &portability; +#endif Check(function("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); std::uint32_t count = 0u; const auto enumerate = function("vkEnumeratePhysicalDevices"); Check(enumerate(instance, &count, nullptr), "vkEnumeratePhysicalDevices"); std::vector devices(count); Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); - int selected = -1; - for (const auto physical : devices) { - VkPhysicalDeviceProperties properties{}; - function("vkGetPhysicalDeviceProperties")(physical, &properties); - if (const auto* wanted = std::getenv("ANYPS5_GPU"); wanted != nullptr && std::string(properties.deviceName).find(wanted) == std::string::npos) continue; - const int rank = properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? 3 : properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU ? 2 : 1; - if (properties.apiVersion < VK_API_VERSION_1_1 || rank <= selected) continue; - context.physical = physical; - context.limits = properties.limits; - selected = rank; - } - Require(context.physical != VK_NULL_HANDLE, "no Vulkan 1.1 device"); + context.physical = SelectFixturePhysicalDevice(devices, function("vkGetPhysicalDeviceProperties")); VkPhysicalDeviceProperties selectedProperties{}; function("vkGetPhysicalDeviceProperties")(context.physical, &selectedProperties); + context.limits = selectedProperties.limits; std::printf("Selected GPU name=%s vendor=0x%x device=0x%x driver=%u\n", selectedProperties.deviceName, selectedProperties.vendorID, selectedProperties.deviceID, selectedProperties.driverVersion); const auto queues = function("vkGetPhysicalDeviceQueueFamilyProperties"); queues(context.physical, &count, nullptr); @@ -137,7 +146,7 @@ private: class TargetMemory { public: - explicit TargetMemory(std::size_t bytes) : bytes(bytes) { + explicit TargetMemory(std::size_t bytes, bool registerAllocation = false) : bytes(bytes), registered(registerAllocation) { #ifdef _WIN32 pointer = GuestArena::GuestArenaAllocate_nid_postfix(bytes, 65536u); GuestArena::GuestArenaCommit_nid_postfix(pointer, bytes, PAGE_READWRITE, bytes); @@ -153,8 +162,10 @@ public: #endif Require(pointer != nullptr, "multisample target fixture allocation failed"); std::memset(pointer, 0xa5, bytes); + if (registered) GuestAllocations::Mutation().Add(pointer, bytes, true, true, true); } ~TargetMemory() { + if (registered) GuestAllocations::Mutation().Remove(pointer); #ifdef _WIN32 GuestArena::GuestArenaReset_nid_postfix(pointer, bytes); GuestArena::GuestArenaRelease_nid_postfix(pointer, bytes); @@ -168,8 +179,49 @@ public: private: void* pointer = nullptr; std::size_t bytes; + bool registered = false; }; +alignas(256) constexpr std::array HtileMaskKernel{ + 0xd7460000u, 0x04010c06u, 0x34000084u, 0xdc388000u, 0x04020000u, 0xbf8c3f70u, 0xd7710008u, 0x00120805u, + 0xd7710009u, 0x00120a05u, 0xd771000au, 0x00120c05u, 0xd771000bu, 0x00120e05u, 0xdc788000u, 0x00020800u, + 0xbf810000u, +}; + +std::vector DriverPm4Packet(std::uint32_t opcode, std::initializer_list body) { + std::vector words{0xc0000000u | (static_cast(body.size() - 1u) << 16u) | (opcode << 8u)}; + words.insert(words.end(), body); + return words; +} + +Context SubmitDriverHtileClear(std::uint64_t address, std::unique_lock& gpuLock) { + const auto shader = reinterpret_cast(HtileMaskKernel.data()); + std::vector commands; + const auto append = [&commands](std::vector packet) { commands.insert(commands.end(), packet.begin(), packet.end()); }; + append(DriverPm4Packet(0x76u, {0x20cu, static_cast(shader >> 8u)})); + append(DriverPm4Packet(0x76u, {0x20du, static_cast(shader >> 40u) & 0xffu})); + append(DriverPm4Packet(0x76u, {0x207u, 64u})); + append(DriverPm4Packet(0x76u, {0x208u, 1u})); + append(DriverPm4Packet(0x76u, {0x209u, 1u})); + append(DriverPm4Packet(0x76u, {0x213u, (6u << 1u) | 0x80u})); + append(DriverPm4Packet(0x76u, {0x240u, 0u})); + append(DriverPm4Packet(0x76u, {0x241u, 0u})); + append(DriverPm4Packet(0x76u, {0x242u, static_cast(address)})); + append(DriverPm4Packet(0x76u, {0x243u, static_cast(address >> 32u)})); + append(DriverPm4Packet(0x76u, {0x244u, 0xfffc00f0u})); + append(DriverPm4Packet(0x76u, {0x245u, 0u})); + append(DriverPm4Packet(0x15u, {32u, 1u, 1u, 0x41u})); + Packet packet{commands.data(), static_cast(commands.size()), 0u, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "native depth HTILE clear: PM4 submission failed"); + gpuLock.unlock(); + try { AgcDriverWaitIdle_nid_postfix(); } + catch (...) { gpuLock.lock(); throw; } + gpuLock.lock(); + const auto driverDevice = AgcDriver::DriverDetail::DriverTestAccess::GetDevice(); + Require(driverDevice != nullptr, "HTILE clear driver device is missing"); + return driverDevice->graphicsContext(); +} + template void TargetRefuses(Action action, std::string_view expected) { std::string message; @@ -548,7 +600,8 @@ void CheckDrawFeedback() { Require(device != nullptr, "native depth draw feedback requires Vulkan"); constexpr VkExtent2D extent{5u, 3u}; constexpr std::uint32_t samples = 4u; - TargetMemory memory(static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples))), metadata(65536u); + const auto depthBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples)); + TargetMemory memory(depthBytes), metadata(65536u); std::memset(metadata.Data(), 0, 65536u); DepthTarget depth{memory.Address(), 0u, extent, VK_FORMAT_D32_SFLOAT, 0.0f, 0u, metadata.Address(), false, samples}; depth.depthSwizzle = 24u; @@ -581,6 +634,9 @@ void CheckDrawFeedback() { const std::array initial{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0u}, {ShaderRecompiler::ShaderStage::Fragment, &producer, pixelOffset}}}; device->Draw(state, draw, initial); device->WaitIdle(); + std::array probe{}; + TargetRefuses([&] { AgcDriver::GuestMemory::Read(depth.address, probe); }, "sample-preserving guest publication"); + Require(DepthSurfaceAt(depth.address), "a refused guest read retired a native depth surface a draw wrote"); reader.spirv = DepthFeedbackFragmentShader(binding, true); reader.variantId = 3u; const auto words = DepthSampleWords(depth); @@ -597,10 +653,100 @@ void CheckDrawFeedback() { const std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0u}, {ShaderRecompiler::ShaderStage::Fragment, &reader, pixelOffset}}}; TargetRefuses([&] { device->Draw(state, draw, shaders); }, "sampling a native multisampled depth target the draw attaches"); device->WaitIdle(); - std::puts("Native depth feedback refused through the draw resource path"); + TargetMemory testedMemory(depthBytes), testedMetadata(65536u); + std::memset(testedMetadata.Data(), 0, 65536u); + const auto guestByte = [](std::size_t offset) { return static_cast(offset * 7u + 3u); }; + for (std::size_t offset = 0u; offset < depthBytes; ++offset) testedMemory.Data()[offset] = guestByte(offset); + auto tested = depth; + tested.address = testedMemory.Address(); + tested.htileAddress = testedMetadata.Address(); + state.depth = tested; + state.depthWrite = false; + device->Draw(state, draw, initial); + device->WaitIdle(); + Require(DepthSurfaceAt(tested.address), "a depth-test-only draw left no resident native depth surface"); + AgcDriver::GuestMemory::Read(tested.address + depthBytes - 1u, probe); + Require(probe[0] == guestByte(depthBytes - 1u), "a guest read after a depth-test-only draw did not return its guest bytes"); + Require(!DepthSurfaceAt(tested.address), "a guest read left a native depth surface only depth-tested resident"); + std::puts("Native depth feedback refused through the draw resource path; a written surface refused and a depth-tested one retired on guest read"); } -enum class ClearFault { None, PartialStore, CpuWrite, DepthWrite, BadMetadata, UnprovenDispatch, PendingWrite, Overlap }; +void CheckDepthAttachmentAlias() { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "native depth attachment alias requires Vulkan"); + const auto context = device->graphicsContext(); + constexpr VkExtent2D extent{5u, 3u}; + constexpr std::uint32_t samples = 4u; + const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples)); + TargetMemory memory(bytes), firstMetadata(65536u), secondMetadata(65536u); + std::memset(memory.Data(), 0x3c, bytes); + std::memset(firstMetadata.Data(), 0, 65536u); + std::memset(secondMetadata.Data(), 0, 65536u); + struct Cleanup { + AgcDriver::VulkanDevice& device; + ~Cleanup() { device.WaitIdle(); ClearCachedTextures(device.Device()); ClearDepthSurfaces(device.Device()); } + } cleanup{*device}; + DepthTarget first{memory.Address(), 0u, extent, VK_FORMAT_D32_SFLOAT, 0.0f, 0u, firstMetadata.Address(), false, samples}; + first.depthSwizzle = 24u; + first.iterate256 = first.htilePipeAligned = first.htileDepthOnly = true; + const auto firstView = DepthSurfaceView(context, first); + const auto words = DepthSampleWords(first); + auto kept = DepthSurfaceTexture(context, words, DecodeTextureResource(words), VkComponentMapping{}); + Require(kept != nullptr, "attachment alias fixture has no held native depth view"); + auto second = first; + second.extent.width = 6u; + second.htileAddress = secondMetadata.Address(); + Require(MultisampledDepthBytes_nid_no_patch(second.extent, 4u, samples) <= bytes, "attachment alias fixture exceeds its backing"); + const auto secondView = DepthSurfaceView(context, second); + Require(secondView != VK_NULL_HANDLE && secondView != firstView, "an aliased depth attachment reused the previous layout's view"); + Require(DepthSurfaceView(context, second) == secondView, "an unchanged native depth attachment was not reused"); + for (std::size_t offset = 0u; offset < bytes; ++offset) Require(memory.Data()[offset] == std::byte{0x3c}, "retiring an unwritten attachment alias changed guest bytes"); + std::puts("native depth attachment alias changes layout while retaining held GPU views"); +} + +void CheckStorageAliasRetirement() { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "native depth storage alias requires Vulkan"); + const auto context = device->graphicsContext(); + constexpr VkExtent2D extent{5u, 3u}; + constexpr std::uint32_t samples = 4u; + constexpr std::size_t prefix = 65536u; + const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples)); + TargetMemory memory(prefix + bytes), metadata(65536u); + std::memset(memory.Data(), 0x3c, prefix + bytes); + std::memset(metadata.Data(), 0, 65536u); + struct Cleanup { + AgcDriver::VulkanDevice& device; + ~Cleanup() { device.WaitIdle(); ClearCachedTextures(device.Device()); ClearDepthSurfaces(device.Device()); } + } cleanup{*device}; + GuestTextureResource alias{}; + alias.baseAddress = memory.Address(); + alias.width = 256u; + alias.height = static_cast((prefix + bytes) / 1024u); + alias.mipCount = alias.allocatedMipCount = 1u; + alias.dimension = TextureDimension::k2D; + alias.tileMode = TextureTileMode::kLinear; + alias.format = 22u; + alias.dstSelX = 0u; + alias.dstSelY = alias.dstSelZ = 4u; + alias.dstSelW = 5u; + auto cached = CachedStorageSurface(context, alias); + Require(cached != nullptr, "storage alias was not cached before native depth creation"); + DepthTarget depth{memory.Address() + prefix, 0u, extent, VK_FORMAT_D32_SFLOAT, 0.0f, 0u, metadata.Address(), false, samples}; + depth.depthSwizzle = 24u; + depth.iterate256 = depth.htilePipeAligned = depth.htileDepthOnly = true; + Require(DepthSurfaceView(context, depth) != VK_NULL_HANDLE && DepthSurfaceAt(depth.address), "storage alias fixture has no native depth owner"); + auto disjoint = alias; + disjoint.height = static_cast(prefix / 1024u); + Require(CachedStorageSurface(context, disjoint) != nullptr, "disjoint storage alias was not created"); + Require(DepthSurfaceAt(depth.address), "storage access ending at native depth retired a disjoint owner"); + Require(CachedStorageSurface(context, alias) != nullptr, "overlapping cached storage alias was not returned"); + Require(!DepthSurfaceAt(depth.address), "a cached storage view kept a native depth owner inside its range"); + for (std::size_t offset = 0u; offset < prefix + bytes; ++offset) Require(memory.Data()[offset] == std::byte{0x3c}, "retiring unwritten native depth changed guest bytes"); + std::puts("cached storage span retires only intersecting native depth owners"); +} + +enum class ClearFault { None, PartialStore, CpuWrite, DepthWrite, BadMetadata, UnprovenDispatch, PendingWrite, Overlap, PossibleStore, CompletedPossibleStore, DriverDispatch, ImportWindowWrite }; void CheckClearDispatch() { const std::array code{ @@ -609,7 +755,7 @@ void CheckClearDispatch() { 0xd771000a, 0x00120c05, 0xd771000b, 0x00120e05, 0xdc788000, 0x00020800, 0xbf810000, }; - const std::array userData{0u, 0u, 65536u, 0u, 0xf0u, 0xf0u}; + const std::array userData{0u, 0u, 65536u, 0u, 0xfffc00f0u, 0u}; const ShaderRecompiler::ShaderComputeStageInfo compute{{64u, 1u, 1u}, 0u, {true, false, false}, false, 1u}; const std::array packet{0u, 32u, 1u, 1u, 1u}; Require(HtileDepthClearBytes_nid_no_patch(code, userData, compute, packet, 0u) == 32768u, "complete HTILE mask dispatch was not recognized"); @@ -649,10 +795,10 @@ void CheckClearDispatch() { } } -void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, bool one, bool clearing = false, std::optional depthOnlyOverride = std::nullopt, ClearFault fault = ClearFault::None, bool iterate256 = true) { +void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, bool one, bool clearing, std::optional depthOnlyOverride, ClearFault fault, std::unique_lock& gpuLock, bool iterate256 = true) { const VkExtent2D extent{5u, 3u}; const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, format == VK_FORMAT_D16_UNORM ? 2u : 4u, fragments)); - TargetMemory memory(bytes), metadata(65536u); + TargetMemory memory(bytes), metadata(65536u, fault == ClearFault::DriverDispatch); const auto metadataBytes = HtileMetadataBytes(extent); const bool depthOnly = depthOnlyOverride.value_or(one); const std::uint32_t pattern = one ? (depthOnly ? 0xfffffff0u : 0xfffc00f0u) : 0u; @@ -679,6 +825,7 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, } const auto view = DepthSurfaceView(context, target); Require(DepthSurfaceView(context, target) == view, "native depth attachment did not reuse its image"); + NoteDepthSurfaceWrite(context, target); std::array beforeSampling{}; TargetRefuses([&] { AgcDriver::GuestMemory::Read(target.address + bytes - 1u, beforeSampling); }, "sample-preserving guest publication"); TargetRefuses([&] { RequireNoMultisampleTarget(target.htileAddress + metadataBytes - 1u, 1u, "a DMA copy"); }, "sample-preserving guest publication"); @@ -770,17 +917,65 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, target.clearDepth = clearOne ? 1.0f : 0.0f; Require(DepthSurfaceView(context, target) == view, "updating the native clear value replaced its attachment"); const std::uint32_t clearPattern = clearOne ? (depthOnly ? 0xfffffff0u : 0xfffc00f0u) : 0u; - const auto nativeBytes = fault == ClearFault::UnprovenDispatch ? 0u : static_cast(metadataBytes); - NoteHtileDepthClear(target.htileAddress, static_cast(metadataBytes), nativeBytes); - if (fault == ClearFault::Overlap) NoteHtileDepthClear(target.htileAddress, static_cast(metadataBytes), nativeBytes); - const auto storedBytes = fault == ClearFault::PartialStore ? 4u : static_cast(metadataBytes); - AgcDriver::GuestMemory::StoreOwnBytes(target.htileAddress, storedBytes, [&] { - for (std::size_t i = 0u; i < storedBytes / 4u; ++i) std::memcpy(metadata.Data() + i * 4u, &clearPattern, 4u); - if (fault == ClearFault::BadMetadata) metadata.Data()[0] = std::byte{0x0f}; - }); - if (fault == ClearFault::CpuWrite) metadata.Data()[metadataBytes - 1u] ^= std::byte{0x01}; - if (fault == ClearFault::DepthWrite) memory.Data()[bytes - 1u] ^= std::byte{0x01}; - if (fault == ClearFault::PendingWrite) QueueTargetByteWrite(context, recorder, target.htileAddress, std::byte{0}); + if (fault == ClearFault::DriverDispatch && clearOne) { + struct ShutdownDriver { + ~ShutdownDriver() { + try { AgcDriverShutdown_nid_postfix(); } catch (...) {} + } + } shutdownDriver; + const auto generation = AgcDriver::GuestMemory::TrackerGeneration(); + const auto driverContext = SubmitDriverHtileClear(target.htileAddress, gpuLock); + recorder.Activate(); + Require(Recorder::Active() == &recorder, "driver idle did not restore the native depth fixture recorder"); + if (!AgcDriver::GuestMemory::Watched(target.htileAddress, metadataBytes)) { + Require(PrepareImportWatch(driverContext) == ImportWatch::Unwatch, + "the HTILE metadata range lost write-watch coverage on a device whose host imports stay watched"); + std::puts("skipped native HTILE write-coverage assertion: the write tracker does not cover the imported metadata range; the production depth-sampling path may still decline this clear"); + TargetRefuses([&] { Require(DepthSurfaceHolds(context, DecodeTextureResource(words), texture.get()), "native depth owner was not retained"); }, + "native multisample depth clear requires tracked HTILE generations"); + return; + } else { + const bool storedAll = AgcDriver::GuestMemory::StoredAllSince_nid_no_patch(target.htileAddress, metadataBytes, generation); + Require(storedAll, "driver idle returned before recording the completed HTILE dispatch"); + } + } else { + const auto nativeBytes = fault == ClearFault::UnprovenDispatch ? 0u : static_cast(metadataBytes); + if (fault == ClearFault::PartialStore || fault == ClearFault::PossibleStore || fault == ClearFault::CompletedPossibleStore) { + for (std::size_t i = 0u; i < metadataBytes / 4u; ++i) std::memcpy(metadata.Data() + i * 4u, &clearPattern, 4u); + } + const bool completionCandidate = NoteHtileDepthClear(target.htileAddress, static_cast(metadataBytes), nativeBytes); + if (fault == ClearFault::ImportWindowWrite) { + const auto beforeImport = AgcDriver::GuestMemory::TrackerGeneration(); + Require(AgcDriver::GuestMemory::ImportWatched(target.htileAddress, static_cast(metadataBytes), [&] { + metadata.Data()[0] ^= std::byte{0x01}; + return true; + }), "HTILE host import simulation failed"); + Require(AgcDriver::GuestMemory::CollectWritesUncached(target.htileAddress, static_cast(metadataBytes)) > beforeImport, "HTILE import window did not stamp its changed page"); + } + if (completionCandidate) ArmHtileDepthClear(target.htileAddress, static_cast(metadataBytes)); + if (fault == ClearFault::Overlap) NoteHtileDepthClear(target.htileAddress, static_cast(metadataBytes), nativeBytes); + const auto storedBytes = fault == ClearFault::PartialStore ? 4u : static_cast(metadataBytes); + if (fault == ClearFault::PossibleStore || fault == ClearFault::CompletedPossibleStore) { + const auto generation = AgcDriver::GuestMemory::TrackerGeneration(); + AgcDriver::GuestMemory::MarkPossiblyWritten(target.htileAddress, metadataBytes); + Require(!AgcDriver::GuestMemory::StoredAllSince_nid_no_patch(target.htileAddress, metadataBytes, generation), "a possible HTILE range proved complete coverage"); + if (fault == ClearFault::CompletedPossibleStore) CompleteHtileDepthClear(target.htileAddress, static_cast(metadataBytes)); + } else AgcDriver::GuestMemory::StoreOwnBytes(target.htileAddress, storedBytes, [&] { + for (std::size_t i = 0u; i < storedBytes / 4u; ++i) std::memcpy(metadata.Data() + i * 4u, &clearPattern, 4u); + if (fault == ClearFault::BadMetadata) metadata.Data()[0] = std::byte{0x0f}; + }); + if (fault == ClearFault::CpuWrite) metadata.Data()[metadataBytes - 1u] ^= std::byte{0x01}; + if (fault == ClearFault::DepthWrite) memory.Data()[bytes - 1u] ^= std::byte{0x01}; + if (fault == ClearFault::PendingWrite) { + QueueTargetByteWrite(context, recorder, target.htileAddress, std::byte{0}); + std::string pendingRefusal; + try { CachedSampledTexture(context, words); } + catch (const std::exception& error) { pendingRefusal = error.what(); } + const bool refused = pendingRefusal.find("external GPU writes") != std::string::npos; + if (!refused) std::fprintf(stderr, "Pending HTILE texture refusal: %s\n", pendingRefusal.c_str()); + Require(refused, "an unrecognized pending HTILE write was accepted or refused for another reason"); + } + } Require(DepthSurfaceHolds(context, DecodeTextureResource(words), texture.get()), "completed native depth clear lost its sampled view"); recorder.Sync(); Require(DepthSurfaceView(context, target) == view && CachedSampledTexture(context, words) == texture, "completed native depth clear replaced an attachment or cached view"); @@ -806,10 +1001,10 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, Require(probe[0] == std::byte{0xa5}, "resident depth guard blocked an unrelated read"); auto alias = target; alias.htileAddress += 65536u; - TargetRefuses([&] { DepthSurfaceView(context, alias); }, "layout or metadata changed"); + TargetRefuses([&] { DepthSurfaceView(context, alias); }, "sample-preserving guest publication"); alias = target; alias.htileDepthOnly = !target.htileDepthOnly; - TargetRefuses([&] { DepthSurfaceView(context, alias); }, "layout or metadata changed"); + TargetRefuses([&] { DepthSurfaceView(context, alias); }, "sample-preserving guest publication"); auto resource = DecodeTextureResource(words); for (std::uint32_t field = 0u; field < 18u; ++field) { auto changed = resource; @@ -843,6 +1038,59 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, std::printf("Native depth format=%u samples=%u clear=%u iterate256=%u: imported, rasterized twice, sampled and refused conflicts\n", static_cast(format), fragments, one, iterate256); } +void CheckUnwrittenGuestAccess(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)); + const auto metadataBytes = static_cast(HtileMetadataBytes(extent)); + TargetMemory memory(bytes), metadata(65536u), unrelated(65536u); + const auto guestByte = [](std::size_t offset) { return static_cast(offset * 7u + 3u); }; + 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); }; + auto view = DepthSurfaceView(context, target); + Require(view != VK_NULL_HANDLE && resident(), "unwritten native depth fixture has no resident owner"); + auto otherDevice = context; + otherDevice.device = VK_NULL_HANDLE; + NoteDepthSurfaceWrite(otherDevice, target); + auto unattached = target; + unattached.address += 65536u; + NoteDepthSurfaceWrite(context, unattached); + std::array probe{}; + AgcDriver::GuestMemory::Read(unrelated.Address(), probe); + Require(resident(), "an unrelated guest read retired a native depth surface"); + for (const bool readMetadata : {false, true}) { + if (readMetadata) RequireNoMultisampleTarget(target.htileAddress + metadataBytes - 1u, 1u, "a DMA copy"); + else { + AgcDriver::GuestMemory::Read(target.address + bytes - 1u, probe); + Require(probe[0] == guestByte(bytes - 1u), "a guest read over an unwritten native depth surface did not return its guest bytes"); + } + Require(!resident(), "a guest access left an unwritten native depth surface resident"); + RequireNoResidentDepthRead_nid_no_patch(target.address, bytes); + RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes); + recorder.Sync(); + const auto rebound = DepthSurfaceView(context, target); + Require(rebound != VK_NULL_HANDLE && rebound != view && resident(), "rebinding after a guest access did not import a new native depth surface"); + view = rebound; + } + NoteDepthSurfaceWrite(context, target); + TargetRefuses([&] { AgcDriver::GuestMemory::Read(target.address, probe); }, "sample-preserving guest publication"); + TargetRefuses([&] { RequireNoMultisampleTarget(target.htileAddress, 1u, "a DMA copy"); }, "sample-preserving guest publication"); + Require(resident(), "a refused guest access retired a written native depth surface"); + for (std::size_t offset = 0u; offset < bytes; ++offset) Require(memory.Data()[offset] == guestByte(offset), "retiring an unwritten native depth surface changed its guest bytes"); + Require(std::all_of(metadata.Data(), metadata.Data() + metadataBytes, [](std::byte value) { return value == std::byte{0}; }), "retiring an unwritten native depth surface changed its HTILE"); + std::printf("Native depth format=%u samples=%u: guest access retired the unwritten surface and refused the written one\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)); @@ -868,7 +1116,7 @@ void CheckHtileRouting(Context context, VkFormat format, std::uint32_t fragments auto alias = target; alias.samples = 1u; alias.address = single.Address(); - TargetRefuses([&] { DepthSurfaceView(context, alias); }, "layout or metadata changed"); + Require(DepthSurfaceView(context, alias) != VK_NULL_HANDLE, "a single-sample bind could not retire an unwritten native HTILE owner"); Require(untouched(), "a single-sample bind over a native owner's HTILE stored expanded words into it"); std::printf("Native depth format=%u samples=%u: binds over the owner's HTILE left its words alone\n", static_cast(format), fragments); } @@ -1031,6 +1279,7 @@ void CheckRetirement(Context context, VkFormat format, std::uint32_t samples, st const auto words = DepthSampleWords(target); const auto resource = DecodeTextureResource(words); const auto firstView = DepthSurfaceView(context, target); + NoteDepthSurfaceWrite(context, target); auto firstTexture = DepthSurfaceTexture(context, words, resource, VkComponentMapping{}); Require(firstTexture != nullptr, "retirement fixture has no native sampled view"); ColorTarget color{colorMemory.Address(), extent, VK_FORMAT_R8G8B8A8_UNORM, extent.width * extent.height * 4u, 0u}; @@ -1129,7 +1378,9 @@ int main(int argc, char** argv) { } CheckDrawFeedback(); const auto context = device->GetContext(); - const std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + if (argc == 1) CheckDepthAttachmentAlias(); + if (argc == 1) CheckStorageAliasRetirement(); + std::unique_lock lock(AgcDriver::GuestMemory::GpuMutex()); Require(DepthSupported(context, VK_FORMAT_D32_SFLOAT, 4u), "the native depth regression requires sampled D32 with four samples"); if (argc == 2 && std::string_view(argv[1]) == "--clear-refusals") { for (const auto& [fault, expected] : std::array{ @@ -1137,10 +1388,11 @@ int main(int argc, char** argv) { std::pair{ClearFault::CpuWrite, "CPU writes raced"}, std::pair{ClearFault::DepthWrite, "guest writes"}, std::pair{ClearFault::BadMetadata, "does not prove its clear value"}, + std::pair{ClearFault::PossibleStore, "no complete tracked HTILE store"}, std::pair{ClearFault::UnprovenDispatch, "incomplete or unproven"}, std::pair{ClearFault::PendingWrite, "external GPU writes"}, std::pair{ClearFault::Overlap, "overlapping native multisample depth"}}) { - TargetRefuses([&] { CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, fault); }, expected); + TargetRefuses([&] { CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, fault, lock); }, expected); std::printf("Native depth clear fault=%u refused: %s\n", static_cast(fault), expected); } return 0; @@ -1152,10 +1404,16 @@ int main(int argc, char** argv) { for (const auto samples : {2u, 4u, 8u}) { if (!DepthSupported(context, format, samples)) { unavailable += 4u; continue; } for (const bool depthOnly : {false, true}) { - for (const bool one : {false, true}) { CheckDepthTarget(context, format, samples, one, true, depthOnly); ++checked; } + for (const bool one : {false, true}) { CheckDepthTarget(context, format, samples, one, true, depthOnly, ClearFault::None, lock); ++checked; } } } } + CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::CompletedPossibleStore, lock); + ++checked; + CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::ImportWindowWrite, lock); + ++checked; + CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::DriverDispatch, lock); + ++checked; std::printf("Native depth completed clear cases checked=%u unavailable=%u\n", checked, unavailable); return 0; } @@ -1176,9 +1434,10 @@ int main(int argc, char** argv) { for (const auto samples : {2u, 4u, 8u}) { if (!DepthSupported(context, format, samples)) { unavailable += 4u; continue; } for (const bool one : {false, true}) { - for (const bool iterate256 : {true, false}) { CheckDepthTarget(context, format, samples, one, false, std::nullopt, ClearFault::None, iterate256); ++checked; } + for (const bool iterate256 : {true, false}) { CheckDepthTarget(context, format, samples, one, false, std::nullopt, ClearFault::None, lock, iterate256); ++checked; } } CheckHtileRouting(context, format, samples); + CheckUnwrittenGuestAccess(context, format, samples); } } CheckEligibility(context); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/OptimizeOnFiber.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/OptimizeOnFiber.cpp new file mode 100644 index 0000000000..68f3ccb6a6 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/OptimizeOnFiber.cpp @@ -0,0 +1,140 @@ +#include "prx/libSceFiber/include/Api.hpp" +#if defined(ANYPS5_ENABLE_SPIRV_TOOLS) && ANYPS5_ENABLE_SPIRV_TOOLS +#include "SpirvBackend/SpirvOptimizer.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::size_t ContextBytes = 64 * 1024; +constexpr std::size_t FiberStackLimit = 8 * 1024; +constexpr std::uint32_t VulkanVersion = 0x00401000u; +constexpr std::uint32_t SpirvVersion = 0x00010300u; +constexpr std::uint64_t Fill = 0x5aa5c33c5aa5c33cull; + +alignas(16) std::array FiberContext; +struct alignas(8) FiberStorage { + unsigned char bytes[0x100]; +}; +FiberStorage FiberObjectStorage; + +constexpr std::array Module{ + 0x07230203u, 0x00010300u, 0x0008000bu, 0x00000044u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0006000fu, 0x00000005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000bu, 0x00060010u, 0x00000004u, + 0x00000011u, 0x00000040u, 0x00000001u, 0x00000001u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x00040005u, 0x00000008u, 0x65646e69u, 0x00000078u, 0x00080005u, + 0x0000000bu, 0x475f6c67u, 0x61626f6cu, 0x766e496cu, 0x7461636fu, 0x496e6f69u, 0x00000044u, 0x00030005u, + 0x00000010u, 0x006d7573u, 0x00040005u, 0x00000011u, 0x74617473u, 0x00000065u, 0x00040005u, 0x00000013u, + 0x61746144u, 0x00000000u, 0x00050006u, 0x00000013u, 0x00000000u, 0x756c6176u, 0x00007365u, 0x00030005u, + 0x00000015u, 0x00000000u, 0x00030005u, 0x0000001cu, 0x00000069u, 0x00040047u, 0x0000000bu, 0x0000000bu, + 0x0000001cu, 0x00040047u, 0x00000012u, 0x00000006u, 0x00000004u, 0x00030047u, 0x00000013u, 0x00000002u, + 0x00050048u, 0x00000013u, 0x00000000u, 0x00000023u, 0x00000000u, 0x00040047u, 0x00000015u, 0x00000021u, + 0x00000000u, 0x00040047u, 0x00000015u, 0x00000022u, 0x00000000u, 0x00040047u, 0x00000043u, 0x0000000bu, + 0x00000019u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00040015u, 0x00000006u, + 0x00000020u, 0x00000000u, 0x00040020u, 0x00000007u, 0x00000007u, 0x00000006u, 0x00040017u, 0x00000009u, + 0x00000006u, 0x00000003u, 0x00040020u, 0x0000000au, 0x00000001u, 0x00000009u, 0x0004003bu, 0x0000000au, + 0x0000000bu, 0x00000001u, 0x0004002bu, 0x00000006u, 0x0000000cu, 0x00000000u, 0x00040020u, 0x0000000du, + 0x00000001u, 0x00000006u, 0x0003001du, 0x00000012u, 0x00000006u, 0x0003001eu, 0x00000013u, 0x00000012u, + 0x00040020u, 0x00000014u, 0x0000000cu, 0x00000013u, 0x0004003bu, 0x00000014u, 0x00000015u, 0x0000000cu, + 0x00040015u, 0x00000016u, 0x00000020u, 0x00000001u, 0x0004002bu, 0x00000016u, 0x00000017u, 0x00000000u, + 0x00040020u, 0x00000019u, 0x0000000cu, 0x00000006u, 0x0004002bu, 0x00000006u, 0x00000023u, 0x00000010u, + 0x00020014u, 0x00000024u, 0x0004002bu, 0x00000006u, 0x00000027u, 0x00000001u, 0x0004002bu, 0x00000006u, + 0x00000038u, 0x0019660du, 0x0004002bu, 0x00000006u, 0x0000003au, 0x3c6ef35fu, 0x0004002bu, 0x00000016u, + 0x0000003du, 0x00000001u, 0x0004002bu, 0x00000006u, 0x00000042u, 0x00000040u, 0x0006002cu, 0x00000009u, + 0x00000043u, 0x00000042u, 0x00000027u, 0x00000027u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, + 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu, 0x00000007u, 0x00000008u, 0x00000007u, 0x0004003bu, + 0x00000007u, 0x00000010u, 0x00000007u, 0x0004003bu, 0x00000007u, 0x00000011u, 0x00000007u, 0x0004003bu, + 0x00000007u, 0x0000001cu, 0x00000007u, 0x00050041u, 0x0000000du, 0x0000000eu, 0x0000000bu, 0x0000000cu, + 0x0004003du, 0x00000006u, 0x0000000fu, 0x0000000eu, 0x0003003eu, 0x00000008u, 0x0000000fu, 0x0003003eu, + 0x00000010u, 0x0000000cu, 0x0004003du, 0x00000006u, 0x00000018u, 0x00000008u, 0x00060041u, 0x00000019u, + 0x0000001au, 0x00000015u, 0x00000017u, 0x00000018u, 0x0004003du, 0x00000006u, 0x0000001bu, 0x0000001au, + 0x0003003eu, 0x00000011u, 0x0000001bu, 0x0003003eu, 0x0000001cu, 0x0000000cu, 0x000200f9u, 0x0000001du, + 0x000200f8u, 0x0000001du, 0x000400f6u, 0x0000001fu, 0x00000020u, 0x00000000u, 0x000200f9u, 0x00000021u, + 0x000200f8u, 0x00000021u, 0x0004003du, 0x00000006u, 0x00000022u, 0x0000001cu, 0x000500b0u, 0x00000024u, + 0x00000025u, 0x00000022u, 0x00000023u, 0x000400fau, 0x00000025u, 0x0000001eu, 0x0000001fu, 0x000200f8u, + 0x0000001eu, 0x0004003du, 0x00000006u, 0x00000026u, 0x00000011u, 0x000500c7u, 0x00000006u, 0x00000028u, + 0x00000026u, 0x00000027u, 0x000500abu, 0x00000024u, 0x00000029u, 0x00000028u, 0x0000000cu, 0x000300f7u, + 0x0000002bu, 0x00000000u, 0x000400fau, 0x00000029u, 0x0000002au, 0x00000031u, 0x000200f8u, 0x0000002au, + 0x0004003du, 0x00000006u, 0x0000002cu, 0x00000011u, 0x0004003du, 0x00000006u, 0x0000002du, 0x0000001cu, + 0x00050084u, 0x00000006u, 0x0000002eu, 0x0000002cu, 0x0000002du, 0x0004003du, 0x00000006u, 0x0000002fu, + 0x00000010u, 0x00050080u, 0x00000006u, 0x00000030u, 0x0000002fu, 0x0000002eu, 0x0003003eu, 0x00000010u, + 0x00000030u, 0x000200f9u, 0x0000002bu, 0x000200f8u, 0x00000031u, 0x0004003du, 0x00000006u, 0x00000032u, + 0x00000011u, 0x0004003du, 0x00000006u, 0x00000033u, 0x0000001cu, 0x00050080u, 0x00000006u, 0x00000034u, + 0x00000032u, 0x00000033u, 0x0004003du, 0x00000006u, 0x00000035u, 0x00000010u, 0x000500c6u, 0x00000006u, + 0x00000036u, 0x00000035u, 0x00000034u, 0x0003003eu, 0x00000010u, 0x00000036u, 0x000200f9u, 0x0000002bu, + 0x000200f8u, 0x0000002bu, 0x0004003du, 0x00000006u, 0x00000037u, 0x00000011u, 0x00050084u, 0x00000006u, + 0x00000039u, 0x00000037u, 0x00000038u, 0x00050080u, 0x00000006u, 0x0000003bu, 0x00000039u, 0x0000003au, + 0x0003003eu, 0x00000011u, 0x0000003bu, 0x000200f9u, 0x00000020u, 0x000200f8u, 0x00000020u, 0x0004003du, + 0x00000006u, 0x0000003cu, 0x0000001cu, 0x00050080u, 0x00000006u, 0x0000003eu, 0x0000003cu, 0x0000003du, + 0x0003003eu, 0x0000001cu, 0x0000003eu, 0x000200f9u, 0x0000001du, 0x000200f8u, 0x0000001fu, 0x0004003du, + 0x00000006u, 0x0000003fu, 0x00000008u, 0x0004003du, 0x00000006u, 0x00000040u, 0x00000010u, 0x00060041u, + 0x00000019u, 0x00000041u, 0x00000015u, 0x00000017u, 0x0000003fu, 0x0003003eu, 0x00000041u, 0x00000040u, + 0x000100fdu, 0x00010038u +}; + +std::vector Optimized; +std::exception_ptr Failure; + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +std::vector Optimize() { + return ShaderRecompiler::ValidateAndOptimizeSpirv(Module, VulkanVersion, SpirvVersion); +} + +void APS5_VABI OptimizeOnFiber(std::uint64_t, std::uint64_t) { + try { + Optimized = Optimize(); + } catch (...) { + Failure = std::current_exception(); + } + sceFiberReturnToThread(0, nullptr); +} + +std::size_t FiberStackUsed() { + std::size_t untouched = 0; + while (untouched < FiberContext.size() && FiberContext[untouched] == Fill) ++untouched; + return (FiberContext.size() - untouched) * sizeof(std::uint64_t); +} + +} + +int main() { + try { + FiberContext.fill(Fill); + auto* fiber = reinterpret_cast(&FiberObjectStorage); + Require(_sceFiberInitializeImpl_nid_postfix(fiber, "optimize", OptimizeOnFiber, 0, FiberContext.data(), ContextBytes, nullptr, 0) == 0, "SPIR-V optimization on a fiber: the fiber was not initialized"); + const auto before = ShaderRecompiler::OptimizerHostThreads(); + Require(sceFiberRun_nid_postfix(fiber, 0, nullptr) == 0, "SPIR-V optimization on a fiber: the fiber did not run"); + const auto onFiber = ShaderRecompiler::OptimizerHostThreads() - before; + Require(sceFiberFinalize(fiber) == 0, "SPIR-V optimization on a fiber: the fiber was not finalized"); + if (Failure) std::rethrow_exception(Failure); + const auto used = FiberStackUsed(); + const auto beforeThread = ShaderRecompiler::OptimizerHostThreads(); + const auto onThreadResult = Optimize(); + const auto onThread = ShaderRecompiler::OptimizerHostThreads() - beforeThread; + Require(Optimized == onThreadResult, "SPIR-V optimization on a fiber returned another module than on the thread"); + Require(used <= FiberStackLimit, "SPIR-V optimization on a fiber running on its context used " + std::to_string(used) + " bytes of it, more than " + std::to_string(FiberStackLimit) + ": the optimizer ran on it"); + Require(onFiber == 1, "SPIR-V optimization on a fiber running on its context started " + std::to_string(onFiber) + " host threads instead of one"); + Require(onThread == 0, "SPIR-V optimization on an ordinary thread started " + std::to_string(onThread) + " host threads instead of optimizing on its own stack"); + std::cout << "SPIR-V optimization on a fiber used " << used << " bytes of its context\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} +#else +int main() { + return 77; +} +#endif diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/OversizedDescriptorSets.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/OversizedDescriptorSets.cpp index 8984302a91..213d8c7aaf 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/OversizedDescriptorSets.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/OversizedDescriptorSets.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "VulkanTestDevice.hpp" +#include "FixtureDeviceSelection.hpp" #include #include #include @@ -50,15 +51,7 @@ public: std::vector devices(count); Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); const auto properties = function("vkGetPhysicalDeviceProperties"); - context.physical = devices.front(); - for (const auto candidate : devices) { - VkPhysicalDeviceProperties described{}; - properties(candidate, &described); - if (described.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { - context.physical = candidate; - break; - } - } + context.physical = SelectFixturePhysicalDevice(devices, properties); VkPhysicalDeviceProperties selected{}; properties(context.physical, &selected); context.limits = selected.limits; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/OversizedDispatch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/OversizedDispatch.cpp index 03d1ada000..b31f2c597f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/OversizedDispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/OversizedDispatch.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -141,14 +142,17 @@ void CheckLdsSplitThrows(AgcDriver::VulkanDevice& device) { request.useCache = false; const auto result = ShaderRecompiler::Recompile(request); Require(result.workgroupMemoryDwords != 0u, "oversized dispatch: the LDS shader did not take the device-memory path"); - bool threw = false; - try { - device.Dispatch(result, 1, device.WorkGroupCountLimit()[1] + 1u, 1, {}, reinterpret_cast(code.data())); - } catch (const std::runtime_error&) { - threw = true; + for (const auto z : {1u, std::numeric_limits::max()}) { + bool threw = false; + try { + device.Dispatch(result, 1, device.WorkGroupCountLimit()[1] + 1u, z, {}, reinterpret_cast(code.data())); + } catch (const std::runtime_error& error) { + Require(std::string(error.what()).find("a split aliases the device-memory LDS slots") != std::string::npos, "oversized dispatch: wrong LDS split refusal: " + std::string(error.what())); + threw = true; + } + device.WaitIdle(); + Require(threw, "oversized dispatch: a Y count over the limit with device-memory LDS did not throw"); } - device.WaitIdle(); - Require(threw, "oversized dispatch: a Y count over the limit with device-memory LDS did not throw"); } alignas(256) constexpr std::array GroupYCode{ diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PackedFloatColorTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PackedFloatColorTarget.cpp index 9b322cb6d0..238cb5ce89 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PackedFloatColorTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PackedFloatColorTarget.cpp @@ -45,6 +45,7 @@ constexpr std::uint32_t Height = 128; constexpr std::uint32_t ScissorWidth = 128; constexpr std::size_t BlockBytes = 65536; constexpr std::uint32_t Float11_11_10Info = 0x4171cu; +constexpr std::uint32_t StandardOrderInfo = Float11_11_10Info & ~0x1800u; constexpr std::uint32_t TiledAttrib3 = 0x4dc6c000u; constexpr std::uint32_t LinearAttrib3 = 0x09000000u; constexpr std::uint32_t Float32Export = 9; @@ -58,7 +59,7 @@ 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} }}; -alignas(256) std::array, 32> Programs{}; +alignas(256) std::array, 64> Programs{}; std::size_t programCount = 0; std::uint32_t SmallFloat(float value, std::uint32_t mantissaBits) { @@ -78,7 +79,8 @@ std::uint32_t SmallFloat(float value, std::uint32_t mantissaBits) { return (static_cast(exponent - 1 + 15) << mantissaBits) | static_cast(std::floor((fraction * 2.0 - 1.0) * scale)); } -std::uint32_t Packed(std::array rgb) { +std::uint32_t Packed(std::array rgb, bool standardOrder) { + if (standardOrder) return SmallFloat(rgb[0], 5) | (SmallFloat(rgb[1], 6) << 10u) | (SmallFloat(rgb[2], 6) << 21u); return SmallFloat(rgb[2], 5) | (SmallFloat(rgb[1], 6) << 10u) | (SmallFloat(rgb[0], 6) << 21u); } @@ -170,17 +172,17 @@ struct Block { std::uint8_t* data = nullptr; }; -AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t attrib3) { +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t attrib3, bool standardOrder) { AgcDriver::Registers cx; cx[0x318] = static_cast(block.Address() >> 8u); cx[0x31b] = 0; - cx[0x31c] = Float11_11_10Info; + cx[0x31c] = standardOrder ? StandardOrderInfo : Float11_11_10Info; cx[0x31d] = 0; cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); cx[0x3b8] = attrib3; cx[0x390] = static_cast(block.Address() >> 40u); auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); - Require(color.format == VK_FORMAT_R32_UINT && color.elementBytes == 4 && color.componentMapping == 0xc6u && color.packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 && color.bytes == block.bytes, "the 11_11_10 float target did not decode as a packed R32_UINT surface"); + Require(color.format == VK_FORMAT_R32_UINT && color.elementBytes == 4 && color.componentMapping == (standardOrder ? 0xe4u : 0xc6u) && color.packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 && color.bytes == block.bytes, "the 11_11_10 float target did not decode as a packed R32_UINT surface"); return color; } @@ -290,10 +292,10 @@ std::string Describe(std::array rgb) { return text; } -void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t attrib3, ColorTileMode mode, const char* kind) { +void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t attrib3, ColorTileMode mode, const char* kind, bool standardOrder) { const ColorTargetLayout layout(Width, Height, mode, 4); Require(layout.Bytes() == block.bytes, "the test surface size disagrees with its layout"); - const auto color = DecodeTarget(block, attrib3); + const auto color = DecodeTarget(block, attrib3, standardOrder); auto before = Fill(block); const float nan = std::numeric_limits::quiet_NaN(); const float infinity = std::numeric_limits::infinity(); @@ -311,12 +313,12 @@ void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t at }}; for (const auto& rgb : cases) { Draw(device, color, Float32Export, Float32Program(rgb)); - ExpectSurface(device, block, layout, before, Packed(rgb), std::string("a 32_ABGR export of ") + Describe(rgb) + " into a " + kind + " 11_11_10 float target"); + ExpectSurface(device, block, layout, before, Packed(rgb, standardOrder), std::string("a 32_ABGR export of ") + Describe(rgb) + " into a " + kind + " 11_11_10 float target"); before = ReadBack(device, block); } const std::array halves{0.5f, 0.75f, 0.125f}; Draw(device, color, Float16Export, Float16Program(halves)); - ExpectSurface(device, block, layout, before, Packed(halves), std::string("an FP16_ABGR export of ") + Describe(halves) + " into a " + kind + " 11_11_10 float target"); + ExpectSurface(device, block, layout, before, Packed(halves, standardOrder), std::string("an FP16_ABGR export of ") + Describe(halves) + " into a " + kind + " 11_11_10 float target"); } } @@ -327,8 +329,10 @@ int main() { if (!device) return VulkanTestSkipped; Block tiled(ColorTargetLayout(Width, Height, ColorTileMode::RenderTarget, 4).Bytes()); Block linear(ColorTargetLayout(Width, Height, ColorTileMode::Linear, 4).Bytes()); - TargetTests(*device, tiled, TiledAttrib3, ColorTileMode::RenderTarget, "tiled"); - TargetTests(*device, linear, LinearAttrib3, ColorTileMode::Linear, "linear"); + for (const bool standardOrder : {false, true}) { + TargetTests(*device, tiled, TiledAttrib3, ColorTileMode::RenderTarget, standardOrder ? "tiled standard-order" : "tiled", standardOrder); + TargetTests(*device, linear, LinearAttrib3, ColorTileMode::Linear, standardOrder ? "linear standard-order" : "linear", standardOrder); + } std::puts("11_11_10 float color target tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444ColorTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444ColorTarget.cpp new file mode 100644 index 0000000000..85326136cf --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444ColorTarget.cpp @@ -0,0 +1,330 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "Optimization/ResourceProgram.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::ColorTargetLayout; +using AgcDriver::Graphics::ColorTileMode; +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 256; +constexpr std::uint32_t Height = 128; +constexpr std::uint32_t ScissorWidth = 128; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Unorm4_4_4_4Info = 0x8000u | (19u << 2u) | (2u << 11u); +constexpr std::uint32_t LinearAttrib3 = 0x09000000u; +constexpr std::uint32_t Float32Export = 9; +constexpr std::uint32_t Float16Export = 4; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +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} +}}; + +alignas(256) std::array, 32> Programs{}; +std::size_t programCount = 0; + +struct Golden { + std::array rgba; + std::uint16_t word; +}; + +std::uint32_t Half(float value) { + Require(value == 0.0f || (value >= 0.0625f && value <= 1.0f), "the test's half values are normal and at most 1"); + if (value == 0.0f) return 0; + const auto bits = std::bit_cast(value); + Require((bits & 0x1fffu) == 0, "the test's half values are exact"); + return ((((bits >> 23u) & 0xffu) - 112u) << 10u) | ((bits >> 13u) & 0x3ffu); +} + +std::span Program(const std::array& vgprs, bool compressed) { + Require(programCount < Programs.size(), "too many test programs"); + auto& code = Programs[programCount++]; + std::size_t at = 0; + for (std::uint32_t vgpr = 0; vgpr < (compressed ? 2u : 4u); ++vgpr) { + code[at++] = 0x7e0002ffu | ((4u + vgpr) << 17u); + code[at++] = vgprs[vgpr]; + } + code[at++] = compressed ? 0xf8001c0fu : 0xf800180fu; + code[at++] = compressed ? 0x00000504u : 0x07060504u; + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + +std::span Float32Program(std::array rgba) { + return Program({std::bit_cast(rgba[0]), std::bit_cast(rgba[1]), std::bit_cast(rgba[2]), std::bit_cast(rgba[3])}, false); +} + +std::span Float16Program(std::array rgba) { + return Program({Half(rgba[0]) | (Half(rgba[1]) << 16u), Half(rgba[2]) | (Half(rgba[3]) << 16u), 0, 0}, true); +} + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (watched) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (watched) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the color target"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "cannot allocate the color target"); + Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched"); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + AgcDriver::Graphics::StorageTexture::FlushPending(Address(), bytes, nullptr, "test release"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + if (watched) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (watched) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + bool watched; + std::uint8_t* data = nullptr; +}; + +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t attrib3) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(block.Address() >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Unorm4_4_4_4Info; + cx[0x31d] = 0; + cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); + cx[0x3b8] = attrib3; + cx[0x390] = static_cast(block.Address() >> 40u); + auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + Require(color.format == VK_FORMAT_R16_UINT && color.elementBytes == 2 && color.componentMapping == 0x1bu && color.packing == ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4 && color.bytes == block.bytes, "the 4_4_4_4 unorm target did not decode as a packed R16_UINT target"); + return color; +} + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t exportFormat, std::span pixelCode) { + const auto target = device.Target(); + constexpr std::uint32_t waveSize = 64; + std::vector vertexUserData(4, 0u); + const auto address = reinterpret_cast(Triangle.data()); + const std::array vertexBuffer{static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), static_cast(Triangle.size()), 0x01016facu}; + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.colors = {color}; + state.color = 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}, {ScissorWidth, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + VkPipelineColorBlendAttachmentState blend{}; + blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + state.blends = {blend}; + state.blend = blend; + state.blendConstants = {}; + + const auto codeAddress = reinterpret_cast(pixelCode.data()); + const AgcDriver::Registers context{{0x1b6, 0x8000}, {0x1b3, 0}, {0x1b4, 0}, {0x203, 0}, {0x1c5, exportFormat}}; + const std::array pixelMemory{{{codeAddress, std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, codeAddress, pixelCode, 0, {}}, + {waveSize, 0, {}, std::nullopt, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + const auto handle = ShaderRecompiler::PrepareShader(fragment); + AgcDriver::DriverDetail::ShaderSnapshot snapshot{codeAddress, 0, 1, {pixelCode.begin(), pixelCode.end()}, {}}; + snapshot.prepared->entries.push_back({0, handle}); + fragment.shader.code = snapshot.code; + fragment.context.pixel = AgcDriver::Graphics::DecodePixelStageInfo(context, AgcDriver::Graphics::ExportMappings(state)); + fragment.context.pixel->targetExportPacking = AgcDriver::Graphics::ExportPackings(state); + const ShaderRecompiler::SrtRuntime runtime{}; + const auto materialize = [&](const ShaderRecompiler::RecompileRequest& request) { + const auto invocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, request); + return invocation.Materialize(*invocation.Capture(runtime)); + }; + const auto pixelResult = materialize(fragment); + auto unpacked = fragment; + unpacked.context.pixel->targetExportPacking.fill(ShaderRecompiler::ColorExportPacking::None); + const auto plain = materialize(unpacked); + Require(pixelResult->variantId == ShaderRecompiler::GetPreparedArtifact(*handle).variantId && plain->variantId == pixelResult->variantId, "the draw did not use the registered pixel shader"); + Require(plain->PipelineVariantId() != pixelResult->PipelineVariantId() && materialize(fragment)->PipelineVariantId() == pixelResult->PipelineVariantId(), "the 4_4_4_4 unorm packing did not key its own specialization"); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, pixelResult.get(), PixelPushOffset(vertexPush, target)} + }}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); +} + +std::vector ReadBack(AgcDriver::VulkanDevice& device, const Block& block) { + AgcDriver::Graphics::StorageTexture::FlushPending(block.Address(), block.bytes, nullptr, "test read-back"); + device.WaitIdle(); + std::vector stored(block.bytes / 2u); + AgcDriver::GuestMemory::Read(block.Address(), std::as_writable_bytes(std::span(stored)), 1); + return stored; +} + +void ExpectSurface(AgcDriver::VulkanDevice& device, const Block& block, const ColorTargetLayout& layout, const std::vector& before, std::uint32_t word, const std::string& what) { + const auto stored = ReadBack(device, block); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto index = layout.Offset(x, y) / 2u; + const bool inside = x < ScissorWidth; + const std::uint32_t expected = inside ? word : before[index]; + if (stored[index] == expected) continue; + char detail[160]; + std::snprintf(detail, sizeof(detail), ": pixel (%u, %u) %s the scissor holds 0x%04x, expected 0x%04x", x, y, inside ? "inside" : "outside", stored[index], expected); + throw std::runtime_error(what + detail); + } + } +} + +std::vector Fill(Block& block) { + std::vector words(block.bytes / 2u); + for (std::size_t index = 0; index < words.size(); ++index) words[index] = static_cast((index * 0x9e3779b9u) >> 11u); + std::memcpy(block.data, words.data(), block.bytes); + return words; +} + +std::string Describe(std::array rgba) { + char text[112]; + std::snprintf(text, sizeof(text), "(%.9g, %.9g, %.9g, %.9g)", rgba[0], rgba[1], rgba[2], rgba[3]); + return text; +} + +void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t attrib3, ColorTileMode mode, const char* kind) { + const ColorTargetLayout layout(Width, Height, mode, 2); + Require(layout.Bytes() == block.bytes, "the test surface size disagrees with its layout"); + const auto color = DecodeTarget(block, attrib3); + auto before = Fill(block); + const float nan = std::numeric_limits::quiet_NaN(); + const float infinity = std::numeric_limits::infinity(); + const std::array float32Cases{{ + {{1.0f, 0.0f, 0.0f, 0.0f}, 0xf000u}, + {{0.0f, 1.0f, 0.0f, 0.0f}, 0x0f00u}, + {{0.0f, 0.0f, 1.0f, 0.0f}, 0x00f0u}, + {{0.0f, 0.0f, 0.0f, 1.0f}, 0x000fu}, + {{1.0f, 1.0f, 1.0f, 1.0f}, 0xffffu}, + {{1.0f, 0.5f, 0.0f, 0.25f}, 0xf804u}, + {{0.0f, 1.0f, 0.5f, 0.25f}, 0x0f84u}, + {{nan, -1.0f, 2.0f, infinity}, 0x00ffu}, + {{1.0f / 15.0f, 2.0f / 15.0f, 7.0f / 15.0f, 14.0f / 15.0f}, 0x127eu}, + {{0.1f, 0.7f, 0.9f, 0.3f}, 0x2ad5u}, + {{-infinity, std::numeric_limits::min(), 0.999999940f, std::numeric_limits::denorm_min()}, 0x00f0u}, + }}; + for (const auto& golden : float32Cases) { + Draw(device, color, Float32Export, Float32Program(golden.rgba)); + ExpectSurface(device, block, layout, before, golden.word, std::string("a 32_ABGR export of ") + Describe(golden.rgba) + " into a " + kind + " 4_4_4_4 unorm target"); + before = ReadBack(device, block); + } + const std::array float16Cases{{ + {{1.0f, 0.0f, 0.0f, 0.0f}, 0xf000u}, + {{0.0f, 1.0f, 0.0f, 0.0f}, 0x0f00u}, + {{0.0f, 0.0f, 1.0f, 0.0f}, 0x00f0u}, + {{0.0f, 0.0f, 0.0f, 1.0f}, 0x000fu}, + {{1.0f, 1.0f, 1.0f, 1.0f}, 0xffffu}, + {{1.0f, 0.5f, 0.0f, 0.25f}, 0xf804u}, + {{0.5f, 0.75f, 0.125f, 0.25f}, 0x8b24u}, + }}; + for (const auto& golden : float16Cases) { + Draw(device, color, Float16Export, Float16Program(golden.rgba)); + ExpectSurface(device, block, layout, before, golden.word, std::string("an FP16_ABGR export of ") + Describe(golden.rgba) + " into a " + kind + " 4_4_4_4 unorm target"); + before = ReadBack(device, block); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Block linear(ColorTargetLayout(Width, Height, ColorTileMode::Linear, 2).Bytes()); + TargetTests(*device, linear, LinearAttrib3, ColorTileMode::Linear, "linear"); + std::puts("4_4_4_4 unorm color target 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/PackedUnorm4444MultisamplePartialWrite.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444MultisamplePartialWrite.cpp new file mode 100644 index 0000000000..894ccc375a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444MultisamplePartialWrite.cpp @@ -0,0 +1,427 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "Optimization/ResourceProgram.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#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 AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Side = 16; +constexpr std::array SampleCounts{2, 4, 8}; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Unorm4_4_4_4Info = 0x8000u | (19u << 2u) | (2u << 11u); +constexpr std::uint32_t TiledAttrib3 = 0x4dc6c000u; +constexpr std::uint32_t Float32Export = 9; +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Words = 32; +constexpr std::uint32_t LoadResult = 12; +constexpr double EdgeMargin = 1.0 / 32.0; +constexpr std::array First{0.2f, 0.4f, 0.6f, 0.8f}; +constexpr std::array Second{13.0f / 15.0f, 0.0f, 1.0f, 0.0f}; +constexpr std::array Third{1.0f / 15.0f, 1.0f, 0.0f, 4.0f / 15.0f}; +constexpr std::array Fourth{0.0f, 7.0f / 15.0f, 0.0f, 11.0f / 15.0f}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +alignas(256) constexpr std::array LoadCode{ + 0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c, + 0x80000501, 0xe0301010, 0x80000601, 0xe0301014, 0x80000701, 0xbf8c3f70, 0xf0001f30, 0x00011005, + 0xbf8c3f70, 0xe0701030, 0x80001001, 0xe0701034, 0x80001101, 0xe0701038, 0x80001201, 0xe070103c, + 0x80001301, 0xbf810000, +}; + +constexpr std::array, 3> Covering{{ + {-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f} +}}; + +constexpr std::array, 3> Cutting{{ + {-0.93f, -1.0f, 0.5f, 1.0f}, {0.37f, -1.0f, 0.5f, 1.0f}, {-0.21f, 0.94f, 0.5f, 1.0f} +}}; + +constexpr std::array, 3> Crossing{{ + {-0.6f, -0.2f, 0.5f, 1.0f}, {0.9f, -0.85f, 0.5f, 1.0f}, {0.5f, 0.9f, 0.5f, 1.0f} +}}; + +constexpr std::array, 3> Overlapping{{ + {-0.08f, -0.47f, 0.5f, 1.0f}, {1.0f, -0.13f, 0.5f, 1.0f}, {0.29f, 1.0f, 0.5f, 1.0f} +}}; + +alignas(256) std::array, 24> Programs{}; +std::size_t programCount = 0; +alignas(256) std::array Buffer{}; + +std::uint32_t MultisampledAttrib(std::uint32_t samples) { + const auto shift = static_cast(std::countr_zero(samples)); + return (shift << 12u) | (shift << 15u); +} + +std::uint32_t Unorm(float value) { + if (!(value > 0.0f)) return 0; + if (value >= 1.0f) return 15; + return static_cast(std::nearbyint(static_cast(value) * 15.0)); +} + +std::uint32_t Packed(std::array rgba) { + return Unorm(rgba[3]) | (Unorm(rgba[2]) << 4u) | (Unorm(rgba[1]) << 8u) | (Unorm(rgba[0]) << 12u); +} + +std::span Float32Program(std::array rgba) { + Require(programCount < Programs.size(), "too many test programs"); + auto& code = Programs[programCount++]; + std::size_t at = 0; + for (std::uint32_t vgpr = 0; vgpr < 4u; ++vgpr) { + code[at++] = 0x7e0002ffu | ((4u + vgpr) << 17u); + code[at++] = std::bit_cast(rgba[vgpr]); + } + code[at++] = 0xf800180fu; + code[at++] = 0x07060504u; + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes) { +#ifdef _WIN32 + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); +#endif + Require(data != nullptr, "cannot allocate the multisampled color target"); + std::memset(data, 0, bytes); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + VirtualFree(data, 0, MEM_RELEASE); +#else + std::free(data); +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + std::uint8_t* data = nullptr; +}; + +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t samples) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(block.Address() >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Unorm4_4_4_4Info; + cx[0x31d] = MultisampledAttrib(samples); + 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_R16_UINT && color.componentMapping == 0x1bu && color.packing == ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4 && color.samples == samples && color.extent.width == Side && color.extent.height == Side, "the target did not decode as a 16x16 packed R16_UINT surface of " + std::to_string(samples) + " samples"); + return color; +} + +AgcDriver::QueueState Queue(const Block& block, std::uint32_t targetMask, std::uint32_t samples) { + AgcDriver::QueueState queue; + queue.userConfig[0x242] = 4; + queue.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, targetMask}, {0x8f, 0xf}, {0x202, 0xcc0010}, + {0x1c4, 0}, {0x1c5, Float32Export}, {0x1c3, 4}, + {0x318, static_cast(block.Address() >> 8u)}, {0x390, static_cast(block.Address() >> 40u)}, + {0x31b, 0}, {0x31c, Unorm4_4_4_4Info}, {0x31d, MultisampledAttrib(samples)}, + {0x3b0, ((Side - 1u) << 14u) | (Side - 1u)}, {0x3b8, TiledAttrib3}, {0x1e0, 0}, + {0xc, 0}, {0xd, (Side << 16u) | Side}, + {0x81, 0x80000000}, {0x82, (Side << 16u) | Side}, + {0x90, 0x80000000}, {0x91, (Side << 16u) | Side}, + {0x94, 0x80000000}, {0x95, (Side << 16u) | Side}, + {0x10f, std::bit_cast(static_cast(Side) / 2.0f)}, {0x110, std::bit_cast(static_cast(Side) / 2.0f)}, + {0x111, std::bit_cast(-static_cast(Side) / 2.0f)}, {0x112, std::bit_cast(static_cast(Side) / 2.0f)}, + {0x113, std::bit_cast(1.0f)}, {0x114, 0}, + {0xb4, 0}, {0xb5, std::bit_cast(1.0f)} + }; + queue.shader[0x008] = 0x100; + queue.shader[0x009] = 0; + return queue; +} + +AgcDriver::Graphics::State TargetState(const Block& block, std::uint32_t exportedMask, std::uint32_t samples) { + const bool partial = exportedMask != 0xfu; + const auto decoded = AgcDriver::Graphics::DecodeState(Queue(block, exportedMask, samples)); + const auto& color = decoded.colors.front(); + char detail[256]; + std::snprintf(detail, sizeof(detail), "CB_TARGET_MASK 0x%x of the %u-sample 4_4_4_4 target decoded %zu targets, %u samples, word mask 0x%04x, write mask 0x%x and %s shading", exportedMask, samples, decoded.colors.size(), decoded.samples, color.partialWriteMask, decoded.blends.front().colorWriteMask, decoded.perSampleShading ? "per-sample" : "pixel-rate"); + Require(decoded.colors.size() == 1u && decoded.samples == samples && color.samples == samples && color.partialWriteMask == (partial ? AgcDriver::Graphics::PackedWriteMask(color.componentMapping, exportedMask) : 0u) && decoded.blends.size() == 1u && decoded.blends.front().colorWriteMask == (partial ? 0u : 0xfu) && decoded.blend.colorWriteMask == decoded.blends.front().colorWriteMask && decoded.perSampleShading == partial, detail); + const auto merge = AgcDriver::Graphics::ExportTargetMerge(decoded); + Require(merge.mask == color.partialWriteMask && merge.samples == (partial ? samples : 1u) && !merge.depthStencilWrites && merge.descriptor == (partial ? AgcDriver::Graphics::EncodeTextureResource(AgcDriver::Graphics::TargetMergeSurface(color)) : std::array{}), "the decoded " + std::to_string(samples) + "-sample target exported another merge than its own surface"); + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 64u, 64u, std::nullopt, std::nullopt}; + state.colors = decoded.colors; + state.color = decoded.color; + state.hasColorTarget = true; + state.renderExtent = {Side, Side}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Side), static_cast(Side), -static_cast(Side), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Side, Side}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blends = decoded.blends; + state.blend = decoded.blend; + state.blendConstants = {}; + state.samples = decoded.samples; + state.perSampleShading = decoded.perSampleShading; + return state; +} + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::State& state, const std::array, 3>& triangle, std::span pixelCode) { + const auto target = device.Target(); + constexpr std::uint32_t waveSize = 64; + std::vector vertexUserData(4, 0u); + const auto address = reinterpret_cast(triangle.data()); + const std::array vertexBuffer{static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), static_cast(triangle.size()), 0x01016facu}; + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + const auto codeAddress = reinterpret_cast(pixelCode.data()); + const AgcDriver::Registers context{{0x1b6, 0x8000}, {0x1b3, 0}, {0x1b4, 0}, {0x203, 0}, {0x1c5, Float32Export}}; + const std::array pixelMemory{{{codeAddress, std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, codeAddress, pixelCode, 0, {}}, + {waveSize, 0, {}, std::nullopt, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + const auto handle = ShaderRecompiler::PrepareShader(fragment); + AgcDriver::DriverDetail::ShaderSnapshot snapshot{codeAddress, 0, 1, {pixelCode.begin(), pixelCode.end()}, {}}; + snapshot.prepared->entries.push_back({0, handle}); + fragment.shader.code = snapshot.code; + fragment.context.pixel = AgcDriver::Graphics::DecodePixelStageInfo(context, AgcDriver::Graphics::ExportMappings(state)); + fragment.context.pixel->targetExportPacking = AgcDriver::Graphics::ExportPackings(state); + fragment.context.pixel->targetMerge = AgcDriver::Graphics::ExportTargetMerge(state); + const ShaderRecompiler::SrtRuntime runtime{}; + const auto invocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, fragment); + const auto pixelResult = invocation.Materialize(*invocation.Capture(runtime)); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, pixelResult.get(), PixelPushOffset(vertexPush, target)} + }}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); +} + +std::vector LoadSamples(AgcDriver::VulkanDevice& device, const std::array& texture, std::uint32_t samples) { + std::vector userData(16, 0u); + const auto address = reinterpret_cast(Buffer.data()); + const std::array buffer{static_cast(address), static_cast((address >> 32u) & 0xffffu), static_cast(Buffer.size() * 4u), 0x31016facu}; + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(LoadCode.data()), std::as_bytes(std::span(LoadCode))}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(LoadCode.data()), LoadCode, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto program = ShaderRecompiler::Recompile(request); + std::vector loaded(Side * Side * samples); + for (std::uint32_t first = 0; first < loaded.size(); first += Threads) { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto index = first + tid; + const auto pixel = index / samples; + const std::array words{0u, 0u, 0u, pixel % Side, pixel / Side, index % samples}; + std::copy(words.begin(), words.end(), &Buffer[tid * Words]); + } + device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast(LoadCode.data())); + device.WaitIdle(); + for (std::uint32_t tid = 0; tid < Threads; ++tid) loaded[first + tid] = Buffer[tid * Words + LoadResult]; + } + return loaded; +} + +double EdgeDistance(const std::array, 3>& triangle, double x, double y, std::size_t from) { + const auto pixels = [](const std::array& vertex) { + return std::array{(static_cast(vertex[0]) + 1.0) * 0.5 * Side, Side - (static_cast(vertex[1]) + 1.0) * 0.5 * Side}; + }; + const auto a = pixels(triangle[from]); + const auto b = pixels(triangle[(from + 1u) % triangle.size()]); + const auto c = pixels(triangle[(from + 2u) % triangle.size()]); + const auto length = std::hypot(b[0] - a[0], b[1] - a[1]); + const auto side = [&](double px, double py) { return ((b[0] - a[0]) * (py - a[1]) - (b[1] - a[1]) * (px - a[0])) / length; }; + return side(c[0], c[1]) < 0 ? -side(x, y) : side(x, y); +} + +double Inside(const std::array, 3>& triangle, double x, double y) { + return std::min({EdgeDistance(triangle, x, y, 0), EdgeDistance(triangle, x, y, 1), EdgeDistance(triangle, x, y, 2)}); +} + +void RunSamples(AgcDriver::VulkanDevice& device, const Block& block, std::uint32_t samples) { + const auto color = DecodeTarget(block, samples); + const auto full = TargetState(block, 0xfu, samples); + Draw(device, full, Covering, Float32Program(First)); + const auto merged = TargetState(block, 0x1u, samples); + Require(merged.colors.front().partialWriteMask == 0xf000u, "an X write of the 4_4_4_4 target did not merge bits 15..12"); + Draw(device, merged, Cutting, Float32Program(Second)); + Draw(device, full, Overlapping, Float32Program(Third)); + const auto texture = AgcDriver::Graphics::EncodeTextureResource(AgcDriver::Graphics::TargetMergeSurface(color)); + const auto loaded = LoadSamples(device, texture, samples); + const auto background = Packed(First); + const auto written = (background & 0x0fffu) | (Packed(Second) & 0xf000u); + const auto over = Packed(Third); + Require(written != background && over != written && over != background, "the test colors do not differ in the merged nibble"); + const auto locations = AgcDriver::Graphics::SampleLocations(merged); + std::vector pixelsByCoverage(samples + 1u); + std::uint32_t checked = 0; + std::uint32_t overdrawn = 0; + std::uint32_t mergedAfterOverdraw = 0; + for (std::uint32_t y = 0; y < Side; ++y) { + for (std::uint32_t x = 0; x < Side; ++x) { + std::uint32_t covered = 0; + for (std::uint32_t sample = 0; sample < samples; ++sample) { + const auto word = loaded[(y * Side + x) * samples + sample]; + char where[160]; + std::snprintf(where, sizeof(where), "sample %u of pixel (%u, %u) holds 0x%04x", sample, x, y, word); + Require(word == written || word == background || word == over, std::string(where) + ", neither the merged word, the first draw's word nor the last draw's word"); + covered += word == written ? 1u : 0u; + const auto px = x + static_cast(locations[sample].x); + const auto py = y + static_cast(locations[sample].y); + const auto distance = Inside(Cutting, px, py); + const auto overDistance = Inside(Overlapping, px, py); + if (std::fabs(distance) < EdgeMargin || std::fabs(overDistance) < EdgeMargin) continue; + ++checked; + const auto expected = overDistance > 0 ? over : distance > 0 ? written : background; + overdrawn += overDistance > 0 && distance > 0 ? 1u : 0u; + mergedAfterOverdraw += overDistance <= 0 && distance > 0 ? 1u : 0u; + Require(word == expected, std::string(where) + (overDistance > 0 ? ", which the last full write covers" : distance > 0 ? ", which only the merging triangle covers" : ", which no later triangle covers")); + } + ++pixelsByCoverage[covered]; + } + } + const auto partlyMerged = std::accumulate(pixelsByCoverage.begin() + 1, pixelsByCoverage.end() - 1, 0u); + Require(checked >= Side * Side * samples * 8u / 10u, "fewer than eight in ten samples are clear of the triangles' edges"); + Require(pixelsByCoverage[0] != 0 && pixelsByCoverage[samples] != 0 && partlyMerged != 0, "the merged samples do not leave pixels unmerged, merged and partly merged"); + Require(overdrawn != 0 && mergedAfterOverdraw != 0, "the last full write does not cover some merged samples and leave others"); + Draw(device, full, Covering, Float32Program(First)); + const auto lowMerged = TargetState(block, 0x8u, samples); + Require(lowMerged.colors.front().partialWriteMask == 0x000fu, "a W write of the 4_4_4_4 target did not merge bits 3..0"); + Draw(device, merged, Cutting, Float32Program(Second)); + Draw(device, lowMerged, Crossing, Float32Program(Fourth)); + const auto twice = LoadSamples(device, texture, samples); + std::uint32_t checkedTwice = 0; + std::uint32_t mergedTwice = 0; + for (std::uint32_t y = 0; y < Side; ++y) { + for (std::uint32_t x = 0; x < Side; ++x) { + for (std::uint32_t sample = 0; sample < samples; ++sample) { + const auto px = x + static_cast(locations[sample].x); + const auto py = y + static_cast(locations[sample].y); + const auto high = Inside(Cutting, px, py); + const auto low = Inside(Crossing, px, py); + if (std::fabs(high) < EdgeMargin || std::fabs(low) < EdgeMargin) continue; + ++checkedTwice; + mergedTwice += high > 0 && low > 0 ? 1u : 0u; + auto expected = background; + if (high > 0) expected = (expected & 0x0fffu) | (Packed(Second) & 0xf000u); + if (low > 0) expected = (expected & 0xfff0u) | (Packed(Fourth) & 0x000fu); + const auto word = twice[(y * Side + x) * samples + sample]; + char where[192]; + std::snprintf(where, sizeof(where), "after an X merge and a W merge drawn back to back, sample %u of pixel (%u, %u) holds 0x%04x, expected 0x%04x", sample, x, y, word, expected); + Require(word == expected, where); + } + } + } + Require(checkedTwice >= Side * Side * samples * 8u / 10u && mergedTwice != 0, "the back-to-back merges leave fewer than eight in ten samples clear of their edges, or no sample inside both"); + std::printf("4_4_4_4 unorm %u-sample partial write tests passed: %u partly merged pixels kept bits 15..12 merged into their covered samples only, a full write drawn right after covered %u merged samples, and %u samples kept both of two merges drawn back to back\n", samples, partlyMerged, overdrawn, mergedTwice); +} + +bool SupportsSamples(const AgcDriver::Graphics::Context& context, std::uint32_t samples) { + VkImageFormatProperties properties{}; + const VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT; + return (context.limits.framebufferColorSampleCounts & samples) != 0u && context.imageFormatProperties(context.physical, VK_FORMAT_R16_UINT, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage, 0, &properties) == VK_SUCCESS && (properties.sampleCounts & samples) != 0u; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device); + for (const auto& [present, feature] : {std::pair{context.storageImageMultisample, "shaderStorageImageMultisample"}, std::pair{context.sampleRateShading, "sampleRateShading"}, std::pair{TargetHasCapability(device->Target(), spv::CapabilityFragmentShaderPixelInterlockEXT), "fragmentShaderPixelInterlock"}}) { + if (present) continue; + std::printf("skipped, the device lacks %s\n", feature); + return VulkanTestSkipped; + } + std::vector> blocks; + std::uint32_t tested = 0; + for (const auto samples : SampleCounts) { + if (!SupportsSamples(context, samples)) { + std::printf("%u-sample R16_UINT storage color targets skipped, the device lacks them\n", samples); + continue; + } + blocks.push_back(std::make_unique(4u * BlockBytes)); + RunSamples(*device, *blocks.back(), samples); + ++tested; + } + if (tested == 0) return VulkanTestSkipped; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444PartialWrite.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444PartialWrite.cpp new file mode 100644 index 0000000000..1acad9581f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444PartialWrite.cpp @@ -0,0 +1,531 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "Optimization/ResourceProgram.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::ColorTargetLayout; +using AgcDriver::Graphics::ColorTileMode; +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 256; +constexpr std::uint32_t Height = 128; +constexpr std::uint32_t ScissorWidth = 128; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Unorm4_4_4_4Info = 0x8000u | (19u << 2u) | (2u << 11u); +constexpr std::uint32_t TiledAttrib3 = 0x4dc6c000u; +constexpr std::uint32_t Float32Export = 9; +constexpr std::uint32_t Float16Export = 4; +constexpr std::uint32_t PositionInputs = 0x300u; +constexpr std::uint32_t DepthInput = 0x400u; +constexpr std::uint32_t LateKill = 0x40u; +constexpr std::array CheckerboardKill{0x7e040f00u, 0x7e060f01u, 0x3a040702u, 0x36040481u, 0x7da40480u}; + +enum class Kill { None, BeforeExport, AfterExport }; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +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} +}}; + +alignas(256) std::array, 64> Programs{}; +std::size_t programCount = 0; + +std::uint32_t Unorm(float value) { + if (!(value > 0.0f)) return 0; + if (value >= 1.0f) return 15; + return static_cast(std::nearbyint(static_cast(value) * 15.0)); +} + +std::uint32_t Packed(std::array rgba) { + return Unorm(rgba[3]) | (Unorm(rgba[2]) << 4u) | (Unorm(rgba[1]) << 8u) | (Unorm(rgba[0]) << 12u); +} + +std::uint32_t Half(float value) { + Require(value == 0.0f || (value >= 0.0625f && value <= 1.0f), "the test's half values are normal and at most 1"); + if (value == 0.0f) return 0; + const auto bits = std::bit_cast(value); + Require((bits & 0x1fffu) == 0, "the test's half values are exact"); + return ((((bits >> 23u) & 0xffu) - 112u) << 10u) | ((bits >> 13u) & 0x3ffu); +} + +std::span Program(const std::array& vgprs, bool compressed, Kill kill = Kill::None) { + Require(programCount < Programs.size(), "too many test programs"); + auto& code = Programs[programCount++]; + std::size_t at = 0; + if (kill == Kill::BeforeExport) { + for (const auto word : CheckerboardKill) code[at++] = word; + } + for (std::uint32_t vgpr = 0; vgpr < (compressed ? 2u : 4u); ++vgpr) { + code[at++] = 0x7e0002ffu | ((4u + vgpr) << 17u); + code[at++] = vgprs[vgpr]; + } + if (kill == Kill::AfterExport) { + code[at++] = compressed ? 0xf800040fu : 0xf800000fu; + code[at++] = compressed ? 0x00000504u : 0x07060504u; + for (const auto word : CheckerboardKill) code[at++] = word; + code[at++] = 0xf8001890u; + code[at++] = 0u; + } else { + code[at++] = compressed ? 0xf8001c0fu : 0xf800180fu; + code[at++] = compressed ? 0x00000504u : 0x07060504u; + } + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + +std::span Float32Program(std::array rgba, Kill kill = Kill::None) { + return Program({std::bit_cast(rgba[0]), std::bit_cast(rgba[1]), std::bit_cast(rgba[2]), std::bit_cast(rgba[3])}, false, kill); +} + +std::span Float16Program(std::array rgba) { + return Program({Half(rgba[0]) | (Half(rgba[1]) << 16u), Half(rgba[2]) | (Half(rgba[3]) << 16u), 0, 0}, true); +} + +std::span DepthAsXProgram(std::array rgba) { + Require(programCount < Programs.size(), "too many test programs"); + auto& code = Programs[programCount++]; + std::size_t at = 0; + code[at++] = 0x7e080300u; + for (std::uint32_t vgpr = 1; vgpr < 4u; ++vgpr) { + code[at++] = 0x7e0002ffu | ((4u + vgpr) << 17u); + code[at++] = std::bit_cast(rgba[vgpr]); + } + code[at++] = 0xf800180fu; + code[at++] = 0x07060504u; + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + +const std::array, 3>& TriangleAt(float z) { + static std::map, 3>> triangles; + return triangles.try_emplace(z, std::array, 3>{{{-1.0f, -1.0f, z, 1.0f}, {3.0f, -1.0f, z, 1.0f}, {-1.0f, 3.0f, z, 1.0f}}}).first->second; +} + +struct DrawSetup { + std::uint32_t inputs = 0; + std::uint32_t shaderControl = 0; + std::span> vertices = Triangle; + std::uint64_t depth = 0; + bool depthWrite = false; +}; + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (watched) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (watched) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the color target"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "cannot allocate the color target"); + Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched"); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + AgcDriver::Graphics::StorageTexture::FlushPending(Address(), bytes, nullptr, "test release"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + if (watched) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (watched) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + bool watched; + std::uint8_t* data = nullptr; +}; + +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(block.Address() >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Unorm4_4_4_4Info; + cx[0x31d] = 0; + cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); + cx[0x3b8] = TiledAttrib3; + cx[0x390] = static_cast(block.Address() >> 40u); + auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + Require(color.format == VK_FORMAT_R16_UINT && color.componentMapping == 0x1bu && color.packing == ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4 && color.tileMode == ColorTileMode::RenderTarget && color.bytes == block.bytes, "the tiled 4_4_4_4 unorm target did not decode as a packed R16_UINT target"); + return color; +} + +std::uint32_t Draw(AgcDriver::VulkanDevice& device, AgcDriver::Graphics::ColorTarget color, std::uint32_t exportedMask, std::uint32_t exportFormat, std::span pixelCode, const DrawSetup& setup = {}) { + const auto target = device.Target(); + constexpr std::uint32_t waveSize = 64; + std::vector vertexUserData(4, 0u); + const auto address = reinterpret_cast(setup.vertices.data()); + const std::array vertexBuffer{static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), static_cast(setup.vertices.size()), 0x01016facu}; + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + const bool partial = exportedMask != 0xfu; + color.partialWriteMask = partial ? AgcDriver::Graphics::PackedWriteMask(color.componentMapping, exportedMask) : 0u; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.colors = {color}; + state.color = 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}, {ScissorWidth, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + VkPipelineColorBlendAttachmentState blend{}; + blend.colorWriteMask = partial ? 0u : VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + state.blends = {blend}; + state.blend = blend; + state.blendConstants = {}; + state.pixelKill = (setup.shaderControl & LateKill) != 0u; + if (setup.depth != 0) { + state.depth = AgcDriver::Graphics::DepthTarget{setup.depth, 0, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0, false}; + state.depthTest = true; + state.depthWrite = setup.depthWrite; + state.depthCompare = VK_COMPARE_OP_LESS; + } + + const auto codeAddress = reinterpret_cast(pixelCode.data()); + const AgcDriver::Registers context{{0x1b6, 0x8000}, {0x1b3, setup.inputs}, {0x1b4, setup.inputs}, {0x203, setup.shaderControl}, {0x1c5, exportFormat}}; + const std::array pixelMemory{{{codeAddress, std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, codeAddress, pixelCode, 0, {}}, + {waveSize, 0, {}, std::nullopt, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + const auto handle = ShaderRecompiler::PrepareShader(fragment); + AgcDriver::DriverDetail::ShaderSnapshot snapshot{codeAddress, 0, 1, {pixelCode.begin(), pixelCode.end()}, {}}; + snapshot.prepared->entries.push_back({0, handle}); + fragment.shader.code = snapshot.code; + fragment.context.pixel = AgcDriver::Graphics::DecodePixelStageInfo(context, AgcDriver::Graphics::ExportMappings(state)); + fragment.context.pixel->targetExportPacking = AgcDriver::Graphics::ExportPackings(state); + fragment.context.pixel->targetMerge = AgcDriver::Graphics::ExportTargetMerge(state); + Require(fragment.context.pixel->pixelKillEnable == ((setup.shaderControl & LateKill) != 0u) && !fragment.context.pixel->earlyZ && fragment.context.pixel->targetMerge.depthStencilWrites == (partial && setup.depth != 0 && setup.depthWrite), "the draw's pixel kill under late tests or its depth and stencil writes did not decode"); + Require((fragment.context.pixel->targetMerge.mask != 0u) == partial && fragment.context.pixel->targetMerge.mask == color.partialWriteMask, "the draw's target merge disagrees with its write mask"); + const ShaderRecompiler::SrtRuntime runtime{}; + const auto invocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, fragment); + const auto pixelResult = invocation.Materialize(*invocation.Capture(runtime)); + const bool registered = pixelResult->variantId == ShaderRecompiler::GetPreparedArtifact(*handle).variantId; + Require(registered != partial, partial ? "the merging draw reused the registered pixel shader" : "the full write did not use the registered pixel shader"); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, pixelResult.get(), PixelPushOffset(vertexPush, target)} + }}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(setup.vertices.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + return color.partialWriteMask; +} + +std::vector ReadBack(AgcDriver::VulkanDevice& device, const Block& block) { + AgcDriver::Graphics::StorageTexture::FlushPending(block.Address(), block.bytes, nullptr, "test read-back"); + device.WaitIdle(); + std::vector stored(block.bytes / 2u); + AgcDriver::GuestMemory::Read(block.Address(), std::as_writable_bytes(std::span(stored)), 1); + return stored; +} + +void ExpectSurface(AgcDriver::VulkanDevice& device, const Block& block, const ColorTargetLayout& layout, const std::vector& before, std::uint32_t word, std::uint32_t wordMask, const std::string& what) { + const auto stored = ReadBack(device, block); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto index = layout.Offset(x, y) / 2u; + const bool inside = x < ScissorWidth; + const std::uint32_t expected = inside ? (before[index] & ~wordMask) | (word & wordMask) : before[index]; + if (stored[index] == expected) continue; + char detail[192]; + std::snprintf(detail, sizeof(detail), ": pixel (%u, %u) %s the scissor holds 0x%04x, expected 0x%04x (it held 0x%04x)", x, y, inside ? "inside" : "outside", stored[index], expected, before[index]); + throw std::runtime_error(what + detail); + } + } +} + +std::vector Fill(Block& block, std::uint32_t seed) { + std::vector words(block.bytes / 2u); + for (std::size_t index = 0; index < words.size(); ++index) words[index] = static_cast(((index + seed) * 0x9e3779b9u) >> 11u); + std::memcpy(block.data, words.data(), block.bytes); + return words; +} + +struct Case { + std::uint32_t mask; + std::uint32_t wordMask; + std::array rgba; +}; + +std::uint32_t DrawCase(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t mask, std::uint32_t wordMask, std::uint32_t exportFormat, std::span pixelCode, const DrawSetup& setup = {}) { + const auto drawn = Draw(device, color, mask, exportFormat, pixelCode, setup); + char detail[160]; + std::snprintf(detail, sizeof(detail), "CB_TARGET_MASK 0x%x merged the word mask 0x%04x, not 0x%04x", mask, drawn, mask == 0xfu ? 0u : wordMask); + Require(drawn == (mask == 0xfu ? 0u : wordMask), detail); + return wordMask; +} + +void ExpectWords(AgcDriver::VulkanDevice& device, const Block& block, const ColorTargetLayout& layout, const std::vector& expected, const std::vector& before, const std::string& what) { + const auto stored = ReadBack(device, block); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto index = layout.Offset(x, y) / 2u; + if (stored[index] == expected[index]) continue; + char detail[192]; + std::snprintf(detail, sizeof(detail), ": pixel (%u, %u) holds 0x%04x, expected 0x%04x (it held 0x%04x)", x, y, stored[index], expected[index], before[index]); + throw std::runtime_error(what + detail); + } + } +} + +void BackToBack(AgcDriver::VulkanDevice& device, Block& block, const ColorTargetLayout& layout, const AgcDriver::Graphics::ColorTarget& color, std::span draws, std::uint32_t seed, const std::string& what) { + const auto before = Fill(block, seed); + auto expected = before; + for (const auto& [mask, wordMask, rgba] : draws) { + DrawCase(device, color, mask, wordMask, Float32Export, Float32Program(rgba)); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < ScissorWidth; ++x) { + auto& word = expected[layout.Offset(x, y) / 2u]; + word = static_cast((word & ~wordMask) | (Packed(rgba) & wordMask)); + } + } + } + ExpectWords(device, block, layout, expected, before, what); +} + +void SameDrawOverdraw(AgcDriver::VulkanDevice& device, Block& block, const ColorTargetLayout& layout, const AgcDriver::Graphics::ColorTarget& color, float first, float last, std::uint32_t seed) { + constexpr std::uint32_t Layers = 16; + static std::map>> stacks; + auto& vertices = stacks[last]; + for (std::uint32_t layer = 0; layer < Layers; ++layer) { + const auto& triangle = TriangleAt(layer % 2u == Layers % 2u ? first : last); + vertices.insert(vertices.end(), triangle.begin(), triangle.end()); + } + const auto before = Fill(block, seed); + auto expected = before; + const std::uint32_t nibble = static_cast(std::nearbyint(static_cast(last) * 15.0)); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < ScissorWidth; ++x) { + auto& word = expected[layout.Offset(x, y) / 2u]; + word = static_cast((word & 0x0fffu) | (nibble << 12u)); + } + } + DrawCase(device, color, 0x1u, 0xf000u, Float32Export, DepthAsXProgram({0.0f, 0.4f, 0.6f, 1.0f}), {DepthInput, 0u, vertices}); + char what[160]; + std::snprintf(what, sizeof(what), "one draw of %u full-screen triangles alternating z %.2f and %.2f, merging z into X", Layers, first, last); + ExpectWords(device, block, layout, expected, before, what); +} + +void DepthRejection(AgcDriver::VulkanDevice& device, Block& block, const ColorTargetLayout& layout, const AgcDriver::Graphics::ColorTarget& color) { + Block depth(static_cast(Width) * Height * 4u); + const std::vector cleared(depth.bytes / 4u, 1.0f); + std::memcpy(depth.data, cleared.data(), depth.bytes); + const std::array nearer{0.6f, 0.2f, 0.8f, 0.4f}; + const std::array behind{0.0f, 1.0f, 1.0f, 1.0f}; + const std::array front{1.0f, 0.0f, 0.0f, 1.0f / 15.0f}; + auto before = Fill(block, 301u); + auto expected = before; + const auto inside = [](std::uint32_t x) { return x < ScissorWidth; }; + const auto survivor = [&](std::uint32_t x, std::uint32_t y) { return inside(x) && ((x ^ y) & 1u) == 0u; }; + const auto expect = [&](const auto& select, std::uint32_t word, std::uint32_t wordMask) { + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + if (!select(x, y)) continue; + auto& stored = expected[layout.Offset(x, y) / 2u]; + stored = static_cast((stored & ~wordMask) | (word & wordMask)); + } + } + }; + DrawCase(device, color, 0xfu, 0xffffu, Float32Export, Float32Program(nearer, Kill::BeforeExport), {PositionInputs, LateKill, TriangleAt(0.5f), depth.Address(), true}); + expect(survivor, Packed(nearer), 0xffffu); + ExpectWords(device, block, layout, expected, before, "a full write at z 0.5 that kills a checkerboard and writes depth"); + before = expected; + DrawCase(device, color, 0x1u, 0xf000u, Float32Export, Float32Program(behind), {0u, 0u, TriangleAt(0.75f), depth.Address(), false}); + expect([&](std::uint32_t x, std::uint32_t y) { return inside(x) && !survivor(x, y); }, Packed(behind), 0xf000u); + ExpectWords(device, block, layout, expected, before, "an X merge at z 0.75 behind the depth 0.5 the surviving pixels hold"); + before = expected; + DrawCase(device, color, 0x1u, 0xf000u, Float32Export, Float32Program(front), {0u, 0u, TriangleAt(0.25f), depth.Address(), false}); + expect([&](std::uint32_t x, std::uint32_t) { return inside(x); }, Packed(front), 0xf000u); + ExpectWords(device, block, layout, expected, before, "an X merge at z 0.25 in front of every stored depth"); +} + +void CountedKill(AgcDriver::VulkanDevice& device, Block& block, const ColorTargetLayout& layout, const AgcDriver::Graphics::ColorTarget& color) { + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(recorder != nullptr, "the test device has no command recorder to count samples"); + recorder->CountSamples(); + } + const auto before = Fill(block, 501u); + const std::array rgba{0.6f, 0.2f, 0.8f, 0.4f}; + std::string refusal; + try { + DrawCase(device, color, 0x1u, 0xf000u, Float32Export, Float32Program(rgba, Kill::BeforeExport), {PositionInputs, LateKill}); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("while occlusion queries count samples") != std::string::npos, "a merge that kills under late tests drew while occlusion queries count samples" + (refusal.empty() ? std::string() : ": " + refusal)); + DrawCase(device, color, 0x1u, 0xf000u, Float32Export, Float32Program(rgba)); + ExpectSurface(device, block, layout, before, Packed(rgba), 0xf000u, "a merged X write that kills nothing while occlusion queries count samples"); +} + +void ExpectKilled(AgcDriver::VulkanDevice& device, const Block& block, const ColorTargetLayout& layout, const std::vector& before, std::uint32_t word, std::uint32_t wordMask, const std::string& what) { + const auto stored = ReadBack(device, block); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto index = layout.Offset(x, y) / 2u; + const bool kept = x < ScissorWidth && ((x ^ y) & 1u) == 0u; + const std::uint32_t expected = kept ? (before[index] & ~wordMask) | (word & wordMask) : before[index]; + if (stored[index] == expected) continue; + char detail[192]; + std::snprintf(detail, sizeof(detail), ": %s pixel (%u, %u) holds 0x%04x, expected 0x%04x (it held 0x%04x)", kept ? "a surviving" : x < ScissorWidth ? "a killed" : "an unscissored", x, y, stored[index], expected, before[index]); + throw std::runtime_error(what + detail); + } + } +} + +std::string Describe(std::uint32_t mask, std::array rgba) { + char text[160]; + std::snprintf(text, sizeof(text), "CB_TARGET_MASK 0x%x write of (%.9g, %.9g, %.9g, %.9g)", mask, rgba[0], rgba[1], rgba[2], rgba[3]); + return text; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (!TargetHasCapability(device->Target(), spv::CapabilityFragmentShaderPixelInterlockEXT)) { + std::puts("skipped, the device lacks fragmentShaderPixelInterlock"); + return VulkanTestSkipped; + } + const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 2); + Block block(layout.Bytes()); + const auto color = DecodeTarget(block); + Require(AgcDriver::Graphics::PackedWriteMask(color.componentMapping, 1u) == 0xf000u && AgcDriver::Graphics::PackedWriteMask(color.componentMapping, 8u) == 0x000fu && AgcDriver::Graphics::PackedWriteMask(color.componentMapping, 5u) == 0xf0f0u, "the export components did not map to the guest nibbles (X in 15..12, W in 3..0)"); + const std::array cases{{ + {0x1u, 0xf000u, {1.0f, 0.0f, 0.0f, 0.0f}}, + {0x1u, 0xf000u, {7.0f / 15.0f, 1.0f, 1.0f, 1.0f}}, + {0x5u, 0xf0f0u, {0.2f, 0.4f, 0.6f, 0.8f}}, + {0x2u, 0x0f00u, {0.0f, 2.0f / 15.0f, 1.0f, 1.0f}}, + {0x8u, 0x000fu, {1.0f, 1.0f, 1.0f, 11.0f / 15.0f}}, + {0x6u, 0x0ff0u, {0.0f, 0.0f, 0.0f, 0.0f}}, + {0xeu, 0x0fffu, {0.0f, 1.0f, 0.5f, 0.25f}}, + {0xfu, 0xffffu, {0.1f, 0.7f, 0.9f, 0.3f}}, + {0x1u, 0xf000u, {0.0f, 0.0f, 0.0f, 0.0f}}, + }}; + const std::array goldens{0xf000u, 0x7fffu, 0x369cu, 0x02ffu, 0xfffbu, 0x0000u, 0x0f84u, 0x2ad5u, 0x0000u}; + auto before = Fill(block, 0); + for (std::size_t index = 0; index < cases.size(); ++index) { + const auto& [mask, wordMask, rgba] = cases[index]; + DrawCase(*device, color, mask, wordMask, Float32Export, Float32Program(rgba)); + ExpectSurface(*device, block, layout, before, goldens[index], wordMask, "a 32_ABGR " + Describe(mask, rgba) + " into a tiled 4_4_4_4 unorm target"); + before = ReadBack(*device, block); + } + const std::array b{2.0f / 15.0f, 1.0f, 1.0f, 1.0f}; + const std::array c{0.6f, 0.2f, 0.8f, 0.4f}; + const std::array d{1.0f, 0.0f, 9.0f / 15.0f, 0.0f}; + const std::array mergeThenFull{{{0x1u, 0xf000u, b}, {0xfu, 0xffffu, c}}}; + const std::array fullThenMerge{{{0xfu, 0xffffu, c}, {0x1u, 0xf000u, b}}}; + const std::array mergeThenMerge{{{0x1u, 0xf000u, b}, {0x4u, 0x00f0u, d}, {0x8u, 0x000fu, c}}}; + BackToBack(*device, block, layout, color, mergeThenFull, 11, "a merged X write followed at once by a full write"); + BackToBack(*device, block, layout, color, fullThenMerge, 12, "a full write followed at once by a merged X write"); + BackToBack(*device, block, layout, color, mergeThenMerge, 13, "merged X, Z and W writes drawn back to back"); + before = Fill(block, 77); + const std::array halves{0.5f, 0.75f, 0.125f, 0.25f}; + const auto wordMask = DrawCase(*device, color, 0x9u, 0xf00fu, Float16Export, Float16Program(halves)); + ExpectSurface(*device, block, layout, before, Packed(halves), wordMask, "an FP16_ABGR " + Describe(0x9u, halves) + " into a tiled 4_4_4_4 unorm target"); + for (const auto kill : {Kill::BeforeExport, Kill::AfterExport}) { + for (const auto& [mask, wordMask, rgba] : {Case{0x1u, 0xf000u, {0.6f, 0.2f, 0.8f, 0.4f}}, Case{0x6u, 0x0ff0u, {1.0f, 2.0f / 15.0f, 13.0f / 15.0f, 0.0f}}}) { + before = Fill(block, 91u + mask); + const auto killedMask = DrawCase(*device, color, mask, wordMask, Float32Export, Float32Program(rgba, kill), {PositionInputs, LateKill}); + ExpectKilled(*device, block, layout, before, Packed(rgba), killedMask, std::string(kill == Kill::BeforeExport ? "a late-test checkerboard kill before " : "a late-test checkerboard kill after ") + Describe(mask, rgba) + " into a tiled 4_4_4_4 unorm target"); + } + } + SameDrawOverdraw(*device, block, layout, color, 0.2f, 0.8f, 401u); + SameDrawOverdraw(*device, block, layout, color, 0.8f, 0.2f, 402u); + DepthRejection(*device, block, layout, color); + CountedKill(*device, block, layout, color); + std::printf("4_4_4_4 unorm partial write tests passed: %zu masked draws, 3 back-to-back sequences, 4 checkerboard-killing draws, 2 same-draw overdraws of 16 layers, a depth-rejected merge and a refused killing merge while samples are counted, into a %ux%u tiled target, the killed, rejected and unscissored pixels kept\n", cases.size() + 1u, Width, Height); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PartiallyResidentSnapshot.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PartiallyResidentSnapshot.cpp index 0afb33266f..7d4ab406e6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PartiallyResidentSnapshot.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PartiallyResidentSnapshot.cpp @@ -2,13 +2,16 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "FixtureDeviceSelection.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" +#include "SpirvBackend/SpirvModule.hpp" #include #ifdef _WIN32 #ifndef NOMINMAX @@ -18,6 +21,7 @@ #else #include #endif +#include #include #include #include @@ -29,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +59,8 @@ public: Device() { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + library = SDL_LoadObject("libvulkan.1.dylib"); #else library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -65,6 +72,12 @@ public: application.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &application; +#ifdef __APPLE__ + const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + info.enabledExtensionCount = 1; + info.ppEnabledExtensionNames = &portability; +#endif Check(function("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); std::uint32_t count = 0; const auto enumerate = function("vkEnumeratePhysicalDevices"); @@ -72,7 +85,7 @@ public: Require(count != 0, "no Vulkan device"); std::vector devices(count); Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); - context.physical = devices.front(); + context.physical = SelectFixturePhysicalDevice(devices, function("vkGetPhysicalDeviceProperties")); const auto extensions = function("vkEnumerateDeviceExtensionProperties"); Check(extensions(context.physical, nullptr, &count, nullptr), "vkEnumerateDeviceExtensionProperties"); std::vector available(count); @@ -353,6 +366,201 @@ void UntrackedHint(const Context& context, ReservedSurface& surface) { } #endif +#ifdef _WIN32 +std::vector TexelFetchShader(std::uint32_t side) { + ShaderRecompiler::SpirvModule module; + module.EmitCapability(spv::CapabilityShader); + module.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450); + const auto u32 = module.Type(spv::OpTypeInt, 32u, 0u); + const auto i32 = module.Type(spv::OpTypeInt, 32u, 1u); + const auto v4 = module.Type(spv::OpTypeVector, u32, 4u); + const auto v3 = module.Type(spv::OpTypeVector, u32, 3u); + const auto v2 = module.Type(spv::OpTypeVector, i32, 2u); + const auto voidType = module.Type(spv::OpTypeVoid); + const auto constant = [&](std::uint32_t value) { return module.Constant(spv::OpConstant, u32, value); }; + const auto array = module.Type(spv::OpTypeRuntimeArray, v4); + module.AddAnnotation(spv::OpDecorate, array, spv::DecorationArrayStride, 16u); + const auto block = module.Type(spv::OpTypeStruct, array); + module.AddAnnotation(spv::OpDecorate, block, spv::DecorationBlock); + module.AddAnnotation(spv::OpMemberDecorate, block, 0u, spv::DecorationOffset, 0u); + const auto buffer = module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassStorageBuffer, block), spv::StorageClassStorageBuffer); + module.AddAnnotation(spv::OpDecorate, buffer, spv::DecorationDescriptorSet, 0u); + module.AddAnnotation(spv::OpDecorate, buffer, spv::DecorationBinding, 1u); + const auto imageType = module.Type(spv::OpTypeImage, u32, spv::Dim2D, 0u, 0u, 0u, 1u, spv::ImageFormatUnknown); + const auto image = module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, imageType), spv::StorageClassUniformConstant); + module.AddAnnotation(spv::OpDecorate, image, spv::DecorationDescriptorSet, 0u); + module.AddAnnotation(spv::OpDecorate, image, spv::DecorationBinding, 0u); + const auto group = module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassInput, v3), spv::StorageClassInput); + module.AddAnnotation(spv::OpDecorate, group, spv::DecorationBuiltIn, spv::BuiltInWorkgroupId); + const auto main = module.AllocateId(); + module.AddFunction(spv::OpFunction, voidType, main, spv::FunctionControlMaskNone, module.Type(spv::OpTypeFunction, voidType)); + module.AddFunction(spv::OpLabel, module.AllocateId()); + const auto loaded = module.AllocateId(); + module.AddFunction(spv::OpLoad, v3, loaded, group); + const auto index = module.AllocateId(); + module.AddFunction(spv::OpCompositeExtract, u32, index, loaded, 0u); + const auto binary = [&](spv::Op op, std::uint32_t a, std::uint32_t b) { + const auto result = module.AllocateId(); + module.AddFunction(op, u32, result, a, b); + return result; + }; + const auto signedValue = [&](std::uint32_t value) { + const auto result = module.AllocateId(); + module.AddFunction(spv::OpBitcast, i32, result, value); + return result; + }; + const auto coordinate = module.AllocateId(); + module.AddFunction(spv::OpCompositeConstruct, v2, coordinate, signedValue(binary(spv::OpUMod, index, constant(side))), signedValue(binary(spv::OpUDiv, index, constant(side)))); + const auto object = module.AllocateId(); + module.AddFunction(spv::OpLoad, imageType, object, image); + const auto value = module.AllocateId(); + module.AddFunction(spv::OpImageFetch, v4, value, object, coordinate, spv::ImageOperandsLodMask, constant(0u)); + const auto pointer = module.AllocateId(); + module.AddFunction(spv::OpAccessChain, module.Type(spv::OpTypePointer, spv::StorageClassStorageBuffer, v4), pointer, buffer, constant(0u), index); + module.AddFunction(spv::OpStore, pointer, value); + module.AddFunction(spv::OpReturn); + module.AddFunction(spv::OpFunctionEnd); + module.EmitEntryPoint(spv::ExecutionModelGLCompute, main, "main", {group}); + module.AddExecutionMode(main, spv::ExecutionModeLocalSize, 1u, 1u, 1u); + return module.Finalize(); +} + +class TexelFetch { +public: + TexelFetch(const Context& context, const Texture& texture, Buffer& output, std::span code) : context(context) { + try { + const std::array bindings{{{0u, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1u, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}, {1u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}}}; + VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + info.bindingCount = 2u; + info.pBindings = bindings.data(); + Check(context.Function("vkCreateDescriptorSetLayout")(context.device, &info, nullptr, &setLayout), "texel fetch descriptor layout"); + const std::array sizes{{{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1u}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u}}}; + VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + poolInfo.maxSets = 1u; + poolInfo.poolSizeCount = 2u; + poolInfo.pPoolSizes = sizes.data(); + Check(context.Function("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &pool), "texel fetch descriptor pool"); + const VkDescriptorSetAllocateInfo allocate{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr, pool, 1u, &setLayout}; + Check(context.Function("vkAllocateDescriptorSets")(context.device, &allocate, &set), "texel fetch descriptor set"); + const VkDescriptorImageInfo image{VK_NULL_HANDLE, texture.View(), texture.Layout()}; + const VkDescriptorBufferInfo buffer{output.Handle(), 0u, output.Bytes().size()}; + const std::array writes{{{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, 0u, 0u, 1u, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, &image, nullptr, nullptr}, {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, 1u, 0u, 1u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nullptr, &buffer, nullptr}}}; + context.Function("vkUpdateDescriptorSets")(context.device, 2u, writes.data(), 0u, nullptr); + VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + layoutInfo.setLayoutCount = 1u; + layoutInfo.pSetLayouts = &setLayout; + Check(context.Function("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &layout), "texel fetch pipeline layout"); + VkShaderModuleCreateInfo shaderInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + shaderInfo.codeSize = code.size_bytes(); + shaderInfo.pCode = code.data(); + Check(context.Function("vkCreateShaderModule")(context.device, &shaderInfo, nullptr, &module), "texel fetch shader module"); + VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineInfo.layout = layout; + pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0u, VK_SHADER_STAGE_COMPUTE_BIT, module, "main", nullptr}; + Check(context.Function("vkCreateComputePipelines")(context.device, VK_NULL_HANDLE, 1u, &pipelineInfo, nullptr, &pipeline), "texel fetch pipeline"); + } catch (...) { + release(); + throw; + } + } + ~TexelFetch() { release(); } + TexelFetch(const TexelFetch&) = delete; + TexelFetch& operator=(const TexelFetch&) = delete; + + void Run(VkCommandBuffer commands, std::uint32_t groups) const { + context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0u, 1u, &set, 0u, nullptr); + context.Function("vkCmdDispatch")(commands, groups, 1u, 1u); + } + +private: + void release() noexcept { + if (pipeline != VK_NULL_HANDLE) context.Function("vkDestroyPipeline")(context.device, pipeline, nullptr); + if (module != VK_NULL_HANDLE) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + if (layout != VK_NULL_HANDLE) context.Function("vkDestroyPipelineLayout")(context.device, layout, nullptr); + if (pool != VK_NULL_HANDLE) context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); + if (setLayout != VK_NULL_HANDLE) context.Function("vkDestroyDescriptorSetLayout")(context.device, setLayout, nullptr); + } + + Context context; + VkDescriptorSetLayout setLayout = VK_NULL_HANDLE; + VkDescriptorPool pool = VK_NULL_HANDLE; + VkDescriptorSet set = VK_NULL_HANDLE; + VkPipelineLayout layout = VK_NULL_HANDLE; + VkShaderModule module = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; +}; + +std::vector FetchTexels(const Context& context, const Texture& texture, std::uint32_t side) { + const auto count = side * side; + Buffer output(context, std::size_t{count} * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + const TexelFetch fetch(context, texture, output, TexelFetchShader(side)); + CommandBatch batch(context); + fetch.Run(batch.Handle(), count); + RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + batch.SubmitAndWait(); + output.Invalidate(); + std::vector texels(count); + for (std::uint32_t index = 0; index < count; ++index) std::memcpy(&texels[index], output.Bytes().data() + std::size_t{index} * 16u, sizeof(std::uint32_t)); + return texels; +} + +void ComparedShared(const Context& context) { + constexpr std::uint32_t side = 128; + constexpr std::size_t bytes = 2 * BlockBytes; + constexpr std::uint32_t before = 0x5a5a5a5au; + constexpr std::uint32_t stored = 0x13579bdfu; + struct Direct { + std::uint8_t* memory = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(2 * bytes, BlockBytes)); + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, 0, bytes, nullptr); + bool registered = false; + ~Direct() { + if (registered) GuestAllocations::Mutation().Remove(memory); + if (memory != nullptr) { + GuestArena::GuestArenaReset_nid_postfix(memory, 2 * bytes); + GuestArena::GuestArenaRelease_nid_postfix(memory, 2 * bytes); + } + if (section == nullptr) return; + GuestArena::GuestArenaForgetSection_nid_postfix(section); + CloseHandle(section); + } + } direct; + Require(direct.memory != nullptr && direct.section != nullptr, "compared shared snapshot: cannot create the direct memory"); + GuestArena::GuestArenaForgetSection_nid_postfix(direct.section); + GuestArena::GuestArenaMapDirect_nid_postfix(direct.memory, bytes, direct.section, 0, PAGE_READWRITE); + GuestAllocations::Mutation().Add(direct.memory, bytes, true, true, true); + direct.registered = true; + const auto address = static_cast(reinterpret_cast(direct.memory)); + std::fill_n(reinterpret_cast(direct.memory), bytes / sizeof(std::uint32_t), before); + Require(AgcDriver::GuestMemory::Watched(address, bytes), "compared shared snapshot: the direct memory is not watched"); + AgcDriver::GuestMemory::Unwatch(address, BlockBytes); + const std::array, 1> imported{{{address, address + BlockBytes}}}; + AgcDriver::GuestMemory::Rewatch(imported); + Require(AgcDriver::GuestMemory::Watched(address, BlockBytes), "compared shared snapshot: the rewatched block is not watched"); + const auto words = Descriptor(address, side); + Require(DescribeSurface(DecodeTextureResource(words)).guestBytes == BlockBytes, "compared shared snapshot: the descriptor does not describe the rewatched block"); + const auto lookup = [&] { + AgcDriver::GuestMemory::BumpCollectEpoch(); + return CachedSampledTexture(context, words); + }; + const auto cached = lookup(); + Require(cached != nullptr && !cached->ViewsStorageImage(), "compared shared snapshot: the rewatched block was not sampled through a snapshot"); + Require(lookup() == cached, "compared shared snapshot: the unchanged rewatched block did not reuse its snapshot"); + const auto initial = FetchTexels(context, *cached, side); + Require(std::all_of(initial.begin(), initial.end(), [](std::uint32_t texel) { return texel == before; }), "compared shared snapshot: the GPU did not read the cached texels"); + GuestArena::GuestArenaMapDirect_nid_postfix(direct.memory + bytes, bytes, direct.section, 0, PAGE_READWRITE); + *reinterpret_cast(direct.memory) = stored; + const auto fresh = lookup(); + Require(fresh != nullptr && fresh != cached, "the first lookup after a CPU store into shared compared memory returned the snapshot cached before the sharing"); + const auto texels = FetchTexels(context, *fresh, side); + Require(texels.front() == stored, "the GPU read the old texel on the first lookup after a CPU store into shared compared memory"); + Require(std::all_of(texels.begin() + 1, texels.end(), [](std::uint32_t texel) { return texel == before; }), "the GPU read texels the CPU store into shared compared memory did not write"); + Require(lookup() == fresh, "the snapshot of shared compared memory was not reused after its compare"); + ClearCachedTextures(context.device); +} +#endif + void PendingStores(const Context& context, ReservedSurface& surface) { const auto address = surface.Address(); const auto words = Descriptor(address); @@ -421,6 +629,14 @@ int main(int argc, char** argv) { LargeTracked(context, surface, LargeSurfaceBytes); LargeTracked(context, surface, BlockBytes); } else if (argc == 2 && std::string_view(argv[1]) == "--pending-stores") PendingStores(context, surface); + else if (argc == 2 && std::string_view(argv[1]) == "--compared-shared") { +#ifdef _WIN32 + ComparedShared(context); +#else + std::puts("skipped, only Windows direct memory is compared once shared"); + return Skipped; +#endif + } else if (argc == 2 && std::string_view(argv[1]) == "--untracked-hint") { #ifdef _WIN32 UntrackedHint(context, surface); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PeriodCopy.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PeriodCopy.cpp new file mode 100644 index 0000000000..0c4fecdb35 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PeriodCopy.cpp @@ -0,0 +1,116 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +alignas(256) constexpr std::array Code{ + 0xbfa00002u, 0xd7460000u, 0x04010c0cu, 0xf4201a84u, 0xfa000000u, 0xbf8cc07fu, 0x7da8006au, 0xbf88001du, + 0xf4200204u, 0xfa000004u, 0xbf8cc07fu, 0x7e020c08u, 0x81ea0880u, 0x7e025701u, 0x100202ffu, 0x4f7ffffeu, + 0x7e020f01u, 0x7ed60501u, 0x936a6b6au, 0x9aea6b6au, 0x4a02026au, 0xd56a0001u, 0x00020101u, 0xd5690001u, + 0x00020208u, 0x4c020300u, 0x7d860208u, 0x4e040208u, 0x02020501u, 0x7d860208u, 0x4e040208u, 0x02020501u, + 0xe0002000u, 0x80000101u, 0xbf8c3f70u, 0xe0102000u, 0x80010100u, 0xbf810000u}; +constexpr std::uint32_t Untouched = 0xcafebabeu; +constexpr std::uint32_t ElementFormat = 0x00014004u; +constexpr std::uint32_t RecordFormat = 0x0004dfacu; +constexpr std::uint32_t Elements = 8192; +alignas(256) std::array Source{}; +alignas(256) std::array Destination{}; +alignas(256) std::array Record{}; + +void AppendDescriptor(std::vector& data, const void* base, std::uint32_t stride, std::uint32_t records, std::uint32_t format) { + const auto address = reinterpret_cast(base); + data.insert(data.end(), {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), records, format}); +} + +std::vector Commands(std::uint32_t groups) { + std::vector data; + AppendDescriptor(data, Source.data(), 4, Elements, ElementFormat); + AppendDescriptor(data, Destination.data(), 4, Elements, ElementFormat); + AppendDescriptor(data, Record.data(), 16, 1, RecordFormat); + std::vector words; + const auto registers = [&](std::uint32_t first, std::span values) { + words.push_back(0xc0007600u | (static_cast(values.size()) << 16u)); + words.push_back(first); + words.insert(words.end(), values.begin(), values.end()); + }; + const std::array shape{64, 1, 1}; + registers(0x207, shape); + const auto address = reinterpret_cast(Code.data()); + const std::array program{static_cast(address >> 8u), static_cast(address >> 40u)}; + registers(0x20c, program); + const std::array resource{(static_cast(data.size()) << 1u) | (1u << 7u)}; + registers(0x213, resource); + registers(0x240, data); + words.insert(words.end(), {0xc0031500u, groups, 1, 1, 0x41u}); + return words; +} + +std::uint64_t Taken() { + using namespace AgcDriver::DriverDetail; + return copyPathCounts[CopyCpu].load() + copyPathCounts[CopyGpu].load() + copyPathCounts[CopyAliased].load(); +} + +void ExecuteTests() { + struct Case { + const char* name; + std::uint32_t count, period, groups; + bool hle; + }; + constexpr std::array cases{{ + {"one full group", 64, 64, 1, true}, + {"a period above the count", 1000, 4096, 16, true}, + {"a partial last group", 100, 100, 2, true}, + {"a period below the count", 300, 7, 5, false}, + {"more groups than the count needs", 64, 64, 2, false}, + }}; + const bool hleEnabled = std::getenv("APS5_NO_COPY_HLE") == nullptr; + for (const auto& item : cases) { + const bool hle = item.hle && hleEnabled; + for (std::size_t i = 0; i < Source.size(); ++i) Source[i] = static_cast(i) * 2654435761u + 1u; + Destination.fill(Untouched); + Record = {item.count, item.period, 0, 0}; + const auto taken = Taken(); + const auto words = Commands(item.groups); + Packet packet{const_cast(words.data()), static_cast(words.size()), 0, {}}; + AgcDriver::Submit(&packet, 0); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(Destination.data()), sizeof(Destination)); + const std::string where = std::string(" (") + item.name + ")"; + Require(Taken() - taken == (hle ? 1u : 0u), (hle ? "the copy did not take the HLE path" : "the copy took the HLE path") + where); + for (std::size_t i = 0; i < Destination.size(); ++i) { + const auto expected = i < item.count ? Source[i % item.period] : Untouched; + Require(Destination[i] == expected, "copy mismatch at element " + std::to_string(i) + where + ": got " + std::to_string(Destination[i]) + ", expected " + std::to_string(expected)); + } + } +} + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + ExecuteTests(); + AgcDriverShutdown_nid_postfix(); + std::puts("period copy tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + AgcDriverShutdown_nid_postfix(); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PipelineJournal.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelineJournal.cpp index 16d3742761..403380ca02 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PipelineJournal.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelineJournal.cpp @@ -11,6 +11,7 @@ #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" +#include "ShaderCacheDirectory.hpp" #include "../NarrowConstantStoreFixture.hpp" #include "Recompiler.hpp" #include "Optimization/ResourceProgram.hpp" @@ -31,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -149,7 +151,7 @@ AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block) { return color; } -void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::span pixelCode) { +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::span pixelCode, std::uint32_t colorWriteMask = 0xf) { const auto target = device.Target(); constexpr std::uint32_t waveSize = 64; std::vector vertexUserData(4, 0u); @@ -180,7 +182,7 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge state.cullMode = VK_CULL_MODE_NONE; state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; VkPipelineColorBlendAttachmentState blend{}; - blend.colorWriteMask = 0xf; + blend.colorWriteMask = colorWriteMask; state.blends = {blend}; state.blend = blend; state.blendConstants = {}; @@ -344,6 +346,36 @@ void JournalTests() { } } +void HeldModuleTests() { + using namespace NarrowConstantStoreFixture; + constexpr std::uint32_t programs = 16; + Block compute(BlockBytes); + const auto words = AddressWords(compute); + std::vector> codes; + for (std::uint32_t salt = 1; salt <= programs; ++salt) codes.push_back(ComputeCode(salt)); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "no device to record the compute programs"); + device->WaitForPipelineJournalReplay(); + std::vector results; + for (const auto& code : codes) results.push_back(CompileCompute(*device, code, words, 32)); + for (std::size_t index = 0; index < results.size(); ++index) device->Dispatch(results[index], 1, 1, 1, {}, reinterpret_cast(codes[index].data())); + device->WaitIdle(); + AgcDriver::Graphics::FlushPipelineJournal(); + } + SetEnvironment("APS5_PIPELINE_JOURNAL_THREADS", "1"); + const auto before = Snapshot(); + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "no device to replay the compute programs"); + device->WaitForPipelineJournalReplay(); + const auto replayed = Snapshot(); + SetEnvironment("APS5_PIPELINE_JOURNAL_THREADS", "2"); + Require(replayed.journal.loadedRecipes == programs + 2 && replayed.journal.loadedModules == programs + 3 && replayed.journal.droppedRecords == 0, "the journal does not hold the 16 compute programs beside the first two pipelines"); + Require(replayed.journal.replayedCompute - before.journal.replayedCompute == programs + 1 && replayed.journal.replayedGraphics - before.journal.replayedGraphics == 1 && replayed.journal.replayRejected == before.journal.replayRejected && replayed.journal.replayFailed == before.journal.replayFailed, "the one-thread replay did not build every recorded pipeline"); + const auto held = replayed.journal.replayHeldPeakBytes; + Require(held > 0 && held * 4 < replayed.journal.loadedBytes, "the one-thread replay held " + std::to_string(held) + " bytes of modules at once from a " + std::to_string(replayed.journal.loadedBytes) + "-byte journal"); +} + // --benchmark [count]: `count` distinct compute programs, dispatched for the first time on three // devices in turn. The first device starts without the driver's pipeline cache file (a driver with // an on-disk shader cache of its own, lavapipe's Mesa cache here, still serves from it unless that @@ -395,6 +427,272 @@ void Benchmark(std::uint32_t count) { std::printf(" warm driver cache, journal: %llu pipelines built by the replay in %.1f ms of replay work (%.1f ms from the device opening), then %llu built at their dispatches, %u dispatches in %.1f ms (%.2f ms each)\n", static_cast(replayed.journal.replayedCompute), replayed.journal.replayMs, replayed.openMs, static_cast(replayed.created), count, replayed.dispatchMs, replayed.dispatchMs / count); } +struct JournalRecordHeader { + std::uint32_t kind; + std::uint32_t reserved; + std::uint64_t payloadBytes; + std::uint64_t payloadHash; +}; +static_assert(sizeof(JournalRecordHeader) == 24); + +constexpr std::uint32_t JournalModuleRecord = 1u; +constexpr std::uint32_t JournalComputeRecord = 3u; +constexpr std::uint32_t JournalHeaderBytes = 32u; + +void AppendJournalRecord(std::vector& file, std::uint32_t kind, const std::vector& payload) { + const JournalRecordHeader header{kind, 0u, payload.size(), ShaderRecompiler::HashBytes(payload)}; + const auto offset = file.size(); + file.resize(offset + sizeof(header) + payload.size()); + std::memcpy(file.data() + offset, &header, sizeof(header)); + std::memcpy(file.data() + offset + sizeof(header), payload.data(), payload.size()); + while ((file.size() - offset - sizeof(header)) % 8u != 0) file.push_back(std::byte{0}); +} + +std::vector ReadJournalComputeIdentities(const std::filesystem::path& path) { + std::vector file; + std::ifstream input(path, std::ios::binary); + const std::vector bytes((std::istreambuf_iterator(input)), std::istreambuf_iterator()); + file.reserve(bytes.size()); + for (const auto byte : bytes) file.push_back(static_cast(byte)); + std::vector identities; + std::size_t cursor = JournalHeaderBytes; + while (cursor + sizeof(JournalRecordHeader) <= file.size()) { + JournalRecordHeader header{}; + std::memcpy(&header, file.data() + cursor, sizeof(header)); + const auto payload = cursor + sizeof(header); + if (header.payloadBytes > file.size() - payload) break; + if (header.kind == JournalComputeRecord && header.payloadBytes >= 17u) { + ShaderRecompiler::PipelineIdentity identity; + std::memcpy(&identity.low, file.data() + payload, sizeof(identity.low)); + std::memcpy(&identity.high, file.data() + payload + 8, sizeof(identity.high)); + identities.push_back(identity); + } + cursor = payload + header.payloadBytes; + while (cursor % 8u != 0) ++cursor; + } + return identities; +} + +void NewestEntriesTests() { + using namespace NarrowConstantStoreFixture; + Block compute(BlockBytes); + const auto words = AddressWords(compute); + const auto recorded = Snapshot(); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the newest entries test could not be opened"); + device->WaitForPipelineJournalReplay(); + for (std::uint32_t salt = 1; salt <= 3; ++salt) { + const auto code = ComputeCode(salt); + const auto result = CompileCompute(*device, code, words, 32); + device->Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + } + device->WaitIdle(); + AgcDriver::Graphics::FlushPipelineJournal(); + const auto after = Snapshot(); + Require(after.journal.recordedCompute == recorded.journal.recordedCompute + 3, "the three compute pipelines were not recorded"); + } + SetEnvironment("APS5_PIPELINE_JOURNAL_ENTRIES", "2"); + const auto before = Snapshot(); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the newest entries replay could not be opened"); + device->WaitForPipelineJournalReplay(); + const auto replayed = Snapshot(); + Require(replayed.journal.replayedCompute == before.journal.replayedCompute + 2, "the replay did not build the two newest entries: built " + std::to_string(replayed.journal.replayedCompute - before.journal.replayedCompute) + " compute and " + std::to_string(replayed.journal.replayedGraphics - before.journal.replayedGraphics) + " graphics, " + std::to_string(replayed.journal.replayRejected - before.journal.replayRejected) + " rejected, " + std::to_string(replayed.journal.replayPresent - before.journal.replayPresent) + " present, " + std::to_string(replayed.journal.replaySkipped - before.journal.replaySkipped) + " skipped"); + Require(replayed.journal.replayedGraphics == before.journal.replayedGraphics, "the replay built an entry that is not among the newest"); + const auto created = device->ComputePipelinesCreated(); + const auto newest = ComputeCode(3); + const auto result = CompileCompute(*device, newest, words, 32); + device->Dispatch(result, 1, 1, 1, {}, reinterpret_cast(newest.data())); + device->WaitIdle(); + Require(device->ComputePipelinesCreated() == created, "the newest pipeline was not built by the replay"); + const auto oldest = ComputeCode(0); + const auto old = CompileCompute(*device, oldest, words, 32); + device->Dispatch(old, 1, 1, 1, {}, reinterpret_cast(oldest.data())); + device->WaitIdle(); + Require(device->ComputePipelinesCreated() == created + 1, "the oldest pipeline was built by the replay"); + } + SetEnvironment("APS5_PIPELINE_JOURNAL_ENTRIES", "1024"); +} + +void EvictionTests() { + using namespace NarrowConstantStoreFixture; + const auto path = AgcDriver::Graphics::PipelineJournalPath(); + AgcDriver::Graphics::FlushPipelineJournal(); + constexpr std::size_t Modules = 24; + constexpr std::size_t ModuleBytes = 64u * 1024u; + std::vector file; + for (std::size_t index = 0; index < Modules; ++index) { + std::vector module(20u + ModuleBytes, std::byte{0x5a}); + const std::uint64_t low = index + 1u; + const std::uint64_t high = 0u; + std::memcpy(module.data(), &low, sizeof(low)); + std::memcpy(module.data() + 8, &high, sizeof(high)); + AppendJournalRecord(file, JournalModuleRecord, module); + std::vector compute(17u, std::byte{0}); + std::memcpy(compute.data(), &low, sizeof(low)); + std::memcpy(compute.data() + 8, &high, sizeof(high)); + AppendJournalRecord(file, JournalComputeRecord, compute); + } + { + std::ofstream out(path, std::ios::binary | std::ios::app); + Require(static_cast(out), "the synthetic journal records could not be written"); + out.write(reinterpret_cast(file.data()), static_cast(file.size())); + out.flush(); + Require(!out.fail(), "the synthetic journal records could not be written"); + } + Require(std::filesystem::file_size(path) > 1024u * 1024u, "the journal is smaller than the cap"); + const auto before = Snapshot(); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the eviction test could not be opened"); + device->WaitForPipelineJournalReplay(); + Block compute(BlockBytes); + Block target(ColorTargetLayout(Width, Height, ColorTileMode::Linear, 2).Bytes()); + const auto words = AddressWords(compute); + const auto code = ComputeCode(7); + const auto result = CompileCompute(*device, code, words, 32); + device->Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device->WaitIdle(); + std::memset(target.data, 0, target.bytes); + Draw(*device, DecodeTarget(target), Program(std::array{1.0f, 0.0f, 0.0f, 1.0f})); + device->WaitIdle(); + AgcDriver::Graphics::FlushPipelineJournal(); + } + const auto after = Snapshot(); + Require(after.journal.evictedRecords > before.journal.evictedRecords, "the journal was not rewritten when it reached its cap: " + std::to_string(after.journal.evictedRecords) + " records evicted, " + std::to_string(after.journal.writeFailures - before.journal.writeFailures) + " write failures"); + const auto size = std::filesystem::file_size(path); + Require(size <= 1024u * 1024u + 64u * 1024u, "the rewritten journal is larger than the cap"); + const auto identities = ReadJournalComputeIdentities(path); + Require(!identities.empty(), "the rewritten journal holds no compute pipeline"); + bool newest = false; + for (const auto& identity : identities) { + if (identity.low == Modules) newest = true; + Require(identity.low != 1u, "the oldest pipeline survived the rewrite"); + } + Require(newest, "the newest pipeline did not survive the rewrite"); +} + +void EvictionWatermarkTests() { + using namespace NarrowConstantStoreFixture; + const auto path = AgcDriver::Graphics::PipelineJournalPath(); + const auto before = Snapshot(); + Block compute(BlockBytes); + const auto words = AddressWords(compute); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the eviction watermark test could not be opened"); + device->WaitForPipelineJournalReplay(); + for (std::uint32_t salt = 8; salt <= 10; ++salt) { + auto result = CompileCompute(*device, ComputeCode(salt), words, 32); + result.shaderDataUserWords.assign(16u * 1024u, salt); + AgcDriver::Graphics::RecordComputePipeline(result, true); + } + AgcDriver::Graphics::FlushPipelineJournal(); + } + const auto after = Snapshot(); + Require(after.journal.recordedCompute == before.journal.recordedCompute + 3, "the records after the rewrite were not recorded"); + Require(after.journal.writeFailures == before.journal.writeFailures, "the records after the rewrite failed to write"); + Require(after.journal.evictedRecords == before.journal.evictedRecords, "three consecutive records after a rewrite triggered another rewrite"); + Require(std::filesystem::file_size(path) <= 1024u * 1024u, "the journal exceeded its cap after the consecutive records"); +} + +void EvictionKeepsPendingModules() { + using namespace NarrowConstantStoreFixture; + const auto path = AgcDriver::Graphics::PipelineJournalPath(); + Block target(ColorTargetLayout(Width, Height, ColorTileMode::Linear, 2).Bytes()); + const auto color = DecodeTarget(target); + const std::array red{1.0f, 0.0f, 0.0f, 1.0f}; + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the pending module test could not be opened"); + device->WaitForPipelineJournalReplay(); + std::memset(target.data, 0, target.bytes); + Draw(*device, color, Program(red)); + device->WaitIdle(); + AgcDriver::Graphics::FlushPipelineJournal(); + } + constexpr std::size_t FillerBytes = 600u * 1024u; + { + std::vector file; + std::vector module(20u + FillerBytes, std::byte{0x5a}); + const std::uint64_t low = 0x5a5au; + const std::uint64_t high = 0u; + std::memcpy(module.data(), &low, sizeof(low)); + std::memcpy(module.data() + 8, &high, sizeof(high)); + AppendJournalRecord(file, JournalModuleRecord, module); + std::vector compute(17u + FillerBytes, std::byte{0}); + std::memcpy(compute.data(), &low, sizeof(low)); + std::memcpy(compute.data() + 8, &high, sizeof(high)); + AppendJournalRecord(file, JournalComputeRecord, compute); + std::ofstream out(path, std::ios::binary | std::ios::app); + Require(static_cast(out), "the filler records could not be written"); + out.write(reinterpret_cast(file.data()), static_cast(file.size())); + out.flush(); + Require(!out.fail(), "the filler records could not be written"); + } + Require(std::filesystem::file_size(path) > 1024u * 1024u, "the journal is smaller than the cap"); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the pending module test could not be opened"); + device->WaitForPipelineJournalReplay(); + std::memset(target.data, 0, target.bytes); + Draw(*device, color, Program(red), 0x7); + device->WaitIdle(); + AgcDriver::Graphics::FlushPipelineJournal(); + } + const auto before = Snapshot(); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the pending module replay could not be opened"); + device->WaitForPipelineJournalReplay(); + const auto replayed = Snapshot(); + Require(replayed.journal.replayedGraphics == before.journal.replayedGraphics + 1, "the record written after the eviction was not replayed: " + std::to_string(replayed.journal.replayedGraphics - before.journal.replayedGraphics) + " graphics built, " + std::to_string(replayed.journal.replayRejected - before.journal.replayRejected) + " rejected"); + Require(replayed.journal.replayRejected == before.journal.replayRejected, "the record written after the eviction was rejected: the eviction dropped a module it needed"); + } +} + +void OversizedRecordKeepsTheJournal() { + using namespace NarrowConstantStoreFixture; + const auto path = AgcDriver::Graphics::PipelineJournalPath(); + Block compute(BlockBytes); + const auto words = AddressWords(compute); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the oversized record test could not be opened"); + device->WaitForPipelineJournalReplay(); + const auto code = ComputeCode(0); + const auto result = CompileCompute(*device, code, words, 32); + device->Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device->WaitIdle(); + AgcDriver::Graphics::FlushPipelineJournal(); + } + const auto before = Snapshot(); + const auto sizeBefore = std::filesystem::file_size(path); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the oversized record test could not be opened"); + device->WaitForPipelineJournalReplay(); + const auto code = ComputeCode(2); + auto result = CompileCompute(*device, code, words, 32); + result.shaderDataUserWords.assign(400000u, 0u); + AgcDriver::Graphics::RecordComputePipeline(result, false); + AgcDriver::Graphics::FlushPipelineJournal(); + } + const auto after = Snapshot(); + Require(after.journal.writeFailures == before.journal.writeFailures + 1, "the oversized record was not counted as a write failure"); + Require(std::filesystem::file_size(path) == sizeBefore, "the oversized record emptied the journal: " + std::to_string(sizeBefore) + " bytes became " + std::to_string(std::filesystem::file_size(path))); + const auto before2 = Snapshot(); + { + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "the device for the oversized record replay could not be opened"); + device->WaitForPipelineJournalReplay(); + const auto replayed = Snapshot(); + Require(replayed.journal.replayedCompute == before2.journal.replayedCompute + 1, "the record that was in the journal did not survive the oversized one"); + } +} + } int main(int argc, char** argv) { @@ -406,6 +704,7 @@ int main(int argc, char** argv) { SetEnvironment("APS5_NO_PIPELINE_JOURNAL", "0"); SetEnvironment("APS5_VERIFY_SET_LAYOUTS", "1"); SetEnvironment("APS5_PIPELINE_JOURNAL_THREADS", "2"); + SetEnvironment("APS5_PIPELINE_JOURNAL_MAX_MIB", "1"); struct Cleanup { std::filesystem::path directory; ~Cleanup() { @@ -422,6 +721,12 @@ int main(int argc, char** argv) { return 0; } JournalTests(); + NewestEntriesTests(); + EvictionTests(); + EvictionWatermarkTests(); + EvictionKeepsPendingModules(); + OversizedRecordKeepsTheJournal(); + HeldModuleTests(); std::puts("pipeline journal record and replay passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PipelineOptimizedReadback.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelineOptimizedReadback.cpp index f2b56411c0..2ff511dcd1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PipelineOptimizedReadback.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelineOptimizedReadback.cpp @@ -64,6 +64,8 @@ struct PipelineTrace { std::mutex mutex; std::condition_variable released; bool allowOptimization = false; + std::uint32_t libraries = 0; + std::uint32_t retaining = 0; std::vector fast; std::vector optimized; VkCommandBuffer boundCommands = VK_NULL_HANDLE; @@ -90,7 +92,12 @@ VKAPI_ATTR VkResult VKAPI_CALL CreatePipelines(VkDevice device, VkPipelineCache const auto result = Trace->create(device, cache, count, infos, allocator, pipelines); std::lock_guard lock(Trace->mutex); for (std::uint32_t index = 0; index < count; ++index) { - if (pipelines[index] == VK_NULL_HANDLE || (infos[index].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) != 0) continue; + if (pipelines[index] == VK_NULL_HANDLE) continue; + if ((infos[index].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) != 0) { + ++Trace->libraries; + if ((infos[index].flags & VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT) != 0) ++Trace->retaining; + continue; + } auto& handles = (infos[index].flags & VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT) != 0 ? Trace->optimized : Trace->fast; handles.push_back(pipelines[index]); } @@ -217,12 +224,17 @@ void Run(AgcDriver::VulkanDevice& device, Context context, PipelineTrace& trace, CheckPixels(masked, phase); }; render(false, "fast link"); + { + std::lock_guard lock(trace.mutex); + Require(trace.libraries != 0, "the readback pipeline's libraries were not created through the traced device"); + Require(trace.retaining == (optimize ? trace.libraries : 0u), std::to_string(trace.retaining) + " of the " + std::to_string(trace.libraries) + " libraries were created to retain link-time optimization info, expected " + (optimize ? "all of them" : "none, since no optimized link will be made")); + } trace.ReleaseOptimization(); WaitForOptimizedPipelines(context.device); Require(PipelineLibraryCountersOf(context.device).linked == 1, "the readback pipeline was not linked from graphics libraries"); Require(trace.optimized.size() == (optimize ? 1u : 0u), "the optimized pipeline creation count differs from the requested mode"); render(optimize, "completed link"); - std::printf("pipeline readback passed: fast draws %u, optimized draws %u, optimized creations %zu\n", optimize ? 1u : 2u, optimize ? 1u : 0u, trace.optimized.size()); + std::printf("pipeline readback passed: fast draws %u, optimized draws %u, optimized creations %zu, libraries %u (%u retaining link-time optimization info)\n", optimize ? 1u : 2u, optimize ? 1u : 0u, trace.optimized.size(), trace.libraries, trace.retaining); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedDispatch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedDispatch.cpp index 8cc143e3c5..4996854346 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedDispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedDispatch.cpp @@ -132,10 +132,8 @@ void Submit(std::vector& words) { int main() { #ifdef _WIN32 _putenv_s("APS5_PIPELINED_DRAWS", "1"); - _putenv_s("APS5_PIPELINE_DMA", "1"); #else setenv("APS5_PIPELINED_DRAWS", "1", 1); - setenv("APS5_PIPELINE_DMA", "1", 1); #endif try { { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedDma.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedDma.cpp index d43e135c35..7a62e1e7cf 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedDma.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedDma.cpp @@ -74,10 +74,8 @@ void ExpectNoDrain(std::uint32_t opcode, std::uint64_t before, const char* what) int main() { #ifdef _WIN32 _putenv_s("APS5_PIPELINED_DRAWS", "1"); - _putenv_s("APS5_PIPELINE_DMA", "1"); #else setenv("APS5_PIPELINED_DRAWS", "1", 1); - setenv("APS5_PIPELINE_DMA", "1", 1); #endif try { alignas(256) static std::array first{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedFill.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedFill.cpp index 1c13bc169d..c501698a57 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedFill.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedFill.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -75,16 +76,29 @@ void AppendLabel(std::vector& words, std::uint32_t* target, std:: } struct Blocker { - std::atomic release{false}; + struct State { + std::atomic release{false}; + std::atomic done{false}; + }; + std::shared_ptr state = std::make_shared(); std::uint32_t* target = nullptr; - ~Blocker() { release = true; } + ~Blocker() { Release(); } + void Release() { state->release = true; } void Enqueue(std::vector writes = {}) { - DrawPipeline::Queue0().Enqueue([this] { - while (!release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + DrawPipeline::Queue0().Enqueue([state = state, target = target] { + while (!state->release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (target != nullptr) std::atomic_ref(*target).store(1); - std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); + { std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); } + state->done = true; }, std::move(writes)); } + void Join() const { + const auto start = std::chrono::steady_clock::now(); + while (!state->done.load()) { + Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the blocker never ran"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } }; void SubmitBehind(Blocker& blocker, std::vector& words) { @@ -107,7 +121,7 @@ void ReleaseWhen(Blocker& blocker, std::size_t reached, std::size_t passed) { Require(now - start < std::chrono::seconds(20), "the queue worker never reached the fill"); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - blocker.release = true; + blocker.Release(); } void Finish() { @@ -118,6 +132,11 @@ void Finish() { std::_Exit(1); } idle.get(); + const auto start = std::chrono::steady_clock::now(); + while (DrawPipeline::Queue0().Outstanding() != 0) { + Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the draw pipeline kept an item after the queue went idle"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(Output.data()), sizeof(Output)); AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(Scratch.data()), sizeof(Scratch)); } @@ -135,6 +154,7 @@ void InputsAfterQueuedLabels() { SubmitBehind(blocker, words); ReleaseWhen(blocker, 4, 5); Finish(); + blocker.Join(); for (std::size_t i = 0; i < Output.size(); ++i) { const auto expected = i < 128 ? 0xababababu : 0xcafebabeu; Require(Output[i] == expected, "a fill read its control or pattern before the queued labels that write them, at " + std::to_string(i)); @@ -156,6 +176,7 @@ void FillIntoFullPipeline() { SubmitBehind(blocker, words); ReleaseWhen(blocker, depth, depth + 1); Finish(); + blocker.Join(); for (std::size_t i = 0; i + 2 < depth; ++i) Require(Labels[i] == i + 1, "a queued label was not stored"); for (std::size_t i = 0; i < Output.size(); ++i) Require(Output[i] == (i < 256 ? 0x5a5a5a5au : 0xcafebabeu), "a fill behind a full draw pipeline did not land at " + std::to_string(i)); } @@ -180,8 +201,9 @@ void SatisfiedWaitKeepsUnrelatedQueuedWork() { while (DrawPipeline::Queue0().Outstanding() < 2 && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); const bool keptQueued = DrawPipeline::Queue0().Outstanding() >= 2; - blocker.release = true; + blocker.Release(); Finish(); + blocker.Join(); Require(keptQueued, "a satisfied WAIT_REG_MEM drained unrelated queued work"); Require(marker == 1, "a label after a satisfied WAIT_REG_MEM did not execute"); } @@ -211,11 +233,12 @@ void SatisfiedWaitDrainsOverlappingQueuedWork() { std::this_thread::sleep_for(std::chrono::milliseconds(1)); const bool drained = DrawPipeline::DrainRequestsByOpcode(0x3c) > drainsBefore; const bool labelQueuedEarly = DrawPipeline::Queue0().Outstanding() >= 2; - blocker.release = true; + blocker.Release(); Require(submitted.wait_for(std::chrono::seconds(20)) == std::future_status::ready, "WAIT_REG_MEM submission did not finish after the overlapping write"); submitted.get(); Finish(); + blocker.Join(); Require(drained && !labelQueuedEarly, "a satisfied WAIT_REG_MEM skipped an overlapping queued write"); Require(marker == 1, "a label after an overlapping WAIT_REG_MEM did not execute"); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedIndirectDraw.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedIndirectDraw.cpp index 437a8ecaa2..93abe602a5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedIndirectDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedIndirectDraw.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -138,20 +139,34 @@ void Flush(const std::array& memory) { } struct Blocker { - std::atomic release{false}; + struct State { + std::atomic release{false}; + std::atomic done{false}; + }; + std::shared_ptr state = std::make_shared(); + void Release() { state->release = true; } void Enqueue() { - DrawPipeline::Queue0().Enqueue([this] { - while (!release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); - std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); + DrawPipeline::Queue0().Enqueue([state = state] { + while (!state->release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + { std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); } + state->done = true; }, {}); } + void Join() const { + const auto start = std::chrono::steady_clock::now(); + while (!state->done.load()) { + Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the blocker never ran"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } }; -void SubmitBehind(Blocker& blocker, std::vector& commands) { +void SubmitBehind(Blocker& blocker, std::vector& commands, std::size_t queuedBefore) { std::unique_lock gpuLock(AgcDriver::GuestMemory::GpuMutex()); Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; AgcDriver::Submit(&packet, 0); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); + const auto start = std::chrono::steady_clock::now(); + while (DrawPipeline::Queue0().Outstanding() < queuedBefore && std::chrono::steady_clock::now() - start < std::chrono::seconds(60)) std::this_thread::sleep_for(std::chrono::milliseconds(1)); blocker.Enqueue(); } @@ -190,19 +205,20 @@ void IndirectDrawsQueueBehindTheCommitter() { AppendTarget(commands, SecondMemory, Clipped); AppendIndirect(commands); Blocker blocker; - SubmitBehind(blocker, commands); + SubmitBehind(blocker, commands, 3); const auto start = std::chrono::steady_clock::now(); while (DrawPipeline::Queue0().Outstanding() < 3) { if (std::chrono::steady_clock::now() - start > std::chrono::seconds(10)) { const auto outstanding = DrawPipeline::Queue0().Outstanding(); - blocker.release = true; + blocker.Release(); Finish(); Require(false, "the indirect draws drained the pipeline instead of queuing behind it (" + std::to_string(outstanding) + " items outstanding, expected the blocker and two draws)"); } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - blocker.release = true; + blocker.Release(); Finish(); + blocker.Join(); Flush(FirstMemory); Flush(SecondMemory); SameAsDirect(FirstMemory, "the first queued indirect draw"); @@ -219,12 +235,13 @@ void IndirectDrawWaitsForItsQueuedRecordWrite() { AppendTarget(commands, WrittenMemory, Clipped); AppendIndirect(commands); Blocker blocker; - SubmitBehind(blocker, commands); + const auto drainsBefore = DrawPipeline::DrainRequestsByOpcode(0x100); + SubmitBehind(blocker, commands, 1); const auto start = std::chrono::steady_clock::now(); - while (DrawPipeline::Queue0().Outstanding() < 2 && std::chrono::steady_clock::now() - start < std::chrono::seconds(10)) std::this_thread::sleep_for(std::chrono::milliseconds(1)); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - blocker.release = true; + while (DrawPipeline::DrainRequestsByOpcode(0x100) == drainsBefore && std::chrono::steady_clock::now() - start < std::chrono::seconds(10)) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + blocker.Release(); Finish(); + blocker.Join(); Flush(WrittenMemory); Require(LateRecords[0] == 3, "the queued label did not store the record's vertex count"); SameAsDirect(WrittenMemory, "the indirect draw whose record a queued label wrote"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedMetadataPass.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedMetadataPass.cpp new file mode 100644 index 0000000000..0410e4c9a6 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedMetadataPass.cpp @@ -0,0 +1,171 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +namespace { + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 4; +constexpr std::uint32_t Stale = 0x5a5a5a5au; +constexpr std::uint32_t Clear = 0x11223344u; + +constexpr std::size_t MemoryBytes = 65536; + +std::byte* allocateMemory() { +#ifdef _WIN32 + auto* memory = static_cast(VirtualAlloc(nullptr, MemoryBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + auto* memory = static_cast(std::aligned_alloc(MemoryBytes, MemoryBytes)); +#endif + if (memory == nullptr) throw std::runtime_error("pipelined metadata pass: cannot allocate the surface memory"); + std::memset(memory, 0, MemoryBytes); + return memory; +} + +std::byte* const Memory = allocateMemory(); +const std::span Surface{reinterpret_cast(Memory), Width * Height}; +const std::span Cmask{reinterpret_cast(Memory + 4096), 4096}; + +void check(bool condition, const std::string& what) { + if (!condition) throw std::runtime_error("pipelined metadata pass: " + what); +} + +void setContext(std::vector& words, std::uint32_t offset, std::uint32_t value) { + words.insert(words.end(), {0xc0016900u, offset, value}); +} + +void dmaFill(std::vector& words, std::uint32_t data, const void* destination, std::uint32_t bytes) { + const auto address = reinterpret_cast(destination); + words.insert(words.end(), {0xc0055000u, 2u << 29u, data, 0u, static_cast(address), static_cast(address >> 32u), bytes}); +} + +void eliminate(std::vector& words) { + const auto surface = reinterpret_cast(Surface.data()); + const auto cmask = reinterpret_cast(Cmask.data()); + words.insert(words.end(), {0xc0017900u, 0x242u, 4u}); + words.insert(words.end(), {0xc0017600u, 0x008u, 0x100u, 0xc0017600u, 0x009u, 0u}); + const std::vector> context{ + {0x2d5, 0x2000}, {0x1b6, 0}, {0x1b8, 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, 0xcc0020}, + {0x1c4, 0}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028u | 0x2000u}, + {0x31b, 0}, {0x31d, 0}, {0x3b0, (63u << 14u) | 3u}, + {0x3b8, 0x9000000u | (0x18u << 19u) | 0x4000000u}, {0x1e0, 0}, + {0xc, 0}, {0xd, 0x40040}, + {0x81, 0x80000000}, {0x82, 0x40040}, + {0x90, 0x80000000}, {0x91, 0x40040}, + {0x94, 0x80000000}, {0x95, 0x40040}, + {0x318, static_cast(surface >> 8u)}, {0x390, static_cast(surface >> 40u)}, + {0x31f, static_cast(cmask >> 8u)}, {0x398, static_cast(cmask >> 40u)}, + {0x323, Clear}, {0x324, 0}, + {0x0, 0}, {0xb4, 0}, + {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}, {0xb5, std::bit_cast(1.0f)}, + }; + for (const auto& [offset, value] : context) setContext(words, offset, value); + words.insert(words.end(), {0xc0002f00u, 1u}); + words.insert(words.end(), {0xc0012d00u, 3u, 2u}); +} + +void submit(std::vector& words) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "submission failed"); + AgcDriverWaitIdle_nid_postfix(); +} + +void expectEliminated(const std::string& what) { + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast(Surface.data()), Surface.size_bytes(), nullptr, "test"); + } + for (std::size_t i = 0; i < Surface.size(); ++i) check(Surface[i] == Clear, what + ": texel " + std::to_string(i) + " holds " + std::to_string(Surface[i])); + const auto cmaskBytes = AgcDriver::Graphics::CmaskLayout(Width, Height).Bytes(); + for (std::size_t i = 0; i < cmaskBytes; ++i) check(Cmask[i] == 0xffu, what + ": the CMASK was not left expanded"); +} + +} + +int main() { +#ifdef _WIN32 + _putenv_s("APS5_PIPELINED_DRAWS", "1"); + _putenv_s("APS5_DRAW_PIPELINE", "1"); +#else + setenv("APS5_PIPELINED_DRAWS", "1", 1); + setenv("APS5_DRAW_PIPELINE", "1", 1); +#endif + try { + { + GuestAllocations::Mutation mutation; + mutation.Add(Surface.data(), Surface.size_bytes(), true, true, true); + mutation.Add(Cmask.data(), Cmask.size(), true, true, true); + } + using AgcDriver::DriverDetail::DrawPipeline; + std::fill(Surface.begin(), Surface.end(), Stale); + std::fill(Cmask.begin(), Cmask.end(), std::uint8_t{0}); + std::vector first; + eliminate(first); + submit(first); + expectEliminated("the first eliminate"); + const auto before = DrawPipeline::PipelinedMetadataPasses(); + std::vector second; + std::atomic release{false}; + DrawPipeline::Queue0().Enqueue([&release] { + while (!release.load(std::memory_order_acquire)) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + }, {}); + std::thread releaser([&release] { + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + release.store(true, std::memory_order_release); + }); + dmaFill(second, Stale, Surface.data(), static_cast(Surface.size_bytes())); + dmaFill(second, 0, Cmask.data(), static_cast(Cmask.size())); + eliminate(second); + submit(second); + releaser.join(); + check(DrawPipeline::PipelinedMetadataPasses() == before + 1u, "the second eliminate was not ordered with the commits (" + std::to_string(DrawPipeline::PipelinedMetadataPasses() - before) + " pipelined)"); + expectEliminated("the eliminate after the queued stores"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(Surface.data()); + mutation.Remove(Cmask.data()); + } + LibcRunShutdown_nid_postfix(); + std::puts("pipelined metadata pass tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { LibcRunShutdown_nid_postfix(); } catch (...) {} + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedPrefetch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedPrefetch.cpp index cf3574966c..fe2b116f63 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedPrefetch.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelinedPrefetch.cpp @@ -106,12 +106,12 @@ int main() { const auto prefetched = SubmitBehindBusyPipeline(prefetch, 0x11111111u, true); check(prefetched.outstanding >= 2, "a DMA_DATA prefetch drained the draw pipeline: the label behind it never queued behind the held commit (" + std::to_string(prefetched.outstanding) + " outstanding)"); check(prefetched.drains == 0, "a DMA_DATA prefetch drained the draw pipeline (" + std::to_string(prefetched.drains) + " packet drains)"); - const auto copied = SubmitBehindBusyPipeline(copy, 0x22222222u, false); - check(copied.outstanding == 1, "a DMA_DATA copy did not hold the queue worker (" + std::to_string(copied.outstanding) + " outstanding)"); - check(copied.drains == 1, "a DMA_DATA copy did not drain the draw pipeline (" + std::to_string(copied.drains) + " packet drains)"); + const auto copied = SubmitBehindBusyPipeline(copy, 0x22222222u, true); + check(copied.outstanding >= 2, "a DMA_DATA copy that misses every queued write drained the draw pipeline: the label behind it never queued behind the held commit (" + std::to_string(copied.outstanding) + " outstanding)"); + check(copied.drains == 0, "a DMA_DATA copy that misses every queued write drained the draw pipeline (" + std::to_string(copied.drains) + " packet drains)"); for (std::size_t i = 0; i < Destination.size(); ++i) check(Destination[i] == (i < 128 ? Source[i] : 0u), "the copy did not land, or wrote past its bytes"); LibcRunShutdown_nid_postfix(); - std::puts("a DMA_DATA prefetch keeps the draw pipeline, a copy drains it"); + std::puts("a DMA_DATA prefetch and a copy that misses every queued write keep the draw pipeline"); return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp index f9813bbe14..6eb4c60d13 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp @@ -49,11 +49,31 @@ struct Vertex { float w; }; -constexpr std::array Corners{{{-0.8f, -0.8f, 1.0f}, {0.9f, -0.6f, 2.5f}, {-0.5f, 0.85f, 0.5f}}}; +constexpr std::array TriangleCorners{{{-0.8f, -0.8f, 1.0f}, {0.9f, -0.6f, 2.5f}, {-0.5f, 0.85f, 0.5f}}}; +constexpr std::array RectangleCorners{{{-0.8f, -0.8f, 1.5f}, {0.7f, -0.8f, 1.5f}, {-0.8f, 0.6f, 1.5f}}}; -std::array, 3> ClipPositions() { +enum class Emulation { + Geometry, + Tessellation, + RectList, +}; + +const std::array& Corners(Emulation emulation) { + return emulation == Emulation::RectList ? RectangleCorners : TriangleCorners; +} + +std::array, 3> ClipPositions(Emulation emulation) { + const auto& corners = Corners(emulation); std::array, 3> positions{}; - for (std::size_t i = 0; i < Corners.size(); ++i) positions[i] = {Corners[i].x * Corners[i].w, Corners[i].y * Corners[i].w, 0.5f * Corners[i].w, Corners[i].w}; + for (std::size_t i = 0; i < corners.size(); ++i) positions[i] = {corners[i].x * corners[i].w, corners[i].y * corners[i].w, 0.5f * corners[i].w, corners[i].w}; + return positions; +} + +constexpr std::array StripCorners{{{-0.8f, -0.8f, 1.0f}, {0.8f, -0.8f, 1.0f}, {-0.8f, 0.8f, 1.0f}, {0.8f, 0.8f, 1.0f}}}; + +std::array, 4> StripPositions() { + std::array, 4> positions{}; + for (std::size_t i = 0; i < StripCorners.size(); ++i) positions[i] = {StripCorners[i].x, StripCorners[i].y, 0.5f, 1.0f}; return positions; } @@ -62,16 +82,14 @@ std::array BufferDescriptor(const void* data, std::uint32_t st return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; } -enum class Emulation { - Geometry, - Tessellation, -}; - -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) { +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) { Pixels.fill(Unwritten); auto target = device.Target(); target.fragmentShaderBarycentricEnabled = false; - static const auto positions = ClipPositions(); + static const auto trianglePositions = ClipPositions(Emulation::Geometry); + static const auto rectanglePositions = ClipPositions(Emulation::RectList); + static const auto stripPositions = StripPositions(); + const auto positions = strip ? std::span>(stripPositions) : std::span>(emulation == Emulation::RectList ? rectanglePositions : trianglePositions); std::vector vertexUserData(4, 0u); const auto vertexBuffer = BufferDescriptor(positions.data(), 16u, static_cast(positions.size())); @@ -109,7 +127,12 @@ ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, Emulatio std::vector shaders{{ShaderStage::Vertex, &vertexResult, 0}}; ShaderRecompiler::RecompileResult geometry; ShaderRecompiler::BarycentricTessellationShaders patches; - if (pixelResult.barycentricEmulation.active && emulation == Emulation::Geometry) { + ShaderRecompiler::RectListShaders rectangle; + if (emulation == Emulation::RectList) { + rectangle = ShaderRecompiler::BuildRectListShaders(vertexResult, pixelResult, target); + shaders.push_back({ShaderStage::TessellationControl, &rectangle.control, 0}); + shaders.push_back({ShaderStage::TessellationEvaluation, &rectangle.evaluation, 0}); + } else if (pixelResult.barycentricEmulation.active && emulation == Emulation::Geometry) { geometry = ShaderRecompiler::BuildBarycentricGeometryShader(vertexResult, pixelResult, target, device.GeometryLimits()); shaders.push_back({ShaderStage::Geometry, &geometry, 0}); } else if (pixelResult.barycentricEmulation.active) { @@ -125,7 +148,8 @@ ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, Emulatio state.colors = {state.color}; state.hasColorTarget = true; state.renderExtent = {Width, Height}; - state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.topology = strip ? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : emulation == Emulation::RectList ? VK_PRIMITIVE_TOPOLOGY_PATCH_LIST : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.rectList = emulation == Emulation::RectList; state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; state.negativeOneToOne = false; state.scissor = {{0, 0}, {Width, Height}}; @@ -147,9 +171,10 @@ struct Expected { std::array perspective; }; -Expected Barycentrics(std::uint32_t x, std::uint32_t y, bool snapped) { +Expected Barycentrics(Emulation emulation, std::uint32_t x, std::uint32_t y, bool snapped) { + const auto& corners = Corners(emulation); std::array, 3> window{}; - for (std::size_t i = 0; i < Corners.size(); ++i) window[i] = {(Corners[i].x + 1.0) * Width / 2.0, (1.0 - Corners[i].y) * Height / 2.0}; + for (std::size_t i = 0; i < corners.size(); ++i) window[i] = {(corners[i].x + 1.0) * Width / 2.0, (1.0 - corners[i].y) * Height / 2.0}; if (snapped) { for (auto& corner : window) { for (auto& coordinate : corner) coordinate = std::round(coordinate * 256.0) / 256.0; @@ -160,22 +185,23 @@ Expected Barycentrics(std::uint32_t x, std::uint32_t y, bool snapped) { const auto edge = [](const std::array& a, const std::array& b, double cx, double cy) { return (b[0] - a[0]) * (cy - a[1]) - (b[1] - a[1]) * (cx - a[0]); }; const double area = edge(window[0], window[1], window[2][0], window[2][1]); const std::array b{edge(window[1], window[2], px, py) / area, edge(window[2], window[0], px, py) / area, edge(window[0], window[1], px, py) / area}; - Expected expected{std::min({b[0], b[1], b[2]}) > 0.02, {}, {}}; + const bool inside = emulation == Emulation::RectList ? std::min(b[1], b[2]) > 0.02 && std::max(b[1], b[2]) < 0.98 : std::min({b[0], b[1], b[2]}) > 0.02; + Expected expected{inside, {}, {}}; double sum = 0.0; - for (std::size_t i = 0; i < 3; ++i) sum += b[i] / Corners[i].w; + for (std::size_t i = 0; i < 3; ++i) sum += b[i] / corners[i].w; for (std::size_t i = 0; i < 3; ++i) { expected.linear[i] = static_cast(b[i]); - expected.perspective[i] = static_cast(b[i] / Corners[i].w / sum); + expected.perspective[i] = static_cast(b[i] / corners[i].w / sum); } return expected; } -Expected BarycentricsUnsnapped(std::uint32_t x, std::uint32_t y) { - return Barycentrics(x, y, false); +Expected BarycentricsUnsnapped(Emulation emulation, std::uint32_t x, std::uint32_t y) { + return Barycentrics(emulation, x, y, false); } -Expected BarycentricsSnapped(std::uint32_t x, std::uint32_t y) { - return Barycentrics(x, y, true); +Expected BarycentricsSnapped(Emulation emulation, std::uint32_t x, std::uint32_t y) { + return Barycentrics(emulation, x, y, true); } bool MatchesEither(float value, float unsnapped, float snapped) { @@ -186,13 +212,15 @@ float Pixel(std::uint32_t x, std::uint32_t y, std::uint32_t channel) { return Pixels[(y * Width + x) * 4u + channel]; } -std::uint32_t CheckBarycentrics(const std::string& what, bool perspective) { +std::uint32_t CheckBarycentrics(Emulation emulation, const std::string& what, bool perspective) { std::uint32_t checked = 0; + std::uint32_t beyond = 0; for (std::uint32_t y = 0; y < Height; ++y) { for (std::uint32_t x = 0; x < Width; ++x) { - const auto expected = BarycentricsUnsnapped(x, y); + const auto expected = BarycentricsUnsnapped(emulation, x, y); if (!expected.inside) continue; - const auto snapped = BarycentricsSnapped(x, y); + const auto snapped = BarycentricsSnapped(emulation, x, y); + if (expected.linear[0] < -0.02f) ++beyond; const auto& weights = perspective ? expected.perspective : expected.linear; const auto& snappedWeights = perspective ? snapped.perspective : snapped.linear; for (std::uint32_t channel = 0; channel < 4; ++channel) { @@ -205,17 +233,18 @@ std::uint32_t CheckBarycentrics(const std::string& what, bool perspective) { } } Require(checked > 200u, what + ": too few interior pixels were checked"); + Require(emulation != Emulation::RectList || beyond > 200u, what + ": too few pixels past the rectangle's diagonal were checked"); return checked; } -void CheckVertexValues() { - const auto positions = ClipPositions(); +void CheckVertexValues(Emulation emulation) { + const auto positions = ClipPositions(emulation); const auto p0 = positions[0][0]; const std::array expected{positions[1][0] - p0, positions[2][0] - p0, p0}; std::uint32_t checked = 0; for (std::uint32_t y = 0; y < Height; ++y) { for (std::uint32_t x = 0; x < Width; ++x) { - if (!BarycentricsUnsnapped(x, y).inside) continue; + if (!BarycentricsUnsnapped(emulation, x, y).inside) continue; for (std::uint32_t channel = 0; channel < 3; ++channel) { Require(Pixel(x, y, channel) == expected[channel], "v_interp_mov: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(Pixel(x, y, channel)) + ", expected " + std::to_string(expected[channel])); } @@ -225,14 +254,14 @@ void CheckVertexValues() { Require(checked > 200u, "v_interp_mov: too few interior pixels were checked"); } -void CheckInterpolated() { - const auto positions = ClipPositions(); +void CheckInterpolated(Emulation emulation) { + const auto positions = ClipPositions(emulation); std::uint32_t checked = 0; for (std::uint32_t y = 0; y < Height; ++y) { for (std::uint32_t x = 0; x < Width; ++x) { - const auto expected = BarycentricsUnsnapped(x, y); + const auto expected = BarycentricsUnsnapped(emulation, x, y); if (!expected.inside) continue; - const auto snapped = BarycentricsSnapped(x, y); + const auto snapped = BarycentricsSnapped(emulation, x, y); float reference = 0.0f; float snappedReference = 0.0f; for (std::size_t i = 0; i < 3; ++i) { @@ -249,13 +278,13 @@ void CheckInterpolated() { } void CheckFlat(const std::string& what, std::size_t provoking) { - const auto positions = ClipPositions(); + const auto positions = ClipPositions(Emulation::Geometry); std::uint32_t checked = 0; for (std::uint32_t y = 0; y < Height; ++y) { for (std::uint32_t x = 0; x < Width; ++x) { - const auto expected = BarycentricsUnsnapped(x, y); + const auto expected = BarycentricsUnsnapped(Emulation::Geometry, x, y); if (!expected.inside) continue; - const auto snapped = BarycentricsSnapped(x, y); + const auto snapped = BarycentricsSnapped(Emulation::Geometry, x, y); for (std::uint32_t channel = 0; channel < 2; ++channel) { Require(MatchesEither(Pixel(x, y, channel), expected.perspective[1u + channel], snapped.perspective[1u + channel]), what + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") weight " + std::to_string(channel) + " is " + std::to_string(Pixel(x, y, channel))); } @@ -282,31 +311,116 @@ void CheckWinding(AgcDriver::VulkanDevice& device, Emulation emulation, const st } } +struct StripPixel { + bool inside; + std::array order; + std::array weights; +}; + +StripPixel StripBarycentrics(std::uint32_t x, std::uint32_t y, bool snapped) { + std::array, 4> window{}; + for (std::size_t i = 0; i < StripCorners.size(); ++i) { + window[i] = {(StripCorners[i].x + 1.0) * Width / 2.0, (1.0 - StripCorners[i].y) * Height / 2.0}; + if (snapped) { + for (auto& coordinate : window[i]) coordinate = std::round(coordinate * 256.0) / 256.0; + } + } + const double px = x + 0.5; + const double py = y + 0.5; + const auto edge = [](const std::array& a, const std::array& b, double cx, double cy) { return (b[0] - a[0]) * (cy - a[1]) - (b[1] - a[1]) * (cx - a[0]); }; + for (const std::array order : {std::array{0, 1, 2}, std::array{1, 3, 2}}) { + const auto& a = window[order[0]]; + const auto& b = window[order[1]]; + const auto& c = window[order[2]]; + const double area = edge(a, b, c[0], c[1]); + const std::array weights{edge(b, c, px, py) / area, edge(c, a, px, py) / area, edge(a, b, px, py) / area}; + if (std::min({weights[0], weights[1], weights[2]}) > 0.02) return {true, order, weights}; + } + return {false, {}, {}}; +} + +void CheckStrip(AgcDriver::VulkanDevice& device, Emulation emulation, const std::string& name) { + Draw(device, emulation, BarycentricPixelCode, PixelInputBit(PixelInput::LinearCenter), VK_CULL_MODE_NONE, false, VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, true); + std::array checked{}; + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto expected = StripBarycentrics(x, y, false); + if (!expected.inside) continue; + const auto snapped = StripBarycentrics(x, y, true); + for (std::uint32_t channel = 0; channel < 4; ++channel) { + const auto reference = static_cast(expected.weights[1u + (channel & 1u)]); + const auto snappedReference = snapped.inside && snapped.order == expected.order ? static_cast(snapped.weights[1u + (channel & 1u)]) : reference; + Require(MatchesEither(Pixel(x, y, channel), reference, snappedReference), name + " triangle strip: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(Pixel(x, y, channel)) + ", expected " + std::to_string(reference)); + } + ++checked[expected.order[0]]; + } + } + Require(checked[0] > 200u && checked[1] > 200u, name + " triangle strip: too few pixels of each triangle were checked"); + static const auto positions = StripPositions(); + Draw(device, emulation, VertexValuePixelCode, PixelInputBit(PixelInput::PerspectiveCenter), VK_CULL_MODE_NONE, false, VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, true); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto expected = StripBarycentrics(x, y, false); + if (!expected.inside) continue; + const auto first = positions[expected.order[0]][0]; + const std::array values{positions[expected.order[1]][0] - first, positions[expected.order[2]][0] - first, first}; + for (std::uint32_t channel = 0; channel < 3; ++channel) { + Require(Pixel(x, y, channel) == values[channel], name + " triangle strip v_interp_mov: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(Pixel(x, y, channel)) + ", expected " + std::to_string(values[channel])); + } + } + } + std::printf("barycentric emulation through the %s passed for a triangle strip: %u and %u pixels of its even and odd triangle\n", name.c_str(), checked[0], checked[1]); +} + +void CheckStripFlatLast(AgcDriver::VulkanDevice& device, Emulation emulation, const std::string& name) { + Draw(device, emulation, FlatBarycentricPixelCode, PixelInputBit(PixelInput::PerspectiveCenter), VK_CULL_MODE_NONE, true, VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT, true); + const auto positions = StripPositions(); + std::array checked{}; + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto expected = StripBarycentrics(x, y, false); + if (!expected.inside) continue; + const auto triangle = expected.order[0]; + const auto provoking = triangle == 0u ? 2u : 3u; + for (std::uint32_t channel = 2; channel < 4; ++channel) { + Require(Pixel(x, y, channel) == positions[provoking][0], name + " flat last-provoking strip: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") flat channel " + std::to_string(channel) + " is " + std::to_string(Pixel(x, y, channel)) + ", expected vertex " + std::to_string(provoking) + " value " + std::to_string(positions[provoking][0])); + } + ++checked[triangle]; + } + } + Require(checked[0] > 200u && checked[1] > 200u, name + " flat last-provoking strip: too few pixels of each triangle were checked"); + std::printf("flat last-provoking strip through %s passed: %u and %u pixels of its even and odd triangle\n", name.c_str(), checked[0], checked[1]); +} + void RunEmulation(AgcDriver::VulkanDevice& device, Emulation emulation) { - const std::string name = emulation == Emulation::Geometry ? "geometry stage" : "tessellation stages"; + const std::string name = emulation == Emulation::Geometry ? "geometry stage" : emulation == Emulation::Tessellation ? "tessellation stages" : "rect-list stages"; const auto perspective = PixelInputBit(PixelInput::PerspectiveCenter); const auto linear = PixelInputBit(PixelInput::LinearCenter); auto result = Draw(device, emulation, BarycentricPixelCode, perspective); Require(result.barycentricEmulation.active && result.barycentricEmulation.smooth && !result.barycentricEmulation.linear, "reading PERSP_CENTER I/J did not emulate perspective barycentrics"); - const auto smooth = CheckBarycentrics(name + " PERSP_CENTER I/J", true); + const auto smooth = CheckBarycentrics(emulation, name + " PERSP_CENTER I/J", true); result = Draw(device, emulation, BarycentricPixelCode, linear); Require(result.barycentricEmulation.active && !result.barycentricEmulation.smooth && result.barycentricEmulation.linear, "reading LINEAR_CENTER I/J did not emulate linear barycentrics"); - const auto noPerspective = CheckBarycentrics(name + " LINEAR_CENTER I/J", false); + const auto noPerspective = CheckBarycentrics(emulation, name + " LINEAR_CENTER I/J", false); result = Draw(device, emulation, VertexValuePixelCode, perspective); Require(result.barycentricEmulation.active && result.fragmentParameters.size() == 1 && result.fragmentParameters[0].perVertex, "v_interp_mov of P10 and P20 did not emulate per-vertex parameters"); - CheckVertexValues(); + CheckVertexValues(emulation); result = Draw(device, emulation, InterpolatedPixelCode, perspective); Require(!result.barycentricEmulation.active, "plain v_interp_p1/p2 needed a generated stage"); - CheckInterpolated(); - CheckWinding(device, emulation, name); - result = Draw(device, emulation, FlatBarycentricPixelCode, perspective, VK_CULL_MODE_NONE, true); - Require(result.barycentricEmulation.active && std::any_of(result.fragmentParameters.begin(), result.fragmentParameters.end(), [](const auto& parameter) { return parameter.flat && !parameter.perVertex; }), "a flat P0 read beside raw I/J did not pass a flat parameter through the emulation"); - CheckFlat(name + " flat input, first provoking vertex", 0u); - if (emulation == Emulation::Tessellation) { - Draw(device, emulation, FlatBarycentricPixelCode, perspective, VK_CULL_MODE_NONE, true, VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT); - CheckFlat(name + " flat input, last provoking vertex", 2u); + CheckInterpolated(emulation); + if (emulation != Emulation::RectList) { + CheckWinding(device, emulation, name); + result = Draw(device, emulation, FlatBarycentricPixelCode, perspective, VK_CULL_MODE_NONE, true); + Require(result.barycentricEmulation.active && std::any_of(result.fragmentParameters.begin(), result.fragmentParameters.end(), [](const auto& parameter) { return parameter.flat && !parameter.perVertex; }), "a flat P0 read beside raw I/J did not pass a flat parameter through the emulation"); + CheckFlat(name + " flat input, first provoking vertex", 0u); + if (emulation == Emulation::Tessellation) { + Draw(device, emulation, FlatBarycentricPixelCode, perspective, VK_CULL_MODE_NONE, true, VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT); + CheckFlat(name + " flat input, last provoking vertex", 2u); + CheckStripFlatLast(device, emulation, name); + } + CheckStrip(device, emulation, name); } - std::printf("barycentric emulation through the %s passed: %u perspective and %u linear I/J pixels, raw vertex values, host interpolation, face culling and flat inputs\n", name.c_str(), smooth, noPerspective); + std::printf("barycentric emulation through the %s passed: %u perspective and %u linear I/J pixels, raw vertex values, host interpolation%s\n", name.c_str(), smooth, noPerspective, emulation == Emulation::RectList ? "" : ", face culling, flat inputs and triangle strips"); } } @@ -323,8 +437,12 @@ int main() { } if (geometry) RunEmulation(*device, Emulation::Geometry); else std::puts("geometry stage skipped, the device lacks geometryShader"); - if (tessellation) RunEmulation(*device, Emulation::Tessellation); - else std::puts("tessellation stages skipped, the device lacks tessellationShader"); + if (tessellation) { + RunEmulation(*device, Emulation::Tessellation); + RunEmulation(*device, Emulation::RectList); + } else { + std::puts("tessellation and rect-list stages skipped, the device lacks tessellationShader"); + } return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelExecInterpolation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelExecInterpolation.cpp new file mode 100644 index 0000000000..f9d09c28f4 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelExecInterpolation.cpp @@ -0,0 +1,159 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 48; +alignas(256) std::array Pixels{}; +constexpr std::array, 3> Corners{{{-0.8f, -0.8f, 1.0f}, {0.9f, -0.6f, 2.5f}, {-0.5f, 0.85f, 0.5f}}}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf800020f, 0x03020100, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +alignas(256) constexpr std::array ExecCopyPixelCode{ + 0xbefe0a7e, 0xc8080000, 0xbe80047e, 0xbefe0400, 0xc8090001, 0xf800180f, 0x02020202, 0xbf810000, +}; + +alignas(256) constexpr std::array RepeatedWqmPixelCode{ + 0xbefe0a7e, 0xc8080000, 0xbefe0a7e, 0xc8090001, 0xf800180f, 0x02020202, 0xbf810000, +}; + +alignas(256) constexpr std::array ExpandedExecPixelCode{ + 0xc8080000, 0xbefe0a7e, 0xc8090001, 0xf800180f, 0x02020202, 0xbf810000, +}; + +std::array, 3> ClipPositions() { + std::array, 3> positions{}; + for (std::size_t i = 0; i < Corners.size(); ++i) positions[i] = {Corners[i][0] * Corners[i][2], Corners[i][1] * Corners[i][2], 0.5f * Corners[i][2], Corners[i][2]}; + return positions; +} + +struct Expected { + bool inside; + float value; +}; + +Expected Interpolated(std::uint32_t x, std::uint32_t y, bool snapped) { + std::array, 3> window{}; + for (std::size_t i = 0; i < Corners.size(); ++i) window[i] = {(Corners[i][0] + 1.0) * Width / 2.0, (1.0 - Corners[i][1]) * Height / 2.0}; + if (snapped) { + for (auto& corner : window) for (auto& coordinate : corner) coordinate = std::round(coordinate * 256.0) / 256.0; + } + const double px = x + 0.5; + const double py = y + 0.5; + const auto edge = [](const std::array& a, const std::array& b, double cx, double cy) { return (b[0] - a[0]) * (cy - a[1]) - (b[1] - a[1]) * (cx - a[0]); }; + const double area = edge(window[0], window[1], window[2][0], window[2][1]); + const std::array weights{edge(window[1], window[2], px, py) / area, edge(window[2], window[0], px, py) / area, edge(window[0], window[1], px, py) / area}; + double denominator = 0.0; + for (std::size_t i = 0; i < Corners.size(); ++i) denominator += weights[i] / Corners[i][2]; + const auto positions = ClipPositions(); + double value = 0.0; + for (std::size_t i = 0; i < Corners.size(); ++i) value += weights[i] / Corners[i][2] / denominator * positions[i][0]; + return {std::min({weights[0], weights[1], weights[2]}) > 0.02, static_cast(value)}; +} + +std::uint32_t CheckPixels() { + std::uint32_t checked = 0; + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto expected = Interpolated(x, y, false); + if (!expected.inside) continue; + const auto snapped = Interpolated(x, y, true); + for (std::uint32_t channel = 0; channel < 4; ++channel) { + const auto value = Pixels[(y * Width + x) * 4u + channel]; + Require(std::fabs(value - expected.value) <= 1e-4f || std::fabs(value - snapped.value) <= 1e-4f, "EXEC interpolation: incorrect pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel)); + } + ++checked; + } + } + Require(checked > 200u, "EXEC interpolation: too few interior pixels were checked"); + return checked; +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { + auto target = device.Target(); + target.fragmentShaderBarycentricEnabled = false; + static const auto positions = ClipPositions(); + const auto address = reinterpret_cast(positions.data()); + const std::array vertexUserData{static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), 3u, 0x01016facu}; + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = waveSize == 32u; + pixel.interpolatorCount = 1; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCenter); + pixel.hasPerspectiveCenterVgpr = true; + pixel.executeOnNoop = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + const auto compile = [&](std::span code) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, {}, std::nullopt, pixel, std::nullopt, memory}, target, PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + return ShaderRecompiler::Recompile(fragment); + }; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R32G32B32A32_SFLOAT, sizeof(Pixels), 0xe4u, AgcDriver::Graphics::ColorTileMode::Linear, 16u}; + 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 = {}; + for (const auto code : {std::span(ExecCopyPixelCode), std::span(RepeatedWqmPixelCode)}) { + const auto result = compile(code); + Require(!result.barycentricEmulation.active, "changing EXEC between v_interp_p1/p2 needed a generated stage"); + Pixels.fill(-7.0f); + const std::array shaders{{{ShaderStage::Vertex, &vertexResult, 0}, {ShaderStage::Fragment, &result, PixelPushOffset(vertexPush, target)}}}; + device.Draw(state, {0, 3, 0, 1, 0, false}, shaders); + device.WaitIdle(); + std::printf("wave%u %zu words: host interpolation matched %u pixels\n", waveSize, code.size(), CheckPixels()); + } + Require(compile(ExpandedExecPixelCode).barycentricEmulation.active, "a partial interpolation written before WQM incorrectly used host interpolation"); + std::printf("wave%u: expanded EXEC keeps barycentric emulation\n", waveSize); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (const auto wave : {32u, 64u}) Run(*device, wave); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp index 116235fa11..74adeac4c0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "Recompiler.hpp" #include "VulkanTestDevice.hpp" @@ -228,6 +229,7 @@ int main() { Draw(*device, waveSize); Check(waveSize, 3); } + Require(!device->GraphicsPipelineLibraries() || AgcDriver::Graphics::PipelineLibraryCountersOf(device->Device()).linked == 0, "pixel interlock: a primitive-ordered pixel shader's pipeline was linked from graphics pipeline libraries"); std::printf("pixel interlock tests passed: 10 draws of %u overlapping triangles over %u pixels, 2 of them with a killed BDA fault in every %uth column\n", Layers, Width * Height, FaultColumns); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLimit.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLimit.cpp new file mode 100644 index 0000000000..594281d148 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLimit.cpp @@ -0,0 +1,181 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 32; +constexpr std::uint32_t High = 0x11110000u; +constexpr std::uint32_t Last = 0x22220000u; +constexpr std::uint32_t Past = 0x33330000u; +alignas(256) std::array Pixels{}; +alignas(256) std::array Results{}; +constexpr std::uint32_t Overflow = 256u; +alignas(256) std::array OverflowPixels{}; +alignas(256) std::array OverflowResults{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +std::vector PixelCode(std::uint32_t width) { + std::vector code{0x7e040f00, 0x7e060f01, 0xd5430006, 0x0409ff03, width}; + const auto mov = [&](std::uint32_t reg, std::uint32_t literal) { + code.push_back(0x7e0002ffu | (reg << 17u)); + code.push_back(literal); + }; + const auto shl = [&](std::uint32_t dest, std::uint32_t source) { + code.push_back(0x34000000u | (dest << 17u) | (source << 9u) | 0x82u); + }; + const auto write = [&](std::uint32_t index, std::uint32_t value) { + mov(16u, index); + shl(17u, 16u); + mov(18u, value); + code.push_back(0xd8340000u); + code.push_back((18u << 8u) | 17u); + }; + write(8000u, High); + write(8191u, Last); + write(8192u, Past); + code.push_back(0xbf8cc07fu); + const auto read = [&](std::uint32_t index, std::uint32_t dest) { + mov(16u, index); + shl(17u, 16u); + code.push_back(0xd8d80000u); + code.push_back((dest << 24u) | 17u); + }; + read(8000u, 10u); + read(8191u, 11u); + read(8192u, 12u); + shl(15u, 3u); + code.push_back(0xd8d80000u); + code.push_back((13u << 24u) | 15u); + code.push_back(0xbf8cc07fu); + for (std::uint32_t slot = 0; slot < 4u; ++slot) { + code.push_back(0xe0702000u | (slot * 4u)); + code.push_back(0x80010006u | ((10u + slot) << 8u)); + } + code.insert(code.end(), {0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000}); + return code; +} + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +constexpr std::array, 3> Triangles{{ + {-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f} +}}; + +void Draw(AgcDriver::VulkanDevice& device, std::uint32_t width, std::uint32_t height, std::span pixels, std::span results, std::uint32_t draws = 1u) { + const auto pixelCode = PixelCode(width); + std::fill(pixels.begin(), pixels.end(), std::byte{0}); + std::fill(results.begin(), results.end(), 0u); + const auto target = device.Target(); + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Triangles.data(), 16u, static_cast(Triangles.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {64u, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = false; + 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); + std::vector pixelUserData(8, 0u); + const auto stored = BufferDescriptor(results.data(), 16u, width * height); + std::copy(stored.begin(), stored.end(), pixelUserData.begin() + 4); + 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}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + Require(pixelResult.functionLdsBuffer == target.largePixelFunctionLdsBuffer, target.largePixelFunctionLdsBuffer ? "pixel LDS limit: the full array was not backed by a buffer" : "pixel LDS limit: a driver that keeps the function array used the buffer"); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, 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, static_cast(Triangles.size()), 0, 1, 0, false}; + for (std::uint32_t n = 0; n < draws; ++n) device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32u) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Draw(*device, Width, Height, Pixels, Results, 2u); + for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { + for (std::uint32_t channel = 0; channel < 4u; ++channel) Require(Pixels[pixel * 4u + channel] == std::byte{255}, "pixel LDS limit: missing color"); + const auto base = pixel * 4u; + Require(Results[base] == High, "pixel LDS limit: dword 8000 expected " + std::to_string(High) + " got " + std::to_string(Results[base])); + Require(Results[base + 1u] == Last, "pixel LDS limit: dword 8191 expected " + std::to_string(Last) + " got " + std::to_string(Results[base + 1u])); + Require(Results[base + 2u] == 0u, "pixel LDS limit: dword 8192 wrote " + std::to_string(Results[base + 2u])); + } + std::puts("pixel LDS limit passed: dword 8000 and 8191, 2048 pixels, two draws"); + if (device->Target().largePixelFunctionLdsBuffer) { + bool threw = false; + try { + Draw(*device, Overflow, Overflow, OverflowPixels, OverflowResults); + } catch (const std::exception& error) { + threw = std::string_view(error.what()) == "AGC graphics: pixel function LDS subgroup slots exhausted"; + if (!threw) throw; + } + Require(threw, "pixel LDS limit: a draw past the subgroup slots did not throw"); + std::puts("pixel LDS limit overflow passed"); + } + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelSampleCoverage.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelSampleCoverage.cpp index 7c6d5398c2..f3e0672a31 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelSampleCoverage.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelSampleCoverage.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,14 @@ #include #include +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context Context(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + namespace { using AgcDriver::Graphics::Require; @@ -54,7 +63,12 @@ constexpr std::array, 3> HalfTriangle{{ {-1.0f, -1.0f, 0.5f, 1.0f}, {1.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 1.0f, 0.5f, 1.0f} }}; -alignas(256) std::array, 4> Programs{}; +constexpr std::array, 3> OtherHalfTriangle{{ + {1.0f, -1.0f, 0.5f, 1.0f}, {1.0f, 1.0f, 0.5f, 1.0f}, {-1.0f, 1.0f, 0.5f, 1.0f} +}}; + +alignas(256) std::array, 16> Programs{}; +alignas(256) std::array DepthMemory{}; std::size_t programCount = 0; std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { @@ -76,6 +90,22 @@ std::span Program(std::uint32_t coverageVgpr) { return std::span(code.data(), at); } +std::span SampleIndexProgram() { + Require(programCount < Programs.size(), "too many test programs"); + auto& code = Programs[programCount++]; + std::size_t at = 0; + code[at++] = 0xd5480018u; + code[at++] = 0x02111100u; + code[at++] = 0x7e000c00u | (20u << 17u) | (256u + 24u); + code[at++] = 0x7e000280u | (21u << 17u); + code[at++] = 0x7e000280u | (22u << 17u); + code[at++] = 0x7e0002f2u | (23u << 17u); + code[at++] = 0xf800180fu; + code[at++] = 0x17161514u; + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + struct Block { explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) { #ifdef _WIN32 @@ -134,12 +164,12 @@ struct Block { std::uint8_t* data = nullptr; }; -AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t info) { +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t info, std::uint32_t attrib = 0) { AgcDriver::Registers cx; cx[0x318] = static_cast(block.Address() >> 8u); cx[0x31b] = 0; cx[0x31c] = info; - cx[0x31d] = 0; + cx[0x31d] = attrib; cx[0x323] = 0; cx[0x324] = 0; cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); @@ -150,7 +180,7 @@ AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t return color; } -void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t inputAddr, std::span pixelCode, std::span, 3> triangle = Triangle, std::uint32_t rasterSamples = 1) { +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t inputAddr, std::span pixelCode, std::span, 3> triangle = Triangle, std::uint32_t rasterSamples = 1, const AgcDriver::Graphics::DepthTarget* depth = nullptr, bool perSample = false) { const auto target = device.Target(); constexpr std::uint32_t waveSize = 64; std::vector vertexUserData(4, 0u); @@ -171,6 +201,8 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge pixel.inputAddr = inputAddr; pixel.ancillary = (inputAddr & ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary)) != 0u; pixel.hasPerspectiveCenterVgpr = (inputAddr & ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCenter)) != 0u; + pixel.sampleShading = perSample; + pixel.iteratesSamples = perSample; pixel.targetOutputMode.at(0) = Float32AbgrExport; pixel.targetExportMapping.fill(0xe4u); const std::vector pixelUserData(8, 0u); @@ -207,7 +239,15 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge state.blend = blend; state.blendConstants = {}; state.samples = rasterSamples; - state.mergedCoverage = rasterSamples > 1; + state.mergedCoverage = rasterSamples > 1 && depth == nullptr; + state.truncatedCoverage = rasterSamples > 1 && depth != nullptr; + state.perSampleShading = perSample; + if (depth != nullptr) { + state.depth = *depth; + state.depthTest = true; + state.depthWrite = false; + state.depthCompare = VK_COMPARE_OP_ALWAYS; + } const AgcDriver::Pm4::DrawParameters draw{0, static_cast(triangle.size()), 0, 1, 0, false}; device.Draw(state, draw, shaders); } @@ -273,6 +313,112 @@ bool ExpectMerged(AgcDriver::VulkanDevice& device) { return true; } +std::string ExpectTruncated(AgcDriver::VulkanDevice& device) { + if ((AgcDriver::VulkanDeviceTestAccess::Context(device).limits.framebufferDepthSampleCounts & VK_SAMPLE_COUNT_2_BIT) == 0) return "the device has no two-sample depth targets (framebufferDepthSampleCounts)"; + constexpr std::size_t bytes = std::size_t{Width} * Height * 16u; + AgcDriver::Graphics::DepthTarget depth{reinterpret_cast(DepthMemory.data()), 0u, {Width, Height}, VK_FORMAT_D32_SFLOAT, 0.0f, 0}; + depth.samples = 2; + std::array written{}; + std::uint32_t untouched = 0; + for (const auto* triangle : {&HalfTriangle, &OtherHalfTriangle}) { + Block block(bytes); + std::fill(block.data, block.data + bytes, std::uint8_t{0xff}); + const auto color = DecodeTarget(block, Rgba32FloatInfo); + try { + Draw(device, color, ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage), Program(0u), *triangle, 2u, &depth); + } catch (const std::exception& error) { + if (std::string_view(error.what()).find("truncated coverage reduction") == std::string_view::npos) throw; + return "the device lacks VK_AMD_mixed_attachment_samples and VK_NV_framebuffer_mixed_samples with truncated coverage reduction"; + } + const auto stored = ReadBack(device, block.Address(), bytes); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto* texel = stored.data() + (static_cast(y) * Width + x) * 16u; + if (std::all_of(texel, texel + 16, [](std::uint8_t byte) { return byte == 0xffu; })) { + ++untouched; + continue; + } + std::array value{}; + std::memcpy(value.data(), texel, sizeof(value)); + const auto mask = static_cast(value[0]); + Require(value[0] == static_cast(mask) && mask >= 1u && mask <= 3u && value[1] == 0.0f && value[2] == 0.0f && value[3] == 1.0f, "truncated coverage: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(value[0]) + ", " + std::to_string(value[1]) + ", " + std::to_string(value[2]) + ", " + std::to_string(value[3]) + ")"); + ++written[mask]; + } + } + } + Require(written[2] == 0, "truncated coverage: " + std::to_string(written[2]) + " pixels that cover only sample 1 were written into the one-sample target"); + Require(written[1] != 0 && written[3] != 0 && untouched != 0, "truncated coverage: two half-covering triangles wrote " + std::to_string(written[3]) + " fully covered and " + std::to_string(written[1]) + " sample-0-only pixels and left " + std::to_string(untouched)); + std::uint32_t perSampleWritten = 0; + std::uint32_t perSampleUntouched = 0; + for (const auto* triangle : {&HalfTriangle, &OtherHalfTriangle}) { + Block block(bytes); + std::fill(block.data, block.data + bytes, std::uint8_t{0xff}); + const auto color = DecodeTarget(block, Rgba32FloatInfo); + Draw(device, color, ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary), SampleIndexProgram(), *triangle, 2u, &depth, true); + const auto stored = ReadBack(device, block.Address(), bytes); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto* texel = stored.data() + (static_cast(y) * Width + x) * 16u; + if (std::all_of(texel, texel + 16, [](std::uint8_t byte) { return byte == 0xffu; })) { + ++perSampleUntouched; + continue; + } + std::array value{}; + std::memcpy(value.data(), texel, sizeof(value)); + Require(value == std::array{0.0f, 0.0f, 0.0f, 1.0f}, "truncated coverage per sample: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(value[0]) + ", " + std::to_string(value[1]) + ", " + std::to_string(value[2]) + ", " + std::to_string(value[3]) + "), not sample 0's output"); + ++perSampleWritten; + } + } + } + Require(perSampleWritten == written[1] + written[3] && perSampleUntouched == untouched, "truncated coverage per sample: the draws wrote " + std::to_string(perSampleWritten) + " pixels where one invocation per pixel wrote " + std::to_string(written[1] + written[3])); + return {}; +} + +std::string ExpectOneFragment(AgcDriver::VulkanDevice& device) { + if ((AgcDriver::VulkanDeviceTestAccess::Context(device).limits.framebufferDepthSampleCounts & VK_SAMPLE_COUNT_4_BIT) == 0) return "the device has no four-sample depth targets (framebufferDepthSampleCounts)"; + constexpr std::size_t bytes = std::size_t{Width} * Height * 16u; + AgcDriver::Graphics::DepthTarget depth{reinterpret_cast(DepthMemory.data()), 0u, {Width, Height}, VK_FORMAT_D32_SFLOAT, 0.0f, 0}; + depth.samples = 4; + std::array written{}; + std::uint32_t untouched = 0; + for (const auto* triangle : {&HalfTriangle, &OtherHalfTriangle}) { + Block block(bytes); + std::fill(block.data, block.data + bytes, std::uint8_t{0xff}); + const auto color = DecodeTarget(block, Rgba32FloatInfo, 0x2000u); + Require(color.samples == 1u, "a 4-sample color target with one fragment did not decode as one stored sample"); + try { + Draw(device, color, ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage), Program(0u), *triangle, 4u, &depth); + } catch (const std::exception& error) { + if (std::string_view(error.what()).find("truncated coverage reduction") == std::string_view::npos) throw; + return "the device lacks VK_AMD_mixed_attachment_samples and VK_NV_framebuffer_mixed_samples with truncated coverage reduction"; + } + const auto stored = ReadBack(device, block.Address(), bytes); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto* texel = stored.data() + (static_cast(y) * Width + x) * 16u; + if (std::all_of(texel, texel + 16, [](std::uint8_t byte) { return byte == 0xffu; })) { + ++untouched; + continue; + } + std::array value{}; + std::memcpy(value.data(), texel, sizeof(value)); + const auto mask = static_cast(value[0]); + Require(value[0] == static_cast(mask) && mask >= 1u && mask <= 15u && value[1] == 0.0f && value[2] == 0.0f && value[3] == 1.0f, "one-fragment coverage: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(value[0]) + ", " + std::to_string(value[1]) + ", " + std::to_string(value[2]) + ", " + std::to_string(value[3]) + ")"); + ++written[mask]; + } + } + } + std::uint32_t withoutSampleZero = 0; + std::uint32_t partial = 0; + for (std::uint32_t mask = 1; mask < 16u; ++mask) { + if ((mask & 1u) == 0) withoutSampleZero += written[mask]; + else if (mask != 15u) partial += written[mask]; + } + Require(withoutSampleZero == 0, "one-fragment coverage: " + std::to_string(withoutSampleZero) + " pixels whose coverage lacks sample 0 were written into the one fragment"); + Require(written[15] != 0 && partial != 0 && untouched != 0, "one-fragment coverage: two half-covering triangles wrote " + std::to_string(written[15]) + " fully covered and " + std::to_string(partial) + " partly covered pixels and left " + std::to_string(untouched) + " untouched"); + return {}; +} + } int main() { @@ -285,6 +431,8 @@ int main() { Expect(*device, PixelInputBit(PixelInput::PerspectiveCenter) | PixelInputBit(PixelInput::SampleCoverage), 2u, "SAMPLE_COVERAGE after the perspective center"); Expect(*device, PixelInputBit(PixelInput::Ancillary) | PixelInputBit(PixelInput::SampleCoverage), 1u, "SAMPLE_COVERAGE after ANCILLARY"); if (!ExpectMerged(*device)) std::puts("merged coverage not tested: the device lacks VK_NV_framebuffer_mixed_samples with merged coverage reduction"); + if (const auto skipped = ExpectTruncated(*device); !skipped.empty()) std::printf("truncated coverage not tested: %s\n", skipped.c_str()); + if (const auto skipped = ExpectOneFragment(*device); !skipped.empty()) std::printf("one-fragment coverage not tested: %s\n", skipped.c_str()); std::puts("pixel sample coverage tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PooledBufferRange.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PooledBufferRange.cpp index 785886aa84..988207259f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PooledBufferRange.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PooledBufferRange.cpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "VulkanTestDevice.hpp" +#include "FixtureDeviceSelection.hpp" #include "../AllocationFailure.hpp" #include "../SlabReturn.hpp" #include @@ -143,6 +144,8 @@ public: Device() { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + library = SDL_LoadObject("libvulkan.1.dylib"); #else library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -154,6 +157,12 @@ public: application.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &application; +#ifdef __APPLE__ + const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + info.enabledExtensionCount = 1; + info.ppEnabledExtensionNames = &portability; +#endif Check(function("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); std::uint32_t count = 0; const auto enumerate = function("vkEnumeratePhysicalDevices"); @@ -162,22 +171,11 @@ public: std::vector devices(count); Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); const auto properties = function("vkGetPhysicalDeviceProperties"); - const auto* requested = std::getenv("ANYPS5_GPU"); - const bool named = requested != nullptr && *requested != '\0'; - context.physical = devices.front(); - for (const auto candidate : devices) { - VkPhysicalDeviceProperties described{}; - properties(candidate, &described); - if (named ? std::strstr(described.deviceName, requested) != nullptr : described.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { - context.physical = candidate; - break; - } - } + context.physical = SelectFixturePhysicalDevice(devices, properties); VkPhysicalDeviceProperties selected{}; properties(context.physical, &selected); context.limits = selected.limits; std::printf("device %s vendor=0x%x device=0x%x driver=0x%x\n", selected.deviceName, selected.vendorID, selected.deviceID, selected.driverVersion); - Require(requested == nullptr || std::strstr(selected.deviceName, requested) != nullptr, "selected device does not match ANYPS5_GPU"); VkPhysicalDeviceFeatures supported{}; function("vkGetPhysicalDeviceFeatures")(context.physical, &supported); Require(supported.robustBufferAccess == VK_TRUE, "the device lacks robustBufferAccess"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparationScopeSettle.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparationScopeSettle.cpp index b67059d036..ab06a87e40 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparationScopeSettle.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparationScopeSettle.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgc/Shader/include/InterpolantMapping.hpp" #include "VulkanTestDevice.hpp" #include "SceShaders.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" @@ -15,8 +16,6 @@ #include #include -extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps); - namespace { void Require(bool condition, const char* message) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp index beaed11d60..1273ff05c7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp @@ -371,6 +371,34 @@ void TransactionLock() { Require(excluded, "a transaction ran beside one that had read prepared state"); } +void CommittedLinks() { + ShaderSnapshot front{}; + auto pixel = std::make_shared(); + ResolvePreparedGraphics(front, pixel, 0, {}); + Require(front.prepared->fragments.size() == 1 && front.prepared->fragments.front().lock() == pixel, "a resolved link was not published"); + ShaderSnapshot unrelated{}; + auto other = std::make_shared(); + std::optional open(std::in_place); + static_cast(open->Read(unrelated)); + auto repeated = std::async(std::launch::async, [&] { ResolvePreparedGraphics(front, pixel, 0, {}); }); + const bool unblocked = repeated.wait_for(std::chrono::seconds(10)) == std::future_status::ready; + auto linking = std::async(std::launch::async, [&] { ResolvePreparedGraphics(front, other, 0, {}); }); + const bool excluded = linking.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout; + open->Commit(); + open.reset(); + repeated.get(); + linking.get(); + Require(unblocked, "resolving a link the shader already had waited for another thread's transaction"); + Require(excluded && front.prepared->fragments.size() == 2, "a new link was published beside a transaction that had read prepared state"); + { + ShaderPreparationTransaction transaction; + transaction.Edit(front).fragments.clear(); + ResolvePreparedGraphics(front, pixel, 0, {}); + transaction.Commit(); + } + Require(front.prepared->fragments.size() == 1 && front.prepared->fragments.front().lock() == pixel, "a link resolved in a transaction that had changed the shader was checked against the published links"); +} + void PreparationPriority(bool beforeSubmit) { const auto workers = std::max(2u, std::thread::hardware_concurrency() / 2u); std::barrier started(static_cast(workers + 1u)); @@ -415,6 +443,7 @@ int main() { RegistryVersions(); FusedHullHeaderSelection(); TransactionLock(); + CommittedLinks(); PreparationPriority(false); PreparationPriority(true); auto device = OpenVulkanTestDevice(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index 734a7c6825..82c999000d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -437,6 +437,15 @@ void SharedCodeHeaders() { AgcDriverRegisterShader_nid_postfix(&mesh.shader); AgcDriverResolveShaderAbi_nid_postfix(&vertex.shader, {}, primitive); AgcDriverRegisterShader_nid_postfix(&mesh.shader); + const auto snapshots = [&] { + const auto registry = AgcDriver::DriverDetail::Driver::Get().RegisteredShaders(); + Require(registry != nullptr, "shared-code headers published no shader registry"); + auto pair = std::pair{AgcDriver::DriverDetail::RegisteredHeader(*registry, &vertex.shader), AgcDriver::DriverDetail::RegisteredHeader(*registry, &mesh.shader)}; + Require(pair.first != nullptr && pair.second != nullptr && pair.first != pair.second, "the shared-code headers were not registered apart"); + return pair; + }; + const auto [vertexSnapshot, meshSnapshot] = snapshots(); + Require(vertexSnapshot->registeredState != meshSnapshot->registeredState, "a header with other registers shared another header's registered state"); Shader copy = vertex.shader; copy.user_data = nullptr; AgcDriverResolveShaderAbi_nid_postfix(©, {}, primitive); @@ -461,6 +470,8 @@ void SharedCodeHeaders() { mesh.shader.cx_registers = mesh.context.data(); mesh.shader.num_cx_registers = mesh.context.size(); AgcDriverRegisterShader_nid_postfix(&mesh.shader); + const auto [sameVertex, sameMesh] = snapshots(); + Require(sameVertex->registeredState == sameMesh->registeredState && sameMesh->registeredState->context.at(0x2d5) == 0x2000u, "two headers with the same code and registers decoded their registered state twice"); AgcDriverResolveShaderAbi_nid_postfix(&mesh.shader, {}, primitive); AgcDriverResolveShaderAbi_nid_postfix(&vertex.shader, {}, primitive); Require(sceAgcDriverSubmitDcb(&packet) == 0, "same-type registered header draw failed"); @@ -958,11 +969,13 @@ void AbiDeferredCompute() { caller.shader.specials = &caller.specials; caller.specials.dispatch_modifier = 0x8000u; AgcDriverRegisterShader_nid_postfix(&caller.shader); + const bool lazy = std::getenv("APS5_LAZY_SHADER_PREPARE") != nullptr; const auto before = ShaderRecompiler::FrontEndBuilds(); AgcDriverResolveShaderAbi_nid_postfix(&caller.shader, {}, {}); const auto attempted = ShaderRecompiler::FrontEndBuilds(); AgcDriverResolveShaderAbi_nid_postfix(&caller.shader, {}, {}); - Require(attempted > before && ShaderRecompiler::FrontEndBuilds() == attempted, "a compute static ABI deferred to its dispatch was prepared again for the same state"); + if (lazy) Require(attempted == before && ShaderRecompiler::FrontEndBuilds() == attempted, "a compute static ABI resolved under lazy preparation built its front end before its dispatch"); + else Require(attempted > before && ShaderRecompiler::FrontEndBuilds() == attempted, "a compute static ABI deferred to its dispatch was prepared again for the same state"); const auto first = reinterpret_cast(AddFiveCallee.data()); const auto second = reinterpret_cast(AddSixtyFourCallee.data()); const auto output = reinterpret_cast(CallerOutput.data()); @@ -981,6 +994,88 @@ void AbiDeferredCompute() { AgcDriverShutdown_nid_postfix(); } +void AbiDeferredConcurrent() { + using namespace NarrowConstantStoreFixture; + Require(std::getenv("APS5_LAZY_SHADER_PREPARE") != nullptr, "lazy preparation is not enabled"); + struct Header { + Shader shader{}; + std::array registers{}; + ShaderSpecialRegs specials{}; + }; + static Header header; + const auto address = reinterpret_cast(Code.data()); + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18; + header.shader.header_size = sizeof(header); + header.shader.shader_size = sizeof(Code); + header.shader.code = Code.data(); + header.shader.sh_registers = header.registers.data(); + header.shader.num_sh_registers = header.registers.size(); + header.shader.specials = &header.specials; + header.specials.dispatch_modifier = 0x8000; + header.registers = {{{0x20c, static_cast(address >> 8u)}, {0x20d, static_cast(address >> 40u)}, {0x207, Threads}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 4}}}; + AgcDriverRegisterShader_nid_postfix(&header.shader); + AgcDriverResolveShaderAbi_nid_postfix(&header.shader, {}, {}); + constexpr std::size_t Queues = 4; + constexpr std::size_t OutputBytes = 65536; +#ifdef _WIN32 + auto* allocation = static_cast(VirtualAlloc(nullptr, Queues * OutputBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + auto* allocation = static_cast(std::aligned_alloc(OutputBytes, Queues * OutputBytes)); +#endif + Require(allocation != nullptr, "cannot allocate the concurrent first use outputs"); + const auto release = [](std::uint8_t* block) { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + }; + const std::unique_ptr ownedOutputs(allocation, release); + GuestAllocations::Mutation().Add(allocation, Queues * OutputBytes, true, true, true); + std::fill_n(allocation, Queues * OutputBytes, Fill); + std::vector> commands(Queues); + std::vector packets; + for (std::size_t queue = 0; queue < Queues; ++queue) { + const auto output = reinterpret_cast(allocation + queue * OutputBytes); + for (const auto reg : header.registers) commands[queue].insert(commands[queue].end(), {0xc0017600u, reg.offset, reg.value}); + commands[queue].insert(commands[queue].end(), {0xc0027600u, 0x240, static_cast(output), static_cast(output >> 32u), 0xc0031500u, 1, 1, 1, 0x8041}); + packets.push_back({commands[queue].data(), static_cast(commands[queue].size()), 0, {}}); + } + const auto builds = ShaderRecompiler::FrontEndBuilds(); + const auto uses = AgcDriver::DriverDetail::Driver::DeferredAbiUses(); + std::barrier start(static_cast(Queues)); + std::vector threads; + for (std::size_t queue = 0; queue < Queues; ++queue) { + threads.emplace_back([&, queue] { + start.arrive_and_wait(); + sceAgcDriverSubmitAcb(0x20u + static_cast(queue), &packets[queue]); + }); + } + for (auto& thread : threads) thread.join(); + AgcDriverWaitIdle_nid_postfix(); + AgcDriverSuspendPoint_nid_postfix(); + AgcDriverWaitIdle_nid_postfix(); + for (std::size_t queue = 0; queue < Queues; ++queue) { + const auto* output = allocation + queue * OutputBytes; + for (std::size_t offset = 0; offset < OutputBytes; ++offset) { + const auto expected = offset < Threads * LaneBytes ? ExpectedLane[offset % LaneBytes] : Fill; + if (output[offset] != expected) throw std::runtime_error("concurrent first use: queue " + std::to_string(queue) + " byte " + std::to_string(offset) + " is " + std::to_string(output[offset]) + ", expected " + std::to_string(expected)); + } + } + const auto after = AgcDriver::DriverDetail::Driver::DeferredAbiUses(); + const auto built = ShaderRecompiler::FrontEndBuilds() - builds; + const auto entries = AgcDriver::DriverDetail::Driver::Get().PreparedEntries(&header.shader); + const auto matched = (after.entries - after.unmatchedEntries) - (uses.entries - uses.unmatchedEntries); + const auto counts = std::to_string(built) + " front-end builds, " + std::to_string(entries) + " prepared entries, " + std::to_string(after.firstUses - uses.firstUses) + " first uses, " + std::to_string(after.found - uses.found) + " found, " + std::to_string(after.own - uses.own) + " own, " + std::to_string(matched) + " entries used"; + std::cout << "concurrent first use of a deferred ABI: " << counts << '\n'; + Require(after.firstUses == uses.firstUses + 1 && after.found == uses.found + 1 && after.own == uses.own, ("four queues making the first use of one deferred ABI at once did not prepare it once (" + counts + ")").c_str()); + Require(built == after.entries - uses.entries && entries == built, ("four queues making the first use of one deferred ABI at once prepared an artifact besides its preparation (" + counts + ")").c_str()); + Require(matched == 1, ("four queues making the first use of one deferred ABI at once did not all use the same entry of its preparation (" + counts + ")").c_str()); + AgcDriverShutdown_nid_postfix(); +} + void AbiDeferredRegistrationFailure() { static AbiHeader store; DescribeAbi(store, WaterfallStoreCode, 0, {{0x20c, ProgramLow(WaterfallStoreCode.data())}, {0x20d, ProgramHigh(WaterfallStoreCode.data())}, {0x207, 64}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 2u << 1u}}, {}); @@ -1198,6 +1293,63 @@ void AbiDeferredRectanglePairs() { ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "custom per-vertex interpolation is unsupported"); } +void CommittedAbis() { + static AbiHeader front; + static AbiHeader pixel; + DescribeFront(front); + DescribeAbi(pixel, PassthroughPixelCode, 1, {{0x8, ProgramLow(PassthroughPixelCode.data())}, {0x9, ProgramHigh(PassthroughPixelCode.data())}, {0xa, 0}, {0xb, 0}}, {{0x1b6, 1}, {0x1b3, 2}, {0x1b4, 2}, {0x191, 0}, {0x1c5, 4}}); + for (auto* header : {&front, &pixel}) AgcDriverRegisterShader_nid_postfix(&header->shader); + const std::array stages{&front.shader, &pixel.shader}; + const std::array primitive{{{0x242, 4}}}; + const std::array interpolant{{{0x191, 0x20}}}; + const auto resolve = [&] { + AgcDriver::ShaderPreparationScope scope({&front.shader, &pixel.shader}); + AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); + AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, {}, primitive); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &pixel.shader, 0); + scope.Commit(); + }; + const auto resolveOther = [&] { + AgcDriver::ShaderPreparationScope scope({&pixel.shader}); + AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, interpolant, {}); + scope.Commit(); + }; + resolve(); + const auto registry = AgcDriver::DriverDetail::Driver::Get().RegisteredShaders(); + Require(registry != nullptr, "committed ABI resolution published no shader registry"); + const auto pixelSnapshot = AgcDriver::DriverDetail::RegisteredHeader(*registry, &pixel.shader); + Require(pixelSnapshot != nullptr, "committed ABI resolution published no pixel snapshot"); + const auto committedAbis = [&] { + std::lock_guard lock(pixelSnapshot->prepared->mutex); + return pixelSnapshot->prepared->registeredAbis.size(); + }; + AgcDriver::DriverDetail::AwaitRegistrationPreparation(); + const auto before = ShaderRecompiler::FrontEndBuilds(); + const auto abisBefore = committedAbis(); + bool unblocked = false; + bool excluded = false; + std::uint64_t repeatedBuilds = 0; + std::future repeated; + std::future other; + { + AgcDriver::DriverDetail::ShaderSnapshot unrelated{}; + AgcDriver::DriverDetail::ShaderPreparationTransaction open; + static_cast(open.Read(unrelated)); + repeated = std::async(std::launch::async, resolve); + unblocked = repeated.wait_for(std::chrono::seconds(10)) == std::future_status::ready; + repeatedBuilds = ShaderRecompiler::FrontEndBuilds() - before; + other = std::async(std::launch::async, resolveOther); + const auto started = std::chrono::steady_clock::now(); + while (ShaderRecompiler::FrontEndBuilds() == before + repeatedBuilds && std::chrono::steady_clock::now() - started < std::chrono::seconds(10)) std::this_thread::yield(); + excluded = other.wait_for(std::chrono::milliseconds(500)) == std::future_status::timeout && committedAbis() == abisBefore; + open.Commit(); + } + repeated.get(); + other.get(); + Require(unblocked && repeatedBuilds == 0, "resolving ABIs the shaders already had waited for another thread's shader preparation, or prepared them again"); + Require(excluded, "a new ABI was published beside a shader preparation that had read prepared state"); +} + void RelocatedHeaders() { alignas(256) static const std::array code{0x7e0002ffu, 0, 0x7e0202ffu, 0, 0x7e0402ffu, 0, 0x7e0602ffu, 0x3f800000u, 0xf80008cfu, 0x03020100u, 0xbf810000u}; struct Header { @@ -1261,16 +1413,85 @@ void RelocatedHeaders() { ExpectFailure([&] { AgcDriverResolveShaderAbi_nid_postfix(&otherContext.shader, {}, primitive); }, "replaced shader header"); } +void DispatchAheadPrewarm() { + alignas(256) std::array pendingCode{0xbf810000u}; + alignas(256) std::array lazyCode{}; + lazyCode[0] = 0xbf810000u; + lazyCode[64] = 0xbf810000u; + struct Header { + Shader shader{}; + std::array registers{}; + ShaderSpecialRegs specials{}; + } pendingHeader, lazyHeader; + const auto fillHeader = [](Header& header, const void* code, std::size_t codeBytes) { + const auto address = reinterpret_cast(code); + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(codeBytes); + header.shader.code = const_cast(code); + header.shader.sh_registers = header.registers.data(); + header.shader.num_sh_registers = header.registers.size(); + header.shader.specials = &header.specials; + header.specials.dispatch_modifier = 0x8000; + header.registers = {{{0x20c, static_cast(address >> 8u)}, {0x20d, static_cast(address >> 40u)}, {0x207, 1}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 2}}}; + }; + fillHeader(pendingHeader, pendingCode.data(), sizeof(pendingCode)); + fillHeader(lazyHeader, lazyCode.data(), sizeof(lazyCode)); + AgcDriverRegisterShader_nid_postfix(&pendingHeader.shader); + AgcDriverRegisterShader_nid_postfix(&lazyHeader.shader); + AgcDriver::DriverDetail::AwaitRegistrationPreparation(); + const auto registry = AgcDriver::DriverDetail::Driver::Get().RegisteredShaders(); + Require(registry != nullptr, "dispatch ahead prewarm test published no shader registry"); + const auto pendingSnapshot = AgcDriver::DriverDetail::RegisteredHeader(*registry, &pendingHeader.shader); + const auto lazySnapshot = AgcDriver::DriverDetail::RegisteredHeader(*registry, &lazyHeader.shader); + Require(pendingSnapshot != nullptr && lazySnapshot != nullptr, "dispatch ahead prewarm test published no snapshots"); + { + std::lock_guard lock(pendingSnapshot->prepared->mutex); + pendingSnapshot->prepared->entries.clear(); + pendingSnapshot->prepared->demanded.store(false, std::memory_order_release); + pendingSnapshot->prepared->pending = true; + } + { + std::lock_guard lock(lazySnapshot->prepared->mutex); + lazySnapshot->prepared->entries.clear(); + lazySnapshot->prepared->pending = false; + } + alignas(256) static const std::array invalidCode{0xbf9f0000u}; + const auto invalidAddress = reinterpret_cast(invalidCode.data()); + const auto lazyOffsetAddress = reinterpret_cast(lazyCode.data() + 64); + alignas(16) const std::array indirectPairs{0x20cu, static_cast(lazyOffsetAddress >> 8u), 0x20du, static_cast(lazyOffsetAddress >> 40u), 0x240u, 7u}; + const auto pairsAddress = reinterpret_cast(indirectPairs.data()); + std::vector commands; + for (const auto reg : pendingHeader.registers) commands.insert(commands.end(), {0xc0017600u, reg.offset, reg.value}); + commands.insert(commands.end(), {0xc0017600u, 0x20cu, static_cast(invalidAddress >> 8u), 0xc0017600u, 0x20du, static_cast(invalidAddress >> 40u), 0xc0031500u, 1, 1, 1, 0x8041u, 0xffff1000u}); + commands.insert(commands.end(), {0xc0017600u, 0x20cu, pendingHeader.registers[0].value, 0xc0017600u, 0x20du, pendingHeader.registers[1].value, 0xc0031500u, 1, 1, 1, 0x8041u}); + commands.insert(commands.end(), {0xc0036300u, static_cast(pairsAddress), static_cast(pairsAddress >> 32u), 0x80000000u, 3u}); + commands.insert(commands.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitAcb(0x20, &packet) == 0, "dispatch ahead prewarm submit failed"); + ExpectFailure([] { AgcDriverWaitIdle_nid_postfix(); }, "s_code_end"); + AgcDriver::DriverDetail::AwaitRegistrationPreparation(); + AgcDriver::DriverDetail::AwaitLibraryPrebuilds(); + Require(pendingSnapshot->prepared->demanded.load(std::memory_order_acquire), "dispatch ahead did not prioritize the pending shader"); + { + std::lock_guard lock(lazySnapshot->prepared->mutex); + Require(lazySnapshot->prepared->entries.size() == 1 && lazySnapshot->prepared->entries.front().codeOffset == 64 && lazySnapshot->prepared->entries.front().handle != nullptr, "dispatch ahead did not prewarm the lazy shader for its dispatched offset"); + } + ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "s_code_end"); +} + } int main(int argc, char** argv) { try { const std::string_view abiMode = argc == 2 ? argv[1] : ""; - const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state" || abiMode == "--abi-deferred-registration-failure" || abiMode == "--abi-deferred-vertex-inputs" || abiMode == "--abi-deferred-rectangle-pairs"; - Require(argc == 1 || abiDeferred || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable" || std::string_view(argv[1]) == "--registered-workgroup" || std::string_view(argv[1]) == "--lazy")), "invalid test arguments"); + const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-concurrent" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state" || abiMode == "--abi-deferred-registration-failure" || abiMode == "--abi-deferred-vertex-inputs" || abiMode == "--abi-deferred-rectangle-pairs"; + Require(argc == 1 || abiDeferred || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable" || std::string_view(argv[1]) == "--registered-workgroup" || std::string_view(argv[1]) == "--dispatch-ahead-prewarm" || std::string_view(argv[1]) == "--lazy")), "invalid test arguments"); if (abiDeferred) { if (!OpenVulkanTestDevice()) return VulkanTestSkipped; if (abiMode == "--abi-deferred-compute") AbiDeferredCompute(); + else if (abiMode == "--abi-deferred-concurrent") AbiDeferredConcurrent(); else if (abiMode == "--abi-deferred-pixel") AbiDeferredPixel(); else if (abiMode == "--abi-deferred-stages") AbiDeferredStages(); else if (abiMode == "--abi-deferred-undecodable-state") AbiDeferredUndecodableState(); @@ -1294,6 +1515,12 @@ int main(int argc, char** argv) { AgcDriverShutdown_nid_postfix(); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--dispatch-ahead-prewarm") { + if (!OpenVulkanTestDevice()) return VulkanTestSkipped; + DispatchAheadPrewarm(); + std::cout << "dispatch ahead prewarm tests passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--deferred") { if (!OpenVulkanTestDevice()) return VulkanTestSkipped; DeferredRegistration(); @@ -1318,6 +1545,7 @@ int main(int argc, char** argv) { device.reset(); SharedCodeHeaders(); RelocatedHeaders(); + CommittedAbis(); RegistrationWithoutSpecials(); Registration(argc == 2); std::cout << "prepared shader and transactional registration tests passed\n"; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PresentKeyWriterWait.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PresentKeyWriterWait.cpp new file mode 100644 index 0000000000..e85b6e55d0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PresentKeyWriterWait.cpp @@ -0,0 +1,202 @@ +#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.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 +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Side = 128; +constexpr std::uint64_t DisplayFormat = 0x8000000000000000ull; +constexpr std::size_t SurfaceBytes = 65536; +constexpr std::size_t KeysOffset = SurfaceBytes; +constexpr std::size_t KeysCapacity = 32768; +constexpr std::size_t CopiedKeys = 256; +constexpr std::size_t SourceKeysOffset = KeysOffset + KeysCapacity; +constexpr std::size_t BlockBytes = 2 * 65536; +constexpr std::uint8_t Uncompressed = 0xff; +constexpr std::uint8_t Cleared = 0x00; + +alignas(256) constexpr std::array CopyKeysCode{ + 0x34020083, 0xe0341000, 0x80000201, 0xbf8c3f70, 0xe0741000, 0x80010201, 0xbf810000, +}; + +std::uint64_t AddressOf(const void* data) { + return static_cast(reinterpret_cast(data)); +} + +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(65536, BlockBytes)); +#endif + Require(block != nullptr, "present key writer wait: 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; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = AddressOf(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x31016facu}; +} + +ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code, const std::vector& userData) { + 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; + return ShaderRecompiler::Recompile(request); +} + +struct Scenario { + std::uint8_t* surface; + std::uint8_t* keys; + std::uint8_t* source; + std::size_t keyBytes; + AgcDriver::DisplayBuffer buffer; + std::vector copyData; +}; + +Scenario Prepare(std::uint8_t* block) { + Scenario scenario{block, block + KeysOffset, block + SourceKeysOffset, 0, {AddressOf(block), DisplayFormat, Side, Side}, std::vector(16, 0u)}; + scenario.buffer.dccAddress = AddressOf(scenario.keys); + scenario.keyBytes = AgcDriver::DisplayBufferKeyBytes(scenario.buffer); + Require(AgcDriver::DisplayBufferSize(scenario.buffer) <= SurfaceBytes && scenario.keyBytes >= CopiedKeys && scenario.keyBytes <= KeysCapacity, "present key writer wait: the display buffer does not fit the guest block"); + const auto source = BufferDescriptor(scenario.source, CopiedKeys); + const auto destination = BufferDescriptor(scenario.keys, CopiedKeys); + std::copy(source.begin(), source.end(), scenario.copyData.begin()); + std::copy(destination.begin(), destination.end(), scenario.copyData.begin() + 4); + return scenario; +} + +void Reset(const Scenario& scenario) { + std::memset(scenario.surface, 0x55, SurfaceBytes); + std::memset(scenario.keys, Uncompressed, scenario.keyBytes); + std::memset(scenario.keys, Cleared, CopiedKeys); + std::memset(scenario.source, Uncompressed, CopiedKeys); + AgcDriver::GuestMemory::BumpCollectEpoch(); +} + +std::string PresentFailure(AgcDriver::VulkanDevice& device, const AgcDriver::DisplayBuffer& buffer) { + try { + device.PresentDisplayBuffer(buffer); + } catch (const std::exception& error) { + return error.what(); + } + return "the headless present succeeded"; +} + +const std::string NoSwapchain = "Vulkan presentation: device has no swapchain"; + +bool CanCopyKeys(AgcDriver::VulkanDevice& device, const Scenario& scenario) { + const std::array pattern{0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu}; + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const bool filled = device.FillBuffer(AddressOf(scenario.keys), CopiedKeys, pattern); + device.WaitIdle(); + return filled; +} + +void RunPendingWriter(AgcDriver::VulkanDevice& device, const Scenario& scenario) { + Reset(scenario); + const auto copy = Compile(device, CopyKeysCode, scenario.copyData); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Require(AgcDriver::GuestMemory::GpuMutex().DepthOnThisThread() == 1, "present key writer wait: the presenter's hold is not the outermost"); + device.Dispatch(copy, 1, 1, 1, {}, reinterpret_cast(CopyKeysCode.data())); + Require(AgcDriver::Graphics::DccKeyWritersPending(scenario.buffer.dccAddress, scenario.keyBytes), "a recorded kernel that copies data over the display keys is not seen as a pending key writer"); + const auto holds = AgcDriver::GuestMemory::GpuHoldSerial(); + const auto failure = PresentFailure(device, scenario.buffer); + const auto acquired = AgcDriver::GuestMemory::GpuHoldSerial() - holds; + Require(failure == NoSwapchain, "a present behind a pending writer of the display keys stopped before the swapchain: " + failure); + Require(acquired != 0, "the presenter synchronized with the display keys' writer without releasing the GPU mutex"); + Require(!AgcDriver::Graphics::DccKeyWritersPending(scenario.buffer.dccAddress, scenario.keyBytes), "the display keys' writer is still pending after the present"); + Require(scenario.keys[0] == Uncompressed && scenario.keys[CopiedKeys - 1] == Uncompressed, "the present read the display keys before their writer landed"); +} + +void RunNoWriter(AgcDriver::VulkanDevice& device, const Scenario& scenario) { + std::memset(scenario.keys, Uncompressed, scenario.keyBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + device.WaitIdle(); + Require(!AgcDriver::Graphics::DccKeyWritersPending(scenario.buffer.dccAddress, scenario.keyBytes), "the idle device still reports a pending writer of the display keys"); + const auto holds = AgcDriver::GuestMemory::GpuHoldSerial(); + const auto failure = PresentFailure(device, scenario.buffer); + const auto acquired = AgcDriver::GuestMemory::GpuHoldSerial() - holds; + Require(failure == NoSwapchain, "a present with no pending writer of the display keys stopped before the swapchain: " + failure); + Require(acquired == 0, "a present with no pending writer of the display keys released the GPU mutex"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (!device->CanWaitUnlocked()) { + std::puts("skipped, the device has no timeline semaphores: the presenter cannot wait without the GPU mutex"); + return VulkanTestSkipped; + } + GuestBlock block; + const auto scenario = Prepare(block.Data()); + if (!CanCopyKeys(*device, scenario)) { + std::puts("skipped, host imports are unavailable: the key writer of the test is a copied writer"); + return VulkanTestSkipped; + } + RunPendingWriter(*device, scenario); + RunNoWriter(*device, scenario); + std::puts("present key writer wait 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/ProgramWritesMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ProgramWritesMemory.cpp new file mode 100644 index 0000000000..9efc1296de --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ProgramWritesMemory.cpp @@ -0,0 +1,48 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint32_t EndProgram = 0xbf810000u; + +bool writes(std::initializer_list words) { + AgcDriver::DriverDetail::ShaderSnapshot snapshot{}; + snapshot.code.assign(words.begin(), words.end()); + return AgcDriver::DriverDetail::ProgramWritesMemory(snapshot, 0); +} + +void expect(bool condition, const std::string& what) { + if (!condition) throw std::runtime_error("program memory writes: " + what); +} + +} + +int main() { + try { + expect(!writes({0xf800000fu, 0x00000000u, EndProgram}), "an export-only program writes memory"); + expect(!writes({0xe0300000u, 0x80000100u, EndProgram}), "a buffer load writes memory"); + expect(writes({0xe0700000u, 0x80000100u, EndProgram}), "a buffer store does not write memory"); + expect(writes({0xf0200f00u, 0x00000000u, EndProgram}), "an image store does not write memory"); + expect(writes({0xd8360000u, 0x00000000u, EndProgram}), "a GDS write does not write memory"); + expect(writes({0xbe802000u, EndProgram}), "an indirect jump is treated as reading only"); + expect(writes({0x7e000280u}), "code that never ends decodes as reading only"); + AgcDriver::DriverDetail::ShaderSnapshot store{}; + store.code = {0xe0700000u, 0x80000100u, EndProgram}; + expect(AgcDriver::DriverDetail::ProgramWritesMemory(store, 0) && AgcDriver::DriverDetail::ProgramWritesMemory(store, 0), "the cached answer differs"); + expect(!AgcDriver::DriverDetail::ProgramWritesMemory(store, 2), "the program from an offset past the store writes memory"); + LibcRunShutdown_nid_postfix(); + std::puts("program memory writes tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { LibcRunShutdown_nid_postfix(); } catch (...) {} + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/QueueZeroLateInterrupt.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/QueueZeroLateInterrupt.cpp index 00f3ca4d2e..b1b08b90c9 100755 --- a/core/libs/prx/libSceAgcDriver/tests/execution/QueueZeroLateInterrupt.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/QueueZeroLateInterrupt.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" @@ -27,9 +28,6 @@ #include #include -extern "C" int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -extern "C" int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); - namespace { constexpr std::uint32_t Lanes = 32; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/QueuedIndexReadOrder.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/QueuedIndexReadOrder.cpp index acc2b1617a..d69048386f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/QueuedIndexReadOrder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/QueuedIndexReadOrder.cpp @@ -343,10 +343,8 @@ void CopyPendingImage(std::size_t bytes, const char* phase) { int main() { #ifdef _WIN32 _putenv_s("APS5_PIPELINED_DRAWS", "1"); - _putenv_s("APS5_PIPELINE_DMA", "1"); #else setenv("APS5_PIPELINED_DRAWS", "1", 1); - setenv("APS5_PIPELINE_DMA", "1", 1); #endif try { { @@ -373,6 +371,11 @@ int main() { AgcDriverWaitIdle_nid_postfix(); Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "queued index read: the image write left no pending result to order against"); Require(IndicesIntact(), "queued index read: the image's results reached guest memory before any consumer asked"); + const auto copiedAddress = static_cast(reinterpret_cast(Copied)); + const auto heldQueries = AgcDriver::DriverDetail::DrawPipeline::HeldReadQueries(); + Require(!AgcDriver::Graphics::StorageTexture::FlushPending(copiedAddress, PoolBytes), "queued index read: a flush over memory without pending results stored something"); + Require(AgcDriver::DriverDetail::DrawPipeline::HeldReadQueries() == heldQueries, "queued index read: a flush with nothing to store asked the draw pipeline for held reads"); + Require(IndicesIntact(), "queued index read: a flush over other memory stored the image's results"); GateUntilQueued(); std::fill_n(ColorMemory, ColorBytes, std::byte{0}); Submit(Draw(Vertices.data(), Vertices.size()), "the indexed draw"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/RcpHardware.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/RcpHardware.cpp new file mode 100644 index 0000000000..66a3c78357 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/RcpHardware.cpp @@ -0,0 +1,162 @@ +#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 = 32; +constexpr std::array, 256> Measured{{ + {0x000000u, 0x3f800000u}, {0x002922u, 0x3f7fadd6u}, {0x00a1a9u, 0x3f7ebe44u}, {0x010c47u, 0x3f7debceu}, + {0x013df5u, 0x3f7d8a32u}, {0x01a87fu, 0x3f7cb9ddu}, {0x024f43u, 0x3f7b766fu}, {0x02d76du, 0x3f7a70beu}, + {0x031690u, 0x3f79f820u}, {0x033035u, 0x3f79c742u}, {0x03adabu, 0x3f78d946u}, {0x04435bu, 0x3f77bfa4u}, + {0x04f3a1u, 0x3f76772au}, {0x057a96u, 0x3f757df8u}, {0x05c6afu, 0x3f74f253u}, {0x06342au, 0x3f742a80u}, + {0x06bf6fu, 0x3f732e24u}, {0x07709au, 0x3f71f00au}, {0x07eb02u, 0x3f711627u}, {0x086273u, 0x3f704304u}, + {0x09362bu, 0x3f6ed04au}, {0x099950u, 0x3f6e2438u}, {0x09f75cu, 0x3f6d81e2u}, {0x0a097cu, 0x3f6d62b3u}, + {0x0ab7e3u, 0x3f6c3840u}, {0x0b1a69u, 0x3f6b90f0u}, {0x0b93a5u, 0x3f6ac454u}, {0x0becd7u, 0x3f6a2eadu}, + {0x0bf0b4u, 0x3f6a2836u}, {0x0c5c7fu, 0x3f697463u}, {0x0c9164u, 0x3f691c8au}, {0x0cb1e2u, 0x3f68e6b5u}, + {0x0d5f89u, 0x3f67c8a0u}, {0x0dd27au, 0x3f670cc6u}, {0x0e09e4u, 0x3f66b2a2u}, {0x0ead02u, 0x3f65aae2u}, + {0x0ed904u, 0x3f656421u}, {0x0f17a3u, 0x3f64ffbeu}, {0x0f21ddu, 0x3f64ef61u}, {0x0f2bcdu, 0x3f64df7du}, + {0x0f4205u, 0x3f64bbfeu}, {0x0f8808u, 0x3f644c6cu}, {0x0faddcu, 0x3f641050u}, {0x0fd630u, 0x3f63d05fu}, + {0x1012f0u, 0x3f637050u}, {0x108fe7u, 0x3f62abb4u}, {0x10a3d6u, 0x3f628c77u}, {0x1184b9u, 0x3f612e5au}, + {0x119a72u, 0x3f610cc2u}, {0x11e20bu, 0x3f609e4fu}, {0x123f59u, 0x3f600f00u}, {0x128b2fu, 0x3f5f9b0du}, + {0x12bd4au, 0x3f5f4eb3u}, {0x12f18du, 0x3f5eff48u}, {0x135d16u, 0x3f5e5c8du}, {0x13deefu, 0x3f5d994bu}, + {0x140ceeu, 0x3f5d5472u}, {0x14a0f9u, 0x3f5c77fdu}, {0x14f473u, 0x3f5bfc70u}, {0x15051au, 0x3f5be3dau}, + {0x153e7cu, 0x3f5b8f4fu}, {0x15b949u, 0x3f5adb3au}, {0x1600a3u, 0x3f5a7320u}, {0x1676ebu, 0x3f59c766u}, + {0x172abbu, 0x3f58c45au}, {0x1738f7u, 0x3f58aff3u}, {0x17a05au, 0x3f581c33u}, {0x17f5e8u, 0x3f57a288u}, + {0x1818e8u, 0x3f5770e9u}, {0x185f1au, 0x3f570da8u}, {0x18f135u, 0x3f564038u}, {0x194f2fu, 0x3f55bce2u}, + {0x1a61dbu, 0x3f54409cu}, {0x1a63e2u, 0x3f543dd2u}, {0x1af098u, 0x3f537d12u}, {0x1bd958u, 0x3f52413au}, + {0x1cb224u, 0x3f511e54u}, {0x1deb7au, 0x3f4f7f68u}, {0x1df9fdu, 0x3f4f6c59u}, {0x1e27a1u, 0x3f4f307du}, + {0x1e398fu, 0x3f4f1903u}, {0x1ef9d8u, 0x3f4e1e85u}, {0x1fb17cu, 0x3f4d317eu}, {0x1fec12u, 0x3f4ce652u}, + {0x202036u, 0x3f4ca39au}, {0x20b671u, 0x3f4be450u}, {0x211c70u, 0x3f4b633bu}, {0x21b455u, 0x3f4aa42eu}, + {0x2217beu, 0x3f4a27e7u}, {0x22c947u, 0x3f494b6eu}, {0x230d97u, 0x3f48f719u}, {0x23a75fu, 0x3f483a41u}, + {0x244f6fu, 0x3f476d74u}, {0x24ede6u, 0x3f46add8u}, {0x250d33u, 0x3f46882au}, {0x254b0cu, 0x3f463de2u}, + {0x263ba6u, 0x3f451ef3u}, {0x269e0du, 0x3f44aa89u}, {0x26a2c0u, 0x3f44a4fdu}, {0x26bb7du, 0x3f4487d0u}, + {0x26e875u, 0x3f4452ddu}, {0x2744e3u, 0x3f43e660u}, {0x281972u, 0x3f42eeaau}, {0x29b882u, 0x3f4111f2u}, + {0x2a3af4u, 0x3f407e00u}, {0x2a96fbu, 0x3f401628u}, {0x2afa95u, 0x3f3fa642u}, {0x2b0537u, 0x3f3f9a58u}, + {0x2baf6au, 0x3f3edc66u}, {0x2c7557u, 0x3f3e015au}, {0x2d1c9au, 0x3f3d49c5u}, {0x2d711au, 0x3f3ced8cu}, + {0x2e0531u, 0x3f3c4cc6u}, {0x2e4415u, 0x3f3c08d1u}, {0x2e8e7du, 0x3f3bb8aau}, {0x2eae05u, 0x3f3b96c8u}, + {0x2fa54eu, 0x3f3a8eaeu}, {0x301850u, 0x3f3a14d7u}, {0x30d16au, 0x3f39520au}, {0x31f5efu, 0x3f38216cu}, + {0x32c6ecu, 0x3f374a2du}, {0x33d0ceu, 0x3f363b28u}, {0x34b9b5u, 0x3f355051u}, {0x34c9ebu, 0x3f35400eu}, + {0x364b9du, 0x3f33c092u}, {0x36f675u, 0x3f3318bau}, {0x376cbfu, 0x3f32a53au}, {0x37dc76u, 0x3f3238afu}, + {0x387a34u, 0x3f31a04au}, {0x3898d1u, 0x3f3182d5u}, {0x392630u, 0x3f30fb4bu}, {0x398406u, 0x3f30a1c6u}, + {0x3aa8b5u, 0x3f2f8cd0u}, {0x3b1287u, 0x3f2f2983u}, {0x3b6186u, 0x3f2edfabu}, {0x3bbbe9u, 0x3f2e8b79u}, + {0x3bfb1eu, 0x3f2e50c8u}, {0x3cf38cu, 0x3f2d6b98u}, {0x3d9c17u, 0x3f2cd172u}, {0x3e7d1bu, 0x3f2c054du}, + {0x3e875fu, 0x3f2bfc08u}, {0x3f63afu, 0x3f2b360fu}, {0x3f98e2u, 0x3f2b0685u}, {0x3fa7d9u, 0x3f2af92au}, + {0x407b49u, 0x3f2a3d5au}, {0x4174afu, 0x3f2961e2u}, {0x427df9u, 0x3f287ad8u}, {0x43435cu, 0x3f27d089u}, + {0x437bcbu, 0x3f27a016u}, {0x44985eu, 0x3f26ad72u}, {0x451abdu, 0x3f263f34u}, {0x457d42u, 0x3f25ec44u}, + {0x472077u, 0x3f248ef6u}, {0x4724dau, 0x3f248b56u}, {0x47469au, 0x3f246f78u}, {0x482c9cu, 0x3f23b287u}, + {0x486824u, 0x3f2381e6u}, {0x48db40u, 0x3f232432u}, {0x499523u, 0x3f228dc2u}, {0x49b64au, 0x3f22730au}, + {0x49dea9u, 0x3f22528du}, {0x4a23d5u, 0x3f221b02u}, {0x4b14f9u, 0x3f215a86u}, {0x4c25fcu, 0x3f2082beu}, + {0x4cbd87u, 0x3f200befu}, {0x4cdd20u, 0x3f1ff340u}, {0x4dd422u, 0x3f1f334cu}, {0x4ef8aau, 0x3f1e5249u}, + {0x4f20f3u, 0x3f1e337eu}, {0x4f426du, 0x3f1e19f1u}, {0x5051c1u, 0x3f1d4c05u}, {0x506bf2u, 0x3f1d3841u}, + {0x507313u, 0x3f1d32e0u}, {0x519088u, 0x3f1c5cc0u}, {0x520935u, 0x3f1c02eau}, {0x52e6b4u, 0x3f1b5f10u}, + {0x536d35u, 0x3f1afc38u}, {0x54a6d8u, 0x3f1a17a2u}, {0x55c663u, 0x3f19485eu}, {0x56ed5bu, 0x3f187600u}, + {0x571242u, 0x3f185bd8u}, {0x5790f8u, 0x3f180249u}, {0x57ee05u, 0x3f17c0c8u}, {0x588a7eu, 0x3f175320u}, + {0x58d556u, 0x3f171ee4u}, {0x59a54au, 0x3f168e80u}, {0x59f999u, 0x3f165444u}, {0x5affb2u, 0x3f15a05bu}, + {0x5b855cu, 0x3f15453fu}, {0x5bd86du, 0x3f150cd9u}, {0x5c90a9u, 0x3f149059u}, {0x5cd546u, 0x3f146230u}, + {0x5d9dc9u, 0x3f13dbf0u}, {0x5e14d9u, 0x3f138caau}, {0x5e8766u, 0x3f1340b6u}, {0x5f41cau, 0x3f12c5c6u}, + {0x5f5572u, 0x3f12b8dcu}, {0x60fd9bu, 0x3f11a440u}, {0x616499u, 0x3f1161b4u}, {0x625d1cu, 0x3f10c218u}, + {0x62c33au, 0x3f1080e8u}, {0x63c0e3u, 0x3f0fdff7u}, {0x641547u, 0x3f0faabcu}, {0x64b8d7u, 0x3f0f43feu}, + {0x651327u, 0x3f0f0b83u}, {0x658cdau, 0x3f0ebfadu}, {0x65dc9fu, 0x3f0e8e23u}, {0x662b59u, 0x3f0e5d60u}, + {0x66d228u, 0x3f0df67eu}, {0x670f27u, 0x3f0dd104u}, {0x67e4c4u, 0x3f0d4e61u}, {0x6916adu, 0x3f0c94eeu}, + {0x6a50dfu, 0x3f0bd86cu}, {0x6a7a5au, 0x3f0bbfaeu}, {0x6b0a18u, 0x3f0b6a37u}, {0x6b0d54u, 0x3f0b684cu}, + {0x6b4013u, 0x3f0b4a3au}, {0x6b4cb2u, 0x3f0b42c1u}, {0x6bd361u, 0x3f0af338u}, {0x6bf46cu, 0x3f0adfc3u}, + {0x6cad4au, 0x3f0a734au}, {0x6cb93du, 0x3f0a6c4cu}, {0x6d76b0u, 0x3f09fdddu}, {0x6dc676u, 0x3f09cf92u}, + {0x6e36aau, 0x3f098ea8u}, {0x6f0367u, 0x3f0918d3u}, {0x6fa5cau, 0x3f08bbedu}, {0x70c958u, 0x3f08165eu}, + {0x71e6c2u, 0x3f0775ccu}, {0x721583u, 0x3f075ba3u}, {0x72e6ccu, 0x3f06e703u}, {0x72fdf2u, 0x3f06da29u}, + {0x73596du, 0x3f06a778u}, {0x7403e4u, 0x3f064966u}, {0x744916u, 0x3f06235cu}, {0x74c9dfu, 0x3f05dccau}, + {0x74e69au, 0x3f05cd16u}, {0x756dbau, 0x3f05836cu}, {0x7631a9u, 0x3f05192au}, {0x7731afu, 0x3f048f50u}, + {0x7774a2u, 0x3f046b72u}, {0x791a7cu, 0x3f038b32u}, {0x795e82u, 0x3f036751u}, {0x7aa2c1u, 0x3f02bd52u}, + {0x7c2684u, 0x3f01f444u}, {0x7c64ebu, 0x3f01d422u}, {0x7d2cafu, 0x3f016db2u}, {0x7d959fu, 0x3f013822u}, + {0x7e62aau, 0x3f00cffbu}, {0x7ebff2u, 0x3f00a0d0u}, {0x7eecf4u, 0x3f008a1au}, {0x7f1505u, 0x3f0075eau}, + {0x7f1b10u, 0x3f0072dfu}, {0x7f2614u, 0x3f006d53u}, {0x7ff486u, 0x3f0005beu}, {0x7fffffu, 0x3f000001u}, +}}; +constexpr std::array Exponents{1u, 60u, 127u, 128u, 200u, 252u}; +constexpr std::uint32_t Cases = static_cast(Measured.size() * Exponents.size() * 2u); +constexpr std::uint32_t Batches = Cases / Threads; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020082, 0xe0301000, 0x80000201, 0xbf8c3f70, 0x7e065502, 0xe0701000, 0x80010301, 0xbf8c3f70, + 0xbf810000, +}; + +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::uint32_t Sample(std::uint32_t index) { + const auto& measured = Measured[index % Measured.size()]; + const auto exponent = Exponents[(index / Measured.size()) % Exponents.size()]; + const auto sign = (index / static_cast(Measured.size() * Exponents.size())) << 31u; + return sign | exponent << 23u | measured[0]; +} + +std::uint32_t Expected(std::uint32_t index) { + const auto& measured = Measured[index % Measured.size()]; + const auto exponent = Exponents[(index / Measured.size()) % Exponents.size()]; + const auto sign = (index / static_cast(Measured.size() * Exponents.size())) << 31u; + return sign | ((measured[1] >> 23u) + 127u - exponent) << 23u | (measured[1] & 0x7fffffu); +} +} + +int main() { + try { + if (std::getenv("APS5_EXACT_RCP") == nullptr) return VulkanTestSkipped; + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {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 = ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}; + const auto result = ShaderRecompiler::Recompile(request); + for (std::uint32_t batch = 0; batch < Batches; ++batch) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Input[tid] = Sample(batch * Threads + tid); + Output.fill(0xdeadbeefu); + device->Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device->WaitIdle(); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto wanted = Expected(batch * Threads + tid); + Require(Output[tid] == wanted, std::string("v_rcp_f32 of ") + Hex(Input[tid]) + " is " + Hex(Output[tid]) + ", expected the RDNA 2 value " + Hex(wanted)); + } + } + std::puts("rcp hardware 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/RectListCorner.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/RectListCorner.cpp index 0657f99bca..eeeb0fd05c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/RectListCorner.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/RectListCorner.cpp @@ -2,6 +2,9 @@ #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "Recompiler.hpp" #include "SpirvBackend/SpirvModule.hpp" +#if ANYPS5_ENABLE_SPIRV_TOOLS +#include "SpirvBackend/SpirvOptimizer.hpp" +#endif #include "VulkanTestDevice.hpp" #include #include @@ -46,8 +49,11 @@ public: builder.EmitEntryPoint(spv::ExecutionModelVertex, main, "main", {vertexIndex, position, identity}); result(spv::OpLabel); const auto index = result(spv::OpLoad, intType, vertexIndex); - const auto first = result(spv::OpIEqual, boolType, index, constant(intType, 0u)); - const auto second = result(spv::OpIEqual, boolType, index, constant(intType, 1u)); + const auto boolVectorType = type(spv::OpTypeVector, boolType, 4u); + const auto firstScalar = result(spv::OpIEqual, boolType, index, constant(intType, 0u)); + const auto secondScalar = result(spv::OpIEqual, boolType, index, constant(intType, 1u)); + const auto first = result(spv::OpCompositeConstruct, boolVectorType, firstScalar, firstScalar, firstScalar, firstScalar); + const auto second = result(spv::OpCompositeConstruct, boolVectorType, secondScalar, secondScalar, secondScalar, secondScalar); const auto positionValue = result(spv::OpSelect, vec4Type, first, vector(-1.0f, -1.0f, 0.0f, 1.0f), result(spv::OpSelect, vec4Type, second, vector(1.0f, -1.0f, 0.0f, 1.0f), vector(-1.0f, 1.0f, 0.0f, 1.0f))); const auto identityValue = result(spv::OpSelect, vec4Type, first, vector(0.0f, 0.0f, 0.0f, 1.0f), result(spv::OpSelect, vec4Type, second, vector(1.0f, 0.0f, 0.0f, 1.0f), vector(0.0f, 1.0f, 0.0f, 1.0f))); emit(spv::OpStore, position, positionValue); @@ -128,19 +134,24 @@ std::uint32_t VertexOf(std::uint32_t x, std::uint32_t y) { return (red >= 128u ? 1u : 0u) + (green >= 128u ? 2u : 0u); } -int Run(AgcDriver::VulkanDevice& device) { +int Run(AgcDriver::VulkanDevice& device, std::uint32_t maximumVersion) { std::cout << "device: " << device.DeviceName() << "\n"; auto target = device.Target(); if (!target.tessellation.has_value() || !TargetHasCapability(target, spv::CapabilityTessellation)) { std::cout << "skipped, device has no tessellation\n"; return VulkanTestSkipped; } - if (target.spirvVersion > 0x00010400u) target.spirvVersion = 0x00010400u; + if (target.spirvVersion > maximumVersion) target.spirvVersion = maximumVersion; Require(target.spirvVersion >= 0x00010300u, "rect-list corner draw needs SPIR-V 1.3"); + std::cout << "SPIR-V " << (target.spirvVersion >> 16u) << "." << ((target.spirvVersion >> 8u) & 0xffu) << "\n"; ShaderRecompiler::RecompileResult vertex; ShaderRecompiler::RecompileResult fragment; vertex.spirv = ShaderRecompiler::SharedSpirv(Emitter(target.spirvVersion).Vertex()); fragment.spirv = ShaderRecompiler::SharedSpirv(Emitter(target.spirvVersion).Fragment()); +#if ANYPS5_ENABLE_SPIRV_TOOLS + static_cast(ShaderRecompiler::ValidateAndOptimizeSpirv(vertex.spirv, target.vulkanVersion, target.spirvVersion, target.nonConstantImageOffsets, false)); + static_cast(ShaderRecompiler::ValidateAndOptimizeSpirv(fragment.spirv, target.vulkanVersion, target.spirvVersion, target.nonConstantImageOffsets, false)); +#endif vertex.parameterExports = {0u}; fragment.fragmentParameters = {{0u, 0u, false, false}}; const auto rectangle = ShaderRecompiler::BuildRectListShaders(vertex, fragment, target); @@ -194,8 +205,12 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - const auto result = Run(*device); + auto result = Run(*device, 0x00010300u); if (result != 0) return result; + if (device->Target().spirvVersion >= 0x00010400u) { + result = Run(*device, 0x00010400u); + if (result != 0) return result; + } std::cout << "rect-list corner tests passed\n"; return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ResidentHeapMirror.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ResidentHeapMirror.cpp new file mode 100644 index 0000000000..bf1cc2eae4 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ResidentHeapMirror.cpp @@ -0,0 +1,393 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ResidentMirror.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint64_t Block = 65536; +constexpr std::uint64_t HeapBytes = 8u << 20u; +constexpr std::uint64_t JoinedBytes = 16u << 20u; +constexpr std::uint64_t ThreadStride = 16384; +constexpr std::uint64_t StoreBlock = 3 * Block; +constexpr std::uint32_t LateThread = 5; +constexpr std::uint64_t ZeroedBlock = 2 * Block; + +alignas(256) constexpr std::array Code{ + 0x3404008e, 0xdc388000, 0x04000002, 0x34060082, 0xbf8c3f70, 0xe0701000, 0x80010403, 0xe0701000, 0x80020303, 0xbf810000, +}; + +alignas(256) constexpr std::array CopyCode{0x34060082, 0xe0301000, 0x80000403, 0xbf8c3f70, 0xe0701000, 0x80010403, 0xbf810000}; + +class OutputBlock { +public: + OutputBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, Block, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(Block, Block)); +#endif + Require(block != nullptr, "resident heap mirror: cannot allocate the output block"); + GuestAllocations::Mutation().Add(block, Block, true, true, true); + } + + ~OutputBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + OutputBlock(const OutputBlock&) = delete; + OutputBlock& operator=(const OutputBlock&) = delete; + + std::uint32_t* Data() const { return block; } + +private: + std::uint32_t* block = nullptr; +}; + +class GuestHeap { +public: + explicit GuestHeap(bool writable, std::uint64_t ranges = 1, std::uint64_t rangeBytes = HeapBytes, void* at = nullptr) : ranges(ranges), rangeBytes(rangeBytes) { + const auto bytes = rangeBytes * ranges; +#ifdef _WIN32 + raw = at == nullptr ? GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block) + : GuestArena::GuestArenaAllocateAtOrAbove_nid_postfix(reinterpret_cast(at), bytes, Block); + Require(raw != nullptr && (at == nullptr || raw == at), "resident heap mirror: cannot allocate the guest heap"); + GuestArena::GuestArenaCommit_nid_postfix(raw, bytes, PAGE_READWRITE, bytes); + base = static_cast(raw); +#else + raw = mmap(at, bytes + Block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | (at != nullptr ? MAP_FIXED_NOREPLACE : 0), -1, 0); + Require(raw != MAP_FAILED && (at == nullptr || raw == at), "resident heap mirror: cannot map the guest heap"); + base = reinterpret_cast((reinterpret_cast(raw) + Block - 1) / Block * Block); +#endif + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(base, bytes); + GuestAllocations::Mutation mutation; + for (std::uint64_t range = 0; range < ranges; ++range) mutation.Add(base + range * rangeBytes, rangeBytes, true, writable, true); + } + + ~GuestHeap() { + { + GuestAllocations::Mutation mutation; + for (std::uint64_t range = 0; range < ranges; ++range) mutation.Remove(base + range * rangeBytes); + } + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(base, rangeBytes * ranges); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(raw, rangeBytes * ranges); + GuestArena::GuestArenaRelease_nid_postfix(raw, rangeBytes * ranges); +#else + munmap(raw, rangeBytes * ranges + Block); +#endif + } + + GuestHeap(const GuestHeap&) = delete; + GuestHeap& operator=(const GuestHeap&) = delete; + + std::uint8_t* Data() const { return base; } + void* Raw() const { return raw; } + std::uint64_t Address() const { return reinterpret_cast(base); } + + void Store(std::uint64_t offset, std::uint32_t value) const { std::memcpy(base + offset, &value, sizeof(value)); } + + std::uint32_t Load(std::uint64_t offset) const { + std::uint32_t value = 0; + std::memcpy(&value, base + offset, sizeof(value)); + return value; + } + +private: + std::uint64_t ranges = 1; + std::uint64_t rangeBytes = HeapBytes; + void* raw = nullptr; + std::uint8_t* base = nullptr; +}; + +struct ImportBudget { + explicit ImportBudget(std::uint64_t bytes) { AgcDriver::Graphics::SetHostImportBudget(bytes); } + ~ImportBudget() { AgcDriver::Graphics::SetHostImportBudget(0); } + ImportBudget(const ImportBudget&) = delete; + ImportBudget& operator=(const ImportBudget&) = delete; +}; + +std::array BufferDescriptor(std::uint64_t address, std::uint32_t bytes) { + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x31016facu}; +} + +std::string Hex(std::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +std::uint32_t Pattern(std::uint32_t thread) { + return 0x5a000000u + thread * 0x00010203u + 1u; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, const std::vector& userData) { + const std::array regions{{{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, regions}, + 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 Dispatch(AgcDriver::VulkanDevice& device, std::uint64_t read, std::uint64_t written, const OutputBlock& output) { + std::fill(output.Data(), output.Data() + Threads, 0xdeadbeefu); + std::vector userData(12, 0u); + userData[0] = static_cast(read); + userData[1] = static_cast(read >> 32u); + const auto out = BufferDescriptor(reinterpret_cast(output.Data()), static_cast(Threads * sizeof(std::uint32_t))); + const auto store = BufferDescriptor(written + StoreBlock, static_cast(Threads * sizeof(std::uint32_t))); + std::copy(out.begin(), out.end(), userData.begin() + 4); + std::copy(store.begin(), store.end(), userData.begin() + 8); + Run(device, Code, userData); +} + +void Dispatch(AgcDriver::VulkanDevice& device, const GuestHeap& read, const GuestHeap& written, const OutputBlock& output) { + Dispatch(device, read.Address(), written.Address(), output); +} + +void Copy(AgcDriver::VulkanDevice& device, std::uint64_t source, const OutputBlock& output) { + std::fill(output.Data(), output.Data() + Threads, 0xdeadbeefu); + std::vector userData(8, 0u); + const auto in = BufferDescriptor(source, static_cast(Threads * sizeof(std::uint32_t))); + const auto out = BufferDescriptor(reinterpret_cast(output.Data()), static_cast(Threads * sizeof(std::uint32_t))); + std::copy(in.begin(), in.end(), userData.begin()); + std::copy(out.begin(), out.end(), userData.begin() + 4); + Run(device, CopyCode, userData); +} + +void CheckReads(const OutputBlock& output, const std::string& step, bool late) { + for (std::uint32_t thread = 0; thread < Threads; ++thread) { + const auto expected = thread * ThreadStride < Block ? Pattern(thread) : late && thread == LateThread ? Pattern(thread) : 0u; + Require(output.Data()[thread] == expected, "resident heap mirror: " + step + ": thread " + std::to_string(thread) + " read " + Hex(output.Data()[thread]) + " at heap offset " + Hex(thread * ThreadStride) + ", expected " + Hex(expected)); + } +} + +void CheckStores(const GuestHeap& written, const std::string& step) { + for (std::uint32_t thread = 0; thread < Threads; ++thread) { + const auto value = written.Load(StoreBlock + thread * sizeof(std::uint32_t)); + Require(value == thread * 4u, "resident heap mirror: " + step + ": the GPU store of thread " + std::to_string(thread) + " into a never-written heap block reached the guest as " + Hex(value) + ", expected " + Hex(thread * 4u)); + } +} + +void CheckHeld(std::uint64_t before, std::uint64_t written, const std::string& step) { + const auto held = AgcDriver::Graphics::MirrorCounters().heapBytes - before; + Require(held == written, "resident heap mirror: " + step + ": the heap mirrors of two " + std::to_string(HeapBytes >> 10u) + " KiB heaps hold " + std::to_string(held >> 10u) + " KiB of device memory; the guest and the GPU wrote " + std::to_string(written >> 10u) + " KiB of them"); +} + +void ResidentTests(AgcDriver::VulkanDevice& device) { + GuestHeap read(false); + GuestHeap written(true); + const OutputBlock output; + for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) read.Store(thread * ThreadStride, Pattern(thread)); + written.Store(16, 0x77u); + const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + Block); + const auto before = AgcDriver::Graphics::MirrorCounters(); + Dispatch(device, read, written, output); + const auto made = AgcDriver::Graphics::MirrorCounters(); + Require(made.heapMirrors == before.heapMirrors + 2, "resident heap mirror: the two heaps were not heap mirrored (" + std::to_string(made.heapMirrors - before.heapMirrors) + " mirrors made)"); + CheckHeld(before.heapBytes, 3 * Block, "first dispatch"); + CheckReads(output, "first dispatch", false); + CheckStores(written, "first dispatch"); + Require(written.Load(16) == 0x77u, "resident heap mirror: the written heap's guest bytes changed"); + read.Store(LateThread * ThreadStride, Pattern(LateThread)); + std::memset(read.Data() + ZeroedBlock, 0, Block); + Dispatch(device, read, written, output); + CheckHeld(before.heapBytes, 4 * Block, "after a guest write into a never-written block and zeros written into another"); + CheckReads(output, "after a guest write into a never-written block", true); + const auto steady = AgcDriver::Graphics::MirrorCounters(); + Dispatch(device, read, written, output); + const auto again = AgcDriver::Graphics::MirrorCounters(); + Require(again.rebuilds == steady.rebuilds && again.heapBytes == steady.heapBytes, "resident heap mirror: an unchanged dispatch rebuilt or grew the heap mirrors"); + CheckReads(output, "unchanged dispatch", true); + CheckStores(written, "unchanged dispatch"); +} + +struct AddressSpaceLimit { + explicit AddressSpaceLimit(std::uint64_t bytes) { AgcDriver::Graphics::SetResidentAddressSpaceLimit(bytes); } + ~AddressSpaceLimit() { AgcDriver::Graphics::SetResidentAddressSpaceLimit(0); } + AddressSpaceLimit(const AddressSpaceLimit&) = delete; + AddressSpaceLimit& operator=(const AddressSpaceLimit&) = delete; +}; + +void CopiedTests(AgcDriver::VulkanDevice& device) { + const AddressSpaceLimit limit(1); + GuestHeap read(false); + GuestHeap written(true); + const OutputBlock output; + for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) read.Store(thread * ThreadStride, Pattern(thread)); + written.Store(16, 0x77u); + const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + Block); + const auto before = AgcDriver::Graphics::MirrorCounters(); + Dispatch(device, read, written, output); + const auto made = AgcDriver::Graphics::MirrorCounters(); + Require(made.heapMirrors == before.heapMirrors + 2, "copied heap mirror: the two heaps were not heap mirrored without sparse address space (" + std::to_string(made.heapMirrors - before.heapMirrors) + " mirrors made)"); + Require(made.heapBytes - before.heapBytes == 2 * HeapBytes, "copied heap mirror: two " + std::to_string(HeapBytes >> 10u) + " KiB heaps without sparse address space hold " + std::to_string((made.heapBytes - before.heapBytes) >> 10u) + " KiB, expected whole copies"); + CheckReads(output, "copied, first dispatch", false); + CheckStores(written, "copied, first dispatch"); + Require(written.Load(16) == 0x77u, "copied heap mirror: the written heap's guest bytes changed"); + read.Store(LateThread * ThreadStride, Pattern(LateThread)); + Dispatch(device, read, written, output); + CheckReads(output, "copied, after a guest write", true); + CheckStores(written, "copied, after a guest write"); +} + +struct ImportWindow { + explicit ImportWindow(std::uint64_t bytes) { AgcDriver::Graphics::SetHostImportRangeWindow(bytes); } + ~ImportWindow() { AgcDriver::Graphics::SetHostImportRangeWindow(std::uint64_t{64} << 20u); } + ImportWindow(const ImportWindow&) = delete; + ImportWindow& operator=(const ImportWindow&) = delete; +}; + +void PartialImportTests(AgcDriver::VulkanDevice& device, bool copied, bool readFirst) { + const AddressSpaceLimit limit(copied ? 1 : 0); + const ImportWindow window(2 * Block); + GuestHeap first(!readFirst); + GuestHeap second(readFirst); + const auto& read = readFirst ? first : second; + const auto& written = readFirst ? second : first; + const OutputBlock output; + const auto tail = HeapBytes - Block; + for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) read.Store(thread * ThreadStride, Pattern(thread)); + for (std::uint32_t thread = 0; thread < Threads; ++thread) { + read.Store(tail + thread * sizeof(std::uint32_t), Pattern(thread)); + written.Store(tail + thread * sizeof(std::uint32_t), Pattern(thread)); + } + const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + 3 * Block); + Dispatch(device, read, written, output); + CheckReads(output, "partial heap import", false); + const auto& selected = read.Address() < written.Address() ? read : written; + const auto* prefix = AgcDriver::Graphics::ExistingHostImportFor(device.graphicsContext(), selected.Address(), 2 * Block); + Require(prefix != nullptr && prefix->base == selected.Address() && prefix->bytes == 2 * Block, "partial heap import: the first registered heap has no exact two-block imported prefix"); + const auto held = AgcDriver::Graphics::HeapMirrorsWithin(selected.Address(), selected.Address() + HeapBytes); + const auto expected = copied ? HeapBytes - prefix->bytes : (selected.Address() == read.Address() ? Block : 2 * Block); + Require(held.mirrors == 1 && held.bytes == expected, "partial heap import: its mirror holds " + std::to_string(held.bytes) + " bytes, expected " + std::to_string(expected)); + Copy(device, selected.Address() + tail, output); + for (std::uint32_t thread = 0; thread < Threads; ++thread) Require(output.Data()[thread] == Pattern(thread), "partial heap import: GPU read of the mirrored tail lost its contents"); + selected.Store(tail + 2 * sizeof(std::uint32_t), 0x12345678u); + Copy(device, selected.Address() + tail, output); + for (std::uint32_t thread = 0; thread < Threads; ++thread) Require(output.Data()[thread] == (thread == 2 ? 0x12345678u : Pattern(thread)), "partial heap import: refresh lost a CPU write or its unchanged neighbors"); + const auto refreshed = AgcDriver::Graphics::HeapMirrorsWithin(selected.Address(), selected.Address() + HeapBytes); + Require(refreshed.mirrors == 1 && refreshed.bytes == expected, "partial heap import: refreshing the selected tail changed its accounted extent"); +} + +void ReusedAddressTests(AgcDriver::VulkanDevice& device) { + GuestHeap written(true); + const OutputBlock output; + const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + Block); + void* at = nullptr; + std::uint64_t address = 0; + { + GuestHeap first(false); + for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) first.Store(thread * ThreadStride, Pattern(thread)); + Dispatch(device, first, written, output); + CheckReads(output, "the first heap", false); + at = first.Raw(); + address = first.Address(); + } + GuestHeap second(false, 1, HeapBytes, at); + Require(second.Address() == address, "resident heap mirror: the second heap did not take the first heap's address"); + for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) second.Store(thread * ThreadStride, ~Pattern(thread)); + Dispatch(device, second, written, output); + for (std::uint32_t thread = 0; thread < Threads; ++thread) { + const auto expected = thread * ThreadStride < Block ? ~Pattern(thread) : 0u; + Require(output.Data()[thread] == expected, "resident heap mirror: a heap mapped where a mirrored heap was freed: thread " + std::to_string(thread) + " read " + Hex(output.Data()[thread]) + ", expected " + Hex(expected)); + } + const auto held = AgcDriver::Graphics::HeapMirrorsWithin(address, address + HeapBytes); + Require(held.mirrors == 1 && held.bytes == Block, "resident heap mirror: a heap mapped where a mirrored heap was freed holds " + std::to_string(held.mirrors) + " live heap mirrors (" + std::to_string(held.bytes >> 10u) + " KiB), expected 1 (64 KiB)"); +} + +void JoinedImportTests(AgcDriver::VulkanDevice& device) { + GuestHeap pair(true, 2, JoinedBytes); + const OutputBlock output; + const auto begin = pair.Address(); + const auto end = begin + 2 * JoinedBytes; + for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) pair.Store(thread * ThreadStride, Pattern(thread)); + pair.Store(JoinedBytes - sizeof(std::uint32_t), 0x41u); + pair.Store(JoinedBytes, 0x42u); + const auto before = AgcDriver::Graphics::HeapMirrorsWithin(begin, end); + { + const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + Block); + Dispatch(device, begin, begin + JoinedBytes, output); + CheckReads(output, "two adjacent heaps", false); + } + const auto mirrored = AgcDriver::Graphics::HeapMirrorsWithin(begin, end); + Require(before.mirrors == 0 && mirrored.mirrors == 2 && mirrored.bytes == 4 * Block, "resident heap mirror: two adjacent heaps hold " + std::to_string(before.mirrors) + " heap mirrors before the first dispatch and " + std::to_string(mirrored.mirrors) + " (" + std::to_string(mirrored.bytes >> 10u) + " KiB) after it, expected 0 and 2 (256 KiB); " + std::to_string(AgcDriver::Graphics::HostImportLiveBytes() >> 10u) + " KiB host imports live"); + const auto live = AgcDriver::Graphics::HostImportLiveBytes(); + Copy(device, begin + JoinedBytes - Threads * sizeof(std::uint32_t) / 2, output); + Require(output.Data()[Threads / 2 - 1] == 0x41u && output.Data()[Threads / 2] == 0x42u, "resident heap mirror: a view across two adjacent heaps read " + Hex(output.Data()[Threads / 2 - 1]) + " " + Hex(output.Data()[Threads / 2]) + ", expected 0x41 0x42"); + const auto joined = AgcDriver::Graphics::HostImportLiveBytes() - live; + Require(joined >= 2 * JoinedBytes, "resident heap mirror: a view across two adjacent heaps made no joined host import (" + std::to_string(joined >> 10u) + " KiB imported)"); + Dispatch(device, begin, begin + JoinedBytes, output); + CheckReads(output, "after the joined import", false); + const auto released = AgcDriver::Graphics::HeapMirrorsWithin(begin, end); + Require(released.mirrors == 0 && released.bytes == 0, "resident heap mirror: a joined host import over two heap-mirrored heaps left " + std::to_string(released.mirrors) + " of their " + std::to_string(mirrored.mirrors) + " heap mirrors and " + std::to_string(released.bytes >> 10u) + " of " + std::to_string(mirrored.bytes >> 10u) + " KiB held"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; +#ifdef _WIN32 + const bool watched = GuestArena::GuestArenaWriteWatched_nid_postfix(); +#else + const bool watched = GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix(); +#endif + if (!watched) { + std::puts("skipped, no guest write watch: heap mirrors need one"); + return VulkanTestSkipped; + } + ResidentTests(*device); + ReusedAddressTests(*device); + JoinedImportTests(*device); + CopiedTests(*device); + for (const bool copied : {false, true}) { + for (const bool readFirst : {false, true}) PartialImportTests(*device, copied, readFirst); + } + std::puts("resident heap mirror 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/SampleMultisampleTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp index 4d4f77cca1..5870d759ac 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp @@ -9,6 +9,9 @@ #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include "prx/libSceAgcDriver/Graphics/include/State.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "Recompiler.hpp" #include "Optimization/ResourceProgram.hpp" #include "VulkanTestDevice.hpp" @@ -17,6 +20,8 @@ #define NOMINMAX #endif #include +#else +#include #endif #include #include @@ -57,9 +62,13 @@ constexpr std::uint32_t Float16x4Info = 0x0730u; constexpr std::uint32_t MultisampledAttrib = (SampleShift << 12u) | (SampleShift << 15u); constexpr std::uint32_t TiledAttrib3 = 0x4dc6c000u; constexpr std::uint32_t Float32Export = 9; +constexpr std::uint32_t Uint16AbgrExport = 7; constexpr std::uint32_t Format16161616Float = 71; constexpr std::uint32_t Unorm8x4SwappedInfo = 0x8828u; +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 Type2DMsaa = 14; constexpr std::uint32_t TileRenderTarget = 0x1b; constexpr std::uint32_t TileZ64KBX = 0x18; @@ -74,6 +83,7 @@ constexpr std::uint32_t FillOnes = 0x3c003c00u; constexpr std::array Ones{1.0f, 1.0f, 1.0f, 1.0f}; constexpr std::array Written{0.75f, 0.25f, 0.5f, 1.0f}; constexpr std::uint32_t StoredSample = 1; +constexpr std::array PointMipSampler{0u, 0xfffu << 12u, 1u << 26u, 0u}; alignas(256) constexpr std::array VertexCode{ 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, @@ -86,6 +96,12 @@ constexpr std::array DrawnUnorm8{64.0f / 255.0f, 128.0f / 255.0f, 32.0 alignas(256) constexpr std::array Unorm8PixelCode{ 0x7e0802ff, std::bit_cast(DrawnUnorm8[0]), 0x7e0a02ff, std::bit_cast(DrawnUnorm8[1]), 0x7e0c02ff, std::bit_cast(DrawnUnorm8[2]), 0x7e0e02ff, std::bit_cast(DrawnUnorm8[3]), 0xf800180f, 0x07060504, 0xbf810000, }; +constexpr std::array DrawnUint8{0x12u, 0xbeu, 0x42u, 0x81u}; +alignas(256) constexpr std::array Uint8PixelCode{ + 0x7e0802ff, DrawnUint8[0] | (DrawnUint8[1] << 16u), 0x7e0a02ff, DrawnUint8[2] | (DrawnUint8[3] << 16u), 0xf8001c0f, 0x00000504, 0xbf810000, +}; +constexpr std::uint32_t FilledSint8 = 0x81beff12u; +constexpr std::array FilledSint8Texel{0x12u, 0xffffffffu, 0xffffffbeu, 0xffffff81u}; alignas(256) constexpr std::array LoadCode{ 0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c, @@ -101,6 +117,15 @@ alignas(256) constexpr std::array ArrayLoadCode{ 0x80001301, 0xbf810000, }; +alignas(256) constexpr std::array PlaneCode{ + 0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c, + 0x80000501, 0xe0301010, 0x80000601, 0xbf8c3f70, 0xf0001f08, 0x00011005, 0xf09c0f08, 0x00611402, + 0xf0900f08, 0x00611802, 0xbf8c3f70, 0xe0701030, 0x80001001, 0xe0701034, 0x80001101, 0xe0701038, + 0x80001201, 0xe070103c, 0x80001301, 0xe0701040, 0x80001401, 0xe0701044, 0x80001501, 0xe0701048, + 0x80001601, 0xe070104c, 0x80001701, 0xe0701050, 0x80001801, 0xe0701054, 0x80001901, 0xe0701058, + 0x80001a01, 0xe070105c, 0x80001b01, 0xbf810000, +}; + alignas(256) constexpr std::array StoreCode{ 0x34020087, 0xe030100c, 0x80000501, 0xe0301010, 0x80000601, 0xe0301014, 0x80000701, 0xe0301018, 0x80001001, 0xe030101c, 0x80001101, 0xe0301020, 0x80001201, 0xe0301024, 0x80001301, 0xbf8c3f70, @@ -120,14 +145,31 @@ constexpr std::array, 3> Triangle{{ alignas(256) std::array Buffer{}; struct Block { - explicit Block(std::size_t bytes) : bytes(bytes) { + explicit Block(std::size_t bytes, bool watched = false) : bytes(bytes), watched(watched) { #ifdef _WIN32 - data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + if (watched) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } #else - data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + if (watched) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the color target"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } #endif - Require(data != nullptr, "cannot allocate the multisampled color target"); + Require(data != nullptr, "cannot allocate the color target"); std::memset(data, 0, bytes); + Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched"); GuestAllocations::Mutation mutation; mutation.Add(data, bytes, true, true, true); } @@ -137,15 +179,26 @@ struct Block { mutation.Remove(data); } #ifdef _WIN32 - VirtualFree(data, 0, MEM_RELEASE); + if (watched) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } #else - std::free(data); + if (watched) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } #endif } Block(const Block&) = delete; Block& operator=(const Block&) = delete; std::uint64_t Address() const { return reinterpret_cast(data); } std::size_t bytes; + bool watched; std::uint8_t* data = nullptr; }; @@ -177,6 +230,21 @@ AgcDriver::Graphics::ColorTarget DecodeSwappedTarget(const Block& block) { return color; } +AgcDriver::Graphics::ColorTarget DecodeUintTarget(const Block& block) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(block.Address() >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Uint8x4Info; + cx[0x31d] = MultisampledAttrib; + cx[0x3b0] = ((Side - 1u) << 14u) | (Side - 1u); + cx[0x3b8] = TiledAttrib3; + cx[0x390] = static_cast(block.Address() >> 40u); + auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + Require(color.format == VK_FORMAT_R8G8B8A8_UINT && color.samples == Samples && color.extent.width == Side && color.extent.height == Side, "the 8_8_8_8_UINT target did not decode as a 16x16 RGBA8 UINT surface of 4 samples"); + color.uintExport = true; + return color; +} + void CheckSwappedSamples(const std::vector>& loaded) { const std::array swapped{DrawnUnorm8[2], DrawnUnorm8[1], DrawnUnorm8[0], DrawnUnorm8[3]}; const auto codes = [](const std::array& texel) { @@ -196,6 +264,19 @@ void CheckSwappedSamples(const std::vector>& loaded) { Require(drawn != 0, "no sample of the BGRA8 target holds the drawn color"); } +void CheckUintSamples(const std::vector>& loaded) { + std::size_t drawn = 0; + for (const auto& texel : loaded) { + const auto words = std::bit_cast>(texel); + if (words == std::array{}) continue; + char text[128]; + std::snprintf(text, sizeof(text), "a sample of the 8_8_8_8_UINT target reads %08x %08x %08x %08x instead of the drawn color", words[0], words[1], words[2], words[3]); + Require(words == DrawnUint8, text); + ++drawn; + } + Require(drawn != 0, "no sample of the 8_8_8_8_UINT target holds the drawn color"); +} + AgcDriver::Graphics::State TargetState(const AgcDriver::Graphics::ColorTarget& color) { VkPipelineColorBlendAttachmentState blend{}; blend.colorWriteMask = 0xf; @@ -214,7 +295,7 @@ AgcDriver::Graphics::State TargetState(const AgcDriver::Graphics::ColorTarget& c state.blends = {blend}; state.blend = blend; state.blendConstants = {}; - state.samples = Samples; + state.samples = color.samples; return state; } @@ -236,7 +317,7 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::State& sta const auto vertexResult = ShaderRecompiler::Recompile(vertex); const auto vertexPush = static_cast(vertexResult.pushConstants.size()); const auto codeAddress = reinterpret_cast(pixelCode.data()); - const AgcDriver::Registers context{{0x1b6, 0x8000}, {0x1b3, 0}, {0x1b4, 0}, {0x203, 0}, {0x1c5, Float32Export}}; + const AgcDriver::Registers context{{0x1b6, 0x8000}, {0x1b3, 0}, {0x1b4, 0}, {0x203, 0}, {0x1c5, state.color.uintExport ? Uint16AbgrExport : Float32Export}}; const std::array pixelMemory{{{codeAddress, std::as_bytes(pixelCode)}}}; ShaderRecompiler::RecompileRequest fragment{ {ShaderStage::Fragment, codeAddress, pixelCode, 0, {}}, @@ -274,12 +355,13 @@ std::array TargetDescriptor(const Block& block, std::uint32_t }; } -ShaderRecompiler::RecompileResult CompileProgram(AgcDriver::VulkanDevice& device, std::span code, const std::array& texture, std::vector& userData) { +ShaderRecompiler::RecompileResult CompileProgram(AgcDriver::VulkanDevice& device, std::span code, const std::array& texture, std::vector& userData, const std::array& sampler = {}) { const auto address = reinterpret_cast(Buffer.data()); const std::array buffer{static_cast(address), static_cast((address >> 32u) & 0xffffu), static_cast(Buffer.size() * 4u), 0x31016facu}; userData.assign(16, 0u); 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{ @@ -349,19 +431,19 @@ void RequireStoreRefused(AgcDriver::VulkanDevice& device, const std::array> RunLoad(AgcDriver::VulkanDevice& device, const std::array& texture, bool arrayed) { +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); - std::vector> loaded(Side * Side * Samples); + 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) { Buffer.fill(0xdeadbeefu); for (std::uint32_t tid = 0; tid < Threads; ++tid) { const auto index = first + tid; - const auto pixel = index / Samples; + const auto pixel = index / fragments; const std::array words = arrayed - ? std::array{0u, 0u, pixel % Side, pixel / Side, 0u, index % Samples} - : std::array{0u, 0u, 0u, pixel % Side, pixel / Side, index % Samples}; + ? std::array{0u, 0u, pixel % Side, pixel / Side, 0u, index % fragments} + : std::array{0u, 0u, 0u, pixel % Side, pixel / Side, index % fragments}; std::copy(words.begin(), words.end(), &Buffer[tid * Words]); } device.Dispatch(program, 1, 1, 1, {}, codeAddress); @@ -373,9 +455,9 @@ std::vector> RunLoad(AgcDriver::VulkanDevice& device, const return loaded; } -std::vector> LoadSamples(AgcDriver::VulkanDevice& device, const std::array& texture) { - auto loaded = RunLoad(device, texture, false); - const auto loadedArray = RunLoad(device, texture, true); +std::vector> LoadSamples(AgcDriver::VulkanDevice& device, const std::array& texture, std::uint32_t fragments = Samples) { + auto loaded = RunLoad(device, texture, false, fragments); + const auto loadedArray = RunLoad(device, texture, true, fragments); Require(loadedArray == loaded, "Dim2DMsaaArray image_load disagreed with Dim2DMsaa image_load on the multisampled color target"); return loaded; } @@ -384,6 +466,37 @@ std::vector> LoadSamples(AgcDriver::VulkanDevice& device, c return LoadSamples(device, TargetDescriptor(block, SampleShift, format, tileMode)); } +void CheckFirstSampleReads(AgcDriver::VulkanDevice& device, const std::array& texture, const std::vector>& samples, bool distinct, const std::string& what) { + std::vector userData; + const auto program = CompileProgram(device, PlaneCode, texture, userData, PointMipSampler); + const auto codeAddress = reinterpret_cast(PlaneCode.data()); + const auto bits = [&](std::size_t index) { return std::bit_cast>(samples[index]); }; + bool differs = false; + for (std::uint32_t first = 0; first < Side * Side; first += Threads) { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto pixel = first + tid; + const std::array words{std::bit_cast((pixel % Side + 0.5f) / Side), std::bit_cast((pixel / Side + 0.5f) / Side), std::bit_cast(1.0f), pixel % Side, pixel / Side}; + std::copy(words.begin(), words.end(), &Buffer[tid * Words]); + } + device.Dispatch(program, 1, 1, 1, {}, codeAddress); + device.WaitIdle(); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto pixel = first + tid; + const auto sampleZero = bits(pixel * Samples); + for (std::uint32_t sample = 1; sample < Samples; ++sample) differs = differs || bits(pixel * Samples + sample) != sampleZero; + for (std::uint32_t read = 0; read < 3u; ++read) { + std::array texel{}; + std::copy_n(&Buffer[tid * Words + LoadResult + read * 4u], texel.size(), texel.begin()); + char where[200]; + std::snprintf(where, sizeof(where), "%s of pixel (%u, %u) read %08x %08x %08x %08x, sample 0 is %08x %08x %08x %08x", read == 0u ? "a DIM 2D image_load" : read == 1u ? "a DIM 2D image_sample_lz" : "a DIM 2D image_sample_l at LOD 1", pixel % Side, pixel / Side, texel[0], texel[1], texel[2], texel[3], sampleZero[0], sampleZero[1], sampleZero[2], sampleZero[3]); + Require(texel == sampleZero, std::string(where) + " " + what); + } + } + } + Require(!distinct || differs, "no pixel's sample 0 differs from its other samples " + what); +} + std::array CompressedDescriptor(const Block& block, std::uint32_t tileMode, std::uint64_t htile) { auto texture = TargetDescriptor(block, SampleShift, Format16161616Float, tileMode); const auto meta = htile >> 8u; @@ -643,23 +756,255 @@ int NativeVertexInput(std::unique_ptr& device) { return 0; } +alignas(256) constexpr std::array NativeFmaskCode{0x34020084u, 0xe0381000u, 0x80000201u, 0xbf8c3f70u, 0xf0001f08u, 0x00020602u, 0xbf8c3f70u, 0xe0781000u, 0x80010601u, 0xbf810000u}; +alignas(256) std::array NativeFmaskInput{}; +alignas(256) std::array NativeFmaskOutput{}; + +std::array NativeFmaskDescriptor(const Block& fmask, const Block& cmask, std::uint32_t pipeXor) { + const auto address = fmask.Address() | pipeXor; + const auto metadata = cmask.Address(); + return {static_cast(address >> 8u), static_cast(address >> 40u) | (166u << 20u) | (((Side - 1u) & 3u) << 30u), ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), 4u | (1u << 3u) | (4u << 6u) | (24u << 20u) | (9u << 28u), 0u, 0u, (1u << 19u) | (1u << 21u) | (static_cast(metadata >> 8u) << 24u), static_cast(metadata >> 16u)}; +} + +ShaderRecompiler::RecompileResult CompileNativeFmask(AgcDriver::VulkanDevice& device, const std::array& image, bool registration) { + std::vector userData(16u); + const auto buffer = [](const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return std::array{static_cast(address), static_cast(address >> 32u) & 0xffffu, bytes, 0x31016facu}; + }; + const auto input = buffer(NativeFmaskInput.data(), sizeof(NativeFmaskInput)); + const auto output = buffer(NativeFmaskOutput.data(), Threads * 16u); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4u); + std::copy(image.begin(), image.end(), userData.begin() + 8u); + const std::array memory{{{reinterpret_cast(NativeFmaskCode.data()), std::as_bytes(std::span(NativeFmaskCode))}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + auto target = device.Target(); + target.registrationModules = registration; + ShaderRecompiler::RecompileRequest request{{ShaderStage::Compute, reinterpret_cast(NativeFmaskCode.data()), NativeFmaskCode, 0u, {}}, {32u, 0u, userData, compute, std::nullopt, std::nullopt, memory}, target, {0u, 0u, 0u, 128u}}; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void DispatchNativeFmask(AgcDriver::VulkanDevice& device, const std::array& image, bool registration) { + device.Dispatch(CompileNativeFmask(device, image, registration), 1u, 1u, 1u); + device.WaitIdle(); +} + +void NativeFmask(std::unique_ptr& device) { + using namespace AgcDriver::Graphics; + Block colorMemory(4u * BlockBytes, true); + Block dcc(BlockBytes, true); + Block cmask(BlockBytes, true); + Block fmask(BlockBytes, true); + Block unrelated(BlockBytes, true); + struct ReleaseDevice { + std::unique_ptr& value; + ~ReleaseDevice() { value.reset(); } + } releaseDevice{device}; + constexpr std::uint32_t sentinel = 0xdeadbeefu; + for (const auto pipeXor : {0u, 0xf00u}) { + std::memset(colorMemory.data, 0, colorMemory.bytes); + std::memset(dcc.data, 0, dcc.bytes); + std::memset(cmask.data, 0xcc, cmask.bytes); + std::memset(fmask.data, 0x11, fmask.bytes); + AgcDriver::Registers cx{{0x318, static_cast(colorMemory.Address() >> 8u)}, {0x31b, 0u}, {0x31c, Float16x4Info | 0x10006000u}, {0x31d, (3u << 12u) | (3u << 15u)}, {0x31e, 0u}, {0x31f, static_cast(cmask.Address() >> 8u)}, {0x321, static_cast((fmask.Address() | pipeXor) >> 8u)}, {0x325, static_cast(dcc.Address() >> 8u)}, {0x390, static_cast(colorMemory.Address() >> 40u)}, {0x398, static_cast(cmask.Address() >> 40u)}, {0x3a0, static_cast(fmask.Address() >> 40u)}, {0x3a8, static_cast(dcc.Address() >> 40u)}, {0x3b0, ((Side - 1u) << 14u) | (Side - 1u)}, {0x3b8, TiledAttrib3}}; + const auto color = DecodeColorBuffer(cx, 0u); + Require(color.samples == 8u && color.cmaskAddress == cmask.Address(), "native FMASK fixture lost its decoded target association"); + std::array, 8> expected{}; + for (std::uint32_t sample = 0u; sample < 8u; ++sample) { + expected[sample] = {static_cast(sample + 1u) / 8.0f, 0.25f, 0.5f, 1.0f}; + auto pixel = PixelCode; + pixel[1] = std::bit_cast(expected[sample][0]); + pixel[3] = std::bit_cast(expected[sample][1]); + pixel[5] = std::bit_cast(expected[sample][2]); + pixel[7] = std::bit_cast(expected[sample][3]); + auto state = TargetState(color); + state.quadSampleMask.fill(static_cast(1u << sample)); + Draw(*device, state, 0u, 2u, pixel); + } + const auto native = LoadSamples(*device, TargetDescriptor(colorMemory, 3u), 8u); + std::vector covered; + for (std::uint32_t pixel = 0; pixel < Side * Side; ++pixel) { + bool all = true; + for (std::size_t sample = 0; sample < 8u; ++sample) all = all && native[pixel * 8u + sample] == expected[sample]; + if (all) covered.push_back(pixel); + } + Require(!covered.empty(), "native FMASK fixture did not render eight distinct sample colors"); + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + const auto pixel = covered[lane % covered.size()]; + NativeFmaskInput[lane * 4u] = pixel % Side; + NativeFmaskInput[lane * 4u + 1u] = pixel / Side; + NativeFmaskInput[lane * 4u + 2u] = 0u; + NativeFmaskInput[lane * 4u + 3u] = 0u; + } + const auto image = NativeFmaskDescriptor(fmask, cmask, pipeXor); + const auto check = [&] { + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + const std::array mapping{0x76543210u, 1u, 0x76543210u, 0u}; + for (std::size_t component = 0; component < 4u; ++component) Require(NativeFmaskOutput[lane * 4u + component] == mapping[component], "native FMASK load did not preserve its expanded sample indices and selectors"); + for (std::uint32_t sample = 0; sample < 8u; ++sample) { + const auto fragment = NativeFmaskOutput[lane * 4u] >> (4u * sample) & 15u; + const auto pixel = NativeFmaskInput[lane * 4u + 1u] * Side + NativeFmaskInput[lane * 4u]; + Require(native[pixel * 8u + fragment] == expected[sample], "native FMASK remapped a sampled color to another fragment"); + } + } + Require(std::all_of(NativeFmaskOutput.begin() + Threads * 4u, NativeFmaskOutput.end(), [](auto word) { return word == sentinel; }), "native FMASK wrote beyond its output"); + }; + for (const auto registration : {false, true}) { + for (std::uint32_t repeat = 0; repeat < 2u; ++repeat) { + NativeFmaskOutput.fill(sentinel); + DispatchNativeFmask(*device, image, registration); + check(); + } + const auto program = CompileNativeFmask(*device, image, registration); + const auto codeAddress = reinterpret_cast(NativeFmaskCode.data()); + std::shared_ptr recipe; + NativeFmaskOutput.fill(sentinel); + device->Dispatch(program, 1u, 1u, 1u, {}, codeAddress, nullptr, &recipe); + device->WaitIdle(); + check(); + Require(recipe == nullptr, "native FMASK emitted a reusable recipe without an ownership proof"); + const auto prepared = device->PrepareDispatch(program, {}, Threads, codeAddress); + Require(prepared != nullptr, "native FMASK did not prepare a deferred dispatch"); + NativeFmaskOutput.fill(sentinel); + device->Dispatch(program, 1u, 1u, 1u, {}, codeAddress, prepared); + device->WaitIdle(); + check(); + } + Require(LoadSamples(*device, TargetDescriptor(colorMemory, 3u), 8u) == native, "FMASK sampling changed the native samples"); + Require(std::all_of(fmask.data, fmask.data + fmask.bytes, [](auto byte) { return byte == 0x11u; }) && std::all_of(cmask.data, cmask.data + cmask.bytes, [](auto byte) { return byte == 0xccu; }), "native FMASK sampling published or changed guest metadata"); + const auto refused = [&](const auto& descriptor, std::string_view reason) { + NativeFmaskOutput.fill(sentinel); + std::string error; + try { DispatchNativeFmask(*device, descriptor, false); } catch (const std::runtime_error& e) { error = e.what(); } + Require(error.find(reason) != std::string::npos, "native FMASK missing refusal: " + error); + Require(std::all_of(NativeFmaskOutput.begin(), NativeFmaskOutput.end(), [sentinel](auto word) { return word == sentinel; }), "rejected native FMASK wrote output"); + }; + refused(NativeFmaskDescriptor(unrelated, cmask, pipeXor), "descriptor does not match"); + refused(NativeFmaskDescriptor(fmask, unrelated, pipeXor), "descriptor does not match"); + refused(NativeFmaskDescriptor(fmask, cmask, pipeXor ^ 0x100u), "descriptor does not match"); + auto resized = image; + resized[2] += 1u; + refused(resized, "extent or subresource does not match"); + const auto deferredProgram = CompileNativeFmask(*device, image, false); + const auto codeAddress = reinterpret_cast(NativeFmaskCode.data()); + const auto preparedBeforeStore = device->PrepareDispatch(deferredProgram, {}, Threads, codeAddress); + Require(preparedBeforeStore != nullptr, "native FMASK did not prepare its metadata invalidation probe"); + std::array same{}; + same.fill(std::byte{0x11}); + AgcDriver::GuestMemory::Write(fmask.Address(), same); + refused(image, "writes to a resident native FMASK"); + NativeFmaskOutput.fill(sentinel); + std::string deferredError; + try { + device->Dispatch(deferredProgram, 1u, 1u, 1u, {}, codeAddress, preparedBeforeStore); + device->WaitIdle(); + } catch (const std::runtime_error& error) { + deferredError = error.what(); + } + Require(deferredError.find("writes to a resident native FMASK") != std::string::npos, "deferred native FMASK missed its metadata store: " + deferredError); + Require(std::all_of(NativeFmaskOutput.begin(), NativeFmaskOutput.end(), [](auto word) { return word == sentinel; }), "rejected deferred native FMASK wrote output"); + device->SubmitRecorded(true); + device->WaitIdle(); + ClearMultisampleTargets(device->Device()); + ClearCachedTextures(device->Device()); + cx[0x31c] &= ~0x2000u; + cx[0x31f] = 0u; + cx[0x398] = 0u; + const auto withoutCmask = DecodeColorBuffer(cx, 0u); + Require(withoutCmask.cmaskAddress == 0u, "the no-CMASK control retained metadata"); + Draw(*device, TargetState(withoutCmask)); + const auto uncompressed = LoadSamples(*device, TargetDescriptor(colorMemory, 3u), 8u); + Require(std::find(uncompressed.begin(), uncompressed.end(), Drawn) != uncompressed.end(), "tracking native FMASK broke ordinary DCC rendering without CMASK"); + device->SubmitRecorded(true); + device->WaitIdle(); + ClearMultisampleTargets(device->Device()); + ClearCachedTextures(device->Device()); + Draw(*device, TargetState(color)); + auto recreated = color; + recreated.pipeBankXor ^= 0x4000u; + recreated.fmaskAddress = 0u; + recreated.fmaskPipeBankXor = 0u; + Draw(*device, TargetState(recreated)); + std::array formerFmask{}; + formerFmask.fill(std::byte{0x22}); + AgcDriver::GuestMemory::Write(fmask.Address(), formerFmask); + Draw(*device, TargetState(recreated)); + auto recreatedDescriptor = TargetDescriptor(colorMemory, 3u); + recreatedDescriptor[0] |= recreated.pipeBankXor >> 8u; + const auto recreatedSamples = LoadSamples(*device, recreatedDescriptor, 8u); + Require(std::find(recreatedSamples.begin(), recreatedSamples.end(), Drawn) != recreatedSamples.end(), "former FMASK metadata affected the recreated native target"); + device->SubmitRecorded(true); + device->WaitIdle(); + ClearMultisampleTargets(device->Device()); + ClearCachedTextures(device->Device()); + std::printf("native FMASK: XOR=0x%x, eight distinct samples, both registration modes, cached reuse, association/extent/write refusals and unchanged guest metadata passed\n", pipeXor); + } +} + } int main(int argc, char** argv) { try { auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; + if (argc == 2 && std::string_view(argv[1]) == "--native-fmask") { + { + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device); + if (!context.storageImageMultisample || (context.limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_8_BIT) == 0) { + std::puts("native FMASK skipped, the device does not render or store 8-sample color"); + return VulkanTestSkipped; + } + } + NativeFmask(device); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--native-vertex-input") return NativeVertexInput(device); Block block(4u * BlockBytes); Block other(BlockBytes); const auto color = DecodeTarget(block); const auto state = TargetState(color); + Draw(*device, state); + const auto drawn = LoadSamples(*device, block); + CheckSamples(state, drawn, {}); + CheckFirstSampleReads(*device, TargetDescriptor(block, SampleShift), drawn, true, "from the drawn 4x target"); + Draw(*device, state, 0, 2u, Unorm8PixelCode); + const auto redrawn = LoadSamples(*device, block); + Require(redrawn != drawn, "drawing the 4x target again with another color left its samples unchanged"); + CheckFirstSampleReads(*device, TargetDescriptor(block, SampleShift), redrawn, true, "from the 4x target drawn again with another color after a first read"); + Block zeroed(BlockBytes); + CheckFirstSampleReads(*device, TargetDescriptor(zeroed, SampleShift), std::vector>(Side * Side * Samples), false, "from zeroed color memory no draw rendered to"); + Block aliased(4u * BlockBytes); + Draw(*device, TargetState(DecodeSwappedTarget(aliased)), 0, 2u, Unorm8PixelCode); + Draw(*device, TargetState(DecodeTarget(aliased))); + const auto aliasedSamples = LoadSamples(*device, aliased); + CheckSamples(state, aliasedSamples, {}); + CheckFirstSampleReads(*device, TargetDescriptor(aliased, SampleShift), aliasedSamples, true, "from a 4x RGBA16F target drawn after a BGRA8 target at the same address"); + std::puts("DIM 2D image_load, image_sample_lz and image_sample_l reads of a 4x color target return each pixel's sample 0"); + Block swapped(4u * BlockBytes); + Draw(*device, TargetState(DecodeSwappedTarget(swapped)), 0, 2u, Unorm8PixelCode); + const auto swappedSamples = LoadSamples(*device, swapped, Format8888Unorm); + 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 integer(4u * BlockBytes); + Block signedFill(BlockBytes); + if ((AgcDriver::VulkanDeviceTestAccess::ContextOf(*device).limits.sampledImageIntegerSampleCounts & VK_SAMPLE_COUNT_4_BIT) == 0) { + std::puts("DIM 2D reads of 4x 8_8_8_8_UINT and 8_8_8_8_SINT targets not tested: sampledImageIntegerSampleCounts lacks VK_SAMPLE_COUNT_4_BIT"); + } else { + Draw(*device, TargetState(DecodeUintTarget(integer)), 0, 2u, Uint8PixelCode); + const auto integerSamples = LoadSamples(*device, integer, Format8888Uint); + CheckUintSamples(integerSamples); + CheckFirstSampleReads(*device, TargetDescriptor(integer, SampleShift, Format8888Uint), integerSamples, true, "from a drawn 4x 8_8_8_8_UINT target"); + StoreWords(signedFill, BlockBytes, FilledSint8); + CheckFirstSampleReads(*device, TargetDescriptor(signedFill, SampleShift, Format8888Sint), std::vector>(Side * Side * Samples, std::bit_cast>(FilledSint8Texel)), false, "from 8_8_8_8_SINT color memory no draw rendered to"); + std::puts("DIM 2D reads of a drawn 4x 8_8_8_8_UINT target and of 8_8_8_8_SINT color memory no draw rendered to return each pixel's sample 0 bit for bit"); + } if (!AgcDriver::VulkanDeviceTestAccess::ContextOf(*device).storageImageMultisample) { - std::puts("multisampled target sampling not tested: storage image multisampling unavailable"); + std::puts("multisampled target fills, stores and swizzle modes not tested: storage image multisampling unavailable"); return VulkanTestSkipped; } - Draw(*device, state); - CheckSamples(state, LoadSamples(*device, block), {}); const auto colorBytes = AgcDriver::Graphics::MultisampledColorBytes(color); Require(colorBytes != 0 && colorBytes <= block.bytes, "the 4x target's color memory does not fit its block"); AgcDriver::Graphics::NoteColorMetadataFill(block.Address(), static_cast(colorBytes), FillOnes); @@ -670,6 +1015,20 @@ int main(int argc, char** argv) { Block stored(BlockBytes); StoreWords(stored, colorBytes, FillOnes); CheckUniform(LoadSamples(*device, stored), Ones, "from color memory of half-float ones no draw rendered to"); + Block reused(BlockBytes); + StoreWords(reused, colorBytes, FillOnes); + CheckUniform(LoadSamples(*device, reused), Ones, "from color memory of half-float ones no draw rendered to, before the title reuses it"); + AgcDriver::Graphics::RequireNoMultisampleColorTarget(reused.Address(), static_cast(colorBytes / 2u), "a storage image access"); + StoreWords(reused, colorBytes, 0u); + CheckUniform(LoadSamples(*device, reused), {}, "from color memory no draw rendered to after the title reused it for zeros"); + std::string drawnRefusal; + try { + AgcDriver::Graphics::RequireNoMultisampleColorTarget(block.Address(), static_cast(colorBytes / 2u), "a storage image access"); + } catch (const std::exception& error) { + drawnRefusal = error.what(); + } + Require(drawnRefusal.find("whose samples are only on the host") != std::string::npos, "a guest access over a drawn 4x target is not refused"); + std::puts("a guest access over a 4x texture no draw rendered to retires it, so a later load reads the memory's new value, and a guest access over a drawn target is still refused"); const auto storedState = TargetState(DecodeTarget(stored)); Draw(*device, storedState); CheckSamples(storedState, LoadSamples(*device, stored), Ones); @@ -686,6 +1045,23 @@ int main(int argc, char** argv) { Block undrawn(BlockBytes); StoreSamples(*device, TargetDescriptor(undrawn, SampleShift), SampleWrites(leftHalf)); CheckWritten(std::vector>(Side * Side * Samples), LoadSamples(*device, undrawn), leftHalf, "into zeroed color memory no draw rendered to"); + const auto refusedOver = [&](const Block& over) { + try { + AgcDriver::Graphics::RequireNoMultisampleColorTarget(over.Address(), static_cast(colorBytes / 2u), "a storage image access"); + } catch (const std::exception& error) { + return std::string(error.what()).find("whose samples are only on the host") != std::string::npos; + } + return false; + }; + Require(refusedOver(undrawn), "a guest access over a 4x texture no draw rendered to but a dispatch wrote is not refused"); + Block absorbing(BlockBytes); + StoreWords(absorbing, colorBytes, FillOnes); + CheckUniform(LoadSamples(*device, absorbing), Ones, "from color memory of half-float ones no draw rendered to, before a fill the texture absorbs"); + Require(AgcDriver::Graphics::MultisampledColorFillAbsorbs(absorbing.Address(), static_cast(colorBytes), 0u), "a fill of 0 over the whole color memory of a 4x texture no draw rendered to is not absorbed"); + AgcDriver::Graphics::NoteColorMetadataFill(absorbing.Address(), static_cast(colorBytes), 0u); + Require(refusedOver(absorbing), "a guest access over a 4x texture no draw rendered to that absorbed a fill is not refused"); + CheckUniform(LoadSamples(*device, absorbing), {}, "from a 4x texture no draw rendered to after it absorbed a fill of 0"); + std::puts("a guest access over a 4x texture no draw rendered to is still refused once a dispatch wrote it or it absorbed a fill"); Block mixedStore(BlockBytes); StoreWords(mixedStore, colorBytes, FillOnes); std::memcpy(mixedStore.data + colorBytes - sizeof(odd), &odd, sizeof(odd)); @@ -722,10 +1098,6 @@ int main(int argc, char** argv) { StoreWords(standard, colorBytes, FillOnes); RequireRefused(*device, TargetDescriptor(standard, SampleShift, Format16161616Float, TileS64KBX), "has no modeled multisampled color footprint", "loading an S swizzle multisampled texture no draw rendered to"); std::puts("every sample of a 4x color target loaded through its multisampled texture descriptor matches the triangle drawn into it, then the fill of its color memory; a surface no draw rendered to loads the value its color memory repeats and keeps it under a later draw"); - Block swapped(4u * BlockBytes); - Draw(*device, TargetState(DecodeSwappedTarget(swapped)), 0, 2u, Unorm8PixelCode); - CheckSwappedSamples(LoadSamples(*device, swapped, Format8888Unorm)); - std::puts("a 4x BGRA8 color target loaded through an RGBA8 texture descriptor reads its drawn color with red and blue swapped"); Block destination(BlockBytes); struct ReleaseIndexDevice { std::unique_ptr& device; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SampleOrder.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SampleOrder.cpp index 9e534056ab..ba9346b3e7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SampleOrder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SampleOrder.cpp @@ -1122,6 +1122,22 @@ Samples ReadTarget(const Context& context, VkImageView view, bool color, std::ui return ReadValues(values, count); } +Samples ReadDepth(const Context& context, const AgcDriver::Graphics::DepthTarget& depth, std::uint32_t samples) { + const auto target = AgcDriver::Graphics::DepthSurfaceOrderTarget(context, depth); + const VkImageAspectFlags aspects = VK_IMAGE_ASPECT_DEPTH_BIT | (AgcDriver::Graphics::HasStencil(target.format) ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u); + Image copy(context, target.format, samples, ReferenceExtent, DepthUsage, 0u); + VkImageCopy region{}; + region.srcSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0u, 0u, 1u}; + region.dstSubresource = region.srcSubresource; + region.extent = {ReferenceExtent.width, ReferenceExtent.height, 1u}; + Submit(context, [&](VkCommandBuffer commands) { + copy.ToGeneral(commands, aspects); + FullBarrier(context, commands); + context.Function("vkCmdCopyImage")(commands, target.image, VK_IMAGE_LAYOUT_GENERAL, copy.Handle(), VK_IMAGE_LAYOUT_GENERAL, 1u, ®ion); + }); + return ReadTarget(context, copy.View(target.format, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_USAGE_SAMPLED_BIT), false, samples); +} + Rendered RenderCheckerboard(AgcDriver::VulkanDevice& device, const Context& context, std::uint32_t samples, std::size_t run, const std::optional>& locations) { AgcDriver::Graphics::ColorTarget color{}; color.extent = ReferenceExtent; @@ -1169,7 +1185,7 @@ Rendered RenderCheckerboard(AgcDriver::VulkanDevice& device, const Context& cont Settle(device); Rendered rendered; rendered.color = ReadTarget(context, AgcDriver::Graphics::MultisampleTargetView(context, color), true, samples); - rendered.depth = ReadTarget(context, AgcDriver::Graphics::DepthSurfaceOrderTarget(context, depth).view, false, samples); + rendered.depth = ReadDepth(context, depth, samples); return rendered; } @@ -1348,7 +1364,7 @@ void CheckRecipeDepth(AgcDriver::VulkanDevice& device, const Context& context, c std::printf("%s: builds no recipe, since a draw that reorders its samples is built in full each time, with its sample swaps\n", what.c_str()); return; } - const auto read = [&] { return ReadTarget(context, AgcDriver::Graphics::DepthSurfaceOrderTarget(context, depth).view, false, samples); }; + const auto read = [&] { return ReadDepth(context, depth, samples); }; Samples recorded; slopes.beforeReplay = [&] { Settle(device); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp index 46e3184ea6..6cfb074620 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp @@ -1,10 +1,12 @@ #include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp" #include "prx/libSceAgcDriver/Graphics/include/PlaceholderImages.hpp" @@ -281,38 +283,38 @@ private: class WatchedSurface { public: - explicit WatchedSurface(const Context& context) : context(context) { + explicit WatchedSurface(const Context& context, std::size_t bytes = SurfaceBytes) : context(context), span(bytes) { #ifdef _WIN32 - memory = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, 65536)); - if (memory != nullptr) GuestArena::GuestArenaCommit_nid_postfix(memory, SurfaceBytes, PAGE_READWRITE, SurfaceBytes); + memory = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, 65536)); + if (memory != nullptr) GuestArena::GuestArenaCommit_nid_postfix(memory, bytes, PAGE_READWRITE, bytes); #else constexpr std::uintptr_t alignment = 65536; - void* mapped = mmap(nullptr, SurfaceBytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + void* mapped = mmap(nullptr, bytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mapped != MAP_FAILED) { const auto begin = reinterpret_cast(mapped); const auto aligned = (begin + alignment - 1) & ~(alignment - 1); if (aligned != begin) munmap(mapped, aligned - begin); - if (aligned + SurfaceBytes != begin + SurfaceBytes + alignment) munmap(reinterpret_cast(aligned + SurfaceBytes), begin + alignment - aligned); + if (aligned + bytes != begin + bytes + alignment) munmap(reinterpret_cast(aligned + bytes), begin + alignment - aligned); memory = reinterpret_cast(aligned); - GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(memory, SurfaceBytes); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(memory, bytes); } #endif Require(memory != nullptr, "cannot allocate the watched surface"); - Require(AgcDriver::GuestMemory::Watched(Address(), SurfaceBytes), "the watched surface is not write-watched"); - std::memset(memory, 0x5a, SurfaceBytes); - GuestAllocations::Mutation().Add(memory, SurfaceBytes, true, true, true); - AgcDriver::GuestMemory::CollectWritesUncached(Address(), SurfaceBytes); + Require(AgcDriver::GuestMemory::Watched(Address(), bytes), "the watched surface is not write-watched"); + std::memset(memory, 0x5a, bytes); + GuestAllocations::Mutation().Add(memory, bytes, true, true, true); + AgcDriver::GuestMemory::CollectWritesUncached(Address(), bytes); } ~WatchedSurface() { ClearCachedTextures(context.device); GuestAllocations::Mutation().Remove(memory); #ifdef _WIN32 - GuestArena::GuestArenaReset_nid_postfix(memory, SurfaceBytes); - GuestArena::GuestArenaRelease_nid_postfix(memory, SurfaceBytes); + GuestArena::GuestArenaReset_nid_postfix(memory, span); + GuestArena::GuestArenaRelease_nid_postfix(memory, span); #else - munmap(memory, SurfaceBytes); - GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(memory, SurfaceBytes); + munmap(memory, span); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(memory, span); #endif } @@ -323,13 +325,14 @@ public: private: const Context& context; + std::size_t span = 0; std::uint8_t* memory = nullptr; }; -std::array Descriptor(std::uint64_t address, std::uint32_t side = Side) { +std::array Descriptor(std::uint64_t address, std::uint32_t side = Side, std::uint32_t format = Format32UInt) { return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((side - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((side - 1u) & 3u) << 30u), ((side - 1u) >> 2u) | ((side - 1u) << 14u), 0xfacu | (TileR64KBX << 20u) | (Type2D << 28u), 0u, @@ -339,13 +342,13 @@ std::array Descriptor(std::uint64_t address, std::uint32_t sid }; } -std::shared_ptr Sampled(const Context& context, std::uint64_t address, std::uint32_t side = Side) { - const auto words = Descriptor(address, side); +std::shared_ptr Sampled(const Context& context, std::uint64_t address, std::uint32_t side = Side, std::uint32_t format = Format32UInt) { + const auto words = Descriptor(address, side, format); return CachedSampledTexture(context, words); } -GuestTextureResource Resource(std::uint64_t address, std::uint32_t side = Side) { - const auto words = Descriptor(address, side); +GuestTextureResource Resource(std::uint64_t address, std::uint32_t side = Side, std::uint32_t format = Format32UInt) { + const auto words = Descriptor(address, side, format); return DecodeTextureResource(words); } @@ -672,6 +675,55 @@ void snapshotTests(const Context& context) { Require(TextureCacheUsage().sampledBytes == tiny->AllocationBytes(), "a snapshot whose " + std::to_string(tiny->AllocationBytes()) + "-byte image is smaller than its " + std::to_string(guestBytes) + " guest bytes counted " + std::to_string(TextureCacheUsage().sampledBytes) + " bytes, not its allocation"); } +void recycleTests(const Context& context) { + Block block(context); + ClearCachedTextures(context.device); + const auto address = block.Surface(0); + std::memset(reinterpret_cast(address), 0xa1, SurfaceBytes); + const auto first = Sampled(context, address); + Require(Sampled(context, address) == first, "an unchanged surface did not serve its snapshot"); + std::memset(reinterpret_cast(address), 0xb2, SurfaceBytes); + const auto second = Sampled(context, address); + Require(second != first, "a changed surface did not replace its snapshot"); + std::memset(reinterpret_cast(address), 0xa1, SurfaceBytes); + const auto third = Sampled(context, address); + Require(third != second, "the replaced snapshot's size-equal buffer was not reread from guest memory"); + Require(Sampled(context, address) == third, "an unchanged surface did not serve its remade snapshot"); + constexpr std::uint32_t BigSide = Side * 2; + constexpr std::size_t BigBytes = std::size_t{BigSide} * BigSide * 4u; + const WatchedSurface watchedFirst(context, BigBytes); + const WatchedSurface watchedSecond(context, BigBytes); + const auto bigFirst = Sampled(context, watchedFirst.Address(), BigSide); + Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "the first big snapshot did not park its dropped " + std::to_string(BigBytes) + "-byte buffer in the spare pool"); + const auto bigSecond = Sampled(context, watchedSecond.Address(), BigSide); + Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "the second big snapshot did not take the parked spare of its size"); +} + +void spareAgeTests(const Context& context) { + Block block(context); + ClearCachedTextures(context.device); + constexpr std::uint32_t BigSide = Side * 2; + constexpr std::size_t BigBytes = std::size_t{BigSide} * BigSide * 4u; + { + const WatchedSurface watched(context, BigBytes); + const auto first = Sampled(context, watched.Address(), BigSide); + Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "the big snapshot did not park its dropped buffer in the spare pool"); + for (std::uint64_t present = 0; present <= SpareSnapshotPresents; ++present) Recorder::CountPresent(); + const auto small = Sampled(context, block.Surface(0)); + Require(TextureCacheUsage().spareEntries == 0 && TextureCacheUsage().spareBytes == 0, "a spare idle for " + std::to_string(SpareSnapshotPresents + 1u) + " presents was not released"); + } + ClearCachedTextures(context.device); + { + const WatchedSurface watchedFirst(context, BigBytes); + const WatchedSurface watchedSecond(context, BigBytes); + const auto second = Sampled(context, watchedFirst.Address(), BigSide); + Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "the second big snapshot did not park its dropped buffer in the spare pool"); + for (std::uint64_t present = 0; present < SpareSnapshotPresents; ++present) Recorder::CountPresent(); + const auto third = Sampled(context, watchedSecond.Address(), BigSide); + Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "a spare taken within " + std::to_string(SpareSnapshotPresents) + " presents was released instead of reused"); + } +} + void heldTests(const Context& context) { Block block(context); auto held = Sampled(context, block.Surface(0)); @@ -952,7 +1004,7 @@ std::uint64_t ExpectedBudget(const Context& context) { VkPhysicalDeviceMemoryBudgetPropertiesEXT reported{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; VkPhysicalDeviceMemoryProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2, &reported}; ReportBudget(context.physical, &properties); - return SampledTextureBudget(context.memory, &reported, SampledTextureMemory() + TextureCacheUsage().storageBytes); + return SampledTextureBudget(context.memory, &reported, SampledTextureMemory() + TextureCacheUsage().storageBytes, TextureCacheUsage().storageBytes); } std::uint64_t ReadBudget(const Context& context) { @@ -997,8 +1049,10 @@ void storageMemoryTests(Context context) { context.memoryProperties2 = &ReportBudget; Block block(context); auto held = Sampled(context, block.Surface(0)); - const auto quarter = std::min(StorageTextureBudget(context.memory, nullptr, 0, 0), ReportedBudget / 4u); - Require(ReadStorageBudget(context) == quarter, "52 GiB free of a 64 GiB budget does not leave the storage image cache its quarter"); + const auto heap = LargestDeviceLocalHeap(context.memory); + Require(heap.has_value(), "the device has no device-local heap"); + 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; const auto pressed = ReadStorageBudget(context); const auto expected = GiB + TextureCacheUsage().storageBytes; @@ -1006,6 +1060,116 @@ void storageMemoryTests(Context context) { std::printf("reported heap budget %llu MiB with %llu MiB in use: storage image cache budget %llu MiB\n", static_cast(ReportedBudget >> 20u), static_cast(PressedUsage >> 20u), static_cast(pressed >> 20u)); } +struct RecoveryProbe { + PFN_vkGetDeviceProcAddr deviceProc = nullptr; + std::uint64_t released = 0; + unsigned requests = 0; + unsigned failures = 0; + bool pressure = false; +}; + +RecoveryProbe recovery; + +VkDeviceSize ReclaimForTest(VkDeviceSize bytes, bool newImage) { + Require(newImage, "an image allocation did not identify itself to the recovery callback"); + ++recovery.requests; + Require(ReleaseTextureCaches(bytes, false) == 0, "a buffer allocation was allowed to reclaim the cache held by image creation"); + const auto released = ReleaseTextureCaches(bytes, newImage); + recovery.released += released; + return released; +} + +VKAPI_ATTR VkResult VKAPI_CALL AllocateForTest(VkDevice device, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks* allocator, VkDeviceMemory* memory) { + if (recovery.pressure && recovery.released == 0) { + ++recovery.failures; + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + return reinterpret_cast(recovery.deviceProc(device, "vkAllocateMemory"))(device, info, allocator, memory); +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL RecoveryProc(VkDevice device, const char* name) { + if (std::strcmp(name, "vkAllocateMemory") == 0) return reinterpret_cast(AllocateForTest); + return recovery.deviceProc(device, name); +} + +void recoveryTests(Context context, const std::string& mode) { + recovery = RecoveryProbe{}; + recovery.deviceProc = context.deviceProc; + context.deviceProc = RecoveryProc; + context.cacheReclaim = ReclaimForTest; + context.bufferPool.reset(); + Block block(context); + const bool storage = mode == "recover-storage"; + if (storage) { + auto oldest = CachedStorageSurface(context, Resource(block.Surface(0))); + const auto allocated = oldest->AllocationBytes(); + std::weak_ptr released = oldest; + oldest.reset(); + auto pending = CachedStorageSurface(context, Resource(block.Surface(1))); + pending->MarkDirty(); + recovery.pressure = true; + auto made = CachedStorageSurface(context, Resource(block.Surface(2))); + recovery.pressure = false; + Require(made != nullptr && released.expired(), "storage allocation recovery did not release its own oldest clean image"); + Require(pending->Dirty() && StorageImageCached(context, pending.get()), "storage allocation recovery changed an image with pending results"); + Require(recovery.released == allocated, "storage allocation recovery counted memory still held by its caller"); + pending->Flush(); + Require(!pending->Dirty(), "pending storage results could not be stored after memory recovery"); + } else if (mode == "recover-sampled") { + auto oldest = Sampled(context, block.Surface(0)); + Require(!oldest->ViewsStorageImage(), "the sampled recovery fixture needs a snapshot allocation"); + const auto allocated = oldest->AllocationBytes(); + std::weak_ptr released = oldest; + oldest.reset(); + auto held = Sampled(context, block.Surface(1)); + recovery.pressure = true; + auto made = Sampled(context, block.Surface(2)); + recovery.pressure = false; + Require(made != nullptr && released.expired(), "sampled allocation recovery did not release its own oldest unused texture"); + Require(Sampled(context, block.Surface(1)) == held, "sampled allocation recovery removed a texture still held by its caller"); + Require(recovery.released == allocated, "sampled allocation recovery counted memory still held by its caller"); + } else { + const bool srgb = mode == "recover-srgb"; + if (srgb) { + context.srgbDecodeFormats = SrgbDecodeFormats(context.formatProperties, context.physical); + context.srgbCopyFormats = SrgbCopyFormats(context.formatProperties, context.physical, context.srgbDecodeFormats); + Require(SrgbExpandsToRgba8(context, 128u), "the device cannot serve the sRGB expansion copy fixture"); + } + auto oldest = Sampled(context, block.Surface(0)); + Require(!oldest->ViewsStorageImage(), "the sampled recovery fixture needs a snapshot allocation"); + const auto allocated = oldest->AllocationBytes(); + std::weak_ptr released = oldest; + oldest.reset(); + auto held = Sampled(context, block.Surface(1)); + const auto side = srgb ? 1024u : 512u; + const auto format = srgb ? 128u : 43u; + const auto sourceWords = Descriptor(block.Surface(4), side, format); + const auto sourceResource = DecodeTextureResource(sourceWords); + auto source = CachedStorageSurface(context, sourceResource); + source->MarkDirty(); + if (srgb) { + Require(Texture::CanSrgbExpandFrom(context, *source, sourceResource), "the storage surface cannot serve an sRGB expansion copy"); + } else { + Require(Texture::CanRepackCopyFrom(*source, sourceResource), "the storage surface cannot serve a repacked copy"); + } + recovery.pressure = true; + auto made = CachedSampledTexture(context, sourceWords); + recovery.pressure = false; + Require(made != nullptr && made->SharedStorageSource() == source, "sampled copy allocation recovery did not serve the pending storage image"); + Require(released.expired(), "sampled copy allocation recovery did not release its own oldest unused texture"); + Require(source->Dirty() && StorageImageCached(context, source.get()), "sampled copy allocation recovery changed or evicted an image with pending results"); + Require(!held->Released() && held.use_count() == 2, "sampled copy allocation recovery removed a texture still held by its caller"); + const auto refreshed = Sampled(context, block.Surface(1)); + Require(refreshed != nullptr && (refreshed == held || refreshed->ViewsStorageImage()), "sampled copy cache refresh did not serve the surface"); + Require(recovery.released == allocated, "sampled copy allocation recovery counted memory still held by its caller"); + source->Flush(); + Require(!source->Dirty(), "pending storage results could not be stored after memory recovery"); + } + Require(recovery.requests == 1 && recovery.failures >= 1 && recovery.failures <= 2, "image allocation recovery was skipped or retried without a bound"); + const char* label = storage ? "storage" : mode == "recover-srgb" ? "sRGB" : mode == "recover-repack" ? "repacked" : "sampled"; + std::printf("%s image allocation recovered after %u injected failures: %llu bytes released\n", label, recovery.failures, static_cast(recovery.released)); +} + } int main(int argc, char** argv) { @@ -1018,11 +1182,12 @@ int main(int argc, char** argv) { } const bool resources = mode == "resource-admission" || mode == "resource-oversized" || mode == "resource-clear"; const bool disableStorageImageMultisample = mode == "without-storage-image-multisample"; + const bool recover = mode == "recover-sampled" || mode == "recover-storage" || mode == "recover-srgb" || mode == "recover-repack"; #ifdef _WIN32 - if (!memory) _putenv_s("APS5_TEXTURE_CACHE_MIB", "1"); + if (!memory) _putenv_s("APS5_TEXTURE_CACHE_MIB", recover ? "16" : "1"); if (std::getenv("APS5_TEMPLATE_AUDIT_MS") == nullptr) _putenv_s("APS5_TEMPLATE_AUDIT_MS", "250"); #else - if (!memory) setenv("APS5_TEXTURE_CACHE_MIB", "1", 1); + if (!memory) setenv("APS5_TEXTURE_CACHE_MIB", recover ? "16" : "1", 1); setenv("APS5_TEMPLATE_AUDIT_MS", "250", 0); #endif std::unique_ptr device; @@ -1082,10 +1247,16 @@ int main(int argc, char** argv) { std::puts("sampled texture memory budget tests passed"); return 0; } + if (recover) { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + recoveryTests(context, mode); + return 0; + } { std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); reportedBudgetTests(context); snapshotTests(context); + recycleTests(context); heldTests(context); releasedTests(context); rescanTests(context); @@ -1097,6 +1268,7 @@ int main(int argc, char** argv) { secondUseTests(context); earlyMissTests(context); collidingKeyTests(context); + spareAgeTests(context); ClearCachedTextures(context.device); } std::puts("sampled texture cache budget tests passed"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaCvtF32Ubyte.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaCvtF32Ubyte.cpp index 4468d5d220..dac88be437 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaCvtF32Ubyte.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaCvtF32Ubyte.cpp @@ -17,20 +17,50 @@ using ShaderRecompiler::ShaderStage; constexpr std::uint32_t Threads = 64; constexpr std::uint32_t Inputs = 4; constexpr std::uint32_t Results = 4; +constexpr std::uint32_t Selectors = 7; alignas(256) std::array Input{}; alignas(256) std::array Output{}; -alignas(256) constexpr std::array SdwaCode{ +constexpr std::array ModifierWords{14, 16, 18, 20}; + +alignas(256) std::array SdwaCode{ 0x34020082, 0x34060082, 0xe0302000, 0x80000401, 0x7e1402ff, 0xabcd1234, 0x7e1602ff, 0xabcd1234, 0x7e1802ff, 0xabcd1234, 0x7e1a02ff, 0xabcd1234, - 0xbf8c3f70, 0x7e1424f9, 0x00040604, 0x7e1624f9, 0x00050604, 0x7e1826f9, 0x000d0604, 0x7e1a28f9, 0x000b0604, + 0xbf8c3f70, 0x7e1422f9, 0x00060604, 0x7e1624f9, 0x00060604, 0x7e1826f9, 0x00060604, 0x7e1a28f9, 0x00060604, 0xe0702000, 0x80010a03, 0xe0702004, 0x80010b03, 0xe0702008, 0x80010c03, 0xe070200c, 0x80010d03, 0xbf810000, }; +const std::array SelectorNames{"BYTE_0", "BYTE_1", "BYTE_2", "BYTE_3", "WORD_0", "WORD_1", "DWORD"}; + +struct SelectVector { + std::uint32_t value; + std::array zeroExtended; + std::array signExtended; +}; + +constexpr std::array Vectors{{ + {0x00000000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {0xffffffffu, {0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x0000ffffu, 0x0000ffffu, 0xffffffffu}, {0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu}}, + {0x12345678u, {0x00000078u, 0x00000056u, 0x00000034u, 0x00000012u, 0x00005678u, 0x00001234u, 0x12345678u}, {0x00000078u, 0x00000056u, 0x00000034u, 0x00000012u, 0x00005678u, 0x00001234u, 0x12345678u}}, + {0x80808080u, {0x00000080u, 0x00000080u, 0x00000080u, 0x00000080u, 0x00008080u, 0x00008080u, 0x80808080u}, {0xffffff80u, 0xffffff80u, 0xffffff80u, 0xffffff80u, 0xffff8080u, 0xffff8080u, 0x80808080u}}, + {0x7f7f7f7fu, {0x0000007fu, 0x0000007fu, 0x0000007fu, 0x0000007fu, 0x00007f7fu, 0x00007f7fu, 0x7f7f7f7fu}, {0x0000007fu, 0x0000007fu, 0x0000007fu, 0x0000007fu, 0x00007f7fu, 0x00007f7fu, 0x7f7f7f7fu}}, + {0x8182a3f4u, {0x000000f4u, 0x000000a3u, 0x00000082u, 0x00000081u, 0x0000a3f4u, 0x00008182u, 0x8182a3f4u}, {0xfffffff4u, 0xffffffa3u, 0xffffff82u, 0xffffff81u, 0xffffa3f4u, 0xffff8182u, 0x8182a3f4u}}, + {0x7f00ff80u, {0x00000080u, 0x000000ffu, 0x00000000u, 0x0000007fu, 0x0000ff80u, 0x00007f00u, 0x7f00ff80u}, {0xffffff80u, 0xffffffffu, 0x00000000u, 0x0000007fu, 0xffffff80u, 0x00007f00u, 0x7f00ff80u}}, + {0x00008000u, {0x00000000u, 0x00000080u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00000000u, 0x00008000u}, {0x00000000u, 0xffffff80u, 0x00000000u, 0x00000000u, 0xffff8000u, 0x00000000u, 0x00008000u}}, + {0x0000007fu, {0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x0000007fu}, {0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x0000007fu}}, + {0x00000080u, {0x00000080u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00000080u}, {0xffffff80u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00000080u}}, + {0x80000000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00008000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0xffffff80u, 0x00000000u, 0xffff8000u, 0x80000000u}}, + {0x00800000u, {0x00000000u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00000000u, 0x00000080u, 0x00800000u}, {0x00000000u, 0x00000000u, 0xffffff80u, 0x00000000u, 0x00000000u, 0x00000080u, 0x00800000u}}, + {0x0000ff00u, {0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u, 0x0000ff00u, 0x00000000u, 0x0000ff00u}, {0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0xffffff00u, 0x00000000u, 0x0000ff00u}}, + {0xdeadbeefu, {0x000000efu, 0x000000beu, 0x000000adu, 0x000000deu, 0x0000beefu, 0x0000deadu, 0xdeadbeefu}, {0xffffffefu, 0xffffffbeu, 0xffffffadu, 0xffffffdeu, 0xffffbeefu, 0xffffdeadu, 0xdeadbeefu}}, + {0x01020304u, {0x00000004u, 0x00000003u, 0x00000002u, 0x00000001u, 0x00000304u, 0x00000102u, 0x01020304u}, {0x00000004u, 0x00000003u, 0x00000002u, 0x00000001u, 0x00000304u, 0x00000102u, 0x01020304u}}, + {0xf0e0d0c0u, {0x000000c0u, 0x000000d0u, 0x000000e0u, 0x000000f0u, 0x0000d0c0u, 0x0000f0e0u, 0xf0e0d0c0u}, {0xffffffc0u, 0xffffffd0u, 0xffffffe0u, 0xfffffff0u, 0xffffd0c0u, 0xfffff0e0u, 0xf0e0d0c0u}}, +}}; + void FillInput() { for (std::uint32_t tid = 0; tid < Threads; ++tid) { - Input[tid * Inputs] = tid | ((tid + 64u) << 8u) | ((255u - tid) << 16u) | (((tid * 3u + 128u) & 0xffu) << 24u); + Input[tid * Inputs] = Vectors[tid % Vectors.size()].value; } } @@ -49,7 +79,10 @@ std::array BufferDescriptor(const void* data, std::uint32_t co return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x11016facu}; } -void Run(AgcDriver::VulkanDevice& device) { +void Run(AgcDriver::VulkanDevice& device, std::uint32_t selector, bool signExtend) { + for (const auto word : ModifierWords) { + SdwaCode[word] = 0x00000604u | (selector << 16u) | (signExtend ? 1u << 19u : 0u); + } Output.fill(0xdeadbeefu); std::vector userData(8, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size())); @@ -71,18 +104,16 @@ void Run(AgcDriver::VulkanDevice& device) { device.WaitIdle(); } -void Check() { +void Check(std::uint32_t selector, bool signExtend) { for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const auto source = Input[tid * Inputs]; + const auto& vector = Vectors[tid % Vectors.size()]; + const auto selected = signExtend ? vector.signExtended[selector] : vector.zeroExtended[selector]; const auto* out = &Output[tid * Results]; - const auto where = [&](std::uint32_t result) { return "sdwa cvt f32 ubyte: thread " + std::to_string(tid) + " result " + std::to_string(result) + " is " + Hex(out[result]); }; - const auto byte1 = FloatBits((source >> 8u) & 0xffu); - const auto byte3 = FloatBits(source >> 24u); - const auto signFill = FloatBits((source & 0x80000000u) != 0u ? 0xffu : 0u); - Require(out[0] == byte1, where(0) + ", expected " + Hex(byte1) + ": v_cvt_f32_ubyte1 src0_sel:WORD_0 must convert bits 15:8"); - Require(out[1] == byte3, where(1) + ", expected " + Hex(byte3) + ": v_cvt_f32_ubyte1 src0_sel:WORD_1 must convert bits 31:24"); - Require(out[2] == signFill, where(2) + ", expected " + Hex(signFill) + ": v_cvt_f32_ubyte2 src0_sel:WORD_1 src0_sext must convert the sign extension of the high word"); - Require(out[3] == signFill, where(3) + ", expected " + Hex(signFill) + ": v_cvt_f32_ubyte3 src0_sel:BYTE_3 src0_sext must convert the sign extension of the top byte"); + for (std::uint32_t byte = 0; byte < Results; ++byte) { + const auto expected = FloatBits((selected >> (byte * 8u)) & 0xffu); + Require(out[byte] == expected, "sdwa v_cvt_f32_ubyte" + std::to_string(byte) + " src0_sel:" + SelectorNames[selector] + (signExtend ? " src0_sext" : "") + " of " + + Hex(vector.value) + " is " + Hex(out[byte]) + ", expected " + Hex(expected)); + } } } @@ -93,8 +124,12 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; FillInput(); - Run(*device); - Check(); + for (const bool signExtend : {false, true}) { + for (std::uint32_t selector = 0; selector < Selectors; ++selector) { + Run(*device, selector, signExtend); + Check(selector, signExtend); + } + } std::puts("sdwa cvt f32 ubyte tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SgprPatternFillKeys.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SgprPatternFillKeys.cpp index c6dd1e4ca1..8d0bdf3153 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SgprPatternFillKeys.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SgprPatternFillKeys.cpp @@ -98,7 +98,7 @@ void MatchTests() { alignas(8) std::uint32_t Finished = 0; std::uint32_t Submitted = 0; -constexpr std::uint32_t MaxResubmits = 16; +constexpr std::uint32_t MaxResubmits = 512; void Submit(const std::vector& data, std::uint32_t groups) { std::vector words; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ShutdownThreads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ShutdownThreads.cpp index 8f835d52de..82832ba244 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ShutdownThreads.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ShutdownThreads.cpp @@ -76,6 +76,41 @@ void ClosingJoinsPreparation() { Require(ranHere && RegistrationPreparationThreads() == 0, "a closed preparation pool restarted after being joined"); } +void ClosingDropsQueuedPrebuilds() { + using namespace AgcDriver::DriverDetail; + std::promise started; + auto running = started.get_future(); + std::promise gate; + auto opened = gate.get_future().share(); + bool queuedRan = false; + SubmitLibraryPrebuild([&] { started.set_value(); opened.wait(); }); + for (int index = 0; index < 3; ++index) SubmitLibraryPrebuild([&] { queuedRan = true; }); + running.wait(); + auto entering = std::make_shared>(); + auto entered = entering->get_future(); + auto awaited = std::make_shared>(); + auto woken = awaited->get_future(); + std::thread([entering, awaited] { + entering->set_value(); + AwaitLibraryPrebuilds(); + awaited->set_value(); + }).detach(); + entered.wait(); + const bool waiting = woken.wait_for(std::chrono::milliseconds(20)) == std::future_status::timeout; + auto closing = std::async(std::launch::async, [] { CloseRegistrationPreparation(); }); + const bool waited = closing.wait_for(std::chrono::milliseconds(20)) == std::future_status::timeout; + gate.set_value(); + const bool returned = closing.wait_for(std::chrono::seconds(10)) == std::future_status::ready; + const bool wokeUp = woken.wait_for(std::chrono::seconds(10)) == std::future_status::ready; + closing.get(); + Require(waiting, "a waiter for the library prebuilds returned while one was still running"); + Require(waited, "closing returned while a library prebuild was still running"); + Require(returned, "closing did not return once the running library prebuild ended"); + Require(wokeUp, "a waiter for the library prebuilds did not return after closing dropped the queued ones"); + Require(!queuedRan, "closing ran the queued library prebuilds"); + Require(RegistrationPreparationThreads() == 0, "closing the library prebuilds left their thread running"); +} + void StoppingJoinsCompareHelpers() { using namespace AgcDriver::Graphics; const std::vector first(std::size_t{2} << 20u, std::byte{1}); @@ -168,6 +203,7 @@ int main(int argc, char** argv) { try { const std::string_view mode = argc > 1 ? argv[1] : ""; if (mode == "--joined") ClosingJoinsPreparation(); + else if (mode == "--queued-prebuilds") ClosingDropsQueuedPrebuilds(); else if (mode == "--compare-helpers") StoppingJoinsCompareHelpers(); else if (mode == "--draw-committer") ShutdownJoinsDrawCommitter(); else if (mode == "--shader-cache-writer") ShutdownJoinsShaderCacheWriter(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SkipShaderFile.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SkipShaderFile.cpp new file mode 100644 index 0000000000..cec7c1cc4c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SkipShaderFile.cpp @@ -0,0 +1,102 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +alignas(256) constexpr std::array First{0xd7460000u, 0x04010c04u, 0x7e0202ffu, 0x12345678u, 0xe0702000u, 0x80000100u, 0xbf810000u}; +alignas(256) constexpr std::array Second{0xd7460000u, 0x04010c04u, 0x7e0202ffu, 0x12345678u, 0xe0702000u, 0x80000100u, 0xbf810000u}; +constexpr std::uint32_t Untouched = 0xcafebabeu; +constexpr std::uint32_t Stored = 0x12345678u; +alignas(256) std::array FirstOutput{}; +alignas(256) std::array SecondOutput{}; + +void Dispatch(const std::array& code, std::array& output) { + const auto address = reinterpret_cast(output.data()); + const std::array data{static_cast(address), static_cast((address >> 32u) & 0xffffu) | (4u << 16u), static_cast(output.size()), 0x00014204u}; + const auto program = reinterpret_cast(code.data()); + std::vector words{ + 0xc0037600u, 0x207, 64, 1, 1, + 0xc0027600u, 0x20c, static_cast(program >> 8u), static_cast(program >> 40u), + 0xc0017600u, 0x213, (4u << 1u) | (1u << 7u), + 0xc0047600u, 0x240, data[0], data[1], data[2], data[3], + 0xc0031500u, 1, 1, 1, 0x41u, + }; + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + AgcDriver::Submit(&packet, 0); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::GuestMemory::FlushGpuWrites(address, sizeof(output)); +} + +std::string Hex(const void* address) { + char text[32]; + std::snprintf(text, sizeof(text), "%llx", static_cast(reinterpret_cast(address))); + return text; +} + +void List(const std::filesystem::path& path, const std::string& contents) { + std::ofstream(path, std::ios::trunc) << contents; + std::this_thread::sleep_for(std::chrono::milliseconds(1100)); +} + +void Run(const char* phase, bool firstSkipped, bool secondSkipped) { + FirstOutput.fill(Untouched); + SecondOutput.fill(Untouched); + Dispatch(First, FirstOutput); + Dispatch(Second, SecondOutput); + for (std::size_t i = 0; i < FirstOutput.size(); ++i) { + Require(FirstOutput[i] == (firstSkipped ? Untouched : Stored), std::string(phase) + ": the first shader's dispatch " + (firstSkipped ? "stored" : "did not store") + " dword " + std::to_string(i)); + Require(SecondOutput[i] == (secondSkipped ? Untouched : Stored), std::string(phase) + ": the second shader's dispatch " + (secondSkipped ? "stored" : "did not store") + " dword " + std::to_string(i)); + } +} + +} + +int main() { + const auto path = std::filesystem::temp_directory_path() / ("aps5-skip-shader-file-" + Hex(&FirstOutput) + ".txt"); + try { + List(path, "# listed\n" + Hex(First.data()) + "\n"); +#ifdef _WIN32 + Require(_putenv_s("APS5_SKIP_SHADER_FILE", path.string().c_str()) == 0, "cannot set APS5_SKIP_SHADER_FILE"); +#else + Require(setenv("APS5_SKIP_SHADER_FILE", path.string().c_str(), 1) == 0, "cannot set APS5_SKIP_SHADER_FILE"); +#endif + { + const auto device = OpenVulkanTestDevice(); + if (!device) { + std::filesystem::remove(path); + return VulkanTestSkipped; + } + } + Run("first listed", true, false); + List(path, Hex(Second.data()) + " # the first is not listed any more\n"); + Run("second listed", false, true); + List(path, Hex(First.data()) + "zz 1" + std::string(16, '0') + " 0x" + Hex(Second.data()) + "\n"); + Run("malformed and overflowing tokens", false, true); + List(path, ""); + Run("none listed", false, false); + AgcDriverShutdown_nid_postfix(); + std::filesystem::remove(path); + std::puts("skip shader file tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + AgcDriverShutdown_nid_postfix(); + std::error_code ignored; + std::filesystem::remove(path, ignored); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SparseBufferHoles.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SparseBufferHoles.cpp index a7a25f9f88..55cae19285 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SparseBufferHoles.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SparseBufferHoles.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "VulkanTestDevice.hpp" +#include "FixtureDeviceSelection.hpp" #include #ifdef _WIN32 #ifndef NOMINMAX @@ -40,6 +41,8 @@ public: Device() { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + library = SDL_LoadObject("libvulkan.1.dylib"); #else library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -51,6 +54,12 @@ public: application.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &application; +#ifdef __APPLE__ + const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + info.enabledExtensionCount = 1; + info.ppEnabledExtensionNames = &portability; +#endif Check(function("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); std::uint32_t count = 0; const auto enumerate = function("vkEnumeratePhysicalDevices"); @@ -58,18 +67,7 @@ public: Require(count != 0, "no Vulkan device"); std::vector devices(count); Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); - const auto properties = function("vkGetPhysicalDeviceProperties"); - int rank = -1; - for (const auto candidate : devices) { - VkPhysicalDeviceProperties described{}; - properties(candidate, &described); - if (described.apiVersion < VK_API_VERSION_1_1) continue; - const int candidateRank = described.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? 2 : described.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU ? 1 : 0; - if (candidateRank <= rank) continue; - context.physical = candidate; - rank = candidateRank; - } - Require(context.physical != VK_NULL_HANDLE, "no Vulkan 1.1 device"); + context.physical = SelectFixturePhysicalDevice(devices, function("vkGetPhysicalDeviceProperties")); VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}; VkPhysicalDeviceProperties2 selected{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &subgroup}; function("vkGetPhysicalDeviceProperties2")(context.physical, &selected); @@ -325,7 +323,7 @@ void Run(const Context& context, const CopyPipeline& pipeline, bool writable, co std::memset(readback.Bytes().data(), 0xcc, BufferBytes); VkBuffer primed = VK_NULL_HANDLE; { - Buffer earlier(context, BufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + Buffer earlier(context, BufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); primed = earlier.Handle(); std::memset(earlier.Bytes().data(), 0xa5, BufferBytes); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SparseIndirectIndices.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SparseIndirectIndices.cpp index 809321d1b5..20a4dc48e2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SparseIndirectIndices.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SparseIndirectIndices.cpp @@ -168,7 +168,7 @@ void Target(std::vector& words, void* pointer) { Append(words, 0x69, {0x390, static_cast(address >> 40u)}); } -void Run(SparseIndices& indices, std::uint32_t size, bool multi, bool afterGap, bool unreadable) { +void Run(SparseIndices& indices, std::uint32_t size, bool multi, bool afterGap, bool unreadable, bool sized = true, std::uint32_t second = DeclaredIndices) { indices.Fill(size); Reference.fill(Kept); Actual.fill(Kept); @@ -177,7 +177,7 @@ void Run(SparseIndices& indices, std::uint32_t size, bool multi, bool afterGap, const auto first = unreadable ? 16u / size : afterGap ? indices.AfterGap(size) : 0u; Records[8] = 3; Records[9] = 1; - Records[10] = DeclaredIndices; + Records[10] = second; Records[16] = 3; Records[17] = 1; Records[18] = 16u / size; @@ -192,7 +192,7 @@ void Run(SparseIndices& indices, std::uint32_t size, bool multi, bool afterGap, Target(words, Actual.data()); Append(words, 0x2a, {size == 2 ? 0u : 1u}); Append(words, 0x26, {Low(indices.Base()), High(indices.Base())}); - Append(words, 0x13, {DeclaredIndices}); + if (sized) Append(words, 0x13, {DeclaredIndices}); Append(words, 0x11, {1, Low(Records.data()), High(Records.data())}); if (multi) Append(words, 0x38, {0, 0, 0, 0x40000280u, 3, Low(Count.data()), High(Count.data()), 32, 0}); else Append(words, 0x25, {0, 0, 0, 0}); @@ -204,7 +204,7 @@ void Run(SparseIndices& indices, std::uint32_t size, bool multi, bool afterGap, const auto covered = std::count_if(Reference.begin(), Reference.end(), [](std::byte value) { return value != Kept; }); Require(covered != 0, "the reference triangle did not draw"); Require(Actual == Reference, "the sparse indexed indirect draw differs from the reference triangle"); - std::cout << "sparse indices " << size << " bytes, " << (multi ? "counted multi" : "single") << ", " << (afterGap ? "after gap" : "before gap") << " passed\n"; + std::cout << "sparse indices " << size << " bytes, " << (multi ? "counted multi" : "single") << ", " << (afterGap ? "after gap" : "before gap") << (sized ? "" : ", no index size") << " passed\n"; } } @@ -218,10 +218,13 @@ int main(int argc, char** argv) { Register(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); Register(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); SparseIndices indices; - if (argc == 2 && std::string(argv[1]) == "--unreadable") { + const std::string mode = argc == 2 ? argv[1] : ""; + if (mode == "--unreadable" || mode == "--no-size-unreadable" || mode == "--no-size-unreadable-record") { bool refused = false; try { - Run(indices, 2, false, false, true); + if (mode == "--unreadable") Run(indices, 2, false, false, true); + else if (mode == "--no-size-unreadable") Run(indices, 2, false, false, true, false); + else Run(indices, 2, true, false, false, false); } catch (const std::runtime_error& error) { const std::string message = error.what(); refused = message.find("guest memory is not readable") != std::string::npos && message.find("+0x6)") != std::string::npos; @@ -232,10 +235,11 @@ int main(int argc, char** argv) { std::cout << "the unreadable indirect index range was refused\n"; return 0; } + const bool sized = mode != "--no-size"; for (const auto size : {2u, 4u}) { for (const auto multi : {false, true}) { - Run(indices, size, multi, false, false); - Run(indices, size, multi, true, false); + Run(indices, size, multi, false, false, sized, sized ? DeclaredIndices : 0u); + Run(indices, size, multi, true, false, sized, sized ? DeclaredIndices : 0u); } } AgcDriverShutdown_nid_postfix(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StencilKnownReplace.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StencilKnownReplace.cpp new file mode 100644 index 0000000000..284eb2e7d6 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StencilKnownReplace.cpp @@ -0,0 +1,191 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::byte Kept{0x40}; + +alignas(256) constexpr std::array VertexCode{ + 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e060280u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u, +}; +alignas(256) constexpr std::array WhitePixelCode{0x7e0e02f2u, 0xf800180fu, 0x07070707u, 0xbf810000u}; +alignas(256) std::array ColorMemory{}; +alignas(256) std::array DepthMemory{}; +alignas(256) std::array StencilMemory{}; + +struct Header { + Shader shader{}; + ShaderUserData userData{}; + std::array registers{}; +}; + +alignas(8) Header VertexHeader{}; +alignas(8) Header PixelHeader{}; + +void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(bytes); + header.shader.code = code; + header.shader.type = type; + header.shader.user_data = &header.userData; + if (type == 1) { + const auto address = reinterpret_cast(code); + header.registers = {{{0x008, static_cast(address >> 8u)}, {0x009, static_cast(address >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast(1.0f)}}}; + header.shader.sh_registers = header.registers.data(); + header.shader.num_sh_registers = header.registers.size(); + } + AgcDriverRegisterShader_nid_postfix(&header.shader); +} + +void Append(std::vector& commands, std::uint32_t opcode, std::initializer_list payload) { + commands.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + commands.insert(commands.end(), payload); +} + +struct StencilState { + std::uint32_t depthControl; + std::uint32_t operations; + std::uint32_t refMask; +}; + +std::vector Draw(const StencilState& stencilState) { + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto pixel = reinterpret_cast(WhitePixelCode.data()); + const auto color = reinterpret_cast(ColorMemory.data()); + const auto depth = reinterpret_cast(DepthMemory.data()); + const auto stencil = reinterpret_cast(StencilMemory.data()); + const std::vector> context{ + {0x2d5, 0x2000}, + {0x1b3, 0}, {0x1b4, 0}, {0x1b6, 0}, {0x207, 0}, {0x200, stencilState.depthControl}, {0x203, 0x800}, + {0x2dc, 0xaa00}, {0x2f8, 0}, {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, 9}, {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}, + {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}, + {0x318, static_cast(color >> 8u)}, {0x390, static_cast(color >> 40u)}, {0x292, 2}, + {0x000, 0}, {0x002, 0}, {0x007, (3u << 16u) | 63u}, + {0x008, 0}, {0x009, std::bit_cast(1.0f)}, {0x00a, 0}, {0x00b, 0}, + {0x010, 3}, {0x011, 1}, + {0x012, static_cast(depth >> 8u)}, {0x014, static_cast(depth >> 8u)}, + {0x013, static_cast(stencil >> 8u)}, {0x015, static_cast(stencil >> 8u)}, + {0x01a, static_cast(depth >> 40u)}, {0x01c, static_cast(depth >> 40u)}, + {0x01b, static_cast(stencil >> 40u)}, {0x01d, static_cast(stencil >> 40u)}, + {0x10b, stencilState.operations}, {0x10c, stencilState.refMask}, {0x10d, stencilState.refMask}, + }; + const std::vector> shader{ + {0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, 1u << 1u}, {0x8c, 0}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast(1.0f)}, + }; + std::vector commands; + Append(commands, 0x79, {0x242, 4}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value}); + Append(commands, 0x2d, {3, 2}); + return commands; +} + +void Submit(const StencilState& stencilState) { + ColorMemory.fill(Kept); + auto commands = Draw(stencilState); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the stencil draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); +} + +constexpr std::uint32_t StencilEnable = 1u; +constexpr std::uint32_t Equal = 2u << 8u; +constexpr std::uint32_t LessEqual = 3u << 8u; +constexpr std::uint32_t Greater = 4u << 8u; +constexpr std::uint32_t NotEqual = 5u << 8u; +constexpr std::uint32_t Always = 7u << 8u; + +std::uint32_t RefMask(std::uint32_t test, std::uint32_t compareMask, std::uint32_t writeMask, std::uint32_t opValue) { + return test | (compareMask << 8u) | (writeMask << 16u) | (opValue << 24u); +} + +std::array Fill(std::uint32_t value) { + Submit({StencilEnable | Always, 0x30u, RefMask(value, 0xff, 0xff, 0)}); + const auto coverage = ColorMemory; + Require(std::count(coverage.begin(), coverage.end(), Kept) != static_cast(coverage.size()), "the stencil fill covered no pixels"); + return coverage; +} + +void Expect(const std::array& coverage, std::uint32_t value, const std::string& what) { + Submit({StencilEnable | Equal, 0u, RefMask(value, 0xff, 0, 0)}); + std::size_t drawn = 0; + for (std::size_t pixel = 0; pixel < coverage.size(); pixel += 4u) { + const bool covered = coverage[pixel] != Kept; + drawn += covered; + for (std::size_t channel = 0; channel < 4u; ++channel) Require(ColorMemory[pixel + channel] == (covered ? std::byte{255} : Kept), what + ": the stencil is not " + std::to_string(value) + " exactly where the draw covered"); + } + Require(drawn != 0, what + ": the probe covered no pixels"); +} + +void Case(const std::string& what, std::uint32_t initial, const StencilState& stencilState, std::uint32_t expected, bool testPasses = true) { + const auto coverage = Fill(initial); + Submit(stencilState); + if (testPasses) Require(ColorMemory == coverage, what + ": the draw under test did not color exactly the filled pixels"); + else Require(std::all_of(ColorMemory.begin(), ColorMemory.end(), [](auto value) { return value == Kept; }), what + ": a draw whose stencil test fails wrote color"); + Expect(coverage, expected, what); +} + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Registered(PixelHeader, WhitePixelCode.data(), sizeof(WhitePixelCode), 1); + const std::array pixelContext{{{0x203, 0x800}, {0x1c4, 9}, {0x1b6, 0}}}; + AgcDriverResolveShaderAbi_nid_postfix(&PixelHeader.shader, pixelContext, {}); + Case("an equal test replaced by the operation value", 0xff, {StencilEnable | Equal, 0x40u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40); + Case("an equal test replaced by the operation value on pass and depth fail", 0xff, {StencilEnable | Equal, 0x440u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40); + Case("an equal test replaced by 0xff", 0x25, {StencilEnable | Equal, 0x20u, RefMask(0x25, 0xff, 0xff, 0)}, 0xff); + Case("an equal test replaced through a partial write mask", 0xff, {StencilEnable | Equal, 0x40u, RefMask(0xff, 0xff, 0xf0, 0x40)}, 0x4f); + Case("a failed not-equal test replaced by the operation value", 0xff, {StencilEnable | NotEqual, 0x4u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40, false); + Case("a less-or-equal test against 0xff replaced by the operation value", 0xff, {StencilEnable | LessEqual, 0x40u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40); + Case("a failed greater test against 0xff replaced by the operation value", 0xff, {StencilEnable | Greater, 0x4u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40, false); + AgcDriverShutdown_nid_postfix(); + std::puts("stencil replacements with a known stencil value passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { AgcDriverShutdown_nid_postfix(); } catch (...) {} + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StencilLogicOperations.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StencilLogicOperations.cpp new file mode 100644 index 0000000000..21f62c48d6 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StencilLogicOperations.cpp @@ -0,0 +1,202 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#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 = 16; +constexpr std::uint32_t WaveSize = 64; +constexpr std::uint32_t PlainControl = 0x50u; +constexpr std::byte Kept{0x40}; +alignas(256) std::array Pixels{}; +alignas(256) std::array DepthMemory{}; +alignas(256) std::array StencilMemory{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +alignas(256) constexpr std::array PixelCode{ + 0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000, +}; + +alignas(256) constexpr std::array, 3> Vertices{{ + {-1.0f, -1.0f, 0.25f, 1.0f}, {3.0f, -1.0f, 0.25f, 1.0f}, {-1.0f, 3.0f, 0.25f, 1.0f}, +}}; + +struct Shaders { + ShaderRecompiler::RecompileResult vertex; + ShaderRecompiler::RecompileResult pixel; + std::uint32_t pixelPush = 0; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +ShaderRecompiler::ShaderPixelStageInfo PixelInfo() { + const AgcDriver::Registers context{ + {0x1b3, 0x700u}, {0x1b4, 0x700u}, {0x1b6, 0u}, {0x1c5, 9u}, {0x203, PlainControl}, + }; + return AgcDriver::Graphics::DecodePixelStageInfo(context, {0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u}); +} + +Shaders Compile(AgcDriver::VulkanDevice& device) { + const auto target = device.Target(); + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Vertices.data(), 16u, static_cast(Vertices.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {WaveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + Shaders shaders{ShaderRecompiler::Recompile(vertex), {}, 0}; + const auto vertexPush = static_cast(shaders.vertex.pushConstants.size()); + 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, {}}, + {WaveSize, 0, pixelUserData, std::nullopt, PixelInfo(), std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + shaders.pixel = ShaderRecompiler::Recompile(fragment); + shaders.pixelPush = PixelPushOffset(vertexPush, target); + return shaders; +} + +AgcDriver::QueueState StencilQueue(std::uint32_t ops, std::uint32_t refMask, std::uint32_t depthControl) { + AgcDriver::QueueState queue; + queue.userConfig[0x242] = 4; + queue.context = { + {0x2d5, 0x2000}, + {0x1b6, 0}, {0x1b8, 0}, {0x207, 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}, + {0x000, 0}, {0x002, 0}, {0x007, (1u << 16u) | 3u}, {0x00a, 0}, {0x00b, 0}, {0x010, 0x22900983u}, {0x011, 0x20000181u}, + {0x012, 0x100}, {0x014, 0x100}, {0x013, 0x200}, {0x015, 0x200}, {0x005, 0x300}, {0x01e, 0x1}, + {0x10b, ops}, {0x10c, refMask}, {0x10d, refMask}, {0x200, depthControl} + }; + const auto address = reinterpret_cast(Pixels.data()); + queue.context[0x318] = static_cast(address >> 8u); + queue.context[0x390] = static_cast(address >> 40u); + queue.context[0x10f] = std::bit_cast(32.0f); + queue.context[0x110] = std::bit_cast(32.0f); + queue.context[0x111] = std::bit_cast(-2.0f); + queue.context[0x112] = std::bit_cast(2.0f); + queue.context[0x113] = std::bit_cast(1.0f); + queue.context[0x114] = 0; + queue.context[0xb4] = 0; + queue.context[0xb5] = std::bit_cast(1.0f); + queue.shader[0x008] = 0x100; + queue.shader[0x009] = 0; + return queue; +} + +void Draw(AgcDriver::VulkanDevice& device, const Shaders& shaders, const VkStencilOpState& stencil) { + Pixels.fill(Kept); + const std::array stages{{ + {ShaderStage::Vertex, &shaders.vertex, 0}, + {ShaderStage::Fragment, &shaders.pixel, shaders.pixelPush} + }}; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, WaveSize, WaveSize, 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.depth = AgcDriver::Graphics::DepthTarget{reinterpret_cast(DepthMemory.data()), reinterpret_cast(StencilMemory.data()), {Width, Height}, VK_FORMAT_D32_SFLOAT_S8_UINT, 1.0f, 0}; + state.depthTest = false; + state.depthCompare = VK_COMPARE_OP_ALWAYS; + state.stencilTest = true; + state.stencilFront = stencil; + state.stencilBack = stencil; + 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 = {}; + device.Draw(state, AgcDriver::Pm4::DrawParameters{0, 3u, 0, 1u, 0, false}, stages); + device.WaitIdle(); +} + +std::uint32_t Expected(std::uint32_t op, std::uint32_t stored, std::uint32_t value, std::uint32_t writeMask) { + const auto result = op == 10u ? stored & value : op == 11u ? stored | value : op == 12u ? stored ^ value : ~(stored ^ value); + return ((stored & ~writeMask) | (result & writeMask)) & 0xffu; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto shaders = Compile(*device); + const auto replace = [](std::uint32_t value) { return VkStencilOpState{VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE, VK_COMPARE_OP_ALWAYS, 0xffu, 0xffu, value}; }; + const auto equal = [](std::uint32_t value) { return VkStencilOpState{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_EQUAL, 0xffu, 0u, value}; }; + std::size_t checked = 0; + for (const auto stored : {0xa5u, 0x3cu}) { + for (const auto op : {10u, 11u, 12u, 15u}) { + for (const auto [writeMask, value] : {std::pair{0xffu, 0x3cu}, std::pair{0x0fu, 0x96u}, std::pair{0xf6u, 0xffu}}) { + for (const auto [ops, compare] : {std::pair{op << 4u, 7u}, std::pair{op, 0u}}) { + const auto refMask = 0xff00u | (writeMask << 16u) | (value << 24u); + const auto face = AgcDriver::Graphics::DecodeState(StencilQueue(ops, refMask, 1u | (compare << 8u))).stencilFront; + const auto expected = Expected(op, stored, value, writeMask); + const auto name = "stencil operation " + std::to_string(op) + " in DB_STENCIL_CONTROL " + std::to_string(ops) + " with DB_STENCILREFMASK " + std::to_string(refMask) + " on stored value " + std::to_string(stored); + Draw(*device, shaders, replace(stored)); + Draw(*device, shaders, face); + Draw(*device, shaders, equal(expected)); + for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { + Require(Pixels[pixel * 4u] == std::byte{255}, name + ": pixel " + std::to_string(pixel) + " does not hold the expected stencil value " + std::to_string(expected)); + } + Draw(*device, shaders, equal(expected ^ 0x80u)); + Require(std::ranges::all_of(std::span(Pixels).first(4), [](std::byte value) { return value == Kept; }), name + ": the stencil value also equals " + std::to_string(expected ^ 0x80u)); + ++checked; + } + } + } + } + std::printf("stencil logic operation tests passed: %zu states\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/StencilReplay.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp index 3612fae6ef..14d571cf48 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp @@ -88,7 +88,7 @@ ShaderRecompiler::ShaderPixelStageInfo PixelInfo(std::uint32_t shaderControl) { return AgcDriver::Graphics::DecodePixelStageInfo(context, {0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u}); } -void Draw(AgcDriver::VulkanDevice& device, std::span pixelCode, std::uint32_t shaderControl, const VkStencilOpState& stencil, std::uint32_t vertices = 3u, std::uint32_t instances = 1u, bool depthFail = false, const std::function& alter = {}, const std::function& alterDraw = {}, std::span> vertexData = Vertices, std::span vertexCode = VertexCode, std::span pixelData = {}, std::span pixelSrt = {}, const std::function& alterVertex = {}) { +void Draw(AgcDriver::VulkanDevice& device, std::span pixelCode, std::uint32_t shaderControl, const VkStencilOpState& stencil, std::uint32_t vertices = 3u, std::uint32_t instances = 1u, bool depthFail = false, const std::function& alter = {}, const std::function& alterDraw = {}, std::span> vertexData = Vertices, std::span vertexCode = VertexCode, std::span pixelData = {}, std::span pixelSrt = {}, const std::function& alterVertex = {}, std::uint32_t pixelPushBytes = 0u) { Pixels.fill(Kept); auto target = device.Target(); std::vector capabilities(target.supportedCapabilities.begin(), target.supportedCapabilities.end()); @@ -113,11 +113,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::span pixelC auto pixelUserData = pixelData.empty() ? std::vector(8, 0u) : std::vector(pixelData.begin(), pixelData.end()); std::vector pixelMemory{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}; pixelMemory.insert(pixelMemory.end(), pixelSrt.begin(), pixelSrt.end()); + auto pixelLayout = PixelPushLayout(vertexPush, target); + if (pixelPushBytes != 0u) pixelLayout.pushConstantSizeBytes = pixelPushBytes; ShaderRecompiler::RecompileRequest fragment{ {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {WaveSize, 0, pixelUserData, std::nullopt, PixelInfo(shaderControl), std::nullopt, pixelMemory}, target, - PixelPushLayout(vertexPush, target) + pixelLayout }; fragment.useCache = false; const auto pixelResult = ShaderRecompiler::Recompile(fragment); @@ -350,6 +352,14 @@ int main() { const std::array colorData{0x3f800000u}; Draw(*device, userColor, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, colorData); Check(all, "dynamic reference preserves the adjacent shader-data push payload"); + auto bufferColor = std::vector(alternating.begin(), alternating.end()); + *std::find(bufferColor.begin(), bufferColor.end(), 0x7e0e02f2u) = 0x7e0e0208u; + bufferColor.insert(bufferColor.begin(), {0xf4200200u, 0xfa000000u, 0xbf8cc07fu}); + alignas(256) const std::array colorBuffer{0x3f800000u, 0u, 0u, 0u}; + const auto colorDescriptor = BufferDescriptor(colorBuffer.data(), 4u, static_cast(colorBuffer.size())); + const std::array colorMemory{{{reinterpret_cast(colorBuffer.data()), std::as_bytes(std::span(colorBuffer))}}}; + Draw(*device, bufferColor, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, colorDescriptor, colorMemory, {}, sizeof(std::uint32_t)); + Check(all, "dynamic reference keeps guest buffer offsets in shader data outside the push constants"); auto guarded = std::vector(alternating.begin(), alternating.end()); *std::find(guarded.begin(), guarded.end(), 0x7e0e02f2u) = 0x7e0e020cu; guarded.insert(guarded.begin(), {0xf4040280u, 0xfa000018u, 0xf4000305u, 0xfa000000u, 0xbf8cc07fu}); @@ -369,6 +379,29 @@ int main() { catch (const std::exception& error) { faultThrown = std::string(error.what()).find("BDA") != std::string::npos; } Require(faultThrown, "poisoned readonly SRT replay did not throw a host fault"); Require(std::ranges::all_of(Pixels, [](std::byte value) { return value == Kept; }), "poisoned replay published guest color results before its fault"); + alignas(256) std::array columnValues{}; + alignas(256) std::array columnTexels{}; + columnValues[0] = 0x2fu; + for (std::uint32_t x = 0; x < Width; ++x) { + columnValues[x + 1u] = 0x30u + x; + columnTexels[x] = 1.0f; + } + constexpr std::array bufferImageCode{ + 0x7e060f00u, 0x7e080280u, 0xf0001108u, 0x00010503u, 0xe0302000u, 0x80000603u, 0xe0302000u, 0x80030803u, + 0xbf8c3f70u, 0x7e0e02f2u, 0xf8000082u, 0x00000600u, 0xf800180fu, 0x07050805u, 0xbf810000u, + }; + const auto columnBuffer = BufferDescriptor(columnValues.data() + 1, 4u, Width); + const auto alignedBuffer = BufferDescriptor(columnValues.data(), 4u, Width + 1u); + const auto texelAddress = static_cast(reinterpret_cast(columnTexels.data())); + const std::array columnTexture{static_cast(texelAddress >> 8u), static_cast((texelAddress >> 40u) & 0xffu) | (22u << 20u) | (((Width - 1u) & 3u) << 30u), (Width - 1u) >> 2u, 0xfacu | (9u << 28u), 0u, 0u, 0u, 0u}; + std::vector bufferImageData(columnBuffer.begin(), columnBuffer.end()); + bufferImageData.insert(bufferImageData.end(), columnTexture.begin(), columnTexture.end()); + bufferImageData.insert(bufferImageData.end(), alignedBuffer.begin(), alignedBuffer.end()); + Draw(*device, PlainPixelCode, plain, replace(0u)); + Draw(*device, bufferImageCode, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, bufferImageData); + Check(all, "a replayed export beside an image draws the image's texels"); + Draw(*device, PlainPixelCode, plain, equal(0x35u)); + Check([](std::uint32_t x, std::uint32_t) { return x == 5u; }, "a replayed export beside an image reads its guest buffer at a 4-byte offset"); std::puts("stencil replay tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StorageCacheBudget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StorageCacheBudget.cpp index a3a273ff50..87090fa580 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StorageCacheBudget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StorageCacheBudget.cpp @@ -162,6 +162,7 @@ int main(int argc, char** argv) { Require(first != nullptr, "surface 0 has no cached storage image"); Require(AgcDriver::Graphics::SharedResourceCache().Size() != 0, "the storage cache workload did not populate the resource cache"); const auto held = std::max(first->AllocationBytes(), first->GuestBytes()); + const auto allocated = first->AllocationBytes(); const auto reused = static_cast(FixedBudget / held) + 1u; std::printf("texture cache budget %llu MiB; each 512x512 32_UINT surface holds %llu MiB; cyclic set of %u surfaces (%llu MiB)\n", static_cast(budget >> 20u), static_cast(held >> 20u), reused, static_cast((reused * held) >> 20u)); Require(budget >= reused * held, "the cyclic set exceeds the test cache budget"); @@ -183,8 +184,8 @@ int main(int argc, char** argv) { Require(cached == (surface >= 2u), "surface " + std::to_string(surface) + (cached ? " is still cached" : " was evicted") + " after " + std::to_string(capacity + 2u) + " surfaces under a budget of " + std::to_string(capacity) + " of them"); Require(cached == !images[surface].expired(), "surface " + std::to_string(surface) + (cached ? " is cached but its weak image expired" : " was evicted but its resource cache image is still alive")); } - const auto released = AgcDriver::Graphics::ReleaseTextureCaches(held); - Require(released >= held, "a release of one image's bytes freed " + std::to_string(released >> 20u) + " MiB, less than the " + std::to_string(held >> 20u) + " MiB of the oldest image"); + const auto released = AgcDriver::Graphics::ReleaseTextureCaches(allocated); + Require(released == allocated, "a release of one image's bytes counted " + std::to_string(released) + " bytes, not its " + std::to_string(allocated) + "-byte allocation"); Require(StorageTexture::FindLive(base + 2u * Stride, SurfaceBytes) == nullptr && images[2].expired(), "the oldest cached storage image survived a release of its bytes"); Require(StorageTexture::FindLive(base + 3u * Stride, SurfaceBytes) != nullptr && !images[3].expired(), "a release of one image's bytes evicted more than the oldest image"); Require(StorageTexture::FindLive(base + (capacity + 1u) * Stride, SurfaceBytes) != nullptr, "a release of one image's bytes evicted the newest image"); @@ -194,11 +195,11 @@ int main(int argc, char** argv) { Require(Store(*device, base, 4) != nullptr, "surface 4 is not cached"); for (std::uint32_t surface = 5; surface < capacity + 2u; ++surface) Require(Store(*device, base, surface) != nullptr, "surface " + std::to_string(surface) + " is not cached"); } - Require(AgcDriver::Graphics::ReleaseTextureCaches(held) >= held, "a release with a dirty oldest image freed nothing"); + Require(AgcDriver::Graphics::ReleaseTextureCaches(allocated) == allocated, "a release with a dirty oldest image did not free one clean allocation"); Require(StorageTexture::FindLive(base + 3u * Stride, SurfaceBytes) != nullptr, "a release took the dirty oldest image before a clean one"); Require(StorageTexture::FindLive(base + 4u * Stride, SurfaceBytes) == nullptr && images[4].expired(), "a release did not take the oldest clean image"); const auto rest = AgcDriver::Graphics::ReleaseTextureCaches(~std::uint64_t{0}); - Require(rest >= held * (capacity - 3u), "a release of everything freed " + std::to_string(rest >> 20u) + " MiB of the " + std::to_string((held * (capacity - 3u)) >> 20u) + " MiB cached"); + Require(rest == allocated * (capacity - 3u), "a release of everything counted " + std::to_string(rest) + " bytes, not the " + std::to_string(allocated * (capacity - 3u)) + " bytes allocated"); device->WaitIdle(); for (std::uint32_t surface = 0; surface < capacity + 2u; ++surface) { if (surface == 3u) continue; @@ -210,7 +211,7 @@ int main(int argc, char** argv) { } StorageTexture::FlushPending(base + 3u * Stride, static_cast(SurfaceBytes), nullptr, "test"); device->WaitIdle(); - Require(AgcDriver::Graphics::ReleaseTextureCaches(~std::uint64_t{0}) >= held, "a release did not take the image once its results were stored"); + Require(AgcDriver::Graphics::ReleaseTextureCaches(~std::uint64_t{0}) == allocated, "a release did not take the image once its results were stored"); Require(StorageTexture::FindLive(base + 3u * Stride, SurfaceBytes) == nullptr, "surface 3 survived a release after its results were stored"); Require(AgcDriver::Graphics::ReleaseTextureCaches(held) == 0, "a release from empty caches reported freed bytes"); } catch (...) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StorageFileWrite.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StorageFileWrite.cpp index 6fd7ebae81..8658f5b45b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StorageFileWrite.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StorageFileWrite.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" @@ -35,27 +38,6 @@ #endif class FileStream; -struct GuestIovec { void* base; std::size_t length; }; - -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t); -std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelWritev(int, const GuestIovec*, int); -std::int64_t APS5_VABI sceKernelPwritev(int, const GuestIovec*, int, std::int64_t); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -std::int64_t APS5_VABI _write_nid_postfix(int, const void*, std::size_t); -std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI writev_nid_postfix(int, const GuestIovec*, int); -std::int64_t APS5_VABI pwritev_nid_postfix(int, const GuestIovec*, int, std::int64_t); -int APS5_VABI sceKernelAioSubmitWriteCommands(KernelAioRwRequest*, std::int32_t, std::int32_t, std::int32_t*); -int APS5_VABI sceKernelAioSubmitWriteCommandsMultiple(KernelAioRwRequest*, std::int32_t, std::int32_t, std::int32_t*); -int APS5_VABI sceKernelAioDeleteRequest(std::int32_t, std::int32_t*); -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -std::size_t APS5_VABI fwrite_nid_postfix(const void*, std::size_t, std::size_t, FileStream*); -int APS5_VABI fclose_nid_postfix(FileStream*); -} namespace { @@ -192,7 +174,7 @@ bool CheckWrite(Api api, const char* name, GuestSurface& surface, GuestSurface& const auto hold = ProduceHeld(surface, unrelated); OutputFile file; auto* source = surface.Data() + SourceOffset; - const std::array vectors{{{source, 111}, {nullptr, 0}, {source + 111, 65537}, {source + 65648, TransferBytes - 65648}}}; + const std::array vectors{{{source, 111}, {nullptr, 0}, {source + 111, 65537}, {source + 65648, TransferBytes - 65648}}}; const bool positioned = api == Api::Pwrite || api == Api::PosixPwrite || api == Api::Pwritev || api == Api::PosixPwritev || api == Api::Aio || api == Api::AioMultiple; if (api == Api::Fwrite) { auto* stream = fopen_nid_postfix(file.Name().c_str(), "wb"); @@ -237,7 +219,7 @@ bool CheckWrite(Api api, const char* name, GuestSurface& surface, GuestSurface& KernelAioResult result{}; KernelAioRwRequest request{FileOffset, TransferBytes, source, &result, output}; std::int32_t id = 0; - Require((api == Api::Aio ? sceKernelAioSubmitWriteCommands(&request, 1, 0, &id) : sceKernelAioSubmitWriteCommandsMultiple(&request, 1, 0, &id)) == 0, "file write: AIO submission failed"); + Require((api == Api::Aio ? sceKernelAioSubmitWriteCommands(&request, 1, 1, &id) : sceKernelAioSubmitWriteCommandsMultiple(&request, 1, 1, &id)) == 0, "file write: AIO submission failed"); std::int32_t removed = -1; Require(sceKernelAioDeleteRequest(id, &removed) == 0 && removed == 0, "file write: AIO deletion failed"); Require(result.state == 3, "file write: AIO did not complete"); @@ -326,8 +308,8 @@ void CheckShutdownAndGuard() { OutputFile file; std::vector source(TransferBytes, std::byte{0x5a}); AgcDriver::GuestMemory::SetFlushHook(nullptr); - AgcDriver::GuestMemory::SetReadGuard([](std::uint64_t, std::size_t) { throw std::runtime_error("file source read guard"); }); const auto descriptor = file.Open(); + AgcDriver::GuestMemory::SetReadGuard([](std::uint64_t, std::size_t) { throw std::runtime_error("file source read guard"); }); bool refused = false; try { sceKernelWrite(descriptor, source.data(), source.size()); } catch (const std::runtime_error& error) { refused = std::string(error.what()) == "file source read guard"; } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StoreIdleResults.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StoreIdleResults.cpp index b2935dd9c9..c9c8e08c3a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StoreIdleResults.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StoreIdleResults.cpp @@ -112,7 +112,7 @@ int main(int argc, char** argv) { Store(texels); if (wake) { - const auto pendingDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + const auto pendingDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); while (!AgcDriver::Graphics::StorageTexture::AnyPending() && std::chrono::steady_clock::now() < pendingDeadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "idle wake fixture did not observe pending storage results"); std::this_thread::sleep_for(std::chrono::milliseconds(5)); @@ -127,7 +127,7 @@ int main(int argc, char** argv) { cpu = nextTexels; Store(nextTexels); } - const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); while (!stored() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); Require(stored(), "the queue-idle store did not bring the result to guest memory without a direct flush or further submission"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SubmitProgress.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SubmitProgress.cpp new file mode 100644 index 0000000000..1996782ef5 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SubmitProgress.cpp @@ -0,0 +1,154 @@ +#include "VulkanTestDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include +#include +#include +#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); } +alignas(256) std::array Code{0xf4080200u, 0xfa000000u, 0xbe840381u, 0x9304ff04u, 0x0019660du, 0x8004ff04u, 0x3c6ef35fu, 0x80828102u, 0xbf078002u, 0xbf85fff9u, 0xbf8cc07fu, 0x7e000204u, 0xe0700000u, 0x80020000u, 0xbf810000u}; +std::uint32_t Low(const void* p) { return static_cast(reinterpret_cast(p)); } +std::uint32_t High(const void* p) { return static_cast(reinterpret_cast(p) >> 32u); } +void Set(std::vector& words, std::uint32_t reg, std::uint32_t value) { words.insert(words.end(), {0xc0017600u, reg, value}); } +void Dispatch(std::vector& words, const void* descriptor, std::uint32_t iterations = 32) { + const auto address = reinterpret_cast(Code.data()); + Set(words, 0x20c, static_cast(address >> 8u)); + Set(words, 0x20d, static_cast(address >> 40u)); + for (auto reg : {0x207u, 0x208u, 0x209u}) Set(words, reg, 1); + Set(words, 0x212, 0); Set(words, 0x213, 6); Set(words, 0x240, Low(descriptor)); Set(words, 0x241, High(descriptor)); Set(words, 0x242, iterations); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); +} +void Release(std::vector& words, const void* label, std::uint32_t value) { + const std::uint32_t interrupt = value % 2 == 0 ? 2 : 0; + words.insert(words.end(), {0xc0064900u, 0x514u, (2u << 29u) | (interrupt << 24u), Low(label), High(label), value, 0u, 0u}); +} +void Await(const std::uint32_t* label, std::uint32_t value) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (*static_cast(label) != value) { + Require(std::chrono::steady_clock::now() < deadline, "submission lost its real GPU completion store"); + std::this_thread::yield(); + } + AgcDriverWaitIdle_nid_postfix(); +} +} + +int main() { + void* block = nullptr; + try { + auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; device.reset(); +#ifdef _WIN32 + block = VirtualAlloc(nullptr, 65536, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); +#else + block = std::aligned_alloc(65536, 65536); +#endif + Require(block != nullptr, "cannot allocate submit-progress guest memory"); + std::memset(block, 0, 65536); + GuestAllocations::Mutation().Add(block, 65536, true, true, true); + auto* output = static_cast(block); + auto* descriptor = output + 64; auto* label = output + 128; auto* neighbor = output + 192; + descriptor[0] = Low(output); descriptor[1] = High(output) & 0xffffu; descriptor[2] = 16; descriptor[3] = 0x31016facu; + *neighbor = 0xabcdef12u; + const auto submit = [&](std::uint32_t value) { + *label = 0; label[1] = 0xfeedbeefu; + AgcDriver::GuestMemory::MarkWritten(reinterpret_cast(label), 8); + std::vector words; Dispatch(words, descriptor); + if (value % 2 != 0) { + auto* selfGate = output + 384; selfGate[0] = 0; selfGate[1] = 0; + AgcDriver::GuestMemory::MarkWritten(reinterpret_cast(selfGate), 8); + Release(words, selfGate, 1); + if (value % 3 == 0) words.insert(words.end(), {0xc0079300u, 0x13u, Low(selfGate), High(selfGate), 1, 0, 0xffffffffu, 0xffffffffu, 4}); + else words.insert(words.end(), {0xc0053c00u, 0x13u, Low(selfGate), High(selfGate), 1, 0xffffffffu, 4}); + } + Release(words, label, value); + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + AgcDriver::Submit(&packet, 0); + }; + submit(1); Await(label, 1); + std::uint32_t ready = 0; + for (std::uint32_t value = 2; value < 66; ++value) { + submit(value); + ready += *static_cast(label) == value; + Await(label, value); + Require(*label == value, "submission lost its real GPU completion store"); + Require(label[1] == 0, "64-bit GPU marker did not replace its genuine CPU-written high word"); + Require(*neighbor == 0xabcdef12u, "progress wait damaged neighboring guest bytes"); + } + std::cout << ready << "/64 small dependent submissions completed at return\n"; + std::uint32_t prefixesReady = 0; + for (std::uint32_t index = 0; index < 16; ++index) { + auto* continued = output + 320; + *continued = 0; *label = 0; + AgcDriver::GuestMemory::MarkWritten(reinterpret_cast(label), 8); + AgcDriver::GuestMemory::MarkWritten(reinterpret_cast(continued), 8); + std::vector prefix; Dispatch(prefix, descriptor); Release(prefix, label, 100 + index); + prefix.insert(prefix.end(), {0xc0005900u, 0u}); + const auto control = prefix.size() - 1; + Release(prefix, continued, 200 + index); + Packet prefixPacket{prefix.data(), static_cast(prefix.size()), 0, {}}; + AgcDriver::Submit(&prefixPacket, 0); + prefixesReady += *static_cast(label) == 100 + index; + Require(*continued == 0, "an unreleased REWIND continuation executed early"); + std::atomic logicalDone{false}, waiterFailed{false}; + std::thread waiter; + bool earlyLogicalCompletion = false; + if (index == 0) { + waiter = std::thread([&] { + try { AgcDriverWaitIdle_nid_postfix(); logicalDone.store(true, std::memory_order_release); } + catch (...) { waiterFailed.store(true, std::memory_order_release); } + }); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + earlyLogicalCompletion = logicalDone.load(std::memory_order_acquire); + } + std::atomic_ref(prefix[control]).store(0x80000000u, std::memory_order_release); + AgcDriver::GuestMemory::MarkWritten(reinterpret_cast(&prefix[control]), 4); + Await(label, 100 + index); Await(continued, 200 + index); + if (waiter.joinable()) waiter.join(); + Require(!waiterFailed.load() && !earlyLogicalCompletion, "prefix acknowledgement completed the unreleased logical submission"); + } + std::cout << prefixesReady << "/16 finite REWIND prefixes completed at return\n"; + auto* gate = output + 256; *gate = 0; *label = 0; + AgcDriver::GuestMemory::MarkWritten(reinterpret_cast(gate), 4); + AgcDriver::GuestMemory::MarkWritten(reinterpret_cast(label), 4); + std::vector waiting{0xc0053c00u, 0x13u, Low(gate), High(gate), 1, 0xffffffffu, 0x19u}; + Release(waiting, label, 77); + Packet waitPacket{waiting.data(), static_cast(waiting.size()), 0, {}}; + AgcDriver::Submit(&waitPacket, 0); + Require(*label == 0, "dependent wait fabricated its completion"); + *gate = 1; + AgcDriver::GuestMemory::MarkWritten(reinterpret_cast(gate), 4); + Await(label, 77); + *label = 0; + AgcDriver::GuestMemory::MarkWritten(reinterpret_cast(label), 4); + std::vector slow; Dispatch(slow, descriptor, 1u << 22u); Release(slow, label, 78); + Packet slowPacket{slow.data(), static_cast(slow.size()), 0, {}}; + const auto began = std::chrono::steady_clock::now(); + AgcDriver::Submit(&slowPacket, 0); + Require(std::chrono::steady_clock::now() - began < std::chrono::milliseconds(100), "progress budget became an unbounded GPU drain"); + Await(label, 78); + Require(*neighbor == 0xabcdef12u, "pending progress work damaged neighboring guest bytes"); + AgcDriverShutdown_nid_postfix(); + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { AgcDriverShutdown_nid_postfix(); } catch (...) {} + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp index 87c935f0d2..7f13530d09 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -156,6 +157,22 @@ alignas(256) constexpr std::array LocalIndexAsData{ 0x0000030a, 0xd8340010, 0x0000070a, 0xbf8cc07f, 0xbefd2106, }; +alignas(256) constexpr std::array LocalInactiveIndex{ + 0x7e108704, 0x7da80b02, 0x160e06a8, 0x7e060302, 0xd9340020, 0x00000207, 0xbe802006, +}; + +alignas(256) constexpr std::array LocalRestoredIndex{ + 0x7e108704, 0x160e06a8, 0x7da80b02, 0x7e060302, 0xbefe04c1, 0xd9340020, 0x00000207, 0xbe802006, +}; + +alignas(256) constexpr std::array LocalObservedWindow{ + 0xbefc0383, 0x7da80b02, 0x160e06a8, 0x7e108704, 0xd9340020, 0x00000807, 0xbe802006, +}; + +alignas(256) constexpr std::array LocalNegatedConjunction{ + 0x7d880b02, 0x160e06a8, 0x02100e80, 0x7da20d02, 0x87fe6a7e, 0xbefe087e, 0xd9340020, 0x00000807, 0xbe802006, +}; + alignas(256) constexpr std::array HullDescendingRecords{ 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, @@ -946,6 +963,28 @@ void RequireMergedHullWithoutSubgroups(const ShaderRecompiler::SpirvTarget& targ } } +void RequireMergedHullConstantIndices(const ShaderRecompiler::SpirvTarget& target) { + std::uint32_t cursor = 0; + const auto hull = Compile(target, ShaderStage::TessellationControl, MergedHullCode, cursor, MergedTriangles); + const auto words = hull.spirv.Words(); + std::unordered_set constants; + std::unordered_set folded; + for (std::size_t at = 5; at + 1u < words.size(); at += words[at] >> 16u) { + const auto count = words[at] >> 16u; + Require(count != 0u && at + count <= words.size(), "the merged hull program compiled to a malformed SPIR-V instruction"); + const auto opcode = words[at] & 0xffffu; + if (opcode == spv::OpConstant && count >= 3u) constants.insert(words[at + 2u]); + const bool integer = opcode == spv::OpIAdd || opcode == spv::OpIMul || opcode == spv::OpUDiv || opcode == spv::OpUMod || opcode == spv::OpShiftRightLogical || opcode == spv::OpBitwiseAnd; + const auto operand = [&](std::uint32_t id) { return constants.contains(id) || folded.contains(id); }; + if (integer && count == 5u && operand(words[at + 3u]) && operand(words[at + 4u])) folded.insert(words[at + 2u]); + if (opcode == spv::OpAccessChain) { + for (std::uint32_t index = 4u; index < count; ++index) { + Require(!folded.contains(words[at + index]), "the merged hull program indexes a tessellation attribute with arithmetic on constants instead of a constant, which AMD's Windows driver reads wrongly"); + } + } + } +} + void RequireRegisteredMergedStages() { const auto low = [](const auto& code) { return static_cast(reinterpret_cast(code.data()) >> 8u); }; const auto high = [](const auto& code) { return static_cast(reinterpret_cast(code.data()) >> 40u); }; @@ -982,6 +1021,13 @@ int main() { ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainNeighbourPoint, "neither its patch's control points nor a patch-constant record", RecordPatches); ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainCulledPrimitive, "passthrough primitive"); ExpectRefusal(target, ShaderStage::Local, LocalIndexAsData, "outside a tessellation ring or LDS address"); + { + std::uint32_t cursor = 0; + Require(!Compile(target, ShaderStage::Local, LocalInactiveIndex, cursor).spirv.empty(), "a local program whose relative index survives only on inactive lanes was not compiled"); + } + ExpectRefusal(target, ShaderStage::Local, LocalRestoredIndex, "outside a tessellation ring or LDS address"); + ExpectRefusal(target, ShaderStage::Local, LocalObservedWindow, "outside a tessellation ring or LDS address"); + ExpectRefusal(target, ShaderStage::Local, LocalNegatedConjunction, "outside a tessellation ring or LDS address"); ExpectRefusal(target, ShaderStage::TessellationControl, HullRelativeData, "GE_CNTL.PRIM_GRP_SIZE is 0", RecordPatches); ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainRelativeData, "GE_CNTL.PRIM_GRP_SIZE is 0", RecordPatches); ExpectRefusal(target, ShaderStage::TessellationControl, HullCode, "outside its patch's quad factors", {{3u, 3u, 2u, 2u, 2u}, Indices}); @@ -1003,6 +1049,7 @@ int main() { ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullOutsidePatch, "reads LDS outside its patch's control point records", MergedTriangles); ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullReadLane, "another lane's register", MergedTriangles); RequireMergedHullWithoutSubgroups(target); + RequireMergedHullConstantIndices(target); RequirePatches(*device, target, MergedHullCode, "merged hull program", MergedTriangles, MergedHullCode); RequirePatches(*device, target, MergedHullRegisters, "merged hull program storing its local part's registers", MergedTriangles, MergedHullRegisters); RequireCounterClockwise(*device, target); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VCvtPkU8F32RoundMode.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VCvtPkU8F32RoundMode.cpp new file mode 100644 index 0000000000..88725601dd --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VCvtPkU8F32RoundMode.cpp @@ -0,0 +1,150 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0x34040082u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xd55e000au, 0x02010104u, 0xe0701000u, + 0x80010a02u, 0xbf810000u, +}; + +alignas(256) std::array RoundModeCode{ + 0x34020084u, 0x34040082u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xbfa40000u, 0xd55e000au, 0x02010104u, + 0xe0701000u, 0x80010a02u, 0xbf810000u, +}; + +struct PackVector { + std::uint32_t value; + std::uint32_t denormalsFlushed[4]; + std::uint32_t denormalsKept[4]; +}; + +constexpr std::array Vectors{{ + {0x00000000u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0x3e000000u, {0x00u, 0x01u, 0x00u, 0x00u}, {0x00u, 0x01u, 0x00u, 0x00u}}, + {0x3e800000u, {0x00u, 0x01u, 0x00u, 0x00u}, {0x00u, 0x01u, 0x00u, 0x00u}}, + {0x3f000000u, {0x00u, 0x01u, 0x00u, 0x00u}, {0x00u, 0x01u, 0x00u, 0x00u}}, + {0x3f400000u, {0x01u, 0x01u, 0x00u, 0x00u}, {0x01u, 0x01u, 0x00u, 0x00u}}, + {0x3f800000u, {0x01u, 0x01u, 0x01u, 0x01u}, {0x01u, 0x01u, 0x01u, 0x01u}}, + {0x3fa00000u, {0x01u, 0x02u, 0x01u, 0x01u}, {0x01u, 0x02u, 0x01u, 0x01u}}, + {0x3fc00000u, {0x02u, 0x02u, 0x01u, 0x01u}, {0x02u, 0x02u, 0x01u, 0x01u}}, + {0x40200000u, {0x02u, 0x03u, 0x02u, 0x02u}, {0x02u, 0x03u, 0x02u, 0x02u}}, + {0x40600000u, {0x04u, 0x04u, 0x03u, 0x03u}, {0x04u, 0x04u, 0x03u, 0x03u}}, + {0x40700000u, {0x04u, 0x04u, 0x03u, 0x03u}, {0x04u, 0x04u, 0x03u, 0x03u}}, + {0x42ff0000u, {0x80u, 0x80u, 0x7fu, 0x7fu}, {0x80u, 0x80u, 0x7fu, 0x7fu}}, + {0x43008000u, {0x80u, 0x81u, 0x80u, 0x80u}, {0x80u, 0x81u, 0x80u, 0x80u}}, + {0x43482000u, {0xc8u, 0xc9u, 0xc8u, 0xc8u}, {0xc8u, 0xc9u, 0xc8u, 0xc8u}}, + {0x437e8000u, {0xfeu, 0xffu, 0xfeu, 0xfeu}, {0xfeu, 0xffu, 0xfeu, 0xfeu}}, + {0x437ea000u, {0xffu, 0xffu, 0xfeu, 0xfeu}, {0xffu, 0xffu, 0xfeu, 0xfeu}}, + {0x437ee000u, {0xffu, 0xffu, 0xfeu, 0xfeu}, {0xffu, 0xffu, 0xfeu, 0xfeu}}, + {0x437f0000u, {0xffu, 0xffu, 0xffu, 0xffu}, {0xffu, 0xffu, 0xffu, 0xffu}}, + {0x437f4000u, {0xffu, 0xffu, 0xffu, 0xffu}, {0xffu, 0xffu, 0xffu, 0xffu}}, + {0x437f7fffu, {0xffu, 0xffu, 0xffu, 0xffu}, {0xffu, 0xffu, 0xffu, 0xffu}}, + {0x437f8000u, {0xffu, 0xffu, 0xffu, 0xffu}, {0xffu, 0xffu, 0xffu, 0xffu}}, + {0x437fe000u, {0xffu, 0xffu, 0xffu, 0xffu}, {0xffu, 0xffu, 0xffu, 0xffu}}, + {0x43800000u, {0xffu, 0xffu, 0xffu, 0xffu}, {0xffu, 0xffu, 0xffu, 0xffu}}, + {0x501502f9u, {0xffu, 0xffu, 0xffu, 0xffu}, {0xffu, 0xffu, 0xffu, 0xffu}}, + {0xbe000000u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0xbf000000u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0xbeffffffu, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0xbf800000u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0xd01502f9u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0x00000001u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x01u, 0x00u, 0x00u}}, + {0x80000001u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0x007fffffu, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x01u, 0x00u, 0x00u}}, + {0x00800000u, {0x00u, 0x01u, 0x00u, 0x00u}, {0x00u, 0x01u, 0x00u, 0x00u}}, + {0x7f800000u, {0xffu, 0xffu, 0xffu, 0xffu}, {0xffu, 0xffu, 0xffu, 0xffu}}, + {0xff800000u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0x7fc00000u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0xffc00000u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, + {0x7f800001u, {0x00u, 0x00u, 0x00u, 0x00u}, {0x00u, 0x00u, 0x00u, 0x00u}}, +}}; + +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, std::span code, ShaderRecompiler::ShaderFloatMode floatMode) { + Input.fill(0u); + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) Input[lane * Inputs] = Vectors[lane].value; + 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::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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(bool kept, std::uint32_t rounding, bool ieee, const std::string& setting) { + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) { + const auto& vector = Vectors[lane]; + const auto expected = kept ? vector.denormalsKept[rounding] : vector.denormalsFlushed[rounding]; + Require(Output[lane] == expected, std::string("v_cvt_pk_u8_f32 round mode ") + std::to_string(rounding) + setting + (kept ? ", f32 denormals kept" : ", f32 denormals flushed") + + (ieee ? ", IEEE=1" : ", IEEE=0") + " of " + Hex(vector.value) + " is " + Hex(Output[lane]) + ", expected " + Hex(expected)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (std::uint32_t denormals = 0; denormals < 4u; ++denormals) { + const bool kept = (denormals & 1u) != 0u; + const std::string field = ", f32 denormal mode " + std::to_string(denormals); + for (std::uint32_t rounding = 0; rounding < 4u; ++rounding) { + for (const bool ieee : {false, true}) { + Run(*device, Code, ShaderRecompiler::ShaderFloatMode{0xc0u | denormals << 4u | rounding, true, ieee, false}); + Check(kept, rounding, ieee, field); + RoundModeCode[5] = 0xbfa40000u | rounding; + Run(*device, RoundModeCode, ShaderRecompiler::ShaderFloatMode{0xc0u | denormals << 4u, true, ieee, false}); + Check(kept, rounding, ieee, field + " set by s_round_mode"); + } + } + } + std::puts("v_cvt_pk_u8_f32 round mode 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/VertexClipCullDistances.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDistances.cpp index 08adb9fde4..23c82c25eb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDistances.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDistances.cpp @@ -18,6 +18,7 @@ using ShaderRecompiler::ShaderStage; constexpr std::uint32_t Width = 192; constexpr std::uint32_t Height = 128; constexpr std::uint32_t DistanceVector = 0x00400000u; +constexpr std::uint32_t CullDistanceEnables = 0x0000ff00u; alignas(256) std::array Pixels{}; alignas(256) constexpr std::array VertexCode{ @@ -185,14 +186,19 @@ int main() { std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); return VulkanTestSkipped; } + const bool cullDistance = TargetHasCapability(device->Target(), spv::CapabilityCullDistance); + if (!cullDistance) std::puts("cull distance cases skipped, the device has no shaderCullDistance"); + const auto runs = [&](const Row& row) { return cullDistance || (row.control & CullDistanceEnables) == 0u; }; for (const auto waveSize : {64u, 32u}) { for (const auto& row : Rows) { + if (!runs(row)) continue; Draw(*device, waveSize, row, false); Check(row, waveSize, false); } } if (device->Target().mesh.has_value()) { for (const auto& row : Rows) { + if (!runs(row)) continue; Draw(*device, 64u, row, true); Check(row, 64u, true); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDraw.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDraw.cpp index 076e006c49..b4d62cdc54 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDraw.cpp @@ -33,6 +33,7 @@ using AgcDriver::Graphics::Require; constexpr std::uint32_t Width = 192; constexpr std::uint32_t Height = 128; constexpr std::uint32_t DistanceVector = 0x00400000u; +constexpr std::uint32_t CullDistanceEnables = 0x0000ff00u; constexpr std::uint32_t SetContextReg = 0x69u; constexpr std::uint32_t SetShReg = 0x76u; constexpr std::uint32_t SetUconfigReg = 0x79u; @@ -212,14 +213,18 @@ int main(int argc, char** argv) { const bool noKey = argc > 1 && std::string(argv[1]) == "no-key"; if (noKey) Require(std::getenv("APS5_NO_DRAW_KEY") != nullptr, "the no-key run must start with APS5_NO_DRAW_KEY set"); PositionExportLayoutTests(); + bool cullDistance = false; { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; + cullDistance = TargetHasCapability(device->Target(), spv::CapabilityCullDistance); } + if (!cullDistance) std::puts("cull distance steps skipped, the device has no shaderCullDistance"); ColorMemory = AllocateWatched(2 * Block); Register(VertexShader, VertexCode.data(), sizeof(VertexCode), 2); Register(PixelShader, PixelCode.data(), sizeof(PixelCode), 1); for (const auto& step : Steps) { + if (!cullDistance && (step.control & CullDistanceEnables) != 0u) continue; std::array vertices{}; for (std::size_t i = 0; i < vertices.size(); ++i) { vertices[i] = {{PositionX[i], PositionY[i], 0.5f, 1.0f}, {step.component0[i], step.component1[i], step.component2[i], 0.0f}}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VertexPointSize.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VertexPointSize.cpp new file mode 100644 index 0000000000..253201b452 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VertexPointSize.cpp @@ -0,0 +1,165 @@ +#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 +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Extent = 64u; +constexpr std::uint32_t MiscVector = 1u << 21u; +constexpr std::uint32_t VertexPointSize = 1u << 16u; +alignas(256) std::array pixels{}; +alignas(256) std::array vertices{}; +constexpr std::array positionCode{0xf4080100u, 0xfa000000u, 0x4a0a0a02u, 0x4a0a0b08u, 0x4a0a0a03u, 0xe00c2000u, 0x80010005u, 0xbf8c3f70u, 0xf80008cfu, 0x03020100u, 0xbf810000u}; +constexpr std::array pixelCode{0x7e0002f2u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u}; + +std::vector SizeExportCode(float size) { + std::vector code(positionCode.begin(), positionCode.begin() + 8); + code.insert(code.end(), {0x7e0802ffu, std::bit_cast(size), 0xf80000cfu, 0x03020100u, 0xf80008d1u, 0x00000004u, 0xbf810000u}); + return code; +} + +struct PointSize { + float size = 1.0f; + float minimum = 0.0f; + float maximum = 0.0f; + bool fromVertex = false; +}; + +struct Square { + unsigned pixels = 0; + std::uint32_t left = Extent, top = Extent, right = 0, bottom = 0; +}; + +std::vector RunCase(AgcDriver::VulkanDevice& device, const char* name, std::span vertexCode, const PointSize& points, float offset) { + const std::array, 4> centres{{{16.0f, 16.0f}, {48.0f, 16.0f}, {16.0f, 48.0f}, {48.0f, 48.0f}}}; + vertices.fill(std::byte{0}); + constexpr std::uint32_t stride = 16u; + for (std::size_t index = 0; index < centres.size(); ++index) { + const std::array position{(centres[index][0] + offset) / Extent * 2.0f - 1.0f, (centres[index][1] + offset) / Extent * 2.0f - 1.0f, 0.0f, 1.0f}; + std::memcpy(vertices.data() + index * stride, position.data(), sizeof(position)); + } + std::array descriptor{}; + const auto tableAddress = reinterpret_cast(descriptor.data()); + std::array userData{static_cast(tableAddress), static_cast(tableAddress >> 32u), 0u, 0u}; + const std::array memory{{{tableAddress, std::as_bytes(std::span(descriptor))}}}; + ShaderRecompiler::ShaderVertexStageInfo vertexInfo{}; + vertexInfo.fetchEmbedded = true; + vertexInfo.resourcesNum = 1u; + vertexInfo.resourcesDst[0] = {0, 4, 0, 0}; + vertexInfo.paClVsOutCntl = points.fromVertex ? MiscVector | VertexPointSize : 0u; + vertexInfo.pointList = true; + vertexInfo.pointSize = points.size; + vertexInfo.pointSizeMin = points.minimum; + vertexInfo.pointSizeMax = points.maximum; + ShaderRecompiler::RecompileRequest request{{ShaderStage::Vertex, reinterpret_cast(vertexCode.data()), vertexCode, 0, {}}, {32u, 0u, userData, std::nullopt, std::nullopt, vertexInfo, memory}, device.Target(), {0u, 0u, 0u, 96u}}; + const auto address = reinterpret_cast(vertices.data()); + descriptor = {static_cast(address), static_cast(address >> 32u) | (stride << 16u), static_cast(centres.size()), 0x01000facu | (77u << 12u)}; + request.context.vertex->resources[0].fields = descriptor; + const auto vertex = ShaderRecompiler::Recompile(request); + const auto pushBytes = static_cast(vertex.pushConstants.size()); + ShaderRecompiler::ShaderPixelStageInfo pixelInfo{}; + pixelInfo.wave32 = true; + pixelInfo.targetOutputMode[0] = 9u; + pixelInfo.targetExportMapping.fill(0xe4u); + pixelInfo.rasterPrimitive = ShaderRecompiler::RasterPrimitive::Point; + ShaderRecompiler::RecompileRequest pixelRequest{{ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {32u, 0u, {}, std::nullopt, pixelInfo, std::nullopt, {}}, device.Target(), PixelPushLayout(pushBytes, device.Target())}; + const auto pixel = ShaderRecompiler::Recompile(pixelRequest); + const std::array shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, PixelPushOffset(pushBytes, device.Target())}}}; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(pixels.data()), {Extent, Extent}, VK_FORMAT_R8G8B8A8_UNORM, pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Extent, Extent}; + state.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + state.paClVsOutCntl = vertexInfo.paClVsOutCntl; + state.vertexPoints = true; + state.pointSize = points.size; + state.pointSizeMin = points.minimum; + state.pointSizeMax = points.maximum; + state.viewport = {0.0f, static_cast(Extent), static_cast(Extent), -static_cast(Extent), 0.0f, 1.0f}; + state.scissor = {{0, 0}, {Extent, Extent}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15u; + state.blends = {state.blend}; + AgcDriver::Pm4::DrawParameters draw{0u, static_cast(centres.size()), 0u, 1u, 0u, false}; + draw.firstInstance = 0u; + pixels.fill(std::byte{0}); + device.Draw(state, draw, shaders); + device.WaitIdle(); + std::vector squares(centres.size()); + for (std::uint32_t y = 0; y < Extent; ++y) { + for (std::uint32_t x = 0; x < Extent; ++x) { + const auto index = (y * Extent + x) * 4u; + if (pixels[index] == std::byte{0} && pixels[index + 3u] == std::byte{0}) continue; + auto& square = squares[(y >= Extent / 2u ? 2u : 0u) + (x >= Extent / 2u ? 1u : 0u)]; + ++square.pixels; + square.left = std::min(square.left, x); + square.top = std::min(square.top, y); + square.right = std::max(square.right, x + 1u); + square.bottom = std::max(square.bottom, y + 1u); + } + } + std::cout << "case " << name << ":"; + for (const auto& square : squares) std::cout << " " << square.pixels << " [" << square.left << "," << square.top << ")-(" << square.right << "," << square.bottom << ")"; + std::cout << "\n"; + return squares; +} + +void RequireSquares(const std::vector& squares, std::uint32_t side, const char* name) { + for (const auto& square : squares) { + Require(square.pixels == side * side && square.right - square.left == side && square.bottom - square.top == side, std::string(name) + ": a point is not a " + std::to_string(side) + "x" + std::to_string(side) + " square"); + } +} + +void RequireAtMostOnePixel(const std::vector& squares, const char* name) { + for (const auto& square : squares) Require(square.pixels <= 1u, std::string(name) + ": a point with no positive size covers more than one pixel"); +} + +void Run(AgcDriver::VulkanDevice& device) { + std::cout << "device: " << device.DeviceName() << "\n"; + RequireSquares(RunCase(device, "register size 1", positionCode, {1.0f, 1.0f, 1.0f, false}, 0.5f), 1u, "register size 1"); + RequireSquares(RunCase(device, "register size 4", positionCode, {4.0f, 1.0f, 1.0f, false}, 0.0f), 4u, "register size 4"); + RequireSquares(RunCase(device, "register size 3 ignores the clamp", positionCode, {3.0f, 6.0f, 8.0f, false}, 0.5f), 3u, "register size 3 ignores the clamp"); + const auto two = SizeExportCode(2.0f); + RequireSquares(RunCase(device, "vertex size 2 within [1, 8]", two, {1.0f, 1.0f, 8.0f, true}, 0.0f), 2u, "vertex size 2 within [1, 8]"); + const auto six = SizeExportCode(6.0f); + RequireSquares(RunCase(device, "vertex size 6 clamped to 4", six, {1.0f, 0.5f, 4.0f, true}, 0.0f), 4u, "vertex size 6 clamped to 4"); + const auto one = SizeExportCode(1.0f); + RequireSquares(RunCase(device, "vertex size 1 clamped to 5", one, {1.0f, 5.0f, 8.0f, true}, 0.5f), 5u, "vertex size 1 clamped to 5"); + RequireSquares(RunCase(device, "vertex size over a register size", six, {1.0f, 1.0f, 8.0f, true}, 0.0f), 6u, "vertex size over a register size"); + const auto zero = SizeExportCode(0.0f); + RequireAtMostOnePixel(RunCase(device, "vertex size 0 within [0, 4]", zero, {0.0f, 0.0f, 4.0f, true}, 0.5f), "vertex size 0 within [0, 4]"); + const auto negative = SizeExportCode(-3.0f); + RequireAtMostOnePixel(RunCase(device, "vertex size -3 within [0, 4]", negative, {1.0f, 0.0f, 4.0f, true}, 0.5f), "vertex size -3 within [0, 4]"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VertexTableReadOrder.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VertexTableReadOrder.cpp new file mode 100644 index 0000000000..62786e6108 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VertexTableReadOrder.cpp @@ -0,0 +1,205 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" +#include "SceShaders.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::DriverDetail::DrawPipeline; + +void check(bool condition, const std::string& what) { + if (!condition) throw std::runtime_error("vertex table read order: " + what); +} + +constexpr std::size_t UserDataOffset = 288; +constexpr std::size_t DirectOffset = 352; +constexpr std::size_t SemanticOffset = 384; + +alignas(64) std::array BufferTable{0x1000u, 0x20000u, 64u, 0x3000u, 0x7000u, 0x20000u, 32u, 0x3000u}; +alignas(64) std::array AttribTable{(0x2cu << 5u) | 1u}; +alignas(64) std::array Elsewhere{}; +std::vector Header(SemanticOffset + sizeof(ShaderSemantic)); + +void BuildHeader() { + std::array direct{}; + direct.fill(ShaderRegs::AGC_ILLEGAL_DIRECT_OFFSET); + direct[static_cast(ShaderRegs::AgcDirectResourceType::PtrVertexBufferTable)] = 0; + direct[static_cast(ShaderRegs::AgcDirectResourceType::PtrVertexAttribDescTable)] = 2; + std::memcpy(Header.data() + DirectOffset, direct.data(), sizeof(direct)); + ShaderSemantic semantic{}; + semantic.size_in_elements = 1; + std::memcpy(Header.data() + SemanticOffset, &semantic, sizeof(semantic)); + ShaderUserData userData{}; + userData.direct_resource_offset = reinterpret_cast(Header.data() + DirectOffset); + userData.direct_resource_count = ShaderRegs::AGC_DIRECT_RESOURCE_TYPE_COUNT; + std::memcpy(Header.data() + UserDataOffset, &userData, sizeof(userData)); + Shader shader{}; + shader.user_data = reinterpret_cast(Header.data() + UserDataOffset); + shader.input_semantics = reinterpret_cast(Header.data() + SemanticOffset); + shader.num_input_semantics = 1; + std::memcpy(Header.data(), &shader, sizeof(shader)); +} + +std::uint64_t Address(const void* data) { + return static_cast(reinterpret_cast(data)); +} + +std::array UserWords() { + const auto buffer = Address(BufferTable.data()); + const auto attrib = Address(AttribTable.data()); + return {static_cast(buffer), static_cast(buffer >> 32u), static_cast(attrib), static_cast(attrib >> 32u)}; +} + +ShaderRecompiler::ShaderVertexStageInfo Decode(std::vector& reads) { + const auto userWords = UserWords(); + return AgcDriver::DriverDetail::DecodeOrderedVertexStageInfo(Header, Address(Header.data()), userWords, reads, 0); +} + +struct Gate { + std::atomic release{false}; +}; + +std::vector> Gates; + +std::shared_ptr Hold(const void* range, std::size_t bytes, std::function store) { + auto gate = Gates.emplace_back(std::make_shared()); + DrawPipeline::Queue0().Enqueue([gate, store = std::move(store)] { + while (!gate->release.load(std::memory_order_acquire)) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + store(); + }, {{Address(range), Address(range) + bytes}}); + return gate; +} + +void StopQueue() { + AgcDriver::GuestMemory::SetReadGuard(nullptr); + for (const auto& gate : Gates) gate->release.store(true, std::memory_order_release); + DrawPipeline::Active() = false; + DrawPipeline::Queue0().Stop(); +} + +void WaitEmpty() { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (DrawPipeline::QueuedItems() != 0) { + check(std::chrono::steady_clock::now() < deadline, "the pipeline never emptied"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +std::shared_ptr completeAtBufferRead; +const void* completeAtRead = BufferTable.data(); + +void CompleteQueuedStoreAtBufferRead(std::uint64_t address, std::size_t) { + if (address != Address(completeAtRead) || completeAtBufferRead == nullptr) return; + std::exchange(completeAtBufferRead, nullptr)->release.store(true, std::memory_order_release); + WaitEmpty(); +} + +ShaderRecompiler::ShaderVertexStageInfo DecodeBehindQueuedStore(const void* range, std::size_t bytes, std::function store, std::vector& reads) { + WaitEmpty(); + auto gate = Hold(range, bytes, std::move(store)); + std::thread releaser([gate] { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + gate->release.store(true, std::memory_order_release); + }); + ShaderRecompiler::ShaderVertexStageInfo info; + try { + info = Decode(reads); + } catch (...) { + releaser.join(); + throw; + } + releaser.join(); + return info; +} + +} + +int main() { + try { + BuildHeader(); + DrawPipeline::Active() = true; + std::vector reads; + + auto info = Decode(reads); + check(info.fetchEmbedded && info.resourcesNum == 1 && info.resources[0].fields[0] == 0x7000u && info.resources[0].fields[2] == 32u, "the tables did not decode to the second vertex buffer"); + + info = DecodeBehindQueuedStore(BufferTable.data() + 4, 16, [] { + BufferTable[4] = 0x9000u; + BufferTable[6] = 128u; + }, reads); + check(info.resources[0].fields[0] == 0x9000u && info.resources[0].fields[2] == 128u, "the vertex buffer V# was read before the queued store into the vertex buffer table"); + check(reads.size() == 2 && reads[1].address == Address(BufferTable.data() + 4) && std::memcmp(reads[1].Bytes().data(), BufferTable.data() + 4, 16) == 0, "the recorded V# read is not the stored V#"); + check(DrawPipeline::QueuedItems() == 0, "the store into the vertex buffer table is still queued"); + + info = DecodeBehindQueuedStore(AttribTable.data(), 4, [] { AttribTable[0] = 0x2cu << 5u; }, reads); + check(info.resources[0].fields[0] == 0x1000u && info.resources[0].fields[2] == 64u, "the attribute word was read before the queued store into the vertex attribute table"); + check(reads.size() == 2 && reads[0].address == Address(AttribTable.data()) && reads[1].address == Address(BufferTable.data()), "the decode did not read the stored attribute word and the V# it names"); + + WaitEmpty(); + auto gate = Hold(Elsewhere.data(), sizeof(Elsewhere), [] {}); + info = Decode(reads); + check(info.resources[0].fields[0] == 0x1000u, "the decode next to an unrelated queued store changed"); + check(DrawPipeline::QueuedItems() == 1, "a decode that overlaps nothing queued drained the pipeline"); + gate->release.store(true, std::memory_order_release); + WaitEmpty(); + + completeAtBufferRead = Hold(AttribTable.data(), 4, [] { AttribTable[0] = (0x2cu << 5u) | 1u; }); + AgcDriver::GuestMemory::SetReadGuard(&CompleteQueuedStoreAtBufferRead); + auto drains = DrawPipeline::Queue0().DecodeDrains(); + info = Decode(reads); + AgcDriver::GuestMemory::SetReadGuard(nullptr); + check(completeAtBufferRead == nullptr, "the queued attribute store did not complete during the decode"); + check(info.resources[0].fields[0] == 0x9000u && info.resources[0].fields[2] == 128u, "the decode kept the vertex buffer of an attribute word that a queued store replaced during the decode"); + check(reads.size() == 2 && reads[1].address == Address(BufferTable.data() + 4), "the recorded reads are not the stored attribute word and the V# it names"); + check(DrawPipeline::Queue0().DecodeDrains() == drains + 1, "the drain the decode caused was not counted"); + + WaitEmpty(); + auto finished = Hold(BufferTable.data() + 4, 16, [] { + BufferTable[4] = 0xb000u; + BufferTable[6] = 256u; + }); + finished->release.store(true, std::memory_order_release); + WaitEmpty(); + gate = Hold(Elsewhere.data(), sizeof(Elsewhere), [] {}); + drains = DrawPipeline::Queue0().DecodeDrains(); + info = Decode(reads); + check(info.resources[0].fields[0] == 0xb000u && info.resources[0].fields[2] == 256u, "the decode did not read the V# a finished item stored"); + check(DrawPipeline::QueuedItems() == 1 && DrawPipeline::Queue0().DecodeDrains() == drains, "a decode of a table that a finished item wrote drained the pipeline"); + gate->release.store(true, std::memory_order_release); + WaitEmpty(); + + completeAtBufferRead = Hold(Elsewhere.data(), sizeof(Elsewhere), [] { Elsewhere[0] = 1u; }); + completeAtRead = BufferTable.data() + 4; + AgcDriver::GuestMemory::SetReadGuard(&CompleteQueuedStoreAtBufferRead); + drains = DrawPipeline::Queue0().DecodeDrains(); + info = Decode(reads); + AgcDriver::GuestMemory::SetReadGuard(nullptr); + check(completeAtBufferRead == nullptr, "the unrelated queued store did not complete during the decode"); + check(info.resources[0].fields[0] == 0xb000u && DrawPipeline::Queue0().DecodeDrains() == drains, "a store elsewhere that retired during the decode made the decode drain"); + + StopQueue(); + std::puts("vertex table read order tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { StopQueue(); } catch (...) {} + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VertexUserDataTail.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VertexUserDataTail.cpp index df9e6f2c6d..2130d6f232 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/VertexUserDataTail.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VertexUserDataTail.cpp @@ -125,7 +125,7 @@ void ControlStateDraws(std::vector prefix) { CheckBranchColors(activeExec, emptyExec, "EXEC"); } -void VertexTailDraws() { +void VertexTailDraws(bool cullDistance) { std::vector prefix{ 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, 0x7e060280u, 0x7e0802f2u, 0xbe8c03ffu, 0x3f800000u, 0xbf060c0cu, @@ -142,8 +142,12 @@ void VertexTailDraws() { alignas(256) const std::array clipContinuation{0xbf840005u, 0x7e08020cu, 0xf80000cfu, 0x04030201u, 0xf80008dfu, 0x01010101u, 0xbf810000u}; const auto clipped = Check(prefix, clipContinuation, 0x00400001u, clipContinuation.size()); Require(clipped != original && std::count(clipped.begin(), clipped.end(), Kept) > kept, "a clip distance exported by the continuation did not clip the triangle"); - const auto culled = Check(prefix, clipContinuation, 0x00400100u, clipContinuation.size()); - Require(culled == original, "a mixed-sign cull distance discarded the triangle"); + if (cullDistance) { + const auto culled = Check(prefix, clipContinuation, 0x00400100u, clipContinuation.size()); + Require(culled == original, "a mixed-sign cull distance discarded the triangle"); + } else { + std::puts("cull distance draw skipped, the device has no shaderCullDistance"); + } ControlStateDraws(prefix); } @@ -151,11 +155,13 @@ void VertexTailDraws() { int main() { try { + bool cullDistance = false; { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; + cullDistance = TargetHasCapability(device->Target(), spv::CapabilityCullDistance); } - VertexTailDraws(); + VertexTailDraws(cullDistance); AgcDriverShutdown_nid_postfix(); std::puts("vertex user-data tail tests passed"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/WindowLaneSpill.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/WindowLaneSpill.cpp index f597fc79ee..a18422c711 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/WindowLaneSpill.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/WindowLaneSpill.cpp @@ -7,7 +7,13 @@ #include #include #include +#include #include +#ifdef _WIN32 +#include +#else +#include +#endif namespace { @@ -15,7 +21,8 @@ using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; constexpr std::uint32_t Threads = 32; -alignas(256) std::array Output{}; +constexpr std::uint32_t WideThreads = 64; +alignas(256) std::array Output{}; alignas(256) constexpr std::array Code{ 0x7e020300, 0xbefc030c, 0x7e048500, 0xd7610003, 0x00010004, 0xd7610003, 0x00010205, 0xd7610003, 0x00010406, @@ -29,28 +36,111 @@ alignas(256) constexpr std::array IndexedStoreCode{ 0xd7600009, 0x00010303, 0xd760000a, 0x00010503, 0xd760000b, 0x00010703, 0xe0702000, 0x80020001, 0xbf810000, }; +alignas(256) constexpr std::array ReusedRegisterCode{ + 0x7e060300, 0x7e020300, 0xbefc030c, 0x7e048500, 0xd7610003, 0x00010004, 0xd7610003, 0x00010205, 0xd7610003, + 0x00010406, 0xd7610003, 0x00010607, 0x7e048500, 0xbe840380, 0xbe850380, 0xbe860380, 0xbe870380, 0xd7600008, + 0x00010103, 0xd7600009, 0x00010303, 0xd760000a, 0x00010503, 0xd760000b, 0x00010703, 0xe0702000, 0x80020001, + 0xbf810000, +}; + +alignas(256) constexpr std::array OverwrittenSpillCode{ + 0x877c8104, 0x7e040280, 0x7e060280, 0xd7610004, 0x00010008, 0x7e048500, 0x7e0802f2, 0xd7600008, 0x00010104, + 0xf09c0f08, 0x00400802, 0xe0700000, 0x80030800, 0xbf810000, +}; + +alignas(256) constexpr std::array AliasingStoreCode{ + 0x7e020300, 0xbefc030c, 0x7e048500, 0xd7610003, 0x0001000d, 0x7e040287, 0x7e048500, + 0xd7600008, 0x00010103, 0x7e040208, 0xe0702000, 0x80010201, 0xbf810000, +}; + +alignas(256) constexpr std::array WideAliasingStoreCode{ + 0x7e020300, 0xbefc030c, 0x7e048500, 0xd7610003, 0x0001500d, 0x7e040287, 0x7e048500, + 0xd7600008, 0x00015103, 0x7e040208, 0xe0702000, 0x80010201, 0xbf810000, +}; + +alignas(256) constexpr std::array StoreAfterReadCode{ + 0x7e020300, 0xbefc030c, 0x7e048500, 0xd7610003, 0x0001000d, 0xd7600008, + 0x00010103, 0x7e048500, 0x7e040208, 0xe0702000, 0x80010201, 0xbf810000, +}; + +constexpr std::uint32_t Spilled = 0x1234u; + +class StderrCapture { +public: + StderrCapture() { + std::fflush(stderr); + file = std::tmpfile(); + Require(file != nullptr, "cannot open a temporary file for stderr"); +#ifdef _WIN32 + saved = _dup(_fileno(stderr)); + _dup2(_fileno(file), _fileno(stderr)); +#else + saved = dup(fileno(stderr)); + dup2(fileno(file), fileno(stderr)); +#endif + } + std::string Finish() { + std::fflush(stderr); +#ifdef _WIN32 + _dup2(saved, _fileno(stderr)); + _close(saved); +#else + dup2(saved, fileno(stderr)); + close(saved); +#endif + std::rewind(file); + std::string text; + char buffer[4096]; + for (std::size_t read = 0; (read = std::fread(buffer, 1, sizeof(buffer), file)) != 0;) text.append(buffer, read); + std::fclose(file); + std::fwrite(text.data(), 1, text.size(), stderr); + return text; + } + +private: + std::FILE* file = nullptr; + int saved = -1; +}; + std::array BufferDescriptor(const void* data, std::uint32_t count) { const auto address = reinterpret_cast(data); return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x11016facu}; } -void Run(AgcDriver::VulkanDevice& device, std::span code) { - Output.fill(0xdeadbeefu); - std::vector userData(16, 0u); - const auto output = BufferDescriptor(Output.data(), static_cast(Output.size())); - std::copy(output.begin(), output.end(), userData.begin() + 4); +ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code, std::span userData, std::uint32_t waveSize = Threads) { 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}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 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}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, device.Target(), {0, 0, 0, 128} }; request.useCache = false; - const auto result = ShaderRecompiler::Recompile(request); + return ShaderRecompiler::Recompile(request); +} + +std::string Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t relativeIndex = 0, std::uint32_t waveSize = Threads) { + Output.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size())); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[12] = relativeIndex; + userData[13] = Spilled; + const auto result = Compile(device, code, userData, waveSize); + StderrCapture log; device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); + return log.Finish(); +} + +constexpr std::string_view Overwritten = "which holds a scalar spilled with v_writelane_b32 that a later v_readlane_b32 was replaced with"; + +void CheckSpilled(const std::string& log, const std::string& name, std::uint32_t lanes = Threads) { + Require(log.find(Overwritten) == std::string::npos, name + ": reported an overwritten spill: " + log); + for (std::uint32_t tid = 0; tid < lanes; ++tid) { + Require(Output[tid] == Spilled, name + ": lane " + std::to_string(tid) + " stored " + std::to_string(Output[tid]) + ", expected the spilled " + std::to_string(Spilled)); + } } void Check(const std::string& name) { @@ -59,6 +149,17 @@ void Check(const std::string& name) { } } +void CheckRefused(AgcDriver::VulkanDevice& device, std::span code, const std::string& name) { + const std::vector userData(16, 0u); + std::string error; + try { + static_cast(Compile(device, code, userData)); + } catch (const std::exception& thrown) { + error = thrown.what(); + } + Require(error.find("GetSamplerResource dword 0 is not a valid runtime value") != std::string::npos, name + ": " + (error.empty() ? std::string("recompiled") : error)); +} + } int main() { @@ -73,6 +174,20 @@ int main() { Check("window lane spill"); Run(*device, IndexedStoreCode); Check("window lane spill past an indexed store"); + CheckRefused(*device, OverwrittenSpillCode, "window lane spill overwritten by an ordinary write before its v_readlane"); + Run(*device, ReusedRegisterCode); + Check("window lane spill into a register with ordinary writes past an indexed store"); + CheckSpilled(Run(*device, AliasingStoreCode, 0), "window lane spill past an indexed store with m0 = 0"); + const auto aliased = Run(*device, AliasingStoreCode, 1); + Require(aliased.find(Overwritten) != std::string::npos, "window lane spill overwritten by an indexed store with m0 = 1 was not reported: " + aliased); + CheckSpilled(Run(*device, StoreAfterReadCode, 1), "window lane spill overwritten by an indexed store with m0 = 1 after its v_readlane"); + if (device->Target().subgroupSize >= WideThreads) { + CheckSpilled(Run(*device, WideAliasingStoreCode, 0, WideThreads), "wave64 window lane spill in lane 40 past an indexed store with m0 = 0", WideThreads); + const auto wide = Run(*device, WideAliasingStoreCode, 1, WideThreads); + Require(wide.find("stored 40 in lane 40 of vector window slot 1") != std::string::npos, "wave64 window lane spill in lane 40 overwritten by an indexed store with m0 = 1 was not reported: " + wide); + } else { + std::printf("wave64 cases skipped, subgroup size %u\n", device->Target().subgroupSize); + } std::puts("window lane spill tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ZeroStrideVertexFetch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ZeroStrideVertexFetch.cpp index 5715c39d27..0642450135 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ZeroStrideVertexFetch.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ZeroStrideVertexFetch.cpp @@ -124,6 +124,28 @@ void Draw(AgcDriver::VulkanDevice& device, const Case& value) { device.WaitIdle(); } +void DescriptorDiagnostics(AgcDriver::VulkanDevice& device) { + for (std::uint32_t component = 0; component < 4u; ++component) { + for (const auto selector : {2u, 3u}) { + const auto shift = component * 3u; + const Case value{3u, 16u, true, "reserved selector", (0xfacu & ~(7u << shift)) | (selector << shift)}; + const auto fields = VertexBufferDescriptor(value); + char expected[192]; + std::snprintf(expected, sizeof(expected), "reserved prepared vertex component selector %u at location 0 component %u (V#=[%08x %08x %08x %08x])", + selector, component, fields[0], fields[1], fields[2], fields[3]); + bool rejected = false; + try { + Draw(device, value); + } catch (const std::runtime_error& error) { + const std::string message(error.what()); + Require(message.find(expected) != std::string::npos, "reserved selector error did not identify the component and descriptor: " + message); + rejected = true; + } + Require(rejected, "a reserved vertex component selector was accepted"); + } + } +} + void Check(const Case& value) { for (std::uint32_t vertex = 0; vertex < Vertices; ++vertex) { for (std::uint32_t component = 0; component < 4u; ++component) { @@ -142,19 +164,34 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - constexpr std::array cases{{ - {2u, 1u, true, "OOB_SELECT 2, one record"}, + constexpr std::uint32_t Wzyx = 7u | (6u << 3u) | (1u << 6u) | (0u << 9u); + constexpr std::array cases{{ + {0u, 0u, false, "OOB_SELECT 0, no records"}, + {0u, 1u, false, "OOB_SELECT 0, one record"}, + {0u, 16u, false, "OOB_SELECT 0, records cover the element's bytes"}, + {0u, 0xffffffffu, false, "OOB_SELECT 0, NUM_RECORDS 0xffffffff"}, + {0u, 0xffffffffu, false, "OOB_SELECT 0, NUM_RECORDS 0xffffffff, selectors W Z 1 0", Wzyx}, + {1u, 0u, false, "OOB_SELECT 1, no records"}, + {1u, 3u, true, "OOB_SELECT 1, NUM_RECORDS counts the three vertices' records"}, + {1u, 4u, true, "OOB_SELECT 1, NUM_RECORDS past the vertices"}, + {1u, 0xffffffffu, true, "OOB_SELECT 1, NUM_RECORDS 0xffffffff"}, + {1u, 3u, true, "OOB_SELECT 1, NUM_RECORDS 3, selectors W Z 1 0", Wzyx}, {2u, 0u, false, "OOB_SELECT 2, no records"}, - {3u, 16u, true, "OOB_SELECT 3, whole element in range"}, + {2u, 1u, true, "OOB_SELECT 2, one record"}, + {2u, 16u, true, "OOB_SELECT 2, whole element in range"}, {3u, 0u, false, "OOB_SELECT 3, no records"}, - {0u, 16u, true, "OOB_SELECT 0, whole element in range"}, - {3u, 16u, true, "OOB_SELECT 3, selectors W Z 1 0", 7u | (6u << 3u) | (1u << 6u) | (0u << 9u)}, - {3u, 0u, false, "OOB_SELECT 3, no records, selectors W Z 1 0", 7u | (6u << 3u) | (1u << 6u) | (0u << 9u)}, + {3u, 8u, false, "OOB_SELECT 3, the element only partly fits"}, + {3u, 15u, false, "OOB_SELECT 3, the element misses its last byte"}, + {3u, 16u, true, "OOB_SELECT 3, whole element in range"}, + {3u, 0xffffffffu, true, "OOB_SELECT 3, NUM_RECORDS 0xffffffff"}, + {3u, 16u, true, "OOB_SELECT 3, selectors W Z 1 0", Wzyx}, + {3u, 8u, false, "OOB_SELECT 3, partly fits, selectors W Z 1 0", Wzyx}, }}; for (const auto& value : cases) { Draw(*device, value); Check(value); } + DescriptorDiagnostics(*device); std::puts("zero-stride vertex fetch tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/shaders/MultisampleReadback.frag b/core/libs/prx/libSceAgcDriver/tests/shaders/MultisampleReadback.frag index 3a72da6058..2f0e104c90 100644 --- a/core/libs/prx/libSceAgcDriver/tests/shaders/MultisampleReadback.frag +++ b/core/libs/prx/libSceAgcDriver/tests/shaders/MultisampleReadback.frag @@ -10,7 +10,7 @@ void main() { uint y = uint(gl_FragCoord.y); #if READ_DEPTH uint element = (x + y * params.width + params.layer * params.width * params.height) * params.samples + uint(gl_SampleID); - gl_FragDepth = 0.125 + float(element + 1u) / 65536.0; + gl_FragDepth = 0.125 + float(element + 1u) / 1048576.0; #else uint value = x + y * params.width + params.layer * params.width * params.height + uint(gl_SampleID) * 31u; color = uvec4(0x91352700u ^ value, 0x45219b00u ^ (value * 3u), 0xc7391d00u ^ (value * 5u), 0x6b43e100u ^ (value * 7u)); diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp new file mode 100644 index 0000000000..943f962b6e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -0,0 +1,743 @@ +#include "prx/libSceAgcDriver/State/include/Configuration.hpp" +#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" +#include "prx/libSceAgcDriver/Execution/include/FrameDumpIo.hpp" +#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +namespace { + +using AgcDriver::FrameCall; +using AgcDriver::FrameCallKind; +using AgcDriver::FrameCaptureReader; +using AgcDriver::FrameMemoryChunk; +using AgcDriver::FrameMemoryKind; +using AgcDriver::FrameRangeAccess; +using AgcDriver::FrameRangeKind; +using Clock = std::chrono::steady_clock; + +struct Dump { + std::uint64_t address = 0; + std::uint64_t bytes = 0; + std::filesystem::path path; +}; + +struct Options { + std::filesystem::path directory; + std::vector dumps; + std::filesystem::path frames; + bool pace = true; + bool strict = false; + std::chrono::milliseconds paceTimeout{10000}; + std::chrono::milliseconds memoryPaceTimeout{10000}; + std::chrono::milliseconds releaseAfter{3}; + std::chrono::milliseconds drainTimeout{10000}; + bool release = true; + bool synthesize = true; +}; + +class Frames { +public: + void MarkReady() { + std::lock_guard lock(mutex); + ready.push_back(Clock::now()); + } + + void Failed(std::exception_ptr error) { + std::lock_guard lock(mutex); + if (!failure) failure = error; + } + + std::vector Times() const { + std::lock_guard lock(mutex); + return ready; + } + + std::exception_ptr Failure() const { + std::lock_guard lock(mutex); + return failure; + } + +private: + mutable std::mutex mutex; + std::vector ready; + std::exception_ptr failure; +}; + +class ReplayFlip final : public AgcDriver::IFlipRequest { +public: + explicit ReplayFlip(Frames& frames) : frames(frames) {} + void GpuReady(const std::shared_ptr&) override { frames.MarkReady(); } + void Fail(std::exception_ptr error) noexcept override { frames.Failed(error); } + +private: + Frames& frames; +}; + +class ReplayRenderingWait final : public AgcDriver::IRenderingWait { +public: + void Wait() override {} +}; + +class ReplayOutput final : public AgcDriver::IVideoOutput { +public: + explicit ReplayOutput(Frames& frames) : frames(frames) {} + std::shared_ptr Reserve(const AgcDriver::FlipInfo&) override { return std::make_shared(frames); } + std::shared_ptr CaptureRenderingWait(std::uint32_t) override { return std::make_shared(); } + void Fail(std::exception_ptr error) noexcept override { frames.Failed(error); } + +private: + Frames& frames; +}; + +std::uint64_t headerNumber(const std::string& header, std::string_view key) { + const auto quoted = "\"" + std::string(key) + "\":"; + const auto at = header.find(quoted); + if (at == std::string::npos) throw std::runtime_error("frame replay: the capture header has no " + std::string(key)); + return std::strtoull(header.c_str() + at + quoted.size(), nullptr, 10); +} + +void restore(const FrameCaptureReader& reader, const FrameMemoryChunk& chunk) { + reader.Read(chunk, std::span(reinterpret_cast(static_cast(chunk.address)), static_cast(chunk.bytes))); +} + +bool committedWritable(std::uint64_t address, std::uint64_t bytes) { +#ifdef _WIN32 + for (auto at = address; at < address + bytes;) { + MEMORY_BASIC_INFORMATION info{}; + if (VirtualQuery(reinterpret_cast(static_cast(at)), &info, sizeof(info)) == 0 || info.State != MEM_COMMIT) return false; + const auto access = info.Protect & 0xffu; + if ((info.Protect & PAGE_GUARD) != 0 || (access != PAGE_READWRITE && access != PAGE_EXECUTE_READWRITE)) return false; + at = reinterpret_cast(info.BaseAddress) + info.RegionSize; + } + return true; +#else + static_cast(address); + static_cast(bytes); + return false; +#endif +} + +std::pair progress(); + +struct Skipped { + std::vector> ranges; + std::uint64_t bytes = 0; + + void Add(const FrameMemoryChunk& chunk) { + ranges.emplace_back(chunk.address, chunk.address + chunk.bytes); + bytes += chunk.bytes; + } + + bool Overlaps(const FrameMemoryChunk& chunk) const { + return std::any_of(ranges.begin(), ranges.end(), [&](const auto& range) { return chunk.address < range.second && range.first < chunk.address + chunk.bytes; }); + } +}; + +void mapHostRanges(const FrameCaptureReader& reader, Skipped& skipped) { + std::vector host; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Range && chunk.rangeKind == FrameRangeKind::Host) host.push_back(&chunk); + } +#ifdef _WIN32 + constexpr std::uint64_t Granularity = 64u * 1024u; + std::vector> envelopes; + for (const auto* chunk : host) { + const auto begin = chunk->address & ~(Granularity - 1); + const auto end = (chunk->address + chunk->bytes + Granularity - 1) & ~(Granularity - 1); + if (!envelopes.empty() && begin <= envelopes.back().second) envelopes.back().second = std::max(envelopes.back().second, end); + else envelopes.emplace_back(begin, end); + } + std::vector> refused; + for (const auto& [begin, end] : envelopes) { + if (VirtualAlloc(reinterpret_cast(static_cast(begin)), static_cast(end - begin), MEM_RESERVE, PAGE_NOACCESS) == nullptr) refused.emplace_back(begin, end); + } + for (const auto* chunk : host) { + const bool blocked = std::any_of(refused.begin(), refused.end(), [&](const auto& range) { return chunk->address < range.second && range.first < chunk->address + chunk->bytes; }); + if (!blocked && VirtualAlloc(reinterpret_cast(static_cast(chunk->address)), static_cast(chunk->bytes), MEM_COMMIT, PAGE_READWRITE) != nullptr) continue; + if ((chunk->access & FrameRangeAccess::Gpu) != 0) throw std::runtime_error("frame replay: cannot map the GPU-visible host range at " + std::to_string(chunk->address)); + skipped.Add(*chunk); + } +#else + for (const auto* chunk : host) skipped.Add(*chunk); +#endif +} + +Skipped restoreMemory(const FrameCaptureReader& reader) { + std::uintptr_t base = 0; + std::size_t size = 0; + GuestArena::GuestArenaRange_nid_postfix(&base, &size); + if (!reader.Header().empty() && headerNumber(reader.Header(), "arena_base") != base) throw std::runtime_error("frame replay: the guest arena is at a different address than in the capture"); + progress(); + Skipped skipped; + mapHostRanges(reader, skipped); + { + GuestAllocations::Mutation mutation; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind != FrameMemoryKind::Range || chunk.rangeKind == FrameRangeKind::Host) continue; + const bool gpu = (chunk.access & FrameRangeAccess::Gpu) != 0; + if (committedWritable(chunk.address, chunk.bytes)) { + if (!gpu) skipped.Add(chunk); + else std::memset(reinterpret_cast(static_cast(chunk.address)), 0, static_cast(chunk.bytes)); + continue; + } + auto* const pointer = reinterpret_cast(static_cast(chunk.address)); + const auto bytes = static_cast(chunk.bytes); + try { + GuestArena::GuestArenaMarkUsed_nid_postfix(pointer, bytes); + } catch (const std::exception&) { + if (gpu) throw; + skipped.Add(chunk); + continue; + } + GuestArena::GuestArenaCommit_nid_postfix(pointer, bytes, 0x04, bytes); + mutation.Add(pointer, bytes, (chunk.access & FrameRangeAccess::Readable) != 0, (chunk.access & FrameRangeAccess::Writable) != 0, gpu); + } + } + std::vector snapshot; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Snapshot && !skipped.Overlaps(chunk)) snapshot.push_back(&chunk); + } + const auto workers = std::max(1, std::thread::hardware_concurrency()); + for (std::size_t first = 0; first < snapshot.size(); first += workers) { + std::vector> pending; + for (auto index = first; index < std::min(snapshot.size(), first + workers); ++index) { + pending.push_back(std::async(std::launch::async, [&reader, chunk = snapshot[index]] { restore(reader, *chunk); })); + } + for (auto& future : pending) future.get(); + } + return skipped; +} + +std::pair progress() { + std::uint64_t accepted = 0; + std::uint64_t completed = 0; + AgcDriverSerialProgress_nid_postfix(&accepted, &completed); + return {accepted, completed}; +} + +struct Replay { + const Options& options; + const FrameCaptureReader& reader; + Frames frames; + std::map> outputs; + std::mutex memoryMutex; + std::map> groups; + Skipped skipped; + std::atomic paceTimeouts{0}; + std::atomic staleRegistrations{0}; + std::atomic failedRegistrations{0}; + std::atomic submits{0}; + std::atomic deltaBytes{0}; + std::atomic paceWaitedNs{0}; + std::atomic stalled{false}; + std::atomic stalledAt{0}; + std::atomic earlyReleases{0}; + std::atomic lateReleases{0}; + std::atomic syntheticReleases{0}; + std::atomic sequenced{0}; + std::uint64_t unfinished = 0; + std::size_t threads = 0; + bool recording = false; + Clock::time_point startedAt{}; + Clock::time_point endedAt{}; + + double paceWaitedSeconds() const { + return static_cast(paceWaitedNs.load()) / 1e9; + } + + void report() const { + const auto [accepted, completed] = progress(); + std::fprintf(stderr, "[frame-replay] %llu submits, %zu frames ready, %.1f s since the first frame, pacing waited %.1f s (%llu timeouts), serials %llu/%llu\n", + static_cast(submits.load()), frames.Times().size(), std::chrono::duration(Clock::now() - startedAt).count(), paceWaitedSeconds(), + static_cast(paceTimeouts.load()), static_cast(completed), static_cast(accepted)); + } + + static bool paced(FrameCallKind kind) { + return kind == FrameCallKind::Submit || kind == FrameCallKind::Flip || kind == FrameCallKind::ReserveEopFlip || kind == FrameCallKind::Suspend || kind == FrameCallKind::Memory; + } + + void pace(const FrameCall& call) { + if (!options.pace || !recording || !paced(call.kind) || call.accepted < call.completed) return; + if (stalled.load()) { + if (progress().second == stalledAt.load()) return; + stalled.store(false); + } + const auto inFlight = call.accepted - call.completed; + const bool memory = call.kind == FrameCallKind::Memory; + const auto began = Clock::now(); + const auto deadline = began + (memory ? options.memoryPaceTimeout : options.paceTimeout); + const auto waited = [&] { paceWaitedNs += std::chrono::duration_cast(Clock::now() - began).count(); }; + while (true) { + const auto [accepted, completed] = progress(); + if (accepted - completed <= inFlight) return waited(); + if (Clock::now() >= deadline) { + waited(); + if (memory) return; + stalled.store(true); + stalledAt.store(completed); + std::fprintf(stderr, "[frame-replay] pacing gave up at call %llu (%s): serials %llu/%llu, the capture had %llu in flight\n", static_cast(call.ordinal), + std::string(AgcDriver::FrameCallName(call.kind)).c_str(), static_cast(completed), static_cast(accepted), static_cast(inFlight)); + ++paceTimeouts; + if (options.strict) throw std::runtime_error("frame replay: call " + std::to_string(call.ordinal) + " waited longer than the pacing timeout"); + return; + } + std::this_thread::sleep_for(std::chrono::microseconds(50)); + } + } + + void execute(const FrameCall& call) { + pace(call); + switch (call.kind) { + case FrameCallKind::RegisterShader: + registration([&] { AgcDriverRegisterShader_nid_postfix(shader(call.address)); }); + return; + case FrameCallKind::ResolveShaderAbi: + registration([&] { AgcDriverResolveShaderAbi_nid_postfix(shader(call.address), call.context, call.primitive); }); + return; + case FrameCallKind::ResolveGraphicsAbi: + registration([&] { AgcDriverResolveGraphicsAbi_nid_postfix(shader(call.values.at(0)), shader(call.values.at(1)), static_cast(call.values.at(2))); }); + return; + case FrameCallKind::ResolveGraphicsStagesAbi: { + std::vector stages; + for (const auto value : call.values) stages.push_back(shader(value)); + registration([&] { AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, call.context, call.primitive); }); + return; + } + case FrameCallKind::RegisterVideoOutput: { + const auto handle = static_cast(call.values.at(0)); + auto output = std::make_shared(frames); + AgcDriverRegisterVideoOutput_nid_postfix(handle, output); + outputs.emplace(handle, std::move(output)); + return; + } + case FrameCallKind::UnregisterVideoOutput: { + const auto handle = static_cast(call.values.at(0)); + AgcDriverUnregisterVideoOutput_nid_postfix(handle, outputs.at(handle)); + outputs.erase(handle); + return; + } + case FrameCallKind::SetTessellationFactorRing: + sceAgcDriverSetTFRing(reinterpret_cast(static_cast(call.address)), static_cast(call.size)); + return; + case FrameCallKind::Submit: { + const Packet packet{reinterpret_cast(static_cast(call.address)), static_cast(call.size), static_cast(call.flags), {}}; + if (call.queue == 0) sceAgcDriverSubmitDcb(&packet); + else sceAgcDriverSubmitAcb(call.queue, &packet); + if (++submits % 25 == 0) report(); + return; + } + case FrameCallKind::Flip: + AgcDriverSubmitFlip_nid_postfix(flipInfo(call)); + return; + case FrameCallKind::ReserveEopFlip: { + const auto token = static_cast(call.values.at(4)); + if (AgcDriverReserveEopFlipAt_nid_postfix(flipInfo(call), token) != token) throw std::runtime_error("frame replay: EOP flip token " + std::to_string(token) + " was not reserved"); + return; + } + case FrameCallKind::Suspend: + AgcDriverSuspendPoint_nid_postfix(); + return; + case FrameCallKind::Memory: { + std::lock_guard lock(memoryMutex); + const auto found = groups.find(call.values.at(0)); + if (found == groups.end()) return; + for (const auto* chunk : found->second) { + if (skipped.Overlaps(*chunk)) continue; + restore(reader, *chunk); + deltaBytes += chunk->bytes; + } + return; + } + case FrameCallKind::FrameStart: + case FrameCallKind::End: + case FrameCallKind::DisplayBuffer: + return; + } + throw std::runtime_error("frame replay: unknown call kind"); + } + + static std::uint64_t load(std::uint64_t address, std::size_t bytes) { + std::uint64_t value = 0; + std::memcpy(&value, reinterpret_cast(static_cast(address)), bytes); + return value; + } + + static bool satisfies(const AgcDriver::AwaitedWait& wait, std::uint64_t value) { + const auto masked = value & wait.mask; + const auto reference = wait.reference & wait.mask; + switch (wait.function) { + case 1: return masked < reference; + case 2: return masked <= reference; + case 3: return masked == reference; + case 4: return masked != reference; + case 5: return masked >= reference; + case 6: return masked > reference; + default: return false; + } + } + + static std::optional satisfying(const AgcDriver::AwaitedWait& wait, std::uint64_t current) { + const auto keep = current & ~wait.mask; + const auto reference = wait.reference & wait.mask; + switch (wait.function) { + case 2: + case 3: + case 5: return keep | reference; + case 1: return reference == 0 ? std::nullopt : std::optional(keep); + case 4: return keep | (~reference & wait.mask); + case 6: return reference == wait.mask ? std::nullopt : std::optional(keep | ((reference + 1) & wait.mask)); + default: return std::nullopt; + } + } + + bool writeRecordedValue(const FrameCall& call, const AgcDriver::AwaitedWait& wait) { + if (call.kind != FrameCallKind::Memory) return false; + const auto found = groups.find(call.values.at(0)); + if (found == groups.end()) return false; + const auto address = wait.address; + for (const auto* chunk : found->second) { + if (chunk->address > address || address + 4 > chunk->address + chunk->bytes || skipped.Overlaps(*chunk)) continue; + const auto bytes = reader.Read(*chunk); + const auto length = static_cast(std::min(wait.bytes == 8 ? 8 : 4, chunk->address + chunk->bytes - address)); + const auto* const recorded = bytes.data() + (address - chunk->address); + auto* const target = reinterpret_cast(static_cast(address)); + if (std::memcmp(target, recorded, length) == 0) return false; + std::uint64_t value = 0; + std::memcpy(&value, recorded, length); + if (wait.function != 0 && !satisfies(wait, value)) return false; + std::memcpy(target, recorded, length); + return true; + } + return false; + } + + void releaseAwaited(std::span calls, std::size_t from) { + std::lock_guard lock(memoryMutex); + std::array awaited{}; + const auto count = AgcDriverAwaitedWaits_nid_postfix(awaited.data(), awaited.size()); + for (std::size_t slot = 0; slot < count; ++slot) { + const auto& wait = awaited[slot]; + if (wait.address == 0) continue; + const std::size_t bytes = wait.bytes == 8 ? 8 : 4; + if (wait.function != 0 && satisfies(wait, load(wait.address, bytes))) continue; + bool released = false; + for (auto index = from; index < calls.size() && !released; ++index) released = writeRecordedValue(calls[index], wait); + for (auto index = std::min(from, calls.size()); index > 0 && !released; --index) { + released = writeRecordedValue(calls[index - 1], wait); + if (released) ++lateReleases; + } + if (!released && options.synthesize) { + if (const auto value = satisfying(wait, load(wait.address, bytes))) { + std::memcpy(reinterpret_cast(static_cast(wait.address)), &*value, bytes); + ++syntheticReleases; + released = true; + } + } + if (released) ++earlyReleases; + } + } + + void runWindow(std::span calls) { + std::map> byThread; + for (std::size_t index = 0; index < calls.size(); ++index) byThread[calls[index].thread].push_back(index); + threads = byThread.size(); + std::mutex mutex; + std::condition_variable turn; + std::size_t started = 0; + std::exception_ptr failure; + std::atomic finished{false}; + std::thread breaker([&] { + if (!options.release) return; + auto lastCompleted = progress().second; + auto lastProgress = Clock::now(); + while (!finished.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const auto completed = progress().second; + if (completed != lastCompleted) { + lastCompleted = completed; + lastProgress = Clock::now(); + continue; + } + if (Clock::now() - lastProgress < options.releaseAfter) continue; + releaseAwaited(calls, sequenced.load()); + lastProgress = Clock::now(); + } + }); + std::vector workers; + for (const auto& entry : byThread) { + workers.emplace_back([&, &indices = entry.second] { + for (const auto index : indices) { + std::unique_lock lock(mutex); + turn.wait(lock, [&] { return started == index || failure != nullptr; }); + if (failure != nullptr) return; + const auto advance = [&] { + ++started; + sequenced.store(started); + lock.unlock(); + turn.notify_all(); + }; + const auto kind = calls[index].kind; + const bool ordered = kind == FrameCallKind::Memory || kind == FrameCallKind::RegisterVideoOutput || kind == FrameCallKind::UnregisterVideoOutput; + if (!ordered) advance(); + try { + execute(calls[index]); + if (ordered) advance(); + } catch (...) { + if (!lock.owns_lock()) lock.lock(); + if (failure == nullptr) failure = std::current_exception(); + turn.notify_all(); + return; + } + } + }); + } + for (auto& worker : workers) worker.join(); + if (failure == nullptr) drain(); + finished.store(true); + breaker.join(); + if (failure != nullptr) std::rethrow_exception(failure); + } + + void drain() { + auto lastCompleted = progress().second; + auto lastProgress = Clock::now(); + while (true) { + const auto [accepted, completed] = progress(); + unfinished = accepted - completed; + if (unfinished == 0) return; + if (completed != lastCompleted) { + lastCompleted = completed; + lastProgress = Clock::now(); + } else if (Clock::now() - lastProgress >= options.drainTimeout) { + std::fprintf(stderr, "[frame-replay] %llu submissions did not complete after the last captured call (serials %llu/%llu)\n", static_cast(unfinished), + static_cast(completed), static_cast(accepted)); + return; + } + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + } + + template + void registration(TFunction&& function) { + try { + function(); + } catch (const std::exception& error) { + if (recording && options.strict) throw; + ++(recording ? failedRegistrations : staleRegistrations); + std::fprintf(stderr, "[frame-replay] %s registration failed: %s\n", recording ? "recorded" : "prologue", error.what()); + } + } + + static const Shader* shader(std::uint64_t address) { + return reinterpret_cast(static_cast(address)); + } + + static AgcDriver::FlipInfo flipInfo(const FrameCall& call) { + return {static_cast(call.values.at(0)), static_cast(call.values.at(1)), static_cast(call.values.at(2)), static_cast(call.values.at(3))}; + } +}; + +int usage() { + std::fprintf(stderr, "usage: agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--strict]\n" + " [--dump
::]...\n" + " replays the driver calls and guest memory that APS5_FRAME_CAPTURE= recorded, through this\n" + " build's AGC driver with no title running, and prints one JSON line with the frame times\n" + " --no-pace issues calls without waiting for the in-flight depth the title had\n" + " --no-release never copies recorded CPU stores early to release a GPU wait\n" + " --release-after-ms milliseconds without a completion before awaited dwords are released (3)\n" + " --no-synthesize releases a wait only with a value the capture recorded, never with its reference value\n" + " --strict fails on a pacing timeout or a failed registration inside the captured frames\n" + " --dump writes guest memory to a file after the replay (hexadecimal or decimal)\n" + " --frames writes each recorded and replayed display buffer as reference-.bmp and replay-.bmp\n"); + return 2; +} + +Dump parseDump(std::string_view text) { + const auto first = text.find(':'); + const auto second = first == std::string_view::npos ? std::string_view::npos : text.find(':', first + 1); + if (second == std::string_view::npos) throw std::invalid_argument("frame replay: --dump takes
::"); + const auto number = [](std::string_view part) { + const std::string value(part); + char* end = nullptr; + const auto parsed = std::strtoull(value.c_str(), &end, 0); + if (value.empty() || end != value.c_str() + value.size()) throw std::invalid_argument("frame replay: '" + value + "' is not a number"); + return static_cast(parsed); + }; + return {number(text.substr(0, first)), number(text.substr(first + 1, second - first - 1)), std::filesystem::path(std::string(text.substr(second + 1)))}; +} + +void writeDump(const Dump& dump) { + auto* const pointer = reinterpret_cast(static_cast(dump.address)); + GuestArena::GuestArenaPrepareHostRead_nid_no_patch(pointer, static_cast(dump.bytes)); + std::ofstream file(dump.path, std::ios::binary | std::ios::trunc); + file.write(reinterpret_cast(pointer), static_cast(dump.bytes)); + if (!file.good()) throw std::runtime_error("frame replay: cannot write " + dump.path.string()); +} + +struct FrameCheck { + std::size_t compared = 0; + double identical = 1.0; + int maxDifference = 0; +}; + +void writeFrame(const std::filesystem::path& path, const AgcDriver::DisplayBuffer& buffer, std::span pixels) { + std::FILE* file = std::fopen(path.string().c_str(), "wb"); + const auto failure = file != nullptr ? AgcDriver::WriteBmpAndClose_nid_no_patch(file, buffer.width, buffer.height, pixels) : AgcDriver::FrameDumpIoFailure{"open", errno}; + if (failure) std::fprintf(stderr, "[frame-replay] cannot write %s (%s failed)\n", path.string().c_str(), failure.operation); +} + +FrameCheck compareFrames(const FrameCaptureReader& reader, const Options& options) { + FrameCheck check; + if (!options.frames.empty()) std::filesystem::create_directories(options.frames); + for (const auto& call : reader.Calls()) { + if (call.kind != FrameCallKind::DisplayBuffer || call.values.size() < 9) continue; + AgcDriver::DisplayBuffer buffer{}; + buffer.address = call.address; + buffer.pixelFormat = call.values[1]; + buffer.width = static_cast(call.values[2]); + buffer.height = static_cast(call.values[3]); + buffer.tilingMode = static_cast(call.values[4]); + buffer.pitchInPixel = static_cast(call.values[5]); + buffer.dccAddress = call.values[6]; + buffer.dccClearColor = call.values[7]; + buffer.alignment = static_cast(call.values[8]); + const auto chunk = std::find_if(reader.Chunks().begin(), reader.Chunks().end(), [&](const FrameMemoryChunk& candidate) { return candidate.kind == FrameMemoryKind::Reference && candidate.group == call.values[0]; }); + if (chunk == reader.Chunks().end()) continue; + const auto reference = reader.Read(*chunk); + const auto replayed = AgcDriver::ReadDisplayBuffer(buffer); + ++check.compared; + std::size_t same = 0; + const auto pixels = std::min(reference.size(), replayed.size()) / 4; + for (std::size_t pixel = 0; pixel < pixels; ++pixel) { + bool equal = true; + for (std::size_t channel = 0; channel < 4; ++channel) { + const auto difference = std::abs(static_cast(reference[pixel * 4 + channel]) - static_cast(replayed[pixel * 4 + channel])); + check.maxDifference = std::max(check.maxDifference, difference); + equal = equal && difference == 0; + } + same += equal ? 1 : 0; + } + const auto total = std::max(reference.size(), replayed.size()) / 4; + check.identical = std::min(check.identical, total == 0 ? 1.0 : static_cast(same) / static_cast(total)); + if (options.frames.empty()) continue; + const auto index = std::to_string(call.values[0]); + writeFrame(options.frames / ("reference-" + index + ".bmp"), buffer, reference); + writeFrame(options.frames / ("replay-" + index + ".bmp"), buffer, replayed); + } + return check; +} + +int run(const Options& options) { + const auto loadStart = Clock::now(); + const FrameCaptureReader reader(options.directory); + if (reader.Truncated()) std::fprintf(stderr, "[frame-replay] the capture is truncated; replaying the complete records\n"); + auto skipped = restoreMemory(reader); + const auto loaded = Clock::now(); + Replay replay{options, reader}; + replay.skipped = std::move(skipped); + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Delta) replay.groups[chunk.group].push_back(&chunk); + } + const auto& calls = reader.Calls(); + const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; }); + for (auto call = calls.begin(); call != frameStart; ++call) replay.execute(*call); + replay.recording = true; + replay.startedAt = Clock::now(); + if (frameStart != calls.end()) replay.runWindow(std::span(calls).subspan(static_cast(frameStart - calls.begin()) + 1)); + if (frameStart == calls.end()) AgcDriverWaitIdle_nid_postfix(); + replay.endedAt = Clock::now(); + if (const auto failure = replay.frames.Failure()) std::rethrow_exception(failure); + FrameCheck check; + if (replay.unfinished == 0) { + for (const auto& dump : options.dumps) writeDump(dump); + check = compareFrames(reader, options); + } else if (!options.dumps.empty()) { + std::fprintf(stderr, "[frame-replay] no dumps written: the GPU work did not finish\n"); + } + const auto ready = replay.frames.Times(); + std::vector frameMs; + for (std::size_t index = 1; index < ready.size(); ++index) frameMs.push_back(std::chrono::duration(ready[index] - ready[index - 1]).count()); + std::string frameList; + for (const auto value : frameMs) frameList += (frameList.empty() ? "" : ",") + std::to_string(static_cast(value)); + std::sort(frameMs.begin(), frameMs.end()); + const auto percentile = [&](double fraction) { return frameMs.empty() ? 0.0 : frameMs[std::min(frameMs.size() - 1, static_cast(fraction * static_cast(frameMs.size())))]; }; + const auto seconds = std::chrono::duration(replay.endedAt - replay.startedAt).count(); + std::printf("{\"frames\":%zu,\"replay_s\":%.3f,\"fps\":%.3f,\"frame_ms\":{\"p50\":%.3f,\"p90\":%.3f,\"max\":%.3f,\"each\":[%s]},\"submits\":%llu,\"delta_mib\":%.1f,\"pace_timeouts\":%llu,\"pace_wait_s\":%.3f,\"stale_registrations\":%llu,\"failed_registrations\":%llu,\"skipped_mib\":%.1f,\"threads\":%zu,\"released_waits\":%llu,\"of_them_from_earlier_writes\":%llu,\"of_them_synthesized\":%llu,\"unfinished\":%llu,\"frames_compared\":%zu,\"pixels_identical\":%.6f,\"max_channel_diff\":%d,\"load_s\":%.3f}\n", + ready.size(), seconds, seconds > 0 ? static_cast(ready.size()) / seconds : 0.0, percentile(0.5), percentile(0.9), frameMs.empty() ? 0.0 : frameMs.back(), frameList.c_str(), + static_cast(replay.submits.load()), static_cast(replay.deltaBytes.load()) / (1024.0 * 1024.0), static_cast(replay.paceTimeouts.load()), + replay.paceWaitedSeconds(), static_cast(replay.staleRegistrations.load()), static_cast(replay.failedRegistrations.load()), + static_cast(replay.skipped.bytes) / (1024.0 * 1024.0), replay.threads, static_cast(replay.earlyReleases.load()), static_cast(replay.lateReleases.load()), + static_cast(replay.syntheticReleases.load()), + static_cast(replay.unfinished), check.compared, check.identical, check.maxDifference, std::chrono::duration(loaded - loadStart).count()); + std::fflush(stdout); + return replay.unfinished == 0 ? 0 : 3; +} + +} + +int main(int argc, char** argv) { + Options options; + for (int index = 1; index < argc; ++index) { + const std::string_view argument = argv[index]; + if (argument == "--no-pace") options.pace = false; + else if (argument == "--no-release") options.release = false; + else if (argument == "--no-synthesize") options.synthesize = false; + else if (argument == "--release-after-ms" && index + 1 < argc) options.releaseAfter = std::chrono::milliseconds(std::strtoll(argv[++index], nullptr, 10)); + else if (argument == "--strict") options.strict = true; + else if (argument == "--dump" && index + 1 < argc) options.dumps.push_back(parseDump(argv[++index])); + else if (argument == "--frames" && index + 1 < argc) options.frames = argv[++index]; + else if (!argument.starts_with("-") && options.directory.empty()) options.directory = argument; + else return usage(); + } + if (options.directory.empty()) return usage(); +#ifdef _WIN32 + _putenv_s("APS5_FRAME_CAPTURE", ""); +#else + unsetenv("APS5_FRAME_CAPTURE"); +#endif + int code = 2; + try { + code = run(options); + if (code == 0) { + AgcDriverShutdown_nid_postfix(); + return 0; + } + } catch (const std::exception& error) { + std::fprintf(stderr, "agc_frame_replay: %s\n", error.what()); + } + std::fflush(stdout); + std::fflush(stderr); + std::_Exit(code); +} diff --git a/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp index e4eeec9174..7bad5361bb 100644 --- a/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp @@ -49,6 +49,7 @@ bool g_graph = false; bool g_maintenance8 = false; bool g_robust2 = false; bool g_waveLdsMemoryBarriers = false; +bool g_deviceScopeLdsMemoryBarriers = false; bool g_code = false; bool Replay(const char* path) { @@ -149,6 +150,7 @@ bool Replay(const char* path) { if (g_maintenance8) request.request.target.nonConstantImageOffsets = true; if (g_robust2) request.request.target.robustBufferAccess2 = true; if (g_waveLdsMemoryBarriers) request.request.target.waveLdsMemoryBarriers = true; + if (g_deviceScopeLdsMemoryBarriers) request.request.target.deviceScopeLdsMemoryBarriers = true; const auto result = ShaderRecompiler::Recompile(request.request); std::printf(" recompiled: %zu SPIR-V words\n", result.spirv.size()); if (g_spirv) { @@ -186,7 +188,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] ...\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] ...\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; @@ -215,6 +217,10 @@ int main(int argc, char** argv) { g_waveLdsMemoryBarriers = true; continue; } + if (std::string(argv[i]) == "--device-scope-lds-memory-barriers") { + g_deviceScopeLdsMemoryBarriers = true; + continue; + } if (std::string(argv[i]) == "--mem") { g_memory = true; continue; diff --git a/core/libs/prx/libSceAgcResourceRegistration/libSceAgcResourceRegistration.hpp b/core/libs/prx/libSceAgcResourceRegistration/libSceAgcResourceRegistration.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAgcResourceRegistration/libSceAgcResourceRegistration.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAgcVsh/Export.cpp b/core/libs/prx/libSceAgcVsh/Export.cpp index fb34206c21..b648c87cc5 100644 --- a/core/libs/prx/libSceAgcVsh/Export.cpp +++ b/core/libs/prx/libSceAgcVsh/Export.cpp @@ -1,3 +1,153 @@ +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" +#include "prx/libSceAgcVsh/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,6 +155,904 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceAgcDcbCopyData_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("1rZSWUv1IRc", sceAgcDcbCopyData_nid_no_patch); + +int APS5_VABI sceAgcBranchPatchSetElseTarget_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("QmfvaYpsOcI", sceAgcBranchPatchSetElseTarget_nid_no_patch); + +int APS5_VABI sceAgcCbSetShRegistersDirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("UZbQjYAwwXM", sceAgcCbSetShRegistersDirect_nid_no_patch); + +int APS5_VABI sceAgcCreateShader_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("f3dg2CSgRKY", sceAgcCreateShader_nid_no_patch); + +int APS5_VABI sceAgcDcbSetNumInstancesGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("6DFuRKT4C9w", sceAgcDcbSetNumInstancesGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetUcRegisterDirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("aP1Ki9G3++4", sceAgcDcbSetUcRegisterDirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndexIndirectMultiGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("r98I08t+LOg", sceAgcDcbDrawIndexIndirectMultiGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbAtomicGdsGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("1tB0xkLNjcw", sceAgcDcbAtomicGdsGetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbCopyDataGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("CbQh3DKMSno", sceAgcAcbCopyDataGetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbWaitUntilSafeForRendering_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("GPbUp9jXQa8", sceAgcAcbWaitUntilSafeForRendering_nid_no_patch); + +int APS5_VABI sceAgcAsyncCondExecPatchSetEnd_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("k-JpyR2dYAM", sceAgcAsyncCondExecPatchSetEnd_nid_no_patch); + +int APS5_VABI sceAgcDcbDmaDataGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("2ccJz9LQI+w", sceAgcDcbDmaDataGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetUcRegistersIndirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("hvUfkUIQcOE", sceAgcDcbSetUcRegistersIndirect_nid_no_patch); + +int APS5_VABI sceAgcGetRegisterDefaults2_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("2JtWUUiYBXs", sceAgcGetRegisterDefaults2_nid_no_patch); + +int APS5_VABI sceAgcCbReleaseMem_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("wr23dPKyWc0", sceAgcCbReleaseMem_nid_no_patch); + +int APS5_VABI sceAgcCondExecPatchSetEnd_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ORWsxIbk4TE", sceAgcCondExecPatchSetEnd_nid_no_patch); + +int APS5_VABI sceAgcDcbAtomicGds_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("pH3-dfRpfA0", sceAgcDcbAtomicGds_nid_no_patch); + +int APS5_VABI sceAgcDcbSetCxRegistersIndirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("GBCh3zCihoU", sceAgcDcbSetCxRegistersIndirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcCbSetUcRegisterRangeDirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("MDLD5Ly94Xk", sceAgcCbSetUcRegisterRangeDirect_nid_no_patch); + +int APS5_VABI sceAgcDcbEventWrite_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("aJf+j5yntiU", sceAgcDcbEventWrite_nid_no_patch); + +int APS5_VABI sceAgcDcbWriteData_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("i1jyy49AjXU", sceAgcDcbWriteData_nid_no_patch); + +int APS5_VABI sceAgcSetUcRegIndirectPatchSetAddress_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("6lNcCp+fxi4", sceAgcSetUcRegIndirectPatchSetAddress_nid_no_patch); + +int APS5_VABI sceAgcCbCondWriteGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("FuVbkyKlf+s", sceAgcCbCondWriteGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbQueueEndOfShaderActionGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("zg6u-N6Otxs", sceAgcDcbQueueEndOfShaderActionGetSize_nid_no_patch); + +int APS5_VABI sceAgcGetPacketSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Lkf86B98qPc", sceAgcGetPacketSize_nid_no_patch); + +int APS5_VABI sceAgcAcbAcquireMem_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("KT-hTp-Ch14", sceAgcAcbAcquireMem_nid_no_patch); + +int APS5_VABI sceAgcAcbEventWriteGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Y-5vneiBtzk", sceAgcAcbEventWriteGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetIndexIndirectArgs_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("0o3VDdtA6nM", sceAgcDcbSetIndexIndirectArgs_nid_no_patch); + +int APS5_VABI sceAgcAcbDmaData_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("-RnpfpxIhec", sceAgcAcbDmaData_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndexIndirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("t1vNu082-jM", sceAgcDcbDrawIndexIndirect_nid_no_patch); + +int APS5_VABI sceAgcDcbPrimeUtcl2GetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("KjPeVduz6jU", sceAgcDcbPrimeUtcl2GetSize_nid_no_patch); + +int APS5_VABI sceAgcAsyncCondExecPatchSetCommandAddress_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("3ZWa3AoyWZQ", sceAgcAsyncCondExecPatchSetCommandAddress_nid_no_patch); + +int APS5_VABI sceAgcAcbAcquireMemGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ewobAQeMo5k", sceAgcAcbAcquireMemGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetIndexIndirectArgsGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("AFIh8SQkYlQ", sceAgcDcbSetIndexIndirectArgsGetSize_nid_no_patch); + +int APS5_VABI sceAgcCreateInterpolantMapping_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("pdEV7bI6COI", sceAgcCreateInterpolantMapping_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndexIndirectMulti_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ypVBz4uPKcQ", sceAgcDcbDrawIndexIndirectMulti_nid_no_patch); + +int APS5_VABI sceAgcDcbEventWriteGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("C4l9fB17t8w", sceAgcDcbEventWriteGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetUcRegisterDirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("w4-d0n60hdo", sceAgcDcbSetUcRegisterDirect_nid_no_patch); + +int APS5_VABI sceAgcQueueEndOfPipeActionPatchAddress_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("0fWWK5uG9rQ", sceAgcQueueEndOfPipeActionPatchAddress_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndirectMultiGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("pYoKs3lPy88", sceAgcDcbDrawIndirectMultiGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetBaseDrawIndirectArgsGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("MMlmJAL7N5w", sceAgcDcbSetBaseDrawIndirectArgsGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetNumInstances_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("tSBxhAPyytQ", sceAgcDcbSetNumInstances_nid_no_patch); + +int APS5_VABI sceAgcDcbWaitOnAddressGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("43WJ08sSugE", sceAgcDcbWaitOnAddressGetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbAtomicGds_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("cduV1f0dcGQ", sceAgcAcbAtomicGds_nid_no_patch); + +int APS5_VABI sceAgcDcbSetBoolPredicationEnableGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("yheJGN-ay+A", sceAgcDcbSetBoolPredicationEnableGetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbAtomicMemGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("da1Sm8-QDoU", sceAgcAcbAtomicMemGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetCfRegisterRangeDirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("BVFg3CWU6Eo", sceAgcDcbSetCfRegisterRangeDirect_nid_no_patch); + +int APS5_VABI sceAgcDcbCondExecGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ou16V5hh5sg", sceAgcDcbCondExecGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndexMultiInstanced_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Rlx+bykm0r0", sceAgcDcbDrawIndexMultiInstanced_nid_no_patch); + +int APS5_VABI sceAgcDcbSetCxRegistersIndirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ZvwO9euwYzc", sceAgcDcbSetCxRegistersIndirect_nid_no_patch); + +int APS5_VABI sceAgcCbSetShRegistersDirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("yUBESvCCJ4I", sceAgcCbSetShRegistersDirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcCondExecPatchSetCommandAddress_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("YWTKOju587o", sceAgcCondExecPatchSetCommandAddress_nid_no_patch); + +int APS5_VABI sceAgcRewindPatchSetRewindState_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ziVA3whp3p4", sceAgcRewindPatchSetRewindState_nid_no_patch); + +int APS5_VABI sceAgcQueueEndOfPipeActionPatchGcrCntl_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("J8YCgfKAMQs", sceAgcQueueEndOfPipeActionPatchGcrCntl_nid_no_patch); + +int APS5_VABI sceAgcWaitRegMemPatchReference_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("7nOoijNPvEU", sceAgcWaitRegMemPatchReference_nid_no_patch); + +int APS5_VABI sceAgcAcbRewind_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("DwICrVxerkY", sceAgcAcbRewind_nid_no_patch); + +int APS5_VABI sceAgcAcbCondExecGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ozKzBP4aki4", sceAgcAcbCondExecGetSize_nid_no_patch); + +int APS5_VABI sceAgcSuspendPointAndCheckStatus_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("b+fis+WZ3Ig", sceAgcSuspendPointAndCheckStatus_nid_no_patch); + +int APS5_VABI sceAgcAcbWaitRegMem_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("htn36gPnBk4", sceAgcAcbWaitRegMem_nid_no_patch); + +int APS5_VABI sceAgcDcbAcquireMem_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("57labkp+rSQ", sceAgcDcbAcquireMem_nid_no_patch); + +int APS5_VABI sceAgcSetCxRegIndirectPatchSetNumRegisters_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("whb1RL7K4Ss", sceAgcSetCxRegIndirectPatchSetNumRegisters_nid_no_patch); + +int APS5_VABI sceAgcDcbContextStateOp_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("HabmgqPwPw0", sceAgcDcbContextStateOp_nid_no_patch); + +int APS5_VABI sceAgcAcbWriteData_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("eZ4+17OQz4Q", sceAgcAcbWriteData_nid_no_patch); + +int APS5_VABI sceAgcAcbDmaDataGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("M0ttm8h7SKA", sceAgcAcbDmaDataGetSize_nid_no_patch); + +int APS5_VABI sceAgcCbSetShRegisterRangeDirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("bxGoVxpdSPQ", sceAgcCbSetShRegisterRangeDirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcCbNop_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("LtTouSCZjHM", sceAgcCbNop_nid_no_patch); + +int APS5_VABI sceAgcDcbResetQueue_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("TRO721eVt4g", sceAgcDcbResetQueue_nid_no_patch); + +int APS5_VABI sceAgcDcbDmaData_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("WmAc2MEj6Io", sceAgcDcbDmaData_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndexGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("6ee9Hd3EWXQ", sceAgcDcbDrawIndexGetSize_nid_no_patch); + +int APS5_VABI sceAgcDmaDataPatchSetDstAddressOrOffset_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("IxYiarKlXxM", sceAgcDmaDataPatchSetDstAddressOrOffset_nid_no_patch); + +int APS5_VABI sceAgcCbQueueEndOfPipeActionGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("hL7C0IRpWZI", sceAgcCbQueueEndOfPipeActionGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetFlip_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("YUeqkyT7mEQ", sceAgcDcbSetFlip_nid_no_patch); + +int APS5_VABI sceAgcDcbSetMarker_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("QhCbS4X9Rl8", sceAgcDcbSetMarker_nid_no_patch); + +int APS5_VABI sceAgcDcbSetPredication_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("bbFueFP+J4k", sceAgcDcbSetPredication_nid_no_patch); + +int APS5_VABI sceAgcDcbAtomicMem_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("1-gUn1PI4Sw", sceAgcDcbAtomicMem_nid_no_patch); + +int APS5_VABI sceAgcGetRegisterDefaults2Internal_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("wRbq6ZjNop4", sceAgcGetRegisterDefaults2Internal_nid_no_patch); + +int APS5_VABI sceAgcSetShRegIndirectPatchAddRegisters_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("z2duB-hHQSM", sceAgcSetShRegIndirectPatchAddRegisters_nid_no_patch); + +int APS5_VABI sceAgcDcbPrimeUtcl2_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("jt3pl7EN17o", sceAgcDcbPrimeUtcl2_nid_no_patch); + +int APS5_VABI sceAgcAcbRewindGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("0ZOG0jc9nRg", sceAgcAcbRewindGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetZPassPredicationEnableGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("XN+Iuu7XsM8", sceAgcDcbSetZPassPredicationEnableGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbWaitUntilSafeForRendering_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("MWiElSNE8j8", sceAgcDcbWaitUntilSafeForRendering_nid_no_patch); + +int APS5_VABI sceAgcBranchPatchSetThenTarget_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("xb8VgcXQhvI", sceAgcBranchPatchSetThenTarget_nid_no_patch); + +int APS5_VABI sceAgcSetCxRegIndirectPatchSetAddress_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("vcmNN+AAXnY", sceAgcSetCxRegIndirectPatchSetAddress_nid_no_patch); + +int APS5_VABI sceAgcDcbAtomicMemGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("oz6zQq1JwCE", sceAgcDcbAtomicMemGetSize_nid_no_patch); + +int APS5_VABI sceAgcUpdatePrimState_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Y3ymLfZ1384", sceAgcUpdatePrimState_nid_no_patch); + +int APS5_VABI sceAgcCbNopGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("t7PlZ9nt5Lc", sceAgcCbNopGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbGetLodStats_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("vuSXe69VILM", sceAgcDcbGetLodStats_nid_no_patch); + +int APS5_VABI sceAgcWaitRegMemPatchAddress_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("3KDcnM3lrcU", sceAgcWaitRegMemPatchAddress_nid_no_patch); + +int APS5_VABI sceAgcQueueEndOfPipeActionPatchData_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("MlEw1feXcjg", sceAgcQueueEndOfPipeActionPatchData_nid_no_patch); + +int APS5_VABI sceAgcGetRegisterDefaultsInternal_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("uIwxsqDlHRc", sceAgcGetRegisterDefaultsInternal_nid_no_patch); + +int APS5_VABI sceAgcInit_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("kW3GLb7QfPg", sceAgcInit_nid_no_patch); + +int APS5_VABI sceAgcDcbDispatchIndirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("w8HVkEeXPv8", sceAgcDcbDispatchIndirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbCondExec_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("BIPexNBSGog", sceAgcDcbCondExec_nid_no_patch); + +int APS5_VABI sceAgcAcbSetMarker_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("xAeBOa0A3kk", sceAgcAcbSetMarker_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndexOffsetGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("qMlfB1ZhMDc", sceAgcDcbDrawIndexOffsetGetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbJump_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("e1DFTg+Sd8U", sceAgcAcbJump_nid_no_patch); + +int APS5_VABI sceAgcDcbSetIndexCount_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("8N2tmT3jmC8", sceAgcDcbSetIndexCount_nid_no_patch); + +int APS5_VABI sceAgcDcbSetShRegistersIndirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("nNlUtdDDvZ0", sceAgcDcbSetShRegistersIndirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbStallCommandBufferParserGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("+u6dKSLWM2o", sceAgcDcbStallCommandBufferParserGetSize_nid_no_patch); + +int APS5_VABI sceAgcSuspendPoint_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("h9z6+0hEydk", sceAgcSuspendPoint_nid_no_patch); + +int APS5_VABI sceAgcWaitRegMemPatchMask_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("hXAnLgDHCoI", sceAgcWaitRegMemPatchMask_nid_no_patch); + +int APS5_VABI sceAgcAcbDispatchIndirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("PxKWV2fVAps", sceAgcAcbDispatchIndirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbAtomicGdsGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("hcIxS8pmXF4", sceAgcAcbAtomicGdsGetSize_nid_no_patch); + +int APS5_VABI sceAgcCbCondWrite_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("7toV+elXqNM", sceAgcCbCondWrite_nid_no_patch); + +int APS5_VABI sceAgcDcbEndOcclusionQueryGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("P1CugZ99Uzc", sceAgcDcbEndOcclusionQueryGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetPredicationDisableGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("vLrBL8DQiz8", sceAgcDcbSetPredicationDisableGetSize_nid_no_patch); + +int APS5_VABI sceAgcLinkShaders_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("MqAdbRMdNz4", sceAgcLinkShaders_nid_no_patch); + +int APS5_VABI sceAgcDcbWaitRegMem_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("VmW0Tdpy420", sceAgcDcbWaitRegMem_nid_no_patch); + +int APS5_VABI sceAgcGetRegisterDefaults_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Wi82ArQtAwg", sceAgcGetRegisterDefaults_nid_no_patch); + +int APS5_VABI sceAgcCbSetShRegisterRangeDirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("n2fD4A+pb+g", sceAgcCbSetShRegisterRangeDirect_nid_no_patch); + +int APS5_VABI sceAgcSetCxRegIndirectPatchAddRegisters_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("d-6uF9sZDIU", sceAgcSetCxRegIndirectPatchAddRegisters_nid_no_patch); + +int APS5_VABI sceAgcDcbDispatchIndirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("CtB+A9-VxO0", sceAgcDcbDispatchIndirect_nid_no_patch); + +int APS5_VABI sceAgcCbMemSemaphore_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("vHX9guneRBY", sceAgcCbMemSemaphore_nid_no_patch); + +int APS5_VABI sceAgcCbSetUcRegisterRangeDirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("JOWmDrl+j20", sceAgcCbSetUcRegisterRangeDirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcCbDispatch_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("k3GhuSNmBLU", sceAgcCbDispatch_nid_no_patch); + +int APS5_VABI sceAgcDcbCopyDataGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("b5u0Jzm8TF8", sceAgcDcbCopyDataGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndexMultiInstancedGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("mR9j7+SfM34", sceAgcDcbDrawIndexMultiInstancedGetSize_nid_no_patch); + +int APS5_VABI sceAgcDmaDataPatchSetSrcAddressOrOffsetOrImmediate_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("cdDRpqcFGbU", sceAgcDmaDataPatchSetSrcAddressOrOffsetOrImmediate_nid_no_patch); + +int APS5_VABI sceAgcSetShRegIndirectPatchSetAddress_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Qrj4c+61z4A", sceAgcSetShRegIndirectPatchSetAddress_nid_no_patch); + +int APS5_VABI sceAgcDcbJump_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("xSAR0LTcRKM", sceAgcDcbJump_nid_no_patch); + +int APS5_VABI sceAgcDcbJumpGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("VEGu4dixjUg", sceAgcDcbJumpGetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbPrimeUtcl2_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("szG7hz2yEhA", sceAgcAcbPrimeUtcl2_nid_no_patch); + +int APS5_VABI sceAgcDcbSetCxRegisterDirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("1DeUNpRIDDA", sceAgcDcbSetCxRegisterDirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcQueueEndOfPipeActionPatchType_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("T9fjQIINoeE", sceAgcQueueEndOfPipeActionPatchType_nid_no_patch); + +int APS5_VABI sceAgcDcbSetCxRegisterDirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("LHFXRrlTPD8", sceAgcDcbSetCxRegisterDirect_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndex_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("q88lQ+GP5Yk", sceAgcDcbDrawIndex_nid_no_patch); + +int APS5_VABI sceAgcCbDispatchGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Abendgtz+3o", sceAgcCbDispatchGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbGetLodStatsGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("rUuVjyR+Rd4", sceAgcDcbGetLodStatsGetSize_nid_no_patch); + +int APS5_VABI sceAgcGetDataPacketPayloadAddress_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("CQsSq6l6+kA", sceAgcGetDataPacketPayloadAddress_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndirect_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("1q1titRBL6o", sceAgcDcbDrawIndirect_nid_no_patch); + +int APS5_VABI sceAgcAcbSetFlip_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ebixW91gpPw", sceAgcAcbSetFlip_nid_no_patch); + +int APS5_VABI sceAgcGetFusedShaderSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("nQT5kYLv0cg", sceAgcGetFusedShaderSize_nid_no_patch); + +int APS5_VABI sceAgcSetRangePredication_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("n8vgpaQg6dA", sceAgcSetRangePredication_nid_no_patch); + +int APS5_VABI sceAgcDcbSetShRegisterDirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("QhPDD513V0w", sceAgcDcbSetShRegisterDirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbSetIndexSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("GIIW2J37e70", sceAgcDcbSetIndexSize_nid_no_patch); + +int APS5_VABI sceAgcSetPacketPredication_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("w6Dj1VJt5qY", sceAgcSetPacketPredication_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndexOffset_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("B+aG9DUnTKA", sceAgcDcbDrawIndexOffset_nid_no_patch); + +int APS5_VABI sceAgcAsyncRewindPatchSetRewindState_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("eWaWyFegzgQ", sceAgcAsyncRewindPatchSetRewindState_nid_no_patch); + +int APS5_VABI sceAgcJumpPatchSetTarget_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("2BS4EtAaF28", sceAgcJumpPatchSetTarget_nid_no_patch); + +int APS5_VABI sceAgcBranchPatchSetCompareAddress_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("GXBlM-ekzrI", sceAgcBranchPatchSetCompareAddress_nid_no_patch); + +int APS5_VABI sceAgcAcbJumpGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("b-oySn+G2tE", sceAgcAcbJumpGetSize_nid_no_patch); + +int APS5_VABI sceAgcCbBranch_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("w1KFAHVqpaU", sceAgcCbBranch_nid_no_patch); + +int APS5_VABI sceAgcDcbDrawIndexIndirectGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("mStuvI0zOtc", sceAgcDcbDrawIndexIndirectGetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbCopyData_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("qzMN2XKGA4k", sceAgcAcbCopyData_nid_no_patch); + +int APS5_VABI sceAgcDcbWriteDataGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("p9tI+yTvx68", sceAgcDcbWriteDataGetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbAtomicMem_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("XKKuA6VkSRc", sceAgcAcbAtomicMem_nid_no_patch); + +int APS5_VABI sceAgcAcbPrimeUtcl2GetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("eCjKaqeeQ5s", sceAgcAcbPrimeUtcl2GetSize_nid_no_patch); + +int APS5_VABI sceAgcAcbResetQueue_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("JrtiDtKeS38", sceAgcAcbResetQueue_nid_no_patch); + +int APS5_VABI sceAgcAcbQueueEndOfShaderActionGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("F8NLhWvFemI", sceAgcAcbQueueEndOfShaderActionGetSize_nid_no_patch); + +int APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize_nid_no_patch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ms1xVoZ-Vwc", sceAgcDcbBeginOcclusionQueryGetSize_nid_no_patch); } diff --git a/core/libs/prx/libSceAgcVsh/include/Api.hpp b/core/libs/prx/libSceAgcVsh/include/Api.hpp new file mode 100644 index 0000000000..efe8a800e6 --- /dev/null +++ b/core/libs/prx/libSceAgcVsh/include/Api.hpp @@ -0,0 +1,158 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAgcDcbCopyData_nid_no_patch(void); +int APS5_VABI sceAgcBranchPatchSetElseTarget_nid_no_patch(void); +int APS5_VABI sceAgcCbSetShRegistersDirect_nid_no_patch(void); +int APS5_VABI sceAgcCreateShader_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetNumInstancesGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetUcRegisterDirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndexIndirectMultiGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbAtomicGdsGetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbCopyDataGetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbWaitUntilSafeForRendering_nid_no_patch(void); +int APS5_VABI sceAgcAsyncCondExecPatchSetEnd_nid_no_patch(void); +int APS5_VABI sceAgcDcbDmaDataGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetUcRegistersIndirect_nid_no_patch(void); +int APS5_VABI sceAgcGetRegisterDefaults2_nid_no_patch(void); +int APS5_VABI sceAgcCbReleaseMem_nid_no_patch(void); +int APS5_VABI sceAgcCondExecPatchSetEnd_nid_no_patch(void); +int APS5_VABI sceAgcDcbAtomicGds_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetCxRegistersIndirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcCbSetUcRegisterRangeDirect_nid_no_patch(void); +int APS5_VABI sceAgcDcbEventWrite_nid_no_patch(void); +int APS5_VABI sceAgcDcbWriteData_nid_no_patch(void); +int APS5_VABI sceAgcSetUcRegIndirectPatchSetAddress_nid_no_patch(void); +int APS5_VABI sceAgcCbCondWriteGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbQueueEndOfShaderActionGetSize_nid_no_patch(void); +int APS5_VABI sceAgcGetPacketSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbAcquireMem_nid_no_patch(void); +int APS5_VABI sceAgcAcbEventWriteGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetIndexIndirectArgs_nid_no_patch(void); +int APS5_VABI sceAgcAcbDmaData_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndexIndirect_nid_no_patch(void); +int APS5_VABI sceAgcDcbPrimeUtcl2GetSize_nid_no_patch(void); +int APS5_VABI sceAgcAsyncCondExecPatchSetCommandAddress_nid_no_patch(void); +int APS5_VABI sceAgcAcbAcquireMemGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetIndexIndirectArgsGetSize_nid_no_patch(void); +int APS5_VABI sceAgcCreateInterpolantMapping_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndexIndirectMulti_nid_no_patch(void); +int APS5_VABI sceAgcDcbEventWriteGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetUcRegisterDirect_nid_no_patch(void); +int APS5_VABI sceAgcQueueEndOfPipeActionPatchAddress_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndirectMultiGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetBaseDrawIndirectArgsGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetNumInstances_nid_no_patch(void); +int APS5_VABI sceAgcDcbWaitOnAddressGetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbAtomicGds_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetBoolPredicationEnableGetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbAtomicMemGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetCfRegisterRangeDirect_nid_no_patch(void); +int APS5_VABI sceAgcDcbCondExecGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndexMultiInstanced_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetCxRegistersIndirect_nid_no_patch(void); +int APS5_VABI sceAgcCbSetShRegistersDirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcCondExecPatchSetCommandAddress_nid_no_patch(void); +int APS5_VABI sceAgcRewindPatchSetRewindState_nid_no_patch(void); +int APS5_VABI sceAgcQueueEndOfPipeActionPatchGcrCntl_nid_no_patch(void); +int APS5_VABI sceAgcWaitRegMemPatchReference_nid_no_patch(void); +int APS5_VABI sceAgcAcbRewind_nid_no_patch(void); +int APS5_VABI sceAgcAcbCondExecGetSize_nid_no_patch(void); +int APS5_VABI sceAgcSuspendPointAndCheckStatus_nid_no_patch(void); +int APS5_VABI sceAgcAcbWaitRegMem_nid_no_patch(void); +int APS5_VABI sceAgcDcbAcquireMem_nid_no_patch(void); +int APS5_VABI sceAgcSetCxRegIndirectPatchSetNumRegisters_nid_no_patch(void); +int APS5_VABI sceAgcDcbContextStateOp_nid_no_patch(void); +int APS5_VABI sceAgcAcbWriteData_nid_no_patch(void); +int APS5_VABI sceAgcAcbDmaDataGetSize_nid_no_patch(void); +int APS5_VABI sceAgcCbSetShRegisterRangeDirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcCbNop_nid_no_patch(void); +int APS5_VABI sceAgcDcbResetQueue_nid_no_patch(void); +int APS5_VABI sceAgcDcbDmaData_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndexGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDmaDataPatchSetDstAddressOrOffset_nid_no_patch(void); +int APS5_VABI sceAgcCbQueueEndOfPipeActionGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetFlip_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetMarker_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetPredication_nid_no_patch(void); +int APS5_VABI sceAgcDcbAtomicMem_nid_no_patch(void); +int APS5_VABI sceAgcGetRegisterDefaults2Internal_nid_no_patch(void); +int APS5_VABI sceAgcSetShRegIndirectPatchAddRegisters_nid_no_patch(void); +int APS5_VABI sceAgcDcbPrimeUtcl2_nid_no_patch(void); +int APS5_VABI sceAgcAcbRewindGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetZPassPredicationEnableGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbWaitUntilSafeForRendering_nid_no_patch(void); +int APS5_VABI sceAgcBranchPatchSetThenTarget_nid_no_patch(void); +int APS5_VABI sceAgcSetCxRegIndirectPatchSetAddress_nid_no_patch(void); +int APS5_VABI sceAgcDcbAtomicMemGetSize_nid_no_patch(void); +int APS5_VABI sceAgcUpdatePrimState_nid_no_patch(void); +int APS5_VABI sceAgcCbNopGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbGetLodStats_nid_no_patch(void); +int APS5_VABI sceAgcWaitRegMemPatchAddress_nid_no_patch(void); +int APS5_VABI sceAgcQueueEndOfPipeActionPatchData_nid_no_patch(void); +int APS5_VABI sceAgcGetRegisterDefaultsInternal_nid_no_patch(void); +int APS5_VABI sceAgcInit_nid_no_patch(void); +int APS5_VABI sceAgcDcbDispatchIndirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbCondExec_nid_no_patch(void); +int APS5_VABI sceAgcAcbSetMarker_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndexOffsetGetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbJump_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetIndexCount_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetShRegistersIndirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbStallCommandBufferParserGetSize_nid_no_patch(void); +int APS5_VABI sceAgcSuspendPoint_nid_no_patch(void); +int APS5_VABI sceAgcWaitRegMemPatchMask_nid_no_patch(void); +int APS5_VABI sceAgcAcbDispatchIndirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbAtomicGdsGetSize_nid_no_patch(void); +int APS5_VABI sceAgcCbCondWrite_nid_no_patch(void); +int APS5_VABI sceAgcDcbEndOcclusionQueryGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetPredicationDisableGetSize_nid_no_patch(void); +int APS5_VABI sceAgcLinkShaders_nid_no_patch(void); +int APS5_VABI sceAgcDcbWaitRegMem_nid_no_patch(void); +int APS5_VABI sceAgcGetRegisterDefaults_nid_no_patch(void); +int APS5_VABI sceAgcCbSetShRegisterRangeDirect_nid_no_patch(void); +int APS5_VABI sceAgcSetCxRegIndirectPatchAddRegisters_nid_no_patch(void); +int APS5_VABI sceAgcDcbDispatchIndirect_nid_no_patch(void); +int APS5_VABI sceAgcCbMemSemaphore_nid_no_patch(void); +int APS5_VABI sceAgcCbSetUcRegisterRangeDirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcCbDispatch_nid_no_patch(void); +int APS5_VABI sceAgcDcbCopyDataGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndexMultiInstancedGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDmaDataPatchSetSrcAddressOrOffsetOrImmediate_nid_no_patch(void); +int APS5_VABI sceAgcSetShRegIndirectPatchSetAddress_nid_no_patch(void); +int APS5_VABI sceAgcDcbJump_nid_no_patch(void); +int APS5_VABI sceAgcDcbJumpGetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbPrimeUtcl2_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetCxRegisterDirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcQueueEndOfPipeActionPatchType_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetCxRegisterDirect_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndex_nid_no_patch(void); +int APS5_VABI sceAgcCbDispatchGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbGetLodStatsGetSize_nid_no_patch(void); +int APS5_VABI sceAgcGetDataPacketPayloadAddress_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndirect_nid_no_patch(void); +int APS5_VABI sceAgcAcbSetFlip_nid_no_patch(void); +int APS5_VABI sceAgcGetFusedShaderSize_nid_no_patch(void); +int APS5_VABI sceAgcSetRangePredication_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetShRegisterDirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbSetIndexSize_nid_no_patch(void); +int APS5_VABI sceAgcSetPacketPredication_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndexOffset_nid_no_patch(void); +int APS5_VABI sceAgcAsyncRewindPatchSetRewindState_nid_no_patch(void); +int APS5_VABI sceAgcJumpPatchSetTarget_nid_no_patch(void); +int APS5_VABI sceAgcBranchPatchSetCompareAddress_nid_no_patch(void); +int APS5_VABI sceAgcAcbJumpGetSize_nid_no_patch(void); +int APS5_VABI sceAgcCbBranch_nid_no_patch(void); +int APS5_VABI sceAgcDcbDrawIndexIndirectGetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbCopyData_nid_no_patch(void); +int APS5_VABI sceAgcDcbWriteDataGetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbAtomicMem_nid_no_patch(void); +int APS5_VABI sceAgcAcbPrimeUtcl2GetSize_nid_no_patch(void); +int APS5_VABI sceAgcAcbResetQueue_nid_no_patch(void); +int APS5_VABI sceAgcAcbQueueEndOfShaderActionGetSize_nid_no_patch(void); +int APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize_nid_no_patch(void); + +} diff --git a/core/libs/prx/libSceAgcVsh/libSceAgcVsh.hpp b/core/libs/prx/libSceAgcVsh/libSceAgcVsh.hpp new file mode 100644 index 0000000000..9291d1e52e --- /dev/null +++ b/core/libs/prx/libSceAgcVsh/libSceAgcVsh.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAgcVsh/include/Api.hpp" diff --git a/core/libs/prx/libSceAgcVshDebug/libSceAgcVshDebug.hpp b/core/libs/prx/libSceAgcVshDebug/libSceAgcVshDebug.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAgcVshDebug/libSceAgcVshDebug.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAjm.native/AjmLibrary.cmake b/core/libs/prx/libSceAjm.native/AjmLibrary.cmake new file mode 100644 index 0000000000..ec9f5b9a81 --- /dev/null +++ b/core/libs/prx/libSceAjm.native/AjmLibrary.cmake @@ -0,0 +1,15 @@ +function(add_sce_ajm_library target) + set(ajmDir ${CMAKE_CURRENT_FUNCTION_LIST_DIR}) + add_library(${target} SHARED EXCLUDE_FROM_ALL + ${ajmDir}/Export.cpp + ${ajmDir}/src/Ajm.cpp + ${ARGN} + ) + target_include_directories(${target} PRIVATE + ${LIBS_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src + ${CMAKE_SOURCE_DIR}/3rdparty/vgmstream/src/coding/libs + ) + target_link_libraries(${target} PRIVATE atrac9 atrac9_vibration ffmpeg libc libkernel) + configure_windows_unwind(${target}) +endfunction() diff --git a/core/libs/prx/libSceAjm.native/CMakeLists.txt b/core/libs/prx/libSceAjm.native/CMakeLists.txt index 51b7c2507f..9f0a86e55b 100644 --- a/core/libs/prx/libSceAjm.native/CMakeLists.txt +++ b/core/libs/prx/libSceAjm.native/CMakeLists.txt @@ -2,7 +2,7 @@ enable_language(C) set(LIBATRAC9_DIR ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src) set(LIBATRAC9_PATCHED_DIR ${CMAKE_CURRENT_BINARY_DIR}/atrac9) -set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${LIBATRAC9_DIR}/unpack.c ${LIBATRAC9_DIR}/bit_reader.c ${LIBATRAC9_DIR}/decinit.c) +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${LIBATRAC9_DIR}/unpack.c ${LIBATRAC9_DIR}/bit_reader.c ${LIBATRAC9_DIR}/decinit.c ${LIBATRAC9_DIR}/utility.c) include(${CMAKE_CURRENT_SOURCE_DIR}/PatchLibAtrac9.cmake) add_library(atrac9 STATIC EXCLUDE_FROM_ALL ${LIBATRAC9_DIR}/band_extension.c @@ -17,7 +17,7 @@ add_library(atrac9 STATIC EXCLUDE_FROM_ALL ${LIBATRAC9_DIR}/scale_factors.c ${LIBATRAC9_DIR}/tables.c ${LIBATRAC9_PATCHED_DIR}/unpack.c - ${LIBATRAC9_DIR}/utility.c + ${LIBATRAC9_PATCHED_DIR}/utility.c ) target_include_directories(atrac9 PRIVATE ${LIBATRAC9_DIR}) set_target_properties(atrac9 PROPERTIES POSITION_INDEPENDENT_CODE ON) @@ -29,22 +29,16 @@ add_library(atrac9_vibration STATIC EXCLUDE_FROM_ALL ) set_target_properties(atrac9_vibration PROPERTIES POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden) -add_library(libSceAjm.native SHARED EXCLUDE_FROM_ALL - ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Ajm.cpp -) - -target_include_directories(libSceAjm.native PRIVATE - ${LIBS_INCLUDE_DIR} - ${LIBATRAC9_DIR} - ${VGMSTREAM_ATRAC9_DIR} -) -target_link_libraries(libSceAjm.native PRIVATE atrac9 atrac9_vibration ffmpeg libc libkernel) -configure_windows_unwind(libSceAjm.native) +include(${CMAKE_CURRENT_SOURCE_DIR}/AjmLibrary.cmake) +add_sce_ajm_library(libSceAjm.native) if(BUILD_TESTING) add_test_executable(atrac9_gradient_tests tests/Atrac9Gradient.c ${LIBATRAC9_DIR}/bit_reader.c) target_include_directories(atrac9_gradient_tests PRIVATE ${LIBATRAC9_PATCHED_DIR} ${LIBATRAC9_DIR}) + target_link_libraries(atrac9_gradient_tests PRIVATE atrac9) + if(NOT WIN32) + target_link_libraries(atrac9_gradient_tests PRIVATE m) + endif() target_compile_options(atrac9_gradient_tests PRIVATE -ffunction-sections -fdata-sections) if(APPLE) target_link_options(atrac9_gradient_tests PRIVATE LINKER:-dead_strip) @@ -71,6 +65,11 @@ if(BUILD_TESTING) target_link_libraries(guest_ajm_vibration_tests PRIVATE libSceAjm.native libc) configure_windows_unwind(guest_ajm_vibration_tests) add_test(NAME guest_ajm_vibration COMMAND guest_ajm_vibration_tests) + add_test_executable(guest_ajm_wvorbis_tests ${LIBS_INCLUDE_DIR}/tests/GuestAjmWVorbis.cpp) + target_include_directories(guest_ajm_wvorbis_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ajm_wvorbis_tests PRIVATE libSceAjm.native libc) + configure_windows_unwind(guest_ajm_wvorbis_tests) + add_test(NAME guest_ajm_wvorbis COMMAND guest_ajm_wvorbis_tests) if(UNIX) add_test_executable(guest_ajm_teardown_tests ${LIBS_INCLUDE_DIR}/tests/GuestAjmTeardown.cpp) target_include_directories(guest_ajm_teardown_tests PRIVATE ${LIBS_INCLUDE_DIR}) @@ -79,7 +78,7 @@ if(BUILD_TESTING) add_test(NAME guest_ajm_teardown COMMAND guest_ajm_teardown_tests $) endif() if(WIN32) - set_tests_properties(guest_ajm guest_ajm_vibration PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_ajm guest_ajm_vibration guest_ajm_wvorbis PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceAjm.native/Export.cpp b/core/libs/prx/libSceAjm.native/Export.cpp index 2df65a236d..c9f1f976a7 100644 --- a/core/libs/prx/libSceAjm.native/Export.cpp +++ b/core/libs/prx/libSceAjm.native/Export.cpp @@ -1,9 +1,11 @@ +#include "prx/libSceAjm.native/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include #include +#include // Entry points this library does not implement: reported before the unimplemented-call exception so an // APS5_TRACE_AJM run shows which one a title reached. @@ -12,6 +14,43 @@ static void AjmStub(const char* name) { NotImplemented_nid_no_patch(name); } +static_assert(sizeof(WVorbisHeaderPacketParameter) == 72); +static_assert(offsetof(WVorbisHeaderPacketParameter, setupPacket) == 32); +static_assert(sizeof(WVorbisSetupPacketParameter) == 56); +static_assert(offsetof(WVorbisSetupPacketParameter, input) == 16); + +namespace { + +constexpr int AJM_WVORBIS_ERROR_INVALID_PARAMETER = static_cast(0x8A6E0001); +constexpr int AJM_WVORBIS_ERROR_INVALID_SIZE = static_cast(0x8A6E0002); +constexpr int AJM_WVORBIS_ERROR_OUTPUT_TOO_SMALL = static_cast(0x8A6E0003); +constexpr int AJM_WVORBIS_ERROR_INVALID_SETUP = static_cast(0x8A6E0004); +constexpr std::size_t WVORBIS_LENGTH_TABLE_SIZE = 10; +constexpr std::size_t VORBIS_IDENTIFICATION_SIZE = 30; +constexpr std::size_t VORBIS_COMMENT_SIZE = 16; +constexpr std::size_t WVORBIS_SETUP_MIN_SIZE = 9; +constexpr std::size_t WVORBIS_SETUP_PART1_LENGTH_OFFSET = 7; +constexpr std::int32_t VORBIS_BLOCK_SIZE_MIN = 64; +constexpr std::int32_t VORBIS_BLOCK_SIZE_MAX = 8192; +constexpr std::uint8_t VORBIS_MAGIC[] {'v', 'o', 'r', 'b', 'i', 's'}; + +std::int32_t WVorbisBlockSize(std::int32_t log2) { + return static_cast(1u << (static_cast(log2) & 31u)); +} + +std::uint8_t* PutLittle(std::uint8_t* out, std::uint32_t value, std::size_t bytes) { + for (std::size_t i = 0; i < bytes; ++i) out[i] = static_cast(value >> (8 * i)); + return out + bytes; +} + +std::uint8_t* PutVorbisHeaderStart(std::uint8_t* out, std::uint8_t type) { + *out++ = type; + std::memcpy(out, VORBIS_MAGIC, sizeof(VORBIS_MAGIC)); + return out + sizeof(VORBIS_MAGIC); +} + +} + extern "C" { int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const void* pcm_input, size_t pcm_input_size, void* bitstream_output, size_t bitstream_output_size, void* result) { @@ -32,12 +71,49 @@ const char* APS5_VABI sceAjmStrError(int error) { return nullptr; } -int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(WVorbisHeaderPacketParameter* param) { + if (param == nullptr) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (param->size != sizeof(WVorbisHeaderPacketParameter)) return AJM_WVORBIS_ERROR_INVALID_SIZE; + if (param->bytesPerSecond < 0) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (WVorbisBlockSize(param->shortBlockExponent) < VORBIS_BLOCK_SIZE_MIN) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (param->longBlockExponent < param->shortBlockExponent || WVorbisBlockSize(param->longBlockExponent) > VORBIS_BLOCK_SIZE_MAX) + return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (param->setupPacket == nullptr) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (param->setupPacketSize < WVORBIS_SETUP_MIN_SIZE) return AJM_WVORBIS_ERROR_INVALID_SETUP; + if (param->output == nullptr) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + constexpr std::size_t headers = WVORBIS_LENGTH_TABLE_SIZE + VORBIS_IDENTIFICATION_SIZE + VORBIS_COMMENT_SIZE; + if (param->outputCapacity < headers || param->setupPacketSize > param->outputCapacity - headers) return AJM_WVORBIS_ERROR_OUTPUT_TOO_SMALL; + + const auto* setup = static_cast(param->setupPacket); + const auto part1 = static_cast(setup[WVORBIS_SETUP_PART1_LENGTH_OFFSET] | setup[WVORBIS_SETUP_PART1_LENGTH_OFFSET + 1] << 8); + auto* out = static_cast(param->output); + out = PutLittle(out, 0, 2); + out = PutLittle(out, VORBIS_IDENTIFICATION_SIZE, 2); + out = PutLittle(out, VORBIS_COMMENT_SIZE, 2); + out = PutLittle(out, part1, 2); + out = PutLittle(out, static_cast(param->setupPacketSize) - part1, 2); + + out = PutVorbisHeaderStart(out, 1); + out = PutLittle(out, 0, 4); + out = PutLittle(out, static_cast(param->channels), 1); + out = PutLittle(out, param->sampleRate, 4); + out = PutLittle(out, 0, 4); + out = PutLittle(out, static_cast(param->bytesPerSecond) * 8u, 4); + out = PutLittle(out, 0, 4); + *out++ = static_cast((param->shortBlockExponent & 0xF) | (param->longBlockExponent & 0xF) << 4); + *out++ = 1; + + out = PutVorbisHeaderStart(out, 3); + out = PutLittle(out, 0, 4); + out = PutLittle(out, 0, 4); + *out++ = 1; + + std::memcpy(out, setup, param->setupPacketSize); + param->outputSize = headers + param->setupPacketSize; return 0; } -int APS5_VABI sceAjmDecWVorbisCreateSetupPacket(void) { +int APS5_VABI sceAjmDecWVorbisCreateSetupPacket(WVorbisSetupPacketParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAjm.native/PatchLibAtrac9.cmake b/core/libs/prx/libSceAjm.native/PatchLibAtrac9.cmake index 6c1b91c006..ed0a64c6e9 100644 --- a/core/libs/prx/libSceAjm.native/PatchLibAtrac9.cmake +++ b/core/libs/prx/libSceAjm.native/PatchLibAtrac9.cmake @@ -28,3 +28,21 @@ if(libatrac9ConfigGuardPosition EQUAL -1) endif() string(REPLACE "${libatrac9OldConfigGuard}" "${libatrac9NewConfigGuard}" libatrac9Decinit "${libatrac9Decinit}") file(WRITE ${LIBATRAC9_PATCHED_DIR}/decinit.c "${libatrac9Decinit}") + +file(READ ${LIBATRAC9_DIR}/utility.c libatrac9Utility) +set(libatrac9OldReverse "\treturn value >> (32 - bitCount);") +set(libatrac9NewReverse "\treturn bitCount ? value >> (32 - bitCount) : 0u;") +string(FIND "${libatrac9Utility}" "${libatrac9OldReverse}" libatrac9ReversePosition) +if(libatrac9ReversePosition EQUAL -1) + message(FATAL_ERROR "LibAtrac9 BitReverse32 has changed; review the local shift-by-32 guard") +endif() +string(REPLACE "${libatrac9OldReverse}" "${libatrac9NewReverse}" libatrac9Utility "${libatrac9Utility}") + +set(libatrac9OldSignExtend "\treturn (value << shift) >> shift;") +set(libatrac9NewSignExtend "\treturn (int)((unsigned int)value << shift) >> shift;") +string(FIND "${libatrac9Utility}" "${libatrac9OldSignExtend}" libatrac9SignExtendPosition) +if(libatrac9SignExtendPosition EQUAL -1) + message(FATAL_ERROR "LibAtrac9 SignExtend32 has changed; review the local signed-shift guard") +endif() +string(REPLACE "${libatrac9OldSignExtend}" "${libatrac9NewSignExtend}" libatrac9Utility "${libatrac9Utility}") +file(WRITE ${LIBATRAC9_PATCHED_DIR}/utility.c "${libatrac9Utility}") diff --git a/core/libs/prx/libSceAjm.native/include/Api.hpp b/core/libs/prx/libSceAjm.native/include/Api.hpp new file mode 100644 index 0000000000..6547522846 --- /dev/null +++ b/core/libs/prx/libSceAjm.native/include/Api.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "SceTypes.hpp" + +struct WVorbisHeaderPacketParameter { + std::size_t size; + std::int32_t channels; + std::uint32_t sampleRate; + std::int32_t bytesPerSecond; + std::int32_t shortBlockExponent; + std::int32_t longBlockExponent; + std::uint32_t reserved; + const void* setupPacket; + std::size_t setupPacketSize; + void* output; + std::size_t outputCapacity; + std::size_t outputSize; +}; + +struct WVorbisSetupPacketParameter { + std::size_t size; + std::int32_t channels; + std::uint32_t reserved; + const void* input; + std::size_t inputSize; + void* output; + std::size_t outputCapacity; + std::size_t outputSize; +}; + +extern "C" { + +int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const void* pcmInput, size_t pcmInputSize, void* bitstreamOutput, size_t bitstreamOutputSize, void* result); +const char* APS5_VABI sceAjmStrError(int error); +int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(WVorbisHeaderPacketParameter* param); +int APS5_VABI sceAjmDecWVorbisCreateSetupPacket(WVorbisSetupPacketParameter* parameter); +int APS5_VABI sceAjmBatchGetLog(); +int APS5_VABI sceAjmInstanceAvailable(); +uint32_t APS5_VABI sceAjmInstanceCodecType(uint32_t instanceId); +int APS5_VABI sceAjmInstanceExtend(uint32_t context, uint32_t codec, uint64_t flags, uint32_t instance); +int APS5_VABI sceAjmInstanceSwitch(uint32_t context, uint32_t codec, uint64_t flags, uint32_t instance); +int APS5_VABI sceAjmInitialize(int64_t reserved, uint32_t* context); +int APS5_VABI sceAjmFinalize(uint32_t context); +int APS5_VABI sceAjmModuleRegister(uint32_t context, uint32_t codec, int64_t reserved); +int APS5_VABI sceAjmModuleUnregister(uint32_t context, uint32_t codec); +int APS5_VABI sceAjmMemoryRegister(uint32_t context, void* ptr, size_t pages); +int APS5_VABI sceAjmMemoryUnregister(uint32_t context, void* ptr); +int APS5_VABI sceAjmInstanceCreate(uint32_t context, uint32_t codec, uint64_t flags, uint32_t* instance); +int APS5_VABI sceAjmInstanceDestroy(uint32_t context, uint32_t instance); +int APS5_VABI sceAjmDecAt9ParseConfigData(const void* configData, AjmDecAt9ConfigDataInfo* configInfo); +int APS5_VABI sceAjmDecMp3ParseFrame(const uint8_t* stream, uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame); +int APS5_VABI sceAjmBatchInitialize(void* buffer, size_t size, AjmBatchInfo* info); +int APS5_VABI sceAjmBatchJobInitialize(AjmBatchInfo* info, uint32_t instance, const void* codecParameters, size_t codecParametersSize, void* result); +int APS5_VABI sceAjmBatchJobClearContext(AjmBatchInfo* info, uint32_t instance, void* result); +int APS5_VABI sceAjmBatchJobSetGaplessDecode(AjmBatchInfo* info, uint32_t instance, const void* gaplessDecode, int reset, void* result); +int APS5_VABI sceAjmBatchJobControl(AjmBatchInfo* info, uint32_t instance, uint64_t flags, const void* sidebandInput, size_t sidebandInputSize, void* sidebandOutput, size_t sidebandOutputSize); +int APS5_VABI sceAjmBatchJobRunSplit(AjmBatchInfo* info, uint32_t instance, uint64_t flags, const AjmBuffer* inputBuffers, size_t inputBuffersNum, const AjmBuffer* outputBuffers, size_t outputBuffersNum, void* sidebandOutput, size_t sidebandOutputSize); +int APS5_VABI sceAjmBatchJobRun(AjmBatchInfo* info, uint32_t instance, uint64_t flags, const void* dataInput, size_t dataInputSize, void* dataOutput, size_t dataOutputSize, void* sidebandOutput, size_t sidebandOutputSize); +int APS5_VABI sceAjmBatchJobSetResampleParameters(AjmBatchInfo* info, uint32_t instance, float ratio, uint32_t flags, void* result); +int APS5_VABI sceAjmBatchJobSetResampleParametersEx(AjmBatchInfo* info, uint32_t instance, float ratioStart, float ratioChangePerSample, uint32_t flags, void* result); +int APS5_VABI sceAjmBatchJobGetResampleInfo(AjmBatchInfo* info, uint32_t instance, void* result); +int APS5_VABI sceAjmBatchJobDecode(AjmBatchInfo* info, uint32_t instance, const void* bitstreamInput, size_t bitstreamInputSize, void* pcmOutput, size_t pcmOutputSize, void* result); +int APS5_VABI sceAjmBatchJobDecodeSplit(AjmBatchInfo* info, uint32_t instance, const AjmBuffer* inputBuffers, size_t inputBuffersNum, const AjmBuffer* outputBuffers, size_t outputBuffersNum, void* result); +int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo* info, uint32_t instance, const void* bitstreamInput, size_t bitstreamInputSize, void* pcmOutput, size_t pcmOutputSize, void* result); +int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo* info, uint32_t instance, void* result); +int APS5_VABI sceAjmBatchJobGetInfo(AjmBatchInfo* info, uint32_t instance, void* result); +int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo* info, uint32_t instance, void* result, size_t resultSize); +int APS5_VABI sceAjmBatchJobGetStatistics(AjmBatchInfo* info, float interval, void* result); +int APS5_VABI sceAjmBatchStart(uint32_t context, const AjmBatchInfo* info, int priority, AjmBatchError* error, uint32_t* batch); +int APS5_VABI sceAjmBatchWait(uint32_t context, uint32_t batch, uint32_t timeout, AjmBatchError* error); +int APS5_VABI sceAjmBatchCancel(uint32_t context, uint32_t batch); +int APS5_VABI sceAjmBatchErrorDump(const AjmBatchInfo* info, AjmBatchError* error); +void* APS5_VABI sceAjmBatchJobControlBufferRa(void* position, uint32_t instance, uint64_t flags, const void* sideband_input, size_t sideband_input_size, void* sideband_output, size_t sideband_output_size, void* return_address); +void* APS5_VABI sceAjmBatchJobRunBufferRa(void* position, uint32_t instance, uint64_t flags, const void* data_input, size_t data_input_size, void* data_output, size_t data_output_size, void* sideband_output, size_t sideband_output_size, void* return_address); +void* APS5_VABI sceAjmBatchJobRunSplitBufferRa(void* position, uint32_t instance, uint64_t flags, const AjmBuffer* input_buffers, size_t input_buffers_num, const AjmBuffer* output_buffers, size_t output_buffers_num, void* sideband_output, size_t sideband_output_size, void* return_address); +int APS5_VABI sceAjmBatchStartBuffer(uint32_t context, const uint8_t* batch, uint32_t batch_size, int priority, AjmBatchError* error, uint32_t* batch_id); + +} diff --git a/core/libs/prx/libSceAjm.native/libSceAjm.native.hpp b/core/libs/prx/libSceAjm.native/libSceAjm.native.hpp new file mode 100644 index 0000000000..ed8be05e6b --- /dev/null +++ b/core/libs/prx/libSceAjm.native/libSceAjm.native.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAjm.native/include/Api.hpp" diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 69fc72c729..72284a8281 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAjm.native/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -35,6 +36,8 @@ constexpr int SCE_AJM_ERROR_INVALID_INSTANCE = static_cast(0x80930003); constexpr int SCE_AJM_ERROR_INVALID_BATCH = static_cast(0x80930004); constexpr int SCE_AJM_ERROR_INVALID_PARAMETER = static_cast(0x80930005); constexpr int SCE_AJM_ERROR_OUT_OF_RESOURCES = static_cast(0x80930007); +constexpr int SCE_AJM_ERROR_MALFORMED_BATCH = static_cast(0x80930011); +constexpr int SCE_AJM_ERROR_INVALID_ADDRESS = static_cast(0x80930016); constexpr std::int32_t AJM_RESULT_NOT_INITIALIZED = 0x00000001; constexpr std::int32_t AJM_RESULT_INVALID_DATA = 0x00000002; @@ -215,6 +218,7 @@ struct JobHeader { std::uint32_t inputCount; std::uint32_t outputCount; std::uint8_t parameters[16]; + bool console; }; struct PackedJob { @@ -690,9 +694,17 @@ void EmitFrame(Instance& instance, PcmOutputs& outputs, const std::uint8_t* pcm, ResamplerProduce(instance, outputs, channels, encoding, sampleBytes); } +// A PS4 reports an ATRAC9 stream's bitrate from its superframe: 448-byte superframes of 4 frames of 256 +// samples at 48 kHz gave 168000 (Knowledge is Power: Decades, every run job's format sideband). +std::uint32_t At9Bitrate(const Instance& instance) { + const auto samples = static_cast(instance.info.framesInSuperframe) * static_cast(instance.info.frameSamples); + if (samples == 0) return 0; + return static_cast(static_cast(instance.info.superframeSize) * 8u * static_cast(instance.info.samplingRate) / samples); +} + SidebandFormat CurrentFormat(const Instance& instance) { switch (instance.codec) { - case CODEC_AT9: return {static_cast(instance.info.channels), ChannelMask(static_cast(instance.info.channels)), static_cast(instance.info.samplingRate), PcmEncoding(instance), 0, 0}; + case CODEC_AT9: return {static_cast(instance.info.channels), ChannelMask(static_cast(instance.info.channels)), static_cast(instance.info.samplingRate), PcmEncoding(instance), At9Bitrate(instance), 0}; case CODEC_MP3: return {instance.mp3Channels, ChannelMask(instance.mp3Channels), instance.mp3SampleRate, PcmEncoding(instance), instance.mp3Bitrate, 0}; case CODEC_OPUS: return {instance.opusChannels, ChannelMask(instance.opusChannels), instance.opusSampleRate, PcmEncoding(instance), 0, 0}; case CODEC_M4AAC: return {instance.aacChannels, ChannelMask(instance.aacChannels), instance.aacSampleRate, PcmEncoding(instance), instance.aacBitrate, 0}; @@ -767,12 +779,22 @@ void RunAt9(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c if (segmentEnded()) break; if (instance.superframeRemaining == 0) consumed += RiffDataOffset(input.data() + consumed, input.size() - consumed); const std::size_t needed = instance.superframeRemaining == 0 ? superframeSize : instance.superframeRemaining; + const bool roomForFrame = Resampling(instance) ? pcmOutputs.Room() >= channels * sampleBytes : pcmOutputs.Room() >= frameBytes; + // A PS4 reports which side ran out (probe runs of a title's ATRAC9 stream through libSceAjm's + // builders): PARTIAL_INPUT when the input ran out, on a superframe boundary or not, with room + // left; NOT_ENOUGH_ROOM when the room ran out with input left, after however many frames (and + // with none decoded for a zero room); both (0x18) when the input ended just as the output + // filled, which is every Wwise run job. A run without input (a format query) reports 0. + // Resampled runs were not measured and keep the earlier rule: a trailing partial superframe is + // partial input, a zero-output run is out of room. + const bool measured = !Resampling(instance); if (input.size() - consumed < needed) { - if (input.size() != consumed) result |= AJM_RESULT_PARTIAL_INPUT; + if (input.size() != consumed || (measured && !input.empty())) result |= AJM_RESULT_PARTIAL_INPUT; + if (measured && !input.empty() && !roomForFrame) result |= AJM_RESULT_NOT_ENOUGH_ROOM; break; } - if (Resampling(instance) ? pcmOutputs.Room() < channels * sampleBytes : pcmOutputs.Room() < frameBytes) { - if (frames == 0 && pcmOutputs.produced == 0) result |= AJM_RESULT_NOT_ENOUGH_ROOM; + if (!roomForFrame) { + if (measured || (frames == 0 && pcmOutputs.produced == 0)) result |= AJM_RESULT_NOT_ENOUGH_ROOM; break; } if (instance.superframeRemaining == 0) { @@ -796,6 +818,7 @@ void RunAt9(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c ++frames; EmitFrame(instance, pcmOutputs, pcm.data(), frameSamples, channels, sampleBytes); + if (job.console && (job.flags & RUN_MULTIPLE_FRAMES) == 0) break; } if (segmentEnded()) instance.segmentSamples = 0; @@ -851,14 +874,15 @@ std::uint32_t ReadBigEndian(const std::uint8_t* data, std::size_t bytes) { int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame); -void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint32_t header, AjmDecMp3ParseFrame* frame) { +void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint32_t header, AjmDecMp3ParseFrame* frame, int parseOfl) { const std::size_t end = std::min(streamSize, frame->frame_size); - if (end <= 4) return; - const std::size_t available = end - 4; + const std::size_t sideInfoStart = 4 + (((header >> 16u) & 1u) == 0 ? 2u : 0u); + if (end <= sideInfoStart) return; + const std::size_t available = end - sideInfoStart; std::size_t bit = 0; const auto readBits = [&](std::uint32_t count) { std::uint32_t value = 0; - for (std::uint32_t i = 0; i < count; ++i, ++bit) value = (value << 1u) | (bit / 8 < available ? (stream[4 + bit / 8] >> (7 - bit % 8)) & 1u : 0u); + for (std::uint32_t i = 0; i < count; ++i, ++bit) value = (value << 1u) | (bit / 8 < available ? (stream[sideInfoStart + bit / 8] >> (7 - bit % 8)) & 1u : 0u); return value; }; const bool mpeg1 = ((header >> 19u) & 3u) == 3u; @@ -872,11 +896,20 @@ void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint } } bit += mainDataBits; - std::size_t at = 4 + std::min((bit + 7) / 8, available); + std::size_t at = sideInfoStart + std::min((bit + 7) / 8, available); - if (at + 8 <= end && (std::memcmp(stream + at, "Xing", 4) == 0 || std::memcmp(stream + at, "Info", 4) == 0)) { - const std::uint8_t flags = stream[at + 7]; - std::size_t field = at + 8; + std::size_t tagAt = at; + if (tagAt + 8 > end || (std::memcmp(stream + tagAt, "Xing", 4) != 0 && std::memcmp(stream + tagAt, "Info", 4) != 0)) { + const std::size_t fixedOffset = mpeg1 ? (mono ? 21u : 36u) : (mono ? 13u : 21u); + tagAt = 0; + if (fixedOffset + 8 <= end && (std::memcmp(stream + fixedOffset, "Xing", 4) == 0 || std::memcmp(stream + fixedOffset, "Info", 4) == 0)) { + tagAt = fixedOffset; + } + } + + if (tagAt != 0) { + const std::uint8_t flags = stream[tagAt + 7]; + std::size_t field = tagAt + 8; std::uint32_t frames = 0; if (flags & 0x01u) { if (field + 4 > end) return; @@ -903,30 +936,34 @@ void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint frame->encoder_delay = frame->samples_per_channel + delay + 529; frame->ofl_type = MP3_OFL_LAME; } - } else if (at != 36 && end >= 40 && std::memcmp(stream + 36, "VBRI", 4) == 0) { - NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (VBRI header outside MPEG-1 stereo)"); - } else if (at + 26 <= end && std::memcmp(stream + at, "VBRI", 4) == 0) { - frame->encoder_delay = ReadBigEndian(stream + at + 6, 2); + return; + } + + const std::size_t vbriAt = end >= 40 && std::memcmp(stream + 36, "VBRI", 4) == 0 ? 36 : at; + if (vbriAt + 26 <= end && std::memcmp(stream + vbriAt, "VBRI", 4) == 0) { + frame->encoder_delay = ReadBigEndian(stream + vbriAt + 6, 2); frame->ofl_type = MP3_OFL_VBRI; - if (frame->frame_size <= streamSize) { + if (parseOfl == 1 && frame->frame_size <= streamSize) { AjmDecMp3ParseFrame next{}; - if (ParseMp3Header(stream + frame->frame_size, streamSize - static_cast(frame->frame_size), 1, &next) == 0 && next.ofl_type == MP3_OFL_FGH) { + if (ParseMp3Header(stream + frame->frame_size, streamSize - static_cast(frame->frame_size), 2, &next) == 0 && next.ofl_type == MP3_OFL_FGH) { frame->encoder_delay += next.encoder_delay; frame->total_samples = next.total_samples; frame->ofl_type = MP3_OFL_VBRI_AND_FGH; } } - } else if (at + 10 <= end) { - while (at + 9 < end && stream[at] != 0xB4u) ++at; - if (at + 9 >= end) return; - std::uint8_t crc = 0xFF; - for (std::size_t i = 0; i < 9; ++i) { - for (int shift = 7; shift >= 0; --shift) { - const bool same = ((crc >> 7u) & 1u) == ((stream[at + i] >> shift) & 1u); - crc = static_cast(same ? (crc << 1u) ^ 0x45u : crc << 1u); - } + return; + } + + while (at + 9 < end && stream[at] != 0xB4u) ++at; + if (at + 9 >= end) return; + std::uint8_t crc = 0xFF; + for (std::size_t i = 0; i < 9; ++i) { + for (int shift = 7; shift >= 0; --shift) { + const bool same = ((crc >> 7u) & 1u) == ((stream[at + i] >> shift) & 1u); + crc = static_cast(same ? (crc << 1u) ^ 0x45u : crc << 1u); } - if (stream[at + 9] != crc) return; + } + if (stream[at + 9] == crc) { frame->encoder_delay = ReadBigEndian(stream + at + 1, 2); frame->total_samples = ReadBigEndian(stream + at + 3, 4); frame->ofl_type = MP3_OFL_FGH; @@ -944,13 +981,13 @@ int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int par }; const std::uint32_t header = (std::uint32_t{stream[0]} << 24u) | (std::uint32_t{stream[1]} << 16u) | (std::uint32_t{stream[2]} << 8u) | stream[3]; const std::uint32_t version = (header >> 19u) & 3u; + const std::uint32_t layer = (header >> 17u) & 3u; const std::uint32_t sampleRate = sampleRates[version][(header >> 10u) & 3u]; const std::uint32_t bitrate = kbps[version][(header >> 12u) & 15u] * 1000u; - const bool valid = (header >> 21u) == 0x7FFu && sampleRate != 0 && bitrate != 0; + const bool valid = (header >> 21u) == 0x7FFu && layer != 0 && sampleRate != 0 && bitrate != 0; AJM_TRACE("[ajm] parse mp3 frame %02x %02x %02x %02x (%u bytes, parse ofl %d) -> %s, %u Hz, %u bps\n", stream[0], stream[1], stream[2], stream[3], streamSize, parseOfl, valid ? "ok" : "invalid", sampleRate, bitrate); if (!valid) return SCE_AJM_ERROR_INVALID_PARAMETER; - if (parseOfl && ((header >> 17u) & 3u) != 1u) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup outside layer III)"); - if (parseOfl && ((header >> 16u) & 1u) == 0) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup in a CRC-protected frame)"); + if (parseOfl && layer != 1u) return SCE_AJM_ERROR_INVALID_PARAMETER; const bool mpeg1 = version == 3; frame->frame_size = (mpeg1 ? 144u : 72u) * bitrate / sampleRate + ((header >> 9u) & 1u); frame->num_channels = ((header >> 6u) & 3u) == 3u ? 1u : 2u; @@ -961,7 +998,7 @@ int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int par frame->num_frames = 0; frame->total_samples = 0; frame->ofl_type = MP3_OFL_NONE; - if (parseOfl) ParseMp3Ofl(stream, streamSize, header, frame); + if (parseOfl) ParseMp3Ofl(stream, streamSize, header, frame, parseOfl); return 0; } @@ -1122,7 +1159,6 @@ std::size_t OpusPacketSamples(const std::uint8_t* packet, std::size_t size) { return frames * frameSamples; } - std::size_t OpusLength(const std::uint8_t* data, std::size_t size, std::size_t& at) { if (at >= size) return SIZE_MAX; const std::size_t first = data[at++]; @@ -1678,6 +1714,169 @@ void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* out } } +int RegisterBatch(std::uint32_t context, AjmBatchError* error, std::uint32_t* batch) { + if (error) std::memset(error, 0, sizeof(*error)); + const auto id = g_nextBatch.fetch_add(1, std::memory_order_relaxed); + { + std::lock_guard lock(g_batchLock); + g_batches.emplace(id, context); + } + *batch = id; + return 0; +} + +// The PS4 batch buffer (sceAjmBatchStartBuffer) is not private: the PS4 SDK builds it with inline code +// in the title, so it has the console's layout, taken from shadPS4's reimplementation of the PS4 +// library. It is a run of chunks, each starting with a 32-bit word that holds the chunk kind in bits +// 0-5 and a payload in bits 6-25. A job chunk has the instance as payload and, in a second word, the +// byte count of the chunks after it that describe the job; an inline chunk holds that many bytes of data +// a job's control buffer may point to. A job's flags chunk has flags bits 32-51 as payload and bits 0-31 +// in its second word; its buffer chunks have a 32-bit size and a 64-bit address. +enum class ConsoleChunk : std::uint32_t { + Job = 0, + InputRunBuffer = 1, + InputControlBuffer = 2, + ControlFlags = 3, + RunFlags = 4, + ReturnAddress = 6, + InlineBuffer = 7, + OutputRunBuffer = 17, + OutputControlBuffer = 18, +}; + +constexpr std::uint32_t CONSOLE_STATISTICS_INSTANCE = 0x80000; +constexpr std::uint64_t CONSOLE_FLAGS_VERSION_AND_CODEC = 0x7FF; + +// The low header word as the PS4's libSceAjm builders write it (a trace of 32 545 batches a Wwise title +// started on a PS4): bits 0-3 the kind, bit 4 the output direction of a buffer chunk +// (0x11 output run, 0x12 output control), bits 6-25 the job's instance or the flags' bits 32-51. The +// builders leave bits 5, 26, 27 and 31 as they were, and a title that reuses its batch memory (Wwise +// does) carries the previous batch's bytes there: a 6-bit kind read job headers as 16, 32 or 48 and run +// flags as 20, 36 or 52, and a 26-bit payload read flags bit 52 set. +ConsoleChunk ConsoleChunkKind(std::uint32_t word) { + const std::uint32_t kind = word & 0xFu; + return static_cast(kind == 1 || kind == 2 ? kind | (word & 0x10u) : kind); +} + +std::uint32_t ConsoleChunkPayload(std::uint32_t word) { + return (word >> 6) & 0xFFFFFu; +} + +std::uint8_t* PutConsoleChunk(std::uint8_t* at, ConsoleChunk kind, std::uint32_t payload, std::uint32_t word) { + const std::uint32_t words[2] = {static_cast(kind) | ((payload & 0xFFFFFu) << 6), word}; + std::memcpy(at, words, sizeof(words)); + return at + sizeof(words); +} + +std::uint8_t* PutConsoleFlags(std::uint8_t* at, ConsoleChunk kind, std::uint64_t flags) { + return PutConsoleChunk(at, kind, static_cast(flags >> 32), static_cast(flags)); +} + +std::uint8_t* PutConsoleBuffer(std::uint8_t* at, ConsoleChunk kind, const void* address, std::size_t size) { + at = PutConsoleChunk(at, kind, 0, static_cast(size)); + std::memcpy(at, &address, sizeof(address)); + return at + sizeof(address); +} + +std::uint8_t* BeginConsoleJob(std::uint8_t* job, const void* returnAddress) { + std::uint8_t* at = job + 2 * sizeof(std::uint32_t); + return returnAddress ? PutConsoleBuffer(at, ConsoleChunk::ReturnAddress, returnAddress, 0) : at; +} + +void* EndConsoleJob(std::uint8_t* job, std::uint8_t* end, std::uint32_t instance) { + PutConsoleChunk(job, ConsoleChunk::Job, instance, static_cast(end - job) - 2 * sizeof(std::uint32_t)); + return end; +} + +struct ConsoleJob { + JobHeader header; + std::vector buffers; +}; + +// What sceAjmBatchStartBuffer reports in the AjmBatchError when it refuses a batch: the error, the job +// chunk, the offending chunk's offset in the job and the job's return address, as a PS4 fills them for a +// buffer chunk with a null address and a nonzero size (INVALID_ADDRESS, nothing of the batch run). The +// other refusals here report MALFORMED_BATCH the same way; they were not measured. +struct ConsoleRefusal { + std::int32_t code = SCE_AJM_ERROR_MALFORMED_BATCH; + const std::uint8_t* job = nullptr; + std::uint32_t offset = 0; + const void* returnAddress = nullptr; +}; + +// Reads one console job: the chunks between the job header at `job` and `end`. A buffer chunk of size +// zero is dropped whatever its address, as a PS4 does. Returns false with the refusal filled for a null +// address with a nonzero size, for chunks that overrun the job and for a job without flags. +bool ReadConsoleJob(std::uint32_t instance, const std::uint8_t* job, const std::uint8_t* end, ConsoleJob& out, ConsoleRefusal& refusal) { + out.header = JobHeader{}; + out.header.instance = instance; + out.header.console = true; + std::vector inputs; + std::vector outputs; + AjmBuffer controlInput{}; + AjmBuffer controlOutput{}; + ConsoleChunk flagsKind = ConsoleChunk::Job; + refusal = ConsoleRefusal{}; + refusal.job = job; + const std::uint8_t* at = job + 2 * sizeof(std::uint32_t); + while (at < end) { + refusal.offset = static_cast(at - job); + if (end - at < 8) return false; + std::uint32_t words[2]; + std::memcpy(words, at, sizeof(words)); + const auto kind = ConsoleChunkKind(words[0]); + if (kind == ConsoleChunk::ControlFlags || kind == ConsoleChunk::RunFlags) { + if (flagsKind != ConsoleChunk::Job) return false; + flagsKind = kind; + out.header.flags = (std::uint64_t{ConsoleChunkPayload(words[0])} << 32) | words[1]; + at += sizeof(words); + continue; + } + if (kind != ConsoleChunk::InputRunBuffer && kind != ConsoleChunk::InputControlBuffer && kind != ConsoleChunk::ReturnAddress && kind != ConsoleChunk::OutputRunBuffer && kind != ConsoleChunk::OutputControlBuffer) { + NotImplemented_nid_no_patch(("sceAjmBatchStartBuffer (job chunk kind " + std::to_string(static_cast(kind)) + ")").c_str()); + } + if (end - at < 16) return false; + AjmBuffer buffer{nullptr, words[1]}; + std::memcpy(&buffer.ptr, at + sizeof(words), sizeof(buffer.ptr)); + at += 16; + if (kind == ConsoleChunk::ReturnAddress) { + refusal.returnAddress = buffer.ptr; + continue; + } + if (buffer.size == 0) continue; + if (!buffer.ptr) { + refusal.code = SCE_AJM_ERROR_INVALID_ADDRESS; + return false; + } + if (kind == ConsoleChunk::InputRunBuffer) inputs.push_back(buffer); + if (kind == ConsoleChunk::OutputRunBuffer) outputs.push_back(buffer); + if (kind == ConsoleChunk::InputControlBuffer) controlInput = buffer; + if (kind == ConsoleChunk::OutputControlBuffer) controlOutput = buffer; + } + refusal.offset = static_cast(end - job); + if (flagsKind == ConsoleChunk::Job) return false; + JobHeader& header = out.header; + header.sideband = controlOutput.ptr; + header.sidebandSize = controlOutput.size; + if (instance == CONSOLE_STATISTICS_INSTANCE) { + header.kind = JobKind::GetStatistics; + } else if (flagsKind == ConsoleChunk::ControlFlags) { + constexpr std::uint64_t implemented = CONSOLE_FLAGS_VERSION_AND_CODEC | CONTROL_RESET | CONTROL_INITIALIZE | SIDEBAND_GAPLESS_DECODE; + if ((header.flags & ~implemented) != 0) NotImplemented_nid_no_patch("sceAjmBatchStartBuffer (control job flags other than RESET, INITIALIZE and SIDEBAND_GAPLESS_DECODE)"); + if (!inputs.empty() || !outputs.empty()) NotImplemented_nid_no_patch("sceAjmBatchStartBuffer (control job with run buffers)"); + header.kind = JobKind::Control; + if (controlInput.size) inputs.push_back(controlInput); + } else { + if (controlInput.size) NotImplemented_nid_no_patch("sceAjmBatchStartBuffer (run job with an input control buffer)"); + header.kind = JobKind::Run; + } + header.inputCount = static_cast(inputs.size()); + header.outputCount = static_cast(outputs.size()); + AJM_TRACE("[ajm] console job: instance %u, %s flags 0x%llx, %u input and %u output buffers, control input %llu and output %llu bytes\n", instance, flagsKind == ConsoleChunk::ControlFlags ? "control" : "run", static_cast(header.flags), header.inputCount, header.outputCount, static_cast(controlInput.size), static_cast(controlOutput.size)); + out.buffers = std::move(inputs); + out.buffers.insert(out.buffers.end(), outputs.begin(), outputs.end()); + return true; +} } extern "C" { @@ -1879,14 +2078,19 @@ int APS5_VABI sceAjmBatchJobGetStatistics(AjmBatchInfo* info, float interval, vo int APS5_VABI sceAjmBatchStart(uint32_t context, const AjmBatchInfo* info, int priority, AjmBatchError* error, uint32_t* batch) { if (context == 0 || context >= g_nextContext.load(std::memory_order_relaxed)) return SCE_AJM_ERROR_INVALID_CONTEXT; (void)priority; - if (!info || !batch) return SCE_AJM_ERROR_INVALID_PARAMETER; + if (!info || !batch || !info->p_buffer || info->offset > info->size) return SCE_AJM_ERROR_INVALID_PARAMETER; AJM_TRACE("[ajm] batch start: context %u, priority %d, %llu of %llu buffer bytes used\n", context, priority, static_cast(info->offset), static_cast(info->size)); const auto* cursor = static_cast(info->p_buffer); - const auto* end = cursor + info->offset; + auto remaining = info->offset; std::vector buffers; - while (cursor < end) { + while (remaining != 0) { + if (remaining < sizeof(PackedJob)) NotImplemented_nid_no_patch("AJM batch contains a truncated private job header"); PackedJob record; std::memcpy(&record, cursor, sizeof(record)); + const std::size_t bufferCount = static_cast(record.inputCount) + record.outputCount; + const std::size_t parameterBytes = (static_cast(record.parameterSize) + 7u) & ~std::size_t{7}; + const std::size_t requiredBytes = sizeof(PackedJob) + bufferCount * sizeof(AjmBuffer) + parameterBytes; + if (record.parameterSize > sizeof(JobHeader::parameters) || record.bytes < requiredBytes || record.bytes > remaining) NotImplemented_nid_no_patch("AJM batch contains an invalid private job size"); JobHeader job{}; job.kind = static_cast(record.kind); job.bytes = record.bytes; @@ -1901,16 +2105,10 @@ int APS5_VABI sceAjmBatchStart(uint32_t context, const AjmBatchInfo* info, int p if (!buffers.empty()) std::memcpy(buffers.data(), cursor + sizeof(record), buffers.size() * sizeof(AjmBuffer)); if (record.parameterSize) std::memcpy(job.parameters, cursor + sizeof(record) + buffers.size() * sizeof(AjmBuffer), record.parameterSize); Execute(job, buffers.data(), buffers.data() + job.inputCount); - cursor += record.bytes; + remaining -= record.bytes; + if (remaining != 0) cursor += record.bytes; } - if (error) std::memset(error, 0, sizeof(*error)); - const auto id = g_nextBatch.fetch_add(1, std::memory_order_relaxed); - { - std::lock_guard lock(g_batchLock); - g_batches.emplace(id, context); - } - *batch = id; - return 0; + return RegisterBatch(context, error, batch); } int APS5_VABI sceAjmBatchWait(uint32_t context, uint32_t batch, uint32_t timeout, AjmBatchError* error) { @@ -1939,4 +2137,69 @@ int APS5_VABI sceAjmBatchErrorDump(const AjmBatchInfo* info, AjmBatchError* erro return 0; } +void* APS5_VABI sceAjmBatchJobControlBufferRa(void* position, uint32_t instance, uint64_t flags, const void* sideband_input, size_t sideband_input_size, void* sideband_output, size_t sideband_output_size, void* return_address) { + auto* job = static_cast(position); + auto* at = BeginConsoleJob(job, return_address); + at = PutConsoleBuffer(at, ConsoleChunk::InputControlBuffer, sideband_input, sideband_input_size); + at = PutConsoleFlags(at, ConsoleChunk::ControlFlags, flags); + at = PutConsoleBuffer(at, ConsoleChunk::OutputControlBuffer, sideband_output, sideband_output_size); + return EndConsoleJob(job, at, instance); +} + +void* APS5_VABI sceAjmBatchJobRunSplitBufferRa(void* position, uint32_t instance, uint64_t flags, const AjmBuffer* input_buffers, size_t input_buffers_num, const AjmBuffer* output_buffers, size_t output_buffers_num, void* sideband_output, size_t sideband_output_size, void* return_address) { + if ((input_buffers_num && !input_buffers) || (output_buffers_num && !output_buffers)) NotImplemented_nid_no_patch("AJM batch job with a null buffer list and a nonzero buffer count"); + auto* job = static_cast(position); + auto* at = BeginConsoleJob(job, return_address); + for (std::size_t index = 0; index < input_buffers_num; ++index) at = PutConsoleBuffer(at, ConsoleChunk::InputRunBuffer, input_buffers[index].ptr, input_buffers[index].size); + at = PutConsoleFlags(at, ConsoleChunk::RunFlags, flags); + for (std::size_t index = 0; index < output_buffers_num; ++index) at = PutConsoleBuffer(at, ConsoleChunk::OutputRunBuffer, output_buffers[index].ptr, output_buffers[index].size); + at = PutConsoleBuffer(at, ConsoleChunk::OutputControlBuffer, sideband_output, sideband_output_size); + return EndConsoleJob(job, at, instance); +} + +void* APS5_VABI sceAjmBatchJobRunBufferRa(void* position, uint32_t instance, uint64_t flags, const void* data_input, size_t data_input_size, void* data_output, size_t data_output_size, void* sideband_output, size_t sideband_output_size, void* return_address) { + const AjmBuffer input{const_cast(data_input), data_input_size}; + const AjmBuffer output{data_output, data_output_size}; + return sceAjmBatchJobRunSplitBufferRa(position, instance, flags, &input, 1, &output, 1, sideband_output, sideband_output_size, return_address); +} + +int APS5_VABI sceAjmBatchStartBuffer(uint32_t context, const uint8_t* batch, uint32_t batch_size, int priority, AjmBatchError* error, uint32_t* batch_id) { + if (context == 0 || context >= g_nextContext.load(std::memory_order_relaxed)) return SCE_AJM_ERROR_INVALID_CONTEXT; + if (!batch_id || !batch) return SCE_AJM_ERROR_INVALID_PARAMETER; + AJM_TRACE("[ajm] batch start buffer: context %u, priority %d, %u bytes\n", context, priority, batch_size); + std::vector jobs; + ConsoleRefusal refusal; + const std::uint8_t* at = batch; + const std::uint8_t* end = batch + batch_size; + bool accepted = batch_size % 8 == 0; + while (accepted && at < end) { + refusal = ConsoleRefusal{}; + refusal.job = at; + if (end - at < 8) { + accepted = false; + break; + } + std::uint32_t words[2]; + std::memcpy(words, at, sizeof(words)); + const std::uint8_t* body = at + sizeof(words); + if (words[1] > static_cast(end - body)) { + accepted = false; + break; + } + const auto kind = ConsoleChunkKind(words[0]); + if (kind == ConsoleChunk::Job) { + accepted = ReadConsoleJob(ConsoleChunkPayload(words[0]), at, body + words[1], jobs.emplace_back(), refusal); + } else if (kind != ConsoleChunk::InlineBuffer) { + NotImplemented_nid_no_patch(("sceAjmBatchStartBuffer (batch chunk kind " + std::to_string(static_cast(kind)) + ")").c_str()); + } + at = body + words[1]; + } + if (!accepted) { + if (error) *error = AjmBatchError{refusal.code, refusal.job, refusal.offset, refusal.returnAddress}; + return SCE_AJM_ERROR_MALFORMED_BATCH; + } + for (const ConsoleJob& job : jobs) Execute(job.header, job.buffers.data(), job.buffers.data() + job.header.inputCount); + return RegisterBatch(context, error, batch_id); +} + } diff --git a/core/libs/prx/libSceAjm/CMakeLists.txt b/core/libs/prx/libSceAjm/CMakeLists.txt new file mode 100644 index 0000000000..7a9c708eae --- /dev/null +++ b/core/libs/prx/libSceAjm/CMakeLists.txt @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/../libSceAjm.native/AjmLibrary.cmake) +add_sce_ajm_library(libSceAjm ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp) diff --git a/core/libs/prx/libSceAjm/Export.cpp b/core/libs/prx/libSceAjm/Export.cpp index fb34206c21..87830816c5 100644 --- a/core/libs/prx/libSceAjm/Export.cpp +++ b/core/libs/prx/libSceAjm/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAjm/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAjm/include/Api.hpp b/core/libs/prx/libSceAjm/include/Api.hpp new file mode 100644 index 0000000000..2cc01a795b --- /dev/null +++ b/core/libs/prx/libSceAjm/include/Api.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +} diff --git a/core/libs/prx/libSceAjm/libSceAjm.hpp b/core/libs/prx/libSceAjm/libSceAjm.hpp new file mode 100644 index 0000000000..302a86a22a --- /dev/null +++ b/core/libs/prx/libSceAjm/libSceAjm.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAjm/include/Api.hpp" diff --git a/core/libs/prx/libSceAjmi/Export.cpp b/core/libs/prx/libSceAjmi/Export.cpp index 83e8ac5ffa..bdb3e1159d 100644 --- a/core/libs/prx/libSceAjmi/Export.cpp +++ b/core/libs/prx/libSceAjmi/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAjmi/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -12,8 +13,8 @@ int APS5_VABI sceAjmBatchJobInlineBuffer(void) { return 0; } -int APS5_VABI sceAjmBatchJobRunBufferRa(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAjmBatchJobRunBufferRa_nid_disambig1(void) { + NotImplemented_nid_no_patch("sceAjmBatchJobRunBufferRa"); return 0; } diff --git a/core/libs/prx/libSceAjmi/include/Api.hpp b/core/libs/prx/libSceAjmi/include/Api.hpp new file mode 100644 index 0000000000..f12efb729c --- /dev/null +++ b/core/libs/prx/libSceAjmi/include/Api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAjmBatchJobInlineBuffer(void); +int APS5_VABI sceAjmBatchJobRunBufferRa_nid_disambig1(void); + +} diff --git a/core/libs/prx/libSceAjmi/libSceAjmi.hpp b/core/libs/prx/libSceAjmi/libSceAjmi.hpp new file mode 100644 index 0000000000..739e63957f --- /dev/null +++ b/core/libs/prx/libSceAjmi/libSceAjmi.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAjmi/include/Api.hpp" diff --git a/core/libs/prx/libSceAmpr/Export.cpp b/core/libs/prx/libSceAmpr/Export.cpp index 6e55ecbd7b..ab976c5ed3 100644 --- a/core/libs/prx/libSceAmpr/Export.cpp +++ b/core/libs/prx/libSceAmpr/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceAmpr/include/Api.hpp" #include #include #include @@ -96,7 +98,6 @@ static int AppendNop(Apr::CommandBufferObject* buffer, std::uint32_t dwords, con if (bytes > RemainingCommandBytes(*buffer)) return SCE_KERNEL_ERROR_EBUSY; const Apr::CommandHeader header{Apr::Opcode::Nop, static_cast(bytes)}; std::uint8_t* destination = buffer->base + buffer->offset; - std::memset(destination, 0, bytes); std::memcpy(destination, &header, sizeof(header)); if (data) std::memcpy(destination + sizeof(header), data, std::uint64_t{dwords} * 4u); buffer->offset += static_cast(bytes); @@ -227,22 +228,11 @@ static int RecordWriteAddressFromCounterPair(Apr::CommandBufferObject* buffer, v static std::atomic_flag sharedPtrSpinLock; -struct AmmUsageStatsData { - std::uint64_t sizeInBytes; - std::uint16_t numPageTablePoolEntries; - std::uint16_t allocatedPageTablePoolEntries; - std::uint16_t highWatermarkAllocatedEntries; - std::uint16_t reserved; - std::uint32_t ringIdleFlags; -}; static_assert(sizeof(AmmUsageStatsData) == 0x18); static constexpr std::uint16_t AmmPageTablePoolEntries = 512; static constexpr std::uint32_t AmmRingIdleFlags = 0x7; extern "C" { -int APS5_VABI _Cnd_init_nid_postfix(void** handle); -int APS5_VABI wcstombs_s_nid_postfix(std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t* source, std::size_t limit); -[[noreturn]] void APS5_VABI __cxa_call_terminate_nid_postfix(void* exception); int APS5_VABI sceAmprCommandBufferWriteAddressOnCompletion(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t value) { if (!ValidWriteAddress(address)) return SCE_KERNEL_ERROR_EINVAL; @@ -432,7 +422,6 @@ int APS5_VABI sceAmprCommandBufferNop(Apr::CommandBufferObject* buffer, std::uin int APS5_VABI sceAmprCommandBufferNopWithData(Apr::CommandBufferObject* buffer, std::uint32_t dwords, const std::uint32_t* data) { if (dwords > 15u) return SCE_KERNEL_ERROR_EINVAL; - if (dwords != 0u && !data) throw std::invalid_argument("sceAmprCommandBufferNopWithData: null data"); return AppendNop(buffer, dwords, data); } @@ -626,11 +615,6 @@ std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00(std return sizeof(Apr::WriteKernelEventQueueCommand); } -struct AmmSubmitResult { - std::int32_t result; - std::uint32_t errorOffset; -}; - int APS5_VABI sceAmprAmmCommandBufferConstructor(Apr::CommandBufferObject* buffer) { (void)buffer; return 0; @@ -847,11 +831,6 @@ int APS5_VABI sceAmprAmmWaitCommandBufferCompletion(std::uint32_t id) { return AmmSubmitted_nid_no_patch(id) ? 0 : SCE_KERNEL_ERROR_ESRCH; } -int APS5_VABI _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - void APS5_VABI _Unlock_shared_ptr_spin_lock_nid_postfix(void) { sharedPtrSpinLock.clear(std::memory_order_release); } @@ -860,21 +839,11 @@ int APS5_VABI _Cnd_init_with_name_nid_postfix(void** handle, const char*) { return _Cnd_init_nid_postfix(handle); } -int APS5_VABI _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZNKSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateEP2tmPKcSE__nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -884,8 +853,8 @@ void APS5_VABI _Lock_shared_ptr_spin_lock_nid_postfix(void) { while (sharedPtrSpinLock.test_and_set(std::memory_order_acquire)) std::this_thread::yield(); } - [[noreturn]] void APS5_VABI __cxa_call_unexpected_nid_postfix(void* unwindException) { + __cxa_call_terminate_nid_postfix(unwindException); } diff --git a/core/libs/prx/libSceAmpr/include/Api.hpp b/core/libs/prx/libSceAmpr/include/Api.hpp new file mode 100644 index 0000000000..8939494652 --- /dev/null +++ b/core/libs/prx/libSceAmpr/include/Api.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include "prx/libkernel/Apr/include/AprCommandBuffer.hpp" +#include "prx/libc/include/GuestTime.hpp" +#include "SceTypes.hpp" + +struct AmmSubmitResult { + std::int32_t result; + std::uint32_t errorOffset; +}; + +struct AmmUsageStatsData { + std::uint64_t sizeInBytes; + std::uint16_t numPageTablePoolEntries; + std::uint16_t allocatedPageTablePoolEntries; + std::uint16_t highWatermarkAllocatedEntries; + std::uint16_t reserved; + std::uint32_t ringIdleFlags; +}; + +extern "C" { + +int APS5_VABI sceAmprCommandBufferWriteAddressOnCompletion(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t value); +int APS5_VABI sceAmprCommandBufferWriteCounterOnCompletion(Apr::CommandBufferObject* buffer, std::uint8_t counter, std::uint32_t value); +int APS5_VABI sceAmprCommandBufferWaitOnAddress(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t reference, std::uint8_t compare, std::uint8_t flush); +int APS5_VABI sceAmprCommandBufferWaitOnCounter(Apr::CommandBufferObject* buffer, std::uint8_t counter, std::uint32_t reference, std::uint8_t compare, std::uint8_t flush); +int APS5_VABI sceAmprCommandBufferWriteKernelEventQueueOnCompletion(Apr::CommandBufferObject* buffer, std::uint64_t equeue, std::int32_t ident, std::uint64_t data); +int APS5_VABI sceAmprCommandBufferWriteAddressFromTimeCounterOnCompletion(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address); +int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterOnCompletion(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint8_t counter); +int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterPairOnCompletion(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint8_t counter); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressOnCompletion(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteCounterOnCompletion(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnAddress(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnCounter(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteKernelEventQueueOnCompletion(); +int APS5_VABI sceAmprAprCommandBufferConstructor(Apr::CommandBufferObject* buffer, uint64_t* gatherState, uint64_t* scatterState); +int APS5_VABI sceAmprAprCommandBufferDestructor(Apr::CommandBufferObject* buffer, uint64_t* gatherState, uint64_t* scatterState); +int APS5_VABI sceAmprAprCommandBufferMapBegin(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection); +int APS5_VABI sceAmprAprCommandBufferMapDirectBegin(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t directOffset, std::uint64_t size, std::int32_t type, std::int32_t protection); +int APS5_VABI sceAmprAprCommandBufferMapEnd(Apr::CommandBufferObject* buffer); +int APS5_VABI sceAmprAprCommandBufferReadFile(Apr::CommandBufferObject* buffer, uint64_t* gatherState, uint64_t* scatterState, uint32_t fileId, void* destination, uint64_t size, uint64_t offset); +int APS5_VABI sceAmprAprCommandBufferReadFileGather(Apr::CommandBufferObject* buffer, std::uint64_t* mapState, std::uint64_t* scatterGatherState, std::uint64_t size, std::uint64_t offset); +int APS5_VABI sceAmprAprCommandBufferReadFileGatherScatter(Apr::CommandBufferObject* buffer, std::uint64_t* mapState, std::uint64_t* scatterGatherState, void* destination, std::uint64_t size, std::uint64_t offset); +int APS5_VABI sceAmprAprCommandBufferReadFileScatter(Apr::CommandBufferObject* buffer, std::uint64_t* mapState, std::uint64_t* scatterGatherState, void* destination, std::uint64_t size); +int APS5_VABI sceAmprAprCommandBufferResetGatherScatterState(Apr::CommandBufferObject* buffer); +void* APS5_VABI sceAmprCommandBufferClearBuffer(Apr::CommandBufferObject* buffer); +int APS5_VABI sceAmprCommandBufferConstructMarker(Apr::CommandBufferObject* buffer, std::uint32_t type, const char* text, const std::uint32_t* color); +int APS5_VABI sceAmprCommandBufferConstructNop(Apr::CommandBufferObject* buffer, std::int16_t type, const void* payload, std::uint32_t payloadBytes, const std::uint32_t* word); +int APS5_VABI sceAmprCommandBufferConstructor(Apr::CommandBufferObject* buffer); +int APS5_VABI sceAmprCommandBufferDestructor(Apr::CommandBufferObject* buffer); +void* APS5_VABI sceAmprCommandBufferGetBufferBaseAddress(const Apr::CommandBufferObject* buffer); +uint32_t APS5_VABI sceAmprCommandBufferGetCurrentOffset(const Apr::CommandBufferObject* buffer); +uint32_t APS5_VABI sceAmprCommandBufferGetNumCommands(const Apr::CommandBufferObject* buffer); +uint32_t APS5_VABI sceAmprCommandBufferGetSize(const Apr::CommandBufferObject* buffer); +uint32_t APS5_VABI sceAmprCommandBufferGetType(const Apr::CommandBufferObject* buffer); +int APS5_VABI sceAmprCommandBufferNop(Apr::CommandBufferObject* buffer, std::uint32_t dwords); +int APS5_VABI sceAmprCommandBufferNopWithData(Apr::CommandBufferObject* buffer, std::uint32_t dwords, const std::uint32_t* data); +int APS5_VABI sceAmprCommandBufferPopMarker(Apr::CommandBufferObject* buffer); +int APS5_VABI sceAmprCommandBufferPushMarker(Apr::CommandBufferObject* buffer, const char* text); +int APS5_VABI sceAmprCommandBufferPushMarkerWithColor(Apr::CommandBufferObject* buffer, const char* text, std::uint32_t color); +int APS5_VABI sceAmprCommandBufferReset(Apr::CommandBufferObject* buffer); +int APS5_VABI sceAmprCommandBufferSetBuffer(Apr::CommandBufferObject* buffer, void* memory, uint32_t size); +int APS5_VABI sceAmprCommandBufferSetMarker(Apr::CommandBufferObject* buffer, const char* text); +int APS5_VABI sceAmprCommandBufferSetMarkerWithColor(Apr::CommandBufferObject* buffer, const char* text, const std::uint32_t* color); +int APS5_VABI sceAmprCommandBufferWaitOnAddress_04_00(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t reference, std::uint8_t compare, std::uint8_t flush); +int APS5_VABI sceAmprCommandBufferWaitOnCounter_04_00(Apr::CommandBufferObject* buffer, std::uint8_t counter, std::uint8_t access, std::uint64_t reference, std::uint8_t compare, std::uint8_t maskOperation, std::uint64_t mask, std::uint8_t flush); +int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterPair_04_00(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint8_t counter, std::uint64_t atStart); +int APS5_VABI sceAmprCommandBufferWriteAddressFromCounter_04_00(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint8_t counter, std::uint64_t atStart); +int APS5_VABI sceAmprCommandBufferWriteAddressFromTimeCounter_04_00(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t atStart); +int APS5_VABI sceAmprCommandBufferWriteAddress_04_00(Apr::CommandBufferObject* buffer, uint64_t* address, uint64_t value, uint32_t flags); +int APS5_VABI sceAmprCommandBufferWriteCounter_04_00(Apr::CommandBufferObject* buffer, std::uint8_t counter, std::uint8_t access, std::uint64_t value, std::uint8_t operation, std::uint8_t atStart); +int APS5_VABI sceAmprCommandBufferWriteKernelEventQueue_04_00(Apr::CommandBufferObject* buffer, std::uint64_t equeue, std::int32_t ident, std::uint64_t data, std::uint64_t atStart); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeMapBegin(std::uint64_t address, std::uint64_t size, std::uint32_t type, std::uint32_t protection); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeMapDirectBegin(std::uint64_t address, std::uint64_t directOffset, std::uint64_t size, std::uint32_t type, std::uint32_t protection); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeMapEnd(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeNop(std::uint32_t dwords); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeNopWithData(std::uint32_t dwords); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizePopMarker(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizePushMarker(const char* text); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizePushMarkerWithColor(const char* text, std::uint32_t color); +uint32_t APS5_VABI sceAmprMeasureCommandSizeReadFile(void); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileGather(std::uint64_t size, std::uint64_t offset); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileGatherScatter(void* destination, std::uint64_t size, std::uint64_t offset); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileScatter(void* destination, std::uint64_t size); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeResetGatherScatterState(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeSetMarker(const char* text); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeSetMarkerWithColor(const char* text, std::uint32_t color); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnAddress_04_00(volatile std::uint64_t* address, std::uint64_t, std::uint8_t compare, std::uint8_t flush); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnCounter_04_00(std::uint8_t, std::uint8_t access, std::uint64_t, std::uint8_t compare, std::uint8_t maskOperation, std::uint64_t, std::uint8_t flush); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounterPair_04_00(volatile std::uint64_t* address, std::uint8_t counter); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounter_04_00(volatile std::uint64_t* address, std::uint8_t counter); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromTimeCounter_04_00(volatile std::uint64_t* address); +uint32_t APS5_VABI sceAmprMeasureCommandSizeWriteAddress_04_00(void); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteCounter_04_00(std::uint8_t counter, std::uint8_t access, std::uint64_t, std::uint8_t operation); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00(std::uint64_t, std::int32_t, std::uint64_t); +int APS5_VABI sceAmprAmmCommandBufferConstructor(Apr::CommandBufferObject* buffer); +int APS5_VABI sceAmprAmmCommandBufferDestructor(Apr::CommandBufferObject* buffer); +int APS5_VABI sceAmprAmmCommandBufferMap(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection); +int APS5_VABI sceAmprAmmCommandBufferMapWithGpuMaskId(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection, std::uint8_t gpuMaskId); +int APS5_VABI sceAmprAmmCommandBufferMapDirect(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t directOffset, std::uint64_t size, std::int32_t type, std::int32_t protection); +int APS5_VABI sceAmprAmmCommandBufferMapDirectWithGpuMaskId(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t directOffset, std::uint64_t size, std::int32_t type, std::int32_t protection, std::uint8_t gpuMaskId); +int APS5_VABI sceAmprAmmCommandBufferUnmap(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMap(std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMapWithGpuMaskId(std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection, std::uint8_t gpuMaskId); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMapDirect(std::uint64_t address, std::uint64_t directOffset, std::uint64_t size, std::int32_t type, std::int32_t protection); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMapDirectWithGpuMaskId(std::uint64_t address, std::uint64_t directOffset, std::uint64_t size, std::int32_t type, std::int32_t protection, std::uint8_t gpuMaskId); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeUnmap(std::uint64_t address, std::uint64_t size); +int APS5_VABI sceAmprAmmCommandBufferRemap(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t source, std::uint64_t size, std::int32_t protection); +int APS5_VABI sceAmprAmmCommandBufferRemapWithGpuMaskId(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t source, std::uint64_t size, std::int32_t protection, std::uint8_t gpuMaskId); +int APS5_VABI sceAmprAmmCommandBufferMultiMap(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t alias, std::uint64_t size, std::int32_t protection); +int APS5_VABI sceAmprAmmCommandBufferMultiMapWithGpuMaskId(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t alias, std::uint64_t size, std::int32_t protection, std::uint8_t gpuMaskId); +int APS5_VABI sceAmprAmmCommandBufferModifyProtect(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size, std::int32_t protection, std::int32_t mask); +int APS5_VABI sceAmprAmmCommandBufferModifyProtectWithGpuMaskId(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size, std::int32_t protection, std::int32_t mask, std::uint8_t gpuMaskId); +int APS5_VABI sceAmprAmmCommandBufferModifyMtypeProtect(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection, std::int32_t mask); +int APS5_VABI sceAmprAmmCommandBufferModifyMtypeProtectWithGpuMaskId(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection, std::int32_t mask, std::uint8_t gpuMaskId); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeRemap(std::uint64_t address, std::uint64_t source, std::uint64_t size, std::int32_t protection); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeRemapWithGpuMaskId(std::uint64_t address, std::uint64_t source, std::uint64_t size, std::int32_t protection, std::uint8_t gpuMaskId); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMultiMap(std::uint64_t address, std::uint64_t alias, std::uint64_t size, std::int32_t protection); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMultiMapWithGpuMaskId(std::uint64_t address, std::uint64_t alias, std::uint64_t size, std::int32_t protection, std::uint8_t gpuMaskId); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeModifyProtect(std::uint64_t address, std::uint64_t size, std::int32_t protection, std::int32_t mask); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeModifyProtectWithGpuMaskId(std::uint64_t address, std::uint64_t size, std::int32_t protection, std::int32_t mask, std::uint8_t gpuMaskId); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeModifyMtypeProtect(std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection, std::int32_t mask); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeModifyMtypeProtectWithGpuMaskId(std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection, std::int32_t mask, std::uint8_t gpuMaskId); +int APS5_VABI sceAmprAmmCommandBufferMapAsPrt(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size); +int APS5_VABI sceAmprAmmCommandBufferAllocatePaForPrt(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection); +int APS5_VABI sceAmprAmmCommandBufferRemapIntoPrt(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t source, std::uint64_t size, std::int32_t protection, std::uint32_t opcode); +int APS5_VABI sceAmprAmmCommandBufferUnmapToPrt(Apr::CommandBufferObject* buffer, std::uint64_t address, std::uint64_t size); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMapAsPrt(std::uint64_t address, std::uint64_t size); +std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeAllocatePaForPrt(std::uint64_t address, std::uint64_t size, std::int32_t type, std::int32_t protection); +int APS5_VABI sceAmprAmmGiveDirectMemory(std::int64_t searchStart, std::int64_t searchEnd, std::size_t size, std::size_t alignment, int usage, std::int64_t* offset); +int APS5_VABI sceAmprAmmGetVirtualAddressRanges(std::uint64_t* start, std::uint64_t* end, std::uint64_t* multimapStart, std::uint64_t* multimapEnd); +int APS5_VABI sceAmprAmmSetPageTablePoolOccupancyNotificationThreshold(void); +int APS5_VABI sceAmprAmmSubmitCommandBuffer(void* base, std::uint32_t offset, std::uint32_t priority); +int APS5_VABI sceAmprAmmSubmitCommandBuffer2(void* base, std::uint32_t offset, std::uint32_t priority, AmmSubmitResult* result, std::uint32_t* id); +int APS5_VABI sceAmprAmmSubmitCommandBuffer3(void* base, std::uint32_t offset, std::uint32_t priority, std::uint32_t* id); +int APS5_VABI sceAmprAmmWaitCommandBufferCompletion(std::uint32_t id); +void APS5_VABI _Unlock_shared_ptr_spin_lock_nid_postfix(void); +int APS5_VABI _Cnd_init_with_name_nid_postfix(void** handle, const char*); +int APS5_VABI _ZNKSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateEP2tmPKcSE__nid_postfix(void); +int APS5_VABI _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(void); +void APS5_VABI _Lock_shared_ptr_spin_lock_nid_postfix(void); +SonyLconv* APS5_VABI sceAmprLocaleconv(void); +unsigned __int128 APS5_VABI sceAmprUdivti3(unsigned __int128 dividend, unsigned __int128 divisor); +double APS5_VABI sceAmprSinh(double x, double y); +char* APS5_VABI sceAmprStrtokS(char* str, const char* delim, char** context); +int APS5_VABI sceAmprCtimeS(char* buffer, std::size_t size, const std::int64_t* time); +int APS5_VABI sceAmprWcscpyS(std::uint16_t* dest, std::size_t size, const std::uint16_t* src); +void* APS5_VABI sceAmprSetConstraintHandlerS(void* handler); +int APS5_VABI sceAmprFltrounds(void); +void APS5_VABI sceAmprIgnoreHandlerS(const char* message, void* pointer, int error); +int APS5_VABI sceAmprWcstombsS(std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t* source, std::size_t limit); +std::size_t APS5_VABI sceAmprMbstowcs(std::uint16_t* dest, const char* src, std::size_t size); +int APS5_VABI sceAmprAsctimeS(char* buffer, std::size_t size, const GuestTm* time); +int APS5_VABI sceAmprFpclassifyd(double value); +int APS5_VABI sceAmprAmmGetUsageStatsData(AmmUsageStatsData* stats); + +[[noreturn]] void APS5_VABI __cxa_call_unexpected_nid_postfix(void* unwindException); + +} diff --git a/core/libs/prx/libSceAmpr/libSceAmpr.hpp b/core/libs/prx/libSceAmpr/libSceAmpr.hpp new file mode 100644 index 0000000000..de5e0e7862 --- /dev/null +++ b/core/libs/prx/libSceAmpr/libSceAmpr.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAmpr/include/Api.hpp" diff --git a/core/libs/prx/libSceAppChecker/Export.cpp b/core/libs/prx/libSceAppChecker/Export.cpp index b50753644e..1ed8f0345d 100644 --- a/core/libs/prx/libSceAppChecker/Export.cpp +++ b/core/libs/prx/libSceAppChecker/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAppChecker/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAppChecker/include/Api.hpp b/core/libs/prx/libSceAppChecker/include/Api.hpp new file mode 100644 index 0000000000..9c850dbc67 --- /dev/null +++ b/core/libs/prx/libSceAppChecker/include/Api.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAppCheckerAbort(); +int APS5_VABI sceAppCheckerExecute(); +int APS5_VABI sceAppCheckerExecuteEx(); + +} diff --git a/core/libs/prx/libSceAppChecker/libSceAppChecker.hpp b/core/libs/prx/libSceAppChecker/libSceAppChecker.hpp new file mode 100644 index 0000000000..471d19be34 --- /dev/null +++ b/core/libs/prx/libSceAppChecker/libSceAppChecker.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAppChecker/include/Api.hpp" diff --git a/core/libs/prx/libSceAppContent/Export.cpp b/core/libs/prx/libSceAppContent/Export.cpp index bb0eba21ea..3485cd7b86 100644 --- a/core/libs/prx/libSceAppContent/Export.cpp +++ b/core/libs/prx/libSceAppContent/Export.cpp @@ -1,16 +1,20 @@ +#include "prx/libSceAppContent/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/RemoveTree.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" #include +#include #include #include #include #include #include +static constexpr int SCE_APP_CONTENT_ERROR_NOT_INITIALIZED = static_cast(0x80D90001); static constexpr int SCE_APP_CONTENT_ERROR_PARAMETER = static_cast(0x80D90002); static constexpr int SCE_APP_CONTENT_ERROR_BUSY = static_cast(0x80D90003); static constexpr int SCE_APP_CONTENT_ERROR_NOT_MOUNTED = static_cast(0x80D90004); @@ -23,13 +27,22 @@ static constexpr char TEMPORARY_MOUNT_POINT[] = "/temp0"; static constexpr char DOWNLOAD_MOUNT_POINT[] = "/download0"; static constexpr uint32_t TEMPORARY_DATA_OPTION_FORMAT = 1; -struct AppContentAddcontInfo { - NpUnifiedEntitlementLabel entitlement_label; - uint32_t status; -}; - namespace { +std::atomic_bool initialized{false}; +std::mutex initializationMutex; + +std::uint32_t compiledSdkVersion() { + const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t version; + std::memcpy(&version, process + 16, sizeof(version)); + return version; +} + +bool validEntitlementLabel(const NpUnifiedEntitlementLabel& label, std::uint32_t minimumSdkVersion) { + return compiledSdkVersion() < minimumSdkVersion || std::all_of(std::begin(label.padding), std::end(label.padding), [](char value) { return value == 0; }); +} + struct TemporaryData { std::mutex mutex; const std::filesystem::path directory = ResolvePath_nid_no_patch(TEMPORARY_MOUNT_POINT); @@ -57,30 +70,37 @@ static void ClearDirectory(const std::filesystem::path& directory) { extern "C" { 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; if (!entitlement_label || !mount_point) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (!validEntitlementLabel(*entitlement_label, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER; return SCE_APP_CONTENT_ERROR_NOT_FOUND; } int APS5_VABI sceAppContentAddcontUnmount(const AppContentMountPoint* mount_point) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; if (!mount_point) return SCE_APP_CONTENT_ERROR_PARAMETER; return SCE_APP_CONTENT_ERROR_NOT_FOUND; } int APS5_VABI sceAppContentGetAddcontInfo(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label, AppContentAddcontInfo* info) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; (void)service_label; if (!entitlement_label || !info) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (!validEntitlementLabel(*entitlement_label, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER; return SCE_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT; } -int APS5_VABI sceAppContentGetAddcontInfoList(uint32_t service_label, AppContentAddcontInfo* list, uint32_t list_num, uint32_t* hit_num) { - (void)service_label; - if ((!list || list_num == 0) && !hit_num) return SCE_APP_CONTENT_ERROR_PARAMETER; - if (hit_num) *hit_num = 0; +int APS5_VABI sceAppContentGetAddcontInfoList(uint32_t serviceLabel, AppContentAddcontInfo* list, uint32_t listNum, uint32_t* hitNum) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; + (void)serviceLabel; + if (!hitNum) return SCE_APP_CONTENT_ERROR_PARAMETER; + *hitNum = 0; return 0; } int APS5_VABI sceAppContentAppParamGetInt(uint32_t param_id, int32_t* value) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; if (!value) return SCE_APP_CONTENT_ERROR_PARAMETER; switch (param_id) { case APPPARAM_ID_SKU_FLAG: @@ -95,6 +115,7 @@ int APS5_VABI sceAppContentAppParamGetInt(uint32_t param_id, int32_t* value) { } int APS5_VABI sceAppContentDownloadDataFormat(const AppContentMountPoint* mount_point) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; if (!mount_point) return SCE_APP_CONTENT_ERROR_PARAMETER; if (std::strncmp(mount_point->data, DOWNLOAD_MOUNT_POINT, sizeof(mount_point->data)) != 0) return SCE_APP_CONTENT_ERROR_NOT_MOUNTED; const auto directory = ResolvePath_nid_no_patch(DOWNLOAD_MOUNT_POINT); @@ -103,12 +124,13 @@ int APS5_VABI sceAppContentDownloadDataFormat(const AppContentMountPoint* mount_ return 0; } -int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMountPoint* mount_point, size_t* available_space_kb) { - if (!mount_point || !available_space_kb) return SCE_APP_CONTENT_ERROR_PARAMETER; - if (std::strncmp(mount_point->data, DOWNLOAD_MOUNT_POINT, sizeof(mount_point->data)) != 0) return SCE_APP_CONTENT_ERROR_NOT_MOUNTED; +int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMountPoint* mountPoint, size_t* availableSpaceKb) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; + if (!mountPoint || !availableSpaceKb) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (std::strncmp(mountPoint->data, DOWNLOAD_MOUNT_POINT, sizeof(mountPoint->data)) != 0) return SCE_APP_CONTENT_ERROR_NOT_MOUNTED; const std::uint64_t quotaKb = GetAppDownloadDataSizeMiB_nid_postfix() * 1024u; if (quotaKb == 0) { - *available_space_kb = 0; + *availableSpaceKb = 0; return 0; } const auto directory = ResolvePath_nid_no_patch(DOWNLOAD_MOUNT_POINT); @@ -118,19 +140,27 @@ int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMount if (entry.is_regular_file()) usedKb += (entry.file_size() + 1023u) / 1024u; } const std::uint64_t hostKb = std::filesystem::space(directory).available / 1024u; - *available_space_kb = static_cast(std::min(quotaKb - std::min(quotaKb, usedKb), hostKb)); + *availableSpaceKb = static_cast(std::min(quotaKb - std::min(quotaKb, usedKb), hostKb)); return 0; } -int APS5_VABI sceAppContentInitialize(const AppContentInitParam* init_param, AppContentBootParam* boot_param) { - (void)init_param; - if (!boot_param) return SCE_APP_CONTENT_ERROR_PARAMETER; +int APS5_VABI sceAppContentInitialize(const AppContentInitParam* initParam, AppContentBootParam* bootParam) { + std::lock_guard lock(initializationMutex); + const auto sdkVersion = compiledSdkVersion(); + if (sdkVersion >= 0x01500000) { + if (initialized.load()) return SCE_APP_CONTENT_ERROR_BUSY; + if (initParam == nullptr || bootParam == nullptr) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (!std::all_of(std::begin(initParam->reserved), std::end(initParam->reserved), [](char value) { return value == 0; }) || !std::all_of(std::begin(bootParam->reserved1), std::end(bootParam->reserved1), [](char value) { return value == 0; }) || !std::all_of(std::begin(bootParam->reserved2), std::end(bootParam->reserved2), [](char value) { return value == 0; })) return SCE_APP_CONTENT_ERROR_PARAMETER; + } + if (bootParam == nullptr) return SCE_APP_CONTENT_ERROR_PARAMETER; Temporary(); - std::memset(boot_param, 0, sizeof(*boot_param)); + std::memset(bootParam, 0, sizeof(*bootParam)); + initialized = true; return 0; } int APS5_VABI sceAppContentTemporaryDataFormat(const AppContentMountPoint* mountPoint) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; if (!mountPoint) return SCE_APP_CONTENT_ERROR_PARAMETER; auto& state = Temporary(); std::lock_guard lock(state.mutex); @@ -140,6 +170,7 @@ int APS5_VABI sceAppContentTemporaryDataFormat(const AppContentMountPoint* mount } int APS5_VABI sceAppContentTemporaryDataGetAvailableSpaceKb(const AppContentMountPoint* mountPoint, size_t* availableSpaceKb) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; if (!mountPoint || !availableSpaceKb) return SCE_APP_CONTENT_ERROR_PARAMETER; auto& state = Temporary(); std::lock_guard lock(state.mutex); @@ -149,6 +180,7 @@ int APS5_VABI sceAppContentTemporaryDataGetAvailableSpaceKb(const AppContentMoun } int APS5_VABI sceAppContentTemporaryDataMount2(uint32_t option, AppContentMountPoint* mountPoint) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; if (!mountPoint || option > TEMPORARY_DATA_OPTION_FORMAT) return SCE_APP_CONTENT_ERROR_PARAMETER; auto& state = Temporary(); std::lock_guard lock(state.mutex); @@ -163,6 +195,7 @@ int APS5_VABI sceAppContentTemporaryDataMount2(uint32_t option, AppContentMountP } int APS5_VABI sceAppContentTemporaryDataUnmount(const AppContentMountPoint* mountPoint) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; if (!mountPoint) return SCE_APP_CONTENT_ERROR_PARAMETER; auto& state = Temporary(); std::lock_guard lock(state.mutex); @@ -172,22 +205,27 @@ int APS5_VABI sceAppContentTemporaryDataUnmount(const AppContentMountPoint* moun return 0; } - int APS5_VABI sceAppContentAddcontEnqueueDownload(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; (void)service_label; if (!entitlement_label) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (!validEntitlementLabel(*entitlement_label, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER; return SCE_APP_CONTENT_ERROR_NOT_FOUND; } int APS5_VABI sceAppContentAddcontDelete(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; (void)service_label; if (!entitlement_label) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (!validEntitlementLabel(*entitlement_label, 0x03500000)) return SCE_APP_CONTENT_ERROR_PARAMETER; return SCE_APP_CONTENT_ERROR_NOT_FOUND; } int APS5_VABI sceAppContentGetAddcontDownloadProgress(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label, void* progress) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; (void)service_label; if (!entitlement_label || !progress) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (!validEntitlementLabel(*entitlement_label, 0x03500000)) return SCE_APP_CONTENT_ERROR_PARAMETER; return SCE_APP_CONTENT_ERROR_NOT_FOUND; } int APS5_VABI sceAppContentAddcontEnqueueDownloadByEntitlemetId(void) { @@ -250,9 +288,12 @@ int APS5_VABI sceAppContentGetDownloadedStoreCountry(void) { return 0; } -int APS5_VABI sceAppContentGetEntitlementKey(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAppContentGetEntitlementKey(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentEntitlementKey* key) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; + (void)serviceLabel; + if (!entitlementLabel || !key) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (!validEntitlementLabel(*entitlementLabel, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER; + return SCE_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT; } int APS5_VABI sceAppContentGetPftFlag(void) { @@ -270,9 +311,8 @@ int APS5_VABI sceAppContentRequestPatchInstall(void) { return 0; } -int APS5_VABI sceAppContentTemporaryDataMount(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAppContentTemporaryDataMount(AppContentMountPoint* mountPoint) { + return sceAppContentTemporaryDataMount2(TEMPORARY_DATA_OPTION_FORMAT, mountPoint); } } diff --git a/core/libs/prx/libSceAppContent/include/Api.hpp b/core/libs/prx/libSceAppContent/include/Api.hpp new file mode 100644 index 0000000000..cd0a799e37 --- /dev/null +++ b/core/libs/prx/libSceAppContent/include/Api.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "SceTypes.hpp" + +struct AppContentAddcontInfo { + NpUnifiedEntitlementLabel entitlement_label; + uint32_t status; +}; + +struct AppContentEntitlementKey { + char data[16]; +}; + +extern "C" { + +int APS5_VABI sceAppContentAddcontMount(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentMountPoint* mountPoint); +int APS5_VABI sceAppContentAddcontUnmount(const AppContentMountPoint* mountPoint); +int APS5_VABI sceAppContentGetAddcontInfo(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentAddcontInfo* info); +int APS5_VABI sceAppContentGetAddcontInfoList(uint32_t serviceLabel, AppContentAddcontInfo* list, uint32_t listNum, uint32_t* hitNum); +int APS5_VABI sceAppContentAppParamGetInt(uint32_t paramId, int32_t* value); +int APS5_VABI sceAppContentDownloadDataFormat(const AppContentMountPoint* mountPoint); +int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMountPoint* mountPoint, size_t* availableSpaceKb); +int APS5_VABI sceAppContentInitialize(const AppContentInitParam* initParam, AppContentBootParam* bootParam); +int APS5_VABI sceAppContentTemporaryDataFormat(const AppContentMountPoint* mountPoint); +int APS5_VABI sceAppContentTemporaryDataGetAvailableSpaceKb(const AppContentMountPoint* mountPoint, size_t* availableSpaceKb); +int APS5_VABI sceAppContentTemporaryDataMount2(uint32_t option, AppContentMountPoint* mountPoint); +int APS5_VABI sceAppContentTemporaryDataUnmount(const AppContentMountPoint* mountPoint); +int APS5_VABI sceAppContentAddcontEnqueueDownload(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel); +int APS5_VABI sceAppContentAddcontDelete(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel); +int APS5_VABI sceAppContentGetAddcontDownloadProgress(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* progress); +int APS5_VABI sceAppContentAddcontEnqueueDownloadByEntitlemetId(void); +int APS5_VABI sceAppContentAddcontEnqueueDownloadSp(void); +int APS5_VABI sceAppContentAddcontMountByEntitlemetId(void); +int APS5_VABI sceAppContentAddcontShrink(void); +int APS5_VABI sceAppContentAppParamGetString(void); +int APS5_VABI sceAppContentDownload0Expand(void); +int APS5_VABI sceAppContentDownload0Shrink(void); +int APS5_VABI sceAppContentDownload1Expand(void); +int APS5_VABI sceAppContentDownload1Shrink(void); +int APS5_VABI sceAppContentGetAddcontInfoByEntitlementId(void); +int APS5_VABI sceAppContentGetAddcontInfoListByIroTag(void); +int APS5_VABI sceAppContentGetDownloadedStoreCountry(void); +int APS5_VABI sceAppContentGetEntitlementKey(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentEntitlementKey* key); +int APS5_VABI sceAppContentGetPftFlag(void); +int APS5_VABI sceAppContentGetRegion(void); +int APS5_VABI sceAppContentRequestPatchInstall(void); +int APS5_VABI sceAppContentTemporaryDataMount(AppContentMountPoint* mountPoint); + +} diff --git a/core/libs/prx/libSceAppContent/libSceAppContent.hpp b/core/libs/prx/libSceAppContent/libSceAppContent.hpp new file mode 100644 index 0000000000..d92b11cf65 --- /dev/null +++ b/core/libs/prx/libSceAppContent/libSceAppContent.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAppContent/include/Api.hpp" diff --git a/core/libs/prx/libSceAppDbShellCoreClient/libSceAppDbShellCoreClient.hpp b/core/libs/prx/libSceAppDbShellCoreClient/libSceAppDbShellCoreClient.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAppDbShellCoreClient/libSceAppDbShellCoreClient.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAppDbShellCoreClientForSystemService/libSceAppDbShellCoreClientForSystemService.hpp b/core/libs/prx/libSceAppDbShellCoreClientForSystemService/libSceAppDbShellCoreClientForSystemService.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAppDbShellCoreClientForSystemService/libSceAppDbShellCoreClientForSystemService.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAppInstUtil/Export.cpp b/core/libs/prx/libSceAppInstUtil/Export.cpp index 845fd759b0..51721067a8 100644 --- a/core/libs/prx/libSceAppInstUtil/Export.cpp +++ b/core/libs/prx/libSceAppInstUtil/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAppInstUtil/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -25,7 +26,7 @@ int APS5_VABI sceAppInstUtilAppIsInInstalling(int32_t* installing) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppIsInUpdating() { +int APS5_VABI sceAppInstUtilAppIsInUpdating(const char* titleId, bool* updating) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -33,11 +34,11 @@ int APS5_VABI sceAppInstUtilAppExists(const char* titleId, int32_t* exists) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppGetSize() { +int APS5_VABI sceAppInstUtilAppGetSize(const char* titleId, std::uint64_t* size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppInstallPkg() { +int APS5_VABI sceAppInstUtilAppInstallPkg(const char* packagePath, std::uint64_t serviceArgument) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -53,11 +54,11 @@ int APS5_VABI sceAppInstUtilAppUnInstall(const char* titleId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppUnInstallAddcont() { +int APS5_VABI sceAppInstUtilAppUnInstallAddcont(const char* titleId, const char* contentLabel) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppUnInstallPat() { +int APS5_VABI sceAppInstUtilAppUnInstallPat(const char* titleId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -65,19 +66,509 @@ int APS5_VABI sceAppInstUtilAppUnInstallTheme() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppUnInstallTypes() { +int APS5_VABI sceAppInstUtilAppUnInstallTypes(const char* titleId, std::uint32_t types) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilGetInstallProgressInfo() { +int APS5_VABI sceAppInstUtilGetInstallProgressInfo(const char* titleId, AppInstallProgressInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilGetInstallProgress() { +int APS5_VABI sceAppInstUtilGetInstallProgress(const char* titleId, void* progress) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilGetPrimaryAppSlot() { +int APS5_VABI sceAppInstUtilGetPrimaryAppSlot(const char* titleId, void* slot) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetAppInstallStatusByAppInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppUnInstall2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppUnInstallForRemote(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppUnInstallForRemote2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetAddcontListTotalSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetAddcontInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetAddcontInfoByContentId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetAddcontInstalledStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetInsertedDiscTitleId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppUpdateStartTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppInstallSharePlayLink(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppInstallSharePlayLinkByUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetAddcontNum(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppCancelUnInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppDestroyMetadata(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilCheckAppSystemVer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppConvertAppDownloadToDisc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetMainAppTitleIdForSP(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetSubAppTitleIdForSP(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetMainAppTitleId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetSubAppTitleId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetAppInstallStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppDestroyPkg(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilJson(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetInsertedDiscRoleType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilRequestInstallDiscGame(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilRequestDataDiscCopy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilCancelDataDiscCopy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetDataDiscCopyProgress(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilSetPrimaryAppSlot(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetAppSlotInfoList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetAppEmptySlot(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppInstallGetLinkState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppUnInstallForMultiInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppInstallPs4CloudGame(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppStoreCompilationDiscInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetCompilationDiscInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetStoreCompilationDiscInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetInsertedDiscTitleIdList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppInstallRequestCompilationDiscApp(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetNoRelatedAddcontTitleList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetIrremovableAppList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppRequestMoveApps(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppProhibitDownloadInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppAllowDownloadInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetAppMoveProgressInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppCancelMoveApps(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetMoveErrorAppList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetAllAppSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppCancelGetAllAppSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetInsertedDiscMasterDataId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetAppOtherSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppCancelGetAppOtherSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetInsertedDiscTotal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppSetRemasterInstallType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetInstallProgressInfoForMultiDisc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilMountBdEmu(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilUnmountBdEmu(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetInstallStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilResumeInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilPauseInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilRegisterDownload(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilStartInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilInstallPatch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetPatchInstallStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilResumePatchInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilPausePatchInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilCancelInstallPatch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilCancelInstallAddcont(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilCancelInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilCancelForMultiInstall(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilInstallByPackage(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilIsRestoreState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetRestoreState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetAddons(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetInstallOptions(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetContentSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilCancelGetInformation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetWorkspaceStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilDeleteWorkspace(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetParentalControlLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilCheckLatestPatch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilCancelCheckPatch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppInstallPsNowIcon(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetAddcontInfoList2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetAddcontInfoList3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilClearAddcontInfoCache(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetContentSize2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppInstallContentPush(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetAvailableStorage(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilSwitchInstallLocation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetConfigList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilSwitchConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilRestoreDataSource(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilGetNeededStorageSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilAppGetStorageDestType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppInstUtilInstallAddons(void) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAppInstUtil/include/Api.hpp b/core/libs/prx/libSceAppInstUtil/include/Api.hpp new file mode 100644 index 0000000000..eeb9b40a31 --- /dev/null +++ b/core/libs/prx/libSceAppInstUtil/include/Api.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include "SceTypes.hpp" + +struct AppInstallProgressInfo; + +extern "C" { + +int APS5_VABI sceAppInstUtilInitialize(void); +int APS5_VABI sceAppInstUtilTerminate(void); +int APS5_VABI sceAppInstUtilAppInstallAll(void* reserved); +int APS5_VABI sceAppInstUtilAppInstallTitleDir(const char* titleId, const char* titleDir, void* reserved); +int APS5_VABI sceAppInstUtilAppIsInInstalling(int32_t* installing); +int APS5_VABI sceAppInstUtilAppIsInUpdating(const char* titleId, bool* updating); +int APS5_VABI sceAppInstUtilAppExists(const char* titleId, int32_t* exists); +int APS5_VABI sceAppInstUtilAppGetSize(const char* titleId, std::uint64_t* size); +int APS5_VABI sceAppInstUtilAppInstallPkg(const char* packagePath, std::uint64_t serviceArgument); +int APS5_VABI sceAppInstUtilAppPrepareOverwritePkg(); +int APS5_VABI sceAppInstUtilAppRecoverApp(); +int APS5_VABI sceAppInstUtilAppUnInstall(const char* titleId); +int APS5_VABI sceAppInstUtilAppUnInstallAddcont(const char* titleId, const char* contentLabel); +int APS5_VABI sceAppInstUtilAppUnInstallPat(const char* titleId); +int APS5_VABI sceAppInstUtilAppUnInstallTheme(); +int APS5_VABI sceAppInstUtilAppUnInstallTypes(const char* titleId, std::uint32_t types); +int APS5_VABI sceAppInstUtilGetInstallProgressInfo(const char* titleId, AppInstallProgressInfo* info); +int APS5_VABI sceAppInstUtilGetInstallProgress(const char* titleId, void* progress); +int APS5_VABI sceAppInstUtilGetPrimaryAppSlot(const char* titleId, void* slot); + +int APS5_VABI sceAppInstUtilAppGetAppInstallStatusByAppInfo(void); +int APS5_VABI sceAppInstUtilAppUnInstall2(void); +int APS5_VABI sceAppInstUtilAppUnInstallForRemote(void); +int APS5_VABI sceAppInstUtilAppUnInstallForRemote2(void); +int APS5_VABI sceAppInstUtilAppGetAddcontListTotalSize(void); +int APS5_VABI sceAppInstUtilAppGetAddcontInfo(void); +int APS5_VABI sceAppInstUtilAppGetAddcontInfoByContentId(void); +int APS5_VABI sceAppInstUtilGetAddcontInstalledStatus(void); +int APS5_VABI sceAppInstUtilAppGetInsertedDiscTitleId(void); +int APS5_VABI sceAppInstUtilAppUpdateStartTime(void); +int APS5_VABI sceAppInstUtilAppInstallSharePlayLink(void); +int APS5_VABI sceAppInstUtilAppInstallSharePlayLinkByUser(void); +int APS5_VABI sceAppInstUtilAppGetAddcontNum(void); +int APS5_VABI sceAppInstUtilAppCancelUnInstall(void); +int APS5_VABI sceAppInstUtilAppDestroyMetadata(void); +int APS5_VABI sceAppInstUtilCheckAppSystemVer(void); +int APS5_VABI sceAppInstUtilAppConvertAppDownloadToDisc(void); +int APS5_VABI sceAppInstUtilGetMainAppTitleIdForSP(void); +int APS5_VABI sceAppInstUtilGetSubAppTitleIdForSP(void); +int APS5_VABI sceAppInstUtilGetMainAppTitleId(void); +int APS5_VABI sceAppInstUtilGetSubAppTitleId(void); +int APS5_VABI sceAppInstUtilAppGetAppInstallStatus(void); +int APS5_VABI sceAppInstUtilAppDestroyPkg(void); +int APS5_VABI sceAppInstUtilJson(void); +int APS5_VABI sceAppInstUtilGetInsertedDiscRoleType(void); +int APS5_VABI sceAppInstUtilRequestInstallDiscGame(void); +int APS5_VABI sceAppInstUtilRequestDataDiscCopy(void); +int APS5_VABI sceAppInstUtilCancelDataDiscCopy(void); +int APS5_VABI sceAppInstUtilGetDataDiscCopyProgress(void); +int APS5_VABI sceAppInstUtilSetPrimaryAppSlot(void); +int APS5_VABI sceAppInstUtilGetAppSlotInfoList(void); +int APS5_VABI sceAppInstUtilGetAppEmptySlot(void); +int APS5_VABI sceAppInstUtilAppInstallGetLinkState(void); +int APS5_VABI sceAppInstUtilAppUnInstallForMultiInstall(void); +int APS5_VABI sceAppInstUtilAppInstallPs4CloudGame(void); +int APS5_VABI sceAppInstUtilAppStoreCompilationDiscInfo(void); +int APS5_VABI sceAppInstUtilAppGetCompilationDiscInfo(void); +int APS5_VABI sceAppInstUtilAppGetStoreCompilationDiscInfo(void); +int APS5_VABI sceAppInstUtilAppGetInsertedDiscTitleIdList(void); +int APS5_VABI sceAppInstUtilAppInstallRequestCompilationDiscApp(void); +int APS5_VABI sceAppInstUtilAppGetNoRelatedAddcontTitleList(void); +int APS5_VABI sceAppInstUtilAppGetIrremovableAppList(void); +int APS5_VABI sceAppInstUtilAppRequestMoveApps(void); +int APS5_VABI sceAppInstUtilAppProhibitDownloadInstall(void); +int APS5_VABI sceAppInstUtilAppAllowDownloadInstall(void); +int APS5_VABI sceAppInstUtilGetAppMoveProgressInfo(void); +int APS5_VABI sceAppInstUtilAppCancelMoveApps(void); +int APS5_VABI sceAppInstUtilAppGetMoveErrorAppList(void); +int APS5_VABI sceAppInstUtilAppGetAllAppSize(void); +int APS5_VABI sceAppInstUtilAppCancelGetAllAppSize(void); +int APS5_VABI sceAppInstUtilAppGetInsertedDiscMasterDataId(void); +int APS5_VABI sceAppInstUtilAppGetAppOtherSize(void); +int APS5_VABI sceAppInstUtilAppCancelGetAppOtherSize(void); +int APS5_VABI sceAppInstUtilGetInsertedDiscTotal(void); +int APS5_VABI sceAppInstUtilAppSetRemasterInstallType(void); +int APS5_VABI sceAppInstUtilGetInstallProgressInfoForMultiDisc(void); +int APS5_VABI sceAppInstUtilMountBdEmu(void); +int APS5_VABI sceAppInstUtilUnmountBdEmu(void); +int APS5_VABI sceAppInstUtilGetInstallStatus(void); +int APS5_VABI sceAppInstUtilResumeInstall(void); +int APS5_VABI sceAppInstUtilPauseInstall(void); +int APS5_VABI sceAppInstUtilRegisterDownload(void); +int APS5_VABI sceAppInstUtilInstall(void); +int APS5_VABI sceAppInstUtilStartInstall(void); +int APS5_VABI sceAppInstUtilInstallPatch(void); +int APS5_VABI sceAppInstUtilGetPatchInstallStatus(void); +int APS5_VABI sceAppInstUtilResumePatchInstall(void); +int APS5_VABI sceAppInstUtilPausePatchInstall(void); +int APS5_VABI sceAppInstUtilCancelInstallPatch(void); +int APS5_VABI sceAppInstUtilCancelInstallAddcont(void); +int APS5_VABI sceAppInstUtilCancelInstall(void); +int APS5_VABI sceAppInstUtilCancelForMultiInstall(void); +int APS5_VABI sceAppInstUtilInstallByPackage(void); +int APS5_VABI sceAppInstUtilIsRestoreState(void); +int APS5_VABI sceAppInstUtilGetRestoreState(void); +int APS5_VABI sceAppInstUtilGetAddons(void); +int APS5_VABI sceAppInstUtilGetInstallOptions(void); +int APS5_VABI sceAppInstUtilGetContentSize(void); +int APS5_VABI sceAppInstUtilCancelGetInformation(void); +int APS5_VABI sceAppInstUtilGetWorkspaceStatus(void); +int APS5_VABI sceAppInstUtilDeleteWorkspace(void); +int APS5_VABI sceAppInstUtilGetParentalControlLevel(void); +int APS5_VABI sceAppInstUtilCheckLatestPatch(void); +int APS5_VABI sceAppInstUtilCancelCheckPatch(void); +int APS5_VABI sceAppInstUtilAppInstallPsNowIcon(void); +int APS5_VABI sceAppInstUtilGetAddcontInfoList2(void); +int APS5_VABI sceAppInstUtilGetAddcontInfoList3(void); +int APS5_VABI sceAppInstUtilClearAddcontInfoCache(void); +int APS5_VABI sceAppInstUtilGetContentSize2(void); +int APS5_VABI sceAppInstUtilAppInstallContentPush(void); +int APS5_VABI sceAppInstUtilGetAvailableStorage(void); +int APS5_VABI sceAppInstUtilSwitchInstallLocation(void); +int APS5_VABI sceAppInstUtilGetConfigList(void); +int APS5_VABI sceAppInstUtilSwitchConfig(void); +int APS5_VABI sceAppInstUtilRestoreDataSource(void); +int APS5_VABI sceAppInstUtilGetNeededStorageSize(void); +int APS5_VABI sceAppInstUtilAppGetStorageDestType(void); +int APS5_VABI sceAppInstUtilInstallAddons(void); + +} diff --git a/core/libs/prx/libSceAppInstUtil/libSceAppInstUtil.hpp b/core/libs/prx/libSceAppInstUtil/libSceAppInstUtil.hpp new file mode 100644 index 0000000000..8d20c5a6f2 --- /dev/null +++ b/core/libs/prx/libSceAppInstUtil/libSceAppInstUtil.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAppInstUtil/include/Api.hpp" diff --git a/core/libs/prx/libSceAsyncStorageInternal/libSceAsyncStorageInternal.hpp b/core/libs/prx/libSceAsyncStorageInternal/libSceAsyncStorageInternal.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAsyncStorageInternal/libSceAsyncStorageInternal.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAt9Enc/Export.cpp b/core/libs/prx/libSceAt9Enc/Export.cpp index 77fee077ec..09b53ddbc8 100644 --- a/core/libs/prx/libSceAt9Enc/Export.cpp +++ b/core/libs/prx/libSceAt9Enc/Export.cpp @@ -1,9 +1,9 @@ +#include "prx/libSceAt9Enc/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" - extern "C" { int32_t APS5_VABI sceAt9EncClearContext(void *pAt9EncWorkArea, int32_t *pInternalResult) { diff --git a/core/libs/prx/libSceAt9Enc/include/Api.hpp b/core/libs/prx/libSceAt9Enc/include/Api.hpp new file mode 100644 index 0000000000..e060d77e4b --- /dev/null +++ b/core/libs/prx/libSceAt9Enc/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int32_t APS5_VABI sceAt9EncClearContext(void *pAt9EncWorkArea, int32_t *pInternalResult); +int32_t APS5_VABI sceAt9EncCreateEncoder(void *pAt9EncWorkArea, void *pAt9EncParam, int32_t memSize, int32_t *pInternalResult); +int32_t APS5_VABI sceAt9EncEncode(void *pAt9EncWorkArea, const void *pInputBuf, void *pOutputBuf, uint32_t inputBufSize, uint32_t outputBufSize, uint32_t *pOutputDataByte, uint32_t *pUsedBufSize, int32_t *pInternalResult); +int32_t APS5_VABI sceAt9EncFlush(void *pAt9EncWorkArea, void *pOutputBuf, uint32_t outputBufSize, uint32_t *pOutputDataByte, int32_t *pInternalResult); +int32_t APS5_VABI sceAt9EncQueryMemSize(void *pAt9EncParam, int32_t *pMemSize, int32_t *pInternalResult); + +} diff --git a/core/libs/prx/libSceAt9Enc/libSceAt9Enc.hpp b/core/libs/prx/libSceAt9Enc/libSceAt9Enc.hpp new file mode 100644 index 0000000000..c0960da642 --- /dev/null +++ b/core/libs/prx/libSceAt9Enc/libSceAt9Enc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAt9Enc/include/Api.hpp" diff --git a/core/libs/prx/libSceAudio/libSceAudio.hpp b/core/libs/prx/libSceAudio/libSceAudio.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudio/libSceAudio.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudio3d/CMakeLists.txt b/core/libs/prx/libSceAudio3d/CMakeLists.txt index 791d2fac7e..2d4e024734 100644 --- a/core/libs/prx/libSceAudio3d/CMakeLists.txt +++ b/core/libs/prx/libSceAudio3d/CMakeLists.txt @@ -22,6 +22,15 @@ if(BUILD_TESTING) set_tests_properties(guest_audio3d_port PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_audio3d_speaker_array_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudio3dSpeakerArray.cpp) + target_include_directories(guest_audio3d_speaker_array_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_audio3d_speaker_array_tests PRIVATE libSceAudio3d libSceAudioOut libc) + configure_windows_unwind(guest_audio3d_speaker_array_tests) + add_test(NAME guest_audio3d_speaker_array COMMAND guest_audio3d_speaker_array_tests) + if(WIN32) + set_tests_properties(guest_audio3d_speaker_array PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_audio3d_mixer_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudio3dMixer.cpp) target_include_directories(guest_audio3d_mixer_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/core/libs/prx/libSceAudio3d/Export.cpp b/core/libs/prx/libSceAudio3d/Export.cpp index 28854734cc..0eddd25c80 100644 --- a/core/libs/prx/libSceAudio3d/Export.cpp +++ b/core/libs/prx/libSceAudio3d/Export.cpp @@ -1,3 +1,6 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" +#include "prx/libSceAudioOut/include/AudioOut.hpp" +#include "prx/libSceAudio3d/include/Audio3d.hpp" #include #include #include @@ -14,12 +17,6 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -extern "C" { -int APS5_VABI sceAudioOutOpen(int user_id, int type, int index, std::uint32_t len, std::uint32_t freq, std::uint32_t param); -int APS5_VABI sceAudioOutOutput(int handle, const void* ptr); -int APS5_VABI sceAudioOutClose(int handle); -} - namespace { constexpr int AUDIO3D_ERROR_INVALID_PORT = static_cast(0x80EA0002); @@ -199,22 +196,17 @@ int APS5_VABI sceAudio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_leve return 0; } -int APS5_VABI sceAudio3dPortGetAttributesSupported(uint32_t port_id, uint32_t* attributes, uint32_t* num_attributes) { +int APS5_VABI sceAudio3dPortGetAttributesSupported(uint32_t portId, uint32_t* attributes, uint32_t* numAttributes) { std::lock_guard lock(g_mutex); - if (num_attributes == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER; - if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT; + if (!PortIsOpen(portId)) return AUDIO3D_ERROR_INVALID_PORT; + if (numAttributes == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER; constexpr std::uint32_t supported[]{ AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL, AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS, AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE, }; - if (attributes == nullptr) { - *num_attributes = std::size(supported); - return 0; - } - const std::uint32_t count = std::min(*num_attributes, std::size(supported)); - std::copy_n(std::begin(supported), count, attributes); - *num_attributes = count; + *numAttributes = std::size(supported); + if (attributes != nullptr) std::copy(std::begin(supported), std::end(supported), attributes); return 0; } @@ -477,29 +469,29 @@ int APS5_VABI sceAudio3dReportUnregisterHandler(void) { return 0; } -int APS5_VABI sceAudio3dGetSpeakerArrayMemorySize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +static_assert(sizeof(Audio3dSpeakerArrayParameters) == 0x20); + +size_t APS5_VABI sceAudio3dGetSpeakerArrayMemorySize(uint32_t num_speakers, uint8_t is_3d) { + return sceAudioOut2GetSpeakerArrayMemorySize(num_speakers, is_3d, 0); } -int APS5_VABI sceAudio3dCreateSpeakerArray(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudio3dCreateSpeakerArray(AudioOut2SpeakerArrayHandle* handle, const Audio3dSpeakerArrayParameters* parameters) { + if (!handle || !parameters) return AUDIO3D_ERROR_INVALID_PARAMETER; + unsigned char extended[0x28] = {}; + std::memcpy(extended, parameters, sizeof(*parameters)); + return sceAudioOut2SpeakerArrayCreate(handle, extended, nullptr); } -int APS5_VABI sceAudio3dDeleteSpeakerArray(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudio3dDeleteSpeakerArray(AudioOut2SpeakerArrayHandle handle) { + return sceAudioOut2SpeakerArrayDestroy(handle); } -int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t num_coefficients) { + return sceAudioOut2GetSpeakerArrayCoefficients(handle, pos, spread, coefficients, num_coefficients, 0, 0.1f); } -int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients2(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t num_coefficients, uint8_t height_aware, float downmix_spread_radius) { + return sceAudioOut2GetSpeakerArrayCoefficients(handle, pos, spread, coefficients, num_coefficients, height_aware, downmix_spread_radius); } } diff --git a/core/libs/prx/libSceAudio3d/include/Audio3d.hpp b/core/libs/prx/libSceAudio3d/include/Audio3d.hpp new file mode 100644 index 0000000000..54c1e127b3 --- /dev/null +++ b/core/libs/prx/libSceAudio3d/include/Audio3d.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "SceTypes.hpp" + +struct Audio3dSpeakerArrayParameters { + const AudioOut2Position* positions; + std::uint32_t numSpeakers; + std::uint8_t is3d; + void* memory; + std::size_t memorySize; +}; + +extern "C" { + +void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* p); +int APS5_VABI sceAudio3dInitialize(int64_t reserved); +int APS5_VABI sceAudio3dTerminate(); +int APS5_VABI sceAudio3dPortAdvance(uint32_t portId); +int APS5_VABI sceAudio3dPortClose(uint32_t portId); +int APS5_VABI sceAudio3dPortGetQueueLevel(uint32_t portId, uint32_t* queueLevel, uint32_t* queueAvailable); +int APS5_VABI sceAudio3dPortGetAttributesSupported(uint32_t portId, uint32_t* attributes, uint32_t* numAttributes); +int APS5_VABI sceAudio3dPortOpen(int userId, const Audio3dOpenParameters* parameters, uint32_t* id); +int APS5_VABI sceAudio3dPortPush(uint32_t portId, uint32_t blocking); +int APS5_VABI sceAudio3dPortSetAttribute(uint32_t portId, uint32_t attributeId, const void* attribute, size_t attributeSize); +int APS5_VABI sceAudio3dObjectReserve(uint32_t portId, uint32_t* objectId); +int APS5_VABI sceAudio3dObjectUnreserve(uint32_t portId, uint32_t objectId); +int APS5_VABI sceAudio3dObjectSetAttributes(uint32_t portId, uint32_t objectId, uint64_t numAttributes, const Audio3dAttribute* attributeArray); +int APS5_VABI sceAudio3dPortFlush(uint32_t portId); +int APS5_VABI sceAudio3dBedWrite2(uint32_t portId, uint32_t numChannels, uint32_t format, const void* buffer, uint32_t numSamples, uint32_t outputRoute, bool); +int APS5_VABI sceAudio3dBedWrite(uint32_t portId, uint32_t numChannels, uint32_t format, const void* buffer, uint32_t numSamples); +int APS5_VABI sceAudio3dAudioOutOpen(uint32_t portId, int userId, std::int32_t type, std::int32_t index, uint32_t len, uint32_t freq, uint32_t param); +int APS5_VABI sceAudio3dAudioOutClose(std::int32_t handle); +int APS5_VABI sceAudio3dAudioOutOutput(std::int32_t handle, const void* ptr); +int APS5_VABI sceAudio3dAudioOutOutputs(AudioOutOutputParam* param, uint32_t num); +int APS5_VABI sceAudio3dStrError(void); +int APS5_VABI sceAudio3dPortOpenEx(void); +int APS5_VABI sceAudio3dPortCreate(void); +int APS5_VABI sceAudio3dPortDestroy(void); +int APS5_VABI sceAudio3dPortGetStatus(void); +int APS5_VABI sceAudio3dReportRegisterHandler(void); +int APS5_VABI sceAudio3dReportUnregisterHandler(void); +size_t APS5_VABI sceAudio3dGetSpeakerArrayMemorySize(uint32_t numSpeakers, uint8_t is3d); +int APS5_VABI sceAudio3dCreateSpeakerArray(AudioOut2SpeakerArrayHandle* handle, const Audio3dSpeakerArrayParameters* parameters); +int APS5_VABI sceAudio3dDeleteSpeakerArray(AudioOut2SpeakerArrayHandle handle); +int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t numCoefficients); +int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients2(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t numCoefficients, uint8_t heightAware, float downmixSpreadRadius); + +} diff --git a/core/libs/prx/libSceAudio3d/libSceAudio3d.hpp b/core/libs/prx/libSceAudio3d/libSceAudio3d.hpp new file mode 100644 index 0000000000..dcdc650f61 --- /dev/null +++ b/core/libs/prx/libSceAudio3d/libSceAudio3d.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAudio3d/include/Audio3d.hpp" diff --git a/core/libs/prx/libSceAudioIn/Export.cpp b/core/libs/prx/libSceAudioIn/Export.cpp index 68fa257250..b1d4089ca1 100644 --- a/core/libs/prx/libSceAudioIn/Export.cpp +++ b/core/libs/prx/libSceAudioIn/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioIn/include/AudioIn.hpp" #include #include #include "SceTypes.hpp" @@ -17,12 +18,12 @@ constexpr int AUDIO_IN_ERROR_INVALID_HANDLE = static_cast(0x80260101); constexpr int AUDIO_IN_ERROR_INVALID_SIZE = static_cast(0x80260102); constexpr int AUDIO_IN_ERROR_INVALID_FREQ = static_cast(0x80260103); constexpr int AUDIO_IN_ERROR_INVALID_TYPE = static_cast(0x80260104); +constexpr int AUDIO_IN_ERROR_INVALID_POINTER = static_cast(0x80260105); constexpr int AUDIO_IN_ERROR_INVALID_PARAM = static_cast(0x80260106); constexpr int AUDIO_IN_ERROR_PORT_FULL = static_cast(0x80260107); constexpr int AUDIO_IN_ERROR_BUSY = static_cast(0x8026010A); constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 1; constexpr std::uint32_t MAX_QUEUED_BLOCKS = 4; -constexpr std::uint32_t MAX_ASYNC_LEN = 384; using Clock = std::chrono::steady_clock; @@ -175,29 +176,29 @@ int32_t APS5_VABI sceAudioInClose(int32_t handle) { return 0; } -int32_t APS5_VABI sceAudioInHqOpen(int32_t user_id, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) { - (void)user_id; - if (const int error = checkArguments(type, index, len, freq); error != 0) return error; - if (len != 128) return AUDIO_IN_ERROR_INVALID_SIZE; +int32_t APS5_VABI sceAudioInHqOpen(int32_t userId, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) { if (freq != 48000) return AUDIO_IN_ERROR_INVALID_FREQ; + if (len != 128) return AUDIO_IN_ERROR_INVALID_SIZE; + if (param != 2) return AUDIO_IN_ERROR_INVALID_PARAM; + if (const int error = checkArguments(type, index, len, freq); error != 0) return error; return openPort(len, freq, param); } -int32_t APS5_VABI sceAudioInAsyncOpen(int32_t user_id, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) { - (void)user_id; - if (const int error = checkArguments(type, index, len, freq); error != 0 && error != AUDIO_IN_ERROR_INVALID_SIZE) return error; - if (len != 128 && len != 256 && len != MAX_ASYNC_LEN) return AUDIO_IN_ERROR_INVALID_SIZE; +int32_t APS5_VABI sceAudioInAsyncOpen(int32_t userId, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) { + if (freq != 48000) return AUDIO_IN_ERROR_INVALID_FREQ; + if (len != 128) return AUDIO_IN_ERROR_INVALID_SIZE; + if (const int error = checkArguments(type, index, len, freq); error != 0) return error; return openPort(len, freq, param); } int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard lock(g_mutex); + if (!find(handle)) return AUDIO_IN_ERROR_INVALID_HANDLE; + return 0; } int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain) { - if (!gain) throw std::runtime_error("sceAudioInGetGain: null gain"); + if (!gain) return AUDIO_IN_ERROR_INVALID_POINTER; *gain = 1.0f; std::lock_guard lock(g_mutex); return find(handle) ? 0 : AUDIO_IN_ERROR_INVALID_HANDLE; @@ -258,14 +259,14 @@ int APS5_VABI sceAudioInGetHandleStatusInfo(void) { return 0; } -int APS5_VABI sceAudioInOpenEx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudioInOpenEx(int user_id, uint32_t type, uint32_t index, uint32_t extra, uint32_t len, uint32_t freq, uint32_t param) { + (void)extra; + return sceAudioInOpen(user_id, type, index, len, freq, param); } -int APS5_VABI sceAudioInHqOpenEx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudioInHqOpenEx(int user_id, uint32_t type, uint32_t index, uint32_t extra, uint32_t len, uint32_t freq, uint32_t param) { + (void)extra; + return sceAudioInOpen(user_id, type, index, len, freq, param); } int APS5_VABI sceAudioInDeviceOpenEx(void) { @@ -289,12 +290,10 @@ int APS5_VABI sceAudioInSetSparkSideTone(void) { } int APS5_VABI sceAudioInSetCompressorPreGain(void) { - NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceAudioInSetNoiseGateThreshold(void) { - NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAudioIn/include/AudioIn.hpp b/core/libs/prx/libSceAudioIn/include/AudioIn.hpp new file mode 100644 index 0000000000..f4d273aab6 --- /dev/null +++ b/core/libs/prx/libSceAudioIn/include/AudioIn.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudioInInit(); +int APS5_VABI sceAudioInGetSilentState(int handle); +int APS5_VABI sceAudioInInput(int handle, void* dest); +int APS5_VABI sceAudioInOpen(int userId, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param); +int32_t APS5_VABI sceAudioInClose(int32_t handle); +int32_t APS5_VABI sceAudioInHqOpen(int32_t userId, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param); +int32_t APS5_VABI sceAudioInAsyncOpen(int32_t userId, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param); +int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle); +int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain); +int32_t APS5_VABI sceAudioInGetRerouteCount(int32_t handle); +int APS5_VABI sceAudioInDeviceIdOpen(void); +int APS5_VABI sceAudioInDeviceIdAsyncOpen(void); +int APS5_VABI sceAudioInSetUsbGain(void); +int APS5_VABI sceAudioInIsSharedDevice(void); +int APS5_VABI sceAudioInDeviceIdHqOpen(void); +int APS5_VABI sceAudioInDeviceHqOpen(void); +int APS5_VABI sceAudioInSetConnections(void); +int APS5_VABI sceAudioInSetPortConnections(void); +int APS5_VABI sceAudioInSetPortStatuses(void); +int APS5_VABI sceAudioInGetHandleStatusInfo(void); +int APS5_VABI sceAudioInOpenEx(int userId, uint32_t type, uint32_t index, uint32_t extra, uint32_t len, uint32_t freq, uint32_t param); +int APS5_VABI sceAudioInHqOpenEx(int userId, uint32_t type, uint32_t index, uint32_t extra, uint32_t len, uint32_t freq, uint32_t param); +int APS5_VABI sceAudioInDeviceOpenEx(void); +int APS5_VABI sceAudioInChangeAppModuleState(void); +int APS5_VABI sceAudioInSetSparkParam(void); +int APS5_VABI sceAudioInSetSparkSideTone(void); +int APS5_VABI sceAudioInSetCompressorPreGain(void); +int APS5_VABI sceAudioInSetNoiseGateThreshold(void); +int APS5_VABI sceAudioInSetMicLevel(void); +int APS5_VABI sceAudioInSetDeviceMuteStatus(void); +int APS5_VABI sceAudioInSetDevConnection(void); +int APS5_VABI sceAudioInCountPorts(void); +int APS5_VABI sceAudioInSetUserMute(void); +int APS5_VABI sceAudioInVmicCreate(void); +int APS5_VABI sceAudioInVmicDestroy(void); +int APS5_VABI sceAudioInVmicWrite(void); +int APS5_VABI sceAudioInVmicGetStatus(void); +int APS5_VABI sceAudioInVmicSetMute(void); +int APS5_VABI sceAudioInSetMode(void); +int APS5_VABI sceAudioInSetMode2(void); +int APS5_VABI sceAudioInExtCtrl(void); +int APS5_VABI sceAudioInInputs(void); + +} diff --git a/core/libs/prx/libSceAudioIn/libSceAudioIn.hpp b/core/libs/prx/libSceAudioIn/libSceAudioIn.hpp new file mode 100644 index 0000000000..6da8abb473 --- /dev/null +++ b/core/libs/prx/libSceAudioIn/libSceAudioIn.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAudioIn/include/AudioIn.hpp" diff --git a/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp b/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp index 71b2edeba4..566f101c98 100644 --- a/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp +++ b/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioLatencyEstimation/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAudioLatencyEstimation/include/Api.hpp b/core/libs/prx/libSceAudioLatencyEstimation/include/Api.hpp new file mode 100644 index 0000000000..7d8dffbfc4 --- /dev/null +++ b/core/libs/prx/libSceAudioLatencyEstimation/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudioLatencyEstimationAddData(); +int APS5_VABI sceAudioLatencyEstimationGetDelayTime(); +int APS5_VABI sceAudioLatencyEstimationInitialize(); +int APS5_VABI sceAudioLatencyEstimationRelease(); + +} diff --git a/core/libs/prx/libSceAudioLatencyEstimation/libSceAudioLatencyEstimation.hpp b/core/libs/prx/libSceAudioLatencyEstimation/libSceAudioLatencyEstimation.hpp new file mode 100644 index 0000000000..5df8b57860 --- /dev/null +++ b/core/libs/prx/libSceAudioLatencyEstimation/libSceAudioLatencyEstimation.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAudioLatencyEstimation/include/Api.hpp" diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index af79a20cf1..7c97d07a55 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -47,6 +47,21 @@ if(BUILD_TESTING) 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}) + target_link_libraries(audio_out_buffer_size_tests PRIVATE libc libkernel libScePad) + configure_windows_unwind(audio_out_buffer_size_tests) + foreach(bufferSizeMode IN ITEMS pass_through wrap mix threshold) + set(bufferSizeTest "audio_out_buffer_size_${bufferSizeMode}") + add_test(NAME ${bufferSizeTest} COMMAND audio_out_buffer_size_tests ${bufferSizeMode}) + set_tests_properties(${bufferSizeTest} PROPERTIES TIMEOUT 5) + if(WIN32) + set_tests_properties(${bufferSizeTest} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + add_test_executable(audio_out_vibration_tests tests/AudioOutVibration.cpp) target_include_directories(audio_out_vibration_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(audio_out_vibration_tests PRIVATE libSceAudioOut libScePad libc) @@ -171,6 +186,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(audio_out_info_tests tests/AudioOutInfo.cpp) + target_include_directories(audio_out_info_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out_info_tests PRIVATE libSceAudioOut libc) + configure_windows_unwind(audio_out_info_tests) + add_test(NAME audio_out_info COMMAND audio_out_info_tests) + if(WIN32) + set_tests_properties(audio_out_info PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(audio_out2_port_layouts_tests tests/AudioOut2PortLayouts.cpp) target_include_directories(audio_out2_port_layouts_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(audio_out2_port_layouts_tests PRIVATE libSceAudioOut libc) @@ -220,4 +245,14 @@ if(BUILD_TESTING) set_tests_properties(audio_out2_shutdown audio_out2_waiting_push_shutdown audio_out2_advance_shutdown audio_out_shutdown PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(audio_out_system_state_tests tests/AudioOutSystemState.cpp) + target_include_directories(audio_out_system_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out_system_state_tests PRIVATE libSceAudioOut libc) + configure_windows_unwind(audio_out_system_state_tests) + add_test(NAME audio_out_system_state COMMAND audio_out_system_state_tests) + if(WIN32) + set_tests_properties(audio_out_system_state PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceAudioOut/include/AudioOut.hpp b/core/libs/prx/libSceAudioOut/include/AudioOut.hpp new file mode 100644 index 0000000000..ba5e6b3e93 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/include/AudioOut.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudioOutInit(); +int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len, std::uint32_t freq, std::uint32_t param); +int APS5_VABI sceAudioOutClose(int handle); +int APS5_VABI sceAudioOutOutput(int handle, const void* ptr); +int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num); +int APS5_VABI sceAudioOutSetVolume(int handle, std::uint32_t flag, int* vol); +int APS5_VABI sceAudioOutGetLastOutputTime(int handle, std::uint64_t* outputTime); +int APS5_VABI sceAudioOutGetPortState(int handle, AudioOutPortState* state); +int APS5_VABI sceAudioOutDeviceIdOpen(std::uint64_t deviceId, std::int32_t type, std::int32_t index, std::uint32_t length, std::int32_t frequency, std::uint32_t parameter); +int APS5_VABI sceAudioOutSysClose(int handle); +int APS5_VABI sceAudioOutSysConfigureOutput(int type, std::uint64_t flags, std::uint32_t mode, std::uint32_t target, std::uint64_t option); +int APS5_VABI sceAudioOutSysGetHdmiMonitorInfo(int type, void* info, std::uint32_t size); +int APS5_VABI sceAudioOutSysOpen(int userId, std::uint32_t mode); +int APS5_VABI sceAudioOutSetMixLevelPadSpk(int handle, std::uint32_t mixLevel); +int APS5_VABI sceAudioOutExClose(int handle); +int APS5_VABI sceAudioOutExConfigureOutput(int control, std::uint64_t flags, std::uint32_t mode, std::uint32_t target, std::uint64_t option); +int APS5_VABI sceAudioOutExGetMonitorInfo(int type, int index, void* info, std::uint32_t size); +int APS5_VABI sceAudioOutExOpen(int userId, std::uint32_t mode); +int APS5_VABI sceAudioOutGetSystemState(AudioOutSystemState* state); +int APS5_VABI sceAudioOutOpenEx(void); +int APS5_VABI sceAudioOutSetSystemDebugState(void); +int APS5_VABI sceAudioOutGetHandleStatusInfo(void); +int APS5_VABI sceAudioOutGetSimulatedHandleStatusInfo2(void); +int APS5_VABI sceAudioOutChangeAppModuleState(void); +int APS5_VABI sceAudioOutGetSimulatedHandleStatusInfo(void); +int APS5_VABI sceAudioOutGetSimulatedBusUsableStatusByBusType(void); +int APS5_VABI sceAudioOutAttachToApplicationByPid(void); +int APS5_VABI sceAudioOutDetachFromApplicationByPid(void); +int APS5_VABI sceAudioOutPtOpen(void); +int APS5_VABI sceAudioOutPtClose(void); +int APS5_VABI sceAudioOutPtGetLastOutputTime(void); +int APS5_VABI sceAudioOutExPtOpen(void); +int APS5_VABI sceAudioOutExPtClose(void); +int APS5_VABI sceAudioOutExPtGetLastOutputTime(void); +int APS5_VABI sceAudioOutExGetLastOutputTime(void); +int APS5_VABI sceAudioOutGetInfoOpenNum(void); +int APS5_VABI sceAudioOutGetInfo(void); +int APS5_VABI sceAudioOutMasteringInit(uint32_t flags); +int APS5_VABI sceAudioOutMasteringSetParam(const AudioOut2MasteringParamsHeader* param, uint32_t flags); +int APS5_VABI sceAudioOutMasteringGetState(AudioOut2MasteringStatesHeader* state); +int APS5_VABI sceAudioOutMasteringTerm(void); +int APS5_VABI sceAudioOutSetPadSpkVolume(void); +int APS5_VABI sceAudioOutSetPadJackVolume(void); +int APS5_VABI sceAudioOutSetUsbVolume(void); +int APS5_VABI sceAudioOutSetCaesarVolume(void); +int APS5_VABI sceAudioOutSetHeadphoneOutMode(void); +int APS5_VABI sceAudioOutGetFocusEnablePid(void); +int APS5_VABI sceAudioOutSetMainOutput(void); +int APS5_VABI sceAudioOutSetVolumeDown(void); +int APS5_VABI sceAudioOutSetChatBalance(void); +int APS5_VABI sceAudioOutSetRecMode(void); +int APS5_VABI sceAudioOutSetAllMute(void); +int APS5_VABI sceAudioOutSetSparkEQ(void); +int APS5_VABI sceAudioOutArbitrationInitialize(void); +int APS5_VABI sceAudioOutEnable3DAudioByArbitration(void); +int APS5_VABI sceAudioOutAssignHRTF(void); +int APS5_VABI sceAudioOutEnable3DAudioForUser(void); +int APS5_VABI sceAudioOutEnableMonoHpOutput(void); +int APS5_VABI sceAudioOutEnableTVVirtual(void); +int APS5_VABI sceAudioOutEnableTVCorrection(void); +int APS5_VABI sceAudioOutEnableVrControllerLowPowerMode(void); +int APS5_VABI sceAudioOutEnableCinematicVrRotation(void); +int APS5_VABI sceAudioOutCustomHrtfIsExist(void); +int APS5_VABI sceAudioOutExConfigureOutputMode(void); +int APS5_VABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode(void); +int APS5_VABI sceAudioOutExGetSystemInfo(void); +int APS5_VABI sceAudioOutExGetOutputInfo(void); +int APS5_VABI sceAudioOutSysConfigureOutputMode(void); +int APS5_VABI sceAudioOutSysGetMonitorInfo(void); +int APS5_VABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode(void); +int APS5_VABI sceAudioOutSysGetSystemInfo(void); +int APS5_VABI sceAudioOutSysGetOutputInfo(void); + +} diff --git a/core/libs/prx/libSceAudioOut/include/AudioOut2.hpp b/core/libs/prx/libSceAudioOut/include/AudioOut2.hpp new file mode 100644 index 0000000000..d7212024f9 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/include/AudioOut2.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudioOut2ContextAdvance(AudioOut2ContextHandle ctx); +int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam* params, void* buffer, size_t bufferSize, AudioOut2ContextHandle* ctx); +int APS5_VABI sceAudioOut2ContextDestroy(AudioOut2ContextHandle ctx); +int APS5_VABI sceAudioOut2ContextGetQueueLevel(AudioOut2ContextHandle ctx, uint32_t* queueLevel, uint32_t* availableQueue); +int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t blocking); +int APS5_VABI sceAudioOut2ContextQueryMemory(const AudioOut2ContextParam* params, size_t* memorySize); +int APS5_VABI sceAudioOut2ContextResetParam(AudioOut2ContextParam* params); +int APS5_VABI sceAudioOut2ContextSetAttributes(AudioOut2ContextHandle ctx, const AudioOut2Attribute* attributes, uint32_t num); +int APS5_VABI sceAudioOut2LoInit(void); +int APS5_VABI sceAudioOut2LoTerminate(void); +int APS5_VABI sceAudioOut2LoContextQueryMemory(const AudioOut2ContextParam* params, size_t* memorySize); +int APS5_VABI sceAudioOut2LoContextCreate(const AudioOut2ContextParam* params, void* buffer, size_t bufferSize, AudioOut2ContextHandle* ctx); +int APS5_VABI sceAudioOut2LoContextDestroy(AudioOut2ContextHandle ctx); +int APS5_VABI sceAudioOut2LoContextSetAttributes(AudioOut2ContextHandle ctx, const AudioOut2Attribute* attributes, uint32_t num); +int APS5_VABI sceAudioOut2LoContextAdvance(AudioOut2ContextHandle ctx); +int APS5_VABI sceAudioOut2LoContextPush(AudioOut2ContextHandle ctx, uint32_t blocking); +int APS5_VABI sceAudioOut2LoContextGetQueueLevel(AudioOut2ContextHandle ctx, uint32_t* queueLevel, uint32_t* availableQueue); +int APS5_VABI sceAudioOut2LoPortCreate(AudioOut2ContextHandle ctx, const AudioOut2PortParam* params, AudioOut2PortHandle* port); +int APS5_VABI sceAudioOut2LoPortDestroy(AudioOut2PortHandle port); +int APS5_VABI sceAudioOut2LoPortSetAttributes(AudioOut2PortHandle port, const AudioOut2Attribute* attributes, uint32_t num); +int APS5_VABI sceAudioOut2LoPortGetState(AudioOut2PortHandle port, AudioOut2PortState* state); +int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags); +int APS5_VABI sceAudioOut2MasteringInitEx(uint32_t flags); +int APS5_VABI sceAudioOut2MasteringTerm(void); +int APS5_VABI sceAudioOut2MasteringGetState(AudioOut2MasteringStatesHeader* state, uint32_t output, AudioOut2UserHandle user); +int APS5_VABI sceAudioOut2MasteringSetParam(const AudioOut2MasteringParamsHeader* param, uint32_t output, uint32_t flags); +int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut2PortParam* params, AudioOut2PortHandle* port); +int APS5_VABI sceAudioOut2PortDestroy(AudioOut2PortHandle port); +int APS5_VABI sceAudioOut2PortGetState(AudioOut2PortHandle port, AudioOut2PortState* state); +int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle port, const AudioOut2Attribute* attributes, uint32_t num); +int APS5_VABI sceAudioOut2SpeakerArrayCreate(AudioOut2SpeakerArrayHandle* handle, const void* vbapParams, const void* ambiParams); +int APS5_VABI sceAudioOut2SpeakerArrayDestroy(AudioOut2SpeakerArrayHandle handle); +int APS5_VABI sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(AudioOut2SpeakerArrayHandle handle, uint32_t ambisonicsChannel, float* coefficients, uint32_t numCoefficients); +int APS5_VABI sceAudioOut2GetSpeakerArrayCoefficients(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t numCoefficients, uint8_t heightAware, float downmixSpreadRadius); +size_t APS5_VABI sceAudioOut2GetSpeakerArrayMemorySize(uint32_t numSpeakers, uint8_t is3d, uint8_t isAmbisonics); +int APS5_VABI sceAudioOut2GetSpeakerInfo(AudioOut2SpeakerInfo* info, uint32_t flags); +int APS5_VABI sceAudioOut2Initialize(void); +int APS5_VABI sceAudioOut2InitializeEx(std::uint32_t param); +int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t output); +int APS5_VABI sceAudioOut2GetSystemState(AudioOut2SystemState* state); +int APS5_VABI sceAudioOut2SetSystemDebugState(const AudioOut2SystemDebugStateParam* param); +int APS5_VABI sceAudioOut2EnableChat(void); +int APS5_VABI sceAudioOut2DisableChat(void); +int APS5_VABI sceAudioOut2UserCreate(uint32_t userId, AudioOut2UserHandle* handle); +int APS5_VABI sceAudioOut2UserDestroy(AudioOut2UserHandle handle); +int APS5_VABI sceAudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle, uint32_t* contextAttributes, uint32_t* portAttributes); +int APS5_VABI sceAudioOut2UserCreateEx(void); +int APS5_VABI sceAudioOut2ArbitrationInitialize(void); +int APS5_VABI sceAudioOut2MbusInit(void); +int APS5_VABI sceAudioOut2MbusSetPortConnections(void); +int APS5_VABI sceAudioOut2MbusSetConnections(void); +int APS5_VABI sceAudioOut2MbusSetPortStatuses(void); +int APS5_VABI sceAudioOut2MbusSetHeadphoneOutMode(void); +int APS5_VABI sceAudioOut2MbusUserLoginFor3DAudio(void); +int APS5_VABI sceAudioOut2MbusSetDeviceConnections(void); +int APS5_VABI sceAudioOut2MbusSetMorpheusParam(void); +int APS5_VABI sceAudioOut2MbusSetCaesarParam(void); +int APS5_VABI sceAudioOut2MbusSetSparkParam(void); +int APS5_VABI sceAudioOut2AllAudioMainToHeadphoneForDiag(void); +int APS5_VABI sceAudioOut2LoPortGetStateEx(void); +int APS5_VABI sceAudioOut2ReportRegisterHandler(void); +int APS5_VABI sceAudioOut2ReportUnregisterHandler(void); +int APS5_VABI sceAudioOut2GetTvCorrectionInfo(void); +int APS5_VABI sceAudioOut2GetIpcHandle(void); +int APS5_VABI sceAudioOut2GetSystemStateEx(void); +int APS5_VABI sceAudioOut2SetDebugHeadphoneMode(void); +int APS5_VABI sceAudioOut2DebugStateCtrl(void); +int APS5_VABI sceAudioOut2ContextBedWrite(void); +int APS5_VABI sceAudioOut2PortCreateEx(void); +int APS5_VABI sceAudioOut2Terminate(void); +int APS5_VABI sceAudioOut2PortRegister(void); +int APS5_VABI sceAudioOut2PortUnregister(void); +int APS5_VABI sceAudioOut2GetAmbisonicsEncodeCoefficients(void); +int APS5_VABI sceAudioOut2GetHrtfIdForCronos(void); + +} diff --git a/core/libs/prx/libSceAudioOut/libSceAudioOut.hpp b/core/libs/prx/libSceAudioOut/libSceAudioOut.hpp new file mode 100644 index 0000000000..d092ed95af --- /dev/null +++ b/core/libs/prx/libSceAudioOut/libSceAudioOut.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "prx/libSceAudioOut/include/AudioOut.hpp" +#include "prx/libSceAudioOut/include/AudioOut2.hpp" diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index 0eda307e26..44cb922956 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include #include #include @@ -40,7 +41,7 @@ static constexpr std::uint64_t DRAIN_TIMEOUT_US = 200000; static constexpr std::uint64_t DRAIN_SLEEP_US = 1000; static constexpr int MONITOR_TYPE_HDMI = 1; static constexpr std::uint32_t HDMI_MONITOR_INFO_SIZE = 0x180; -static constexpr std::uint32_t EX_MONITOR_INFO_MIN_SIZE = 0xA0; +static constexpr std::uint32_t EX_MONITOR_INFO_SIZE = 0x400; static constexpr std::uint32_t EX_OPEN_MODE_MAX = 10; static constexpr std::uint32_t EX_OPEN_MODES = 0x61F; static constexpr std::uint32_t EX_CONFIGURE_MODE_DEFAULT = 0xFF; @@ -401,12 +402,23 @@ static int allocatePort(int type, std::uint32_t len, std::uint32_t freq, Format return -2144993275; } +static std::atomic g_audioOut2Initialized{false}; + +bool AudioOut2IsInitialized_nid_no_patch() { + return g_audioOut2Initialized.load(); +} + extern "C" { -int APS5_VABI sceAudioOutInit() { +int APS5_VABI sceAudioOut2Initialize(void) { + g_audioOut2Initialized.store(true); return 0; } +int APS5_VABI sceAudioOutInit() { + return sceAudioOut2Initialize(); +} + int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len, std::uint32_t freq, std::uint32_t param) { (void)userId; @@ -599,7 +611,7 @@ int APS5_VABI sceAudioOutGetPortState(int handle, AudioOutPortState* state) { return 0; } -int APS5_VABI sceAudioOutDeviceIdOpen() { +int APS5_VABI sceAudioOutDeviceIdOpen(std::uint64_t deviceId, std::int32_t type, std::int32_t index, std::uint32_t length, std::int32_t frequency, std::uint32_t parameter) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -635,15 +647,10 @@ int APS5_VABI sceAudioOutSysConfigureOutput(int type, std::uint64_t flags, std:: } int APS5_VABI sceAudioOutSysGetHdmiMonitorInfo(int type, void* info, std::uint32_t size) { - if (type != MONITOR_TYPE_HDMI) { - throw std::runtime_error("sceAudioOutSysGetHdmiMonitorInfo: monitor type " + std::to_string(type) + " not supported"); - } - if (size != HDMI_MONITOR_INFO_SIZE) { - throw std::runtime_error("sceAudioOutSysGetHdmiMonitorInfo: size " + std::to_string(size) + " not supported"); - } - if (info == nullptr) { - return -2144993276; - } + if (info == nullptr) return static_cast(0x80260004); + if (size != HDMI_MONITOR_INFO_SIZE) return static_cast(0x80260006); + if (type != MONITOR_TYPE_HDMI && type != 0x10) return static_cast(0x8026000C); + if (type == 0x10) throw std::runtime_error("sceAudioOutSysGetHdmiMonitorInfo: monitor type 16 not supported"); std::memset(info, 0, HDMI_MONITOR_INFO_SIZE); return 0; } @@ -656,20 +663,21 @@ int APS5_VABI sceAudioOutSysOpen(int userId, std::uint32_t mode) { return allocatePort(PORT_TYPE_SYS, SYS_PORT_SAMPLES, SYS_PORT_FRAME_RATE, Format::S16_8Ch); } -int APS5_VABI sceAudioOutSetMixLevelPadSpk(int handle, int mixLevel) { +int APS5_VABI sceAudioOutSetMainOutput() { + return 0; +} + +int APS5_VABI sceAudioOutSetMixLevelPadSpk(int handle, std::uint32_t mixLevel) { std::lock_guard lock(g_mutex); Port* port = getPort(handle); if (port == nullptr) { return -2144993277; } - if (port->type != PORT_TYPE_PADSPK) { - return -2144993270; - } if (mixLevel > DEFAULT_VOLUME) { return -2144993260; } - if (mixLevel < 0) { - throw std::runtime_error("sceAudioOutSetMixLevelPadSpk: negative mix level not supported"); + if (port->type != PORT_TYPE_PADSPK) { + return -2144993270; } port->mixLevel = mixLevel; return 0; @@ -706,16 +714,10 @@ int APS5_VABI sceAudioOutExConfigureOutput(int control, std::uint64_t flags, std } int APS5_VABI sceAudioOutExGetMonitorInfo(int type, int index, void* info, std::uint32_t size) { - if (type != 0 || index != 0) { - throw std::runtime_error("sceAudioOutExGetMonitorInfo: monitor " + std::to_string(type) + ", " + std::to_string(index) + " not supported"); - } - if (size < EX_MONITOR_INFO_MIN_SIZE) { - throw std::runtime_error("sceAudioOutExGetMonitorInfo: size " + std::to_string(size) + " not supported"); - } - if (info == nullptr) { - return -2144993276; - } - std::memset(info, 0, size); + if (type != 0) return static_cast(0x80260003); + if (info == nullptr) return static_cast(0x80260004); + if (size != EX_MONITOR_INFO_SIZE) return static_cast(0x80260006); + std::memset(info, 0, EX_MONITOR_INFO_SIZE); return 0; } @@ -728,4 +730,13 @@ int APS5_VABI sceAudioOutExOpen(int userId, std::uint32_t mode) { return allocatePort(PORT_TYPE_EX, highRate ? 1024 : 256, highRate ? 192000 : 48000, Format::S16Stereo); } +int APS5_VABI sceAudioOutGetSystemState(AudioOutSystemState* state) { + if (state == nullptr) { + return -2144993276; + } + *state = {}; + state->loudness = 0.0f; + return 0; +} + } diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp index e11ee633e5..905eaa42f5 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include #include #include diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp index c58bd4c542..42363a9998 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp @@ -25,6 +25,7 @@ bool AudioOut2TraceEnabled(); // Seconds since the first AudioOut2 call, for the trace. double AudioOut2TraceSeconds(); +bool AudioOut2IsInitialized_nid_no_patch(); #define AUDIOOUT2_TRACE(...) \ do { \ diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Low.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Low.cpp index eaba6e102d..e427f40302 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Low.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Low.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include #include #include "SceTypes.hpp" @@ -5,23 +6,14 @@ extern "C" { -int APS5_VABI sceAudioOut2Initialize(void); -int APS5_VABI sceAudioOut2ContextQueryMemory(const AudioOut2ContextParam* params, size_t* memory_size); -int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam* params, void* buffer, size_t buffer_size, AudioOut2ContextHandle* ctx); -int APS5_VABI sceAudioOut2ContextDestroy(AudioOut2ContextHandle ctx); -int APS5_VABI sceAudioOut2ContextSetAttributes(AudioOut2ContextHandle ctx, const AudioOut2Attribute* attributes, uint32_t num); -int APS5_VABI sceAudioOut2ContextAdvance(AudioOut2ContextHandle ctx); -int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t blocking); -int APS5_VABI sceAudioOut2ContextGetQueueLevel(AudioOut2ContextHandle ctx, uint32_t* queue_level, uint32_t* available_queue); -int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut2PortParam* params, AudioOut2PortHandle* port); -int APS5_VABI sceAudioOut2PortDestroy(AudioOut2PortHandle port); -int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle port, const AudioOut2Attribute* attributes, uint32_t num); -int APS5_VABI sceAudioOut2PortGetState(AudioOut2PortHandle port, AudioOut2PortState* state); - int APS5_VABI sceAudioOut2LoInit(void) { return sceAudioOut2Initialize(); } +int APS5_VABI sceAudioOut2LoTerminate(void) { + return 0; +} + int APS5_VABI sceAudioOut2LoContextQueryMemory(const AudioOut2ContextParam* params, size_t* memory_size) { return sceAudioOut2ContextQueryMemory(params, memory_size); } diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp index e48537db95..88e948e434 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp @@ -1,5 +1,8 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" +#include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -7,29 +10,93 @@ namespace { constexpr int SCE_AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM = static_cast(0x80268201); constexpr int SCE_AUDIO_OUT2_ERROR_MASTERING_INVALID_STATES_ID = 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_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; + +std::atomic g_masteringInitialized{false}; + +int InitializeMastering(uint32_t flags, uint32_t accepted) { + if (g_masteringInitialized.load()) return 0; + if ((flags & ~accepted) != 0) return SCE_AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM; + g_masteringInitialized.store(true); + return 0; +} + constexpr std::uint32_t AUDIO_OUT2_MASTERING_STATES_ID_DEFAULT = 1; constexpr std::uint32_t AUDIO_OUT2_MASTERING_STATES_ID_V2 = 2; constexpr std::uint32_t AUDIO_OUT2_MASTERING_STRUCT_ID_COMPRESSOR_DEFAULT = 0x01010001; constexpr std::uint32_t AUDIO_OUT2_MASTERING_STRUCT_ID_COMPRESSOR_V2 = 0x01020001; constexpr std::uint32_t AUDIO_OUT2_MASTERING_STRUCT_ID_LIMITER_DEFAULT = 0x01010003; +constexpr std::uint32_t AUDIO_OUT_MASTERING_PARAMS_ID_DEFAULT = 1; +constexpr std::uint32_t AUDIO_OUT_MASTERING_STATES_ID_DEFAULT = 1; + +constexpr std::size_t AUDIO_OUT_MASTERING_COMPRESSOR_INPUT_RMS_OFFSET = 32; +constexpr std::size_t AUDIO_OUT_MASTERING_COMPRESSOR_GAIN_OFFSET = 128; +constexpr std::size_t AUDIO_OUT_MASTERING_LIMITER_INPUT_PEAK_OFFSET = 240; +constexpr std::size_t AUDIO_OUT_MASTERING_LIMITER_OUTPUT_PEAK_OFFSET = 272; +constexpr std::size_t AUDIO_OUT_MASTERING_LIMITER_GAIN_PEAK_OFFSET = 304; + +static_assert(sizeof(AudioOut2MasteringStates) == 336); +static_assert(offsetof(AudioOut2MasteringStates, compressor_states) + offsetof(AudioOut2MasteringCompressorStates, input_rms) == AUDIO_OUT_MASTERING_COMPRESSOR_INPUT_RMS_OFFSET); +static_assert(offsetof(AudioOut2MasteringStates, compressor_states) + offsetof(AudioOut2MasteringCompressorStates, compression_coeff) == AUDIO_OUT_MASTERING_COMPRESSOR_GAIN_OFFSET); +static_assert(offsetof(AudioOut2MasteringStates, limiter_states) + offsetof(AudioOut2MasteringLimiterStates, input_peak) == AUDIO_OUT_MASTERING_LIMITER_INPUT_PEAK_OFFSET); +static_assert(offsetof(AudioOut2MasteringStates, limiter_states) + offsetof(AudioOut2MasteringLimiterStates, output_peak) == AUDIO_OUT_MASTERING_LIMITER_OUTPUT_PEAK_OFFSET); +static_assert(offsetof(AudioOut2MasteringStates, limiter_states) + offsetof(AudioOut2MasteringLimiterStates, gain_peak) == AUDIO_OUT_MASTERING_LIMITER_GAIN_PEAK_OFFSET); + } extern "C" { -int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) { +int APS5_VABI sceAudioOutMasteringInit(uint32_t flags) { if (flags != 0) NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI sceAudioOutMasteringTerm(void) { + return 0; +} + +int APS5_VABI sceAudioOutMasteringGetState(AudioOut2MasteringStatesHeader* state) { + if (state == nullptr) APS5_INVALID_ARG_EX; + if (state->states_id != AUDIO_OUT_MASTERING_STATES_ID_DEFAULT) NotImplemented_nid_no_patch(__func__); + auto* full = static_cast(static_cast(state)); + std::memset(full->compressor_states.input_rms, 0, sizeof(full->compressor_states.input_rms)); + std::memset(full->compressor_states.compression_coeff, 0, sizeof(full->compressor_states.compression_coeff)); + std::memset(full->limiter_states.input_peak, 0, sizeof(full->limiter_states.input_peak)); + std::memset(full->limiter_states.output_peak, 0, sizeof(full->limiter_states.output_peak)); + std::memset(full->limiter_states.gain_peak, 0, sizeof(full->limiter_states.gain_peak)); + return 0; +} + +int APS5_VABI sceAudioOutMasteringSetParam(const AudioOut2MasteringParamsHeader* param, uint32_t flags) { + if (param == nullptr) APS5_INVALID_ARG_EX; + if (flags != 0) NotImplemented_nid_no_patch(__func__); + if (param->params_id != AUDIO_OUT_MASTERING_PARAMS_ID_DEFAULT) NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) { + return InitializeMastering(flags, 0); +} + +int APS5_VABI sceAudioOut2MasteringInitEx(uint32_t flags) { + return InitializeMastering(flags, AUDIO_OUT2_MASTERING_INIT_EX_MODE); +} + int APS5_VABI sceAudioOut2MasteringTerm(void) { + if (!g_masteringInitialized.exchange(false)) return SCE_AUDIO_OUT2_ERROR_MASTERING_NOT_INITIALIZED; return 0; } int APS5_VABI sceAudioOut2MasteringGetState(AudioOut2MasteringStatesHeader* state, uint32_t output, AudioOut2UserHandle user) { (void)output; (void)user; - if (state == nullptr) return SCE_AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM; + 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) { case AUDIO_OUT2_MASTERING_STATES_ID_DEFAULT: { auto* full = static_cast(static_cast(state)); @@ -58,8 +125,9 @@ int APS5_VABI sceAudioOut2MasteringGetState(AudioOut2MasteringStatesHeader* stat int APS5_VABI sceAudioOut2MasteringSetParam(const AudioOut2MasteringParamsHeader* param, uint32_t output, uint32_t flags) { (void)output; - (void)flags; + 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; return 0; } diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp index 14c9bc2c63..e8a547fefc 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include #include #include diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp index 26170f4622..36b6746149 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include #include #include @@ -7,6 +8,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "AudioOut2Internal.hpp" namespace { @@ -68,7 +70,7 @@ extern "C" { int APS5_VABI sceAudioOut2SpeakerArrayCreate(AudioOut2SpeakerArrayHandle* handle, const void* vbap_params, const void* ambi_params) { const auto* params = static_cast(vbap_params); - if (handle == nullptr || params == nullptr || ambi_params == nullptr || params->positions == nullptr || + if (handle == nullptr || params == nullptr || params->positions == nullptr || params->numSpeakers == 0 || params->numSpeakers > MaxSpeakers || params->memory == nullptr || params->memorySize < sizeof(SpeakerArray) || reinterpret_cast(params->memory) % alignof(SpeakerArray) != 0) { APS5_INVALID_ARG_EX; @@ -120,8 +122,13 @@ size_t APS5_VABI sceAudioOut2GetSpeakerArrayMemorySize(uint32_t num_speakers, ui } int APS5_VABI sceAudioOut2GetSpeakerInfo(AudioOut2SpeakerInfo* info, uint32_t flags) { - (void)flags; - if (!info) return static_cast(0x80260502); + constexpr int ErrorInvalidParam = static_cast(0x80268001); + constexpr int ErrorNotInitialized = static_cast(0x80268006); + constexpr int ErrorInvalidPointer = static_cast(0x8026800C); + constexpr std::uint32_t MaxFlags = 1; + if (!AudioOut2IsInitialized_nid_no_patch()) return ErrorNotInitialized; + if (!info) return ErrorInvalidPointer; + if (flags > MaxFlags) return ErrorInvalidParam; constexpr std::uint8_t SpeakerTypeTelevision = 0; constexpr std::uint32_t FrontLeftAndRight = 0x3; constexpr AudioOut2SpeakerAngle NoSpeaker = {-360, -360}; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp index c647f607ea..6723eb1371 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp @@ -1,4 +1,6 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -11,13 +13,14 @@ static std::atomic g_chatEnabled{false}; extern "C" { -int APS5_VABI sceAudioOut2Initialize(void) { - return 0; +int APS5_VABI sceAudioOut2InitializeEx(std::uint32_t param) { + (void)param; + return sceAudioOut2Initialize(); } int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t output) { - (void)userId; if (output > MAX_SUPPORTED_3D_OUTPUT) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM; + if (userId != 0 && (userId != 0xFF || output == 0)) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM; return 0; } @@ -28,7 +31,15 @@ int APS5_VABI sceAudioOut2GetSystemState(AudioOut2SystemState* state) { } int APS5_VABI sceAudioOut2SetSystemDebugState(const AudioOut2SystemDebugStateParam* param) { - if (param == nullptr) return static_cast(0x80260502); + constexpr int ErrorInvalidPointer = static_cast(0x8026800C); + constexpr std::uint32_t DebugStateIdLoudness = 1; + constexpr std::uint32_t DebugStateIdUnmodelled = 0x20; + constexpr std::size_t LoudnessParamSize = 8; + constexpr std::size_t UnmodelledParamSize = 4; + if (param == nullptr) return ErrorInvalidPointer; + if (param->debug_state_id == DebugStateIdUnmodelled && param->param_size == UnmodelledParamSize) NotImplemented_nid_no_patch(__func__); + if (param->debug_state_id != DebugStateIdLoudness || param->param_size != LoudnessParamSize) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM; + if (param->param == nullptr) NotImplemented_nid_no_patch(__func__); AUDIOOUT2_TRACE("t=%.3f SetSystemDebugState: debug state %u recorded; the host has no system mix to apply it to\n", AudioOut2TraceSeconds(), param->debug_state_id); return 0; } diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp index a8c42717bf..5a8bce8592 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -32,13 +33,13 @@ int APS5_VABI sceAudioOut2UserDestroy(AudioOut2UserHandle handle) { } int APS5_VABI sceAudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle, uint32_t* contextAttributes, uint32_t* portAttributes) { - if (!contextAttributes || !portAttributes) - throw std::runtime_error("sceAudioOut2UserGetSupportedAttributes: null output"); + if (handle == 0 || handle == std::numeric_limits::max()) return static_cast(0x80268010); + if (!contextAttributes && !portAttributes) return static_cast(0x80268001); std::lock_guard lock(g_usersLock); if (!g_users.contains(handle)) throw std::runtime_error("sceAudioOut2UserGetSupportedAttributes: unknown user handle"); - *contextAttributes = 0; - *portAttributes = (1u << 0) | (1u << 1); + if (contextAttributes) *contextAttributes = 0; + if (portAttributes) *portAttributes = (1u << 0) | (1u << 1); return 0; } diff --git a/core/libs/prx/libSceAudioOut/src/Unimplemented.cpp b/core/libs/prx/libSceAudioOut/src/Unimplemented.cpp index 02abcf4fd9..55134a8ef6 100644 --- a/core/libs/prx/libSceAudioOut/src/Unimplemented.cpp +++ b/core/libs/prx/libSceAudioOut/src/Unimplemented.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include #include #include "SceTypes.hpp" @@ -36,8 +38,7 @@ int APS5_VABI sceAudioOutChangeAppModuleState(void) { } int APS5_VABI sceAudioOutGetSimulatedHandleStatusInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x809B00FF); } int APS5_VABI sceAudioOutGetSimulatedBusUsableStatusByBusType(void) { @@ -91,32 +92,10 @@ int APS5_VABI sceAudioOutExGetLastOutputTime(void) { } int APS5_VABI sceAudioOutGetInfoOpenNum(void) { - NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceAudioOutGetInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioOutMasteringInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioOutMasteringSetParam(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioOutMasteringGetState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioOutMasteringTerm(void) { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -150,11 +129,6 @@ int APS5_VABI sceAudioOutGetFocusEnablePid(void) { return 0; } -int APS5_VABI sceAudioOutSetMainOutput(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceAudioOutSetVolumeDown(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -365,11 +339,6 @@ int APS5_VABI sceAudioOut2GetSystemStateEx(void) { return 0; } -int APS5_VABI sceAudioOut2MasteringInitEx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceAudioOut2SetDebugHeadphoneMode(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -380,11 +349,6 @@ int APS5_VABI sceAudioOut2DebugStateCtrl(void) { return 0; } -int APS5_VABI sceAudioOut2LoTerminate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceAudioOut2ContextBedWrite(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -395,11 +359,6 @@ int APS5_VABI sceAudioOut2PortCreateEx(void) { return 0; } -int APS5_VABI sceAudioOut2InitializeEx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceAudioOut2Terminate(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2DeviceState.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2DeviceState.cpp index 404037d497..82e8464648 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2DeviceState.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2DeviceState.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/libSceAudioOut.hpp" #include "SceTypes.hpp" #include @@ -8,23 +9,15 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOut2ContextResetParam(AudioOut2ContextParam*); -int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam*, void*, std::size_t, AudioOut2ContextHandle*); -int APS5_VABI sceAudioOut2ContextDestroy(AudioOut2ContextHandle); -int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle, const AudioOut2PortParam*, AudioOut2PortHandle*); -int APS5_VABI sceAudioOut2PortDestroy(AudioOut2PortHandle); -int APS5_VABI sceAudioOut2PortGetState(AudioOut2PortHandle, AudioOut2PortState*); -int APS5_VABI sceAudioOut2GetSpeakerInfo(AudioOut2SpeakerInfo*, std::uint32_t); -int APS5_VABI sceAudioOut2GetSystemState(AudioOut2SystemState*); -} - static void Require(bool value) { if (!value) std::abort(); } namespace { constexpr int errorInvalidArgument = static_cast(0x80260502); constexpr int errorInvalidHandle = static_cast(0x80260503); +constexpr int errorInvalidParam = static_cast(0x80268001); +constexpr int errorNotInitialized = static_cast(0x80268006); +constexpr int errorInvalidPointer = static_cast(0x8026800C); constexpr std::uint16_t portTypeMain = 0; constexpr std::uint16_t outputMain = 1; constexpr std::int16_t volumeMax = 127; @@ -101,7 +94,38 @@ void SpeakerInfoReportsTheFrontPair() { Require(info.speaker_angle[0].azimuth == 30 && info.speaker_angle[0].elevation == 0); Require(info.speaker_angle[1].azimuth == -30 && info.speaker_angle[1].elevation == 0); for (int index = 2; index < 16; index++) Require(info.speaker_angle[index].azimuth == -360 && info.speaker_angle[index].elevation == -360); - Require(sceAudioOut2GetSpeakerInfo(nullptr, 0) == errorInvalidArgument); + Require(sceAudioOut2GetSpeakerInfo(nullptr, 0) == errorInvalidPointer); +} + +void SpeakerInfoRequiresInitialization() { + AudioOut2SpeakerInfo info; + std::memset(&info, 0xAB, sizeof(info)); + const AudioOut2SpeakerInfo untouched = info; + Require(sceAudioOut2GetSpeakerInfo(&info, 0) == errorNotInitialized); + Require(sceAudioOut2GetSpeakerInfo(nullptr, 0) == errorNotInitialized); + Require(sceAudioOut2GetSpeakerInfo(&info, 2) == errorNotInitialized); + Require(std::memcmp(&info, &untouched, sizeof(info)) == 0); +} + +void SpeakerInfoAfterAudioOutInit() { + AudioOut2SpeakerInfo info; + std::memset(&info, 0xAB, sizeof(info)); + Require(sceAudioOutInit() == 0); + Require(sceAudioOut2GetSpeakerInfo(&info, 0) == 0); +} + +void SpeakerInfoRejectsBadFlags() { + AudioOut2SpeakerInfo info; + std::memset(&info, 0xAB, sizeof(info)); + const AudioOut2SpeakerInfo untouched = info; + Require(sceAudioOut2GetSpeakerInfo(&info, 2) == errorInvalidParam); + Require(sceAudioOut2GetSpeakerInfo(&info, 0x80000000) == errorInvalidParam); + Require(sceAudioOut2GetSpeakerInfo(nullptr, 2) == errorInvalidPointer); + Require(std::memcmp(&info, &untouched, sizeof(info)) == 0); + Require(sceAudioOut2GetSpeakerInfo(&info, 1) == 0); + Require(info.type == 0); + Require(info.available_bits == 0x3); + Require(info.speaker_angle[0].azimuth == 30 && info.speaker_angle[1].azimuth == -30); } void SystemStateIsSilentAndRejectsNull() { @@ -119,6 +143,9 @@ int main() { std::filesystem::remove(path); SetEnvironment("SDL_DISKAUDIOFILE", path.string()); + SpeakerInfoRequiresInitialization(); + SpeakerInfoAfterAudioOutInit(); + AudioOut2ContextParam params{}; Require(sceAudioOut2ContextResetParam(¶ms) == 0); params.num_grains = 256; @@ -129,6 +156,7 @@ int main() { PortStateFollowsTheDataFormat(context); PortStateRejectsBadArguments(context); SpeakerInfoReportsTheFrontPair(); + SpeakerInfoRejectsBadFlags(); SystemStateIsSilentAndRejectsNull(); Require(sceAudioOut2ContextDestroy(context) == 0); diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp index 9a09ec4fba..cc08e950f6 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp @@ -1,25 +1,15 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include #include - -extern "C" { -int APS5_VABI sceAudioOut2Initialize(); -int APS5_VABI sceAudioOut2LoInit(); -int APS5_VABI sceAudioOut2LoTerminate(); -int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t); -int APS5_VABI sceAudioOut2MasteringInit(std::uint32_t); -int APS5_VABI sceAudioOut2MasteringTerm(); -int APS5_VABI sceAudioOut2MasteringSetParam(const void*, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioOut2MasteringGetState(void*, std::uint32_t, std::uint64_t); -int APS5_VABI sceAudioOut2SetSystemDebugState(const void*); -int APS5_VABI sceAudioOut2EnableChat(); -int APS5_VABI sceAudioOut2DisableChat(); -} +#include static void Require(bool value, const char* message) { if (value) return; @@ -37,6 +27,16 @@ static bool ThrowsRuntimeError(TFunction function) { return false; } +template +static bool ThrowsException(TFunction function) { + try { + function(); + } catch (const std::exception&) { + return true; + } + return false; +} + namespace { static_assert(sizeof(AudioOut2MasteringCompressorStates) == 208); @@ -48,67 +48,164 @@ static_assert(sizeof(AudioOut2MasteringStatesV2) == 400); constexpr int systemUser = 0xFF; constexpr int user = 0x10000000; constexpr int errorInvalidParam = static_cast(0x80268001); -constexpr int errorInvalidArgument = static_cast(0x80260502); +constexpr int errorInvalidPointer = static_cast(0x8026800C); constexpr int errorMasteringInvalidApiParam = static_cast(0x80268201); constexpr int errorMasteringInvalidStatesId = static_cast(0x80268204); +constexpr int errorMasteringNotInitialized = static_cast(0x80268203); +constexpr int errorMasteringInvalidFlag = static_cast(0x80268205); void TestSet3DLatency() { - Require(sceAudioOut2Set3DLatency(systemUser, 0) == 0, "output 0 for the system user must be accepted"); + Require(sceAudioOut2Set3DLatency(systemUser, 0) == errorInvalidParam, "output 0 for selector 255 must be refused"); + for (std::uint32_t output = 0; output <= 2; ++output) { + Require(sceAudioOut2Set3DLatency(0, output) == 0, "selector 0 must accept each supported output"); + } Require(sceAudioOut2Set3DLatency(systemUser, 1) == 0, "output 1 for the system user must be accepted"); Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "output 2 for the system user must be accepted"); Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "output 2 must be accepted again"); - Require(sceAudioOut2Set3DLatency(user, 1) == 0, "output 1 for another user must be accepted"); + Require(sceAudioOut2Set3DLatency(user, 1) == errorInvalidParam, "a user id is not a valid latency selector"); Require(sceAudioOut2Set3DLatency(systemUser, 3) == errorInvalidParam, "output 3 must be refused"); Require(sceAudioOut2Set3DLatency(systemUser, 0xFFFFFFFFu) == errorInvalidParam, "a large output must be refused"); } void TestMasteringInit() { + const AudioOut2MasteringParamsHeader params{1u}; + AudioOut2MasteringStates storage{}; + Require(sceAudioOut2MasteringTerm() == errorMasteringNotInitialized, "termination before initialization is refused"); + Require(sceAudioOut2MasteringSetParam(¶ms, 0, 0) == errorMasteringNotInitialized, "parameters before initialization are refused"); + Require(sceAudioOut2MasteringGetState(nullptr, 0, 0) == errorMasteringNotInitialized, "the initialization check comes before the null check"); + Require(sceAudioOut2MasteringGetState(&storage.states_header, 0, 0) == errorMasteringNotInitialized, "states before initialization are refused"); + Require(sceAudioOut2MasteringInit(1) == errorMasteringInvalidApiParam, "flags 1 are refused"); + Require(sceAudioOut2MasteringInit(0x80000000u) == errorMasteringInvalidApiParam, "sceAudioOut2MasteringInit accepts no flag"); Require(sceAudioOut2MasteringInit(0) == 0, "flags 0 must be accepted"); - Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInit(1); }), "flags 1 must throw"); + Require(sceAudioOut2MasteringInit(1) == 0, "an initialized stage returns 0 before the flags are checked"); + Require(sceAudioOut2MasteringInit(0) == 0, "a second initialization must be accepted"); } void TestMasteringTerm() { - Require(sceAudioOut2MasteringInit(0) == 0, "a second initialization must be accepted"); Require(sceAudioOut2MasteringTerm() == 0, "termination must be accepted"); - Require(sceAudioOut2MasteringTerm() == 0, "termination must be accepted for each initialization"); + Require(sceAudioOut2MasteringTerm() == errorMasteringNotInitialized, "initializations are not counted"); + Require(sceAudioOut2MasteringInitEx(1) == errorMasteringInvalidApiParam, "sceAudioOut2MasteringInitEx refuses the low bits"); + Require(sceAudioOut2MasteringInitEx(0x80000001u) == errorMasteringInvalidApiParam, "sceAudioOut2MasteringInitEx refuses the low bits with the mode bit"); + Require(sceAudioOut2MasteringInitEx(0x80000000u) == 0, "sceAudioOut2MasteringInitEx accepts the mode bit"); + Require(sceAudioOut2MasteringTerm() == 0, "termination after sceAudioOut2MasteringInitEx must be accepted"); + Require(sceAudioOut2MasteringInitEx(0) == 0, "sceAudioOut2MasteringInitEx accepts flags 0"); } void TestMasteringSetParam() { - const std::uint32_t params[4] = {1u, 0u, 0u, 0u}; - Require(sceAudioOut2MasteringSetParam(params, 0, 0) == 0, "mastering parameters must be accepted"); - Require(sceAudioOut2MasteringSetParam(params, 2, 0) == 0, "mastering parameters for output 2 must be accepted"); + const AudioOut2MasteringParamsHeader params{1u}; + Require(sceAudioOut2MasteringSetParam(¶ms, 0, 0) == 0, "mastering parameters must be accepted"); + Require(sceAudioOut2MasteringSetParam(¶ms, 2, 0) == 0, "mastering parameters for output 2 must be accepted"); Require(sceAudioOut2MasteringSetParam(nullptr, 0, 0) == errorMasteringInvalidApiParam, "null mastering parameters return the invalid API parameter error"); + Require(sceAudioOut2MasteringSetParam(nullptr, 0, 2) == errorMasteringInvalidApiParam, "the null check comes before the flag check"); + Require(sceAudioOut2MasteringSetParam(¶ms, 0, 1) == 0, "flags 1 must be accepted"); + Require(sceAudioOut2MasteringSetParam(¶ms, 0, 2) == errorMasteringInvalidFlag, "flags above 1 are refused"); } void TestMasteringGetState() { alignas(16) std::uint8_t storage[512]; - Require(sceAudioOut2MasteringGetState(nullptr, 0, 0) == errorMasteringInvalidApiParam, "a null state returns the invalid API parameter error"); - auto* header = reinterpret_cast(storage); + Require(sceAudioOut2MasteringGetState(nullptr, 0, 0) == errorInvalidPointer, "a null state returns the invalid pointer error"); + auto* header = reinterpret_cast(storage); auto* words = reinterpret_cast(storage); - *header = 1u; - Require(sceAudioOut2MasteringGetState(storage, 0, 0) == 0, "states id 1 must be accepted"); - Require(*header == 1u, "states id 1 is kept"); + header->states_id = 1u; + Require(sceAudioOut2MasteringGetState(header, 0, 0) == 0, "states id 1 must be accepted"); + Require(header->states_id == 1u, "states id 1 is kept"); Require(words[4] == 0x01010001u && words[5] == 8u + 8u + 3u * 8u * 4u * 2u, "the compressor states descriptor is filled"); Require(words[56] == 0x01010003u && words[57] == 8u + 8u + 8u * 4u * 3u, "the limiter states descriptor is filled"); std::fill(storage, storage + sizeof(storage), 0xA5); - *header = 2u; - Require(sceAudioOut2MasteringGetState(storage, 2, 1) == 0, "states id 2 must be accepted"); - Require(*header == 2u, "states id 2 is kept"); + header->states_id = 2u; + Require(sceAudioOut2MasteringGetState(header, 2, 1) == 0, "states id 2 must be accepted"); + Require(header->states_id == 2u, "states id 2 is kept"); Require(words[4] == 0x01020001u && words[5] == 8u + 8u + 4u * 8u * 4u * 2u, "the V2 compressor states descriptor is filled"); Require(words[72] == 0x01010003u && words[73] == 8u + 8u + 8u * 4u * 3u, "the V2 limiter states descriptor is filled"); Require(std::all_of(storage + 8, storage + 16, [](std::uint8_t byte) { return byte == 0; }), "the reserved words are zeroed"); Require(std::all_of(storage + 24, storage + 288, [](std::uint8_t byte) { return byte == 0; }), "the V2 compressor states are zeroed"); - *header = 3u; - Require(sceAudioOut2MasteringGetState(storage, 0, 0) == errorMasteringInvalidStatesId, "an unknown states id is refused"); - *header = 0u; - Require(sceAudioOut2MasteringGetState(storage, 0, 0) == errorMasteringInvalidStatesId, "states id 0 is refused"); + header->states_id = 3u; + Require(sceAudioOut2MasteringGetState(header, 0, 0) == errorMasteringInvalidStatesId, "an unknown states id is refused"); + header->states_id = 0u; + Require(sceAudioOut2MasteringGetState(header, 0, 0) == errorMasteringInvalidStatesId, "states id 0 is refused"); +} + +void TestLegacyMasteringInit() { + Require(sceAudioOutMasteringInit(0) == 0, "legacy flags 0 must be accepted"); + Require(ThrowsException([] { sceAudioOutMasteringInit(1); }), "legacy flags SwapEqCompressor must throw"); + Require(sceAudioOutMasteringInit(0) == 0, "a repeated legacy initialization must be accepted"); + Require(ThrowsException([] { sceAudioOutMasteringInit(2); }), "legacy flags bit 1 must throw"); + Require(ThrowsException([] { sceAudioOutMasteringInit(3); }), "legacy flags bits 0 and 1 must throw"); + Require(ThrowsException([] { sceAudioOutMasteringInit(0x80000000u); }), "legacy flags bit 31 must throw"); + Require(ThrowsException([] { sceAudioOutMasteringInit(0xFFFFFFFFu); }), "legacy flags 0xFFFFFFFF must throw"); +} + +void TestLegacyMasteringTerm() { + Require(sceAudioOutMasteringTerm() == 0, "legacy termination must be accepted"); + Require(sceAudioOutMasteringTerm() == 0, "legacy termination must be accepted for each initialization"); +} + +void TestLegacyMasteringSetParam() { + alignas(16) std::uint32_t params[16]; + std::fill(params, params + 16, 0xA5A5A5A5u); + params[0] = 1u; + Require(sceAudioOutMasteringSetParam(reinterpret_cast(params), 0) == 0, "legacy mastering parameters must be accepted"); + Require(ThrowsException([&] { sceAudioOutMasteringSetParam(reinterpret_cast(params), 1); }), "legacy parameters with SwapEqCompressor must throw"); + Require(params[0] == 1u && params[1] == 0xA5A5A5A5u && params[15] == 0xA5A5A5A5u, "legacy mastering parameters are not modified"); + Require(ThrowsException([&] { sceAudioOutMasteringSetParam(reinterpret_cast(params), 2); }), "legacy parameters with flags bit 1 must throw"); + Require(ThrowsException([&] { sceAudioOutMasteringSetParam(reinterpret_cast(params), 0xFFFFFFFFu); }), "legacy parameters with unknown flags must throw"); + Require(ThrowsException([] { sceAudioOutMasteringSetParam(nullptr, 0); }), "null legacy parameters must throw"); + Require(ThrowsException([] { sceAudioOutMasteringSetParam(nullptr, 2); }), "null legacy parameters with unknown flags must throw"); + params[0] = 0u; + Require(ThrowsException([&] { sceAudioOutMasteringSetParam(reinterpret_cast(params), 0); }), "legacy params id 0 must throw"); + params[0] = 2u; + Require(ThrowsException([&] { sceAudioOutMasteringSetParam(reinterpret_cast(params), 0); }), "legacy params id 2 must throw"); + params[0] = 0x01010001u; + Require(ThrowsException([&] { sceAudioOutMasteringSetParam(reinterpret_cast(params), 0); }), "an unknown legacy params id must throw"); +} + +void TestLegacyMasteringGetState() { + constexpr std::size_t size = 512; + constexpr std::size_t statesSize = 336; + alignas(16) std::uint8_t storage[size]; + auto* header = reinterpret_cast(storage); + std::fill(storage, storage + size, 0xA5); + *header = 1u; + Require(sceAudioOutMasteringGetState(reinterpret_cast(storage)) == 0, "legacy states id 1 must be accepted"); + Require(*header == 1u, "legacy states id 1 is kept"); + const std::pair meters[] = {{32, 128}, {128, 224}, {240, 272}, {272, 304}, {304, 336}}; + for (const auto& meter : meters) { + Require(std::all_of(storage + meter.first, storage + meter.second, [](std::uint8_t byte) { return byte == 0; }), "the legacy mastering meters are zeroed"); + } + Require(std::all_of(storage + 4, storage + 32, [](std::uint8_t byte) { return byte == 0xA5; }), "the bytes before the compressor meters are not modified"); + Require(std::all_of(storage + 224, storage + 240, [](std::uint8_t byte) { return byte == 0xA5; }), "the limiter descriptor bytes are not modified"); + Require(std::all_of(storage + statesSize, storage + size, [](std::uint8_t byte) { return byte == 0xA5; }), "the bytes after the states are not modified"); + Require(sceAudioOutMasteringGetState(reinterpret_cast(storage)) == 0, "legacy states id 1 must be accepted again"); + + for (std::uint32_t id : {0u, 2u, 3u, 0x01010001u, 0xFFFFFFFFu}) { + std::fill(storage, storage + size, 0xA5); + *header = id; + Require(ThrowsException([&] { sceAudioOutMasteringGetState(reinterpret_cast(storage)); }), "an unknown legacy states id must throw"); + Require(*header == id && std::all_of(storage + 4, storage + size, [](std::uint8_t byte) { return byte == 0xA5; }), "a refused legacy states id leaves the buffer unmodified"); + } + Require(ThrowsException([] { sceAudioOutMasteringGetState(nullptr); }), "a null legacy state must throw"); } void TestSetSystemDebugState() { - const std::uint64_t param[4] = {1, 0, 0, 0}; - Require(sceAudioOut2SetSystemDebugState(reinterpret_cast(param)) == 0, "a debug state must be accepted"); - Require(sceAudioOut2SetSystemDebugState(reinterpret_cast(param)) == 0, "a debug state must be accepted again"); - Require(sceAudioOut2SetSystemDebugState(nullptr) == errorInvalidArgument, "a null debug state is refused"); + std::uint32_t loudness[2] = {1, 0}; + const auto set = [&loudness](std::uint32_t id, void* data, std::size_t size) { + const AudioOut2SystemDebugStateParam param{id, 0, data, size}; + return sceAudioOut2SetSystemDebugState(¶m); + }; + Require(set(1, loudness, 8) == 0, "the loudness debug state must be accepted"); + Require(set(1, loudness, 8) == 0, "the loudness debug state must be accepted again"); + Require(set(1, loudness, 0) == errorInvalidParam, "a loudness debug state of size 0 is refused"); + Require(set(1, loudness, 4) == errorInvalidParam, "a loudness debug state of size 4 is refused"); + Require(set(1, loudness, 16) == errorInvalidParam, "a loudness debug state of size 16 is refused"); + Require(set(0, loudness, 8) == errorInvalidParam, "debug state id 0 is refused"); + Require(set(2, loudness, 8) == errorInvalidParam, "debug state id 2 is refused"); + Require(set(0x21, loudness, 8) == errorInvalidParam, "debug state id 0x21 is refused"); + Require(set(0xFFFFFFFFu, loudness, 8) == errorInvalidParam, "a large debug state id is refused"); + Require(set(0x20, loudness, 8) == errorInvalidParam, "debug state id 0x20 of size 8 is refused"); + Require(set(0x20, loudness, 0) == errorInvalidParam, "debug state id 0x20 of size 0 is refused"); + Require(ThrowsRuntimeError([&] { set(0x20, loudness, 4); }), "debug state id 0x20 of size 4 must throw"); + Require(ThrowsRuntimeError([&] { set(1, nullptr, 8); }), "a loudness debug state without a param must throw"); + Require(sceAudioOut2SetSystemDebugState(nullptr) == errorInvalidPointer, "a null debug state is refused"); } void TestEnableChat() { @@ -122,7 +219,9 @@ void TestEnableChat() { void TestLowLatencyService() { Require(sceAudioOut2LoInit() == 0, "starting the low-latency service must be accepted"); Require(sceAudioOut2LoInit() == 0, "starting the low-latency service again must be accepted"); - Require(ThrowsRuntimeError([] { sceAudioOut2LoTerminate(); }), "stopping the low-latency service must throw"); + Require(sceAudioOut2LoTerminate() == 0, "stopping the low-latency service returns 0"); + Require(sceAudioOut2LoTerminate() == 0, "stopping a stopped low-latency service returns 0"); + Require(sceAudioOut2InitializeEx(0) == 0 && sceAudioOut2InitializeEx(1) == 0, "the extended initialization succeeds"); } } @@ -134,6 +233,10 @@ int main() { TestMasteringTerm(); TestMasteringSetParam(); TestMasteringGetState(); + TestLegacyMasteringInit(); + TestLegacyMasteringTerm(); + TestLegacyMasteringSetParam(); + TestLegacyMasteringGetState(); TestSetSystemDebugState(); TestEnableChat(); TestLowLatencyService(); diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortBuffers.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortBuffers.cpp index b1d5725eca..fff06c8167 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortBuffers.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortBuffers.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include "SceTypes.hpp" #include @@ -17,18 +18,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOut2ContextResetParam(AudioOut2ContextParam*); -int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam*, void*, std::size_t, AudioOut2ContextHandle*); -int APS5_VABI sceAudioOut2ContextDestroy(AudioOut2ContextHandle); -int APS5_VABI sceAudioOut2ContextAdvance(AudioOut2ContextHandle); -int APS5_VABI sceAudioOut2ContextGetQueueLevel(AudioOut2ContextHandle, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle, std::uint32_t); -int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle, const AudioOut2PortParam*, AudioOut2PortHandle*); -int APS5_VABI sceAudioOut2PortDestroy(AudioOut2PortHandle); -int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle, const AudioOut2Attribute*, std::uint32_t); -} - static void Require(bool value) { if (!value) std::abort(); } static void SetEnvironment(const char* name, const std::string& value) { diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp index 1cad3a7422..dcdcb0268c 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include "SceTypes.hpp" #include @@ -14,19 +15,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOut2UserCreate(std::uint32_t, AudioOut2UserHandle*); -int APS5_VABI sceAudioOut2UserDestroy(AudioOut2UserHandle); -int APS5_VABI sceAudioOut2UserGetSupportedAttributes(AudioOut2UserHandle, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceAudioOut2ContextResetParam(AudioOut2ContextParam*); -int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam*, void*, std::size_t, AudioOut2ContextHandle*); -int APS5_VABI sceAudioOut2ContextDestroy(AudioOut2ContextHandle); -int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle, std::uint32_t); -int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle, const AudioOut2PortParam*, AudioOut2PortHandle*); -int APS5_VABI sceAudioOut2PortDestroy(AudioOut2PortHandle); -int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle, const AudioOut2Attribute*, std::uint32_t); -} - static void Require(bool value) { if (!value) std::abort(); } static void SetEnvironment(const char* name, const std::string& value) { @@ -77,11 +65,17 @@ void TestUserAttributes() { } Require(threw && context.value == 0xFFFFFFFFu && port.value == 0xFFFFFFFFu); }; - rejects(first, true, false); - rejects(first, false, true); - rejects(first, true, true); - rejects(0, false, false); - rejects(std::numeric_limits::max(), false, false); + context.value = port.value = 0xFFFFFFFFu; + Require(sceAudioOut2UserGetSupportedAttributes(first, nullptr, &port.value) == 0); + Require(context.value == 0xFFFFFFFFu && port.value == 3); + Require(sceAudioOut2UserGetSupportedAttributes(first, &context.value, nullptr) == 0 && context.value == 0); + context.value = port.value = 0xFFFFFFFFu; + Require(sceAudioOut2UserGetSupportedAttributes(first, nullptr, nullptr) == static_cast(0x80268001)); + for (const AudioOut2UserHandle handle : {AudioOut2UserHandle{0}, std::numeric_limits::max()}) { + Require(sceAudioOut2UserGetSupportedAttributes(handle, &context.value, &port.value) == static_cast(0x80268010)); + Require(sceAudioOut2UserGetSupportedAttributes(handle, nullptr, nullptr) == static_cast(0x80268010)); + Require(context.value == 0xFFFFFFFFu && port.value == 0xFFFFFFFFu); + } Require(sceAudioOut2UserDestroy(first) == 0); rejects(first, false, false); std::thread([&] { diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Timing.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Timing.cpp index 36074daece..0dfd811378 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2Timing.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Timing.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" #include "SceTypes.hpp" #include @@ -14,29 +15,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOut2ContextResetParam(AudioOut2ContextParam*); -int APS5_VABI sceAudioOut2ContextQueryMemory(const AudioOut2ContextParam*, std::size_t*); -int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam*, void*, std::size_t, AudioOut2ContextHandle*); -int APS5_VABI sceAudioOut2ContextDestroy(AudioOut2ContextHandle); -int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle, std::uint32_t); -int APS5_VABI sceAudioOut2ContextGetQueueLevel(AudioOut2ContextHandle, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle, const AudioOut2PortParam*, AudioOut2PortHandle*); -int APS5_VABI sceAudioOut2PortDestroy(AudioOut2PortHandle); -int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle, const AudioOut2Attribute*, std::uint32_t); -int APS5_VABI sceAudioOut2LoContextQueryMemory(const AudioOut2ContextParam*, std::size_t*); -int APS5_VABI sceAudioOut2LoContextCreate(const AudioOut2ContextParam*, void*, std::size_t, AudioOut2ContextHandle*); -int APS5_VABI sceAudioOut2LoContextDestroy(AudioOut2ContextHandle); -int APS5_VABI sceAudioOut2LoContextSetAttributes(AudioOut2ContextHandle, const AudioOut2Attribute*, std::uint32_t); -int APS5_VABI sceAudioOut2LoContextAdvance(AudioOut2ContextHandle); -int APS5_VABI sceAudioOut2LoContextPush(AudioOut2ContextHandle, std::uint32_t); -int APS5_VABI sceAudioOut2LoContextGetQueueLevel(AudioOut2ContextHandle, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceAudioOut2LoPortCreate(AudioOut2ContextHandle, const AudioOut2PortParam*, AudioOut2PortHandle*); -int APS5_VABI sceAudioOut2LoPortDestroy(AudioOut2PortHandle); -int APS5_VABI sceAudioOut2LoPortSetAttributes(AudioOut2PortHandle, const AudioOut2Attribute*, std::uint32_t); -int APS5_VABI sceAudioOut2LoPortGetState(AudioOut2PortHandle, AudioOut2PortState*); -} - static void Require(bool value) { if (!value) std::abort(); } static void SetEnvironment(const char* name, const std::string& value) { diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp new file mode 100644 index 0000000000..6b2eaaef4e --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp @@ -0,0 +1,95 @@ +#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 +#include +#include +#include + +namespace { +constexpr SDL_AudioDeviceID device = 7; +int opened = 0; +int closed = 0; +int queued = 0; +std::uint32_t queuedBytes = 0; +const void* supplied = nullptr; +std::uint32_t suppliedBytes = 0; + +void Require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +void RunPassThrough() { + std::vector samples(512u * 8u); + for (std::size_t i = 0; i < samples.size(); ++i) samples[i] = static_cast(i % 97u) / 97.0f; + const int handle = sceAudioOutOpen(0, 0, 0, 512, 48000, 5); + Require(handle > 0 && opened == 1, "a 512-frame F32 8-channel port did not open its device"); + supplied = samples.data(); + suppliedBytes = static_cast(samples.size() * sizeof(float)); + Require(sceAudioOutOutput(handle, samples.data()) == 512, "a 512-frame output did not return 512"); + Require(queued == 1 && queuedBytes == 16384, "a 512-frame F32 8-channel output did not queue 16384 bytes"); + Require(sceAudioOutClose(handle) == 0 && closed == 1, "the port did not close its device"); +} + +void RunOverflow(std::uint32_t frames, std::uint32_t format, std::uint32_t frameBytes) { + std::vector frame(frameBytes); + const int handle = sceAudioOutOpen(0, 0, 0, frames, 48000, format); + Require(handle > 0 && opened == 1, "a port with an oversized length did not open its device"); + supplied = frame.data(); + suppliedBytes = frameBytes; + bool threw = false; + try { + static_cast(sceAudioOutOutput(handle, frame.data())); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "an output whose buffer size overflows did not throw"); + Require(queued == 0, "an output whose buffer size overflows queued audio"); + Require(sceAudioOutClose(handle) == 0 && closed == 1, "the port did not close after the refused output"); +} +} + +int InitializeSdlAudio_nid_no_patch() { return 0; } +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"; } +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 "dummy"; } +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 && desired->callback == nullptr, "the main port did not open the default device"); + ++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* data, Uint32 bytes) { + Require(id == device, "wrong queue fed"); + ++queued; + queuedBytes = bytes; + if (bytes <= suppliedBytes) Require(std::memcmp(data, supplied, bytes) == 0, "the queued audio is not the supplied samples"); + 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]; + if (mode == "pass_through") RunPassThrough(); + else if (mode == "wrap") RunOverflow(0x80000000u, 5, 8u * sizeof(float)); + else if (mode == "mix") RunOverflow(0x80000000u, 7, 8u * sizeof(float)); + else if (mode == "threshold") RunOverflow(0x40000000u, 0, sizeof(std::int16_t)); + else Require(false, "invalid buffer size mode"); + std::puts("audio out buffer size 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/AudioOutEx.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutEx.cpp index b96f9dd570..0242fe8839 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutEx.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutEx.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,13 +6,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOutExOpen(int userId, std::uint32_t mode); -int APS5_VABI sceAudioOutExConfigureOutput(int control, std::uint64_t flags, std::uint32_t mode, std::uint32_t target, std::uint64_t option); -int APS5_VABI sceAudioOutExClose(int handle); -int APS5_VABI sceAudioOutOutput(int handle, const void* ptr); -} - static constexpr int SYSTEM_USER = 0xFF; static constexpr int ERROR_EX_INVALID_MODE = static_cast(0x80260015u); static constexpr int ERROR_EX_INVALID_CONTROL = static_cast(0x80268009u); diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutExMonitorInfo.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutExMonitorInfo.cpp index fecdf065e1..6e6194c862 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutExMonitorInfo.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutExMonitorInfo.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,12 +6,6 @@ #include #include #include -#include -#include - -extern "C" { -int APS5_VABI sceAudioOutExGetMonitorInfo(int, int, void*, std::uint32_t); -} static void Require(bool value, const char* message) { if (value) return; @@ -21,28 +16,16 @@ static void Require(bool value, const char* message) { namespace { constexpr std::uint32_t titleSize = 0x400; -constexpr std::uint32_t minimumSize = 0xA0; constexpr std::size_t descriptorCountOffset = 0x98; constexpr std::size_t guardSize = 16; constexpr std::uint8_t untouched = 0xA5; constexpr int invalidPointer = static_cast(0x80260004); -bool Throws(int type, int index, std::uint32_t size, const std::string& expected) { - std::array info{}; - try { - sceAudioOutExGetMonitorInfo(type, index, info.data(), size); - } catch (const std::runtime_error& error) { - if (std::string(error.what()) == expected) return true; - std::fprintf(stderr, "threw: %s\n", error.what()); - return false; - } - return false; -} - -void CheckEmpty(std::uint32_t size) { +void CheckEmpty(int index) { + constexpr auto size = titleSize; std::array buffer{}; buffer.fill(untouched); - Require(sceAudioOutExGetMonitorInfo(0, 0, buffer.data(), size) == 0, "the monitor query must succeed"); + Require(sceAudioOutExGetMonitorInfo(0, index, buffer.data(), size) == 0, "the monitor query must succeed"); for (std::uint32_t i = 0; i < size; i++) { Require(buffer[i] == 0, "the monitor info must describe a sink without audio descriptors"); } @@ -57,15 +40,14 @@ void CheckEmpty(std::uint32_t size) { } int main() { - CheckEmpty(titleSize); - CheckEmpty(minimumSize); - Require(sceAudioOutExGetMonitorInfo(0, 0, nullptr, titleSize) == invalidPointer, - "a null buffer must report an invalid pointer"); - Require(Throws(0, 0, minimumSize - 1, "sceAudioOutExGetMonitorInfo: size 159 not supported"), - "a buffer without room for the descriptor count must throw"); - Require(Throws(1, 0, titleSize, "sceAudioOutExGetMonitorInfo: monitor 1, 0 not supported"), - "a nonzero first argument must throw"); - Require(Throws(0, 1, titleSize, "sceAudioOutExGetMonitorInfo: monitor 0, 1 not supported"), - "a nonzero second argument must throw"); + for (const int index : {0, 1, -1, 100}) CheckEmpty(index); + std::array buffer; + buffer.fill(untouched); + for (const std::uint32_t size : {0u, 0xA0u, titleSize - 1, titleSize + 1, 0xffffffffu}) { + Require(sceAudioOutExGetMonitorInfo(0, 0, buffer.data(), size) == static_cast(0x80260006), "exact size required"); + Require(sceAudioOutExGetMonitorInfo(0, 0, nullptr, size) == invalidPointer, "pointer checked before size"); + Require(sceAudioOutExGetMonitorInfo(1, 0, nullptr, size) == static_cast(0x80260003), "type checked before pointer"); + for (const auto value : buffer) Require(value == untouched, "failure preserves output"); + } return 0; } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutInfo.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutInfo.cpp new file mode 100644 index 0000000000..f246625a21 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutInfo.cpp @@ -0,0 +1,18 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +int main() { + Require(sceAudioOutGetSimulatedHandleStatusInfo() == static_cast(0x809B00FF), "simulated handle status info is not supported"); + Require(sceAudioOutGetInfoOpenNum() == 0, "open port count info returns 0"); + Require(sceAudioOutGetInfo() == 0, "port info returns 0"); + std::puts("audio out info calls: PASS"); +} diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp index e6cdeed0d7..487624514e 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -9,16 +11,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioOutClose(int); -int APS5_VABI sceAudioOutOutput(int, const void*); -int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); -int APS5_VABI sceAudioOutGetPortState(int, AudioOutPortState*); -int APS5_VABI sceAudioOutGetLastOutputTime(int, std::uint64_t*); -std::uint64_t APS5_VABI sceKernelGetProcessTime(); -} - static void Require(bool value, const char* message) { if (value) return; std::fprintf(stderr, "%s\n", message); @@ -231,6 +223,7 @@ int main() { setenv("SDL_AUDIODRIVER", "dummy", 1); #endif sceKernelGetProcessTime(); + TestOutput(portTypeMain); TestOutput(portTypeVibration); TestPortsAreIndependent(); diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutMixLevelPadSpk.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutMixLevelPadSpk.cpp index 9c7ac4e9af..ae686bee18 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutMixLevelPadSpk.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutMixLevelPadSpk.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -11,14 +12,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioOutClose(int); -int APS5_VABI sceAudioOutOutput(int, const void*); -int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*); -int APS5_VABI sceAudioOutSetMixLevelPadSpk(int, int); -} - static void Require(bool value) { if (!value) std::abort(); } static void SetEnvironment(const char* name, const std::string& value) { @@ -153,7 +146,7 @@ void TestOtherPortType() { Require(Play(portTypeMain, formatS16Mono, block, [](int) {}) == block); Require(Play(portTypeMain, formatS16Mono, block, [](int handle) { Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == invalidPortType); - Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidPortType); + Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidMixLevel); }) == block); } @@ -164,7 +157,7 @@ void TestRejectedLevels() { closed = handle; Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == 0); Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidMixLevel); - Require(ThrowsRuntimeError([handle] { sceAudioOutSetMixLevelPadSpk(handle, -1); })); + Require(sceAudioOutSetMixLevelPadSpk(handle, UINT32_MAX) == invalidMixLevel); Require(sceAudioOutSetMixLevelPadSpk(0, unity) == invalidPort); Require(sceAudioOutSetMixLevelPadSpk(handle + 1, unity) == invalidPort); }) == Scaled(block, unity / 2)); diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutPortLocks.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutPortLocks.cpp index 107a2ef2f5..15703d8ba8 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutPortLocks.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutPortLocks.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "SDL.h" #include "prx/libc/include/general/VabiMacros.hpp" @@ -12,12 +13,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioOutOutput(int, const void*); -int APS5_VABI sceAudioOutClose(int); -} - namespace { constexpr std::uint32_t Grain = 256; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutResampling.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutResampling.cpp index c56be14970..6b9577e91d 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutResampling.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutResampling.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "SDL.h" #include "prx/libc/include/general/VabiMacros.hpp" @@ -7,12 +8,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioOutOutput(int, const void*); -int APS5_VABI sceAudioOutClose(int); -} - namespace { constexpr std::uint32_t grain = 256; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutShutdown.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutShutdown.cpp index dd5390753d..fcadcec548 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutShutdown.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutShutdown.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -11,17 +13,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOut2ContextResetParam(AudioOut2ContextParam*); -int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam*, void*, std::size_t, AudioOut2ContextHandle*); -int APS5_VABI sceAudioOut2ContextAdvance(AudioOut2ContextHandle); -int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle, std::uint32_t); -int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle, const AudioOut2PortParam*, AudioOut2PortHandle*); -int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle, const AudioOut2Attribute*, std::uint32_t); -int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioOutOutput(int, const void*); -} - namespace { constexpr std::uint32_t grain = 256; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutSys.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutSys.cpp index eb5878fc18..6c8f9561ee 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutSys.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutSys.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,14 +6,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOutSysOpen(int userId, std::uint32_t mode); -int APS5_VABI sceAudioOutSysConfigureOutput(int type, std::uint64_t flags, std::uint32_t mode, std::uint32_t target, std::uint64_t option); -int APS5_VABI sceAudioOutSysClose(int handle); -int APS5_VABI sceAudioOutExOpen(int userId, std::uint32_t mode); -int APS5_VABI sceAudioOutOutput(int handle, const void* ptr); -} - static constexpr int SYSTEM_USER = 0xFF; static bool failed = false; @@ -53,6 +46,10 @@ int main() { Require(Throws([] { sceAudioOutSysConfigureOutput(1, 0, 4, 1, 0); }), "another mode throws"); Require(Throws([] { sceAudioOutSysConfigureOutput(1, 0, 5, 2, 0); }), "another target throws"); + int mainOutput = -1; + Require(!Throws([&mainOutput] { mainOutput = sceAudioOutSetMainOutput(); }), "sceAudioOutSetMainOutput does not throw"); + Require(mainOutput == 0, "sceAudioOutSetMainOutput returns 0"); + Require(sceAudioOutSysClose(truehd) == 0, "close the first Sys port"); Require(Throws([truehd] { sceAudioOutSysClose(truehd); }), "a closed Sys port throws"); const int ex = sceAudioOutExOpen(SYSTEM_USER, 0); diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutSysHdmiMonitorInfo.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutSysHdmiMonitorInfo.cpp index ce23a664d7..1ea013ca7d 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutSysHdmiMonitorInfo.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutSysHdmiMonitorInfo.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -8,10 +9,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOutSysGetHdmiMonitorInfo(int, void*, std::uint32_t); -} - static void Require(bool value, const char* message) { if (value) return; std::fprintf(stderr, "%s\n", message); @@ -59,13 +56,14 @@ int main() { Require(sceAudioOutSysGetHdmiMonitorInfo(monitorTypeHdmi, nullptr, infoSize) == invalidPointer, "a null buffer must report an invalid pointer"); - Require(Throws(monitorTypeHdmi, infoSize - 1, - "sceAudioOutSysGetHdmiMonitorInfo: size 383 not supported"), "a short buffer must throw"); - Require(Throws(monitorTypeHdmi, infoSize + 1, - "sceAudioOutSysGetHdmiMonitorInfo: size 385 not supported"), "a long buffer must throw"); + buffer.fill(untouched); + for (const std::uint32_t size : {0u, infoSize - 1, infoSize + 1, 0xffffffffu}) { + Require(sceAudioOutSysGetHdmiMonitorInfo(monitorTypeHdmi, buffer.data(), size) == static_cast(0x80260006), "exact size required"); + Require(sceAudioOutSysGetHdmiMonitorInfo(0, nullptr, size) == invalidPointer, "pointer checked before size and type"); + for (const auto value : buffer) Require(value == untouched, "failure preserves output"); + } + for (const int type : {0, 2, -1, 17}) Require(sceAudioOutSysGetHdmiMonitorInfo(type, buffer.data(), infoSize) == static_cast(0x8026000C), "unsupported type"); Require(Throws(0x10, infoSize, - "sceAudioOutSysGetHdmiMonitorInfo: monitor type 16 not supported"), "monitor type 0x10 must throw"); - Require(Throws(0, infoSize, - "sceAudioOutSysGetHdmiMonitorInfo: monitor type 0 not supported"), "monitor type 0 must throw"); + "sceAudioOutSysGetHdmiMonitorInfo: monitor type 16 not supported"), "unimplemented device query"); return 0; } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp new file mode 100644 index 0000000000..c925334fbd --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp @@ -0,0 +1,39 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +namespace { + +constexpr int invalidPointer = static_cast(0x80260004); + +void TestGetSystemState() { + AudioOutSystemState state{}; + state.loudness = -1.0f; + state.reserved8[0] = 0xAA; + state.reserved[2] = ~0ull; + Require(sceAudioOutGetSystemState(&state) == 0, "sceAudioOutGetSystemState must succeed with valid pointer"); + Require(state.loudness == 0.0f, "loudness must be reported as 0.0f"); + Require(state.reserved8[0] == 0 && state.reserved[2] == 0, "reserved fields must be cleared"); +} + +void TestInvalidPointer() { + Require(sceAudioOutGetSystemState(nullptr) == invalidPointer, "null pointer must return SCE_AUDIO_OUT_ERROR_INVALID_POINTER"); +} + +} + +int main() { + TestGetSystemState(); + TestInvalidPointer(); + return 0; +} diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp index 4f27e0bf7d..6c01a3a317 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "prx/libScePad/include/PadState.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -7,15 +8,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioOutExOpen(int, std::uint32_t); -int APS5_VABI sceAudioOutClose(int); -int APS5_VABI sceAudioOutOutput(int, const void*); -int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); -int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*); -} - static void Require(bool value, const char* message) { if (!value) throw std::runtime_error(message); } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp index 6d0a7ca4f1..79681dff96 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" #include "SDL.h" #include "prx/libSceAudioOut/include/AudioOutSdl.hpp" #include "prx/libScePad/include/PadState.hpp" @@ -13,15 +14,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioOutClose(int); -int APS5_VABI sceAudioOutOutput(int, const void*); -int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); -int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*); -int APS5_VABI sceAudioOutGetLastOutputTime(int, std::uint64_t*); -} - namespace { constexpr std::uint32_t frames = 256; bool available = true; diff --git a/core/libs/prx/libSceAudioPropagation/AudioPropagation.hpp b/core/libs/prx/libSceAudioPropagation/AudioPropagation.hpp index 602d531cfa..f00b2ea48a 100644 --- a/core/libs/prx/libSceAudioPropagation/AudioPropagation.hpp +++ b/core/libs/prx/libSceAudioPropagation/AudioPropagation.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCEAUDIOPROPAGATION_AUDIOPROPAGATION_HPP #define CORE_LIBS_PRX_LIBSCEAUDIOPROPAGATION_AUDIOPROPAGATION_HPP +#include #include #include @@ -9,6 +10,10 @@ namespace AudioPropagation { +inline constexpr std::int32_t ErrorInvalidArgument = static_cast(0x8A700001); +inline constexpr std::int32_t ErrorInvalidHandle = static_cast(0x8A700002); +inline constexpr std::int32_t ErrorNullPointer = static_cast(0x8A700003); + inline constexpr std::uint32_t MaterialId = 0x010107d1; inline constexpr std::size_t MaterialSize = 0x40; inline constexpr std::uint32_t SystemMemoryId = 0x010107d4; @@ -23,6 +28,115 @@ inline constexpr std::size_t PortalParamsSize = 0x60; inline constexpr std::uint32_t PathEntryId = 0x010107d9; inline constexpr std::size_t PathEntrySize = 0x28; +inline constexpr std::uint32_t SystemOptionsId = 0x010107d5; +inline constexpr std::size_t SystemOptionsSize = 56; + +struct SystemOptions { + AudioPropagationStructDescriptor desc; + std::uint32_t maxSources; + std::uint32_t maxMaterials; + std::uint32_t parameter24; + std::uint32_t reflectionOrder; + std::uint32_t frameSamples; + float propagationSpeed; + float maxPathLength; + std::uint32_t flags; + std::uint8_t opaque[8]; +}; + +static_assert(sizeof(SystemOptions) == SystemOptionsSize); +static_assert(offsetof(SystemOptions, frameSamples) == 32); +static_assert(offsetof(SystemOptions, flags) == 44); + +struct SourceInfo { + AudioPropagationHandle system; + float position[3]; + std::uint32_t reserved20; + float direction[3]; + std::uint32_t reserved36; + std::uint32_t attribute2Mode; + float attribute2Values[3]; + float attribute3Values[4]; + std::uint32_t pathCapacity; + std::uint32_t parameter76; +}; + +struct SystemInfo { + void* cpuMemory; + std::size_t cpuMemorySize; + std::uint32_t index; + std::uint32_t frameSamples; + std::uint32_t parameter24; + std::uint32_t maxSources; + std::uint32_t maxMaterials; + std::uint32_t pathCapacityPerSource; + float propagationSpeed; + std::uint32_t reserved44; +}; + +static_assert(sizeof(SourceInfo) == 80); +static_assert(offsetof(SourceInfo, position) == 8); +static_assert(offsetof(SourceInfo, direction) == 24); +static_assert(offsetof(SourceInfo, attribute2Mode) == 40); +static_assert(offsetof(SourceInfo, attribute2Values) == 44); +static_assert(offsetof(SourceInfo, attribute3Values) == 56); +static_assert(offsetof(SourceInfo, pathCapacity) == 72); +static_assert(offsetof(SourceInfo, parameter76) == 76); +static_assert(sizeof(SystemInfo) == 48); +static_assert(offsetof(SystemInfo, cpuMemorySize) == 8); +static_assert(offsetof(SystemInfo, index) == 16); +static_assert(offsetof(SystemInfo, frameSamples) == 20); +static_assert(offsetof(SystemInfo, parameter24) == 24); +static_assert(offsetof(SystemInfo, maxSources) == 28); +static_assert(offsetof(SystemInfo, maxMaterials) == 32); +static_assert(offsetof(SystemInfo, pathCapacityPerSource) == 36); +static_assert(offsetof(SystemInfo, propagationSpeed) == 40); +struct ReportRegistration; + +struct ReportArguments { + std::uint32_t generalOffset; + std::uint32_t floatingOffset; + void* overflowArguments; + void* savedRegisters; +}; + +struct ReportInfo { + std::uint64_t size; + AudioPropagationHandle handle; + std::uint32_t category; + std::int32_t result; + std::int32_t operation; + std::uint32_t reserved; + const char* function; + const char* format; + ReportArguments arguments; +}; + +using ReportHandler = void (APS5_VABI*)(const ReportInfo* report, void* userData); + +static_assert(sizeof(ReportArguments) == 24); +static_assert(offsetof(ReportArguments, generalOffset) == 0); +static_assert(offsetof(ReportArguments, floatingOffset) == 4); +static_assert(offsetof(ReportArguments, overflowArguments) == 8); +static_assert(offsetof(ReportArguments, savedRegisters) == 16); +static_assert(sizeof(ReportInfo) == 72); +static_assert(offsetof(ReportInfo, size) == 0); +static_assert(offsetof(ReportInfo, handle) == 8); +static_assert(offsetof(ReportInfo, category) == 16); +static_assert(offsetof(ReportInfo, result) == 20); +static_assert(offsetof(ReportInfo, operation) == 24); +static_assert(offsetof(ReportInfo, function) == 32); +static_assert(offsetof(ReportInfo, format) == 40); +static_assert(offsetof(ReportInfo, arguments) == 48); + +inline constexpr std::uint32_t MaxPaths = 36; +inline constexpr std::uint32_t MaxPathPoints = 4; +inline constexpr std::uint32_t PathId = 0x010107d3; +inline constexpr std::uint32_t PathPointId = 0x010107d2; +inline constexpr std::uint32_t PathInvalid = 1; +inline constexpr std::uint32_t PathPortal = 2; + + struct Attribute { std::uint32_t id; std::uint32_t reserved0; @@ -32,13 +146,38 @@ struct Attribute { std::uint32_t reserved2; }; +struct PathPoint { + AudioPropagationStructDescriptor desc; + AudioPropagationHandle room; + float gain; + float position[3]; + std::byte opaque[8]; +}; + +struct AudioPath { + AudioPropagationStructDescriptor desc; + PathPoint points[4]; + std::uint32_t pointCount; + std::uint32_t flags; + std::byte opaque[8]; +}; + +static_assert(sizeof(PathPoint) == 48); +static_assert(offsetof(PathPoint, room) == 16); +static_assert(offsetof(PathPoint, gain) == 24); +static_assert(offsetof(PathPoint, position) == 28); +static_assert(sizeof(AudioPath) == 224); +static_assert(offsetof(AudioPath, points) == 16); +static_assert(offsetof(AudioPath, pointCount) == 208); +static_assert(offsetof(AudioPath, flags) == 212); + struct RenderInfo { AudioPropagationStructDescriptor desc; AudioPropagationHandle source; - void* output; + float* output; std::size_t outputSize; std::uint32_t format; - std::uint32_t reserved; + std::uint32_t channelCount; }; struct PortalParams { @@ -53,6 +192,7 @@ static_assert(sizeof(Attribute) == 0x20); static_assert(sizeof(RenderInfo) == RenderInfoSize); static_assert(offsetof(RenderInfo, source) == 0x10); static_assert(offsetof(RenderInfo, format) == 0x28); +static_assert(offsetof(RenderInfo, channelCount) == 0x2c); static_assert(sizeof(PortalParams) == PortalParamsSize); static_assert(offsetof(PortalParams, rooms) == 0x50); @@ -60,8 +200,8 @@ static_assert(offsetof(PortalParams, rooms) == 0x50); extern "C" { -std::int32_t APS5_VABI sceAudioPropagationSystemQueryMemory(const void* options, AudioPropagationSystemMemory* outMemory); -std::int32_t APS5_VABI sceAudioPropagationSystemCreate(const void* options, const AudioPropagationSystemMemory* memory, AudioPropagationHandle* outSystem); +std::int32_t APS5_VABI sceAudioPropagationSystemQueryMemory(const AudioPropagation::SystemOptions* options, AudioPropagationSystemMemory* outMemory); +std::int32_t APS5_VABI sceAudioPropagationSystemCreate(const AudioPropagation::SystemOptions* options, const AudioPropagationSystemMemory* memory, AudioPropagationHandle* outSystem); std::int32_t APS5_VABI sceAudioPropagationSystemDestroy(AudioPropagationHandle system); std::int32_t APS5_VABI sceAudioPropagationSystemSetAttributes(AudioPropagationHandle system, const AudioPropagation::Attribute* attributes, std::uint32_t count); std::int32_t APS5_VABI sceAudioPropagationSystemRegisterMaterial(AudioPropagationHandle system, const AudioPropagationStructDescriptor* material, AudioPropagationHandle* outMaterial); @@ -78,11 +218,12 @@ std::int32_t APS5_VABI sceAudioPropagationSourceDestroy(AudioPropagationHandle s std::int32_t APS5_VABI sceAudioPropagationSourceSetAttributes(AudioPropagationHandle source, const AudioPropagation::Attribute* attributes, std::uint32_t count); std::int32_t APS5_VABI sceAudioPropagationSourceGetAudioPathCount(AudioPropagationHandle source, std::uint32_t* count); std::int32_t APS5_VABI sceAudioPropagationSourceGetAudioPath(AudioPropagationHandle source, std::uint32_t index, AudioPropagationHandle* outPath); +std::int32_t APS5_VABI sceAudioPropagationPathGetNumPoints(AudioPropagationHandle path, std::uint32_t* count); std::int32_t APS5_VABI sceAudioPropagationSourceGetRays(AudioPropagationHandle source, void* rays, std::uint32_t* count); -std::int32_t APS5_VABI sceAudioPropagationSourceCalculateAudioPaths(AudioPropagationHandle source, const void* rays, std::uint32_t rayCount, std::uint32_t flags, void* paths, std::uint32_t pathCount); +std::int32_t APS5_VABI sceAudioPropagationSourceCalculateAudioPaths(AudioPropagationHandle source, const void* rays, std::uint32_t rayCount, std::uint32_t reflectionOrder, void* paths, std::uint32_t pathCount); std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPaths(AudioPropagationHandle source, const void* entries, std::uint32_t count); -std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPath(AudioPropagationHandle path, const void* data, float gain); -std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle system, const AudioPropagation::RenderInfo* infos, std::uint32_t count); +std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPath(AudioPropagationHandle path, const AudioPropagation::AudioPath* data, float gain); +std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle system, AudioPropagation::RenderInfo* infos, std::uint32_t count); } diff --git a/core/libs/prx/libSceAudioPropagation/CMakeLists.txt b/core/libs/prx/libSceAudioPropagation/CMakeLists.txt index 29417b8962..0575687fe3 100644 --- a/core/libs/prx/libSceAudioPropagation/CMakeLists.txt +++ b/core/libs/prx/libSceAudioPropagation/CMakeLists.txt @@ -5,7 +5,7 @@ configure_windows_unwind(libSceAudioPropagation) if(BUILD_TESTING) add_test_executable(guest_audio_propagation_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudioPropagation.cpp) target_include_directories(guest_audio_propagation_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_audio_propagation_tests PRIVATE libSceAudioPropagation libc) + target_link_libraries(guest_audio_propagation_tests PRIVATE libSceAudioPropagation libc ${CMAKE_DL_LIBS}) configure_windows_unwind(guest_audio_propagation_tests) add_test(NAME guest_audio_propagation COMMAND guest_audio_propagation_tests) if(WIN32) diff --git a/core/libs/prx/libSceAudioPropagation/Export.cpp b/core/libs/prx/libSceAudioPropagation/Export.cpp index 474117fe6f..2c257811bf 100644 --- a/core/libs/prx/libSceAudioPropagation/Export.cpp +++ b/core/libs/prx/libSceAudioPropagation/Export.cpp @@ -1,6 +1,9 @@ +#include "prx/libSceAudioPropagation/include/Api.hpp" +#include "prx/libSceAudioPropagation/AudioPropagation.hpp" #include "AudioPropagation.hpp" #include "prx/libc/include/General.hpp" +#include #include #include #include @@ -22,21 +25,29 @@ constexpr std::uint64_t SystemMagic = [] { struct SystemState { std::uint64_t magic; - std::uint64_t reserved; + std::uint32_t frameSamples; + std::uint32_t maxSources; }; -enum class Kind { Room, Portal, Source, Material }; +enum class Kind { Room, Portal, Source, Material, Path }; struct Object { Kind kind; AudioPropagationHandle system; }; +struct PathState { + AudioPath data{}; + float gain = 0.0f; +}; + struct Registry { std::mutex mutex; std::unordered_set systems; std::unordered_map objects; std::unordered_map orphans; + std::unordered_map> sourcePaths; + std::unordered_map paths; AudioPropagationHandle next = 1; }; @@ -99,6 +110,13 @@ void destroyObject(AudioPropagationHandle system, AudioPropagationHandle handle, return; } require(requireObject(state, handle, kind, function).system == system, function); + if (kind == Kind::Source) { + for (const auto path : state.sourcePaths.at(handle)) { + state.paths.erase(path); + state.objects.erase(path); + } + state.sourcePaths.erase(handle); + } state.objects.erase(handle); } @@ -115,23 +133,49 @@ void requireNoPropagation(bool none, const char* function) { extern "C" { -std::int32_t APS5_VABI sceAudioPropagationSystemQueryMemory(const void* options, AudioPropagationSystemMemory* outMemory) { - require(options != nullptr, __func__); - requireDescriptor(outMemory != nullptr ? &outMemory->desc : nullptr, SystemMemoryId, SystemMemorySize, __func__); - outMemory->size_cpu_mem = sizeof(SystemState); +std::int32_t APS5_VABI sceAudioPropagationSystemQueryMemory(const SystemOptions* options, AudioPropagationSystemMemory* outMemory) { + if (!options || !outMemory) return ErrorNullPointer; + if (outMemory->desc.id != SystemMemoryId || outMemory->desc.size != SystemMemorySize) return static_cast(0x8A700007); + outMemory->p_cpu_mem = nullptr; + outMemory->size_cpu_mem = 0; + outMemory->p_gpu_mem = nullptr; outMemory->size_gpu_mem = 0; + if (options->desc.id != SystemOptionsId || options->desc.size != SystemOptionsSize) return static_cast(0x8A700007); + if (options->maxSources == 0 || options->maxSources > 64 || options->maxMaterials == 0 || options->maxMaterials > 64 || options->parameter24 != 6 || options->reflectionOrder == 0 || options->reflectionOrder > 2) return ErrorInvalidArgument; + const auto frameSamples = options->frameSamples; + if (frameSamples != 256 && frameSamples != 512 && frameSamples != 1024 && frameSamples != 2048 && frameSamples != 4096) return ErrorInvalidArgument; + if (!(options->propagationSpeed > 0x1p-23f) || options->propagationSpeed > 1372.0f || !(options->maxPathLength > 0x1p-23f) || options->maxPathLength > 1372.0f || options->flags > 31 || ((options->flags & 1) && frameSamples != 512)) return ErrorInvalidArgument; + if (options->flags & 1) throw std::runtime_error(std::string(__func__) + ": GPU memory requirements depend on an unsupported native runtime query"); + const std::uint64_t sources = options->maxSources; + const std::uint64_t paths = sources * (options->reflectionOrder == 1 ? 6 : 36); + const float delaySeconds = options->maxPathLength / options->propagationSpeed; + const float delaySamples = delaySeconds * 48000.0f; + const auto delayCapacity = static_cast(std::ceil(delaySamples)); + std::uint64_t bytes = sources * 1024 + paths * (32 * (options->reflectionOrder + 2) + 8928); + bytes += frameSamples == 256 ? 2048 : 4 * frameSamples; + bytes += 4 * delayCapacity * sources; + bytes += bytes & 15; + bytes += 160 * paths + 12352 + 4208 * options->maxMaterials; + if (frameSamples == 256) bytes += sources * ((options->flags & 2) ? 36896 : 16416); + if (options->flags & 4) bytes += 2000656; + if (options->flags & 8) bytes += 288 * paths; + if (options->flags & 16) bytes += 144 * paths; + outMemory->size_cpu_mem = bytes + 5368 - ((bytes & 15) ^ 8); return 0; } -std::int32_t APS5_VABI sceAudioPropagationSystemCreate(const void* options, const AudioPropagationSystemMemory* memory, AudioPropagationHandle* outSystem) { +std::int32_t APS5_VABI sceAudioPropagationSystemCreate(const SystemOptions* options, const AudioPropagationSystemMemory* memory, AudioPropagationHandle* outSystem) { require(options != nullptr && outSystem != nullptr, __func__); requireDescriptor(memory != nullptr ? &memory->desc : nullptr, SystemMemoryId, SystemMemorySize, __func__); + auto requiredMemory = *memory; + const auto result = sceAudioPropagationSystemQueryMemory(options, &requiredMemory); + if (result != 0) return result; const auto address = reinterpret_cast(memory->p_cpu_mem); - require(address != 0 && address % alignof(SystemState) == 0 && memory->size_cpu_mem >= sizeof(SystemState) && memory->size_gpu_mem == 0, __func__); + require(address != 0 && address % alignof(SystemState) == 0 && memory->size_cpu_mem >= requiredMemory.size_cpu_mem && memory->size_gpu_mem == 0, __func__); auto& state = registry(); std::lock_guard lock(state.mutex); require(!state.systems.contains(address), __func__); - *static_cast(memory->p_cpu_mem) = SystemState{SystemMagic, 0}; + *static_cast(memory->p_cpu_mem) = SystemState{SystemMagic, options->frameSamples, options->maxSources}; state.systems.insert(address); *outSystem = address; return 0; @@ -141,9 +185,11 @@ std::int32_t APS5_VABI sceAudioPropagationSystemDestroy(AudioPropagationHandle s auto& state = registry(); std::lock_guard lock(state.mutex); requireSystem(state, system, __func__); + std::erase_if(state.paths, [&](const auto& entry) { return state.objects.at(entry.first).system == system; }); + std::erase_if(state.sourcePaths, [&](const auto& entry) { return state.objects.at(entry.first).system == system; }); std::erase_if(state.objects, [&](const auto& entry) { if (entry.second.system != system) return false; - state.orphans.insert(entry); + if (entry.second.kind != Kind::Path) state.orphans.insert(entry); return true; }); reinterpret_cast(system)->magic = 0; @@ -229,7 +275,30 @@ std::int32_t APS5_VABI sceAudioPropagationPortalSetAttributes(AudioPropagationHa } std::int32_t APS5_VABI sceAudioPropagationSourceCreate(AudioPropagationHandle system, AudioPropagationHandle* outSource) { - createObject(system, Kind::Source, outSource, __func__); + require(outSource != nullptr, __func__); + auto& state = registry(); + std::lock_guard lock(state.mutex); + AudioPropagationHandle source = 0; + createObjectLocked(state, system, Kind::Source, &source, __func__); + std::array handles{}; + try { + for (auto& handle : handles) { + createObjectLocked(state, system, Kind::Path, &handle, __func__); + PathState path{}; + path.data.desc = {PathId, sizeof(AudioPath)}; + path.data.flags = PathInvalid; + state.paths.emplace(handle, path); + } + state.sourcePaths.emplace(source, handles); + } catch (...) { + for (const auto handle : handles) { + state.paths.erase(handle); + state.objects.erase(handle); + } + state.objects.erase(source); + throw; + } + *outSource = source; return 0; } @@ -251,16 +320,28 @@ std::int32_t APS5_VABI sceAudioPropagationSourceGetAudioPathCount(AudioPropagati std::lock_guard lock(state.mutex); requireObject(state, source, Kind::Source, __func__); require(count != nullptr, __func__); - *count = 0; + *count = MaxPaths; return 0; } std::int32_t APS5_VABI sceAudioPropagationSourceGetAudioPath(AudioPropagationHandle source, std::uint32_t index, AudioPropagationHandle* outPath) { auto& state = registry(); std::lock_guard lock(state.mutex); - requireObject(state, source, Kind::Source, __func__); - static_cast(outPath); - throw std::invalid_argument(std::string(__func__) + ": index " + std::to_string(index) + " out of range (0 audio paths)"); + const auto found = state.objects.find(source); + if (found == state.objects.end() || found->second.kind != Kind::Source || !liveSystem(state, found->second.system)) return ErrorInvalidHandle; + if (!outPath) return ErrorNullPointer; + if (index >= MaxPaths) return ErrorInvalidArgument; + *outPath = state.sourcePaths.at(source)[index]; + return 0; +} + +std::int32_t APS5_VABI sceAudioPropagationPathGetNumPoints(AudioPropagationHandle path, std::uint32_t* count) { + auto& state = registry(); + std::lock_guard lock(state.mutex); + requireObject(state, path, Kind::Path, __func__); + require(count != nullptr, __func__); + *count = state.paths.at(path).data.pointCount; + return 0; } std::int32_t APS5_VABI sceAudioPropagationSourceGetRays(AudioPropagationHandle source, void* rays, std::uint32_t* count) { @@ -272,15 +353,14 @@ std::int32_t APS5_VABI sceAudioPropagationSourceGetRays(AudioPropagationHandle s return 0; } -std::int32_t APS5_VABI sceAudioPropagationSourceCalculateAudioPaths(AudioPropagationHandle source, const void* rays, std::uint32_t rayCount, std::uint32_t flags, void* paths, std::uint32_t pathCount) { +std::int32_t APS5_VABI sceAudioPropagationSourceCalculateAudioPaths(AudioPropagationHandle source, const void* rays, std::uint32_t rayCount, std::uint32_t reflectionOrder, void* paths, std::uint32_t pathCount) { auto& state = registry(); std::lock_guard lock(state.mutex); - requireObject(state, source, Kind::Source, __func__); - static_cast(rays); - static_cast(flags); - static_cast(paths); - requireNoPropagation(rayCount == 0 && pathCount == 0, __func__); - return 0; + const auto found = state.objects.find(source); + if (found == state.objects.end() || found->second.kind != Kind::Source || !liveSystem(state, found->second.system)) return ErrorInvalidHandle; + if (!rays || !paths) return ErrorNullPointer; + if (rayCount != 6 || reflectionOrder == 0 || pathCount == 0) return ErrorInvalidArgument; + throw std::runtime_error(__func__); } std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPaths(AudioPropagationHandle source, const void* entries, std::uint32_t count) { @@ -292,125 +372,100 @@ std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPaths(AudioPropagationHa return 0; } -std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPath(AudioPropagationHandle path, const void* data, float gain) { - static_cast(path); - static_cast(data); - static_cast(gain); - NotImplemented_nid_no_patch(__func__); +std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPath(AudioPropagationHandle path, const AudioPath* data, float gain) { + auto& state = registry(); + std::lock_guard lock(state.mutex); + const auto system = requireObject(state, path, Kind::Path, __func__).system; + requireDescriptor(data != nullptr ? &static_cast(data)->desc : nullptr, PathId, sizeof(AudioPath), __func__); + const auto replacement = *static_cast(data); + require(std::isfinite(gain) && gain >= 0.0f && replacement.pointCount <= MaxPathPoints && (replacement.flags & ~(PathInvalid | PathPortal)) == 0, __func__); + for (std::uint32_t index = 0; index < replacement.pointCount; ++index) { + const auto& point = replacement.points[index]; + requireDescriptor(&point.desc, PathPointId, sizeof(PathPoint), __func__); + require(std::isfinite(point.gain) && point.gain >= 0.0f && std::isfinite(point.position[0]) && std::isfinite(point.position[1]) && std::isfinite(point.position[2]), __func__); + if (point.room != 0) require(requireObject(state, point.room, Kind::Room, __func__).system == system, __func__); + } + state.paths.at(path) = PathState{replacement, gain}; return 0; } -std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle system, const RenderInfo* infos, std::uint32_t count) { +std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle system, RenderInfo* infos, std::uint32_t count) { require(infos != nullptr && count != 0, __func__); auto& state = registry(); std::lock_guard lock(state.mutex); requireSystem(state, system, __func__); + const auto& configuration = *reinterpret_cast(system); + require(count <= configuration.maxSources, __func__); for (std::uint32_t index = 0; index < count; ++index) { const auto& info = infos[index]; requireDescriptor(&info.desc, RenderInfoId, RenderInfoSize, __func__); require(requireObject(state, info.source, Kind::Source, __func__).system == system, __func__); require(info.output != nullptr && info.outputSize != 0, __func__); - if (info.format != RenderFormat) throw std::runtime_error(std::string(__func__) + ": render format " + std::to_string(info.format) + " is unknown"); + std::uint32_t channels = 0; + switch (info.format) { + case 0: channels = 4; break; + case 1: channels = 9; break; + case 2: channels = 16; break; + case 4: + case 5: channels = 8; break; + default: throw std::runtime_error(std::string(__func__) + ": render format " + std::to_string(info.format) + " is unknown"); + } + const auto requiredBytes = static_cast(configuration.frameSamples) * channels * sizeof(float); + require(info.outputSize >= requiredBytes && info.outputSize % (channels * sizeof(float)) == 0, __func__); + for (const auto handle : state.sourcePaths.at(info.source)) { + const auto& path = state.paths.at(handle); + if (path.gain > 0.0f && path.data.pointCount != 0 && (path.data.flags & PathInvalid) == 0) { + throw std::runtime_error(std::string(__func__) + ": nonempty audio path rendering is not implemented"); + } + } + } + for (std::uint32_t index = 0; index < count; ++index) { + infos[index].channelCount = 0; } return 0; } -int APS5_VABI sceAudioPropagationPathGetNumPoints() { +int APS5_VABI sceAudioPropagationReportApi(std::uint32_t category, AudioPropagationHandle handle, std::int32_t result, std::int32_t operation, const char* function, const char* format, ...) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAudioPropagationReportApi() { +int APS5_VABI sceAudioPropagationResetAttributes(Attribute* attributes, std::uint32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAudioPropagationResetAttributes() { +int APS5_VABI sceAudioPropagationSourceQueryInfo(AudioPropagationHandle source, SourceInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAudioPropagationSourceQueryInfo() { +int APS5_VABI sceAudioPropagationSystemLock(AudioPropagationHandle system) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAudioPropagationSystemLock() { +int APS5_VABI sceAudioPropagationSystemQueryInfo(AudioPropagationHandle system, SystemInfo* info, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAudioPropagationSystemQueryInfo() { +int APS5_VABI sceAudioPropagationReportRegisterHandler(std::uint32_t category, ReportHandler handler, void* userData, ReportRegistration** registration) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAudioPropagationAudioPathInit() { +int APS5_VABI sceAudioPropagationReportUnregisterHandler(ReportRegistration* registration) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAudioPropagationAudioPathPointInit() { +int APS5_VABI sceAudioPropagationSystemEnumHandles(AudioPropagationHandle* handles, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAudioPropagationMaterialInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationPortalSettingsInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationRayInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSourceRenderInfoInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSourceSetAudioPathsParamInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystem() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemMemoryInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemOptionInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationReportRegisterHandler() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationReportUnregisterHandler() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemEnumHandles() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemUnlock() { +int APS5_VABI sceAudioPropagationSystemUnlock(AudioPropagationHandle system) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAudioPropagation/include/Api.hpp b/core/libs/prx/libSceAudioPropagation/include/Api.hpp new file mode 100644 index 0000000000..131fe6b5cc --- /dev/null +++ b/core/libs/prx/libSceAudioPropagation/include/Api.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "prx/libSceAudioPropagation/AudioPropagation.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudioPropagationReportApi(std::uint32_t category, AudioPropagationHandle handle, std::int32_t result, std::int32_t operation, const char* function, const char* format, ...); +int APS5_VABI sceAudioPropagationResetAttributes(AudioPropagation::Attribute* attributes, std::uint32_t count); +int APS5_VABI sceAudioPropagationSourceQueryInfo(AudioPropagationHandle source, AudioPropagation::SourceInfo* info); +int APS5_VABI sceAudioPropagationSystemLock(AudioPropagationHandle system); +int APS5_VABI sceAudioPropagationSystemQueryInfo(AudioPropagationHandle system, AudioPropagation::SystemInfo* info, std::size_t size); +int APS5_VABI sceAudioPropagationReportRegisterHandler(std::uint32_t category, AudioPropagation::ReportHandler handler, void* userData, AudioPropagation::ReportRegistration** registration); +int APS5_VABI sceAudioPropagationReportUnregisterHandler(AudioPropagation::ReportRegistration* registration); +int APS5_VABI sceAudioPropagationSystemEnumHandles(AudioPropagationHandle* handles, std::uint32_t capacity); +int APS5_VABI sceAudioPropagationSystemUnlock(AudioPropagationHandle system); + +} diff --git a/core/libs/prx/libSceAudioPropagation/libSceAudioPropagation.hpp b/core/libs/prx/libSceAudioPropagation/libSceAudioPropagation.hpp new file mode 100644 index 0000000000..6431f41cde --- /dev/null +++ b/core/libs/prx/libSceAudioPropagation/libSceAudioPropagation.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "prx/libSceAudioPropagation/AudioPropagation.hpp" +#include "prx/libSceAudioPropagation/include/Api.hpp" diff --git a/core/libs/prx/libSceAudioSystem/Export.cpp b/core/libs/prx/libSceAudioSystem/Export.cpp index d5c4291d01..de11eb33dd 100644 --- a/core/libs/prx/libSceAudioSystem/Export.cpp +++ b/core/libs/prx/libSceAudioSystem/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioSystem/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAudioSystem/include/Api.hpp b/core/libs/prx/libSceAudioSystem/include/Api.hpp new file mode 100644 index 0000000000..c15ab7a627 --- /dev/null +++ b/core/libs/prx/libSceAudioSystem/include/Api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudioSystemSystemResume(); +int APS5_VABI sceAudioSystemSystemSuspend(); + +} diff --git a/core/libs/prx/libSceAudioSystem/libSceAudioSystem.hpp b/core/libs/prx/libSceAudioSystem/libSceAudioSystem.hpp new file mode 100644 index 0000000000..092b35b31a --- /dev/null +++ b/core/libs/prx/libSceAudioSystem/libSceAudioSystem.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAudioSystem/include/Api.hpp" diff --git a/core/libs/prx/libSceAudiodReport/libSceAudiodReport.hpp b/core/libs/prx/libSceAudiodReport/libSceAudiodReport.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodReport/libSceAudiodReport.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudiodec.native/Export.cpp b/core/libs/prx/libSceAudiodec.native/Export.cpp index 4f00015020..96acf5c292 100644 --- a/core/libs/prx/libSceAudiodec.native/Export.cpp +++ b/core/libs/prx/libSceAudiodec.native/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudiodec.native/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAudiodec.native/include/Api.hpp b/core/libs/prx/libSceAudiodec.native/include/Api.hpp new file mode 100644 index 0000000000..f5050e0019 --- /dev/null +++ b/core/libs/prx/libSceAudiodec.native/include/Api.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudiodecClearContextEx(void); +int APS5_VABI sceAudiodecCreateDecoderEx(void); +int APS5_VABI sceAudiodecDecode2(void); +int APS5_VABI sceAudiodecDecode2Ex(void); +int APS5_VABI sceAudiodecDecode2WithPriority(void); +int APS5_VABI sceAudiodecDecode2WithPriorityEx(void); +int APS5_VABI sceAudiodecDecodeEx(void); +int APS5_VABI sceAudiodecDecodeWithPriority(void); +int APS5_VABI sceAudiodecDecodeWithPriorityEx(void); +int APS5_VABI sceAudiodecDeleteDecoderEx(void); +int APS5_VABI sceAudiodecGetContext(void); +int APS5_VABI sceAudiodecInitialize(void); +int APS5_VABI sceAudiodecRegisterCodec(void); +int APS5_VABI sceAudiodecTerminate(void); + +} diff --git a/core/libs/prx/libSceAudiodec.native/libSceAudiodec.native.hpp b/core/libs/prx/libSceAudiodec.native/libSceAudiodec.native.hpp new file mode 100644 index 0000000000..2f7cfa515f --- /dev/null +++ b/core/libs/prx/libSceAudiodec.native/libSceAudiodec.native.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAudiodec.native/include/Api.hpp" diff --git a/core/libs/prx/libSceAudiodec/Export.cpp b/core/libs/prx/libSceAudiodec/Export.cpp index ffc1d6e1f7..4840fde2d0 100644 --- a/core/libs/prx/libSceAudiodec/Export.cpp +++ b/core/libs/prx/libSceAudiodec/Export.cpp @@ -1,6 +1,9 @@ +#include "prx/libSceAudiodec/include/Api.hpp" #include #include #include +#include +#include #include #include #include "SceTypes.hpp" @@ -88,7 +91,7 @@ std::int32_t validateCtrl(const AudiodecCtrl* ctrl, std::uint32_t codecType) { if (infoSize != sizeof(AudiodecMp3Info)) return ERROR_INVALID_BSI_INFO_SIZE; break; default: - if (paramSize < sizeof(AudiodecParamM4aac)) return ERROR_INVALID_PARAM_SIZE; + if (paramSize != sizeof(AudiodecParamM4aac) && paramSize != 28 && paramSize != 32) return ERROR_INVALID_PARAM_SIZE; if (infoSize != sizeof(AudiodecM4aacInfo)) return ERROR_INVALID_BSI_INFO_SIZE; break; } @@ -133,12 +136,20 @@ void fillMp3Info(AudiodecMp3Info* info, const Audiodec::Mp3Header& header) { } std::int32_t createAac(const AudiodecParamM4aac* param, AudiodecM4aacInfo* info, Instance& instance) { + std::uint32_t extensionMode = 0; + std::uint32_t outputMode = 0; + const auto* bytes = reinterpret_cast(param); + if (param->ui_size >= 28) std::memcpy(&extensionMode, bytes + 24, sizeof(extensionMode)); + if (extensionMode > 1) return static_cast(0x807F0305); + if (param->ui_size == 32) std::memcpy(&outputMode, bytes + 28, sizeof(outputMode)); + if (outputMode > 2) return static_cast(0x807F0306); + if (extensionMode != 0 || outputMode != 0) throw std::runtime_error("M4AAC extended decoding modes are not implemented"); if (param->ui_config_number != M4AAC_CONFIG_ADTS && param->ui_config_number != M4AAC_CONFIG_RAW) return ERROR_M4AAC_INVALID_CONFIG_NUMBER; const std::uint32_t sampleRate = Audiodec::AacSampleRate(param->ui_sampling_freq_index); if (param->ui_config_number == M4AAC_CONFIG_RAW && sampleRate == 0) return ERROR_M4AAC_INVALID_SAMPLING_FREQ; - if (param->ui_max_channels > 8) return ERROR_M4AAC_INVALID_MAX_CHANNELS; - if (param->ui_enable_heaac > 1) return ERROR_M4AAC_INVALID_ENABLE_HEAAC; - const std::uint32_t channels = param->ui_max_channels == 0 ? 2 : param->ui_max_channels; + if (param->ui_max_channels != 1 && param->ui_max_channels != 2 && param->ui_max_channels != 6 && param->ui_max_channels != 8) return ERROR_M4AAC_INVALID_MAX_CHANNELS; + if (param->ui_enable_heaac > 1 || (param->ui_enable_heaac == 1 && param->ui_max_channels > 2)) return ERROR_M4AAC_INVALID_ENABLE_HEAAC; + const std::uint32_t channels = param->ui_max_channels; instance.decoder = Audiodec::CreateAac(param->i_bw_pcm, param->ui_config_number == M4AAC_CONFIG_ADTS, param->ui_sampling_freq_index, channels, param->ui_enable_heaac == 1); instance.channels = channels; instance.sampleRate = sampleRate; diff --git a/core/libs/prx/libSceAudiodec/include/Api.hpp b/core/libs/prx/libSceAudiodec/include/Api.hpp new file mode 100644 index 0000000000..e02050acef --- /dev/null +++ b/core/libs/prx/libSceAudiodec/include/Api.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int32_t APS5_VABI sceAudiodecInitLibrary(uint32_t codecType); +int32_t APS5_VABI sceAudiodecTermLibrary(uint32_t codecType); +int32_t APS5_VABI sceAudiodecCreateDecoder(AudiodecCtrl* ctrl, uint32_t codecType); +int32_t APS5_VABI sceAudiodecDeleteDecoder(int32_t handle); +int32_t APS5_VABI sceAudiodecClearContext(int32_t handle); +int32_t APS5_VABI sceAudiodecDecode(int32_t handle, AudiodecCtrl* ctrl); + +} diff --git a/core/libs/prx/libSceAudiodec/libSceAudiodec.hpp b/core/libs/prx/libSceAudiodec/libSceAudiodec.hpp new file mode 100644 index 0000000000..b8ec4f574f --- /dev/null +++ b/core/libs/prx/libSceAudiodec/libSceAudiodec.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAudiodec/include/Api.hpp" diff --git a/core/libs/prx/libSceAudiodecCpu/Export.cpp b/core/libs/prx/libSceAudiodecCpu/Export.cpp index c0c2f83744..e4ac1ea15b 100644 --- a/core/libs/prx/libSceAudiodecCpu/Export.cpp +++ b/core/libs/prx/libSceAudiodecCpu/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudiodecCpu/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAudiodecCpu/include/Api.hpp b/core/libs/prx/libSceAudiodecCpu/include/Api.hpp new file mode 100644 index 0000000000..336933274e --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpu/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudiodecCpuClearContext(void); +int APS5_VABI sceAudiodecCpuDecode(void); +int APS5_VABI sceAudiodecCpuInitDecoder(void); +int APS5_VABI sceAudiodecCpuInternalClearContext(void); +int APS5_VABI sceAudiodecCpuInternalDecode(void); +int APS5_VABI sceAudiodecCpuInternalInitDecoder(void); +int APS5_VABI sceAudiodecCpuInternalQueryMemSize(void); +int APS5_VABI sceAudiodecCpuQueryMemSize(void); + +} diff --git a/core/libs/prx/libSceAudiodecCpu/libSceAudiodecCpu.hpp b/core/libs/prx/libSceAudiodecCpu/libSceAudiodecCpu.hpp new file mode 100644 index 0000000000..e896a52007 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpu/libSceAudiodecCpu.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAudiodecCpu/include/Api.hpp" diff --git a/core/libs/prx/libSceAudiodecCpuAlac/libSceAudiodecCpuAlac.hpp b/core/libs/prx/libSceAudiodecCpuAlac/libSceAudiodecCpuAlac.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuAlac/libSceAudiodecCpuAlac.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudiodecCpuDdp/libSceAudiodecCpuDdp.hpp b/core/libs/prx/libSceAudiodecCpuDdp/libSceAudiodecCpuDdp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDdp/libSceAudiodecCpuDdp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudiodecCpuDts/libSceAudiodecCpuDts.hpp b/core/libs/prx/libSceAudiodecCpuDts/libSceAudiodecCpuDts.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDts/libSceAudiodecCpuDts.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudiodecCpuDtsHdLbr/libSceAudiodecCpuDtsHdLbr.hpp b/core/libs/prx/libSceAudiodecCpuDtsHdLbr/libSceAudiodecCpuDtsHdLbr.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDtsHdLbr/libSceAudiodecCpuDtsHdLbr.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudiodecCpuDtsHdMa/libSceAudiodecCpuDtsHdMa.hpp b/core/libs/prx/libSceAudiodecCpuDtsHdMa/libSceAudiodecCpuDtsHdMa.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDtsHdMa/libSceAudiodecCpuDtsHdMa.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudiodecCpuFlac/libSceAudiodecCpuFlac.hpp b/core/libs/prx/libSceAudiodecCpuFlac/libSceAudiodecCpuFlac.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuFlac/libSceAudiodecCpuFlac.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudiodecCpuLpcm/libSceAudiodecCpuLpcm.hpp b/core/libs/prx/libSceAudiodecCpuLpcm/libSceAudiodecCpuLpcm.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuLpcm/libSceAudiodecCpuLpcm.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudiodecCpuM4aac/libSceAudiodecCpuM4aac.hpp b/core/libs/prx/libSceAudiodecCpuM4aac/libSceAudiodecCpuM4aac.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuM4aac/libSceAudiodecCpuM4aac.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAudiodecCpuTrhd/libSceAudiodecCpuTrhd.hpp b/core/libs/prx/libSceAudiodecCpuTrhd/libSceAudiodecCpuTrhd.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuTrhd/libSceAudiodecCpuTrhd.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAutoMounterClient/Export.cpp b/core/libs/prx/libSceAutoMounterClient/Export.cpp index 3b3e49adb8..ab7eed9165 100644 --- a/core/libs/prx/libSceAutoMounterClient/Export.cpp +++ b/core/libs/prx/libSceAutoMounterClient/Export.cpp @@ -1,19 +1,28 @@ +#include "prx/libSceAutoMounterClient/include/Api.hpp" #include #include #include "SceTypes.hpp" #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); + extern "C" { -int APS5_VABI sceAutoMounterClientGetUsbDeviceInfo() { +int APS5_VABI sceAutoMounterClientGetUsbDeviceInfo(const char* deviceName, AutoMounterUsbDeviceInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAutoMounterClientGetUsbDeviceList() { +int APS5_VABI sceAutoMounterClientGetUsbDeviceList(char (*deviceNames)[64], std::int32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAutoMounterClientInit() { +int APS5_VABI sceAutoMounterClientInit(PthreadAttr* threadAttr) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAutoMounterClient/include/Api.hpp b/core/libs/prx/libSceAutoMounterClient/include/Api.hpp new file mode 100644 index 0000000000..766f754f18 --- /dev/null +++ b/core/libs/prx/libSceAutoMounterClient/include/Api.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "SceTypes.hpp" + +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; +}; + +extern "C" { + +int APS5_VABI sceAutoMounterClientGetUsbDeviceInfo(const char* deviceName, AutoMounterUsbDeviceInfo* info); +int APS5_VABI sceAutoMounterClientGetUsbDeviceList(char (*deviceNames)[64], std::int32_t* count); +int APS5_VABI sceAutoMounterClientInit(PthreadAttr* threadAttr); +int APS5_VABI sceAutoMounterClientRegisterCallback(); +int APS5_VABI sceAutoMounterClientTerm(); +int APS5_VABI sceAutoMounterClientUnregisterCallback(); + +} diff --git a/core/libs/prx/libSceAutoMounterClient/libSceAutoMounterClient.hpp b/core/libs/prx/libSceAutoMounterClient/libSceAutoMounterClient.hpp new file mode 100644 index 0000000000..dd1e89097a --- /dev/null +++ b/core/libs/prx/libSceAutoMounterClient/libSceAutoMounterClient.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAutoMounterClient/include/Api.hpp" diff --git a/core/libs/prx/libSceAvPlayer.native/Export.cpp b/core/libs/prx/libSceAvPlayer.native/Export.cpp index 2584dda091..3834e0cfb5 100644 --- a/core/libs/prx/libSceAvPlayer.native/Export.cpp +++ b/core/libs/prx/libSceAvPlayer.native/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAvPlayer.native/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAvPlayer.native/include/Api.hpp b/core/libs/prx/libSceAvPlayer.native/include/Api.hpp new file mode 100644 index 0000000000..bb1e1f40dc --- /dev/null +++ b/core/libs/prx/libSceAvPlayer.native/include/Api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAvPlayerPrintf(void); +int APS5_VABI sceAvPlayerVprintf(void); + +} diff --git a/core/libs/prx/libSceAvPlayer.native/libSceAvPlayer.native.hpp b/core/libs/prx/libSceAvPlayer.native/libSceAvPlayer.native.hpp new file mode 100644 index 0000000000..589a07f273 --- /dev/null +++ b/core/libs/prx/libSceAvPlayer.native/libSceAvPlayer.native.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAvPlayer.native/include/Api.hpp" diff --git a/core/libs/prx/libSceAvPlayer/Export.cpp b/core/libs/prx/libSceAvPlayer/Export.cpp index 2cdb89887d..be3c03f01e 100644 --- a/core/libs/prx/libSceAvPlayer/Export.cpp +++ b/core/libs/prx/libSceAvPlayer/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAvPlayer/include/Api.hpp" // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -151,7 +152,7 @@ int APS5_VABI sceAvPlayerSetAvailableBandwidth(AvPlayerInternal* h, uint32_t sta (void)minimum_bandwidth; (void)maximum_bandwidth; if (!h) return SCE_AVPLAYER_ERROR_INVALID_PARAMS; - return SCE_OK; + return ToPlayer(h)->SetAvailableBandwidth(); } int APS5_VABI sceAvPlayerSetLogCallback(void* callback, void* user_data) { @@ -175,10 +176,9 @@ int APS5_VABI sceAvPlayerStart(AvPlayerInternal* h) { return ToPlayer(h)->Start(); } -int APS5_VABI sceAvPlayerStartEx(AvPlayerInternal* h, const void* start_info_ex) { - (void)start_info_ex; - if (!h) return SCE_AVPLAYER_ERROR_INVALID_PARAMS; - return ToPlayer(h)->Start(); +int APS5_VABI sceAvPlayerStartEx(AvPlayerInternal* handle, const StartInfo* startInfo) { + if (handle == nullptr || startInfo == nullptr || startInfo->size != sizeof(*startInfo)) return SCE_AVPLAYER_ERROR_INVALID_PARAMS; + return ToPlayer(handle)->Start(startInfo->startTime); } int APS5_VABI sceAvPlayerStop(AvPlayerInternal* h) { @@ -199,5 +199,28 @@ void SetAvPlayerClockForTest_nid_no_patch(AvPlayerInternal* h, double millisecon if (h) ToPlayer(h)->SetClockTimeForTest(milliseconds); } +void HoldAvPlayerAudioEofForTest_nid_no_patch(AvPlayerInternal* h, bool hold) { + if (h) ToPlayer(h)->SetHoldAudioEofForTest(hold); +} + +bool AvPlayerAudioEofHeldForTest_nid_no_patch(AvPlayerInternal* h) { + return h && ToPlayer(h)->AudioEofHeldForTest(); +} + +bool AvPlayerAudioEndedForTest_nid_no_patch(AvPlayerInternal* h) { + return h && ToPlayer(h)->AudioEndedForTest(); +} + +int QueuedAvPlayerAudioForTest_nid_no_patch(AvPlayerInternal* h) { + return h ? ToPlayer(h)->QueuedAudioForTest() : 0; +} + +bool NextQueuedAvPlayerAudioTimestampForTest_nid_no_patch(AvPlayerInternal* h, std::uint64_t* timestamp) { + std::uint64_t value = 0; + if (!h || !timestamp || !ToPlayer(h)->NextQueuedAudioTimestampForTest(value)) return false; + *timestamp = value; + return true; +} + } #pragma GCC visibility pop diff --git a/core/libs/prx/libSceAvPlayer/include/Api.hpp b/core/libs/prx/libSceAvPlayer/include/Api.hpp new file mode 100644 index 0000000000..ac8eb6cf31 --- /dev/null +++ b/core/libs/prx/libSceAvPlayer/include/Api.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "prx/libSceAvPlayer/include/AvPlayer.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { + +int APS5_VABI sceAvPlayerAddSource(AvPlayerInternal* h, const char* filename); +int APS5_VABI sceAvPlayerAddSourceEx(AvPlayerInternal* h, uint32_t uriType, const AvPlayerSourceDetails* sourceDetails); +int APS5_VABI sceAvPlayerChangeStream(AvPlayerInternal* h, uint32_t oldStreamId, uint32_t newStreamId); +int APS5_VABI sceAvPlayerClose(AvPlayerInternal* h); +uint64_t APS5_VABI sceAvPlayerCurrentTime(AvPlayerInternal* h); +int APS5_VABI sceAvPlayerDisableStream(AvPlayerInternal* h, uint32_t streamId); +int APS5_VABI sceAvPlayerEnableStream(AvPlayerInternal* h, uint32_t streamId); +Bool APS5_VABI sceAvPlayerGetAudioData(AvPlayerInternal* h, AvPlayerFrameInfo* audioInfo); +int APS5_VABI sceAvPlayerGetStreamInfo(AvPlayerInternal* h, uint32_t streamId, AvPlayerStreamInfo* info); +int APS5_VABI sceAvPlayerGetStreamInfoEx(AvPlayerInternal* h, uint32_t streamId, AvPlayerStreamInfoEx* info); +Bool APS5_VABI sceAvPlayerGetVideoData(AvPlayerInternal* h, AvPlayerFrameInfo* videoInfo); +Bool APS5_VABI sceAvPlayerGetVideoDataEx(AvPlayerInternal* h, AvPlayerFrameInfoEx* videoInfo); +AvPlayerInternal* APS5_VABI sceAvPlayerInit(AvPlayerInitData* init); +int APS5_VABI sceAvPlayerInitEx(const AvPlayerInitDataEx* initEx, AvPlayerInternal** handle); +Bool APS5_VABI sceAvPlayerIsActive(AvPlayerInternal* h); +int APS5_VABI sceAvPlayerJumpToTime(AvPlayerInternal* h, uint64_t timeMs); +int APS5_VABI sceAvPlayerPause(AvPlayerInternal* h); +int APS5_VABI sceAvPlayerPostInit(AvPlayerInternal* h, const AvPlayerPostInitData* postInit); +int APS5_VABI sceAvPlayerResume(AvPlayerInternal* h); +int APS5_VABI sceAvPlayerSetAvSyncMode(AvPlayerInternal* h, uint32_t syncMode); +int APS5_VABI sceAvPlayerSetAvailableBandwidth(AvPlayerInternal* h, uint32_t startBandwidth, uint32_t minimumBandwidth, uint32_t maximumBandwidth); +int APS5_VABI sceAvPlayerSetLogCallback(void* callback, void* userData); +int APS5_VABI sceAvPlayerSetLooping(AvPlayerInternal* h, Bool loop); +int APS5_VABI sceAvPlayerSetTrickSpeed(AvPlayerInternal* h, int32_t trickSpeed); +int APS5_VABI sceAvPlayerStart(AvPlayerInternal* h); +int APS5_VABI sceAvPlayerStartEx(AvPlayerInternal* handle, const AvPlayer::StartInfo* startInfo); +int APS5_VABI sceAvPlayerStop(AvPlayerInternal* h); +int APS5_VABI sceAvPlayerStreamCount(AvPlayerInternal* h); + +} diff --git a/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp b/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp index 49bf029391..d81882a932 100644 --- a/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp +++ b/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp @@ -51,6 +51,13 @@ public: virtual void OnError() = 0; }; +struct StartInfo { + std::uint64_t size; + std::uint64_t startTime; +}; + +static_assert(sizeof(StartInfo) == 16); + struct SourceSettings { AvPlayerMemAllocator memory; AvPlayerFileReplacement file; @@ -66,7 +73,7 @@ public: virtual bool EnableStream(std::uint32_t index) = 0; virtual bool DisableStream(std::uint32_t index) = 0; virtual bool ChangeStream(std::uint32_t from, std::uint32_t to) = 0; - virtual int Start() = 0; + virtual int Start(std::uint64_t startTime) = 0; virtual void Stop() = 0; virtual void Pause() = 0; virtual void Resume() = 0; @@ -80,6 +87,11 @@ public: virtual std::uint64_t CurrentTime() = 0; virtual bool Finished() = 0; virtual void SetClockTimeForTest(double milliseconds) = 0; + virtual void SetHoldAudioEofForTest(bool hold) = 0; + virtual bool AudioEofHeldForTest() = 0; + virtual bool AudioEndedForTest() = 0; + virtual int QueuedAudioForTest() = 0; + virtual bool NextQueuedAudioTimestampForTest(std::uint64_t& timestamp) = 0; }; std::unique_ptr OpenSource(const SourceSettings& settings, const std::string& path, ISourceEvents& events); @@ -87,6 +99,11 @@ std::unique_ptr OpenSource(const SourceSettings& settings, const std::s std::uint32_t DetectSourceType(std::string_view path); void SetAvPlayerClockForTest_nid_no_patch(AvPlayerInternal* h, double milliseconds); +void HoldAvPlayerAudioEofForTest_nid_no_patch(AvPlayerInternal* h, bool hold); +bool AvPlayerAudioEofHeldForTest_nid_no_patch(AvPlayerInternal* h); +bool AvPlayerAudioEndedForTest_nid_no_patch(AvPlayerInternal* h); +int QueuedAvPlayerAudioForTest_nid_no_patch(AvPlayerInternal* h); +bool NextQueuedAvPlayerAudioTimestampForTest_nid_no_patch(AvPlayerInternal* h, std::uint64_t* timestamp); class Player final : public AvPlayerInternal, private ISourceEvents { public: @@ -103,13 +120,14 @@ public: int EnableStream(std::uint32_t index); int DisableStream(std::uint32_t index); int ChangeStream(std::uint32_t from, std::uint32_t to); - int Start(); + int Start(std::uint64_t startTime = 0); int Stop(); int Pause(); int Resume(); int JumpToTime(std::uint64_t milliseconds); int SetLooping(bool enabled); int SetTrickSpeed(std::int32_t trickSpeed); + int SetAvailableBandwidth(); int SetAvSyncMode(std::uint32_t mode); bool GetVideoData(AvPlayerFrameInfoEx& info); bool GetVideoData(AvPlayerFrameInfo& info); @@ -117,6 +135,11 @@ public: bool IsActive(); std::uint64_t CurrentTime(); void SetClockTimeForTest(double milliseconds); + void SetHoldAudioEofForTest(bool hold); + bool AudioEofHeldForTest(); + bool AudioEndedForTest(); + int QueuedAudioForTest(); + bool NextQueuedAudioTimestampForTest(std::uint64_t& timestamp); private: enum class State { Initial, Ready, Play, Pause, Stop, EndOfFile, Error }; @@ -136,7 +159,7 @@ private: void autoStart(); void checkEndOfFile(); bool readyLocked() const; - int startLocked(); + int startLocked(std::uint64_t startTime = 0); int stopLocked(); AvPlayerFileReplacement file{}; diff --git a/core/libs/prx/libSceAvPlayer/libSceAvPlayer.hpp b/core/libs/prx/libSceAvPlayer/libSceAvPlayer.hpp new file mode 100644 index 0000000000..9a78810578 --- /dev/null +++ b/core/libs/prx/libSceAvPlayer/libSceAvPlayer.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAvPlayer/include/Api.hpp" diff --git a/core/libs/prx/libSceAvPlayer/src/Player.cpp b/core/libs/prx/libSceAvPlayer/src/Player.cpp index 191a185847..3f928b7ea8 100644 --- a/core/libs/prx/libSceAvPlayer/src/Player.cpp +++ b/core/libs/prx/libSceAvPlayer/src/Player.cpp @@ -265,15 +265,15 @@ int Player::ChangeStream(std::uint32_t from, std::uint32_t to) { return SCE_OK; } -int Player::Start() { +int Player::Start(std::uint64_t startTime) { std::lock_guard lock(mutex); - return startLocked(); + return startLocked(startTime); } -int Player::startLocked() { +int Player::startLocked(std::uint64_t startTime) { if (!source) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; if (state != State::Ready && state != State::Stop && stopLocked() != SCE_OK) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; - const int result = source->Start(); + const int result = source->Start(startTime); if (result != SCE_OK) { previous = state; state = State::Error; @@ -334,14 +334,20 @@ int Player::SetLooping(bool enabled) { } int Player::SetTrickSpeed(std::int32_t trickSpeed) { - if (trickSpeed == 0) return SCE_AVPLAYER_ERROR_INVALID_PARAMS; std::lock_guard lock(mutex); + if (!source) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; + if (trickSpeed == 0) return SCE_AVPLAYER_ERROR_INVALID_PARAMS; if (state == State::Stop || state == State::EndOfFile || state == State::Error) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; speed = trickSpeed; - if (source) source->SetSpeed(trickSpeed); + source->SetSpeed(trickSpeed); return SCE_OK; } +int Player::SetAvailableBandwidth() { + std::lock_guard lock(mutex); + return source ? SCE_AVPLAYER_ERROR_OPERATION_FAILED : SCE_OK; +} + int Player::SetAvSyncMode(std::uint32_t mode) { if (mode != SyncModeDefault && mode != SyncModeNone) return SCE_AVPLAYER_ERROR_INVALID_PARAMS; std::lock_guard lock(mutex); @@ -391,4 +397,29 @@ void Player::SetClockTimeForTest(double milliseconds) { if (source) source->SetClockTimeForTest(milliseconds); } +void Player::SetHoldAudioEofForTest(bool hold) { + std::lock_guard lock(mutex); + if (source) source->SetHoldAudioEofForTest(hold); +} + +bool Player::AudioEofHeldForTest() { + std::lock_guard lock(mutex); + return source && source->AudioEofHeldForTest(); +} + +bool Player::AudioEndedForTest() { + std::lock_guard lock(mutex); + return source && source->AudioEndedForTest(); +} + +int Player::QueuedAudioForTest() { + std::lock_guard lock(mutex); + return source ? source->QueuedAudioForTest() : 0; +} + +bool Player::NextQueuedAudioTimestampForTest(std::uint64_t& timestamp) { + std::lock_guard lock(mutex); + return source && source->NextQueuedAudioTimestampForTest(timestamp); +} + } diff --git a/core/libs/prx/libSceAvPlayer/src/Source.cpp b/core/libs/prx/libSceAvPlayer/src/Source.cpp index 70f7e7fd31..452e4a88c8 100644 --- a/core/libs/prx/libSceAvPlayer/src/Source.cpp +++ b/core/libs/prx/libSceAvPlayer/src/Source.cpp @@ -151,6 +151,7 @@ public: base = milliseconds; wall = std::chrono::steady_clock::now(); running = true; + if (overrideTime >= 0.0) overrideTime = milliseconds; } void Reset(double milliseconds) { @@ -327,7 +328,8 @@ public: return true; } - int Start() override { + int Start(std::uint64_t startTime) override { + if (duration != 0 && startTime > duration) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; if (video.stream < 0 && audio.stream < 0) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; for (auto* decoder : {&video, &audio}) { if (decoder->stream < 0) continue; @@ -338,7 +340,7 @@ public: } } addVideoDecodeAheadBuffers(); - const auto start = startOffset; + const auto start = startTime != 0 ? startTime : startOffset; startOffset = 0; seek(start); { @@ -353,6 +355,9 @@ public: audioDriving = false; presented = false; lastPresented = 0; + audioHasTimestamp = false; + lastAudioTimestamp = 0.0; + reconciledAudioClock = false; presentationClock.Reset(static_cast(start)); for (auto* decoder : {&video, &audio}) { decoder->switchTo = -1; @@ -443,6 +448,34 @@ public: presentationClock.SetOverrideForTest(milliseconds); } + void SetHoldAudioEofForTest(bool hold) override { + std::lock_guard lock(mutex); + holdAudioEof = hold; + condition.notify_all(); + } + + bool AudioEofHeldForTest() override { + std::lock_guard lock(mutex); + return audioEofHeld; + } + + bool AudioEndedForTest() override { + std::lock_guard lock(mutex); + return audio.stream >= 0 && audio.ended; + } + + int QueuedAudioForTest() override { + std::lock_guard lock(mutex); + return static_cast(audio.frames.size()); + } + + bool NextQueuedAudioTimestampForTest(std::uint64_t& timestamp) override { + std::lock_guard lock(mutex); + if (audio.frames.empty()) return false; + timestamp = audio.frames.front().info.timestamp; + return true; + } + bool GetVideoData(AvPlayerFrameInfoEx& info) override { bool taken = false; { @@ -474,6 +507,7 @@ public: std::uint64_t CurrentTime() override { std::lock_guard lock(mutex); + reconcileExhaustedAudioClock(); return clockMillis(); } @@ -509,6 +543,34 @@ private: return duration != 0 ? std::min(milliseconds, duration) : milliseconds; } + bool audioExhausted() const { + return audioHasTimestamp && demuxEnded && !reposition && audio.ended && audio.packets.empty() && audio.frames.empty(); + } + + bool audioClockReconciled() const { + return reconciledAudioClock && reconciledAudioEpoch == clockEpoch; + } + + void markAudioClockReconciled() { + reconciledAudioClock = true; + reconciledAudioEpoch = clockEpoch; + } + + void reconcileExhaustedAudioClock() { + if (!audioDriving || !audioExhausted() || audioClockReconciled()) return; + presentationClock.Rebase(lastAudioTimestamp); + markAudioClockReconciled(); + } + + void waitBeforeAudioEof() { + std::unique_lock lock(mutex); + if (!holdAudioEof || stopping) return; + audioEofHeld = true; + condition.notify_all(); + condition.wait(lock, [&] { return stopping || !holdAudioEof; }); + audioEofHeld = false; + } + bool takeVideo(AvPlayerFrameInfoEx& info) { if (!started || video.stream < 0 || speed < 0) return false; if (speed != NormalSpeed) dropLateAudio(); @@ -541,12 +603,23 @@ private: if (!started || paused || audio.stream < 0 || speed != NormalSpeed) return false; auto& frames = audio.frames; while (!frames.empty() && frames.front().epoch < minEpoch) recycleFront(audio); - if (frames.empty()) return false; + if (frames.empty()) { + reconcileExhaustedAudioClock(); + return false; + } const auto epoch = frames.front().epoch; present(audio, info); + const auto currentClock = presentationClock.Now(); + const auto timestamp = static_cast(info.timestamp); + lastAudioTimestamp = timestamp; + audioHasTimestamp = true; + const bool drifted = currentClock > timestamp + 50.0 || timestamp > currentClock + 50.0; + const bool exhausted = audioExhausted() && !audioClockReconciled(); + const bool shouldRebase = !audioDriving || epoch > clockEpoch || drifted || exhausted; audioDriving = true; clockEpoch = std::max(clockEpoch, epoch); - presentationClock.Rebase(static_cast(info.timestamp)); + if (shouldRebase) presentationClock.Rebase(timestamp); + if (exhausted) markAudioClockReconciled(); return true; } @@ -794,6 +867,7 @@ private: pushAll(PacketKind::Flush, demuxEpoch, requested->target); demuxEnded = false; audioDriving = false; + reconciledAudioClock = false; } condition.notify_all(); if (requested->announce) events.OnWarning(SCE_AVPLAYER_ERROR_WAR_JUMP_COMPLETE); @@ -888,9 +962,11 @@ private: receiveFrames(decoder, frame); avcodec_flush_buffers(decoder.context); } + if (item.kind == PacketKind::End && !decoder.video) waitBeforeAudioEof(); std::lock_guard lock(mutex); if (item.kind == PacketKind::End) { decoder.ended = true; + if (!decoder.video) reconcileExhaustedAudioClock(); } else { decoder.epoch = item.epoch; decoder.skipBefore = 0; @@ -1097,6 +1173,12 @@ private: std::uint64_t clockEpoch = 0; std::deque loopEpochs; bool audioDriving = false; + bool audioHasTimestamp = false; + double lastAudioTimestamp = 0.0; + bool reconciledAudioClock = false; + std::uint64_t reconciledAudioEpoch = 0; + bool holdAudioEof = false; + bool audioEofHeld = false; bool presented = false; std::uint64_t lastPresented = 0; Clock presentationClock; diff --git a/core/libs/prx/libSceAvPlayerStreaming/Export.cpp b/core/libs/prx/libSceAvPlayerStreaming/Export.cpp index b484bc1af2..ba81d51df8 100644 --- a/core/libs/prx/libSceAvPlayerStreaming/Export.cpp +++ b/core/libs/prx/libSceAvPlayerStreaming/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAvPlayerStreaming/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAvPlayerStreaming/include/Api.hpp b/core/libs/prx/libSceAvPlayerStreaming/include/Api.hpp new file mode 100644 index 0000000000..0fcaf246ad --- /dev/null +++ b/core/libs/prx/libSceAvPlayerStreaming/include/Api.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI MvpHttpAbort_nid_postfix(void); +int APS5_VABI MvpHttpDeInit_nid_postfix(void); +int APS5_VABI MvpHttpGet_nid_postfix(void); +int APS5_VABI MvpHttpGetLastError_nid_postfix(void); +int APS5_VABI MvpHttpHead_nid_postfix(void); +int APS5_VABI MvpHttpHeadCleanup_nid_postfix(void); +int APS5_VABI MvpHttpInit_nid_postfix(void); +int APS5_VABI MvpHttpPost_nid_postfix(void); +int APS5_VABI MvpHttpSetCert_nid_postfix(void); +int APS5_VABI MvpHttpSetRecvTimeout_nid_postfix(void); +int APS5_VABI MvpHttpSetRedirectCb_nid_postfix(void); +int APS5_VABI sceTsCloseFile(void); +int APS5_VABI sceTsDisableRepresentation(void); +int APS5_VABI sceTsEnableRepresentation(void); +int APS5_VABI sceTsEnableStream(void); +int APS5_VABI sceTsGetAvailableBW(void); +int APS5_VABI sceTsGetLiveStatus(void); +int APS5_VABI sceTsGetNextAuInfo(void); +int APS5_VABI sceTsGetNextUnitData(void); +int APS5_VABI sceTsGetRepresentationCount(void); +int APS5_VABI sceTsGetRepresentationInfo(void); +int APS5_VABI sceTsGetSeekWindow(void); +int APS5_VABI sceTsGetStreamInfo(void); +int APS5_VABI sceTsGetUrlEncryptionType(void); +int APS5_VABI sceTsJumpPTS(void); +int APS5_VABI sceTsOpenFile(void); +int APS5_VABI sceTsOpenFileEx(void); +int APS5_VABI sceTsReleaseBuffer(void); +int APS5_VABI sceTsRepresentationIsEnabled(void); +int APS5_VABI sceTsSetAvailableBW(void); +int APS5_VABI sceTsSetStartingBW(void); +int APS5_VABI sceTsStartFileStreaming(void); +int APS5_VABI sceTsStopFileStreaming(void); + +} diff --git a/core/libs/prx/libSceAvPlayerStreaming/libSceAvPlayerStreaming.hpp b/core/libs/prx/libSceAvPlayerStreaming/libSceAvPlayerStreaming.hpp new file mode 100644 index 0000000000..b6ecf8bfa3 --- /dev/null +++ b/core/libs/prx/libSceAvPlayerStreaming/libSceAvPlayerStreaming.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAvPlayerStreaming/include/Api.hpp" diff --git a/core/libs/prx/libSceAvSetting/Export.cpp b/core/libs/prx/libSceAvSetting/Export.cpp index e53788bea3..1861a92646 100644 --- a/core/libs/prx/libSceAvSetting/Export.cpp +++ b/core/libs/prx/libSceAvSetting/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAvSetting/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceAvSetting/include/Api.hpp b/core/libs/prx/libSceAvSetting/include/Api.hpp new file mode 100644 index 0000000000..6ce470cc12 --- /dev/null +++ b/core/libs/prx/libSceAvSetting/include/Api.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAvControlGetPuTypeByMonitorInfo(void); +int APS5_VABI sceAvControlIsVideoOutModeSupportedByMonitorInfoWithOptions(void); +int APS5_VABI sceAvControlSetDitherModeForDiag(void); +int APS5_VABI sceAvControlSetLayoutModeForDiag(void); +int APS5_VABI sceAvControlSetMonitorFakeMode(void); +int APS5_VABI sceAvControlGetCurrentOutputSysMode(void); +int APS5_VABI sceAvControlGetNativeHdmiMonitorInfo(void); +int APS5_VABI sceAvControlGetRawMonitorInfo(void); +int APS5_VABI sceAvControlGetKsvList(void); +int APS5_VABI sceAvControlGetRawEdid(void); +int APS5_VABI sceAvControlRemoveProcessOutputMode(void); +int APS5_VABI sceAvControlUpdateDriverStatus(void); +int APS5_VABI sceAvControlNotifyUmdEvent(void); +int APS5_VABI sceAvControlAddCallbacksForLnc(void); +int APS5_VABI sceAvControlAddCallbacksForResourceArbitrator(void); +int APS5_VABI sceAvControlRemoveCallbacks(void); +int APS5_VABI sceAvControlCheckCallback(void); +int APS5_VABI sceAvControlWaitCallback(void); +int APS5_VABI sceAvControlSetColorMode(void); +int APS5_VABI sceAvControlCheckMonitorInfoCapability(void); +int APS5_VABI sceAvControlCheckVideoOutMode(void); +int APS5_VABI sceAvControlSetCheckoutputMode(void); +int APS5_VABI sceAvControlSetSystemVideoMode(void); +int APS5_VABI sceAvControlDebugAddCallbacks(void); + +} diff --git a/core/libs/prx/libSceAvSetting/libSceAvSetting.hpp b/core/libs/prx/libSceAvSetting/libSceAvSetting.hpp new file mode 100644 index 0000000000..76b2b29473 --- /dev/null +++ b/core/libs/prx/libSceAvSetting/libSceAvSetting.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceAvSetting/include/Api.hpp" diff --git a/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp b/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceAvcap2mvr/libSceAvcap2mvr.hpp b/core/libs/prx/libSceAvcap2mvr/libSceAvcap2mvr.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceAvcap2mvr/libSceAvcap2mvr.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceBackupRestoreUtil/Export.cpp b/core/libs/prx/libSceBackupRestoreUtil/Export.cpp index 77695475b9..a737e30ad8 100644 --- a/core/libs/prx/libSceBackupRestoreUtil/Export.cpp +++ b/core/libs/prx/libSceBackupRestoreUtil/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceBackupRestoreUtil/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceBackupRestoreUtil/include/Api.hpp b/core/libs/prx/libSceBackupRestoreUtil/include/Api.hpp new file mode 100644 index 0000000000..7851d2c315 --- /dev/null +++ b/core/libs/prx/libSceBackupRestoreUtil/include/Api.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceBackupRestoreUtilBackupData(); +int APS5_VABI sceBackupRestoreUtilCancelBackupData(); +int APS5_VABI sceBackupRestoreUtilCancelGetRequiredSize(); +int APS5_VABI sceBackupRestoreUtilCancelGetTitles(); +int APS5_VABI sceBackupRestoreUtilCancelRestoreData(); +int APS5_VABI sceBackupRestoreUtilDeleteBackupData(); +int APS5_VABI sceBackupRestoreUtilGetBackupDataInfo(); +int APS5_VABI sceBackupRestoreUtilGetBackupParam(); +int APS5_VABI sceBackupRestoreUtilGetBackupProgress(); +int APS5_VABI sceBackupRestoreUtilGetRequiredSize(); +int APS5_VABI sceBackupRestoreUtilGetRestoreProgress(); +int APS5_VABI sceBackupRestoreUtilGetTitles(); +int APS5_VABI sceBackupRestoreUtilInitialize(); +int APS5_VABI sceBackupRestoreUtilPrepareBackup(); +int APS5_VABI sceBackupRestoreUtilRestoreData(); +int APS5_VABI sceBackupRestoreUtilTerminate(); + +} diff --git a/core/libs/prx/libSceBackupRestoreUtil/libSceBackupRestoreUtil.hpp b/core/libs/prx/libSceBackupRestoreUtil/libSceBackupRestoreUtil.hpp new file mode 100644 index 0000000000..357f0a92c0 --- /dev/null +++ b/core/libs/prx/libSceBackupRestoreUtil/libSceBackupRestoreUtil.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceBackupRestoreUtil/include/Api.hpp" diff --git a/core/libs/prx/libSceBeisobmf/Export.cpp b/core/libs/prx/libSceBeisobmf/Export.cpp index a2eee1a93d..31c0e4b2fe 100644 --- a/core/libs/prx/libSceBeisobmf/Export.cpp +++ b/core/libs/prx/libSceBeisobmf/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceBeisobmf/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceBeisobmf/include/Api.hpp b/core/libs/prx/libSceBeisobmf/include/Api.hpp new file mode 100644 index 0000000000..60bc60a75e --- /dev/null +++ b/core/libs/prx/libSceBeisobmf/include/Api.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI isobmf_box_getchildbox_nid_postfix(void); +int APS5_VABI isobmf_box_getdiskoffset_nid_postfix(void); +int APS5_VABI isobmf_box_getflags32b_nid_postfix(void); +int APS5_VABI isobmf_box_getmember_length_nid_postfix(void); +int APS5_VABI isobmf_box_getmember_ptr_nid_postfix(void); +int APS5_VABI isobmf_box_getmember_type_nid_postfix(void); +int APS5_VABI isobmf_box_getnextbox_nid_postfix(void); +int APS5_VABI isobmf_box_getstate_nid_postfix(void); +int APS5_VABI isobmf_box_gettype_nid_postfix(void); +int APS5_VABI isobmf_decoder_clear_nid_postfix(void); +int APS5_VABI isobmf_decoder_create_nid_postfix(void); +int APS5_VABI isobmf_decoder_decode_nid_postfix(void); +int APS5_VABI isobmf_decoder_destroy_nid_postfix(void); +int APS5_VABI isobmf_decoder_get_decoder_tree_nid_postfix(void); +int APS5_VABI isobmf_decoder_getquerydata_nid_postfix(void); +int APS5_VABI isobmf_decoder_next_disk_offset_nid_postfix(void); +int APS5_VABI isobmf_decoder_query_nid_postfix(void); +int APS5_VABI isobmf_memmanager_set_nid_postfix(void); + +} diff --git a/core/libs/prx/libSceBeisobmf/libSceBeisobmf.hpp b/core/libs/prx/libSceBeisobmf/libSceBeisobmf.hpp new file mode 100644 index 0000000000..7b7de4f4a4 --- /dev/null +++ b/core/libs/prx/libSceBeisobmf/libSceBeisobmf.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceBeisobmf/include/Api.hpp" diff --git a/core/libs/prx/libSceBemp2sys/Export.cpp b/core/libs/prx/libSceBemp2sys/Export.cpp index 3bf12ad835..94f4dd4563 100644 --- a/core/libs/prx/libSceBemp2sys/Export.cpp +++ b/core/libs/prx/libSceBemp2sys/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceBemp2sys/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceBemp2sys/include/Api.hpp b/core/libs/prx/libSceBemp2sys/include/Api.hpp new file mode 100644 index 0000000000..82e55243e5 --- /dev/null +++ b/core/libs/prx/libSceBemp2sys/include/Api.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI bemp2sys_logprintf_getlevel_nid_postfix(void); +int APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void); +int APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(void); +int APS5_VABI bemp2sys_memmanager_set_nid_postfix(void); +int APS5_VABI bemp2sys_packetizer_create_nid_postfix(void); +int APS5_VABI bemp2sys_packetizer_destroy_nid_postfix(void); +int APS5_VABI bemp2sys_packetizer_init_nid_postfix(void); +int APS5_VABI bemp2sys_packetizer_release_nid_postfix(void); +int APS5_VABI bemp2sys_pesparser_create_nid_postfix(void); +int APS5_VABI bemp2sys_pesparser_destroy_nid_postfix(void); +int APS5_VABI bemp2sys_pesparser_init_nid_postfix(void); +int APS5_VABI bemp2sys_pesparser_parse_nid_postfix(void); +int APS5_VABI bemp2sys_pesparser_release_nid_postfix(void); +int APS5_VABI bemp2sys_pesparser_sendmessage_nid_postfix(void); +int APS5_VABI bemp2sys_psdecoder_create_nid_postfix(void); +int APS5_VABI bemp2sys_psdecoder_destroy_nid_postfix(void); +int APS5_VABI bemp2sys_psdecoder_init_nid_postfix(void); +int APS5_VABI bemp2sys_psdecoder_readpacket_nid_postfix(void); +int APS5_VABI bemp2sys_psdecoder_release_nid_postfix(void); +int APS5_VABI bemp2sys_psdecoder_sendmessage_nid_postfix(void); +int APS5_VABI bemp2sys_tsdecoder_create_nid_postfix(void); +int APS5_VABI bemp2sys_tsdecoder_destroy_nid_postfix(void); +int APS5_VABI bemp2sys_tsdecoder_init_nid_postfix(void); +int APS5_VABI bemp2sys_tsdecoder_readpacket_nid_postfix(void); +int APS5_VABI bemp2sys_tsdecoder_release_nid_postfix(void); +int APS5_VABI bemp2sys_tsdecoder_sendmessage_nid_postfix(void); +int APS5_VABI bemp2sys_tsfilter_create_nid_postfix(void); +int APS5_VABI bemp2sys_tsfilter_destroy_nid_postfix(void); +int APS5_VABI bemp2sys_tsfilter_init_nid_postfix(void); +int APS5_VABI bemp2sys_tsfilter_release_nid_postfix(void); +int APS5_VABI bemp2sys_tsfilter_sendmessage_nid_postfix(void); + +} diff --git a/core/libs/prx/libSceBemp2sys/libSceBemp2sys.hpp b/core/libs/prx/libSceBemp2sys/libSceBemp2sys.hpp new file mode 100644 index 0000000000..23c390341f --- /dev/null +++ b/core/libs/prx/libSceBemp2sys/libSceBemp2sys.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceBemp2sys/include/Api.hpp" diff --git a/core/libs/prx/libSceBgft/Export.cpp b/core/libs/prx/libSceBgft/Export.cpp index bb89f27861..c94cfeb6b8 100644 --- a/core/libs/prx/libSceBgft/Export.cpp +++ b/core/libs/prx/libSceBgft/Export.cpp @@ -1,11 +1,23 @@ +#include "prx/libSceBgft/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +static_assert(sizeof(BgftStorageTaskParam) == 112); +static_assert(offsetof(BgftStorageTaskParam, contentId) == 8); +static_assert(offsetof(BgftStorageTaskParam, text32) == 32); +static_assert(offsetof(BgftStorageTaskParam, value56) == 56); +static_assert(offsetof(BgftStorageTaskParam, text64) == 64); +static_assert(offsetof(BgftStorageTaskParam, value96) == 96); +static_assert(offsetof(BgftStorageTaskParam, value104) == 104); + +static_assert(sizeof(BgftInitParam) == 16); +static_assert(offsetof(BgftInitParam, heapSize) == 8); + extern "C" { -int APS5_VABI sceBgftServiceInit(void* params) { +int APS5_VABI sceBgftServiceInit(const BgftInitParam* params) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -29,11 +41,11 @@ int APS5_VABI sceBgftServiceDownloadStopTaskAll() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceDownloadFindActivePatchTask() { +int APS5_VABI sceBgftServiceDownloadFindActivePatchTask(const char* contentId, int32_t* taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceDownloadFindActivePupTask() { +int APS5_VABI sceBgftServiceDownloadFindActivePupTask(int32_t* taskId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -41,7 +53,7 @@ int APS5_VABI sceBgftServiceDownloadFindTaskByContentId(const char* contentId, i NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceDownloadGetProgress(int32_t taskId, void* progress) { +int APS5_VABI sceBgftServiceDownloadGetProgress(int32_t taskId, BgftProgress* progress) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -61,59 +73,59 @@ int APS5_VABI sceBgftServiceDownloadStopTask(int32_t taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadFindActiveGameAndGameAcTask() { +int APS5_VABI sceBgftServiceIntDownloadFindActiveGameAndGameAcTask(const char* contentId, int32_t* taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadFindActiveTask() { +int APS5_VABI sceBgftServiceIntDownloadFindActiveTask(const char* contentId, int32_t subType, int32_t* taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetGameAndGameAcProgress() { +int APS5_VABI sceBgftServiceIntDownloadGetGameAndGameAcProgress(const char* contentId, BgftProgress* progress) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetPatchProgress() { +int APS5_VABI sceBgftServiceIntDownloadGetPatchProgress(const char* contentId, BgftProgress* progress) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetPlayGoProgress() { +int APS5_VABI sceBgftServiceIntDownloadGetPlayGoProgress(const char* contentId, BgftProgress* progress) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetProgress() { +int APS5_VABI sceBgftServiceIntDownloadGetProgress(int32_t taskId, BgftProgress* progress) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetTaskInfo() { +int APS5_VABI sceBgftServiceIntDownloadGetTaskInfo(int32_t taskId, BgftTaskInfo* taskInfo) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadPauseTask() { +int APS5_VABI sceBgftServiceIntDownloadPauseTask(int32_t taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadRegisterTaskByStorageEx() { +int APS5_VABI sceBgftServiceIntDownloadRegisterTaskByStorageEx(const BgftStorageTaskParam* params, int32_t* taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadResumeTask() { +int APS5_VABI sceBgftServiceIntDownloadResumeTask(int32_t taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadStopTask() { +int APS5_VABI sceBgftServiceIntDownloadStopTask(int32_t taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadStartTask() { +int APS5_VABI sceBgftServiceIntDownloadStartTask(int32_t taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadUnregisterTask() { +int APS5_VABI sceBgftServiceIntDownloadUnregisterTask(int32_t taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntInit() { +int APS5_VABI sceBgftServiceIntInit(const BgftInitParam* params) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -337,4 +349,73 @@ int APS5_VABI sceBgftServiceIntUploadUnregisterTaskAll(void) { return 0; } +int APS5_VABI sceBgftServiceIntDebugPlayGoClearSetFreeZone() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDebugPlayGoIsSetFreeZone() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadCheckPatchUpdateState() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadClearStats() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetApplicationDataSize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetPatchGoProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadHideTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadSetResumeState() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadSetStartState() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntPlayGoCheckDiscIniChunksInstall() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntPlayGoStartDataDiscInstall() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntSetTaskVisible() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntUploadGetAvailableTaskCount() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntUploadGetTaskInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntUploadHideTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntUploadSetStartState() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntUploadSetUploadError() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceBgft/include/Api.hpp b/core/libs/prx/libSceBgft/include/Api.hpp new file mode 100644 index 0000000000..9e39227e29 --- /dev/null +++ b/core/libs/prx/libSceBgft/include/Api.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include "SceTypes.hpp" + +struct BgftInitParam { + void* heap; + uint64_t heapSize; +}; + +struct BgftProgress; +struct BgftTaskInfo; + +struct BgftStorageTaskParam { + std::uint32_t value0; + std::uint32_t value4; + const char* contentId; + const char* text16; + std::byte unused24[8]; + const char* text32; + const char* text40; + const char* text48; + std::uint32_t value56; + std::byte padding60[4]; + const char* text64; + const char* text72; + const char* text80; + const char* text88; + std::uint64_t value96; + std::uint32_t value104; +}; + +extern "C" { + +int APS5_VABI sceBgftServiceInit(const BgftInitParam* params); +int APS5_VABI sceBgftServiceTerm(void); +int APS5_VABI sceBgftServiceDownloadPauseTaskAll(); +int APS5_VABI sceBgftServiceDownloadResumeTaskAll(); +int APS5_VABI sceBgftServiceDownloadStartTaskAll(); +int APS5_VABI sceBgftServiceDownloadStopTaskAll(); +int APS5_VABI sceBgftServiceDownloadFindActivePatchTask(const char* contentId, int32_t* taskId); +int APS5_VABI sceBgftServiceDownloadFindActivePupTask(int32_t* taskId); +int APS5_VABI sceBgftServiceDownloadFindTaskByContentId(const char* contentId, int32_t subType, int32_t* taskId); +int APS5_VABI sceBgftServiceDownloadGetProgress(int32_t taskId, BgftProgress* progress); +int APS5_VABI sceBgftServiceDownloadPauseTask(int32_t taskId); +int APS5_VABI sceBgftServiceDownloadResumeTask(int32_t taskId); +int APS5_VABI sceBgftServiceDownloadStartTask(int32_t taskId); +int APS5_VABI sceBgftServiceDownloadStopTask(int32_t taskId); +int APS5_VABI sceBgftServiceIntDownloadFindActiveGameAndGameAcTask(const char* contentId, int32_t* taskId); +int APS5_VABI sceBgftServiceIntDownloadFindActiveTask(const char* contentId, int32_t subType, int32_t* taskId); +int APS5_VABI sceBgftServiceIntDownloadGetGameAndGameAcProgress(const char* contentId, BgftProgress* progress); +int APS5_VABI sceBgftServiceIntDownloadGetPatchProgress(const char* contentId, BgftProgress* progress); +int APS5_VABI sceBgftServiceIntDownloadGetPlayGoProgress(const char* contentId, BgftProgress* progress); +int APS5_VABI sceBgftServiceIntDownloadGetProgress(int32_t taskId, BgftProgress* progress); +int APS5_VABI sceBgftServiceIntDownloadGetTaskInfo(int32_t taskId, BgftTaskInfo* taskInfo); +int APS5_VABI sceBgftServiceIntDownloadPauseTask(int32_t taskId); +int APS5_VABI sceBgftServiceIntDownloadRegisterTaskByStorageEx(const BgftStorageTaskParam* params, int32_t* taskId); +int APS5_VABI sceBgftServiceIntDownloadResumeTask(int32_t taskId); +int APS5_VABI sceBgftServiceIntDownloadStopTask(int32_t taskId); +int APS5_VABI sceBgftServiceIntDownloadStartTask(int32_t taskId); +int APS5_VABI sceBgftServiceIntDownloadUnregisterTask(int32_t taskId); +int APS5_VABI sceBgftServiceIntInit(const BgftInitParam* params); +int APS5_VABI sceBgftServiceDownloadQueryTask(void); +int APS5_VABI sceBgftServiceIntDebugDownloadCorruptPkg(void); +int APS5_VABI sceBgftServiceIntDebugDownloadRequest(void); +int APS5_VABI sceBgftServiceIntDebugPlayGoGetPlayGoStatusString(void); +int APS5_VABI sceBgftServiceIntDebugPlayGoIsPaused(void); +int APS5_VABI sceBgftServiceIntDebugPlayGoResume(void); +int APS5_VABI sceBgftServiceIntDebugPlayGoRevertToFullState(void); +int APS5_VABI sceBgftServiceIntDebugPlayGoRevertToInitialState(void); +int APS5_VABI sceBgftServiceIntDebugPlayGoRevertToSnapshot(void); +int APS5_VABI sceBgftServiceIntDebugPlayGoSnapshotByTitleId(void); +int APS5_VABI sceBgftServiceIntDebugPlayGoSuspend(void); +int APS5_VABI sceBgftServiceIntDownloadDebugDeleteBgftEnvFile(void); +int APS5_VABI sceBgftServiceIntDownloadDebugDownloadBgftEnvFile(void); +int APS5_VABI sceBgftServiceIntDownloadDebugGetBgftEnvInfoString(void); +int APS5_VABI sceBgftServiceIntDownloadDebugGetStat(void); +int APS5_VABI sceBgftServiceIntDownloadGetExtUsbUserStorageSize(void); +int APS5_VABI sceBgftServiceIntDownloadGetUserStorageSize(void); +int APS5_VABI sceBgftServiceIntDownloadPauseTaskAll(void); +int APS5_VABI sceBgftServiceIntDownloadQueryTask(void); +int APS5_VABI sceBgftServiceIntDownloadRegisterTaskByStorage(void); +int APS5_VABI sceBgftServiceIntDownloadRestartTask(void); +int APS5_VABI sceBgftServiceIntDownloadResumeTaskAll(void); +int APS5_VABI sceBgftServiceIntDownloadStartTaskAll(void); +int APS5_VABI sceBgftServiceIntDownloadStopTaskAll(void); +int APS5_VABI sceBgftServiceIntDownloadUnregisterTaskAll(void); +int APS5_VABI sceBgftServiceIntExtUsbCheckActiveDownloadTasks(void); +int APS5_VABI sceBgftServiceIntGetTaskInfoValueInt(void); +int APS5_VABI sceBgftServiceIntGetTaskInfoValueString(void); +int APS5_VABI sceBgftServiceIntGetTaskInfoValueStringIndex(void); +int APS5_VABI sceBgftServiceIntPlayGoFindTaskId(void); +int APS5_VABI sceBgftServiceIntPlayGoGetDiscIniChunksInstallProgress(void); +int APS5_VABI sceBgftServiceIntPlayGoIsPaused(void); +int APS5_VABI sceBgftServiceIntPlayGoResume(void); +int APS5_VABI sceBgftServiceIntTerm(void); +int APS5_VABI sceBgftServiceIntUploadGetProgress(void); +int APS5_VABI sceBgftServiceIntUploadQueryTask(void); +int APS5_VABI sceBgftServiceIntUploadRegisterTask(void); +int APS5_VABI sceBgftServiceIntUploadRestartTask(void); +int APS5_VABI sceBgftServiceIntUploadStartTask(void); +int APS5_VABI sceBgftServiceIntUploadStartTaskAll(void); +int APS5_VABI sceBgftServiceIntUploadStopTask(void); +int APS5_VABI sceBgftServiceIntUploadStopTaskAll(void); +int APS5_VABI sceBgftServiceIntUploadUnregisterTask(void); +int APS5_VABI sceBgftServiceIntUploadUnregisterTaskAll(void); + +int APS5_VABI sceBgftServiceIntDebugPlayGoClearSetFreeZone(); +int APS5_VABI sceBgftServiceIntDebugPlayGoIsSetFreeZone(); +int APS5_VABI sceBgftServiceIntDownloadCheckPatchUpdateState(); +int APS5_VABI sceBgftServiceIntDownloadClearStats(); +int APS5_VABI sceBgftServiceIntDownloadGetApplicationDataSize(); +int APS5_VABI sceBgftServiceIntDownloadGetPatchGoProgress(); +int APS5_VABI sceBgftServiceIntDownloadHideTask(); +int APS5_VABI sceBgftServiceIntDownloadSetResumeState(); +int APS5_VABI sceBgftServiceIntDownloadSetStartState(); +int APS5_VABI sceBgftServiceIntPlayGoCheckDiscIniChunksInstall(); +int APS5_VABI sceBgftServiceIntPlayGoStartDataDiscInstall(); +int APS5_VABI sceBgftServiceIntSetTaskVisible(); +int APS5_VABI sceBgftServiceIntUploadGetAvailableTaskCount(); +int APS5_VABI sceBgftServiceIntUploadGetTaskInfo(); +int APS5_VABI sceBgftServiceIntUploadHideTask(); +int APS5_VABI sceBgftServiceIntUploadSetStartState(); +int APS5_VABI sceBgftServiceIntUploadSetUploadError(); + +} diff --git a/core/libs/prx/libSceBgft/libSceBgft.hpp b/core/libs/prx/libSceBgft/libSceBgft.hpp new file mode 100644 index 0000000000..27e9095851 --- /dev/null +++ b/core/libs/prx/libSceBgft/libSceBgft.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceBgft/include/Api.hpp" diff --git a/core/libs/prx/libSceBgsStorage/libSceBgsStorage.hpp b/core/libs/prx/libSceBgsStorage/libSceBgsStorage.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceBgsStorage/libSceBgsStorage.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceBluetoothHid/Export.cpp b/core/libs/prx/libSceBluetoothHid/Export.cpp index 5b7a222827..e1e00be946 100644 --- a/core/libs/prx/libSceBluetoothHid/Export.cpp +++ b/core/libs/prx/libSceBluetoothHid/Export.cpp @@ -1,50 +1,89 @@ +#include "prx/libSceBluetoothHid/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include +#include + +static_assert(sizeof(BluetoothHidThreadParam) == 24); +static_assert(offsetof(BluetoothHidThreadParam, affinityMask) == 8); +static_assert(offsetof(BluetoothHidThreadParam, reserved) == 16); +static_assert(sizeof(BluetoothHidParam) == 16); +static_assert(sizeof(BluetoothHidEvent) == 104); +static_assert(offsetof(BluetoothHidEvent, reportSize) == 14); +static_assert(offsetof(BluetoothHidEvent, report) == 24); + +namespace { +std::mutex stateMutex; +bool initialized = false; +bool callbackRegistered = false; +constexpr std::int32_t InvalidArgument = static_cast(0x81540001u); +constexpr std::int32_t NotInitialized = static_cast(0x81540004u); +constexpr std::int32_t NoCallback = static_cast(0x81540006u); +} extern "C" { std::int32_t APS5_VABI sceBluetoothHidInit() { + std::lock_guard lock(stateMutex); + initialized = true; return 0; } -std::int32_t APS5_VABI sceBluetoothHidRegisterCallback(void* callback, std::int32_t flags, void* param) { - (void)flags; - (void)param; - if (callback == nullptr) throw std::invalid_argument(std::string(__func__) + ": null callback"); +std::int32_t APS5_VABI sceBluetoothHidRegisterCallback(BluetoothHidCallback callback, void* userData, const BluetoothHidParam* param) { + std::lock_guard lock(stateMutex); + if (!initialized) return NotInitialized; + if (callback == nullptr) return InvalidArgument; + if (param != nullptr) { + if (param->reserved != 0) return InvalidArgument; + if (param->threadParam != nullptr) { + if (param->threadParam->reserved != 0) return InvalidArgument; + throw std::runtime_error("sceBluetoothHidRegisterCallback: custom worker scheduling is not implemented"); + } + } + (void)userData; + callbackRegistered = true; return 0; } std::int32_t APS5_VABI sceBluetoothHidUnregisterCallback() { + std::lock_guard lock(stateMutex); + if (!initialized) return NotInitialized; + if (!callbackRegistered) return NoCallback; + callbackRegistered = false; return 0; } std::int32_t APS5_VABI sceBluetoothHidRegisterDevice(std::uint16_t vendorId, std::uint16_t productId) { + std::lock_guard lock(stateMutex); + if (!initialized) return NotInitialized; + if (!callbackRegistered) return NoCallback; (void)vendorId; (void)productId; return 0; } std::int32_t APS5_VABI sceBluetoothHidUnregisterDevice(std::uint16_t vendorId, std::uint16_t productId) { + std::lock_guard lock(stateMutex); + if (!initialized) return NotInitialized; (void)vendorId; (void)productId; return 0; } -int APS5_VABI sceBluetoothHidGetDeviceName() { +int APS5_VABI sceBluetoothHidGetDeviceName(std::uint64_t deviceHandle, BluetoothHidDeviceName* name) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBluetoothHidGetInputReport() { +int APS5_VABI sceBluetoothHidGetInputReport(std::uint64_t deviceHandle, std::uint8_t reportId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBluetoothHidSetOutputReport() { +int APS5_VABI sceBluetoothHidSetOutputReport(std::uint64_t deviceHandle, std::uint8_t reportId, const void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -53,42 +92,42 @@ int APS5_VABI sceBluetoothHidDebugGetVersion() { return 0x1e; } -int APS5_VABI sceBluetoothHidDisconnectDevice() { +int APS5_VABI sceBluetoothHidDisconnectDevice(std::int32_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBluetoothHidGetDeviceInfo() { +int APS5_VABI sceBluetoothHidGetDeviceInfo(std::int32_t deviceId, BluetoothHidDeviceInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBluetoothHidGetFeatureReport() { +int APS5_VABI sceBluetoothHidGetFeatureReport(std::uint64_t deviceHandle, std::uint8_t reportId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBluetoothHidGetReportDescriptor() { +int APS5_VABI sceBluetoothHidGetReportDescriptor(std::uint64_t deviceHandle, void* descriptor, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBluetoothHidInterruptOutput() { +int APS5_VABI sceBluetoothHidInterruptOutput(std::uint64_t deviceHandle, std::uint8_t reportId, const void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBluetoothHidParamInitialize() { +int APS5_VABI sceBluetoothHidParamInitialize(BluetoothHidParam* param, const BluetoothHidThreadParam* threadParam) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBluetoothHidSetFeatureReport() { +int APS5_VABI sceBluetoothHidSetFeatureReport(std::uint64_t deviceHandle, std::uint8_t reportId, const void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBluetoothHidThreadParamInitialize() { +int APS5_VABI sceBluetoothHidThreadParamInitialize(BluetoothHidThreadParam* param, std::int32_t priority, std::uint64_t affinityMask) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceBluetoothHid/include/Api.hpp b/core/libs/prx/libSceBluetoothHid/include/Api.hpp new file mode 100644 index 0000000000..ff0741f33f --- /dev/null +++ b/core/libs/prx/libSceBluetoothHid/include/Api.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include + +struct BluetoothHidThreadParam { + std::int32_t priority; + std::uint32_t padding; + std::uint64_t affinityMask; + std::uint32_t reserved; + std::uint32_t tailPadding; +}; + +struct BluetoothHidParam { + const BluetoothHidThreadParam* threadParam; + std::uint32_t reserved; + std::uint32_t padding; +}; + +struct BluetoothHidEvent { + std::uint64_t deviceHandle; + std::uint16_t vendorId; + std::uint16_t productId; + std::uint8_t type; + std::uint8_t status; + std::uint16_t reportSize; + std::uint8_t reserved[8]; + std::uint8_t report[78]; + std::uint8_t padding[2]; +}; + +struct BluetoothHidDeviceInfo; + +struct BluetoothHidDeviceName; + +using BluetoothHidCallback = void (APS5_VABI *)(const BluetoothHidEvent*, std::int32_t, void*); + +extern "C" { + +std::int32_t APS5_VABI sceBluetoothHidInit(); +std::int32_t APS5_VABI sceBluetoothHidRegisterCallback(BluetoothHidCallback callback, void* userData, const BluetoothHidParam* param); +std::int32_t APS5_VABI sceBluetoothHidUnregisterCallback(); +std::int32_t APS5_VABI sceBluetoothHidRegisterDevice(std::uint16_t vendorId, std::uint16_t productId); +std::int32_t APS5_VABI sceBluetoothHidUnregisterDevice(std::uint16_t vendorId, std::uint16_t productId); +int APS5_VABI sceBluetoothHidGetDeviceName(std::uint64_t deviceHandle, BluetoothHidDeviceName* name); +int APS5_VABI sceBluetoothHidGetInputReport(std::uint64_t deviceHandle, std::uint8_t reportId); +int APS5_VABI sceBluetoothHidSetOutputReport(std::uint64_t deviceHandle, std::uint8_t reportId, const void* data, std::size_t size); +int APS5_VABI sceBluetoothHidDebugGetVersion(); +int APS5_VABI sceBluetoothHidDisconnectDevice(std::int32_t deviceId); +int APS5_VABI sceBluetoothHidGetDeviceInfo(std::int32_t deviceId, BluetoothHidDeviceInfo* info); +int APS5_VABI sceBluetoothHidGetFeatureReport(std::uint64_t deviceHandle, std::uint8_t reportId); +int APS5_VABI sceBluetoothHidGetReportDescriptor(std::uint64_t deviceHandle, void* descriptor, std::size_t size); +int APS5_VABI sceBluetoothHidInterruptOutput(std::uint64_t deviceHandle, std::uint8_t reportId, const void* data, std::size_t size); +int APS5_VABI sceBluetoothHidParamInitialize(BluetoothHidParam* param, const BluetoothHidThreadParam* threadParam); +int APS5_VABI sceBluetoothHidSetFeatureReport(std::uint64_t deviceHandle, std::uint8_t reportId, const void* data, std::size_t size); +int APS5_VABI sceBluetoothHidThreadParamInitialize(BluetoothHidThreadParam* param, std::int32_t priority, std::uint64_t affinityMask); + +} diff --git a/core/libs/prx/libSceBluetoothHid/libSceBluetoothHid.hpp b/core/libs/prx/libSceBluetoothHid/libSceBluetoothHid.hpp new file mode 100644 index 0000000000..ee177d4896 --- /dev/null +++ b/core/libs/prx/libSceBluetoothHid/libSceBluetoothHid.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceBluetoothHid/include/Api.hpp" diff --git a/core/libs/prx/libSceCamera/CMakeLists.txt b/core/libs/prx/libSceCamera/CMakeLists.txt new file mode 100644 index 0000000000..27f982657d --- /dev/null +++ b/core/libs/prx/libSceCamera/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(libSceCamera SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceCamera PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceCamera) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_camera_tests ${LIBS_INCLUDE_DIR}/tests/GuestCamera.cpp) + target_include_directories(guest_camera_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_camera_tests PRIVATE libSceCamera libc) + configure_windows_unwind(guest_camera_tests) + add_test(NAME guest_camera COMMAND guest_camera_tests) + if(WIN32) + set_tests_properties(guest_camera PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceCamera/Export.cpp b/core/libs/prx/libSceCamera/Export.cpp new file mode 100644 index 0000000000..c4fa002d9d --- /dev/null +++ b/core/libs/prx/libSceCamera/Export.cpp @@ -0,0 +1,595 @@ +#include "prx/libSceCamera/include/Api.hpp" +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +static constexpr int SCE_CAMERA_ERROR_PARAM = static_cast(0x802E0000); + +extern "C" { + +int APS5_VABI sceCameraIsAttached(int32_t index) { + if (index != 0) return SCE_CAMERA_ERROR_PARAM; + return 0; +} + +int APS5_VABI sceCameraOpen(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraClose(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetAttribute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetDeviceInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetFrameData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraIsValidFrameData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraStopByHandle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetDeviceIDWithoutOpen(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetAttribute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraInitializeRegistryCalibData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetContrast(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2Finalize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetConnectedDeviceAndDriverMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2IsAttached(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetSharpness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetTrackerMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetContrast(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetCalibDataForEve(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetCalibDataFromDevice(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetContrast(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraIsConnectedForSystem(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetExposureGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetRegister(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetVideoSyncInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetConfigInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetAttributeInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetWhiteBalance(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetMmapConnectedCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetSaturation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetDeviceConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraAudioOpen(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetAutoExposureGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetCalibDataFromDeviceForEve(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetWhiteBalance(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetVrCaptureInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetCalibDataForEve(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetAttribute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetLensCorrection(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetCalibData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetProcessFocusByHandle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetExposureGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetSharpness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2Initialize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetHue(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraAccGetData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetAttribute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetCalibrationData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetSaturation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetHue(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2Stop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetSaturation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetAutoWhiteBalance(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraAudioClose(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetProductInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetGamma(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetLensCorrection(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraChangeAppModuleState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetFrameData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetContrast(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetSaturation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetSharpness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetExposureGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraStartByHandle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2Open(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2Start(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetLensCorrectionInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraOpenByModuleId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetCalibrationData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraAudioGetData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetHue(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetGamma(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetCalibData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraWaitForEveReady(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetRegistryInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraAudioGetData2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetAppModuleFocus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetDefectivePixelCancellation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetDeviceID(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetForceActivate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetWhiteBalance(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetFieldOfView(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2IsValidFrameData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetProcessFocus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetGamma(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetWhiteBalance(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetDeviceConfigWithoutHandle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraDeviceOpen(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetUacModeInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetAutoExposureGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetRegister(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetGamma(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraRemoveAppModuleFocus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetAutoWhiteBalance(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetDefectivePixelCancellation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2Close(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetVideoSync(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraWaitForLukeReady(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetDefectivePixelCancellationInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2GetSharpness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetAutoWhiteBalance(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetDebugStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCamera2SetAutoExposureGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraIsConfigChangeDone(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetExposureGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetVideoSync(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraAudioReset(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetAutoExposureGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraSetAutoWhiteBalance(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraGetHue(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceCameraCloseByHandle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +} diff --git a/core/libs/prx/libSceCamera/include/Api.hpp b/core/libs/prx/libSceCamera/include/Api.hpp new file mode 100644 index 0000000000..169844eb58 --- /dev/null +++ b/core/libs/prx/libSceCamera/include/Api.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCameraIsAttached(int32_t index); +int APS5_VABI sceCameraOpen(void); +int APS5_VABI sceCameraClose(void); +int APS5_VABI sceCameraStart(void); +int APS5_VABI sceCameraStop(void); +int APS5_VABI sceCameraGetConfig(void); +int APS5_VABI sceCameraSetConfig(void); +int APS5_VABI sceCameraSetAttribute(void); +int APS5_VABI sceCameraGetDeviceInfo(void); +int APS5_VABI sceCameraGetFrameData(void); +int APS5_VABI sceCameraIsValidFrameData(void); +int APS5_VABI sceCameraStopByHandle(void); +int APS5_VABI sceCameraGetDeviceIDWithoutOpen(void); +int APS5_VABI sceCameraGetAttribute(void); +int APS5_VABI sceCameraInitializeRegistryCalibData(void); +int APS5_VABI sceCamera2GetContrast(void); +int APS5_VABI sceCamera2Finalize(void); +int APS5_VABI sceCameraGetConnectedDeviceAndDriverMode(void); +int APS5_VABI sceCamera2IsAttached(void); +int APS5_VABI sceCamera2SetSharpness(void); +int APS5_VABI sceCameraSetTrackerMode(void); +int APS5_VABI sceCameraSetContrast(void); +int APS5_VABI sceCameraGetCalibDataForEve(void); +int APS5_VABI sceCameraGetCalibDataFromDevice(void); +int APS5_VABI sceCamera2SetContrast(void); +int APS5_VABI sceCameraIsConnectedForSystem(void); +int APS5_VABI sceCamera2SetExposureGain(void); +int APS5_VABI sceCameraSetRegister(void); +int APS5_VABI sceCameraSetVideoSyncInternal(void); +int APS5_VABI sceCameraSetConfigInternal(void); +int APS5_VABI sceCameraSetAttributeInternal(void); +int APS5_VABI sceCamera2SetWhiteBalance(void); +int APS5_VABI sceCameraGetMmapConnectedCount(void); +int APS5_VABI sceCamera2GetSaturation(void); +int APS5_VABI sceCameraGetDeviceConfig(void); +int APS5_VABI sceCameraAudioOpen(void); +int APS5_VABI sceCamera2GetAutoExposureGain(void); +int APS5_VABI sceCameraGetCalibDataFromDeviceForEve(void); +int APS5_VABI sceCameraGetWhiteBalance(void); +int APS5_VABI sceCameraGetVrCaptureInfo(void); +int APS5_VABI sceCameraSetCalibDataForEve(void); +int APS5_VABI sceCamera2GetAttribute(void); +int APS5_VABI sceCameraSetLensCorrection(void); +int APS5_VABI sceCameraSetCalibData(void); +int APS5_VABI sceCameraSetProcessFocusByHandle(void); +int APS5_VABI sceCamera2SetConfig(void); +int APS5_VABI sceCameraGetExposureGain(void); +int APS5_VABI sceCameraSetSharpness(void); +int APS5_VABI sceCamera2Initialize(void); +int APS5_VABI sceCameraSetHue(void); +int APS5_VABI sceCameraAccGetData(void); +int APS5_VABI sceCamera2SetAttribute(void); +int APS5_VABI sceCameraGetCalibrationData(void); +int APS5_VABI sceCameraGetSaturation(void); +int APS5_VABI sceCamera2GetHue(void); +int APS5_VABI sceCamera2Stop(void); +int APS5_VABI sceCamera2SetSaturation(void); +int APS5_VABI sceCamera2GetAutoWhiteBalance(void); +int APS5_VABI sceCameraAudioClose(void); +int APS5_VABI sceCameraGetProductInfo(void); +int APS5_VABI sceCameraGetGamma(void); +int APS5_VABI sceCameraGetLensCorrection(void); +int APS5_VABI sceCameraChangeAppModuleState(void); +int APS5_VABI sceCamera2GetFrameData(void); +int APS5_VABI sceCameraGetContrast(void); +int APS5_VABI sceCameraSetSaturation(void); +int APS5_VABI sceCameraGetSharpness(void); +int APS5_VABI sceCamera2GetExposureGain(void); +int APS5_VABI sceCameraStartByHandle(void); +int APS5_VABI sceCamera2Open(void); +int APS5_VABI sceCamera2Start(void); +int APS5_VABI sceCameraSetLensCorrectionInternal(void); +int APS5_VABI sceCameraOpenByModuleId(void); +int APS5_VABI sceCamera2GetCalibrationData(void); +int APS5_VABI sceCameraAudioGetData(void); +int APS5_VABI sceCamera2SetHue(void); +int APS5_VABI sceCamera2GetGamma(void); +int APS5_VABI sceCameraGetCalibData(void); +int APS5_VABI sceCameraWaitForEveReady(void); +int APS5_VABI sceCameraGetRegistryInfo(void); +int APS5_VABI sceCameraAudioGetData2(void); +int APS5_VABI sceCameraSetAppModuleFocus(void); +int APS5_VABI sceCameraSetDefectivePixelCancellation(void); +int APS5_VABI sceCameraGetDeviceID(void); +int APS5_VABI sceCameraSetForceActivate(void); +int APS5_VABI sceCameraSetWhiteBalance(void); +int APS5_VABI sceCamera2GetFieldOfView(void); +int APS5_VABI sceCamera2IsValidFrameData(void); +int APS5_VABI sceCameraSetProcessFocus(void); +int APS5_VABI sceCameraSetGamma(void); +int APS5_VABI sceCamera2GetConfig(void); +int APS5_VABI sceCamera2GetWhiteBalance(void); +int APS5_VABI sceCameraGetDeviceConfigWithoutHandle(void); +int APS5_VABI sceCameraDeviceOpen(void); +int APS5_VABI sceCameraSetUacModeInternal(void); +int APS5_VABI sceCameraGetAutoExposureGain(void); +int APS5_VABI sceCameraGetRegister(void); +int APS5_VABI sceCamera2SetGamma(void); +int APS5_VABI sceCameraRemoveAppModuleFocus(void); +int APS5_VABI sceCameraGetAutoWhiteBalance(void); +int APS5_VABI sceCameraGetDefectivePixelCancellation(void); +int APS5_VABI sceCamera2Close(void); +int APS5_VABI sceCamera2SetVideoSync(void); +int APS5_VABI sceCameraWaitForLukeReady(void); +int APS5_VABI sceCameraSetDefectivePixelCancellationInternal(void); +int APS5_VABI sceCamera2GetSharpness(void); +int APS5_VABI sceCamera2SetAutoWhiteBalance(void); +int APS5_VABI sceCameraSetDebugStop(void); +int APS5_VABI sceCamera2SetAutoExposureGain(void); +int APS5_VABI sceCameraIsConfigChangeDone(void); +int APS5_VABI sceCameraSetExposureGain(void); +int APS5_VABI sceCameraSetVideoSync(void); +int APS5_VABI sceCameraAudioReset(void); +int APS5_VABI sceCameraSetAutoExposureGain(void); +int APS5_VABI sceCameraSetAutoWhiteBalance(void); +int APS5_VABI sceCameraGetHue(void); +int APS5_VABI sceCameraCloseByHandle(void); + +} diff --git a/core/libs/prx/libSceCamera/libSceCamera.hpp b/core/libs/prx/libSceCamera/libSceCamera.hpp new file mode 100644 index 0000000000..f11c2f4973 --- /dev/null +++ b/core/libs/prx/libSceCamera/libSceCamera.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCamera/include/Api.hpp" diff --git a/core/libs/prx/libSceCdlgG2P/CMakeLists.txt b/core/libs/prx/libSceCdlgG2P/CMakeLists.txt index 9809109f69..f15414c733 100644 --- a/core/libs/prx/libSceCdlgG2P/CMakeLists.txt +++ b/core/libs/prx/libSceCdlgG2P/CMakeLists.txt @@ -1,11 +1,12 @@ add_library(libSceCdlgG2P SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceCdlgG2P PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceCdlgG2P) +target_link_libraries(libSceCdlgG2P PRIVATE libSceUserService) if(BUILD_TESTING) add_test_executable(guest_g2p_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestG2PDialog.cpp) target_include_directories(guest_g2p_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_g2p_dialog_tests PRIVATE libSceCdlgG2P) + target_link_libraries(guest_g2p_dialog_tests PRIVATE libSceCdlgG2P libSceUserService) add_test(NAME guest_g2p_dialog COMMAND guest_g2p_dialog_tests) if(WIN32) set_tests_properties(guest_g2p_dialog PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceCdlgG2P/Export.cpp b/core/libs/prx/libSceCdlgG2P/Export.cpp index 082b4bb331..a6f7113420 100644 --- a/core/libs/prx/libSceCdlgG2P/Export.cpp +++ b/core/libs/prx/libSceCdlgG2P/Export.cpp @@ -1,8 +1,12 @@ +#include "prx/libSceCdlgG2P/include/Api.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include "SceTypes.hpp" #include #include #include #include -#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" namespace { @@ -12,9 +16,10 @@ constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_INVALID_ARGUMENT = static_cast(0x80B8000Au); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; @@ -25,47 +30,51 @@ std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; extern "C" { int APS5_VABI sceG2PDialogInitialize(void) { - int expected = COMMON_DIALOG_STATUS_NONE; - if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; - return 0; + int expected = COMMON_DIALOG_STATUS_NONE; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + return 0; } -int APS5_VABI sceG2PDialogOpen(const void* param) { - const int status = g_status.load(); - if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; - if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; - g_status = COMMON_DIALOG_STATUS_FINISHED; - return 0; +int APS5_VABI sceG2PDialogOpen(const G2PDialogParam* param) { + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (!ValidateCommonDialogBaseParam(param->base) || param->size != sizeof(*param) || param->userId == -1 || !std::all_of(std::begin(param->reserved), std::end(param->reserved), [](std::uint8_t value) { return value == 0; })) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + UserServiceLoginUserIdList users{}; + if (sceUserServiceGetLoginUserIdList(&users) != 0 || std::none_of(std::begin(users.user_id), std::end(users.user_id), [param](std::int32_t userId) { return userId != -1 && userId == param->userId; })) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; } int APS5_VABI sceG2PDialogUpdateStatus(void) { - return g_status.load(); + return g_status.load(); } int APS5_VABI sceG2PDialogGetStatus(void) { - return g_status.load(); + return g_status.load(); } -int APS5_VABI sceG2PDialogGetResult(void* result) { - const int status = g_status.load(); - if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; - if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; - std::memcpy(result, &canceled, sizeof(canceled)); - return 0; +int APS5_VABI sceG2PDialogGetResult(CommonDialogResultPrefix* result) { + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + return 0; } int APS5_VABI sceG2PDialogClose(void) { - if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - g_status = COMMON_DIALOG_STATUS_FINISHED; - return 0; + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status != COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_NOT_RUNNING; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; } int APS5_VABI sceG2PDialogTerminate(void) { - if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - return 0; + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + return 0; } } diff --git a/core/libs/prx/libSceCdlgG2P/include/Api.hpp b/core/libs/prx/libSceCdlgG2P/include/Api.hpp new file mode 100644 index 0000000000..5233fb9246 --- /dev/null +++ b/core/libs/prx/libSceCdlgG2P/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceG2PDialogInitialize(void); +int APS5_VABI sceG2PDialogOpen(const G2PDialogParam* param); +int APS5_VABI sceG2PDialogUpdateStatus(void); +int APS5_VABI sceG2PDialogGetStatus(void); +int APS5_VABI sceG2PDialogGetResult(CommonDialogResultPrefix* result); +int APS5_VABI sceG2PDialogClose(void); +int APS5_VABI sceG2PDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceCdlgG2P/libSceCdlgG2P.hpp b/core/libs/prx/libSceCdlgG2P/libSceCdlgG2P.hpp new file mode 100644 index 0000000000..b01cc903f1 --- /dev/null +++ b/core/libs/prx/libSceCdlgG2P/libSceCdlgG2P.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCdlgG2P/include/Api.hpp" diff --git a/core/libs/prx/libSceCdlgPlayerReview/CMakeLists.txt b/core/libs/prx/libSceCdlgPlayerReview/CMakeLists.txt index 7b4466af1a..f3aa2fabdc 100644 --- a/core/libs/prx/libSceCdlgPlayerReview/CMakeLists.txt +++ b/core/libs/prx/libSceCdlgPlayerReview/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(libSceCdlgPlayerReview SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceCdlgPlayerReview PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceCdlgPlayerReview) +target_link_libraries(libSceCdlgPlayerReview PRIVATE libSceUserService) if(MINGW) foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) @@ -11,7 +12,7 @@ endif() if(BUILD_TESTING) add_test_executable(guest_player_review_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestPlayerReviewDialog.cpp) target_include_directories(guest_player_review_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_player_review_dialog_tests PRIVATE libSceCdlgPlayerReview) + target_link_libraries(guest_player_review_dialog_tests PRIVATE libSceCdlgPlayerReview libSceUserService) add_test(NAME guest_player_review_dialog COMMAND guest_player_review_dialog_tests) if(WIN32) set_tests_properties(guest_player_review_dialog PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceCdlgPlayerReview/Export.cpp b/core/libs/prx/libSceCdlgPlayerReview/Export.cpp index 2e454c8741..75e23b282e 100644 --- a/core/libs/prx/libSceCdlgPlayerReview/Export.cpp +++ b/core/libs/prx/libSceCdlgPlayerReview/Export.cpp @@ -1,6 +1,10 @@ +#include "prx/libSceCdlgPlayerReview/include/Api.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include "SceTypes.hpp" #include #include -#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" namespace { @@ -10,8 +14,8 @@ constexpr int COMMON_DIALOG_STATUS_NONE = 0; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int COMMON_DIALOG_ERROR_INVALID_ARGUMENT = static_cast(0x80B8000Au); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); @@ -35,17 +39,22 @@ int APS5_VABI scePlayerReviewDialogTerminate(void) { return COMMON_DIALOG_ERROR_NOT_RUNNING; } -int APS5_VABI scePlayerReviewDialogOpen(const void* param) { +int APS5_VABI scePlayerReviewDialogOpen(const PlayerReviewDialogParam* param) { + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; - if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (!ValidateCommonDialogBaseParam(param->base) || param->size != sizeof(*param) || param->userId == -1 || !std::all_of(std::begin(param->reserved), std::end(param->reserved), [](std::uint8_t value) { return value == 0; }) || param->mode > 1) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + UserServiceLoginUserIdList users{}; + if (sceUserServiceGetLoginUserIdList(&users) != 0 || std::none_of(std::begin(users.user_id), std::end(users.user_id), [param](std::int32_t userId) { return userId != -1 && userId == param->userId; })) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } int APS5_VABI scePlayerReviewDialogClose(void) { - if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status != COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_NOT_RUNNING; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } @@ -58,13 +67,13 @@ int APS5_VABI scePlayerReviewDialogUpdateStatus(void) { return g_status.load(); } -int APS5_VABI scePlayerReviewDialogGetResult(void* result) { +int APS5_VABI scePlayerReviewDialogGetResult(CommonDialogResultPrefix* result) { + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - *static_cast(result) = COMMON_DIALOG_RESULT_USER_CANCELED; - return 0; + result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + return COMMON_DIALOG_RESULT_USER_CANCELED; } } diff --git a/core/libs/prx/libSceCdlgPlayerReview/include/Api.hpp b/core/libs/prx/libSceCdlgPlayerReview/include/Api.hpp new file mode 100644 index 0000000000..5951bb3d08 --- /dev/null +++ b/core/libs/prx/libSceCdlgPlayerReview/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePlayerReviewDialogInitialize(void); +int APS5_VABI scePlayerReviewDialogTerminate(void); +int APS5_VABI scePlayerReviewDialogOpen(const PlayerReviewDialogParam* param); +int APS5_VABI scePlayerReviewDialogClose(void); +int APS5_VABI scePlayerReviewDialogGetStatus(void); +int APS5_VABI scePlayerReviewDialogUpdateStatus(void); +int APS5_VABI scePlayerReviewDialogGetResult(CommonDialogResultPrefix* result); + +} diff --git a/core/libs/prx/libSceCdlgPlayerReview/libSceCdlgPlayerReview.hpp b/core/libs/prx/libSceCdlgPlayerReview/libSceCdlgPlayerReview.hpp new file mode 100644 index 0000000000..a7037746d2 --- /dev/null +++ b/core/libs/prx/libSceCdlgPlayerReview/libSceCdlgPlayerReview.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCdlgPlayerReview/include/Api.hpp" diff --git a/core/libs/prx/libSceCdlgUtilServer/libSceCdlgUtilServer.hpp b/core/libs/prx/libSceCdlgUtilServer/libSceCdlgUtilServer.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceCdlgUtilServer/libSceCdlgUtilServer.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceCesCs-module/libSceCesCs-module.hpp b/core/libs/prx/libSceCesCs-module/libSceCesCs-module.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceCesCs-module/libSceCesCs-module.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceCesCs/CMakeLists.txt b/core/libs/prx/libSceCesCs/CMakeLists.txt index c9d9076534..991387c540 100644 --- a/core/libs/prx/libSceCesCs/CMakeLists.txt +++ b/core/libs/prx/libSceCesCs/CMakeLists.txt @@ -25,10 +25,21 @@ if(BUILD_TESTING) endif() if(BUILD_TESTING) - foreach(variant IN ITEMS unicode unicode_module) - add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesUnicode.cpp) + foreach(variant IN ITEMS unicode unicode_module japanese japanese_module wide wide_module chinese chinese_module mbc mbc_module) + if(variant MATCHES "^mbc") + add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesMbc.cpp) + elseif(variant MATCHES "^chinese") + add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesChinese.cpp) + elseif(variant MATCHES "^wide") + add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesWide.cpp) + elseif(variant MATCHES "^japanese") + add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesJapanese.cpp) + else() + add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesUnicode.cpp) + endif() target_include_directories(guest_ces_${variant}_tests PRIVATE ${LIBS_INCLUDE_DIR}) - if(variant STREQUAL "unicode_module") + configure_windows_unwind(guest_ces_${variant}_tests) + if(variant MATCHES "_module$") target_link_libraries(guest_ces_${variant}_tests PRIVATE libSceCesCs-module) else() target_link_libraries(guest_ces_${variant}_tests PRIVATE libSceCesCs) @@ -36,7 +47,7 @@ if(BUILD_TESTING) add_test(NAME guest_ces_${variant} COMMAND guest_ces_${variant}_tests) endforeach() if(WIN32) - set_tests_properties(guest_ces_unicode guest_ces_unicode_module PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_ces_unicode guest_ces_unicode_module guest_ces_japanese guest_ces_japanese_module guest_ces_wide guest_ces_wide_module guest_ces_chinese guest_ces_chinese_module guest_ces_mbc guest_ces_mbc_module PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceCesCs/Cp936.hpp b/core/libs/prx/libSceCesCs/Cp936.hpp new file mode 100644 index 0000000000..09e143d1d0 --- /dev/null +++ b/core/libs/prx/libSceCesCs/Cp936.hpp @@ -0,0 +1,262 @@ +#pragma once + +#include + +namespace Cp936 { + +inline constexpr uint16_t TO_UCS2[126 * 190] = { + 0x4E02, 0x4E04, 0x4E05, 0x4E06, 0x4E0F, 0x4E12, 0x4E17, 0x4E1F, 0x4E20, 0x4E21, 0x4E23, 0x4E26, 0x4E29, 0x4E2E, 0x4E2F, 0x4E31, 0x4E33, 0x4E35, 0x4E37, 0x4E3C, 0x4E40, 0x4E41, 0x4E42, 0x4E44, 0x4E46, 0x4E4A, 0x4E51, 0x4E55, 0x4E57, 0x4E5A, 0x4E5B, 0x4E62, 0x4E63, 0x4E64, 0x4E65, 0x4E67, 0x4E68, 0x4E6A, 0x4E6B, 0x4E6C, 0x4E6D, 0x4E6E, 0x4E6F, 0x4E72, 0x4E74, 0x4E75, 0x4E76, 0x4E77, 0x4E78, 0x4E79, 0x4E7A, 0x4E7B, 0x4E7C, 0x4E7D, 0x4E7F, 0x4E80, 0x4E81, 0x4E82, 0x4E83, 0x4E84, 0x4E85, 0x4E87, 0x4E8A, 0x4E90, 0x4E96, 0x4E97, 0x4E99, 0x4E9C, 0x4E9D, 0x4E9E, 0x4EA3, 0x4EAA, 0x4EAF, 0x4EB0, 0x4EB1, 0x4EB4, 0x4EB6, 0x4EB7, 0x4EB8, 0x4EB9, 0x4EBC, 0x4EBD, 0x4EBE, 0x4EC8, 0x4ECC, 0x4ECF, 0x4ED0, 0x4ED2, 0x4EDA, 0x4EDB, 0x4EDC, 0x4EE0, 0x4EE2, 0x4EE6, 0x4EE7, + 0x4EE9, 0x4EED, 0x4EEE, 0x4EEF, 0x4EF1, 0x4EF4, 0x4EF8, 0x4EF9, 0x4EFA, 0x4EFC, 0x4EFE, 0x4F00, 0x4F02, 0x4F03, 0x4F04, 0x4F05, 0x4F06, 0x4F07, 0x4F08, 0x4F0B, 0x4F0C, 0x4F12, 0x4F13, 0x4F14, 0x4F15, 0x4F16, 0x4F1C, 0x4F1D, 0x4F21, 0x4F23, 0x4F28, 0x4F29, 0x4F2C, 0x4F2D, 0x4F2E, 0x4F31, 0x4F33, 0x4F35, 0x4F37, 0x4F39, 0x4F3B, 0x4F3E, 0x4F3F, 0x4F40, 0x4F41, 0x4F42, 0x4F44, 0x4F45, 0x4F47, 0x4F48, 0x4F49, 0x4F4A, 0x4F4B, 0x4F4C, 0x4F52, 0x4F54, 0x4F56, 0x4F61, 0x4F62, 0x4F66, 0x4F68, 0x4F6A, 0x4F6B, 0x4F6D, 0x4F6E, 0x4F71, 0x4F72, 0x4F75, 0x4F77, 0x4F78, 0x4F79, 0x4F7A, 0x4F7D, 0x4F80, 0x4F81, 0x4F82, 0x4F85, 0x4F86, 0x4F87, 0x4F8A, 0x4F8C, 0x4F8E, 0x4F90, 0x4F92, 0x4F93, 0x4F95, 0x4F96, 0x4F98, 0x4F99, 0x4F9A, 0x4F9C, 0x4F9E, 0x4F9F, 0x4FA1, 0x4FA2, + 0x4FA4, 0x4FAB, 0x4FAD, 0x4FB0, 0x4FB1, 0x4FB2, 0x4FB3, 0x4FB4, 0x4FB6, 0x4FB7, 0x4FB8, 0x4FB9, 0x4FBA, 0x4FBB, 0x4FBC, 0x4FBD, 0x4FBE, 0x4FC0, 0x4FC1, 0x4FC2, 0x4FC6, 0x4FC7, 0x4FC8, 0x4FC9, 0x4FCB, 0x4FCC, 0x4FCD, 0x4FD2, 0x4FD3, 0x4FD4, 0x4FD5, 0x4FD6, 0x4FD9, 0x4FDB, 0x4FE0, 0x4FE2, 0x4FE4, 0x4FE5, 0x4FE7, 0x4FEB, 0x4FEC, 0x4FF0, 0x4FF2, 0x4FF4, 0x4FF5, 0x4FF6, 0x4FF7, 0x4FF9, 0x4FFB, 0x4FFC, 0x4FFD, 0x4FFF, 0x5000, 0x5001, 0x5002, 0x5003, 0x5004, 0x5005, 0x5006, 0x5007, 0x5008, 0x5009, 0x500A, 0x500B, 0x500E, 0x5010, 0x5011, 0x5013, 0x5015, 0x5016, 0x5017, 0x501B, 0x501D, 0x501E, 0x5020, 0x5022, 0x5023, 0x5024, 0x5027, 0x502B, 0x502F, 0x5030, 0x5031, 0x5032, 0x5033, 0x5034, 0x5035, 0x5036, 0x5037, 0x5038, 0x5039, 0x503B, 0x503D, 0x503F, 0x5040, + 0x5041, 0x5042, 0x5044, 0x5045, 0x5046, 0x5049, 0x504A, 0x504B, 0x504D, 0x5050, 0x5051, 0x5052, 0x5053, 0x5054, 0x5056, 0x5057, 0x5058, 0x5059, 0x505B, 0x505D, 0x505E, 0x505F, 0x5060, 0x5061, 0x5062, 0x5063, 0x5064, 0x5066, 0x5067, 0x5068, 0x5069, 0x506A, 0x506B, 0x506D, 0x506E, 0x506F, 0x5070, 0x5071, 0x5072, 0x5073, 0x5074, 0x5075, 0x5078, 0x5079, 0x507A, 0x507C, 0x507D, 0x5081, 0x5082, 0x5083, 0x5084, 0x5086, 0x5087, 0x5089, 0x508A, 0x508B, 0x508C, 0x508E, 0x508F, 0x5090, 0x5091, 0x5092, 0x5093, 0x5094, 0x5095, 0x5096, 0x5097, 0x5098, 0x5099, 0x509A, 0x509B, 0x509C, 0x509D, 0x509E, 0x509F, 0x50A0, 0x50A1, 0x50A2, 0x50A4, 0x50A6, 0x50AA, 0x50AB, 0x50AD, 0x50AE, 0x50AF, 0x50B0, 0x50B1, 0x50B3, 0x50B4, 0x50B5, 0x50B6, 0x50B7, 0x50B8, 0x50B9, 0x50BC, + 0x50BD, 0x50BE, 0x50BF, 0x50C0, 0x50C1, 0x50C2, 0x50C3, 0x50C4, 0x50C5, 0x50C6, 0x50C7, 0x50C8, 0x50C9, 0x50CA, 0x50CB, 0x50CC, 0x50CD, 0x50CE, 0x50D0, 0x50D1, 0x50D2, 0x50D3, 0x50D4, 0x50D5, 0x50D7, 0x50D8, 0x50D9, 0x50DB, 0x50DC, 0x50DD, 0x50DE, 0x50DF, 0x50E0, 0x50E1, 0x50E2, 0x50E3, 0x50E4, 0x50E5, 0x50E8, 0x50E9, 0x50EA, 0x50EB, 0x50EF, 0x50F0, 0x50F1, 0x50F2, 0x50F4, 0x50F6, 0x50F7, 0x50F8, 0x50F9, 0x50FA, 0x50FC, 0x50FD, 0x50FE, 0x50FF, 0x5100, 0x5101, 0x5102, 0x5103, 0x5104, 0x5105, 0x5108, 0x5109, 0x510A, 0x510C, 0x510D, 0x510E, 0x510F, 0x5110, 0x5111, 0x5113, 0x5114, 0x5115, 0x5116, 0x5117, 0x5118, 0x5119, 0x511A, 0x511B, 0x511C, 0x511D, 0x511E, 0x511F, 0x5120, 0x5122, 0x5123, 0x5124, 0x5125, 0x5126, 0x5127, 0x5128, 0x5129, 0x512A, 0x512B, + 0x512C, 0x512D, 0x512E, 0x512F, 0x5130, 0x5131, 0x5132, 0x5133, 0x5134, 0x5135, 0x5136, 0x5137, 0x5138, 0x5139, 0x513A, 0x513B, 0x513C, 0x513D, 0x513E, 0x5142, 0x5147, 0x514A, 0x514C, 0x514E, 0x514F, 0x5150, 0x5152, 0x5153, 0x5157, 0x5158, 0x5159, 0x515B, 0x515D, 0x515E, 0x515F, 0x5160, 0x5161, 0x5163, 0x5164, 0x5166, 0x5167, 0x5169, 0x516A, 0x516F, 0x5172, 0x517A, 0x517E, 0x517F, 0x5183, 0x5184, 0x5186, 0x5187, 0x518A, 0x518B, 0x518E, 0x518F, 0x5190, 0x5191, 0x5193, 0x5194, 0x5198, 0x519A, 0x519D, 0x519E, 0x519F, 0x51A1, 0x51A3, 0x51A6, 0x51A7, 0x51A8, 0x51A9, 0x51AA, 0x51AD, 0x51AE, 0x51B4, 0x51B8, 0x51B9, 0x51BA, 0x51BE, 0x51BF, 0x51C1, 0x51C2, 0x51C3, 0x51C5, 0x51C8, 0x51CA, 0x51CD, 0x51CE, 0x51D0, 0x51D2, 0x51D3, 0x51D4, 0x51D5, 0x51D6, 0x51D7, + 0x51D8, 0x51D9, 0x51DA, 0x51DC, 0x51DE, 0x51DF, 0x51E2, 0x51E3, 0x51E5, 0x51E6, 0x51E7, 0x51E8, 0x51E9, 0x51EA, 0x51EC, 0x51EE, 0x51F1, 0x51F2, 0x51F4, 0x51F7, 0x51FE, 0x5204, 0x5205, 0x5209, 0x520B, 0x520C, 0x520F, 0x5210, 0x5213, 0x5214, 0x5215, 0x521C, 0x521E, 0x521F, 0x5221, 0x5222, 0x5223, 0x5225, 0x5226, 0x5227, 0x522A, 0x522C, 0x522F, 0x5231, 0x5232, 0x5234, 0x5235, 0x523C, 0x523E, 0x5244, 0x5245, 0x5246, 0x5247, 0x5248, 0x5249, 0x524B, 0x524E, 0x524F, 0x5252, 0x5253, 0x5255, 0x5257, 0x5258, 0x5259, 0x525A, 0x525B, 0x525D, 0x525F, 0x5260, 0x5262, 0x5263, 0x5264, 0x5266, 0x5268, 0x526B, 0x526C, 0x526D, 0x526E, 0x5270, 0x5271, 0x5273, 0x5274, 0x5275, 0x5276, 0x5277, 0x5278, 0x5279, 0x527A, 0x527B, 0x527C, 0x527E, 0x5280, 0x5283, 0x5284, 0x5285, + 0x5286, 0x5287, 0x5289, 0x528A, 0x528B, 0x528C, 0x528D, 0x528E, 0x528F, 0x5291, 0x5292, 0x5294, 0x5295, 0x5296, 0x5297, 0x5298, 0x5299, 0x529A, 0x529C, 0x52A4, 0x52A5, 0x52A6, 0x52A7, 0x52AE, 0x52AF, 0x52B0, 0x52B4, 0x52B5, 0x52B6, 0x52B7, 0x52B8, 0x52B9, 0x52BA, 0x52BB, 0x52BC, 0x52BD, 0x52C0, 0x52C1, 0x52C2, 0x52C4, 0x52C5, 0x52C6, 0x52C8, 0x52CA, 0x52CC, 0x52CD, 0x52CE, 0x52CF, 0x52D1, 0x52D3, 0x52D4, 0x52D5, 0x52D7, 0x52D9, 0x52DA, 0x52DB, 0x52DC, 0x52DD, 0x52DE, 0x52E0, 0x52E1, 0x52E2, 0x52E3, 0x52E5, 0x52E6, 0x52E7, 0x52E8, 0x52E9, 0x52EA, 0x52EB, 0x52EC, 0x52ED, 0x52EE, 0x52EF, 0x52F1, 0x52F2, 0x52F3, 0x52F4, 0x52F5, 0x52F6, 0x52F7, 0x52F8, 0x52FB, 0x52FC, 0x52FD, 0x5301, 0x5302, 0x5303, 0x5304, 0x5307, 0x5309, 0x530A, 0x530B, 0x530C, 0x530E, + 0x5311, 0x5312, 0x5313, 0x5314, 0x5318, 0x531B, 0x531C, 0x531E, 0x531F, 0x5322, 0x5324, 0x5325, 0x5327, 0x5328, 0x5329, 0x532B, 0x532C, 0x532D, 0x532F, 0x5330, 0x5331, 0x5332, 0x5333, 0x5334, 0x5335, 0x5336, 0x5337, 0x5338, 0x533C, 0x533D, 0x5340, 0x5342, 0x5344, 0x5346, 0x534B, 0x534C, 0x534D, 0x5350, 0x5354, 0x5358, 0x5359, 0x535B, 0x535D, 0x5365, 0x5368, 0x536A, 0x536C, 0x536D, 0x5372, 0x5376, 0x5379, 0x537B, 0x537C, 0x537D, 0x537E, 0x5380, 0x5381, 0x5383, 0x5387, 0x5388, 0x538A, 0x538E, 0x538F, 0x5390, 0x5391, 0x5392, 0x5393, 0x5394, 0x5396, 0x5397, 0x5399, 0x539B, 0x539C, 0x539E, 0x53A0, 0x53A1, 0x53A4, 0x53A7, 0x53AA, 0x53AB, 0x53AC, 0x53AD, 0x53AF, 0x53B0, 0x53B1, 0x53B2, 0x53B3, 0x53B4, 0x53B5, 0x53B7, 0x53B8, 0x53B9, 0x53BA, 0x53BC, 0x53BD, + 0x53BE, 0x53C0, 0x53C3, 0x53C4, 0x53C5, 0x53C6, 0x53C7, 0x53CE, 0x53CF, 0x53D0, 0x53D2, 0x53D3, 0x53D5, 0x53DA, 0x53DC, 0x53DD, 0x53DE, 0x53E1, 0x53E2, 0x53E7, 0x53F4, 0x53FA, 0x53FE, 0x53FF, 0x5400, 0x5402, 0x5405, 0x5407, 0x540B, 0x5414, 0x5418, 0x5419, 0x541A, 0x541C, 0x5422, 0x5424, 0x5425, 0x542A, 0x5430, 0x5433, 0x5436, 0x5437, 0x543A, 0x543D, 0x543F, 0x5441, 0x5442, 0x5444, 0x5445, 0x5447, 0x5449, 0x544C, 0x544D, 0x544E, 0x544F, 0x5451, 0x545A, 0x545D, 0x545E, 0x545F, 0x5460, 0x5461, 0x5463, 0x5465, 0x5467, 0x5469, 0x546A, 0x546B, 0x546C, 0x546D, 0x546E, 0x546F, 0x5470, 0x5474, 0x5479, 0x547A, 0x547E, 0x547F, 0x5481, 0x5483, 0x5485, 0x5487, 0x5488, 0x5489, 0x548A, 0x548D, 0x5491, 0x5493, 0x5497, 0x5498, 0x549C, 0x549E, 0x549F, 0x54A0, 0x54A1, + 0x54A2, 0x54A5, 0x54AE, 0x54B0, 0x54B2, 0x54B5, 0x54B6, 0x54B7, 0x54B9, 0x54BA, 0x54BC, 0x54BE, 0x54C3, 0x54C5, 0x54CA, 0x54CB, 0x54D6, 0x54D8, 0x54DB, 0x54E0, 0x54E1, 0x54E2, 0x54E3, 0x54E4, 0x54EB, 0x54EC, 0x54EF, 0x54F0, 0x54F1, 0x54F4, 0x54F5, 0x54F6, 0x54F7, 0x54F8, 0x54F9, 0x54FB, 0x54FE, 0x5500, 0x5502, 0x5503, 0x5504, 0x5505, 0x5508, 0x550A, 0x550B, 0x550C, 0x550D, 0x550E, 0x5512, 0x5513, 0x5515, 0x5516, 0x5517, 0x5518, 0x5519, 0x551A, 0x551C, 0x551D, 0x551E, 0x551F, 0x5521, 0x5525, 0x5526, 0x5528, 0x5529, 0x552B, 0x552D, 0x5532, 0x5534, 0x5535, 0x5536, 0x5538, 0x5539, 0x553A, 0x553B, 0x553D, 0x5540, 0x5542, 0x5545, 0x5547, 0x5548, 0x554B, 0x554C, 0x554D, 0x554E, 0x554F, 0x5551, 0x5552, 0x5553, 0x5554, 0x5557, 0x5558, 0x5559, 0x555A, 0x555B, + 0x555D, 0x555E, 0x555F, 0x5560, 0x5562, 0x5563, 0x5568, 0x5569, 0x556B, 0x556F, 0x5570, 0x5571, 0x5572, 0x5573, 0x5574, 0x5579, 0x557A, 0x557D, 0x557F, 0x5585, 0x5586, 0x558C, 0x558D, 0x558E, 0x5590, 0x5592, 0x5593, 0x5595, 0x5596, 0x5597, 0x559A, 0x559B, 0x559E, 0x55A0, 0x55A1, 0x55A2, 0x55A3, 0x55A4, 0x55A5, 0x55A6, 0x55A8, 0x55A9, 0x55AA, 0x55AB, 0x55AC, 0x55AD, 0x55AE, 0x55AF, 0x55B0, 0x55B2, 0x55B4, 0x55B6, 0x55B8, 0x55BA, 0x55BC, 0x55BF, 0x55C0, 0x55C1, 0x55C2, 0x55C3, 0x55C6, 0x55C7, 0x55C8, 0x55CA, 0x55CB, 0x55CE, 0x55CF, 0x55D0, 0x55D5, 0x55D7, 0x55D8, 0x55D9, 0x55DA, 0x55DB, 0x55DE, 0x55E0, 0x55E2, 0x55E7, 0x55E9, 0x55ED, 0x55EE, 0x55F0, 0x55F1, 0x55F4, 0x55F6, 0x55F8, 0x55F9, 0x55FA, 0x55FB, 0x55FC, 0x55FF, 0x5602, 0x5603, 0x5604, 0x5605, + 0x5606, 0x5607, 0x560A, 0x560B, 0x560D, 0x5610, 0x5611, 0x5612, 0x5613, 0x5614, 0x5615, 0x5616, 0x5617, 0x5619, 0x561A, 0x561C, 0x561D, 0x5620, 0x5621, 0x5622, 0x5625, 0x5626, 0x5628, 0x5629, 0x562A, 0x562B, 0x562E, 0x562F, 0x5630, 0x5633, 0x5635, 0x5637, 0x5638, 0x563A, 0x563C, 0x563D, 0x563E, 0x5640, 0x5641, 0x5642, 0x5643, 0x5644, 0x5645, 0x5646, 0x5647, 0x5648, 0x5649, 0x564A, 0x564B, 0x564F, 0x5650, 0x5651, 0x5652, 0x5653, 0x5655, 0x5656, 0x565A, 0x565B, 0x565D, 0x565E, 0x565F, 0x5660, 0x5661, 0x5663, 0x5665, 0x5666, 0x5667, 0x566D, 0x566E, 0x566F, 0x5670, 0x5672, 0x5673, 0x5674, 0x5675, 0x5677, 0x5678, 0x5679, 0x567A, 0x567D, 0x567E, 0x567F, 0x5680, 0x5681, 0x5682, 0x5683, 0x5684, 0x5687, 0x5688, 0x5689, 0x568A, 0x568B, 0x568C, 0x568D, 0x5690, + 0x5691, 0x5692, 0x5694, 0x5695, 0x5696, 0x5697, 0x5698, 0x5699, 0x569A, 0x569B, 0x569C, 0x569D, 0x569E, 0x569F, 0x56A0, 0x56A1, 0x56A2, 0x56A4, 0x56A5, 0x56A6, 0x56A7, 0x56A8, 0x56A9, 0x56AA, 0x56AB, 0x56AC, 0x56AD, 0x56AE, 0x56B0, 0x56B1, 0x56B2, 0x56B3, 0x56B4, 0x56B5, 0x56B6, 0x56B8, 0x56B9, 0x56BA, 0x56BB, 0x56BD, 0x56BE, 0x56BF, 0x56C0, 0x56C1, 0x56C2, 0x56C3, 0x56C4, 0x56C5, 0x56C6, 0x56C7, 0x56C8, 0x56C9, 0x56CB, 0x56CC, 0x56CD, 0x56CE, 0x56CF, 0x56D0, 0x56D1, 0x56D2, 0x56D3, 0x56D5, 0x56D6, 0x56D8, 0x56D9, 0x56DC, 0x56E3, 0x56E5, 0x56E6, 0x56E7, 0x56E8, 0x56E9, 0x56EA, 0x56EC, 0x56EE, 0x56EF, 0x56F2, 0x56F3, 0x56F6, 0x56F7, 0x56F8, 0x56FB, 0x56FC, 0x5700, 0x5701, 0x5702, 0x5705, 0x5707, 0x570B, 0x570C, 0x570D, 0x570E, 0x570F, 0x5710, 0x5711, + 0x5712, 0x5713, 0x5714, 0x5715, 0x5716, 0x5717, 0x5718, 0x5719, 0x571A, 0x571B, 0x571D, 0x571E, 0x5720, 0x5721, 0x5722, 0x5724, 0x5725, 0x5726, 0x5727, 0x572B, 0x5731, 0x5732, 0x5734, 0x5735, 0x5736, 0x5737, 0x5738, 0x573C, 0x573D, 0x573F, 0x5741, 0x5743, 0x5744, 0x5745, 0x5746, 0x5748, 0x5749, 0x574B, 0x5752, 0x5753, 0x5754, 0x5755, 0x5756, 0x5758, 0x5759, 0x5762, 0x5763, 0x5765, 0x5767, 0x576C, 0x576E, 0x5770, 0x5771, 0x5772, 0x5774, 0x5775, 0x5778, 0x5779, 0x577A, 0x577D, 0x577E, 0x577F, 0x5780, 0x5781, 0x5787, 0x5788, 0x5789, 0x578A, 0x578D, 0x578E, 0x578F, 0x5790, 0x5791, 0x5794, 0x5795, 0x5796, 0x5797, 0x5798, 0x5799, 0x579A, 0x579C, 0x579D, 0x579E, 0x579F, 0x57A5, 0x57A8, 0x57AA, 0x57AC, 0x57AF, 0x57B0, 0x57B1, 0x57B3, 0x57B5, 0x57B6, 0x57B7, + 0x57B9, 0x57BA, 0x57BB, 0x57BC, 0x57BD, 0x57BE, 0x57BF, 0x57C0, 0x57C1, 0x57C4, 0x57C5, 0x57C6, 0x57C7, 0x57C8, 0x57C9, 0x57CA, 0x57CC, 0x57CD, 0x57D0, 0x57D1, 0x57D3, 0x57D6, 0x57D7, 0x57DB, 0x57DC, 0x57DE, 0x57E1, 0x57E2, 0x57E3, 0x57E5, 0x57E6, 0x57E7, 0x57E8, 0x57E9, 0x57EA, 0x57EB, 0x57EC, 0x57EE, 0x57F0, 0x57F1, 0x57F2, 0x57F3, 0x57F5, 0x57F6, 0x57F7, 0x57FB, 0x57FC, 0x57FE, 0x57FF, 0x5801, 0x5803, 0x5804, 0x5805, 0x5808, 0x5809, 0x580A, 0x580C, 0x580E, 0x580F, 0x5810, 0x5812, 0x5813, 0x5814, 0x5816, 0x5817, 0x5818, 0x581A, 0x581B, 0x581C, 0x581D, 0x581F, 0x5822, 0x5823, 0x5825, 0x5826, 0x5827, 0x5828, 0x5829, 0x582B, 0x582C, 0x582D, 0x582E, 0x582F, 0x5831, 0x5832, 0x5833, 0x5834, 0x5836, 0x5837, 0x5838, 0x5839, 0x583A, 0x583B, 0x583C, 0x583D, + 0x583E, 0x583F, 0x5840, 0x5841, 0x5842, 0x5843, 0x5845, 0x5846, 0x5847, 0x5848, 0x5849, 0x584A, 0x584B, 0x584E, 0x584F, 0x5850, 0x5852, 0x5853, 0x5855, 0x5856, 0x5857, 0x5859, 0x585A, 0x585B, 0x585C, 0x585D, 0x585F, 0x5860, 0x5861, 0x5862, 0x5863, 0x5864, 0x5866, 0x5867, 0x5868, 0x5869, 0x586A, 0x586D, 0x586E, 0x586F, 0x5870, 0x5871, 0x5872, 0x5873, 0x5874, 0x5875, 0x5876, 0x5877, 0x5878, 0x5879, 0x587A, 0x587B, 0x587C, 0x587D, 0x587F, 0x5882, 0x5884, 0x5886, 0x5887, 0x5888, 0x588A, 0x588B, 0x588C, 0x588D, 0x588E, 0x588F, 0x5890, 0x5891, 0x5894, 0x5895, 0x5896, 0x5897, 0x5898, 0x589B, 0x589C, 0x589D, 0x58A0, 0x58A1, 0x58A2, 0x58A3, 0x58A4, 0x58A5, 0x58A6, 0x58A7, 0x58AA, 0x58AB, 0x58AC, 0x58AD, 0x58AE, 0x58AF, 0x58B0, 0x58B1, 0x58B2, 0x58B3, 0x58B4, + 0x58B5, 0x58B6, 0x58B7, 0x58B8, 0x58B9, 0x58BA, 0x58BB, 0x58BD, 0x58BE, 0x58BF, 0x58C0, 0x58C2, 0x58C3, 0x58C4, 0x58C6, 0x58C7, 0x58C8, 0x58C9, 0x58CA, 0x58CB, 0x58CC, 0x58CD, 0x58CE, 0x58CF, 0x58D0, 0x58D2, 0x58D3, 0x58D4, 0x58D6, 0x58D7, 0x58D8, 0x58D9, 0x58DA, 0x58DB, 0x58DC, 0x58DD, 0x58DE, 0x58DF, 0x58E0, 0x58E1, 0x58E2, 0x58E3, 0x58E5, 0x58E6, 0x58E7, 0x58E8, 0x58E9, 0x58EA, 0x58ED, 0x58EF, 0x58F1, 0x58F2, 0x58F4, 0x58F5, 0x58F7, 0x58F8, 0x58FA, 0x58FB, 0x58FC, 0x58FD, 0x58FE, 0x58FF, 0x5900, 0x5901, 0x5903, 0x5905, 0x5906, 0x5908, 0x5909, 0x590A, 0x590B, 0x590C, 0x590E, 0x5910, 0x5911, 0x5912, 0x5913, 0x5917, 0x5918, 0x591B, 0x591D, 0x591E, 0x5920, 0x5921, 0x5922, 0x5923, 0x5926, 0x5928, 0x592C, 0x5930, 0x5932, 0x5933, 0x5935, 0x5936, 0x593B, + 0x593D, 0x593E, 0x593F, 0x5940, 0x5943, 0x5945, 0x5946, 0x594A, 0x594C, 0x594D, 0x5950, 0x5952, 0x5953, 0x5959, 0x595B, 0x595C, 0x595D, 0x595E, 0x595F, 0x5961, 0x5963, 0x5964, 0x5966, 0x5967, 0x5968, 0x5969, 0x596A, 0x596B, 0x596C, 0x596D, 0x596E, 0x596F, 0x5970, 0x5971, 0x5972, 0x5975, 0x5977, 0x597A, 0x597B, 0x597C, 0x597E, 0x597F, 0x5980, 0x5985, 0x5989, 0x598B, 0x598C, 0x598E, 0x598F, 0x5990, 0x5991, 0x5994, 0x5995, 0x5998, 0x599A, 0x599B, 0x599C, 0x599D, 0x599F, 0x59A0, 0x59A1, 0x59A2, 0x59A6, 0x59A7, 0x59AC, 0x59AD, 0x59B0, 0x59B1, 0x59B3, 0x59B4, 0x59B5, 0x59B6, 0x59B7, 0x59B8, 0x59BA, 0x59BC, 0x59BD, 0x59BF, 0x59C0, 0x59C1, 0x59C2, 0x59C3, 0x59C4, 0x59C5, 0x59C7, 0x59C8, 0x59C9, 0x59CC, 0x59CD, 0x59CE, 0x59CF, 0x59D5, 0x59D6, 0x59D9, 0x59DB, + 0x59DE, 0x59DF, 0x59E0, 0x59E1, 0x59E2, 0x59E4, 0x59E6, 0x59E7, 0x59E9, 0x59EA, 0x59EB, 0x59ED, 0x59EE, 0x59EF, 0x59F0, 0x59F1, 0x59F2, 0x59F3, 0x59F4, 0x59F5, 0x59F6, 0x59F7, 0x59F8, 0x59FA, 0x59FC, 0x59FD, 0x59FE, 0x5A00, 0x5A02, 0x5A0A, 0x5A0B, 0x5A0D, 0x5A0E, 0x5A0F, 0x5A10, 0x5A12, 0x5A14, 0x5A15, 0x5A16, 0x5A17, 0x5A19, 0x5A1A, 0x5A1B, 0x5A1D, 0x5A1E, 0x5A21, 0x5A22, 0x5A24, 0x5A26, 0x5A27, 0x5A28, 0x5A2A, 0x5A2B, 0x5A2C, 0x5A2D, 0x5A2E, 0x5A2F, 0x5A30, 0x5A33, 0x5A35, 0x5A37, 0x5A38, 0x5A39, 0x5A3A, 0x5A3B, 0x5A3D, 0x5A3E, 0x5A3F, 0x5A41, 0x5A42, 0x5A43, 0x5A44, 0x5A45, 0x5A47, 0x5A48, 0x5A4B, 0x5A4C, 0x5A4D, 0x5A4E, 0x5A4F, 0x5A50, 0x5A51, 0x5A52, 0x5A53, 0x5A54, 0x5A56, 0x5A57, 0x5A58, 0x5A59, 0x5A5B, 0x5A5C, 0x5A5D, 0x5A5E, 0x5A5F, 0x5A60, + 0x5A61, 0x5A63, 0x5A64, 0x5A65, 0x5A66, 0x5A68, 0x5A69, 0x5A6B, 0x5A6C, 0x5A6D, 0x5A6E, 0x5A6F, 0x5A70, 0x5A71, 0x5A72, 0x5A73, 0x5A78, 0x5A79, 0x5A7B, 0x5A7C, 0x5A7D, 0x5A7E, 0x5A80, 0x5A81, 0x5A82, 0x5A83, 0x5A84, 0x5A85, 0x5A86, 0x5A87, 0x5A88, 0x5A89, 0x5A8A, 0x5A8B, 0x5A8C, 0x5A8D, 0x5A8E, 0x5A8F, 0x5A90, 0x5A91, 0x5A93, 0x5A94, 0x5A95, 0x5A96, 0x5A97, 0x5A98, 0x5A99, 0x5A9C, 0x5A9D, 0x5A9E, 0x5A9F, 0x5AA0, 0x5AA1, 0x5AA2, 0x5AA3, 0x5AA4, 0x5AA5, 0x5AA6, 0x5AA7, 0x5AA8, 0x5AA9, 0x5AAB, 0x5AAC, 0x5AAD, 0x5AAE, 0x5AAF, 0x5AB0, 0x5AB1, 0x5AB4, 0x5AB6, 0x5AB7, 0x5AB9, 0x5ABA, 0x5ABB, 0x5ABC, 0x5ABD, 0x5ABF, 0x5AC0, 0x5AC3, 0x5AC4, 0x5AC5, 0x5AC6, 0x5AC7, 0x5AC8, 0x5ACA, 0x5ACB, 0x5ACD, 0x5ACE, 0x5ACF, 0x5AD0, 0x5AD1, 0x5AD3, 0x5AD5, 0x5AD7, 0x5AD9, + 0x5ADA, 0x5ADB, 0x5ADD, 0x5ADE, 0x5ADF, 0x5AE2, 0x5AE4, 0x5AE5, 0x5AE7, 0x5AE8, 0x5AEA, 0x5AEC, 0x5AED, 0x5AEE, 0x5AEF, 0x5AF0, 0x5AF2, 0x5AF3, 0x5AF4, 0x5AF5, 0x5AF6, 0x5AF7, 0x5AF8, 0x5AF9, 0x5AFA, 0x5AFB, 0x5AFC, 0x5AFD, 0x5AFE, 0x5AFF, 0x5B00, 0x5B01, 0x5B02, 0x5B03, 0x5B04, 0x5B05, 0x5B06, 0x5B07, 0x5B08, 0x5B0A, 0x5B0B, 0x5B0C, 0x5B0D, 0x5B0E, 0x5B0F, 0x5B10, 0x5B11, 0x5B12, 0x5B13, 0x5B14, 0x5B15, 0x5B18, 0x5B19, 0x5B1A, 0x5B1B, 0x5B1C, 0x5B1D, 0x5B1E, 0x5B1F, 0x5B20, 0x5B21, 0x5B22, 0x5B23, 0x5B24, 0x5B25, 0x5B26, 0x5B27, 0x5B28, 0x5B29, 0x5B2A, 0x5B2B, 0x5B2C, 0x5B2D, 0x5B2E, 0x5B2F, 0x5B30, 0x5B31, 0x5B33, 0x5B35, 0x5B36, 0x5B38, 0x5B39, 0x5B3A, 0x5B3B, 0x5B3C, 0x5B3D, 0x5B3E, 0x5B3F, 0x5B41, 0x5B42, 0x5B43, 0x5B44, 0x5B45, 0x5B46, 0x5B47, + 0x5B48, 0x5B49, 0x5B4A, 0x5B4B, 0x5B4C, 0x5B4D, 0x5B4E, 0x5B4F, 0x5B52, 0x5B56, 0x5B5E, 0x5B60, 0x5B61, 0x5B67, 0x5B68, 0x5B6B, 0x5B6D, 0x5B6E, 0x5B6F, 0x5B72, 0x5B74, 0x5B76, 0x5B77, 0x5B78, 0x5B79, 0x5B7B, 0x5B7C, 0x5B7E, 0x5B7F, 0x5B82, 0x5B86, 0x5B8A, 0x5B8D, 0x5B8E, 0x5B90, 0x5B91, 0x5B92, 0x5B94, 0x5B96, 0x5B9F, 0x5BA7, 0x5BA8, 0x5BA9, 0x5BAC, 0x5BAD, 0x5BAE, 0x5BAF, 0x5BB1, 0x5BB2, 0x5BB7, 0x5BBA, 0x5BBB, 0x5BBC, 0x5BC0, 0x5BC1, 0x5BC3, 0x5BC8, 0x5BC9, 0x5BCA, 0x5BCB, 0x5BCD, 0x5BCE, 0x5BCF, 0x5BD1, 0x5BD4, 0x5BD5, 0x5BD6, 0x5BD7, 0x5BD8, 0x5BD9, 0x5BDA, 0x5BDB, 0x5BDC, 0x5BE0, 0x5BE2, 0x5BE3, 0x5BE6, 0x5BE7, 0x5BE9, 0x5BEA, 0x5BEB, 0x5BEC, 0x5BED, 0x5BEF, 0x5BF1, 0x5BF2, 0x5BF3, 0x5BF4, 0x5BF5, 0x5BF6, 0x5BF7, 0x5BFD, 0x5BFE, 0x5C00, 0x5C02, + 0x5C03, 0x5C05, 0x5C07, 0x5C08, 0x5C0B, 0x5C0C, 0x5C0D, 0x5C0E, 0x5C10, 0x5C12, 0x5C13, 0x5C17, 0x5C19, 0x5C1B, 0x5C1E, 0x5C1F, 0x5C20, 0x5C21, 0x5C23, 0x5C26, 0x5C28, 0x5C29, 0x5C2A, 0x5C2B, 0x5C2D, 0x5C2E, 0x5C2F, 0x5C30, 0x5C32, 0x5C33, 0x5C35, 0x5C36, 0x5C37, 0x5C43, 0x5C44, 0x5C46, 0x5C47, 0x5C4C, 0x5C4D, 0x5C52, 0x5C53, 0x5C54, 0x5C56, 0x5C57, 0x5C58, 0x5C5A, 0x5C5B, 0x5C5C, 0x5C5D, 0x5C5F, 0x5C62, 0x5C64, 0x5C67, 0x5C68, 0x5C69, 0x5C6A, 0x5C6B, 0x5C6C, 0x5C6D, 0x5C70, 0x5C72, 0x5C73, 0x5C74, 0x5C75, 0x5C76, 0x5C77, 0x5C78, 0x5C7B, 0x5C7C, 0x5C7D, 0x5C7E, 0x5C80, 0x5C83, 0x5C84, 0x5C85, 0x5C86, 0x5C87, 0x5C89, 0x5C8A, 0x5C8B, 0x5C8E, 0x5C8F, 0x5C92, 0x5C93, 0x5C95, 0x5C9D, 0x5C9E, 0x5C9F, 0x5CA0, 0x5CA1, 0x5CA4, 0x5CA5, 0x5CA6, 0x5CA7, 0x5CA8, + 0x5CAA, 0x5CAE, 0x5CAF, 0x5CB0, 0x5CB2, 0x5CB4, 0x5CB6, 0x5CB9, 0x5CBA, 0x5CBB, 0x5CBC, 0x5CBE, 0x5CC0, 0x5CC2, 0x5CC3, 0x5CC5, 0x5CC6, 0x5CC7, 0x5CC8, 0x5CC9, 0x5CCA, 0x5CCC, 0x5CCD, 0x5CCE, 0x5CCF, 0x5CD0, 0x5CD1, 0x5CD3, 0x5CD4, 0x5CD5, 0x5CD6, 0x5CD7, 0x5CD8, 0x5CDA, 0x5CDB, 0x5CDC, 0x5CDD, 0x5CDE, 0x5CDF, 0x5CE0, 0x5CE2, 0x5CE3, 0x5CE7, 0x5CE9, 0x5CEB, 0x5CEC, 0x5CEE, 0x5CEF, 0x5CF1, 0x5CF2, 0x5CF3, 0x5CF4, 0x5CF5, 0x5CF6, 0x5CF7, 0x5CF8, 0x5CF9, 0x5CFA, 0x5CFC, 0x5CFD, 0x5CFE, 0x5CFF, 0x5D00, 0x5D01, 0x5D04, 0x5D05, 0x5D08, 0x5D09, 0x5D0A, 0x5D0B, 0x5D0C, 0x5D0D, 0x5D0F, 0x5D10, 0x5D11, 0x5D12, 0x5D13, 0x5D15, 0x5D17, 0x5D18, 0x5D19, 0x5D1A, 0x5D1C, 0x5D1D, 0x5D1F, 0x5D20, 0x5D21, 0x5D22, 0x5D23, 0x5D25, 0x5D28, 0x5D2A, 0x5D2B, 0x5D2C, 0x5D2F, + 0x5D30, 0x5D31, 0x5D32, 0x5D33, 0x5D35, 0x5D36, 0x5D37, 0x5D38, 0x5D39, 0x5D3A, 0x5D3B, 0x5D3C, 0x5D3F, 0x5D40, 0x5D41, 0x5D42, 0x5D43, 0x5D44, 0x5D45, 0x5D46, 0x5D48, 0x5D49, 0x5D4D, 0x5D4E, 0x5D4F, 0x5D50, 0x5D51, 0x5D52, 0x5D53, 0x5D54, 0x5D55, 0x5D56, 0x5D57, 0x5D59, 0x5D5A, 0x5D5C, 0x5D5E, 0x5D5F, 0x5D60, 0x5D61, 0x5D62, 0x5D63, 0x5D64, 0x5D65, 0x5D66, 0x5D67, 0x5D68, 0x5D6A, 0x5D6D, 0x5D6E, 0x5D70, 0x5D71, 0x5D72, 0x5D73, 0x5D75, 0x5D76, 0x5D77, 0x5D78, 0x5D79, 0x5D7A, 0x5D7B, 0x5D7C, 0x5D7D, 0x5D7E, 0x5D7F, 0x5D80, 0x5D81, 0x5D83, 0x5D84, 0x5D85, 0x5D86, 0x5D87, 0x5D88, 0x5D89, 0x5D8A, 0x5D8B, 0x5D8C, 0x5D8D, 0x5D8E, 0x5D8F, 0x5D90, 0x5D91, 0x5D92, 0x5D93, 0x5D94, 0x5D95, 0x5D96, 0x5D97, 0x5D98, 0x5D9A, 0x5D9B, 0x5D9C, 0x5D9E, 0x5D9F, 0x5DA0, + 0x5DA1, 0x5DA2, 0x5DA3, 0x5DA4, 0x5DA5, 0x5DA6, 0x5DA7, 0x5DA8, 0x5DA9, 0x5DAA, 0x5DAB, 0x5DAC, 0x5DAD, 0x5DAE, 0x5DAF, 0x5DB0, 0x5DB1, 0x5DB2, 0x5DB3, 0x5DB4, 0x5DB5, 0x5DB6, 0x5DB8, 0x5DB9, 0x5DBA, 0x5DBB, 0x5DBC, 0x5DBD, 0x5DBE, 0x5DBF, 0x5DC0, 0x5DC1, 0x5DC2, 0x5DC3, 0x5DC4, 0x5DC6, 0x5DC7, 0x5DC8, 0x5DC9, 0x5DCA, 0x5DCB, 0x5DCC, 0x5DCE, 0x5DCF, 0x5DD0, 0x5DD1, 0x5DD2, 0x5DD3, 0x5DD4, 0x5DD5, 0x5DD6, 0x5DD7, 0x5DD8, 0x5DD9, 0x5DDA, 0x5DDC, 0x5DDF, 0x5DE0, 0x5DE3, 0x5DE4, 0x5DEA, 0x5DEC, 0x5DED, 0x5DF0, 0x5DF5, 0x5DF6, 0x5DF8, 0x5DF9, 0x5DFA, 0x5DFB, 0x5DFC, 0x5DFF, 0x5E00, 0x5E04, 0x5E07, 0x5E09, 0x5E0A, 0x5E0B, 0x5E0D, 0x5E0E, 0x5E12, 0x5E13, 0x5E17, 0x5E1E, 0x5E1F, 0x5E20, 0x5E21, 0x5E22, 0x5E23, 0x5E24, 0x5E25, 0x5E28, 0x5E29, 0x5E2A, 0x5E2B, + 0x5E2C, 0x5E2F, 0x5E30, 0x5E32, 0x5E33, 0x5E34, 0x5E35, 0x5E36, 0x5E39, 0x5E3A, 0x5E3E, 0x5E3F, 0x5E40, 0x5E41, 0x5E43, 0x5E46, 0x5E47, 0x5E48, 0x5E49, 0x5E4A, 0x5E4B, 0x5E4D, 0x5E4E, 0x5E4F, 0x5E50, 0x5E51, 0x5E52, 0x5E53, 0x5E56, 0x5E57, 0x5E58, 0x5E59, 0x5E5A, 0x5E5C, 0x5E5D, 0x5E5F, 0x5E60, 0x5E63, 0x5E64, 0x5E65, 0x5E66, 0x5E67, 0x5E68, 0x5E69, 0x5E6A, 0x5E6B, 0x5E6C, 0x5E6D, 0x5E6E, 0x5E6F, 0x5E70, 0x5E71, 0x5E75, 0x5E77, 0x5E79, 0x5E7E, 0x5E81, 0x5E82, 0x5E83, 0x5E85, 0x5E88, 0x5E89, 0x5E8C, 0x5E8D, 0x5E8E, 0x5E92, 0x5E98, 0x5E9B, 0x5E9D, 0x5EA1, 0x5EA2, 0x5EA3, 0x5EA4, 0x5EA8, 0x5EA9, 0x5EAA, 0x5EAB, 0x5EAC, 0x5EAE, 0x5EAF, 0x5EB0, 0x5EB1, 0x5EB2, 0x5EB4, 0x5EBA, 0x5EBB, 0x5EBC, 0x5EBD, 0x5EBF, 0x5EC0, 0x5EC1, 0x5EC2, 0x5EC3, 0x5EC4, 0x5EC5, + 0x5EC6, 0x5EC7, 0x5EC8, 0x5ECB, 0x5ECC, 0x5ECD, 0x5ECE, 0x5ECF, 0x5ED0, 0x5ED4, 0x5ED5, 0x5ED7, 0x5ED8, 0x5ED9, 0x5EDA, 0x5EDC, 0x5EDD, 0x5EDE, 0x5EDF, 0x5EE0, 0x5EE1, 0x5EE2, 0x5EE3, 0x5EE4, 0x5EE5, 0x5EE6, 0x5EE7, 0x5EE9, 0x5EEB, 0x5EEC, 0x5EED, 0x5EEE, 0x5EEF, 0x5EF0, 0x5EF1, 0x5EF2, 0x5EF3, 0x5EF5, 0x5EF8, 0x5EF9, 0x5EFB, 0x5EFC, 0x5EFD, 0x5F05, 0x5F06, 0x5F07, 0x5F09, 0x5F0C, 0x5F0D, 0x5F0E, 0x5F10, 0x5F12, 0x5F14, 0x5F16, 0x5F19, 0x5F1A, 0x5F1C, 0x5F1D, 0x5F1E, 0x5F21, 0x5F22, 0x5F23, 0x5F24, 0x5F28, 0x5F2B, 0x5F2C, 0x5F2E, 0x5F30, 0x5F32, 0x5F33, 0x5F34, 0x5F35, 0x5F36, 0x5F37, 0x5F38, 0x5F3B, 0x5F3D, 0x5F3E, 0x5F3F, 0x5F41, 0x5F42, 0x5F43, 0x5F44, 0x5F45, 0x5F46, 0x5F47, 0x5F48, 0x5F49, 0x5F4A, 0x5F4B, 0x5F4C, 0x5F4D, 0x5F4E, 0x5F4F, 0x5F51, + 0x5F54, 0x5F59, 0x5F5A, 0x5F5B, 0x5F5C, 0x5F5E, 0x5F5F, 0x5F60, 0x5F63, 0x5F65, 0x5F67, 0x5F68, 0x5F6B, 0x5F6E, 0x5F6F, 0x5F72, 0x5F74, 0x5F75, 0x5F76, 0x5F78, 0x5F7A, 0x5F7D, 0x5F7E, 0x5F7F, 0x5F83, 0x5F86, 0x5F8D, 0x5F8E, 0x5F8F, 0x5F91, 0x5F93, 0x5F94, 0x5F96, 0x5F9A, 0x5F9B, 0x5F9D, 0x5F9E, 0x5F9F, 0x5FA0, 0x5FA2, 0x5FA3, 0x5FA4, 0x5FA5, 0x5FA6, 0x5FA7, 0x5FA9, 0x5FAB, 0x5FAC, 0x5FAF, 0x5FB0, 0x5FB1, 0x5FB2, 0x5FB3, 0x5FB4, 0x5FB6, 0x5FB8, 0x5FB9, 0x5FBA, 0x5FBB, 0x5FBE, 0x5FBF, 0x5FC0, 0x5FC1, 0x5FC2, 0x5FC7, 0x5FC8, 0x5FCA, 0x5FCB, 0x5FCE, 0x5FD3, 0x5FD4, 0x5FD5, 0x5FDA, 0x5FDB, 0x5FDC, 0x5FDE, 0x5FDF, 0x5FE2, 0x5FE3, 0x5FE5, 0x5FE6, 0x5FE8, 0x5FE9, 0x5FEC, 0x5FEF, 0x5FF0, 0x5FF2, 0x5FF3, 0x5FF4, 0x5FF6, 0x5FF7, 0x5FF9, 0x5FFA, 0x5FFC, 0x6007, + 0x6008, 0x6009, 0x600B, 0x600C, 0x6010, 0x6011, 0x6013, 0x6017, 0x6018, 0x601A, 0x601E, 0x601F, 0x6022, 0x6023, 0x6024, 0x602C, 0x602D, 0x602E, 0x6030, 0x6031, 0x6032, 0x6033, 0x6034, 0x6036, 0x6037, 0x6038, 0x6039, 0x603A, 0x603D, 0x603E, 0x6040, 0x6044, 0x6045, 0x6046, 0x6047, 0x6048, 0x6049, 0x604A, 0x604C, 0x604E, 0x604F, 0x6051, 0x6053, 0x6054, 0x6056, 0x6057, 0x6058, 0x605B, 0x605C, 0x605E, 0x605F, 0x6060, 0x6061, 0x6065, 0x6066, 0x606E, 0x6071, 0x6072, 0x6074, 0x6075, 0x6077, 0x607E, 0x6080, 0x6081, 0x6082, 0x6085, 0x6086, 0x6087, 0x6088, 0x608A, 0x608B, 0x608E, 0x608F, 0x6090, 0x6091, 0x6093, 0x6095, 0x6097, 0x6098, 0x6099, 0x609C, 0x609E, 0x60A1, 0x60A2, 0x60A4, 0x60A5, 0x60A7, 0x60A9, 0x60AA, 0x60AE, 0x60B0, 0x60B3, 0x60B5, 0x60B6, 0x60B7, + 0x60B9, 0x60BA, 0x60BD, 0x60BE, 0x60BF, 0x60C0, 0x60C1, 0x60C2, 0x60C3, 0x60C4, 0x60C7, 0x60C8, 0x60C9, 0x60CC, 0x60CD, 0x60CE, 0x60CF, 0x60D0, 0x60D2, 0x60D3, 0x60D4, 0x60D6, 0x60D7, 0x60D9, 0x60DB, 0x60DE, 0x60E1, 0x60E2, 0x60E3, 0x60E4, 0x60E5, 0x60EA, 0x60F1, 0x60F2, 0x60F5, 0x60F7, 0x60F8, 0x60FB, 0x60FC, 0x60FD, 0x60FE, 0x60FF, 0x6102, 0x6103, 0x6104, 0x6105, 0x6107, 0x610A, 0x610B, 0x610C, 0x6110, 0x6111, 0x6112, 0x6113, 0x6114, 0x6116, 0x6117, 0x6118, 0x6119, 0x611B, 0x611C, 0x611D, 0x611E, 0x6121, 0x6122, 0x6125, 0x6128, 0x6129, 0x612A, 0x612C, 0x612D, 0x612E, 0x612F, 0x6130, 0x6131, 0x6132, 0x6133, 0x6134, 0x6135, 0x6136, 0x6137, 0x6138, 0x6139, 0x613A, 0x613B, 0x613C, 0x613D, 0x613E, 0x6140, 0x6141, 0x6142, 0x6143, 0x6144, 0x6145, 0x6146, + 0x6147, 0x6149, 0x614B, 0x614D, 0x614F, 0x6150, 0x6152, 0x6153, 0x6154, 0x6156, 0x6157, 0x6158, 0x6159, 0x615A, 0x615B, 0x615C, 0x615E, 0x615F, 0x6160, 0x6161, 0x6163, 0x6164, 0x6165, 0x6166, 0x6169, 0x616A, 0x616B, 0x616C, 0x616D, 0x616E, 0x616F, 0x6171, 0x6172, 0x6173, 0x6174, 0x6176, 0x6178, 0x6179, 0x617A, 0x617B, 0x617C, 0x617D, 0x617E, 0x617F, 0x6180, 0x6181, 0x6182, 0x6183, 0x6184, 0x6185, 0x6186, 0x6187, 0x6188, 0x6189, 0x618A, 0x618C, 0x618D, 0x618F, 0x6190, 0x6191, 0x6192, 0x6193, 0x6195, 0x6196, 0x6197, 0x6198, 0x6199, 0x619A, 0x619B, 0x619C, 0x619E, 0x619F, 0x61A0, 0x61A1, 0x61A2, 0x61A3, 0x61A4, 0x61A5, 0x61A6, 0x61AA, 0x61AB, 0x61AD, 0x61AE, 0x61AF, 0x61B0, 0x61B1, 0x61B2, 0x61B3, 0x61B4, 0x61B5, 0x61B6, 0x61B8, 0x61B9, 0x61BA, 0x61BB, + 0x61BC, 0x61BD, 0x61BF, 0x61C0, 0x61C1, 0x61C3, 0x61C4, 0x61C5, 0x61C6, 0x61C7, 0x61C9, 0x61CC, 0x61CD, 0x61CE, 0x61CF, 0x61D0, 0x61D3, 0x61D5, 0x61D6, 0x61D7, 0x61D8, 0x61D9, 0x61DA, 0x61DB, 0x61DC, 0x61DD, 0x61DE, 0x61DF, 0x61E0, 0x61E1, 0x61E2, 0x61E3, 0x61E4, 0x61E5, 0x61E7, 0x61E8, 0x61E9, 0x61EA, 0x61EB, 0x61EC, 0x61ED, 0x61EE, 0x61EF, 0x61F0, 0x61F1, 0x61F2, 0x61F3, 0x61F4, 0x61F6, 0x61F7, 0x61F8, 0x61F9, 0x61FA, 0x61FB, 0x61FC, 0x61FD, 0x61FE, 0x6200, 0x6201, 0x6202, 0x6203, 0x6204, 0x6205, 0x6207, 0x6209, 0x6213, 0x6214, 0x6219, 0x621C, 0x621D, 0x621E, 0x6220, 0x6223, 0x6226, 0x6227, 0x6228, 0x6229, 0x622B, 0x622D, 0x622F, 0x6230, 0x6231, 0x6232, 0x6235, 0x6236, 0x6238, 0x6239, 0x623A, 0x623B, 0x623C, 0x6242, 0x6244, 0x6245, 0x6246, 0x624A, + 0x624F, 0x6250, 0x6255, 0x6256, 0x6257, 0x6259, 0x625A, 0x625C, 0x625D, 0x625E, 0x625F, 0x6260, 0x6261, 0x6262, 0x6264, 0x6265, 0x6268, 0x6271, 0x6272, 0x6274, 0x6275, 0x6277, 0x6278, 0x627A, 0x627B, 0x627D, 0x6281, 0x6282, 0x6283, 0x6285, 0x6286, 0x6287, 0x6288, 0x628B, 0x628C, 0x628D, 0x628E, 0x628F, 0x6290, 0x6294, 0x6299, 0x629C, 0x629D, 0x629E, 0x62A3, 0x62A6, 0x62A7, 0x62A9, 0x62AA, 0x62AD, 0x62AE, 0x62AF, 0x62B0, 0x62B2, 0x62B3, 0x62B4, 0x62B6, 0x62B7, 0x62B8, 0x62BA, 0x62BE, 0x62C0, 0x62C1, 0x62C3, 0x62CB, 0x62CF, 0x62D1, 0x62D5, 0x62DD, 0x62DE, 0x62E0, 0x62E1, 0x62E4, 0x62EA, 0x62EB, 0x62F0, 0x62F2, 0x62F5, 0x62F8, 0x62F9, 0x62FA, 0x62FB, 0x6300, 0x6303, 0x6304, 0x6305, 0x6306, 0x630A, 0x630B, 0x630C, 0x630D, 0x630F, 0x6310, 0x6312, 0x6313, + 0x6314, 0x6315, 0x6317, 0x6318, 0x6319, 0x631C, 0x6326, 0x6327, 0x6329, 0x632C, 0x632D, 0x632E, 0x6330, 0x6331, 0x6333, 0x6334, 0x6335, 0x6336, 0x6337, 0x6338, 0x633B, 0x633C, 0x633E, 0x633F, 0x6340, 0x6341, 0x6344, 0x6347, 0x6348, 0x634A, 0x6351, 0x6352, 0x6353, 0x6354, 0x6356, 0x6357, 0x6358, 0x6359, 0x635A, 0x635B, 0x635C, 0x635D, 0x6360, 0x6364, 0x6365, 0x6366, 0x6368, 0x636A, 0x636B, 0x636C, 0x636F, 0x6370, 0x6372, 0x6373, 0x6374, 0x6375, 0x6378, 0x6379, 0x637C, 0x637D, 0x637E, 0x637F, 0x6381, 0x6383, 0x6384, 0x6385, 0x6386, 0x638B, 0x638D, 0x6391, 0x6393, 0x6394, 0x6395, 0x6397, 0x6399, 0x639A, 0x639B, 0x639C, 0x639D, 0x639E, 0x639F, 0x63A1, 0x63A4, 0x63A6, 0x63AB, 0x63AF, 0x63B1, 0x63B2, 0x63B5, 0x63B6, 0x63B9, 0x63BB, 0x63BD, 0x63BF, 0x63C0, + 0x63C1, 0x63C2, 0x63C3, 0x63C5, 0x63C7, 0x63C8, 0x63CA, 0x63CB, 0x63CC, 0x63D1, 0x63D3, 0x63D4, 0x63D5, 0x63D7, 0x63D8, 0x63D9, 0x63DA, 0x63DB, 0x63DC, 0x63DD, 0x63DF, 0x63E2, 0x63E4, 0x63E5, 0x63E6, 0x63E7, 0x63E8, 0x63EB, 0x63EC, 0x63EE, 0x63EF, 0x63F0, 0x63F1, 0x63F3, 0x63F5, 0x63F7, 0x63F9, 0x63FA, 0x63FB, 0x63FC, 0x63FE, 0x6403, 0x6404, 0x6406, 0x6407, 0x6408, 0x6409, 0x640A, 0x640D, 0x640E, 0x6411, 0x6412, 0x6415, 0x6416, 0x6417, 0x6418, 0x6419, 0x641A, 0x641D, 0x641F, 0x6422, 0x6423, 0x6424, 0x6425, 0x6427, 0x6428, 0x6429, 0x642B, 0x642E, 0x642F, 0x6430, 0x6431, 0x6432, 0x6433, 0x6435, 0x6436, 0x6437, 0x6438, 0x6439, 0x643B, 0x643C, 0x643E, 0x6440, 0x6442, 0x6443, 0x6449, 0x644B, 0x644C, 0x644D, 0x644E, 0x644F, 0x6450, 0x6451, 0x6453, 0x6455, + 0x6456, 0x6457, 0x6459, 0x645A, 0x645B, 0x645C, 0x645D, 0x645F, 0x6460, 0x6461, 0x6462, 0x6463, 0x6464, 0x6465, 0x6466, 0x6468, 0x646A, 0x646B, 0x646C, 0x646E, 0x646F, 0x6470, 0x6471, 0x6472, 0x6473, 0x6474, 0x6475, 0x6476, 0x6477, 0x647B, 0x647C, 0x647D, 0x647E, 0x647F, 0x6480, 0x6481, 0x6483, 0x6486, 0x6488, 0x6489, 0x648A, 0x648B, 0x648C, 0x648D, 0x648E, 0x648F, 0x6490, 0x6493, 0x6494, 0x6497, 0x6498, 0x649A, 0x649B, 0x649C, 0x649D, 0x649F, 0x64A0, 0x64A1, 0x64A2, 0x64A3, 0x64A5, 0x64A6, 0x64A7, 0x64A8, 0x64AA, 0x64AB, 0x64AF, 0x64B1, 0x64B2, 0x64B3, 0x64B4, 0x64B6, 0x64B9, 0x64BB, 0x64BD, 0x64BE, 0x64BF, 0x64C1, 0x64C3, 0x64C4, 0x64C6, 0x64C7, 0x64C8, 0x64C9, 0x64CA, 0x64CB, 0x64CC, 0x64CF, 0x64D1, 0x64D3, 0x64D4, 0x64D5, 0x64D6, 0x64D9, 0x64DA, + 0x64DB, 0x64DC, 0x64DD, 0x64DF, 0x64E0, 0x64E1, 0x64E3, 0x64E5, 0x64E7, 0x64E8, 0x64E9, 0x64EA, 0x64EB, 0x64EC, 0x64ED, 0x64EE, 0x64EF, 0x64F0, 0x64F1, 0x64F2, 0x64F3, 0x64F4, 0x64F5, 0x64F6, 0x64F7, 0x64F8, 0x64F9, 0x64FA, 0x64FB, 0x64FC, 0x64FD, 0x64FE, 0x64FF, 0x6501, 0x6502, 0x6503, 0x6504, 0x6505, 0x6506, 0x6507, 0x6508, 0x650A, 0x650B, 0x650C, 0x650D, 0x650E, 0x650F, 0x6510, 0x6511, 0x6513, 0x6514, 0x6515, 0x6516, 0x6517, 0x6519, 0x651A, 0x651B, 0x651C, 0x651D, 0x651E, 0x651F, 0x6520, 0x6521, 0x6522, 0x6523, 0x6524, 0x6526, 0x6527, 0x6528, 0x6529, 0x652A, 0x652C, 0x652D, 0x6530, 0x6531, 0x6532, 0x6533, 0x6537, 0x653A, 0x653C, 0x653D, 0x6540, 0x6541, 0x6542, 0x6543, 0x6544, 0x6546, 0x6547, 0x654A, 0x654B, 0x654D, 0x654E, 0x6550, 0x6552, 0x6553, + 0x6554, 0x6557, 0x6558, 0x655A, 0x655C, 0x655F, 0x6560, 0x6561, 0x6564, 0x6565, 0x6567, 0x6568, 0x6569, 0x656A, 0x656D, 0x656E, 0x656F, 0x6571, 0x6573, 0x6575, 0x6576, 0x6578, 0x6579, 0x657A, 0x657B, 0x657C, 0x657D, 0x657E, 0x657F, 0x6580, 0x6581, 0x6582, 0x6583, 0x6584, 0x6585, 0x6586, 0x6588, 0x6589, 0x658A, 0x658D, 0x658E, 0x658F, 0x6592, 0x6594, 0x6595, 0x6596, 0x6598, 0x659A, 0x659D, 0x659E, 0x65A0, 0x65A2, 0x65A3, 0x65A6, 0x65A8, 0x65AA, 0x65AC, 0x65AE, 0x65B1, 0x65B2, 0x65B3, 0x65B4, 0x65B5, 0x65B6, 0x65B7, 0x65B8, 0x65BA, 0x65BB, 0x65BE, 0x65BF, 0x65C0, 0x65C2, 0x65C7, 0x65C8, 0x65C9, 0x65CA, 0x65CD, 0x65D0, 0x65D1, 0x65D3, 0x65D4, 0x65D5, 0x65D8, 0x65D9, 0x65DA, 0x65DB, 0x65DC, 0x65DD, 0x65DE, 0x65DF, 0x65E1, 0x65E3, 0x65E4, 0x65EA, 0x65EB, + 0x65F2, 0x65F3, 0x65F4, 0x65F5, 0x65F8, 0x65F9, 0x65FB, 0x65FC, 0x65FD, 0x65FE, 0x65FF, 0x6601, 0x6604, 0x6605, 0x6607, 0x6608, 0x6609, 0x660B, 0x660D, 0x6610, 0x6611, 0x6612, 0x6616, 0x6617, 0x6618, 0x661A, 0x661B, 0x661C, 0x661E, 0x6621, 0x6622, 0x6623, 0x6624, 0x6626, 0x6629, 0x662A, 0x662B, 0x662C, 0x662E, 0x6630, 0x6632, 0x6633, 0x6637, 0x6638, 0x6639, 0x663A, 0x663B, 0x663D, 0x663F, 0x6640, 0x6642, 0x6644, 0x6645, 0x6646, 0x6647, 0x6648, 0x6649, 0x664A, 0x664D, 0x664E, 0x6650, 0x6651, 0x6658, 0x6659, 0x665B, 0x665C, 0x665D, 0x665E, 0x6660, 0x6662, 0x6663, 0x6665, 0x6667, 0x6669, 0x666A, 0x666B, 0x666C, 0x666D, 0x6671, 0x6672, 0x6673, 0x6675, 0x6678, 0x6679, 0x667B, 0x667C, 0x667D, 0x667F, 0x6680, 0x6681, 0x6683, 0x6685, 0x6686, 0x6688, 0x6689, + 0x668A, 0x668B, 0x668D, 0x668E, 0x668F, 0x6690, 0x6692, 0x6693, 0x6694, 0x6695, 0x6698, 0x6699, 0x669A, 0x669B, 0x669C, 0x669E, 0x669F, 0x66A0, 0x66A1, 0x66A2, 0x66A3, 0x66A4, 0x66A5, 0x66A6, 0x66A9, 0x66AA, 0x66AB, 0x66AC, 0x66AD, 0x66AF, 0x66B0, 0x66B1, 0x66B2, 0x66B3, 0x66B5, 0x66B6, 0x66B7, 0x66B8, 0x66BA, 0x66BB, 0x66BC, 0x66BD, 0x66BF, 0x66C0, 0x66C1, 0x66C2, 0x66C3, 0x66C4, 0x66C5, 0x66C6, 0x66C7, 0x66C8, 0x66C9, 0x66CA, 0x66CB, 0x66CC, 0x66CD, 0x66CE, 0x66CF, 0x66D0, 0x66D1, 0x66D2, 0x66D3, 0x66D4, 0x66D5, 0x66D6, 0x66D7, 0x66D8, 0x66DA, 0x66DE, 0x66DF, 0x66E0, 0x66E1, 0x66E2, 0x66E3, 0x66E4, 0x66E5, 0x66E7, 0x66E8, 0x66EA, 0x66EB, 0x66EC, 0x66ED, 0x66EE, 0x66EF, 0x66F1, 0x66F5, 0x66F6, 0x66F8, 0x66FA, 0x66FB, 0x66FD, 0x6701, 0x6702, 0x6703, + 0x6704, 0x6705, 0x6706, 0x6707, 0x670C, 0x670E, 0x670F, 0x6711, 0x6712, 0x6713, 0x6716, 0x6718, 0x6719, 0x671A, 0x671C, 0x671E, 0x6720, 0x6721, 0x6722, 0x6723, 0x6724, 0x6725, 0x6727, 0x6729, 0x672E, 0x6730, 0x6732, 0x6733, 0x6736, 0x6737, 0x6738, 0x6739, 0x673B, 0x673C, 0x673E, 0x673F, 0x6741, 0x6744, 0x6745, 0x6747, 0x674A, 0x674B, 0x674D, 0x6752, 0x6754, 0x6755, 0x6757, 0x6758, 0x6759, 0x675A, 0x675B, 0x675D, 0x6762, 0x6763, 0x6764, 0x6766, 0x6767, 0x676B, 0x676C, 0x676E, 0x6771, 0x6774, 0x6776, 0x6778, 0x6779, 0x677A, 0x677B, 0x677D, 0x6780, 0x6782, 0x6783, 0x6785, 0x6786, 0x6788, 0x678A, 0x678C, 0x678D, 0x678E, 0x678F, 0x6791, 0x6792, 0x6793, 0x6794, 0x6796, 0x6799, 0x679B, 0x679F, 0x67A0, 0x67A1, 0x67A4, 0x67A6, 0x67A9, 0x67AC, 0x67AE, 0x67B1, + 0x67B2, 0x67B4, 0x67B9, 0x67BA, 0x67BB, 0x67BC, 0x67BD, 0x67BE, 0x67BF, 0x67C0, 0x67C2, 0x67C5, 0x67C6, 0x67C7, 0x67C8, 0x67C9, 0x67CA, 0x67CB, 0x67CC, 0x67CD, 0x67CE, 0x67D5, 0x67D6, 0x67D7, 0x67DB, 0x67DF, 0x67E1, 0x67E3, 0x67E4, 0x67E6, 0x67E7, 0x67E8, 0x67EA, 0x67EB, 0x67ED, 0x67EE, 0x67F2, 0x67F5, 0x67F6, 0x67F7, 0x67F8, 0x67F9, 0x67FA, 0x67FB, 0x67FC, 0x67FE, 0x6801, 0x6802, 0x6803, 0x6804, 0x6806, 0x680D, 0x6810, 0x6812, 0x6814, 0x6815, 0x6818, 0x6819, 0x681A, 0x681B, 0x681C, 0x681E, 0x681F, 0x6820, 0x6822, 0x6823, 0x6824, 0x6825, 0x6826, 0x6827, 0x6828, 0x682B, 0x682C, 0x682D, 0x682E, 0x682F, 0x6830, 0x6831, 0x6834, 0x6835, 0x6836, 0x683A, 0x683B, 0x683F, 0x6847, 0x684B, 0x684D, 0x684F, 0x6852, 0x6856, 0x6857, 0x6858, 0x6859, 0x685A, 0x685B, + 0x685C, 0x685D, 0x685E, 0x685F, 0x686A, 0x686C, 0x686D, 0x686E, 0x686F, 0x6870, 0x6871, 0x6872, 0x6873, 0x6875, 0x6878, 0x6879, 0x687A, 0x687B, 0x687C, 0x687D, 0x687E, 0x687F, 0x6880, 0x6882, 0x6884, 0x6887, 0x6888, 0x6889, 0x688A, 0x688B, 0x688C, 0x688D, 0x688E, 0x6890, 0x6891, 0x6892, 0x6894, 0x6895, 0x6896, 0x6898, 0x6899, 0x689A, 0x689B, 0x689C, 0x689D, 0x689E, 0x689F, 0x68A0, 0x68A1, 0x68A3, 0x68A4, 0x68A5, 0x68A9, 0x68AA, 0x68AB, 0x68AC, 0x68AE, 0x68B1, 0x68B2, 0x68B4, 0x68B6, 0x68B7, 0x68B8, 0x68B9, 0x68BA, 0x68BB, 0x68BC, 0x68BD, 0x68BE, 0x68BF, 0x68C1, 0x68C3, 0x68C4, 0x68C5, 0x68C6, 0x68C7, 0x68C8, 0x68CA, 0x68CC, 0x68CE, 0x68CF, 0x68D0, 0x68D1, 0x68D3, 0x68D4, 0x68D6, 0x68D7, 0x68D9, 0x68DB, 0x68DC, 0x68DD, 0x68DE, 0x68DF, 0x68E1, 0x68E2, + 0x68E4, 0x68E5, 0x68E6, 0x68E7, 0x68E8, 0x68E9, 0x68EA, 0x68EB, 0x68EC, 0x68ED, 0x68EF, 0x68F2, 0x68F3, 0x68F4, 0x68F6, 0x68F7, 0x68F8, 0x68FB, 0x68FD, 0x68FE, 0x68FF, 0x6900, 0x6902, 0x6903, 0x6904, 0x6906, 0x6907, 0x6908, 0x6909, 0x690A, 0x690C, 0x690F, 0x6911, 0x6913, 0x6914, 0x6915, 0x6916, 0x6917, 0x6918, 0x6919, 0x691A, 0x691B, 0x691C, 0x691D, 0x691E, 0x6921, 0x6922, 0x6923, 0x6925, 0x6926, 0x6927, 0x6928, 0x6929, 0x692A, 0x692B, 0x692C, 0x692E, 0x692F, 0x6931, 0x6932, 0x6933, 0x6935, 0x6936, 0x6937, 0x6938, 0x693A, 0x693B, 0x693C, 0x693E, 0x6940, 0x6941, 0x6943, 0x6944, 0x6945, 0x6946, 0x6947, 0x6948, 0x6949, 0x694A, 0x694B, 0x694C, 0x694D, 0x694E, 0x694F, 0x6950, 0x6951, 0x6952, 0x6953, 0x6955, 0x6956, 0x6958, 0x6959, 0x695B, 0x695C, 0x695F, + 0x6961, 0x6962, 0x6964, 0x6965, 0x6967, 0x6968, 0x6969, 0x696A, 0x696C, 0x696D, 0x696F, 0x6970, 0x6972, 0x6973, 0x6974, 0x6975, 0x6976, 0x697A, 0x697B, 0x697D, 0x697E, 0x697F, 0x6981, 0x6983, 0x6985, 0x698A, 0x698B, 0x698C, 0x698E, 0x698F, 0x6990, 0x6991, 0x6992, 0x6993, 0x6996, 0x6997, 0x6999, 0x699A, 0x699D, 0x699E, 0x699F, 0x69A0, 0x69A1, 0x69A2, 0x69A3, 0x69A4, 0x69A5, 0x69A6, 0x69A9, 0x69AA, 0x69AC, 0x69AE, 0x69AF, 0x69B0, 0x69B2, 0x69B3, 0x69B5, 0x69B6, 0x69B8, 0x69B9, 0x69BA, 0x69BC, 0x69BD, 0x69BE, 0x69BF, 0x69C0, 0x69C2, 0x69C3, 0x69C4, 0x69C5, 0x69C6, 0x69C7, 0x69C8, 0x69C9, 0x69CB, 0x69CD, 0x69CF, 0x69D1, 0x69D2, 0x69D3, 0x69D5, 0x69D6, 0x69D7, 0x69D8, 0x69D9, 0x69DA, 0x69DC, 0x69DD, 0x69DE, 0x69E1, 0x69E2, 0x69E3, 0x69E4, 0x69E5, 0x69E6, + 0x69E7, 0x69E8, 0x69E9, 0x69EA, 0x69EB, 0x69EC, 0x69EE, 0x69EF, 0x69F0, 0x69F1, 0x69F3, 0x69F4, 0x69F5, 0x69F6, 0x69F7, 0x69F8, 0x69F9, 0x69FA, 0x69FB, 0x69FC, 0x69FE, 0x6A00, 0x6A01, 0x6A02, 0x6A03, 0x6A04, 0x6A05, 0x6A06, 0x6A07, 0x6A08, 0x6A09, 0x6A0B, 0x6A0C, 0x6A0D, 0x6A0E, 0x6A0F, 0x6A10, 0x6A11, 0x6A12, 0x6A13, 0x6A14, 0x6A15, 0x6A16, 0x6A19, 0x6A1A, 0x6A1B, 0x6A1C, 0x6A1D, 0x6A1E, 0x6A20, 0x6A22, 0x6A23, 0x6A24, 0x6A25, 0x6A26, 0x6A27, 0x6A29, 0x6A2B, 0x6A2C, 0x6A2D, 0x6A2E, 0x6A30, 0x6A32, 0x6A33, 0x6A34, 0x6A36, 0x6A37, 0x6A38, 0x6A39, 0x6A3A, 0x6A3B, 0x6A3C, 0x6A3F, 0x6A40, 0x6A41, 0x6A42, 0x6A43, 0x6A45, 0x6A46, 0x6A48, 0x6A49, 0x6A4A, 0x6A4B, 0x6A4C, 0x6A4D, 0x6A4E, 0x6A4F, 0x6A51, 0x6A52, 0x6A53, 0x6A54, 0x6A55, 0x6A56, 0x6A57, 0x6A5A, + 0x6A5C, 0x6A5D, 0x6A5E, 0x6A5F, 0x6A60, 0x6A62, 0x6A63, 0x6A64, 0x6A66, 0x6A67, 0x6A68, 0x6A69, 0x6A6A, 0x6A6B, 0x6A6C, 0x6A6D, 0x6A6E, 0x6A6F, 0x6A70, 0x6A72, 0x6A73, 0x6A74, 0x6A75, 0x6A76, 0x6A77, 0x6A78, 0x6A7A, 0x6A7B, 0x6A7D, 0x6A7E, 0x6A7F, 0x6A81, 0x6A82, 0x6A83, 0x6A85, 0x6A86, 0x6A87, 0x6A88, 0x6A89, 0x6A8A, 0x6A8B, 0x6A8C, 0x6A8D, 0x6A8F, 0x6A92, 0x6A93, 0x6A94, 0x6A95, 0x6A96, 0x6A98, 0x6A99, 0x6A9A, 0x6A9B, 0x6A9C, 0x6A9D, 0x6A9E, 0x6A9F, 0x6AA1, 0x6AA2, 0x6AA3, 0x6AA4, 0x6AA5, 0x6AA6, 0x6AA7, 0x6AA8, 0x6AAA, 0x6AAD, 0x6AAE, 0x6AAF, 0x6AB0, 0x6AB1, 0x6AB2, 0x6AB3, 0x6AB4, 0x6AB5, 0x6AB6, 0x6AB7, 0x6AB8, 0x6AB9, 0x6ABA, 0x6ABB, 0x6ABC, 0x6ABD, 0x6ABE, 0x6ABF, 0x6AC0, 0x6AC1, 0x6AC2, 0x6AC3, 0x6AC4, 0x6AC5, 0x6AC6, 0x6AC7, 0x6AC8, 0x6AC9, + 0x6ACA, 0x6ACB, 0x6ACC, 0x6ACD, 0x6ACE, 0x6ACF, 0x6AD0, 0x6AD1, 0x6AD2, 0x6AD3, 0x6AD4, 0x6AD5, 0x6AD6, 0x6AD7, 0x6AD8, 0x6AD9, 0x6ADA, 0x6ADB, 0x6ADC, 0x6ADD, 0x6ADE, 0x6ADF, 0x6AE0, 0x6AE1, 0x6AE2, 0x6AE3, 0x6AE4, 0x6AE5, 0x6AE6, 0x6AE7, 0x6AE8, 0x6AE9, 0x6AEA, 0x6AEB, 0x6AEC, 0x6AED, 0x6AEE, 0x6AEF, 0x6AF0, 0x6AF1, 0x6AF2, 0x6AF3, 0x6AF4, 0x6AF5, 0x6AF6, 0x6AF7, 0x6AF8, 0x6AF9, 0x6AFA, 0x6AFB, 0x6AFC, 0x6AFD, 0x6AFE, 0x6AFF, 0x6B00, 0x6B01, 0x6B02, 0x6B03, 0x6B04, 0x6B05, 0x6B06, 0x6B07, 0x6B08, 0x6B09, 0x6B0A, 0x6B0B, 0x6B0C, 0x6B0D, 0x6B0E, 0x6B0F, 0x6B10, 0x6B11, 0x6B12, 0x6B13, 0x6B14, 0x6B15, 0x6B16, 0x6B17, 0x6B18, 0x6B19, 0x6B1A, 0x6B1B, 0x6B1C, 0x6B1D, 0x6B1E, 0x6B1F, 0x6B25, 0x6B26, 0x6B28, 0x6B29, 0x6B2A, 0x6B2B, 0x6B2C, 0x6B2D, 0x6B2E, + 0x6B2F, 0x6B30, 0x6B31, 0x6B33, 0x6B34, 0x6B35, 0x6B36, 0x6B38, 0x6B3B, 0x6B3C, 0x6B3D, 0x6B3F, 0x6B40, 0x6B41, 0x6B42, 0x6B44, 0x6B45, 0x6B48, 0x6B4A, 0x6B4B, 0x6B4D, 0x6B4E, 0x6B4F, 0x6B50, 0x6B51, 0x6B52, 0x6B53, 0x6B54, 0x6B55, 0x6B56, 0x6B57, 0x6B58, 0x6B5A, 0x6B5B, 0x6B5C, 0x6B5D, 0x6B5E, 0x6B5F, 0x6B60, 0x6B61, 0x6B68, 0x6B69, 0x6B6B, 0x6B6C, 0x6B6D, 0x6B6E, 0x6B6F, 0x6B70, 0x6B71, 0x6B72, 0x6B73, 0x6B74, 0x6B75, 0x6B76, 0x6B77, 0x6B78, 0x6B7A, 0x6B7D, 0x6B7E, 0x6B7F, 0x6B80, 0x6B85, 0x6B88, 0x6B8C, 0x6B8E, 0x6B8F, 0x6B90, 0x6B91, 0x6B94, 0x6B95, 0x6B97, 0x6B98, 0x6B99, 0x6B9C, 0x6B9D, 0x6B9E, 0x6B9F, 0x6BA0, 0x6BA2, 0x6BA3, 0x6BA4, 0x6BA5, 0x6BA6, 0x6BA7, 0x6BA8, 0x6BA9, 0x6BAB, 0x6BAC, 0x6BAD, 0x6BAE, 0x6BAF, 0x6BB0, 0x6BB1, 0x6BB2, 0x6BB6, + 0x6BB8, 0x6BB9, 0x6BBA, 0x6BBB, 0x6BBC, 0x6BBD, 0x6BBE, 0x6BC0, 0x6BC3, 0x6BC4, 0x6BC6, 0x6BC7, 0x6BC8, 0x6BC9, 0x6BCA, 0x6BCC, 0x6BCE, 0x6BD0, 0x6BD1, 0x6BD8, 0x6BDA, 0x6BDC, 0x6BDD, 0x6BDE, 0x6BDF, 0x6BE0, 0x6BE2, 0x6BE3, 0x6BE4, 0x6BE5, 0x6BE6, 0x6BE7, 0x6BE8, 0x6BE9, 0x6BEC, 0x6BED, 0x6BEE, 0x6BF0, 0x6BF1, 0x6BF2, 0x6BF4, 0x6BF6, 0x6BF7, 0x6BF8, 0x6BFA, 0x6BFB, 0x6BFC, 0x6BFE, 0x6BFF, 0x6C00, 0x6C01, 0x6C02, 0x6C03, 0x6C04, 0x6C08, 0x6C09, 0x6C0A, 0x6C0B, 0x6C0C, 0x6C0E, 0x6C12, 0x6C17, 0x6C1C, 0x6C1D, 0x6C1E, 0x6C20, 0x6C23, 0x6C25, 0x6C2B, 0x6C2C, 0x6C2D, 0x6C31, 0x6C33, 0x6C36, 0x6C37, 0x6C39, 0x6C3A, 0x6C3B, 0x6C3C, 0x6C3E, 0x6C3F, 0x6C43, 0x6C44, 0x6C45, 0x6C48, 0x6C4B, 0x6C4C, 0x6C4D, 0x6C4E, 0x6C4F, 0x6C51, 0x6C52, 0x6C53, 0x6C56, 0x6C58, + 0x6C59, 0x6C5A, 0x6C62, 0x6C63, 0x6C65, 0x6C66, 0x6C67, 0x6C6B, 0x6C6C, 0x6C6D, 0x6C6E, 0x6C6F, 0x6C71, 0x6C73, 0x6C75, 0x6C77, 0x6C78, 0x6C7A, 0x6C7B, 0x6C7C, 0x6C7F, 0x6C80, 0x6C84, 0x6C87, 0x6C8A, 0x6C8B, 0x6C8D, 0x6C8E, 0x6C91, 0x6C92, 0x6C95, 0x6C96, 0x6C97, 0x6C98, 0x6C9A, 0x6C9C, 0x6C9D, 0x6C9E, 0x6CA0, 0x6CA2, 0x6CA8, 0x6CAC, 0x6CAF, 0x6CB0, 0x6CB4, 0x6CB5, 0x6CB6, 0x6CB7, 0x6CBA, 0x6CC0, 0x6CC1, 0x6CC2, 0x6CC3, 0x6CC6, 0x6CC7, 0x6CC8, 0x6CCB, 0x6CCD, 0x6CCE, 0x6CCF, 0x6CD1, 0x6CD2, 0x6CD8, 0x6CD9, 0x6CDA, 0x6CDC, 0x6CDD, 0x6CDF, 0x6CE4, 0x6CE6, 0x6CE7, 0x6CE9, 0x6CEC, 0x6CED, 0x6CF2, 0x6CF4, 0x6CF9, 0x6CFF, 0x6D00, 0x6D02, 0x6D03, 0x6D05, 0x6D06, 0x6D08, 0x6D09, 0x6D0A, 0x6D0D, 0x6D0F, 0x6D10, 0x6D11, 0x6D13, 0x6D14, 0x6D15, 0x6D16, 0x6D18, + 0x6D1C, 0x6D1D, 0x6D1F, 0x6D20, 0x6D21, 0x6D22, 0x6D23, 0x6D24, 0x6D26, 0x6D28, 0x6D29, 0x6D2C, 0x6D2D, 0x6D2F, 0x6D30, 0x6D34, 0x6D36, 0x6D37, 0x6D38, 0x6D3A, 0x6D3F, 0x6D40, 0x6D42, 0x6D44, 0x6D49, 0x6D4C, 0x6D50, 0x6D55, 0x6D56, 0x6D57, 0x6D58, 0x6D5B, 0x6D5D, 0x6D5F, 0x6D61, 0x6D62, 0x6D64, 0x6D65, 0x6D67, 0x6D68, 0x6D6B, 0x6D6C, 0x6D6D, 0x6D70, 0x6D71, 0x6D72, 0x6D73, 0x6D75, 0x6D76, 0x6D79, 0x6D7A, 0x6D7B, 0x6D7D, 0x6D7E, 0x6D7F, 0x6D80, 0x6D81, 0x6D83, 0x6D84, 0x6D86, 0x6D87, 0x6D8A, 0x6D8B, 0x6D8D, 0x6D8F, 0x6D90, 0x6D92, 0x6D96, 0x6D97, 0x6D98, 0x6D99, 0x6D9A, 0x6D9C, 0x6DA2, 0x6DA5, 0x6DAC, 0x6DAD, 0x6DB0, 0x6DB1, 0x6DB3, 0x6DB4, 0x6DB6, 0x6DB7, 0x6DB9, 0x6DBA, 0x6DBB, 0x6DBC, 0x6DBD, 0x6DBE, 0x6DC1, 0x6DC2, 0x6DC3, 0x6DC8, 0x6DC9, 0x6DCA, + 0x6DCD, 0x6DCE, 0x6DCF, 0x6DD0, 0x6DD2, 0x6DD3, 0x6DD4, 0x6DD5, 0x6DD7, 0x6DDA, 0x6DDB, 0x6DDC, 0x6DDF, 0x6DE2, 0x6DE3, 0x6DE5, 0x6DE7, 0x6DE8, 0x6DE9, 0x6DEA, 0x6DED, 0x6DEF, 0x6DF0, 0x6DF2, 0x6DF4, 0x6DF5, 0x6DF6, 0x6DF8, 0x6DFA, 0x6DFD, 0x6DFE, 0x6DFF, 0x6E00, 0x6E01, 0x6E02, 0x6E03, 0x6E04, 0x6E06, 0x6E07, 0x6E08, 0x6E09, 0x6E0B, 0x6E0F, 0x6E12, 0x6E13, 0x6E15, 0x6E18, 0x6E19, 0x6E1B, 0x6E1C, 0x6E1E, 0x6E1F, 0x6E22, 0x6E26, 0x6E27, 0x6E28, 0x6E2A, 0x6E2C, 0x6E2E, 0x6E30, 0x6E31, 0x6E33, 0x6E35, 0x6E36, 0x6E37, 0x6E39, 0x6E3B, 0x6E3C, 0x6E3D, 0x6E3E, 0x6E3F, 0x6E40, 0x6E41, 0x6E42, 0x6E45, 0x6E46, 0x6E47, 0x6E48, 0x6E49, 0x6E4A, 0x6E4B, 0x6E4C, 0x6E4F, 0x6E50, 0x6E51, 0x6E52, 0x6E55, 0x6E57, 0x6E59, 0x6E5A, 0x6E5C, 0x6E5D, 0x6E5E, 0x6E60, 0x6E61, + 0x6E62, 0x6E63, 0x6E64, 0x6E65, 0x6E66, 0x6E67, 0x6E68, 0x6E69, 0x6E6A, 0x6E6C, 0x6E6D, 0x6E6F, 0x6E70, 0x6E71, 0x6E72, 0x6E73, 0x6E74, 0x6E75, 0x6E76, 0x6E77, 0x6E78, 0x6E79, 0x6E7A, 0x6E7B, 0x6E7C, 0x6E7D, 0x6E80, 0x6E81, 0x6E82, 0x6E84, 0x6E87, 0x6E88, 0x6E8A, 0x6E8B, 0x6E8C, 0x6E8D, 0x6E8E, 0x6E91, 0x6E92, 0x6E93, 0x6E94, 0x6E95, 0x6E96, 0x6E97, 0x6E99, 0x6E9A, 0x6E9B, 0x6E9D, 0x6E9E, 0x6EA0, 0x6EA1, 0x6EA3, 0x6EA4, 0x6EA6, 0x6EA8, 0x6EA9, 0x6EAB, 0x6EAC, 0x6EAD, 0x6EAE, 0x6EB0, 0x6EB3, 0x6EB5, 0x6EB8, 0x6EB9, 0x6EBC, 0x6EBE, 0x6EBF, 0x6EC0, 0x6EC3, 0x6EC4, 0x6EC5, 0x6EC6, 0x6EC8, 0x6EC9, 0x6ECA, 0x6ECC, 0x6ECD, 0x6ECE, 0x6ED0, 0x6ED2, 0x6ED6, 0x6ED8, 0x6ED9, 0x6EDB, 0x6EDC, 0x6EDD, 0x6EE3, 0x6EE7, 0x6EEA, 0x6EEB, 0x6EEC, 0x6EED, 0x6EEE, 0x6EEF, + 0x6EF0, 0x6EF1, 0x6EF2, 0x6EF3, 0x6EF5, 0x6EF6, 0x6EF7, 0x6EF8, 0x6EFA, 0x6EFB, 0x6EFC, 0x6EFD, 0x6EFE, 0x6EFF, 0x6F00, 0x6F01, 0x6F03, 0x6F04, 0x6F05, 0x6F07, 0x6F08, 0x6F0A, 0x6F0B, 0x6F0C, 0x6F0D, 0x6F0E, 0x6F10, 0x6F11, 0x6F12, 0x6F16, 0x6F17, 0x6F18, 0x6F19, 0x6F1A, 0x6F1B, 0x6F1C, 0x6F1D, 0x6F1E, 0x6F1F, 0x6F21, 0x6F22, 0x6F23, 0x6F25, 0x6F26, 0x6F27, 0x6F28, 0x6F2C, 0x6F2E, 0x6F30, 0x6F32, 0x6F34, 0x6F35, 0x6F37, 0x6F38, 0x6F39, 0x6F3A, 0x6F3B, 0x6F3C, 0x6F3D, 0x6F3F, 0x6F40, 0x6F41, 0x6F42, 0x6F43, 0x6F44, 0x6F45, 0x6F48, 0x6F49, 0x6F4A, 0x6F4C, 0x6F4E, 0x6F4F, 0x6F50, 0x6F51, 0x6F52, 0x6F53, 0x6F54, 0x6F55, 0x6F56, 0x6F57, 0x6F59, 0x6F5A, 0x6F5B, 0x6F5D, 0x6F5F, 0x6F60, 0x6F61, 0x6F63, 0x6F64, 0x6F65, 0x6F67, 0x6F68, 0x6F69, 0x6F6A, 0x6F6B, + 0x6F6C, 0x6F6F, 0x6F70, 0x6F71, 0x6F73, 0x6F75, 0x6F76, 0x6F77, 0x6F79, 0x6F7B, 0x6F7D, 0x6F7E, 0x6F7F, 0x6F80, 0x6F81, 0x6F82, 0x6F83, 0x6F85, 0x6F86, 0x6F87, 0x6F8A, 0x6F8B, 0x6F8F, 0x6F90, 0x6F91, 0x6F92, 0x6F93, 0x6F94, 0x6F95, 0x6F96, 0x6F97, 0x6F98, 0x6F99, 0x6F9A, 0x6F9B, 0x6F9D, 0x6F9E, 0x6F9F, 0x6FA0, 0x6FA2, 0x6FA3, 0x6FA4, 0x6FA5, 0x6FA6, 0x6FA8, 0x6FA9, 0x6FAA, 0x6FAB, 0x6FAC, 0x6FAD, 0x6FAE, 0x6FAF, 0x6FB0, 0x6FB1, 0x6FB2, 0x6FB4, 0x6FB5, 0x6FB7, 0x6FB8, 0x6FBA, 0x6FBB, 0x6FBC, 0x6FBD, 0x6FBE, 0x6FBF, 0x6FC1, 0x6FC3, 0x6FC4, 0x6FC5, 0x6FC6, 0x6FC7, 0x6FC8, 0x6FCA, 0x6FCB, 0x6FCC, 0x6FCD, 0x6FCE, 0x6FCF, 0x6FD0, 0x6FD3, 0x6FD4, 0x6FD5, 0x6FD6, 0x6FD7, 0x6FD8, 0x6FD9, 0x6FDA, 0x6FDB, 0x6FDC, 0x6FDD, 0x6FDF, 0x6FE2, 0x6FE3, 0x6FE4, 0x6FE5, + 0x6FE6, 0x6FE7, 0x6FE8, 0x6FE9, 0x6FEA, 0x6FEB, 0x6FEC, 0x6FED, 0x6FF0, 0x6FF1, 0x6FF2, 0x6FF3, 0x6FF4, 0x6FF5, 0x6FF6, 0x6FF7, 0x6FF8, 0x6FF9, 0x6FFA, 0x6FFB, 0x6FFC, 0x6FFD, 0x6FFE, 0x6FFF, 0x7000, 0x7001, 0x7002, 0x7003, 0x7004, 0x7005, 0x7006, 0x7007, 0x7008, 0x7009, 0x700A, 0x700B, 0x700C, 0x700D, 0x700E, 0x700F, 0x7010, 0x7012, 0x7013, 0x7014, 0x7015, 0x7016, 0x7017, 0x7018, 0x7019, 0x701C, 0x701D, 0x701E, 0x701F, 0x7020, 0x7021, 0x7022, 0x7024, 0x7025, 0x7026, 0x7027, 0x7028, 0x7029, 0x702A, 0x702B, 0x702C, 0x702D, 0x702E, 0x702F, 0x7030, 0x7031, 0x7032, 0x7033, 0x7034, 0x7036, 0x7037, 0x7038, 0x703A, 0x703B, 0x703C, 0x703D, 0x703E, 0x703F, 0x7040, 0x7041, 0x7042, 0x7043, 0x7044, 0x7045, 0x7046, 0x7047, 0x7048, 0x7049, 0x704A, 0x704B, 0x704D, + 0x704E, 0x7050, 0x7051, 0x7052, 0x7053, 0x7054, 0x7055, 0x7056, 0x7057, 0x7058, 0x7059, 0x705A, 0x705B, 0x705C, 0x705D, 0x705F, 0x7060, 0x7061, 0x7062, 0x7063, 0x7064, 0x7065, 0x7066, 0x7067, 0x7068, 0x7069, 0x706A, 0x706E, 0x7071, 0x7072, 0x7073, 0x7074, 0x7077, 0x7079, 0x707A, 0x707B, 0x707D, 0x7081, 0x7082, 0x7083, 0x7084, 0x7086, 0x7087, 0x7088, 0x708B, 0x708C, 0x708D, 0x708F, 0x7090, 0x7091, 0x7093, 0x7097, 0x7098, 0x709A, 0x709B, 0x709E, 0x709F, 0x70A0, 0x70A1, 0x70A2, 0x70A3, 0x70A4, 0x70A5, 0x70A6, 0x70A7, 0x70A8, 0x70A9, 0x70AA, 0x70B0, 0x70B2, 0x70B4, 0x70B5, 0x70B6, 0x70BA, 0x70BE, 0x70BF, 0x70C4, 0x70C5, 0x70C6, 0x70C7, 0x70C9, 0x70CB, 0x70CC, 0x70CD, 0x70CE, 0x70CF, 0x70D0, 0x70D1, 0x70D2, 0x70D3, 0x70D4, 0x70D5, 0x70D6, 0x70D7, 0x70DA, + 0x70DC, 0x70DD, 0x70DE, 0x70E0, 0x70E1, 0x70E2, 0x70E3, 0x70E5, 0x70EA, 0x70EE, 0x70F0, 0x70F1, 0x70F2, 0x70F3, 0x70F4, 0x70F5, 0x70F6, 0x70F8, 0x70FA, 0x70FB, 0x70FC, 0x70FE, 0x70FF, 0x7100, 0x7101, 0x7102, 0x7103, 0x7104, 0x7105, 0x7106, 0x7107, 0x7108, 0x710B, 0x710C, 0x710D, 0x710E, 0x710F, 0x7111, 0x7112, 0x7114, 0x7117, 0x711B, 0x711C, 0x711D, 0x711E, 0x711F, 0x7120, 0x7121, 0x7122, 0x7123, 0x7124, 0x7125, 0x7127, 0x7128, 0x7129, 0x712A, 0x712B, 0x712C, 0x712D, 0x712E, 0x7132, 0x7133, 0x7134, 0x7135, 0x7137, 0x7138, 0x7139, 0x713A, 0x713B, 0x713C, 0x713D, 0x713E, 0x713F, 0x7140, 0x7141, 0x7142, 0x7143, 0x7144, 0x7146, 0x7147, 0x7148, 0x7149, 0x714B, 0x714D, 0x714F, 0x7150, 0x7151, 0x7152, 0x7153, 0x7154, 0x7155, 0x7156, 0x7157, 0x7158, 0x7159, + 0x715A, 0x715B, 0x715D, 0x715F, 0x7160, 0x7161, 0x7162, 0x7163, 0x7165, 0x7169, 0x716A, 0x716B, 0x716C, 0x716D, 0x716F, 0x7170, 0x7171, 0x7174, 0x7175, 0x7176, 0x7177, 0x7179, 0x717B, 0x717C, 0x717E, 0x717F, 0x7180, 0x7181, 0x7182, 0x7183, 0x7185, 0x7186, 0x7187, 0x7188, 0x7189, 0x718B, 0x718C, 0x718D, 0x718E, 0x7190, 0x7191, 0x7192, 0x7193, 0x7195, 0x7196, 0x7197, 0x719A, 0x719B, 0x719C, 0x719D, 0x719E, 0x71A1, 0x71A2, 0x71A3, 0x71A4, 0x71A5, 0x71A6, 0x71A7, 0x71A9, 0x71AA, 0x71AB, 0x71AD, 0x71AE, 0x71AF, 0x71B0, 0x71B1, 0x71B2, 0x71B4, 0x71B6, 0x71B7, 0x71B8, 0x71BA, 0x71BB, 0x71BC, 0x71BD, 0x71BE, 0x71BF, 0x71C0, 0x71C1, 0x71C2, 0x71C4, 0x71C5, 0x71C6, 0x71C7, 0x71C8, 0x71C9, 0x71CA, 0x71CB, 0x71CC, 0x71CD, 0x71CF, 0x71D0, 0x71D1, 0x71D2, 0x71D3, + 0x71D6, 0x71D7, 0x71D8, 0x71D9, 0x71DA, 0x71DB, 0x71DC, 0x71DD, 0x71DE, 0x71DF, 0x71E1, 0x71E2, 0x71E3, 0x71E4, 0x71E6, 0x71E8, 0x71E9, 0x71EA, 0x71EB, 0x71EC, 0x71ED, 0x71EF, 0x71F0, 0x71F1, 0x71F2, 0x71F3, 0x71F4, 0x71F5, 0x71F6, 0x71F7, 0x71F8, 0x71FA, 0x71FB, 0x71FC, 0x71FD, 0x71FE, 0x71FF, 0x7200, 0x7201, 0x7202, 0x7203, 0x7204, 0x7205, 0x7207, 0x7208, 0x7209, 0x720A, 0x720B, 0x720C, 0x720D, 0x720E, 0x720F, 0x7210, 0x7211, 0x7212, 0x7213, 0x7214, 0x7215, 0x7216, 0x7217, 0x7218, 0x7219, 0x721A, 0x721B, 0x721C, 0x721E, 0x721F, 0x7220, 0x7221, 0x7222, 0x7223, 0x7224, 0x7225, 0x7226, 0x7227, 0x7229, 0x722B, 0x722D, 0x722E, 0x722F, 0x7232, 0x7233, 0x7234, 0x723A, 0x723C, 0x723E, 0x7240, 0x7241, 0x7242, 0x7243, 0x7244, 0x7245, 0x7246, 0x7249, 0x724A, + 0x724B, 0x724E, 0x724F, 0x7250, 0x7251, 0x7253, 0x7254, 0x7255, 0x7257, 0x7258, 0x725A, 0x725C, 0x725E, 0x7260, 0x7263, 0x7264, 0x7265, 0x7268, 0x726A, 0x726B, 0x726C, 0x726D, 0x7270, 0x7271, 0x7273, 0x7274, 0x7276, 0x7277, 0x7278, 0x727B, 0x727C, 0x727D, 0x7282, 0x7283, 0x7285, 0x7286, 0x7287, 0x7288, 0x7289, 0x728C, 0x728E, 0x7290, 0x7291, 0x7293, 0x7294, 0x7295, 0x7296, 0x7297, 0x7298, 0x7299, 0x729A, 0x729B, 0x729C, 0x729D, 0x729E, 0x72A0, 0x72A1, 0x72A2, 0x72A3, 0x72A4, 0x72A5, 0x72A6, 0x72A7, 0x72A8, 0x72A9, 0x72AA, 0x72AB, 0x72AE, 0x72B1, 0x72B2, 0x72B3, 0x72B5, 0x72BA, 0x72BB, 0x72BC, 0x72BD, 0x72BE, 0x72BF, 0x72C0, 0x72C5, 0x72C6, 0x72C7, 0x72C9, 0x72CA, 0x72CB, 0x72CC, 0x72CF, 0x72D1, 0x72D3, 0x72D4, 0x72D5, 0x72D6, 0x72D8, 0x72DA, 0x72DB, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x3000, 0x3001, 0x3002, 0x00B7, 0x02C9, 0x02C7, 0x00A8, 0x3003, 0x3005, 0x2014, 0xFF5E, 0x2016, 0x2026, 0x2018, 0x2019, 0x201C, 0x201D, 0x3014, 0x3015, 0x3008, 0x3009, 0x300A, 0x300B, 0x300C, 0x300D, 0x300E, 0x300F, 0x3016, 0x3017, 0x3010, 0x3011, 0x00B1, 0x00D7, 0x00F7, 0x2236, 0x2227, 0x2228, 0x2211, 0x220F, 0x222A, 0x2229, 0x2208, 0x2237, 0x221A, 0x22A5, 0x2225, 0x2220, 0x2312, 0x2299, 0x222B, 0x222E, 0x2261, 0x224C, 0x2248, 0x223D, 0x221D, 0x2260, 0x226E, 0x226F, 0x2264, 0x2265, 0x221E, 0x2235, 0x2234, 0x2642, 0x2640, 0x00B0, 0x2032, 0x2033, 0x2103, 0xFF04, 0x00A4, 0xFFE0, 0xFFE1, 0x2030, 0x00A7, 0x2116, 0x2606, 0x2605, 0x25CB, 0x25CF, 0x25CE, 0x25C7, 0x25C6, 0x25A1, 0x25A0, 0x25B3, 0x25B2, 0x203B, 0x2192, 0x2190, 0x2191, 0x2193, 0x3013, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177, 0x2178, 0x2179, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2488, 0x2489, 0x248A, 0x248B, 0x248C, 0x248D, 0x248E, 0x248F, 0x2490, 0x2491, 0x2492, 0x2493, 0x2494, 0x2495, 0x2496, 0x2497, 0x2498, 0x2499, 0x249A, 0x249B, 0x2474, 0x2475, 0x2476, 0x2477, 0x2478, 0x2479, 0x247A, 0x247B, 0x247C, 0x247D, 0x247E, 0x247F, 0x2480, 0x2481, 0x2482, 0x2483, 0x2484, 0x2485, 0x2486, 0x2487, 0x2460, 0x2461, 0x2462, 0x2463, 0x2464, 0x2465, 0x2466, 0x2467, 0x2468, 0x2469, 0x0000, 0x0000, 0x3220, 0x3221, 0x3222, 0x3223, 0x3224, 0x3225, 0x3226, 0x3227, 0x3228, 0x3229, 0x0000, 0x0000, 0x2160, 0x2161, 0x2162, 0x2163, 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x216A, 0x216B, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0xFF01, 0xFF02, 0xFF03, 0xFFE5, 0xFF05, 0xFF06, 0xFF07, 0xFF08, 0xFF09, 0xFF0A, 0xFF0B, 0xFF0C, 0xFF0D, 0xFF0E, 0xFF0F, 0xFF10, 0xFF11, 0xFF12, 0xFF13, 0xFF14, 0xFF15, 0xFF16, 0xFF17, 0xFF18, 0xFF19, 0xFF1A, 0xFF1B, 0xFF1C, 0xFF1D, 0xFF1E, 0xFF1F, 0xFF20, 0xFF21, 0xFF22, 0xFF23, 0xFF24, 0xFF25, 0xFF26, 0xFF27, 0xFF28, 0xFF29, 0xFF2A, 0xFF2B, 0xFF2C, 0xFF2D, 0xFF2E, 0xFF2F, 0xFF30, 0xFF31, 0xFF32, 0xFF33, 0xFF34, 0xFF35, 0xFF36, 0xFF37, 0xFF38, 0xFF39, 0xFF3A, 0xFF3B, 0xFF3C, 0xFF3D, 0xFF3E, 0xFF3F, 0xFF40, 0xFF41, 0xFF42, 0xFF43, 0xFF44, 0xFF45, 0xFF46, 0xFF47, 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFF4C, 0xFF4D, 0xFF4E, 0xFF4F, 0xFF50, 0xFF51, 0xFF52, 0xFF53, 0xFF54, 0xFF55, 0xFF56, 0xFF57, 0xFF58, 0xFF59, 0xFF5A, 0xFF5B, 0xFF5C, 0xFF5D, 0xFFE3, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x3041, 0x3042, 0x3043, 0x3044, 0x3045, 0x3046, 0x3047, 0x3048, 0x3049, 0x304A, 0x304B, 0x304C, 0x304D, 0x304E, 0x304F, 0x3050, 0x3051, 0x3052, 0x3053, 0x3054, 0x3055, 0x3056, 0x3057, 0x3058, 0x3059, 0x305A, 0x305B, 0x305C, 0x305D, 0x305E, 0x305F, 0x3060, 0x3061, 0x3062, 0x3063, 0x3064, 0x3065, 0x3066, 0x3067, 0x3068, 0x3069, 0x306A, 0x306B, 0x306C, 0x306D, 0x306E, 0x306F, 0x3070, 0x3071, 0x3072, 0x3073, 0x3074, 0x3075, 0x3076, 0x3077, 0x3078, 0x3079, 0x307A, 0x307B, 0x307C, 0x307D, 0x307E, 0x307F, 0x3080, 0x3081, 0x3082, 0x3083, 0x3084, 0x3085, 0x3086, 0x3087, 0x3088, 0x3089, 0x308A, 0x308B, 0x308C, 0x308D, 0x308E, 0x308F, 0x3090, 0x3091, 0x3092, 0x3093, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x30A1, 0x30A2, 0x30A3, 0x30A4, 0x30A5, 0x30A6, 0x30A7, 0x30A8, 0x30A9, 0x30AA, 0x30AB, 0x30AC, 0x30AD, 0x30AE, 0x30AF, 0x30B0, 0x30B1, 0x30B2, 0x30B3, 0x30B4, 0x30B5, 0x30B6, 0x30B7, 0x30B8, 0x30B9, 0x30BA, 0x30BB, 0x30BC, 0x30BD, 0x30BE, 0x30BF, 0x30C0, 0x30C1, 0x30C2, 0x30C3, 0x30C4, 0x30C5, 0x30C6, 0x30C7, 0x30C8, 0x30C9, 0x30CA, 0x30CB, 0x30CC, 0x30CD, 0x30CE, 0x30CF, 0x30D0, 0x30D1, 0x30D2, 0x30D3, 0x30D4, 0x30D5, 0x30D6, 0x30D7, 0x30D8, 0x30D9, 0x30DA, 0x30DB, 0x30DC, 0x30DD, 0x30DE, 0x30DF, 0x30E0, 0x30E1, 0x30E2, 0x30E3, 0x30E4, 0x30E5, 0x30E6, 0x30E7, 0x30E8, 0x30E9, 0x30EA, 0x30EB, 0x30EC, 0x30ED, 0x30EE, 0x30EF, 0x30F0, 0x30F1, 0x30F2, 0x30F3, 0x30F4, 0x30F5, 0x30F6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, 0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, 0x03C9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFE35, 0xFE36, 0xFE39, 0xFE3A, 0xFE3F, 0xFE40, 0xFE3D, 0xFE3E, 0xFE41, 0xFE42, 0xFE43, 0xFE44, 0x0000, 0x0000, 0xFE3B, 0xFE3C, 0xFE37, 0xFE38, 0xFE31, 0x0000, 0xFE33, 0xFE34, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0401, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0451, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x02CA, 0x02CB, 0x02D9, 0x2013, 0x2015, 0x2025, 0x2035, 0x2105, 0x2109, 0x2196, 0x2197, 0x2198, 0x2199, 0x2215, 0x221F, 0x2223, 0x2252, 0x2266, 0x2267, 0x22BF, 0x2550, 0x2551, 0x2552, 0x2553, 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x255C, 0x255D, 0x255E, 0x255F, 0x2560, 0x2561, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x256B, 0x256C, 0x256D, 0x256E, 0x256F, 0x2570, 0x2571, 0x2572, 0x2573, 0x2581, 0x2582, 0x2583, 0x2584, 0x2585, 0x2586, 0x2587, 0x2588, 0x2589, 0x258A, 0x258B, 0x258C, 0x258D, 0x258E, 0x258F, 0x2593, 0x2594, 0x2595, 0x25BC, 0x25BD, 0x25E2, 0x25E3, 0x25E4, 0x25E5, 0x2609, 0x2295, 0x3012, 0x301D, 0x301E, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0101, 0x00E1, 0x01CE, 0x00E0, 0x0113, 0x00E9, 0x011B, 0x00E8, 0x012B, 0x00ED, 0x01D0, 0x00EC, 0x014D, 0x00F3, 0x01D2, 0x00F2, 0x016B, 0x00FA, 0x01D4, 0x00F9, 0x01D6, 0x01D8, 0x01DA, 0x01DC, 0x00FC, 0x00EA, 0x0251, 0x0000, 0x0144, 0x0148, 0x0000, 0x0261, 0x0000, 0x0000, 0x0000, 0x0000, 0x3105, 0x3106, 0x3107, 0x3108, 0x3109, 0x310A, 0x310B, 0x310C, 0x310D, 0x310E, 0x310F, 0x3110, 0x3111, 0x3112, 0x3113, 0x3114, 0x3115, 0x3116, 0x3117, 0x3118, 0x3119, 0x311A, 0x311B, 0x311C, 0x311D, 0x311E, 0x311F, 0x3120, 0x3121, 0x3122, 0x3123, 0x3124, 0x3125, 0x3126, 0x3127, 0x3128, 0x3129, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x3021, 0x3022, 0x3023, 0x3024, 0x3025, 0x3026, 0x3027, 0x3028, 0x3029, 0x32A3, 0x338E, 0x338F, 0x339C, 0x339D, 0x339E, 0x33A1, 0x33C4, 0x33CE, 0x33D1, 0x33D2, 0x33D5, 0xFE30, 0xFFE2, 0xFFE4, 0x0000, 0x2121, 0x3231, 0x0000, 0x2010, 0x0000, 0x0000, 0x0000, 0x30FC, 0x309B, 0x309C, 0x30FD, 0x30FE, 0x3006, 0x309D, 0x309E, 0xFE49, 0xFE4A, 0xFE4B, 0xFE4C, 0xFE4D, 0xFE4E, 0xFE4F, 0xFE50, 0xFE51, 0xFE52, 0xFE54, 0xFE55, 0xFE56, 0xFE57, 0xFE59, 0xFE5A, 0xFE5B, 0xFE5C, 0xFE5D, 0xFE5E, 0xFE5F, 0xFE60, 0xFE61, 0xFE62, 0xFE63, 0xFE64, 0xFE65, 0xFE66, 0xFE68, 0xFE69, 0xFE6A, 0xFE6B, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3007, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x2500, 0x2501, 0x2502, 0x2503, 0x2504, 0x2505, 0x2506, 0x2507, 0x2508, 0x2509, 0x250A, 0x250B, 0x250C, 0x250D, 0x250E, 0x250F, 0x2510, 0x2511, 0x2512, 0x2513, 0x2514, 0x2515, 0x2516, 0x2517, 0x2518, 0x2519, 0x251A, 0x251B, 0x251C, 0x251D, 0x251E, 0x251F, 0x2520, 0x2521, 0x2522, 0x2523, 0x2524, 0x2525, 0x2526, 0x2527, 0x2528, 0x2529, 0x252A, 0x252B, 0x252C, 0x252D, 0x252E, 0x252F, 0x2530, 0x2531, 0x2532, 0x2533, 0x2534, 0x2535, 0x2536, 0x2537, 0x2538, 0x2539, 0x253A, 0x253B, 0x253C, 0x253D, 0x253E, 0x253F, 0x2540, 0x2541, 0x2542, 0x2543, 0x2544, 0x2545, 0x2546, 0x2547, 0x2548, 0x2549, 0x254A, 0x254B, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x72DC, 0x72DD, 0x72DF, 0x72E2, 0x72E3, 0x72E4, 0x72E5, 0x72E6, 0x72E7, 0x72EA, 0x72EB, 0x72F5, 0x72F6, 0x72F9, 0x72FD, 0x72FE, 0x72FF, 0x7300, 0x7302, 0x7304, 0x7305, 0x7306, 0x7307, 0x7308, 0x7309, 0x730B, 0x730C, 0x730D, 0x730F, 0x7310, 0x7311, 0x7312, 0x7314, 0x7318, 0x7319, 0x731A, 0x731F, 0x7320, 0x7323, 0x7324, 0x7326, 0x7327, 0x7328, 0x732D, 0x732F, 0x7330, 0x7332, 0x7333, 0x7335, 0x7336, 0x733A, 0x733B, 0x733C, 0x733D, 0x7340, 0x7341, 0x7342, 0x7343, 0x7344, 0x7345, 0x7346, 0x7347, 0x7348, 0x7349, 0x734A, 0x734B, 0x734C, 0x734E, 0x734F, 0x7351, 0x7353, 0x7354, 0x7355, 0x7356, 0x7358, 0x7359, 0x735A, 0x735B, 0x735C, 0x735D, 0x735E, 0x735F, 0x7361, 0x7362, 0x7363, 0x7364, 0x7365, 0x7366, 0x7367, 0x7368, 0x7369, 0x736A, 0x736B, 0x736E, 0x7370, + 0x7371, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x7372, 0x7373, 0x7374, 0x7375, 0x7376, 0x7377, 0x7378, 0x7379, 0x737A, 0x737B, 0x737C, 0x737D, 0x737F, 0x7380, 0x7381, 0x7382, 0x7383, 0x7385, 0x7386, 0x7388, 0x738A, 0x738C, 0x738D, 0x738F, 0x7390, 0x7392, 0x7393, 0x7394, 0x7395, 0x7397, 0x7398, 0x7399, 0x739A, 0x739C, 0x739D, 0x739E, 0x73A0, 0x73A1, 0x73A3, 0x73A4, 0x73A5, 0x73A6, 0x73A7, 0x73A8, 0x73AA, 0x73AC, 0x73AD, 0x73B1, 0x73B4, 0x73B5, 0x73B6, 0x73B8, 0x73B9, 0x73BC, 0x73BD, 0x73BE, 0x73BF, 0x73C1, 0x73C3, 0x73C4, 0x73C5, 0x73C6, 0x73C7, 0x73CB, 0x73CC, 0x73CE, 0x73D2, 0x73D3, 0x73D4, 0x73D5, 0x73D6, 0x73D7, 0x73D8, 0x73DA, 0x73DB, 0x73DC, 0x73DD, 0x73DF, 0x73E1, 0x73E2, 0x73E3, 0x73E4, 0x73E6, 0x73E8, 0x73EA, 0x73EB, 0x73EC, 0x73EE, 0x73EF, 0x73F0, 0x73F1, 0x73F3, 0x73F4, 0x73F5, 0x73F6, + 0x73F7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x73F8, 0x73F9, 0x73FA, 0x73FB, 0x73FC, 0x73FD, 0x73FE, 0x73FF, 0x7400, 0x7401, 0x7402, 0x7404, 0x7407, 0x7408, 0x740B, 0x740C, 0x740D, 0x740E, 0x7411, 0x7412, 0x7413, 0x7414, 0x7415, 0x7416, 0x7417, 0x7418, 0x7419, 0x741C, 0x741D, 0x741E, 0x741F, 0x7420, 0x7421, 0x7423, 0x7424, 0x7427, 0x7429, 0x742B, 0x742D, 0x742F, 0x7431, 0x7432, 0x7437, 0x7438, 0x7439, 0x743A, 0x743B, 0x743D, 0x743E, 0x743F, 0x7440, 0x7442, 0x7443, 0x7444, 0x7445, 0x7446, 0x7447, 0x7448, 0x7449, 0x744A, 0x744B, 0x744C, 0x744D, 0x744E, 0x744F, 0x7450, 0x7451, 0x7452, 0x7453, 0x7454, 0x7456, 0x7458, 0x745D, 0x7460, 0x7461, 0x7462, 0x7463, 0x7464, 0x7465, 0x7466, 0x7467, 0x7468, 0x7469, 0x746A, 0x746B, 0x746C, 0x746E, 0x746F, 0x7471, 0x7472, 0x7473, 0x7474, 0x7475, 0x7478, 0x7479, + 0x747A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x747B, 0x747C, 0x747D, 0x747F, 0x7482, 0x7484, 0x7485, 0x7486, 0x7488, 0x7489, 0x748A, 0x748C, 0x748D, 0x748F, 0x7491, 0x7492, 0x7493, 0x7494, 0x7495, 0x7496, 0x7497, 0x7498, 0x7499, 0x749A, 0x749B, 0x749D, 0x749F, 0x74A0, 0x74A1, 0x74A2, 0x74A3, 0x74A4, 0x74A5, 0x74A6, 0x74AA, 0x74AB, 0x74AC, 0x74AD, 0x74AE, 0x74AF, 0x74B0, 0x74B1, 0x74B2, 0x74B3, 0x74B4, 0x74B5, 0x74B6, 0x74B7, 0x74B8, 0x74B9, 0x74BB, 0x74BC, 0x74BD, 0x74BE, 0x74BF, 0x74C0, 0x74C1, 0x74C2, 0x74C3, 0x74C4, 0x74C5, 0x74C6, 0x74C7, 0x74C8, 0x74C9, 0x74CA, 0x74CB, 0x74CC, 0x74CD, 0x74CE, 0x74CF, 0x74D0, 0x74D1, 0x74D3, 0x74D4, 0x74D5, 0x74D6, 0x74D7, 0x74D8, 0x74D9, 0x74DA, 0x74DB, 0x74DD, 0x74DF, 0x74E1, 0x74E5, 0x74E7, 0x74E8, 0x74E9, 0x74EA, 0x74EB, 0x74EC, 0x74ED, 0x74F0, 0x74F1, + 0x74F2, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x74F3, 0x74F5, 0x74F8, 0x74F9, 0x74FA, 0x74FB, 0x74FC, 0x74FD, 0x74FE, 0x7500, 0x7501, 0x7502, 0x7503, 0x7505, 0x7506, 0x7507, 0x7508, 0x7509, 0x750A, 0x750B, 0x750C, 0x750E, 0x7510, 0x7512, 0x7514, 0x7515, 0x7516, 0x7517, 0x751B, 0x751D, 0x751E, 0x7520, 0x7521, 0x7522, 0x7523, 0x7524, 0x7526, 0x7527, 0x752A, 0x752E, 0x7534, 0x7536, 0x7539, 0x753C, 0x753D, 0x753F, 0x7541, 0x7542, 0x7543, 0x7544, 0x7546, 0x7547, 0x7549, 0x754A, 0x754D, 0x7550, 0x7551, 0x7552, 0x7553, 0x7555, 0x7556, 0x7557, 0x7558, 0x755D, 0x755E, 0x755F, 0x7560, 0x7561, 0x7562, 0x7563, 0x7564, 0x7567, 0x7568, 0x7569, 0x756B, 0x756C, 0x756D, 0x756E, 0x756F, 0x7570, 0x7571, 0x7573, 0x7575, 0x7576, 0x7577, 0x757A, 0x757B, 0x757C, 0x757D, 0x757E, 0x7580, 0x7581, 0x7582, 0x7584, 0x7585, + 0x7587, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x7588, 0x7589, 0x758A, 0x758C, 0x758D, 0x758E, 0x7590, 0x7593, 0x7595, 0x7598, 0x759B, 0x759C, 0x759E, 0x75A2, 0x75A6, 0x75A7, 0x75A8, 0x75A9, 0x75AA, 0x75AD, 0x75B6, 0x75B7, 0x75BA, 0x75BB, 0x75BF, 0x75C0, 0x75C1, 0x75C6, 0x75CB, 0x75CC, 0x75CE, 0x75CF, 0x75D0, 0x75D1, 0x75D3, 0x75D7, 0x75D9, 0x75DA, 0x75DC, 0x75DD, 0x75DF, 0x75E0, 0x75E1, 0x75E5, 0x75E9, 0x75EC, 0x75ED, 0x75EE, 0x75EF, 0x75F2, 0x75F3, 0x75F5, 0x75F6, 0x75F7, 0x75F8, 0x75FA, 0x75FB, 0x75FD, 0x75FE, 0x7602, 0x7604, 0x7606, 0x7607, 0x7608, 0x7609, 0x760B, 0x760D, 0x760E, 0x760F, 0x7611, 0x7612, 0x7613, 0x7614, 0x7616, 0x761A, 0x761C, 0x761D, 0x761E, 0x7621, 0x7623, 0x7627, 0x7628, 0x762C, 0x762E, 0x762F, 0x7631, 0x7632, 0x7636, 0x7637, 0x7639, 0x763A, 0x763B, 0x763D, 0x7641, 0x7642, + 0x7644, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x7645, 0x7646, 0x7647, 0x7648, 0x7649, 0x764A, 0x764B, 0x764E, 0x764F, 0x7650, 0x7651, 0x7652, 0x7653, 0x7655, 0x7657, 0x7658, 0x7659, 0x765A, 0x765B, 0x765D, 0x765F, 0x7660, 0x7661, 0x7662, 0x7664, 0x7665, 0x7666, 0x7667, 0x7668, 0x7669, 0x766A, 0x766C, 0x766D, 0x766E, 0x7670, 0x7671, 0x7672, 0x7673, 0x7674, 0x7675, 0x7676, 0x7677, 0x7679, 0x767A, 0x767C, 0x767F, 0x7680, 0x7681, 0x7683, 0x7685, 0x7689, 0x768A, 0x768C, 0x768D, 0x768F, 0x7690, 0x7692, 0x7694, 0x7695, 0x7697, 0x7698, 0x769A, 0x769B, 0x769C, 0x769D, 0x769E, 0x769F, 0x76A0, 0x76A1, 0x76A2, 0x76A3, 0x76A5, 0x76A6, 0x76A7, 0x76A8, 0x76A9, 0x76AA, 0x76AB, 0x76AC, 0x76AD, 0x76AF, 0x76B0, 0x76B3, 0x76B5, 0x76B6, 0x76B7, 0x76B8, 0x76B9, 0x76BA, 0x76BB, 0x76BC, 0x76BD, 0x76BE, 0x76C0, 0x76C1, + 0x76C3, 0x554A, 0x963F, 0x57C3, 0x6328, 0x54CE, 0x5509, 0x54C0, 0x7691, 0x764C, 0x853C, 0x77EE, 0x827E, 0x788D, 0x7231, 0x9698, 0x978D, 0x6C28, 0x5B89, 0x4FFA, 0x6309, 0x6697, 0x5CB8, 0x80FA, 0x6848, 0x80AE, 0x6602, 0x76CE, 0x51F9, 0x6556, 0x71AC, 0x7FF1, 0x8884, 0x50B2, 0x5965, 0x61CA, 0x6FB3, 0x82AD, 0x634C, 0x6252, 0x53ED, 0x5427, 0x7B06, 0x516B, 0x75A4, 0x5DF4, 0x62D4, 0x8DCB, 0x9776, 0x628A, 0x8019, 0x575D, 0x9738, 0x7F62, 0x7238, 0x767D, 0x67CF, 0x767E, 0x6446, 0x4F70, 0x8D25, 0x62DC, 0x7A17, 0x6591, 0x73ED, 0x642C, 0x6273, 0x822C, 0x9881, 0x677F, 0x7248, 0x626E, 0x62CC, 0x4F34, 0x74E3, 0x534A, 0x529E, 0x7ECA, 0x90A6, 0x5E2E, 0x6886, 0x699C, 0x8180, 0x7ED1, 0x68D2, 0x78C5, 0x868C, 0x9551, 0x508D, 0x8C24, 0x82DE, 0x80DE, 0x5305, 0x8912, 0x5265, + 0x76C4, 0x76C7, 0x76C9, 0x76CB, 0x76CC, 0x76D3, 0x76D5, 0x76D9, 0x76DA, 0x76DC, 0x76DD, 0x76DE, 0x76E0, 0x76E1, 0x76E2, 0x76E3, 0x76E4, 0x76E6, 0x76E7, 0x76E8, 0x76E9, 0x76EA, 0x76EB, 0x76EC, 0x76ED, 0x76F0, 0x76F3, 0x76F5, 0x76F6, 0x76F7, 0x76FA, 0x76FB, 0x76FD, 0x76FF, 0x7700, 0x7702, 0x7703, 0x7705, 0x7706, 0x770A, 0x770C, 0x770E, 0x770F, 0x7710, 0x7711, 0x7712, 0x7713, 0x7714, 0x7715, 0x7716, 0x7717, 0x7718, 0x771B, 0x771C, 0x771D, 0x771E, 0x7721, 0x7723, 0x7724, 0x7725, 0x7727, 0x772A, 0x772B, 0x772C, 0x772E, 0x7730, 0x7731, 0x7732, 0x7733, 0x7734, 0x7739, 0x773B, 0x773D, 0x773E, 0x773F, 0x7742, 0x7744, 0x7745, 0x7746, 0x7748, 0x7749, 0x774A, 0x774B, 0x774C, 0x774D, 0x774E, 0x774F, 0x7752, 0x7753, 0x7754, 0x7755, 0x7756, 0x7757, 0x7758, 0x7759, + 0x775C, 0x8584, 0x96F9, 0x4FDD, 0x5821, 0x9971, 0x5B9D, 0x62B1, 0x62A5, 0x66B4, 0x8C79, 0x9C8D, 0x7206, 0x676F, 0x7891, 0x60B2, 0x5351, 0x5317, 0x8F88, 0x80CC, 0x8D1D, 0x94A1, 0x500D, 0x72C8, 0x5907, 0x60EB, 0x7119, 0x88AB, 0x5954, 0x82EF, 0x672C, 0x7B28, 0x5D29, 0x7EF7, 0x752D, 0x6CF5, 0x8E66, 0x8FF8, 0x903C, 0x9F3B, 0x6BD4, 0x9119, 0x7B14, 0x5F7C, 0x78A7, 0x84D6, 0x853D, 0x6BD5, 0x6BD9, 0x6BD6, 0x5E01, 0x5E87, 0x75F9, 0x95ED, 0x655D, 0x5F0A, 0x5FC5, 0x8F9F, 0x58C1, 0x81C2, 0x907F, 0x965B, 0x97AD, 0x8FB9, 0x7F16, 0x8D2C, 0x6241, 0x4FBF, 0x53D8, 0x535E, 0x8FA8, 0x8FA9, 0x8FAB, 0x904D, 0x6807, 0x5F6A, 0x8198, 0x8868, 0x9CD6, 0x618B, 0x522B, 0x762A, 0x5F6C, 0x658C, 0x6FD2, 0x6EE8, 0x5BBE, 0x6448, 0x5175, 0x51B0, 0x67C4, 0x4E19, 0x79C9, 0x997C, 0x70B3, + 0x775D, 0x775E, 0x775F, 0x7760, 0x7764, 0x7767, 0x7769, 0x776A, 0x776D, 0x776E, 0x776F, 0x7770, 0x7771, 0x7772, 0x7773, 0x7774, 0x7775, 0x7776, 0x7777, 0x7778, 0x777A, 0x777B, 0x777C, 0x7781, 0x7782, 0x7783, 0x7786, 0x7787, 0x7788, 0x7789, 0x778A, 0x778B, 0x778F, 0x7790, 0x7793, 0x7794, 0x7795, 0x7796, 0x7797, 0x7798, 0x7799, 0x779A, 0x779B, 0x779C, 0x779D, 0x779E, 0x77A1, 0x77A3, 0x77A4, 0x77A6, 0x77A8, 0x77AB, 0x77AD, 0x77AE, 0x77AF, 0x77B1, 0x77B2, 0x77B4, 0x77B6, 0x77B7, 0x77B8, 0x77B9, 0x77BA, 0x77BC, 0x77BE, 0x77C0, 0x77C1, 0x77C2, 0x77C3, 0x77C4, 0x77C5, 0x77C6, 0x77C7, 0x77C8, 0x77C9, 0x77CA, 0x77CB, 0x77CC, 0x77CE, 0x77CF, 0x77D0, 0x77D1, 0x77D2, 0x77D3, 0x77D4, 0x77D5, 0x77D6, 0x77D8, 0x77D9, 0x77DA, 0x77DD, 0x77DE, 0x77DF, 0x77E0, 0x77E1, + 0x77E4, 0x75C5, 0x5E76, 0x73BB, 0x83E0, 0x64AD, 0x62E8, 0x94B5, 0x6CE2, 0x535A, 0x52C3, 0x640F, 0x94C2, 0x7B94, 0x4F2F, 0x5E1B, 0x8236, 0x8116, 0x818A, 0x6E24, 0x6CCA, 0x9A73, 0x6355, 0x535C, 0x54FA, 0x8865, 0x57E0, 0x4E0D, 0x5E03, 0x6B65, 0x7C3F, 0x90E8, 0x6016, 0x64E6, 0x731C, 0x88C1, 0x6750, 0x624D, 0x8D22, 0x776C, 0x8E29, 0x91C7, 0x5F69, 0x83DC, 0x8521, 0x9910, 0x53C2, 0x8695, 0x6B8B, 0x60ED, 0x60E8, 0x707F, 0x82CD, 0x8231, 0x4ED3, 0x6CA7, 0x85CF, 0x64CD, 0x7CD9, 0x69FD, 0x66F9, 0x8349, 0x5395, 0x7B56, 0x4FA7, 0x518C, 0x6D4B, 0x5C42, 0x8E6D, 0x63D2, 0x53C9, 0x832C, 0x8336, 0x67E5, 0x78B4, 0x643D, 0x5BDF, 0x5C94, 0x5DEE, 0x8BE7, 0x62C6, 0x67F4, 0x8C7A, 0x6400, 0x63BA, 0x8749, 0x998B, 0x8C17, 0x7F20, 0x94F2, 0x4EA7, 0x9610, 0x98A4, 0x660C, 0x7316, + 0x77E6, 0x77E8, 0x77EA, 0x77EF, 0x77F0, 0x77F1, 0x77F2, 0x77F4, 0x77F5, 0x77F7, 0x77F9, 0x77FA, 0x77FB, 0x77FC, 0x7803, 0x7804, 0x7805, 0x7806, 0x7807, 0x7808, 0x780A, 0x780B, 0x780E, 0x780F, 0x7810, 0x7813, 0x7815, 0x7819, 0x781B, 0x781E, 0x7820, 0x7821, 0x7822, 0x7824, 0x7828, 0x782A, 0x782B, 0x782E, 0x782F, 0x7831, 0x7832, 0x7833, 0x7835, 0x7836, 0x783D, 0x783F, 0x7841, 0x7842, 0x7843, 0x7844, 0x7846, 0x7848, 0x7849, 0x784A, 0x784B, 0x784D, 0x784F, 0x7851, 0x7853, 0x7854, 0x7858, 0x7859, 0x785A, 0x785B, 0x785C, 0x785E, 0x785F, 0x7860, 0x7861, 0x7862, 0x7863, 0x7864, 0x7865, 0x7866, 0x7867, 0x7868, 0x7869, 0x786F, 0x7870, 0x7871, 0x7872, 0x7873, 0x7874, 0x7875, 0x7876, 0x7878, 0x7879, 0x787A, 0x787B, 0x787D, 0x787E, 0x787F, 0x7880, 0x7881, 0x7882, + 0x7883, 0x573A, 0x5C1D, 0x5E38, 0x957F, 0x507F, 0x80A0, 0x5382, 0x655E, 0x7545, 0x5531, 0x5021, 0x8D85, 0x6284, 0x949E, 0x671D, 0x5632, 0x6F6E, 0x5DE2, 0x5435, 0x7092, 0x8F66, 0x626F, 0x64A4, 0x63A3, 0x5F7B, 0x6F88, 0x90F4, 0x81E3, 0x8FB0, 0x5C18, 0x6668, 0x5FF1, 0x6C89, 0x9648, 0x8D81, 0x886C, 0x6491, 0x79F0, 0x57CE, 0x6A59, 0x6210, 0x5448, 0x4E58, 0x7A0B, 0x60E9, 0x6F84, 0x8BDA, 0x627F, 0x901E, 0x9A8B, 0x79E4, 0x5403, 0x75F4, 0x6301, 0x5319, 0x6C60, 0x8FDF, 0x5F1B, 0x9A70, 0x803B, 0x9F7F, 0x4F88, 0x5C3A, 0x8D64, 0x7FC5, 0x65A5, 0x70BD, 0x5145, 0x51B2, 0x866B, 0x5D07, 0x5BA0, 0x62BD, 0x916C, 0x7574, 0x8E0C, 0x7A20, 0x6101, 0x7B79, 0x4EC7, 0x7EF8, 0x7785, 0x4E11, 0x81ED, 0x521D, 0x51FA, 0x6A71, 0x53A8, 0x8E87, 0x9504, 0x96CF, 0x6EC1, 0x9664, 0x695A, + 0x7884, 0x7885, 0x7886, 0x7888, 0x788A, 0x788B, 0x788F, 0x7890, 0x7892, 0x7894, 0x7895, 0x7896, 0x7899, 0x789D, 0x789E, 0x78A0, 0x78A2, 0x78A4, 0x78A6, 0x78A8, 0x78A9, 0x78AA, 0x78AB, 0x78AC, 0x78AD, 0x78AE, 0x78AF, 0x78B5, 0x78B6, 0x78B7, 0x78B8, 0x78BA, 0x78BB, 0x78BC, 0x78BD, 0x78BF, 0x78C0, 0x78C2, 0x78C3, 0x78C4, 0x78C6, 0x78C7, 0x78C8, 0x78CC, 0x78CD, 0x78CE, 0x78CF, 0x78D1, 0x78D2, 0x78D3, 0x78D6, 0x78D7, 0x78D8, 0x78DA, 0x78DB, 0x78DC, 0x78DD, 0x78DE, 0x78DF, 0x78E0, 0x78E1, 0x78E2, 0x78E3, 0x78E4, 0x78E5, 0x78E6, 0x78E7, 0x78E9, 0x78EA, 0x78EB, 0x78ED, 0x78EE, 0x78EF, 0x78F0, 0x78F1, 0x78F3, 0x78F5, 0x78F6, 0x78F8, 0x78F9, 0x78FB, 0x78FC, 0x78FD, 0x78FE, 0x78FF, 0x7900, 0x7902, 0x7903, 0x7904, 0x7906, 0x7907, 0x7908, 0x7909, 0x790A, 0x790B, + 0x790C, 0x7840, 0x50A8, 0x77D7, 0x6410, 0x89E6, 0x5904, 0x63E3, 0x5DDD, 0x7A7F, 0x693D, 0x4F20, 0x8239, 0x5598, 0x4E32, 0x75AE, 0x7A97, 0x5E62, 0x5E8A, 0x95EF, 0x521B, 0x5439, 0x708A, 0x6376, 0x9524, 0x5782, 0x6625, 0x693F, 0x9187, 0x5507, 0x6DF3, 0x7EAF, 0x8822, 0x6233, 0x7EF0, 0x75B5, 0x8328, 0x78C1, 0x96CC, 0x8F9E, 0x6148, 0x74F7, 0x8BCD, 0x6B64, 0x523A, 0x8D50, 0x6B21, 0x806A, 0x8471, 0x56F1, 0x5306, 0x4ECE, 0x4E1B, 0x51D1, 0x7C97, 0x918B, 0x7C07, 0x4FC3, 0x8E7F, 0x7BE1, 0x7A9C, 0x6467, 0x5D14, 0x50AC, 0x8106, 0x7601, 0x7CB9, 0x6DEC, 0x7FE0, 0x6751, 0x5B58, 0x5BF8, 0x78CB, 0x64AE, 0x6413, 0x63AA, 0x632B, 0x9519, 0x642D, 0x8FBE, 0x7B54, 0x7629, 0x6253, 0x5927, 0x5446, 0x6B79, 0x50A3, 0x6234, 0x5E26, 0x6B86, 0x4EE3, 0x8D37, 0x888B, 0x5F85, 0x902E, + 0x790D, 0x790E, 0x790F, 0x7910, 0x7911, 0x7912, 0x7914, 0x7915, 0x7916, 0x7917, 0x7918, 0x7919, 0x791A, 0x791B, 0x791C, 0x791D, 0x791F, 0x7920, 0x7921, 0x7922, 0x7923, 0x7925, 0x7926, 0x7927, 0x7928, 0x7929, 0x792A, 0x792B, 0x792C, 0x792D, 0x792E, 0x792F, 0x7930, 0x7931, 0x7932, 0x7933, 0x7935, 0x7936, 0x7937, 0x7938, 0x7939, 0x793D, 0x793F, 0x7942, 0x7943, 0x7944, 0x7945, 0x7947, 0x794A, 0x794B, 0x794C, 0x794D, 0x794E, 0x794F, 0x7950, 0x7951, 0x7952, 0x7954, 0x7955, 0x7958, 0x7959, 0x7961, 0x7963, 0x7964, 0x7966, 0x7969, 0x796A, 0x796B, 0x796C, 0x796E, 0x7970, 0x7971, 0x7972, 0x7973, 0x7974, 0x7975, 0x7976, 0x7979, 0x797B, 0x797C, 0x797D, 0x797E, 0x797F, 0x7982, 0x7983, 0x7986, 0x7987, 0x7988, 0x7989, 0x798B, 0x798C, 0x798D, 0x798E, 0x7990, 0x7991, + 0x7992, 0x6020, 0x803D, 0x62C5, 0x4E39, 0x5355, 0x90F8, 0x63B8, 0x80C6, 0x65E6, 0x6C2E, 0x4F46, 0x60EE, 0x6DE1, 0x8BDE, 0x5F39, 0x86CB, 0x5F53, 0x6321, 0x515A, 0x8361, 0x6863, 0x5200, 0x6363, 0x8E48, 0x5012, 0x5C9B, 0x7977, 0x5BFC, 0x5230, 0x7A3B, 0x60BC, 0x9053, 0x76D7, 0x5FB7, 0x5F97, 0x7684, 0x8E6C, 0x706F, 0x767B, 0x7B49, 0x77AA, 0x51F3, 0x9093, 0x5824, 0x4F4E, 0x6EF4, 0x8FEA, 0x654C, 0x7B1B, 0x72C4, 0x6DA4, 0x7FDF, 0x5AE1, 0x62B5, 0x5E95, 0x5730, 0x8482, 0x7B2C, 0x5E1D, 0x5F1F, 0x9012, 0x7F14, 0x98A0, 0x6382, 0x6EC7, 0x7898, 0x70B9, 0x5178, 0x975B, 0x57AB, 0x7535, 0x4F43, 0x7538, 0x5E97, 0x60E6, 0x5960, 0x6DC0, 0x6BBF, 0x7889, 0x53FC, 0x96D5, 0x51CB, 0x5201, 0x6389, 0x540A, 0x9493, 0x8C03, 0x8DCC, 0x7239, 0x789F, 0x8776, 0x8FED, 0x8C0D, 0x53E0, + 0x7993, 0x7994, 0x7995, 0x7996, 0x7997, 0x7998, 0x7999, 0x799B, 0x799C, 0x799D, 0x799E, 0x799F, 0x79A0, 0x79A1, 0x79A2, 0x79A3, 0x79A4, 0x79A5, 0x79A6, 0x79A8, 0x79A9, 0x79AA, 0x79AB, 0x79AC, 0x79AD, 0x79AE, 0x79AF, 0x79B0, 0x79B1, 0x79B2, 0x79B4, 0x79B5, 0x79B6, 0x79B7, 0x79B8, 0x79BC, 0x79BF, 0x79C2, 0x79C4, 0x79C5, 0x79C7, 0x79C8, 0x79CA, 0x79CC, 0x79CE, 0x79CF, 0x79D0, 0x79D3, 0x79D4, 0x79D6, 0x79D7, 0x79D9, 0x79DA, 0x79DB, 0x79DC, 0x79DD, 0x79DE, 0x79E0, 0x79E1, 0x79E2, 0x79E5, 0x79E8, 0x79EA, 0x79EC, 0x79EE, 0x79F1, 0x79F2, 0x79F3, 0x79F4, 0x79F5, 0x79F6, 0x79F7, 0x79F9, 0x79FA, 0x79FC, 0x79FE, 0x79FF, 0x7A01, 0x7A04, 0x7A05, 0x7A07, 0x7A08, 0x7A09, 0x7A0A, 0x7A0C, 0x7A0F, 0x7A10, 0x7A11, 0x7A12, 0x7A13, 0x7A15, 0x7A16, 0x7A18, 0x7A19, 0x7A1B, + 0x7A1C, 0x4E01, 0x76EF, 0x53EE, 0x9489, 0x9876, 0x9F0E, 0x952D, 0x5B9A, 0x8BA2, 0x4E22, 0x4E1C, 0x51AC, 0x8463, 0x61C2, 0x52A8, 0x680B, 0x4F97, 0x606B, 0x51BB, 0x6D1E, 0x515C, 0x6296, 0x6597, 0x9661, 0x8C46, 0x9017, 0x75D8, 0x90FD, 0x7763, 0x6BD2, 0x728A, 0x72EC, 0x8BFB, 0x5835, 0x7779, 0x8D4C, 0x675C, 0x9540, 0x809A, 0x5EA6, 0x6E21, 0x5992, 0x7AEF, 0x77ED, 0x953B, 0x6BB5, 0x65AD, 0x7F0E, 0x5806, 0x5151, 0x961F, 0x5BF9, 0x58A9, 0x5428, 0x8E72, 0x6566, 0x987F, 0x56E4, 0x949D, 0x76FE, 0x9041, 0x6387, 0x54C6, 0x591A, 0x593A, 0x579B, 0x8EB2, 0x6735, 0x8DFA, 0x8235, 0x5241, 0x60F0, 0x5815, 0x86FE, 0x5CE8, 0x9E45, 0x4FC4, 0x989D, 0x8BB9, 0x5A25, 0x6076, 0x5384, 0x627C, 0x904F, 0x9102, 0x997F, 0x6069, 0x800C, 0x513F, 0x8033, 0x5C14, 0x9975, 0x6D31, 0x4E8C, + 0x7A1D, 0x7A1F, 0x7A21, 0x7A22, 0x7A24, 0x7A25, 0x7A26, 0x7A27, 0x7A28, 0x7A29, 0x7A2A, 0x7A2B, 0x7A2C, 0x7A2D, 0x7A2E, 0x7A2F, 0x7A30, 0x7A31, 0x7A32, 0x7A34, 0x7A35, 0x7A36, 0x7A38, 0x7A3A, 0x7A3E, 0x7A40, 0x7A41, 0x7A42, 0x7A43, 0x7A44, 0x7A45, 0x7A47, 0x7A48, 0x7A49, 0x7A4A, 0x7A4B, 0x7A4C, 0x7A4D, 0x7A4E, 0x7A4F, 0x7A50, 0x7A52, 0x7A53, 0x7A54, 0x7A55, 0x7A56, 0x7A58, 0x7A59, 0x7A5A, 0x7A5B, 0x7A5C, 0x7A5D, 0x7A5E, 0x7A5F, 0x7A60, 0x7A61, 0x7A62, 0x7A63, 0x7A64, 0x7A65, 0x7A66, 0x7A67, 0x7A68, 0x7A69, 0x7A6A, 0x7A6B, 0x7A6C, 0x7A6D, 0x7A6E, 0x7A6F, 0x7A71, 0x7A72, 0x7A73, 0x7A75, 0x7A7B, 0x7A7C, 0x7A7D, 0x7A7E, 0x7A82, 0x7A85, 0x7A87, 0x7A89, 0x7A8A, 0x7A8B, 0x7A8C, 0x7A8E, 0x7A8F, 0x7A90, 0x7A93, 0x7A94, 0x7A99, 0x7A9A, 0x7A9B, 0x7A9E, 0x7AA1, + 0x7AA2, 0x8D30, 0x53D1, 0x7F5A, 0x7B4F, 0x4F10, 0x4E4F, 0x9600, 0x6CD5, 0x73D0, 0x85E9, 0x5E06, 0x756A, 0x7FFB, 0x6A0A, 0x77FE, 0x9492, 0x7E41, 0x51E1, 0x70E6, 0x53CD, 0x8FD4, 0x8303, 0x8D29, 0x72AF, 0x996D, 0x6CDB, 0x574A, 0x82B3, 0x65B9, 0x80AA, 0x623F, 0x9632, 0x59A8, 0x4EFF, 0x8BBF, 0x7EBA, 0x653E, 0x83F2, 0x975E, 0x5561, 0x98DE, 0x80A5, 0x532A, 0x8BFD, 0x5420, 0x80BA, 0x5E9F, 0x6CB8, 0x8D39, 0x82AC, 0x915A, 0x5429, 0x6C1B, 0x5206, 0x7EB7, 0x575F, 0x711A, 0x6C7E, 0x7C89, 0x594B, 0x4EFD, 0x5FFF, 0x6124, 0x7CAA, 0x4E30, 0x5C01, 0x67AB, 0x8702, 0x5CF0, 0x950B, 0x98CE, 0x75AF, 0x70FD, 0x9022, 0x51AF, 0x7F1D, 0x8BBD, 0x5949, 0x51E4, 0x4F5B, 0x5426, 0x592B, 0x6577, 0x80A4, 0x5B75, 0x6276, 0x62C2, 0x8F90, 0x5E45, 0x6C1F, 0x7B26, 0x4F0F, 0x4FD8, 0x670D, + 0x7AA3, 0x7AA4, 0x7AA7, 0x7AA9, 0x7AAA, 0x7AAB, 0x7AAE, 0x7AAF, 0x7AB0, 0x7AB1, 0x7AB2, 0x7AB4, 0x7AB5, 0x7AB6, 0x7AB7, 0x7AB8, 0x7AB9, 0x7ABA, 0x7ABB, 0x7ABC, 0x7ABD, 0x7ABE, 0x7AC0, 0x7AC1, 0x7AC2, 0x7AC3, 0x7AC4, 0x7AC5, 0x7AC6, 0x7AC7, 0x7AC8, 0x7AC9, 0x7ACA, 0x7ACC, 0x7ACD, 0x7ACE, 0x7ACF, 0x7AD0, 0x7AD1, 0x7AD2, 0x7AD3, 0x7AD4, 0x7AD5, 0x7AD7, 0x7AD8, 0x7ADA, 0x7ADB, 0x7ADC, 0x7ADD, 0x7AE1, 0x7AE2, 0x7AE4, 0x7AE7, 0x7AE8, 0x7AE9, 0x7AEA, 0x7AEB, 0x7AEC, 0x7AEE, 0x7AF0, 0x7AF1, 0x7AF2, 0x7AF3, 0x7AF4, 0x7AF5, 0x7AF6, 0x7AF7, 0x7AF8, 0x7AFB, 0x7AFC, 0x7AFE, 0x7B00, 0x7B01, 0x7B02, 0x7B05, 0x7B07, 0x7B09, 0x7B0C, 0x7B0D, 0x7B0E, 0x7B10, 0x7B12, 0x7B13, 0x7B16, 0x7B17, 0x7B18, 0x7B1A, 0x7B1C, 0x7B1D, 0x7B1F, 0x7B21, 0x7B22, 0x7B23, 0x7B27, 0x7B29, + 0x7B2D, 0x6D6E, 0x6DAA, 0x798F, 0x88B1, 0x5F17, 0x752B, 0x629A, 0x8F85, 0x4FEF, 0x91DC, 0x65A7, 0x812F, 0x8151, 0x5E9C, 0x8150, 0x8D74, 0x526F, 0x8986, 0x8D4B, 0x590D, 0x5085, 0x4ED8, 0x961C, 0x7236, 0x8179, 0x8D1F, 0x5BCC, 0x8BA3, 0x9644, 0x5987, 0x7F1A, 0x5490, 0x5676, 0x560E, 0x8BE5, 0x6539, 0x6982, 0x9499, 0x76D6, 0x6E89, 0x5E72, 0x7518, 0x6746, 0x67D1, 0x7AFF, 0x809D, 0x8D76, 0x611F, 0x79C6, 0x6562, 0x8D63, 0x5188, 0x521A, 0x94A2, 0x7F38, 0x809B, 0x7EB2, 0x5C97, 0x6E2F, 0x6760, 0x7BD9, 0x768B, 0x9AD8, 0x818F, 0x7F94, 0x7CD5, 0x641E, 0x9550, 0x7A3F, 0x544A, 0x54E5, 0x6B4C, 0x6401, 0x6208, 0x9E3D, 0x80F3, 0x7599, 0x5272, 0x9769, 0x845B, 0x683C, 0x86E4, 0x9601, 0x9694, 0x94EC, 0x4E2A, 0x5404, 0x7ED9, 0x6839, 0x8DDF, 0x8015, 0x66F4, 0x5E9A, 0x7FB9, + 0x7B2F, 0x7B30, 0x7B32, 0x7B34, 0x7B35, 0x7B36, 0x7B37, 0x7B39, 0x7B3B, 0x7B3D, 0x7B3F, 0x7B40, 0x7B41, 0x7B42, 0x7B43, 0x7B44, 0x7B46, 0x7B48, 0x7B4A, 0x7B4D, 0x7B4E, 0x7B53, 0x7B55, 0x7B57, 0x7B59, 0x7B5C, 0x7B5E, 0x7B5F, 0x7B61, 0x7B63, 0x7B64, 0x7B65, 0x7B66, 0x7B67, 0x7B68, 0x7B69, 0x7B6A, 0x7B6B, 0x7B6C, 0x7B6D, 0x7B6F, 0x7B70, 0x7B73, 0x7B74, 0x7B76, 0x7B78, 0x7B7A, 0x7B7C, 0x7B7D, 0x7B7F, 0x7B81, 0x7B82, 0x7B83, 0x7B84, 0x7B86, 0x7B87, 0x7B88, 0x7B89, 0x7B8A, 0x7B8B, 0x7B8C, 0x7B8E, 0x7B8F, 0x7B91, 0x7B92, 0x7B93, 0x7B96, 0x7B98, 0x7B99, 0x7B9A, 0x7B9B, 0x7B9E, 0x7B9F, 0x7BA0, 0x7BA3, 0x7BA4, 0x7BA5, 0x7BAE, 0x7BAF, 0x7BB0, 0x7BB2, 0x7BB3, 0x7BB5, 0x7BB6, 0x7BB7, 0x7BB9, 0x7BBA, 0x7BBB, 0x7BBC, 0x7BBD, 0x7BBE, 0x7BBF, 0x7BC0, 0x7BC2, 0x7BC3, + 0x7BC4, 0x57C2, 0x803F, 0x6897, 0x5DE5, 0x653B, 0x529F, 0x606D, 0x9F9A, 0x4F9B, 0x8EAC, 0x516C, 0x5BAB, 0x5F13, 0x5DE9, 0x6C5E, 0x62F1, 0x8D21, 0x5171, 0x94A9, 0x52FE, 0x6C9F, 0x82DF, 0x72D7, 0x57A2, 0x6784, 0x8D2D, 0x591F, 0x8F9C, 0x83C7, 0x5495, 0x7B8D, 0x4F30, 0x6CBD, 0x5B64, 0x59D1, 0x9F13, 0x53E4, 0x86CA, 0x9AA8, 0x8C37, 0x80A1, 0x6545, 0x987E, 0x56FA, 0x96C7, 0x522E, 0x74DC, 0x5250, 0x5BE1, 0x6302, 0x8902, 0x4E56, 0x62D0, 0x602A, 0x68FA, 0x5173, 0x5B98, 0x51A0, 0x89C2, 0x7BA1, 0x9986, 0x7F50, 0x60EF, 0x704C, 0x8D2F, 0x5149, 0x5E7F, 0x901B, 0x7470, 0x89C4, 0x572D, 0x7845, 0x5F52, 0x9F9F, 0x95FA, 0x8F68, 0x9B3C, 0x8BE1, 0x7678, 0x6842, 0x67DC, 0x8DEA, 0x8D35, 0x523D, 0x8F8A, 0x6EDA, 0x68CD, 0x9505, 0x90ED, 0x56FD, 0x679C, 0x88F9, 0x8FC7, 0x54C8, + 0x7BC5, 0x7BC8, 0x7BC9, 0x7BCA, 0x7BCB, 0x7BCD, 0x7BCE, 0x7BCF, 0x7BD0, 0x7BD2, 0x7BD4, 0x7BD5, 0x7BD6, 0x7BD7, 0x7BD8, 0x7BDB, 0x7BDC, 0x7BDE, 0x7BDF, 0x7BE0, 0x7BE2, 0x7BE3, 0x7BE4, 0x7BE7, 0x7BE8, 0x7BE9, 0x7BEB, 0x7BEC, 0x7BED, 0x7BEF, 0x7BF0, 0x7BF2, 0x7BF3, 0x7BF4, 0x7BF5, 0x7BF6, 0x7BF8, 0x7BF9, 0x7BFA, 0x7BFB, 0x7BFD, 0x7BFF, 0x7C00, 0x7C01, 0x7C02, 0x7C03, 0x7C04, 0x7C05, 0x7C06, 0x7C08, 0x7C09, 0x7C0A, 0x7C0D, 0x7C0E, 0x7C10, 0x7C11, 0x7C12, 0x7C13, 0x7C14, 0x7C15, 0x7C17, 0x7C18, 0x7C19, 0x7C1A, 0x7C1B, 0x7C1C, 0x7C1D, 0x7C1E, 0x7C20, 0x7C21, 0x7C22, 0x7C23, 0x7C24, 0x7C25, 0x7C28, 0x7C29, 0x7C2B, 0x7C2C, 0x7C2D, 0x7C2E, 0x7C2F, 0x7C30, 0x7C31, 0x7C32, 0x7C33, 0x7C34, 0x7C35, 0x7C36, 0x7C37, 0x7C39, 0x7C3A, 0x7C3B, 0x7C3C, 0x7C3D, 0x7C3E, + 0x7C42, 0x9AB8, 0x5B69, 0x6D77, 0x6C26, 0x4EA5, 0x5BB3, 0x9A87, 0x9163, 0x61A8, 0x90AF, 0x97E9, 0x542B, 0x6DB5, 0x5BD2, 0x51FD, 0x558A, 0x7F55, 0x7FF0, 0x64BC, 0x634D, 0x65F1, 0x61BE, 0x608D, 0x710A, 0x6C57, 0x6C49, 0x592F, 0x676D, 0x822A, 0x58D5, 0x568E, 0x8C6A, 0x6BEB, 0x90DD, 0x597D, 0x8017, 0x53F7, 0x6D69, 0x5475, 0x559D, 0x8377, 0x83CF, 0x6838, 0x79BE, 0x548C, 0x4F55, 0x5408, 0x76D2, 0x8C89, 0x9602, 0x6CB3, 0x6DB8, 0x8D6B, 0x8910, 0x9E64, 0x8D3A, 0x563F, 0x9ED1, 0x75D5, 0x5F88, 0x72E0, 0x6068, 0x54FC, 0x4EA8, 0x6A2A, 0x8861, 0x6052, 0x8F70, 0x54C4, 0x70D8, 0x8679, 0x9E3F, 0x6D2A, 0x5B8F, 0x5F18, 0x7EA2, 0x5589, 0x4FAF, 0x7334, 0x543C, 0x539A, 0x5019, 0x540E, 0x547C, 0x4E4E, 0x5FFD, 0x745A, 0x58F6, 0x846B, 0x80E1, 0x8774, 0x72D0, 0x7CCA, 0x6E56, + 0x7C43, 0x7C44, 0x7C45, 0x7C46, 0x7C47, 0x7C48, 0x7C49, 0x7C4A, 0x7C4B, 0x7C4C, 0x7C4E, 0x7C4F, 0x7C50, 0x7C51, 0x7C52, 0x7C53, 0x7C54, 0x7C55, 0x7C56, 0x7C57, 0x7C58, 0x7C59, 0x7C5A, 0x7C5B, 0x7C5C, 0x7C5D, 0x7C5E, 0x7C5F, 0x7C60, 0x7C61, 0x7C62, 0x7C63, 0x7C64, 0x7C65, 0x7C66, 0x7C67, 0x7C68, 0x7C69, 0x7C6A, 0x7C6B, 0x7C6C, 0x7C6D, 0x7C6E, 0x7C6F, 0x7C70, 0x7C71, 0x7C72, 0x7C75, 0x7C76, 0x7C77, 0x7C78, 0x7C79, 0x7C7A, 0x7C7E, 0x7C7F, 0x7C80, 0x7C81, 0x7C82, 0x7C83, 0x7C84, 0x7C85, 0x7C86, 0x7C87, 0x7C88, 0x7C8A, 0x7C8B, 0x7C8C, 0x7C8D, 0x7C8E, 0x7C8F, 0x7C90, 0x7C93, 0x7C94, 0x7C96, 0x7C99, 0x7C9A, 0x7C9B, 0x7CA0, 0x7CA1, 0x7CA3, 0x7CA6, 0x7CA7, 0x7CA8, 0x7CA9, 0x7CAB, 0x7CAC, 0x7CAD, 0x7CAF, 0x7CB0, 0x7CB4, 0x7CB5, 0x7CB6, 0x7CB7, 0x7CB8, 0x7CBA, + 0x7CBB, 0x5F27, 0x864E, 0x552C, 0x62A4, 0x4E92, 0x6CAA, 0x6237, 0x82B1, 0x54D7, 0x534E, 0x733E, 0x6ED1, 0x753B, 0x5212, 0x5316, 0x8BDD, 0x69D0, 0x5F8A, 0x6000, 0x6DEE, 0x574F, 0x6B22, 0x73AF, 0x6853, 0x8FD8, 0x7F13, 0x6362, 0x60A3, 0x5524, 0x75EA, 0x8C62, 0x7115, 0x6DA3, 0x5BA6, 0x5E7B, 0x8352, 0x614C, 0x9EC4, 0x78FA, 0x8757, 0x7C27, 0x7687, 0x51F0, 0x60F6, 0x714C, 0x6643, 0x5E4C, 0x604D, 0x8C0E, 0x7070, 0x6325, 0x8F89, 0x5FBD, 0x6062, 0x86D4, 0x56DE, 0x6BC1, 0x6094, 0x6167, 0x5349, 0x60E0, 0x6666, 0x8D3F, 0x79FD, 0x4F1A, 0x70E9, 0x6C47, 0x8BB3, 0x8BF2, 0x7ED8, 0x8364, 0x660F, 0x5A5A, 0x9B42, 0x6D51, 0x6DF7, 0x8C41, 0x6D3B, 0x4F19, 0x706B, 0x83B7, 0x6216, 0x60D1, 0x970D, 0x8D27, 0x7978, 0x51FB, 0x573E, 0x57FA, 0x673A, 0x7578, 0x7A3D, 0x79EF, 0x7B95, + 0x7CBF, 0x7CC0, 0x7CC2, 0x7CC3, 0x7CC4, 0x7CC6, 0x7CC9, 0x7CCB, 0x7CCE, 0x7CCF, 0x7CD0, 0x7CD1, 0x7CD2, 0x7CD3, 0x7CD4, 0x7CD8, 0x7CDA, 0x7CDB, 0x7CDD, 0x7CDE, 0x7CE1, 0x7CE2, 0x7CE3, 0x7CE4, 0x7CE5, 0x7CE6, 0x7CE7, 0x7CE9, 0x7CEA, 0x7CEB, 0x7CEC, 0x7CED, 0x7CEE, 0x7CF0, 0x7CF1, 0x7CF2, 0x7CF3, 0x7CF4, 0x7CF5, 0x7CF6, 0x7CF7, 0x7CF9, 0x7CFA, 0x7CFC, 0x7CFD, 0x7CFE, 0x7CFF, 0x7D00, 0x7D01, 0x7D02, 0x7D03, 0x7D04, 0x7D05, 0x7D06, 0x7D07, 0x7D08, 0x7D09, 0x7D0B, 0x7D0C, 0x7D0D, 0x7D0E, 0x7D0F, 0x7D10, 0x7D11, 0x7D12, 0x7D13, 0x7D14, 0x7D15, 0x7D16, 0x7D17, 0x7D18, 0x7D19, 0x7D1A, 0x7D1B, 0x7D1C, 0x7D1D, 0x7D1E, 0x7D1F, 0x7D21, 0x7D23, 0x7D24, 0x7D25, 0x7D26, 0x7D28, 0x7D29, 0x7D2A, 0x7D2C, 0x7D2D, 0x7D2E, 0x7D30, 0x7D31, 0x7D32, 0x7D33, 0x7D34, 0x7D35, + 0x7D36, 0x808C, 0x9965, 0x8FF9, 0x6FC0, 0x8BA5, 0x9E21, 0x59EC, 0x7EE9, 0x7F09, 0x5409, 0x6781, 0x68D8, 0x8F91, 0x7C4D, 0x96C6, 0x53CA, 0x6025, 0x75BE, 0x6C72, 0x5373, 0x5AC9, 0x7EA7, 0x6324, 0x51E0, 0x810A, 0x5DF1, 0x84DF, 0x6280, 0x5180, 0x5B63, 0x4F0E, 0x796D, 0x5242, 0x60B8, 0x6D4E, 0x5BC4, 0x5BC2, 0x8BA1, 0x8BB0, 0x65E2, 0x5FCC, 0x9645, 0x5993, 0x7EE7, 0x7EAA, 0x5609, 0x67B7, 0x5939, 0x4F73, 0x5BB6, 0x52A0, 0x835A, 0x988A, 0x8D3E, 0x7532, 0x94BE, 0x5047, 0x7A3C, 0x4EF7, 0x67B6, 0x9A7E, 0x5AC1, 0x6B7C, 0x76D1, 0x575A, 0x5C16, 0x7B3A, 0x95F4, 0x714E, 0x517C, 0x80A9, 0x8270, 0x5978, 0x7F04, 0x8327, 0x68C0, 0x67EC, 0x78B1, 0x7877, 0x62E3, 0x6361, 0x7B80, 0x4FED, 0x526A, 0x51CF, 0x8350, 0x69DB, 0x9274, 0x8DF5, 0x8D31, 0x89C1, 0x952E, 0x7BAD, 0x4EF6, + 0x7D37, 0x7D38, 0x7D39, 0x7D3A, 0x7D3B, 0x7D3C, 0x7D3D, 0x7D3E, 0x7D3F, 0x7D40, 0x7D41, 0x7D42, 0x7D43, 0x7D44, 0x7D45, 0x7D46, 0x7D47, 0x7D48, 0x7D49, 0x7D4A, 0x7D4B, 0x7D4C, 0x7D4D, 0x7D4E, 0x7D4F, 0x7D50, 0x7D51, 0x7D52, 0x7D53, 0x7D54, 0x7D55, 0x7D56, 0x7D57, 0x7D58, 0x7D59, 0x7D5A, 0x7D5B, 0x7D5C, 0x7D5D, 0x7D5E, 0x7D5F, 0x7D60, 0x7D61, 0x7D62, 0x7D63, 0x7D64, 0x7D65, 0x7D66, 0x7D67, 0x7D68, 0x7D69, 0x7D6A, 0x7D6B, 0x7D6C, 0x7D6D, 0x7D6F, 0x7D70, 0x7D71, 0x7D72, 0x7D73, 0x7D74, 0x7D75, 0x7D76, 0x7D78, 0x7D79, 0x7D7A, 0x7D7B, 0x7D7C, 0x7D7D, 0x7D7E, 0x7D7F, 0x7D80, 0x7D81, 0x7D82, 0x7D83, 0x7D84, 0x7D85, 0x7D86, 0x7D87, 0x7D88, 0x7D89, 0x7D8A, 0x7D8B, 0x7D8C, 0x7D8D, 0x7D8E, 0x7D8F, 0x7D90, 0x7D91, 0x7D92, 0x7D93, 0x7D94, 0x7D95, 0x7D96, 0x7D97, + 0x7D98, 0x5065, 0x8230, 0x5251, 0x996F, 0x6E10, 0x6E85, 0x6DA7, 0x5EFA, 0x50F5, 0x59DC, 0x5C06, 0x6D46, 0x6C5F, 0x7586, 0x848B, 0x6868, 0x5956, 0x8BB2, 0x5320, 0x9171, 0x964D, 0x8549, 0x6912, 0x7901, 0x7126, 0x80F6, 0x4EA4, 0x90CA, 0x6D47, 0x9A84, 0x5A07, 0x56BC, 0x6405, 0x94F0, 0x77EB, 0x4FA5, 0x811A, 0x72E1, 0x89D2, 0x997A, 0x7F34, 0x7EDE, 0x527F, 0x6559, 0x9175, 0x8F7F, 0x8F83, 0x53EB, 0x7A96, 0x63ED, 0x63A5, 0x7686, 0x79F8, 0x8857, 0x9636, 0x622A, 0x52AB, 0x8282, 0x6854, 0x6770, 0x6377, 0x776B, 0x7AED, 0x6D01, 0x7ED3, 0x89E3, 0x59D0, 0x6212, 0x85C9, 0x82A5, 0x754C, 0x501F, 0x4ECB, 0x75A5, 0x8BEB, 0x5C4A, 0x5DFE, 0x7B4B, 0x65A4, 0x91D1, 0x4ECA, 0x6D25, 0x895F, 0x7D27, 0x9526, 0x4EC5, 0x8C28, 0x8FDB, 0x9773, 0x664B, 0x7981, 0x8FD1, 0x70EC, 0x6D78, + 0x7D99, 0x7D9A, 0x7D9B, 0x7D9C, 0x7D9D, 0x7D9E, 0x7D9F, 0x7DA0, 0x7DA1, 0x7DA2, 0x7DA3, 0x7DA4, 0x7DA5, 0x7DA7, 0x7DA8, 0x7DA9, 0x7DAA, 0x7DAB, 0x7DAC, 0x7DAD, 0x7DAF, 0x7DB0, 0x7DB1, 0x7DB2, 0x7DB3, 0x7DB4, 0x7DB5, 0x7DB6, 0x7DB7, 0x7DB8, 0x7DB9, 0x7DBA, 0x7DBB, 0x7DBC, 0x7DBD, 0x7DBE, 0x7DBF, 0x7DC0, 0x7DC1, 0x7DC2, 0x7DC3, 0x7DC4, 0x7DC5, 0x7DC6, 0x7DC7, 0x7DC8, 0x7DC9, 0x7DCA, 0x7DCB, 0x7DCC, 0x7DCD, 0x7DCE, 0x7DCF, 0x7DD0, 0x7DD1, 0x7DD2, 0x7DD3, 0x7DD4, 0x7DD5, 0x7DD6, 0x7DD7, 0x7DD8, 0x7DD9, 0x7DDA, 0x7DDB, 0x7DDC, 0x7DDD, 0x7DDE, 0x7DDF, 0x7DE0, 0x7DE1, 0x7DE2, 0x7DE3, 0x7DE4, 0x7DE5, 0x7DE6, 0x7DE7, 0x7DE8, 0x7DE9, 0x7DEA, 0x7DEB, 0x7DEC, 0x7DED, 0x7DEE, 0x7DEF, 0x7DF0, 0x7DF1, 0x7DF2, 0x7DF3, 0x7DF4, 0x7DF5, 0x7DF6, 0x7DF7, 0x7DF8, 0x7DF9, + 0x7DFA, 0x5C3D, 0x52B2, 0x8346, 0x5162, 0x830E, 0x775B, 0x6676, 0x9CB8, 0x4EAC, 0x60CA, 0x7CBE, 0x7CB3, 0x7ECF, 0x4E95, 0x8B66, 0x666F, 0x9888, 0x9759, 0x5883, 0x656C, 0x955C, 0x5F84, 0x75C9, 0x9756, 0x7ADF, 0x7ADE, 0x51C0, 0x70AF, 0x7A98, 0x63EA, 0x7A76, 0x7EA0, 0x7396, 0x97ED, 0x4E45, 0x7078, 0x4E5D, 0x9152, 0x53A9, 0x6551, 0x65E7, 0x81FC, 0x8205, 0x548E, 0x5C31, 0x759A, 0x97A0, 0x62D8, 0x72D9, 0x75BD, 0x5C45, 0x9A79, 0x83CA, 0x5C40, 0x5480, 0x77E9, 0x4E3E, 0x6CAE, 0x805A, 0x62D2, 0x636E, 0x5DE8, 0x5177, 0x8DDD, 0x8E1E, 0x952F, 0x4FF1, 0x53E5, 0x60E7, 0x70AC, 0x5267, 0x6350, 0x9E43, 0x5A1F, 0x5026, 0x7737, 0x5377, 0x7EE2, 0x6485, 0x652B, 0x6289, 0x6398, 0x5014, 0x7235, 0x89C9, 0x51B3, 0x8BC0, 0x7EDD, 0x5747, 0x83CC, 0x94A7, 0x519B, 0x541B, 0x5CFB, + 0x7DFB, 0x7DFC, 0x7DFD, 0x7DFE, 0x7DFF, 0x7E00, 0x7E01, 0x7E02, 0x7E03, 0x7E04, 0x7E05, 0x7E06, 0x7E07, 0x7E08, 0x7E09, 0x7E0A, 0x7E0B, 0x7E0C, 0x7E0D, 0x7E0E, 0x7E0F, 0x7E10, 0x7E11, 0x7E12, 0x7E13, 0x7E14, 0x7E15, 0x7E16, 0x7E17, 0x7E18, 0x7E19, 0x7E1A, 0x7E1B, 0x7E1C, 0x7E1D, 0x7E1E, 0x7E1F, 0x7E20, 0x7E21, 0x7E22, 0x7E23, 0x7E24, 0x7E25, 0x7E26, 0x7E27, 0x7E28, 0x7E29, 0x7E2A, 0x7E2B, 0x7E2C, 0x7E2D, 0x7E2E, 0x7E2F, 0x7E30, 0x7E31, 0x7E32, 0x7E33, 0x7E34, 0x7E35, 0x7E36, 0x7E37, 0x7E38, 0x7E39, 0x7E3A, 0x7E3C, 0x7E3D, 0x7E3E, 0x7E3F, 0x7E40, 0x7E42, 0x7E43, 0x7E44, 0x7E45, 0x7E46, 0x7E48, 0x7E49, 0x7E4A, 0x7E4B, 0x7E4C, 0x7E4D, 0x7E4E, 0x7E4F, 0x7E50, 0x7E51, 0x7E52, 0x7E53, 0x7E54, 0x7E55, 0x7E56, 0x7E57, 0x7E58, 0x7E59, 0x7E5A, 0x7E5B, 0x7E5C, + 0x7E5D, 0x4FCA, 0x7AE3, 0x6D5A, 0x90E1, 0x9A8F, 0x5580, 0x5496, 0x5361, 0x54AF, 0x5F00, 0x63E9, 0x6977, 0x51EF, 0x6168, 0x520A, 0x582A, 0x52D8, 0x574E, 0x780D, 0x770B, 0x5EB7, 0x6177, 0x7CE0, 0x625B, 0x6297, 0x4EA2, 0x7095, 0x8003, 0x62F7, 0x70E4, 0x9760, 0x5777, 0x82DB, 0x67EF, 0x68F5, 0x78D5, 0x9897, 0x79D1, 0x58F3, 0x54B3, 0x53EF, 0x6E34, 0x514B, 0x523B, 0x5BA2, 0x8BFE, 0x80AF, 0x5543, 0x57A6, 0x6073, 0x5751, 0x542D, 0x7A7A, 0x6050, 0x5B54, 0x63A7, 0x62A0, 0x53E3, 0x6263, 0x5BC7, 0x67AF, 0x54ED, 0x7A9F, 0x82E6, 0x9177, 0x5E93, 0x88E4, 0x5938, 0x57AE, 0x630E, 0x8DE8, 0x80EF, 0x5757, 0x7B77, 0x4FA9, 0x5FEB, 0x5BBD, 0x6B3E, 0x5321, 0x7B50, 0x72C2, 0x6846, 0x77FF, 0x7736, 0x65F7, 0x51B5, 0x4E8F, 0x76D4, 0x5CBF, 0x7AA5, 0x8475, 0x594E, 0x9B41, 0x5080, + 0x7E5E, 0x7E5F, 0x7E60, 0x7E61, 0x7E62, 0x7E63, 0x7E64, 0x7E65, 0x7E66, 0x7E67, 0x7E68, 0x7E69, 0x7E6A, 0x7E6B, 0x7E6C, 0x7E6D, 0x7E6E, 0x7E6F, 0x7E70, 0x7E71, 0x7E72, 0x7E73, 0x7E74, 0x7E75, 0x7E76, 0x7E77, 0x7E78, 0x7E79, 0x7E7A, 0x7E7B, 0x7E7C, 0x7E7D, 0x7E7E, 0x7E7F, 0x7E80, 0x7E81, 0x7E83, 0x7E84, 0x7E85, 0x7E86, 0x7E87, 0x7E88, 0x7E89, 0x7E8A, 0x7E8B, 0x7E8C, 0x7E8D, 0x7E8E, 0x7E8F, 0x7E90, 0x7E91, 0x7E92, 0x7E93, 0x7E94, 0x7E95, 0x7E96, 0x7E97, 0x7E98, 0x7E99, 0x7E9A, 0x7E9C, 0x7E9D, 0x7E9E, 0x7EAE, 0x7EB4, 0x7EBB, 0x7EBC, 0x7ED6, 0x7EE4, 0x7EEC, 0x7EF9, 0x7F0A, 0x7F10, 0x7F1E, 0x7F37, 0x7F39, 0x7F3B, 0x7F3C, 0x7F3D, 0x7F3E, 0x7F3F, 0x7F40, 0x7F41, 0x7F43, 0x7F46, 0x7F47, 0x7F48, 0x7F49, 0x7F4A, 0x7F4B, 0x7F4C, 0x7F4D, 0x7F4E, 0x7F4F, 0x7F52, + 0x7F53, 0x9988, 0x6127, 0x6E83, 0x5764, 0x6606, 0x6346, 0x56F0, 0x62EC, 0x6269, 0x5ED3, 0x9614, 0x5783, 0x62C9, 0x5587, 0x8721, 0x814A, 0x8FA3, 0x5566, 0x83B1, 0x6765, 0x8D56, 0x84DD, 0x5A6A, 0x680F, 0x62E6, 0x7BEE, 0x9611, 0x5170, 0x6F9C, 0x8C30, 0x63FD, 0x89C8, 0x61D2, 0x7F06, 0x70C2, 0x6EE5, 0x7405, 0x6994, 0x72FC, 0x5ECA, 0x90CE, 0x6717, 0x6D6A, 0x635E, 0x52B3, 0x7262, 0x8001, 0x4F6C, 0x59E5, 0x916A, 0x70D9, 0x6D9D, 0x52D2, 0x4E50, 0x96F7, 0x956D, 0x857E, 0x78CA, 0x7D2F, 0x5121, 0x5792, 0x64C2, 0x808B, 0x7C7B, 0x6CEA, 0x68F1, 0x695E, 0x51B7, 0x5398, 0x68A8, 0x7281, 0x9ECE, 0x7BF1, 0x72F8, 0x79BB, 0x6F13, 0x7406, 0x674E, 0x91CC, 0x9CA4, 0x793C, 0x8389, 0x8354, 0x540F, 0x6817, 0x4E3D, 0x5389, 0x52B1, 0x783E, 0x5386, 0x5229, 0x5088, 0x4F8B, 0x4FD0, + 0x7F56, 0x7F59, 0x7F5B, 0x7F5C, 0x7F5D, 0x7F5E, 0x7F60, 0x7F63, 0x7F64, 0x7F65, 0x7F66, 0x7F67, 0x7F6B, 0x7F6C, 0x7F6D, 0x7F6F, 0x7F70, 0x7F73, 0x7F75, 0x7F76, 0x7F77, 0x7F78, 0x7F7A, 0x7F7B, 0x7F7C, 0x7F7D, 0x7F7F, 0x7F80, 0x7F82, 0x7F83, 0x7F84, 0x7F85, 0x7F86, 0x7F87, 0x7F88, 0x7F89, 0x7F8B, 0x7F8D, 0x7F8F, 0x7F90, 0x7F91, 0x7F92, 0x7F93, 0x7F95, 0x7F96, 0x7F97, 0x7F98, 0x7F99, 0x7F9B, 0x7F9C, 0x7FA0, 0x7FA2, 0x7FA3, 0x7FA5, 0x7FA6, 0x7FA8, 0x7FA9, 0x7FAA, 0x7FAB, 0x7FAC, 0x7FAD, 0x7FAE, 0x7FB1, 0x7FB3, 0x7FB4, 0x7FB5, 0x7FB6, 0x7FB7, 0x7FBA, 0x7FBB, 0x7FBE, 0x7FC0, 0x7FC2, 0x7FC3, 0x7FC4, 0x7FC6, 0x7FC7, 0x7FC8, 0x7FC9, 0x7FCB, 0x7FCD, 0x7FCF, 0x7FD0, 0x7FD1, 0x7FD2, 0x7FD3, 0x7FD6, 0x7FD7, 0x7FD9, 0x7FDA, 0x7FDB, 0x7FDC, 0x7FDD, 0x7FDE, 0x7FE2, + 0x7FE3, 0x75E2, 0x7ACB, 0x7C92, 0x6CA5, 0x96B6, 0x529B, 0x7483, 0x54E9, 0x4FE9, 0x8054, 0x83B2, 0x8FDE, 0x9570, 0x5EC9, 0x601C, 0x6D9F, 0x5E18, 0x655B, 0x8138, 0x94FE, 0x604B, 0x70BC, 0x7EC3, 0x7CAE, 0x51C9, 0x6881, 0x7CB1, 0x826F, 0x4E24, 0x8F86, 0x91CF, 0x667E, 0x4EAE, 0x8C05, 0x64A9, 0x804A, 0x50DA, 0x7597, 0x71CE, 0x5BE5, 0x8FBD, 0x6F66, 0x4E86, 0x6482, 0x9563, 0x5ED6, 0x6599, 0x5217, 0x88C2, 0x70C8, 0x52A3, 0x730E, 0x7433, 0x6797, 0x78F7, 0x9716, 0x4E34, 0x90BB, 0x9CDE, 0x6DCB, 0x51DB, 0x8D41, 0x541D, 0x62CE, 0x73B2, 0x83F1, 0x96F6, 0x9F84, 0x94C3, 0x4F36, 0x7F9A, 0x51CC, 0x7075, 0x9675, 0x5CAD, 0x9886, 0x53E6, 0x4EE4, 0x6E9C, 0x7409, 0x69B4, 0x786B, 0x998F, 0x7559, 0x5218, 0x7624, 0x6D41, 0x67F3, 0x516D, 0x9F99, 0x804B, 0x5499, 0x7B3C, 0x7ABF, + 0x7FE4, 0x7FE7, 0x7FE8, 0x7FEA, 0x7FEB, 0x7FEC, 0x7FED, 0x7FEF, 0x7FF2, 0x7FF4, 0x7FF5, 0x7FF6, 0x7FF7, 0x7FF8, 0x7FF9, 0x7FFA, 0x7FFD, 0x7FFE, 0x7FFF, 0x8002, 0x8007, 0x8008, 0x8009, 0x800A, 0x800E, 0x800F, 0x8011, 0x8013, 0x801A, 0x801B, 0x801D, 0x801E, 0x801F, 0x8021, 0x8023, 0x8024, 0x802B, 0x802C, 0x802D, 0x802E, 0x802F, 0x8030, 0x8032, 0x8034, 0x8039, 0x803A, 0x803C, 0x803E, 0x8040, 0x8041, 0x8044, 0x8045, 0x8047, 0x8048, 0x8049, 0x804E, 0x804F, 0x8050, 0x8051, 0x8053, 0x8055, 0x8056, 0x8057, 0x8059, 0x805B, 0x805C, 0x805D, 0x805E, 0x805F, 0x8060, 0x8061, 0x8062, 0x8063, 0x8064, 0x8065, 0x8066, 0x8067, 0x8068, 0x806B, 0x806C, 0x806D, 0x806E, 0x806F, 0x8070, 0x8072, 0x8073, 0x8074, 0x8075, 0x8076, 0x8077, 0x8078, 0x8079, 0x807A, 0x807B, 0x807C, + 0x807D, 0x9686, 0x5784, 0x62E2, 0x9647, 0x697C, 0x5A04, 0x6402, 0x7BD3, 0x6F0F, 0x964B, 0x82A6, 0x5362, 0x9885, 0x5E90, 0x7089, 0x63B3, 0x5364, 0x864F, 0x9C81, 0x9E93, 0x788C, 0x9732, 0x8DEF, 0x8D42, 0x9E7F, 0x6F5E, 0x7984, 0x5F55, 0x9646, 0x622E, 0x9A74, 0x5415, 0x94DD, 0x4FA3, 0x65C5, 0x5C65, 0x5C61, 0x7F15, 0x8651, 0x6C2F, 0x5F8B, 0x7387, 0x6EE4, 0x7EFF, 0x5CE6, 0x631B, 0x5B6A, 0x6EE6, 0x5375, 0x4E71, 0x63A0, 0x7565, 0x62A1, 0x8F6E, 0x4F26, 0x4ED1, 0x6CA6, 0x7EB6, 0x8BBA, 0x841D, 0x87BA, 0x7F57, 0x903B, 0x9523, 0x7BA9, 0x9AA1, 0x88F8, 0x843D, 0x6D1B, 0x9A86, 0x7EDC, 0x5988, 0x9EBB, 0x739B, 0x7801, 0x8682, 0x9A6C, 0x9A82, 0x561B, 0x5417, 0x57CB, 0x4E70, 0x9EA6, 0x5356, 0x8FC8, 0x8109, 0x7792, 0x9992, 0x86EE, 0x6EE1, 0x8513, 0x66FC, 0x6162, 0x6F2B, + 0x807E, 0x8081, 0x8082, 0x8085, 0x8088, 0x808A, 0x808D, 0x808E, 0x808F, 0x8090, 0x8091, 0x8092, 0x8094, 0x8095, 0x8097, 0x8099, 0x809E, 0x80A3, 0x80A6, 0x80A7, 0x80A8, 0x80AC, 0x80B0, 0x80B3, 0x80B5, 0x80B6, 0x80B8, 0x80B9, 0x80BB, 0x80C5, 0x80C7, 0x80C8, 0x80C9, 0x80CA, 0x80CB, 0x80CF, 0x80D0, 0x80D1, 0x80D2, 0x80D3, 0x80D4, 0x80D5, 0x80D8, 0x80DF, 0x80E0, 0x80E2, 0x80E3, 0x80E6, 0x80EE, 0x80F5, 0x80F7, 0x80F9, 0x80FB, 0x80FE, 0x80FF, 0x8100, 0x8101, 0x8103, 0x8104, 0x8105, 0x8107, 0x8108, 0x810B, 0x810C, 0x8115, 0x8117, 0x8119, 0x811B, 0x811C, 0x811D, 0x811F, 0x8120, 0x8121, 0x8122, 0x8123, 0x8124, 0x8125, 0x8126, 0x8127, 0x8128, 0x8129, 0x812A, 0x812B, 0x812D, 0x812E, 0x8130, 0x8133, 0x8134, 0x8135, 0x8137, 0x8139, 0x813A, 0x813B, 0x813C, 0x813D, + 0x813F, 0x8C29, 0x8292, 0x832B, 0x76F2, 0x6C13, 0x5FD9, 0x83BD, 0x732B, 0x8305, 0x951A, 0x6BDB, 0x77DB, 0x94C6, 0x536F, 0x8302, 0x5192, 0x5E3D, 0x8C8C, 0x8D38, 0x4E48, 0x73AB, 0x679A, 0x6885, 0x9176, 0x9709, 0x7164, 0x6CA1, 0x7709, 0x5A92, 0x9541, 0x6BCF, 0x7F8E, 0x6627, 0x5BD0, 0x59B9, 0x5A9A, 0x95E8, 0x95F7, 0x4EEC, 0x840C, 0x8499, 0x6AAC, 0x76DF, 0x9530, 0x731B, 0x68A6, 0x5B5F, 0x772F, 0x919A, 0x9761, 0x7CDC, 0x8FF7, 0x8C1C, 0x5F25, 0x7C73, 0x79D8, 0x89C5, 0x6CCC, 0x871C, 0x5BC6, 0x5E42, 0x68C9, 0x7720, 0x7EF5, 0x5195, 0x514D, 0x52C9, 0x5A29, 0x7F05, 0x9762, 0x82D7, 0x63CF, 0x7784, 0x85D0, 0x79D2, 0x6E3A, 0x5E99, 0x5999, 0x8511, 0x706D, 0x6C11, 0x62BF, 0x76BF, 0x654F, 0x60AF, 0x95FD, 0x660E, 0x879F, 0x9E23, 0x94ED, 0x540D, 0x547D, 0x8C2C, 0x6478, + 0x8140, 0x8141, 0x8142, 0x8143, 0x8144, 0x8145, 0x8147, 0x8149, 0x814D, 0x814E, 0x814F, 0x8152, 0x8156, 0x8157, 0x8158, 0x815B, 0x815C, 0x815D, 0x815E, 0x815F, 0x8161, 0x8162, 0x8163, 0x8164, 0x8166, 0x8168, 0x816A, 0x816B, 0x816C, 0x816F, 0x8172, 0x8173, 0x8175, 0x8176, 0x8177, 0x8178, 0x8181, 0x8183, 0x8184, 0x8185, 0x8186, 0x8187, 0x8189, 0x818B, 0x818C, 0x818D, 0x818E, 0x8190, 0x8192, 0x8193, 0x8194, 0x8195, 0x8196, 0x8197, 0x8199, 0x819A, 0x819E, 0x819F, 0x81A0, 0x81A1, 0x81A2, 0x81A4, 0x81A5, 0x81A7, 0x81A9, 0x81AB, 0x81AC, 0x81AD, 0x81AE, 0x81AF, 0x81B0, 0x81B1, 0x81B2, 0x81B4, 0x81B5, 0x81B6, 0x81B7, 0x81B8, 0x81B9, 0x81BC, 0x81BD, 0x81BE, 0x81BF, 0x81C4, 0x81C5, 0x81C7, 0x81C8, 0x81C9, 0x81CB, 0x81CD, 0x81CE, 0x81CF, 0x81D0, 0x81D1, 0x81D2, + 0x81D3, 0x6479, 0x8611, 0x6A21, 0x819C, 0x78E8, 0x6469, 0x9B54, 0x62B9, 0x672B, 0x83AB, 0x58A8, 0x9ED8, 0x6CAB, 0x6F20, 0x5BDE, 0x964C, 0x8C0B, 0x725F, 0x67D0, 0x62C7, 0x7261, 0x4EA9, 0x59C6, 0x6BCD, 0x5893, 0x66AE, 0x5E55, 0x52DF, 0x6155, 0x6728, 0x76EE, 0x7766, 0x7267, 0x7A46, 0x62FF, 0x54EA, 0x5450, 0x94A0, 0x90A3, 0x5A1C, 0x7EB3, 0x6C16, 0x4E43, 0x5976, 0x8010, 0x5948, 0x5357, 0x7537, 0x96BE, 0x56CA, 0x6320, 0x8111, 0x607C, 0x95F9, 0x6DD6, 0x5462, 0x9981, 0x5185, 0x5AE9, 0x80FD, 0x59AE, 0x9713, 0x502A, 0x6CE5, 0x5C3C, 0x62DF, 0x4F60, 0x533F, 0x817B, 0x9006, 0x6EBA, 0x852B, 0x62C8, 0x5E74, 0x78BE, 0x64B5, 0x637B, 0x5FF5, 0x5A18, 0x917F, 0x9E1F, 0x5C3F, 0x634F, 0x8042, 0x5B7D, 0x556E, 0x954A, 0x954D, 0x6D85, 0x60A8, 0x67E0, 0x72DE, 0x51DD, 0x5B81, + 0x81D4, 0x81D5, 0x81D6, 0x81D7, 0x81D8, 0x81D9, 0x81DA, 0x81DB, 0x81DC, 0x81DD, 0x81DE, 0x81DF, 0x81E0, 0x81E1, 0x81E2, 0x81E4, 0x81E5, 0x81E6, 0x81E8, 0x81E9, 0x81EB, 0x81EE, 0x81EF, 0x81F0, 0x81F1, 0x81F2, 0x81F5, 0x81F6, 0x81F7, 0x81F8, 0x81F9, 0x81FA, 0x81FD, 0x81FF, 0x8203, 0x8207, 0x8208, 0x8209, 0x820A, 0x820B, 0x820E, 0x820F, 0x8211, 0x8213, 0x8215, 0x8216, 0x8217, 0x8218, 0x8219, 0x821A, 0x821D, 0x8220, 0x8224, 0x8225, 0x8226, 0x8227, 0x8229, 0x822E, 0x8232, 0x823A, 0x823C, 0x823D, 0x823F, 0x8240, 0x8241, 0x8242, 0x8243, 0x8245, 0x8246, 0x8248, 0x824A, 0x824C, 0x824D, 0x824E, 0x8250, 0x8251, 0x8252, 0x8253, 0x8254, 0x8255, 0x8256, 0x8257, 0x8259, 0x825B, 0x825C, 0x825D, 0x825E, 0x8260, 0x8261, 0x8262, 0x8263, 0x8264, 0x8265, 0x8266, 0x8267, + 0x8269, 0x62E7, 0x6CDE, 0x725B, 0x626D, 0x94AE, 0x7EBD, 0x8113, 0x6D53, 0x519C, 0x5F04, 0x5974, 0x52AA, 0x6012, 0x5973, 0x6696, 0x8650, 0x759F, 0x632A, 0x61E6, 0x7CEF, 0x8BFA, 0x54E6, 0x6B27, 0x9E25, 0x6BB4, 0x85D5, 0x5455, 0x5076, 0x6CA4, 0x556A, 0x8DB4, 0x722C, 0x5E15, 0x6015, 0x7436, 0x62CD, 0x6392, 0x724C, 0x5F98, 0x6E43, 0x6D3E, 0x6500, 0x6F58, 0x76D8, 0x78D0, 0x76FC, 0x7554, 0x5224, 0x53DB, 0x4E53, 0x5E9E, 0x65C1, 0x802A, 0x80D6, 0x629B, 0x5486, 0x5228, 0x70AE, 0x888D, 0x8DD1, 0x6CE1, 0x5478, 0x80DA, 0x57F9, 0x88F4, 0x8D54, 0x966A, 0x914D, 0x4F69, 0x6C9B, 0x55B7, 0x76C6, 0x7830, 0x62A8, 0x70F9, 0x6F8E, 0x5F6D, 0x84EC, 0x68DA, 0x787C, 0x7BF7, 0x81A8, 0x670B, 0x9E4F, 0x6367, 0x78B0, 0x576F, 0x7812, 0x9739, 0x6279, 0x62AB, 0x5288, 0x7435, 0x6BD7, + 0x826A, 0x826B, 0x826C, 0x826D, 0x8271, 0x8275, 0x8276, 0x8277, 0x8278, 0x827B, 0x827C, 0x8280, 0x8281, 0x8283, 0x8285, 0x8286, 0x8287, 0x8289, 0x828C, 0x8290, 0x8293, 0x8294, 0x8295, 0x8296, 0x829A, 0x829B, 0x829E, 0x82A0, 0x82A2, 0x82A3, 0x82A7, 0x82B2, 0x82B5, 0x82B6, 0x82BA, 0x82BB, 0x82BC, 0x82BF, 0x82C0, 0x82C2, 0x82C3, 0x82C5, 0x82C6, 0x82C9, 0x82D0, 0x82D6, 0x82D9, 0x82DA, 0x82DD, 0x82E2, 0x82E7, 0x82E8, 0x82E9, 0x82EA, 0x82EC, 0x82ED, 0x82EE, 0x82F0, 0x82F2, 0x82F3, 0x82F5, 0x82F6, 0x82F8, 0x82FA, 0x82FC, 0x82FD, 0x82FE, 0x82FF, 0x8300, 0x830A, 0x830B, 0x830D, 0x8310, 0x8312, 0x8313, 0x8316, 0x8318, 0x8319, 0x831D, 0x831E, 0x831F, 0x8320, 0x8321, 0x8322, 0x8323, 0x8324, 0x8325, 0x8326, 0x8329, 0x832A, 0x832E, 0x8330, 0x8332, 0x8337, 0x833B, + 0x833D, 0x5564, 0x813E, 0x75B2, 0x76AE, 0x5339, 0x75DE, 0x50FB, 0x5C41, 0x8B6C, 0x7BC7, 0x504F, 0x7247, 0x9A97, 0x98D8, 0x6F02, 0x74E2, 0x7968, 0x6487, 0x77A5, 0x62FC, 0x9891, 0x8D2B, 0x54C1, 0x8058, 0x4E52, 0x576A, 0x82F9, 0x840D, 0x5E73, 0x51ED, 0x74F6, 0x8BC4, 0x5C4F, 0x5761, 0x6CFC, 0x9887, 0x5A46, 0x7834, 0x9B44, 0x8FEB, 0x7C95, 0x5256, 0x6251, 0x94FA, 0x4EC6, 0x8386, 0x8461, 0x83E9, 0x84B2, 0x57D4, 0x6734, 0x5703, 0x666E, 0x6D66, 0x8C31, 0x66DD, 0x7011, 0x671F, 0x6B3A, 0x6816, 0x621A, 0x59BB, 0x4E03, 0x51C4, 0x6F06, 0x67D2, 0x6C8F, 0x5176, 0x68CB, 0x5947, 0x6B67, 0x7566, 0x5D0E, 0x8110, 0x9F50, 0x65D7, 0x7948, 0x7941, 0x9A91, 0x8D77, 0x5C82, 0x4E5E, 0x4F01, 0x542F, 0x5951, 0x780C, 0x5668, 0x6C14, 0x8FC4, 0x5F03, 0x6C7D, 0x6CE3, 0x8BAB, 0x6390, + 0x833E, 0x833F, 0x8341, 0x8342, 0x8344, 0x8345, 0x8348, 0x834A, 0x834B, 0x834C, 0x834D, 0x834E, 0x8353, 0x8355, 0x8356, 0x8357, 0x8358, 0x8359, 0x835D, 0x8362, 0x8370, 0x8371, 0x8372, 0x8373, 0x8374, 0x8375, 0x8376, 0x8379, 0x837A, 0x837E, 0x837F, 0x8380, 0x8381, 0x8382, 0x8383, 0x8384, 0x8387, 0x8388, 0x838A, 0x838B, 0x838C, 0x838D, 0x838F, 0x8390, 0x8391, 0x8394, 0x8395, 0x8396, 0x8397, 0x8399, 0x839A, 0x839D, 0x839F, 0x83A1, 0x83A2, 0x83A3, 0x83A4, 0x83A5, 0x83A6, 0x83A7, 0x83AC, 0x83AD, 0x83AE, 0x83AF, 0x83B5, 0x83BB, 0x83BE, 0x83BF, 0x83C2, 0x83C3, 0x83C4, 0x83C6, 0x83C8, 0x83C9, 0x83CB, 0x83CD, 0x83CE, 0x83D0, 0x83D1, 0x83D2, 0x83D3, 0x83D5, 0x83D7, 0x83D9, 0x83DA, 0x83DB, 0x83DE, 0x83E2, 0x83E3, 0x83E4, 0x83E6, 0x83E7, 0x83E8, 0x83EB, 0x83EC, + 0x83ED, 0x6070, 0x6D3D, 0x7275, 0x6266, 0x948E, 0x94C5, 0x5343, 0x8FC1, 0x7B7E, 0x4EDF, 0x8C26, 0x4E7E, 0x9ED4, 0x94B1, 0x94B3, 0x524D, 0x6F5C, 0x9063, 0x6D45, 0x8C34, 0x5811, 0x5D4C, 0x6B20, 0x6B49, 0x67AA, 0x545B, 0x8154, 0x7F8C, 0x5899, 0x8537, 0x5F3A, 0x62A2, 0x6A47, 0x9539, 0x6572, 0x6084, 0x6865, 0x77A7, 0x4E54, 0x4FA8, 0x5DE7, 0x9798, 0x64AC, 0x7FD8, 0x5CED, 0x4FCF, 0x7A8D, 0x5207, 0x8304, 0x4E14, 0x602F, 0x7A83, 0x94A6, 0x4FB5, 0x4EB2, 0x79E6, 0x7434, 0x52E4, 0x82B9, 0x64D2, 0x79BD, 0x5BDD, 0x6C81, 0x9752, 0x8F7B, 0x6C22, 0x503E, 0x537F, 0x6E05, 0x64CE, 0x6674, 0x6C30, 0x60C5, 0x9877, 0x8BF7, 0x5E86, 0x743C, 0x7A77, 0x79CB, 0x4E18, 0x90B1, 0x7403, 0x6C42, 0x56DA, 0x914B, 0x6CC5, 0x8D8B, 0x533A, 0x86C6, 0x66F2, 0x8EAF, 0x5C48, 0x9A71, 0x6E20, + 0x83EE, 0x83EF, 0x83F3, 0x83F4, 0x83F5, 0x83F6, 0x83F7, 0x83FA, 0x83FB, 0x83FC, 0x83FE, 0x83FF, 0x8400, 0x8402, 0x8405, 0x8407, 0x8408, 0x8409, 0x840A, 0x8410, 0x8412, 0x8413, 0x8414, 0x8415, 0x8416, 0x8417, 0x8419, 0x841A, 0x841B, 0x841E, 0x841F, 0x8420, 0x8421, 0x8422, 0x8423, 0x8429, 0x842A, 0x842B, 0x842C, 0x842D, 0x842E, 0x842F, 0x8430, 0x8432, 0x8433, 0x8434, 0x8435, 0x8436, 0x8437, 0x8439, 0x843A, 0x843B, 0x843E, 0x843F, 0x8440, 0x8441, 0x8442, 0x8443, 0x8444, 0x8445, 0x8447, 0x8448, 0x8449, 0x844A, 0x844B, 0x844C, 0x844D, 0x844E, 0x844F, 0x8450, 0x8452, 0x8453, 0x8454, 0x8455, 0x8456, 0x8458, 0x845D, 0x845E, 0x845F, 0x8460, 0x8462, 0x8464, 0x8465, 0x8466, 0x8467, 0x8468, 0x846A, 0x846E, 0x846F, 0x8470, 0x8472, 0x8474, 0x8477, 0x8479, 0x847B, + 0x847C, 0x53D6, 0x5A36, 0x9F8B, 0x8DA3, 0x53BB, 0x5708, 0x98A7, 0x6743, 0x919B, 0x6CC9, 0x5168, 0x75CA, 0x62F3, 0x72AC, 0x5238, 0x529D, 0x7F3A, 0x7094, 0x7638, 0x5374, 0x9E4A, 0x69B7, 0x786E, 0x96C0, 0x88D9, 0x7FA4, 0x7136, 0x71C3, 0x5189, 0x67D3, 0x74E4, 0x58E4, 0x6518, 0x56B7, 0x8BA9, 0x9976, 0x6270, 0x7ED5, 0x60F9, 0x70ED, 0x58EC, 0x4EC1, 0x4EBA, 0x5FCD, 0x97E7, 0x4EFB, 0x8BA4, 0x5203, 0x598A, 0x7EAB, 0x6254, 0x4ECD, 0x65E5, 0x620E, 0x8338, 0x84C9, 0x8363, 0x878D, 0x7194, 0x6EB6, 0x5BB9, 0x7ED2, 0x5197, 0x63C9, 0x67D4, 0x8089, 0x8339, 0x8815, 0x5112, 0x5B7A, 0x5982, 0x8FB1, 0x4E73, 0x6C5D, 0x5165, 0x8925, 0x8F6F, 0x962E, 0x854A, 0x745E, 0x9510, 0x95F0, 0x6DA6, 0x82E5, 0x5F31, 0x6492, 0x6D12, 0x8428, 0x816E, 0x9CC3, 0x585E, 0x8D5B, 0x4E09, 0x53C1, + 0x847D, 0x847E, 0x847F, 0x8480, 0x8481, 0x8483, 0x8484, 0x8485, 0x8486, 0x848A, 0x848D, 0x848F, 0x8490, 0x8491, 0x8492, 0x8493, 0x8494, 0x8495, 0x8496, 0x8498, 0x849A, 0x849B, 0x849D, 0x849E, 0x849F, 0x84A0, 0x84A2, 0x84A3, 0x84A4, 0x84A5, 0x84A6, 0x84A7, 0x84A8, 0x84A9, 0x84AA, 0x84AB, 0x84AC, 0x84AD, 0x84AE, 0x84B0, 0x84B1, 0x84B3, 0x84B5, 0x84B6, 0x84B7, 0x84BB, 0x84BC, 0x84BE, 0x84C0, 0x84C2, 0x84C3, 0x84C5, 0x84C6, 0x84C7, 0x84C8, 0x84CB, 0x84CC, 0x84CE, 0x84CF, 0x84D2, 0x84D4, 0x84D5, 0x84D7, 0x84D8, 0x84D9, 0x84DA, 0x84DB, 0x84DC, 0x84DE, 0x84E1, 0x84E2, 0x84E4, 0x84E7, 0x84E8, 0x84E9, 0x84EA, 0x84EB, 0x84ED, 0x84EE, 0x84EF, 0x84F1, 0x84F2, 0x84F3, 0x84F4, 0x84F5, 0x84F6, 0x84F7, 0x84F8, 0x84F9, 0x84FA, 0x84FB, 0x84FD, 0x84FE, 0x8500, 0x8501, + 0x8502, 0x4F1E, 0x6563, 0x6851, 0x55D3, 0x4E27, 0x6414, 0x9A9A, 0x626B, 0x5AC2, 0x745F, 0x8272, 0x6DA9, 0x68EE, 0x50E7, 0x838E, 0x7802, 0x6740, 0x5239, 0x6C99, 0x7EB1, 0x50BB, 0x5565, 0x715E, 0x7B5B, 0x6652, 0x73CA, 0x82EB, 0x6749, 0x5C71, 0x5220, 0x717D, 0x886B, 0x95EA, 0x9655, 0x64C5, 0x8D61, 0x81B3, 0x5584, 0x6C55, 0x6247, 0x7F2E, 0x5892, 0x4F24, 0x5546, 0x8D4F, 0x664C, 0x4E0A, 0x5C1A, 0x88F3, 0x68A2, 0x634E, 0x7A0D, 0x70E7, 0x828D, 0x52FA, 0x97F6, 0x5C11, 0x54E8, 0x90B5, 0x7ECD, 0x5962, 0x8D4A, 0x86C7, 0x820C, 0x820D, 0x8D66, 0x6444, 0x5C04, 0x6151, 0x6D89, 0x793E, 0x8BBE, 0x7837, 0x7533, 0x547B, 0x4F38, 0x8EAB, 0x6DF1, 0x5A20, 0x7EC5, 0x795E, 0x6C88, 0x5BA1, 0x5A76, 0x751A, 0x80BE, 0x614E, 0x6E17, 0x58F0, 0x751F, 0x7525, 0x7272, 0x5347, 0x7EF3, + 0x8503, 0x8504, 0x8505, 0x8506, 0x8507, 0x8508, 0x8509, 0x850A, 0x850B, 0x850D, 0x850E, 0x850F, 0x8510, 0x8512, 0x8514, 0x8515, 0x8516, 0x8518, 0x8519, 0x851B, 0x851C, 0x851D, 0x851E, 0x8520, 0x8522, 0x8523, 0x8524, 0x8525, 0x8526, 0x8527, 0x8528, 0x8529, 0x852A, 0x852D, 0x852E, 0x852F, 0x8530, 0x8531, 0x8532, 0x8533, 0x8534, 0x8535, 0x8536, 0x853E, 0x853F, 0x8540, 0x8541, 0x8542, 0x8544, 0x8545, 0x8546, 0x8547, 0x854B, 0x854C, 0x854D, 0x854E, 0x854F, 0x8550, 0x8551, 0x8552, 0x8553, 0x8554, 0x8555, 0x8557, 0x8558, 0x855A, 0x855B, 0x855C, 0x855D, 0x855F, 0x8560, 0x8561, 0x8562, 0x8563, 0x8565, 0x8566, 0x8567, 0x8569, 0x856A, 0x856B, 0x856C, 0x856D, 0x856E, 0x856F, 0x8570, 0x8571, 0x8573, 0x8575, 0x8576, 0x8577, 0x8578, 0x857C, 0x857D, 0x857F, 0x8580, + 0x8581, 0x7701, 0x76DB, 0x5269, 0x80DC, 0x5723, 0x5E08, 0x5931, 0x72EE, 0x65BD, 0x6E7F, 0x8BD7, 0x5C38, 0x8671, 0x5341, 0x77F3, 0x62FE, 0x65F6, 0x4EC0, 0x98DF, 0x8680, 0x5B9E, 0x8BC6, 0x53F2, 0x77E2, 0x4F7F, 0x5C4E, 0x9A76, 0x59CB, 0x5F0F, 0x793A, 0x58EB, 0x4E16, 0x67FF, 0x4E8B, 0x62ED, 0x8A93, 0x901D, 0x52BF, 0x662F, 0x55DC, 0x566C, 0x9002, 0x4ED5, 0x4F8D, 0x91CA, 0x9970, 0x6C0F, 0x5E02, 0x6043, 0x5BA4, 0x89C6, 0x8BD5, 0x6536, 0x624B, 0x9996, 0x5B88, 0x5BFF, 0x6388, 0x552E, 0x53D7, 0x7626, 0x517D, 0x852C, 0x67A2, 0x68B3, 0x6B8A, 0x6292, 0x8F93, 0x53D4, 0x8212, 0x6DD1, 0x758F, 0x4E66, 0x8D4E, 0x5B70, 0x719F, 0x85AF, 0x6691, 0x66D9, 0x7F72, 0x8700, 0x9ECD, 0x9F20, 0x5C5E, 0x672F, 0x8FF0, 0x6811, 0x675F, 0x620D, 0x7AD6, 0x5885, 0x5EB6, 0x6570, 0x6F31, + 0x8582, 0x8583, 0x8586, 0x8588, 0x8589, 0x858A, 0x858B, 0x858C, 0x858D, 0x858E, 0x8590, 0x8591, 0x8592, 0x8593, 0x8594, 0x8595, 0x8596, 0x8597, 0x8598, 0x8599, 0x859A, 0x859D, 0x859E, 0x859F, 0x85A0, 0x85A1, 0x85A2, 0x85A3, 0x85A5, 0x85A6, 0x85A7, 0x85A9, 0x85AB, 0x85AC, 0x85AD, 0x85B1, 0x85B2, 0x85B3, 0x85B4, 0x85B5, 0x85B6, 0x85B8, 0x85BA, 0x85BB, 0x85BC, 0x85BD, 0x85BE, 0x85BF, 0x85C0, 0x85C2, 0x85C3, 0x85C4, 0x85C5, 0x85C6, 0x85C7, 0x85C8, 0x85CA, 0x85CB, 0x85CC, 0x85CD, 0x85CE, 0x85D1, 0x85D2, 0x85D4, 0x85D6, 0x85D7, 0x85D8, 0x85D9, 0x85DA, 0x85DB, 0x85DD, 0x85DE, 0x85DF, 0x85E0, 0x85E1, 0x85E2, 0x85E3, 0x85E5, 0x85E6, 0x85E7, 0x85E8, 0x85EA, 0x85EB, 0x85EC, 0x85ED, 0x85EE, 0x85EF, 0x85F0, 0x85F1, 0x85F2, 0x85F3, 0x85F4, 0x85F5, 0x85F6, 0x85F7, + 0x85F8, 0x6055, 0x5237, 0x800D, 0x6454, 0x8870, 0x7529, 0x5E05, 0x6813, 0x62F4, 0x971C, 0x53CC, 0x723D, 0x8C01, 0x6C34, 0x7761, 0x7A0E, 0x542E, 0x77AC, 0x987A, 0x821C, 0x8BF4, 0x7855, 0x6714, 0x70C1, 0x65AF, 0x6495, 0x5636, 0x601D, 0x79C1, 0x53F8, 0x4E1D, 0x6B7B, 0x8086, 0x5BFA, 0x55E3, 0x56DB, 0x4F3A, 0x4F3C, 0x9972, 0x5DF3, 0x677E, 0x8038, 0x6002, 0x9882, 0x9001, 0x5B8B, 0x8BBC, 0x8BF5, 0x641C, 0x8258, 0x64DE, 0x55FD, 0x82CF, 0x9165, 0x4FD7, 0x7D20, 0x901F, 0x7C9F, 0x50F3, 0x5851, 0x6EAF, 0x5BBF, 0x8BC9, 0x8083, 0x9178, 0x849C, 0x7B97, 0x867D, 0x968B, 0x968F, 0x7EE5, 0x9AD3, 0x788E, 0x5C81, 0x7A57, 0x9042, 0x96A7, 0x795F, 0x5B59, 0x635F, 0x7B0B, 0x84D1, 0x68AD, 0x5506, 0x7F29, 0x7410, 0x7D22, 0x9501, 0x6240, 0x584C, 0x4ED6, 0x5B83, 0x5979, 0x5854, + 0x85F9, 0x85FA, 0x85FC, 0x85FD, 0x85FE, 0x8600, 0x8601, 0x8602, 0x8603, 0x8604, 0x8606, 0x8607, 0x8608, 0x8609, 0x860A, 0x860B, 0x860C, 0x860D, 0x860E, 0x860F, 0x8610, 0x8612, 0x8613, 0x8614, 0x8615, 0x8617, 0x8618, 0x8619, 0x861A, 0x861B, 0x861C, 0x861D, 0x861E, 0x861F, 0x8620, 0x8621, 0x8622, 0x8623, 0x8624, 0x8625, 0x8626, 0x8628, 0x862A, 0x862B, 0x862C, 0x862D, 0x862E, 0x862F, 0x8630, 0x8631, 0x8632, 0x8633, 0x8634, 0x8635, 0x8636, 0x8637, 0x8639, 0x863A, 0x863B, 0x863D, 0x863E, 0x863F, 0x8640, 0x8641, 0x8642, 0x8643, 0x8644, 0x8645, 0x8646, 0x8647, 0x8648, 0x8649, 0x864A, 0x864B, 0x864C, 0x8652, 0x8653, 0x8655, 0x8656, 0x8657, 0x8658, 0x8659, 0x865B, 0x865C, 0x865D, 0x865F, 0x8660, 0x8661, 0x8663, 0x8664, 0x8665, 0x8666, 0x8667, 0x8668, 0x8669, + 0x866A, 0x736D, 0x631E, 0x8E4B, 0x8E0F, 0x80CE, 0x82D4, 0x62AC, 0x53F0, 0x6CF0, 0x915E, 0x592A, 0x6001, 0x6C70, 0x574D, 0x644A, 0x8D2A, 0x762B, 0x6EE9, 0x575B, 0x6A80, 0x75F0, 0x6F6D, 0x8C2D, 0x8C08, 0x5766, 0x6BEF, 0x8892, 0x78B3, 0x63A2, 0x53F9, 0x70AD, 0x6C64, 0x5858, 0x642A, 0x5802, 0x68E0, 0x819B, 0x5510, 0x7CD6, 0x5018, 0x8EBA, 0x6DCC, 0x8D9F, 0x70EB, 0x638F, 0x6D9B, 0x6ED4, 0x7EE6, 0x8404, 0x6843, 0x9003, 0x6DD8, 0x9676, 0x8BA8, 0x5957, 0x7279, 0x85E4, 0x817E, 0x75BC, 0x8A8A, 0x68AF, 0x5254, 0x8E22, 0x9511, 0x63D0, 0x9898, 0x8E44, 0x557C, 0x4F53, 0x66FF, 0x568F, 0x60D5, 0x6D95, 0x5243, 0x5C49, 0x5929, 0x6DFB, 0x586B, 0x7530, 0x751C, 0x606C, 0x8214, 0x8146, 0x6311, 0x6761, 0x8FE2, 0x773A, 0x8DF3, 0x8D34, 0x94C1, 0x5E16, 0x5385, 0x542C, 0x70C3, + 0x866D, 0x866F, 0x8670, 0x8672, 0x8673, 0x8674, 0x8675, 0x8676, 0x8677, 0x8678, 0x8683, 0x8684, 0x8685, 0x8686, 0x8687, 0x8688, 0x8689, 0x868E, 0x868F, 0x8690, 0x8691, 0x8692, 0x8694, 0x8696, 0x8697, 0x8698, 0x8699, 0x869A, 0x869B, 0x869E, 0x869F, 0x86A0, 0x86A1, 0x86A2, 0x86A5, 0x86A6, 0x86AB, 0x86AD, 0x86AE, 0x86B2, 0x86B3, 0x86B7, 0x86B8, 0x86B9, 0x86BB, 0x86BC, 0x86BD, 0x86BE, 0x86BF, 0x86C1, 0x86C2, 0x86C3, 0x86C5, 0x86C8, 0x86CC, 0x86CD, 0x86D2, 0x86D3, 0x86D5, 0x86D6, 0x86D7, 0x86DA, 0x86DC, 0x86DD, 0x86E0, 0x86E1, 0x86E2, 0x86E3, 0x86E5, 0x86E6, 0x86E7, 0x86E8, 0x86EA, 0x86EB, 0x86EC, 0x86EF, 0x86F5, 0x86F6, 0x86F7, 0x86FA, 0x86FB, 0x86FC, 0x86FD, 0x86FF, 0x8701, 0x8704, 0x8705, 0x8706, 0x870B, 0x870C, 0x870E, 0x870F, 0x8710, 0x8711, 0x8714, + 0x8716, 0x6C40, 0x5EF7, 0x505C, 0x4EAD, 0x5EAD, 0x633A, 0x8247, 0x901A, 0x6850, 0x916E, 0x77B3, 0x540C, 0x94DC, 0x5F64, 0x7AE5, 0x6876, 0x6345, 0x7B52, 0x7EDF, 0x75DB, 0x5077, 0x6295, 0x5934, 0x900F, 0x51F8, 0x79C3, 0x7A81, 0x56FE, 0x5F92, 0x9014, 0x6D82, 0x5C60, 0x571F, 0x5410, 0x5154, 0x6E4D, 0x56E2, 0x63A8, 0x9893, 0x817F, 0x8715, 0x892A, 0x9000, 0x541E, 0x5C6F, 0x81C0, 0x62D6, 0x6258, 0x8131, 0x9E35, 0x9640, 0x9A6E, 0x9A7C, 0x692D, 0x59A5, 0x62D3, 0x553E, 0x6316, 0x54C7, 0x86D9, 0x6D3C, 0x5A03, 0x74E6, 0x889C, 0x6B6A, 0x5916, 0x8C4C, 0x5F2F, 0x6E7E, 0x73A9, 0x987D, 0x4E38, 0x70F7, 0x5B8C, 0x7897, 0x633D, 0x665A, 0x7696, 0x60CB, 0x5B9B, 0x5A49, 0x4E07, 0x8155, 0x6C6A, 0x738B, 0x4EA1, 0x6789, 0x7F51, 0x5F80, 0x65FA, 0x671B, 0x5FD8, 0x5984, 0x5A01, + 0x8719, 0x871B, 0x871D, 0x871F, 0x8720, 0x8724, 0x8726, 0x8727, 0x8728, 0x872A, 0x872B, 0x872C, 0x872D, 0x872F, 0x8730, 0x8732, 0x8733, 0x8735, 0x8736, 0x8738, 0x8739, 0x873A, 0x873C, 0x873D, 0x8740, 0x8741, 0x8742, 0x8743, 0x8744, 0x8745, 0x8746, 0x874A, 0x874B, 0x874D, 0x874F, 0x8750, 0x8751, 0x8752, 0x8754, 0x8755, 0x8756, 0x8758, 0x875A, 0x875B, 0x875C, 0x875D, 0x875E, 0x875F, 0x8761, 0x8762, 0x8766, 0x8767, 0x8768, 0x8769, 0x876A, 0x876B, 0x876C, 0x876D, 0x876F, 0x8771, 0x8772, 0x8773, 0x8775, 0x8777, 0x8778, 0x8779, 0x877A, 0x877F, 0x8780, 0x8781, 0x8784, 0x8786, 0x8787, 0x8789, 0x878A, 0x878C, 0x878E, 0x878F, 0x8790, 0x8791, 0x8792, 0x8794, 0x8795, 0x8796, 0x8798, 0x8799, 0x879A, 0x879B, 0x879C, 0x879D, 0x879E, 0x87A0, 0x87A1, 0x87A2, 0x87A3, + 0x87A4, 0x5DCD, 0x5FAE, 0x5371, 0x97E6, 0x8FDD, 0x6845, 0x56F4, 0x552F, 0x60DF, 0x4E3A, 0x6F4D, 0x7EF4, 0x82C7, 0x840E, 0x59D4, 0x4F1F, 0x4F2A, 0x5C3E, 0x7EAC, 0x672A, 0x851A, 0x5473, 0x754F, 0x80C3, 0x5582, 0x9B4F, 0x4F4D, 0x6E2D, 0x8C13, 0x5C09, 0x6170, 0x536B, 0x761F, 0x6E29, 0x868A, 0x6587, 0x95FB, 0x7EB9, 0x543B, 0x7A33, 0x7D0A, 0x95EE, 0x55E1, 0x7FC1, 0x74EE, 0x631D, 0x8717, 0x6DA1, 0x7A9D, 0x6211, 0x65A1, 0x5367, 0x63E1, 0x6C83, 0x5DEB, 0x545C, 0x94A8, 0x4E4C, 0x6C61, 0x8BEC, 0x5C4B, 0x65E0, 0x829C, 0x68A7, 0x543E, 0x5434, 0x6BCB, 0x6B66, 0x4E94, 0x6342, 0x5348, 0x821E, 0x4F0D, 0x4FAE, 0x575E, 0x620A, 0x96FE, 0x6664, 0x7269, 0x52FF, 0x52A1, 0x609F, 0x8BEF, 0x6614, 0x7199, 0x6790, 0x897F, 0x7852, 0x77FD, 0x6670, 0x563B, 0x5438, 0x9521, 0x727A, + 0x87A5, 0x87A6, 0x87A7, 0x87A9, 0x87AA, 0x87AE, 0x87B0, 0x87B1, 0x87B2, 0x87B4, 0x87B6, 0x87B7, 0x87B8, 0x87B9, 0x87BB, 0x87BC, 0x87BE, 0x87BF, 0x87C1, 0x87C2, 0x87C3, 0x87C4, 0x87C5, 0x87C7, 0x87C8, 0x87C9, 0x87CC, 0x87CD, 0x87CE, 0x87CF, 0x87D0, 0x87D4, 0x87D5, 0x87D6, 0x87D7, 0x87D8, 0x87D9, 0x87DA, 0x87DC, 0x87DD, 0x87DE, 0x87DF, 0x87E1, 0x87E2, 0x87E3, 0x87E4, 0x87E6, 0x87E7, 0x87E8, 0x87E9, 0x87EB, 0x87EC, 0x87ED, 0x87EF, 0x87F0, 0x87F1, 0x87F2, 0x87F3, 0x87F4, 0x87F5, 0x87F6, 0x87F7, 0x87F8, 0x87FA, 0x87FB, 0x87FC, 0x87FD, 0x87FF, 0x8800, 0x8801, 0x8802, 0x8804, 0x8805, 0x8806, 0x8807, 0x8808, 0x8809, 0x880B, 0x880C, 0x880D, 0x880E, 0x880F, 0x8810, 0x8811, 0x8812, 0x8814, 0x8817, 0x8818, 0x8819, 0x881A, 0x881C, 0x881D, 0x881E, 0x881F, 0x8820, + 0x8823, 0x7A00, 0x606F, 0x5E0C, 0x6089, 0x819D, 0x5915, 0x60DC, 0x7184, 0x70EF, 0x6EAA, 0x6C50, 0x7280, 0x6A84, 0x88AD, 0x5E2D, 0x4E60, 0x5AB3, 0x559C, 0x94E3, 0x6D17, 0x7CFB, 0x9699, 0x620F, 0x7EC6, 0x778E, 0x867E, 0x5323, 0x971E, 0x8F96, 0x6687, 0x5CE1, 0x4FA0, 0x72ED, 0x4E0B, 0x53A6, 0x590F, 0x5413, 0x6380, 0x9528, 0x5148, 0x4ED9, 0x9C9C, 0x7EA4, 0x54B8, 0x8D24, 0x8854, 0x8237, 0x95F2, 0x6D8E, 0x5F26, 0x5ACC, 0x663E, 0x9669, 0x73B0, 0x732E, 0x53BF, 0x817A, 0x9985, 0x7FA1, 0x5BAA, 0x9677, 0x9650, 0x7EBF, 0x76F8, 0x53A2, 0x9576, 0x9999, 0x7BB1, 0x8944, 0x6E58, 0x4E61, 0x7FD4, 0x7965, 0x8BE6, 0x60F3, 0x54CD, 0x4EAB, 0x9879, 0x5DF7, 0x6A61, 0x50CF, 0x5411, 0x8C61, 0x8427, 0x785D, 0x9704, 0x524A, 0x54EE, 0x56A3, 0x9500, 0x6D88, 0x5BB5, 0x6DC6, 0x6653, + 0x8824, 0x8825, 0x8826, 0x8827, 0x8828, 0x8829, 0x882A, 0x882B, 0x882C, 0x882D, 0x882E, 0x882F, 0x8830, 0x8831, 0x8833, 0x8834, 0x8835, 0x8836, 0x8837, 0x8838, 0x883A, 0x883B, 0x883D, 0x883E, 0x883F, 0x8841, 0x8842, 0x8843, 0x8846, 0x8847, 0x8848, 0x8849, 0x884A, 0x884B, 0x884E, 0x884F, 0x8850, 0x8851, 0x8852, 0x8853, 0x8855, 0x8856, 0x8858, 0x885A, 0x885B, 0x885C, 0x885D, 0x885E, 0x885F, 0x8860, 0x8866, 0x8867, 0x886A, 0x886D, 0x886F, 0x8871, 0x8873, 0x8874, 0x8875, 0x8876, 0x8878, 0x8879, 0x887A, 0x887B, 0x887C, 0x8880, 0x8883, 0x8886, 0x8887, 0x8889, 0x888A, 0x888C, 0x888E, 0x888F, 0x8890, 0x8891, 0x8893, 0x8894, 0x8895, 0x8897, 0x8898, 0x8899, 0x889A, 0x889B, 0x889D, 0x889E, 0x889F, 0x88A0, 0x88A1, 0x88A3, 0x88A5, 0x88A6, 0x88A7, 0x88A8, 0x88A9, + 0x88AA, 0x5C0F, 0x5B5D, 0x6821, 0x8096, 0x5578, 0x7B11, 0x6548, 0x6954, 0x4E9B, 0x6B47, 0x874E, 0x978B, 0x534F, 0x631F, 0x643A, 0x90AA, 0x659C, 0x80C1, 0x8C10, 0x5199, 0x68B0, 0x5378, 0x87F9, 0x61C8, 0x6CC4, 0x6CFB, 0x8C22, 0x5C51, 0x85AA, 0x82AF, 0x950C, 0x6B23, 0x8F9B, 0x65B0, 0x5FFB, 0x5FC3, 0x4FE1, 0x8845, 0x661F, 0x8165, 0x7329, 0x60FA, 0x5174, 0x5211, 0x578B, 0x5F62, 0x90A2, 0x884C, 0x9192, 0x5E78, 0x674F, 0x6027, 0x59D3, 0x5144, 0x51F6, 0x80F8, 0x5308, 0x6C79, 0x96C4, 0x718A, 0x4F11, 0x4FEE, 0x7F9E, 0x673D, 0x55C5, 0x9508, 0x79C0, 0x8896, 0x7EE3, 0x589F, 0x620C, 0x9700, 0x865A, 0x5618, 0x987B, 0x5F90, 0x8BB8, 0x84C4, 0x9157, 0x53D9, 0x65ED, 0x5E8F, 0x755C, 0x6064, 0x7D6E, 0x5A7F, 0x7EEA, 0x7EED, 0x8F69, 0x55A7, 0x5BA3, 0x60AC, 0x65CB, 0x7384, + 0x88AC, 0x88AE, 0x88AF, 0x88B0, 0x88B2, 0x88B3, 0x88B4, 0x88B5, 0x88B6, 0x88B8, 0x88B9, 0x88BA, 0x88BB, 0x88BD, 0x88BE, 0x88BF, 0x88C0, 0x88C3, 0x88C4, 0x88C7, 0x88C8, 0x88CA, 0x88CB, 0x88CC, 0x88CD, 0x88CF, 0x88D0, 0x88D1, 0x88D3, 0x88D6, 0x88D7, 0x88DA, 0x88DB, 0x88DC, 0x88DD, 0x88DE, 0x88E0, 0x88E1, 0x88E6, 0x88E7, 0x88E9, 0x88EA, 0x88EB, 0x88EC, 0x88ED, 0x88EE, 0x88EF, 0x88F2, 0x88F5, 0x88F6, 0x88F7, 0x88FA, 0x88FB, 0x88FD, 0x88FF, 0x8900, 0x8901, 0x8903, 0x8904, 0x8905, 0x8906, 0x8907, 0x8908, 0x8909, 0x890B, 0x890C, 0x890D, 0x890E, 0x890F, 0x8911, 0x8914, 0x8915, 0x8916, 0x8917, 0x8918, 0x891C, 0x891D, 0x891E, 0x891F, 0x8920, 0x8922, 0x8923, 0x8924, 0x8926, 0x8927, 0x8928, 0x8929, 0x892C, 0x892D, 0x892E, 0x892F, 0x8931, 0x8932, 0x8933, 0x8935, + 0x8937, 0x9009, 0x7663, 0x7729, 0x7EDA, 0x9774, 0x859B, 0x5B66, 0x7A74, 0x96EA, 0x8840, 0x52CB, 0x718F, 0x5FAA, 0x65EC, 0x8BE2, 0x5BFB, 0x9A6F, 0x5DE1, 0x6B89, 0x6C5B, 0x8BAD, 0x8BAF, 0x900A, 0x8FC5, 0x538B, 0x62BC, 0x9E26, 0x9E2D, 0x5440, 0x4E2B, 0x82BD, 0x7259, 0x869C, 0x5D16, 0x8859, 0x6DAF, 0x96C5, 0x54D1, 0x4E9A, 0x8BB6, 0x7109, 0x54BD, 0x9609, 0x70DF, 0x6DF9, 0x76D0, 0x4E25, 0x7814, 0x8712, 0x5CA9, 0x5EF6, 0x8A00, 0x989C, 0x960E, 0x708E, 0x6CBF, 0x5944, 0x63A9, 0x773C, 0x884D, 0x6F14, 0x8273, 0x5830, 0x71D5, 0x538C, 0x781A, 0x96C1, 0x5501, 0x5F66, 0x7130, 0x5BB4, 0x8C1A, 0x9A8C, 0x6B83, 0x592E, 0x9E2F, 0x79E7, 0x6768, 0x626C, 0x4F6F, 0x75A1, 0x7F8A, 0x6D0B, 0x9633, 0x6C27, 0x4EF0, 0x75D2, 0x517B, 0x6837, 0x6F3E, 0x9080, 0x8170, 0x5996, 0x7476, + 0x8938, 0x8939, 0x893A, 0x893B, 0x893C, 0x893D, 0x893E, 0x893F, 0x8940, 0x8942, 0x8943, 0x8945, 0x8946, 0x8947, 0x8948, 0x8949, 0x894A, 0x894B, 0x894C, 0x894D, 0x894E, 0x894F, 0x8950, 0x8951, 0x8952, 0x8953, 0x8954, 0x8955, 0x8956, 0x8957, 0x8958, 0x8959, 0x895A, 0x895B, 0x895C, 0x895D, 0x8960, 0x8961, 0x8962, 0x8963, 0x8964, 0x8965, 0x8967, 0x8968, 0x8969, 0x896A, 0x896B, 0x896C, 0x896D, 0x896E, 0x896F, 0x8970, 0x8971, 0x8972, 0x8973, 0x8974, 0x8975, 0x8976, 0x8977, 0x8978, 0x8979, 0x897A, 0x897C, 0x897D, 0x897E, 0x8980, 0x8982, 0x8984, 0x8985, 0x8987, 0x8988, 0x8989, 0x898A, 0x898B, 0x898C, 0x898D, 0x898E, 0x898F, 0x8990, 0x8991, 0x8992, 0x8993, 0x8994, 0x8995, 0x8996, 0x8997, 0x8998, 0x8999, 0x899A, 0x899B, 0x899C, 0x899D, 0x899E, 0x899F, 0x89A0, + 0x89A1, 0x6447, 0x5C27, 0x9065, 0x7A91, 0x8C23, 0x59DA, 0x54AC, 0x8200, 0x836F, 0x8981, 0x8000, 0x6930, 0x564E, 0x8036, 0x7237, 0x91CE, 0x51B6, 0x4E5F, 0x9875, 0x6396, 0x4E1A, 0x53F6, 0x66F3, 0x814B, 0x591C, 0x6DB2, 0x4E00, 0x58F9, 0x533B, 0x63D6, 0x94F1, 0x4F9D, 0x4F0A, 0x8863, 0x9890, 0x5937, 0x9057, 0x79FB, 0x4EEA, 0x80F0, 0x7591, 0x6C82, 0x5B9C, 0x59E8, 0x5F5D, 0x6905, 0x8681, 0x501A, 0x5DF2, 0x4E59, 0x77E3, 0x4EE5, 0x827A, 0x6291, 0x6613, 0x9091, 0x5C79, 0x4EBF, 0x5F79, 0x81C6, 0x9038, 0x8084, 0x75AB, 0x4EA6, 0x88D4, 0x610F, 0x6BC5, 0x5FC6, 0x4E49, 0x76CA, 0x6EA2, 0x8BE3, 0x8BAE, 0x8C0A, 0x8BD1, 0x5F02, 0x7FFC, 0x7FCC, 0x7ECE, 0x8335, 0x836B, 0x56E0, 0x6BB7, 0x97F3, 0x9634, 0x59FB, 0x541F, 0x94F6, 0x6DEB, 0x5BC5, 0x996E, 0x5C39, 0x5F15, 0x9690, + 0x89A2, 0x89A3, 0x89A4, 0x89A5, 0x89A6, 0x89A7, 0x89A8, 0x89A9, 0x89AA, 0x89AB, 0x89AC, 0x89AD, 0x89AE, 0x89AF, 0x89B0, 0x89B1, 0x89B2, 0x89B3, 0x89B4, 0x89B5, 0x89B6, 0x89B7, 0x89B8, 0x89B9, 0x89BA, 0x89BB, 0x89BC, 0x89BD, 0x89BE, 0x89BF, 0x89C0, 0x89C3, 0x89CD, 0x89D3, 0x89D4, 0x89D5, 0x89D7, 0x89D8, 0x89D9, 0x89DB, 0x89DD, 0x89DF, 0x89E0, 0x89E1, 0x89E2, 0x89E4, 0x89E7, 0x89E8, 0x89E9, 0x89EA, 0x89EC, 0x89ED, 0x89EE, 0x89F0, 0x89F1, 0x89F2, 0x89F4, 0x89F5, 0x89F6, 0x89F7, 0x89F8, 0x89F9, 0x89FA, 0x89FB, 0x89FC, 0x89FD, 0x89FE, 0x89FF, 0x8A01, 0x8A02, 0x8A03, 0x8A04, 0x8A05, 0x8A06, 0x8A08, 0x8A09, 0x8A0A, 0x8A0B, 0x8A0C, 0x8A0D, 0x8A0E, 0x8A0F, 0x8A10, 0x8A11, 0x8A12, 0x8A13, 0x8A14, 0x8A15, 0x8A16, 0x8A17, 0x8A18, 0x8A19, 0x8A1A, 0x8A1B, 0x8A1C, + 0x8A1D, 0x5370, 0x82F1, 0x6A31, 0x5A74, 0x9E70, 0x5E94, 0x7F28, 0x83B9, 0x8424, 0x8425, 0x8367, 0x8747, 0x8FCE, 0x8D62, 0x76C8, 0x5F71, 0x9896, 0x786C, 0x6620, 0x54DF, 0x62E5, 0x4F63, 0x81C3, 0x75C8, 0x5EB8, 0x96CD, 0x8E0A, 0x86F9, 0x548F, 0x6CF3, 0x6D8C, 0x6C38, 0x607F, 0x52C7, 0x7528, 0x5E7D, 0x4F18, 0x60A0, 0x5FE7, 0x5C24, 0x7531, 0x90AE, 0x94C0, 0x72B9, 0x6CB9, 0x6E38, 0x9149, 0x6709, 0x53CB, 0x53F3, 0x4F51, 0x91C9, 0x8BF1, 0x53C8, 0x5E7C, 0x8FC2, 0x6DE4, 0x4E8E, 0x76C2, 0x6986, 0x865E, 0x611A, 0x8206, 0x4F59, 0x4FDE, 0x903E, 0x9C7C, 0x6109, 0x6E1D, 0x6E14, 0x9685, 0x4E88, 0x5A31, 0x96E8, 0x4E0E, 0x5C7F, 0x79B9, 0x5B87, 0x8BED, 0x7FBD, 0x7389, 0x57DF, 0x828B, 0x90C1, 0x5401, 0x9047, 0x55BB, 0x5CEA, 0x5FA1, 0x6108, 0x6B32, 0x72F1, 0x80B2, 0x8A89, + 0x8A1E, 0x8A1F, 0x8A20, 0x8A21, 0x8A22, 0x8A23, 0x8A24, 0x8A25, 0x8A26, 0x8A27, 0x8A28, 0x8A29, 0x8A2A, 0x8A2B, 0x8A2C, 0x8A2D, 0x8A2E, 0x8A2F, 0x8A30, 0x8A31, 0x8A32, 0x8A33, 0x8A34, 0x8A35, 0x8A36, 0x8A37, 0x8A38, 0x8A39, 0x8A3A, 0x8A3B, 0x8A3C, 0x8A3D, 0x8A3F, 0x8A40, 0x8A41, 0x8A42, 0x8A43, 0x8A44, 0x8A45, 0x8A46, 0x8A47, 0x8A49, 0x8A4A, 0x8A4B, 0x8A4C, 0x8A4D, 0x8A4E, 0x8A4F, 0x8A50, 0x8A51, 0x8A52, 0x8A53, 0x8A54, 0x8A55, 0x8A56, 0x8A57, 0x8A58, 0x8A59, 0x8A5A, 0x8A5B, 0x8A5C, 0x8A5D, 0x8A5E, 0x8A5F, 0x8A60, 0x8A61, 0x8A62, 0x8A63, 0x8A64, 0x8A65, 0x8A66, 0x8A67, 0x8A68, 0x8A69, 0x8A6A, 0x8A6B, 0x8A6C, 0x8A6D, 0x8A6E, 0x8A6F, 0x8A70, 0x8A71, 0x8A72, 0x8A73, 0x8A74, 0x8A75, 0x8A76, 0x8A77, 0x8A78, 0x8A7A, 0x8A7B, 0x8A7C, 0x8A7D, 0x8A7E, 0x8A7F, + 0x8A80, 0x6D74, 0x5BD3, 0x88D5, 0x9884, 0x8C6B, 0x9A6D, 0x9E33, 0x6E0A, 0x51A4, 0x5143, 0x57A3, 0x8881, 0x539F, 0x63F4, 0x8F95, 0x56ED, 0x5458, 0x5706, 0x733F, 0x6E90, 0x7F18, 0x8FDC, 0x82D1, 0x613F, 0x6028, 0x9662, 0x66F0, 0x7EA6, 0x8D8A, 0x8DC3, 0x94A5, 0x5CB3, 0x7CA4, 0x6708, 0x60A6, 0x9605, 0x8018, 0x4E91, 0x90E7, 0x5300, 0x9668, 0x5141, 0x8FD0, 0x8574, 0x915D, 0x6655, 0x97F5, 0x5B55, 0x531D, 0x7838, 0x6742, 0x683D, 0x54C9, 0x707E, 0x5BB0, 0x8F7D, 0x518D, 0x5728, 0x54B1, 0x6512, 0x6682, 0x8D5E, 0x8D43, 0x810F, 0x846C, 0x906D, 0x7CDF, 0x51FF, 0x85FB, 0x67A3, 0x65E9, 0x6FA1, 0x86A4, 0x8E81, 0x566A, 0x9020, 0x7682, 0x7076, 0x71E5, 0x8D23, 0x62E9, 0x5219, 0x6CFD, 0x8D3C, 0x600E, 0x589E, 0x618E, 0x66FE, 0x8D60, 0x624E, 0x55B3, 0x6E23, 0x672D, 0x8F67, + 0x8A81, 0x8A82, 0x8A83, 0x8A84, 0x8A85, 0x8A86, 0x8A87, 0x8A88, 0x8A8B, 0x8A8C, 0x8A8D, 0x8A8E, 0x8A8F, 0x8A90, 0x8A91, 0x8A92, 0x8A94, 0x8A95, 0x8A96, 0x8A97, 0x8A98, 0x8A99, 0x8A9A, 0x8A9B, 0x8A9C, 0x8A9D, 0x8A9E, 0x8A9F, 0x8AA0, 0x8AA1, 0x8AA2, 0x8AA3, 0x8AA4, 0x8AA5, 0x8AA6, 0x8AA7, 0x8AA8, 0x8AA9, 0x8AAA, 0x8AAB, 0x8AAC, 0x8AAD, 0x8AAE, 0x8AAF, 0x8AB0, 0x8AB1, 0x8AB2, 0x8AB3, 0x8AB4, 0x8AB5, 0x8AB6, 0x8AB7, 0x8AB8, 0x8AB9, 0x8ABA, 0x8ABB, 0x8ABC, 0x8ABD, 0x8ABE, 0x8ABF, 0x8AC0, 0x8AC1, 0x8AC2, 0x8AC3, 0x8AC4, 0x8AC5, 0x8AC6, 0x8AC7, 0x8AC8, 0x8AC9, 0x8ACA, 0x8ACB, 0x8ACC, 0x8ACD, 0x8ACE, 0x8ACF, 0x8AD0, 0x8AD1, 0x8AD2, 0x8AD3, 0x8AD4, 0x8AD5, 0x8AD6, 0x8AD7, 0x8AD8, 0x8AD9, 0x8ADA, 0x8ADB, 0x8ADC, 0x8ADD, 0x8ADE, 0x8ADF, 0x8AE0, 0x8AE1, 0x8AE2, + 0x8AE3, 0x94E1, 0x95F8, 0x7728, 0x6805, 0x69A8, 0x548B, 0x4E4D, 0x70B8, 0x8BC8, 0x6458, 0x658B, 0x5B85, 0x7A84, 0x503A, 0x5BE8, 0x77BB, 0x6BE1, 0x8A79, 0x7C98, 0x6CBE, 0x76CF, 0x65A9, 0x8F97, 0x5D2D, 0x5C55, 0x8638, 0x6808, 0x5360, 0x6218, 0x7AD9, 0x6E5B, 0x7EFD, 0x6A1F, 0x7AE0, 0x5F70, 0x6F33, 0x5F20, 0x638C, 0x6DA8, 0x6756, 0x4E08, 0x5E10, 0x8D26, 0x4ED7, 0x80C0, 0x7634, 0x969C, 0x62DB, 0x662D, 0x627E, 0x6CBC, 0x8D75, 0x7167, 0x7F69, 0x5146, 0x8087, 0x53EC, 0x906E, 0x6298, 0x54F2, 0x86F0, 0x8F99, 0x8005, 0x9517, 0x8517, 0x8FD9, 0x6D59, 0x73CD, 0x659F, 0x771F, 0x7504, 0x7827, 0x81FB, 0x8D1E, 0x9488, 0x4FA6, 0x6795, 0x75B9, 0x8BCA, 0x9707, 0x632F, 0x9547, 0x9635, 0x84B8, 0x6323, 0x7741, 0x5F81, 0x72F0, 0x4E89, 0x6014, 0x6574, 0x62EF, 0x6B63, 0x653F, + 0x8AE4, 0x8AE5, 0x8AE6, 0x8AE7, 0x8AE8, 0x8AE9, 0x8AEA, 0x8AEB, 0x8AEC, 0x8AED, 0x8AEE, 0x8AEF, 0x8AF0, 0x8AF1, 0x8AF2, 0x8AF3, 0x8AF4, 0x8AF5, 0x8AF6, 0x8AF7, 0x8AF8, 0x8AF9, 0x8AFA, 0x8AFB, 0x8AFC, 0x8AFD, 0x8AFE, 0x8AFF, 0x8B00, 0x8B01, 0x8B02, 0x8B03, 0x8B04, 0x8B05, 0x8B06, 0x8B08, 0x8B09, 0x8B0A, 0x8B0B, 0x8B0C, 0x8B0D, 0x8B0E, 0x8B0F, 0x8B10, 0x8B11, 0x8B12, 0x8B13, 0x8B14, 0x8B15, 0x8B16, 0x8B17, 0x8B18, 0x8B19, 0x8B1A, 0x8B1B, 0x8B1C, 0x8B1D, 0x8B1E, 0x8B1F, 0x8B20, 0x8B21, 0x8B22, 0x8B23, 0x8B24, 0x8B25, 0x8B27, 0x8B28, 0x8B29, 0x8B2A, 0x8B2B, 0x8B2C, 0x8B2D, 0x8B2E, 0x8B2F, 0x8B30, 0x8B31, 0x8B32, 0x8B33, 0x8B34, 0x8B35, 0x8B36, 0x8B37, 0x8B38, 0x8B39, 0x8B3A, 0x8B3B, 0x8B3C, 0x8B3D, 0x8B3E, 0x8B3F, 0x8B40, 0x8B41, 0x8B42, 0x8B43, 0x8B44, + 0x8B45, 0x5E27, 0x75C7, 0x90D1, 0x8BC1, 0x829D, 0x679D, 0x652F, 0x5431, 0x8718, 0x77E5, 0x80A2, 0x8102, 0x6C41, 0x4E4B, 0x7EC7, 0x804C, 0x76F4, 0x690D, 0x6B96, 0x6267, 0x503C, 0x4F84, 0x5740, 0x6307, 0x6B62, 0x8DBE, 0x53EA, 0x65E8, 0x7EB8, 0x5FD7, 0x631A, 0x63B7, 0x81F3, 0x81F4, 0x7F6E, 0x5E1C, 0x5CD9, 0x5236, 0x667A, 0x79E9, 0x7A1A, 0x8D28, 0x7099, 0x75D4, 0x6EDE, 0x6CBB, 0x7A92, 0x4E2D, 0x76C5, 0x5FE0, 0x949F, 0x8877, 0x7EC8, 0x79CD, 0x80BF, 0x91CD, 0x4EF2, 0x4F17, 0x821F, 0x5468, 0x5DDE, 0x6D32, 0x8BCC, 0x7CA5, 0x8F74, 0x8098, 0x5E1A, 0x5492, 0x76B1, 0x5B99, 0x663C, 0x9AA4, 0x73E0, 0x682A, 0x86DB, 0x6731, 0x732A, 0x8BF8, 0x8BDB, 0x9010, 0x7AF9, 0x70DB, 0x716E, 0x62C4, 0x77A9, 0x5631, 0x4E3B, 0x8457, 0x67F1, 0x52A9, 0x86C0, 0x8D2E, 0x94F8, 0x7B51, + 0x8B46, 0x8B47, 0x8B48, 0x8B49, 0x8B4A, 0x8B4B, 0x8B4C, 0x8B4D, 0x8B4E, 0x8B4F, 0x8B50, 0x8B51, 0x8B52, 0x8B53, 0x8B54, 0x8B55, 0x8B56, 0x8B57, 0x8B58, 0x8B59, 0x8B5A, 0x8B5B, 0x8B5C, 0x8B5D, 0x8B5E, 0x8B5F, 0x8B60, 0x8B61, 0x8B62, 0x8B63, 0x8B64, 0x8B65, 0x8B67, 0x8B68, 0x8B69, 0x8B6A, 0x8B6B, 0x8B6D, 0x8B6E, 0x8B6F, 0x8B70, 0x8B71, 0x8B72, 0x8B73, 0x8B74, 0x8B75, 0x8B76, 0x8B77, 0x8B78, 0x8B79, 0x8B7A, 0x8B7B, 0x8B7C, 0x8B7D, 0x8B7E, 0x8B7F, 0x8B80, 0x8B81, 0x8B82, 0x8B83, 0x8B84, 0x8B85, 0x8B86, 0x8B87, 0x8B88, 0x8B89, 0x8B8A, 0x8B8B, 0x8B8C, 0x8B8D, 0x8B8E, 0x8B8F, 0x8B90, 0x8B91, 0x8B92, 0x8B93, 0x8B94, 0x8B95, 0x8B96, 0x8B97, 0x8B98, 0x8B99, 0x8B9A, 0x8B9B, 0x8B9C, 0x8B9D, 0x8B9E, 0x8B9F, 0x8BAC, 0x8BB1, 0x8BBB, 0x8BC7, 0x8BD0, 0x8BEA, 0x8C09, + 0x8C1E, 0x4F4F, 0x6CE8, 0x795D, 0x9A7B, 0x6293, 0x722A, 0x62FD, 0x4E13, 0x7816, 0x8F6C, 0x64B0, 0x8D5A, 0x7BC6, 0x6869, 0x5E84, 0x88C5, 0x5986, 0x649E, 0x58EE, 0x72B6, 0x690E, 0x9525, 0x8FFD, 0x8D58, 0x5760, 0x7F00, 0x8C06, 0x51C6, 0x6349, 0x62D9, 0x5353, 0x684C, 0x7422, 0x8301, 0x914C, 0x5544, 0x7740, 0x707C, 0x6D4A, 0x5179, 0x54A8, 0x8D44, 0x59FF, 0x6ECB, 0x6DC4, 0x5B5C, 0x7D2B, 0x4ED4, 0x7C7D, 0x6ED3, 0x5B50, 0x81EA, 0x6E0D, 0x5B57, 0x9B03, 0x68D5, 0x8E2A, 0x5B97, 0x7EFC, 0x603B, 0x7EB5, 0x90B9, 0x8D70, 0x594F, 0x63CD, 0x79DF, 0x8DB3, 0x5352, 0x65CF, 0x7956, 0x8BC5, 0x963B, 0x7EC4, 0x94BB, 0x7E82, 0x5634, 0x9189, 0x6700, 0x7F6A, 0x5C0A, 0x9075, 0x6628, 0x5DE6, 0x4F50, 0x67DE, 0x505A, 0x4F5C, 0x5750, 0x5EA7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x8C38, 0x8C39, 0x8C3A, 0x8C3B, 0x8C3C, 0x8C3D, 0x8C3E, 0x8C3F, 0x8C40, 0x8C42, 0x8C43, 0x8C44, 0x8C45, 0x8C48, 0x8C4A, 0x8C4B, 0x8C4D, 0x8C4E, 0x8C4F, 0x8C50, 0x8C51, 0x8C52, 0x8C53, 0x8C54, 0x8C56, 0x8C57, 0x8C58, 0x8C59, 0x8C5B, 0x8C5C, 0x8C5D, 0x8C5E, 0x8C5F, 0x8C60, 0x8C63, 0x8C64, 0x8C65, 0x8C66, 0x8C67, 0x8C68, 0x8C69, 0x8C6C, 0x8C6D, 0x8C6E, 0x8C6F, 0x8C70, 0x8C71, 0x8C72, 0x8C74, 0x8C75, 0x8C76, 0x8C77, 0x8C7B, 0x8C7C, 0x8C7D, 0x8C7E, 0x8C7F, 0x8C80, 0x8C81, 0x8C83, 0x8C84, 0x8C86, 0x8C87, 0x8C88, 0x8C8B, 0x8C8D, 0x8C8E, 0x8C8F, 0x8C90, 0x8C91, 0x8C92, 0x8C93, 0x8C95, 0x8C96, 0x8C97, 0x8C99, 0x8C9A, 0x8C9B, 0x8C9C, 0x8C9D, 0x8C9E, 0x8C9F, 0x8CA0, 0x8CA1, 0x8CA2, 0x8CA3, 0x8CA4, 0x8CA5, 0x8CA6, 0x8CA7, 0x8CA8, 0x8CA9, 0x8CAA, 0x8CAB, 0x8CAC, + 0x8CAD, 0x4E8D, 0x4E0C, 0x5140, 0x4E10, 0x5EFF, 0x5345, 0x4E15, 0x4E98, 0x4E1E, 0x9B32, 0x5B6C, 0x5669, 0x4E28, 0x79BA, 0x4E3F, 0x5315, 0x4E47, 0x592D, 0x723B, 0x536E, 0x6C10, 0x56DF, 0x80E4, 0x9997, 0x6BD3, 0x777E, 0x9F17, 0x4E36, 0x4E9F, 0x9F10, 0x4E5C, 0x4E69, 0x4E93, 0x8288, 0x5B5B, 0x556C, 0x560F, 0x4EC4, 0x538D, 0x539D, 0x53A3, 0x53A5, 0x53AE, 0x9765, 0x8D5D, 0x531A, 0x53F5, 0x5326, 0x532E, 0x533E, 0x8D5C, 0x5366, 0x5363, 0x5202, 0x5208, 0x520E, 0x522D, 0x5233, 0x523F, 0x5240, 0x524C, 0x525E, 0x5261, 0x525C, 0x84AF, 0x527D, 0x5282, 0x5281, 0x5290, 0x5293, 0x5182, 0x7F54, 0x4EBB, 0x4EC3, 0x4EC9, 0x4EC2, 0x4EE8, 0x4EE1, 0x4EEB, 0x4EDE, 0x4F1B, 0x4EF3, 0x4F22, 0x4F64, 0x4EF5, 0x4F25, 0x4F27, 0x4F09, 0x4F2B, 0x4F5E, 0x4F67, 0x6538, 0x4F5A, 0x4F5D, + 0x8CAE, 0x8CAF, 0x8CB0, 0x8CB1, 0x8CB2, 0x8CB3, 0x8CB4, 0x8CB5, 0x8CB6, 0x8CB7, 0x8CB8, 0x8CB9, 0x8CBA, 0x8CBB, 0x8CBC, 0x8CBD, 0x8CBE, 0x8CBF, 0x8CC0, 0x8CC1, 0x8CC2, 0x8CC3, 0x8CC4, 0x8CC5, 0x8CC6, 0x8CC7, 0x8CC8, 0x8CC9, 0x8CCA, 0x8CCB, 0x8CCC, 0x8CCD, 0x8CCE, 0x8CCF, 0x8CD0, 0x8CD1, 0x8CD2, 0x8CD3, 0x8CD4, 0x8CD5, 0x8CD6, 0x8CD7, 0x8CD8, 0x8CD9, 0x8CDA, 0x8CDB, 0x8CDC, 0x8CDD, 0x8CDE, 0x8CDF, 0x8CE0, 0x8CE1, 0x8CE2, 0x8CE3, 0x8CE4, 0x8CE5, 0x8CE6, 0x8CE7, 0x8CE8, 0x8CE9, 0x8CEA, 0x8CEB, 0x8CEC, 0x8CED, 0x8CEE, 0x8CEF, 0x8CF0, 0x8CF1, 0x8CF2, 0x8CF3, 0x8CF4, 0x8CF5, 0x8CF6, 0x8CF7, 0x8CF8, 0x8CF9, 0x8CFA, 0x8CFB, 0x8CFC, 0x8CFD, 0x8CFE, 0x8CFF, 0x8D00, 0x8D01, 0x8D02, 0x8D03, 0x8D04, 0x8D05, 0x8D06, 0x8D07, 0x8D08, 0x8D09, 0x8D0A, 0x8D0B, 0x8D0C, + 0x8D0D, 0x4F5F, 0x4F57, 0x4F32, 0x4F3D, 0x4F76, 0x4F74, 0x4F91, 0x4F89, 0x4F83, 0x4F8F, 0x4F7E, 0x4F7B, 0x4FAA, 0x4F7C, 0x4FAC, 0x4F94, 0x4FE6, 0x4FE8, 0x4FEA, 0x4FC5, 0x4FDA, 0x4FE3, 0x4FDC, 0x4FD1, 0x4FDF, 0x4FF8, 0x5029, 0x504C, 0x4FF3, 0x502C, 0x500F, 0x502E, 0x502D, 0x4FFE, 0x501C, 0x500C, 0x5025, 0x5028, 0x507E, 0x5043, 0x5055, 0x5048, 0x504E, 0x506C, 0x507B, 0x50A5, 0x50A7, 0x50A9, 0x50BA, 0x50D6, 0x5106, 0x50ED, 0x50EC, 0x50E6, 0x50EE, 0x5107, 0x510B, 0x4EDD, 0x6C3D, 0x4F58, 0x4F65, 0x4FCE, 0x9FA0, 0x6C46, 0x7C74, 0x516E, 0x5DFD, 0x9EC9, 0x9998, 0x5181, 0x5914, 0x52F9, 0x530D, 0x8A07, 0x5310, 0x51EB, 0x5919, 0x5155, 0x4EA0, 0x5156, 0x4EB3, 0x886E, 0x88A4, 0x4EB5, 0x8114, 0x88D2, 0x7980, 0x5B34, 0x8803, 0x7FB8, 0x51AB, 0x51B1, 0x51BD, 0x51BC, + 0x8D0E, 0x8D0F, 0x8D10, 0x8D11, 0x8D12, 0x8D13, 0x8D14, 0x8D15, 0x8D16, 0x8D17, 0x8D18, 0x8D19, 0x8D1A, 0x8D1B, 0x8D1C, 0x8D20, 0x8D51, 0x8D52, 0x8D57, 0x8D5F, 0x8D65, 0x8D68, 0x8D69, 0x8D6A, 0x8D6C, 0x8D6E, 0x8D6F, 0x8D71, 0x8D72, 0x8D78, 0x8D79, 0x8D7A, 0x8D7B, 0x8D7C, 0x8D7D, 0x8D7E, 0x8D7F, 0x8D80, 0x8D82, 0x8D83, 0x8D86, 0x8D87, 0x8D88, 0x8D89, 0x8D8C, 0x8D8D, 0x8D8E, 0x8D8F, 0x8D90, 0x8D92, 0x8D93, 0x8D95, 0x8D96, 0x8D97, 0x8D98, 0x8D99, 0x8D9A, 0x8D9B, 0x8D9C, 0x8D9D, 0x8D9E, 0x8DA0, 0x8DA1, 0x8DA2, 0x8DA4, 0x8DA5, 0x8DA6, 0x8DA7, 0x8DA8, 0x8DA9, 0x8DAA, 0x8DAB, 0x8DAC, 0x8DAD, 0x8DAE, 0x8DAF, 0x8DB0, 0x8DB2, 0x8DB6, 0x8DB7, 0x8DB9, 0x8DBB, 0x8DBD, 0x8DC0, 0x8DC1, 0x8DC2, 0x8DC5, 0x8DC7, 0x8DC8, 0x8DC9, 0x8DCA, 0x8DCD, 0x8DD0, 0x8DD2, 0x8DD3, + 0x8DD4, 0x51C7, 0x5196, 0x51A2, 0x51A5, 0x8BA0, 0x8BA6, 0x8BA7, 0x8BAA, 0x8BB4, 0x8BB5, 0x8BB7, 0x8BC2, 0x8BC3, 0x8BCB, 0x8BCF, 0x8BCE, 0x8BD2, 0x8BD3, 0x8BD4, 0x8BD6, 0x8BD8, 0x8BD9, 0x8BDC, 0x8BDF, 0x8BE0, 0x8BE4, 0x8BE8, 0x8BE9, 0x8BEE, 0x8BF0, 0x8BF3, 0x8BF6, 0x8BF9, 0x8BFC, 0x8BFF, 0x8C00, 0x8C02, 0x8C04, 0x8C07, 0x8C0C, 0x8C0F, 0x8C11, 0x8C12, 0x8C14, 0x8C15, 0x8C16, 0x8C19, 0x8C1B, 0x8C18, 0x8C1D, 0x8C1F, 0x8C20, 0x8C21, 0x8C25, 0x8C27, 0x8C2A, 0x8C2B, 0x8C2E, 0x8C2F, 0x8C32, 0x8C33, 0x8C35, 0x8C36, 0x5369, 0x537A, 0x961D, 0x9622, 0x9621, 0x9631, 0x962A, 0x963D, 0x963C, 0x9642, 0x9649, 0x9654, 0x965F, 0x9667, 0x966C, 0x9672, 0x9674, 0x9688, 0x968D, 0x9697, 0x96B0, 0x9097, 0x909B, 0x909D, 0x9099, 0x90AC, 0x90A1, 0x90B4, 0x90B3, 0x90B6, 0x90BA, + 0x8DD5, 0x8DD8, 0x8DD9, 0x8DDC, 0x8DE0, 0x8DE1, 0x8DE2, 0x8DE5, 0x8DE6, 0x8DE7, 0x8DE9, 0x8DED, 0x8DEE, 0x8DF0, 0x8DF1, 0x8DF2, 0x8DF4, 0x8DF6, 0x8DFC, 0x8DFE, 0x8DFF, 0x8E00, 0x8E01, 0x8E02, 0x8E03, 0x8E04, 0x8E06, 0x8E07, 0x8E08, 0x8E0B, 0x8E0D, 0x8E0E, 0x8E10, 0x8E11, 0x8E12, 0x8E13, 0x8E15, 0x8E16, 0x8E17, 0x8E18, 0x8E19, 0x8E1A, 0x8E1B, 0x8E1C, 0x8E20, 0x8E21, 0x8E24, 0x8E25, 0x8E26, 0x8E27, 0x8E28, 0x8E2B, 0x8E2D, 0x8E30, 0x8E32, 0x8E33, 0x8E34, 0x8E36, 0x8E37, 0x8E38, 0x8E3B, 0x8E3C, 0x8E3E, 0x8E3F, 0x8E43, 0x8E45, 0x8E46, 0x8E4C, 0x8E4D, 0x8E4E, 0x8E4F, 0x8E50, 0x8E53, 0x8E54, 0x8E55, 0x8E56, 0x8E57, 0x8E58, 0x8E5A, 0x8E5B, 0x8E5C, 0x8E5D, 0x8E5E, 0x8E5F, 0x8E60, 0x8E61, 0x8E62, 0x8E63, 0x8E64, 0x8E65, 0x8E67, 0x8E68, 0x8E6A, 0x8E6B, 0x8E6E, + 0x8E71, 0x90B8, 0x90B0, 0x90CF, 0x90C5, 0x90BE, 0x90D0, 0x90C4, 0x90C7, 0x90D3, 0x90E6, 0x90E2, 0x90DC, 0x90D7, 0x90DB, 0x90EB, 0x90EF, 0x90FE, 0x9104, 0x9122, 0x911E, 0x9123, 0x9131, 0x912F, 0x9139, 0x9143, 0x9146, 0x520D, 0x5942, 0x52A2, 0x52AC, 0x52AD, 0x52BE, 0x54FF, 0x52D0, 0x52D6, 0x52F0, 0x53DF, 0x71EE, 0x77CD, 0x5EF4, 0x51F5, 0x51FC, 0x9B2F, 0x53B6, 0x5F01, 0x755A, 0x5DEF, 0x574C, 0x57A9, 0x57A1, 0x587E, 0x58BC, 0x58C5, 0x58D1, 0x5729, 0x572C, 0x572A, 0x5733, 0x5739, 0x572E, 0x572F, 0x575C, 0x573B, 0x5742, 0x5769, 0x5785, 0x576B, 0x5786, 0x577C, 0x577B, 0x5768, 0x576D, 0x5776, 0x5773, 0x57AD, 0x57A4, 0x578C, 0x57B2, 0x57CF, 0x57A7, 0x57B4, 0x5793, 0x57A0, 0x57D5, 0x57D8, 0x57DA, 0x57D9, 0x57D2, 0x57B8, 0x57F4, 0x57EF, 0x57F8, 0x57E4, 0x57DD, + 0x8E73, 0x8E75, 0x8E77, 0x8E78, 0x8E79, 0x8E7A, 0x8E7B, 0x8E7D, 0x8E7E, 0x8E80, 0x8E82, 0x8E83, 0x8E84, 0x8E86, 0x8E88, 0x8E89, 0x8E8A, 0x8E8B, 0x8E8C, 0x8E8D, 0x8E8E, 0x8E91, 0x8E92, 0x8E93, 0x8E95, 0x8E96, 0x8E97, 0x8E98, 0x8E99, 0x8E9A, 0x8E9B, 0x8E9D, 0x8E9F, 0x8EA0, 0x8EA1, 0x8EA2, 0x8EA3, 0x8EA4, 0x8EA5, 0x8EA6, 0x8EA7, 0x8EA8, 0x8EA9, 0x8EAA, 0x8EAD, 0x8EAE, 0x8EB0, 0x8EB1, 0x8EB3, 0x8EB4, 0x8EB5, 0x8EB6, 0x8EB7, 0x8EB8, 0x8EB9, 0x8EBB, 0x8EBC, 0x8EBD, 0x8EBE, 0x8EBF, 0x8EC0, 0x8EC1, 0x8EC2, 0x8EC3, 0x8EC4, 0x8EC5, 0x8EC6, 0x8EC7, 0x8EC8, 0x8EC9, 0x8ECA, 0x8ECB, 0x8ECC, 0x8ECD, 0x8ECF, 0x8ED0, 0x8ED1, 0x8ED2, 0x8ED3, 0x8ED4, 0x8ED5, 0x8ED6, 0x8ED7, 0x8ED8, 0x8ED9, 0x8EDA, 0x8EDB, 0x8EDC, 0x8EDD, 0x8EDE, 0x8EDF, 0x8EE0, 0x8EE1, 0x8EE2, 0x8EE3, + 0x8EE4, 0x580B, 0x580D, 0x57FD, 0x57ED, 0x5800, 0x581E, 0x5819, 0x5844, 0x5820, 0x5865, 0x586C, 0x5881, 0x5889, 0x589A, 0x5880, 0x99A8, 0x9F19, 0x61FF, 0x8279, 0x827D, 0x827F, 0x828F, 0x828A, 0x82A8, 0x8284, 0x828E, 0x8291, 0x8297, 0x8299, 0x82AB, 0x82B8, 0x82BE, 0x82B0, 0x82C8, 0x82CA, 0x82E3, 0x8298, 0x82B7, 0x82AE, 0x82CB, 0x82CC, 0x82C1, 0x82A9, 0x82B4, 0x82A1, 0x82AA, 0x829F, 0x82C4, 0x82CE, 0x82A4, 0x82E1, 0x8309, 0x82F7, 0x82E4, 0x830F, 0x8307, 0x82DC, 0x82F4, 0x82D2, 0x82D8, 0x830C, 0x82FB, 0x82D3, 0x8311, 0x831A, 0x8306, 0x8314, 0x8315, 0x82E0, 0x82D5, 0x831C, 0x8351, 0x835B, 0x835C, 0x8308, 0x8392, 0x833C, 0x8334, 0x8331, 0x839B, 0x835E, 0x832F, 0x834F, 0x8347, 0x8343, 0x835F, 0x8340, 0x8317, 0x8360, 0x832D, 0x833A, 0x8333, 0x8366, 0x8365, + 0x8EE5, 0x8EE6, 0x8EE7, 0x8EE8, 0x8EE9, 0x8EEA, 0x8EEB, 0x8EEC, 0x8EED, 0x8EEE, 0x8EEF, 0x8EF0, 0x8EF1, 0x8EF2, 0x8EF3, 0x8EF4, 0x8EF5, 0x8EF6, 0x8EF7, 0x8EF8, 0x8EF9, 0x8EFA, 0x8EFB, 0x8EFC, 0x8EFD, 0x8EFE, 0x8EFF, 0x8F00, 0x8F01, 0x8F02, 0x8F03, 0x8F04, 0x8F05, 0x8F06, 0x8F07, 0x8F08, 0x8F09, 0x8F0A, 0x8F0B, 0x8F0C, 0x8F0D, 0x8F0E, 0x8F0F, 0x8F10, 0x8F11, 0x8F12, 0x8F13, 0x8F14, 0x8F15, 0x8F16, 0x8F17, 0x8F18, 0x8F19, 0x8F1A, 0x8F1B, 0x8F1C, 0x8F1D, 0x8F1E, 0x8F1F, 0x8F20, 0x8F21, 0x8F22, 0x8F23, 0x8F24, 0x8F25, 0x8F26, 0x8F27, 0x8F28, 0x8F29, 0x8F2A, 0x8F2B, 0x8F2C, 0x8F2D, 0x8F2E, 0x8F2F, 0x8F30, 0x8F31, 0x8F32, 0x8F33, 0x8F34, 0x8F35, 0x8F36, 0x8F37, 0x8F38, 0x8F39, 0x8F3A, 0x8F3B, 0x8F3C, 0x8F3D, 0x8F3E, 0x8F3F, 0x8F40, 0x8F41, 0x8F42, 0x8F43, + 0x8F44, 0x8368, 0x831B, 0x8369, 0x836C, 0x836A, 0x836D, 0x836E, 0x83B0, 0x8378, 0x83B3, 0x83B4, 0x83A0, 0x83AA, 0x8393, 0x839C, 0x8385, 0x837C, 0x83B6, 0x83A9, 0x837D, 0x83B8, 0x837B, 0x8398, 0x839E, 0x83A8, 0x83BA, 0x83BC, 0x83C1, 0x8401, 0x83E5, 0x83D8, 0x5807, 0x8418, 0x840B, 0x83DD, 0x83FD, 0x83D6, 0x841C, 0x8438, 0x8411, 0x8406, 0x83D4, 0x83DF, 0x840F, 0x8403, 0x83F8, 0x83F9, 0x83EA, 0x83C5, 0x83C0, 0x8426, 0x83F0, 0x83E1, 0x845C, 0x8451, 0x845A, 0x8459, 0x8473, 0x8487, 0x8488, 0x847A, 0x8489, 0x8478, 0x843C, 0x8446, 0x8469, 0x8476, 0x848C, 0x848E, 0x8431, 0x846D, 0x84C1, 0x84CD, 0x84D0, 0x84E6, 0x84BD, 0x84D3, 0x84CA, 0x84BF, 0x84BA, 0x84E0, 0x84A1, 0x84B9, 0x84B4, 0x8497, 0x84E5, 0x84E3, 0x850C, 0x750D, 0x8538, 0x84F0, 0x8539, 0x851F, 0x853A, + 0x8F45, 0x8F46, 0x8F47, 0x8F48, 0x8F49, 0x8F4A, 0x8F4B, 0x8F4C, 0x8F4D, 0x8F4E, 0x8F4F, 0x8F50, 0x8F51, 0x8F52, 0x8F53, 0x8F54, 0x8F55, 0x8F56, 0x8F57, 0x8F58, 0x8F59, 0x8F5A, 0x8F5B, 0x8F5C, 0x8F5D, 0x8F5E, 0x8F5F, 0x8F60, 0x8F61, 0x8F62, 0x8F63, 0x8F64, 0x8F65, 0x8F6A, 0x8F80, 0x8F8C, 0x8F92, 0x8F9D, 0x8FA0, 0x8FA1, 0x8FA2, 0x8FA4, 0x8FA5, 0x8FA6, 0x8FA7, 0x8FAA, 0x8FAC, 0x8FAD, 0x8FAE, 0x8FAF, 0x8FB2, 0x8FB3, 0x8FB4, 0x8FB5, 0x8FB7, 0x8FB8, 0x8FBA, 0x8FBB, 0x8FBC, 0x8FBF, 0x8FC0, 0x8FC3, 0x8FC6, 0x8FC9, 0x8FCA, 0x8FCB, 0x8FCC, 0x8FCD, 0x8FCF, 0x8FD2, 0x8FD6, 0x8FD7, 0x8FDA, 0x8FE0, 0x8FE1, 0x8FE3, 0x8FE7, 0x8FEC, 0x8FEF, 0x8FF1, 0x8FF2, 0x8FF4, 0x8FF5, 0x8FF6, 0x8FFA, 0x8FFB, 0x8FFC, 0x8FFE, 0x8FFF, 0x9007, 0x9008, 0x900C, 0x900E, 0x9013, 0x9015, + 0x9018, 0x8556, 0x853B, 0x84FF, 0x84FC, 0x8559, 0x8548, 0x8568, 0x8564, 0x855E, 0x857A, 0x77A2, 0x8543, 0x8572, 0x857B, 0x85A4, 0x85A8, 0x8587, 0x858F, 0x8579, 0x85AE, 0x859C, 0x8585, 0x85B9, 0x85B7, 0x85B0, 0x85D3, 0x85C1, 0x85DC, 0x85FF, 0x8627, 0x8605, 0x8629, 0x8616, 0x863C, 0x5EFE, 0x5F08, 0x593C, 0x5941, 0x8037, 0x5955, 0x595A, 0x5958, 0x530F, 0x5C22, 0x5C25, 0x5C2C, 0x5C34, 0x624C, 0x626A, 0x629F, 0x62BB, 0x62CA, 0x62DA, 0x62D7, 0x62EE, 0x6322, 0x62F6, 0x6339, 0x634B, 0x6343, 0x63AD, 0x63F6, 0x6371, 0x637A, 0x638E, 0x63B4, 0x636D, 0x63AC, 0x638A, 0x6369, 0x63AE, 0x63BC, 0x63F2, 0x63F8, 0x63E0, 0x63FF, 0x63C4, 0x63DE, 0x63CE, 0x6452, 0x63C6, 0x63BE, 0x6445, 0x6441, 0x640B, 0x641B, 0x6420, 0x640C, 0x6426, 0x6421, 0x645E, 0x6484, 0x646D, 0x6496, + 0x9019, 0x901C, 0x9023, 0x9024, 0x9025, 0x9027, 0x9028, 0x9029, 0x902A, 0x902B, 0x902C, 0x9030, 0x9031, 0x9032, 0x9033, 0x9034, 0x9037, 0x9039, 0x903A, 0x903D, 0x903F, 0x9040, 0x9043, 0x9045, 0x9046, 0x9048, 0x9049, 0x904A, 0x904B, 0x904C, 0x904E, 0x9054, 0x9055, 0x9056, 0x9059, 0x905A, 0x905C, 0x905D, 0x905E, 0x905F, 0x9060, 0x9061, 0x9064, 0x9066, 0x9067, 0x9069, 0x906A, 0x906B, 0x906C, 0x906F, 0x9070, 0x9071, 0x9072, 0x9073, 0x9076, 0x9077, 0x9078, 0x9079, 0x907A, 0x907B, 0x907C, 0x907E, 0x9081, 0x9084, 0x9085, 0x9086, 0x9087, 0x9089, 0x908A, 0x908C, 0x908D, 0x908E, 0x908F, 0x9090, 0x9092, 0x9094, 0x9096, 0x9098, 0x909A, 0x909C, 0x909E, 0x909F, 0x90A0, 0x90A4, 0x90A5, 0x90A7, 0x90A8, 0x90A9, 0x90AB, 0x90AD, 0x90B2, 0x90B7, 0x90BC, 0x90BD, 0x90BF, + 0x90C0, 0x647A, 0x64B7, 0x64B8, 0x6499, 0x64BA, 0x64C0, 0x64D0, 0x64D7, 0x64E4, 0x64E2, 0x6509, 0x6525, 0x652E, 0x5F0B, 0x5FD2, 0x7519, 0x5F11, 0x535F, 0x53F1, 0x53FD, 0x53E9, 0x53E8, 0x53FB, 0x5412, 0x5416, 0x5406, 0x544B, 0x5452, 0x5453, 0x5454, 0x5456, 0x5443, 0x5421, 0x5457, 0x5459, 0x5423, 0x5432, 0x5482, 0x5494, 0x5477, 0x5471, 0x5464, 0x549A, 0x549B, 0x5484, 0x5476, 0x5466, 0x549D, 0x54D0, 0x54AD, 0x54C2, 0x54B4, 0x54D2, 0x54A7, 0x54A6, 0x54D3, 0x54D4, 0x5472, 0x54A3, 0x54D5, 0x54BB, 0x54BF, 0x54CC, 0x54D9, 0x54DA, 0x54DC, 0x54A9, 0x54AA, 0x54A4, 0x54DD, 0x54CF, 0x54DE, 0x551B, 0x54E7, 0x5520, 0x54FD, 0x5514, 0x54F3, 0x5522, 0x5523, 0x550F, 0x5511, 0x5527, 0x552A, 0x5567, 0x558F, 0x55B5, 0x5549, 0x556D, 0x5541, 0x5555, 0x553F, 0x5550, 0x553C, + 0x90C2, 0x90C3, 0x90C6, 0x90C8, 0x90C9, 0x90CB, 0x90CC, 0x90CD, 0x90D2, 0x90D4, 0x90D5, 0x90D6, 0x90D8, 0x90D9, 0x90DA, 0x90DE, 0x90DF, 0x90E0, 0x90E3, 0x90E4, 0x90E5, 0x90E9, 0x90EA, 0x90EC, 0x90EE, 0x90F0, 0x90F1, 0x90F2, 0x90F3, 0x90F5, 0x90F6, 0x90F7, 0x90F9, 0x90FA, 0x90FB, 0x90FC, 0x90FF, 0x9100, 0x9101, 0x9103, 0x9105, 0x9106, 0x9107, 0x9108, 0x9109, 0x910A, 0x910B, 0x910C, 0x910D, 0x910E, 0x910F, 0x9110, 0x9111, 0x9112, 0x9113, 0x9114, 0x9115, 0x9116, 0x9117, 0x9118, 0x911A, 0x911B, 0x911C, 0x911D, 0x911F, 0x9120, 0x9121, 0x9124, 0x9125, 0x9126, 0x9127, 0x9128, 0x9129, 0x912A, 0x912B, 0x912C, 0x912D, 0x912E, 0x9130, 0x9132, 0x9133, 0x9134, 0x9135, 0x9136, 0x9137, 0x9138, 0x913A, 0x913B, 0x913C, 0x913D, 0x913E, 0x913F, 0x9140, 0x9141, 0x9142, + 0x9144, 0x5537, 0x5556, 0x5575, 0x5576, 0x5577, 0x5533, 0x5530, 0x555C, 0x558B, 0x55D2, 0x5583, 0x55B1, 0x55B9, 0x5588, 0x5581, 0x559F, 0x557E, 0x55D6, 0x5591, 0x557B, 0x55DF, 0x55BD, 0x55BE, 0x5594, 0x5599, 0x55EA, 0x55F7, 0x55C9, 0x561F, 0x55D1, 0x55EB, 0x55EC, 0x55D4, 0x55E6, 0x55DD, 0x55C4, 0x55EF, 0x55E5, 0x55F2, 0x55F3, 0x55CC, 0x55CD, 0x55E8, 0x55F5, 0x55E4, 0x8F94, 0x561E, 0x5608, 0x560C, 0x5601, 0x5624, 0x5623, 0x55FE, 0x5600, 0x5627, 0x562D, 0x5658, 0x5639, 0x5657, 0x562C, 0x564D, 0x5662, 0x5659, 0x565C, 0x564C, 0x5654, 0x5686, 0x5664, 0x5671, 0x566B, 0x567B, 0x567C, 0x5685, 0x5693, 0x56AF, 0x56D4, 0x56D7, 0x56DD, 0x56E1, 0x56F5, 0x56EB, 0x56F9, 0x56FF, 0x5704, 0x570A, 0x5709, 0x571C, 0x5E0F, 0x5E19, 0x5E14, 0x5E11, 0x5E31, 0x5E3B, 0x5E3C, + 0x9145, 0x9147, 0x9148, 0x9151, 0x9153, 0x9154, 0x9155, 0x9156, 0x9158, 0x9159, 0x915B, 0x915C, 0x915F, 0x9160, 0x9166, 0x9167, 0x9168, 0x916B, 0x916D, 0x9173, 0x917A, 0x917B, 0x917C, 0x9180, 0x9181, 0x9182, 0x9183, 0x9184, 0x9186, 0x9188, 0x918A, 0x918E, 0x918F, 0x9193, 0x9194, 0x9195, 0x9196, 0x9197, 0x9198, 0x9199, 0x919C, 0x919D, 0x919E, 0x919F, 0x91A0, 0x91A1, 0x91A4, 0x91A5, 0x91A6, 0x91A7, 0x91A8, 0x91A9, 0x91AB, 0x91AC, 0x91B0, 0x91B1, 0x91B2, 0x91B3, 0x91B6, 0x91B7, 0x91B8, 0x91B9, 0x91BB, 0x91BC, 0x91BD, 0x91BE, 0x91BF, 0x91C0, 0x91C1, 0x91C2, 0x91C3, 0x91C4, 0x91C5, 0x91C6, 0x91C8, 0x91CB, 0x91D0, 0x91D2, 0x91D3, 0x91D4, 0x91D5, 0x91D6, 0x91D7, 0x91D8, 0x91D9, 0x91DA, 0x91DB, 0x91DD, 0x91DE, 0x91DF, 0x91E0, 0x91E1, 0x91E2, 0x91E3, 0x91E4, + 0x91E5, 0x5E37, 0x5E44, 0x5E54, 0x5E5B, 0x5E5E, 0x5E61, 0x5C8C, 0x5C7A, 0x5C8D, 0x5C90, 0x5C96, 0x5C88, 0x5C98, 0x5C99, 0x5C91, 0x5C9A, 0x5C9C, 0x5CB5, 0x5CA2, 0x5CBD, 0x5CAC, 0x5CAB, 0x5CB1, 0x5CA3, 0x5CC1, 0x5CB7, 0x5CC4, 0x5CD2, 0x5CE4, 0x5CCB, 0x5CE5, 0x5D02, 0x5D03, 0x5D27, 0x5D26, 0x5D2E, 0x5D24, 0x5D1E, 0x5D06, 0x5D1B, 0x5D58, 0x5D3E, 0x5D34, 0x5D3D, 0x5D6C, 0x5D5B, 0x5D6F, 0x5D5D, 0x5D6B, 0x5D4B, 0x5D4A, 0x5D69, 0x5D74, 0x5D82, 0x5D99, 0x5D9D, 0x8C73, 0x5DB7, 0x5DC5, 0x5F73, 0x5F77, 0x5F82, 0x5F87, 0x5F89, 0x5F8C, 0x5F95, 0x5F99, 0x5F9C, 0x5FA8, 0x5FAD, 0x5FB5, 0x5FBC, 0x8862, 0x5F61, 0x72AD, 0x72B0, 0x72B4, 0x72B7, 0x72B8, 0x72C3, 0x72C1, 0x72CE, 0x72CD, 0x72D2, 0x72E8, 0x72EF, 0x72E9, 0x72F2, 0x72F4, 0x72F7, 0x7301, 0x72F3, 0x7303, 0x72FA, + 0x91E6, 0x91E7, 0x91E8, 0x91E9, 0x91EA, 0x91EB, 0x91EC, 0x91ED, 0x91EE, 0x91EF, 0x91F0, 0x91F1, 0x91F2, 0x91F3, 0x91F4, 0x91F5, 0x91F6, 0x91F7, 0x91F8, 0x91F9, 0x91FA, 0x91FB, 0x91FC, 0x91FD, 0x91FE, 0x91FF, 0x9200, 0x9201, 0x9202, 0x9203, 0x9204, 0x9205, 0x9206, 0x9207, 0x9208, 0x9209, 0x920A, 0x920B, 0x920C, 0x920D, 0x920E, 0x920F, 0x9210, 0x9211, 0x9212, 0x9213, 0x9214, 0x9215, 0x9216, 0x9217, 0x9218, 0x9219, 0x921A, 0x921B, 0x921C, 0x921D, 0x921E, 0x921F, 0x9220, 0x9221, 0x9222, 0x9223, 0x9224, 0x9225, 0x9226, 0x9227, 0x9228, 0x9229, 0x922A, 0x922B, 0x922C, 0x922D, 0x922E, 0x922F, 0x9230, 0x9231, 0x9232, 0x9233, 0x9234, 0x9235, 0x9236, 0x9237, 0x9238, 0x9239, 0x923A, 0x923B, 0x923C, 0x923D, 0x923E, 0x923F, 0x9240, 0x9241, 0x9242, 0x9243, 0x9244, + 0x9245, 0x72FB, 0x7317, 0x7313, 0x7321, 0x730A, 0x731E, 0x731D, 0x7315, 0x7322, 0x7339, 0x7325, 0x732C, 0x7338, 0x7331, 0x7350, 0x734D, 0x7357, 0x7360, 0x736C, 0x736F, 0x737E, 0x821B, 0x5925, 0x98E7, 0x5924, 0x5902, 0x9963, 0x9967, 0x9968, 0x9969, 0x996A, 0x996B, 0x996C, 0x9974, 0x9977, 0x997D, 0x9980, 0x9984, 0x9987, 0x998A, 0x998D, 0x9990, 0x9991, 0x9993, 0x9994, 0x9995, 0x5E80, 0x5E91, 0x5E8B, 0x5E96, 0x5EA5, 0x5EA0, 0x5EB9, 0x5EB5, 0x5EBE, 0x5EB3, 0x8D53, 0x5ED2, 0x5ED1, 0x5EDB, 0x5EE8, 0x5EEA, 0x81BA, 0x5FC4, 0x5FC9, 0x5FD6, 0x5FCF, 0x6003, 0x5FEE, 0x6004, 0x5FE1, 0x5FE4, 0x5FFE, 0x6005, 0x6006, 0x5FEA, 0x5FED, 0x5FF8, 0x6019, 0x6035, 0x6026, 0x601B, 0x600F, 0x600D, 0x6029, 0x602B, 0x600A, 0x603F, 0x6021, 0x6078, 0x6079, 0x607B, 0x607A, 0x6042, + 0x9246, 0x9247, 0x9248, 0x9249, 0x924A, 0x924B, 0x924C, 0x924D, 0x924E, 0x924F, 0x9250, 0x9251, 0x9252, 0x9253, 0x9254, 0x9255, 0x9256, 0x9257, 0x9258, 0x9259, 0x925A, 0x925B, 0x925C, 0x925D, 0x925E, 0x925F, 0x9260, 0x9261, 0x9262, 0x9263, 0x9264, 0x9265, 0x9266, 0x9267, 0x9268, 0x9269, 0x926A, 0x926B, 0x926C, 0x926D, 0x926E, 0x926F, 0x9270, 0x9271, 0x9272, 0x9273, 0x9275, 0x9276, 0x9277, 0x9278, 0x9279, 0x927A, 0x927B, 0x927C, 0x927D, 0x927E, 0x927F, 0x9280, 0x9281, 0x9282, 0x9283, 0x9284, 0x9285, 0x9286, 0x9287, 0x9288, 0x9289, 0x928A, 0x928B, 0x928C, 0x928D, 0x928F, 0x9290, 0x9291, 0x9292, 0x9293, 0x9294, 0x9295, 0x9296, 0x9297, 0x9298, 0x9299, 0x929A, 0x929B, 0x929C, 0x929D, 0x929E, 0x929F, 0x92A0, 0x92A1, 0x92A2, 0x92A3, 0x92A4, 0x92A5, 0x92A6, + 0x92A7, 0x606A, 0x607D, 0x6096, 0x609A, 0x60AD, 0x609D, 0x6083, 0x6092, 0x608C, 0x609B, 0x60EC, 0x60BB, 0x60B1, 0x60DD, 0x60D8, 0x60C6, 0x60DA, 0x60B4, 0x6120, 0x6126, 0x6115, 0x6123, 0x60F4, 0x6100, 0x610E, 0x612B, 0x614A, 0x6175, 0x61AC, 0x6194, 0x61A7, 0x61B7, 0x61D4, 0x61F5, 0x5FDD, 0x96B3, 0x95E9, 0x95EB, 0x95F1, 0x95F3, 0x95F5, 0x95F6, 0x95FC, 0x95FE, 0x9603, 0x9604, 0x9606, 0x9608, 0x960A, 0x960B, 0x960C, 0x960D, 0x960F, 0x9612, 0x9615, 0x9616, 0x9617, 0x9619, 0x961A, 0x4E2C, 0x723F, 0x6215, 0x6C35, 0x6C54, 0x6C5C, 0x6C4A, 0x6CA3, 0x6C85, 0x6C90, 0x6C94, 0x6C8C, 0x6C68, 0x6C69, 0x6C74, 0x6C76, 0x6C86, 0x6CA9, 0x6CD0, 0x6CD4, 0x6CAD, 0x6CF7, 0x6CF8, 0x6CF1, 0x6CD7, 0x6CB2, 0x6CE0, 0x6CD6, 0x6CFA, 0x6CEB, 0x6CEE, 0x6CB1, 0x6CD3, 0x6CEF, 0x6CFE, + 0x92A8, 0x92A9, 0x92AA, 0x92AB, 0x92AC, 0x92AD, 0x92AF, 0x92B0, 0x92B1, 0x92B2, 0x92B3, 0x92B4, 0x92B5, 0x92B6, 0x92B7, 0x92B8, 0x92B9, 0x92BA, 0x92BB, 0x92BC, 0x92BD, 0x92BE, 0x92BF, 0x92C0, 0x92C1, 0x92C2, 0x92C3, 0x92C4, 0x92C5, 0x92C6, 0x92C7, 0x92C9, 0x92CA, 0x92CB, 0x92CC, 0x92CD, 0x92CE, 0x92CF, 0x92D0, 0x92D1, 0x92D2, 0x92D3, 0x92D4, 0x92D5, 0x92D6, 0x92D7, 0x92D8, 0x92D9, 0x92DA, 0x92DB, 0x92DC, 0x92DD, 0x92DE, 0x92DF, 0x92E0, 0x92E1, 0x92E2, 0x92E3, 0x92E4, 0x92E5, 0x92E6, 0x92E7, 0x92E8, 0x92E9, 0x92EA, 0x92EB, 0x92EC, 0x92ED, 0x92EE, 0x92EF, 0x92F0, 0x92F1, 0x92F2, 0x92F3, 0x92F4, 0x92F5, 0x92F6, 0x92F7, 0x92F8, 0x92F9, 0x92FA, 0x92FB, 0x92FC, 0x92FD, 0x92FE, 0x92FF, 0x9300, 0x9301, 0x9302, 0x9303, 0x9304, 0x9305, 0x9306, 0x9307, 0x9308, + 0x9309, 0x6D39, 0x6D27, 0x6D0C, 0x6D43, 0x6D48, 0x6D07, 0x6D04, 0x6D19, 0x6D0E, 0x6D2B, 0x6D4D, 0x6D2E, 0x6D35, 0x6D1A, 0x6D4F, 0x6D52, 0x6D54, 0x6D33, 0x6D91, 0x6D6F, 0x6D9E, 0x6DA0, 0x6D5E, 0x6D93, 0x6D94, 0x6D5C, 0x6D60, 0x6D7C, 0x6D63, 0x6E1A, 0x6DC7, 0x6DC5, 0x6DDE, 0x6E0E, 0x6DBF, 0x6DE0, 0x6E11, 0x6DE6, 0x6DDD, 0x6DD9, 0x6E16, 0x6DAB, 0x6E0C, 0x6DAE, 0x6E2B, 0x6E6E, 0x6E4E, 0x6E6B, 0x6EB2, 0x6E5F, 0x6E86, 0x6E53, 0x6E54, 0x6E32, 0x6E25, 0x6E44, 0x6EDF, 0x6EB1, 0x6E98, 0x6EE0, 0x6F2D, 0x6EE2, 0x6EA5, 0x6EA7, 0x6EBD, 0x6EBB, 0x6EB7, 0x6ED7, 0x6EB4, 0x6ECF, 0x6E8F, 0x6EC2, 0x6E9F, 0x6F62, 0x6F46, 0x6F47, 0x6F24, 0x6F15, 0x6EF9, 0x6F2F, 0x6F36, 0x6F4B, 0x6F74, 0x6F2A, 0x6F09, 0x6F29, 0x6F89, 0x6F8D, 0x6F8C, 0x6F78, 0x6F72, 0x6F7C, 0x6F7A, 0x6FD1, + 0x930A, 0x930B, 0x930C, 0x930D, 0x930E, 0x930F, 0x9310, 0x9311, 0x9312, 0x9313, 0x9314, 0x9315, 0x9316, 0x9317, 0x9318, 0x9319, 0x931A, 0x931B, 0x931C, 0x931D, 0x931E, 0x931F, 0x9320, 0x9321, 0x9322, 0x9323, 0x9324, 0x9325, 0x9326, 0x9327, 0x9328, 0x9329, 0x932A, 0x932B, 0x932C, 0x932D, 0x932E, 0x932F, 0x9330, 0x9331, 0x9332, 0x9333, 0x9334, 0x9335, 0x9336, 0x9337, 0x9338, 0x9339, 0x933A, 0x933B, 0x933C, 0x933D, 0x933F, 0x9340, 0x9341, 0x9342, 0x9343, 0x9344, 0x9345, 0x9346, 0x9347, 0x9348, 0x9349, 0x934A, 0x934B, 0x934C, 0x934D, 0x934E, 0x934F, 0x9350, 0x9351, 0x9352, 0x9353, 0x9354, 0x9355, 0x9356, 0x9357, 0x9358, 0x9359, 0x935A, 0x935B, 0x935C, 0x935D, 0x935E, 0x935F, 0x9360, 0x9361, 0x9362, 0x9363, 0x9364, 0x9365, 0x9366, 0x9367, 0x9368, 0x9369, + 0x936B, 0x6FC9, 0x6FA7, 0x6FB9, 0x6FB6, 0x6FC2, 0x6FE1, 0x6FEE, 0x6FDE, 0x6FE0, 0x6FEF, 0x701A, 0x7023, 0x701B, 0x7039, 0x7035, 0x704F, 0x705E, 0x5B80, 0x5B84, 0x5B95, 0x5B93, 0x5BA5, 0x5BB8, 0x752F, 0x9A9E, 0x6434, 0x5BE4, 0x5BEE, 0x8930, 0x5BF0, 0x8E47, 0x8B07, 0x8FB6, 0x8FD3, 0x8FD5, 0x8FE5, 0x8FEE, 0x8FE4, 0x8FE9, 0x8FE6, 0x8FF3, 0x8FE8, 0x9005, 0x9004, 0x900B, 0x9026, 0x9011, 0x900D, 0x9016, 0x9021, 0x9035, 0x9036, 0x902D, 0x902F, 0x9044, 0x9051, 0x9052, 0x9050, 0x9068, 0x9058, 0x9062, 0x905B, 0x66B9, 0x9074, 0x907D, 0x9082, 0x9088, 0x9083, 0x908B, 0x5F50, 0x5F57, 0x5F56, 0x5F58, 0x5C3B, 0x54AB, 0x5C50, 0x5C59, 0x5B71, 0x5C63, 0x5C66, 0x7FBC, 0x5F2A, 0x5F29, 0x5F2D, 0x8274, 0x5F3C, 0x9B3B, 0x5C6E, 0x5981, 0x5983, 0x598D, 0x59A9, 0x59AA, 0x59A3, + 0x936C, 0x936D, 0x936E, 0x936F, 0x9370, 0x9371, 0x9372, 0x9373, 0x9374, 0x9375, 0x9376, 0x9377, 0x9378, 0x9379, 0x937A, 0x937B, 0x937C, 0x937D, 0x937E, 0x937F, 0x9380, 0x9381, 0x9382, 0x9383, 0x9384, 0x9385, 0x9386, 0x9387, 0x9388, 0x9389, 0x938A, 0x938B, 0x938C, 0x938D, 0x938E, 0x9390, 0x9391, 0x9392, 0x9393, 0x9394, 0x9395, 0x9396, 0x9397, 0x9398, 0x9399, 0x939A, 0x939B, 0x939C, 0x939D, 0x939E, 0x939F, 0x93A0, 0x93A1, 0x93A2, 0x93A3, 0x93A4, 0x93A5, 0x93A6, 0x93A7, 0x93A8, 0x93A9, 0x93AA, 0x93AB, 0x93AC, 0x93AD, 0x93AE, 0x93AF, 0x93B0, 0x93B1, 0x93B2, 0x93B3, 0x93B4, 0x93B5, 0x93B6, 0x93B7, 0x93B8, 0x93B9, 0x93BA, 0x93BB, 0x93BC, 0x93BD, 0x93BE, 0x93BF, 0x93C0, 0x93C1, 0x93C2, 0x93C3, 0x93C4, 0x93C5, 0x93C6, 0x93C7, 0x93C8, 0x93C9, 0x93CB, 0x93CC, + 0x93CD, 0x5997, 0x59CA, 0x59AB, 0x599E, 0x59A4, 0x59D2, 0x59B2, 0x59AF, 0x59D7, 0x59BE, 0x5A05, 0x5A06, 0x59DD, 0x5A08, 0x59E3, 0x59D8, 0x59F9, 0x5A0C, 0x5A09, 0x5A32, 0x5A34, 0x5A11, 0x5A23, 0x5A13, 0x5A40, 0x5A67, 0x5A4A, 0x5A55, 0x5A3C, 0x5A62, 0x5A75, 0x80EC, 0x5AAA, 0x5A9B, 0x5A77, 0x5A7A, 0x5ABE, 0x5AEB, 0x5AB2, 0x5AD2, 0x5AD4, 0x5AB8, 0x5AE0, 0x5AE3, 0x5AF1, 0x5AD6, 0x5AE6, 0x5AD8, 0x5ADC, 0x5B09, 0x5B17, 0x5B16, 0x5B32, 0x5B37, 0x5B40, 0x5C15, 0x5C1C, 0x5B5A, 0x5B65, 0x5B73, 0x5B51, 0x5B53, 0x5B62, 0x9A75, 0x9A77, 0x9A78, 0x9A7A, 0x9A7F, 0x9A7D, 0x9A80, 0x9A81, 0x9A85, 0x9A88, 0x9A8A, 0x9A90, 0x9A92, 0x9A93, 0x9A96, 0x9A98, 0x9A9B, 0x9A9C, 0x9A9D, 0x9A9F, 0x9AA0, 0x9AA2, 0x9AA3, 0x9AA5, 0x9AA7, 0x7E9F, 0x7EA1, 0x7EA3, 0x7EA5, 0x7EA8, 0x7EA9, + 0x93CE, 0x93CF, 0x93D0, 0x93D1, 0x93D2, 0x93D3, 0x93D4, 0x93D5, 0x93D7, 0x93D8, 0x93D9, 0x93DA, 0x93DB, 0x93DC, 0x93DD, 0x93DE, 0x93DF, 0x93E0, 0x93E1, 0x93E2, 0x93E3, 0x93E4, 0x93E5, 0x93E6, 0x93E7, 0x93E8, 0x93E9, 0x93EA, 0x93EB, 0x93EC, 0x93ED, 0x93EE, 0x93EF, 0x93F0, 0x93F1, 0x93F2, 0x93F3, 0x93F4, 0x93F5, 0x93F6, 0x93F7, 0x93F8, 0x93F9, 0x93FA, 0x93FB, 0x93FC, 0x93FD, 0x93FE, 0x93FF, 0x9400, 0x9401, 0x9402, 0x9403, 0x9404, 0x9405, 0x9406, 0x9407, 0x9408, 0x9409, 0x940A, 0x940B, 0x940C, 0x940D, 0x940E, 0x940F, 0x9410, 0x9411, 0x9412, 0x9413, 0x9414, 0x9415, 0x9416, 0x9417, 0x9418, 0x9419, 0x941A, 0x941B, 0x941C, 0x941D, 0x941E, 0x941F, 0x9420, 0x9421, 0x9422, 0x9423, 0x9424, 0x9425, 0x9426, 0x9427, 0x9428, 0x9429, 0x942A, 0x942B, 0x942C, 0x942D, + 0x942E, 0x7EAD, 0x7EB0, 0x7EBE, 0x7EC0, 0x7EC1, 0x7EC2, 0x7EC9, 0x7ECB, 0x7ECC, 0x7ED0, 0x7ED4, 0x7ED7, 0x7EDB, 0x7EE0, 0x7EE1, 0x7EE8, 0x7EEB, 0x7EEE, 0x7EEF, 0x7EF1, 0x7EF2, 0x7F0D, 0x7EF6, 0x7EFA, 0x7EFB, 0x7EFE, 0x7F01, 0x7F02, 0x7F03, 0x7F07, 0x7F08, 0x7F0B, 0x7F0C, 0x7F0F, 0x7F11, 0x7F12, 0x7F17, 0x7F19, 0x7F1C, 0x7F1B, 0x7F1F, 0x7F21, 0x7F22, 0x7F23, 0x7F24, 0x7F25, 0x7F26, 0x7F27, 0x7F2A, 0x7F2B, 0x7F2C, 0x7F2D, 0x7F2F, 0x7F30, 0x7F31, 0x7F32, 0x7F33, 0x7F35, 0x5E7A, 0x757F, 0x5DDB, 0x753E, 0x9095, 0x738E, 0x7391, 0x73AE, 0x73A2, 0x739F, 0x73CF, 0x73C2, 0x73D1, 0x73B7, 0x73B3, 0x73C0, 0x73C9, 0x73C8, 0x73E5, 0x73D9, 0x987C, 0x740A, 0x73E9, 0x73E7, 0x73DE, 0x73BA, 0x73F2, 0x740F, 0x742A, 0x745B, 0x7426, 0x7425, 0x7428, 0x7430, 0x742E, 0x742C, + 0x942F, 0x9430, 0x9431, 0x9432, 0x9433, 0x9434, 0x9435, 0x9436, 0x9437, 0x9438, 0x9439, 0x943A, 0x943B, 0x943C, 0x943D, 0x943F, 0x9440, 0x9441, 0x9442, 0x9443, 0x9444, 0x9445, 0x9446, 0x9447, 0x9448, 0x9449, 0x944A, 0x944B, 0x944C, 0x944D, 0x944E, 0x944F, 0x9450, 0x9451, 0x9452, 0x9453, 0x9454, 0x9455, 0x9456, 0x9457, 0x9458, 0x9459, 0x945A, 0x945B, 0x945C, 0x945D, 0x945E, 0x945F, 0x9460, 0x9461, 0x9462, 0x9463, 0x9464, 0x9465, 0x9466, 0x9467, 0x9468, 0x9469, 0x946A, 0x946C, 0x946D, 0x946E, 0x946F, 0x9470, 0x9471, 0x9472, 0x9473, 0x9474, 0x9475, 0x9476, 0x9477, 0x9478, 0x9479, 0x947A, 0x947B, 0x947C, 0x947D, 0x947E, 0x947F, 0x9480, 0x9481, 0x9482, 0x9483, 0x9484, 0x9491, 0x9496, 0x9498, 0x94C7, 0x94CF, 0x94D3, 0x94D4, 0x94DA, 0x94E6, 0x94FB, 0x951C, + 0x9520, 0x741B, 0x741A, 0x7441, 0x745C, 0x7457, 0x7455, 0x7459, 0x7477, 0x746D, 0x747E, 0x749C, 0x748E, 0x7480, 0x7481, 0x7487, 0x748B, 0x749E, 0x74A8, 0x74A9, 0x7490, 0x74A7, 0x74D2, 0x74BA, 0x97EA, 0x97EB, 0x97EC, 0x674C, 0x6753, 0x675E, 0x6748, 0x6769, 0x67A5, 0x6787, 0x676A, 0x6773, 0x6798, 0x67A7, 0x6775, 0x67A8, 0x679E, 0x67AD, 0x678B, 0x6777, 0x677C, 0x67F0, 0x6809, 0x67D8, 0x680A, 0x67E9, 0x67B0, 0x680C, 0x67D9, 0x67B5, 0x67DA, 0x67B3, 0x67DD, 0x6800, 0x67C3, 0x67B8, 0x67E2, 0x680E, 0x67C1, 0x67FD, 0x6832, 0x6833, 0x6860, 0x6861, 0x684E, 0x6862, 0x6844, 0x6864, 0x6883, 0x681D, 0x6855, 0x6866, 0x6841, 0x6867, 0x6840, 0x683E, 0x684A, 0x6849, 0x6829, 0x68B5, 0x688F, 0x6874, 0x6877, 0x6893, 0x686B, 0x68C2, 0x696E, 0x68FC, 0x691F, 0x6920, 0x68F9, + 0x9527, 0x9533, 0x953D, 0x9543, 0x9548, 0x954B, 0x9555, 0x955A, 0x9560, 0x956E, 0x9574, 0x9575, 0x9577, 0x9578, 0x9579, 0x957A, 0x957B, 0x957C, 0x957D, 0x957E, 0x9580, 0x9581, 0x9582, 0x9583, 0x9584, 0x9585, 0x9586, 0x9587, 0x9588, 0x9589, 0x958A, 0x958B, 0x958C, 0x958D, 0x958E, 0x958F, 0x9590, 0x9591, 0x9592, 0x9593, 0x9594, 0x9595, 0x9596, 0x9597, 0x9598, 0x9599, 0x959A, 0x959B, 0x959C, 0x959D, 0x959E, 0x959F, 0x95A0, 0x95A1, 0x95A2, 0x95A3, 0x95A4, 0x95A5, 0x95A6, 0x95A7, 0x95A8, 0x95A9, 0x95AA, 0x95AB, 0x95AC, 0x95AD, 0x95AE, 0x95AF, 0x95B0, 0x95B1, 0x95B2, 0x95B3, 0x95B4, 0x95B5, 0x95B6, 0x95B7, 0x95B8, 0x95B9, 0x95BA, 0x95BB, 0x95BC, 0x95BD, 0x95BE, 0x95BF, 0x95C0, 0x95C1, 0x95C2, 0x95C3, 0x95C4, 0x95C5, 0x95C6, 0x95C7, 0x95C8, 0x95C9, 0x95CA, + 0x95CB, 0x6924, 0x68F0, 0x690B, 0x6901, 0x6957, 0x68E3, 0x6910, 0x6971, 0x6939, 0x6960, 0x6942, 0x695D, 0x6984, 0x696B, 0x6980, 0x6998, 0x6978, 0x6934, 0x69CC, 0x6987, 0x6988, 0x69CE, 0x6989, 0x6966, 0x6963, 0x6979, 0x699B, 0x69A7, 0x69BB, 0x69AB, 0x69AD, 0x69D4, 0x69B1, 0x69C1, 0x69CA, 0x69DF, 0x6995, 0x69E0, 0x698D, 0x69FF, 0x6A2F, 0x69ED, 0x6A17, 0x6A18, 0x6A65, 0x69F2, 0x6A44, 0x6A3E, 0x6AA0, 0x6A50, 0x6A5B, 0x6A35, 0x6A8E, 0x6A79, 0x6A3D, 0x6A28, 0x6A58, 0x6A7C, 0x6A91, 0x6A90, 0x6AA9, 0x6A97, 0x6AAB, 0x7337, 0x7352, 0x6B81, 0x6B82, 0x6B87, 0x6B84, 0x6B92, 0x6B93, 0x6B8D, 0x6B9A, 0x6B9B, 0x6BA1, 0x6BAA, 0x8F6B, 0x8F6D, 0x8F71, 0x8F72, 0x8F73, 0x8F75, 0x8F76, 0x8F78, 0x8F77, 0x8F79, 0x8F7A, 0x8F7C, 0x8F7E, 0x8F81, 0x8F82, 0x8F84, 0x8F87, 0x8F8B, + 0x95CC, 0x95CD, 0x95CE, 0x95CF, 0x95D0, 0x95D1, 0x95D2, 0x95D3, 0x95D4, 0x95D5, 0x95D6, 0x95D7, 0x95D8, 0x95D9, 0x95DA, 0x95DB, 0x95DC, 0x95DD, 0x95DE, 0x95DF, 0x95E0, 0x95E1, 0x95E2, 0x95E3, 0x95E4, 0x95E5, 0x95E6, 0x95E7, 0x95EC, 0x95FF, 0x9607, 0x9613, 0x9618, 0x961B, 0x961E, 0x9620, 0x9623, 0x9624, 0x9625, 0x9626, 0x9627, 0x9628, 0x9629, 0x962B, 0x962C, 0x962D, 0x962F, 0x9630, 0x9637, 0x9638, 0x9639, 0x963A, 0x963E, 0x9641, 0x9643, 0x964A, 0x964E, 0x964F, 0x9651, 0x9652, 0x9653, 0x9656, 0x9657, 0x9658, 0x9659, 0x965A, 0x965C, 0x965D, 0x965E, 0x9660, 0x9663, 0x9665, 0x9666, 0x966B, 0x966D, 0x966E, 0x966F, 0x9670, 0x9671, 0x9673, 0x9678, 0x9679, 0x967A, 0x967B, 0x967C, 0x967D, 0x967E, 0x967F, 0x9680, 0x9681, 0x9682, 0x9683, 0x9684, 0x9687, 0x9689, + 0x968A, 0x8F8D, 0x8F8E, 0x8F8F, 0x8F98, 0x8F9A, 0x8ECE, 0x620B, 0x6217, 0x621B, 0x621F, 0x6222, 0x6221, 0x6225, 0x6224, 0x622C, 0x81E7, 0x74EF, 0x74F4, 0x74FF, 0x750F, 0x7511, 0x7513, 0x6534, 0x65EE, 0x65EF, 0x65F0, 0x660A, 0x6619, 0x6772, 0x6603, 0x6615, 0x6600, 0x7085, 0x66F7, 0x661D, 0x6634, 0x6631, 0x6636, 0x6635, 0x8006, 0x665F, 0x6654, 0x6641, 0x664F, 0x6656, 0x6661, 0x6657, 0x6677, 0x6684, 0x668C, 0x66A7, 0x669D, 0x66BE, 0x66DB, 0x66DC, 0x66E6, 0x66E9, 0x8D32, 0x8D33, 0x8D36, 0x8D3B, 0x8D3D, 0x8D40, 0x8D45, 0x8D46, 0x8D48, 0x8D49, 0x8D47, 0x8D4D, 0x8D55, 0x8D59, 0x89C7, 0x89CA, 0x89CB, 0x89CC, 0x89CE, 0x89CF, 0x89D0, 0x89D1, 0x726E, 0x729F, 0x725D, 0x7266, 0x726F, 0x727E, 0x727F, 0x7284, 0x728B, 0x728D, 0x728F, 0x7292, 0x6308, 0x6332, 0x63B0, + 0x968C, 0x968E, 0x9691, 0x9692, 0x9693, 0x9695, 0x9696, 0x969A, 0x969B, 0x969D, 0x969E, 0x969F, 0x96A0, 0x96A1, 0x96A2, 0x96A3, 0x96A4, 0x96A5, 0x96A6, 0x96A8, 0x96A9, 0x96AA, 0x96AB, 0x96AC, 0x96AD, 0x96AE, 0x96AF, 0x96B1, 0x96B2, 0x96B4, 0x96B5, 0x96B7, 0x96B8, 0x96BA, 0x96BB, 0x96BF, 0x96C2, 0x96C3, 0x96C8, 0x96CA, 0x96CB, 0x96D0, 0x96D1, 0x96D3, 0x96D4, 0x96D6, 0x96D7, 0x96D8, 0x96D9, 0x96DA, 0x96DB, 0x96DC, 0x96DD, 0x96DE, 0x96DF, 0x96E1, 0x96E2, 0x96E3, 0x96E4, 0x96E5, 0x96E6, 0x96E7, 0x96EB, 0x96EC, 0x96ED, 0x96EE, 0x96F0, 0x96F1, 0x96F2, 0x96F4, 0x96F5, 0x96F8, 0x96FA, 0x96FB, 0x96FC, 0x96FD, 0x96FF, 0x9702, 0x9703, 0x9705, 0x970A, 0x970B, 0x970C, 0x9710, 0x9711, 0x9712, 0x9714, 0x9715, 0x9717, 0x9718, 0x9719, 0x971A, 0x971B, 0x971D, 0x971F, + 0x9720, 0x643F, 0x64D8, 0x8004, 0x6BEA, 0x6BF3, 0x6BFD, 0x6BF5, 0x6BF9, 0x6C05, 0x6C07, 0x6C06, 0x6C0D, 0x6C15, 0x6C18, 0x6C19, 0x6C1A, 0x6C21, 0x6C29, 0x6C24, 0x6C2A, 0x6C32, 0x6535, 0x6555, 0x656B, 0x724D, 0x7252, 0x7256, 0x7230, 0x8662, 0x5216, 0x809F, 0x809C, 0x8093, 0x80BC, 0x670A, 0x80BD, 0x80B1, 0x80AB, 0x80AD, 0x80B4, 0x80B7, 0x80E7, 0x80E8, 0x80E9, 0x80EA, 0x80DB, 0x80C2, 0x80C4, 0x80D9, 0x80CD, 0x80D7, 0x6710, 0x80DD, 0x80EB, 0x80F1, 0x80F4, 0x80ED, 0x810D, 0x810E, 0x80F2, 0x80FC, 0x6715, 0x8112, 0x8C5A, 0x8136, 0x811E, 0x812C, 0x8118, 0x8132, 0x8148, 0x814C, 0x8153, 0x8174, 0x8159, 0x815A, 0x8171, 0x8160, 0x8169, 0x817C, 0x817D, 0x816D, 0x8167, 0x584D, 0x5AB5, 0x8188, 0x8182, 0x8191, 0x6ED5, 0x81A3, 0x81AA, 0x81CC, 0x6726, 0x81CA, 0x81BB, + 0x9721, 0x9722, 0x9723, 0x9724, 0x9725, 0x9726, 0x9727, 0x9728, 0x9729, 0x972B, 0x972C, 0x972E, 0x972F, 0x9731, 0x9733, 0x9734, 0x9735, 0x9736, 0x9737, 0x973A, 0x973B, 0x973C, 0x973D, 0x973F, 0x9740, 0x9741, 0x9742, 0x9743, 0x9744, 0x9745, 0x9746, 0x9747, 0x9748, 0x9749, 0x974A, 0x974B, 0x974C, 0x974D, 0x974E, 0x974F, 0x9750, 0x9751, 0x9754, 0x9755, 0x9757, 0x9758, 0x975A, 0x975C, 0x975D, 0x975F, 0x9763, 0x9764, 0x9766, 0x9767, 0x9768, 0x976A, 0x976B, 0x976C, 0x976D, 0x976E, 0x976F, 0x9770, 0x9771, 0x9772, 0x9775, 0x9777, 0x9778, 0x9779, 0x977A, 0x977B, 0x977D, 0x977E, 0x977F, 0x9780, 0x9781, 0x9782, 0x9783, 0x9784, 0x9786, 0x9787, 0x9788, 0x9789, 0x978A, 0x978C, 0x978E, 0x978F, 0x9790, 0x9793, 0x9795, 0x9796, 0x9797, 0x9799, 0x979A, 0x979B, 0x979C, + 0x979D, 0x81C1, 0x81A6, 0x6B24, 0x6B37, 0x6B39, 0x6B43, 0x6B46, 0x6B59, 0x98D1, 0x98D2, 0x98D3, 0x98D5, 0x98D9, 0x98DA, 0x6BB3, 0x5F40, 0x6BC2, 0x89F3, 0x6590, 0x9F51, 0x6593, 0x65BC, 0x65C6, 0x65C4, 0x65C3, 0x65CC, 0x65CE, 0x65D2, 0x65D6, 0x7080, 0x709C, 0x7096, 0x709D, 0x70BB, 0x70C0, 0x70B7, 0x70AB, 0x70B1, 0x70E8, 0x70CA, 0x7110, 0x7113, 0x7116, 0x712F, 0x7131, 0x7173, 0x715C, 0x7168, 0x7145, 0x7172, 0x714A, 0x7178, 0x717A, 0x7198, 0x71B3, 0x71B5, 0x71A8, 0x71A0, 0x71E0, 0x71D4, 0x71E7, 0x71F9, 0x721D, 0x7228, 0x706C, 0x7118, 0x7166, 0x71B9, 0x623E, 0x623D, 0x6243, 0x6248, 0x6249, 0x793B, 0x7940, 0x7946, 0x7949, 0x795B, 0x795C, 0x7953, 0x795A, 0x7962, 0x7957, 0x7960, 0x796F, 0x7967, 0x797A, 0x7985, 0x798A, 0x799A, 0x79A7, 0x79B3, 0x5FD1, 0x5FD0, + 0x979E, 0x979F, 0x97A1, 0x97A2, 0x97A4, 0x97A5, 0x97A6, 0x97A7, 0x97A8, 0x97A9, 0x97AA, 0x97AC, 0x97AE, 0x97B0, 0x97B1, 0x97B3, 0x97B5, 0x97B6, 0x97B7, 0x97B8, 0x97B9, 0x97BA, 0x97BB, 0x97BC, 0x97BD, 0x97BE, 0x97BF, 0x97C0, 0x97C1, 0x97C2, 0x97C3, 0x97C4, 0x97C5, 0x97C6, 0x97C7, 0x97C8, 0x97C9, 0x97CA, 0x97CB, 0x97CC, 0x97CD, 0x97CE, 0x97CF, 0x97D0, 0x97D1, 0x97D2, 0x97D3, 0x97D4, 0x97D5, 0x97D6, 0x97D7, 0x97D8, 0x97D9, 0x97DA, 0x97DB, 0x97DC, 0x97DD, 0x97DE, 0x97DF, 0x97E0, 0x97E1, 0x97E2, 0x97E3, 0x97E4, 0x97E5, 0x97E8, 0x97EE, 0x97EF, 0x97F0, 0x97F1, 0x97F2, 0x97F4, 0x97F7, 0x97F8, 0x97F9, 0x97FA, 0x97FB, 0x97FC, 0x97FD, 0x97FE, 0x97FF, 0x9800, 0x9801, 0x9802, 0x9803, 0x9804, 0x9805, 0x9806, 0x9807, 0x9808, 0x9809, 0x980A, 0x980B, 0x980C, 0x980D, + 0x980E, 0x603C, 0x605D, 0x605A, 0x6067, 0x6041, 0x6059, 0x6063, 0x60AB, 0x6106, 0x610D, 0x615D, 0x61A9, 0x619D, 0x61CB, 0x61D1, 0x6206, 0x8080, 0x807F, 0x6C93, 0x6CF6, 0x6DFC, 0x77F6, 0x77F8, 0x7800, 0x7809, 0x7817, 0x7818, 0x7811, 0x65AB, 0x782D, 0x781C, 0x781D, 0x7839, 0x783A, 0x783B, 0x781F, 0x783C, 0x7825, 0x782C, 0x7823, 0x7829, 0x784E, 0x786D, 0x7856, 0x7857, 0x7826, 0x7850, 0x7847, 0x784C, 0x786A, 0x789B, 0x7893, 0x789A, 0x7887, 0x789C, 0x78A1, 0x78A3, 0x78B2, 0x78B9, 0x78A5, 0x78D4, 0x78D9, 0x78C9, 0x78EC, 0x78F2, 0x7905, 0x78F4, 0x7913, 0x7924, 0x791E, 0x7934, 0x9F9B, 0x9EF9, 0x9EFB, 0x9EFC, 0x76F1, 0x7704, 0x770D, 0x76F9, 0x7707, 0x7708, 0x771A, 0x7722, 0x7719, 0x772D, 0x7726, 0x7735, 0x7738, 0x7750, 0x7751, 0x7747, 0x7743, 0x775A, 0x7768, + 0x980F, 0x9810, 0x9811, 0x9812, 0x9813, 0x9814, 0x9815, 0x9816, 0x9817, 0x9818, 0x9819, 0x981A, 0x981B, 0x981C, 0x981D, 0x981E, 0x981F, 0x9820, 0x9821, 0x9822, 0x9823, 0x9824, 0x9825, 0x9826, 0x9827, 0x9828, 0x9829, 0x982A, 0x982B, 0x982C, 0x982D, 0x982E, 0x982F, 0x9830, 0x9831, 0x9832, 0x9833, 0x9834, 0x9835, 0x9836, 0x9837, 0x9838, 0x9839, 0x983A, 0x983B, 0x983C, 0x983D, 0x983E, 0x983F, 0x9840, 0x9841, 0x9842, 0x9843, 0x9844, 0x9845, 0x9846, 0x9847, 0x9848, 0x9849, 0x984A, 0x984B, 0x984C, 0x984D, 0x984E, 0x984F, 0x9850, 0x9851, 0x9852, 0x9853, 0x9854, 0x9855, 0x9856, 0x9857, 0x9858, 0x9859, 0x985A, 0x985B, 0x985C, 0x985D, 0x985E, 0x985F, 0x9860, 0x9861, 0x9862, 0x9863, 0x9864, 0x9865, 0x9866, 0x9867, 0x9868, 0x9869, 0x986A, 0x986B, 0x986C, 0x986D, + 0x986E, 0x7762, 0x7765, 0x777F, 0x778D, 0x777D, 0x7780, 0x778C, 0x7791, 0x779F, 0x77A0, 0x77B0, 0x77B5, 0x77BD, 0x753A, 0x7540, 0x754E, 0x754B, 0x7548, 0x755B, 0x7572, 0x7579, 0x7583, 0x7F58, 0x7F61, 0x7F5F, 0x8A48, 0x7F68, 0x7F74, 0x7F71, 0x7F79, 0x7F81, 0x7F7E, 0x76CD, 0x76E5, 0x8832, 0x9485, 0x9486, 0x9487, 0x948B, 0x948A, 0x948C, 0x948D, 0x948F, 0x9490, 0x9494, 0x9497, 0x9495, 0x949A, 0x949B, 0x949C, 0x94A3, 0x94A4, 0x94AB, 0x94AA, 0x94AD, 0x94AC, 0x94AF, 0x94B0, 0x94B2, 0x94B4, 0x94B6, 0x94B7, 0x94B8, 0x94B9, 0x94BA, 0x94BC, 0x94BD, 0x94BF, 0x94C4, 0x94C8, 0x94C9, 0x94CA, 0x94CB, 0x94CC, 0x94CD, 0x94CE, 0x94D0, 0x94D1, 0x94D2, 0x94D5, 0x94D6, 0x94D7, 0x94D9, 0x94D8, 0x94DB, 0x94DE, 0x94DF, 0x94E0, 0x94E2, 0x94E4, 0x94E5, 0x94E7, 0x94E8, 0x94EA, + 0x986F, 0x9870, 0x9871, 0x9872, 0x9873, 0x9874, 0x988B, 0x988E, 0x9892, 0x9895, 0x9899, 0x98A3, 0x98A8, 0x98A9, 0x98AA, 0x98AB, 0x98AC, 0x98AD, 0x98AE, 0x98AF, 0x98B0, 0x98B1, 0x98B2, 0x98B3, 0x98B4, 0x98B5, 0x98B6, 0x98B7, 0x98B8, 0x98B9, 0x98BA, 0x98BB, 0x98BC, 0x98BD, 0x98BE, 0x98BF, 0x98C0, 0x98C1, 0x98C2, 0x98C3, 0x98C4, 0x98C5, 0x98C6, 0x98C7, 0x98C8, 0x98C9, 0x98CA, 0x98CB, 0x98CC, 0x98CD, 0x98CF, 0x98D0, 0x98D4, 0x98D6, 0x98D7, 0x98DB, 0x98DC, 0x98DD, 0x98E0, 0x98E1, 0x98E2, 0x98E3, 0x98E4, 0x98E5, 0x98E6, 0x98E9, 0x98EA, 0x98EB, 0x98EC, 0x98ED, 0x98EE, 0x98EF, 0x98F0, 0x98F1, 0x98F2, 0x98F3, 0x98F4, 0x98F5, 0x98F6, 0x98F7, 0x98F8, 0x98F9, 0x98FA, 0x98FB, 0x98FC, 0x98FD, 0x98FE, 0x98FF, 0x9900, 0x9901, 0x9902, 0x9903, 0x9904, 0x9905, 0x9906, + 0x9907, 0x94E9, 0x94EB, 0x94EE, 0x94EF, 0x94F3, 0x94F4, 0x94F5, 0x94F7, 0x94F9, 0x94FC, 0x94FD, 0x94FF, 0x9503, 0x9502, 0x9506, 0x9507, 0x9509, 0x950A, 0x950D, 0x950E, 0x950F, 0x9512, 0x9513, 0x9514, 0x9515, 0x9516, 0x9518, 0x951B, 0x951D, 0x951E, 0x951F, 0x9522, 0x952A, 0x952B, 0x9529, 0x952C, 0x9531, 0x9532, 0x9534, 0x9536, 0x9537, 0x9538, 0x953C, 0x953E, 0x953F, 0x9542, 0x9535, 0x9544, 0x9545, 0x9546, 0x9549, 0x954C, 0x954E, 0x954F, 0x9552, 0x9553, 0x9554, 0x9556, 0x9557, 0x9558, 0x9559, 0x955B, 0x955E, 0x955F, 0x955D, 0x9561, 0x9562, 0x9564, 0x9565, 0x9566, 0x9567, 0x9568, 0x9569, 0x956A, 0x956B, 0x956C, 0x956F, 0x9571, 0x9572, 0x9573, 0x953A, 0x77E7, 0x77EC, 0x96C9, 0x79D5, 0x79ED, 0x79E3, 0x79EB, 0x7A06, 0x5D47, 0x7A03, 0x7A02, 0x7A1E, 0x7A14, + 0x9908, 0x9909, 0x990A, 0x990B, 0x990C, 0x990E, 0x990F, 0x9911, 0x9912, 0x9913, 0x9914, 0x9915, 0x9916, 0x9917, 0x9918, 0x9919, 0x991A, 0x991B, 0x991C, 0x991D, 0x991E, 0x991F, 0x9920, 0x9921, 0x9922, 0x9923, 0x9924, 0x9925, 0x9926, 0x9927, 0x9928, 0x9929, 0x992A, 0x992B, 0x992C, 0x992D, 0x992F, 0x9930, 0x9931, 0x9932, 0x9933, 0x9934, 0x9935, 0x9936, 0x9937, 0x9938, 0x9939, 0x993A, 0x993B, 0x993C, 0x993D, 0x993E, 0x993F, 0x9940, 0x9941, 0x9942, 0x9943, 0x9944, 0x9945, 0x9946, 0x9947, 0x9948, 0x9949, 0x994A, 0x994B, 0x994C, 0x994D, 0x994E, 0x994F, 0x9950, 0x9951, 0x9952, 0x9953, 0x9956, 0x9957, 0x9958, 0x9959, 0x995A, 0x995B, 0x995C, 0x995D, 0x995E, 0x995F, 0x9960, 0x9961, 0x9962, 0x9964, 0x9966, 0x9973, 0x9978, 0x9979, 0x997B, 0x997E, 0x9982, 0x9983, + 0x9989, 0x7A39, 0x7A37, 0x7A51, 0x9ECF, 0x99A5, 0x7A70, 0x7688, 0x768E, 0x7693, 0x7699, 0x76A4, 0x74DE, 0x74E0, 0x752C, 0x9E20, 0x9E22, 0x9E28, 0x9E29, 0x9E2A, 0x9E2B, 0x9E2C, 0x9E32, 0x9E31, 0x9E36, 0x9E38, 0x9E37, 0x9E39, 0x9E3A, 0x9E3E, 0x9E41, 0x9E42, 0x9E44, 0x9E46, 0x9E47, 0x9E48, 0x9E49, 0x9E4B, 0x9E4C, 0x9E4E, 0x9E51, 0x9E55, 0x9E57, 0x9E5A, 0x9E5B, 0x9E5C, 0x9E5E, 0x9E63, 0x9E66, 0x9E67, 0x9E68, 0x9E69, 0x9E6A, 0x9E6B, 0x9E6C, 0x9E71, 0x9E6D, 0x9E73, 0x7592, 0x7594, 0x7596, 0x75A0, 0x759D, 0x75AC, 0x75A3, 0x75B3, 0x75B4, 0x75B8, 0x75C4, 0x75B1, 0x75B0, 0x75C3, 0x75C2, 0x75D6, 0x75CD, 0x75E3, 0x75E8, 0x75E6, 0x75E4, 0x75EB, 0x75E7, 0x7603, 0x75F1, 0x75FC, 0x75FF, 0x7610, 0x7600, 0x7605, 0x760C, 0x7617, 0x760A, 0x7625, 0x7618, 0x7615, 0x7619, + 0x998C, 0x998E, 0x999A, 0x999B, 0x999C, 0x999D, 0x999E, 0x999F, 0x99A0, 0x99A1, 0x99A2, 0x99A3, 0x99A4, 0x99A6, 0x99A7, 0x99A9, 0x99AA, 0x99AB, 0x99AC, 0x99AD, 0x99AE, 0x99AF, 0x99B0, 0x99B1, 0x99B2, 0x99B3, 0x99B4, 0x99B5, 0x99B6, 0x99B7, 0x99B8, 0x99B9, 0x99BA, 0x99BB, 0x99BC, 0x99BD, 0x99BE, 0x99BF, 0x99C0, 0x99C1, 0x99C2, 0x99C3, 0x99C4, 0x99C5, 0x99C6, 0x99C7, 0x99C8, 0x99C9, 0x99CA, 0x99CB, 0x99CC, 0x99CD, 0x99CE, 0x99CF, 0x99D0, 0x99D1, 0x99D2, 0x99D3, 0x99D4, 0x99D5, 0x99D6, 0x99D7, 0x99D8, 0x99D9, 0x99DA, 0x99DB, 0x99DC, 0x99DD, 0x99DE, 0x99DF, 0x99E0, 0x99E1, 0x99E2, 0x99E3, 0x99E4, 0x99E5, 0x99E6, 0x99E7, 0x99E8, 0x99E9, 0x99EA, 0x99EB, 0x99EC, 0x99ED, 0x99EE, 0x99EF, 0x99F0, 0x99F1, 0x99F2, 0x99F3, 0x99F4, 0x99F5, 0x99F6, 0x99F7, 0x99F8, + 0x99F9, 0x761B, 0x763C, 0x7622, 0x7620, 0x7640, 0x762D, 0x7630, 0x763F, 0x7635, 0x7643, 0x763E, 0x7633, 0x764D, 0x765E, 0x7654, 0x765C, 0x7656, 0x766B, 0x766F, 0x7FCA, 0x7AE6, 0x7A78, 0x7A79, 0x7A80, 0x7A86, 0x7A88, 0x7A95, 0x7AA6, 0x7AA0, 0x7AAC, 0x7AA8, 0x7AAD, 0x7AB3, 0x8864, 0x8869, 0x8872, 0x887D, 0x887F, 0x8882, 0x88A2, 0x88C6, 0x88B7, 0x88BC, 0x88C9, 0x88E2, 0x88CE, 0x88E3, 0x88E5, 0x88F1, 0x891A, 0x88FC, 0x88E8, 0x88FE, 0x88F0, 0x8921, 0x8919, 0x8913, 0x891B, 0x890A, 0x8934, 0x892B, 0x8936, 0x8941, 0x8966, 0x897B, 0x758B, 0x80E5, 0x76B2, 0x76B4, 0x77DC, 0x8012, 0x8014, 0x8016, 0x801C, 0x8020, 0x8022, 0x8025, 0x8026, 0x8027, 0x8029, 0x8028, 0x8031, 0x800B, 0x8035, 0x8043, 0x8046, 0x804D, 0x8052, 0x8069, 0x8071, 0x8983, 0x9878, 0x9880, 0x9883, + 0x99FA, 0x99FB, 0x99FC, 0x99FD, 0x99FE, 0x99FF, 0x9A00, 0x9A01, 0x9A02, 0x9A03, 0x9A04, 0x9A05, 0x9A06, 0x9A07, 0x9A08, 0x9A09, 0x9A0A, 0x9A0B, 0x9A0C, 0x9A0D, 0x9A0E, 0x9A0F, 0x9A10, 0x9A11, 0x9A12, 0x9A13, 0x9A14, 0x9A15, 0x9A16, 0x9A17, 0x9A18, 0x9A19, 0x9A1A, 0x9A1B, 0x9A1C, 0x9A1D, 0x9A1E, 0x9A1F, 0x9A20, 0x9A21, 0x9A22, 0x9A23, 0x9A24, 0x9A25, 0x9A26, 0x9A27, 0x9A28, 0x9A29, 0x9A2A, 0x9A2B, 0x9A2C, 0x9A2D, 0x9A2E, 0x9A2F, 0x9A30, 0x9A31, 0x9A32, 0x9A33, 0x9A34, 0x9A35, 0x9A36, 0x9A37, 0x9A38, 0x9A39, 0x9A3A, 0x9A3B, 0x9A3C, 0x9A3D, 0x9A3E, 0x9A3F, 0x9A40, 0x9A41, 0x9A42, 0x9A43, 0x9A44, 0x9A45, 0x9A46, 0x9A47, 0x9A48, 0x9A49, 0x9A4A, 0x9A4B, 0x9A4C, 0x9A4D, 0x9A4E, 0x9A4F, 0x9A50, 0x9A51, 0x9A52, 0x9A53, 0x9A54, 0x9A55, 0x9A56, 0x9A57, 0x9A58, + 0x9A59, 0x9889, 0x988C, 0x988D, 0x988F, 0x9894, 0x989A, 0x989B, 0x989E, 0x989F, 0x98A1, 0x98A2, 0x98A5, 0x98A6, 0x864D, 0x8654, 0x866C, 0x866E, 0x867F, 0x867A, 0x867C, 0x867B, 0x86A8, 0x868D, 0x868B, 0x86AC, 0x869D, 0x86A7, 0x86A3, 0x86AA, 0x8693, 0x86A9, 0x86B6, 0x86C4, 0x86B5, 0x86CE, 0x86B0, 0x86BA, 0x86B1, 0x86AF, 0x86C9, 0x86CF, 0x86B4, 0x86E9, 0x86F1, 0x86F2, 0x86ED, 0x86F3, 0x86D0, 0x8713, 0x86DE, 0x86F4, 0x86DF, 0x86D8, 0x86D1, 0x8703, 0x8707, 0x86F8, 0x8708, 0x870A, 0x870D, 0x8709, 0x8723, 0x873B, 0x871E, 0x8725, 0x872E, 0x871A, 0x873E, 0x8748, 0x8734, 0x8731, 0x8729, 0x8737, 0x873F, 0x8782, 0x8722, 0x877D, 0x877E, 0x877B, 0x8760, 0x8770, 0x874C, 0x876E, 0x878B, 0x8753, 0x8763, 0x877C, 0x8764, 0x8759, 0x8765, 0x8793, 0x87AF, 0x87A8, 0x87D2, + 0x9A5A, 0x9A5B, 0x9A5C, 0x9A5D, 0x9A5E, 0x9A5F, 0x9A60, 0x9A61, 0x9A62, 0x9A63, 0x9A64, 0x9A65, 0x9A66, 0x9A67, 0x9A68, 0x9A69, 0x9A6A, 0x9A6B, 0x9A72, 0x9A83, 0x9A89, 0x9A8D, 0x9A8E, 0x9A94, 0x9A95, 0x9A99, 0x9AA6, 0x9AA9, 0x9AAA, 0x9AAB, 0x9AAC, 0x9AAD, 0x9AAE, 0x9AAF, 0x9AB2, 0x9AB3, 0x9AB4, 0x9AB5, 0x9AB9, 0x9ABB, 0x9ABD, 0x9ABE, 0x9ABF, 0x9AC3, 0x9AC4, 0x9AC6, 0x9AC7, 0x9AC8, 0x9AC9, 0x9ACA, 0x9ACD, 0x9ACE, 0x9ACF, 0x9AD0, 0x9AD2, 0x9AD4, 0x9AD5, 0x9AD6, 0x9AD7, 0x9AD9, 0x9ADA, 0x9ADB, 0x9ADC, 0x9ADD, 0x9ADE, 0x9AE0, 0x9AE2, 0x9AE3, 0x9AE4, 0x9AE5, 0x9AE7, 0x9AE8, 0x9AE9, 0x9AEA, 0x9AEC, 0x9AEE, 0x9AF0, 0x9AF1, 0x9AF2, 0x9AF3, 0x9AF4, 0x9AF5, 0x9AF6, 0x9AF7, 0x9AF8, 0x9AFA, 0x9AFC, 0x9AFD, 0x9AFE, 0x9AFF, 0x9B00, 0x9B01, 0x9B02, 0x9B04, 0x9B05, + 0x9B06, 0x87C6, 0x8788, 0x8785, 0x87AD, 0x8797, 0x8783, 0x87AB, 0x87E5, 0x87AC, 0x87B5, 0x87B3, 0x87CB, 0x87D3, 0x87BD, 0x87D1, 0x87C0, 0x87CA, 0x87DB, 0x87EA, 0x87E0, 0x87EE, 0x8816, 0x8813, 0x87FE, 0x880A, 0x881B, 0x8821, 0x8839, 0x883C, 0x7F36, 0x7F42, 0x7F44, 0x7F45, 0x8210, 0x7AFA, 0x7AFD, 0x7B08, 0x7B03, 0x7B04, 0x7B15, 0x7B0A, 0x7B2B, 0x7B0F, 0x7B47, 0x7B38, 0x7B2A, 0x7B19, 0x7B2E, 0x7B31, 0x7B20, 0x7B25, 0x7B24, 0x7B33, 0x7B3E, 0x7B1E, 0x7B58, 0x7B5A, 0x7B45, 0x7B75, 0x7B4C, 0x7B5D, 0x7B60, 0x7B6E, 0x7B7B, 0x7B62, 0x7B72, 0x7B71, 0x7B90, 0x7BA6, 0x7BA7, 0x7BB8, 0x7BAC, 0x7B9D, 0x7BA8, 0x7B85, 0x7BAA, 0x7B9C, 0x7BA2, 0x7BAB, 0x7BB4, 0x7BD1, 0x7BC1, 0x7BCC, 0x7BDD, 0x7BDA, 0x7BE5, 0x7BE6, 0x7BEA, 0x7C0C, 0x7BFE, 0x7BFC, 0x7C0F, 0x7C16, 0x7C0B, + 0x9B07, 0x9B09, 0x9B0A, 0x9B0B, 0x9B0C, 0x9B0D, 0x9B0E, 0x9B10, 0x9B11, 0x9B12, 0x9B14, 0x9B15, 0x9B16, 0x9B17, 0x9B18, 0x9B19, 0x9B1A, 0x9B1B, 0x9B1C, 0x9B1D, 0x9B1E, 0x9B20, 0x9B21, 0x9B22, 0x9B24, 0x9B25, 0x9B26, 0x9B27, 0x9B28, 0x9B29, 0x9B2A, 0x9B2B, 0x9B2C, 0x9B2D, 0x9B2E, 0x9B30, 0x9B31, 0x9B33, 0x9B34, 0x9B35, 0x9B36, 0x9B37, 0x9B38, 0x9B39, 0x9B3A, 0x9B3D, 0x9B3E, 0x9B3F, 0x9B40, 0x9B46, 0x9B4A, 0x9B4B, 0x9B4C, 0x9B4E, 0x9B50, 0x9B52, 0x9B53, 0x9B55, 0x9B56, 0x9B57, 0x9B58, 0x9B59, 0x9B5A, 0x9B5B, 0x9B5C, 0x9B5D, 0x9B5E, 0x9B5F, 0x9B60, 0x9B61, 0x9B62, 0x9B63, 0x9B64, 0x9B65, 0x9B66, 0x9B67, 0x9B68, 0x9B69, 0x9B6A, 0x9B6B, 0x9B6C, 0x9B6D, 0x9B6E, 0x9B6F, 0x9B70, 0x9B71, 0x9B72, 0x9B73, 0x9B74, 0x9B75, 0x9B76, 0x9B77, 0x9B78, 0x9B79, 0x9B7A, + 0x9B7B, 0x7C1F, 0x7C2A, 0x7C26, 0x7C38, 0x7C41, 0x7C40, 0x81FE, 0x8201, 0x8202, 0x8204, 0x81EC, 0x8844, 0x8221, 0x8222, 0x8223, 0x822D, 0x822F, 0x8228, 0x822B, 0x8238, 0x823B, 0x8233, 0x8234, 0x823E, 0x8244, 0x8249, 0x824B, 0x824F, 0x825A, 0x825F, 0x8268, 0x887E, 0x8885, 0x8888, 0x88D8, 0x88DF, 0x895E, 0x7F9D, 0x7F9F, 0x7FA7, 0x7FAF, 0x7FB0, 0x7FB2, 0x7C7C, 0x6549, 0x7C91, 0x7C9D, 0x7C9C, 0x7C9E, 0x7CA2, 0x7CB2, 0x7CBC, 0x7CBD, 0x7CC1, 0x7CC7, 0x7CCC, 0x7CCD, 0x7CC8, 0x7CC5, 0x7CD7, 0x7CE8, 0x826E, 0x66A8, 0x7FBF, 0x7FCE, 0x7FD5, 0x7FE5, 0x7FE1, 0x7FE6, 0x7FE9, 0x7FEE, 0x7FF3, 0x7CF8, 0x7D77, 0x7DA6, 0x7DAE, 0x7E47, 0x7E9B, 0x9EB8, 0x9EB4, 0x8D73, 0x8D84, 0x8D94, 0x8D91, 0x8DB1, 0x8D67, 0x8D6D, 0x8C47, 0x8C49, 0x914A, 0x9150, 0x914E, 0x914F, 0x9164, + 0x9B7C, 0x9B7D, 0x9B7E, 0x9B7F, 0x9B80, 0x9B81, 0x9B82, 0x9B83, 0x9B84, 0x9B85, 0x9B86, 0x9B87, 0x9B88, 0x9B89, 0x9B8A, 0x9B8B, 0x9B8C, 0x9B8D, 0x9B8E, 0x9B8F, 0x9B90, 0x9B91, 0x9B92, 0x9B93, 0x9B94, 0x9B95, 0x9B96, 0x9B97, 0x9B98, 0x9B99, 0x9B9A, 0x9B9B, 0x9B9C, 0x9B9D, 0x9B9E, 0x9B9F, 0x9BA0, 0x9BA1, 0x9BA2, 0x9BA3, 0x9BA4, 0x9BA5, 0x9BA6, 0x9BA7, 0x9BA8, 0x9BA9, 0x9BAA, 0x9BAB, 0x9BAC, 0x9BAD, 0x9BAE, 0x9BAF, 0x9BB0, 0x9BB1, 0x9BB2, 0x9BB3, 0x9BB4, 0x9BB5, 0x9BB6, 0x9BB7, 0x9BB8, 0x9BB9, 0x9BBA, 0x9BBB, 0x9BBC, 0x9BBD, 0x9BBE, 0x9BBF, 0x9BC0, 0x9BC1, 0x9BC2, 0x9BC3, 0x9BC4, 0x9BC5, 0x9BC6, 0x9BC7, 0x9BC8, 0x9BC9, 0x9BCA, 0x9BCB, 0x9BCC, 0x9BCD, 0x9BCE, 0x9BCF, 0x9BD0, 0x9BD1, 0x9BD2, 0x9BD3, 0x9BD4, 0x9BD5, 0x9BD6, 0x9BD7, 0x9BD8, 0x9BD9, 0x9BDA, + 0x9BDB, 0x9162, 0x9161, 0x9170, 0x9169, 0x916F, 0x917D, 0x917E, 0x9172, 0x9174, 0x9179, 0x918C, 0x9185, 0x9190, 0x918D, 0x9191, 0x91A2, 0x91A3, 0x91AA, 0x91AD, 0x91AE, 0x91AF, 0x91B5, 0x91B4, 0x91BA, 0x8C55, 0x9E7E, 0x8DB8, 0x8DEB, 0x8E05, 0x8E59, 0x8E69, 0x8DB5, 0x8DBF, 0x8DBC, 0x8DBA, 0x8DC4, 0x8DD6, 0x8DD7, 0x8DDA, 0x8DDE, 0x8DCE, 0x8DCF, 0x8DDB, 0x8DC6, 0x8DEC, 0x8DF7, 0x8DF8, 0x8DE3, 0x8DF9, 0x8DFB, 0x8DE4, 0x8E09, 0x8DFD, 0x8E14, 0x8E1D, 0x8E1F, 0x8E2C, 0x8E2E, 0x8E23, 0x8E2F, 0x8E3A, 0x8E40, 0x8E39, 0x8E35, 0x8E3D, 0x8E31, 0x8E49, 0x8E41, 0x8E42, 0x8E51, 0x8E52, 0x8E4A, 0x8E70, 0x8E76, 0x8E7C, 0x8E6F, 0x8E74, 0x8E85, 0x8E8F, 0x8E94, 0x8E90, 0x8E9C, 0x8E9E, 0x8C78, 0x8C82, 0x8C8A, 0x8C85, 0x8C98, 0x8C94, 0x659B, 0x89D6, 0x89DE, 0x89DA, 0x89DC, + 0x9BDC, 0x9BDD, 0x9BDE, 0x9BDF, 0x9BE0, 0x9BE1, 0x9BE2, 0x9BE3, 0x9BE4, 0x9BE5, 0x9BE6, 0x9BE7, 0x9BE8, 0x9BE9, 0x9BEA, 0x9BEB, 0x9BEC, 0x9BED, 0x9BEE, 0x9BEF, 0x9BF0, 0x9BF1, 0x9BF2, 0x9BF3, 0x9BF4, 0x9BF5, 0x9BF6, 0x9BF7, 0x9BF8, 0x9BF9, 0x9BFA, 0x9BFB, 0x9BFC, 0x9BFD, 0x9BFE, 0x9BFF, 0x9C00, 0x9C01, 0x9C02, 0x9C03, 0x9C04, 0x9C05, 0x9C06, 0x9C07, 0x9C08, 0x9C09, 0x9C0A, 0x9C0B, 0x9C0C, 0x9C0D, 0x9C0E, 0x9C0F, 0x9C10, 0x9C11, 0x9C12, 0x9C13, 0x9C14, 0x9C15, 0x9C16, 0x9C17, 0x9C18, 0x9C19, 0x9C1A, 0x9C1B, 0x9C1C, 0x9C1D, 0x9C1E, 0x9C1F, 0x9C20, 0x9C21, 0x9C22, 0x9C23, 0x9C24, 0x9C25, 0x9C26, 0x9C27, 0x9C28, 0x9C29, 0x9C2A, 0x9C2B, 0x9C2C, 0x9C2D, 0x9C2E, 0x9C2F, 0x9C30, 0x9C31, 0x9C32, 0x9C33, 0x9C34, 0x9C35, 0x9C36, 0x9C37, 0x9C38, 0x9C39, 0x9C3A, + 0x9C3B, 0x89E5, 0x89EB, 0x89EF, 0x8A3E, 0x8B26, 0x9753, 0x96E9, 0x96F3, 0x96EF, 0x9706, 0x9701, 0x9708, 0x970F, 0x970E, 0x972A, 0x972D, 0x9730, 0x973E, 0x9F80, 0x9F83, 0x9F85, 0x9F86, 0x9F87, 0x9F88, 0x9F89, 0x9F8A, 0x9F8C, 0x9EFE, 0x9F0B, 0x9F0D, 0x96B9, 0x96BC, 0x96BD, 0x96CE, 0x96D2, 0x77BF, 0x96E0, 0x928E, 0x92AE, 0x92C8, 0x933E, 0x936A, 0x93CA, 0x938F, 0x943E, 0x946B, 0x9C7F, 0x9C82, 0x9C85, 0x9C86, 0x9C87, 0x9C88, 0x7A23, 0x9C8B, 0x9C8E, 0x9C90, 0x9C91, 0x9C92, 0x9C94, 0x9C95, 0x9C9A, 0x9C9B, 0x9C9E, 0x9C9F, 0x9CA0, 0x9CA1, 0x9CA2, 0x9CA3, 0x9CA5, 0x9CA6, 0x9CA7, 0x9CA8, 0x9CA9, 0x9CAB, 0x9CAD, 0x9CAE, 0x9CB0, 0x9CB1, 0x9CB2, 0x9CB3, 0x9CB4, 0x9CB5, 0x9CB6, 0x9CB7, 0x9CBA, 0x9CBB, 0x9CBC, 0x9CBD, 0x9CC4, 0x9CC5, 0x9CC6, 0x9CC7, 0x9CCA, 0x9CCB, + 0x9C3C, 0x9C3D, 0x9C3E, 0x9C3F, 0x9C40, 0x9C41, 0x9C42, 0x9C43, 0x9C44, 0x9C45, 0x9C46, 0x9C47, 0x9C48, 0x9C49, 0x9C4A, 0x9C4B, 0x9C4C, 0x9C4D, 0x9C4E, 0x9C4F, 0x9C50, 0x9C51, 0x9C52, 0x9C53, 0x9C54, 0x9C55, 0x9C56, 0x9C57, 0x9C58, 0x9C59, 0x9C5A, 0x9C5B, 0x9C5C, 0x9C5D, 0x9C5E, 0x9C5F, 0x9C60, 0x9C61, 0x9C62, 0x9C63, 0x9C64, 0x9C65, 0x9C66, 0x9C67, 0x9C68, 0x9C69, 0x9C6A, 0x9C6B, 0x9C6C, 0x9C6D, 0x9C6E, 0x9C6F, 0x9C70, 0x9C71, 0x9C72, 0x9C73, 0x9C74, 0x9C75, 0x9C76, 0x9C77, 0x9C78, 0x9C79, 0x9C7A, 0x9C7B, 0x9C7D, 0x9C7E, 0x9C80, 0x9C83, 0x9C84, 0x9C89, 0x9C8A, 0x9C8C, 0x9C8F, 0x9C93, 0x9C96, 0x9C97, 0x9C98, 0x9C99, 0x9C9D, 0x9CAA, 0x9CAC, 0x9CAF, 0x9CB9, 0x9CBE, 0x9CBF, 0x9CC0, 0x9CC1, 0x9CC2, 0x9CC8, 0x9CC9, 0x9CD1, 0x9CD2, 0x9CDA, 0x9CDB, 0x9CE0, + 0x9CE1, 0x9CCC, 0x9CCD, 0x9CCE, 0x9CCF, 0x9CD0, 0x9CD3, 0x9CD4, 0x9CD5, 0x9CD7, 0x9CD8, 0x9CD9, 0x9CDC, 0x9CDD, 0x9CDF, 0x9CE2, 0x977C, 0x9785, 0x9791, 0x9792, 0x9794, 0x97AF, 0x97AB, 0x97A3, 0x97B2, 0x97B4, 0x9AB1, 0x9AB0, 0x9AB7, 0x9E58, 0x9AB6, 0x9ABA, 0x9ABC, 0x9AC1, 0x9AC0, 0x9AC5, 0x9AC2, 0x9ACB, 0x9ACC, 0x9AD1, 0x9B45, 0x9B43, 0x9B47, 0x9B49, 0x9B48, 0x9B4D, 0x9B51, 0x98E8, 0x990D, 0x992E, 0x9955, 0x9954, 0x9ADF, 0x9AE1, 0x9AE6, 0x9AEF, 0x9AEB, 0x9AFB, 0x9AED, 0x9AF9, 0x9B08, 0x9B0F, 0x9B13, 0x9B1F, 0x9B23, 0x9EBD, 0x9EBE, 0x7E3B, 0x9E82, 0x9E87, 0x9E88, 0x9E8B, 0x9E92, 0x93D6, 0x9E9D, 0x9E9F, 0x9EDB, 0x9EDC, 0x9EDD, 0x9EE0, 0x9EDF, 0x9EE2, 0x9EE9, 0x9EE7, 0x9EE5, 0x9EEA, 0x9EEF, 0x9F22, 0x9F2C, 0x9F2F, 0x9F39, 0x9F37, 0x9F3D, 0x9F3E, 0x9F44, + 0x9CE3, 0x9CE4, 0x9CE5, 0x9CE6, 0x9CE7, 0x9CE8, 0x9CE9, 0x9CEA, 0x9CEB, 0x9CEC, 0x9CED, 0x9CEE, 0x9CEF, 0x9CF0, 0x9CF1, 0x9CF2, 0x9CF3, 0x9CF4, 0x9CF5, 0x9CF6, 0x9CF7, 0x9CF8, 0x9CF9, 0x9CFA, 0x9CFB, 0x9CFC, 0x9CFD, 0x9CFE, 0x9CFF, 0x9D00, 0x9D01, 0x9D02, 0x9D03, 0x9D04, 0x9D05, 0x9D06, 0x9D07, 0x9D08, 0x9D09, 0x9D0A, 0x9D0B, 0x9D0C, 0x9D0D, 0x9D0E, 0x9D0F, 0x9D10, 0x9D11, 0x9D12, 0x9D13, 0x9D14, 0x9D15, 0x9D16, 0x9D17, 0x9D18, 0x9D19, 0x9D1A, 0x9D1B, 0x9D1C, 0x9D1D, 0x9D1E, 0x9D1F, 0x9D20, 0x9D21, 0x9D22, 0x9D23, 0x9D24, 0x9D25, 0x9D26, 0x9D27, 0x9D28, 0x9D29, 0x9D2A, 0x9D2B, 0x9D2C, 0x9D2D, 0x9D2E, 0x9D2F, 0x9D30, 0x9D31, 0x9D32, 0x9D33, 0x9D34, 0x9D35, 0x9D36, 0x9D37, 0x9D38, 0x9D39, 0x9D3A, 0x9D3B, 0x9D3C, 0x9D3D, 0x9D3E, 0x9D3F, 0x9D40, 0x9D41, + 0x9D42, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x9D43, 0x9D44, 0x9D45, 0x9D46, 0x9D47, 0x9D48, 0x9D49, 0x9D4A, 0x9D4B, 0x9D4C, 0x9D4D, 0x9D4E, 0x9D4F, 0x9D50, 0x9D51, 0x9D52, 0x9D53, 0x9D54, 0x9D55, 0x9D56, 0x9D57, 0x9D58, 0x9D59, 0x9D5A, 0x9D5B, 0x9D5C, 0x9D5D, 0x9D5E, 0x9D5F, 0x9D60, 0x9D61, 0x9D62, 0x9D63, 0x9D64, 0x9D65, 0x9D66, 0x9D67, 0x9D68, 0x9D69, 0x9D6A, 0x9D6B, 0x9D6C, 0x9D6D, 0x9D6E, 0x9D6F, 0x9D70, 0x9D71, 0x9D72, 0x9D73, 0x9D74, 0x9D75, 0x9D76, 0x9D77, 0x9D78, 0x9D79, 0x9D7A, 0x9D7B, 0x9D7C, 0x9D7D, 0x9D7E, 0x9D7F, 0x9D80, 0x9D81, 0x9D82, 0x9D83, 0x9D84, 0x9D85, 0x9D86, 0x9D87, 0x9D88, 0x9D89, 0x9D8A, 0x9D8B, 0x9D8C, 0x9D8D, 0x9D8E, 0x9D8F, 0x9D90, 0x9D91, 0x9D92, 0x9D93, 0x9D94, 0x9D95, 0x9D96, 0x9D97, 0x9D98, 0x9D99, 0x9D9A, 0x9D9B, 0x9D9C, 0x9D9D, 0x9D9E, 0x9D9F, 0x9DA0, 0x9DA1, + 0x9DA2, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x9DA3, 0x9DA4, 0x9DA5, 0x9DA6, 0x9DA7, 0x9DA8, 0x9DA9, 0x9DAA, 0x9DAB, 0x9DAC, 0x9DAD, 0x9DAE, 0x9DAF, 0x9DB0, 0x9DB1, 0x9DB2, 0x9DB3, 0x9DB4, 0x9DB5, 0x9DB6, 0x9DB7, 0x9DB8, 0x9DB9, 0x9DBA, 0x9DBB, 0x9DBC, 0x9DBD, 0x9DBE, 0x9DBF, 0x9DC0, 0x9DC1, 0x9DC2, 0x9DC3, 0x9DC4, 0x9DC5, 0x9DC6, 0x9DC7, 0x9DC8, 0x9DC9, 0x9DCA, 0x9DCB, 0x9DCC, 0x9DCD, 0x9DCE, 0x9DCF, 0x9DD0, 0x9DD1, 0x9DD2, 0x9DD3, 0x9DD4, 0x9DD5, 0x9DD6, 0x9DD7, 0x9DD8, 0x9DD9, 0x9DDA, 0x9DDB, 0x9DDC, 0x9DDD, 0x9DDE, 0x9DDF, 0x9DE0, 0x9DE1, 0x9DE2, 0x9DE3, 0x9DE4, 0x9DE5, 0x9DE6, 0x9DE7, 0x9DE8, 0x9DE9, 0x9DEA, 0x9DEB, 0x9DEC, 0x9DED, 0x9DEE, 0x9DEF, 0x9DF0, 0x9DF1, 0x9DF2, 0x9DF3, 0x9DF4, 0x9DF5, 0x9DF6, 0x9DF7, 0x9DF8, 0x9DF9, 0x9DFA, 0x9DFB, 0x9DFC, 0x9DFD, 0x9DFE, 0x9DFF, 0x9E00, 0x9E01, + 0x9E02, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x9E03, 0x9E04, 0x9E05, 0x9E06, 0x9E07, 0x9E08, 0x9E09, 0x9E0A, 0x9E0B, 0x9E0C, 0x9E0D, 0x9E0E, 0x9E0F, 0x9E10, 0x9E11, 0x9E12, 0x9E13, 0x9E14, 0x9E15, 0x9E16, 0x9E17, 0x9E18, 0x9E19, 0x9E1A, 0x9E1B, 0x9E1C, 0x9E1D, 0x9E1E, 0x9E24, 0x9E27, 0x9E2E, 0x9E30, 0x9E34, 0x9E3B, 0x9E3C, 0x9E40, 0x9E4D, 0x9E50, 0x9E52, 0x9E53, 0x9E54, 0x9E56, 0x9E59, 0x9E5D, 0x9E5F, 0x9E60, 0x9E61, 0x9E62, 0x9E65, 0x9E6E, 0x9E6F, 0x9E72, 0x9E74, 0x9E75, 0x9E76, 0x9E77, 0x9E78, 0x9E79, 0x9E7A, 0x9E7B, 0x9E7C, 0x9E7D, 0x9E80, 0x9E81, 0x9E83, 0x9E84, 0x9E85, 0x9E86, 0x9E89, 0x9E8A, 0x9E8C, 0x9E8D, 0x9E8E, 0x9E8F, 0x9E90, 0x9E91, 0x9E94, 0x9E95, 0x9E96, 0x9E97, 0x9E98, 0x9E99, 0x9E9A, 0x9E9B, 0x9E9C, 0x9E9E, 0x9EA0, 0x9EA1, 0x9EA2, 0x9EA3, 0x9EA4, 0x9EA5, 0x9EA7, 0x9EA8, 0x9EA9, + 0x9EAA, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x9EAB, 0x9EAC, 0x9EAD, 0x9EAE, 0x9EAF, 0x9EB0, 0x9EB1, 0x9EB2, 0x9EB3, 0x9EB5, 0x9EB6, 0x9EB7, 0x9EB9, 0x9EBA, 0x9EBC, 0x9EBF, 0x9EC0, 0x9EC1, 0x9EC2, 0x9EC3, 0x9EC5, 0x9EC6, 0x9EC7, 0x9EC8, 0x9ECA, 0x9ECB, 0x9ECC, 0x9ED0, 0x9ED2, 0x9ED3, 0x9ED5, 0x9ED6, 0x9ED7, 0x9ED9, 0x9EDA, 0x9EDE, 0x9EE1, 0x9EE3, 0x9EE4, 0x9EE6, 0x9EE8, 0x9EEB, 0x9EEC, 0x9EED, 0x9EEE, 0x9EF0, 0x9EF1, 0x9EF2, 0x9EF3, 0x9EF4, 0x9EF5, 0x9EF6, 0x9EF7, 0x9EF8, 0x9EFA, 0x9EFD, 0x9EFF, 0x9F00, 0x9F01, 0x9F02, 0x9F03, 0x9F04, 0x9F05, 0x9F06, 0x9F07, 0x9F08, 0x9F09, 0x9F0A, 0x9F0C, 0x9F0F, 0x9F11, 0x9F12, 0x9F14, 0x9F15, 0x9F16, 0x9F18, 0x9F1A, 0x9F1B, 0x9F1C, 0x9F1D, 0x9F1E, 0x9F1F, 0x9F21, 0x9F23, 0x9F24, 0x9F25, 0x9F26, 0x9F27, 0x9F28, 0x9F29, 0x9F2A, 0x9F2B, 0x9F2D, 0x9F2E, 0x9F30, + 0x9F31, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x9F32, 0x9F33, 0x9F34, 0x9F35, 0x9F36, 0x9F38, 0x9F3A, 0x9F3C, 0x9F3F, 0x9F40, 0x9F41, 0x9F42, 0x9F43, 0x9F45, 0x9F46, 0x9F47, 0x9F48, 0x9F49, 0x9F4A, 0x9F4B, 0x9F4C, 0x9F4D, 0x9F4E, 0x9F4F, 0x9F52, 0x9F53, 0x9F54, 0x9F55, 0x9F56, 0x9F57, 0x9F58, 0x9F59, 0x9F5A, 0x9F5B, 0x9F5C, 0x9F5D, 0x9F5E, 0x9F5F, 0x9F60, 0x9F61, 0x9F62, 0x9F63, 0x9F64, 0x9F65, 0x9F66, 0x9F67, 0x9F68, 0x9F69, 0x9F6A, 0x9F6B, 0x9F6C, 0x9F6D, 0x9F6E, 0x9F6F, 0x9F70, 0x9F71, 0x9F72, 0x9F73, 0x9F74, 0x9F75, 0x9F76, 0x9F77, 0x9F78, 0x9F79, 0x9F7A, 0x9F7B, 0x9F7C, 0x9F7D, 0x9F7E, 0x9F81, 0x9F82, 0x9F8D, 0x9F8E, 0x9F8F, 0x9F90, 0x9F91, 0x9F92, 0x9F93, 0x9F94, 0x9F95, 0x9F96, 0x9F97, 0x9F98, 0x9F9C, 0x9F9D, 0x9F9E, 0x9FA1, 0x9FA2, 0x9FA3, 0x9FA4, 0x9FA5, 0xF92C, 0xF979, 0xF995, 0xF9E7, + 0xF9F1, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0xFA0C, 0xFA0D, 0xFA0E, 0xFA0F, 0xFA11, 0xFA13, 0xFA14, 0xFA18, 0xFA1F, 0xFA20, 0xFA21, 0xFA23, 0xFA24, 0xFA27, 0xFA28, 0xFA29, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +} diff --git a/core/libs/prx/libSceCesCs/Cp949.hpp b/core/libs/prx/libSceCesCs/Cp949.hpp index ec0166ce13..54c8333dd4 100644 --- a/core/libs/prx/libSceCesCs/Cp949.hpp +++ b/core/libs/prx/libSceCesCs/Cp949.hpp @@ -38,7 +38,7 @@ inline constexpr uint16_t TO_UCS2[126 * 190] = { 0xC6A8, 0xC6AA, 0xC6AB, 0xC6AC, 0xC6AD, 0xC6AE, 0xC6AF, 0xC6B2, 0xC6B3, 0xC6B5, 0xC6B6, 0xC6B7, 0xC6BB, 0xC6BC, 0xC6BD, 0xC6BE, 0xC6BF, 0xC6C2, 0xC6C4, 0xC6C6, 0xC6C7, 0xC6C8, 0xC6C9, 0xC6CA, 0xC6CB, 0xC6CE, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC6CF, 0xC6D1, 0xC6D2, 0xC6D3, 0xC6D5, 0xC6D6, 0xC6D7, 0xC6D8, 0xC6D9, 0xC6DA, 0xC6DB, 0xC6DE, 0xC6DF, 0xC6E2, 0xC6E3, 0xC6E4, 0xC6E5, 0xC6E6, 0xC6E7, 0xC6EA, 0xC6EB, 0xC6ED, 0xC6EE, 0xC6EF, 0xC6F1, 0xC6F2, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC6F3, 0xC6F4, 0xC6F5, 0xC6F6, 0xC6F7, 0xC6FA, 0xC6FB, 0xC6FC, 0xC6FE, 0xC6FF, 0xC700, 0xC701, 0xC702, 0xC703, 0xC706, 0xC707, 0xC709, 0xC70A, 0xC70B, 0xC70D, 0xC70E, 0xC70F, 0xC710, 0xC711, 0xC712, 0xC713, 0xC716, 0xC718, 0xC71A, 0xC71B, 0xC71C, 0xC71D, 0xC71E, 0xC71F, 0xC722, 0xC723, 0xC725, 0xC726, 0xC727, 0xC729, 0xC72A, 0xC72B, 0xC72C, 0xC72D, 0xC72E, 0xC72F, 0xC732, 0xC734, 0xC736, 0xC738, 0xC739, 0xC73A, 0xC73B, 0xC73E, 0xC73F, 0xC741, 0xC742, 0xC743, 0xC745, 0xC746, 0xC747, 0xC748, 0xC749, 0xC74B, 0xC74E, 0xC750, 0xC759, 0xC75A, 0xC75B, 0xC75D, 0xC75E, 0xC75F, 0xC761, 0xC762, 0xC763, 0xC764, 0xC765, 0xC766, 0xC767, 0xC769, 0xC76A, 0xC76C, 0xC76D, 0xC76E, 0xC76F, 0xC770, 0xC771, 0xC772, 0xC773, 0xC776, 0xC777, 0xC779, 0xC77A, 0xC77B, 0xC77F, 0xC780, 0xC781, 0xC782, 0xC786, 0xC78B, 0xC78C, 0xC78D, 0xC78F, 0xC792, 0xC793, 0xC795, 0xC799, 0xC79B, 0xC79C, 0xC79D, 0xC79E, 0xC79F, 0xC7A2, 0xC7A7, 0xC7A8, 0xC7A9, 0xC7AA, 0xC7AB, 0xC7AE, 0xC7AF, 0xC7B1, 0xC7B2, 0xC7B3, 0xC7B5, 0xC7B6, 0xC7B7, 0xC7B8, 0xC7B9, 0xC7BA, 0xC7BB, 0xC7BE, 0xC7C2, 0xC7C3, 0xC7C4, 0xC7C5, 0xC7C6, 0xC7C7, 0xC7CA, 0xC7CB, 0xC7CD, 0xC7CF, 0xC7D1, 0xC7D2, 0xC7D3, 0xC7D4, 0xC7D5, 0xC7D6, 0xC7D7, 0xC7D9, 0xC7DA, 0xC7DB, 0xC7DC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC7DE, 0xC7DF, 0xC7E0, 0xC7E1, 0xC7E2, 0xC7E3, 0xC7E5, 0xC7E6, 0xC7E7, 0xC7E9, 0xC7EA, 0xC7EB, 0xC7ED, 0xC7EE, 0xC7EF, 0xC7F0, 0xC7F1, 0xC7F2, 0xC7F3, 0xC7F4, 0xC7F5, 0xC7F6, 0xC7F7, 0xC7F8, 0xC7F9, 0xC7FA, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC7FB, 0xC7FC, 0xC7FD, 0xC7FE, 0xC7FF, 0xC802, 0xC803, 0xC805, 0xC806, 0xC807, 0xC809, 0xC80B, 0xC80C, 0xC80D, 0xC80E, 0xC80F, 0xC812, 0xC814, 0xC817, 0xC818, 0xC819, 0xC81A, 0xC81B, 0xC81E, 0xC81F, 0xC821, 0xC822, 0xC823, 0xC825, 0xC826, 0xC827, 0xC828, 0xC829, 0xC82A, 0xC82B, 0xC82E, 0xC830, 0xC832, 0xC833, 0xC834, 0xC835, 0xC836, 0xC837, 0xC839, 0xC83A, 0xC83B, 0xC83D, 0xC83E, 0xC83F, 0xC841, 0xC842, 0xC843, 0xC844, 0xC845, 0xC846, 0xC847, 0xC84A, 0xC84B, 0xC84E, 0xC84F, 0xC850, 0xC851, 0xC852, 0xC853, 0xC855, 0xC856, 0xC857, 0xC858, 0xC859, 0xC85A, 0xC85B, 0xC85C, 0xC85D, 0xC85E, 0xC85F, 0xC860, 0xC861, 0xC862, 0xC863, 0xC864, 0xC865, 0xC866, 0xC867, 0xC868, 0xC869, 0xC86A, 0xC86B, 0xC86C, 0xC86D, 0xC86E, 0xC86F, 0xC872, 0xC873, 0xC875, 0xC876, 0xC877, 0xC879, 0xC87B, 0xC87C, 0xC87D, 0xC87E, 0xC87F, 0xC882, 0xC884, 0xC888, 0xC889, 0xC88A, 0xC88E, 0xC88F, 0xC890, 0xC891, 0xC892, 0xC893, 0xC895, 0xC896, 0xC897, 0xC898, 0xC899, 0xC89A, 0xC89B, 0xC89C, 0xC89E, 0xC8A0, 0xC8A2, 0xC8A3, 0xC8A4, 0xC8A5, 0xC8A6, 0xC8A7, 0xC8A9, 0xC8AA, 0xC8AB, 0xC8AC, 0xC8AD, 0xC8AE, 0xC8AF, 0xC8B0, 0xC8B1, 0xC8B2, 0xC8B3, 0xC8B4, 0xC8B5, 0xC8B6, 0xC8B7, 0xC8B8, 0xC8B9, 0xC8BA, 0xC8BB, 0xC8BE, 0xC8BF, 0xC8C0, 0xC8C1, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC8C2, 0xC8C3, 0xC8C5, 0xC8C6, 0xC8C7, 0xC8C9, 0xC8CA, 0xC8CB, 0xC8CD, 0xC8CE, 0xC8CF, 0xC8D0, 0xC8D1, 0xC8D2, 0xC8D3, 0xC8D6, 0xC8D8, 0xC8DA, 0xC8DB, 0xC8DC, 0xC8DD, 0xC8DE, 0xC8DF, 0xC8E2, 0xC8E3, 0xC8E5, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC8E6, 0xC8E7, 0xC8E8, 0xC8E9, 0xC8EA, 0xC8EB, 0xC8EC, 0xC8ED, 0xC8EE, 0xC8EF, 0xC8F0, 0xC8F1, 0xC8F2, 0xC8F3, 0xC8F4, 0xC8F6, 0xC8F7, 0xC8F8, 0xC8F9, 0xC8FA, 0xC8FB, 0xC8FE, 0xC8FF, 0xC901, 0xC902, 0xC903, 0xC907, 0xC908, 0xC909, 0xC90A, 0xC90B, 0xC90E, 0x3000, 0x3001, 0x3002, 0x00B7, 0x2025, 0x2026, 0x00A8, 0x3003, 0x00AD, 0x2015, 0x2225, 0xFF3C, 0x223C, 0x2018, 0x2019, 0x201C, 0x201D, 0x3014, 0x3015, 0x3008, 0x3009, 0x300A, 0x300B, 0x300C, 0x300D, 0x300E, 0x300F, 0x3010, 0x3011, 0x00B1, 0x00D7, 0x00F7, 0x2260, 0x2264, 0x2265, 0x221E, 0x2234, 0x00B0, 0x2032, 0x2033, 0x2103, 0x212B, 0xFFE0, 0xFFE1, 0xFFE5, 0x2642, 0x2640, 0x2220, 0x22A5, 0x2312, 0x2202, 0x2207, 0x2261, 0x2252, 0x00A7, 0x203B, 0x2606, 0x2605, 0x25CB, 0x25CF, 0x25CE, 0x25C7, 0x25C6, 0x25A1, 0x25A0, 0x25B3, 0x25B2, 0x25BD, 0x25BC, 0x2192, 0x2190, 0x2191, 0x2193, 0x2194, 0x3013, 0x226A, 0x226B, 0x221A, 0x223D, 0x221D, 0x2235, 0x222B, 0x222C, 0x2208, 0x220B, 0x2286, 0x2287, 0x2282, 0x2283, 0x222A, 0x2229, 0x2227, 0x2228, 0xFFE2, - 0xC910, 0xC912, 0xC913, 0xC914, 0xC915, 0xC916, 0xC917, 0xC919, 0xC91A, 0xC91B, 0xC91C, 0xC91D, 0xC91E, 0xC91F, 0xC920, 0xC921, 0xC922, 0xC923, 0xC924, 0xC925, 0xC926, 0xC927, 0xC928, 0xC929, 0xC92A, 0xC92B, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC92D, 0xC92E, 0xC92F, 0xC930, 0xC931, 0xC932, 0xC933, 0xC935, 0xC936, 0xC937, 0xC938, 0xC939, 0xC93A, 0xC93B, 0xC93C, 0xC93D, 0xC93E, 0xC93F, 0xC940, 0xC941, 0xC942, 0xC943, 0xC944, 0xC945, 0xC946, 0xC947, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC948, 0xC949, 0xC94A, 0xC94B, 0xC94C, 0xC94D, 0xC94E, 0xC94F, 0xC952, 0xC953, 0xC955, 0xC956, 0xC957, 0xC959, 0xC95A, 0xC95B, 0xC95C, 0xC95D, 0xC95E, 0xC95F, 0xC962, 0xC964, 0xC965, 0xC966, 0xC967, 0xC968, 0xC969, 0xC96A, 0xC96B, 0xC96D, 0xC96E, 0xC96F, 0x21D2, 0x21D4, 0x2200, 0x2203, 0x00B4, 0xFF5E, 0x02C7, 0x02D8, 0x02DD, 0x02DA, 0x02D9, 0x00B8, 0x02DB, 0x00A1, 0x00BF, 0x02D0, 0x222E, 0x2211, 0x220F, 0x00A4, 0x2109, 0x2030, 0x25C1, 0x25C0, 0x25B7, 0x25B6, 0x2664, 0x2660, 0x2661, 0x2665, 0x2667, 0x2663, 0x2299, 0x25C8, 0x25A3, 0x25D0, 0x25D1, 0x2592, 0x25A4, 0x25A5, 0x25A8, 0x25A7, 0x25A6, 0x25A9, 0x2668, 0x260F, 0x260E, 0x261C, 0x261E, 0x00B6, 0x2020, 0x2021, 0x2195, 0x2197, 0x2199, 0x2196, 0x2198, 0x266D, 0x2669, 0x266A, 0x266C, 0x327F, 0x321C, 0x2116, 0x33C7, 0x2122, 0x33C2, 0x33D8, 0x2121, 0x20AC, 0x00AE, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0xC910, 0xC912, 0xC913, 0xC914, 0xC915, 0xC916, 0xC917, 0xC919, 0xC91A, 0xC91B, 0xC91C, 0xC91D, 0xC91E, 0xC91F, 0xC920, 0xC921, 0xC922, 0xC923, 0xC924, 0xC925, 0xC926, 0xC927, 0xC928, 0xC929, 0xC92A, 0xC92B, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC92D, 0xC92E, 0xC92F, 0xC930, 0xC931, 0xC932, 0xC933, 0xC935, 0xC936, 0xC937, 0xC938, 0xC939, 0xC93A, 0xC93B, 0xC93C, 0xC93D, 0xC93E, 0xC93F, 0xC940, 0xC941, 0xC942, 0xC943, 0xC944, 0xC945, 0xC946, 0xC947, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC948, 0xC949, 0xC94A, 0xC94B, 0xC94C, 0xC94D, 0xC94E, 0xC94F, 0xC952, 0xC953, 0xC955, 0xC956, 0xC957, 0xC959, 0xC95A, 0xC95B, 0xC95C, 0xC95D, 0xC95E, 0xC95F, 0xC962, 0xC964, 0xC965, 0xC966, 0xC967, 0xC968, 0xC969, 0xC96A, 0xC96B, 0xC96D, 0xC96E, 0xC96F, 0x21D2, 0x21D4, 0x2200, 0x2203, 0x00B4, 0xFF5E, 0x02C7, 0x02D8, 0x02DD, 0x02DA, 0x02D9, 0x00B8, 0x02DB, 0x00A1, 0x00BF, 0x02D0, 0x222E, 0x2211, 0x220F, 0x00A4, 0x2109, 0x2030, 0x25C1, 0x25C0, 0x25B7, 0x25B6, 0x2664, 0x2660, 0x2661, 0x2665, 0x2667, 0x2663, 0x2299, 0x25C8, 0x25A3, 0x25D0, 0x25D1, 0x2592, 0x25A4, 0x25A5, 0x25A8, 0x25A7, 0x25A6, 0x25A9, 0x2668, 0x260F, 0x260E, 0x261C, 0x261E, 0x00B6, 0x2020, 0x2021, 0x2195, 0x2197, 0x2199, 0x2196, 0x2198, 0x266D, 0x2669, 0x266A, 0x266C, 0x327F, 0x321C, 0x2116, 0x33C7, 0x2122, 0x33C2, 0x33D8, 0x2121, 0x20AC, 0x00AE, 0x327E, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC971, 0xC972, 0xC973, 0xC975, 0xC976, 0xC977, 0xC978, 0xC979, 0xC97A, 0xC97B, 0xC97D, 0xC97E, 0xC97F, 0xC980, 0xC981, 0xC982, 0xC983, 0xC984, 0xC985, 0xC986, 0xC987, 0xC98A, 0xC98B, 0xC98D, 0xC98E, 0xC98F, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC991, 0xC992, 0xC993, 0xC994, 0xC995, 0xC996, 0xC997, 0xC99A, 0xC99C, 0xC99E, 0xC99F, 0xC9A0, 0xC9A1, 0xC9A2, 0xC9A3, 0xC9A4, 0xC9A5, 0xC9A6, 0xC9A7, 0xC9A8, 0xC9A9, 0xC9AA, 0xC9AB, 0xC9AC, 0xC9AD, 0xC9AE, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC9AF, 0xC9B0, 0xC9B1, 0xC9B2, 0xC9B3, 0xC9B4, 0xC9B5, 0xC9B6, 0xC9B7, 0xC9B8, 0xC9B9, 0xC9BA, 0xC9BB, 0xC9BC, 0xC9BD, 0xC9BE, 0xC9BF, 0xC9C2, 0xC9C3, 0xC9C5, 0xC9C6, 0xC9C9, 0xC9CB, 0xC9CC, 0xC9CD, 0xC9CE, 0xC9CF, 0xC9D2, 0xC9D4, 0xC9D7, 0xC9D8, 0xC9DB, 0xFF01, 0xFF02, 0xFF03, 0xFF04, 0xFF05, 0xFF06, 0xFF07, 0xFF08, 0xFF09, 0xFF0A, 0xFF0B, 0xFF0C, 0xFF0D, 0xFF0E, 0xFF0F, 0xFF10, 0xFF11, 0xFF12, 0xFF13, 0xFF14, 0xFF15, 0xFF16, 0xFF17, 0xFF18, 0xFF19, 0xFF1A, 0xFF1B, 0xFF1C, 0xFF1D, 0xFF1E, 0xFF1F, 0xFF20, 0xFF21, 0xFF22, 0xFF23, 0xFF24, 0xFF25, 0xFF26, 0xFF27, 0xFF28, 0xFF29, 0xFF2A, 0xFF2B, 0xFF2C, 0xFF2D, 0xFF2E, 0xFF2F, 0xFF30, 0xFF31, 0xFF32, 0xFF33, 0xFF34, 0xFF35, 0xFF36, 0xFF37, 0xFF38, 0xFF39, 0xFF3A, 0xFF3B, 0xFFE6, 0xFF3D, 0xFF3E, 0xFF3F, 0xFF40, 0xFF41, 0xFF42, 0xFF43, 0xFF44, 0xFF45, 0xFF46, 0xFF47, 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFF4C, 0xFF4D, 0xFF4E, 0xFF4F, 0xFF50, 0xFF51, 0xFF52, 0xFF53, 0xFF54, 0xFF55, 0xFF56, 0xFF57, 0xFF58, 0xFF59, 0xFF5A, 0xFF5B, 0xFF5C, 0xFF5D, 0xFFE3, 0xC9DE, 0xC9DF, 0xC9E1, 0xC9E3, 0xC9E5, 0xC9E6, 0xC9E8, 0xC9E9, 0xC9EA, 0xC9EB, 0xC9EE, 0xC9F2, 0xC9F3, 0xC9F4, 0xC9F5, 0xC9F6, 0xC9F7, 0xC9FA, 0xC9FB, 0xC9FD, 0xC9FE, 0xC9FF, 0xCA01, 0xCA02, 0xCA03, 0xCA04, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xCA05, 0xCA06, 0xCA07, 0xCA0A, 0xCA0E, 0xCA0F, 0xCA10, 0xCA11, 0xCA12, 0xCA13, 0xCA15, 0xCA16, 0xCA17, 0xCA19, 0xCA1A, 0xCA1B, 0xCA1C, 0xCA1D, 0xCA1E, 0xCA1F, 0xCA20, 0xCA21, 0xCA22, 0xCA23, 0xCA24, 0xCA25, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xCA26, 0xCA27, 0xCA28, 0xCA2A, 0xCA2B, 0xCA2C, 0xCA2D, 0xCA2E, 0xCA2F, 0xCA30, 0xCA31, 0xCA32, 0xCA33, 0xCA34, 0xCA35, 0xCA36, 0xCA37, 0xCA38, 0xCA39, 0xCA3A, 0xCA3B, 0xCA3C, 0xCA3D, 0xCA3E, 0xCA3F, 0xCA40, 0xCA41, 0xCA42, 0xCA43, 0xCA44, 0xCA45, 0xCA46, 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3138, 0x3139, 0x313A, 0x313B, 0x313C, 0x313D, 0x313E, 0x313F, 0x3140, 0x3141, 0x3142, 0x3143, 0x3144, 0x3145, 0x3146, 0x3147, 0x3148, 0x3149, 0x314A, 0x314B, 0x314C, 0x314D, 0x314E, 0x314F, 0x3150, 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x3158, 0x3159, 0x315A, 0x315B, 0x315C, 0x315D, 0x315E, 0x315F, 0x3160, 0x3161, 0x3162, 0x3163, 0x3164, 0x3165, 0x3166, 0x3167, 0x3168, 0x3169, 0x316A, 0x316B, 0x316C, 0x316D, 0x316E, 0x316F, 0x3170, 0x3171, 0x3172, 0x3173, 0x3174, 0x3175, 0x3176, 0x3177, 0x3178, 0x3179, 0x317A, 0x317B, 0x317C, 0x317D, 0x317E, 0x317F, 0x3180, 0x3181, 0x3182, 0x3183, 0x3184, 0x3185, 0x3186, 0x3187, 0x3188, 0x3189, 0x318A, 0x318B, 0x318C, 0x318D, 0x318E, 0xCA47, 0xCA48, 0xCA49, 0xCA4A, 0xCA4B, 0xCA4E, 0xCA4F, 0xCA51, 0xCA52, 0xCA53, 0xCA55, 0xCA56, 0xCA57, 0xCA58, 0xCA59, 0xCA5A, 0xCA5B, 0xCA5E, 0xCA62, 0xCA63, 0xCA64, 0xCA65, 0xCA66, 0xCA67, 0xCA69, 0xCA6A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xCA6B, 0xCA6C, 0xCA6D, 0xCA6E, 0xCA6F, 0xCA70, 0xCA71, 0xCA72, 0xCA73, 0xCA74, 0xCA75, 0xCA76, 0xCA77, 0xCA78, 0xCA79, 0xCA7A, 0xCA7B, 0xCA7C, 0xCA7E, 0xCA7F, 0xCA80, 0xCA81, 0xCA82, 0xCA83, 0xCA85, 0xCA86, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xCA87, 0xCA88, 0xCA89, 0xCA8A, 0xCA8B, 0xCA8C, 0xCA8D, 0xCA8E, 0xCA8F, 0xCA90, 0xCA91, 0xCA92, 0xCA93, 0xCA94, 0xCA95, 0xCA96, 0xCA97, 0xCA99, 0xCA9A, 0xCA9B, 0xCA9C, 0xCA9D, 0xCA9E, 0xCA9F, 0xCAA0, 0xCAA1, 0xCAA2, 0xCAA3, 0xCAA4, 0xCAA5, 0xCAA6, 0xCAA7, 0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177, 0x2178, 0x2179, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2160, 0x2161, 0x2162, 0x2163, 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, 0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, 0x03C9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, diff --git a/core/libs/prx/libSceCesCs/Export.cpp b/core/libs/prx/libSceCesCs/Export.cpp index 25dcb94514..e396f4a21d 100644 --- a/core/libs/prx/libSceCesCs/Export.cpp +++ b/core/libs/prx/libSceCesCs/Export.cpp @@ -1,39 +1,50 @@ +#include "prx/libSceCesCs/include/Api.hpp" #include #include #include +#include #include +#include +#include +#include #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "JisX0208.hpp" #include "JisX0212.hpp" +#include "Cp936.hpp" +#include "Gb18030.hpp" #include "Cp949.hpp" #include "Cp950.hpp" +#include "SingleByte.hpp" namespace { constexpr int CES_ERROR_INVALID_PARAMETER = static_cast(0x805C0001); +constexpr int CES_ERROR_INVALID_PROFILE = static_cast(0x805C0004); constexpr int CES_ERROR_INVALID_SRC_BUFFER = static_cast(0x805C0010); constexpr int CES_ERROR_SRC_BUFFER_END = static_cast(0x805C0011); constexpr int CES_ERROR_INVALID_ENCODE = static_cast(0x805C0014); +constexpr int CES_ERROR_ILLEGAL_CODE = static_cast(0x805C0015); constexpr int CES_ERROR_UNASSIGNED_CODE = static_cast(0x805C0020); +constexpr int CES_ERROR_UNSUPPORTED_CODE = static_cast(0x805C0024); constexpr int CES_ERROR_INVALID_DST_BUFFER = static_cast(0x805C0030); constexpr int CES_ERROR_DST_BUFFER_END = static_cast(0x805C0031); -constexpr uint8_t PROFILES[3] = {0, 1, 2}; -constexpr const uint8_t* CP1252_PROFILE = &PROFILES[0]; -constexpr const uint8_t* CP1251_PROFILE = &PROFILES[1]; -constexpr const uint8_t* CP1250_PROFILE = &PROFILES[2]; +constexpr uint8_t PROFILES[3 + SingleByte::Count][8] = {{0}, {1}, {2}}; +constexpr const uint8_t* CP1252_PROFILE = PROFILES[0]; +constexpr const uint8_t* CP1251_PROFILE = PROFILES[1]; +constexpr const uint8_t* CP1250_PROFILE = PROFILES[2]; constexpr uint16_t CP1252_HIGH[32] = { - 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, - 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178, + 0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0000, 0x017D, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0000, 0x017E, 0x0178, }; constexpr uint16_t CP1251_HIGH[128] = { 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, - 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0098, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, + 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, 0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, @@ -43,8 +54,8 @@ constexpr uint16_t CP1251_HIGH[128] = { }; constexpr uint16_t CP1250_HIGH[128] = { - 0x20AC, 0x0081, 0x201A, 0x0083, 0x201E, 0x2026, 0x2020, 0x2021, 0x0088, 0x2030, 0x0160, 0x2039, 0x015A, 0x0164, 0x017D, 0x0179, - 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0098, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A, + 0x20AC, 0x0000, 0x201A, 0x0000, 0x201E, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0160, 0x2039, 0x015A, 0x0164, 0x017D, 0x0179, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A, 0x00A0, 0x02C7, 0x02D8, 0x0141, 0x00A4, 0x0104, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x015E, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x017B, 0x00B0, 0x00B1, 0x02DB, 0x0142, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x0105, 0x015F, 0x00BB, 0x013D, 0x02DD, 0x013E, 0x017C, 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, @@ -59,21 +70,33 @@ constexpr uint16_t CP932_IBM_HEAD[28] = { 0xFFE2, 0xFFE4, 0xFF07, 0xFF02, 0x3231, 0x2116, 0x2121, 0x2235, }; -uint32_t sbcToUnicode(const uint8_t* profile, uint8_t sbc) { - if (profile == CP1251_PROFILE || profile == CP1250_PROFILE) { - const uint16_t* high = profile == CP1251_PROFILE ? CP1251_HIGH : CP1250_HIGH; - return sbc >= 0x80 ? high[sbc - 0x80] : sbc; +const uint8_t* singleByteProfile(SingleByte::Table table) { + return PROFILES[3 + table]; +} + +const uint16_t* sbcHighTable(const void* profile) { + if (profile == CP1251_PROFILE) return CP1251_HIGH; + if (profile == CP1250_PROFILE) return CP1250_HIGH; + for (std::size_t table = 0; table < SingleByte::Count; ++table) { + if (profile == singleByteProfile(static_cast(table))) return SingleByte::HIGH[table]; } - return sbc >= 0x80 && sbc < 0xA0 ? CP1252_HIGH[sbc - 0x80] : sbc; + return nullptr; +} + +bool sbcToUnicode(const uint8_t* profile, uint8_t sbc, uint32_t& code) { + code = sbc; + if (sbc < 0x80) return true; + if (const uint16_t* high = sbcHighTable(profile)) code = high[sbc - 0x80]; + else if (sbc < 0xA0) code = CP1252_HIGH[sbc - 0x80]; + return code != 0; } bool unicodeToSbc(const uint8_t* profile, uint32_t code, uint8_t& sbc) { - if (profile == CP1251_PROFILE || profile == CP1250_PROFILE) { + if (const uint16_t* high = sbcHighTable(profile)) { if (code < 0x80) { sbc = static_cast(code); return true; } - const uint16_t* high = profile == CP1251_PROFILE ? CP1251_HIGH : CP1250_HIGH; for (uint32_t i = 0; i < 128; ++i) { if (high[i] == code) { sbc = static_cast(0x80 + i); @@ -95,25 +118,32 @@ bool unicodeToSbc(const uint8_t* profile, uint32_t code, uint8_t& sbc) { return false; } -int decodeUtf8(const uint8_t* utf8, uint32_t utf8max, uint32_t& length, uint32_t& code) { +int decodeScalarUtf8(const uint8_t* utf8, uint32_t utf8max, uint32_t& length, uint32_t& code) { const uint8_t lead = utf8[0]; + length = 0; uint32_t minimum = 0; if (lead < 0x80) { length = 1; code = lead; return 0; } - if ((lead & 0xE0) == 0xC0) { length = 2; code = lead & 0x1F; minimum = 0x80; } - else if ((lead & 0xF0) == 0xE0) { length = 3; code = lead & 0x0F; minimum = 0x800; } - else if ((lead & 0xF8) == 0xF0) { length = 4; code = lead & 0x07; minimum = 0x10000; } - else return CES_ERROR_INVALID_ENCODE; - - for (uint32_t i = 1; i < length; ++i) { - if (i >= utf8max) return CES_ERROR_SRC_BUFFER_END; - if ((utf8[i] & 0xC0) != 0x80) return CES_ERROR_INVALID_ENCODE; + if (lead < 0xC0 || lead >= 0xFE) return CES_ERROR_INVALID_ENCODE; + if (lead < 0xE0) { length = 2; code = lead & 0x1F; minimum = 0x80; } + else if (lead < 0xF0) { length = 3; code = lead & 0x0F; minimum = 0x800; } + else if (lead < 0xF8) { length = 4; code = lead & 0x07; minimum = 0x10000; } + else if (lead < 0xFC) { length = 5; code = lead & 0x03; minimum = 0x200000; } + else { length = 6; code = lead & 0x01; minimum = 0x4000000; } + const bool truncated = utf8max < length; + const uint32_t present = truncated ? utf8max : length; + for (uint32_t i = 1; i < present; ++i) { + if ((utf8[i] & 0xC0) != 0x80) { + length = truncated ? i + 1 : i; + return CES_ERROR_INVALID_ENCODE; + } code = code << 6 | (utf8[i] & 0x3F); } - if (code < minimum || code > 0x10FFFF || (code >= 0xD800 && code <= 0xDFFF)) return CES_ERROR_INVALID_ENCODE; + if (truncated) return CES_ERROR_SRC_BUFFER_END; + if (code < minimum || (code >= 0xD800 && code <= 0xDFFF)) return CES_ERROR_ILLEGAL_CODE; return 0; } @@ -127,25 +157,345 @@ uint32_t encodeUtf8(uint32_t code, uint8_t* utf8) { utf8[1] = static_cast(0x80 | (code & 0x3F)); return 2; } - utf8[0] = static_cast(0xE0 | code >> 12); - utf8[1] = static_cast(0x80 | (code >> 6 & 0x3F)); - utf8[2] = static_cast(0x80 | (code & 0x3F)); - return 3; + if (code < 0x10000) { + utf8[0] = static_cast(0xE0 | code >> 12); + utf8[1] = static_cast(0x80 | (code >> 6 & 0x3F)); + utf8[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } + utf8[0] = static_cast(0xF0 | code >> 18); + utf8[1] = static_cast(0x80 | (code >> 12 & 0x3F)); + utf8[2] = static_cast(0x80 | (code >> 6 & 0x3F)); + utf8[3] = static_cast(0x80 | (code & 0x3F)); + return 4; } constexpr uint8_t EUC_JP_TAG[8] = {'E', 'U', 'C', 'J', 'P', '9', '3', '2'}; +constexpr uint8_t EUC_JP_X0208_TAG[8] = {'E', 'U', 'C', 'J', 'X', '2', '0', '8'}; +constexpr uint8_t EUC_JP_X0208_SS2_TAG[8] = {'E', 'U', 'C', 'J', 'X', 'S', 'S', '2'}; +constexpr uint8_t EUC_JP_X0208_SS3_TAG[8] = {'E', 'U', 'C', 'J', 'X', 'S', 'S', '3'}; constexpr uint8_t SJIS_TAG[8] = {'S', 'J', 'I', 'S', '1', '9', '9', '7'}; +constexpr uint8_t SJIS_X0208_TAG[8] = {'S', 'J', 'I', 'S', 'X', '2', '0', '8'}; + +bool hasTag(const void* profile, const uint8_t (&tag)[8]) { + return profile != nullptr && std::memcmp(profile, tag, sizeof(tag)) == 0; +} + +bool isEucJpX0208(const void* profile) { + return hasTag(profile, EUC_JP_X0208_TAG) || hasTag(profile, EUC_JP_X0208_SS2_TAG) || hasTag(profile, EUC_JP_X0208_SS3_TAG); +} bool isEucJp(const void* profile) { - return profile != nullptr && std::memcmp(profile, EUC_JP_TAG, sizeof(EUC_JP_TAG)) == 0; + return hasTag(profile, EUC_JP_TAG) || isEucJpX0208(profile); +} + +bool hasSs2(const void* profile) { + return !hasTag(profile, EUC_JP_X0208_TAG); +} + +bool hasSs3(const void* profile) { + return hasTag(profile, EUC_JP_X0208_SS3_TAG); +} + +bool isSjisX0208(const void* profile) { + return hasTag(profile, SJIS_X0208_TAG); } bool isSjis(const void* profile) { - return profile != nullptr && std::memcmp(profile, SJIS_TAG, sizeof(SJIS_TAG)) == 0; + return hasTag(profile, SJIS_TAG) || isSjisX0208(profile); +} + +struct JisChange { + uint16_t jis; + uint16_t code; +}; + +constexpr JisChange JIS_X0208_CHANGES[7] = {{0x213D, 0x2014}, {0x2141, 0x301C}, {0x2142, 0x2016}, {0x215D, 0x2212}, {0x2171, 0x00A2}, {0x2172, 0x00A3}, {0x224C, 0x00AC}}; +constexpr uint16_t JIS_X0208_BACKSLASH_CELL = 0x2140; +constexpr JisChange JIS_X0208_EUC_FALLBACKS[2] = {{0x216F, 0x00A5}, {0x2131, 0x203E}}; + +enum class JisStrict { None, Sjis, Euc }; + +constexpr uint32_t jisIndex(uint16_t jis) { + return ((jis >> 8) - 0x21) * 94 + (jis & 0xFF) - 0x21; +} + +bool isCp932ExtensionRow(uint32_t row) { + return row == 13 || (row >= 89 && row <= 92); +} + +uint16_t strictJisX0208(JisStrict strict, uint32_t index) { + if (isCp932ExtensionRow(index / 94 + 1)) return 0; + if (index == jisIndex(JIS_X0208_BACKSLASH_CELL)) return strict == JisStrict::Sjis ? 0x005C : 0xFF3C; + for (const JisChange& change : JIS_X0208_CHANGES) { + if (jisIndex(change.jis) == index) return change.code; + } + return JisX0208::TO_UCS2[index]; +} + +uint32_t jisX0208ToUnicode(JisStrict strict, uint32_t index) { + return strict == JisStrict::None ? JisX0208::TO_UCS2[index] : strictJisX0208(strict, index); +} + +uint32_t sjisX0208Single(uint8_t byte) { + return byte == 0x5C ? 0x00A5 : byte == 0x7E ? 0x203E : byte; +} + +constexpr uint8_t EUC_CN_TAG[8] = {'E', 'U', 'C', 'C', 'N', '9', '3', '6'}; +constexpr uint8_t GBK_TAG[8] = {'G', 'B', 'K', 'C', 'P', '9', '3', '6'}; + +bool isEucCn(const void* profile) { + return profile != nullptr && std::memcmp(profile, EUC_CN_TAG, sizeof(EUC_CN_TAG)) == 0; +} + +bool isGbk(const void* profile) { + return profile != nullptr && std::memcmp(profile, GBK_TAG, sizeof(GBK_TAG)) == 0; } bool isSbc(const void* profile) { - return profile == CP1252_PROFILE || profile == CP1251_PROFILE || profile == CP1250_PROFILE; + return profile == CP1252_PROFILE || sbcHighTable(profile) != nullptr; +} + +int rejectLead(uint32_t& length) { + length = 1; + return CES_ERROR_INVALID_ENCODE; +} + +int needBytes(uint32_t size, uint32_t& length) { + length = size; + return CES_ERROR_SRC_BUFFER_END; +} + +int rejectTrail(uint8_t trail, uint32_t size, uint32_t& length) { + length = trail == 0 ? 1 : size; + return CES_ERROR_INVALID_ENCODE; +} + +bool stopsAtNul(const uint8_t* mbcs, uint32_t mbcsMax, uint32_t from, uint32_t size, uint32_t& length) { + for (uint32_t index = from; index < size && index < mbcsMax; ++index) { + if (mbcs[index] == 0) { + length = index; + return true; + } + } + return false; +} + +int unassigned(uint32_t size, uint32_t& length) { + length = size; + return CES_ERROR_UNASSIGNED_CODE; +} + +int decodeGb(const void* profile, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t& length, uint32_t& code) { + const uint8_t lead = mbcs[0]; + if (lead < 0x80) { + length = 1; + code = lead; + return 0; + } + const bool gbk = isGbk(profile); + if (gbk && lead == 0x80) { + length = 1; + code = 0x20AC; + return 0; + } + if (lead == 0xFF || (!gbk && lead < 0xA1)) return rejectLead(length); + if (mbcsMax < 2) return needBytes(2, length); + const uint8_t trail = mbcs[1]; + if (trail == 0xFF || (gbk ? trail < 0x40 || trail == 0x7F : trail < 0xA1)) return rejectTrail(trail, 2, length); + code = Cp936::TO_UCS2[(lead - 0x81) * 190 + (trail < 0x7F ? trail - 0x40 : trail - 0x41)]; + if (code == 0) return unassigned(2, length); + length = 2; + return 0; +} + +const std::vector>& cp936Reverse() { + static const auto reverse = [] { + std::vector> entries; + for (uint32_t cell = 0; cell < 126 * 190; ++cell) { + if (Cp936::TO_UCS2[cell] != 0) entries.emplace_back(Cp936::TO_UCS2[cell], static_cast(cell)); + } + std::sort(entries.begin(), entries.end()); + return entries; + }(); + return reverse; +} + +int encodeGb(const void* profile, uint32_t code, uint8_t* mbcs, uint32_t& length) { + if (code < 0x80) { + length = 1; + mbcs[0] = static_cast(code); + return 0; + } + const bool gbk = isGbk(profile); + if (gbk && code == 0x20AC) { + length = 1; + mbcs[0] = 0x80; + return 0; + } + if (code > 0xFFFF) return CES_ERROR_UNASSIGNED_CODE; + const auto& reverse = cp936Reverse(); + for (auto found = std::lower_bound(reverse.begin(), reverse.end(), std::pair(static_cast(code), 0)); + found != reverse.end() && found->first == code; ++found) { + const uint8_t lead = static_cast(0x81 + found->second / 190); + const uint32_t cell = found->second % 190; + const uint8_t trail = static_cast(cell < 0x3F ? 0x40 + cell : 0x41 + cell); + if (!gbk && (lead < 0xA1 || trail < 0xA1)) continue; + mbcs[0] = lead; + mbcs[1] = trail; + length = 2; + return 0; + } + return CES_ERROR_UNASSIGNED_CODE; +} + +constexpr uint8_t GB18030_2000_TAG[8] = {'G', 'B', '1', '8', '2', '0', '0', '0'}; +constexpr uint8_t GB18030_2005_TAG[8] = {'G', 'B', '1', '8', '2', '0', '0', '5'}; +constexpr uint32_t GB_CELLS = 126 * 190; + +struct Gb18030Tables { + std::span twoByte; + std::span fourByte; + std::span fallback; +}; + +constexpr Gb18030Tables GB18030_2000{Gb18030::TWO_BYTE_2000, Gb18030::FOUR_BYTE_2000, {}}; +constexpr Gb18030Tables GB18030_2005{Gb18030::TWO_BYTE_2005, Gb18030::FOUR_BYTE_2005, Gb18030::FALLBACK_2005}; + +const Gb18030Tables* gb18030Tables(const void* profile) { + if (profile == nullptr) return nullptr; + if (std::memcmp(profile, GB18030_2000_TAG, sizeof(GB18030_2000_TAG)) == 0) return &GB18030_2000; + if (std::memcmp(profile, GB18030_2005_TAG, sizeof(GB18030_2005_TAG)) == 0) return &GB18030_2005; + return nullptr; +} + +bool isGb18030(const void* profile) { + return gb18030Tables(profile) != nullptr; +} + +bool isGb(const void* profile) { + return isGbk(profile) || isGb18030(profile); +} + +const uint32_t* findRun(std::span runs, uint32_t key) { + const auto next = std::upper_bound(runs.begin(), runs.end(), key, [](uint32_t value, const uint32_t (&run)[3]) { return value < run[0]; }); + if (next == runs.begin()) return nullptr; + const uint32_t* run = *std::prev(next); + return key - run[0] < run[2] ? run : nullptr; +} + +uint32_t gb18030Cell(const Gb18030Tables& tables, uint32_t cell) { + const uint32_t* run = findRun(tables.twoByte, cell); + return run ? run[1] + cell - run[0] : Cp936::TO_UCS2[cell]; +} + +void putGbCell(uint32_t cell, uint8_t* mbcs) { + const uint32_t column = cell % 190; + mbcs[0] = static_cast(0x81 + cell / 190); + mbcs[1] = static_cast(column < 0x3F ? 0x40 + column : 0x41 + column); +} + +int decodeGb18030(const Gb18030Tables& tables, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t& length, uint32_t& code) { + const uint8_t lead = mbcs[0]; + if (lead < 0x80) { + length = 1; + code = lead; + return 0; + } + if (lead == 0x80 || lead == 0xFF) return rejectLead(length); + if (mbcsMax < 2) return needBytes(2, length); + const uint8_t second = mbcs[1]; + if (second >= 0x30 && second <= 0x39) { + if (stopsAtNul(mbcs, mbcsMax, 2, 4, length)) return CES_ERROR_INVALID_ENCODE; + if (mbcsMax < 4) return needBytes(4, length); + if (mbcs[2] < 0x81 || mbcs[2] == 0xFF || mbcs[3] < 0x30 || mbcs[3] > 0x39) return rejectTrail(mbcs[2], 4, length); + const uint32_t linear = (((lead - 0x81) * 10 + (second - 0x30)) * 126 + (mbcs[2] - 0x81)) * 10 + (mbcs[3] - 0x30); + const uint32_t* run = findRun(tables.fourByte, linear); + if (!run) return unassigned(4, length); + length = 4; + code = run[1] + linear - run[0]; + return 0; + } + if (second < 0x40 || second == 0x7F || second == 0xFF) return rejectTrail(second, 2, length); + length = 2; + code = gb18030Cell(tables, (lead - 0x81) * 190 + (second < 0x7F ? second - 0x40 : second - 0x41)); + return 0; +} + +const std::vector>& gb18030Reverse(const Gb18030Tables& tables) { + const auto build = [](const Gb18030Tables& source) { + std::vector> entries; + entries.reserve(GB_CELLS); + for (uint32_t cell = 0; cell < GB_CELLS; ++cell) entries.emplace_back(gb18030Cell(source, cell), static_cast(cell)); + std::sort(entries.begin(), entries.end()); + return entries; + }; + static const auto reverse2000 = build(GB18030_2000); + static const auto reverse2005 = build(GB18030_2005); + return &tables == &GB18030_2005 ? reverse2005 : reverse2000; +} + +int encodeGb18030(const Gb18030Tables& tables, uint32_t code, uint8_t* mbcs, uint32_t& length) { + if (code < 0x80) { + length = 1; + mbcs[0] = static_cast(code); + return 0; + } + const auto& reverse = gb18030Reverse(tables); + const auto found = std::lower_bound(reverse.begin(), reverse.end(), std::pair(code, 0)); + if (found != reverse.end() && found->first == code) { + putGbCell(found->second, mbcs); + length = 2; + return 0; + } + for (const auto& run : tables.fourByte) { + if (code - run[1] >= run[2]) continue; + uint32_t linear = run[0] + code - run[1]; + mbcs[3] = static_cast(0x30 + linear % 10); + linear /= 10; + mbcs[2] = static_cast(0x81 + linear % 126); + linear /= 126; + mbcs[1] = static_cast(0x30 + linear % 10); + mbcs[0] = static_cast(0x81 + linear / 10); + length = 4; + return 0; + } + for (const auto& entry : tables.fallback) { + if (entry[0] != code) continue; + putGbCell(entry[1], mbcs); + length = 2; + return 0; + } + return CES_ERROR_UNASSIGNED_CODE; +} + +bool sjisToJis(uint8_t lead, uint8_t trail, int& row, int& cell) { + if (!((lead >= 0x81 && lead <= 0x9F) || (lead >= 0xE0 && lead <= 0xFC))) return false; + if (trail < 0x40 || trail == 0x7F || trail > 0xFC) return false; + row = lead <= 0x9F ? (lead - 0x81) * 2 + 1 : (lead - 0xE0) * 2 + 63; + if (trail >= 0x9F) { + row += 1; + cell = trail - 0x9E; + } else { + cell = trail > 0x7E ? trail - 0x40 : trail - 0x3F; + } + return true; +} + +void jisToSjis(int row, int cell, uint8_t& lead, uint8_t& trail) { + if (row % 2 != 0) { + lead = static_cast(row < 63 ? 0x81 + (row - 1) / 2 : 0xE0 + (row - 63) / 2); + trail = static_cast(cell <= 63 ? cell + 0x3F : cell + 0x40); + } else { + lead = static_cast(row < 63 ? 0x81 + (row - 2) / 2 : 0xE0 + (row - 64) / 2); + trail = static_cast(cell + 0x9E); + } +} + +constexpr uint8_t JIS_X0213_PLANE2_ROWS[26] = {1, 8, 3, 4, 5, 12, 13, 14, 15, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94}; + +bool isSjisSingle(uint8_t byte) { + return byte < 0x80 || (byte >= 0xA1 && byte <= 0xDF); } const void* contextProfile(const void* context) { @@ -157,7 +507,7 @@ const void* contextProfile(const void* context) { constexpr uint8_t EUC_KR_TAG[8] = {'E', 'U', 'C', 'K', 'R', '9', '4', '9'}; constexpr uint8_t UHC_TAG[8] = {'U', 'H', 'C', 'C', 'P', '9', '4', '9'}; constexpr uint8_t BIG5_TAG[8] = {'B', 'I', 'G', '5', 'C', '9', '5', '0'}; -constexpr uint16_t BIG5_LAST_CELL[6] = {0x2550, 0x255E, 0x2561, 0x256A, 0x5341, 0x5345}; +constexpr uint16_t BIG5_LAST_CELL[2] = {0x5341, 0x5345}; bool isEucKr(const void* profile) { return profile != nullptr && std::memcmp(profile, EUC_KR_TAG, sizeof(EUC_KR_TAG)) == 0; @@ -172,7 +522,7 @@ bool isBig5(const void* profile) { } bool isMbcs(const void* profile) { - return isEucJp(profile) || isSjis(profile) || isSbc(profile) || isEucKr(profile) || isUhc(profile) || isBig5(profile); + return isEucJp(profile) || isSjis(profile) || isSbc(profile) || isEucCn(profile) || isGb(profile) || isEucKr(profile) || isUhc(profile) || isBig5(profile); } int decodeCp949(bool unified, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t& length, uint32_t& code) { @@ -182,11 +532,11 @@ int decodeCp949(bool unified, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t& l code = lead; return 0; } - if (lead < (unified ? 0x81 : 0xA1) || lead == 0xFF) return CES_ERROR_INVALID_ENCODE; - if (mbcsMax < 2) return CES_ERROR_SRC_BUFFER_END; + if (lead < (unified ? 0x81 : 0xA1) || lead == 0xFF) return rejectLead(length); + if (mbcsMax < 2) return needBytes(2, length); const uint8_t trail = mbcs[1]; const bool extended = unified && ((trail >= 0x41 && trail <= 0x5A) || (trail >= 0x61 && trail <= 0x7A) || (trail >= 0x81 && trail <= 0xA0)); - if (!extended && (trail < 0xA1 || trail == 0xFF)) return CES_ERROR_INVALID_ENCODE; + if (!extended && (trail < 0xA1 || trail == 0xFF)) return rejectTrail(trail, 2, length); length = 2; code = Cp949::TO_UCS2[(lead - 0x81) * 190 + (trail - 0x41)]; return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; @@ -199,11 +549,11 @@ int decodeCp950(const uint8_t* mbcs, uint32_t mbcsMax, uint32_t& length, uint32_ code = lead; return 0; } - if (lead == 0x80 || lead == 0xFF) return CES_ERROR_INVALID_ENCODE; - if (mbcsMax < 2) return CES_ERROR_SRC_BUFFER_END; + if (lead == 0x80 || lead == 0xFF) return rejectLead(length); + if (mbcsMax < 2) return needBytes(2, length); const uint8_t trail = mbcs[1]; - if (trail < 0x40 || (trail > 0x7E && trail < 0xA1) || trail == 0xFF) return CES_ERROR_INVALID_ENCODE; - if (lead < 0xA1 || lead > 0xF9) return CES_ERROR_UNASSIGNED_CODE; + if (trail < 0x40 || (trail > 0x7E && trail < 0xA1) || trail == 0xFF) return rejectTrail(trail, 2, length); + if (lead < 0xA1 || lead > 0xF9) return unassigned(2, length); length = 2; code = Cp950::TO_UCS2[(lead - 0xA1) * 157 + (trail < 0xA1 ? trail - 0x40 : trail - 0xA1 + 63)]; return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; @@ -264,19 +614,95 @@ int encodeDbcs(const std::vector& reverse, bool euc, uint32_t code, ui return 0; } +bool jisX0212RowEmpty(uint8_t row) { + for (uint32_t cell = 0; cell < 94; ++cell) { + if (JisX0212::TO_UCS2[(row - 0xA1) * 94 + cell] != 0) return false; + } + return true; +} + +int decodeEucJpScalar(const uint8_t* euc, uint32_t eucMax, uint32_t& length, uint32_t& code) { + const uint8_t lead = euc[0]; + length = 1; + if (lead == 0x0E || lead == 0xA0 || lead == 0xFF) return CES_ERROR_INVALID_ENCODE; + if (lead < 0xA0 && lead != 0x8E && lead != 0x8F) { + code = lead; + return 0; + } + const uint32_t needed = lead == 0x8F ? 3 : 2; + for (uint32_t i = 1; i < needed && i < eucMax; ++i) { + if (euc[i] == 0) { + length = i; + return CES_ERROR_INVALID_ENCODE; + } + } + length = needed; + if (eucMax < needed) return CES_ERROR_SRC_BUFFER_END; + const uint8_t trail = euc[1]; + if (lead == 0x8E) { + if (trail >= 0xA1 && trail <= 0xDF) { + code = 0xFF61 + (trail - 0xA1); + return 0; + } + return trail >= 0xE0 && trail <= 0xFE ? CES_ERROR_UNASSIGNED_CODE : CES_ERROR_INVALID_ENCODE; + } + if (lead == 0x8F) { + if (trail < 0xA1 || trail > 0xFE || euc[2] < 0xA1 || euc[2] > 0xFE) return CES_ERROR_INVALID_ENCODE; + return jisX0212RowEmpty(trail) ? CES_ERROR_UNASSIGNED_CODE : CES_ERROR_INVALID_ENCODE; + } + if (trail < 0xA1 || trail > 0xFE) return CES_ERROR_INVALID_ENCODE; + code = JisX0208::TO_UCS2[(lead - 0xA1) * 94 + (trail - 0xA1)]; + return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; +} + +int decodeEucJp(const void* profile, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t& length, uint32_t& code) { + if (hasTag(profile, EUC_JP_TAG)) return decodeEucJpScalar(mbcs, mbcsMax, length, code); + const uint8_t lead = mbcs[0]; + if (lead < 0x80) { + length = 1; + code = lead; + return 0; + } + if (lead == 0x8E && hasSs2(profile)) { + if (mbcsMax < 2) return needBytes(2, length); + if (mbcs[1] < 0xA1 || mbcs[1] > 0xDF) return rejectTrail(mbcs[1], 2, length); + length = 2; + code = 0xFF61 + (mbcs[1] - 0xA1); + return 0; + } + if (lead >= 0xA1 && lead <= 0xFE) { + if (mbcsMax < 2) return needBytes(2, length); + if (mbcs[1] < 0xA1 || mbcs[1] > 0xFE) return rejectTrail(mbcs[1], 2, length); + length = 2; + code = jisX0208ToUnicode(isEucJpX0208(profile) ? JisStrict::Euc : JisStrict::None, (lead - 0xA1) * 94 + (mbcs[1] - 0xA1)); + return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; + } + if (lead == 0x8F && hasSs3(profile)) { + if (stopsAtNul(mbcs, mbcsMax, 1, 3, length)) return CES_ERROR_INVALID_ENCODE; + if (mbcsMax < 3) return needBytes(3, length); + if (mbcs[1] < 0xA1 || mbcs[1] > 0xFE || mbcs[2] < 0xA1 || mbcs[2] > 0xFE) return rejectTrail(mbcs[1], 3, length); + length = 3; + code = JisX0212::TO_UCS2[(mbcs[1] - 0xA1) * 94 + (mbcs[2] - 0xA1)]; + return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; + } + return rejectLead(length); +} + int decodeMbcs(const void* profile, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t& length, uint32_t& code) { if (isEucKr(profile) || isUhc(profile)) return decodeCp949(isUhc(profile), mbcs, mbcsMax, length, code); if (isBig5(profile)) return decodeCp950(mbcs, mbcsMax, length, code); + if (const Gb18030Tables* tables = gb18030Tables(profile)) return decodeGb18030(*tables, mbcs, mbcsMax, length, code); + if (isEucCn(profile) || isGbk(profile)) return decodeGb(profile, mbcs, mbcsMax, length, code); const uint8_t lead = mbcs[0]; if (isSbc(profile)) { length = 1; - code = sbcToUnicode(static_cast(profile), lead); - return 0; + return sbcToUnicode(static_cast(profile), lead, code) ? 0 : CES_ERROR_UNASSIGNED_CODE; } if (isSjis(profile)) { + const bool strict = isSjisX0208(profile); if (lead < 0x80) { length = 1; - code = lead; + code = strict ? sjisX0208Single(lead) : lead; return 0; } if (lead >= 0xA1 && lead <= 0xDF) { @@ -285,10 +711,10 @@ int decodeMbcs(const void* profile, const uint8_t* mbcs, uint32_t mbcsMax, uint3 return 0; } if ((lead >= 0x81 && lead <= 0x9F) || (lead >= 0xE0 && lead <= 0xFC)) { - if (mbcsMax < 2) return CES_ERROR_SRC_BUFFER_END; + if (mbcsMax < 2) return needBytes(2, length); const uint8_t trail = mbcs[1]; - if (trail < 0x40 || trail == 0x7F || trail > 0xFC) return CES_ERROR_INVALID_ENCODE; - if (lead >= 0xFA) { + if (trail < 0x40 || trail == 0x7F || trail > 0xFC) return rejectTrail(trail, 2, length); + if (lead >= 0xFA && !strict) { const uint32_t trailIdx = trail < 0x7F ? trail - 0x40 : trail - 0x41; const uint32_t offset = (lead - 0xFA) * 188 + trailIdx; if (offset <= 387) { @@ -301,55 +727,24 @@ int decodeMbcs(const void* profile, const uint8_t* mbcs, uint32_t mbcsMax, uint3 const int row = 89 + kanjiIdx / 94; const int cell = 1 + kanjiIdx % 94; code = JisX0208::TO_UCS2[(row - 1) * 94 + (cell - 1)]; - if (code == 0) return CES_ERROR_UNASSIGNED_CODE; + if (code == 0) return unassigned(2, length); length = 2; return 0; } - return CES_ERROR_UNASSIGNED_CODE; + return unassigned(2, length); } - int row = (lead <= 0x9F) ? (lead - 0x81) * 2 + 1 : (lead - 0xE0) * 2 + 63; + int row = 0; int cell = 0; - if (trail >= 0x9F) { - row += 1; - cell = trail - 0x9E; - } else { - cell = (trail > 0x7E) ? trail - 0x40 : trail - 0x3F; - } - if (row < 1 || row > 94 || cell < 1 || cell > 94) return CES_ERROR_UNASSIGNED_CODE; - code = JisX0208::TO_UCS2[(row - 1) * 94 + (cell - 1)]; - if (code == 0) return CES_ERROR_UNASSIGNED_CODE; + sjisToJis(lead, trail, row, cell); + if (row < 1 || row > 94 || cell < 1 || cell > 94) return unassigned(2, length); + code = jisX0208ToUnicode(strict ? JisStrict::Sjis : JisStrict::None, (row - 1) * 94 + (cell - 1)); + if (code == 0) return unassigned(2, length); length = 2; return 0; } - return CES_ERROR_INVALID_ENCODE; + return rejectLead(length); } - if (lead < 0x80) { - length = 1; - code = lead; - return 0; - } - if (lead == 0x8E) { - if (mbcsMax < 2) return CES_ERROR_SRC_BUFFER_END; - if (mbcs[1] < 0xA1 || mbcs[1] > 0xDF) return CES_ERROR_INVALID_ENCODE; - length = 2; - code = 0xFF61 + (mbcs[1] - 0xA1); - return 0; - } - if (lead >= 0xA1 && lead <= 0xFE) { - if (mbcsMax < 2) return CES_ERROR_SRC_BUFFER_END; - if (mbcs[1] < 0xA1 || mbcs[1] > 0xFE) return CES_ERROR_INVALID_ENCODE; - length = 2; - code = JisX0208::TO_UCS2[(lead - 0xA1) * 94 + (mbcs[1] - 0xA1)]; - return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; - } - if (lead == 0x8F) { - if (mbcsMax < 3) return CES_ERROR_SRC_BUFFER_END; - if (mbcs[1] < 0xA1 || mbcs[1] > 0xFE || mbcs[2] < 0xA1 || mbcs[2] > 0xFE) return CES_ERROR_INVALID_ENCODE; - length = 3; - code = JisX0212::TO_UCS2[(mbcs[1] - 0xA1) * 94 + (mbcs[2] - 0xA1)]; - return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; - } - return CES_ERROR_INVALID_ENCODE; + return decodeEucJp(profile, mbcs, mbcsMax, length, code); } struct JisCell { @@ -358,72 +753,109 @@ struct JisCell { bool supplementary; }; -const std::vector& jisReverse() { - static const auto reverse = [] { - std::vector entries; - entries.reserve(2 * 94 * 94); - for (uint32_t cell = 0; cell < 94 * 94; ++cell) { - if (JisX0208::TO_UCS2[cell] != 0) entries.push_back({JisX0208::TO_UCS2[cell], static_cast(cell), false}); - } - for (uint32_t cell = 0; cell < 94 * 94; ++cell) { - if (JisX0212::TO_UCS2[cell] != 0) entries.push_back({JisX0212::TO_UCS2[cell], static_cast(cell), true}); - } - std::stable_sort(entries.begin(), entries.end(), [](const JisCell& left, const JisCell& right) { return left.code < right.code; }); - entries.erase(std::unique(entries.begin(), entries.end(), [](const JisCell& left, const JisCell& right) { return left.code == right.code; }), entries.end()); - return entries; - }(); - return reverse; +std::vector buildJisReverse(JisStrict strict) { + std::vector entries; + entries.reserve(2 * 94 * 94); + for (uint32_t cell = 0; cell < 94 * 94; ++cell) { + const uint16_t code = static_cast(jisX0208ToUnicode(strict, cell)); + if (code != 0) entries.push_back({code, static_cast(cell), false}); + } + if (strict == JisStrict::Euc) { + for (const JisChange& fallback : JIS_X0208_EUC_FALLBACKS) entries.push_back({fallback.code, static_cast(jisIndex(fallback.jis)), false}); + } + for (uint32_t cell = 0; cell < 94 * 94; ++cell) { + if (JisX0212::TO_UCS2[cell] != 0) entries.push_back({JisX0212::TO_UCS2[cell], static_cast(cell), true}); + } + std::stable_sort(entries.begin(), entries.end(), [](const JisCell& left, const JisCell& right) { return left.code < right.code; }); + entries.erase(std::unique(entries.begin(), entries.end(), [](const JisCell& left, const JisCell& right) { return left.code == right.code; }), entries.end()); + return entries; +} + +const std::vector& jisReverse(JisStrict strict) { + static const auto reverse = buildJisReverse(JisStrict::None); + static const auto sjisReverse = buildJisReverse(JisStrict::Sjis); + static const auto eucReverse = buildJisReverse(JisStrict::Euc); + return strict == JisStrict::Sjis ? sjisReverse : strict == JisStrict::Euc ? eucReverse : reverse; +} + +uint32_t jisAlias(uint32_t code) { + switch (code) { + case 0x00A2: return 0xFFE0; + case 0x00A3: return 0xFFE1; + case 0x00A5: return 0xFFE5; + case 0x00AC: return 0xFFE2; + case 0x2014: return 0x2015; + case 0x2016: return 0x2225; + case 0x203E: return 0xFFE3; + case 0x2212: return 0xFF0D; + case 0x301C: return 0xFF5E; + default: return code; + } } int encodeMbcs(const void* profile, uint32_t code, uint8_t* mbcs, uint32_t& length) { if (isEucKr(profile) || isUhc(profile)) return encodeDbcs(cp949Reverse(), isEucKr(profile), code, mbcs, length); if (isBig5(profile)) return encodeDbcs(cp950Reverse(), false, code, mbcs, length); + if (const Gb18030Tables* tables = gb18030Tables(profile)) return encodeGb18030(*tables, code, mbcs, length); + if (isEucCn(profile) || isGbk(profile)) return encodeGb(profile, code, mbcs, length); if (isSbc(profile)) { length = 1; return unicodeToSbc(static_cast(profile), code, mbcs[0]) ? 0 : CES_ERROR_UNASSIGNED_CODE; } if (isSjis(profile)) { - if (code < 0x80) { + const bool strict = isSjisX0208(profile); + if (strict && (code == 0x00A5 || code == 0x203E)) { + length = 1; + mbcs[0] = code == 0x00A5 ? 0x5C : 0x7E; + return 0; + } + if (code < 0x80 && !(strict && (code == 0x5C || code == 0x7E))) { length = 1; mbcs[0] = static_cast(code); return 0; } + if (!strict && code <= 0x9F) return CES_ERROR_UNSUPPORTED_CODE; + if (!strict && code == 0xFEFF) { + length = 0; + return 0; + } + if (!strict) code = jisAlias(code); if (code >= 0xFF61 && code <= 0xFF9F) { length = 1; mbcs[0] = static_cast(0xA1 + (code - 0xFF61)); return 0; } if (code > 0xFFFF) return CES_ERROR_UNASSIGNED_CODE; - const auto& reverse = jisReverse(); + const auto& reverse = jisReverse(strict ? JisStrict::Sjis : JisStrict::None); const auto found = std::lower_bound(reverse.begin(), reverse.end(), static_cast(code), [](const JisCell& entry, uint16_t value) { return entry.code < value; }); if (found == reverse.end() || found->code != code || found->supplementary) return CES_ERROR_UNASSIGNED_CODE; - const int row = found->cell / 94 + 1; - const int cell = found->cell % 94 + 1; - if (row % 2 != 0) { - mbcs[0] = static_cast(row < 63 ? 0x81 + (row - 1) / 2 : 0xE0 + (row - 63) / 2); - mbcs[1] = static_cast(cell <= 63 ? cell + 0x3F : cell + 0x40); - } else { - mbcs[0] = static_cast(row < 63 ? 0x81 + (row - 2) / 2 : 0xE0 + (row - 64) / 2); - mbcs[1] = static_cast(cell + 0x9E); - } + jisToSjis(found->cell / 94 + 1, found->cell % 94 + 1, mbcs[0], mbcs[1]); length = 2; return 0; } - if (code < 0x80) { + const bool strict = isEucJpX0208(profile); + if (!strict && code == 0x0E) return CES_ERROR_UNASSIGNED_CODE; + if (code < 0x80 || (!strict && code < 0xA0 && code != 0x8E && code != 0x8F)) { length = 1; mbcs[0] = static_cast(code); return 0; } + if (!strict && code == 0xFEFF) { + length = 0; + return 0; + } + if (!strict) code = jisAlias(code); if (code >= 0xFF61 && code <= 0xFF9F) { + if (!hasSs2(profile)) return CES_ERROR_UNASSIGNED_CODE; length = 2; mbcs[0] = 0x8E; mbcs[1] = static_cast(0xA1 + (code - 0xFF61)); return 0; } if (code > 0xFFFF) return CES_ERROR_UNASSIGNED_CODE; - const auto& reverse = jisReverse(); + const auto& reverse = jisReverse(strict ? JisStrict::Euc : JisStrict::None); const auto found = std::lower_bound(reverse.begin(), reverse.end(), static_cast(code), [](const JisCell& entry, uint16_t value) { return entry.code < value; }); - if (found == reverse.end() || found->code != code) return CES_ERROR_UNASSIGNED_CODE; + if (found == reverse.end() || found->code != code || (found->supplementary && !hasSs3(profile))) return CES_ERROR_UNASSIGNED_CODE; if (found->supplementary) { length = 3; mbcs[0] = 0x8F; @@ -440,65 +872,69 @@ int encodeMbcs(const void* profile, uint32_t code, uint8_t* mbcs, uint32_t& leng int decodeUcs2(const uint16_t* ucs2, uint32_t, uint32_t& length, uint32_t& code) { length = 1; code = ucs2[0]; - return code >= 0xD800 && code <= 0xDFFF ? CES_ERROR_INVALID_ENCODE : 0; + return code >= 0xD800 && code <= 0xDFFF ? CES_ERROR_ILLEGAL_CODE : 0; } uint32_t encodeUcs2(uint32_t code, uint16_t* ucs2) { + if (code > 0xFFFF) return 0; ucs2[0] = static_cast(code); return 1; } -template -int toMbcs(const void* context, const Source* source, uint32_t sourceMax, uint32_t* sourceLen, uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, Decode decode) { +uint32_t encodeUtf16(uint32_t code, uint16_t* utf16) { + if (code < 0x10000) { + utf16[0] = static_cast(code); + return 1; + } + utf16[0] = static_cast(0xD800 | (code - 0x10000) >> 10); + utf16[1] = static_cast(0xDC00 | (code & 0x3FF)); + return 2; +} + +template +int convertString(const void* context, const Source* source, uint32_t sourceMax, uint32_t* sourceLen, Target* target, uint32_t targetMax, uint32_t* targetLen, bool counting, Decode decode, Encode encode) { + if (sourceLen) *sourceLen = 0; + if (targetLen) *targetLen = 0; + if (target && targetMax > 0) target[0] = 0; if (!context) return CES_ERROR_INVALID_PARAMETER; const void* profile = contextProfile(context); if (!isMbcs(profile)) return CES_ERROR_INVALID_PARAMETER; if (!source) return CES_ERROR_INVALID_SRC_BUFFER; + if (!counting && !target) return CES_ERROR_INVALID_DST_BUFFER; uint32_t read = 0; uint32_t written = 0; - while (read < sourceMax && source[read] != 0) { + int result = 0; + while ((sourceMax == 0 || read < sourceMax) && source[read] != 0) { uint32_t length = 0; uint32_t code = 0; - if (const int result = decode(source + read, sourceMax - read, length, code); result != 0) return result; - uint8_t encoded[3]; - uint32_t size = 0; - if (const int result = encodeMbcs(profile, code, encoded, size); result != 0) return result; - if (mbcs) { - if (written + size > mbcsMax) return CES_ERROR_DST_BUFFER_END; - std::memcpy(mbcs + written, encoded, size); - } - read += length; - written += size; - } - if (sourceLen) *sourceLen = read; - if (mbcsLen) *mbcsLen = written; - return 0; -} - -template -int fromMbcs(const void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, Target* target, uint32_t targetMax, uint32_t* targetLen, Encode encode) { - if (!context) return CES_ERROR_INVALID_PARAMETER; - const void* profile = contextProfile(context); - if (!isMbcs(profile)) return CES_ERROR_INVALID_PARAMETER; - if (!mbcs) return CES_ERROR_INVALID_SRC_BUFFER; - uint32_t read = 0; - uint32_t written = 0; - while (read < mbcsMax && mbcs[read] != 0) { - uint32_t length = 0; - uint32_t code = 0; - if (const int result = decodeMbcs(profile, mbcs + read, mbcsMax - read, length, code); result != 0) return result; + const uint32_t available = sourceMax == 0 ? UINT32_MAX - read : sourceMax - read; + if ((result = decode(profile, source + read, available, length, code)) != 0) break; Target encoded[4]; - const uint32_t size = encode(code, encoded); - if (target) { - if (written + size > targetMax) return CES_ERROR_DST_BUFFER_END; + uint32_t size = 0; + if ((result = encode(profile, code, encoded, size)) != 0) break; + if (!counting) { + if (written + size >= targetMax) { + result = CES_ERROR_DST_BUFFER_END; + break; + } std::memcpy(target + written, encoded, size * sizeof(Target)); } read += length; written += size; } - if (mbcsLen) *mbcsLen = read; + if (!counting && written < targetMax) target[written] = 0; + if (sourceLen) *sourceLen = read; if (targetLen) *targetLen = written; - return 0; + return result; +} + +template +int rejected(int result, uint32_t length, uint32_t* sourceSize, Unit* character, uint32_t* produced) { + if (length == 0) return result; + if (sourceSize) *sourceSize = length; + if (character) *character = 0; + if (produced) *produced = 0; + return result; } int mbcsToUtf8(bool accepted, const void* profile, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint8_t* utf8, uint32_t utf8Max, uint32_t* utf8Len) { @@ -508,8 +944,8 @@ int mbcsToUtf8(bool accepted, const void* profile, const uint8_t* mbcs, uint32_t if (!utf8) return CES_ERROR_INVALID_DST_BUFFER; uint32_t consumed = 0; uint32_t code = 0; - if (const int result = decodeMbcs(profile, mbcs, mbcsMax, consumed, code); result != 0) return result; - uint8_t encoded[3]; + if (const int result = decodeMbcs(profile, mbcs, mbcsMax, consumed, code); result != 0) return rejected(result, consumed, mbcsLen, nullptr, utf8Len); + uint8_t encoded[4]; const uint32_t produced = encodeUtf8(code, encoded); if (utf8Max < produced) return CES_ERROR_DST_BUFFER_END; std::memcpy(utf8, encoded, produced); @@ -518,6 +954,224 @@ int mbcsToUtf8(bool accepted, const void* profile, const uint8_t* mbcs, uint32_t return 0; } +constexpr bool NATIVE_BIG_ENDIAN = std::endian::native == std::endian::big; + +template +uint32_t loadUnit(const Unit* source) { + const auto* bytes = reinterpret_cast(source); + uint32_t code = 0; + for (uint32_t i = 0; i < sizeof(Unit); ++i) code |= uint32_t(bytes[i]) << (8 * (BigEndian ? sizeof(Unit) - 1 - i : i)); + return code; +} + +template +void storeUnit(Unit* target, uint32_t code) { + auto* bytes = reinterpret_cast(target); + for (uint32_t i = 0; i < sizeof(Unit); ++i) bytes[i] = static_cast(code >> (8 * (BigEndian ? sizeof(Unit) - 1 - i : i))); +} + +bool isScalar(uint32_t code) { + return code <= 0x10FFFF && (code < 0xD800 || code > 0xDFFF); +} + +int scalarError(uint32_t code) { + if (code > 0x10FFFF) return CES_ERROR_INVALID_ENCODE; + return code >= 0xD800 && code <= 0xDFFF ? CES_ERROR_ILLEGAL_CODE : 0; +} + +template +int decodeUtf16(const uint16_t* utf16, uint32_t utf16max, uint32_t& length, uint32_t& code) { + code = loadUnit(utf16); + length = 1; + if (code >= 0xDC00 && code <= 0xDFFF) return CES_ERROR_INVALID_ENCODE; + if (code >= 0xD800 && code <= 0xDBFF) { + if (utf16max < 2) return CES_ERROR_SRC_BUFFER_END; + const uint32_t low = loadUnit(utf16 + 1); + if (low < 0xDC00 || low > 0xDFFF) return CES_ERROR_INVALID_ENCODE; + code = 0x10000 + ((code - 0xD800) << 10) + low - 0xDC00; + length = 2; + } + return 0; +} + +int decodeProfiled(bool (*matches)(const void*), const void* profile, const uint8_t* source, uint32_t sourceMax, const void* target, uint32_t& length, uint32_t& code) { + if (!matches(profile)) return CES_ERROR_INVALID_PARAMETER; + if (!source) return CES_ERROR_INVALID_SRC_BUFFER; + if (sourceMax == 0) return CES_ERROR_SRC_BUFFER_END; + if (!target) return CES_ERROR_INVALID_DST_BUFFER; + return decodeMbcs(profile, source, sourceMax, length, code); +} + + +int profiledToUcs2(bool (*matches)(const void*), const void* profile, const uint8_t* source, uint32_t sourceMax, uint32_t* sourceSize, uint16_t* ucs2) { + uint32_t length = 0; + uint32_t code = 0; + if (const int result = decodeProfiled(matches, profile, source, sourceMax, ucs2, length, code); result != 0) return rejected(result, length, sourceSize, ucs2, nullptr); + if (code > 0xFFFF) return CES_ERROR_UNASSIGNED_CODE; + *ucs2 = static_cast(code); + if (sourceSize) *sourceSize = length; + return 0; +} + +int profiledToUtf8(bool (*matches)(const void*), const void* profile, const uint8_t* source, uint32_t sourceMax, uint32_t* sourceSize, uint8_t* utf8, uint32_t utf8max, uint32_t* utf8Len) { + uint32_t length = 0; + uint32_t code = 0; + if (const int result = decodeProfiled(matches, profile, source, sourceMax, utf8, length, code); result != 0) return rejected(result, length, sourceSize, nullptr, utf8Len); + uint8_t encoded[4]; + const uint32_t produced = encodeUtf8(code, encoded); + if (utf8max < produced) return CES_ERROR_DST_BUFFER_END; + std::memcpy(utf8, encoded, produced); + if (sourceSize) *sourceSize = length; + if (utf8Len) *utf8Len = produced; + return 0; +} + +template +int profiledToUtf16(bool (*matches)(const void*), const void* profile, const uint8_t* source, uint32_t sourceMax, uint32_t* sourceSize, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len) { + uint32_t length = 0; + uint32_t code = 0; + if (const int result = decodeProfiled(matches, profile, source, sourceMax, utf16, length, code); result != 0) return rejected(result, length, sourceSize, nullptr, utf16Len); + const uint32_t units = code < 0x10000 ? 1 : 2; + if (utf16max < units) return CES_ERROR_DST_BUFFER_END; + if (units == 1) { + storeUnit(utf16, code); + } else { + storeUnit(utf16, 0xD800 | (code - 0x10000) >> 10); + storeUnit(utf16 + 1, 0xDC00 | (code & 0x3FF)); + } + if (sourceSize) *sourceSize = length; + if (utf16Len) *utf16Len = units; + return 0; +} + +template +int profiledToUtf32(bool (*matches)(const void*), const void* profile, const uint8_t* source, uint32_t sourceMax, uint32_t* sourceSize, uint32_t* utf32) { + uint32_t length = 0; + uint32_t code = 0; + if (const int result = decodeProfiled(matches, profile, source, sourceMax, utf32, length, code); result != 0) return rejected(result, length, sourceSize, utf32, nullptr); + storeUnit(utf32, code); + if (sourceSize) *sourceSize = length; + return 0; +} + +int encodeProfiled(const void* profile, uint32_t code, uint8_t* target, uint32_t targetMax, uint32_t* targetSize) { + uint8_t encoded[4]; + uint32_t size = 0; + if (const int result = encodeMbcs(profile, code, encoded, size); result != 0) return result; + if (targetMax < size) return CES_ERROR_DST_BUFFER_END; + std::memcpy(target, encoded, size); + if (targetSize) *targetSize = size; + return 0; +} + +int scalarToProfiled(bool (*matches)(const void*), uint32_t code, const void* profile, uint8_t* target, uint32_t targetMax, uint32_t* targetSize) { + if (!matches(profile)) return CES_ERROR_INVALID_PARAMETER; + if (!target) return CES_ERROR_INVALID_DST_BUFFER; + int result = scalarError(code); + uint8_t encoded[4]; + uint32_t size = 0; + if (result == 0) result = encodeMbcs(profile, code, encoded, size); + if (result == 0 && targetMax < size) result = CES_ERROR_DST_BUFFER_END; + if (result == 0) std::memcpy(target, encoded, size); + if (targetSize) *targetSize = result == 0 || result == CES_ERROR_DST_BUFFER_END ? size : 0; + return result; +} + +template +int utf32ToProfiled(bool (*matches)(const void*), const uint32_t* utf32, const void* profile, uint8_t* target, uint32_t targetMax, uint32_t* targetSize) { + if (!matches(profile)) return CES_ERROR_INVALID_PARAMETER; + if (!utf32) return CES_ERROR_INVALID_SRC_BUFFER; + return scalarToProfiled(matches, loadUnit(utf32), profile, target, targetMax, targetSize); +} + +template +int bufferToProfiled(bool (*matches)(const void*), const Source* source, uint32_t sourceMax, uint32_t* sourceLen, const void* profile, uint8_t* target, uint32_t targetMax, uint32_t* targetSize, Decode decode) { + if (!matches(profile)) return CES_ERROR_INVALID_PARAMETER; + if (!source) return CES_ERROR_INVALID_SRC_BUFFER; + if (sourceMax == 0) return CES_ERROR_SRC_BUFFER_END; + if (!target) return CES_ERROR_INVALID_DST_BUFFER; + uint32_t length = 0; + uint32_t code = 0; + if (const int result = decode(source, sourceMax, length, code); result != 0) return result; + if (const int result = encodeProfiled(profile, code, target, targetMax, targetSize); result != 0) return result; + if (sourceLen) *sourceLen = length; + return 0; +} + +int decodeUtf32(const uint32_t* utf32, uint32_t, uint32_t& length, uint32_t& code) { + length = 1; + code = utf32[0]; + return isScalar(code) ? 0 : CES_ERROR_INVALID_ENCODE; +} + +uint32_t encodeUtf32(uint32_t code, uint32_t* utf32) { + utf32[0] = code; + return 1; +} + +template +int unitSource(const void*, const Unit* source, uint32_t sourceMax, uint32_t& length, uint32_t& code) { + return Decode(source, sourceMax, length, code); +} + +template +int unitTarget(const void*, uint32_t code, Unit* target, uint32_t& length) { + length = Encode(code, target); + return length != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; +} + +int sbcToUnit(const uint8_t* profile, uint8_t sbc, const void* target, uint32_t& code) { + if (!isSbc(profile)) return CES_ERROR_INVALID_PARAMETER; + if (!target) return CES_ERROR_INVALID_DST_BUFFER; + return sbcToUnicode(profile, sbc, code) ? 0 : CES_ERROR_UNASSIGNED_CODE; +} + +template +int sbcToUtf16(const uint8_t* profile, uint8_t sbc, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len) { + uint32_t code = 0; + if (const int result = sbcToUnit(profile, sbc, utf16, code); result != 0) return result; + if (utf16max < 1) return CES_ERROR_DST_BUFFER_END; + storeUnit(utf16, code); + if (utf16Len) *utf16Len = 1; + return 0; +} + +template +int sbcToUtf32(const uint8_t* profile, uint8_t sbc, uint32_t* utf32) { + uint32_t code = 0; + if (const int result = sbcToUnit(profile, sbc, utf32, code); result != 0) return result; + storeUnit(utf32, code); + return 0; +} + +int scalarToSbc(uint32_t code, const uint8_t* profile, uint8_t* sbc) { + if (!isSbc(profile)) return CES_ERROR_INVALID_PARAMETER; + if (!sbc) return CES_ERROR_INVALID_DST_BUFFER; + if (!isScalar(code)) return CES_ERROR_INVALID_ENCODE; + return unicodeToSbc(profile, code, *sbc) ? 0 : CES_ERROR_UNASSIGNED_CODE; +} + +template +int utf16ToSbc(const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, const uint8_t* profile, uint8_t* sbc) { + if (!isSbc(profile)) return CES_ERROR_INVALID_PARAMETER; + if (!utf16) return CES_ERROR_INVALID_SRC_BUFFER; + if (utf16max == 0) return CES_ERROR_SRC_BUFFER_END; + if (!sbc) return CES_ERROR_INVALID_DST_BUFFER; + uint32_t length = 0; + uint32_t code = 0; + if (const int result = decodeUtf16(utf16, utf16max, length, code); result != 0) return result; + if (!unicodeToSbc(profile, code, *sbc)) return CES_ERROR_UNASSIGNED_CODE; + if (utf16Len) *utf16Len = length; + return 0; +} + +template +int utf32ToSbc(const uint32_t* utf32, const uint8_t* profile, uint8_t* sbc) { + if (!isSbc(profile)) return CES_ERROR_INVALID_PARAMETER; + if (!utf32) return CES_ERROR_INVALID_SRC_BUFFER; + return scalarToSbc(loadUnit(utf32), profile, sbc); +} + } extern "C" { @@ -534,14 +1188,135 @@ const void* APS5_VABI sceCesRefersUcsProfileCp1250(void) { return CP1250_PROFILE; } +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_1(void) { + return singleByteProfile(SingleByte::Iso8859_1); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_2(void) { + return singleByteProfile(SingleByte::Iso8859_2); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_4(void) { + return singleByteProfile(SingleByte::Iso8859_4); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_5(void) { + return singleByteProfile(SingleByte::Iso8859_5); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_9(void) { + return singleByteProfile(SingleByte::Iso8859_9); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_10(void) { + return singleByteProfile(SingleByte::Iso8859_10); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_13(void) { + return singleByteProfile(SingleByte::Iso8859_13); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_14(void) { + return singleByteProfile(SingleByte::Iso8859_14); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_15(void) { + return singleByteProfile(SingleByte::Iso8859_15); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_16(void) { + return singleByteProfile(SingleByte::Iso8859_16); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp437(void) { + return singleByteProfile(SingleByte::Cp437); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp737(void) { + return singleByteProfile(SingleByte::Cp737); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp775(void) { + return singleByteProfile(SingleByte::Cp775); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp850(void) { + return singleByteProfile(SingleByte::Cp850); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp852(void) { + return singleByteProfile(SingleByte::Cp852); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp855(void) { + return singleByteProfile(SingleByte::Cp855); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp860(void) { + return singleByteProfile(SingleByte::Cp860); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp861(void) { + return singleByteProfile(SingleByte::Cp861); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp862(void) { + return singleByteProfile(SingleByte::Cp862); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp863(void) { + return singleByteProfile(SingleByte::Cp863); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp865(void) { + return singleByteProfile(SingleByte::Cp865); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp866(void) { + return singleByteProfile(SingleByte::Cp866); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileKoi8R(void) { + return singleByteProfile(SingleByte::Koi8R); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileKoi8U(void) { + return singleByteProfile(SingleByte::Koi8U); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1254(void) { + return singleByteProfile(SingleByte::Cp1254); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1256(void) { + return singleByteProfile(SingleByte::Cp1256); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1258(void) { + return singleByteProfile(SingleByte::Cp1258); +} + void* APS5_VABI sceCesUcsProfileInitSJis1997Cp932(void* sheet) { if (!sheet) return nullptr; std::memcpy(sheet, SJIS_TAG, sizeof(SJIS_TAG)); return sheet; } +void* APS5_VABI sceCesUcsProfileInitSJis1997X0208(void* sheet) { + if (!sheet) return nullptr; + std::memcpy(sheet, SJIS_X0208_TAG, sizeof(SJIS_X0208_TAG)); + return sheet; +} + int APS5_VABI sceCesMbcsUcsContextInit(void* context, const void* profile) { - if (!context) return CES_ERROR_INVALID_PARAMETER; + if (!context || !profile) return CES_ERROR_INVALID_PARAMETER; + std::memcpy(context, &profile, sizeof(profile)); + return 0; +} + +int APS5_VABI sceCesMbcsUcsContextInitCopy(void* context, const void* source) { + if (!context || !source) return CES_ERROR_INVALID_PARAMETER; + const void* profile = contextProfile(source); std::memcpy(context, &profile, sizeof(profile)); return 0; } @@ -552,6 +1327,24 @@ void* APS5_VABI sceCesUcsProfileInitEucJpCp51932(void* sheet) { return sheet; } +void* APS5_VABI sceCesUcsProfileInitEucJpX0208(void* sheet) { + if (!sheet) return nullptr; + std::memcpy(sheet, EUC_JP_X0208_TAG, sizeof(EUC_JP_X0208_TAG)); + return sheet; +} + +void* APS5_VABI sceCesUcsProfileInitEucJpX0208Ss2(void* sheet) { + if (!sheet) return nullptr; + std::memcpy(sheet, EUC_JP_X0208_SS2_TAG, sizeof(EUC_JP_X0208_SS2_TAG)); + return sheet; +} + +void* APS5_VABI sceCesUcsProfileInitEucJpX0208Ss2Ss3(void* sheet) { + if (!sheet) return nullptr; + std::memcpy(sheet, EUC_JP_X0208_SS3_TAG, sizeof(EUC_JP_X0208_SS3_TAG)); + return sheet; +} + void* APS5_VABI sceCesUcsProfileInitEucKr(void* sheet) { if (!sheet) return nullptr; std::memcpy(sheet, EUC_KR_TAG, sizeof(EUC_KR_TAG)); @@ -571,107 +1364,805 @@ void* APS5_VABI sceCesUcsProfileInitBig5Cp950(void* sheet) { } int APS5_VABI sceCesUtf8StrToMbcsStr(void* context, const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8Len, uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen) { - if (!mbcs) return CES_ERROR_INVALID_DST_BUFFER; - return toMbcs(context, utf8, utf8max, utf8Len, mbcs, mbcsMax, mbcsLen, decodeUtf8); + return convertString(context, utf8, utf8max, utf8Len, mbcs, mbcsMax, mbcsLen, false, unitSource, encodeMbcs); } int APS5_VABI sceCesUtf8StrGetMbcsLen(void* context, const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8Len, uint32_t* mbcsLen) { - return toMbcs(context, utf8, utf8max, utf8Len, static_cast(nullptr), 0, mbcsLen, decodeUtf8); + return convertString(context, utf8, utf8max, utf8Len, static_cast(nullptr), 0, mbcsLen, true, unitSource, encodeMbcs); } int APS5_VABI sceCesUcs2StrToMbcsStr(void* context, const uint16_t* ucs2, uint32_t ucs2max, uint32_t* ucs2Len, uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen) { - if (!mbcs) return CES_ERROR_INVALID_DST_BUFFER; - return toMbcs(context, ucs2, ucs2max, ucs2Len, mbcs, mbcsMax, mbcsLen, decodeUcs2); + return convertString(context, ucs2, ucs2max, ucs2Len, mbcs, mbcsMax, mbcsLen, false, unitSource, encodeMbcs); } int APS5_VABI sceCesUcs2StrGetMbcsLen(void* context, const uint16_t* ucs2, uint32_t ucs2max, uint32_t* ucs2Len, uint32_t* mbcsLen) { - return toMbcs(context, ucs2, ucs2max, ucs2Len, static_cast(nullptr), 0, mbcsLen, decodeUcs2); + return convertString(context, ucs2, ucs2max, ucs2Len, static_cast(nullptr), 0, mbcsLen, true, unitSource, encodeMbcs); } int APS5_VABI sceCesMbcsStrToUtf8Str(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint8_t* utf8, uint32_t utf8max, uint32_t* utf8Len) { - if (!utf8) return CES_ERROR_INVALID_DST_BUFFER; - return fromMbcs(context, mbcs, mbcsMax, mbcsLen, utf8, utf8max, utf8Len, encodeUtf8); + return convertString(context, mbcs, mbcsMax, mbcsLen, utf8, utf8max, utf8Len, false, decodeMbcs, unitTarget); } int APS5_VABI sceCesMbcsStrGetUtf8Len(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* utf8Len) { - return fromMbcs(context, mbcs, mbcsMax, mbcsLen, static_cast(nullptr), 0, utf8Len, encodeUtf8); + return convertString(context, mbcs, mbcsMax, mbcsLen, static_cast(nullptr), 0, utf8Len, true, decodeMbcs, unitTarget); } int APS5_VABI sceCesMbcsStrToUcs2Str(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint16_t* ucs2, uint32_t ucs2max, uint32_t* ucs2Len) { - if (!ucs2) return CES_ERROR_INVALID_DST_BUFFER; - return fromMbcs(context, mbcs, mbcsMax, mbcsLen, ucs2, ucs2max, ucs2Len, encodeUcs2); + return convertString(context, mbcs, mbcsMax, mbcsLen, ucs2, ucs2max, ucs2Len, false, decodeMbcs, unitTarget); } int APS5_VABI sceCesMbcsStrGetUcs2Len(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* ucs2Len) { - return fromMbcs(context, mbcs, mbcsMax, mbcsLen, static_cast(nullptr), 0, ucs2Len, encodeUcs2); + return convertString(context, mbcs, mbcsMax, mbcsLen, static_cast(nullptr), 0, ucs2Len, true, decodeMbcs, unitTarget); } int APS5_VABI sceCesEucJpToUcs2(const void* profile, const uint8_t* euc, uint32_t eucMax, uint32_t* eucSize, uint16_t* ucs2) { + if (eucSize) *eucSize = 0; + if (ucs2) *ucs2 = 0; + if (!profile) return CES_ERROR_INVALID_PROFILE; if (!isEucJp(profile)) return CES_ERROR_INVALID_PARAMETER; - if (!euc) return CES_ERROR_INVALID_SRC_BUFFER; - if (eucMax == 0) return CES_ERROR_SRC_BUFFER_END; - if (!ucs2) return CES_ERROR_INVALID_DST_BUFFER; + if (!euc || eucMax == 0) return CES_ERROR_INVALID_SRC_BUFFER; uint32_t length = 0; uint32_t code = 0; - if (const int result = decodeMbcs(profile, euc, eucMax, length, code); result != 0) return result; - *ucs2 = static_cast(code); + const int decoded = decodeEucJp(profile, euc, eucMax, length, code); if (eucSize) *eucSize = length; + if (decoded != 0) return decoded; + if (!ucs2) return CES_ERROR_INVALID_DST_BUFFER; + *ucs2 = static_cast(code); return 0; } int APS5_VABI sceCesSbcToUtf8(const uint8_t* profile, uint8_t sbc, uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len) { + if (utf8_len) *utf8_len = 0; + if (!profile) return CES_ERROR_INVALID_PROFILE; if (!isSbc(profile)) return CES_ERROR_INVALID_PARAMETER; + uint32_t code = 0; + if (!sbcToUnicode(profile, sbc, code)) return CES_ERROR_UNASSIGNED_CODE; + uint8_t encoded[4]; + const uint32_t length = encodeUtf8(code, encoded); + if (utf8_len) *utf8_len = length; if (!utf8) return CES_ERROR_INVALID_DST_BUFFER; - - uint8_t encoded[3]; - const uint32_t length = encodeUtf8(sbcToUnicode(profile, sbc), encoded); if (utf8max < length) return CES_ERROR_DST_BUFFER_END; for (uint32_t i = 0; i < length; ++i) utf8[i] = encoded[i]; - if (utf8_len) *utf8_len = length; return 0; } int APS5_VABI sceCesUtf8ToSbc(const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len, const uint8_t* profile, uint8_t* sbc) { - if (!isSbc(profile)) return CES_ERROR_INVALID_PARAMETER; - if (!utf8) return CES_ERROR_INVALID_SRC_BUFFER; - if (utf8max == 0) return CES_ERROR_SRC_BUFFER_END; - if (!sbc) return CES_ERROR_INVALID_DST_BUFFER; - + if (utf8_len) *utf8_len = 0; + if (sbc) *sbc = 0; + if (!utf8 || utf8max == 0) return CES_ERROR_INVALID_SRC_BUFFER; uint32_t length = 0; uint32_t code = 0; - if (const int result = decodeUtf8(utf8, utf8max, length, code); result != 0) return result; - if (!unicodeToSbc(profile, code, *sbc)) return CES_ERROR_UNASSIGNED_CODE; + const int decoded = decodeScalarUtf8(utf8, utf8max, length, code); if (utf8_len) *utf8_len = length; + if (decoded != 0) return decoded; + if (!profile) return CES_ERROR_INVALID_PROFILE; + if (!isSbc(profile)) return CES_ERROR_INVALID_PARAMETER; + uint8_t value = 0; + if (!unicodeToSbc(profile, code, value)) return CES_ERROR_UNASSIGNED_CODE; + if (!sbc) return CES_ERROR_INVALID_DST_BUFFER; + *sbc = value; return 0; } int APS5_VABI sceCesEucJpToUtf8(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + if (eucLength) *eucLength = 0; + if (utf8Length) *utf8Length = 0; + if (!profile) return CES_ERROR_INVALID_PROFILE; if (!isEucJp(profile)) return CES_ERROR_INVALID_PARAMETER; - if (!eucBuffer) return CES_ERROR_INVALID_SRC_BUFFER; - if (eucMax == 0) return CES_ERROR_SRC_BUFFER_END; - if (!utf8Buffer) return CES_ERROR_INVALID_DST_BUFFER; + if (!eucBuffer || eucMax == 0) return CES_ERROR_INVALID_SRC_BUFFER; uint32_t consumed = 0; uint32_t code = 0; - if (const int result = decodeMbcs(profile, eucBuffer, eucMax, consumed, code); result != 0) return result; - uint8_t encoded[3]; + const int decoded = decodeEucJp(profile, eucBuffer, eucMax, consumed, code); + if (eucLength) *eucLength = consumed; + if (decoded != 0) return decoded; + uint8_t encoded[4]; const uint32_t produced = encodeUtf8(code, encoded); + if (utf8Length) *utf8Length = produced; + if (!utf8Buffer) return CES_ERROR_INVALID_DST_BUFFER; if (utf8Max < produced) return CES_ERROR_DST_BUFFER_END; std::memcpy(utf8Buffer, encoded, produced); - if (eucLength) *eucLength = consumed; - if (utf8Length) *utf8Length = produced; return 0; } -int APS5_VABI sceCesEucKrToUtf8(void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { +int APS5_VABI sceCesEucKrToUtf8(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { return mbcsToUtf8(isEucKr(profile), profile, eucBuffer, eucMax, eucLength, utf8Buffer, utf8Max, utf8Length); } -int APS5_VABI sceCesBig5ToUtf8(void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Length, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { +int APS5_VABI sceCesBig5ToUtf8(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Length, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { return mbcsToUtf8(isBig5(profile), profile, big5Buffer, big5Max, big5Length, utf8Buffer, utf8Max, utf8Length); } -int APS5_VABI sceCesUhcToUtf8(void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { +int APS5_VABI sceCesUhcToUtf8(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { return mbcsToUtf8(isUhc(profile), profile, uhcBuffer, uhcMax, uhcLength, utf8Buffer, utf8Max, utf8Length); } +int APS5_VABI sceCesSJisToUcs2(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* ucs2) { + return profiledToUcs2(isSjis, profile, sjisBuffer, sjisMax, sjisSize, ucs2); +} + +int APS5_VABI sceCesSJisToUtf8(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + return profiledToUtf8(isSjis, profile, sjisBuffer, sjisMax, sjisSize, utf8Buffer, utf8Max, utf8Length); +} + +int APS5_VABI sceCesSJisToUtf16(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isSjis, profile, sjisBuffer, sjisMax, sjisSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesSJisToUtf16be(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isSjis, profile, sjisBuffer, sjisMax, sjisSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesSJisToUtf16le(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isSjis, profile, sjisBuffer, sjisMax, sjisSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesSJisToUtf32(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint32_t* utf32) { + return profiledToUtf32(isSjis, profile, sjisBuffer, sjisMax, sjisSize, utf32); +} + +int APS5_VABI sceCesSJisToUtf32be(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint32_t* utf32) { + return profiledToUtf32(isSjis, profile, sjisBuffer, sjisMax, sjisSize, utf32); +} + +int APS5_VABI sceCesSJisToUtf32le(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint32_t* utf32) { + return profiledToUtf32(isSjis, profile, sjisBuffer, sjisMax, sjisSize, utf32); +} + +int APS5_VABI sceCesEucJpToUtf16(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isEucJp, profile, eucBuffer, eucMax, eucSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesEucJpToUtf16be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isEucJp, profile, eucBuffer, eucMax, eucSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesEucJpToUtf16le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isEucJp, profile, eucBuffer, eucMax, eucSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesEucJpToUtf32(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32) { + return profiledToUtf32(isEucJp, profile, eucBuffer, eucMax, eucSize, utf32); +} + +int APS5_VABI sceCesEucJpToUtf32be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32) { + return profiledToUtf32(isEucJp, profile, eucBuffer, eucMax, eucSize, utf32); +} + +int APS5_VABI sceCesEucJpToUtf32le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32) { + return profiledToUtf32(isEucJp, profile, eucBuffer, eucMax, eucSize, utf32); +} + +int APS5_VABI sceCesUcs2ToSJis(uint16_t ucs2, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize) { + return scalarToProfiled(isSjis, ucs2, profile, sjisBuffer, sjisMax, sjisSize); +} + +int APS5_VABI sceCesUtf8ToSJis(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize) { + return bufferToProfiled(isSjis, utf8Buffer, utf8Max, utf8Length, profile, sjisBuffer, sjisMax, sjisSize, decodeScalarUtf8); +} + +int APS5_VABI sceCesUtf16ToSJis(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize) { + return bufferToProfiled(isSjis, utf16Buffer, utf16Max, utf16Length, profile, sjisBuffer, sjisMax, sjisSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16beToSJis(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize) { + return bufferToProfiled(isSjis, utf16Buffer, utf16Max, utf16Length, profile, sjisBuffer, sjisMax, sjisSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16leToSJis(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize) { + return bufferToProfiled(isSjis, utf16Buffer, utf16Max, utf16Length, profile, sjisBuffer, sjisMax, sjisSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf32ToSJis(uint32_t utf32, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize) { + return scalarToProfiled(isSjis, utf32, profile, sjisBuffer, sjisMax, sjisSize); +} + +int APS5_VABI sceCesUtf32beToSJis(const uint32_t* utf32, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize) { + return utf32ToProfiled(isSjis, utf32, profile, sjisBuffer, sjisMax, sjisSize); +} + +int APS5_VABI sceCesUtf32leToSJis(const uint32_t* utf32, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize) { + return utf32ToProfiled(isSjis, utf32, profile, sjisBuffer, sjisMax, sjisSize); +} + +int APS5_VABI sceCesUcs2ToEucJp(uint16_t ucs2, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return scalarToProfiled(isEucJp, ucs2, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUtf8ToEucJp(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucJp, utf8Buffer, utf8Max, utf8Length, profile, eucBuffer, eucMax, eucSize, decodeScalarUtf8); +} + +int APS5_VABI sceCesUtf16ToEucJp(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucJp, utf16Buffer, utf16Max, utf16Length, profile, eucBuffer, eucMax, eucSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16beToEucJp(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucJp, utf16Buffer, utf16Max, utf16Length, profile, eucBuffer, eucMax, eucSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16leToEucJp(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucJp, utf16Buffer, utf16Max, utf16Length, profile, eucBuffer, eucMax, eucSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf32ToEucJp(uint32_t utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return scalarToProfiled(isEucJp, utf32, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUtf32beToEucJp(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return utf32ToProfiled(isEucJp, utf32, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUtf32leToEucJp(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return utf32ToProfiled(isEucJp, utf32, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesSJisCodeToJisX0208(uint16_t sjisCode, uint8_t* ku, uint8_t* ten) { + if (!ku || !ten) return CES_ERROR_INVALID_DST_BUFFER; + int row = 0; + int cell = 0; + if (!sjisToJis(static_cast(sjisCode >> 8), static_cast(sjisCode), row, cell) || row > 94) return CES_ERROR_INVALID_ENCODE; + *ku = static_cast(row); + *ten = static_cast(cell); + return 0; +} + +int APS5_VABI sceCesJisX0208ToSJisCode(uint8_t ku, uint8_t ten, uint16_t* sjisCode) { + if (!sjisCode) return CES_ERROR_INVALID_DST_BUFFER; + if (ku < 1 || ku > 94 || ten < 1 || ten > 94) return CES_ERROR_INVALID_ENCODE; + uint8_t lead = 0; + uint8_t trail = 0; + jisToSjis(ku, ten, lead, trail); + *sjisCode = static_cast(lead << 8 | trail); + return 0; +} + +int APS5_VABI sceCesSJisCodeToJisX0213(uint16_t sjisCode, uint8_t* men, uint8_t* ku, uint8_t* ten) { + if (!men || !ku || !ten) return CES_ERROR_INVALID_DST_BUFFER; + int row = 0; + int cell = 0; + if (!sjisToJis(static_cast(sjisCode >> 8), static_cast(sjisCode), row, cell)) return CES_ERROR_INVALID_ENCODE; + *men = row <= 94 ? 1 : 2; + *ku = row <= 94 ? static_cast(row) : JIS_X0213_PLANE2_ROWS[row - 95]; + *ten = static_cast(cell); + return 0; +} + +int APS5_VABI sceCesJisX0213ToSJisCode(uint8_t men, uint8_t ku, uint8_t ten, uint16_t* sjisCode) { + if (!sjisCode) return CES_ERROR_INVALID_DST_BUFFER; + if (men < 1 || men > 2 || ku < 1 || ku > 94 || ten < 1 || ten > 94) return CES_ERROR_INVALID_ENCODE; + int row = ku; + if (men == 2) { + const auto* found = std::find(std::begin(JIS_X0213_PLANE2_ROWS), std::end(JIS_X0213_PLANE2_ROWS), ku); + if (found == std::end(JIS_X0213_PLANE2_ROWS)) return CES_ERROR_INVALID_ENCODE; + row = 95 + static_cast(found - std::begin(JIS_X0213_PLANE2_ROWS)); + } + uint8_t lead = 0; + uint8_t trail = 0; + jisToSjis(row, ten, lead, trail); + *sjisCode = static_cast(lead << 8 | trail); + return 0; +} + +int APS5_VABI sceCesSJisGetCode(const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* sjisCode) { + if (!sjisBuffer) return CES_ERROR_INVALID_SRC_BUFFER; + if (sjisMax == 0) return CES_ERROR_SRC_BUFFER_END; + if (!sjisCode) return CES_ERROR_INVALID_DST_BUFFER; + const uint8_t lead = sjisBuffer[0]; + if (isSjisSingle(lead)) { + *sjisCode = lead; + if (sjisSize) *sjisSize = 1; + return 0; + } + if (!((lead >= 0x81 && lead <= 0x9F) || (lead >= 0xE0 && lead <= 0xFC))) return CES_ERROR_INVALID_ENCODE; + if (sjisMax < 2) return CES_ERROR_SRC_BUFFER_END; + int row = 0; + int cell = 0; + if (!sjisToJis(lead, sjisBuffer[1], row, cell)) return CES_ERROR_INVALID_ENCODE; + *sjisCode = static_cast(lead << 8 | sjisBuffer[1]); + if (sjisSize) *sjisSize = 2; + return 0; +} + +int APS5_VABI sceCesSJisPutCode(uint16_t sjisCode, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize) { + if (!sjisBuffer) return CES_ERROR_INVALID_DST_BUFFER; + const uint8_t lead = static_cast(sjisCode >> 8); + const uint8_t trail = static_cast(sjisCode); + int row = 0; + int cell = 0; + const uint32_t size = lead == 0 ? 1 : 2; + if (size == 1 ? !isSjisSingle(trail) : !sjisToJis(lead, trail, row, cell)) return CES_ERROR_INVALID_ENCODE; + if (sjisMax < size) return CES_ERROR_DST_BUFFER_END; + if (size == 2) sjisBuffer[0] = lead; + sjisBuffer[size - 1] = trail; + if (sjisSize) *sjisSize = size; + return 0; +} + +int APS5_VABI sceCesMbcToUcs2(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint16_t* ucs2) { + return profiledToUcs2(isMbcs, profile, mbcBuffer, mbcMax, mbcLen, ucs2); +} + +int APS5_VABI sceCesMbcToUtf8(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Len) { + return profiledToUtf8(isMbcs, profile, mbcBuffer, mbcMax, mbcLen, utf8Buffer, utf8Max, utf8Len); +} + +int APS5_VABI sceCesMbcToUtf16(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len) { + return profiledToUtf16(isMbcs, profile, mbcBuffer, mbcMax, mbcLen, utf16Buffer, utf16Max, utf16Len); +} + +int APS5_VABI sceCesMbcToUtf16be(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len) { + return profiledToUtf16(isMbcs, profile, mbcBuffer, mbcMax, mbcLen, utf16Buffer, utf16Max, utf16Len); +} + +int APS5_VABI sceCesMbcToUtf16le(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len) { + return profiledToUtf16(isMbcs, profile, mbcBuffer, mbcMax, mbcLen, utf16Buffer, utf16Max, utf16Len); +} + +int APS5_VABI sceCesMbcToUtf32(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint32_t* utf32) { + return profiledToUtf32(isMbcs, profile, mbcBuffer, mbcMax, mbcLen, utf32); +} + +int APS5_VABI sceCesMbcToUtf32be(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint32_t* utf32) { + return profiledToUtf32(isMbcs, profile, mbcBuffer, mbcMax, mbcLen, utf32); +} + +int APS5_VABI sceCesMbcToUtf32le(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint32_t* utf32) { + return profiledToUtf32(isMbcs, profile, mbcBuffer, mbcMax, mbcLen, utf32); +} + +int APS5_VABI sceCesUcs2ToMbc(uint16_t ucs2, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen) { + return scalarToProfiled(isMbcs, ucs2, profile, mbcBuffer, mbcMax, mbcLen); +} + +int APS5_VABI sceCesUtf8ToMbc(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Len, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen) { + return bufferToProfiled(isMbcs, utf8Buffer, utf8Max, utf8Len, profile, mbcBuffer, mbcMax, mbcLen, decodeScalarUtf8); +} + +int APS5_VABI sceCesUtf16ToMbc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen) { + return bufferToProfiled(isMbcs, utf16Buffer, utf16Max, utf16Len, profile, mbcBuffer, mbcMax, mbcLen, decodeUtf16); +} + +int APS5_VABI sceCesUtf16beToMbc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen) { + return bufferToProfiled(isMbcs, utf16Buffer, utf16Max, utf16Len, profile, mbcBuffer, mbcMax, mbcLen, decodeUtf16); +} + +int APS5_VABI sceCesUtf16leToMbc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen) { + return bufferToProfiled(isMbcs, utf16Buffer, utf16Max, utf16Len, profile, mbcBuffer, mbcMax, mbcLen, decodeUtf16); +} + +int APS5_VABI sceCesUtf32ToMbc(uint32_t utf32, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen) { + return scalarToProfiled(isMbcs, utf32, profile, mbcBuffer, mbcMax, mbcLen); +} + +int APS5_VABI sceCesUtf32beToMbc(const uint32_t* utf32, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen) { + return utf32ToProfiled(isMbcs, utf32, profile, mbcBuffer, mbcMax, mbcLen); +} + +int APS5_VABI sceCesUtf32leToMbc(const uint32_t* utf32, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen) { + return utf32ToProfiled(isMbcs, utf32, profile, mbcBuffer, mbcMax, mbcLen); +} + +int APS5_VABI sceCesSbcToUcs2(const uint8_t* profile, uint8_t sbc, uint16_t* ucs2) { + uint32_t code = 0; + if (const int result = sbcToUnit(profile, sbc, ucs2, code); result != 0) return result; + *ucs2 = static_cast(code); + return 0; +} + +int APS5_VABI sceCesSbcToUtf16(const uint8_t* profile, uint8_t sbc, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len) { + return sbcToUtf16(profile, sbc, utf16, utf16max, utf16Len); +} + +int APS5_VABI sceCesSbcToUtf16be(const uint8_t* profile, uint8_t sbc, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len) { + return sbcToUtf16(profile, sbc, utf16, utf16max, utf16Len); +} + +int APS5_VABI sceCesSbcToUtf16le(const uint8_t* profile, uint8_t sbc, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len) { + return sbcToUtf16(profile, sbc, utf16, utf16max, utf16Len); +} + +int APS5_VABI sceCesSbcToUtf32(const uint8_t* profile, uint8_t sbc, uint32_t* utf32) { + return sbcToUtf32(profile, sbc, utf32); +} + +int APS5_VABI sceCesSbcToUtf32be(const uint8_t* profile, uint8_t sbc, uint32_t* utf32) { + return sbcToUtf32(profile, sbc, utf32); +} + +int APS5_VABI sceCesSbcToUtf32le(const uint8_t* profile, uint8_t sbc, uint32_t* utf32) { + return sbcToUtf32(profile, sbc, utf32); +} + +int APS5_VABI sceCesUcs2ToSbc(uint16_t ucs2, const uint8_t* profile, uint8_t* sbc) { + return scalarToSbc(ucs2, profile, sbc); +} + +int APS5_VABI sceCesUtf16ToSbc(const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, const uint8_t* profile, uint8_t* sbc) { + return utf16ToSbc(utf16, utf16max, utf16Len, profile, sbc); +} + +int APS5_VABI sceCesUtf16beToSbc(const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, const uint8_t* profile, uint8_t* sbc) { + return utf16ToSbc(utf16, utf16max, utf16Len, profile, sbc); +} + +int APS5_VABI sceCesUtf16leToSbc(const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, const uint8_t* profile, uint8_t* sbc) { + return utf16ToSbc(utf16, utf16max, utf16Len, profile, sbc); +} + +int APS5_VABI sceCesUtf32ToSbc(uint32_t utf32, const uint8_t* profile, uint8_t* sbc) { + return scalarToSbc(utf32, profile, sbc); +} + +int APS5_VABI sceCesUtf32beToSbc(const uint32_t* utf32, const uint8_t* profile, uint8_t* sbc) { + return utf32ToSbc(utf32, profile, sbc); +} + +int APS5_VABI sceCesUtf32leToSbc(const uint32_t* utf32, const uint8_t* profile, uint8_t* sbc) { + return utf32ToSbc(utf32, profile, sbc); +} + +int APS5_VABI sceCesUtf16StrToMbcsStr(void* context, const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen) { + return convertString(context, utf16, utf16max, utf16Len, mbcs, mbcsMax, mbcsLen, false, unitSource, uint16_t>, encodeMbcs); +} + +int APS5_VABI sceCesUtf16StrGetMbcsLen(void* context, const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, uint32_t* mbcsLen) { + return convertString(context, utf16, utf16max, utf16Len, static_cast(nullptr), 0, mbcsLen, true, unitSource, uint16_t>, encodeMbcs); +} + +int APS5_VABI sceCesMbcsStrToUtf16Str(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len) { + return convertString(context, mbcs, mbcsMax, mbcsLen, utf16, utf16max, utf16Len, false, decodeMbcs, unitTarget); +} + +int APS5_VABI sceCesMbcsStrGetUtf16Len(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* utf16Len) { + return convertString(context, mbcs, mbcsMax, mbcsLen, static_cast(nullptr), 0, utf16Len, true, decodeMbcs, unitTarget); +} + +int APS5_VABI sceCesUtf32StrToMbcsStr(void* context, const uint32_t* utf32, uint32_t utf32max, uint32_t* utf32Len, uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen) { + return convertString(context, utf32, utf32max, utf32Len, mbcs, mbcsMax, mbcsLen, false, unitSource, encodeMbcs); +} + +int APS5_VABI sceCesUtf32StrGetMbcsLen(void* context, const uint32_t* utf32, uint32_t utf32max, uint32_t* utf32Len, uint32_t* mbcsLen) { + return convertString(context, utf32, utf32max, utf32Len, static_cast(nullptr), 0, mbcsLen, true, unitSource, encodeMbcs); +} + +int APS5_VABI sceCesMbcsStrToUtf32Str(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* utf32, uint32_t utf32max, uint32_t* utf32Len) { + return convertString(context, mbcs, mbcsMax, mbcsLen, utf32, utf32max, utf32Len, false, decodeMbcs, unitTarget); +} + +int APS5_VABI sceCesMbcsStrGetUtf32Len(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* utf32Len) { + return convertString(context, mbcs, mbcsMax, mbcsLen, static_cast(nullptr), 0, utf32Len, true, decodeMbcs, unitTarget); +} + +void* APS5_VABI sceCesUcsProfileInitEucCnGb2312(void* sheet) { + if (!sheet) return nullptr; + std::memcpy(sheet, EUC_CN_TAG, sizeof(EUC_CN_TAG)); + return sheet; +} + +void* APS5_VABI sceCesUcsProfileInitGbkCp936(void* sheet) { + if (!sheet) return nullptr; + std::memcpy(sheet, GBK_TAG, sizeof(GBK_TAG)); + return sheet; +} + +void* APS5_VABI sceCesUcsProfileInitGb18030_2000(void* sheet) { + if (!sheet) return nullptr; + std::memcpy(sheet, GB18030_2000_TAG, sizeof(GB18030_2000_TAG)); + return sheet; +} + +void* APS5_VABI sceCesUcsProfileInitGb18030_2005(void* sheet) { + if (!sheet) return nullptr; + std::memcpy(sheet, GB18030_2005_TAG, sizeof(GB18030_2005_TAG)); + return sheet; +} + +int APS5_VABI sceCesEucCnToUcs2(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* ucs2) { + return profiledToUcs2(isEucCn, profile, eucBuffer, eucMax, eucSize, ucs2); +} + +int APS5_VABI sceCesEucCnToUtf8(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + return profiledToUtf8(isEucCn, profile, eucBuffer, eucMax, eucSize, utf8Buffer, utf8Max, utf8Length); +} + +int APS5_VABI sceCesEucCnToUtf16(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isEucCn, profile, eucBuffer, eucMax, eucSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesEucCnToUtf16be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isEucCn, profile, eucBuffer, eucMax, eucSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesEucCnToUtf16le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isEucCn, profile, eucBuffer, eucMax, eucSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesEucCnToUtf32(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32) { + return profiledToUtf32(isEucCn, profile, eucBuffer, eucMax, eucSize, utf32); +} + +int APS5_VABI sceCesEucCnToUtf32be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32) { + return profiledToUtf32(isEucCn, profile, eucBuffer, eucMax, eucSize, utf32); +} + +int APS5_VABI sceCesEucCnToUtf32le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32) { + return profiledToUtf32(isEucCn, profile, eucBuffer, eucMax, eucSize, utf32); +} + +int APS5_VABI sceCesUcs2ToEucCn(uint16_t ucs2, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return scalarToProfiled(isEucCn, ucs2, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUtf8ToEucCn(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucCn, utf8Buffer, utf8Max, utf8Length, profile, eucBuffer, eucMax, eucSize, decodeScalarUtf8); +} + +int APS5_VABI sceCesUtf16ToEucCn(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucCn, utf16Buffer, utf16Max, utf16Length, profile, eucBuffer, eucMax, eucSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16beToEucCn(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucCn, utf16Buffer, utf16Max, utf16Length, profile, eucBuffer, eucMax, eucSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16leToEucCn(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucCn, utf16Buffer, utf16Max, utf16Length, profile, eucBuffer, eucMax, eucSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf32ToEucCn(uint32_t utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return scalarToProfiled(isEucCn, utf32, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUtf32beToEucCn(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return utf32ToProfiled(isEucCn, utf32, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUtf32leToEucCn(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return utf32ToProfiled(isEucCn, utf32, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesGbToUcs2(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint16_t* ucs2) { + return profiledToUcs2(isGb, profile, gbBuffer, gbMax, gbSize, ucs2); +} + +int APS5_VABI sceCesGbToUtf8(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + return profiledToUtf8(isGb, profile, gbBuffer, gbMax, gbSize, utf8Buffer, utf8Max, utf8Length); +} + +int APS5_VABI sceCesGbToUtf16(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isGb, profile, gbBuffer, gbMax, gbSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesGbToUtf16be(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isGb, profile, gbBuffer, gbMax, gbSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesGbToUtf16le(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isGb, profile, gbBuffer, gbMax, gbSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesGbToUtf32(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint32_t* utf32) { + return profiledToUtf32(isGb, profile, gbBuffer, gbMax, gbSize, utf32); +} + +int APS5_VABI sceCesGbToUtf32be(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint32_t* utf32) { + return profiledToUtf32(isGb, profile, gbBuffer, gbMax, gbSize, utf32); +} + +int APS5_VABI sceCesGbToUtf32le(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint32_t* utf32) { + return profiledToUtf32(isGb, profile, gbBuffer, gbMax, gbSize, utf32); +} + +int APS5_VABI sceCesUcs2ToGb(uint16_t ucs2, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize) { + return scalarToProfiled(isGb, ucs2, profile, gbBuffer, gbMax, gbSize); +} + +int APS5_VABI sceCesUtf8ToGb(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize) { + return bufferToProfiled(isGb, utf8Buffer, utf8Max, utf8Length, profile, gbBuffer, gbMax, gbSize, decodeScalarUtf8); +} + +int APS5_VABI sceCesUtf16ToGb(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize) { + return bufferToProfiled(isGb, utf16Buffer, utf16Max, utf16Length, profile, gbBuffer, gbMax, gbSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16beToGb(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize) { + return bufferToProfiled(isGb, utf16Buffer, utf16Max, utf16Length, profile, gbBuffer, gbMax, gbSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16leToGb(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize) { + return bufferToProfiled(isGb, utf16Buffer, utf16Max, utf16Length, profile, gbBuffer, gbMax, gbSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf32ToGb(uint32_t utf32, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize) { + return scalarToProfiled(isGb, utf32, profile, gbBuffer, gbMax, gbSize); +} + +int APS5_VABI sceCesUtf32beToGb(const uint32_t* utf32, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize) { + return utf32ToProfiled(isGb, utf32, profile, gbBuffer, gbMax, gbSize); +} + +int APS5_VABI sceCesUtf32leToGb(const uint32_t* utf32, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize) { + return utf32ToProfiled(isGb, utf32, profile, gbBuffer, gbMax, gbSize); +} + +int APS5_VABI sceCesEucKrToUcs2(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* ucs2) { + return profiledToUcs2(isEucKr, profile, eucBuffer, eucMax, eucSize, ucs2); +} + +int APS5_VABI sceCesEucKrToUtf16(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isEucKr, profile, eucBuffer, eucMax, eucSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesEucKrToUtf16be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isEucKr, profile, eucBuffer, eucMax, eucSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesEucKrToUtf16le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isEucKr, profile, eucBuffer, eucMax, eucSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesEucKrToUtf32(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32) { + return profiledToUtf32(isEucKr, profile, eucBuffer, eucMax, eucSize, utf32); +} + +int APS5_VABI sceCesEucKrToUtf32be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32) { + return profiledToUtf32(isEucKr, profile, eucBuffer, eucMax, eucSize, utf32); +} + +int APS5_VABI sceCesEucKrToUtf32le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32) { + return profiledToUtf32(isEucKr, profile, eucBuffer, eucMax, eucSize, utf32); +} + +int APS5_VABI sceCesUhcToUcs2(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint16_t* ucs2) { + return profiledToUcs2(isUhc, profile, uhcBuffer, uhcMax, uhcSize, ucs2); +} + +int APS5_VABI sceCesUhcToUtf16(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isUhc, profile, uhcBuffer, uhcMax, uhcSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesUhcToUtf16be(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isUhc, profile, uhcBuffer, uhcMax, uhcSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesUhcToUtf16le(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isUhc, profile, uhcBuffer, uhcMax, uhcSize, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesUhcToUtf32(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint32_t* utf32) { + return profiledToUtf32(isUhc, profile, uhcBuffer, uhcMax, uhcSize, utf32); +} + +int APS5_VABI sceCesUhcToUtf32be(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint32_t* utf32) { + return profiledToUtf32(isUhc, profile, uhcBuffer, uhcMax, uhcSize, utf32); +} + +int APS5_VABI sceCesUhcToUtf32le(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint32_t* utf32) { + return profiledToUtf32(isUhc, profile, uhcBuffer, uhcMax, uhcSize, utf32); +} + +int APS5_VABI sceCesBig5ToUcs2(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint16_t* ucs2) { + return profiledToUcs2(isBig5, profile, big5Buffer, big5Max, big5Size, ucs2); +} + +int APS5_VABI sceCesBig5ToUtf16(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isBig5, profile, big5Buffer, big5Max, big5Size, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesBig5ToUtf16be(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isBig5, profile, big5Buffer, big5Max, big5Size, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesBig5ToUtf16le(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length) { + return profiledToUtf16(isBig5, profile, big5Buffer, big5Max, big5Size, utf16Buffer, utf16Max, utf16Length); +} + +int APS5_VABI sceCesBig5ToUtf32(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint32_t* utf32) { + return profiledToUtf32(isBig5, profile, big5Buffer, big5Max, big5Size, utf32); +} + +int APS5_VABI sceCesBig5ToUtf32be(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint32_t* utf32) { + return profiledToUtf32(isBig5, profile, big5Buffer, big5Max, big5Size, utf32); +} + +int APS5_VABI sceCesBig5ToUtf32le(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint32_t* utf32) { + return profiledToUtf32(isBig5, profile, big5Buffer, big5Max, big5Size, utf32); +} + +int APS5_VABI sceCesUcs2ToEucKr(uint16_t ucs2, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return scalarToProfiled(isEucKr, ucs2, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUtf8ToEucKr(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucKr, utf8Buffer, utf8Max, utf8Length, profile, eucBuffer, eucMax, eucSize, decodeScalarUtf8); +} + +int APS5_VABI sceCesUtf16ToEucKr(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucKr, utf16Buffer, utf16Max, utf16Length, profile, eucBuffer, eucMax, eucSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16beToEucKr(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucKr, utf16Buffer, utf16Max, utf16Length, profile, eucBuffer, eucMax, eucSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16leToEucKr(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return bufferToProfiled(isEucKr, utf16Buffer, utf16Max, utf16Length, profile, eucBuffer, eucMax, eucSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf32ToEucKr(uint32_t utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return scalarToProfiled(isEucKr, utf32, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUtf32beToEucKr(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return utf32ToProfiled(isEucKr, utf32, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUtf32leToEucKr(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize) { + return utf32ToProfiled(isEucKr, utf32, profile, eucBuffer, eucMax, eucSize); +} + +int APS5_VABI sceCesUcs2ToUhc(uint16_t ucs2, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize) { + return scalarToProfiled(isUhc, ucs2, profile, uhcBuffer, uhcMax, uhcSize); +} + +int APS5_VABI sceCesUtf8ToUhc(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize) { + return bufferToProfiled(isUhc, utf8Buffer, utf8Max, utf8Length, profile, uhcBuffer, uhcMax, uhcSize, decodeScalarUtf8); +} + +int APS5_VABI sceCesUtf16ToUhc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize) { + return bufferToProfiled(isUhc, utf16Buffer, utf16Max, utf16Length, profile, uhcBuffer, uhcMax, uhcSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16beToUhc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize) { + return bufferToProfiled(isUhc, utf16Buffer, utf16Max, utf16Length, profile, uhcBuffer, uhcMax, uhcSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf16leToUhc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize) { + return bufferToProfiled(isUhc, utf16Buffer, utf16Max, utf16Length, profile, uhcBuffer, uhcMax, uhcSize, decodeUtf16); +} + +int APS5_VABI sceCesUtf32ToUhc(uint32_t utf32, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize) { + return scalarToProfiled(isUhc, utf32, profile, uhcBuffer, uhcMax, uhcSize); +} + +int APS5_VABI sceCesUtf32beToUhc(const uint32_t* utf32, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize) { + return utf32ToProfiled(isUhc, utf32, profile, uhcBuffer, uhcMax, uhcSize); +} + +int APS5_VABI sceCesUtf32leToUhc(const uint32_t* utf32, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize) { + return utf32ToProfiled(isUhc, utf32, profile, uhcBuffer, uhcMax, uhcSize); +} + +int APS5_VABI sceCesUcs2ToBig5(uint16_t ucs2, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size) { + return scalarToProfiled(isBig5, ucs2, profile, big5Buffer, big5Max, big5Size); +} + +int APS5_VABI sceCesUtf8ToBig5(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size) { + return bufferToProfiled(isBig5, utf8Buffer, utf8Max, utf8Length, profile, big5Buffer, big5Max, big5Size, decodeScalarUtf8); +} + +int APS5_VABI sceCesUtf16ToBig5(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size) { + return bufferToProfiled(isBig5, utf16Buffer, utf16Max, utf16Length, profile, big5Buffer, big5Max, big5Size, decodeUtf16); +} + +int APS5_VABI sceCesUtf16beToBig5(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size) { + return bufferToProfiled(isBig5, utf16Buffer, utf16Max, utf16Length, profile, big5Buffer, big5Max, big5Size, decodeUtf16); +} + +int APS5_VABI sceCesUtf16leToBig5(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size) { + return bufferToProfiled(isBig5, utf16Buffer, utf16Max, utf16Length, profile, big5Buffer, big5Max, big5Size, decodeUtf16); +} + +int APS5_VABI sceCesUtf32ToBig5(uint32_t utf32, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size) { + return scalarToProfiled(isBig5, utf32, profile, big5Buffer, big5Max, big5Size); +} + +int APS5_VABI sceCesUtf32beToBig5(const uint32_t* utf32, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size) { + return utf32ToProfiled(isBig5, utf32, profile, big5Buffer, big5Max, big5Size); +} + +int APS5_VABI sceCesUtf32leToBig5(const uint32_t* utf32, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size) { + return utf32ToProfiled(isBig5, utf32, profile, big5Buffer, big5Max, big5Size); +} + } diff --git a/core/libs/prx/libSceCesCs/Gb18030.hpp b/core/libs/prx/libSceCesCs/Gb18030.hpp new file mode 100644 index 0000000000..5f13d60813 --- /dev/null +++ b/core/libs/prx/libSceCesCs/Gb18030.hpp @@ -0,0 +1,135 @@ +#pragma once + +#include + +namespace Gb18030 { + +inline constexpr uint32_t TWO_BYTE_2000[111][3] = { + {0x17C0, 0xE4C6, 0x60}, {0x187E, 0xE526, 0x60}, {0x18E8, 0xE766, 0x6}, {0x1920, 0x20AC, 0x1}, {0x1921, 0xE76D, 0x1}, {0x192C, 0xE76E, 0x2}, + {0x193A, 0xE770, 0x2}, {0x193C, 0xE586, 0x60}, {0x19FA, 0xE5E6, 0x60}, {0x1AAD, 0xE772, 0xB}, {0x1AB8, 0xE646, 0x60}, {0x1B6E, 0xE77D, 0x8}, + {0x1B76, 0xE6A6, 0x60}, {0x1BEE, 0xE785, 0x8}, {0x1C0E, 0xE78D, 0x7}, {0x1C21, 0xE794, 0x2}, {0x1C28, 0xE796, 0x1}, {0x1C2B, 0xE797, 0x9}, + {0x1C34, 0xE706, 0x60}, {0x1CB5, 0xE7A0, 0xF}, {0x1CE5, 0xE7AF, 0xD}, {0x1D47, 0xE7BC, 0xB}, {0x1D6D, 0xE7C7, 0x1}, {0x1D70, 0x1F9, 0x1}, + {0x1D72, 0xE7C9, 0x4}, {0x1D9B, 0xE7CD, 0x15}, {0x1DC8, 0xE7E2, 0x1}, {0x1DCB, 0xE7E3, 0x1}, {0x1DCD, 0xE7E4, 0x3}, {0x1DF8, 0x303E, 0x1}, + {0x1DF9, 0x2FF0, 0xC}, {0x1E06, 0xE7F4, 0xD}, {0x1E5F, 0xE801, 0xF}, {0x1ECE, 0xE000, 0x5E}, {0x1F8C, 0xE05E, 0x5E}, {0x204A, 0xE0BC, 0x5E}, + {0x2108, 0xE11A, 0x5E}, {0x21C6, 0xE178, 0x5E}, {0x2284, 0xE1D6, 0x5E}, {0x408D, 0xE810, 0x5}, {0x58B2, 0xE234, 0x5E}, {0x5970, 0xE292, 0x5E}, + {0x5A2E, 0xE2F0, 0x5E}, {0x5AEC, 0xE34E, 0x5E}, {0x5BAA, 0xE3AC, 0x5E}, {0x5C68, 0xE40A, 0x5E}, {0x5CD6, 0x2E81, 0x1}, {0x5CD7, 0xE816, 0x3}, + {0x5CDA, 0x2E84, 0x1}, {0x5CDB, 0x3473, 0x1}, {0x5CDC, 0x3447, 0x1}, {0x5CDD, 0x2E88, 0x1}, {0x5CDE, 0x2E8B, 0x1}, {0x5CDF, 0xE81E, 0x1}, + {0x5CE0, 0x359E, 0x1}, {0x5CE1, 0x361A, 0x1}, {0x5CE2, 0x360E, 0x1}, {0x5CE3, 0x2E8C, 0x1}, {0x5CE4, 0x2E97, 0x1}, {0x5CE5, 0x396E, 0x1}, + {0x5CE6, 0x3918, 0x1}, {0x5CE7, 0xE826, 0x1}, {0x5CE8, 0x39CF, 0x1}, {0x5CE9, 0x39DF, 0x1}, {0x5CEA, 0x3A73, 0x1}, {0x5CEB, 0x39D0, 0x1}, + {0x5CEC, 0xE82B, 0x2}, {0x5CEE, 0x3B4E, 0x1}, {0x5CEF, 0x3C6E, 0x1}, {0x5CF0, 0x3CE0, 0x1}, {0x5CF1, 0x2EA7, 0x1}, {0x5CF2, 0xE831, 0x2}, + {0x5CF4, 0x2EAA, 0x1}, {0x5CF5, 0x4056, 0x1}, {0x5CF6, 0x415F, 0x1}, {0x5CF7, 0x2EAE, 0x1}, {0x5CF8, 0x4337, 0x1}, {0x5CF9, 0x2EB3, 0x1}, + {0x5CFA, 0x2EB6, 0x2}, {0x5CFC, 0xE83B, 0x1}, {0x5CFD, 0x43B1, 0x1}, {0x5CFE, 0x43AC, 0x1}, {0x5CFF, 0x2EBB, 0x1}, {0x5D00, 0x43DD, 0x1}, + {0x5D01, 0x44D6, 0x1}, {0x5D02, 0x4661, 0x1}, {0x5D03, 0x464C, 0x1}, {0x5D04, 0xE843, 0x1}, {0x5D05, 0x4723, 0x1}, {0x5D06, 0x4729, 0x1}, + {0x5D07, 0x477C, 0x1}, {0x5D08, 0x478D, 0x1}, {0x5D09, 0x2ECA, 0x1}, {0x5D0A, 0x4947, 0x1}, {0x5D0B, 0x497A, 0x1}, {0x5D0C, 0x497D, 0x1}, + {0x5D0D, 0x4982, 0x2}, {0x5D0F, 0x4985, 0x2}, {0x5D11, 0x499F, 0x1}, {0x5D12, 0x499B, 0x1}, {0x5D13, 0x49B7, 0x1}, {0x5D14, 0x49B6, 0x1}, + {0x5D15, 0xE854, 0x2}, {0x5D17, 0x4CA3, 0x1}, {0x5D18, 0x4C9F, 0x3}, {0x5D1B, 0x4C77, 0x1}, {0x5D1C, 0x4CA2, 0x1}, {0x5D1D, 0x4D13, 0x7}, + {0x5D24, 0x4DAE, 0x1}, {0x5D25, 0xE864, 0x1}, {0x5D26, 0xE468, 0x5E}, +}; + +inline constexpr uint32_t FOUR_BYTE_2000[207][3] = { + {0x0, 0x80, 0x24}, {0x24, 0xA5, 0x2}, {0x26, 0xA9, 0x7}, {0x2D, 0xB2, 0x5}, {0x32, 0xB8, 0x1F}, {0x51, 0xD8, 0x8}, + {0x59, 0xE2, 0x6}, {0x5F, 0xEB, 0x1}, {0x60, 0xEE, 0x4}, {0x64, 0xF4, 0x3}, {0x67, 0xF8, 0x1}, {0x68, 0xFB, 0x1}, + {0x69, 0xFD, 0x4}, {0x6D, 0x102, 0x11}, {0x7E, 0x114, 0x7}, {0x85, 0x11C, 0xF}, {0x94, 0x12C, 0x18}, {0xAC, 0x145, 0x3}, + {0xAF, 0x149, 0x4}, {0xB3, 0x14E, 0x1D}, {0xD0, 0x16C, 0x62}, {0x132, 0x1CF, 0x1}, {0x133, 0x1D1, 0x1}, {0x134, 0x1D3, 0x1}, + {0x135, 0x1D5, 0x1}, {0x136, 0x1D7, 0x1}, {0x137, 0x1D9, 0x1}, {0x138, 0x1DB, 0x1}, {0x139, 0x1DD, 0x1C}, {0x155, 0x1FA, 0x57}, + {0x1AC, 0x252, 0xF}, {0x1BB, 0x262, 0x65}, {0x220, 0x2C8, 0x1}, {0x221, 0x2CC, 0xD}, {0x22E, 0x2DA, 0xB7}, {0x2E5, 0x3A2, 0x1}, + {0x2E6, 0x3AA, 0x7}, {0x2ED, 0x3C2, 0x1}, {0x2EE, 0x3CA, 0x37}, {0x325, 0x402, 0xE}, {0x333, 0x450, 0x1}, {0x334, 0x452, 0x1BBE}, + {0x1EF2, 0x2011, 0x2}, {0x1EF4, 0x2017, 0x1}, {0x1EF5, 0x201A, 0x2}, {0x1EF7, 0x201E, 0x7}, {0x1EFE, 0x2027, 0x9}, {0x1F07, 0x2031, 0x1}, + {0x1F08, 0x2034, 0x1}, {0x1F09, 0x2036, 0x5}, {0x1F0E, 0x203C, 0x70}, {0x1F7E, 0x20AD, 0x56}, {0x1FD4, 0x2104, 0x1}, {0x1FD5, 0x2106, 0x3}, + {0x1FD8, 0x210A, 0xC}, {0x1FE4, 0x2117, 0xA}, {0x1FEE, 0x2122, 0x3E}, {0x202C, 0x216C, 0x4}, {0x2030, 0x217A, 0x16}, {0x2046, 0x2194, 0x2}, + {0x2048, 0x219A, 0x6E}, {0x20B6, 0x2209, 0x6}, {0x20BC, 0x2210, 0x1}, {0x20BD, 0x2212, 0x3}, {0x20C0, 0x2216, 0x4}, {0x20C4, 0x221B, 0x2}, + {0x20C6, 0x2221, 0x2}, {0x20C8, 0x2224, 0x1}, {0x20C9, 0x2226, 0x1}, {0x20CA, 0x222C, 0x2}, {0x20CC, 0x222F, 0x5}, {0x20D1, 0x2238, 0x5}, + {0x20D6, 0x223E, 0xA}, {0x20E0, 0x2249, 0x3}, {0x20E3, 0x224D, 0x5}, {0x20E8, 0x2253, 0xD}, {0x20F5, 0x2262, 0x2}, {0x20F7, 0x2268, 0x6}, + {0x20FD, 0x2270, 0x25}, {0x2122, 0x2296, 0x3}, {0x2125, 0x229A, 0xB}, {0x2130, 0x22A6, 0x19}, {0x2149, 0x22C0, 0x52}, {0x219B, 0x2313, 0x14D}, + {0x22E8, 0x246A, 0xA}, {0x22F2, 0x249C, 0x64}, {0x2356, 0x254C, 0x4}, {0x235A, 0x2574, 0xD}, {0x2367, 0x2590, 0x3}, {0x236A, 0x2596, 0xA}, + {0x2374, 0x25A2, 0x10}, {0x2384, 0x25B4, 0x8}, {0x238C, 0x25BE, 0x8}, {0x2394, 0x25C8, 0x3}, {0x2397, 0x25CC, 0x2}, {0x2399, 0x25D0, 0x12}, + {0x23AB, 0x25E6, 0x1F}, {0x23CA, 0x2607, 0x2}, {0x23CC, 0x260A, 0x36}, {0x2402, 0x2641, 0x1}, {0x2403, 0x2643, 0x83E}, {0x2C41, 0x2E82, 0x2}, + {0x2C43, 0x2E85, 0x3}, {0x2C46, 0x2E89, 0x2}, {0x2C48, 0x2E8D, 0xA}, {0x2C52, 0x2E98, 0xF}, {0x2C61, 0x2EA8, 0x2}, {0x2C63, 0x2EAB, 0x3}, + {0x2C66, 0x2EAF, 0x4}, {0x2C6A, 0x2EB4, 0x2}, {0x2C6C, 0x2EB8, 0x3}, {0x2C6F, 0x2EBC, 0xE}, {0x2C7D, 0x2ECB, 0x125}, {0x2DA2, 0x2FFC, 0x4}, + {0x2DA6, 0x3004, 0x1}, {0x2DA7, 0x3018, 0x5}, {0x2DAC, 0x301F, 0x2}, {0x2DAE, 0x302A, 0x14}, {0x2DC2, 0x303F, 0x2}, {0x2DC4, 0x3094, 0x7}, + {0x2DCB, 0x309F, 0x2}, {0x2DCD, 0x30F7, 0x5}, {0x2DD2, 0x30FF, 0x6}, {0x2DD8, 0x312A, 0xF6}, {0x2ECE, 0x322A, 0x7}, {0x2ED5, 0x3232, 0x71}, + {0x2F46, 0x32A4, 0xEA}, {0x3030, 0x3390, 0xC}, {0x303C, 0x339F, 0x2}, {0x303E, 0x33A2, 0x22}, {0x3060, 0x33C5, 0x9}, {0x3069, 0x33CF, 0x2}, + {0x306B, 0x33D3, 0x2}, {0x306D, 0x33D6, 0x71}, {0x30DE, 0x3448, 0x2B}, {0x3109, 0x3474, 0x12A}, {0x3233, 0x359F, 0x6F}, {0x32A2, 0x360F, 0xB}, + {0x32AD, 0x361B, 0x2FD}, {0x35AA, 0x3919, 0x55}, {0x35FF, 0x396F, 0x60}, {0x365F, 0x39D1, 0xE}, {0x366D, 0x39E0, 0x93}, {0x3700, 0x3A74, 0xDA}, + {0x37DA, 0x3B4F, 0x11F}, {0x38F9, 0x3C6F, 0x71}, {0x396A, 0x3CE1, 0x375}, {0x3CDF, 0x4057, 0x108}, {0x3DE7, 0x4160, 0x1D7}, {0x3FBE, 0x4338, 0x74}, + {0x4032, 0x43AD, 0x4}, {0x4036, 0x43B2, 0x2B}, {0x4061, 0x43DE, 0xF8}, {0x4159, 0x44D7, 0x175}, {0x42CE, 0x464D, 0x14}, {0x42E2, 0x4662, 0xC1}, + {0x43A3, 0x4724, 0x5}, {0x43A8, 0x472A, 0x52}, {0x43FA, 0x477D, 0x10}, {0x440A, 0x478E, 0x1B9}, {0x45C3, 0x4948, 0x32}, {0x45F5, 0x497B, 0x2}, + {0x45F7, 0x497E, 0x4}, {0x45FB, 0x4984, 0x1}, {0x45FC, 0x4987, 0x14}, {0x4610, 0x499C, 0x3}, {0x4613, 0x49A0, 0x16}, {0x4629, 0x49B8, 0x2BF}, + {0x48E8, 0x4C78, 0x27}, {0x490F, 0x4CA4, 0x6F}, {0x497E, 0x4D1A, 0x94}, {0x4A12, 0x4DAF, 0x51}, {0x4A63, 0x9FA6, 0x385A}, {0x82BD, 0xE76C, 0x1}, + {0x82BE, 0xE7C8, 0x1}, {0x82BF, 0xE7E7, 0xD}, {0x82CC, 0xE815, 0x1}, {0x82CD, 0xE819, 0x5}, {0x82D2, 0xE81F, 0x7}, {0x82D9, 0xE827, 0x4}, + {0x82DD, 0xE82D, 0x4}, {0x82E1, 0xE833, 0x8}, {0x82E9, 0xE83C, 0x7}, {0x82F0, 0xE844, 0x10}, {0x8300, 0xE856, 0xE}, {0x830E, 0xE865, 0x10C7}, + {0x93D5, 0xF92D, 0x4C}, {0x9421, 0xF97A, 0x1B}, {0x943C, 0xF996, 0x51}, {0x948D, 0xF9E8, 0x9}, {0x9496, 0xF9F2, 0x1A}, {0x94B0, 0xFA10, 0x1}, + {0x94B1, 0xFA12, 0x1}, {0x94B2, 0xFA15, 0x3}, {0x94B5, 0xFA19, 0x6}, {0x94BB, 0xFA22, 0x1}, {0x94BC, 0xFA25, 0x2}, {0x94BE, 0xFA2A, 0x406}, + {0x98C4, 0xFE32, 0x1}, {0x98C5, 0xFE45, 0x4}, {0x98C9, 0xFE53, 0x1}, {0x98CA, 0xFE58, 0x1}, {0x98CB, 0xFE67, 0x1}, {0x98CC, 0xFE6C, 0x95}, + {0x9961, 0xFF5F, 0x81}, {0x99E2, 0xFFE6, 0x1A}, {0x2E248, 0x10000, 0x100000}, +}; + +inline constexpr uint32_t TWO_BYTE_2005[118][3] = { + {0x17C0, 0xE4C6, 0x60}, {0x187E, 0xE526, 0x60}, {0x18E8, 0xE766, 0x6}, {0x1920, 0x20AC, 0x1}, {0x1921, 0xE76D, 0x1}, {0x192C, 0xE76E, 0x2}, + {0x193A, 0xE770, 0x2}, {0x193C, 0xE586, 0x60}, {0x19FA, 0xE5E6, 0x60}, {0x1AAD, 0xE772, 0xB}, {0x1AB8, 0xE646, 0x60}, {0x1B6E, 0xE77D, 0x8}, + {0x1B76, 0xE6A6, 0x60}, {0x1BEE, 0xE785, 0x8}, {0x1C0E, 0xFE10, 0x1}, {0x1C0F, 0xFE12, 0x1}, {0x1C10, 0xFE11, 0x1}, {0x1C11, 0xFE13, 0x4}, + {0x1C21, 0xFE17, 0x2}, {0x1C28, 0xFE19, 0x1}, {0x1C2B, 0xE797, 0x9}, {0x1C34, 0xE706, 0x60}, {0x1CB5, 0xE7A0, 0xF}, {0x1CE5, 0xE7AF, 0xD}, + {0x1D47, 0xE7BC, 0xB}, {0x1D6D, 0x1E3F, 0x1}, {0x1D70, 0x1F9, 0x1}, {0x1D72, 0xE7C9, 0x4}, {0x1D9B, 0xE7CD, 0x15}, {0x1DC8, 0xE7E2, 0x1}, + {0x1DCB, 0xE7E3, 0x1}, {0x1DCD, 0xE7E4, 0x3}, {0x1DF8, 0x303E, 0x1}, {0x1DF9, 0x2FF0, 0xC}, {0x1E06, 0xE7F4, 0xD}, {0x1E5F, 0xE801, 0xF}, + {0x1ECE, 0xE000, 0x5E}, {0x1F8C, 0xE05E, 0x5E}, {0x204A, 0xE0BC, 0x5E}, {0x2108, 0xE11A, 0x5E}, {0x21C6, 0xE178, 0x5E}, {0x2284, 0xE1D6, 0x5E}, + {0x408D, 0xE810, 0x5}, {0x58B2, 0xE234, 0x5E}, {0x5970, 0xE292, 0x5E}, {0x5A2E, 0xE2F0, 0x5E}, {0x5AEC, 0xE34E, 0x5E}, {0x5BAA, 0xE3AC, 0x5E}, + {0x5C68, 0xE40A, 0x5E}, {0x5CD6, 0x2E81, 0x1}, {0x5CD7, 0x20087, 0x1}, {0x5CD8, 0x20089, 0x1}, {0x5CD9, 0x200CC, 0x1}, {0x5CDA, 0x2E84, 0x1}, + {0x5CDB, 0x3473, 0x1}, {0x5CDC, 0x3447, 0x1}, {0x5CDD, 0x2E88, 0x1}, {0x5CDE, 0x2E8B, 0x1}, {0x5CDF, 0x9FB4, 0x1}, {0x5CE0, 0x359E, 0x1}, + {0x5CE1, 0x361A, 0x1}, {0x5CE2, 0x360E, 0x1}, {0x5CE3, 0x2E8C, 0x1}, {0x5CE4, 0x2E97, 0x1}, {0x5CE5, 0x396E, 0x1}, {0x5CE6, 0x3918, 0x1}, + {0x5CE7, 0x9FB5, 0x1}, {0x5CE8, 0x39CF, 0x1}, {0x5CE9, 0x39DF, 0x1}, {0x5CEA, 0x3A73, 0x1}, {0x5CEB, 0x39D0, 0x1}, {0x5CEC, 0x9FB6, 0x2}, + {0x5CEE, 0x3B4E, 0x1}, {0x5CEF, 0x3C6E, 0x1}, {0x5CF0, 0x3CE0, 0x1}, {0x5CF1, 0x2EA7, 0x1}, {0x5CF2, 0x215D7, 0x1}, {0x5CF3, 0x9FB8, 0x1}, + {0x5CF4, 0x2EAA, 0x1}, {0x5CF5, 0x4056, 0x1}, {0x5CF6, 0x415F, 0x1}, {0x5CF7, 0x2EAE, 0x1}, {0x5CF8, 0x4337, 0x1}, {0x5CF9, 0x2EB3, 0x1}, + {0x5CFA, 0x2EB6, 0x2}, {0x5CFC, 0x2298F, 0x1}, {0x5CFD, 0x43B1, 0x1}, {0x5CFE, 0x43AC, 0x1}, {0x5CFF, 0x2EBB, 0x1}, {0x5D00, 0x43DD, 0x1}, + {0x5D01, 0x44D6, 0x1}, {0x5D02, 0x4661, 0x1}, {0x5D03, 0x464C, 0x1}, {0x5D04, 0x9FB9, 0x1}, {0x5D05, 0x4723, 0x1}, {0x5D06, 0x4729, 0x1}, + {0x5D07, 0x477C, 0x1}, {0x5D08, 0x478D, 0x1}, {0x5D09, 0x2ECA, 0x1}, {0x5D0A, 0x4947, 0x1}, {0x5D0B, 0x497A, 0x1}, {0x5D0C, 0x497D, 0x1}, + {0x5D0D, 0x4982, 0x2}, {0x5D0F, 0x4985, 0x2}, {0x5D11, 0x499F, 0x1}, {0x5D12, 0x499B, 0x1}, {0x5D13, 0x49B7, 0x1}, {0x5D14, 0x49B6, 0x1}, + {0x5D15, 0x9FBA, 0x1}, {0x5D16, 0x241FE, 0x1}, {0x5D17, 0x4CA3, 0x1}, {0x5D18, 0x4C9F, 0x3}, {0x5D1B, 0x4C77, 0x1}, {0x5D1C, 0x4CA2, 0x1}, + {0x5D1D, 0x4D13, 0x7}, {0x5D24, 0x4DAE, 0x1}, {0x5D25, 0x9FBB, 0x1}, {0x5D26, 0xE468, 0x5E}, +}; + +inline constexpr uint32_t FOUR_BYTE_2005[209][3] = { + {0x0, 0x80, 0x24}, {0x24, 0xA5, 0x2}, {0x26, 0xA9, 0x7}, {0x2D, 0xB2, 0x5}, {0x32, 0xB8, 0x1F}, {0x51, 0xD8, 0x8}, + {0x59, 0xE2, 0x6}, {0x5F, 0xEB, 0x1}, {0x60, 0xEE, 0x4}, {0x64, 0xF4, 0x3}, {0x67, 0xF8, 0x1}, {0x68, 0xFB, 0x1}, + {0x69, 0xFD, 0x4}, {0x6D, 0x102, 0x11}, {0x7E, 0x114, 0x7}, {0x85, 0x11C, 0xF}, {0x94, 0x12C, 0x18}, {0xAC, 0x145, 0x3}, + {0xAF, 0x149, 0x4}, {0xB3, 0x14E, 0x1D}, {0xD0, 0x16C, 0x62}, {0x132, 0x1CF, 0x1}, {0x133, 0x1D1, 0x1}, {0x134, 0x1D3, 0x1}, + {0x135, 0x1D5, 0x1}, {0x136, 0x1D7, 0x1}, {0x137, 0x1D9, 0x1}, {0x138, 0x1DB, 0x1}, {0x139, 0x1DD, 0x1C}, {0x155, 0x1FA, 0x57}, + {0x1AC, 0x252, 0xF}, {0x1BB, 0x262, 0x65}, {0x220, 0x2C8, 0x1}, {0x221, 0x2CC, 0xD}, {0x22E, 0x2DA, 0xB7}, {0x2E5, 0x3A2, 0x1}, + {0x2E6, 0x3AA, 0x7}, {0x2ED, 0x3C2, 0x1}, {0x2EE, 0x3CA, 0x37}, {0x325, 0x402, 0xE}, {0x333, 0x450, 0x1}, {0x334, 0x452, 0x19ED}, + {0x1D21, 0xE7C7, 0x1}, {0x1D22, 0x1E40, 0x1D0}, {0x1EF2, 0x2011, 0x2}, {0x1EF4, 0x2017, 0x1}, {0x1EF5, 0x201A, 0x2}, {0x1EF7, 0x201E, 0x7}, + {0x1EFE, 0x2027, 0x9}, {0x1F07, 0x2031, 0x1}, {0x1F08, 0x2034, 0x1}, {0x1F09, 0x2036, 0x5}, {0x1F0E, 0x203C, 0x70}, {0x1F7E, 0x20AD, 0x56}, + {0x1FD4, 0x2104, 0x1}, {0x1FD5, 0x2106, 0x3}, {0x1FD8, 0x210A, 0xC}, {0x1FE4, 0x2117, 0xA}, {0x1FEE, 0x2122, 0x3E}, {0x202C, 0x216C, 0x4}, + {0x2030, 0x217A, 0x16}, {0x2046, 0x2194, 0x2}, {0x2048, 0x219A, 0x6E}, {0x20B6, 0x2209, 0x6}, {0x20BC, 0x2210, 0x1}, {0x20BD, 0x2212, 0x3}, + {0x20C0, 0x2216, 0x4}, {0x20C4, 0x221B, 0x2}, {0x20C6, 0x2221, 0x2}, {0x20C8, 0x2224, 0x1}, {0x20C9, 0x2226, 0x1}, {0x20CA, 0x222C, 0x2}, + {0x20CC, 0x222F, 0x5}, {0x20D1, 0x2238, 0x5}, {0x20D6, 0x223E, 0xA}, {0x20E0, 0x2249, 0x3}, {0x20E3, 0x224D, 0x5}, {0x20E8, 0x2253, 0xD}, + {0x20F5, 0x2262, 0x2}, {0x20F7, 0x2268, 0x6}, {0x20FD, 0x2270, 0x25}, {0x2122, 0x2296, 0x3}, {0x2125, 0x229A, 0xB}, {0x2130, 0x22A6, 0x19}, + {0x2149, 0x22C0, 0x52}, {0x219B, 0x2313, 0x14D}, {0x22E8, 0x246A, 0xA}, {0x22F2, 0x249C, 0x64}, {0x2356, 0x254C, 0x4}, {0x235A, 0x2574, 0xD}, + {0x2367, 0x2590, 0x3}, {0x236A, 0x2596, 0xA}, {0x2374, 0x25A2, 0x10}, {0x2384, 0x25B4, 0x8}, {0x238C, 0x25BE, 0x8}, {0x2394, 0x25C8, 0x3}, + {0x2397, 0x25CC, 0x2}, {0x2399, 0x25D0, 0x12}, {0x23AB, 0x25E6, 0x1F}, {0x23CA, 0x2607, 0x2}, {0x23CC, 0x260A, 0x36}, {0x2402, 0x2641, 0x1}, + {0x2403, 0x2643, 0x83E}, {0x2C41, 0x2E82, 0x2}, {0x2C43, 0x2E85, 0x3}, {0x2C46, 0x2E89, 0x2}, {0x2C48, 0x2E8D, 0xA}, {0x2C52, 0x2E98, 0xF}, + {0x2C61, 0x2EA8, 0x2}, {0x2C63, 0x2EAB, 0x3}, {0x2C66, 0x2EAF, 0x4}, {0x2C6A, 0x2EB4, 0x2}, {0x2C6C, 0x2EB8, 0x3}, {0x2C6F, 0x2EBC, 0xE}, + {0x2C7D, 0x2ECB, 0x125}, {0x2DA2, 0x2FFC, 0x4}, {0x2DA6, 0x3004, 0x1}, {0x2DA7, 0x3018, 0x5}, {0x2DAC, 0x301F, 0x2}, {0x2DAE, 0x302A, 0x14}, + {0x2DC2, 0x303F, 0x2}, {0x2DC4, 0x3094, 0x7}, {0x2DCB, 0x309F, 0x2}, {0x2DCD, 0x30F7, 0x5}, {0x2DD2, 0x30FF, 0x6}, {0x2DD8, 0x312A, 0xF6}, + {0x2ECE, 0x322A, 0x7}, {0x2ED5, 0x3232, 0x71}, {0x2F46, 0x32A4, 0xEA}, {0x3030, 0x3390, 0xC}, {0x303C, 0x339F, 0x2}, {0x303E, 0x33A2, 0x22}, + {0x3060, 0x33C5, 0x9}, {0x3069, 0x33CF, 0x2}, {0x306B, 0x33D3, 0x2}, {0x306D, 0x33D6, 0x71}, {0x30DE, 0x3448, 0x2B}, {0x3109, 0x3474, 0x12A}, + {0x3233, 0x359F, 0x6F}, {0x32A2, 0x360F, 0xB}, {0x32AD, 0x361B, 0x2FD}, {0x35AA, 0x3919, 0x55}, {0x35FF, 0x396F, 0x60}, {0x365F, 0x39D1, 0xE}, + {0x366D, 0x39E0, 0x93}, {0x3700, 0x3A74, 0xDA}, {0x37DA, 0x3B4F, 0x11F}, {0x38F9, 0x3C6F, 0x71}, {0x396A, 0x3CE1, 0x375}, {0x3CDF, 0x4057, 0x108}, + {0x3DE7, 0x4160, 0x1D7}, {0x3FBE, 0x4338, 0x74}, {0x4032, 0x43AD, 0x4}, {0x4036, 0x43B2, 0x2B}, {0x4061, 0x43DE, 0xF8}, {0x4159, 0x44D7, 0x175}, + {0x42CE, 0x464D, 0x14}, {0x42E2, 0x4662, 0xC1}, {0x43A3, 0x4724, 0x5}, {0x43A8, 0x472A, 0x52}, {0x43FA, 0x477D, 0x10}, {0x440A, 0x478E, 0x1B9}, + {0x45C3, 0x4948, 0x32}, {0x45F5, 0x497B, 0x2}, {0x45F7, 0x497E, 0x4}, {0x45FB, 0x4984, 0x1}, {0x45FC, 0x4987, 0x14}, {0x4610, 0x499C, 0x3}, + {0x4613, 0x49A0, 0x16}, {0x4629, 0x49B8, 0x2BF}, {0x48E8, 0x4C78, 0x27}, {0x490F, 0x4CA4, 0x6F}, {0x497E, 0x4D1A, 0x94}, {0x4A12, 0x4DAF, 0x51}, + {0x4A63, 0x9FA6, 0x385A}, {0x82BD, 0xE76C, 0x1}, {0x82BE, 0xE7C8, 0x1}, {0x82BF, 0xE7E7, 0xD}, {0x82CC, 0xE815, 0x1}, {0x82CD, 0xE819, 0x5}, + {0x82D2, 0xE81F, 0x7}, {0x82D9, 0xE827, 0x4}, {0x82DD, 0xE82D, 0x4}, {0x82E1, 0xE833, 0x8}, {0x82E9, 0xE83C, 0x7}, {0x82F0, 0xE844, 0x10}, + {0x8300, 0xE856, 0xE}, {0x830E, 0xE865, 0x10C7}, {0x93D5, 0xF92D, 0x4C}, {0x9421, 0xF97A, 0x1B}, {0x943C, 0xF996, 0x51}, {0x948D, 0xF9E8, 0x9}, + {0x9496, 0xF9F2, 0x1A}, {0x94B0, 0xFA10, 0x1}, {0x94B1, 0xFA12, 0x1}, {0x94B2, 0xFA15, 0x3}, {0x94B5, 0xFA19, 0x6}, {0x94BB, 0xFA22, 0x1}, + {0x94BC, 0xFA25, 0x2}, {0x94BE, 0xFA2A, 0x406}, {0x98C4, 0xFE32, 0x1}, {0x98C5, 0xFE45, 0x4}, {0x98C9, 0xFE53, 0x1}, {0x98CA, 0xFE58, 0x1}, + {0x98CB, 0xFE67, 0x1}, {0x98CC, 0xFE6C, 0x95}, {0x9961, 0xFF5F, 0x81}, {0x99E2, 0xFFE6, 0x1A}, {0x2E248, 0x10000, 0x100000}, +}; + +inline constexpr uint32_t FALLBACK_2005[24][2] = { + {0xE78D, 0x1C0E}, {0xE78E, 0x1C0F}, {0xE78F, 0x1C10}, {0xE790, 0x1C11}, {0xE791, 0x1C12}, {0xE792, 0x1C13}, + {0xE793, 0x1C14}, {0xE794, 0x1C21}, {0xE795, 0x1C22}, {0xE796, 0x1C28}, {0xE816, 0x5CD7}, {0xE817, 0x5CD8}, + {0xE818, 0x5CD9}, {0xE81E, 0x5CDF}, {0xE826, 0x5CE7}, {0xE82B, 0x5CEC}, {0xE82C, 0x5CED}, {0xE831, 0x5CF2}, + {0xE832, 0x5CF3}, {0xE83B, 0x5CFC}, {0xE843, 0x5D04}, {0xE854, 0x5D15}, {0xE855, 0x5D16}, {0xE864, 0x5D25}, +}; + +} diff --git a/core/libs/prx/libSceCesCs/PendingExports.cpp b/core/libs/prx/libSceCesCs/PendingExports.cpp index 165fe278e8..ac98b0d50a 100644 --- a/core/libs/prx/libSceCesCs/PendingExports.cpp +++ b/core/libs/prx/libSceCesCs/PendingExports.cpp @@ -1,202 +1,23 @@ +#include "prx/libSceCesCs/include/Api.hpp" #include "prx/libc/include/General.hpp" extern "C" { -int APS5_VABI sceCesBig5ToUcs2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesBig5ToUtf16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesBig5ToUtf16be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesBig5ToUtf16le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesBig5ToUtf32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesBig5ToUtf32be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesBig5ToUtf32le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucCnToUcs2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucCnToUtf16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucCnToUtf16be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucCnToUtf16le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucCnToUtf32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucCnToUtf32be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucCnToUtf32le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucCnToUtf8(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesEucCnUcsProfileSetSbcs(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesEucJpToUtf16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucJpToUtf16be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucJpToUtf16le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucJpToUtf32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucJpToUtf32be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucJpToUtf32le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesEucJpUcsProfileSetSbcs(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesEucKrToUcs2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucKrToUtf16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucKrToUtf16be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucKrToUtf16le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucKrToUtf32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucKrToUtf32be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesEucKrToUtf32le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesEucKrUcsProfileSetSbcs(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesGbToUcs2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesGbToUtf16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesGbToUtf16be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesGbToUtf16le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesGbToUtf32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesGbToUtf32be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesGbToUtf32le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesGbToUtf8(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesGbUcsProfileSet4ByteCharRange(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -272,86 +93,11 @@ int APS5_VABI sceCesJisGetLevel(void) { return 0; } -int APS5_VABI sceCesJisX0208ToSJisCode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesJisX0213ToSJisCode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesJiscsToUcs(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesMbcToUcs2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcToUtf16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcToUtf16be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcToUtf16le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcToUtf32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcToUtf32be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcToUtf32le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcToUtf8(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcsStrGetUtf16Len(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcsStrGetUtf32Len(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcsStrToUtf16Str(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcsStrToUtf32Str(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesMbcsUcsContextInitCopy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharCode(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -382,61 +128,16 @@ int APS5_VABI sceCesRefersUcsProfileCp1253(void) { return 0; } -int APS5_VABI sceCesRefersUcsProfileCp1254(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileCp1255(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesRefersUcsProfileCp1256(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileCp1257(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesRefersUcsProfileCp1258(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp437(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp737(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp775(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp850(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp852(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp855(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileCp857(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -447,41 +148,11 @@ int APS5_VABI sceCesRefersUcsProfileCp858(void) { return 0; } -int APS5_VABI sceCesRefersUcsProfileCp860(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp861(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp862(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp863(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileCp864(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesRefersUcsProfileCp865(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp866(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileCp869(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -502,61 +173,16 @@ int APS5_VABI sceCesRefersUcsProfileGbT1988Tilde0x7e(void) { return 0; } -int APS5_VABI sceCesRefersUcsProfileIso8859_1(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileIso8859_10(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileIso8859_11(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesRefersUcsProfileIso8859_13(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileIso8859_14(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileIso8859_15(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileIso8859_16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileIso8859_2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileIso8859_3(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesRefersUcsProfileIso8859_4(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileIso8859_5(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileIso8859_6(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -572,11 +198,6 @@ int APS5_VABI sceCesRefersUcsProfileIso8859_8(void) { return 0; } -int APS5_VABI sceCesRefersUcsProfileIso8859_9(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileJisX0201(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -602,16 +223,6 @@ int APS5_VABI sceCesRefersUcsProfileJisX0201Tilde0x7e(void) { return 0; } -int APS5_VABI sceCesRefersUcsProfileKoi8R(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileKoi8U(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileKsX1003(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -622,106 +233,11 @@ int APS5_VABI sceCesRefersUcsProfileKsX1003Tilde0x7e(void) { return 0; } -int APS5_VABI sceCesSJisCodeToJisX0208(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisCodeToJisX0213(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisGetCode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisPutCode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisToUcs2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisToUtf16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisToUtf16be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisToUtf16le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisToUtf32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisToUtf32be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisToUtf32le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSJisToUtf8(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesSJisUcsProfileSetSbcs(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesSbcToUcs2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSbcToUtf16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSbcToUtf16be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSbcToUtf16le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSbcToUtf32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSbcToUtf32be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesSbcToUtf32le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUcs2StrGetIso2022Len(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -732,91 +248,11 @@ int APS5_VABI sceCesUcs2StrToIso2022Str(void) { return 0; } -int APS5_VABI sceCesUcs2ToBig5(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcs2ToEucCn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcs2ToEucJp(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcs2ToEucKr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcs2ToGb(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcs2ToMbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcs2ToSJis(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcs2ToSbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcs2ToUhc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcsProfileInitEucCnGb2312(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUcsProfileInitEucJis2004(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesUcsProfileInitEucJpX0208(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcsProfileInitEucJpX0208Ss2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcsProfileInitEucJpX0208Ss2Ss3(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcsProfileInitGb18030_2000(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcsProfileInitGb18030_2005(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUcsProfileInitGbkCp936(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUcsProfileInitIso2022JpCp50221(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -832,11 +268,6 @@ int APS5_VABI sceCesUcsProfileInitIso2022JpRfc1468(void) { return 0; } -int APS5_VABI sceCesUcsProfileInitSJis1997X0208(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUcsProfileInitSJis2004X0213(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -847,41 +278,6 @@ int APS5_VABI sceCesUcsToJiscs(void) { return 0; } -int APS5_VABI sceCesUhcToUcs2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUhcToUtf16(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUhcToUtf16be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUhcToUtf16le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUhcToUtf32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUhcToUtf32be(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUhcToUtf32le(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUhcUcsProfileSetSbcs(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -892,311 +288,21 @@ int APS5_VABI sceCesUtf16StrGetIso2022Len(void) { return 0; } -int APS5_VABI sceCesUtf16StrGetMbcsLen(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUtf16StrToIso2022Str(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesUtf16StrToMbcsStr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16ToBig5(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16ToEucCn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16ToEucJp(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16ToEucKr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16ToGb(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16ToMbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16ToSJis(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16ToSbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16ToUhc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16beToBig5(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16beToEucCn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16beToEucJp(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16beToEucKr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16beToGb(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16beToMbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16beToSJis(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16beToSbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16beToUhc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16leToBig5(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16leToEucCn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16leToEucJp(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16leToEucKr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16leToGb(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16leToMbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16leToSJis(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16leToSbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf16leToUhc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUtf32StrGetIso2022Len(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesUtf32StrGetMbcsLen(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUtf32StrToIso2022Str(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesUtf32StrToMbcsStr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32ToBig5(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32ToEucCn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32ToEucJp(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32ToEucKr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32ToGb(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32ToMbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32ToSJis(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32ToSbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32ToUhc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32beToBig5(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32beToEucCn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32beToEucJp(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32beToEucKr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32beToGb(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32beToMbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32beToSJis(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32beToSbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32beToUhc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32leToBig5(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32leToEucCn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32leToEucJp(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32leToEucKr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32leToGb(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32leToMbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32leToSJis(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32leToSbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf32leToUhc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesUtf8StrGetIso2022Len(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1207,44 +313,4 @@ int APS5_VABI sceCesUtf8StrToIso2022Str(void) { return 0; } -int APS5_VABI sceCesUtf8ToBig5(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf8ToEucCn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf8ToEucJp(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf8ToEucKr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf8ToGb(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf8ToMbc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf8ToSJis(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesUtf8ToUhc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceCesCs/SingleByte.hpp b/core/libs/prx/libSceCesCs/SingleByte.hpp new file mode 100644 index 0000000000..ebe46104cd --- /dev/null +++ b/core/libs/prx/libSceCesCs/SingleByte.hpp @@ -0,0 +1,312 @@ +#pragma once + +#include +#include + +namespace SingleByte { + +enum Table : std::size_t { + Iso8859_1, + Iso8859_2, + Iso8859_4, + Iso8859_5, + Iso8859_9, + Iso8859_10, + Iso8859_13, + Iso8859_14, + Iso8859_15, + Iso8859_16, + Cp437, + Cp737, + Cp775, + Cp850, + Cp852, + Cp855, + Cp860, + Cp861, + Cp862, + Cp863, + Cp865, + Cp866, + Koi8R, + Koi8U, + Cp1254, + Cp1256, + Cp1258, + Count, +}; + +inline constexpr uint16_t HIGH[Count][128] = { + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, + }, + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x0104, 0x02D8, 0x0141, 0x00A4, 0x013D, 0x015A, 0x00A7, 0x00A8, 0x0160, 0x015E, 0x0164, 0x0179, 0x00AD, 0x017D, 0x017B, + 0x00B0, 0x0105, 0x02DB, 0x0142, 0x00B4, 0x013E, 0x015B, 0x02C7, 0x00B8, 0x0161, 0x015F, 0x0165, 0x017A, 0x02DD, 0x017E, 0x017C, + 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, + 0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF, + 0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F, + 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9, + }, + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x0104, 0x0138, 0x0156, 0x00A4, 0x0128, 0x013B, 0x00A7, 0x00A8, 0x0160, 0x0112, 0x0122, 0x0166, 0x00AD, 0x017D, 0x00AF, + 0x00B0, 0x0105, 0x02DB, 0x0157, 0x00B4, 0x0129, 0x013C, 0x02C7, 0x00B8, 0x0161, 0x0113, 0x0123, 0x0167, 0x014A, 0x017E, 0x014B, + 0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E, 0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x012A, + 0x0110, 0x0145, 0x014C, 0x0136, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x0168, 0x016A, 0x00DF, + 0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F, 0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x012B, + 0x0111, 0x0146, 0x014D, 0x0137, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x0169, 0x016B, 0x02D9, + }, + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409, 0x040A, 0x040B, 0x040C, 0x00AD, 0x040E, 0x040F, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + 0x2116, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, 0x0458, 0x0459, 0x045A, 0x045B, 0x045C, 0x00A7, 0x045E, 0x045F, + }, + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x011E, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0130, 0x015E, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x011F, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0131, 0x015F, 0x00FF, + }, + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x0104, 0x0112, 0x0122, 0x012A, 0x0128, 0x0136, 0x00A7, 0x013B, 0x0110, 0x0160, 0x0166, 0x017D, 0x00AD, 0x016A, 0x014A, + 0x00B0, 0x0105, 0x0113, 0x0123, 0x012B, 0x0129, 0x0137, 0x00B7, 0x013C, 0x0111, 0x0161, 0x0167, 0x017E, 0x2015, 0x016B, 0x014B, + 0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E, 0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x0145, 0x014C, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0168, 0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F, 0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x0146, 0x014D, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x0169, 0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x0138, + }, + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x201D, 0x00A2, 0x00A3, 0x00A4, 0x201E, 0x00A6, 0x00A7, 0x00D8, 0x00A9, 0x0156, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00C6, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x201C, 0x00B5, 0x00B6, 0x00B7, 0x00F8, 0x00B9, 0x0157, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00E6, + 0x0104, 0x012E, 0x0100, 0x0106, 0x00C4, 0x00C5, 0x0118, 0x0112, 0x010C, 0x00C9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012A, 0x013B, + 0x0160, 0x0143, 0x0145, 0x00D3, 0x014C, 0x00D5, 0x00D6, 0x00D7, 0x0172, 0x0141, 0x015A, 0x016A, 0x00DC, 0x017B, 0x017D, 0x00DF, + 0x0105, 0x012F, 0x0101, 0x0107, 0x00E4, 0x00E5, 0x0119, 0x0113, 0x010D, 0x00E9, 0x017A, 0x0117, 0x0123, 0x0137, 0x012B, 0x013C, + 0x0161, 0x0144, 0x0146, 0x00F3, 0x014D, 0x00F5, 0x00F6, 0x00F7, 0x0173, 0x0142, 0x015B, 0x016B, 0x00FC, 0x017C, 0x017E, 0x2019, + }, + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x1E02, 0x1E03, 0x00A3, 0x010A, 0x010B, 0x1E0A, 0x00A7, 0x1E80, 0x00A9, 0x1E82, 0x1E0B, 0x1EF2, 0x00AD, 0x00AE, 0x0178, + 0x1E1E, 0x1E1F, 0x0120, 0x0121, 0x1E40, 0x1E41, 0x00B6, 0x1E56, 0x1E81, 0x1E57, 0x1E83, 0x1E60, 0x1EF3, 0x1E84, 0x1E85, 0x1E61, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x0174, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x1E6A, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x0176, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x0175, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x1E6B, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x0177, 0x00FF, + }, + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x20AC, 0x00A5, 0x0160, 0x00A7, 0x0161, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x017D, 0x00B5, 0x00B6, 0x00B7, 0x017E, 0x00B9, 0x00BA, 0x00BB, 0x0152, 0x0153, 0x0178, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, + }, + { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x0104, 0x0105, 0x0141, 0x20AC, 0x201E, 0x0160, 0x00A7, 0x0161, 0x00A9, 0x0218, 0x00AB, 0x0179, 0x00AD, 0x017A, 0x017B, + 0x00B0, 0x00B1, 0x010C, 0x0142, 0x017D, 0x201D, 0x00B6, 0x00B7, 0x017E, 0x010D, 0x0219, 0x00BB, 0x0152, 0x0153, 0x0178, 0x017C, + 0x00C0, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0106, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x0110, 0x0143, 0x00D2, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x015A, 0x0170, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0118, 0x021A, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x0107, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x0111, 0x0144, 0x00F2, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x015B, 0x0171, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0119, 0x021B, 0x00FF, + }, + { + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, + }, + { + 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, + 0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, + 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03C9, 0x03AC, 0x03AD, 0x03AE, 0x03CA, 0x03AF, 0x03CC, 0x03CD, 0x03CB, 0x03CE, 0x0386, 0x0388, 0x0389, 0x038A, 0x038C, 0x038E, + 0x038F, 0x00B1, 0x2265, 0x2264, 0x03AA, 0x03AB, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, + }, + { + 0x0106, 0x00FC, 0x00E9, 0x0101, 0x00E4, 0x0123, 0x00E5, 0x0107, 0x0142, 0x0113, 0x0156, 0x0157, 0x012B, 0x0179, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x014D, 0x00F6, 0x0122, 0x00A2, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x00A4, + 0x0100, 0x012A, 0x00F3, 0x017B, 0x017C, 0x017A, 0x201D, 0x00A6, 0x00A9, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x0141, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0104, 0x010C, 0x0118, 0x0116, 0x2563, 0x2551, 0x2557, 0x255D, 0x012E, 0x0160, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0172, 0x016A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x017D, + 0x0105, 0x010D, 0x0119, 0x0117, 0x012F, 0x0161, 0x0173, 0x016B, 0x017E, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x00D3, 0x00DF, 0x014C, 0x0143, 0x00F5, 0x00D5, 0x00B5, 0x0144, 0x0136, 0x0137, 0x013B, 0x013C, 0x0146, 0x0112, 0x0145, 0x2019, + 0x00AD, 0x00B1, 0x201C, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x201E, 0x00B0, 0x2219, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0, + }, + { + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4, + 0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0, + }, + { + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x016F, 0x0107, 0x00E7, 0x0142, 0x00EB, 0x0150, 0x0151, 0x00EE, 0x0179, 0x00C4, 0x0106, + 0x00C9, 0x0139, 0x013A, 0x00F4, 0x00F6, 0x013D, 0x013E, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x0164, 0x0165, 0x0141, 0x00D7, 0x010D, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x0104, 0x0105, 0x017D, 0x017E, 0x0118, 0x0119, 0x00AC, 0x017A, 0x010C, 0x015F, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x011A, 0x015E, 0x2563, 0x2551, 0x2557, 0x255D, 0x017B, 0x017C, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0102, 0x0103, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x0111, 0x0110, 0x010E, 0x00CB, 0x010F, 0x0147, 0x00CD, 0x00CE, 0x011B, 0x2518, 0x250C, 0x2588, 0x2584, 0x0162, 0x016E, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, 0x0154, 0x00DA, 0x0155, 0x0170, 0x00FD, 0x00DD, 0x0163, 0x00B4, + 0x00AD, 0x02DD, 0x02DB, 0x02C7, 0x02D8, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x02D9, 0x0171, 0x0158, 0x0159, 0x25A0, 0x00A0, + }, + { + 0x0452, 0x0402, 0x0453, 0x0403, 0x0451, 0x0401, 0x0454, 0x0404, 0x0455, 0x0405, 0x0456, 0x0406, 0x0457, 0x0407, 0x0458, 0x0408, + 0x0459, 0x0409, 0x045A, 0x040A, 0x045B, 0x040B, 0x045C, 0x040C, 0x045E, 0x040E, 0x045F, 0x040F, 0x044E, 0x042E, 0x044A, 0x042A, + 0x0430, 0x0410, 0x0431, 0x0411, 0x0446, 0x0426, 0x0434, 0x0414, 0x0435, 0x0415, 0x0444, 0x0424, 0x0433, 0x0413, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0445, 0x0425, 0x0438, 0x0418, 0x2563, 0x2551, 0x2557, 0x255D, 0x0439, 0x0419, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x043A, 0x041A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x043B, 0x041B, 0x043C, 0x041C, 0x043D, 0x041D, 0x043E, 0x041E, 0x043F, 0x2518, 0x250C, 0x2588, 0x2584, 0x041F, 0x044F, 0x2580, + 0x042F, 0x0440, 0x0420, 0x0441, 0x0421, 0x0442, 0x0422, 0x0443, 0x0423, 0x0436, 0x0416, 0x0432, 0x0412, 0x044C, 0x042C, 0x2116, + 0x00AD, 0x044B, 0x042B, 0x0437, 0x0417, 0x0448, 0x0428, 0x044D, 0x042D, 0x0449, 0x0429, 0x0447, 0x0427, 0x00A7, 0x25A0, 0x00A0, + }, + { + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E3, 0x00E0, 0x00C1, 0x00E7, 0x00EA, 0x00CA, 0x00E8, 0x00CD, 0x00D4, 0x00EC, 0x00C3, 0x00C2, + 0x00C9, 0x00C0, 0x00C8, 0x00F4, 0x00F5, 0x00F2, 0x00DA, 0x00F9, 0x00CC, 0x00D5, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x20A7, 0x00D3, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00D2, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, + }, + { + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00D0, 0x00F0, 0x00DE, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00FE, 0x00FB, 0x00DD, 0x00FD, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00C1, 0x00CD, 0x00D3, 0x00DA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, + }, + { + 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, + }, + { + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00C2, 0x00E0, 0x00B6, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x2017, 0x00C0, 0x00A7, + 0x00C9, 0x00C8, 0x00CA, 0x00F4, 0x00CB, 0x00CF, 0x00FB, 0x00F9, 0x00A4, 0x00D4, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x00DB, 0x0192, + 0x00A6, 0x00B4, 0x00F3, 0x00FA, 0x00A8, 0x00B8, 0x00B3, 0x00AF, 0x00CE, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00BE, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, + }, + { + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00A4, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, + }, + { + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + 0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040E, 0x045E, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x2116, 0x00A4, 0x25A0, 0x00A0, + }, + { + 0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590, + 0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2219, 0x221A, 0x2248, 0x2264, 0x2265, 0x00A0, 0x2321, 0x00B0, 0x00B2, 0x00B7, 0x00F7, + 0x2550, 0x2551, 0x2552, 0x0451, 0x2553, 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x255C, 0x255D, 0x255E, + 0x255F, 0x2560, 0x2561, 0x0401, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x256B, 0x256C, 0x00A9, + 0x044E, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, 0x0445, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, + 0x043F, 0x044F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, 0x044C, 0x044B, 0x0437, 0x0448, 0x044D, 0x0449, 0x0447, 0x044A, + 0x042E, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, 0x0425, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, + 0x041F, 0x042F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, 0x042C, 0x042B, 0x0417, 0x0428, 0x042D, 0x0429, 0x0427, 0x042A, + }, + { + 0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590, + 0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2219, 0x221A, 0x2248, 0x2264, 0x2265, 0x00A0, 0x2321, 0x00B0, 0x00B2, 0x00B7, 0x00F7, + 0x2550, 0x2551, 0x2552, 0x0451, 0x0454, 0x2554, 0x0456, 0x0457, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x0491, 0x255D, 0x255E, + 0x255F, 0x2560, 0x2561, 0x0401, 0x0404, 0x2563, 0x0406, 0x0407, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x0490, 0x256C, 0x00A9, + 0x044E, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, 0x0445, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, + 0x043F, 0x044F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, 0x044C, 0x044B, 0x0437, 0x0448, 0x044D, 0x0449, 0x0447, 0x044A, + 0x042E, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, 0x0425, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, + 0x041F, 0x042F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, 0x042C, 0x042B, 0x0417, 0x0428, 0x042D, 0x0429, 0x0427, 0x042A, + }, + { + 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x008E, 0x008F, + 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x009E, 0x0178, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x011E, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0130, 0x015E, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x011F, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0131, 0x015F, 0x00FF, + }, + { + 0x20AC, 0x067E, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0679, 0x2039, 0x0152, 0x0686, 0x0698, 0x0688, + 0x06AF, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x06A9, 0x2122, 0x0691, 0x203A, 0x0153, 0x200C, 0x200D, 0x06BA, + 0x00A0, 0x060C, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x06BE, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x061B, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x061F, + 0x06C1, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x00D7, 0x0637, 0x0638, 0x0639, 0x063A, 0x0640, 0x0641, 0x0642, 0x0643, + 0x00E0, 0x0644, 0x00E2, 0x0645, 0x0646, 0x0647, 0x0648, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x0649, 0x064A, 0x00EE, 0x00EF, + 0x064B, 0x064C, 0x064D, 0x064E, 0x00F4, 0x064F, 0x0650, 0x00F7, 0x0651, 0x00F9, 0x0652, 0x00FB, 0x00FC, 0x200E, 0x200F, 0x06D2, + }, + { + 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x008A, 0x2039, 0x0152, 0x008D, 0x008E, 0x008F, + 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x009A, 0x203A, 0x0153, 0x009D, 0x009E, 0x0178, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x0300, 0x00CD, 0x00CE, 0x00CF, + 0x0110, 0x00D1, 0x0309, 0x00D3, 0x00D4, 0x01A0, 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x01AF, 0x0303, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x0301, 0x00ED, 0x00EE, 0x00EF, + 0x0111, 0x00F1, 0x0323, 0x00F3, 0x00F4, 0x01A1, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x01B0, 0x20AB, 0x00FF, + }, +}; + +} diff --git a/core/libs/prx/libSceCesCs/Unicode.cpp b/core/libs/prx/libSceCesCs/Unicode.cpp index 5a4b10399b..45f36923ee 100644 --- a/core/libs/prx/libSceCesCs/Unicode.cpp +++ b/core/libs/prx/libSceCesCs/Unicode.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceCesCs/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceCesCs/include/Api.hpp b/core/libs/prx/libSceCesCs/include/Api.hpp new file mode 100644 index 0000000000..de9d5746e4 --- /dev/null +++ b/core/libs/prx/libSceCesCs/include/Api.hpp @@ -0,0 +1,295 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1252(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1251(void); +const void* APS5_VABI sceCesRefersUcsProfileCp1250(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_1(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_2(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_4(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_5(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_9(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_10(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_13(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_14(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_15(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_16(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp437(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp737(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp775(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp850(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp852(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp855(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp860(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp861(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp862(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp863(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp865(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp866(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileKoi8R(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileKoi8U(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1254(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1256(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1258(void); +void* APS5_VABI sceCesUcsProfileInitSJis1997Cp932(void* sheet); +void* APS5_VABI sceCesUcsProfileInitSJis1997X0208(void* sheet); +int APS5_VABI sceCesMbcsUcsContextInit(void* context, const void* profile); +int APS5_VABI sceCesMbcsUcsContextInitCopy(void* context, const void* source); +void* APS5_VABI sceCesUcsProfileInitEucJpCp51932(void* sheet); +void* APS5_VABI sceCesUcsProfileInitEucJpX0208(void* sheet); +void* APS5_VABI sceCesUcsProfileInitEucJpX0208Ss2(void* sheet); +void* APS5_VABI sceCesUcsProfileInitEucJpX0208Ss2Ss3(void* sheet); +void* APS5_VABI sceCesUcsProfileInitEucKr(void* sheet); +void* APS5_VABI sceCesUcsProfileInitUhc(void* sheet); +void* APS5_VABI sceCesUcsProfileInitBig5Cp950(void* sheet); +int APS5_VABI sceCesUtf8StrToMbcsStr(void* context, const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8Len, uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen); +int APS5_VABI sceCesUtf8StrGetMbcsLen(void* context, const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8Len, uint32_t* mbcsLen); +int APS5_VABI sceCesUcs2StrToMbcsStr(void* context, const uint16_t* ucs2, uint32_t ucs2max, uint32_t* ucs2Len, uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen); +int APS5_VABI sceCesUcs2StrGetMbcsLen(void* context, const uint16_t* ucs2, uint32_t ucs2max, uint32_t* ucs2Len, uint32_t* mbcsLen); +int APS5_VABI sceCesMbcsStrToUtf8Str(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint8_t* utf8, uint32_t utf8max, uint32_t* utf8Len); +int APS5_VABI sceCesMbcsStrGetUtf8Len(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* utf8Len); +int APS5_VABI sceCesMbcsStrToUcs2Str(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint16_t* ucs2, uint32_t ucs2max, uint32_t* ucs2Len); +int APS5_VABI sceCesMbcsStrGetUcs2Len(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* ucs2Len); +int APS5_VABI sceCesEucJpToUcs2(const void* profile, const uint8_t* euc, uint32_t eucMax, uint32_t* eucSize, uint16_t* ucs2); +int APS5_VABI sceCesSbcToUtf8(const uint8_t* profile, uint8_t sbc, uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len); +int APS5_VABI sceCesUtf8ToSbc(const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len, const uint8_t* profile, uint8_t* sbc); +int APS5_VABI sceCesEucJpToUtf8(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length); +int APS5_VABI sceCesEucKrToUtf8(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length); +int APS5_VABI sceCesBig5ToUtf8(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Length, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length); +int APS5_VABI sceCesUhcToUtf8(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length); +int APS5_VABI sceCesSJisToUcs2(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* ucs2); +int APS5_VABI sceCesSJisToUtf8(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length); +int APS5_VABI sceCesSJisToUtf16(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesSJisToUtf16be(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesSJisToUtf16le(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesSJisToUtf32(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint32_t* utf32); +int APS5_VABI sceCesSJisToUtf32be(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint32_t* utf32); +int APS5_VABI sceCesSJisToUtf32le(const void* profile, const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint32_t* utf32); +int APS5_VABI sceCesEucJpToUtf16(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesEucJpToUtf16be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesEucJpToUtf16le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesEucJpToUtf32(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32); +int APS5_VABI sceCesEucJpToUtf32be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32); +int APS5_VABI sceCesEucJpToUtf32le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32); +int APS5_VABI sceCesUcs2ToSJis(uint16_t ucs2, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize); +int APS5_VABI sceCesUtf8ToSJis(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize); +int APS5_VABI sceCesUtf16ToSJis(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize); +int APS5_VABI sceCesUtf16beToSJis(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize); +int APS5_VABI sceCesUtf16leToSJis(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize); +int APS5_VABI sceCesUtf32ToSJis(uint32_t utf32, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize); +int APS5_VABI sceCesUtf32beToSJis(const uint32_t* utf32, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize); +int APS5_VABI sceCesUtf32leToSJis(const uint32_t* utf32, const void* profile, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize); +int APS5_VABI sceCesUcs2ToEucJp(uint16_t ucs2, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf8ToEucJp(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf16ToEucJp(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf16beToEucJp(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf16leToEucJp(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf32ToEucJp(uint32_t utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf32beToEucJp(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf32leToEucJp(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesSJisCodeToJisX0208(uint16_t sjisCode, uint8_t* ku, uint8_t* ten); +int APS5_VABI sceCesJisX0208ToSJisCode(uint8_t ku, uint8_t ten, uint16_t* sjisCode); +int APS5_VABI sceCesSJisCodeToJisX0213(uint16_t sjisCode, uint8_t* men, uint8_t* ku, uint8_t* ten); +int APS5_VABI sceCesJisX0213ToSJisCode(uint8_t men, uint8_t ku, uint8_t ten, uint16_t* sjisCode); +int APS5_VABI sceCesSJisGetCode(const uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize, uint16_t* sjisCode); +int APS5_VABI sceCesSJisPutCode(uint16_t sjisCode, uint8_t* sjisBuffer, uint32_t sjisMax, uint32_t* sjisSize); +int APS5_VABI sceCesMbcToUcs2(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint16_t* ucs2); +int APS5_VABI sceCesMbcToUtf8(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Len); +int APS5_VABI sceCesMbcToUtf16(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len); +int APS5_VABI sceCesMbcToUtf16be(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len); +int APS5_VABI sceCesMbcToUtf16le(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len); +int APS5_VABI sceCesMbcToUtf32(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint32_t* utf32); +int APS5_VABI sceCesMbcToUtf32be(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint32_t* utf32); +int APS5_VABI sceCesMbcToUtf32le(const void* profile, const uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen, uint32_t* utf32); +int APS5_VABI sceCesSbcToUcs2(const uint8_t* profile, uint8_t sbc, uint16_t* ucs2); +int APS5_VABI sceCesSbcToUtf16(const uint8_t* profile, uint8_t sbc, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len); +int APS5_VABI sceCesSbcToUtf16be(const uint8_t* profile, uint8_t sbc, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len); +int APS5_VABI sceCesSbcToUtf16le(const uint8_t* profile, uint8_t sbc, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len); +int APS5_VABI sceCesSbcToUtf32(const uint8_t* profile, uint8_t sbc, uint32_t* utf32); +int APS5_VABI sceCesSbcToUtf32be(const uint8_t* profile, uint8_t sbc, uint32_t* utf32); +int APS5_VABI sceCesSbcToUtf32le(const uint8_t* profile, uint8_t sbc, uint32_t* utf32); +int APS5_VABI sceCesUcs2ToSbc(uint16_t ucs2, const uint8_t* profile, uint8_t* sbc); +int APS5_VABI sceCesUtf16ToSbc(const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, const uint8_t* profile, uint8_t* sbc); +int APS5_VABI sceCesUtf16beToSbc(const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, const uint8_t* profile, uint8_t* sbc); +int APS5_VABI sceCesUtf16leToSbc(const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, const uint8_t* profile, uint8_t* sbc); +int APS5_VABI sceCesUtf32ToSbc(uint32_t utf32, const uint8_t* profile, uint8_t* sbc); +int APS5_VABI sceCesUtf32beToSbc(const uint32_t* utf32, const uint8_t* profile, uint8_t* sbc); +int APS5_VABI sceCesUtf32leToSbc(const uint32_t* utf32, const uint8_t* profile, uint8_t* sbc); +int APS5_VABI sceCesUtf16StrToMbcsStr(void* context, const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen); +int APS5_VABI sceCesUtf16StrGetMbcsLen(void* context, const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, uint32_t* mbcsLen); +int APS5_VABI sceCesMbcsStrToUtf16Str(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len); +int APS5_VABI sceCesMbcsStrGetUtf16Len(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* utf16Len); +int APS5_VABI sceCesUtf32StrToMbcsStr(void* context, const uint32_t* utf32, uint32_t utf32max, uint32_t* utf32Len, uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen); +int APS5_VABI sceCesUtf32StrGetMbcsLen(void* context, const uint32_t* utf32, uint32_t utf32max, uint32_t* utf32Len, uint32_t* mbcsLen); +int APS5_VABI sceCesMbcsStrToUtf32Str(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* utf32, uint32_t utf32max, uint32_t* utf32Len); +int APS5_VABI sceCesMbcsStrGetUtf32Len(void* context, const uint8_t* mbcs, uint32_t mbcsMax, uint32_t* mbcsLen, uint32_t* utf32Len); +void* APS5_VABI sceCesUcsProfileInitEucCnGb2312(void* sheet); +void* APS5_VABI sceCesUcsProfileInitGbkCp936(void* sheet); +void* APS5_VABI sceCesUcsProfileInitGb18030_2000(void* sheet); +void* APS5_VABI sceCesUcsProfileInitGb18030_2005(void* sheet); +int APS5_VABI sceCesEucCnToUcs2(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* ucs2); +int APS5_VABI sceCesEucCnToUtf8(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length); +int APS5_VABI sceCesEucCnToUtf16(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesEucCnToUtf16be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesEucCnToUtf16le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesEucCnToUtf32(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32); +int APS5_VABI sceCesEucCnToUtf32be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32); +int APS5_VABI sceCesEucCnToUtf32le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32); +int APS5_VABI sceCesUcs2ToEucCn(uint16_t ucs2, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf8ToEucCn(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf16ToEucCn(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf16beToEucCn(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf16leToEucCn(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf32ToEucCn(uint32_t utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf32beToEucCn(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf32leToEucCn(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesGbToUcs2(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint16_t* ucs2); +int APS5_VABI sceCesGbToUtf8(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length); +int APS5_VABI sceCesGbToUtf16(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesGbToUtf16be(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesGbToUtf16le(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesGbToUtf32(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint32_t* utf32); +int APS5_VABI sceCesGbToUtf32be(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint32_t* utf32); +int APS5_VABI sceCesGbToUtf32le(const void* profile, const uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize, uint32_t* utf32); +int APS5_VABI sceCesUcs2ToGb(uint16_t ucs2, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize); +int APS5_VABI sceCesUtf8ToGb(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize); +int APS5_VABI sceCesUtf16ToGb(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize); +int APS5_VABI sceCesUtf16beToGb(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize); +int APS5_VABI sceCesUtf16leToGb(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize); +int APS5_VABI sceCesUtf32ToGb(uint32_t utf32, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize); +int APS5_VABI sceCesUtf32beToGb(const uint32_t* utf32, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize); +int APS5_VABI sceCesUtf32leToGb(const uint32_t* utf32, const void* profile, uint8_t* gbBuffer, uint32_t gbMax, uint32_t* gbSize); +int APS5_VABI sceCesEucKrToUcs2(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* ucs2); +int APS5_VABI sceCesEucKrToUtf16(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesEucKrToUtf16be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesEucKrToUtf16le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesEucKrToUtf32(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32); +int APS5_VABI sceCesEucKrToUtf32be(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32); +int APS5_VABI sceCesEucKrToUtf32le(const void* profile, const uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize, uint32_t* utf32); +int APS5_VABI sceCesUhcToUcs2(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint16_t* ucs2); +int APS5_VABI sceCesUhcToUtf16(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesUhcToUtf16be(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesUhcToUtf16le(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesUhcToUtf32(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint32_t* utf32); +int APS5_VABI sceCesUhcToUtf32be(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint32_t* utf32); +int APS5_VABI sceCesUhcToUtf32le(const void* profile, const uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize, uint32_t* utf32); +int APS5_VABI sceCesBig5ToUcs2(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint16_t* ucs2); +int APS5_VABI sceCesBig5ToUtf16(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesBig5ToUtf16be(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesBig5ToUtf16le(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length); +int APS5_VABI sceCesBig5ToUtf32(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint32_t* utf32); +int APS5_VABI sceCesBig5ToUtf32be(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint32_t* utf32); +int APS5_VABI sceCesBig5ToUtf32le(const void* profile, const uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size, uint32_t* utf32); +int APS5_VABI sceCesUcs2ToEucKr(uint16_t ucs2, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf8ToEucKr(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf16ToEucKr(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf16beToEucKr(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf16leToEucKr(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf32ToEucKr(uint32_t utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf32beToEucKr(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUtf32leToEucKr(const uint32_t* utf32, const void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucSize); +int APS5_VABI sceCesUcs2ToUhc(uint16_t ucs2, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize); +int APS5_VABI sceCesUtf8ToUhc(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize); +int APS5_VABI sceCesUtf16ToUhc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize); +int APS5_VABI sceCesUtf16beToUhc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize); +int APS5_VABI sceCesUtf16leToUhc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize); +int APS5_VABI sceCesUtf32ToUhc(uint32_t utf32, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize); +int APS5_VABI sceCesUtf32beToUhc(const uint32_t* utf32, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize); +int APS5_VABI sceCesUtf32leToUhc(const uint32_t* utf32, const void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcSize); +int APS5_VABI sceCesUcs2ToBig5(uint16_t ucs2, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size); +int APS5_VABI sceCesUtf8ToBig5(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size); +int APS5_VABI sceCesUtf16ToBig5(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size); +int APS5_VABI sceCesUtf16beToBig5(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size); +int APS5_VABI sceCesUtf16leToBig5(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Length, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size); +int APS5_VABI sceCesUtf32ToBig5(uint32_t utf32, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size); +int APS5_VABI sceCesUtf32beToBig5(const uint32_t* utf32, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size); +int APS5_VABI sceCesUtf32leToBig5(const uint32_t* utf32, const void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Size); +int APS5_VABI sceCesEucCnUcsProfileSetSbcs(void); +int APS5_VABI sceCesEucJpUcsProfileSetSbcs(void); +int APS5_VABI sceCesEucKrUcsProfileSetSbcs(void); +int APS5_VABI sceCesGbUcsProfileSet4ByteCharRange(void); +int APS5_VABI sceCesGbUcsProfileSetSbcs(void); +int APS5_VABI sceCesGbUcsProfileSetUdaMapping(void); +int APS5_VABI sceCesIso2022JpUcsProfileSetSbcs(void); +int APS5_VABI sceCesIso2022StrGetUcs2Len(void); +int APS5_VABI sceCesIso2022StrGetUtf16Len(void); +int APS5_VABI sceCesIso2022StrGetUtf32Len(void); +int APS5_VABI sceCesIso2022StrGetUtf8Len(void); +int APS5_VABI sceCesIso2022StrToUcs2Str(void); +int APS5_VABI sceCesIso2022StrToUtf16Str(void); +int APS5_VABI sceCesIso2022StrToUtf32Str(void); +int APS5_VABI sceCesIso2022StrToUtf8Str(void); +int APS5_VABI sceCesIso2022UcsContextInit(void); +int APS5_VABI sceCesIso2022UcsContextInitCopy(void); +int APS5_VABI sceCesJisGetLevel(void); +int APS5_VABI sceCesJiscsToUcs(void); +int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharCode(void); +int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharUCode(void); +int APS5_VABI sceCesRefersUcsProfileAscii(void); +int APS5_VABI sceCesRefersUcsProfileAsciiWithKatakana(void); +int APS5_VABI sceCesRefersUcsProfileBs4730(void); +int APS5_VABI sceCesRefersUcsProfileCp1253(void); +int APS5_VABI sceCesRefersUcsProfileCp1255(void); +int APS5_VABI sceCesRefersUcsProfileCp1257(void); +int APS5_VABI sceCesRefersUcsProfileCp857(void); +int APS5_VABI sceCesRefersUcsProfileCp858(void); +int APS5_VABI sceCesRefersUcsProfileCp864(void); +int APS5_VABI sceCesRefersUcsProfileCp869(void); +int APS5_VABI sceCesRefersUcsProfileCp874(void); +int APS5_VABI sceCesRefersUcsProfileGbT1988(void); +int APS5_VABI sceCesRefersUcsProfileGbT1988Tilde0x7e(void); +int APS5_VABI sceCesRefersUcsProfileIso8859_11(void); +int APS5_VABI sceCesRefersUcsProfileIso8859_3(void); +int APS5_VABI sceCesRefersUcsProfileIso8859_6(void); +int APS5_VABI sceCesRefersUcsProfileIso8859_7(void); +int APS5_VABI sceCesRefersUcsProfileIso8859_8(void); +int APS5_VABI sceCesRefersUcsProfileJisX0201(void); +int APS5_VABI sceCesRefersUcsProfileJisX0201Katakana(void); +int APS5_VABI sceCesRefersUcsProfileJisX0201Roman(void); +int APS5_VABI sceCesRefersUcsProfileJisX0201RomanTilde0x7e(void); +int APS5_VABI sceCesRefersUcsProfileJisX0201Tilde0x7e(void); +int APS5_VABI sceCesRefersUcsProfileKsX1003(void); +int APS5_VABI sceCesRefersUcsProfileKsX1003Tilde0x7e(void); +int APS5_VABI sceCesSJisUcsProfileSetSbcs(void); +int APS5_VABI sceCesUcs2StrGetIso2022Len(void); +int APS5_VABI sceCesUcs2StrToIso2022Str(void); +int APS5_VABI sceCesUcs2ToMbc(uint16_t ucs2, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen); +int APS5_VABI sceCesUcsProfileInitEucJis2004(void); +int APS5_VABI sceCesUcsProfileInitIso2022JpCp50221(void); +int APS5_VABI sceCesUcsProfileInitIso2022JpCp50222(void); +int APS5_VABI sceCesUcsProfileInitIso2022JpRfc1468(void); +int APS5_VABI sceCesUcsProfileInitSJis2004X0213(void); +int APS5_VABI sceCesUcsToJiscs(void); +int APS5_VABI sceCesUhcUcsProfileSetSbcs(void); +int APS5_VABI sceCesUtf16StrGetIso2022Len(void); +int APS5_VABI sceCesUtf16StrToIso2022Str(void); +int APS5_VABI sceCesUtf16ToMbc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen); +int APS5_VABI sceCesUtf16beToMbc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen); +int APS5_VABI sceCesUtf16leToMbc(const uint16_t* utf16Buffer, uint32_t utf16Max, uint32_t* utf16Len, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen); +int APS5_VABI sceCesUtf32StrGetIso2022Len(void); +int APS5_VABI sceCesUtf32StrToIso2022Str(void); +int APS5_VABI sceCesUtf32ToMbc(uint32_t utf32, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen); +int APS5_VABI sceCesUtf32beToMbc(const uint32_t* utf32, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen); +int APS5_VABI sceCesUtf32leToMbc(const uint32_t* utf32, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen); +int APS5_VABI sceCesUtf8StrGetIso2022Len(void); +int APS5_VABI sceCesUtf8StrToIso2022Str(void); +int APS5_VABI sceCesUtf8ToMbc(const uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Len, const void* profile, uint8_t* mbcBuffer, uint32_t mbcMax, uint32_t* mbcLen); +int APS5_VABI sceCesUcs2ToUtf8(uint16_t source, uint8_t* target, uint32_t capacity, uint32_t* targetLength); +int APS5_VABI sceCesUtf8ToUcs2(const uint8_t* source, uint32_t available, uint32_t* sourceLength, uint16_t* target); +int APS5_VABI sceCesUtf8ToUtf32(const uint8_t* source, uint32_t available, uint32_t* sourceLength, uint32_t* target); +int APS5_VABI sceCesUtf8ToUtf32be(const uint8_t* source, uint32_t available, uint32_t* sourceLength, uint32_t* target); +int APS5_VABI sceCesUtf8ToUtf32le(const uint8_t* source, uint32_t available, uint32_t* sourceLength, uint32_t* target); +int APS5_VABI sceCesUtf8ToUtf16(const uint8_t* source, uint32_t available, uint32_t* sourceLength, uint16_t* target, uint32_t capacity, uint32_t* targetLength); +int APS5_VABI sceCesUtf8ToUtf16be(const uint8_t* source, uint32_t available, uint32_t* sourceLength, uint16_t* target, uint32_t capacity, uint32_t* targetLength); +int APS5_VABI sceCesUtf8ToUtf16le(const uint8_t* source, uint32_t available, uint32_t* sourceLength, uint16_t* target, uint32_t capacity, uint32_t* targetLength); +int APS5_VABI sceCesUtf16ToUtf8(const uint16_t* source, uint32_t available, uint32_t* sourceLength, uint8_t* target, uint32_t capacity, uint32_t* targetLength); +int APS5_VABI sceCesUtf16beToUtf8(const uint16_t* source, uint32_t available, uint32_t* sourceLength, uint8_t* target, uint32_t capacity, uint32_t* targetLength); +int APS5_VABI sceCesUtf16leToUtf8(const uint16_t* source, uint32_t available, uint32_t* sourceLength, uint8_t* target, uint32_t capacity, uint32_t* targetLength); +int APS5_VABI sceCesUtf32ToUtf8(uint32_t source, uint8_t* target, uint32_t capacity, uint32_t* targetLength); +int APS5_VABI sceCesUtf32beToUtf8(const uint32_t* source, uint8_t* target, uint32_t capacity, uint32_t* targetLength); +int APS5_VABI sceCesUtf32leToUtf8(const uint32_t* source, uint8_t* target, uint32_t capacity, uint32_t* targetLength); + +} diff --git a/core/libs/prx/libSceCesCs/libSceCesCs.hpp b/core/libs/prx/libSceCesCs/libSceCesCs.hpp new file mode 100644 index 0000000000..d1bff8e93d --- /dev/null +++ b/core/libs/prx/libSceCesCs/libSceCesCs.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCesCs/include/Api.hpp" diff --git a/core/libs/prx/libSceCommonDialog/CommonDialog.hpp b/core/libs/prx/libSceCommonDialog/CommonDialog.hpp index c9eb03df55..260eb371bc 100644 --- a/core/libs/prx/libSceCommonDialog/CommonDialog.hpp +++ b/core/libs/prx/libSceCommonDialog/CommonDialog.hpp @@ -1,7 +1,254 @@ #ifndef CORE_LIBS_PRX_LIBSCECOMMONDIALOG_COMMONDIALOG_HPP #define CORE_LIBS_PRX_LIBSCECOMMONDIALOG_COMMONDIALOG_HPP +#include +#include +#include +#include + +struct CommonDialogBaseParam { + std::uint64_t size; + std::uint8_t reserved[36]; + std::uint32_t magic; +}; + +struct NpAuthAuthorizedAppDialogParam { + CommonDialogBaseParam base; + std::uint64_t size; + std::int32_t userId; + std::uint32_t padding60; + const char* application; + const char* text; + std::uint32_t mode; + std::uint32_t padding84; + std::uint8_t reserved[32]; +}; + +static_assert(sizeof(NpAuthAuthorizedAppDialogParam) == 120); +static_assert(offsetof(NpAuthAuthorizedAppDialogParam, application) == 64); +static_assert(offsetof(NpAuthAuthorizedAppDialogParam, text) == 72); +static_assert(offsetof(NpAuthAuthorizedAppDialogParam, mode) == 80); +static_assert(offsetof(NpAuthAuthorizedAppDialogParam, reserved) == 88); + +struct VrSetupDialogParam { + std::uint64_t size; + std::uint8_t reserved8; + std::uint8_t reserved9; + std::uint8_t data[46]; +}; + +static_assert(sizeof(VrSetupDialogParam) == 56); + +struct UsbStorageDialogParam { + CommonDialogBaseParam base; + std::uint64_t size; + char path[1024]; + std::int32_t userId; + std::uint32_t padding; +}; + +struct UsbStorageDialogResult { + std::uint64_t size; + std::int32_t result; + std::int32_t status; + std::uint8_t reserved[64]; +}; + +static_assert(offsetof(UsbStorageDialogParam, path) == 56); +static_assert(offsetof(UsbStorageDialogParam, userId) == 1080); +static_assert(sizeof(UsbStorageDialogResult) == 80); +static_assert(offsetof(UsbStorageDialogResult, result) == 8); +static_assert(offsetof(UsbStorageDialogResult, status) == 12); + +struct LoginDialogParam { + std::uint32_t size; + std::uint32_t mode; + std::int32_t userIds[9]; + std::uint32_t reserved[5]; +}; + +struct LoginDialogResult { + std::int32_t result; + std::int32_t errorCode; + std::uint8_t data[8]; +}; + +static_assert(sizeof(LoginDialogParam) == 64); +static_assert(offsetof(LoginDialogParam, userIds) == 8); +static_assert(offsetof(LoginDialogParam, reserved) == 44); +static_assert(sizeof(LoginDialogResult) == 16); +static_assert(offsetof(LoginDialogResult, errorCode) == 4); + +struct NetCtlApDialogParam { + CommonDialogBaseParam base; + std::uint64_t size; + std::uint32_t value56; + std::uint32_t reserved60; + char option[9]; + std::uint8_t reserved[31]; +}; + +struct NetCtlApDialogResult { + std::int32_t result; + std::uint8_t reserved[36]; +}; + +static_assert(sizeof(NetCtlApDialogParam) == 104); +static_assert(offsetof(NetCtlApDialogParam, option) == 64); +static_assert(offsetof(NetCtlApDialogParam, reserved) == 73); +static_assert(sizeof(NetCtlApDialogResult) == 40); + +struct G2PDialogParam { + CommonDialogBaseParam base; + std::int32_t userId; + std::uint32_t padding; + std::uint64_t size; + std::uint8_t reserved[64]; +}; + +struct PlayerReviewDialogParam { + CommonDialogBaseParam base; + std::uint64_t size; + std::int32_t userId; + std::uint8_t target[40]; + std::uint32_t mode; + std::uint8_t reserved[64]; +}; + +struct PlayerSelectionDialogPlayer { + std::uint64_t accountId; + std::uint32_t option0; + std::uint32_t option1; + std::uint32_t mode; + std::uint8_t reserved[64]; + std::uint32_t padding; +}; + +struct PlayerSelectionDialogParam { + CommonDialogBaseParam base; + std::uint64_t size; + std::int32_t userId; + char title[64]; + std::uint32_t maxPlayers; + std::uint32_t mode; + std::uint32_t padding0; + const PlayerSelectionDialogPlayer* players; + std::uint32_t playerCount; + std::uint8_t reserved[64]; + std::uint32_t padding1; +}; + +struct PlayerSelectionDialogResult { + std::int32_t result; + std::uint32_t option; + std::uint64_t* accountIds; + std::uint32_t count; + std::uint32_t padding; +}; + +static_assert(sizeof(PlayerSelectionDialogPlayer) == 88); +static_assert(offsetof(PlayerSelectionDialogPlayer, reserved) == 20); +static_assert(sizeof(PlayerSelectionDialogParam) == 216); +static_assert(offsetof(PlayerSelectionDialogParam, maxPlayers) == 124); +static_assert(offsetof(PlayerSelectionDialogParam, players) == 136); +static_assert(offsetof(PlayerSelectionDialogParam, reserved) == 148); +static_assert(sizeof(PlayerSelectionDialogResult) == 24); +static_assert(offsetof(PlayerSelectionDialogResult, accountIds) == 8); + +struct NpCommerceDialogResult { + std::int32_t result; + std::uint8_t authorized; + std::uint8_t padding[3]; + std::uint64_t userData; + std::uint8_t reserved[32]; +}; + +static_assert(sizeof(NpCommerceDialogResult) == 48); +static_assert(offsetof(NpCommerceDialogResult, userData) == 8); + +struct NpCommerceDialogParam { + CommonDialogBaseParam base; + std::uint32_t size; + std::int32_t userId; + std::uint32_t mode; + std::uint32_t option; + const char* const* products; + std::uint32_t productCount; + std::uint32_t padding; + std::uint64_t argument; + std::uint64_t userData; + std::uint8_t reserved[32]; +}; + +static_assert(sizeof(NpCommerceDialogParam) == 128); +static_assert(offsetof(NpCommerceDialogParam, products) == 64); +static_assert(offsetof(NpCommerceDialogParam, userData) == 88); + +struct NpCommerceDialogParam2 { + CommonDialogBaseParam base; + std::uint32_t size; + std::int32_t userId; + std::uint32_t mode; + std::uint32_t option; + std::uint32_t contentMode; + std::uint32_t padding0; + const char* const* products; + std::uint32_t productCount; + std::uint32_t padding1; + std::uint64_t argument; + std::uint64_t userData; + std::uint8_t reserved[32]; +}; + +static_assert(sizeof(NpCommerceDialogParam2) == 136); +static_assert(offsetof(NpCommerceDialogParam2, products) == 72); +static_assert(offsetof(NpCommerceDialogParam2, userData) == 96); + +struct WebBrowserDialogExtendedResult { + std::uint64_t size; + std::uint32_t type; + char content[2048]; + std::uint8_t reserved0[4]; + char* extendedContent; + std::uint32_t extendedCapacity; + std::uint8_t reserved1[20]; +}; + +struct WebBrowserDialogResult { + std::int32_t result; + std::uint32_t padding; + WebBrowserDialogExtendedResult* extended; + std::uint8_t reserved[240]; +}; + +static_assert(sizeof(WebBrowserDialogExtendedResult) == 2096); +static_assert(offsetof(WebBrowserDialogExtendedResult, extendedContent) == 2064); +static_assert(sizeof(WebBrowserDialogResult) == 256); + +struct WebBrowserDialogAllowList { + std::uint64_t size; + const char* addresses[20]; + std::uint8_t reserved[256]; +}; + +static_assert(sizeof(WebBrowserDialogAllowList) == 424); + +struct CommonDialogResultPrefix { + std::uint8_t untouched[8]; + std::int32_t result; +}; + +static_assert(sizeof(CommonDialogBaseParam) == 48); +static_assert(sizeof(G2PDialogParam) == 128); +static_assert(sizeof(PlayerReviewDialogParam) == 168); +static_assert(offsetof(CommonDialogResultPrefix, result) == 8); + +inline bool ValidateCommonDialogBaseParam(const CommonDialogBaseParam& param) { + return param.size == sizeof(param) && param.magic == static_cast(reinterpret_cast(¶m)) - 1060003575u && std::all_of(std::begin(param.reserved), std::end(param.reserved), [](std::uint8_t value) { return value == 0; }); +} + constexpr int COMMON_DIALOG_OK = 0; +constexpr int COMMON_DIALOG_ERROR_ALREADY_SYSTEM_INITIALIZED = static_cast(0x80B80002); constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004); #endif diff --git a/core/libs/prx/libSceCommonDialog/Export.cpp b/core/libs/prx/libSceCommonDialog/Export.cpp index bb18755ae2..8d090d7d35 100644 --- a/core/libs/prx/libSceCommonDialog/Export.cpp +++ b/core/libs/prx/libSceCommonDialog/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceCommonDialog/include/Api.hpp" #include #include #include @@ -12,7 +13,7 @@ extern "C" { int APS5_VABI sceCommonDialogInitialize(void) { bool expected = false; if (!g_initialized.compare_exchange_strong(expected, true)) { - return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + return COMMON_DIALOG_ERROR_ALREADY_SYSTEM_INITIALIZED; } return COMMON_DIALOG_OK; } diff --git a/core/libs/prx/libSceCommonDialog/include/Api.hpp b/core/libs/prx/libSceCommonDialog/include/Api.hpp new file mode 100644 index 0000000000..50330e8aa4 --- /dev/null +++ b/core/libs/prx/libSceCommonDialog/include/Api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCommonDialogInitialize(void); +bool APS5_VABI sceCommonDialogIsUsed(void); + +} diff --git a/core/libs/prx/libSceCommonDialog/libSceCommonDialog.hpp b/core/libs/prx/libSceCommonDialog/libSceCommonDialog.hpp new file mode 100644 index 0000000000..2379b9c83f --- /dev/null +++ b/core/libs/prx/libSceCommonDialog/libSceCommonDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCommonDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceCompanionHttpd/CMakeLists.txt b/core/libs/prx/libSceCompanionHttpd/CMakeLists.txt new file mode 100644 index 0000000000..cdc8922b8a --- /dev/null +++ b/core/libs/prx/libSceCompanionHttpd/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(libSceCompanionHttpd SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceCompanionHttpd PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceCompanionHttpd) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_companion_httpd_tests ${LIBS_INCLUDE_DIR}/tests/GuestCompanionHttpd.cpp) + target_include_directories(guest_companion_httpd_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_companion_httpd_tests PRIVATE libSceCompanionHttpd libc) + configure_windows_unwind(guest_companion_httpd_tests) + add_test(NAME guest_companion_httpd COMMAND guest_companion_httpd_tests) + if(WIN32) + set_tests_properties(guest_companion_httpd PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceCompanionHttpd/Export.cpp b/core/libs/prx/libSceCompanionHttpd/Export.cpp index 48240a3fe1..7941374a21 100644 --- a/core/libs/prx/libSceCompanionHttpd/Export.cpp +++ b/core/libs/prx/libSceCompanionHttpd/Export.cpp @@ -1,91 +1,154 @@ +#include "prx/libSceCompanionHttpd/include/Api.hpp" #include #include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int CompanionHttpdErrorInvalidParam = static_cast(0x80E40004u); +constexpr int CompanionHttpdErrorNotInitialized = static_cast(0x80E40006u); +constexpr int CompanionHttpdErrorAlreadyInitialized = static_cast(0x80E40007u); +constexpr int CompanionHttpdErrorNoEvent = static_cast(0x80E40008u); +constexpr int CompanionHttpdErrorAlreadyStarted = static_cast(0x80E4000Au); +constexpr int CompanionHttpdErrorNotStarted = static_cast(0x80E4000Bu); +constexpr int CompanionHttpdErrorAlreadyRegistered = static_cast(0x80E4000Cu); + +static_assert(sizeof(CompanionHttpdOptParam) == 0x60); + +struct CompanionHttpdState { + std::mutex mutex; + bool initialized = false; + bool started = false; + CompanionHttpdRequestCallback callback = nullptr; + void* callbackArgument = nullptr; +}; + +CompanionHttpdState& State() { + static CompanionHttpdState state; + return state; +} + +} + extern "C" { -int APS5_VABI sceCompanionHttpdAddHeader() { +int APS5_VABI sceCompanionHttpdOptParamInitialize(CompanionHttpdOptParam* param) { + if (param == nullptr) return CompanionHttpdErrorInvalidParam; + std::memset(param, 0, sizeof(*param)); + return 0; +} + +int APS5_VABI sceCompanionHttpdInitialize(const CompanionHttpdOptParam* param) { + if (param == nullptr) return CompanionHttpdErrorInvalidParam; + auto& state = State(); + std::lock_guard lock(state.mutex); + if (state.initialized) return CompanionHttpdErrorAlreadyInitialized; + state.initialized = true; + return 0; +} + +int APS5_VABI sceCompanionHttpdTerminate(void) { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.initialized) return CompanionHttpdErrorNotInitialized; + if (state.started) NotImplemented_nid_no_patch(__func__); + state.initialized = false; + state.callback = nullptr; + state.callbackArgument = nullptr; + return 0; +} + +int APS5_VABI sceCompanionHttpdRegisterRequestCallback(CompanionHttpdRequestCallback function, void* argument) { + if (function == nullptr) return CompanionHttpdErrorInvalidParam; + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.initialized) return CompanionHttpdErrorNotInitialized; + if (state.callback != nullptr) return CompanionHttpdErrorAlreadyRegistered; + state.callback = function; + state.callbackArgument = argument; + return 0; +} + +int APS5_VABI sceCompanionHttpdUnregisterRequestCallback(void) { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.initialized) return CompanionHttpdErrorNotInitialized; + if (state.callback == nullptr) NotImplemented_nid_no_patch(__func__); + state.callback = nullptr; + state.callbackArgument = nullptr; + return 0; +} + +int APS5_VABI sceCompanionHttpdStart(void) { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.initialized) return CompanionHttpdErrorNotInitialized; + if (state.started) return CompanionHttpdErrorAlreadyStarted; + state.started = true; + return 0; +} + +int APS5_VABI sceCompanionHttpdStop(void) { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.initialized) return CompanionHttpdErrorNotInitialized; + if (!state.started) return CompanionHttpdErrorNotStarted; + state.started = false; + return 0; +} + +int APS5_VABI sceCompanionHttpdGetEvent(void* event) { + if (event == nullptr) return CompanionHttpdErrorInvalidParam; + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.initialized) return CompanionHttpdErrorNotInitialized; + return CompanionHttpdErrorNoEvent; +} + +int APS5_VABI sceCompanionHttpdAddHeader(const char* key, const char* value, void* response) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCompanionHttpdGet2ndScreenStatus() { +int APS5_VABI sceCompanionHttpdSetStatus(int status, void* response) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCompanionHttpdGetEvent() { +int APS5_VABI sceCompanionHttpdSetBody(const char* body, std::uint64_t size, void* response) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCompanionHttpdGetUserId() { +int APS5_VABI sceCompanionHttpdGet2ndScreenStatus(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCompanionHttpdInitialize() { +int APS5_VABI sceCompanionHttpdGetUserId(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCompanionHttpdInitialize2() { +int APS5_VABI sceCompanionHttpdInitialize2(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCompanionHttpdOptParamInitialize() { +int APS5_VABI sceCompanionHttpdRegisterRequestBodyReceptionCallback(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCompanionHttpdRegisterRequestBodyReceptionCallback() { +int APS5_VABI sceCompanionHttpdRegisterRequestCallback2(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCompanionHttpdRegisterRequestCallback() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCompanionHttpdRegisterRequestCallback2() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCompanionHttpdSetBody() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCompanionHttpdSetStatus() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCompanionHttpdStart() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCompanionHttpdStop() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCompanionHttpdTerminate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCompanionHttpdUnregisterRequestBodyReceptionCallback() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCompanionHttpdUnregisterRequestCallback() { +int APS5_VABI sceCompanionHttpdUnregisterRequestBodyReceptionCallback(void) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceCompanionHttpd/include/Api.hpp b/core/libs/prx/libSceCompanionHttpd/include/Api.hpp new file mode 100644 index 0000000000..6a7e2d194d --- /dev/null +++ b/core/libs/prx/libSceCompanionHttpd/include/Api.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "SceTypes.hpp" + +struct CompanionHttpdOptParam { + std::uint64_t reserved; + void* workMemory; + std::uint64_t workMemorySize; + std::uint8_t settings[0x40]; + const void* temporaryMountPoint; +}; + +using CompanionHttpdRequestCallback = int (*)(int, int, const void*, void*, void*); + +extern "C" { + +int APS5_VABI sceCompanionHttpdOptParamInitialize(CompanionHttpdOptParam* param); +int APS5_VABI sceCompanionHttpdInitialize(const CompanionHttpdOptParam* param); +int APS5_VABI sceCompanionHttpdTerminate(void); +int APS5_VABI sceCompanionHttpdRegisterRequestCallback(CompanionHttpdRequestCallback function, void* argument); +int APS5_VABI sceCompanionHttpdUnregisterRequestCallback(void); +int APS5_VABI sceCompanionHttpdStart(void); +int APS5_VABI sceCompanionHttpdStop(void); +int APS5_VABI sceCompanionHttpdGetEvent(void* event); +int APS5_VABI sceCompanionHttpdAddHeader(const char* key, const char* value, void* response); +int APS5_VABI sceCompanionHttpdSetStatus(int status, void* response); +int APS5_VABI sceCompanionHttpdSetBody(const char* body, std::uint64_t size, void* response); +int APS5_VABI sceCompanionHttpdGet2ndScreenStatus(void); +int APS5_VABI sceCompanionHttpdGetUserId(void); +int APS5_VABI sceCompanionHttpdInitialize2(void); +int APS5_VABI sceCompanionHttpdRegisterRequestBodyReceptionCallback(void); +int APS5_VABI sceCompanionHttpdRegisterRequestCallback2(void); +int APS5_VABI sceCompanionHttpdUnregisterRequestBodyReceptionCallback(void); + +} diff --git a/core/libs/prx/libSceCompanionHttpd/libSceCompanionHttpd.hpp b/core/libs/prx/libSceCompanionHttpd/libSceCompanionHttpd.hpp new file mode 100644 index 0000000000..9626d1bfa7 --- /dev/null +++ b/core/libs/prx/libSceCompanionHttpd/libSceCompanionHttpd.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCompanionHttpd/include/Api.hpp" diff --git a/core/libs/prx/libSceCompanionUtil/Export.cpp b/core/libs/prx/libSceCompanionUtil/Export.cpp index 7a208122da..810abefbdc 100644 --- a/core/libs/prx/libSceCompanionUtil/Export.cpp +++ b/core/libs/prx/libSceCompanionUtil/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceCompanionUtil/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,7 +6,7 @@ extern "C" { -int APS5_VABI sceCompanionUtilGetEvent() { +int APS5_VABI sceCompanionUtilGetEvent(void* outEvent) { return static_cast(0x80AD0008); } diff --git a/core/libs/prx/libSceCompanionUtil/include/Api.hpp b/core/libs/prx/libSceCompanionUtil/include/Api.hpp new file mode 100644 index 0000000000..c71bbbf72b --- /dev/null +++ b/core/libs/prx/libSceCompanionUtil/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCompanionUtilGetEvent(void* outEvent); +int APS5_VABI sceCompanionUtilGetRemoteOskEvent(); +int APS5_VABI sceCompanionUtilInitialize(); +int APS5_VABI sceCompanionUtilOptParamInitialize(); +int APS5_VABI sceCompanionUtilTerminate(); + +} diff --git a/core/libs/prx/libSceCompanionUtil/libSceCompanionUtil.hpp b/core/libs/prx/libSceCompanionUtil/libSceCompanionUtil.hpp new file mode 100644 index 0000000000..e49fee6a03 --- /dev/null +++ b/core/libs/prx/libSceCompanionUtil/libSceCompanionUtil.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCompanionUtil/include/Api.hpp" diff --git a/core/libs/prx/libSceComposite/Export.cpp b/core/libs/prx/libSceComposite/Export.cpp index ebf58fb725..96949e8e2f 100644 --- a/core/libs/prx/libSceComposite/Export.cpp +++ b/core/libs/prx/libSceComposite/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceComposite/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -26,13 +27,11 @@ int APS5_VABI sceCompositorCommandGpuPerfEnd() { } int APS5_VABI sceCompositorCreateIndirectRenderTarget() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80d40006); } int APS5_VABI sceCompositorDeleteIndirectRenderTarget() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80d40006); } int APS5_VABI sceCompositorFlush() { @@ -261,17 +260,14 @@ int APS5_VABI sceCompositorSetGnmContextCommand() { } int APS5_VABI sceCompositorSetIndirectCanvasCommand() { - NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceCompositorSetIndirectCompositionCommand() { - NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceCompositorSetIndirectRenderTargetConfigCommand() { - NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceComposite/include/Api.hpp b/core/libs/prx/libSceComposite/include/Api.hpp new file mode 100644 index 0000000000..0df26cb6bc --- /dev/null +++ b/core/libs/prx/libSceComposite/include/Api.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCompositorAllocateIndex(); +int APS5_VABI sceCompositorCheckCrash(); +int APS5_VABI sceCompositorCommandGpuPerfBegin(); +int APS5_VABI sceCompositorCommandGpuPerfEnd(); +int APS5_VABI sceCompositorCreateIndirectRenderTarget(); +int APS5_VABI sceCompositorDeleteIndirectRenderTarget(); +int APS5_VABI sceCompositorFlush(); +int APS5_VABI sceCompositorFlushWithRepeat(); +int APS5_VABI sceCompositorGetAnotherProcessSystemAddress(); +int APS5_VABI sceCompositorGetAnotherProcessSystemSize(); +int APS5_VABI sceCompositorGetAnotherProcessVideoAddress(); +int APS5_VABI sceCompositorGetAnotherProcessVideoSize(); +int APS5_VABI sceCompositorGetCanvasHandle(); +int APS5_VABI sceCompositorGetClientList(); +int APS5_VABI sceCompositorGetDmemOffset(); +int APS5_VABI sceCompositorGetDummyAddress(); +int APS5_VABI sceCompositorGetDummySize(); +int APS5_VABI sceCompositorGetRemainedCommandNum(); +int APS5_VABI sceCompositorGetRenderTargetResolution(); +int APS5_VABI sceCompositorGetSystemAddress(); +int APS5_VABI sceCompositorGetSystemSize(); +int APS5_VABI sceCompositorGetVideoAddress(); +int APS5_VABI sceCompositorGetVideoSize(); +int APS5_VABI sceCompositorHandleProcessEvents(); +int APS5_VABI sceCompositorInit(); +int APS5_VABI sceCompositorInitWithProcessOrder(); +int APS5_VABI sceCompositorInsertThreadTraceMarker(); +int APS5_VABI sceCompositorIsDebugCaptureEnabled(); +int APS5_VABI sceCompositorIsReady4K(); +int APS5_VABI sceCompositorLockCommandBuffer(); +int APS5_VABI sceCompositorMakeCanvasHandle(); +int APS5_VABI sceCompositorMapAnotherProcess(); +int APS5_VABI sceCompositorMemoryPoolDecommit(); +int APS5_VABI sceCompositorPrivacyMaskPushArea(); +int APS5_VABI sceCompositorQuit(); +int APS5_VABI sceCompositorReleaseCommandBuffer(); +int APS5_VABI sceCompositorReleaseIndex(); +int APS5_VABI sceCompositorReleaseLocalStall(); +int APS5_VABI sceCompositorReserveCommandBuffer(); +int APS5_VABI sceCompositorResetZoomCommand(); +int APS5_VABI sceCompositorSetAgcCompositeCanvasCommand(); +int APS5_VABI sceCompositorSetAgcContextCommand(); +int APS5_VABI sceCompositorSetCompositeCanvasCommandInC(); +int APS5_VABI sceCompositorSetCursorImageAddress(); +int APS5_VABI sceCompositorSetDebugPositionCommand(); +int APS5_VABI sceCompositorSetEndOfFrameCommand(); +int APS5_VABI sceCompositorSetEventCommand(); +int APS5_VABI sceCompositorSetFilterType(); +int APS5_VABI sceCompositorSetFlipCommand2(); +int APS5_VABI sceCompositorSetGameSufaceControlCommand(); +int APS5_VABI sceCompositorSetGnmContextCommand(); +int APS5_VABI sceCompositorSetIndirectCanvasCommand(); +int APS5_VABI sceCompositorSetIndirectCompositionCommand(); +int APS5_VABI sceCompositorSetIndirectRenderTargetConfigCommand(); +int APS5_VABI sceCompositorSetInvisibleCanvasCommand(); +int APS5_VABI sceCompositorSetKeepDisplayBufferCommand(); +int APS5_VABI sceCompositorSetLocalStallCommand(); +int APS5_VABI sceCompositorSetMemoryCommand(); +int APS5_VABI sceCompositorSetPatchCommand(); +int APS5_VABI sceCompositorSetPostEventCommand(); +int APS5_VABI sceCompositorSetRepeatCommand(); +int APS5_VABI sceCompositorSetResolutionCommand(); +int APS5_VABI sceCompositorSetScalingLayoutInfo(); +int APS5_VABI sceCompositorSetSeparateContextCommand(); +int APS5_VABI sceCompositorSetSystemConfigWithMask(); +int APS5_VABI sceCompositorSetVideoOutMode(); +int APS5_VABI sceCompositorSetVirtualCanvasAgcContextCommand(); +int APS5_VABI sceCompositorSetVirtualCanvasCommandFooter(); +int APS5_VABI sceCompositorSetVirtualCanvasPatchCommand(); +int APS5_VABI sceCompositorSetVrMode(); +int APS5_VABI sceCompositorSetZoomCommand(); +int APS5_VABI sceCompositorSystemConfig(); +int APS5_VABI sceCompositorWaitEndOfRendering(); +int APS5_VABI sceCompositorWaitEvent(); +int APS5_VABI sceCompositorWaitPostEvent(); +int APS5_VABI sceCompsoitorGetGpuClock(); +int APS5_VABI sceCompsoitorGetProcessRenderingTime(); +int APS5_VABI sceCompsoitorGetRenderingTime(); + +} diff --git a/core/libs/prx/libSceComposite/libSceComposite.hpp b/core/libs/prx/libSceComposite/libSceComposite.hpp new file mode 100644 index 0000000000..2829781c84 --- /dev/null +++ b/core/libs/prx/libSceComposite/libSceComposite.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceComposite/include/Api.hpp" diff --git a/core/libs/prx/libSceCompositeExt/libSceCompositeExt.hpp b/core/libs/prx/libSceCompositeExt/libSceCompositeExt.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceCompositeExt/libSceCompositeExt.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceConnectAccountDialog/libSceConnectAccountDialog.hpp b/core/libs/prx/libSceConnectAccountDialog/libSceConnectAccountDialog.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceConnectAccountDialog/libSceConnectAccountDialog.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceContentDelete/Export.cpp b/core/libs/prx/libSceContentDelete/Export.cpp index 3a9fe2a4c8..ecd7c66372 100644 --- a/core/libs/prx/libSceContentDelete/Export.cpp +++ b/core/libs/prx/libSceContentDelete/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceContentDelete/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -9,32 +10,33 @@ namespace { std::atomic g_initialized{false}; -void RequireInitialized(const char* function) { - if (!g_initialized.load()) throw std::logic_error(std::string(function) + ": not initialized"); -} +constexpr int InvalidArgument = static_cast(0x809D5001u); +constexpr int NotInitialized = static_cast(0x809D5002u); +constexpr int AlreadyInitialized = static_cast(0x809D5003u); } extern "C" { -int APS5_VABI sceContentDeleteInitialize(const ContentDeleteInitParam* init_param) { - if (init_param == nullptr) APS5_INVALID_ARG_EX; +int APS5_VABI sceContentDeleteInitialize(const ContentDeleteInitParam* initParam) { + if (initParam == nullptr || initParam->heap_size != 0x4000) return InvalidArgument; bool expected = false; - if (!g_initialized.compare_exchange_strong(expected, true)) throw std::logic_error(std::string(__func__) + ": already initialized"); + if (!g_initialized.compare_exchange_strong(expected, true)) return AlreadyInitialized; return 0; } int APS5_VABI sceContentDeleteByPath(const char* contentPath) { + if (!g_initialized.load()) return NotInitialized; if (contentPath == nullptr) APS5_INVALID_ARG_EX; - RequireInitialized(__func__); + if (*contentPath == 0) return InvalidArgument; throw std::logic_error(std::string(__func__) + ": " + contentPath + " is not in the host content library, which is empty, and the console's error for it is unknown"); } int APS5_VABI sceContentDeleteById(std::int64_t contentId) { - RequireInitialized(__func__); + if (!g_initialized.load()) return NotInitialized; throw std::logic_error(std::string(__func__) + ": content id " + std::to_string(contentId) + " is not in the host content library, which is empty, and the console's error for it is unknown"); } -int APS5_VABI sceContentDeleteByIdListForSf(void) { +int APS5_VABI sceContentDeleteByIdListForSf(const std::int64_t* contentIds, std::size_t count) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -56,7 +58,7 @@ int APS5_VABI sceContentDeleteMemoryTerminate(void) { int APS5_VABI sceContentDeleteTerminate(void) { bool expected = true; - if (!g_initialized.compare_exchange_strong(expected, false)) throw std::logic_error(std::string(__func__) + ": not initialized"); + if (!g_initialized.compare_exchange_strong(expected, false)) return NotInitialized; return 0; } diff --git a/core/libs/prx/libSceContentDelete/include/Api.hpp b/core/libs/prx/libSceContentDelete/include/Api.hpp new file mode 100644 index 0000000000..cabb8b7ac2 --- /dev/null +++ b/core/libs/prx/libSceContentDelete/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceContentDeleteInitialize(const ContentDeleteInitParam* initParam); +int APS5_VABI sceContentDeleteByPath(const char* contentPath); +int APS5_VABI sceContentDeleteById(std::int64_t contentId); +int APS5_VABI sceContentDeleteByIdListForSf(const std::int64_t* contentIds, std::size_t count); +int APS5_VABI sceContentDeleteGlobalMstat(void); +int APS5_VABI sceContentDeleteMemoryInitialize(void); +int APS5_VABI sceContentDeleteMemoryTerminate(void); +int APS5_VABI sceContentDeleteTerminate(void); + +} diff --git a/core/libs/prx/libSceContentDelete/libSceContentDelete.hpp b/core/libs/prx/libSceContentDelete/libSceContentDelete.hpp new file mode 100644 index 0000000000..5fc5f90294 --- /dev/null +++ b/core/libs/prx/libSceContentDelete/libSceContentDelete.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceContentDelete/include/Api.hpp" diff --git a/core/libs/prx/libSceContentExport/Export.cpp b/core/libs/prx/libSceContentExport/Export.cpp index b8a366cc65..975349f1e5 100644 --- a/core/libs/prx/libSceContentExport/Export.cpp +++ b/core/libs/prx/libSceContentExport/Export.cpp @@ -1,20 +1,23 @@ +#include "prx/libSceContentExport/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include -#include #include #include #include #include namespace { -constexpr std::size_t MaxExecution = 10; +constexpr std::int32_t MaxExecution = 10; +constexpr int ErrorInvalidState = static_cast(0x809D3002u); +constexpr int ErrorBusy = static_cast(0x809D3003u); +constexpr int ErrorNotInitialized = static_cast(0x809D3004u); +constexpr int ErrorExecutionMax = static_cast(0x809D3018u); std::atomic g_initialized{false}; std::mutex g_sessionMutex; std::set g_sessions; -std::int32_t g_lastSessionId = 0; void RequireSession(const char* function, std::int32_t expId) { if (!g_initialized.load()) throw std::logic_error(std::string(function) + ": not initialized"); @@ -40,22 +43,22 @@ int APS5_VABI sceContentExportFinish(std::int32_t expId) { return 0; } -int APS5_VABI sceContentExportFromData(void) { +int APS5_VABI sceContentExportFromData(std::int32_t expId, const ContentExportParam* param, std::size_t contentLength, ContentExportDataCallback callback, void* userData, char* path, std::size_t pathBufferLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceContentExportFromFile(void) { +int APS5_VABI sceContentExportFromFile(std::int32_t expId, const ContentExportParam* param, const char* sourcePath, char* path, std::size_t pathBufferLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceContentExportFromFileWithThumbnail(void) { +int APS5_VABI sceContentExportFromFileWithThumbnail(std::int32_t expId, const ContentExportParam* param, const char* sourcePath, const char* thumbnailPath, char* path, std::size_t pathBufferLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceContentExportAddToFavorites(void) { +int APS5_VABI sceContentExportAddToFavorites(std::int64_t contentId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -75,22 +78,22 @@ int APS5_VABI sceContentExportFrom(void) { return 0; } -int APS5_VABI sceContentExportFromFileForSf(void) { +int APS5_VABI sceContentExportFromFileForSf(std::int32_t expId, const ContentExportParam* param, const ContentExportSfParam* sfParam, const char* sourcePath, const char* thumbnailPath, char* path, std::size_t pathBufferLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceContentExportFromFileWithContentIdList(void) { +int APS5_VABI sceContentExportFromFileWithContentIdList(std::int32_t expId, const ContentExportParam* param, const char* sourcePath, const char* thumbnailPath, const std::int64_t* contentIds, std::uint32_t contentCount, char* path, std::size_t pathBufferLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceContentExportFromFileWithTitleIdList(void) { +int APS5_VABI sceContentExportFromFileWithTitleIdList(std::int32_t expId, const ContentExportParam* param, const char* sourcePath, const char* thumbnailPath, const char* titleIdList, char* path, std::size_t pathBufferLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceContentExportInit(void) { +int APS5_VABI sceContentExportInit(const ContentExportInitParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -100,44 +103,43 @@ int APS5_VABI sceContentExportMalloc(void) { return 0; } -int APS5_VABI sceContentExportRemoveFromFavorites(void) { +int APS5_VABI sceContentExportRemoveFromFavorites(std::int64_t contentId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceContentExportRenameContent(void) { +int APS5_VABI sceContentExportRenameContent(std::int64_t contentId, const char* name) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceContentExportValidateContents(void) { +int APS5_VABI sceContentExportValidateContents(const std::int64_t* contentIds, std::uint32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceContentExportStart(void) { std::lock_guard lock(g_sessionMutex); - if (!g_initialized.load()) throw std::logic_error(std::string(__func__) + ": not initialized"); - if (g_sessions.size() >= MaxExecution) throw std::runtime_error(std::string(__func__) + ": behaviour beyond 10 live export sessions is unknown"); - if (g_lastSessionId == std::numeric_limits::max()) throw std::runtime_error(std::string(__func__) + ": export session ids exhausted"); - g_sessions.insert(++g_lastSessionId); - return g_lastSessionId; + if (!g_initialized.load()) return ErrorNotInitialized; + for (std::int32_t expId = 0; expId < MaxExecution; ++expId) { + if (g_sessions.insert(expId).second) return expId; + } + return ErrorExecutionMax; } int APS5_VABI sceContentExportGetProgress(std::int32_t expId) { std::lock_guard lock(g_sessionMutex); RequireSession(__func__, expId); - throw std::runtime_error(std::string(__func__) + ": progress of a session with no running export is unknown"); + throw std::runtime_error(std::string(__func__) + ": progress requires the calling title SDK compatibility mode"); } int APS5_VABI sceContentExportCancel(std::int32_t expId) { std::lock_guard lock(g_sessionMutex); RequireSession(__func__, expId); - throw std::runtime_error(std::string(__func__) + ": cancelling a session with no running export is unknown"); + return ErrorInvalidState; } -int APS5_VABI sceContentExportFromDataWithThumbnail(std::int32_t expId, const ContentExportParam* param, const ContentExportCallbackParam* contentCallbackParam, - const char* thumbnailType, const ContentExportCallbackParam* thumbnailCallbackParam, char* path, std::size_t pathBufferLength) { +int APS5_VABI sceContentExportFromDataWithThumbnail(std::int32_t expId, const ContentExportParam* param, const ContentExportCallbackParam* contentCallbackParam, const char* thumbnailType, const ContentExportCallbackParam* thumbnailCallbackParam, char* path, std::size_t pathBufferLength) { std::lock_guard lock(g_sessionMutex); RequireSession(__func__, expId); throw std::runtime_error(std::string(__func__) + ": the host has no media library to export into"); @@ -145,9 +147,9 @@ int APS5_VABI sceContentExportFromDataWithThumbnail(std::int32_t expId, const Co int APS5_VABI sceContentExportTerm(void) { std::lock_guard lock(g_sessionMutex); - if (!g_sessions.empty()) throw std::runtime_error(std::string(__func__) + ": terminating with live export sessions is unknown"); + if (!g_sessions.empty()) return ErrorBusy; bool expected = true; - if (!g_initialized.compare_exchange_strong(expected, false)) throw std::logic_error(std::string(__func__) + ": not initialized"); + if (!g_initialized.compare_exchange_strong(expected, false)) return ErrorNotInitialized; return 0; } diff --git a/core/libs/prx/libSceContentExport/include/Api.hpp b/core/libs/prx/libSceContentExport/include/Api.hpp new file mode 100644 index 0000000000..97edfa3e73 --- /dev/null +++ b/core/libs/prx/libSceContentExport/include/Api.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceContentExportInit2(const ContentExportInitParam2* initParam); +int APS5_VABI sceContentExportFinish(std::int32_t expId); +int APS5_VABI sceContentExportFromData(std::int32_t expId, const ContentExportParam* param, std::size_t contentLength, ContentExportDataCallback callback, void* userData, char* path, std::size_t pathBufferLength); +int APS5_VABI sceContentExportFromFile(std::int32_t expId, const ContentExportParam* param, const char* sourcePath, char* path, std::size_t pathBufferLength); +int APS5_VABI sceContentExportFromFileWithThumbnail(std::int32_t expId, const ContentExportParam* param, const char* sourcePath, const char* thumbnailPath, char* path, std::size_t pathBufferLength); +int APS5_VABI sceContentExportAddToFavorites(std::int64_t contentId); +int APS5_VABI sceContentExportDataProvideFunction(void); +int APS5_VABI sceContentExportFree(void); +int APS5_VABI sceContentExportFrom(void); +int APS5_VABI sceContentExportFromFileForSf(std::int32_t expId, const ContentExportParam* param, const ContentExportSfParam* sfParam, const char* sourcePath, const char* thumbnailPath, char* path, std::size_t pathBufferLength); +int APS5_VABI sceContentExportFromFileWithContentIdList(std::int32_t expId, const ContentExportParam* param, const char* sourcePath, const char* thumbnailPath, const std::int64_t* contentIds, std::uint32_t contentCount, char* path, std::size_t pathBufferLength); +int APS5_VABI sceContentExportFromFileWithTitleIdList(std::int32_t expId, const ContentExportParam* param, const char* sourcePath, const char* thumbnailPath, const char* titleIdList, char* path, std::size_t pathBufferLength); +int APS5_VABI sceContentExportInit(const ContentExportInitParam* param); +int APS5_VABI sceContentExportMalloc(void); +int APS5_VABI sceContentExportRemoveFromFavorites(std::int64_t contentId); +int APS5_VABI sceContentExportRenameContent(std::int64_t contentId, const char* name); +int APS5_VABI sceContentExportValidateContents(const std::int64_t* contentIds, std::uint32_t count); +int APS5_VABI sceContentExportStart(void); +int APS5_VABI sceContentExportGetProgress(std::int32_t expId); +int APS5_VABI sceContentExportCancel(std::int32_t expId); +int APS5_VABI sceContentExportFromDataWithThumbnail(std::int32_t expId, const ContentExportParam* param, const ContentExportCallbackParam* contentCallbackParam, const char* thumbnailType, const ContentExportCallbackParam* thumbnailCallbackParam, char* path, std::size_t pathBufferLength); +int APS5_VABI sceContentExportTerm(void); + +} diff --git a/core/libs/prx/libSceContentExport/libSceContentExport.hpp b/core/libs/prx/libSceContentExport/libSceContentExport.hpp new file mode 100644 index 0000000000..a7887a9290 --- /dev/null +++ b/core/libs/prx/libSceContentExport/libSceContentExport.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceContentExport/include/Api.hpp" diff --git a/core/libs/prx/libSceContentListController/libSceContentListController.hpp b/core/libs/prx/libSceContentListController/libSceContentListController.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceContentListController/libSceContentListController.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceContentSearch/Export.cpp b/core/libs/prx/libSceContentSearch/Export.cpp index 2c9b8a5777..0fbd052c73 100644 --- a/core/libs/prx/libSceContentSearch/Export.cpp +++ b/core/libs/prx/libSceContentSearch/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceContentSearch/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -10,6 +11,8 @@ namespace { std::atomic g_initialized{false}; constexpr int ErrorNotInitialized = static_cast(0x809D1001u); +constexpr int ErrorAlreadyInitialized = static_cast(0x809D1002u); +constexpr int ErrorInvalidArgument = static_cast(0x809D1003u); constexpr int ErrorLimitTooBig = static_cast(0x809D100Au); constexpr std::uint32_t MaxSearchLimit = 92; constexpr std::int64_t LibraryUpdateId = 0; @@ -63,42 +66,44 @@ void ValidateOrderBy(const char* funcName, const ContentSearchContentOrderByCond extern "C" { int APS5_VABI sceContentSearchInit(const ContentSearchInitParam* init_param) { - if (init_param == nullptr) APS5_INVALID_ARG_EX; + if (g_initialized.load()) return ErrorAlreadyInitialized; + if (init_param == nullptr || init_param->memory_size == 0 || init_param->memory_size % 16384 != 0) return ErrorInvalidArgument; bool expected = false; - if (!g_initialized.compare_exchange_strong(expected, true)) throw std::logic_error(std::string(__func__) + ": already initialized"); + if (!g_initialized.compare_exchange_strong(expected, true)) return ErrorAlreadyInitialized; return 0; } int APS5_VABI sceContentSearchCloseMetadata(std::int32_t metadataId) { if (!g_initialized.load()) return ErrorNotInitialized; - ThrowUnknownMetadataId(__func__, metadataId); + return ErrorInvalidArgument; } int APS5_VABI sceContentSearchGetMetadataFieldInfo(std::int32_t metadataId, const char* field, std::int32_t* metadataType, std::int32_t* size) { if (!g_initialized.load()) return ErrorNotInitialized; - if (field == nullptr || metadataType == nullptr || size == nullptr) APS5_INVALID_ARG_EX; + if (field == nullptr || metadataType == nullptr || size == nullptr) return ErrorInvalidArgument; ThrowUnknownMetadataId(__func__, metadataId); } int APS5_VABI sceContentSearchGetMetadataValue(std::int32_t metadataId, const char* field, void* value) { if (!g_initialized.load()) return ErrorNotInitialized; - if (field == nullptr || value == nullptr) APS5_INVALID_ARG_EX; + if (field == nullptr || value == nullptr) return ErrorInvalidArgument; ThrowUnknownMetadataId(__func__, metadataId); } int APS5_VABI sceContentSearchOpenMetadata(const char* filePath, std::int32_t* metadataId) { if (!g_initialized.load()) return ErrorNotInitialized; - if (filePath == nullptr || metadataId == nullptr) APS5_INVALID_ARG_EX; + if (filePath == nullptr || metadataId == nullptr) return ErrorInvalidArgument; throw std::logic_error(std::string(__func__) + ": " + filePath + " is not in the host content library, which is empty, and the console's error for it is unknown"); } int APS5_VABI sceContentSearchSearchContent(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::uint32_t offset, std::uint32_t limit, std::int64_t* numOfContent, void* infos, std::int64_t* lastUpdateId) { (void)offset; if (!g_initialized.load()) return ErrorNotInitialized; - if (numOfContent == nullptr || lastUpdateId == nullptr || (infos == nullptr && limit != 0)) APS5_INVALID_ARG_EX; + if ((columnSet == nullptr) != (columnSetLength == 0) || (orderByConditions == nullptr) != (orderByConditionsLength == 0) || limit == 0) return ErrorInvalidArgument; + if (limit > MaxSearchLimit) return ErrorLimitTooBig; + if (numOfContent == nullptr || lastUpdateId == nullptr || infos == nullptr) return ErrorInvalidArgument; ValidateColumnSets(__func__, columnSet, columnSetLength); ValidateOrderBy(__func__, orderByConditions, orderByConditionsLength); - if (limit > MaxSearchLimit) return ErrorLimitTooBig; *numOfContent = 0; *lastUpdateId = LibraryUpdateId; return 0; @@ -106,20 +111,20 @@ int APS5_VABI sceContentSearchSearchContent(const ContentSearchContentColumnSet* int APS5_VABI sceContentSearchGetContentLastUpdateId(std::int64_t* lastUpdateId) { if (!g_initialized.load()) return ErrorNotInitialized; - if (lastUpdateId == nullptr) APS5_INVALID_ARG_EX; + if (lastUpdateId == nullptr) return ErrorInvalidArgument; *lastUpdateId = LibraryUpdateId; return 0; } int APS5_VABI sceContentSearchOpenMetadataByContentId(std::int64_t contentId, std::int32_t* metadataId) { if (!g_initialized.load()) return ErrorNotInitialized; - if (metadataId == nullptr) APS5_INVALID_ARG_EX; + if (metadataId == nullptr) return ErrorInvalidArgument; throw std::logic_error(std::string(__func__) + ": content id " + std::to_string(contentId) + " is not in the host content library, which is empty, and the console's error for it is unknown"); } int APS5_VABI sceContentSearchGetTotalContentSize(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, std::int64_t* size, std::int64_t* lastUpdateId) { if (!g_initialized.load()) return ErrorNotInitialized; - if (size == nullptr || lastUpdateId == nullptr) APS5_INVALID_ARG_EX; + if ((columnSet == nullptr) != (columnSetLength == 0) || size == nullptr || lastUpdateId == nullptr) return ErrorInvalidArgument; ValidateColumnSets(__func__, columnSet, columnSetLength); *size = 0; *lastUpdateId = LibraryUpdateId; @@ -128,7 +133,7 @@ int APS5_VABI sceContentSearchGetTotalContentSize(const ContentSearchContentColu int APS5_VABI sceContentSearchGetNumOfContent(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, std::int64_t* numOfContent, std::int64_t* lastUpdateId) { if (!g_initialized.load()) return ErrorNotInitialized; - if (numOfContent == nullptr || lastUpdateId == nullptr) APS5_INVALID_ARG_EX; + if ((columnSet == nullptr) != (columnSetLength == 0) || numOfContent == nullptr || lastUpdateId == nullptr) return ErrorInvalidArgument; ValidateColumnSets(__func__, columnSet, columnSetLength); *numOfContent = 0; *lastUpdateId = LibraryUpdateId; @@ -137,7 +142,7 @@ int APS5_VABI sceContentSearchGetNumOfContent(const ContentSearchContentColumnSe int APS5_VABI sceContentSearchTerm(void) { bool expected = true; - if (!g_initialized.compare_exchange_strong(expected, false)) throw std::logic_error(std::string(__func__) + ": not initialized"); + if (!g_initialized.compare_exchange_strong(expected, false)) return ErrorNotInitialized; return 0; } diff --git a/core/libs/prx/libSceContentSearch/include/Api.hpp b/core/libs/prx/libSceContentSearch/include/Api.hpp new file mode 100644 index 0000000000..b54e8efb17 --- /dev/null +++ b/core/libs/prx/libSceContentSearch/include/Api.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceContentSearchInit(const ContentSearchInitParam* initParam); +int APS5_VABI sceContentSearchCloseMetadata(std::int32_t metadataId); +int APS5_VABI sceContentSearchGetMetadataFieldInfo(std::int32_t metadataId, const char* field, std::int32_t* metadataType, std::int32_t* size); +int APS5_VABI sceContentSearchGetMetadataValue(std::int32_t metadataId, const char* field, void* value); +int APS5_VABI sceContentSearchOpenMetadata(const char* filePath, std::int32_t* metadataId); +int APS5_VABI sceContentSearchSearchContent(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::uint32_t offset, std::uint32_t limit, std::int64_t* numOfContent, void* infos, std::int64_t* lastUpdateId); +int APS5_VABI sceContentSearchGetContentLastUpdateId(std::int64_t* lastUpdateId); +int APS5_VABI sceContentSearchOpenMetadataByContentId(std::int64_t contentId, std::int32_t* metadataId); +int APS5_VABI sceContentSearchGetTotalContentSize(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, std::int64_t* size, std::int64_t* lastUpdateId); +int APS5_VABI sceContentSearchGetNumOfContent(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, std::int64_t* numOfContent, std::int64_t* lastUpdateId); +int APS5_VABI sceContentSearchTerm(void); +int APS5_VABI sceContentBinderEnd(); +int APS5_VABI sceContentBinderGetNumOfDifference(); +int APS5_VABI sceContentBinderInitialize(); +int APS5_VABI sceContentBinderIsUpdated(); +int APS5_VABI sceContentBinderReset(); +int APS5_VABI sceContentBinderSearchDifference(); +int APS5_VABI sceContentBinderStart(); +int APS5_VABI sceContentBinderTerminate(); +int APS5_VABI sceContentSearchGetApplicationLastUpdateId(); +int APS5_VABI sceContentSearchGetMyApplicationIndex(); +int APS5_VABI sceContentSearchGetNumOfApplication(); +int APS5_VABI sceContentSearchSearchApplication(); + +} diff --git a/core/libs/prx/libSceContentSearch/libSceContentSearch.hpp b/core/libs/prx/libSceContentSearch/libSceContentSearch.hpp new file mode 100644 index 0000000000..79d8740064 --- /dev/null +++ b/core/libs/prx/libSceContentSearch/libSceContentSearch.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceContentSearch/include/Api.hpp" diff --git a/core/libs/prx/libSceConvertKeycode/CMakeLists.txt b/core/libs/prx/libSceConvertKeycode/CMakeLists.txt index d4f82eb5f7..341ff705b0 100644 --- a/core/libs/prx/libSceConvertKeycode/CMakeLists.txt +++ b/core/libs/prx/libSceConvertKeycode/CMakeLists.txt @@ -1,15 +1,41 @@ add_library(libSceConvertKeycode SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceConvertKeycode PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceConvertKeycode PRIVATE libSceUserService) configure_windows_unwind(libSceConvertKeycode) if(BUILD_TESTING) add_test_executable(guest_convert_keycode_tests ${LIBS_INCLUDE_DIR}/tests/GuestConvertKeycode.cpp) target_include_directories(guest_convert_keycode_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_convert_keycode_tests PRIVATE libSceConvertKeycode libc) + target_link_libraries(guest_convert_keycode_tests PRIVATE libSceConvertKeycode libSceUserService libSceSystemService libc) configure_windows_unwind(guest_convert_keycode_tests) add_test(NAME guest_convert_keycode COMMAND guest_convert_keycode_tests) if(WIN32) set_tests_properties(guest_convert_keycode PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_convert_keycode_title_tests ${LIBS_INCLUDE_DIR}/tests/GuestConvertKeycodeTitle.cpp) + target_include_directories(guest_convert_keycode_title_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_convert_keycode_title_tests PRIVATE libSceConvertKeycode libSceUserService libSceSystemService libc) + configure_windows_unwind(guest_convert_keycode_title_tests) + string(REPEAT "T" 130 convertKeycodeLongTitle) + foreach(titleCase IN ITEMS oversized-id oversized-title cusa ppsa malformed-json missing) + set(titleRun ${CMAKE_CURRENT_BINARY_DIR}/convert_keycode_title_${titleCase}) + file(REMOVE_RECURSE ${titleRun}) + file(MAKE_DIRECTORY ${titleRun}) + if(titleCase STREQUAL "oversized-id") + file(WRITE ${titleRun}/app0/sce_sys/param.json "{\"titleId\": \"CUSA0123456789012345\", \"localizedParameters\": {\"en-US\": {\"titleName\": \"Fixture\"}}}") + elseif(titleCase STREQUAL "oversized-title") + file(WRITE ${titleRun}/app0/sce_sys/param.json "{\"titleId\": \"PPSA00000\", \"localizedParameters\": {\"en-US\": {\"titleName\": \"${convertKeycodeLongTitle}\"}}}") + elseif(titleCase STREQUAL "cusa" OR titleCase STREQUAL "ppsa") + string(TOUPPER ${titleCase} titlePrefix) + file(WRITE ${titleRun}/app0/sce_sys/param.json "{\"titleId\": \"${titlePrefix}00000\", \"localizedParameters\": {\"en-US\": {\"titleName\": \"Fixture\"}}}") + elseif(titleCase STREQUAL "malformed-json") + file(WRITE ${titleRun}/app0/sce_sys/param.json "{") + endif() + add_test(NAME guest_convert_keycode_title_${titleCase} COMMAND guest_convert_keycode_title_tests WORKING_DIRECTORY ${titleRun}) + if(WIN32) + set_tests_properties(guest_convert_keycode_title_${titleCase} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() endif() diff --git a/core/libs/prx/libSceConvertKeycode/Export.cpp b/core/libs/prx/libSceConvertKeycode/Export.cpp index 9b9e92908f..f96e2deca1 100644 --- a/core/libs/prx/libSceConvertKeycode/Export.cpp +++ b/core/libs/prx/libSceConvertKeycode/Export.cpp @@ -1,53 +1,2445 @@ +#include "prx/libSceConvertKeycode/include/Api.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include +#include #include +#include #include -#include #include "SceTypes.hpp" -#include "HitLog.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libSceConvertKeycode/ImeKeyboardType.hpp" +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" +#include namespace { constexpr int kErrInvalidAddress = static_cast(0x80BC0031u); -constexpr int kErrInvalidUserId = static_cast(0x80BC0010u); -constexpr std::uint32_t kIgnoredStateBits = 0x4000000u; +constexpr std::array BaseVirtualKeys = { + 255, 255, 255, 255, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 48, 13, 27, 8, 9, 32, 187, 219, 221, + 186, 191, 191, 192, 222, 220, 188, 190, 189, 20, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 44, 145, 19, 45, 36, 33, 46, 35, 34, 39, + 37, 40, 38, 144, 111, 106, 109, 107, 13, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 96, 110, 226, 93, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 12, 255, 255, 255, 255, 255, 255, 255, + 162, 160, 164, 91, 163, 161, 165, 92, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +}; -char16_t UsCharacter(std::uint16_t keycode) { - static constexpr char16_t kPunctuation[] = u"-=[]\\\0;'`,./"; - static constexpr char16_t kKeypad[] = u"/*-+\0" u"1234567890."; - if (keycode >= 0x04 && keycode <= 0x1D) return static_cast(u'a' + (keycode - 0x04)); - if (keycode >= 0x1E && keycode <= 0x26) return static_cast(u'1' + (keycode - 0x1E)); - if (keycode == 0x27) return u'0'; - if (keycode == 0x2C) return u' '; - if (keycode >= 0x2D && keycode <= 0x38) return kPunctuation[keycode - 0x2D]; - if (keycode == 0x64) return u'\\'; - if (keycode >= 0x54 && keycode <= 0x63) return kKeypad[keycode - 0x54]; - return 0; +constexpr auto VirtualKeys = [] { + std::array, 39> layouts{}; + for (auto& layout : layouts) layout = BaseVirtualKeys; + layouts[1][28] = 90; + layouts[1][29] = 89; + layouts[1][45] = 189; + layouts[1][46] = 221; + layouts[1][47] = 186; + layouts[1][48] = 187; + layouts[2][28] = 90; + layouts[2][29] = 89; + layouts[2][45] = 219; + layouts[2][46] = 221; + layouts[2][47] = 186; + layouts[2][48] = 192; + layouts[2][49] = 223; + layouts[2][50] = 223; + layouts[2][51] = 222; + layouts[2][52] = 220; + layouts[2][53] = 191; + layouts[3][45] = 189; + layouts[3][46] = 187; + layouts[3][47] = 219; + layouts[3][48] = 221; + layouts[3][49] = 220; + layouts[3][50] = 220; + layouts[3][51] = 186; + layouts[3][53] = 192; + layouts[3][56] = 191; + layouts[3][100] = 255; + layouts[4][45] = 189; + layouts[4][46] = 187; + layouts[4][47] = 219; + layouts[4][48] = 221; + layouts[4][49] = 222; + layouts[4][50] = 222; + layouts[4][51] = 186; + layouts[4][52] = 192; + layouts[4][53] = 223; + layouts[4][56] = 191; + layouts[5][45] = 219; + layouts[5][46] = 221; + layouts[5][47] = 186; + layouts[5][48] = 187; + layouts[6][45] = 219; + layouts[6][46] = 221; + layouts[6][47] = 186; + layouts[6][48] = 187; + layouts[6][100] = 255; + layouts[8][4] = 81; + layouts[8][16] = 188; + layouts[8][20] = 65; + layouts[8][26] = 90; + layouts[8][29] = 87; + layouts[8][45] = 219; + layouts[8][46] = 187; + layouts[8][49] = 220; + layouts[8][50] = 220; + layouts[8][51] = 77; + layouts[8][52] = 192; + layouts[8][53] = 222; + layouts[8][54] = 190; + layouts[8][55] = 191; + layouts[8][56] = 223; + layouts[9][4] = 81; + layouts[9][16] = 188; + layouts[9][20] = 65; + layouts[9][26] = 90; + layouts[9][29] = 87; + layouts[9][45] = 219; + layouts[9][46] = 189; + layouts[9][49] = 220; + layouts[9][50] = 220; + layouts[9][51] = 77; + layouts[9][52] = 192; + layouts[9][53] = 222; + layouts[9][54] = 190; + layouts[9][55] = 191; + layouts[9][56] = 187; + layouts[10][45] = 189; + layouts[10][46] = 187; + layouts[10][47] = 219; + layouts[10][48] = 221; + layouts[10][49] = 220; + layouts[10][50] = 220; + layouts[10][51] = 186; + layouts[10][52] = 192; + layouts[10][53] = 222; + layouts[10][56] = 191; + layouts[11] = layouts[2]; + layouts[12] = layouts[5]; + layouts[13][45] = 219; + layouts[13][46] = 191; + layouts[13][49] = 220; + layouts[13][50] = 220; + layouts[13][51] = 187; + layouts[13][52] = 192; + layouts[13][53] = 222; + layouts[15][46] = 191; + layouts[15][47] = 219; + layouts[15][48] = 221; + layouts[15][49] = 220; + layouts[15][50] = 220; + layouts[15][51] = 186; + layouts[15][53] = 192; + layouts[16][45] = 189; + layouts[16][46] = 187; + layouts[16][47] = 219; + layouts[16][48] = 221; + layouts[16][49] = 220; + layouts[16][50] = 220; + layouts[16][51] = 186; + layouts[16][53] = 192; + layouts[16][56] = 191; + layouts[17] = layouts[5]; + layouts[18] = layouts[3]; + layouts[20][45] = 223; + layouts[20][46] = 189; + layouts[20][47] = 219; + layouts[20][48] = 221; + layouts[20][49] = 188; + layouts[20][50] = 188; + layouts[20][51] = 186; + layouts[20][52] = 190; + layouts[20][53] = 192; + layouts[20][54] = 191; + layouts[20][55] = 220; + layouts[20][56] = 190; + layouts[21][45] = 189; + layouts[21][46] = 222; + layouts[21][47] = 192; + layouts[21][48] = 219; + layouts[21][49] = 221; + layouts[21][50] = 221; + layouts[21][51] = 187; + layouts[21][52] = 186; + layouts[21][53] = 243; + layouts[21][56] = 191; + layouts[21][100] = 255; + layouts[21][135] = 226; + layouts[21][136] = 242; + layouts[21][137] = 220; + layouts[21][138] = 28; + layouts[21][139] = 247; + layouts[22] = layouts[21]; + layouts[23][45] = 189; + layouts[23][46] = 187; + layouts[23][47] = 219; + layouts[23][48] = 221; + layouts[23][49] = 220; + layouts[23][50] = 220; + layouts[23][51] = 186; + layouts[23][53] = 192; + layouts[23][56] = 191; + layouts[23][136] = 21; + layouts[23][138] = 25; + layouts[24] = layouts[3]; + layouts[25] = layouts[3]; + layouts[26] = layouts[3]; + layouts[27] = layouts[3]; + layouts[28] = layouts[3]; + layouts[29] = layouts[16]; + layouts[30] = layouts[16]; + layouts[31][28] = 90; + layouts[31][29] = 89; + layouts[31][46] = 191; + layouts[31][47] = 219; + layouts[31][48] = 221; + layouts[31][49] = 220; + layouts[31][50] = 220; + layouts[31][51] = 186; + layouts[31][53] = 192; + layouts[32] = layouts[16]; + layouts[33] = layouts[16]; + layouts[34] = layouts[16]; + layouts[35] = layouts[16]; + layouts[36][28] = 90; + layouts[36][29] = 89; + layouts[36][39] = 192; + layouts[36][45] = 191; + layouts[36][46] = 187; + layouts[36][47] = 219; + layouts[36][48] = 221; + layouts[36][49] = 220; + layouts[36][50] = 220; + layouts[36][51] = 186; + layouts[36][53] = 48; + layouts[37] = layouts[3]; + layouts[38] = layouts[3]; + return layouts; +}(); + +constexpr int kErrInvalidKeyboardType = static_cast(0x80BC001Du); +constexpr auto CharacterTables = [] { + std::array, 111> tables{}; + tables[1][4] = 327777; + tables[1][5] = 327778; + tables[1][6] = 327779; + tables[1][7] = 327780; + tables[1][8] = 327781; + tables[1][9] = 327782; + tables[1][10] = 327783; + tables[1][11] = 327784; + tables[1][12] = 327785; + tables[1][13] = 327786; + tables[1][14] = 327787; + tables[1][15] = 327788; + tables[1][16] = 327789; + tables[1][17] = 327790; + tables[1][18] = 327791; + tables[1][19] = 327792; + tables[1][20] = 327793; + tables[1][21] = 327794; + tables[1][22] = 327795; + tables[1][23] = 327796; + tables[1][24] = 327797; + tables[1][25] = 327798; + tables[1][26] = 327799; + tables[1][27] = 327800; + tables[1][28] = 327802; + tables[1][29] = 327801; + tables[1][30] = 262193; + tables[1][31] = 262194; + tables[1][32] = 262195; + tables[1][33] = 262196; + tables[1][34] = 262197; + tables[1][35] = 262198; + tables[1][36] = 262199; + tables[1][37] = 262200; + tables[1][38] = 262201; + tables[1][39] = 262192; + tables[1][40] = 262154; + tables[1][42] = 262152; + tables[1][43] = 262153; + tables[1][44] = 262176; + tables[1][45] = 262183; + tables[1][46] = 262238; + tables[1][47] = 262396; + tables[1][48] = 262312; + tables[1][49] = 262180; + tables[1][50] = 262180; + tables[1][51] = 262390; + tables[1][52] = 262372; + tables[1][53] = 262311; + tables[1][54] = 262188; + tables[1][55] = 262190; + tables[1][56] = 262189; + tables[1][83] = 393299; + tables[1][84] = 393263; + tables[1][85] = 393258; + tables[1][86] = 393261; + tables[1][87] = 393259; + tables[1][88] = 393226; + tables[1][89] = 393293; + tables[1][90] = 393297; + tables[1][91] = 393294; + tables[1][92] = 393296; + tables[1][93] = 393309; + tables[1][94] = 393295; + tables[1][95] = 393290; + tables[1][96] = 393298; + tables[1][97] = 393291; + tables[1][98] = 393289; + tables[1][99] = 393292; + tables[1][100] = 262204; + tables[1][103] = 393277; + tables[1][133] = 393260; + tables[1][134] = 393277; + tables[1][140] = 393260; + tables[2] = tables[1]; + tables[2][4] = 327745; + tables[2][5] = 327746; + tables[2][6] = 327747; + tables[2][7] = 327748; + tables[2][8] = 327749; + tables[2][9] = 327750; + tables[2][10] = 327751; + tables[2][11] = 327752; + tables[2][12] = 327753; + tables[2][13] = 327754; + tables[2][14] = 327755; + tables[2][15] = 327756; + tables[2][16] = 327757; + tables[2][17] = 327758; + tables[2][18] = 327759; + tables[2][19] = 327760; + tables[2][20] = 327761; + tables[2][21] = 327762; + tables[2][22] = 327763; + tables[2][23] = 327764; + tables[2][24] = 327765; + tables[2][25] = 327766; + tables[2][26] = 327767; + tables[2][27] = 327768; + tables[2][28] = 327770; + tables[2][29] = 327769; + tables[2][30] = 262187; + tables[2][31] = 262178; + tables[2][32] = 262186; + tables[2][33] = 262375; + tables[2][34] = 262181; + tables[2][35] = 262182; + tables[2][36] = 262191; + tables[2][37] = 262184; + tables[2][38] = 262185; + tables[2][39] = 262205; + tables[2][45] = 262207; + tables[2][46] = 262240; + tables[2][47] = 262376; + tables[2][48] = 262177; + tables[2][49] = 262307; + tables[2][50] = 262307; + tables[2][51] = 262377; + tables[2][52] = 262368; + tables[2][53] = 262320; + tables[2][54] = 262203; + tables[2][55] = 262202; + tables[2][56] = 262239; + tables[2][89] = 393265; + tables[2][90] = 393266; + tables[2][91] = 393267; + tables[2][92] = 393268; + tables[2][93] = 393269; + tables[2][94] = 393270; + tables[2][95] = 393271; + tables[2][96] = 393272; + tables[2][97] = 393273; + tables[2][98] = 393264; + tables[2][99] = 393262; + tables[2][100] = 262206; + tables[3][8] = 336044; + tables[3][30] = 262310; + tables[3][31] = 262208; + tables[3][32] = 262179; + tables[3][33] = 262320; + tables[3][34] = 262311; + tables[3][35] = 262316; + tables[3][36] = 262268; + tables[3][37] = 262306; + tables[3][45] = 262324; + tables[3][46] = 262270; + tables[3][47] = 262235; + tables[3][48] = 262237; + tables[3][49] = 262269; + tables[3][50] = 262269; + tables[3][52] = 262267; + tables[3][100] = 262236; + tables[3][136] = 262324; + tables[3][137] = 262324; + tables[3][138] = 262324; + tables[3][146] = 262324; + tables[3][147] = 262324; + tables[4] = tables[1]; + tables[4][28] = 327801; + tables[4][29] = 327802; + tables[4][45] = 262189; + tables[4][46] = 262205; + tables[4][47] = 262235; + tables[4][48] = 262237; + tables[4][49] = 262236; + tables[4][50] = 262236; + tables[4][51] = 262203; + tables[4][52] = 262183; + tables[4][53] = 262240; + tables[4][56] = 262191; + tables[4][100] = 262236; + tables[5] = tables[2]; + tables[5][28] = 327769; + tables[5][29] = 327770; + tables[5][30] = 262177; + tables[5][31] = 262208; + tables[5][32] = 262179; + tables[5][33] = 262180; + tables[5][35] = 262238; + tables[5][36] = 262182; + tables[5][37] = 262186; + tables[5][38] = 262184; + tables[5][39] = 262185; + tables[5][45] = 262239; + tables[5][46] = 262187; + tables[5][47] = 262267; + tables[5][48] = 262269; + tables[5][49] = 262268; + tables[5][50] = 262268; + tables[5][51] = 262202; + tables[5][52] = 262178; + tables[5][53] = 262270; + tables[5][54] = 262204; + tables[5][55] = 262206; + tables[5][56] = 262207; + tables[5][100] = 262268; + tables[6][34] = 270508; + tables[7] = tables[1]; + tables[7][4] = 393025; + tables[7][5] = 393026; + tables[7][6] = 393027; + tables[7][7] = 393028; + tables[7][8] = 393029; + tables[7][9] = 393030; + tables[7][10] = 393031; + tables[7][11] = 393032; + tables[7][12] = 393033; + tables[7][13] = 393034; + tables[7][14] = 393035; + tables[7][15] = 393036; + tables[7][16] = 393037; + tables[7][17] = 393038; + tables[7][18] = 393039; + tables[7][19] = 393040; + tables[7][20] = 393041; + tables[7][21] = 393042; + tables[7][22] = 393043; + tables[7][23] = 393044; + tables[7][24] = 393045; + tables[7][25] = 393046; + tables[7][26] = 393047; + tables[7][27] = 393048; + tables[7][28] = 393049; + tables[7][29] = 393050; + tables[7][30] = 327441; + tables[7][31] = 327442; + tables[7][32] = 327443; + tables[7][33] = 327444; + tables[7][34] = 327445; + tables[7][35] = 327446; + tables[7][36] = 327447; + tables[7][37] = 327448; + tables[7][38] = 327449; + tables[7][39] = 327440; + tables[7][44] = 274432; + tables[7][45] = 327437; + tables[7][46] = 327486; + tables[7][47] = 327456; + tables[7][48] = 327483; + tables[7][49] = 327485; + tables[7][50] = 327485; + tables[7][51] = 327451; + tables[7][52] = 327450; + tables[7][53] = 0; + tables[7][54] = 327436; + tables[7][55] = 327438; + tables[7][56] = 327439; + tables[7][100] = 0; + tables[7][135] = 327653; + tables[8] = tables[2]; + tables[8][4] = 392993; + tables[8][5] = 392994; + tables[8][6] = 392995; + tables[8][7] = 392996; + tables[8][8] = 392997; + tables[8][9] = 392998; + tables[8][10] = 392999; + tables[8][11] = 393000; + tables[8][12] = 393001; + tables[8][13] = 393002; + tables[8][14] = 393003; + tables[8][15] = 393004; + tables[8][16] = 393005; + tables[8][17] = 393006; + tables[8][18] = 393007; + tables[8][19] = 393008; + tables[8][20] = 393009; + tables[8][21] = 393010; + tables[8][22] = 393011; + tables[8][23] = 393012; + tables[8][24] = 393013; + tables[8][25] = 393014; + tables[8][26] = 393015; + tables[8][27] = 393016; + tables[8][28] = 393017; + tables[8][29] = 393018; + tables[8][30] = 327425; + tables[8][31] = 270364; + tables[8][32] = 327427; + tables[8][33] = 327428; + tables[8][34] = 327429; + tables[8][35] = 327430; + tables[8][36] = 270360; + tables[8][37] = 327432; + tables[8][38] = 327433; + tables[8][39] = 0; + tables[8][45] = 327453; + tables[8][46] = 327518; + tables[8][47] = 327488; + tables[8][48] = 327515; + tables[8][49] = 327517; + tables[8][50] = 327517; + tables[8][51] = 327435; + tables[8][52] = 327434; + tables[8][53] = 0; + tables[8][54] = 327452; + tables[8][55] = 327454; + tables[8][56] = 327455; + tables[8][100] = 0; + tables[8][135] = 327487; + tables[8][137] = 327516; + tables[9] = tables[4]; + tables[9][46] = 262238; + tables[9][47] = 262208; + tables[9][48] = 262235; + tables[9][49] = 262237; + tables[9][50] = 262237; + tables[9][52] = 262202; + tables[9][53] = 0; + tables[9][100] = 0; + tables[9][135] = 262236; + tables[9][137] = 262309; + tables[10] = tables[5]; + tables[10][31] = 262178; + tables[10][35] = 262182; + tables[10][36] = 262183; + tables[10][37] = 262184; + tables[10][38] = 262185; + tables[10][39] = 0; + tables[10][45] = 262205; + tables[10][46] = 262270; + tables[10][47] = 262240; + tables[10][48] = 262267; + tables[10][49] = 262269; + tables[10][50] = 262269; + tables[10][51] = 262187; + tables[10][52] = 262186; + tables[10][53] = 0; + tables[10][100] = 0; + tables[10][135] = 262239; + tables[10][137] = 262268; + tables[11] = tables[9]; + tables[11][4] = 274625; + tables[11][5] = 274611; + tables[11][6] = 274621; + tables[11][7] = 274615; + tables[11][8] = 274596; + tables[11][9] = 274639; + tables[11][10] = 274605; + tables[11][11] = 274607; + tables[11][12] = 274635; + tables[11][13] = 274654; + tables[11][14] = 274638; + tables[11][15] = 274666; + tables[11][16] = 274658; + tables[11][17] = 274655; + tables[11][18] = 274665; + tables[11][19] = 274619; + tables[11][20] = 274623; + tables[11][21] = 274617; + tables[11][22] = 274632; + tables[11][23] = 274603; + tables[11][24] = 274634; + tables[11][25] = 274642; + tables[11][26] = 274630; + tables[11][27] = 274613; + tables[11][28] = 274675; + tables[11][29] = 274628; + tables[11][30] = 274636; + tables[11][31] = 274645; + tables[11][32] = 274594; + tables[11][33] = 274598; + tables[11][34] = 274600; + tables[11][35] = 274602; + tables[11][36] = 274660; + tables[11][37] = 274662; + tables[11][38] = 274664; + tables[11][39] = 274671; + tables[11][45] = 274651; + tables[11][46] = 274648; + tables[11][47] = 274587; + tables[11][48] = 274588; + tables[11][49] = 274656; + tables[11][50] = 274656; + tables[11][51] = 274668; + tables[11][52] = 274609; + tables[11][54] = 274637; + tables[11][55] = 274667; + tables[11][56] = 274657; + tables[11][135] = 274669; + tables[11][137] = 274684; + tables[12] = tables[11]; + tables[12][8] = 274595; + tables[12][29] = 274627; + tables[12][32] = 274593; + tables[12][33] = 274597; + tables[12][34] = 274599; + tables[12][35] = 274601; + tables[12][36] = 274659; + tables[12][37] = 274661; + tables[12][38] = 274663; + tables[12][39] = 274674; + tables[12][48] = 274444; + tables[12][49] = 274445; + tables[12][50] = 274445; + tables[12][54] = 274433; + tables[12][55] = 274434; + tables[12][56] = 274683; + tables[12][89] = 393265; + tables[12][90] = 393266; + tables[12][91] = 393267; + tables[12][92] = 393268; + tables[12][93] = 393269; + tables[12][94] = 393270; + tables[12][95] = 393271; + tables[12][96] = 393272; + tables[12][97] = 393273; + tables[12][98] = 393264; + tables[12][99] = 393262; + tables[13] = tables[9]; + tables[13][4] = 327553; + tables[13][5] = 327546; + tables[13][6] = 327551; + tables[13][7] = 327548; + tables[13][8] = 327538; + tables[13][9] = 327562; + tables[13][10] = 327543; + tables[13][11] = 327544; + tables[13][12] = 327558; + tables[13][13] = 327567; + tables[13][14] = 327561; + tables[13][15] = 327576; + tables[13][16] = 327571; + tables[13][17] = 327568; + tables[13][18] = 327575; + tables[13][19] = 327550; + tables[13][20] = 327552; + tables[13][21] = 327549; + tables[13][22] = 327556; + tables[13][23] = 327542; + tables[13][24] = 327557; + tables[13][25] = 327563; + tables[13][26] = 327555; + tables[13][27] = 327547; + tables[13][28] = 327581; + tables[13][29] = 327554; + tables[13][30] = 327559; + tables[13][31] = 327564; + tables[13][32] = 327537; + tables[13][33] = 327539; + tables[13][34] = 327540; + tables[13][35] = 327541; + tables[13][36] = 327572; + tables[13][37] = 327573; + tables[13][38] = 327574; + tables[13][39] = 327580; + tables[13][45] = 327566; + tables[13][46] = 327565; + tables[13][47] = 327582; + tables[13][48] = 327583; + tables[13][49] = 327569; + tables[13][50] = 327569; + tables[13][51] = 327578; + tables[13][52] = 327545; + tables[13][54] = 327560; + tables[13][55] = 327577; + tables[13][56] = 327570; + tables[13][135] = 327579; + tables[13][137] = 327536; + tables[14] = tables[13]; + tables[14][8] = 327528; + tables[14][29] = 327535; + tables[14][32] = 327527; + tables[14][33] = 327529; + tables[14][34] = 327530; + tables[14][35] = 327531; + tables[14][36] = 327532; + tables[14][37] = 327533; + tables[14][38] = 327534; + tables[14][39] = 327526; + tables[14][48] = 327522; + tables[14][49] = 327523; + tables[14][50] = 327523; + tables[14][54] = 327524; + tables[14][55] = 327521; + tables[14][56] = 327525; + tables[14][89] = 393265; + tables[14][90] = 393266; + tables[14][91] = 393267; + tables[14][92] = 393268; + tables[14][93] = 393269; + tables[14][94] = 393270; + tables[14][95] = 393271; + tables[14][96] = 393272; + tables[14][97] = 393273; + tables[14][98] = 393264; + tables[14][99] = 393262; + tables[15] = tables[11]; + tables[15][4] = 274529; + tables[15][5] = 274515; + tables[15][6] = 274525; + tables[15][7] = 274519; + tables[15][8] = 274500; + tables[15][9] = 274543; + tables[15][10] = 274509; + tables[15][11] = 274511; + tables[15][12] = 274539; + tables[15][13] = 274558; + tables[15][14] = 274542; + tables[15][15] = 274570; + tables[15][16] = 274562; + tables[15][17] = 274559; + tables[15][18] = 274569; + tables[15][19] = 274523; + tables[15][20] = 274527; + tables[15][21] = 274521; + tables[15][22] = 274536; + tables[15][23] = 274507; + tables[15][24] = 274538; + tables[15][25] = 274546; + tables[15][26] = 274534; + tables[15][27] = 274517; + tables[15][28] = 274579; + tables[15][29] = 274532; + tables[15][30] = 274540; + tables[15][31] = 274549; + tables[15][32] = 274498; + tables[15][33] = 274502; + tables[15][34] = 274504; + tables[15][35] = 274506; + tables[15][36] = 274564; + tables[15][37] = 274566; + tables[15][38] = 274568; + tables[15][39] = 274575; + tables[15][45] = 274555; + tables[15][46] = 274552; + tables[15][49] = 274560; + tables[15][50] = 274560; + tables[15][51] = 274572; + tables[15][52] = 274513; + tables[15][54] = 274541; + tables[15][55] = 274571; + tables[15][56] = 274561; + tables[15][135] = 274573; + tables[16] = tables[15]; + tables[16][8] = 274499; + tables[16][29] = 274531; + tables[16][32] = 274497; + tables[16][33] = 274501; + tables[16][34] = 274503; + tables[16][35] = 274505; + tables[16][36] = 274563; + tables[16][37] = 274565; + tables[16][38] = 274567; + tables[16][39] = 274578; + tables[16][48] = 274444; + tables[16][49] = 274445; + tables[16][50] = 274445; + tables[16][54] = 274433; + tables[16][55] = 274434; + tables[16][56] = 274683; + tables[16][89] = 393265; + tables[16][90] = 393266; + tables[16][91] = 393267; + tables[16][92] = 393268; + tables[16][93] = 393269; + tables[16][94] = 393270; + tables[16][95] = 393271; + tables[16][96] = 393272; + tables[16][97] = 393273; + tables[16][98] = 393264; + tables[16][99] = 393262; + tables[17] = tables[1]; + tables[17][30] = 327729; + tables[17][31] = 327730; + tables[17][32] = 327731; + tables[17][33] = 327732; + tables[17][34] = 327733; + tables[17][35] = 327734; + tables[17][36] = 327735; + tables[17][37] = 327736; + tables[17][38] = 327737; + tables[17][39] = 327728; + tables[17][45] = 327903; + tables[17][46] = 262324; + tables[17][47] = 327932; + tables[17][48] = 327723; + tables[17][49] = 327715; + tables[17][50] = 327715; + tables[17][51] = 327926; + tables[17][52] = 327908; + tables[17][53] = 262238; + tables[17][54] = 327724; + tables[17][55] = 327726; + tables[17][136] = 262238; + tables[17][137] = 262238; + tables[17][138] = 262238; + tables[17][146] = 262238; + tables[17][147] = 262238; + tables[18] = tables[2]; + tables[18][30] = 327713; + tables[18][31] = 327714; + tables[18][32] = 327847; + tables[18][33] = 327716; + tables[18][34] = 327717; + tables[18][35] = 327718; + tables[18][36] = 327727; + tables[18][37] = 327720; + tables[18][38] = 327721; + tables[18][39] = 327741; + tables[18][45] = 327743; + tables[18][47] = 327900; + tables[18][48] = 327722; + tables[18][49] = 327719; + tables[18][50] = 327719; + tables[18][51] = 327894; + tables[18][52] = 327876; + tables[18][54] = 327739; + tables[18][55] = 327738; + tables[19][8] = 336044; + tables[19][16] = 327861; + tables[19][20] = 327744; + tables[19][31] = 327858; + tables[19][32] = 327859; + tables[19][36] = 327803; + tables[19][37] = 327771; + tables[19][38] = 327773; + tables[19][39] = 327805; + tables[19][45] = 327772; + tables[19][48] = 327806; + tables[19][100] = 262268; + tables[20] = tables[1]; + tables[20][28] = 327801; + tables[20][29] = 327802; + tables[20][46] = 262305; + tables[20][47] = 262240; + tables[20][48] = 262187; + tables[20][49] = 327911; + tables[20][50] = 327911; + tables[20][51] = 327921; + tables[20][52] = 262324; + tables[20][53] = 262330; + tables[21] = tables[2]; + tables[21][28] = 327769; + tables[21][29] = 327770; + tables[21][30] = 262177; + tables[21][32] = 262327; + tables[21][33] = 262180; + tables[21][46] = 262335; + tables[21][47] = 262238; + tables[21][48] = 262186; + tables[21][49] = 327879; + tables[21][50] = 327879; + tables[21][51] = 327889; + tables[21][52] = 262312; + tables[21][53] = 262314; + tables[22] = tables[3]; + tables[22][30] = 262268; + tables[22][33] = 262270; + tables[22][34] = 262272; + tables[22][36] = 0; + tables[22][37] = 0; + tables[22][45] = 0; + tables[22][46] = 0; + tables[22][49] = 327805; + tables[22][50] = 327805; + tables[22][53] = 262236; + tables[22][100] = 0; + tables[22][136] = 262270; + tables[22][137] = 262270; + tables[22][138] = 262270; + tables[22][146] = 262270; + tables[22][147] = 262270; + tables[23] = tables[17]; + tables[23][4] = 327793; + tables[23][16] = 327724; + tables[23][20] = 327777; + tables[23][26] = 327802; + tables[23][28] = 327801; + tables[23][29] = 327799; + tables[23][30] = 327718; + tables[23][31] = 327913; + tables[23][32] = 327714; + tables[23][33] = 327719; + tables[23][34] = 327720; + tables[23][35] = 327725; + tables[23][36] = 327912; + tables[23][37] = 327775; + tables[23][38] = 327911; + tables[23][39] = 327904; + tables[23][45] = 327721; + tables[23][46] = 327741; + tables[23][47] = 327774; + tables[23][48] = 327716; + tables[23][49] = 327722; + tables[23][50] = 327722; + tables[23][51] = 327789; + tables[23][52] = 327929; + tables[23][53] = 262322; + tables[23][54] = 327739; + tables[23][55] = 327738; + tables[23][56] = 327713; + tables[24] = tables[21]; + tables[24][4] = 327761; + tables[24][16] = 327743; + tables[24][20] = 327745; + tables[24][26] = 327770; + tables[24][29] = 327767; + tables[24][30] = 327729; + tables[24][31] = 327730; + tables[24][32] = 327731; + tables[24][33] = 327732; + tables[24][34] = 327733; + tables[24][35] = 327734; + tables[24][36] = 327735; + tables[24][37] = 327736; + tables[24][38] = 327737; + tables[24][39] = 327728; + tables[24][45] = 327856; + tables[24][46] = 327723; + tables[24][47] = 327848; + tables[24][48] = 327843; + tables[24][49] = 327861; + tables[24][50] = 327861; + tables[24][51] = 327757; + tables[24][52] = 327717; + tables[24][53] = 0; + tables[24][54] = 327726; + tables[24][55] = 327727; + tables[24][56] = 327847; + tables[24][136] = 262312; + tables[24][137] = 262312; + tables[24][138] = 262312; + tables[24][146] = 262312; + tables[24][147] = 262312; + tables[25][8] = 336044; + tables[25][31] = 262270; + tables[25][32] = 327715; + tables[25][33] = 327803; + tables[25][34] = 327771; + tables[25][35] = 327804; + tables[25][36] = 262240; + tables[25][37] = 327772; + tables[25][38] = 327774; + tables[25][39] = 327744; + tables[25][45] = 327773; + tables[25][46] = 327805; + tables[25][48] = 327844; + tables[25][136] = 262270; + tables[25][137] = 262270; + tables[25][138] = 262270; + tables[25][146] = 262270; + tables[25][147] = 262270; + tables[26] = tables[20]; + tables[26][46] = 262380; + tables[26][47] = 262376; + tables[26][49] = 262393; + tables[26][50] = 262393; + tables[26][51] = 262386; + tables[26][52] = 262368; + tables[26][53] = 262236; + tables[27] = tables[21]; + tables[27][32] = 262307; + tables[27][46] = 262238; + tables[27][47] = 262377; + tables[27][49] = 262311; + tables[27][50] = 262311; + tables[27][51] = 262375; + tables[27][52] = 262320; + tables[27][53] = 262268; + tables[28] = tables[6]; + tables[28][8] = 336044; + tables[28][47] = 262235; + tables[28][48] = 262237; + tables[28][51] = 262208; + tables[28][52] = 262179; + tables[29][47] = 262267; + tables[29][48] = 262269; + tables[30] = tables[20]; + tables[30][45] = 262191; + tables[30][46] = 262320; + tables[30][47] = 262312; + tables[30][48] = 262186; + tables[30][49] = 262204; + tables[30][50] = 262204; + tables[30][51] = 262187; + tables[30][53] = 262208; + tables[30][100] = 262237; + tables[31] = tables[21]; + tables[31][32] = 262179; + tables[31][36] = 262239; + tables[31][39] = 262183; + tables[31][46] = 262270; + tables[31][48] = 262268; + tables[31][49] = 262206; + tables[31][50] = 262206; + tables[31][51] = 262321; + tables[31][52] = 262240; + tables[31][53] = 262311; + tables[31][56] = 262205; + tables[31][100] = 262235; + tables[31][136] = 262270; + tables[31][137] = 262270; + tables[31][138] = 262270; + tables[31][146] = 262270; + tables[31][147] = 262270; + tables[32][6] = 327842; + tables[32][8] = 336044; + tables[32][16] = 327861; + tables[32][21] = 327862; + tables[32][22] = 327903; + tables[32][27] = 327867; + tables[32][29] = 327851; + tables[32][30] = 262329; + tables[32][31] = 262322; + tables[32][32] = 262323; + tables[32][33] = 262332; + tables[32][34] = 262333; + tables[32][35] = 262334; + tables[32][36] = 262307; + tables[32][37] = 262267; + tables[32][38] = 262269; + tables[32][45] = 262236; + tables[32][46] = 262328; + tables[32][53] = 262316; + tables[32][55] = 262327; + tables[32][100] = 262310; + tables[32][136] = 262328; + tables[32][137] = 262328; + tables[32][138] = 262328; + tables[32][146] = 262328; + tables[32][147] = 262328; + tables[33] = tables[26]; + tables[33][46] = 262315; + tables[33][47] = 262187; + tables[33][48] = 262324; + tables[33][49] = 262270; + tables[33][50] = 262270; + tables[33][51] = 327911; + tables[33][52] = 262330; + tables[34] = tables[27]; + tables[34][32] = 262179; + tables[34][46] = 262331; + tables[34][47] = 262186; + tables[34][48] = 262240; + tables[34][49] = 262238; + tables[34][50] = 262238; + tables[34][51] = 327879; + tables[34][52] = 262314; + tables[35] = tables[6]; + tables[35][8] = 336044; + tables[35][31] = 262208; + tables[35][32] = 262307; + tables[35][33] = 262311; + tables[35][36] = 262267; + tables[35][37] = 262235; + tables[35][38] = 262237; + tables[35][39] = 262269; + tables[35][47] = 262312; + tables[35][48] = 262237; + tables[35][136] = 262312; + tables[35][137] = 262312; + tables[35][138] = 262312; + tables[35][146] = 262312; + tables[35][147] = 262312; + tables[36] = tables[4]; + tables[36][4] = 328772; + tables[36][5] = 328760; + tables[36][6] = 328769; + tables[36][7] = 328754; + tables[36][8] = 328771; + tables[36][9] = 328752; + tables[36][10] = 328767; + tables[36][11] = 328768; + tables[36][12] = 328776; + tables[36][13] = 328766; + tables[36][14] = 328763; + tables[36][15] = 328756; + tables[36][16] = 328780; + tables[36][17] = 328770; + tables[36][18] = 328777; + tables[36][19] = 328759; + tables[36][20] = 328761; + tables[36][21] = 328762; + tables[36][22] = 328779; + tables[36][23] = 328757; + tables[36][24] = 328755; + tables[36][25] = 328764; + tables[36][26] = 328774; + tables[36][27] = 328775; + tables[36][28] = 328765; + tables[36][29] = 328783; + tables[36][47] = 328773; + tables[36][48] = 328778; + tables[36][51] = 328758; + tables[36][52] = 328781; + tables[36][53] = 328785; + tables[36][54] = 328753; + tables[36][55] = 328782; + tables[36][56] = 262190; + tables[37] = tables[5]; + tables[37][4] = 328740; + tables[37][5] = 328728; + tables[37][6] = 328737; + tables[37][7] = 328722; + tables[37][8] = 328739; + tables[37][9] = 328720; + tables[37][10] = 328735; + tables[37][11] = 328736; + tables[37][12] = 328744; + tables[37][13] = 328734; + tables[37][14] = 328731; + tables[37][15] = 328724; + tables[37][16] = 328748; + tables[37][17] = 328738; + tables[37][18] = 328745; + tables[37][19] = 328727; + tables[37][20] = 328729; + tables[37][21] = 328730; + tables[37][22] = 328747; + tables[37][23] = 328725; + tables[37][24] = 328723; + tables[37][25] = 328732; + tables[37][26] = 328742; + tables[37][27] = 328743; + tables[37][28] = 328733; + tables[37][29] = 328751; + tables[37][31] = 262178; + tables[37][32] = 270614; + tables[37][33] = 262203; + tables[37][35] = 262202; + tables[37][36] = 262207; + tables[37][47] = 328741; + tables[37][48] = 328746; + tables[37][49] = 262191; + tables[37][50] = 262191; + tables[37][51] = 328726; + tables[37][52] = 328749; + tables[37][53] = 328705; + tables[37][54] = 328721; + tables[37][55] = 328750; + tables[37][56] = 262188; + tables[37][100] = 262191; + tables[38] = tables[4]; + tables[38][49] = 262179; + tables[38][50] = 262179; + tables[39] = tables[5]; + tables[39][31] = 262178; + tables[39][32] = 262307; + tables[39][49] = 262270; + tables[39][50] = 262270; + tables[39][52] = 262208; + tables[39][53] = 262316; + tables[40][4] = 327905; + tables[40][8] = 327913; + tables[40][12] = 327917; + tables[40][18] = 327923; + tables[40][24] = 327930; + tables[40][33] = 270508; + tables[40][53] = 262310; + tables[41][4] = 327873; + tables[41][8] = 327881; + tables[41][12] = 327885; + tables[41][18] = 327891; + tables[41][24] = 327898; + tables[42] = tables[4]; + tables[42][4] = 332038; + tables[42][5] = 332146; + tables[42][6] = 332046; + tables[42][7] = 332043; + tables[42][8] = 332035; + tables[42][9] = 332037; + tables[42][10] = 332050; + tables[42][11] = 332137; + tables[42][12] = 332131; + tables[42][13] = 332133; + tables[42][14] = 332129; + tables[42][15] = 332149; + tables[42][16] = 332147; + tables[42][17] = 332142; + tables[42][18] = 332130; + tables[42][19] = 332134; + tables[42][20] = 332039; + tables[42][21] = 332032; + tables[42][22] = 332034; + tables[42][23] = 332041; + tables[42][24] = 332135; + tables[42][25] = 332049; + tables[42][26] = 332044; + tables[42][27] = 332048; + tables[42][28] = 332141; + tables[42][29] = 332047; + tables[42][50] = 262237; + tables[42][52] = 270360; + tables[43] = tables[5]; + tables[43][4] = 332038; + tables[43][5] = 332146; + tables[43][6] = 332046; + tables[43][7] = 332043; + tables[43][8] = 332036; + tables[43][9] = 332037; + tables[43][10] = 332050; + tables[43][11] = 332137; + tables[43][12] = 332131; + tables[43][13] = 332133; + tables[43][14] = 332129; + tables[43][15] = 332149; + tables[43][16] = 332147; + tables[43][17] = 332142; + tables[43][18] = 332132; + tables[43][19] = 332136; + tables[43][20] = 332040; + tables[43][21] = 332033; + tables[43][22] = 332034; + tables[43][23] = 332042; + tables[43][24] = 332135; + tables[43][25] = 332049; + tables[43][26] = 332045; + tables[43][27] = 332048; + tables[43][28] = 332141; + tables[43][29] = 332047; + tables[43][50] = 262269; + tables[43][52] = 270364; + tables[44] = tables[20]; + tables[44][45] = 262187; + tables[44][46] = 262236; + tables[44][47] = 327909; + tables[44][48] = 262312; + tables[44][49] = 262183; + tables[44][50] = 262183; + tables[44][51] = 327928; + tables[44][52] = 327910; + tables[44][53] = 262268; + tables[45] = tables[34]; + tables[45][33] = 262308; + tables[45][46] = 262240; + tables[45][47] = 327877; + tables[45][48] = 262238; + tables[45][49] = 262186; + tables[45][50] = 262186; + tables[45][51] = 327896; + tables[45][52] = 327878; + tables[45][53] = 262311; + tables[45][136] = 262240; + tables[45][137] = 262240; + tables[45][138] = 262240; + tables[45][146] = 262240; + tables[45][147] = 262240; + tables[46] = tables[35]; + tables[46][16] = 327861; + tables[46][33] = 262180; + tables[46][46] = 262324; + tables[46][47] = 0; + tables[46][48] = 262270; + tables[46][136] = 262324; + tables[46][137] = 262324; + tables[46][138] = 262324; + tables[46][146] = 262324; + tables[46][147] = 262324; + tables[47] = tables[44]; + tables[47][46] = 262324; + tables[47][51] = 327926; + tables[47][52] = 327908; + tables[47][53] = 262311; + tables[47][136] = 262312; + tables[47][137] = 262312; + tables[47][138] = 262312; + tables[47][146] = 262312; + tables[47][147] = 262312; + tables[48] = tables[45]; + tables[48][51] = 327894; + tables[48][52] = 327876; + tables[48][53] = 262333; + tables[48][136] = 262238; + tables[48][137] = 262238; + tables[48][138] = 262238; + tables[48][146] = 262238; + tables[48][147] = 262238; + tables[49] = tables[46]; + tables[49][45] = 262236; + tables[49][46] = 0; + tables[49][100] = 262268; + tables[49][136] = 262270; + tables[49][137] = 262270; + tables[49][138] = 262270; + tables[49][146] = 262270; + tables[49][147] = 262270; + tables[50] = tables[47]; + tables[50][51] = 327910; + tables[50][52] = 327928; + tables[50][53] = 262333; + tables[50][136] = 262324; + tables[50][137] = 262324; + tables[50][138] = 262324; + tables[50][146] = 262324; + tables[50][147] = 262324; + tables[51] = tables[45]; + tables[51][51] = 327878; + tables[51][52] = 327896; + tables[52] = tables[46]; + tables[52][34] = 262272; + tables[52][46] = 262268; + tables[52][100] = 262236; + tables[52][136] = 0; + tables[52][137] = 0; + tables[52][138] = 0; + tables[52][146] = 0; + tables[52][147] = 0; + tables[53] = tables[38]; + tables[53][4] = 340231; + tables[53][5] = 340246; + tables[53][6] = 340239; + tables[53][7] = 340238; + tables[53][8] = 340237; + tables[53][9] = 340241; + tables[53][10] = 340245; + tables[53][11] = 340248; + tables[53][12] = 340251; + tables[53][13] = 340264; + tables[53][14] = 340252; + tables[53][15] = 340256; + tables[53][16] = 340265; + tables[53][17] = 340249; + tables[53][18] = 340255; + tables[53][19] = 340259; + tables[53][20] = 340230; + tables[53][21] = 340240; + tables[53][22] = 340235; + tables[53][23] = 340244; + tables[53][24] = 340263; + tables[53][25] = 340242; + tables[53][26] = 340234; + tables[53][27] = 340236; + tables[53][28] = 340247; + tables[53][29] = 340232; + tables[53][30] = 274693; + tables[53][31] = 274697; + tables[53][32] = 262855; + tables[53][33] = 262859; + tables[53][34] = 274707; + tables[53][35] = 262858; + tables[53][36] = 262873; + tables[53][37] = 274714; + tables[53][38] = 274718; + tables[53][39] = 274722; + tables[53][45] = 274726; + tables[53][51] = 274724; + tables[53][52] = 270360; + tables[53][54] = 274717; + tables[53][55] = 274721; + tables[53][56] = 274725; + tables[54] = tables[43]; + tables[54][4] = 327777; + tables[54][5] = 327778; + tables[54][6] = 327779; + tables[54][7] = 327780; + tables[54][8] = 327781; + tables[54][9] = 327782; + tables[54][10] = 327783; + tables[54][11] = 327784; + tables[54][12] = 327785; + tables[54][13] = 327786; + tables[54][14] = 327787; + tables[54][15] = 327788; + tables[54][16] = 327789; + tables[54][17] = 327790; + tables[54][18] = 327791; + tables[54][19] = 327792; + tables[54][20] = 327793; + tables[54][21] = 327794; + tables[54][22] = 327795; + tables[54][23] = 327796; + tables[54][24] = 327797; + tables[54][25] = 327798; + tables[54][26] = 327799; + tables[54][27] = 327800; + tables[54][28] = 327801; + tables[54][29] = 327802; + tables[54][50] = 262179; + tables[54][100] = 262236; + tables[55] = tables[5]; + tables[55][52] = 270364; + tables[56] = tables[7]; + tables[56][46] = 327453; + tables[56][47] = 327483; + tables[56][48] = 327485; + tables[56][49] = 327484; + tables[56][50] = 327484; + tables[56][52] = 270360; + tables[56][53] = 270360; + tables[56][100] = 327653; + tables[56][135] = 0; + tables[57] = tables[8]; + tables[57][31] = 327456; + tables[57][35] = 327486; + tables[57][36] = 327430; + tables[57][37] = 327434; + tables[57][38] = 327432; + tables[57][39] = 327433; + tables[57][45] = 327487; + tables[57][46] = 327435; + tables[57][47] = 327515; + tables[57][48] = 327517; + tables[57][49] = 327516; + tables[57][50] = 327516; + tables[57][51] = 327450; + tables[57][52] = 270364; + tables[57][53] = 327518; + tables[57][100] = 327516; + tables[57][135] = 0; + tables[57][137] = 0; + tables[58] = tables[42]; + tables[58][4] = 353765; + tables[58][5] = 354056; + tables[58][6] = 365009; + tables[58][7] = 354088; + tables[58][8] = 355380; + tables[58][9] = 356459; + tables[58][10] = 349983; + tables[58][11] = 359161; + tables[58][12] = 352776; + tables[58][13] = 348993; + tables[58][14] = 350503; + tables[58][15] = 347693; + tables[58][16] = 347648; + tables[58][17] = 352019; + tables[58][18] = 347834; + tables[58][19] = 352195; + tables[58][20] = 352843; + tables[58][21] = 349155; + tables[58][22] = 351288; + tables[58][23] = 351999; + tables[58][24] = 351345; + tables[58][25] = 350579; + tables[58][26] = 357680; + tables[58][27] = 366307; + tables[58][28] = 349020; + tables[58][29] = 365005; + tables[58][50] = 262179; + tables[59] = tables[1]; + tables[59][47] = 262376; + tables[59][51] = 262377; + tables[59][52] = 262368; + tables[60] = tables[2]; + tables[60][47] = 262396; + tables[60][51] = 262390; + tables[60][52] = 262372; + tables[61] = tables[4]; + tables[61][47] = 262238; + tables[61][48] = 262328; + tables[61][49] = 262204; + tables[61][50] = 262204; + tables[61][52] = 262240; + tables[61][53] = 262179; + tables[61][56] = 327913; + tables[61][100] = 262315; + tables[61][136] = 262240; + tables[61][137] = 262240; + tables[61][138] = 262240; + tables[61][146] = 262240; + tables[61][147] = 262240; + tables[62] = tables[39]; + tables[62][32] = 262191; + tables[62][35] = 262207; + tables[62][47] = 262238; + tables[62][48] = 262312; + tables[62][49] = 262206; + tables[62][50] = 262206; + tables[62][52] = 262240; + tables[62][53] = 262268; + tables[62][54] = 262183; + tables[62][55] = 262190; + tables[62][56] = 327881; + tables[62][100] = 262331; + tables[62][136] = 262240; + tables[62][137] = 262240; + tables[62][138] = 262240; + tables[62][146] = 262240; + tables[62][147] = 262240; + tables[63] = tables[52]; + tables[63][8] = 0; + tables[63][18] = 327847; + tables[63][19] = 327862; + tables[63][30] = 262321; + tables[63][33] = 262306; + tables[63][34] = 262308; + tables[63][35] = 262316; + tables[63][36] = 262310; + tables[63][37] = 262322; + tables[63][38] = 262323; + tables[63][39] = 262332; + tables[63][45] = 262333; + tables[63][46] = 262334; + tables[63][47] = 262235; + tables[63][48] = 262237; + tables[63][49] = 262269; + tables[63][50] = 262269; + tables[63][51] = 262270; + tables[63][52] = 262267; + tables[63][53] = 262236; + tables[63][54] = 262319; + tables[63][55] = 262317; + tables[63][56] = 262324; + tables[63][100] = 262320; + tables[64] = tables[23]; + tables[64][35] = 327847; + tables[64][37] = 327713; + tables[64][46] = 327725; + tables[64][49] = 327861; + tables[64][50] = 327861; + tables[64][56] = 327741; + tables[65] = tables[24]; + tables[65][46] = 327775; + tables[65][48] = 327722; + tables[65][49] = 327843; + tables[65][50] = 327843; + tables[65][53] = 262323; + tables[65][56] = 327723; + tables[66][8] = 336044; + tables[66][30] = 327804; + tables[66][31] = 327744; + tables[66][32] = 327715; + tables[66][33] = 327803; + tables[66][34] = 327771; + tables[66][35] = 327774; + tables[66][38] = 327803; + tables[66][39] = 327805; + tables[66][47] = 327771; + tables[66][48] = 327773; + tables[66][49] = 262240; + tables[66][50] = 262240; + tables[66][52] = 262324; + tables[66][56] = 262270; + tables[66][100] = 262236; + tables[67][49] = 262240; + tables[67][50] = 262240; + tables[67][52] = 262324; + tables[67][56] = 262270; + tables[67][136] = 262240; + tables[67][137] = 262240; + tables[67][138] = 262240; + tables[67][146] = 262240; + tables[67][147] = 262240; + tables[68] = tables[4]; + tables[68][49] = 262179; + tables[68][100] = 0; + tables[69] = tables[5]; + tables[69][49] = 262270; + tables[69][100] = 0; + tables[70][4] = 327941; + tables[70][6] = 327943; + tables[70][8] = 327961; + tables[70][15] = 328002; + tables[70][17] = 328004; + tables[70][18] = 327923; + tables[70][22] = 328027; + tables[70][24] = 270508; + tables[70][27] = 328058; + tables[70][29] = 328060; + tables[71][4] = 327940; + tables[71][6] = 327942; + tables[71][8] = 327960; + tables[71][15] = 328001; + tables[71][17] = 328003; + tables[71][18] = 327891; + tables[71][22] = 328026; + tables[71][27] = 328057; + tables[71][29] = 328059; + tables[72] = tables[4]; + tables[72][47] = 262324; + tables[72][48] = 262235; + tables[72][49] = 0; + tables[72][50] = 262237; + tables[72][51] = 327911; + tables[72][52] = 262270; + tables[72][53] = 262183; + tables[72][56] = 262203; + tables[72][135] = 262191; + tables[73] = tables[5]; + tables[73][35] = 262312; + tables[73][47] = 262240; + tables[73][48] = 262267; + tables[73][49] = 0; + tables[73][50] = 262269; + tables[73][51] = 327879; + tables[73][52] = 262238; + tables[73][53] = 262178; + tables[73][56] = 262202; + tables[73][135] = 262207; + tables[74][8] = 262320; + tables[74][20] = 262191; + tables[74][26] = 262207; + tables[74][30] = 262329; + tables[74][31] = 262322; + tables[74][32] = 262323; + tables[74][33] = 262307; + tables[74][34] = 262306; + tables[74][35] = 262316; + tables[74][46] = 262311; + tables[74][48] = 262314; + tables[74][50] = 262330; + tables[74][135] = 262320; + tables[75] = tables[20]; + tables[75][12] = 327985; + tables[75][45] = 262186; + tables[75][46] = 262189; + tables[75][47] = 327967; + tables[75][48] = 327932; + tables[75][49] = 0; + tables[75][50] = 262188; + tables[75][51] = 328031; + tables[75][52] = 327785; + tables[75][53] = 262178; + tables[75][54] = 327926; + tables[75][55] = 327911; + tables[75][56] = 262190; + tables[76] = tables[21]; + tables[76][31] = 262183; + tables[76][32] = 262238; + tables[76][33] = 262187; + tables[76][46] = 262239; + tables[76][47] = 327966; + tables[76][48] = 262364; + tables[76][49] = 0; + tables[76][50] = 262203; + tables[76][51] = 328030; + tables[76][52] = 327984; + tables[76][53] = 262377; + tables[76][54] = 327894; + tables[76][55] = 327879; + tables[76][56] = 262202; + tables[77] = tables[52]; + tables[77][4] = 327910; + tables[77][8] = 270508; + tables[77][12] = 327785; + tables[77][16] = 0; + tables[77][20] = 262208; + tables[77][22] = 262367; + tables[77][30] = 262206; + tables[77][31] = 262307; + tables[77][32] = 262179; + tables[77][34] = 262333; + tables[77][45] = 262236; + tables[77][47] = 262312; + tables[77][50] = 262240; + tables[77][51] = 262324; + tables[77][53] = 262204; + tables[77][100] = 262268; + tables[78][4] = 327878; + tables[78][12] = 327984; + tables[79] = tables[20]; + tables[79][41] = 262268; + tables[79][46] = 262335; + tables[79][47] = 262324; + tables[79][50] = 327805; + tables[79][52] = 262267; + tables[79][53] = 262268; + tables[80] = tables[21]; + tables[80][32] = 262179; + tables[80][46] = 262305; + tables[80][47] = 262312; + tables[80][50] = 327773; + tables[80][52] = 262235; + tables[80][53] = 262320; + tables[81][20] = 262208; + tables[81][45] = 262236; + tables[81][48] = 262270; + tables[81][50] = 262240; + tables[81][52] = 262238; + tables[81][53] = 262316; + tables[82] = tables[4]; + tables[82][4] = 263732; + tables[82][5] = 263748; + tables[82][6] = 263716; + tables[82][7] = 263754; + tables[82][8] = 263723; + tables[82][9] = 263720; + tables[82][10] = 263748; + tables[82][11] = 263719; + tables[82][12] = 263751; + tables[82][13] = 263722; + tables[82][14] = 263750; + tables[82][15] = 263749; + tables[82][16] = 263721; + tables[82][17] = 263753; + tables[82][18] = 263726; + tables[82][19] = 263725; + tables[82][20] = 263734; + tables[82][21] = 263746; + tables[82][22] = 263731; + tables[82][23] = 263745; + tables[82][24] = 263737; + tables[82][25] = 263729; + tables[82][26] = 263733; + tables[82][27] = 263713; + tables[82][28] = 263738; + tables[82][29] = 263718; + tables[82][47] = 263724; + tables[82][48] = 263727; + tables[82][51] = 263747; + tables[82][52] = 263735; + tables[82][53] = 263728; + tables[82][54] = 263752; + tables[82][55] = 263730; + tables[82][56] = 263736; + tables[83] = tables[5]; + tables[83][4] = 263760; + tables[83][5] = 263748; + tables[83][6] = 262269; + tables[83][7] = 262237; + tables[83][8] = 263759; + tables[83][9] = 262235; + tables[83][10] = 263748; + tables[83][11] = 263715; + tables[83][12] = 262391; + tables[83][13] = 263744; + tables[83][14] = 263692; + tables[83][15] = 262191; + tables[83][16] = 270361; + tables[83][17] = 263714; + tables[83][18] = 262359; + tables[83][19] = 263707; + tables[83][20] = 263758; + tables[83][21] = 263756; + tables[83][22] = 263757; + tables[83][23] = 263748; + tables[83][24] = 270360; + tables[83][25] = 262267; + tables[83][26] = 263755; + tables[83][27] = 263762; + tables[83][28] = 263717; + tables[83][29] = 262270; + tables[83][38] = 262185; + tables[83][39] = 262184; + tables[83][47] = 262204; + tables[83][48] = 262206; + tables[83][53] = 263761; + tables[83][54] = 262188; + tables[83][55] = 262190; + tables[83][56] = 263711; + tables[84] = tables[1]; + tables[84][4] = 331295; + tables[84][5] = 331316; + tables[84][6] = 331329; + tables[84][7] = 331265; + tables[84][8] = 331315; + tables[84][9] = 331284; + tables[84][10] = 331328; + tables[84][11] = 331337; + tables[84][12] = 331299; + tables[84][13] = 331336; + tables[84][14] = 331314; + tables[84][15] = 331306; + tables[84][16] = 331287; + tables[84][17] = 331319; + tables[84][18] = 331289; + tables[84][19] = 331298; + tables[84][20] = 331334; + tables[84][21] = 331294; + tables[84][22] = 331307; + tables[84][23] = 331312; + tables[84][24] = 331317; + tables[84][25] = 331309; + tables[84][26] = 331332; + tables[84][27] = 331291; + tables[84][28] = 331313; + tables[84][29] = 331292; + tables[84][30] = 331333; + tables[84][31] = 327727; + tables[84][32] = 327725; + tables[84][33] = 331296; + tables[84][34] = 331286; + tables[84][35] = 331320; + tables[84][36] = 331318; + tables[84][37] = 331268; + tables[84][38] = 331285; + tables[84][39] = 331272; + tables[84][45] = 331266; + tables[84][46] = 331274; + tables[84][47] = 331290; + tables[84][48] = 331301; + tables[84][49] = 331267; + tables[84][50] = 331267; + tables[84][51] = 331303; + tables[84][52] = 331271; + tables[84][53] = 327775; + tables[84][54] = 331297; + tables[84][55] = 331331; + tables[84][56] = 331293; + tables[84][100] = 331267; + tables[84][103] = 0; + tables[84][133] = 0; + tables[84][134] = 0; + tables[84][140] = 0; + tables[85] = tables[2]; + tables[85][4] = 331300; + tables[85][5] = 331322; + tables[85][6] = 331273; + tables[85][7] = 331279; + tables[85][8] = 331278; + tables[85][9] = 331330; + tables[85][10] = 331276; + tables[85][11] = 331335; + tables[85][12] = 331283; + tables[85][13] = 331339; + tables[85][14] = 331305; + tables[85][15] = 331304; + tables[85][16] = 327743; + tables[85][17] = 331340; + tables[85][18] = 331311; + tables[85][19] = 331277; + tables[85][20] = 331344; + tables[85][21] = 331281; + tables[85][22] = 331270; + tables[85][23] = 331288; + tables[85][24] = 331338; + tables[85][25] = 331310; + tables[85][26] = 327714; + tables[85][27] = 327721; + tables[85][28] = 331341; + tables[85][29] = 327720; + tables[85][30] = 327723; + tables[85][31] = 331345; + tables[85][32] = 331346; + tables[85][33] = 331347; + tables[85][34] = 331348; + tables[85][35] = 331321; + tables[85][36] = 331327; + tables[85][37] = 331349; + tables[85][38] = 331350; + tables[85][39] = 331351; + tables[85][45] = 331352; + tables[85][46] = 331353; + tables[85][47] = 331280; + tables[85][48] = 327724; + tables[85][49] = 331269; + tables[85][50] = 331269; + tables[85][51] = 331275; + tables[85][52] = 327726; + tables[85][53] = 327717; + tables[85][54] = 331282; + tables[85][55] = 331308; + tables[85][56] = 331302; + tables[85][100] = 331269; + tables[85][103] = 0; + tables[85][133] = 0; + tables[85][134] = 0; + tables[85][140] = 0; + tables[86] = tables[1]; + tables[86][30] = 262187; + tables[86][31] = 327963; + tables[86][32] = 328033; + tables[86][33] = 327949; + tables[86][34] = 328025; + tables[86][35] = 328062; + tables[86][36] = 327933; + tables[86][37] = 327905; + tables[86][38] = 327917; + tables[86][39] = 327913; + tables[86][45] = 262205; + tables[86][46] = 262324; + tables[86][47] = 327930; + tables[86][48] = 262185; + tables[86][49] = 262312; + tables[86][50] = 262312; + tables[86][51] = 328047; + tables[86][52] = 262311; + tables[86][53] = 262203; + tables[86][100] = 262236; + tables[86][103] = 0; + tables[86][133] = 0; + tables[86][134] = 0; + tables[86][140] = 0; + tables[87] = tables[2]; + tables[87][30] = 262193; + tables[87][31] = 262194; + tables[87][32] = 262195; + tables[87][33] = 262196; + tables[87][34] = 262197; + tables[87][35] = 262198; + tables[87][36] = 262199; + tables[87][37] = 262200; + tables[87][38] = 262201; + tables[87][39] = 262192; + tables[87][45] = 262181; + tables[87][46] = 262855; + tables[87][47] = 262191; + tables[87][48] = 262184; + tables[87][49] = 262183; + tables[87][50] = 262183; + tables[87][51] = 262178; + tables[87][52] = 262177; + tables[87][54] = 262207; + tables[87][100] = 262268; + tables[87][103] = 0; + tables[87][133] = 0; + tables[87][134] = 0; + tables[87][140] = 0; + tables[88][5] = 262267; + tables[88][6] = 262182; + tables[88][7] = 262416; + tables[88][8] = 270508; + tables[88][9] = 262235; + tables[88][10] = 262237; + tables[88][14] = 262466; + tables[88][15] = 262465; + tables[88][17] = 262269; + tables[88][20] = 262236; + tables[88][22] = 262417; + tables[88][25] = 262208; + tables[88][26] = 262268; + tables[88][27] = 262179; + tables[88][30] = 262270; + tables[88][31] = 262855; + tables[88][32] = 262238; + tables[88][33] = 262872; + tables[88][34] = 262320; + tables[88][35] = 262875; + tables[88][36] = 262240; + tables[88][37] = 262873; + tables[88][38] = 262324; + tables[88][39] = 262877; + tables[88][45] = 262312; + tables[88][46] = 262328; + tables[88][47] = 262391; + tables[88][48] = 262359; + tables[88][49] = 262308; + tables[88][50] = 262308; + tables[88][51] = 262180; + tables[88][52] = 262367; + tables[88][54] = 262204; + tables[88][55] = 262206; + tables[88][56] = 262186; + tables[89] = tables[4]; + tables[89][4] = 328625; + tables[89][5] = 328626; + tables[89][6] = 328648; + tables[89][7] = 328628; + tables[89][8] = 328629; + tables[89][9] = 328646; + tables[89][10] = 328627; + tables[89][11] = 328631; + tables[89][12] = 328633; + tables[89][13] = 328638; + tables[89][14] = 328634; + tables[89][15] = 328635; + tables[89][16] = 328636; + tables[89][17] = 328637; + tables[89][18] = 328639; + tables[89][19] = 328640; + tables[89][20] = 262203; + tables[89][21] = 328641; + tables[89][22] = 328643; + tables[89][23] = 328644; + tables[89][24] = 328632; + tables[89][25] = 328649; + tables[89][26] = 263106; + tables[89][27] = 328647; + tables[89][28] = 328645; + tables[89][29] = 328630; + tables[89][51] = 263044; + tables[89][100] = 262204; + tables[89][103] = 0; + tables[89][133] = 0; + tables[89][134] = 0; + tables[89][140] = 0; + tables[90] = tables[5]; + tables[90][4] = 328593; + tables[90][5] = 328594; + tables[90][6] = 328616; + tables[90][7] = 328596; + tables[90][8] = 328597; + tables[90][9] = 328614; + tables[90][10] = 328595; + tables[90][11] = 328599; + tables[90][12] = 328601; + tables[90][13] = 328606; + tables[90][14] = 328602; + tables[90][15] = 328603; + tables[90][16] = 328604; + tables[90][17] = 328605; + tables[90][18] = 328607; + tables[90][19] = 328608; + tables[90][20] = 262202; + tables[90][21] = 328609; + tables[90][22] = 328611; + tables[90][23] = 328612; + tables[90][24] = 328600; + tables[90][25] = 328617; + tables[90][26] = 263045; + tables[90][27] = 328615; + tables[90][28] = 328613; + tables[90][29] = 328598; + tables[90][51] = 262312; + tables[90][100] = 262206; + tables[90][103] = 0; + tables[90][133] = 0; + tables[90][134] = 0; + tables[90][140] = 0; + tables[91][6] = 262313; + tables[91][8] = 270508; + tables[91][21] = 262318; + tables[91][28] = 262309; + tables[91][31] = 262322; + tables[91][32] = 262323; + tables[91][33] = 262307; + tables[91][34] = 262311; + tables[91][35] = 262326; + tables[91][37] = 262308; + tables[91][38] = 262310; + tables[91][39] = 262320; + tables[91][45] = 262321; + tables[91][46] = 262333; + tables[91][47] = 262315; + tables[91][48] = 262331; + tables[91][50] = 262316; + tables[91][51] = 263045; + tables[92] = tables[4]; + tables[92][103] = 0; + tables[92][133] = 0; + tables[92][134] = 0; + tables[92][140] = 0; + tables[93] = tables[5]; + tables[93][103] = 0; + tables[93][133] = 0; + tables[93][134] = 0; + tables[93][140] = 0; + tables[94] = tables[92]; + tables[94][30] = 327939; + tables[94][31] = 327906; + tables[94][32] = 327914; + tables[94][33] = 327924; + tables[94][34] = 262912; + tables[94][35] = 262921; + tables[94][36] = 262915; + tables[94][37] = 262913; + tables[94][38] = 262947; + tables[94][39] = 327953; + tables[94][46] = 270507; + tables[94][47] = 328112; + tables[94][48] = 328097; + tables[95] = tables[93]; + tables[95][30] = 327938; + tables[95][31] = 327874; + tables[95][32] = 327882; + tables[95][33] = 327892; + tables[95][34] = 262912; + tables[95][35] = 262921; + tables[95][36] = 262915; + tables[95][37] = 262913; + tables[95][38] = 262947; + tables[95][39] = 327952; + tables[95][47] = 328111; + tables[95][48] = 328096; + tables[96] = tables[92]; + tables[96][40] = 0; + tables[96][42] = 0; + tables[96][43] = 0; + tables[96][44] = 0; + tables[96][83] = 0; + tables[96][84] = 0; + tables[96][85] = 0; + tables[96][86] = 0; + tables[96][87] = 0; + tables[96][88] = 0; + tables[96][89] = 0; + tables[96][90] = 0; + tables[96][91] = 0; + tables[96][92] = 0; + tables[96][93] = 0; + tables[96][94] = 0; + tables[96][95] = 0; + tables[96][96] = 0; + tables[96][97] = 0; + tables[96][98] = 0; + tables[96][99] = 0; + tables[97] = tables[93]; + tables[97][40] = 0; + tables[97][42] = 0; + tables[97][43] = 0; + tables[97][44] = 0; + tables[97][83] = 0; + tables[97][84] = 0; + tables[97][85] = 0; + tables[97][86] = 0; + tables[97][87] = 0; + tables[97][88] = 0; + tables[97][89] = 0; + tables[97][90] = 0; + tables[97][91] = 0; + tables[97][92] = 0; + tables[97][93] = 0; + tables[97][94] = 0; + tables[97][95] = 0; + tables[97][96] = 0; + tables[97][97] = 0; + tables[97][98] = 0; + tables[97][99] = 0; + tables[98] = tables[92]; + tables[98][47] = 327939; + tables[98][48] = 327918; + tables[98][49] = 327906; + tables[98][50] = 327906; + tables[98][51] = 328217; + tables[98][52] = 328219; + tables[98][53] = 270366; + tables[99] = tables[93]; + tables[99][47] = 327938; + tables[99][48] = 327886; + tables[99][49] = 327874; + tables[99][50] = 327874; + tables[99][51] = 328216; + tables[99][52] = 328218; + tables[99][53] = 270365; + tables[99][54] = 262203; + tables[99][55] = 262202; + tables[100] = tables[88]; + tables[100][5] = 0; + tables[100][6] = 262313; + tables[100][7] = 327953; + tables[100][9] = 0; + tables[100][10] = 0; + tables[100][14] = 0; + tables[100][15] = 328002; + tables[100][17] = 0; + tables[100][19] = 262311; + tables[100][20] = 0; + tables[100][22] = 262367; + tables[100][25] = 0; + tables[100][26] = 0; + tables[100][27] = 0; + tables[100][47] = 262235; + tables[100][48] = 262237; + tables[100][49] = 262236; + tables[100][50] = 262236; + tables[100][51] = 262203; + tables[100][52] = 262183; + tables[100][53] = 262240; + tables[100][56] = 0; + tables[101] = tables[29]; + tables[101][7] = 327952; + tables[101][15] = 328001; + tables[101][45] = 270355; + tables[101][46] = 262321; + tables[101][49] = 262268; + tables[101][50] = 262268; + tables[101][51] = 262202; + tables[101][52] = 262178; + tables[101][53] = 262270; + tables[101][54] = 262315; + tables[101][55] = 262331; + tables[102] = tables[92]; + tables[102][28] = 327802; + tables[102][29] = 327801; + tables[102][39] = 327926; + tables[102][45] = 327932; + tables[102][46] = 327923; + tables[102][47] = 328017; + tables[102][48] = 327930; + tables[102][49] = 328049; + tables[102][50] = 328049; + tables[102][51] = 327913; + tables[102][52] = 327905; + tables[102][53] = 262192; + tables[102][56] = 262189; + tables[102][100] = 327917; + tables[103] = tables[87]; + tables[103][30] = 262183; + tables[103][31] = 262178; + tables[103][32] = 262187; + tables[103][33] = 262177; + tables[103][34] = 262181; + tables[103][35] = 262191; + tables[103][36] = 262205; + tables[103][37] = 262184; + tables[103][38] = 262185; + tables[103][39] = 327894; + tables[103][45] = 327900; + tables[103][46] = 327891; + tables[103][47] = 328016; + tables[103][48] = 327898; + tables[103][49] = 328048; + tables[103][50] = 328048; + tables[103][51] = 327881; + tables[103][52] = 327873; + tables[103][53] = 262311; + tables[103][100] = 327885; + tables[104] = tables[88]; + tables[104][4] = 262372; + tables[104][8] = 262340; + tables[104][12] = 262349; + tables[104][13] = 262381; + tables[104][16] = 262204; + tables[104][24] = 270508; + tables[104][29] = 262206; + tables[104][54] = 262203; + tables[104][100] = 262204; + tables[105] = tables[36]; + tables[105][22] = 328790; + tables[105][48] = 328791; + tables[105][52] = 328788; + tables[105][53] = 327719; + tables[105][83] = 0; + tables[105][100] = 263313; + tables[106] = tables[37]; + tables[106][22] = 328710; + tables[106][48] = 328711; + tables[106][52] = 328708; + tables[106][53] = 0; + tables[106][100] = 263312; + tables[107][24] = 263313; + tables[108][24] = 263312; + tables[109][40] = 262154; + tables[109][42] = 262152; + tables[109][43] = 262153; + tables[109][88] = 262154; + tables[109][186] = 262152; + tables[109][187] = 262153; + tables[110][31] = 327962; + tables[110][32] = 328032; + tables[110][33] = 327948; + tables[110][34] = 328024; + tables[110][35] = 328061; + tables[110][36] = 327901; + tables[110][37] = 327873; + tables[110][38] = 327885; + tables[110][39] = 327881; + tables[110][47] = 327898; + tables[110][51] = 328046; + return tables; +}(); + +constexpr std::array, 41> CharacterLayouts = {{ + {1, 2, 3, 0}, + {4, 5, 6, 0}, + {7, 8, 0, 0}, + {9, 10, 0, 0}, + {11, 12, 0, 0}, + {13, 14, 0, 0}, + {15, 16, 0, 0}, + {17, 18, 19, 0}, + {20, 21, 22, 0}, + {23, 24, 25, 0}, + {26, 27, 28, 29}, + {30, 31, 32, 0}, + {33, 34, 35, 0}, + {36, 37, 0, 0}, + {38, 39, 40, 41}, + {42, 43, 0, 0}, + {44, 45, 46, 0}, + {47, 48, 49, 0}, + {50, 51, 52, 0}, + {47, 48, 49, 0}, + {53, 54, 0, 55}, + {56, 57, 0, 0}, + {58, 54, 0, 55}, + {59, 60, 3, 0}, + {61, 62, 63, 0}, + {64, 65, 66, 67}, + {68, 69, 70, 71}, + {72, 73, 74, 0}, + {75, 76, 77, 78}, + {79, 80, 81, 0}, + {82, 83, 0, 0}, + {84, 85, 0, 0}, + {86, 87, 88, 0}, + {89, 90, 91, 0}, + {92, 93, 0, 0}, + {94, 95, 96, 97}, + {98, 99, 100, 101}, + {102, 103, 104, 0}, + {105, 106, 107, 108}, + {109, 109, 109, 109}, + {110, 110, 110, 110}, +}}; + +constexpr std::array KoreanInitial = { + 12593, 12594, 12596, 12599, 12600, 12601, 12609, 12610, 12611, 12613, 12614, 12615, 12616, 12617, 12618, 12619, 12620, 12621, 12622 +}; + +constexpr std::array KoreanFinal = { + 12593, 12594, 12595, 12596, 12597, 12598, 12599, 12601, 12602, 12603, 12604, 12605, 12606, 12607, 12608, 12609, 12610, 12612, 12613, 12614, 12615, 12616, 12618, 12619, 12620, 12621, 12622 +}; + +std::size_t CharacterLayout(std::uint32_t type, std::uint32_t state) { + constexpr std::array layouts = {18, 7, 0, 1, 14, 8, 29, 17, 9, 25, 24, 23, 10, 11, 16, 26, 27, 12, 1, 19, 28, 3, 3, 1, 1, 1, 1, 1, 1, 1, 14, 32, 14, 34, 35, 36, 37, 1, 1}; + const bool imeMode = (state & 0x4000000u) != 0; + const bool alternateMode = (state & 0x10000000u) != 0; + switch (type) { + case 19: return imeMode ? 13 : 1; + case 22: return alternateMode ? 2 : 3; + case 23: + if (!imeMode) return alternateMode ? 2 : 3; + if ((state & 0x8000000u) == 0) return 6; + return alternateMode ? 4 : 5; + case 24: return imeMode ? 15 : 1; + case 26: + if (imeMode) return 20; + return alternateMode ? 21 : 1; + case 29: + case 39: + if (imeMode) return 22; + return alternateMode ? 21 : 1; + case 25: + case 27: + case 28: return alternateMode ? 21 : 1; + case 30: return imeMode ? 30 : 1; + case 31: return imeMode ? 31 : 14; + case 33: return imeMode ? 33 : 14; + case 38: return imeMode ? 38 : 1; + default: return layouts[type - 1]; + } +} + +std::uint32_t CharacterEntry(std::size_t layout, std::size_t layer, std::uint16_t keycode) { + if (keycode >= 256) return 0; + return CharacterTables[CharacterLayouts[layout][layer]][keycode]; +} + +char16_t ConvertCharacter(std::uint16_t keycode, std::uint32_t state, std::uint32_t type) { + if (keycode == 83 || (keycode & 0xFFF8u) == 224) return 0; + const auto layout = CharacterLayout(type, state); + const bool shifted = (state & 0x2200u) != 0; + const bool caps = (state & 0x20000u) != 0; + if (layout == 0 && caps) { + if (keycode == 47) return shifted ? 200 : 220; + if (keycode == 52) return shifted ? 192 : 196; + if (keycode == 51) return shifted ? 201 : 214; + } + const auto& layers = CharacterLayouts[layout]; + if ((state & 0x4000u) != 0 && (layers[2] != 0 || layers[3] != 0)) { + auto entry = CharacterEntry(layout, shifted ? 3 : 2, keycode); + if (entry == 0) return static_cast(CharacterEntry(39, 0, keycode)); + if (caps && (entry & 0x10000u) != 0) { + const auto opposite = CharacterEntry(layout, shifted ? 2 : 3, keycode); + if (opposite != 0) entry = opposite; + } + return static_cast(entry); + } + auto entry = CharacterEntry(layout, shifted ? 1 : 0, keycode); + if (entry == 0) return 0; + if (caps) { + if ((type == 26 || type == 29 || type == 39) && (state & 0x4000000u) != 0) { + entry = CharacterEntry(layout, shifted ? 3 : 0, keycode); + } else if (type == 32 && !shifted && ((keycode >= 31 && keycode <= 39) || keycode == 47 || keycode == 51)) { + entry = CharacterEntry(40, 0, keycode); + } else if ((entry & 0x10000u) != 0) { + entry = CharacterEntry(layout, shifted ? 0 : 1, keycode); + } + if (entry == 0) return 0; + } + if ((entry & 0x20000u) != 0) { + const bool numLock = (state & 0x10000u) != 0; + if (!numLock && keycode >= 89 && keycode <= 99) return 0; + entry = CharacterEntry(layout, numLock ? 1 : 0, keycode); + if (entry == 0) return 0; + } + auto character = static_cast(entry); + if ((state & 0x4000000u) != 0 && type == 24) { + if ((character >= 12592 && character < 12688) || (character >= 44032 && character < 55216) || (character & 0xFF00u) == 0x1100) { + if (character >= 4352 && character <= 4370) return KoreanInitial[character - 4352]; + if (character >= 4449 && character <= 4469) return static_cast(character + 8174); + if (character >= 4520 && character <= 4546) return KoreanFinal[character - 4520]; + return 0; + } + } + if ((state & 0x4000000u) != 0 && type == 30) { + if ((state & 0x80u) == 0) { + if (keycode == 5) character = shifted ? 1570 : 1575; + if (shifted && keycode == 10) character = 1571; + if (shifted && keycode == 23) character = 1573; + } + const auto modifiers = state & 0xFFF8FFFFu; + if (modifiers == 67117312 || modifiers == 67121152 || modifiers == 67109632 || modifiers == 67113472) { + constexpr std::array formatting = {0x200D, 0x200C, 0x200E, 0x200F}; + return keycode >= 30 && keycode <= 33 ? formatting[keycode - 30] : 0; + } + if ((state & 0x1100u) != 0 && shifted) return 0; + } + return character; +} +bool Ps4Client() { + try { + const auto titleId = GetAppTitleId_nid_postfix(); + return ImeKeyboardType::IsPs4TitleId(std::string_view(titleId.value, strnlen(titleId.value, sizeof(titleId.value)))); + } catch (const std::runtime_error&) { + return false; + } catch (const std::length_error&) { + return false; + } } } // namespace extern "C" { int APS5_VABI sceConvertKeycodeGetImeKeyboardType(int32_t userId, uint32_t* type) { - if (type == nullptr) return kErrInvalidAddress; - if (userId < 0) return kErrInvalidUserId; - *type = 0; - return 0; + const bool ps4Client = Ps4Client(); + const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t sdkVersion; + std::memcpy(&sdkVersion, process + 16, sizeof(sdkVersion)); + return ImeKeyboardType::Resolve(userId, type, sdkVersion, ps4Client, sceUserServiceGetKeyboardType, sceUserServiceGetJapaneseInputType, sceUserServiceGetTraditionalChineseInputType); } -int APS5_VABI sceConvertKeycodeGetVirtualKeycode(std::uint64_t a0, std::uint64_t a1, std::uint64_t a2, std::uint64_t a3) { - APS5_HIT("CONVERTKEYCODE", "sceConvertKeycodeGetVirtualKeycode args 0x%llx 0x%llx 0x%llx 0x%llx -> error (signature unknown)", - static_cast(a0), static_cast(a1), static_cast(a2), static_cast(a3)); - return kErrInvalidAddress; +int APS5_VABI sceConvertKeycodeGetVirtualKeycode(std::uint16_t keycode, std::uint32_t keyboardType, std::uint16_t* virtualKeycode) { + if (virtualKeycode == nullptr) return kErrInvalidAddress; + if (keyboardType == 0 || keyboardType > VirtualKeys.size()) return kErrInvalidKeyboardType; + if (keyboardType >= 31) throw std::runtime_error("sceConvertKeycodeGetVirtualKeycode: guest sdk version is unavailable"); + auto value = keycode < BaseVirtualKeys.size() ? VirtualKeys[keyboardType - 1][keycode] : 255; + if ((keyboardType == 2 || keyboardType == 3 || keyboardType == 12) && (value == 89 || value == 90)) throw std::runtime_error("sceConvertKeycodeGetVirtualKeycode: guest sdk version is unavailable"); + *virtualKeycode = static_cast(value); + return 0; } -int APS5_VABI sceConvertKeycodeGetCharacterFromKeyboardData(void) { +int APS5_VABI sceConvertKeycodeGetCharacterFromKeyboardData(const KeyboardData* keyData, std::int32_t index, std::uint32_t state, std::uint32_t keyboardType, char16_t* character) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceConvertKeycodeGetCharacter(std::uint16_t keycode, std::uint32_t state, std::uint32_t type, char16_t* character) { if (character == nullptr) return kErrInvalidAddress; - if ((state & ~kIgnoredStateBits) != 0) throw std::runtime_error("sceConvertKeycodeGetCharacter: unsupported state " + std::to_string(state)); - if (type != 0) throw std::runtime_error("sceConvertKeycodeGetCharacter: unsupported keyboard type " + std::to_string(type)); - *character = UsCharacter(keycode); + if (type == 0 || type > 39) return kErrInvalidKeyboardType; + if (type >= 31) throw std::runtime_error("sceConvertKeycodeGetCharacter: guest sdk version is unavailable"); + *character = ConvertCharacter(keycode, state, type); return 0; } diff --git a/core/libs/prx/libSceConvertKeycode/ImeKeyboardType.hpp b/core/libs/prx/libSceConvertKeycode/ImeKeyboardType.hpp new file mode 100644 index 0000000000..ec7e041937 --- /dev/null +++ b/core/libs/prx/libSceConvertKeycode/ImeKeyboardType.hpp @@ -0,0 +1,71 @@ +#ifndef CORE_LIBS_PRX_LIBSCECONVERTKEYCODE_IMEKEYBOARDTYPE_HPP +#define CORE_LIBS_PRX_LIBSCECONVERTKEYCODE_IMEKEYBOARDTYPE_HPP + +#include +#include + +// sceConvertKeycodeGetImeKeyboardType: the UserService keyboard type (plus the Japanese and Traditional Chinese +// input variants) to IME keyboard type mapping and the SDK compatibility rules, as specified by the repository +// owner (see docs/dev/debt/libSceConvertKeycode.md). Header-only so the guest test drives every row and error path +// with fake UserService getters. +namespace ImeKeyboardType { + +constexpr int kErrInvalidAddress = static_cast(0x80BC0031u); +constexpr int kErrInvalidUserId = static_cast(0x80BC0010u); +constexpr int kErrGeneric = static_cast(0x80BC00FFu); +constexpr int kUserServiceUnknownUser = static_cast(0x80960105u); + +constexpr std::int32_t kJapaneseKeyboard = 37; +constexpr std::int32_t kTraditionalChineseKeyboard = 39; + +// Index = UserService keyboard type - 16. Types 37 and 39 are resolved through their variants; 0 = no mapping. +constexpr std::int32_t kFirstMappedType = 16; +constexpr std::uint32_t kImeTypeByKeyboardType[] = { + 1, 2, 3, 5, 4, 6, 7, 8, 10, 11, 9, 12, 13, 14, 15, 16, 17, 18, 19, 20, // 16..35 + 21, 0, 24, 0, 25, 30, 31, 33, 32, 34, 35, 36, 37, 38, // 36..49 (37 and 39 are variants) +}; +constexpr std::int32_t kMappedTypeCount = static_cast(sizeof(kImeTypeByKeyboardType) / sizeof(kImeTypeByKeyboardType[0])); + +constexpr std::uint32_t LookUp(std::int32_t keyboardType, std::int32_t japaneseVariant, std::int32_t traditionalChineseVariant) { + if (keyboardType == kJapaneseKeyboard) { + constexpr std::uint32_t japanese[] = {22, 23}; + return japaneseVariant >= 0 && japaneseVariant < 2 ? japanese[japaneseVariant] : 0; + } + if (keyboardType == kTraditionalChineseKeyboard) { + constexpr std::uint32_t traditionalChinese[] = {26, 27, 28, 29, 39}; + return traditionalChineseVariant >= 0 && traditionalChineseVariant < 5 ? traditionalChinese[traditionalChineseVariant] : 0; + } + const std::int64_t index = std::int64_t{keyboardType} - kFirstMappedType; + return index >= 0 && index < kMappedTypeCount ? kImeTypeByKeyboardType[index] : 0; +} + +// Applied after the lookup. 38 -> 4 below PS5 SDK 0x06000000 / PS4 SDK 0x10000000; 39 -> 29 below PS5 SDK 0x08000000 +// and for every PS4 client. +constexpr std::uint32_t ApplySdkCompatibility(std::uint32_t imeType, std::uint32_t sdkVersion, bool ps4Client) { + if (imeType == 38 && sdkVersion < (ps4Client ? 0x10000000u : 0x06000000u)) return 4; + if (imeType == 39 && (ps4Client || sdkVersion < 0x08000000u)) return 29; + return imeType; +} + +constexpr bool IsPs4TitleId(std::string_view titleId) { + return titleId.substr(0, 4) == "CUSA"; +} + +template +int Resolve(std::int32_t userId, std::uint32_t* type, std::uint32_t sdkVersion, bool ps4Client, GetKeyboardType getKeyboardType, GetJapaneseInputType getJapaneseInputType, GetTraditionalChineseInputType getTraditionalChineseInputType) { + if (type == nullptr) return kErrInvalidAddress; + std::int32_t keyboardType = 0; + const int result = getKeyboardType(userId, &keyboardType); + if (result == kUserServiceUnknownUser) return kErrInvalidUserId; + if (result != 0) return kErrGeneric; + std::int32_t japaneseVariant = 0; + std::int32_t traditionalChineseVariant = 0; + if (keyboardType == kJapaneseKeyboard && getJapaneseInputType(userId, &japaneseVariant) != 0) japaneseVariant = 0; + if (keyboardType == kTraditionalChineseKeyboard && getTraditionalChineseInputType(userId, &traditionalChineseVariant) != 0) traditionalChineseVariant = 0; + *type = ApplySdkCompatibility(LookUp(keyboardType, japaneseVariant, traditionalChineseVariant), sdkVersion, ps4Client); + return 0; +} + +} // namespace ImeKeyboardType + +#endif diff --git a/core/libs/prx/libSceConvertKeycode/include/Api.hpp b/core/libs/prx/libSceConvertKeycode/include/Api.hpp new file mode 100644 index 0000000000..437b5117c5 --- /dev/null +++ b/core/libs/prx/libSceConvertKeycode/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceConvertKeycodeGetImeKeyboardType(int32_t userId, uint32_t* type); +int APS5_VABI sceConvertKeycodeGetVirtualKeycode(std::uint16_t keycode, std::uint32_t keyboardType, std::uint16_t* virtualKeycode); +int APS5_VABI sceConvertKeycodeGetCharacterFromKeyboardData(const KeyboardData* keyData, std::int32_t index, std::uint32_t state, std::uint32_t keyboardType, char16_t* character); +int APS5_VABI sceConvertKeycodeGetCharacter(std::uint16_t keycode, std::uint32_t state, std::uint32_t type, char16_t* character); + +} diff --git a/core/libs/prx/libSceConvertKeycode/libSceConvertKeycode.hpp b/core/libs/prx/libSceConvertKeycode/libSceConvertKeycode.hpp new file mode 100644 index 0000000000..45c6357f44 --- /dev/null +++ b/core/libs/prx/libSceConvertKeycode/libSceConvertKeycode.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceConvertKeycode/include/Api.hpp" diff --git a/core/libs/prx/libSceCtrlp/libSceCtrlp.hpp b/core/libs/prx/libSceCtrlp/libSceCtrlp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceCtrlp/libSceCtrlp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp b/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp index a383508620..04ed029ae6 100644 --- a/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp +++ b/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceCustomMusicAudioOut/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceCustomMusicAudioOut/include/Api.hpp b/core/libs/prx/libSceCustomMusicAudioOut/include/Api.hpp new file mode 100644 index 0000000000..934f42befc --- /dev/null +++ b/core/libs/prx/libSceCustomMusicAudioOut/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCustomMusicAudioOutFinalize(); +int APS5_VABI sceCustomMusicAudioOutGetAllMusicVolume(); +int APS5_VABI sceCustomMusicAudioOutGetMuted(); +int APS5_VABI sceCustomMusicAudioOutGetVolume(); +int APS5_VABI sceCustomMusicAudioOutSetAllMusicVolume(); + +} diff --git a/core/libs/prx/libSceCustomMusicAudioOut/libSceCustomMusicAudioOut.hpp b/core/libs/prx/libSceCustomMusicAudioOut/libSceCustomMusicAudioOut.hpp new file mode 100644 index 0000000000..387b464246 --- /dev/null +++ b/core/libs/prx/libSceCustomMusicAudioOut/libSceCustomMusicAudioOut.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCustomMusicAudioOut/include/Api.hpp" diff --git a/core/libs/prx/libSceCustomMusicCore/Export.cpp b/core/libs/prx/libSceCustomMusicCore/Export.cpp index fa0c494e42..65b3aaccac 100644 --- a/core/libs/prx/libSceCustomMusicCore/Export.cpp +++ b/core/libs/prx/libSceCustomMusicCore/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceCustomMusicCore/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceCustomMusicCore/include/Api.hpp b/core/libs/prx/libSceCustomMusicCore/include/Api.hpp new file mode 100644 index 0000000000..478406741d --- /dev/null +++ b/core/libs/prx/libSceCustomMusicCore/include/Api.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCustomMusicCoreBgmClose(); +int APS5_VABI sceCustomMusicCoreBgmOpen(); +int APS5_VABI sceCustomMusicCoreBgmOpen2(); +int APS5_VABI sceCustomMusicCoreBgmOutput(); +int APS5_VABI sceCustomMusicCoreBgmSetAudioVolume(); +int APS5_VABI sceCustomMusicCoreBgmStart(); +int APS5_VABI sceCustomMusicCoreBgmStop(); +int APS5_VABI sceCustomMusicCoreGetBgmAuthorityStatus(); +int APS5_VABI sceCustomMusicCoreGetHdcpStatus(); +int APS5_VABI sceCustomMusicCoreGetSystemAudioVolume(); +int APS5_VABI sceCustomMusicCorePostEvent(); +int APS5_VABI sceCustomMusicCoreSendEvent(); +int APS5_VABI sceCustomMusicCoreSendMulticastEvent(); +int APS5_VABI sceCustomMusicCoreSendSpMessage(); +int APS5_VABI sceCustomMusicCoreSetSystemAudioVolume(); +int APS5_VABI sceCustomMusicCoreStartOperation(); +int APS5_VABI sceCustomMusicCoreStartOperation2(); +int APS5_VABI sceCustomMusicCoreStopOperation(); + +} diff --git a/core/libs/prx/libSceCustomMusicCore/libSceCustomMusicCore.hpp b/core/libs/prx/libSceCustomMusicCore/libSceCustomMusicCore.hpp new file mode 100644 index 0000000000..e7d4cf7206 --- /dev/null +++ b/core/libs/prx/libSceCustomMusicCore/libSceCustomMusicCore.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCustomMusicCore/include/Api.hpp" diff --git a/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp b/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp index ee9009496a..e64d1e4444 100644 --- a/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp +++ b/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceCustomMusicSysCallWrapper/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceCustomMusicSysCallWrapper/include/Api.hpp b/core/libs/prx/libSceCustomMusicSysCallWrapper/include/Api.hpp new file mode 100644 index 0000000000..63ad57d532 --- /dev/null +++ b/core/libs/prx/libSceCustomMusicSysCallWrapper/include/Api.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCustomMusicSelect(); + +} diff --git a/core/libs/prx/libSceCustomMusicSysCallWrapper/libSceCustomMusicSysCallWrapper.hpp b/core/libs/prx/libSceCustomMusicSysCallWrapper/libSceCustomMusicSysCallWrapper.hpp new file mode 100644 index 0000000000..22ad944a67 --- /dev/null +++ b/core/libs/prx/libSceCustomMusicSysCallWrapper/libSceCustomMusicSysCallWrapper.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCustomMusicSysCallWrapper/include/Api.hpp" diff --git a/core/libs/prx/libSceDataTransfer/Export.cpp b/core/libs/prx/libSceDataTransfer/Export.cpp index 43fb2a1574..5c993b6dfb 100644 --- a/core/libs/prx/libSceDataTransfer/Export.cpp +++ b/core/libs/prx/libSceDataTransfer/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceDataTransfer/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceDataTransfer/include/Api.hpp b/core/libs/prx/libSceDataTransfer/include/Api.hpp new file mode 100644 index 0000000000..f04a12fef4 --- /dev/null +++ b/core/libs/prx/libSceDataTransfer/include/Api.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceDataTransferAbortFgTransfer(); +int APS5_VABI sceDataTransferAbortRequest(); +int APS5_VABI sceDataTransferAbortSearchPS4(); +int APS5_VABI sceDataTransferCheckBgTransferRunning(); +int APS5_VABI sceDataTransferGetFgTransferProgress(); +int APS5_VABI sceDataTransferGetPrepareFgTransferProgress(); +int APS5_VABI sceDataTransferInitialize(); +int APS5_VABI sceDataTransferRequestGetAppInfoPS4(); +int APS5_VABI sceDataTransferRequestGetSavedataInfoPS4(); +int APS5_VABI sceDataTransferRequestGetUsersPS4(); +int APS5_VABI sceDataTransferRequestLaunchPS4(); +int APS5_VABI sceDataTransferRequestPrepareFgTransfer(); +int APS5_VABI sceDataTransferRequestRebootAuthPS4(); +int APS5_VABI sceDataTransferRequestSearchPS4(); +int APS5_VABI sceDataTransferRequestStartFgTransfer(); +int APS5_VABI sceDataTransferRequestTransferTimePS4(); +int APS5_VABI sceDataTransferTargetAbortBindSavedata(); +int APS5_VABI sceDataTransferTargetAbortDeactivate(); +int APS5_VABI sceDataTransferTargetAbortEth0(); +int APS5_VABI sceDataTransferTargetAbortGetDeviceInfo(); +int APS5_VABI sceDataTransferTargetAbortGetDeviceInfoApplication(); +int APS5_VABI sceDataTransferTargetAbortGetTitles(); +int APS5_VABI sceDataTransferTargetAbortGetUsers(); +int APS5_VABI sceDataTransferTargetAbortLaunch(); +int APS5_VABI sceDataTransferTargetAbortPrepareTransfer(); +int APS5_VABI sceDataTransferTargetAbortPwrReq(); +int APS5_VABI sceDataTransferTargetAbortReboot(); +int APS5_VABI sceDataTransferTargetAbortSendSsoNew2Old(); +int APS5_VABI sceDataTransferTargetAbortSendSsoOld2New(); +int APS5_VABI sceDataTransferTargetAbortTransfer(); +int APS5_VABI sceDataTransferTargetAbortTransferSpeed(); +int APS5_VABI sceDataTransferTargetEventIsAuthReady(); +int APS5_VABI sceDataTransferTargetEventIsIPv6Ready(); +int APS5_VABI sceDataTransferTargetEventIsPwrReqReady(); +int APS5_VABI sceDataTransferTargetGetFailedUsers(); +int APS5_VABI sceDataTransferTargetGetRebootData(); +int APS5_VABI sceDataTransferTargetGetTransferProgress(); +int APS5_VABI sceDataTransferTargetRequestAbortSearch(); +int APS5_VABI sceDataTransferTargetRequestActivate(); +int APS5_VABI sceDataTransferTargetRequestAuth(); +int APS5_VABI sceDataTransferTargetRequestBindSavedata(); +int APS5_VABI sceDataTransferTargetRequestComplete(); +int APS5_VABI sceDataTransferTargetRequestCreateRebootData(); +int APS5_VABI sceDataTransferTargetRequestDeactivate(); +int APS5_VABI sceDataTransferTargetRequestEndTransfer(); +int APS5_VABI sceDataTransferTargetRequestGetDeviceInfo(); +int APS5_VABI sceDataTransferTargetRequestGetDeviceInfoApplication(); +int APS5_VABI sceDataTransferTargetRequestGetTitles(); +int APS5_VABI sceDataTransferTargetRequestGetUsers(); +int APS5_VABI sceDataTransferTargetRequestLaunch(); +int APS5_VABI sceDataTransferTargetRequestPrepareTransfer(); +int APS5_VABI sceDataTransferTargetRequestPrepareTransferProgress(); +int APS5_VABI sceDataTransferTargetRequestPwrReq(); +int APS5_VABI sceDataTransferTargetRequestReboot(); +int APS5_VABI sceDataTransferTargetRequestSearch(); +int APS5_VABI sceDataTransferTargetRequestSendSsoNew2Old(); +int APS5_VABI sceDataTransferTargetRequestSendSsoOld2New(); +int APS5_VABI sceDataTransferTargetRequestStartTransfer(); +int APS5_VABI sceDataTransferTargetRequestTransferSpeed(); +int APS5_VABI sceDataTransferTerminate(); + +} diff --git a/core/libs/prx/libSceDataTransfer/libSceDataTransfer.hpp b/core/libs/prx/libSceDataTransfer/libSceDataTransfer.hpp new file mode 100644 index 0000000000..dfa5f26f82 --- /dev/null +++ b/core/libs/prx/libSceDataTransfer/libSceDataTransfer.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceDataTransfer/include/Api.hpp" diff --git a/core/libs/prx/libSceDbg/libSceDbg.hpp b/core/libs/prx/libSceDbg/libSceDbg.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDbg/libSceDbg.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDbgAssist/libSceDbgAssist.hpp b/core/libs/prx/libSceDbgAssist/libSceDbgAssist.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDbgAssist/libSceDbgAssist.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDbgThreadSanitizer/Export.cpp b/core/libs/prx/libSceDbgThreadSanitizer/Export.cpp index f2bbed9a32..dcd840a54c 100644 --- a/core/libs/prx/libSceDbgThreadSanitizer/Export.cpp +++ b/core/libs/prx/libSceDbgThreadSanitizer/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceDbgThreadSanitizer/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" diff --git a/core/libs/prx/libSceDbgThreadSanitizer/include/Api.hpp b/core/libs/prx/libSceDbgThreadSanitizer/include/Api.hpp new file mode 100644 index 0000000000..0e19a53fdc --- /dev/null +++ b/core/libs/prx/libSceDbgThreadSanitizer/include/Api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +void APS5_VABI __tsan_acquire(void*); +void APS5_VABI __tsan_release(void*); + +} diff --git a/core/libs/prx/libSceDbgThreadSanitizer/libSceDbgThreadSanitizer.hpp b/core/libs/prx/libSceDbgThreadSanitizer/libSceDbgThreadSanitizer.hpp new file mode 100644 index 0000000000..e6ff7c3d4b --- /dev/null +++ b/core/libs/prx/libSceDbgThreadSanitizer/libSceDbgThreadSanitizer.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceDbgThreadSanitizer/include/Api.hpp" diff --git a/core/libs/prx/libSceDeci5/libSceDeci5.hpp b/core/libs/prx/libSceDeci5/libSceDeci5.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5/libSceDeci5.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Appmp/libSceDeci5Appmp.hpp b/core/libs/prx/libSceDeci5Appmp/libSceDeci5Appmp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Appmp/libSceDeci5Appmp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5CpComm/libSceDeci5CpComm.hpp b/core/libs/prx/libSceDeci5CpComm/libSceDeci5CpComm.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5CpComm/libSceDeci5CpComm.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Ctrlp/libSceDeci5Ctrlp.hpp b/core/libs/prx/libSceDeci5Ctrlp/libSceDeci5Ctrlp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Ctrlp/libSceDeci5Ctrlp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Dbgp/libSceDeci5Dbgp.hpp b/core/libs/prx/libSceDeci5Dbgp/libSceDeci5Dbgp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Dbgp/libSceDeci5Dbgp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Drfp/libSceDeci5Drfp.hpp b/core/libs/prx/libSceDeci5Drfp/libSceDeci5Drfp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Drfp/libSceDeci5Drfp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Eventp/libSceDeci5Eventp.hpp b/core/libs/prx/libSceDeci5Eventp/libSceDeci5Eventp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Eventp/libSceDeci5Eventp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Fsmp/libSceDeci5Fsmp.hpp b/core/libs/prx/libSceDeci5Fsmp/libSceDeci5Fsmp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Fsmp/libSceDeci5Fsmp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Pgcmp/libSceDeci5Pgcmp.hpp b/core/libs/prx/libSceDeci5Pgcmp/libSceDeci5Pgcmp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Pgcmp/libSceDeci5Pgcmp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Rdrfp/libSceDeci5Rdrfp.hpp b/core/libs/prx/libSceDeci5Rdrfp/libSceDeci5Rdrfp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Rdrfp/libSceDeci5Rdrfp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Tsmp/libSceDeci5Tsmp.hpp b/core/libs/prx/libSceDeci5Tsmp/libSceDeci5Tsmp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Tsmp/libSceDeci5Tsmp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5Ttyp/libSceDeci5Ttyp.hpp b/core/libs/prx/libSceDeci5Ttyp/libSceDeci5Ttyp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5Ttyp/libSceDeci5Ttyp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDeci5UlpCommon/libSceDeci5UlpCommon.hpp b/core/libs/prx/libSceDeci5UlpCommon/libSceDeci5UlpCommon.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDeci5UlpCommon/libSceDeci5UlpCommon.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDepth/Export.cpp b/core/libs/prx/libSceDepth/Export.cpp index edfa2e580e..58c40c04fd 100644 --- a/core/libs/prx/libSceDepth/Export.cpp +++ b/core/libs/prx/libSceDepth/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceDepth/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceDepth/include/Api.hpp b/core/libs/prx/libSceDepth/include/Api.hpp new file mode 100644 index 0000000000..fae208c300 --- /dev/null +++ b/core/libs/prx/libSceDepth/include/Api.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceDepthCalculateRectificationInformation(void); +int APS5_VABI sceDepthDisableHeteroMode(void); +int APS5_VABI sceDepthEnableExtendedMode(void); +int APS5_VABI sceDepthEnableHeteroMode(void); +int APS5_VABI sceDepthEnableHeteroModeAndGenerateInformation(void); +int APS5_VABI sceDepthGetCalibrationData(void); +int APS5_VABI sceDepthGetImage(void); +int APS5_VABI sceDepthGetRectificationInformation(void); +int APS5_VABI sceDepthGetSrParameter(void); +int APS5_VABI sceDepthHandCandidateTrackerGetResult(void); +int APS5_VABI sceDepthHasCalibrationData(void); +int APS5_VABI sceDepthHeadCandidateDetectorGetResults(void); +int APS5_VABI sceDepthHeadCandidateTrackerGetResult(void); +int APS5_VABI sceDepthHeadCandidateTrackerSetValidationInformation(void); +int APS5_VABI sceDepthInitialize(void); +int APS5_VABI sceDepthLoadCalibrationData(void); +int APS5_VABI sceDepthQueryMemory(void); +int APS5_VABI sceDepthSaveInternalBuffers(void); +int APS5_VABI sceDepthSetCalibrationData(void); +int APS5_VABI sceDepthSetCommands(void); +int APS5_VABI sceDepthSetPostFilterParameter(void); +int APS5_VABI sceDepthSetPreFilterParameter(void); +int APS5_VABI sceDepthSetRectificationInformation(void); +int APS5_VABI sceDepthSetRoi(void); +int APS5_VABI sceDepthSetRoiset(void); +int APS5_VABI sceDepthSetSrParameter(void); +int APS5_VABI sceDepthSetSrTnrParameter(void); +int APS5_VABI sceDepthSubmit(void); +int APS5_VABI sceDepthTerminate(void); +int APS5_VABI sceDepthUpdate(void); +int APS5_VABI sceDepthValidateCurrentCalibrationData(void); +int APS5_VABI sceDepthWaitAndExecutePostProcess(void); +int APS5_VABI _sceDepthCalcHeteroAdjustParam(); +int APS5_VABI _sceDepthHeadCandidateTrackerGetInformation(); +int APS5_VABI _sceDepthHeadCandidateTrackerSetInformation(); +int APS5_VABI _sceDepthSetCameraFactoryCalibrationData(); +int APS5_VABI _sceDepthSetHeteroAdjustParam(); +int APS5_VABI _sceDepthSetHeteroMode(); + +} diff --git a/core/libs/prx/libSceDepth/libSceDepth.hpp b/core/libs/prx/libSceDepth/libSceDepth.hpp new file mode 100644 index 0000000000..8899705618 --- /dev/null +++ b/core/libs/prx/libSceDepth/libSceDepth.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceDepth/include/Api.hpp" diff --git a/core/libs/prx/libSceDepth2/Export.cpp b/core/libs/prx/libSceDepth2/Export.cpp index d043a5728d..18a122f40c 100644 --- a/core/libs/prx/libSceDepth2/Export.cpp +++ b/core/libs/prx/libSceDepth2/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceDepth2/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceDepth2/include/Api.hpp b/core/libs/prx/libSceDepth2/include/Api.hpp new file mode 100644 index 0000000000..97193eee0c --- /dev/null +++ b/core/libs/prx/libSceDepth2/include/Api.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceDepth2GetImage(void); +int APS5_VABI sceDepth2Initialize(void); +int APS5_VABI sceDepth2QueryMemory(void); +int APS5_VABI sceDepth2SaveInternalBuffers(void); +int APS5_VABI sceDepth2SetCameraFactoryCalibrationData(void); +int APS5_VABI sceDepth2SetCommand(void); +int APS5_VABI sceDepth2SetRoi(void); +int APS5_VABI sceDepth2Submit(void); +int APS5_VABI sceDepth2Terminate(void); +int APS5_VABI sceDepth2WaitAndExecutePostProcess(void); + +} diff --git a/core/libs/prx/libSceDepth2/libSceDepth2.hpp b/core/libs/prx/libSceDepth2/libSceDepth2.hpp new file mode 100644 index 0000000000..30a843bc1b --- /dev/null +++ b/core/libs/prx/libSceDepth2/libSceDepth2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceDepth2/include/Api.hpp" diff --git a/core/libs/prx/libSceDipsw/Export.cpp b/core/libs/prx/libSceDipsw/Export.cpp index 3cddf792b2..a822968f7d 100644 --- a/core/libs/prx/libSceDipsw/Export.cpp +++ b/core/libs/prx/libSceDipsw/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceDipsw/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceDipsw/include/Api.hpp b/core/libs/prx/libSceDipsw/include/Api.hpp new file mode 100644 index 0000000000..655ebbc6de --- /dev/null +++ b/core/libs/prx/libSceDipsw/include/Api.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceKernelCheckDipsw(); +int APS5_VABI sceKernelInitializeDipsw(); +int APS5_VABI sceKernelIsAssistMode(); +int APS5_VABI sceKernelIsDevelopmentMode(); +int APS5_VABI sceKernelIsDisableRazor(); +int APS5_VABI sceKernelReadDipswData(); +int APS5_VABI sceKernelSetDipsw(); +int APS5_VABI sceKernelUnsetDipsw(); +int APS5_VABI sceKernelWriteDipswData(); + +} diff --git a/core/libs/prx/libSceDipsw/libSceDipsw.hpp b/core/libs/prx/libSceDipsw/libSceDipsw.hpp new file mode 100644 index 0000000000..6d1d472e8b --- /dev/null +++ b/core/libs/prx/libSceDipsw/libSceDipsw.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceDipsw/include/Api.hpp" diff --git a/core/libs/prx/libSceDiscId/Export.cpp b/core/libs/prx/libSceDiscId/Export.cpp index 055e970b29..33a87a4698 100644 --- a/core/libs/prx/libSceDiscId/Export.cpp +++ b/core/libs/prx/libSceDiscId/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceDiscId/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,12 +6,12 @@ extern "C" { -int APS5_VABI sceDiscIdGet() { +int APS5_VABI sceDiscIdGet(DiscId* discId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceDiscIdGet2() { +int APS5_VABI sceDiscIdGet2(DiscId2* discId, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceDiscId/include/Api.hpp b/core/libs/prx/libSceDiscId/include/Api.hpp new file mode 100644 index 0000000000..52b6039516 --- /dev/null +++ b/core/libs/prx/libSceDiscId/include/Api.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "SceTypes.hpp" + +struct DiscId; + +struct DiscId2; + +extern "C" { + +int APS5_VABI sceDiscIdGet(DiscId* discId); +int APS5_VABI sceDiscIdGet2(DiscId2* discId, std::uint32_t flags); + +} diff --git a/core/libs/prx/libSceDiscId/libSceDiscId.hpp b/core/libs/prx/libSceDiscId/libSceDiscId.hpp new file mode 100644 index 0000000000..26273e4024 --- /dev/null +++ b/core/libs/prx/libSceDiscId/libSceDiscId.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceDiscId/include/Api.hpp" diff --git a/core/libs/prx/libSceDiscMap/Export.cpp b/core/libs/prx/libSceDiscMap/Export.cpp index 80f2a899dc..7eea1e3999 100644 --- a/core/libs/prx/libSceDiscMap/Export.cpp +++ b/core/libs/prx/libSceDiscMap/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceDiscMap/include/Api.hpp" #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -19,7 +20,7 @@ std::int32_t APS5_VABI sceDiscMapIsRequestOnHDD(const char* path, std::int64_t o } APS5_EXPORT("ioKMruft1ek", sceDiscMapUnknown_ioKMruft1ek); -std::int32_t APS5_VABI sceDiscMapUnknown_ioKMruft1ek(const char* path, std::int64_t offset, std::int64_t size, void* flags, void* out1, void* out2) { +std::int32_t APS5_VABI sceDiscMapUnknown_ioKMruft1ek(const char* path, std::int64_t offset, std::int64_t size, std::uint64_t* flags, std::uint64_t* out1, std::uint64_t* out2) { (void)path; (void)offset; (void)size; diff --git a/core/libs/prx/libSceDiscMap/include/Api.hpp b/core/libs/prx/libSceDiscMap/include/Api.hpp new file mode 100644 index 0000000000..392ba50981 --- /dev/null +++ b/core/libs/prx/libSceDiscMap/include/Api.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +std::int32_t APS5_VABI sceDiscMapIsRequestOnHDD(const char* path, std::int64_t offset, std::int64_t size, std::int32_t* result); +std::int32_t APS5_VABI sceDiscMapUnknown_ioKMruft1ek(const char* path, std::int64_t offset, std::int64_t size, std::uint64_t* flags, std::uint64_t* out1, std::uint64_t* out2); +int APS5_VABI sceDiscMapGetPackageSize(std::int64_t fflags, int* ret1, int* ret2); + +} diff --git a/core/libs/prx/libSceDiscMap/libSceDiscMap.hpp b/core/libs/prx/libSceDiscMap/libSceDiscMap.hpp new file mode 100644 index 0000000000..e380270d79 --- /dev/null +++ b/core/libs/prx/libSceDiscMap/libSceDiscMap.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceDiscMap/include/Api.hpp" diff --git a/core/libs/prx/libSceDolbyVision/libSceDolbyVision.hpp b/core/libs/prx/libSceDolbyVision/libSceDolbyVision.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDolbyVision/libSceDolbyVision.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDseehx/Export.cpp b/core/libs/prx/libSceDseehx/Export.cpp index 43fce06ef6..b54b261746 100644 --- a/core/libs/prx/libSceDseehx/Export.cpp +++ b/core/libs/prx/libSceDseehx/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceDseehx/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceDseehx/include/Api.hpp b/core/libs/prx/libSceDseehx/include/Api.hpp new file mode 100644 index 0000000000..de8de8649b --- /dev/null +++ b/core/libs/prx/libSceDseehx/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceDseehxConvert(); +int APS5_VABI sceDseehxExtInit(); +int APS5_VABI sceDseehxFlush(); +int APS5_VABI sceDseehxInit(); + +} diff --git a/core/libs/prx/libSceDseehx/libSceDseehx.hpp b/core/libs/prx/libSceDseehx/libSceDseehx.hpp new file mode 100644 index 0000000000..a70d9c176e --- /dev/null +++ b/core/libs/prx/libSceDseehx/libSceDseehx.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceDseehx/include/Api.hpp" diff --git a/core/libs/prx/libSceDtcpIp/libSceDtcpIp.hpp b/core/libs/prx/libSceDtcpIp/libSceDtcpIp.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceDtcpIp/libSceDtcpIp.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceDtsEnc/Export.cpp b/core/libs/prx/libSceDtsEnc/Export.cpp index 06a9bc9f32..058fb9d21b 100644 --- a/core/libs/prx/libSceDtsEnc/Export.cpp +++ b/core/libs/prx/libSceDtsEnc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceDtsEnc/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceDtsEnc/include/Api.hpp b/core/libs/prx/libSceDtsEnc/include/Api.hpp new file mode 100644 index 0000000000..c58f777bab --- /dev/null +++ b/core/libs/prx/libSceDtsEnc/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceDtsEncCreateEncoder(); +int APS5_VABI sceDtsEncDeleteEncoder(); +int APS5_VABI sceDtsEncEncode(); +int APS5_VABI sceDtsEncEncodeAsync(); +int APS5_VABI sceDtsEncFlush(); +int APS5_VABI sceDtsEncIsEncoding(); +int APS5_VABI sceDtsEncWaitEncode(); + +} diff --git a/core/libs/prx/libSceDtsEnc/libSceDtsEnc.hpp b/core/libs/prx/libSceDtsEnc/libSceDtsEnc.hpp new file mode 100644 index 0000000000..c8fe34a7d9 --- /dev/null +++ b/core/libs/prx/libSceDtsEnc/libSceDtsEnc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceDtsEnc/include/Api.hpp" diff --git a/core/libs/prx/libSceEditMp4/Export.cpp b/core/libs/prx/libSceEditMp4/Export.cpp index 9bafeb8142..cc4aed4a43 100644 --- a/core/libs/prx/libSceEditMp4/Export.cpp +++ b/core/libs/prx/libSceEditMp4/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceEditMp4/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceEditMp4/include/Api.hpp b/core/libs/prx/libSceEditMp4/include/Api.hpp new file mode 100644 index 0000000000..219759f5bf --- /dev/null +++ b/core/libs/prx/libSceEditMp4/include/Api.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceEditMp4Abort(); +int APS5_VABI sceEditMp4Edit(); +int APS5_VABI sceEditMp4GetStatus(); +int APS5_VABI sceEditMp4Initialize(); +int APS5_VABI sceEditMp4InitializeParam(); +int APS5_VABI sceEditMp4QueryMemSize(); +int APS5_VABI sceEditMp4SetFinishCallback(); +int APS5_VABI sceEditMp4Terminate(); +int APS5_VABI sceEditMp4Unite(); + +} diff --git a/core/libs/prx/libSceEditMp4/libSceEditMp4.hpp b/core/libs/prx/libSceEditMp4/libSceEditMp4.hpp new file mode 100644 index 0000000000..d19448ddb3 --- /dev/null +++ b/core/libs/prx/libSceEditMp4/libSceEditMp4.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceEditMp4/include/Api.hpp" diff --git a/core/libs/prx/libSceEmbeddedTts/libSceEmbeddedTts.hpp b/core/libs/prx/libSceEmbeddedTts/libSceEmbeddedTts.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceEmbeddedTts/libSceEmbeddedTts.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceEmbeddedTtsCoreG3/libSceEmbeddedTtsCoreG3.hpp b/core/libs/prx/libSceEmbeddedTtsCoreG3/libSceEmbeddedTtsCoreG3.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceEmbeddedTtsCoreG3/libSceEmbeddedTtsCoreG3.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceErrorDialog/Export.cpp b/core/libs/prx/libSceErrorDialog/Export.cpp index d3c922f2b1..5d6ed9d85e 100644 --- a/core/libs/prx/libSceErrorDialog/Export.cpp +++ b/core/libs/prx/libSceErrorDialog/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceErrorDialog/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceErrorDialog/include/Api.hpp b/core/libs/prx/libSceErrorDialog/include/Api.hpp new file mode 100644 index 0000000000..3b12339095 --- /dev/null +++ b/core/libs/prx/libSceErrorDialog/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceErrorDialogInitialize(void); +int APS5_VABI sceErrorDialogOpen(const void* param); +int APS5_VABI sceErrorDialogUpdateStatus(void); +int APS5_VABI sceErrorDialogGetStatus(void); +int APS5_VABI sceErrorDialogClose(void); +int APS5_VABI sceErrorDialogTerminate(void); +int APS5_VABI sceErrorDialogOpenDetail(); +int APS5_VABI sceErrorDialogOpenWithReport(); + +} diff --git a/core/libs/prx/libSceErrorDialog/libSceErrorDialog.hpp b/core/libs/prx/libSceErrorDialog/libSceErrorDialog.hpp new file mode 100644 index 0000000000..9681a99ea5 --- /dev/null +++ b/core/libs/prx/libSceErrorDialog/libSceErrorDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceErrorDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceEsvm/Export.cpp b/core/libs/prx/libSceEsvm/Export.cpp index 54def11467..cbef694c99 100644 --- a/core/libs/prx/libSceEsvm/Export.cpp +++ b/core/libs/prx/libSceEsvm/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceEsvm/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceEsvm/include/Api.hpp b/core/libs/prx/libSceEsvm/include/Api.hpp new file mode 100644 index 0000000000..d11b339d0e --- /dev/null +++ b/core/libs/prx/libSceEsvm/include/Api.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceEsvmAppAudioSyncCreateContext(); +int APS5_VABI sceEsvmAppAudioSyncFlush(); +int APS5_VABI sceEsvmSyncVideo(); + +} diff --git a/core/libs/prx/libSceEsvm/libSceEsvm.hpp b/core/libs/prx/libSceEsvm/libSceEsvm.hpp new file mode 100644 index 0000000000..015c133208 --- /dev/null +++ b/core/libs/prx/libSceEsvm/libSceEsvm.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceEsvm/include/Api.hpp" diff --git a/core/libs/prx/libSceEsvmEngine/Export.cpp b/core/libs/prx/libSceEsvmEngine/Export.cpp index 6667c6812b..1ffc3f6398 100644 --- a/core/libs/prx/libSceEsvmEngine/Export.cpp +++ b/core/libs/prx/libSceEsvmEngine/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceEsvmEngine/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceEsvmEngine/include/Api.hpp b/core/libs/prx/libSceEsvmEngine/include/Api.hpp new file mode 100644 index 0000000000..a99c298234 --- /dev/null +++ b/core/libs/prx/libSceEsvmEngine/include/Api.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceEsvmEngineDestroyMediaKeySystemAccess(); +int APS5_VABI sceEsvmEngineMediaKeySessionClose(); +int APS5_VABI sceEsvmEngineMediaKeySessionGenerateRequest(); +int APS5_VABI sceEsvmEngineMediaKeySessionGetExpiration(); +int APS5_VABI sceEsvmEngineMediaKeySessionGetKeyStatuses(); +int APS5_VABI sceEsvmEngineMediaKeySessionGetSessionId(); +int APS5_VABI sceEsvmEngineMediaKeySessionLoad(); +int APS5_VABI sceEsvmEngineMediaKeySessionRemove(); +int APS5_VABI sceEsvmEngineMediaKeySessionSetEventHandler(); +int APS5_VABI sceEsvmEngineMediaKeySessionUpdate(); +int APS5_VABI sceEsvmEngineMediaKeySystemAccessCreateMediaKeys(); +int APS5_VABI sceEsvmEngineMediaKeySystemAccessDestroyMediaKeys(); +int APS5_VABI sceEsvmEngineMediaKeySystemAccessGetConfiguration(); +int APS5_VABI sceEsvmEngineMediaKeySystemAccessSetCdmStorageConfig(); +int APS5_VABI sceEsvmEngineMediaKeysCreateSession(); +int APS5_VABI sceEsvmEngineMediaKeysDestroySession(); +int APS5_VABI sceEsvmEngineMediaKeysSetEventHandler(); +int APS5_VABI sceEsvmEngineMediaKeysSetServerCertificate(); +int APS5_VABI sceEsvmEngineRequestMediaKeySystemAccess(); + +} diff --git a/core/libs/prx/libSceEsvmEngine/libSceEsvmEngine.hpp b/core/libs/prx/libSceEsvmEngine/libSceEsvmEngine.hpp new file mode 100644 index 0000000000..c8bc3ef97c --- /dev/null +++ b/core/libs/prx/libSceEsvmEngine/libSceEsvmEngine.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceEsvmEngine/include/Api.hpp" diff --git a/core/libs/prx/libSceFiber/CMakeLists.txt b/core/libs/prx/libSceFiber/CMakeLists.txt index 883b7fc154..ef08d950c5 100644 --- a/core/libs/prx/libSceFiber/CMakeLists.txt +++ b/core/libs/prx/libSceFiber/CMakeLists.txt @@ -8,9 +8,11 @@ if(BUILD_TESTING) target_include_directories(fiber_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(fiber_tests PRIVATE libSceFiber libc) add_test(NAME fiber COMMAND fiber_tests) + add_test(NAME fiber_contextless COMMAND fiber_tests --contextless) add_asan_test_options(fiber detect_stack_use_after_return=0) + add_asan_test_options(fiber_contextless detect_stack_use_after_return=0) if(WIN32) - set_tests_properties(fiber PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(fiber fiber_contextless PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceFiber/Export.cpp b/core/libs/prx/libSceFiber/Export.cpp index 81aec33202..616919690c 100644 --- a/core/libs/prx/libSceFiber/Export.cpp +++ b/core/libs/prx/libSceFiber/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFiber/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -36,8 +37,11 @@ static constexpr std::size_t FIBER_MIN_CONTEXT_SIZE = 512; static constexpr std::uint64_t FIBER_CONTEXT_FILL = 0xdeadbeefdeadbeefull; static constexpr std::size_t FIBER_HOST_STACK_MARGIN = 1u << 20; static constexpr std::size_t FIBER_GUEST_STACK_MIN = 64u << 10; - -using GuestFiberEntry = void (APS5_VABI*)(std::uint64_t argOnInitialize, std::uint64_t argOnRun); +static constexpr std::uint32_t FIBER_FLAG_NO_ULOBJMGR = 0x1; +static constexpr std::uint32_t FIBER_FLAG_CONTEXT_SIZE_CHECK = 0x10; +static constexpr std::uint32_t FIBER_FLAG_SET_FPU_REGISTERS = 0x100; +static constexpr std::uint32_t FIBER_DEFAULT_MXCSR = 0x9fc0; +static constexpr std::uint16_t FIBER_DEFAULT_FPU_CONTROL = 0x037f; // A fiber that is switching away stays Suspending until its context is saved; whoever runs next // on that host thread publishes Suspended. Fibers migrate between threads, so a resumer must never @@ -60,6 +64,7 @@ struct Fiber { void* savedStack; char name[FIBER_MAX_NAME_LENGTH + 1]; bool contextSizeCheck; + bool setFpuRegisters; std::uint8_t* hostStack; std::uint64_t hostStackBytes; }; @@ -101,14 +106,17 @@ static bool TraceFibers() { static void CompletePendingSuspend() { auto& thread = ThreadState(); if (thread.pendingSuspend) { - thread.pendingSuspend->state.store(FiberState::Suspended, std::memory_order_release); + auto* fiber = thread.pendingSuspend; + const auto state = fiber->context ? FiberState::Suspended : FiberState::Idle; + if (!fiber->context) fiber->savedStack = nullptr; + fiber->state.store(state, std::memory_order_release); thread.pendingSuspend = nullptr; } } #ifdef _WIN32 -extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void* load); +extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void** load, Fiber* start); extern "C" void Aps5FiberTrampoline_nid_no_patch(); asm(R"( @@ -138,7 +146,12 @@ Aps5FiberSwitchStack_nid_no_patch: stmxcsr 0xa0(%rsp) fnstcw 0xa4(%rsp) mov %rsp, (%rcx) - mov %rdx, %rsp + mov (%rdx), %rsp + test %r8, %r8 + jz 1f + mov %r8, %r12 + jmp Aps5FiberTrampoline_nid_no_patch +1: movaps 0x00(%rsp), %xmm6 movaps 0x10(%rsp), %xmm7 movaps 0x20(%rsp), %xmm8 @@ -209,7 +222,7 @@ static void UnpinStack(const void* context, std::uint64_t bytes) { #else -extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void* load); +extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void** load, Fiber* start); extern "C" void Aps5FiberTrampoline_nid_no_patch(); asm(".text\n" @@ -224,7 +237,12 @@ asm(".text\n" stmxcsr 0(%rsp) fnstcw 4(%rsp) mov %rsp, (%rdi) - mov %rsi, %rsp + mov (%rsi), %rsp + test %rdx, %rdx + jz 1f + mov %rdx, %r12 + jmp )" APS5_ASM_SYMBOL("Aps5FiberTrampoline_nid_no_patch") R"( +1: ldmxcsr 0(%rsp) fldcw 4(%rsp) add $8, %rsp @@ -332,6 +350,7 @@ static std::uint8_t* StackTop(const Fiber* fiber) { } static std::uint64_t* SizedWindow(const Fiber* fiber) { + if (!fiber->context) return nullptr; return reinterpret_cast(StackTop(fiber) - fiber->contextSize); } @@ -357,6 +376,10 @@ static Fiber* AsFiber(FiberObject* object) { extern "C" [[noreturn]] void Aps5FiberMain_nid_no_patch(Fiber* fiber) { CompletePendingSuspend(); + if (!fiber->context && fiber->setFpuRegisters) { + _mm_setcsr(FIBER_DEFAULT_MXCSR); + asm volatile("fldcw %0" : : "m"(FIBER_DEFAULT_FPU_CONTROL)); + } fiber->entry(fiber->argOnInitialize, ThreadState().transfer); throw std::runtime_error(std::string("sceFiber: entry function of fiber '") + fiber->name + "' returned"); } @@ -365,9 +388,13 @@ static void PrepareInitialStack(Fiber* fiber) { const auto top = reinterpret_cast(StackTop(fiber)) & ~static_cast(15); auto* frame = reinterpret_cast(top - 256); std::memset(frame, 0, sizeof(*frame)); - frame->mxcsr = _mm_getcsr(); - std::uint16_t control = 0; - asm volatile("fnstcw %0" : "=m"(control)); + std::uint16_t control = FIBER_DEFAULT_FPU_CONTROL; + if (fiber->setFpuRegisters) { + frame->mxcsr = FIBER_DEFAULT_MXCSR; + } else { + frame->mxcsr = _mm_getcsr(); + asm volatile("fnstcw %0" : "=m"(control)); + } frame->fpuControl = control; frame->r12 = reinterpret_cast(fiber); frame->returnAddress = reinterpret_cast(&Aps5FiberTrampoline_nid_no_patch); @@ -383,15 +410,55 @@ static bool AcquireForResume(Fiber* target) { } if (state != FiberState::Idle && state != FiberState::Suspended) return false; if (target->state.compare_exchange_weak(state, FiberState::Running, std::memory_order_acq_rel)) { - if (state == FiberState::Idle) PrepareInitialStack(target); + if (state == FiberState::Idle && target->context) PrepareInitialStack(target); return true; } } } +static int32_t ValidateContext(const void* addr_context, uint64_t size_context) { + if (addr_context == nullptr && size_context != 0) return SCE_FIBER_ERROR_INVALID; + if ((reinterpret_cast(addr_context) & 15) != 0 || (size_context & 15) != 0) return SCE_FIBER_ERROR_ALIGNMENT; + if (addr_context != nullptr && size_context < FIBER_MIN_CONTEXT_SIZE) return SCE_FIBER_ERROR_RANGE; + return SCE_OK; +} + +static void AttachStack(Fiber* fiber, void* addr_context, uint64_t size_context) { + fiber->context = static_cast(addr_context); + fiber->contextSize = size_context; + if (size_context < FIBER_GUEST_STACK_MIN) AllocateHostStack(fiber); + if (fiber->contextSizeCheck) { + auto* words = SizedWindow(fiber); + std::fill(words, words + size_context / sizeof(std::uint64_t), FIBER_CONTEXT_FILL); + } + PinStack(addr_context, size_context); +} + +static void DetachStack(Fiber* fiber) { + if (!fiber->context) return; + UnpinStack(fiber->context, fiber->contextSize); + ReleaseHostStack(fiber); +} + +static int32_t AcquireWithContext(Fiber* target, void* addr_context, uint64_t size_context) { + if (addr_context == nullptr && size_context == 0) return AcquireForResume(target) ? SCE_OK : SCE_FIBER_ERROR_STATE; + if (addr_context == nullptr) return SCE_FIBER_ERROR_INVALID; + if (const auto valid = ValidateContext(addr_context, size_context); valid != SCE_OK) return valid; + auto expected = FiberState::Idle; + if (!target->state.compare_exchange_strong(expected, FiberState::Running, std::memory_order_acq_rel)) return SCE_FIBER_ERROR_INVALID; + if (target->context) { + target->state.store(FiberState::Idle, std::memory_order_release); + return SCE_FIBER_ERROR_INVALID; + } + AttachStack(target, addr_context, size_context); + if (TraceFibers()) std::fprintf(stderr, "[fiber] attach %s context=%p+0x%llx\n", target->name, addr_context, static_cast(size_context)); + PrepareInitialStack(target); + return SCE_OK; +} + static void Resume(Fiber* target, void** save, std::uint64_t argOnRun) { const auto* frame = static_cast(target->savedStack); - if (frame->returnAddress == 0) { + if (target->context && frame->returnAddress == 0) { std::fprintf(stderr, "[fiber] resuming '%s' with a wiped context: saved=%p context=%p size=0x%llx\n", target->name, target->savedStack, static_cast(target->context), static_cast(target->contextSize)); const auto* words = static_cast(target->savedStack); @@ -401,62 +468,36 @@ static void Resume(Fiber* target, void** save, std::uint64_t argOnRun) { } ThreadState().current = target; ThreadState().transfer = argOnRun; - SetBounds(FiberBounds(target)); - Aps5FiberSwitchStack_nid_no_patch(save, target->savedStack); + SetBounds(target->context ? FiberBounds(target) : ThreadState().threadBounds); + Aps5FiberSwitchStack_nid_no_patch(save, target->context ? &target->savedStack : &ThreadState().threadStack, target->context ? nullptr : target); } -extern "C" { - -int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject* object, const char* name, GuestFiberEntry entry, uint64_t arg_on_initialize, void* addr_context, uint64_t size_context, const void* opt_param, uint32_t build_version) { - (void)opt_param; - (void)build_version; +static int32_t InitializeFiber(FiberObject* object, const char* name, GuestFiberEntry entry, uint64_t arg_on_initialize, void* addr_context, uint64_t size_context, uint32_t flags) { if (!object || !name || !entry) return SCE_FIBER_ERROR_NULL; if ((reinterpret_cast(object) & 7) != 0) return SCE_FIBER_ERROR_ALIGNMENT; - if (addr_context == nullptr && size_context != 0) return SCE_FIBER_ERROR_INVALID; - if ((reinterpret_cast(addr_context) & 15) != 0 || (size_context & 15) != 0) return SCE_FIBER_ERROR_ALIGNMENT; - if (addr_context == nullptr) return SCE_FIBER_ERROR_INVALID; - if (size_context < FIBER_MIN_CONTEXT_SIZE) return SCE_FIBER_ERROR_RANGE; + if (const auto valid = ValidateContext(addr_context, size_context); valid != SCE_OK) return valid; + if ((flags & ~(FIBER_FLAG_NO_ULOBJMGR | FIBER_FLAG_CONTEXT_SIZE_CHECK | FIBER_FLAG_SET_FPU_REGISTERS)) != 0) throw std::runtime_error("_sceFiberInitializeWithInternalOptionImpl: unsupported flags " + std::to_string(flags)); auto* fiber = reinterpret_cast(object); - if (fiber->magic == FIBER_MAGIC) { - UnpinStack(fiber->context, fiber->contextSize); - ReleaseHostStack(fiber); - } + if (fiber->magic == FIBER_MAGIC) DetachStack(fiber); std::memset(object, 0, FIBER_OBJECT_SIZE); fiber->magic = FIBER_MAGIC; fiber->state.store(FiberState::Idle, std::memory_order_relaxed); fiber->entry = entry; fiber->argOnInitialize = arg_on_initialize; - fiber->context = static_cast(addr_context); - fiber->contextSize = size_context; std::strncpy(fiber->name, name, FIBER_MAX_NAME_LENGTH); - if (size_context < FIBER_GUEST_STACK_MIN) AllocateHostStack(fiber); - fiber->contextSizeCheck = g_contextSizeCheck.load(std::memory_order_relaxed); - if (fiber->contextSizeCheck) { - auto* words = SizedWindow(fiber); - std::fill(words, words + size_context / sizeof(std::uint64_t), FIBER_CONTEXT_FILL); - } - PinStack(addr_context, size_context); - if (TraceFibers()) std::fprintf(stderr, "[fiber] init %s object=%p context=%p+0x%llx entry=%p\n", fiber->name, static_cast(object), addr_context, static_cast(size_context), reinterpret_cast(entry)); + fiber->contextSizeCheck = g_contextSizeCheck.load(std::memory_order_relaxed) || (flags & FIBER_FLAG_CONTEXT_SIZE_CHECK) != 0; + fiber->setFpuRegisters = (flags & FIBER_FLAG_SET_FPU_REGISTERS) != 0; + if (addr_context) AttachStack(fiber, addr_context, size_context); + if (TraceFibers()) std::fprintf(stderr, "[fiber] init %s object=%p context=%p+0x%llx entry=%p flags=0x%x\n", fiber->name, static_cast(object), addr_context, static_cast(size_context), reinterpret_cast(entry), flags); return SCE_OK; } -int32_t APS5_VABI sceFiberFinalize(FiberObject* object) { - auto* fiber = AsFiber(object); - if (!fiber) return object ? SCE_FIBER_ERROR_INVALID : SCE_FIBER_ERROR_NULL; - const auto state = fiber->state.load(std::memory_order_acquire); - if (state == FiberState::Running || state == FiberState::Suspending) return SCE_FIBER_ERROR_STATE; - fiber->magic = 0; - UnpinStack(fiber->context, fiber->contextSize); - ReleaseHostStack(fiber); - return SCE_OK; -} - -int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject* object, uint64_t arg_on_run, uint64_t* arg_on_return) { +static int32_t RunFiber(FiberObject* object, void* addr_context, uint64_t size_context, uint64_t arg_on_run, uint64_t* arg_on_return, std::uint64_t threadFramePointer) { auto* fiber = AsFiber(object); if (!fiber) return object ? SCE_FIBER_ERROR_INVALID : SCE_FIBER_ERROR_NULL; if (ThreadState().current) return SCE_FIBER_ERROR_PERMISSION; - if (!AcquireForResume(fiber)) return SCE_FIBER_ERROR_STATE; - ThreadState().threadFramePointer = reinterpret_cast(static_cast(__builtin_frame_address(0))[0]); + if (const auto acquired = AcquireWithContext(fiber, addr_context, size_context); acquired != SCE_OK) return acquired; + ThreadState().threadFramePointer = threadFramePointer; ThreadState().threadBounds = CurrentBounds(); Resume(fiber, &ThreadState().threadStack, arg_on_run); CompletePendingSuspend(); @@ -465,12 +506,13 @@ int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject* object, uint64_t arg_on_r return SCE_OK; } -int32_t APS5_VABI sceFiberSwitch(FiberObject* object, uint64_t arg_on_run, uint64_t* arg_on_run_out) { +static int32_t SwitchFiber(FiberObject* object, void* addr_context, uint64_t size_context, uint64_t arg_on_run, uint64_t* arg_on_run_out) { auto* target = AsFiber(object); if (!target) return object ? SCE_FIBER_ERROR_INVALID : SCE_FIBER_ERROR_NULL; auto* self = ThreadState().current; if (!self) return SCE_FIBER_ERROR_PERMISSION; - if (target == self || !AcquireForResume(target)) return SCE_FIBER_ERROR_STATE; + if (target == self) return SCE_FIBER_ERROR_STATE; + if (const auto acquired = AcquireWithContext(target, addr_context, size_context); acquired != SCE_OK) return acquired; if (TraceFibers()) { auto** frame = static_cast(__builtin_frame_address(0)); void* chain[6] = {}; @@ -489,6 +531,36 @@ int32_t APS5_VABI sceFiberSwitch(FiberObject* object, uint64_t arg_on_run, uint6 return SCE_OK; } +static std::uint64_t CallerFramePointer(void* frame) { + return reinterpret_cast(static_cast(frame)[0]); +} + +extern "C" { + +int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject* object, const char* name, GuestFiberEntry entry, uint64_t arg_on_initialize, void* addr_context, uint64_t size_context, const void* opt_param, uint32_t build_version) { + (void)opt_param; + (void)build_version; + return InitializeFiber(object, name, entry, arg_on_initialize, addr_context, size_context, 0); +} + +int32_t APS5_VABI sceFiberFinalize(FiberObject* object) { + auto* fiber = AsFiber(object); + if (!fiber) return object ? SCE_FIBER_ERROR_INVALID : SCE_FIBER_ERROR_NULL; + const auto state = fiber->state.load(std::memory_order_acquire); + if (state == FiberState::Running || state == FiberState::Suspending) return SCE_FIBER_ERROR_STATE; + fiber->magic = 0; + DetachStack(fiber); + return SCE_OK; +} + +int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject* object, uint64_t arg_on_run, uint64_t* arg_on_return) { + return RunFiber(object, nullptr, 0, arg_on_run, arg_on_return, CallerFramePointer(__builtin_frame_address(0))); +} + +int32_t APS5_VABI sceFiberSwitch(FiberObject* object, uint64_t arg_on_run, uint64_t* arg_on_run_out) { + return SwitchFiber(object, nullptr, 0, arg_on_run, arg_on_run_out); +} + int32_t APS5_VABI sceFiberReturnToThread(uint64_t arg_on_return, uint64_t* arg_on_run) { auto* self = ThreadState().current; if (!self) return SCE_FIBER_ERROR_PERMISSION; @@ -498,7 +570,7 @@ int32_t APS5_VABI sceFiberReturnToThread(uint64_t arg_on_return, uint64_t* arg_o ThreadState().current = nullptr; ThreadState().transfer = arg_on_return; SetBounds(ThreadState().threadBounds); - Aps5FiberSwitchStack_nid_no_patch(&self->savedStack, ThreadState().threadStack); + Aps5FiberSwitchStack_nid_no_patch(&self->savedStack, &ThreadState().threadStack, nullptr); CompletePendingSuspend(); if (arg_on_run) *arg_on_run = ThreadState().transfer; return SCE_OK; @@ -515,13 +587,13 @@ int32_t APS5_VABI sceFiberGetInfo(FiberObject* object, FiberInfo* fiber_info) { auto* fiber = AsFiber(object); if (!fiber || !fiber_info) return object && fiber_info ? SCE_FIBER_ERROR_INVALID : SCE_FIBER_ERROR_NULL; if (fiber_info->size != sizeof(FiberInfo)) return SCE_FIBER_ERROR_INVALID; - fiber_info->entry = reinterpret_cast(fiber->entry); + fiber_info->entry = fiber->entry; fiber_info->arg_on_initialize = fiber->argOnInitialize; fiber_info->addr_context = SizedWindow(fiber); fiber_info->size_context = fiber->contextSize; std::memcpy(fiber_info->name, fiber->name, sizeof(fiber_info->name)); fiber_info->size_context_margin = static_cast(-1); - if (fiber->contextSizeCheck) { + if (fiber->contextSizeCheck && fiber->context) { const auto* words = SizedWindow(fiber); const auto* end = words + fiber->contextSize / sizeof(std::uint64_t); fiber_info->size_context_margin = static_cast(std::find_if(words, end, [](std::uint64_t word) { return word != FIBER_CONTEXT_FILL; }) - words) * sizeof(std::uint64_t); @@ -565,24 +637,18 @@ int32_t APS5_VABI sceFiberStopContextSizeCheck(void) { return g_contextSizeCheck.compare_exchange_strong(expected, false) ? SCE_OK : SCE_FIBER_ERROR_STATE; } -int APS5_VABI _sceFiberAttachContextAndRun(FiberObject* fiber, void* addr_context, uint64_t size_context, - uint64_t arg_on_run, uint64_t* arg_on_return) { - NotImplemented_nid_no_patch(__func__); - return 0; +int32_t APS5_VABI _sceFiberAttachContextAndRun(FiberObject* object, void* addr_context, uint64_t size_context, uint64_t arg_on_run, uint64_t* arg_on_return) { + return RunFiber(object, addr_context, size_context, arg_on_run, arg_on_return, CallerFramePointer(__builtin_frame_address(0))); } -int APS5_VABI _sceFiberAttachContextAndSwitch(FiberObject* fiber, void* addr_context, uint64_t size_context, - uint64_t arg_on_run, uint64_t* arg_on_run_out) { - NotImplemented_nid_no_patch(__func__); - return 0; +int32_t APS5_VABI _sceFiberAttachContextAndSwitch(FiberObject* object, void* addr_context, uint64_t size_context, uint64_t arg_on_run, uint64_t* arg_on_run_out) { + return SwitchFiber(object, addr_context, size_context, arg_on_run, arg_on_run_out); } -int APS5_VABI _sceFiberInitializeWithInternalOptionImpl(FiberObject* fiber, const char* name, GuestFiberEntry entry, - uint64_t arg_on_initialize, void* addr_context, - uint64_t size_context, const FiberOptParam* opt_param, - uint32_t flags, uint32_t build_version) { - NotImplemented_nid_no_patch(__func__); - return 0; +int32_t APS5_VABI _sceFiberInitializeWithInternalOptionImpl(FiberObject* object, const char* name, GuestFiberEntry entry, uint64_t arg_on_initialize, void* addr_context, uint64_t size_context, const FiberOptParam* opt_param, uint32_t flags, uint32_t build_version) { + (void)opt_param; + (void)build_version; + return InitializeFiber(object, name, entry, arg_on_initialize, addr_context, size_context, flags); } } diff --git a/core/libs/prx/libSceFiber/include/Api.hpp b/core/libs/prx/libSceFiber/include/Api.hpp new file mode 100644 index 0000000000..e9cef9744c --- /dev/null +++ b/core/libs/prx/libSceFiber/include/Api.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "SceTypes.hpp" + +using GuestFiberEntry = FiberEntry; + +extern "C" { + +int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject* object, const char* name, GuestFiberEntry entry, uint64_t argOnInitialize, void* addrContext, uint64_t sizeContext, const void* optParam, uint32_t buildVersion); +int32_t APS5_VABI sceFiberFinalize(FiberObject* object); +int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject* object, uint64_t argOnRun, uint64_t* argOnReturn); +int32_t APS5_VABI sceFiberSwitch(FiberObject* object, uint64_t argOnRun, uint64_t* argOnRunOut); +int32_t APS5_VABI sceFiberReturnToThread(uint64_t argOnReturn, uint64_t* argOnRun); +int32_t APS5_VABI sceFiberGetSelf(FiberObject** fiber); +int32_t APS5_VABI sceFiberGetInfo(FiberObject* object, FiberInfo* fiberInfo); +int32_t APS5_VABI sceFiberRename(FiberObject* object, const char* name); +int32_t APS5_VABI sceFiberOptParamInitialize(FiberOptParam* optParam); +int32_t APS5_VABI sceFiberGetThreadFramePointerAddress(uint64_t* addrFramePointer); +int32_t APS5_VABI _sceFiberGetThreadFramePointerAddress(uint64_t* addrFramePointer); +int32_t APS5_VABI sceFiberStartContextSizeCheck(uint32_t flags); +int32_t APS5_VABI sceFiberStopContextSizeCheck(void); +int32_t APS5_VABI _sceFiberAttachContextAndRun(FiberObject* object, void* addrContext, uint64_t sizeContext, uint64_t argOnRun, uint64_t* argOnReturn); +int32_t APS5_VABI _sceFiberAttachContextAndSwitch(FiberObject* object, void* addrContext, uint64_t sizeContext, uint64_t argOnRun, uint64_t* argOnRunOut); +int32_t APS5_VABI _sceFiberInitializeWithInternalOptionImpl(FiberObject* object, const char* name, GuestFiberEntry entry, uint64_t argOnInitialize, void* addrContext, uint64_t sizeContext, const FiberOptParam* optParam, uint32_t flags, uint32_t buildVersion); + +} diff --git a/core/libs/prx/libSceFiber/libSceFiber.hpp b/core/libs/prx/libSceFiber/libSceFiber.hpp new file mode 100644 index 0000000000..1a16c725b6 --- /dev/null +++ b/core/libs/prx/libSceFiber/libSceFiber.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceFiber/include/Api.hpp" diff --git a/core/libs/prx/libSceFios2/Export.cpp b/core/libs/prx/libSceFios2/Export.cpp index eeedc28190..b97176c204 100644 --- a/core/libs/prx/libSceFios2/Export.cpp +++ b/core/libs/prx/libSceFios2/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFios2/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceFios2/include/Api.hpp b/core/libs/prx/libSceFios2/include/Api.hpp new file mode 100644 index 0000000000..354858a01d --- /dev/null +++ b/core/libs/prx/libSceFios2/include/Api.hpp @@ -0,0 +1,176 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceFiosArchiveGetDecompressorThreadCount(); +int APS5_VABI sceFiosArchiveGetMountBufferSize(); +int APS5_VABI sceFiosArchiveGetMountBufferSizeSync(); +int APS5_VABI sceFiosArchiveMount(); +int APS5_VABI sceFiosArchiveMountSync(); +int APS5_VABI sceFiosArchiveMountWithOrder(); +int APS5_VABI sceFiosArchiveMountWithOrderSync(); +int APS5_VABI sceFiosArchiveSetDecompressorThreadCount(); +int APS5_VABI sceFiosArchiveUnmount(); +int APS5_VABI sceFiosArchiveUnmountSync(); +int APS5_VABI sceFiosCacheContainsFileRangeSync(); +int APS5_VABI sceFiosCacheContainsFileSync(); +int APS5_VABI sceFiosCacheFlushFileRangeSync(); +int APS5_VABI sceFiosCacheFlushFileSync(); +int APS5_VABI sceFiosCacheFlushSync(); +int APS5_VABI sceFiosCachePrefetchFH(); +int APS5_VABI sceFiosCachePrefetchFHRange(); +int APS5_VABI sceFiosCachePrefetchFHRangeSync(); +int APS5_VABI sceFiosCachePrefetchFHSync(); +int APS5_VABI sceFiosCachePrefetchFile(); +int APS5_VABI sceFiosCachePrefetchFileRange(); +int APS5_VABI sceFiosCachePrefetchFileRangeSync(); +int APS5_VABI sceFiosCachePrefetchFileSync(); +int APS5_VABI sceFiosCancelAllOps(); +int APS5_VABI sceFiosClearTimeStamps(); +int APS5_VABI sceFiosCloseAllFiles(); +int APS5_VABI sceFiosDHClose(); +int APS5_VABI sceFiosDHCloseSync(); +int APS5_VABI sceFiosDHGetPath(); +int APS5_VABI sceFiosDHOpen(); +int APS5_VABI sceFiosDHOpenSync(); +int APS5_VABI sceFiosDHRead(); +int APS5_VABI sceFiosDHReadSync(); +int APS5_VABI sceFiosDLLInitialize(); +int APS5_VABI sceFiosDLLTerminate(); +int APS5_VABI sceFiosDateFromComponents(); +int APS5_VABI sceFiosDateGetCurrent(); +int APS5_VABI sceFiosDateToComponents(); +int APS5_VABI sceFiosDeallocatePassthruFH(); +int APS5_VABI sceFiosDebugDumpDH(); +int APS5_VABI sceFiosDebugDumpDate(); +int APS5_VABI sceFiosDebugDumpError(); +int APS5_VABI sceFiosDebugDumpFH(); +int APS5_VABI sceFiosDebugDumpOp(); +int APS5_VABI sceFiosDelete(); +int APS5_VABI sceFiosDeleteSync(); +int APS5_VABI sceFiosDirectoryCreate(); +int APS5_VABI sceFiosDirectoryCreateSync(); +int APS5_VABI sceFiosDirectoryCreateWithMode(); +int APS5_VABI sceFiosDirectoryCreateWithModeSync(); +int APS5_VABI sceFiosDirectoryDelete(); +int APS5_VABI sceFiosDirectoryDeleteSync(); +int APS5_VABI sceFiosDirectoryExists(); +int APS5_VABI sceFiosDirectoryExistsSync(); +int APS5_VABI sceFiosExists(); +int APS5_VABI sceFiosExistsSync(); +int APS5_VABI sceFiosFHClose(); +int APS5_VABI sceFiosFHCloseSync(); +int APS5_VABI sceFiosFHGetOpenParams(); +int APS5_VABI sceFiosFHGetPath(); +int APS5_VABI sceFiosFHGetSize(); +int APS5_VABI sceFiosFHOpen(); +int APS5_VABI sceFiosFHOpenSync(); +int APS5_VABI sceFiosFHOpenWithMode(); +int APS5_VABI sceFiosFHOpenWithModeSync(); +int APS5_VABI sceFiosFHPread(); +int APS5_VABI sceFiosFHPreadSync(); +int APS5_VABI sceFiosFHPreadv(); +int APS5_VABI sceFiosFHPreadvSync(); +int APS5_VABI sceFiosFHPwrite(); +int APS5_VABI sceFiosFHPwriteSync(); +int APS5_VABI sceFiosFHPwritev(); +int APS5_VABI sceFiosFHPwritevSync(); +int APS5_VABI sceFiosFHRead(); +int APS5_VABI sceFiosFHReadSync(); +int APS5_VABI sceFiosFHReadv(); +int APS5_VABI sceFiosFHReadvSync(); +int APS5_VABI sceFiosFHSeek(); +int APS5_VABI sceFiosFHStat(); +int APS5_VABI sceFiosFHStatSync(); +int APS5_VABI sceFiosFHSync(); +int APS5_VABI sceFiosFHSyncSync(); +int APS5_VABI sceFiosFHTell(); +int APS5_VABI sceFiosFHToFileno(); +int APS5_VABI sceFiosFHTruncate(); +int APS5_VABI sceFiosFHTruncateSync(); +int APS5_VABI sceFiosFHWrite(); +int APS5_VABI sceFiosFHWriteSync(); +int APS5_VABI sceFiosFHWritev(); +int APS5_VABI sceFiosFHWritevSync(); +int APS5_VABI sceFiosFileDelete(); +int APS5_VABI sceFiosFileDeleteSync(); +int APS5_VABI sceFiosFileExists(); +int APS5_VABI sceFiosFileExistsSync(); +int APS5_VABI sceFiosFileGetSize(); +int APS5_VABI sceFiosFileGetSizeSync(); +int APS5_VABI sceFiosFileRead(); +int APS5_VABI sceFiosFileReadSync(); +int APS5_VABI sceFiosFileTruncate(); +int APS5_VABI sceFiosFileTruncateSync(); +int APS5_VABI sceFiosFileWrite(); +int APS5_VABI sceFiosFileWriteSync(); +int APS5_VABI sceFiosFilenoToFH(); +int APS5_VABI sceFiosGetAllDHs(); +int APS5_VABI sceFiosGetAllFHs(); +int APS5_VABI sceFiosGetAllOps(); +int APS5_VABI sceFiosGetDefaultOpAttr(); +int APS5_VABI sceFiosGetGlobalDefaultOpAttr(); +int APS5_VABI sceFiosGetSuspendCount(); +int APS5_VABI sceFiosGetThreadDefaultOpAttr(); +int APS5_VABI sceFiosIOFilterAdd(); +int APS5_VABI sceFiosIOFilterCache(); +int APS5_VABI sceFiosIOFilterGetInfo(); +int APS5_VABI sceFiosIOFilterPsarcDearchiver(); +int APS5_VABI sceFiosIOFilterRemove(); +int APS5_VABI sceFiosInitialize(); +int APS5_VABI sceFiosIsIdle(); +int APS5_VABI sceFiosIsInitialized(); +int APS5_VABI sceFiosIsSuspended(); +int APS5_VABI sceFiosIsValidHandle(); +int APS5_VABI sceFiosOpCancel(); +int APS5_VABI sceFiosOpDelete(); +int APS5_VABI sceFiosOpGetActualCount(); +int APS5_VABI sceFiosOpGetAttr(); +int APS5_VABI sceFiosOpGetBuffer(); +int APS5_VABI sceFiosOpGetError(); +int APS5_VABI sceFiosOpGetOffset(); +int APS5_VABI sceFiosOpGetPath(); +int APS5_VABI sceFiosOpGetRequestCount(); +int APS5_VABI sceFiosOpIsCancelled(); +int APS5_VABI sceFiosOpIsDone(); +int APS5_VABI sceFiosOpReschedule(); +int APS5_VABI sceFiosOpRescheduleWithPriority(); +int APS5_VABI sceFiosOpSetBuffer(); +int APS5_VABI sceFiosOpSyncWait(); +int APS5_VABI sceFiosOpSyncWaitForIO(); +int APS5_VABI sceFiosOpWait(); +int APS5_VABI sceFiosOpWaitUntil(); +int APS5_VABI sceFiosOverlayAdd(); +int APS5_VABI sceFiosOverlayGetInfo(); +int APS5_VABI sceFiosOverlayGetList(); +int APS5_VABI sceFiosOverlayModify(); +int APS5_VABI sceFiosOverlayRemove(); +int APS5_VABI sceFiosOverlayResolveSync(); +int APS5_VABI sceFiosPathNormalize(); +int APS5_VABI sceFiosPathcmp(); +int APS5_VABI sceFiosPathncmp(); +int APS5_VABI sceFiosPrintTimeStamps(); +int APS5_VABI sceFiosPrintf(); +int APS5_VABI sceFiosRename(); +int APS5_VABI sceFiosRenameSync(); +int APS5_VABI sceFiosResolve(); +int APS5_VABI sceFiosResolveSync(); +int APS5_VABI sceFiosResume(); +int APS5_VABI sceFiosSaveTimeStamp(); +int APS5_VABI sceFiosSetGlobalDefaultOpAttr(); +int APS5_VABI sceFiosSetThreadDefaultOpAttr(); +int APS5_VABI sceFiosShutdownAndCancelOps(); +int APS5_VABI sceFiosStat(); +int APS5_VABI sceFiosStatSync(); +int APS5_VABI sceFiosSuspend(); +int APS5_VABI sceFiosTerminate(); +int APS5_VABI sceFiosTimeGetCurrent(); +int APS5_VABI sceFiosTimeIntervalFromNanoseconds(); +int APS5_VABI sceFiosTimeIntervalToNanoseconds(); +int APS5_VABI sceFiosTraceTimestamp(); +int APS5_VABI sceFiosUpdateParameters(); +int APS5_VABI sceFiosVprintf(); + +} diff --git a/core/libs/prx/libSceFios2/libSceFios2.hpp b/core/libs/prx/libSceFios2/libSceFios2.hpp new file mode 100644 index 0000000000..5f164953f7 --- /dev/null +++ b/core/libs/prx/libSceFios2/libSceFios2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceFios2/include/Api.hpp" diff --git a/core/libs/prx/libSceFont-module/libSceFont-module.hpp b/core/libs/prx/libSceFont-module/libSceFont-module.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceFont-module/libSceFont-module.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceFont.native/libSceFont.native.hpp b/core/libs/prx/libSceFont.native/libSceFont.native.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceFont.native/libSceFont.native.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceFont/CMakeLists.txt b/core/libs/prx/libSceFont/CMakeLists.txt index cec16f879b..949896a3b0 100644 --- a/core/libs/prx/libSceFont/CMakeLists.txt +++ b/core/libs/prx/libSceFont/CMakeLists.txt @@ -16,6 +16,33 @@ if(BUILD_TESTING) set_tests_properties(guest_font guest_system_font PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_font_metrics_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontMetrics.cpp) + target_include_directories(guest_font_metrics_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_font_metrics_tests PRIVATE libSceFont libSceFontFt libc) + configure_windows_unwind(guest_font_metrics_tests) + add_test(NAME guest_font_metrics COMMAND guest_font_metrics_tests) + if(WIN32) + set_tests_properties(guest_font_metrics PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_font_style_information_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontStyleInformation.cpp) + target_include_directories(guest_font_style_information_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_font_style_information_tests PRIVATE libSceFont libSceFontFt libc) + configure_windows_unwind(guest_font_style_information_tests) + add_test(NAME guest_font_style_information COMMAND guest_font_style_information_tests) + if(WIN32) + set_tests_properties(guest_font_style_information PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_font_outline_profile_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontOutlineProfile.cpp) + target_include_directories(guest_font_outline_profile_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_font_outline_profile_tests PRIVATE libSceFont libSceFontFt libc) + configure_windows_unwind(guest_font_outline_profile_tests) + add_test(NAME guest_font_outline_profile COMMAND guest_font_outline_profile_tests) + if(WIN32) + set_tests_properties(guest_font_outline_profile PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_font_writing_line_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontWritingLine.cpp) target_include_directories(guest_font_writing_line_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_font_writing_line_tests PRIVATE libSceFont libc) @@ -25,6 +52,15 @@ if(BUILD_TESTING) set_tests_properties(guest_font_writing_line PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_font_graphics_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontGraphics.cpp) + target_include_directories(guest_font_graphics_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_font_graphics_tests PRIVATE libSceFont libc) + configure_windows_unwind(guest_font_graphics_tests) + add_test(NAME guest_font_graphics COMMAND guest_font_graphics_tests) + if(WIN32) + set_tests_properties(guest_font_graphics PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(font_face_lifetime_tests ${LIBS_INCLUDE_DIR}/tests/FontFaceLifetime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/State.cpp) target_include_directories(font_face_lifetime_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libSceFont/FontLibrary.cmake b/core/libs/prx/libSceFont/FontLibrary.cmake index 47bf723165..975d86432e 100644 --- a/core/libs/prx/libSceFont/FontLibrary.cmake +++ b/core/libs/prx/libSceFont/FontLibrary.cmake @@ -1,6 +1,7 @@ function(add_sce_font_library target) set(fontDir ${CMAKE_CURRENT_FUNCTION_LIST_DIR}) add_library(${target} SHARED EXCLUDE_FROM_ALL + ${fontDir}/src/Graphics.cpp ${fontDir}/src/Layout.cpp ${fontDir}/src/Library.cpp ${fontDir}/src/Open.cpp diff --git a/core/libs/prx/libSceFont/include/Api.hpp b/core/libs/prx/libSceFont/include/Api.hpp new file mode 100644 index 0000000000..2e7d3d5ca6 --- /dev/null +++ b/core/libs/prx/libSceFont/include/Api.hpp @@ -0,0 +1,336 @@ +#pragma once + +#include "prx/libSceFont/include/FontTypes.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct FontGraphicsDevice; + +struct FontGraphicsDeviceParam; + +struct FontGraphicsService; + +struct FontGraphicsServiceParam; + +struct FontWords; + +struct FontGlyphSupport; + +struct FontOutlineProfile; + +struct FontMetrics; + +struct FontStyleInformation; + +struct FontGlyphExpandBufferState; + +struct FontGraphicsResource; + +struct FontGraphicsFrame; + +struct FontGraphicsDesignResource; + +struct FontGraphicsDesign; + +struct FontGraphicsCanvas; + +struct FontGraphicsCanvasSequence; + +struct FontGraphicsSurface; + +struct FontGraphicsSurfaceTexture; + +struct FontGraphicsFill; + +struct FontGraphicsRegion { + float x; + float y; + float width; + float height; + std::uint32_t flags; + std::uint32_t tag; + float outerRadius; + float innerRadius; +}; + +struct FontGraphicsFillMethod { + std::int32_t mode; + std::int32_t type; + std::uint32_t flags; + std::uint32_t tag; + std::byte reserved[16]; +}; + +struct FontGraphicsFillPlot { + float mapping[4]; + const FontGraphicsRegion* region; + std::uint16_t tag; + std::uint16_t reserved26; + std::uint16_t layout; + std::uint8_t flags; + std::byte reserved[33]; +}; + +struct FontGraphicsFillRates { + float mapping[4]; + const FontGraphicsFill* fill; + std::uint16_t tag; + std::uint16_t effect; + std::uint16_t layout; + std::uint8_t flags; + std::byte reserved[33]; +}; + +struct FontGraphicsPoolUsage { + std::uint32_t capacity; + std::uint32_t used; + std::uint32_t available; + std::uint32_t peak; + std::int32_t change; +}; + +struct FontGraphicsMemoryUsage { + FontGraphicsPoolUsage storage; + FontGraphicsPoolUsage allocations; + void* address; +}; + +struct FontGraphicsDeviceUsage { + FontGraphicsMemoryUsage first; + FontGraphicsMemoryUsage second; +}; + +namespace Font { + +using GraphicsErrorCallback = void (APS5_VABI*)(int error); + +} + +extern "C" { + +int APS5_VABI sceFontMemoryInit(FontMemory* memory, void* regionAddress, std::uint32_t regionSize, const FontMemoryInterface* iface, void* mspaceObject, FontMemoryDestroyFunction destroyCallback, void* destroyObject); +int APS5_VABI sceFontMemoryTerm(FontMemory* memory); +int APS5_VABI sceFontCreateLibraryWithEdition(const FontMemory* memory, const void* createParams, std::uint64_t edition, FontLibrary* pLibrary); +int APS5_VABI sceFontCreateLibrary(const FontMemory* memory, const void* createParams, FontLibrary* pLibrary); +int APS5_VABI sceFontDestroyLibrary(FontLibrary* pLibrary); +int APS5_VABI sceFontCreateRendererWithEdition(const FontMemory* memory, const void* createParams, std::uint64_t edition, FontRenderer* pRenderer); +int APS5_VABI sceFontCreateRenderer(const FontMemory* memory, const void* createParams, FontRenderer* pRenderer); +int APS5_VABI sceFontDestroyRenderer(FontRenderer* pRenderer); +int APS5_VABI sceFontRendererGetOutlineBufferSize(FontRenderer fontRenderer, std::uint32_t* size); +int APS5_VABI sceFontRendererResetOutlineBuffer(FontRenderer fontRenderer); +int APS5_VABI sceFontRendererSetOutlineBufferPolicy(FontRenderer fontRenderer, std::uint64_t bufferPolicy, std::uint32_t basalSize, std::uint32_t limitSize); +int APS5_VABI sceFontGetPixelResolution(FontLibrary library, std::uint32_t* subPixelCount); +int APS5_VABI sceFontAttachDeviceCacheBuffer(FontLibrary library, void* buffer, std::uint32_t size); +int APS5_VABI sceFontClearDeviceCache(FontLibrary library); +int APS5_VABI sceFontDettachDeviceCacheBuffer(FontLibrary library, void** buffer, std::uint32_t* size); +int APS5_VABI sceFontSupportExternalFonts(FontLibrary library, std::uint32_t fontMax, std::uint32_t formats); +int APS5_VABI sceFontSupportSystemFonts(FontLibrary library); +void APS5_VABI sceFontSetGraphicsErrorCallback(Font::GraphicsErrorCallback callback); +int APS5_VABI sceFontSetFontsOpenMode(FontLibrary library, std::uint32_t openMode); +int APS5_VABI sceFontOpenFontMemory(FontLibrary library, const void* fontAddress, std::uint32_t fontSize, const FontOpenDetail* openDetail, FontHandle* pFontHandle); +int APS5_VABI sceFontOpenFontFile(FontLibrary library, const char* path, std::uint32_t openMode, const FontOpenDetail* openDetail, FontHandle* pFontHandle); +int APS5_VABI sceFontOpenFontInstance(FontHandle fontHandle, FontHandle setupFont, FontHandle* pFontHandle); +int APS5_VABI sceFontOpenFontSet(FontLibrary library, std::uint32_t fontSetType, std::uint32_t openMode, const FontOpenDetail* openDetail, FontHandle* pFontHandle); +int APS5_VABI sceFontCloseFont(FontHandle fontHandle); +int APS5_VABI sceFontGetLibrary(FontHandle fontHandle, FontLibrary* pLibrary); +int APS5_VABI sceFontGetFontGlyphsCount(FontHandle fontHandle, std::uint32_t* glyphsCount); +int APS5_VABI sceFontGetCharGlyphCode(FontHandle fontHandle, std::uint32_t code, std::uint32_t* glyphCode); +int APS5_VABI sceFontGetFontResolution(FontHandle fontHandle, std::uint32_t* pResolution, float* pScalePixel); +int APS5_VABI sceFontBindRenderer(FontHandle fontHandle, FontRenderer renderer); +int APS5_VABI sceFontRebindRenderer(FontHandle fontHandle); +int APS5_VABI sceFontUnbindRenderer(FontHandle fontHandle); +int APS5_VABI sceFontGetCharGlyphMetrics(FontHandle fontHandle, std::uint32_t code, FontGlyphMetrics* metrics); +int APS5_VABI sceFontGetRenderCharGlyphMetrics(FontHandle fontHandle, std::uint32_t code, FontGlyphMetrics* metrics); +int APS5_VABI sceFontGetHorizontalLayout(FontHandle fontHandle, FontHorizontalLayout* layout); +int APS5_VABI sceFontGetVerticalLayout(FontHandle fontHandle, FontVerticalLayout* layout); +int APS5_VABI sceFontGetKerning(FontHandle fontHandle, std::uint32_t preCode, std::uint32_t code, FontKerning* kerning); +int APS5_VABI sceFontGetRenderScaledKerning(FontHandle fontHandle, std::uint32_t preCode, std::uint32_t code, FontKerning* kerning); +int APS5_VABI sceFontRenderCharGlyphImage(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result); +int APS5_VABI sceFontRenderCharGlyphImageHorizontal(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result); +int APS5_VABI sceFontRenderCharGlyphImageVertical(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result); +void APS5_VABI sceFontRenderSurfaceInit(FontRenderSurface* renderSurface, void* buffer, int bufWidthByte, int pixelSizeByte, int widthPixel, int heightPixel); +void APS5_VABI sceFontRenderSurfaceSetScissor(FontRenderSurface* renderSurface, int x0, int y0, int w, int h); +int APS5_VABI sceFontRenderSurfaceSetStyleFrame(FontRenderSurface* renderSurface, FontStyleFrame* styleFrame); +int APS5_VABI sceFontGenerateCharGlyph(FontHandle fontHandle, std::uint32_t code, const FontGenerateGlyphDetail* detail, FontGlyph* pGlyph); +int APS5_VABI sceFontDeleteGlyph(const FontMemory* memory, FontGlyph* pGlyph); +int APS5_VABI sceFontGlyphDefineAttribute(FontGlyph glyph, std::int32_t attribute, std::int32_t* previousAttribute); +int APS5_VABI sceFontGlyphGetAttribute(FontGlyph glyph, int attribute, int* nowAttribute); +int APS5_VABI sceFontGlyphGetGlyphForm(FontGlyph glyph); +int APS5_VABI sceFontGlyphGetMetricsForm(FontGlyph glyph); +int APS5_VABI sceFontGlyphGetScalePixel(FontGlyph glyph, float* w, float* h); +const FontGlyphMetrics* APS5_VABI sceFontGlyphRefersMetrics(FontGlyph glyph); +const FontGlyphMetricsHorizontal* APS5_VABI sceFontGlyphRefersMetricsHorizontal(FontGlyph glyph); +const FontGlyphMetricsHorizontalAdvance* APS5_VABI sceFontGlyphRefersMetricsHorizontalAdvance(FontGlyph glyph); +const FontGlyphMetricsHorizontalX* APS5_VABI sceFontGlyphRefersMetricsHorizontalX(FontGlyph glyph); +FontGlyphOutline* APS5_VABI sceFontGlyphRefersOutline(FontGlyph glyph); +int APS5_VABI sceFontDefineAttribute(FontHandle fontHandle, int attribute, int* oldAttribute); +int APS5_VABI sceFontGetAttribute(FontHandle fontHandle, int attribute, int* nowAttribute); +int APS5_VABI sceFontSetScriptLanguage(FontHandle fontHandle, int fontScript, int fontLanguage); +int APS5_VABI sceFontGetScriptLanguage(FontHandle fontHandle, int fontScript, int* fontLanguage); +int APS5_VABI sceFontSetTypographicDesign(FontHandle fontHandle, int typographic, int feature); +int APS5_VABI sceFontGetTypographicDesign(FontHandle fontHandle, int typographic, int* feature); +int APS5_VABI sceFontSetScalePixel(FontHandle fontHandle, float w, float h); +int APS5_VABI sceFontSetScalePoint(FontHandle fontHandle, float w, float h); +int APS5_VABI sceFontSetResolutionDpi(FontHandle fontHandle, std::uint32_t hDpi, std::uint32_t vDpi); +int APS5_VABI sceFontSetEffectSlant(FontHandle fontHandle, float slantRatio); +int APS5_VABI sceFontSetEffectWeight(FontHandle fontHandle, float weightXScale, float weightYScale, std::uint32_t mode); +int APS5_VABI sceFontGetScalePixel(FontHandle fontHandle, float* w, float* h); +int APS5_VABI sceFontGetScalePoint(FontHandle fontHandle, float* w, float* h); +int APS5_VABI sceFontGetResolutionDpi(FontHandle fontHandle, std::uint32_t* hDpi, std::uint32_t* vDpi); +int APS5_VABI sceFontGetEffectSlant(FontHandle fontHandle, float* slantRatio); +int APS5_VABI sceFontGetEffectWeight(FontHandle fontHandle, float* weightXScale, float* weightYScale, std::uint32_t* mode); +int APS5_VABI sceFontSetupRenderScalePixel(FontHandle fontHandle, float w, float h); +int APS5_VABI sceFontSetupRenderScalePoint(FontHandle fontHandle, float w, float h); +int APS5_VABI sceFontSetupRenderEffectSlant(FontHandle fontHandle, float slantRatio); +int APS5_VABI sceFontSetupRenderEffectWeight(FontHandle fontHandle, float weightXScale, float weightYScale, std::uint32_t mode); +int APS5_VABI sceFontGetRenderScalePixel(FontHandle fontHandle, float* w, float* h); +int APS5_VABI sceFontGetRenderScalePoint(FontHandle fontHandle, float* w, float* h); +int APS5_VABI sceFontGetRenderEffectSlant(FontHandle fontHandle, float* slantRatio); +int APS5_VABI sceFontGetRenderEffectWeight(FontHandle fontHandle, float* weightXScale, float* weightYScale, std::uint32_t* mode); +int APS5_VABI sceFontStyleFrameInit(FontStyleFrame* styleFrame); +int APS5_VABI sceFontStyleFrameSetScalePixel(FontStyleFrame* styleFrame, float w, float h); +int APS5_VABI sceFontStyleFrameSetScalePoint(FontStyleFrame* styleFrame, float w, float h); +int APS5_VABI sceFontStyleFrameSetResolutionDpi(FontStyleFrame* styleFrame, std::uint32_t hDpi, std::uint32_t vDpi); +int APS5_VABI sceFontStyleFrameSetEffectSlant(FontStyleFrame* styleFrame, float slantRatio); +int APS5_VABI sceFontStyleFrameSetEffectWeight(FontStyleFrame* styleFrame, float weightXScale, float weightYScale, std::uint32_t mode); +int APS5_VABI sceFontStyleFrameUnsetScale(FontStyleFrame* styleFrame); +int APS5_VABI sceFontStyleFrameUnsetEffectSlant(FontStyleFrame* styleFrame); +int APS5_VABI sceFontStyleFrameUnsetEffectWeight(FontStyleFrame* styleFrame); +int APS5_VABI sceFontStyleFrameGetScalePixel(const FontStyleFrame* styleFrame, float* w, float* h); +int APS5_VABI sceFontStyleFrameGetScalePoint(const FontStyleFrame* styleFrame, float* w, float* h); +int APS5_VABI sceFontStyleFrameGetResolutionDpi(const FontStyleFrame* styleFrame, std::uint32_t* hDpi, std::uint32_t* vDpi); +int APS5_VABI sceFontStyleFrameGetEffectSlant(const FontStyleFrame* styleFrame, float* slantRatio); +int APS5_VABI sceFontStyleFrameGetEffectWeight(const FontStyleFrame* styleFrame, float* weightXScale, float* weightYScale, std::uint32_t* mode); +int APS5_VABI sceFontTextSourceInit(FontTextSource* textSource, const void* textAddress, std::uint32_t textSizeByte, FontTextParseFunction textParser, void* textObject); +int APS5_VABI sceFontTextSourceRewind(FontTextSource* textSource); +int APS5_VABI sceFontTextSourceSetDefaultFont(FontTextSource* textSource, FontHandle defaultFont); +int APS5_VABI sceFontTextSourceSetWritingForm(FontTextSource* textSource, std::int32_t writingForm); +int APS5_VABI sceFontCreateString(const FontMemory* fontMemory, FontTextSource* textSource, const FontCreateStringDetail* detail, FontString* pFontString); +int APS5_VABI sceFontDestroyString(FontString* pFontString); +std::uint32_t APS5_VABI sceFontStringGetTerminateCode(FontString fontString); +void* APS5_VABI sceFontStringGetTerminateOrder(FontString fontString); +int APS5_VABI sceFontStringGetWritingForm(FontString fontString); +FontTextCharacter* APS5_VABI sceFontStringRefersTextCharacters(FontString fontString, std::uint32_t* characterCount); +const FontTextCharacter* APS5_VABI sceFontStringRefersRenderCharacters(FontString fontString, FontTextCharacter* startCharacter, FontTextCharacter* lastCharacter, std::uint32_t* characterCount); +int APS5_VABI sceFontCharacterGetBidiLevel(const FontTextCharacter* textCharacter, int* bidiLevel); +int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter* textCharacter, int* syllableStringState); +int APS5_VABI sceFontCharacterGetTextFontCode(const FontTextCharacter* textCharacter, FontHandle* pFontHandle, std::uint32_t* textCode); +int APS5_VABI sceFontCharacterGetTextOrder(const FontTextCharacter* textCharacter, void** pTextOrder); +std::uint32_t APS5_VABI sceFontCharacterLooksFormatCharacters(const FontTextCharacter* textCharacter); +std::uint32_t APS5_VABI sceFontCharacterLooksWhiteSpace(const FontTextCharacter* textCharacter); +FontTextCharacter* APS5_VABI sceFontCharacterRefersTextBack(const FontTextCharacter* textCharacter); +FontTextCharacter* APS5_VABI sceFontCharacterRefersTextNext(const FontTextCharacter* textCharacter); +FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(const FontTextCharacter* textCharacter, const FontTextCharacter* termCharacter, FontTextCodes* textCodes); +FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes* textCodesStep); +FontTextCodes* APS5_VABI sceFontTextCodesStepBack(FontTextCodes* textCodesStep); +int APS5_VABI sceFontWritingInit(FontWriting* fontWriting, FontString fontString, const FontTextCharacter* fontCharacter); +int APS5_VABI sceFontWritingGetRenderMetrics(FontWriting* fontWriting, FontWritingMetrics* writingMetrics); +const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* fontWriting); +FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting* fontWriting, const FontWritingLetterStep** pLetterStep); +int APS5_VABI sceFontWritingSetMaskInvisible(FontWriting* fontWriting, std::int32_t mask); +int APS5_VABI sceFontControl(std::int32_t command, void* object, std::uintptr_t argument); +int APS5_VABI sceFontCreateGraphicsDevice(const FontMemory* memory, const FontGraphicsDeviceParam* param, FontGraphicsDevice** device); +int APS5_VABI sceFontCreateGraphicsService(const FontMemory* memory, const FontGraphicsServiceParam* param, FontGraphicsService** service); +int APS5_VABI sceFontCreateGraphicsServiceWithEdition(const FontMemory* memory, const FontGraphicsServiceParam* param, std::uint64_t unusedSlot, FontGraphicsService** service); +int APS5_VABI sceFontCreateWords(const FontMemory* memory, FontTextSource* source, std::uint64_t unusedSlot, FontWords** words); +int APS5_VABI sceFontDestroyGraphicsDevice(FontGraphicsDevice** device); +int APS5_VABI sceFontDestroyGraphicsService(FontGraphicsService** service); +int APS5_VABI sceFontDestroyWords(FontWords** words); +int APS5_VABI sceFontGetFontGlyphsOutlineProfile(FontHandle font, FontOutlineProfile* profile); +int APS5_VABI sceFontGetFontMetrics(FontHandle font, std::uint32_t mode, FontMetrics* metrics); +int APS5_VABI sceFontGetFontStyleInformation(FontHandle font, FontStyleInformation* information); +int APS5_VABI sceFontGetGlyphExpandBufferState(FontHandle font, FontGlyphExpandBufferState* state); +int APS5_VABI sceFontGlyphRenderImage(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output); +int APS5_VABI sceFontGlyphRenderImageHorizontal(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output); +int APS5_VABI sceFontGlyphRenderImageVertical(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output); +int APS5_VABI sceFontGraphicsBeginFrame(FontGraphicsFrame* frame, std::uint64_t flags, FontGraphicsResource** resource); +int APS5_VABI sceFontGraphicsDrawingCancel(FontGraphicsFrame** frame); +int APS5_VABI sceFontGraphicsDrawingFinish(FontGraphicsFrame** frame, FontGraphicsDevice* device); +int APS5_VABI sceFontGraphicsEndFrame(FontGraphicsFrame* frame); +int APS5_VABI sceFontGraphicsExchangeResource(FontGraphicsResource* resource, FontGraphicsDesignResource* replacement, FontGraphicsDesignResource** previous); +int APS5_VABI sceFontGraphicsFillMethodInit(FontGraphicsFillMethod* method, std::int32_t mode, std::int32_t type); +int APS5_VABI sceFontGraphicsFillPlotInit(FontGraphicsFillPlot* plot, const FontGraphicsRegion* region); +int APS5_VABI sceFontGraphicsFillPlotSetLayout(FontGraphicsFillPlot* plot, std::int32_t first, std::int32_t second); +int APS5_VABI sceFontGraphicsFillPlotSetMapping(FontGraphicsFillPlot* plot, float first, float second, float third, float fourth); +int APS5_VABI sceFontGraphicsFillRatesInit(FontGraphicsFillRates* rates, const FontGraphicsFill* fill, std::uint32_t effect); +int APS5_VABI sceFontGraphicsFillRatesSetFillEffect(FontGraphicsFillRates* rates, const FontGraphicsFill* fill, std::uint32_t effect); +int APS5_VABI sceFontGraphicsFillRatesSetLayout(FontGraphicsFillRates* rates, std::int32_t first, std::int32_t second); +int APS5_VABI sceFontGraphicsFillRatesSetMapping(FontGraphicsFillRates* rates, float first, float second, float third, float fourth); +int APS5_VABI sceFontGraphicsGetDeviceUsage(FontGraphicsDevice* device, FontGraphicsDeviceUsage* usage); +int APS5_VABI sceFontGraphicsRegionInit(FontGraphicsRegion* region, float x, float y, float width, float height); +int APS5_VABI sceFontGraphicsRegionInitCircular(FontGraphicsRegion* region, float centerX, float centerY, float outerRadius, float innerRadius); +int APS5_VABI sceFontGraphicsRegionInitRoundish(FontGraphicsRegion* region, float x, float y, float width, float height, float outerRadius, float innerRadius, std::uint32_t flags); +int APS5_VABI sceFontGraphicsRelease(void** object); +int APS5_VABI sceFontGraphicsRenderResource(FontGraphicsFrame* frame, FontGraphicsDesignResource* design, float x, float y, FontGraphicsResource** resource); +int APS5_VABI sceFontGraphicsSetFramePolicy(FontGraphicsResource* resource, std::int32_t policy, std::uint32_t value); +int APS5_VABI sceFontGraphicsSetupClipping(FontGraphicsResource* resource, const FontGraphicsRegion* region); +int APS5_VABI sceFontGraphicsSetupColorRates(FontGraphicsResource* resource, float red, float green, float blue, float alpha); +int APS5_VABI sceFontGraphicsSetupFillMethod(FontGraphicsResource* resource, const FontGraphicsFillMethod* method); +int APS5_VABI sceFontGraphicsSetupFillRates(FontGraphicsResource* resource, const FontGraphicsFillRates* rates); +int APS5_VABI sceFontGraphicsSetupGlyphFill(FontGraphicsResource* resource, const FontGraphicsFill* fill); +int APS5_VABI sceFontGraphicsSetupGlyphFillPlot(FontGraphicsResource* resource, const FontGraphicsFillPlot* plot); +int APS5_VABI sceFontGraphicsSetupHandleDefault(FontGraphicsResource* resource, std::uint64_t flags); +int APS5_VABI sceFontGraphicsSetupLocation(FontGraphicsResource* resource, float x, float y); +int APS5_VABI sceFontGraphicsSetupPositioning(FontGraphicsResource* resource, std::uint32_t flags, float x, float y); +int APS5_VABI sceFontGraphicsSetupRotation(FontGraphicsResource* resource, float angle, float pivotX, float pivotY); +int APS5_VABI sceFontGraphicsSetupScaling(FontGraphicsResource* resource, float scaleX, float scaleY, float pivotX, float pivotY); +int APS5_VABI sceFontGraphicsSetupShapeFill(FontGraphicsResource* resource, const FontGraphicsFill* fill); +int APS5_VABI sceFontGraphicsSetupShapeFillPlot(FontGraphicsResource* resource, const FontGraphicsFillPlot* plot); +int APS5_VABI sceFontGraphicsStructureCanvas(FontGraphicsDevice* device, std::uint32_t width, std::uint32_t height, std::int32_t format, FontGraphicsCanvas** canvas); +int APS5_VABI sceFontGraphicsStructureCanvasSequence(FontGraphicsDevice* device, FontGraphicsCanvas* canvas, const FontGraphicsSurface* surface, std::uint64_t flags, FontGraphicsCanvasSequence** sequence); +int APS5_VABI sceFontGraphicsStructureDesign(FontGraphicsDevice* device, std::int32_t format, FontGraphicsDesign** design); +int APS5_VABI sceFontGraphicsStructureDesignResource(FontGraphicsDevice* device, FontGraphicsDesign* design, FontGraphicsDesignResource** resource); +int APS5_VABI sceFontGraphicsStructureSurfaceTexture(FontGraphicsDevice* device, const FontGraphicsSurface* surface, FontGraphicsSurfaceTexture** texture); +int APS5_VABI sceFontGraphicsUpdateClipping(FontGraphicsResource* resource, const FontGraphicsRegion* region); +int APS5_VABI sceFontGraphicsUpdateColorRates(FontGraphicsResource* resource, float red, float green, float blue, float alpha); +int APS5_VABI sceFontGraphicsUpdateFillMethod(FontGraphicsResource* resource, const FontGraphicsFillMethod* method); +int APS5_VABI sceFontGraphicsUpdateFillRates(FontGraphicsResource* resource, const FontGraphicsFillRates* rates); +int APS5_VABI sceFontGraphicsUpdateGlyphFill(FontGraphicsResource* resource, const FontGraphicsFill* fill); +int APS5_VABI sceFontGraphicsUpdateGlyphFillPlot(FontGraphicsResource* resource, const FontGraphicsFillPlot* plot); +int APS5_VABI sceFontGraphicsUpdateLocation(FontGraphicsResource* resource, float x, float y); +int APS5_VABI sceFontGraphicsUpdatePositioning(FontGraphicsResource* resource, std::uint32_t flags, float x, float y); +int APS5_VABI sceFontGraphicsUpdateRotation(FontGraphicsResource* resource, float angle, float pivotX, float pivotY); +int APS5_VABI sceFontGraphicsUpdateScaling(FontGraphicsResource* resource, float scaleX, float scaleY, float pivotX, float pivotY); +int APS5_VABI sceFontGraphicsUpdateShapeFill(FontGraphicsResource* resource, const FontGraphicsFill* fill); +int APS5_VABI sceFontGraphicsUpdateShapeFillPlot(FontGraphicsResource* resource, const FontGraphicsFillPlot* plot); +int APS5_VABI sceFontSupportGlyphs(const FontGlyphSupport* support); +int APS5_VABI sceFontWordsFindWordCharacters(FontWords* words, FontTextCharacter* firstCharacter, FontTextCharacter* endCharacter, FontTextCharacter** lastCharacter, FontTextCharacter** nextCharacter); +int APS5_VABI sceFontGraphicsDrawupFillTextureImageObject(FontGraphicsFrame* frame, const void* data, std::uint32_t dataSize, float x, float y, float width, float height, FontGraphicsFill** fill); +int APS5_VABI sceFontGraphicsDrawupFillTexturePatternObject(FontGraphicsFrame* frame, const void* data, std::uint32_t dataSize, std::uint32_t firstMode, std::uint32_t secondMode, std::int32_t firstCount, std::int32_t firstLayout, std::int32_t secondLayout, std::int32_t secondCount, FontGraphicsFill** fill); +int APS5_VABI sceFontCreateWritingLine(const FontMemory* memory, std::int32_t writingForm, const void* writingLineDetail, void** pWritingLine); +int APS5_VABI sceFontDestroyWritingLine(void** pWritingLine); +int APS5_VABI sceFontWritingLineClear(void* writingLine); +int APS5_VABI sceFontWritingLineWritesOrder(void* writingLine, std::uint64_t writingAttribute, const FontWritingMetrics* writingMetrics, void* writingOrderer); +const FontWritingLineStep* APS5_VABI sceFontWritingLineRefersRenderStep(void* writingLine); +int APS5_VABI sceFontWritingLineGetRenderMetrics(void* writingLine, FontWritingMetrics* writingMetrics); +int APS5_VABI sceFontWritingLineGetOrderingSpace(void* writingLine, float* advanceSpace, float* headSpace, float* inlineSpace, float* tailSpace); + +} diff --git a/core/libs/prx/libSceFont/include/FontDriver.hpp b/core/libs/prx/libSceFont/include/FontDriver.hpp index 889c367dda..70b562b215 100644 --- a/core/libs/prx/libSceFont/include/FontDriver.hpp +++ b/core/libs/prx/libSceFont/include/FontDriver.hpp @@ -202,12 +202,15 @@ using DriverTermFunction = int (APS5_VABI*)(FontLibNative* library); using DriverSupportFunction = int (APS5_VABI*)(FontLibNative* library, std::uint32_t formats); using DriverOpenFunction = int (APS5_VABI*)(FontLibNative* library, std::uint32_t mode, const void* fontAddress, std::uint32_t fontSize, std::uint32_t subFontIndex, std::uint32_t uniqueWord, FontObj** inoutFontObj); using DriverCloseFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t flags); -using DriverScaleFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint16_t* outUnitsPerEm, float* outScale); +using DriverScaleFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t* outUnitsPerEm, float* outScale); using DriverMetricFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t metricId, std::uint16_t* outMetric); using DriverGlyphsCountFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t* outCount); using DriverGlyphIndexFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t codepoint, std::uint32_t* outGlyphIndex); using DriverSetCharWithDpiFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t dpiX, std::uint32_t dpiY, float scaleX, float scaleY, float* outScaleX, float* outScaleY); using DriverSetCharDefaultDpiFunction = int (APS5_VABI*)(FontObj* fontObj, float scaleX, float scaleY, float* outScaleX, float* outScaleY); +using DriverFontMetricsFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t form, FontMetrics* outMetrics); +using DriverOutlineProfileFunction = int (APS5_VABI*)(FontObj* fontObj, FontOutlineProfile* outProfile); +using DriverStyleInformationFunction = int (APS5_VABI*)(FontObj* fontObj, FontStyleInformation* outInformation); using DriverComputeLayoutFunction = int (APS5_VABI*)(FontObj* fontObj, const StyleStateBlock* style, std::uint8_t* outWords); using DriverLoadGlyphCachedFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t glyphIndex, std::int32_t mode, std::uint64_t* outWords); using DriverGetGlyphMetricsFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t* optParam2, std::uint8_t mode, std::uint8_t* outParams, FontGlyphMetrics* outMetrics); @@ -225,16 +228,17 @@ struct SysDriver { std::uint8_t reserved_0x30[0x08]; DriverOpenFunction open; DriverCloseFunction close; - std::uint8_t reserved_0x48[0x08]; + DriverStyleInformationFunction style_information; DriverScaleFunction scale; std::uint8_t reserved_0x58[0x08]; DriverMetricFunction metric; DriverGlyphsCountFunction glyphs_count; - std::uint8_t reserved_0x70[0x08]; + DriverOutlineProfileFunction outline_profile; DriverGlyphIndexFunction glyph_index; DriverSetCharWithDpiFunction set_char_with_dpi; DriverSetCharDefaultDpiFunction set_char_default_dpi; - std::uint8_t reserved_0x90[0x10]; + std::uint8_t reserved_0x90[0x08]; + DriverFontMetricsFunction font_metrics; DriverComputeLayoutFunction compute_layout; DriverLoadGlyphCachedFunction load_glyph_cached; std::uint8_t reserved_0xb0[0x08]; @@ -341,8 +345,11 @@ static_assert(offsetof(FontLibNative, list_head) == 0xE0); static_assert(sizeof(SysDriver) == 0x120); static_assert(offsetof(SysDriver, pixel_resolution) == 0x10); static_assert(offsetof(SysDriver, open) == 0x38); +static_assert(offsetof(SysDriver, style_information) == 0x48); static_assert(offsetof(SysDriver, scale) == 0x50); +static_assert(offsetof(SysDriver, outline_profile) == 0x70); static_assert(offsetof(SysDriver, glyph_index) == 0x78); +static_assert(offsetof(SysDriver, font_metrics) == 0x98); static_assert(offsetof(SysDriver, compute_layout) == 0xA0); static_assert(offsetof(SysDriver, get_glyph_metrics) == 0xB8); static_assert(offsetof(SysDriver, apply_glyph_adjust) == 0xE0); diff --git a/core/libs/prx/libSceFont/include/FontGraphics.hpp b/core/libs/prx/libSceFont/include/FontGraphics.hpp new file mode 100644 index 0000000000..a5dbe25b02 --- /dev/null +++ b/core/libs/prx/libSceFont/include/FontGraphics.hpp @@ -0,0 +1,25 @@ +#ifndef CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTGRAPHICS_HPP +#define CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTGRAPHICS_HPP + +#include + +#include "prx/libSceFont/include/Api.hpp" + +namespace Font { + +void SetGraphicsErrorCallback(GraphicsErrorCallback callback); +int GraphicsRegionInit(FontGraphicsRegion* region, float x, float y, float width, float height); +int GraphicsRegionInitCircular(FontGraphicsRegion* region, float centerX, float centerY, float outerRadius, float innerRadius); +int GraphicsRegionInitRoundish(FontGraphicsRegion* region, float x, float y, float width, float height, float outerRadius, float innerRadius, std::uint32_t flags); +int GraphicsFillMethodInit(FontGraphicsFillMethod* method, std::int32_t mode, std::int32_t type); +int GraphicsFillPlotInit(FontGraphicsFillPlot* plot, const FontGraphicsRegion* region); +int GraphicsFillPlotSetLayout(FontGraphicsFillPlot* plot, std::int32_t first, std::int32_t second); +int GraphicsFillPlotSetMapping(FontGraphicsFillPlot* plot, float first, float second, float third, float fourth); +int GraphicsFillRatesInit(FontGraphicsFillRates* rates, const FontGraphicsFill* fill, std::uint32_t effect); +int GraphicsFillRatesSetFillEffect(FontGraphicsFillRates* rates, const FontGraphicsFill* fill, std::uint32_t effect); +int GraphicsFillRatesSetLayout(FontGraphicsFillRates* rates, std::int32_t first, std::int32_t second); +int GraphicsFillRatesSetMapping(FontGraphicsFillRates* rates, float first, float second, float third, float fourth); + +} + +#endif diff --git a/core/libs/prx/libSceFont/include/FontInternal.hpp b/core/libs/prx/libSceFont/include/FontInternal.hpp index 8c68010f10..da4a70b28a 100644 --- a/core/libs/prx/libSceFont/include/FontInternal.hpp +++ b/core/libs/prx/libSceFont/include/FontInternal.hpp @@ -34,10 +34,7 @@ struct FontState { std::shared_ptr> faceData; FT_Face face = nullptr; std::vector fallbacks; - float scaleW = 16.0f; - float scaleH = 16.0f; std::map scriptLanguages; - std::map typographicFeatures; int attribute = 0; FontState() = default; @@ -134,8 +131,11 @@ void CachedStyleSetScalar(CachedStyle& style, float value); void ClearRenderOutputs(FontGlyphMetrics* metrics, FontRenderOutput* result); int ComputeHorizontalLayout(FontHandle handle, const StyleStateBlock* style, std::uint8_t* outWords); int ComputeVerticalLayout(FontHandle handle, const StyleStateBlock* style, std::uint8_t* outWords); -int GetCharGlyphMetrics(FontHandle handle, std::uint32_t code, FontGlyphMetrics* metrics, bool useCachedStyle); +int GetCharGlyphMetrics(FontHandle handle, std::uint32_t code, FontGlyphMetrics* metrics, bool useCachedStyle, float* usedScaleW = nullptr, float* usedScaleH = nullptr); int RenderCharGlyphImageCore(FontHandle handle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result); +int ComputeFontMetrics(FontHandle handle, std::uint32_t form, FontMetrics* metrics); +int ComputeFontStyleInformation(FontHandle handle, FontStyleInformation* information); +int ComputeFontGlyphsOutlineProfile(FontHandle handle, FontOutlineProfile* profile); } diff --git a/core/libs/prx/libSceFont/include/FontTypes.hpp b/core/libs/prx/libSceFont/include/FontTypes.hpp index eb1a548bb4..6507485349 100644 --- a/core/libs/prx/libSceFont/include/FontTypes.hpp +++ b/core/libs/prx/libSceFont/include/FontTypes.hpp @@ -423,12 +423,55 @@ struct FontVerticalLayout { float decorationSpan; }; +struct FontMetrics { + std::uint32_t form; + float ascender; + float descender; + float lineGap; + float maxAdvance; + float verticalDescender; + float verticalAscender; + float xMin; + float yMin; + float xMax; + float yMax; + float emWidth; + float emHeight; + float effectWeightX; + float effectWeightY; + float slantRatio; +}; + +struct FontOutlineProfile { + std::uint32_t maxContours; + std::uint32_t maxPoints; + std::uint32_t maxCompositeContours; + std::uint32_t maxCompositePoints; +}; + +struct FontStyleInformation { + const char* familyName; + const char* styleName; + std::uint16_t styleFlags; + std::uint16_t weight; + std::uint16_t width; + std::uint16_t layoutFlags; +}; + static_assert(sizeof(FontHandleOpaque) == 0x100); static_assert(sizeof(FontMemory) == 0x40); static_assert(sizeof(FontMemoryInterface) == 0x30); static_assert(sizeof(FontOpenDetail) == 0x20); static_assert(sizeof(FontGlyphMetrics) == 0x20); static_assert(sizeof(FontKerning) == 0x10); +static_assert(sizeof(FontMetrics) == 0x40); +static_assert(offsetof(FontMetrics, maxAdvance) == 0x10); +static_assert(offsetof(FontMetrics, xMin) == 0x1C); +static_assert(offsetof(FontMetrics, emWidth) == 0x2C); +static_assert(offsetof(FontMetrics, effectWeightX) == 0x34); +static_assert(sizeof(FontStyleInformation) == 0x18); +static_assert(sizeof(FontOutlineProfile) == 0x10); +static_assert(offsetof(FontStyleInformation, styleFlags) == 0x10); static_assert(sizeof(FontGlyphImageMetrics) == 0x18); static_assert(sizeof(FontGenerateGlyphDetail) == 0x20); static_assert(sizeof(FontResultStage) == 0x18); diff --git a/core/libs/prx/libSceFont/libSceFont.hpp b/core/libs/prx/libSceFont/libSceFont.hpp new file mode 100644 index 0000000000..c39c48d023 --- /dev/null +++ b/core/libs/prx/libSceFont/libSceFont.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceFont/include/Api.hpp" diff --git a/core/libs/prx/libSceFont/src/Graphics.cpp b/core/libs/prx/libSceFont/src/Graphics.cpp new file mode 100644 index 0000000000..8a005d478a --- /dev/null +++ b/core/libs/prx/libSceFont/src/Graphics.cpp @@ -0,0 +1,195 @@ +#include +#include + +#include "prx/libSceFont/include/FontGraphics.hpp" +#include "prx/libSceFont/include/FontTypes.hpp" + +namespace { + +constexpr int FONT_GRAPHICS_ERROR_INVALID_FILL = static_cast(0x80460086); +constexpr int FONT_GRAPHICS_ERROR_INVALID_REGION = static_cast(0x80460087); +constexpr int FONT_GRAPHICS_ERROR_INVALID_FILL_PLOT = static_cast(0x80460088); +constexpr int FONT_GRAPHICS_ERROR_INVALID_FILL_RATES = static_cast(0x80460089); + +constexpr std::uint32_t RegionTag = 0xf60; +constexpr std::uint32_t RectangleRegionFlags = 0xf; +constexpr std::uint32_t CircularRegionFlags = 0xff; +constexpr std::uint32_t FillMethodTag = 0xf01b; +constexpr std::uint32_t FillMethodFlags = 0xf; +constexpr std::uint16_t FillPlotTag = 0xf01c; +constexpr std::uint16_t FillRatesTag = 0xf01a; +constexpr std::uint16_t DefaultLayout = 0xf77; +constexpr std::uint32_t FillEffects = 0x1033f; + +std::atomic g_errorCallback{nullptr}; + +int Fail(int error) { + if (const auto callback = g_errorCallback.load()) callback(error); + return error; +} + +bool IsObject(const void* pointer) { + return reinterpret_cast(pointer) + 1 >= 2; +} + +bool IsValidFill(const FontGraphicsFill* fill) { + const auto value = reinterpret_cast(fill); + if (value >= -1 && value < 3) return true; + const std::uint8_t mode = *reinterpret_cast(fill); + return (mode & 0xf) == 0 && mode >= 0x10 && mode <= 0x50; +} + +bool IsValidEffect(std::uint32_t effect) { + return effect < 17 && ((FillEffects >> effect) & 1) != 0; +} + +bool IsValidLayoutNibble(std::uint32_t nibble) { + return nibble <= 4 && nibble != 3; +} + +bool IsValidLayout(std::int32_t first, std::int32_t second) { + const bool firstValid = first == 0 || first == 0x170 || first == 0x207 || first == 0x400 || first == 0x800 || first == 0xf77; + const auto bits = static_cast(second); + return firstValid && IsValidLayoutNibble((bits >> 4) & 0xf) && IsValidLayoutNibble(bits & 0xf); +} + +bool IsUsableRegion(const FontGraphicsRegion& region) { + if (region.tag != RegionTag) return false; + if (!(region.outerRadius >= 0.0f) || !(region.innerRadius >= 0.0f)) return false; + const float half = (region.width < region.height ? region.width : region.height) * 0.5f; + if (region.outerRadius == 0.0f) return half >= region.innerRadius; + return half >= region.outerRadius && region.outerRadius >= region.innerRadius; +} + +std::uint8_t MappedFlags(std::uint8_t flags) { + return static_cast((flags & 0xfc) | 1); +} + +} + +namespace Font { + +void SetGraphicsErrorCallback(GraphicsErrorCallback callback) { + g_errorCallback.store(callback); +} + +int GraphicsRegionInit(FontGraphicsRegion* region, float x, float y, float width, float height) { + if (!region || 0.0f > width || 0.0f > height) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + *region = {x, y, width, height, RectangleRegionFlags, RegionTag, 0.0f, 0.0f}; + return SCE_FONT_OK; +} + +int GraphicsRegionInitCircular(FontGraphicsRegion* region, float centerX, float centerY, float outerRadius, float innerRadius) { + if (innerRadius > outerRadius || !region || 0.0f >= outerRadius || 0.0f >= innerRadius) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + const float diameter = outerRadius + outerRadius; + *region = {centerX - outerRadius, centerY - outerRadius, diameter, diameter, CircularRegionFlags, RegionTag, outerRadius, innerRadius}; + return SCE_FONT_OK; +} + +int GraphicsRegionInitRoundish(FontGraphicsRegion* region, float x, float y, float width, float height, float outerRadius, float innerRadius, std::uint32_t flags) { + if (!region || 0.0f > innerRadius || 0.0f > outerRadius || 0.0f >= height || 0.0f >= width || flags > 0xff) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + if (outerRadius > height * 0.5f || outerRadius > width * 0.5f) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + if (outerRadius < innerRadius && outerRadius != 0.0f) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + *region = {x, y, width, height, flags, RegionTag, outerRadius, innerRadius}; + return SCE_FONT_OK; +} + +int GraphicsFillMethodInit(FontGraphicsFillMethod* method, std::int32_t mode, std::int32_t type) { + const auto modeBits = static_cast(mode); + const bool modeValid = (modeBits & 0xf) == 0 && (modeBits >> 4) < 6; + const bool typeValid = static_cast(type) - 1 < 7 || type == 12; + if (!method || !modeValid || !typeValid) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + *method = {}; + method->mode = mode; + method->type = type; + method->flags = FillMethodFlags; + method->tag = FillMethodTag; + return SCE_FONT_OK; +} + +int GraphicsFillPlotInit(FontGraphicsFillPlot* plot, const FontGraphicsRegion* region) { + if (!IsObject(plot)) return Fail(FONT_GRAPHICS_ERROR_INVALID_FILL_PLOT); + if (IsObject(region) && !IsUsableRegion(*region)) { + plot->tag = 0; + return Fail(FONT_GRAPHICS_ERROR_INVALID_REGION); + } + *plot = {}; + if (!region) { + plot->flags = 2; + } else if (!IsObject(region)) { + plot->flags = 6; + plot->layout = DefaultLayout; + } else { + plot->mapping[0] = region->x; + plot->mapping[1] = region->y; + plot->mapping[2] = region->x + region->width; + plot->mapping[3] = region->y + region->height; + plot->flags = 1; + plot->layout = DefaultLayout; + } + plot->region = region; + plot->tag = FillPlotTag; + return SCE_FONT_OK; +} + +int GraphicsFillPlotSetLayout(FontGraphicsFillPlot* plot, std::int32_t first, std::int32_t second) { + if (!IsObject(plot) || plot->tag != FillPlotTag) return Fail(FONT_GRAPHICS_ERROR_INVALID_FILL_PLOT); + if (!IsValidLayout(first, second)) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + const auto combined = static_cast(first) | static_cast(second); + plot->layout = static_cast(combined); + plot->flags = static_cast((plot->flags & 0xf6) | (combined != 0 ? 1 : 0) | 8); + return SCE_FONT_OK; +} + +int GraphicsFillPlotSetMapping(FontGraphicsFillPlot* plot, float first, float second, float third, float fourth) { + if (!IsObject(plot) || plot->tag != FillPlotTag) return Fail(FONT_GRAPHICS_ERROR_INVALID_FILL_PLOT); + plot->mapping[0] = first; + plot->mapping[1] = second; + plot->mapping[2] = third; + plot->mapping[3] = fourth; + plot->flags = MappedFlags(plot->flags); + return SCE_FONT_OK; +} + +int GraphicsFillRatesInit(FontGraphicsFillRates* rates, const FontGraphicsFill* fill, std::uint32_t effect) { + if (!IsObject(rates)) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + *rates = {}; + if (!IsValidFill(fill)) return Fail(FONT_GRAPHICS_ERROR_INVALID_FILL); + if (!IsValidEffect(effect)) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + rates->fill = fill; + rates->tag = FillRatesTag; + rates->effect = static_cast(effect); + rates->layout = DefaultLayout; + rates->flags = 2; + return SCE_FONT_OK; +} + +int GraphicsFillRatesSetFillEffect(FontGraphicsFillRates* rates, const FontGraphicsFill* fill, std::uint32_t effect) { + if (!IsObject(rates) || rates->tag != FillRatesTag) return Fail(FONT_GRAPHICS_ERROR_INVALID_FILL_RATES); + if (!IsValidFill(fill)) return Fail(FONT_GRAPHICS_ERROR_INVALID_FILL); + if (!IsValidEffect(effect)) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + rates->effect = static_cast(effect); + rates->fill = fill; + return SCE_FONT_OK; +} + +int GraphicsFillRatesSetLayout(FontGraphicsFillRates* rates, std::int32_t first, std::int32_t second) { + if (!IsObject(rates) || rates->tag != FillRatesTag) return Fail(FONT_GRAPHICS_ERROR_INVALID_FILL_RATES); + if (!IsValidLayout(first, second)) return Fail(SCE_FONT_ERROR_INVALID_PARAMETER); + const auto combined = static_cast(first) | static_cast(second); + rates->flags = static_cast((rates->flags & 0xfe) | (combined != 0 ? 1 : 0)); + rates->layout = 0; + return SCE_FONT_OK; +} + +int GraphicsFillRatesSetMapping(FontGraphicsFillRates* rates, float first, float second, float third, float fourth) { + if (!IsObject(rates) || rates->tag != FillRatesTag) return Fail(FONT_GRAPHICS_ERROR_INVALID_FILL_RATES); + rates->mapping[0] = first; + rates->mapping[1] = second; + rates->mapping[2] = third; + rates->mapping[3] = fourth; + rates->flags = MappedFlags(rates->flags); + return SCE_FONT_OK; +} + +} diff --git a/core/libs/prx/libSceFont/src/Layout.cpp b/core/libs/prx/libSceFont/src/Layout.cpp index a59a3b5f2f..d297840675 100644 --- a/core/libs/prx/libSceFont/src/Layout.cpp +++ b/core/libs/prx/libSceFont/src/Layout.cpp @@ -120,7 +120,8 @@ void FillImageMetrics(FontRenderOutput* result, const FontGlyphMetrics* metrics, int RenderGlyphIndexToSurface(FontObj& obj, std::uint32_t glyphIndex, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { ClearRenderOutputs(metrics, result); - if (!surface->buffer || surface->width <= 0 || surface->height <= 0 || surface->widthByte <= 0 || surface->pixelSizeByte <= 0) return SCE_FONT_ERROR_NO_SUPPORT_SURFACE; + const bool emptyExtent = surface->width == 0 || surface->height == 0; + if (!surface->buffer || surface->width < 0 || surface->height < 0 || surface->widthByte < 0 || (!emptyExtent && surface->widthByte == 0) || surface->pixelSizeByte <= 0) return SCE_FONT_ERROR_NO_SUPPORT_SURFACE; const int bytesPerPixel = surface->pixelSizeByte; if (bytesPerPixel != 1 && bytesPerPixel != 4) return SCE_FONT_ERROR_NO_SUPPORT_SURFACE; const auto face = static_cast(obj.ft_face); @@ -365,7 +366,7 @@ int Font::ComputeVerticalLayout(FontHandle handle, const StyleStateBlock* style, return SCE_FONT_OK; } -int Font::GetCharGlyphMetrics(FontHandle handle, std::uint32_t code, FontGlyphMetrics* metrics, bool useCachedStyle) { +int Font::GetCharGlyphMetrics(FontHandle handle, std::uint32_t code, FontGlyphMetrics* metrics, bool useCachedStyle, float* usedScaleW, float* usedScaleH) { const auto clearMetrics = [&] { if (metrics) *metrics = {}; }; @@ -434,6 +435,8 @@ int Font::GetCharGlyphMetrics(FontHandle handle, std::uint32_t code, FontGlyphMe metrics->Vertical.bearingX = static_cast(vertBearingX) / 64.0f * ratioX; metrics->Vertical.bearingY = static_cast(vertBearingY) / 64.0f * ratioY; metrics->Vertical.advance = static_cast(slot->metrics.vertAdvance) / 64.0f * ratioY; + if (usedScaleW) *usedScaleW = scaleW; + if (usedScaleH) *usedScaleH = scaleH; return finish(SCE_FONT_OK); } @@ -527,3 +530,109 @@ bool Font::BuildTrueOutline(GeneratedGlyph& glyph) { glyph.outlineInitialized = true; return true; } + +int Font::ComputeFontMetrics(FontHandle handle, std::uint32_t form, FontMetrics* metrics) { + auto* font = GetNativeFont(handle); + auto* library = font ? static_cast(font->library) : nullptr; + if (!library || !FontContext(library, font)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + const SysDriver* driver = library->sys_driver; + if (!driver || !driver->set_char_with_dpi || !driver->set_char_default_dpi || !driver->font_metrics) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + const std::uint32_t fontId = font->open_info.ctx_entry_index; + if (static_cast(fontId) < 0) { + metrics->form = form; + metrics->ascender = 0.0f; + metrics->descender = 0.0f; + metrics->lineGap = 0.0f; + metrics->maxAdvance = 0.0f; + metrics->verticalDescender = 0.0f; + metrics->verticalAscender = 0.0f; + return SCE_FONT_OK; + } + const std::uint32_t modeLow = font->flags & 0x0Fu; + FontObj* head = nullptr; + std::uint32_t lockWord = 0; + auto* entry = AcquireFontCtxEntry(FontContext(library, font), fontId, modeLow, &head, &lockWord); + if (!entry) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + FontObj* match = FindSubFont(head, font->open_info.sub_font_index); + int rc = SCE_FONT_ERROR_FATAL; + if ((lockWord & OPEN_BIT) != 0 && (lockWord & COUNT_MASK) != 0 && match) { + float scaleX = font->style.scale_w; + float scaleY = font->style.scale_h; + match->font_handle = handle; + match->shift_units_x = 0; + match->shift_units_y = 0; + rc = SetDriverCharSize(driver, match, &font->style, scaleX, scaleY); + if (rc == SCE_FONT_OK) rc = driver->font_metrics(match, form, metrics); + } + ReleaseFontCtxEntryLock(entry, modeLow, lockWord); + return rc == SCE_FONT_OK ? rc : SCE_FONT_ERROR_INVALID_FONT_HANDLE; +} + +int Font::ComputeFontStyleInformation(FontHandle handle, FontStyleInformation* information) { + auto* font = GetNativeFont(handle); + auto* library = static_cast(font->library); + if (!library) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + if (!information) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (const auto* record = static_cast(font->open_info.fontset_record)) { + std::memcpy(&information->familyName, record + 8, sizeof(information->familyName)); + std::memcpy(&information->styleName, record + 16, sizeof(information->styleName)); + information->styleFlags = record[4]; + information->weight = static_cast(record[5] * 100); + information->width = record[6]; + information->layoutFlags = record[7]; + return SCE_FONT_OK; + } + const SysDriver* driver = library->sys_driver; + auto* context = FontContext(library, font); + if (!driver || !driver->style_information || !context) return SCE_FONT_ERROR_FATAL; + const std::uint32_t modeLow = font->flags & 0x0Fu; + FontObj* head = nullptr; + std::uint32_t lockWord = 0; + auto* entry = AcquireFontCtxEntry(context, font->open_info.ctx_entry_index, modeLow, &head, &lockWord); + if (!entry) return SCE_FONT_ERROR_FATAL; + FontObj* match = FindSubFont(head, font->open_info.sub_font_index); + int rc = SCE_FONT_ERROR_FATAL; + if ((lockWord & OPEN_BIT) != 0 && (lockWord & COUNT_MASK) != 0 && match) { + match->font_handle = handle; + rc = driver->style_information(match, information); + information->weight = static_cast(information->weight * 100); + } + ReleaseFontCtxEntryLock(entry, modeLow, lockWord); + return rc; +} + +int Font::ComputeFontGlyphsOutlineProfile(FontHandle handle, FontOutlineProfile* profile) { + auto* font = GetNativeFont(handle); + if (!font || font->magic != HANDLE_MAGIC) { + if (profile) *profile = {}; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + if (!profile) return SCE_FONT_ERROR_INVALID_PARAMETER; + *profile = {}; + auto* library = static_cast(font->library); + auto* context = library ? FontContext(library, font) : nullptr; + if (!context) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + const std::uint32_t fontId = font->open_info.ctx_entry_index; + if (static_cast(fontId) < 0) return SCE_FONT_OK; + const SysDriver* driver = library->sys_driver; + if (!driver || !driver->outline_profile) return SCE_FONT_ERROR_FATAL; + const std::uint32_t modeLow = font->flags & 0x0Fu; + FontObj* head = nullptr; + std::uint32_t lockWord = 0; + auto* entry = AcquireFontCtxEntry(context, fontId, modeLow, &head, &lockWord); + if (!entry) return SCE_FONT_ERROR_FATAL; + FontObj* match = FindSubFont(head, font->open_info.sub_font_index); + int rc = SCE_FONT_ERROR_FATAL; + FontOutlineProfile faceProfile{}; + if ((lockWord & OPEN_BIT) != 0 && (lockWord & COUNT_MASK) != 0 && match) { + match->font_handle = handle; + rc = driver->outline_profile(match, &faceProfile); + } + ReleaseFontCtxEntryLock(entry, modeLow, lockWord); + if (rc != SCE_FONT_OK) return rc; + profile->maxContours = std::max(profile->maxContours, faceProfile.maxContours); + profile->maxPoints = std::max(profile->maxPoints, faceProfile.maxPoints); + profile->maxCompositeContours = std::max(profile->maxCompositeContours, faceProfile.maxCompositeContours); + profile->maxCompositePoints = std::max(profile->maxCompositePoints, faceProfile.maxCompositePoints); + return SCE_FONT_OK; +} diff --git a/core/libs/prx/libSceFont/src/Library.cpp b/core/libs/prx/libSceFont/src/Library.cpp index e872a3fa66..54d3f48d0c 100644 --- a/core/libs/prx/libSceFont/src/Library.cpp +++ b/core/libs/prx/libSceFont/src/Library.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFont/include/Api.hpp" // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,6 +6,7 @@ #include #include #include +#include #include "prx/libSceFont/include/FontInternal.hpp" @@ -298,10 +300,19 @@ int APS5_VABI sceFontRendererSetOutlineBufferPolicy(FontRenderer fontRenderer, s } int APS5_VABI sceFontGetPixelResolution(FontLibrary library, std::uint32_t* subPixelCount) { + auto* lib = static_cast(library); + std::uint32_t previous = 0; + if (!lib || !AcquireLibraryLock(lib, previous)) { + if (subPixelCount) *subPixelCount = 0; + return SCE_FONT_ERROR_INVALID_LIBRARY; + } + const auto unlock = [previous](FontLibNative* locked) { ReleaseLibraryLock(locked, previous); }; + const std::unique_ptr lock(lib, unlock); + if (!lib->sys_driver || !lib->sys_driver->pixel_resolution) { + if (subPixelCount) *subPixelCount = 0; + return SCE_FONT_ERROR_INVALID_LIBRARY; + } if (!subPixelCount) return SCE_FONT_ERROR_INVALID_PARAMETER; - *subPixelCount = 0; - const auto* lib = static_cast(library); - if (!lib || lib->magic != LIBRARY_MAGIC || !lib->sys_driver || !lib->sys_driver->pixel_resolution) return SCE_FONT_ERROR_INVALID_LIBRARY; *subPixelCount = lib->sys_driver->pixel_resolution(); return SCE_FONT_OK; } @@ -368,26 +379,37 @@ int APS5_VABI sceFontClearDeviceCache(FontLibrary library) { } int APS5_VABI sceFontDettachDeviceCacheBuffer(FontLibrary library, void** buffer, std::uint32_t* size) { + const auto clearOutputs = [&] { + if (buffer) *buffer = nullptr; + if (size) *size = 0; + }; auto* lib = static_cast(library); - if (!lib || lib->magic != LIBRARY_MAGIC) return SCE_FONT_ERROR_INVALID_LIBRARY; std::uint32_t previous = 0; - if (!AcquireLibraryLock(lib, previous)) return SCE_FONT_ERROR_INVALID_LIBRARY; + if (!lib || lib->magic != LIBRARY_MAGIC || !AcquireLibraryLock(lib, previous)) { + clearOutputs(); + return SCE_FONT_ERROR_INVALID_LIBRARY; + } std::uint32_t* current = AcquireDeviceCache(lib); - if (!current) { - ReleaseDeviceCache(lib, nullptr); - ReleaseLibraryLock(lib, previous); - return SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER; - } - if (size) *size = current[0]; - if (buffer) { - *buffer = current; - } else if ((lib->flags & 1u) != 0) { - lib->iface->dealloc(lib->alloc_ctx, current); - } - lib->flags &= ~1u; ReleaseDeviceCache(lib, nullptr); + int result = SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER; + if (current) { + const std::uint32_t capacity = current[0]; + std::memset(current + 1, 0, 5 * sizeof(std::uint32_t)); + if ((lib->flags & 1u) == 0) { + if (buffer) *buffer = current; + if (size) *size = capacity; + ReleaseLibraryLock(lib, previous); + return SCE_FONT_OK; + } + result = SCE_FONT_ERROR_FATAL; + if (lib->iface && lib->iface->dealloc) { + lib->iface->dealloc(lib->alloc_ctx, current); + result = SCE_FONT_OK; + } + } + clearOutputs(); ReleaseLibraryLock(lib, previous); - return SCE_FONT_OK; + return result; } int APS5_VABI sceFontSupportExternalFonts(FontLibrary library, std::uint32_t fontMax, std::uint32_t formats) { diff --git a/core/libs/prx/libSceFont/src/Open.cpp b/core/libs/prx/libSceFont/src/Open.cpp index 21e0272990..62d33f609e 100644 --- a/core/libs/prx/libSceFont/src/Open.cpp +++ b/core/libs/prx/libSceFont/src/Open.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFont/include/Api.hpp" // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -82,7 +83,8 @@ void InitializeOpenedHandle(FontHandleNative* font, FontLibNative* library, Font driver->metric(obj, 0xEA00, metric); font->metricB = metric[0]; float scale = 0.0f; - driver->scale(obj, metric, &scale); + std::uint32_t resolution = 0; + driver->scale(obj, &resolution, &scale); font->style = {}; font->style.dpi_x = 0x48; font->style.dpi_y = 0x48; @@ -397,8 +399,6 @@ int APS5_VABI sceFontOpenFontInstance(FontHandle fontHandle, FontHandle setupFon ReleaseFontLock(source, sourceLock); FontState& targetState = ResetState(target); if (FontState* sourceState = TryGetState(fontHandle)) { - targetState.scaleW = sourceState->scaleW; - targetState.scaleH = sourceState->scaleH; if (sourceState->faceData && !sourceState->faceData->empty()) LoadStateFace(targetState, sourceState->faceData, subFontIndex); for (const FallbackFace& fallback : sourceState->fallbacks) AddStateFallbackFace(targetState, fallback.data, fallback.subFontIndex); } @@ -492,6 +492,11 @@ int APS5_VABI sceFontGetLibrary(FontHandle fontHandle, FontLibrary* pLibrary) { } int APS5_VABI sceFontGetFontGlyphsCount(FontHandle fontHandle, std::uint32_t* glyphsCount) { + const auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != HANDLE_MAGIC) { + if (glyphsCount) *glyphsCount = 0; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } if (!glyphsCount) return SCE_FONT_ERROR_INVALID_PARAMETER; *glyphsCount = 0; const int rc = CallOpenFontObject(fontHandle, [&](const SysDriver& driver, FontObj* obj) { @@ -503,19 +508,31 @@ int APS5_VABI sceFontGetFontGlyphsCount(FontHandle fontHandle, std::uint32_t* gl } int APS5_VABI sceFontGetCharGlyphCode(FontHandle fontHandle, std::uint32_t code, std::uint32_t* glyphCode) { + if (glyphCode) *glyphCode = 0; + const auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != HANDLE_MAGIC) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + if (code == 0) return SCE_FONT_ERROR_NO_SUPPORT_CODE; if (!glyphCode) return SCE_FONT_ERROR_INVALID_PARAMETER; - *glyphCode = 0; + if ((code & 0x80000000u) != 0 && (code & 0x3fff0000u) != 0) return SCE_FONT_ERROR_NO_SUPPORT_CODE; + std::uint32_t index = 0; const int rc = CallOpenFontObject(fontHandle, [&](const SysDriver& driver, FontObj* obj) { + if ((code & 0x80000000u) != 0) { + if (!driver.glyphs_count) return SCE_FONT_ERROR_FATAL; + std::uint32_t count = 0; + const int result = driver.glyphs_count(obj, &count); + if (result != SCE_FONT_OK) return result; + index = code & 0x3fffffffu; + return index < count ? SCE_FONT_OK : SCE_FONT_ERROR_NO_SUPPORT_GLYPH; + } if (!driver.glyph_index) return SCE_FONT_ERROR_FATAL; - if (code == 0) return SCE_FONT_ERROR_NO_SUPPORT_CODE; - return driver.glyph_index(obj, code, glyphCode); + return driver.glyph_index(obj, code, &index); }); - if (rc != SCE_FONT_OK) *glyphCode = 0; + if (rc == SCE_FONT_OK) *glyphCode = index | 0x80000000u; return rc; } int APS5_VABI sceFontGetFontResolution(FontHandle fontHandle, std::uint32_t* pResolution, float* pScalePixel) { - std::uint16_t unitsPerEm = 0; + std::uint32_t unitsPerEm = 0; float scalePixel = 0.0f; const int rc = CallOpenFontObject(fontHandle, [&](const SysDriver& driver, FontObj* obj) { if (!pResolution && !pScalePixel) return SCE_FONT_ERROR_INVALID_PARAMETER; diff --git a/core/libs/prx/libSceFont/src/Render.cpp b/core/libs/prx/libSceFont/src/Render.cpp index 3f78fa635a..0647f2192e 100644 --- a/core/libs/prx/libSceFont/src/Render.cpp +++ b/core/libs/prx/libSceFont/src/Render.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFont/include/Api.hpp" // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -158,6 +159,63 @@ int APS5_VABI sceFontGetHorizontalLayout(FontHandle fontHandle, FontHorizontalLa return SCE_FONT_OK; } +int APS5_VABI sceFontGetFontMetrics(FontHandle fontHandle, std::uint32_t form, FontMetrics* metrics) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (font && font->magic == HANDLE_MAGIC && AcquireFontLock(font, fontLock)) { + if (!metrics) { + ReleaseFontLock(font, fontLock); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + const int rc = ComputeFontMetrics(fontHandle, form, metrics); + ReleaseFontLock(font, fontLock); + if (rc == SCE_FONT_OK) return rc; + } + if (metrics) { + *metrics = {}; + metrics->effectWeightX = 1.0f; + metrics->effectWeightY = 1.0f; + } + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; +} + +int APS5_VABI sceFontGetFontStyleInformation(FontHandle fontHandle, FontStyleInformation* information) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + int rc = SCE_FONT_ERROR_INVALID_FONT_HANDLE; + if (font && font->magic == HANDLE_MAGIC && AcquireFontLock(font, fontLock)) { + rc = ComputeFontStyleInformation(fontHandle, information); + ReleaseFontLock(font, fontLock); + if (rc == SCE_FONT_OK) return rc; + } + if (information) { + information->familyName = ""; + information->styleName = ""; + information->styleFlags = 0; + information->weight = 0; + information->width = 0; + information->layoutFlags = 0; + } + return rc; +} + +int APS5_VABI sceFontGetFontGlyphsOutlineProfile(FontHandle fontHandle, FontOutlineProfile* profile) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + int rc = SCE_FONT_ERROR_INVALID_FONT_HANDLE; + if (font && font->magic == HANDLE_MAGIC) { + if (!profile) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (font->open_info.fontset_record) NotImplemented_nid_no_patch(__func__); + if (AcquireFontLock(font, fontLock)) { + rc = ComputeFontGlyphsOutlineProfile(fontHandle, profile); + ReleaseFontLock(font, fontLock); + if (rc == SCE_FONT_OK) return rc; + } + } + if (profile) *profile = {}; + return rc; +} + int APS5_VABI sceFontGetVerticalLayout(FontHandle fontHandle, FontVerticalLayout* layout) { int rc = SCE_FONT_ERROR_INVALID_FONT_HANDLE; auto* font = GetNativeFont(fontHandle); @@ -188,7 +246,12 @@ int APS5_VABI sceFontGetKerning(FontHandle fontHandle, std::uint32_t preCode, st if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; *kerning = {}; const FontState* state = TryGetState(fontHandle); - if (state) LoadKerning(*state, state->scaleW, state->scaleH, preCode, code, kerning); + if (state) { + float scaleW = 0.0f; + float scaleH = 0.0f; + StyleStateGetScalePixel(&font->style, &scaleW, &scaleH); + LoadKerning(*state, scaleW, scaleH, preCode, code, kerning); + } ReleaseFontLock(font, fontLock); return SCE_FONT_OK; } @@ -373,8 +436,7 @@ int APS5_VABI sceFontGenerateCharGlyph(FontHandle fontHandle, std::uint32_t code *pGlyph = nullptr; if (!fontHandle) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; if (code == 0) return SCE_FONT_ERROR_NO_SUPPORT_CODE; - const FontState* state = TryGetState(fontHandle); - if (!state) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + if (!TryGetState(fontHandle)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; const std::uint8_t glyphForm = detail ? detail->glyph_form : 0; const std::uint8_t metricsForm = detail ? detail->metrics_form : 0; const std::uint16_t formOptions = detail ? detail->form_options : 0; @@ -385,7 +447,9 @@ int APS5_VABI sceFontGenerateCharGlyph(FontHandle fontHandle, std::uint32_t code if ((glyphForm != 0 && metricsForm == 0) || glyphForm > 1 || metricsForm > 4) return SCE_FONT_ERROR_INVALID_PARAMETER; if (glyphMemory && glyphMemory->mem_kind != MEMORY_MAGIC) return SCE_FONT_ERROR_INVALID_PARAMETER; FontGlyphMetrics metrics{}; - const int rc = GetCharGlyphMetrics(fontHandle, code, &metrics, false); + float scaleW = 0.0f; + float scaleH = 0.0f; + const int rc = GetCharGlyphMetrics(fontHandle, code, &metrics, false, &scaleW, &scaleH); if (rc != SCE_FONT_OK) return rc; auto* generated = new (std::nothrow) GeneratedGlyph(); if (!generated) return SCE_FONT_ERROR_ALLOCATION_FAILED; @@ -395,13 +459,13 @@ int APS5_VABI sceFontGenerateCharGlyph(FontHandle fontHandle, std::uint32_t code generated->glyph.flags = formOptions; generated->glyph.glyph_form = glyphForm; generated->glyph.metrics_form = metricsForm; - generated->glyph.em_size = ClampToU16(state->scaleH); + generated->glyph.em_size = ClampToU16(scaleH); generated->glyph.baseline = ClampToU16(metrics.Horizontal.bearingY); generated->glyph.height_px = ClampToU16(metrics.height); generated->glyph.origin_x = 0; generated->glyph.origin_y = generated->glyph.baseline; - generated->glyph.scale_x = state->scaleW; - generated->glyph.base_scale = state->scaleH; + generated->glyph.scale_x = scaleW; + generated->glyph.base_scale = scaleH; generated->glyph.memory = glyphMemory; generated->owner = fontHandle; generated->outline.outline_flags = generated->glyph.flags; @@ -420,11 +484,18 @@ int APS5_VABI sceFontDeleteGlyph(const FontMemory* memory, FontGlyph* pGlyph) { return SCE_FONT_OK; } -int APS5_VABI sceFontGlyphDefineAttribute(FontGlyph glyph, int attribute, int* oldAttribute) { - auto* generated = TryGetGeneratedGlyph(glyph); - if (!generated) return SCE_FONT_ERROR_INVALID_GLYPH; - if (oldAttribute) *oldAttribute = generated->attribute; - generated->attribute = attribute; +int APS5_VABI sceFontGlyphDefineAttribute(FontGlyph glyph, std::int32_t attribute, std::int32_t* previousAttribute) { + if (!glyph || glyph->magic != GLYPH_MAGIC) { + if (previousAttribute) *previousAttribute = 0; + return SCE_FONT_ERROR_INVALID_GLYPH; + } + if (attribute != 0x10 && attribute != 0x11) { + if (previousAttribute) *previousAttribute = 0; + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + const std::int32_t previous = (glyph->flags & 2) ? 0x11 : 0x10; + glyph->flags = static_cast((glyph->flags & ~2u) | ((attribute - 0x10) << 1)); + if (previousAttribute) *previousAttribute = previous; return SCE_FONT_OK; } @@ -433,7 +504,7 @@ int APS5_VABI sceFontGlyphGetAttribute(FontGlyph glyph, int attribute, int* nowA if (!nowAttribute) return SCE_FONT_ERROR_INVALID_PARAMETER; auto* generated = TryGetGeneratedGlyph(glyph); if (!generated) return SCE_FONT_ERROR_INVALID_GLYPH; - *nowAttribute = generated->attribute; + *nowAttribute = (glyph->flags & 2) ? 0x11 : 0x10; return SCE_FONT_OK; } diff --git a/core/libs/prx/libSceFont/src/Style.cpp b/core/libs/prx/libSceFont/src/Style.cpp index e4adb89793..fcb40847fe 100644 --- a/core/libs/prx/libSceFont/src/Style.cpp +++ b/core/libs/prx/libSceFont/src/Style.cpp @@ -1,8 +1,10 @@ +#include "prx/libSceFont/include/Api.hpp" // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include +#include #include "prx/libSceFont/include/FontInternal.hpp" @@ -59,20 +61,61 @@ int AccessFontSettings(FontHandle fontHandle, Access&& access) { }); } -int SetFontSetting(FontHandle fontHandle, std::map FontState::*table, int key, int value) { - return AccessFontSettings(fontHandle, [&](FontState& state) { - (state.*table)[key] = value; - return SCE_FONT_OK; - }); +bool HasTypographyRecordFeature(const void* record, int typographic) { + const auto* bytes = static_cast(record); + std::uint32_t count; + std::memcpy(&count, bytes + 32, sizeof(count)); + for (std::uint32_t index = 0; index < count; ++index) { + std::int32_t fontId; + std::memcpy(&fontId, bytes + 36 + sizeof(fontId) * index, sizeof(fontId)); + if (count == 1) { + if (typographic == 65538) return fontId == 43 || fontId == 12; + return static_cast(fontId) <= 29 && (0x30000c00u & (1u << fontId)) != 0; + } + if ((std::to_integer(bytes[100 + index]) & 0x80u) != 0 && fontId >= 0) return index == (typographic == 65538 ? 2u : 1u); + } + return false; } -int GetFontSetting(FontHandle fontHandle, std::map FontState::*table, int key, int* value) { - if (!value) return SCE_FONT_ERROR_INVALID_PARAMETER; - return AccessFontSettings(fontHandle, [&](FontState& state) { - const auto found = (state.*table).find(key); - *value = found == (state.*table).end() ? 0 : found->second; +int CheckTypography(FontHandleNative* font, int typographic, bool setting) { + const auto* record = font->open_info.fontset_record; + switch (typographic) { + case 1: + case 17: return SCE_FONT_OK; - }); + case 65537: + if (setting) return !record || HasTypographyRecordFeature(record, typographic) ? SCE_FONT_ERROR_INVALID_PARAMETER : SCE_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY; + return !record || HasTypographyRecordFeature(record, typographic) ? SCE_FONT_OK : SCE_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY; + case 65538: + return !record || HasTypographyRecordFeature(record, typographic) ? SCE_FONT_OK : SCE_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY; + case 65539: + return record ? SCE_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY : SCE_FONT_OK; + default: + return SCE_FONT_ERROR_INVALID_PARAMETER; + } +} + +bool IsScriptLanguageValid(int script, int language) { + if (script == 0x100) return language >= 0x100 && language <= 0x105; + if (script == 0x600) return language >= 0x600 && language <= 0x602; + if (script != 0x3000) return false; + switch (language) { + case 0x3000: + case 0x3011: + case 0x3012: + case 0x3013: + case 0x3014: + case 0x3016: + case 0x3017: + case 0x3018: + case 0x3021: + case 0x3022: + case 0x3023: + case 0x3041: + return true; + default: + return false; + } } void ResetScaleOutputs(float* w, float* h) { @@ -119,30 +162,45 @@ int APS5_VABI sceFontGetAttribute(FontHandle fontHandle, int attribute, int* now } int APS5_VABI sceFontSetScriptLanguage(FontHandle fontHandle, int fontScript, int fontLanguage) { - return SetFontSetting(fontHandle, &FontState::scriptLanguages, fontScript, fontLanguage); + const int result = AccessFontSettings(fontHandle, [&](FontState&) { + return IsScriptLanguageValid(fontScript, fontLanguage) ? SCE_FONT_OK : SCE_FONT_ERROR_INVALID_PARAMETER; + }); + if (result != SCE_FONT_OK) return result; + throw std::logic_error("sceFontSetScriptLanguage: driver language selection is not implemented"); } int APS5_VABI sceFontGetScriptLanguage(FontHandle fontHandle, int fontScript, int* fontLanguage) { - return GetFontSetting(fontHandle, &FontState::scriptLanguages, fontScript, fontLanguage); + if (fontLanguage) *fontLanguage = 0; + const int result = AccessFontSettings(fontHandle, [&](FontState&) { + if (!fontLanguage) return SCE_FONT_ERROR_INVALID_PARAMETER; + return fontScript == 0x100 || fontScript == 0x600 || fontScript == 0x3000 ? SCE_FONT_OK : SCE_FONT_ERROR_INVALID_PARAMETER; + }); + if (result != SCE_FONT_OK) return result; + throw std::logic_error("sceFontGetScriptLanguage: driver language query is not implemented"); } int APS5_VABI sceFontSetTypographicDesign(FontHandle fontHandle, int typographic, int feature) { - return SetFontSetting(fontHandle, &FontState::typographicFeatures, typographic, feature); + const int result = ReadFontStyle(fontHandle, true, [&](FontHandleNative* font) { + const int validation = CheckTypography(font, typographic, true); + if (validation != SCE_FONT_OK) return validation; + return feature == 0 || feature == (typographic == 1 ? 1 : 32) ? SCE_FONT_OK : SCE_FONT_ERROR_INVALID_PARAMETER; + }); + if (result != SCE_FONT_OK) return result; + throw std::logic_error("sceFontSetTypographicDesign: driver typography selection is not implemented"); } int APS5_VABI sceFontGetTypographicDesign(FontHandle fontHandle, int typographic, int* feature) { - return GetFontSetting(fontHandle, &FontState::typographicFeatures, typographic, feature); + const int result = ReadFontStyle(fontHandle, true, [&](FontHandleNative* font) { + const int validation = CheckTypography(font, typographic, false); + return feature ? validation : SCE_FONT_ERROR_INVALID_PARAMETER; + }); + if (feature) *feature = 0; + if (result != SCE_FONT_OK) return result; + throw std::logic_error("sceFontGetTypographicDesign: driver typography query is not implemented"); } int APS5_VABI sceFontSetScalePixel(FontHandle fontHandle, float w, float h) { - return UpdateFontStyle(fontHandle, [&](FontHandleNative* font) { - const int changed = StyleStateSetScalePixel(&font->style, w, h); - if (FontState* state = TryGetState(fontHandle)) { - state->scaleW = w; - state->scaleH = h; - } - return changed; - }); + return UpdateFontStyle(fontHandle, [&](FontHandleNative* font) { return StyleStateSetScalePixel(&font->style, w, h); }); } int APS5_VABI sceFontSetScalePoint(FontHandle fontHandle, float w, float h) { diff --git a/core/libs/prx/libSceFont/src/SystemFont.cpp b/core/libs/prx/libSceFont/src/SystemFont.cpp index 5bcfb0789e..12d39768c2 100644 --- a/core/libs/prx/libSceFont/src/SystemFont.cpp +++ b/core/libs/prx/libSceFont/src/SystemFont.cpp @@ -60,7 +60,7 @@ constexpr std::array FAMILY_FILES = {{ {"NotoSansCJK-Light.ttc", "NotoSansCJK-Regular.ttc", "NotoSansCJK-Medium.ttc", "NotoSansCJK-Bold.ttc"}, 0}, {{"DFHEI5-SONY.ttf", "DFHEI5-SONY.ttf", "DFHEI5-SONY.ttf", "DFHEI5-SONY.ttf"}, {"NotoSansCJK-Light.ttc", "NotoSansCJK-Regular.ttc", "NotoSansCJK-Medium.ttc", "NotoSansCJK-Bold.ttc"}, 2}, - {{"SCEPS4Yoongd-Medium.otf", "SCEPS4Yoongd-Medium.otf", "SCEPS4Yoongd-Medium.otf", "SCEPS4Yoongd-Bold.otf"}, + {{"SCEPS4Yoongd-Light.otf", "SCEPS4Yoongd-Medium.otf", "SCEPS4Yoongd-Medium.otf", "SCEPS4Yoongd-Bold.otf"}, {"NotoSansCJK-Light.ttc", "NotoSansCJK-Regular.ttc", "NotoSansCJK-Medium.ttc", "NotoSansCJK-Bold.ttc"}, 1}, {{"SSTArabic-Light.otf", "SSTArabic-Roman.otf", "SSTArabic-Medium.otf", "SSTArabic-Bold.otf"}, {"NotoSansArabic-Light.ttf", "NotoSansArabic-Regular.ttf", "NotoSansArabic-Medium.ttf", "NotoSansArabic-Bold.ttf"}, 0}, @@ -154,8 +154,12 @@ std::vector Font::SystemFontCandidates(std::uint32_t fontS } std::filesystem::path Font::SystemFontDirectory() { +#ifdef _WIN32 + const wchar_t* configured = _wgetenv(L"ANYPS5_SYSTEM_FONTS"); +#else const char* configured = std::getenv("ANYPS5_SYSTEM_FONTS"); - if (!configured || configured[0] == '\0') return ExecutableDirectory() / "anyps5-fonts"; +#endif + if (!configured || configured[0] == 0) return ExecutableDirectory() / "anyps5-fonts"; const std::filesystem::path directory(configured); std::error_code error; if (!std::filesystem::is_directory(directory, error)) throw std::runtime_error("libSceFont: ANYPS5_SYSTEM_FONTS is not a directory: " + directory.string()); diff --git a/core/libs/prx/libSceFont/src/Text.cpp b/core/libs/prx/libSceFont/src/Text.cpp index afc8a452f7..9b074211f0 100644 --- a/core/libs/prx/libSceFont/src/Text.cpp +++ b/core/libs/prx/libSceFont/src/Text.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFont/include/Api.hpp" // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -20,6 +21,7 @@ constexpr std::uint16_t FONT_STRING_MAGIC = 0x0F05; constexpr std::uint16_t FONT_WRITING_MAGIC = 0x0F06; constexpr std::uint16_t CREATE_STRING_DETAIL_ID = 0x0FD4; constexpr std::uint32_t WRITING_FORM_HORIZONTAL = 0x10; +constexpr std::uint32_t WRITING_FORM_HORIZONTAL_RTL = 0x11; constexpr std::uint32_t WRITING_FORM_HORIZONTAL_LTR = 0x12; constexpr std::int32_t PARSER_RESULT_ERROR = -1; constexpr std::int32_t PARSER_RESULT_TERMINATE = 0; @@ -62,6 +64,7 @@ struct FontWritingData { float penY = 0.0f; bool hasAggregateMetrics = false; bool hasCurrentStep = false; + bool hasCurrentCharacter = false; bool maskFormatCharacters = true; bool ownsStorage = false; bool hasRawGlyphMetrics = false; @@ -143,6 +146,37 @@ FontTextCharacter* TextCharacters(FontStringData* data) { return data && data->storage && !data->storage->characters.empty() ? data->storage->characters.data() : nullptr; } +bool IsRightToLeftLetter(std::uint32_t code) { + if ((code >= 0x0660u && code <= 0x0669u) || (code >= 0x06F0u && code <= 0x06F9u)) return false; + return (code >= 0x0590u && code <= 0x08FFu) || (code >= 0xFB1Du && code <= 0xFDFFu) || (code >= 0xFE70u && code <= 0xFEFFu) || + (code >= 0x10800u && code <= 0x10FFFu) || (code >= 0x1E800u && code <= 0x1EFFFu); +} + +bool IsDirectionNeutral(std::uint32_t code) { + return code < 0x41u || (code >= 0x5Bu && code <= 0x60u) || (code >= 0x7Bu && code <= 0xBFu) || code == 0xD7u || code == 0xF7u || + (code >= 0x2000u && code <= 0x2BFFu) || (code >= 0x3000u && code <= 0x3004u) || (code >= 0xFF00u && code <= 0xFF20u); +} + +std::uint32_t DefaultStringForm(const std::vector& characters) { + std::uint32_t isolateDepth = 0; + for (const FontTextCharacter& character : characters) { + const std::uint32_t code = character.characterCode; + if (code >= 0x2066u && code <= 0x2068u) { + ++isolateDepth; + continue; + } + if (code == 0x2069u) { + if (isolateDepth != 0) --isolateDepth; + continue; + } + if (isolateDepth != 0) continue; + if (code == 0x200Eu) return WRITING_FORM_HORIZONTAL_LTR; + if (code == 0x200Fu || IsRightToLeftLetter(code)) return WRITING_FORM_HORIZONTAL_RTL; + if (!IsDirectionNeutral(code)) return WRITING_FORM_HORIZONTAL_LTR; + } + return WRITING_FORM_HORIZONTAL_LTR; +} + std::uint32_t LevelOf(const FontTextCharacter* character) { return static_cast((character->flags >> 24) & 0xFFu); } @@ -326,6 +360,7 @@ int APS5_VABI sceFontCreateString(const FontMemory* fontMemory, FontTextSource* 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()); data->renderCount = data->textCount; @@ -650,6 +685,7 @@ const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* fon if (!PopulateWritingStep(*writing, writing->nextIndex)) return nullptr; writing->currentIndex = writing->nextIndex; writing->hasCurrentStep = true; + writing->hasCurrentCharacter = true; AccumulateWritingMetrics(*writing); ++writing->nextIndex; return &writing->step; @@ -658,7 +694,8 @@ 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->storage || writing->currentIndex >= writing->storage->characters.size()) return nullptr; + if (!writing || !writing->hasCurrentStep || !writing->hasCurrentCharacter || !writing->storage || writing->currentIndex >= writing->storage->characters.size()) return nullptr; + writing->hasCurrentCharacter = false; auto& letterStep = writing->letterStep; letterStep = {}; letterStep.x = writing->step.x; diff --git a/core/libs/prx/libSceFont/src/Unimplemented.cpp b/core/libs/prx/libSceFont/src/Unimplemented.cpp index bc79aed66e..20405d21b6 100644 --- a/core/libs/prx/libSceFont/src/Unimplemented.cpp +++ b/core/libs/prx/libSceFont/src/Unimplemented.cpp @@ -1,354 +1,386 @@ +#include "prx/libSceFont/include/Api.hpp" // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "prx/libc/include/General.hpp" +#include "prx/libSceFont/include/FontGraphics.hpp" +#include "prx/libSceFont/include/FontTypes.hpp" + +static_assert(sizeof(FontGraphicsRegion) == 32); +static_assert(offsetof(FontGraphicsRegion, x) == 0); +static_assert(offsetof(FontGraphicsRegion, y) == 4); +static_assert(offsetof(FontGraphicsRegion, width) == 8); +static_assert(offsetof(FontGraphicsRegion, height) == 12); +static_assert(offsetof(FontGraphicsRegion, flags) == 16); +static_assert(offsetof(FontGraphicsRegion, tag) == 20); +static_assert(offsetof(FontGraphicsRegion, outerRadius) == 24); +static_assert(offsetof(FontGraphicsRegion, innerRadius) == 28); + +static_assert(sizeof(FontGraphicsFillMethod) == 32); +static_assert(offsetof(FontGraphicsFillMethod, mode) == 0); +static_assert(offsetof(FontGraphicsFillMethod, type) == 4); +static_assert(offsetof(FontGraphicsFillMethod, flags) == 8); +static_assert(offsetof(FontGraphicsFillMethod, tag) == 12); +static_assert(offsetof(FontGraphicsFillMethod, reserved) == 16); + +static_assert(sizeof(FontGraphicsFillPlot) == 64); +static_assert(offsetof(FontGraphicsFillPlot, mapping) == 0); +static_assert(offsetof(FontGraphicsFillPlot, region) == 16); +static_assert(offsetof(FontGraphicsFillPlot, tag) == 24); +static_assert(offsetof(FontGraphicsFillPlot, reserved26) == 26); +static_assert(offsetof(FontGraphicsFillPlot, layout) == 28); +static_assert(offsetof(FontGraphicsFillPlot, flags) == 30); +static_assert(offsetof(FontGraphicsFillPlot, reserved) == 31); + +static_assert(sizeof(FontGraphicsFillRates) == 64); +static_assert(offsetof(FontGraphicsFillRates, mapping) == 0); +static_assert(offsetof(FontGraphicsFillRates, fill) == 16); +static_assert(offsetof(FontGraphicsFillRates, tag) == 24); +static_assert(offsetof(FontGraphicsFillRates, effect) == 26); +static_assert(offsetof(FontGraphicsFillRates, layout) == 28); +static_assert(offsetof(FontGraphicsFillRates, flags) == 30); +static_assert(offsetof(FontGraphicsFillRates, reserved) == 31); + +static_assert(sizeof(FontGraphicsPoolUsage) == 20); +static_assert(offsetof(FontGraphicsPoolUsage, capacity) == 0); +static_assert(offsetof(FontGraphicsPoolUsage, used) == 4); +static_assert(offsetof(FontGraphicsPoolUsage, available) == 8); +static_assert(offsetof(FontGraphicsPoolUsage, peak) == 12); +static_assert(offsetof(FontGraphicsPoolUsage, change) == 16); + +static_assert(sizeof(FontGraphicsMemoryUsage) == 48); +static_assert(offsetof(FontGraphicsMemoryUsage, storage) == 0); +static_assert(offsetof(FontGraphicsMemoryUsage, allocations) == 20); +static_assert(offsetof(FontGraphicsMemoryUsage, address) == 40); + +static_assert(sizeof(FontGraphicsDeviceUsage) == 96); +static_assert(offsetof(FontGraphicsDeviceUsage, first) == 0); +static_assert(offsetof(FontGraphicsDeviceUsage, second) == 48); #pragma GCC visibility push(default) extern "C" { -int APS5_VABI sceFontControl() { +int APS5_VABI sceFontControl(std::int32_t command, void* object, std::uintptr_t argument) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontCreateGraphicsDevice() { +int APS5_VABI sceFontCreateGraphicsDevice(const FontMemory* memory, const FontGraphicsDeviceParam* param, FontGraphicsDevice** device) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontCreateGraphicsService() { +int APS5_VABI sceFontCreateGraphicsService(const FontMemory* memory, const FontGraphicsServiceParam* param, FontGraphicsService** service) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontCreateGraphicsServiceWithEdition() { +int APS5_VABI sceFontCreateGraphicsServiceWithEdition(const FontMemory* memory, const FontGraphicsServiceParam* param, std::uint64_t unusedSlot, FontGraphicsService** service) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontCreateWords() { +int APS5_VABI sceFontCreateWords(const FontMemory* memory, FontTextSource* source, std::uint64_t unusedSlot, FontWords** words) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontDestroyGraphicsDevice() { +int APS5_VABI sceFontDestroyGraphicsDevice(FontGraphicsDevice** device) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontDestroyGraphicsService() { +int APS5_VABI sceFontDestroyGraphicsService(FontGraphicsService** service) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontDestroyWords() { +int APS5_VABI sceFontDestroyWords(FontWords** words) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGetFontGlyphsOutlineProfile() { +int APS5_VABI sceFontGetGlyphExpandBufferState(FontHandle font, FontGlyphExpandBufferState* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGetFontMetrics() { +int APS5_VABI sceFontGlyphRenderImage(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGetFontStyleInformation() { +int APS5_VABI sceFontGlyphRenderImageHorizontal(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGetGlyphExpandBufferState() { +int APS5_VABI sceFontGlyphRenderImageVertical(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGlyphRenderImage() { +int APS5_VABI sceFontGraphicsBeginFrame(FontGraphicsFrame* frame, std::uint64_t flags, FontGraphicsResource** resource) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGlyphRenderImageHorizontal() { +int APS5_VABI sceFontGraphicsDrawingCancel(FontGraphicsFrame** frame) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGlyphRenderImageVertical() { +int APS5_VABI sceFontGraphicsDrawingFinish(FontGraphicsFrame** frame, FontGraphicsDevice* device) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsBeginFrame() { +int APS5_VABI sceFontGraphicsEndFrame(FontGraphicsFrame* frame) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsDrawingCancel() { +int APS5_VABI sceFontGraphicsExchangeResource(FontGraphicsResource* resource, FontGraphicsDesignResource* replacement, FontGraphicsDesignResource** previous) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsDrawingFinish() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGraphicsEndFrame() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGraphicsExchangeResource() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsFillMethodInit(FontGraphicsFillMethod* method, std::int32_t mode, std::int32_t type) { + return Font::GraphicsFillMethodInit(method, mode, type); } -int APS5_VABI sceFontGraphicsFillMethodInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsFillPlotInit(FontGraphicsFillPlot* plot, const FontGraphicsRegion* region) { + return Font::GraphicsFillPlotInit(plot, region); } -int APS5_VABI sceFontGraphicsFillPlotInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsFillPlotSetLayout(FontGraphicsFillPlot* plot, std::int32_t first, std::int32_t second) { + return Font::GraphicsFillPlotSetLayout(plot, first, second); } -int APS5_VABI sceFontGraphicsFillPlotSetLayout() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsFillPlotSetMapping(FontGraphicsFillPlot* plot, float first, float second, float third, float fourth) { + return Font::GraphicsFillPlotSetMapping(plot, first, second, third, fourth); } -int APS5_VABI sceFontGraphicsFillPlotSetMapping() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsFillRatesInit(FontGraphicsFillRates* rates, const FontGraphicsFill* fill, std::uint32_t effect) { + return Font::GraphicsFillRatesInit(rates, fill, effect); } -int APS5_VABI sceFontGraphicsFillRatesInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsFillRatesSetFillEffect(FontGraphicsFillRates* rates, const FontGraphicsFill* fill, std::uint32_t effect) { + return Font::GraphicsFillRatesSetFillEffect(rates, fill, effect); } -int APS5_VABI sceFontGraphicsFillRatesSetFillEffect() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsFillRatesSetLayout(FontGraphicsFillRates* rates, std::int32_t first, std::int32_t second) { + return Font::GraphicsFillRatesSetLayout(rates, first, second); } -int APS5_VABI sceFontGraphicsFillRatesSetLayout() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsFillRatesSetMapping(FontGraphicsFillRates* rates, float first, float second, float third, float fourth) { + return Font::GraphicsFillRatesSetMapping(rates, first, second, third, fourth); } -int APS5_VABI sceFontGraphicsFillRatesSetMapping() { +int APS5_VABI sceFontGraphicsGetDeviceUsage(FontGraphicsDevice* device, FontGraphicsDeviceUsage* usage) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsGetDeviceUsage() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsRegionInit(FontGraphicsRegion* region, float x, float y, float width, float height) { + return Font::GraphicsRegionInit(region, x, y, width, height); } -int APS5_VABI sceFontGraphicsRegionInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsRegionInitCircular(FontGraphicsRegion* region, float centerX, float centerY, float outerRadius, float innerRadius) { + return Font::GraphicsRegionInitCircular(region, centerX, centerY, outerRadius, innerRadius); } -int APS5_VABI sceFontGraphicsRegionInitCircular() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceFontGraphicsRegionInitRoundish(FontGraphicsRegion* region, float x, float y, float width, float height, float outerRadius, float innerRadius, std::uint32_t flags) { + return Font::GraphicsRegionInitRoundish(region, x, y, width, height, outerRadius, innerRadius, flags); } -int APS5_VABI sceFontGraphicsRegionInitRoundish() { +int APS5_VABI sceFontGraphicsRelease(void** object) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsRelease() { +int APS5_VABI sceFontGraphicsRenderResource(FontGraphicsFrame* frame, FontGraphicsDesignResource* design, float x, float y, FontGraphicsResource** resource) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsRenderResource() { +int APS5_VABI sceFontGraphicsSetFramePolicy(FontGraphicsResource* resource, std::int32_t policy, std::uint32_t value) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetFramePolicy() { +int APS5_VABI sceFontGraphicsSetupClipping(FontGraphicsResource* resource, const FontGraphicsRegion* region) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupClipping() { +int APS5_VABI sceFontGraphicsSetupColorRates(FontGraphicsResource* resource, float red, float green, float blue, float alpha) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupColorRates() { +int APS5_VABI sceFontGraphicsSetupFillMethod(FontGraphicsResource* resource, const FontGraphicsFillMethod* method) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupFillMethod() { +int APS5_VABI sceFontGraphicsSetupFillRates(FontGraphicsResource* resource, const FontGraphicsFillRates* rates) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupFillRates() { +int APS5_VABI sceFontGraphicsSetupGlyphFill(FontGraphicsResource* resource, const FontGraphicsFill* fill) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupGlyphFill() { +int APS5_VABI sceFontGraphicsSetupGlyphFillPlot(FontGraphicsResource* resource, const FontGraphicsFillPlot* plot) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupGlyphFillPlot() { +int APS5_VABI sceFontGraphicsSetupHandleDefault(FontGraphicsResource* resource, std::uint64_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupHandleDefault() { +int APS5_VABI sceFontGraphicsSetupLocation(FontGraphicsResource* resource, float x, float y) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupLocation() { +int APS5_VABI sceFontGraphicsSetupPositioning(FontGraphicsResource* resource, std::uint32_t flags, float x, float y) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupPositioning() { +int APS5_VABI sceFontGraphicsSetupRotation(FontGraphicsResource* resource, float angle, float pivotX, float pivotY) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupRotation() { +int APS5_VABI sceFontGraphicsSetupScaling(FontGraphicsResource* resource, float scaleX, float scaleY, float pivotX, float pivotY) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupScaling() { +int APS5_VABI sceFontGraphicsSetupShapeFill(FontGraphicsResource* resource, const FontGraphicsFill* fill) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupShapeFill() { +int APS5_VABI sceFontGraphicsSetupShapeFillPlot(FontGraphicsResource* resource, const FontGraphicsFillPlot* plot) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsSetupShapeFillPlot() { +int APS5_VABI sceFontGraphicsStructureCanvas(FontGraphicsDevice* device, std::uint32_t width, std::uint32_t height, std::int32_t format, FontGraphicsCanvas** canvas) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsStructureCanvas() { +int APS5_VABI sceFontGraphicsStructureCanvasSequence(FontGraphicsDevice* device, FontGraphicsCanvas* canvas, const FontGraphicsSurface* surface, std::uint64_t flags, FontGraphicsCanvasSequence** sequence) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsStructureCanvasSequence() { +int APS5_VABI sceFontGraphicsStructureDesign(FontGraphicsDevice* device, std::int32_t format, FontGraphicsDesign** design) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsStructureDesign() { +int APS5_VABI sceFontGraphicsStructureDesignResource(FontGraphicsDevice* device, FontGraphicsDesign* design, FontGraphicsDesignResource** resource) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsStructureDesignResource() { +int APS5_VABI sceFontGraphicsStructureSurfaceTexture(FontGraphicsDevice* device, const FontGraphicsSurface* surface, FontGraphicsSurfaceTexture** texture) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsStructureSurfaceTexture() { +int APS5_VABI sceFontGraphicsUpdateClipping(FontGraphicsResource* resource, const FontGraphicsRegion* region) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateClipping() { +int APS5_VABI sceFontGraphicsUpdateColorRates(FontGraphicsResource* resource, float red, float green, float blue, float alpha) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateColorRates() { +int APS5_VABI sceFontGraphicsUpdateFillMethod(FontGraphicsResource* resource, const FontGraphicsFillMethod* method) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateFillMethod() { +int APS5_VABI sceFontGraphicsUpdateFillRates(FontGraphicsResource* resource, const FontGraphicsFillRates* rates) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateFillRates() { +int APS5_VABI sceFontGraphicsUpdateGlyphFill(FontGraphicsResource* resource, const FontGraphicsFill* fill) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateGlyphFill() { +int APS5_VABI sceFontGraphicsUpdateGlyphFillPlot(FontGraphicsResource* resource, const FontGraphicsFillPlot* plot) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateGlyphFillPlot() { +int APS5_VABI sceFontGraphicsUpdateLocation(FontGraphicsResource* resource, float x, float y) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateLocation() { +int APS5_VABI sceFontGraphicsUpdatePositioning(FontGraphicsResource* resource, std::uint32_t flags, float x, float y) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdatePositioning() { +int APS5_VABI sceFontGraphicsUpdateRotation(FontGraphicsResource* resource, float angle, float pivotX, float pivotY) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateRotation() { +int APS5_VABI sceFontGraphicsUpdateScaling(FontGraphicsResource* resource, float scaleX, float scaleY, float pivotX, float pivotY) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateScaling() { +int APS5_VABI sceFontGraphicsUpdateShapeFill(FontGraphicsResource* resource, const FontGraphicsFill* fill) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateShapeFill() { +int APS5_VABI sceFontGraphicsUpdateShapeFillPlot(FontGraphicsResource* resource, const FontGraphicsFillPlot* plot) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsUpdateShapeFillPlot() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceFontSetGraphicsErrorCallback(Font::GraphicsErrorCallback callback) { + Font::SetGraphicsErrorCallback(callback); } -int APS5_VABI sceFontSupportGlyphs() { +int APS5_VABI sceFontSupportGlyphs(const FontGlyphSupport* support) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontWordsFindWordCharacters() { +int APS5_VABI sceFontWordsFindWordCharacters(FontWords* words, FontTextCharacter* firstCharacter, FontTextCharacter* endCharacter, FontTextCharacter** lastCharacter, FontTextCharacter** nextCharacter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsDrawupFillTextureImageObject(void) { +int APS5_VABI sceFontGraphicsDrawupFillTextureImageObject(FontGraphicsFrame* frame, const void* data, std::uint32_t dataSize, float x, float y, float width, float height, FontGraphicsFill** fill) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsDrawupFillTexturePatternObject(void) { +int APS5_VABI sceFontGraphicsDrawupFillTexturePatternObject(FontGraphicsFrame* frame, const void* data, std::uint32_t dataSize, std::uint32_t firstMode, std::uint32_t secondMode, std::int32_t firstCount, std::int32_t firstLayout, std::int32_t secondLayout, std::int32_t secondCount, FontGraphicsFill** fill) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceFont/src/WritingLine.cpp b/core/libs/prx/libSceFont/src/WritingLine.cpp index 35e014c3c8..95771489a0 100644 --- a/core/libs/prx/libSceFont/src/WritingLine.cpp +++ b/core/libs/prx/libSceFont/src/WritingLine.cpp @@ -1,7 +1,8 @@ +#include "prx/libSceFont/include/Api.hpp" #include #include #include -#include +#include #include #include #include @@ -11,24 +12,19 @@ namespace { -constexpr std::int32_t WRITING_FORM_HORIZONTAL = 0x10; constexpr std::int32_t WRITING_FORM_HORIZONTAL_RTL = 0x11; constexpr std::int32_t WRITING_FORM_HORIZONTAL_LTR = 0x12; -constexpr std::uint16_t CREATE_WRITING_LINE_DETAIL_ID = 0x0FD5; constexpr std::uint64_t WRITING_ATTRIBUTE_ID_MASK = 0xFF; constexpr std::uint64_t WRITING_ATTRIBUTE_ID = 0xFA; constexpr std::uint64_t WRITING_ATTRIBUTE_SPACE_BEFORE = std::uint64_t{1} << 30; constexpr std::uint64_t WRITING_ATTRIBUTE_SPACE_AFTER = std::uint64_t{1} << 31; constexpr std::size_t WRITING_LEVEL_COUNT = 0x7F; -struct CreateWritingLineDetail { - std::uint16_t detailId; -}; - struct WritingOrder { void* orderer = nullptr; WritingOrder* prev = nullptr; WritingOrder* next = nullptr; + std::uint64_t reserved = 0; std::uint64_t attribute = 0; FontWritingMetrics metrics{}; }; @@ -39,22 +35,42 @@ struct WritingLevel { }; struct WritingLine { + std::uint16_t kind = 0xF0A; + std::uint16_t reserved = 0; std::int32_t form = 0; + const FontMemory* memory = nullptr; FontWritingMetrics renderMetrics{}; FontWritingLineStep step{}; float advanceX = 0.0f; float advanceY = 0.0f; float orderingSpace = 0.0f; float steppedSpace = 0.0f; - std::size_t lastLevel = 0; + std::uint32_t lastLevel = 0; + std::uint32_t reservedLevel = 0; + WritingOrder* first = nullptr; WritingOrder* last = nullptr; WritingOrder* head = nullptr; WritingOrder* tail = nullptr; WritingOrder* cursor = nullptr; + std::uint64_t reservedCursor = 0; std::array levels{}; - std::deque orders; }; +static_assert(sizeof(WritingOrder) == 64); +static_assert(offsetof(WritingOrder, attribute) == 32); +static_assert(offsetof(WritingOrder, metrics) == 40); +static_assert(sizeof(WritingLine) == 2208); +static_assert(offsetof(WritingLine, step) == 40); +static_assert(offsetof(WritingLine, advanceX) == 104); +static_assert(offsetof(WritingLine, last) == 136); +static_assert(offsetof(WritingLine, head) == 144); +static_assert(offsetof(WritingLine, cursor) == 160); +static_assert(offsetof(WritingLine, levels) == 176); + +bool IsMemoryValid(const FontMemory* memory) { + return memory && memory->mem_kind == 0xF00 && memory->iface && memory->iface->alloc && memory->iface->dealloc; +} + std::mutex linesMutex; std::unordered_set lines; @@ -62,13 +78,6 @@ std::unordered_set lines; throw std::runtime_error(std::string(function) + ": " + what); } -WritingLine* GetLine(const char* function, void* writingLine) { - auto* line = static_cast(writingLine); - std::lock_guard lock(linesMutex); - if (!lines.contains(line)) Unsupported(function, "unknown writing line handle (error code not verified)"); - return line; -} - bool IsRightToLeft(const WritingLine& line) { return line.form == WRITING_FORM_HORIZONTAL_RTL; } @@ -94,7 +103,13 @@ float TailSpace(const WritingLine& line, const WritingOrder& order) { } void ResetOrders(WritingLine& line) { - line.orders.clear(); + for (WritingOrder* order = line.head; order;) { + WritingOrder* next = order->next; + std::destroy_at(order); + line.memory->iface->dealloc(line.memory->mspace_handle, order); + order = next; + } + line.first = nullptr; line.levels.fill({}); line.advanceX = 0.0f; line.advanceY = 0.0f; @@ -217,50 +232,71 @@ void ExtendRenderMetrics(WritingLine& line) { extern "C" { int APS5_VABI sceFontCreateWritingLine(const FontMemory* memory, std::int32_t writingForm, const void* writingLineDetail, void** pWritingLine) { - (void)memory; - if (!pWritingLine) Unsupported(__func__, "null writing line pointer (error code not verified)"); - if (writingForm != WRITING_FORM_HORIZONTAL && writingForm != WRITING_FORM_HORIZONTAL_RTL && writingForm != WRITING_FORM_HORIZONTAL_LTR) - Unsupported(__func__, "writing form " + std::to_string(writingForm) + " is not modelled"); - if (writingLineDetail && static_cast(writingLineDetail)->detailId != CREATE_WRITING_LINE_DETAIL_ID) - Unsupported(__func__, "unknown writing line detail id"); - auto* line = new WritingLine{}; + if (!memory) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (!IsMemoryValid(memory)) return SCE_FONT_ERROR_INVALID_MEMORY; + if (!pWritingLine || writingLineDetail || (writingForm != WRITING_FORM_HORIZONTAL_RTL && writingForm != WRITING_FORM_HORIZONTAL_LTR)) return SCE_FONT_ERROR_INVALID_PARAMETER; + void* allocation = memory->iface->alloc(memory->mspace_handle, sizeof(WritingLine)); + if (!allocation) return SCE_FONT_ERROR_ALLOCATION_FAILED; + auto* line = std::construct_at(static_cast(allocation)); + line->memory = memory; line->form = writingForm; { std::lock_guard lock(linesMutex); - lines.insert(line); + try { + lines.insert(line); + } catch (...) { + std::destroy_at(line); + memory->iface->dealloc(memory->mspace_handle, allocation); + throw; + } } *pWritingLine = line; return SCE_FONT_OK; } int APS5_VABI sceFontDestroyWritingLine(void** pWritingLine) { - if (!pWritingLine || !*pWritingLine) return SCE_FONT_OK; - auto* line = GetLine(__func__, *pWritingLine); + if (!pWritingLine || !*pWritingLine) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* line = static_cast(*pWritingLine); { std::lock_guard lock(linesMutex); + if (!lines.contains(line) || !IsMemoryValid(line->memory)) return SCE_FONT_ERROR_INVALID_WRITING; lines.erase(line); } - delete line; + const FontMemory* memory = line->memory; + ResetOrders(*line); + line->kind = 0; + std::destroy_at(line); + memory->iface->dealloc(memory->mspace_handle, line); *pWritingLine = nullptr; return SCE_FONT_OK; } int APS5_VABI sceFontWritingLineClear(void* writingLine) { - auto* line = GetLine(__func__, writingLine); + if (!writingLine) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* line = static_cast(writingLine); + { + std::lock_guard lock(linesMutex); + if (!lines.contains(line)) return SCE_FONT_ERROR_INVALID_WRITING; + } + if (!IsMemoryValid(line->memory)) return SCE_FONT_ERROR_INVALID_WRITING; ResetOrders(*line); return SCE_FONT_OK; } int APS5_VABI sceFontWritingLineWritesOrder(void* writingLine, std::uint64_t writingAttribute, const FontWritingMetrics* writingMetrics, void* writingOrderer) { - auto* line = GetLine(__func__, writingLine); - if (writingAttribute == 0) writingAttribute = WRITING_ATTRIBUTE_ID; - if ((writingAttribute & WRITING_ATTRIBUTE_ID_MASK) != WRITING_ATTRIBUTE_ID) - Unsupported(__func__, "writing attribute " + std::to_string(writingAttribute) + " is not modelled"); const std::size_t level = (writingAttribute >> 8) & 0xFF; - if (level >= WRITING_LEVEL_COUNT) Unsupported(__func__, "embedding level " + std::to_string(level) + " (error code not verified)"); - if (!writingOrderer) Unsupported(__func__, "null writing orderer (error code not verified)"); + if (!writingLine || !writingOrderer || (writingAttribute & WRITING_ATTRIBUTE_ID_MASK) != WRITING_ATTRIBUTE_ID || level >= WRITING_LEVEL_COUNT) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* line = static_cast(writingLine); + { + std::lock_guard lock(linesMutex); + if (!lines.contains(line)) return SCE_FONT_ERROR_INVALID_WRITING; + } + if (!IsMemoryValid(line->memory)) return SCE_FONT_ERROR_INVALID_PARAMETER; if (line->cursor) ResetOrders(*line); - WritingOrder* order = &line->orders.emplace_back(); + void* allocation = line->memory->iface->alloc(line->memory->mspace_handle, sizeof(WritingOrder)); + if (!allocation) return SCE_FONT_ERROR_ALLOCATION_FAILED; + WritingOrder* order = std::construct_at(static_cast(allocation)); + if (!line->last) line->first = order; order->orderer = writingOrderer; order->attribute = writingAttribute; if (writingMetrics) order->metrics = *writingMetrics; @@ -271,13 +307,17 @@ int APS5_VABI sceFontWritingLineWritesOrder(void* writingLine, std::uint64_t wri line->advanceX += order->metrics.advanceX; line->advanceY += order->metrics.advanceY; line->last = order; - line->lastLevel = level; + line->lastLevel = static_cast(level); return SCE_FONT_OK; } const FontWritingLineStep* APS5_VABI sceFontWritingLineRefersRenderStep(void* writingLine) { if (!writingLine) return nullptr; - auto* line = GetLine(__func__, writingLine); + auto* line = static_cast(writingLine); + { + std::lock_guard lock(linesMutex); + if (!lines.contains(line)) return nullptr; + } if (line->cursor) { ExtendRenderMetrics(*line); } else { @@ -319,14 +359,32 @@ const FontWritingLineStep* APS5_VABI sceFontWritingLineRefersRenderStep(void* wr } int APS5_VABI sceFontWritingLineGetRenderMetrics(void* writingLine, FontWritingMetrics* writingMetrics) { - auto* line = GetLine(__func__, writingLine); - if (!writingMetrics) Unsupported(__func__, "null metrics pointer (error code not verified)"); + if (!writingMetrics) return SCE_FONT_ERROR_INVALID_PARAMETER; + *writingMetrics = {}; + if (!writingLine) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* line = static_cast(writingLine); + std::lock_guard lock(linesMutex); + if (!lines.contains(line)) return SCE_FONT_ERROR_INVALID_WRITING; *writingMetrics = line->renderMetrics; return SCE_FONT_OK; } int APS5_VABI sceFontWritingLineGetOrderingSpace(void* writingLine, float* advanceSpace, float* headSpace, float* inlineSpace, float* tailSpace) { - auto* line = GetLine(__func__, writingLine); + auto* line = static_cast(writingLine); + int error = SCE_FONT_OK; + { + std::lock_guard lock(linesMutex); + if (!line) error = SCE_FONT_ERROR_INVALID_PARAMETER; + else if (!lines.contains(line)) error = SCE_FONT_ERROR_INVALID_WRITING; + else if (line->form != WRITING_FORM_HORIZONTAL_RTL && line->form != WRITING_FORM_HORIZONTAL_LTR) error = SCE_FONT_ERROR_INVALID_PARAMETER; + } + if (error != SCE_FONT_OK) { + if (advanceSpace) *advanceSpace = 0.0f; + if (headSpace) *headSpace = 0.0f; + if (inlineSpace) *inlineSpace = 0.0f; + if (tailSpace) *tailSpace = 0.0f; + return error; + } if (headSpace && !tailSpace) Unsupported(__func__, "a head space pointer without a tail space pointer is not modelled"); if (advanceSpace) *advanceSpace = line->advanceX; if (headSpace) { diff --git a/core/libs/prx/libSceFontFt-module/libSceFontFt-module.hpp b/core/libs/prx/libSceFontFt-module/libSceFontFt-module.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceFontFt-module/libSceFontFt-module.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceFontFt/Export.cpp b/core/libs/prx/libSceFontFt/Export.cpp index 0dcd8bcf90..c376b1c590 100644 --- a/core/libs/prx/libSceFontFt/Export.cpp +++ b/core/libs/prx/libSceFontFt/Export.cpp @@ -1,41 +1,12 @@ +#include "prx/libSceFontFt/include/Api.hpp" // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include - -#include -#include FT_FREETYPE_H -#include FT_MODULE_H - #include "prx/libSceFontFt/include/FontFtDriver.hpp" #include "prx/libc/include/General.hpp" #pragma GCC visibility push(default) -namespace { - -FT_Library ProbeLibrary() { - static const FT_Library library = [] { - FT_Library created = nullptr; - if (FT_Init_FreeType(&created) != 0) return static_cast(nullptr); - return created; - }(); - return library; -} - -int SupportFormat(const char* function, std::initializer_list modules) { - const FT_Library library = ProbeLibrary(); - if (library == nullptr) throw std::runtime_error(std::string(function) + ": FreeType failed to initialize"); - for (const char* module : modules) { - if (FT_Get_Module(library, module) == nullptr) throw std::runtime_error(std::string(function) + ": FreeType module " + module + " is not built in"); - } - return 0; -} - -} - extern "C" { const Font::SysDriver* APS5_VABI sceFontSelectLibraryFt(int value) { @@ -46,86 +17,85 @@ const Font::RendererSelection* APS5_VABI sceFontSelectRendererFt(int value) { return value == 0 ? FontFt::RendererTable() : nullptr; } -int APS5_VABI sceFontFtInitAliases() { +int APS5_VABI sceFontFtInitAliases(FontFtAliases** aliases) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontFtSetAliasFont() { +int APS5_VABI sceFontFtSetAliasFont(const char* alias, std::uint32_t fontId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontFtSetAliasPath() { +int APS5_VABI sceFontFtSetAliasPath(const char* alias, const char* path) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontFtSupportBdf() { - return SupportFormat(__func__, {"bdf"}); +int APS5_VABI sceFontFtSupportBdf(FontLibrary library) { + return FontFt::SupportFormats(library, {"bdf"}); } -int APS5_VABI sceFontFtSupportCid() { - return SupportFormat(__func__, {"t1cid"}); +int APS5_VABI sceFontFtSupportCid(FontLibrary library) { + return FontFt::SupportFormats(library, {"t1cid"}); } -int APS5_VABI sceFontFtSupportFontFormats() { +int APS5_VABI sceFontFtSupportFontFormats(FontLibrary library, std::uint32_t formats) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontFtSupportOpenType() { - return SupportFormat(__func__, {"cff", "truetype"}); +int APS5_VABI sceFontFtSupportOpenType(FontLibrary library) { + return FontFt::SupportFormats(library, {"cff", "truetype"}); } -int APS5_VABI sceFontFtSupportOpenTypeOtf() { - return SupportFormat(__func__, {"cff"}); +int APS5_VABI sceFontFtSupportOpenTypeOtf(FontLibrary library) { + return FontFt::SupportFormats(library, {"cff"}); } -int APS5_VABI sceFontFtSupportOpenTypeTtf() { - return SupportFormat(__func__, {"truetype"}); +int APS5_VABI sceFontFtSupportOpenTypeTtf(FontLibrary library) { + return FontFt::SupportFormats(library, {"truetype"}); } -int APS5_VABI sceFontFtSupportPcf() { - return SupportFormat(__func__, {"pcf"}); +int APS5_VABI sceFontFtSupportPcf(FontLibrary library) { + return FontFt::SupportFormats(library, {"pcf"}); } -int APS5_VABI sceFontFtSupportPfr() { - return SupportFormat(__func__, {"pfr"}); +int APS5_VABI sceFontFtSupportPfr(FontLibrary library) { + return FontFt::SupportFormats(library, {"pfr"}); } -int APS5_VABI sceFontFtSupportSystemFonts() { +int APS5_VABI sceFontFtSupportSystemFonts(FontLibrary library) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontFtSupportTrueType() { - return SupportFormat(__func__, {"truetype"}); +int APS5_VABI sceFontFtSupportTrueType(FontLibrary library) { + return FontFt::SupportFormats(library, {"truetype"}); } -int APS5_VABI sceFontFtSupportTrueTypeGx() { +int APS5_VABI sceFontFtSupportTrueTypeGx(FontLibrary library) { + return FontFt::SupportFormats(library, {"truetype"}); +} + +int APS5_VABI sceFontFtSupportType1(FontLibrary library) { + return FontFt::SupportFormats(library, {"type1"}); +} + +int APS5_VABI sceFontFtSupportType42(FontLibrary library) { + return FontFt::SupportFormats(library, {"type42"}); +} + +int APS5_VABI sceFontFtSupportWinFonts(FontLibrary library) { + return FontFt::SupportFormats(library, {"winfonts"}); +} + +int APS5_VABI sceFontFtTermAliases(FontFtAliases** aliases) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontFtSupportType1() { - return SupportFormat(__func__, {"type1"}); -} - -int APS5_VABI sceFontFtSupportType42() { - return SupportFormat(__func__, {"type42"}); -} - -int APS5_VABI sceFontFtSupportWinFonts() { - return SupportFormat(__func__, {"winfonts"}); -} - -int APS5_VABI sceFontFtTermAliases() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSelectGlyphsFt() { +const FontFtGlyphSelection* APS5_VABI sceFontSelectGlyphsFt(std::int32_t selector) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceFontFt/include/Api.hpp b/core/libs/prx/libSceFontFt/include/Api.hpp new file mode 100644 index 0000000000..b31e84ae16 --- /dev/null +++ b/core/libs/prx/libSceFontFt/include/Api.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "prx/libSceFont/include/FontTypes.hpp" +#include "prx/libSceFont/include/FontDriver.hpp" +#include "SceTypes.hpp" + +struct FontFtAliases; + +struct FontFtGlyphSelection; + +extern "C" { + +const Font::SysDriver* APS5_VABI sceFontSelectLibraryFt(int value); +const Font::RendererSelection* APS5_VABI sceFontSelectRendererFt(int value); +int APS5_VABI sceFontFtInitAliases(FontFtAliases** aliases); +int APS5_VABI sceFontFtSetAliasFont(const char* alias, std::uint32_t fontId); +int APS5_VABI sceFontFtSetAliasPath(const char* alias, const char* path); +int APS5_VABI sceFontFtSupportBdf(FontLibrary library); +int APS5_VABI sceFontFtSupportCid(FontLibrary library); +int APS5_VABI sceFontFtSupportFontFormats(FontLibrary library, std::uint32_t formats); +int APS5_VABI sceFontFtSupportOpenType(FontLibrary library); +int APS5_VABI sceFontFtSupportOpenTypeOtf(FontLibrary library); +int APS5_VABI sceFontFtSupportOpenTypeTtf(FontLibrary library); +int APS5_VABI sceFontFtSupportPcf(FontLibrary library); +int APS5_VABI sceFontFtSupportPfr(FontLibrary library); +int APS5_VABI sceFontFtSupportSystemFonts(FontLibrary library); +int APS5_VABI sceFontFtSupportTrueType(FontLibrary library); +int APS5_VABI sceFontFtSupportTrueTypeGx(FontLibrary library); +int APS5_VABI sceFontFtSupportType1(FontLibrary library); +int APS5_VABI sceFontFtSupportType42(FontLibrary library); +int APS5_VABI sceFontFtSupportWinFonts(FontLibrary library); +int APS5_VABI sceFontFtTermAliases(FontFtAliases** aliases); +const FontFtGlyphSelection* APS5_VABI sceFontSelectGlyphsFt(std::int32_t selector); + +} diff --git a/core/libs/prx/libSceFontFt/include/FontFtDriver.hpp b/core/libs/prx/libSceFontFt/include/FontFtDriver.hpp index 87e7e92931..35ba74ff66 100644 --- a/core/libs/prx/libSceFontFt/include/FontFtDriver.hpp +++ b/core/libs/prx/libSceFontFt/include/FontFtDriver.hpp @@ -4,12 +4,15 @@ #ifndef CORE_LIBS_PRX_LIBSCEFONTFT_INCLUDE_FONTFTDRIVER_HPP #define CORE_LIBS_PRX_LIBSCEFONTFT_INCLUDE_FONTFTDRIVER_HPP +#include + #include "prx/libSceFont/include/FontDriver.hpp" namespace FontFt { const Font::SysDriver* DriverTable(); const Font::RendererSelection* RendererTable(); +int SupportFormats(FontLibrary library, std::initializer_list modules); } diff --git a/core/libs/prx/libSceFontFt/libSceFontFt.hpp b/core/libs/prx/libSceFontFt/libSceFontFt.hpp new file mode 100644 index 0000000000..a14b5f23e0 --- /dev/null +++ b/core/libs/prx/libSceFontFt/libSceFontFt.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceFontFt/include/Api.hpp" diff --git a/core/libs/prx/libSceFontFt/src/Driver.cpp b/core/libs/prx/libSceFontFt/src/Driver.cpp index 7f49954e0a..7ac80488ea 100644 --- a/core/libs/prx/libSceFontFt/src/Driver.cpp +++ b/core/libs/prx/libSceFontFt/src/Driver.cpp @@ -1,13 +1,23 @@ +#include "prx/libSceFontFt/include/Api.hpp" // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include +#include +#include +#include #include #include #include #include #include +#include #include +#ifdef _WIN32 +#include +#endif #include #include FT_FREETYPE_H @@ -193,6 +203,41 @@ int APS5_VABI LibrarySupport(FontLibNative* library, std::uint32_t formats) { return SCE_FONT_OK; } +#ifdef _WIN32 +void CloseMappedFile(FT_Stream stream) { + UnmapViewOfFile(stream->base); + delete stream; +} + +FT_Error OpenFileFace(FT_Library library, const char* path, FT_Long subFontIndex, FT_Face* face) { + const HANDLE file = CreateFileW(std::filesystem::path(reinterpret_cast(path)).c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) return FT_Err_Cannot_Open_Resource; + LARGE_INTEGER size{}; + if (!GetFileSizeEx(file, &size) || size.QuadPart <= 0 || size.QuadPart > std::numeric_limits::max()) { + CloseHandle(file); + return FT_Err_Cannot_Open_Stream; + } + const HANDLE mapping = CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr); + CloseHandle(file); + if (!mapping) return FT_Err_Cannot_Open_Stream; + void* view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); + CloseHandle(mapping); + if (!view) return FT_Err_Cannot_Open_Stream; + auto* stream = new FT_StreamRec{}; + stream->base = static_cast(view); + stream->size = static_cast(size.QuadPart); + stream->close = &CloseMappedFile; + FT_Open_Args args{}; + args.flags = FT_OPEN_STREAM; + args.stream = stream; + return FT_Open_Face(library, &args, subFontIndex, face); +} +#else +FT_Error OpenFileFace(FT_Library library, const char* path, FT_Long subFontIndex, FT_Face* face) { + return FT_New_Face(library, path, subFontIndex, face); +} +#endif + int APS5_VABI LibraryOpenFont(FontLibNative* library, std::uint32_t mode, const void* fontAddress, std::uint32_t fontSize, std::uint32_t subFontIndex, std::uint32_t uniqueWord, FontObj** inoutFontObj) { (void)uniqueWord; if (!library || !fontAddress || !inoutFontObj) return SCE_FONT_ERROR_INVALID_PARAMETER; @@ -217,7 +262,7 @@ int APS5_VABI LibraryOpenFont(FontLibNative* library, std::uint32_t mode, const if (fromMemory) { error = FT_New_Memory_Face(*ftLibrary, static_cast(fontAddress), static_cast(fontSize), static_cast(subFontIndex), &face); } else { - error = FT_New_Face(*ftLibrary, static_cast(fontAddress), static_cast(subFontIndex), &face); + error = OpenFileFace(*ftLibrary, static_cast(fontAddress), static_cast(subFontIndex), &face); } if (error != 0 || !face) { if (fromMemory || error == FT_Err_Unknown_File_Format) return SCE_FONT_ERROR_NO_SUPPORT_FORMAT; @@ -264,7 +309,7 @@ int APS5_VABI LibraryCloseFont(FontObj* obj, std::uint32_t flags) { return SCE_FONT_OK; } -int APS5_VABI LibraryGetFaceScale(FontObj* obj, std::uint16_t* outUnitsPerEm, float* outScale) { +int APS5_VABI LibraryGetFaceScale(FontObj* obj, std::uint32_t* outUnitsPerEm, float* outScale) { if (!obj || !outUnitsPerEm || !outScale) return SCE_FONT_ERROR_FATAL; const FT_Face face = FaceOf(obj); if (!face) return SCE_FONT_ERROR_FATAL; @@ -733,6 +778,143 @@ std::uint64_t APS5_VABI RendererQuery(RendererNative* renderer, std::uint8_t* pa return 0; } +float ScaledUnits(std::int64_t units, std::int64_t fixed16) { + return FromF26Dot6(RoundMulFixed(units, fixed16)); +} + +void StoreVerticalFontMetrics(FT_Face face, const TT_OS2* os2, std::int64_t xShift, std::int64_t yShift, FontMetrics* metrics) { + const auto xScale = static_cast(face->size->metrics.x_scale); + const auto yScale = static_cast(face->size->metrics.y_scale); + const auto unitsPerEm = static_cast(static_cast(face->units_per_EM)); + const auto* vhea = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_VHEA)); + if (vhea) { + const std::int64_t minTop = vhea->min_Top_Side_Bearing; + metrics->ascender = ScaledUnits(minTop + yShift, yScale); + metrics->descender = ScaledUnits(static_cast(vhea->yMax_Extent) + yShift, yScale); + metrics->lineGap = 0.0f; + if (vhea->Version >= 0x110000 && vhea->Line_Gap != 0) { + metrics->lineGap = FromF26Dot6(RoundMulFixed(vhea->Line_Gap, xScale) - TruncFixed(unitsPerEm * xScale)); + } + metrics->verticalDescender = ScaledUnits(static_cast(vhea->Descender) + xShift, xScale); + const std::int64_t ascender = os2 ? os2->sTypoAscender : static_cast(minTop + face->bbox.yMax); + metrics->verticalAscender = ScaledUnits(ascender + yShift, yScale); + } else { + std::int64_t ascender = 0; + if (os2) { + ascender = os2->sTypoAscender; + metrics->ascender = ScaledUnits(ascender - face->bbox.yMax, yScale); + metrics->descender = ScaledUnits(ascender - face->bbox.yMin, yScale); + metrics->lineGap = 0.0f; + } else { + const std::int32_t lineGap = face->height - face->ascender + face->descender; + const std::int32_t halfGap = lineGap / 2; + metrics->ascender = ScaledUnits(halfGap, yScale); + metrics->descender = ScaledUnits(face->ascender - face->descender + halfGap, yScale); + metrics->lineGap = ScaledUnits(lineGap, xScale); + ascender = face->ascender + halfGap; + } + metrics->verticalAscender = ScaledUnits(ascender + yShift, yScale); + metrics->verticalDescender = ScaledUnits(-static_cast(unitsPerEm / 2), xScale); + } + metrics->maxAdvance = ScaledUnits(face->max_advance_height, yScale); +} + +std::uint32_t StoreHorizontalFontMetrics(FT_Face face, const TT_OS2* os2, std::uint32_t form, std::int64_t xShift, std::int64_t yShift, FontMetrics* metrics) { + const auto xScale = static_cast(face->size->metrics.x_scale); + const auto yScale = static_cast(face->size->metrics.y_scale); + const auto* hhea = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_HHEA)); + metrics->verticalDescender = 0.0f; + metrics->verticalAscender = 0.0f; + bool fromHorizontalHeader = form == 1 || ((form == 2 || form == 3) && !os2); + if (form == 2 && os2) { + metrics->ascender = ScaledUnits(static_cast(os2->sTypoAscender) + yShift, yScale); + metrics->descender = ScaledUnits(static_cast(os2->sTypoDescender) + yShift, yScale); + metrics->lineGap = ScaledUnits(os2->sTypoLineGap, yScale); + fromHorizontalHeader = metrics->ascender == 0.0f && metrics->descender == 0.0f; + } else if (form == 3 && os2) { + metrics->ascender = ScaledUnits(static_cast(os2->usWinAscent) + yShift, yScale); + metrics->descender = FromF26Dot6(-RoundMulFixed(static_cast(os2->usWinDescent) - yShift, yScale)); + metrics->lineGap = hhea ? ScaledUnits(hhea->Line_Gap, yScale) : 0.0f; + fromHorizontalHeader = metrics->ascender == metrics->descender; + } + if (fromHorizontalHeader) { + form = 1; + if (hhea) { + metrics->ascender = ScaledUnits(static_cast(hhea->Ascender) + yShift, yScale); + metrics->descender = ScaledUnits(static_cast(hhea->Descender) + yShift, yScale); + metrics->lineGap = ScaledUnits(hhea->Line_Gap, yScale); + } else { + metrics->ascender = ScaledUnits(static_cast(face->ascender) + yShift, yScale); + metrics->descender = ScaledUnits(static_cast(face->descender) + yShift, yScale); + metrics->lineGap = 0.0f; + } + } + metrics->maxAdvance = ScaledUnits(static_cast(face->max_advance_width) + xShift, xScale); + return form; +} + +int APS5_VABI LibraryGetFontMetrics(FontObj* obj, std::uint32_t form, FontMetrics* metrics) { + const FT_Face face = obj ? FaceOf(obj) : nullptr; + if (!face || !face->size || !metrics) return SCE_FONT_ERROR_FATAL; + const auto xScale = static_cast(face->size->metrics.x_scale); + const auto yScale = static_cast(face->size->metrics.y_scale); + const std::int64_t xShift = obj->shift_cache_x; + const std::int64_t yShift = obj->shift_cache_y; + const auto* os2 = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_OS2)); + if (os2 && os2->version == 0xFFFF) os2 = nullptr; + if (form == 4) { + StoreVerticalFontMetrics(face, os2, xShift, yShift, metrics); + } else { + form = StoreHorizontalFontMetrics(face, os2, form, xShift, yShift, metrics); + } + metrics->xMin = ScaledUnits(static_cast(face->bbox.xMin) + xShift, xScale); + metrics->yMin = ScaledUnits(static_cast(face->bbox.yMin) + yShift, yScale); + metrics->xMax = ScaledUnits(static_cast(face->bbox.xMax) + xShift, xScale); + metrics->yMax = ScaledUnits(static_cast(face->bbox.yMax) + yShift, yScale); + const auto unitsPerEm = static_cast(static_cast(face->units_per_EM)); + metrics->emWidth = FromF26Dot6(TruncFixed(unitsPerEm * xScale)); + metrics->emHeight = FromF26Dot6(TruncFixed(unitsPerEm * yScale)); + const auto* handle = reinterpret_cast(obj->font_handle); + metrics->effectWeightX = handle ? handle->style.effect_weight_x : 0.0f; + metrics->effectWeightY = handle ? handle->style.effect_weight_y : 0.0f; + metrics->slantRatio = handle ? handle->style.slant_ratio : 0.0f; + metrics->form = form; + return SCE_FONT_OK; +} + +int APS5_VABI LibraryGetOutlineProfile(FontObj* obj, FontOutlineProfile* profile) { + const FT_Face face = obj ? FaceOf(obj) : nullptr; + if (!face) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + const auto* maxp = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_MAXP)); + if (!maxp) throw std::runtime_error("FontFt: outline profile of a face without a maxp table"); + if (maxp->version < 0x10000) return SCE_FONT_ERROR_NO_SUPPORT_FUNCTION; + profile->maxContours = maxp->maxContours; + profile->maxPoints = maxp->maxPoints; + profile->maxCompositeContours = maxp->maxCompositeContours; + profile->maxCompositePoints = maxp->maxCompositePoints; + return SCE_FONT_OK; +} + +std::uint16_t WeightClass(std::uint16_t weight) { + return weight >= 100 && weight <= 900 && weight % 100 == 0 ? static_cast(weight / 100) : 0; +} + +int APS5_VABI LibraryGetFontStyleInformation(FontObj* obj, FontStyleInformation* information) { + const FT_Face face = obj ? FaceOf(obj) : nullptr; + if (!face || !information) return SCE_FONT_ERROR_FATAL; + const auto* header = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_HEAD)); + if (!header) throw std::runtime_error("FontFt: style information of a face without a head table"); + const auto* os2 = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_OS2)); + if (os2 && os2->version == 0xFFFF) os2 = nullptr; + information->familyName = face->family_name; + information->styleName = face->style_name; + information->styleFlags = static_cast(face->style_flags & (FT_STYLE_FLAG_ITALIC | FT_STYLE_FLAG_BOLD)); + information->weight = os2 ? WeightClass(os2->usWeightClass) : 0; + information->width = os2 && os2->usWidthClass < 10 ? os2->usWidthClass : 0; + information->layoutFlags = static_cast(1 + (header->Font_Direction < 0 ? 1 : 0) + (FT_HAS_VERTICAL(face) ? 0x80 : 0)); + return SCE_FONT_OK; +} + SysDriver MakeDriverTable() { SysDriver driver{}; driver.pixel_resolution = &LibraryGetPixelResolution; @@ -747,6 +929,9 @@ SysDriver MakeDriverTable() { driver.glyph_index = &LibraryGetGlyphIndex; driver.set_char_with_dpi = &LibrarySetCharSizeWithDpi; driver.set_char_default_dpi = &LibrarySetCharSizeDefaultDpi; + driver.font_metrics = &LibraryGetFontMetrics; + driver.style_information = &LibraryGetFontStyleInformation; + driver.outline_profile = &LibraryGetOutlineProfile; driver.compute_layout = &LibraryComputeLayout; driver.compute_layout_alt = &LibraryComputeLayoutAlt; driver.load_glyph_cached = &LibraryLoadGlyphCached; @@ -768,3 +953,27 @@ const Font::SysDriver* FontFt::DriverTable() { const Font::RendererSelection* FontFt::RendererTable() { return &rendererTable; } + +int FontFt::SupportFormats(FontLibrary library, std::initializer_list modules) { + auto* native = static_cast(library); + if (!native) return SCE_FONT_ERROR_INVALID_LIBRARY; + auto lockWord = std::atomic_ref(native->lock_word); + std::uint32_t previous = lockWord.load(std::memory_order_acquire); + for (;;) { + if (native->magic != Font::LIBRARY_MAGIC) return SCE_FONT_ERROR_INVALID_LIBRARY; + if ((previous & Font::LOCK_BIT) == 0 && lockWord.compare_exchange_weak(previous, previous | Font::LOCK_BIT, std::memory_order_acq_rel)) break; + std::this_thread::sleep_for(std::chrono::microseconds(30)); + previous = lockWord.load(std::memory_order_acquire); + } + const auto unlock = [previous](Font::FontLibNative* locked) { + std::atomic_ref(locked->lock_word).store(previous, std::memory_order_release); + }; + const std::unique_ptr guard(native, unlock); + if (native->sys_driver != &driverTable) throw std::runtime_error("FontFt: unsupported library driver"); + const auto* context = static_cast(native->fontset_registry); + if (!context || !context->library) return SCE_FONT_ERROR_INVALID_LIBRARY; + for (const char* module : modules) { + if (!FT_Get_Module(context->library, module)) return SCE_FONT_ERROR_NO_SUPPORT_FORMAT; + } + return SCE_FONT_OK; +} diff --git a/core/libs/prx/libSceFontGs/libSceFontGs.hpp b/core/libs/prx/libSceFontGs/libSceFontGs.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceFontGs/libSceFontGs.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceFreeType/CMakeLists.txt b/core/libs/prx/libSceFreeType/CMakeLists.txt index e0a3b3e116..fb0affb25c 100644 --- a/core/libs/prx/libSceFreeType/CMakeLists.txt +++ b/core/libs/prx/libSceFreeType/CMakeLists.txt @@ -1,29 +1,116 @@ add_library(libSceFreeType SHARED EXCLUDE_FROM_ALL Export.cpp + src/Bitmap.cpp + src/Outline.cpp + src/Charmap.cpp + src/FaceInfo.cpp src/GuestObjects.cpp + src/Names.cpp + src/Trigonometry.cpp + src/OutlineDecompose.cpp ) target_include_directories(libSceFreeType PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceFreeType PRIVATE freetype) +target_sources(libSceFreeType PRIVATE src/SizeRequest.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_freetype_request_size_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeRequestSize.cpp) + target_include_directories(guest_freetype_request_size_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_request_size_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_request_size_tests) + add_test(NAME guest_freetype_request_size COMMAND guest_freetype_request_size_tests) + if(WIN32) + set_tests_properties(guest_freetype_request_size PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +target_sources(libSceFreeType PRIVATE src/List.cpp) if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_options(libSceFreeType PRIVATE LINKER:--exclude-libs,ALL) endif() configure_windows_unwind(libSceFreeType) if(BUILD_TESTING) + add_test_executable(guest_freetype_bitmap_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeBitmap.cpp) + target_include_directories(guest_freetype_bitmap_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_bitmap_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_bitmap_tests) + add_test(NAME guest_freetype_bitmap COMMAND guest_freetype_bitmap_tests) + if(WIN32) + set_tests_properties(guest_freetype_bitmap PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_freetype_outline_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOutline.cpp) + target_include_directories(guest_freetype_outline_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_outline_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_outline_tests) + add_test(NAME guest_freetype_outline COMMAND guest_freetype_outline_tests) + add_test_executable(guest_freetype_charmap_info_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeCharmapInfo.cpp) + target_include_directories(guest_freetype_charmap_info_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_charmap_info_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_charmap_info_tests) + add_test(NAME guest_freetype_charmap_info COMMAND guest_freetype_charmap_info_tests) add_test_executable(guest_freetype_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeType.cpp) target_include_directories(guest_freetype_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_freetype_tests PRIVATE libSceFreeType freetype libc) configure_windows_unwind(guest_freetype_tests) add_test(NAME guest_freetype COMMAND guest_freetype_tests) + add_test_executable(guest_freetype_outline_mutations_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOutlineMutations.cpp) + target_include_directories(guest_freetype_outline_mutations_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_outline_mutations_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_outline_mutations_tests) + add_test(NAME guest_freetype_outline_mutations COMMAND guest_freetype_outline_mutations_tests) + add_test_executable(guest_freetype_face_info_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeFaceInfo.cpp) + target_include_directories(guest_freetype_face_info_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_face_info_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_face_info_tests) + add_test(NAME guest_freetype_face_info COMMAND guest_freetype_face_info_tests) 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) configure_windows_unwind(guest_freetype_charmap_tests) add_test(NAME guest_freetype_charmap COMMAND guest_freetype_charmap_tests) + add_test_executable(guest_freetype_names_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeNames.cpp) + target_include_directories(guest_freetype_names_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_names_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_names_tests) + add_test(NAME guest_freetype_names COMMAND guest_freetype_names_tests) + add_test_executable(guest_freetype_sfnt_names_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeSfntNames.cpp) + target_include_directories(guest_freetype_sfnt_names_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_sfnt_names_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_sfnt_names_tests) + add_test(NAME guest_freetype_sfnt_names COMMAND guest_freetype_sfnt_names_tests) if(WIN32) + set_tests_properties(guest_freetype_sfnt_names PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_freetype_names PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") set_tests_properties(guest_freetype PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") set_tests_properties(guest_freetype_charmap PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(guest_freetype_outline PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_freetype_charmap_info PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_freetype_outline_mutations PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_freetype_face_info PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_freetype_list_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeList.cpp) + target_include_directories(guest_freetype_list_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_list_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_list_tests) + add_test(NAME guest_freetype_list COMMAND guest_freetype_list_tests) + set_tests_properties(guest_freetype_list PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freetype_list PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceFreeType/Export.cpp b/core/libs/prx/libSceFreeType/Export.cpp index 0c4f8d79b1..a8919bfb49 100644 --- a/core/libs/prx/libSceFreeType/Export.cpp +++ b/core/libs/prx/libSceFreeType/Export.cpp @@ -1,7 +1,15 @@ +#include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeType/include/GuestObjects.hpp" +#include FT_OUTLINE_H #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/General.hpp" +#include FT_GASP_H +#include FT_CID_H +#include FT_SIZES_H + +#include +#include #include #include #include @@ -13,6 +21,8 @@ using namespace FreeType; +static_assert(sizeof(FT_Bool) == 1 && sizeof(FT_Int) == 4 && sizeof(FT_UInt) == 4); + namespace { void FinalizeSlot(void* object) { @@ -24,15 +34,19 @@ void FinalizeSlot(void* object) { void FinalizeSize(void* object) { auto* host = static_cast(object); - auto* face = static_cast(host->generic.data); - if (face->size.generic.finalizer) face->size.generic.finalizer(&face->size); - face->rec.size = nullptr; + auto* rec = static_cast(host->generic.data); + auto* face = static_cast(host->face->generic.data); + SyncSizeRecord_nid_no_patch(*face, *rec, host); + SyncSizePointer_nid_no_patch(*face); + if (rec->generic.finalizer) rec->generic.finalizer(rec); } void FinalizeFace(void* object) { auto* host = static_cast(object); auto* face = static_cast(host->generic.data); + SyncSizePointer_nid_no_patch(*face); if (face->rec.generic.finalizer) face->rec.generic.finalizer(&face->rec); + face->finalized = true; } void PublishFace(GuestLibrary* library, std::unique_ptr face, FT_Face host, GuestFaceRec** aface) { @@ -44,7 +58,7 @@ void PublishFace(GuestLibrary* library, std::unique_ptr face, FT_Face library->faces.push_back(std::move(face)); host->generic = {owner, FinalizeFace}; if (host->glyph) host->glyph->generic = {owner, FinalizeSlot}; - if (host->size) host->size->generic = {owner, FinalizeSize}; + if (host->size) host->size->generic = {&owner->size, FinalizeSize}; *aface = &owner->rec; hostOwner.release(); } @@ -105,6 +119,13 @@ int APS5_VABI FT_Done_FreeType_nid_postfix(GuestLibrary* library) { return error; } +int APS5_VABI FT_Select_Size_nid_postfix(GuestFaceRec* face, std::int32_t strikeIndex) { + GuestFace* owner = FaceOf(face); + const FT_Error error = FT_Select_Size(owner ? owner->host : nullptr, strikeIndex); + if (owner) SyncSize(*owner); + return error; +} + int APS5_VABI FT_New_Face_nid_postfix(GuestLibrary* library, const char* filepathname, std::int64_t face_index, GuestFaceRec** aface) { if (!filepathname) return FT_Err_Invalid_Argument; const std::int64_t faceIndex = OpenFaceIndex(face_index); @@ -135,13 +156,72 @@ int APS5_VABI FT_New_Memory_Face_nid_postfix(GuestLibrary* library, const unsign return 0; } +int APS5_VABI FT_Reference_Face_nid_postfix(GuestFaceRec* face) { + const GuestFace* owner = FaceOf(face); + return FT_Reference_Face(owner ? owner->host : nullptr); +} + int APS5_VABI FT_Done_Face_nid_postfix(GuestFaceRec* face) { GuestFace* owner = FaceOf(face); const FT_Error error = FT_Done_Face(owner ? owner->host : nullptr); - if (error == 0) owner->library->faces.remove_if([owner](const auto& entry) { return entry.get() == owner; }); + if (error == 0 && owner->finalized) owner->library->faces.remove_if([owner](const auto& entry) { return entry.get() == owner; }); return error; } +int APS5_VABI FT_New_Size_nid_postfix(GuestFaceRec* face, GuestSizeRec** asize) { + GuestFace* owner = FaceOf(face); + FT_Face hostFace = owner ? owner->host : nullptr; + if (!hostFace || !asize || !hostFace->driver) { + FT_Size unused = nullptr; + return FT_New_Size(hostFace, asize ? &unused : nullptr); + } + const bool activates = asize == &owner->rec.size; + *asize = nullptr; + if (activates) hostFace->size = nullptr; + try { + owner->sizes.emplace_back(); + } catch (const std::bad_alloc&) { + return FT_Err_Out_Of_Memory; + } + auto entry = std::prev(owner->sizes.end()); + FT_Size host = nullptr; + const FT_Error error = FT_New_Size(hostFace, activates ? &hostFace->size : &host); + if (activates) host = hostFace->size; + if (error != 0) { + owner->sizes.erase(entry); + SyncSizePointer_nid_no_patch(*owner); + return error; + } + entry->host = host; + SyncSizeRecord_nid_no_patch(*owner, entry->rec, host); + host->generic = {&entry->rec, FinalizeSize}; + *asize = &entry->rec; + SyncSizePointer_nid_no_patch(*owner); + return 0; +} + +int APS5_VABI FT_Activate_Size_nid_postfix(GuestSizeRec* size) { + FT_Size host = size ? static_cast(size->internal) : nullptr; + const FT_Error error = FT_Activate_Size(host); + if (error == 0) SyncSize(*static_cast(host->face->generic.data)); + return error; +} + +int APS5_VABI FT_Done_Size_nid_postfix(GuestSizeRec* size) { + FT_Size host = size ? static_cast(size->internal) : nullptr; + auto* owner = host && host->face ? static_cast(host->face->generic.data) : nullptr; + const FT_Error error = FT_Done_Size(host); + if (error != 0) return error; + if (host == owner->initialSize) { + owner->initialSize = nullptr; + owner->size.internal = nullptr; + } else { + owner->sizes.remove_if([host](const auto& entry) { return entry.host == host; }); + } + SyncSize(*owner); + return 0; +} + int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(GuestFaceRec* face, std::uint32_t pixel_width, std::uint32_t pixel_height) { GuestFace* owner = FaceOf(face); const FT_Error error = FT_Set_Pixel_Sizes(owner ? owner->host : nullptr, pixel_width, pixel_height); @@ -149,11 +229,46 @@ int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(GuestFaceRec* face, std::uint32_t p return error; } +int APS5_VABI FT_Set_Char_Size_nid_postfix(GuestFaceRec* face, std::int64_t charWidth, std::int64_t charHeight, + std::uint32_t horizontalResolution, std::uint32_t verticalResolution) { + GuestFace* owner = FaceOf(face); + if (!owner) return FT_Err_Invalid_Face_Handle; + if (!owner->host->size) return FT_Err_Invalid_Size_Handle; + if (!charWidth) charWidth = charHeight; + else if (!charHeight) charHeight = charWidth; + if (charWidth < 64) charWidth = 64; + if (charHeight < 64) charHeight = 64; + if (!horizontalResolution) horizontalResolution = verticalResolution; + else if (!verticalResolution) verticalResolution = horizontalResolution; + if (!horizontalResolution) horizontalResolution = verticalResolution = 72; + const auto maximum = std::numeric_limits::max(); + if (charWidth > (maximum - 36) / horizontalResolution || charHeight > (maximum - 36) / verticalResolution) { + throw std::runtime_error("FT_Set_Char_Size: a character size or resolution outside the host arithmetic range is not implemented"); + } + const FT_Error error = FT_Set_Char_Size(owner->host, static_cast(charWidth), static_cast(charHeight), + horizontalResolution, verticalResolution); + SyncSize(*owner); + return error; +} + std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(GuestFaceRec* face, std::uint64_t charcode) { const GuestFace* owner = FaceOf(face); return FT_Get_Char_Index(owner ? owner->host : nullptr, static_cast(static_cast(charcode))); } +int APS5_VABI FT_Get_Kerning_nid_postfix(GuestFaceRec* face, std::uint32_t leftGlyph, std::uint32_t rightGlyph, std::uint32_t kernMode, GuestVector* kerning) { + const GuestFace* owner = FaceOf(face); + if (!owner || !kerning) return FT_Get_Kerning(owner ? owner->host : nullptr, leftGlyph, rightGlyph, kernMode, nullptr); + if (leftGlyph > std::numeric_limits::max() && FT_IS_SFNT(owner->host)) { + *kerning = {0, 0}; + return 0; + } + FT_Vector result{}; + const FT_Error error = FT_Get_Kerning(owner->host, leftGlyph, rightGlyph, kernMode, &result); + *kerning = {result.x, result.y}; + return error; +} + int APS5_VABI FT_Load_Glyph_nid_postfix(GuestFaceRec* face, std::uint32_t glyph_index, std::int32_t load_flags) { GuestFace* owner = FaceOf(face); const FT_Error error = FT_Load_Glyph(owner ? owner->host : nullptr, glyph_index, load_flags); @@ -161,6 +276,27 @@ int APS5_VABI FT_Load_Glyph_nid_postfix(GuestFaceRec* face, std::uint32_t glyph_ return error; } +int APS5_VABI FT_Get_CID_Registry_Ordering_Supplement_nid_postfix(GuestFaceRec* face, const char** registry, + const char** ordering, std::int32_t* supplement) { + const GuestFace* owner = FaceOf(face); + return FT_Get_CID_Registry_Ordering_Supplement(owner ? owner->host : nullptr, registry, ordering, supplement); +} + +int APS5_VABI FT_Get_CID_Is_Internally_CID_Keyed_nid_postfix(GuestFaceRec* face, std::uint8_t* isCid) { + const GuestFace* owner = FaceOf(face); + return FT_Get_CID_Is_Internally_CID_Keyed(owner ? owner->host : nullptr, isCid); +} + +int APS5_VABI FT_Get_CID_From_Glyph_Index_nid_postfix(GuestFaceRec* face, std::uint32_t glyphIndex, std::uint32_t* cid) { + const GuestFace* owner = FaceOf(face); + return FT_Get_CID_From_Glyph_Index(owner ? owner->host : nullptr, glyphIndex, cid); +} + +int APS5_VABI FT_Get_Gasp_nid_postfix(GuestFaceRec* face, std::uint32_t ppem) { + const GuestFace* owner = FaceOf(face); + return FT_Get_Gasp(owner ? owner->host : nullptr, ppem); +} + int APS5_VABI FT_Load_Char_nid_postfix(GuestFaceRec* face, std::uint64_t charCode, std::int32_t loadFlags) { GuestFace* owner = FaceOf(face); const FT_Error error = FT_Load_Char(owner ? owner->host : nullptr, static_cast(static_cast(charCode)), loadFlags); @@ -168,6 +304,42 @@ int APS5_VABI FT_Load_Char_nid_postfix(GuestFaceRec* face, std::uint64_t charCod return error; } +int APS5_VABI FT_Outline_Get_Orientation_nid_postfix(GuestOutline* outline) { + if (!outline || outline->n_points == 0) return FT_ORIENTATION_TRUETYPE; + const auto initial = outline->points[0]; + GuestBBox bounds{initial.x, initial.y, initial.x, initial.y}; + for (unsigned index = 1; index < outline->n_points; ++index) { + const auto point = outline->points[index]; + bounds.xMin = std::min(bounds.xMin, point.x); + bounds.yMin = std::min(bounds.yMin, point.y); + bounds.xMax = std::max(bounds.xMax, point.x); + bounds.yMax = std::max(bounds.yMax, point.y); + } + if (bounds.xMin == bounds.xMax || bounds.yMin == bounds.yMax || + bounds.xMin < -0x1000000 || bounds.yMin < -0x1000000 || + bounds.xMax > 0x1000000 || bounds.yMax > 0x1000000) return FT_ORIENTATION_NONE; + const auto xMagnitude = static_cast((bounds.xMin < 0 ? -bounds.xMin : bounds.xMin) | + (bounds.xMax < 0 ? -bounds.xMax : bounds.xMax)); + const int xShift = std::max(0, static_cast(std::bit_width(xMagnitude)) - 15); + const int yShift = std::max(0, static_cast(std::bit_width(static_cast(bounds.yMax - bounds.yMin))) - 15); + std::int64_t area = 0; + unsigned first = 0; + for (unsigned contour = 0; contour < outline->n_contours; ++contour) { + const unsigned last = outline->contours[contour]; + auto previousX = outline->points[last].x >> xShift; + auto previousY = outline->points[last].y >> yShift; + for (unsigned index = first; index <= last; ++index) { + const auto x = outline->points[index].x >> xShift; + const auto y = outline->points[index].y >> yShift; + area += (y - previousY) * (x + previousX); + previousX = x; + previousY = y; + } + first = last + 1; + } + return area > 0 ? FT_ORIENTATION_POSTSCRIPT : area < 0 ? FT_ORIENTATION_TRUETYPE : FT_ORIENTATION_NONE; +} + int APS5_VABI FT_Render_Glyph_nid_postfix(GuestGlyphSlotRec* slot, std::int32_t render_mode) { GuestFace* owner = FaceOf(slot); if (owner) WriteBackOutline(*owner); @@ -176,6 +348,19 @@ int APS5_VABI FT_Render_Glyph_nid_postfix(GuestGlyphSlotRec* slot, std::int32_t return error; } +int APS5_VABI FT_Outline_Copy_nid_postfix(const GuestOutline* source, GuestOutline* target) { + if (!source || !target) return FT_Err_Invalid_Outline; + if (source->n_points != target->n_points || source->n_contours != target->n_contours) return FT_Err_Invalid_Argument; + if (source == target) return FT_Err_Ok; + if (source->n_points) { + std::memmove(target->points, source->points, source->n_points * sizeof(GuestVector)); + std::memmove(target->tags, source->tags, source->n_points * sizeof(unsigned char)); + } + if (source->n_contours) std::memmove(target->contours, source->contours, source->n_contours * sizeof(std::uint16_t)); + target->flags = (source->flags & ~FT_OUTLINE_OWNER) | (target->flags & FT_OUTLINE_OWNER); + return FT_Err_Ok; +} + int APS5_VABI FT_Select_Charmap_nid_postfix(GuestFaceRec* face, std::int32_t encoding) { GuestFace* owner = FaceOf(face); const FT_Error error = FT_Select_Charmap(owner ? owner->host : nullptr, static_cast(encoding)); @@ -205,4 +390,20 @@ std::uint64_t APS5_VABI FT_Get_Next_Char_nid_postfix(GuestFaceRec* face, std::ui return FT_Get_Next_Char(owner ? owner->host : nullptr, static_cast(static_cast(charcode)), glyphIndex); } +void APS5_VABI FT_Set_Transform_nid_postfix(GuestFaceRec* face, const GuestMatrix* matrix, const GuestVector* delta) { + GuestFace* owner = FaceOf(face); + if (!owner) return; + const auto component = [](std::int64_t value) { + if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { + throw std::runtime_error("FT_Set_Transform: a component outside the host FT_Long range is not implemented"); + } + return static_cast(value); + }; + FT_Matrix nativeMatrix{}; + FT_Vector nativeDelta{}; + if (matrix) nativeMatrix = {component(matrix->xx), component(matrix->xy), component(matrix->yx), component(matrix->yy)}; + if (delta) nativeDelta = {component(delta->x), component(delta->y)}; + FT_Set_Transform(owner->host, matrix ? &nativeMatrix : nullptr, delta ? &nativeDelta : nullptr); +} + } diff --git a/core/libs/prx/libSceFreeType/include/Api.hpp b/core/libs/prx/libSceFreeType/include/Api.hpp new file mode 100644 index 0000000000..163d4120df --- /dev/null +++ b/core/libs/prx/libSceFreeType/include/Api.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "prx/libSceFreeType/include/GuestList.hpp" +#include "prx/libSceFreeType/include/GuestFreeType.hpp" +#include "SceTypes.hpp" + +extern "C" { + +std::int32_t APS5_VABI FT_Outline_GetInsideBorder_nid_postfix(const FreeType::GuestOutline* outline); +std::int32_t APS5_VABI FT_Outline_GetOutsideBorder_nid_postfix(const FreeType::GuestOutline* outline); + +int APS5_VABI FT_Init_FreeType_nid_postfix(FreeType::GuestLibrary** alibrary); +int APS5_VABI FT_Done_FreeType_nid_postfix(FreeType::GuestLibrary* library); +int APS5_VABI FT_New_Face_nid_postfix(FreeType::GuestLibrary* library, const char* filepathname, std::int64_t faceIndex, FreeType::GuestFaceRec** aface); +int APS5_VABI FT_New_Memory_Face_nid_postfix(FreeType::GuestLibrary* library, const unsigned char* fileBase, std::int64_t fileSize, std::int64_t faceIndex, FreeType::GuestFaceRec** aface); +int APS5_VABI FT_Reference_Face_nid_postfix(FreeType::GuestFaceRec* face); +int APS5_VABI FT_Done_Face_nid_postfix(FreeType::GuestFaceRec* face); +int APS5_VABI FT_Select_Size_nid_postfix(FreeType::GuestFaceRec* face, std::int32_t strikeIndex); +int APS5_VABI FT_New_Size_nid_postfix(FreeType::GuestFaceRec* face, FreeType::GuestSizeRec** asize); +int APS5_VABI FT_Activate_Size_nid_postfix(FreeType::GuestSizeRec* size); +int APS5_VABI FT_Done_Size_nid_postfix(FreeType::GuestSizeRec* size); +int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t pixelWidth, std::uint32_t pixelHeight); +int APS5_VABI FT_Set_Char_Size_nid_postfix(FreeType::GuestFaceRec* face, std::int64_t charWidth, std::int64_t charHeight, std::uint32_t horizontalResolution, std::uint32_t verticalResolution); +std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(FreeType::GuestFaceRec* face, std::uint64_t charcode); +int APS5_VABI FT_Get_Kerning_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t leftGlyph, std::uint32_t rightGlyph, std::uint32_t kernMode, FreeType::GuestVector* kerning); +int APS5_VABI FT_Load_Glyph_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t glyphIndex, std::int32_t loadFlags); +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); +int APS5_VABI FT_Get_CID_From_Glyph_Index_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t glyphIndex, std::uint32_t* cid); +int APS5_VABI FT_Load_Char_nid_postfix(FreeType::GuestFaceRec* face, std::uint64_t charCode, std::int32_t loadFlags); +int APS5_VABI FT_Outline_Get_Orientation_nid_postfix(FreeType::GuestOutline* outline); +int APS5_VABI FT_Outline_Copy_nid_postfix(const FreeType::GuestOutline* source, FreeType::GuestOutline* target); +int APS5_VABI FT_Outline_Decompose_nid_postfix(FreeType::GuestOutline* outline, const FreeType::GuestOutlineFuncs* functions, void* user); +int APS5_VABI FT_Render_Glyph_nid_postfix(FreeType::GuestGlyphSlotRec* slot, std::int32_t renderMode); +int APS5_VABI FT_Select_Charmap_nid_postfix(FreeType::GuestFaceRec* face, std::int32_t encoding); +int APS5_VABI FT_Set_Charmap_nid_postfix(FreeType::GuestFaceRec* face, FreeType::GuestCharMapRec* charmap); +std::uint64_t APS5_VABI FT_Get_First_Char_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t* glyphIndex); +std::uint64_t APS5_VABI FT_Get_Next_Char_nid_postfix(FreeType::GuestFaceRec* face, std::uint64_t charcode, std::uint32_t* glyphIndex); +void APS5_VABI FT_Set_Transform_nid_postfix(FreeType::GuestFaceRec* face, const FreeType::GuestMatrix* matrix, const FreeType::GuestVector* delta); +int APS5_VABI FT_Get_Charmap_Index_nid_postfix(FreeType::GuestCharMapRec* charmap); +std::uint64_t APS5_VABI FT_Get_CMap_Language_ID_nid_postfix(FreeType::GuestCharMapRec* charmap); +std::int64_t APS5_VABI FT_Get_CMap_Format_nid_postfix(FreeType::GuestCharMapRec* charmap); +const char* APS5_VABI FT_Get_X11_Font_Format_nid_postfix(FreeType::GuestFaceRec* face); +std::uint16_t APS5_VABI FT_Get_FSType_Flags_nid_postfix(FreeType::GuestFaceRec* face); +FreeType::GuestListNodeRec* APS5_VABI FT_List_Find_nid_postfix(FreeType::GuestListRec* list, void* data); +void APS5_VABI FT_List_Add_nid_postfix(FreeType::GuestListRec* list, FreeType::GuestListNodeRec* node); +void APS5_VABI FT_List_Insert_nid_postfix(FreeType::GuestListRec* list, FreeType::GuestListNodeRec* node); +void APS5_VABI FT_List_Remove_nid_postfix(FreeType::GuestListRec* list, FreeType::GuestListNodeRec* node); +void APS5_VABI FT_List_Up_nid_postfix(FreeType::GuestListRec* list, FreeType::GuestListNodeRec* node); +int APS5_VABI FT_List_Iterate_nid_postfix(FreeType::GuestListRec* list, FreeType::GuestListIterator iterator, void* user); +void APS5_VABI FT_List_Finalize_nid_postfix(FreeType::GuestListRec* list, FreeType::GuestListDestructor destroy, FreeType::GuestMemoryRec* memory, void* user); +int APS5_VABI FT_Get_Glyph_Name_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t glyphIndex, void* buffer, std::uint32_t bufferSize); +std::uint32_t APS5_VABI FT_Get_Name_Index_nid_postfix(FreeType::GuestFaceRec* face, const char* glyphName); +const char* APS5_VABI FT_Get_Postscript_Name_nid_postfix(FreeType::GuestFaceRec* face); +std::uint32_t APS5_VABI FT_Get_Sfnt_Name_Count_nid_postfix(FreeType::GuestFaceRec* face); +int APS5_VABI FT_Get_Sfnt_Name_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t index, FreeType::GuestSfntName* name); +int APS5_VABI FT_Outline_Check_nid_postfix(FreeType::GuestOutline* outline); +void APS5_VABI FT_Outline_Get_CBox_nid_postfix(const FreeType::GuestOutline* outline, FreeType::GuestBBox* box); +void APS5_VABI FT_Outline_Translate_nid_postfix(const FreeType::GuestOutline* outline, std::int64_t xOffset, std::int64_t yOffset); +void APS5_VABI FT_Outline_Reverse_nid_postfix(FreeType::GuestOutline* outline); +int APS5_VABI FT_Request_Size_nid_postfix(FreeType::GuestFaceRec* face, const FreeType::GuestSizeRequestRec* request); + +void APS5_VABI FT_Bitmap_New_nid_postfix(FreeType::GuestBitmap* bitmap); +int APS5_VABI FT_Bitmap_Copy_nid_postfix(FreeType::GuestLibrary* library, const FreeType::GuestBitmap* source, FreeType::GuestBitmap* target); +int APS5_VABI FT_Bitmap_Convert_nid_postfix(FreeType::GuestLibrary* library, const FreeType::GuestBitmap* source, FreeType::GuestBitmap* target, std::int32_t alignment); +int APS5_VABI FT_Bitmap_Done_nid_postfix(FreeType::GuestLibrary* library, FreeType::GuestBitmap* bitmap); +std::int64_t APS5_VABI FT_Cos_nid_postfix(std::int64_t angle); +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); + +} diff --git a/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp b/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp index 1a2a074378..51acae2148 100644 --- a/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp +++ b/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp @@ -8,6 +8,8 @@ namespace FreeType { +struct GuestLibrary; + struct GuestGeneric { void* data; void (APS5_VABI *finalizer)(void*); @@ -18,6 +20,13 @@ struct GuestVector { std::int64_t y; }; +struct GuestMatrix { + std::int64_t xx; + std::int64_t xy; + std::int64_t yx; + std::int64_t yy; +}; + struct GuestBBox { std::int64_t xMin; std::int64_t yMin; @@ -40,6 +49,15 @@ struct GuestCharMapRec { std::uint16_t encoding_id; }; +struct GuestSfntName { + std::uint16_t platform_id; + std::uint16_t encoding_id; + std::uint16_t language_id; + std::uint16_t name_id; + unsigned char* string; + std::uint32_t string_len; +}; + struct GuestSizeMetrics { std::uint16_t x_ppem; std::uint16_t y_ppem; @@ -58,6 +76,14 @@ struct GuestSizeRec { void* internal; }; +struct GuestSizeRequestRec { + std::uint32_t type; + std::int64_t width; + std::int64_t height; + std::uint32_t horiResolution; + std::uint32_t vertResolution; +}; + struct GuestGlyphMetrics { std::int64_t width; std::int64_t height; @@ -89,6 +115,15 @@ struct GuestOutline { std::int32_t flags; }; +struct GuestOutlineFuncs { + int (APS5_VABI *move_to)(const GuestVector*, void*); + int (APS5_VABI *line_to)(const GuestVector*, void*); + int (APS5_VABI *conic_to)(const GuestVector*, const GuestVector*, void*); + int (APS5_VABI *cubic_to)(const GuestVector*, const GuestVector*, const GuestVector*, void*); + std::int32_t shift; + std::int64_t delta; +}; + struct GuestGlyphSlotRec { void* library; void* face; @@ -149,12 +184,17 @@ struct GuestFaceRec { void* internal; }; +static_assert(sizeof(GuestMatrix) == 32 && offsetof(GuestMatrix, xy) == 8 && offsetof(GuestMatrix, yx) == 16 && offsetof(GuestMatrix, yy) == 24); static_assert(sizeof(GuestGeneric) == 16 && offsetof(GuestGeneric, finalizer) == 8); static_assert(sizeof(GuestBitmapSize) == 32 && offsetof(GuestBitmapSize, size) == 8); static_assert(sizeof(GuestCharMapRec) == 16 && offsetof(GuestCharMapRec, platform_id) == 12); +static_assert(sizeof(GuestSfntName) == 24 && offsetof(GuestSfntName, string) == 8 && offsetof(GuestSfntName, string_len) == 16); static_assert(sizeof(GuestSizeMetrics) == 56 && offsetof(GuestSizeMetrics, x_scale) == 8); static_assert(sizeof(GuestSizeRec) == 88 && offsetof(GuestSizeRec, metrics) == 24); +static_assert(sizeof(GuestSizeRequestRec) == 32 && offsetof(GuestSizeRequestRec, width) == 8 && offsetof(GuestSizeRequestRec, height) == 16); +static_assert(offsetof(GuestSizeRequestRec, horiResolution) == 24 && offsetof(GuestSizeRequestRec, vertResolution) == 28); static_assert(sizeof(GuestBitmap) == 40 && offsetof(GuestBitmap, buffer) == 16 && offsetof(GuestBitmap, palette) == 32); +static_assert(sizeof(GuestOutlineFuncs) == 48 && offsetof(GuestOutlineFuncs, shift) == 32 && offsetof(GuestOutlineFuncs, delta) == 40); static_assert(sizeof(GuestOutline) == 40 && offsetof(GuestOutline, points) == 8 && offsetof(GuestOutline, flags) == 32); static_assert(offsetof(GuestGlyphSlotRec, glyph_index) == 0x18 && offsetof(GuestGlyphSlotRec, metrics) == 0x30); static_assert(offsetof(GuestGlyphSlotRec, advance) == 0x80 && offsetof(GuestGlyphSlotRec, format) == 0x90); diff --git a/core/libs/prx/libSceFreeType/include/GuestList.hpp b/core/libs/prx/libSceFreeType/include/GuestList.hpp new file mode 100644 index 0000000000..1e11a349c1 --- /dev/null +++ b/core/libs/prx/libSceFreeType/include/GuestList.hpp @@ -0,0 +1,38 @@ +#ifndef CORE_LIBS_PRX_LIBSCEFREETYPE_INCLUDE_GUESTLIST_HPP +#define CORE_LIBS_PRX_LIBSCEFREETYPE_INCLUDE_GUESTLIST_HPP + +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include + +namespace FreeType { + +struct GuestListNodeRec { + GuestListNodeRec* prev; + GuestListNodeRec* next; + void* data; +}; + +struct GuestListRec { + GuestListNodeRec* head; + GuestListNodeRec* tail; +}; + +struct GuestMemoryRec { + void* user; + void* (APS5_VABI *alloc)(GuestMemoryRec*, std::int64_t); + void (APS5_VABI *free)(GuestMemoryRec*, void*); + void* (APS5_VABI *realloc)(GuestMemoryRec*, std::int64_t, std::int64_t, void*); +}; + +using GuestListIterator = int (APS5_VABI *)(GuestListNodeRec*, void*); +using GuestListDestructor = void (APS5_VABI *)(GuestMemoryRec*, void*, void*); + +static_assert(sizeof(GuestListNodeRec) == 24 && offsetof(GuestListNodeRec, prev) == 0 && offsetof(GuestListNodeRec, next) == 8 && offsetof(GuestListNodeRec, data) == 16); +static_assert(sizeof(GuestListRec) == 16 && offsetof(GuestListRec, head) == 0 && offsetof(GuestListRec, tail) == 8); +static_assert(sizeof(GuestMemoryRec) == 32 && offsetof(GuestMemoryRec, alloc) == 8 && offsetof(GuestMemoryRec, free) == 16 && offsetof(GuestMemoryRec, realloc) == 24); + +} + +#endif diff --git a/core/libs/prx/libSceFreeType/include/GuestObjects.hpp b/core/libs/prx/libSceFreeType/include/GuestObjects.hpp index d237147b89..d47c3c1037 100644 --- a/core/libs/prx/libSceFreeType/include/GuestObjects.hpp +++ b/core/libs/prx/libSceFreeType/include/GuestObjects.hpp @@ -19,23 +19,40 @@ struct GuestLibrary { std::list> faces; }; +void UnregisterBitmap_nid_no_patch(const GuestBitmap* bitmap); + +struct GuestSize { + GuestSizeRec rec{}; + FT_Size host = nullptr; +}; + struct GuestFace { GuestFaceRec rec{}; GuestGlyphSlotRec slot{}; GuestSizeRec size{}; GuestLibrary* library = nullptr; FT_Face host = nullptr; + bool finalized = false; + FT_Size initialSize = nullptr; + bool sizeInitialized = false; + std::list sizes; std::vector bytes; std::vector fixedSizes; std::vector charmaps; std::vector charmapPointers; std::vector outlinePoints; + + ~GuestFace() { UnregisterBitmap_nid_no_patch(&slot.bitmap); } }; +void RegisterBitmap_nid_no_patch(GuestFace& face); + GuestFace* FaceOf(const GuestFaceRec* face); GuestFace* FaceOf(const GuestGlyphSlotRec* slot); void SyncFace(GuestFace& face); void SyncSize(GuestFace& face); +void SyncSizePointer_nid_no_patch(GuestFace& face); +void SyncSizeRecord_nid_no_patch(GuestFace& face, GuestSizeRec& rec, FT_Size host); void SyncSlot(GuestFace& face); } diff --git a/core/libs/prx/libSceFreeType/libSceFreeType.hpp b/core/libs/prx/libSceFreeType/libSceFreeType.hpp new file mode 100644 index 0000000000..e1970ef93b --- /dev/null +++ b/core/libs/prx/libSceFreeType/libSceFreeType.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceFreeType/include/Api.hpp" diff --git a/core/libs/prx/libSceFreeType/src/Bitmap.cpp b/core/libs/prx/libSceFreeType/src/Bitmap.cpp new file mode 100644 index 0000000000..2e8a5d750e --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/Bitmap.cpp @@ -0,0 +1,100 @@ +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_BITMAP_H + +#include +#include +#include +#include + +using namespace FreeType; + +namespace { + +struct BitmapOwners { + std::mutex mutex; + std::unordered_map bitmaps; +}; + +BitmapOwners& Owners() { + static BitmapOwners owners; + return owners; +} + +FT_Bitmap NativeBitmap(const GuestBitmap& bitmap) { + return {bitmap.rows, bitmap.width, bitmap.pitch, bitmap.buffer, bitmap.num_grays, bitmap.pixel_mode, bitmap.palette_mode, bitmap.palette}; +} + +void SyncBitmap(GuestBitmap& guest, const FT_Bitmap& bitmap) { + guest = {bitmap.rows, bitmap.width, bitmap.pitch, bitmap.buffer, bitmap.num_grays, bitmap.pixel_mode, bitmap.palette_mode, bitmap.palette}; + auto& owners = Owners(); + std::lock_guard lock(owners.mutex); + const auto found = owners.bitmaps.find(&guest); + if (found != owners.bitmaps.end() && found->second) *found->second = bitmap; +} + +} + +namespace FreeType { + +void RegisterBitmap_nid_no_patch(GuestFace& face) { + auto& owners = Owners(); + std::lock_guard lock(owners.mutex); + owners.bitmaps.insert_or_assign(&face.slot.bitmap, face.host->glyph ? &face.host->glyph->bitmap : nullptr); +} + +void UnregisterBitmap_nid_no_patch(const GuestBitmap* bitmap) { + auto& owners = Owners(); + std::lock_guard lock(owners.mutex); + owners.bitmaps.erase(bitmap); +} + +} + +extern "C" { + +void APS5_VABI FT_Bitmap_New_nid_postfix(GuestBitmap* bitmap) { + if (bitmap) SyncBitmap(*bitmap, {}); +} + +int APS5_VABI FT_Bitmap_Copy_nid_postfix(GuestLibrary* library, const GuestBitmap* source, GuestBitmap* target) { + if (!library) return FT_Err_Invalid_Library_Handle; + if (!source || !target) return FT_Err_Invalid_Argument; + if (source == target) { + SyncBitmap(*target, NativeBitmap(*target)); + return 0; + } + if (source->buffer && source->buffer == target->buffer) { + throw std::runtime_error("FT_Bitmap_Copy: overlapping source and target buffers are not implemented"); + } + const FT_Bitmap nativeSource = NativeBitmap(*source); + FT_Bitmap nativeTarget = NativeBitmap(*target); + const FT_Error error = FT_Bitmap_Copy(library->host, &nativeSource, &nativeTarget); + SyncBitmap(*target, nativeTarget); + return error; +} + +int APS5_VABI FT_Bitmap_Convert_nid_postfix(GuestLibrary* library, const GuestBitmap* source, GuestBitmap* target, std::int32_t alignment) { + if (!library) return FT_Err_Invalid_Library_Handle; + if (!source || !target) return FT_Err_Invalid_Argument; + if (source == target || (source->buffer && source->buffer == target->buffer)) { + throw std::runtime_error("FT_Bitmap_Convert: overlapping source and target buffers are not implemented"); + } + const FT_Bitmap nativeSource = NativeBitmap(*source); + FT_Bitmap nativeTarget = NativeBitmap(*target); + const FT_Error error = FT_Bitmap_Convert(library->host, &nativeSource, &nativeTarget, alignment); + SyncBitmap(*target, nativeTarget); + return error; +} + +int APS5_VABI FT_Bitmap_Done_nid_postfix(GuestLibrary* library, GuestBitmap* bitmap) { + if (!library) return FT_Err_Invalid_Library_Handle; + if (!bitmap) return FT_Err_Invalid_Argument; + FT_Bitmap native = NativeBitmap(*bitmap); + const FT_Error error = FT_Bitmap_Done(library->host, &native); + SyncBitmap(*bitmap, native); + return error; +} + +} diff --git a/core/libs/prx/libSceFreeType/src/Charmap.cpp b/core/libs/prx/libSceFreeType/src/Charmap.cpp new file mode 100644 index 0000000000..6f7ee76773 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/Charmap.cpp @@ -0,0 +1,42 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_TRUETYPE_TABLES_H + +#include +#include +#include + +using namespace FreeType; + +namespace { + +FT_CharMap HostCharmap(const GuestCharMapRec* charmap) { + if (!charmap || !charmap->face) return nullptr; + const GuestFace* owner = FaceOf(static_cast(charmap->face)); + if (owner && owner->host) { + for (std::size_t index = 0; index < owner->charmapPointers.size(); ++index) { + if (owner->charmapPointers[index] == charmap) return owner->host->charmaps[index]; + } + } + throw std::runtime_error("FreeType: a charmap outside the guest face's read-only array is not implemented"); +} + +} + +extern "C" { + +int APS5_VABI FT_Get_Charmap_Index_nid_postfix(GuestCharMapRec* charmap) { + return FT_Get_Charmap_Index(HostCharmap(charmap)); +} + +std::uint64_t APS5_VABI FT_Get_CMap_Language_ID_nid_postfix(GuestCharMapRec* charmap) { + return FT_Get_CMap_Language_ID(HostCharmap(charmap)); +} + +std::int64_t APS5_VABI FT_Get_CMap_Format_nid_postfix(GuestCharMapRec* charmap) { + return FT_Get_CMap_Format(HostCharmap(charmap)); +} + +} \ No newline at end of file diff --git a/core/libs/prx/libSceFreeType/src/FaceInfo.cpp b/core/libs/prx/libSceFreeType/src/FaceInfo.cpp new file mode 100644 index 0000000000..a5969dcbfa --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/FaceInfo.cpp @@ -0,0 +1,23 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_FONT_FORMATS_H + +#include + +using namespace FreeType; + +extern "C" { + +const char* APS5_VABI FT_Get_X11_Font_Format_nid_postfix(GuestFaceRec* face) { + const GuestFace* owner = FaceOf(face); + return FT_Get_X11_Font_Format(owner ? owner->host : nullptr); +} + +std::uint16_t APS5_VABI FT_Get_FSType_Flags_nid_postfix(GuestFaceRec* face) { + const GuestFace* owner = FaceOf(face); + return FT_Get_FSType_Flags(owner ? owner->host : nullptr); +} + +} diff --git a/core/libs/prx/libSceFreeType/src/GuestObjects.cpp b/core/libs/prx/libSceFreeType/src/GuestObjects.cpp index 79662344d2..1ecb345092 100644 --- a/core/libs/prx/libSceFreeType/src/GuestObjects.cpp +++ b/core/libs/prx/libSceFreeType/src/GuestObjects.cpp @@ -1,6 +1,7 @@ #include "prx/libSceFreeType/include/GuestObjects.hpp" #include +#include namespace FreeType { @@ -13,6 +14,7 @@ GuestFace* FaceOf(const GuestGlyphSlotRec* slot) { } void SyncFace(GuestFace& face) { + RegisterBitmap_nid_no_patch(face); const FT_Face host = face.host; auto& rec = face.rec; rec.num_faces = host->num_faces; @@ -50,20 +52,48 @@ void SyncFace(GuestFace& face) { rec.underline_position = host->underline_position; rec.underline_thickness = host->underline_thickness; rec.glyph = host->glyph ? &face.slot : nullptr; - rec.size = host->size ? &face.size : nullptr; rec.internal = &face; SyncSize(face); SyncSlot(face); } -void SyncSize(GuestFace& face) { - face.size.face = &face.rec; - if (!face.host->size) { - face.size.metrics = {}; +void SyncSizeRecord_nid_no_patch(GuestFace& face, GuestSizeRec& rec, FT_Size host) { + rec.face = &face.rec; + rec.internal = host; + if (!host) { + rec.metrics = {}; return; } - const auto& metrics = face.host->size->metrics; - face.size.metrics = {metrics.x_ppem, metrics.y_ppem, metrics.x_scale, metrics.y_scale, metrics.ascender, metrics.descender, metrics.height, metrics.max_advance}; + const auto& metrics = host->metrics; + rec.metrics = {metrics.x_ppem, metrics.y_ppem, metrics.x_scale, metrics.y_scale, metrics.ascender, metrics.descender, metrics.height, metrics.max_advance}; +} + +void SyncSizePointer_nid_no_patch(GuestFace& face) { + const FT_Size active = face.host->size; + if (!active) { + face.rec.size = nullptr; + return; + } + if (active == face.initialSize) { + face.rec.size = &face.size; + return; + } + for (auto& size : face.sizes) { + if (size.host != active) continue; + face.rec.size = &size.rec; + return; + } + throw std::runtime_error("FreeType: an active size created outside the guest bridge is not implemented"); +} + +void SyncSize(GuestFace& face) { + if (!face.sizeInitialized) { + face.initialSize = face.host->size; + face.sizeInitialized = true; + SyncSizeRecord_nid_no_patch(face, face.size, face.initialSize); + } + SyncSizePointer_nid_no_patch(face); + if (face.rec.size) SyncSizeRecord_nid_no_patch(face, *face.rec.size, face.host->size); } void SyncSlot(GuestFace& face) { diff --git a/core/libs/prx/libSceFreeType/src/List.cpp b/core/libs/prx/libSceFreeType/src/List.cpp new file mode 100644 index 0000000000..198f3207b6 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/List.cpp @@ -0,0 +1,116 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestList.hpp" +#include "prx/libc/include/GuestArena.hpp" + +#include +#include +#include +#include FT_LIST_H + +using namespace FreeType; + +namespace { + +class ListWrite { +public: + ListWrite(GuestListRec* list, std::array nodes = {}) + : writes{GuestArena::HostWrite(list, sizeof(*list)), + GuestArena::HostWrite(nodes[0], nodes[0] ? sizeof(*nodes[0]) : 0), + GuestArena::HostWrite(nodes[1], nodes[1] ? sizeof(*nodes[1]) : 0), + GuestArena::HostWrite(nodes[2], nodes[2] ? sizeof(*nodes[2]) : 0), + GuestArena::HostWrite(nodes[3], nodes[3] ? sizeof(*nodes[3]) : 0)} { + for (const auto& write : writes) { + if (!write.Open()) throw std::runtime_error("FT_List: a destination is not writable"); + } + } + +private: + std::array writes; +}; + +} + +extern "C" { + +GuestListNodeRec* APS5_VABI FT_List_Find_nid_postfix(GuestListRec* list, void* data) { + if (!list) return nullptr; + for (auto* node = list->head; node; node = node->next) { + if (node->data == data) return node; + } + return nullptr; +} + +void APS5_VABI FT_List_Add_nid_postfix(GuestListRec* list, GuestListNodeRec* node) { + if (!list || !node) return; + const ListWrite write(list, {node, list->tail}); + auto* tail = list->tail; + node->next = nullptr; + node->prev = tail; + if (tail) tail->next = node; + else list->head = node; + list->tail = node; +} + +void APS5_VABI FT_List_Insert_nid_postfix(GuestListRec* list, GuestListNodeRec* node) { + if (!list || !node) return; + const ListWrite write(list, {node, list->head}); + auto* head = list->head; + node->next = head; + node->prev = nullptr; + if (head) head->prev = node; + else list->tail = node; + list->head = node; +} + +void APS5_VABI FT_List_Remove_nid_postfix(GuestListRec* list, GuestListNodeRec* node) { + if (!list || !node) return; + const ListWrite write(list, {node->prev, node->next}); + auto* previous = node->prev; + auto* next = node->next; + if (previous) previous->next = next; + else list->head = next; + if (next) next->prev = previous; + else list->tail = previous; +} + +void APS5_VABI FT_List_Up_nid_postfix(GuestListRec* list, GuestListNodeRec* node) { + if (!list || !node || !node->prev) return; + const ListWrite write(list, {node, node->prev, node->next, list->head}); + auto* previous = node->prev; + auto* next = node->next; + previous->next = next; + if (next) next->prev = previous; + else list->tail = previous; + node->prev = nullptr; + node->next = list->head; + list->head->prev = node; + list->head = node; +} + +int APS5_VABI FT_List_Iterate_nid_postfix(GuestListRec* list, GuestListIterator iterator, void* user) { + if (!list || !iterator) return FT_Err_Invalid_Argument; + auto* node = list->head; + while (node) { + auto* next = node->next; + const int error = iterator(node, user); + if (error) return error; + node = next; + } + return 0; +} + +void APS5_VABI FT_List_Finalize_nid_postfix(GuestListRec* list, GuestListDestructor destroy, GuestMemoryRec* memory, void* user) { + if (!list || !memory) return; + const ListWrite write(list); + auto* node = list->head; + while (node) { + auto* next = node->next; + if (destroy) destroy(memory, node->data, user); + memory->free(memory, node); + node = next; + } + list->head = nullptr; + list->tail = nullptr; +} + +} diff --git a/core/libs/prx/libSceFreeType/src/Names.cpp b/core/libs/prx/libSceFreeType/src/Names.cpp new file mode 100644 index 0000000000..02e1f9f39c --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/Names.cpp @@ -0,0 +1,56 @@ +#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_SFNT_NAMES_H + +#include + +using namespace FreeType; + +extern "C" { + +int APS5_VABI FT_Get_Glyph_Name_nid_postfix(GuestFaceRec* face, std::uint32_t glyphIndex, void* buffer, std::uint32_t bufferSize) { + const GuestFace* owner = FaceOf(face); + const FT_Face host = owner ? owner->host : nullptr; + if (!host || !buffer || bufferSize == 0) return FT_Get_Glyph_Name(host, glyphIndex, buffer, bufferSize); + GuestArena::HostWrite write(buffer, bufferSize); + if (static_cast(glyphIndex) >= host->num_glyphs) { + static_cast(buffer)[0] = '\0'; + return FT_Err_Invalid_Glyph_Index; + } + return FT_Get_Glyph_Name(host, glyphIndex, buffer, bufferSize); +} + +std::uint32_t APS5_VABI FT_Get_Name_Index_nid_postfix(GuestFaceRec* face, const char* glyphName) { + const GuestFace* owner = FaceOf(face); + return FT_Get_Name_Index(owner ? owner->host : nullptr, glyphName); +} + +const char* APS5_VABI FT_Get_Postscript_Name_nid_postfix(GuestFaceRec* face) { + const GuestFace* owner = FaceOf(face); + return FT_Get_Postscript_Name(owner ? owner->host : nullptr); +} + +std::uint32_t APS5_VABI FT_Get_Sfnt_Name_Count_nid_postfix(GuestFaceRec* face) { + const GuestFace* owner = FaceOf(face); + return FT_Get_Sfnt_Name_Count(owner ? owner->host : nullptr); +} + +int APS5_VABI FT_Get_Sfnt_Name_nid_postfix(GuestFaceRec* face, std::uint32_t index, GuestSfntName* name) { + const GuestFace* owner = FaceOf(face); + FT_SfntName native{}; + const FT_Error error = FT_Get_Sfnt_Name(owner ? owner->host : nullptr, index, name ? &native : nullptr); + if (!error) { + name->platform_id = native.platform_id; + name->encoding_id = native.encoding_id; + name->language_id = native.language_id; + name->name_id = native.name_id; + name->string = native.string; + name->string_len = native.string_len; + } + return error; +} + +} diff --git a/core/libs/prx/libSceFreeType/src/Outline.cpp b/core/libs/prx/libSceFreeType/src/Outline.cpp new file mode 100644 index 0000000000..8c9892ad30 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/Outline.cpp @@ -0,0 +1,65 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestFreeType.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include FT_OUTLINE_H + +#include +#include +#include +#include + +using namespace FreeType; + +extern "C" { + +int APS5_VABI FT_Outline_Check_nid_postfix(GuestOutline* outline) { + if (!outline) return FT_Outline_Check(nullptr); + FT_Outline native{outline->n_contours, outline->n_points, nullptr, nullptr, outline->contours, outline->flags}; + return FT_Outline_Check(&native); +} + +void APS5_VABI FT_Outline_Get_CBox_nid_postfix(const GuestOutline* outline, GuestBBox* box) { + if (!outline || !box) return; + GuestBBox bounds{}; + if (outline->n_points) { + const GuestVector& first = outline->points[0]; + bounds = {first.x, first.y, first.x, first.y}; + for (std::size_t index = 1; index < outline->n_points; ++index) { + const GuestVector& point = outline->points[index]; + bounds.xMin = std::min(bounds.xMin, point.x); + bounds.yMin = std::min(bounds.yMin, point.y); + bounds.xMax = std::max(bounds.xMax, point.x); + bounds.yMax = std::max(bounds.yMax, point.y); + } + } + *box = bounds; +} + +void APS5_VABI FT_Outline_Translate_nid_postfix(const GuestOutline* outline, std::int64_t xOffset, std::int64_t yOffset) { + if (!outline) return; + for (std::size_t index = 0; index < outline->n_points; ++index) { + GuestVector& point = outline->points[index]; + point.x = std::bit_cast(static_cast(point.x) + static_cast(xOffset)); + point.y = std::bit_cast(static_cast(point.y) + static_cast(yOffset)); + } +} + +void APS5_VABI FT_Outline_Reverse_nid_postfix(GuestOutline* outline) { + if (!outline) return; + std::size_t first = 1; + for (std::size_t contour = 0; contour < outline->n_contours; ++contour) { + const std::size_t last = outline->contours[contour]; + for (std::size_t left = first, right = last; left < right; ++left, --right) { + std::swap(outline->points[left], outline->points[right]); + } + for (std::size_t left = first, right = last; left < right; ++left, --right) { + std::swap(outline->tags[left], outline->tags[right]); + } + first = last + 2; + } + outline->flags ^= FT_OUTLINE_REVERSE_FILL; +} + +} diff --git a/core/libs/prx/libSceFreeType/src/OutlineBorders.cpp b/core/libs/prx/libSceFreeType/src/OutlineBorders.cpp new file mode 100644 index 0000000000..34d54d7c03 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/OutlineBorders.cpp @@ -0,0 +1,68 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include +#include +#include +#include + +namespace { + +bool Clockwise(const FreeType::GuestOutline* outline) { + if (!outline || outline->n_points == 0) return true; + if (!outline->points) throw std::invalid_argument("FreeType outline border: null points"); + auto minimum = outline->points[0]; + auto maximum = minimum; + for (unsigned i = 1; i < outline->n_points; ++i) { + const auto point = outline->points[i]; + minimum.x = std::min(minimum.x, point.x); + minimum.y = std::min(minimum.y, point.y); + maximum.x = std::max(maximum.x, point.x); + maximum.y = std::max(maximum.y, point.y); + } + if (minimum.x == maximum.x || minimum.y == maximum.y) return false; + constexpr std::int64_t limit = 0x1000000; + if (minimum.x < -limit || minimum.y < -limit || maximum.x > limit || maximum.y > limit) return false; + if (outline->n_contours == 0) return false; + if (!outline->contours) throw std::invalid_argument("FreeType outline border: null contours"); + unsigned first = 0; + for (unsigned contour = 0; contour < outline->n_contours; ++contour) { + const unsigned last = outline->contours[contour]; + if (last < first || last >= outline->n_points) + throw std::invalid_argument("FreeType outline border: invalid contour endpoint"); + first = last + 1; + } + const auto magnitude = [](std::int64_t value) { return static_cast(value < 0 ? -value : value); }; + const auto shift = [](std::uint32_t value) { const auto bits = std::bit_width(value); return bits > 15 ? bits - 15 : 0; }; + const auto xShift = shift(magnitude(maximum.x) | magnitude(minimum.x)); + const auto yShift = shift(static_cast(maximum.y - minimum.y)); + std::int64_t area = 0; + first = 0; + for (unsigned contour = 0; contour < outline->n_contours; ++contour) { + const unsigned last = outline->contours[contour]; + auto previous = outline->points[last]; + previous.x >>= xShift; + previous.y >>= yShift; + for (unsigned i = first; i <= last; ++i) { + auto current = outline->points[i]; + current.x >>= xShift; + current.y >>= yShift; + area += (current.y - previous.y) * (current.x + previous.x); + previous = current; + } + first = last + 1; + } + return area < 0; +} + +} + +extern "C" { + +std::int32_t APS5_VABI FT_Outline_GetInsideBorder_nid_postfix(const FreeType::GuestOutline* outline) { + return Clockwise(outline) ? 1 : 0; +} + +std::int32_t APS5_VABI FT_Outline_GetOutsideBorder_nid_postfix(const FreeType::GuestOutline* outline) { + return Clockwise(outline) ? 0 : 1; +} + +} diff --git a/core/libs/prx/libSceFreeType/src/OutlineDecompose.cpp b/core/libs/prx/libSceFreeType/src/OutlineDecompose.cpp new file mode 100644 index 0000000000..2304dc5a9b --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/OutlineDecompose.cpp @@ -0,0 +1,130 @@ +#include "prx/libSceFreeType/include/GuestFreeType.hpp" + +#include +#include FT_FREETYPE_H + +#include +#include +#include + +using namespace FreeType; + +namespace { + +std::int64_t Scale(std::int64_t value, std::int32_t shift, std::int64_t delta) { + if (shift < 0 || shift >= 63) { + throw std::runtime_error("FT_Outline_Decompose: a shift outside 0-62 is not implemented"); + } + constexpr auto minimum = std::numeric_limits::min(); + constexpr auto maximum = std::numeric_limits::max(); + const std::int64_t factor = std::int64_t{1} << shift; + if (value < minimum / factor || value > maximum / factor) { + throw std::runtime_error("FT_Outline_Decompose: overflowing coordinate scaling is not implemented"); + } + const std::int64_t scaled = value * factor; + if ((delta > 0 && scaled < minimum + delta) || (delta < 0 && scaled > maximum + delta)) { + throw std::runtime_error("FT_Outline_Decompose: overflowing coordinate translation is not implemented"); + } + return scaled - delta; +} + +GuestVector Scale(GuestVector point, std::int32_t shift, std::int64_t delta) { + return {Scale(point.x, shift, delta), Scale(point.y, shift, delta)}; +} + +std::int64_t Midpoint(std::int64_t first, std::int64_t second) { + if ((second > 0 && first > std::numeric_limits::max() - second) || + (second < 0 && first < std::numeric_limits::min() - second)) { + throw std::runtime_error("FT_Outline_Decompose: overflowing midpoint arithmetic is not implemented"); + } + return (first + second) / 2; +} + +GuestVector Midpoint(GuestVector first, GuestVector second) { + return {Midpoint(first.x, second.x), Midpoint(first.y, second.y)}; +} + +} + +extern "C" int APS5_VABI FT_Outline_Decompose_nid_postfix(GuestOutline* outline, const GuestOutlineFuncs* functions, void* user) { + if (!outline) return FT_Err_Invalid_Outline; + if (!functions) return FT_Err_Invalid_Argument; + const std::int32_t shift = functions->shift; + const std::int64_t delta = functions->delta; + std::int32_t previousEnd = -1; + for (std::uint32_t contour = 0; contour < outline->n_contours; ++contour) { + const std::int32_t first = previousEnd + 1; + const std::int32_t last = outline->contours[contour]; + if (last < first) return FT_Err_Invalid_Outline; + previousEnd = last; + auto* points = outline->points; + auto* tags = outline->tags; + std::int32_t limit = last; + std::int32_t point = first; + GuestVector start = Scale(points[first], shift, delta); + const GuestVector end = Scale(points[last], shift, delta); + const int firstTag = tags[first] & 3; + if (firstTag == FT_CURVE_TAG_CUBIC) return FT_Err_Invalid_Outline; + if (firstTag == FT_CURVE_TAG_CONIC) { + if ((tags[last] & 3) == FT_CURVE_TAG_ON) { + start = end; + --limit; + } else { + start = Midpoint(start, end); + } + --point; + } + int error = functions->move_to(&start, user); + if (error != 0) return error; + bool closed = false; + while (point < limit) { + ++point; + const int tag = tags[point] & 3; + if (tag == FT_CURVE_TAG_ON) { + const GuestVector to = Scale(points[point], shift, delta); + error = functions->line_to(&to, user); + if (error != 0) return error; + } else if (tag == FT_CURVE_TAG_CONIC) { + GuestVector control = Scale(points[point], shift, delta); + while (true) { + if (point == limit) { + error = functions->conic_to(&control, &start, user); + closed = true; + break; + } + ++point; + const int nextTag = tags[point] & 3; + const GuestVector next = Scale(points[point], shift, delta); + if (nextTag == FT_CURVE_TAG_ON) { + error = functions->conic_to(&control, &next, user); + break; + } + if (nextTag != FT_CURVE_TAG_CONIC) return FT_Err_Invalid_Outline; + const GuestVector middle = Midpoint(control, next); + error = functions->conic_to(&control, &middle, user); + if (error != 0) return error; + control = next; + } + if (error != 0) return error; + } else { + if (point + 1 > limit || (tags[point + 1] & 3) != FT_CURVE_TAG_CUBIC) return FT_Err_Invalid_Outline; + const GuestVector control1 = Scale(points[point], shift, delta); + const GuestVector control2 = Scale(points[point + 1], shift, delta); + point += 2; + if (point <= limit) { + const GuestVector to = Scale(points[point], shift, delta); + error = functions->cubic_to(&control1, &control2, &to, user); + } else { + error = functions->cubic_to(&control1, &control2, &start, user); + closed = true; + } + if (error != 0) return error; + } + } + if (!closed) { + error = functions->line_to(&start, user); + if (error != 0) return error; + } + } + return 0; +} diff --git a/core/libs/prx/libSceFreeType/src/SizeRequest.cpp b/core/libs/prx/libSceFreeType/src/SizeRequest.cpp new file mode 100644 index 0000000000..e70ea25111 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/SizeRequest.cpp @@ -0,0 +1,25 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/General.hpp" + +#include + +using namespace FreeType; + +extern "C" { + +int APS5_VABI FT_Request_Size_nid_postfix(GuestFaceRec* face, const GuestSizeRequestRec* request) { + GuestFace* owner = FaceOf(face); + if (!owner || !owner->host->size || !request) return FT_Request_Size(owner ? owner->host : nullptr, nullptr); + if (request->width < 0 || request->height < 0 || request->type >= FT_SIZE_REQUEST_TYPE_MAX) return FT_Err_Invalid_Argument; + if (request->width > std::numeric_limits::max() || request->height > std::numeric_limits::max()) { + NotImplemented_nid_no_patch(__func__); + } + FT_Size_RequestRec hostRequest{static_cast(request->type), static_cast(request->width), + static_cast(request->height), request->horiResolution, request->vertResolution}; + const FT_Error error = FT_Request_Size(owner->host, &hostRequest); + SyncSize(*owner); + return error; +} + +} diff --git a/core/libs/prx/libSceFreeType/src/Trigonometry.cpp b/core/libs/prx/libSceFreeType/src/Trigonometry.cpp new file mode 100644 index 0000000000..a8f85d47fc --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/Trigonometry.cpp @@ -0,0 +1,81 @@ +#include "prx/libSceFreeType/include/GuestFreeType.hpp" + +#include +#include FT_TRIGONOMETRY_H + +#include +#include +#include +#include + +using namespace FreeType; + +namespace { + +FT_Angle NormalizeAngle(std::int64_t angle) { + return static_cast(angle % (360ll << 16)); +} + +GuestVector TangentVector(std::int64_t angle) { + constexpr std::array angles{ + 1740967, 919879, 466945, 234379, 117304, 58666, 29335, 14668, 7334, 3667, 1833, + 917, 458, 229, 115, 57, 29, 14, 7, 4, 2, 1}; + std::int64_t remaining = NormalizeAngle(angle); + GuestVector vector{1ll << 24, 0}; + while (remaining < -(45ll << 16)) { + vector = {vector.y, -vector.x}; + remaining += 90ll << 16; + } + while (remaining > (45ll << 16)) { + vector = {-vector.y, vector.x}; + remaining -= 90ll << 16; + } + std::int64_t rounding = 1; + for (std::size_t iteration = 1; iteration <= angles.size(); ++iteration) { + const std::int64_t direction = remaining < 0 ? 1 : -1; + const GuestVector step{(vector.y + rounding) >> iteration, (vector.x + rounding) >> iteration}; + vector.x += direction * step.x; + vector.y -= direction * step.y; + remaining += direction * angles[iteration - 1]; + rounding <<= 1; + } + return vector; +} + +} + +extern "C" { + +std::int64_t APS5_VABI FT_Cos_nid_postfix(std::int64_t angle) { + return FT_Cos(NormalizeAngle(angle)); +} + +std::int64_t APS5_VABI FT_Sin_nid_postfix(std::int64_t angle) { + return FT_Sin(NormalizeAngle(angle)); +} + +std::int64_t APS5_VABI FT_Tan_nid_postfix(std::int64_t angle) { + const GuestVector vector = TangentVector(angle); + const std::uint64_t x = static_cast(vector.x < 0 ? -vector.x : vector.x); + const std::uint64_t y = static_cast(vector.y < 0 ? -vector.y : vector.y); + const auto quotient = static_cast(x == 0 ? 0x7FFFFFFFull : ((y << 16) + (x >> 1)) / x); + return (vector.x < 0) != (vector.y < 0) ? -quotient : quotient; +} + +void APS5_VABI FT_Vector_Unit_nid_postfix(GuestVector* vector, std::int64_t angle) { + if (!vector) return; + FT_Vector native{}; + FT_Vector_Unit(&native, NormalizeAngle(angle)); + vector->x = native.x; + vector->y = native.y; +} + +std::int64_t APS5_VABI FT_Angle_Diff_nid_postfix(std::int64_t first, std::int64_t second) { + if ((first > 0 && second < std::numeric_limits::min() + first) || + (first < 0 && second > std::numeric_limits::max() + first)) { + throw std::runtime_error("FT_Angle_Diff: overflowing signed angle subtraction is not implemented"); + } + return FT_Angle_Diff(0, NormalizeAngle(second - first)); +} + +} diff --git a/core/libs/prx/libSceFreeTypeOl/CMakeLists.txt b/core/libs/prx/libSceFreeTypeOl/CMakeLists.txt new file mode 100644 index 0000000000..e8f18eac97 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOl/CMakeLists.txt @@ -0,0 +1,38 @@ +add_library(libSceFreeTypeOl SHARED EXCLUDE_FROM_ALL Export.cpp ../libSceFreeType/src/OutlineBorders.cpp) +target_include_directories(libSceFreeTypeOl PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceFreeTypeOl) + +if(BUILD_TESTING) + add_test_executable(guest_freetype_outline_border_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOutlineBorder.cpp) + target_include_directories(guest_freetype_outline_border_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_outline_border_tests PRIVATE freetype ${CMAKE_DL_LIBS}) + if(WIN32) + target_link_libraries(guest_freetype_outline_border_tests PRIVATE libc) + endif() + configure_windows_unwind(guest_freetype_outline_border_tests) + add_custom_target(freetype_outline_border_patched DEPENDS + ${PATCHED_DIR}/libSceFreeTypeOl.prx ${PATCHED_DIR}/libSceFreeTypeOt.prx ${PATCHED_DIR}/libkernel.prx ${LIBC_PATCHED}) + add_dependencies(guest_freetype_outline_border_tests freetype_outline_border_patched) + add_test(NAME guest_freetype_outline_border_reference COMMAND guest_freetype_outline_border_tests reference) + foreach(module IN ITEMS Ol Ot) + foreach(border IN ITEMS inside outside) + add_test(NAME guest_freetype_outline_border_${module}_${border} + COMMAND guest_freetype_outline_border_tests $ ${border} raw) + add_test(NAME guest_freetype_outline_border_${module}_${border}_patched + COMMAND guest_freetype_outline_border_tests ${PATCHED_DIR}/libSceFreeType${module}.prx ${border} patched) + set_tests_properties(guest_freetype_outline_border_${module}_${border} + guest_freetype_outline_border_${module}_${border}_patched PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freetype_outline_border_${module}_${border} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_freetype_outline_border_${module}_${border}_patched PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:${PATCHED_DIR}") + endif() + endforeach() + endforeach() + set_tests_properties(guest_freetype_outline_border_reference PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freetype_outline_border_reference PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceFreeTypeOl/libSceFreeTypeOl.hpp b/core/libs/prx/libSceFreeTypeOl/libSceFreeTypeOl.hpp new file mode 100644 index 0000000000..e1970ef93b --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOl/libSceFreeTypeOl.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceFreeType/include/Api.hpp" diff --git a/core/libs/prx/libSceFreeTypeOptOl/libSceFreeTypeOptOl.hpp b/core/libs/prx/libSceFreeTypeOptOl/libSceFreeTypeOptOl.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOptOl/libSceFreeTypeOptOl.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt new file mode 100644 index 0000000000..aca3d5c0df --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(libSceFreeTypeOt SHARED EXCLUDE_FROM_ALL Export.cpp ../libSceFreeType/src/OutlineBorders.cpp) +target_include_directories(libSceFreeTypeOt PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceFreeTypeOt) diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp index d81f4a5e32..c4ec1cd9ca 100644 --- a/core/libs/prx/libSceFreeTypeOt/Export.cpp +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -5,6 +5,745 @@ extern "C" { -APS5_DUMMY_FUN + +int APS5_VABI sceFreeTypeOtUnknown_UD7IFu7ML2U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UD7IFu7ML2U", sceFreeTypeOtUnknown_UD7IFu7ML2U_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_2T7lbS_CSzA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2T7lbS+CSzA", sceFreeTypeOtUnknown_2T7lbS_CSzA_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_MGEIQsujKaY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("MGEIQsujKaY", sceFreeTypeOtUnknown_MGEIQsujKaY_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_mkmgcRnAJog_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mkmgcRnAJog", sceFreeTypeOtUnknown_mkmgcRnAJog_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_m18bTUNF_0g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("m18bTUNF-0g", sceFreeTypeOtUnknown_m18bTUNF_0g_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_vTZ34rw1sfg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("vTZ34rw1sfg", sceFreeTypeOtUnknown_vTZ34rw1sfg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_PvCNusrp9xQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("PvCNusrp9xQ", sceFreeTypeOtUnknown_PvCNusrp9xQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_dlM4ASZdcjk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dlM4ASZdcjk", sceFreeTypeOtUnknown_dlM4ASZdcjk_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Ev3BqJ_U1Fg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Ev3BqJ-U1Fg", sceFreeTypeOtUnknown_Ev3BqJ_U1Fg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZhtAXOoLNyo", sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("73vg+nRH68g", sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wBKAYO7yEXw", sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("nSIux4OQyJM", sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Dr3F3qrSQ2c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Dr3F3qrSQ2c", sceFreeTypeOtUnknown_Dr3F3qrSQ2c_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_avLzN0tTxLI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("avLzN0tTxLI", sceFreeTypeOtUnknown_avLzN0tTxLI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_0rKmI1ZbMAc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0rKmI1ZbMAc", sceFreeTypeOtUnknown_0rKmI1ZbMAc_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_BBRhYfYBDgU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BBRhYfYBDgU", sceFreeTypeOtUnknown_BBRhYfYBDgU_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_c1nbr8oSWlg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("c1nbr8oSWlg", sceFreeTypeOtUnknown_c1nbr8oSWlg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_AVEEb57HhUw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("AVEEb57HhUw", sceFreeTypeOtUnknown_AVEEb57HhUw_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_x8y0qzLiYOU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("x8y0qzLiYOU", sceFreeTypeOtUnknown_x8y0qzLiYOU_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("VW4GD-TaIUc", sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("GiuNQjoj4fs", sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_5qqbXOGlHw4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5qqbXOGlHw4", sceFreeTypeOtUnknown_5qqbXOGlHw4_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_4LY5pyZ5IQk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4LY5pyZ5IQk", sceFreeTypeOtUnknown_4LY5pyZ5IQk_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_zE0V_0aouZ4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("zE0V+0aouZ4", sceFreeTypeOtUnknown_zE0V_0aouZ4_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_LsyswTUNTRs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LsyswTUNTRs", sceFreeTypeOtUnknown_LsyswTUNTRs_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_AfL_nqNC5fY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("AfL-nqNC5fY", sceFreeTypeOtUnknown_AfL_nqNC5fY_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_WIvTHYs7ZQQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("WIvTHYs7ZQQ", sceFreeTypeOtUnknown_WIvTHYs7ZQQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_yrtsLmTfa48_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("yrtsLmTfa48", sceFreeTypeOtUnknown_yrtsLmTfa48_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_tCMhTh7h8D4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("tCMhTh7h8D4", sceFreeTypeOtUnknown_tCMhTh7h8D4_nid_no_patch); + +int APS5_VABI sceFreeTypeOtNewSize_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("zyezaHweY3U", sceFreeTypeOtNewSize_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_C_PpdS5zris_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("C-PpdS5zris", sceFreeTypeOtUnknown_C_PpdS5zris_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_g8eLxL9VAhU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("g8eLxL9VAhU", sceFreeTypeOtUnknown_g8eLxL9VAhU_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_OeNH41MA2Vs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OeNH41MA2Vs", sceFreeTypeOtUnknown_OeNH41MA2Vs_nid_no_patch); + +int APS5_VABI sceFreeTypeOtOutlineCopy_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Bhm8Injl0KA", sceFreeTypeOtOutlineCopy_nid_no_patch); + +int APS5_VABI sceFreeTypeOtOutlineDecompose_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++lgV9pRsO4", sceFreeTypeOtOutlineDecompose_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BqGJsJcFYaM", sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("umIqupy8UU0", sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("l6Cf9rb9fZQ", sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BgUMBGvhATU", sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_0Aal_TIC4so_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Aal-TIC4so", sceFreeTypeOtUnknown_0Aal_TIC4so_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_4RRjK_Wd1lU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4RRjK-Wd1lU", sceFreeTypeOtUnknown_4RRjK_Wd1lU_nid_no_patch); + +int APS5_VABI sceFreeTypeOtOutlineGetOrientation_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("weVsBCT+TTQ", sceFreeTypeOtOutlineGetOrientation_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QH2emcgeFck", sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_NG7OgYsgcF8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NG7OgYsgcF8", sceFreeTypeOtUnknown_NG7OgYsgcF8_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("9km02mPqPJE", sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Lv6K1d47jg", sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_8fAMdZCnewA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("8fAMdZCnewA", sceFreeTypeOtUnknown_8fAMdZCnewA_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_LsB538_IPrE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LsB538+IPrE", sceFreeTypeOtUnknown_LsB538_IPrE_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_hz_J3hohXW8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hz-J3hohXW8", sceFreeTypeOtUnknown_hz_J3hohXW8_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_KVbmKCGGowg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KVbmKCGGowg", sceFreeTypeOtUnknown_KVbmKCGGowg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_hF3stKUWbWg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hF3stKUWbWg", sceFreeTypeOtUnknown_hF3stKUWbWg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_p5GNJaXO2yE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("p5GNJaXO2yE", sceFreeTypeOtUnknown_p5GNJaXO2yE_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_8cnVx1yH9_I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("8cnVx1yH9-I", sceFreeTypeOtUnknown_8cnVx1yH9_I_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_HVSaZbxYi2s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HVSaZbxYi2s", sceFreeTypeOtUnknown_HVSaZbxYi2s_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("qAPXM5DN62Q", sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5ipBcLYVc2k", sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_8A0cAU4yBVY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("8A0cAU4yBVY", sceFreeTypeOtUnknown_8A0cAU4yBVY_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_z9Wsaf34JA4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("z9Wsaf34JA4", sceFreeTypeOtUnknown_z9Wsaf34JA4_nid_no_patch); + +int APS5_VABI sceFreeTypeOtRequestSize_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("PaDk9bdmdhU", sceFreeTypeOtRequestSize_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Z8KG6700sw4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Z8KG6700sw4", sceFreeTypeOtUnknown_Z8KG6700sw4_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_uK7vGxAQVxM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("uK7vGxAQVxM", sceFreeTypeOtUnknown_uK7vGxAQVxM_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_zetC8Tp1S8w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("zetC8Tp1S8w", sceFreeTypeOtUnknown_zetC8Tp1S8w_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_DAmwG6U7YIc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DAmwG6U7YIc", sceFreeTypeOtUnknown_DAmwG6U7YIc_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Yu9oXRVmTTo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Yu9oXRVmTTo", sceFreeTypeOtUnknown_Yu9oXRVmTTo_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_ihIHD8BTNxI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ihIHD8BTNxI", sceFreeTypeOtUnknown_ihIHD8BTNxI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_yCEJIsjIOO8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("yCEJIsjIOO8", sceFreeTypeOtUnknown_yCEJIsjIOO8_nid_no_patch); + +int APS5_VABI sceFreeTypeOtSin_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("L1uHkWTazkI", sceFreeTypeOtSin_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_C7N2GuwqTA0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("C7N2GuwqTA0", sceFreeTypeOtUnknown_C7N2GuwqTA0_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_JgtSc0g3G6E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("JgtSc0g3G6E", sceFreeTypeOtUnknown_JgtSc0g3G6E_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_gVf7xRLdWm8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("gVf7xRLdWm8", sceFreeTypeOtUnknown_gVf7xRLdWm8_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_FHdeCFVf_f8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("FHdeCFVf-f8", sceFreeTypeOtUnknown_FHdeCFVf_f8_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Qmgc0XACzRg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Qmgc0XACzRg", sceFreeTypeOtUnknown_Qmgc0XACzRg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_0rGx_oFMLyk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0rGx-oFMLyk", sceFreeTypeOtUnknown_0rGx_oFMLyk_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_thA48N_0VwQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("thA48N-0VwQ", sceFreeTypeOtUnknown_thA48N_0VwQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_jl0VxYZb8_o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jl0VxYZb8-o", sceFreeTypeOtUnknown_jl0VxYZb8_o_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Er0Lin_u20o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Er0Lin-u20o", sceFreeTypeOtUnknown_Er0Lin_u20o_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_KHj92a3WDn4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KHj92a3WDn4", sceFreeTypeOtUnknown_KHj92a3WDn4_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_tDssQCH2syY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("tDssQCH2syY", sceFreeTypeOtUnknown_tDssQCH2syY_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_dXJK59vXRxs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dXJK59vXRxs", sceFreeTypeOtUnknown_dXJK59vXRxs_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_BB4gl7SgLdk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BB4gl7SgLdk", sceFreeTypeOtUnknown_BB4gl7SgLdk_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Fv8MWCUH8k8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Fv8MWCUH8k8", sceFreeTypeOtUnknown_Fv8MWCUH8k8_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_RzgDJTjQst4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("RzgDJTjQst4", sceFreeTypeOtUnknown_RzgDJTjQst4_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_4yt9cD1qcEI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4yt9cD1qcEI", sceFreeTypeOtUnknown_4yt9cD1qcEI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_u6Xquj1bFwc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("u6Xquj1bFwc", sceFreeTypeOtUnknown_u6Xquj1bFwc_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown__Xm1cLEpBec_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-Xm1cLEpBec", sceFreeTypeOtUnknown__Xm1cLEpBec_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_uDXTnGbAz44_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("uDXTnGbAz44", sceFreeTypeOtUnknown_uDXTnGbAz44_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_iruPsrXCN7U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("iruPsrXCN7U", sceFreeTypeOtUnknown_iruPsrXCN7U_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_a7RlARfexoo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("a7RlARfexoo", sceFreeTypeOtUnknown_a7RlARfexoo_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_jX1zq1i8KfI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jX1zq1i8KfI", sceFreeTypeOtUnknown_jX1zq1i8KfI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_U_0WKVAePww_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("U+0WKVAePww", sceFreeTypeOtUnknown_U_0WKVAePww_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_dlbnv6Wass8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dlbnv6Wass8", sceFreeTypeOtUnknown_dlbnv6Wass8_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_cmd41395g7Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cmd41395g7Y", sceFreeTypeOtUnknown_cmd41395g7Y_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_SX5I_XzH8HI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("SX5I+XzH8HI", sceFreeTypeOtUnknown_SX5I_XzH8HI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Bmi8PdvsM3M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Bmi8PdvsM3M", sceFreeTypeOtUnknown_Bmi8PdvsM3M_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_jJh4SMt8NFQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jJh4SMt8NFQ", sceFreeTypeOtUnknown_jJh4SMt8NFQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_1z6bBg1qBfU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1z6bBg1qBfU", sceFreeTypeOtUnknown_1z6bBg1qBfU_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_mF0B64O954g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mF0B64O954g", sceFreeTypeOtUnknown_mF0B64O954g_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_c002A4zTOgA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("c002A4zTOgA", sceFreeTypeOtUnknown_c002A4zTOgA_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_mcT796Qgj_g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mcT796Qgj+g", sceFreeTypeOtUnknown_mcT796Qgj_g_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_b5gZp_r3WWI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("b5gZp-r3WWI", sceFreeTypeOtUnknown_b5gZp_r3WWI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_S_n0Md8Rjfw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("S+n0Md8Rjfw", sceFreeTypeOtUnknown_S_n0Md8Rjfw_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_6a1p3QgXvo0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6a1p3QgXvo0", sceFreeTypeOtUnknown_6a1p3QgXvo0_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_JFSCOXTEvfg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("JFSCOXTEvfg", sceFreeTypeOtUnknown_JFSCOXTEvfg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_xejRyEbsLcw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xejRyEbsLcw", sceFreeTypeOtUnknown_xejRyEbsLcw_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_R1mZutnlTiM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("R1mZutnlTiM", sceFreeTypeOtUnknown_R1mZutnlTiM_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_4J4Baf8ana8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4J4Baf8ana8", sceFreeTypeOtUnknown_4J4Baf8ana8_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Az4H9FlfxCA", sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_xj3tNJptfLI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xj3tNJptfLI", sceFreeTypeOtUnknown_xj3tNJptfLI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_TPKhRt1QIeE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("TPKhRt1QIeE", sceFreeTypeOtUnknown_TPKhRt1QIeE_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_6_yXRlVrjbc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6+yXRlVrjbc", sceFreeTypeOtUnknown_6_yXRlVrjbc_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown__RLa_6fbshA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+RLa-6fbshA", sceFreeTypeOtUnknown__RLa_6fbshA_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_pBqh_Uh_gns_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("pBqh-Uh-gns", sceFreeTypeOtUnknown_pBqh_Uh_gns_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2rcvKZfm5rI", sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_LfyUi_LkdCk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LfyUi-LkdCk", sceFreeTypeOtUnknown_LfyUi_LkdCk_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_rzIWh7FJnmU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("rzIWh7FJnmU", sceFreeTypeOtUnknown_rzIWh7FJnmU_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_cKndiZnt0EA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cKndiZnt0EA", sceFreeTypeOtUnknown_cKndiZnt0EA_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_eqGCaeizvWM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("eqGCaeizvWM", sceFreeTypeOtUnknown_eqGCaeizvWM_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() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Po8kyd3DlIw", sceFreeTypeOtUnknown_Po8kyd3DlIw_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_B3uU5aDZn94_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("B3uU5aDZn94", sceFreeTypeOtUnknown_B3uU5aDZn94_nid_no_patch); + +int APS5_VABI sceFreeTypeOtTan_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dojyaLKzKN0", sceFreeTypeOtTan_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("08-mhD-AAds", sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("64XMq6sDxUo", sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_4xG87W9dTSg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4xG87W9dTSg", sceFreeTypeOtUnknown_4xG87W9dTSg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_4_SScYDT5Pg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4+SScYDT5Pg", sceFreeTypeOtUnknown_4_SScYDT5Pg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_2rmvbvOCyjY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2rmvbvOCyjY", sceFreeTypeOtUnknown_2rmvbvOCyjY_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_1jmC2QcpRl0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1jmC2QcpRl0", sceFreeTypeOtUnknown_1jmC2QcpRl0_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_HMcNe70D9TI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HMcNe70D9TI", sceFreeTypeOtUnknown_HMcNe70D9TI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_6wJwUIVwAxg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6wJwUIVwAxg", sceFreeTypeOtUnknown_6wJwUIVwAxg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_PrZtZyFwM6c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("PrZtZyFwM6c", sceFreeTypeOtUnknown_PrZtZyFwM6c_nid_no_patch); + +int APS5_VABI sceFreeTypeOtVectorUnit_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("3WpTQrvGUcU", sceFreeTypeOtVectorUnit_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_P6WOAeb0lJ8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("P6WOAeb0lJ8", sceFreeTypeOtUnknown_P6WOAeb0lJ8_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_XGVABnpNksQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("XGVABnpNksQ", sceFreeTypeOtUnknown_XGVABnpNksQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_Ln9I6YfmYJU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Ln9I6YfmYJU", sceFreeTypeOtUnknown_Ln9I6YfmYJU_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_7cVMwHmN308_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("7cVMwHmN308", sceFreeTypeOtUnknown_7cVMwHmN308_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_AEU2tkjdZ2w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("AEU2tkjdZ2w", sceFreeTypeOtUnknown_AEU2tkjdZ2w_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_2lSRg9FfIOY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2lSRg9FfIOY", sceFreeTypeOtUnknown_2lSRg9FfIOY_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_fPrLu1YGOaw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("fPrLu1YGOaw", sceFreeTypeOtUnknown_fPrLu1YGOaw_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_t3vCgnT5YL0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("t3vCgnT5YL0", sceFreeTypeOtUnknown_t3vCgnT5YL0_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_8bkQ_YRnpOE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("8bkQ+YRnpOE", sceFreeTypeOtUnknown_8bkQ_YRnpOE_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_q4qhGebYuGI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("q4qhGebYuGI", sceFreeTypeOtUnknown_q4qhGebYuGI_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_ni_K_leoQNg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ni-K-leoQNg", sceFreeTypeOtUnknown_ni_K_leoQNg_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_XfcgJB6V2a0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("XfcgJB6V2a0", sceFreeTypeOtUnknown_XfcgJB6V2a0_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_TWzf5iykiGw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("TWzf5iykiGw", sceFreeTypeOtUnknown_TWzf5iykiGw_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown__b4iVKArt94_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-b4iVKArt94", sceFreeTypeOtUnknown__b4iVKArt94_nid_no_patch); + +int APS5_VABI sceFreeTypeOtUnknown_uSNqxrARpEQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("uSNqxrARpEQ", sceFreeTypeOtUnknown_uSNqxrARpEQ_nid_no_patch); } diff --git a/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp new file mode 100644 index 0000000000..e1970ef93b --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceFreeType/include/Api.hpp" diff --git a/core/libs/prx/libSceFsInternalForVsh/Export.cpp b/core/libs/prx/libSceFsInternalForVsh/Export.cpp index 4a393cfbb7..e98aa4a828 100644 --- a/core/libs/prx/libSceFsInternalForVsh/Export.cpp +++ b/core/libs/prx/libSceFsInternalForVsh/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFsInternalForVsh/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -211,13 +212,11 @@ int APS5_VABI sceFsDrfsResumeSocket() { } int APS5_VABI sceFsErriCtl() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x8002002d); } int APS5_VABI sceFsErriGetState() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x8002002d); } int APS5_VABI sceFsExfatMkfsExternalHdd() { diff --git a/core/libs/prx/libSceFsInternalForVsh/include/Api.hpp b/core/libs/prx/libSceFsInternalForVsh/include/Api.hpp new file mode 100644 index 0000000000..15f7c8533c --- /dev/null +++ b/core/libs/prx/libSceFsInternalForVsh/include/Api.hpp @@ -0,0 +1,184 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceBdSchedCancelBackgroundCopyRequest(); +int APS5_VABI sceBdSchedCancelPrioritizedBackgroundCopyRequest(); +int APS5_VABI sceBdSchedConfigure(); +int APS5_VABI sceBdSchedConfigure2(); +int APS5_VABI sceBdSchedDeconfigure(); +int APS5_VABI sceBdSchedGetBackgroundCopyRequest(); +int APS5_VABI sceBdSchedGetExtentMap(); +int APS5_VABI sceBdSchedGetPrioritizedBackgroundCopyRequest(); +int APS5_VABI sceBdSchedGetState(); +int APS5_VABI sceBdSchedSetBackgroundCopyRequest(); +int APS5_VABI sceBdSchedSetDiscIdentity(); +int APS5_VABI sceBdSchedSetPrioritizedBackgroundCopyRequest(); +int APS5_VABI sceBdSchedStartGame(); +int APS5_VABI sceBdSchedStartRipping(); +int APS5_VABI sceBdSchedStopScheduling(); +int APS5_VABI sceFsBfsCheckCleanFlag(); +int APS5_VABI sceFsBindApplication(); +int APS5_VABI sceFsBindPackages(); +int APS5_VABI sceFsCheckFormat(); +int APS5_VABI sceFsCheckSingleUser(); +int APS5_VABI sceFsCreateExfatPartition(); +int APS5_VABI sceFsCreatePfsSaveDataImage(); +int APS5_VABI sceFsCreatePfsTrophyDataImage(); +int APS5_VABI sceFsCreatePprPfsTrophyDataImage(); +int APS5_VABI sceFsDeviceAlignedPread(); +int APS5_VABI sceFsDeviceAlignedPwrite(); +int APS5_VABI sceFsDevpfsA53IocGetBuildMetadataStatus(); +int APS5_VABI sceFsDevpfsCancelWaitEvent(); +int APS5_VABI sceFsDevpfsCreateForVSH(); +int APS5_VABI sceFsDevpfsDeleteForVSH(); +int APS5_VABI sceFsDevpfsDeregisterEvent(); +int APS5_VABI sceFsDevpfsEventComplete(); +int APS5_VABI sceFsDevpfsEventResponseComplete(); +int APS5_VABI sceFsDevpfsFetchMetaEntry(); +int APS5_VABI sceFsDevpfsFsck(); +int APS5_VABI sceFsDevpfsInitFetchMetaEntryCtx(); +int APS5_VABI sceFsDevpfsMountForFSMP(); +int APS5_VABI sceFsDevpfsRegisterEvent(); +int APS5_VABI sceFsDevpfsWaitEvent(); +int APS5_VABI sceFsDrfsRegisterSocket(); +int APS5_VABI sceFsDrfsResumeSocket(); +int APS5_VABI sceFsErriCtl(); +int APS5_VABI sceFsErriGetState(); +int APS5_VABI sceFsExfatMkfsExternalHdd(); +int APS5_VABI sceFsExfatfsMkfs(); +int APS5_VABI sceFsExtUSBSchedDestroy(); +int APS5_VABI sceFsExtUSBSchedInsert(); +int APS5_VABI sceFsExtUSBSchedInsertForHDD(); +int APS5_VABI sceFsExternalStorageDeconfigure(); +int APS5_VABI sceFsExternalStorageGetRawDevice(); +int APS5_VABI sceFsExternalStorageInitialize(); +int APS5_VABI sceFsExternalStorageVerifyMetadata(); +int APS5_VABI sceFsGetAttribute(); +int APS5_VABI sceFsGetClusterStatistics(); +int APS5_VABI sceFsGetDeviceSectorsize(); +int APS5_VABI sceFsGetFsMetadata(); +int APS5_VABI sceFsGetFsMetadataWithProgress(); +int APS5_VABI sceFsGetSmrInfo(); +int APS5_VABI sceFsGschedCtl(); +int APS5_VABI sceFsISSchedConfigCurrentThread(); +int APS5_VABI sceFsISSchedConfigDescriptor(); +int APS5_VABI sceFsISSchedConfigThread(); +int APS5_VABI sceFsISSchedMapStat(); +int APS5_VABI sceFsISSchedResultToPriority(); +int APS5_VABI sceFsISSchedResultToSlot(); +int APS5_VABI sceFsISSchedStatCurrentThread(); +int APS5_VABI sceFsISSchedStatDescriptor(); +int APS5_VABI sceFsISSchedStatThread(); +int APS5_VABI sceFsISSchedUnmapStat(); +int APS5_VABI sceFsInitCreatePfsSaveDataOpt(); +int APS5_VABI sceFsInitCreatePfsTrophyDataOpt(); +int APS5_VABI sceFsInitMountDownloadDataOpt(); +int APS5_VABI sceFsInitMountGamePkgOpt(); +int APS5_VABI sceFsInitMountLwfsCompatOpt(); +int APS5_VABI sceFsInitMountLwfsOpt(); +int APS5_VABI sceFsInitMountPprPkgOpt(); +int APS5_VABI sceFsInitMountSaveDataOpt(); +int APS5_VABI sceFsInitMountTrophyDataOpt(); +int APS5_VABI sceFsInitUmountDownloadDataOpt(); +int APS5_VABI sceFsInitUmountGamePkgOpt(); +int APS5_VABI sceFsInitUmountLwfsCompatOpt(); +int APS5_VABI sceFsInitUmountLwfsOpt(); +int APS5_VABI sceFsInitUmountPprPkgOpt(); +int APS5_VABI sceFsInitUmountSaveDataOpt(); +int APS5_VABI sceFsInitUmountTrophyDataOpt(); +int APS5_VABI sceFsInitWorkspaceFileMetadataParam(); +int APS5_VABI sceFsLvdAttach(); +int APS5_VABI sceFsLvdAttach2(); +int APS5_VABI sceFsLvdAttachPartiallyCryptedDownloadData(); +int APS5_VABI sceFsLvdAttachSingleDefaultImage(); +int APS5_VABI sceFsLvdAttachSingleImage(); +int APS5_VABI sceFsLvdDetach(); +int APS5_VABI sceFsLvdDisableLayer(); +int APS5_VABI sceFsLvdEnableLayer(); +int APS5_VABI sceFsLvdSetDiscIdentity(); +int APS5_VABI sceFsLvdWaitResumed(); +int APS5_VABI sceFsMlfsMount(); +int APS5_VABI sceFsMlfsMountWithFlags(); +int APS5_VABI sceFsMlfsUmount(); +int APS5_VABI sceFsMountDownloadData(); +int APS5_VABI sceFsMountGamePkg(); +int APS5_VABI sceFsMountLwfs(); +int APS5_VABI sceFsMountLwfsCompat(); +int APS5_VABI sceFsMountPartiallyCryptedDownloadData(); +int APS5_VABI sceFsMountPprPkg(); +int APS5_VABI sceFsMountSaveData(); +int APS5_VABI sceFsMountTrophyData(); +int APS5_VABI sceFsNsfsAddOverlayToNamespace(); +int APS5_VABI sceFsNsfsMountWithFlags(); +int APS5_VABI sceFsNsfsSetGlobalExclude(); +int APS5_VABI sceFsNsfsUmount(); +int APS5_VABI sceFsPartitionTrim(); +int APS5_VABI sceFsReserveCluster(); +int APS5_VABI sceFsSetAttribute(); +int APS5_VABI sceFsSetClusterCacheSize(); +int APS5_VABI sceFsSetFileReservedTotal(); +int APS5_VABI sceFsSetSystemReserveSize(); +int APS5_VABI sceFsSetWorkspaceFileHash(); +int APS5_VABI sceFsStatWorkspace(); +int APS5_VABI sceFsSysStorageGetStatus(); +int APS5_VABI sceFsTransactionCancel(); +int APS5_VABI sceFsTransactionSaveDataInitMountOpt(); +int APS5_VABI sceFsTransactionSaveDataMount(); +int APS5_VABI sceFsTransactionStart(); +int APS5_VABI sceFsTrophyImageError(); +int APS5_VABI sceFsUfsAllocateAdditionalContents(); +int APS5_VABI sceFsUfsAllocateAligned(); +int APS5_VABI sceFsUfsAllocateDownloadData(); +int APS5_VABI sceFsUfsAllocateGameImage(); +int APS5_VABI sceFsUfsAllocatePatchImage(); +int APS5_VABI sceFsUfsAllocateSaveData(); +int APS5_VABI sceFsUfsAllocateTrophyData(); +int APS5_VABI sceFsUfsCheckCleanFlag(); +int APS5_VABI sceFsUfsCheckFixedCylinderGroupSize(); +int APS5_VABI sceFsUfsCollectTelemetry(); +int APS5_VABI sceFsUfsCollectTelemetry2(); +int APS5_VABI sceFsUfsDirectWrite(); +int APS5_VABI sceFsUfsEstimateZoneCapacityForUser(); +int APS5_VABI sceFsUfsExtendFile(); +int APS5_VABI sceFsUfsFsck(); +int APS5_VABI sceFsUfsFsckWithMM(); +int APS5_VABI sceFsUfsGetExtentMap(); +int APS5_VABI sceFsUfsGrowLwfs(); +int APS5_VABI sceFsUfsGrowfs(); +int APS5_VABI sceFsUfsHideBlocksFromUser(); +int APS5_VABI sceFsUfsInitalizeSparseFile(); +int APS5_VABI sceFsUfsLargewriteFsck(); +int APS5_VABI sceFsUfsLargewriteFsckWithMM(); +int APS5_VABI sceFsUfsMkLwfs(); +int APS5_VABI sceFsUfsMkfs(); +int APS5_VABI sceFsUfsMkfsForExternalHDD(); +int APS5_VABI sceFsUfsMkfsForSystem(); +int APS5_VABI sceFsUfsMkfsWithFixedCylinderGroupSize(); +int APS5_VABI sceFsUfsReallocateSparseFile(); +int APS5_VABI sceFsUfsRecryptBlocks(); +int APS5_VABI sceFsUfsRecryptGetState(); +int APS5_VABI sceFsUfsRecryptIsUnfinished(); +int APS5_VABI sceFsUfsRecryptSyncMetadata(); +int APS5_VABI sceFsUfsSeekHoleInFile(); +int APS5_VABI sceFsUfsStopOperation(); +int APS5_VABI sceFsUmountDownloadData(); +int APS5_VABI sceFsUmountGamePkg(); +int APS5_VABI sceFsUmountLwfs(); +int APS5_VABI sceFsUmountLwfsCompat(); +int APS5_VABI sceFsUmountSaveData(); +int APS5_VABI sceFsUmountTrophyData(); +int APS5_VABI sceFsUmountWorkspace(); +int APS5_VABI sceFsUnbindApplication(); +int APS5_VABI sceFsUnbindPackages(); +int APS5_VABI sceFsWsCreate(); +int APS5_VABI sceFsWsDelete(); +int APS5_VABI sceFsWsFetchFileMetadata(); +int APS5_VABI sceFsWsFsck(); +int APS5_VABI sceFsWsGetStatus(); +int APS5_VABI sceFsWsMount(); +int APS5_VABI sceFsWsUmount(); + +} diff --git a/core/libs/prx/libSceFsInternalForVsh/libSceFsInternalForVsh.hpp b/core/libs/prx/libSceFsInternalForVsh/libSceFsInternalForVsh.hpp new file mode 100644 index 0000000000..bba8925b6e --- /dev/null +++ b/core/libs/prx/libSceFsInternalForVsh/libSceFsInternalForVsh.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceFsInternalForVsh/include/Api.hpp" diff --git a/core/libs/prx/libSceGLSlimVSH/libSceGLSlimVSH.hpp b/core/libs/prx/libSceGLSlimVSH/libSceGLSlimVSH.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/libSceGLSlimVSH.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceGameCustomDataDialog/CMakeLists.txt b/core/libs/prx/libSceGameCustomDataDialog/CMakeLists.txt new file mode 100644 index 0000000000..6f19d7891e --- /dev/null +++ b/core/libs/prx/libSceGameCustomDataDialog/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libSceGameCustomDataDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceGameCustomDataDialog PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceGameCustomDataDialog) + +if(BUILD_TESTING) + add_test_executable(guest_game_custom_data_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestGameCustomDataDialog.cpp) + target_include_directories(guest_game_custom_data_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_game_custom_data_dialog_tests PRIVATE libSceGameCustomDataDialog) + add_test(NAME guest_game_custom_data_dialog COMMAND guest_game_custom_data_dialog_tests) + if(WIN32) + set_tests_properties(guest_game_custom_data_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceGameCustomDataDialog/Export.cpp b/core/libs/prx/libSceGameCustomDataDialog/Export.cpp new file mode 100644 index 0000000000..48093e841d --- /dev/null +++ b/core/libs/prx/libSceGameCustomDataDialog/Export.cpp @@ -0,0 +1,82 @@ +#include "prx/libSceGameCustomDataDialog/include/Api.hpp" +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +namespace { + +constexpr int COMMON_DIALOG_STATUS_NONE = 0; +constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; +constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; +constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; + +std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; + +} + +extern "C" { + +int APS5_VABI sceGameCustomDataDialogInitialize(void) { + int expected = COMMON_DIALOG_STATUS_NONE; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + return 0; +} + +int APS5_VABI sceGameCustomDataDialogOpenA(const void* param) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; +} + +int APS5_VABI sceGameCustomDataDialogUpdateStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceGameCustomDataDialogGetStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceGameCustomDataDialogGetResultA(void* result) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; + std::memcpy(result, &canceled, sizeof(canceled)); + return 0; +} + +int APS5_VABI sceGameCustomDataDialogClose(void) { + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; +} + +int APS5_VABI sceGameCustomDataDialogTerminate(void) { + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + return 0; +} + +int APS5_VABI sceGameCustomDataDialogGetResult() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceGameCustomDataDialogOpen() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceGameCustomDataDialog/include/Api.hpp b/core/libs/prx/libSceGameCustomDataDialog/include/Api.hpp new file mode 100644 index 0000000000..20d28826e7 --- /dev/null +++ b/core/libs/prx/libSceGameCustomDataDialog/include/Api.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceGameCustomDataDialogInitialize(void); +int APS5_VABI sceGameCustomDataDialogOpenA(const void* param); +int APS5_VABI sceGameCustomDataDialogUpdateStatus(void); +int APS5_VABI sceGameCustomDataDialogGetStatus(void); +int APS5_VABI sceGameCustomDataDialogGetResultA(void* result); +int APS5_VABI sceGameCustomDataDialogClose(void); +int APS5_VABI sceGameCustomDataDialogTerminate(void); +int APS5_VABI sceGameCustomDataDialogGetResult(); +int APS5_VABI sceGameCustomDataDialogOpen(); + +} diff --git a/core/libs/prx/libSceGameCustomDataDialog/libSceGameCustomDataDialog.hpp b/core/libs/prx/libSceGameCustomDataDialog/libSceGameCustomDataDialog.hpp new file mode 100644 index 0000000000..c5267d6b1a --- /dev/null +++ b/core/libs/prx/libSceGameCustomDataDialog/libSceGameCustomDataDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceGameCustomDataDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceGameLiveStreaming/Export.cpp b/core/libs/prx/libSceGameLiveStreaming/Export.cpp index 71b57a3129..d9e1f9a227 100644 --- a/core/libs/prx/libSceGameLiveStreaming/Export.cpp +++ b/core/libs/prx/libSceGameLiveStreaming/Export.cpp @@ -1,42 +1,54 @@ +#include "prx/libSceGameLiveStreaming/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceUserService/UserService.hpp" #include +#include +#include +#include + +static std::mutex g_mutex; +static bool g_initialized = false; +static constexpr int ErrorInvalidArgument = static_cast(0x80A00002); +static constexpr int ErrorAlreadyInitialized = static_cast(0x80A00003); +static constexpr int ErrorNotInitialized = static_cast(0x80A00004); +static constexpr std::size_t ProgramInfoSize = 0x134C; -struct GameLiveStreamingStatus2 { - std::int32_t userId; - bool isOnAir; - std::uint8_t align[3]; - std::uint32_t spectatorCounts; - std::uint32_t textMessageCounts; - std::uint32_t commandMessageCounts; - std::uint32_t broadcastVideoResolution; - std::uint8_t reserved[48]; -}; static_assert(sizeof(GameLiveStreamingStatus2) == 72); extern "C" { -int APS5_VABI sceGameLiveStreamingInitialize(size_t heap_size) { - if (heap_size == 0) APS5_INVALID_ARG_EX; +int APS5_VABI sceGameLiveStreamingInitialize(size_t heapSize) { + if (heapSize != 0x4000) return ErrorInvalidArgument; + std::lock_guard lock(g_mutex); + if (g_initialized) return ErrorAlreadyInitialized; + g_initialized = true; return 0; } int APS5_VABI sceGameLiveStreamingTerminate(void) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + g_initialized = false; return 0; } int APS5_VABI sceGameLiveStreamingGetCurrentStatus2(GameLiveStreamingStatus2* status) { - if (status == nullptr) APS5_INVALID_ARG_EX; + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (status == nullptr) return ErrorInvalidArgument; std::memset(status, 0, sizeof(*status)); status->userId = USER_SERVICE_USER_ID_INVALID; return 0; } -int APS5_VABI sceGameLiveStreamingGetProgramInfo() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingGetProgramInfo(GameLiveStreamingProgramInfo* info) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (info == nullptr) return ErrorInvalidArgument; + std::memset(info, 0, ProgramInfoSize); return 0; } @@ -85,7 +97,7 @@ int APS5_VABI sceGameLiveStreamingSetMaxBitrate(void) { return 0; } -int APS5_VABI sceGameLiveStreamingGetCurrentStatus(void) { +int APS5_VABI sceGameLiveStreamingGetCurrentStatus(GameLiveStreamingStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -250,8 +262,10 @@ int APS5_VABI sceGameLiveStreamingStopDebugBroadcast(void) { return 0; } -int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(bool isEnableLiveStreaming) { + std::lock_guard lock(g_mutex); + if (!g_initialized) throw std::logic_error(std::string(__func__) + ": not initialized"); + (void)isEnableLiveStreaming; return 0; } diff --git a/core/libs/prx/libSceGameLiveStreaming/include/Api.hpp b/core/libs/prx/libSceGameLiveStreaming/include/Api.hpp new file mode 100644 index 0000000000..28b744e492 --- /dev/null +++ b/core/libs/prx/libSceGameLiveStreaming/include/Api.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include + +struct GameLiveStreamingProgramInfo; + +struct GameLiveStreamingStatus { + std::uint8_t opaque00[12]; + std::uint8_t reserved[60]; +}; + +static_assert(offsetof(GameLiveStreamingStatus, reserved) == 0x0C); +static_assert(sizeof(GameLiveStreamingStatus) == 0x48); + +struct GameLiveStreamingStatus2 { + std::int32_t userId; + bool isOnAir; + std::uint8_t align[3]; + std::uint32_t spectatorCounts; + std::uint32_t textMessageCounts; + std::uint32_t commandMessageCounts; + std::uint32_t broadcastVideoResolution; + std::uint8_t reserved[48]; +}; + +extern "C" { + +int APS5_VABI sceGameLiveStreamingInitialize(size_t heapSize); +int APS5_VABI sceGameLiveStreamingTerminate(void); +int APS5_VABI sceGameLiveStreamingGetCurrentStatus2(GameLiveStreamingStatus2* status); +int APS5_VABI sceGameLiveStreamingGetProgramInfo(GameLiveStreamingProgramInfo* info); +int APS5_VABI sceGameLiveStreamingScreenInitialize(void); +int APS5_VABI sceGameLiveStreamingScreenInitializeSeparateModeParameter(void); +int APS5_VABI sceGameLiveStreamingScreenTerminate(void); +int APS5_VABI sceGameLiveStreamingScreenCloseSeparateMode(void); +int APS5_VABI sceGameLiveStreamingScreenConfigureSeparateMode(void); +int APS5_VABI sceGameLiveStreamingScreenOpenSeparateMode(void); +int APS5_VABI sceGameLiveStreamingScreenSetMode(void); +int APS5_VABI sceGameLiveStreamingPermitLiveStreaming(void); +int APS5_VABI sceGameLiveStreamingSetMaxBitrate(void); +int APS5_VABI sceGameLiveStreamingGetCurrentStatus(GameLiveStreamingStatus* status); +int APS5_VABI sceGameLiveStreamingEnableSocialFeedback(void); +int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessagesCount(void); +int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessages(void); +int APS5_VABI sceGameLiveStreamingPermitServerSideRecording(void); +int APS5_VABI sceGameLiveStreamingSetMetadata(void); +int APS5_VABI sceGameLiveStreamingSetSpoilerTag(void); +int APS5_VABI sceGameLiveStreamingClearSpoilerTag(void); +int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommands(void); +int APS5_VABI sceGameLiveStreamingClearPresetSocialFeedbackCommands(void); +int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription(void); +int APS5_VABI sceGameLiveStreamingClearSocialFeedbackMessages(void); +int APS5_VABI sceGameLiveStreamingPostSocialMessage(void); +int APS5_VABI sceGameLiveStreamingSetCameraFrameSetting(void); +int APS5_VABI sceGameLiveStreamingSetServiceProviderPermission(void); +int APS5_VABI sceGameLiveStreamingSetDefaultServiceProviderPermission(void); +int APS5_VABI sceGameLiveStreamingSetInvitationSessionId(void); +int APS5_VABI sceGameLiveStreamingStartGenerateStandbyScreenResource(void); +int APS5_VABI sceGameLiveStreamingStopGenerateStandbyScreenResource(void); +int APS5_VABI sceGameLiveStreamingRegisterCallback(void); +int APS5_VABI sceGameLiveStreamingUnregisterCallback(void); +int APS5_VABI sceGameLiveStreamingCheckCallback(void); +int APS5_VABI sceGameLiveStreamingSetStandbyScreenResource(void); +int APS5_VABI sceGameLiveStreamingGetCurrentBroadcastScreenLayout(void); +int APS5_VABI sceGameLiveStreamingLaunchLiveViewer(void); +int APS5_VABI sceGameLiveStreamingLaunchLiveViewerA(void); +int APS5_VABI sceGameLiveStreamingSetGuardAreas(void); +int APS5_VABI sceGameLiveStreamingStartSocialFeedbackMessageFiltering(void); +int APS5_VABI sceGameLiveStreamingStopSocialFeedbackMessageFiltering(void); +int APS5_VABI sceGameLiveStreamingApplySocialFeedbackMessageFilter(void); +int APS5_VABI sceGameLiveStreamingSetLinkCommentPreset(void); +int APS5_VABI sceGameLiveStreamingStartDebugBroadcast(void); +int APS5_VABI sceGameLiveStreamingStopDebugBroadcast(void); +int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(bool isEnableLiveStreaming); + +} diff --git a/core/libs/prx/libSceGameLiveStreaming/libSceGameLiveStreaming.hpp b/core/libs/prx/libSceGameLiveStreaming/libSceGameLiveStreaming.hpp new file mode 100644 index 0000000000..65b14e5bdf --- /dev/null +++ b/core/libs/prx/libSceGameLiveStreaming/libSceGameLiveStreaming.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceGameLiveStreaming/include/Api.hpp" diff --git a/core/libs/prx/libSceGameUpdate/Export.cpp b/core/libs/prx/libSceGameUpdate/Export.cpp index 37c0901309..0e3ef3835e 100644 --- a/core/libs/prx/libSceGameUpdate/Export.cpp +++ b/core/libs/prx/libSceGameUpdate/Export.cpp @@ -1,33 +1,73 @@ +#include "prx/libSceGameUpdate/include/Api.hpp" +#include #include #include #include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -// Update checks need the network, which is not available; requests fail so the game skips the check. -static constexpr int SCE_GAME_UPDATE_ERROR_OFFLINE = static_cast(0x80D50001); static constexpr int SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED = static_cast(0x80412801); static constexpr int SCE_GAME_UPDATE_ERROR_ALREADY_INITIALIZED = static_cast(0x80412802); static constexpr int SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT = static_cast(0x80412803); static constexpr int SCE_GAME_UPDATE_ERROR_INVALID_SIZE = static_cast(0x80412804); static constexpr int SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND = static_cast(0x80412805); +static constexpr int SCE_GAME_UPDATE_ERROR_TOO_MANY_REQUESTS = static_cast(0x80412806); +static constexpr int SCE_GAME_UPDATE_ERROR_ABORTED = static_cast(0x80412807); +static constexpr int FIRST_REQUEST_ID = 0x20000000; +static constexpr int LAST_REQUEST_ID = 0x2FFFFFFF; static_assert(sizeof(GameUpdateCheckParam) == 0x30); static_assert(sizeof(GameUpdateCheckResult) == 0x30); -static std::atomic g_initialized{false}; +struct Request { + int id; + bool aborted; +}; -static int CheckRequestId(int request_id) { - if (request_id <= 0) { - return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND; +static std::mutex stateMutex; +static bool initialized = false; +static int nextRequestId = FIRST_REQUEST_ID; +static std::array requests{}; + +static Request* findRequest(int requestId) { + for (Request& request : requests) { + if (request.id != 0 && request.id == requestId) { + return &request; + } } - if (!g_initialized.load()) { - return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; - } - return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND; + return nullptr; } -static bool HasNonZeroReserved(const GameUpdateCheckParam* param) { +static int followingRequestId(int requestId) { + return requestId == LAST_REQUEST_ID ? FIRST_REQUEST_ID : requestId + 1; +} + +static int takeRequestId() { + int requestId = nextRequestId; + while (findRequest(requestId) != nullptr) { + requestId = followingRequestId(requestId); + } + nextRequestId = followingRequestId(requestId); + return requestId; +} + +static int checkRequestId(int requestId, Request** request) { + if (requestId <= 0) { + return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND; + } + if (!initialized) { + return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; + } + *request = findRequest(requestId); + if (*request == nullptr) { + return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND; + } + return 0; +} + +static bool hasNonZeroReserved(const GameUpdateCheckParam* param) { for (std::uint32_t word : param->reserved) { if (word != 0) { return true; @@ -38,12 +78,18 @@ static bool HasNonZeroReserved(const GameUpdateCheckParam* param) { extern "C" { -int APS5_VABI sceGameUpdateAbortRequest(int request_id) { - return CheckRequestId(request_id); +int APS5_VABI sceGameUpdateAbortRequest(int requestId) { + std::lock_guard lock(stateMutex); + Request* request = nullptr; + if (int error = checkRequestId(requestId, &request); error != 0) { + return error; + } + request->aborted = true; + return 0; } -int APS5_VABI sceGameUpdateCheck(int request_id, const GameUpdateCheckParam* param, GameUpdateCheckResult* result) { - if (request_id <= 0) { +int APS5_VABI sceGameUpdateCheck(int requestId, const GameUpdateCheckParam* param, GameUpdateCheckResult* result) { + if (requestId <= 0) { return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND; } if (param == nullptr || result == nullptr) { @@ -52,55 +98,81 @@ int APS5_VABI sceGameUpdateCheck(int request_id, const GameUpdateCheckParam* par if (param->size != sizeof(GameUpdateCheckParam) || result->size != sizeof(GameUpdateCheckResult)) { return SCE_GAME_UPDATE_ERROR_INVALID_SIZE; } - if (param->option != 0 || HasNonZeroReserved(param)) { + if (param->option != 0 || hasNonZeroReserved(param)) { return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT; } - return CheckRequestId(request_id); -} - -int APS5_VABI sceGameUpdateCreateRequest(void) { - if (!g_initialized.load()) { - return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; + std::lock_guard lock(stateMutex); + Request* request = nullptr; + if (int error = checkRequestId(requestId, &request); error != 0) { + return error; } - return SCE_GAME_UPDATE_ERROR_OFFLINE; -} - -int APS5_VABI sceGameUpdateDeleteRequest(int request_id) { - return CheckRequestId(request_id); -} - -int APS5_VABI sceGameUpdateGetAddcontLatestVersion(uint32_t service_label, const void* entitlement_label, GameUpdateAddcontVersionInfo* info) { - (void)service_label; - if (info == nullptr) { - return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT; + if (request->aborted) { + return SCE_GAME_UPDATE_ERROR_ABORTED; } - if (entitlement_label == nullptr) { - return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT; - } - if (!g_initialized.load()) { - return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; - } - if (info->size < sizeof(GameUpdateAddcontVersionInfo)) { - return SCE_GAME_UPDATE_ERROR_INVALID_SIZE; - } - const std::size_t size = info->size; - *info = {}; - info->size = size; - info->found = false; + result->found = false; + result->addcont_found = false; return 0; } +int APS5_VABI sceGameUpdateCreateRequest(void) { + std::lock_guard lock(stateMutex); + if (!initialized) { + return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; + } + for (Request& request : requests) { + if (request.id != 0) { + continue; + } + const int requestId = takeRequestId(); + request = {requestId, false}; + return requestId; + } + return SCE_GAME_UPDATE_ERROR_TOO_MANY_REQUESTS; +} + +int APS5_VABI sceGameUpdateDeleteRequest(int requestId) { + std::lock_guard lock(stateMutex); + Request* request = nullptr; + if (int error = checkRequestId(requestId, &request); error != 0) { + return error; + } + *request = {}; + return 0; +} + +int APS5_VABI sceGameUpdateGetAddcontLatestVersion(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, GameUpdateAddcontVersionInfo* info) { + (void)serviceLabel; + if (info == nullptr) { + return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT; + } + if (entitlementLabel == nullptr) { + return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT; + } + { + std::lock_guard lock(stateMutex); + if (!initialized) { + return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; + } + } + throw std::runtime_error("sceGameUpdateGetAddcontLatestVersion: entitlement version lookup is not implemented"); +} + int APS5_VABI sceGameUpdateInitialize(void) { - if (g_initialized.exchange(true)) { + std::lock_guard lock(stateMutex); + if (initialized) { return SCE_GAME_UPDATE_ERROR_ALREADY_INITIALIZED; } + initialized = true; return 0; } int APS5_VABI sceGameUpdateTerminate(void) { - if (!g_initialized.exchange(false)) { + std::lock_guard lock(stateMutex); + if (!initialized) { return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; } + initialized = false; + requests = {}; return 0; } diff --git a/core/libs/prx/libSceGameUpdate/include/Api.hpp b/core/libs/prx/libSceGameUpdate/include/Api.hpp new file mode 100644 index 0000000000..2de12ef941 --- /dev/null +++ b/core/libs/prx/libSceGameUpdate/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceGameUpdateAbortRequest(int requestId); +int APS5_VABI sceGameUpdateCheck(int requestId, const GameUpdateCheckParam* param, GameUpdateCheckResult* result); +int APS5_VABI sceGameUpdateCreateRequest(void); +int APS5_VABI sceGameUpdateDeleteRequest(int requestId); +int APS5_VABI sceGameUpdateGetAddcontLatestVersion(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, GameUpdateAddcontVersionInfo* info); +int APS5_VABI sceGameUpdateInitialize(void); +int APS5_VABI sceGameUpdateTerminate(void); + +} diff --git a/core/libs/prx/libSceGameUpdate/libSceGameUpdate.hpp b/core/libs/prx/libSceGameUpdate/libSceGameUpdate.hpp new file mode 100644 index 0000000000..9807085ba6 --- /dev/null +++ b/core/libs/prx/libSceGameUpdate/libSceGameUpdate.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceGameUpdate/include/Api.hpp" diff --git a/core/libs/prx/libSceGic/Export.cpp b/core/libs/prx/libSceGic/Export.cpp index 6fff8198ce..2c4656fad6 100644 --- a/core/libs/prx/libSceGic/Export.cpp +++ b/core/libs/prx/libSceGic/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceGic/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceGic/include/Api.hpp b/core/libs/prx/libSceGic/include/Api.hpp new file mode 100644 index 0000000000..889405a588 --- /dev/null +++ b/core/libs/prx/libSceGic/include/Api.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceGicGetValue(); + +} diff --git a/core/libs/prx/libSceGic/libSceGic.hpp b/core/libs/prx/libSceGic/libSceGic.hpp new file mode 100644 index 0000000000..1f60bb76a8 --- /dev/null +++ b/core/libs/prx/libSceGic/libSceGic.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceGic/include/Api.hpp" diff --git a/core/libs/prx/libSceGifParser/libSceGifParser.hpp b/core/libs/prx/libSceGifParser/libSceGifParser.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceGifParser/libSceGifParser.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceGnmDriverCompat1/libSceGnmDriverCompat1.hpp b/core/libs/prx/libSceGnmDriverCompat1/libSceGnmDriverCompat1.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceGnmDriverCompat1/libSceGnmDriverCompat1.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp b/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp index f8a8c67c6a..b09d2a8a87 100644 --- a/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp +++ b/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp @@ -1,38 +1,152 @@ +#include "prx/libSceGnmDriverForNeoMode/include/Api.hpp" #include +#include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libSceGnmDriverForNeoMode/include/InitSequences.hpp" +#include "prx/libSceGnmDriverForNeoMode/include/EmbeddedShaders.hpp" + +namespace { + +constexpr std::uint32_t InitPacketWords = 0x100; + +template +int WriteHardwareState(std::uint32_t* cmdbuf, std::uint32_t size, const std::array& state) { + if (size < InitPacketWords) return 0; + auto* at = std::copy(GnmNeo::ClearState.begin(), GnmNeo::ClearState.end(), cmdbuf); + at = std::copy(state.begin(), state.end(), at); + const auto padding = static_cast(cmdbuf + InitPacketWords - at - 1); + at[0] = 0xc0001000u | ((padding - 1) << 16); + at[1] = 0; + return InitPacketWords; +} + +template +int WriteContextState(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t packetWords, const std::array& state) { + if (size != packetWords) return 0; + std::copy(state.begin(), state.end(), cmdbuf); + return static_cast(packetWords); +} + +constexpr std::uint32_t OpSetContextReg = 0x69; +constexpr std::uint32_t OpSetShReg = 0x76; + +std::uint32_t* EmitPacket(std::uint32_t* at, std::uint32_t opcode, std::initializer_list body) { + *at++ = 0xc0000000u | ((static_cast(body.size()) - 1) << 16) | (opcode << 8); + for (const auto word : body) *at++ = word; + return at; +} + +std::uint32_t AddressLo(const void* code) { + return static_cast(reinterpret_cast(code) >> 8); +} + +std::uint32_t AddressHi(const void* code) { + return static_cast(reinterpret_cast(code) >> 40); +} + +constexpr int Failure = static_cast(0x8EEE00FFu); +constexpr int NoInterface = static_cast(0x80000000u); +constexpr int ValidationNotEnabled = static_cast(0x80D13FFFu); + +constexpr int CommandFailed = -1; +constexpr std::uint32_t OpNop = 0x10; +constexpr std::uint32_t NeoDrawFlags = 0xe0000000u; +constexpr std::uint32_t AutoIndex = 2; + +constexpr std::uint32_t Header(std::uint32_t opcode, std::uint32_t bodyWords, bool compute = false, bool predicated = false) { + return 0xc0000000u | ((bodyWords - 1) << 16) | (opcode << 8) | (compute ? 2u : 0u) | (predicated ? 1u : 0u); +} + +std::uint32_t* Nop(std::uint32_t* at, std::uint32_t bodyWords) { + at[0] = Header(OpNop, bodyWords); + at[1] = 0; + return at + 1 + bodyWords; +} + +constexpr std::uint32_t ShaderStageCount = 7; +constexpr std::uint32_t StageVs = 2; +constexpr std::uint32_t StageEs = 4; +constexpr std::uint32_t StageLs = 6; +constexpr std::uint32_t IndirectSgprBase[ShaderStageCount] = {0, 0, 0x4c, 0, 0xcc, 0, 0x14c}; +constexpr std::uint32_t DrawIndirectArgsBytes = 0x10; +constexpr std::uint32_t DrawIndexedIndirectArgsBytes = 0x14; + +std::uint32_t IndirectSgpr(std::uint32_t stage, std::uint32_t offset) { + return offset == 0 ? 0 : (offset & 0xffffu) + IndirectSgprBase[stage]; +} + +bool ValidIndirect(std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr) { + return stage < ShaderStageCount && vertexSgpr < 0x10 && instanceSgpr < 0x10; +} + +bool IndexedIndirectStage(std::uint32_t stage) { + return stage == StageVs || stage == StageEs || stage == StageLs; +} + +constexpr std::uint32_t OpSetUconfigReg = 0x79; +constexpr std::uint32_t IaMultiVgtParam = 0x40000258u; + +std::uint32_t* UpdatePsRegisters(std::uint32_t* cmdbuf, const std::uint32_t* regs) { + auto* at = EmitPacket(cmdbuf, OpSetShReg, {8, regs[0], 0}); + at = EmitPacket(at, OpSetShReg, {10, regs[2], regs[3]}); + at = EmitPacket(at, OpNop, {0xc01e01c4u, regs[4], regs[5]}); + at = EmitPacket(at, OpNop, {0xc01e01b3u, regs[6], regs[7]}); + at = EmitPacket(at, OpNop, {0xc01e01b6u, regs[8]}); + at = EmitPacket(at, OpNop, {0xc01e01b8u, regs[9]}); + at = EmitPacket(at, OpNop, {0xc01e0203u, regs[10]}); + return EmitPacket(at, OpNop, {0xc01e008fu, regs[11]}); +} + +} // namespace extern "C" { -int APS5_VABI sceGnmBeginWorkload(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmBeginWorkload(std::uint32_t stream, std::uint64_t* workload) { + if (!workload) return 3; + *workload = stream < 0x10 ? 1 : 0; + return stream < 0x10 ? 0 : 1; +} + +int APS5_VABI sceGnmComputeWaitOnAddress(std::uint32_t* cmdbuf, std::uint32_t size, std::uint64_t address, std::uint32_t mask, std::uint32_t compare, std::uint32_t reference) { + if (!cmdbuf || size != 14) return CommandFailed; + auto* at = Nop(cmdbuf, 3); + const bool memory = address > 0xffff; + at[0] = Header(0x3c, 6); + at[1] = (memory ? 1u << 4 : 0u) | (compare & 7); + at[2] = static_cast(address & (memory ? 0xfffffffcu : 0xffffu)); + at[3] = static_cast(address >> 32); + at[4] = reference; + at[5] = mask; + at[6] = 10; + Nop(at + 7, 2); return 0; } -int APS5_VABI sceGnmComputeWaitOnAddress(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmComputeWaitSemaphore(std::uint32_t* cmdbuf, std::uint32_t size, std::uint64_t semaphoreAddress, std::uint32_t flags) { + if (!cmdbuf || size != 10) return CommandFailed; + auto* at = Nop(cmdbuf, 3); + at[0] = Header(0x39, 2); + at[1] = static_cast(semaphoreAddress); + at[2] = (flags == 1 ? 0x100000u : 0u) - 0x20000000u + static_cast(semaphoreAddress >> 32); + Nop(at + 3, 2); return 0; } -int APS5_VABI sceGnmComputeWaitSemaphore(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceGnmCreateWorkloadStream(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmCreateWorkloadStream(std::uint64_t name, std::uint32_t* stream) { + if (name == 0 || !stream) return 3; + *stream = 1; return 0; } int APS5_VABI sceGnmDebugModuleReset(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmDebugReset(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmDestroyWorkloadStream(void) { @@ -45,43 +159,96 @@ int APS5_VABI sceGnmDingDongForWorkload(void) { return 0; } -int APS5_VABI sceGnmDispatchDirect(void) { - NotImplemented_nid_no_patch(__func__); +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) { + if (!cmdbuf || size != 9 || static_cast(threadsX | threadsY | threadsZ) < 0) return CommandFailed; + cmdbuf[0] = Header(0x15, 4, true, flags & 1); + cmdbuf[1] = threadsX; + cmdbuf[2] = threadsY; + cmdbuf[3] = threadsZ; + cmdbuf[4] = (flags & 0x18) + 1; + Nop(cmdbuf + 5, 3); return 0; } -int APS5_VABI sceGnmDispatchIndirect(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDispatchIndirect(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t flags) { + if (!cmdbuf || size != 7) return CommandFailed; + cmdbuf[0] = Header(0x16, 2, true, flags & 1); + cmdbuf[1] = dataOffset; + cmdbuf[2] = (flags & 0x18) + 1; + Nop(cmdbuf + 3, 3); return 0; } -int APS5_VABI sceGnmDispatchIndirectOnMec(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDispatchIndirectOnMec(std::uint32_t* cmdbuf, std::uint32_t size, std::uint64_t args, std::uint32_t modifier) { + if (!cmdbuf || size != 8 || args == 0 || (args & 3) != 0) return CommandFailed; + cmdbuf[0] = Header(0x16, 3, true, modifier & 1); + cmdbuf[1] = static_cast(args); + cmdbuf[2] = static_cast(args >> 32); + cmdbuf[3] = (modifier & 0x18) | 1; + cmdbuf[4] = Header(OpNop, 3); + cmdbuf[5] = 0; return 0; } -int APS5_VABI sceGnmDrawIndex(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDrawIndex(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t indexCount, std::uint64_t indexAddress, std::uint32_t flags, std::uint32_t) { + if (!cmdbuf || size != 10 || indexAddress == 0 || (indexAddress & 1) != 0 || (flags & 0x1ffffffeu) != 0) return CommandFailed; + cmdbuf[0] = Header(0x27, 5); + cmdbuf[1] = indexCount; + cmdbuf[2] = static_cast(indexAddress); + cmdbuf[3] = static_cast(indexAddress >> 32); + cmdbuf[4] = indexCount; + cmdbuf[5] = flags & NeoDrawFlags; + Nop(cmdbuf + 6, 3); return 0; } -int APS5_VABI sceGnmDrawIndexAuto(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDrawIndexAuto(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t indexCount, std::uint32_t flags) { + if (!cmdbuf || size != 7 || (flags & 0x1ffffffeu) != 0) return CommandFailed; + cmdbuf[0] = Header(0x2d, 2); + cmdbuf[1] = indexCount; + cmdbuf[2] = (flags & NeoDrawFlags) | AutoIndex; + Nop(cmdbuf + 3, 3); return 0; } -int APS5_VABI sceGnmDrawIndexIndirect(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDrawIndexIndirect(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags) { + if (!cmdbuf || size != 9 || !ValidIndirect(stage, vertexSgpr, instanceSgpr)) return CommandFailed; + cmdbuf[0] = Header(0x25, 4, false, flags & 1); + cmdbuf[1] = dataOffset; + cmdbuf[2] = IndirectSgpr(stage, vertexSgpr); + cmdbuf[3] = IndirectSgpr(stage, instanceSgpr); + cmdbuf[4] = flags & NeoDrawFlags; + Nop(cmdbuf + 5, 3); return 0; } -int APS5_VABI sceGnmDrawIndexIndirectCountMulti(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDrawIndexIndirectCountMulti(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t maxCount, std::uint64_t countAddress, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags) { + if (!cmdbuf || size != 0x10 || vertexSgpr >= 0x10 || instanceSgpr >= 0x10 || !IndexedIndirectStage(stage)) return CommandFailed; + auto* at = Nop(cmdbuf, 2); + at[0] = Header(0x9d, 9, false, flags & 1); + at[1] = dataOffset; + at[2] = IndirectSgpr(stage, vertexSgpr); + at[3] = IndirectSgpr(stage, instanceSgpr); + at[4] = countAddress != 0 ? 1u << 30 : 0; + at[5] = maxCount; + at[6] = static_cast(countAddress); + at[7] = static_cast(countAddress >> 32); + at[8] = DrawIndexedIndirectArgsBytes; + at[9] = flags & NeoDrawFlags; + Nop(at + 10, 2); return 0; } -int APS5_VABI sceGnmDrawIndexIndirectMulti(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDrawIndexIndirectMulti(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t maxCount, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags) { + if (!cmdbuf || size != 11 || vertexSgpr >= 0x10 || instanceSgpr >= 0x10 || !IndexedIndirectStage(stage)) return CommandFailed; + cmdbuf[0] = Header(0x38, 6, false, flags & 1); + cmdbuf[1] = dataOffset; + cmdbuf[2] = IndirectSgpr(stage, vertexSgpr); + cmdbuf[3] = IndirectSgpr(stage, instanceSgpr); + cmdbuf[4] = maxCount; + cmdbuf[5] = DrawIndexedIndirectArgsBytes; + cmdbuf[6] = flags & NeoDrawFlags; + Nop(cmdbuf + 7, 3); return 0; } @@ -90,49 +257,76 @@ int APS5_VABI sceGnmDrawIndexMultiInstanced(void) { return 0; } -int APS5_VABI sceGnmDrawIndexOffset(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDrawIndexOffset(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t indexOffset, std::uint32_t indexCount, std::uint32_t flags) { + if (!cmdbuf || size != 9) return CommandFailed; + cmdbuf[0] = Header(0x35, 4, false, flags & 1); + cmdbuf[1] = indexCount; + cmdbuf[2] = indexOffset; + cmdbuf[3] = indexCount; + cmdbuf[4] = flags & NeoDrawFlags; + Nop(cmdbuf + 5, 3); return 0; } -int APS5_VABI sceGnmDrawIndirect(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDrawIndirect(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags) { + if (!cmdbuf || size != 9 || !ValidIndirect(stage, vertexSgpr, instanceSgpr)) return CommandFailed; + cmdbuf[0] = Header(0x24, 4, false, flags & 1); + cmdbuf[1] = dataOffset; + cmdbuf[2] = IndirectSgpr(stage, vertexSgpr); + cmdbuf[3] = IndirectSgpr(stage, instanceSgpr); + cmdbuf[4] = (flags & NeoDrawFlags) | AutoIndex; + Nop(cmdbuf + 5, 3); return 0; } -int APS5_VABI sceGnmDrawIndirectCountMulti(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDrawIndirectCountMulti(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t maxCount, std::uint64_t countAddress, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags) { + if (!cmdbuf || size != 0x10 || !ValidIndirect(stage, vertexSgpr, instanceSgpr)) return CommandFailed; + auto* at = Nop(cmdbuf, 2); + at[0] = Header(0x9c, 9, false, flags & 1); + at[1] = dataOffset; + at[2] = IndirectSgpr(stage, vertexSgpr); + at[3] = IndirectSgpr(stage, instanceSgpr); + at[4] = countAddress != 0 ? 1u << 30 : 0; + at[5] = maxCount; + at[6] = static_cast(countAddress); + at[7] = static_cast(countAddress >> 32); + at[8] = DrawIndirectArgsBytes; + at[9] = (flags & NeoDrawFlags) | AutoIndex; + Nop(at + 10, 2); return 0; } -int APS5_VABI sceGnmDrawIndirectMulti(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmDrawIndirectMulti(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t maxCount, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags) { + if (!cmdbuf || size != 11 || !ValidIndirect(stage, vertexSgpr, instanceSgpr)) return CommandFailed; + cmdbuf[0] = Header(0x2c, 6, false, flags & 1); + cmdbuf[1] = dataOffset; + cmdbuf[2] = IndirectSgpr(stage, vertexSgpr); + cmdbuf[3] = IndirectSgpr(stage, instanceSgpr); + cmdbuf[4] = maxCount; + cmdbuf[5] = DrawIndirectArgsBytes; + cmdbuf[6] = (flags & NeoDrawFlags) | AutoIndex; + Nop(cmdbuf + 7, 3); return 0; } -int APS5_VABI sceGnmDrawInitDefaultHardwareState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceGnmDrawInitDefaultHardwareState(std::uint32_t* cmdbuf, std::uint32_t size) { + return WriteHardwareState(cmdbuf, size, GnmNeo::HardwareState); } -int APS5_VABI sceGnmDrawInitDefaultHardwareState200(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceGnmDrawInitDefaultHardwareState200(std::uint32_t* cmdbuf, std::uint32_t size) { + return WriteHardwareState(cmdbuf, size, GnmNeo::HardwareState200); } -int APS5_VABI sceGnmDrawInitDefaultHardwareState350(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceGnmDrawInitDefaultHardwareState350(std::uint32_t* cmdbuf, std::uint32_t size) { + return WriteHardwareState(cmdbuf, size, GnmNeo::HardwareState350); } -int APS5_VABI sceGnmDrawInitToDefaultContextState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceGnmDrawInitToDefaultContextState(std::uint32_t* cmdbuf, std::uint32_t size) { + return WriteContextState(cmdbuf, size, 0x20, GnmNeo::ContextState); } -int APS5_VABI sceGnmDrawInitToDefaultContextState400(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceGnmDrawInitToDefaultContextState400(std::uint32_t* cmdbuf, std::uint32_t size) { + return WriteContextState(cmdbuf, size, 0x100, GnmNeo::ContextState400); } int APS5_VABI sceGnmDrawInitToDefaultContextStateInternalCommand(void) { @@ -151,28 +345,23 @@ int APS5_VABI sceGnmDrawOpaqueAuto(void) { } int APS5_VABI sceGnmDriverInternalRetrieveGnmInterface(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return NoInterface; } int APS5_VABI sceGnmDriverInternalRetrieveGnmInterfaceForGpuDebugger(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return NoInterface; } int APS5_VABI sceGnmDriverInternalRetrieveGnmInterfaceForReplay(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return NoInterface; } int APS5_VABI sceGnmDriverInternalRetrieveGnmInterfaceForResourceRegistration(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return NoInterface; } int APS5_VABI sceGnmDriverInternalRetrieveGnmInterfaceForValidation(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return NoInterface; } int APS5_VABI sceGnmDriverInternalVirtualQuery(void) { @@ -181,23 +370,20 @@ int APS5_VABI sceGnmDriverInternalVirtualQuery(void) { } int APS5_VABI sceGnmDriverTraceInProgress(void) { - NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceGnmEndWorkload(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceGnmEndWorkload(std::uint64_t workload) { + if (workload == 0) return 2; + return ((workload >> 56) & 0xff) > 0xf ? 2 : 0; } int APS5_VABI sceGnmFindResources(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmFindResourcesPublic(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmGetCoredumpAddress(void) { @@ -216,8 +402,7 @@ int APS5_VABI sceGnmGetCoredumpProtectionFaultTimestamp(void) { } int APS5_VABI sceGnmGetDbgGcHandle(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return -1; } int APS5_VABI sceGnmGetGpuBlockStatus(void) { @@ -226,8 +411,7 @@ int APS5_VABI sceGnmGetGpuBlockStatus(void) { } int APS5_VABI sceGnmGetGpuCoreClockFrequency(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return 911000000; } int APS5_VABI sceGnmGetGpuInfoStatus(void) { @@ -246,18 +430,15 @@ int APS5_VABI sceGnmGetNumTcaUnits(void) { } int APS5_VABI sceGnmGetOffChipTessellationBufferSize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return 0x800000; } int APS5_VABI sceGnmGetOwnerName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmGetPhysicalCounterFromVirtualized(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmGetProtectionFaultTimeStamp(void) { @@ -266,33 +447,27 @@ int APS5_VABI sceGnmGetProtectionFaultTimeStamp(void) { } int APS5_VABI sceGnmGetResourceBaseAddressAndSizeInBytes(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmGetResourceName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmGetResourceRegistrationBuffers(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmGetResourceShaderGuid(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmGetResourceType(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmGetResourceUserData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmGetShaderProgramBaseAddress(void) { @@ -315,8 +490,12 @@ int APS5_VABI sceGnmGpuPaDebugLeave(void) { return 0; } -int APS5_VABI sceGnmInsertDingDongMarker(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmInsertDingDongMarker(std::uint32_t* cmdbuf, std::uint32_t size) { + if (!cmdbuf || size != 4) return CommandFailed; + cmdbuf[0] = Header(OpNop, 3); + cmdbuf[1] = 0; + cmdbuf[2] = 0; + cmdbuf[3] = 0; return 0; } @@ -356,8 +535,7 @@ int APS5_VABI sceGnmPaHeartbeat(void) { } int APS5_VABI sceGnmQueryResourceRegistrationUserMemoryRequirements(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmRaiseUserExceptionEvent(void) { @@ -366,8 +544,7 @@ int APS5_VABI sceGnmRaiseUserExceptionEvent(void) { } int APS5_VABI sceGnmRegisterGdsResource(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmRegisterGnmLiveCallbackConfig(void) { @@ -376,18 +553,15 @@ int APS5_VABI sceGnmRegisterGnmLiveCallbackConfig(void) { } int APS5_VABI sceGnmRegisterOwner(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmRegisterOwnerForSystem(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmRegisterResource(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmRequestFlipAndSubmitDoneForWorkload(void) { @@ -395,93 +569,134 @@ int APS5_VABI sceGnmRequestFlipAndSubmitDoneForWorkload(void) { return 0; } -int APS5_VABI sceGnmResetVgtControl(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmResetVgtControl(std::uint32_t* cmdbuf, std::uint32_t size) { + if (!cmdbuf || size != 3) return CommandFailed; + cmdbuf[0] = Header(OpSetUconfigReg, 2); + cmdbuf[1] = IaMultiVgtParam; + cmdbuf[2] = 0x6d007fu; return 0; } int APS5_VABI sceGnmSdmaClose(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSdmaConstFill(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSdmaCopyLinear(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSdmaCopyTiled(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSdmaCopyWindow(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSdmaFlush(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSdmaGetMinCmdSize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSdmaOpen(void) { - NotImplemented_nid_no_patch(__func__); + return Failure; +} + +int APS5_VABI sceGnmSetCsShaderWithModifier(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs, std::uint32_t modifier) { + if (!cmdbuf || size <= 0x18 || !regs || (modifier & 0xfffffc3fu) != 0 || regs[1] != 0) return CommandFailed; + const std::uint32_t rsrc1 = modifier == 0 ? regs[2] : (regs[2] & 0xfffffc3fu) | modifier; + auto* at = EmitPacket(cmdbuf, OpSetShReg, {0x20c, regs[0], 0}); + at = EmitPacket(at, OpSetShReg, {0x212, rsrc1, regs[3]}); + at = EmitPacket(at, OpSetShReg, {0x207, regs[4], regs[5], regs[6]}); + Nop(at, 11); return 0; } -int APS5_VABI sceGnmSetCsShaderWithModifier(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmSetEmbeddedPsShader(std::uint32_t* cmdbuf, std::uint32_t, std::uint32_t shaderId, std::uint32_t) { + if (shaderId > 1) return Failure; + const bool black = shaderId == 0; + const auto* code = black ? GnmNeo::BlackPs.data() : GnmNeo::DepthOnlyPs.data(); + auto* at = EmitPacket(cmdbuf, OpSetShReg, {8, AddressLo(code), AddressHi(code)}); + at = EmitPacket(at, OpSetShReg, {10, black ? 0xc0000u : 0x2000u, black ? 4u : 0u}); + at = EmitPacket(at, OpSetContextReg, {0x1c4, 0, black ? 4u : 2u}); + at = EmitPacket(at, OpSetContextReg, {0x1b3, 2, 2}); + at = EmitPacket(at, OpSetContextReg, {0x1b6, 0}); + at = EmitPacket(at, OpSetContextReg, {0x1b8, 0}); + at = EmitPacket(at, OpSetContextReg, {0x203, 0x10}); + at = EmitPacket(at, OpSetContextReg, {0x8f, black ? 0xfu : 3u}); + at[0] = 0xc00a1000u; + at[1] = 0; return 0; } -int APS5_VABI sceGnmSetEmbeddedPsShader(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmSetEmbeddedVsShader(std::uint32_t* cmdbuf, std::uint32_t, std::uint32_t shaderId, std::uint32_t) { + if (shaderId != 0) return Failure; + const auto* code = GnmNeo::FullscreenVs.data(); + auto* at = EmitPacket(cmdbuf, OpSetShReg, {0x48, AddressLo(code), AddressHi(code)}); + at = EmitPacket(at, OpSetShReg, {0x4a, 0xc0000, 4}); + at = EmitPacket(at, OpSetContextReg, {0x207, 0}); + at = EmitPacket(at, OpSetContextReg, {0x1b1, 0}); + at = EmitPacket(at, OpSetContextReg, {0x1c3, 4}); + at[0] = 0xc00a1000u; + at[1] = 0; return 0; } -int APS5_VABI sceGnmSetEmbeddedVsShader(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceGnmSetPsShader350(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmSetPsShader350(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs) { + if (!cmdbuf || size <= 0x27) return CommandFailed; + if (!regs) { + auto* at = EmitPacket(cmdbuf, OpSetShReg, {8, 0, 0}); + at = EmitPacket(at, OpSetContextReg, {0x203, 0}); + at = EmitPacket(at, OpSetContextReg, {0x8f, 0xf}); + Nop(at, 0x1d); + return 0; + } + if (regs[1] != 0) return CommandFailed; + auto* at = EmitPacket(cmdbuf, OpSetShReg, {8, regs[0], 0}); + at = EmitPacket(at, OpSetShReg, {10, regs[2], regs[3]}); + at = EmitPacket(at, OpSetContextReg, {0x1c4, regs[4], regs[5]}); + at = EmitPacket(at, OpSetContextReg, {0x1b3, regs[6], regs[7]}); + at = EmitPacket(at, OpSetContextReg, {0x1b6, regs[8]}); + at = EmitPacket(at, OpSetContextReg, {0x1b8, regs[9]}); + at = EmitPacket(at, OpSetContextReg, {0x203, regs[10]}); + at = EmitPacket(at, OpSetContextReg, {0x8f, regs[11]}); + Nop(at, 11); return 0; } int APS5_VABI sceGnmSetResourceRegistrationUserMemory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSetResourceUserData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSetSpiEnableSqCounters(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmSetSpiEnableSqCountersForUnitInstance(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } -int APS5_VABI sceGnmSetVgtControl(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmSetVgtControl(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t primGroupSizeMinusOne, std::uint32_t partialVsWaveMode, std::uint32_t switchOnlyOnEop) { + if (!cmdbuf || size != 3 || primGroupSizeMinusOne >= 0x100 || (switchOnlyOnEop | partialVsWaveMode) >= 2) return CommandFailed; + const std::uint32_t switchOnEop = static_cast(switchOnlyOnEop != 0) << 20; + const std::uint32_t switchOnEoi = static_cast(switchOnlyOnEop == 0) << 19; + const std::uint32_t value = switchOnlyOnEop != 0 + ? ((partialVsWaveMode & 1) << 16) | primGroupSizeMinusOne | switchOnEop | switchOnEoi | 0x40000u + : (primGroupSizeMinusOne & 0x1cffffu) | switchOnEop | switchOnEoi | 0x50000u; + cmdbuf[0] = Header(OpSetUconfigReg, 2); + cmdbuf[1] = IaMultiVgtParam; + cmdbuf[2] = value | 0x600000u; return 0; } @@ -506,73 +721,100 @@ int APS5_VABI sceGnmSubmitCommandBuffersForWorkload(void) { } int APS5_VABI sceGnmUnregisterAllResourcesForOwner(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmUnregisterOwnerAndResources(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Failure; } int APS5_VABI sceGnmUnregisterResource(void) { - NotImplemented_nid_no_patch(__func__); + return Failure; +} + +int APS5_VABI sceGnmUpdateGsShader(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs) { + if (!cmdbuf || size < 0x1d || !regs || regs[1] != 0) return CommandFailed; + auto* at = EmitPacket(cmdbuf, OpSetShReg, {0x88, regs[0], 0}); + at = EmitPacket(at, OpSetShReg, {0x8a, regs[2], regs[3]}); + at = EmitPacket(at, OpNop, {0xc01e02e5u, regs[4]}); + at = EmitPacket(at, OpNop, {0xc01e029bu, regs[5]}); + at = EmitPacket(at, OpNop, {0xc01e02e4u, regs[6]}); + Nop(at, 11); return 0; } -int APS5_VABI sceGnmUpdateGsShader(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmUpdateHsShader(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs, std::uint32_t lsHsConfig) { + if (!cmdbuf || size <= 0x1c || !regs || regs[1] != 0) return CommandFailed; + auto* at = EmitPacket(cmdbuf, OpSetShReg, {0x108, regs[0], 0}); + at = EmitPacket(at, OpSetShReg, {0x10a, regs[2], regs[3]}); + at = EmitPacket(at, OpNop, {0xc01e0286u, regs[5], regs[6]}); + at = EmitPacket(at, OpNop, {0xc01e02dbu, regs[4]}); + at = EmitPacket(at, OpNop, {0xc01e02d6u, lsHsConfig}); + Nop(at, 11); return 0; } -int APS5_VABI sceGnmUpdateHsShader(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmUpdatePsShader(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs) { + if (!cmdbuf || size <= 0x27) return CommandFailed; + if (!regs) { + auto* at = EmitPacket(cmdbuf, OpSetShReg, {8, 0, 0}); + at = EmitPacket(at, OpNop, {0xc01e0203u, 0}); + Nop(at, 0x20); + return 0; + } + if (regs[1] != 0) return CommandFailed; + Nop(UpdatePsRegisters(cmdbuf, regs), 11); return 0; } -int APS5_VABI sceGnmUpdatePsShader(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmUpdatePsShader350(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs) { + if (!cmdbuf || size <= 0x27) return CommandFailed; + if (!regs) { + auto* at = EmitPacket(cmdbuf, OpSetShReg, {8, 0, 0}); + at = EmitPacket(at, OpNop, {0xc01e0203u, 0}); + at = EmitPacket(at, OpSetContextReg, {0x8f, 0xf}); + Nop(at, 0x1d); + return 0; + } + if (regs[1] != 0) return CommandFailed; + Nop(UpdatePsRegisters(cmdbuf, regs), 11); return 0; } -int APS5_VABI sceGnmUpdatePsShader350(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceGnmUpdateVsShader(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGnmUpdateVsShader(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs, std::uint32_t modifier) { + if (!cmdbuf || size <= 0x1c || !regs || (modifier & 0xfcfffc3fu) != 0 || regs[1] != 0) return CommandFailed; + const std::uint32_t rsrc1 = modifier == 0 ? regs[2] : (regs[2] & 0xfcfffc3fu) | modifier; + auto* at = EmitPacket(cmdbuf, OpSetShReg, {0x48, regs[0], 0}); + at = EmitPacket(at, OpSetShReg, {0x4a, rsrc1, regs[3]}); + at = EmitPacket(at, OpNop, {0xc01e0207u, regs[6]}); + at = EmitPacket(at, OpNop, {0xc01e01b1u, regs[4]}); + at = EmitPacket(at, OpNop, {0xc01e01c3u, regs[5]}); + Nop(at, 11); return 0; } int APS5_VABI sceGnmValidateDisableDiagnostics(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ValidationNotEnabled; } int APS5_VABI sceGnmValidateDisableDiagnostics2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ValidationNotEnabled; } int APS5_VABI sceGnmValidateDispatchCommandBuffers(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ValidationNotEnabled; } int APS5_VABI sceGnmValidateDrawCommandBuffers(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ValidationNotEnabled; } int APS5_VABI sceGnmValidateGetDiagnosticInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ValidationNotEnabled; } int APS5_VABI sceGnmValidateGetDiagnostics(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ValidationNotEnabled; } int APS5_VABI sceGnmValidateGetVersion(void) { @@ -581,18 +823,15 @@ int APS5_VABI sceGnmValidateGetVersion(void) { } int APS5_VABI sceGnmValidateOnSubmitEnabled(void) { - NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceGnmValidateResetState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ValidationNotEnabled; } int APS5_VABI sceGnmValidationRegisterMemoryCheckCallback(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ValidationNotEnabled; } } diff --git a/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp b/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp new file mode 100644 index 0000000000..f9ecf8db4c --- /dev/null +++ b/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceGnmBeginWorkload(std::uint32_t stream, std::uint64_t* workload); +int APS5_VABI sceGnmComputeWaitOnAddress(std::uint32_t* cmdbuf, std::uint32_t size, std::uint64_t address, std::uint32_t mask, std::uint32_t compare, std::uint32_t reference); +int APS5_VABI sceGnmComputeWaitSemaphore(std::uint32_t* cmdbuf, std::uint32_t size, std::uint64_t semaphoreAddress, std::uint32_t flags); +int APS5_VABI sceGnmCreateWorkloadStream(std::uint64_t name, std::uint32_t* stream); +int APS5_VABI sceGnmDebugModuleReset(void); +int APS5_VABI sceGnmDebugReset(void); +int APS5_VABI sceGnmDestroyWorkloadStream(void); +int APS5_VABI sceGnmDingDongForWorkload(void); +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); +int APS5_VABI sceGnmDrawIndex(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t indexCount, std::uint64_t indexAddress, std::uint32_t flags, std::uint32_t); +int APS5_VABI sceGnmDrawIndexAuto(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t indexCount, std::uint32_t flags); +int APS5_VABI sceGnmDrawIndexIndirect(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags); +int APS5_VABI sceGnmDrawIndexIndirectCountMulti(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t maxCount, std::uint64_t countAddress, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags); +int APS5_VABI sceGnmDrawIndexIndirectMulti(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t maxCount, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags); +int APS5_VABI sceGnmDrawIndexMultiInstanced(void); +int APS5_VABI sceGnmDrawIndexOffset(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t indexOffset, std::uint32_t indexCount, std::uint32_t flags); +int APS5_VABI sceGnmDrawIndirect(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags); +int APS5_VABI sceGnmDrawIndirectCountMulti(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t maxCount, std::uint64_t countAddress, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags); +int APS5_VABI sceGnmDrawIndirectMulti(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t maxCount, std::uint32_t stage, std::uint32_t vertexSgpr, std::uint32_t instanceSgpr, std::uint32_t flags); +int APS5_VABI sceGnmDrawInitDefaultHardwareState(std::uint32_t* cmdbuf, std::uint32_t size); +int APS5_VABI sceGnmDrawInitDefaultHardwareState200(std::uint32_t* cmdbuf, std::uint32_t size); +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 sceGnmDrawInitToDefaultContextStateInternalSize(void); +int APS5_VABI sceGnmDrawOpaqueAuto(void); +int APS5_VABI sceGnmDriverInternalRetrieveGnmInterface(void); +int APS5_VABI sceGnmDriverInternalRetrieveGnmInterfaceForGpuDebugger(void); +int APS5_VABI sceGnmDriverInternalRetrieveGnmInterfaceForReplay(void); +int APS5_VABI sceGnmDriverInternalRetrieveGnmInterfaceForResourceRegistration(void); +int APS5_VABI sceGnmDriverInternalRetrieveGnmInterfaceForValidation(void); +int APS5_VABI sceGnmDriverInternalVirtualQuery(void); +int APS5_VABI sceGnmDriverTraceInProgress(void); +int APS5_VABI sceGnmEndWorkload(std::uint64_t workload); +int APS5_VABI sceGnmFindResources(void); +int APS5_VABI sceGnmFindResourcesPublic(void); +int APS5_VABI sceGnmGetCoredumpAddress(void); +int APS5_VABI sceGnmGetCoredumpMode(void); +int APS5_VABI sceGnmGetCoredumpProtectionFaultTimestamp(void); +int APS5_VABI sceGnmGetDbgGcHandle(void); +int APS5_VABI sceGnmGetGpuBlockStatus(void); +int APS5_VABI sceGnmGetGpuCoreClockFrequency(void); +int APS5_VABI sceGnmGetGpuInfoStatus(void); +int APS5_VABI sceGnmGetLastWaitedAddress(void); +int APS5_VABI sceGnmGetNumTcaUnits(void); +int APS5_VABI sceGnmGetOffChipTessellationBufferSize(void); +int APS5_VABI sceGnmGetOwnerName(void); +int APS5_VABI sceGnmGetPhysicalCounterFromVirtualized(void); +int APS5_VABI sceGnmGetProtectionFaultTimeStamp(void); +int APS5_VABI sceGnmGetResourceBaseAddressAndSizeInBytes(void); +int APS5_VABI sceGnmGetResourceName(void); +int APS5_VABI sceGnmGetResourceRegistrationBuffers(void); +int APS5_VABI sceGnmGetResourceShaderGuid(void); +int APS5_VABI sceGnmGetResourceType(void); +int APS5_VABI sceGnmGetResourceUserData(void); +int APS5_VABI sceGnmGetShaderProgramBaseAddress(void); +int APS5_VABI sceGnmGetShaderStatus(void); +int APS5_VABI sceGnmGpuPaDebugEnter(void); +int APS5_VABI sceGnmGpuPaDebugLeave(void); +int APS5_VABI sceGnmInsertDingDongMarker(std::uint32_t* cmdbuf, std::uint32_t size); +int APS5_VABI sceGnmInsertThreadTraceMarker(void); +int APS5_VABI sceGnmIsCoredumpValid(void); +int APS5_VABI sceGnmLogicalTcaUnitToPhysical(void); +int APS5_VABI sceGnmMapComputeQueueWithPriority(void); +int APS5_VABI sceGnmPaDisableFlipCallbacks(void); +int APS5_VABI sceGnmPaEnableFlipCallbacks(void); +int APS5_VABI sceGnmPaHeartbeat(void); +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 sceGnmRegisterOwnerForSystem(void); +int APS5_VABI sceGnmRegisterResource(void); +int APS5_VABI sceGnmRequestFlipAndSubmitDoneForWorkload(void); +int APS5_VABI sceGnmResetVgtControl(std::uint32_t* cmdbuf, std::uint32_t size); +int APS5_VABI sceGnmSdmaClose(void); +int APS5_VABI sceGnmSdmaConstFill(void); +int APS5_VABI sceGnmSdmaCopyLinear(void); +int APS5_VABI sceGnmSdmaCopyTiled(void); +int APS5_VABI sceGnmSdmaCopyWindow(void); +int APS5_VABI sceGnmSdmaFlush(void); +int APS5_VABI sceGnmSdmaGetMinCmdSize(void); +int APS5_VABI sceGnmSdmaOpen(void); +int APS5_VABI sceGnmSetCsShaderWithModifier(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs, std::uint32_t modifier); +int APS5_VABI sceGnmSetEmbeddedPsShader(std::uint32_t* cmdbuf, std::uint32_t, std::uint32_t shaderId, std::uint32_t); +int APS5_VABI sceGnmSetEmbeddedVsShader(std::uint32_t* cmdbuf, std::uint32_t, std::uint32_t shaderId, std::uint32_t); +int APS5_VABI sceGnmSetPsShader350(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs); +int APS5_VABI sceGnmSetResourceRegistrationUserMemory(void); +int APS5_VABI sceGnmSetResourceUserData(void); +int APS5_VABI sceGnmSetSpiEnableSqCounters(void); +int APS5_VABI sceGnmSetSpiEnableSqCountersForUnitInstance(void); +int APS5_VABI sceGnmSetVgtControl(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t primGroupSizeMinusOne, std::uint32_t partialVsWaveMode, std::uint32_t switchOnlyOnEop); +int APS5_VABI sceGnmSetWaveLimitMultiplier(void); +int APS5_VABI sceGnmSetWaveLimitMultipliers(void); +int APS5_VABI sceGnmSubmitAndFlipCommandBuffersForWorkload(void); +int APS5_VABI sceGnmSubmitCommandBuffersForWorkload(void); +int APS5_VABI sceGnmUnregisterAllResourcesForOwner(void); +int APS5_VABI sceGnmUnregisterOwnerAndResources(void); +int APS5_VABI sceGnmUnregisterResource(void); +int APS5_VABI sceGnmUpdateGsShader(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs); +int APS5_VABI sceGnmUpdateHsShader(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs, std::uint32_t lsHsConfig); +int APS5_VABI sceGnmUpdatePsShader(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs); +int APS5_VABI sceGnmUpdatePsShader350(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs); +int APS5_VABI sceGnmUpdateVsShader(std::uint32_t* cmdbuf, std::uint32_t size, const std::uint32_t* regs, std::uint32_t modifier); +int APS5_VABI sceGnmValidateDisableDiagnostics(void); +int APS5_VABI sceGnmValidateDisableDiagnostics2(void); +int APS5_VABI sceGnmValidateDispatchCommandBuffers(void); +int APS5_VABI sceGnmValidateDrawCommandBuffers(void); +int APS5_VABI sceGnmValidateGetDiagnosticInfo(void); +int APS5_VABI sceGnmValidateGetDiagnostics(void); +int APS5_VABI sceGnmValidateGetVersion(void); +int APS5_VABI sceGnmValidateOnSubmitEnabled(void); +int APS5_VABI sceGnmValidateResetState(void); +int APS5_VABI sceGnmValidationRegisterMemoryCheckCallback(void); + +} diff --git a/core/libs/prx/libSceGnmDriverForNeoMode/include/EmbeddedShaders.hpp b/core/libs/prx/libSceGnmDriverForNeoMode/include/EmbeddedShaders.hpp new file mode 100644 index 0000000000..621e98ef3d --- /dev/null +++ b/core/libs/prx/libSceGnmDriverForNeoMode/include/EmbeddedShaders.hpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CORE_LIBS_PRX_LIBSCEGNMDRIVERFORNEOMODE_INCLUDE_EMBEDDEDSHADERS_HPP +#define CORE_LIBS_PRX_LIBSCEGNMDRIVERFORNEOMODE_INCLUDE_EMBEDDEDSHADERS_HPP + +#include +#include + +namespace GnmNeo { + +alignas(256) constexpr std::array FullscreenVs{ + 0xbeeb0387u, 0x36020081u, 0x34020281u, 0x360000c2u, 0x4a0202c1u, 0x4a0000c1u, 0x7e020b01u, 0x7e000b00u, + 0x7e040280u, 0x7e0602f2u, 0xf80008cfu, 0x03020001u, 0xf800020fu, 0x03030303u, 0xbf810000u, +}; + +alignas(256) constexpr std::array BlackPs{ + 0xbeeb0383u, 0x7e000280u, 0x5e000100u, 0xbf800000u, 0xf8001c0fu, 0x00000000u, 0xbf810000u, +}; + +alignas(256) constexpr std::array DepthOnlyPs{ + 0xbeeb0383u, 0x7e040280u, 0xf8001803u, 0x00000202u, 0xbf810000u, +}; + +} // namespace GnmNeo + +#endif diff --git a/core/libs/prx/libSceGnmDriverForNeoMode/include/InitSequences.hpp b/core/libs/prx/libSceGnmDriverForNeoMode/include/InitSequences.hpp new file mode 100644 index 0000000000..5768dd1c92 --- /dev/null +++ b/core/libs/prx/libSceGnmDriverForNeoMode/include/InitSequences.hpp @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CORE_LIBS_PRX_LIBSCEGNMDRIVERFORNEOMODE_INCLUDE_INITSEQUENCES_HPP +#define CORE_LIBS_PRX_LIBSCEGNMDRIVERFORNEOMODE_INCLUDE_INITSEQUENCES_HPP + +#include +#include + +namespace GnmNeo { + +constexpr std::array ClearState{ + 0xc0012800u, 0x80000000u, 0x80000000u, 0xc0001200u, 0x0u, 0xc0055800u, 0x2ec47fc0u, 0xffffffffu, + 0x0u, 0x0u, 0x0u, 0xau, +}; + +constexpr std::array HardwareState{ + 0xc0017600u, 0x216u, 0xffffffffu, 0xc0017600u, 0x217u, 0xffffffffu, 0xc0017600u, 0x215u, + 0x0u, 0xc0016900u, 0x2f9u, 0x2du, 0xc0016900u, 0x282u, 0x8u, 0xc0016900u, + 0x280u, 0x80008u, 0xc0016900u, 0x281u, 0xffff0000u, 0xc0016900u, 0x204u, 0x0u, + 0xc0016900u, 0x206u, 0x43fu, 0xc0016900u, 0x83u, 0xffffu, 0xc0016900u, 0x317u, + 0x10u, 0xc0016900u, 0x2fau, 0x3f800000u, 0xc0016900u, 0x2fcu, 0x3f800000u, 0xc0016900u, + 0x2fbu, 0x3f800000u, 0xc0016900u, 0x2fdu, 0x3f800000u, 0xc0016900u, 0x202u, 0xcc0010u, + 0xc0016900u, 0x30eu, 0xffffffffu, 0xc0016900u, 0x30fu, 0xffffffffu, 0xc0002f00u, 0x1u, + 0xc0017600u, 0x7u, 0x1ffu, 0xc0017600u, 0x46u, 0x1ffu, 0xc0017600u, 0x87u, + 0x1ffu, 0xc0017600u, 0xc7u, 0x1ffu, 0xc0017600u, 0x107u, 0x0u, 0xc0017600u, + 0x147u, 0x1ffu, 0xc0016900u, 0x1b1u, 0x2u, 0xc0016900u, 0x101u, 0x0u, + 0xc0016900u, 0x100u, 0xffffffffu, 0xc0016900u, 0x103u, 0x0u, 0xc0016900u, 0x284u, + 0x0u, 0xc0016900u, 0x290u, 0x0u, 0xc0016900u, 0x2aeu, 0x0u, 0xc0016900u, + 0x292u, 0x0u, 0xc0016900u, 0x293u, 0x6000000u, 0xc0016900u, 0x2f8u, 0x0u, + 0xc0016900u, 0x2deu, 0x1e9u, 0xc0036900u, 0x295u, 0x100u, 0x100u, 0x4u, + 0xc0017900u, 0x200u, 0xe0000000u, +}; + +constexpr std::array HardwareState200{ + 0xc0017600u, 0x216u, 0xffffffffu, 0xc0017600u, 0x217u, 0xffffffffu, 0xc0017600u, 0x219u, + 0xffffffffu, 0xc0017600u, 0x21au, 0xffffffffu, 0xc0017600u, 0x215u, 0x0u, 0xc0016900u, + 0x2f9u, 0x2du, 0xc0016900u, 0x282u, 0x8u, 0xc0016900u, 0x280u, 0x80008u, + 0xc0016900u, 0x281u, 0xffff0000u, 0xc0016900u, 0x204u, 0x0u, 0xc0016900u, 0x206u, + 0x43fu, 0xc0016900u, 0x83u, 0xffffu, 0xc0016900u, 0x317u, 0x10u, 0xc0016900u, + 0x2fau, 0x3f800000u, 0xc0016900u, 0x2fcu, 0x3f800000u, 0xc0016900u, 0x2fbu, 0x3f800000u, + 0xc0016900u, 0x2fdu, 0x3f800000u, 0xc0016900u, 0x202u, 0xcc0010u, 0xc0016900u, 0x30eu, + 0xffffffffu, 0xc0016900u, 0x30fu, 0xffffffffu, 0xc0002f00u, 0x1u, 0xc0017600u, 0x7u, + 0x1701ffu, 0xc0017600u, 0x46u, 0x1701fdu, 0xc0017600u, 0x87u, 0x1701ffu, 0xc0017600u, + 0xc7u, 0x1701fdu, 0xc0017600u, 0x107u, 0x17u, 0xc0017600u, 0x147u, 0x1701fdu, + 0xc0017600u, 0x47u, 0x1cu, 0xc0016900u, 0x1b1u, 0x2u, 0xc0016900u, 0x101u, + 0x0u, 0xc0016900u, 0x100u, 0xffffffffu, 0xc0016900u, 0x103u, 0x0u, 0xc0016900u, + 0x284u, 0x0u, 0xc0016900u, 0x290u, 0x0u, 0xc0016900u, 0x2aeu, 0x0u, + 0xc0016900u, 0x292u, 0x0u, 0xc0016900u, 0x293u, 0x6020000u, 0xc0016900u, 0x2f8u, + 0x0u, 0xc0016900u, 0x2deu, 0x1e9u, 0xc0026900u, 0xebu, 0xff00ff00u, 0xff00u, + 0xc0036900u, 0x295u, 0x100u, 0x100u, 0x4u, 0xc0017900u, 0x200u, 0xe0000000u, + 0xc0017900u, 0x40000258u, 0x6d007fu, +}; + +constexpr std::array HardwareState350{ + 0xc0017600u, 0x216u, 0xffffffffu, 0xc0017600u, 0x217u, 0xffffffffu, 0xc0017600u, 0x219u, + 0xffffffffu, 0xc0017600u, 0x21au, 0xffffffffu, 0xc0017600u, 0x215u, 0x0u, 0xc0016900u, + 0x2f9u, 0x2du, 0xc0016900u, 0x282u, 0x8u, 0xc0016900u, 0x280u, 0x80008u, + 0xc0016900u, 0x281u, 0xffff0000u, 0xc0016900u, 0x204u, 0x0u, 0xc0016900u, 0x206u, + 0x43fu, 0xc0016900u, 0x83u, 0xffffu, 0xc0016900u, 0x317u, 0x10u, 0xc0016900u, + 0x2fau, 0x3f800000u, 0xc0016900u, 0x2fcu, 0x3f800000u, 0xc0016900u, 0x2fbu, 0x3f800000u, + 0xc0016900u, 0x2fdu, 0x3f800000u, 0xc0016900u, 0x202u, 0xcc0010u, 0xc0016900u, 0x30eu, + 0xffffffffu, 0xc0016900u, 0x30fu, 0xffffffffu, 0xc0002f00u, 0x1u, 0xc0017600u, 0x7u, + 0x1701ffu, 0xc0017600u, 0x46u, 0x1701fdu, 0xc0017600u, 0x87u, 0x1701ffu, 0xc0017600u, + 0xc7u, 0x1701fdu, 0xc0017600u, 0x107u, 0x17u, 0xc0017600u, 0x147u, 0x1701fdu, + 0xc0017600u, 0x47u, 0x1cu, 0xc0016900u, 0x1b1u, 0x2u, 0xc0016900u, 0x101u, + 0x0u, 0xc0016900u, 0x100u, 0xffffffffu, 0xc0016900u, 0x103u, 0x0u, 0xc0016900u, + 0x284u, 0x0u, 0xc0016900u, 0x290u, 0x0u, 0xc0016900u, 0x2aeu, 0x0u, + 0xc0016900u, 0x102u, 0x0u, 0xc0016900u, 0x292u, 0x0u, 0xc0016900u, 0x293u, + 0x6020000u, 0xc0016900u, 0x2f8u, 0x0u, 0xc0016900u, 0x2deu, 0x1e9u, 0xc0026900u, + 0xebu, 0xff00ff00u, 0xff00u, 0xc0036900u, 0x295u, 0x100u, 0x100u, 0x4u, + 0xc0017900u, 0x200u, 0xe0000000u, 0xc0017900u, 0x40000258u, 0x6d007fu, +}; + +constexpr std::array ContextState{ + 0xc0012800u, 0x80000000u, 0x80000000u, 0xc0001200u, 0x0u, 0xc0002f00u, 0x1u, 0xc0016900u, + 0x102u, 0x0u, 0xc0016900u, 0x202u, 0xcc0010u, 0xc0026900u, 0xebu, 0xff00ff00u, + 0xff00u, 0xc00d1000u, 0x0u, +}; + +constexpr std::array ContextState400{ + 0xc0012800u, 0x80000000u, 0x80000000u, 0xc0001200u, 0x0u, 0xc0016900u, 0x2f9u, 0x2du, + 0xc0016900u, 0x282u, 0x8u, 0xc0016900u, 0x280u, 0x80008u, 0xc0016900u, 0x281u, + 0xffff0000u, 0xc0016900u, 0x204u, 0x0u, 0xc0016900u, 0x206u, 0x43fu, 0xc0016900u, + 0x83u, 0xffffu, 0xc0016900u, 0x317u, 0x10u, 0xc0016900u, 0x2fau, 0x3f800000u, + 0xc0016900u, 0x2fcu, 0x3f800000u, 0xc0016900u, 0x2fbu, 0x3f800000u, 0xc0016900u, 0x2fdu, + 0x3f800000u, 0xc0016900u, 0x202u, 0xcc0010u, 0xc0016900u, 0x30eu, 0xffffffffu, 0xc0016900u, + 0x30fu, 0xffffffffu, 0xc0002f00u, 0x1u, 0xc0016900u, 0x1b1u, 0x2u, 0xc0016900u, + 0x101u, 0x0u, 0xc0016900u, 0x100u, 0xffffffffu, 0xc0016900u, 0x103u, 0x0u, + 0xc0016900u, 0x284u, 0x0u, 0xc0016900u, 0x290u, 0x0u, 0xc0016900u, 0x2aeu, + 0x0u, 0xc0016900u, 0x102u, 0x0u, 0xc0016900u, 0x292u, 0x0u, 0xc0016900u, + 0x293u, 0x6020000u, 0xc0016900u, 0x2f8u, 0x0u, 0xc0016900u, 0x2deu, 0x1e9u, + 0xc0026900u, 0xebu, 0xff00ff00u, 0xff00u, 0xc0036900u, 0x295u, 0x100u, 0x100u, + 0x4u, 0xc0017900u, 0x40000258u, 0x6d007fu, 0xc09a1000u, 0x0u, +}; + +} // namespace GnmNeo + +#endif diff --git a/core/libs/prx/libSceGnmDriverForNeoMode/libSceGnmDriverForNeoMode.hpp b/core/libs/prx/libSceGnmDriverForNeoMode/libSceGnmDriverForNeoMode.hpp new file mode 100644 index 0000000000..ebeb3b8bad --- /dev/null +++ b/core/libs/prx/libSceGnmDriverForNeoMode/libSceGnmDriverForNeoMode.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceGnmDriverForNeoMode/include/Api.hpp" diff --git a/core/libs/prx/libSceGpuCapture/libSceGpuCapture.hpp b/core/libs/prx/libSceGpuCapture/libSceGpuCapture.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceGpuCapture/libSceGpuCapture.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceGpuTrace/libSceGpuTrace.hpp b/core/libs/prx/libSceGpuTrace/libSceGpuTrace.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceGpuTrace/libSceGpuTrace.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceGvMp4Parser/libSceGvMp4Parser.hpp b/core/libs/prx/libSceGvMp4Parser/libSceGvMp4Parser.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceGvMp4Parser/libSceGvMp4Parser.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceHand/libSceHand.hpp b/core/libs/prx/libSceHand/libSceHand.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceHand/libSceHand.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceHandTracker/libSceHandTracker.hpp b/core/libs/prx/libSceHandTracker/libSceHandTracker.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceHandTracker/libSceHandTracker.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceHdrScopes/libSceHdrScopes.hpp b/core/libs/prx/libSceHdrScopes/libSceHdrScopes.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceHdrScopes/libSceHdrScopes.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceHeadTracker/libSceHeadTracker.hpp b/core/libs/prx/libSceHeadTracker/libSceHeadTracker.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceHeadTracker/libSceHeadTracker.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceHidControl/Export.cpp b/core/libs/prx/libSceHidControl/Export.cpp index c3bfd0d3ed..bb7329027a 100644 --- a/core/libs/prx/libSceHidControl/Export.cpp +++ b/core/libs/prx/libSceHidControl/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHidControl/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceHidControl/include/Api.hpp b/core/libs/prx/libSceHidControl/include/Api.hpp new file mode 100644 index 0000000000..ca9e12b806 --- /dev/null +++ b/core/libs/prx/libSceHidControl/include/Api.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceHidControlEnableMicBeamForming(void); +int APS5_VABI sceHidControlSetAllVibrationMute(void); +int APS5_VABI sceHidControlSetVibrationTriggerEffectWeakForce(void); +int APS5_VABI sceHidControlIsForceUpdateNeeded(void); +int APS5_VABI sceHidControlSetRemoteMergeProcess(void); +int APS5_VABI sceHidControlSetCollaboInputSettingOverrideProcess(void); +int APS5_VABI sceHidControlSetForceAdvancedVibrationMode(void); +int APS5_VABI sceHidControlPswUpdateIsUpdateNeeded(void); +int APS5_VABI sceHidControlPswUpdateIsUpdateNeededForMultiDevice(void); +int APS5_VABI sceHidControlPswUpdateIsReady(void); +int APS5_VABI sceHidControlPswUpdateStart(void); +int APS5_VABI sceHidControlPswUpdateGetStatus(void); +int APS5_VABI sceHidControlPswUpdateSendData(void); +int APS5_VABI sceHidControlPswUpdateSendAppendixData(void); +int APS5_VABI sceHidControlPswUpdateGetReconnectStatus(void); +int APS5_VABI sceHidControlPswUpdateGetDeviceFirmwareUpdateTelemetryData(void); +int APS5_VABI sceHidControlGetPswState(void); +int APS5_VABI sceHidControlGetPswBatteryState(void); +int APS5_VABI sceHidControlSetPswDeviceName(void); +int APS5_VABI sceHidControlGetPswDeviceName(void); +int APS5_VABI sceHidControlSetPswConsoleMute(void); +int APS5_VABI sceHidControlSetPswSpeakerVolume(void); +int APS5_VABI sceHidControlSetPswSidetoneLevel(void); +int APS5_VABI sceHidControlGetPswSidetoneLevel(void); +int APS5_VABI sceHidControlSetPswEqPresetSelection(void); +int APS5_VABI sceHidControlGetPswEqPresetSelection(void); +int APS5_VABI sceHidControlSetPswEqPresetName(void); +int APS5_VABI sceHidControlGetPswEqPresetName(void); +int APS5_VABI sceHidControlSetPswEqBandValues(void); +int APS5_VABI sceHidControlGetPswEqBandValues(void); +int APS5_VABI sceHidControlGetPswAllEqBandValues(void); +int APS5_VABI sceHidControlSetPswResetSettings(void); +int APS5_VABI sceHidControlSetPswNetworkLocale(void); +int APS5_VABI sceHidControlGetPswNetworkLocale(void); +int APS5_VABI sceHidControlGetPswDeviceNameForNearby(void); +int APS5_VABI sceHidControlConnectPswDeviceForNearby(void); +int APS5_VABI sceHidControlGetPswDeviceInfo(void); +int APS5_VABI sceHidControlSetPswCommand(void); +int APS5_VABI sceHidControlGetPswCommand(void); +int APS5_VABI sceHidControlGetPswDeviceIdFromIdxAdd(void); + +} diff --git a/core/libs/prx/libSceHidControl/libSceHidControl.hpp b/core/libs/prx/libSceHidControl/libSceHidControl.hpp new file mode 100644 index 0000000000..6a943525cb --- /dev/null +++ b/core/libs/prx/libSceHidControl/libSceHidControl.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceHidControl/include/Api.hpp" diff --git a/core/libs/prx/libSceHmd/CMakeLists.txt b/core/libs/prx/libSceHmd/CMakeLists.txt index d5a15e9ca8..5bf180e478 100644 --- a/core/libs/prx/libSceHmd/CMakeLists.txt +++ b/core/libs/prx/libSceHmd/CMakeLists.txt @@ -1,11 +1,13 @@ add_library(libSceHmd SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceHmd PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceHmd PRIVATE libc) configure_windows_unwind(libSceHmd) if(BUILD_TESTING) add_test_executable(guest_hmd_tests ${LIBS_INCLUDE_DIR}/tests/GuestHmd.cpp) target_include_directories(guest_hmd_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_hmd_tests PRIVATE libSceHmd) + configure_windows_unwind(guest_hmd_tests) add_test(NAME guest_hmd COMMAND guest_hmd_tests) if(WIN32) set_tests_properties(guest_hmd PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceHmd/Export.cpp b/core/libs/prx/libSceHmd/Export.cpp index ba66e13820..c859361ee4 100644 --- a/core/libs/prx/libSceHmd/Export.cpp +++ b/core/libs/prx/libSceHmd/Export.cpp @@ -1,7 +1,19 @@ +#include "prx/libSceHmd/include/Api.hpp" #include "Hmd.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" #include +#include +#include +#include +#include + +static_assert(sizeof(HmdCommandBuffer) == 16); +static_assert(offsetof(HmdCommandBuffer, dwordCount) == 8); + +static_assert(sizeof(HmdFieldOfView) == 16); +static_assert(sizeof(HmdAssemblyError) == 16); namespace { @@ -14,8 +26,54 @@ std::int32_t Initialize(const Hmd::InitializeParam* param) { return initialized.exchange(true) ? Hmd::AlreadyInitialized : 0; } +std::uint32_t CompiledSdkVersion() { + static const auto* processParameters = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t version; + std::memcpy(&version, processParameters + 0x10, sizeof(version)); + return version; } +std::int32_t CommandQueryHandle(std::int32_t handle, const char* query) { + if (handle != 0) return Hmd::InvalidHandle; + if (initialized.load()) NotImplemented_nid_no_patch((std::string(query) + " with handle 0 after sceHmdInitialize").c_str()); + if (CompiledSdkVersion() >= Hmd::StartupStateSdkVersion) NotImplemented_nid_no_patch((std::string(query) + " with handle 0 before sceHmdInitialize from SDK 4.50").c_str()); + return Hmd::NotInitialized; +} + +template +T ParameterWord(const Hmd2dVrCommandParameter* parameter, std::size_t offset) { + T value; + std::memcpy(&value, reinterpret_cast(parameter) + offset, sizeof(value)); + return value; +} + +std::int32_t Vr2dCommandArguments(const Hmd2dVrCommandParameter* parameter, const HmdCommandBuffer* commandBuffer) { + if (!parameter || !commandBuffer) return Hmd::ParameterNull; + for (const std::size_t offset : {0x00u, 0x08u, 0x20u}) { + if (ParameterWord(parameter, offset) == 0) return Hmd::ParameterNull; + } + if (ParameterWord(parameter, 0x34) != 0 || ParameterWord(parameter, 0x38) != 0) return Hmd::ParameterInvalid; + return 0; +} + +} + +static_assert(sizeof(HmdMirroringAspectDebug) == 12); +static_assert(offsetof(HmdMirroringAspectDebug, mode) == 10); +static_assert(sizeof(HmdMirroringAspect) == 8); +static_assert(offsetof(HmdMirroringAspect, aspect) == 2); + +static_assert(sizeof(HmdDfuData) == 16); +static_assert(offsetof(HmdDfuData, size) == 8); +static_assert(offsetof(HmdDfuData, mode) == 12); + +static_assert(sizeof(HmdScreenData) == 16); +static_assert(offsetof(HmdScreenData, size) == 8); +static_assert(sizeof(HmdEyeDistances) == 8); +static_assert(sizeof(HmdEyeOffset) == 12); + +static_assert(sizeof(HmdLedData) == 9); + extern "C" { std::int32_t APS5_VABI sceHmdInitialize(const Hmd::InitializeParam* param) { @@ -69,7 +127,7 @@ std::int32_t APS5_VABI sceHmdInternalMmapIsConnect() { return 0; } -int APS5_VABI sceHmdInternalGetPUVersion(void) { +int APS5_VABI sceHmdInternalGetPUVersion(std::uint32_t* version) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -79,22 +137,22 @@ int APS5_VABI sceHmdInternalEventInitialize(void) { return 0; } -int APS5_VABI sceHmdInternalSetVirtualDisplaySize(void) { +int APS5_VABI sceHmdInternalSetVirtualDisplaySize(std::uint8_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSocialScreenSetOutput(void) { +int APS5_VABI sceHmdInternalSocialScreenSetOutput(std::uint32_t output, const HmdScreenOutputParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetVirtualDisplayDepth(void) { +int APS5_VABI sceHmdInternalGetVirtualDisplayDepth(std::uint8_t* depth) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetEyeStatus(void) { +int APS5_VABI sceHmdInternalGetEyeStatus(std::int32_t handle, HmdEyeOffset* left, HmdEyeOffset* right) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -109,12 +167,12 @@ int APS5_VABI sceHmdReprojectionUnsetCallback(void) { return 0; } -int APS5_VABI sceHmdInternalSetCrashReportCommand(void) { +int APS5_VABI sceHmdInternalSetCrashReportCommand(std::uint8_t command) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetDebugTextMode(void) { +int APS5_VABI sceHmdInternalGetDebugTextMode(std::uint8_t* mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -123,7 +181,7 @@ int APS5_VABI sceHmdDistortionGetWorkMemoryAlign(void) { return 0x400; } -int APS5_VABI sceHmdInternalRegisterEventCallback(void) { +int APS5_VABI sceHmdInternalRegisterEventCallback(HmdEventCallback callback) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -132,57 +190,57 @@ int APS5_VABI sceHmdDistortionGetWorkMemorySize(void) { return 0x20000; } -int APS5_VABI sceHmdReprojectionStartWideNear(void) { +int APS5_VABI sceHmdReprojectionStartWideNear(const HmdReprojectionWideNearParameter* parameter, const HmdReprojectionPose* pose, std::uint64_t flipArg, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetPUSerialNumber(void) { +int APS5_VABI sceHmdInternalGetPUSerialNumber(char* serialNumber) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetDebugSocialScreenMode(void) { +int APS5_VABI sceHmdInternalGetDebugSocialScreenMode(std::uint8_t* mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetM2LedBrightness(void) { +int APS5_VABI sceHmdInternalSetM2LedBrightness(std::int32_t handle, std::uint32_t ledMask, const std::uint8_t* brightness) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetIPD(void) { +int APS5_VABI sceHmdInternalSetIPD(std::int32_t mode, HmdEyeDistances distances) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalMirroringModeSetAspect(void) { +int APS5_VABI sceHmdInternalMirroringModeSetAspect(const HmdMirroringAspect* aspect) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalDfuGetStatus(void) { +int APS5_VABI sceHmdInternalDfuGetStatus(std::uint64_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalAnotherScreenSendVideo(void) { +int APS5_VABI sceHmdInternalAnotherScreenSendVideo(const HmdScreenData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetRequiredPUPVersion(void) { +int APS5_VABI sceHmdInternalGetRequiredPUPVersion(std::uint32_t* firstVersion, std::uint32_t* secondVersion) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionSetCallback(void) { +int APS5_VABI sceHmdReprojectionSetCallback(const HmdReprojectionCallbackParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalDfuCheckAfterPvt(void) { +int APS5_VABI sceHmdInternalDfuCheckAfterPvt(std::uint8_t* afterPvt) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -192,17 +250,17 @@ int APS5_VABI sceHmdInternalResetInertialSensor(void) { return 0; } -int APS5_VABI sceHmdReprojectionSetUserEventStart(void) { +int APS5_VABI sceHmdReprojectionSetUserEventStart(std::int64_t eventQueue, std::int32_t eventId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdDistortionSetOutputMinColor(void) { +int APS5_VABI sceHmdDistortionSetOutputMinColor(float red, float green, float blue) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetVirtualDisplayDepth(void) { +int APS5_VABI sceHmdInternalSetVirtualDisplayDepth(std::uint8_t depth) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -211,12 +269,12 @@ int APS5_VABI sceHmdGetDistortionWorkMemoryAlign(void) { return 0x400; } -int APS5_VABI sceHmdReprojectionStartMultilayer(void) { +int APS5_VABI sceHmdReprojectionStartMultilayer(const HmdReprojectionMultilayerParameter* layers, std::uint32_t layerCount, const HmdReprojectionSubmissionParameter* submission, const HmdReprojectionPose* pose, std::uint64_t flipArg, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalPushVr2dData(void) { +int APS5_VABI sceHmdInternalPushVr2dData(const HmdVr2dData* data) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -226,54 +284,54 @@ int APS5_VABI sceHmdReprojectionClearUserEventEnd(void) { return 0; } -int APS5_VABI sceHmdInternalGetIPD(void) { +int APS5_VABI sceHmdInternalGetIPD(HmdEyeDistances* distances) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetVRMode(void) { +int APS5_VABI sceHmdInternalSetVRMode(std::int32_t enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalCrashReportReadDataSize(void) { +int APS5_VABI sceHmdInternalCrashReportReadDataSize(std::uint32_t* dataSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetVr2dData(void) { +int APS5_VABI sceHmdInternalGetVr2dData(HmdVr2dData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalAnotherScreenSendAudio(void) { +int APS5_VABI sceHmdInternalAnotherScreenSendAudio(const HmdScreenData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetPuPositionParam(void) { +int APS5_VABI sceHmdInternalGetPuPositionParam(HmdPuPositionParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionInitializeCapture(void) { +int APS5_VABI sceHmdReprojectionInitializeCapture(const HmdCaptureInitializeParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdGet2DEyeOffset(std::int32_t handle, void* leftOffset, void* rightOffset) { +int APS5_VABI sceHmdGet2DEyeOffset(std::int32_t handle, HmdEyeOffset* leftOffset, HmdEyeOffset* rightOffset) { (void)handle; (void)leftOffset; (void)rightOffset; return initialized.load() ? Hmd::InvalidHandle : Hmd::NotInitialized; } -int APS5_VABI sceHmdInternalSetDebugSocialScreenMode(void) { +int APS5_VABI sceHmdInternalSetDebugSocialScreenMode(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetHmuPowerStatusForDebug(void) { +int APS5_VABI sceHmdInternalGetHmuPowerStatusForDebug(std::uint8_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -282,12 +340,12 @@ int APS5_VABI sceHmdGetDistortionWorkMemorySize(void) { return 0x20000; } -int APS5_VABI sceHmdReprojectionSetDisplayBuffers(void) { +int APS5_VABI sceHmdReprojectionSetDisplayBuffers(std::int32_t videoOutHandle, std::int32_t firstBufferIndex, std::int32_t secondBufferIndex, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalAnotherScreenSetFadeAndSwitch(void) { +int APS5_VABI sceHmdInternalAnotherScreenSetFadeAndSwitch(const HmdScreenFadeParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -297,12 +355,12 @@ int APS5_VABI sceHmdInternalCreateSharedMemory(void) { return 0; } -int APS5_VABI sceHmdInternalMirroringModeSetAspectDebug(void) { +int APS5_VABI sceHmdInternalMirroringModeSetAspectDebug(const HmdMirroringAspectDebug* aspect) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetPuBuildNumber(void) { +int APS5_VABI sceHmdInternalGetPuBuildNumber(std::uint32_t* buildNumber) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -311,102 +369,100 @@ int APS5_VABI sceHmdDistortionGetCompoundEyeCorrectionCommand(void) { return static_cast(0x81110009); } -int APS5_VABI sceHmdInternalSetS3dPassMode(void) { +int APS5_VABI sceHmdInternalSetS3dPassMode(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionStopCapture(void) { +int APS5_VABI sceHmdReprojectionStopCapture(HmdCaptureInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalIsCommonDlgMiniAppVr2d(void) { +bool APS5_VABI sceHmdInternalIsCommonDlgMiniAppVr2d(void) { + return false; +} + +int APS5_VABI sceHmdInternalMmapGetSensorCalibrationData(void* buffer, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalMmapGetSensorCalibrationData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHmdDistortionGetCorrectionCommand(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceHmdDistortionGetCorrectionCommand(std::int32_t handle, const HmdDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { + (void)parameter; + (void)commandBuffer; + return CommandQueryHandle(handle, __func__); } int APS5_VABI sceHmdReprojectionQueryOnionBuffAlign(void) { return 0x100; } -int APS5_VABI sceHmdInternalGetDebugMode(void) { +int APS5_VABI sceHmdInternalGetDebugMode(std::uint8_t* mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalIsMiniAppVr2d(void) { +bool APS5_VABI sceHmdInternalIsMiniAppVr2d(void) { + return false; +} + +bool APS5_VABI sceHmdInternalIsCommonDlgVr2d(void) { + return false; +} + +int APS5_VABI sceHmdReprojectionSetOutputMinColor(float red, float green, float blue) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalIsCommonDlgVr2d(void) { +int APS5_VABI sceHmdInternalSetPortConnection(std::uint64_t connection) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionSetOutputMinColor(void) { +int APS5_VABI sceHmdReprojectionDebugGetLastInfo(HmdReprojectionDebugInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetPortConnection(void) { +int APS5_VABI sceHmdInternalSetM2LedOn(std::int32_t handle, std::uint32_t ledMask, std::uint32_t brightness) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionDebugGetLastInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHmdInternalSetM2LedOn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHmdGetFieldOfView(std::int32_t handle, void* fieldOfView) { +int APS5_VABI sceHmdGetFieldOfView(std::int32_t handle, HmdFieldOfView* fieldOfView) { (void)handle; if (!fieldOfView) return Hmd::ParameterNull; return initialized.load() ? Hmd::InvalidHandle : Hmd::NotInitialized; } -int APS5_VABI sceHmdReprojectionAddDisplayBuffer(void) { +int APS5_VABI sceHmdReprojectionAddDisplayBuffer(std::int32_t bufferIndex, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetIpdSettingEnableForSystemService(void) { +int APS5_VABI sceHmdInternalSetIpdSettingEnableForSystemService(std::uint8_t enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetLedOn(void) { +int APS5_VABI sceHmdInternalSetLedOn(std::int32_t handle, std::uint32_t ledMask) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternal3dAudioSendData(void) { +int APS5_VABI sceHmdInternal3dAudioSendData(std::int32_t port, const void* data, std::int32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionInitialize(void) { +int APS5_VABI sceHmdReprojectionInitialize(const HmdReprojectionInitializeParameter* parameter, std::uint32_t mode, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetDeviceInformationByHandle(void) { +int APS5_VABI sceHmdInternalGetDeviceInformationByHandle(std::int32_t handle, HmdDeviceInformation* information) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -421,12 +477,12 @@ int APS5_VABI sceHmdInternalAnotherScreenClose(void) { return 0; } -int APS5_VABI sceHmdInternalCrashReportReadData(void) { +int APS5_VABI sceHmdInternalCrashReportReadData(void* buffer, std::uint32_t* bytesRead, std::uint32_t bufferSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetDeviceInformation(void) { +int APS5_VABI sceHmdInternalGetDeviceInformation(HmdDeviceInformation* information) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -436,7 +492,7 @@ int APS5_VABI sceHmdReprojectionStartCapture(void) { return 0; } -int APS5_VABI sceHmdInternalGetDefaultLedData(void) { +int APS5_VABI sceHmdInternalGetDefaultLedData(std::int32_t handle, std::uint32_t* mask, HmdLedData* data) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -450,52 +506,52 @@ int APS5_VABI sceHmdReprojectionQueryGarlicBuffAlign(void) { return 0x100; } -int APS5_VABI sceHmdInternalSetHmuPowerControl(void) { +int APS5_VABI sceHmdInternalSetHmuPowerControl(std::uint8_t control) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalBindDeviceWithUserId(void) { +int APS5_VABI sceHmdInternalBindDeviceWithUserId(std::int32_t handle, std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetHmuSerialNumber(void) { +int APS5_VABI sceHmdInternalGetHmuSerialNumber(std::uint8_t* serialNumber) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdDistortionGetWideNearCorrectionCommand(void) { +int APS5_VABI sceHmdDistortionGetWideNearCorrectionCommand(std::int32_t handle, const HmdWideNearDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { + (void)parameter; + (void)commandBuffer; + return CommandQueryHandle(handle, __func__); +} + +int APS5_VABI sceHmdInternalCheckS3dPassModeAvailable(bool* available) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalCheckS3dPassModeAvailable(void) { +int APS5_VABI sceHmdInternalAnotherScreenGetAudioStatus(HmdScreenStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalAnotherScreenGetAudioStatus(void) { +int APS5_VABI sceHmdInternalSeparateModeGetAudioStatus(HmdScreenStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSeparateModeGetAudioStatus(void) { +int APS5_VABI sceHmdReprojectionStartLiveCapture(const HmdLiveCaptureParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionStartLiveCapture(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI sceHmdInternalIsGameVr2d(void) { + return false; } -int APS5_VABI sceHmdInternalIsGameVr2d(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHmdGetAssyError(void* data) { +int APS5_VABI sceHmdGetAssyError(HmdAssemblyError* data) { if (!data) return Hmd::ParameterNull; return initialized.load() ? Hmd::DeviceDisconnected : Hmd::NotInitialized; } @@ -505,7 +561,7 @@ int APS5_VABI sceHmdReprojectionFinalize(void) { return 0; } -int APS5_VABI sceHmdInternalSocialScreenSetFadeAndSwitch(void) { +int APS5_VABI sceHmdInternalSocialScreenSetFadeAndSwitch(const HmdScreenFadeParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -515,27 +571,27 @@ int APS5_VABI sceHmdInternalDfuStart(void) { return 0; } -int APS5_VABI sceHmdInternalSetVirtualDisplayHeight(void) { +int APS5_VABI sceHmdInternalSetVirtualDisplayHeight(std::uint8_t height) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdDistortionInitialize(void) { +int APS5_VABI sceHmdDistortionInitialize(void* workMemory) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalDfuSetMode(void) { +int APS5_VABI sceHmdInternalDfuSetMode(std::uint32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetForcedCrash(void) { +int APS5_VABI sceHmdInternalSetForcedCrash(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalDfuCheckPartialUpdateAvailable(void) { +int APS5_VABI sceHmdInternalDfuCheckPartialUpdateAvailable(bool* available) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -545,7 +601,7 @@ int APS5_VABI sceHmdInternalSeparateModeClose(void) { return 0; } -int APS5_VABI sceHmdInternalSetDebugTextMode(void) { +int APS5_VABI sceHmdInternalSetDebugTextMode(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -555,17 +611,17 @@ int APS5_VABI sceHmdInternalCrashReportClose(void) { return 0; } -int APS5_VABI sceHmdInternalSetHmuPowerControlForDebug(void) { +int APS5_VABI sceHmdInternalSetHmuPowerControlForDebug(std::uint8_t control) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetVirtualDisplayHeight(void) { +int APS5_VABI sceHmdInternalGetVirtualDisplayHeight(std::uint8_t* height) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionStart(void) { +int APS5_VABI sceHmdReprojectionStart(const HmdReprojectionFrameParameter* parameter, const HmdReprojectionPose* pose, std::uint64_t flipArg, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -580,7 +636,7 @@ int APS5_VABI sceHmdInternalDfuOpen(void) { return 0; } -int APS5_VABI sceHmdInternalSetDebugMode(void) { +int APS5_VABI sceHmdInternalSetDebugMode(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -590,22 +646,22 @@ int APS5_VABI sceHmdInternalCrashReportCancel(void) { return 0; } -int APS5_VABI sceHmdInternalSeparateModeGetVideoStatus(void) { +int APS5_VABI sceHmdInternalSeparateModeGetVideoStatus(HmdScreenStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalCheckDeviceModelMk3(void) { +int APS5_VABI sceHmdInternalCheckDeviceModelMk3(bool* isMk3) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdSetVr2dEnable(void) { +int APS5_VABI sceHmdSetVr2dEnable(std::int32_t enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetSidetone(void) { +int APS5_VABI sceHmdInternalSetSidetone(std::uint8_t level) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -615,42 +671,44 @@ int APS5_VABI sceHmdInternalSeparateModeOpen(void) { return 0; } -int APS5_VABI sceHmdInternalMmapGetModeId(void) { +int APS5_VABI sceHmdInternalMmapGetModeId(std::uint32_t* modeId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdDistortionGet2dVrCommand(void) { +int APS5_VABI sceHmdDistortionGet2dVrCommand(std::int32_t handle, const Hmd2dVrCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { + if (const auto result = Vr2dCommandArguments(parameter, commandBuffer); result != 0) return result; + return CommandQueryHandle(handle, __func__); +} + +int APS5_VABI sceHmdInternalDfuSendSize(std::int32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalDfuSendSize(void) { +int APS5_VABI sceHmdGetWideNearDistortionCorrectionCommand(std::int32_t handle, const HmdWideNearDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { + (void)parameter; + (void)commandBuffer; + return CommandQueryHandle(handle, __func__); +} + +int APS5_VABI sceHmdReprojectionStartMultilayer2(const HmdReprojectionMultilayerParameter* layers, std::uint32_t layerCount, const HmdReprojectionSubmissionParameter* submission, std::uint64_t flipArg, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdGetWideNearDistortionCorrectionCommand(void) { +int APS5_VABI sceHmdGetDistortionCorrectionCommand(std::int32_t handle, const HmdDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { + (void)parameter; + (void)commandBuffer; + return CommandQueryHandle(handle, __func__); +} + +int APS5_VABI sceHmdInternalSetDeviceConnection(std::uint64_t connection) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionStartMultilayer2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHmdGetDistortionCorrectionCommand(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHmdInternalSetDeviceConnection(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHmdInternalGetVirtualDisplaySize(void) { +int APS5_VABI sceHmdInternalGetVirtualDisplaySize(std::uint8_t* size) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -660,12 +718,12 @@ int APS5_VABI sceHmdReprojectionUnsetDisplayBuffers(void) { return 0; } -int APS5_VABI sceHmdInternalGetHmuOpticalParam(void) { +int APS5_VABI sceHmdInternalGetHmuOpticalParam(HmdOpticalParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSeparateModeSendVideo(void) { +int APS5_VABI sceHmdInternalSeparateModeSendVideo(const HmdScreenData* data) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -679,12 +737,12 @@ int APS5_VABI sceHmdReprojectionQueryOnionBuffSize(void) { return 0x810; } -int APS5_VABI sceHmdReprojectionStartWithOverlay(void) { +int APS5_VABI sceHmdReprojectionStartWithOverlay(const HmdReprojectionFrameParameter* parameter, const HmdReprojectionPose* pose, std::uint64_t flipArg, const HmdReprojectionOverlayParameter* overlay, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionSetUserEventEnd(void) { +int APS5_VABI sceHmdReprojectionSetUserEventEnd(std::int64_t eventQueue, std::int32_t eventId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -694,22 +752,22 @@ int APS5_VABI sceHmdInternalCrashReportOpen(void) { return 0; } -int APS5_VABI sceHmdInternalSetDemoMode(void) { +int APS5_VABI sceHmdInternalSetDemoMode(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionStartWideNearWithOverlay(void) { +int APS5_VABI sceHmdReprojectionStartWideNearWithOverlay(const HmdReprojectionWideNearParameter* parameter, const HmdReprojectionPose* pose, std::uint64_t flipArg, const HmdReprojectionOverlayParameter* overlay, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdGetDistortionParams(void) { +int APS5_VABI sceHmdGetDistortionParams(HmdDistortionParameters* parameters) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalDfuSend(void) { +int APS5_VABI sceHmdInternalDfuSend(const HmdDfuData* data) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -719,12 +777,12 @@ int APS5_VABI sceHmdReprojectionClearUserEventStart(void) { return 0; } -int APS5_VABI sceHmdInternalSetUserType(void) { +int APS5_VABI sceHmdInternalSetUserType(std::uint8_t userType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetDemoMode(void) { +int APS5_VABI sceHmdInternalGetDemoMode(std::uint8_t* mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -734,12 +792,12 @@ int APS5_VABI sceHmdInternalMapSharedMemory(void) { return 0; } -int APS5_VABI sceHmdInternalGetCrashDumpInfo(void) { +int APS5_VABI sceHmdInternalGetCrashDumpInfo(HmdCrashDumpInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetTv4kCapability(void) { +int APS5_VABI sceHmdInternalGetTv4kCapability(std::uint8_t* capability) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -749,7 +807,7 @@ int APS5_VABI sceHmdInternalDfuReset(void) { return 0; } -int APS5_VABI sceHmdReprojectionStart2dVr(void) { +int APS5_VABI sceHmdReprojectionStart2dVr(const HmdReprojection2dVrParameter* parameter, std::uint64_t flipArg, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -759,44 +817,44 @@ int APS5_VABI sceHmdInternalMmapGetCount(void) { return 0; } -int APS5_VABI sceHmdInternalSetDefaultLedData(void) { +int APS5_VABI sceHmdInternalSetDefaultLedData(std::int32_t handle, std::uint32_t mask, const HmdLedData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdGetInertialSensorData(std::int32_t handle, void* data, std::int32_t count) { +int APS5_VABI sceHmdGetInertialSensorData(std::int32_t handle, HmdInertialSensorData* data, std::uint32_t count) { (void)handle; (void)data; (void)count; return initialized.load() ? Hmd::InvalidHandle : Hmd::NotInitialized; } -int APS5_VABI sceHmdInternal3dAudioClose(void) { +int APS5_VABI sceHmdInternal3dAudioClose(std::int32_t port) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetBrightness(void) { +int APS5_VABI sceHmdInternalSetBrightness(std::uint8_t brightness) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSocialScreenGetFadeState(void) { +int APS5_VABI sceHmdInternalSocialScreenGetFadeState(std::int32_t* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSeparateModeSendAudio(void) { +int APS5_VABI sceHmdInternalSeparateModeSendAudio(const HmdScreenData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetStatusReport(void) { +int APS5_VABI sceHmdInternalGetStatusReport(std::int32_t handle, HmdStatusReport* report) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetBrightness(void) { +int APS5_VABI sceHmdInternalGetBrightness(std::uint8_t* brightness) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -806,7 +864,7 @@ int APS5_VABI sceHmdReprojectionFinalizeCapture(void) { return 0; } -int APS5_VABI sceHmdInternalSetPortStatus(void) { +int APS5_VABI sceHmdInternalSetPortStatus(std::uint64_t status) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -816,37 +874,37 @@ int APS5_VABI sceHmdReprojectionStop(void) { return 0; } -int APS5_VABI sceHmdInternalAnotherScreenGetVideoStatus(void) { +int APS5_VABI sceHmdInternalAnotherScreenGetVideoStatus(HmdScreenStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternal3dAudioOpen(void) { +int APS5_VABI sceHmdInternal3dAudioOpen(std::int32_t port) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalAnotherScreenGetFadeState(void) { +int APS5_VABI sceHmdInternalAnotherScreenGetFadeState(std::int32_t* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalSetDebugGpo(void) { +int APS5_VABI sceHmdInternalSetDebugGpo(std::uint32_t value) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetIpdSettingEnableForSystemService(void) { +int APS5_VABI sceHmdInternalGetIpdSettingEnableForSystemService(std::uint8_t* enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdReprojectionDebugGetLastInfoMultilayer(void) { +int APS5_VABI sceHmdReprojectionDebugGetLastInfoMultilayer(HmdReprojectionMultilayerDebugInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmdInternalGetPuRevision(void) { +int APS5_VABI sceHmdInternalGetPuRevision(std::uint32_t* revision) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -855,9 +913,9 @@ int APS5_VABI sceHmdReprojectionQueryGarlicBuffSize(void) { return 0x100000; } -int APS5_VABI sceHmdGet2dVrCommand(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceHmdGet2dVrCommand(std::int32_t handle, const Hmd2dVrCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { + if (const auto result = Vr2dCommandArguments(parameter, commandBuffer); result != 0) return result; + return CommandQueryHandle(handle, __func__); } } diff --git a/core/libs/prx/libSceHmd/Hmd.hpp b/core/libs/prx/libSceHmd/Hmd.hpp index eb2f77625f..e535975c5e 100644 --- a/core/libs/prx/libSceHmd/Hmd.hpp +++ b/core/libs/prx/libSceHmd/Hmd.hpp @@ -15,6 +15,7 @@ inline constexpr std::int32_t DeviceDisconnected = static_cast(0x8 inline constexpr std::int32_t ParameterNull = static_cast(0x81110008); inline constexpr std::int32_t ParameterInvalid = static_cast(0x81110009); inline constexpr std::int32_t UnsupportedFeature = static_cast(0x81110016); +inline constexpr std::uint32_t StartupStateSdkVersion = 0x04500000; enum class DeviceStatus : std::uint32_t { Ready = 0, @@ -51,6 +52,26 @@ static_assert(offsetof(DeviceInformation, hmuMount) == 24); } +struct HmdFieldOfView { + float angles[4]; +}; +struct HmdEyeOffset { + float x; + float y; + float z; +}; +struct HmdInertialSensorData; +struct HmdAssemblyError { + float offsets[4]; +}; +struct HmdDistortionCommandParameter; +struct HmdWideNearDistortionCommandParameter; +struct Hmd2dVrCommandParameter; +struct HmdCommandBuffer { + const std::uint32_t* commands; + std::uint32_t dwordCount; +}; + extern "C" { std::int32_t APS5_VABI sceHmdInitialize(const Hmd::InitializeParam* param); std::int32_t APS5_VABI sceHmdInitialize315(const Hmd::InitializeParam* param); @@ -69,10 +90,20 @@ int APS5_VABI sceHmdReprojectionQueryGarlicBuffAlign(void); int APS5_VABI sceHmdReprojectionQueryGarlicBuffSize(void); int APS5_VABI sceHmdReprojectionQueryOnionBuffAlign(void); int APS5_VABI sceHmdReprojectionQueryOnionBuffSize(void); -int APS5_VABI sceHmdGetFieldOfView(std::int32_t handle, void* fieldOfView); -int APS5_VABI sceHmdGet2DEyeOffset(std::int32_t handle, void* leftOffset, void* rightOffset); -int APS5_VABI sceHmdGetInertialSensorData(std::int32_t handle, void* data, std::int32_t count); -int APS5_VABI sceHmdGetAssyError(void* data); +int APS5_VABI sceHmdGetFieldOfView(std::int32_t handle, HmdFieldOfView* fieldOfView); +int APS5_VABI sceHmdGet2DEyeOffset(std::int32_t handle, HmdEyeOffset* leftOffset, HmdEyeOffset* rightOffset); +int APS5_VABI sceHmdGetInertialSensorData(std::int32_t handle, HmdInertialSensorData* data, std::uint32_t count); +int APS5_VABI sceHmdGetAssyError(HmdAssemblyError* data); +int APS5_VABI sceHmdDistortionGet2dVrCommand(std::int32_t handle, const Hmd2dVrCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdDistortionGetCorrectionCommand(std::int32_t handle, const HmdDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdDistortionGetWideNearCorrectionCommand(std::int32_t handle, const HmdWideNearDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdGet2dVrCommand(std::int32_t handle, const Hmd2dVrCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdGetDistortionCorrectionCommand(std::int32_t handle, const HmdDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdGetWideNearDistortionCorrectionCommand(std::int32_t handle, const HmdWideNearDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +bool APS5_VABI sceHmdInternalIsGameVr2d(void); +bool APS5_VABI sceHmdInternalIsMiniAppVr2d(void); +bool APS5_VABI sceHmdInternalIsCommonDlgVr2d(void); +bool APS5_VABI sceHmdInternalIsCommonDlgMiniAppVr2d(void); } #endif diff --git a/core/libs/prx/libSceHmd/include/Api.hpp b/core/libs/prx/libSceHmd/include/Api.hpp new file mode 100644 index 0000000000..3feeb6eb93 --- /dev/null +++ b/core/libs/prx/libSceHmd/include/Api.hpp @@ -0,0 +1,232 @@ +#pragma once + +#include "SceTypes.hpp" +#include "prx/libSceHmd/Hmd.hpp" + +struct HmdCaptureInfo; + +struct HmdCaptureInitializeParameter; + +struct HmdCrashDumpInfo; + +struct HmdDeviceInformation; + +struct HmdDfuData { + const void* data; + std::uint32_t size; + std::uint32_t mode; +}; + +struct HmdDistortionParameters; + +using HmdEventCallback = void (APS5_VABI *)(std::uint32_t events); + +struct HmdEyeDistances { + std::uint32_t left; + std::uint32_t right; +}; + + + +struct HmdLedData { + std::uint8_t values[9]; +}; + +struct HmdLiveCaptureParameter; + +struct HmdMirroringAspect { + std::uint16_t reserved0; + std::int16_t aspect; + std::uint16_t reserved1; + std::uint16_t reserved2; +}; + +struct HmdMirroringAspectDebug { + std::uint16_t values[5]; + std::uint8_t mode; +}; + +struct HmdOpticalParameter; + +struct HmdPuPositionParameter; + +struct HmdReprojection2dVrParameter; + +struct HmdReprojectionCallbackParameter; + +struct HmdReprojectionDebugInfo; + +struct HmdReprojectionFrameParameter; + +struct HmdReprojectionInitializeParameter; + +struct HmdReprojectionMultilayerDebugInfo; + +struct HmdReprojectionMultilayerParameter; + +struct HmdReprojectionOverlayParameter; + +struct HmdReprojectionPose; + +struct HmdReprojectionSubmissionParameter; + +struct HmdReprojectionWideNearParameter; + +struct HmdScreenData { + const void* data; + std::int32_t size; +}; + +struct HmdScreenFadeParameter; + +struct HmdScreenOutputParameter; + +struct HmdScreenStatus; + +struct HmdStatusReport; + +struct HmdVr2dData; + +extern "C" { + +int APS5_VABI sceHmdInternalGetPUVersion(std::uint32_t* version); +int APS5_VABI sceHmdInternalEventInitialize(void); +int APS5_VABI sceHmdInternalSetVirtualDisplaySize(std::uint8_t size); +int APS5_VABI sceHmdInternalSocialScreenSetOutput(std::uint32_t output, const HmdScreenOutputParameter* parameter); +int APS5_VABI sceHmdInternalGetVirtualDisplayDepth(std::uint8_t* depth); +int APS5_VABI sceHmdInternalGetEyeStatus(std::int32_t handle, HmdEyeOffset* left, HmdEyeOffset* right); +int APS5_VABI sceHmdInternalAnotherScreenOpen(void); +int APS5_VABI sceHmdReprojectionUnsetCallback(void); +int APS5_VABI sceHmdInternalSetCrashReportCommand(std::uint8_t command); +int APS5_VABI sceHmdInternalGetDebugTextMode(std::uint8_t* mode); +int APS5_VABI sceHmdInternalRegisterEventCallback(HmdEventCallback callback); +int APS5_VABI sceHmdReprojectionStartWideNear(const HmdReprojectionWideNearParameter* parameter, const HmdReprojectionPose* pose, std::uint64_t flipArg, const void* reserved); +int APS5_VABI sceHmdInternalGetPUSerialNumber(char* serialNumber); +int APS5_VABI sceHmdInternalGetDebugSocialScreenMode(std::uint8_t* mode); +int APS5_VABI sceHmdInternalSetM2LedBrightness(std::int32_t handle, std::uint32_t ledMask, const std::uint8_t* brightness); +int APS5_VABI sceHmdInternalSetIPD(std::int32_t mode, HmdEyeDistances distances); +int APS5_VABI sceHmdInternalMirroringModeSetAspect(const HmdMirroringAspect* aspect); +int APS5_VABI sceHmdInternalDfuGetStatus(std::uint64_t* status); +int APS5_VABI sceHmdInternalAnotherScreenSendVideo(const HmdScreenData* data); +int APS5_VABI sceHmdInternalGetRequiredPUPVersion(std::uint32_t* firstVersion, std::uint32_t* secondVersion); +int APS5_VABI sceHmdReprojectionSetCallback(const HmdReprojectionCallbackParameter* parameter); +int APS5_VABI sceHmdInternalDfuCheckAfterPvt(std::uint8_t* afterPvt); +int APS5_VABI sceHmdInternalResetInertialSensor(void); +int APS5_VABI sceHmdReprojectionSetUserEventStart(std::int64_t eventQueue, std::int32_t eventId); +int APS5_VABI sceHmdDistortionSetOutputMinColor(float red, float green, float blue); +int APS5_VABI sceHmdInternalSetVirtualDisplayDepth(std::uint8_t depth); +int APS5_VABI sceHmdGetDistortionWorkMemoryAlign(void); +int APS5_VABI sceHmdReprojectionStartMultilayer(const HmdReprojectionMultilayerParameter* layers, std::uint32_t layerCount, const HmdReprojectionSubmissionParameter* submission, const HmdReprojectionPose* pose, std::uint64_t flipArg, const void* reserved); +int APS5_VABI sceHmdInternalPushVr2dData(const HmdVr2dData* data); +int APS5_VABI sceHmdReprojectionClearUserEventEnd(void); +int APS5_VABI sceHmdInternalGetIPD(HmdEyeDistances* distances); +int APS5_VABI sceHmdInternalSetVRMode(std::int32_t enabled); +int APS5_VABI sceHmdInternalCrashReportReadDataSize(std::uint32_t* dataSize); +int APS5_VABI sceHmdInternalGetVr2dData(HmdVr2dData* data); +int APS5_VABI sceHmdInternalAnotherScreenSendAudio(const HmdScreenData* data); +int APS5_VABI sceHmdInternalGetPuPositionParam(HmdPuPositionParameter* parameter); +int APS5_VABI sceHmdReprojectionInitializeCapture(const HmdCaptureInitializeParameter* parameter); +int APS5_VABI sceHmdInternalSetDebugSocialScreenMode(std::uint8_t mode); +int APS5_VABI sceHmdInternalGetHmuPowerStatusForDebug(std::uint8_t* status); +int APS5_VABI sceHmdGetDistortionWorkMemorySize(void); +int APS5_VABI sceHmdReprojectionSetDisplayBuffers(std::int32_t videoOutHandle, std::int32_t firstBufferIndex, std::int32_t secondBufferIndex, const void* reserved); +int APS5_VABI sceHmdInternalAnotherScreenSetFadeAndSwitch(const HmdScreenFadeParameter* parameter); +int APS5_VABI sceHmdInternalCreateSharedMemory(void); +int APS5_VABI sceHmdInternalMirroringModeSetAspectDebug(const HmdMirroringAspectDebug* aspect); +int APS5_VABI sceHmdInternalGetPuBuildNumber(std::uint32_t* buildNumber); +int APS5_VABI sceHmdDistortionGetCompoundEyeCorrectionCommand(void); +int APS5_VABI sceHmdInternalSetS3dPassMode(std::uint8_t mode); +int APS5_VABI sceHmdReprojectionStopCapture(HmdCaptureInfo* info); +int APS5_VABI sceHmdInternalMmapGetSensorCalibrationData(void* buffer, std::uint32_t size); +int APS5_VABI sceHmdInternalGetDebugMode(std::uint8_t* mode); +int APS5_VABI sceHmdReprojectionSetOutputMinColor(float red, float green, float blue); +int APS5_VABI sceHmdInternalSetPortConnection(std::uint64_t connection); +int APS5_VABI sceHmdReprojectionDebugGetLastInfo(HmdReprojectionDebugInfo* info); +int APS5_VABI sceHmdInternalSetM2LedOn(std::int32_t handle, std::uint32_t ledMask, std::uint32_t brightness); +int APS5_VABI sceHmdReprojectionAddDisplayBuffer(std::int32_t bufferIndex, const void* reserved); +int APS5_VABI sceHmdInternalSetIpdSettingEnableForSystemService(std::uint8_t enabled); +int APS5_VABI sceHmdInternalSetLedOn(std::int32_t handle, std::uint32_t ledMask); +int APS5_VABI sceHmdInternal3dAudioSendData(std::int32_t port, const void* data, std::int32_t size); +int APS5_VABI sceHmdReprojectionInitialize(const HmdReprojectionInitializeParameter* parameter, std::uint32_t mode, const void* reserved); +int APS5_VABI sceHmdInternalGetDeviceInformationByHandle(std::int32_t handle, HmdDeviceInformation* information); +int APS5_VABI sceHmdReprojectionStopLiveCapture(void); +int APS5_VABI sceHmdInternalAnotherScreenClose(void); +int APS5_VABI sceHmdInternalCrashReportReadData(void* buffer, std::uint32_t* bytesRead, std::uint32_t bufferSize); +int APS5_VABI sceHmdInternalGetDeviceInformation(HmdDeviceInformation* information); +int APS5_VABI sceHmdReprojectionStartCapture(void); +int APS5_VABI sceHmdInternalGetDefaultLedData(std::int32_t handle, std::uint32_t* mask, HmdLedData* data); +int APS5_VABI sceHmdInternalDfuClose(void); +int APS5_VABI sceHmdInternalSetHmuPowerControl(std::uint8_t control); +int APS5_VABI sceHmdInternalBindDeviceWithUserId(std::int32_t handle, std::int32_t userId); +int APS5_VABI sceHmdInternalGetHmuSerialNumber(std::uint8_t* serialNumber); +int APS5_VABI sceHmdInternalCheckS3dPassModeAvailable(bool* available); +int APS5_VABI sceHmdInternalAnotherScreenGetAudioStatus(HmdScreenStatus* status); +int APS5_VABI sceHmdInternalSeparateModeGetAudioStatus(HmdScreenStatus* status); +int APS5_VABI sceHmdReprojectionStartLiveCapture(const HmdLiveCaptureParameter* parameter); +int APS5_VABI sceHmdReprojectionFinalize(void); +int APS5_VABI sceHmdInternalSocialScreenSetFadeAndSwitch(const HmdScreenFadeParameter* parameter); +int APS5_VABI sceHmdInternalDfuStart(void); +int APS5_VABI sceHmdInternalSetVirtualDisplayHeight(std::uint8_t height); +int APS5_VABI sceHmdDistortionInitialize(void* workMemory); +int APS5_VABI sceHmdInternalDfuSetMode(std::uint32_t mode); +int APS5_VABI sceHmdInternalSetForcedCrash(std::uint8_t mode); +int APS5_VABI sceHmdInternalDfuCheckPartialUpdateAvailable(bool* available); +int APS5_VABI sceHmdInternalSeparateModeClose(void); +int APS5_VABI sceHmdInternalSetDebugTextMode(std::uint8_t mode); +int APS5_VABI sceHmdInternalCrashReportClose(void); +int APS5_VABI sceHmdInternalSetHmuPowerControlForDebug(std::uint8_t control); +int APS5_VABI sceHmdInternalGetVirtualDisplayHeight(std::uint8_t* height); +int APS5_VABI sceHmdReprojectionStart(const HmdReprojectionFrameParameter* parameter, const HmdReprojectionPose* pose, std::uint64_t flipArg, const void* reserved); +int APS5_VABI sceHmdInternalResetLedForVsh(void); +int APS5_VABI sceHmdInternalDfuOpen(void); +int APS5_VABI sceHmdInternalSetDebugMode(std::uint8_t mode); +int APS5_VABI sceHmdInternalCrashReportCancel(void); +int APS5_VABI sceHmdInternalSeparateModeGetVideoStatus(HmdScreenStatus* status); +int APS5_VABI sceHmdInternalCheckDeviceModelMk3(bool* isMk3); +int APS5_VABI sceHmdSetVr2dEnable(std::int32_t enabled); +int APS5_VABI sceHmdInternalSetSidetone(std::uint8_t level); +int APS5_VABI sceHmdInternalSeparateModeOpen(void); +int APS5_VABI sceHmdInternalMmapGetModeId(std::uint32_t* modeId); +int APS5_VABI sceHmdInternalDfuSendSize(std::int32_t size); +int APS5_VABI sceHmdReprojectionStartMultilayer2(const HmdReprojectionMultilayerParameter* layers, std::uint32_t layerCount, const HmdReprojectionSubmissionParameter* submission, std::uint64_t flipArg, const void* reserved); +int APS5_VABI sceHmdInternalSetDeviceConnection(std::uint64_t connection); +int APS5_VABI sceHmdInternalGetVirtualDisplaySize(std::uint8_t* size); +int APS5_VABI sceHmdReprojectionUnsetDisplayBuffers(void); +int APS5_VABI sceHmdInternalGetHmuOpticalParam(HmdOpticalParameter* parameter); +int APS5_VABI sceHmdInternalSeparateModeSendVideo(const HmdScreenData* data); +int APS5_VABI sceHmdInternalResetLedForVrTracker(void); +int APS5_VABI sceHmdReprojectionStartWithOverlay(const HmdReprojectionFrameParameter* parameter, const HmdReprojectionPose* pose, std::uint64_t flipArg, const HmdReprojectionOverlayParameter* overlay, const void* reserved); +int APS5_VABI sceHmdReprojectionSetUserEventEnd(std::int64_t eventQueue, std::int32_t eventId); +int APS5_VABI sceHmdInternalCrashReportOpen(void); +int APS5_VABI sceHmdInternalSetDemoMode(std::uint8_t mode); +int APS5_VABI sceHmdReprojectionStartWideNearWithOverlay(const HmdReprojectionWideNearParameter* parameter, const HmdReprojectionPose* pose, std::uint64_t flipArg, const HmdReprojectionOverlayParameter* overlay, const void* reserved); +int APS5_VABI sceHmdGetDistortionParams(HmdDistortionParameters* parameters); +int APS5_VABI sceHmdInternalDfuSend(const HmdDfuData* data); +int APS5_VABI sceHmdReprojectionClearUserEventStart(void); +int APS5_VABI sceHmdInternalSetUserType(std::uint8_t userType); +int APS5_VABI sceHmdInternalGetDemoMode(std::uint8_t* mode); +int APS5_VABI sceHmdInternalMapSharedMemory(void); +int APS5_VABI sceHmdInternalGetCrashDumpInfo(HmdCrashDumpInfo* info); +int APS5_VABI sceHmdInternalGetTv4kCapability(std::uint8_t* capability); +int APS5_VABI sceHmdInternalDfuReset(void); +int APS5_VABI sceHmdReprojectionStart2dVr(const HmdReprojection2dVrParameter* parameter, std::uint64_t flipArg, const void* reserved); +int APS5_VABI sceHmdInternalMmapGetCount(void); +int APS5_VABI sceHmdInternalSetDefaultLedData(std::int32_t handle, std::uint32_t mask, const HmdLedData* data); +int APS5_VABI sceHmdInternal3dAudioClose(std::int32_t port); +int APS5_VABI sceHmdInternalSetBrightness(std::uint8_t brightness); +int APS5_VABI sceHmdInternalSocialScreenGetFadeState(std::int32_t* state); +int APS5_VABI sceHmdInternalSeparateModeSendAudio(const HmdScreenData* data); +int APS5_VABI sceHmdInternalGetStatusReport(std::int32_t handle, HmdStatusReport* report); +int APS5_VABI sceHmdInternalGetBrightness(std::uint8_t* brightness); +int APS5_VABI sceHmdReprojectionFinalizeCapture(void); +int APS5_VABI sceHmdInternalSetPortStatus(std::uint64_t status); +int APS5_VABI sceHmdReprojectionStop(void); +int APS5_VABI sceHmdInternalAnotherScreenGetVideoStatus(HmdScreenStatus* status); +int APS5_VABI sceHmdInternal3dAudioOpen(std::int32_t port); +int APS5_VABI sceHmdInternalAnotherScreenGetFadeState(std::int32_t* state); +int APS5_VABI sceHmdInternalSetDebugGpo(std::uint32_t value); +int APS5_VABI sceHmdInternalGetIpdSettingEnableForSystemService(std::uint8_t* enabled); +int APS5_VABI sceHmdReprojectionDebugGetLastInfoMultilayer(HmdReprojectionMultilayerDebugInfo* info); +int APS5_VABI sceHmdInternalGetPuRevision(std::uint32_t* revision); + +} diff --git a/core/libs/prx/libSceHmd/libSceHmd.hpp b/core/libs/prx/libSceHmd/libSceHmd.hpp new file mode 100644 index 0000000000..fb3f263602 --- /dev/null +++ b/core/libs/prx/libSceHmd/libSceHmd.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "prx/libSceHmd/Hmd.hpp" +#include "prx/libSceHmd/include/Api.hpp" diff --git a/core/libs/prx/libSceHmd2/CMakeLists.txt b/core/libs/prx/libSceHmd2/CMakeLists.txt index fc0c32cbf4..6d7565ac4a 100644 --- a/core/libs/prx/libSceHmd2/CMakeLists.txt +++ b/core/libs/prx/libSceHmd2/CMakeLists.txt @@ -14,3 +14,15 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_hmd2_gaze_tests ${LIBS_INCLUDE_DIR}/tests/GuestHmd2Gaze.cpp) + target_include_directories(guest_hmd2_gaze_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_hmd2_gaze_tests PRIVATE libSceHmd2 libc) + configure_windows_unwind(guest_hmd2_gaze_tests) + add_test(NAME guest_hmd2_gaze COMMAND guest_hmd2_gaze_tests) + if(WIN32) + set_tests_properties(guest_hmd2_gaze PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceHmd2/Export.cpp b/core/libs/prx/libSceHmd2/Export.cpp index f6f53d804c..82c55782b7 100644 --- a/core/libs/prx/libSceHmd2/Export.cpp +++ b/core/libs/prx/libSceHmd2/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceHmd2/include/Api.hpp" +#include "prx/libSceHmd2/Hmd2.hpp" #include "Hmd2.hpp" #include "prx/libc/include/general/ExportMacros.hpp" #include "prx/libc/include/ApplicationHeap.hpp" @@ -6,10 +8,68 @@ #include "prx/libSceUserService/UserService.hpp" #include "SceTypes.hpp" +#include #include #include #include +namespace Hmd2 { + +static_assert(sizeof(ReprojectionOutput) == 32); +static_assert(sizeof(ReprojectionInitializeParameter) == 72); +static_assert(offsetof(ReprojectionInitializeParameter, threadPriority) == 16); +static_assert(offsetof(ReprojectionInitializeParameter, threadAffinity) == 20); +static_assert(offsetof(ReprojectionInitializeParameter, renderingMode) == 32); +static_assert(offsetof(ReprojectionInitializeParameter, auxiliaryMemory) == 40); +static_assert(offsetof(ReprojectionInitializeParameter, timingInterval) == 48); + +static_assert(sizeof(ReprojectionOption) == 128); +static_assert(sizeof(ReprojectionParameter) == 24); +static_assert(sizeof(ReprojectionBufferedParameter) == 24); +static_assert(offsetof(ReprojectionParameter, option) == 16); +static_assert(offsetof(ReprojectionBufferedParameter, option) == 16); + +static_assert(sizeof(MirroringParameter) == 16); +static_assert(sizeof(MirroringOption) == 32); + +static_assert(sizeof(GazeResultParameter) == 16); +static_assert(sizeof(FoveatedRenderingResult) == 24); +static_assert(offsetof(FoveatedRenderingResult, direction) == 8); + +static_assert(sizeof(GazeObjectSelectionTransform) == 64); +static_assert(sizeof(GazeObjectSelectionObject) == 96); +static_assert(offsetof(GazeObjectSelectionObject, transform) == 16); +static_assert(offsetof(GazeObjectSelectionObject, boundingBoxes) == 80); +static_assert(offsetof(GazeObjectSelectionObject, boundingBoxCount) == 88); +static_assert(sizeof(GazeObjectSelectionParameter) == 120); +static_assert(offsetof(GazeObjectSelectionParameter, transform) == 24); +static_assert(offsetof(GazeObjectSelectionParameter, selectionMode) == 88); +static_assert(offsetof(GazeObjectSelectionParameter, timestamp) == 96); +static_assert(offsetof(GazeObjectSelectionParameter, objects) == 104); +static_assert(offsetof(GazeObjectSelectionParameter, objectCount) == 112); +static_assert(sizeof(GazeObjectSelectionResult) == 56); +static_assert(offsetof(GazeObjectSelectionResult, objectId) == 8); +static_assert(offsetof(GazeObjectSelectionResult, gazeTimestamp) == 16); +static_assert(offsetof(GazeObjectSelectionResult, origin) == 28); +static_assert(offsetof(GazeObjectSelectionResult, direction) == 40); +static_assert(offsetof(GazeObjectSelectionResult, distance) == 52); + +static_assert(sizeof(CalibrationPoint) == 12); +static_assert(sizeof(GazeCalibrationData) == 16); +static_assert(offsetof(GazeCalibrationData, size) == 8); +static_assert(sizeof(GazeUserCalibrationData) == 16); +static_assert(offsetof(GazeUserCalibrationData, size) == 8); +static_assert(sizeof(GazeUserCalibrationStatus) == 20); +static_assert(offsetof(GazeUserCalibrationStatus, point) == 8); +static_assert(sizeof(GazeObjectSelectionInitializeParameter) == 24); +static_assert(offsetof(GazeObjectSelectionInitializeParameter, memory) == 8); +static_assert(offsetof(GazeObjectSelectionInitializeParameter, memorySize) == 16); +static_assert(sizeof(GazeObjectSelectionMemoryParameter) == 8); +static_assert(sizeof(GazeObjectSelectionMemoryInfo) == 16); +static_assert(offsetof(GazeObjectSelectionMemoryInfo, alignment) == 8); + +} + namespace { constexpr std::int32_t DeviceHandle = 0x0e010000; @@ -50,7 +110,7 @@ std::int32_t GetDeviceInformation(Hmd2::DeviceInformation* info) { extern "C" { -int APS5_VABI sceHmd2ReprojectionGetStatus() { +int APS5_VABI sceHmd2ReprojectionGetStatus(Hmd2::ReprojectionStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -94,14 +154,40 @@ std::int32_t APS5_VABI sceHmd2Close(std::int32_t handle) { return 0; } -int APS5_VABI sceHmd2GazeGetResult() { - NotImplemented_nid_no_patch(__func__); +static std::int32_t Hmd2GazeHandle(const Hmd2::GazeResultParameter* param, const void* result, std::uint32_t resultSize, const char* name) { + if (!param || !result) return Hmd2::ParameterNull; + std::uint32_t size; + std::memcpy(&size, result, sizeof(size)); + if (param->size != sizeof(Hmd2::GazeResultParameter) || size != resultSize) return Hmd2::ParameterInvalid; + std::lock_guard lock(stateMutex); + if (openHandle == 0 || param->handle != openHandle) return Hmd2::InvalidHandle; + NotImplemented_nid_no_patch(name); return 0; } -int APS5_VABI sceHmd2GazeGetResultForFoveatedRendering() { - NotImplemented_nid_no_patch(__func__); - return 0; +static void Hmd2StoreInvalidGaze(std::byte* result, std::size_t offset) { + const std::int32_t invalidIndex = -1; + const float invalidPair[2] = {-0.0f, 0.0f}; + const float invalidScalar = -1.0f; + std::memcpy(result + 4, &invalidIndex, sizeof(invalidIndex)); + std::memcpy(result + offset, invalidPair, sizeof(invalidPair)); + std::memcpy(result + offset + sizeof(invalidPair), &invalidScalar, sizeof(invalidScalar)); +} + +int APS5_VABI sceHmd2GazeGetResult(const Hmd2::GazeResultParameter* param, Hmd2::GazeResult* result) { + const std::int32_t status = Hmd2GazeHandle(param, result, 0x38, __func__); + if (status != Hmd2::InvalidHandle) return status; + auto* bytes = reinterpret_cast(result); + std::memset(bytes + 4, 0, 0x38 - 4); + Hmd2StoreInvalidGaze(bytes, 0x14); + return status; +} + +int APS5_VABI sceHmd2GazeGetResultForFoveatedRendering(const Hmd2::GazeResultParameter* param, Hmd2::FoveatedRenderingResult* result) { + const std::int32_t status = Hmd2GazeHandle(param, result, 0x18, __func__); + if (status != Hmd2::InvalidHandle) return status; + Hmd2StoreInvalidGaze(reinterpret_cast(result), 8); + return status; } std::int32_t APS5_VABI sceHmd2GetDeviceInformation(Hmd2::DeviceInformation* info) { @@ -130,7 +216,7 @@ std::int32_t APS5_VABI sceHmd2Open(std::int32_t userId, std::int32_t type, std:: return DeviceHandle; } -int APS5_VABI sceHmd2ReprojectionBeginFrame() { +int APS5_VABI sceHmd2ReprojectionBeginFrame(std::uint64_t frameId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -145,12 +231,12 @@ int APS5_VABI sceHmd2ReprojectionDisableVrMode() { return 0; } -int APS5_VABI sceHmd2ReprojectionEnableMirroring() { +int APS5_VABI sceHmd2ReprojectionEnableMirroring(const Hmd2::MirroringParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmd2ReprojectionEnableVrMode() { +int APS5_VABI sceHmd2ReprojectionEnableVrMode(std::uint64_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -163,17 +249,17 @@ Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionGetMirroringWorkMemorySizeAlign() { return {0x4000, 0x100}; } -int APS5_VABI sceHmd2ReprojectionGetPredictedDisplayTime() { +int APS5_VABI sceHmd2ReprojectionGetPredictedDisplayTime(std::uint64_t frameId, std::uint64_t* time) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmd2ReprojectionInitialize() { +int APS5_VABI sceHmd2ReprojectionInitialize(const Hmd2::ReprojectionInitializeParameter* parameter, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmd2ReprojectionQueryBufferSizeAlign() { +Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionQueryBufferSizeAlign() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -182,32 +268,32 @@ Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionQueryDisplayBufferSizeAlign() { return {0x6000000, 0x10000}; } -int APS5_VABI sceHmd2ReprojectionSetAllowPositionalReprojection() { +int APS5_VABI sceHmd2ReprojectionSetAllowPositionalReprojection(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmd2ReprojectionSetMirroringOption() { +int APS5_VABI sceHmd2ReprojectionSetMirroringOption(const Hmd2::MirroringOption* option) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmd2ReprojectionSetParam() { +int APS5_VABI sceHmd2ReprojectionSetParam(std::uint32_t bufferSetId, std::uint64_t frameId, const Hmd2::ReprojectionParameter* parameter, std::uint64_t reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmd2ReprojectionSetParamWithBuffer() { +int APS5_VABI sceHmd2ReprojectionSetParamWithBuffer(std::uint32_t bufferSetId, std::uint64_t frameId, const Hmd2::ReprojectionBufferedParameter* parameter, std::uint64_t reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmd2ReprojectionSetRenderConfig() { +int APS5_VABI sceHmd2ReprojectionSetRenderConfig(std::uint64_t flags, std::uint64_t reserved, std::uint64_t reserved2) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmd2ReprojectionSetTiming() { +int APS5_VABI sceHmd2ReprojectionSetTiming(std::int32_t timing) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -259,12 +345,12 @@ int APS5_VABI sceHmd2ReprojectionSet() { return 0; } -int APS5_VABI sceHmd2ReprojectionSetEventGpuEnd() { +int APS5_VABI sceHmd2ReprojectionSetEventGpuEnd(KernelEqueue queue, std::int32_t eventId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHmd2ReprojectionSetEventGpuStart() { +int APS5_VABI sceHmd2ReprojectionSetEventGpuStart(KernelEqueue queue, std::int32_t eventId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -274,7 +360,6 @@ int APS5_VABI sceHmd2RpeorjectionSetParam() { return 0; } - std::int32_t APS5_VABI Hmd2BeforeInitializeZero() { return BeforeInitialize(0, __func__); } @@ -292,13 +377,11 @@ APS5_EXPORT("9KnNj-dyASg", Hmd2BeforeInitializeZero); APS5_EXPORT("Qta2bkstbFk", Hmd2BeforeInitializeZero); APS5_EXPORT("5ZcObnDRBXE", Hmd2BeforeInitializeZero); APS5_EXPORT("AvPNn0qx4WQ", Hmd2BeforeInitializeZero); -APS5_EXPORT("jp0apqNGgi8", Hmd2BeforeInitializeZero); +int APS5_VABI sceHmd2SetIpdOffset(std::uint8_t enabled, float offset) { + return BeforeInitialize(0, __func__); +} APS5_EXPORT("-kZT7X7OMg0", Hmd2BeforeInitializeZero); APS5_EXPORT("1ewLtng-SVk", Hmd2BeforeInitializeZero); -APS5_EXPORT("B0b8DeIUGes", Hmd2BeforeInitializeZero); -APS5_EXPORT("HclLg4Oj5yw", Hmd2BeforeInitializeZero); -APS5_EXPORT("VbEvrQXsZyY", Hmd2BeforeInitializeZero); -APS5_EXPORT("bB6MKZi5UCQ", Hmd2BeforeInitializeZero); APS5_EXPORT("OXQ1EDf02XU", Hmd2BeforeInitializeZero); APS5_EXPORT("IAxfMTvFm0M", Hmd2BeforeInitializeZero); APS5_EXPORT("qTJtAtaLeno", Hmd2BeforeInitializeZero); @@ -310,7 +393,9 @@ APS5_EXPORT("MjY1gBSaed0", Hmd2BeforeInitializeZero); APS5_EXPORT("evAMjJYHqW4", Hmd2BeforeInitializeZero); APS5_EXPORT("r93FLEZ3BBk", Hmd2BeforeInitializeZero); APS5_EXPORT("IdYURdEdNFo", Hmd2BeforeInitializeZero); -APS5_EXPORT("CVcL2Cu2AEI", Hmd2BeforeInitializeZero); +int APS5_VABI sceHmd2GazeActivateGazeTracking(std::int32_t mode) { + return BeforeInitialize(0, __func__); +} APS5_EXPORT("NmKIfVxr2s0", Hmd2BeforeInitializeZero); APS5_EXPORT("KeDLkkp62Gg", Hmd2BeforeInitializeZero); APS5_EXPORT("+aadu6-ra+A", Hmd2BeforeInitializeZero); @@ -347,14 +432,22 @@ APS5_EXPORT("TJ8lyRsjFY4", Hmd2BeforeInitializeError0004); APS5_EXPORT("-XQVcLnyX68", Hmd2BeforeInitializeError0004); APS5_EXPORT("giQwQoHhYYA", Hmd2BeforeInitializeError0004); APS5_EXPORT("zNc9IkgawMI", Hmd2BeforeInitializeError0004); -APS5_EXPORT("-tnG5UJPvAc", Hmd2BeforeInitializeError0004); -APS5_EXPORT("FPhFmG+jXuQ", Hmd2BeforeInitializeError0004); +int APS5_VABI sceHmd2GazeStartUserCalibration() { + return BeforeInitialize(Hmd2::Error0004, __func__); +} +int APS5_VABI sceHmd2GazeStopUserCalibration() { + return BeforeInitialize(Hmd2::Error0004, __func__); +} APS5_EXPORT("CKlkvNngofQ", Hmd2BeforeInitializeError0004); -APS5_EXPORT("yL1CZaciFWE", Hmd2BeforeInitializeError0004); +int APS5_VABI sceHmd2GazeSetUserCalibrationPoint(Hmd2::CalibrationPoint point) { + return BeforeInitialize(Hmd2::Error0004, __func__); +} APS5_EXPORT("qlzgyG3s0RY", Hmd2BeforeInitializeError0004); APS5_EXPORT("kTIDrDLzMv0", Hmd2BeforeInitializeError0004); APS5_EXPORT("9Gee0ef8a+U", Hmd2BeforeInitializeError0004); -APS5_EXPORT("ecZVjQdwmBI", Hmd2BeforeInitializeError0004); +int APS5_VABI sceHmd2GazeSetEnabledEye(std::int32_t eye) { + return BeforeInitialize(Hmd2::Error0004, __func__); +} std::int32_t APS5_VABI Hmd2BeforeInitializeParameterNull() { return BeforeInitialize(Hmd2::ParameterNull, __func__); @@ -367,14 +460,20 @@ APS5_EXPORT("J44idh9izZM", Hmd2BeforeInitializeParameterNull); APS5_EXPORT("Ps0wAvYaa1E", Hmd2BeforeInitializeParameterNull); APS5_EXPORT("kLnMR8oQ1MY", Hmd2BeforeInitializeParameterNull); APS5_EXPORT("kMz4rbBxvjo", Hmd2BeforeInitializeParameterNull); -APS5_EXPORT("DIU1sFRRaiE", Hmd2BeforeInitializeParameterNull); -APS5_EXPORT("ZQvU4vDzZGQ", Hmd2BeforeInitializeParameterNull); +int APS5_VABI sceHmd2GazeGetCalibrationData(std::int32_t type, Hmd2::GazeCalibrationData* data) { + return BeforeInitialize(Hmd2::ParameterNull, __func__); +} +int APS5_VABI sceHmd2GazeSetUserCalibrationData(const Hmd2::GazeUserCalibrationData* data) { + return BeforeInitialize(Hmd2::ParameterNull, __func__); +} APS5_EXPORT("TJpyGGP9YEY", Hmd2BeforeInitializeParameterNull); APS5_EXPORT("Dih+Odk+RC0", Hmd2BeforeInitializeParameterNull); APS5_EXPORT("oaqdSJw+HVY", Hmd2BeforeInitializeParameterNull); APS5_EXPORT("m35gtHuanXU", Hmd2BeforeInitializeParameterNull); APS5_EXPORT("CTM7QPLJF9Q", Hmd2BeforeInitializeParameterNull); -APS5_EXPORT("+rExHeC9lP0", Hmd2BeforeInitializeParameterNull); +int APS5_VABI sceHmd2GazeObjectSelectionInitialize(const Hmd2::GazeObjectSelectionInitializeParameter* parameter) { + return BeforeInitialize(Hmd2::ParameterNull, __func__); +} std::int32_t APS5_VABI Hmd2BeforeInitializeParameterInvalid() { return BeforeInitialize(Hmd2::ParameterInvalid, __func__); @@ -395,18 +494,30 @@ std::int32_t APS5_VABI Hmd2BeforeInitializeReprojectionNotInitialized() { APS5_EXPORT("d3SEIYTP6wc", Hmd2BeforeInitializeReprojectionNotInitialized); APS5_EXPORT("EVECbTvBezo", Hmd2BeforeInitializeReprojectionNotInitialized); APS5_EXPORT("ciusPdJKjSU", Hmd2BeforeInitializeReprojectionNotInitialized); -APS5_EXPORT("DIwqTkcZiJo", Hmd2BeforeInitializeReprojectionNotInitialized); -APS5_EXPORT("64ERZWVFBTE", Hmd2BeforeInitializeReprojectionNotInitialized); -APS5_EXPORT("z0+0gXUJWs8", Hmd2BeforeInitializeReprojectionNotInitialized); -APS5_EXPORT("rjAd4nCY4DM", Hmd2BeforeInitializeReprojectionNotInitialized); -APS5_EXPORT("Acyg4vTdGm0", Hmd2BeforeInitializeReprojectionNotInitialized); +int APS5_VABI sceHmd2ReprojectionSetUserEventStart(KernelEqueue queue, std::int32_t eventId) { + return BeforeInitialize(Hmd2::ReprojectionNotInitialized, __func__); +} +int APS5_VABI sceHmd2ReprojectionSetUserEventEnd(KernelEqueue queue, std::int32_t eventId) { + return BeforeInitialize(Hmd2::ReprojectionNotInitialized, __func__); +} +int APS5_VABI sceHmd2ReprojectionClearUserEventStart() { + return BeforeInitialize(Hmd2::ReprojectionNotInitialized, __func__); +} +int APS5_VABI sceHmd2ReprojectionClearUserEventEnd() { + return BeforeInitialize(Hmd2::ReprojectionNotInitialized, __func__); +} +int APS5_VABI sceHmd2ReprojectionGetCurrentOutput(Hmd2::ReprojectionOutput* output) { + return BeforeInitialize(Hmd2::ReprojectionNotInitialized, __func__); +} APS5_EXPORT("aGOWaEeg04I", Hmd2BeforeInitializeReprojectionNotInitialized); APS5_EXPORT("9up+EPhw1No", Hmd2BeforeInitializeReprojectionNotInitialized); std::int32_t APS5_VABI Hmd2BeforeInitializeError002C() { return BeforeInitialize(Hmd2::Error002C, __func__); } -APS5_EXPORT("V1kLQmP-17U", Hmd2BeforeInitializeError002C); +int APS5_VABI sceHmd2GazeObjectSelectionTerminate() { + return BeforeInitialize(Hmd2::Error002C, __func__); +} std::int32_t APS5_VABI Hmd2BeforeInitializeError1002() { return BeforeInitialize(Hmd2::Error1002, __func__); @@ -427,10 +538,6 @@ std::int32_t APS5_VABI Hmd2BeforeInitializeMinusOne() { } APS5_EXPORT("EAkv8Mqt33E", Hmd2BeforeInitializeMinusOne); APS5_EXPORT("Rv7bkmKvmz0", Hmd2BeforeInitializeMinusOne); -APS5_EXPORT("1oPZizn0SNA", Hmd2BeforeInitializeMinusOne); -APS5_EXPORT("TwCpE-nNrlo", Hmd2BeforeInitializeMinusOne); -APS5_EXPORT("jkqAeQfdtxw", Hmd2BeforeInitializeMinusOne); -APS5_EXPORT("+l0RQph81Z8", Hmd2BeforeInitializeMinusOne); APS5_EXPORT("qsmAtGw1Ncc", Hmd2BeforeInitializeMinusOne); APS5_EXPORT("uKhwYoMsIsA", Hmd2BeforeInitializeMinusOne); APS5_EXPORT("WNdaigr94dI", Hmd2BeforeInitializeMinusOne); @@ -438,6 +545,38 @@ APS5_EXPORT("P6S5ZVWf-H0", Hmd2BeforeInitializeMinusOne); APS5_EXPORT("sun6NWE1sdg", Hmd2BeforeInitializeMinusOne); APS5_EXPORT("--TFsJYXo04", Hmd2BeforeInitializeMinusOne); +std::int32_t APS5_VABI sceHmd2InternalTrackingOpen() { + return BeforeInitialize(0, __func__); +} + +std::int32_t APS5_VABI sceHmd2InternalTrackingClose() { + return BeforeInitialize(0, __func__); +} + +std::int32_t APS5_VABI sceHmd2InternalLedDetectorOpen() { + return BeforeInitialize(0, __func__); +} + +std::int32_t APS5_VABI sceHmd2InternalLedDetectorClose() { + return BeforeInitialize(0, __func__); +} + +std::int32_t APS5_VABI sceHmd2InternalTrackingStart() { + return BeforeInitialize(-1, __func__); +} + +std::int32_t APS5_VABI sceHmd2InternalTrackingStop() { + return BeforeInitialize(-1, __func__); +} + +std::int32_t APS5_VABI sceHmd2InternalLedDetectorStart() { + return BeforeInitialize(-1, __func__); +} + +std::int32_t APS5_VABI sceHmd2InternalLedDetectorStop() { + return BeforeInitialize(-1, __func__); +} + std::int32_t APS5_VABI Hmd2BeforeInitializeValue0400() { return BeforeInitialize(0x400, __func__); } @@ -563,8 +702,14 @@ APS5_EXPORT("Tc9rasWfrm4", Hmd2CheckFirstThenError0004); APS5_EXPORT("ckTbKTX4KZg", Hmd2CheckFirstThenError0004); APS5_EXPORT("D7576PUbUjk", Hmd2CheckFirstThenError0004); APS5_EXPORT("tTJ04SrBT8U", Hmd2CheckFirstThenError0004); -APS5_EXPORT("sDP7hZ-P6OI", Hmd2CheckFirstThenError0004); -APS5_EXPORT("J+eVNbN9ndY", Hmd2CheckFirstThenError0004); +int APS5_VABI sceHmd2GazeGetDeviceStatus(std::int32_t* status) { + if (status == nullptr) return Hmd2::ParameterNull; + return BeforeInitialize(Hmd2::Error0004, __func__); +} +int APS5_VABI sceHmd2GazeGetUserCalibrationStatus(Hmd2::GazeUserCalibrationStatus* status) { + if (status == nullptr) return Hmd2::ParameterNull; + return BeforeInitialize(Hmd2::Error0004, __func__); +} APS5_EXPORT("kozvHBNyxf0", Hmd2CheckFirstThenError0004); APS5_EXPORT("638Tmg5y6U4", Hmd2CheckFirstThenError0004); @@ -576,7 +721,10 @@ APS5_EXPORT("8ETtFtp5zqY", Hmd2CheckFirstThenZero); APS5_EXPORT("CWPkBm3EXQA", Hmd2CheckFirstThenZero); APS5_EXPORT("R62liQQIAaA", Hmd2CheckFirstThenZero); APS5_EXPORT("-bcWLHsdzo0", Hmd2CheckFirstThenZero); -APS5_EXPORT("JngJzgNbu8U", Hmd2CheckFirstThenZero); +int APS5_VABI sceHmd2SetFieldOfView(const Hmd2::FieldOfView* fieldOfView) { + if (fieldOfView == nullptr) return Hmd2::ParameterNull; + return BeforeInitialize(0, __func__); +} APS5_EXPORT("xNBSz8xrGdk", Hmd2CheckFirstThenZero); APS5_EXPORT("yUVPfN-jRIU", Hmd2CheckFirstThenZero); APS5_EXPORT("BKPR3RZ5aO4", Hmd2CheckFirstThenZero); @@ -610,8 +758,16 @@ std::int32_t APS5_VABI Hmd2CheckFirstTwoThenParameterInvalid(const void* a0, con return BeforeInitialize(Hmd2::ParameterInvalid, __func__); } APS5_EXPORT("PPX68fNToFM", Hmd2CheckFirstTwoThenParameterInvalid); -APS5_EXPORT("LxPIfjHaY4c", Hmd2CheckFirstTwoThenParameterInvalid); -APS5_EXPORT("w8gBTTdg+po", Hmd2CheckFirstTwoThenParameterInvalid); +int APS5_VABI sceHmd2GazeObjectSelectionQueryMemory(const Hmd2::GazeObjectSelectionMemoryParameter* parameter, Hmd2::GazeObjectSelectionMemoryInfo* info) { + if (parameter == nullptr) return Hmd2::ParameterNull; + if (info == nullptr) return Hmd2::ParameterNull; + return BeforeInitialize(Hmd2::ParameterInvalid, __func__); +} +int APS5_VABI sceHmd2GazeObjectSelectionGetSelectedObject(const Hmd2::GazeObjectSelectionParameter* parameter, Hmd2::GazeObjectSelectionResult* result) { + if (parameter == nullptr) return Hmd2::ParameterNull; + if (result == nullptr) return Hmd2::ParameterNull; + return BeforeInitialize(Hmd2::ParameterInvalid, __func__); +} std::int32_t APS5_VABI Hmd2CheckSecondThenError002C(const void* a0, const void* a1) { (void)a0; @@ -647,7 +803,11 @@ std::int32_t APS5_VABI Hmd2CheckSecondThenParameterInvalid(const void* a0, const return BeforeInitialize(Hmd2::ParameterInvalid, __func__); } APS5_EXPORT("72U64E+0z6U", Hmd2CheckSecondThenParameterInvalid); -APS5_EXPORT("LCQzi5ILGrY", Hmd2CheckSecondThenParameterInvalid); +int APS5_VABI sceHmd2GazeGetCalibrationDataMaxSize(std::int32_t type, std::uint32_t* size) { + (void)type; + if (size == nullptr) return Hmd2::ParameterNull; + return BeforeInitialize(Hmd2::ParameterInvalid, __func__); +} std::int32_t APS5_VABI Hmd2CheckFirstThenError800d0203(const void* a0) { if (a0 == nullptr) return Hmd2::ParameterNull; @@ -702,7 +862,11 @@ std::int32_t APS5_VABI Hmd2CheckSecondThenInvalidHandle(const void* a0, const vo if (a1 == nullptr) return Hmd2::ParameterNull; return BeforeInitialize(Hmd2::InvalidHandle, __func__); } -APS5_EXPORT("wEHILc7TP04", Hmd2CheckSecondThenInvalidHandle); +int APS5_VABI sceHmd2GazeInsertRawDataWithHandle(std::int32_t handle, Hmd2::GazeRawData* data) { + (void)handle; + if (data == nullptr) return Hmd2::ParameterNull; + return BeforeInitialize(Hmd2::InvalidHandle, __func__); +} std::int32_t APS5_VABI Hmd2CheckSecondThenError1002(const void* a0, const void* a1) { (void)a0; @@ -711,7 +875,6 @@ std::int32_t APS5_VABI Hmd2CheckSecondThenError1002(const void* a0, const void* } APS5_EXPORT("W-ZIlUXcrNA", Hmd2CheckSecondThenError1002); - std::int32_t APS5_VABI Hmd2WriteStateTwo(void* a0) { if (a0 == nullptr) return Hmd2::ParameterNull; static constexpr std::uint8_t bytes[] = {0x02, 0x00, 0x00, 0x00, 0x00}; diff --git a/core/libs/prx/libSceHmd2/Hmd2.hpp b/core/libs/prx/libSceHmd2/Hmd2.hpp index c5ae98b060..4623331f91 100644 --- a/core/libs/prx/libSceHmd2/Hmd2.hpp +++ b/core/libs/prx/libSceHmd2/Hmd2.hpp @@ -60,9 +60,13 @@ static_assert(sizeof(FieldOfView) == 16); struct SizeAlign { std::uint64_t size; std::uint64_t align; + + constexpr SizeAlign(std::uint64_t size = 0, std::uint64_t align = 0) noexcept : size(size), align(align) {} }; static_assert(sizeof(SizeAlign) == 16); +static_assert(offsetof(SizeAlign, size) == 0); +static_assert(offsetof(SizeAlign, align) == 8); } @@ -76,6 +80,7 @@ std::int32_t APS5_VABI sceHmd2GetDeviceInformationByHandle(std::int32_t handle, std::int32_t APS5_VABI sceHmd2GetFieldOfView(std::int32_t handle, Hmd2::FieldOfView* fieldOfView); std::int32_t APS5_VABI sceHmd2GetFieldOfViewWithoutHandle(Hmd2::FieldOfView* fieldOfView); std::int32_t APS5_VABI sceHmd2SetVibration(std::int32_t handle, std::uint32_t level); +Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionQueryBufferSizeAlign(); Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionQueryDisplayBufferSizeAlign(); Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionQuerySeeThroughBufferSizeAlign(); Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionGetMirroringDisplayBufferSizeAlign(); diff --git a/core/libs/prx/libSceHmd2/include/Api.hpp b/core/libs/prx/libSceHmd2/include/Api.hpp new file mode 100644 index 0000000000..141cd26128 --- /dev/null +++ b/core/libs/prx/libSceHmd2/include/Api.hpp @@ -0,0 +1,384 @@ +#pragma once + +#include "prx/libSceHmd2/Hmd2.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include + +namespace Hmd2 { + +struct ReprojectionStatus; + +} + +namespace Hmd2 { + +struct ReprojectionOutput { + std::uint32_t textureDescriptor[8]; +}; + +} + +namespace Hmd2 { + +struct ReprojectionInitializeParameter { + void* workMemory; + void* displayMemory; + std::int32_t threadPriority; + std::uint32_t threadAffinity; + std::uint32_t queueParameter24; + std::uint32_t queueParameter28; + std::uint32_t renderingMode; + std::uint32_t reserved36; + void* auxiliaryMemory; + std::uint32_t timingInterval; + std::uint32_t reserved52[5]; +}; + +} + +namespace Hmd2 { + +struct ReprojectionLayer; + +} + +namespace Hmd2 { + +struct ReprojectionBufferedLayer; + +} + +namespace Hmd2 { + +struct ReprojectionOption { + std::uint32_t size; + std::uint32_t flags; + std::uint32_t reserved[30]; +}; + +} + +namespace Hmd2 { + +struct ReprojectionParameter { + const ReprojectionLayer* layers; + std::int32_t layerCount; + std::uint8_t padding[4]; + const ReprojectionOption* option; +}; + +} + +namespace Hmd2 { + +struct ReprojectionBufferedParameter { + const ReprojectionBufferedLayer* layers; + std::int32_t layerCount; + std::uint8_t padding[4]; + const ReprojectionOption* option; +}; + +} + +namespace Hmd2 { + +struct MirroringParameter { + void* workMemory; + void* displayMemory; +}; + +} + +namespace Hmd2 { + +struct MirroringOption { + std::uint32_t mode; + std::uint32_t reserved[7]; +}; + +} + +namespace Hmd2 { + +struct GazeResultParameter { + std::uint32_t size; + std::int32_t handle; + std::uint8_t reserved[8]; +}; + +} + +namespace Hmd2 { + +struct GazeResult; + +} + +namespace Hmd2 { + +struct FoveatedRenderingResult { + std::uint32_t size; + std::int32_t status; + float direction[3]; + std::uint8_t reserved[4]; +}; + +} + +namespace Hmd2 { + +struct GazeCalibrationData { + void* data; + std::int32_t size; +}; + +} + +namespace Hmd2 { + +struct GazeUserCalibrationData { + const void* data; + std::int32_t size; +}; + +} + +namespace Hmd2 { + +struct GazeUserCalibrationStatus { + std::int32_t status; + std::int32_t eye; + float point[3]; +}; + +} + +namespace Hmd2 { + +struct GazeRawData; + +} + +namespace Hmd2 { + +struct GazeObjectSelectionTransform { + float values[16]; +}; + +} + +namespace Hmd2 { + +struct GazeObjectSelectionObject { + std::uint64_t id; + std::uint32_t enabled; + std::uint32_t reserved12; + GazeObjectSelectionTransform transform; + const GazeObjectSelectionTransform* boundingBoxes; + std::uint32_t boundingBoxCount; + std::uint32_t reserved92; +}; + +} + +namespace Hmd2 { + +struct GazeObjectSelectionParameter { + std::uint32_t size; + std::uint32_t reserved4; + std::uint64_t reserved8; + std::uint64_t gazeTimestamp; + GazeObjectSelectionTransform transform; + std::uint32_t selectionMode; + std::uint32_t filterMode; + std::uint64_t timestamp; + const GazeObjectSelectionObject* objects; + std::uint32_t objectCount; + std::uint32_t reserved116; +}; + +} + +namespace Hmd2 { + +struct GazeObjectSelectionResult { + std::uint32_t size; + std::uint32_t selected; + std::uint64_t objectId; + std::uint64_t gazeTimestamp; + std::uint32_t gazeValid; + float origin[3]; + float direction[3]; + float distance; +}; + +} + +namespace Hmd2 { + +struct GazeObjectSelectionInitializeParameter { + std::uint32_t size; + std::uint32_t maxObjects; + void* memory; + std::uint32_t memorySize; + std::uint32_t reserved; +}; + +} + +namespace Hmd2 { + +struct GazeObjectSelectionMemoryParameter { + std::uint32_t size; + std::uint32_t maxObjects; +}; + +} + +namespace Hmd2 { + +struct GazeObjectSelectionMemoryInfo { + std::uint32_t size; + std::uint32_t memorySize; + std::uint64_t alignment; +}; + +} + +namespace Hmd2 { + +struct CalibrationPoint { + float x; + float y; + float z; +}; + +} + +extern "C" { + +int APS5_VABI sceHmd2ReprojectionGetStatus(Hmd2::ReprojectionStatus* status); +int APS5_VABI sceHmd2GazeGetResult(const Hmd2::GazeResultParameter* param, Hmd2::GazeResult* result); +int APS5_VABI sceHmd2GazeGetResultForFoveatedRendering(const Hmd2::GazeResultParameter* param, Hmd2::FoveatedRenderingResult* result); +int APS5_VABI sceHmd2ReprojectionBeginFrame(std::uint64_t frameId); +int APS5_VABI sceHmd2ReprojectionDisableMirroring(); +int APS5_VABI sceHmd2ReprojectionDisableVrMode(); +int APS5_VABI sceHmd2ReprojectionEnableMirroring(const Hmd2::MirroringParameter* parameter); +int APS5_VABI sceHmd2ReprojectionEnableVrMode(std::uint64_t mode); +int APS5_VABI sceHmd2ReprojectionGetPredictedDisplayTime(std::uint64_t frameId, std::uint64_t* time); +int APS5_VABI sceHmd2ReprojectionInitialize(const Hmd2::ReprojectionInitializeParameter* parameter, const void* reserved); +int APS5_VABI sceHmd2ReprojectionSetAllowPositionalReprojection(std::uint8_t mode); +int APS5_VABI sceHmd2ReprojectionSetMirroringOption(const Hmd2::MirroringOption* option); +int APS5_VABI sceHmd2ReprojectionSetParam(std::uint32_t bufferSetId, std::uint64_t frameId, const Hmd2::ReprojectionParameter* parameter, std::uint64_t reserved); +int APS5_VABI sceHmd2ReprojectionSetParamWithBuffer(std::uint32_t bufferSetId, std::uint64_t frameId, const Hmd2::ReprojectionBufferedParameter* parameter, std::uint64_t reserved); +int APS5_VABI sceHmd2ReprojectionSetRenderConfig(std::uint64_t flags, std::uint64_t reserved, std::uint64_t reserved2); +int APS5_VABI sceHmd2ReprojectionSetTiming(std::int32_t timing); +int APS5_VABI sceHmd2ReprojectionTerminate(); +int APS5_VABI sceHmd2ReprojectionSeeThroughDraw(); +int APS5_VABI sceHmd2ReprojectionSeeThroughIC(); +int APS5_VABI sceHmd2ReprojectionVideoOutEq(); +int APS5_VABI sceHmd2ReprojectionWaitShader(); +int APS5_VABI sceHmd2SeeThroughCallbackGetPoseDataFunc(); +int APS5_VABI sceHmd2ReprojectionClear(); +int APS5_VABI sceHmd2ReprojectionSet(); +int APS5_VABI sceHmd2ReprojectionSetEventGpuEnd(KernelEqueue queue, std::int32_t eventId); +int APS5_VABI sceHmd2ReprojectionSetEventGpuStart(KernelEqueue queue, std::int32_t eventId); +int APS5_VABI sceHmd2RpeorjectionSetParam(); +std::int32_t APS5_VABI Hmd2BeforeInitializeZero(); +int APS5_VABI sceHmd2SetIpdOffset(std::uint8_t enabled, float offset); +int APS5_VABI sceHmd2GazeActivateGazeTracking(std::int32_t mode); +std::int32_t APS5_VABI Hmd2BeforeInitializeNotInitialized(); +std::int32_t APS5_VABI Hmd2BeforeInitializeError0004(); +int APS5_VABI sceHmd2GazeStartUserCalibration(); +int APS5_VABI sceHmd2GazeStopUserCalibration(); +int APS5_VABI sceHmd2GazeSetUserCalibrationPoint(Hmd2::CalibrationPoint point); +int APS5_VABI sceHmd2GazeSetEnabledEye(std::int32_t eye); +std::int32_t APS5_VABI Hmd2BeforeInitializeParameterNull(); +int APS5_VABI sceHmd2GazeGetCalibrationData(std::int32_t type, Hmd2::GazeCalibrationData* data); +int APS5_VABI sceHmd2GazeSetUserCalibrationData(const Hmd2::GazeUserCalibrationData* data); +int APS5_VABI sceHmd2GazeObjectSelectionInitialize(const Hmd2::GazeObjectSelectionInitializeParameter* parameter); +std::int32_t APS5_VABI Hmd2BeforeInitializeParameterInvalid(); +std::int32_t APS5_VABI Hmd2BeforeInitializeReprojectionNotInitialized(); +int APS5_VABI sceHmd2ReprojectionSetUserEventStart(KernelEqueue queue, std::int32_t eventId); +int APS5_VABI sceHmd2ReprojectionSetUserEventEnd(KernelEqueue queue, std::int32_t eventId); +int APS5_VABI sceHmd2ReprojectionClearUserEventStart(); +int APS5_VABI sceHmd2ReprojectionClearUserEventEnd(); +int APS5_VABI sceHmd2ReprojectionGetCurrentOutput(Hmd2::ReprojectionOutput* output); +std::int32_t APS5_VABI Hmd2BeforeInitializeError002C(); +int APS5_VABI sceHmd2GazeObjectSelectionTerminate(); +std::int32_t APS5_VABI Hmd2BeforeInitializeError1002(); +std::int32_t APS5_VABI Hmd2BeforeInitializeError1003(); +std::int32_t APS5_VABI Hmd2BeforeInitializeMinusOne(); +std::int32_t APS5_VABI Hmd2BeforeInitializeValue0400(); +std::int32_t APS5_VABI Hmd2BeforeInitializeValue20000(); +std::int32_t APS5_VABI sceHmd2InternalTrackingOpen(); +std::int32_t APS5_VABI sceHmd2InternalTrackingStart(); +std::int32_t APS5_VABI sceHmd2InternalTrackingStop(); +std::int32_t APS5_VABI sceHmd2InternalTrackingClose(); +std::int32_t APS5_VABI sceHmd2InternalLedDetectorOpen(); +std::int32_t APS5_VABI sceHmd2InternalLedDetectorStart(); +std::int32_t APS5_VABI sceHmd2InternalLedDetectorStop(); +std::int32_t APS5_VABI sceHmd2InternalLedDetectorClose(); +int APS5_VABI sceHmd2InternalSetPortStatus(void); +int APS5_VABI sceHmd2InternalLedDetectorStartBySlot(void); +int APS5_VABI sceHmd2InternalImageStartBySlot(void); +int APS5_VABI sceHmd2InternalSetPortConnection(void); +int APS5_VABI sceHmd2InternalSetCameraSeeThroughMode(void); +int APS5_VABI sceHmd2InternalSetFakeLensSeparationDistance(void); +int APS5_VABI sceHmd2InternalDfuSend(void); +int APS5_VABI sceHmd2InternalLedDetectorOpenBySlot(void); +int APS5_VABI sceHmd2InternalImageOpenBySlot(void); +int APS5_VABI sceHmd2InternalLedDetectorStopBySlot(void); +int APS5_VABI sceHmd2InternalSetDeviceConnection(void); +int APS5_VABI sceHmd2InternalLedDetectorCloseBySlot(void); +int APS5_VABI sceHmd2InternalSetSidetone(void); +int APS5_VABI sceHmd2InternalGetDeviceId(void); +int APS5_VABI sceHmd2InternalSetBrightness(void); +int APS5_VABI sceHmd2InternalImageStopBySlot(void); +int APS5_VABI sceHmd2InternalSetVibrationMode(void); +int APS5_VABI sceHmd2InternalGetVr2dData(void); +int APS5_VABI sceHmd2InternalImageCloseBySlot(void); +std::int32_t APS5_VABI Hmd2CheckFirstThenError0004(const void* a0); +int APS5_VABI sceHmd2GazeGetDeviceStatus(std::int32_t* status); +int APS5_VABI sceHmd2GazeGetUserCalibrationStatus(Hmd2::GazeUserCalibrationStatus* status); +std::int32_t APS5_VABI Hmd2CheckFirstThenZero(const void* a0); +int APS5_VABI sceHmd2SetFieldOfView(const Hmd2::FieldOfView* fieldOfView); +std::int32_t APS5_VABI Hmd2CheckFirstThenError1002(const void* a0); +std::int32_t APS5_VABI Hmd2CheckFirstThenMinusOne(const void* a0); +std::int32_t APS5_VABI Hmd2CheckFirstTwoThenParameterInvalid(const void* a0, const void* a1); +int APS5_VABI sceHmd2GazeObjectSelectionQueryMemory(const Hmd2::GazeObjectSelectionMemoryParameter* parameter, Hmd2::GazeObjectSelectionMemoryInfo* info); +int APS5_VABI sceHmd2GazeObjectSelectionGetSelectedObject(const Hmd2::GazeObjectSelectionParameter* parameter, Hmd2::GazeObjectSelectionResult* result); +std::int32_t APS5_VABI Hmd2CheckSecondThenError002C(const void* a0, const void* a1); +std::int32_t APS5_VABI Hmd2CheckFirstThenParameterInvalid(const void* a0); +std::int32_t APS5_VABI Hmd2CheckAllSixThenZero(const void* a0, const void* a1, const void* a2, const void* a3, const void* a4, const void* a5); +std::int32_t APS5_VABI Hmd2CheckSecondThenParameterInvalid(const void* a0, const void* a1); +int APS5_VABI sceHmd2GazeGetCalibrationDataMaxSize(std::int32_t type, std::uint32_t* size); +std::int32_t APS5_VABI Hmd2CheckFirstThenError800d0203(const void* a0); +std::int32_t APS5_VABI Hmd2CheckFirstThenError1003(const void* a0); +std::int32_t APS5_VABI Hmd2CheckFirstThenError1010(const void* a0); +std::int32_t APS5_VABI Hmd2CheckFirstTwoThenError1002(const void* a0, const void* a1); +std::int32_t APS5_VABI Hmd2CheckFirstTwoThenError1003(const void* a0, const void* a1); +std::int32_t APS5_VABI Hmd2CheckFirstAndThirdThenZero(const void* a0, const void* a1, const void* a2); +std::int32_t APS5_VABI Hmd2CheckFirstAndThirdThenError1003(const void* a0, const void* a1, const void* a2); +std::int32_t APS5_VABI Hmd2CheckSecondThenInvalidHandle(const void* a0, const void* a1); +int APS5_VABI sceHmd2GazeInsertRawDataWithHandle(std::int32_t handle, Hmd2::GazeRawData* data); +std::int32_t APS5_VABI Hmd2CheckSecondThenError1002(const void* a0, const void* a1); +std::int32_t APS5_VABI Hmd2WriteStateTwo(void* a0); +std::int32_t APS5_VABI Hmd2WriteSize0x2000Align0x100(std::uint64_t* a0, std::uint64_t* a1); +std::int32_t APS5_VABI Hmd2WriteFourConstants(void* a0); +std::int32_t APS5_VABI Hmd2WriteSixHalvesThenInvalidHandle(std::uint64_t a0, void* a1); +std::int32_t APS5_VABI Hmd2WriteTwelveHalvesThenInvalidHandle(std::uint64_t a0, void* a1); +std::int32_t APS5_VABI Hmd2SlotUpTo3CheckSecondThirdThenZero(std::uint64_t a0, const void* a1, const void* a2); +std::int32_t APS5_VABI Hmd2SlotUpTo1ThenError1002(std::uint64_t a0); +std::int32_t APS5_VABI Hmd2SlotUpTo1CheckSecondThenError1002(std::uint64_t a0, const void* a1); +std::int32_t APS5_VABI Hmd2SlotUpTo1ThenMinusOne(std::uint64_t a0); +std::int32_t APS5_VABI Hmd2SlotUpTo2ThenMinusOne(std::uint64_t a0); +std::int32_t APS5_VABI Hmd2SlotUpTo2ThenError1002(std::uint64_t a0); +std::int32_t APS5_VABI Hmd2SlotUpTo2ThenError1003(std::uint64_t a0); +std::int32_t APS5_VABI Hmd2SlotUpTo2CheckSecondThenError002C(std::uint64_t a0, const void* a1); +std::int32_t APS5_VABI Hmd2IndexZeroCheckSecondThenZero(std::uint64_t a0, const void* a1); + +} diff --git a/core/libs/prx/libSceHmd2/libSceHmd2.hpp b/core/libs/prx/libSceHmd2/libSceHmd2.hpp new file mode 100644 index 0000000000..0c3f869cc7 --- /dev/null +++ b/core/libs/prx/libSceHmd2/libSceHmd2.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "prx/libSceHmd2/Hmd2.hpp" +#include "prx/libSceHmd2/include/Api.hpp" diff --git a/core/libs/prx/libSceHmdSetupDialog/CMakeLists.txt b/core/libs/prx/libSceHmdSetupDialog/CMakeLists.txt new file mode 100644 index 0000000000..a294abe2b0 --- /dev/null +++ b/core/libs/prx/libSceHmdSetupDialog/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libSceHmdSetupDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceHmdSetupDialog PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceHmdSetupDialog) + +if(BUILD_TESTING) + add_test_executable(guest_hmd_setup_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestHmdSetupDialog.cpp) + target_include_directories(guest_hmd_setup_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_hmd_setup_dialog_tests PRIVATE libSceHmdSetupDialog) + add_test(NAME guest_hmd_setup_dialog COMMAND guest_hmd_setup_dialog_tests) + if(WIN32) + set_tests_properties(guest_hmd_setup_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceHmdSetupDialog/Export.cpp b/core/libs/prx/libSceHmdSetupDialog/Export.cpp index 6fb905167d..156643bf24 100644 --- a/core/libs/prx/libSceHmdSetupDialog/Export.cpp +++ b/core/libs/prx/libSceHmdSetupDialog/Export.cpp @@ -1,42 +1,71 @@ +#include "prx/libSceHmdSetupDialog/include/Api.hpp" +#include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int COMMON_DIALOG_STATUS_NONE = 0; +constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; +constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; +constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; + +std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; + +} + extern "C" { -int APS5_VABI sceHmdSetupDialogClose() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceHmdSetupDialogInitialize(void) { + int expected = COMMON_DIALOG_STATUS_NONE; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; return 0; } -int APS5_VABI sceHmdSetupDialogGetResult() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceHmdSetupDialogOpen(const void* param) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } -int APS5_VABI sceHmdSetupDialogGetStatus() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceHmdSetupDialogUpdateStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceHmdSetupDialogGetStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceHmdSetupDialogGetResult(void* result) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; + std::memcpy(result, &canceled, sizeof(canceled)); return 0; } -int APS5_VABI sceHmdSetupDialogInitialize() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceHmdSetupDialogClose(void) { + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } -int APS5_VABI sceHmdSetupDialogOpen() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHmdSetupDialogTerminate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHmdSetupDialogUpdateStatus() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceHmdSetupDialogTerminate(void) { + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; return 0; } diff --git a/core/libs/prx/libSceHmdSetupDialog/include/Api.hpp b/core/libs/prx/libSceHmdSetupDialog/include/Api.hpp new file mode 100644 index 0000000000..abe27b70aa --- /dev/null +++ b/core/libs/prx/libSceHmdSetupDialog/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceHmdSetupDialogInitialize(void); +int APS5_VABI sceHmdSetupDialogOpen(const void* param); +int APS5_VABI sceHmdSetupDialogUpdateStatus(void); +int APS5_VABI sceHmdSetupDialogGetStatus(void); +int APS5_VABI sceHmdSetupDialogGetResult(void* result); +int APS5_VABI sceHmdSetupDialogClose(void); +int APS5_VABI sceHmdSetupDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceHmdSetupDialog/libSceHmdSetupDialog.hpp b/core/libs/prx/libSceHmdSetupDialog/libSceHmdSetupDialog.hpp new file mode 100644 index 0000000000..4b5986bfa2 --- /dev/null +++ b/core/libs/prx/libSceHmdSetupDialog/libSceHmdSetupDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceHmdSetupDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index 5c537baf62..ad94bc3ba5 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHttp/include/Http.hpp" #include #include #ifdef _WIN32 @@ -24,17 +25,22 @@ // No network is emulated: contexts, templates and requests can be created, but any request // that would touch the network fails with the library's network error. static std::atomic g_nextHandle{1}; -using HttpRequestStatusCallback = void (APS5_VABI *)(int, int, void*); + struct RequestStatusRegistration { HttpRequestStatusCallback callback; void* argument; }; static std::mutex g_statusMutex; static std::map g_statusCallbacks; -struct HttpsCaList { - void* certs; - int certsNum; -}; + +static_assert(sizeof(HttpCookieStats) == 32); +static_assert(offsetof(HttpCookieStats, peakUsedBytes) == 16); + +static_assert(sizeof(HttpMemoryPoolStats) == 24); +static_assert(offsetof(HttpMemoryPoolStats, peakUsedSize) == 8); +static_assert(offsetof(HttpMemoryPoolStats, usedSize) == 16); +static_assert(sizeof(HttpRequestHeader) == 16); +static_assert(offsetof(HttpRequestHeader, value) == 8); #ifdef _WIN32 struct HostMutex { @@ -104,6 +110,7 @@ struct HttpEpoll { static std::mutex g_stateMutex; static std::unordered_set g_contexts; +static std::unordered_map g_contextPoolSizes; static std::unordered_map> g_epolls; static std::shared_ptr FindEpoll(HttpEpollHandle eh) { @@ -150,6 +157,16 @@ int APS5_VABI sceHttpCookieFlush(int httpCtxId) { return 0; } +int APS5_VABI sceHttpGetMemoryPoolStats(int httpCtxId, HttpMemoryPoolStats* currentStat) { + if (!currentStat) APS5_INVALID_ARG_EX; + std::lock_guard lock(g_stateMutex); + if (g_contexts.empty()) return ERROR_BEFORE_INIT; + const auto pool = g_contextPoolSizes.find(httpCtxId); + if (pool == g_contextPoolSizes.end()) return ERROR_INVALID_ID; + *currentStat = {static_cast(pool->second), 0, 0}; + return 0; +} + int APS5_VABI sceHttpCreateConnection(int tmpl_id, const char* server_name, const char* scheme, uint16_t port, int enable_keep_alive) { (void)server_name; (void)scheme; @@ -261,9 +278,9 @@ int APS5_VABI sceHttpGetStatusCode(int request_id, int* status_code) { int APS5_VABI sceHttpInit_nid_postfix(int memid, int ssl_ctx_id, uint64_t pool_size) { (void)memid; (void)ssl_ctx_id; - (void)pool_size; const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); std::lock_guard lock(g_stateMutex); + g_contextPoolSizes[id] = pool_size; g_contexts.insert(id); AddHttpContext(id); return id; @@ -283,9 +300,8 @@ int APS5_VABI sceHttpSendRequest(int request_id, const void* post_data, size_t s } int APS5_VABI sceHttpSetAuthEnabled(int id, int enable) { - (void)id; - (void)enable; - return 0; + if (!HttpInitialised()) return ERROR_BEFORE_INIT; + return SetHttpOption(id, &HttpOptions::authEnabled, enable != 0) ? 0 : ERROR_INVALID_ID; } int APS5_VABI sceHttpSetAuthInfoCallback(int id, HttpAuthInfoCallback callback, void* userArg) { @@ -296,10 +312,9 @@ int APS5_VABI sceHttpSetAuthInfoCallback(int id, HttpAuthInfoCallback callback, } int APS5_VABI sceHttpSetCookieEnabled(int id, int enable) { - (void)id; if (static_cast(enable) > 1) return ERROR_INVALID_VALUE; - if (enable != 0) NotImplemented_nid_no_patch(__func__); - return 0; + if (!HttpInitialised()) return ERROR_BEFORE_INIT; + return SetHttpOption(id, &HttpOptions::cookieEnabled, enable != 0) ? 0 : ERROR_INVALID_ID; } int APS5_VABI sceHttpSetAutoRedirect(int id, int enable) { @@ -388,6 +403,7 @@ int APS5_VABI sceHttpTerm_nid_postfix(int http_ctx_id) { std::vector> epolls; { std::lock_guard lock(g_stateMutex); + g_contextPoolSizes.erase(http_ctx_id); if (g_contexts.empty()) return ERROR_BEFORE_INIT; if (g_contexts.erase(http_ctx_id) == 0) return ERROR_INVALID_ID; RemoveHttpContext(http_ctx_id); @@ -451,7 +467,6 @@ int APS5_VABI sceHttpSetChunkedTransferEnabled(int id, int enable) { return 0; } - int APS5_VABI sceHttpSetInflateGZIPEnabled(int id, int enable) { (void)id; if (static_cast(enable) > 1) return ERROR_INVALID_VALUE; @@ -634,12 +649,12 @@ int APS5_VABI sceHttpsFreeCaList(int http_ctx_id, HttpsCaList* ca_list) { return 0; } -int APS5_VABI sceHttpAuthCacheImport(void) { +int APS5_VABI sceHttpAuthCacheImport(int contextId, const void* buffer, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpAuthCacheExport(void) { +int APS5_VABI sceHttpAuthCacheExport(int contextId, void* buffer, std::size_t size, std::size_t* requiredSize) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -657,11 +672,6 @@ int APS5_VABI sceHttpDbgShowStat(void) { return 0; } -int APS5_VABI sceHttpGetMemoryPoolStats(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceHttpDbgShowConnectionStat(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -677,107 +687,116 @@ int APS5_VABI sceHttpDbgShowMemoryPoolStat(void) { return 0; } -int APS5_VABI sceHttpDbgGetConnectionStat(void) { +int APS5_VABI sceHttpDbgGetConnectionStat(std::int32_t connectionId, HttpConnectionStat* stat) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpDbgGetRequestStat(void) { +int APS5_VABI sceHttpDbgGetRequestStat(std::int32_t requestId, HttpRequestStat* stat) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpAddRequestHeaderRaw(void) { +int APS5_VABI sceHttpAddRequestHeaderRaw(int id, const char* header, int mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpRemoveRequestHeader(void) { +int APS5_VABI sceHttpRemoveRequestHeader(int id, const char* name) { + (void)id; + (void)name; + return 0; +} + +int APS5_VABI sceHttpGetAuthEnabled(int id, int* enable) { + if (!HttpInitialised()) return ERROR_BEFORE_INIT; + if (!enable) return ERROR_INVALID_VALUE; + const auto options = FindHttpOptions(id); + if (!options) return ERROR_INVALID_ID; + *enable = options->authEnabled ? 1 : 0; + return 0; +} + +int APS5_VABI sceHttpSetEpollId(std::int32_t id, std::int32_t epollId, void* userArg) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpGetAuthEnabled(void) { +int APS5_VABI sceHttpGetEpollId(std::int32_t id, std::int32_t* epollId, void** userArg) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpSetEpollId(void) { +int APS5_VABI sceHttpGetCookie(int contextId, const char* url, char* cookie, std::size_t* requiredSize, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpGetEpollId(void) { +int APS5_VABI sceHttpGetCookieEnabled(int id, int* enable) { + if (!HttpInitialised()) return ERROR_BEFORE_INIT; + if (!enable) return ERROR_INVALID_VALUE; + const auto options = FindHttpOptions(id); + if (!options) return ERROR_INVALID_ID; + *enable = options->cookieEnabled ? 1 : 0; + return 0; +} + +int APS5_VABI sceHttpAddCookie(int contextId, const char* url, const char* cookie, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpGetCookie(void) { +int APS5_VABI sceHttpSetCookieSendCallback(std::int32_t id, HttpCookieSendCallback callback, void* userArg) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpGetCookieEnabled(void) { +int APS5_VABI sceHttpCookieExport(int contextId, void* buffer, std::size_t size, std::size_t* requiredSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpAddCookie(void) { +int APS5_VABI sceHttpCookieImport(int contextId, const void* buffer, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpSetCookieSendCallback(void) { +int APS5_VABI sceHttpSetCookieTotalMaxSize(int contextId, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCookieExport(void) { +int APS5_VABI sceHttpSetCookieMaxSize(int contextId, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCookieImport(void) { +int APS5_VABI sceHttpSetCookieMaxNum(int contextId, std::uint32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpSetCookieTotalMaxSize(void) { +int APS5_VABI sceHttpSetCookieMaxNumPerDomain(int contextId, std::uint32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpSetCookieMaxSize(void) { +int APS5_VABI sceHttpGetCookieStats(int id, HttpCookieStats* stats) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpSetCookieMaxNum(void) { +int APS5_VABI sceHttpSetPriorityOption(int id, int priority) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpSetCookieMaxNumPerDomain(void) { +int APS5_VABI sceHttpSetPolicyOption(int id, int policy) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpGetCookieStats(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHttpSetPriorityOption(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHttpSetPolicyOption(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHttpRequestGetAllHeaders(void) { +int APS5_VABI sceHttpRequestGetAllHeaders(int requestId, HttpRequestHeader* headers, std::uint32_t* headerCount, char* contentLength, std::size_t* contentLengthSize) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -788,37 +807,38 @@ int APS5_VABI sceHttpSetDefaultAcceptEncodingGZIPEnabled(int http_ctx_id, int en return SetHttpContextAcceptEncodingGzip(http_ctx_id, enable != 0) ? 0 : ERROR_INVALID_ID; } -int APS5_VABI sceHttpSetProxy(void) { +int APS5_VABI sceHttpSetProxy(int id, int, int, const char* hostname, std::uint16_t) { + if (!HttpInitialised()) return ERROR_BEFORE_INIT; + if (!hostname) return ERROR_INVALID_VALUE; + return FindHttpOptions(id) ? 0 : ERROR_INVALID_ID; +} + +int APS5_VABI sceHttpSetSocketCreationCallback(std::int32_t id, HttpSocketCreationCallback callback, void* userArg) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpSetSocketCreationCallback(void) { +int APS5_VABI sceHttpSetHttp09Enabled(int id, int enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpSetHttp09Enabled(void) { +int APS5_VABI sceHttpGetRegisteredCtxIds(int contextId, int* netPoolId, int* sslContextId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpGetRegisteredCtxIds(void) { +int APS5_VABI sceHttpGetConnectionStat(std::int32_t connectionId, HttpConnectionStat* stat) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpGetConnectionStat(void) { +int APS5_VABI sceHttpAddQuery(int id, const char* query) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpAddQuery(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceHttpSetDelayBuildRequestEnabled(void) { +int APS5_VABI sceHttpSetDelayBuildRequestEnabled(int id, int enabled) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceHttp/include/Http.hpp b/core/libs/prx/libSceHttp/include/Http.hpp new file mode 100644 index 0000000000..505be14652 --- /dev/null +++ b/core/libs/prx/libSceHttp/include/Http.hpp @@ -0,0 +1,157 @@ +#pragma once + +#include "SceTypes.hpp" + +struct HttpConnectionStat; +struct HttpRequestStat; + +struct HttpCookieStats { + std::size_t usedBytes; + std::uint32_t cookieCount; + std::uint32_t reserved; + std::size_t peakUsedBytes; + std::uint32_t peakCookieCount; + std::uint32_t evictedCookieCount; +}; + +struct HttpMemoryPoolStats { + std::size_t poolSize; + std::size_t peakUsedSize; + std::size_t usedSize; +}; + +struct HttpRequestHeader { + const char* name; + const char* value; +}; + +struct HttpsCaList { + void* certs; + int certsNum; +}; + +using HttpCookieSendCallback = int (APS5_VABI *)(std::int32_t, const char*, const char*, void*); + +using HttpSocketCreationCallback = void (APS5_VABI *)(std::int32_t, std::int32_t, void*); + +using HttpRequestStatusCallback = void (APS5_VABI *)(int, int, void*); + +extern "C" { + +int APS5_VABI sceHttpAbortRequest(int requestId); +int APS5_VABI sceHttpAbortRequestForce(int requestId); +int APS5_VABI sceHttpAddRequestHeader(int id, const char* name, const char* value, uint32_t mode); +int APS5_VABI sceHttpAuthCacheFlush(int httpCtxId); +int APS5_VABI sceHttpCookieFlush(int httpCtxId); +int APS5_VABI sceHttpCreateConnection(int tmplId, const char* serverName, const char* scheme, uint16_t port, int enableKeepAlive); +int APS5_VABI sceHttpCreateConnectionWithURL(int tmplId, const char* url, int enableKeepAlive); +int APS5_VABI sceHttpCreateEpoll(int httpCtxId, HttpEpollHandle* eh); +int APS5_VABI sceHttpCreateRequest(int connId, int method, const char* path, uint64_t contentLength); +int APS5_VABI sceHttpCreateRequestWithURL2(int connId, const char* method, const char* url, uint64_t contentLength); +int APS5_VABI sceHttpCreateTemplate(int httpCtxId, const char* userAgent, int httpVer, int isAutoProxyConf); +int APS5_VABI sceHttpDeleteConnection(int connId); +int APS5_VABI sceHttpDeleteRequest(int reqId); +int APS5_VABI sceHttpDeleteTemplate(int tmplId); +int APS5_VABI sceHttpDestroyEpoll(int httpCtxId, HttpEpollHandle eh); +int APS5_VABI sceHttpGetAllResponseHeaders(int requestId, char** header, size_t* headerSize); +int APS5_VABI sceHttpGetResponseContentLength(int requestId, int* result, uint64_t* contentLength); +int APS5_VABI sceHttpGetStatusCode(int requestId, int* statusCode); +int APS5_VABI sceHttpInit_nid_postfix(int memid, int sslCtxId, uint64_t poolSize); +int APS5_VABI sceHttpsDisableOption(int id, uint32_t sslFlags); +int APS5_VABI sceHttpSendRequest(int requestId, const void* postData, size_t size); +int APS5_VABI sceHttpSetAuthEnabled(int id, int enable); +int APS5_VABI sceHttpSetAuthInfoCallback(int id, HttpAuthInfoCallback callback, void* userArg); +int APS5_VABI sceHttpSetCookieEnabled(int id, int enable); +int APS5_VABI sceHttpSetAutoRedirect(int id, int enable); +int APS5_VABI sceHttpSetConnectTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttpSetEpoll(int id, HttpEpollHandle eh, void* userArg); +int APS5_VABI sceHttpSetNonblock(int id, int enable); +int APS5_VABI sceHttpTrySetNonblock(int id, int enable); +int APS5_VABI sceHttpSetRecvTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttpSetRecvBlockSize(int id, uint32_t blockSize); +int APS5_VABI sceHttpSetResponseHeaderMaxSize(int id, uint64_t headerSize); +int APS5_VABI sceHttpSetRequestContentLength(int requestId, uint64_t contentLength); +int APS5_VABI sceHttpSetResolveRetry(int id, int32_t retry); +int APS5_VABI sceHttpSetResolveTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttpSetSendTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttpsSetMinSslVersion(int id, uint32_t sslVersion); +int APS5_VABI sceHttpsSetSslCallback(int id, HttpsCallback cbfunc, void* userArg); +int APS5_VABI sceHttpTerm_nid_postfix(int httpCtxId); +int APS5_VABI sceHttpUnsetEpoll(int id); +int APS5_VABI sceHttpWaitRequest(HttpEpollHandle eh, HttpNBEvent* nbev, int maxevents, int timeout); +int APS5_VABI sceHttpCreateRequestWithURL(int connId, int method, const char* url, uint64_t contentLength); +int APS5_VABI sceHttpReadData(int requestId, void* data, size_t size); +int APS5_VABI sceHttpRedirectCacheFlush(int httpCtxId); +int APS5_VABI sceHttpSetChunkedTransferEnabled(int id, int enable); +int APS5_VABI sceHttpSetInflateGZIPEnabled(int id, int enable); +int APS5_VABI sceHttpSetRequestStatusCallback(int id, HttpRequestStatusCallback callback, void* argument); +int APS5_VABI sceHttpParseResponseHeader(const char* header, std::size_t headerLen, const char* fieldStr, const char** fieldValue, std::size_t* valueLen); +int APS5_VABI sceHttpCreateRequest2(int connId, const char* method, const char* path, uint64_t contentLength); +int APS5_VABI sceHttpsEnableOption(int id, uint32_t sslFlags); +int APS5_VABI sceHttpsLoadCert(int httpCtxId, int num, void* caList, void* cert, void* key); +int APS5_VABI sceHttpsUnloadCert(int httpCtxId); +int APS5_VABI sceHttpGetLastErrno(int requestId, int* errnoOut); +int APS5_VABI sceHttpsGetSslError(int id, int* errNum, uint32_t* detail); +int APS5_VABI sceHttpSetCookieRecvCallback(int id, HttpCookieRecvCallback cbfunc, void* userArg); +int APS5_VABI sceHttpsSetSslVersion(int id, int sslVersion); +int APS5_VABI sceHttpSetRedirectCallback(int id, HttpRedirectCallback cbfunc, void* userArg); +int APS5_VABI sceHttpAbortWaitRequest(HttpEpollHandle eh); +int APS5_VABI sceHttpsGetCaList(int httpCtxId, HttpsCaList* caList); +int APS5_VABI sceHttpsFreeCaList(int httpCtxId, HttpsCaList* caList); +int APS5_VABI sceHttpAuthCacheImport(int contextId, const void* buffer, std::size_t size); +int APS5_VABI sceHttpAuthCacheExport(int contextId, void* buffer, std::size_t size, std::size_t* requiredSize); +int APS5_VABI sceHttpDbgEnableProfile(void); +int APS5_VABI sceHttpDbgSetPrintf(void); +int APS5_VABI sceHttpDbgShowStat(void); +int APS5_VABI sceHttpGetMemoryPoolStats(int contextId, HttpMemoryPoolStats* stats); +int APS5_VABI sceHttpDbgShowConnectionStat(void); +int APS5_VABI sceHttpDbgShowRequestStat(void); +int APS5_VABI sceHttpDbgShowMemoryPoolStat(void); +int APS5_VABI sceHttpDbgGetConnectionStat(std::int32_t connectionId, HttpConnectionStat* stat); +int APS5_VABI sceHttpDbgGetRequestStat(std::int32_t requestId, HttpRequestStat* stat); +int APS5_VABI sceHttpAddRequestHeaderRaw(int id, const char* header, int mode); +int APS5_VABI sceHttpRemoveRequestHeader(int id, const char* name); +int APS5_VABI sceHttpGetAuthEnabled(int id, int* enable); +int APS5_VABI sceHttpSetEpollId(std::int32_t id, std::int32_t epollId, void* userArg); +int APS5_VABI sceHttpGetEpollId(std::int32_t id, std::int32_t* epollId, void** userArg); +int APS5_VABI sceHttpGetCookie(int contextId, const char* url, char* cookie, std::size_t* requiredSize, std::size_t size); +int APS5_VABI sceHttpGetCookieEnabled(int id, int* enable); +int APS5_VABI sceHttpAddCookie(int contextId, const char* url, const char* cookie, std::size_t size); +int APS5_VABI sceHttpSetCookieSendCallback(std::int32_t id, HttpCookieSendCallback callback, void* userArg); +int APS5_VABI sceHttpCookieExport(int contextId, void* buffer, std::size_t size, std::size_t* requiredSize); +int APS5_VABI sceHttpCookieImport(int contextId, const void* buffer, std::size_t size); +int APS5_VABI sceHttpSetCookieTotalMaxSize(int contextId, std::uint32_t size); +int APS5_VABI sceHttpSetCookieMaxSize(int contextId, std::uint32_t size); +int APS5_VABI sceHttpSetCookieMaxNum(int contextId, std::uint32_t count); +int APS5_VABI sceHttpSetCookieMaxNumPerDomain(int contextId, std::uint32_t count); +int APS5_VABI sceHttpGetCookieStats(int id, HttpCookieStats* stats); +int APS5_VABI sceHttpSetPriorityOption(int id, int priority); +int APS5_VABI sceHttpSetPolicyOption(int id, int policy); +int APS5_VABI sceHttpRequestGetAllHeaders(int requestId, HttpRequestHeader* headers, std::uint32_t* headerCount, char* contentLength, std::size_t* contentLengthSize); +int APS5_VABI sceHttpSetDefaultAcceptEncodingGZIPEnabled(int httpCtxId, int enable); +int APS5_VABI sceHttpSetProxy(int id, int, int, const char* hostname, std::uint16_t); +int APS5_VABI sceHttpSetSocketCreationCallback(std::int32_t id, HttpSocketCreationCallback callback, void* userArg); +int APS5_VABI sceHttpSetHttp09Enabled(int id, int enabled); +int APS5_VABI sceHttpGetRegisteredCtxIds(int contextId, int* netPoolId, int* sslContextId); +int APS5_VABI sceHttpGetConnectionStat(std::int32_t connectionId, HttpConnectionStat* stat); +int APS5_VABI sceHttpAddQuery(int id, const char* query); +int APS5_VABI sceHttpSetDelayBuildRequestEnabled(int id, int enabled); +int APS5_VABI sceHttpGetAutoRedirect(int id, int* enable); +int APS5_VABI sceHttpGetNonblock(int id, int* enable); +int APS5_VABI sceHttpTryGetNonblock(int id, int* enable); +int APS5_VABI sceHttpGetAcceptEncodingGZIPEnabled(int id, int* enable); +int APS5_VABI sceHttpSetAcceptEncodingGZIPEnabled(int id, int enable); +int APS5_VABI sceHttpGetEpoll(int id, HttpEpollHandle* epoll, void** argument); +int APS5_VABI sceHttpCacheRedirectedConnectionEnabled(int id, int enable); +int APS5_VABI sceHttpsEnableOptionPrivate(int id, std::uint32_t sslFlags); +int APS5_VABI sceHttpsDisableOptionPrivate(int id, std::uint32_t sslFlags); +int APS5_VABI sceHttpParseStatusLine(const char* statusLine, size_t lineLen, int32_t* httpMajorVer, int32_t* httpMinorVer, int32_t* responseCode, const char** reasonPhrase, size_t* phraseLen); +int APS5_VABI sceHttpUriBuild(char* out, size_t* requiredSize, size_t capacity, const SceHttpUriElement* element, uint32_t option); +int APS5_VABI sceHttpUriEscape(char* out, size_t* require, size_t prepare, const char* in); +int APS5_VABI sceHttpUriUnescape(char* out, size_t* requiredSize, size_t capacity, const char* input); +int APS5_VABI sceHttpUriMerge(char* mergedUrl, const char* url, const char* relativeUri, size_t* require, size_t prepare, uint32_t option); +int APS5_VABI sceHttpUriParse(SceHttpUriElement* out, const char* srcUrl, void* pool, size_t* require, size_t prepare); +int APS5_VABI sceHttpUriCopy(SceHttpUriElement* out, const SceHttpUriElement* src, void* pool, size_t prepare, size_t* require); +int APS5_VABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize); + +} diff --git a/core/libs/prx/libSceHttp/libSceHttp.hpp b/core/libs/prx/libSceHttp/libSceHttp.hpp new file mode 100644 index 0000000000..69e0774038 --- /dev/null +++ b/core/libs/prx/libSceHttp/libSceHttp.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceHttp/include/Http.hpp" diff --git a/core/libs/prx/libSceHttp/src/Options.cpp b/core/libs/prx/libSceHttp/src/Options.cpp index 710b33311a..415c462ee4 100644 --- a/core/libs/prx/libSceHttp/src/Options.cpp +++ b/core/libs/prx/libSceHttp/src/Options.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHttp/include/Http.hpp" #include "prx/libSceHttp/src/Options.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceHttp/src/HttpErrors.hpp" diff --git a/core/libs/prx/libSceHttp/src/Options.hpp b/core/libs/prx/libSceHttp/src/Options.hpp index beca67b395..6c2205ecc9 100644 --- a/core/libs/prx/libSceHttp/src/Options.hpp +++ b/core/libs/prx/libSceHttp/src/Options.hpp @@ -6,6 +6,8 @@ struct HttpOptions { bool autoRedirect = true; + bool authEnabled = false; + bool cookieEnabled = false; bool nonblock = false; bool acceptEncodingGzip = false; HttpEpollHandle epoll = nullptr; diff --git a/core/libs/prx/libSceHttp/src/Parse.cpp b/core/libs/prx/libSceHttp/src/Parse.cpp index 0732f62407..1662a05f43 100644 --- a/core/libs/prx/libSceHttp/src/Parse.cpp +++ b/core/libs/prx/libSceHttp/src/Parse.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHttp/include/Http.hpp" #include #include #include @@ -11,10 +12,10 @@ bool isAsciiDigit(char c) { return c >= '0' && c <= '9'; } -bool parseVersionNumber(const char* line, size_t lineLen, size_t& index, int32_t* value) { - if (index >= lineLen || !isAsciiDigit(line[index])) return false; +bool parseVersionNumber(const char* line, size_t& index, int32_t* value) { + if (!isAsciiDigit(line[index])) return false; uint32_t number = 0; - while (index < lineLen && isAsciiDigit(line[index])) { + while (isAsciiDigit(line[index])) { number = number * 10 + static_cast(line[index] - '0'); ++index; } @@ -36,11 +37,11 @@ int APS5_VABI sceHttpParseStatusLine(const char* statusLine, size_t lineLen, int if (lineLen < 8 || std::memcmp(statusLine, "HTTP/", 5) != 0) return ERROR_PARSE_HTTP_INVALID_RESPONSE; size_t index = 5; - if (!parseVersionNumber(statusLine, lineLen, index, httpMajorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; - if (index >= lineLen || statusLine[index] != '.') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!parseVersionNumber(statusLine, index, httpMajorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (statusLine[index] != '.') return ERROR_PARSE_HTTP_INVALID_RESPONSE; ++index; - if (!parseVersionNumber(statusLine, lineLen, index, httpMinorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; - if (index >= lineLen || statusLine[index] != ' ') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!parseVersionNumber(statusLine, index, httpMinorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (statusLine[index] != ' ') return ERROR_PARSE_HTTP_INVALID_RESPONSE; ++index; if (lineLen - index < 3) return ERROR_PARSE_HTTP_INVALID_RESPONSE; @@ -51,10 +52,11 @@ int APS5_VABI sceHttpParseStatusLine(const char* statusLine, size_t lineLen, int index += 3; const char* phrase = statusLine + index; - const void* lineFeed = std::memchr(phrase, '\n', lineLen - index); - if (!lineFeed) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + const size_t searched = lineLen - index + 1; + size_t length = 0; + while (length != searched && phrase[length] != '\n') ++length; + if (length == searched) return ERROR_PARSE_HTTP_INVALID_RESPONSE; - size_t length = static_cast(static_cast(lineFeed) - phrase); const size_t consumed = index + length + 1; if (consumed > static_cast(INT32_MAX)) { throw std::out_of_range("sceHttpParseStatusLine: consumed byte count exceeds int"); diff --git a/core/libs/prx/libSceHttp/src/Uri.cpp b/core/libs/prx/libSceHttp/src/Uri.cpp index 670a29e7eb..f2c4f83d2d 100644 --- a/core/libs/prx/libSceHttp/src/Uri.cpp +++ b/core/libs/prx/libSceHttp/src/Uri.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHttp/include/Http.hpp" #include #include #include @@ -7,6 +8,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libSceHttp/src/HttpErrors.hpp" namespace { @@ -28,6 +30,7 @@ struct UriParts { std::string_view password; std::string_view hostname; std::string path; + size_t pathSpace = 0; std::string_view query; std::string_view fragment; uint16_t port = 0; @@ -54,8 +57,8 @@ bool equalsIgnoreCase(std::string_view left, std::string_view right) { } uint16_t defaultPort(std::string_view scheme) { - if (equalsIgnoreCase(scheme, "http")) return 80; - if (equalsIgnoreCase(scheme, "https")) return 443; + if (scheme.size() >= 5 && equalsIgnoreCase(scheme.substr(0, 5), "https")) return 443; + if (scheme.size() >= 4 && equalsIgnoreCase(scheme.substr(0, 4), "http")) return 80; return 0; } @@ -63,31 +66,21 @@ bool isHostCharacter(char c) { return isAsciiAlpha(c) || isAsciiDigit(c) || c == '-' || c == '.' || c == '_'; } -std::string removeDotSegments(std::string_view input) { - std::string output; - while (!input.empty()) { - if (input.starts_with("../")) input.remove_prefix(3); - else if (input.starts_with("./")) input.remove_prefix(2); - else if (input.starts_with("/./")) input.remove_prefix(2); - else if (input == "/.") input = "/"; - else if (input.starts_with("/../") || input == "/..") { - input = input.size() == 3 ? std::string_view("/") : input.substr(3); - const size_t slash = output.rfind('/'); - output.erase(slash == std::string::npos ? 0 : slash); - } else if (input == "." || input == "..") input = {}; - else { - const size_t end = input.find('/', 1); - const size_t length = end == std::string_view::npos ? input.size() : end; - output.append(input.substr(0, length)); - input.remove_prefix(length); - } - } - return output; +bool isUserinfoCharacter(char c) { + return isAsciiAlpha(c) || isAsciiDigit(c) || std::string_view("-._~!$&'()*+,;=%:").find(c) != std::string_view::npos; +} + +std::string sweptPath(std::string_view path) { + const std::string source(path); + std::string swept(source.size() + 1, '\0'); + sceHttpUriSweepPath(swept.data(), source.c_str(), source.size() + 1); + swept.resize(std::strlen(swept.c_str())); + return swept; } size_t schemeLength(std::string_view uri) { size_t schemeEnd = 0; - if (!uri.empty() && isAsciiAlpha(uri[0])) { + if (!uri.empty() && (isAsciiAlpha(uri[0]) || uri[0] == '+' || uri[0] == '-' || uri[0] == '.')) { schemeEnd = 1; while (schemeEnd < uri.size() && (isAsciiAlpha(uri[schemeEnd]) || isAsciiDigit(uri[schemeEnd]) || uri[schemeEnd] == '+' || uri[schemeEnd] == '-' || uri[schemeEnd] == '.')) ++schemeEnd; @@ -109,49 +102,51 @@ int parseUri(std::string_view uri, UriParts& parts) { parts.opaque = !uri.starts_with("//"); if (!parts.opaque) uri.remove_prefix(2); - std::string_view authority = uri.substr(0, uri.find_first_of("/?#")); - uri.remove_prefix(authority.size()); - - const size_t at = authority.rfind('@'); - if (at != std::string_view::npos) { - const std::string_view userinfo = authority.substr(0, at); + size_t userinfoEnd = 0; + while (userinfoEnd < uri.size() && isUserinfoCharacter(uri[userinfoEnd])) ++userinfoEnd; + if (userinfoEnd < uri.size() && uri[userinfoEnd] == '@') { + const std::string_view userinfo = uri.substr(0, userinfoEnd); const size_t colon = userinfo.find(':'); parts.username = userinfo.substr(0, colon); if (colon != std::string_view::npos) parts.password = userinfo.substr(colon + 1); - authority.remove_prefix(at + 1); + uri.remove_prefix(userinfoEnd + 1); } - std::string_view port; - if (authority.starts_with('[')) { - const size_t close = authority.find(']'); + if (uri.starts_with('[')) { + const size_t close = uri.find(']'); if (close == std::string_view::npos) return ERROR_INVALID_URL; - parts.hostname = authority.substr(1, close - 1); - authority.remove_prefix(close + 1); - if (!authority.empty() && authority[0] != ':') return ERROR_INVALID_URL; - if (!authority.empty()) port = authority.substr(1); + parts.hostname = uri.substr(1, close - 1); for (const char c : parts.hostname) { if (!isHostCharacter(c) && c != ':') return ERROR_INVALID_URL; } + uri.remove_prefix(close + 1); } else { - const size_t colon = authority.find(':'); - parts.hostname = authority.substr(0, colon); - if (colon != std::string_view::npos) port = authority.substr(colon + 1); - for (const char c : parts.hostname) { - if (!isHostCharacter(c)) return ERROR_INVALID_URL; + size_t hostEnd = 0; + if (!uri.starts_with('.')) { + while (hostEnd < uri.size() && isHostCharacter(uri[hostEnd])) ++hostEnd; } + parts.hostname = uri.substr(0, hostEnd); + uri.remove_prefix(hostEnd); } - if (port.size() > 5) return ERROR_INVALID_URL; - uint32_t portValue = 0; - for (const char c : port) { - if (!isAsciiDigit(c)) return ERROR_INVALID_URL; - portValue = portValue * 10 + static_cast(c - '0'); + parts.port = defaultPort(parts.scheme); + if (uri.starts_with(':')) { + uri.remove_prefix(1); + const std::string_view port = uri.substr(0, uri.find('/')); + uri.remove_prefix(port.size()); + if (port.size() > 5) return ERROR_INVALID_URL; + uint32_t portValue = 0; + for (const char c : port) { + if (!isAsciiDigit(c)) return ERROR_INVALID_URL; + portValue = portValue * 10 + static_cast(c - '0'); + } + if (portValue > 0x10000) return ERROR_INVALID_URL; + if (!port.empty()) parts.port = static_cast(portValue); } - if (portValue > 0xFFFF) return ERROR_INVALID_URL; - parts.port = port.empty() ? defaultPort(parts.scheme) : static_cast(portValue); const std::string_view path = uri.substr(0, uri.find_first_of("?#")); - parts.path = removeDotSegments(path); + parts.path = sweptPath(path); + parts.pathSpace = path.size(); uri.remove_prefix(path.size()); if (uri.starts_with('?')) { @@ -163,8 +158,8 @@ int parseUri(std::string_view uri, UriParts& parts) { } size_t poolSize(const UriParts& parts) { - const std::string_view fields[] = {parts.scheme, parts.username, parts.password, parts.hostname, parts.path, parts.query, parts.fragment}; - size_t size = 0; + const std::string_view fields[] = {parts.scheme, parts.username, parts.password, parts.hostname, parts.query, parts.fragment}; + size_t size = parts.pathSpace + 1; for (const std::string_view field : fields) size += field.size() + 1; return size; } @@ -181,31 +176,42 @@ int copyOut(const std::string& value, char* out, size_t* require, size_t prepare extern "C" { -int APS5_VABI sceHttpUriBuild(char* out, size_t* require, size_t prepare, const SceHttpUriElement* src_element, uint32_t option) { - if (!src_element) return ERROR_INVALID_URL; - if (!out && !require) return ERROR_INVALID_VALUE; +int APS5_VABI sceHttpUriBuild(char* out, size_t* requiredSize, size_t capacity, const SceHttpUriElement* element, uint32_t option) { + if (!element) return ERROR_INVALID_URL; + if (!out && !requiredSize) return ERROR_INVALID_VALUE; - auto field = [](const char* value) { return value ? std::string_view(value) : std::string_view(); }; - const std::string_view scheme = field(src_element->scheme); - const std::string_view username = option & URI_BUILD_WITH_USERNAME ? field(src_element->username) : std::string_view(); - const std::string_view password = option & URI_BUILD_WITH_PASSWORD ? field(src_element->password) : std::string_view(); - const std::string_view hostname = option & URI_BUILD_WITH_HOSTNAME ? field(src_element->hostname) : std::string_view(); + const auto field = [](const char* value, size_t limit) { + size_t length = 0; + if (value) while (length < limit && value[length] != '\0') ++length; + return value ? std::string_view(value, length) : std::string_view(); + }; + const auto scheme = option & (URI_BUILD_WITH_SCHEME | URI_BUILD_WITH_PORT) ? field(element->scheme, 32) : std::string_view(); + const auto username = option & URI_BUILD_WITH_USERNAME ? field(element->username, 256) : std::string_view(); + const auto password = option & URI_BUILD_WITH_PASSWORD ? field(element->password, 256) : std::string_view(); + const auto hostname = option & URI_BUILD_WITH_HOSTNAME ? field(element->hostname, 255) : std::string_view(); std::string uri; if (option & URI_BUILD_WITH_SCHEME && !scheme.empty()) uri.append(scheme).push_back(':'); - if (!src_element->opaque) uri.append("//"); + if (element->opaque != 1) uri.append("//"); uri.append(username); if (!password.empty()) uri.append(":").append(password); if (!username.empty() || !password.empty()) uri.push_back('@'); - if (hostname.find(':') != std::string_view::npos) uri.append("[").append(hostname).append("]"); - else uri.append(hostname); - if (option & URI_BUILD_WITH_PORT && src_element->port != 0 && src_element->port != defaultPort(scheme)) { - uri.append(":").append(std::to_string(src_element->port)); + uri.append(hostname); + if (option & URI_BUILD_WITH_PORT && element->port != 0) { + std::uint16_t port = 0; + if (scheme.size() >= 5 && equalsIgnoreCase(scheme.substr(0, 5), "https")) port = 443; + else if ((scheme.size() >= 4 && equalsIgnoreCase(scheme.substr(0, 4), "http")) || (scheme.size() >= 3 && equalsIgnoreCase(scheme.substr(0, 3), "ttp"))) port = 80; + if (element->port != port) uri.append(":").append(std::to_string(element->port)); } - if (option & URI_BUILD_WITH_PATH) uri.append(field(src_element->path)); - if (option & URI_BUILD_WITH_QUERY) uri.append(field(src_element->query)); - if (option & URI_BUILD_WITH_FRAGMENT) uri.append(field(src_element->fragment)); - return copyOut(uri, out, require, prepare); + if (option & URI_BUILD_WITH_PATH) uri.append(field(element->path, URI_MAX_LENGTH)); + if (option & URI_BUILD_WITH_QUERY) uri.append(field(element->query, URI_MAX_LENGTH)); + if (option & URI_BUILD_WITH_FRAGMENT) uri.append(field(element->fragment, URI_MAX_LENGTH)); + const size_t size = uri.size() + 1 + ((option & URI_BUILD_WITH_SCHEME) && element->scheme && scheme.empty()); + if (requiredSize) *requiredSize = size; + if (!out) return 0; + if (capacity < size) return ERROR_OUT_OF_MEMORY; + std::memcpy(out, uri.c_str(), uri.size() + 1); + return 0; } int APS5_VABI sceHttpUriEscape(char* out, size_t* require, size_t prepare, const char* in) { @@ -228,34 +234,56 @@ int APS5_VABI sceHttpUriEscape(char* out, size_t* require, size_t prepare, const return copyOut(escaped, out, require, prepare); } -int APS5_VABI sceHttpUriUnescape(char* out, size_t* require, size_t prepare, const char* in) { - if (!in || (!out && !require)) return ERROR_INVALID_VALUE; +int APS5_VABI sceHttpUriUnescape(char* out, size_t* requiredSize, size_t capacity, const char* input) { + if (!input || (!out && !requiredSize)) return ERROR_INVALID_VALUE; - const auto hexDigit = [](char c) -> int { - 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 -1; + const std::string_view source(input); + const auto decode = [](const char* digits, unsigned int& value) { + const auto result = std::from_chars(digits, digits + 2, value, 16); + return result.ec == std::errc{} && result.ptr == digits + 2; }; - const std::string_view input(in); - std::string decoded; - for (size_t i = 0; i < input.size(); ++i) { - if (input[i] != '%') { - decoded.push_back(input[i]); - continue; + for (size_t index = 0; index < source.size(); ++index) { + if (source[index] != '%') continue; + unsigned int value = 0; + if (source.size() - index < 3 || !decode(source.data() + index + 1, value)) return ERROR_INVALID_URL; + index += 2; + } + + if (!source.empty()) { + std::uint32_t sdkVersion = 0; + const auto* parameters = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::memcpy(&sdkVersion, parameters + 0x10, sizeof(sdkVersion)); + if (sdkVersion < 0x1000000) { + for (size_t index = 0; index < source.size(); ++index) { + const char value = source[index]; + if (value == '%') { + index += 2; + continue; + } + if (!isAsciiAlpha(value) && !isAsciiDigit(value) && value != '*' && value != '-' && value != '.' && value != '_' && value != '+') return ERROR_INVALID_URL; + } } - if (input.size() - i < 3) return ERROR_INVALID_URL; - const int high = hexDigit(input[i + 1]); - const int low = hexDigit(input[i + 2]); - if (high < 0 || low < 0) return ERROR_INVALID_URL; - decoded.push_back(static_cast(high * 16 + low)); - i += 2; } - if (out && prepare < decoded.size() + 1) { - std::memcpy(out, decoded.data(), std::min(prepare, decoded.size())); - return ERROR_OUT_OF_MEMORY; + + size_t written = 0; + for (size_t index = 0; index < source.size(); ++index) { + if (out && written >= capacity) return ERROR_OUT_OF_MEMORY; + char value = source[index]; + if (value == '%') { + unsigned int decoded = 0; + decode(source.data() + index + 1, decoded); + value = static_cast(decoded); + index += 2; + } + if (out) out[written] = value; + ++written; } - return copyOut(decoded, out, require, prepare); + if (out) { + if (written >= capacity) return ERROR_OUT_OF_MEMORY; + out[written] = '\0'; + } + if (requiredSize) *requiredSize = written + 1; + return 0; } int APS5_VABI sceHttpUriMerge(char* merged_url, const char* url, const char* relative_uri, size_t* require, size_t prepare, uint32_t option) { @@ -311,10 +339,12 @@ int APS5_VABI sceHttpUriParse(SceHttpUriElement* out, const char* src_url, void* if (const int result = parseUri(src_url, parts); result != 0) return result; const std::string_view fields[] = {parts.scheme, parts.username, parts.password, parts.hostname, parts.path, parts.query, parts.fragment}; + const size_t spaces[] = {parts.scheme.size(), parts.username.size(), parts.password.size(), parts.hostname.size(), parts.pathSpace, parts.query.size(), + parts.fragment.size()}; const size_t size = poolSize(parts); + if (write && prepare < size) return ERROR_OUT_OF_MEMORY; if (require) *require = size; if (!write) return 0; - if (prepare < size) return ERROR_OUT_OF_MEMORY; char* next = static_cast(pool); char* strings[7]; @@ -322,7 +352,7 @@ int APS5_VABI sceHttpUriParse(SceHttpUriElement* out, const char* src_url, void* strings[i] = next; if (!fields[i].empty()) std::memcpy(next, fields[i].data(), fields[i].size()); next[fields[i].size()] = '\0'; - next += fields[i].size() + 1; + next += spaces[i] + 1; } *out = {}; out->opaque = parts.opaque; @@ -342,31 +372,38 @@ int APS5_VABI sceHttpUriCopy(SceHttpUriElement* out, const SceHttpUriElement* sr const bool write = out && pool; if (!write && !require) return ERROR_INVALID_VALUE; + if (write) { + std::memset(out, 0, sizeof(*out)); + std::memset(pool, 0, prepare); + } const char* const fields[] = {src->scheme, src->hostname, src->path, src->username, src->password}; - SceHttpUriElement copy{}; - char** const slots[] = {©.scheme, ©.hostname, ©.path, ©.username, ©.password}; - if (write) std::memset(pool, 0, prepare); + const uint16_t port = src->port; + size_t lengths[5]; size_t used = 0; for (size_t i = 0; i < 5; ++i) { - const size_t length = fields[i] ? std::strlen(fields[i]) : 0; - const size_t needed = length + 1; - if (write) { - if (needed > prepare - used) { - *out = {}; - std::memset(pool, 0, prepare); - return ERROR_OUT_OF_MEMORY; - } - char* next = static_cast(pool) + used; - if (length != 0) std::memcpy(next, fields[i], length); - next[length] = '\0'; - *slots[i] = next; - } - used += needed; + lengths[i] = fields[i] ? std::strlen(fields[i]) : 0; + used += lengths[i] + 1; } - if (write) { - copy.port = src->port; - *out = copy; + if (!write) { + *require = used; + return 0; } + if (used > prepare) return ERROR_OUT_OF_MEMORY; + + char* next = static_cast(pool); + char* strings[5]; + for (size_t i = 0; i < 5; ++i) { + strings[i] = next; + if (lengths[i] != 0) std::memcpy(next, fields[i], lengths[i]); + next[lengths[i]] = '\0'; + next += lengths[i] + 1; + } + out->scheme = strings[0]; + out->hostname = strings[1]; + out->path = strings[2]; + out->username = strings[3]; + out->password = strings[4]; + out->port = port; if (require) *require = used; return 0; } diff --git a/core/libs/prx/libSceHttp2/Export.cpp b/core/libs/prx/libSceHttp2/Export.cpp index 3d681f4ede..7cb14fc27a 100644 --- a/core/libs/prx/libSceHttp2/Export.cpp +++ b/core/libs/prx/libSceHttp2/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHttp2/include/Http2.hpp" #include #include #include @@ -7,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -23,6 +26,7 @@ struct Http2Handle { Http2Kind kind; int owner; std::size_t poolSize; + std::shared_ptr cookieMemory; }; static std::atomic g_nextHandle{1}; @@ -59,13 +63,6 @@ static bool HandleIsLive(int id) { return FindAnyHandle(id) != nullptr; } -struct Http2MemoryPoolStats { - size_t pool_size; - size_t max_inuse_size; - size_t current_inuse_size; - int32_t reserved; -}; - static std::mutex g_completionsMutex; static std::unordered_map> g_completions; @@ -348,11 +345,14 @@ int APS5_VABI sceHttp2CookieFlush(int id) { return 0; } -int APS5_VABI sceHttp2CreateCookieBox(int lib_http2_ctx_id) { +int APS5_VABI sceHttp2CreateCookieBox(std::size_t poolSize) { + if (poolSize < 0x10000) return static_cast(0x817b121du); + if (poolSize > std::numeric_limits::max() - 0x3fff) throw std::overflow_error("sceHttp2CreateCookieBox: pool size overflow"); + const auto allocationSize = (poolSize + 0x3fff) & ~std::size_t{0x3fff}; + auto memory = std::make_shared(allocationSize); std::lock_guard lock(g_handlesMutex); - if (FindHandle(lib_http2_ctx_id, Http2Kind::Context) == nullptr) return ERROR_INVALID_ID; const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); - g_handles[id] = {Http2Kind::CookieBox, lib_http2_ctx_id, 0}; + g_handles[id] = {Http2Kind::CookieBox, 0, poolSize, std::move(memory)}; return id; } @@ -374,39 +374,38 @@ int APS5_VABI sceHttp2SetRequestNoContentLength(int id) { return 0; } - int APS5_VABI sceHttp2SetResolveRetry(int id, int32_t retry) { (void)id; (void)retry; return 0; } -int APS5_VABI sceHttp2WebSocketCreateRequest() { +int APS5_VABI sceHttp2WebSocketCreateRequest(std::int32_t templateId, const char* url, Http2WebSocketTextCallback textCallback, Http2WebSocketDataCallback dataCallback, Http2WebSocketCloseCallback closeCallback, Http2WebSocketErrorCallback errorCallback, void* receiveBuffer, std::size_t receiveBufferSize, void* userData, const void* option) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttp2WebSocketCloseAsync() { +int APS5_VABI sceHttp2WebSocketCloseAsync(std::int32_t requestId, const std::uint16_t* status, const char* reason, std::uint8_t flags, const Http2AsyncOption* asyncOption, const void* option) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttp2WebSocketSendTextMessageAsync() { +int APS5_VABI sceHttp2WebSocketSendTextMessageAsync(std::int32_t requestId, const char* text, std::uint8_t flags, const Http2AsyncOption* asyncOption, const void* option) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttp2WebSocketSendDataMessageAsync() { +int APS5_VABI sceHttp2WebSocketSendDataMessageAsync(std::int32_t requestId, const void* data, std::size_t dataSize, std::uint8_t flags, const Http2AsyncOption* asyncOption, const void* option) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttp2WebSocketSetPingInterval() { +int APS5_VABI sceHttp2WebSocketSetPingInterval(std::int32_t requestId, std::uint32_t intervalMicroseconds) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttp2WebSocketSetPingTimeout() { +int APS5_VABI sceHttp2WebSocketSetPingTimeout(std::int32_t requestId, std::uint32_t timeoutMicroseconds) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceHttp2/include/Http2.hpp b/core/libs/prx/libSceHttp2/include/Http2.hpp new file mode 100644 index 0000000000..bf7b34e6a7 --- /dev/null +++ b/core/libs/prx/libSceHttp2/include/Http2.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "SceTypes.hpp" + +struct Http2MemoryPoolStats { + size_t pool_size; + size_t max_inuse_size; + size_t current_inuse_size; + int32_t reserved; +}; + +using Http2WebSocketTextCallback = void (APS5_VABI*)(std::int32_t requestId, const char* text, std::uint32_t flags, void* userData); + +using Http2WebSocketDataCallback = void (APS5_VABI*)(std::int32_t requestId, const void* data, std::size_t dataSize, std::uint32_t flags, void* userData); + +using Http2WebSocketCloseCallback = void (APS5_VABI*)(std::int32_t requestId, const std::uint16_t* status, const char* reason, std::uint32_t flags, void* userData); + +using Http2WebSocketErrorCallback = void (APS5_VABI*)(std::int32_t requestId, std::int32_t error, void* userData); + +extern "C" { + +int APS5_VABI sceHttp2AddRequestHeader(int id, const char* name, const char* value, uint32_t mode); +int APS5_VABI sceHttp2CreateRequestWithURL(int tmplId, const char* method, const char* url, uint64_t contentLength); +int APS5_VABI sceHttp2CreateTemplate(int libHttp2CtxId, const char* userAgent, int httpVer, int isAutoProxyConf); +int APS5_VABI sceHttp2DeleteRequest(int reqId); +int APS5_VABI sceHttp2DeleteTemplate(int tmplId); +int APS5_VABI sceHttp2GetAllResponseHeaders(int reqId, char** header, size_t* headerSize); +int APS5_VABI sceHttp2GetResponseContentLength(int reqId, int* result, uint64_t* contentLength); +int APS5_VABI sceHttp2GetStatusCode(int reqId, int* statusCode); +int APS5_VABI sceHttp2Init(int libnetMemId, int libsslCtxId, size_t poolSize, int maxConcurrentRequest); +int APS5_VABI sceHttp2ReadData(int reqId, void* data, size_t size); +int APS5_VABI sceHttp2ReadDataAsync(int reqId, void* data, size_t size, void* kqueueOption, void* option); +int APS5_VABI sceHttp2SendRequest(int reqId, const void* postData, size_t size); +int APS5_VABI sceHttp2SendRequestAsync(int reqId, const void* postData, size_t size, Http2AsyncOption* kqueueOption, void* option); +int APS5_VABI sceHttp2SetAuthEnabled(int id, int isEnable); +int APS5_VABI sceHttp2SetAutoRedirect(int id, int enable); +int APS5_VABI sceHttp2SetConnectionWaitTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttp2SetConnectTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttp2SetInflateGZIPEnabled(int id, int enable); +int APS5_VABI sceHttp2SetRecvTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttp2SetRedirectCallback(int id, void* cbFunc, void* userArg); +int APS5_VABI sceHttp2SetRequestContentLength(int id, uint64_t contentLength); +int APS5_VABI sceHttp2SetResolveTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttp2SetSendTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttp2SetSslCallback(int id, void* cbFunc, void* userArg); +int APS5_VABI sceHttp2SetTimeOut(int id, uint32_t usec); +int APS5_VABI sceHttp2SslDisableOption(int id, uint32_t sslFlags); +int APS5_VABI sceHttp2SslEnableOption(int id, uint32_t sslFlags); +int APS5_VABI sceHttp2SetMinSslVersion(int id, uint32_t sslVersion); +int APS5_VABI sceHttp2Term(int libHttp2CtxId); +int APS5_VABI sceHttp2WaitAsync(int reqId, Http2AsyncResult* result, uint32_t* timeout, void* option); +int APS5_VABI sceHttp2AbortRequest(int reqId); +int APS5_VABI sceHttp2GetMemoryPoolStats(int libHttp2CtxId, Http2MemoryPoolStats* stats); +int APS5_VABI sceHttp2CookieFlush(int id); +int APS5_VABI sceHttp2CreateCookieBox(std::size_t poolSize); +int APS5_VABI sceHttp2DeleteCookieBox(int cookieBoxId); +int APS5_VABI sceHttp2SetCookieBox(int id, int cookieBoxId); +int APS5_VABI sceHttp2SetRequestNoContentLength(int id); +int APS5_VABI sceHttp2SetResolveRetry(int id, int32_t retry); +int APS5_VABI sceHttp2WebSocketCreateRequest(std::int32_t templateId, const char* url, Http2WebSocketTextCallback textCallback, Http2WebSocketDataCallback dataCallback, Http2WebSocketCloseCallback closeCallback, Http2WebSocketErrorCallback errorCallback, void* receiveBuffer, std::size_t receiveBufferSize, void* userData, const void* option); +int APS5_VABI sceHttp2WebSocketCloseAsync(std::int32_t requestId, const std::uint16_t* status, const char* reason, std::uint8_t flags, const Http2AsyncOption* asyncOption, const void* option); +int APS5_VABI sceHttp2WebSocketSendTextMessageAsync(std::int32_t requestId, const char* text, std::uint8_t flags, const Http2AsyncOption* asyncOption, const void* option); +int APS5_VABI sceHttp2WebSocketSendDataMessageAsync(std::int32_t requestId, const void* data, std::size_t dataSize, std::uint8_t flags, const Http2AsyncOption* asyncOption, const void* option); +int APS5_VABI sceHttp2WebSocketSetPingInterval(std::int32_t requestId, std::uint32_t intervalMicroseconds); +int APS5_VABI sceHttp2WebSocketSetPingTimeout(std::int32_t requestId, std::uint32_t timeoutMicroseconds); +int APS5_VABI sceHttp2AddCookie(void); +int APS5_VABI sceHttp2AuthCacheFlush(void); +int APS5_VABI sceHttp2CookieExport(void); +int APS5_VABI sceHttp2CookieImport(void); +int APS5_VABI sceHttp2GetAuthEnabled(void); +int APS5_VABI sceHttp2GetAutoRedirect(void); +int APS5_VABI sceHttp2GetCookie(void); +int APS5_VABI sceHttp2GetCookieBox(void); +int APS5_VABI sceHttp2GetCookieStats(void); +int APS5_VABI sceHttp2RedirectCacheFlush(void); +int APS5_VABI sceHttp2RemoveRequestHeader(void); +int APS5_VABI sceHttp2SetAuthInfoCallback(void); +int APS5_VABI sceHttp2SetCookieMaxNum(void); +int APS5_VABI sceHttp2SetCookieMaxNumPerDomain(void); +int APS5_VABI sceHttp2SetCookieMaxSize(void); +int APS5_VABI sceHttp2SetCookieRecvCallback(void); +int APS5_VABI sceHttp2SetCookieSendCallback(void); +int APS5_VABI sceHttp2SetPreSendCallback(void); + +} diff --git a/core/libs/prx/libSceHttp2/libSceHttp2.hpp b/core/libs/prx/libSceHttp2/libSceHttp2.hpp new file mode 100644 index 0000000000..d6ec538a09 --- /dev/null +++ b/core/libs/prx/libSceHttp2/libSceHttp2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceHttp2/include/Http2.hpp" diff --git a/core/libs/prx/libSceHttpCache/Export.cpp b/core/libs/prx/libSceHttpCache/Export.cpp index 6f5d37ccb1..b7f0f3ef35 100644 --- a/core/libs/prx/libSceHttpCache/Export.cpp +++ b/core/libs/prx/libSceHttpCache/Export.cpp @@ -1,96 +1,108 @@ +#include "prx/libSceHttpCache/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +static_assert(sizeof(HttpCacheHeader) == 16); +static_assert(sizeof(HttpCacheRequest) == 32); +static_assert(offsetof(HttpCacheRequest, url) == 8); +static_assert(offsetof(HttpCacheRequest, headers) == 24); +static_assert(sizeof(HttpCacheResponseHeader) == 24); +static_assert(offsetof(HttpCacheResponseHeader, headers) == 16); +static_assert(sizeof(HttpCacheResponse) == 32); +static_assert(offsetof(HttpCacheResponse, contentLength) == 24); +static_assert(sizeof(HttpCacheQuotaEntry) == 16); +static_assert(sizeof(HttpCacheQuota) == 16); + extern "C" { -int APS5_VABI sceHttpCacheClear() { +int APS5_VABI sceHttpCacheClear(std::int32_t cacheId, std::int32_t userId, const char* tag) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheClearAll() { +int APS5_VABI sceHttpCacheClearAll(std::int32_t cacheId, std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheCompleteRequest() { +int APS5_VABI sceHttpCacheCompleteRequest(std::int32_t requestId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheCreateRequest() { +int APS5_VABI sceHttpCacheCreateRequest(std::int32_t cacheId, std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheCreateRequestWithTag() { +int APS5_VABI sceHttpCacheCreateRequestWithTag(std::int32_t cacheId, std::int32_t userId, const char* tag) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheDeleteRequest() { +int APS5_VABI sceHttpCacheDeleteRequest(std::int32_t requestId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheInit() { +int APS5_VABI sceHttpCacheInit(std::size_t poolSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheReadData() { +int APS5_VABI sceHttpCacheReadData(std::int32_t requestId, std::size_t size, void* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheRetrieve() { +int APS5_VABI sceHttpCacheRetrieve(std::int32_t requestId, const HttpCacheRequest* request, HttpCacheResponse* response, HttpCacheRequest* nextRequest, std::int32_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheRetrieveWithMemoryPool() { +int APS5_VABI sceHttpCacheRetrieveWithMemoryPool(std::int32_t requestId, const HttpCacheRequest* request, HttpCacheResponse* response, HttpCacheRequest* nextRequest, std::int32_t* result, void* pool, std::size_t* requiredSize, std::size_t poolSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheRevalidate() { +int APS5_VABI sceHttpCacheRevalidate(std::int32_t requestId, const HttpCacheResponseHeader* header, HttpCacheResponse* response, std::int32_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheRevalidateWithMemoryPool() { +int APS5_VABI sceHttpCacheRevalidateWithMemoryPool(std::int32_t requestId, const HttpCacheResponseHeader* header, HttpCacheResponse* response, std::int32_t* result, void* pool, std::size_t* requiredSize, std::size_t poolSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheSetCacheSharing() { +int APS5_VABI sceHttpCacheSetCacheSharing(std::uint8_t enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheSetData() { +int APS5_VABI sceHttpCacheSetData(std::int32_t requestId, const void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheSetQuota() { +int APS5_VABI sceHttpCacheSetQuota(std::int32_t cacheId, const HttpCacheQuota* quota) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheSetResponseHeader() { +int APS5_VABI sceHttpCacheSetResponseHeader(std::int32_t requestId, const HttpCacheResponseHeader* header) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheSystemClearAll() { +int APS5_VABI sceHttpCacheSystemClearAll(const char* path) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceHttpCacheSystemInit() { +int APS5_VABI sceHttpCacheSystemInit(const char* path) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -108,12 +120,11 @@ int APS5_VABI sceHttpCacheSystemTerm() { return 0; } -int APS5_VABI sceHttpCacheTerm() { +int APS5_VABI sceHttpCacheTerm(std::int32_t cacheId) { NotImplemented_nid_no_patch(__func__); return 0; } - int APS5_VABI sceHttpCacheClearAllUserTags() { NotImplemented_nid_no_patch(__func__); return 0; @@ -125,7 +136,6 @@ int APS5_VABI sceHttpCacheClearCacheInfo() { } int APS5_VABI sceHttpCacheDebugSetUrl() { - NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceHttpCache/include/Api.hpp b/core/libs/prx/libSceHttpCache/include/Api.hpp new file mode 100644 index 0000000000..634bf42348 --- /dev/null +++ b/core/libs/prx/libSceHttpCache/include/Api.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include + +struct HttpCacheHeader { + const char* name; + const char* value; +}; + +struct HttpCacheRequest { + std::uint32_t method; + std::uint32_t reserved; + const char* url; + std::size_t headerCount; + const HttpCacheHeader* headers; +}; + +struct HttpCacheResponseHeader { + std::uint32_t statusCode; + std::uint32_t reserved; + std::size_t headerCount; + const HttpCacheHeader* headers; +}; + +struct HttpCacheResponse { + std::uint32_t statusCode; + std::uint32_t reserved; + std::size_t headerCount; + const HttpCacheHeader* headers; + std::uint64_t contentLength; +}; + +struct HttpCacheQuotaEntry { + const char* name; + std::uint64_t size; +}; + +struct HttpCacheQuota { + std::size_t count; + const HttpCacheQuotaEntry* entries; +}; + +extern "C" { + +int APS5_VABI sceHttpCacheClear(std::int32_t cacheId, std::int32_t userId, const char* tag); +int APS5_VABI sceHttpCacheClearAll(std::int32_t cacheId, std::int32_t userId); +int APS5_VABI sceHttpCacheCompleteRequest(std::int32_t requestId); +int APS5_VABI sceHttpCacheCreateRequest(std::int32_t cacheId, std::int32_t userId); +int APS5_VABI sceHttpCacheCreateRequestWithTag(std::int32_t cacheId, std::int32_t userId, const char* tag); +int APS5_VABI sceHttpCacheDeleteRequest(std::int32_t requestId); +int APS5_VABI sceHttpCacheInit(std::size_t poolSize); +int APS5_VABI sceHttpCacheReadData(std::int32_t requestId, std::size_t size, void* data); +int APS5_VABI sceHttpCacheRetrieve(std::int32_t requestId, const HttpCacheRequest* request, HttpCacheResponse* response, HttpCacheRequest* nextRequest, std::int32_t* result); +int APS5_VABI sceHttpCacheRetrieveWithMemoryPool(std::int32_t requestId, const HttpCacheRequest* request, HttpCacheResponse* response, HttpCacheRequest* nextRequest, std::int32_t* result, void* pool, std::size_t* requiredSize, std::size_t poolSize); +int APS5_VABI sceHttpCacheRevalidate(std::int32_t requestId, const HttpCacheResponseHeader* header, HttpCacheResponse* response, std::int32_t* result); +int APS5_VABI sceHttpCacheRevalidateWithMemoryPool(std::int32_t requestId, const HttpCacheResponseHeader* header, HttpCacheResponse* response, std::int32_t* result, void* pool, std::size_t* requiredSize, std::size_t poolSize); +int APS5_VABI sceHttpCacheSetCacheSharing(std::uint8_t enabled); +int APS5_VABI sceHttpCacheSetData(std::int32_t requestId, const void* data, std::size_t size); +int APS5_VABI sceHttpCacheSetQuota(std::int32_t cacheId, const HttpCacheQuota* quota); +int APS5_VABI sceHttpCacheSetResponseHeader(std::int32_t requestId, const HttpCacheResponseHeader* header); +int APS5_VABI sceHttpCacheSystemClearAll(const char* path); +int APS5_VABI sceHttpCacheSystemInit(const char* path); +int APS5_VABI sceHttpCacheSystemSendStatistics(); +int APS5_VABI sceHttpCacheSystemShutdown(); +int APS5_VABI sceHttpCacheSystemTerm(); +int APS5_VABI sceHttpCacheTerm(std::int32_t cacheId); +int APS5_VABI sceHttpCacheClearAllUserTags(); +int APS5_VABI sceHttpCacheClearCacheInfo(); +int APS5_VABI sceHttpCacheDebugSetUrl(); +int APS5_VABI sceHttpCacheRegisterCacheInfoUrl(); +int APS5_VABI sceHttpCacheRemoveTag(); + +} diff --git a/core/libs/prx/libSceHttpCache/libSceHttpCache.hpp b/core/libs/prx/libSceHttpCache/libSceHttpCache.hpp new file mode 100644 index 0000000000..1a210e0748 --- /dev/null +++ b/core/libs/prx/libSceHttpCache/libSceHttpCache.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceHttpCache/include/Api.hpp" diff --git a/core/libs/prx/libSceHubAppUtil/Export.cpp b/core/libs/prx/libSceHubAppUtil/Export.cpp index b4a5ae963d..2ed0d86fca 100644 --- a/core/libs/prx/libSceHubAppUtil/Export.cpp +++ b/core/libs/prx/libSceHubAppUtil/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHubAppUtil/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceHubAppUtil/include/Api.hpp b/core/libs/prx/libSceHubAppUtil/include/Api.hpp new file mode 100644 index 0000000000..721784ffcb --- /dev/null +++ b/core/libs/prx/libSceHubAppUtil/include/Api.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceHubAppUtilDeeplinkToLiveDetailView(); +int APS5_VABI sceHubAppUtilDeeplinkToPSSubscriptions(); +int APS5_VABI sceHubAppUtilGetInstallationStatus(); + +} diff --git a/core/libs/prx/libSceHubAppUtil/libSceHubAppUtil.hpp b/core/libs/prx/libSceHubAppUtil/libSceHubAppUtil.hpp new file mode 100644 index 0000000000..6636d9db00 --- /dev/null +++ b/core/libs/prx/libSceHubAppUtil/libSceHubAppUtil.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceHubAppUtil/include/Api.hpp" diff --git a/core/libs/prx/libSceIcu/libSceIcu.hpp b/core/libs/prx/libSceIcu/libSceIcu.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceIcu/libSceIcu.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceIdu/Export.cpp b/core/libs/prx/libSceIdu/Export.cpp index def1b88f9a..753cc6aa8b 100644 --- a/core/libs/prx/libSceIdu/Export.cpp +++ b/core/libs/prx/libSceIdu/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceIdu/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceIdu/include/Api.hpp b/core/libs/prx/libSceIdu/include/Api.hpp new file mode 100644 index 0000000000..4e9d5daaaa --- /dev/null +++ b/core/libs/prx/libSceIdu/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceIduGetEnv(); +int APS5_VABI sceIduInit(); +int APS5_VABI sceIduInstallPkg(); +int APS5_VABI sceIduIsAppInstalled(); +int APS5_VABI sceIduIsRifInstalled(); +int APS5_VABI sceIduIsStaffMode(); +int APS5_VABI sceIduReboot(); +int APS5_VABI sceIduTerm(); + +} diff --git a/core/libs/prx/libSceIdu/libSceIdu.hpp b/core/libs/prx/libSceIdu/libSceIdu.hpp new file mode 100644 index 0000000000..b899940205 --- /dev/null +++ b/core/libs/prx/libSceIdu/libSceIdu.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceIdu/include/Api.hpp" diff --git a/core/libs/prx/libSceImageUtil/libSceImageUtil.hpp b/core/libs/prx/libSceImageUtil/libSceImageUtil.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceImageUtil/libSceImageUtil.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceIme/Export.cpp b/core/libs/prx/libSceIme/Export.cpp index 6eaf4cbbe8..a6c3a3d181 100644 --- a/core/libs/prx/libSceIme/Export.cpp +++ b/core/libs/prx/libSceIme/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceIme/include/Api.hpp" #include #include #include @@ -15,6 +16,11 @@ #include "prx/libSceIme/include/ImeFilterText.hpp" #include "prx/libSceIme/include/ImeInput.hpp" +static_assert(sizeof(ImeEditText) == 64); +static_assert(offsetof(ImeEvent, param) == 8); +static_assert(sizeof(ImeVshCaretGeometry) == 16); +static_assert(sizeof(ImeVshTextGeometry) == 16); + namespace { constexpr int ErrorBusy = static_cast(0x80bc0001u); @@ -495,7 +501,7 @@ int APS5_VABI sceImeSetCaret(const Caret* caret) { return 0; } -int APS5_VABI sceImeSetConsecutiveClauseConversionEnabled(bool enabled) { +int APS5_VABI sceImeSetConsecutiveClauseConversionEnabled(std::uint8_t enabled) { (void)enabled; NotImplemented_nid_no_patch(__func__); return 0; @@ -581,77 +587,77 @@ int APS5_VABI sceImeUpdate(EventHandler handler) { } } -int APS5_VABI sceImeVshOpen(void) { +int APS5_VABI sceImeVshOpen(ImeVshContext** context, const ImeVshParameter* parameter, const char16_t* text) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeKeyboardUpdate(void) { +int APS5_VABI sceImeKeyboardUpdate(EventHandler handler) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshUpdate(void) { +int APS5_VABI sceImeVshUpdate(ImeVshContext* context) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshUpdateContext(void) { +int APS5_VABI sceImeVshUpdateContext(ImeVshContext* context, const char16_t* text, std::uint32_t caretIndex) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshClearPreedit(void) { +int APS5_VABI sceImeVshClearPreedit(ImeVshContext* context) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshNotifySetText(void) { +int APS5_VABI sceImeVshNotifySetText(ImeVshContext* context, const char16_t* text, std::uint32_t length) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshSetCaretGeometry(void) { +int APS5_VABI sceImeVshSetCaretGeometry(ImeVshContext* context, const ImeVshCaretGeometry* geometry) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshConfirmPreedit(void) { +int APS5_VABI sceImeVshConfirmPreedit(ImeVshContext* context) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshSetCaretIndexInPreedit(void) { +int APS5_VABI sceImeVshSetCaretIndexInPreedit(ImeVshContext* context, std::int32_t index) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshSetPreeditGeometry(void) { +int APS5_VABI sceImeVshSetPreeditGeometry(ImeVshContext* context, const ImeVshTextGeometry* geometry) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshSetSelectGeometry(void) { +int APS5_VABI sceImeVshSetSelectGeometry(ImeVshContext* context, const ImeVshTextGeometry* geometry) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshClose(void) { +int APS5_VABI sceImeVshClose(ImeVshContext* context) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshGetPanelPositionAndForm(void) { +int APS5_VABI sceImeVshGetPanelPositionAndForm(ImeVshContext* context, PositionAndForm* form) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeSetCandidateIndex(void) { +int APS5_VABI sceImeSetCandidateIndex(std::int32_t index) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeConfirmCandidate(void) { +int APS5_VABI sceImeConfirmCandidate(std::int32_t index) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -661,12 +667,12 @@ int APS5_VABI sceImeDisableController(void) { return 0; } -int APS5_VABI sceImeSetVoiceDictationEnabled(void) { +int APS5_VABI sceImeSetVoiceDictationEnabled(bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeSetEditEventWithRemotePlayEnabled(void) { +int APS5_VABI sceImeSetEditEventWithRemotePlayEnabled(bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -675,122 +681,122 @@ int APS5_VABI sceImeForTestFunction(void) { return ErrorInternal; } -int APS5_VABI sceImeVshInformConfirmdString2(void) { +int APS5_VABI sceImeVshInformConfirmdString2(ImeVshContext* context, const char16_t* text, std::int32_t firstParameter, std::int32_t secondParameter, std::int32_t thirdParameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshSetSelectionText(void) { +int APS5_VABI sceImeVshSetSelectionText(ImeVshContext* context, const char16_t* text, std::uint32_t length) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeCheckUpdateTextInfo(void) { +int APS5_VABI sceImeCheckUpdateTextInfo(ImeEditText* info, std::uint8_t repair) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeCheckRemoteEventParam(void) { +int APS5_VABI sceImeCheckRemoteEventParam(ImeEvent* event, std::uint8_t repair) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeKeyboardOpenInternal(void) { +int APS5_VABI sceImeKeyboardOpenInternal(std::int32_t userId, EventHandler handler, const KeyboardParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshSetParam(void) { +int APS5_VABI sceImeVshSetParam(ImeVshContext* context, const ImeVshParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshUpdateContext2(void) { +int APS5_VABI sceImeVshUpdateContext2(ImeVshContext* context, const char16_t* text, std::uint32_t caretIndex) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshSendTextInfo(void) { +int APS5_VABI sceImeVshSendTextInfo(ImeVshContext* context, const void* info, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshInformConfirmdString(void) { +int APS5_VABI sceImeVshInformConfirmdString(ImeVshContext* context, const char16_t* text, std::int32_t parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshNotifyCurrentTextInfo(void) { +int APS5_VABI sceImeVshNotifyCurrentTextInfo(ImeVshContext* context, const char16_t* text, std::int32_t caretIndex, std::int32_t preeditFirst, std::int32_t preeditSecond, std::int32_t selectionFirst, std::int32_t selectionSecond) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshNotifyCurrentTextInfoForReconvertText(void) { +int APS5_VABI sceImeVshNotifyCurrentTextInfoForReconvertText(ImeVshContext* context, const char16_t* text, std::int32_t caretIndex, std::int32_t preeditFirst, std::int32_t preeditSecond, std::int32_t selectionFirst, std::int32_t selectionSecond, std::int32_t option) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshNotifyDeleteText(void) { +int APS5_VABI sceImeVshNotifyDeleteText(ImeVshContext* context, const char16_t* text, std::int32_t length, std::int32_t parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshNotifyUpdateCaretText(void) { +int APS5_VABI sceImeVshNotifyUpdateCaretText(ImeVshContext* context, const char16_t* text, std::int32_t length, std::int32_t parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshCheckAutoReConvertingPreeditText(void) { +int APS5_VABI sceImeVshCheckAutoReConvertingPreeditText(ImeVshContext* context, const char16_t* text) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshSetPanelPosition(void) { +int APS5_VABI sceImeVshSetPanelPosition(ImeVshContext* context, float x, float y, std::int32_t horizontalAlignment, std::int32_t verticalAlignment) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeVshNotifyPreeditMetaInfo(void) { +int APS5_VABI sceImeVshNotifyPreeditMetaInfo(ImeVshContext* context, const ImePreeditMetaInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeConfigSet(void) { +int APS5_VABI sceImeConfigSet(std::uint64_t unusedArgument, std::uint64_t configuration, std::uint32_t key, std::uint32_t value) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeConfigGet(void) { +int APS5_VABI sceImeConfigGet(std::uint64_t unusedArgument, std::uint64_t configuration, std::uint32_t key) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeDicGetWords(void) { +int APS5_VABI sceImeDicGetWords(std::int32_t userId, std::uint64_t dictionary, ImeDictionaryWord* words) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeDicAddWord(void) { +int APS5_VABI sceImeDicAddWord(std::int32_t userId, std::uint64_t dictionary, const ImeDictionaryWord* word) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeDicDeleteWord(void) { +int APS5_VABI sceImeDicDeleteWord(std::int32_t userId, std::uint64_t dictionary, const ImeDictionaryWord* word) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeDicReplaceWord(void) { +int APS5_VABI sceImeDicReplaceWord(std::int32_t userId, std::uint64_t dictionary, const ImeDictionaryWord* oldWord, const ImeDictionaryWord* newWord) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeDicDeleteLearnDics(void) { +int APS5_VABI sceImeDicDeleteLearnDics(std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceImeDicDeleteUserDics(void) { +int APS5_VABI sceImeDicDeleteUserDics(std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceIme/include/Api.hpp b/core/libs/prx/libSceIme/include/Api.hpp new file mode 100644 index 0000000000..50ed144cfc --- /dev/null +++ b/core/libs/prx/libSceIme/include/Api.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include "prx/libSceIme/include/ImeFilterText.hpp" +#include "SceTypes.hpp" + +struct ImeDictionaryWord; + +struct ImeVshContext; + +struct ImePreeditMetaInfo; + +struct ImeVshCaretGeometry { + std::int32_t x; + std::int32_t y; + std::uint32_t height; + std::uint32_t index; +}; + +struct ImeVshParameter; + +struct ImeVshTextGeometry { + std::int32_t x; + std::int32_t y; + std::uint32_t width; + std::uint32_t height; +}; + +extern "C" { + +int APS5_VABI sceImeOpen_nid_postfix(const Param* param, const ExtendedParam* extended); +int APS5_VABI sceImeClose_nid_postfix(void); +int APS5_VABI sceImeGetPanelPositionAndForm(PositionAndForm* form); +int APS5_VABI sceImeGetPanelSize(const Param* param, uint32_t* width, uint32_t* height); +int APS5_VABI sceImeKeyboardClose(int32_t userId); +int APS5_VABI sceImeKeyboardGetInfo(uint32_t resourceId, KeyboardInfo* info); +int APS5_VABI sceImeKeyboardGetResourceId(int32_t userId, KeyboardResourceIdArray* resourceIds); +int APS5_VABI sceImeKeyboardOpen(int32_t userId, const KeyboardParam* param); +int APS5_VABI sceImeKeyboardSetMode(int32_t userId, uint32_t mode); +int APS5_VABI sceImeFilterText(char16_t* dst, int32_t* outLength, uint32_t capacity, const ime_filter::FilterParam* param, const char16_t* src, uint32_t srcLength); +int APS5_VABI sceImeCheckFilterText(const void* param); +void APS5_VABI sceImeParamInit(Param* param); +int APS5_VABI sceImeSetCaret(const Caret* caret); +int APS5_VABI sceImeSetConsecutiveClauseConversionEnabled(std::uint8_t enabled); +int APS5_VABI sceImeSetText(const char16_t* text, uint32_t length); +int APS5_VABI sceImeSetTextGeometry(TextAreaMode mode, const TextGeometry* geometry); +int APS5_VABI sceImeUpdate(EventHandler handler); +int APS5_VABI sceImeVshOpen(ImeVshContext** context, const ImeVshParameter* parameter, const char16_t* text); +int APS5_VABI sceImeKeyboardUpdate(EventHandler handler); +int APS5_VABI sceImeVshUpdate(ImeVshContext* context); +int APS5_VABI sceImeVshUpdateContext(ImeVshContext* context, const char16_t* text, std::uint32_t caretIndex); +int APS5_VABI sceImeVshClearPreedit(ImeVshContext* context); +int APS5_VABI sceImeVshNotifySetText(ImeVshContext* context, const char16_t* text, std::uint32_t length); +int APS5_VABI sceImeVshSetCaretGeometry(ImeVshContext* context, const ImeVshCaretGeometry* geometry); +int APS5_VABI sceImeVshConfirmPreedit(ImeVshContext* context); +int APS5_VABI sceImeVshSetCaretIndexInPreedit(ImeVshContext* context, std::int32_t index); +int APS5_VABI sceImeVshSetPreeditGeometry(ImeVshContext* context, const ImeVshTextGeometry* geometry); +int APS5_VABI sceImeVshSetSelectGeometry(ImeVshContext* context, const ImeVshTextGeometry* geometry); +int APS5_VABI sceImeVshClose(ImeVshContext* context); +int APS5_VABI sceImeVshGetPanelPositionAndForm(ImeVshContext* context, PositionAndForm* form); +int APS5_VABI sceImeSetCandidateIndex(std::int32_t index); +int APS5_VABI sceImeConfirmCandidate(std::int32_t index); +int APS5_VABI sceImeDisableController(void); +int APS5_VABI sceImeSetVoiceDictationEnabled(bool enabled); +int APS5_VABI sceImeSetEditEventWithRemotePlayEnabled(bool enabled); +int APS5_VABI sceImeForTestFunction(void); +int APS5_VABI sceImeVshInformConfirmdString2(ImeVshContext* context, const char16_t* text, std::int32_t firstParameter, std::int32_t secondParameter, std::int32_t thirdParameter); +int APS5_VABI sceImeVshSetSelectionText(ImeVshContext* context, const char16_t* text, std::uint32_t length); +int APS5_VABI sceImeCheckUpdateTextInfo(ImeEditText* info, std::uint8_t repair); +int APS5_VABI sceImeCheckRemoteEventParam(ImeEvent* event, std::uint8_t repair); +int APS5_VABI sceImeKeyboardOpenInternal(std::int32_t userId, EventHandler handler, const KeyboardParam* param); +int APS5_VABI sceImeVshSetParam(ImeVshContext* context, const ImeVshParameter* parameter); +int APS5_VABI sceImeVshUpdateContext2(ImeVshContext* context, const char16_t* text, std::uint32_t caretIndex); +int APS5_VABI sceImeVshSendTextInfo(ImeVshContext* context, const void* info, std::uint32_t size); +int APS5_VABI sceImeVshInformConfirmdString(ImeVshContext* context, const char16_t* text, std::int32_t parameter); +int APS5_VABI sceImeVshNotifyCurrentTextInfo(ImeVshContext* context, const char16_t* text, std::int32_t caretIndex, std::int32_t preeditFirst, std::int32_t preeditSecond, std::int32_t selectionFirst, std::int32_t selectionSecond); +int APS5_VABI sceImeVshNotifyCurrentTextInfoForReconvertText(ImeVshContext* context, const char16_t* text, std::int32_t caretIndex, std::int32_t preeditFirst, std::int32_t preeditSecond, std::int32_t selectionFirst, std::int32_t selectionSecond, std::int32_t option); +int APS5_VABI sceImeVshNotifyDeleteText(ImeVshContext* context, const char16_t* text, std::int32_t length, std::int32_t parameter); +int APS5_VABI sceImeVshNotifyUpdateCaretText(ImeVshContext* context, const char16_t* text, std::int32_t length, std::int32_t parameter); +int APS5_VABI sceImeVshCheckAutoReConvertingPreeditText(ImeVshContext* context, const char16_t* text); +int APS5_VABI sceImeVshSetPanelPosition(ImeVshContext* context, float x, float y, std::int32_t horizontalAlignment, std::int32_t verticalAlignment); +int APS5_VABI sceImeVshNotifyPreeditMetaInfo(ImeVshContext* context, const ImePreeditMetaInfo* info); +int APS5_VABI sceImeConfigSet(std::uint64_t unusedArgument, std::uint64_t configuration, std::uint32_t key, std::uint32_t value); +int APS5_VABI sceImeConfigGet(std::uint64_t unusedArgument, std::uint64_t configuration, std::uint32_t key); +int APS5_VABI sceImeDicGetWords(std::int32_t userId, std::uint64_t dictionary, ImeDictionaryWord* words); +int APS5_VABI sceImeDicAddWord(std::int32_t userId, std::uint64_t dictionary, const ImeDictionaryWord* word); +int APS5_VABI sceImeDicDeleteWord(std::int32_t userId, std::uint64_t dictionary, const ImeDictionaryWord* word); +int APS5_VABI sceImeDicReplaceWord(std::int32_t userId, std::uint64_t dictionary, const ImeDictionaryWord* oldWord, const ImeDictionaryWord* newWord); +int APS5_VABI sceImeDicDeleteLearnDics(std::int32_t userId); +int APS5_VABI sceImeDicDeleteUserDics(std::int32_t userId); +int APS5_VABI FinalizeImeModule_nid_postfix(void); +int APS5_VABI InitializeImeModule_nid_postfix(void); + +} diff --git a/core/libs/prx/libSceIme/libSceIme.hpp b/core/libs/prx/libSceIme/libSceIme.hpp new file mode 100644 index 0000000000..01b649be31 --- /dev/null +++ b/core/libs/prx/libSceIme/libSceIme.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceIme/include/Api.hpp" diff --git a/core/libs/prx/libSceImeBackend/libSceImeBackend.hpp b/core/libs/prx/libSceImeBackend/libSceImeBackend.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceImeBackend/libSceImeBackend.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceImeDialog/Export.cpp b/core/libs/prx/libSceImeDialog/Export.cpp index c9b181b025..94cdcf67c7 100644 --- a/core/libs/prx/libSceImeDialog/Export.cpp +++ b/core/libs/prx/libSceImeDialog/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceImeDialog/include/Api.hpp" #include #include #include @@ -17,6 +18,7 @@ constexpr std::uint32_t EndStatusUserCanceled = 1; constexpr std::uint32_t EndStatusAborted = 2; constexpr std::uint32_t MaxTextLength = 2048; constexpr int ErrorBusy = static_cast(0x80bc0001u); +constexpr int ErrorSuspending = static_cast(0x80bc0009u); constexpr int ErrorNotOpened = static_cast(0x80bc0002u); constexpr int ErrorNotInUse = static_cast(0x80bc0107u); constexpr int ErrorInvalidMaxTextLength = static_cast(0x80bc0016u); @@ -190,21 +192,22 @@ int APS5_VABI sceImeDialogForceClose(void) { } int APS5_VABI sceImeDialogGetCurrentStarState(int64_t state) { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_mutex); + if (g_dialog.status == StatusNone) return ErrorNotInUse; + if (state == 0) return ErrorInvalidAddress; + *reinterpret_cast(state) = 0; return 0; } -int APS5_VABI sceImeDialogInitInternal(Param* param, ExtendedParam* extended) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceImeDialogInitInternal(const Param* param, const ExtendedParam* extended) { + return sceImeDialogInit(param, extended); } -int APS5_VABI sceImeDialogSetPanelPosition(int32_t posx, int32_t posy) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceImeDialogSetPanelPosition(int32_t, int32_t) { + std::lock_guard lock(g_mutex); + return g_dialog.status == StatusNone ? ErrorNotInUse : ErrorSuspending; } - int APS5_VABI sceImeDialogInitInternalWithSubInfo() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceImeDialog/include/Api.hpp b/core/libs/prx/libSceImeDialog/include/Api.hpp new file mode 100644 index 0000000000..70f28051e4 --- /dev/null +++ b/core/libs/prx/libSceImeDialog/include/Api.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceImeDialogAbort(void); +int APS5_VABI sceImeDialogGetPanelPositionAndForm(PositionAndForm* form); +int APS5_VABI sceImeDialogGetPanelSize(const Param* param, uint32_t* width, uint32_t* height); +int APS5_VABI sceImeDialogGetPanelSizeExtended(const Param* param, const ExtendedParam* extended, uint32_t* width, uint32_t* height); +int APS5_VABI sceImeDialogGetResult(Result* result); +int APS5_VABI sceImeDialogGetStatus(void); +int APS5_VABI sceImeDialogInit(const Param* param, const ExtendedParam* extended); +int APS5_VABI sceImeDialogTerm(void); +int APS5_VABI sceImeDialogForTestFunction(); +int APS5_VABI sceImeDialogForceClose(void); +int APS5_VABI sceImeDialogGetCurrentStarState(int64_t state); +int APS5_VABI sceImeDialogInitInternal(const Param* param, const ExtendedParam* extended); +int APS5_VABI sceImeDialogSetPanelPosition(int32_t, int32_t); +int APS5_VABI sceImeDialogInitInternalWithSubInfo(); + +} diff --git a/core/libs/prx/libSceImeDialog/libSceImeDialog.hpp b/core/libs/prx/libSceImeDialog/libSceImeDialog.hpp new file mode 100644 index 0000000000..b9c6e0c899 --- /dev/null +++ b/core/libs/prx/libSceImeDialog/libSceImeDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceImeDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceInvitationDialog/Export.cpp b/core/libs/prx/libSceInvitationDialog/Export.cpp new file mode 100644 index 0000000000..9053e853ee --- /dev/null +++ b/core/libs/prx/libSceInvitationDialog/Export.cpp @@ -0,0 +1,37 @@ +#include "prx/libSceInvitationDialog/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceInvitationDialogGetResult(InvitationDialogResult* result) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceInvitationDialogInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceInvitationDialogOpen() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceInvitationDialogTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceInvitationDialogUpdateStatus() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceInvitationDialogClose(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceInvitationDialog/include/Api.hpp b/core/libs/prx/libSceInvitationDialog/include/Api.hpp new file mode 100644 index 0000000000..884ec82465 --- /dev/null +++ b/core/libs/prx/libSceInvitationDialog/include/Api.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include + +struct InvitationDialogRecipients { + std::uint32_t count; + NpOnlineId onlineIds[16]; +}; + +struct InvitationDialogResult { + std::uint8_t opaque00[8]; + std::int32_t errorCode; + std::uint32_t opaque0C; + InvitationDialogRecipients* recipients; + std::uint8_t reserved[32]; +}; + +static_assert(sizeof(NpOnlineId) == 20); +static_assert(offsetof(InvitationDialogRecipients, onlineIds) == 4); +static_assert(sizeof(InvitationDialogRecipients) == 324); +static_assert(offsetof(InvitationDialogResult, errorCode) == 0x08); +static_assert(offsetof(InvitationDialogResult, opaque0C) == 0x0C); +static_assert(offsetof(InvitationDialogResult, recipients) == 0x10); +static_assert(offsetof(InvitationDialogResult, reserved) == 0x18); +static_assert(sizeof(InvitationDialogResult) == 0x38); + +extern "C" { + +int APS5_VABI sceInvitationDialogGetResult(InvitationDialogResult* result); +int APS5_VABI sceInvitationDialogInitialize(); +int APS5_VABI sceInvitationDialogOpen(); +int APS5_VABI sceInvitationDialogTerminate(); +int APS5_VABI sceInvitationDialogUpdateStatus(); +int APS5_VABI sceInvitationDialogClose(void); + +} diff --git a/core/libs/prx/libSceInvitationDialog/libSceInvitationDialog.hpp b/core/libs/prx/libSceInvitationDialog/libSceInvitationDialog.hpp new file mode 100644 index 0000000000..52cc7b951a --- /dev/null +++ b/core/libs/prx/libSceInvitationDialog/libSceInvitationDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceInvitationDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceIpmi/CMakeLists.txt b/core/libs/prx/libSceIpmi/CMakeLists.txt new file mode 100644 index 0000000000..f549ae49f1 --- /dev/null +++ b/core/libs/prx/libSceIpmi/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(libSceIpmi SHARED EXCLUDE_FROM_ALL Export.cpp PendingExports.cpp) +target_include_directories(libSceIpmi PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceIpmi) diff --git a/core/libs/prx/libSceIpmi/Export.cpp b/core/libs/prx/libSceIpmi/Export.cpp index 238bb67ecf..993a1254b3 100644 --- a/core/libs/prx/libSceIpmi/Export.cpp +++ b/core/libs/prx/libSceIpmi/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceIpmi/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceIpmi/PendingExports.cpp b/core/libs/prx/libSceIpmi/PendingExports.cpp new file mode 100644 index 0000000000..5970f64dd6 --- /dev/null +++ b/core/libs/prx/libSceIpmi/PendingExports.cpp @@ -0,0 +1,206 @@ +#include "prx/libSceIpmi/include/Api.hpp" +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI _ZN4IPMI4impl11SessionImplC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfig20estimateMsgQueueSizeEmj(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI7SessionD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImpl9tryGetMsgEjPvPmm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6ServerD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6ServerD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server6createEPPS0_PKNS0_6ConfigEPvS6_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6ConfigC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImplD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjiPKvm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6ClientD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImpl7sendMsgEjPKvmPj(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6ClientC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI7SessionD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK4IPMI4impl11SessionImpl16isPeerPrivilegedEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI7SessionC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImplD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6ClientD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server12EventHandlerD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImplD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImpl7destroyEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImpl6getMsgEjPvPmmPj(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjjiPKNS_8DataInfoEj(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6ClientD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6createEPPS0_PKNS0_6ConfigEPvS6_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6ServerC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImplC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI7SessionD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6ServerD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server6ConfigC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI4impl11SessionImpl9getServerEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6ConfigC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN4IPMI6Server6ConfigC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceIpmi/include/Api.hpp b/core/libs/prx/libSceIpmi/include/Api.hpp new file mode 100644 index 0000000000..200ce258ea --- /dev/null +++ b/core/libs/prx/libSceIpmi/include/Api.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceIpmiMgrGetIpcBlockerPid(void); +int APS5_VABI sceIpmiMgrGetIpcBlockerPidByAppId(void); +int APS5_VABI sceIpmiMgrPauseDump(void); +int APS5_VABI sceIpmiMgrRaiseExceptionToPid(void); +int APS5_VABI sceIpmiMgrStartDump(void); +int APS5_VABI _ZN4IPMI4impl11SessionImplC1Ev(void); +int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfig20estimateMsgQueueSizeEmj(void); +int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void); +int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC1Ev(void); +int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void); +int APS5_VABI _ZN4IPMI7SessionD1Ev(void); +int APS5_VABI _ZN4IPMI4impl11SessionImpl9tryGetMsgEjPvPmm(void); +int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void); +int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC1Ev(void); +int APS5_VABI _ZN4IPMI6ServerD2Ev(void); +int APS5_VABI _ZN4IPMI6ServerD0Ev(void); +int APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void); +int APS5_VABI _ZN4IPMI6Server6createEPPS0_PKNS0_6ConfigEPvS6_(void); +int APS5_VABI _ZN4IPMI6Client6ConfigC2Ev(void); +int APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void); +int APS5_VABI _ZN4IPMI4impl11SessionImplD1Ev(void); +int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjiPKvm(void); +int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC2Ev(void); +int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void); +int APS5_VABI _ZN4IPMI6ClientD2Ev(void); +int APS5_VABI _ZN4IPMI4impl11SessionImpl7sendMsgEjPKvmPj(void); +int APS5_VABI _ZN4IPMI6ClientC2Ev(void); +int APS5_VABI _ZN4IPMI7SessionD2Ev(void); +int APS5_VABI _ZNK4IPMI4impl11SessionImpl16isPeerPrivilegedEv(void); +int APS5_VABI _ZN4IPMI7SessionC2Ev(void); +int APS5_VABI _ZN4IPMI4impl11SessionImplD2Ev(void); +int APS5_VABI _ZN4IPMI6ClientD1Ev(void); +int APS5_VABI _ZN4IPMI6Server12EventHandlerD0Ev(void); +int APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void); +int APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(void); +int APS5_VABI _ZN4IPMI4impl11SessionImplD0Ev(void); +int APS5_VABI _ZN4IPMI4impl11SessionImpl7destroyEv(void); +int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC1Ev(void); +int APS5_VABI _ZN4IPMI4impl11SessionImpl6getMsgEjPvPmmPj(void); +int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjjiPKNS_8DataInfoEj(void); +int APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void); +int APS5_VABI _ZN4IPMI6ClientD0Ev(void); +int APS5_VABI _ZN4IPMI6Client6createEPPS0_PKNS0_6ConfigEPvS6_(void); +int APS5_VABI _ZN4IPMI6ServerC2Ev(void); +int APS5_VABI _ZN4IPMI4impl11SessionImplC2Ev(void); +int APS5_VABI _ZN4IPMI7SessionD0Ev(void); +int APS5_VABI _ZN4IPMI6ServerD1Ev(void); +int APS5_VABI _ZN4IPMI6Server6ConfigC1Ev(void); +int APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void); +int APS5_VABI _ZN4IPMI4impl11SessionImpl9getServerEv(void); +int APS5_VABI _ZN4IPMI6Client6ConfigC1Ev(void); +int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC2Ev(void); +int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC2Ev(void); +int APS5_VABI _ZN4IPMI6Server6ConfigC2Ev(void); + +} diff --git a/core/libs/prx/libSceIpmi/libSceIpmi.hpp b/core/libs/prx/libSceIpmi/libSceIpmi.hpp new file mode 100644 index 0000000000..c7fc7c9a22 --- /dev/null +++ b/core/libs/prx/libSceIpmi/libSceIpmi.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceIpmi/include/Api.hpp" diff --git a/core/libs/prx/libSceJemspace/Export.cpp b/core/libs/prx/libSceJemspace/Export.cpp index d81f4a5e32..c6a8018fa1 100644 --- a/core/libs/prx/libSceJemspace/Export.cpp +++ b/core/libs/prx/libSceJemspace/Export.cpp @@ -1,10 +1,59 @@ -#include -#include +#include "prx/libSceJemspace/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceJemspaceCalloc() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceCreate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceDestroy() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceFree() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceGetStats() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceMalloc() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceMallocUsableSize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceMemalign() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspacePosixMemalign() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspacePrintStats() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspacePurge() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceRealloc() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceReallocalign() { + NotImplemented_nid_no_patch(__func__); +} } diff --git a/core/libs/prx/libSceJemspace/include/Api.hpp b/core/libs/prx/libSceJemspace/include/Api.hpp new file mode 100644 index 0000000000..5f280f8dcb --- /dev/null +++ b/core/libs/prx/libSceJemspace/include/Api.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceJemspaceCalloc(); +int APS5_VABI sceJemspaceCreate(); +int APS5_VABI sceJemspaceDestroy(); +int APS5_VABI sceJemspaceFree(); +int APS5_VABI sceJemspaceGetStats(); +int APS5_VABI sceJemspaceMalloc(); +int APS5_VABI sceJemspaceMallocUsableSize(); +int APS5_VABI sceJemspaceMemalign(); +int APS5_VABI sceJemspacePosixMemalign(); +int APS5_VABI sceJemspacePrintStats(); +int APS5_VABI sceJemspacePurge(); +int APS5_VABI sceJemspaceRealloc(); +int APS5_VABI sceJemspaceReallocalign(); + +} diff --git a/core/libs/prx/libSceJemspace/libSceJemspace.hpp b/core/libs/prx/libSceJemspace/libSceJemspace.hpp new file mode 100644 index 0000000000..efe1e331fb --- /dev/null +++ b/core/libs/prx/libSceJemspace/libSceJemspace.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceJemspace/include/Api.hpp" diff --git a/core/libs/prx/libSceJitBridge/libSceJitBridge.hpp b/core/libs/prx/libSceJitBridge/libSceJitBridge.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceJitBridge/libSceJitBridge.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceJobManager/CMakeLists.txt b/core/libs/prx/libSceJobManager/CMakeLists.txt index 8539bbd0c2..1364b0e64d 100644 --- a/core/libs/prx/libSceJobManager/CMakeLists.txt +++ b/core/libs/prx/libSceJobManager/CMakeLists.txt @@ -1,14 +1,71 @@ add_library(libSceJobManager SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceJobManager PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(libSceJobManager PRIVATE libSceAgc libSceAgcDriver) +target_link_libraries(libSceJobManager PRIVATE libSceAgc libSceAgcDriver libSceRazorCpu ulobjmgr) configure_windows_unwind(libSceJobManager) if(BUILD_TESTING) + add_test_executable(guest_job_manager_profiler_tests ${LIBS_INCLUDE_DIR}/tests/JobManagerProfiler.cpp Export.cpp) + target_include_directories(guest_job_manager_profiler_tests PRIVATE ${LIBS_INCLUDE_DIR} + ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR} + $) + target_compile_options(guest_job_manager_profiler_tests PRIVATE -msse4.2) + target_link_libraries(guest_job_manager_profiler_tests PRIVATE libSceAgc libSceAgcDriver libkernel libc ulobjmgr) + configure_windows_unwind(guest_job_manager_profiler_tests) + add_test(NAME guest_job_manager_profiler COMMAND guest_job_manager_profiler_tests) + add_test(NAME guest_job_manager_profiler_gpu COMMAND guest_job_manager_profiler_tests gpu) + set_tests_properties(guest_job_manager_profiler guest_job_manager_profiler_gpu PROPERTIES TIMEOUT 20) + set_tests_properties(guest_job_manager_profiler_gpu PROPERTIES SKIP_RETURN_CODE 77 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1") + if(WIN32) + set_tests_properties(guest_job_manager_profiler guest_job_manager_profiler_gpu PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(job_manager_compute_progress_tests ${LIBS_INCLUDE_DIR}/tests/JobManagerComputeProgress.cpp) + target_include_directories(job_manager_compute_progress_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(job_manager_compute_progress_tests PRIVATE libc) + configure_windows_unwind(job_manager_compute_progress_tests) + add_test(NAME guest_job_manager_compute_progress COMMAND job_manager_compute_progress_tests) + set_tests_properties(guest_job_manager_compute_progress PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_job_manager_compute_progress PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_job_manager_failure_tests ${LIBS_INCLUDE_DIR}/tests/JobManagerFailures.cpp Export.cpp) + target_include_directories(guest_job_manager_failure_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_job_manager_failure_tests PRIVATE libSceAgc libSceAgcDriver libSceRazorCpu libkernel libc ulobjmgr) + configure_windows_unwind(guest_job_manager_failure_tests) + add_test(NAME guest_job_manager_failures COMMAND guest_job_manager_failure_tests) + set_tests_properties(guest_job_manager_failures PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_job_manager_failures PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_job_manager_registration_tests ${LIBS_INCLUDE_DIR}/tests/JobManagerRegistration.cpp Export.cpp) + target_include_directories(guest_job_manager_registration_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_job_manager_registration_tests PRIVATE libSceAgc libSceAgcDriver libSceRazorCpu libkernel libc) + configure_windows_unwind(guest_job_manager_registration_tests) + add_test(NAME guest_job_manager_registration COMMAND guest_job_manager_registration_tests) + set_tests_properties(guest_job_manager_registration PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_job_manager_registration PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_job_manager_tests ${LIBS_INCLUDE_DIR}/tests/GuestJobManager.cpp) target_include_directories(guest_job_manager_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_job_manager_tests PRIVATE libSceJobManager libSceAgc libSceAgcDriver libkernel libc) configure_windows_unwind(guest_job_manager_tests) - foreach(testCase IN ITEMS memory validation dispatch ordering nested limits priority shutdown) + if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_library(job_manager_reference SHARED EXCLUDE_FROM_ALL + ${LIBS_INCLUDE_DIR}/tests/GuestJobManager.cpp + ${LIBS_INCLUDE_DIR}/tests/JobManagerReference.cpp) + target_include_directories(job_manager_reference PRIVATE ${LIBS_INCLUDE_DIR}) + target_compile_definitions(job_manager_reference PRIVATE ANYPS5_JOB_MANAGER_REFERENCE) + target_link_libraries(job_manager_reference PRIVATE libkernel libc ${CMAKE_DL_LIBS}) + set_target_properties(job_manager_reference PROPERTIES PREFIX "" SUFFIX .prx + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + configure_windows_unwind(job_manager_reference) + endif() + foreach(testCase IN ITEMS memory owned_state validation dispatch ordering nested limits node_pool priority shutdown worker_policy worker_wake worker_wait worker_count worker_spin shutdown_running shutdown_waiter shutdown_queued shutdown_destroy shutdown_partial shutdown_cross_wait concurrent_wait) add_test(NAME guest_job_manager_${testCase} COMMAND guest_job_manager_tests ${testCase}) set_tests_properties(guest_job_manager_${testCase} PROPERTIES TIMEOUT 20) if(WIN32) @@ -16,4 +73,13 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + set_tests_properties(guest_job_manager_worker_spin PROPERTIES ENVIRONMENT "APS5_TIME_SCALE=1") + foreach(scale IN ITEMS 0.1 10) + add_test(NAME guest_job_manager_worker_spin_scale_${scale} COMMAND guest_job_manager_tests worker_spin) + set_tests_properties(guest_job_manager_worker_spin_scale_${scale} PROPERTIES TIMEOUT 20 ENVIRONMENT "APS5_TIME_SCALE=${scale}") + if(WIN32) + set_tests_properties(guest_job_manager_worker_spin_scale_${scale} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() endif() diff --git a/core/libs/prx/libSceJobManager/ComputeProgress.hpp b/core/libs/prx/libSceJobManager/ComputeProgress.hpp new file mode 100644 index 0000000000..aadd732245 --- /dev/null +++ b/core/libs/prx/libSceJobManager/ComputeProgress.hpp @@ -0,0 +1,31 @@ +#ifndef CORE_LIBS_PRX_LIBSCEJOBMANAGER_COMPUTEPROGRESS_HPP +#define CORE_LIBS_PRX_LIBSCEJOBMANAGER_COMPUTEPROGRESS_HPP + +#include +#include + +namespace JobManager { + +class ComputeProgress { +public: + std::uint32_t Publish(std::uint32_t count) { + total += count; + return total; + } + + std::uint32_t Collect(std::uint64_t value) { + const auto current = static_cast(value); + const std::uint32_t finished = current - seen; + if (finished > std::uint32_t{total - seen}) throw std::runtime_error("libSceJobManager: a compute label advanced beyond its published work"); + seen = current; + return finished; + } + +private: + std::uint32_t total = 0; + std::uint32_t seen = 0; +}; + +} + +#endif diff --git a/core/libs/prx/libSceJobManager/Export.cpp b/core/libs/prx/libSceJobManager/Export.cpp index 155a95220d..0901e79c0f 100644 --- a/core/libs/prx/libSceJobManager/Export.cpp +++ b/core/libs/prx/libSceJobManager/Export.cpp @@ -1,47 +1,35 @@ +#include "prx/libSceRazorCpu/libSceRazorCpu.hpp" +#include "prx/ulobjmgr/ulobjmgr.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceJobManager/include/Api.hpp" +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" +#include "prx/libSceAgcDriver/Eq/include/Event.hpp" +#include "prx/libSceAgc/Cb/include/Sync.hpp" +#include "prx/libSceAgc/Cb/include/Registers.hpp" +#include "prx/libSceAgc/Cb/include/Dispatch.hpp" +#include "prx/libSceAgc/Acb/include/Sync.hpp" +#include "prx/libSceAgc/Acb/include/Memory.hpp" +#include "prx/libSceAgc/Acb/include/Dispatch.hpp" +#include "prx/libSceAgc/Acb/include/Control.hpp" #include #include #include #include #include #include +#include #include -#include +#include +#include #include +#include #include -#include -#include #include "SceTypes.hpp" +#include "ComputeProgress.hpp" #include "prx/libc/include/General.hpp" - -extern "C" { -int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched); -int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param); -int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize); -int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** result); -int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); -int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo); -int APS5_VABI sceKernelAddUserEventEdge(KernelEqueue eq, int id); -int APS5_VABI sceKernelTriggerUserEvent(KernelEqueue eq, int id, void* udata); -int APS5_VABI sceKernelGetEventFilter(const KernelEvent* ev); -std::uintptr_t APS5_VABI sceKernelGetEventId(const KernelEvent* ev); -int APS5_VABI sceAgcDriverAddEqEvent(KernelEqueue eq, int id, void* udata); -int APS5_VABI sceAgcDriverDeleteEqEvent(KernelEqueue eq, int id); -int APS5_VABI sceAgcDriverSubmitAcb(std::uint32_t queue, const Packet* packet); -std::uint32_t* APS5_VABI sceAgcAcbAcquireMem(CommandBuffer* buf, std::uint32_t gcrControl, const volatile void* base, std::uint64_t sizeBytes, std::uint32_t pollCycles); -std::uint32_t* APS5_VABI sceAgcCbSetShRegistersDirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs); -std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuffer* buf, std::uint32_t offset, const std::uint32_t* values, std::uint32_t numValues); -std::uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, const char* str, std::uint32_t color); -std::uint32_t* APS5_VABI sceAgcAcbPopMarker(CommandBuffer* buf); -std::uint32_t* APS5_VABI sceAgcCbDispatch(CommandBuffer* buf, std::uint32_t threadGroupX, std::uint32_t threadGroupY, std::uint32_t threadGroupZ, std::uint32_t modifier); -std::uint32_t* APS5_VABI sceAgcAcbDispatchIndirect(CommandBuffer* buf, const volatile void* indirectArguments, std::uint32_t modifier); -std::uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer* buf, std::uint8_t action, std::uint16_t gcrControl, std::uint8_t dst, std::uint8_t cachePolicy, const volatile Label* address, std::uint8_t dataSelect, std::uint64_t data, std::uint16_t gdsOffset, std::uint16_t gdsSize, std::uint8_t interrupt, std::uint32_t interruptContextId); -} +#include "prx/libc/include/GuestArena.hpp" namespace { @@ -70,35 +58,10 @@ constexpr std::uint64_t ThreadRecordBytes = 0x48; constexpr std::uint64_t SequenceBytes = 0x240; constexpr std::uint64_t NodeBytes = 72; constexpr std::uint64_t ComputeContextBytes = 0x168; -constexpr std::uint64_t ComputeCommandBytes = 0x3ff * 104 + 0xff * 148 + 0x3f * 32; constexpr std::uint32_t FirstComputeQueue = 0x20; constexpr std::uint32_t UserDataRegister = 0x240; constexpr int UserEventFilter = -11; -struct MemorySizeQueryParams { - std::uint32_t version; - std::uint32_t workers; - std::uint32_t sequences; - std::uint32_t invocations; - std::uint32_t dispatches; - std::uint32_t syncPoints; - std::uint32_t waits; - std::uint32_t computeSequences; - std::uint64_t computeQueueMask; -}; - -struct ConfigParams { - std::uint32_t version; - std::uint32_t threadFlags; - std::uint32_t stackSize; - std::uint32_t priority; - const char* threadName; - std::uint32_t spinMicroseconds; - std::uint32_t computeAffinity; - std::uint32_t computePriority; - std::uint32_t reserved; -}; - constexpr ConfigParams DefaultConfig{0, 0x00021fff, 0x00010000, 0x2bc, nullptr, 0, 0x1fff, 0x100, 0}; struct BlockHeader { @@ -109,31 +72,87 @@ struct BlockHeader { std::uint32_t reserved; }; -using JobEntry = void (APS5_VABI*)(void* argument, std::uint32_t index); - std::uint64_t AlignUp(std::uint64_t value, std::uint64_t alignment) { return (value + alignment - 1) & ~(alignment - 1); } +std::uint64_t ComputeCommandBudget() { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + const ShaderRegister shaderRegister{}; + const std::array userData{}; + bool encoded = sceAgcAcbAcquireMem(&buffer, 0x20380, nullptr, 0, 0x190) != nullptr; + encoded = encoded && sceAgcCbSetShRegistersDirect(&buffer, &shaderRegister, 1) != nullptr; + encoded = encoded && sceAgcCbSetShRegisterRangeDirect(&buffer, UserDataRegister, userData.data(), 2) != nullptr; + encoded = encoded && sceAgcAcbPushMarker(&buffer, "1234567890123456789012345678901", 0) != nullptr; + encoded = encoded && sceAgcCbDispatch(&buffer, 1, 1, 1, 0) != nullptr; + encoded = encoded && sceAgcAcbPopMarker(&buffer) != nullptr; + const auto dispatchBytes = static_cast(buffer.cursor_up - words.data()) * 4; + buffer.cursor_up = words.data(); + encoded = encoded && sceAgcCbReleaseMem(&buffer, 0x2f, 0, 1, 3, nullptr, 1, 0, 0, 0, 0, 0) != nullptr; + encoded = encoded && sceAgcAcbWaitRegMem(&buffer, 0, 3, 3, nullptr, 0, ~std::uint64_t{0}, 0x190) != nullptr; + encoded = encoded && sceAgcAcbRewind(&buffer, 1) != nullptr; + encoded = encoded && sceAgcCbReleaseMem(&buffer, 0x2f, 0x200, 1, 3, nullptr, 1, 0, 0, 0, 0, 0) != nullptr; + encoded = encoded && sceAgcAcbWriteData(&buffer, 2, 3, 0, userData.data(), 1, 1, 0) != nullptr; + const auto syncBytes = static_cast(buffer.cursor_up - words.data()) * 4; + buffer.cursor_up = words.data(); + encoded = encoded && sceAgcCbReleaseMem(&buffer, 0x2f, 0x200, 1, 3, nullptr, 1, 0, 0, 0, 2, 0) != nullptr; + const auto waitBytes = static_cast(buffer.cursor_up - words.data()) * 4; + if (!encoded || dispatchBytes > 255 || syncBytes > 255 || waitBytes > 255) throw std::runtime_error("libSceJobManager: compute command size measurement failed"); + return MaxDispatches * dispatchBytes + MaxSyncPoints * syncBytes + MaxWaits * waitBytes; +} + struct Manager; class Sequence; enum class ItemType { Dispatch, SyncPoint, Wait, Compute, ComputeIndirect }; struct Item { - ItemType type; + Item* next = nullptr; + Item* runnableNext = nullptr; Sequence* sequence; - JobEntry entry; - void* argument; - std::uint32_t count; - std::uint32_t handedOut; - std::uint32_t completed; - bool* passed; - const void* shader = nullptr; - std::array groups{}; - const void* indirectArguments = nullptr; - std::string name; - bool named = false; + struct Cpu { + JobEntry entry; + void* argument; + std::uint32_t count; + std::uint32_t handedOut; + std::uint32_t completed; + }; + struct Compute { + const void* shader; + void* userData; + union { + std::array groups; + const void* indirectArguments; + }; + }; + union { + Cpu cpu; + Compute gpu; + bool* passed; + }; + const char* name = nullptr; + ItemType type; + bool encoded = false; +}; + +static_assert(sizeof(Item) == NodeBytes); + +struct RunQueue { + Item* first = nullptr; + Item* last = nullptr; + + void Push(Item* item) { + item->runnableNext = nullptr; + if (last != nullptr) last->runnableNext = item; + else first = item; + last = item; + } + + void Pop() { + first = first->runnableNext; + if (first == nullptr) last = nullptr; + } }; struct Counters { @@ -143,27 +162,6 @@ struct Counters { std::uint32_t waits = 0; }; -class ISequence { -public: - APS5_VABI virtual int Dispatch(JobEntry entry, void* argument, std::uint32_t count, const char* name) = 0; - APS5_VABI virtual int SyncPoint() = 0; - APS5_VABI virtual int Wait() = 0; - APS5_VABI virtual void CompleteDestructor() {} - APS5_VABI virtual void DeletingDestructor() { throw std::runtime_error("libSceJobManager: a sequence was deleted by its caller"); } - APS5_VABI virtual int DispatchCompute(const void* shader, void* userData, std::int32_t x, std::int32_t y, std::int32_t z, const char* name) = 0; - APS5_VABI virtual int DispatchComputeIndirect(const void* shader, void* userData, const void* indirectArguments, const char* name) = 0; -}; - -class ISequenceFactory { -public: - APS5_VABI virtual int Create(ISequence** out, std::uint32_t invocations, std::uint32_t dispatches, std::uint32_t syncPoints, std::uint32_t waits, const char* name) = 0; - APS5_VABI virtual int Destroy(ISequence* sequence) = 0; - APS5_VABI virtual void CompleteDestructor() {} - APS5_VABI virtual void DeletingDestructor() { throw std::runtime_error("libSceJobManager: a sequence factory was deleted by its caller"); } - APS5_VABI virtual int CreateCompute(ISequence** out, std::uint32_t invocations, std::uint32_t dispatches, std::uint32_t syncPoints, std::uint32_t waits, const char* name) = 0; - APS5_VABI virtual int DestroyCompute(ISequence* sequence) = 0; -}; - class Sequence final : public ISequence { public: APS5_VABI int Dispatch(JobEntry entry, void* argument, std::uint32_t count, const char* name) override; @@ -172,24 +170,32 @@ public: APS5_VABI int DispatchCompute(const void* shader, void* userData, std::int32_t x, std::int32_t y, std::int32_t z, const char* name) override; APS5_VABI int DispatchComputeIndirect(const void* shader, void* userData, const void* indirectArguments, const char* name) override; - bool Open(Manager* owner, const Counters& limit, std::uint32_t runQueue); + bool Open(Manager* owner, const Counters& limit, std::uint32_t runQueue, const char* name); int WaitAndClose(std::uint32_t index); void Advance(); void DispatchCompleted(std::uint32_t invocations); void CollectCompute(); - bool Busy() const { return outstanding != 0; } + void SetComputeStorage(std::uint32_t* commands, std::size_t words, Label* completion); + void CancelWaits(); Manager* manager = nullptr; bool open = false; bool compute = false; std::uint32_t queue = 0; + std::uint16_t profileId = 0; private: int Latch(int code); void RequireOpen() const; int WaitLocked(std::unique_lock& guard); int AppendCompute(Item item); + void Append(Item* item); + void RemoveWait(Item* item); void SubmitCompute(const Item& item); + void SubmitComputeAhead(Item* item); + void RetireCompute(); + void FlushCompute(); + void Profile(std::uint16_t event); Counters limits; Counters submitted; @@ -197,16 +203,42 @@ private: std::uint32_t priority = 0; int error = 0; std::uint32_t outstanding = 0; - std::size_t cursor = 0; - std::deque items; + Item* reserved = nullptr; + Item* first = nullptr; + Item* last = nullptr; + Item* cursor = nullptr; std::condition_variable passed; - Label label{}; - std::uint64_t labelSeen = 0; - std::uint64_t labelTotal = 0; + Label* label = nullptr; + JobManager::ComputeProgress progress; + std::uint32_t pendingCompute = 0; + std::uint32_t activeCompute = 0; + std::uint32_t availableCompute = 0; bool batchStarted = false; - std::deque> commands; + bool profileActive = false; + bool profileCpu = false; + bool profileGpuBarrier = false; + const char* profileName = nullptr; + std::uint32_t* commandBegin = nullptr; + std::size_t commandWords = 0; + std::size_t commandUsed = 0; }; +static_assert(sizeof(Sequence) < SequenceBytes); + +struct alignas(64) SequenceSlot { + Sequence value; + std::array padding; +}; + +static_assert(sizeof(SequenceSlot) == SequenceBytes); + +struct WorkerSlot { + Pthread thread = nullptr; + std::array padding; +}; + +static_assert(sizeof(WorkerSlot) == ThreadRecordBytes); + class Factory final : public ISequenceFactory { public: APS5_VABI int Create(ISequence** out, std::uint32_t invocations, std::uint32_t dispatches, std::uint32_t syncPoints, std::uint32_t waits, const char* name) override; @@ -219,37 +251,63 @@ public: }; struct Manager { + std::uint32_t objectId = 0; + std::uint16_t profileId = 0; std::mutex lock; std::condition_variable work; + std::condition_variable quiesced; bool running = false; std::uint32_t blockedWaiters = 0; - std::uint32_t freeNodes = 0; + Item* freeNodes = nullptr; std::uint32_t freeSequences = 0; std::uint32_t persistentThreads = 0; - std::array, 2> runQueues; - std::vector sequences; - std::deque computeSequences; + std::uint32_t workerCount = 0; + std::uint32_t sleepingWorkers = 0; + std::uint32_t wakeTokens = 0; + std::uint64_t pendingInvocations = 0; + std::uint64_t spinTicks = 0; + std::array runQueues; + std::span sequences; + std::span computeSequences; std::uint32_t freeComputeSequences = 0; - std::vector computeQueues; + std::uint64_t computeQueues = 0; KernelEqueue computeEvents = 0; Pthread computeHandler = nullptr; std::array factories; std::array unifiedFactories; - std::vector workers; + std::span workers; + std::uint32_t activeWorkers = 0; + void* stateBegin = nullptr; + std::size_t stateBytes = 0; + std::span nodes; }; +static_assert(sizeof(Manager) <= StateEnd - StateOffset); + +thread_local Manager* currentWorker = nullptr; + +void WakeWorkers(Manager& manager, std::uint32_t invocations, bool publisherCanRun = true) { + const std::uint32_t available = invocations - (publisherCanRun && currentWorker == &manager ? 1u : 0u); + manager.wakeTokens += std::min(available, manager.sleepingWorkers - manager.wakeTokens); + if (manager.wakeTokens != 0) manager.work.notify_all(); +} + Manager*& Implementation(void* self) { return *static_cast(self); } -bool TakeNode(Manager& manager) { - if (manager.freeNodes == 0) return false; - --manager.freeNodes; - return true; +Item* TakeNode(Manager& manager) { + Item* item = manager.freeNodes; + if (item != nullptr) { + manager.freeNodes = item->next; + *item = {}; + } + return item; } -void ReturnNodes(Manager& manager, std::uint32_t count) { - manager.freeNodes += count; +void ReturnNode(Manager& manager, Item* item) { + item->next = manager.freeNodes; + manager.freeNodes = item; } int Sequence::Latch(int code) { @@ -257,7 +315,12 @@ int Sequence::Latch(int code) { return code; } -bool Sequence::Open(Manager* owner, const Counters& limit, std::uint32_t runQueue) { +void Sequence::Profile(std::uint16_t event) { + const auto id = static_cast(profileId - (compute && event <= 2 ? manager->sequences.size() : 0)); + static_cast(sceRazorCpuJobManagerSequence(profileName, manager->profileId, id, event)); +} + +bool Sequence::Open(Manager* owner, const Counters& limit, std::uint32_t runQueue, const char* name) { manager = owner; limits = limit; submitted = {}; @@ -265,12 +328,21 @@ bool Sequence::Open(Manager* owner, const Counters& limit, std::uint32_t runQueu priority = runQueue; error = 0; outstanding = 0; - cursor = 0; - items.clear(); - commands.clear(); + cursor = nullptr; + first = nullptr; + last = nullptr; + commandUsed = 0; batchStarted = false; - if (!TakeNode(*owner)) return false; + activeCompute = 0; + availableCompute = 0; + profileActive = false; + profileCpu = false; + profileGpuBarrier = false; + profileName = name; + reserved = TakeNode(*owner); + if (reserved == nullptr) return false; open = true; + Profile(1); return true; } @@ -278,33 +350,93 @@ void Sequence::RequireOpen() const { if (!open) throw std::runtime_error("libSceJobManager: a destroyed sequence was used"); } +void Sequence::Append(Item* item) { + item->next = nullptr; + if (last != nullptr) last->next = item; + else first = item; + last = item; + if (cursor == nullptr) cursor = item; +} + +void Sequence::RemoveWait(Item* item) { + Item* previous = nullptr; + for (Item* current = first; current != nullptr; current = current->next) { + if (current == item) { + if (previous != nullptr) previous->next = item->next; + else first = item->next; + if (last == item) last = previous; + if (cursor == item) cursor = item->next; + return; + } + previous = current; + } +} + void Sequence::Advance() { - while (cursor < items.size()) { - Item& item = items[cursor]; + while (cursor != nullptr) { + Item& item = *cursor; if (item.type == ItemType::Dispatch) { + if (profileGpuBarrier) { + Profile(4); + profileActive = false; + profileGpuBarrier = false; + } + if (!profileActive) Profile(3); + profileActive = true; + profileCpu = true; + if (!manager->running) return; ++outstanding; - ++cursor; - manager->runQueues[priority].push_back(&item); - manager->work.notify_all(); + cursor = item.next; + manager->runQueues[priority].Push(&item); + manager->pendingInvocations -= item.cpu.count; + WakeWorkers(*manager, item.cpu.count); continue; } if (item.type == ItemType::Compute || item.type == ItemType::ComputeIndirect) { + if (!manager->running) return; + if (!profileActive) Profile(3); + profileActive = true; + profileGpuBarrier = false; ++outstanding; - ++cursor; - SubmitCompute(item); + ++activeCompute; + cursor = item.next; + if (!item.encoded) { + SubmitCompute(item); + item.encoded = true; + } + RetireCompute(); continue; } - if (outstanding != 0) return; + FlushCompute(); + if (outstanding != 0) { + if (item.type == ItemType::SyncPoint && !profileCpu && manager->running) SubmitComputeAhead(item.next); + return; + } if (item.type == ItemType::SyncPoint) { + if (profileCpu) { + Profile(4); + profileActive = false; + profileCpu = false; + } + profileGpuBarrier = profileActive; ++completed.syncPoints; - ++cursor; + cursor = item.next; continue; } + if (profileActive) Profile(4); + profileActive = false; + profileCpu = false; + profileGpuBarrier = false; bool* const waiter = item.passed; - const auto retired = static_cast(cursor); - items.erase(items.begin(), items.begin() + static_cast(retired + 1)); - cursor = 0; - commands.clear(); + while (first != &item) { + Item* retired = first; + first = first->next; + ReturnNode(*manager, retired); + } + first = item.next; + if (last == &item) last = nullptr; + cursor = first; + commandUsed = 0; batchStarted = false; if (error == 0) { submitted.invocations -= completed.invocations; @@ -313,7 +445,6 @@ void Sequence::Advance() { --submitted.waits; completed = {}; } - ReturnNodes(*manager, retired); *waiter = true; passed.notify_all(); } @@ -326,7 +457,14 @@ void Sequence::DispatchCompleted(std::uint32_t invocations) { if (outstanding == 0) Advance(); } -int Sequence::Dispatch(JobEntry entry, void* argument, std::uint32_t count, const char*) { +void Sequence::CancelWaits() { + for (Item* item = first; item != nullptr; item = item->next) { + if (item->type == ItemType::Wait) *item->passed = true; + } + passed.notify_all(); +} + +int Sequence::Dispatch(JobEntry entry, void* argument, std::uint32_t count, const char* name) { if (entry == nullptr) return SequenceNullArgument; if (count == 0 || count > MaxInvocations) return SequenceOutOfRange; const std::lock_guard guard(manager->lock); @@ -336,14 +474,20 @@ int Sequence::Dispatch(JobEntry entry, void* argument, std::uint32_t count, cons submitted.invocations += count; ++submitted.dispatches; if (before.invocations + count > limits.invocations || before.dispatches >= limits.dispatches) return Latch(SequenceLimitExceeded); - if (!TakeNode(*manager)) return Latch(SequenceNoNodes); - items.push_back({ItemType::Dispatch, this, entry, argument, count, 0, 0, nullptr}); + Item* item = TakeNode(*manager); + if (item == nullptr) return Latch(SequenceNoNodes); + item->type = ItemType::Dispatch; + item->sequence = this; + item->name = name; + item->cpu = {entry, argument, count, 0, 0}; + Append(item); + manager->pendingInvocations += count; Advance(); return 0; } void Sequence::SubmitCompute(const Item& item) { - const auto* bytes = static_cast(item.shader); + const auto* bytes = static_cast(item.gpu.shader); const ShaderRegister* registers = nullptr; const std::uint8_t* specials = nullptr; std::memcpy(®isters, bytes + 0x20, sizeof(registers)); @@ -351,36 +495,85 @@ void Sequence::SubmitCompute(const Item& item) { const std::uint32_t registerCount = bytes[0x5c]; std::uint32_t modifier = 0; std::memcpy(&modifier, specials + 0x10, sizeof(modifier)); - const auto userData = reinterpret_cast(item.argument); + const auto userData = reinterpret_cast(item.gpu.userData); const std::array userDataWords{static_cast(userData), static_cast(userData >> 32)}; - auto& words = commands.emplace_back(64 + 3 * registerCount + item.name.size() / 4); - CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; - bool encoded = sceAgcAcbAcquireMem(&buffer, 0x20380u | (batchStarted ? 0u : 1u), nullptr, 0, 0x190) != nullptr; - encoded = encoded && sceAgcCbSetShRegistersDirect(&buffer, registers, registerCount) != nullptr; - encoded = encoded && sceAgcCbSetShRegisterRangeDirect(&buffer, UserDataRegister, userDataWords.data(), 2) != nullptr; - if (item.named) encoded = encoded && sceAgcAcbPushMarker(&buffer, item.name.c_str(), 0) != nullptr; - if ((modifier & 1) != 0) { + const auto capacity = std::min(commandWords - commandUsed, 64 + 3 * registerCount + (item.name != nullptr ? std::strlen(item.name) / 4 : 0)); + auto* const words = commandBegin + commandUsed; + CommandBuffer buffer{words, words + capacity, words, words + capacity, nullptr, nullptr, 0}; + { + const GuestArena::HostWrite destination(words, capacity * 4); + bool encoded = sceAgcAcbAcquireMem(&buffer, 0x20380u | (batchStarted ? 0u : 1u), nullptr, 0, 0x190) != nullptr; + encoded = encoded && sceAgcCbSetShRegistersDirect(&buffer, registers, registerCount) != nullptr; + encoded = encoded && sceAgcCbSetShRegisterRangeDirect(&buffer, UserDataRegister, userDataWords.data(), 2) != nullptr; + if (item.name != nullptr) encoded = encoded && sceAgcAcbPushMarker(&buffer, item.name, 0) != nullptr; if (item.type == ItemType::Compute) { - encoded = encoded && sceAgcCbDispatch(&buffer, item.groups[0], item.groups[1], item.groups[2], modifier) != nullptr; + encoded = encoded && sceAgcCbDispatch(&buffer, item.gpu.groups[0], item.gpu.groups[1], item.gpu.groups[2], modifier) != nullptr; } else { - encoded = encoded && sceAgcAcbDispatchIndirect(&buffer, item.indirectArguments, modifier) != nullptr; + encoded = encoded && sceAgcAcbDispatchIndirect(&buffer, item.gpu.indirectArguments, modifier) != nullptr; } + if (item.name != nullptr) encoded = encoded && sceAgcAcbPopMarker(&buffer) != nullptr; + if (!encoded) throw std::runtime_error("libSceJobManager: a compute dispatch did not fit its command buffer"); } - if (item.named) encoded = encoded && sceAgcAcbPopMarker(&buffer) != nullptr; - encoded = encoded && sceAgcCbReleaseMem(&buffer, 0x2f, 0x620, 0, 0, &label, 1, ++labelTotal, 0, 0, 2, 0) != nullptr; - if (!encoded) throw std::runtime_error("libSceJobManager: a compute dispatch did not fit its command buffer"); batchStarted = true; - const Packet packet{words.data(), static_cast(buffer.cursor_up - words.data()), 0, {}}; + ++pendingCompute; + const Packet packet{words, static_cast(buffer.cursor_up - words), 0, {}}; + commandUsed += packet.dw_num; if (sceAgcDriverSubmitAcb(queue, &packet) != 0) throw std::runtime_error("libSceJobManager: sceAgcDriverSubmitAcb failed"); } -void Sequence::CollectCompute() { - const std::uint64_t value = label.value; - const auto finished = static_cast(value - labelSeen); - labelSeen = value; - if (finished == 0) return; +void Sequence::SetComputeStorage(std::uint32_t* commands, std::size_t words, Label* completion) { + commandBegin = commands; + commandWords = words; + label = completion; + const GuestArena::HostWrite destination(label, sizeof(*label)); + label->value = 0; +} + +void Sequence::SubmitComputeAhead(Item* item) { + while (item != nullptr) { + if (item->type == ItemType::Compute || item->type == ItemType::ComputeIndirect) { + if (!item->encoded) { + SubmitCompute(*item); + item->encoded = true; + } + } else { + FlushCompute(); + if (item->type != ItemType::SyncPoint) return; + } + item = item->next; + } +} + +void Sequence::FlushCompute() { + if (pendingCompute == 0) return; + if (commandWords - commandUsed < 8) throw std::runtime_error("libSceJobManager: a compute completion did not fit its command buffer"); + auto* const words = commandBegin + commandUsed; + CommandBuffer buffer{words, words + 8, words, words + 8, nullptr, nullptr, 0}; + { + const GuestArena::HostWrite destination(words, 32); + if (sceAgcCbReleaseMem(&buffer, 0x2f, 0x200, 1, 3, label, 1, progress.Publish(pendingCompute), 0, 0, 2, 0) == nullptr) { + throw std::runtime_error("libSceJobManager: a compute completion could not be encoded"); + } + } + pendingCompute = 0; + const Packet packet{words, static_cast(buffer.cursor_up - words), 0, {}}; + commandUsed += packet.dw_num; + if (sceAgcDriverSubmitAcb(queue, &packet) != 0) throw std::runtime_error("libSceJobManager: sceAgcDriverSubmitAcb failed for a completion"); +} + +void Sequence::RetireCompute() { + const auto finished = std::min(activeCompute, availableCompute); + activeCompute -= finished; + availableCompute -= finished; completed.dispatches += finished; outstanding -= finished; +} + +void Sequence::CollectCompute() { + const auto finished = progress.Collect(label->value); + if (finished == 0) return; + availableCompute += finished; + RetireCompute(); if (outstanding == 0) Advance(); } @@ -390,8 +583,10 @@ int Sequence::AppendCompute(Item item) { if (!compute) throw std::runtime_error("libSceJobManager: a compute dispatch was made on a CPU sequence"); if (error != 0) return SequenceInvalid; if (submitted.dispatches++ >= limits.dispatches) return Latch(SequenceLimitExceeded); - if (!TakeNode(*manager)) return Latch(SequenceNoNodes); - items.push_back(std::move(item)); + Item* stored = TakeNode(*manager); + if (stored == nullptr) return Latch(SequenceNoNodes); + *stored = item; + Append(stored); Advance(); return 0; } @@ -399,22 +594,26 @@ int Sequence::AppendCompute(Item item) { int Sequence::DispatchCompute(const void* shader, void* userData, std::int32_t x, std::int32_t y, std::int32_t z, const char* name) { if (shader == nullptr) return SequenceNullArgument; if (x <= 0 || y <= 0 || z <= 0) return SequenceOutOfRange; - Item item{ItemType::Compute, this, nullptr, userData, 0, 0, 0, nullptr}; - item.shader = shader; - item.groups = {static_cast(x), static_cast(y), static_cast(z)}; - item.named = name != nullptr; - if (item.named) item.name = name; - return AppendCompute(std::move(item)); + Item item{}; + item.type = ItemType::Compute; + item.sequence = this; + item.gpu.shader = shader; + item.gpu.userData = userData; + item.gpu.groups = {static_cast(x), static_cast(y), static_cast(z)}; + item.name = name; + return AppendCompute(item); } int Sequence::DispatchComputeIndirect(const void* shader, void* userData, const void* indirectArguments, const char* name) { if (shader == nullptr || indirectArguments == nullptr || (reinterpret_cast(indirectArguments) & 0x1f) != 0) return SequenceNullArgument; - Item item{ItemType::ComputeIndirect, this, nullptr, userData, 0, 0, 0, nullptr}; - item.shader = shader; - item.indirectArguments = indirectArguments; - item.named = name != nullptr; - if (item.named) item.name = name; - return AppendCompute(std::move(item)); + Item item{}; + item.type = ItemType::ComputeIndirect; + item.sequence = this; + item.gpu.shader = shader; + item.gpu.userData = userData; + item.gpu.indirectArguments = indirectArguments; + item.name = name; + return AppendCompute(item); } int Sequence::SyncPoint() { @@ -423,8 +622,11 @@ int Sequence::SyncPoint() { if (error != 0) return SequenceInvalid; const std::uint32_t before = submitted.syncPoints++; if (before >= limits.syncPoints) return Latch(SequenceLimitExceeded); - if (!TakeNode(*manager)) return Latch(SequenceNoNodes); - items.push_back({ItemType::SyncPoint, this, nullptr, nullptr, 0, 0, 0, nullptr}); + Item* item = TakeNode(*manager); + if (item == nullptr) return Latch(SequenceNoNodes); + item->type = ItemType::SyncPoint; + item->sequence = this; + Append(item); Advance(); return 0; } @@ -437,13 +639,19 @@ int Sequence::Wait() { int Sequence::WaitAndClose(std::uint32_t index) { std::unique_lock guard(manager->lock); const int result = WaitLocked(guard); - const auto remaining = static_cast(std::count_if(items.begin(), items.end(), [](const Item& item) { return item.type != ItemType::Wait; })); - ReturnNodes(*manager, remaining + 1); - items.clear(); - commands.clear(); - cursor = 0; + while (first != nullptr) { + Item* item = first; + first = item->next; + if (item->type != ItemType::Wait) ReturnNode(*manager, item); + } + ReturnNode(*manager, reserved); + reserved = nullptr; + last = nullptr; + commandUsed = 0; + cursor = nullptr; outstanding = 0; open = false; + Profile(2); (compute ? manager->freeComputeSequences : manager->freeSequences) |= std::uint32_t{1} << index; return result; } @@ -458,10 +666,23 @@ int Sequence::WaitLocked(std::unique_lock& guard) { } ++manager->blockedWaiters; bool done = false; - items.push_back({ItemType::Wait, this, nullptr, nullptr, 0, 0, 0, &done}); + Item waiter{}; + waiter.type = ItemType::Wait; + waiter.sequence = this; + waiter.passed = &done; + Append(&waiter); Advance(); + if (!done && currentWorker == manager) { + std::uint64_t runnable = 0; + for (const auto& queue : manager->runQueues) { + for (const Item* item = queue.first; item != nullptr; item = item->runnableNext) runnable += item->cpu.count - item->cpu.handedOut; + } + WakeWorkers(*manager, static_cast(std::min(runnable, std::uint64_t{manager->workerCount})), false); + } passed.wait(guard, [&done] { return done; }); + RemoveWait(&waiter); --manager->blockedWaiters; + manager->quiesced.notify_all(); return result; } @@ -471,15 +692,15 @@ bool ValidLimits(std::uint32_t invocations, std::uint32_t dispatches, std::uint3 } template -int CreateIn(Manager* manager, TSequences& sequences, std::uint32_t& free, std::uint32_t priority, ISequence** out, const Counters& limits) { +int CreateIn(Manager* manager, TSequences& sequences, std::uint32_t& free, std::uint32_t priority, ISequence** out, const Counters& limits, const char* name) { if (out == nullptr) return SequenceNullArgument; if (!ValidLimits(limits.invocations, limits.dispatches, limits.syncPoints, limits.waits)) return SequenceOutOfRange; const std::lock_guard guard(manager->lock); if (free == 0) return SequenceNoneFree; const int index = std::countr_zero(free); free &= ~(std::uint32_t{1} << index); - Sequence& sequence = sequences[static_cast(index)]; - if (!sequence.Open(manager, limits, priority)) return SequenceNoNodes; + Sequence& sequence = sequences[static_cast(index)].value; + if (!sequence.Open(manager, limits, priority, name)) return SequenceNoNodes; *out = &sequence; return 0; } @@ -491,25 +712,25 @@ int DestroyIn(Manager* manager, TSequences& sequences, const std::uint32_t& free { const std::lock_guard guard(manager->lock); for (; index < sequences.size(); ++index) { - if (&sequences[index] == sequence) break; + if (&sequences[index].value == sequence) break; } if (index == sequences.size() || (free & (std::uint32_t{1} << index)) != 0) return SequenceInvalid; - found = &sequences[index]; + found = &sequences[index].value; } static_cast(found->WaitAndClose(index)); return 0; } -int Factory::Create(ISequence** out, std::uint32_t invocations, std::uint32_t dispatches, std::uint32_t syncPoints, std::uint32_t waits, const char*) { - return CreateIn(manager, manager->sequences, manager->freeSequences, priority, out, {invocations, dispatches, syncPoints, waits}); +int Factory::Create(ISequence** out, std::uint32_t invocations, std::uint32_t dispatches, std::uint32_t syncPoints, std::uint32_t waits, const char* name) { + return CreateIn(manager, manager->sequences, manager->freeSequences, priority, out, {invocations, dispatches, syncPoints, waits}, name); } int Factory::Destroy(ISequence* sequence) { return DestroyIn(manager, manager->sequences, manager->freeSequences, sequence); } -int Factory::CreateCompute(ISequence** out, std::uint32_t invocations, std::uint32_t dispatches, std::uint32_t syncPoints, std::uint32_t waits, const char*) { - return CreateIn(manager, manager->computeSequences, manager->freeComputeSequences, priority, out, {invocations, dispatches, syncPoints, waits}); +int Factory::CreateCompute(ISequence** out, std::uint32_t invocations, std::uint32_t dispatches, std::uint32_t syncPoints, std::uint32_t waits, const char* name) { + return CreateIn(manager, manager->computeSequences, manager->freeComputeSequences, priority, out, {invocations, dispatches, syncPoints, waits}, name); } int Factory::DestroyCompute(ISequence* sequence) { @@ -525,7 +746,7 @@ void* APS5_VABI ComputeHandlerEntry(void* argument) { if (count <= 0) continue; if (sceKernelGetEventFilter(&event) == UserEventFilter && sceKernelGetEventId(&event) == 0) return nullptr; const std::lock_guard guard(manager.lock); - for (Sequence& sequence : manager.computeSequences) sequence.CollectCompute(); + for (SequenceSlot& slot : manager.computeSequences) slot.value.CollectCompute(); } } @@ -533,44 +754,84 @@ bool RunOne(Manager& manager, std::unique_lock& guard) { Item* item = nullptr; for (const std::size_t queue : {std::size_t{1}, std::size_t{0}}) { auto& pending = manager.runQueues[queue]; - if (pending.empty()) continue; - item = pending.front(); - if (item->handedOut + 1 == item->count) pending.pop_front(); + if (pending.first == nullptr) continue; + item = pending.first; + if (item->cpu.handedOut + 1 == item->cpu.count) pending.Pop(); break; } if (item == nullptr) return false; - const std::uint32_t index = item->handedOut++; - const JobEntry entry = item->entry; - void* const argument = item->argument; + const std::uint32_t index = item->cpu.handedOut++; + const JobEntry entry = item->cpu.entry; + void* const argument = item->cpu.argument; + const auto dispatchId = static_cast(item - manager.nodes.data()); guard.unlock(); + static_cast(sceRazorCpuJobManagerJob(manager.profileId, dispatchId, static_cast(index), 16)); entry(argument, index); + static_cast(sceRazorCpuJobManagerJob(manager.profileId, dispatchId, static_cast(index), 32)); guard.lock(); - if (++item->completed == item->count) item->sequence->DispatchCompleted(item->count); + if (++item->cpu.completed == item->cpu.count) { + static_cast(sceRazorCpuJobManagerDispatch(item->name, manager.profileId, item->sequence->profileId, dispatchId, static_cast(item->cpu.count), reinterpret_cast(entry), reinterpret_cast(argument))); + item->sequence->DispatchCompleted(item->cpu.count); + } return true; } void* APS5_VABI WorkerEntry(void* argument) { Manager& manager = *static_cast(argument); + currentWorker = &manager; + std::uint64_t lastWork = __rdtsc(); std::unique_lock guard(manager.lock); while (manager.running) { - if (RunOne(manager, guard)) continue; - manager.work.wait(guard, [&manager] { return !manager.running || !manager.runQueues[0].empty() || !manager.runQueues[1].empty(); }); + if (RunOne(manager, guard)) { + lastWork = __rdtsc(); + continue; + } + const bool spinning = manager.pendingInvocations != 0 && __rdtsc() - lastWork < manager.spinTicks; + if (spinning || manager.sleepingWorkers >= manager.workerCount - manager.persistentThreads) { + guard.unlock(); + _mm_pause(); + guard.lock(); + continue; + } + ++manager.sleepingWorkers; + manager.work.wait(guard, [&manager] { return !manager.running || manager.wakeTokens != 0; }); + if (manager.wakeTokens != 0) --manager.wakeTokens; + --manager.sleepingWorkers; } + currentWorker = nullptr; return nullptr; } -std::uint32_t NodeCount(std::uint64_t memory, std::uint64_t size, std::uint32_t workers, std::uint32_t sequences, std::uint32_t computeQueues, bool* fits) { +struct WorkLayout { + std::uint64_t workers = 0; + std::uint64_t sequences = 0; + std::uint64_t stateEnd = 0; + std::uint64_t pool = 0; + std::uint64_t commands = 0; + std::uint64_t commandBytes = 0; + std::uint64_t labels = 0; + std::uint32_t nodes = 0; +}; + +WorkLayout LayoutFor(std::uint64_t memory, std::uint64_t size, std::uint32_t workers, std::uint32_t sequences, std::uint32_t computeQueues) { + WorkLayout layout; const std::uint64_t end = memory + size; - std::uint64_t address = AlignUp(memory + StateEnd, 8) + workers * ThreadRecordBytes; + layout.workers = AlignUp(memory + StateEnd, 8); + std::uint64_t address = layout.workers + workers * ThreadRecordBytes; for (const std::uint64_t bytes : {std::uint64_t{0x100}, std::uint64_t{0x80}, std::uint64_t{0x80}, std::uint64_t{0x80}}) address = AlignUp(address, 64) + bytes; - address = AlignUp(address, 64) + (sequences + computeQueues) * SequenceBytes; + layout.sequences = AlignUp(address, 64); + address = layout.sequences + (sequences + computeQueues) * SequenceBytes; + layout.stateEnd = address; if (computeQueues != 0) { - address = AlignUp(address + ComputeContextBytes, 0x100) + AlignUp(ComputeCommandBytes * computeQueues, 8); - address += AlignUp(4 * std::uint64_t{computeQueues}, 8) + 8 * std::uint64_t{computeQueues}; + layout.commandBytes = ComputeCommandBudget(); + layout.commands = AlignUp(address + ComputeContextBytes, 0x100); + layout.labels = AlignUp(layout.commands + layout.commandBytes * computeQueues + 4 * std::uint64_t{computeQueues}, 8); + address = layout.labels + 8 * std::uint64_t{computeQueues}; } address = AlignUp(address, 64); - *fits = address <= end; - return *fits ? static_cast(((end - address) / NodeBytes) & 0xffff) : 0; + layout.pool = address; + if (address <= end) layout.nodes = static_cast(((end - address) / NodeBytes) & 0xffff); + return layout; } int ValidateWorkers(const ConfigParams& config, std::uint32_t workers) { @@ -581,6 +842,17 @@ int ValidateWorkers(const ConfigParams& config, std::uint32_t workers) { return 0; } +class ThreadAttributes { +public: + ThreadAttributes() { + if (scePthreadAttrInit(&value) != 0) throw std::runtime_error("libSceJobManager: scePthreadAttrInit failed"); + } + ~ThreadAttributes() { static_cast(scePthreadAttrDestroy(&value)); } + ThreadAttributes(const ThreadAttributes&) = delete; + ThreadAttributes& operator=(const ThreadAttributes&) = delete; + PthreadAttr value = nullptr; +}; + void StartWorkers(Manager& manager, const ConfigParams& config, std::uint32_t workers) { std::uint32_t remaining = config.threadFlags & 0x1fff; for (std::uint32_t index = 0; index < workers; ++index) { @@ -589,46 +861,88 @@ void StartWorkers(Manager& manager, const ConfigParams& config, std::uint32_t wo affinity = remaining & (~remaining + 1); remaining &= remaining - 1; } - const std::string name = config.threadName != nullptr ? std::string(config.threadName).substr(0, 31) : "SceJobManagerWorker" + std::to_string(index); + std::array name{}; + if (config.threadName != nullptr) std::snprintf(name.data(), name.size(), "%.31s", config.threadName); + else std::snprintf(name.data(), name.size(), "SceJobManagerWorker%u", index); const KernelSchedParam schedule{static_cast(config.priority)}; - PthreadAttr attributes = nullptr; - if (scePthreadAttrInit(&attributes) != 0) throw std::runtime_error("libSceJobManager: scePthreadAttrInit failed"); - if (scePthreadAttrSetinheritsched(&attributes, 0) != 0 || scePthreadAttrSetschedparam(&attributes, &schedule) != 0 || - scePthreadAttrSetstacksize(&attributes, config.stackSize) != 0 || scePthreadAttrSetaffinity(&attributes, affinity) != 0) { + ThreadAttributes attributes; + if (scePthreadAttrSetinheritsched(&attributes.value, 0) != 0 || scePthreadAttrSetschedparam(&attributes.value, &schedule) != 0 || + scePthreadAttrSetstacksize(&attributes.value, config.stackSize) != 0 || scePthreadAttrSetaffinity(&attributes.value, affinity) != 0) { throw std::runtime_error("libSceJobManager: a worker thread attribute was rejected"); } Pthread thread = nullptr; - const int created = scePthreadCreate(&thread, &attributes, WorkerEntry, &manager, name.c_str()); - scePthreadAttrDestroy(&attributes); + const int created = scePthreadCreate(&thread, &attributes.value, WorkerEntry, &manager, name.data()); if (created != 0) throw std::runtime_error("libSceJobManager: scePthreadCreate failed for a worker"); - manager.workers.push_back(thread); + manager.workers[manager.activeWorkers++].thread = thread; } } -void StartCompute(Manager& manager, std::uint32_t computeSequences, std::uint64_t computeQueueMask, std::uint32_t priority, std::uint32_t affinity) { +void StartCompute(Manager& manager, const WorkLayout& layout, std::uint32_t computeSequences, std::uint64_t computeQueueMask, std::uint32_t priority, std::uint32_t affinity) { if (sceKernelCreateEqueue(&manager.computeEvents, "SceJobManagerComputeEventQueue") != 0) throw std::runtime_error("libSceJobManager: sceKernelCreateEqueue failed"); for (std::uint64_t mask = computeQueueMask; mask != 0; mask &= mask - 1) { const std::uint32_t queue = FirstComputeQueue + static_cast(std::countr_zero(mask)); if (sceAgcDriverAddEqEvent(manager.computeEvents, static_cast(queue), nullptr) != 0) throw std::runtime_error("libSceJobManager: sceAgcDriverAddEqEvent failed"); - manager.computeQueues.push_back(queue); + manager.computeQueues |= std::uint64_t{1} << (queue - FirstComputeQueue); } - manager.computeSequences.resize(computeSequences); + std::uint64_t availableQueues = computeQueueMask; for (std::uint32_t index = 0; index < computeSequences; ++index) { - manager.computeSequences[index].compute = true; - manager.computeSequences[index].queue = manager.computeQueues[index]; + Sequence& sequence = manager.computeSequences[index].value; + sequence.compute = true; + sequence.queue = FirstComputeQueue + static_cast(std::countr_zero(availableQueues)); + availableQueues &= availableQueues - 1; + sequence.SetComputeStorage(reinterpret_cast(layout.commands + index * layout.commandBytes), + static_cast(layout.commandBytes / 4), reinterpret_cast(layout.labels + index * sizeof(Label))); } manager.freeComputeSequences = computeSequences == MaxSequences ? ~std::uint32_t{0} : (std::uint32_t{1} << computeSequences) - 1; const KernelSchedParam schedule{static_cast(priority)}; - PthreadAttr attributes = nullptr; - if (scePthreadAttrInit(&attributes) != 0 || scePthreadAttrSetinheritsched(&attributes, 0) != 0 || scePthreadAttrSetschedparam(&attributes, &schedule) != 0 || - scePthreadAttrSetstacksize(&attributes, 0x4000) != 0 || scePthreadAttrSetaffinity(&attributes, affinity) != 0) { + ThreadAttributes attributes; + if (scePthreadAttrSetinheritsched(&attributes.value, 0) != 0 || scePthreadAttrSetschedparam(&attributes.value, &schedule) != 0 || + scePthreadAttrSetstacksize(&attributes.value, 0x4000) != 0 || scePthreadAttrSetaffinity(&attributes.value, affinity) != 0) { throw std::runtime_error("libSceJobManager: a compute handler thread attribute was rejected"); } - const int created = scePthreadCreate(&manager.computeHandler, &attributes, ComputeHandlerEntry, &manager, "SceJobManagerComputeEventHdlr"); - scePthreadAttrDestroy(&attributes); + const int created = scePthreadCreate(&manager.computeHandler, &attributes.value, ComputeHandlerEntry, &manager, "SceJobManagerComputeEventHdlr"); if (created != 0) throw std::runtime_error("libSceJobManager: scePthreadCreate failed for the compute handler"); } +void StopManager(Manager& manager) { + { + const std::lock_guard guard(manager.lock); + manager.running = false; + manager.work.notify_all(); + } + while (manager.activeWorkers != 0) { + if (scePthreadJoin(manager.workers[manager.activeWorkers - 1].thread, nullptr) != 0) throw std::runtime_error("sce::Job::JobManager::shutdown: scePthreadJoin failed"); + manager.workers[--manager.activeWorkers].thread = nullptr; + } + if (manager.computeHandler != nullptr) { + if (sceKernelAddUserEventEdge(manager.computeEvents, 0) != 0 || sceKernelTriggerUserEvent(manager.computeEvents, 0, nullptr) != 0) { + throw std::runtime_error("sce::Job::JobManager::shutdown: the compute handler could not be signalled"); + } + if (scePthreadJoin(manager.computeHandler, nullptr) != 0) throw std::runtime_error("sce::Job::JobManager::shutdown: scePthreadJoin failed"); + manager.computeHandler = nullptr; + } + if (manager.computeEvents != 0) { + for (std::uint64_t mask = manager.computeQueues; mask != 0; mask &= mask - 1) { + const auto queue = FirstComputeQueue + static_cast(std::countr_zero(mask)); + static_cast(sceAgcDriverDeleteEqEvent(manager.computeEvents, static_cast(queue))); + } + if (sceKernelDeleteEqueue(manager.computeEvents) != 0) throw std::runtime_error("sce::Job::JobManager::shutdown: sceKernelDeleteEqueue failed"); + manager.computeEvents = 0; + manager.computeQueues = 0; + } + std::unique_lock guard(manager.lock); + for (SequenceSlot& slot : manager.sequences) slot.value.CancelWaits(); + for (SequenceSlot& slot : manager.computeSequences) slot.value.CancelWaits(); + manager.quiesced.wait(guard, [&manager] { return manager.blockedWaiters == 0; }); +} + +void DestroyManager(Manager* manager) { + const GuestArena::HostWrite memory(manager->stateBegin, manager->stateBytes); + for (SequenceSlot& slot : manager->sequences) std::destroy_at(&slot); + for (SequenceSlot& slot : manager->computeSequences) std::destroy_at(&slot); + std::destroy_at(manager); +} + int Initialize(void* self, void* memory, std::uint64_t size, std::uint32_t workers, std::uint32_t sequences, std::uint32_t computeSequences, std::uint64_t computeQueueMask, const ConfigParams* config, bool unified) { if (Implementation(self) != nullptr) return ManagerAlreadyInitialized; if (memory == nullptr) return ManagerNullMemory; @@ -642,35 +956,76 @@ int Initialize(void* self, void* memory, std::uint64_t size, std::uint32_t worke } if (size < StateOffset) return ManagerMemoryTooSmall; if (size < StateEnd) throw std::runtime_error("sce::Job::JobManager::initialize: a memory block smaller than 0x2d8 bytes is not supported"); - std::memset(static_cast(memory) + StateOffset, 0, StateEnd - StateOffset); - const BlockHeader header{0x4d4a, static_cast(StateOffset), 0x05000001, static_cast(size), 0}; - std::memcpy(memory, &header, sizeof(header)); + { + const GuestArena::HostWrite destination(memory, StateEnd); + std::memset(static_cast(memory) + StateOffset, 0, StateEnd - StateOffset); + const BlockHeader header{0x4d4a, static_cast(StateOffset), 0x05000001, static_cast(size), 0}; + std::memcpy(memory, &header, sizeof(header)); + } if (config != nullptr && config->version > 1) return ManagerInvalidArgument; const ConfigParams& settings = config != nullptr ? *config : DefaultConfig; const auto computeQueues = computeSequences != 0 ? static_cast(std::popcount(computeQueueMask)) : 0; - bool fits = false; - const std::uint32_t nodes = NodeCount(base, size, workers, sequences, computeQueues, &fits); - if (!fits || nodes == 0) return ManagerMemoryTooSmall; + const auto layout = LayoutFor(base, size, workers, sequences, computeQueues); + if (layout.nodes == 0) return ManagerMemoryTooSmall; if (const int invalid = ValidateWorkers(settings, workers); invalid != 0) return invalid; const bool ownHandlerSettings = (settings.version & 1) != 0; const std::uint32_t handlerPriority = ownHandlerSettings ? settings.computePriority : 0x100; const std::uint32_t handlerAffinity = ownHandlerSettings ? settings.computeAffinity : 0x1fff; if (computeSequences != 0 && (handlerPriority < 0x100 || handlerPriority > 0x2ff || handlerAffinity == 0 || handlerAffinity > 0x1fff)) return ManagerInvalidArgument; - auto* manager = new Manager; + const GuestArena::HostWrite stateMemory(memory, static_cast(layout.stateEnd - base)); + const GuestArena::HostWrite nodeMemory(reinterpret_cast(layout.pool), layout.nodes * sizeof(Item)); + Manager* manager = std::construct_at(reinterpret_cast(base + StateOffset)); + manager->stateBegin = memory; + manager->stateBytes = static_cast(layout.stateEnd - base); + manager->nodes = {reinterpret_cast(layout.pool), layout.nodes}; + std::unique_ptr owner(manager, DestroyManager); manager->running = true; - manager->freeNodes = nodes - 1; + manager->workerCount = workers; + manager->spinTicks = sceKernelGetTscFrequency() / 1000000 * settings.spinMicroseconds; + for (std::uint32_t index = 0; index < layout.nodes; ++index) std::construct_at(&manager->nodes[index]); + for (std::uint32_t index = layout.nodes; index > 1; --index) ReturnNode(*manager, &manager->nodes[index - 1]); manager->freeSequences = sequences == MaxSequences ? ~std::uint32_t{0} : (std::uint32_t{1} << sequences) - 1; - manager->sequences = std::vector(sequences); + manager->workers = {reinterpret_cast(layout.workers), workers}; + for (WorkerSlot& worker : manager->workers) std::construct_at(&worker); + auto* cpuSlots = reinterpret_cast(layout.sequences); + auto* computeSlots = cpuSlots + sequences; + for (std::uint32_t index = 0; index < sequences; ++index) { + std::construct_at(cpuSlots + index); + cpuSlots[index].value.profileId = static_cast(index); + manager->sequences = {cpuSlots, index + 1}; + } + for (std::uint32_t index = 0; index < computeSequences; ++index) { + std::construct_at(computeSlots + index); + computeSlots[index].value.profileId = static_cast(sequences + index); + manager->computeSequences = {computeSlots, index + 1}; + } for (std::uint32_t priority = 0; priority < 2; ++priority) { manager->factories[priority].manager = manager; manager->factories[priority].priority = priority; manager->unifiedFactories[priority].manager = manager; manager->unifiedFactories[priority].priority = priority; } - if (computeSequences != 0) StartCompute(*manager, computeSequences, computeQueueMask, handlerPriority, handlerAffinity); - Implementation(self) = manager; - StartWorkers(*manager, settings, workers); - return 0; + try { + if (computeSequences != 0) StartCompute(*manager, layout, computeSequences, computeQueueMask, handlerPriority, handlerAffinity); + StartWorkers(*manager, settings, workers); + } catch (...) { + try { + StopManager(*manager); + } catch (...) { + Implementation(self) = owner.release(); + throw; + } + throw; + } + Implementation(self) = owner.release(); + const int result = _sceUlobjmgrRegisterObject_nid_postfix(base, 0x21, &manager->objectId); + std::uint32_t crc = 0; + for (std::uint32_t byte = 0; byte < sizeof(base); ++byte) { + crc ^= static_cast(base >> (byte * 8)); + for (std::uint32_t bit = 0; bit < 8; ++bit) crc = (crc >> 1) ^ (0x82f63b78u & (0u - (crc & 1))); + } + manager->profileId = static_cast(crc >> 16); + return result; } ISequenceFactory* FactoryFor(void* self, std::uint32_t priority, bool unified) { @@ -693,8 +1048,6 @@ void APS5_VABI _ZN3sce3Job10JobManagerC2Ev(void* self) { Implementation(self) = nullptr; } -int APS5_VABI _ZN3sce3Job10JobManager8shutdownEv(void* self); - void APS5_VABI _ZN3sce3Job10JobManagerD1Ev(void* self) { if (Implementation(self) != nullptr) { static_cast(_ZN3sce3Job10JobManager8shutdownEv(self)); @@ -703,6 +1056,7 @@ void APS5_VABI _ZN3sce3Job10JobManagerD1Ev(void* self) { void APS5_VABI _ZN3sce3Job10JobManagerD2Ev(void* self) { _ZN3sce3Job10JobManagerD1Ev(self); + } std::uint64_t APS5_VABI _ZN3sce3Job10JobManager27calculateRequiredMemorySizeEPKNS1_21MemorySizeQueryParamsE(const MemorySizeQueryParams* params) { @@ -724,7 +1078,7 @@ std::uint64_t APS5_VABI _ZN3sce3Job10JobManager27calculateRequiredMemorySizeEPKN if (computeSequences != 0) { const auto queues = static_cast(std::popcount(params->computeQueueMask)); bytes = (bytes + (params->sequences + queues) * SequenceBytes + 0x267) & ~std::uint64_t{0xff}; - bytes = (bytes + ComputeCommandBytes * queues + 3) & ~std::uint64_t{3}; + bytes = (bytes + ComputeCommandBudget() * queues + 3) & ~std::uint64_t{3}; bytes = (bytes + 4 * queues + 7) & ~std::uint64_t{7}; bytes += 8 * queues; } else { @@ -745,28 +1099,15 @@ int APS5_VABI _ZN3sce3Job10JobManager10initializeEPvmjjPKNS1_12ConfigParamsE(voi int APS5_VABI _ZN3sce3Job10JobManager8shutdownEv(void* self) { Manager* manager = Implementation(self); if (manager == nullptr) return ManagerNotInitialized; + if (currentWorker == manager) throw std::runtime_error("sce::Job::JobManager::shutdown: a worker cannot join itself"); { const std::lock_guard guard(manager->lock); - if (manager->blockedWaiters != 0) throw std::runtime_error("sce::Job::JobManager::shutdown: a thread is still blocked in a sequence wait"); - for (const Sequence& sequence : manager->computeSequences) { - if (sequence.open && sequence.Busy()) throw std::runtime_error("sce::Job::JobManager::shutdown: a compute sequence still has work in flight"); - } manager->running = false; - manager->work.notify_all(); - } - for (const Pthread worker : manager->workers) { - if (scePthreadJoin(worker, nullptr) != 0) throw std::runtime_error("sce::Job::JobManager::shutdown: scePthreadJoin failed"); - } - if (manager->computeHandler != nullptr) { - if (sceKernelAddUserEventEdge(manager->computeEvents, 0) != 0 || sceKernelTriggerUserEvent(manager->computeEvents, 0, nullptr) != 0) { - throw std::runtime_error("sce::Job::JobManager::shutdown: the compute handler could not be signalled"); - } - if (scePthreadJoin(manager->computeHandler, nullptr) != 0) throw std::runtime_error("sce::Job::JobManager::shutdown: scePthreadJoin failed"); - for (const std::uint32_t queue : manager->computeQueues) static_cast(sceAgcDriverDeleteEqEvent(manager->computeEvents, static_cast(queue))); - if (sceKernelDeleteEqueue(manager->computeEvents) != 0) throw std::runtime_error("sce::Job::JobManager::shutdown: sceKernelDeleteEqueue failed"); } + StopManager(*manager); + static_cast(_sceUlobjmgrUnregisterObject_nid_postfix(manager->objectId)); Implementation(self) = nullptr; - delete manager; + DestroyManager(manager); return 0; } @@ -781,7 +1122,7 @@ ISequenceFactory* APS5_VABI _ZN3sce3Job10JobManager34getUnifiedSequenceFactoryIn int APS5_VABI _ZN3sce3Job10JobManager24setPersistentThreadCountEj(void* self, std::uint32_t count) { Manager* manager = Implementation(self); if (manager == nullptr) return ManagerNotInitialized; - if (count > manager->workers.size()) return ManagerInvalidArgument; + if (count > manager->workerCount) return ManagerInvalidArgument; const std::lock_guard guard(manager->lock); manager->persistentThreads = count; return 0; diff --git a/core/libs/prx/libSceJobManager/include/Api.hpp b/core/libs/prx/libSceJobManager/include/Api.hpp new file mode 100644 index 0000000000..f76c944d21 --- /dev/null +++ b/core/libs/prx/libSceJobManager/include/Api.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct MemorySizeQueryParams { + std::uint32_t version; + std::uint32_t workers; + std::uint32_t sequences; + std::uint32_t invocations; + std::uint32_t dispatches; + std::uint32_t syncPoints; + std::uint32_t waits; + std::uint32_t computeSequences; + std::uint64_t computeQueueMask; +}; + +struct ConfigParams { + std::uint32_t version; + std::uint32_t threadFlags; + std::uint32_t stackSize; + std::uint32_t priority; + const char* threadName; + std::uint32_t spinMicroseconds; + std::uint32_t computeAffinity; + std::uint32_t computePriority; + std::uint32_t reserved; +}; + +using JobEntry = void (APS5_VABI*)(void* argument, std::uint32_t index); + +class ISequence { +public: + APS5_VABI virtual int Dispatch(JobEntry entry, void* argument, std::uint32_t count, const char* name) = 0; + APS5_VABI virtual int SyncPoint() = 0; + APS5_VABI virtual int Wait() = 0; + APS5_VABI virtual void CompleteDestructor() {} + APS5_VABI virtual void DeletingDestructor() { throw std::runtime_error("libSceJobManager: a sequence was deleted by its caller"); } + APS5_VABI virtual int DispatchCompute(const void* shader, void* userData, std::int32_t x, std::int32_t y, std::int32_t z, const char* name) = 0; + APS5_VABI virtual int DispatchComputeIndirect(const void* shader, void* userData, const void* indirectArguments, const char* name) = 0; +}; + +class ISequenceFactory { +public: + APS5_VABI virtual int Create(ISequence** out, std::uint32_t invocations, std::uint32_t dispatches, std::uint32_t syncPoints, std::uint32_t waits, const char* name) = 0; + APS5_VABI virtual int Destroy(ISequence* sequence) = 0; + APS5_VABI virtual void CompleteDestructor() {} + APS5_VABI virtual void DeletingDestructor() { throw std::runtime_error("libSceJobManager: a sequence factory was deleted by its caller"); } + APS5_VABI virtual int CreateCompute(ISequence** out, std::uint32_t invocations, std::uint32_t dispatches, std::uint32_t syncPoints, std::uint32_t waits, const char* name) = 0; + APS5_VABI virtual int DestroyCompute(ISequence* sequence) = 0; +}; + +extern "C" { + +void APS5_VABI _ZN3sce3Job10JobManagerC1Ev(void* self); +void APS5_VABI _ZN3sce3Job10JobManagerC2Ev(void* self); +void APS5_VABI _ZN3sce3Job10JobManagerD1Ev(void* self); +void APS5_VABI _ZN3sce3Job10JobManagerD2Ev(void* self); +std::uint64_t APS5_VABI _ZN3sce3Job10JobManager27calculateRequiredMemorySizeEPKNS1_21MemorySizeQueryParamsE(const MemorySizeQueryParams* params); +int APS5_VABI _ZN3sce3Job10JobManager10initializeEPvmjjjmPKNS1_12ConfigParamsE(void* self, void* memory, std::uint64_t memorySize, std::uint32_t workers, std::uint32_t sequences, std::uint32_t computeSequences, std::uint64_t computeQueueMask, const ConfigParams* config); +int APS5_VABI _ZN3sce3Job10JobManager10initializeEPvmjjPKNS1_12ConfigParamsE(void* self, void* memory, std::uint64_t memorySize, std::uint32_t workers, std::uint32_t sequences, const ConfigParams* config); +int APS5_VABI _ZN3sce3Job10JobManager8shutdownEv(void* self); +ISequenceFactory* APS5_VABI _ZN3sce3Job10JobManager27getSequenceFactoryInterfaceENS1_8PriorityE(void* self, std::uint32_t priority); +ISequenceFactory* APS5_VABI _ZN3sce3Job10JobManager34getUnifiedSequenceFactoryInterfaceENS1_8PriorityE(void* self, std::uint32_t priority); +int APS5_VABI _ZN3sce3Job10JobManager24setPersistentThreadCountEj(void* self, std::uint32_t count); + +} diff --git a/core/libs/prx/libSceJobManager/libSceJobManager.hpp b/core/libs/prx/libSceJobManager/libSceJobManager.hpp new file mode 100644 index 0000000000..740c6ddc49 --- /dev/null +++ b/core/libs/prx/libSceJobManager/libSceJobManager.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceJobManager/include/Api.hpp" diff --git a/core/libs/prx/libSceJpegDec/Export.cpp b/core/libs/prx/libSceJpegDec/Export.cpp index 19ec61c18b..d7d11c7277 100644 --- a/core/libs/prx/libSceJpegDec/Export.cpp +++ b/core/libs/prx/libSceJpegDec/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceJpegDec/include/Api.hpp" #include #include #include @@ -18,18 +19,18 @@ static_assert(sizeof(JpegDecImageInfo) == 0x24); namespace { -constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_ADDR = static_cast(0x80690001); -constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_SIZE = static_cast(0x80690002); -constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_PARAM = static_cast(0x80690003); -constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_HANDLE = static_cast(0x80690004); -constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_WORK_MEMORY = static_cast(0x80690005); -constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_DATA = static_cast(0x80690010); -constexpr std::int32_t SCE_JPEG_DEC_ERROR_DECODE_ERROR = static_cast(0x80690012); +constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_ADDR = static_cast(0x80650001); +constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_SIZE = static_cast(0x80650002); +constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_PARAM = static_cast(0x80650003); +constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_HANDLE = static_cast(0x80650004); +constexpr std::int32_t SCE_JPEG_DEC_ERROR_IMAGE_TOO_WIDE = static_cast(0x80650006); +constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_DATA = static_cast(0x80650010); +constexpr std::int32_t SCE_JPEG_DEC_ERROR_DECODE_ERROR = static_cast(0x80650012); constexpr std::uint32_t ATTRIBUTE_CSC_ORDINARY = 1; constexpr std::uint32_t ATTRIBUTE_CSC_FULL = 2; -constexpr std::uint32_t MEMORY_SIZE = 0x20; -constexpr std::uintptr_t HANDLE_ALIGNMENT = 8; +constexpr std::uint32_t MEMORY_OVERHEAD = 29536; +constexpr std::uintptr_t HANDLE_ALIGNMENT = 32; constexpr std::uint16_t DECODE_MODE_NORMAL = 0; constexpr std::uint16_t DOWN_SCALE_NONE = 1; @@ -40,7 +41,9 @@ constexpr std::uint16_t DOWN_SCALE_EIGHTH = 8; constexpr std::uint16_t PIXEL_FORMAT_R8G8B8A8 = 0; constexpr std::uint16_t PIXEL_FORMAT_B8G8R8A8 = 1; -constexpr std::uint16_t COLOR_SPACE_RGB = 3; +constexpr std::uint16_t COLOR_SPACE_UNKNOWN = 0; +constexpr std::uint16_t COLOR_SPACE_YCBCR = 1; +constexpr std::uint16_t COLOR_SPACE_GRAY = 2; constexpr std::uint32_t BYTES_PER_PIXEL = 4; constexpr std::uint32_t MAX_PACKED_DIMENSION = 32767; @@ -48,22 +51,31 @@ constexpr std::uint32_t MAX_PACKED_DIMENSION = 32767; struct Context { Context* self; std::uint32_t attribute; + std::uint32_t maxImageWidth; + std::uint32_t rowBufferCapacity; }; -static_assert(sizeof(Context) + HANDLE_ALIGNMENT - 1 <= MEMORY_SIZE); +static_assert(sizeof(Context) + HANDLE_ALIGNMENT - 1 <= MEMORY_OVERHEAD); std::int32_t validateCreateParam(const JpegDecCreateParam* param) { - if (!param) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + if (!param) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; if (param->this_size != sizeof(JpegDecCreateParam)) return SCE_JPEG_DEC_ERROR_INVALID_SIZE; if (param->attribute != ATTRIBUTE_CSC_ORDINARY && param->attribute != ATTRIBUTE_CSC_FULL) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + if (param->max_image_width == 0 || param->max_image_width > 65535) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; return 0; } -std::uint32_t requireDownScale(const char* function, std::uint16_t decodeMode, std::uint16_t downScale) { +std::uint32_t workMemorySize(const JpegDecCreateParam& param) { + const std::uint32_t alignedWidth = (param.max_image_width + 31) & ~std::uint32_t{31}; + return MEMORY_OVERHEAD + (alignedWidth + 16) * (param.attribute == ATTRIBUTE_CSC_ORDINARY ? 54 : 166); +} + +bool validDecodeOptions(std::uint16_t decodeMode, std::uint16_t downScale) { + return decodeMode <= 1 && (downScale == DOWN_SCALE_NONE || downScale == DOWN_SCALE_HALF || downScale == DOWN_SCALE_QUARTER || downScale == DOWN_SCALE_EIGHTH); +} + +void requireDecodeMode(const char* function, std::uint16_t decodeMode) { if (decodeMode != DECODE_MODE_NORMAL) throw std::runtime_error(std::string(function) + ": decode mode " + std::to_string(decodeMode) + " is not implemented"); - if (downScale != DOWN_SCALE_NONE && downScale != DOWN_SCALE_HALF && downScale != DOWN_SCALE_QUARTER && downScale != DOWN_SCALE_EIGHTH) - throw std::runtime_error(std::string(function) + ": down scale " + std::to_string(downScale) + " is not implemented"); - return downScale; } Context* toContext(void* handle) { @@ -77,13 +89,53 @@ void fillImageInfo(const Decoder::Jpeg::Header& header, std::uint32_t downScale, *info = {}; info->image_width = header.width; info->image_height = header.height; - info->color_space = COLOR_SPACE_RGB; + info->color_space = COLOR_SPACE_UNKNOWN; + if (header.channels == 1) info->color_space = COLOR_SPACE_GRAY; + if (header.channels == 3 && (header.app0BeforeFrame || (header.componentIds[0] == 1 && header.componentIds[1] == 2 && header.componentIds[2] == 3))) info->color_space = COLOR_SPACE_YCBCR; info->component_number = static_cast(header.channels); - info->suitable_csc_attribute = ATTRIBUTE_CSC_ORDINARY; + std::uint32_t maxHorizontal = 1; + std::uint32_t maxVertical = 1; + std::uint32_t blocksPerMcu = 0; + for (std::uint32_t i = 0; i < header.channels; ++i) { + info->component_id[i] = header.componentIds[i]; + info->sampling_factor[i] = header.samplingFactors[i]; + const std::uint32_t horizontal = header.channels == 1 ? 1 : header.samplingFactors[i] >> 4; + const std::uint32_t vertical = header.channels == 1 ? 1 : header.samplingFactors[i] & 15; + if (horizontal > maxHorizontal) maxHorizontal = horizontal; + if (vertical > maxVertical) maxVertical = vertical; + blocksPerMcu += horizontal * vertical; + } + if (info->color_space == COLOR_SPACE_GRAY) info->suitable_csc_attribute = ATTRIBUTE_CSC_ORDINARY; + if (info->color_space == COLOR_SPACE_YCBCR) info->suitable_csc_attribute = (12 / maxHorizontal) * blocksPerMcu < 37 ? ATTRIBUTE_CSC_ORDINARY : ATTRIBUTE_CSC_FULL; + if (header.progressive) { + const std::uint32_t mcuColumns = (header.width + 8 * maxHorizontal - 1) / (8 * maxHorizontal); + const std::uint32_t mcuRows = (header.height + 8 * maxVertical - 1) / (8 * maxVertical); + info->coefficient_mem_size = mcuColumns * mcuRows * blocksPerMcu * 128 + 64; + } info->output_image_width = Decoder::Jpeg::ScaledDimension(header.width, downScale); info->output_image_height = Decoder::Jpeg::ScaledDimension(header.height, downScale); } +std::uint32_t rowBufferSize(const Decoder::Jpeg::Header& header, std::uint32_t downScale) { + std::uint32_t maxHorizontal = 1; + if (header.channels != 1) { + for (std::uint32_t i = 0; i < header.channels; ++i) { + const std::uint32_t horizontal = header.samplingFactors[i] >> 4; + if (horizontal > maxHorizontal) maxHorizontal = horizontal; + } + } + const std::uint32_t mcuColumns = (header.width + 8 * maxHorizontal - 1) / (8 * maxHorizontal); + const std::uint32_t blockSize = 8 / downScale; + std::uint32_t size = 0; + for (std::uint32_t i = 0; i < header.channels; ++i) { + const std::uint32_t horizontal = header.channels == 1 ? 1 : header.samplingFactors[i] >> 4; + const std::uint32_t vertical = header.channels == 1 ? 1 : header.samplingFactors[i] & 15; + const std::uint32_t stride = (mcuColumns * blockSize * horizontal + 7) & ~std::uint32_t{7}; + size += stride * vertical * blockSize; + } + return (size + 31) & ~std::uint32_t{31}; +} + std::span toBytes(const void* data, std::uint32_t size) { return {static_cast(data), size}; } @@ -92,41 +144,55 @@ std::span toBytes(const void* data, std::uint32_t size) { extern "C" { -int32_t APS5_VABI sceJpegDecCreate(const JpegDecCreateParam* param, void* memory_address, uint32_t memory_size, void** handle) { +int32_t APS5_VABI sceJpegDecCreate(const JpegDecCreateParam* param, void* memoryAddress, uint32_t memorySize, void** handle) { const std::int32_t result = validateCreateParam(param); if (result != 0) return result; - if (!memory_address || !handle) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; - if (memory_size < MEMORY_SIZE) return SCE_JPEG_DEC_ERROR_INVALID_WORK_MEMORY; - const auto address = reinterpret_cast(memory_address); + if (!memoryAddress) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + if (memorySize < workMemorySize(*param)) return SCE_JPEG_DEC_ERROR_INVALID_SIZE; + if (!handle) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + const auto address = reinterpret_cast(memoryAddress); const std::uintptr_t aligned = (address + HANDLE_ALIGNMENT - 1) & ~(HANDLE_ALIGNMENT - 1); auto* context = new (reinterpret_cast(aligned)) Context{}; context->self = context; context->attribute = param->attribute; + context->maxImageWidth = (param->max_image_width + 31) & ~std::uint32_t{31}; + context->rowBufferCapacity = (context->maxImageWidth + 16) * (param->attribute == ATTRIBUTE_CSC_ORDINARY ? 24 : 80); *handle = context; return 0; } -int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param, JpegDecImageInfo* image_info) { +int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param, JpegDecImageInfo* imageInfo) { const Context* context = toContext(handle); if (!context) return SCE_JPEG_DEC_ERROR_INVALID_HANDLE; - (void)context; - if (!param) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; - if (!param->jpeg_mem_addr || !param->image_mem_addr || (reinterpret_cast(param->image_mem_addr) & 3u) != 0) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + if (!param) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + if (!param->jpeg_mem_addr || !param->image_mem_addr) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + if ((reinterpret_cast(param->image_mem_addr) & 3) != 0 && (param->pixel_format == 0 || param->pixel_format == 1 || param->pixel_format == 9 || param->pixel_format == 10)) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; if (param->jpeg_mem_size == 0 || param->image_mem_size == 0) return SCE_JPEG_DEC_ERROR_INVALID_SIZE; - if (param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; - if (param->alpha_value > 0xFF) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; - const std::uint32_t downScale = requireDownScale(__func__, param->decode_mode, param->down_scale); + if (!validDecodeOptions(param->decode_mode, param->down_scale)) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + if (param->pixel_format != 0 && param->pixel_format != 1 && param->pixel_format != 9 && param->pixel_format != 10 && param->pixel_format != 11) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + if (param->alpha_value > 255) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + requireDecodeMode(__func__, param->decode_mode); + if (param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) throw std::runtime_error("sceJpegDecDecode: pixel format is not implemented"); + const std::uint32_t downScale = param->down_scale; const std::span jpeg = toBytes(param->jpeg_mem_addr, param->jpeg_mem_size); const std::optional header = Decoder::Jpeg::ParseHeader(jpeg); if (!header) return SCE_JPEG_DEC_ERROR_INVALID_DATA; + JpegDecImageInfo parsedInfo{}; + fillImageInfo(*header, downScale, &parsedInfo); + if (parsedInfo.coefficient_mem_size != 0 && (!param->coefficient_mem_addr || param->coefficient_mem_size < parsedInfo.coefficient_mem_size)) return static_cast(0x80650005); + if (parsedInfo.color_space == COLOR_SPACE_UNKNOWN) return static_cast(0x80650011); const std::uint32_t outputWidth = Decoder::Jpeg::ScaledDimension(header->width, downScale); const std::uint32_t outputHeight = Decoder::Jpeg::ScaledDimension(header->height, downScale); const std::uint64_t rowSize = static_cast(outputWidth) * BYTES_PER_PIXEL; const std::uint64_t pitch = param->image_pitch == 0 ? rowSize : param->image_pitch; - if (pitch < rowSize) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; - if ((outputHeight - 1) * pitch + rowSize > param->image_mem_size) return SCE_JPEG_DEC_ERROR_INVALID_SIZE; + if (param->image_pitch != 0) { + if (pitch < rowSize || pitch % 4 != 0 || pitch * outputHeight >= 0x80000000 || pitch * outputHeight > param->image_mem_size) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + } else if (pitch * outputHeight > param->image_mem_size) { + return SCE_JPEG_DEC_ERROR_INVALID_SIZE; + } + if (((outputWidth + 31) & ~std::uint32_t{31}) > context->maxImageWidth || rowBufferSize(*header, downScale) > context->rowBufferCapacity) return SCE_JPEG_DEC_ERROR_IMAGE_TOO_WIDE; const std::optional image = downScale == DOWN_SCALE_NONE ? Decoder::Jpeg::Decode(jpeg) : Decoder::Jpeg::DecodeScaled(jpeg, downScale); if (!image || image->width != outputWidth || image->height != outputHeight) return SCE_JPEG_DEC_ERROR_DECODE_ERROR; @@ -153,7 +219,7 @@ int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param } } - if (image_info) fillImageInfo(*header, downScale, image_info); + if (imageInfo) fillImageInfo(*header, downScale, imageInfo); if (outputWidth > MAX_PACKED_DIMENSION || outputHeight > MAX_PACKED_DIMENSION) return 0; return static_cast(outputWidth << 16 | outputHeight); } @@ -165,21 +231,24 @@ int32_t APS5_VABI sceJpegDecDelete(void* handle) { return 0; } -int32_t APS5_VABI sceJpegDecParseHeader(const JpegDecParseParam* param, JpegDecImageInfo* image_info) { - if (!param) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; - if (!param->jpeg_mem_addr || !image_info) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; +int32_t APS5_VABI sceJpegDecParseHeader(const JpegDecParseParam* param, JpegDecImageInfo* imageInfo) { + if (!param) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + if (!param->jpeg_mem_addr) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; if (param->jpeg_mem_size == 0) return SCE_JPEG_DEC_ERROR_INVALID_SIZE; - const std::uint32_t downScale = requireDownScale(__func__, param->decode_mode, param->down_scale); + if (!validDecodeOptions(param->decode_mode, param->down_scale)) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + if (!imageInfo) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + requireDecodeMode(__func__, param->decode_mode); + const std::uint32_t downScale = param->down_scale; const std::optional header = Decoder::Jpeg::ParseHeader(toBytes(param->jpeg_mem_addr, param->jpeg_mem_size)); if (!header) return SCE_JPEG_DEC_ERROR_INVALID_DATA; - fillImageInfo(*header, downScale, image_info); + fillImageInfo(*header, downScale, imageInfo); return 0; } int32_t APS5_VABI sceJpegDecQueryMemorySize(const JpegDecCreateParam* param) { const std::int32_t result = validateCreateParam(param); if (result != 0) return result; - return static_cast(MEMORY_SIZE); + return static_cast(workMemorySize(*param)); } int APS5_VABI sceJpegDecDecodeWithInputControl() { diff --git a/core/libs/prx/libSceJpegDec/include/Api.hpp b/core/libs/prx/libSceJpegDec/include/Api.hpp new file mode 100644 index 0000000000..0f0892be0a --- /dev/null +++ b/core/libs/prx/libSceJpegDec/include/Api.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int32_t APS5_VABI sceJpegDecCreate(const JpegDecCreateParam* param, void* memoryAddress, uint32_t memorySize, void** handle); +int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param, JpegDecImageInfo* imageInfo); +int32_t APS5_VABI sceJpegDecDelete(void* handle); +int32_t APS5_VABI sceJpegDecParseHeader(const JpegDecParseParam* param, JpegDecImageInfo* imageInfo); +int32_t APS5_VABI sceJpegDecQueryMemorySize(const JpegDecCreateParam* param); +int APS5_VABI sceJpegDecDecodeWithInputControl(); + +} diff --git a/core/libs/prx/libSceJpegDec/libSceJpegDec.hpp b/core/libs/prx/libSceJpegDec/libSceJpegDec.hpp new file mode 100644 index 0000000000..e1581fb344 --- /dev/null +++ b/core/libs/prx/libSceJpegDec/libSceJpegDec.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceJpegDec/include/Api.hpp" diff --git a/core/libs/prx/libSceJpegEnc/Export.cpp b/core/libs/prx/libSceJpegEnc/Export.cpp index 6de513f53d..6a2407eebe 100644 --- a/core/libs/prx/libSceJpegEnc/Export.cpp +++ b/core/libs/prx/libSceJpegEnc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceJpegEnc/include/Api.hpp" #include #include #include @@ -82,7 +83,7 @@ std::int32_t validateEncodeParam(const JpegEncEncodeParam* param) { if (param->sampling_type != SAMPLING_TYPE_FULL && param->sampling_type != SAMPLING_TYPE_422 && param->sampling_type != SAMPLING_TYPE_420) { return SCE_JPEG_ENC_ERROR_INVALID_PARAM; } - if (param->restart_interval > static_cast(MAX_IMAGE_DIMENSION)) return SCE_JPEG_ENC_ERROR_INVALID_PARAM; + if (param->restart_interval < RESTART_INTERVAL_PER_ROW || param->restart_interval > static_cast(MAX_IMAGE_DIMENSION)) return SCE_JPEG_ENC_ERROR_INVALID_PARAM; switch (param->pixel_format) { case PIXEL_FORMAT_R8G8B8A8: @@ -186,27 +187,25 @@ int32_t APS5_VABI sceJpegEncDelete(void* handle) { return 0; } -int32_t APS5_VABI sceJpegEncEncode(void* handle, const JpegEncEncodeParam* param, JpegEncOutputInfo* output_info) { +int32_t APS5_VABI sceJpegEncEncode(void* handle, const JpegEncEncodeParam* param, JpegEncOutputInfo* outputInfo) { if (!toEncoder(handle)) return SCE_JPEG_ENC_ERROR_INVALID_HANDLE; const std::int32_t result = validateEncodeParam(param); if (result != 0) return result; if (param->encode_mode == ENCODE_MODE_MJPEG) throw std::runtime_error("sceJpegEncEncode: MJPEG encode mode is not implemented"); - if (param->restart_interval < RESTART_INTERVAL_PER_ROW) throw std::runtime_error("sceJpegEncEncode: unknown negative restart interval"); const std::uint32_t channels = param->pixel_format == PIXEL_FORMAT_Y8 ? 1 : 3; const std::vector pixels = toPackedPixels(*param, channels); const std::uint32_t restartBlocks = param->restart_interval > 0 ? static_cast(param->restart_interval) : 0; const std::uint32_t restartRows = param->restart_interval == RESTART_INTERVAL_PER_ROW ? 1 : 0; - const std::vector jpeg = Decoder::Jpeg::Encode(pixels, param->image_width, param->image_height, channels, toQuality(*param), toSampling(*param), - restartBlocks, restartRows); + const std::vector jpeg = Decoder::Jpeg::Encode(pixels, param->image_width, param->image_height, channels, toQuality(*param), toSampling(*param), restartBlocks, restartRows); if (jpeg.size() > param->jpeg_size) return SCE_JPEG_ENC_ERROR_INVALID_SIZE; std::memcpy(param->jpeg, jpeg.data(), jpeg.size()); - if (output_info) { - output_info->size = static_cast(jpeg.size()); - output_info->height = param->image_height; + if (outputInfo) { + outputInfo->size = static_cast(jpeg.size()); + outputInfo->height = param->image_height; } - return 0; + return static_cast(jpeg.size()); } int32_t APS5_VABI sceJpegEncQueryMemorySize(const JpegEncCreateParam* param) { diff --git a/core/libs/prx/libSceJpegEnc/include/Api.hpp b/core/libs/prx/libSceJpegEnc/include/Api.hpp new file mode 100644 index 0000000000..25215e5d07 --- /dev/null +++ b/core/libs/prx/libSceJpegEnc/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int32_t APS5_VABI sceJpegEncCreate(const JpegEncCreateParam* param, void* memory, uint32_t memorySize, void** handle); +int32_t APS5_VABI sceJpegEncDelete(void* handle); +int32_t APS5_VABI sceJpegEncEncode(void* handle, const JpegEncEncodeParam* param, JpegEncOutputInfo* outputInfo); +int32_t APS5_VABI sceJpegEncQueryMemorySize(const JpegEncCreateParam* param); + +} diff --git a/core/libs/prx/libSceJpegEnc/libSceJpegEnc.hpp b/core/libs/prx/libSceJpegEnc/libSceJpegEnc.hpp new file mode 100644 index 0000000000..a5548622ca --- /dev/null +++ b/core/libs/prx/libSceJpegEnc/libSceJpegEnc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceJpegEnc/include/Api.hpp" diff --git a/core/libs/prx/libSceJpegParser/libSceJpegParser.hpp b/core/libs/prx/libSceJpegParser/libSceJpegParser.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceJpegParser/libSceJpegParser.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceJsc/libSceJsc.hpp b/core/libs/prx/libSceJsc/libSceJsc.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceJsc/libSceJsc.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceJscCompiler/libSceJscCompiler.hpp b/core/libs/prx/libSceJscCompiler/libSceJscCompiler.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceJscCompiler/libSceJscCompiler.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceJson/libSceJson.hpp b/core/libs/prx/libSceJson/libSceJson.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceJson/libSceJson.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceJson2/CMakeLists.txt b/core/libs/prx/libSceJson2/CMakeLists.txt index f3e7cb682f..cf4780cf80 100644 --- a/core/libs/prx/libSceJson2/CMakeLists.txt +++ b/core/libs/prx/libSceJson2/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceJson2 SHARED EXCLUDE_FROM_ALL Export.cpp) +add_library(libSceJson2 SHARED EXCLUDE_FROM_ALL Export.cpp InventoryExports.cpp) target_include_directories(libSceJson2 PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceJson2) @@ -8,8 +8,19 @@ if(BUILD_TESTING) target_link_libraries(guest_json_tests PRIVATE libSceJson2 libc) configure_windows_unwind(guest_json_tests) add_test(NAME guest_json COMMAND guest_json_tests) + add_test(NAME guest_json_numeric_locale COMMAND guest_json_tests --numeric-locale) + set_tests_properties(guest_json_numeric_locale PROPERTIES SKIP_RETURN_CODE 77) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(numeric_locale_dir ${CMAKE_CURRENT_BINARY_DIR}/numeric-locale) + set(numeric_locale_data ${LIBS_INCLUDE_DIR}/tests/numeric-locale) + add_test(NAME guest_json_numeric_locale_setup COMMAND sh -c + "rm -rf '${numeric_locale_dir}' && mkdir -p '${numeric_locale_dir}' && I18NPATH='${numeric_locale_dir}' localedef -c -f '${numeric_locale_data}/comma.charmap' -i '${numeric_locale_data}/comma.locale' '${numeric_locale_dir}/xx_COMMA'; test -f '${numeric_locale_dir}/xx_COMMA/LC_NUMERIC'") + set_tests_properties(guest_json_numeric_locale_setup PROPERTIES FIXTURES_SETUP json_numeric_locale) + set_tests_properties(guest_json_numeric_locale PROPERTIES FIXTURES_REQUIRED json_numeric_locale + ENVIRONMENT "LOCPATH=${numeric_locale_dir}") + endif() if(WIN32) - set_tests_properties(guest_json PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_json guest_json_numeric_locale PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceJson2/Export.cpp b/core/libs/prx/libSceJson2/Export.cpp index 6231181390..477b3d020f 100644 --- a/core/libs/prx/libSceJson2/Export.cpp +++ b/core/libs/prx/libSceJson2/Export.cpp @@ -1,76 +1,34 @@ +#include "prx/libSceJson2/include/Api.hpp" #include +#include #include +#include #include #include #include #include #include #include +#include #include +#include +#include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" namespace { -enum ValueType : std::int32_t { - TypeNull = 0, - TypeBoolean = 1, - TypeInteger = 2, - TypeUInteger = 3, - TypeReal = 4, - TypeString = 5, - TypeArray = 6, - TypeObject = 7, -}; +using namespace SceJson2; -constexpr int JsonErrorParse = static_cast(0x80920101); +constexpr int JsonErrorParse = static_cast(0x80848101); -struct Node; -struct Value { Node* node; }; -struct String { std::string* text; }; -struct Array { std::list* items; }; -struct Pair; -struct Object { std::list* items; }; -struct ArrayIterator { std::list::iterator it; }; -struct ObjectIterator { std::list::iterator it; }; -struct Pair { - String key; - std::uint64_t reserved; - Value value; -}; static_assert(sizeof(Value) == 8 && sizeof(String) == 8 && sizeof(Array) == 8 && sizeof(Object) == 8); static_assert(sizeof(ArrayIterator) == 8 && sizeof(ObjectIterator) == 8); static_assert(offsetof(Pair, value) == 0x10); -using TypeMismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); -using SpecialFloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); -using ElementAccessFailureHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); - -struct Handlers { - TypeMismatchHandler typeMismatch = nullptr; - void* typeMismatchContext = nullptr; - SpecialFloatHandler specialFloat = nullptr; - void* specialFloatContext = nullptr; - ElementAccessFailureHandler elementAccessFailure = nullptr; - void* elementAccessFailureContext = nullptr; -}; - -struct Node { - ValueType type = TypeNull; - bool boolean = false; - std::int64_t integer = 0; - std::uint64_t uinteger = 0; - double real = 0; - String string{}; - Array array{}; - Object object{}; - Handlers handlers{}; -}; - -using NullAccessCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*); - struct Globals { std::mutex mutex; NullAccessCallback callback = nullptr; @@ -289,8 +247,9 @@ void AppendEscaped(std::string& out, const std::string& text) { std::string RealText(double value) { if (!std::isfinite(value)) throw std::runtime_error("sce::Json: cannot serialize a non-finite number"); char buffer[32]; - std::snprintf(buffer, sizeof(buffer), "%.17g", value); - return buffer; + const auto result = std::to_chars(buffer, buffer + sizeof(buffer), value, std::chars_format::general, 17); + if (result.ec != std::errc{}) throw std::runtime_error("sce::Json: real number formatting failed"); + return std::string(buffer, result.ptr); } void Serialize(std::string& out, const Node& n) { @@ -333,30 +292,82 @@ void Serialize(std::string& out, const Node& n) { throw std::runtime_error("sce::Json: invalid value type"); } +int CompareText(const char* left, const char* right) { + while (*left != '\0' && *left == *right) { + ++left; + ++right; + } + return static_cast(*left) - static_cast(*right); +} + +std::list::iterator ObjectPosition(const Object& object, const std::string& key) { + return std::find_if(object.items->begin(), object.items->end(), [&](const Pair& pair) { return std::strcmp(pair.key.text->c_str(), key.c_str()) >= 0; }); +} + +Value& ObjectEntry(Object& object, const std::string& key) { + auto position = ObjectPosition(object, key); + if (position != object.items->end() && std::strcmp(position->key.text->c_str(), key.c_str()) == 0) return position->value; + Pair pair{}; + Construct(pair.key, key); + try { + Construct(pair.value); + return object.items->insert(position, pair)->value; + } catch (...) { + Destroy(pair.value); + Destroy(pair.key); + throw; + } +} + class Parser { public: - Parser(const char* text, std::size_t size, const Handlers& handlers) : _text(text), _end(text + size), _handlers(handlers) {} + Parser(const char* text, std::size_t size, const Handlers& handlers) : text(text), end(text + size), handlers(handlers) {} + Parser(ParserReadCallback read, void* context, const Handlers& handlers) : read(read), context(context), handlers(handlers) {} bool Parse(Node& out) { skip(); - if (!value(out)) return false; - skip(); - return _text == _end || *_text == '\0'; + return value(out); } + int Error() const { return readError < 0 ? readError : JsonErrorParse; } + private: static constexpr std::size_t MaxNestingDepth = 512; - const char* _text; - const char* _end; - Handlers _handlers; - std::size_t _depth = 0; + const char* text = nullptr; + const char* end = nullptr; + ParserReadCallback read = nullptr; + void* context = nullptr; + Handlers handlers; + std::size_t depth = 0; + int current = -2; + int readError = 0; - void skip() { while (_text < _end && (*_text == ' ' || *_text == '\t' || *_text == '\n' || *_text == '\r')) ++_text; } + int peek() { + if (current != -2) return current; + if (read != nullptr) { + char character = 0; + readError = read(character, context); + current = readError < 0 ? -1 : static_cast(character); + } else if (text == end) { + readError = JsonErrorParse; + current = -1; + } else { + current = static_cast(*text++); + } + return current; + } + int take() { + const int character = peek(); + if (character >= 0) current = -2; + return character; + } + void skip() { while (peek() == ' ' || peek() == '\t' || peek() == '\n' || peek() == '\r') take(); } bool literal(const char* word) { - const auto length = std::strlen(word); - if (static_cast(_end - _text) < length || std::memcmp(_text, word, length) != 0) return false; - _text += length; + for (; *word != '\0'; ++word) { + if (peek() != static_cast(*word)) return false; + take(); + } return true; } static void appendUtf8(std::string& out, std::uint32_t code) { @@ -366,10 +377,9 @@ private: else { out += static_cast(0xf0 | (code >> 18)); out += static_cast(0x80 | ((code >> 12) & 0x3f)); out += static_cast(0x80 | ((code >> 6) & 0x3f)); out += static_cast(0x80 | (code & 0x3f)); } } bool hex4(std::uint32_t& code) { - if (_end - _text < 4) return false; code = 0; for (int i = 0; i < 4; ++i) { - const char c = *_text++; + const int c = take(); code <<= 4; if (c >= '0' && c <= '9') code |= static_cast(c - '0'); else if (c >= 'a' && c <= 'f') code |= static_cast(c - 'a' + 10); @@ -379,14 +389,14 @@ private: return true; } bool string(std::string& out) { - if (_text >= _end || *_text != '"') return false; - ++_text; - while (_text < _end && *_text != '"') { - const char c = *_text++; + if (peek() < 0 || peek() != '"') return false; + take(); + while (peek() >= 0 && peek() != '"') { + const int c = take(); if (static_cast(c) < 0x20) return false; if (c != '\\') { out += c; continue; } - if (_text >= _end) return false; - switch (*_text++) { + if (peek() < 0) return false; + switch (take()) { case '"': out += '"'; break; case '\\': out += '\\'; break; case '/': out += '/'; break; @@ -409,31 +419,30 @@ private: default: return false; } } - if (_text >= _end) return false; - ++_text; + if (peek() < 0) return false; + take(); return true; } bool number(Node& out) { - const char* start = _text; - if (_text < _end && *_text == '-') ++_text; - if (_text >= _end || *_text < '0' || *_text > '9') return false; - if (*_text == '0') ++_text; - else while (_text < _end && *_text >= '0' && *_text <= '9') ++_text; + std::string token; + if (peek() >= 0 && peek() == '-') token.push_back(static_cast(take())); + if (peek() < 0 || peek() < '0' || peek() > '9') return false; + while (peek() >= '0' && peek() <= '9') token.push_back(static_cast(take())); bool integral = true; - if (_text < _end && *_text == '.') { + if (peek() >= 0 && peek() == '.') { integral = false; - ++_text; - if (_text >= _end || *_text < '0' || *_text > '9') return false; - while (_text < _end && *_text >= '0' && *_text <= '9') ++_text; + token.push_back(static_cast(take())); + if (peek() < 0 || peek() < '0' || peek() > '9') return false; + while (peek() >= 0 && peek() >= '0' && peek() <= '9') token.push_back(static_cast(take())); } - if (_text < _end && (*_text == 'e' || *_text == 'E')) { + if (peek() >= 0 && (peek() == 'e' || peek() == 'E')) { integral = false; - ++_text; - if (_text < _end && (*_text == '+' || *_text == '-')) ++_text; - if (_text >= _end || *_text < '0' || *_text > '9') return false; - while (_text < _end && *_text >= '0' && *_text <= '9') ++_text; + token.push_back(static_cast(take())); + if (peek() >= 0 && (peek() == '+' || peek() == '-')) token.push_back(static_cast(take())); + if (peek() < 0 || peek() < '0' || peek() > '9') return false; + while (peek() >= 0 && peek() >= '0' && peek() <= '9') token.push_back(static_cast(take())); } - const std::string text(start, _text); + const std::string& text = token; if (integral) { errno = 0; if (text[0] == '-') { @@ -442,21 +451,29 @@ private: } else { const unsigned long long value = std::strtoull(text.c_str(), nullptr, 10); if (errno == 0) { - if (value <= static_cast(INT64_MAX)) { out.type = TypeInteger; out.integer = static_cast(value); } - else { out.type = TypeUInteger; out.uinteger = value; } + out.type = TypeUInteger; + out.uinteger = value; return true; } } } - const double real = std::strtod(text.c_str(), nullptr); - if (!std::isfinite(real)) throw std::runtime_error("sce::Json::Parser::parse: number " + text + " overflows a double; the console's behaviour is unverified"); + double real = 0; + const auto parsed = std::from_chars(text.data(), text.data() + text.size(), real); + bool converted = parsed.ec == std::errc{} && parsed.ptr == text.data() + text.size(); + if (parsed.ec == std::errc::result_out_of_range) { + std::istringstream stream(text); + stream.imbue(std::locale::classic()); + stream >> real; + converted = !stream.fail() && stream.eof(); + } + 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; out.real = real; return true; } bool value(Node& out) { - if (_text >= _end) return false; - switch (*_text) { + if (peek() < 0) return false; + switch (peek()) { case 'n': if (!literal("null")) return false; SetType(out, TypeNull); return true; case 't': if (!literal("true")) return false; SetType(out, TypeBoolean); out.boolean = true; return true; case 'f': if (!literal("false")) return false; SetType(out, TypeBoolean); return true; @@ -468,49 +485,46 @@ private: return true; } case '[': { - if (_depth == MaxNestingDepth) return false; - ++_text; - ++_depth; + if (depth == MaxNestingDepth) return false; + take(); + ++depth; SetType(out, TypeArray); skip(); - if (_text < _end && *_text == ']') { ++_text; --_depth; return true; } + if (peek() >= 0 && peek() == ']') { take(); --depth; return true; } for (;;) { - Value item{}; + auto& item = out.array.items->emplace_back(); Construct(item); - item.node->handlers = _handlers; - out.array.items->push_back(item); + item.node->handlers = handlers; skip(); if (!value(*item.node)) return false; skip(); - if (_text < _end && *_text == ',') { ++_text; continue; } - if (_text < _end && *_text == ']') { ++_text; --_depth; return true; } + if (peek() >= 0 && peek() == ',') { take(); continue; } + if (peek() >= 0 && peek() == ']') { take(); --depth; return true; } return false; } } case '{': { - if (_depth == MaxNestingDepth) return false; - ++_text; - ++_depth; + if (depth == MaxNestingDepth) return false; + take(); + ++depth; SetType(out, TypeObject); skip(); - if (_text < _end && *_text == '}') { ++_text; --_depth; return true; } + if (peek() >= 0 && peek() == '}') { take(); --depth; return true; } for (;;) { skip(); std::string key; if (!string(key)) return false; skip(); - if (_text >= _end || *_text != ':') return false; - ++_text; + if (peek() < 0 || peek() != ':') return false; + take(); skip(); - Pair pair{}; - Construct(pair.key, key); - Construct(pair.value); - pair.value.node->handlers = _handlers; - out.object.items->push_back(pair); - if (!value(*pair.value.node)) return false; + Value& entry = ObjectEntry(out.object, key); + Clear(*entry.node); + entry.node->handlers = handlers; + if (!value(*entry.node)) return false; skip(); - if (_text < _end && *_text == ',') { ++_text; continue; } - if (_text < _end && *_text == '}') { ++_text; --_depth; return true; } + if (peek() >= 0 && peek() == ',') { take(); continue; } + if (peek() >= 0 && peek() == '}') { take(); --depth; return true; } return false; } } @@ -520,6 +534,25 @@ private: } }; +int ParseValue(Value* out, Parser& parser) { + Node parsed{}; + parsed.handlers = NodeOf(*out).handlers; + bool parsedOk = false; + try { + parsedOk = parser.Parse(parsed); + } catch (...) { + Clear(parsed); + throw; + } + if (!parsedOk) { + Clear(parsed); + return parser.Error(); + } + Clear(NodeOf(*out)); + *out->node = parsed; + return 0; +} + [[noreturn]] void APS5_VABI JsonMemAllocatorPureVirtual() { throw std::runtime_error("sce::Json::MemAllocator: pure virtual function called"); } @@ -531,16 +564,6 @@ void* const JsonMemAllocatorVtable[] = { reinterpret_cast(&JsonMemAllocatorPureVirtual), reinterpret_cast(&JsonMemAllocatorPureVirtual), }; -Value& ObjectEntry(Object& object, const std::string& key) { - for (auto& pair : *object.items) - if (*pair.key.text == key) return pair.value; - Pair pair{}; - Construct(pair.key, key); - Construct(pair.value); - object.items->push_back(pair); - return object.items->back().value; -} - } extern "C" { @@ -609,8 +632,8 @@ bool APS5_VABI _ZNK3sce4Json6String5emptyEv(const String* self) { return self->t std::size_t APS5_VABI _ZNK3sce4Json6String6lengthEv(const String* self) { return self->text->size(); } std::size_t APS5_VABI _ZNK3sce4Json6String4sizeEv(const String* self) { return self->text->size(); } std::size_t APS5_VABI _ZNK3sce4Json6String8capacityEv(const String* self) { return self->text->capacity(); } -bool APS5_VABI _ZNK3sce4Json6StringeqEPKc(const String* self, const char* text) { return text != nullptr && *self->text == text; } -bool APS5_VABI _ZNK3sce4Json6StringeqERKS1_(const String* self, const String* other) { return other != nullptr && other->text != nullptr && *self->text == *other->text; } +bool APS5_VABI _ZNK3sce4Json6StringeqEPKc(const String* self, const char* text) { return text != nullptr && CompareText(self->text->c_str(), text) == 0; } +bool APS5_VABI _ZNK3sce4Json6StringeqERKS1_(const String* self, const String* other) { return other != nullptr && other->text != nullptr && CompareText(self->text->c_str(), other->text->c_str()) == 0; } void APS5_VABI _ZN3sce4Json6String5clearEv(String* self) { self->text->clear(); } void APS5_VABI _ZN3sce4Json6String6resizeEm(String* self, std::size_t count) { self->text->resize(count); } void APS5_VABI _ZN3sce4Json6String7reserveEm(String* self, std::size_t capacity) { self->text->reserve(capacity); } @@ -643,18 +666,54 @@ String* APS5_VABI _ZNK3sce4Json6String6substrEmm(String* result, const String* s } std::size_t APS5_VABI _ZNK3sce4Json6String4findEcm(const String* self, char character, std::size_t position) { return self->text->find(character, position); } std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEcm(const String* self, char character, std::size_t position) { return self->text->rfind(character, position); } +char APS5_VABI _ZNK3sce4Json6String2atEm(const String* self, std::size_t position) { + if (position >= self->text->size()) throw std::out_of_range("sce::Json::String::at: position past the end"); + return (*self->text)[position]; +} +std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcm(const String* self, const char* text, std::size_t position) { + if (text == nullptr) throw std::invalid_argument("sce::Json::String::find: null text"); + return self->text->find(text, position); +} +std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcmm(const String* self, const char* text, std::size_t position, std::size_t length) { + if (text == nullptr && length > 0) throw std::invalid_argument("sce::Json::String::find: null text"); + return self->text->find(std::string_view(text == nullptr ? "" : text, length), position); +} +std::size_t APS5_VABI _ZNK3sce4Json6String4findERKS1_m(const String* self, const String* other, std::size_t position) { + if (other == nullptr || other->text == nullptr) throw std::invalid_argument("sce::Json::String::find: null other"); + return self->text->find(*other->text, position); +} +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcm(const String* self, const char* text, std::size_t position) { + if (text == nullptr) throw std::invalid_argument("sce::Json::String::rfind: null text"); + return self->text->rfind(text, position); +} +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcmm(const String* self, const char* text, std::size_t position, std::size_t length) { + if (text == nullptr && length > 0) throw std::invalid_argument("sce::Json::String::rfind: null text"); + return self->text->rfind(std::string_view(text == nullptr ? "" : text, length), position); +} +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindERKS1_m(const String* self, const String* other, std::size_t position) { + if (other == nullptr || other->text == nullptr) throw std::invalid_argument("sce::Json::String::rfind: null other"); + return self->text->rfind(*other->text, position); +} int APS5_VABI _ZNK3sce4Json6String7compareEPKc(const String* self, const char* text) { if (text == nullptr) throw std::invalid_argument("sce::Json::String::compare: null text"); - return self->text->compare(text); + return CompareText(self->text->c_str(), text); } int APS5_VABI _ZNK3sce4Json6String7compareERKS1_(const String* self, const String* other) { if (other == nullptr || other->text == nullptr) throw std::invalid_argument("sce::Json::String::compare: null other"); - return self->text->compare(*other->text); + return CompareText(self->text->c_str(), other->text->c_str()); } void APS5_VABI _ZN3sce4Json5ArrayC1Ev(Array* self) { Construct(*self); } void APS5_VABI _ZN3sce4Json5ArrayC1ERKS1_(Array* self, const Array* other) { CopyArray(*self, *other); } void APS5_VABI _ZN3sce4Json5ArrayD1Ev(Array* self) { Destroy(*self); } +Array* APS5_VABI _ZN3sce4Json5ArrayaSERKS1_(Array* self, const Array* other) { + if (self == other) return self; + Array copy{}; + CopyArray(copy, *other); + Destroy(*self); + *self = copy; + return self; +} void APS5_VABI _ZN3sce4Json5Array9push_backERKNS0_5ValueE(Array* self, const Value* value) { Value copy{}; CopyInto(copy, *value); @@ -665,8 +724,8 @@ void APS5_VABI _ZN3sce4Json5Array9push_backERKNS0_5ValueE(Array* self, const Val throw; } } -ArrayIterator* APS5_VABI _ZNK3sce4Json5Array5beginEv(ArrayIterator* result, const Array* self) { result->it = self->items->begin(); return result; } -ArrayIterator* APS5_VABI _ZNK3sce4Json5Array3endEv(ArrayIterator* result, const Array* self) { result->it = self->items->end(); return result; } +ArrayIterator* APS5_VABI _ZNK3sce4Json5Array5beginEv(ArrayIterator* result, const Array* self) { result->state = new ArrayIteratorState{self->items->begin(), self->items->end()}; return result; } +ArrayIterator* APS5_VABI _ZNK3sce4Json5Array3endEv(ArrayIterator* result, const Array* self) { result->state = new ArrayIteratorState{self->items->end(), self->items->end()}; return result; } bool APS5_VABI _ZNK3sce4Json5Array5emptyEv(const Array* self) { return self->items->empty(); } std::size_t APS5_VABI _ZNK3sce4Json5Array4sizeEv(const Array* self) { return self->items->size(); } void APS5_VABI _ZN3sce4Json5Array5clearEv(Array* self) { @@ -681,14 +740,37 @@ const Value* APS5_VABI _ZNK3sce4Json5Array4backEv(const Array* self) { if (self->items->empty()) throw std::out_of_range("sce::Json::Array::back: empty array"); return &self->items->back(); } -void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(ArrayIterator* self) { (void)self; } -ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(ArrayIterator* self) { ++self->it; return self; } -Value* APS5_VABI _ZNK3sce4Json5Array8iteratordeEv(const ArrayIterator* self) { return &*self->it; } -Value* APS5_VABI _ZNK3sce4Json5Array8iteratorptEv(const ArrayIterator* self) { return &*self->it; } -bool APS5_VABI _ZNK3sce4Json5Array8iteratorneERKS2_(const ArrayIterator* self, const ArrayIterator* other) { return self->it != other->it; } -void APS5_VABI _ZN3sce4Json5Array8iteratorC1ERKS2_(ArrayIterator* self, const ArrayIterator* other) { self->it = other->it; } +void APS5_VABI _ZN3sce4Json5Array9pop_frontEv(Array* self) { + if (self->items->empty()) throw std::out_of_range("sce::Json::Array::pop_front: empty array"); + Destroy(self->items->front()); + self->items->pop_front(); +} +void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(ArrayIterator* self) { delete self->state; self->state = nullptr; } +ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(ArrayIterator* self) { + if (self->state && self->state->position != self->state->end) ++self->state->position; + return self; +} +Value* APS5_VABI _ZNK3sce4Json5Array8iteratordeEv(const ArrayIterator* self) { + if (!self->state || self->state->position == self->state->end) throw std::out_of_range("sce::Json::Array::iterator: no current element"); + return &*self->state->position; +} +Value* APS5_VABI _ZNK3sce4Json5Array8iteratorptEv(const ArrayIterator* self) { return _ZNK3sce4Json5Array8iteratordeEv(self); } +bool APS5_VABI _ZNK3sce4Json5Array8iteratorneERKS2_(const ArrayIterator* self, const ArrayIterator* other) { + if (!self->state || !other->state) return false; + const Value* left = self->state && self->state->position != self->state->end ? &*self->state->position : nullptr; + const Value* right = other->state && other->state->position != other->state->end ? &*other->state->position : nullptr; + return left != right; +} +void APS5_VABI _ZN3sce4Json5Array8iteratorC1ERKS2_(ArrayIterator* self, const ArrayIterator* other) { + self->state = other->state ? new ArrayIteratorState(*other->state) : nullptr; +} ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iterator7advanceEm(ArrayIterator* self, std::size_t count) { - std::advance(self->it, count); + if (self->state) { + while (count && self->state->position != self->state->end) { + ++self->state->position; + --count; + } + } return self; } @@ -711,33 +793,56 @@ void APS5_VABI _ZN3sce4Json6Object5clearEv(Object* self) { self->items->clear(); } Value* APS5_VABI _ZN3sce4Json6ObjectixERKNS0_6StringE(Object* self, const String* key) { return &ObjectEntry(*self, *key->text); } -ObjectIterator* APS5_VABI _ZNK3sce4Json6Object5beginEv(ObjectIterator* result, const Object* self) { result->it = self->items->begin(); return result; } -ObjectIterator* APS5_VABI _ZNK3sce4Json6Object3endEv(ObjectIterator* result, const Object* self) { result->it = self->items->end(); return result; } -void APS5_VABI _ZN3sce4Json6Object8iteratorD1Ev(ObjectIterator* self) { (void)self; } -ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEv(ObjectIterator* self) { ++self->it; return self; } -Pair* APS5_VABI _ZNK3sce4Json6Object8iteratordeEv(const ObjectIterator* self) { return &*self->it; } -Pair* APS5_VABI _ZNK3sce4Json6Object8iteratorptEv(const ObjectIterator* self) { return &*self->it; } +ObjectIterator* APS5_VABI _ZNK3sce4Json6Object5beginEv(ObjectIterator* result, const Object* self) { result->state = new ObjectIteratorState{self->items, self->items->begin()}; return result; } +ObjectIterator* APS5_VABI _ZNK3sce4Json6Object3endEv(ObjectIterator* result, const Object* self) { result->state = new ObjectIteratorState{self->items, self->items->end()}; return result; } +void APS5_VABI _ZN3sce4Json6Object8iteratorD1Ev(ObjectIterator* self) { delete self->state; self->state = nullptr; } +ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEv(ObjectIterator* self) { + if (self->state && self->state->owner) { + if (self->state->position == self->state->owner->end()) throw std::out_of_range("sce::Json::Object::iterator: advance past the end"); + ++self->state->position; + } + return self; +} +Pair* APS5_VABI _ZNK3sce4Json6Object8iteratordeEv(const ObjectIterator* self) { + if (!self->state || !self->state->owner || self->state->position == self->state->owner->end()) throw std::out_of_range("sce::Json::Object::iterator: no current element"); + return &*self->state->position; +} +Pair* APS5_VABI _ZNK3sce4Json6Object8iteratorptEv(const ObjectIterator* self) { return _ZNK3sce4Json6Object8iteratordeEv(self); } ObjectIterator* APS5_VABI _ZNK3sce4Json6Object4findERKNS0_6StringE(ObjectIterator* result, const Object* self, const String* key) { - result->it = std::find_if(self->items->begin(), self->items->end(), [&](const Pair& pair) { return *pair.key.text == *key->text; }); + const auto position = std::find_if(self->items->begin(), self->items->end(), [&](const Pair& pair) { return std::strcmp(pair.key.text->c_str(), key->text->c_str()) == 0; }); + result->state = new ObjectIteratorState{self->items, position}; return result; } -bool APS5_VABI _ZNK3sce4Json6Object8iteratorneERKS2_(const ObjectIterator* self, const ObjectIterator* other) { return self->it != other->it; } -bool APS5_VABI _ZNK3sce4Json6Object8iteratoreqERKS2_(const ObjectIterator* self, const ObjectIterator* other) { return self->it == other->it; } -void APS5_VABI _ZN3sce4Json6Object8iteratorC1ERKS2_(ObjectIterator* self, const ObjectIterator* other) { self->it = other->it; } +bool APS5_VABI _ZNK3sce4Json6Object8iteratoreqERKS2_(const ObjectIterator* self, const ObjectIterator* other) { + if (!self->state || !other->state) return false; + return self->state->owner == other->state->owner && self->state->position == other->state->position; +} +bool APS5_VABI _ZNK3sce4Json6Object8iteratorneERKS2_(const ObjectIterator* self, const ObjectIterator* other) { + if (!self->state || !other->state) return false; + return !_ZNK3sce4Json6Object8iteratoreqERKS2_(self, other); +} +void APS5_VABI _ZN3sce4Json6Object8iteratorC1ERKS2_(ObjectIterator* self, const ObjectIterator* other) { + self->state = other->state ? new ObjectIteratorState(*other->state) : nullptr; +} ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iterator7advanceEm(ObjectIterator* self, std::size_t count) { - std::advance(self->it, count); + if (self->state && self->state->owner) { + while (count) { + _ZN3sce4Json6Object8iteratorppEv(self); + + --count; + } + } return self; } bool APS5_VABI _ZNK3sce4Json6Object5emptyEv(const Object* self) { return self->items->empty(); } std::size_t APS5_VABI _ZNK3sce4Json6Object4sizeEv(const Object* self) { return self->items->size(); } -std::size_t APS5_VABI _ZN3sce4Json6Object5eraseERKNS0_6StringE(Object* self, const String* key) { +void APS5_VABI _ZN3sce4Json6Object5eraseERKNS0_6StringE(Object* self, const String* key) { if (key == nullptr || key->text == nullptr) throw std::invalid_argument("sce::Json::Object::erase: null key"); - auto it = std::find_if(self->items->begin(), self->items->end(), [&](const Pair& pair) { return *pair.key.text == *key->text; }); - if (it == self->items->end()) return 0; + auto it = std::find_if(self->items->begin(), self->items->end(), [&](const Pair& pair) { return std::strcmp(pair.key.text->c_str(), key->text->c_str()) == 0; }); + if (it == self->items->end()) return; Destroy(it->key); Destroy(it->value); self->items->erase(it); - return 1; } void APS5_VABI _ZN3sce4Json5ValueC1Ev(Value* self) { Construct(*self); } @@ -777,7 +882,16 @@ Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(Value* self, const Value* other) { return self; } void APS5_VABI _ZN3sce4Json5Value4swapERS1_(Value* self, Value* other) { - std::swap(self->node, other->node); + Node temporary{}; + try { + AssignNode(temporary, NodeOf(*self)); + AssignNode(NodeOf(*self), NodeOf(*other)); + AssignNode(NodeOf(*other), temporary); + } catch (...) { + Clear(temporary); + throw; + } + Clear(temporary); } int APS5_VABI _ZN3sce4Json5Value3setENS0_9ValueTypeE(Value* self, ValueType type) { SetType(NodeOf(*self), type); return 0; } @@ -823,15 +937,15 @@ ValueType APS5_VABI _ZNK3sce4Json5Value7getTypeEv(const Value* self) { return No const bool* APS5_VABI _ZNK3sce4Json5Value10getBooleanEv(const Value* self) { return &Typed(self, TypeBoolean).boolean; } const std::int64_t* APS5_VABI _ZNK3sce4Json5Value10getIntegerEv(const Value* self) { Node& n = const_cast(NodeOf(*self)); - if (n.type == TypeUInteger && n.uinteger <= static_cast(INT64_MAX)) { - n.integer = static_cast(n.uinteger); + if (n.type == TypeUInteger) { + n.integer = std::bit_cast(n.uinteger); return &n.integer; } return &Typed(self, TypeInteger).integer; } const std::uint64_t* APS5_VABI _ZNK3sce4Json5Value11getUIntegerEv(const Value* self) { Node& n = const_cast(NodeOf(*self)); - if (n.type == TypeInteger && n.integer >= 0) { + if (n.type == TypeInteger) { n.uinteger = static_cast(n.integer); return &n.uinteger; } @@ -858,7 +972,7 @@ const Value* APS5_VABI _ZNK3sce4Json5ValueixEPKc(const Value* self, const char* const Node& n = NodeOf(*self); if (key != nullptr && n.type == TypeObject) for (const auto& pair : *n.object.items) - if (*pair.key.text == key) return &pair.value; + if (std::strcmp(pair.key.text->c_str(), key) == 0) return &pair.value; return &MissingElement(self); } const Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const Value* self, std::size_t index) { @@ -878,7 +992,7 @@ const Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const Value* const Node& n = NodeOf(*self); if (key != nullptr && n.type == TypeObject) for (const auto& pair : *n.object.items) - if (*pair.key.text == *key->text) return &pair.value; + if (std::strcmp(pair.key.text->c_str(), key->text->c_str()) == 0) return &pair.value; return &MissingElement(self); } Value* APS5_VABI _ZN3sce4Json5Value10referValueEm(Value* self, std::size_t index) { @@ -892,7 +1006,7 @@ Value* APS5_VABI _ZN3sce4Json5Value10referValueERKNS0_6StringE(Value* self, cons Node& n = NodeOf(*self); if (key == nullptr || n.type != TypeObject) return nullptr; for (auto& pair : *n.object.items) - if (*pair.key.text == *key->text) return &pair.value; + if (std::strcmp(pair.key.text->c_str(), key->text->c_str()) == 0) return &pair.value; return nullptr; } Array* APS5_VABI _ZN3sce4Json5Value10referArrayEv(Value* self) { @@ -925,67 +1039,35 @@ int APS5_VABI _ZNK3sce4Json5Value8toStringERNS0_6StringE(const Value* self, Stri return 0; } -using ParserReadCallback = int (APS5_VABI*)(char&, void*); - -int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value* out, const char* text, std::size_t size); - int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(Value* out, ParserReadCallback read, void* context) { if (read == nullptr) throw std::invalid_argument("sce::Json::Parser::parse: null read callback"); - std::string text; - char ch = 0; - while (read(ch, context) == 0 && ch != '\0') text.push_back(ch); - return _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(out, text.data(), text.size()); + Parser parser(read, context, NodeOf(*out).handlers); + return ParseValue(out, parser); } int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value* out, const char* text, std::size_t size) { - if (text == nullptr || size == 0) return JsonErrorParse; - Node parsed{}; - parsed.handlers = NodeOf(*out).handlers; - Parser parser(text, size, parsed.handlers); - bool parsedOk = false; - try { - parsedOk = parser.Parse(parsed); - } catch (...) { - Clear(parsed); - throw; - } - if (!parsedOk) { - Clear(parsed); - return JsonErrorParse; - } - Clear(NodeOf(*out)); - *out->node = parsed; - return 0; + if (size == 0) return JsonErrorParse; + if (text == nullptr) throw std::invalid_argument("sce::Json::Parser::parse: null text"); + Parser parser(text, size, NodeOf(*out).handlers); + return ParseValue(out, parser); } - -struct InitParameterRtti { - void* allocParam; - void* userData; - std::size_t fileBufferSize; -}; - void APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize) { self->allocParam = allocParam; self->userData = userData; self->fileBufferSize = fileBufferSize; } -struct InitParameter2 { - void* allocator; - void* userData; - std::size_t fileBufferSize; - std::int32_t specialFloatFormatType; - std::uint32_t reserved[3]; -}; static_assert(sizeof(InitParameter2) == 0x28 && offsetof(InitParameter2, specialFloatFormatType) == 0x18); void APS5_VABI _ZN3sce4Json14InitParameter2C1Ev(InitParameter2* self) { *self = {}; + self->specialFloatFormatType = 2; } void APS5_VABI _ZN3sce4Json14InitParameter2C2Ev(InitParameter2* self) { _ZN3sce4Json14InitParameter2C1Ev(self); + } void APS5_VABI _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(InitParameter2* self, void* allocator, void* userData) { @@ -1057,9 +1139,17 @@ int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEP bool APS5_VABI _ZNK3sce4Json5ValuecvbEv(const Value* self) { const Node& n = NodeOf(*self); - if (n.type == TypeNull) return false; - if (n.type == TypeBoolean) return n.boolean; - return true; + switch (n.type) { + case TypeNull: return false; + case TypeBoolean: return n.boolean; + case TypeInteger: return n.integer != 0; + case TypeUInteger: return n.uinteger != 0; + case TypeReal: return n.real != 0.0; + case TypeString: return n.string.text && !n.string.text->empty(); + case TypeArray: + case TypeObject: return true; + default: throw std::runtime_error("sce::Json::Value: invalid value type"); + } } void APS5_VABI _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(Pair* self, const String* key, const Value* value) { @@ -1081,10 +1171,10 @@ void APS5_VABI _ZN3sce4Json6Object4PairD1Ev(Pair* self) { } ObjectIterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(ObjectIterator* result, Object* self, const Pair* pair) { - auto it = std::find_if(self->items->begin(), self->items->end(), [&](const Pair& p) { return *p.key.text == *pair->key.text; }); - if (it != self->items->end()) { - AssignNode(NodeOf(it->value), NodeOf(pair->value)); - result->it = it; + auto it = ObjectPosition(*self, *pair->key.text); + auto state = std::make_unique(ObjectIteratorState{self->items, it}); + if (it != self->items->end() && std::strcmp(it->key.text->c_str(), pair->key.text->c_str()) == 0) { + result->state = state.release(); return result; } Pair copy{}; @@ -1092,7 +1182,8 @@ ObjectIterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(ObjectIterator Construct(copy.key, *pair->key.text); try { CopyInto(copy.value, pair->value); - result->it = self->items->insert(self->items->end(), copy); + state->position = self->items->insert(it, copy); + result->state = state.release(); return result; } catch (...) { Destroy(copy.value); @@ -1102,33 +1193,50 @@ ObjectIterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(ObjectIterator } ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEi(ArrayIterator* result, ArrayIterator* self, int) { - result->it = self->it; - ++self->it; + _ZN3sce4Json5Array8iteratorC1ERKS2_(result, self); + _ZN3sce4Json5Array8iteratorppEv(self); + return result; } ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratoraSERKS2_(ArrayIterator* self, const ArrayIterator* other) { - self->it = other->it; + if (self->state) { + if (other->state && other->state->position != other->state->end) { + *self->state = *other->state; + } else { + delete self->state; + self->state = nullptr; + } + } return self; } void APS5_VABI _ZN3sce4Json5Array8iteratorC1Ev(ArrayIterator* self) { - self->it = {}; + self->state = new ArrayIteratorState{}; } ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEi(ObjectIterator* result, ObjectIterator* self, int) { - result->it = self->it; - ++self->it; + auto state = self->state ? std::make_unique(*self->state) : nullptr; + _ZN3sce4Json6Object8iteratorppEv(self); + + result->state = state.release(); return result; } ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratoraSERKS2_(ObjectIterator* self, const ObjectIterator* other) { - self->it = other->it; + if (self->state) { + if (other->state && other->state->owner) { + *self->state = *other->state; + } else { + delete self->state; + self->state = nullptr; + } + } return self; } void APS5_VABI _ZN3sce4Json6Object8iteratorC1Ev(ObjectIterator* self) { - self->it = {}; + self->state = new ObjectIteratorState{}; } void APS5_VABI _ZN3sce4Json6Object4PairC1Ev(Pair* self) { diff --git a/core/libs/prx/libSceJson2/InventoryExports.cpp b/core/libs/prx/libSceJson2/InventoryExports.cpp new file mode 100644 index 0000000000..c509808eb3 --- /dev/null +++ b/core/libs/prx/libSceJson2/InventoryExports.cpp @@ -0,0 +1,404 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceJson2Unknown_001_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json11Initializer24setAllocatorInfoCallBackEPFviNS0_9ValueTypeEPvES3_", sceJson2Unknown_001_nid_no_patch); + +int APS5_VABI sceJson2Unknown_002_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json11Initializer28setGlobalSpecialFloatHandlerEPFKNS0_5ValueENS0_12FunctionTypeEdPS3_PvPbES6_", sceJson2Unknown_002_nid_no_patch); + +int APS5_VABI sceJson2Unknown_003_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json11Initializer28setGlobalTypeMismatchHandlerEPFRKNS0_5ValueENS0_9ValueTypeEPS3_S6_PvES7_", sceJson2Unknown_003_nid_no_patch); + +int APS5_VABI sceJson2Unknown_004_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json11Initializer36setGlobalElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKNS0_5ValueES9_PvESA_", sceJson2Unknown_004_nid_no_patch); + +int APS5_VABI sceJson2Unknown_005_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json11InitializerC2Ev", sceJson2Unknown_005_nid_no_patch); + +int APS5_VABI sceJson2Unknown_006_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json11InitializerD2Ev", sceJson2Unknown_006_nid_no_patch); + +int APS5_VABI sceJson2Unknown_007_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json12MemAllocatorD0Ev", sceJson2Unknown_007_nid_no_patch); + +int APS5_VABI sceJson2Unknown_008_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json12MemAllocatorD1Ev", sceJson2Unknown_008_nid_no_patch); + +int APS5_VABI sceJson2Unknown_009_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json17InitParameterRttiC2EPNS0_14AllocParamRttiEPvm", sceJson2Unknown_009_nid_no_patch); + +int APS5_VABI sceJson2Unknown_010_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json19InternalInitializer19terminate_staticlibEv", sceJson2Unknown_010_nid_no_patch); + +int APS5_VABI sceJson2Unknown_011_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE", sceJson2Unknown_011_nid_no_patch); + +int APS5_VABI sceJson2Unknown_012_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json19InternalInitializerC1Ev", sceJson2Unknown_012_nid_no_patch); + +int APS5_VABI sceJson2Unknown_013_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json19InternalInitializerC2Ev", sceJson2Unknown_013_nid_no_patch); + +int APS5_VABI sceJson2Unknown_014_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json19InternalInitializerD1Ev", sceJson2Unknown_014_nid_no_patch); + +int APS5_VABI sceJson2Unknown_015_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json19InternalInitializerD2Ev", sceJson2Unknown_015_nid_no_patch); + +int APS5_VABI sceJson2Unknown_016_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json4FreeEPv", sceJson2Unknown_016_nid_no_patch); + +int APS5_VABI sceJson2Unknown_017_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Array10push_frontERKNS0_5ValueE", sceJson2Unknown_017_nid_no_patch); + +int APS5_VABI sceJson2Unknown_018_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Array5eraseERKNS1_8iteratorE", sceJson2Unknown_018_nid_no_patch); + +int APS5_VABI sceJson2Unknown_019_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Array6insertERKNS1_8iteratorERKNS0_5ValueE", sceJson2Unknown_019_nid_no_patch); + +int APS5_VABI sceJson2Unknown_020_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Array8iteratorC2ERKS2_", sceJson2Unknown_020_nid_no_patch); + +int APS5_VABI sceJson2Unknown_021_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Array8iteratorC2Ev", sceJson2Unknown_021_nid_no_patch); + +int APS5_VABI sceJson2Unknown_022_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Array8iteratorD2Ev", sceJson2Unknown_022_nid_no_patch); + +int APS5_VABI sceJson2Unknown_023_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Array8pop_backEv", sceJson2Unknown_023_nid_no_patch); + +int APS5_VABI sceJson2Unknown_024_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Array9pop_frontEv", sceJson2Unknown_024_nid_no_patch); + +int APS5_VABI sceJson2Unknown_025_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ArrayC2ERKS1_", sceJson2Unknown_025_nid_no_patch); + +int APS5_VABI sceJson2Unknown_026_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ArrayC2Ev", sceJson2Unknown_026_nid_no_patch); + +int APS5_VABI sceJson2Unknown_027_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ArrayD2Ev", sceJson2Unknown_027_nid_no_patch); + +int APS5_VABI sceJson2Unknown_029_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Value11referStringEv", sceJson2Unknown_029_nid_no_patch); + +int APS5_VABI sceJson2Unknown_030_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Value12referBooleanEv", sceJson2Unknown_030_nid_no_patch); + +int APS5_VABI sceJson2Unknown_031_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Value12referIntegerEv", sceJson2Unknown_031_nid_no_patch); + +int APS5_VABI sceJson2Unknown_032_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Value13referUIntegerEv", sceJson2Unknown_032_nid_no_patch); + +int APS5_VABI sceJson2Unknown_033_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Value9referRealEv", sceJson2Unknown_033_nid_no_patch); + +int APS5_VABI sceJson2Unknown_034_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_", sceJson2Unknown_034_nid_no_patch); + +int APS5_VABI sceJson2Unknown_035_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_", sceJson2Unknown_035_nid_no_patch); + +int APS5_VABI sceJson2Unknown_036_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2ENS0_9ValueTypeE", sceJson2Unknown_036_nid_no_patch); + +int APS5_VABI sceJson2Unknown_037_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2EPKc", sceJson2Unknown_037_nid_no_patch); + +int APS5_VABI sceJson2Unknown_038_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2ERKNS0_5ArrayE", sceJson2Unknown_038_nid_no_patch); + +int APS5_VABI sceJson2Unknown_039_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2ERKNS0_6ObjectE", sceJson2Unknown_039_nid_no_patch); + +int APS5_VABI sceJson2Unknown_040_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2ERKNS0_6StringE", sceJson2Unknown_040_nid_no_patch); + +int APS5_VABI sceJson2Unknown_041_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2ERKS1_", sceJson2Unknown_041_nid_no_patch); + +int APS5_VABI sceJson2Unknown_042_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2Eb", sceJson2Unknown_042_nid_no_patch); + +int APS5_VABI sceJson2Unknown_043_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2Ed", sceJson2Unknown_043_nid_no_patch); + +int APS5_VABI sceJson2Unknown_044_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2El", sceJson2Unknown_044_nid_no_patch); + +int APS5_VABI sceJson2Unknown_045_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2Em", sceJson2Unknown_045_nid_no_patch); + +int APS5_VABI sceJson2Unknown_046_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueC2Ev", sceJson2Unknown_046_nid_no_patch); + +int APS5_VABI sceJson2Unknown_047_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json5ValueD2Ev", sceJson2Unknown_047_nid_no_patch); + +int APS5_VABI sceJson2Unknown_048_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6MallocEm", sceJson2Unknown_048_nid_no_patch); + +int APS5_VABI sceJson2Unknown_049_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE", sceJson2Unknown_049_nid_no_patch); + +int APS5_VABI sceJson2Unknown_050_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6Object4PairC2Ev", sceJson2Unknown_050_nid_no_patch); + +int APS5_VABI sceJson2Unknown_051_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6Object4PairD2Ev", sceJson2Unknown_051_nid_no_patch); + +int APS5_VABI sceJson2Unknown_052_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6Object8iteratorC2ERKS2_", sceJson2Unknown_052_nid_no_patch); + +int APS5_VABI sceJson2Unknown_053_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6Object8iteratorC2Ev", sceJson2Unknown_053_nid_no_patch); + +int APS5_VABI sceJson2Unknown_054_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6Object8iteratorD2Ev", sceJson2Unknown_054_nid_no_patch); + +int APS5_VABI sceJson2Unknown_055_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6ObjectC2ERKS1_", sceJson2Unknown_055_nid_no_patch); + +int APS5_VABI sceJson2Unknown_056_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6ObjectC2Ev", sceJson2Unknown_056_nid_no_patch); + +int APS5_VABI sceJson2Unknown_057_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6ObjectD2Ev", sceJson2Unknown_057_nid_no_patch); + +int APS5_VABI sceJson2Unknown_058_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6Parser5parseERNS0_5ValueEPKc", sceJson2Unknown_058_nid_no_patch); + +int APS5_VABI sceJson2Unknown_059_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6StringC2EPKc", sceJson2Unknown_059_nid_no_patch); + +int APS5_VABI sceJson2Unknown_060_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6StringC2ERKS1_", sceJson2Unknown_060_nid_no_patch); + +int APS5_VABI sceJson2Unknown_061_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6StringC2Ev", sceJson2Unknown_061_nid_no_patch); + +int APS5_VABI sceJson2Unknown_062_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZN3sce4Json6StringD2Ev", sceJson2Unknown_062_nid_no_patch); + +int APS5_VABI sceJson2Unknown_063_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZNK3sce4Json14InitParameter211getUserDataEv", sceJson2Unknown_063_nid_no_patch); + +int APS5_VABI sceJson2Unknown_064_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZNK3sce4Json14InitParameter212getAllocatorEv", sceJson2Unknown_064_nid_no_patch); + +int APS5_VABI sceJson2Unknown_065_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZNK3sce4Json14InitParameter217getFileBufferSizeEv", sceJson2Unknown_065_nid_no_patch); + +int APS5_VABI sceJson2Unknown_066_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZNK3sce4Json14InitParameter225getSpecialFloatFormatTypeEv", sceJson2Unknown_066_nid_no_patch); + +int APS5_VABI sceJson2Unknown_067_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("_ZNK3sce4Json5ValueixERKNS0_6StringE", sceJson2Unknown_067_nid_no_patch); + +} diff --git a/core/libs/prx/libSceJson2/include/Api.hpp b/core/libs/prx/libSceJson2/include/Api.hpp new file mode 100644 index 0000000000..630d23d193 --- /dev/null +++ b/core/libs/prx/libSceJson2/include/Api.hpp @@ -0,0 +1,347 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SceJson2 { + +enum ValueType : std::int32_t { + TypeNull = 0, + TypeBoolean = 1, + TypeInteger = 2, + TypeUInteger = 3, + TypeReal = 4, + TypeString = 5, + TypeArray = 6, + TypeObject = 7, +}; + +} + +namespace SceJson2 { + +struct Node; + +} + +namespace SceJson2 { + +struct Value { Node* node; }; + +} + +namespace SceJson2 { + +struct String { std::string* text; }; + +} + +namespace SceJson2 { + +struct Array { std::list* items; }; + +} + +namespace SceJson2 { + +struct Pair; + +} + +namespace SceJson2 { + +struct Object { std::list* items; }; + +} + +namespace SceJson2 { + +struct ArrayIteratorState { + std::list::iterator position; + std::list::iterator end; +}; + +} + +namespace SceJson2 { + +struct ArrayIterator { ArrayIteratorState* state; }; + +} + +namespace SceJson2 { + +struct ObjectIteratorState { + std::list* owner = nullptr; + std::list::iterator position{}; +}; + +} + +namespace SceJson2 { + +struct ObjectIterator { ObjectIteratorState* state; }; + +} + +namespace SceJson2 { + +struct Pair { + String key; + std::uint64_t reserved; + Value value; +}; + +} + +namespace SceJson2 { + +using TypeMismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); + +} + +namespace SceJson2 { + +using SpecialFloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); + +} + +namespace SceJson2 { + +using ElementAccessFailureHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); + +} + +namespace SceJson2 { + +struct Handlers { + TypeMismatchHandler typeMismatch = nullptr; + void* typeMismatchContext = nullptr; + SpecialFloatHandler specialFloat = nullptr; + void* specialFloatContext = nullptr; + ElementAccessFailureHandler elementAccessFailure = nullptr; + void* elementAccessFailureContext = nullptr; +}; + +} + +namespace SceJson2 { + +struct Node { + ValueType type = TypeNull; + bool boolean = false; + std::int64_t integer = 0; + std::uint64_t uinteger = 0; + double real = 0; + String string{}; + Array array{}; + Object object{}; + Handlers handlers{}; +}; + +} + +namespace SceJson2 { + +using NullAccessCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*); + +} + +namespace SceJson2 { + +using ParserReadCallback = int (APS5_VABI*)(char&, void*); + +} + +namespace SceJson2 { + +struct InitParameterRtti { + void* allocParam; + void* userData; + std::size_t fileBufferSize; +}; + +} + +namespace SceJson2 { + +struct InitParameter2 { + void* allocator; + void* userData; + std::size_t fileBufferSize; + std::int32_t specialFloatFormatType; + std::uint32_t reserved[3]; +}; + +} + +extern "C" { + +int APS5_VABI _ZN3sce4Json11InitializerC1Ev(void* self); +int APS5_VABI _ZN3sce4Json11InitializerD1Ev(void* self); +int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(void* self, const void* parameter); +int APS5_VABI _ZN3sce4Json11Initializer9terminateEv(void* self); +int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void* self, SceJson2::NullAccessCallback callback, void* context); +int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void* self, SceJson2::NullAccessCallback callback, void* context); +void APS5_VABI _ZN3sce4Json12MemAllocatorC2Ev(void* self); +void APS5_VABI _ZN3sce4Json12MemAllocatorD2Ev(void* self); +void APS5_VABI _ZN3sce4Json6StringC1Ev(SceJson2::String* self); +void APS5_VABI _ZN3sce4Json6StringC1EPKc(SceJson2::String* self, const char* text); +void APS5_VABI _ZN3sce4Json6StringC1ERKS1_(SceJson2::String* self, const SceJson2::String* other); +void APS5_VABI _ZN3sce4Json6StringD1Ev(SceJson2::String* self); +SceJson2::String* APS5_VABI _ZN3sce4Json6StringaSERKS1_(SceJson2::String* self, const SceJson2::String* other); +const char* APS5_VABI _ZNK3sce4Json6String5c_strEv(const SceJson2::String* self); +bool APS5_VABI _ZNK3sce4Json6String5emptyEv(const SceJson2::String* self); +std::size_t APS5_VABI _ZNK3sce4Json6String6lengthEv(const SceJson2::String* self); +std::size_t APS5_VABI _ZNK3sce4Json6String4sizeEv(const SceJson2::String* self); +std::size_t APS5_VABI _ZNK3sce4Json6String8capacityEv(const SceJson2::String* self); +bool APS5_VABI _ZNK3sce4Json6StringeqEPKc(const SceJson2::String* self, const char* text); +bool APS5_VABI _ZNK3sce4Json6StringeqERKS1_(const SceJson2::String* self, const SceJson2::String* other); +void APS5_VABI _ZN3sce4Json6String5clearEv(SceJson2::String* self); +void APS5_VABI _ZN3sce4Json6String6resizeEm(SceJson2::String* self, std::size_t count); +void APS5_VABI _ZN3sce4Json6String7reserveEm(SceJson2::String* self, std::size_t capacity); +SceJson2::String* APS5_VABI _ZN3sce4Json6String6appendERKS1_(SceJson2::String* self, const SceJson2::String* other); +SceJson2::String* APS5_VABI _ZN3sce4Json6String6appendEPKc(SceJson2::String* self, const char* text); +SceJson2::String* APS5_VABI _ZN3sce4Json6String6appendEPKcm(SceJson2::String* self, const char* text, std::size_t length); +SceJson2::String* APS5_VABI _ZN3sce4Json6StringpLEPKc(SceJson2::String* self, const char* text); +SceJson2::String* APS5_VABI _ZN3sce4Json6StringpLEh(SceJson2::String* self, unsigned char ch); +SceJson2::String* APS5_VABI _ZNK3sce4Json6String6substrEmm(SceJson2::String* result, const SceJson2::String* self, std::size_t position, std::size_t length); +std::size_t APS5_VABI _ZNK3sce4Json6String4findEcm(const SceJson2::String* self, char character, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEcm(const SceJson2::String* self, char character, std::size_t position); +char APS5_VABI _ZNK3sce4Json6String2atEm(const SceJson2::String* self, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcm(const SceJson2::String* self, const char* text, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcmm(const SceJson2::String* self, const char* text, std::size_t position, std::size_t length); +std::size_t APS5_VABI _ZNK3sce4Json6String4findERKS1_m(const SceJson2::String* self, const SceJson2::String* other, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcm(const SceJson2::String* self, const char* text, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcmm(const SceJson2::String* self, const char* text, std::size_t position, std::size_t length); +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindERKS1_m(const SceJson2::String* self, const SceJson2::String* other, std::size_t position); +int APS5_VABI _ZNK3sce4Json6String7compareEPKc(const SceJson2::String* self, const char* text); +int APS5_VABI _ZNK3sce4Json6String7compareERKS1_(const SceJson2::String* self, const SceJson2::String* other); +void APS5_VABI _ZN3sce4Json5ArrayC1Ev(SceJson2::Array* self); +void APS5_VABI _ZN3sce4Json5ArrayC1ERKS1_(SceJson2::Array* self, const SceJson2::Array* other); +void APS5_VABI _ZN3sce4Json5ArrayD1Ev(SceJson2::Array* self); +SceJson2::Array* APS5_VABI _ZN3sce4Json5ArrayaSERKS1_(SceJson2::Array* self, const SceJson2::Array* other); +void APS5_VABI _ZN3sce4Json5Array9push_backERKNS0_5ValueE(SceJson2::Array* self, const SceJson2::Value* value); +SceJson2::ArrayIterator* APS5_VABI _ZNK3sce4Json5Array5beginEv(SceJson2::ArrayIterator* result, const SceJson2::Array* self); +SceJson2::ArrayIterator* APS5_VABI _ZNK3sce4Json5Array3endEv(SceJson2::ArrayIterator* result, const SceJson2::Array* self); +bool APS5_VABI _ZNK3sce4Json5Array5emptyEv(const SceJson2::Array* self); +std::size_t APS5_VABI _ZNK3sce4Json5Array4sizeEv(const SceJson2::Array* self); +void APS5_VABI _ZN3sce4Json5Array5clearEv(SceJson2::Array* self); +const SceJson2::Value* APS5_VABI _ZNK3sce4Json5Array5frontEv(const SceJson2::Array* self); +const SceJson2::Value* APS5_VABI _ZNK3sce4Json5Array4backEv(const SceJson2::Array* self); +void APS5_VABI _ZN3sce4Json5Array9pop_frontEv(SceJson2::Array* self); +void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(SceJson2::ArrayIterator* self); +SceJson2::ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(SceJson2::ArrayIterator* self); +SceJson2::Value* APS5_VABI _ZNK3sce4Json5Array8iteratordeEv(const SceJson2::ArrayIterator* self); +SceJson2::Value* APS5_VABI _ZNK3sce4Json5Array8iteratorptEv(const SceJson2::ArrayIterator* self); +bool APS5_VABI _ZNK3sce4Json5Array8iteratorneERKS2_(const SceJson2::ArrayIterator* self, const SceJson2::ArrayIterator* other); +void APS5_VABI _ZN3sce4Json5Array8iteratorC1ERKS2_(SceJson2::ArrayIterator* self, const SceJson2::ArrayIterator* other); +SceJson2::ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iterator7advanceEm(SceJson2::ArrayIterator* self, std::size_t count); +void APS5_VABI _ZN3sce4Json6ObjectC1Ev(SceJson2::Object* self); +void APS5_VABI _ZN3sce4Json6ObjectC1ERKS1_(SceJson2::Object* self, const SceJson2::Object* other); +void APS5_VABI _ZN3sce4Json6ObjectD1Ev(SceJson2::Object* self); +SceJson2::Object* APS5_VABI _ZN3sce4Json6ObjectaSERKS1_(SceJson2::Object* self, const SceJson2::Object* other); +void APS5_VABI _ZN3sce4Json6Object5clearEv(SceJson2::Object* self); +SceJson2::Value* APS5_VABI _ZN3sce4Json6ObjectixERKNS0_6StringE(SceJson2::Object* self, const SceJson2::String* key); +SceJson2::ObjectIterator* APS5_VABI _ZNK3sce4Json6Object5beginEv(SceJson2::ObjectIterator* result, const SceJson2::Object* self); +SceJson2::ObjectIterator* APS5_VABI _ZNK3sce4Json6Object3endEv(SceJson2::ObjectIterator* result, const SceJson2::Object* self); +void APS5_VABI _ZN3sce4Json6Object8iteratorD1Ev(SceJson2::ObjectIterator* self); +SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEv(SceJson2::ObjectIterator* self); +SceJson2::Pair* APS5_VABI _ZNK3sce4Json6Object8iteratordeEv(const SceJson2::ObjectIterator* self); +SceJson2::Pair* APS5_VABI _ZNK3sce4Json6Object8iteratorptEv(const SceJson2::ObjectIterator* self); +SceJson2::ObjectIterator* APS5_VABI _ZNK3sce4Json6Object4findERKNS0_6StringE(SceJson2::ObjectIterator* result, const SceJson2::Object* self, const SceJson2::String* key); +bool APS5_VABI _ZNK3sce4Json6Object8iteratoreqERKS2_(const SceJson2::ObjectIterator* self, const SceJson2::ObjectIterator* other); +bool APS5_VABI _ZNK3sce4Json6Object8iteratorneERKS2_(const SceJson2::ObjectIterator* self, const SceJson2::ObjectIterator* other); +void APS5_VABI _ZN3sce4Json6Object8iteratorC1ERKS2_(SceJson2::ObjectIterator* self, const SceJson2::ObjectIterator* other); +SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iterator7advanceEm(SceJson2::ObjectIterator* self, std::size_t count); +bool APS5_VABI _ZNK3sce4Json6Object5emptyEv(const SceJson2::Object* self); +std::size_t APS5_VABI _ZNK3sce4Json6Object4sizeEv(const SceJson2::Object* self); +void APS5_VABI _ZN3sce4Json6Object5eraseERKNS0_6StringE(SceJson2::Object* self, const SceJson2::String* key); +void APS5_VABI _ZN3sce4Json5ValueC1Ev(SceJson2::Value* self); +void APS5_VABI _ZN3sce4Json5ValueC1Eb(SceJson2::Value* self, bool value); +void APS5_VABI _ZN3sce4Json5ValueC1El(SceJson2::Value* self, std::int64_t value); +void APS5_VABI _ZN3sce4Json5ValueC1Em(SceJson2::Value* self, std::uint64_t value); +void APS5_VABI _ZN3sce4Json5ValueC1Ed(SceJson2::Value* self, double value); +void APS5_VABI _ZN3sce4Json5ValueC1EPKc(SceJson2::Value* self, const char* text); +void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6StringE(SceJson2::Value* self, const SceJson2::String* text); +void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(SceJson2::Value* self, const SceJson2::Array* array); +void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(SceJson2::Value* self, const SceJson2::Object* object); +void APS5_VABI _ZN3sce4Json5ValueC1ERKS1_(SceJson2::Value* self, const SceJson2::Value* other); +void APS5_VABI _ZN3sce4Json5ValueD1Ev(SceJson2::Value* self); +SceJson2::Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(SceJson2::Value* self, const SceJson2::Value* other); +void APS5_VABI _ZN3sce4Json5Value4swapERS1_(SceJson2::Value* self, SceJson2::Value* other); +int APS5_VABI _ZN3sce4Json5Value3setENS0_9ValueTypeE(SceJson2::Value* self, SceJson2::ValueType type); +int APS5_VABI _ZN3sce4Json5Value3setEb(SceJson2::Value* self, bool value); +int APS5_VABI _ZN3sce4Json5Value3setEl(SceJson2::Value* self, std::int64_t value); +int APS5_VABI _ZN3sce4Json5Value3setEm(SceJson2::Value* self, std::uint64_t value); +int APS5_VABI _ZN3sce4Json5Value3setEd(SceJson2::Value* self, double value); +int APS5_VABI _ZN3sce4Json5Value3setEPKc(SceJson2::Value* self, const char* text); +int APS5_VABI _ZN3sce4Json5Value3setERKNS0_6StringE(SceJson2::Value* self, const SceJson2::String* text); +int APS5_VABI _ZN3sce4Json5Value3setERKNS0_5ArrayE(SceJson2::Value* self, const SceJson2::Array* array); +int APS5_VABI _ZN3sce4Json5Value3setERKNS0_6ObjectE(SceJson2::Value* self, const SceJson2::Object* object); +int APS5_VABI _ZN3sce4Json5Value3setERKS1_(SceJson2::Value* self, const SceJson2::Value* other); +SceJson2::ValueType APS5_VABI _ZNK3sce4Json5Value7getTypeEv(const SceJson2::Value* self); +const bool* APS5_VABI _ZNK3sce4Json5Value10getBooleanEv(const SceJson2::Value* self); +const std::int64_t* APS5_VABI _ZNK3sce4Json5Value10getIntegerEv(const SceJson2::Value* self); +const std::uint64_t* APS5_VABI _ZNK3sce4Json5Value11getUIntegerEv(const SceJson2::Value* self); +const double* APS5_VABI _ZNK3sce4Json5Value7getRealEv(const SceJson2::Value* self); +const SceJson2::String* APS5_VABI _ZNK3sce4Json5Value9getStringEv(const SceJson2::Value* self); +const SceJson2::Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const SceJson2::Value* self); +const SceJson2::Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const SceJson2::Value* self); +std::size_t APS5_VABI _ZNK3sce4Json5Value5countEv(const SceJson2::Value* self); +const SceJson2::Value* APS5_VABI _ZNK3sce4Json5ValueixEPKc(const SceJson2::Value* self, const char* key); +const SceJson2::Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const SceJson2::Value* self, std::size_t index); +void APS5_VABI _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(SceJson2::Value* self, SceJson2::ValueType type); +const SceJson2::Value* APS5_VABI _ZNK3sce4Json5Value8getValueEm(const SceJson2::Value* self, std::size_t index); +const SceJson2::Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const SceJson2::Value* self, const SceJson2::String* key); +SceJson2::Value* APS5_VABI _ZN3sce4Json5Value10referValueEm(SceJson2::Value* self, std::size_t index); +SceJson2::Value* APS5_VABI _ZN3sce4Json5Value10referValueERKNS0_6StringE(SceJson2::Value* self, const SceJson2::String* key); +SceJson2::Array* APS5_VABI _ZN3sce4Json5Value10referArrayEv(SceJson2::Value* self); +SceJson2::Object* APS5_VABI _ZN3sce4Json5Value11referObjectEv(SceJson2::Value* self); +int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringE(SceJson2::Value* self, SceJson2::String* out); +int APS5_VABI _ZNK3sce4Json5Value8toStringERNS0_6StringE(const SceJson2::Value* self, SceJson2::String* out); +int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(SceJson2::Value* out, SceJson2::ParserReadCallback read, void* context); +int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(SceJson2::Value* out, const char* text, std::size_t size); +void APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(SceJson2::InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize); +void APS5_VABI _ZN3sce4Json14InitParameter2C1Ev(SceJson2::InitParameter2* self); +void APS5_VABI _ZN3sce4Json14InitParameter2C2Ev(SceJson2::InitParameter2* self); +void APS5_VABI _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(SceJson2::InitParameter2* self, void* allocator, void* userData); +void APS5_VABI _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(SceJson2::InitParameter2* self, void* allocParam, void* userData); +void APS5_VABI _ZN3sce4Json14InitParameter217setFileBufferSizeEm(SceJson2::InitParameter2* self, std::size_t size); +void APS5_VABI _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(SceJson2::InitParameter2* self, std::int32_t type); +int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(void* self, const SceJson2::InitParameter2* parameter); +void APS5_VABI _ZN3sce4Json5Value5clearEv(SceJson2::Value* self); +void APS5_VABI _ZN3sce4Json12MemAllocator11notifyErrorEimPv(void* self, int error, std::size_t size, void* userData); +void APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(SceJson2::NullAccessCallback callback, void* context); +int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(SceJson2::Value* self, SceJson2::ElementAccessFailureHandler handler, void* context); +int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(SceJson2::Value* self, SceJson2::SpecialFloatHandler handler, void* context); +int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(SceJson2::Value* self, SceJson2::TypeMismatchHandler handler, void* context); +bool APS5_VABI _ZNK3sce4Json5ValuecvbEv(const SceJson2::Value* self); +void APS5_VABI _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(SceJson2::Pair* self, const SceJson2::String* key, const SceJson2::Value* value); +void APS5_VABI _ZN3sce4Json6Object4PairD1Ev(SceJson2::Pair* self); +SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(SceJson2::ObjectIterator* result, SceJson2::Object* self, const SceJson2::Pair* pair); +SceJson2::ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEi(SceJson2::ArrayIterator* result, SceJson2::ArrayIterator* self, int); +SceJson2::ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratoraSERKS2_(SceJson2::ArrayIterator* self, const SceJson2::ArrayIterator* other); +void APS5_VABI _ZN3sce4Json5Array8iteratorC1Ev(SceJson2::ArrayIterator* self); +SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEi(SceJson2::ObjectIterator* result, SceJson2::ObjectIterator* self, int); +SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratoraSERKS2_(SceJson2::ObjectIterator* self, const SceJson2::ObjectIterator* other); +void APS5_VABI _ZN3sce4Json6Object8iteratorC1Ev(SceJson2::ObjectIterator* self); +void APS5_VABI _ZN3sce4Json6Object4PairC1Ev(SceJson2::Pair* self); + +} diff --git a/core/libs/prx/libSceJson2/libSceJson2.hpp b/core/libs/prx/libSceJson2/libSceJson2.hpp new file mode 100644 index 0000000000..1ff338c252 --- /dev/null +++ b/core/libs/prx/libSceJson2/libSceJson2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceJson2/include/Api.hpp" diff --git a/core/libs/prx/libSceJxr/CMakeLists.txt b/core/libs/prx/libSceJxr/CMakeLists.txt new file mode 100644 index 0000000000..251402ba35 --- /dev/null +++ b/core/libs/prx/libSceJxr/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceJxr PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceJxr) + +if(BUILD_TESTING) + add_test_executable(guest_jxr_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxr.cpp) + target_include_directories(guest_jxr_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jxr_tests PRIVATE libSceJxr libc) + configure_windows_unwind(guest_jxr_tests) + add_test(NAME guest_jxr COMMAND guest_jxr_tests) + if(WIN32) + set_tests_properties(guest_jxr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp index 608aedc418..bd2404ed30 100644 --- a/core/libs/prx/libSceJxr/Export.cpp +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -1,14 +1,24 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceJxr/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { +constexpr std::int64_t WmpErrSuccess = 0; +} + extern "C" { APS5_DUMMY_FUN -int APS5_VABI PKFree_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI PKFree_nid_postfix(void** memory) { + if (memory != nullptr) { + free_nid_postfix(*memory); + + *memory = nullptr; + } + return WmpErrSuccess; } int APS5_VABI PixelFormatLookup_nid_postfix(void) { diff --git a/core/libs/prx/libSceJxr/include/Api.hpp b/core/libs/prx/libSceJxr/include/Api.hpp new file mode 100644 index 0000000000..b29df513d6 --- /dev/null +++ b/core/libs/prx/libSceJxr/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +std::int64_t APS5_VABI PKFree_nid_postfix(void** memory); +int APS5_VABI PixelFormatLookup_nid_postfix(void); +int APS5_VABI readIS_nid_postfix(void); +int APS5_VABI writeIS_nid_postfix(void); + +} diff --git a/core/libs/prx/libSceJxr/libSceJxr.hpp b/core/libs/prx/libSceJxr/libSceJxr.hpp new file mode 100644 index 0000000000..e92de5c284 --- /dev/null +++ b/core/libs/prx/libSceJxr/libSceJxr.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceJxr/include/Api.hpp" diff --git a/core/libs/prx/libSceJxrParser/libSceJxrParser.hpp b/core/libs/prx/libSceJxrParser/libSceJxrParser.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceJxrParser/libSceJxrParser.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceKbEmulate/libSceKbEmulate.hpp b/core/libs/prx/libSceKbEmulate/libSceKbEmulate.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceKbEmulate/libSceKbEmulate.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceKeyboard/Export.cpp b/core/libs/prx/libSceKeyboard/Export.cpp index d8d408237a..fce1492046 100644 --- a/core/libs/prx/libSceKeyboard/Export.cpp +++ b/core/libs/prx/libSceKeyboard/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceKeyboard/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceKeyboard/include/KeyboardState.hpp" @@ -24,51 +25,49 @@ int APS5_VABI sceKeyboardReadState(int32_t handle, KeyboardData* data) { return Keyboard::ReadState(handle, data); } -int APS5_VABI sceKeyboardGetKey2Char(int32_t handle, int32_t arrange, uint32_t led, uint32_t modifier_key, uint16_t key_code, KeyboardCharData* char_data) { - return Keyboard::GetKey2Char(handle, arrange, led, modifier_key, key_code, char_data); +int APS5_VABI sceKeyboardGetConnection(std::int32_t handle, bool* connected) { + return Keyboard::GetConnection(handle, connected); } -int APS5_VABI sceKeyboardConnectPort(void) { +int APS5_VABI sceKeyboardGetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint8_t led, std::uint8_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData) { + return Keyboard::GetKey2Char(handle, arrange, led, modifierKey, keyCode, charData); +} + +int APS5_VABI sceKeyboardConnectPort(std::int32_t first, std::int32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceKeyboardDebugGetDeviceId(void) { +int APS5_VABI sceKeyboardDebugGetDeviceId(std::int32_t handle, std::int32_t* deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceKeyboardDeviceOpen(void) { +int APS5_VABI sceKeyboardDeviceOpen(std::uint64_t deviceId, std::int32_t index, std::uint32_t options) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceKeyboardDisconnectAll(void) { + return Keyboard::DisconnectAll(); +} + +int APS5_VABI sceKeyboardDisconnectDevice(std::int32_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceKeyboardDisconnectDevice(void) { +int APS5_VABI sceKeyboardDisconnectPort(std::int32_t first, std::int32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceKeyboardDisconnectPort(void) { +int APS5_VABI sceKeyboardGetDeviceInfo(std::int32_t deviceId, KeyboardDeviceInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceKeyboardGetConnection(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceKeyboardGetDeviceInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceKeyboardGetHandle(void) { +int APS5_VABI sceKeyboardGetHandle(std::int32_t userId, std::int32_t type, std::int32_t index) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -88,12 +87,12 @@ int APS5_VABI sceKeyboardPadEmulateOpen(void) { return 0; } -int APS5_VABI sceKeyboardSetProcessFocus(void) { +int APS5_VABI sceKeyboardSetProcessFocus(const KeyboardProcessFocus* focus) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceKeyboardSetProcessPrivilege(void) { +int APS5_VABI sceKeyboardSetProcessPrivilege(std::uint8_t privilege) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceKeyboard/include/Api.hpp b/core/libs/prx/libSceKeyboard/include/Api.hpp new file mode 100644 index 0000000000..a44cb9ca83 --- /dev/null +++ b/core/libs/prx/libSceKeyboard/include/Api.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "SceTypes.hpp" + +struct KeyboardDeviceInfo; + +struct KeyboardProcessFocus; + +extern "C" { + +int APS5_VABI sceKeyboardInit(void); +int APS5_VABI sceKeyboardOpen(int userId, int32_t type, int32_t index, const void* param); +int APS5_VABI sceKeyboardClose(int32_t handle); +int APS5_VABI sceKeyboardRead(int32_t handle, KeyboardData* data, int32_t num); +int APS5_VABI sceKeyboardReadState(int32_t handle, KeyboardData* data); +int APS5_VABI sceKeyboardGetConnection(std::int32_t handle, bool* connected); +int APS5_VABI sceKeyboardGetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint8_t led, std::uint8_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData); +int APS5_VABI sceKeyboardConnectPort(std::int32_t first, std::int32_t second); +int APS5_VABI sceKeyboardDebugGetDeviceId(std::int32_t handle, std::int32_t* deviceId); +int APS5_VABI sceKeyboardDeviceOpen(std::uint64_t deviceId, std::int32_t index, std::uint32_t options); +int APS5_VABI sceKeyboardDisconnectAll(void); +int APS5_VABI sceKeyboardDisconnectDevice(std::int32_t deviceId); +int APS5_VABI sceKeyboardDisconnectPort(std::int32_t first, std::int32_t second); +int APS5_VABI sceKeyboardGetDeviceInfo(std::int32_t deviceId, KeyboardDeviceInfo* info); +int APS5_VABI sceKeyboardGetHandle(std::int32_t userId, std::int32_t type, std::int32_t index); +int APS5_VABI sceKeyboardMbusInit(void); +int APS5_VABI sceKeyboardPadEmulateClose(void); +int APS5_VABI sceKeyboardPadEmulateOpen(void); +int APS5_VABI sceKeyboardSetProcessFocus(const KeyboardProcessFocus* focus); +int APS5_VABI sceKeyboardSetProcessPrivilege(std::uint8_t privilege); +int APS5_VABI sceDbgKeyboardClose(); +int APS5_VABI sceDbgKeyboardGetKey2Char(); +int APS5_VABI sceDbgKeyboardInit(); +int APS5_VABI sceDbgKeyboardOpen(); +int APS5_VABI sceDbgKeyboardRead(); +int APS5_VABI sceDbgKeyboardReadState(); + +} diff --git a/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp b/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp index 11c32a5447..3552af3153 100644 --- a/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp +++ b/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp @@ -18,6 +18,8 @@ int Open(int userId, std::int32_t type, std::int32_t index); int Close(std::int32_t handle); int Read(std::int32_t handle, KeyboardData* data, std::int32_t num); int ReadState(std::int32_t handle, KeyboardData* data); +int GetConnection(std::int32_t handle, bool* connected); +int DisconnectAll(); int GetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint32_t led, std::uint32_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData); void Publish(const KeyboardInputEvent& event); } diff --git a/core/libs/prx/libSceKeyboard/libSceKeyboard.hpp b/core/libs/prx/libSceKeyboard/libSceKeyboard.hpp new file mode 100644 index 0000000000..8ab2bf1b0d --- /dev/null +++ b/core/libs/prx/libSceKeyboard/libSceKeyboard.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceKeyboard/include/Api.hpp" diff --git a/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp b/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp index 0a16e2d75e..ba9d9bfff8 100644 --- a/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp +++ b/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp @@ -19,8 +19,8 @@ std::uint32_t modifiers = 0; std::array keys{}; int keyCount = 0; -constexpr char US_NORMAL[] = "1234567890\n\0\b\t -=[]\\\0;'`,./"; -constexpr char US_SHIFTED[] = "!@#$%^&*()\n\0\b\t _+{}|\0:\"~<>?"; +constexpr char US_NORMAL[] = "1234567890\n\0\b\t -=[]\\\\;'`,./"; +constexpr char US_SHIFTED[] = "!@#$%^&*()\n\0\b\t _+{}||:\"~<>?"; constexpr char JIS_NORMAL[] = "1234567890\n\0\b\t -^@[]];:\0,./"; constexpr char JIS_SHIFTED[] = "!\"#$%&'()\0\n\0\b\t =~`{}}+*\0<>?"; constexpr char KEYPAD_DIGITS[] = "1234567890."; @@ -81,8 +81,9 @@ std::uint16_t character(bool jis, std::uint32_t ledState, std::uint32_t modifier const char* table = jis ? (shift ? JIS_SHIFTED : JIS_NORMAL) : (shift ? US_SHIFTED : US_NORMAL); return static_cast(table[keyCode - 0x1e]); } + if (!jis && keyCode == 0x64) return shift ? '|' : '\\'; if (jis && keyCode == 0x87) return shift ? '_' : '\\'; - if (jis && keyCode == 0x89) return shift ? '|' : '\\'; + if (jis && keyCode == 0x89) return shift ? '|' : 0x00A5; switch (keyCode) { case 0x54: return '/'; case 0x55: return '*'; @@ -153,21 +154,36 @@ int ReadState(std::int32_t handle, KeyboardData* data) { return KEYBOARD_OK; } +int GetConnection(std::int32_t handle, bool* isConnected) { + std::lock_guard lock(keyboardMutex); + if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED; + if (handle != KEYBOARD_HANDLE || !opened) return KEYBOARD_ERROR_INVALID_HANDLE; + if (isConnected == nullptr) return KEYBOARD_ERROR_INVALID_ARG; + *isConnected = connected; + return KEYBOARD_OK; +} + +int DisconnectAll() { + KeyboardInputEvent event{}; + event.connectionChange = true; + event.connected = false; + Publish(event); + return KEYBOARD_OK; +} + int GetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint32_t ledState, std::uint32_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData) { { std::lock_guard lock(keyboardMutex); if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED; + if (keyCode < 4 || keyCode > 231) return KEYBOARD_ERROR_INVALID_ARG; if (handle != KEYBOARD_HANDLE || !opened) return KEYBOARD_ERROR_INVALID_HANDLE; } if (charData == nullptr) return KEYBOARD_ERROR_INVALID_ARG; if (arrange != KEYBOARD_ARRANGEMENT_101 && arrange != KEYBOARD_ARRANGEMENT_106) return KEYBOARD_ERROR_INVALID_ARG; - if (arrange == KEYBOARD_ARRANGEMENT_106 && (ledState & KEYBOARD_LED_KANA) != 0) { - NotImplemented_nid_no_patch(__func__); - return KEYBOARD_ERROR_INVALID_ARG; - } *charData = {}; - charData->char_code = character(arrange == KEYBOARD_ARRANGEMENT_106, ledState, modifierKey, keyCode); - charData->processed = charData->char_code != 0; + const std::uint16_t converted = character(arrange == KEYBOARD_ARRANGEMENT_106, ledState, modifierKey, keyCode); + charData->char_code = converted != 0 ? converted : keyCode; + charData->processed = converted != 0; charData->length = charData->processed ? 1 : 0; return KEYBOARD_OK; } diff --git a/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp b/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp index 97e1dd5129..e9856f2c1a 100644 --- a/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp +++ b/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp @@ -1,25 +1,23 @@ +#include "prx/libSceKeyboard/include/Api.hpp" #include "prx/libSceKeyboard/include/keyboard_structs.h" #include "prx/libSceKeyboard/include/KeyboardState.hpp" #include "prx/libSceVideoOut/include/KeyboardInput.hpp" #include "SDL_events.h" #include "SDL_keyboard.h" +#include #include +#include +#include +#include #include -extern "C" { -int APS5_VABI sceKeyboardInit(void); -int APS5_VABI sceKeyboardOpen(int, std::int32_t, std::int32_t, const void*); -int APS5_VABI sceKeyboardClose(std::int32_t); -int APS5_VABI sceKeyboardRead(std::int32_t, KeyboardData*, std::int32_t); -int APS5_VABI sceKeyboardReadState(std::int32_t, KeyboardData*); -int APS5_VABI sceKeyboardGetKey2Char(std::int32_t, std::int32_t, std::uint32_t, std::uint32_t, std::uint16_t, KeyboardCharData*); -int APS5_VABI sceKeyboardConnectPort(void); -int APS5_VABI sceKeyboardDisconnectAll(void); +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (value) return; + std::fprintf(stderr, "%s:%u: keyboard assertion failed\n", location.file_name(), location.line()); + std::abort(); } -static void Require(bool value) { if (!value) std::abort(); } - static SDL_Event KeyEvent(Uint32 type, SDL_Scancode scancode, Uint16 mod = 0, Uint8 repeat = 0) { SDL_Event event{}; event.type = type; @@ -33,25 +31,84 @@ static SDL_Event KeyEvent(Uint32 type, SDL_Scancode scancode, Uint16 mod = 0, Ui static std::uint16_t Char(std::uint32_t led, std::uint32_t modifierKey, std::uint16_t keyCode, std::int32_t arrange = KEYBOARD_ARRANGEMENT_101) { KeyboardCharData data{}; Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, arrange, led, modifierKey, keyCode, &data) == KEYBOARD_OK); - Require(data.processed == (data.char_code != 0) && data.length == (data.processed ? 1 : 0)); - return data.char_code; + Require(data.processed ? data.char_code != 0 : data.char_code == keyCode); + Require(data.length == (data.processed ? 1 : 0)); + return data.processed ? data.char_code : 0; } static std::uint16_t Jis(std::uint32_t modifierKey, std::uint16_t keyCode, std::uint32_t led = 0) { return Char(led, modifierKey, keyCode, KEYBOARD_ARRANGEMENT_106); } +static void CheckInvalidKeys(bool opened) { + constexpr std::uint16_t invalidKeys[] = {0, 1, 2, 3, 232, 255, 256, 65535}; + for (const auto key : invalidKeys) { + for (const auto handle : {KEYBOARD_HANDLE, 42}) { + KeyboardCharData data; + std::memset(&data, 0xa5, sizeof(data)); + unsigned char before[sizeof(data)]; + std::memcpy(before, &data, sizeof(data)); + Require(sceKeyboardGetKey2Char(handle, KEYBOARD_ARRANGEMENT_101, 0, 0, key, &data) == KEYBOARD_ERROR_INVALID_ARG); + Require(std::memcmp(&data, before, sizeof(data)) == 0); + Require(sceKeyboardGetKey2Char(handle, KEYBOARD_ARRANGEMENT_106, KEYBOARD_LED_KANA, 0, key, nullptr) == KEYBOARD_ERROR_INVALID_ARG); + } + } + for (const auto key : {std::uint16_t{4}, std::uint16_t{231}}) { + KeyboardCharData data{}; + const auto expected = opened ? KEYBOARD_OK : KEYBOARD_ERROR_INVALID_HANDLE; + Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, 0, 0, key, &data) == expected); + } +} + +static void CheckUnconvertedKeys() { + constexpr std::uint16_t unconvertedKeys[] = {0x29, 0x39, 0x3a, 0x4f, 0x59, 0x88, 0xe0, 0xe7}; + for (const auto arrange : {KEYBOARD_ARRANGEMENT_101, KEYBOARD_ARRANGEMENT_106}) { + for (const auto key : unconvertedKeys) { + KeyboardCharData data{}; + Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, arrange, 0, 0, key, &data) == KEYBOARD_OK); + Require(!data.processed && data.char_code == key); + } + } +} + +static void CheckKanaLed() { + for (const auto arrange : {KEYBOARD_ARRANGEMENT_101, KEYBOARD_ARRANGEMENT_106}) { + for (const auto modifier : {std::uint8_t{0}, std::uint8_t{KEYBOARD_MOD_LEFT_SHIFT}, std::uint8_t{KEYBOARD_MOD_RIGHT_SHIFT}}) { + for (const auto led : {std::uint8_t{0}, std::uint8_t{KEYBOARD_LED_NUM_LOCK | KEYBOARD_LED_CAPS_LOCK}}) { + for (std::uint16_t key = 4; key <= 231; ++key) { + KeyboardCharData withoutKana{}; + KeyboardCharData withKana{}; + Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, arrange, led, modifier, key, &withoutKana) == KEYBOARD_OK); + Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, arrange, led | KEYBOARD_LED_KANA, modifier, key, &withKana) == KEYBOARD_OK); + Require(withKana.processed == withoutKana.processed); + Require(withKana.length == withoutKana.length && withKana.char_code == withoutKana.char_code); + } + } + } + } +} + int main() { KeyboardData data[16]{}; KeyboardCharData charData{}; + bool connected = false; Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_ERROR_NOT_INITIALIZED); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_NOT_INITIALIZED); Require(sceKeyboardInit() == KEYBOARD_OK); Require(sceKeyboardInit() == KEYBOARD_OK); + CheckInvalidKeys(false); Require(sceKeyboardOpen(1, 0, 1, nullptr) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardOpen(1, 1, 0, nullptr) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, 0, 0, 0x04, &charData) == KEYBOARD_ERROR_INVALID_HANDLE); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_HANDLE); + CheckInvalidKeys(true); + CheckUnconvertedKeys(); + CheckKanaLed(); Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_ERROR_ALREADY_OPENED); + Require(sceKeyboardGetConnection(42, &connected) == KEYBOARD_ERROR_INVALID_HANDLE); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, nullptr) == KEYBOARD_ERROR_INVALID_ARG); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_OK && connected); Require(sceKeyboardRead(42, data, 1) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardRead(KEYBOARD_HANDLE, nullptr, 1) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 17) == KEYBOARD_ERROR_INVALID_ARG); @@ -96,9 +153,11 @@ int main() { window.window.event = SDL_WINDOWEVENT_CLOSE; input.HandleEvent(window, 7); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && !data[0].connected && data[0].length == 1 && data[0].key_code[0] == 0); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_OK && !connected); window.window.event = SDL_WINDOWEVENT_FOCUS_GAINED; input.HandleEvent(window, 7); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].connected); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_OK && connected); for (std::uint16_t key = 0x04; key < 0x04 + 20; ++key) { KeyboardInputEvent press{}; @@ -115,13 +174,15 @@ int main() { Require(Char(KEYBOARD_LED_CAPS_LOCK, 0, 0x1e) == '1' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x1e) == '!'); Require(Char(0, 0, 0x27) == '0' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x27) == ')'); Require(Char(0, 0, 0x28) == '\n' && Char(0, 0, 0x2a) == '\b' && Char(0, 0, 0x2b) == '\t' && Char(0, 0, 0x2c) == ' '); - Require(Char(0, 0, 0x29) == 0 && Char(0, 0, 0x32) == 0 && Char(0, 0, 0x3a) == 0 && Char(0, 0, 0xe1) == 0); + Require(Char(0, 0, 0x29) == 0 && Char(0, 0, 0x3a) == 0 && Char(0, 0, 0xe1) == 0); Require(Char(0, 0, 0x31) == '\\' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x31) == '|'); Require(Char(0, 0, 0x34) == '\'' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x34) == '"'); Require(Char(0, 0, 0x38) == '/' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x38) == '?'); Require(Char(0, 0, 0x59) == 0 && Char(KEYBOARD_LED_NUM_LOCK, 0, 0x59) == '1'); Require(Char(KEYBOARD_LED_NUM_LOCK, 0, 0x62) == '0' && Char(KEYBOARD_LED_NUM_LOCK, 0, 0x63) == '.'); Require(Char(0, 0, 0x54) == '/' && Char(0, 0, 0x57) == '+' && Char(0, 0, 0x58) == '\n'); + Require(Char(0, 0, 0x32) == '\\' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x32) == '|'); + Require(Char(0, 0, 0x64) == '\\' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x64) == '|'); Require(Char(0, 0, 0x87) == 0 && Char(0, 0, 0x89) == 0); constexpr std::uint32_t shift = KEYBOARD_MOD_LEFT_SHIFT; @@ -134,33 +195,46 @@ int main() { Require(Jis(0, 0x31) == ']' && Jis(shift, 0x31) == '}' && Jis(0, 0x32) == ']' && Jis(shift, 0x32) == '}'); Require(Jis(0, 0x33) == ';' && Jis(shift, 0x33) == '+' && Jis(0, 0x34) == ':' && Jis(shift, 0x34) == '*'); Require(Jis(0, 0x35) == 0 && Jis(shift, 0x35) == 0 && Jis(0, 0x36) == ',' && Jis(shift, 0x37) == '>' && Jis(shift, 0x38) == '?'); - Require(Jis(0, 0x87) == '\\' && Jis(shift, 0x87) == '_' && Jis(0, 0x89) == '\\' && Jis(shift, 0x89) == '|'); + Require(Jis(0, 0x87) == '\\' && Jis(shift, 0x87) == '_' && Jis(0, 0x89) == 0x00A5 && Jis(shift, 0x89) == '|'); Require(Jis(0, 0x59, KEYBOARD_LED_NUM_LOCK) == '1' && Jis(0, 0x59) == 0 && Jis(0, 0x55) == '*' && Jis(0, 0xe1) == 0); Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, 0, 0, 0x04, nullptr) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_106, 0, 0, 0x04, nullptr) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, 2, 0, 0, 0x04, &charData) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_OK); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardReadState(KEYBOARD_HANDLE, data) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_HANDLE); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].length == 1 && data[0].key_code[0] == 0); + Require(data[0].connected); + KeyboardInputEvent pressed{}; + pressed.keyCode = 0x04; + pressed.pressed = true; + KeyboardPublishInput_nid_postfix(pressed); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].key_code[0] == 0x04); + Require(sceKeyboardDisconnectAll() == KEYBOARD_OK); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && !data[0].connected && data[0].length == 1 && data[0].key_code[0] == 0); + Require(sceKeyboardReadState(KEYBOARD_HANDLE, data) == KEYBOARD_OK && !data[0].connected && data[0].key_code[0] == 0); + Require(sceKeyboardDisconnectAll() == KEYBOARD_OK); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0); + pressed.keyCode = 0x05; + KeyboardPublishInput_nid_postfix(pressed); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0); + window.window.event = SDL_WINDOWEVENT_FOCUS_LOST; + input.HandleEvent(window, 7); + window.window.event = SDL_WINDOWEVENT_FOCUS_GAINED; + input.HandleEvent(window, 7); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].connected); Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_OK); bool threwConnect = false; try { - sceKeyboardConnectPort(); + sceKeyboardConnectPort(0, 0); + } catch (const std::runtime_error&) { threwConnect = true; } Require(threwConnect); - - bool threwDisconnect = false; - try { - sceKeyboardDisconnectAll(); - } catch (const std::runtime_error&) { - threwDisconnect = true; - } - Require(threwDisconnect); } diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index 1297f6abcf..f17760d179 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -3,10 +3,24 @@ target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR}) set_source_files_properties(UserContext.cpp PROPERTIES COMPILE_OPTIONS -fno-omit-frame-pointer) target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS}) target_sources(libSceLibcInternal PRIVATE Directory.cpp) +target_sources(libSceLibcInternal PRIVATE InitOnce.cpp) +if(BUILD_TESTING) + add_test_executable(guest_init_once_tests ${LIBS_INCLUDE_DIR}/tests/GuestInitOnce.cpp) + target_include_directories(guest_init_once_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_init_once_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_init_once_tests) + add_test(NAME guest_init_once COMMAND guest_init_once_tests) + set_tests_properties(guest_init_once PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_init_once PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() target_sources(libSceLibcInternal PRIVATE Err.cpp) configure_windows_unwind(libSceLibcInternal) target_sources(libSceLibcInternal PRIVATE FilesystemStatistics.cpp PathStatistics.cpp) target_sources(libSceLibcInternal PRIVATE Inet.cpp) +target_sources(libSceLibcInternal PRIVATE Filesystem.cpp) if(BUILD_TESTING) add_test_executable(guest_statvfs_tests ${LIBS_INCLUDE_DIR}/tests/GuestStatvfs.cpp) @@ -60,10 +74,11 @@ if(BUILD_TESTING) endif() add_test_executable(guest_posix_spawn_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSpawn.cpp) target_include_directories(guest_posix_spawn_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_posix_spawn_tests PRIVATE libSceLibcInternal libc) + target_link_libraries(guest_posix_spawn_tests PRIVATE libSceLibcInternal libc libkernel) configure_windows_unwind(guest_posix_spawn_tests) add_test(NAME guest_posix_spawn COMMAND guest_posix_spawn_tests) set_tests_properties(guest_posix_spawn PROPERTIES TIMEOUT 20) + add_asan_test_options(guest_posix_spawn handle_sigbus=0) if(WIN32) set_tests_properties(guest_posix_spawn PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") @@ -149,3 +164,15 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_libc_filesystem_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcFilesystem.cpp) + target_include_directories(guest_libc_filesystem_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_filesystem_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_libc_filesystem_tests) + add_test(NAME guest_libc_filesystem COMMAND guest_libc_filesystem_tests) + if(WIN32) + set_tests_properties(guest_libc_filesystem PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibcInternal/Directory.cpp b/core/libs/prx/libSceLibcInternal/Directory.cpp index 48a1614e85..ffb52e8614 100644 --- a/core/libs/prx/libSceLibcInternal/Directory.cpp +++ b/core/libs/prx/libSceLibcInternal/Directory.cpp @@ -1,12 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/GuestDirectory.hpp" #include "prx/libkernel/File/include/DirectoryDescriptor.hpp" #include #include -extern "C" int APS5_VABI close_nid_postfix(int d); -extern "C" int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...); -extern "C" int* APS5_VABI __error_nid_postfix(); - extern "C" void* APS5_VABI fdopendir_nid_postfix(int fd) { constexpr int SetDescriptorFlags = 2; constexpr int CloseOnExec = 1; diff --git a/core/libs/prx/libSceLibcInternal/Err.cpp b/core/libs/prx/libSceLibcInternal/Err.cpp index f16dd24ca1..3e67760ef9 100644 --- a/core/libs/prx/libSceLibcInternal/Err.cpp +++ b/core/libs/prx/libSceLibcInternal/Err.cpp @@ -1,18 +1,14 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include -using ErrExit = void (APS5_VABI*)(int); - extern "C" { extern const char* __progname_nid_postfix; -int APS5_VABI vfprintf_nid_postfix(FileStream* stream, const char* format, VaList* args); -int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...); -int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream); -char* APS5_VABI strerror_nid_postfix(int error); -int* APS5_VABI __error_nid_postfix(); + } namespace { @@ -32,8 +28,10 @@ const char* ProgramName() { void Report(const char* format, VaList* args, const int* code) { FileStream* const stream = ErrFile(); fprintf_nid_postfix(stream, "%s: ", ProgramName()); + if (format != nullptr) { vfprintf_nid_postfix(stream, format, args); + if (code != nullptr) fputs_nid_postfix(": ", stream); } if (code != nullptr) fprintf_nid_postfix(stream, "%s\n", strerror_nid_postfix(*code)); @@ -73,6 +71,7 @@ void APS5_VABI vwarnc_nid_postfix(int code, const char* format, VaList* args) { void APS5_VABI vwarn_nid_postfix(const char* format, VaList* args) { vwarnc_nid_postfix(*__error_nid_postfix(), format, args); + } void APS5_VABI vwarnx_nid_postfix(const char* format, VaList* args) { @@ -86,6 +85,7 @@ void APS5_VABI vwarnx_nid_postfix(const char* format, VaList* args) { [[noreturn]] void APS5_VABI verr_nid_postfix(int status, const char* format, VaList* args) { verrc_nid_postfix(status, *__error_nid_postfix(), format, args); + } [[noreturn]] void APS5_VABI verrx_nid_postfix(int status, const char* format, VaList* args) { @@ -97,6 +97,7 @@ void APS5_VABI warnc_nid_postfix(int code, const char* format, ...) { APS5_ERR_VA_LIST args; APS5_ERR_VA_START(args, format); vwarnc_nid_postfix(code, format, reinterpret_cast(args)); + APS5_ERR_VA_END(args); } @@ -105,6 +106,15 @@ void APS5_VABI warn_nid_postfix(const char* format, ...) { APS5_ERR_VA_LIST args; APS5_ERR_VA_START(args, format); vwarnc_nid_postfix(code, format, reinterpret_cast(args)); + + APS5_ERR_VA_END(args); +} + +void APS5_VABI _warn_nid_postfix(const char* format, ...) { + APS5_ERR_VA_LIST args; + APS5_ERR_VA_START(args, format); + vwarn_nid_postfix(format, reinterpret_cast(args)); + APS5_ERR_VA_END(args); } @@ -112,6 +122,7 @@ void APS5_VABI warnx_nid_postfix(const char* format, ...) { APS5_ERR_VA_LIST args; APS5_ERR_VA_START(args, format); vwarnx_nid_postfix(format, reinterpret_cast(args)); + APS5_ERR_VA_END(args); } @@ -119,6 +130,7 @@ void APS5_VABI warnx_nid_postfix(const char* format, ...) { APS5_ERR_VA_LIST args; APS5_ERR_VA_START(args, format); verrc_nid_postfix(status, code, format, reinterpret_cast(args)); + } [[noreturn]] void APS5_VABI err_nid_postfix(int status, const char* format, ...) { @@ -126,12 +138,21 @@ void APS5_VABI warnx_nid_postfix(const char* format, ...) { APS5_ERR_VA_LIST args; APS5_ERR_VA_START(args, format); verrc_nid_postfix(status, code, format, reinterpret_cast(args)); + +} + +[[noreturn]] void APS5_VABI _err_nid_postfix(int status, const char* format, ...) { + APS5_ERR_VA_LIST args; + APS5_ERR_VA_START(args, format); + verr_nid_postfix(status, format, reinterpret_cast(args)); + } [[noreturn]] void APS5_VABI errx_nid_postfix(int status, const char* format, ...) { APS5_ERR_VA_LIST args; APS5_ERR_VA_START(args, format); verrx_nid_postfix(status, format, reinterpret_cast(args)); + } } diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 7ca113639e..d018c80566 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HeapDiagnostics.hpp" @@ -13,14 +15,10 @@ #include #endif -extern "C" void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors); -extern "C" int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, ModuleInfoEx* info); extern "C" const char* __progname_nid_postfix; namespace { -using ThreadDestructorFunction = void (APS5_VABI*)(void*); - struct ThreadDestructor { ThreadDestructorFunction function; void* object; @@ -67,7 +65,7 @@ void APS5_VABI RunThreadDestructors_nid_no_patch() { bool FindDsoModule(const void* dsoSymbol, KernelModule& handle) { ModuleInfoEx info{}; info.st_size = sizeof(ModuleInfoEx); - if (sceKernelGetModuleInfoFromAddr(reinterpret_cast(dsoSymbol), 2, &info) != 0) { + if (sceKernelGetModuleInfoFromAddr(reinterpret_cast(dsoSymbol), 1, &info) != 0) { handle = 0; return false; } @@ -95,6 +93,7 @@ bool ForceThreadDestructorPass(KernelModule handle) { void RegisterThreadExitHook() { [[maybe_unused]] static const bool registered = [] { sceKernelSetThreadDtors(RunThreadDestructors_nid_no_patch); + return true; }(); } @@ -109,6 +108,10 @@ const char* APS5_VABI getprogname_nid_postfix() { return __progname_nid_postfix; } +const char* APS5_VABI _getprogname_nid_postfix() { + return getprogname_nid_postfix(); +} + void APS5_VABI __cxa_finalize_nid_postfix(void* dsoHandle) { CxaFinalize_nid_no_patch(dsoHandle); } diff --git a/core/libs/prx/libSceLibcInternal/FileTime.cpp b/core/libs/prx/libSceLibcInternal/FileTime.cpp index a3bbfe8edc..5b98207e49 100644 --- a/core/libs/prx/libSceLibcInternal/FileTime.cpp +++ b/core/libs/prx/libSceLibcInternal/FileTime.cpp @@ -1,16 +1,12 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include -struct LibcUtimbuf { - std::int64_t actime; - std::int64_t modtime; -}; static_assert(sizeof(LibcUtimbuf) == 16); -extern "C" int APS5_VABI utimes_nid_postfix(const char*, const KernelTimeval*); -extern "C" int* APS5_VABI __error_nid_postfix(); - extern "C" int APS5_VABI utime_nid_postfix(const char* path, const LibcUtimbuf* times) { int* const error = __error_nid_postfix(); const int saved = *error; diff --git a/core/libs/prx/libSceLibcInternal/Filesystem.cpp b/core/libs/prx/libSceLibcInternal/Filesystem.cpp new file mode 100644 index 0000000000..36341ff483 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/Filesystem.cpp @@ -0,0 +1,183 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/GuestDirectory.hpp" +#include "prx/libc/include/General.hpp" +#include "SceTypes.hpp" +#include +#include +#include + +namespace { + +constexpr std::size_t NameCapacity = 260; +constexpr std::size_t CurrentCapacity = 256; +constexpr int NoSuchFile = 2; +constexpr int Exists = 17; +constexpr int NotDirectory = 20; +constexpr int Invalid = 22; +constexpr int NameTooLong = 63; +constexpr std::uint16_t DefaultDirectoryMode = 0777; +constexpr std::uint16_t PermissionBits = 07777; + +enum FileType : int { + TypeNotFound = -1, + TypeNone = 0, + TypeRegular = 1, + TypeDirectory = 2, + TypeSymlink = 3, + TypeBlock = 4, + TypeCharacter = 5, + TypeFifo = 6, + TypeSocket = 7, + TypeUnknown = 8, +}; + +int& GuestErrno() { + return *__error_nid_postfix(); +} + +int TypeOfMode(std::uint16_t mode) { + switch (mode & 0170000) { + case 0010000: return TypeFifo; + case 0020000: return TypeCharacter; + case 0040000: return TypeDirectory; + case 0060000: return TypeBlock; + case 0100000: return TypeRegular; + case 0120000: return TypeSymlink; + case 0140000: return TypeSocket; + default: return TypeUnknown; + } +} + +int StatType(const char* path, int* perms, int* error) { + GuestErrno() = 0; + FileStat status{}; + int type; + if (stat_nid_postfix(path, &status) == 0) { + GuestErrno() = 0; + if (perms) *perms = status.st_mode & PermissionBits; + type = TypeOfMode(status.st_mode); + } else { + const int code = GuestErrno(); + type = code == NoSuchFile || code == NotDirectory || code == Invalid || code == NameTooLong ? TypeNotFound : TypeNone; + } + if (error) *error = GuestErrno(); + return type; +} + +void CopyName(char* target, const char* source) { + std::size_t length = 0; + while (length < NameCapacity - 1 && source[length] != '\0') { + target[length] = source[length]; + ++length; + } + target[length] = '\0'; +} + +bool IsDotEntry(const char* name) { + return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +} + +} + +extern "C" { + +int APS5_VABI _ZNSt10filesystem5_StatEPKcPNS_5permsEPi_nid_postfix(const char* path, int* perms, int* error) { + return StatType(path, perms, error); +} + +int APS5_VABI _ZNSt10filesystem6_LstatEPKcPNS_5permsEPi_nid_postfix(const char* path, int* perms, int* error) { + return StatType(path, perms, error); +} + +std::uint64_t APS5_VABI _ZNSt10filesystem10_File_sizeEPKcPi_nid_postfix(const char* path, int* error) { + FileStat status{}; + if (stat_nid_postfix(path, &status) != 0) { + if (error) *error = GuestErrno(); + return UINT64_MAX; + } + return static_cast(status.st_size); +} + +int APS5_VABI _ZNSt10filesystem16_Last_write_timeEPKcPlS2__nid_postfix(const char* path, std::int64_t* seconds, std::int64_t* nanoseconds) { + FileStat status{}; + if (stat_nid_postfix(path, &status) != 0) return GuestErrno(); + *seconds = status.st_mtim.tv_sec; + *nanoseconds = status.st_mtim.tv_nsec; + return 0; +} + +int APS5_VABI _ZNSt10filesystem9_Make_dirEPKcS1__nid_postfix(const char* path, const char* existing) { + GuestErrno() = 0; + std::uint16_t mode = DefaultDirectoryMode; + if (existing) { + FileStat status{}; + if (stat_nid_postfix(existing, &status) != 0) return -GuestErrno(); + mode = status.st_mode; + } + if (mkdir_nid_postfix(path, mode) == 0) return 1; + if (GuestErrno() == Exists) return 0; + return -GuestErrno(); +} + +bool APS5_VABI _ZNSt10filesystem11_Remove_dirEPKc_nid_postfix(const char* path) { + return rmdir_nid_postfix(path) == 0; +} + +int APS5_VABI _ZNSt10filesystem7_UnlinkEPKc_nid_postfix(const char* path) { + GuestErrno() = 0; + return unlink_nid_postfix(path) == 0 ? 0 : GuestErrno(); +} + +int APS5_VABI _ZNSt10filesystem6_ChmodEPKcNS_5permsE_nid_postfix(const char* path, int perms) { + return chmod_nid_postfix(path, static_cast(perms)); +} + +bool APS5_VABI _ZNSt10filesystem12_Current_getERA260_c_nid_postfix(char* target) { + char current[CurrentCapacity]; + const bool found = getcwd_nid_postfix(current, sizeof(current)) != nullptr; + if (found) CopyName(target, current); + else target[0] = '\0'; + return found; +} + +char* APS5_VABI _ZNSt10filesystem9_Read_dirERA260_cPvRNS_9file_typeE_nid_postfix(char* target, void* handle, int* type) { + *type = TypeUnknown; + GuestDirectoryEntry entry{}; + GuestDirectoryEntry* result = nullptr; + while (readdir_r_nid_postfix(handle, &entry, &result) == 0 && result) { + if (IsDotEntry(entry.name)) continue; + CopyName(target, entry.name); + return target; + } + target[0] = '\0'; + return target; +} + +void* APS5_VABI _ZNSt10filesystem9_Open_dirERA260_cPKcRiRNS_9file_typeE_nid_postfix(char* target, const char* path, int* error, int* type) { + GuestErrno() = 0; + void* handle = opendir_nid_postfix(path); + if (!handle) { + *error = GuestErrno(); + return nullptr; + } + *error = 0; + _ZNSt10filesystem9_Read_dirERA260_cPvRNS_9file_typeE_nid_postfix(target, handle, type); + if (target[0] != '\0') return handle; + closedir_nid_postfix(handle); + return nullptr; +} + +int APS5_VABI _ZNSt10filesystem10_Close_dirEPv_nid_postfix(void* handle) { + return closedir_nid_postfix(handle); +} + +void APS5_VABI _ZNSt10filesystem18_Xfilesystem_errorEPKcSt10error_code_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +void APS5_VABI _ZNSt10filesystem18_Xfilesystem_errorEPKcRKNS_4pathESt10error_code_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp b/core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp index 8cb50f1eca..998c338235 100644 --- a/core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp +++ b/core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp @@ -1,10 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceLibcInternal/include/Statvfs.hpp" #include -extern "C" int APS5_VABI _fstatfs_nid_postfix(int, KernelStatfs*); -extern "C" int* APS5_VABI __error_nid_postfix(); - extern "C" int APS5_VABI fstatvfs_nid_postfix(int descriptor, LibcStatvfs* output) { int* const error = __error_nid_postfix(); const int saved = *error; diff --git a/core/libs/prx/libSceLibcInternal/Getopt.cpp b/core/libs/prx/libSceLibcInternal/Getopt.cpp index 80645072b4..43248491d9 100644 --- a/core/libs/prx/libSceLibcInternal/Getopt.cpp +++ b/core/libs/prx/libSceLibcInternal/Getopt.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -7,12 +9,6 @@ #include #include -struct GuestOption { - const char* name; - int hasArgument; - int* flag; - int value; -}; static_assert(sizeof(GuestOption) == 32); static_assert(offsetof(GuestOption, hasArgument) == 8); static_assert(offsetof(GuestOption, flag) == 16); @@ -21,7 +17,6 @@ static_assert(offsetof(GuestOption, value) == 24); extern "C" { extern const char* __progname_nid_postfix; extern FileStream* __stderrp_nid_postfix; -char* APS5_VABI getenv_nid_postfix(const char* name); int opterr_nid_postfix = 1; int optind_nid_postfix = 1; diff --git a/core/libs/prx/libSceLibcInternal/Inet.cpp b/core/libs/prx/libSceLibcInternal/Inet.cpp index 90996aacf6..1dd8ad853d 100644 --- a/core/libs/prx/libSceLibcInternal/Inet.cpp +++ b/core/libs/prx/libSceLibcInternal/Inet.cpp @@ -1,12 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -const char* APS5_VABI __inet_ntop_nid_postfix(int family, const void* source, char* destination, std::uint32_t capacity); -} - namespace { constexpr int AddressFamilyInet = 2; @@ -94,12 +92,18 @@ std::uint32_t APS5_VABI inet_addr_nid_postfix(const char* text) { char* APS5_VABI inet_ntoa_r_nid_postfix(std::uint32_t address, char* buffer, std::uint32_t size) { __inet_ntop_nid_postfix(AddressFamilyInet, &address, buffer, size); + return buffer; } +char* APS5_VABI __inet_ntoa_r_nid_postfix(std::uint32_t address, char* buffer, std::uint32_t size) { + return inet_ntoa_r_nid_postfix(address, buffer, size); +} + char* APS5_VABI inet_ntoa_nid_postfix(std::uint32_t address) { std::memcpy(g_ntoaBuffer, "[inet_ntoa error]", sizeof(g_ntoaBuffer)); __inet_ntop_nid_postfix(AddressFamilyInet, &address, g_ntoaBuffer, sizeof(g_ntoaBuffer)); + return g_ntoaBuffer; } diff --git a/core/libs/prx/libSceLibcInternal/InitOnce.cpp b/core/libs/prx/libSceLibcInternal/InitOnce.cpp new file mode 100644 index 0000000000..94d59b1ce7 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/InitOnce.cpp @@ -0,0 +1,56 @@ +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +namespace { + +constexpr std::uintptr_t OnceIdle = 0; +constexpr std::uintptr_t OnceRunning = 1; +constexpr std::uintptr_t OnceDone = 2; +constexpr unsigned long InitFailed = 4; + +std::atomic& OnceState(void** once) { + return *reinterpret_cast*>(once); +} + +} + +extern "C" { + +int APS5_VABI __std_init_once_begin_initialize_clr_nid_postfix(void** once, unsigned long flags, int* pending, void** context) { + (void)flags; + (void)context; + auto& state = OnceState(once); + for (;;) { + std::uintptr_t current = state.load(std::memory_order_acquire); + if (current == OnceIdle) { + if (state.compare_exchange_weak(current, OnceRunning, std::memory_order_acq_rel)) { + *pending = 1; + return 1; + } + continue; + } + if (current == OnceDone) { + *pending = 0; + return 1; + } + sceKernelSyncOnAddressWait(reinterpret_cast(once), static_cast(OnceRunning), nullptr, nullptr); + } +} + +int APS5_VABI __std_init_once_complete_clr_nid_postfix(void** once, unsigned long flags, void* context) { + (void)context; + OnceState(once).store(flags == InitFailed ? OnceIdle : OnceDone, std::memory_order_release); + sceKernelSyncOnAddressWake(once, INT_MAX); + return 1; +} + +[[noreturn]] void APS5_VABI __std_init_once_link_alternate_names_and_abort_nid_postfix() { + std::abort(); +} + +} diff --git a/core/libs/prx/libSceLibcInternal/PathStatistics.cpp b/core/libs/prx/libSceLibcInternal/PathStatistics.cpp index c66f3dcc3b..bca817dd44 100644 --- a/core/libs/prx/libSceLibcInternal/PathStatistics.cpp +++ b/core/libs/prx/libSceLibcInternal/PathStatistics.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/FilesystemError.hpp" #include #include @@ -18,7 +19,6 @@ #endif struct LibcStatvfs; -extern "C" int APS5_VABI fstatvfs_nid_postfix(int, LibcStatvfs*); namespace { int Fail(int error) { diff --git a/core/libs/prx/libSceLibcInternal/SignalList.cpp b/core/libs/prx/libSceLibcInternal/SignalList.cpp index d27a5531c4..e33acb01fe 100644 --- a/core/libs/prx/libSceLibcInternal/SignalList.cpp +++ b/core/libs/prx/libSceLibcInternal/SignalList.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -77,16 +79,16 @@ extern const char* const sys_signame_nid_postfix[32] = { "USR2", }; -std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count); - void APS5_VABI psignal_nid_postfix(unsigned int signal, const char* prefix) { const char* const message = signal < 32 ? sys_siglist_nid_postfix[signal] : "Unknown signal"; if (prefix != nullptr && prefix[0] != 0) { _write_nid_postfix(2, prefix, std::strlen(prefix)); _write_nid_postfix(2, ": ", 2); + } _write_nid_postfix(2, message, std::strlen(message)); _write_nid_postfix(2, "\n", 1); + } } diff --git a/core/libs/prx/libSceLibcInternal/Spawn.cpp b/core/libs/prx/libSceLibcInternal/Spawn.cpp index b40cbc9d10..4c7bd815b2 100644 --- a/core/libs/prx/libSceLibcInternal/Spawn.cpp +++ b/core/libs/prx/libSceLibcInternal/Spawn.cpp @@ -1,7 +1,16 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libSceLibcInternal/include/Spawn.hpp" +#include "prx/libc/include/ChildProcesses.hpp" #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/General.hpp" +#ifdef _WIN32 +#include "prx/libc/include/WindowsDescriptor.hpp" +#endif +#include "prx/libkernel/File/include/FileFlags.hpp" +#include #include +#include #include #include #include @@ -9,15 +18,42 @@ #include #include #include +#include +#ifdef _WIN32 +#include +#else +#include +#endif +#if defined(__linux__) +#include +#elif defined(__APPLE__) +#include +#include +#include +#endif -extern "C" char* APS5_VABI getenv_nid_postfix(const char* name); +#if defined(__linux__) +extern char** environ; +#endif namespace { constexpr int NoSuchFile = 2; constexpr int BadDescriptor = 9; constexpr int OutOfMemory = 12; +constexpr int Fault = 14; +constexpr int InvalidArgument = 22; constexpr std::string_view DefaultSearchPath = "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"; +constexpr std::string_view ApplicationRoot = "/app0/"; +constexpr std::string_view MainExecutable = "/app0/eboot.bin"; +#ifdef _WIN32 +constexpr wchar_t LoadExecParentVariable[] = L"ANYPS5_LOADEXEC_PARENT"; +constexpr wchar_t LoadExecSkipVariable[] = L"ANYPS5_LOADEXEC_ARGV_SKIP="; +#else +constexpr std::uint32_t SpawnFlagsImplemented = 0x03; +#endif +constexpr std::uint32_t SpawnFlagResetIds = 0x01; +constexpr std::uint32_t SpawnFlagSetGroup = 0x02; PosixSpawnAttributes& Attributes(PosixSpawnAttributes* const* attributes, const char* function) { if (attributes == nullptr || *attributes == nullptr) throw std::invalid_argument(std::string(function) + ": uninitialized attributes"); @@ -29,36 +65,6 @@ PosixSpawnFileActions& FileActions(PosixSpawnFileActions* const* actions, const return **actions; } -bool GuestPathExists(const std::string& path) { - std::filesystem::path native; - if (ResolvePathError_nid_no_patch(path.c_str(), native) != 0) return false; - std::error_code error; - return std::filesystem::exists(native, error); -} - -bool ProgramFound(std::string_view name, bool searchesPath) { - if (name.empty()) return false; - if (!searchesPath || name.find('/') != std::string_view::npos) return GuestPathExists(std::string(name)); - const char* variable = getenv_nid_postfix("PATH"); - std::string_view directories = variable != nullptr ? std::string_view(variable) : DefaultSearchPath; - while (true) { - const auto separator = directories.find(':'); - const auto directory = directories.substr(0, separator); - if (GuestPathExists((directory.empty() ? std::string(".") : std::string(directory)) + '/' + std::string(name))) return true; - if (separator == std::string_view::npos) return false; - directories.remove_prefix(separator + 1); - } -} - -int Spawn(const char* function, const char* file, bool searchesPath, PosixSpawnFileActions* const* actions, PosixSpawnAttributes* const* attributes) { - if (file == nullptr) throw std::invalid_argument(std::string(function) + ": null path"); - if (actions != nullptr) FileActions(actions, function); - if (attributes != nullptr) Attributes(attributes, function); - if (!ProgramFound(file, searchesPath)) return NoSuchFile; - NotImplemented_nid_no_patch((std::string(function) + ": process creation").c_str()); - return -1; -} - int Append(PosixSpawnFileActions& actions, PosixSpawnFileAction action) { try { actions.actions.push_back(std::move(action)); @@ -68,6 +74,382 @@ int Append(PosixSpawnFileActions& actions, PosixSpawnFileAction action) { return 0; } +bool GuestPathExists(const std::string& path) { + std::filesystem::path native; + if (ResolvePathError_nid_no_patch(path.c_str(), native) != 0) return false; + std::error_code error; + return std::filesystem::exists(native, error); +} + +bool ProgramFound(std::string_view name, bool searchesPath, std::string& found) { + if (name.empty()) return false; + if (!searchesPath || name.find('/') != std::string_view::npos) { + found = std::string(name); + return GuestPathExists(found); + } + const char* variable = getenv_nid_postfix("PATH"); + std::string_view directories = variable != nullptr ? std::string_view(variable) : DefaultSearchPath; + while (true) { + const auto separator = directories.find(':'); + const auto directory = directories.substr(0, separator); + found = (directory.empty() ? std::string(".") : std::string(directory)) + '/' + std::string(name); + if (GuestPathExists(found)) return true; + if (separator == std::string_view::npos) return false; + directories.remove_prefix(separator + 1); + } +} + +std::string FlattenedExecutableName(const std::string& guest, const char* function) { + std::string_view relative(guest); + if (!relative.starts_with(ApplicationRoot)) NotImplemented_nid_no_patch((std::string(function) + ": executables outside /app0 (" + guest + ")").c_str()); + relative.remove_prefix(ApplicationRoot.size()); + std::string name; + while (true) { + const std::size_t separator = relative.find('/'); + const std::string_view component = relative.substr(0, separator); + if (component.empty() || component == "." || component == "..") NotImplemented_nid_no_patch((std::string(function) + ": a path with an empty, . or .. component (" + guest + ")").c_str()); + name += (name.empty() ? "" : ".") + std::string(component); + if (separator == std::string_view::npos) return name; + relative.remove_prefix(separator + 1); + } +} + +std::filesystem::path OwnExecutable() { +#if defined(_WIN32) + std::wstring own(32768, L'\0'); + const DWORD length = GetModuleFileNameW(nullptr, own.data(), static_cast(own.size())); + if (length == 0 || length >= own.size()) throw std::system_error(GetLastError(), std::system_category(), "posix_spawn: GetModuleFileNameW"); + own.resize(length); + return std::filesystem::path(own); +#elif defined(__APPLE__) + std::uint32_t size = 0; + _NSGetExecutablePath(nullptr, &size); + std::vector executable(size); + std::error_code error; + if (_NSGetExecutablePath(executable.data(), &size) == 0) return std::filesystem::canonical(executable.data(), error); + throw std::runtime_error("posix_spawn: cannot locate the executable"); +#else + std::error_code error; + const auto own = std::filesystem::read_symlink("/proc/self/exe", error); + if (error) throw std::system_error(error, "posix_spawn: cannot read /proc/self/exe"); + return own; +#endif +} + +std::filesystem::path ConvertedExecutableTarget(const char* function, const std::string& guest) { + const auto own = OwnExecutable(); + if (guest == MainExecutable) return own; + std::error_code error; + const auto name = std::filesystem::path(reinterpret_cast((FlattenedExecutableName(guest, function) + own.extension().string()).c_str())); + const auto target = own.parent_path() / name; + if (!std::filesystem::is_regular_file(target, error)) + NotImplemented_nid_no_patch((std::string(function) + ": " + guest + " without its converted executable; relink with --executable " + std::string(guest.substr(ApplicationRoot.size()))).c_str()); + return target; +} + +int HostOpenFlags(std::int32_t sceFlags) { + int f = 0; + const int acc = sceFlags & SCE_KERNEL_O_ACCMODE; + if (acc == SCE_KERNEL_O_RDONLY) f |= 0; + else if (acc == SCE_KERNEL_O_WRONLY) f |= 1; + else if (acc == SCE_KERNEL_O_RDWR) f |= 2; + else throw std::invalid_argument("posix_spawn_file_actions_addopen: invalid access mode"); +#ifdef _WIN32 + if (sceFlags & SCE_KERNEL_O_APPEND) f |= _O_APPEND; + if (sceFlags & SCE_KERNEL_O_CREAT) f |= _O_CREAT; + if (sceFlags & SCE_KERNEL_O_TRUNC) f |= _O_TRUNC; + if (sceFlags & SCE_KERNEL_O_EXCL) f |= _O_EXCL; + f |= _O_BINARY; +#else + if (sceFlags & SCE_KERNEL_O_APPEND) f |= O_APPEND; + if (sceFlags & SCE_KERNEL_O_CREAT) f |= O_CREAT; + if (sceFlags & SCE_KERNEL_O_TRUNC) f |= O_TRUNC; + if (sceFlags & SCE_KERNEL_O_EXCL) f |= O_EXCL; +#endif + return f; +} + +#ifdef _WIN32 +std::wstring Utf16(const std::string& text) { + if (text.empty()) return {}; + const int size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.data(), static_cast(text.size()), nullptr, 0); + if (size <= 0) throw std::runtime_error("posix_spawn: argument is not valid UTF-8"); + std::wstring wide(static_cast(size), L'\0'); + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.data(), static_cast(text.size()), wide.data(), size); + return wide; +} + +std::wstring QuotedArgument(const std::wstring& argument) { + if (!argument.empty() && argument.find_first_of(L" \t\n\v\"") == std::wstring::npos) return argument; + std::wstring quoted = L"\""; + for (std::size_t index = 0;; ++index) { + std::size_t backslashes = 0; + while (index < argument.size() && argument[index] == L'\\') { + ++backslashes; + ++index; + } + if (index == argument.size()) { + quoted.append(backslashes * 2, L'\\'); + break; + } + quoted.append(argument[index] == L'"' ? backslashes * 2 + 1 : backslashes, L'\\'); + quoted.push_back(argument[index]); + } + quoted.push_back(L'"'); + return quoted; +} +#endif + +std::string HexFlag(std::uint32_t value) { + constexpr char digits[] = "0123456789abcdef"; + std::string text; + if (value == 0) text.push_back('0'); + while (value > 0) { + text.insert(text.begin(), digits[value & 0xfu]); + value >>= 4; + } + return "0x" + text; +} + +std::uint32_t SpawnAttributeFlags(PosixSpawnAttributes* const* attributes) { + return attributes != nullptr && *attributes != nullptr ? static_cast(static_cast((*attributes)->flags)) : 0; +} + +#ifdef _WIN32 +struct ChildStreams { + HANDLE handles[3] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; +}; + +HANDLE DuplicateInheritable(HANDLE source) { + HANDLE handle = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), source, GetCurrentProcess(), &handle, 0, TRUE, DUPLICATE_SAME_ACCESS)) + throw std::system_error(GetLastError(), std::system_category(), "posix_spawn: duplicating a descriptor"); + return handle; +} + +void CloseRedirectedStreams(ChildStreams& streams) { + for (HANDLE handle : streams.handles) + if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle); +} + +void ReplaceChildStream(ChildStreams& streams, int descriptor, HANDLE source) { + HANDLE previous = streams.handles[descriptor]; + streams.handles[descriptor] = source == INVALID_HANDLE_VALUE ? INVALID_HANDLE_VALUE : DuplicateInheritable(source); + if (previous != INVALID_HANDLE_VALUE) CloseHandle(previous); +} + +int ApplyChildRedirection(ChildStreams& streams, const PosixSpawnFileAction& action) { + switch (action.kind) { + case PosixSpawnFileActionKind::Close: + if (action.descriptor >= 0 && action.descriptor < 3) ReplaceChildStream(streams, action.descriptor, INVALID_HANDLE_VALUE); + return 0; + case PosixSpawnFileActionKind::Duplicate: + if (action.descriptor < 0 || action.newDescriptor < 0) return BadDescriptor; + if (action.newDescriptor > 2) + NotImplemented_nid_no_patch(("posix_spawn: a child descriptor " + std::to_string(action.newDescriptor) + " beyond the standard streams").c_str()); + { + const HANDLE source = WindowsDescriptorHandle_nid_no_patch(action.descriptor); + if (source == INVALID_HANDLE_VALUE) return BadDescriptor; + ReplaceChildStream(streams, action.newDescriptor, source); + } + return 0; + case PosixSpawnFileActionKind::Open: { + if (action.descriptor < 0) return BadDescriptor; + if (action.descriptor > 2) + NotImplemented_nid_no_patch(("posix_spawn: a child descriptor " + std::to_string(action.descriptor) + " beyond the standard streams").c_str()); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(action.path.c_str(), native)) return error; + try { + const int fd = ::_wopen(native.c_str(), HostOpenFlags(action.openFlags), action.mode); + if (fd < 0) return errno; + const HANDLE source = WindowsDescriptorHandle_nid_no_patch(fd); + ReplaceChildStream(streams, action.descriptor, source); + ::_close(fd); + } catch (const std::invalid_argument&) { + return InvalidArgument; + } + return 0; + } + } + return 0; +} + +std::vector ChildEnvironment(char* const* environment) { + std::vector entries; + const auto missesMarkers = [](const std::wstring& entry) { + return entry.compare(0, std::size(LoadExecParentVariable) - 1, LoadExecParentVariable) != 0 && + entry.compare(0, std::size(LoadExecSkipVariable) - 1, LoadExecSkipVariable) != 0; + }; + if (environment != nullptr) + for (std::size_t index = 0; environment[index] != nullptr; ++index) { + const std::wstring entry = Utf16(environment[index]); + if (missesMarkers(entry)) + entries.push_back(entry); + } + else { + const LPWCH block = GetEnvironmentStringsW(); + for (const wchar_t* text = block; *text != L'\0'; ) { + std::size_t length = 0; + while (text[length] != L'\0') ++length; + if (length > 0) { + const std::wstring entry(text, length); + if (missesMarkers(entry)) + entries.push_back(entry); + } + text += length + 1; + } + FreeEnvironmentStringsW(block); + } + entries.push_back(std::wstring(LoadExecSkipVariable) + L"1"); + std::sort(entries.begin(), entries.end(), [](const auto& left, const auto& right) { return _wcsicmp(left.c_str(), right.c_str()) < 0; }); + std::vector block; + for (const auto& entry : entries) { + block.insert(block.end(), entry.begin(), entry.end()); + block.push_back(0); + } + block.push_back(0); + return block; +} + +int SpawnChild(const char* function, const std::string& guest, PosixSpawnFileActions* const* actions, + PosixSpawnAttributes* const* attributes, std::int32_t* processId, char* const* arguments, char* const* environment) { + const auto target = ConvertedExecutableTarget(function, guest); + if (const std::uint32_t attributes_flags = SpawnAttributeFlags(attributes); attributes_flags != 0) + NotImplemented_nid_no_patch(("posix_spawn: the attribute flags " + HexFlag(attributes_flags) + " on this host").c_str()); + ChildStreams streams; + const DWORD identifiers[] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}; + bool redirected = false; + for (std::size_t index = 0; index < 3; ++index) { + const HANDLE source = GetStdHandle(identifiers[index]); + if (source == nullptr || source == INVALID_HANDLE_VALUE) continue; + streams.handles[index] = DuplicateInheritable(source); + redirected = true; + } + if (actions != nullptr) { + for (const auto& action : (*actions)->actions) { + if (action.kind != PosixSpawnFileActionKind::Close || (action.descriptor >= 0 && action.descriptor < 3)) redirected = true; + const int error = ApplyChildRedirection(streams, action); + if (error != 0) { + CloseRedirectedStreams(streams); + return error; + } + } + } + std::wstring command = L"\"" + target.wstring() + L"\""; + for (std::size_t index = 0; arguments[index] != nullptr; ++index) + command += L" " + QuotedArgument(Utf16(arguments[index])); + if (command.size() >= 32767) throw std::runtime_error("posix_spawn: command line is too long"); + auto environmentBlock = ChildEnvironment(environment); + PROCESS_INFORMATION process{}; + const BOOL created = [&] { + if (!redirected || (streams.handles[0] == INVALID_HANDLE_VALUE && streams.handles[1] == INVALID_HANDLE_VALUE && streams.handles[2] == INVALID_HANDLE_VALUE)) { + STARTUPINFOW plain{}; + plain.cb = sizeof(plain); + return CreateProcessW(const_cast(target.c_str()), command.data(), nullptr, nullptr, FALSE, + environmentBlock.empty() ? 0u : CREATE_UNICODE_ENVIRONMENT, environmentBlock.empty() ? nullptr : environmentBlock.data(), + nullptr, &plain, &process); + } + STARTUPINFOEXW startup{}; + startup.StartupInfo.cb = sizeof(startup); + startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + startup.StartupInfo.hStdInput = streams.handles[0]; + startup.StartupInfo.hStdOutput = streams.handles[1]; + startup.StartupInfo.hStdError = streams.handles[2]; + std::vector inherited; + for (HANDLE handle : streams.handles) + if (handle != INVALID_HANDLE_VALUE) inherited.push_back(handle); + SIZE_T bytes = 0; + InitializeProcThreadAttributeList(nullptr, 1, 0, &bytes); + std::vector storage((bytes + sizeof(std::max_align_t) - 1) / sizeof(std::max_align_t)); + auto* list = reinterpret_cast(storage.data()); + if (!InitializeProcThreadAttributeList(list, 1, 0, &bytes) || + !UpdateProcThreadAttribute(list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited.data(), inherited.size() * sizeof(HANDLE), nullptr, nullptr)) + throw std::system_error(GetLastError(), std::system_category(), "posix_spawn: selecting inherited handles"); + const std::unique_ptr<_PROC_THREAD_ATTRIBUTE_LIST, void (*)(_PROC_THREAD_ATTRIBUTE_LIST*)> cleanup(list, DeleteProcThreadAttributeList); + return CreateProcessW(const_cast(target.c_str()), command.data(), nullptr, nullptr, TRUE, + EXTENDED_STARTUPINFO_PRESENT | (environmentBlock.empty() ? 0u : CREATE_UNICODE_ENVIRONMENT), + environmentBlock.empty() ? nullptr : environmentBlock.data(), nullptr, &startup.StartupInfo, &process); + }(); + if (!created) { + const DWORD error = GetLastError(); + CloseRedirectedStreams(streams); + throw std::system_error(error, std::system_category(), "posix_spawn: creating the converted executable"); + } + CloseRedirectedStreams(streams); + CloseHandle(process.hThread); + RegisterChildProcess_nid_no_patch(process.dwProcessId, static_cast(reinterpret_cast(process.hProcess))); + *processId = static_cast(process.dwProcessId); + return 0; +} +#else +int SpawnChild(const char* function, const std::string& guest, PosixSpawnFileActions* const* actions, + PosixSpawnAttributes* const* attributes, std::int32_t* processId, char* const* arguments, char* const* environment) { + const auto target = ConvertedExecutableTarget(function, guest); + posix_spawnattr_t hostAttributes; + posix_spawnattr_t* usedAttributes = nullptr; + const std::uint32_t flags = SpawnAttributeFlags(attributes); + if ((flags & ~SpawnFlagsImplemented) != 0) + NotImplemented_nid_no_patch(("posix_spawn: the attribute flags " + HexFlag(flags)).c_str()); + if (flags != 0) { + if (posix_spawnattr_init(&hostAttributes) != 0) return Fault; + usedAttributes = &hostAttributes; + if ((flags & SpawnFlagResetIds) != 0) posix_spawnattr_setflags(usedAttributes, POSIX_SPAWN_RESETIDS); + if ((flags & SpawnFlagSetGroup) != 0) { + posix_spawnattr_setflags(usedAttributes, POSIX_SPAWN_SETPGROUP); + posix_spawnattr_setpgroup(usedAttributes, (*attributes)->processGroup); + } + } + posix_spawn_file_actions_t hostActions; + posix_spawn_file_actions_t* usedActions = nullptr; + if (actions != nullptr && !(*actions)->actions.empty()) { + if (posix_spawn_file_actions_init(&hostActions) != 0) { + if (usedAttributes != nullptr) posix_spawnattr_destroy(usedAttributes); + return Fault; + } + usedActions = &hostActions; + } + int result = 0; + if (actions != nullptr && usedActions != nullptr) + for (const auto& action : (*actions)->actions) { + switch (action.kind) { + case PosixSpawnFileActionKind::Close: + result = posix_spawn_file_actions_addclose(usedActions, action.descriptor); + break; + case PosixSpawnFileActionKind::Duplicate: + result = posix_spawn_file_actions_adddup2(usedActions, action.descriptor, action.newDescriptor); + break; + case PosixSpawnFileActionKind::Open: { + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(action.path.c_str(), native)) result = error; + else { + try { + result = posix_spawn_file_actions_addopen(usedActions, action.descriptor, native.c_str(), HostOpenFlags(action.openFlags), action.mode); + } catch (const std::invalid_argument&) { + result = InvalidArgument; + } + } + break; + } + } + if (result != 0) break; + } + if (result == 0) { + char* const* hostEnvironment = environment; +#if defined(__linux__) + if (hostEnvironment == nullptr) hostEnvironment = environ; +#elif defined(__APPLE__) + if (hostEnvironment == nullptr) hostEnvironment = *_NSGetEnviron(); +#endif + pid_t childPid = -1; + result = posix_spawn(&childPid, target.c_str(), usedActions, usedAttributes, arguments, hostEnvironment); + if (result == 0) *processId = static_cast(childPid); + } + if (usedActions != nullptr) posix_spawn_file_actions_destroy(usedActions); + if (usedAttributes != nullptr) posix_spawnattr_destroy(usedAttributes); + return result; +} +#endif + } extern "C" { @@ -198,18 +580,23 @@ int APS5_VABI posix_spawn_file_actions_addclose_nid_postfix(PosixSpawnFileAction return Append(FileActions(actions, "posix_spawn_file_actions_addclose"), {PosixSpawnFileActionKind::Close, descriptor, {}, 0, 0, 0}); } +int SpawnProgram(const char* function, const char* file, bool searchesPath, PosixSpawnFileActions* const* actions, + PosixSpawnAttributes* const* attributes, std::int32_t* processId, char* const* arguments, char* const* environment) { + if (file == nullptr) throw std::invalid_argument(std::string(function) + ": null path"); + if (actions != nullptr) FileActions(actions, function); + if (attributes != nullptr) Attributes(attributes, function); + std::string found; + if (!ProgramFound(file, searchesPath, found)) return NoSuchFile; + if (arguments == nullptr) return Fault; + return SpawnChild(function, found, actions, attributes, processId, arguments, environment); +} + int APS5_VABI posix_spawn_nid_postfix(std::int32_t* processId, const char* path, PosixSpawnFileActions* const* actions, PosixSpawnAttributes* const* attributes, char* const* arguments, char* const* environment) { - (void)processId; - (void)arguments; - (void)environment; - return Spawn("posix_spawn", path, false, actions, attributes); + return SpawnProgram("posix_spawn", path, false, actions, attributes, processId, arguments, environment); } int APS5_VABI posix_spawnp_nid_postfix(std::int32_t* processId, const char* file, PosixSpawnFileActions* const* actions, PosixSpawnAttributes* const* attributes, char* const* arguments, char* const* environment) { - (void)processId; - (void)arguments; - (void)environment; - return Spawn("posix_spawnp", file, true, actions, attributes); + return SpawnProgram("posix_spawnp", file, true, actions, attributes, processId, arguments, environment); } } diff --git a/core/libs/prx/libSceLibcInternal/UserContext.cpp b/core/libs/prx/libSceLibcInternal/UserContext.cpp index f5f6c7eae8..9ef01bd612 100644 --- a/core/libs/prx/libSceLibcInternal/UserContext.cpp +++ b/core/libs/prx/libSceLibcInternal/UserContext.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/GuestStacks.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -7,8 +9,6 @@ #include #include -extern "C" int APS5_VABI setcontext_nid_postfix(const UserUcontext* context); - namespace { using ContextFunction = void (APS5_VABI*)(std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t); @@ -20,12 +20,53 @@ constexpr std::uint64_t MinimumStackSize = 2048; extern "C" { +#if defined(_WIN32) && defined(__SEH__) +[[noreturn]] void APS5_VABI Aps5MakecontextStart_nid_no_patch(UserUcontext* context, ContextFunction function, const std::uint64_t* arguments); + +[[noreturn]] void APS5_VABI Aps5MakecontextFinish_nid_no_patch(UserUcontext* context) { + if (context->link == nullptr) LibcExit_nid_no_patch(0); + setcontext_nid_postfix(context->link); + + std::abort(); +} + +asm(R"( +.text +.globl Aps5MakecontextStart_nid_no_patch +.def Aps5MakecontextStart_nid_no_patch; .scl 2; .type 32; .endef +.seh_proc Aps5MakecontextStart_nid_no_patch +Aps5MakecontextStart_nid_no_patch: + pushq %rbp + .seh_pushreg %rbp + subq $48, %rsp + .seh_stackalloc 48 + leaq 48(%rsp), %rbp + .seh_setframe %rbp, 48 + .seh_endprologue + movq %rdi, -8(%rbp) + movq %rsi, %rax + movq %rdx, %r10 + movq (%r10), %rdi + movq 8(%r10), %rsi + movq 16(%r10), %rdx + movq 24(%r10), %rcx + movq 32(%r10), %r8 + movq 40(%r10), %r9 + call *%rax + movq -8(%rbp), %rdi + call Aps5MakecontextFinish_nid_no_patch + ud2 +.seh_endproc +)"); +#else [[noreturn]] void APS5_VABI Aps5MakecontextStart_nid_no_patch(UserUcontext* context, ContextFunction function, const std::uint64_t* arguments) { function(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); if (context->link == nullptr) LibcExit_nid_no_patch(0); setcontext_nid_postfix(context->link); + std::abort(); } +#endif void APS5_VABI makecontext_nid_postfix(UserUcontext* context, void (APS5_VABI* start)(), int count, ...) { if (context == nullptr || context->mcontext.len != sizeof(UserMcontext)) return; diff --git a/core/libs/prx/libSceLibcInternal/include/Api.hpp b/core/libs/prx/libSceLibcInternal/include/Api.hpp new file mode 100644 index 0000000000..d10e3f958b --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/include/Api.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include "prx/libSceLibcInternal/include/Statvfs.hpp" +#include "prx/libSceLibcInternal/include/Spawn.hpp" +#include "prx/libkernel/System/include/UserContext.hpp" +#include "prx/libc/include/FileStream.hpp" +#include "SceTypes.hpp" + +using ErrExit = void (APS5_VABI*)(int); + +struct GuestOption { + const char* name; + int hasArgument; + int* flag; + int value; +}; + +struct LibcUtimbuf { + std::int64_t actime; + std::int64_t modtime; +}; + +using ThreadDestructorFunction = void (APS5_VABI*)(void*); + +extern "C" { + +void* APS5_VABI fdopendir_nid_postfix(int fd); +void APS5_VABI err_set_file_nid_postfix(FileStream* stream); +void APS5_VABI err_set_exit_nid_postfix(ErrExit function); +void APS5_VABI vwarnc_nid_postfix(int code, const char* format, VaList* args); +void APS5_VABI vwarn_nid_postfix(const char* format, VaList* args); +void APS5_VABI vwarnx_nid_postfix(const char* format, VaList* args); +void APS5_VABI warnc_nid_postfix(int code, const char* format, ...); +void APS5_VABI warn_nid_postfix(const char* format, ...); +void APS5_VABI _warn_nid_postfix(const char* format, ...); +void APS5_VABI warnx_nid_postfix(const char* format, ...); +const char* APS5_VABI getprogname_nid_postfix(); +const char* APS5_VABI _getprogname_nid_postfix(); +void APS5_VABI __cxa_finalize_nid_postfix(void* dsoHandle); +void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info); +int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(ThreadDestructorFunction destructor, void* object, void* dsoSymbol); +void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix(); +int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle); +unsigned int APS5_VABI alarm_nid_postfix(unsigned int seconds); +int APS5_VABI fstatvfs_nid_postfix(int descriptor, LibcStatvfs* output); +int APS5_VABI utime_nid_postfix(const char* path, const LibcUtimbuf* times); +int APS5_VABI getopt_nid_postfix(int count, char* const* arguments, const char* options); +int APS5_VABI getopt_long_nid_postfix(int count, char* const* arguments, const char* options, const GuestOption* longOptions, int* index); +int APS5_VABI getopt_long_only_nid_postfix(int count, char* const* arguments, const char* options, const GuestOption* longOptions, int* index); +int APS5_VABI inet_aton_nid_postfix(const char* text, std::uint32_t* address); +std::uint32_t APS5_VABI inet_addr_nid_postfix(const char* text); +char* APS5_VABI inet_ntoa_r_nid_postfix(std::uint32_t address, char* buffer, std::uint32_t size); +char* APS5_VABI __inet_ntoa_r_nid_postfix(std::uint32_t address, char* buffer, std::uint32_t size); +char* APS5_VABI inet_ntoa_nid_postfix(std::uint32_t address); +int APS5_VABI statvfs_nid_postfix(const char* path, LibcStatvfs* output); +void APS5_VABI psignal_nid_postfix(unsigned int signal, const char* prefix); +int APS5_VABI posix_spawnattr_init_nid_postfix(PosixSpawnAttributes** attributes); +int APS5_VABI posix_spawnattr_destroy_nid_postfix(PosixSpawnAttributes** attributes); +int APS5_VABI posix_spawnattr_setflags_nid_postfix(PosixSpawnAttributes** attributes, std::int16_t flags); +int APS5_VABI posix_spawnattr_setsigdefault_nid_postfix(PosixSpawnAttributes** attributes, const GuestSignalSet* signalDefault); +int APS5_VABI posix_spawnattr_getflags_nid_postfix(PosixSpawnAttributes* const* attributes, std::int16_t* flags); +int APS5_VABI posix_spawnattr_getpgroup_nid_postfix(PosixSpawnAttributes* const* attributes, std::int32_t* processGroup); +int APS5_VABI posix_spawnattr_getschedparam_nid_postfix(PosixSpawnAttributes* const* attributes, std::int32_t* schedulingParameter); +int APS5_VABI posix_spawnattr_getschedpolicy_nid_postfix(PosixSpawnAttributes* const* attributes, std::int32_t* schedulingPolicy); +int APS5_VABI posix_spawnattr_getsigdefault_nid_postfix(PosixSpawnAttributes* const* attributes, GuestSignalSet* signalDefault); +int APS5_VABI posix_spawnattr_getsigmask_nid_postfix(PosixSpawnAttributes* const* attributes, GuestSignalSet* signalMask); +int APS5_VABI posix_spawnattr_setpgroup_nid_postfix(PosixSpawnAttributes** attributes, std::int32_t processGroup); +int APS5_VABI posix_spawnattr_setschedparam_nid_postfix(PosixSpawnAttributes** attributes, const std::int32_t* schedulingParameter); +int APS5_VABI posix_spawnattr_setschedpolicy_nid_postfix(PosixSpawnAttributes** attributes, std::int32_t schedulingPolicy); +int APS5_VABI posix_spawnattr_setsigmask_nid_postfix(PosixSpawnAttributes** attributes, const GuestSignalSet* signalMask); +int APS5_VABI posix_spawn_file_actions_init_nid_postfix(PosixSpawnFileActions** actions); +int APS5_VABI posix_spawn_file_actions_destroy_nid_postfix(PosixSpawnFileActions** actions); +int APS5_VABI posix_spawn_file_actions_addopen_nid_postfix(PosixSpawnFileActions** actions, int descriptor, const char* path, int openFlags, std::uint16_t mode); +int APS5_VABI posix_spawn_file_actions_adddup2_nid_postfix(PosixSpawnFileActions** actions, int descriptor, int newDescriptor); +int APS5_VABI posix_spawn_file_actions_addclose_nid_postfix(PosixSpawnFileActions** actions, int descriptor); +int APS5_VABI posix_spawn_nid_postfix(std::int32_t* processId, const char* path, PosixSpawnFileActions* const* actions, PosixSpawnAttributes* const* attributes, char* const* arguments, char* const* environment); +int APS5_VABI posix_spawnp_nid_postfix(std::int32_t* processId, const char* file, PosixSpawnFileActions* const* actions, PosixSpawnAttributes* const* attributes, char* const* arguments, char* const* environment); +void APS5_VABI makecontext_nid_postfix(UserUcontext* context, void (APS5_VABI* start)(), int count, ...); + +[[noreturn]] void APS5_VABI verrc_nid_postfix(int status, int code, const char* format, VaList* args); +[[noreturn]] void APS5_VABI verr_nid_postfix(int status, const char* format, VaList* args); +[[noreturn]] void APS5_VABI verrx_nid_postfix(int status, const char* format, VaList* args); +[[noreturn]] void APS5_VABI errc_nid_postfix(int status, int code, const char* format, ...); +[[noreturn]] void APS5_VABI err_nid_postfix(int status, const char* format, ...); +[[noreturn]] void APS5_VABI _err_nid_postfix(int status, const char* format, ...); +[[noreturn]] void APS5_VABI errx_nid_postfix(int status, const char* format, ...); + +int APS5_VABI _ZNSt10filesystem5_StatEPKcPNS_5permsEPi_nid_postfix(const char* path, int* perms, int* error); +int APS5_VABI _ZNSt10filesystem6_LstatEPKcPNS_5permsEPi_nid_postfix(const char* path, int* perms, int* error); +std::uint64_t APS5_VABI _ZNSt10filesystem10_File_sizeEPKcPi_nid_postfix(const char* path, int* error); +int APS5_VABI _ZNSt10filesystem16_Last_write_timeEPKcPlS2__nid_postfix(const char* path, std::int64_t* seconds, std::int64_t* nanoseconds); +int APS5_VABI _ZNSt10filesystem9_Make_dirEPKcS1__nid_postfix(const char* path, const char* existing); +bool APS5_VABI _ZNSt10filesystem11_Remove_dirEPKc_nid_postfix(const char* path); +int APS5_VABI _ZNSt10filesystem7_UnlinkEPKc_nid_postfix(const char* path); +int APS5_VABI _ZNSt10filesystem6_ChmodEPKcNS_5permsE_nid_postfix(const char* path, int perms); +bool APS5_VABI _ZNSt10filesystem12_Current_getERA260_c_nid_postfix(char* target); +char* APS5_VABI _ZNSt10filesystem9_Read_dirERA260_cPvRNS_9file_typeE_nid_postfix(char* target, void* handle, int* type); +void* APS5_VABI _ZNSt10filesystem9_Open_dirERA260_cPKcRiRNS_9file_typeE_nid_postfix(char* target, const char* path, int* error, int* type); +int APS5_VABI _ZNSt10filesystem10_Close_dirEPv_nid_postfix(void* handle); +void APS5_VABI _ZNSt10filesystem18_Xfilesystem_errorEPKcSt10error_code_nid_postfix(); +void APS5_VABI _ZNSt10filesystem18_Xfilesystem_errorEPKcRKNS_4pathESt10error_code_nid_postfix(); +int APS5_VABI __std_init_once_begin_initialize_clr_nid_postfix(void** once, unsigned long flags, int* pending, void** context); +int APS5_VABI __std_init_once_complete_clr_nid_postfix(void** once, unsigned long flags, void* context); +[[noreturn]] void APS5_VABI __std_init_once_link_alternate_names_and_abort_nid_postfix(); + +} diff --git a/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp b/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp new file mode 100644 index 0000000000..7da0381b01 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceLibcInternal/include/Api.hpp" diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt new file mode 100644 index 0000000000..7a8ad2999c --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -0,0 +1,106 @@ +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) +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) + +if(BUILD_TESTING) + add_test_executable(guest_libressl_x509_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Errors.cpp) + target_include_directories(guest_libressl_x509_errors_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_errors_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_x509_errors_tests) + add_test(NAME guest_libressl_x509_errors COMMAND guest_libressl_x509_errors_tests) + set_tests_properties(guest_libressl_x509_errors PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_errors PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_digest_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslDigest.cpp) + target_include_directories(guest_libressl_digest_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_digest_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_digest_tests) + add_test(NAME guest_libressl_digest COMMAND guest_libressl_digest_tests) + set_tests_properties(guest_libressl_digest PROPERTIES TIMEOUT 30) + add_test_executable(guest_libressl_asn1_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Asn1.cpp) + target_include_directories(guest_libressl_asn1_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_asn1_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_asn1_tests) + 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) + if(WIN32) + set_tests_properties(guest_libressl_digest guest_libressl_asn1 guest_libressl_asn1_allocator PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_des_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslDes.cpp) + target_include_directories(guest_libressl_des_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_des_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_des_tests) + add_test(NAME guest_libressl_des COMMAND guest_libressl_des_tests) + set_tests_properties(guest_libressl_des PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libressl_des PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_runtime_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslRuntime.cpp) + target_include_directories(guest_libressl_runtime_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_runtime_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_runtime_tests) + add_test(NAME guest_libressl_runtime COMMAND guest_libressl_runtime_tests) + set_tests_properties(guest_libressl_runtime PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_runtime PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_aes_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslAes.cpp) + target_include_directories(guest_libressl_aes_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_aes_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_aes_tests) + add_test(NAME guest_libressl_aes COMMAND guest_libressl_aes_tests) + set_tests_properties(guest_libressl_aes PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_aes PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_error_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslError.cpp) + target_include_directories(guest_libressl_error_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_error_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_error_tests) + add_test(NAME guest_libressl_error COMMAND guest_libressl_error_tests) + set_tests_properties(guest_libressl_error PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_error PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_ocsp_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslOcsp.cpp) + target_include_directories(guest_libressl_ocsp_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ocsp_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_ocsp_tests) + add_test(NAME guest_libressl_ocsp COMMAND guest_libressl_ocsp_tests) + set_tests_properties(guest_libressl_ocsp PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ocsp PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_libressl_cipher_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslCipher.cpp) + target_include_directories(guest_libressl_cipher_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_cipher_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_cipher_tests) + add_test(NAME guest_libressl_cipher COMMAND guest_libressl_cipher_tests) + set_tests_properties(guest_libressl_cipher PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_cipher 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 93b05e0fec..93387ef0ba 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -1,81 +1,29 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include #include #include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "mbedtls/des.h" + +namespace { + +constexpr std::size_t DesScheduleBytes = 128; + +} extern "C" { APS5_DUMMY_FUN -int APS5_VABI AES_cbc_encrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI AES_cfb128_encrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI AES_ctr128_encrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI AES_decrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI AES_ecb_encrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI AES_encrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI AES_set_decrypt_key_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI AES_set_encrypt_key_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ASN1_INTEGER_get_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ASN1_STRING_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ASN1_STRING_length_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI ASN1_STRING_print_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI ASN1_STRING_to_UTF8_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ASN1_STRING_type_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI ASN1_TIME_print_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -271,16 +219,6 @@ int APS5_VABI CRYPTO_cleanup_all_ex_data_nid_postfix(void) { return 0; } -int APS5_VABI CRYPTO_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI CRYPTO_malloc_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI CRYPTO_num_locks_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -296,24 +234,28 @@ int APS5_VABI CRYPTO_set_locking_callback_nid_postfix(void) { return 0; } -int APS5_VABI CRYPTO_set_mem_functions_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); +void APS5_VABI DES_ecb_encrypt_nid_postfix(const unsigned char* input, unsigned char* output, const unsigned char* schedule, int enc) { + if (input == nullptr || output == nullptr || schedule == nullptr) APS5_INVALID_ARG_EX; + mbedtls_des_context context; + mbedtls_des_init(&context); + unsigned char block[MBEDTLS_DES_KEY_SIZE]; + std::memcpy(block, input, sizeof(block)); + int status = enc ? mbedtls_des_setkey_enc(&context, schedule) : mbedtls_des_setkey_dec(&context, schedule); + if (status == 0) status = mbedtls_des_crypt_ecb(&context, block, output); + mbedtls_des_free(&context); + if (status != 0) throw std::runtime_error("DES_ecb_encrypt: mbedtls failed"); +} + +int APS5_VABI DES_set_key_nid_postfix(const unsigned char* key, unsigned char* schedule) { + if (key == nullptr || schedule == nullptr) APS5_INVALID_ARG_EX; + std::memset(schedule, 0, DesScheduleBytes); + std::memcpy(schedule, key, MBEDTLS_DES_KEY_SIZE); return 0; } -int APS5_VABI DES_ecb_encrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI DES_set_key_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI DES_set_odd_parity_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI DES_set_odd_parity_nid_postfix(unsigned char* key) { + if (key == nullptr) APS5_INVALID_ARG_EX; + mbedtls_des_key_set_parity(key); } int APS5_VABI DTLSv1_client_method_nid_postfix(void) { @@ -506,11 +448,6 @@ int APS5_VABI ENGINE_set_default_nid_postfix(void) { return 0; } -int APS5_VABI ERR_clear_error_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI ERR_error_string_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -526,21 +463,6 @@ int APS5_VABI ERR_free_strings_nid_postfix(void) { return 0; } -int APS5_VABI ERR_get_error_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ERR_peek_error_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ERR_remove_thread_state_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_CIPHER_CTX_cleanup_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -846,36 +768,6 @@ int APS5_VABI HMAC_Update_nid_postfix(void) { return 0; } -int APS5_VABI MD4_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD4_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD4_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD5_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD5_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD5_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OBJ_cleanup_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -921,21 +813,11 @@ int APS5_VABI OCSP_basic_verify_nid_postfix(void) { return 0; } -int APS5_VABI OCSP_cert_status_str_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OCSP_check_validity_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI OCSP_crl_reason_str_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OCSP_resp_count_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -956,11 +838,6 @@ int APS5_VABI OCSP_response_status_nid_postfix(void) { return 0; } -int APS5_VABI OCSP_response_status_str_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OCSP_single_get0_status_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1026,31 +903,6 @@ int APS5_VABI PKCS12_parse_nid_postfix(void) { return 0; } -int APS5_VABI RAND_add_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI RAND_bytes_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI RAND_file_name_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI RAND_load_file_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI RAND_status_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_flags_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1116,101 +968,6 @@ int APS5_VABI RSA_verify_PKCS1_PSS_nid_postfix(void) { return 0; } -int APS5_VABI SHA1_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA1_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA1_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA224_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA224_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA224_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA256_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA256_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA256_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA256_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA384_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA384_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA384_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA512_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA512_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA512_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CIPHER_get_bits_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CIPHER_get_name_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CIPHER_get_version_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_add_client_CA_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1911,9 +1668,69 @@ int APS5_VABI X509_verify_cert_nid_postfix(void) { return 0; } -int APS5_VABI X509_verify_cert_error_string_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +const char* APS5_VABI X509_verify_cert_error_string_nid_postfix(std::int64_t error) { + static constexpr const char* messages[] { + "ok", + nullptr, + "unable to get issuer certificate", + "unable to get certificate CRL", + "unable to decrypt certificate's signature", + "unable to decrypt CRL's signature", + "unable to decode issuer public key", + "certificate signature failure", + "CRL signature failure", + "certificate is not yet valid", + "certificate has expired", + "CRL is not yet valid", + "CRL has expired", + "format error in certificate's notBefore field", + "format error in certificate's notAfter field", + "format error in CRL's lastUpdate field", + "format error in CRL's nextUpdate field", + "out of memory", + "self signed certificate", + "self signed certificate in certificate chain", + "unable to get local issuer certificate", + "unable to verify the first certificate", + "certificate chain too long", + "certificate revoked", + "invalid CA certificate", + "path length constraint exceeded", + "unsupported certificate purpose", + "certificate not trusted", + "certificate rejected", + "subject issuer mismatch", + "authority and subject key identifier mismatch", + "authority and issuer serial number mismatch", + "key usage does not include certificate signing", + "unable to get CRL issuer certificate", + "unhandled critical extension", + "key usage does not include CRL signing", + "unhandled critical CRL extension", + "invalid non-CA certificate (has CA markings)", + "proxy path length constraint exceeded", + "key usage does not include digital signature", + "proxy certificates not allowed, please set the appropriate flag", + "invalid or inconsistent certificate extension", + "invalid or inconsistent certificate policy extension", + "no explicit policy", + "Different CRL scope", + "Unsupported extension feature", + "RFC 3779 resource not subset of parent's resources", + "permitted subtree violation", + "excluded subtree violation", + "name constraints minimum and maximum not supported", + "application verification failure", + "unsupported name constraint type", + "unsupported or invalid name constraint syntax", + "unsupported or invalid name syntax", + "CRL path validation error", + }; + const auto code = static_cast(error); + if (code < sizeof(messages) / sizeof(messages[0]) && messages[code]) return messages[code]; + static char unknown[100]; + std::snprintf(unknown, sizeof(unknown), "error number %" PRId64, error); + return unknown; } int APS5_VABI d2i_OCSP_RESPONSE_nid_postfix(void) { diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp new file mode 100644 index 0000000000..c7edd129f6 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -0,0 +1,456 @@ +#pragma once + +#include "SceTypes.hpp" + +struct Asn1String { + std::int32_t length; + std::int32_t type; + unsigned char* data; + std::int64_t flags; +}; + +inline constexpr int MaxRounds = 14; + +struct AesKey { + std::uint32_t roundKeys[4 * (MaxRounds + 1)]; + std::int32_t rounds; +}; + +struct Md4Context { + std::uint32_t A, B, C, D; + std::uint32_t Nl, Nh; + std::uint32_t data[16]; + std::uint32_t num; +}; + +struct Md5Context { + std::uint32_t A, B, C, D; + std::uint32_t Nl, Nh; + std::uint32_t data[16]; + std::uint32_t num; +}; + +struct Sha1Context { + std::uint32_t h[5]; + std::uint32_t Nl, Nh; + std::uint32_t data[16]; + std::uint32_t num; +}; + +struct Sha256Context { + std::uint32_t h[8]; + std::uint32_t Nl, Nh; + std::uint32_t data[16]; + std::uint32_t num, mdLength; +}; + +struct Sha512Context { + std::uint64_t h[8]; + std::uint64_t Nl, Nh; + std::uint64_t data[16]; + std::uint32_t num, mdLength; +}; + +extern "C" { + +std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); +unsigned char* APS5_VABI ASN1_STRING_data_nid_postfix(Asn1String* value); +int APS5_VABI ASN1_STRING_length_nid_postfix(const Asn1String* value); +int APS5_VABI ASN1_STRING_print_nid_postfix(void); +int APS5_VABI ASN1_STRING_to_UTF8_nid_postfix(unsigned char** output, Asn1String* value); +int APS5_VABI ASN1_STRING_type_nid_postfix(Asn1String* value); +int APS5_VABI ASN1_TIME_print_nid_postfix(void); +int APS5_VABI BIO_ctrl_nid_postfix(void); +int APS5_VABI BIO_f_ssl_nid_postfix(void); +int APS5_VABI BIO_free_nid_postfix(void); +int APS5_VABI BIO_new_nid_postfix(void); +int APS5_VABI BIO_new_fp_nid_postfix(void); +int APS5_VABI BIO_new_mem_buf_nid_postfix(void); +int APS5_VABI BIO_new_socket_nid_postfix(void); +int APS5_VABI BIO_printf_nid_postfix(void); +int APS5_VABI BIO_puts_nid_postfix(void); +int APS5_VABI BIO_s_file_nid_postfix(void); +int APS5_VABI BIO_s_mem_nid_postfix(void); +int APS5_VABI BIO_write_nid_postfix(void); +int APS5_VABI BN_CTX_end_nid_postfix(void); +int APS5_VABI BN_CTX_free_nid_postfix(void); +int APS5_VABI BN_CTX_new_nid_postfix(void); +int APS5_VABI BN_CTX_start_nid_postfix(void); +int APS5_VABI BN_add_word_nid_postfix(void); +int APS5_VABI BN_bin2bn_nid_postfix(void); +int APS5_VABI BN_bn2bin_nid_postfix(void); +int APS5_VABI BN_free_nid_postfix(void); +int APS5_VABI BN_hex2bn_nid_postfix(void); +int APS5_VABI BN_mod_inverse_nid_postfix(void); +int APS5_VABI BN_mul_nid_postfix(void); +int APS5_VABI BN_new_nid_postfix(void); +int APS5_VABI BN_num_bits_nid_postfix(void); +int APS5_VABI BN_print_nid_postfix(void); +int APS5_VABI BN_set_word_nid_postfix(void); +int APS5_VABI BN_sub_word_nid_postfix(void); +int APS5_VABI CMAC_CTX_free_nid_postfix(void); +int APS5_VABI CMAC_CTX_new_nid_postfix(void); +int APS5_VABI CMAC_Final_nid_postfix(void); +int APS5_VABI CMAC_Init_nid_postfix(void); +int APS5_VABI CMAC_Update_nid_postfix(void); +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); +int APS5_VABI CRYPTO_THREADID_set_numeric_nid_postfix(void); +int APS5_VABI CRYPTO_cleanup_all_ex_data_nid_postfix(void); +int APS5_VABI CRYPTO_num_locks_nid_postfix(void); +int APS5_VABI CRYPTO_set_add_lock_callback_nid_postfix(void); +int APS5_VABI CRYPTO_set_locking_callback_nid_postfix(void); +void APS5_VABI DES_ecb_encrypt_nid_postfix(const unsigned char* input, unsigned char* output, const unsigned char* schedule, int enc); +int APS5_VABI DES_set_key_nid_postfix(const unsigned char* key, unsigned char* schedule); +void APS5_VABI DES_set_odd_parity_nid_postfix(unsigned char* key); +int APS5_VABI DTLSv1_client_method_nid_postfix(void); +int APS5_VABI DTLSv1_server_method_nid_postfix(void); +int APS5_VABI ECDH_compute_key_nid_postfix(void); +int APS5_VABI ECDSA_sign_nid_postfix(void); +int APS5_VABI ECDSA_size_nid_postfix(void); +int APS5_VABI ECDSA_verify_nid_postfix(void); +int APS5_VABI EC_GROUP_free_nid_postfix(void); +int APS5_VABI EC_GROUP_get_degree_nid_postfix(void); +int APS5_VABI EC_GROUP_new_by_curve_name_nid_postfix(void); +int APS5_VABI EC_KEY_check_key_nid_postfix(void); +int APS5_VABI EC_KEY_free_nid_postfix(void); +int APS5_VABI EC_KEY_generate_key_nid_postfix(void); +int APS5_VABI EC_KEY_get0_group_nid_postfix(void); +int APS5_VABI EC_KEY_get0_private_key_nid_postfix(void); +int APS5_VABI EC_KEY_get0_public_key_nid_postfix(void); +int APS5_VABI EC_KEY_new_nid_postfix(void); +int APS5_VABI EC_KEY_new_by_curve_name_nid_postfix(void); +int APS5_VABI EC_KEY_set_group_nid_postfix(void); +int APS5_VABI EC_KEY_set_private_key_nid_postfix(void); +int APS5_VABI EC_KEY_set_public_key_nid_postfix(void); +int APS5_VABI EC_POINT_free_nid_postfix(void); +int APS5_VABI EC_POINT_mul_nid_postfix(void); +int APS5_VABI EC_POINT_new_nid_postfix(void); +int APS5_VABI EC_POINT_oct2point_nid_postfix(void); +int APS5_VABI EC_POINT_point2oct_nid_postfix(void); +int APS5_VABI ENGINE_by_id_nid_postfix(void); +int APS5_VABI ENGINE_cleanup_nid_postfix(void); +int APS5_VABI ENGINE_ctrl_nid_postfix(void); +int APS5_VABI ENGINE_ctrl_cmd_nid_postfix(void); +int APS5_VABI ENGINE_finish_nid_postfix(void); +int APS5_VABI ENGINE_free_nid_postfix(void); +int APS5_VABI ENGINE_get_first_nid_postfix(void); +int APS5_VABI ENGINE_get_id_nid_postfix(void); +int APS5_VABI ENGINE_get_next_nid_postfix(void); +int APS5_VABI ENGINE_init_nid_postfix(void); +int APS5_VABI ENGINE_load_builtin_engines_nid_postfix(void); +int APS5_VABI ENGINE_load_private_key_nid_postfix(void); +int APS5_VABI ENGINE_set_default_nid_postfix(void); +void APS5_VABI ERR_clear_error_nid_postfix(void); +int APS5_VABI ERR_error_string_nid_postfix(void); +int APS5_VABI ERR_error_string_n_nid_postfix(void); +int APS5_VABI ERR_free_strings_nid_postfix(void); +std::uint64_t APS5_VABI ERR_get_error_nid_postfix(void); +std::uint64_t APS5_VABI ERR_peek_error_nid_postfix(void); +void APS5_VABI ERR_remove_thread_state_nid_postfix(const void* threadId); +int APS5_VABI EVP_CIPHER_CTX_cleanup_nid_postfix(void); +int APS5_VABI EVP_CIPHER_CTX_ctrl_nid_postfix(void); +int APS5_VABI EVP_CIPHER_CTX_free_nid_postfix(void); +int APS5_VABI EVP_CIPHER_CTX_init_nid_postfix(void); +int APS5_VABI EVP_CIPHER_CTX_new_nid_postfix(void); +int APS5_VABI EVP_CIPHER_CTX_set_padding_nid_postfix(void); +int APS5_VABI EVP_CIPHER_block_size_nid_postfix(void); +int APS5_VABI EVP_CipherFinal_ex_nid_postfix(void); +int APS5_VABI EVP_CipherInit_ex_nid_postfix(void); +int APS5_VABI EVP_CipherUpdate_nid_postfix(void); +int APS5_VABI EVP_DecryptFinal_ex_nid_postfix(void); +int APS5_VABI EVP_DecryptInit_ex_nid_postfix(void); +int APS5_VABI EVP_DecryptUpdate_nid_postfix(void); +int APS5_VABI EVP_Digest_nid_postfix(void); +int APS5_VABI EVP_DigestFinal_ex_nid_postfix(void); +int APS5_VABI EVP_DigestInit_ex_nid_postfix(void); +int APS5_VABI EVP_DigestUpdate_nid_postfix(void); +int APS5_VABI EVP_DigestVerifyFinal_nid_postfix(void); +int APS5_VABI EVP_DigestVerifyInit_nid_postfix(void); +int APS5_VABI EVP_EncryptFinal_ex_nid_postfix(void); +int APS5_VABI EVP_EncryptInit_ex_nid_postfix(void); +int APS5_VABI EVP_EncryptUpdate_nid_postfix(void); +int APS5_VABI EVP_MD_CTX_cleanup_nid_postfix(void); +int APS5_VABI EVP_MD_CTX_create_nid_postfix(void); +int APS5_VABI EVP_MD_CTX_destroy_nid_postfix(void); +int APS5_VABI EVP_MD_CTX_init_nid_postfix(void); +int APS5_VABI EVP_MD_size_nid_postfix(void); +int APS5_VABI EVP_PKEY_copy_parameters_nid_postfix(void); +int APS5_VABI EVP_PKEY_free_nid_postfix(void); +int APS5_VABI EVP_PKEY_get1_EC_KEY_nid_postfix(void); +int APS5_VABI EVP_PKEY_get1_RSA_nid_postfix(void); +int APS5_VABI EVP_PKEY_id_nid_postfix(void); +int APS5_VABI EVP_PKEY_new_nid_postfix(void); +int APS5_VABI EVP_PKEY_set1_RSA_nid_postfix(void); +int APS5_VABI EVP_PKEY_type_nid_postfix(void); +int APS5_VABI EVP_aes_128_cbc_nid_postfix(void); +int APS5_VABI EVP_aes_128_cfb8_nid_postfix(void); +int APS5_VABI EVP_aes_128_ctr_nid_postfix(void); +int APS5_VABI EVP_aes_128_gcm_nid_postfix(void); +int APS5_VABI EVP_aes_192_cbc_nid_postfix(void); +int APS5_VABI EVP_aes_192_cfb8_nid_postfix(void); +int APS5_VABI EVP_aes_192_ctr_nid_postfix(void); +int APS5_VABI EVP_aes_192_gcm_nid_postfix(void); +int APS5_VABI EVP_aes_256_cbc_nid_postfix(void); +int APS5_VABI EVP_aes_256_cfb8_nid_postfix(void); +int APS5_VABI EVP_aes_256_ctr_nid_postfix(void); +int APS5_VABI EVP_aes_256_gcm_nid_postfix(void); +int APS5_VABI EVP_cleanup_nid_postfix(void); +int APS5_VABI EVP_md5_nid_postfix(void); +int APS5_VABI EVP_sha256_nid_postfix(void); +int APS5_VABI EVP_sha384_nid_postfix(void); +int APS5_VABI EVP_sha512_nid_postfix(void); +int APS5_VABI GENERAL_NAMES_free_nid_postfix(void); +int APS5_VABI GENERAL_NAME_free_nid_postfix(void); +int APS5_VABI HMAC_nid_postfix(void); +int APS5_VABI HMAC_CTX_cleanup_nid_postfix(void); +int APS5_VABI HMAC_CTX_init_nid_postfix(void); +int APS5_VABI HMAC_Final_nid_postfix(void); +int APS5_VABI HMAC_Init_nid_postfix(void); +int APS5_VABI HMAC_Init_ex_nid_postfix(void); +int APS5_VABI HMAC_Update_nid_postfix(void); +int APS5_VABI OBJ_cleanup_nid_postfix(void); +int APS5_VABI OBJ_create_nid_postfix(void); +int APS5_VABI OBJ_nid2sn_nid_postfix(void); +int APS5_VABI OBJ_obj2nid_nid_postfix(void); +int APS5_VABI OBJ_sn2nid_nid_postfix(void); +int APS5_VABI OCSP_BASICRESP_free_nid_postfix(void); +int APS5_VABI OCSP_RESPONSE_free_nid_postfix(void); +int APS5_VABI OCSP_basic_add1_cert_nid_postfix(void); +int APS5_VABI OCSP_basic_verify_nid_postfix(void); +const char* APS5_VABI OCSP_cert_status_str_nid_postfix(std::int64_t status); +int APS5_VABI OCSP_check_validity_nid_postfix(void); +const char* APS5_VABI OCSP_crl_reason_str_nid_postfix(std::int64_t reason); +int APS5_VABI OCSP_resp_count_nid_postfix(void); +int APS5_VABI OCSP_resp_get0_nid_postfix(void); +int APS5_VABI OCSP_response_get1_basic_nid_postfix(void); +int APS5_VABI OCSP_response_status_nid_postfix(void); +const char* APS5_VABI OCSP_response_status_str_nid_postfix(std::int64_t status); +int APS5_VABI OCSP_single_get0_status_nid_postfix(void); +int APS5_VABI OPENSSL_add_all_algorithms_noconf_nid_postfix(void); +int APS5_VABI OPENSSL_load_builtin_modules_nid_postfix(void); +int APS5_VABI PEM_X509_INFO_read_bio_nid_postfix(void); +int APS5_VABI PEM_read_RSAPrivateKey_nid_postfix(void); +int APS5_VABI PEM_read_X509_nid_postfix(void); +int APS5_VABI PEM_read_bio_PrivateKey_nid_postfix(void); +int APS5_VABI PEM_read_bio_X509_nid_postfix(void); +int APS5_VABI PEM_read_bio_X509_AUX_nid_postfix(void); +int APS5_VABI PEM_write_bio_X509_nid_postfix(void); +int APS5_VABI PKCS12_PBE_add_nid_postfix(void); +int APS5_VABI PKCS12_free_nid_postfix(void); +int APS5_VABI PKCS12_parse_nid_postfix(void); +int APS5_VABI RSA_flags_nid_postfix(void); +int APS5_VABI RSA_free_nid_postfix(void); +int APS5_VABI RSA_generate_key_ex_nid_postfix(void); +int APS5_VABI RSA_new_nid_postfix(void); +int APS5_VABI RSA_padding_add_PKCS1_PSS_nid_postfix(void); +int APS5_VABI RSA_private_decrypt_nid_postfix(void); +int APS5_VABI RSA_private_encrypt_nid_postfix(void); +int APS5_VABI RSA_public_decrypt_nid_postfix(void); +int APS5_VABI RSA_public_encrypt_nid_postfix(void); +int APS5_VABI RSA_sign_nid_postfix(void); +int APS5_VABI RSA_size_nid_postfix(void); +int APS5_VABI RSA_verify_nid_postfix(void); +int APS5_VABI RSA_verify_PKCS1_PSS_nid_postfix(void); +int APS5_VABI SSL_CIPHER_get_bits_nid_postfix(const void* cipher, std::int32_t* algorithmBits); +const char* APS5_VABI SSL_CIPHER_get_name_nid_postfix(const void* cipher); +char* APS5_VABI SSL_CIPHER_get_version_nid_postfix(const void* cipher); +int APS5_VABI SSL_CTX_add_client_CA_nid_postfix(void); +int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void); +int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void); +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_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); +int APS5_VABI SSL_CTX_new_nid_postfix(void); +int APS5_VABI SSL_CTX_sess_set_new_cb_nid_postfix(void); +int APS5_VABI SSL_CTX_set1_groups_list_nid_postfix(void); +int APS5_VABI SSL_CTX_set_alpn_protos_nid_postfix(void); +int APS5_VABI SSL_CTX_set_alpn_select_cb_nid_postfix(void); +int APS5_VABI SSL_CTX_set_cert_store_nid_postfix(void); +int APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(void); +int APS5_VABI SSL_CTX_set_cipher_list_nid_postfix(void); +int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void); +int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void); +int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void); +int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void); +int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void); +int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void); +int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void); +int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void); +int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void); +int APS5_VABI SSL_CTX_set_verify_nid_postfix(void); +int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void); +int APS5_VABI SSL_CTX_use_PrivateKey_ASN1_nid_postfix(void); +int APS5_VABI SSL_CTX_use_PrivateKey_file_nid_postfix(void); +int APS5_VABI SSL_CTX_use_certificate_nid_postfix(void); +int APS5_VABI SSL_CTX_use_certificate_ASN1_nid_postfix(void); +int APS5_VABI SSL_CTX_use_certificate_chain_file_nid_postfix(void); +int APS5_VABI SSL_CTX_use_certificate_file_nid_postfix(void); +int APS5_VABI SSL_SESSION_free_nid_postfix(void); +int APS5_VABI SSL_accept_nid_postfix(void); +int APS5_VABI SSL_alert_desc_string_long_nid_postfix(void); +int APS5_VABI SSL_check_private_key_nid_postfix(void); +int APS5_VABI SSL_connect_nid_postfix(void); +int APS5_VABI SSL_ctrl_nid_postfix(void); +int APS5_VABI SSL_free_nid_postfix(void); +int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void); +int APS5_VABI SSL_get0_param_nid_postfix(void); +int APS5_VABI SSL_get1_session_nid_postfix(void); +int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void); +int APS5_VABI SSL_get_certificate_nid_postfix(void); +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_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); +int APS5_VABI SSL_get_privatekey_nid_postfix(void); +int APS5_VABI SSL_get_rbio_nid_postfix(void); +int APS5_VABI SSL_get_servername_nid_postfix(void); +int APS5_VABI SSL_get_session_nid_postfix(void); +int APS5_VABI SSL_get_shutdown_nid_postfix(void); +int APS5_VABI SSL_get_verify_result_nid_postfix(void); +int APS5_VABI SSL_get_wbio_nid_postfix(void); +int APS5_VABI SSL_library_init_nid_postfix(void); +int APS5_VABI SSL_load_error_strings_nid_postfix(void); +int APS5_VABI SSL_new_nid_postfix(void); +int APS5_VABI SSL_peek_nid_postfix(void); +int APS5_VABI SSL_pending_nid_postfix(void); +int APS5_VABI SSL_read_nid_postfix(void); +int APS5_VABI SSL_select_next_proto_nid_postfix(void); +int APS5_VABI SSL_set_SSL_CTX_nid_postfix(void); +int APS5_VABI SSL_set_bio_nid_postfix(void); +int APS5_VABI SSL_set_connect_state_nid_postfix(void); +int APS5_VABI SSL_set_ex_data_nid_postfix(void); +int APS5_VABI SSL_set_fd_nid_postfix(void); +int APS5_VABI SSL_set_session_nid_postfix(void); +int APS5_VABI SSL_set_verify_nid_postfix(void); +int APS5_VABI SSL_shutdown_nid_postfix(void); +int APS5_VABI SSL_use_PrivateKey_ASN1_nid_postfix(void); +int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void); +int APS5_VABI SSL_version_nid_postfix(void); +int APS5_VABI SSL_want_nid_postfix(void); +int APS5_VABI SSL_write_nid_postfix(void); +int APS5_VABI SSLeay_nid_postfix(void); +int APS5_VABI SSLv23_client_method_nid_postfix(void); +int APS5_VABI SSLv23_server_method_nid_postfix(void); +int APS5_VABI TLS_client_method_nid_postfix(void); +int APS5_VABI UI_OpenSSL_nid_postfix(void); +int APS5_VABI UI_create_method_nid_postfix(void); +int APS5_VABI UI_destroy_method_nid_postfix(void); +int APS5_VABI UI_get0_user_data_nid_postfix(void); +int APS5_VABI UI_get_input_flags_nid_postfix(void); +int APS5_VABI UI_get_string_type_nid_postfix(void); +int APS5_VABI UI_method_get_closer_nid_postfix(void); +int APS5_VABI UI_method_get_opener_nid_postfix(void); +int APS5_VABI UI_method_get_reader_nid_postfix(void); +int APS5_VABI UI_method_get_writer_nid_postfix(void); +int APS5_VABI UI_method_set_closer_nid_postfix(void); +int APS5_VABI UI_method_set_opener_nid_postfix(void); +int APS5_VABI UI_method_set_reader_nid_postfix(void); +int APS5_VABI UI_method_set_writer_nid_postfix(void); +int APS5_VABI UI_set_result_nid_postfix(void); +int APS5_VABI X509V3_EXT_print_nid_postfix(void); +int APS5_VABI X509_EXTENSION_get_critical_nid_postfix(void); +int APS5_VABI X509_EXTENSION_get_data_nid_postfix(void); +int APS5_VABI X509_EXTENSION_get_object_nid_postfix(void); +int APS5_VABI X509_INFO_free_nid_postfix(void); +int APS5_VABI X509_LOOKUP_file_nid_postfix(void); +int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void); +int APS5_VABI X509_NAME_get_entry_nid_postfix(void); +int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void); +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); +int APS5_VABI X509_STORE_CTX_get_ex_data_nid_postfix(void); +int APS5_VABI X509_STORE_CTX_set_error_nid_postfix(void); +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); +int APS5_VABI X509_STORE_free_nid_postfix(void); +int APS5_VABI X509_STORE_new_nid_postfix(void); +int APS5_VABI X509_STORE_set_flags_nid_postfix(void); +int APS5_VABI X509_VERIFY_PARAM_set1_host_nid_postfix(void); +int APS5_VABI X509_VERIFY_PARAM_set1_ip_asc_nid_postfix(void); +int APS5_VABI X509_VERIFY_PARAM_set_hostflags_nid_postfix(void); +int APS5_VABI X509_check_issued_nid_postfix(void); +int APS5_VABI X509_free_nid_postfix(void); +int APS5_VABI X509_get_ext_d2i_nid_postfix(void); +int APS5_VABI X509_get_issuer_name_nid_postfix(void); +int APS5_VABI X509_get_pubkey_nid_postfix(void); +int APS5_VABI X509_get_serialNumber_nid_postfix(void); +int APS5_VABI X509_get_subject_name_nid_postfix(void); +int APS5_VABI X509_load_crl_file_nid_postfix(void); +int APS5_VABI X509_print_ex_nid_postfix(void); +int APS5_VABI X509_verify_cert_nid_postfix(void); +const char* APS5_VABI X509_verify_cert_error_string_nid_postfix(std::int64_t error); +int APS5_VABI d2i_OCSP_RESPONSE_nid_postfix(void); +int APS5_VABI d2i_PKCS12_bio_nid_postfix(void); +int APS5_VABI d2i_PKCS12_fp_nid_postfix(void); +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 sceLibreSslGetMemoryPoolStats(void); +int APS5_VABI sceLibreSslInit(void); +int APS5_VABI sceLibreSslInitEx(void); +int APS5_VABI sceLibreSslInitEx2(void); +int APS5_VABI sceLibreSslTerm(void); +int APS5_VABI sk_num_nid_postfix(void); +int APS5_VABI sk_pop_nid_postfix(void); +int APS5_VABI sk_pop_free_nid_postfix(void); +int APS5_VABI sk_value_nid_postfix(void); +int APS5_VABI AES_set_encrypt_key_nid_postfix(const unsigned char* userKey, int bits, AesKey* key); +int APS5_VABI AES_set_decrypt_key_nid_postfix(const unsigned char* userKey, int bits, AesKey* key); +void APS5_VABI AES_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, const AesKey* key); +void APS5_VABI AES_decrypt_nid_postfix(const unsigned char* in, unsigned char* out, const AesKey* key); +void APS5_VABI AES_ecb_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, const AesKey* key, int encrypt); +void APS5_VABI AES_cbc_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, std::size_t length, const AesKey* key, unsigned char* ivec, int encrypt); +void APS5_VABI AES_cfb128_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, std::size_t length, const AesKey* key, unsigned char* ivec, int* num, int encrypt); +void APS5_VABI AES_ctr128_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, std::size_t length, const AesKey* key, unsigned char* ivec, unsigned char* ecountBuffer, unsigned* num); +int APS5_VABI MD4_Init_nid_postfix(Md4Context* context); +int APS5_VABI MD4_Update_nid_postfix(Md4Context* context, const void* data, std::size_t length); +int APS5_VABI MD4_Final_nid_postfix(unsigned char* digest, Md4Context* context); +int APS5_VABI MD5_Init_nid_postfix(Md5Context* context); +int APS5_VABI MD5_Update_nid_postfix(Md5Context* context, const void* data, std::size_t length); +int APS5_VABI MD5_Final_nid_postfix(unsigned char* digest, Md5Context* context); +int APS5_VABI SHA1_Init_nid_postfix(Sha1Context* context); +int APS5_VABI SHA1_Update_nid_postfix(Sha1Context* context, const void* data, std::size_t length); +int APS5_VABI SHA1_Final_nid_postfix(unsigned char* digest, Sha1Context* context); +int APS5_VABI SHA224_Init_nid_postfix(Sha256Context* context); +int APS5_VABI SHA256_Init_nid_postfix(Sha256Context* context); +int APS5_VABI SHA256_Update_nid_postfix(Sha256Context* context, const void* data, std::size_t length); +int APS5_VABI SHA224_Update_nid_postfix(Sha256Context* context, const void* data, std::size_t length); +int APS5_VABI SHA256_Final_nid_postfix(unsigned char* digest, Sha256Context* context); +int APS5_VABI SHA224_Final_nid_postfix(unsigned char* digest, Sha256Context* context); +unsigned char* APS5_VABI SHA256_nid_postfix(const unsigned char* data, std::size_t length, unsigned char* digest); +int APS5_VABI SHA384_Init_nid_postfix(Sha512Context* context); +int APS5_VABI SHA512_Init_nid_postfix(Sha512Context* context); +int APS5_VABI SHA512_Update_nid_postfix(Sha512Context* context, const void* data, std::size_t length); +int APS5_VABI SHA384_Update_nid_postfix(Sha512Context* context, const void* data, std::size_t length); +int APS5_VABI SHA512_Final_nid_postfix(unsigned char* digest, Sha512Context* context); +int APS5_VABI SHA384_Final_nid_postfix(unsigned char* digest, Sha512Context* context); +void APS5_VABI RAND_add_nid_postfix(const void*, int, double); +int APS5_VABI RAND_status_nid_postfix(void); +int APS5_VABI RAND_bytes_nid_postfix(unsigned char* buffer, int count); +int APS5_VABI RAND_load_file_nid_postfix(const char*, std::int64_t bytes); +const char* APS5_VABI RAND_file_name_nid_postfix(char* buffer, std::size_t size); +void* APS5_VABI CRYPTO_malloc_nid_postfix(int count, const char*, int); +void APS5_VABI CRYPTO_free_nid_postfix(void* pointer); +int APS5_VABI CRYPTO_set_mem_functions_nid_postfix(void*, void*, void*); + +} diff --git a/core/libs/prx/libSceLibreSsl/include/Error.hpp b/core/libs/prx/libSceLibreSsl/include/Error.hpp new file mode 100644 index 0000000000..0c7a23712f --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/include/Error.hpp @@ -0,0 +1,10 @@ +#ifndef CORE_LIBS_PRX_LIBSCELIBRESSL_INCLUDE_ERROR_HPP +#define CORE_LIBS_PRX_LIBSCELIBRESSL_INCLUDE_ERROR_HPP + +extern "C" { + +void LibreSslPutError_nid_no_patch(int library, int function, int reason); + +} + +#endif diff --git a/core/libs/prx/libSceLibreSsl/libSceLibreSsl.hpp b/core/libs/prx/libSceLibreSsl/libSceLibreSsl.hpp new file mode 100644 index 0000000000..7b25337c40 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/libSceLibreSsl.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceLibreSsl/include/Api.hpp" diff --git a/core/libs/prx/libSceLibreSsl/src/Aes.cpp b/core/libs/prx/libSceLibreSsl/src/Aes.cpp new file mode 100644 index 0000000000..ddbdbe61db --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Aes.cpp @@ -0,0 +1,288 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +namespace { + +constexpr std::size_t BlockSize = 16; + +static_assert(sizeof(AesKey) == 244); + +constexpr std::uint8_t Multiply(std::uint8_t a, std::uint8_t b) { + std::uint8_t product = 0; + while (b) { + if (b & 1) product ^= a; + a = static_cast((a << 1) ^ ((a & 0x80) ? 0x1b : 0)); + b >>= 1; + } + return product; +} + +constexpr std::array MakeSubstitution() { + std::array box {}; + for (int value = 0; value < 256; ++value) { + const auto element = static_cast(value); + std::uint8_t inverse = element; + for (int step = 0; step < 6; ++step) inverse = Multiply(Multiply(inverse, inverse), element); + inverse = Multiply(inverse, inverse); + std::uint8_t result = 0x63; + for (int shift = 0; shift < 5; ++shift) + result ^= static_cast((inverse << shift) | (inverse >> ((8 - shift) & 7))); + box[value] = result; + } + return box; +} + +constexpr std::array MakeInverse(const std::array& box) { + std::array inverse {}; + for (int value = 0; value < 256; ++value) inverse[box[value]] = static_cast(value); + return inverse; +} + +constexpr auto Substitution = MakeSubstitution(); +constexpr auto InverseSubstitution = MakeInverse(Substitution); +static_assert(Substitution[0x00] == 0x63 && Substitution[0x53] == 0xed && Substitution[0xff] == 0x16); + +std::uint32_t LoadBig32(const unsigned char* p) { + return std::uint32_t(p[0]) << 24 | std::uint32_t(p[1]) << 16 | std::uint32_t(p[2]) << 8 | std::uint32_t(p[3]); +} + +void StoreBig32(unsigned char* p, std::uint32_t value) { + for (int i = 0; i < 4; ++i) p[i] = static_cast(value >> (24 - 8 * i)); +} + +std::uint32_t SubstituteWord(std::uint32_t word) { + return std::uint32_t(Substitution[word >> 24]) << 24 | std::uint32_t(Substitution[(word >> 16) & 0xff]) << 16 | + std::uint32_t(Substitution[(word >> 8) & 0xff]) << 8 | Substitution[word & 0xff]; +} + +std::uint32_t InverseMixWord(std::uint32_t word) { + const std::uint8_t a[4] {std::uint8_t(word >> 24), std::uint8_t(word >> 16), std::uint8_t(word >> 8), std::uint8_t(word)}; + std::uint32_t result = 0; + for (int row = 0; row < 4; ++row) { + const std::uint8_t value = Multiply(a[row], 14) ^ Multiply(a[(row + 1) % 4], 11) ^ Multiply(a[(row + 2) % 4], 13) ^ Multiply(a[(row + 3) % 4], 9); + result |= std::uint32_t(value) << (24 - 8 * row); + } + return result; +} + +int RoundsForBits(int bits) { + if (bits == 128) return 10; + if (bits == 192) return 12; + if (bits == 256) return 14; + return 0; +} + +void ExpandKey(const unsigned char* userKey, int bits, AesKey& key) { + const int words = bits / 32; + const int total = 4 * (key.rounds + 1); + auto* w = key.roundKeys; + for (int i = 0; i < words; ++i) w[i] = LoadBig32(userKey + 4 * i); + std::uint8_t roundConstant = 1; + for (int i = words; i < total; ++i) { + std::uint32_t temp = w[i - 1]; + if (i % words == 0) { + temp = SubstituteWord(temp << 8 | temp >> 24) ^ (std::uint32_t(roundConstant) << 24); + roundConstant = Multiply(roundConstant, 2); + } else if (words > 6 && i % words == 4) { + temp = SubstituteWord(temp); + } + w[i] = w[i - words] ^ temp; + } +} + +const AesKey& RequireKey(const AesKey* key, const char* function) { + if (!key) throw std::invalid_argument(std::string(function) + ": null key"); + if (key->rounds != 10 && key->rounds != 12 && key->rounds != 14) + throw std::invalid_argument(std::string(function) + ": key schedule with " + std::to_string(key->rounds) + " rounds"); + return *key; +} + +void RequireBlocks(const void* in, const void* out, std::size_t length, const char* function) { + if (length && (!in || !out)) throw std::invalid_argument(std::string(function) + ": null input or output"); +} + +void LoadState(std::uint8_t (&state)[16], const unsigned char* in, const std::uint32_t* roundKey) { + for (int column = 0; column < 4; ++column) + for (int row = 0; row < 4; ++row) + state[4 * column + row] = in[4 * column + row] ^ std::uint8_t(roundKey[column] >> (24 - 8 * row)); +} + +void AddRoundKey(std::uint8_t (&state)[16], const std::uint32_t* roundKey) { + for (int column = 0; column < 4; ++column) + for (int row = 0; row < 4; ++row) state[4 * column + row] ^= std::uint8_t(roundKey[column] >> (24 - 8 * row)); +} + +void EncryptBlock(const unsigned char* in, unsigned char* out, const AesKey& key) { + std::uint8_t state[16]; + LoadState(state, in, key.roundKeys); + for (int round = 1; round <= key.rounds; ++round) { + std::uint8_t shifted[16]; + for (int column = 0; column < 4; ++column) + for (int row = 0; row < 4; ++row) shifted[4 * column + row] = Substitution[state[4 * ((column + row) % 4) + row]]; + if (round != key.rounds) { + for (int column = 0; column < 4; ++column) { + const std::uint8_t* a = shifted + 4 * column; + const std::uint8_t mixed[4] { + std::uint8_t(Multiply(a[0], 2) ^ Multiply(a[1], 3) ^ a[2] ^ a[3]), + std::uint8_t(a[0] ^ Multiply(a[1], 2) ^ Multiply(a[2], 3) ^ a[3]), + std::uint8_t(a[0] ^ a[1] ^ Multiply(a[2], 2) ^ Multiply(a[3], 3)), + std::uint8_t(Multiply(a[0], 3) ^ a[1] ^ a[2] ^ Multiply(a[3], 2)), + }; + std::memcpy(state + 4 * column, mixed, 4); + } + } else { + std::memcpy(state, shifted, sizeof(state)); + } + AddRoundKey(state, key.roundKeys + 4 * round); + } + std::memcpy(out, state, sizeof(state)); +} + +void DecryptBlock(const unsigned char* in, unsigned char* out, const AesKey& key) { + std::uint8_t state[16]; + LoadState(state, in, key.roundKeys); + for (int round = 1; round <= key.rounds; ++round) { + std::uint8_t shifted[16]; + for (int column = 0; column < 4; ++column) + for (int row = 0; row < 4; ++row) shifted[4 * column + row] = InverseSubstitution[state[4 * ((column + 4 - row) % 4) + row]]; + if (round != key.rounds) { + for (int column = 0; column < 4; ++column) { + const std::uint8_t* a = shifted + 4 * column; + const std::uint8_t mixed[4] { + std::uint8_t(Multiply(a[0], 14) ^ Multiply(a[1], 11) ^ Multiply(a[2], 13) ^ Multiply(a[3], 9)), + std::uint8_t(Multiply(a[0], 9) ^ Multiply(a[1], 14) ^ Multiply(a[2], 11) ^ Multiply(a[3], 13)), + std::uint8_t(Multiply(a[0], 13) ^ Multiply(a[1], 9) ^ Multiply(a[2], 14) ^ Multiply(a[3], 11)), + std::uint8_t(Multiply(a[0], 11) ^ Multiply(a[1], 13) ^ Multiply(a[2], 9) ^ Multiply(a[3], 14)), + }; + std::memcpy(state + 4 * column, mixed, 4); + } + } else { + std::memcpy(state, shifted, sizeof(state)); + } + AddRoundKey(state, key.roundKeys + 4 * round); + } + std::memcpy(out, state, sizeof(state)); +} + +unsigned RequirePosition(int position, const char* function) { + if (position < 0 || position >= static_cast(BlockSize)) + throw std::invalid_argument(std::string(function) + ": block position " + std::to_string(position) + " is outside 0-15"); + return static_cast(position); +} + +void IncrementCounter(unsigned char* counter) { + for (int i = 15; i >= 0; --i) + if (++counter[i]) return; +} +} + +extern "C" { + +int APS5_VABI AES_set_encrypt_key_nid_postfix(const unsigned char* userKey, int bits, AesKey* key) { + if (!userKey || !key) return -1; + const int rounds = RoundsForBits(bits); + if (!rounds) return -2; + key->rounds = rounds; + ExpandKey(userKey, bits, *key); + return 0; +} + +int APS5_VABI AES_set_decrypt_key_nid_postfix(const unsigned char* userKey, int bits, AesKey* key) { + const int result = AES_set_encrypt_key_nid_postfix(userKey, bits, key); + if (result) return result; + auto* w = key->roundKeys; + for (int i = 0, j = 4 * key->rounds; i < j; i += 4, j -= 4) + for (int k = 0; k < 4; ++k) std::swap(w[i + k], w[j + k]); + for (int i = 4; i < 4 * key->rounds; ++i) w[i] = InverseMixWord(w[i]); + return 0; +} + +void APS5_VABI AES_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, const AesKey* key) { + const auto& k = RequireKey(key, __func__); + RequireBlocks(in, out, BlockSize, __func__); + EncryptBlock(in, out, k); +} + +void APS5_VABI AES_decrypt_nid_postfix(const unsigned char* in, unsigned char* out, const AesKey* key) { + const auto& k = RequireKey(key, __func__); + RequireBlocks(in, out, BlockSize, __func__); + DecryptBlock(in, out, k); +} + +void APS5_VABI AES_ecb_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, const AesKey* key, int encrypt) { + if (encrypt == 1) AES_encrypt_nid_postfix(in, out, key); + else AES_decrypt_nid_postfix(in, out, key); +} + +void APS5_VABI AES_cbc_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, std::size_t length, const AesKey* key, unsigned char* ivec, int encrypt) { + const auto& k = RequireKey(key, __func__); + RequireBlocks(in, out, length, __func__); + if (!ivec) throw std::invalid_argument(std::string(__func__) + ": null iv"); + std::uint8_t block[BlockSize]; + if (encrypt) { + while (length) { + const std::size_t count = length < BlockSize ? length : BlockSize; + for (std::size_t n = 0; n < BlockSize; ++n) block[n] = n < count ? in[n] ^ ivec[n] : ivec[n]; + EncryptBlock(block, out, k); + std::memcpy(ivec, out, BlockSize); + if (length <= BlockSize) break; + length -= BlockSize; + in += BlockSize; + out += BlockSize; + } + return; + } + while (length) { + const std::size_t count = length < BlockSize ? length : BlockSize; + std::uint8_t cipher[BlockSize]; + std::memcpy(cipher, in, BlockSize); + DecryptBlock(cipher, block, k); + for (std::size_t n = 0; n < count; ++n) out[n] = block[n] ^ ivec[n]; + std::memcpy(ivec, cipher, BlockSize); + if (length <= BlockSize) break; + length -= BlockSize; + in += BlockSize; + out += BlockSize; + } +} + +void APS5_VABI AES_cfb128_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, std::size_t length, const AesKey* key, unsigned char* ivec, int* num, int encrypt) { + const auto& k = RequireKey(key, __func__); + RequireBlocks(in, out, length, __func__); + if (!ivec || !num) throw std::invalid_argument(std::string(__func__) + ": null iv or position"); + unsigned n = RequirePosition(*num, __func__); + for (std::size_t i = 0; i < length; ++i) { + if (!n) EncryptBlock(ivec, ivec, k); + const auto c = in[i]; + out[i] = ivec[n] ^ c; + ivec[n] = encrypt ? out[i] : c; + n = (n + 1) % BlockSize; + } + *num = static_cast(n); +} + +void APS5_VABI AES_ctr128_encrypt_nid_postfix(const unsigned char* in, unsigned char* out, std::size_t length, const AesKey* key, unsigned char* ivec, unsigned char* ecountBuffer, unsigned* num) { + const auto& k = RequireKey(key, __func__); + RequireBlocks(in, out, length, __func__); + if (!ivec || !ecountBuffer || !num) throw std::invalid_argument(std::string(__func__) + ": null counter, keystream or position"); + if (*num >= BlockSize) throw std::invalid_argument(std::string(__func__) + ": block position " + std::to_string(*num) + " is outside 0-15"); + unsigned n = *num; + for (std::size_t i = 0; i < length; ++i) { + if (!n) { + EncryptBlock(ivec, ecountBuffer, k); + IncrementCounter(ivec); + } + out[i] = in[i] ^ ecountBuffer[n]; + n = (n + 1) % BlockSize; + } + *num = n; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Asn1.cpp b/core/libs/prx/libSceLibreSsl/src/Asn1.cpp new file mode 100644 index 0000000000..409df8adac --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Asn1.cpp @@ -0,0 +1,128 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +static_assert(sizeof(Asn1String) == 24); +static_assert(offsetof(Asn1String, data) == 8); +static_assert(offsetof(Asn1String, flags) == 16); + +int CharacterWidth(int type) { + switch (type) { + case 12: return 0; + case 19: + case 20: + case 22: + case 23: + case 24: + case 26: return 1; + case 28: return 4; + case 30: return 2; + default: return -1; + } +} + +bool ReadCharacter(std::span& input, int width, std::uint32_t& value) { + value = 0; + if (width != 0) { + if (input.size() < static_cast(width)) return false; + for (int index = 0; index < width; ++index) value = (value << 8) | input[index]; + input = input.subspan(width); + } else { + const unsigned char lead = input.front(); + const int bytes = lead < 0x80 ? 1 : lead >= 0xc2 && lead <= 0xdf ? 2 + : lead >= 0xe0 && lead <= 0xef ? 3 : lead >= 0xf0 && lead <= 0xf4 ? 4 : 0; + if (bytes == 0 || input.size() < static_cast(bytes)) return false; + value = lead & (0x7f >> (bytes == 1 ? 0 : bytes)); + for (int index = 1; index < bytes; ++index) { + if ((input[index] & 0xc0) != 0x80) return false; + value = (value << 6) | (input[index] & 0x3f); + } + constexpr std::uint32_t minimum[] = {0, 0, 0x80, 0x800, 0x10000}; + if (value < minimum[bytes]) return false; + input = input.subspan(bytes); + } + return value <= 0x10ffff && (value < 0xd800 || value > 0xdfff); +} + +void AppendCharacter(std::vector& output, std::uint32_t value) { + const int bytes = value < 0x80 ? 1 : value < 0x800 ? 2 : value < 0x10000 ? 3 : 4; + if (output.size() >= static_cast(std::numeric_limits::max() - bytes)) + NotImplemented_nid_no_patch("ASN1_STRING_to_UTF8: output length overflow"); + if (bytes == 1) { + output.push_back(static_cast(value)); + return; + } + constexpr unsigned char prefix[] = {0, 0, 0xc0, 0xe0, 0xf0}; + output.push_back(static_cast(prefix[bytes] | (value >> (6 * (bytes - 1))))); + for (int index = bytes - 2; index >= 0; --index) + output.push_back(static_cast(0x80 | ((value >> (6 * index)) & 0x3f))); +} + +} // namespace + +extern "C" { + +std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value) { + if (!value) return 0; + const auto& integer = *value; + if (integer.type != 2 && integer.type != 0x102) return -1; + if (integer.length > 8) return -1; + if (integer.length <= 0 || !integer.data) return 0; + std::uint64_t magnitude = 0; + for (int index = 0; index < integer.length; ++index) magnitude = (magnitude << 8) | integer.data[index]; + const bool negative = integer.type == 0x102; + const auto limit = static_cast(std::numeric_limits::max()) + negative; + if (magnitude > limit) + NotImplemented_nid_no_patch(__func__); + if (negative && magnitude != 0) return -static_cast(magnitude - 1) - 1; + return static_cast(magnitude); +} + +unsigned char* APS5_VABI ASN1_STRING_data_nid_postfix(Asn1String* value) { + if (!value) APS5_INVALID_ARG_EX; + return value->data; +} + +int APS5_VABI ASN1_STRING_length_nid_postfix(const Asn1String* value) { + if (!value) APS5_INVALID_ARG_EX; + return value->length; +} + +int APS5_VABI ASN1_STRING_type_nid_postfix(Asn1String* value) { + if (!value) APS5_INVALID_ARG_EX; + return value->type; +} + +int APS5_VABI ASN1_STRING_to_UTF8_nid_postfix(unsigned char** output, Asn1String* value) { + if (!value) return -1; + const auto& string = *value; + const int width = CharacterWidth(string.type); + if (width < 0) return -1; + if (!output || (!string.data && string.length != 0)) NotImplemented_nid_no_patch(__func__); + std::size_t length = string.length < 0 ? std::strlen(reinterpret_cast(string.data)) + : static_cast(string.length); + if (length > static_cast(std::numeric_limits::max())) NotImplemented_nid_no_patch(__func__); + std::span input(string.data, length); + std::vector converted; + while (!input.empty()) { + std::uint32_t character; + if (!ReadCharacter(input, width, character)) NotImplemented_nid_no_patch(__func__); + AppendCharacter(converted, character); + } + auto* result = static_cast(malloc_nid_postfix(converted.size() + 1)); + if (!result) NotImplemented_nid_no_patch(__func__); + if (!converted.empty()) std::memcpy(result, converted.data(), converted.size()); + result[converted.size()] = 0; + *output = result; + return static_cast(converted.size()); +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Cipher.cpp b/core/libs/prx/libSceLibreSsl/src/Cipher.cpp new file mode 100644 index 0000000000..bf213e3cbd --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Cipher.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" + +namespace { + +struct Cipher { + std::int32_t valid; + const char* name; + std::uint64_t id; + std::uint64_t keyExchange; + std::uint64_t authentication; + std::uint64_t encryption; + std::uint64_t mac; + std::uint64_t protocol; + std::uint64_t strengthFlags; + std::uint64_t extraFlags; + std::int32_t strengthBits; + std::int32_t algorithmBits; +}; + +static_assert(sizeof(Cipher) == 88); +static_assert(offsetof(Cipher, name) == 8); +static_assert(offsetof(Cipher, id) == 16); +static_assert(offsetof(Cipher, strengthBits) == 80); +static_assert(offsetof(Cipher, algorithmBits) == 84); + +} // namespace + +extern "C" { + +int APS5_VABI SSL_CIPHER_get_bits_nid_postfix(const void* cipher, std::int32_t* algorithmBits) { + if (!cipher) return 0; + const auto* bytes = static_cast(cipher); + if (algorithmBits) { + std::int32_t bits; + std::memcpy(&bits, bytes + offsetof(Cipher, algorithmBits), sizeof(bits)); + *algorithmBits = bits; + } + std::int32_t strength; + std::memcpy(&strength, bytes + offsetof(Cipher, strengthBits), sizeof(strength)); + return strength; +} + +const char* APS5_VABI SSL_CIPHER_get_name_nid_postfix(const void* cipher) { + if (!cipher) return "(NONE)"; + const char* name; + std::memcpy(&name, static_cast(cipher) + offsetof(Cipher, name), sizeof(name)); + return name; +} + +char* APS5_VABI SSL_CIPHER_get_version_nid_postfix(const void* cipher) { + if (!cipher) return const_cast("(NONE)"); + std::uint64_t id; + std::memcpy(&id, static_cast(cipher) + offsetof(Cipher, id), sizeof(id)); + return const_cast((id >> 24) == 3 ? "TLSv1/SSLv3" : "unknown"); +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Digest.cpp b/core/libs/prx/libSceLibreSsl/src/Digest.cpp new file mode 100644 index 0000000000..521c9dbaa6 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Digest.cpp @@ -0,0 +1,516 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +namespace { + +static_assert(sizeof(Md4Context) == 92); +static_assert(sizeof(Md5Context) == 92); +static_assert(sizeof(Sha1Context) == 96); +static_assert(sizeof(Sha256Context) == 112); +static_assert(sizeof(Sha512Context) == 216); + +constexpr std::size_t Sha224Length = 28; +constexpr std::size_t Sha256Length = 32; +constexpr std::size_t Sha384Length = 48; +constexpr std::size_t Sha512Length = 64; + +std::uint32_t LoadLittle32(const unsigned char* p) { + return std::uint32_t(p[0]) | std::uint32_t(p[1]) << 8 | std::uint32_t(p[2]) << 16 | std::uint32_t(p[3]) << 24; +} + +std::uint32_t LoadBig32(const unsigned char* p) { + return std::uint32_t(p[0]) << 24 | std::uint32_t(p[1]) << 16 | std::uint32_t(p[2]) << 8 | std::uint32_t(p[3]); +} + +std::uint64_t LoadBig64(const unsigned char* p) { + return std::uint64_t(LoadBig32(p)) << 32 | LoadBig32(p + 4); +} + +void StoreLittle32(unsigned char* p, std::uint32_t value) { + for (int i = 0; i < 4; ++i) p[i] = static_cast(value >> (8 * i)); +} + +void StoreBig32(unsigned char* p, std::uint32_t value) { + for (int i = 0; i < 4; ++i) p[i] = static_cast(value >> (24 - 8 * i)); +} + +void StoreBig64(unsigned char* p, std::uint64_t value) { + StoreBig32(p, static_cast(value >> 32)); + StoreBig32(p + 4, static_cast(value)); +} + +void Md4Block(Md4Context& c, const unsigned char* block) { + std::uint32_t x[16]; + for (int i = 0; i < 16; ++i) x[i] = LoadLittle32(block + 4 * i); + std::uint32_t a = c.A, b = c.B, cc = c.C, d = c.D; + const auto f = [](std::uint32_t x1, std::uint32_t y, std::uint32_t z) { return (x1 & y) | (~x1 & z); }; + const auto g = [](std::uint32_t x1, std::uint32_t y, std::uint32_t z) { return (x1 & y) | (x1 & z) | (y & z); }; + const auto h = [](std::uint32_t x1, std::uint32_t y, std::uint32_t z) { return x1 ^ y ^ z; }; + static constexpr int shifts1[4] {3, 7, 11, 19}; + static constexpr int shifts2[4] {3, 5, 9, 13}; + static constexpr int shifts3[4] {3, 9, 11, 15}; + static constexpr int order2[16] {0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}; + static constexpr int order3[16] {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}; + for (int i = 0; i < 16; ++i) { + const auto t = std::rotl(a + f(b, cc, d) + x[i], shifts1[i % 4]); + a = d; d = cc; cc = b; b = t; + } + for (int i = 0; i < 16; ++i) { + const auto t = std::rotl(a + g(b, cc, d) + x[order2[i]] + 0x5a827999u, shifts2[i % 4]); + a = d; d = cc; cc = b; b = t; + } + for (int i = 0; i < 16; ++i) { + const auto t = std::rotl(a + h(b, cc, d) + x[order3[i]] + 0x6ed9eba1u, shifts3[i % 4]); + a = d; d = cc; cc = b; b = t; + } + c.A += a; c.B += b; c.C += cc; c.D += d; +} + +void Md5Block(Md5Context& c, const unsigned char* block) { + static constexpr std::uint32_t constants[64] { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, + }; + static constexpr int shifts[16] {7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21}; + std::uint32_t x[16]; + for (int i = 0; i < 16; ++i) x[i] = LoadLittle32(block + 4 * i); + std::uint32_t a = c.A, b = c.B, cc = c.C, d = c.D; + for (int i = 0; i < 64; ++i) { + std::uint32_t mix; + int index; + switch (i / 16) { + case 0: mix = (b & cc) | (~b & d); index = i; break; + case 1: mix = (d & b) | (~d & cc); index = (5 * i + 1) % 16; break; + case 2: mix = b ^ cc ^ d; index = (3 * i + 5) % 16; break; + default: mix = cc ^ (b | ~d); index = (7 * i) % 16; break; + } + const auto t = d; + d = cc; + cc = b; + b += std::rotl(a + mix + constants[i] + x[index], shifts[(i / 16) * 4 + i % 4]); + a = t; + } + c.A += a; c.B += b; c.C += cc; c.D += d; +} + +void Sha1Block(Sha1Context& c, const unsigned char* block) { + std::uint32_t w[80]; + for (int i = 0; i < 16; ++i) w[i] = LoadBig32(block + 4 * i); + for (int i = 16; i < 80; ++i) w[i] = std::rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + std::uint32_t a = c.h[0], b = c.h[1], cc = c.h[2], d = c.h[3], e = c.h[4]; + for (int i = 0; i < 80; ++i) { + std::uint32_t f, k; + if (i < 20) { f = (b & cc) | (~b & d); k = 0x5a827999; } + else if (i < 40) { f = b ^ cc ^ d; k = 0x6ed9eba1; } + else if (i < 60) { f = (b & cc) | (b & d) | (cc & d); k = 0x8f1bbcdc; } + else { f = b ^ cc ^ d; k = 0xca62c1d6; } + const auto t = std::rotl(a, 5) + f + e + k + w[i]; + e = d; d = cc; cc = std::rotl(b, 30); b = a; a = t; + } + c.h[0] += a; c.h[1] += b; c.h[2] += cc; c.h[3] += d; c.h[4] += e; +} + +void Sha256Block(Sha256Context& c, const unsigned char* block) { + static constexpr std::uint32_t constants[64] { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + std::uint32_t w[64]; + for (int i = 0; i < 16; ++i) w[i] = LoadBig32(block + 4 * i); + for (int i = 16; i < 64; ++i) { + const auto s0 = std::rotr(w[i - 15], 7) ^ std::rotr(w[i - 15], 18) ^ (w[i - 15] >> 3); + const auto s1 = std::rotr(w[i - 2], 17) ^ std::rotr(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + std::uint32_t v[8]; + std::memcpy(v, c.h, sizeof(v)); + for (int i = 0; i < 64; ++i) { + const auto s1 = std::rotr(v[4], 6) ^ std::rotr(v[4], 11) ^ std::rotr(v[4], 25); + const auto choice = (v[4] & v[5]) ^ (~v[4] & v[6]); + const auto t1 = v[7] + s1 + choice + constants[i] + w[i]; + const auto s0 = std::rotr(v[0], 2) ^ std::rotr(v[0], 13) ^ std::rotr(v[0], 22); + const auto majority = (v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2]); + std::memmove(v + 1, v, 7 * sizeof(v[0])); + v[4] += t1; + v[0] = t1 + s0 + majority; + } + for (int i = 0; i < 8; ++i) c.h[i] += v[i]; +} + +void Sha512Block(Sha512Context& c, const unsigned char* block) { + static constexpr std::uint64_t constants[80] { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, + 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe, + 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab, + 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, + 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c, + 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817, + }; + std::uint64_t w[80]; + for (int i = 0; i < 16; ++i) w[i] = LoadBig64(block + 8 * i); + for (int i = 16; i < 80; ++i) { + const auto s0 = std::rotr(w[i - 15], 1) ^ std::rotr(w[i - 15], 8) ^ (w[i - 15] >> 7); + const auto s1 = std::rotr(w[i - 2], 19) ^ std::rotr(w[i - 2], 61) ^ (w[i - 2] >> 6); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + std::uint64_t v[8]; + std::memcpy(v, c.h, sizeof(v)); + for (int i = 0; i < 80; ++i) { + const auto s1 = std::rotr(v[4], 14) ^ std::rotr(v[4], 18) ^ std::rotr(v[4], 41); + const auto choice = (v[4] & v[5]) ^ (~v[4] & v[6]); + const auto t1 = v[7] + s1 + choice + constants[i] + w[i]; + const auto s0 = std::rotr(v[0], 28) ^ std::rotr(v[0], 34) ^ std::rotr(v[0], 39); + const auto majority = (v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2]); + std::memmove(v + 1, v, 7 * sizeof(v[0])); + v[4] += t1; + v[0] = t1 + s0 + majority; + } + for (int i = 0; i < 8; ++i) c.h[i] += v[i]; +} + +template TContext& RequireContext(TContext* context, const char* function) { + if (!context) throw std::invalid_argument(std::string(function) + ": null context"); + return *context; +} + +void RequireInput(const void* data, std::size_t length, const char* function) { + if (!data && length) throw std::invalid_argument(std::string(function) + ": null input"); +} + +void RequireDigest(const unsigned char* digest, const char* function) { + if (!digest) throw std::invalid_argument(std::string(function) + ": null digest buffer"); +} + +void RequireBuffered(std::uint32_t buffered, std::size_t block, const char* function) { + if (buffered >= block) throw std::invalid_argument(std::string(function) + ": buffered byte count is not below the block size"); +} + +template void Update32(TContext& c, const void* input, std::size_t length, TBlock block) { + constexpr std::size_t blockSize = 64; + auto* buffer = reinterpret_cast(c.data); + auto* data = static_cast(input); + const std::uint64_t bits = (std::uint64_t(c.Nh) << 32 | c.Nl) + (std::uint64_t(length) << 3); + c.Nl = static_cast(bits); + c.Nh = static_cast(bits >> 32); + if (c.num) { + const std::size_t room = blockSize - c.num; + if (length < room) { + std::memcpy(buffer + c.num, data, length); + c.num += static_cast(length); + return; + } + std::memcpy(buffer + c.num, data, room); + block(c, buffer); + data += room; + length -= room; + c.num = 0; + std::memset(buffer, 0, blockSize); + } + for (; length >= blockSize; data += blockSize, length -= blockSize) block(c, data); + if (length) { + std::memcpy(buffer, data, length); + c.num = static_cast(length); + } +} + +template void Final32(TContext& c, bool bigEndian, TBlock block) { + constexpr std::size_t blockSize = 64; + auto* buffer = reinterpret_cast(c.data); + std::size_t n = c.num; + buffer[n++] = 0x80; + if (n > blockSize - 8) { + std::memset(buffer + n, 0, blockSize - n); + n = 0; + block(c, buffer); + } + std::memset(buffer + n, 0, blockSize - 8 - n); + if (bigEndian) { + StoreBig32(buffer + 56, c.Nh); + StoreBig32(buffer + 60, c.Nl); + } else { + StoreLittle32(buffer + 56, c.Nl); + StoreLittle32(buffer + 60, c.Nh); + } + block(c, buffer); + c.num = 0; + std::memset(buffer, 0, blockSize); +} + +void Sha512Update(Sha512Context& c, const void* input, std::size_t length) { + constexpr std::size_t blockSize = 128; + auto* buffer = reinterpret_cast(c.data); + auto* data = static_cast(input); + const std::uint64_t low = c.Nl + (std::uint64_t(length) << 3); + if (low < c.Nl) ++c.Nh; + c.Nh += std::uint64_t(length) >> 61; + c.Nl = low; + if (c.num) { + const std::size_t room = blockSize - c.num; + if (length < room) { + std::memcpy(buffer + c.num, data, length); + c.num += static_cast(length); + return; + } + std::memcpy(buffer + c.num, data, room); + Sha512Block(c, buffer); + data += room; + length -= room; + c.num = 0; + } + for (; length >= blockSize; data += blockSize, length -= blockSize) Sha512Block(c, data); + if (length) { + std::memcpy(buffer, data, length); + c.num = static_cast(length); + } +} + +int Sha512Final(unsigned char* digest, Sha512Context& c) { + constexpr std::size_t blockSize = 128; + auto* buffer = reinterpret_cast(c.data); + std::size_t n = c.num; + buffer[n++] = 0x80; + if (n > blockSize - 16) { + std::memset(buffer + n, 0, blockSize - n); + n = 0; + Sha512Block(c, buffer); + } + std::memset(buffer + n, 0, blockSize - 16 - n); + StoreBig64(buffer + 112, c.Nh); + StoreBig64(buffer + 120, c.Nl); + Sha512Block(c, buffer); + if (!digest) return 0; + if (c.mdLength != Sha384Length && c.mdLength != Sha512Length) return 0; + for (std::size_t i = 0; i < c.mdLength / 8; ++i) StoreBig64(digest + 8 * i, c.h[i]); + return 1; +} + +int Sha256Final(unsigned char* digest, Sha256Context& c, const char* function) { + RequireDigest(digest, function); + RequireBuffered(c.num, 64, function); + Final32(c, true, Sha256Block); + if (c.mdLength > Sha256Length) return 0; + for (std::size_t i = 0; i < c.mdLength / 4; ++i) StoreBig32(digest + 4 * i, c.h[i]); + return 1; +} +} + +extern "C" { + +int APS5_VABI MD4_Init_nid_postfix(Md4Context* context) { + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + c.A = 0x67452301; + c.B = 0xefcdab89; + c.C = 0x98badcfe; + c.D = 0x10325476; + return 1; +} + +int APS5_VABI MD4_Update_nid_postfix(Md4Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 64, __func__); + Update32(c, data, length, Md4Block); + return 1; +} + +int APS5_VABI MD4_Final_nid_postfix(unsigned char* digest, Md4Context* context) { + auto& c = RequireContext(context, __func__); + RequireDigest(digest, __func__); + RequireBuffered(c.num, 64, __func__); + Final32(c, false, Md4Block); + StoreLittle32(digest, c.A); + StoreLittle32(digest + 4, c.B); + StoreLittle32(digest + 8, c.C); + StoreLittle32(digest + 12, c.D); + return 1; +} + +int APS5_VABI MD5_Init_nid_postfix(Md5Context* context) { + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + c.A = 0x67452301; + c.B = 0xefcdab89; + c.C = 0x98badcfe; + c.D = 0x10325476; + return 1; +} + +int APS5_VABI MD5_Update_nid_postfix(Md5Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 64, __func__); + Update32(c, data, length, Md5Block); + return 1; +} + +int APS5_VABI MD5_Final_nid_postfix(unsigned char* digest, Md5Context* context) { + auto& c = RequireContext(context, __func__); + RequireDigest(digest, __func__); + RequireBuffered(c.num, 64, __func__); + Final32(c, false, Md5Block); + StoreLittle32(digest, c.A); + StoreLittle32(digest + 4, c.B); + StoreLittle32(digest + 8, c.C); + StoreLittle32(digest + 12, c.D); + return 1; +} + +int APS5_VABI SHA1_Init_nid_postfix(Sha1Context* context) { + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + c.h[0] = 0x67452301; + c.h[1] = 0xefcdab89; + c.h[2] = 0x98badcfe; + c.h[3] = 0x10325476; + c.h[4] = 0xc3d2e1f0; + return 1; +} + +int APS5_VABI SHA1_Update_nid_postfix(Sha1Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 64, __func__); + Update32(c, data, length, Sha1Block); + return 1; +} + +int APS5_VABI SHA1_Final_nid_postfix(unsigned char* digest, Sha1Context* context) { + auto& c = RequireContext(context, __func__); + RequireDigest(digest, __func__); + RequireBuffered(c.num, 64, __func__); + Final32(c, true, Sha1Block); + for (int i = 0; i < 5; ++i) StoreBig32(digest + 4 * i, c.h[i]); + return 1; +} + +int APS5_VABI SHA224_Init_nid_postfix(Sha256Context* context) { + static constexpr std::uint32_t initial[8] {0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4}; + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + std::memcpy(c.h, initial, sizeof(initial)); + c.mdLength = Sha224Length; + return 1; +} + +int APS5_VABI SHA256_Init_nid_postfix(Sha256Context* context) { + static constexpr std::uint32_t initial[8] {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + std::memcpy(c.h, initial, sizeof(initial)); + c.mdLength = Sha256Length; + return 1; +} + +int APS5_VABI SHA256_Update_nid_postfix(Sha256Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 64, __func__); + Update32(c, data, length, Sha256Block); + return 1; +} + +int APS5_VABI SHA224_Update_nid_postfix(Sha256Context* context, const void* data, std::size_t length) { + return SHA256_Update_nid_postfix(context, data, length); +} + +int APS5_VABI SHA256_Final_nid_postfix(unsigned char* digest, Sha256Context* context) { + return Sha256Final(digest, RequireContext(context, __func__), __func__); +} + +int APS5_VABI SHA224_Final_nid_postfix(unsigned char* digest, Sha256Context* context) { + return Sha256Final(digest, RequireContext(context, __func__), __func__); +} + +unsigned char* APS5_VABI SHA256_nid_postfix(const unsigned char* data, std::size_t length, unsigned char* digest) { + static unsigned char buffer[Sha256Length]; + RequireInput(data, length, __func__); + if (!digest) digest = buffer; + Sha256Context c; + SHA256_Init_nid_postfix(&c); + + if (length) Update32(c, data, length, Sha256Block); + Sha256Final(digest, c, __func__); + return digest; +} + +int APS5_VABI SHA384_Init_nid_postfix(Sha512Context* context) { + static constexpr std::uint64_t initial[8] { + 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, + 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4, + }; + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + std::memcpy(c.h, initial, sizeof(initial)); + c.mdLength = Sha384Length; + return 1; +} + +int APS5_VABI SHA512_Init_nid_postfix(Sha512Context* context) { + static constexpr std::uint64_t initial[8] { + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, + }; + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + std::memcpy(c.h, initial, sizeof(initial)); + c.mdLength = Sha512Length; + return 1; +} + +int APS5_VABI SHA512_Update_nid_postfix(Sha512Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 128, __func__); + Sha512Update(c, data, length); + return 1; +} + +int APS5_VABI SHA384_Update_nid_postfix(Sha512Context* context, const void* data, std::size_t length) { + return SHA512_Update_nid_postfix(context, data, length); +} + +int APS5_VABI SHA512_Final_nid_postfix(unsigned char* digest, Sha512Context* context) { + auto& c = RequireContext(context, __func__); + RequireBuffered(c.num, 128, __func__); + return Sha512Final(digest, c); +} + +int APS5_VABI SHA384_Final_nid_postfix(unsigned char* digest, Sha512Context* context) { + return SHA512_Final_nid_postfix(digest, context); +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Error.cpp b/core/libs/prx/libSceLibreSsl/src/Error.cpp new file mode 100644 index 0000000000..bad0374dbb --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Error.cpp @@ -0,0 +1,64 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include "prx/libSceLibreSsl/include/Error.hpp" +#include "prx/libc/include/General.hpp" + +namespace { +constexpr int QueueSize = 16; + +struct ErrorQueue { + std::array codes; + int top; + int bottom; +}; + +static_assert(std::is_trivially_destructible_v); + +thread_local ErrorQueue queue{}; + +std::uint64_t Oldest(bool remove) { + if (queue.bottom == queue.top) return 0; + const int index = (queue.bottom + 1) % QueueSize; + const std::uint64_t code = queue.codes[index]; + if (remove) { + queue.bottom = index; + queue.codes[index] = 0; + } + return code; +} + +void Clear() { + queue = {}; +} +} + +extern "C" { + +void LibreSslPutError_nid_no_patch(int library, int function, int reason) { + queue.top = (queue.top + 1) % QueueSize; + if (queue.top == queue.bottom) queue.bottom = (queue.bottom + 1) % QueueSize; + queue.codes[queue.top] = (static_cast(library) & 0xff) << 24 | + (static_cast(function) & 0xfff) << 12 | + (static_cast(reason) & 0xfff); +} + +std::uint64_t APS5_VABI ERR_get_error_nid_postfix() { + return Oldest(true); +} + +std::uint64_t APS5_VABI ERR_peek_error_nid_postfix() { + return Oldest(false); +} + +void APS5_VABI ERR_clear_error_nid_postfix() { + Clear(); +} + +void APS5_VABI ERR_remove_thread_state_nid_postfix(const void* threadId) { + if (threadId != nullptr) NotImplemented_nid_no_patch("ERR_remove_thread_state of a CRYPTO_THREADID"); + Clear(); +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp b/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp new file mode 100644 index 0000000000..e64c504978 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp @@ -0,0 +1,57 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include + +namespace { +struct OcspString { + std::int64_t code; + const char* text; +}; + +template +const char* TableToString(std::int64_t code, const OcspString (&table)[Count]) { + for (const auto& entry : table) { + if (entry.code == code) return entry.text; + } + return "(UNKNOWN)"; +} +} + +extern "C" { + +const char* APS5_VABI OCSP_response_status_str_nid_postfix(std::int64_t status) { + static constexpr OcspString table[] { + {0, "successful"}, + {1, "malformedrequest"}, + {2, "internalerror"}, + {3, "trylater"}, + {5, "sigrequired"}, + {6, "unauthorized"}, + }; + return TableToString(status, table); +} + +const char* APS5_VABI OCSP_cert_status_str_nid_postfix(std::int64_t status) { + static constexpr OcspString table[] { + {0, "good"}, + {1, "revoked"}, + {2, "unknown"}, + }; + return TableToString(status, table); +} + +const char* APS5_VABI OCSP_crl_reason_str_nid_postfix(std::int64_t reason) { + static constexpr OcspString table[] { + {0, "unspecified"}, + {1, "keyCompromise"}, + {2, "cACompromise"}, + {3, "affiliationChanged"}, + {4, "superseded"}, + {5, "cessationOfOperation"}, + {6, "certificateHold"}, + {8, "removeFromCRL"}, + }; + return TableToString(reason, table); +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Runtime.cpp b/core/libs/prx/libSceLibreSsl/src/Runtime.cpp new file mode 100644 index 0000000000..b3ea2389a0 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Runtime.cpp @@ -0,0 +1,57 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/HostEntropy.hpp" + +extern "C" { + +void APS5_VABI RAND_add_nid_postfix(const void*, int, double) { +} + +int APS5_VABI RAND_status_nid_postfix(void) { + return 1; +} + +int APS5_VABI RAND_bytes_nid_postfix(unsigned char* buffer, int count) { + if (count > 0) { + if (!buffer) throw std::invalid_argument(std::string(__func__) + ": null buffer"); + HostEntropy_nid_no_patch(buffer, static_cast(count)); + } + return 1; +} + +int APS5_VABI RAND_load_file_nid_postfix(const char*, std::int64_t bytes) { + return bytes == -1 ? 123456 : static_cast(bytes); +} + +const char* APS5_VABI RAND_file_name_nid_postfix(char* buffer, std::size_t size) { + static constexpr char path[] = "/dev/urandom"; + if (size) { + if (!buffer) throw std::invalid_argument(std::string(__func__) + ": null buffer"); + const std::size_t copied = size - 1 < sizeof(path) - 1 ? size - 1 : sizeof(path) - 1; + std::memcpy(buffer, path, copied); + buffer[copied] = '\0'; + } + return sizeof(path) - 1 >= size ? nullptr : buffer; +} + +void* APS5_VABI CRYPTO_malloc_nid_postfix(int count, const char*, int) { + if (count <= 0) return nullptr; + return malloc_nid_postfix(static_cast(count)); +} + +void APS5_VABI CRYPTO_free_nid_postfix(void* pointer) { + free_nid_postfix(pointer); + +} + +int APS5_VABI CRYPTO_set_mem_functions_nid_postfix(void*, void*, void*) { + return 0; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/tests/Asn1.cpp b/core/libs/prx/libSceLibreSsl/tests/Asn1.cpp new file mode 100644 index 0000000000..e99142e9ad --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/tests/Asn1.cpp @@ -0,0 +1,231 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +static_assert(sizeof(Asn1String) == 24); +static_assert(offsetof(Asn1String, data) == 8); +static_assert(offsetof(Asn1String, flags) == 16); + +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); +} + +template +void RequireInvalidArgument(TAction action, const char* message) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, message); +} + +void CheckConversion(int type, std::span input, std::span expected, int length = -2) { + std::vector storage(input.begin(), input.end()); + storage.push_back(0); + Asn1String value{length == -2 ? static_cast(input.size()) : length, type, storage.data(), 0x123456789abcdef}; + const Asn1String original = value; + unsigned char sentinel = 0xab; + unsigned char* result = &sentinel; + Require(ASN1_STRING_to_UTF8_nid_postfix(&result, &value) == static_cast(expected.size()), "UTF-8 byte length"); + Require(result && result != &sentinel && result != storage.data(), "conversion owns a new buffer"); + Require(expected.empty() || std::memcmp(result, expected.data(), expected.size()) == 0, "converted UTF-8 bytes"); + Require(result[expected.size()] == 0, "conversion terminator"); + Require(value.length == original.length && value.type == original.type && value.data == original.data + && value.flags == original.flags, "conversion preserves the input object"); + Require(std::equal(input.begin(), input.end(), storage.begin()), "conversion preserves the input bytes"); + free_nid_postfix(result); +} + +void Accessors() { + unsigned char data[] = {'a', 0, 'b'}; + Asn1String value{3, 12, data, std::numeric_limits::max()}; + Require(ASN1_STRING_data_nid_postfix(&value) == data, "data accessor preserves pointer identity"); + ASN1_STRING_data_nid_postfix(&value)[2] = 'c'; + Require(data[2] == 'c', "data accessor permits writes"); + Require(ASN1_STRING_length_nid_postfix(&value) == 3, "length includes embedded NUL"); + Require(ASN1_STRING_type_nid_postfix(&value) == 12, "type accessor"); + value = {-9, 0x7fffffff, nullptr, -1}; + Require(ASN1_STRING_data_nid_postfix(&value) == nullptr, "null data is preserved"); + Require(ASN1_STRING_length_nid_postfix(&value) == -9, "length is returned without normalization"); + Require(ASN1_STRING_type_nid_postfix(&value) == 0x7fffffff, "type is returned without normalization"); + RequireInvalidArgument([] { ASN1_STRING_data_nid_postfix(nullptr); }, "null data accessor input"); + RequireInvalidArgument([] { ASN1_STRING_length_nid_postfix(nullptr); }, "null length accessor input"); + RequireInvalidArgument([] { ASN1_STRING_type_nid_postfix(nullptr); }, "null type accessor input"); +} + +void Integers() { + Require(ASN1_INTEGER_get_nid_postfix(nullptr) == 0, "null integer"); + unsigned char data[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0}; + constexpr std::array expected{ + 0, 0x01, 0x0123, 0x012345, 0x01234567, 0x0123456789, 0x0123456789ab, + 0x0123456789abcd, 0x0123456789abcdef}; + for (int length = 0; length <= 8; ++length) { + Asn1String value{length, 2, data, -1}; + Require(ASN1_INTEGER_get_nid_postfix(&value) == expected[length], "positive integer and 64-bit guest long"); + value.type = 0x102; + Require(ASN1_INTEGER_get_nid_postfix(&value) == -expected[length], "negative integer magnitude"); + } + Asn1String value{9, 2, nullptr, 0}; + Require(ASN1_INTEGER_get_nid_postfix(&value) == -1, "overlength is rejected before null data"); + value.length = 8; + Require(ASN1_INTEGER_get_nid_postfix(&value) == 0, "integer with null data"); + for (int length : {std::numeric_limits::min(), -8, -1, 0}) { + for (int type : {2, 0x102}) { + for (auto* bytes : {data, static_cast(nullptr), reinterpret_cast(1)}) { + value = {length, type, bytes, -1}; + const Asn1String original = value; + Require(ASN1_INTEGER_get_nid_postfix(&value) == 0, "LibreSSL 2.5.4 nonpositive integer length"); + Require(value.length == original.length && value.type == original.type && value.data == original.data + && value.flags == original.flags, "nonpositive integer length preserves input"); + } + } + } + value = {-1, 2, data, 0}; + for (int type : {-1, 0, 1, 3, 12, 0x100, 0x202, 0x302}) { + value.type = type; + Require(ASN1_INTEGER_get_nid_postfix(&value) == -1, "only integer types are accepted"); + } + std::array maximum{0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + value = {8, 2, maximum.data(), 0}; + Require(ASN1_INTEGER_get_nid_postfix(&value) == std::numeric_limits::max(), "maximum signed integer"); + value.type = 0x102; + Require(ASN1_INTEGER_get_nid_postfix(&value) == -std::numeric_limits::max(), "negative maximum magnitude"); + std::array minimum{0x80, 0, 0, 0, 0, 0, 0, 0}; + value = {8, 0x102, minimum.data(), -1}; + const Asn1String original = value; + Require(ASN1_INTEGER_get_nid_postfix(&value) == std::numeric_limits::min(), "minimum signed integer"); + Require(value.length == original.length && value.type == original.type && value.data == original.data + && value.flags == original.flags && minimum == std::array{0x80, 0, 0, 0, 0, 0, 0, 0}, "minimum integer preserves input"); + value.type = 2; + RequireThrows([&] { ASN1_INTEGER_get_nid_postfix(&value); }, "positive magnitude above signed maximum"); + value.type = 0x102; + minimum.back() = 1; + RequireThrows([&] { ASN1_INTEGER_get_nid_postfix(&value); }, "negative magnitude below signed minimum"); + value.data = maximum.data(); + maximum[0] = 0x80; + for (int type : {2, 0x102}) { + value.type = type; + RequireThrows([&] { ASN1_INTEGER_get_nid_postfix(&value); }, "source signed-overflow path throws"); + } + std::array zero{}; + value = {9, 2, zero.data(), 0}; + Require(ASN1_INTEGER_get_nid_postfix(&value) == -1, "overlength leading zeros are not trimmed"); +} + +void Strings() { + constexpr unsigned char latin[] = {0, 0x41, 0x7f, 0x80, 0xa3, 0xff}; + constexpr unsigned char latinUtf8[] = {0, 0x41, 0x7f, 0xc2, 0x80, 0xc2, 0xa3, 0xc3, 0xbf}; + constexpr unsigned char bmp[] = {0, 0, 0, 0x7f, 0, 0x80, 7, 0xff, 8, 0, 0x20, 0xac, 0xd7, 0xff, 0xe0, 0, 0xff, 0xff}; + constexpr unsigned char bmpUtf8[] = {0, 0x7f, 0xc2, 0x80, 0xdf, 0xbf, 0xe0, 0xa0, 0x80, 0xe2, 0x82, 0xac, + 0xed, 0x9f, 0xbf, 0xee, 0x80, 0x80, 0xef, 0xbf, 0xbf}; + constexpr unsigned char universal[] = {0, 0, 0, 0x41, 0, 1, 0, 0, 0, 1, 0xf6, 0, 0, 0x10, 0xff, 0xff}; + constexpr unsigned char universalUtf8[] = {0x41, 0xf0, 0x90, 0x80, 0x80, 0xf0, 0x9f, 0x98, 0x80, 0xf4, 0x8f, 0xbf, 0xbf}; + for (int type : {19, 20, 22, 23, 24, 26}) CheckConversion(type, latin, latinUtf8); + CheckConversion(30, bmp, bmpUtf8); + CheckConversion(28, universal, universalUtf8); + CheckConversion(12, bmpUtf8, bmpUtf8); + CheckConversion(12, universalUtf8, universalUtf8); + for (int type : {12, 19, 20, 22, 23, 24, 26, 28, 30}) { + CheckConversion(type, {}, {}); + Asn1String empty{0, type, nullptr, 0}; + unsigned char* result = nullptr; + Require(ASN1_STRING_to_UTF8_nid_postfix(&result, &empty) == 0 && result && *result == 0, "empty null-data string"); + free_nid_postfix(result); + } + constexpr unsigned char terminated[] = {'A', 0xc2, 0xa3, 0, 'X'}; + CheckConversion(12, terminated, std::span(terminated).first(3), -1); + CheckConversion(12, terminated, std::span(terminated).first(3), -7); + constexpr unsigned char ascii[] = {'A', 'B', 'C'}; + CheckConversion(19, ascii, ascii, -1); + + unsigned char sentinel = 0xab; + unsigned char* result = &sentinel; + Require(ASN1_STRING_to_UTF8_nid_postfix(&result, nullptr) == -1 && result == &sentinel, "null conversion input"); + Require(ASN1_STRING_to_UTF8_nid_postfix(nullptr, nullptr) == -1, "null input checked before output"); + for (int type = -1; type <= 32; ++type) { + if (type == 12 || type == 19 || type == 20 || type == 22 || type == 23 || type == 24 || type == 26 || type == 28 || type == 30) continue; + Asn1String value{0, type, nullptr, 0}; + Require(ASN1_STRING_to_UTF8_nid_postfix(&result, &value) == -1 && result == &sentinel, "unsupported string tag"); + Require(ASN1_STRING_to_UTF8_nid_postfix(nullptr, &value) == -1, "tag checked before output"); + } + Asn1String value{0, 12, nullptr, 0}; + RequireThrows([&] { ASN1_STRING_to_UTF8_nid_postfix(nullptr, &value); }, "null conversion output"); + value.length = 1; + RequireThrows([&] { ASN1_STRING_to_UTF8_nid_postfix(&result, &value); }, "null nonempty string data"); + value.length = -1; + RequireThrows([&] { ASN1_STRING_to_UTF8_nid_postfix(&result, &value); }, "null terminated-string data"); +} + +std::size_t lastAllocationSize = 0; +int allocationCount = 0; +int freeCount = 0; + +void* APS5_VABI Allocate_nid_no_patch(std::size_t size) { + lastAllocationSize = size; + ++allocationCount; + return std::malloc(size); +} + +void APS5_VABI Free_nid_no_patch(void* pointer) { + ++freeCount; + std::free(pointer); +} + +void* APS5_VABI Calloc_nid_no_patch(std::size_t, std::size_t) { throw std::runtime_error("unexpected calloc"); } +void* APS5_VABI Reallocate_nid_no_patch(void*, std::size_t) { throw std::runtime_error("unexpected realloc"); } +void* APS5_VABI Align_nid_no_patch(std::size_t, std::size_t) { throw std::runtime_error("unexpected aligned allocation"); } +void* APS5_VABI Realign_nid_no_patch(void*, std::size_t, std::size_t) { throw std::runtime_error("unexpected aligned reallocation"); } +int APS5_VABI PosixAlign_nid_no_patch(void**, std::size_t, std::size_t) { throw std::runtime_error("unexpected posix_memalign"); } + +void Allocator() { + std::array api{ + reinterpret_cast(Allocate_nid_no_patch), reinterpret_cast(Free_nid_no_patch), reinterpret_cast(Calloc_nid_no_patch), + reinterpret_cast(Reallocate_nid_no_patch), reinterpret_cast(Align_nid_no_patch), reinterpret_cast(Realign_nid_no_patch), + reinterpret_cast(PosixAlign_nid_no_patch)}; + ApplicationHeapRegister_nid_no_patch(api.data()); + constexpr unsigned char input[] = {0xff}; + constexpr unsigned char expected[] = {0xc3, 0xbf}; + CheckConversion(20, input, expected); + Require(allocationCount == 1 && lastAllocationSize == 3 && freeCount == 1, "conversion uses the application allocator"); +} + +} // namespace + +int main(int argc, char**) { + if (argc > 1) { + Allocator(); + return 0; + } + std::array api{}; + ApplicationHeapRegister_nid_no_patch(api.data()); + Accessors(); + Integers(); + Strings(); +} diff --git a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt new file mode 100644 index 0000000000..1a3d213378 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt @@ -0,0 +1,22 @@ +add_library(libSceLibreSsl3 SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp) +target_include_directories(libSceLibreSsl3 PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceLibreSsl3 PRIVATE libSceLibreSsl) +configure_windows_unwind(libSceLibreSsl3) + +if(BUILD_TESTING) + add_test_executable(guest_crypto_memcmp_tests ${LIBS_INCLUDE_DIR}/tests/GuestCryptoMemcmp.cpp) + target_include_directories(guest_crypto_memcmp_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_crypto_memcmp_tests PRIVATE libSceLibreSsl3) + add_test(NAME guest_crypto_memcmp COMMAND guest_crypto_memcmp_tests) + set_tests_properties(guest_crypto_memcmp PROPERTIES TIMEOUT 20) + add_test_executable(guest_libressl3_digest_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSsl3Digest.cpp) + target_include_directories(guest_libressl3_digest_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_digest_tests PRIVATE libSceLibreSsl3 libc) + configure_windows_unwind(guest_libressl3_digest_tests) + add_test(NAME guest_libressl3_digest COMMAND guest_libressl3_digest_tests) + set_tests_properties(guest_libressl3_digest PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_crypto_memcmp guest_libressl3_digest PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index d3289b659b..dfd0b1b483 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -31,9 +32,13 @@ int APS5_VABI BIO_vfree_nid_postfix(void) { return 0; } -int APS5_VABI CRYPTO_memcmp_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI CRYPTO_memcmp_nid_postfix(const void* first, const void* second, std::size_t length) { + const auto* firstBytes = static_cast(first); + const auto* secondBytes = static_cast(second); + unsigned int difference = 0; + for (std::size_t index = 0; index < length; ++index) + difference |= firstBytes[index] ^ secondBytes[index]; + return static_cast(difference); } int APS5_VABI DH_get0_key_nid_postfix(void) { @@ -216,11 +221,6 @@ int APS5_VABI EVP_sha224_nid_postfix(void) { return 0; } -int APS5_VABI MD5_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OBJ_cmp_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -281,11 +281,6 @@ int APS5_VABI RSA_up_ref_nid_postfix(void) { return 0; } -int APS5_VABI SHA1_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_add0_chain_cert_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl3/include/Api.hpp b/core/libs/prx/libSceLibreSsl3/include/Api.hpp new file mode 100644 index 0000000000..ff2a535fd7 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/include/Api.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI ASN1_INTEGER_free_nid_postfix(void); +int APS5_VABI ASN1_OBJECT_free_nid_postfix(void); +int APS5_VABI ASN1_STRING_get0_data_nid_postfix(void); +int APS5_VABI BIO_up_ref_nid_postfix(void); +int APS5_VABI BIO_vfree_nid_postfix(void); +int APS5_VABI CRYPTO_memcmp_nid_postfix(const void* first, const void* second, std::size_t length); +int APS5_VABI DH_get0_key_nid_postfix(void); +int APS5_VABI DH_get0_pqg_nid_postfix(void); +int APS5_VABI DSA_get0_key_nid_postfix(void); +int APS5_VABI DSA_get0_pqg_nid_postfix(void); +int APS5_VABI EC_KEY_up_ref_nid_postfix(void); +int APS5_VABI ERR_load_crypto_strings_nid_postfix(void); +int APS5_VABI ERR_peek_last_error_nid_postfix(void); +int APS5_VABI ERR_print_errors_cb_nid_postfix(void); +int APS5_VABI EVP_CIPHER_CTX_set_flags_nid_postfix(void); +int APS5_VABI EVP_DigestInit_nid_postfix(void); +int APS5_VABI EVP_DigestSignFinal_nid_postfix(void); +int APS5_VABI EVP_DigestSignInit_nid_postfix(void); +int APS5_VABI EVP_EncryptInit_nid_postfix(void); +int APS5_VABI EVP_MD_CTX_free_nid_postfix(void); +int APS5_VABI EVP_MD_CTX_new_nid_postfix(void); +int APS5_VABI EVP_PKCS82PKEY_nid_postfix(void); +int APS5_VABI EVP_PKEY_CTX_ctrl_nid_postfix(void); +int APS5_VABI EVP_PKEY_CTX_new_nid_postfix(void); +int APS5_VABI EVP_PKEY_decrypt_nid_postfix(void); +int APS5_VABI EVP_PKEY_decrypt_init_nid_postfix(void); +int APS5_VABI EVP_PKEY_encrypt_nid_postfix(void); +int APS5_VABI EVP_PKEY_encrypt_init_nid_postfix(void); +int APS5_VABI EVP_PKEY_get0_DH_nid_postfix(void); +int APS5_VABI EVP_PKEY_get0_DSA_nid_postfix(void); +int APS5_VABI EVP_PKEY_get0_RSA_nid_postfix(void); +int APS5_VABI EVP_PKEY_new_mac_key_nid_postfix(void); +int APS5_VABI EVP_PKEY_set1_EC_KEY_nid_postfix(void); +int APS5_VABI EVP_PKEY_size_nid_postfix(void); +int APS5_VABI EVP_PKEY_up_ref_nid_postfix(void); +int APS5_VABI EVP_SignFinal_nid_postfix(void); +int APS5_VABI EVP_VerifyFinal_nid_postfix(void); +int APS5_VABI EVP_aes_128_wrap_nid_postfix(void); +int APS5_VABI EVP_aes_192_wrap_nid_postfix(void); +int APS5_VABI EVP_aes_256_wrap_nid_postfix(void); +int APS5_VABI EVP_sha1_nid_postfix(void); +int APS5_VABI EVP_sha224_nid_postfix(void); +int APS5_VABI OBJ_cmp_nid_postfix(void); +int APS5_VABI OBJ_txt2obj_nid_postfix(void); +int APS5_VABI OCSP_CERTID_free_nid_postfix(void); +int APS5_VABI OCSP_cert_to_id_nid_postfix(void); +int APS5_VABI OCSP_resp_find_status_nid_postfix(void); +int APS5_VABI OpenSSL_version_nid_postfix(void); +int APS5_VABI PKCS7_free_nid_postfix(void); +int APS5_VABI PKCS8_PRIV_KEY_INFO_free_nid_postfix(void); +int APS5_VABI RSA_check_key_nid_postfix(void); +int APS5_VABI RSA_get0_key_nid_postfix(void); +int APS5_VABI RSA_padding_add_PKCS1_PSS_mgf1_nid_postfix(void); +int APS5_VABI RSA_up_ref_nid_postfix(void); +int APS5_VABI SSL_CTX_add0_chain_cert_nid_postfix(void); +int APS5_VABI SSL_CTX_clear_chain_certs_nid_postfix(void); +int APS5_VABI SSL_CTX_set_max_proto_version_nid_postfix(void); +int APS5_VABI SSL_CTX_set_min_proto_version_nid_postfix(void); +int APS5_VABI SSL_CTX_up_ref_nid_postfix(void); +int APS5_VABI SSL_SESSION_up_ref_nid_postfix(void); +int APS5_VABI SSL_get_client_random_nid_postfix(void); +int APS5_VABI SSL_up_ref_nid_postfix(void); +int APS5_VABI X509_PUBKEY_get0_param_nid_postfix(void); +int APS5_VABI X509_STORE_CTX_free_nid_postfix(void); +int APS5_VABI X509_STORE_CTX_init_nid_postfix(void); +int APS5_VABI X509_STORE_CTX_new_nid_postfix(void); +int APS5_VABI X509_STORE_up_ref_nid_postfix(void); +int APS5_VABI X509_get0_extensions_nid_postfix(void); +int APS5_VABI X509_get0_notAfter_nid_postfix(void); +int APS5_VABI X509_get0_notBefore_nid_postfix(void); +int APS5_VABI X509_get0_signature_nid_postfix(void); +int APS5_VABI X509_get_ext_nid_postfix(void); +int APS5_VABI X509_get_ext_count_nid_postfix(void); +int APS5_VABI X509_get_version_nid_postfix(void); +int APS5_VABI X509_getm_notAfter_nid_postfix(void); +int APS5_VABI X509_getm_notBefore_nid_postfix(void); +int APS5_VABI X509_up_ref_nid_postfix(void); +int APS5_VABI d2i_ASN1_INTEGER_nid_postfix(void); +int APS5_VABI d2i_ECParameters_nid_postfix(void); +int APS5_VABI d2i_PKCS7_nid_postfix(void); +int APS5_VABI d2i_PKCS8_PRIV_KEY_INFO_bio_nid_postfix(void); +int APS5_VABI d2i_PrivateKey_bio_nid_postfix(void); +int APS5_VABI d2i_RSAPublicKey_bio_nid_postfix(void); +int APS5_VABI d2i_X509_bio_nid_postfix(void); +int APS5_VABI o2i_ECPublicKey_nid_postfix(void); +unsigned char* APS5_VABI MD5_nid_postfix(const unsigned char* data, std::size_t length, unsigned char* digest); +unsigned char* APS5_VABI SHA1_nid_postfix(const unsigned char* data, std::size_t length, unsigned char* digest); + +} diff --git a/core/libs/prx/libSceLibreSsl3/libSceLibreSsl3.hpp b/core/libs/prx/libSceLibreSsl3/libSceLibreSsl3.hpp new file mode 100644 index 0000000000..e9b09745c0 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/libSceLibreSsl3.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceLibreSsl3/include/Api.hpp" diff --git a/core/libs/prx/libSceLibreSsl3/src/Digest.cpp b/core/libs/prx/libSceLibreSsl3/src/Digest.cpp new file mode 100644 index 0000000000..0df5d8b0b4 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/Digest.cpp @@ -0,0 +1,29 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include "prx/libc/include/General.hpp" + +extern "C" { + +unsigned char* APS5_VABI MD5_nid_postfix(const unsigned char* data, std::size_t length, unsigned char* digest) { + if (!digest) throw std::invalid_argument("MD5: null digest buffer"); + Md5Context context; + MD5_Init_nid_postfix(&context); + MD5_Update_nid_postfix(&context, data, length); + MD5_Final_nid_postfix(digest, &context); + + return digest; +} + +unsigned char* APS5_VABI SHA1_nid_postfix(const unsigned char* data, std::size_t length, unsigned char* digest) { + if (!digest) throw std::invalid_argument("SHA1: null digest buffer"); + Sha1Context context; + SHA1_Init_nid_postfix(&context); + SHA1_Update_nid_postfix(&context, data, length); + SHA1_Final_nid_postfix(digest, &context); + + return digest; +} + +} diff --git a/core/libs/prx/libSceLoginDialog/Export.cpp b/core/libs/prx/libSceLoginDialog/Export.cpp index 7ca5a3626c..1f652d35fb 100644 --- a/core/libs/prx/libSceLoginDialog/Export.cpp +++ b/core/libs/prx/libSceLoginDialog/Export.cpp @@ -1,9 +1,10 @@ +#include "prx/libSceLoginDialog/include/Api.hpp" #include #include #include #include -#include #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" namespace { @@ -12,29 +13,13 @@ constexpr int COMMON_DIALOG_STATUS_NONE = 0; constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; -constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x81340001u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x81340002u); -constexpr int COMMON_DIALOG_ERROR_PARAM_INVALID = static_cast(0x81340003u); -constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x81340005u); -constexpr int COMMON_DIALOG_ERROR_NO_SERVICE = static_cast(0x81340007u); +constexpr int LOGIN_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x81340001u); +constexpr int LOGIN_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x81340002u); +constexpr int LOGIN_DIALOG_ERROR_BUSY = static_cast(0x81340005u); +constexpr int LOGIN_DIALOG_ERROR_INVALID_ARGUMENT = static_cast(0x81340003u); constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; -constexpr std::uint32_t LOGIN_DIALOG_PARAM_SIZE = 0x40; -constexpr std::uint32_t LOGIN_DIALOG_SELECTOR_LIMIT = 2; -constexpr std::size_t LOGIN_DIALOG_FIRST_RESERVED_WORD = 0x2C / 4; -bool ValidOpenParam(const void* param) { - if (param == nullptr) return false; - std::uint32_t size = 0; - std::memcpy(&size, param, sizeof(size)); - if (size != LOGIN_DIALOG_PARAM_SIZE) return false; - std::uint32_t words[LOGIN_DIALOG_PARAM_SIZE / sizeof(std::uint32_t)]; - std::memcpy(words, param, sizeof(words)); - if (words[1] >= LOGIN_DIALOG_SELECTOR_LIMIT) return false; - for (std::size_t i = LOGIN_DIALOG_FIRST_RESERVED_WORD; i < std::size(words); ++i) { - if (words[i] != 0) return false; - } - return true; -} +constexpr int LOGIN_DIALOG_ERROR_INVALID_USER = static_cast(0x81340007u); std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; @@ -44,15 +29,18 @@ extern "C" { int APS5_VABI sceLoginDialogInitialize(void) { int expected = COMMON_DIALOG_STATUS_NONE; - if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return LOGIN_DIALOG_ERROR_ALREADY_INITIALIZED; return 0; } -int APS5_VABI sceLoginDialogOpen(const void* param) { - if (!ValidOpenParam(param)) return COMMON_DIALOG_ERROR_PARAM_INVALID; +int APS5_VABI sceLoginDialogOpen(const LoginDialogParam* param) { + if (param == nullptr || param->size != sizeof(*param) || param->mode > 1 || !std::all_of(std::begin(param->reserved), std::end(param->reserved), [](std::uint32_t value) { return value == 0; })) return LOGIN_DIALOG_ERROR_INVALID_ARGUMENT; + for (const auto userId : param->userIds) { + if (userId != -1 && (static_cast(userId) < 0x10000000u || static_cast(userId) >= 0x40000000u)) return LOGIN_DIALOG_ERROR_INVALID_USER; + } const int status = g_status.load(); - if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (status == COMMON_DIALOG_STATUS_NONE) return LOGIN_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return LOGIN_DIALOG_ERROR_BUSY; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } @@ -65,23 +53,24 @@ int APS5_VABI sceLoginDialogGetStatus(void) { return g_status.load(); } -int APS5_VABI sceLoginDialogGetResult(void* result) { - if (result == nullptr) return COMMON_DIALOG_ERROR_PARAM_INVALID; +int APS5_VABI sceLoginDialogGetResult(LoginDialogResult* result) { + if (result == nullptr) return LOGIN_DIALOG_ERROR_INVALID_ARGUMENT; const int status = g_status.load(); - if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - const std::int32_t words[] = {COMMON_DIALOG_RESULT_USER_CANCELED, COMMON_DIALOG_ERROR_NO_SERVICE}; - std::memcpy(result, words, sizeof(words)); - return status == COMMON_DIALOG_STATUS_FINISHED ? 0 : COMMON_DIALOG_ERROR_BUSY; + if (status == COMMON_DIALOG_STATUS_NONE) return LOGIN_DIALOG_ERROR_NOT_INITIALIZED; + result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + result->errorCode = LOGIN_DIALOG_ERROR_INVALID_USER; + if (status != COMMON_DIALOG_STATUS_FINISHED) return LOGIN_DIALOG_ERROR_BUSY; + return 0; } int APS5_VABI sceLoginDialogClose(void) { - if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return LOGIN_DIALOG_ERROR_NOT_INITIALIZED; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } int APS5_VABI sceLoginDialogTerminate(void) { - if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return LOGIN_DIALOG_ERROR_NOT_INITIALIZED; return 0; } diff --git a/core/libs/prx/libSceLoginDialog/include/Api.hpp b/core/libs/prx/libSceLoginDialog/include/Api.hpp new file mode 100644 index 0000000000..51bca2f699 --- /dev/null +++ b/core/libs/prx/libSceLoginDialog/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceLoginDialogInitialize(void); +int APS5_VABI sceLoginDialogOpen(const LoginDialogParam* param); +int APS5_VABI sceLoginDialogUpdateStatus(void); +int APS5_VABI sceLoginDialogGetStatus(void); +int APS5_VABI sceLoginDialogGetResult(LoginDialogResult* result); +int APS5_VABI sceLoginDialogClose(void); +int APS5_VABI sceLoginDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceLoginDialog/libSceLoginDialog.hpp b/core/libs/prx/libSceLoginDialog/libSceLoginDialog.hpp new file mode 100644 index 0000000000..eed4b0aa93 --- /dev/null +++ b/core/libs/prx/libSceLoginDialog/libSceLoginDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceLoginDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceLoginMgrServer/Export.cpp b/core/libs/prx/libSceLoginMgrServer/Export.cpp index c0497cf3de..8c56379312 100644 --- a/core/libs/prx/libSceLoginMgrServer/Export.cpp +++ b/core/libs/prx/libSceLoginMgrServer/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLoginMgrServer/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceLoginMgrServer/include/Api.hpp b/core/libs/prx/libSceLoginMgrServer/include/Api.hpp new file mode 100644 index 0000000000..de64daf5d9 --- /dev/null +++ b/core/libs/prx/libSceLoginMgrServer/include/Api.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceLoginMgrServerCheckSafetyNoticeShown(); +int APS5_VABI sceLoginMgrServerClearMorpheusRequiredUserId(); +int APS5_VABI sceLoginMgrServerConvertToUserId(); +int APS5_VABI sceLoginMgrServerCreateUser(); +int APS5_VABI sceLoginMgrServerDestroyUser(); +int APS5_VABI sceLoginMgrServerDialogGetAppId(); +int APS5_VABI sceLoginMgrServerDialogGetOpenParam(); +int APS5_VABI sceLoginMgrServerDialogNotifyCloseFinished(); +int APS5_VABI sceLoginMgrServerDialogNotifyOpenFinished(); +int APS5_VABI sceLoginMgrServerDialogSetResult(); +int APS5_VABI sceLoginMgrServerGetLogoutInfo(); +int APS5_VABI sceLoginMgrServerGetMorpheusRequiredUserId(); +int APS5_VABI sceLoginMgrServerGetSharePlayAllowPadOperation(); +int APS5_VABI sceLoginMgrServerInitialize(); +int APS5_VABI sceLoginMgrServerInitializeSharePlayAllowPadOperation(); +int APS5_VABI sceLoginMgrServerIsRequestedCdlgClose(); +int APS5_VABI sceLoginMgrServerLoginServiceGetRequestParam(); +int APS5_VABI sceLoginMgrServerLoginServiceNotifyRequestFinished(); +int APS5_VABI sceLoginMgrServerNotifyEasySignInFinished(); +int APS5_VABI sceLoginMgrServerNotifyEasySignInUserCode(); +int APS5_VABI sceLoginMgrServerNotifyHmdConnectInfo(); +int APS5_VABI sceLoginMgrServerNotifySafetyNoticeShown(); +int APS5_VABI sceLoginMgrServerNotifyShellUIState(); +int APS5_VABI sceLoginMgrServerSetLoginFlag(); +int APS5_VABI sceLoginMgrServerSetMorpheusAssignEnabledFlag(); +int APS5_VABI sceLoginMgrServerSetMorpheusRequiredUserId(); +int APS5_VABI sceLoginMgrServerSetSharePlayMode(); +int APS5_VABI sceLoginMgrServerSetUserStatus(); +int APS5_VABI sceLoginMgrServerTerminate(); +int APS5_VABI sceLoginMgrServerUpdateUserIdRalatedToPadUniqueId(); + +} diff --git a/core/libs/prx/libSceLoginMgrServer/libSceLoginMgrServer.hpp b/core/libs/prx/libSceLoginMgrServer/libSceLoginMgrServer.hpp new file mode 100644 index 0000000000..244b5b6a16 --- /dev/null +++ b/core/libs/prx/libSceLoginMgrServer/libSceLoginMgrServer.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceLoginMgrServer/include/Api.hpp" diff --git a/core/libs/prx/libSceLoginService/Export.cpp b/core/libs/prx/libSceLoginService/Export.cpp index 43a1b1dcdf..e3a24fd796 100644 --- a/core/libs/prx/libSceLoginService/Export.cpp +++ b/core/libs/prx/libSceLoginService/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLoginService/include/Api.hpp" #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -12,7 +13,7 @@ int APS5_VABI sceLoginServiceTerminate(void) { return 0; } -int APS5_VABI sceLoginServiceRequestDevices(void* param) { +int APS5_VABI sceLoginServiceRequestDevices(const LoginServiceDeviceRequest* param) { (void)param; NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLoginService/include/Api.hpp b/core/libs/prx/libSceLoginService/include/Api.hpp new file mode 100644 index 0000000000..853ce09844 --- /dev/null +++ b/core/libs/prx/libSceLoginService/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +struct LoginServiceDeviceRequest; + +extern "C" { + +int APS5_VABI sceLoginServiceInitialize(void); +int APS5_VABI sceLoginServiceTerminate(void); +int APS5_VABI sceLoginServiceRequestDevices(const LoginServiceDeviceRequest* param); + +} diff --git a/core/libs/prx/libSceLoginService/libSceLoginService.hpp b/core/libs/prx/libSceLoginService/libSceLoginService.hpp new file mode 100644 index 0000000000..7de7ce0c1e --- /dev/null +++ b/core/libs/prx/libSceLoginService/libSceLoginService.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceLoginService/include/Api.hpp" diff --git a/core/libs/prx/libSceM4aacEnc/Export.cpp b/core/libs/prx/libSceM4aacEnc/Export.cpp index e71f829cca..64de066d07 100644 --- a/core/libs/prx/libSceM4aacEnc/Export.cpp +++ b/core/libs/prx/libSceM4aacEnc/Export.cpp @@ -1,9 +1,9 @@ +#include "prx/libSceM4aacEnc/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" - extern "C" { int32_t APS5_VABI sceM4aacEncClearContext(int32_t m4aacEncHandle) { diff --git a/core/libs/prx/libSceM4aacEnc/include/Api.hpp b/core/libs/prx/libSceM4aacEnc/include/Api.hpp new file mode 100644 index 0000000000..a981414104 --- /dev/null +++ b/core/libs/prx/libSceM4aacEnc/include/Api.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int32_t APS5_VABI sceM4aacEncClearContext(int32_t m4aacEncHandle); +int APS5_VABI sceM4aacEncClearContextEx(); +int32_t APS5_VABI sceM4aacEncCreateEncoder(int32_t channels, int32_t samplingRate, int32_t bitRate, int32_t inputFormat, int32_t outputFormat, int32_t *pInternalError); +int APS5_VABI sceM4aacEncCreateEncoderEx(); +int32_t APS5_VABI sceM4aacEncDeleteEncoder(int32_t m4aacEncHandle, int32_t *pInternalError); +int APS5_VABI sceM4aacEncDeleteEncoderEx(); +int32_t APS5_VABI sceM4aacEncEncode(int32_t m4aacEncHandle, void *pInput, uint32_t inputByte, void *pOutput, uint32_t *pOutputByte, int32_t *pInternalError); +int APS5_VABI sceM4aacEncEncodeEx(); +int APS5_VABI sceM4aacEncEncodeWithPriority(); +int APS5_VABI sceM4aacEncEncodeWithPriorityEx(); +int32_t APS5_VABI sceM4aacEncFlush(int32_t m4aacEncHandle, void *pOutput, uint32_t *pOutputByte, int32_t *pInternalError); +int APS5_VABI sceM4aacEncFlushEx(); +int APS5_VABI sceM4aacEncFlushWithPriority(); +int APS5_VABI sceM4aacEncFlushWithPriorityEx(); +int APS5_VABI sceM4aacEncGetContext(); +int APS5_VABI sceM4aacEncInitialize(); +int APS5_VABI sceM4aacEncTerminate(); + +} diff --git a/core/libs/prx/libSceM4aacEnc/libSceM4aacEnc.hpp b/core/libs/prx/libSceM4aacEnc/libSceM4aacEnc.hpp new file mode 100644 index 0000000000..23842cc39b --- /dev/null +++ b/core/libs/prx/libSceM4aacEnc/libSceM4aacEnc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceM4aacEnc/include/Api.hpp" diff --git a/core/libs/prx/libSceMarlin/libSceMarlin.hpp b/core/libs/prx/libSceMarlin/libSceMarlin.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceMarlin/libSceMarlin.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceMat/libSceMat.hpp b/core/libs/prx/libSceMat/libSceMat.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceMat/libSceMat.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceMbus/Export.cpp b/core/libs/prx/libSceMbus/Export.cpp index 10a101f22c..5212afd405 100644 --- a/core/libs/prx/libSceMbus/Export.cpp +++ b/core/libs/prx/libSceMbus/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMbus/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,11 +6,23 @@ #include +static_assert(sizeof(DeviceServiceThreadParam) == 16); +static_assert(offsetof(DeviceServiceThreadParam, priority) == 8); +static_assert(sizeof(DeviceServiceInitParam) == 16); +static_assert(offsetof(DeviceServiceInitParam, threadParam) == 8); +static_assert(sizeof(DeviceServiceUsageFilter) == 6); + extern "C" { -std::int32_t APS5_VABI sceDeviceServiceInitialize(std::int32_t mode, std::int32_t flags) { +std::int32_t APS5_VABI sceDeviceServiceInitialize(std::int32_t mode, const DeviceServiceInitParam* param) { (void)mode; - (void)flags; + if (param != nullptr) { + if (param->size != sizeof(DeviceServiceInitParam) || param->reserved != 0) return static_cast(0x809B0080u); + if (param->threadParam != nullptr) { + if (param->threadParam->reserved != 0) return static_cast(0x809B0080u); + throw std::runtime_error("sceDeviceServiceInitialize: custom worker scheduling is not implemented"); + } + } return 0; } @@ -17,20 +30,35 @@ std::int32_t APS5_VABI sceDeviceServiceTerminate() { return 0; } -std::int32_t APS5_VABI sceDeviceServiceGetEventState(std::int32_t deviceType) { - (void)deviceType; +std::int32_t APS5_VABI sceDeviceServiceGetEventState(std::int32_t clear) { + (void)clear; return 0; } -std::int32_t APS5_VABI sceDeviceServiceQueryDeviceInfo_(std::int32_t deviceClass, std::int32_t arg1, std::int32_t arg2, void* infos, std::int32_t maxInfos, std::int32_t* count, std::int32_t* reserved, std::uint64_t infoSize) { - (void)deviceClass; - (void)arg1; +std::int32_t APS5_VABI sceDeviceServiceQueryDeviceInfo_(std::int32_t deviceClass, const DeviceServiceUsageFilter* filter, std::int32_t arg2, void* infos, std::int32_t maxInfos, std::int32_t* count, std::int32_t* reserved, std::uint32_t infoSize) { (void)arg2; - (void)infos; (void)reserved; - (void)infoSize; - if (count == nullptr || maxInfos < 0) throw std::invalid_argument(std::string(__func__) + ": invalid argument"); - *count = 0; + constexpr auto invalidArgument = static_cast(0x809B0080u); + switch (deviceClass) { + case 0x1001: + case 0x2001: + case 0x3001: + case 0x4001: + case 0x4002: + case 0x5001: + case 0x6001: + case 0x7001: + case 0x9001: + if (filter != nullptr) return invalidArgument; + break; + case 0x8001: + if (filter == nullptr || filter->minimumUsage > filter->maximumUsage) return invalidArgument; + break; + default: + return invalidArgument; + } + if (infoSize != 112 || maxInfos < 0 || (maxInfos != 0 && infos == nullptr)) return invalidArgument; + if (count != nullptr) *count = 0; return 0; } diff --git a/core/libs/prx/libSceMbus/PendingExports.cpp b/core/libs/prx/libSceMbus/PendingExports.cpp index 907eeb12c0..d5f6a7f050 100644 --- a/core/libs/prx/libSceMbus/PendingExports.cpp +++ b/core/libs/prx/libSceMbus/PendingExports.cpp @@ -1,8 +1,17 @@ +#include "prx/libSceMbus/include/Api.hpp" #include "prx/libc/include/General.hpp" +#include + +static_assert(sizeof(MbusDeviceFunctionState) == 180); + +static_assert(sizeof(MbusDeviceCondition) == 256); + +static_assert(sizeof(MbusEventCallbacks) == 16); + extern "C" { -int APS5_VABI sceMbusAcquireControl() { +int APS5_VABI sceMbusAcquireControl(std::uint64_t control, std::int32_t first, std::int32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -11,57 +20,57 @@ int APS5_VABI sceMbusAcquireControlList() { return static_cast(0x809B00FF); } -int APS5_VABI sceMbusAcquireControlWithState() { +int APS5_VABI sceMbusAcquireControlWithState(std::uint32_t deviceClass, std::int32_t state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusAcquireControlWithState2() { +int APS5_VABI sceMbusAcquireControlWithState2(std::uint32_t deviceClass, std::int32_t moduleId, std::int32_t state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusAcquireControlWithStateFlag() { +int APS5_VABI sceMbusAcquireControlWithStateFlag(std::uint32_t deviceClass, std::uint32_t moduleId, std::uint32_t state, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusAddHandle() { +int APS5_VABI sceMbusAddHandle(std::uint32_t deviceClass, std::int32_t handle, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusAddHandleByDeviceId() { +int APS5_VABI sceMbusAddHandleByDeviceId(std::uint32_t deviceClass, std::int32_t handle, std::uint64_t deviceId, std::uint32_t first, std::uint32_t second, std::uint32_t third) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusAddHandleByUserId() { +int APS5_VABI sceMbusAddHandleByUserId(std::uint32_t deviceClass, std::int32_t handle, std::int32_t userId, std::int32_t first, std::int32_t second, std::int32_t third) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusAddHandleByVidPid() { +int APS5_VABI sceMbusAddHandleByVidPid(std::uint32_t deviceClass, std::int32_t handle, std::int32_t first, std::uint16_t vendorId, std::uint16_t productId, std::int32_t second, std::int32_t third) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusAddHandleWithModuleId() { +int APS5_VABI sceMbusAddHandleWithModuleId(std::uint32_t deviceClass, std::int32_t handle, std::uint32_t flags, std::uint32_t moduleId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusAttachToApplicationByPid() { +int APS5_VABI sceMbusAttachToApplicationByPid(std::int32_t processId, std::int32_t deviceClass, std::int32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusBindDeviceWithUserId() { +int APS5_VABI sceMbusBindDeviceWithUserId(std::uint64_t deviceId, std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusChangeAppModuleState() { +int APS5_VABI sceMbusChangeAppModuleState(std::uint32_t deviceClass, std::int32_t moduleId, std::uint32_t state) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -71,157 +80,157 @@ int APS5_VABI sceMbusCheckDriverCallback() { return 0; } -int APS5_VABI sceMbusConvertToLocalDeviceId() { +int APS5_VABI sceMbusConvertToLocalDeviceId(std::uint64_t deviceId, std::uint32_t* localId, std::uint32_t* deviceClass) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusConvertToLocalDeviceId2() { +int APS5_VABI sceMbusConvertToLocalDeviceId2(std::uint64_t deviceId, std::uint32_t deviceClass, std::uint64_t* localId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusConvertToMbusDeviceId() { +int APS5_VABI sceMbusConvertToMbusDeviceId(std::uint64_t localId, std::uint32_t deviceClass, std::uint64_t* deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugAcquireControl() { +int APS5_VABI sceMbusDebugAcquireControl(std::uint64_t control, std::int32_t first, std::int32_t second, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugAcquireControlWithState() { +int APS5_VABI sceMbusDebugAcquireControlWithState(std::uint32_t deviceClass, std::int32_t state, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugAcquireControlWithState2() { +int APS5_VABI sceMbusDebugAcquireControlWithState2(std::uint32_t deviceClass, std::int32_t moduleId, std::int32_t state, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugAcquireControlWithStateFlag() { +int APS5_VABI sceMbusDebugAcquireControlWithStateFlag(std::uint32_t deviceClass, std::uint32_t moduleId, std::uint32_t state, std::uint32_t flags, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugAddProcess() { +int APS5_VABI sceMbusDebugAddProcess(std::int32_t processId, std::int32_t flags, const MbusProcessInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugCheckProcessResume() { +int APS5_VABI sceMbusDebugCheckProcessResume(MbusProcessInfo* info, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugDisableBgmForShellUi() { +int APS5_VABI sceMbusDebugDisableBgmForShellUi(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugGetApplicationStartupInfo() { +int APS5_VABI sceMbusDebugGetApplicationStartupInfo(std::uint32_t appId, const MbusProcessInfo* info, MbusStartupInfo* startupInfo) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugGetControlStatus() { +int APS5_VABI sceMbusDebugGetControlStatus(std::int32_t deviceClass, std::int32_t processId, MbusControlStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugGetDeviceInfo() { +int APS5_VABI sceMbusDebugGetDeviceInfo(std::uint64_t deviceClass, MbusDebugDeviceInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugGetInternalInfo() { +int APS5_VABI sceMbusDebugGetInternalInfo(MbusInternalInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugGetPriorityInfo() { +int APS5_VABI sceMbusDebugGetPriorityInfo(std::uint32_t deviceClass, MbusPriorityInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugReenableBgmForShellUi() { +int APS5_VABI sceMbusDebugReenableBgmForShellUi(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugReleaseControl() { +int APS5_VABI sceMbusDebugReleaseControl(std::uint64_t control, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugRemoveCameraAppModuleFocus() { +int APS5_VABI sceMbusDebugRemoveCameraAppModuleFocus(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugResumeApplication() { +int APS5_VABI sceMbusDebugResumeApplication(std::int32_t appId, std::uint8_t firstFlag, std::uint8_t secondFlag, MbusProcessInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugSetApplicationFocusByAppId() { +int APS5_VABI sceMbusDebugSetApplicationFocusByAppId(std::uint32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugSetCameraAppModuleFocus() { +int APS5_VABI sceMbusDebugSetCameraAppModuleFocus(std::int32_t processId, std::int32_t moduleId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugSetControllerFocusByAppId() { +int APS5_VABI sceMbusDebugSetControllerFocusByAppId(std::uint32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugSetOtherProcessExcludedAction() { +int APS5_VABI sceMbusDebugSetOtherProcessExcludedAction(std::uint64_t control, std::uint32_t action, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugSetPriority() { +int APS5_VABI sceMbusDebugSetPriority(std::uint64_t control, std::uint32_t priority, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugStartApplication() { +int APS5_VABI sceMbusDebugStartApplication(std::int32_t appId, std::int32_t first, std::int32_t second, std::uint8_t firstFlag, std::uint8_t secondFlag, const MbusStartupParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugStartApplication2() { +int APS5_VABI sceMbusDebugStartApplication2(std::int32_t appId, std::int32_t first, std::int32_t second, std::uint8_t firstFlag, std::uint8_t secondFlag, const MbusStartupParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugStartApplicationNull() { +int APS5_VABI sceMbusDebugStartApplicationNull(std::int32_t appId, std::int32_t first, std::int32_t second, std::uint8_t firstFlag, std::uint8_t secondFlag) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugSuspendApplication() { +int APS5_VABI sceMbusDebugSuspendApplication(std::uint32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugTerminateApplication() { +int APS5_VABI sceMbusDebugTerminateApplication(std::uint32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDebugTerminateProcess() { +int APS5_VABI sceMbusDebugTerminateProcess(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDetachFromApplicationByPid() { +int APS5_VABI sceMbusDetachFromApplicationByPid(std::int32_t processId, std::int32_t deviceClass) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -231,12 +240,12 @@ int APS5_VABI sceMbusDisableBgmForShellUi() { return 0; } -int APS5_VABI sceMbusDisableChat() { +int APS5_VABI sceMbusDisableChat(std::uint32_t deviceClass) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDisconnectDevice() { +int APS5_VABI sceMbusDisconnectDevice(std::uint64_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -251,17 +260,17 @@ int APS5_VABI sceMbusDumpDeviceInfo() { return 0; } -int APS5_VABI sceMbusDumpHandleStatusInfo() { +int APS5_VABI sceMbusDumpHandleStatusInfo(std::uint64_t control, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDumpHandleStatusInfoAll() { +int APS5_VABI sceMbusDumpHandleStatusInfoAll(std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusDumpModuleStateInfo() { +int APS5_VABI sceMbusDumpModuleStateInfo(std::uint64_t control) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -281,7 +290,7 @@ int APS5_VABI sceMbusDumpPortHandleInfo() { return 0; } -int APS5_VABI sceMbusDumpProcMediaInfo() { +int APS5_VABI sceMbusDumpProcMediaInfo(std::uint64_t control) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -296,92 +305,92 @@ int APS5_VABI sceMbusDumpUserMediaInfo() { return 0; } -int APS5_VABI sceMbusEnableChat() { +int APS5_VABI sceMbusEnableChat(std::uint32_t deviceClass) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusEnterAudioOutAutoAttenuation() { +int APS5_VABI sceMbusEnterAudioOutAutoAttenuation(std::int32_t first, std::int32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusEventBusStatusChangeSubscribe() { +int APS5_VABI sceMbusEventBusStatusChangeSubscribe(std::int32_t eventHandle, std::uint32_t first, std::uint32_t second, std::uint64_t firstValue, std::uint64_t secondValue) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusEventBusStatusChangeUnsubscribe() { +int APS5_VABI sceMbusEventBusStatusChangeUnsubscribe(std::uint64_t unusedSlot, std::int32_t subscription) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusEventCallbackFuncsInit_() { +int APS5_VABI sceMbusEventCallbackFuncsInit_(MbusEventCallbacks* callbacks, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusEventCreate_() { +int APS5_VABI sceMbusEventCreate_(std::uint32_t flags, MbusEvent* buffer, std::uint32_t capacity, const MbusEventCallbacks* callbacks, void* userData) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusEventDestroy() { +int APS5_VABI sceMbusEventDestroy(std::int32_t eventHandle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusEventReceive() { +int APS5_VABI sceMbusEventReceive(std::int32_t eventHandle, MbusEvent* events, std::int32_t capacity, std::int32_t wait) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetControlStatus() { +int APS5_VABI sceMbusGetControlStatus(std::uint64_t control, MbusControlStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetDeviceDescription() { +int APS5_VABI sceMbusGetDeviceDescription(std::uint64_t deviceId, std::uint32_t selector, MbusDeviceDescription* description) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetDeviceInfoByBusId_() { +int APS5_VABI sceMbusGetDeviceInfoByBusId_(std::int32_t busId, MbusDeviceInfo* info, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetDeviceInfoByConditionForDeviceService() { +int APS5_VABI sceMbusGetDeviceInfoByConditionForDeviceService(std::int32_t flags, MbusDeviceServiceInfo* infos, std::int32_t capacity, std::int32_t* first, std::int32_t* second, MbusDeviceCondition condition) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetDeviceInfoByCondition_() { +int APS5_VABI sceMbusGetDeviceInfoByCondition_(const MbusDeviceCondition* condition, std::int32_t flags, MbusDeviceInfo* infos, std::int32_t capacity, std::int32_t* first, std::int32_t* second, std::uint32_t infoSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetDeviceInfo_() { +int APS5_VABI sceMbusGetDeviceInfo_(std::uint64_t deviceId, MbusDeviceInfo* info, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetSimulatedBusUsableStatusByBusType() { +int APS5_VABI sceMbusGetSimulatedBusUsableStatusByBusType(std::uint32_t busType, std::int32_t first, std::uint64_t deviceId, std::uint32_t second, std::uint32_t third) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetSimulatedHandleStatusInfo() { +int APS5_VABI sceMbusGetSimulatedHandleStatusInfo(std::int32_t deviceClass, std::int32_t first, std::int32_t second, MbusSimulatedHandleStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetSimulatedHandleStatusInfo2() { +int APS5_VABI sceMbusGetSimulatedHandleStatusInfo2(std::uint32_t deviceClass, std::uint32_t first, std::uint32_t second, std::uint32_t third, MbusSimulatedHandleStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusGetSparkState() { +int APS5_VABI sceMbusGetSparkState(std::uint64_t deviceId, MbusSparkState* state) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -396,22 +405,22 @@ int APS5_VABI sceMbusIsBgmProhibited() { return 0; } -int APS5_VABI sceMbusIsUsingDevice() { +int APS5_VABI sceMbusIsUsingDevice(std::int32_t deviceClass, std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusLeaveAudioOutAutoAttenuation() { +int APS5_VABI sceMbusLeaveAudioOutAutoAttenuation(std::int32_t first, std::int32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusNotifyBtDisconnection() { +int APS5_VABI sceMbusNotifyBtDisconnection(std::uint64_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusNotifyDriverEvent() { +int APS5_VABI sceMbusNotifyDriverEvent(std::int32_t driverId, std::int32_t eventId, std::uint64_t firstValue, std::int32_t flags, std::uint64_t secondValue) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -421,12 +430,12 @@ int APS5_VABI sceMbusReenableBgmForShellUi() { return 0; } -int APS5_VABI sceMbusRegisterDriver() { +int APS5_VABI sceMbusRegisterDriver(std::int32_t driverId, const MbusDriverCallbacks* callbacks, void* userData) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusReleaseControl() { +int APS5_VABI sceMbusReleaseControl(std::uint64_t control) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -436,102 +445,102 @@ int APS5_VABI sceMbusRemoveCameraAppModuleFocus() { return 0; } -int APS5_VABI sceMbusRemoveHandle() { +int APS5_VABI sceMbusRemoveHandle(std::int32_t deviceClass, std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusRemoveProxy() { +int APS5_VABI sceMbusRemoveProxy(std::int32_t processId, std::uint32_t deviceClass, std::uint64_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusResolveByDeviceId() { +int APS5_VABI sceMbusResolveByDeviceId(std::int32_t deviceClass, std::uint64_t deviceId, std::uint32_t first, std::uint32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusResolveByHandle() { +int APS5_VABI sceMbusResolveByHandle(std::int32_t deviceClass, std::int32_t handle, std::int32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusResolveByPlayerId() { +int APS5_VABI sceMbusResolveByPlayerId(std::uint32_t deviceClass, std::uint32_t playerId, std::uint32_t first, std::uint32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusResolveByUserId() { +int APS5_VABI sceMbusResolveByUserId(std::uint32_t deviceClass, std::int32_t userId, std::uint32_t first, std::uint32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetAutoLoginMode() { +int APS5_VABI sceMbusSetAutoLoginMode(std::uint32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetCameraAppModuleFocus() { +int APS5_VABI sceMbusSetCameraAppModuleFocus(std::int32_t moduleId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetDeviceFunctionState() { +int APS5_VABI sceMbusSetDeviceFunctionState(std::uint64_t deviceId, MbusDeviceFunctionState state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetDuckingSetting() { +int APS5_VABI sceMbusSetDuckingSetting(std::int32_t setting) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetProxy() { +int APS5_VABI sceMbusSetProxy(std::int32_t processId, std::int32_t first, std::int32_t second, std::int32_t third, std::uint64_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetRoutingMode() { +int APS5_VABI sceMbusSetRoutingMode(std::int32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetScratchData() { +int APS5_VABI sceMbusSetScratchData(std::uint64_t deviceId, std::int32_t first, std::int32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetScratchDataUpdatedEventMask() { +int APS5_VABI sceMbusSetScratchDataUpdatedEventMask(std::uint32_t mask) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetSocialScreenAudioMode() { +int APS5_VABI sceMbusSetSocialScreenAudioMode(std::int32_t first, std::int32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetTestFlag() { +int APS5_VABI sceMbusSetTestFlag(std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetThreadParam() { +int APS5_VABI sceMbusSetThreadParam(std::int32_t priority, std::uint64_t affinityMask, std::uint64_t reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetUsbPowerBlackList() { +int APS5_VABI sceMbusSetUsbPowerBlackList(std::int32_t first, std::uint8_t count, const MbusUsbPowerEntry* entries) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetUserMuteStatus() { +int APS5_VABI sceMbusSetUserMuteStatus(std::int32_t userId, std::int32_t status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusSetVolumeDown() { +int APS5_VABI sceMbusSetVolumeDown(std::uint32_t deviceClass, std::uint8_t volume) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -541,7 +550,7 @@ int APS5_VABI sceMbusStartAudioOutAuxBroadcast() { return 0; } -int APS5_VABI sceMbusStartAudioOutSharePlay() { +int APS5_VABI sceMbusStartAudioOutSharePlay(std::int32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -560,17 +569,17 @@ int APS5_VABI sceMbusTerm() { return 0; } -int APS5_VABI sceMbusUnregisterDriver() { +int APS5_VABI sceMbusUnregisterDriver(std::int32_t driverId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusUserLogin() { +int APS5_VABI sceMbusUserLogin(std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMbusUserLogout() { +int APS5_VABI sceMbusUserLogout(std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceMbus/include/Api.hpp b/core/libs/prx/libSceMbus/include/Api.hpp new file mode 100644 index 0000000000..d31c99a8aa --- /dev/null +++ b/core/libs/prx/libSceMbus/include/Api.hpp @@ -0,0 +1,192 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include + +struct DeviceServiceThreadParam { + std::uint64_t affinityMask; + std::int32_t priority; + std::uint32_t reserved; +}; + +struct DeviceServiceInitParam { + std::uint32_t size; + std::uint32_t reserved; + const DeviceServiceThreadParam* threadParam; +}; + +struct DeviceServiceUsageFilter { + std::uint16_t usagePage; + std::uint16_t minimumUsage; + std::uint16_t maximumUsage; +}; + +struct MbusDeviceFunctionState { + std::byte data[180]; +}; + +struct MbusDeviceDescription; + +struct MbusDeviceInfo; + +struct MbusSimulatedHandleStatus; + +struct MbusSparkState; + +struct MbusDriverCallbacks; + +struct MbusUsbPowerEntry; + +struct MbusEvent; + +struct alignas(8) MbusDeviceCondition { + std::byte data[256]; +}; + +struct MbusDeviceServiceInfo; + +struct MbusEventCallbacks { + void (APS5_VABI *onOverflow)(std::int32_t eventHandle, void* userData); + void (APS5_VABI *onEvent)(std::int32_t eventHandle, void* userData, const MbusEvent* event); +}; + +struct MbusProcessInfo; + +struct MbusStartupInfo; + +struct MbusStartupParam; + +struct MbusControlStatus; + +struct MbusDebugDeviceInfo; + +struct MbusInternalInfo; + +struct MbusPriorityInfo; + +extern "C" { + +std::int32_t APS5_VABI sceDeviceServiceInitialize(std::int32_t mode, const DeviceServiceInitParam* param); +std::int32_t APS5_VABI sceDeviceServiceTerminate(); +std::int32_t APS5_VABI sceDeviceServiceGetEventState(std::int32_t clear); +std::int32_t APS5_VABI sceDeviceServiceQueryDeviceInfo_(std::int32_t deviceClass, const DeviceServiceUsageFilter* filter, std::int32_t arg2, void* infos, std::int32_t maxInfos, std::int32_t* count, std::int32_t* reserved, std::uint32_t infoSize); +std::int32_t APS5_VABI sceDeviceServiceGetGeneration(void); +int APS5_VABI sceMbusAcquireControl(std::uint64_t control, std::int32_t first, std::int32_t second); +int APS5_VABI sceMbusAcquireControlList(); +int APS5_VABI sceMbusAcquireControlWithState(std::uint32_t deviceClass, std::int32_t state); +int APS5_VABI sceMbusAcquireControlWithState2(std::uint32_t deviceClass, std::int32_t moduleId, std::int32_t state); +int APS5_VABI sceMbusAcquireControlWithStateFlag(std::uint32_t deviceClass, std::uint32_t moduleId, std::uint32_t state, std::uint32_t flags); +int APS5_VABI sceMbusAddHandle(std::uint32_t deviceClass, std::int32_t handle, std::uint32_t flags); +int APS5_VABI sceMbusAddHandleByDeviceId(std::uint32_t deviceClass, std::int32_t handle, std::uint64_t deviceId, std::uint32_t first, std::uint32_t second, std::uint32_t third); +int APS5_VABI sceMbusAddHandleByUserId(std::uint32_t deviceClass, std::int32_t handle, std::int32_t userId, std::int32_t first, std::int32_t second, std::int32_t third); +int APS5_VABI sceMbusAddHandleByVidPid(std::uint32_t deviceClass, std::int32_t handle, std::int32_t first, std::uint16_t vendorId, std::uint16_t productId, std::int32_t second, std::int32_t third); +int APS5_VABI sceMbusAddHandleWithModuleId(std::uint32_t deviceClass, std::int32_t handle, std::uint32_t flags, std::uint32_t moduleId); +int APS5_VABI sceMbusAttachToApplicationByPid(std::int32_t processId, std::int32_t deviceClass, std::int32_t flags); +int APS5_VABI sceMbusBindDeviceWithUserId(std::uint64_t deviceId, std::int32_t userId); +int APS5_VABI sceMbusChangeAppModuleState(std::uint32_t deviceClass, std::int32_t moduleId, std::uint32_t state); +int APS5_VABI sceMbusCheckDriverCallback(); +int APS5_VABI sceMbusConvertToLocalDeviceId(std::uint64_t deviceId, std::uint32_t* localId, std::uint32_t* deviceClass); +int APS5_VABI sceMbusConvertToLocalDeviceId2(std::uint64_t deviceId, std::uint32_t deviceClass, std::uint64_t* localId); +int APS5_VABI sceMbusConvertToMbusDeviceId(std::uint64_t localId, std::uint32_t deviceClass, std::uint64_t* deviceId); +int APS5_VABI sceMbusDebugAcquireControl(std::uint64_t control, std::int32_t first, std::int32_t second, std::int32_t processId); +int APS5_VABI sceMbusDebugAcquireControlWithState(std::uint32_t deviceClass, std::int32_t state, std::int32_t processId); +int APS5_VABI sceMbusDebugAcquireControlWithState2(std::uint32_t deviceClass, std::int32_t moduleId, std::int32_t state, std::int32_t processId); +int APS5_VABI sceMbusDebugAcquireControlWithStateFlag(std::uint32_t deviceClass, std::uint32_t moduleId, std::uint32_t state, std::uint32_t flags, std::int32_t processId); +int APS5_VABI sceMbusDebugAddProcess(std::int32_t processId, std::int32_t flags, const MbusProcessInfo* info); +int APS5_VABI sceMbusDebugCheckProcessResume(MbusProcessInfo* info, std::uint32_t flags); +int APS5_VABI sceMbusDebugDisableBgmForShellUi(std::int32_t processId); +int APS5_VABI sceMbusDebugGetApplicationStartupInfo(std::uint32_t appId, const MbusProcessInfo* info, MbusStartupInfo* startupInfo); +int APS5_VABI sceMbusDebugGetControlStatus(std::int32_t deviceClass, std::int32_t processId, MbusControlStatus* status); +int APS5_VABI sceMbusDebugGetDeviceInfo(std::uint64_t deviceClass, MbusDebugDeviceInfo* info); +int APS5_VABI sceMbusDebugGetInternalInfo(MbusInternalInfo* info); +int APS5_VABI sceMbusDebugGetPriorityInfo(std::uint32_t deviceClass, MbusPriorityInfo* info); +int APS5_VABI sceMbusDebugReenableBgmForShellUi(std::int32_t processId); +int APS5_VABI sceMbusDebugReleaseControl(std::uint64_t control, std::int32_t processId); +int APS5_VABI sceMbusDebugRemoveCameraAppModuleFocus(std::int32_t processId); +int APS5_VABI sceMbusDebugResumeApplication(std::int32_t appId, std::uint8_t firstFlag, std::uint8_t secondFlag, MbusProcessInfo* info); +int APS5_VABI sceMbusDebugSetApplicationFocusByAppId(std::uint32_t appId); +int APS5_VABI sceMbusDebugSetCameraAppModuleFocus(std::int32_t processId, std::int32_t moduleId); +int APS5_VABI sceMbusDebugSetControllerFocusByAppId(std::uint32_t appId); +int APS5_VABI sceMbusDebugSetOtherProcessExcludedAction(std::uint64_t control, std::uint32_t action, std::int32_t processId); +int APS5_VABI sceMbusDebugSetPriority(std::uint64_t control, std::uint32_t priority, std::int32_t processId); +int APS5_VABI sceMbusDebugStartApplication(std::int32_t appId, std::int32_t first, std::int32_t second, std::uint8_t firstFlag, std::uint8_t secondFlag, const MbusStartupParam* param); +int APS5_VABI sceMbusDebugStartApplication2(std::int32_t appId, std::int32_t first, std::int32_t second, std::uint8_t firstFlag, std::uint8_t secondFlag, const MbusStartupParam* param); +int APS5_VABI sceMbusDebugStartApplicationNull(std::int32_t appId, std::int32_t first, std::int32_t second, std::uint8_t firstFlag, std::uint8_t secondFlag); +int APS5_VABI sceMbusDebugSuspendApplication(std::uint32_t appId); +int APS5_VABI sceMbusDebugTerminateApplication(std::uint32_t appId); +int APS5_VABI sceMbusDebugTerminateProcess(std::int32_t processId); +int APS5_VABI sceMbusDetachFromApplicationByPid(std::int32_t processId, std::int32_t deviceClass); +int APS5_VABI sceMbusDisableBgmForShellUi(); +int APS5_VABI sceMbusDisableChat(std::uint32_t deviceClass); +int APS5_VABI sceMbusDisconnectDevice(std::uint64_t deviceId); +int APS5_VABI sceMbusDumpAppInfo(); +int APS5_VABI sceMbusDumpDeviceInfo(); +int APS5_VABI sceMbusDumpHandleStatusInfo(std::uint64_t control, std::int32_t processId); +int APS5_VABI sceMbusDumpHandleStatusInfoAll(std::uint32_t flags); +int APS5_VABI sceMbusDumpModuleStateInfo(std::uint64_t control); +int APS5_VABI sceMbusDumpModuleStateInfoAll(); +int APS5_VABI sceMbusDumpPlayerInfo(); +int APS5_VABI sceMbusDumpPortHandleInfo(); +int APS5_VABI sceMbusDumpProcMediaInfo(std::uint64_t control); +int APS5_VABI sceMbusDumpProcMediaInfoAll(); +int APS5_VABI sceMbusDumpUserMediaInfo(); +int APS5_VABI sceMbusEnableChat(std::uint32_t deviceClass); +int APS5_VABI sceMbusEnterAudioOutAutoAttenuation(std::int32_t first, std::int32_t second); +int APS5_VABI sceMbusEventBusStatusChangeSubscribe(std::int32_t eventHandle, std::uint32_t first, std::uint32_t second, std::uint64_t firstValue, std::uint64_t secondValue); +int APS5_VABI sceMbusEventBusStatusChangeUnsubscribe(std::uint64_t unusedSlot, std::int32_t subscription); +int APS5_VABI sceMbusEventCallbackFuncsInit_(MbusEventCallbacks* callbacks, std::uint32_t size); +int APS5_VABI sceMbusEventCreate_(std::uint32_t flags, MbusEvent* buffer, std::uint32_t capacity, const MbusEventCallbacks* callbacks, void* userData); +int APS5_VABI sceMbusEventDestroy(std::int32_t eventHandle); +int APS5_VABI sceMbusEventReceive(std::int32_t eventHandle, MbusEvent* events, std::int32_t capacity, std::int32_t wait); +int APS5_VABI sceMbusGetControlStatus(std::uint64_t control, MbusControlStatus* status); +int APS5_VABI sceMbusGetDeviceDescription(std::uint64_t deviceId, std::uint32_t selector, MbusDeviceDescription* description); +int APS5_VABI sceMbusGetDeviceInfoByBusId_(std::int32_t busId, MbusDeviceInfo* info, std::uint32_t size); +int APS5_VABI sceMbusGetDeviceInfoByConditionForDeviceService(std::int32_t flags, MbusDeviceServiceInfo* infos, std::int32_t capacity, std::int32_t* first, std::int32_t* second, MbusDeviceCondition condition); +int APS5_VABI sceMbusGetDeviceInfoByCondition_(const MbusDeviceCondition* condition, std::int32_t flags, MbusDeviceInfo* infos, std::int32_t capacity, std::int32_t* first, std::int32_t* second, std::uint32_t infoSize); +int APS5_VABI sceMbusGetDeviceInfo_(std::uint64_t deviceId, MbusDeviceInfo* info, std::uint32_t size); +int APS5_VABI sceMbusGetSimulatedBusUsableStatusByBusType(std::uint32_t busType, std::int32_t first, std::uint64_t deviceId, std::uint32_t second, std::uint32_t third); +int APS5_VABI sceMbusGetSimulatedHandleStatusInfo(std::int32_t deviceClass, std::int32_t first, std::int32_t second, MbusSimulatedHandleStatus* status); +int APS5_VABI sceMbusGetSimulatedHandleStatusInfo2(std::uint32_t deviceClass, std::uint32_t first, std::uint32_t second, std::uint32_t third, MbusSimulatedHandleStatus* status); +int APS5_VABI sceMbusGetSparkState(std::uint64_t deviceId, MbusSparkState* state); +int APS5_VABI sceMbusInit(); +int APS5_VABI sceMbusIsBgmProhibited(); +int APS5_VABI sceMbusIsUsingDevice(std::int32_t deviceClass, std::int32_t handle); +int APS5_VABI sceMbusLeaveAudioOutAutoAttenuation(std::int32_t first, std::int32_t second); +int APS5_VABI sceMbusNotifyBtDisconnection(std::uint64_t deviceId); +int APS5_VABI sceMbusNotifyDriverEvent(std::int32_t driverId, std::int32_t eventId, std::uint64_t firstValue, std::int32_t flags, std::uint64_t secondValue); +int APS5_VABI sceMbusReenableBgmForShellUi(); +int APS5_VABI sceMbusRegisterDriver(std::int32_t driverId, const MbusDriverCallbacks* callbacks, void* userData); +int APS5_VABI sceMbusReleaseControl(std::uint64_t control); +int APS5_VABI sceMbusRemoveCameraAppModuleFocus(); +int APS5_VABI sceMbusRemoveHandle(std::int32_t deviceClass, std::int32_t handle); +int APS5_VABI sceMbusRemoveProxy(std::int32_t processId, std::uint32_t deviceClass, std::uint64_t deviceId); +int APS5_VABI sceMbusResolveByDeviceId(std::int32_t deviceClass, std::uint64_t deviceId, std::uint32_t first, std::uint32_t second); +int APS5_VABI sceMbusResolveByHandle(std::int32_t deviceClass, std::int32_t handle, std::int32_t flags); +int APS5_VABI sceMbusResolveByPlayerId(std::uint32_t deviceClass, std::uint32_t playerId, std::uint32_t first, std::uint32_t second); +int APS5_VABI sceMbusResolveByUserId(std::uint32_t deviceClass, std::int32_t userId, std::uint32_t first, std::uint32_t second); +int APS5_VABI sceMbusSetAutoLoginMode(std::uint32_t mode); +int APS5_VABI sceMbusSetCameraAppModuleFocus(std::int32_t moduleId); +int APS5_VABI sceMbusSetDeviceFunctionState(std::uint64_t deviceId, MbusDeviceFunctionState state); +int APS5_VABI sceMbusSetDuckingSetting(std::int32_t setting); +int APS5_VABI sceMbusSetProxy(std::int32_t processId, std::int32_t first, std::int32_t second, std::int32_t third, std::uint64_t deviceId); +int APS5_VABI sceMbusSetRoutingMode(std::int32_t mode); +int APS5_VABI sceMbusSetScratchData(std::uint64_t deviceId, std::int32_t first, std::int32_t second); +int APS5_VABI sceMbusSetScratchDataUpdatedEventMask(std::uint32_t mask); +int APS5_VABI sceMbusSetSocialScreenAudioMode(std::int32_t first, std::int32_t second); +int APS5_VABI sceMbusSetTestFlag(std::uint32_t flags); +int APS5_VABI sceMbusSetThreadParam(std::int32_t priority, std::uint64_t affinityMask, std::uint64_t reserved); +int APS5_VABI sceMbusSetUsbPowerBlackList(std::int32_t first, std::uint8_t count, const MbusUsbPowerEntry* entries); +int APS5_VABI sceMbusSetUserMuteStatus(std::int32_t userId, std::int32_t status); +int APS5_VABI sceMbusSetVolumeDown(std::uint32_t deviceClass, std::uint8_t volume); +int APS5_VABI sceMbusStartAudioOutAuxBroadcast(); +int APS5_VABI sceMbusStartAudioOutSharePlay(std::int32_t mode); +int APS5_VABI sceMbusStopAudioOutAuxBroadcast(); +int APS5_VABI sceMbusStopAudioOutSharePlay(); +int APS5_VABI sceMbusTerm(); +int APS5_VABI sceMbusUnregisterDriver(std::int32_t driverId); +int APS5_VABI sceMbusUserLogin(std::int32_t userId); +int APS5_VABI sceMbusUserLogout(std::int32_t userId); + +} diff --git a/core/libs/prx/libSceMbus/libSceMbus.hpp b/core/libs/prx/libSceMbus/libSceMbus.hpp new file mode 100644 index 0000000000..e6b42950e3 --- /dev/null +++ b/core/libs/prx/libSceMbus/libSceMbus.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceMbus/include/Api.hpp" diff --git a/core/libs/prx/libSceMediaAppSharedStorage/libSceMediaAppSharedStorage.hpp b/core/libs/prx/libSceMediaAppSharedStorage/libSceMediaAppSharedStorage.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceMediaAppSharedStorage/libSceMediaAppSharedStorage.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceMediaFrameworkInterface/libSceMediaFrameworkInterface.hpp b/core/libs/prx/libSceMediaFrameworkInterface/libSceMediaFrameworkInterface.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceMediaFrameworkInterface/libSceMediaFrameworkInterface.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceMediaFrameworkUtil/libSceMediaFrameworkUtil.hpp b/core/libs/prx/libSceMediaFrameworkUtil/libSceMediaFrameworkUtil.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceMediaFrameworkUtil/libSceMediaFrameworkUtil.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceMetadataReaderWriter/libSceMetadataReaderWriter.hpp b/core/libs/prx/libSceMetadataReaderWriter/libSceMetadataReaderWriter.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceMetadataReaderWriter/libSceMetadataReaderWriter.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceMouse/Export.cpp b/core/libs/prx/libSceMouse/Export.cpp index 121b9d2e6a..1b1cd0f317 100644 --- a/core/libs/prx/libSceMouse/Export.cpp +++ b/core/libs/prx/libSceMouse/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMouse/include/Api.hpp" #include "prx/libSceMouse/include/MouseState.hpp" #include "prx/libc/include/General.hpp" diff --git a/core/libs/prx/libSceMouse/include/Api.hpp b/core/libs/prx/libSceMouse/include/Api.hpp new file mode 100644 index 0000000000..e37913bd10 --- /dev/null +++ b/core/libs/prx/libSceMouse/include/Api.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceMouseInit(); +int APS5_VABI sceMouseOpen(int userId, std::int32_t type, std::int32_t index, const void* param); +int APS5_VABI sceMouseClose(std::int32_t handle); +int APS5_VABI sceMouseRead(std::int32_t handle, MouseData* data, std::int32_t num); +int APS5_VABI sceMouseResetAbsolutePosition(std::int32_t handle, std::uint16_t x, std::uint16_t y); +int APS5_VABI sceMouseConnectPort(); +int APS5_VABI sceMouseDebugGetDeviceId(); +int APS5_VABI sceMouseDeviceOpen(); +int APS5_VABI sceMouseDisconnectDevice(); +int APS5_VABI sceMouseDisconnectPort(); +int APS5_VABI sceMouseGetDeviceInfo(); +int APS5_VABI sceMouseMbusInit(); +int APS5_VABI sceMouseSetHandType(); +int APS5_VABI sceMouseSetPointerSpeed(); +int APS5_VABI sceMouseSetProcessPrivilege(); + +} diff --git a/core/libs/prx/libSceMouse/libSceMouse.hpp b/core/libs/prx/libSceMouse/libSceMouse.hpp new file mode 100644 index 0000000000..512051b814 --- /dev/null +++ b/core/libs/prx/libSceMouse/libSceMouse.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceMouse/include/Api.hpp" diff --git a/core/libs/prx/libSceMouse/src/mouse_impl.cpp b/core/libs/prx/libSceMouse/src/mouse_impl.cpp index d73ccb929d..b4df78375a 100644 --- a/core/libs/prx/libSceMouse/src/mouse_impl.cpp +++ b/core/libs/prx/libSceMouse/src/mouse_impl.cpp @@ -82,10 +82,11 @@ int Read(std::int32_t handle, MouseData* data, std::int32_t num) { return available; } -int ResetAbsolutePosition(std::int32_t handle, std::uint16_t, std::uint16_t) { +int ResetAbsolutePosition(std::int32_t handle, std::uint16_t x, std::uint16_t y) { std::lock_guard lock(mouseMutex); if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + if (x >= 1920 || y >= 1080) return MOUSE_ERROR_INVALID_ARG; return MOUSE_OK; } diff --git a/core/libs/prx/libSceMouse/tests/Mouse.cpp b/core/libs/prx/libSceMouse/tests/Mouse.cpp index 3fd5a90ca2..27c99cc622 100644 --- a/core/libs/prx/libSceMouse/tests/Mouse.cpp +++ b/core/libs/prx/libSceMouse/tests/Mouse.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMouse/include/Api.hpp" #include "prx/libSceMouse/include/mouse_structs.h" #include "prx/libSceMouse/include/MouseState.hpp" #include "prx/libSceVideoOut/include/MouseInput.hpp" @@ -8,11 +9,7 @@ extern "C" { bool MouseIsOpen_nid_no_patch(); -int APS5_VABI sceMouseInit(); -int APS5_VABI sceMouseOpen(int, std::int32_t, std::int32_t, const void*); -int APS5_VABI sceMouseClose(std::int32_t); -int APS5_VABI sceMouseRead(std::int32_t, MouseData*, std::int32_t); -int APS5_VABI sceMouseResetAbsolutePosition(std::int32_t, std::uint16_t, std::uint16_t); + } static void Require(bool value) { if (!value) std::abort(); } @@ -34,6 +31,9 @@ int main() { Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_ERROR_ALREADY_OPENED); Require(sceMouseResetAbsolutePosition(42, 959, 539) == MOUSE_ERROR_INVALID_HANDLE); Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 959, 539) == MOUSE_OK); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 1919, 1079) == MOUSE_OK); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 1920, 1079) == MOUSE_ERROR_INVALID_ARG); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 1919, 1080) == MOUSE_ERROR_INVALID_ARG); Require(sceMouseRead(42, data, 1) == MOUSE_ERROR_INVALID_HANDLE); Require(sceMouseRead(MOUSE_HANDLE, nullptr, 1) == MOUSE_ERROR_INVALID_ARG); Require(sceMouseRead(MOUSE_HANDLE, data, 65) == MOUSE_ERROR_INVALID_ARG); diff --git a/core/libs/prx/libSceMove/Export.cpp b/core/libs/prx/libSceMove/Export.cpp index 2ffc51523e..b69438284d 100644 --- a/core/libs/prx/libSceMove/Export.cpp +++ b/core/libs/prx/libSceMove/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMove/include/Api.hpp" #include #include #include @@ -28,10 +29,12 @@ struct Slot { std::unordered_map openHandles; -int checkHandle(int32_t handle) { +int checkHandle(int32_t handle, bool validArguments = true) { std::lock_guard lock(stateMutex); if (!initialized) return MOVE_ERROR_NOT_INIT; - return openHandles.contains(handle) ? MOVE_OK : MOVE_ERROR_INVALID_HANDLE; + if (!validArguments) return MOVE_ERROR_INVALID_ARG; + if (!openHandles.contains(handle)) return MOVE_ERROR_INVALID_HANDLE; + return MOVE_OK; } } @@ -73,26 +76,22 @@ int APS5_VABI sceMoveClose(int32_t handle) { } int APS5_VABI sceMoveGetDeviceInfo(int32_t handle, void* info) { - if (const int result = checkHandle(handle); result != MOVE_OK) return result; - if (!info) return MOVE_ERROR_INVALID_ARG; + if (const int result = checkHandle(handle, info != nullptr); result != MOVE_OK) return result; return MOVE_NO_CONTROLLER_CONNECTED; } int APS5_VABI sceMoveGetExtensionPortInfo(int32_t handle, void* data) { - if (const int result = checkHandle(handle); result != MOVE_OK) return result; - if (!data) return MOVE_ERROR_INVALID_ARG; + if (const int result = checkHandle(handle, data != nullptr); result != MOVE_OK) return result; return MOVE_NO_CONTROLLER_CONNECTED; } int APS5_VABI sceMoveReadStateLatest(int32_t handle, void* data) { - if (const int result = checkHandle(handle); result != MOVE_OK) return result; - if (!data) return MOVE_ERROR_INVALID_ARG; + if (const int result = checkHandle(handle, data != nullptr); result != MOVE_OK) return result; return MOVE_NO_CONTROLLER_CONNECTED; } int APS5_VABI sceMoveReadStateRecent(int32_t handle, int64_t timestamp, void* data, int32_t* outCount) { - if (const int result = checkHandle(handle); result != MOVE_OK) return result; - if (timestamp < 0 || !data || !outCount) return MOVE_ERROR_INVALID_ARG; + if (const int result = checkHandle(handle, timestamp >= 0 && data != nullptr && outCount != nullptr); result != MOVE_OK) return result; return MOVE_NO_CONTROLLER_CONNECTED; } diff --git a/core/libs/prx/libSceMove/include/Api.hpp b/core/libs/prx/libSceMove/include/Api.hpp new file mode 100644 index 0000000000..5461d6d775 --- /dev/null +++ b/core/libs/prx/libSceMove/include/Api.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceMoveInit(void); +int APS5_VABI sceMoveTerm(void); +int APS5_VABI sceMoveOpen(int32_t userId, int32_t type, int32_t index); +int APS5_VABI sceMoveClose(int32_t handle); +int APS5_VABI sceMoveGetDeviceInfo(int32_t handle, void* info); +int APS5_VABI sceMoveGetExtensionPortInfo(int32_t handle, void* data); +int APS5_VABI sceMoveReadStateLatest(int32_t handle, void* data); +int APS5_VABI sceMoveReadStateRecent(int32_t handle, int64_t timestamp, void* data, int32_t* outCount); +int APS5_VABI sceMoveSetVibration(int32_t handle, uint8_t motor); +int APS5_VABI sceMoveSetLightSphere(int32_t handle, uint8_t red, uint8_t green, uint8_t blue); +int APS5_VABI sceMoveResetLightSphere(int32_t handle); +int APS5_VABI sceMoveGetBluetoothAddress(void); +int APS5_VABI sceMoveGetCalibrationInfo(void); +int APS5_VABI sceMoveGetDeviceId(void); +int APS5_VABI sceMoveIsReproductionModel(void); +int APS5_VABI sceMoveIsValidHandle(void); +int APS5_VABI sceMoveReadStateRecentForTracker(void); +int APS5_VABI sceMoveSetCalibrationMode(void); +int APS5_VABI sceMoveSetExtensionPortOutput(void); +int APS5_VABI sceMoveSetLightSphereForTracker(void); +int APS5_VABI sceMoveWriteCalibrationInfo(void); + +} diff --git a/core/libs/prx/libSceMove/libSceMove.hpp b/core/libs/prx/libSceMove/libSceMove.hpp new file mode 100644 index 0000000000..522d39cc61 --- /dev/null +++ b/core/libs/prx/libSceMove/libSceMove.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceMove/include/Api.hpp" diff --git a/core/libs/prx/libSceMoveTracker/Export.cpp b/core/libs/prx/libSceMoveTracker/Export.cpp index 8b2c497e09..e1c32fd781 100644 --- a/core/libs/prx/libSceMoveTracker/Export.cpp +++ b/core/libs/prx/libSceMoveTracker/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMoveTracker/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -10,17 +11,17 @@ int APS5_VABI sceMoveTrackerCalibrateReset() { return 0; } -int APS5_VABI sceMoveTrackerCalibrateYaw() { +int APS5_VABI sceMoveTrackerCalibrateYaw(std::int32_t handle, const float* direction) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMoveTrackerCameraUpdate() { +int APS5_VABI sceMoveTrackerCameraUpdate(const MoveTrackerCameraFrame* frames) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMoveTrackerCaptureInit() { +int APS5_VABI sceMoveTrackerCaptureInit(void* imageMemory, std::int32_t capacity, std::int32_t interval, const char* path, std::int32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -30,12 +31,12 @@ int APS5_VABI sceMoveTrackerCaptureTerm() { return 0; } -int APS5_VABI sceMoveTrackerControllersUpdate() { +int APS5_VABI sceMoveTrackerControllersUpdate(const MoveTrackerControllerInput* controllers) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMoveTrackerGetState() { +int APS5_VABI sceMoveTrackerGetState(std::int32_t handle, std::uint64_t timestamp, MoveTrackerState* state) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -44,22 +45,22 @@ int APS5_VABI sceMoveTrackerGetVersion() { return 2; } -int APS5_VABI sceMoveTrackerGetWorkingMemorySize() { +int APS5_VABI sceMoveTrackerGetWorkingMemorySize(std::uint32_t* totalSize, std::uint32_t* processingSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMoveTrackerInit() { +int APS5_VABI sceMoveTrackerInit(void* workMemory, std::uint64_t requiredArgument, std::uint32_t pipeId, std::uint32_t queueId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMoveTrackerPlayGetImages() { +int APS5_VABI sceMoveTrackerPlayGetImages(MoveTrackerCameraFrame* frames) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceMoveTrackerPlayGetStates() { +int APS5_VABI sceMoveTrackerPlayGetStates(MoveTrackerPlaybackInfo* info, MoveTrackerState* states) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceMoveTracker/include/Api.hpp b/core/libs/prx/libSceMoveTracker/include/Api.hpp new file mode 100644 index 0000000000..bfe72f92fd --- /dev/null +++ b/core/libs/prx/libSceMoveTracker/include/Api.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "SceTypes.hpp" + +struct MoveTrackerCameraFrame; + +struct MoveTrackerControllerInput; + +struct MoveTrackerPlaybackInfo; + +struct MoveTrackerState; + +extern "C" { + +int APS5_VABI sceMoveTrackerCalibrateReset(); +int APS5_VABI sceMoveTrackerCalibrateYaw(std::int32_t handle, const float* direction); +int APS5_VABI sceMoveTrackerCameraUpdate(const MoveTrackerCameraFrame* frames); +int APS5_VABI sceMoveTrackerCaptureInit(void* imageMemory, std::int32_t capacity, std::int32_t interval, const char* path, std::int32_t mode); +int APS5_VABI sceMoveTrackerCaptureTerm(); +int APS5_VABI sceMoveTrackerControllersUpdate(const MoveTrackerControllerInput* controllers); +int APS5_VABI sceMoveTrackerGetState(std::int32_t handle, std::uint64_t timestamp, MoveTrackerState* state); +int APS5_VABI sceMoveTrackerGetVersion(); +int APS5_VABI sceMoveTrackerGetWorkingMemorySize(std::uint32_t* totalSize, std::uint32_t* processingSize); +int APS5_VABI sceMoveTrackerInit(void* workMemory, std::uint64_t requiredArgument, std::uint32_t pipeId, std::uint32_t queueId); +int APS5_VABI sceMoveTrackerPlayGetImages(MoveTrackerCameraFrame* frames); +int APS5_VABI sceMoveTrackerPlayGetStates(MoveTrackerPlaybackInfo* info, MoveTrackerState* states); +int APS5_VABI sceMoveTrackerPlayStart(); +int APS5_VABI sceMoveTrackerPlayStep(); +int APS5_VABI sceMoveTrackerRecordSave(); +int APS5_VABI sceMoveTrackerRecordStart(); +int APS5_VABI sceMoveTrackerRecordStop(); +int APS5_VABI sceMoveTrackerTerm(); + +} diff --git a/core/libs/prx/libSceMoveTracker/libSceMoveTracker.hpp b/core/libs/prx/libSceMoveTracker/libSceMoveTracker.hpp new file mode 100644 index 0000000000..02c0750675 --- /dev/null +++ b/core/libs/prx/libSceMoveTracker/libSceMoveTracker.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceMoveTracker/include/Api.hpp" diff --git a/core/libs/prx/libSceMsgDialog.native/Export.cpp b/core/libs/prx/libSceMsgDialog.native/Export.cpp index 339bf0049a..52d1a12279 100644 --- a/core/libs/prx/libSceMsgDialog.native/Export.cpp +++ b/core/libs/prx/libSceMsgDialog.native/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMsgDialog.native/libSceMsgDialog.native.hpp" #include #include #include diff --git a/core/libs/prx/libSceMsgDialog.native/libSceMsgDialog.native.hpp b/core/libs/prx/libSceMsgDialog.native/libSceMsgDialog.native.hpp new file mode 100644 index 0000000000..ec8e5c89ab --- /dev/null +++ b/core/libs/prx/libSceMsgDialog.native/libSceMsgDialog.native.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include "prx/libSceMsgDialog/include/Api.hpp" + +extern "C" int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result); diff --git a/core/libs/prx/libSceMsgDialog/Export.cpp b/core/libs/prx/libSceMsgDialog/Export.cpp index 1e02b26eab..c9462f47b7 100644 --- a/core/libs/prx/libSceMsgDialog/Export.cpp +++ b/core/libs/prx/libSceMsgDialog/Export.cpp @@ -1,6 +1,4 @@ -// libSceMsgDialog: message dialog state machine (NONE -> INITIALIZED -> RUNNING -> FINISHED) without a visible dialog. -// The dialog "closes" with the affirmative button after a couple of status polls, so a title waiting for the user -// never blocks. The message text is logged for the first calls. +#include "prx/libSceMsgDialog/include/Api.hpp" #include #include #include @@ -25,7 +23,6 @@ constexpr int kStatusFinished = 3; constexpr int kErrOk = 0; constexpr int kErrNotInitialized = static_cast(0x80B80003u); -constexpr int kErrNotFinished = static_cast(0x80B80005u); constexpr int kErrBusy = static_cast(0x80B80007u); constexpr int kErrNotRunning = static_cast(0x80B8000Bu); constexpr int kErrArgNull = static_cast(0x80B8000Du); @@ -40,9 +37,8 @@ constexpr int kButtonTypeNone = 2; constexpr int kButtonTypeOkCancel = 3; constexpr int kButtonTypeYesNoFocusNo = 4; -constexpr int kButtonIdOk = 1; // OK / YES / BUTTON1 -constexpr int kButtonIdNo = 2; // NO / BUTTON2 -constexpr int kResultOk = 0; +constexpr int kButtonIdOk = 1; +constexpr int kButtonIdNo = 2; std::mutex g_lock; int g_status = kStatusNone; @@ -64,8 +60,6 @@ bool Readable(const void* p, std::size_t n) { #endif } -// Best-effort inspection of SceMsgDialogParam: { SceCommonDialogBaseParam base (0x30); size_t size; int mode (0x38); -// UserMessageParam* userMsg (0x40); ProgressBarParam* progress (0x48); SystemMessageParam* system (0x50); ... }. void Inspect(const void* param) { const std::uint8_t* bytes = static_cast(param); g_mode = kModeUserMsg; @@ -95,7 +89,6 @@ int Poll() { if (g_status == kStatusRunning) { bool done = false; if (g_mode == kModeProgressBar) { - // Progress dialogs are ended by the title (sceMsgDialogClose); the cap only prevents a wait without end. done = std::chrono::steady_clock::now() - g_openedAt > std::chrono::seconds(30); } else { done = --g_pollsLeft <= 0; @@ -104,7 +97,7 @@ int Poll() { } return g_status; } -} // namespace +} extern "C" { @@ -124,7 +117,6 @@ int APS5_VABI sceMsgDialogOpen(const void* param) { if (param == nullptr) return kErrArgNull; std::lock_guard g(g_lock); if (g_status == kStatusRunning) return kErrBusy; - // A title that skipped sceMsgDialogInitialize still gets its dialog (and its FINISHED status). Inspect(param); g_pollsLeft = 2; g_openedAt = std::chrono::steady_clock::now(); @@ -137,15 +129,6 @@ int APS5_VABI sceMsgDialogGetStatus(void) { int APS5_VABI sceMsgDialogUpdateStatus(void) { return Poll(); } -// SceMsgDialogResult: { int32 mode; int32 result; int32 buttonId; uint8 reserved[32] }. -int APS5_VABI sceMsgDialogGetResult(void* result) { - if (result == nullptr) return kErrArgNull; - std::lock_guard g(g_lock); - if (g_status != kStatusFinished) return kErrNotFinished; - std::int32_t out[3] = {g_mode, kResultOk, g_buttonId}; - std::memcpy(result, out, sizeof(out)); - return kErrOk; -} int APS5_VABI sceMsgDialogClose(void) { std::lock_guard g(g_lock); if (g_status != kStatusRunning) return kErrNotRunning; diff --git a/core/libs/prx/libSceMsgDialog/include/Api.hpp b/core/libs/prx/libSceMsgDialog/include/Api.hpp new file mode 100644 index 0000000000..44ceec5270 --- /dev/null +++ b/core/libs/prx/libSceMsgDialog/include/Api.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceMsgDialogInitialize(void); +int APS5_VABI sceMsgDialogTerminate(void); +int APS5_VABI sceMsgDialogOpen(const void* param); +int APS5_VABI sceMsgDialogGetStatus(void); +int APS5_VABI sceMsgDialogUpdateStatus(void); +int APS5_VABI sceMsgDialogClose(void); +int APS5_VABI sceMsgDialogProgressBarInc(int target, std::uint32_t delta); +int APS5_VABI sceMsgDialogProgressBarSetMsg(int target, const char* msg); +int APS5_VABI sceMsgDialogProgressBarSetValue(int target, std::uint32_t rate); + +} diff --git a/core/libs/prx/libSceMsgDialog/libSceMsgDialog.hpp b/core/libs/prx/libSceMsgDialog/libSceMsgDialog.hpp new file mode 100644 index 0000000000..f25195526f --- /dev/null +++ b/core/libs/prx/libSceMsgDialog/libSceMsgDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceMsgDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceMusicCoreInterface/libSceMusicCoreInterface.hpp b/core/libs/prx/libSceMusicCoreInterface/libSceMusicCoreInterface.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceMusicCoreInterface/libSceMusicCoreInterface.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp b/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp index dc0e9adc97..9053d6ba40 100644 --- a/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp +++ b/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMusicFrameworkInterface/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceMusicFrameworkInterface/include/Api.hpp b/core/libs/prx/libSceMusicFrameworkInterface/include/Api.hpp new file mode 100644 index 0000000000..8164b75a7d --- /dev/null +++ b/core/libs/prx/libSceMusicFrameworkInterface/include/Api.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceMusicFwGetCurrentTime(); +int APS5_VABI sceMusicFwGetLoop(); +int APS5_VABI sceMusicFwGetMuted(); +int APS5_VABI sceMusicFwGetNetworkState(); +int APS5_VABI sceMusicFwGetVolume(); +int APS5_VABI sceMusicFwIsEnded(); +int APS5_VABI sceMusicFwIsPlaying(); +int APS5_VABI sceMusicFwIsStopped(); +int APS5_VABI sceMusicFwRemoveSrc(); +int APS5_VABI sceMusicFwSendMessageAsynchronously(); +int APS5_VABI sceMusicFwSendMessageSynchronously(); +int APS5_VABI sceMusicFwSetCurrentTime(); +int APS5_VABI sceMusicFwSetLoop(); +int APS5_VABI sceMusicFwSetShuffle(); +int APS5_VABI sceMusicFwSetVolume(); +int APS5_VABI sceMusicFwSkipNext(); +int APS5_VABI sceMusicFwSkipPrevious(); + +} diff --git a/core/libs/prx/libSceMusicFrameworkInterface/libSceMusicFrameworkInterface.hpp b/core/libs/prx/libSceMusicFrameworkInterface/libSceMusicFrameworkInterface.hpp new file mode 100644 index 0000000000..83fbfaf531 --- /dev/null +++ b/core/libs/prx/libSceMusicFrameworkInterface/libSceMusicFrameworkInterface.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceMusicFrameworkInterface/include/Api.hpp" diff --git a/core/libs/prx/libSceNKWeb/Export.cpp b/core/libs/prx/libSceNKWeb/Export.cpp index 81401ab485..fdef9184da 100644 --- a/core/libs/prx/libSceNKWeb/Export.cpp +++ b/core/libs/prx/libSceNKWeb/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNKWeb/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceNKWeb/include/Api.hpp b/core/libs/prx/libSceNKWeb/include/Api.hpp new file mode 100644 index 0000000000..57b480c9e7 --- /dev/null +++ b/core/libs/prx/libSceNKWeb/include/Api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNKWebInitialize(); +int APS5_VABI sceNKWebTerminate(); + +} diff --git a/core/libs/prx/libSceNKWeb/libSceNKWeb.hpp b/core/libs/prx/libSceNKWeb/libSceNKWeb.hpp new file mode 100644 index 0000000000..d65e494889 --- /dev/null +++ b/core/libs/prx/libSceNKWeb/libSceNKWeb.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNKWeb/include/Api.hpp" diff --git a/core/libs/prx/libSceNKWebCdlgInjectedBundle/libSceNKWebCdlgInjectedBundle.hpp b/core/libs/prx/libSceNKWebCdlgInjectedBundle/libSceNKWebCdlgInjectedBundle.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceNKWebCdlgInjectedBundle/libSceNKWebCdlgInjectedBundle.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index d81f4a5e32..43ccd2eeb3 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -1,3 +1,153 @@ +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" +#include "prx/libSceNKWebKit/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,6 +155,754 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceNKWebKitUnknown____URPJNZ18_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++-URPJNZ18", sceNKWebKitUnknown____URPJNZ18_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___67uYkhqBU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++67uYkhqBU", sceNKWebKitUnknown___67uYkhqBU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___8RYr2gjrw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++8RYr2gjrw", sceNKWebKitUnknown___8RYr2gjrw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___BntMcao1o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++BntMcao1o", sceNKWebKitUnknown___BntMcao1o_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___R8IXi10PI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++R8IXi10PI", sceNKWebKitUnknown___R8IXi10PI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___j2h_QO22w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++j2h+QO22w", sceNKWebKitUnknown___j2h_QO22w_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown____7XwmYLnI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+-+7XwmYLnI", sceNKWebKitUnknown____7XwmYLnI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___wmVyLhgm0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+-wmVyLhgm0", sceNKWebKitUnknown___wmVyLhgm0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__0QEt8ZQWgg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+0QEt8ZQWgg", sceNKWebKitUnknown__0QEt8ZQWgg_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__0YNxcA_duk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+0YNxcA-duk", sceNKWebKitUnknown__0YNxcA_duk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__0ZZzGYxx_I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+0ZZzGYxx+I", sceNKWebKitUnknown__0ZZzGYxx_I_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__0rdnkSicqk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+0rdnkSicqk", sceNKWebKitUnknown__0rdnkSicqk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__1Ex5GCP8Xs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+1Ex5GCP8Xs", sceNKWebKitUnknown__1Ex5GCP8Xs_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__1FfZ_0sxvQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+1FfZ+0sxvQ", sceNKWebKitUnknown__1FfZ_0sxvQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__1ObAtzCrYE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+1ObAtzCrYE", sceNKWebKitUnknown__1ObAtzCrYE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__1n_h4FUu9E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+1n+h4FUu9E", sceNKWebKitUnknown__1n_h4FUu9E_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__26aZpK535w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+26aZpK535w", sceNKWebKitUnknown__26aZpK535w_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__2XuBaAXb_4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+2XuBaAXb+4", sceNKWebKitUnknown__2XuBaAXb_4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__2rTX2KM7Ms_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+2rTX2KM7Ms", sceNKWebKitUnknown__2rTX2KM7Ms_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__31dbB3S_6Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+31dbB3S-6Y", sceNKWebKitUnknown__31dbB3S_6Y_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__3p6wRzzzlQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+3p6wRzzzlQ", sceNKWebKitUnknown__3p6wRzzzlQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__4_GNOUIBt0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+4-GNOUIBt0", sceNKWebKitUnknown__4_GNOUIBt0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__45BiDnCTxw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+45BiDnCTxw", sceNKWebKitUnknown__45BiDnCTxw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__4lVmXsIwqM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+4lVmXsIwqM", sceNKWebKitUnknown__4lVmXsIwqM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__5E2YZZIstM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+5E2YZZIstM", sceNKWebKitUnknown__5E2YZZIstM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__5V_UOk8zS4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+5V-UOk8zS4", sceNKWebKitUnknown__5V_UOk8zS4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__63IB3UdMOY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+63IB3UdMOY", sceNKWebKitUnknown__63IB3UdMOY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__6boXsT6XFA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+6boXsT6XFA", sceNKWebKitUnknown__6boXsT6XFA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__79w68212vg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+79w68212vg", sceNKWebKitUnknown__79w68212vg_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__7HpqmfJz48_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+7HpqmfJz48", sceNKWebKitUnknown__7HpqmfJz48_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__8T1y8Y_lgQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+8T1y8Y-lgQ", sceNKWebKitUnknown__8T1y8Y_lgQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__8VuTEFp13s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+8VuTEFp13s", sceNKWebKitUnknown__8VuTEFp13s_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__9TKACifR2g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+9TKACifR2g", sceNKWebKitUnknown__9TKACifR2g_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__9ZBRWNf4og_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+9ZBRWNf4og", sceNKWebKitUnknown__9ZBRWNf4og_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__9us0nrDaCU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+9us0nrDaCU", sceNKWebKitUnknown__9us0nrDaCU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__AIEKk6K05M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+AIEKk6K05M", sceNKWebKitUnknown__AIEKk6K05M_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Abk7CfTivU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Abk7CfTivU", sceNKWebKitUnknown__Abk7CfTivU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__AnsPWo12m8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+AnsPWo12m8", sceNKWebKitUnknown__AnsPWo12m8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__BDqCDede1o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+BDqCDede1o", sceNKWebKitUnknown__BDqCDede1o_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__BSUP6XSeAw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+BSUP6XSeAw", sceNKWebKitUnknown__BSUP6XSeAw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__BZCJi3OIiQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+BZCJi3OIiQ", sceNKWebKitUnknown__BZCJi3OIiQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ByVxWl1pW8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ByVxWl1pW8", sceNKWebKitUnknown__ByVxWl1pW8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__CgSI_4D534_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+CgSI-4D534", sceNKWebKitUnknown__CgSI_4D534_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__CpPA9_CKUA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+CpPA9+CKUA", sceNKWebKitUnknown__CpPA9_CKUA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__DcUw5YwkSY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+DcUw5YwkSY", sceNKWebKitUnknown__DcUw5YwkSY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__DpDBrSZWUc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+DpDBrSZWUc", sceNKWebKitUnknown__DpDBrSZWUc_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__E3D_W4kpl8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+E3D-W4kpl8", sceNKWebKitUnknown__E3D_W4kpl8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__EN6MgNmVv4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+EN6MgNmVv4", sceNKWebKitUnknown__EN6MgNmVv4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ErgockOJwc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ErgockOJwc", sceNKWebKitUnknown__ErgockOJwc_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__FKudG3uS8A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+FKudG3uS8A", sceNKWebKitUnknown__FKudG3uS8A_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__FWUl_T8yG8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+FWUl-T8yG8", sceNKWebKitUnknown__FWUl_T8yG8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Fca5Eyr4BU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Fca5Eyr4BU", sceNKWebKitUnknown__Fca5Eyr4BU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__FcvpbbdrZM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+FcvpbbdrZM", sceNKWebKitUnknown__FcvpbbdrZM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__FvUhrQaiJQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+FvUhrQaiJQ", sceNKWebKitUnknown__FvUhrQaiJQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__GOwQvxqztE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+GOwQvxqztE", sceNKWebKitUnknown__GOwQvxqztE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Gp0M0sPj9I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Gp0M0sPj9I", sceNKWebKitUnknown__Gp0M0sPj9I_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__H_bnpXdWtk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+H-bnpXdWtk", sceNKWebKitUnknown__H_bnpXdWtk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__H5PTMieXNE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+H5PTMieXNE", sceNKWebKitUnknown__H5PTMieXNE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__HuPyGuX_jY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+HuPyGuX+jY", sceNKWebKitUnknown__HuPyGuX_jY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__IMGO_dXHis_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+IMGO+dXHis", sceNKWebKitUnknown__IMGO_dXHis_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ImJ026_DAw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ImJ026-DAw", sceNKWebKitUnknown__ImJ026_DAw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__IoqHgIw0PM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+IoqHgIw0PM", sceNKWebKitUnknown__IoqHgIw0PM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__JnngM7WyHI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+JnngM7WyHI", sceNKWebKitUnknown__JnngM7WyHI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Kb7j57jDzs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Kb7j57jDzs", sceNKWebKitUnknown__Kb7j57jDzs_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Kx_IV_21hk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Kx-IV+21hk", sceNKWebKitUnknown__Kx_IV_21hk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__LUabdo811U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+LUabdo811U", sceNKWebKitUnknown__LUabdo811U_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__LiOmqon2ZI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+LiOmqon2ZI", sceNKWebKitUnknown__LiOmqon2ZI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__LoAenYdWlM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+LoAenYdWlM", sceNKWebKitUnknown__LoAenYdWlM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__LrmM3KN3N8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+LrmM3KN3N8", sceNKWebKitUnknown__LrmM3KN3N8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__MzGFuNN348_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+MzGFuNN348", sceNKWebKitUnknown__MzGFuNN348_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__N_GwpCV4yk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+N-GwpCV4yk", sceNKWebKitUnknown__N_GwpCV4yk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__N6UhRaLrJE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+N6UhRaLrJE", sceNKWebKitUnknown__N6UhRaLrJE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__NGrXdCa0q8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+NGrXdCa0q8", sceNKWebKitUnknown__NGrXdCa0q8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__NiLTGGKF_E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+NiLTGGKF+E", sceNKWebKitUnknown__NiLTGGKF_E_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Nqfy8HVG84_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Nqfy8HVG84", sceNKWebKitUnknown__Nqfy8HVG84_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__NuhuZH75ak_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+NuhuZH75ak", sceNKWebKitUnknown__NuhuZH75ak_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__OCxbaKiBUo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+OCxbaKiBUo", sceNKWebKitUnknown__OCxbaKiBUo_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__OOjOkuWL6g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+OOjOkuWL6g", sceNKWebKitUnknown__OOjOkuWL6g_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__OkTn3_PBxM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+OkTn3+PBxM", sceNKWebKitUnknown__OkTn3_PBxM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__PDx3sfB_l4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+PDx3sfB-l4", sceNKWebKitUnknown__PDx3sfB_l4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__POsWAWjXo8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+POsWAWjXo8", sceNKWebKitUnknown__POsWAWjXo8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__PeIrQMgYpw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+PeIrQMgYpw", sceNKWebKitUnknown__PeIrQMgYpw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Q7xLvpc0PA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Q7xLvpc0PA", sceNKWebKitUnknown__Q7xLvpc0PA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__QL3zQ6ZNy4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+QL3zQ6ZNy4", sceNKWebKitUnknown__QL3zQ6ZNy4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__QqzEnc5c80_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+QqzEnc5c80", sceNKWebKitUnknown__QqzEnc5c80_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__R0GXLrsPM0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+R0GXLrsPM0", sceNKWebKitUnknown__R0GXLrsPM0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__R2eXISeSvw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+R2eXISeSvw", sceNKWebKitUnknown__R2eXISeSvw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__RkwSDm5kGU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+RkwSDm5kGU", sceNKWebKitUnknown__RkwSDm5kGU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__RtBREW92M0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+RtBREW92M0", sceNKWebKitUnknown__RtBREW92M0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__S72K_fa2_8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+S72K+fa2-8", sceNKWebKitUnknown__S72K_fa2_8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__S7ftAAWhoQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+S7ftAAWhoQ", sceNKWebKitUnknown__S7ftAAWhoQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__SHzgUiuQDE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+SHzgUiuQDE", sceNKWebKitUnknown__SHzgUiuQDE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Sgy63JgEgQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Sgy63JgEgQ", sceNKWebKitUnknown__Sgy63JgEgQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Sozphy1Wa8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Sozphy1Wa8", sceNKWebKitUnknown__Sozphy1Wa8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__TPntL1OK80_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+TPntL1OK80", sceNKWebKitUnknown__TPntL1OK80_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__TT_AplhQWA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+TT+AplhQWA", sceNKWebKitUnknown__TT_AplhQWA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__TjNmuhxdBQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+TjNmuhxdBQ", sceNKWebKitUnknown__TjNmuhxdBQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__U_nxwdrnDY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+U-nxwdrnDY", sceNKWebKitUnknown__U_nxwdrnDY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__UKI_MUuiCU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+UKI-MUuiCU", sceNKWebKitUnknown__UKI_MUuiCU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__VvktcfIouQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+VvktcfIouQ", sceNKWebKitUnknown__VvktcfIouQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__WS2yRv5aBI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+WS2yRv5aBI", sceNKWebKitUnknown__WS2yRv5aBI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__XZfnHByVO0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+XZfnHByVO0", sceNKWebKitUnknown__XZfnHByVO0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__YBfWz5udq4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+YBfWz5udq4", sceNKWebKitUnknown__YBfWz5udq4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__YPwmEWTZwE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+YPwmEWTZwE", sceNKWebKitUnknown__YPwmEWTZwE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__YYEOsi3IMk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+YYEOsi3IMk", sceNKWebKitUnknown__YYEOsi3IMk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__YyqQTUIQJQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+YyqQTUIQJQ", sceNKWebKitUnknown__YyqQTUIQJQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ZgGbFykhls_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ZgGbFykhls", sceNKWebKitUnknown__ZgGbFykhls_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ZwjLJ74Y38_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ZwjLJ74Y38", sceNKWebKitUnknown__ZwjLJ74Y38_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__b1xwU7aUwU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+b1xwU7aUwU", sceNKWebKitUnknown__b1xwU7aUwU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__b5TfVQ3_4s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+b5TfVQ3-4s", sceNKWebKitUnknown__b5TfVQ3_4s_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__bDb7iby5NI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+bDb7iby5NI", sceNKWebKitUnknown__bDb7iby5NI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__baqz7ahOng_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+baqz7ahOng", sceNKWebKitUnknown__baqz7ahOng_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__bvtn3pCtek_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+bvtn3pCtek", sceNKWebKitUnknown__bvtn3pCtek_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__byORTSkWQo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+byORTSkWQo", sceNKWebKitUnknown__byORTSkWQo_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__dAwquupevQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+dAwquupevQ", sceNKWebKitUnknown__dAwquupevQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__dTlroLZao8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+dTlroLZao8", sceNKWebKitUnknown__dTlroLZao8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__eVN0UdRT8c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+eVN0UdRT8c", sceNKWebKitUnknown__eVN0UdRT8c_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__fTKZiGqo8E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+fTKZiGqo8E", sceNKWebKitUnknown__fTKZiGqo8E_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__feeFP8Y2_c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+feeFP8Y2+c", sceNKWebKitUnknown__feeFP8Y2_c_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__iLBOBrdx5A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+iLBOBrdx5A", sceNKWebKitUnknown__iLBOBrdx5A_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__iLdIVuhDhY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+iLdIVuhDhY", sceNKWebKitUnknown__iLdIVuhDhY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__jG4wqqA8yg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+jG4wqqA8yg", sceNKWebKitUnknown__jG4wqqA8yg_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__jLvgfDA97c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+jLvgfDA97c", sceNKWebKitUnknown__jLvgfDA97c_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ji7IBGwnCM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ji7IBGwnCM", sceNKWebKitUnknown__ji7IBGwnCM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__k3JGU_UUXo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+k3JGU-UUXo", sceNKWebKitUnknown__k3JGU_UUXo_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__kX_CdbguB0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+kX-CdbguB0", sceNKWebKitUnknown__kX_CdbguB0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__keSg8ay8cE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+keSg8ay8cE", sceNKWebKitUnknown__keSg8ay8cE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__lI9zCCS_HQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+lI9zCCS-HQ", sceNKWebKitUnknown__lI9zCCS_HQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__lwLqZJLo3g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+lwLqZJLo3g", sceNKWebKitUnknown__lwLqZJLo3g_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__mS029ncrbo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+mS029ncrbo", sceNKWebKitUnknown__mS029ncrbo_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__mYy22Ucg7Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+mYy22Ucg7Y", sceNKWebKitUnknown__mYy22Ucg7Y_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__nHWcLWOmb4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+nHWcLWOmb4", sceNKWebKitUnknown__nHWcLWOmb4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__nSg5kHtXkg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+nSg5kHtXkg", sceNKWebKitUnknown__nSg5kHtXkg_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__nc3IouJMEA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+nc3IouJMEA", sceNKWebKitUnknown__nc3IouJMEA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ndmq8LhAjA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ndmq8LhAjA", sceNKWebKitUnknown__ndmq8LhAjA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__nrEMsyYHiw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+nrEMsyYHiw", sceNKWebKitUnknown__nrEMsyYHiw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__oJHrfcyJJM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+oJHrfcyJJM", sceNKWebKitUnknown__oJHrfcyJJM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ovAafdG0O8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ovAafdG0O8", sceNKWebKitUnknown__ovAafdG0O8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__pWGccNXto8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+pWGccNXto8", sceNKWebKitUnknown__pWGccNXto8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__q0GELad_mY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+q0GELad-mY", sceNKWebKitUnknown__q0GELad_mY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__qXuNlEFSRQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+qXuNlEFSRQ", sceNKWebKitUnknown__qXuNlEFSRQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__qsTbJFS86A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+qsTbJFS86A", sceNKWebKitUnknown__qsTbJFS86A_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__r_TJDA2yIM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+r+TJDA2yIM", sceNKWebKitUnknown__r_TJDA2yIM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__r0UH41I1dI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+r0UH41I1dI", sceNKWebKitUnknown__r0UH41I1dI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__r8OGkuL4dc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+r8OGkuL4dc", sceNKWebKitUnknown__r8OGkuL4dc_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__rFA8ZR_qaI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+rFA8ZR+qaI", sceNKWebKitUnknown__rFA8ZR_qaI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__rPvFDsPbXM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+rPvFDsPbXM", sceNKWebKitUnknown__rPvFDsPbXM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__sUvE9LF80k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+sUvE9LF80k", sceNKWebKitUnknown__sUvE9LF80k_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__suuwfNT5Oc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+suuwfNT5Oc", sceNKWebKitUnknown__suuwfNT5Oc_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__t_MaTLADe4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+t-MaTLADe4", sceNKWebKitUnknown__t_MaTLADe4_nid_no_patch); } diff --git a/core/libs/prx/libSceNKWebKit/include/Api.hpp b/core/libs/prx/libSceNKWebKit/include/Api.hpp new file mode 100644 index 0000000000..9312101ad1 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/include/Api.hpp @@ -0,0 +1,158 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNKWebKitUnknown____URPJNZ18_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown___67uYkhqBU_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown___8RYr2gjrw_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown___BntMcao1o_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown___R8IXi10PI_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown___j2h_QO22w_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown____7XwmYLnI_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown___wmVyLhgm0_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__0QEt8ZQWgg_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__0YNxcA_duk_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__0ZZzGYxx_I_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__0rdnkSicqk_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__1Ex5GCP8Xs_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__1FfZ_0sxvQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__1ObAtzCrYE_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__1n_h4FUu9E_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__26aZpK535w_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__2XuBaAXb_4_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__2rTX2KM7Ms_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__31dbB3S_6Y_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__3p6wRzzzlQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__4_GNOUIBt0_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__45BiDnCTxw_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__4lVmXsIwqM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__5E2YZZIstM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__5V_UOk8zS4_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__63IB3UdMOY_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__6boXsT6XFA_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__79w68212vg_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__7HpqmfJz48_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__8T1y8Y_lgQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__8VuTEFp13s_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__9TKACifR2g_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__9ZBRWNf4og_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__9us0nrDaCU_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__AIEKk6K05M_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__Abk7CfTivU_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__AnsPWo12m8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__BDqCDede1o_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__BSUP6XSeAw_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__BZCJi3OIiQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__ByVxWl1pW8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__CgSI_4D534_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__CpPA9_CKUA_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__DcUw5YwkSY_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__DpDBrSZWUc_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__E3D_W4kpl8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__EN6MgNmVv4_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__ErgockOJwc_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__FKudG3uS8A_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__FWUl_T8yG8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__Fca5Eyr4BU_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__FcvpbbdrZM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__FvUhrQaiJQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__GOwQvxqztE_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__Gp0M0sPj9I_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__H_bnpXdWtk_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__H5PTMieXNE_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__HuPyGuX_jY_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__IMGO_dXHis_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__ImJ026_DAw_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__IoqHgIw0PM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__JnngM7WyHI_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__Kb7j57jDzs_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__Kx_IV_21hk_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__LUabdo811U_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__LiOmqon2ZI_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__LoAenYdWlM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__LrmM3KN3N8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__MzGFuNN348_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__N_GwpCV4yk_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__N6UhRaLrJE_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__NGrXdCa0q8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__NiLTGGKF_E_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__Nqfy8HVG84_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__NuhuZH75ak_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__OCxbaKiBUo_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__OOjOkuWL6g_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__OkTn3_PBxM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__PDx3sfB_l4_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__POsWAWjXo8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__PeIrQMgYpw_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__Q7xLvpc0PA_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__QL3zQ6ZNy4_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__QqzEnc5c80_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__R0GXLrsPM0_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__R2eXISeSvw_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__RkwSDm5kGU_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__RtBREW92M0_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__S72K_fa2_8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__S7ftAAWhoQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__SHzgUiuQDE_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__Sgy63JgEgQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__Sozphy1Wa8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__TPntL1OK80_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__TT_AplhQWA_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__TjNmuhxdBQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__U_nxwdrnDY_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__UKI_MUuiCU_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__VvktcfIouQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__WS2yRv5aBI_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__XZfnHByVO0_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__YBfWz5udq4_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__YPwmEWTZwE_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__YYEOsi3IMk_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__YyqQTUIQJQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__ZgGbFykhls_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__ZwjLJ74Y38_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__b1xwU7aUwU_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__b5TfVQ3_4s_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__bDb7iby5NI_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__baqz7ahOng_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__bvtn3pCtek_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__byORTSkWQo_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__dAwquupevQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__dTlroLZao8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__eVN0UdRT8c_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__fTKZiGqo8E_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__feeFP8Y2_c_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__iLBOBrdx5A_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__iLdIVuhDhY_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__jG4wqqA8yg_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__jLvgfDA97c_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__ji7IBGwnCM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__k3JGU_UUXo_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__kX_CdbguB0_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__keSg8ay8cE_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__lI9zCCS_HQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__lwLqZJLo3g_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__mS029ncrbo_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__mYy22Ucg7Y_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__nHWcLWOmb4_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__nSg5kHtXkg_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__nc3IouJMEA_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__ndmq8LhAjA_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__nrEMsyYHiw_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__oJHrfcyJJM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__ovAafdG0O8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__pWGccNXto8_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__q0GELad_mY_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__qXuNlEFSRQ_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__qsTbJFS86A_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__r_TJDA2yIM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__r0UH41I1dI_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__r8OGkuL4dc_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__rFA8ZR_qaI_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__rPvFDsPbXM_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__sUvE9LF80k_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__suuwfNT5Oc_nid_no_patch(); +int APS5_VABI sceNKWebKitUnknown__t_MaTLADe4_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceNKWebKit/libSceNKWebKit.hpp b/core/libs/prx/libSceNKWebKit/libSceNKWebKit.hpp new file mode 100644 index 0000000000..f291edcd57 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/libSceNKWebKit.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNKWebKit/include/Api.hpp" diff --git a/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp b/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceNet/CMakeLists.txt b/core/libs/prx/libSceNet/CMakeLists.txt index 95faf4584b..f900648cf2 100644 --- a/core/libs/prx/libSceNet/CMakeLists.txt +++ b/core/libs/prx/libSceNet/CMakeLists.txt @@ -4,10 +4,31 @@ target_link_libraries(libSceNet PRIVATE libSceNetCtl) configure_windows_unwind(libSceNet) if(BUILD_TESTING) + add_test_executable(guest_net_protocol_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetProtocol.cpp) + target_include_directories(guest_net_protocol_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_protocol_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_protocol_tests) + add_test(NAME guest_net_protocol COMMAND guest_net_protocol_tests) + set_tests_properties(guest_net_protocol PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_net_protocol PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(net_dontwait_tests ${LIBS_INCLUDE_DIR}/tests/NetDontWait.cpp) target_include_directories(net_dontwait_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(net_dontwait_tests PRIVATE libSceNet libkernel libc) configure_windows_unwind(net_dontwait_tests) + add_test_executable(guest_net_buffer_validation_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetBufferValidation.cpp) + target_include_directories(guest_net_buffer_validation_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_buffer_validation_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_buffer_validation_tests) + add_test(NAME guest_net_buffer_validation COMMAND guest_net_buffer_validation_tests) + set_tests_properties(guest_net_buffer_validation PROPERTIES TIMEOUT 20) + if(WIN32) + target_link_libraries(guest_net_buffer_validation_tests PRIVATE ws2_32) + set_tests_properties(guest_net_buffer_validation PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() foreach(api IN ITEMS send sendto sendmsg recv recvfrom recvmsg duplex blocking) add_test(NAME net_dontwait_${api} COMMAND net_dontwait_tests ${api}) set_tests_properties(net_dontwait_${api} PROPERTIES TIMEOUT 20) @@ -31,6 +52,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + add_test_executable(guest_net_keepalive_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetKeepalive.cpp) + target_include_directories(guest_net_keepalive_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_keepalive_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_keepalive_tests) + add_test(NAME guest_sce_net_keepalive COMMAND guest_net_keepalive_tests) + set_tests_properties(guest_sce_net_keepalive PROPERTIES TIMEOUT 20) + if(WIN32) + target_link_libraries(guest_net_keepalive_tests PRIVATE ws2_32) + set_tests_properties(guest_sce_net_keepalive PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_datagram_receive_tests ${LIBS_INCLUDE_DIR}/tests/GuestDatagramReceive.cpp) target_include_directories(guest_datagram_receive_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_datagram_receive_tests PRIVATE libSceNet libkernel libc) @@ -43,6 +75,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + add_test_executable(guest_net_dns_info_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetDnsInfo.cpp) + target_include_directories(guest_net_dns_info_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_dns_info_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_dns_info_tests) + add_test(NAME guest_net_dns_info COMMAND guest_net_dns_info_tests) + set_tests_properties(guest_net_dns_info PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_net_dns_info PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_refused_connect_tests ${LIBS_INCLUDE_DIR}/tests/GuestRefusedConnect.cpp) target_include_directories(guest_refused_connect_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_refused_connect_tests PRIVATE libSceNet libkernel libc) @@ -66,15 +108,60 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endforeach() endif() + if(WIN32) + add_test_executable(net_empty_stream_tests ${LIBS_INCLUDE_DIR}/tests/NetEmptyStream.cpp) + target_include_directories(net_empty_stream_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(net_empty_stream_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(net_empty_stream_tests) + foreach(api IN ITEMS recv recvfrom recvmsg) + foreach(mode IN ITEMS empty dontwait peek nonblocking queued nullbuf errors datagram) + add_test(NAME net_empty_stream_${api}_${mode} COMMAND net_empty_stream_tests ${api} ${mode}) + set_tests_properties(net_empty_stream_${api}_${mode} PROPERTIES TIMEOUT 20 + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endforeach() + endforeach() + endif() add_test_executable(guest_sce_net_tests ${LIBS_INCLUDE_DIR}/tests/GuestSceNet.cpp) target_include_directories(guest_sce_net_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_sce_net_tests PRIVATE libSceNet libSceNetCtl libkernel libc) configure_windows_unwind(guest_sce_net_tests) add_test(NAME guest_sce_net COMMAND guest_sce_net_tests) + add_test_executable(guest_net_thread_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetThread.cpp) + target_include_directories(guest_net_thread_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_thread_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_thread_tests) + add_test(NAME guest_net_thread COMMAND guest_net_thread_tests) + set_tests_properties(guest_net_thread PROPERTIES TIMEOUT 20) set_tests_properties(guest_sce_net PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77) if(WIN32) target_link_libraries(guest_sce_net_tests PRIVATE ws2_32) - set_tests_properties(guest_sce_net PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_sce_net guest_net_thread PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_net_nodelay_readback_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetNodelayReadback.cpp) + target_include_directories(guest_net_nodelay_readback_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_nodelay_readback_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_nodelay_readback_tests) + add_test(NAME guest_net_nodelay_readback COMMAND guest_net_nodelay_readback_tests) + set_tests_properties(guest_net_nodelay_readback PROPERTIES TIMEOUT 20) + if(WIN32) + target_link_libraries(guest_net_nodelay_readback_tests PRIVATE ws2_32) + set_tests_properties(guest_net_nodelay_readback PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(net_epoll_lifetime_tests ${LIBS_INCLUDE_DIR}/tests/NetEpollLifetime.cpp) + target_include_directories(net_epoll_lifetime_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(net_epoll_lifetime_tests PRIVATE libkernel libc libSceNetCtl) + configure_windows_unwind(net_epoll_lifetime_tests) + if(WIN32) + target_link_libraries(net_epoll_lifetime_tests PRIVATE ws2_32) + endif() + foreach(operation IN ITEMS reuse control close delete modify replace abort destroy) + add_test(NAME net_epoll_lifetime_${operation} COMMAND net_epoll_lifetime_tests ${operation}) + set_tests_properties(net_epoll_lifetime_${operation} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(net_epoll_lifetime_${operation} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() endif() diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index d6a9bd0b15..5c0f4b21a5 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -10,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -43,9 +47,51 @@ #include "prx/libkernel/Socket/include/SocketPoll.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" #include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include "src/ResolverTestHooks.hpp" // Guest socket descriptors retain PS5 semantics while their transport is backed by host sockets. +static_assert(sizeof(NetDhcpOptionInfo) == 24); +static_assert(offsetof(NetDhcpOptionInfo, capacity) == 4); +static_assert(offsetof(NetDhcpOptionInfo, data) == 8); +static_assert(offsetof(NetDhcpOptionInfo, size) == 16); + +static_assert(sizeof(NetRoutingAddressParameter) == 8); +static_assert(sizeof(NetRoutingServerParameter) == 20); +static_assert(offsetof(NetRoutingServerParameter, remoteAddresses) == 12); +static_assert(sizeof(NetRoutingRule) == 20); +static_assert(offsetof(NetRoutingRule, firstPort) == 8); +static_assert(offsetof(NetRoutingRule, secondAddress) == 12); +static_assert(offsetof(NetRoutingRule, flags) == 18); +static_assert(sizeof(NetRoutingRuleParameter) == 16); +static_assert(offsetof(NetRoutingRuleParameter, rules) == 8); + +static_assert(sizeof(NetDhcpdStartParameter) == 24); +static_assert(offsetof(NetDhcpdStartParameter, maxClients) == 20); +static_assert(sizeof(NetPppoeStartParameter) == 272); +static_assert(offsetof(NetPppoeStartParameter, userName) == 8); +static_assert(offsetof(NetPppoeStartParameter, password) == 136); +static_assert(offsetof(NetPppoeStartParameter, mtu) == 264); +static_assert(offsetof(NetPppoeStartParameter, flags) == 268); + +static_assert(sizeof(NetInfoDumpParameter) == 72); +static_assert(offsetof(NetInfoDumpParameter, path) == 8); + +static_assert(sizeof(NetResolverConnectOption) == 16); +static_assert(offsetof(NetResolverConnectOption, resolverTimeout) == 4); +static_assert(offsetof(NetResolverConnectOption, resolverRetryCount) == 8); +static_assert(offsetof(NetResolverConnectOption, reserved) == 12); + +static_assert(sizeof(NetSyncObject) == 24); + +static_assert(sizeof(NetIpv6Address) == 16); + +static_assert(sizeof(NetDnsInfo) == 8); +static_assert(sizeof(NetDns6Info) == 32); + +static_assert(sizeof(NetIpv6InterfaceAddress) == 20); + namespace { // FreeBSD errno numbers as reported through sceNetErrnoLoc (SCE_NET_ERROR_* is 0x80410100 + errno). constexpr int NET_ENOENT = 2; @@ -54,6 +100,7 @@ constexpr int NET_EBADF = 9; constexpr int NET_EACCES = 13; constexpr int NET_EFAULT = 14; constexpr int NET_EINVAL = 22; +constexpr int NET_ENOTTY = 25; constexpr int NET_ENOSPC = 28; constexpr int NET_EDOM = 33; constexpr int NET_EAGAIN = 35; @@ -75,6 +122,7 @@ constexpr int NET_EMSGSIZE = 40; constexpr int NET_ETIMEDOUT = 60; constexpr int NET_ECONNREFUSED = 61; constexpr int NET_EHOSTUNREACH = 65; +constexpr int NET_ENOTINIT = 200; constexpr int NET_ERROR_BASE = static_cast(0x80410100u); constexpr int NET_ERROR_RESOLVER_ENODNS = static_cast(0x804101E1u); constexpr int NET_RESOLVER_ABORT_FLAG_NTOA_PRESERVATION = 0x1; @@ -95,8 +143,10 @@ constexpr int NET_TF_NODELAY = 4; constexpr int NET_SO_SNDTIMEO = 0x1005; constexpr int NET_SO_RCVTIMEO = 0x1006; constexpr int NET_SO_ERROR = 0x1007; +constexpr int NET_SO_PROTOCOL = 0x1016; constexpr int NET_SO_NBIO = 0x1200; constexpr int NET_SO_REUSEADDR = 0x4; +constexpr int NET_SO_KEEPALIVE = 0x8; constexpr int NET_SO_BROADCAST = 0x20; constexpr int NET_SO_ONESBCAST = 0x10000; constexpr int NET_SO_SNDBUF = 0x1001; @@ -212,15 +262,19 @@ bool native_to_guest_address(const sockaddr_storage& native, void* address, std: struct Sock { int family = 0; int type = 0; + int protocol = 0; std::shared_ptr native; bool bound = false; bool listening = false; + bool connectAttempted = false; bool aborted = false; + bool sending_shut = false; bool read_shut = false; int reported_error = 0; int snd_buffer = 0; int rcv_buffer = 0; int onesbcast = 0; + int keepalive = 0; }; #ifdef _WIN32 @@ -277,15 +331,11 @@ HostMutex g_mutex; HostCondition g_cv; std::map g_socks; std::set g_epolls; -std::map> g_epoll_socks; +using EpollRegistrations = std::map>; +std::map g_epoll_socks; std::set g_epoll_aborted; std::map g_pools; -struct NetMemoryPoolStats { - std::size_t pool_size; - std::size_t max_inuse_size; - std::size_t current_inuse_size; -}; struct Resolver { int error = 0; int running = 0; @@ -299,15 +349,24 @@ int g_next_epoll = 0x4000; int g_next_pool = 1; int g_next_resolver = 0x100; bool g_net_inited = false; +std::atomic g_resolverLookupForTests{nullptr}; int* errno_slot() { static thread_local int e = 0; return &e; } -extern "C" int* APS5_VABI __error_nid_postfix(); extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, std::size_t capacity); +std::mutex g_dnsLock; +bool g_dnsOverridden = false; +NetDnsInfo g_dnsOverride{}; +NetDns6Info g_dns6Override{}; + +bool IsZeroAddress(const NetIpv6Address& address) { + return std::all_of(std::begin(address.bytes), std::end(address.bytes), [](std::uint8_t byte) { return byte == 0; }); +} + void set_errno(int err) { *errno_slot() = err; *__error_nid_postfix() = err; @@ -318,6 +377,12 @@ int fail(int err) { return NET_ERROR_BASE | err; } +void* APS5_VABI RunNetThread(void* param) { + const auto* thread = static_cast(param); + thread->entry(thread->arg); + return nullptr; +} + int set_native_timeout(NativeSocket native, int optname, std::int64_t microseconds) { const int native_option = optname == NET_SO_RCVTIMEO ? SO_RCVTIMEO : SO_SNDTIMEO; #ifdef _WIN32 @@ -401,20 +466,6 @@ std::uint64_t swap64(std::uint64_t v) { ((v >> 8) & 0xFF000000u) | ((v >> 24) & 0xFF0000u) | ((v >> 40) & 0xFF00u) | (v >> 56); } -struct NetIovec { - void* base; - std::uint64_t length; -}; - -struct NetMsghdr { - void* name; - std::uint32_t name_length; - NetIovec* iov; - int iov_length; - void* control; - std::uint32_t control_length; - int flags; -}; static_assert(sizeof(NetMsghdr) == 48 && offsetof(NetMsghdr, iov) == 16 && offsetof(NetMsghdr, control) == 32 && offsetof(NetMsghdr, flags) == 44); @@ -424,6 +475,14 @@ int timeout_option(int optname) { return optname; } +#ifdef _WIN32 +short closed_stream_events(const Sock& socket, short native, short requested) { + if (socket.type != NET_SOCK_STREAM || (native & (POLLHUP | POLLERR)) != POLLHUP) return native; + native = static_cast(native | (requested & POLLRDNORM)); + return socket.sending_shut ? native : static_cast(native & ~POLLHUP); +} +#endif + std::int64_t message_length(const NetMsghdr* message) { if (!message) return fail(NET_EFAULT); if (message->iov_length < 0 || message->iov_length > NET_UIO_MAXIOV) return fail(NET_EMSGSIZE); @@ -438,15 +497,6 @@ std::int64_t message_length(const NetMsghdr* message) { return static_cast(total); } -int receive_error(const Sock& socket) { -#ifdef _WIN32 - if (WSAGetLastError() == WSAETIMEDOUT && socket.type != NET_SOCK_STREAM && socket.native->receiveTimeout > 0) return NET_EAGAIN; -#else - (void)socket; -#endif - return native_error(); -} - bool message_flags_supported(int flags, int accepted) { return (flags & ~accepted) == 0; } @@ -474,7 +524,7 @@ auto native_transfer(const NativeSocketHandle& socket, int flags, bool send, TOp if (timeout > 0) { const auto remaining = deadline - std::chrono::steady_clock::now(); if (remaining <= std::chrono::steady_clock::duration::zero()) { - WSASetLastError(completeLength > 0 ? WSAEWOULDBLOCK : WSAETIMEDOUT); + WSASetLastError(send && completeLength == 0 ? WSAETIMEDOUT : WSAEWOULDBLOCK); return transferred > 0 ? static_cast(transferred) : static_cast(-1); } milliseconds = static_cast(std::min(std::chrono::ceil(remaining).count(), milliseconds)); @@ -513,12 +563,23 @@ int native_message_flags(int flags) { return native; } -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) +#ifdef __APPLE__ +bool has_local_address(NativeSocket native) { + sockaddr_storage local{}; + NativeLength local_length = sizeof(local); + if (::getsockname(native, reinterpret_cast(&local), &local_length) != 0) return true; + if (local.ss_family == AF_INET) return reinterpret_cast(local).sin_port != 0; + if (local.ss_family == AF_INET6) return reinterpret_cast(local).sin6_port != 0; + return true; +} +#else bool has_local_address(NativeSocket native) { sockaddr_storage local{}; NativeLength local_length = sizeof(local); return ::getsockname(native, reinterpret_cast(&local), &local_length) == 0 || WSAGetLastError() != WSAEINVAL; } +#endif void note_local_address(int s, const Sock& socket) { if (socket.type == NET_SOCK_STREAM || socket.bound || !has_local_address(socket.native->value)) return; @@ -535,14 +596,38 @@ int wait_for_datagram(const Sock& socket, std::chrono::steady_clock::time_point const auto now = std::chrono::steady_clock::now(); if (now >= deadline) return NET_EAGAIN; const auto remaining = std::chrono::ceil(deadline - now).count(); +#ifdef _WIN32 WSAPOLLFD descriptor{}; descriptor.fd = socket.native->value; descriptor.events = POLLRDNORM; return WSAPoll(&descriptor, 1, static_cast(std::min(remaining, INT_MAX))) == 0 ? NET_EAGAIN : 0; +#else + pollfd descriptor{}; + descriptor.fd = socket.native->value; + descriptor.events = POLLIN; + return ::poll(&descriptor, 1, static_cast(std::min(remaining, INT_MAX))) == 0 ? NET_EAGAIN : 0; +#endif } int wait_for_local_address(int s, const Sock& socket, bool empty, int flags) { - if (socket.type == NET_SOCK_STREAM || socket.bound) return 0; + if (socket.type == NET_SOCK_STREAM) { +#ifdef _WIN32 + if (!empty) return 0; + sockaddr_storage peer{}; + NativeLength length = sizeof(peer); + if (::getpeername(socket.native->value, reinterpret_cast(&peer), &length) != 0) { + const int error = native_error(); + return !socket.connectAttempted && !socket.aborted && !socket.read_shut && error == NET_ENOTCONN ? error : 0; + } + WSAPOLLFD state{}; + state.fd = socket.native->value; + state.events = POLLRDNORM; + return WSAPoll(&state, 1, 0) >= 0 && (state.revents & (POLLERR | POLLHUP | POLLNVAL)) == 0 ? RECEIVE_NOTHING : 0; +#else + return 0; +#endif + } + if (socket.bound) return 0; const bool named = has_local_address(socket.native->value); if (!named && (socket.read_shut || empty)) return RECEIVE_NOTHING; if (!named && (socket.native->nonblock || (flags & NET_MSG_DONTWAIT) != 0)) return NET_EAGAIN; @@ -609,6 +694,10 @@ bool parse_ipv4_parts(const char* text, std::uint32_t& address) { } // namespace +void GuestSceNetTest::SetResolverLookupForTests_nid_no_patch(ResolverLookup lookup) noexcept { + g_resolverLookupForTests.store(lookup); +} + extern "C" { extern const std::uint32_t sce_net_in6addr_any[4] = {}; @@ -619,7 +708,9 @@ int* APS5_VABI sceNetErrnoLoc(void) { } int APS5_VABI sceNetShowNetstat(void) { - return 0; + 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) { @@ -681,8 +772,6 @@ int PollSockets(KernelSocketPoll::Entry* entries, int count, int timeoutMillisec const bool g_socketPollerRegistered = (KernelSetSocketPoller_nid_no_patch(&PollSockets), true); -extern "C" int APS5_VABI sceNetSocketClose(int s); - bool CloseSocketForKernel(int descriptor, int* error) { { std::lock_guard lk(g_mutex); @@ -694,8 +783,6 @@ bool CloseSocketForKernel(int descriptor, int* error) { const bool g_socketCloserRegistered = (KernelSetSocketCloser_nid_no_patch(&CloseSocketForKernel), true); -extern "C" int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval, uint32_t optlen); - bool SetSocketOptionForKernel(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error) { { std::lock_guard lk(g_mutex); @@ -826,13 +913,14 @@ int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol) Sock s; s.family = family; s.type = type; + s.protocol = 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); g_socks[fd] = s; return fd; } -extern "C" int APS5_VABI close_nid_postfix(int d); - int APS5_VABI sceNetSocketClose(int s) { std::shared_ptr native; bool found = false; @@ -842,6 +930,7 @@ int APS5_VABI sceNetSocketClose(int s) { if (it != g_socks.end()) { native = it->second.native; g_socks.erase(it); + for (auto& epoll : g_epoll_socks) epoll.second.erase(s); found = true; } } @@ -949,6 +1038,8 @@ int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) { Sock connection; connection.family = listener.family; connection.type = NET_SOCK_STREAM; + connection.protocol = listener.protocol; + connection.connectAttempted = true; connection.native = std::move(handle); #ifdef _WIN32 connection.native->receiveTimeout = listener.native->receiveTimeout.load(); @@ -1031,8 +1122,13 @@ int APS5_VABI sceNetConnect(int s, const void* addr, uint32_t addrlen) { if (socket.type == NET_SOCK_STREAM) RefuseClosedLoopbackAtOnce_nid_no_patch(socket.native->value, reinterpret_cast(&native)); #endif if (native.ss_family != (socket.family == NET_AF_INET ? AF_INET : AF_INET6)) return fail(NET_EAFNOSUPPORT); + if (socket.type == NET_SOCK_STREAM) { + std::lock_guard lock(g_mutex); + const auto current = g_socks.find(s); + if (current != g_socks.end() && current->second.native == socket.native) current->second.connectAttempted = true; + } if (::connect(socket.native->value, reinterpret_cast(&native), native_length) == 0) { -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) note_local_address(s, socket); #endif return 0; @@ -1068,7 +1164,7 @@ int64_t APS5_VABI sceNetRecv(int s, void* buf, size_t len, int flags) { if (it == g_socks.end()) return fail(NET_EBADF); socket = it->second; } -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) if (const int wait = wait_for_local_address(s, socket, len == 0, flags)) return wait == RECEIVE_NOTHING ? 0 : fail(wait); #endif const GuestArena::HostWrite destination(len == 0 ? nullptr : buf, len); @@ -1080,7 +1176,11 @@ int64_t APS5_VABI sceNetRecv(int s, void* buf, size_t len, int flags) { #ifdef _WIN32 if (result < 0 && socket.type == NET_SOCK_DGRAM && WSAGetLastError() == WSAEMSGSIZE) return static_cast(len); #endif - return result >= 0 ? result : fail(receive_error(socket)); + return result >= 0 ? result : fail(native_error()); +} + +int64_t APS5_VABI GuestNetRecv_nid_no_patch(int s, void* buf, size_t len, int flags) { + return sceNetRecv(s, buf, len, flags); } int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* from, uint32_t* fromlen) { @@ -1097,7 +1197,7 @@ int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* } const GuestArena::HostWrite lengthOutput(from == nullptr ? nullptr : fromlen, from == nullptr ? 0 : sizeof(*fromlen)); if (!lengthOutput.Open()) return fail(NET_EFAULT); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) if (const int wait = wait_for_local_address(s, socket, len == 0, flags)) { if (wait != RECEIVE_NOTHING) return fail(wait); if (from) *fromlen = 0; @@ -1121,7 +1221,7 @@ int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* #ifdef _WIN32 if (result < 0 && socket.type == NET_SOCK_DGRAM && WSAGetLastError() == WSAEMSGSIZE) result = static_cast(len); #endif - if (result < 0) return fail(receive_error(socket)); + if (result < 0) return fail(native_error()); if (from && !native_to_guest_address(peer, from, fromlen)) return fail(NET_EAFNOSUPPORT); return result; } @@ -1143,6 +1243,10 @@ int64_t APS5_VABI sceNetSend(int s, const void* buf, size_t len, int flags) { return result >= 0 ? result : fail(native_error()); } +int64_t APS5_VABI GuestNetSend_nid_no_patch(int s, const void* buf, size_t len, int flags) { + return sceNetSend(s, buf, len, flags); +} + int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, const void* to, uint32_t tolen) { if (!message_flags_supported(flags, NET_MSG_DONTWAIT)) return fail(NET_EOPNOTSUPP); if (!buf && len) return fail(NET_EINVAL); @@ -1171,7 +1275,7 @@ int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, co }, true, static_cast(len)); } if (result < 0) return fail(native_error()); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) note_local_address(s, socket); #endif return result; @@ -1215,7 +1319,7 @@ int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags) { if (!destinations.emplace_back(entry.base, static_cast(entry.length)).Open()) return fail(NET_EFAULT); } const bool datagram = socket.type != NET_SOCK_STREAM; -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) if (const int wait = wait_for_local_address(s, socket, total == 0, flags)) { if (wait != RECEIVE_NOTHING) return fail(wait); msg->flags = 0; @@ -1239,7 +1343,7 @@ int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags) { }); if (received < 0) { #ifdef _WIN32 - if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return fail(receive_error(socket)); + if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return fail(native_error()); received = static_cast(buffer.size()); #else return fail(native_error()); @@ -1277,11 +1381,15 @@ int APS5_VABI sceNetShutdown(int s, int how) { if (it != g_socks.end() && it->second.native == socket.native) it->second.read_shut = true; g_cv.NotifyAll(); } - return error == 0 ? 0 : fail(error); + if (error != 0) return fail(error); + if (how != 0) { + std::lock_guard lk(g_mutex); + const auto it = g_socks.find(s); + if (it != g_socks.end() && it->second.native == socket.native) it->second.sending_shut = true; + } + return 0; } -extern "C" int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option, const void* value, std::uint32_t length); - int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval, uint32_t optlen) { Sock socket; bool found = false; @@ -1327,9 +1435,15 @@ int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval if (value < 0) return fail(NET_EINVAL); if (set_native_timeout(socket.native->value, optname, value) != 0) return fail(native_error()); } else if (optname == NET_SO_REUSEADDR || optname == NET_SO_BROADCAST || optname == NET_SO_SNDBUF || optname == NET_SO_RCVBUF) { + if ((optname == NET_SO_SNDBUF || optname == NET_SO_RCVBUF) && value < 1) return fail(NET_EINVAL); const int native_option = optname == NET_SO_REUSEADDR ? SO_REUSEADDR : optname == NET_SO_BROADCAST ? SO_BROADCAST : optname == NET_SO_SNDBUF ? SO_SNDBUF : SO_RCVBUF; if (::setsockopt(socket.native->value, SOL_SOCKET, native_option, reinterpret_cast(&value), sizeof(value)) != 0) return fail(native_error()); + } else if (optname == NET_SO_KEEPALIVE) { + const int enabled = value != 0; + if (socket.type == NET_SOCK_STREAM && + ::setsockopt(socket.native->value, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&enabled), sizeof(enabled)) != 0) + return fail(native_error()); } else if (optname == NET_SO_ONESBCAST) { } else { return fail(NET_EOPNOTSUPP); @@ -1343,6 +1457,7 @@ int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval if (optname == NET_SO_SNDBUF) it->second.snd_buffer = value; if (optname == NET_SO_RCVBUF) it->second.rcv_buffer = value; if (optname == NET_SO_ONESBCAST) it->second.onesbcast = value; + if (optname == NET_SO_KEEPALIVE) it->second.keepalive = value != 0 ? NET_SO_KEEPALIVE : 0; } return 0; } @@ -1356,6 +1471,12 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint socket = it->second; } if (optval == nullptr || optlen == nullptr) return fail(NET_EINVAL); + if (level == NET_SOL_SOCKET && optname == NET_SO_PROTOCOL) { + if (socket.type == NET_SOCK_RAW) NotImplemented_nid_no_patch(__func__); + *optlen = std::min(*optlen, static_cast(sizeof(socket.protocol))); + std::memcpy(optval, &socket.protocol, *optlen); + return 0; + } if (level == NET_SOL_SOCKET && (optname == NET_SO_RCVTIMEO || optname == NET_SO_SNDTIMEO)) { const int microseconds = optname == NET_SO_RCVTIMEO ? socket.native->receiveTimeout.load() : socket.native->sendTimeout.load(); const KernelTimeval timeout{microseconds / 1000000, microseconds % 1000000}; @@ -1363,7 +1484,18 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint std::memcpy(optval, &timeout, *optlen); return 0; } - if (*optlen < sizeof(int)) return fail(NET_EINVAL); + if (level == NET_SOL_SOCKET && optname == NET_SO_KEEPALIVE) { + int enabled = socket.keepalive; + if (socket.type == NET_SOCK_STREAM) { + NativeLength size = sizeof(enabled); + if (::getsockopt(socket.native->value, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&enabled), &size) != 0) + return fail(native_error()); + } + const int value = enabled != 0 ? NET_SO_KEEPALIVE : 0; + *optlen = std::min(*optlen, static_cast(sizeof(value))); + std::memcpy(optval, &value, *optlen); + return 0; + } if (level == NET_IPPROTO_TCP && socket.type != NET_SOCK_STREAM) return fail(NET_EINVAL); if (level == NET_IPPROTO_TCP && optname == NET_TCP_NODELAY) { int enabled = 0; @@ -1371,10 +1503,11 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint if (::getsockopt(socket.native->value, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&enabled), &size) != 0) return fail(native_error()); const int value = enabled != 0 ? NET_TF_NODELAY : 0; - std::memcpy(optval, &value, sizeof(value)); - *optlen = sizeof(value); + *optlen = std::min(*optlen, static_cast(sizeof(value))); + std::memcpy(optval, &value, *optlen); return 0; } + if (*optlen < sizeof(int)) return fail(NET_EINVAL); if (level == NET_IPPROTO_TCP) return fail(NET_ENOPROTOOPT); if (level != NET_SOL_SOCKET) return fail(NET_EOPNOTSUPP); int value = 0; @@ -1456,7 +1589,9 @@ int APS5_VABI sceNetGetpeername(int s, void* addr, uint32_t* addrlen) { constexpr int NET_SOCKINFO_F_SELF = 0x1; constexpr int NET_SOCKINFO_F_NONBLOCK = 0x200000; +constexpr int NET_SOCKINFO_F_IPV6 = 0x400000; constexpr int NET_SOCKINFO_TIME_WAIT = 0x40; +constexpr int NET_SOCKINFO6_IPV46 = 0x1000; struct NetSockInfo { char name[32]; @@ -1493,6 +1628,11 @@ struct NetSockInfo { static_assert(sizeof(NetSockInfo) == 0xA0 && offsetof(NetSockInfo, s) == 0x24 && offsetof(NetSockInfo, local_adr) == 0x34 && offsetof(NetSockInfo, local_port) == 0x3C && offsetof(NetSockInfo, recv_buffer_size) == 0x64); +static_assert(sizeof(NetSockInfo6) == sizeof(NetSockInfo) && offsetof(NetSockInfo6, s) == offsetof(NetSockInfo, s) && + offsetof(NetSockInfo6, local_port) == offsetof(NetSockInfo, local_port) && offsetof(NetSockInfo6, flags) == offsetof(NetSockInfo, flags) && + offsetof(NetSockInfo6, recv_buffer_size) == offsetof(NetSockInfo, recv_buffer_size) && offsetof(NetSockInfo6, local_adr6) == 0x6C && + offsetof(NetSockInfo6, remote_adr6) == 0x7C && offsetof(NetSockInfo6, tx_drops) == offsetof(NetSockInfo, tx_drops)); + static int sock_buffer_size(const Sock& socket, bool send, std::int32_t& value) { value = send ? socket.snd_buffer : socket.rcv_buffer; if (value != 0) return 0; @@ -1566,13 +1706,92 @@ int APS5_VABI sceNetGetSockInfo(int s, void* info, int n, int flags) { return written; } +static int fill_sock_info6(int s, NetSockInfo6& record, bool includeIpv4) { + Sock socket; + { + std::lock_guard lk(g_mutex); + const auto it = g_socks.find(s); + if (it == g_socks.end()) return NET_EBADF; + socket = it->second; + } + const bool ipv6 = socket.family == NET_AF_INET6; + if (!ipv6 && !(includeIpv4 && socket.family == NET_AF_INET)) return NET_EAFNOSUPPORT; + sockaddr_storage local{}; + NativeLength length = sizeof(local); + if (::getsockname(socket.native->value, reinterpret_cast(&local), &length) != 0) { +#ifdef _WIN32 + if (WSAGetLastError() != WSAEINVAL) return native_error(); +#else + return native_error(); +#endif + } + sockaddr_storage remote{}; + length = sizeof(remote); + if (::getpeername(socket.native->value, reinterpret_cast(&remote), &length) != 0) { + const int error = native_error(); + if (error != NET_ENOTCONN) return error; + } + record = {}; + if (const int error = sock_buffer_size(socket, false, record.recv_buffer_size)) return error; + if (const int error = sock_buffer_size(socket, true, record.send_buffer_size)) return error; + record.s = s; + record.socket_type = static_cast(socket.type); + record.flags = NET_SOCKINFO_F_SELF | (socket.native->nonblock ? NET_SOCKINFO_F_NONBLOCK : 0); + if (ipv6) { + const auto& local6 = reinterpret_cast(local); + const auto& remote6 = reinterpret_cast(remote); + std::memcpy(record.local_adr6, &local6.sin6_addr, sizeof(record.local_adr6)); + std::memcpy(record.remote_adr6, &remote6.sin6_addr, sizeof(record.remote_adr6)); + record.local_port = local6.sin6_port; + record.remote_port = remote6.sin6_port; + record.flags |= NET_SOCKINFO_F_IPV6; + } else { + const auto& local4 = reinterpret_cast(local); + const auto& remote4 = reinterpret_cast(remote); + record.local_adr = local4.sin_addr.s_addr; + record.remote_adr = remote4.sin_addr.s_addr; + record.local_port = local4.sin_port; + record.remote_port = remote4.sin_port; + } + return 0; +} + +int APS5_VABI sceNetGetSockInfo6(std::int32_t s, NetSockInfo6* info, std::uint32_t capacity, std::uint32_t flags) { + if ((flags & ~(NET_SOCKINFO_TIME_WAIT | NET_SOCKINFO6_IPV46)) != 0) return fail(NET_EINVAL); + const bool includeIpv4 = (flags & NET_SOCKINFO6_IPV46) != 0; + std::vector listed; + if (s < 0) { + std::lock_guard lk(g_mutex); + for (const auto& [descriptor, socket] : g_socks) { + if (socket.family == NET_AF_INET6 || (includeIpv4 && socket.family == NET_AF_INET)) listed.push_back(descriptor); + } + } + if (!info) return s < 0 ? static_cast(listed.size()) : fail(NET_EINVAL); + if (static_cast(capacity) < 1) return fail(NET_EINVAL); + NetSockInfo6 record; + if (s >= 0) { + if (const int error = fill_sock_info6(s, record, includeIpv4)) return fail(error); + *info = record; + return 1; + } + int written = 0; + for (const int descriptor : listed) { + if (written == static_cast(capacity)) break; + const int error = fill_sock_info6(descriptor, record, includeIpv4); + if (error == NET_EBADF || error == NET_EAFNOSUPPORT) continue; + if (error != 0) return fail(error); + info[written] = record; + ++written; + } + return written; +} + int APS5_VABI sceNetEpollCreate(const char* name, int flags) { - (void)name; - (void)flags; + if (!name || flags != 0) return fail(NET_EINVAL); std::lock_guard lk(g_mutex); const int id = g_next_epoll++; g_epolls.insert(id); - g_epoll_socks.emplace(id, std::map{}); + g_epoll_socks.emplace(id, EpollRegistrations{}); return id; } @@ -1637,16 +1856,17 @@ int APS5_VABI sceNetEpollControl(int eid, int op, int id, const NetEpollEvent* e } if (op != NET_EPOLL_CTL_ADD && op != NET_EPOLL_CTL_MOD && op != NET_EPOLL_CTL_DEL) return fail(NET_EINVAL); if (op != NET_EPOLL_CTL_DEL && !event) return fail(NET_EINVAL); - if (op != NET_EPOLL_CTL_DEL && g_socks.count(id) == 0) return fail(NET_EBADF); + if (g_socks.count(id) == 0) return fail(NET_EBADF); auto& registrations = g_epoll_socks[eid]; if (op == NET_EPOLL_CTL_ADD) { - if (!registrations.emplace(id, *event).second) return fail(NET_EADDRINUSE); + if (registrations.count(id) != 0) return fail(NET_EADDRINUSE); + registrations.emplace(id, std::make_shared(*event)); } else if (op == NET_EPOLL_CTL_DEL) { if (registrations.erase(id) == 0) return fail(NET_ENOENT); } else { auto registration = registrations.find(id); if (registration == registrations.end()) return fail(NET_ENOENT); - registration->second = *event; + registration->second = std::make_shared(*event); } g_cv.NotifyAll(); return 0; @@ -1654,12 +1874,16 @@ int APS5_VABI sceNetEpollControl(int eid, int op, int id, const NetEpollEvent* e int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int timeout) { if (!events || maxevents <= 0) return fail(NET_EINVAL); + struct Watch { + int descriptor; + std::shared_ptr registration; + Sock socket; + }; const auto deadline = timeout > 0 ? std::chrono::steady_clock::now() + std::chrono::microseconds(timeout) : std::chrono::steady_clock::time_point::max(); for (;;) { - std::vector registered_events; - std::vector> native_sockets; + std::vector watches; { std::lock_guard lk(g_mutex); if (g_epolls.count(eid) == 0) return fail(NET_EBADF); @@ -1669,12 +1893,11 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int for (const auto& entry : registration->second) { const auto socket = g_socks.find(entry.first); if (socket == g_socks.end()) continue; - registered_events.push_back({entry.second.events, 0, static_cast(entry.first), entry.second.data}); - native_sockets.push_back(socket->second.native); + watches.push_back({entry.first, entry.second, socket->second}); } } } - if (registered_events.empty()) { + if (watches.empty()) { if (timeout == 0) return 0; std::unique_lock lk(g_mutex); const auto now = std::chrono::steady_clock::now(); @@ -1686,18 +1909,18 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int } #ifdef _WIN32 - std::vector descriptors(registered_events.size()); + std::vector descriptors(watches.size()); for (std::size_t index = 0; index < descriptors.size(); ++index) { - descriptors[index].fd = native_sockets[index]->value; - if (registered_events[index].events & 1) descriptors[index].events |= POLLRDNORM; - if (registered_events[index].events & 2) descriptors[index].events |= POLLWRNORM; + descriptors[index].fd = watches[index].socket.native->value; + if (watches[index].registration->events & 1) descriptors[index].events |= POLLRDNORM; + if (watches[index].registration->events & 2) descriptors[index].events |= POLLWRNORM; } #else - std::vector descriptors(registered_events.size()); + std::vector descriptors(watches.size()); for (std::size_t index = 0; index < descriptors.size(); ++index) { - descriptors[index].fd = native_sockets[index]->value; - if (registered_events[index].events & 1) descriptors[index].events |= POLLIN; - if (registered_events[index].events & 2) descriptors[index].events |= POLLOUT; + descriptors[index].fd = watches[index].socket.native->value; + if (watches[index].registration->events & 1) descriptors[index].events |= POLLIN; + if (watches[index].registration->events & 2) descriptors[index].events |= POLLOUT; } #endif const auto now = std::chrono::steady_clock::now(); @@ -1710,6 +1933,8 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int } #ifdef _WIN32 const int ready_count = WSAPoll(descriptors.data(), static_cast(descriptors.size()), wait_ms); + for (std::size_t index = 0; index < descriptors.size(); ++index) + descriptors[index].revents = closed_stream_events(watches[index].socket, descriptors[index].revents, descriptors[index].events); #else const int ready_count = ::poll(descriptors.data(), descriptors.size(), wait_ms); #endif @@ -1718,27 +1943,32 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int std::lock_guard lk(g_mutex); if (g_epolls.count(eid) == 0) return fail(NET_EBADF); if (g_epoll_aborted.erase(eid) != 0) return fail(NET_ECONNABORTED); - } - if (ready_count > 0) { - int count = 0; - for (std::size_t index = 0; index < descriptors.size() && count < maxevents; ++index) { - const auto native_events = descriptors[index].revents; - if (!native_events) continue; - std::uint32_t guest_events = 0; + if (ready_count > 0) { + int count = 0; + const auto& registrations = g_epoll_socks.at(eid); + for (std::size_t index = 0; index < descriptors.size() && count < maxevents; ++index) { + const auto registration = registrations.find(watches[index].descriptor); + if (registration == registrations.end() || registration->second != watches[index].registration) continue; + const auto native_events = descriptors[index].revents; + if (!native_events) continue; + std::uint32_t guest_events = 0; #ifdef _WIN32 - if (native_events & (POLLRDNORM | POLLPRI)) guest_events |= 1; - if (native_events & POLLWRNORM) guest_events |= 2; + if (native_events & (POLLRDNORM | POLLPRI)) guest_events |= 1; + if (native_events & POLLWRNORM) guest_events |= 2; #else - if (native_events & (POLLIN | POLLPRI)) guest_events |= 1; - if (native_events & POLLOUT) guest_events |= 2; + if (native_events & (POLLIN | POLLPRI)) guest_events |= 1; + if (native_events & POLLOUT) guest_events |= 2; #endif - if (native_events & POLLERR) guest_events |= 8; - if (native_events & POLLHUP) guest_events |= 16; - events[count] = registered_events[index]; - events[count].events = guest_events; - ++count; + if (native_events & POLLERR) guest_events |= 8; + if (native_events & POLLHUP) guest_events |= 16; + events[count] = *watches[index].registration; + events[count].ident = static_cast(watches[index].descriptor); + events[count].reserved = 0; + events[count].events = guest_events; + ++count; + } + if (count > 0) return count; } - if (count > 0) return count; } if (timeout == 0) return 0; } @@ -1753,20 +1983,32 @@ uint32_t APS5_VABI sceNetNtohl_nid_postfix(uint32_t net32) { return swap32(net32 uint16_t APS5_VABI sceNetNtohs_nid_postfix(uint16_t net16) { return swap16(net16); } int APS5_VABI sceNetInetPton(int af, const char* src, void* dst) { - if (src == nullptr || dst == nullptr) { + if (src == nullptr) { return fail(NET_EINVAL); } if (af != NET_AF_INET && af != NET_AF_INET6) { return fail(NET_EAFNOSUPPORT); } - const std::string text = af == NET_AF_INET ? std::string(src, std::strcspn(src, " \t\n\v\f\r")) : std::string(src); - std::array address{}; + if (dst == nullptr && af == NET_AF_INET6) { + return fail(NET_EINVAL); + } + const auto convert = [](int family, const std::string& text, unsigned char* address) { #ifdef _WIN32 - const int converted = InetPtonA(af == NET_AF_INET ? AF_INET : AF_INET6, text.c_str(), address.data()); + return InetPtonA(family, text.c_str(), address); #else - const int converted = ::inet_pton(af == NET_AF_INET ? AF_INET : AF_INET6, text.c_str(), address.data()); + return ::inet_pton(family, text.c_str(), address); #endif - if (converted == 1) { + }; + const std::string head(src, std::strcspn(src, " \t\n\v\f\r")); + const auto colon = head.rfind(':'); + const bool dottedTail = af == NET_AF_INET6 && colon != std::string::npos && head.find('.', colon) != std::string::npos; + std::array address{}; + if (dottedTail && convert(AF_INET, head.substr(colon + 1), address.data()) != 1) { + return 0; + } + const std::string text = af == NET_AF_INET || dottedTail ? head : std::string(src); + const int converted = af == NET_AF_INET6 ? __inet_pton_nid_postfix(af, text.c_str(), address.data()) : convert(AF_INET, text, address.data()); + if (converted == 1 && dst != nullptr) { std::memcpy(dst, address.data(), af == NET_AF_INET ? 4 : 16); } return converted; @@ -1783,9 +2025,9 @@ const char* APS5_VABI sceNetInetNtop(int af, const void* src, char* dst, uint32_ } char text[INET6_ADDRSTRLEN]; #ifdef _WIN32 - const auto* result = InetNtopA(af == NET_AF_INET ? AF_INET : AF_INET6, const_cast(src), text, sizeof(text)); + const auto* result = af == NET_AF_INET6 ? __inet_ntop_nid_postfix(af, src, text, sizeof(text)) : InetNtopA(AF_INET, const_cast(src), text, sizeof(text)); #else - const auto* result = ::inet_ntop(af == NET_AF_INET ? AF_INET : AF_INET6, src, text, sizeof(text)); + const auto* result = af == NET_AF_INET6 ? __inet_ntop_nid_postfix(af, src, text, sizeof(text)) : ::inet_ntop(AF_INET, src, text, sizeof(text)); #endif if (!result) { set_errno(native_error()); @@ -1808,9 +2050,30 @@ int APS5_VABI sceNetEtherNtostr(const NetEtherAddr* n, char* str, size_t len) { return 0; } +int APS5_VABI sceNetEtherStrton(const char* text, NetEtherAddr* address) { + if (text == nullptr || address == nullptr) return fail(NET_EINVAL); + for (std::size_t index = 0; index < 17; ++index) { + if (text[index] == '\0') return fail(NET_EINVAL); + } + const auto hexDigit = [](char character) { + if (character >= '0' && character <= '9') return character - '0'; + if (character >= 'a' && character <= 'z') return character - 'a' + 10; + if (character >= 'A' && character <= 'Z') return character - 'A' + 10; + return 0x7f; + }; + NetEtherAddr parsed{}; + for (std::size_t index = 0; index < sizeof(parsed.data); ++index) { + if (static_cast(text[index * 3]) >= 128 || static_cast(text[index * 3 + 1]) >= 128) { + NotImplemented_nid_no_patch(__func__); + } + parsed.data[index] = static_cast(hexDigit(text[index * 3]) * 16 + hexDigit(text[index * 3 + 1])); + } + *address = parsed; + return 0; +} + int APS5_VABI sceNetGetMacAddress(NetEtherAddr* addr, int flags) { - (void)flags; - if (addr == nullptr) { + if (addr == nullptr || flags != 0) { return fail(NET_EINVAL); } // Keep guest identity stable without exposing the host adapter address. @@ -1823,7 +2086,7 @@ int APS5_VABI sceNetGetMacAddress(NetEtherAddr* addr, int flags) { int APS5_VABI sceNetResolverCreate(const char* name, int memid, int flags) { (void)name; (void)memid; - (void)flags; + if (flags != 0) return fail(NET_EINVAL); std::lock_guard lk(g_mutex); const int id = g_next_resolver++; g_resolvers[id] = {}; @@ -1839,6 +2102,14 @@ int lookup_host(int rid, const char* hostname, const char* func, int family, std if (!initialize_sockets()) return fail(5); std::uint64_t generation = 0; if (const int error = begin_lookup(rid, &Resolver::abort_ntoa, generation)) return error; + if (const auto lookup = g_resolverLookupForTests.load()) { + if (!lookup(hostname, family, addresses) || addresses.empty()) { + set_errno(NET_ENOENT); + log_soft(func, "host DNS lookup failed"); + return end_lookup(rid, generation, NET_ERROR_RESOLVER_ENODNS); + } + return end_lookup(rid, generation, 0); + } addrinfo hints{}; hints.ai_family = family; addrinfo* results = nullptr; @@ -1875,9 +2146,7 @@ int lookup_ipv4(int rid, const char* hostname, const char* func, std::vector(flags) & 0xfffefffeu) != 0) return fail(NET_EINVAL); if (!hostname || !addr) return fail(NET_EINVAL); std::vector addresses; if (const int error = lookup_ipv4(rid, hostname, __func__, addresses)) return error; @@ -1896,19 +2165,6 @@ int APS5_VABI sceNetResolverStartNtoa6(int rid, const char* hostname, void* addr return 0; } -struct NetResolverRecord { - std::uint32_t address; - std::uint8_t address6[12]; - std::int32_t family; - std::int32_t reserved[3]; -}; - -struct NetResolverInfo { - NetResolverRecord records[10]; - std::int32_t count; - std::int32_t count4; - std::int32_t reserved[14]; -}; static_assert(sizeof(NetResolverRecord) == 32 && offsetof(NetResolverRecord, family) == 16); static_assert(sizeof(NetResolverInfo) == 384 && offsetof(NetResolverInfo, count) == 320 && offsetof(NetResolverInfo, count4) == 324); @@ -2005,43 +2261,53 @@ int APS5_VABI sceNetResolverAbort(int rid, int flags) { } int APS5_VABI sceNetShowIfconfig() { - return 0; + std::lock_guard lock(g_mutex); + if (!g_net_inited) return fail(NET_ENOTINIT); + throw std::runtime_error(__func__); } extern const std::uint8_t in6addr_any_nid_postfix[16] = {}; extern const std::uint8_t in6addr_loopback_nid_postfix[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; int APS5_VABI sceNetClearDnsCache(int flags) { - if (flags != 0) throw std::runtime_error("sceNetClearDnsCache: flags " + std::to_string(flags) + " are not supported"); + std::lock_guard lock(g_mutex); + if (!g_net_inited) return fail(NET_ENOTINIT); + if (flags != 0) return fail(NET_EINVAL); return 0; } -int APS5_VABI sceNetGetStatisticsInfo(void) { +int APS5_VABI sceNetGetStatisticsInfo(NetStatisticsInfo* info, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetInterfaceStats(void) { +int APS5_VABI sceNetGetInterfaceStats(NetInterfaceStats* stats, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSetDnsInfo(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNetSetDnsInfo(const NetDnsInfo* info, std::uint32_t flags) { + if (!g_net_inited) return fail(NET_ENOTINIT); + if (flags != 0) return fail(NET_EINVAL); + NetDnsInfo servers{}; + if (info && !GuestSocketReadGuest_nid_no_patch(&servers, info, sizeof(servers))) return fail(NET_EFAULT); + if (info && servers.primaryAddress == 0) return fail(NET_EINVAL); + std::lock_guard lock(g_dnsLock); + g_dnsOverridden = info != nullptr; + g_dnsOverride = servers; return 0; } -int APS5_VABI sceNetShowIfconfigWithMemory(void) { +int APS5_VABI sceNetShowIfconfigWithMemory(std::int32_t poolId) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceNetShowPolicy(void) { - NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetShowPolicyWithMemory(void) { +int APS5_VABI sceNetShowPolicyWithMemory(std::int32_t poolId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2050,38 +2316,40 @@ int APS5_VABI sceNetShowRoute(void) { return 0; } -int APS5_VABI sceNetShowRouteWithMemory(void) { +int APS5_VABI sceNetShowRouteWithMemory(std::int32_t poolId) { + std::lock_guard lk(g_mutex); + if (g_pools.find(poolId) == g_pools.end()) return fail(NET_EBADF); + return 0; +} + +int APS5_VABI sceNetResolverConnectAbort(NetResolverConnection* connection, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetResolverConnectAbort(void) { +int APS5_VABI sceNetResolverConnectCreate(const char* name, std::int32_t socketType, NetResolverConnection* connection, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetResolverConnectCreate(void) { +int APS5_VABI sceNetResolverConnect(NetResolverConnection* connection, const char* hostname, std::uint16_t port, const NetResolverConnectOption* option) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetResolverConnect(void) { +int APS5_VABI sceNetResolverConnectDestroy(NetResolverConnection* connection) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetResolverConnectDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNetGetSockInfo6(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNetSetDns6Info(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNetSetDns6Info(const NetDns6Info* info, std::uint32_t flags) { + if (!g_net_inited) return fail(NET_ENOTINIT); + if (flags != 0) return fail(NET_EINVAL); + NetDns6Info servers{}; + if (info && !GuestSocketReadGuest_nid_no_patch(&servers, info, sizeof(servers))) return fail(NET_EFAULT); + if (info && IsZeroAddress(servers.primaryAddress)) return fail(NET_EINVAL); + std::lock_guard lock(g_dnsLock); + g_dns6Override = servers; return 0; } @@ -2089,86 +2357,93 @@ int APS5_VABI sceNetShowRoute6(void) { return 0; } -int APS5_VABI sceNetShowRoute6WithMemory(void) { +int APS5_VABI sceNetShowRoute6WithMemory(std::int32_t poolId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetAllocateAllRouteInfo(void) { +NetRouteInfo* APS5_VABI sceNetAllocateAllRouteInfo(std::int32_t interfaceId, std::int32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetAllocateAllRouteInfo6(void) { +NetRouteInfo* APS5_VABI sceNetAllocateAllRouteInfo6(std::int32_t interfaceId, std::int32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetControl(void) { +int APS5_VABI sceNetControl(std::uint32_t interfaceId, std::int32_t interfaceIndex, std::uint32_t command, void* data, std::uint32_t dataSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDbgInit(void) { +int APS5_VABI sceNetDbgInit(NetDebugParameter* parameter, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetEventCallbackCreate(void) { +int APS5_VABI sceNetEventCallbackCreate(std::uint32_t interfaceId, NetEventCallback callback, void* userData, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetEventCallbackDestroy(void) { +int APS5_VABI sceNetEventCallbackDestroy(std::uint32_t interfaceId, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetEventCallbackGetError(void) { +int APS5_VABI sceNetEventCallbackGetError(std::uint32_t interfaceId, std::int32_t* error, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetEventCallbackWaitCB(void) { +int APS5_VABI sceNetEventCallbackWaitCB(std::int32_t timeout, void* waitData, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetFreeAllRouteInfo(void) { +int APS5_VABI sceNetFreeAllRouteInfo(void* routes) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetArpInfo(void) { +int APS5_VABI sceNetGetArpInfo(NetArpInfo* info, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetAppMemInfo(void) { +int APS5_VABI sceNetGetAppMemInfo(NetAppMemoryInfo* info, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetDnsInfo(std::uint32_t* info, int flags) { - if (flags != 0) throw std::runtime_error("sceNetGetDnsInfo: flags " + std::to_string(flags) + " are not supported"); - if (!info) throw std::runtime_error("sceNetGetDnsInfo: a null info is not supported"); +int APS5_VABI sceNetGetDnsInfo(NetDnsInfo* info, std::uint32_t flags) { + if (!g_net_inited) return fail(NET_ENOTINIT); + if (flags != 0 || !info) return fail(NET_EINVAL); + { + std::lock_guard lock(g_dnsLock); + if (g_dnsOverridden) { + *info = g_dnsOverride; + return info->secondaryAddress == 0 ? 1 : 2; + } + } std::array servers{}; const std::size_t count = std::min(NetCtlHostDnsServers_nid_no_patch(servers.data(), servers.size()), servers.size()); std::memcpy(info, servers.data(), sizeof(servers)); return static_cast(count); } -int APS5_VABI sceNetGetIfList(void) { +int APS5_VABI sceNetGetIfList(std::int32_t interfaceId, NetInterfaceInfo* interfaces, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetIfListEx(void) { +int APS5_VABI sceNetGetIfListEx(std::int32_t interfaceId, NetInterfaceInfo* interfaces, std::uint32_t capacity, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetIfListOnce(void) { +int APS5_VABI sceNetGetIfListOnce(std::int32_t interfaceId, NetInterfaceStatistics* statistics, std::uint32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2183,17 +2458,18 @@ const char* APS5_VABI sceNetGetIfName(std::uint32_t index) { return names[index]; } -int APS5_VABI sceNetGetIfnameNumList(void) { +int APS5_VABI sceNetGetIfnameNumList(std::uint32_t* interfaceIds, std::int32_t capacity, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetNameToIndex(void) { +int APS5_VABI sceNetGetNameToIndex(std::uint32_t interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceNetGetRandom(std::uint32_t* output) { + if (!g_net_inited) return fail(NET_ENOTINIT); if (output == nullptr) return fail(NET_EINVAL); static std::mutex mutex; static std::random_device device; @@ -2202,12 +2478,12 @@ int APS5_VABI sceNetGetRandom(std::uint32_t* output) { return 0; } -int APS5_VABI sceNetGetRouteInfo(void) { +int APS5_VABI sceNetGetRouteInfo(std::int32_t family, NetRouteInfo* routes, std::int32_t capacity, std::uint8_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetsockoptEx(void) { +int APS5_VABI sceNetGetsockoptEx(int socketId, int level, int option, void* value, std::uint32_t* valueSize) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2216,12 +2492,12 @@ void APS5_VABI sceNetGetSystemTime(std::uint64_t* output) { if (output != nullptr) *output = sceKernelGetProcessTime(); } -int APS5_VABI sceNetGetSystemMemInfo(void) { +int APS5_VABI sceNetGetSystemMemInfo(NetSystemMemoryInfo* info, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetInitParam(void) { +int APS5_VABI sceNetInitParam(const NetInitializeParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2230,32 +2506,59 @@ int APS5_VABI sceNetInetPtonEx(int af, const char* src, void* dst, int) { if (af != NET_AF_INET) { return sceNetInetPton(af, src, dst); } - if (src == nullptr || dst == nullptr) { + if (src == nullptr) { return fail(NET_EINVAL); } std::uint32_t address = 0; if (!parse_ipv4_parts(src, address)) { return 0; } + if (dst == nullptr) { + return 1; + } const std::array bytes{static_cast(address >> 24), static_cast(address >> 16), static_cast(address >> 8), static_cast(address)}; std::memcpy(dst, bytes.data(), bytes.size()); return 1; } -int APS5_VABI sceNetIoctl(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNetIoctl(int socketId, std::uint32_t command, void* argument) { + constexpr std::uint32_t nonblockingRequest = 0x8004667e; + constexpr std::uint32_t readableRequest = 0x4004667f; + std::shared_ptr native; + { + std::lock_guard lk(g_mutex); + const auto it = g_socks.find(socketId); + if (it != g_socks.end()) native = it->second.native; + } + if (!native) { + if (!GuestSocketIsOpen_nid_no_patch(socketId)) return fail(NET_EBADF); + return ioctl_nid_postfix(socketId, command, argument) == 0 ? 0 : fail(*__error_nid_postfix()); + } + if (command != nonblockingRequest && command != readableRequest) return fail(NET_ENOTTY); + if (!argument) return fail(NET_EFAULT); + if (command == nonblockingRequest) { + const int enabled = *static_cast(argument) != 0; + return sceNetSetsockopt(socketId, NET_SOL_SOCKET, NET_SO_NBIO, &enabled, sizeof(enabled)); + } +#ifdef _WIN32 + u_long available = 0; + if (ioctlsocket(native->value, FIONREAD, &available) != 0) return fail(native_error()); +#else + int available = 0; + if (::ioctl(native->value, FIONREAD, &available) != 0) return fail(native_error()); +#endif + *static_cast(argument) = static_cast(available); return 0; } -void* APS5_VABI sceNetMemoryAllocate(std::int64_t size, int flags) { +void* APS5_VABI sceNetMemoryAllocate(std::int32_t size, int flags) { constexpr int zeroFill = 2; - if ((flags & ~zeroFill) != 0) - throw std::runtime_error("sceNetMemoryAllocate: flags " + std::to_string(flags) + " are not supported"); + constexpr std::size_t blockAlignment = 64; if (size <= 0) return nullptr; void* memory = nullptr; try { - memory = GuestHeap::GuestHeapAllocate_nid_postfix(static_cast(size)); + memory = GuestHeap::GuestHeapAlign_nid_postfix(blockAlignment, static_cast(size)); } catch (const std::bad_alloc&) { return nullptr; } catch (const std::length_error&) { @@ -2269,172 +2572,185 @@ void APS5_VABI sceNetMemoryFree(void* memory) { GuestHeap::GuestHeapFree_nid_postfix(memory); } -int APS5_VABI sceNetSetDnsInfoToKernel(void) { +int APS5_VABI sceNetSetDnsInfoToKernel(const NetDnsInfo* info, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSetsockoptEx(void) { +int APS5_VABI sceNetSetsockoptEx(int socketId, int level, int option, const void* value, std::uint32_t valueSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSocketInternal(void) { +int APS5_VABI sceNetSocketInternal(const char* name, int domain, int type, int protocol) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSyncCreate(void) { +int APS5_VABI sceNetSyncCreate(NetSyncObject* sync, std::uint32_t signals, const char* name) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSyncDestroy(void) { +int APS5_VABI sceNetSyncDestroy(NetSyncObject* sync) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSyncGet(void) { +NetSyncObject* APS5_VABI sceNetSyncGet(std::uint32_t index) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSyncSignal(void) { +int APS5_VABI sceNetSyncSignal(NetSyncObject* sync, std::uint32_t signals) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSyncWait(void) { +int APS5_VABI sceNetSyncWait(NetSyncObject* sync, std::uint32_t* timeout) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSysctl(void) { +int APS5_VABI sceNetSysctl(const int* name, std::uint32_t nameLength, void* oldValue, std::size_t* oldSize, const void* newValue, std::size_t newSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetThreadCreate(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNetThreadCreate(Pthread* thread, const NetThreadParameter* parameter, const char* name) { + if (!parameter) return fail(NET_EINVAL); + Pthread created = nullptr; + const int result = scePthreadCreate(&created, nullptr, RunNetThread, const_cast(parameter), name != nullptr ? name : ""); + if (result != 0) return result; + if (thread) *thread = created; return 0; } -int APS5_VABI sceNetThreadExit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceNetThreadExit() { + scePthreadExit(nullptr); } -int APS5_VABI sceNetThreadJoin(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetThreadJoin(void* thread) { + return scePthreadJoin(static_cast(thread), nullptr); } int APS5_VABI sceNetUsleep(int microseconds) { - if (microseconds < 0) return fail(NET_EINVAL); - return sceKernelUsleep_nid_postfix(static_cast(microseconds)); + if (!g_net_inited) return fail(NET_ENOTINIT); + if (microseconds < 0) { + std::fprintf(stdout, "[LIBNET] usleep %d\n", microseconds); + std::fflush(stdout); + return fail(NET_EINVAL); + } + const int result = sceKernelUsleep_nid_postfix(static_cast(microseconds)); + if (result < 0) set_errno((result & 0xFF00) == 0x100 ? result & 0xFF : 205); + return result; } -int APS5_VABI sceNetShowIfconfigForBuffer(void) { +int APS5_VABI sceNetShowIfconfigForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetShowRouteForBuffer(void) { +int APS5_VABI sceNetShowRouteForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetShowNetstatForBuffer(void) { +int APS5_VABI sceNetShowNetstatForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetShowNetstatEx(void) { +int APS5_VABI sceNetShowNetstatEx(std::uint32_t flags) { + (void)flags; + return 0; +} + +int APS5_VABI sceNetShowNetstatExForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetShowNetstatExForBuffer(void) { +const char* APS5_VABI sceNetInetNtopWithScopeId(std::int32_t family, const void* address, char* text, std::uint32_t size, std::uint32_t scopeId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetInetNtopWithScopeId(void) { +int APS5_VABI sceNetInetPtonWithScopeId(std::int32_t family, const char* text, void* address, std::uint32_t* scopeId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetInetPtonWithScopeId(void) { +int APS5_VABI sceNetGetDns6Info(NetDns6Info* info, std::uint32_t flags) { + if (!g_net_inited) return fail(NET_ENOTINIT); + if (flags != 0 || !info) return fail(NET_EINVAL); + std::lock_guard lock(g_dnsLock); + *info = g_dns6Override; + if (IsZeroAddress(info->primaryAddress)) return 0; + return IsZeroAddress(info->secondaryAddress) ? 1 : 2; +} + +int APS5_VABI sceNetSetDns6InfoToKernel(const NetDns6Info* info, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetDns6Info(void) { +int APS5_VABI sceNetShowRoute6ForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetSetDns6InfoToKernel(void) { +int APS5_VABI sceNetConfigGetIfFlags(std::uint32_t interfaceId, std::uint32_t* flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetShowRoute6ForBuffer(void) { +int APS5_VABI sceNetConfigSetIfFlags(std::uint32_t interfaceId, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigGetIfFlags(void) { +int APS5_VABI sceNetConfigUnsetIfFlags(std::uint32_t interfaceId, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigSetIfFlags(void) { +int APS5_VABI sceNetConfigDelIfaddr(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigUnsetIfFlags(void) { +int APS5_VABI sceNetConfigDownInterface(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigDelIfaddr(void) { +int APS5_VABI sceNetConfigSetDefaultRoute(const std::uint32_t* gateway) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigDownInterface(void) { +int APS5_VABI sceNetConfigSetDefaultRouteDev(const std::uint32_t* gateway) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigSetDefaultRoute(void) { +int APS5_VABI sceNetConfigSetIfaddr(int interfaceId, const std::uint32_t* address, const std::uint32_t* broadcastAddress, const std::uint32_t* netmask) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigSetDefaultRouteDev(void) { +int APS5_VABI sceNetConfigSetIfLinkLocalAddr6(std::uint32_t interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigSetIfaddr(void) { +int APS5_VABI sceNetConfigUpInterface(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigSetIfLinkLocalAddr6(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNetConfigUpInterface(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNetConfigUpInterfaceWithFlags(void) { +int APS5_VABI sceNetConfigUpInterfaceWithFlags(std::uint32_t interfaceId, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2444,62 +2760,62 @@ int APS5_VABI sceNetConfigCleanUpAllInterfaces(void) { return 0; } -int APS5_VABI sceNetConfigFlushRoute(void) { +int APS5_VABI sceNetConfigFlushRoute(std::uint32_t interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigFlushRoute6(void) { +int APS5_VABI sceNetConfigFlushRoute6(std::uint32_t interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigAddIfaddr(void) { +int APS5_VABI sceNetConfigAddIfaddr(int interfaceId, const std::uint32_t* address, const std::uint32_t* broadcastAddress, const std::uint32_t* netmask) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigGetIfaddr(void) { +int APS5_VABI sceNetConfigGetIfaddr(std::int32_t interfaceId, NetIpv4InterfaceAddress* addresses, std::int32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigSetIfmtu(void) { +int APS5_VABI sceNetConfigSetIfmtu(std::uint32_t interfaceId, int mtu) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigAddArp(void) { +int APS5_VABI sceNetConfigAddArp(const NetEtherAddr* etherAddress, const std::uint32_t* address) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigAddArpWithInterface(void) { +int APS5_VABI sceNetConfigAddArpWithInterface(int interfaceId, const NetEtherAddr* etherAddress, const std::uint32_t* address) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigAddMRoute(void) { +int APS5_VABI sceNetConfigAddMRoute(const std::uint32_t* address) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigAddRoute(void) { +int APS5_VABI sceNetConfigAddRoute(const std::uint32_t* destination, const std::uint32_t* gateway, const std::uint32_t* netmask, int hostRoute) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigAddRouteWithInterface(void) { +int APS5_VABI sceNetConfigAddRouteWithInterface(int interfaceId, const std::uint32_t* destination, const std::uint32_t* gateway, const std::uint32_t* netmask, int hostRoute) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigDelArp(void) { +int APS5_VABI sceNetConfigDelArp(const NetEtherAddr* etherAddress, const std::uint32_t* address) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigDelArpWithInterface(void) { +int APS5_VABI sceNetConfigDelArpWithInterface(int interfaceId, const NetEtherAddr* etherAddress, const std::uint32_t* address) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2514,52 +2830,52 @@ int APS5_VABI sceNetConfigDelDefaultRouteDev(void) { return 0; } -int APS5_VABI sceNetConfigDelMRoute(void) { +int APS5_VABI sceNetConfigDelMRoute(const std::uint32_t* address) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigDelRoute(void) { +int APS5_VABI sceNetConfigDelRoute(const std::uint32_t* destination, const std::uint32_t* gateway, const std::uint32_t* netmask) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigGetDefaultRoute(void) { +int APS5_VABI sceNetConfigGetDefaultRoute(std::uint32_t* gateway) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDhcpGetAutoipInfo(void) { +int APS5_VABI sceNetDhcpGetAutoipInfo(std::int32_t interfaceId, NetAutoIpInfo* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDhcpGetInfo(void) { +int APS5_VABI sceNetDhcpGetInfo(std::int32_t interfaceId, NetDhcpInfo* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDhcpGetInfoEx(void) { +int APS5_VABI sceNetDhcpGetInfoEx(std::int32_t interfaceId, std::int32_t type, NetDhcpOptionInfo* info, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDhcpStart(void) { +int APS5_VABI sceNetDhcpStart(std::uint32_t interfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2, NetDhcpStartParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDhcpStop(void) { +int APS5_VABI sceNetDhcpStop(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigAddIfaddr6(void) { +int APS5_VABI sceNetConfigAddIfaddr6(int interfaceId, const NetIpv6Address* address, std::uint8_t prefixLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigDelIfaddr6(void) { +int APS5_VABI sceNetConfigDelIfaddr6(int interfaceId, const NetIpv6Address* address) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2569,57 +2885,57 @@ int APS5_VABI sceNetConfigDelDefaultRoute6(void) { return 0; } -int APS5_VABI sceNetConfigSetIfaddr6(void) { +int APS5_VABI sceNetConfigSetIfaddr6(int interfaceId, const NetIpv6Address* address, std::uint8_t prefixLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigSetIfaddr6WithFlags(void) { +int APS5_VABI sceNetConfigSetIfaddr6WithFlags(int interfaceId, const NetIpv6Address* address, std::uint8_t prefixLength, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigSetDefaultRoute6(void) { +int APS5_VABI sceNetConfigSetDefaultRoute6(int interfaceId, const NetIpv6Address* gateway) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigGetIfaddr6(void) { +int APS5_VABI sceNetConfigGetIfaddr6(int interfaceId, NetIpv6InterfaceAddress* addresses, std::uint32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigGetDefaultRoute6(void) { +int APS5_VABI sceNetConfigGetDefaultRoute6(NetIpv6Address* gateway) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigDelRoute6(void) { +int APS5_VABI sceNetConfigDelRoute6(const NetIpv6Address* destination, std::uint8_t prefixLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigAddRoute6(void) { +int APS5_VABI sceNetConfigAddRoute6(const NetIpv6Address* destination, const NetIpv6Address* gateway, std::uint8_t prefixLength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetAddrConfig6Start(void) { +int APS5_VABI sceNetAddrConfig6Start(std::uint32_t interfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2, const NetAddress6StartParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetAddrConfig6Stop(void) { +int APS5_VABI sceNetAddrConfig6Stop(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetAddrConfig6GetInfo(void) { +int APS5_VABI sceNetAddrConfig6GetInfo(std::int32_t interfaceId, NetAddress6Info* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetInfoDumpStart(void) { +int APS5_VABI sceNetInfoDumpStart(std::int32_t threadParameter1, std::int32_t threadParameter2, const NetInfoDumpParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2629,167 +2945,167 @@ int APS5_VABI sceNetInfoDumpStop(void) { return 0; } -int APS5_VABI sceNetDhcpdStart(void) { +int APS5_VABI sceNetDhcpdStart(std::uint32_t interfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2, const NetDhcpdStartParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDhcpdStop(void) { +int APS5_VABI sceNetDhcpdStop(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDuplicateIpStart(void) { +int APS5_VABI sceNetDuplicateIpStart(std::int32_t interfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDuplicateIpStop(void) { +int APS5_VABI sceNetDuplicateIpStop(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocClearWakeOnWlan(void) { +int APS5_VABI sceNetConfigWlanAdhocClearWakeOnWlan(std::uint32_t interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocCreate(void) { +int APS5_VABI sceNetConfigWlanAdhocCreate(std::int32_t interfaceId, NetWlanAdhocParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocGetWakeOnWlanInfo(void) { +int APS5_VABI sceNetConfigWlanAdhocGetWakeOnWlanInfo(std::int32_t interfaceId, NetWlanWakeInfo* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocJoin(void) { +int APS5_VABI sceNetConfigWlanAdhocJoin(std::int32_t interfaceId, NetWlanAdhocParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocLeave(void) { +int APS5_VABI sceNetConfigWlanAdhocLeave(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan(void) { +int APS5_VABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan(std::uint32_t interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo(void) { +int APS5_VABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo(std::int32_t interfaceId, NetWlanPspWakeInfo* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan(void) { +int APS5_VABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan(std::int32_t interfaceId, NetWlanPspWakeParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocScanJoin(void) { +int APS5_VABI sceNetConfigWlanAdhocScanJoin(std::int32_t interfaceId, const NetWlanAdhocScanParameter* parameter, void* buffer, std::size_t bufferSize, NetWlanJoinResult** result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocSetExtInfoElement(void) { +int APS5_VABI sceNetConfigWlanAdhocSetExtInfoElement(std::int32_t interfaceId, NetWlanInformationElement* parameter, std::int32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanAdhocSetWakeOnWlan(void) { +int APS5_VABI sceNetConfigWlanAdhocSetWakeOnWlan(std::int32_t interfaceId, NetWlanWakeParameter* parameter, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanApStart(void) { +int APS5_VABI sceNetConfigWlanApStart(std::int32_t interfaceId, NetWlanApParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanApStop(void) { +int APS5_VABI sceNetConfigWlanApStop(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanBackgroundScanQuery(void) { +int APS5_VABI sceNetConfigWlanBackgroundScanQuery(std::int32_t interfaceId, void* buffer, std::int32_t size, NetWlanScanResult** results, std::size_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanBackgroundScanStart(void) { +int APS5_VABI sceNetConfigWlanBackgroundScanStart(std::int32_t interfaceId, NetWlanScanParameter* parameter, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanBackgroundScanStop(void) { +int APS5_VABI sceNetConfigWlanBackgroundScanStop(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanDiagGetDeviceInfo(void) { +int APS5_VABI sceNetConfigWlanDiagGetDeviceInfo(std::int32_t interfaceId, NetWlanDeviceInfo* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanDiagSetAntenna(void) { +int APS5_VABI sceNetConfigWlanDiagSetAntenna(std::int32_t interfaceId, NetWlanAntennaParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanDiagSetTxFixedRate(void) { +int APS5_VABI sceNetConfigWlanDiagSetTxFixedRate(std::int32_t interfaceId, NetWlanRateParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanGetDeviceConfig(void) { +int APS5_VABI sceNetConfigWlanGetDeviceConfig(std::int32_t interfaceId, NetWlanDeviceConfiguration* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanInfraGetRssiInfo(void) { +int APS5_VABI sceNetConfigWlanInfraGetRssiInfo(std::int32_t interfaceId, NetWlanRssiInfo* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanInfraLeave(void) { +int APS5_VABI sceNetConfigWlanInfraLeave(int interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanInfraScanJoin(void) { +int APS5_VABI sceNetConfigWlanInfraScanJoin(std::int32_t interfaceId, const NetWlanInfraScanParameter* parameter, void* buffer, std::size_t bufferSize, NetWlanJoinResult** result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanScan(void) { +int APS5_VABI sceNetConfigWlanScan(std::int32_t interfaceId, const NetWlanScanParameter* parameter, std::size_t parameterSize, void* buffer, std::size_t bufferSize, NetWlanScanResult** results, std::size_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigWlanSetDeviceConfig(void) { +int APS5_VABI sceNetConfigWlanSetDeviceConfig(std::int32_t interfaceId, NetWlanDeviceConfiguration* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigEtherGetLinkMode(void) { +int APS5_VABI sceNetConfigEtherGetLinkMode(std::uint32_t interfaceId, int* mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigEtherPostPlugInOutEvent(void) { +int APS5_VABI sceNetConfigEtherPostPlugInOutEvent(std::uint32_t interfaceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigEtherSetLinkMode(void) { +int APS5_VABI sceNetConfigEtherSetLinkMode(std::uint32_t interfaceId, int mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetPppoeStart(void) { +int APS5_VABI sceNetPppoeStart(std::uint32_t interfaceId, std::int32_t pppoeInterfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2, const NetPppoeStartParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2799,27 +3115,27 @@ int APS5_VABI sceNetPppoeStop(void) { return 0; } -int APS5_VABI sceNetBandwidthControlSetDefaultParam(void) { +int APS5_VABI sceNetBandwidthControlSetDefaultParam(const NetBandwidthParameter* parameter, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetBandwidthControlGetDefaultParam(void) { +int APS5_VABI sceNetBandwidthControlGetDefaultParam(NetBandwidthParameter* parameter, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetBandwidthControlGetDataTraffic(void) { +int APS5_VABI sceNetBandwidthControlGetDataTraffic(std::int32_t interfaceId, NetDataTraffic* traffic, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigSetDefaultScope(void) { +int APS5_VABI sceNetConfigSetDefaultScope(std::uint32_t interfaceId, const char* interfaceName, std::int32_t scopeId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetConfigRoutingStart(void) { +int APS5_VABI sceNetConfigRoutingStart(std::uint32_t firstInterfaceId, std::uint32_t secondInterfaceId, const NetRoutingAddressParameter* addressParameter, NetRoutingServerParameter* serverParameter, const NetRoutingRuleParameter* ruleParameter) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2834,64 +3150,57 @@ int APS5_VABI sceNetConfigRoutingShowRoutingConfig(void) { return 0; } -int APS5_VABI sceNetConfigRoutingShowtCtlVar(void) { +int APS5_VABI sceNetConfigRoutingShowtCtlVar(const char* name) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetGetStatisticsInfoInternal(void) { +int APS5_VABI sceNetGetStatisticsInfoInternal(NetStatisticsInfoInternal* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDumpAbort(void) { +int APS5_VABI sceNetDumpAbort(std::int32_t dumpId, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDumpCreate(void) { +int APS5_VABI sceNetDumpCreate(const char* name, std::int32_t bufferSize, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDumpSetFilterParam(void) { +int APS5_VABI sceNetDumpSetFilterParam(std::int32_t socket, const NetDumpFilterParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDumpDestroy(void) { +int APS5_VABI sceNetDumpDestroy(std::int32_t dumpId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetDumpRead(void) { +int APS5_VABI sceNetDumpRead(std::int32_t dumpId, void* buffer, std::int32_t bufferSize, std::uint32_t* flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetEmulationDebugSettingsSet(void) { +int APS5_VABI sceNetEmulationDebugSettingsSet(const NetEmulationParameter* parameter, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetEmulationDebugSettingsGet(void) { +int APS5_VABI sceNetEmulationDebugSettingsGet(NetEmulationParameter* parameter, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetEmulationSet(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetEmulationSet(const NetEmulationParameter*, std::uint32_t) { + return fail(NET_EOPNOTSUPP); } -int APS5_VABI sceNetEmulationGet(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNetEtherStrton(const char* str, void* address) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetEmulationGet(NetEmulationParameter*, std::uint32_t) { + return fail(NET_EOPNOTSUPP); } } diff --git a/core/libs/prx/libSceNet/include/Net.hpp b/core/libs/prx/libSceNet/include/Net.hpp new file mode 100644 index 0000000000..deffa39409 --- /dev/null +++ b/core/libs/prx/libSceNet/include/Net.hpp @@ -0,0 +1,438 @@ +#pragma once + +#include "SceTypes.hpp" + +struct NetRoutingRule { + std::int32_t protocol; + std::uint32_t firstAddress; + std::uint16_t firstPort; + std::uint16_t reserved; + std::uint32_t secondAddress; + std::uint16_t secondPort; + std::uint16_t flags; +}; + +struct NetIovec { + void* base; + std::uint64_t length; +}; + +struct NetResolverRecord { + std::uint32_t address; + std::uint8_t address6[12]; + std::int32_t family; + std::int32_t reserved[3]; +}; + +struct NetWlanInformationElement; +struct NetWlanWakeParameter; +struct NetWlanScanParameter; +struct NetWlanScanResult; +struct NetWlanAdhocScanParameter; +struct NetWlanInfraScanParameter; +struct NetWlanJoinResult; +struct NetDumpFilterParameter; +struct NetWlanAdhocParameter; +struct NetWlanWakeInfo; +struct NetWlanPspWakeInfo; +struct NetWlanPspWakeParameter; +struct NetWlanApParameter; +struct NetWlanDeviceInfo; +struct NetWlanAntennaParameter; +struct NetWlanRateParameter; +struct NetWlanDeviceConfiguration; +struct NetWlanRssiInfo; +struct NetDhcpInfo; +struct NetAutoIpInfo; +struct NetAddress6Info; + +struct NetDhcpOptionInfo { + std::uint32_t option; + std::uint32_t capacity; + void* data; + std::uint32_t size; + std::uint32_t reserved; +}; + +struct NetSockInfo6 { + char name[32]; + std::int32_t pid; + std::int32_t s; + std::int8_t socket_type; + std::int8_t policy; + std::int8_t priority; + std::int8_t reserved8; + std::int32_t recv_queue_length; + std::int32_t send_queue_length; + std::uint32_t local_adr; + std::uint32_t remote_adr; + std::uint16_t local_port; + std::uint16_t remote_port; + std::uint16_t local_vport; + std::uint16_t remote_vport; + std::int32_t state; + std::int32_t flags; + std::int32_t tx_bps; + std::int32_t rx_bps; + std::int32_t max_tx_bps; + std::int32_t max_rx_bps; + std::int32_t tx_vbps; + std::int32_t rx_vbps; + std::int32_t recv_buffer_size; + std::int32_t send_buffer_size; + std::uint8_t local_adr6[16]; + std::uint8_t remote_adr6[16]; + std::int32_t tx_drops; + std::int32_t rx_drops; + std::int32_t tx_wait; + std::int32_t reserved[2]; +}; +struct NetStatisticsInfo; +struct NetInterfaceStats; +struct NetRouteInfo; +struct NetDebugParameter; +struct NetArpInfo; +struct NetAppMemoryInfo; +struct NetSystemMemoryInfo; +struct NetBandwidthParameter; +struct NetDataTraffic; +struct NetEmulationParameter; +struct NetResolverConnection; +struct NetThreadParameter { + void (APS5_VABI *entry)(void*); + void* arg; + std::uint64_t reserved; + std::uint32_t flags; +}; +struct NetIpv4InterfaceAddress; +struct NetStatisticsInfoInternal; +struct NetInitializeParameter; +struct NetDhcpStartParameter; +struct NetAddress6StartParameter; + +struct NetDhcpdStartParameter { + std::uint32_t addresses[5]; + std::uint32_t maxClients; +}; + +struct NetPppoeStartParameter { + std::uint32_t dnsAddresses[2]; + char userName[128]; + char password[128]; + std::int32_t mtu; + std::uint32_t flags; +}; + +struct NetRoutingAddressParameter { + std::uint32_t address; + std::uint32_t mask; +}; + +struct NetRoutingServerParameter { + std::int32_t count; + std::uint32_t localAddresses[2]; + std::uint32_t remoteAddresses[2]; +}; + +struct NetRoutingRuleParameter { + std::int32_t count; + const NetRoutingRule* rules; +}; + +struct NetInfoDumpParameter { + std::uint32_t flags; + std::uint32_t intervalSeconds; + char path[64]; +}; + +struct NetInterfaceInfo; +struct NetInterfaceStatistics; + +struct NetResolverConnectOption { + std::int32_t connectionTimeout; + std::int32_t resolverTimeout; + std::int32_t resolverRetryCount; + std::uint32_t reserved; +}; + +struct NetSyncObject { + void* mutex; + void* condition; + std::uint32_t signals; + std::uint32_t reserved; +}; + +struct NetIpv6Address { + std::uint8_t bytes[16]; +}; + +struct NetDnsInfo { + std::uint32_t primaryAddress; + std::uint32_t secondaryAddress; +}; + +struct NetDns6Info { + NetIpv6Address primaryAddress; + NetIpv6Address secondaryAddress; +}; + +struct NetIpv6InterfaceAddress { + NetIpv6Address address; + std::uint8_t prefixLength; + std::uint8_t reserved[3]; +}; + +struct NetMemoryPoolStats { + std::size_t pool_size; + std::size_t max_inuse_size; + std::size_t current_inuse_size; +}; + +struct NetMsghdr { + void* name; + std::uint32_t name_length; + NetIovec* iov; + int iov_length; + void* control; + std::uint32_t control_length; + int flags; +}; + +struct NetResolverInfo { + NetResolverRecord records[10]; + std::int32_t count; + std::int32_t count4; + std::int32_t reserved[14]; +}; + +using NetAddressCallback = void (APS5_VABI *)(void* userArg, std::int32_t event, std::int32_t error); + +using NetEventCallback = std::uint64_t (APS5_VABI*)(std::uint32_t interfaceId, std::uint64_t events, void* userData, void* waitData); + +extern "C" { + +int* APS5_VABI sceNetErrnoLoc(void); +int APS5_VABI sceNetShowNetstat(void); +int APS5_VABI sceNetInit_nid_postfix(void); +int APS5_VABI sceNetTerm(void); +int APS5_VABI sceNetPoolCreate(const char* name, int size, int flags); +int APS5_VABI sceNetPoolDestroy(int memid); +int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol); +int APS5_VABI sceNetSocketClose(int s); +int APS5_VABI sceNetSocketAbort(int s, int flags); +int APS5_VABI sceNetBind_nid_postfix(int s, const void* addr, uint32_t addrlen); +int APS5_VABI sceNetListen(int s, int backlog); +int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen); +int APS5_VABI sceNetConnect(int s, const void* addr, uint32_t addrlen); +int64_t APS5_VABI sceNetRecv(int s, void* buf, size_t len, int flags); +int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* from, uint32_t* fromlen); +int64_t APS5_VABI sceNetSend(int s, const void* buf, size_t len, int flags); +int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, const void* to, uint32_t tolen); +int64_t APS5_VABI sceNetSendmsg(int s, const NetMsghdr* msg, int flags); +int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags); +int APS5_VABI sceNetShutdown(int s, int how); +int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval, uint32_t optlen); +int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint32_t* optlen); +int APS5_VABI sceNetGetsockname(int s, void* addr, uint32_t* addrlen); +int APS5_VABI sceNetGetpeername(int s, void* addr, uint32_t* addrlen); +int APS5_VABI sceNetGetSockInfo(int s, void* info, int n, int flags); +int APS5_VABI sceNetEpollCreate(const char* name, int flags); +int APS5_VABI sceNetShowNetstatWithMemory(int memid); +int APS5_VABI sceNetGetMemoryPoolStats(int memid, NetMemoryPoolStats* stats); +int APS5_VABI sceNetEpollDestroy(int eid); +int APS5_VABI sceNetEpollAbort(int eid, int flags); +int APS5_VABI sceNetEpollControl(int eid, int op, int id, const NetEpollEvent* event); +int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int timeout); +uint32_t APS5_VABI sceNetHtonl_nid_postfix(uint32_t host32); +uint16_t APS5_VABI sceNetHtons_nid_postfix(uint16_t host16); +uint64_t APS5_VABI sceNetHtonll(std::uint64_t host64); +uint64_t APS5_VABI sceNetNtohll(std::uint64_t net64); +uint32_t APS5_VABI sceNetNtohl_nid_postfix(uint32_t net32); +uint16_t APS5_VABI sceNetNtohs_nid_postfix(uint16_t net16); +int APS5_VABI sceNetInetPton(int af, const char* src, void* dst); +const char* APS5_VABI sceNetInetNtop(int af, const void* src, char* dst, uint32_t size); +int APS5_VABI sceNetEtherNtostr(const NetEtherAddr* n, char* str, size_t len); +int APS5_VABI sceNetEtherStrton(const char* text, NetEtherAddr* address); +int APS5_VABI sceNetGetMacAddress(NetEtherAddr* addr, int flags); +int APS5_VABI sceNetResolverCreate(const char* name, int memid, int flags); +int APS5_VABI sceNetResolverDestroy(int rid); +int APS5_VABI sceNetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout, int retry, int flags); +int APS5_VABI sceNetResolverStartNtoa6(int rid, const char* hostname, void* addr, int timeout, int retry, int flags); +int APS5_VABI sceNetResolverStartNtoaMultipleRecords(int rid, const char* hostname, NetResolverInfo* info, int timeout, int retry, int flags); +int APS5_VABI sceNetResolverStartNtoaMultipleRecordsEx(int rid, const char* hostname, NetResolverInfo* info, int timeout, int retry, int flags); +int APS5_VABI sceNetResolverStartAton(int rid, const void* addr, char* hostname, int len, int timeout, int retry, int flags); +int APS5_VABI sceNetResolverStartAton6(int rid, const void* addr, char* hostname, int len, int timeout, int retry, int flags); +int APS5_VABI sceNetResolverGetError(int rid, int* status); +int APS5_VABI sceNetResolverAbort(int rid, int flags); +int APS5_VABI sceNetShowIfconfig(); +int APS5_VABI sceNetClearDnsCache(int flags); +int APS5_VABI sceNetGetStatisticsInfo(NetStatisticsInfo* info, std::uint32_t flags); +int APS5_VABI sceNetGetInterfaceStats(NetInterfaceStats* stats, std::uint32_t flags); +int APS5_VABI sceNetSetDnsInfo(const NetDnsInfo* info, std::uint32_t flags); +int APS5_VABI sceNetShowIfconfigWithMemory(std::int32_t poolId); +int APS5_VABI sceNetShowPolicy(void); +int APS5_VABI sceNetShowPolicyWithMemory(std::int32_t poolId); +int APS5_VABI sceNetShowRoute(void); +int APS5_VABI sceNetShowRouteWithMemory(std::int32_t poolId); +int APS5_VABI sceNetResolverConnectAbort(NetResolverConnection* connection, std::uint32_t flags); +int APS5_VABI sceNetResolverConnectCreate(const char* name, std::int32_t socketType, NetResolverConnection* connection, std::uint32_t flags); +int APS5_VABI sceNetResolverConnect(NetResolverConnection* connection, const char* hostname, std::uint16_t port, const NetResolverConnectOption* option); +int APS5_VABI sceNetResolverConnectDestroy(NetResolverConnection* connection); +int APS5_VABI sceNetGetSockInfo6(std::int32_t socket, NetSockInfo6* info, std::uint32_t capacity, std::uint32_t flags); +int APS5_VABI sceNetSetDns6Info(const NetDns6Info* info, std::uint32_t flags); +int APS5_VABI sceNetShowRoute6(void); +int APS5_VABI sceNetShowRoute6WithMemory(std::int32_t poolId); +NetRouteInfo* APS5_VABI sceNetAllocateAllRouteInfo(std::int32_t interfaceId, std::int32_t* count); +NetRouteInfo* APS5_VABI sceNetAllocateAllRouteInfo6(std::int32_t interfaceId, std::int32_t* count); +int APS5_VABI sceNetControl(std::uint32_t interfaceId, std::int32_t interfaceIndex, std::uint32_t command, void* data, std::uint32_t dataSize); +int APS5_VABI sceNetDbgInit(NetDebugParameter* parameter, std::uint32_t flags); +int APS5_VABI sceNetEventCallbackCreate(std::uint32_t interfaceId, NetEventCallback callback, void* userData, std::uint32_t flags); +int APS5_VABI sceNetEventCallbackDestroy(std::uint32_t interfaceId, std::uint32_t flags); +int APS5_VABI sceNetEventCallbackGetError(std::uint32_t interfaceId, std::int32_t* error, std::uint32_t flags); +int APS5_VABI sceNetEventCallbackWaitCB(std::int32_t timeout, void* waitData, std::uint32_t flags); +int APS5_VABI sceNetFreeAllRouteInfo(void* routes); +int APS5_VABI sceNetGetArpInfo(NetArpInfo* info, std::uint32_t flags); +int APS5_VABI sceNetGetAppMemInfo(NetAppMemoryInfo* info, std::uint32_t flags); +int APS5_VABI sceNetGetDnsInfo(NetDnsInfo* info, std::uint32_t flags); +int APS5_VABI sceNetGetIfList(std::int32_t interfaceId, NetInterfaceInfo* interfaces, std::uint32_t capacity); +int APS5_VABI sceNetGetIfListEx(std::int32_t interfaceId, NetInterfaceInfo* interfaces, std::uint32_t capacity, std::uint32_t flags); +int APS5_VABI sceNetGetIfListOnce(std::int32_t interfaceId, NetInterfaceStatistics* statistics, std::uint32_t count); +const char* APS5_VABI sceNetGetIfName(std::uint32_t index); +int APS5_VABI sceNetGetIfnameNumList(std::uint32_t* interfaceIds, std::int32_t capacity, std::uint32_t flags); +int APS5_VABI sceNetGetNameToIndex(std::uint32_t interfaceId); +int APS5_VABI sceNetGetRandom(std::uint32_t* output); +int APS5_VABI sceNetGetRouteInfo(std::int32_t family, NetRouteInfo* routes, std::int32_t capacity, std::uint8_t flags); +int APS5_VABI sceNetGetsockoptEx(int socketId, int level, int option, void* value, std::uint32_t* valueSize); +void APS5_VABI sceNetGetSystemTime(std::uint64_t* output); +int APS5_VABI sceNetGetSystemMemInfo(NetSystemMemoryInfo* info, std::uint32_t flags); +int APS5_VABI sceNetInitParam(const NetInitializeParameter* parameter); +int APS5_VABI sceNetInetPtonEx(int af, const char* src, void* dst, int); +int APS5_VABI sceNetIoctl(int socketId, std::uint32_t command, void* argument); +void* APS5_VABI sceNetMemoryAllocate(std::int32_t size, int flags); +void APS5_VABI sceNetMemoryFree(void* memory); +int APS5_VABI sceNetSetDnsInfoToKernel(const NetDnsInfo* info, std::uint32_t flags); +int APS5_VABI sceNetSetsockoptEx(int socketId, int level, int option, const void* value, std::uint32_t valueSize); +int APS5_VABI sceNetSocketInternal(const char* name, int domain, int type, int protocol); +int APS5_VABI sceNetSyncCreate(NetSyncObject* sync, std::uint32_t signals, const char* name); +int APS5_VABI sceNetSyncDestroy(NetSyncObject* sync); +NetSyncObject* APS5_VABI sceNetSyncGet(std::uint32_t index); +int APS5_VABI sceNetSyncSignal(NetSyncObject* sync, std::uint32_t signals); +int APS5_VABI sceNetSyncWait(NetSyncObject* sync, std::uint32_t* timeout); +int APS5_VABI sceNetSysctl(const int* name, std::uint32_t nameLength, void* oldValue, std::size_t* oldSize, const void* newValue, std::size_t newSize); +int APS5_VABI sceNetThreadCreate(Pthread* thread, const NetThreadParameter* parameter, const char* name); +void APS5_VABI sceNetThreadExit(); +int APS5_VABI sceNetThreadJoin(void* thread); +int APS5_VABI sceNetUsleep(int microseconds); +int APS5_VABI sceNetShowIfconfigForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId); +int APS5_VABI sceNetShowRouteForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId); +int APS5_VABI sceNetShowNetstatForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId); +int APS5_VABI sceNetShowNetstatEx(std::uint32_t flags); +int APS5_VABI sceNetShowNetstatExForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId, std::uint32_t flags); +const char* APS5_VABI sceNetInetNtopWithScopeId(std::int32_t family, const void* address, char* text, std::uint32_t size, std::uint32_t scopeId); +int APS5_VABI sceNetInetPtonWithScopeId(std::int32_t family, const char* text, void* address, std::uint32_t* scopeId); +int APS5_VABI sceNetGetDns6Info(NetDns6Info* info, std::uint32_t flags); +int APS5_VABI sceNetSetDns6InfoToKernel(const NetDns6Info* info, std::uint32_t flags); +int APS5_VABI sceNetShowRoute6ForBuffer(char* buffer, std::int32_t bufferSize, std::int32_t poolId); +int APS5_VABI sceNetConfigGetIfFlags(std::uint32_t interfaceId, std::uint32_t* flags); +int APS5_VABI sceNetConfigSetIfFlags(std::uint32_t interfaceId, std::uint32_t flags); +int APS5_VABI sceNetConfigUnsetIfFlags(std::uint32_t interfaceId, std::uint32_t flags); +int APS5_VABI sceNetConfigDelIfaddr(int interfaceId); +int APS5_VABI sceNetConfigDownInterface(int interfaceId); +int APS5_VABI sceNetConfigSetDefaultRoute(const std::uint32_t* gateway); +int APS5_VABI sceNetConfigSetDefaultRouteDev(const std::uint32_t* gateway); +int APS5_VABI sceNetConfigSetIfaddr(int interfaceId, const std::uint32_t* address, const std::uint32_t* broadcastAddress, const std::uint32_t* netmask); +int APS5_VABI sceNetConfigSetIfLinkLocalAddr6(std::uint32_t interfaceId); +int APS5_VABI sceNetConfigUpInterface(int interfaceId); +int APS5_VABI sceNetConfigUpInterfaceWithFlags(std::uint32_t interfaceId, std::uint32_t flags); +int APS5_VABI sceNetConfigCleanUpAllInterfaces(void); +int APS5_VABI sceNetConfigFlushRoute(std::uint32_t interfaceId); +int APS5_VABI sceNetConfigFlushRoute6(std::uint32_t interfaceId); +int APS5_VABI sceNetConfigAddIfaddr(int interfaceId, const std::uint32_t* address, const std::uint32_t* broadcastAddress, const std::uint32_t* netmask); +int APS5_VABI sceNetConfigGetIfaddr(std::int32_t interfaceId, NetIpv4InterfaceAddress* addresses, std::int32_t capacity); +int APS5_VABI sceNetConfigSetIfmtu(std::uint32_t interfaceId, int mtu); +int APS5_VABI sceNetConfigAddArp(const NetEtherAddr* etherAddress, const std::uint32_t* address); +int APS5_VABI sceNetConfigAddArpWithInterface(int interfaceId, const NetEtherAddr* etherAddress, const std::uint32_t* address); +int APS5_VABI sceNetConfigAddMRoute(const std::uint32_t* address); +int APS5_VABI sceNetConfigAddRoute(const std::uint32_t* destination, const std::uint32_t* gateway, const std::uint32_t* netmask, int hostRoute); +int APS5_VABI sceNetConfigAddRouteWithInterface(int interfaceId, const std::uint32_t* destination, const std::uint32_t* gateway, const std::uint32_t* netmask, int hostRoute); +int APS5_VABI sceNetConfigDelArp(const NetEtherAddr* etherAddress, const std::uint32_t* address); +int APS5_VABI sceNetConfigDelArpWithInterface(int interfaceId, const NetEtherAddr* etherAddress, const std::uint32_t* address); +int APS5_VABI sceNetConfigDelDefaultRoute(void); +int APS5_VABI sceNetConfigDelDefaultRouteDev(void); +int APS5_VABI sceNetConfigDelMRoute(const std::uint32_t* address); +int APS5_VABI sceNetConfigDelRoute(const std::uint32_t* destination, const std::uint32_t* gateway, const std::uint32_t* netmask); +int APS5_VABI sceNetConfigGetDefaultRoute(std::uint32_t* gateway); +int APS5_VABI sceNetDhcpGetAutoipInfo(std::int32_t interfaceId, NetAutoIpInfo* parameter); +int APS5_VABI sceNetDhcpGetInfo(std::int32_t interfaceId, NetDhcpInfo* parameter); +int APS5_VABI sceNetDhcpGetInfoEx(std::int32_t interfaceId, std::int32_t type, NetDhcpOptionInfo* info, std::uint32_t size); +int APS5_VABI sceNetDhcpStart(std::uint32_t interfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2, NetDhcpStartParameter* parameter); +int APS5_VABI sceNetDhcpStop(int interfaceId); +int APS5_VABI sceNetConfigAddIfaddr6(int interfaceId, const NetIpv6Address* address, std::uint8_t prefixLength); +int APS5_VABI sceNetConfigDelIfaddr6(int interfaceId, const NetIpv6Address* address); +int APS5_VABI sceNetConfigDelDefaultRoute6(void); +int APS5_VABI sceNetConfigSetIfaddr6(int interfaceId, const NetIpv6Address* address, std::uint8_t prefixLength); +int APS5_VABI sceNetConfigSetIfaddr6WithFlags(int interfaceId, const NetIpv6Address* address, std::uint8_t prefixLength, std::uint32_t flags); +int APS5_VABI sceNetConfigSetDefaultRoute6(int interfaceId, const NetIpv6Address* gateway); +int APS5_VABI sceNetConfigGetIfaddr6(int interfaceId, NetIpv6InterfaceAddress* addresses, std::uint32_t count); +int APS5_VABI sceNetConfigGetDefaultRoute6(NetIpv6Address* gateway); +int APS5_VABI sceNetConfigDelRoute6(const NetIpv6Address* destination, std::uint8_t prefixLength); +int APS5_VABI sceNetConfigAddRoute6(const NetIpv6Address* destination, const NetIpv6Address* gateway, std::uint8_t prefixLength); +int APS5_VABI sceNetAddrConfig6Start(std::uint32_t interfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2, const NetAddress6StartParameter* parameter); +int APS5_VABI sceNetAddrConfig6Stop(int interfaceId); +int APS5_VABI sceNetAddrConfig6GetInfo(std::int32_t interfaceId, NetAddress6Info* parameter); +int APS5_VABI sceNetInfoDumpStart(std::int32_t threadParameter1, std::int32_t threadParameter2, const NetInfoDumpParameter* parameter); +int APS5_VABI sceNetInfoDumpStop(void); +int APS5_VABI sceNetDhcpdStart(std::uint32_t interfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2, const NetDhcpdStartParameter* parameter); +int APS5_VABI sceNetDhcpdStop(int interfaceId); +int APS5_VABI sceNetDuplicateIpStart(std::int32_t interfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2); +int APS5_VABI sceNetDuplicateIpStop(int interfaceId); +int APS5_VABI sceNetConfigWlanAdhocClearWakeOnWlan(std::uint32_t interfaceId); +int APS5_VABI sceNetConfigWlanAdhocCreate(std::int32_t interfaceId, NetWlanAdhocParameter* parameter); +int APS5_VABI sceNetConfigWlanAdhocGetWakeOnWlanInfo(std::int32_t interfaceId, NetWlanWakeInfo* parameter); +int APS5_VABI sceNetConfigWlanAdhocJoin(std::int32_t interfaceId, NetWlanAdhocParameter* parameter); +int APS5_VABI sceNetConfigWlanAdhocLeave(int interfaceId); +int APS5_VABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan(std::uint32_t interfaceId); +int APS5_VABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo(std::int32_t interfaceId, NetWlanPspWakeInfo* parameter); +int APS5_VABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan(std::int32_t interfaceId, NetWlanPspWakeParameter* parameter); +int APS5_VABI sceNetConfigWlanAdhocScanJoin(std::int32_t interfaceId, const NetWlanAdhocScanParameter* parameter, void* buffer, std::size_t bufferSize, NetWlanJoinResult** result); +int APS5_VABI sceNetConfigWlanAdhocSetExtInfoElement(std::int32_t interfaceId, NetWlanInformationElement* parameter, std::int32_t size); +int APS5_VABI sceNetConfigWlanAdhocSetWakeOnWlan(std::int32_t interfaceId, NetWlanWakeParameter* parameter, std::size_t size); +int APS5_VABI sceNetConfigWlanApStart(std::int32_t interfaceId, NetWlanApParameter* parameter); +int APS5_VABI sceNetConfigWlanApStop(int interfaceId); +int APS5_VABI sceNetConfigWlanBackgroundScanQuery(std::int32_t interfaceId, void* buffer, std::int32_t size, NetWlanScanResult** results, std::size_t* count); +int APS5_VABI sceNetConfigWlanBackgroundScanStart(std::int32_t interfaceId, NetWlanScanParameter* parameter, std::uint32_t size); +int APS5_VABI sceNetConfigWlanBackgroundScanStop(int interfaceId); +int APS5_VABI sceNetConfigWlanDiagGetDeviceInfo(std::int32_t interfaceId, NetWlanDeviceInfo* parameter); +int APS5_VABI sceNetConfigWlanDiagSetAntenna(std::int32_t interfaceId, NetWlanAntennaParameter* parameter); +int APS5_VABI sceNetConfigWlanDiagSetTxFixedRate(std::int32_t interfaceId, NetWlanRateParameter* parameter); +int APS5_VABI sceNetConfigWlanGetDeviceConfig(std::int32_t interfaceId, NetWlanDeviceConfiguration* parameter); +int APS5_VABI sceNetConfigWlanInfraGetRssiInfo(std::int32_t interfaceId, NetWlanRssiInfo* parameter); +int APS5_VABI sceNetConfigWlanInfraLeave(int interfaceId); +int APS5_VABI sceNetConfigWlanInfraScanJoin(std::int32_t interfaceId, const NetWlanInfraScanParameter* parameter, void* buffer, std::size_t bufferSize, NetWlanJoinResult** result); +int APS5_VABI sceNetConfigWlanScan(std::int32_t interfaceId, const NetWlanScanParameter* parameter, std::size_t parameterSize, void* buffer, std::size_t bufferSize, NetWlanScanResult** results, std::size_t* count); +int APS5_VABI sceNetConfigWlanSetDeviceConfig(std::int32_t interfaceId, NetWlanDeviceConfiguration* parameter); +int APS5_VABI sceNetConfigEtherGetLinkMode(std::uint32_t interfaceId, int* mode); +int APS5_VABI sceNetConfigEtherPostPlugInOutEvent(std::uint32_t interfaceId); +int APS5_VABI sceNetConfigEtherSetLinkMode(std::uint32_t interfaceId, int mode); +int APS5_VABI sceNetPppoeStart(std::uint32_t interfaceId, std::int32_t pppoeInterfaceId, NetAddressCallback callback, void* userArg, std::int32_t threadParameter1, std::int32_t threadParameter2, const NetPppoeStartParameter* parameter); +int APS5_VABI sceNetPppoeStop(void); +int APS5_VABI sceNetBandwidthControlSetDefaultParam(const NetBandwidthParameter* parameter, std::uint32_t flags); +int APS5_VABI sceNetBandwidthControlGetDefaultParam(NetBandwidthParameter* parameter, std::uint32_t flags); +int APS5_VABI sceNetBandwidthControlGetDataTraffic(std::int32_t interfaceId, NetDataTraffic* traffic, std::uint32_t flags); +int APS5_VABI sceNetConfigSetDefaultScope(std::uint32_t interfaceId, const char* interfaceName, std::int32_t scopeId); +int APS5_VABI sceNetConfigRoutingStart(std::uint32_t firstInterfaceId, std::uint32_t secondInterfaceId, const NetRoutingAddressParameter* addressParameter, NetRoutingServerParameter* serverParameter, const NetRoutingRuleParameter* ruleParameter); +int APS5_VABI sceNetConfigRoutingStop(void); +int APS5_VABI sceNetConfigRoutingShowRoutingConfig(void); +int APS5_VABI sceNetConfigRoutingShowtCtlVar(const char* name); +int APS5_VABI sceNetGetStatisticsInfoInternal(NetStatisticsInfoInternal* info); +int APS5_VABI sceNetDumpAbort(std::int32_t dumpId, std::uint32_t flags); +int APS5_VABI sceNetDumpCreate(const char* name, std::int32_t bufferSize, std::uint32_t flags); +int APS5_VABI sceNetDumpSetFilterParam(std::int32_t socket, const NetDumpFilterParameter* parameter); +int APS5_VABI sceNetDumpDestroy(std::int32_t dumpId); +int APS5_VABI sceNetDumpRead(std::int32_t dumpId, void* buffer, std::int32_t bufferSize, std::uint32_t* flags); +int APS5_VABI sceNetEmulationDebugSettingsSet(const NetEmulationParameter* parameter, std::uint32_t flags); +int APS5_VABI sceNetEmulationDebugSettingsGet(NetEmulationParameter* parameter, std::uint32_t flags); +int APS5_VABI sceNetEmulationSet(const NetEmulationParameter* parameter, std::uint32_t flags); +int APS5_VABI sceNetEmulationGet(NetEmulationParameter* parameter, std::uint32_t flags); + +} diff --git a/core/libs/prx/libSceNet/libSceNet.hpp b/core/libs/prx/libSceNet/libSceNet.hpp new file mode 100644 index 0000000000..9f4c162ed2 --- /dev/null +++ b/core/libs/prx/libSceNet/libSceNet.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNet/include/Net.hpp" diff --git a/core/libs/prx/libSceNet/src/ResolverTestHooks.hpp b/core/libs/prx/libSceNet/src/ResolverTestHooks.hpp new file mode 100644 index 0000000000..cde7e83a8a --- /dev/null +++ b/core/libs/prx/libSceNet/src/ResolverTestHooks.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include + +namespace GuestSceNetTest { +using ResolverLookup = bool (*)(const char*, int, std::vector>&); +void SetResolverLookupForTests_nid_no_patch(ResolverLookup lookup) noexcept; +} diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index c8e3080177..d3e153e34a 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNetCtl/include/NetCtl.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -19,6 +20,7 @@ #include #endif #endif +#include #include #include #include @@ -34,6 +36,10 @@ #include // Network-control state reflects host interface addressing, not PSN sign-in status. + +static constexpr int SCE_NET_CTL_ERROR_NOT_INITIALIZED = static_cast(0x80412101); +static constexpr int SCE_NET_CTL_ERROR_ID_NOT_FOUND = static_cast(0x80412104); +static constexpr int SCE_NET_CTL_ERROR_INVALID_SIZE = static_cast(0x80412111); static constexpr int SCE_NET_CTL_ERROR_CALLBACK_MAX = static_cast(0x80412103); static constexpr int SCE_NET_CTL_ERROR_INVALID_ID = static_cast(0x80412105); static constexpr int SCE_NET_CTL_ERROR_INVALID_CODE = static_cast(0x80412106); @@ -80,11 +86,14 @@ struct CallbackSlot { int lastEvent = 0; }; +static std::atomic_bool g_initialized{true}; static std::mutex g_callbackLock; static CallbackSlot g_callbacks[MAX_CALLBACKS] = {}; static CallbackSlot g_callbacksV6[MAX_CALLBACKS] = {}; +static CallbackSlot g_callbacksNpToolkit[MAX_CALLBACKS] = {}; static int RegisterCallback(CallbackSlot (&callbacks)[MAX_CALLBACKS], NetCtlCallback func, void* arg, int* cid) { + if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; if (!func || !cid) return SCE_NET_CTL_ERROR_INVALID_ADDR; std::lock_guard lock(g_callbackLock); for (int index = 0; index < MAX_CALLBACKS; ++index) { @@ -98,21 +107,23 @@ static int RegisterCallback(CallbackSlot (&callbacks)[MAX_CALLBACKS], NetCtlCall } static int UnregisterCallback(CallbackSlot (&callbacks)[MAX_CALLBACKS], int cid) { + if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; if (cid < 0 || cid >= MAX_CALLBACKS) return SCE_NET_CTL_ERROR_INVALID_ID; std::lock_guard lock(g_callbackLock); + if (!callbacks[cid].func) return SCE_NET_CTL_ERROR_ID_NOT_FOUND; callbacks[cid] = {}; return 0; } -static bool HasIpv4Callback() { +static bool HasCallback(const CallbackSlot (&callbacks)[MAX_CALLBACKS]) { std::lock_guard lock(g_callbackLock); - for (const auto& slot : g_callbacks) { + for (const auto& slot : callbacks) { if (slot.func) return true; } return false; } -static bool HostHasAddress(int family) { +static bool QueryHostHasAddress(int family) { #ifdef _WIN32 static const int socket_status = [] { WSADATA data{}; @@ -169,6 +180,74 @@ struct HostIpv4Interface { std::vector dns; }; +#ifdef _WIN32 +namespace { + +struct HostAddressCache { + std::mutex lock; + bool registered = false; + bool tried = false; + std::atomic generation{1}; + std::uint64_t seen[3]{}; + bool value[3]{}; + std::uint64_t interfaceSeen = 0; + bool interfaceFound = false; + HostIpv4Interface interfaceValue; +}; + +HostAddressCache& AddressCache() { + static HostAddressCache cache; + return cache; +} + +void CALLBACK HostAddressChanged(PVOID, PMIB_UNICASTIPADDRESS_ROW, MIB_NOTIFICATION_TYPE) { + AddressCache().generation.fetch_add(1, std::memory_order_acq_rel); +} + +void CALLBACK HostInterfaceChanged(PVOID, PMIB_IPINTERFACE_ROW, MIB_NOTIFICATION_TYPE) { + AddressCache().generation.fetch_add(1, std::memory_order_acq_rel); +} + +void CALLBACK HostRouteChanged(PVOID, PMIB_IPFORWARD_ROW2, MIB_NOTIFICATION_TYPE) { + AddressCache().generation.fetch_add(1, std::memory_order_acq_rel); +} + +void WatchHostAddresses(HostAddressCache& cache) { + if (cache.tried) return; + cache.tried = true; + HANDLE addresses = nullptr; + HANDLE interfaces = nullptr; + HANDLE routes = nullptr; + cache.registered = NotifyUnicastIpAddressChange(AF_UNSPEC, HostAddressChanged, nullptr, FALSE, &addresses) == NO_ERROR && + NotifyIpInterfaceChange(AF_UNSPEC, HostInterfaceChanged, nullptr, FALSE, &interfaces) == NO_ERROR && + NotifyRouteChange2(AF_UNSPEC, HostRouteChanged, nullptr, FALSE, &routes) == NO_ERROR; + if (!cache.registered) { + if (addresses != nullptr) CancelMibChangeNotify2(addresses); + if (interfaces != nullptr) CancelMibChangeNotify2(interfaces); + if (routes != nullptr) CancelMibChangeNotify2(routes); + } +} + +} +#endif + +static bool HostHasAddress(int family) { +#ifdef _WIN32 + auto& cache = AddressCache(); + const int slot = family == AF_INET ? 0 : family == AF_INET6 ? 1 : 2; + std::lock_guard guard(cache.lock); + WatchHostAddresses(cache); + const auto generation = cache.generation.load(std::memory_order_acquire); + if (cache.registered && cache.seen[slot] == generation) return cache.value[slot]; + const bool value = QueryHostHasAddress(family); + cache.value[slot] = value; + cache.seen[slot] = generation; + return value; +#else + return QueryHostHasAddress(family); +#endif +} + static bool UsableIpv4(in_addr address) { const auto host = ntohl(address.s_addr); return host != 0 && (host >> 24) != 127; @@ -291,7 +370,7 @@ static std::vector HostDnsServers() { } #endif -static bool FindHostIpv4Interface(HostIpv4Interface& found) { +static bool QueryHostIpv4Interface(HostIpv4Interface& found) { #ifdef _WIN32 ULONG size = 0; const ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS; @@ -352,6 +431,25 @@ static bool FindHostIpv4Interface(HostIpv4Interface& found) { #endif } +static bool FindHostIpv4Interface(HostIpv4Interface& found) { +#ifdef _WIN32 + auto& cache = AddressCache(); + std::lock_guard guard(cache.lock); + WatchHostAddresses(cache); + const auto generation = cache.generation.load(std::memory_order_acquire); + if (!cache.registered || cache.interfaceSeen != generation) { + HostIpv4Interface value; + cache.interfaceFound = QueryHostIpv4Interface(value); + cache.interfaceValue = std::move(value); + cache.interfaceSeen = generation; + } + if (cache.interfaceFound) found = cache.interfaceValue; + return cache.interfaceFound; +#else + return QueryHostIpv4Interface(found); +#endif +} + extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, std::size_t capacity) { HostIpv4Interface host; if (!FindHostIpv4Interface(host)) return 0; @@ -406,16 +504,15 @@ static void HostTraffic(std::uint64_t& sent, std::uint64_t& received) { #endif } -extern "C" { - -int APS5_VABI sceNetCtlCheckCallback(void) { - if (!HasIpv4Callback()) return 0; +static int CheckCallbacks(CallbackSlot (&callbacks)[MAX_CALLBACKS]) { + if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; + if (!HasCallback(callbacks)) return 0; const int event = HostHasAddress(AF_UNSPEC) ? NET_CTL_EVENT_IPOBTAINED : NET_CTL_EVENT_DISCONNECTED; CallbackSlot due[MAX_CALLBACKS]; int count = 0; { std::lock_guard lock(g_callbackLock); - for (auto& slot : g_callbacks) { + for (auto& slot : callbacks) { if (!slot.func || slot.lastEvent == event) continue; slot.lastEvent = event; due[count++] = slot; @@ -425,7 +522,14 @@ int APS5_VABI sceNetCtlCheckCallback(void) { return 0; } +extern "C" { + +int APS5_VABI sceNetCtlCheckCallback(void) { + return CheckCallbacks(g_callbacks); +} + int APS5_VABI sceNetCtlGetIfStat(NetCtlIfStat* stat) { + if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; if (!stat) return SCE_NET_CTL_ERROR_INVALID_ADDR; if (!HostHasAddress(AF_UNSPEC)) return SCE_NET_CTL_ERROR_NOT_CONNECTED; *stat = {}; @@ -434,6 +538,7 @@ int APS5_VABI sceNetCtlGetIfStat(NetCtlIfStat* stat) { } int APS5_VABI sceNetCtlGetInfo(int code, NetCtlInfo* info) { + if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; if (!info) return SCE_NET_CTL_ERROR_INVALID_ADDR; if (code < NET_CTL_INFO_DEVICE || code > NET_CTL_INFO_HTTP_PROXY_PORT) return SCE_NET_CTL_ERROR_INVALID_CODE; HostIpv4Interface host; @@ -465,31 +570,39 @@ int APS5_VABI sceNetCtlGetInfo(int code, NetCtlInfo* info) { return 0; } -int APS5_VABI sceNetCtlGetNatInfo(NetCtlNatInfo* nat_info) { - if (!nat_info) return SCE_NET_CTL_ERROR_INVALID_ADDR; - return SCE_NET_CTL_ERROR_NOT_CONNECTED; +int APS5_VABI sceNetCtlGetNatInfo(NetCtlNatInfo* info) { + if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; + if (!info) return SCE_NET_CTL_ERROR_INVALID_ADDR; + if (info->size != sizeof(NetCtlNatInfo)) return SCE_NET_CTL_ERROR_INVALID_SIZE; + throw std::logic_error("sceNetCtlGetNatInfo: NAT discovery is not implemented"); } -int APS5_VABI sceNetCtlGetResult(int event_type, int* error_code) { - (void)event_type; - if (!error_code) return SCE_NET_CTL_ERROR_INVALID_ADDR; - *error_code = 0; +int APS5_VABI sceNetCtlGetResult(int event, int* result) { + if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; + if (!result) return SCE_NET_CTL_ERROR_INVALID_ADDR; + if (event < NET_CTL_EVENT_DISCONNECTED || event > NET_CTL_EVENT_IPOBTAINED) { + throw std::runtime_error("sceNetCtlGetResult: event type " + std::to_string(event) + " is not implemented"); + } + *result = 0; return 0; } int APS5_VABI sceNetCtlGetState(int* state) { + if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; if (!state) return SCE_NET_CTL_ERROR_INVALID_ADDR; *state = HostHasAddress(AF_UNSPEC) ? NET_CTL_STATE_IPOBTAINED : NET_CTL_STATE_DISCONNECTED; return 0; } int APS5_VABI sceNetCtlGetStateV6(int* state) { + if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; if (!state) return SCE_NET_CTL_ERROR_INVALID_ADDR; *state = HostHasAddress(AF_INET6) ? NET_CTL_STATE_IPOBTAINED : NET_CTL_STATE_DISCONNECTED; return 0; } int APS5_VABI sceNetCtlInit(void) { + g_initialized.store(true); return 0; } @@ -501,7 +614,7 @@ int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback func, void* arg, int* c return RegisterCallback(g_callbacksV6, func, arg, cid); } -int APS5_VABI sceNetCtlGetInfoV6(int code, void* info) { +int APS5_VABI sceNetCtlGetInfoV6(std::uint32_t code, NetCtlInfoV6* info) { if (!info) return SCE_NET_CTL_ERROR_INVALID_ADDR; if (code < NET_CTL_INFO_V6_IP_ADDRESS || code > NET_CTL_INFO_V6_SECONDARY_DNS) return SCE_NET_CTL_ERROR_INVALID_CODE; if (!HostHasAddress(AF_INET6)) return SCE_NET_CTL_ERROR_NOT_CONNECTED; @@ -509,9 +622,6 @@ int APS5_VABI sceNetCtlGetInfoV6(int code, void* info) { } void APS5_VABI sceNetCtlTerm(void) { - std::lock_guard lock(g_callbackLock); - for (auto& slot : g_callbacks) slot = {}; - for (auto& slot : g_callbacksV6) slot = {}; } int APS5_VABI sceNetCtlUnregisterCallback(int cid) { @@ -522,39 +632,36 @@ int APS5_VABI sceNetCtlUnregisterCallbackV6(int cid) { return UnregisterCallback(g_callbacksV6, cid); } -int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(void) { +int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(NetCtlCallback callback, void* argument, int* callbackId) { + return RegisterCallback(g_callbacksNpToolkit, callback, argument, callbackId); +} + +int APS5_VABI sceNetCtlRegisterCallbackIpcInt(NetCtlCallback callback, void* argument, int* callbackId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlRegisterCallbackIpcInt(void) { +int APS5_VABI sceNetCtlRegisterCallbackForLibIpcInt(NetCtlCallback callback, void* argument, int* callbackId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlRegisterCallbackForLibIpcInt(void) { +int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(int callbackId) { + return UnregisterCallback(g_callbacksNpToolkit, callbackId); +} + +int APS5_VABI sceNetCtlUnregisterCallbackIpcInt(int callbackId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNetCtlUnregisterCallbackIpcInt(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNetCtlUnregisterCallbackForLibIpcInt(void) { +int APS5_VABI sceNetCtlUnregisterCallbackForLibIpcInt(int callbackId) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceNetCtlCheckCallbackForNpToolkit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return CheckCallbacks(g_callbacksNpToolkit); } int APS5_VABI sceNetCtlCheckCallbackForLibIpcInt(void) { @@ -562,47 +669,47 @@ int APS5_VABI sceNetCtlCheckCallbackForLibIpcInt(void) { return 0; } -int APS5_VABI sceNetCtlClearEventIpcInt(void) { +int APS5_VABI sceNetCtlClearEventIpcInt(int event) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlClearEventForNpToolkit(void) { +int APS5_VABI sceNetCtlClearEventForNpToolkit(int event) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlClearEventForLibIpcInt(void) { +int APS5_VABI sceNetCtlClearEventForLibIpcInt(int event) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetResultIpcInt(void) { +int APS5_VABI sceNetCtlGetResultIpcInt(int event, int* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetStateIpcInt(void) { +int APS5_VABI sceNetCtlGetStateIpcInt(int* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetState2IpcInt(void) { +int APS5_VABI sceNetCtlGetState2IpcInt(int* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetInfoIpcInt(void) { +int APS5_VABI sceNetCtlGetInfoIpcInt(std::uint32_t code, NetCtlInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetEtherLinkMode(void) { +int APS5_VABI sceNetCtlGetEtherLinkMode(int* mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetWifiType(void) { +int APS5_VABI sceNetCtlGetWifiType(int* type) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -617,7 +724,7 @@ int APS5_VABI sceNetCtlConnectWithRetryIpcInt(void) { return 0; } -int APS5_VABI sceNetCtlConnectConfIpcInt(void) { +int APS5_VABI sceNetCtlConnectConfIpcInt(const NetCtlConnectionConfiguration* configuration) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -627,47 +734,47 @@ int APS5_VABI sceNetCtlDisconnectIpcInt(void) { return 0; } -int APS5_VABI sceNetCtlScanIpcInt(void) { +int APS5_VABI sceNetCtlScanIpcInt(const char* const* ssids, std::uint32_t count, const NetCtlScanOptions* options) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetScanInfoBssidIpcInt(void) { +int APS5_VABI sceNetCtlGetScanInfoBssidIpcInt(NetCtlScanBssidList* bssids, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetScanInfoByBssidIpcInt(void) { +int APS5_VABI sceNetCtlGetScanInfoByBssidIpcInt(const std::uint8_t* bssid, NetCtlScanInfo* info, void* buffer, std::size_t* size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetScanInfoForSsidScanIpcInt(void) { +int APS5_VABI sceNetCtlGetScanInfoForSsidScanIpcInt(NetCtlScanInfo* info, void* buffer, std::size_t* size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetScanInfoBssidForSsidListScanIpcInt(void) { +int APS5_VABI sceNetCtlGetScanInfoBssidForSsidListScanIpcInt(NetCtlScanBssidList* bssids, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetScanInfoForSsidListScanIpcInt(void) { +int APS5_VABI sceNetCtlGetScanInfoForSsidListScanIpcInt(const std::uint8_t* bssid, NetCtlScanInfo* info, void* buffer, std::size_t* size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlSetErrorNotificationEnabledIpcInt(void) { +int APS5_VABI sceNetCtlSetErrorNotificationEnabledIpcInt(int enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlRegisterCallbackV6IpcInt(void) { +int APS5_VABI sceNetCtlRegisterCallbackV6IpcInt(NetCtlCallback callback, void* argument, int* callbackId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlUnregisterCallbackV6IpcInt(void) { +int APS5_VABI sceNetCtlUnregisterCallbackV6IpcInt(int callbackId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -681,22 +788,22 @@ int APS5_VABI sceNetCtlGetResultV6(int event_type, int* error_code) { return 0; } -int APS5_VABI sceNetCtlGetResultV6IpcInt(void) { +int APS5_VABI sceNetCtlGetResultV6IpcInt(int event, int* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetStateV6IpcInt(void) { +int APS5_VABI sceNetCtlGetStateV6IpcInt(int* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetInfoV6IpcInt(void) { +int APS5_VABI sceNetCtlGetInfoV6IpcInt(std::uint32_t code, NetCtlInfoV6* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetNatInfoIpcInt(void) { +int APS5_VABI sceNetCtlGetNatInfoIpcInt(NetCtlNatInfoInternal* info) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -726,17 +833,17 @@ int APS5_VABI sceNetCtlEnableBandwidthManagementIpcInt(void) { return 0; } -int APS5_VABI sceNetCtlGetBandwidthInfoIpcInt(void) { +int APS5_VABI sceNetCtlGetBandwidthInfoIpcInt(NetCtlBandwidthInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetNetEvConfigInfoIpcInt(void) { +int APS5_VABI sceNetCtlGetNetEvConfigInfoIpcInt(NetCtlNetEvConfiguration* configuration) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetConnectionElapsedTimeIpcInt(void) { +int APS5_VABI sceNetCtlGetConnectionElapsedTimeIpcInt(std::uint64_t* elapsedTime) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -778,12 +885,12 @@ int APS5_VABI sceNetCtlApCpStart(void) { return 0; } -int APS5_VABI sceNetCtlApAppStartWithRetryPid(void) { +int APS5_VABI sceNetCtlApAppStartWithRetryPid(std::int32_t processId, const char* passcode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlApAppStartWithRetry(void) { +int APS5_VABI sceNetCtlApAppStartWithRetry(const char* passcode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -810,7 +917,7 @@ int APS5_VABI sceNetCtlApAppInitWpaKey(void) { return 0; } -int APS5_VABI sceNetCtlApAppInitWpaKeyForQa(void) { +int APS5_VABI sceNetCtlApAppInitWpaKeyForQa(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -825,22 +932,22 @@ int APS5_VABI sceNetBweCheckCallbackIpcInt(void) { return 0; } -int APS5_VABI sceNetBweClearEventIpcInt(void) { +int APS5_VABI sceNetBweClearEventIpcInt(int event) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetBweRegisterCallbackIpcInt(void) { +int APS5_VABI sceNetBweRegisterCallbackIpcInt(NetCtlCallback callback, void* argument, int* callbackId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetBweUnregisterCallbackIpcInt(void) { +int APS5_VABI sceNetBweUnregisterCallbackIpcInt(int callbackId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetBweGetInfoIpcInt(void) { +int APS5_VABI sceNetBweGetInfoIpcInt(NetBweInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -860,12 +967,12 @@ int APS5_VABI sceNetBweStartInternetConnectionTestBandwidthTestIpcInt(void) { return 0; } -int APS5_VABI sceNetBweSetInternetConnectionTestResultIpcInt(void) { +int APS5_VABI sceNetBweSetInternetConnectionTestResultIpcInt(std::int32_t firstResult, std::int32_t secondResult) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNetCtlGetWifiInfoIpcInt(void) { +int APS5_VABI sceNetCtlGetWifiInfoIpcInt(NetCtlWifiInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceNetCtl/include/NetCtl.hpp b/core/libs/prx/libSceNetCtl/include/NetCtl.hpp new file mode 100644 index 0000000000..f2fa735b00 --- /dev/null +++ b/core/libs/prx/libSceNetCtl/include/NetCtl.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include "SceTypes.hpp" + +union NetCtlInfoV6; +struct NetCtlConnectionConfiguration; +struct NetCtlScanOptions; +struct NetCtlScanBssidList; +struct NetCtlScanInfo; +struct NetCtlNatInfoInternal; +struct NetCtlBandwidthInfo; +struct NetCtlNetEvConfiguration; +struct NetBweInfo; +struct NetCtlWifiInfo; + +extern "C" { + +int APS5_VABI sceNetCtlCheckCallback(void); +int APS5_VABI sceNetCtlGetIfStat(NetCtlIfStat* stat); +int APS5_VABI sceNetCtlGetInfo(int code, NetCtlInfo* info); +int APS5_VABI sceNetCtlGetNatInfo(NetCtlNatInfo* info); +int APS5_VABI sceNetCtlGetResult(int event, int* result); +int APS5_VABI sceNetCtlGetState(int* state); +int APS5_VABI sceNetCtlGetStateV6(int* state); +int APS5_VABI sceNetCtlInit(void); +int APS5_VABI sceNetCtlRegisterCallback(NetCtlCallback func, void* arg, int* cid); +int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback func, void* arg, int* cid); +int APS5_VABI sceNetCtlGetInfoV6(std::uint32_t code, NetCtlInfoV6* info); +void APS5_VABI sceNetCtlTerm(void); +int APS5_VABI sceNetCtlUnregisterCallback(int cid); +int APS5_VABI sceNetCtlUnregisterCallbackV6(int cid); +int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(NetCtlCallback callback, void* argument, int* callbackId); +int APS5_VABI sceNetCtlRegisterCallbackIpcInt(NetCtlCallback callback, void* argument, int* callbackId); +int APS5_VABI sceNetCtlRegisterCallbackForLibIpcInt(NetCtlCallback callback, void* argument, int* callbackId); +int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(int callbackId); +int APS5_VABI sceNetCtlUnregisterCallbackIpcInt(int callbackId); +int APS5_VABI sceNetCtlUnregisterCallbackForLibIpcInt(int callbackId); +int APS5_VABI sceNetCtlCheckCallbackForNpToolkit(void); +int APS5_VABI sceNetCtlCheckCallbackForLibIpcInt(void); +int APS5_VABI sceNetCtlClearEventIpcInt(int event); +int APS5_VABI sceNetCtlClearEventForNpToolkit(int event); +int APS5_VABI sceNetCtlClearEventForLibIpcInt(int event); +int APS5_VABI sceNetCtlGetResultIpcInt(int event, int* result); +int APS5_VABI sceNetCtlGetStateIpcInt(int* state); +int APS5_VABI sceNetCtlGetState2IpcInt(int* state); +int APS5_VABI sceNetCtlGetInfoIpcInt(std::uint32_t code, NetCtlInfo* info); +int APS5_VABI sceNetCtlGetEtherLinkMode(int* mode); +int APS5_VABI sceNetCtlGetWifiType(int* type); +int APS5_VABI sceNetCtlConnectIpcInt(void); +int APS5_VABI sceNetCtlConnectWithRetryIpcInt(void); +int APS5_VABI sceNetCtlConnectConfIpcInt(const NetCtlConnectionConfiguration* configuration); +int APS5_VABI sceNetCtlDisconnectIpcInt(void); +int APS5_VABI sceNetCtlScanIpcInt(const char* const* ssids, std::uint32_t count, const NetCtlScanOptions* options); +int APS5_VABI sceNetCtlGetScanInfoBssidIpcInt(NetCtlScanBssidList* bssids, std::uint32_t capacity); +int APS5_VABI sceNetCtlGetScanInfoByBssidIpcInt(const std::uint8_t* bssid, NetCtlScanInfo* info, void* buffer, std::size_t* size); +int APS5_VABI sceNetCtlGetScanInfoForSsidScanIpcInt(NetCtlScanInfo* info, void* buffer, std::size_t* size); +int APS5_VABI sceNetCtlGetScanInfoBssidForSsidListScanIpcInt(NetCtlScanBssidList* bssids, std::uint32_t capacity); +int APS5_VABI sceNetCtlGetScanInfoForSsidListScanIpcInt(const std::uint8_t* bssid, NetCtlScanInfo* info, void* buffer, std::size_t* size); +int APS5_VABI sceNetCtlSetErrorNotificationEnabledIpcInt(int enabled); +int APS5_VABI sceNetCtlRegisterCallbackV6IpcInt(NetCtlCallback callback, void* argument, int* callbackId); +int APS5_VABI sceNetCtlUnregisterCallbackV6IpcInt(int callbackId); +int APS5_VABI sceNetCtlGetResultV6(int eventType, int* errorCode); +int APS5_VABI sceNetCtlGetResultV6IpcInt(int event, int* result); +int APS5_VABI sceNetCtlGetStateV6IpcInt(int* state); +int APS5_VABI sceNetCtlGetInfoV6IpcInt(std::uint32_t code, NetCtlInfoV6* info); +int APS5_VABI sceNetCtlGetNatInfoIpcInt(NetCtlNatInfoInternal* info); +int APS5_VABI sceNetCtlSetStunWithPaddingFlagIpcInt(void); +int APS5_VABI sceNetCtlUnsetStunWithPaddingFlagIpcInt(void); +int APS5_VABI sceNetCtlIsBandwidthManagementEnabledIpcInt(void); +int APS5_VABI sceNetCtlDisableBandwidthManagementIpcInt(void); +int APS5_VABI sceNetCtlEnableBandwidthManagementIpcInt(void); +int APS5_VABI sceNetCtlGetBandwidthInfoIpcInt(NetCtlBandwidthInfo* info); +int APS5_VABI sceNetCtlGetNetEvConfigInfoIpcInt(NetCtlNetEvConfiguration* configuration); +int APS5_VABI sceNetCtlGetConnectionElapsedTimeIpcInt(std::uint64_t* elapsedTime); +int APS5_VABI sceNetCtlApRpCheckCallback(void); +int APS5_VABI sceNetCtlApRpClearEvent(void); +int APS5_VABI sceNetCtlApRpRegisterCallback(void); +int APS5_VABI sceNetCtlApRpUnregisterCallback(void); +int APS5_VABI sceNetCtlApRpGetResult(void); +int APS5_VABI sceNetCtlApRpGetState(void); +int APS5_VABI sceNetCtlApRpGetInfo(void); +int APS5_VABI sceNetCtlApRpStart(void); +int APS5_VABI sceNetCtlApCpStart(void); +int APS5_VABI sceNetCtlApAppStartWithRetryPid(std::int32_t processId, const char* passcode); +int APS5_VABI sceNetCtlApAppStartWithRetry(const char* passcode); +int APS5_VABI sceNetCtlApRpStartWithRetry(void); +int APS5_VABI sceNetCtlApRpStartConf(void); +int APS5_VABI sceNetCtlApRpStop(void); +int APS5_VABI sceNetCtlApCpStop(void); +int APS5_VABI sceNetCtlApAppInitWpaKey(void); +int APS5_VABI sceNetCtlApAppInitWpaKeyForQa(std::uint8_t mode); +int APS5_VABI sceNetCtlApRestart(void); +int APS5_VABI sceNetBweCheckCallbackIpcInt(void); +int APS5_VABI sceNetBweClearEventIpcInt(int event); +int APS5_VABI sceNetBweRegisterCallbackIpcInt(NetCtlCallback callback, void* argument, int* callbackId); +int APS5_VABI sceNetBweUnregisterCallbackIpcInt(int callbackId); +int APS5_VABI sceNetBweGetInfoIpcInt(NetBweInfo* info); +int APS5_VABI sceNetBweStartInternetConnectionTestIpcInt(void); +int APS5_VABI sceNetBweFinishInternetConnectionTestIpcInt(void); +int APS5_VABI sceNetBweStartInternetConnectionTestBandwidthTestIpcInt(void); +int APS5_VABI sceNetBweSetInternetConnectionTestResultIpcInt(std::int32_t firstResult, std::int32_t secondResult); +int APS5_VABI sceNetCtlGetWifiInfoIpcInt(NetCtlWifiInfo* info); + +} diff --git a/core/libs/prx/libSceNetCtl/libSceNetCtl.hpp b/core/libs/prx/libSceNetCtl/libSceNetCtl.hpp new file mode 100644 index 0000000000..d60a79d958 --- /dev/null +++ b/core/libs/prx/libSceNetCtl/libSceNetCtl.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNetCtl/include/NetCtl.hpp" diff --git a/core/libs/prx/libSceNetCtlAp/Export.cpp b/core/libs/prx/libSceNetCtlAp/Export.cpp index d0bcb19845..8eafcd5e4f 100644 --- a/core/libs/prx/libSceNetCtlAp/Export.cpp +++ b/core/libs/prx/libSceNetCtlAp/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNetCtlAp/include/Api.hpp" #include #include #include @@ -7,6 +8,7 @@ namespace { +constexpr int NET_CTL_AP_ERROR_INVALID_OPT_STR = static_cast(0x80412901); constexpr int NET_CTL_AP_ERROR_NOT_STARTED = static_cast(0x8041298F); constexpr int NET_CTL_AP_ERROR_INVALID_TYPE = static_cast(0x80412990); constexpr int NET_CTL_AP_ERROR_INVALID_ADDR = static_cast(0x80412991); @@ -22,8 +24,6 @@ constexpr std::size_t NET_CTL_AP_INFO_SIZE = 152; constexpr std::size_t NET_CTL_AP_CONNECT_INFO_SIZE = 184; constexpr int MAX_CALLBACKS = 8; -using NetCtlApCallback = void(APS5_VABI*)(int, void*); - struct CallbackSlot { NetCtlApCallback func = nullptr; void* arg = nullptr; @@ -76,6 +76,7 @@ int APS5_VABI sceNetCtlApRegisterCallback(NetCtlApCallback func, void* arg, int* return 0; } } + *cid = -1; return NET_CTL_AP_ERROR_CALLBACK_MAX; } @@ -91,8 +92,8 @@ int APS5_VABI sceNetCtlApUnregisterCallback(int cid) { int APS5_VABI sceNetCtlApGetResult(int eventType, int* errorCode) { std::lock_guard lock(g_lock); if (!g_initialized) return NET_CTL_AP_ERROR_NOT_INITIALIZED; - if (!ValidEventType(eventType)) return NET_CTL_AP_ERROR_INVALID_TYPE; if (!errorCode) return NET_CTL_AP_ERROR_INVALID_ADDR; + if (!ValidEventType(eventType)) return NET_CTL_AP_ERROR_INVALID_TYPE; *errorCode = 0; return 0; } @@ -118,10 +119,14 @@ int APS5_VABI sceNetCtlApStop() { return g_initialized ? NET_CTL_AP_ERROR_NOT_STARTED : NET_CTL_AP_ERROR_NOT_INITIALIZED; } -int APS5_VABI sceNetCtlApGetConnectInfo(std::uint8_t* optStr, void* connectInfo) { - (void)optStr; +int APS5_VABI sceNetCtlApGetConnectInfo(const char* optStr, void* connectInfo) { std::lock_guard lock(g_lock); if (!g_initialized) return NET_CTL_AP_ERROR_NOT_INITIALIZED; + if (optStr != nullptr) { + std::size_t length = 0; + while (length < 9 && optStr[length] != 0) ++length; + if (length > 8) return NET_CTL_AP_ERROR_INVALID_OPT_STR; + } if (!connectInfo) return NET_CTL_AP_ERROR_INVALID_ADDR; if (*static_cast(connectInfo) != NET_CTL_AP_CONNECT_INFO_SIZE) return NET_CTL_AP_ERROR_INVALID_SIZE; return NET_CTL_AP_ERROR_NOT_STARTED; diff --git a/core/libs/prx/libSceNetCtlAp/include/Api.hpp b/core/libs/prx/libSceNetCtlAp/include/Api.hpp new file mode 100644 index 0000000000..31649a048f --- /dev/null +++ b/core/libs/prx/libSceNetCtlAp/include/Api.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "SceTypes.hpp" + +using NetCtlApCallback = void(APS5_VABI*)(int, void*); + +extern "C" { + +int APS5_VABI sceNetCtlApInit(); +void APS5_VABI sceNetCtlApTerm(); +int APS5_VABI sceNetCtlApCheckCallback(); +int APS5_VABI sceNetCtlApClearEvent(int eventType); +int APS5_VABI sceNetCtlApRegisterCallback(NetCtlApCallback func, void* arg, int* cid); +int APS5_VABI sceNetCtlApUnregisterCallback(int cid); +int APS5_VABI sceNetCtlApGetResult(int eventType, int* errorCode); +int APS5_VABI sceNetCtlApGetInfo(void* info); +int APS5_VABI sceNetCtlApGetState(int* state); +int APS5_VABI sceNetCtlApStop(); +int APS5_VABI sceNetCtlApGetConnectInfo(const char* optStr, void* connectInfo); + +} diff --git a/core/libs/prx/libSceNetCtlAp/libSceNetCtlAp.hpp b/core/libs/prx/libSceNetCtlAp/libSceNetCtlAp.hpp new file mode 100644 index 0000000000..1f1138e728 --- /dev/null +++ b/core/libs/prx/libSceNetCtlAp/libSceNetCtlAp.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNetCtlAp/include/Api.hpp" diff --git a/core/libs/prx/libSceNetCtlApDialog/Export.cpp b/core/libs/prx/libSceNetCtlApDialog/Export.cpp index e1d2bc4a27..d6c67ca827 100644 --- a/core/libs/prx/libSceNetCtlApDialog/Export.cpp +++ b/core/libs/prx/libSceNetCtlApDialog/Export.cpp @@ -1,8 +1,10 @@ +#include "prx/libSceNetCtlApDialog/include/Api.hpp" #include #include #include #include #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" namespace { @@ -12,19 +14,13 @@ constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_INVALID_ARGUMENT = static_cast(0x80B8000Au); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; -struct SceNetCtlApDialogResult { - std::int32_t result = COMMON_DIALOG_RESULT_USER_CANCELED; - std::uint8_t reserved[36] = {}; -}; - -static_assert(sizeof(SceNetCtlApDialogResult) == 40); - std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; } @@ -42,17 +38,20 @@ int APS5_VABI sceNetCtlApDialogTerminate(void) { return 0; } -int APS5_VABI sceNetCtlApDialogOpen(const void* param) { +int APS5_VABI sceNetCtlApDialogOpen(const NetCtlApDialogParam* param) { + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (!ValidateCommonDialogBaseParam(param->base)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (param->size != sizeof(*param) || param->reserved60 != 0 || std::find(std::begin(param->option), std::end(param->option), '\0') == std::end(param->option) || !std::all_of(std::begin(param->reserved), std::end(param->reserved), [](std::uint8_t value) { return value == 0; })) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; - if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } int APS5_VABI sceNetCtlApDialogClose(void) { if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (g_status.load() != COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_NOT_RUNNING; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } @@ -65,14 +64,14 @@ int APS5_VABI sceNetCtlApDialogGetStatus(void) { return g_status.load(); } -int APS5_VABI sceNetCtlApDialogGetResult(void* result) { +int APS5_VABI sceNetCtlApDialogGetResult(NetCtlApDialogResult* result) { + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (!std::all_of(std::begin(result->reserved), std::end(result->reserved), [](std::uint8_t value) { return value == 0; })) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; - const SceNetCtlApDialogResult dialogResult{}; - std::memcpy(result, &dialogResult, sizeof(dialogResult)); - return 0; + result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + return COMMON_DIALOG_RESULT_USER_CANCELED; } } diff --git a/core/libs/prx/libSceNetCtlApDialog/include/Api.hpp b/core/libs/prx/libSceNetCtlApDialog/include/Api.hpp new file mode 100644 index 0000000000..ec8df01ad0 --- /dev/null +++ b/core/libs/prx/libSceNetCtlApDialog/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNetCtlApDialogInitialize(void); +int APS5_VABI sceNetCtlApDialogTerminate(void); +int APS5_VABI sceNetCtlApDialogOpen(const NetCtlApDialogParam* param); +int APS5_VABI sceNetCtlApDialogClose(void); +int APS5_VABI sceNetCtlApDialogUpdateStatus(void); +int APS5_VABI sceNetCtlApDialogGetStatus(void); +int APS5_VABI sceNetCtlApDialogGetResult(NetCtlApDialogResult* result); + +} diff --git a/core/libs/prx/libSceNetCtlApDialog/libSceNetCtlApDialog.hpp b/core/libs/prx/libSceNetCtlApDialog/libSceNetCtlApDialog.hpp new file mode 100644 index 0000000000..b62243d95c --- /dev/null +++ b/core/libs/prx/libSceNetCtlApDialog/libSceNetCtlApDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNetCtlApDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceNgs2.native/CMakeLists.txt b/core/libs/prx/libSceNgs2.native/CMakeLists.txt index 93bb6c445e..885da1168b 100644 --- a/core/libs/prx/libSceNgs2.native/CMakeLists.txt +++ b/core/libs/prx/libSceNgs2.native/CMakeLists.txt @@ -2,7 +2,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/Ngs2Library.cmake) add_sce_ngs2_library(libSceNgs2.native) if(BUILD_TESTING) - foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom Parse Stream UserFx Report Geom VoiceIndex HandleErrors VoiceHandles) + foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom Parse Stream UserFx Report Geom VoiceIndex HandleErrors VoiceHandles VoiceEvents) string(TOLOWER ${ngs2Test} testName) add_test_executable(ngs2_${testName}_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Ngs2${ngs2Test}.cpp) target_include_directories(ngs2_${testName}_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src) @@ -14,4 +14,5 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + add_sce_ngs2_test(libSceNgs2.native ngs2_pcm Ngs2Pcm.cpp) endif() diff --git a/core/libs/prx/libSceNgs2.native/Export.cpp b/core/libs/prx/libSceNgs2.native/Export.cpp index 984f3782fe..6f5160436e 100644 --- a/core/libs/prx/libSceNgs2.native/Export.cpp +++ b/core/libs/prx/libSceNgs2.native/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNgs2.native/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceNgs2.native/Ngs2Library.cmake b/core/libs/prx/libSceNgs2.native/Ngs2Library.cmake index b89226f992..27dde605fe 100644 --- a/core/libs/prx/libSceNgs2.native/Ngs2Library.cmake +++ b/core/libs/prx/libSceNgs2.native/Ngs2Library.cmake @@ -5,6 +5,7 @@ function(add_sce_ngs2_library target) ${ngs2Dir}/src/Custom.cpp ${ngs2Dir}/src/Geom.cpp ${ngs2Dir}/src/Pan.cpp + ${ngs2Dir}/src/Pcm.cpp ${ngs2Dir}/src/Rack.cpp ${ngs2Dir}/src/Render.cpp ${ngs2Dir}/src/Reverb.cpp @@ -23,3 +24,15 @@ function(add_sce_ngs2_library target) ) configure_windows_unwind(${target}) endfunction() + +function(add_sce_ngs2_test library name source) + add_test_executable(${name}_tests ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/tests/${source}) + target_include_directories(${name}_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src) + target_link_libraries(${name}_tests PRIVATE ${library} atrac9 libkernel libc) + configure_windows_unwind(${name}_tests) + add_test(NAME ${name} COMMAND ${name}_tests) + if(WIN32) + set_tests_properties(${name} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endfunction() diff --git a/core/libs/prx/libSceNgs2.native/include/Api.hpp b/core/libs/prx/libSceNgs2.native/include/Api.hpp new file mode 100644 index 0000000000..7bc7c26ad3 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/include/Api.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNgs2JobSchedulerResetOption(void); +int APS5_VABI sceNgs2ModuleArrayEnumItems(void); +int APS5_VABI sceNgs2ModuleEnumConfigs(void); +int APS5_VABI sceNgs2ModuleQueueEnumItems(void); +int APS5_VABI sceNgs2RackQueryInfo(void); +int APS5_VABI sceNgs2RackRunCommands(void); +int APS5_VABI sceNgs2StreamCreate(void); +int APS5_VABI sceNgs2StreamCreateWithAllocator(void); +int APS5_VABI sceNgs2StreamDestroy(void); +int APS5_VABI sceNgs2StreamQueryBufferSize(void); +int APS5_VABI sceNgs2StreamQueryInfo(void); +int APS5_VABI sceNgs2StreamResetOption(void); +int APS5_VABI sceNgs2StreamRunCommands(void); +int APS5_VABI sceNgs2SystemQueryInfo(void); +int APS5_VABI sceNgs2SystemRunCommands(void); + +} diff --git a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp index a604adba3d..9f0abe430f 100644 --- a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp +++ b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp @@ -9,14 +9,37 @@ static constexpr int SCE_NGS2_OK = 0; static constexpr int SCE_NGS2_ERROR_INVALID_OUT_ADDRESS = static_cast(0x804A8010); static constexpr int SCE_NGS2_ERROR_INVALID_OUT_SIZE = static_cast(0x804A8011); +static constexpr int SCE_NGS2_ERROR_INVALID_BUFFER_INFO = static_cast(0x804A8020); +static constexpr int SCE_NGS2_ERROR_INVALID_BUFFER_ALLOCATOR = static_cast(0x804A8023); +static constexpr int SCE_NGS2_ERROR_INVALID_SAMPLE_RATE = static_cast(0x804A8051); static constexpr int SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE = static_cast(0x804A8201); static constexpr int SCE_NGS2_ERROR_INVALID_RACK_HANDLE = static_cast(0x804A8202); +static constexpr int SCE_NGS2_ERROR_INVALID_WAVEFORM_TYPE = static_cast(0x804A8050); +static constexpr int SCE_NGS2_ERROR_INVALID_RENDER_BUFFER = static_cast(0x804A8251); +static constexpr int SCE_NGS2_ERROR_INVALID_NUM_RENDER_BUFFERS = static_cast(0x804A830B); static constexpr int SCE_NGS2_ERROR_INVALID_VOICE_INDEX = static_cast(0x804A8305); +static constexpr int SCE_NGS2_ERROR_INVALID_NUM_GRAIN_SAMPLES = static_cast(0x804A8310); +static constexpr int SCE_NGS2_ERROR_INVALID_MAX_GRAIN_SAMPLES = static_cast(0x804A8311); static constexpr int SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA = static_cast(0x804A8430); static constexpr int SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT = static_cast(0x804A8431); static constexpr int SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT = static_cast(0x804A8432); +static constexpr int SCE_NGS2_ERROR_INVALID_PAN_UNIT_ANGLE = static_cast(0x804A8450); +static constexpr int SCE_NGS2_ERROR_INVALID_PAN_SPEAKER = static_cast(0x804A8451); +static constexpr int SCE_NGS2_ERROR_INVALID_PAN_MATRIX_FORMAT = static_cast(0x804A8452); +static constexpr int SCE_NGS2_ERROR_INVALID_PAN_WORK = static_cast(0x804A8453); +static constexpr int SCE_NGS2_ERROR_INVALID_PAN_PARAM = static_cast(0x804A8454); static constexpr int SCE_NGS2_ERROR_INVALID_VOICE_HANDLE = static_cast(0x804A8203); +static constexpr int SCE_NGS2_ERROR_INVALID_VOICE_ACCESS = static_cast(0x804A8351); static constexpr int SCE_NGS2_ERROR_NULL_PARAM_LIST = static_cast(0x804A8360); +static constexpr int SCE_NGS2_ERROR_INVALID_GEOM_LISTENER_ADDRESS = static_cast(0x804A8461); +static constexpr int SCE_NGS2_ERROR_INVALID_GEOM_FLAG = static_cast(0x804A8463); +static constexpr int SCE_NGS2_ERROR_INVALID_OPTION_SIZE = static_cast(0x804A8013); +static constexpr int SCE_NGS2_ERROR_INVALID_NUM_CHANNELS = static_cast(0x804A8052); +static constexpr int SCE_NGS2_ERROR_INVALID_PITCH_SHIFT_OPTION = static_cast(0x804A8098); +static constexpr int SCE_NGS2_ERROR_INVALID_REVERB_OPTION = static_cast(0x804A8099); +static constexpr int SCE_NGS2_ERROR_INVALID_STATE_SIZE = static_cast(0x804A8302); +static constexpr int SCE_NGS2_ERROR_INVALID_BUFFER_INDEX = static_cast(0x804A8306); +static constexpr int SCE_NGS2_ERROR_INVALID_NUM_BUFFERS = static_cast(0x804A830B); static constexpr std::uint32_t SCE_NGS2_RACK_ID_SAMPLER = 0x1000; static constexpr std::uint32_t SCE_NGS2_RACK_ID_SUBMIXER = 0x2000; @@ -24,9 +47,16 @@ static constexpr std::uint32_t SCE_NGS2_RACK_ID_REVERB = 0x2001; static constexpr std::uint32_t SCE_NGS2_RACK_ID_MASTERING = 0x3000; static constexpr std::uint32_t SCE_NGS2_RACK_ID_CUSTOM_SAMPLER = 0x4001; static constexpr std::uint32_t SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER = 0x4002; +static constexpr std::uint32_t SCE_NGS2_RACK_ID_CUSTOM_MASTERING = 0x4003; +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_U8 = 0x11; static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_I16L = 0x12; +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_I16B = 0x13; +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_I24L = 0x14; +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_I32L = 0x16; static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_F32L = 0x18; +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_F32B = 0x19; +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_F64L = 0x1a; static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_ATRAC9 = 0x40; static constexpr std::uint32_t SCE_NGS2_VOICE_EVENT_PLAY = 1; @@ -66,15 +96,31 @@ static constexpr std::uint32_t SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_ static constexpr std::uint32_t SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_EXIT_LOOP = 0x40010004; static constexpr std::uint32_t SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_PITCH = 0x40010005; static constexpr std::uint32_t SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP = 0x40020000; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_SETUP = 0x40030000; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_GAIN = 0x40030004; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_OUTPUT = 0x40030005; static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_FILTER = 0x40001300; -static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX = 0x40001d00; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN = 0x40001500; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX = 0x40001900; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_DELAY = 0x40001d00; static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2 = 0x40001f00; static constexpr std::uint32_t SCE_NGS2_CUSTOM_MAX_MODULES = 24; static constexpr std::uint32_t SCE_NGS2_CUSTOM_MAX_PORTS = 16; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MAX_BUFFERS = 4; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS = 37; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_SUBMIXER_MAX_INPUTS = 2; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE = 12; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_PITCH_SHIFT_OPTION_SIZE = 8; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_PITCH_SHIFT_STATE_SIZE = 8; static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_FILTER = 0x13; -static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_USER_FX = 0x1d; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_GAIN = 0x15; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_REVERB = 0x1b; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_USER_FX = 0x19; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_DELAY = 0x1d; static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2 = 0x1f; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT = 0x20; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_CONVOLUTION = 0x23; static constexpr std::uint32_t SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE = 1; static constexpr std::uint32_t SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND = 2; @@ -186,6 +232,15 @@ struct Ngs2CustomModuleOption { std::uint32_t size; }; +struct Ngs2CustomDelayModuleOption { + Ngs2CustomModuleOption custom_module_option; + std::uint32_t type; + std::uint32_t max_taps; + float max_length; + std::uint32_t reserved; +}; +static_assert(sizeof(Ngs2CustomDelayModuleOption) == 20); + struct Ngs2UserFx2SetupContext { void* common; void* param; @@ -271,6 +326,18 @@ struct Ngs2CustomRackModuleInfo { }; static_assert(sizeof(Ngs2CustomRackModuleInfo) == 40); +struct Ngs2CustomModuleInfo { + std::uint32_t module_id; + std::uint32_t source_buffer_id; + std::uint32_t extra_buffer_id; + std::uint32_t dest_buffer_id; + std::uint32_t state_offset; + std::uint32_t state_size; + std::uint32_t reserved; + std::uint32_t reserved2; +}; +static_assert(sizeof(Ngs2CustomModuleInfo) == 32); + struct Ngs2CustomRackPortInfo { std::uint32_t source_buffer_id; std::uint32_t reserved; @@ -305,6 +372,8 @@ struct Ngs2CustomSubmixerRackOption { }; static_assert(sizeof(Ngs2CustomSubmixerRackOption) == 1288); +using Ngs2CustomMasteringRackOption = Ngs2CustomSubmixerRackOption; + struct Ngs2RackInfo { char name[64]; Ngs2Handle rack_handle; @@ -523,6 +592,12 @@ struct Ngs2CustomVoiceUserFx2Param { }; static_assert(sizeof(Ngs2CustomVoiceUserFx2Param) == 24); +struct Ngs2CustomVoiceGainParam { + Ngs2VoiceParamHeader header; + float gain[SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS]; +}; +static_assert(sizeof(Ngs2CustomVoiceGainParam) == 156); + struct Ngs2CustomVoiceFilterParam { Ngs2VoiceParamHeader header; std::uint32_t type; @@ -607,6 +682,12 @@ struct Ngs2VoicePortInfo { }; static_assert(sizeof(Ngs2VoicePortInfo) == 24); +struct Ngs2VoiceMatrixInfo { + std::uint32_t num_levels; + float levels[64]; +}; +static_assert(sizeof(Ngs2VoiceMatrixInfo) == 260); + struct Ngs2VoiceState { std::uint32_t state_flags; std::int32_t error_code; diff --git a/core/libs/prx/libSceNgs2.native/libSceNgs2.native.hpp b/core/libs/prx/libSceNgs2.native/libSceNgs2.native.hpp new file mode 100644 index 0000000000..1ca6a1c884 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/libSceNgs2.native.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "prx/libSceNgs2.native/include/Api.hpp" +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" diff --git a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp index 6c08a090c1..dda42ea297 100644 --- a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include #include #include @@ -16,8 +17,10 @@ #include "Ngs2Internal.hpp" static constexpr std::uint16_t WAVE_FORMAT_PCM = 0x1; +static constexpr std::uint16_t WAVE_FORMAT_IEEE_FLOAT = 0x3; static constexpr std::uint16_t WAVE_FORMAT_EXTENSIBLE = 0xfffe; static constexpr std::uint8_t ATRAC9_GUID[16] = {0xd2, 0x42, 0xe1, 0x47, 0xba, 0x36, 0x8d, 0x4d, 0x88, 0xfc, 0x61, 0x65, 0x4f, 0x8c, 0x83, 0x6c}; +static constexpr std::uint8_t PCM_GUID[16] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}; static constexpr std::size_t FMT_ATRAC9_SIZE = 52; static constexpr std::size_t FMT_GUID_OFFSET = 24; static constexpr std::size_t FMT_CONFIG_OFFSET = 44; @@ -26,6 +29,8 @@ static constexpr std::uint32_t MAX_SAMPLE_RATE = 192000; static constexpr std::uint32_t WAVEFORM_TYPE_PCM_FIRST = 0x10; static constexpr std::uint32_t WAVEFORM_TYPE_PCM_LAST = 0x1b; static constexpr std::uint32_t WAVEFORM_TYPE_VAG = 0x1c; +static constexpr std::uint32_t VAG_FRAME_SAMPLES = 28; +static constexpr std::uint32_t ATRAC9_FRAME_SAMPLES[16] = {0, 64, 0, 0, 128, 0, 0, 256, 0, 0, 0, 0, 0, 0, 0, 0}; static constexpr std::uint32_t PCM_BYTES_PER_SAMPLE[] = {1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 8, 8}; static bool IsPcmType(std::uint32_t type) { @@ -43,6 +48,37 @@ static Ngs2WaveformBlock PcmBlock(std::uint32_t type, std::uint32_t channels, st return block; } +static Ngs2WaveformBlock VagBlock(std::uint32_t channels, std::uint32_t pos, std::uint32_t count) { + const std::uint32_t frameBytes = 16u * channels; + const std::uint32_t first = pos / VAG_FRAME_SAMPLES; + const std::uint32_t past = (pos + count + VAG_FRAME_SAMPLES - 1u) / VAG_FRAME_SAMPLES; + const auto offset = static_cast(static_cast(first) * frameBytes); + Ngs2WaveformBlock block{}; + block.data_offset = offset; + block.data_size = static_cast(past * frameBytes - offset); + block.num_samples = (past - first) * VAG_FRAME_SAMPLES; + return block; +} + +static Ngs2WaveformBlock Atrac9Block(std::uint32_t config, std::uint32_t pos, std::uint32_t count) { + const std::uint32_t rateIndex = (config >> 20) & 0xfu; + const std::uint32_t frameSamples = ATRAC9_FRAME_SAMPLES[rateIndex]; + if (frameSamples == 0) throw std::runtime_error("NGS2: ATRAC9 waveform blocks at sampling rate index " + std::to_string(rateIndex) + " are not implemented"); + const std::uint32_t superframeShift = (config >> 3) & 3u; + const std::uint32_t superframeBytes = (((config >> 5) & 0x7ffu) + 1u) << superframeShift; + const std::uint32_t superframeSamples = frameSamples << superframeShift; + const std::uint32_t first = pos / superframeSamples; + const std::uint32_t lead = frameSamples + pos; + const std::uint32_t past = (count + lead + superframeSamples - 1u) / superframeSamples; + const std::uint32_t offset = first * superframeBytes; + Ngs2WaveformBlock block{}; + block.data_offset = offset; + block.data_size = past * superframeBytes - offset; + block.num_skip_samples = lead - first * superframeSamples; + block.num_samples = count; + return block; +} + void Ngs2Atrac9DecoderDeleter::operator()(void* handle) const { Atrac9ReleaseHandle(handle); } @@ -272,44 +308,31 @@ static int ReadChunks(std::size_t size, TRead&& read, RiffChunks& chunks) { return SCE_NGS2_OK; } -static bool AddAtrac9Range(Ngs2WaveformInfo& info, std::uint64_t begin, std::uint32_t samples, std::uint32_t repeats) { - if (samples == 0) return true; - const auto preroll = std::min(begin, info.num_audio_unit_samples); - const auto firstFrame = (begin - preroll) / info.num_audio_frame_samples; - const auto skip = begin - firstFrame * info.num_audio_frame_samples; - const auto frames = (skip + samples + info.num_audio_frame_samples - 1) / info.num_audio_frame_samples; - const auto offset = firstFrame * info.audio_frame_size; - const auto bytes = frames * info.audio_frame_size; - if (offset > info.data_size || bytes > info.data_size - offset) return false; - auto& block = info.block[info.num_blocks++]; - block = {info.data_offset + offset, bytes, repeats, static_cast(skip), samples, 0, 0}; - return true; +static void AddAtrac9Block(Ngs2WaveformInfo& info, std::uint32_t begin, std::uint32_t samples, std::uint32_t repeats) { + auto block = Atrac9Block(info.format.config_data, begin, samples); + if (block.data_offset > info.data_size || block.data_size > info.data_size - block.data_offset) + throw std::runtime_error("NGS2: an ATRAC9 loop block reaching past the data chunk is not implemented"); + block.data_offset += info.data_offset; + block.num_repeats = repeats; + info.block[info.num_blocks++] = block; } static int ParseAtrac9Loop(const RiffChunks& chunks, Ngs2WaveformInfo& info) { if (chunks.sampler == nullptr) return SCE_NGS2_OK; - if (chunks.samplerSize < 36) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; - const auto count = ReadLe32(chunks.sampler + 28); - if (count > (chunks.samplerSize - 36) / 24) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; - if (count == 0) return SCE_NGS2_OK; - if (count != 1) throw std::runtime_error("NGS2: multiple waveform loops are not implemented"); - const auto* loop = chunks.sampler + 36; - if (ReadLe32(loop + 4) != 0 || ReadLe32(loop + 16) != 0) - throw std::runtime_error("NGS2: non-forward or fractional waveform loops are not implemented"); - const std::uint64_t begin = ReadLe32(loop + 8); - const std::uint64_t end = static_cast(ReadLe32(loop + 12)) + 1; - const auto skip = info.block[0].num_skip_samples; - const std::uint64_t waveformEnd = static_cast(skip) + info.num_samples; - if (begin < skip || begin >= end || end > waveformEnd) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; - const auto plays = ReadLe32(loop + 20); - info.loop_begin_position = static_cast(begin - skip); - info.loop_end_position = static_cast(end - skip); + if (chunks.samplerSize < 32) throw std::runtime_error("NGS2: a smpl chunk too short for its loop count is not implemented"); + if (ReadLe32(chunks.sampler + 28) == 0) return SCE_NGS2_OK; + if (chunks.samplerSize < 52) throw std::runtime_error("NGS2: a smpl chunk too short for its first loop is not implemented"); + if (ReadLe32(chunks.sampler + 44) < info.num_delay_samples) throw std::runtime_error("NGS2: an ATRAC9 loop starting inside the decoder delay is not implemented"); + const std::uint32_t begin = ReadLe32(chunks.sampler + 44) - info.num_delay_samples; + const std::uint32_t end = ReadLe32(chunks.sampler + 48) - info.num_delay_samples + 1u; + info.loop_begin_position = begin; + info.loop_end_position = end; + if (end <= begin) return SCE_NGS2_OK; + if (end > info.num_samples) throw std::runtime_error("NGS2: a waveform loop ending past the last sample is not implemented"); info.num_blocks = 0; - info.block[0] = {}; - if (!AddAtrac9Range(info, skip, info.loop_begin_position, 0) || - !AddAtrac9Range(info, begin, static_cast(end - begin), plays == 0 ? UINT32_MAX : plays - 1) || - !AddAtrac9Range(info, end, static_cast(waveformEnd - end), 0)) - return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; + if (begin != 0) AddAtrac9Block(info, 0, begin, 0); + AddAtrac9Block(info, begin, end - begin, UINT32_MAX); + if (end != info.num_samples) AddAtrac9Block(info, end, info.num_samples - end, 0); return SCE_NGS2_OK; } @@ -333,7 +356,7 @@ static int ParseAtrac9(const RiffChunks& chunks, Ngs2WaveformInfo& info) { info.num_audio_unit_per_frame = static_cast(codec.framesInSuperframe); info.audio_frame_size = static_cast(codec.superframeSize); info.num_audio_frame_samples = static_cast(codec.frameSamples * codec.framesInSuperframe); - info.num_delay_samples = ReadLe32(chunks.fact + 4); + info.num_delay_samples = static_cast(codec.frameSamples); info.num_blocks = 1; info.block[0].data_offset = chunks.dataOffset; info.block[0].data_size = chunks.dataSize; @@ -342,19 +365,27 @@ static int ParseAtrac9(const RiffChunks& chunks, Ngs2WaveformInfo& info) { return ParseAtrac9Loop(chunks, info); } -static int ParsePcm16(const RiffChunks& chunks, std::size_t size, Ngs2WaveformInfo& info) { +static std::uint32_t PcmWaveformType(std::uint32_t bits, bool floating) { + if (floating) return bits == 32 ? 0x18 : bits == 64 ? 0x1a : 0; + return bits == 8 ? 0x11 : bits == 16 ? SCE_NGS2_WAVEFORM_TYPE_PCM_I16L : bits == 24 ? 0x14 : bits == 32 ? 0x16 : 0; +} + +static int ParsePcm(const RiffChunks& chunks, std::size_t size, Ngs2WaveformInfo& info, bool floating) { const auto* format = chunks.format; if (chunks.formatSize < 16) return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT; + const std::uint32_t bits = ReadLe16(format + 14); + const std::uint32_t type = PcmWaveformType(bits, floating); + if (type == 0) return SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; const std::uint32_t channels = ReadLe16(format + 2); const std::uint32_t sampleRate = ReadLe32(format + 4); - const std::uint32_t bytesPerFrame = channels * sizeof(std::int16_t); - if (ReadLe16(format + 14) != 16 || channels == 0 || channels > NGS2_MAX_CHANNELS || sampleRate == 0 || sampleRate > MAX_SAMPLE_RATE || ReadLe16(format + 12) != bytesPerFrame) { + const std::uint32_t bytesPerFrame = channels * (bits / 8); + if (channels == 0 || channels > NGS2_MAX_CHANNELS || sampleRate == 0 || sampleRate > MAX_SAMPLE_RATE || ReadLe16(format + 12) != bytesPerFrame) { return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT; } if (chunks.sampler != nullptr && chunks.samplerSize >= 32 && ReadLe32(chunks.sampler + 28) != 0) throw std::runtime_error("NGS2: parsing looped waveforms is not implemented"); if (chunks.dataSize > size - chunks.dataOffset || chunks.dataSize % bytesPerFrame != 0) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; const std::size_t dataSize = chunks.dataSize; - info.format = {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, channels, sampleRate, 0, 0, 0}; + info.format = {type, channels, sampleRate, 0, 0, 0}; info.data_offset = static_cast(chunks.dataOffset); info.data_size = static_cast(dataSize); info.num_samples = static_cast(dataSize / bytesPerFrame); @@ -377,7 +408,12 @@ static int ParseWaveform(const RiffChunks& chunks, std::size_t size, Ngs2Wavefor std::memcmp(chunks.format + FMT_GUID_OFFSET, ATRAC9_GUID, sizeof(ATRAC9_GUID)) == 0) { return ParseAtrac9(chunks, info); } - if (tag == WAVE_FORMAT_PCM) return ParsePcm16(chunks, size, info); + if (tag == WAVE_FORMAT_EXTENSIBLE && chunks.formatSize >= FMT_GUID_OFFSET + sizeof(PCM_GUID) && + std::memcmp(chunks.format + FMT_GUID_OFFSET, PCM_GUID, sizeof(PCM_GUID)) == 0) { + return ParsePcm(chunks, size, info, false); + } + if (tag == WAVE_FORMAT_PCM) return ParsePcm(chunks, size, info, false); + if (tag == WAVE_FORMAT_IEEE_FLOAT) return ParsePcm(chunks, size, info, true); throw std::runtime_error("NGS2: parsing waveform format tag " + Ngs2Hex(tag) + " is not implemented"); } @@ -432,21 +468,34 @@ int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_ *block = PcmBlock(type, format->num_channels, sample_pos, num_samples); return SCE_NGS2_OK; } - if (type == WAVEFORM_TYPE_VAG) throw std::runtime_error("NGS2: waveform blocks of type " + Ngs2Hex(type) + " are not implemented"); + if (type == WAVEFORM_TYPE_VAG) { + *block = VagBlock(format->num_channels, sample_pos, num_samples); + return SCE_NGS2_OK; + } if (type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) return SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; - if (format->num_channels == 0 || format->num_channels > NGS2_MAX_CHANNELS || format->sample_rate == 0 || format->sample_rate > MAX_SAMPLE_RATE) { + *block = Atrac9Block(format->config_data, sample_pos, num_samples); + return SCE_NGS2_OK; +} + +int APS5_VABI sceNgs2GetWaveformFrameInfo(const Ngs2WaveformFormat* format, uint32_t* out_frame_size, uint32_t* out_num_frame_samples, uint32_t* out_units_per_frame, uint32_t* out_num_delay_samples) { + if (out_frame_size == nullptr || out_num_frame_samples == nullptr || out_units_per_frame == nullptr || out_num_delay_samples == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + if (format == nullptr || format->num_channels == 0 || format->num_channels > NGS2_MAX_CHANNELS || format->sample_rate == 0 || format->sample_rate > MAX_SAMPLE_RATE) { return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT; } + if (format->waveform_type == SCE_NGS2_WAVEFORM_TYPE_PCM_I16L) { + *out_frame_size = format->num_channels * sizeof(std::int16_t); + *out_num_frame_samples = 1; + *out_units_per_frame = 1; + *out_num_delay_samples = 0; + return SCE_NGS2_OK; + } + if (format->waveform_type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) throw std::runtime_error("NGS2: the frame info of waveform type " + Ngs2Hex(format->waveform_type) + " is not implemented"); Atrac9CodecInfo codec{}; if (!MatchingCodecInfo(*format, codec)) return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT; - const std::uint64_t superframeSamples = static_cast(codec.frameSamples) * codec.framesInSuperframe; - const std::uint64_t skip = sample_pos % superframeSamples; - block->data_offset = sample_pos / superframeSamples * codec.superframeSize; - if (num_samples != 0) { - block->data_size = (skip + num_samples + superframeSamples - 1) / superframeSamples * codec.superframeSize; - block->num_skip_samples = static_cast(skip); - } - block->num_samples = num_samples; + *out_frame_size = static_cast(codec.superframeSize); + *out_num_frame_samples = static_cast(codec.frameSamples * codec.framesInSuperframe); + *out_units_per_frame = static_cast(codec.framesInSuperframe); + *out_num_delay_samples = static_cast(codec.frameSamples); return SCE_NGS2_OK; } diff --git a/core/libs/prx/libSceNgs2.native/src/Custom.cpp b/core/libs/prx/libSceNgs2.native/src/Custom.cpp index d3e586a4d9..5d066d889a 100644 --- a/core/libs/prx/libSceNgs2.native/src/Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Custom.cpp @@ -12,28 +12,96 @@ static constexpr std::uint32_t USER_FX2_FLAG_FIRST_PROCESS = 1; static constexpr std::uint32_t USER_FX2_FLAG_PARAM_CHANGED = 2; -void Ngs2CheckCustomRack(const Ngs2CustomRackOption& option) { - if (option.num_buffers != 1) throw std::runtime_error("NGS2: custom racks with " + std::to_string(option.num_buffers) + " buffers are not implemented"); +static const char* BuiltInModuleName(std::uint32_t moduleId) { + switch (moduleId) { + case SCE_NGS2_CUSTOM_MODULE_ID_GAIN: return "Gain"; + case SCE_NGS2_CUSTOM_MODULE_ID_REVERB: return "Reverb"; + case SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT: return "PitchShift"; + case SCE_NGS2_CUSTOM_MODULE_ID_CONVOLUTION: return "Convolution"; + default: return nullptr; + } +} + +static std::uint32_t OptionWord(const Ngs2CustomModuleOption& option) { + std::uint32_t word = 0; + std::memcpy(&word, reinterpret_cast(&option) + sizeof(option), sizeof(word)); + return word; +} + +static int CheckBuiltInModule(const Ngs2CustomRackModuleInfo& info) { + const auto stateSizeIs = [&info](std::uint32_t size) { return info.state_size == 0 || info.state_size == size; }; + if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_GAIN || info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX) { + if (!stateSizeIs(0)) return SCE_NGS2_ERROR_INVALID_STATE_SIZE; + } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_REVERB) { + if (!stateSizeIs(0)) return SCE_NGS2_ERROR_INVALID_STATE_SIZE; + if (info.option != nullptr && info.option->size != SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE) return SCE_NGS2_ERROR_INVALID_OPTION_SIZE; + if (info.option != nullptr && OptionWord(*info.option) > 1) return SCE_NGS2_ERROR_INVALID_REVERB_OPTION; + } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT) { + if (!stateSizeIs(SCE_NGS2_CUSTOM_PITCH_SHIFT_STATE_SIZE)) return SCE_NGS2_ERROR_INVALID_STATE_SIZE; + if (info.option != nullptr && info.option->size == SCE_NGS2_CUSTOM_PITCH_SHIFT_OPTION_SIZE && OptionWord(*info.option) != 0) { + return SCE_NGS2_ERROR_INVALID_PITCH_SHIFT_OPTION; + } + } + return SCE_NGS2_OK; +} + +static void CheckDelayModule(std::uint32_t rackId, const Ngs2CustomRackModuleInfo& info) { + if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || info.state_size != 0 || info.state_offset != 0) { + throw std::runtime_error("NGS2: this custom Delay rack or module state is not implemented"); + } + if (info.option == nullptr || info.option->size != sizeof(Ngs2CustomDelayModuleOption)) { + throw std::runtime_error("NGS2: this custom Delay module option size is not implemented"); + } + Ngs2CustomDelayModuleOption delay{}; + std::memcpy(&delay, info.option, sizeof(delay)); + if (delay.type > 1 || delay.max_taps == 0 || delay.max_taps > 8 || + !(delay.max_length >= 1.0f && delay.max_length <= 20000.0f) || delay.reserved != 0) { + throw std::runtime_error("NGS2: these custom Delay module option values are not implemented"); + } +} + +int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option, std::uint32_t maxInputs) { + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING && option.num_modules != 0) { + throw std::runtime_error("NGS2: custom mastering racks with modules are not implemented"); + } + if (option.num_buffers == 0 || option.num_buffers > SCE_NGS2_CUSTOM_MAX_BUFFERS || option.num_buffers < maxInputs) { + return SCE_NGS2_ERROR_INVALID_NUM_BUFFERS; + } if (option.num_modules > SCE_NGS2_CUSTOM_MAX_MODULES || option.rack_option.max_ports > SCE_NGS2_CUSTOM_MAX_PORTS) APS5_INVALID_ARG_EX; for (std::uint32_t m = 0; m < option.num_modules; m++) { const auto& info = option.module[m]; if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) { if (info.option == nullptr || info.option->size != sizeof(Ngs2CustomUserFx2ModuleOption)) APS5_INVALID_ARG_EX; - if (info.source_buffer_id != 0 || info.dest_buffer_id != 0) APS5_INVALID_ARG_EX; if (reinterpret_cast(info.option)->process_handler == nullptr) APS5_INVALID_ARG_EX; } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER) { if (info.option != nullptr || info.state_size != 0 || info.state_offset != 0) APS5_INVALID_ARG_EX; - if (info.source_buffer_id != 0 || info.extra_buffer_id != 0 || info.dest_buffer_id != 0) APS5_INVALID_ARG_EX; } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX) { - if (info.source_buffer_id != 0 || info.dest_buffer_id != 0) APS5_INVALID_ARG_EX; - if (info.option != nullptr && info.option->size < sizeof(Ngs2CustomModuleOption)) APS5_INVALID_ARG_EX; - } else { + if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::runtime_error("NGS2: the custom UserFx module on a custom sampler rack is not implemented"); + if (info.option != nullptr) throw std::runtime_error("NGS2: a custom UserFx module option is not implemented"); + } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_DELAY) { + CheckDelayModule(rackId, info); + } else if (BuiltInModuleName(info.module_id) == nullptr) { throw std::runtime_error("NGS2: custom module id " + Ngs2Hex(info.module_id) + " is not implemented"); } + if (info.source_buffer_id >= option.num_buffers || info.extra_buffer_id >= option.num_buffers || info.dest_buffer_id >= option.num_buffers) { + return SCE_NGS2_ERROR_INVALID_BUFFER_INDEX; + } + const int result = CheckBuiltInModule(info); + if (result != SCE_NGS2_OK) return result; + } + if (option.num_buffers != 1) throw std::runtime_error("NGS2: custom racks with " + std::to_string(option.num_buffers) + " buffers are not implemented"); + for (std::uint32_t m = 0; m < option.num_modules; m++) { + const auto moduleId = option.module[m].module_id; + if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN && option.module[m].option != nullptr) throw std::runtime_error("NGS2: an option for the custom Gain module is not implemented"); + if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN && rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) continue; + if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN) throw std::runtime_error("NGS2: the custom Gain module on a custom sampler rack is not implemented"); + const char* name = BuiltInModuleName(moduleId); + if (name != nullptr) throw std::runtime_error(std::string("NGS2: the custom ") + name + " module is not implemented"); } for (std::uint32_t p = 0; p < option.rack_option.max_ports; p++) { if (option.port[p].source_buffer_id != 0) APS5_INVALID_ARG_EX; } + return SCE_NGS2_OK; } static Ngs2UserFx2SetupContext UserFxContext(Ngs2Rack& rack, std::size_t module, std::uint32_t voiceIndex) { @@ -44,14 +112,18 @@ static Ngs2UserFx2SetupContext UserFxContext(Ngs2Rack& rack, std::size_t module, void Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option) { rack.customModuleIds.resize(option.num_modules); + rack.customModules.resize(option.num_modules); rack.userFx.resize(option.num_modules); rack.userFxCommon.resize(option.num_modules); for (auto& voice : rack.voices) { voice.userFx.resize(option.num_modules); voice.customUserFx.resize(option.num_modules); + voice.customGain.assign(option.num_modules, {}); } for (std::uint32_t m = 0; m < option.num_modules; m++) { rack.customModuleIds[m] = option.module[m].module_id; + const auto& info = option.module[m]; + rack.customModules[m] = {info.module_id, info.source_buffer_id, info.extra_buffer_id, info.dest_buffer_id, info.state_offset, info.state_size, 0, 0}; if (option.module[m].module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER) continue; if (option.module[m].module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) { const auto& module = *reinterpret_cast(option.module[m].option); @@ -116,8 +188,31 @@ static void SetCustomUserFxParam(Ngs2Voice& voice, std::uint32_t index, const Ng fx.flags = 1; } +static void SetGainParam(Ngs2Voice& voice, std::uint32_t index, const Ngs2CustomVoiceGainParam& param) { + if (index >= voice.customGain.size() || voice.rack->customModuleIds[index] != SCE_NGS2_CUSTOM_MODULE_ID_GAIN) APS5_INVALID_ARG_EX; + for (const float gain : param.gain) { + if (!(gain >= -32.0f && gain <= 32.0f)) throw std::runtime_error("NGS2: a custom Gain level outside -32 to 32 is not implemented"); + } + std::copy(std::begin(param.gain), std::end(param.gain), voice.customGain[index].target.begin()); +} + +static void ProcessGain(Ngs2Voice& voice, Ngs2VoiceCustomGain& gain, const std::vector& channels, std::uint32_t grain) { + if (voice.hasSamples) { + for (std::uint32_t c = 0; c < voice.channels; c++) { + const float from = gain.current[c]; + const float step = (gain.target[c] - from) / static_cast(grain); + for (std::uint32_t i = 0; i < grain; i++) channels[c][i] *= from + step * static_cast(i + 1); + } + voice.hasSamples = std::any_of(voice.samples.begin(), voice.samples.end(), [](float sample) { return sample != 0.0f; }); + } + gain.current = gain.target; +} + void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { const auto rackId = voice.rack->rackId; + if ((param.id & ~0xffu) == SCE_NGS2_CUSTOM_VOICE_PARAM_DELAY) { + throw std::runtime_error("NGS2: the custom Delay module parameters are not implemented"); + } if (param.id == SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP) { if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); SetupCustomSubmixer(voice, ParamAs(param)); @@ -148,6 +243,11 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { Ngs2SetFilter(voice, filter); return; } + if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN) { + if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); + SetGainParam(voice, param.id & 0x1f, ParamAs(param)); + return; + } if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2) { if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) { throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); @@ -169,7 +269,10 @@ void Ngs2ProcessUserFx(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp std::vector channels(voice.channels); for (std::uint32_t c = 0; c < voice.channels; c++) channels[c] = voice.samples.data() + static_cast(c) * grain; for (std::size_t m = 0; m < voice.rack->customModuleIds.size(); m++) { - if (voice.rack->customModuleIds[m] == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) { + if (voice.rack->customModuleIds[m] == SCE_NGS2_CUSTOM_MODULE_ID_DELAY) continue; + if (voice.rack->customModuleIds[m] == SCE_NGS2_CUSTOM_MODULE_ID_GAIN) { + ProcessGain(voice, voice.customGain[m], channels, grain); + } else if (voice.rack->customModuleIds[m] == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) { if (!voice.hasSamples) continue; auto& fx = voice.userFx[m]; const auto& module = voice.rack->userFx[m]; diff --git a/core/libs/prx/libSceNgs2.native/src/Geom.cpp b/core/libs/prx/libSceNgs2.native/src/Geom.cpp index 28f5d9f10c..9b68d818ef 100644 --- a/core/libs/prx/libSceNgs2.native/src/Geom.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Geom.cpp @@ -41,9 +41,9 @@ Vector PerpendicularHint(const Vector& front) { } int Ngs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* outWork, std::uint32_t flags) { - if (param == nullptr) APS5_INVALID_ARG_EX; + if (param == nullptr) return SCE_NGS2_ERROR_INVALID_GEOM_LISTENER_ADDRESS; if (outWork == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - if (flags > 1) throw std::runtime_error("NGS2: unknown listener coordinate flags"); + if (flags > 1) return SCE_NGS2_ERROR_INVALID_GEOM_FLAG; if (!std::isfinite(param->sound_speed) || param->sound_speed <= 0.0f) APS5_INVALID_ARG_EX; const Vector position = ReadVector(param->position); ReadVector(param->velocity); diff --git a/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp b/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp index adc9f3d665..b5a9935162 100644 --- a/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp +++ b/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp @@ -108,11 +108,17 @@ struct Ngs2VoiceCustomUserFx { std::uint32_t flags = 0; }; +struct Ngs2VoiceCustomGain { + std::array current{}; + std::array target{}; +}; + struct Ngs2Voice { Ngs2Rack* rack = nullptr; Ngs2PlayState state = Ngs2PlayState::Empty; std::uint32_t stateFlags = 0; std::uint32_t channels = 0; + std::uint32_t inputChannels = 0; std::uint32_t sampleRate = 0; std::uint32_t waveformType = 0; Ngs2Atrac9 atrac9; @@ -136,6 +142,7 @@ struct Ngs2Voice { float lfeLevel = 1.0f; std::vector userFx; std::vector customUserFx; + std::vector customGain; Ngs2UserFxProcessHandler userFxHandler = nullptr; std::array userFxData{}; std::uint32_t userFxFlags = 0; @@ -147,6 +154,7 @@ struct Ngs2Voice { void SetEvent(std::uint32_t eventId); void ResetSetup(); const std::uint8_t* WaveformData() const; + std::uint32_t InputChannels() const { return inputChannels != 0 ? inputChannels : channels; } }; struct Ngs2Rack { @@ -166,6 +174,7 @@ struct Ngs2Rack { std::vector userFx; std::vector> userFxCommon; std::vector customModuleIds; + std::vector customModules; std::thread::id lockOwner{}; std::uint32_t lockDepth = 0; }; @@ -182,6 +191,8 @@ struct Ngs2System { }; std::string Ngs2Hex(std::uint32_t value); +std::size_t Ngs2PcmSampleBytes(std::uint32_t waveformType); +void Ngs2ReadPcmFrame(std::uint32_t waveformType, std::uint32_t channels, const std::uint8_t* frame, float* output); const std::uint8_t* Ngs2StreamEnd(const Ngs2Voice& voice, const Ngs2Block& block); std::recursive_mutex& Ngs2Mutex(); @@ -229,7 +240,7 @@ std::size_t Ngs2Atrac9BlockBytes(const Ngs2Voice& voice, const Ngs2WaveformBlock void Ngs2RestartAtrac9(Ngs2Voice& voice); void Ngs2ConsumeAtrac9(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate); bool Ngs2FinishBlock(Ngs2Voice& voice); -void Ngs2CheckCustomRack(const Ngs2CustomRackOption& option); +int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option, std::uint32_t maxInputs); void Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option); void Ngs2CleanupUserFx(Ngs2Rack& rack); void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param); diff --git a/core/libs/prx/libSceNgs2.native/src/Pan.cpp b/core/libs/prx/libSceNgs2.native/src/Pan.cpp index af88b23204..f59c436bc9 100644 --- a/core/libs/prx/libSceNgs2.native/src/Pan.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Pan.cpp @@ -1,11 +1,9 @@ -#include #include #include #include #include #include #include -#include #include "prx/libc/include/General.hpp" #include "Ngs2Internal.hpp" @@ -55,13 +53,18 @@ static void AddBetweenSpeakers(const Ngs2PanWork& work, std::uint32_t speaker, f } int Ngs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle, uint32_t num_speakers) { - if (work == nullptr) APS5_INVALID_ARG_EX; - if (speaker_angles != nullptr && !IsUnitAngle(unit_angle)) APS5_INVALID_ARG_EX; + if (work == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + if (speaker_angles != nullptr && !IsUnitAngle(unit_angle)) return SCE_NGS2_ERROR_INVALID_PAN_UNIT_ANGLE; + if (speaker_angles == nullptr && num_speakers == 0) { + Ngs2PanInit(work, nullptr, DEGREES, 7); + work->unit_angle = unit_angle == RADIANS ? RADIANS : DEGREES; + return SCE_NGS2_OK; + } const auto* layout = LayoutFor(num_speakers); - if (layout == nullptr) APS5_INVALID_ARG_EX; - *work = {}; - work->num_speakers = num_speakers; + if (layout == nullptr) return SCE_NGS2_ERROR_INVALID_PAN_SPEAKER; if (speaker_angles == nullptr) { + *work = {}; + work->num_speakers = num_speakers; for (std::uint32_t i = 0; i < num_speakers; i++) work->speaker_angles[i] = layout->defaultDegrees[i]; work->speaker_angles[num_speakers] = layout->defaultDegrees[0] + DEGREES; work->unit_angle = DEGREES; @@ -73,39 +76,39 @@ int Ngs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle } for (std::uint32_t i = 0; i < num_speakers; i++) { float angle = speaker_angles[layout->inputOrder[i]]; - if (!std::isfinite(angle)) APS5_INVALID_ARG_EX; while (angle < 0.0f) { - if (angle + unit_angle == angle) throw std::runtime_error("NGS2: pan angles too large to wrap are not implemented"); + if (angle + unit_angle == angle) throw std::runtime_error("NGS2: pan speaker angles too large to wrap are not implemented (the PS5 library did not return for them within 60 s)"); angle += unit_angle; } while (angle >= unit_angle) { - if (!(unit_angle < angle)) APS5_INVALID_ARG_EX; - if (angle - unit_angle == angle) throw std::runtime_error("NGS2: pan angles too large to wrap are not implemented"); + if (!(unit_angle < angle)) throw std::runtime_error("NGS2: a pan speaker angle that wraps to the unit angle is not implemented (the PS5 library did not return for it within 60 s)"); + if (angle - unit_angle == angle) throw std::runtime_error("NGS2: pan speaker angles too large to wrap are not implemented (the PS5 library did not return for them within 60 s)"); angle -= unit_angle; } work->speaker_angles[i] = angle; } work->speaker_angles[num_speakers] = work->speaker_angles[0] + unit_angle; work->unit_angle = unit_angle; + work->num_speakers = num_speakers; return SCE_NGS2_OK; } int Ngs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params, uint32_t num_params, uint32_t matrix_format, float* out_volume_matrix) { - if (work == nullptr || params == nullptr) APS5_INVALID_ARG_EX; + if (work == nullptr) return SCE_NGS2_ERROR_INVALID_PAN_WORK; + if (params == nullptr) return SCE_NGS2_ERROR_INVALID_PAN_PARAM; if (out_volume_matrix == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - if (matrix_format != 1 && matrix_format != 2 && matrix_format != 6 && matrix_format != 8) APS5_INVALID_ARG_EX; + if (matrix_format != 1 && matrix_format != 2 && matrix_format != 6 && matrix_format != 8) return SCE_NGS2_ERROR_INVALID_PAN_MATRIX_FORMAT; const auto* layout = LayoutFor(work->num_speakers); - if (layout == nullptr || !IsUnitAngle(work->unit_angle) || !std::isfinite(work->speaker_angles[0]) || num_params > MAX_PAN_PARAMS) APS5_INVALID_ARG_EX; - for (std::uint32_t p = 0; p < num_params; p++) { - if (!std::isfinite(params[p].angle) || std::isnan(params[p].distance)) APS5_INVALID_ARG_EX; - } + if (layout == nullptr) return SCE_NGS2_ERROR_INVALID_PAN_SPEAKER; + if (!IsUnitAngle(work->unit_angle)) return SCE_NGS2_ERROR_INVALID_PAN_UNIT_ANGLE; + if (num_params > MAX_PAN_PARAMS) return SCE_NGS2_ERROR_INVALID_NUM_CHANNELS; std::memset(out_volume_matrix, 0, static_cast(num_params) * matrix_format * sizeof(float)); const std::uint32_t numSpeakers = work->num_speakers; const float unit = work->unit_angle; const float first = work->speaker_angles[0]; for (std::uint32_t p = 0; p < num_params; p++) { const auto& param = params[p]; - const float distance = std::max(-1.0f, std::min(1.0f, param.distance)); + const float distance = param.distance > 1.0f ? 1.0f : param.distance < -1.0f ? -1.0f : param.distance; const float towardWeight = std::sin((distance + 1.0f) * 0.5f * std::numbers::pi_v * 0.5f); const float oppositeWeight = std::sin((1.0f - distance) * 0.5f * std::numbers::pi_v * 0.5f); float angle = param.angle; @@ -113,7 +116,7 @@ int Ngs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params, uint32 const float before = angle; if (angle >= first + unit) angle -= unit; if (angle < first) angle += unit; - if (angle == before) throw std::runtime_error("NGS2: pan angles too large to wrap are not implemented"); + if (angle == before) throw std::runtime_error("NGS2: pan angles that do not wrap into one turn are not implemented (the PS5 library did not return for them within 60 s)"); } float opposite = angle + unit * 0.5f; if (!(opposite < first + unit)) opposite -= unit; diff --git a/core/libs/prx/libSceNgs2.native/src/Pcm.cpp b/core/libs/prx/libSceNgs2.native/src/Pcm.cpp new file mode 100644 index 0000000000..49b8611046 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/src/Pcm.cpp @@ -0,0 +1,57 @@ +#include +#include +#include + +#include "Ngs2Internal.hpp" + +std::size_t Ngs2PcmSampleBytes(std::uint32_t waveformType) { + switch (waveformType) { + case SCE_NGS2_WAVEFORM_TYPE_PCM_U8: return 1; + case SCE_NGS2_WAVEFORM_TYPE_PCM_I16L: return 2; + case SCE_NGS2_WAVEFORM_TYPE_PCM_I24L: return 3; + case SCE_NGS2_WAVEFORM_TYPE_PCM_I32L: + case SCE_NGS2_WAVEFORM_TYPE_PCM_F32L: return 4; + case SCE_NGS2_WAVEFORM_TYPE_PCM_F64L: return 8; + default: throw std::runtime_error("NGS2: PCM waveform type " + Ngs2Hex(waveformType) + " is not implemented"); + } +} + +static std::uint64_t ReadLittleEndian(const std::uint8_t* data, std::size_t bytes) { + std::uint64_t value = 0; + for (std::size_t i = 0; i < bytes; i++) value |= static_cast(data[i]) << (i * 8); + return value; +} + +static void CheckFloatRange(std::uint64_t magnitude, std::uint64_t minimum, std::uint64_t maximum) { + if (magnitude > maximum || (magnitude != 0 && magnitude < minimum)) { + throw std::runtime_error("NGS2: nonfinite, subnormal or out-of-range floating-point PCM is not implemented"); + } +} + +static float ReadSample(std::uint32_t waveformType, const std::uint8_t* sample, std::size_t bytes) { + const auto value = ReadLittleEndian(sample, bytes); + switch (waveformType) { + case SCE_NGS2_WAVEFORM_TYPE_PCM_U8: + return (static_cast(value) - 128) / 128.0f; + case SCE_NGS2_WAVEFORM_TYPE_PCM_I16L: + return std::bit_cast(static_cast(value)) / 32768.0f; + case SCE_NGS2_WAVEFORM_TYPE_PCM_I24L: + return (static_cast(value ^ 0x800000) - 0x800000) / 8388608.0f; + case SCE_NGS2_WAVEFORM_TYPE_PCM_I32L: + return static_cast(std::bit_cast(static_cast(value)) / 2147483648.0); + case SCE_NGS2_WAVEFORM_TYPE_PCM_F32L: + CheckFloatRange(value & 0x7fffffff, 0x00800000, 0x3f800000); + return std::bit_cast(static_cast(value)); + case SCE_NGS2_WAVEFORM_TYPE_PCM_F64L: + CheckFloatRange(value & 0x7fffffffffffffff, 0x3810000000000000, 0x3ff0000000000000); + return static_cast(std::bit_cast(value)); + default: throw std::runtime_error("NGS2: PCM sample conversion is not implemented"); + } +} + +void Ngs2ReadPcmFrame(std::uint32_t waveformType, std::uint32_t channels, const std::uint8_t* frame, float* output) { + const auto bytes = Ngs2PcmSampleBytes(waveformType); + for (std::uint32_t channel = 0; channel < channels; channel++) { + output[channel] = frame == nullptr ? 0.0f : ReadSample(waveformType, frame + channel * bytes, bytes); + } +} diff --git a/core/libs/prx/libSceNgs2.native/src/Rack.cpp b/core/libs/prx/libSceNgs2.native/src/Rack.cpp index 47897d3215..13277128e3 100644 --- a/core/libs/prx/libSceNgs2.native/src/Rack.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Rack.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include #include #include @@ -19,6 +20,7 @@ union RackOptions { Ngs2ReverbRackOption reverb; Ngs2CustomSamplerRackOption customSampler; Ngs2CustomSubmixerRackOption customSubmixer; + Ngs2CustomMasteringRackOption customMastering; }; static std::size_t RackOptionSize(std::uint32_t rackId) { @@ -29,12 +31,13 @@ static std::size_t RackOptionSize(std::uint32_t rackId) { case SCE_NGS2_RACK_ID_REVERB: return sizeof(Ngs2ReverbRackOption); case SCE_NGS2_RACK_ID_CUSTOM_SAMPLER: return sizeof(Ngs2CustomSamplerRackOption); case SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER: return sizeof(Ngs2CustomSubmixerRackOption); + case SCE_NGS2_RACK_ID_CUSTOM_MASTERING: return sizeof(Ngs2CustomMasteringRackOption); default: throw std::runtime_error("NGS2: rack id " + Ngs2Hex(rackId) + " is not implemented"); } } static RackOptions DefaultRackOption(std::uint32_t rackId) { - if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER || rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) { + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER || rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) { throw std::runtime_error("NGS2: default custom rack options are not implemented"); } RackOptions options{}; @@ -83,26 +86,39 @@ static std::uint32_t RackMaxChannels(std::uint32_t rackId, const RackOptions& op if (rackId == SCE_NGS2_RACK_ID_SUBMIXER) return options.submixer.max_channels; if (rackId == SCE_NGS2_RACK_ID_MASTERING) return options.mastering.max_channels; if (rackId == SCE_NGS2_RACK_ID_REVERB) return options.reverb.max_channels; - if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) return options.customSubmixer.max_channels; + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) return options.customSubmixer.max_channels; return NGS2_MAX_CHANNELS; } -static RackOptions CheckedRackOption(std::uint32_t rackId, const Ngs2RackOption* option) { +static int CheckedRackOption(std::uint32_t rackId, const Ngs2RackOption* option, RackOptions& options) { const auto size = RackOptionSize(rackId); - if (option == nullptr) return DefaultRackOption(rackId); + if (option == nullptr) { + options = DefaultRackOption(rackId); + return SCE_NGS2_OK; + } if (option->size != size) throw std::invalid_argument("NGS2: unexpected rack option size " + std::to_string(option->size)); - RackOptions options{}; + options = {}; std::memcpy(&options, option, size); const auto maxChannels = RackMaxChannels(rackId, options); - if (options.common.max_voices == 0 || maxChannels == 0 || maxChannels > NGS2_MAX_CHANNELS) { + if ((rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) && maxChannels > SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS) return SCE_NGS2_ERROR_INVALID_NUM_CHANNELS; + if ((rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) && (options.customSubmixer.max_inputs == 0 || options.customSubmixer.max_inputs > SCE_NGS2_CUSTOM_SUBMIXER_MAX_INPUTS)) { + throw std::runtime_error("NGS2: custom submixer racks with " + std::to_string(options.customSubmixer.max_inputs) + " inputs are not implemented"); + } + if (options.common.max_voices == 0 || maxChannels == 0) throw std::invalid_argument("NGS2: invalid rack voice or channel count"); + int result = SCE_NGS2_OK; + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) result = Ngs2CheckCustomRack(rackId, options.customSampler.custom_rack_option, 0); + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) result = Ngs2CheckCustomRack(rackId, options.customSubmixer.custom_rack_option, options.customSubmixer.max_inputs); + if (result != SCE_NGS2_OK) return result; + if (maxChannels > NGS2_MAX_CHANNELS) { + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) { + throw std::runtime_error("NGS2: custom submixer racks with " + std::to_string(maxChannels) + " channels are not implemented"); + } throw std::invalid_argument("NGS2: invalid rack voice or channel count"); } - if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) Ngs2CheckCustomRack(options.customSampler.custom_rack_option); - if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) Ngs2CheckCustomRack(options.customSubmixer.custom_rack_option); if (rackId == SCE_NGS2_RACK_ID_REVERB && options.reverb.reverb_size != 1) { throw std::runtime_error("NGS2: reverb rack size " + std::to_string(options.reverb.reverb_size) + " is not implemented"); } - return options; + return SCE_NGS2_OK; } static int CreateRack(Ngs2Handle systemHandle, std::uint32_t rackId, const RackOptions& options, const Ngs2ContextBufferInfo& bufferInfo, @@ -116,7 +132,7 @@ static int CreateRack(Ngs2Handle systemHandle, std::uint32_t rackId, const RackO rack->maxChannels = RackMaxChannels(rackId, options); rack->maxFilters = rackId == SCE_NGS2_RACK_ID_SAMPLER ? options.sampler.max_filters : rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER ? options.customSampler.custom_rack_option.num_modules - : rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER ? options.customSubmixer.custom_rack_option.num_modules : 0; + : (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) ? options.customSubmixer.custom_rack_option.num_modules : 0; rack->uid = nextUid++; std::memcpy(rack->name, options.common.name, sizeof(rack->name)); rack->name[sizeof(rack->name) - 1] = '\0'; @@ -124,7 +140,7 @@ static int CreateRack(Ngs2Handle systemHandle, std::uint32_t rackId, const RackO if (rackId == SCE_NGS2_RACK_ID_SAMPLER) rack->maxChannelWorks = options.sampler.max_channel_works; if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) rack->maxChannelWorks = options.customSampler.max_channel_works; if (rackId == SCE_NGS2_RACK_ID_SUBMIXER) rack->maxInputs = options.submixer.max_inputs; - if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) rack->maxInputs = options.customSubmixer.max_inputs; + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) rack->maxInputs = options.customSubmixer.max_inputs; rack->bufferInfo = bufferInfo; rack->allocator = allocator; rack->voices.resize(options.common.max_voices); @@ -134,7 +150,7 @@ static int CreateRack(Ngs2Handle systemHandle, std::uint32_t rackId, const RackO voice.matrices.resize(options.common.max_matrices); } if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) Ngs2SetupUserFx(*rack, options.customSampler.custom_rack_option); - if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) Ngs2SetupUserFx(*rack, options.customSubmixer.custom_rack_option); + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) Ngs2SetupUserFx(*rack, options.customSubmixer.custom_rack_option); system->racks.push_back(rack); *handle = reinterpret_cast(rack); return SCE_NGS2_OK; @@ -165,7 +181,9 @@ extern "C" { int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* option, Ngs2ContextBufferInfo* buffer_info) { if (buffer_info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - CheckedRackOption(rack_id, option); + RackOptions options{}; + const int result = CheckedRackOption(rack_id, option, options); + if (result != SCE_NGS2_OK) throw std::invalid_argument("NGS2: invalid rack option, rejected at creation with " + Ngs2Hex(static_cast(result))); *buffer_info = {}; buffer_info->host_buffer_size = sizeof(Ngs2Rack); return SCE_NGS2_OK; @@ -173,16 +191,20 @@ int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* int APS5_VABI sceNgs2RackCreate(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - const auto options = CheckedRackOption(rack_id, option); if (buffer_info == nullptr) APS5_INVALID_ARG_EX; + RackOptions options{}; + const int result = CheckedRackOption(rack_id, option, options); + if (result != SCE_NGS2_OK) return result; const Ngs2Lock lock; return CreateRack(system_handle, rack_id, options, *buffer_info, {}, handle); } int APS5_VABI sceNgs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - const auto options = CheckedRackOption(rack_id, option); if (allocator == nullptr || allocator->alloc_handler == nullptr || allocator->free_handler == nullptr) APS5_INVALID_ARG_EX; + RackOptions options{}; + const int checked = CheckedRackOption(rack_id, option, options); + if (checked != SCE_NGS2_OK) return checked; const Ngs2Lock lock; if (Ngs2FindSystem(system_handle) == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; Ngs2ContextBufferInfo bufferInfo{}; @@ -245,6 +267,17 @@ int APS5_VABI sceNgs2RackGetInfo(uintptr_t rack_handle, Ngs2RackInfo* info, size return SCE_NGS2_OK; } +int APS5_VABI sceNgs2CustomRackGetModuleInfo(uintptr_t rack_handle, uint32_t module_index, Ngs2CustomModuleInfo* info, size_t info_size) { + if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + const Ngs2Lock lock; + const auto* rack = Ngs2FindRack(rack_handle); + if (rack == nullptr) return SCE_NGS2_ERROR_INVALID_RACK_HANDLE; + if (module_index >= rack->customModules.size() || info_size < sizeof(Ngs2CustomModuleInfo)) APS5_INVALID_ARG_EX; + if (info_size != sizeof(Ngs2CustomModuleInfo)) throw std::runtime_error("NGS2: custom module info of " + std::to_string(info_size) + " bytes is not implemented"); + *info = rack->customModules[module_index]; + return SCE_NGS2_OK; +} + int APS5_VABI sceNgs2RackSetUserData(uintptr_t rack_handle, uintptr_t user_data) { std::lock_guard lock(Ngs2Mutex()); auto* rack = Ngs2FindRack(rack_handle); diff --git a/core/libs/prx/libSceNgs2.native/src/Render.cpp b/core/libs/prx/libSceNgs2.native/src/Render.cpp index fa764d375a..a56dc7072b 100644 --- a/core/libs/prx/libSceNgs2.native/src/Render.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Render.cpp @@ -15,7 +15,7 @@ static constexpr double PHASE_ONE = 4294967296.0; static constexpr std::uint64_t PHASE_FRACTION = 0xffffffff; static const std::uint8_t* FrameAt(const Ngs2Voice& voice, const Ngs2Block& block, std::uint32_t frame) { - const std::size_t frameBytes = voice.channels * sizeof(std::int16_t); + const std::size_t frameBytes = voice.channels * Ngs2PcmSampleBytes(voice.waveformType); if (block.data == nullptr) return nullptr; if (block.streaming) { for (const auto& piece : block.pieces) { @@ -27,15 +27,7 @@ static const std::uint8_t* FrameAt(const Ngs2Voice& voice, const Ngs2Block& bloc } static void ReadPcm(const Ngs2Voice& voice, const std::uint8_t* frame, float* out) { - if (frame == nullptr) { - for (std::uint32_t channel = 0; channel < voice.channels; channel++) out[channel] = 0.0f; - return; - } - for (std::uint32_t channel = 0; channel < voice.channels; channel++) { - std::int16_t sample; - std::memcpy(&sample, frame + channel * sizeof(sample), sizeof(sample)); - out[channel] = sample / 32768.0f; - } + Ngs2ReadPcmFrame(voice.waveformType, voice.channels, frame, out); } static void ReadFrames(Ngs2Voice& voice, float* current, float* next) { @@ -93,7 +85,7 @@ static void Advance(Ngs2Voice& voice, std::uint64_t frames) { const auto step = std::min(frames, limit - block.cursor); block.cursor += static_cast(step); voice.decodedSamples += step; - if (voice.waveformType != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) voice.decodedBytes += step * voice.channels * sizeof(std::int16_t); + if (voice.waveformType != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) voice.decodedBytes += step * voice.channels * Ngs2PcmSampleBytes(voice.waveformType); frames -= step; if (block.streaming) { while (block.pieces.size() > 1 && block.cursor >= block.pieces.front().firstFrame + block.pieces.front().frames) block.pieces.erase(block.pieces.begin()); @@ -221,10 +213,11 @@ float Ngs2DefaultLevel(std::uint32_t sourceChannels, std::uint32_t source, std:: static void MixPort(Ngs2Voice& voice, const Ngs2Voice& source, const Ngs2Port& port, std::uint32_t grain) { const auto* matrix = port.matrix < 0 || source.matrices[port.matrix].empty() ? nullptr : &source.matrices[port.matrix]; - const std::size_t outputs = matrix == nullptr ? voice.channels : std::min(voice.channels, matrix->size() / source.channels); + const auto channels = voice.InputChannels(); + const std::size_t outputs = matrix == nullptr ? channels : std::min(channels, matrix->size() / source.channels); for (std::size_t dst = 0; dst < outputs; dst++) { for (std::uint32_t src = 0; src < source.channels; src++) { - const float level = port.volume * (matrix == nullptr ? Ngs2DefaultLevel(source.channels, src, voice.channels, static_cast(dst)) : (*matrix)[dst * source.channels + src]); + const float level = port.volume * (matrix == nullptr ? Ngs2DefaultLevel(source.channels, src, channels, static_cast(dst)) : (*matrix)[dst * source.channels + src]); if (level == 0.0f) continue; for (std::uint32_t i = 0; i < grain; i++) voice.samples[dst * grain + i] += source.samples[src * grain + i] * level; } @@ -251,7 +244,7 @@ static void RenderVoice(Ngs2Voice& voice, const std::vector& voices, bool& active; ~RenderingScope() { active = false; } } renderingScope{voice.rendering}; - voice.samples.assign(static_cast(grain) * voice.channels, 0.0f); + voice.samples.assign(static_cast(grain) * voice.InputChannels(), 0.0f); voice.hasSamples = false; if (voice.state == Ngs2PlayState::Playing && voice.channels != 0) { if (voice.rack->rackId == SCE_NGS2_RACK_ID_SAMPLER || voice.rack->rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) { @@ -263,6 +256,7 @@ static void RenderVoice(Ngs2Voice& voice, const std::vector& voices, Ngs2ProcessLegacyUserFx(voice, grain, systemRate); Ngs2ProcessUserFx(voice, grain, systemRate); } + if (voice.samples.size() != static_cast(grain) * voice.channels) voice.samples.assign(static_cast(grain) * voice.channels, 0.0f); voice.rendered = true; } @@ -297,6 +291,7 @@ static void WriteOutput(const Ngs2RenderBufferInfo& output, const std::vector(output.buffer); for (std::size_t i = 0; i < mix.size(); i++) { + if (std::isnan(mix[i])) throw std::runtime_error("NGS2: a NaN sample in a PCM_I16L render buffer is not implemented"); pcm[i] = static_cast(std::min(std::lrint(std::clamp(mix[i], -1.0f, 1.0f) * 32768.0f), 32767L)); } } @@ -336,7 +331,7 @@ void Ngs2RenderSystem(Ngs2System& system, const Ngs2RenderBufferInfo* bufferInfo std::vector> mixes(numBufferInfo); for (auto* voice : voices) { RenderVoice(*voice, voices, grain, system.option.sample_rate); - if (voice->rack->rackId != SCE_NGS2_RACK_ID_MASTERING || !voice->hasSamples) continue; + if ((voice->rack->rackId != SCE_NGS2_RACK_ID_MASTERING && voice->rack->rackId != SCE_NGS2_RACK_ID_CUSTOM_MASTERING) || !voice->hasSamples) continue; auto& mix = OutputMix(mixes, bufferInfo, numBufferInfo, *voice, grain); const std::uint32_t outputChannels = bufferInfo[voice->outputId].num_channels; for (std::uint32_t channel = 0; channel < voice->channels; channel++) { diff --git a/core/libs/prx/libSceNgs2.native/src/Reverb.cpp b/core/libs/prx/libSceNgs2.native/src/Reverb.cpp index 131fb321f7..ab3849fbdd 100644 --- a/core/libs/prx/libSceNgs2.native/src/Reverb.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Reverb.cpp @@ -292,9 +292,23 @@ bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp Reset(reverb); reverb.changed = true; } + const std::uint32_t inputChannels = voice.InputChannels(); const std::vector input = voice.samples; std::vector mono(grain), early(grain), late(grain); - MixDown(input.data(), channels, grain, mono.data()); + MixDown(input.data(), inputChannels, grain, mono.data()); + std::vector mapped; + if (inputChannels != channels) { + mapped.assign(static_cast(grain) * channels, 0.0f); + for (std::uint32_t dst = 0; dst < channels; dst++) { + for (std::uint32_t src = 0; src < inputChannels; src++) { + const float level = Ngs2DefaultLevel(inputChannels, src, channels, dst); + if (level == 0.0f) continue; + for (std::uint32_t i = 0; i < grain; i++) mapped[static_cast(dst) * grain + i] += input[static_cast(src) * grain + i] * level; + } + } + voice.samples.assign(static_cast(grain) * channels, 0.0f); + } + const std::vector& direct = inputChannels != channels ? mapped : input; if (reverb.changed) { std::vector oldEarly(grain), oldLate(grain); RunTaps(reverb, reverb.taps, mono.data(), grain, oldEarly.data(), oldLate.data()); @@ -319,7 +333,7 @@ bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp for (std::uint32_t channel = 0; channel < channels; channel++) { auto& state = reverb.channels[channel]; float* out = voice.samples.data() + static_cast(channel) * grain; - const float* in = input.data() + static_cast(channel) * grain; + const float* in = direct.data() + static_cast(channel) * grain; if (channel == LFE_CHANNEL) { std::copy(in, in + grain, out); } else { diff --git a/core/libs/prx/libSceNgs2.native/src/System.cpp b/core/libs/prx/libSceNgs2.native/src/System.cpp index 99eede7a7c..28a7828f36 100644 --- a/core/libs/prx/libSceNgs2.native/src/System.cpp +++ b/core/libs/prx/libSceNgs2.native/src/System.cpp @@ -1,9 +1,11 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include #include #include #include #include #include +#include #include #include #include @@ -17,7 +19,6 @@ #include "prx/libc/include/General.hpp" #include "Ngs2Internal.hpp" - static std::vector& Systems() { static auto* const systems = new std::vector(); return *systems; @@ -197,7 +198,7 @@ int Ngs2ReleaseBuffer(const Ngs2BufferAllocator& allocator, Ngs2ContextBufferInf } static Ngs2ContextBufferInfo Allocate(const Ngs2BufferAllocator* allocator, std::size_t size) { - if (allocator == nullptr || allocator->alloc_handler == nullptr || allocator->free_handler == nullptr) APS5_INVALID_ARG_EX; + if (allocator->free_handler == nullptr) APS5_INVALID_ARG_EX; Ngs2ContextBufferInfo bufferInfo{}; bufferInfo.host_buffer_size = size; bufferInfo.user_data = allocator->user_data; @@ -212,18 +213,36 @@ static Ngs2SystemOption DefaultSystemOption() { option.max_grain_samples = 512; option.num_grain_samples = 256; option.sample_rate = 48000; + option.max_voice_channels = 8; return option; } -static Ngs2SystemOption CheckedSystemOption(const Ngs2SystemOption* option) { - if (option == nullptr) return DefaultSystemOption(); - if (option->size != sizeof(Ngs2SystemOption)) throw std::invalid_argument("NGS2: unexpected system option size " + std::to_string(option->size)); - if (option->flags != 0) throw std::runtime_error("NGS2: system option flags are not implemented"); - if (option->sample_rate == 0 || option->max_grain_samples < MIN_GRAIN_SAMPLES || option->num_grain_samples == 0 || - option->num_grain_samples > option->max_grain_samples) { - throw std::invalid_argument("NGS2: invalid system grain or sample rate"); +static constexpr std::uint32_t MAX_GRAIN_SAMPLES = 2048; +static constexpr std::uint32_t MAX_VOICE_CHANNELS = 37; + +static bool ValidGrainSamples(std::uint32_t grainSamples, std::uint32_t maxSamples) { + return grainSamples >= MIN_GRAIN_SAMPLES && grainSamples <= maxSamples && grainSamples % MIN_GRAIN_SAMPLES == 0; +} + +static bool ValidSampleRate(std::uint32_t sampleRate) { + static constexpr std::uint32_t rates[] = {11025, 12000, 22050, 24000, 44100, 48000, 88200, 96000, 176400, 192000}; + return std::find(std::begin(rates), std::end(rates), sampleRate) != std::end(rates); +} + +static int CheckSystemOption(const Ngs2SystemOption* option, Ngs2SystemOption& checked) { + if (option == nullptr) { + checked = DefaultSystemOption(); + return SCE_NGS2_OK; } - return *option; + if (option->size != sizeof(Ngs2SystemOption)) return SCE_NGS2_ERROR_INVALID_OPTION_SIZE; + constexpr std::uint32_t knownFlags = 0x2; + if ((option->flags & ~knownFlags) != 0) throw std::runtime_error("NGS2: system option flags " + Ngs2Hex(option->flags) + " are not implemented"); + if (!ValidGrainSamples(option->max_grain_samples, MAX_GRAIN_SAMPLES)) return SCE_NGS2_ERROR_INVALID_MAX_GRAIN_SAMPLES; + if (!ValidGrainSamples(option->num_grain_samples, option->max_grain_samples)) return SCE_NGS2_ERROR_INVALID_NUM_GRAIN_SAMPLES; + if (!ValidSampleRate(option->sample_rate)) return SCE_NGS2_ERROR_INVALID_SAMPLE_RATE; + if (option->max_voice_channels < 1 || option->max_voice_channels > MAX_VOICE_CHANNELS) return SCE_NGS2_ERROR_INVALID_NUM_CHANNELS; + checked = *option; + return SCE_NGS2_OK; } static int CreateSystem(const Ngs2SystemOption& option, const Ngs2ContextBufferInfo& bufferInfo, const Ngs2BufferAllocator& allocator, Ngs2Handle* handle) { @@ -246,23 +265,30 @@ extern "C" { int APS5_VABI sceNgs2SystemQueryBufferSize(const Ngs2SystemOption* option, Ngs2ContextBufferInfo* buffer_info) { if (buffer_info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - CheckedSystemOption(option); - *buffer_info = {}; + std::memset(buffer_info, 0, offsetof(Ngs2ContextBufferInfo, user_data)); + Ngs2SystemOption checked{}; + const int result = CheckSystemOption(option, checked); + if (result != SCE_NGS2_OK) return result; buffer_info->host_buffer_size = sizeof(Ngs2System); return SCE_NGS2_OK; } int APS5_VABI sceNgs2SystemCreate(const Ngs2SystemOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { + if (buffer_info == nullptr || buffer_info->host_buffer == nullptr) return SCE_NGS2_ERROR_INVALID_BUFFER_INFO; if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - const auto checked = CheckedSystemOption(option); - if (buffer_info == nullptr) APS5_INVALID_ARG_EX; + Ngs2SystemOption checked{}; + const int result = CheckSystemOption(option, checked); + if (result != SCE_NGS2_OK) return result; const Ngs2Lock lock; return CreateSystem(checked, *buffer_info, {}, handle); } int APS5_VABI sceNgs2SystemCreateWithAllocator(const Ngs2SystemOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { + if (allocator == nullptr || allocator->alloc_handler == nullptr) return SCE_NGS2_ERROR_INVALID_BUFFER_ALLOCATOR; if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - const auto checked = CheckedSystemOption(option); + Ngs2SystemOption checked{}; + const int result = CheckSystemOption(option, checked); + if (result != SCE_NGS2_OK) return result; const auto bufferInfo = Allocate(allocator, sizeof(Ngs2System)); const Ngs2Lock lock; return CreateSystem(checked, bufferInfo, *allocator, handle); @@ -319,7 +345,7 @@ int APS5_VABI sceNgs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num const Ngs2Lock lock; auto* system = Ngs2FindSystem(system_handle); if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; - if (num_samples == 0 || num_samples > system->option.max_grain_samples) APS5_INVALID_ARG_EX; + if (num_samples == 0 || num_samples > system->option.max_grain_samples) return SCE_NGS2_ERROR_INVALID_NUM_GRAIN_SAMPLES; system->option.num_grain_samples = num_samples; return SCE_NGS2_OK; } @@ -328,7 +354,7 @@ int APS5_VABI sceNgs2SystemSetSampleRate(uintptr_t system_handle, uint32_t sampl const Ngs2Lock lock; auto* system = Ngs2FindSystem(system_handle); if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; - if (sample_rate == 0) APS5_INVALID_ARG_EX; + if (!ValidSampleRate(sample_rate)) return SCE_NGS2_ERROR_INVALID_SAMPLE_RATE; for (const auto* rack : system->racks) { for (const auto& voice : rack->voices) { for (const auto& filter : voice.filters) { @@ -390,7 +416,16 @@ int APS5_VABI sceNgs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuffe system = Ngs2FindSystem(system_handle); } if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + if (num_buffer_info > 31) return SCE_NGS2_ERROR_INVALID_NUM_RENDER_BUFFERS; if (buffer_info == nullptr || num_buffer_info == 0) APS5_INVALID_ARG_EX; + for (std::uint32_t i = 0; i < num_buffer_info; i++) { + const auto waveform_type = buffer_info[i].waveform_type; + if (waveform_type != SCE_NGS2_WAVEFORM_TYPE_PCM_I16L && waveform_type != SCE_NGS2_WAVEFORM_TYPE_PCM_I16B && + waveform_type != SCE_NGS2_WAVEFORM_TYPE_PCM_F32L && waveform_type != SCE_NGS2_WAVEFORM_TYPE_PCM_F32B) { + return SCE_NGS2_ERROR_INVALID_WAVEFORM_TYPE; + } + if (buffer_info[i].buffer == nullptr) return SCE_NGS2_ERROR_INVALID_RENDER_BUFFER; + } SystemLockState* state = nullptr; { const auto self = std::this_thread::get_id(); @@ -422,7 +457,7 @@ int APS5_VABI sceNgs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuffe } int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption* option) { - if (option == nullptr) APS5_INVALID_ARG_EX; + if (option == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; *option = DefaultSystemOption(); return SCE_NGS2_OK; } diff --git a/core/libs/prx/libSceNgs2.native/src/Voice.cpp b/core/libs/prx/libSceNgs2.native/src/Voice.cpp index bc19ab0a0a..0efa7461ca 100644 --- a/core/libs/prx/libSceNgs2.native/src/Voice.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Voice.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include #include #include @@ -19,6 +20,16 @@ static constexpr std::uint8_t COMMAND_TYPE_INT = 3; static constexpr std::uint8_t COMMAND_TYPE_UINT = 4; static constexpr std::uint8_t COMMAND_TYPE_FLOAT_ARRAY = 0x11; static constexpr float MAX_MATRIX_LEVEL = 4.0f; +static constexpr float MAX_MASTERING_GAIN = 32.0f; + +struct Ngs2ReturnCode { + int code; +}; + +static void SendEvent(Ngs2Voice& voice, std::uint32_t eventId) { + if (eventId == SCE_NGS2_VOICE_EVENT_PLAY && voice.channels == 0) throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_VOICE_ACCESS}; + voice.SetEvent(eventId); +} void Ngs2Voice::SetEvent(std::uint32_t eventId) { switch (eventId) { @@ -56,6 +67,7 @@ void Ngs2Voice::ResetSetup() { fbwLevel = 1.0f; lfeLevel = 1.0f; channels = 0; + inputChannels = 0; sampleRate = 0; waveformType = 0; atrac9 = {}; @@ -76,7 +88,7 @@ void Ngs2Voice::ResetSetup() { const std::uint8_t* Ngs2StreamEnd(const Ngs2Voice& voice, const Ngs2Block& block) { const auto& last = block.pieces.back(); - return last.data + last.frames * voice.channels * sizeof(std::int16_t); + return last.data + last.frames * voice.channels * Ngs2PcmSampleBytes(voice.waveformType); } const std::uint8_t* Ngs2Voice::WaveformData() const { @@ -85,11 +97,12 @@ const std::uint8_t* Ngs2Voice::WaveformData() const { if (waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) return block.data + block.dataCursor; if (block.streaming) { for (const auto& piece : block.pieces) { - if (block.cursor >= piece.firstFrame && block.cursor - piece.firstFrame < piece.frames) return piece.data + (block.cursor - piece.firstFrame) * channels * sizeof(std::int16_t); + if (block.cursor >= piece.firstFrame && block.cursor - piece.firstFrame < piece.frames) return piece.data + (block.cursor - piece.firstFrame) * channels * Ngs2PcmSampleBytes(waveformType); } return Ngs2StreamEnd(*this, block); } - return block.data + (static_cast(block.info.num_skip_samples) + block.cursor) * channels * sizeof(std::int16_t); + if (block.data == nullptr) return nullptr; + return block.data + (static_cast(block.info.num_skip_samples) + block.cursor) * channels * Ngs2PcmSampleBytes(waveformType); } static Ngs2Port& PortAt(Ngs2Voice& voice, std::uint32_t port) { @@ -158,7 +171,7 @@ static void ApplyCommonParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param break; } case SCE_NGS2_VOICE_PARAM_PATCH: Patch(voice, ParamAs(param)); break; - case SCE_NGS2_VOICE_PARAM_EVENT: voice.SetEvent(ParamAs(param).event_id); break; + case SCE_NGS2_VOICE_PARAM_EVENT: SendEvent(voice, ParamAs(param).event_id); break; case SCE_NGS2_VOICE_PARAM_CALLBACK: { const auto& callback = ParamAs(param); voice.callback = callback.callback; @@ -171,10 +184,14 @@ static void ApplyCommonParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param } static void SetupSampler(Ngs2Voice& voice, const Ngs2WaveformFormat& format) { + if (format.waveform_type == 0 && format.num_channels == 0 && format.sample_rate == 0) { + if (voice.channels == 0) voice.ResetSetup(); + return; + } voice.ResetSetup(); - if (format.waveform_type == 0 && format.num_channels == 0 && format.sample_rate == 0) return; - if (format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_PCM_I16L && format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) { - throw std::runtime_error("NGS2: waveform type " + Ngs2Hex(format.waveform_type) + " is not implemented"); + if (format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) Ngs2PcmSampleBytes(format.waveform_type); + if (format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_PCM_I16L && format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9 && format.config_data != 0) { + throw std::runtime_error("NGS2: configured PCM waveforms are not implemented"); } if (format.frame_offset != 0 || format.frame_margin != 0) throw std::runtime_error("NGS2: waveform frame offset and margin are not implemented"); if (format.num_channels == 0 || format.num_channels > NGS2_MAX_CHANNELS || format.sample_rate == 0) APS5_INVALID_ARG_EX; @@ -225,7 +242,7 @@ static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBl voice.waveformEnd = nullptr; if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) Ngs2RestartAtrac9(voice); } - const std::size_t frameBytes = voice.channels * sizeof(std::int16_t); + const std::size_t frameBytes = voice.channels == 0 || voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 ? 0 : voice.channels * Ngs2PcmSampleBytes(voice.waveformType); if ((param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND) != 0) { AppendStreamData(voice, param, frameBytes); voice.acceptsBlocks = (param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE) != 0; @@ -237,7 +254,7 @@ static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBl const auto& block = param.blocks[i]; if (block.num_samples == 0 && block.data_size == 0) continue; if (allowsSilence && block.data_size == 0) { - if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) throw std::runtime_error("NGS2: a dataless waveform block under the silence flag is not implemented for ATRAC9 voices"); + if (voice.waveformType != SCE_NGS2_WAVEFORM_TYPE_PCM_I16L) throw std::runtime_error("NGS2: a dataless waveform block under the silence flag is not implemented for this format"); voice.blocks.push_back({nullptr, block}); continue; } @@ -357,9 +374,11 @@ static void ApplyParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { if (param.id == SCE_NGS2_REVERB_VOICE_PARAM_SETUP) { const auto& setup = ParamAs(param); if (setup.flags != 0) throw std::runtime_error("NGS2: reverb setup flags are not implemented"); - if (setup.num_input_channels != setup.num_output_channels) throw std::runtime_error("NGS2: reverb channel conversion is not implemented"); - if (setup.num_output_channels != 1 && setup.num_output_channels != 2 && setup.num_output_channels != 6 && setup.num_output_channels != 8) APS5_INVALID_ARG_EX; + for (const auto count : {setup.num_input_channels, setup.num_output_channels}) { + if ((count != 1 && count != 2 && count != 6 && count != 8) || count > voice.rack->maxChannels) APS5_INVALID_ARG_EX; + } SetupMixer(voice, setup.num_output_channels); + voice.inputChannels = setup.num_input_channels; Ngs2SetupReverb(voice); return; } @@ -369,18 +388,18 @@ static void ApplyParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { } break; case SCE_NGS2_RACK_ID_MASTERING: - if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_SETUP) { + case SCE_NGS2_RACK_ID_CUSTOM_MASTERING: + if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_SETUP || param.id == SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_SETUP) { SetupMixer(voice, ParamAs(param).num_io_channels); return; } - if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_GAIN) { + if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_GAIN || param.id == SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_GAIN) { const auto& gain = ParamAs(param); - if (!std::isfinite(gain.fbw_level) || !std::isfinite(gain.lfe_level)) APS5_INVALID_ARG_EX; - voice.fbwLevel = gain.fbw_level; - voice.lfeLevel = gain.lfe_level; + voice.fbwLevel = std::clamp(gain.fbw_level, -MAX_MASTERING_GAIN, MAX_MASTERING_GAIN); + voice.lfeLevel = std::clamp(gain.lfe_level, -MAX_MASTERING_GAIN, MAX_MASTERING_GAIN); return; } - if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT) { + if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT || param.id == SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_OUTPUT) { voice.outputId = ParamAs(param).output_id; return; } @@ -400,9 +419,13 @@ int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHe auto* found = Ngs2FindVoice(voice_handle); if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; auto& voice = *found; - for (const auto* param = param_list;; param = reinterpret_cast(reinterpret_cast(param) + param->next)) { - ApplyParam(voice, *param); - if (param->next == 0) break; + try { + for (const auto* param = param_list;; param = reinterpret_cast(reinterpret_cast(param) + param->next)) { + ApplyParam(voice, *param); + if (param->next == 0) break; + } + } catch (const Ngs2ReturnCode& result) { + return result.code; } return SCE_NGS2_OK; } @@ -413,20 +436,24 @@ int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t voice_handle, const Ngs2VoiceCom if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; auto& voice = *found; if (commands == nullptr && num_commands != 0) APS5_INVALID_ARG_EX; - for (std::size_t i = 0; i < num_commands; i++) { - const auto& command = commands[i]; - const std::uint32_t index = command.id >> 24; - const std::uint32_t id = command.id & 0xffffff; - const std::uint8_t expectedType = id == COMMAND_EVENT ? COMMAND_TYPE_UINT - : id == COMMAND_MATRIX_LEVELS ? COMMAND_TYPE_FLOAT_ARRAY - : id == COMMAND_PORT_VOLUME ? COMMAND_TYPE_FLOAT - : id == COMMAND_PORT_MATRIX ? COMMAND_TYPE_INT : 0; - if (expectedType == 0) throw std::runtime_error("NGS2: voice command " + Ngs2Hex(command.id) + " is not implemented"); - if (command.type != expectedType) throw std::invalid_argument("NGS2: voice command " + Ngs2Hex(command.id) + " has value type " + Ngs2Hex(command.type)); - if (id == COMMAND_EVENT) voice.SetEvent(command.value.u); - else if (id == COMMAND_MATRIX_LEVELS) SetMatrixLevels(voice, index, command.value.levels, command.count); - else if (id == COMMAND_PORT_VOLUME) PortAt(voice, index).volume = command.value.f; - else SetPortMatrix(voice, index, command.value.i); + try { + for (std::size_t i = 0; i < num_commands; i++) { + const auto& command = commands[i]; + const std::uint32_t index = command.id >> 24; + const std::uint32_t id = command.id & 0xffffff; + const std::uint8_t expectedType = id == COMMAND_EVENT ? COMMAND_TYPE_UINT + : id == COMMAND_MATRIX_LEVELS ? COMMAND_TYPE_FLOAT_ARRAY + : id == COMMAND_PORT_VOLUME ? COMMAND_TYPE_FLOAT + : id == COMMAND_PORT_MATRIX ? COMMAND_TYPE_INT : 0; + if (expectedType == 0) throw std::runtime_error("NGS2: voice command " + Ngs2Hex(command.id) + " is not implemented"); + if (command.type != expectedType) throw std::invalid_argument("NGS2: voice command " + Ngs2Hex(command.id) + " has value type " + Ngs2Hex(command.type)); + if (id == COMMAND_EVENT) SendEvent(voice, command.value.u); + else if (id == COMMAND_MATRIX_LEVELS) SetMatrixLevels(voice, index, command.value.levels, command.count); + else if (id == COMMAND_PORT_VOLUME) PortAt(voice, index).volume = command.value.f; + else SetPortMatrix(voice, index, command.value.i); + } + } catch (const Ngs2ReturnCode& result) { + return result.code; } return SCE_NGS2_OK; } @@ -472,6 +499,12 @@ int APS5_VABI sceNgs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state sampler.num_decoded_samples = voice.decodedSamples; return SCE_NGS2_OK; } + case SCE_NGS2_RACK_ID_CUSTOM_MASTERING: { + if (state_size != sizeof(Ngs2VoiceState)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; + *state = {}; + state->state_flags = voice.stateFlags; + return SCE_NGS2_OK; + } default: throw std::runtime_error("NGS2: voice state of rack " + Ngs2Hex(voice.rack->rackId) + " is not implemented"); } } @@ -526,6 +559,20 @@ int APS5_VABI sceNgs2VoiceGetOwner(uintptr_t voice_handle, uintptr_t* owner_rack return SCE_NGS2_OK; } +int APS5_VABI sceNgs2VoiceGetMatrixInfo(uintptr_t voice_handle, uint32_t matrix_id, Ngs2VoiceMatrixInfo* info, size_t info_size) { + if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + if (info_size != sizeof(Ngs2VoiceMatrixInfo)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; + const Ngs2Lock lock; + const auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + if (matrix_id >= found->matrices.size()) APS5_INVALID_ARG_EX; + const auto& levels = found->matrices[matrix_id]; + *info = {}; + info->num_levels = static_cast(levels.size()); + std::copy(levels.begin(), levels.end(), info->levels); + return SCE_NGS2_OK; +} + int APS5_VABI sceNgs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle, uint32_t num_speakers) { return Ngs2PanInit(work, speaker_angles, unit_angle, num_speakers); } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp index eed5eaf1f1..1c72ffa8d4 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include "Ngs2Test.hpp" #include "libatrac9.h" @@ -6,8 +7,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -174,6 +177,15 @@ static void TestParse() { Require(info.num_audio_unit_per_frame == 4 && info.audio_frame_size == SuperframeBytes && info.num_audio_frame_samples == SuperframeSamples); Require(info.num_blocks == 1 && info.block[0].data_offset == info.data_offset && info.block[0].data_size == SuperframeBytes); Require(info.block[0].num_skip_samples == 256 && info.block[0].num_samples == 900); + for (const std::uint32_t factDelay : {0u, 1u, 900u, 0xffffffffu}) { + auto other = file; + constexpr char factTag[] = {'f', 'a', 'c', 't'}; + const auto fact = static_cast(std::search(other.begin(), other.end(), std::begin(factTag), std::end(factTag)) - other.begin()); + std::memcpy(other.data() + fact + 12, &factDelay, sizeof(factDelay)); + Ngs2WaveformInfo parsed{}; + Require(sceNgs2ParseWaveformData(other.data(), other.size(), &parsed) == SCE_NGS2_OK); + Require(parsed.num_delay_samples == 256 && parsed.block[0].num_skip_samples == 256 && parsed.num_samples == 900); + } Require(sceNgs2ParseWaveformData(file.data(), 11, &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA); Require(sceNgs2ParseWaveformData(Superframe, SuperframeBytes, &info) == SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT); @@ -181,18 +193,73 @@ static void TestParse() { Require(sceNgs2ParseWaveformData(wrongRate.data(), wrongRate.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); } +static void RequireAtrac9Block(std::uint32_t config, std::uint32_t pos, std::uint32_t count, std::uint64_t offset, std::uint64_t size, std::uint32_t skip) { + const Ngs2WaveformFormat format{SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, config, 0, 0}; + Ngs2WaveformBlock block; + std::memset(&block, 0xab, sizeof(block)); + Require(sceNgs2CalcWaveformBlock(&format, pos, count, &block) == SCE_NGS2_OK); + Require(block.data_offset == offset && block.data_size == size && block.num_skip_samples == skip && block.num_samples == count); + Require(block.num_repeats == 0 && block.reserved == 0 && block.user_data == 0); +} + static void TestCalcBlock() { const Ngs2WaveformFormat format{SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, Config, 0, 0}; - Ngs2WaveformBlock block{}; Require(sceNgs2CalcWaveformBlock(&format, 1500, 600, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); - Require(sceNgs2CalcWaveformBlock(&format, 1500, 600, &block) == SCE_NGS2_OK); - Require(block.data_offset == SuperframeBytes && block.data_size == 2 * SuperframeBytes && block.num_skip_samples == 476 && block.num_samples == 600); - Require(sceNgs2CalcWaveformBlock(&format, 1500, 0, &block) == SCE_NGS2_OK); - Require(block.data_offset == SuperframeBytes && block.data_size == 0 && block.num_skip_samples == 0 && block.num_samples == 0); - const Ngs2WaveformFormat stereo{SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 2, 48000, Config, 0, 0}; - Require(sceNgs2CalcWaveformBlock(&stereo, 0, 1, &block) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); - const Ngs2WaveformFormat silent{SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 0, 48000, Config, 0, 0}; - Require(sceNgs2CalcWaveformBlock(&silent, 0, 1, &block) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + RequireAtrac9Block(Config, 0, 768, 0, SuperframeBytes, 256); + RequireAtrac9Block(Config, 0, 769, 0, 2 * SuperframeBytes, 256); + RequireAtrac9Block(Config, 1500, 600, SuperframeBytes, 2 * SuperframeBytes, 732); + RequireAtrac9Block(Config, 1500, 0, SuperframeBytes, SuperframeBytes, 732); + RequireAtrac9Block(Config, 4294967195u, 1000, 1073741568u, 3221226240u, 1179); + RequireAtrac9Block(0xFE7007E0, 300, 10, 64, 128, 300); + RequireAtrac9Block(0xFE4007F0, 600, 100, SuperframeBytes, SuperframeBytes, 216); + RequireAtrac9Block(0xFE1007F0, 0, 1, 0, SuperframeBytes, 64); + Ngs2WaveformBlock block{}; + const Ngs2WaveformFormat stereo{SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 2, 44100, Config, 0, 0}; + Require(sceNgs2CalcWaveformBlock(&stereo, 0, 1, &block) == SCE_NGS2_OK && block.data_size == SuperframeBytes && block.num_skip_samples == 256); + const Ngs2WaveformFormat silent{SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 0, 0, Config, 0, 0}; + Require(sceNgs2CalcWaveformBlock(&silent, 0, 1, &block) == SCE_NGS2_OK && block.data_size == SuperframeBytes && block.num_skip_samples == 256); + for (const std::uint32_t rateIndex : {0u, 2u, 3u, 5u, 6u, 8u, 15u}) { + const Ngs2WaveformFormat unknown{SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, (Config & ~0x00F00000u) | rateIndex << 20, 0, 0}; + bool threw = false; + try { sceNgs2CalcWaveformBlock(&unknown, 0, 1, &block); } catch (const std::runtime_error&) { threw = true; } + Require(threw); + } +} + +static void TestFrameInfo() { + const Ngs2WaveformFormat supported[] = { + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, Config, 7, 9}, + }; + const Ngs2WaveformFormat rejected[] = { + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 2, 48000, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 0, 48000, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 9, 48000, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 44100, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 0, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 192001, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, 0, 0, 0}, + }; + for (const auto& format : supported) { + Ngs2WaveformBlock block{}; + Require(sceNgs2CalcWaveformBlock(&format, 0, SuperframeSamples, &block) == SCE_NGS2_OK && block.data_size == 2 * SuperframeBytes); + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + Require(sceNgs2GetWaveformFrameInfo(&format, &values[0], &values[1], &values[2], &values[3]) == SCE_NGS2_OK); + Require(values[0] == SuperframeBytes && values[1] == SuperframeSamples && values[2] == 4 && values[3] == SuperframeSamples / 4); + } + for (const auto& format : rejected) { + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + Require(sceNgs2GetWaveformFrameInfo(&format, &values[0], &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + Require(values[0] == 0xa1 && values[1] == 0xa2 && values[2] == 0xa3 && values[3] == 0xa4); + } + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + Require(sceNgs2GetWaveformFrameInfo(nullptr, &values[0], &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + Require(sceNgs2GetWaveformFrameInfo(&supported[0], nullptr, &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&supported[0], &values[0], nullptr, &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&supported[0], &values[0], &values[1], nullptr, &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&supported[0], &values[0], &values[1], &values[2], nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(nullptr, nullptr, nullptr, nullptr, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(values[0] == 0xa1 && values[1] == 0xa2 && values[2] == 0xa3 && values[3] == 0xa4); } static void RequirePcmBlock(std::uint32_t type, std::uint32_t channels, std::uint32_t pos, std::uint32_t count, std::uint64_t offset, std::uint64_t size) { @@ -229,6 +296,26 @@ static void TestCalcPcmBlock() { Require(sceNgs2CalcWaveformBlock(nullptr, 0, 1, &block) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT && block.data_size == 0 && block.num_samples == 0); } +static void RequireVagBlock(std::uint32_t channels, std::uint32_t pos, std::uint32_t count, std::uint64_t offset, std::uint64_t size, std::uint32_t samples) { + const Ngs2WaveformFormat format{0x1c, channels, 48000, 0, 0, 0}; + Ngs2WaveformBlock block; + std::memset(&block, 0xab, sizeof(block)); + Require(sceNgs2CalcWaveformBlock(&format, pos, count, &block) == SCE_NGS2_OK); + Require(block.data_offset == offset && block.data_size == size && block.num_samples == samples); + Require(block.num_repeats == 0 && block.num_skip_samples == 0 && block.reserved == 0 && block.user_data == 0); +} + +static void TestCalcVagBlock() { + RequireVagBlock(1, 0, 28, 0, 16, 28); + RequireVagBlock(1, 27, 2, 0, 32, 56); + RequireVagBlock(1, 28, 28, 16, 16, 28); + RequireVagBlock(2, 30, 1, 32, 32, 28); + RequireVagBlock(6, 56, 57, 192, 288, 84); + RequireVagBlock(1, 0, 0, 0, 0, 0); + RequireVagBlock(1, 5, 0, 0, 16, 28); + RequireVagBlock(0, 100, 50, 0, 0, 84); +} + static void Set32(std::vector& bytes, std::size_t offset, std::uint32_t value) { for (int i = 0; i < 4; ++i) bytes[offset + i] = static_cast(value >> (i * 8)); } @@ -265,7 +352,7 @@ static void TestParseLoops() { Require(sceNgs2ParseWaveformData(file.data(), file.size(), &info) == SCE_NGS2_OK); Require(info.loop_begin_position == begin && info.loop_end_position == end && info.num_samples == samples); Require(info.num_blocks == 1 + (begin != 0) + (end != samples)); - Require(info.block[begin != 0].num_repeats == 1); + Require(info.block[begin != 0].num_repeats == UINT32_MAX); const auto system = CreateSystem(); const auto voice = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER)); Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, info.format}); @@ -273,12 +360,12 @@ static void TestParseLoops() { Ngs2SamplerVoiceWaveformBlocksParam{{}, file.data(), 0, info.num_blocks, info.block}); Patch(voice, Mastering(system, 1)); Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); - const auto rendered = Render(system, samples + end - begin + Grain); + const auto loop = end - begin; + const auto rendered = Render(system, end + 2 * loop); for (std::uint32_t i = 0; i < rendered.size(); ++i) { - const auto index = i < end ? i : i - (end - begin); - Require(rendered[i] == (i < samples + end - begin ? reference[256 + index] : 0.0f)); + const auto index = i < end ? i : begin + (i - end) % loop; + Require(rendered[i] == reference[256 + index]); } - Require(Flags(voice) == 0); Require(sceNgs2SystemDestroy(system, nullptr) == 0); } } @@ -306,28 +393,66 @@ static void TestParseLoops() { Require(sceNgs2SystemDestroy(system, nullptr) == 0); } +static bool ParseThrows(const std::vector& file) { + Ngs2WaveformInfo info{}; + try { + sceNgs2ParseWaveformData(file.data(), file.size(), &info); + + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +static std::vector ShortSampler(std::vector file, std::uint32_t size) { + file.erase(file.begin() + 100 + size, file.begin() + 160); + Set32(file, 96, size); + Set32(file, 4, static_cast(file.size() - 8)); + return file; +} + static void TestMalformedLoops() { const auto original = LoopedFile(100, 700, 0); - Ngs2WaveformInfo info{}; - for (const auto [offset, value] : {std::pair{128, 2}, {144, 0}, {148, 255}, {148, 5000}}) { + Ngs2WaveformInfo expected{}; + Require(sceNgs2ParseWaveformData(original.data(), original.size(), &expected) == SCE_NGS2_OK && expected.num_blocks == 3); + for (const auto [offset, value] : {std::pair{128, 2}, {140, 1}, {140, 2}, {152, 0x80000000u}, {156, 5}}) { auto file = original; Set32(file, offset, value); - Require(sceNgs2ParseWaveformData(file.data(), file.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA); + Ngs2WaveformInfo info{}; + Require(sceNgs2ParseWaveformData(file.data(), file.size(), &info) == SCE_NGS2_OK); + Require(info.loop_begin_position == 100 && info.loop_end_position == 700 && info.num_blocks == 3); + for (std::uint32_t block = 0; block < 3; ++block) Require(std::memcmp(&info.block[block], &expected.block[block], sizeof(Ngs2WaveformBlock)) == 0); } - for (const auto offset : {140u, 152u}) { + for (const auto [start, last, begin, end] : {std::tuple{256, 255, 0, 0}, {356, 255, 100, 0}, {956, 955, 700, 700}, {956, 700, 700, 445}}) { auto file = original; - Set32(file, offset, 1); - bool rejected = false; - try { sceNgs2ParseWaveformData(file.data(), file.size(), &info); } catch (const std::runtime_error&) { rejected = true; } - Require(rejected); + Set32(file, 144, start); + Set32(file, 148, last); + Ngs2WaveformInfo info{}; + Require(sceNgs2ParseWaveformData(file.data(), file.size(), &info) == SCE_NGS2_OK); + Require(info.loop_begin_position == begin && info.loop_end_position == end && info.num_blocks == 1); + Require(info.block[0].num_repeats == 0 && info.block[0].num_samples == info.num_samples); } + for (const std::uint32_t last : {4096u, 5000u, 0xffffffffu}) { + auto file = original; + Set32(file, 148, last); + Require(ParseThrows(file)); + } + for (const std::uint32_t start : {0u, 255u}) { + auto file = original; + Set32(file, 144, start); + Require(ParseThrows(file)); + } + Ngs2WaveformInfo info{}; + auto pastData = original; + Set32(pastData, 80, 4 * SuperframeSamples - 255); + Require(ParseThrows(pastData)); + Set32(pastData, 80, 4 * SuperframeSamples - 256); + Require(sceNgs2ParseWaveformData(pastData.data(), pastData.size(), &info) == SCE_NGS2_OK && info.num_blocks == 3); for (std::size_t size = 92; size < 168; ++size) Require(sceNgs2ParseWaveformData(original.data(), size, &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA); - auto shortSampler = original; - shortSampler.erase(shortSampler.begin() + 100 + 35, shortSampler.begin() + 160); - Set32(shortSampler, 96, 35); - Set32(shortSampler, 4, static_cast(shortSampler.size() - 8)); - Require(sceNgs2ParseWaveformData(shortSampler.data(), shortSampler.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA); + for (const std::uint32_t size : {28u, 36u, 50u}) Require(ParseThrows(ShortSampler(original, size))); + const auto minimal = ShortSampler(original, 52); + Require(sceNgs2ParseWaveformData(minimal.data(), minimal.size(), &info) == SCE_NGS2_OK && info.num_blocks == 3); } static std::uint8_t* SuperframeBeforeGuardPage() { @@ -538,7 +663,9 @@ int main() { TestParseLoops(); TestMalformedLoops(); TestCalcBlock(); + TestFrameInfo(); TestCalcPcmBlock(); + TestCalcVagBlock(); TestCorruptSuperframeAtPageEnd(); TestStreamPartitions(); TestRefill(reference); diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp index 379c277110..47a189ed42 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp @@ -1,8 +1,11 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include "Ngs2Test.hpp" #include #include +#include #include +#include #include struct Calls { @@ -84,6 +87,7 @@ static bool CreateThrows(const Ngs2CustomSubmixerRackOption& option) { Ngs2ContextBufferInfo query{}; try { sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &option.custom_rack_option.rack_option, &query); + } catch (const TException&) { return true; } @@ -95,17 +99,34 @@ static bool CreateSamplerThrows(const Ngs2CustomSamplerRackOption& option) { Ngs2ContextBufferInfo query{}; try { sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &query); + } catch (const TException&) { return true; } return false; } +static Ngs2CustomMasteringRackOption MasteringRackOption(std::uint32_t channels, std::uint32_t inputs) { + Ngs2CustomMasteringRackOption option{}; + auto& common = option.custom_rack_option.rack_option; + common.size = sizeof(option); + common.max_grain_samples = 512; + common.max_voices = 2; + common.max_matrices = 1; + common.max_ports = 1; + option.custom_rack_option.num_buffers = 1; + option.custom_rack_option.num_modules = 0; + option.max_channels = channels; + option.max_inputs = inputs; + return option; +} + template static bool ControlThrows(uintptr_t voice, std::uint32_t id, TParam param) { param.header = {static_cast(sizeof(TParam)), 0, id}; try { sceNgs2VoiceControl(voice, ¶m.header); + } catch (const TException&) { return true; } @@ -221,6 +242,7 @@ static void TestRackOptions() { bool failed = false; try { sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, nullptr, &query); + } catch (const std::runtime_error&) { failed = true; } @@ -355,6 +377,179 @@ static void TestCustomFilter() { Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } +struct ModuleOptionWords { + std::uint32_t size; + std::uint32_t words[15]; +}; + +static int allocCalls = 0; + +static std::int32_t APS5_VABI CountAlloc(Ngs2ContextBufferInfo*) { + allocCalls++; + return SCE_NGS2_OK; +} + +static std::int32_t APS5_VABI CountFree(Ngs2ContextBufferInfo*) { + return SCE_NGS2_OK; +} + +static Ngs2CustomSubmixerRackOption BuiltInRackOption(const Ngs2CustomUserFx2ModuleOption& userFx2, std::uint32_t moduleId, + const ModuleOptionWords* moduleOption, std::uint32_t stateSize) { + auto option = RackOption(&userFx2, 1); + auto& module = option.custom_rack_option.module[0]; + module.module_id = moduleId; + module.option = reinterpret_cast(moduleOption); + module.state_size = stateSize; + return option; +} + +static void TestCustomRackChecks() { + calls = {}; + const auto system = CreateSystem(); + const auto userFx2 = Module(Gain); + const auto good = RackOption(&userFx2, 1); + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &good.custom_rack_option.rack_option, &query) == SCE_NGS2_OK); + const auto info = Buffer(query); + const auto create = [&](const Ngs2CustomSubmixerRackOption& option) { + uintptr_t rack = 0; + const int result = sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &option.custom_rack_option.rack_option, &info, &rack); + Require(rack == 0); + return result; + }; + + auto option = good; + option.custom_rack_option.num_buffers = 0; + Require(create(option) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + Require(CreateThrows(option)); + option.custom_rack_option.num_buffers = SCE_NGS2_CUSTOM_MAX_BUFFERS + 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + option = good; + option.max_inputs = 2; + Require(create(option) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + option.custom_rack_option.num_buffers = 2; + Require(CreateThrows(option)); + option.custom_rack_option.num_buffers = SCE_NGS2_CUSTOM_MAX_BUFFERS; + option.custom_rack_option.module[0].dest_buffer_id = SCE_NGS2_CUSTOM_MAX_BUFFERS - 1; + Require(CreateThrows(option)); + option.custom_rack_option.module[0].dest_buffer_id = SCE_NGS2_CUSTOM_MAX_BUFFERS; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + option = good; + option.max_inputs = 0; + Require(CreateThrows(option)); + option.max_inputs = SCE_NGS2_CUSTOM_SUBMIXER_MAX_INPUTS + 1; + Require(CreateThrows(option)); + + option = good; + option.custom_rack_option.module[0].source_buffer_id = 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + option = good; + option.custom_rack_option.module[0].extra_buffer_id = 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + + option = good; + option.max_channels = SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS + 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_NUM_CHANNELS); + option.max_channels = 16; + Require(CreateThrows(option)); + option.max_channels = SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS; + Require(CreateThrows(option)); + + Require(create(BuiltInRackOption(userFx2, 0x19, nullptr, 4)) == SCE_NGS2_ERROR_INVALID_STATE_SIZE); + Require(!CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, nullptr, 0))); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, nullptr, 4)) == SCE_NGS2_ERROR_INVALID_STATE_SIZE); + const ModuleOptionWords gainOption{sizeof(Ngs2CustomModuleOption), {}}; + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, &gainOption, 0))); + + const ModuleOptionWords reverb{SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE, {1}}; + const ModuleOptionWords reverbOff{SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE, {0}}; + const ModuleOptionWords reverbSize{16, {1}}; + const ModuleOptionWords reverbWord{SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE, {2}}; + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverb, 0))); + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverbOff, 0))); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverbSize, 0)) == SCE_NGS2_ERROR_INVALID_OPTION_SIZE); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverbWord, 0)) == SCE_NGS2_ERROR_INVALID_REVERB_OPTION); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverb, 4)) == SCE_NGS2_ERROR_INVALID_STATE_SIZE); + + const ModuleOptionWords pitchShift{SCE_NGS2_CUSTOM_PITCH_SHIFT_OPTION_SIZE, {0}}; + const ModuleOptionWords pitchShiftWord{SCE_NGS2_CUSTOM_PITCH_SHIFT_OPTION_SIZE, {1}}; + const auto pitchShiftState = SCE_NGS2_CUSTOM_PITCH_SHIFT_STATE_SIZE; + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT, &pitchShift, pitchShiftState))); + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT, &pitchShift, 0))); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT, &pitchShift, 4)) == SCE_NGS2_ERROR_INVALID_STATE_SIZE); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT, &pitchShiftWord, pitchShiftState)) == SCE_NGS2_ERROR_INVALID_PITCH_SHIFT_OPTION); + + const ModuleOptionWords convolution{64, {}}; + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_CONVOLUTION, &convolution, 64))); + option = BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_CONVOLUTION, &convolution, 64); + option.custom_rack_option.module[0].dest_buffer_id = 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + + option = BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, nullptr, 0); + option.custom_rack_option.num_modules = 2; + option.custom_rack_option.module[1] = good.custom_rack_option.module[0]; + option.custom_rack_option.module[1].dest_buffer_id = 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + Require(CreateThrows(BuiltInRackOption(userFx2, 0x1d, nullptr, 0))); + + option = good; + option.custom_rack_option.num_buffers = 0; + bool rejected = false; + uintptr_t rack = 0; + try { + sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &option.custom_rack_option.rack_option, nullptr, &rack); + + } catch (const std::invalid_argument&) { + rejected = true; + } + Require(rejected); + const Ngs2BufferAllocator allocator{CountAlloc, CountFree, 0}; + Require(sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &option.custom_rack_option.rack_option, &allocator, &rack) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + Require(rack == 0 && allocCalls == 0 && calls.setupVoices.empty()); + + Ngs2CustomSamplerRackOption sampler{}; + sampler.custom_rack_option.rack_option.size = sizeof(sampler); + sampler.custom_rack_option.rack_option.max_grain_samples = 512; + sampler.custom_rack_option.rack_option.max_voices = 1; + sampler.custom_rack_option.num_modules = 1; + sampler.custom_rack_option.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER; + sampler.max_channel_works = 1; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + sampler.custom_rack_option.num_buffers = 1; + sampler.custom_rack_option.module[0].extra_buffer_id = 1; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + Require(rack == 0); + sampler.custom_rack_option.module[0].extra_buffer_id = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_OK); + sampler.custom_rack_option.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_GAIN; + Require(CreateSamplerThrows(sampler)); + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + for (const auto count : {1u, 2u, 24u}) { + sampler.custom_rack_option.num_modules = count; + for (std::uint32_t index = 0; index < count; ++index) { + sampler.custom_rack_option.module[index].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER; + } + sampler.custom_rack_option.module[count - 1].module_id = 0x19; + Require(CreateSamplerThrows(sampler)); + for (const bool useAllocator : {false, true}) { + rack = 0; + rejected = false; + try { + if (useAllocator) { + sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &allocator, &rack); + + } else { + sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack); + + } + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected && rack == 0 && allocCalls == 0); + } + } + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} struct LegacyUserFxState { std::uint32_t calls = 0; @@ -365,6 +560,8 @@ struct LegacyUserFxState { }; static std::int32_t APS5_VABI ProcessLegacy(Ngs2UserFxProcessContext* context) { + Require(context->user_data1 == 123 && context->user_data2 == 456); + Require(context->num_channels == 2 && context->num_grain_samples == Grain && context->sample_rate == 48000); auto& state = *reinterpret_cast(context->user_data0); state.calls++; state.flags = context->flags; @@ -376,7 +573,7 @@ static std::int32_t APS5_VABI ProcessLegacy(Ngs2UserFxProcessContext* context) { return SCE_NGS2_OK; } -static Ngs2CustomSubmixerRackOption LegacyRackOption(const Ngs2CustomModuleOption* option) { +static Ngs2CustomSubmixerRackOption LegacyRackOption(const Ngs2CustomModuleOption* option, std::uint32_t moduleIndex) { Ngs2CustomSubmixerRackOption rack{}; auto& common = rack.custom_rack_option.rack_option; common.size = sizeof(rack); @@ -385,27 +582,28 @@ static Ngs2CustomSubmixerRackOption LegacyRackOption(const Ngs2CustomModuleOptio common.max_matrices = 1; common.max_ports = 1; rack.custom_rack_option.num_buffers = 1; - rack.custom_rack_option.num_modules = 1; - rack.custom_rack_option.module[0].option = option; - rack.custom_rack_option.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_USER_FX; - rack.custom_rack_option.module[0].state_size = 16; + rack.custom_rack_option.num_modules = moduleIndex + 1; + for (std::uint32_t index = 0; index <= moduleIndex; ++index) { + rack.custom_rack_option.module[index].option = option; + rack.custom_rack_option.module[index].module_id = 0x19; + } rack.max_channels = 2; rack.max_inputs = 1; return rack; } -static void TestCustomLegacyUserFx() { +static void TestCustomLegacyUserFx(std::uint32_t moduleIndex) { const auto system = CreateSystem(); const auto master = Mastering(system, 2); const Ngs2CustomModuleOption header{sizeof(Ngs2CustomModuleOption)}; - const auto rack = CustomRack(system, LegacyRackOption(&header)); + const auto rack = CustomRack(system, LegacyRackOption(nullptr, moduleIndex)); const auto custom = Voice(rack); Control(custom, SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 2, 0, 0}); Patch(custom, master); Event(custom, SCE_NGS2_VOICE_EVENT_PLAY); LegacyUserFxState state{}; - Control(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 0, + Control(custom, 0x40001900 | moduleIndex, Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, reinterpret_cast(&state), 123, 456}); auto out = Render(system); @@ -421,20 +619,11 @@ static void TestCustomLegacyUserFx() { std::uint8_t dummy[10]; }; Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 0, BadSizeParam{})); - Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 2, Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, 0, 0, 0})); + Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 31, Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, 0, 0, 0})); - Ngs2CustomModuleOption badSize{2}; - Require(CreateThrows(LegacyRackOption(&badSize))); - - struct ExtendedOption { - std::uint32_t size = 20; - std::uint32_t reserved = 0; - std::uint32_t channels = 4; - float param0 = 1000.0f; - float param1 = 0.0f; - } extendedOption; - const auto rackExtended = CustomRack(system, LegacyRackOption(reinterpret_cast(&extendedOption))); - Require(sceNgs2RackDestroy(rackExtended, nullptr) == SCE_NGS2_OK); + Require(CreateThrows(LegacyRackOption(&header, moduleIndex))); + Require(ControlThrows(custom, 0x40001d00, + Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, reinterpret_cast(&state), 123, 456})); state.result = -1; bool failed = false; @@ -448,7 +637,7 @@ static void TestCustomLegacyUserFx() { Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); - const auto rackNullOpt = CustomRack(system, LegacyRackOption(nullptr)); + const auto rackNullOpt = CustomRack(system, LegacyRackOption(nullptr, moduleIndex)); const auto voiceNullOpt = Voice(rackNullOpt); Control(voiceNullOpt, SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 2, 0, 0}); Patch(voiceNullOpt, master); @@ -457,7 +646,7 @@ static void TestCustomLegacyUserFx() { LegacyUserFxState state2{}; state2.val0 = 0.125f; state2.val1 = 0.75f; - Control(voiceNullOpt, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 0, + Control(voiceNullOpt, 0x40001900 | moduleIndex, Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, reinterpret_cast(&state2), 123, 456}); out = Render(system); Require(state2.calls == 1 && state2.flags == 1); @@ -467,11 +656,259 @@ static void TestCustomLegacyUserFx() { Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } +struct DelayOption { + std::uint32_t size = 20; + std::uint32_t type = 0; + std::uint32_t maxTaps = 4; + float maxLength = 1000.0f; + std::uint32_t reserved = 0; +}; + +static Ngs2CustomSubmixerRackOption DelayRackOption(const DelayOption& delay, std::uint32_t count) { + auto option = LegacyRackOption(nullptr, count - 1); + option.custom_rack_option.rack_option.max_ports = 8; + for (std::uint32_t index = 0; index < count; ++index) { + option.custom_rack_option.module[index].module_id = 0x1d; + option.custom_rack_option.module[index].option = reinterpret_cast(&delay); + } + return option; +} + +static void TestCustomDelay(std::uint32_t count, const DelayOption& delay) { + const auto system = CreateSystem(); + const auto master = Mastering(system, 2); + auto option = DelayRackOption(delay, count); + const auto rack = CustomRack(system, option); + const auto custom = Voice(rack); + Control(custom, SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 2, 0, 0}); + Patch(custom, master); + Event(custom, SCE_NGS2_VOICE_EVENT_PLAY); + for (const float sample : Render(system)) Require(sample == 0.0f); + + std::vector pcm(Grain * 2 * 8); + for (std::uint32_t i = 0; i < pcm.size() / 2; ++i) { + pcm[i * 2] = static_cast(1000 + i * 17); + pcm[i * 2 + 1] = static_cast(-2000 + static_cast(i) * 11); + } + const auto sampler = Sampler(system, pcm); + Patch(sampler, custom); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + const auto checkGrain = [&](std::uint32_t grain) { + const auto out = Render(system); + for (std::uint32_t i = 0; i < Grain * 2; ++i) { + Require(out[i] == static_cast(pcm[grain * Grain * 2 + i]) / 32768.0f); + } + }; + for (std::uint32_t grain = 0; grain < 3; ++grain) checkGrain(grain); + + LegacyUserFxState state{}; + for (std::uint32_t index = 0; index < 256; ++index) { + Ngs2CustomVoiceUserFxParam param{{sizeof(Ngs2CustomVoiceUserFxParam), 0, 0x40001d00 | index}, + ProcessLegacy, reinterpret_cast(&state), 123, 456}; + bool rejected = false; + try { + sceNgs2VoiceControl(custom, ¶m.header); + + } catch (const std::runtime_error& error) { + const std::string message = error.what(); + rejected = message.find("Delay") != std::string::npos && message.find("not implemented") != std::string::npos; + } + Require(rejected); + } + checkGrain(3); + Require(state.calls == 0); + Event(sampler, SCE_NGS2_VOICE_EVENT_STOP); + for (const float sample : Render(system)) Require(sample == 0.0f); + + option.custom_rack_option.module[0].state_size = 4; + Require(CreateThrows(option)); + option.custom_rack_option.module[0].state_size = 0; + option.custom_rack_option.module[0].state_offset = 4; + Require(CreateThrows(option)); + option.custom_rack_option.module[0].state_offset = 0; + option.custom_rack_option.module[0].option = nullptr; + Require(CreateThrows(option)); + Ngs2CustomSamplerRackOption samplerOption{}; + samplerOption.custom_rack_option = DelayRackOption(delay, count).custom_rack_option; + samplerOption.custom_rack_option.rack_option.size = sizeof(samplerOption); + samplerOption.max_channel_works = 1; + Require(CreateSamplerThrows(samplerOption)); + for (const auto invalid : {DelayOption{19}, DelayOption{24}, DelayOption{20, 2}, DelayOption{20, 0, 0}, + DelayOption{20, 0, 9}, DelayOption{20, 0, 4, 0.0f}, DelayOption{20, 0, 4, 20001.0f}, + DelayOption{20, 0, 4, std::numeric_limits::quiet_NaN()}, + DelayOption{20, 0, 4, std::numeric_limits::infinity()}, DelayOption{20, 0, 4, 1000.0f, 1}}) { + Require(CreateThrows(DelayRackOption(invalid, count))); + } + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestCustomGain() { + const auto system = CreateSystem(); + const auto master = Mastering(system, 2); + const auto userFx2 = Module(Gain); + const auto rack = CustomRack(system, BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, nullptr, 0)); + const auto custom = Voice(rack); + Control(custom, SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 2, 0, 0}); + Patch(custom, master); + Event(custom, SCE_NGS2_VOICE_EVENT_PLAY); + std::vector pcm(Grain * 2 * 8, 16384); + const auto sampler = Sampler(system, pcm); + Patch(sampler, custom); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + + auto out = Render(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.0f && out[i * 2 + 1] == 0.0f); + + Ngs2CustomVoiceGainParam gain{}; + gain.gain[0] = 2.0f; + gain.gain[1] = -0.5f; + Control(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, gain); + out = Render(system); + for (std::uint32_t i = 0; i < Grain; i++) { + Require(out[i * 2] == 0.5f * 2.0f * static_cast(i + 1) / Grain); + Require(out[i * 2 + 1] == 0.5f * -0.5f * static_cast(i + 1) / Grain); + } + out = Render(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 1.0f && out[i * 2 + 1] == -0.25f); + + gain.gain[0] = 1.0f; + Control(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, gain); + out = Render(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.5f * (2.0f - static_cast(i + 1) / Grain)); + + auto outside = gain; + outside.gain[2] = 33.0f; + Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, outside)); + Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 1, gain)); + Require(ControlThrows(sampler, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, gain)); + struct ShortGainParam { + Ngs2VoiceParamHeader header; + float gain[2]; + }; + Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, ShortGainParam{})); + + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +template +static bool ModuleInfoThrows(uintptr_t rack, std::uint32_t moduleIndex, std::size_t infoSize) { + Ngs2CustomModuleInfo info; + std::memset(&info, 0xa5, sizeof(info)); + bool threw = false; + try { + sceNgs2CustomRackGetModuleInfo(rack, moduleIndex, &info, infoSize); + + } catch (const TException&) { + threw = true; + } + const auto* bytes = reinterpret_cast(&info); + for (std::size_t i = 0; i < sizeof(info); i++) Require(bytes[i] == 0xa5); + return threw; +} + +static void TestCustomModuleInfo() { + const auto system = CreateSystem(); + const Ngs2CustomUserFx2ModuleOption userFx2{{sizeof(Ngs2CustomUserFx2ModuleOption)}, nullptr, nullptr, nullptr, Gain, sizeof(float), sizeof(float), 8, 0x77}; + const Ngs2CustomUserFx2ModuleOption modules[2] = {userFx2, userFx2}; + auto option = RackOption(modules, 2); + auto& first = option.custom_rack_option.module[0]; + first.state_offset = 0x40; + first.reserved = 0x11; + first.reserved2 = 0x22; + auto& filter = option.custom_rack_option.module[1]; + filter.option = nullptr; + filter.module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER; + filter.state_size = 0; + const auto rack = CustomRack(system, option); + + Ngs2CustomModuleInfo info; + std::memset(&info, 0xa5, sizeof(info)); + Require(sceNgs2CustomRackGetModuleInfo(rack, 0, &info, sizeof(info)) == SCE_NGS2_OK); + Require(info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2 && info.source_buffer_id == 0 && info.extra_buffer_id == 0 && info.dest_buffer_id == 0); + Require(info.state_offset == 0x40 && info.state_size == 16 && info.reserved == 0 && info.reserved2 == 0); + std::memset(&info, 0xa5, sizeof(info)); + Require(sceNgs2CustomRackGetModuleInfo(rack, 1, &info, sizeof(info)) == SCE_NGS2_OK); + Require(info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER && info.state_offset == 0 && info.state_size == 0 && info.reserved == 0 && info.reserved2 == 0); + + Require(sceNgs2CustomRackGetModuleInfo(rack, 0, nullptr, sizeof(info)) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2CustomRackGetModuleInfo(0x1234, 0, nullptr, sizeof(info)) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + std::memset(&info, 0xa5, sizeof(info)); + Require(sceNgs2CustomRackGetModuleInfo(0x1234, 0, &info, sizeof(info)) == SCE_NGS2_ERROR_INVALID_RACK_HANDLE); + Require(sceNgs2CustomRackGetModuleInfo(system, 0, &info, sizeof(info)) == SCE_NGS2_ERROR_INVALID_RACK_HANDLE); + const auto* bytes = reinterpret_cast(&info); + for (std::size_t i = 0; i < sizeof(info); i++) Require(bytes[i] == 0xa5); + Require(ModuleInfoThrows(rack, 2, sizeof(Ngs2CustomModuleInfo))); + Require(ModuleInfoThrows(rack, 0, sizeof(Ngs2CustomModuleInfo) - 1)); + Require(ModuleInfoThrows(rack, 0, sizeof(Ngs2CustomModuleInfo) + 8)); + Require(ModuleInfoThrows(CreateRack(system, SCE_NGS2_RACK_ID_SUBMIXER), 0, sizeof(Ngs2CustomModuleInfo))); + + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + Require(sceNgs2CustomRackGetModuleInfo(rack, 0, &info, sizeof(info)) == SCE_NGS2_ERROR_INVALID_RACK_HANDLE); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestCustomMastering() { + const auto system = CreateSystem(); + const auto good = MasteringRackOption(2, 1); + + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_MASTERING, &good.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_MASTERING, &good.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_OK); + const auto voice = Voice(rack); + + Control(voice, SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_SETUP, Ngs2MasteringVoiceSetupParam{{}, 2, 0}); + Control(voice, SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 1.0f, 1.0f}); + Control(voice, SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_OUTPUT, Ngs2MasteringVoiceOutputParam{{}, 0, 0}); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + + Ngs2VoiceState state{}; + Require(sceNgs2VoiceGetState(voice, &state, sizeof(state)) == SCE_NGS2_OK); + Require((state.state_flags & SCE_NGS2_VOICE_STATE_FLAG_INUSE) != 0); + Require(sceNgs2VoiceGetState(voice, &state, sizeof(state) - 1) == SCE_NGS2_ERROR_INVALID_OUT_SIZE); + + std::vector pcm(Grain * 2 * 8, 16384); + const auto sampler = Sampler(system, pcm); + Patch(sampler, voice); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + + auto out = Render(system); + Require(out.size() == Grain * 2); + Require(out[0] != 0.0f); + + Require(sceNgs2VoiceGetState(voice, &state, sizeof(state)) == SCE_NGS2_OK); + Require((state.state_flags & SCE_NGS2_VOICE_STATE_FLAG_PLAYING) != 0); + + auto createMastering = [&](const Ngs2CustomMasteringRackOption& opt) { + uintptr_t r = 0; + return sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_MASTERING, &opt.custom_rack_option.rack_option, &info, &r); + }; + + auto invalidChannels = good; + invalidChannels.max_channels = SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS + 1; + Require(createMastering(invalidChannels) == SCE_NGS2_ERROR_INVALID_NUM_CHANNELS); + + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + int main() { + for (const auto count : {1u, 2u, 24u}) { + for (const auto delay : {DelayOption{}, DelayOption{20, 1, 1, 1.0f, 0}, DelayOption{20, 1, 8, 20000.0f, 0}}) { + TestCustomDelay(count, delay); + } + } TestUserFxChain(); TestRackOptions(); + TestCustomRackChecks(); TestCustomSampler(); TestCustomFilter(); - TestCustomLegacyUserFx(); + for (const auto moduleIndex : {0u, 1u, 23u}) TestCustomLegacyUserFx(moduleIndex); + TestCustomGain(); + TestCustomModuleInfo(); + TestCustomMastering(); return 0; } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Filter.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Filter.cpp index cf47e314b2..f6dd4600d1 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Filter.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Filter.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include "Ngs2Test.hpp" #include @@ -151,6 +152,7 @@ static void RequireRejected(uintptr_t voice, const Ngs2SamplerVoiceFilterParam& bool rejected = false; try { sceNgs2VoiceControl(voice, ¶m.header); + } catch (const TError&) { rejected = true; } @@ -181,6 +183,7 @@ static void TestRateChangeUnderFilter() { bool rejected = false; try { sceNgs2SystemSetSampleRate(system, 96000); + } catch (const std::runtime_error&) { rejected = true; } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Geom.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Geom.cpp index 2417e360e0..b456ed0cd0 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Geom.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Geom.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include "Ngs2Test.hpp" #include @@ -105,6 +106,7 @@ static void RejectListener(const Ngs2GeomListenerParam* param, std::uint32_t fla bool rejected = false; try { sceNgs2GeomCalcListener(param, &work, flags); + } catch (const std::exception&) { rejected = true; } @@ -115,9 +117,18 @@ static void RejectListener(const Ngs2GeomListenerParam* param, std::uint32_t fla static void TestInvalidListener() { auto param = Listener(); Require(sceNgs2GeomCalcListener(¶m, nullptr, 0) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); - RejectListener(nullptr, 0); - RejectListener(¶m, 2); - RejectListener(¶m, 0xffffffffu); + Ngs2GeomListenerWork work; + std::memset(&work, 0x5a, sizeof(work)); + unsigned char before[sizeof(work)]; + std::memcpy(before, &work, sizeof(work)); + Require(sceNgs2GeomCalcListener(nullptr, &work, 0) == static_cast(0x804A8461)); + Require(sceNgs2GeomCalcListener(nullptr, &work, 2) == static_cast(0x804A8461)); + Require(sceNgs2GeomCalcListener(nullptr, nullptr, 0) == static_cast(0x804A8461)); + Require(sceNgs2GeomCalcListener(¶m, &work, 2) == static_cast(0x804A8463)); + Require(sceNgs2GeomCalcListener(¶m, &work, 0x80000000u) == static_cast(0x804A8463)); + Require(sceNgs2GeomCalcListener(¶m, &work, 0xffffffffu) == static_cast(0x804A8463)); + Require(sceNgs2GeomCalcListener(¶m, nullptr, 2) == static_cast(0x804A8010)); + Require(std::memcmp(before, &work, sizeof(work)) == 0); for (const float value : {0.0f, -1.0f, std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}) { param.sound_speed = value; RejectListener(¶m, 0); diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp index 56246ca178..9682af0e24 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp @@ -1,12 +1,284 @@ #include "Ngs2Test.hpp" +#include +#include +#include +#include #include #include +#include +#include static void Status(int actual, int expected, const char* message) { if (actual != expected) throw std::runtime_error(message); } +static void CheckResetOption() { + Status(sceNgs2SystemResetOption(nullptr), static_cast(0x804A8010u), "null system option did not return the invalid out address error"); + constexpr std::uint64_t size = 0x90; + constexpr std::uint32_t maxGrainSamples = 512; + constexpr std::uint32_t numGrainSamples = 256; + constexpr std::uint32_t sampleRate = 48000; + constexpr std::uint32_t maxVoiceChannels = 8; + unsigned char expected[0x90] = {}; + std::memcpy(expected + 0x00, &size, sizeof(size)); + std::memcpy(expected + 0x6c, &maxGrainSamples, sizeof(maxGrainSamples)); + std::memcpy(expected + 0x70, &numGrainSamples, sizeof(numGrainSamples)); + std::memcpy(expected + 0x74, &sampleRate, sizeof(sampleRate)); + std::memcpy(expected + 0x78, &maxVoiceChannels, sizeof(maxVoiceChannels)); + static_assert(sizeof(Ngs2SystemOption) == sizeof(expected)); + Ngs2SystemOption option; + std::memset(&option, 0xAB, sizeof(option)); + Status(sceNgs2SystemResetOption(&option), SCE_NGS2_OK, "system option reset failed"); + Require(std::memcmp(&option, expected, sizeof(expected)) == 0); + Ngs2ContextBufferInfo query{}; + Status(sceNgs2SystemQueryBufferSize(&option, &query), SCE_NGS2_OK, "reset system option was rejected"); +} + +static int allocations = 0; + +static std::int32_t APS5_VABI CountedAllocate(Ngs2ContextBufferInfo* info) { + info->host_buffer = std::calloc(1, info->host_buffer_size); + allocations++; + return SCE_NGS2_OK; +} + +static std::int32_t APS5_VABI CountedRelease(Ngs2ContextBufferInfo* info) { + std::free(info->host_buffer); + allocations--; + return SCE_NGS2_OK; +} + +static std::string Hex(int value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", static_cast(value)); + return text; +} + +static void Returned(int actual, int expected, const char* entry, const std::string& label) { + if (actual != expected) throw std::runtime_error(std::string(entry) + " with " + label + " returned " + Hex(actual) + " instead of " + Hex(expected)); +} + +static Ngs2SystemOption Option(std::uint32_t maxGrainSamples = 512, std::uint32_t numGrainSamples = 256, std::uint32_t sampleRate = 48000, std::uint32_t maxVoiceChannels = 8) { + Ngs2SystemOption option{}; + option.size = sizeof(option); + option.max_grain_samples = maxGrainSamples; + option.num_grain_samples = numGrainSamples; + option.sample_rate = sampleRate; + option.max_voice_channels = maxVoiceChannels; + return option; +} + +static void CheckOption(const Ngs2SystemOption& option, int expected, const std::string& label) { + constexpr uintptr_t sentinel = 0x5a5a; + Ngs2ContextBufferInfo query{}; + Returned(sceNgs2SystemQueryBufferSize(&option, &query), expected, "sceNgs2SystemQueryBufferSize", label); + Require((query.host_buffer_size != 0) == (expected == SCE_NGS2_OK)); + std::vector storage(query.host_buffer_size / sizeof(std::uint64_t) + 1); + Ngs2ContextBufferInfo buffer{}; + buffer.host_buffer = storage.data(); + buffer.host_buffer_size = query.host_buffer_size; + const Ngs2BufferAllocator allocator{CountedAllocate, CountedRelease, 0}; + uintptr_t created = sentinel; + uintptr_t allocated = sentinel; + Returned(sceNgs2SystemCreate(&option, &buffer, &created), expected, "sceNgs2SystemCreate", label); + Returned(sceNgs2SystemCreateWithAllocator(&option, &allocator, &allocated), expected, "sceNgs2SystemCreateWithAllocator", label); + if (expected != SCE_NGS2_OK) { + Require(created == sentinel && allocated == sentinel && allocations == 0); + return; + } + Require(created != sentinel && allocated != sentinel && allocations == 1); + for (const auto system : {created, allocated}) { + Ngs2SystemInfo info{}; + Status(sceNgs2SystemGetInfo(system, &info, sizeof(info)), SCE_NGS2_OK, "system info of a valid option failed"); + Require(info.max_grain_samples == option.max_grain_samples && info.num_grain_samples == option.num_grain_samples && info.sample_rate == option.sample_rate); + Status(sceNgs2SystemDestroy(system, nullptr), SCE_NGS2_OK, "system destroy of a valid option failed"); + } + Require(allocations == 0); +} + +static void CheckQueryBuffer(const Ngs2SystemOption* option, int expected, const char* label) { + constexpr uintptr_t userData = 0x1234; + Ngs2ContextBufferInfo query; + std::memset(&query, 0xAB, sizeof(query)); + query.user_data = userData; + Returned(sceNgs2SystemQueryBufferSize(option, &query), expected, "sceNgs2SystemQueryBufferSize", label); + Require(query.host_buffer == nullptr && query.user_data == userData); + for (const auto value : query.reserved) Require(value == 0); + Require((query.host_buffer_size != 0) == (expected == SCE_NGS2_OK)); +} + +static void CheckCreateArguments() { + constexpr int outputError = static_cast(0x804A8010u); + constexpr int bufferError = static_cast(0x804A8020u); + constexpr int allocatorError = static_cast(0x804A8023u); + constexpr int rateError = static_cast(0x804A8051u); + constexpr uintptr_t sentinel = 0x5a5a; + const auto valid = Option(); + const auto badRate = Option(512, 256, 7000); + Ngs2ContextBufferInfo query{}; + Status(sceNgs2SystemQueryBufferSize(&valid, &query), SCE_NGS2_OK, "system buffer query failed"); + std::vector storage(query.host_buffer_size / sizeof(std::uint64_t) + 1); + Ngs2ContextBufferInfo buffer{}; + buffer.host_buffer = storage.data(); + buffer.host_buffer_size = query.host_buffer_size; + Ngs2ContextBufferInfo noHost = buffer; + noHost.host_buffer = nullptr; + const Ngs2BufferAllocator allocator{CountedAllocate, CountedRelease, 0}; + const Ngs2BufferAllocator noAlloc{nullptr, CountedRelease, 0}; + + struct CreateCase { + const Ngs2SystemOption* option; + const Ngs2ContextBufferInfo* buffer; + bool handle; + int expected; + const char* label; + }; + for (const auto& c : {CreateCase{&valid, nullptr, true, bufferError, "a null buffer info"}, + CreateCase{&valid, &noHost, true, bufferError, "a null host buffer"}, + CreateCase{&badRate, nullptr, false, bufferError, "a null buffer info, handle and bad option"}, + CreateCase{&badRate, &noHost, false, bufferError, "a null host buffer, handle and bad option"}, + CreateCase{&badRate, &buffer, false, outputError, "a null handle and bad option"}, + CreateCase{&badRate, &buffer, true, rateError, "a bad option"}}) { + uintptr_t created = sentinel; + Returned(sceNgs2SystemCreate(c.option, c.buffer, c.handle ? &created : nullptr), c.expected, "sceNgs2SystemCreate", c.label); + Require(created == sentinel); + } + uintptr_t created = sentinel; + Returned(sceNgs2SystemCreate(nullptr, &buffer, &created), SCE_NGS2_OK, "sceNgs2SystemCreate", "a null option"); + Status(sceNgs2SystemDestroy(created, nullptr), SCE_NGS2_OK, "system destroy failed"); + + struct AllocatorCase { + const Ngs2SystemOption* option; + const Ngs2BufferAllocator* allocator; + bool handle; + int expected; + const char* label; + }; + for (const auto& c : {AllocatorCase{&valid, nullptr, true, allocatorError, "a null allocator"}, + AllocatorCase{&valid, &noAlloc, true, allocatorError, "a null alloc handler"}, + AllocatorCase{&badRate, nullptr, false, allocatorError, "a null allocator, handle and bad option"}, + AllocatorCase{&badRate, &noAlloc, false, allocatorError, "a null alloc handler, handle and bad option"}, + AllocatorCase{&badRate, &allocator, false, outputError, "a null handle and bad option"}, + AllocatorCase{&badRate, &allocator, true, rateError, "a bad option"}}) { + uintptr_t allocated = sentinel; + Returned(sceNgs2SystemCreateWithAllocator(c.option, c.allocator, c.handle ? &allocated : nullptr), c.expected, "sceNgs2SystemCreateWithAllocator", c.label); + Require(allocated == sentinel && allocations == 0); + } + uintptr_t allocated = sentinel; + Returned(sceNgs2SystemCreateWithAllocator(nullptr, &allocator, &allocated), SCE_NGS2_OK, "sceNgs2SystemCreateWithAllocator", "a null option"); + Require(allocations == 1); + Status(sceNgs2SystemDestroy(allocated, nullptr), SCE_NGS2_OK, "system destroy failed"); + Require(allocations == 0); +} + +static void CheckSystemOption() { + constexpr int outputError = static_cast(0x804A8010u); + constexpr int sizeError = static_cast(0x804A8013u); + constexpr int rateError = static_cast(0x804A8051u); + constexpr int channelsError = static_cast(0x804A8052u); + constexpr int grainError = static_cast(0x804A8310u); + constexpr int maxGrainError = static_cast(0x804A8311u); + constexpr std::uint32_t invalid = 0xffffffffu; + + for (const std::uint32_t size : {0u, 8u, 0x88u, 0x98u, invalid}) { + auto option = Option(); + option.size = size; + CheckOption(option, sizeError, "size " + std::to_string(size)); + } + for (const std::uint32_t maxGrain : {0u, 32u, 63u, 65u, 96u, 2047u, 2049u, 2112u, 4096u, invalid}) { + CheckOption(Option(maxGrain, 64), maxGrainError, "max grain samples " + std::to_string(maxGrain)); + } + for (const std::uint32_t maxGrain : {64u, 128u, 576u, 2048u}) { + CheckOption(Option(maxGrain, 64), SCE_NGS2_OK, "max grain samples " + std::to_string(maxGrain)); + CheckOption(Option(maxGrain, maxGrain), SCE_NGS2_OK, "grain samples " + std::to_string(maxGrain)); + } + for (const std::uint32_t grain : {0u, 32u, 63u, 65u, 100u, 255u, 257u, 513u, 576u, 2048u, invalid}) { + CheckOption(Option(512, grain), grainError, "grain samples " + std::to_string(grain)); + } + for (const std::uint32_t grain : {64u, 192u, 448u, 512u}) { + CheckOption(Option(512, grain), SCE_NGS2_OK, "grain samples " + std::to_string(grain)); + } + for (const std::uint32_t rate : {11025u, 12000u, 22050u, 24000u, 44100u, 48000u, 88200u, 96000u, 176400u, 192000u}) { + CheckOption(Option(512, 256, rate), SCE_NGS2_OK, "sample rate " + std::to_string(rate)); + } + for (const std::uint32_t rate : {0u, 8000u, 11024u, 11026u, 16000u, 32000u, 47999u, 48001u, 192001u, invalid}) { + CheckOption(Option(512, 256, rate), rateError, "sample rate " + std::to_string(rate)); + } + for (const std::uint32_t channels : {1u, 2u, 8u, 36u, 37u}) { + CheckOption(Option(512, 256, 48000, channels), SCE_NGS2_OK, "voice channels " + std::to_string(channels)); + } + for (const std::uint32_t channels : {0u, 38u, 39u, invalid}) { + CheckOption(Option(512, 256, 48000, channels), channelsError, "voice channels " + std::to_string(channels)); + } + + auto badSize = Option(100, 100, 7000, 0); + badSize.size = 0x88; + CheckOption(badSize, sizeError, "a bad size and every other field bad"); + CheckOption(Option(100, 100, 7000, 0), maxGrainError, "a bad max grain, grain, rate and channels"); + CheckOption(Option(100, 0, 48000, 8), maxGrainError, "a bad max grain and grain"); + CheckOption(Option(100, 256, 7000, 8), maxGrainError, "a bad max grain and rate"); + CheckOption(Option(100, 256, 48000, 0), maxGrainError, "a bad max grain and channels"); + CheckOption(Option(512, 100, 7000, 0), grainError, "a bad grain, rate and channels"); + CheckOption(Option(512, 576, 48000, 8), grainError, "a grain above the max grain"); + CheckOption(Option(512, 100, 48000, 0), grainError, "a bad grain and channels"); + CheckOption(Option(512, 256, 7000, 0), rateError, "a bad rate and channels"); + + auto scheduled = Option(); + for (auto& character : scheduled.name) character = 'n'; + for (auto& pointer : scheduled.job_scheduler_options) pointer = 0x1000; + CheckOption(scheduled, SCE_NGS2_OK, "a name and job scheduler options"); + + const auto invalidRate = Option(512, 256, 7000); + Status(sceNgs2SystemQueryBufferSize(&invalidRate, nullptr), outputError, "invalid option took precedence over the null output"); + Status(sceNgs2SystemQueryBufferSize(nullptr, nullptr), outputError, "null option took precedence over the null output"); + CheckQueryBuffer(nullptr, SCE_NGS2_OK, "a null option"); + CheckQueryBuffer(&invalidRate, rateError, "a refused rate"); + auto invalidSize = Option(); + invalidSize.size = 0x88; + CheckQueryBuffer(&invalidSize, sizeError, "a refused size"); + const auto valid = Option(); + CheckQueryBuffer(&valid, SCE_NGS2_OK, "a valid option"); +} + +static void CheckSetGrainSamples() { + constexpr int systemError = static_cast(0x804A8201u); + constexpr int grainError = static_cast(0x804A8310u); + const auto system = CreateSystem(); + Ngs2SystemInfo info{}; + Status(sceNgs2SystemSetGrainSamples(0x1234, 100), systemError, "an invalid system handle lost to an invalid grain"); + for (const std::uint32_t grain : {64u, 192u, 512u}) { + Status(sceNgs2SystemSetGrainSamples(system, grain), SCE_NGS2_OK, "a valid grain was refused"); + Status(sceNgs2SystemGetInfo(system, &info, sizeof(info)), SCE_NGS2_OK, "system info failed"); + Require(info.num_grain_samples == grain); + } + for (const std::uint32_t grain : {0u, 513u, 576u, 2048u, 0xffffffffu}) { + Status(sceNgs2SystemSetGrainSamples(system, grain), grainError, "an invalid grain was accepted"); + Status(sceNgs2SystemGetInfo(system, &info, sizeof(info)), SCE_NGS2_OK, "system info failed"); + Require(info.num_grain_samples == 512); + } + Status(sceNgs2SystemDestroy(system, nullptr), SCE_NGS2_OK, "system destroy failed"); +} + +static void CheckSetSampleRate() { + constexpr int systemError = static_cast(0x804A8201u); + constexpr int rateError = static_cast(0x804A8051u); + const auto system = CreateSystem(); + Ngs2SystemInfo info{}; + Status(sceNgs2SystemSetSampleRate(0x1234, 7000), systemError, "an invalid system handle lost to an invalid rate"); + for (const std::uint32_t rate : {11025u, 12000u, 22050u, 24000u, 44100u, 48000u, 88200u, 96000u, 176400u, 192000u}) { + Status(sceNgs2SystemSetSampleRate(system, rate), SCE_NGS2_OK, "a valid rate was refused"); + Status(sceNgs2SystemGetInfo(system, &info, sizeof(info)), SCE_NGS2_OK, "system info failed"); + Require(info.sample_rate == rate); + } + for (const std::uint32_t rate : {0u, 7000u, 8000u, 16000u, 32000u, 47999u, 48001u, 192001u, 0xffffffffu}) { + Status(sceNgs2SystemSetSampleRate(system, rate), rateError, "an invalid rate was accepted"); + Status(sceNgs2SystemGetInfo(system, &info, sizeof(info)), SCE_NGS2_OK, "system info failed"); + Require(info.sample_rate == 192000); + } + Status(sceNgs2SystemDestroy(system, nullptr), SCE_NGS2_OK, "system destroy failed"); +} + int main() { try { constexpr int systemError = static_cast(0x804A8201u); @@ -39,6 +311,11 @@ int main() { Status(sceNgs2SystemDestroy(system, nullptr), SCE_NGS2_OK, "valid system destroy failed"); Status(sceNgs2SystemGetUserData(system, &value), systemError, "retired system returned the wrong error"); Require(value == sentinel); + CheckCreateArguments(); + CheckResetOption(); + CheckSystemOption(); + CheckSetGrainSamples(); + CheckSetSampleRate(); std::cout << "NGS2 PS5 handle errors and validation precedence checked\n"; return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp index a2545f52e6..b72cc16451 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp @@ -22,20 +22,28 @@ static void PutTag(std::vector& out, const char* tag) { out.insert(out.end(), tag, tag + 4); } -static std::vector PcmFile(std::uint32_t channels, std::uint32_t sampleRate, std::uint32_t bits, std::uint32_t frames) { +static std::vector PcmFile(std::uint32_t channels, std::uint32_t sampleRate, std::uint32_t bits, std::uint32_t frames, std::uint32_t tag = 1) { const std::uint32_t frameBytes = channels * bits / 8; + const std::uint32_t formatSize = tag == 0xfffe ? 40 : 16; std::vector file; PutTag(file, "RIFF"); - Put32(file, 36 + frames * frameBytes); + Put32(file, 20 + formatSize + frames * frameBytes); PutTag(file, "WAVE"); PutTag(file, "fmt "); - Put32(file, 16); - Put16(file, 1); + Put32(file, formatSize); + Put16(file, tag); Put16(file, channels); Put32(file, sampleRate); Put32(file, sampleRate * frameBytes); Put16(file, frameBytes); Put16(file, bits); + if (tag == 0xfffe) { + Put16(file, 22); + Put16(file, bits); + Put32(file, 0); + const std::uint8_t pcm[16] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}; + file.insert(file.end(), pcm, pcm + sizeof(pcm)); + } PutTag(file, "data"); Put32(file, frames * frameBytes); file.resize(file.size() + frames * frameBytes, 0x11); @@ -60,8 +68,24 @@ static void TestParsePcm() { std::memcpy(partialFrame.data() + 40, &partialSize, sizeof(partialSize)); Require(sceNgs2ParseWaveformData(partialFrame.data(), partialFrame.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA); - const auto eightBit = PcmFile(1, 44100, 8, 10); - Require(sceNgs2ParseWaveformData(eightBit.data(), eightBit.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + struct Depth { std::uint32_t tag, bits, type; }; + for (const auto depth : {Depth{1, 8, 0x11}, Depth{1, 16, 0x12}, Depth{1, 24, 0x14}, Depth{1, 32, 0x16}, Depth{3, 32, 0x18}, Depth{3, 64, 0x1a}, + Depth{0xfffe, 8, 0x11}, Depth{0xfffe, 24, 0x14}}) { + const auto wave = PcmFile(3, 48000, depth.bits, 10, depth.tag); + const std::uint32_t frameBytes = 3 * depth.bits / 8; + const std::uint32_t dataOffset = static_cast(wave.size() - 10 * frameBytes); + Ngs2WaveformInfo parsed{}; + Require(sceNgs2ParseWaveformData(wave.data(), wave.size(), &parsed) == SCE_NGS2_OK); + Require(parsed.format.waveform_type == depth.type && parsed.format.num_channels == 3 && parsed.format.sample_rate == 48000); + Require(parsed.data_offset == dataOffset && parsed.data_size == 10 * frameBytes && parsed.num_samples == 10); + Require(parsed.audio_unit_size == frameBytes && parsed.num_audio_unit_samples == 1 && parsed.audio_frame_size == frameBytes && parsed.num_audio_frame_samples == 1); + Require(parsed.num_blocks == 1 && parsed.block[0].data_offset == dataOffset && parsed.block[0].data_size == 10 * frameBytes && parsed.block[0].num_samples == 10); + } + for (const auto depth : {Depth{1, 12, 0}, Depth{1, 64, 0}, Depth{3, 16, 0}, Depth{3, 24, 0}, Depth{0xfffe, 12, 0}}) { + const auto wave = PcmFile(1, 44100, depth.bits, 8, depth.tag); + Ngs2WaveformInfo parsed{}; + Require(sceNgs2ParseWaveformData(wave.data(), wave.size(), &parsed) == SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT); + } const auto noChannels = PcmFile(0, 44100, 16, 10); Require(sceNgs2ParseWaveformData(noChannels.data(), noChannels.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); } @@ -113,9 +137,59 @@ static void TestParseFileBank() { std::filesystem::remove(path); } +static void TestFrameInfoPcm() { + for (std::uint32_t channels = 1; channels <= 8; channels++) { + for (const std::uint32_t rate : {1u, 22050u, 48000u, 192000u}) { + const Ngs2WaveformFormat format{SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, channels, rate, 0, 0, 0}; + std::uint32_t frameSize = 0xa1, frameSamples = 0xa2, units = 0xa3, delay = 0xa4; + Require(sceNgs2GetWaveformFrameInfo(&format, &frameSize, &frameSamples, &units, &delay) == SCE_NGS2_OK); + Require(frameSize == channels * 2 && frameSamples == 1 && units == 1 && delay == 0); + const auto file = PcmFile(channels, rate, 16, 10); + Ngs2WaveformInfo info{}; + Require(sceNgs2ParseWaveformData(file.data(), file.size(), &info) == SCE_NGS2_OK); + Require(info.audio_frame_size == frameSize && info.num_audio_frame_samples == frameSamples); + Require(info.num_audio_unit_per_frame == units && info.num_delay_samples == delay); + } + } + const Ngs2WaveformFormat ignored{SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0x12345678, 3, 5}; + std::uint32_t frameSize = 0xa1, frameSamples = 0xa2, units = 0xa3, delay = 0xa4; + Require(sceNgs2GetWaveformFrameInfo(&ignored, &frameSize, &frameSamples, &units, &delay) == SCE_NGS2_OK); + Require(frameSize == 4 && frameSamples == 1 && units == 1 && delay == 0); +} + +static void TestFrameInfoErrors() { + const Ngs2WaveformFormat pcm{SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0, 0, 0}; + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + const auto untouched = [&] { return values[0] == 0xa1 && values[1] == 0xa2 && values[2] == 0xa3 && values[3] == 0xa4; }; + Require(sceNgs2GetWaveformFrameInfo(&pcm, nullptr, &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&pcm, &values[0], nullptr, &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&pcm, &values[0], &values[1], nullptr, &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&pcm, &values[0], &values[1], &values[2], nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(nullptr, nullptr, nullptr, nullptr, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(nullptr, &values[0], &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + Require(untouched()); + const Ngs2WaveformFormat invalid[] = { + {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 0, 48000, 0, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 9, 48000, 0, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 0, 0, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 192001, 0, 0, 0}, + {0x99, 0, 48000, 0, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2, 0, 0, 0, 0}, + }; + for (const auto& format : invalid) { + Require(sceNgs2GetWaveformFrameInfo(&format, &values[0], &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + Require(untouched()); + } + for (const std::uint32_t type : {0u, 0x10u, 0x11u, 0x13u, 0x14u, 0x16u, SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 0x19u, 0x41u, 0xffffffffu}) { + Require(FrameInfoThrows({type, 2, 48000, 0, 0, 0})); + } +} + int main() { TestParsePcm(); TestParseFile(); TestParseFileBank(); + TestFrameInfoPcm(); + TestFrameInfoErrors(); return 0; } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Pcm.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Pcm.cpp new file mode 100644 index 0000000000..a22a766f56 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Pcm.cpp @@ -0,0 +1,221 @@ +#include "Ngs2Test.hpp" + +#include +#include +#include +#include + +struct PcmFixture { + std::uint32_t type; + std::uint32_t bytes; + std::vector data; + std::array samples; +}; + +static const PcmFixture Fixtures[] = { + {SCE_NGS2_WAVEFORM_TYPE_PCM_U8, 1, + {0x00, 0x40, 0x60, 0x80, 0xa0, 0xc0, 0xe0, 0xff}, + {-1, -0.5f, -0.25f, 0, 0.25f, 0.5f, 0.75f, 127.0f / 128}}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, + {0x00, 0x80, 0x00, 0xc0, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x60, 0xff, 0x7f}, + {-1, -0.5f, -0.25f, 0, 0.25f, 0.5f, 0.75f, 32767.0f / 32768}}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_I24L, 3, + {0x00, 0x00, 0x80, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x00, 0x60, 0xff, 0xff, 0x7f}, + {-1, -0.5f, -0.25f, 0, 0.25f, 0.5f, 0.75f, 8388607.0f / 8388608}}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_I32L, 4, + {0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0x7f}, + {-1, -0.5f, -0.25f, 0, 0.25f, 0.5f, 0.75f, 1}}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 4, + {0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x80, 0xbe, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x00, 0x80, 0x3f}, + {-1, -0.5f, -0.25f, 0, 0.25f, 0.5f, 0.75f, 1}}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_F64L, 8, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xbf, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x3f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f}, + {-1, -0.5f, -0.25f, 0, 0.25f, 0.5f, 0.75f, 1}}, +}; + +class Playback { +public: + Playback(std::uint32_t type, std::uint32_t channels = 1, std::uint32_t rate = 48000) + : firstBuffer(usedBuffers), system(CreateSystem()), channels(channels), voice(Voice(CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER))) { + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {type, channels, rate, 0, 0, 0}}); + Patch(voice, Mastering(system, channels)); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + } + + ~Playback() { + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + usedBuffers = firstBuffer; + } + + void Add(const void* data, const Ngs2WaveformBlock* blocks, std::uint32_t count, std::uint32_t flags = 0) { + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, data, flags, count, blocks}); + } + + std::vector Render() { + std::vector output(Grain * channels, 123); + const Ngs2RenderBufferInfo info{output.data(), output.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, channels}; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + return output; + } + + Ngs2SamplerVoiceState State() { + Ngs2SamplerVoiceState state{}; + Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK); + return state; + } + + const std::size_t firstBuffer; + const std::uintptr_t system; + const std::uint32_t channels; + const std::uintptr_t voice; +}; + +template +static void RequireThrows(TFunction function) { + bool threw = false; + try { function(); } catch (const std::exception&) { threw = true; } + Require(threw); +} + +static void TestFramesAndPositions(const PcmFixture& fixture, std::uint32_t channels) { + Playback playback(fixture.type, channels); + std::vector data(3, 0xa5); + constexpr std::uint32_t skip = 3; + for (std::uint32_t frame = 0; frame < skip + Grain * 2; frame++) { + for (std::uint32_t channel = 0; channel < channels; channel++) { + const auto sample = (frame + channel) % fixture.samples.size(); + const auto first = fixture.data.begin() + sample * fixture.bytes; + data.insert(data.end(), first, first + fixture.bytes); + } + } + const auto frameBytes = channels * fixture.bytes; + const Ngs2WaveformBlock block{2, data.size() - 3, 0, skip, Grain * 2, 0, 0}; + playback.Add(data.data() + 1, &block, 1); + Control(playback.voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{}); + Require(playback.State().waveform_data == data.data() + 3 + skip * frameBytes); + for (std::uint32_t pass = 0; pass < 2; pass++) { + const auto output = playback.Render(); + for (std::uint32_t frame = 0; frame < Grain; frame++) { + for (std::uint32_t channel = 0; channel < channels; channel++) { + const auto actual = output[frame * channels + channel]; + const auto expected = fixture.samples[(skip + pass * Grain + frame + channel) % 8]; + if (actual != expected) { + std::fprintf(stderr, "PCM type %#x frame %u channel %u: got %a, expected %a\n", fixture.type, pass * Grain + frame, channel, actual, expected); + } + Require(actual == expected); + } + } + const auto state = playback.State(); + Require(state.num_decoded_samples == (pass + 1) * Grain); + Require(state.decoded_data_size == (pass + 1) * Grain * frameBytes); + Require(state.waveform_data == data.data() + 3 + (skip + (pass + 1) * Grain) * frameBytes); + } + Require(Flags(playback.voice) == 0); +} + +static std::vector callbacks; + +static void APS5_VABI OnBlock(const Ngs2VoiceCallbackInfo* info) { + callbacks.push_back(*info); +} + +static void TestRepeatAndTransition(const PcmFixture& fixture) { + Playback playback(fixture.type); + callbacks.clear(); + Control(playback.voice, SCE_NGS2_VOICE_PARAM_CALLBACK, + Ngs2VoiceCallbackParam{{}, OnBlock, 7, SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT | SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0}); + const Ngs2WaveformBlock blocks[] = {{0, fixture.bytes * 4, 1, 1, 3, 0, 101}, {fixture.bytes * 4, fixture.bytes * 4, 0, 0, 4, 0, 202}}; + playback.Add(fixture.data.data(), blocks, 2); + const auto first = playback.Render(); + constexpr std::uint32_t indices[] = {1, 2, 3, 1, 2, 3, 4, 5}; + for (std::uint32_t i = 0; i < Grain; i++) Require(first[i] == fixture.samples[indices[i]]); + auto state = playback.State(); + Require(state.num_decoded_samples == 8 && state.decoded_data_size == 8 * fixture.bytes); + Require(state.waveform_data == fixture.data.data() + 6 * fixture.bytes); + const auto last = playback.Render(); + Require(last[0] == fixture.samples[6] && last[1] == fixture.samples[7]); + for (std::uint32_t i = 2; i < Grain; i++) Require(last[i] == 0); + state = playback.State(); + Require(state.num_decoded_samples == 10 && state.decoded_data_size == 10 * fixture.bytes); + Require(state.waveform_data == fixture.data.data() + fixture.data.size()); + Require(callbacks.size() == 3); + Require(callbacks[0].flag == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT && callbacks[0].user_data == 101 && callbacks[0].num_repeated == 1); + Require(callbacks[1].flag == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END && callbacks[1].user_data == 101); + Require(callbacks[2].flag == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END && callbacks[2].user_data == 202); + Require(callbacks[0].block_data == fixture.data.data() && callbacks[2].block_data == fixture.data.data() + fixture.bytes * 4); +} + +static void TestResampling(const PcmFixture& fixture) { + Playback playback(fixture.type, 1, 24000); + const Ngs2WaveformBlock blocks[] = {{0, fixture.bytes * 3, 0, 0, 3, 0, 0}, {fixture.bytes * 3, fixture.bytes * 5, 0, 0, 5, 0, 0}}; + playback.Add(fixture.data.data(), blocks, 2); + for (std::uint32_t pass = 0; pass < 2; pass++) { + const auto output = playback.Render(); + for (std::uint32_t i = 0; i < Grain; i++) { + const auto sample = (pass * Grain + i) / 2; + const auto next = sample < 7 ? sample + 1 : sample; + const auto expected = i % 2 == 0 ? fixture.samples[sample] : std::lerp(fixture.samples[sample], fixture.samples[next], 0.5f); + Require(output[i] == expected); + } + Require(playback.State().decoded_data_size == (pass + 1) * 4 * fixture.bytes); + } + Require(playback.State().num_decoded_samples == 8); +} + +static void TestPitch(const PcmFixture& fixture) { + Playback playback(fixture.type); + Control(playback.voice, SCE_NGS2_SAMPLER_VOICE_PARAM_PITCH, Ngs2SamplerVoicePitchParam{{}, 2}); + const Ngs2WaveformBlock block{0, fixture.data.size(), 1, 0, 8, 0, 0}; + playback.Add(fixture.data.data(), &block, 1); + const auto output = playback.Render(); + for (std::uint32_t i = 0; i < Grain; i++) Require(output[i] == fixture.samples[(i * 2) % 8]); + Require(playback.State().num_decoded_samples == 16 && playback.State().decoded_data_size == 16 * fixture.bytes); + Require(Flags(playback.voice) == 0); +} + +static void TestBounds(const PcmFixture& fixture) { + Playback playback(fixture.type, 2); + const Ngs2WaveformBlock shortBlock{0, fixture.data.size() - 1, 1, 0, 4, 0, 0}; + RequireThrows([&] { playback.Add(fixture.data.data(), &shortBlock, 1); }); + const Ngs2WaveformBlock overflow{0, fixture.data.size(), 0, UINT32_MAX, UINT32_MAX, 0, 0}; + RequireThrows([&] { playback.Add(fixture.data.data(), &overflow, 1, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET); }); +} + +static void TestNumericEdges() { + const PcmFixture signed24{SCE_NGS2_WAVEFORM_TYPE_PCM_I24L, 3, + {0x01, 0x00, 0x00, 0xff, 0xff, 0xff, 0x01, 0x00, 0x80, 0xfe, 0xff, 0x7f, + 0x56, 0x34, 0x12, 0xaa, 0xcb, 0xed, 0x80, 0x00, 0x00, 0x80, 0xff, 0xff}, + {0x1p-23f, -0x1p-23f, -8388607.0f / 8388608, 8388606.0f / 8388608, + 1193046.0f / 8388608, -1193046.0f / 8388608, 0x1p-16f, -0x1p-16f}}; + TestFramesAndPositions(signed24, 1); + const PcmFixture signed32{SCE_NGS2_WAVEFORM_TYPE_PCM_I32L, 4, + {0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x80, 0xfe, 0xff, 0xff, 0x7f, + 0x01, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x01, 0xfd, 0xff, 0xff, 0xfe}, + {0x1p-31f, -0x1p-31f, -1, 1, 0x1p-7f, 0x1.000002p-7f, 0x1.000004p-7f, -0x1.000004p-7f}}; + TestFramesAndPositions(signed32, 1); + const PcmFixture floating64{SCE_NGS2_WAVEFORM_TYPE_PCM_F64L, 8, + {0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, 0xb9, 0x3f, 0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, 0xb9, 0xbf, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0xe0, 0x3f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xe0, 0x3f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xb8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, + {0.1f, -0.1f, 0.5f, 0x1.000004p-1f, 0x1p-126f, -0x1p-126f, 0, 0}}; + TestFramesAndPositions(floating64, 1); +} + +int main() { + for (const auto& fixture : Fixtures) { + for (const auto channels : {1u, 2u, 6u, 8u}) TestFramesAndPositions(fixture, channels); + TestRepeatAndTransition(fixture); + TestResampling(fixture); + TestPitch(fixture); + TestBounds(fixture); + } + TestNumericEdges(); + return 0; +} diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp index 4186d67946..f053141be3 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp @@ -1,11 +1,15 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include "Ngs2Test.hpp" #include +#include #include #include #include #include #include +#include +#include #include #include #include @@ -238,21 +242,58 @@ static void TestPan() { } const Ngs2PanParam front{0.0f, 1.0f, 1.0f, 0.0f}; - float out[2] = {}; + float out[2] = {-1.0f, -1.0f}; Require(sceNgs2PanInit(&work, nullptr, 360.0f, 2) == SCE_NGS2_OK); Require(sceNgs2PanGetVolumeMatrix(&work, &front, 1, 2, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); - Require(sceNgs2PanGetVolumeMatrix(&work, &front, 0, 2, out) == SCE_NGS2_OK); - Require(PanThrows(nullptr, &front, 1, 2) && PanThrows(&work, nullptr, 0, 2)); - Require(PanThrows(&work, &front, 1, 4) && PanThrows(&work, &front, 1, 3) && !PanThrows(&work, &front, 1, 8)); + Require(sceNgs2PanGetVolumeMatrix(&work, &front, 0, 2, out) == SCE_NGS2_OK && out[0] == -1.0f && out[1] == -1.0f); + Require(SCE_NGS2_ERROR_INVALID_PAN_UNIT_ANGLE == static_cast(0x804A8450) && SCE_NGS2_ERROR_INVALID_PAN_SPEAKER == static_cast(0x804A8451)); + Require(SCE_NGS2_ERROR_INVALID_PAN_MATRIX_FORMAT == static_cast(0x804A8452) && SCE_NGS2_ERROR_INVALID_PAN_WORK == static_cast(0x804A8453)); + Require(SCE_NGS2_ERROR_INVALID_PAN_PARAM == static_cast(0x804A8454)); + float guard[32]; + const auto panResult = [&](Ngs2PanWork* panWork, const Ngs2PanParam* params, std::uint32_t numParams, std::uint32_t format) { + std::fill(std::begin(guard), std::end(guard), -1.0f); + const int result = sceNgs2PanGetVolumeMatrix(panWork, params, numParams, format, guard); + Require(result == SCE_NGS2_OK || std::all_of(std::begin(guard), std::end(guard), [](float level) { return level == -1.0f; })); + return result; + }; const Ngs2PanParam many[9] = {}; - Require(PanThrows(&work, many, 9, 2) && !PanThrows(&work, many, 8, 2)); + Require(panResult(nullptr, &front, 1, 2) == SCE_NGS2_ERROR_INVALID_PAN_WORK && panResult(nullptr, nullptr, 1, 2) == SCE_NGS2_ERROR_INVALID_PAN_WORK); + Require(sceNgs2PanGetVolumeMatrix(nullptr, &front, 1, 2, nullptr) == SCE_NGS2_ERROR_INVALID_PAN_WORK); + Require(panResult(&work, nullptr, 0, 2) == SCE_NGS2_ERROR_INVALID_PAN_PARAM && panResult(&work, nullptr, 1, 3) == SCE_NGS2_ERROR_INVALID_PAN_PARAM); + Require(sceNgs2PanGetVolumeMatrix(&work, nullptr, 1, 2, nullptr) == SCE_NGS2_ERROR_INVALID_PAN_PARAM); + Require(sceNgs2PanGetVolumeMatrix(&work, &front, 1, 3, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(panResult(&work, &front, 1, 4) == SCE_NGS2_ERROR_INVALID_PAN_MATRIX_FORMAT && panResult(&work, &front, 1, 3) == SCE_NGS2_ERROR_INVALID_PAN_MATRIX_FORMAT); + Require(panResult(&work, many, 9, 3) == SCE_NGS2_ERROR_INVALID_PAN_MATRIX_FORMAT && panResult(&work, &front, 1, 8) == SCE_NGS2_OK); + Require(panResult(&work, many, 9, 2) == SCE_NGS2_ERROR_INVALID_NUM_CHANNELS && panResult(&work, many, 8, 2) == SCE_NGS2_OK); + Ngs2PanWork badWork = work; + badWork.num_speakers = 3; + Require(panResult(&badWork, &front, 1, 2) == SCE_NGS2_ERROR_INVALID_PAN_SPEAKER && panResult(&badWork, &front, 1, 3) == SCE_NGS2_ERROR_INVALID_PAN_MATRIX_FORMAT); + Require(panResult(&badWork, many, 9, 2) == SCE_NGS2_ERROR_INVALID_PAN_SPEAKER); + badWork.unit_angle = 100.0f; + Require(panResult(&badWork, &front, 1, 2) == SCE_NGS2_ERROR_INVALID_PAN_SPEAKER); + badWork.num_speakers = 2; + Require(panResult(&badWork, &front, 1, 2) == SCE_NGS2_ERROR_INVALID_PAN_UNIT_ANGLE && panResult(&badWork, many, 9, 2) == SCE_NGS2_ERROR_INVALID_PAN_UNIT_ANGLE); + Require(sceNgs2PanGetVolumeMatrix(&badWork, &front, 1, 2, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); const Ngs2PanParam nanAngle{NAN, 1.0f, 1.0f, 0.0f}; - Require(PanThrows(&work, &nanAngle, 1, 2)); + Require(panResult(&work, &nanAngle, 1, 2) == SCE_NGS2_OK && guard[0] == 0.0f && guard[1] == 0.0f); const Ngs2PanParam laterNan[2] = {front, nanAngle}; - float untouched[4] = {-1.0f, -1.0f, -1.0f, -1.0f}; - bool rejected = false; - try { sceNgs2PanGetVolumeMatrix(&work, laterNan, 2, 2, untouched); } catch (const std::invalid_argument&) { rejected = true; } - Require(rejected && std::all_of(std::begin(untouched), std::end(untouched), [](float level) { return level == -1.0f; })); + Require(panResult(&work, laterNan, 2, 2) == SCE_NGS2_OK); + Require(std::fabs(guard[0] - 0.707106769f) < 1e-6f && guard[1] == 0.0f && std::fabs(guard[2] - 0.707106769f) < 1e-6f && guard[3] == 0.0f); + Ngs2PanWork sevenWork{}; + Require(sceNgs2PanInit(&sevenWork, nullptr, 360.0f, 7) == SCE_NGS2_OK); + const Ngs2PanParam nanEach[4] = {{NAN, 1.0f, 1.0f, 0.5f}, {30.0f, NAN, 1.0f, 0.5f}, {30.0f, 1.0f, NAN, 0.5f}, {30.0f, 1.0f, 1.0f, NAN}}; + Require(panResult(&sevenWork, nanEach, 4, 8) == SCE_NGS2_OK); + for (std::uint32_t channel = 0; channel < 8; channel++) { + Require(guard[channel * 4] == (channel == 3 ? 0.5f : 0.0f)); + for (std::uint32_t p = 1; p < 4; p++) Require(std::isnan(guard[channel * 4 + p])); + } + Ngs2PanWork radianWork{}; + const float radianPair[] = {-0.52359879f, 0.52359879f}; + Require(sceNgs2PanInit(&radianWork, radianPair, 2.0f * pi, 2) == SCE_NGS2_OK && radianWork.speaker_angles[0] == 0.52359879f); + const Ngs2PanParam belowFirst{0.523598731f, 1.0f, 1.0f, 0.0f}; + const Ngs2PanParam aboveFirst{0.52359885f, 1.0f, 1.0f, 0.0f}; + Require(std::nextafter(0.52359879f, 0.0f) == belowFirst.angle && PanThrows(&radianWork, &belowFirst, 1, 1)); + Require(panResult(&radianWork, &aboveFirst, 1, 1) == SCE_NGS2_OK && std::fabs(guard[0] - 0.707106769f) < 1e-6f); const Ngs2PanParam huge{1e30f, 1.0f, 1.0f, 0.0f}; Require(PanThrows(&work, &huge, 1, 2)); const float hugeSpeaker[] = {1e30f, 30.0f}; @@ -261,18 +302,67 @@ static void TestPan() { Require(sceNgs2PanInit(&work, nullptr, 360.0f, 2) == SCE_NGS2_OK); work.speaker_angles[0] = -INFINITY; Require(PanThrows(&work, &front, 1, 2)); + work.speaker_angles[0] = NAN; + const Ngs2PanParam right{30.0f, 1.0f, 1.0f, 0.0f}; + Require(panResult(&work, &right, 1, 2) == SCE_NGS2_OK && guard[0] == 0.0f && guard[1] == 0.0f); Require(sceNgs2PanInit(&work, nullptr, 360.0f, 2) == SCE_NGS2_OK); - Require(PanInitThrows(nullptr, nullptr, 360.0f, 2)); - for (std::uint32_t count : {0u, 1u, 3u, 6u, 8u}) Require(PanInitThrows(&work, nullptr, 360.0f, count)); + Ngs2PanWork marked; + std::memset(&marked, 0x5a, sizeof(marked)); + Ngs2PanWork expected; + std::memcpy(&expected, &marked, sizeof(marked)); + const auto unchanged = [&] { return std::memcmp(&marked, &expected, sizeof(marked)) == 0; }; const float pair[] = {-30.0f, 30.0f}; - Require(PanInitThrows(&work, pair, 180.0f, 2) && !PanInitThrows(&work, pair, 2.0f * pi, 2)); - const float invalid[] = {0.0f, NAN}; - Require(PanInitThrows(&work, invalid, 360.0f, 2)); + Require(sceNgs2PanInit(nullptr, nullptr, 360.0f, 2) == static_cast(0x804A8010)); + Require(sceNgs2PanInit(nullptr, pair, 180.0f, 3) == static_cast(0x804A8010)); + Require(sceNgs2PanInit(nullptr, nullptr, 100.0f, 3) == static_cast(0x804A8010)); + for (std::uint32_t count : {1u, 3u, 6u, 8u, 255u}) { + Require(sceNgs2PanInit(&marked, nullptr, 360.0f, count) == static_cast(0x804A8451)); + Require(unchanged()); + } + const float sevenDegrees[] = {0.0f, 40.0f, 110.0f, 150.0f, 210.0f, 250.0f, 320.0f, 360.0f}; + for (const float unitAngle : {360.0f, 2.0f * pi, 180.0f, NAN}) { + Ngs2PanWork seven; + std::memset(&seven, 0x5a, sizeof(seven)); + Require(sceNgs2PanInit(&seven, nullptr, unitAngle, 0) == SCE_NGS2_OK && seven.num_speakers == 7); + Require(seven.unit_angle == (unitAngle == 2.0f * pi ? 2.0f * pi : 360.0f)); + for (std::uint32_t i = 0; i < 8; i++) Require(seven.speaker_angles[i] == sevenDegrees[i]); + } + for (std::uint32_t count : {0u, 1u, 3u, 6u, 8u, 255u}) { + Require(sceNgs2PanInit(&marked, pair, 360.0f, count) == static_cast(0x804A8451)); + Require(sceNgs2PanInit(&marked, pair, 2.0f * pi, count) == static_cast(0x804A8451)); + Require(unchanged()); + } + for (const float unitAngle : {180.0f, 0.0f, 359.0f, 361.0f, 90.0f, pi}) { + Require(sceNgs2PanInit(&marked, pair, unitAngle, 2) == static_cast(0x804A8450)); + Require(sceNgs2PanInit(&marked, pair, unitAngle, 3) == static_cast(0x804A8450)); + Require(unchanged()); + } + Require(sceNgs2PanInit(&work, pair, 2.0f * pi, 2) == SCE_NGS2_OK); + const float nanSpeaker[] = {0.0f, NAN}; + Require(sceNgs2PanInit(&work, nanSpeaker, 360.0f, 2) == SCE_NGS2_OK && work.num_speakers == 2 && work.unit_angle == 360.0f); + Require(std::isnan(work.speaker_angles[0]) && work.speaker_angles[1] == 0.0f && std::isnan(work.speaker_angles[2])); const float onUnit[] = {720.0f, 30.0f}; const float belowZero[] = {-360.0f, 30.0f}; Require(PanInitThrows(&work, onUnit, 360.0f, 2) && !PanInitThrows(&work, belowZero, 360.0f, 2)); work.unit_angle = 100.0f; - Require(PanThrows(&work, &front, 1, 2)); + Require(panResult(&work, &front, 1, 2) == SCE_NGS2_ERROR_INVALID_PAN_UNIT_ANGLE); +} + +static void TestPanExplicitAnglesAndNanDistance() { + Ngs2PanWork work; + std::memset(&work, 0x5a, sizeof(work)); + Ngs2PanWork filled; + std::memcpy(&filled, &work, sizeof(work)); + const float pair[] = {-30.0f, 30.0f}; + Require(sceNgs2PanInit(&work, pair, 360.0f, 2) == SCE_NGS2_OK && work.num_speakers == 2 && work.unit_angle == 360.0f); + Require(work.speaker_angles[0] == 30.0f && work.speaker_angles[1] == 330.0f && work.speaker_angles[2] == 390.0f); + Require(std::memcmp(&work.speaker_angles[3], &filled.speaker_angles[3], 5 * sizeof(float)) == 0); + Require(sceNgs2PanInit(&work, nullptr, 360.0f, 2) == SCE_NGS2_OK); + for (std::uint32_t i = 3; i < 8; i++) Require(work.speaker_angles[i] == 0.0f); + const Ngs2PanParam params[2] = {{0.0f, 1.0f, 1.0f, 0.0f}, {30.0f, NAN, 1.0f, 0.0f}}; + float out[4] = {}; + Require(sceNgs2PanGetVolumeMatrix(&work, params, 2, 2, out) == SCE_NGS2_OK); + Require(std::fabs(out[0] - 0.707106769f) < 1e-6f && std::isnan(out[1]) && std::fabs(out[2] - 0.707106769f) < 1e-6f && std::isnan(out[3])); } static void TestPitchAndRepeat() { @@ -663,6 +753,73 @@ static void TestReverb() { } } +static std::vector RenderReverbChannels(std::uint32_t inputs, std::uint32_t outputs, const Ngs2ReverbI3DL2Param& params, const std::vector& pcm, std::uint32_t frames) { + const auto firstBuffer = usedBuffers; + const auto system = CreateSystem(); + Require(sceNgs2SystemSetGrainSamples(system, 256) == SCE_NGS2_OK); + const auto master = Mastering(system, outputs); + const auto reverb = Voice(CreateRack(system, SCE_NGS2_RACK_ID_REVERB)); + Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, inputs, outputs, 0, 0}); + Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_I3DL2, Ngs2ReverbVoiceI3DL2Param{{}, params}); + Patch(reverb, master); + Event(reverb, SCE_NGS2_VOICE_EVENT_PLAY); + const auto sampler = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER)); + Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, inputs, 48000, 0, 0, 0}}); + const Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), 0, 0, static_cast(pcm.size() / inputs), 0, 0}; + Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block}); + Patch(sampler, reverb); + std::vector identity(inputs * inputs, 0.0f); + for (std::uint32_t c = 0; c < inputs; c++) identity[c * inputs + c] = 1.0f; + Control(sampler, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, inputs * inputs, identity.data()}); + Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0}); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + std::vector all; + std::vector out(256 * outputs); + for (std::uint32_t grain = 0; grain * 256 < frames; grain++) { + const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, outputs}; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + all.insert(all.end(), out.begin(), out.end()); + } + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + usedBuffers = firstBuffer; + return all; +} + +static void TestReverbConversion() { + Ngs2ReverbI3DL2Param mixed{1.0f, 1.0f, -1000, 0, 0, 2.91f, 1.3f, -602, 0.015f, -302, 0.022f, 100.0f, 100.0f, 5000.0f, {}}; + std::vector stereo(4096 * 2, 0); + std::uint32_t seed = 1; + for (std::uint32_t i = 0; i < 3000 * 2; i++) { + seed = seed * 1664525u + 1013904223u; + stereo[i] = static_cast(static_cast((seed >> 16) & 0x3fff) - 0x2000); + } + const auto same = RenderReverbChannels(2, 2, mixed, stereo, 8192); + for (std::uint32_t outputs : {6u, 8u}) { + const auto surround = RenderReverbChannels(2, outputs, mixed, stereo, 8192); + float sides = 0.0f; + float lfe = 0.0f; + for (std::uint32_t frame = 0; frame < 8192; frame++) { + for (std::uint32_t c = 0; c < 2; c++) Require(std::fabs(surround[frame * outputs + c] - same[frame * 2 + c]) <= 1e-7f + std::fabs(same[frame * 2 + c]) * 1e-5f); + for (std::uint32_t c = 2; c < outputs; c++) { + if (c != 3) sides = std::max(sides, std::fabs(surround[frame * outputs + c])); + } + lfe = std::max(lfe, std::fabs(surround[frame * outputs + 3])); + } + Require(sides > 1e-4f && lfe == 0.0f); + } + + auto dry = mixed; + dry.wet = 0.0f; + const std::int16_t levels[6] = {8192, 4096, 2048, 1024, 512, 256}; + std::vector six(4096 * 6); + for (std::size_t i = 0; i < six.size(); i++) six[i] = levels[i % 6]; + const auto down = RenderReverbChannels(6, 2, dry, six, 2048); + const float half = std::numbers::sqrt2_v / 2.0f; + const float left = (8192.0f + 2048.0f * half + 512.0f * 0.5f) / 32768.0f; + const float right = (4096.0f + 2048.0f * half + 256.0f * 0.5f) / 32768.0f; + Require(std::fabs(down[1024 * 2] - left) <= left * 1e-5f && std::fabs(down[1024 * 2 + 1] - right) <= right * 1e-5f); +} + static void TestReverbSetup() { const auto system = CreateSystem(); const auto master = Mastering(system, 2); @@ -672,12 +829,12 @@ static void TestReverbSetup() { try { Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, channels, channels, 0, 0}); } catch (const std::invalid_argument&) { rejected = true; } Require(rejected); } - bool conversion = false; - try { Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, 1, 2, 0, 0}); } catch (const std::runtime_error&) { conversion = true; } - Require(conversion); - bool flags = false; - try { Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, 2, 2, 1, 0}); } catch (const std::runtime_error&) { flags = true; } - Require(flags); + for (const auto& counts : {std::array{3, 2}, std::array{2, 7}}) { + bool rejected = false; + try { Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, counts[0], counts[1], 0, 0}); } catch (const std::invalid_argument&) { rejected = true; } + Require(rejected); + } + Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, 1, 2, 0, 0}); Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, 2, 2, 0, 0}); Ngs2ReverbI3DL2Param invalid{1.0f, 0.0f, 0, 0, 0, 1.0f, 1.0f, 0, 0.0f, 0, 0.0f, 100.0f, 100.0f, 5000.0f, {}}; invalid.decay_time = NAN; @@ -700,10 +857,7 @@ static void TestReverbSetup() { Require(loudest > 0.0f && loudest < 1e-8f); Ngs2VoiceState state{}; Require(sceNgs2VoiceGetState(reverb, &state, sizeof(state)) == SCE_NGS2_OK && (state.state_flags & SCE_NGS2_VOICE_STATE_FLAG_INUSE) != 0); - Require(sceNgs2SystemSetSampleRate(system, 7000) == SCE_NGS2_OK); - bool lowRate = false; - try { sceNgs2SystemRender(system, &info, 1); } catch (const std::runtime_error&) { lowRate = true; } - Require(lowRate); + Require(sceNgs2SystemSetSampleRate(system, 7000) == SCE_NGS2_ERROR_INVALID_SAMPLE_RATE); Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); Ngs2ReverbRackOption option{}; @@ -728,13 +882,7 @@ static void TestReverbSetup() { static void TestSampleRate() { const auto system = CreateSystem(); Require(sceNgs2SystemSetSampleRate(0x1234, 96000) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE); - bool threw = false; - try { - sceNgs2SystemSetSampleRate(system, 0); - } catch (const std::invalid_argument&) { - threw = true; - } - Require(threw); + Require(sceNgs2SystemSetSampleRate(system, 0) == SCE_NGS2_ERROR_INVALID_SAMPLE_RATE); Require(sceNgs2SystemSetSampleRate(system, 96000) == SCE_NGS2_OK); Ngs2SystemInfo info{}; Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_OK && info.sample_rate == 96000); @@ -750,6 +898,42 @@ static void TestSampleRate() { Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } +static bool SystemOptionThrows(const Ngs2SystemOption& option) { + Ngs2ContextBufferInfo query{}; + try { + sceNgs2SystemQueryBufferSize(&option, &query); + + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +static void TestSystemOptionFlags() { + Ngs2SystemOption option{}; + Require(sceNgs2SystemResetOption(&option) == SCE_NGS2_OK && option.flags == 0); + option.flags = 0x2; + Ngs2ContextBufferInfo query{}; + Require(sceNgs2SystemQueryBufferSize(&option, &query) == SCE_NGS2_OK && query.host_buffer_size != 0); + const auto info = Buffer(query); + uintptr_t system = 0; + Require(sceNgs2SystemCreate(&option, &info, &system) == SCE_NGS2_OK && system != 0); + Require(sceNgs2SystemSetGrainSamples(system, Grain) == SCE_NGS2_OK); + const auto master = Mastering(system, 1); + const std::vector pcm{1000, 2000, 3000, 4000}; + const auto sampler = Sampler(system, pcm, 1); + Patch(sampler, master); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + const auto out = RenderI16(system); + Require(std::equal(pcm.begin(), pcm.end(), out.begin())); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + + for (const std::uint32_t flags : {0x1u, 0x3u, 0x4u, 0x80000000u, 0x80000002u}) { + option.flags = flags; + Require(SystemOptionThrows(option)); + } +} + static void TestUserData() { uintptr_t value = 1; Require(sceNgs2SystemSetUserData(0x1234, 5) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE); @@ -761,6 +945,7 @@ static void TestUserData() { bool threw = false; try { sceNgs2SystemGetUserData(system, nullptr); + } catch (const std::invalid_argument&) { threw = true; } @@ -784,6 +969,7 @@ static void TestUserData() { threw = false; try { sceNgs2RackGetUserData(rack, nullptr); + } catch (const std::invalid_argument&) { threw = true; } @@ -823,9 +1009,6 @@ static void TestMasteringGain() { Control(toQuad, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0}); Patch(toQuad, quad); Event(toQuad, SCE_NGS2_VOICE_EVENT_PLAY); - bool rejected = false; - try { Control(stereo, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, NAN, 1.0f}); } catch (const std::exception&) { rejected = true; } - Require(rejected); std::vector outStereo(Grain * 2, -1.0f); std::vector outSurround(Grain * 6, -1.0f); @@ -859,6 +1042,80 @@ static void TestMasteringGain() { Require(sceNgs2SystemDestroy(loudSystem, nullptr) == SCE_NGS2_OK); } +static void TestMasteringGainClampedLikePs5() { + std::deque> waveforms; + const auto chain = [&waveforms](std::uint32_t channels, std::uint32_t grains, uintptr_t& master) { + const auto system = CreateSystem(); + master = Mastering(system, channels); + const auto& pcm = waveforms.emplace_back(Grain * grains, 16384); + const auto source = Sampler(system, pcm, 0); + const std::vector levels(channels, 1.0f); + Control(source, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, channels, levels.data()}); + Control(source, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0}); + Patch(source, master); + Event(source, SCE_NGS2_VOICE_EVENT_PLAY); + return system; + }; + const auto render = [](uintptr_t system, std::uint32_t channels) { + std::vector out(Grain * channels, -1.0f); + const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, channels}; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + return out; + }; + const auto expect = [](const std::vector& out, std::uint32_t channels, float fbwOut, float lfeOut) { + for (std::uint32_t i = 0; i < Grain; i++) { + for (std::uint32_t channel = 0; channel < channels; channel++) { + const float level = channel == 3 && channels >= 6 ? lfeOut : fbwOut; + const float sample = out[i * channels + channel]; + Require(std::isnan(level) ? std::isnan(sample) : sample == 0.5f * level); + } + } + }; + const auto gain = [&](std::uint32_t channels, float fbw, float lfe, float fbwOut, float lfeOut) { + uintptr_t master = 0; + const auto system = chain(channels, 1, master); + Control(master, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, fbw, lfe}); + expect(render(system, channels), channels, fbwOut, lfeOut); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + }; + const float inf = INFINITY; + for (const std::uint32_t channels : {6u, 8u}) { + gain(channels, 32.0f, 1.0f, 32.0f, 1.0f); + gain(channels, 33.0f, 1.0f, 32.0f, 1.0f); + gain(channels, 64.0f, 1.0f, 32.0f, 1.0f); + gain(channels, 1e30f, 1.0f, 32.0f, 1.0f); + gain(channels, inf, 1.0f, 32.0f, 1.0f); + gain(channels, -32.0f, 1.0f, -32.0f, 1.0f); + gain(channels, -33.0f, 1.0f, -32.0f, 1.0f); + gain(channels, -inf, 1.0f, -32.0f, 1.0f); + gain(channels, NAN, 1.0f, NAN, 1.0f); + gain(channels, 1.0f, 32.0f, 1.0f, 32.0f); + gain(channels, 1.0f, 33.0f, 1.0f, 32.0f); + gain(channels, 1.0f, 1e30f, 1.0f, 32.0f); + gain(channels, 1.0f, inf, 1.0f, 32.0f); + gain(channels, 1.0f, -33.0f, 1.0f, -32.0f); + gain(channels, 1.0f, -inf, 1.0f, -32.0f); + gain(channels, 1.0f, NAN, 1.0f, NAN); + } + gain(1, 0.5f, 2.0f, 0.5f, 0.5f); + gain(1, inf, NAN, 32.0f, 32.0f); + gain(2, -inf, 1e30f, -32.0f, -32.0f); + gain(2, 2.0f, 0.5f, 2.0f, 2.0f); + + uintptr_t master = 0; + const auto system = chain(6, 4, master); + expect(render(system, 6), 6, 1.0f, 1.0f); + Control(master, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 0.5f, 0.25f}); + expect(render(system, 6), 6, 0.5f, 0.25f); + Event(master, SCE_NGS2_VOICE_EVENT_STOP_IMM); + Event(master, SCE_NGS2_VOICE_EVENT_PLAY); + expect(render(system, 6), 6, 0.5f, 0.25f); + Control(master, SCE_NGS2_MASTERING_VOICE_PARAM_SETUP, Ngs2MasteringVoiceSetupParam{{}, 6, 0}); + Event(master, SCE_NGS2_VOICE_EVENT_PLAY); + expect(render(system, 6), 6, 1.0f, 1.0f); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + static uintptr_t RackVoice(uintptr_t rack, std::uint32_t index) { uintptr_t voice = 0; Require(sceNgs2RackGetVoiceHandle(rack, index, &voice) == SCE_NGS2_OK && voice != 0); @@ -931,6 +1188,7 @@ static void TestStereoIntoSurround() { bool threw = false; try { sceNgs2SystemRender(system, info, 3); + } catch (const std::runtime_error&) { threw = true; } @@ -1407,6 +1665,81 @@ static void TestRenderCapacity(std::uint32_t waveformType, std::uint32_t channel Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } +static void TestRenderBufferCount() { + const auto system = CreateSystem(); + std::vector out(Grain); + Ngs2RenderBufferInfo infos[32]; + for (int i = 0; i < 32; i++) { + infos[i] = Ngs2RenderBufferInfo{out.data(), out.size() * sizeof(int16_t), SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1}; + } + Require(sceNgs2SystemRender(system, infos, 31) == SCE_NGS2_OK); + Require(sceNgs2SystemRender(system, infos, 32) == SCE_NGS2_ERROR_INVALID_NUM_RENDER_BUFFERS); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestRenderBufferTypeValidation() { + const auto system = CreateSystem(); + std::vector out(Grain); + Ngs2RenderBufferInfo info0{out.data(), out.size() * sizeof(int16_t), 0x50, 1}; + Require(sceNgs2SystemRender(system, &info0, 1) == SCE_NGS2_ERROR_INVALID_WAVEFORM_TYPE); + Ngs2RenderBufferInfo valid{out.data(), out.size() * sizeof(int16_t), SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1}; + Ngs2RenderBufferInfo infos[2]; + infos[0] = valid; + infos[1] = Ngs2RenderBufferInfo{out.data(), out.size() * sizeof(int16_t), 0x50, 1}; + Require(sceNgs2SystemRender(system, infos, 2) == SCE_NGS2_ERROR_INVALID_WAVEFORM_TYPE); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestRenderBufferTypeBeforeNull() { + const auto system = CreateSystem(); + std::vector out(Grain); + Ngs2RenderBufferInfo info{nullptr, out.size() * sizeof(int16_t), 0x50, 1}; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_ERROR_INVALID_WAVEFORM_TYPE); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestRenderBufferNull() { + const auto system = CreateSystem(); + std::vector out(Grain); + Ngs2RenderBufferInfo info{nullptr, out.size() * sizeof(int16_t), SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1}; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_ERROR_INVALID_RENDER_BUFFER); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestRenderRefusedDoesNotRender() { + const auto system = CreateSystem(); + const auto master = Mastering(system, 1); + std::vector pcm(Grain, 16384); + const auto sampler = Sampler(system, pcm, 0); + Patch(sampler, master); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + std::vector out(Grain * 2, 123); + Ngs2RenderBufferInfo infos[2]; + infos[0] = Ngs2RenderBufferInfo{out.data(), out.size() * sizeof(int16_t), SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1}; + infos[1] = Ngs2RenderBufferInfo{out.data() + Grain, out.size() * sizeof(int16_t), 0x50, 1}; + Require(sceNgs2SystemRender(system, infos, 2) == SCE_NGS2_ERROR_INVALID_WAVEFORM_TYPE); + Require(std::all_of(out.begin(), out.end(), [](int16_t value) { return value == 123; })); + Ngs2SamplerVoiceState state{}; + Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK); + Require(state.num_decoded_samples == 0); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestRenderBigEndianThrows() { + const auto system = CreateSystem(); + std::vector out(Grain); + Ngs2RenderBufferInfo infoI16B{out.data(), out.size() * sizeof(int16_t), SCE_NGS2_WAVEFORM_TYPE_PCM_I16B, 1}; + bool i16bThrows = false; + try { sceNgs2SystemRender(system, &infoI16B, 1); } catch (const std::runtime_error&) { i16bThrows = true; } + Require(i16bThrows); + std::vector outF(Grain); + Ngs2RenderBufferInfo infoF32B{outF.data(), outF.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32B, 1}; + bool f32bThrows = false; + try { sceNgs2SystemRender(system, &infoF32B, 1); } catch (const std::runtime_error&) { f32bThrows = true; } + Require(f32bThrows); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + static void TestSilentRenderValidation() { const auto system = CreateSystem(); std::vector out(Grain, 123.0f); @@ -1425,12 +1758,12 @@ static void TestSilentRenderValidation() { invalid(); info.num_channels = 1; info.waveform_type = UINT32_MAX; - bool unsupported = false; - try { sceNgs2SystemRender(system, &info, 1); } catch (const std::runtime_error&) { unsupported = true; } - Require(unsupported); + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_ERROR_INVALID_WAVEFORM_TYPE); + Require(std::all_of(out.begin(), out.end(), [](float value) { return value == 123.0f; })); + info.waveform_type = SCE_NGS2_WAVEFORM_TYPE_PCM_F32L; + info.buffer = nullptr; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_ERROR_INVALID_RENDER_BUFFER); Require(std::all_of(out.begin(), out.end(), [](float value) { return value == 123.0f; })); - info = {}; - Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } @@ -1461,11 +1794,17 @@ int main() { TestPcmBlockEnd(); TestPatchToRackHandle(); TestPan(); + TestPanExplicitAnglesAndNanDistance(); TestPitchAndRepeat(); TestSubmixerMatrix(); TestReverb(); TestReverbSetup(); + TestReverbConversion(); TestSampleRate(); + + TestMasteringGainClampedLikePs5(); + + TestSystemOptionFlags(); TestUserData(); TestPcmCallbackReset(); TestPcmCallbackRefill(); @@ -1488,6 +1827,12 @@ int main() { TestRenderCapacity(SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2); TestRenderCapacity(SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1); TestRenderCapacity(SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2); + TestRenderBufferCount(); + TestRenderBufferTypeValidation(); + TestRenderBufferTypeBeforeNull(); + TestRenderBufferNull(); + TestRenderRefusedDoesNotRender(); + TestRenderBigEndianThrows(); TestSilentRenderValidation(); TestAllocator(); exitSystem = CreateSystem(); diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp index 95252adde2..e1c5907df4 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp @@ -6,12 +6,14 @@ #include #include #include +#include #include extern "C" { int APS5_VABI sceNgs2ParseWaveformData(const void*, size_t, Ngs2WaveformInfo*); int APS5_VABI sceNgs2ParseWaveformFile(const char*, uint32_t, Ngs2WaveformInfo*); int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat*, uint32_t, uint32_t, Ngs2WaveformBlock*); +int APS5_VABI sceNgs2GetWaveformFrameInfo(const Ngs2WaveformFormat*, uint32_t*, uint32_t*, uint32_t*, uint32_t*); int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption*); int APS5_VABI sceNgs2SystemQueryBufferSize(const Ngs2SystemOption*, Ngs2ContextBufferInfo*); int APS5_VABI sceNgs2SystemCreate(const Ngs2SystemOption*, const Ngs2ContextBufferInfo*, uintptr_t*); @@ -42,6 +44,8 @@ int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t, const Ngs2VoiceCommand*, size_t int APS5_VABI sceNgs2VoiceGetState(uintptr_t, Ngs2VoiceState*, size_t); int APS5_VABI sceNgs2VoiceGetStateFlags(uintptr_t, uint32_t*); int APS5_VABI sceNgs2VoiceGetOwner(uintptr_t, uintptr_t*, uint32_t*); +int APS5_VABI sceNgs2VoiceGetMatrixInfo(uintptr_t, uint32_t, Ngs2VoiceMatrixInfo*, size_t); +int APS5_VABI sceNgs2CustomRackGetModuleInfo(uintptr_t, uint32_t, Ngs2CustomModuleInfo*, size_t); int APS5_VABI sceNgs2VoiceGetPortInfo(uintptr_t, uint32_t, Ngs2VoicePortInfo*, size_t); int APS5_VABI sceNgs2VoiceQueryInfo(uintptr_t, uint32_t, void*, size_t); int APS5_VABI sceNgs2GeomResetListenerParam(Ngs2GeomListenerParam*); @@ -60,6 +64,18 @@ inline void Check(bool value, int line) { } #define Require(value) Check((value), __LINE__) +inline bool FrameInfoThrows(const Ngs2WaveformFormat& format) { + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + bool threw = false; + try { + sceNgs2GetWaveformFrameInfo(&format, &values[0], &values[1], &values[2], &values[3]); + } catch (const std::exception&) { + threw = true; + } + Require(values[0] == 0xa1 && values[1] == 0xa2 && values[2] == 0xa3 && values[3] == 0xa4); + return threw; +} + inline constexpr std::uint32_t Grain = 8; inline std::vector> buffers; diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceEvents.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceEvents.cpp new file mode 100644 index 0000000000..da4b91132b --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceEvents.cpp @@ -0,0 +1,103 @@ +#include "Ngs2Test.hpp" + +#include +#include + +static const Ngs2SamplerVoiceSetupParam SamplerSetup{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1, 48000, 0, 0, 0}}; + +static Ngs2VoiceEventParam PlayParam() { + return {{sizeof(Ngs2VoiceEventParam), 0, SCE_NGS2_VOICE_PARAM_EVENT}, SCE_NGS2_VOICE_EVENT_PLAY}; +} + +static uintptr_t Sampler(uintptr_t system) { + const auto master = Mastering(system, 1); + const auto voice = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER)); + Patch(voice, master); + return voice; +} + +static void TestPlayWithoutSetup() { + const auto system = CreateSystem(); + const auto voice = Sampler(system); + const auto play = PlayParam(); + const Ngs2VoiceCommand playCommand{2, 0, 4, 0, {.u = SCE_NGS2_VOICE_EVENT_PLAY}}; + Event(voice, SCE_NGS2_VOICE_EVENT_STOP_IMM); + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, nullptr, 0, 0, nullptr}); + Require(sceNgs2VoiceControl(voice, &play.header) == SCE_NGS2_ERROR_INVALID_VOICE_ACCESS); + Require(sceNgs2VoiceRunCommands(voice, &playCommand, 1) == SCE_NGS2_ERROR_INVALID_VOICE_ACCESS); + + std::vector out(Grain, -1.0f); + const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1}; + for (int grain = 0; grain < 3; grain++) { + Require(Flags(voice) == 0); + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + for (float sample : out) Require(sample == 0.0f); + } + Require(Flags(voice) == 0); + + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, SamplerSetup); + Require(sceNgs2VoiceRunCommands(voice, &playCommand, 1) == SCE_NGS2_OK); + Require(Flags(voice) == SCE_NGS2_VOICE_STATE_FLAG_INUSE); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestEmptyFormatKeepsTheSetup() { + const auto system = CreateSystem(); + const auto voice = Sampler(system); + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, SamplerSetup); + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{}); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + Require(Flags(voice) == SCE_NGS2_VOICE_STATE_FLAG_INUSE); + + const auto bare = Sampler(system); + Control(bare, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{}); + const auto play = PlayParam(); + Require(sceNgs2VoiceControl(bare, &play.header) == SCE_NGS2_ERROR_INVALID_VOICE_ACCESS); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestPlayAfterSetupInOneList() { + const auto system = CreateSystem(); + const auto voice = Sampler(system); + struct List { + Ngs2SamplerVoiceSetupParam setup; + Ngs2VoiceEventParam play; + }; + List list{SamplerSetup, PlayParam()}; + list.setup.header = {sizeof(list.setup), static_cast(sizeof(list.setup)), SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP}; + Require(sceNgs2VoiceControl(voice, &list.setup.header) == SCE_NGS2_OK); + Require(Flags(voice) == SCE_NGS2_VOICE_STATE_FLAG_INUSE); + + const auto other = Sampler(system); + struct Reversed { + Ngs2VoiceEventParam play; + Ngs2SamplerVoiceSetupParam setup; + }; + Reversed reversed{PlayParam(), SamplerSetup}; + reversed.play.header = {sizeof(reversed.play), static_cast(sizeof(reversed.play)), SCE_NGS2_VOICE_PARAM_EVENT}; + reversed.setup.header = {sizeof(reversed.setup), 0, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP}; + Require(sceNgs2VoiceControl(other, &reversed.play.header) == SCE_NGS2_ERROR_INVALID_VOICE_ACCESS); + Require(Flags(other) == 0); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestNeverSetUpVoicesOfOtherRacks() { + const auto system = CreateSystem(); + const auto play = PlayParam(); + for (const auto rackId : {SCE_NGS2_RACK_ID_SUBMIXER, SCE_NGS2_RACK_ID_REVERB, SCE_NGS2_RACK_ID_MASTERING}) { + const auto voice = Voice(CreateRack(system, rackId)); + Require(sceNgs2VoiceControl(voice, &play.header) == SCE_NGS2_ERROR_INVALID_VOICE_ACCESS); + Require(Flags(voice) == 0); + } + const auto master = Mastering(system, 1); + Require(Flags(master) == SCE_NGS2_VOICE_STATE_FLAG_INUSE); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +int main() { + TestPlayWithoutSetup(); + TestEmptyFormatKeepsTheSetup(); + TestPlayAfterSetupInOneList(); + TestNeverSetUpVoicesOfOtherRacks(); + return 0; +} diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp index c5fed18835..7d525001c1 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" #include "Ngs2Test.hpp" #include @@ -35,6 +36,55 @@ static void Invalid(std::string_view api, uintptr_t handle) { Require(std::all_of(output.begin(), output.end(), [](auto byte) { return byte == 0xa5; })); } +template +static bool MatrixInfoThrows(uintptr_t voice, std::uint32_t matrixId) { + Ngs2VoiceMatrixInfo info; + std::memset(&info, 0xa5, sizeof(info)); + bool threw = false; + try { + sceNgs2VoiceGetMatrixInfo(voice, matrixId, &info, sizeof(info)); + + } catch (const TException&) { + threw = true; + } + const auto* bytes = reinterpret_cast(&info); + Require(std::all_of(bytes, bytes + sizeof(info), [](auto byte) { return byte == 0xa5; })); + return threw; +} + +static void MatrixInfo() { + Ngs2VoiceMatrixInfo info; + std::memset(&info, 0xa5, sizeof(info)); + Status(sceNgs2VoiceGetMatrixInfo(0x1234, 0, &info, sizeof(info)), InvalidVoice, "GetMatrixInfo accepted an invalid voice"); + const auto* bytes = reinterpret_cast(&info); + Require(std::all_of(bytes, bytes + sizeof(info), [](auto byte) { return byte == 0xa5; })); + Status(sceNgs2VoiceGetMatrixInfo(0x1234, 0, nullptr, sizeof(info)), SCE_NGS2_ERROR_INVALID_OUT_ADDRESS, "GetMatrixInfo checked the voice before null output"); + Status(sceNgs2VoiceGetMatrixInfo(0x1234, 0, &info, sizeof(info) - 1), SCE_NGS2_ERROR_INVALID_OUT_SIZE, "GetMatrixInfo checked the voice before the output size"); + const auto system = CreateSystem(); + const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER); + const auto voice = Voice(rack); + Status(sceNgs2VoiceGetMatrixInfo(voice, 0, &info, sizeof(info)), SCE_NGS2_OK, "GetMatrixInfo failed for a matrix without levels"); + Require(info.num_levels == 0 && std::all_of(std::begin(info.levels), std::end(info.levels), [](float level) { return level == 0.0f; })); + const float levels[4] = {1.0f, 0.5f, -5.0f, 6.0f}; + Control(voice, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 4, levels}); + std::memset(&info, 0xa5, sizeof(info)); + Status(sceNgs2VoiceGetMatrixInfo(voice, 0, &info, sizeof(info)), SCE_NGS2_OK, "GetMatrixInfo failed for a matrix with levels"); + Require(info.num_levels == 4 && info.levels[0] == 1.0f && info.levels[1] == 0.5f && info.levels[2] == -4.0f && info.levels[3] == 4.0f); + Require(std::all_of(std::begin(info.levels) + 4, std::end(info.levels), [](float level) { return level == 0.0f; })); + std::array full{}; + for (std::size_t i = 0; i < full.size(); i++) full[i] = static_cast(i) / 64.0f; + Control(voice, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 64, full.data()}); + Status(sceNgs2VoiceGetMatrixInfo(voice, 0, &info, sizeof(info)), SCE_NGS2_OK, "GetMatrixInfo failed for 64 levels"); + Require(info.num_levels == 64 && std::equal(full.begin(), full.end(), info.levels)); + Status(sceNgs2VoiceGetMatrixInfo(voice, 0, &info, sizeof(info) + 1), SCE_NGS2_ERROR_INVALID_OUT_SIZE, "GetMatrixInfo accepted a larger output"); + Require(MatrixInfoThrows(voice, 1)); + const auto master = Voice(CreateRack(system, SCE_NGS2_RACK_ID_MASTERING)); + Require(MatrixInfoThrows(master, 0)); + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + Status(sceNgs2VoiceGetMatrixInfo(voice, 0, &info, sizeof(info)), InvalidVoice, "GetMatrixInfo accepted a voice of a destroyed rack"); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + int main(int argc, char** argv) { try { if (argc > 2 || (argc == 2 && std::find(Names.begin(), Names.end(), std::string_view(argv[1])) == Names.end())) throw std::runtime_error("unknown voice API test selection"); @@ -80,6 +130,7 @@ int main(int argc, char** argv) { Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); for (auto name : Names) if (selected(name)) Invalid(name, voice); Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + if (argc == 1) MatrixInfo(); std::cout << "NGS2 invalid voice errors, outputs and precedence checked\n"; return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceNgs2/CMakeLists.txt b/core/libs/prx/libSceNgs2/CMakeLists.txt index 41b6cfe96a..116315109b 100644 --- a/core/libs/prx/libSceNgs2/CMakeLists.txt +++ b/core/libs/prx/libSceNgs2/CMakeLists.txt @@ -12,4 +12,5 @@ if(BUILD_TESTING) set_tests_properties(ngs2_core_render PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_sce_ngs2_test(libSceNgs2 ngs2_core_pcm Ngs2Pcm.cpp) endif() diff --git a/core/libs/prx/libSceNgs2/PendingExports.cpp b/core/libs/prx/libSceNgs2/PendingExports.cpp index 79edf98b0c..e1942c4923 100644 --- a/core/libs/prx/libSceNgs2/PendingExports.cpp +++ b/core/libs/prx/libSceNgs2/PendingExports.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNgs2/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,10 +6,6 @@ extern "C" { -int APS5_VABI sceNgs2CustomRackGetModuleInfo(std::uintptr_t rackHandle, std::uint32_t moduleIndex, void* outInfo, std::size_t infoSize) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI sceNgs2FftInit() { NotImplemented_nid_no_patch(__func__); return 0; @@ -21,10 +18,6 @@ int APS5_VABI sceNgs2FftQuerySize() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNgs2GetWaveformFrameInfo(void* format, std::uint32_t* outFrameSize, std::uint32_t* outNumFrameSamples, std::uint32_t* outUnitsPerFrame, std::uint32_t* outNumDelaySamples) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI sceNgs2ParseWaveformUser(std::uintptr_t handler, std::uintptr_t userData, void* outInfo) { NotImplemented_nid_no_patch(__func__); return 0; @@ -37,9 +30,5 @@ int APS5_VABI sceNgs2SystemEnumRackHandles(std::uintptr_t systemHandle, std::uin NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNgs2VoiceGetMatrixInfo(std::uintptr_t voiceHandle, std::uint32_t matrixId, void* outInfo, std::size_t infoSize) { - NotImplemented_nid_no_patch(__func__); - return 0; -} } diff --git a/core/libs/prx/libSceNgs2/include/Api.hpp b/core/libs/prx/libSceNgs2/include/Api.hpp new file mode 100644 index 0000000000..3398761ed6 --- /dev/null +++ b/core/libs/prx/libSceNgs2/include/Api.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNgs2FftInit(); +int APS5_VABI sceNgs2FftProcess(); +int APS5_VABI sceNgs2FftQuerySize(); +int APS5_VABI sceNgs2ParseWaveformUser(std::uintptr_t handler, std::uintptr_t userData, void* outInfo); +int APS5_VABI sceNgs2SystemEnumHandles(std::uintptr_t* outHandles, std::uint32_t maxHandles); +int APS5_VABI sceNgs2SystemEnumRackHandles(std::uintptr_t systemHandle, std::uintptr_t* outHandles, std::uint32_t maxHandles); + +} diff --git a/core/libs/prx/libSceNgs2/libSceNgs2.hpp b/core/libs/prx/libSceNgs2/libSceNgs2.hpp new file mode 100644 index 0000000000..db4f270ed6 --- /dev/null +++ b/core/libs/prx/libSceNgs2/libSceNgs2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNgs2/include/Api.hpp" diff --git a/core/libs/prx/libSceNotification/Export.cpp b/core/libs/prx/libSceNotification/Export.cpp index 90362f2991..8d6772d02d 100644 --- a/core/libs/prx/libSceNotification/Export.cpp +++ b/core/libs/prx/libSceNotification/Export.cpp @@ -1,13 +1,14 @@ +#include "prx/libSceNotification/include/Api.hpp" #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -int APS5_VABI sceNotificationSend(int type, std::uint8_t flag, std::uint8_t* message) { - (void)type; - (void)flag; - (void)message; +int APS5_VABI sceNotificationSend(int userId, bool logged, const char* payload) { + (void)userId; + (void)logged; + (void)payload; NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceNotification/include/Api.hpp b/core/libs/prx/libSceNotification/include/Api.hpp new file mode 100644 index 0000000000..7174c3528a --- /dev/null +++ b/core/libs/prx/libSceNotification/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNotificationSend(int userId, bool logged, const char* payload); +int APS5_VABI sceNotificationSendById(int type, std::uint8_t flag, std::uint8_t* id, std::uint8_t* jsonData); +int APS5_VABI sceNotificationShowPsButtonPersistentBanner(std::uint8_t* jsonParam); +void APS5_VABI sceNotificationHidePsButtonPersistentBanner(); + +} diff --git a/core/libs/prx/libSceNotification/libSceNotification.hpp b/core/libs/prx/libSceNotification/libSceNotification.hpp new file mode 100644 index 0000000000..780084c485 --- /dev/null +++ b/core/libs/prx/libSceNotification/libSceNotification.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNotification/include/Api.hpp" diff --git a/core/libs/prx/libSceNotificationClient/libSceNotificationClient.hpp b/core/libs/prx/libSceNotificationClient/libSceNotificationClient.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceNotificationClient/libSceNotificationClient.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceNpAuth/CMakeLists.txt b/core/libs/prx/libSceNpAuth/CMakeLists.txt index ce6ce6a2f8..67acfa7500 100644 --- a/core/libs/prx/libSceNpAuth/CMakeLists.txt +++ b/core/libs/prx/libSceNpAuth/CMakeLists.txt @@ -5,7 +5,8 @@ configure_windows_unwind(libSceNpAuth) if(BUILD_TESTING) add_test_executable(guest_np_auth_authorized_app_code_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpAuthAuthorizedAppCode.cpp) target_include_directories(guest_np_auth_authorized_app_code_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_np_auth_authorized_app_code_tests PRIVATE libSceNpAuth) + target_link_libraries(guest_np_auth_authorized_app_code_tests PRIVATE libSceNpAuth libc) + configure_windows_unwind(guest_np_auth_authorized_app_code_tests) add_test(NAME guest_np_auth_authorized_app_code COMMAND guest_np_auth_authorized_app_code_tests) if(WIN32) set_tests_properties(guest_np_auth_authorized_app_code PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceNpAuth/Export.cpp b/core/libs/prx/libSceNpAuth/Export.cpp index 7ce7edb5ee..2d2b89ec9d 100644 --- a/core/libs/prx/libSceNpAuth/Export.cpp +++ b/core/libs/prx/libSceNpAuth/Export.cpp @@ -1,7 +1,10 @@ +#include "prx/libSceNpAuth/include/Api.hpp" #include +#include #include #include #include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -22,6 +25,8 @@ struct RequestInfo { bool aborted = false; bool polled = false; int result = SCE_NP_ERROR_SIGNED_OUT; + std::int32_t resolveRetry = 0; + std::array timeouts{}; }; std::mutex g_requestMutex; @@ -62,8 +67,14 @@ int APS5_VABI sceNpAuthDeleteRequest(int req_id) { return 0; } -int APS5_VABI sceNpAuthGetAuthorizedAppCode() { - return SCE_NP_ERROR_SIGNED_OUT; +int APS5_VABI sceNpAuthGetAuthorizedAppCode(std::int32_t requestId, const NpAuthAuthorizedAppCodeParameter* parameter, char* code, std::int32_t* issuerId, bool* authorizationRequired) { + (void)issuerId; + if (parameter == nullptr || code == nullptr || authorizationRequired == nullptr) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + if (parameter->size != sizeof(NpAuthAuthorizedAppCodeParameter)) return static_cast(0x80550302u); + if (parameter->userId == -1 || parameter->clientId == nullptr || parameter->scope == nullptr || parameter->option > 1) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_requestMutex); + if (!g_requests.contains(requestId)) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + throw std::logic_error("sceNpAuthGetAuthorizedAppCode: authorization backend is not implemented"); } int APS5_VABI sceNpAuthGetAuthorizationCodeV3(int req_id, const void* param, void* auth_code, int* issuer_id) { @@ -74,7 +85,6 @@ int APS5_VABI sceNpAuthGetAuthorizationCodeV3(int req_id, const void* param, voi if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; if (it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; if (it->second.aborted) { - it->second.result = ORBIS_NP_AUTH_ERROR_ABORTED; return ORBIS_NP_AUTH_ERROR_ABORTED; } it->second.processed = true; @@ -92,7 +102,6 @@ int APS5_VABI sceNpAuthGetIdTokenV3(int req_id, const void* param, void* id_toke if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; if (it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; if (it->second.aborted) { - it->second.result = ORBIS_NP_AUTH_ERROR_ABORTED; return ORBIS_NP_AUTH_ERROR_ABORTED; } it->second.processed = true; @@ -115,7 +124,7 @@ int APS5_VABI sceNpAuthPollAsync(int req_id, int* result) { std::lock_guard lock(g_requestMutex); auto it = g_requests.find(req_id); if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; - if (!it->second.isAsync || (!it->second.processed && !it->second.aborted)) return ORBIS_NP_AUTH_ERROR_INVALID_ID; + if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; if (!it->second.polled) { it->second.polled = true; return NP_POLL_ASYNC_RUNNING; @@ -124,14 +133,19 @@ int APS5_VABI sceNpAuthPollAsync(int req_id, int* result) { return NP_POLL_ASYNC_FINISHED; } -int APS5_VABI sceNpAuthSetTimeout(int req_id, int resolve_retry, uint32_t resolve_timeout, uint32_t connect_timeout, uint32_t send_timeout, uint32_t receive_timeout) { - (void)resolve_retry; - (void)resolve_timeout; - (void)connect_timeout; - (void)send_timeout; - (void)receive_timeout; +int APS5_VABI sceNpAuthSetTimeout(std::int32_t requestId, std::int32_t resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connectTimeout, std::uint32_t sendTimeout, std::uint32_t receiveTimeout) { + if (resolveRetry < 0) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + if ((static_cast(resolveRetry) | resolveTimeout | connectTimeout | sendTimeout | receiveTimeout) == 0) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + if (resolveTimeout != 0 && resolveTimeout < 1000000) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + if (connectTimeout != 0 && connectTimeout < 10000000) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + if (sendTimeout != 0 && sendTimeout < 10000000) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + if (receiveTimeout != 0 && receiveTimeout < 10000000) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; std::lock_guard lock(g_requestMutex); - return g_requests.contains(req_id) ? 0 : ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + const auto found = g_requests.find(requestId); + if (found == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + found->second.resolveRetry = resolveRetry; + found->second.timeouts = {resolveTimeout, connectTimeout, sendTimeout, receiveTimeout}; + return 0; } int APS5_VABI sceNpAuthWaitAsync(int req_id, int* result) { @@ -139,7 +153,7 @@ int APS5_VABI sceNpAuthWaitAsync(int req_id, int* result) { std::lock_guard lock(g_requestMutex); auto it = g_requests.find(req_id); if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; - if (!it->second.isAsync || (!it->second.processed && !it->second.aborted)) return ORBIS_NP_AUTH_ERROR_INVALID_ID; + if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; *result = it->second.result; return NP_POLL_ASYNC_FINISHED; } diff --git a/core/libs/prx/libSceNpAuth/include/Api.hpp b/core/libs/prx/libSceNpAuth/include/Api.hpp new file mode 100644 index 0000000000..cb003d3a93 --- /dev/null +++ b/core/libs/prx/libSceNpAuth/include/Api.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpAuthAbortRequest(int reqId); +int APS5_VABI sceNpAuthCreateAsyncRequest(const void* param); +int APS5_VABI sceNpAuthCreateRequest(void); +int APS5_VABI sceNpAuthDeleteRequest(int reqId); +int APS5_VABI sceNpAuthGetAuthorizedAppCode(std::int32_t requestId, const NpAuthAuthorizedAppCodeParameter* parameter, char* code, std::int32_t* issuerId, bool* authorizationRequired); +int APS5_VABI sceNpAuthGetAuthorizationCodeV3(int reqId, const void* param, void* authCode, int* issuerId); +int APS5_VABI sceNpAuthGetIdTokenV3(int reqId, const void* param, void* idToken); +int APS5_VABI sceNpAuthGetAuthorizationCode(int reqId, const void* param, void* authCode); +int APS5_VABI sceNpAuthGetIdToken(int reqId, const void* param, void* idToken); +int APS5_VABI sceNpAuthPollAsync(int reqId, int* result); +int APS5_VABI sceNpAuthSetTimeout(std::int32_t requestId, std::int32_t resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connectTimeout, std::uint32_t sendTimeout, std::uint32_t receiveTimeout); +int APS5_VABI sceNpAuthWaitAsync(int reqId, int* result); +int APS5_VABI sceNpAuthGetAuthorizationCodeA(int reqId, const void* param, void* authCode, int* issuerId); +int APS5_VABI sceNpAuthGetIdTokenA(int reqId, const void* param, void* token); + +} diff --git a/core/libs/prx/libSceNpAuth/libSceNpAuth.hpp b/core/libs/prx/libSceNpAuth/libSceNpAuth.hpp new file mode 100644 index 0000000000..1f4aaf4f77 --- /dev/null +++ b/core/libs/prx/libSceNpAuth/libSceNpAuth.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpAuth/include/Api.hpp" diff --git a/core/libs/prx/libSceNpAuthAuthorizedAppDialog/CMakeLists.txt b/core/libs/prx/libSceNpAuthAuthorizedAppDialog/CMakeLists.txt index 3d65cfe796..15dcbb4f2e 100644 --- a/core/libs/prx/libSceNpAuthAuthorizedAppDialog/CMakeLists.txt +++ b/core/libs/prx/libSceNpAuthAuthorizedAppDialog/CMakeLists.txt @@ -2,10 +2,12 @@ add_library(libSceNpAuthAuthorizedAppDialog SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceNpAuthAuthorizedAppDialog PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceNpAuthAuthorizedAppDialog) +target_link_libraries(libSceNpAuthAuthorizedAppDialog PRIVATE libSceUserService) + if(BUILD_TESTING) add_test_executable(guest_np_auth_authorized_app_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpAuthAuthorizedAppDialog.cpp) target_include_directories(guest_np_auth_authorized_app_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_np_auth_authorized_app_dialog_tests PRIVATE libSceNpAuthAuthorizedAppDialog) + target_link_libraries(guest_np_auth_authorized_app_dialog_tests PRIVATE libSceNpAuthAuthorizedAppDialog libSceUserService) add_test(NAME guest_np_auth_authorized_app_dialog COMMAND guest_np_auth_authorized_app_dialog_tests) if(WIN32) set_tests_properties(guest_np_auth_authorized_app_dialog PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceNpAuthAuthorizedAppDialog/Export.cpp b/core/libs/prx/libSceNpAuthAuthorizedAppDialog/Export.cpp index f1cbb534c2..e483fc0a9d 100644 --- a/core/libs/prx/libSceNpAuthAuthorizedAppDialog/Export.cpp +++ b/core/libs/prx/libSceNpAuthAuthorizedAppDialog/Export.cpp @@ -1,8 +1,11 @@ +#include "prx/libSceNpAuthAuthorizedAppDialog/include/Api.hpp" +#include "prx/libSceUserService/UserService.hpp" #include #include #include #include #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" namespace { @@ -12,7 +15,8 @@ constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_INVALID_ARGUMENT = static_cast(0x80B8000Au); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); @@ -30,11 +34,17 @@ int APS5_VABI sceNpAuthAuthorizedAppDialogInitialize(void) { return 0; } -int APS5_VABI sceNpAuthAuthorizedAppDialogOpen(const void* param) { +int APS5_VABI sceNpAuthAuthorizedAppDialogOpen(const NpAuthAuthorizedAppDialogParam* param) { + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (!ValidateCommonDialogBaseParam(param->base)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (param->size != sizeof(*param) || param->userId == -1) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + UserServiceLoginUserIdList users{}; + if (sceUserServiceGetLoginUserIdList(&users) != 0 || std::find(std::begin(users.user_id), std::end(users.user_id), param->userId) == std::end(users.user_id)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (param->application == nullptr || param->application[0] == 0 || param->text == nullptr || param->text[0] == 0) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (std::strlen(param->text) > 1024 || param->mode > 1 || !std::all_of(std::begin(param->reserved), std::end(param->reserved), [](std::uint8_t value) { return value == 0; })) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; - if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } @@ -47,18 +57,18 @@ int APS5_VABI sceNpAuthAuthorizedAppDialogGetStatus(void) { return g_status.load(); } -int APS5_VABI sceNpAuthAuthorizedAppDialogGetResult(void* result) { +int APS5_VABI sceNpAuthAuthorizedAppDialogGetResult(std::int32_t* result) { + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; - std::memcpy(result, &canceled, sizeof(canceled)); - return 0; + *result = COMMON_DIALOG_RESULT_USER_CANCELED; + return COMMON_DIALOG_RESULT_USER_CANCELED; } int APS5_VABI sceNpAuthAuthorizedAppDialogClose(void) { if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (g_status.load() != COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_NOT_RUNNING; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } diff --git a/core/libs/prx/libSceNpAuthAuthorizedAppDialog/include/Api.hpp b/core/libs/prx/libSceNpAuthAuthorizedAppDialog/include/Api.hpp new file mode 100644 index 0000000000..c2514b916f --- /dev/null +++ b/core/libs/prx/libSceNpAuthAuthorizedAppDialog/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpAuthAuthorizedAppDialogInitialize(void); +int APS5_VABI sceNpAuthAuthorizedAppDialogOpen(const NpAuthAuthorizedAppDialogParam* param); +int APS5_VABI sceNpAuthAuthorizedAppDialogUpdateStatus(void); +int APS5_VABI sceNpAuthAuthorizedAppDialogGetStatus(void); +int APS5_VABI sceNpAuthAuthorizedAppDialogGetResult(std::int32_t* result); +int APS5_VABI sceNpAuthAuthorizedAppDialogClose(void); +int APS5_VABI sceNpAuthAuthorizedAppDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceNpAuthAuthorizedAppDialog/libSceNpAuthAuthorizedAppDialog.hpp b/core/libs/prx/libSceNpAuthAuthorizedAppDialog/libSceNpAuthAuthorizedAppDialog.hpp new file mode 100644 index 0000000000..c091f6db11 --- /dev/null +++ b/core/libs/prx/libSceNpAuthAuthorizedAppDialog/libSceNpAuthAuthorizedAppDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpAuthAuthorizedAppDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceNpCommerce/CMakeLists.txt b/core/libs/prx/libSceNpCommerce/CMakeLists.txt index ec34bf1230..1c59d83752 100644 --- a/core/libs/prx/libSceNpCommerce/CMakeLists.txt +++ b/core/libs/prx/libSceNpCommerce/CMakeLists.txt @@ -1,11 +1,12 @@ add_library(libSceNpCommerce SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceNpCommerce PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceNpCommerce) +target_link_libraries(libSceNpCommerce PRIVATE libc libSceUserService) if(BUILD_TESTING) add_test_executable(guest_np_commerce_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCommerceDialog.cpp) target_include_directories(guest_np_commerce_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_np_commerce_dialog_tests PRIVATE libSceNpCommerce libc) + target_link_libraries(guest_np_commerce_dialog_tests PRIVATE libSceNpCommerce libc libSceUserService) configure_windows_unwind(guest_np_commerce_dialog_tests) add_test(NAME guest_np_commerce_dialog COMMAND guest_np_commerce_dialog_tests) if(WIN32) diff --git a/core/libs/prx/libSceNpCommerce/Export.cpp b/core/libs/prx/libSceNpCommerce/Export.cpp index 2fd73aeb90..a94c5c9e82 100644 --- a/core/libs/prx/libSceNpCommerce/Export.cpp +++ b/core/libs/prx/libSceNpCommerce/Export.cpp @@ -1,21 +1,95 @@ +#include "prx/libSceNpCommerce/include/Api.hpp" +#include "prx/libSceUserService/UserService.hpp" #include #include #include +#include +#include "prx/libc/include/ApplicationHeap.hpp" #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" namespace { constexpr int COMMON_DIALOG_STATUS_NONE = 0; constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); -constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80006u); -constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B80009u); +constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); + +constexpr int COMMON_DIALOG_ERROR_INVALID_ARGUMENT = static_cast(0x80B8000Au); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); int g_status = COMMON_DIALOG_STATUS_NONE; +std::uint64_t g_userData = 0; + +std::uint32_t compiledSdkVersion() { + const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t version; + std::memcpy(&version, process + 16, sizeof(version)); + return version; +} + +bool isLoggedIn(std::int32_t userId) { + UserServiceLoginUserIdList users{}; + return sceUserServiceGetLoginUserIdList(&users) == 0 && userId != -1 && std::find(std::begin(users.user_id), std::end(users.user_id), userId) != std::end(users.user_id); +} + +bool validateParam(const NpCommerceDialogParam& param) { + const auto sdkVersion = compiledSdkVersion(); + if (sdkVersion >= 0x01700000 && (param.size != sizeof(param) || !std::all_of(std::begin(param.reserved), std::end(param.reserved), [](std::uint8_t value) { return value == 0; }) || !isLoggedIn(param.userId) || param.option > 7)) return false; + switch (param.mode) { + case 0: + return param.productCount <= 1 && (param.products != nullptr) == (param.productCount != 0); + case 1: + return param.productCount == 1 && param.products != nullptr; + case 2: + return param.productCount <= 1 && (param.productCount != 0 ? param.products != nullptr : sdkVersion < 0x01700000 || param.products == nullptr); + case 3: + return param.productCount >= 1 && param.productCount <= 10 && param.products != nullptr; + case 4: + return param.productCount <= 10 && (param.productCount != 0 ? param.products != nullptr : sdkVersion < 0x01700000 || param.products == nullptr); + case 5: + return param.productCount == 0 && (sdkVersion >= 0x03500000 ? param.argument == 1 : (param.argument & 3) != 0 && (sdkVersion < 0x01700000 || param.argument < 4)); + default: + return false; + } +} + +bool validateParam2(const NpCommerceDialogParam2& param) { + if (!isLoggedIn(param.userId) || param.size != sizeof(param)) return false; + if (param.option >= 8 && (param.contentMode == 0 || (param.contentMode == 1 && param.option >= 100))) return false; + if (!std::all_of(std::begin(param.reserved), std::end(param.reserved), [](std::uint8_t value) { return value == 0; })) return false; + bool valid = false; + switch (param.mode) { + case 0: + valid = param.contentMode < 2 && param.productCount <= 1 && (param.products != nullptr) == (param.productCount != 0); + break; + case 1: + valid = param.contentMode < 2 && param.productCount == 1 && param.products != nullptr; + break; + case 2: + valid = param.productCount <= 1 && (param.products != nullptr) == (param.productCount != 0); + break; + case 3: + valid = param.contentMode < 2 && param.productCount >= 1 && param.productCount <= 10 && param.products != nullptr; + break; + case 4: + valid = param.contentMode < 2 && param.productCount <= 10 && (param.products != nullptr) == (param.productCount != 0); + break; + case 5: + valid = param.productCount == 0 && param.argument == 1; + break; + default: + return false; + } + if (!valid) return false; + if (param.contentMode != 0 && (param.contentMode != 1 || param.mode != 1)) throw std::runtime_error("commerce dialog: extended content process version is not implemented"); + return true; +} } @@ -28,9 +102,12 @@ int APS5_VABI sceNpCommerceDialogInitialize() { return 0; } -int APS5_VABI sceNpCommerceDialogOpen(const void* param) { - if (g_status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; +int APS5_VABI sceNpCommerceDialogOpen(const NpCommerceDialogParam* param) { if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (!ValidateCommonDialogBaseParam(param->base)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (g_status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (!validateParam(*param)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + g_userData = param->userData; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } @@ -43,24 +120,27 @@ int APS5_VABI sceNpCommerceDialogGetStatus(void) { return g_status; } -int APS5_VABI sceNpCommerceDialogGetResult(void* result) { +int APS5_VABI sceNpCommerceDialogGetResult(NpCommerceDialogResult* result) { if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (g_status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (g_status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - // SceNpCommerceDialogResult: int32 result, bool authorized, then reserved bytes. - std::int32_t value = COMMON_DIALOG_RESULT_USER_CANCELED; - std::memcpy(result, &value, sizeof(value)); - std::uint8_t authorized = 0; - std::memcpy(static_cast(result) + sizeof(value), &authorized, sizeof(authorized)); - return 0; + const auto sdkVersion = compiledSdkVersion(); + result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + result->authorized = 0; + result->userData = g_userData; + if (sdkVersion >= 0x01700000) std::fill(std::begin(result->reserved), std::end(result->reserved), 0); + return COMMON_DIALOG_RESULT_USER_CANCELED; } int APS5_VABI sceNpCommerceDialogClose(void) { if (g_status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (g_status != COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_NOT_RUNNING; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } int APS5_VABI sceNpCommerceDialogTerminate() { + if (g_status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; g_status = COMMON_DIALOG_STATUS_NONE; return 0; } @@ -79,14 +159,18 @@ int APS5_VABI sceNpCommerceSetPsStoreIconLayout(int layout) { return 0; } -int APS5_VABI sceNpCommerceDialogOpen2(void) { +int APS5_VABI sceNpCommerceDialogOpen2(const NpCommerceDialogParam2* param) { + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (!ValidateCommonDialogBaseParam(param->base)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (g_status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (!validateParam2(*param)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + g_userData = param->userData; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } int APS5_VABI sceNpCommerceDialogInitializeInternal() { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceNpCommerceDialogInitialize(); } } diff --git a/core/libs/prx/libSceNpCommerce/include/Api.hpp b/core/libs/prx/libSceNpCommerce/include/Api.hpp new file mode 100644 index 0000000000..ddb745fc4a --- /dev/null +++ b/core/libs/prx/libSceNpCommerce/include/Api.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpCommerceDialogInitialize(); +int APS5_VABI sceNpCommerceDialogOpen(const NpCommerceDialogParam* param); +int APS5_VABI sceNpCommerceDialogUpdateStatus(void); +int APS5_VABI sceNpCommerceDialogGetStatus(void); +int APS5_VABI sceNpCommerceDialogGetResult(NpCommerceDialogResult* result); +int APS5_VABI sceNpCommerceDialogClose(void); +int APS5_VABI sceNpCommerceDialogTerminate(); +int APS5_VABI sceNpCommerceHidePsStoreIcon(void); +int APS5_VABI sceNpCommerceShowPsStoreIcon(int pos); +int APS5_VABI sceNpCommerceSetPsStoreIconLayout(int layout); +int APS5_VABI sceNpCommerceDialogOpen2(const NpCommerceDialogParam2* param); +int APS5_VABI sceNpCommerceDialogInitializeInternal(); + +} diff --git a/core/libs/prx/libSceNpCommerce/libSceNpCommerce.hpp b/core/libs/prx/libSceNpCommerce/libSceNpCommerce.hpp new file mode 100644 index 0000000000..240d2d12fe --- /dev/null +++ b/core/libs/prx/libSceNpCommerce/libSceNpCommerce.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpCommerce/include/Api.hpp" diff --git a/core/libs/prx/libSceNpCommon/CMakeLists.txt b/core/libs/prx/libSceNpCommon/CMakeLists.txt index f85817c30e..bab2481853 100644 --- a/core/libs/prx/libSceNpCommon/CMakeLists.txt +++ b/core/libs/prx/libSceNpCommon/CMakeLists.txt @@ -2,5 +2,7 @@ add_library(libSceNpCommon SHARED EXCLUDE_FROM_ALL PendingExports.cpp) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp") target_sources(libSceNpCommon PRIVATE Export.cpp) endif() +target_sources(libSceNpCommon PRIVATE Runtime.cpp Strings.cpp) target_include_directories(libSceNpCommon PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceNpCommon PRIVATE libc libkernel libSceRtc libSceUserService) configure_windows_unwind(libSceNpCommon) diff --git a/core/libs/prx/libSceNpCommon/Export.cpp b/core/libs/prx/libSceNpCommon/Export.cpp new file mode 100644 index 0000000000..77a7b33fc9 --- /dev/null +++ b/core/libs/prx/libSceNpCommon/Export.cpp @@ -0,0 +1,175 @@ +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceNpCommon/include/Api.hpp" +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +namespace { + +constexpr int NP_ERROR_INVALID_ARGUMENT = static_cast(0x80550003); +constexpr int NP_ERROR_INVALID_PLATFORM_TYPE = static_cast(0x80550004); +constexpr int NP_UTIL_ERROR_NOT_MATCH = static_cast(0x80550609); +constexpr int NP_LW_COND_ERROR_TIMEDOUT = static_cast(0x8055800B); +constexpr int NP_LW_MUTEX_ERROR_BUSY = static_cast(0x8055800F); +constexpr int KERNEL_ERROR_EBUSY = static_cast(0x80020010); +constexpr int KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); +constexpr std::size_t ONLINE_ID_MAX_LENGTH = 16; +constexpr int MUTEX_TYPE_RECURSIVE = 2; +constexpr KernelClockid CLOCK_MONOTONIC_ID = 4; + +static_assert(sizeof(NpIdFull) == 36); + +static_assert(sizeof(SceKernelSystemSwVersion) == 0x28); + +int compareBytes(const std::uint8_t* lhs, const std::uint8_t* rhs, std::size_t count) { + for (std::size_t i = 0; i < count; ++i) { + if (lhs[i] != rhs[i]) return lhs[i] < rhs[i] ? -1 : 1; + } + return 0; +} + +} + +extern "C" { + +int APS5_VABI sceNpCmpOnlineId(const NpOnlineId* onlineId1, const NpOnlineId* onlineId2) { + if (!onlineId1 || !onlineId2) return NP_ERROR_INVALID_ARGUMENT; + if (std::strncmp(onlineId1->data, onlineId2->data, ONLINE_ID_MAX_LENGTH) != 0) return NP_UTIL_ERROR_NOT_MATCH; + return 0; +} + +int APS5_VABI sceNpCmpNpIdInOrder(const NpIdFull* npId1, const NpIdFull* npId2, std::int32_t* order) { + if (!npId1 || !npId2 || !order) return NP_ERROR_INVALID_ARGUMENT; + int compare = std::strncmp(npId1->handle.data, npId2->handle.data, ONLINE_ID_MAX_LENGTH); + if (compare == 0) compare = compareBytes(npId1->opt, npId2->opt, sizeof(npId1->opt)); + if (compare == 0) compare = compareBytes(npId1->reserved, npId2->reserved, sizeof(npId1->reserved)); + *order = compare < 0 ? -1 : compare > 0 ? 1 : 0; + return 0; +} + +int APS5_VABI sceNpGetPlatformType(const NpIdFull* npId) { + constexpr std::uint32_t TAG_PS3 = 0x00337370; + constexpr std::uint32_t TAG_VITA = 0x32707370; + constexpr std::uint32_t TAG_PS4 = 0x00347370; + if (!npId) return NP_ERROR_INVALID_ARGUMENT; + if (npId->opt[4] == 0) return 0; + std::uint32_t tag = 0; + std::memcpy(&tag, npId->opt + 4, sizeof(tag)); + switch (tag) { + case TAG_PS3: return 1; + case TAG_VITA: return 2; + case TAG_PS4: return 3; + default: return NP_ERROR_INVALID_PLATFORM_TYPE; + } +} + +int APS5_VABI sceNpIntIsValidOnlineId(const NpOnlineId* onlineId) { + if (!onlineId || onlineId->data[ONLINE_ID_MAX_LENGTH] != 0) return 0; + const std::size_t length = strnlen(onlineId->data, ONLINE_ID_MAX_LENGTH + 1); + if (length < 3 || length > ONLINE_ID_MAX_LENGTH) return 0; + if (!std::isalnum(static_cast(onlineId->data[0]))) return 0; + for (std::size_t i = 1; i < length; ++i) { + const unsigned char character = static_cast(onlineId->data[i]); + if (!std::isalnum(character) && character != '_' && character != '-') return 0; + } + return 1; +} + +int APS5_VABI sceNpMutexInit(PthreadMutex* mutex, const char* name, std::uint64_t flags) { + PthreadMutexattr attr = nullptr; + int result = scePthreadMutexattrInit(&attr); + if (result != 0) return result; + if (flags & 1) result = scePthreadMutexattrSettype(&attr, MUTEX_TYPE_RECURSIVE); + if (result == 0) result = scePthreadMutexInit(mutex, &attr, name); + scePthreadMutexattrDestroy(&attr); + + return result; +} + +int APS5_VABI sceNpMutexDestroy(PthreadMutex* mutex) { + return scePthreadMutexDestroy(mutex); +} + +int APS5_VABI sceNpMutexLock(PthreadMutex* mutex) { + return scePthreadMutexLock(mutex); +} + +int APS5_VABI sceNpMutexTryLock(PthreadMutex* mutex) { + const int result = scePthreadMutexTrylock(mutex); + return result == KERNEL_ERROR_EBUSY ? NP_LW_MUTEX_ERROR_BUSY : result; +} + +int APS5_VABI sceNpMutexUnlock(PthreadMutex* mutex) { + return scePthreadMutexUnlock(mutex); +} + +int APS5_VABI sceNpLwMutexInit(PthreadMutex* mutex, const char* name, std::uint64_t flags) { + return sceNpMutexInit(mutex, name, flags); +} + +int APS5_VABI sceNpLwMutexDestroy(PthreadMutex* mutex) { + return sceNpMutexDestroy(mutex); +} + +int APS5_VABI sceNpLwMutexLock(PthreadMutex* mutex) { + return sceNpMutexLock(mutex); +} + +int APS5_VABI sceNpLwMutexTryLock(PthreadMutex* mutex) { + return sceNpMutexTryLock(mutex); +} + +int APS5_VABI sceNpLwMutexUnlock(PthreadMutex* mutex) { + return sceNpMutexUnlock(mutex); +} + +int APS5_VABI sceNpCondInit(PthreadCond* cond, const char* name, std::uint64_t) { + PthreadCondattr attr = nullptr; + int result = scePthreadCondattrInit(&attr); + if (result != 0) return result; + result = scePthreadCondInit(cond, &attr, name); + scePthreadCondattrDestroy(&attr); + + return result; +} + +int APS5_VABI sceNpCondDestroy(PthreadCond* cond) { + return scePthreadCondDestroy(cond); +} + +int APS5_VABI sceNpCondSignal(PthreadCond* cond) { + return scePthreadCondSignal(cond); +} + +int APS5_VABI sceNpCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, std::uint32_t usec) { + if (usec == 0) return scePthreadCondWait(cond, mutex); + const int result = scePthreadCondTimedwait(cond, mutex, usec); + return result == KERNEL_ERROR_ETIMEDOUT ? NP_LW_COND_ERROR_TIMEDOUT : result; +} + +int APS5_VABI sceNpJoinThread(Pthread thread, void** retval) { + return scePthreadJoin(thread, retval); +} + +int APS5_VABI sceNpGetSystemClockUsec(std::int64_t* usec) { + KernelTimespec now{}; + const int result = sceKernelClockGettime(CLOCK_MONOTONIC_ID, &now); + if (result == 0) *usec = now.tv_sec * 1000000 + now.tv_nsec / 1000; + return result; +} + +void APS5_VABI sceNpGetSdkVersion(char* version) { + SceKernelSystemSwVersion swVersion{}; + swVersion.size = sizeof(swVersion); + sceKernelGetSystemSwVersion_nid_postfix(&swVersion); + + const std::uint32_t hex = swVersion.version; + std::snprintf(version, 8, "%u.%02u", ((hex >> 24) & 0xF) + (hex >> 28) * 10, ((hex >> 16) & 0xF) + ((hex >> 20) & 0xF) * 10); +} + +} diff --git a/core/libs/prx/libSceNpCommon/PendingExports.cpp b/core/libs/prx/libSceNpCommon/PendingExports.cpp index c9d6e53ffd..b6479f111b 100644 --- a/core/libs/prx/libSceNpCommon/PendingExports.cpp +++ b/core/libs/prx/libSceNpCommon/PendingExports.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpCommon/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,341 +6,26 @@ extern "C" { -int APS5_VABI sceNpAllocateKernelMemoryNoAlignment(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpAllocateKernelMemoryWithAlignment(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpArchInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpArchTerm(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpAtomicCas32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpAtomicDec32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpAtomicInc32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpBase64Decoder(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpBase64Encoder(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpBase64GetDecodeSize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpBase64UrlDecoder(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpBase64UrlEncoder(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpBase64UrlGetDecodeSize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCalloutInitCtx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCalloutStartOnCtx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCalloutStartOnCtx64(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCalloutStopOnCtx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCalloutTermCtx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCancelEventFlag(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpClearEventFlag(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCloseEventFlag(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpCloseSema(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpCmpNpIdInOrder(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCmpOnlineId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCondDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCondInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCondSignal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCondSignalAll(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCondSignalTo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCondTimedwait(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCondWait(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCreateEventFlag(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCreateSema(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpCreateThread(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpDbgAssignDebugId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpDbgDumpBinary(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpDbgDumpText(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpDeleteEventFlag(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpDeleteSema(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpFreeKernelMemory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpGetNavSdkVersion(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpGetPlatformType(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpGetProcessId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpGetRandom(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpGetSdkVersion(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpGetSdkVersionUInt(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpGetSystemClockUsec(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpGlobalHeapGetAllocator(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpGlobalHeapGetAllocatorEx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpGlobalHeapGetAllocatorExPtr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpGlobalHeapGetAllocatorPtr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpHeapDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpHeapGetAllocator(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpHeapGetStat(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpHeapInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpHeapShowStat(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpHexToInt(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpInt32ToStr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpInt64ToStr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpIntGetPlatformType(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpIntIsOnlineIdString(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpIntIsValidOnlineId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpIntSetPlatformType(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpIntToHex(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpIpc2ClientInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpIpc2ClientTerm(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpJoinThread(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpJsonParse(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -370,146 +56,11 @@ int APS5_VABI sceNpJsonParseInit(void) { return 0; } -int APS5_VABI sceNpLwCondDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwCondInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwCondSignal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwCondSignalAll(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwCondSignalTo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwCondWait(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwMutexDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwMutexInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwMutexLock(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwMutexTryLock(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpLwMutexUnlock(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMemoryHeapDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMemoryHeapGetAllocator(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMemoryHeapGetAllocatorEx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMemoryHeapInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMutexDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMutexInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMutexLock(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMutexTryLock(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMutexUnlock(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpOpenEventFlag(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpOpenSema(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpPanic(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpPollEventFlag(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpPollSema(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpRtcConvertToPosixTime(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpRtcFormatRFC3339(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpRtcParseRFC3339(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpServerErrorJsonGetErrorCode(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -535,91 +86,11 @@ int APS5_VABI sceNpServerErrorJsonParseMultiInit(void) { return 0; } -int APS5_VABI sceNpSetEventFlag(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpSetPlatformType(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpSignalSema(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpStrBuildHex(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpStrParseHex(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpStrToInt32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpStrToInt64(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpStrToUInt32(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpStrToUInt64(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpStrcpyToBuf(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpStrnParseHex(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpStrncpyToBuf(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpThreadGetId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpUInt32ToStr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpUInt64ToStr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpUserGetUserIdList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpUtilBuildTitleId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpUtilCanonicalizeNpIdForPs4(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -630,11 +101,6 @@ int APS5_VABI sceNpUtilCanonicalizeNpIdForPsp2(void) { return 0; } -int APS5_VABI sceNpUtilCmpAccountId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpUtilGetDateSetAuto(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -670,36 +136,11 @@ int APS5_VABI sceNpUtilGetNpDebug(void) { return 0; } -int APS5_VABI sceNpUtilGetNpLanguageCode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpUtilGetNpLanguageCode2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpUtilGetNpLanguageCode2Str(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpUtilGetNpLanguageCodeStr(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpUtilGetNpTestPatch(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpUtilGetNthChar(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpUtilGetShareTitleCheck(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -730,29 +171,9 @@ int APS5_VABI sceNpUtilGetWebTraceSetting(void) { return 0; } -int APS5_VABI sceNpUtilHttpUrlEncode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpUtilJidToNpId(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpUtilJsonEscape(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpUtilJsonGetOneChar(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -} - -int APS5_VABI sceNpUtilJsonUnescape(void) { - NotImplemented_nid_no_patch(__func__); - return 0; } diff --git a/core/libs/prx/libSceNpCommon/Runtime.cpp b/core/libs/prx/libSceNpCommon/Runtime.cpp new file mode 100644 index 0000000000..510328687f --- /dev/null +++ b/core/libs/prx/libSceNpCommon/Runtime.cpp @@ -0,0 +1,775 @@ +#include "prx/libSceSaveData/SaveDataCore.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceNpCommon/include/Api.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include "prx/libSceRtc/include/Rtc.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/MallocManagedSize.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Semaphore/include/Semaphore.hpp" + +static_assert(sizeof(NpCallout) == 0x28); + +static_assert(sizeof(NpCalloutContext) == 0x28); + +static_assert(sizeof(NpKernelMemory) == 0x18); + +static_assert(sizeof(NpMemoryHeap) == 0x20); + +namespace { + +constexpr int NP_ERROR_NULL_ARGUMENT = static_cast(0x80550003); +constexpr int NP_ERROR_IPC_ALREADY_INITIALIZED = static_cast(0x80559D00); +constexpr int NP_ERROR_IPC_NOT_INITIALIZED = static_cast(0x80559D01); +constexpr int NP_ERROR_ONLINE_ID_MISMATCH = static_cast(0x80550609); +constexpr int NP_ERROR_INVALID_NP_ID = static_cast(0x80550605); +constexpr std::size_t NP_ID_OPT_OFFSET = 0x14; +constexpr std::size_t NP_ID_PLATFORM_OFFSET = 0x18; +constexpr std::size_t NP_ID_TERMINATOR_FLAG_OFFSET = 0x1C; + +std::uint32_t NpIdWord(const std::uint8_t* npId, std::size_t offset) { + std::uint32_t value; + std::memcpy(&value, npId + offset, sizeof(value)); + return value; +} +constexpr std::size_t NP_ONLINE_ID_MAX = 16; +constexpr std::size_t NP_ONLINE_ID_MIN = 3; +constexpr int NP_ERROR_INVALID_ARGUMENT_UTIL = static_cast(0x80550601); +constexpr std::size_t NP_LANGUAGE_CODE_SIZE = 6; +constexpr std::size_t NP_LANGUAGE_CODE2_SIZE = 0x24; + +struct NpLanguageCodes { + const char* code; + const char* tag; +}; + +constexpr NpLanguageCodes NP_LANGUAGE_CODES[] { + {"ja", "ja-JP"}, {"en", "en-US"}, {"fr", "fr-FR"}, {"es", "es-ES"}, {"de", "de-DE"}, {"it", "it-IT"}, + {"nl", "nl-NL"}, {"pt", "pt-PT"}, {"ru", "ru-RU"}, {"ko", "ko-KR"}, {"zh-TW", "zh-Hant"}, {"zh-CN", "zh-Hans"}, + {"fi", "fi-FI"}, {"sv", "sv-SE"}, {"da", "da-DK"}, {"no", "no-NO"}, {"pl", "pl-PL"}, {"pt-BR", "pt-BR"}, + {"en-GB", "en-GB"}, {"tr", "tr-TR"}, {"es-MX", "es-419"}, {"ar", "ar-AE"}, {"fr-CA", "fr-CA"}, {"cs", "cs-CZ"}, + {"hu", "hu-HU"}, {"el", "el-GR"}, {"ro", "ro-RO"}, {"th", "th-TH"}, {"vi", "vi-VN"}, {"id", "id-ID"}, + {"uk", "uk-UA"}, +}; + +const NpLanguageCodes* FindLanguageCodes(int language) { + if (language < 0 || static_cast(language) >= std::size(NP_LANGUAGE_CODES)) return nullptr; + return &NP_LANGUAGE_CODES[language]; +} + +void CopyText(char* out, const char* text, std::size_t size) { + std::strncpy(out, text, size - 1); + out[size - 1] = '\0'; +} + +std::atomic g_debugIds {0}; + +constexpr int NP_UTIL_ERROR_BUFFER_TOO_SMALL = static_cast(0x80550602); +constexpr int NP_UTIL_ERROR_OUT_OF_RANGE = static_cast(0x8055060A); +constexpr std::size_t NP_TITLE_ID_SIZE = 13; +constexpr std::uint8_t NP_TITLE_ID_INDEX_MAX = 99; +constexpr std::size_t NP_DUMP_LINE_BYTES = 16; + +bool IsAsciiLetter(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +bool IsOnlineIdText(const char* text) { + const std::size_t length = std::strlen(text); + if (length < NP_ONLINE_ID_MIN || length > NP_ONLINE_ID_MAX || !IsAsciiLetter(text[0])) return false; + return std::all_of(text + 1, text + length, [](char c) { + return IsAsciiLetter(c) || (c >= '0' && c <= '9') || c == '_' || c == '-'; + }); +} + +constexpr int NP_ERROR_BUFFER_TOO_SMALL = static_cast(0x80558008); +constexpr std::size_t NP_SDK_VERSION_SIZE = 8; +constexpr std::size_t NP_RFC3339_BUFFER_MIN = 0x80; +constexpr std::size_t NP_USER_ID_LIST_SIZE = 20; +constexpr int USER_ID_INVALID = -1; +constexpr KernelClockid NP_SYSTEM_CLOCK = 4; + +static_assert(sizeof(SceKernelSystemSwVersion) == 0x28); + +std::uint32_t SystemVersion() { + SceKernelSystemSwVersion version {}; + version.size = sizeof(version); + sceKernelGetSystemSwVersion_nid_postfix(&version); + + return version.version; +} + +int BcdPair(std::uint32_t value, int shift) { + return static_cast((value >> (shift + 4)) & 0xF) * 10 + static_cast((value >> shift) & 0xF); +} + +constexpr int NP_ERROR_OUT_OF_MEMORY = static_cast(0x80550005); +constexpr int NP_ERROR_NULL_OBJECT = static_cast(0x80558005); +constexpr int NP_ERROR_HEAP_CREATE = static_cast(0x8055800A); +constexpr std::uint64_t NP_KERNEL_MEMORY_ALIGNMENT = 0x4000; +constexpr int NP_KERNEL_MEMORY_PROTECTION = 3; +constexpr std::size_t NP_GLOBAL_HEAP_SIZE = 0x80000; +constexpr char NP_GLOBAL_HEAP_NAME[] = "NpCommonGlobalHeap"; +constexpr std::size_t NP_IPC_CLIENT_HEAP_SIZE = 0x10000; +constexpr char NP_IPC_CLIENT_HEAP_NAME[] = "NpCommonIpcClientHeap"; + +void* APS5_VABI HeapAllocate(std::size_t size, void* context) { + auto* heap = static_cast(context); + return heap->mspace != nullptr ? sceLibcMspaceMalloc_nid_postfix(heap->mspace, size) : nullptr; +} + +void* APS5_VABI HeapReallocate(void* pointer, std::size_t size, void* context) { + auto* heap = static_cast(context); + return heap->mspace != nullptr ? sceLibcMspaceRealloc_nid_postfix(heap->mspace, pointer, size) : nullptr; +} + +void APS5_VABI HeapRelease(void* pointer, void* context) { + auto* heap = static_cast(context); + if (heap->mspace != nullptr && pointer != nullptr) sceLibcMspaceFree_nid_postfix(heap->mspace, pointer); +} + +void* APS5_VABI MemoryHeapAllocate(std::size_t size, void* context) { + return HeapAllocate(size, &static_cast(context)->heap); +} + +void* APS5_VABI MemoryHeapReallocate(void* pointer, std::size_t size, void* context) { + return HeapReallocate(pointer, size, &static_cast(context)->heap); +} + +void APS5_VABI MemoryHeapRelease(void* pointer, void* context) { + HeapRelease(pointer, &static_cast(context)->heap); +} + +std::uint64_t AlignKernelMemory(std::uint64_t size) { + const std::uint64_t remainder = size & (NP_KERNEL_MEMORY_ALIGNMENT - 1); + return remainder == 0 ? size : size + NP_KERNEL_MEMORY_ALIGNMENT - remainder; +} + +int MapKernelMemory(NpKernelMemory* memory, std::uint64_t size, const char* name) { + memory->reserved = 0; + memory->size = size; + return sceKernelMapNamedFlexibleMemory(&memory->address, size, NP_KERNEL_MEMORY_PROTECTION, 0, name); +} + +void UnmapKernelMemory(NpKernelMemory* memory) { + if (memory->address != nullptr) sceKernelMunmap(reinterpret_cast(memory->address), memory->size); + memory->address = nullptr; +} + +int InitHeap(NpHeap* heap, void* base, std::size_t size, const char* name) { + heap->mspace = sceLibcMspaceCreate_nid_postfix(name, base, size, 0); + return heap->mspace != nullptr ? 0 : NP_ERROR_HEAP_CREATE; +} + +void DestroyHeap(NpHeap* heap) { + if (heap->mspace == nullptr) return; + sceLibcMspaceDestroy_nid_postfix(heap->mspace); + + heap->mspace = nullptr; +} + +int InitMemoryHeap(NpMemoryHeap* heap, std::uint64_t size, const char* name) { + if (heap == nullptr) return NP_ERROR_NULL_OBJECT; + int result = MapKernelMemory(&heap->memory, AlignKernelMemory(size), name); + if (result < 0) return result; + result = InitHeap(&heap->heap, heap->memory.address, heap->memory.size, name); + if (result >= 0) return result; + UnmapKernelMemory(&heap->memory); + *heap = {}; + return result; +} + +void DestroyMemoryHeap(NpMemoryHeap* heap) { + DestroyHeap(&heap->heap); + UnmapKernelMemory(&heap->memory); +} + +NpAllocator MemoryHeapAllocator(NpMemoryHeap* heap) { + return {MemoryHeapAllocate, MemoryHeapReallocate, MemoryHeapRelease, heap}; +} + +constexpr int NP_ERROR_NOT_INITIALIZED = static_cast(0x80558001); +constexpr int NP_ERROR_ALREADY_INITIALIZED = static_cast(0x80558002); +constexpr int NP_ERROR_ALREADY_QUEUED = static_cast(0x80558006); +constexpr std::uint64_t NP_CALLOUT_MAX_WAIT = 0xFFFFFFFF; + +std::mutex g_randomMutex; +std::mt19937 g_random {std::random_device {}()}; + +NpMemoryHeap g_globalHeap {}; +NpAllocator g_globalAllocator {}; +NpAllocatorEx g_globalAllocatorEx {}; +int g_archReferences = 0; +std::mutex g_archMutex; +std::mutex g_ipcClientMutex; +bool g_ipcClientInitialized = false; +NpMemoryHeap g_ipcClientHeap {}; + +constexpr int KERNEL_ERROR_EBUSY = static_cast(0x80020010); +constexpr int KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); +constexpr int NP_ERROR_INVALID_ARGUMENT = static_cast(0x80558009); +constexpr int NP_ERROR_TIMEOUT = static_cast(0x8055800B); +constexpr int NP_ERROR_EVENT_FLAG_BUSY = static_cast(0x8055800C); +constexpr int NP_ERROR_SEMA_BUSY = static_cast(0x8055800D); +constexpr int NP_ERROR_MUTEX_BUSY = static_cast(0x8055800F); +constexpr std::uint32_t NP_MUTEX_FLAG_RECURSIVE = 1; +constexpr int PTHREAD_MUTEX_TYPE_RECURSIVE = 2; +constexpr std::uint32_t NP_SEMA_FLAG_PRIORITY = 1; +constexpr std::uint32_t KERNEL_SEMA_ATTR_FIFO = 1; +constexpr std::uint32_t KERNEL_SEMA_ATTR_PRIORITY_SHIFT = 8; +constexpr int NP_THREAD_EXPLICIT_SCHED = 0; +constexpr int NP_THREAD_SCHED_POLICY = 1; + +int NpResult(int result) { + return result < 0 ? result : 0; +} + +int NpBusyResult(int result, int busy) { + return result == KERNEL_ERROR_EBUSY ? busy : NpResult(result); +} + +int InitMutex(PthreadMutex* mutex, const char* name, std::uint32_t flags) { + PthreadMutexattr attr {}; + int result = scePthreadMutexattrInit(&attr); + if (result < 0) return result; + if ((flags & NP_MUTEX_FLAG_RECURSIVE) != 0) result = scePthreadMutexattrSettype(&attr, PTHREAD_MUTEX_TYPE_RECURSIVE); + if (result >= 0) result = scePthreadMutexInit(mutex, &attr, name); + scePthreadMutexattrDestroy(&attr); + + return NpResult(result); +} + +int InitCond(PthreadCond* cond, const char* name) { + PthreadCondattr attr {}; + const int result = scePthreadCondattrInit(&attr); + if (result < 0) return result; + const int created = scePthreadCondInit(cond, &attr, name); + scePthreadCondattrDestroy(&attr); + + return created; +} + +} + +extern "C" { + +int APS5_VABI sceNpAtomicCas32(std::int32_t* value, std::int32_t expected, std::int32_t desired) { + std::atomic_ref(*value).compare_exchange_strong(expected, desired); + return expected; +} + +int APS5_VABI sceNpAtomicInc32(std::int32_t* value) { + return std::atomic_ref(*value).fetch_add(1); +} + +int APS5_VABI sceNpAtomicDec32(std::int32_t* value) { + return std::atomic_ref(*value).fetch_sub(1); +} + +int APS5_VABI sceNpCondSignalAll(PthreadCond* cond) { + return scePthreadCondBroadcast(cond); +} + +int APS5_VABI sceNpCondSignalTo(PthreadCond* cond, Pthread thread) { + return scePthreadCondSignalto(cond, thread); +} + +int APS5_VABI sceNpCondWait(PthreadCond* cond, PthreadMutex* mutex) { + return scePthreadCondWait(cond, mutex); +} + +int APS5_VABI sceNpLwCondInit(PthreadCond* cond, const char* name) { + return InitCond(cond, name); +} + +int APS5_VABI sceNpLwCondDestroy(PthreadCond* cond) { + return scePthreadCondDestroy(cond); +} + +int APS5_VABI sceNpLwCondSignal(PthreadCond* cond) { + return scePthreadCondSignal(cond); +} + +int APS5_VABI sceNpLwCondSignalAll(PthreadCond* cond) { + return scePthreadCondBroadcast(cond); +} + +int APS5_VABI sceNpLwCondSignalTo(PthreadCond* cond, Pthread thread) { + return scePthreadCondSignalto(cond, thread); +} + +int APS5_VABI sceNpLwCondWait(PthreadCond* cond, PthreadMutex* mutex) { + return scePthreadCondWait(cond, mutex); +} + +int APS5_VABI sceNpCreateEventFlag(KernelEventFlag* flag, const char* name, std::uint32_t attr, std::uint64_t pattern) { + return NpResult(sceKernelCreateEventFlag(flag, name, attr, pattern, nullptr)); +} + +int APS5_VABI sceNpDeleteEventFlag(KernelEventFlag flag) { + return NpResult(sceKernelDeleteEventFlag(flag)); +} + +int APS5_VABI sceNpOpenEventFlag(KernelEventFlag* flag, const char* name) { + return NpResult(sceKernelOpenEventFlag(flag, name)); +} + +int APS5_VABI sceNpCloseEventFlag(KernelEventFlag flag) { + return NpResult(sceKernelCloseEventFlag(flag)); +} + +int APS5_VABI sceNpSetEventFlag(KernelEventFlag flag, std::uint64_t pattern) { + return NpResult(sceKernelSetEventFlag(flag, pattern)); +} + +int APS5_VABI sceNpClearEventFlag(KernelEventFlag flag, std::uint64_t pattern) { + return NpResult(sceKernelClearEventFlag(flag, pattern)); +} + +int APS5_VABI sceNpPollEventFlag(KernelEventFlag flag, std::uint64_t pattern, std::uint32_t mode, std::uint64_t* result) { + return NpBusyResult(sceKernelPollEventFlag(flag, pattern, mode, result), NP_ERROR_EVENT_FLAG_BUSY); +} + +int APS5_VABI sceNpCancelEventFlag(KernelEventFlag flag, std::uint64_t pattern, int* waiting) { + return NpResult(sceKernelCancelEventFlag(flag, pattern, waiting)); +} + +int APS5_VABI sceNpCreateSema(KernelSema* sema, std::uint32_t flags, int initial, int maximum, const char* name) { + const std::uint32_t attr = KERNEL_SEMA_ATTR_FIFO + ((flags & NP_SEMA_FLAG_PRIORITY) << KERNEL_SEMA_ATTR_PRIORITY_SHIFT); + return NpResult(sceKernelCreateSema(sema, name, attr, initial, maximum, nullptr)); +} + +int APS5_VABI sceNpDeleteSema(KernelSema sema) { + return NpResult(sceKernelDeleteSema(sema)); +} + +int APS5_VABI sceNpPollSema(KernelSema sema) { + return NpBusyResult(sceKernelPollSema(sema, 1), NP_ERROR_SEMA_BUSY); +} + +int APS5_VABI sceNpSignalSema(KernelSema sema) { + return NpResult(sceKernelSignalSema(sema, 1)); +} + +int APS5_VABI sceNpCreateThread(Pthread* thread, PthreadEntry entry, void* arg, int priority, std::size_t stackSize, KernelCpumask affinity, const char* name) { + PthreadAttr attr {}; + int result = scePthreadAttrInit(&attr); + if (result < 0) return result; + result = scePthreadAttrSetstacksize(&attr, stackSize); + if (result < 0) { + scePthreadAttrDestroy(&attr); + + return result; + } + if (priority != 0) { + result = scePthreadAttrSetinheritsched(&attr, NP_THREAD_EXPLICIT_SCHED); + if (result >= 0) result = scePthreadAttrSetschedpolicy(&attr, NP_THREAD_SCHED_POLICY); + if (result >= 0) { + KernelSchedParam param {}; + param.sched_priority = priority; + result = scePthreadAttrSetschedparam(&attr, ¶m); + } + } + if (result >= 0 && affinity != 0) result = scePthreadAttrSetaffinity(&attr, affinity); + if (result >= 0) result = scePthreadCreate(thread, &attr, entry, arg, name); + scePthreadAttrDestroy(&attr); + + return result; +} + +int APS5_VABI sceNpThreadGetId(Pthread* thread) { + if (thread == nullptr) return NP_ERROR_INVALID_ARGUMENT; + *thread = scePthreadSelf(); + return 0; +} + +int APS5_VABI sceNpGetProcessId(void) { + return getpid_nid_postfix(); +} + +[[noreturn]] void APS5_VABI sceNpPanic(void) { + std::abort(); +} + +int APS5_VABI sceNpDbgDumpText(const char* text, int length) { + return std::printf("%.*s", length, text); +} + +int APS5_VABI sceNpIpc2ClientInit(void) { + std::lock_guard lock(g_ipcClientMutex); + if (g_ipcClientInitialized) return NP_ERROR_IPC_ALREADY_INITIALIZED; + const int result = InitMemoryHeap(&g_ipcClientHeap, NP_IPC_CLIENT_HEAP_SIZE, NP_IPC_CLIENT_HEAP_NAME); + if (result < 0) return result; + g_ipcClientInitialized = true; + return 0; +} + +int APS5_VABI sceNpIpc2ClientTerm(void) { + std::lock_guard lock(g_ipcClientMutex); + if (!g_ipcClientInitialized) return NP_ERROR_IPC_NOT_INITIALIZED; + DestroyMemoryHeap(&g_ipcClientHeap); + g_ipcClientInitialized = false; + return 0; +} + +int APS5_VABI sceNpArchInit(void) { + std::lock_guard lock(g_archMutex); + if (g_archReferences != 0) { + ++g_archReferences; + return 0; + } + const int result = InitMemoryHeap(&g_globalHeap, NP_GLOBAL_HEAP_SIZE, NP_GLOBAL_HEAP_NAME); + if (result < 0) return result; + g_archReferences = 1; + return 0; +} + +void APS5_VABI sceNpArchTerm(void) { + std::lock_guard lock(g_archMutex); + if (g_archReferences == 0) return; + if (--g_archReferences == 0) DestroyMemoryHeap(&g_globalHeap); +} + +int APS5_VABI sceNpHeapInit(NpHeap* heap, void* base, std::size_t size, const char* name) { + return InitHeap(heap, base, size, name); +} + +void APS5_VABI sceNpHeapDestroy(NpHeap* heap) { + DestroyHeap(heap); +} + +void APS5_VABI sceNpHeapGetAllocator(NpHeap* heap, NpAllocator* allocator) { + *allocator = {HeapAllocate, HeapReallocate, HeapRelease, heap}; +} + +int APS5_VABI sceNpHeapGetStat(const NpHeap* heap, NpHeapStat* stat) { + MallocManagedSize stats {}; + stats.size = sizeof(stats); + stats.version = 1; + const int result = sceLibcMspaceMallocStats_nid_postfix(heap->mspace, &stats); + if (result == 0) *stat = {stats.maxSystemSize, stats.maxInuseSize, stats.currentInuseSize}; + return result; +} + +void APS5_VABI sceNpHeapShowStat(const NpHeap* heap) { + NpHeapStat stat {}; + if (sceNpHeapGetStat(heap, &stat) != 0) { + std::printf("[NpCommon] heap statistics unavailable\n"); + return; + } + std::printf("[NpCommon] heap: system %llu bytes, peak in use %llu bytes, in use %llu bytes\n", + static_cast(stat.maxSystemSize), static_cast(stat.maxInuseSize), + static_cast(stat.currentInuseSize)); +} + +int APS5_VABI sceNpMemoryHeapInit(NpMemoryHeap* heap, std::uint64_t size, const char* name) { + return InitMemoryHeap(heap, size, name); +} + +void APS5_VABI sceNpMemoryHeapDestroy(NpMemoryHeap* heap) { + DestroyMemoryHeap(heap); +} + +void APS5_VABI sceNpMemoryHeapGetAllocator(NpMemoryHeap* heap, NpAllocator* allocator) { + *allocator = MemoryHeapAllocator(heap); +} + +void APS5_VABI sceNpMemoryHeapGetAllocatorEx(NpMemoryHeap* heap, int outOfMemoryError, NpAllocatorEx* allocator) { + *allocator = {MemoryHeapAllocator(heap), outOfMemoryError}; +} + +void APS5_VABI sceNpGlobalHeapGetAllocator(NpAllocator* allocator) { + *allocator = MemoryHeapAllocator(&g_globalHeap); +} + +void APS5_VABI sceNpGlobalHeapGetAllocatorEx(NpAllocatorEx* allocator) { + *allocator = {MemoryHeapAllocator(&g_globalHeap), NP_ERROR_OUT_OF_MEMORY}; +} + +NpAllocator* APS5_VABI sceNpGlobalHeapGetAllocatorPtr(void) { + g_globalAllocator = MemoryHeapAllocator(&g_globalHeap); + return &g_globalAllocator; +} + +NpAllocatorEx* APS5_VABI sceNpGlobalHeapGetAllocatorExPtr(void) { + g_globalAllocatorEx = {MemoryHeapAllocator(&g_globalHeap), NP_ERROR_OUT_OF_MEMORY}; + return &g_globalAllocatorEx; +} + +int APS5_VABI sceNpAllocateKernelMemoryNoAlignment(std::uint64_t size, const char* name, void** address, NpKernelMemory* memory) { + if (memory == nullptr) return NP_ERROR_NULL_OBJECT; + const int result = MapKernelMemory(memory, size, name); + if (result >= 0 && address != nullptr) *address = memory->address; + return result; +} + +int APS5_VABI sceNpAllocateKernelMemoryWithAlignment(std::uint64_t size, const char* name, void** address, NpKernelMemory* memory) { + return sceNpAllocateKernelMemoryNoAlignment(AlignKernelMemory(size), name, address, memory); +} + +int APS5_VABI sceNpFreeKernelMemory(NpKernelMemory* memory) { + if (memory == nullptr) return NP_ERROR_NULL_OBJECT; + UnmapKernelMemory(memory); + return 0; +} + +void APS5_VABI sceNpGetSdkVersionUInt(std::uint32_t* version) { + const std::uint32_t value = SystemVersion(); + if (version != nullptr) *version = value; +} + +int APS5_VABI sceNpRtcConvertToPosixTime(const RtcTick* tick, std::int64_t* seconds) { + if (tick->tick == 0) return NP_ERROR_INVALID_ARGUMENT; + RtcDateTime time {}; + const int result = sceRtcSetTick(&time, tick); + if (result < 0) return result; + return NpResult(sceRtcGetTime_t(&time, seconds)); +} + +int APS5_VABI sceNpRtcFormatRFC3339(char* text, std::size_t size, const RtcTick* utc, int timeZoneMinutes) { + if (size < NP_RFC3339_BUFFER_MIN) return NP_ERROR_BUFFER_TOO_SMALL; + const int result = sceRtcFormatRFC3339(text, utc, timeZoneMinutes); + if (result < 0) return result; + text[size - 1] = 0; + return 0; +} + +int APS5_VABI sceNpUserGetUserIdList(int* users, std::size_t capacity, int* count) { + if (capacity < NP_USER_ID_LIST_SIZE) return NP_ERROR_INVALID_ARGUMENT; + int registered[NP_USER_ID_LIST_SIZE]; + std::fill(std::begin(registered), std::end(registered), USER_ID_INVALID); + static_assert(sizeof(UserServiceRegisteredUserIdList) <= sizeof(registered)); + const int result = sceUserServiceGetRegisteredUserIdList(reinterpret_cast(registered)); + if (result < 0) return result; + int found = 0; + for (const int user : registered) + if (user != USER_ID_INVALID) users[found++] = user; + *count = found; + return 0; +} + +int APS5_VABI sceNpRtcParseRFC3339(RtcTick* utc, const char* text) { + return sceRtcParseRFC3339(utc, text); +} + +static std::uint64_t ClockUsec() { + std::int64_t usec = 0; + sceNpGetSystemClockUsec(&usec); + + return static_cast(usec); +} + +static void* APS5_VABI CalloutThread(void* arg) { + auto* context = static_cast(arg); + sceNpMutexLock(&context->mutex); + + for (;;) { + const std::uint64_t now = ClockUsec(); + NpCallout* head = context->pending; + if (head != nullptr && head->deadline <= now) { + context->pending = head->next; + sceNpMutexUnlock(&context->mutex); + + head->handler(head->arg); + sceNpMutexLock(&context->mutex); + + continue; + } + if (context->stopping > 0) break; + const std::uint64_t wait = head != nullptr ? std::min(head->deadline - now, NP_CALLOUT_MAX_WAIT) : 0; + sceNpCondTimedwait(&context->cond, &context->mutex, static_cast(wait)); + + if (context->stopping > 0) break; + } + sceNpMutexUnlock(&context->mutex); + + return nullptr; +} + +int APS5_VABI sceNpCalloutInitCtx(NpCalloutContext* context, const char* name, std::size_t stackSize, int priority, + KernelCpumask affinity) { + if (context->initialized > 0) return NP_ERROR_ALREADY_INITIALIZED; + context->stopping = 0; + context->pending = nullptr; + int result = sceNpMutexInit(&context->mutex, name, 1); + if (result < 0) return result; + result = sceNpCondInit(&context->cond, name, 0); + if (result >= 0) { + result = sceNpCreateThread(&context->thread, CalloutThread, context, priority, stackSize, affinity, name); + if (result >= 0) { + context->initialized = 1; + return 0; + } + sceNpCondDestroy(&context->cond); + + } + sceNpMutexDestroy(&context->mutex); + + return result; +} + +void APS5_VABI sceNpCalloutTermCtx(NpCalloutContext* context) { + if (context->initialized == 0) return; + sceNpMutexLock(&context->mutex); + + context->stopping = 1; + sceNpCondSignal(&context->cond); + sceNpMutexUnlock(&context->mutex); + sceNpJoinThread(context->thread, nullptr); + sceNpCondDestroy(&context->cond); + sceNpMutexDestroy(&context->mutex); + + context->initialized = 0; +} + +int APS5_VABI sceNpCalloutStartOnCtx64(NpCalloutContext* context, NpCallout* callout, std::uint64_t usec, + void (APS5_VABI *handler)(void* arg), void* arg) { + if (context->initialized == 0) return NP_ERROR_NOT_INITIALIZED; + const std::uint64_t deadline = ClockUsec() + usec; + sceNpMutexLock(&context->mutex); + + for (NpCallout* entry = context->pending; entry != nullptr; entry = entry->next) { + if (entry == callout) { + sceNpMutexUnlock(&context->mutex); + + return NP_ERROR_ALREADY_QUEUED; + } + } + *callout = {}; + callout->deadline = deadline; + callout->handler = handler; + callout->arg = arg; + NpCallout** link = &context->pending; + while (*link != nullptr && callout->deadline > (*link)->deadline) link = &(*link)->next; + callout->next = *link; + *link = callout; + sceNpCondSignal(&context->cond); + sceNpMutexUnlock(&context->mutex); + + return 0; +} + +int APS5_VABI sceNpCalloutStartOnCtx(NpCalloutContext* context, NpCallout* callout, std::uint32_t usec, + void (APS5_VABI *handler)(void* arg), void* arg) { + return sceNpCalloutStartOnCtx64(context, callout, usec, handler, arg); +} + +int APS5_VABI sceNpCalloutStopOnCtx(NpCalloutContext* context, NpCallout* callout, int* wasPending) { + if (context->initialized == 0) return NP_ERROR_NOT_INITIALIZED; + sceNpMutexLock(&context->mutex); + + int removed = 0; + for (NpCallout** link = &context->pending; *link != nullptr; link = &(*link)->next) { + if (*link != callout) continue; + *link = callout->next; + removed = 1; + break; + } + if (wasPending != nullptr) *wasPending = removed; + sceNpMutexUnlock(&context->mutex); + + return 0; +} + +std::uint32_t APS5_VABI sceNpGetRandom(void) { + std::lock_guard lock(g_randomMutex); + return static_cast(g_random()); +} + +int APS5_VABI sceNpUtilCmpAccountId(std::uint64_t left, std::uint64_t right) { + return left != right ? 1 : 0; +} + +int APS5_VABI sceNpCmpNpId(const void* npId1, const void* npId2) { + if (!npId1 || !npId2) return NP_ERROR_NULL_ARGUMENT; + const auto* left = static_cast(npId1); + const auto* right = static_cast(npId2); + if (left[NP_ID_TERMINATOR_FLAG_OFFSET] != 1 || right[NP_ID_TERMINATOR_FLAG_OFFSET] != 1) return NP_ERROR_INVALID_NP_ID; + if (std::strncmp(static_cast(npId1), static_cast(npId2), NP_ONLINE_ID_MAX) != 0) return NP_ERROR_ONLINE_ID_MISMATCH; + if (NpIdWord(left, NP_ID_OPT_OFFSET) != NpIdWord(right, NP_ID_OPT_OFFSET)) return NP_ERROR_ONLINE_ID_MISMATCH; + if (NpIdWord(left, NP_ID_PLATFORM_OFFSET) == NpIdWord(right, NP_ID_PLATFORM_OFFSET)) return 0; + if (left[NP_ID_PLATFORM_OFFSET] == 0 || right[NP_ID_PLATFORM_OFFSET] == 0) return 0; + return NP_ERROR_ONLINE_ID_MISMATCH; +} + +bool APS5_VABI sceNpIntIsOnlineIdString(const char* text) { + return text != nullptr && IsOnlineIdText(text); +} + +int APS5_VABI sceNpUtilGetNpLanguageCode(int language, char* code) { + const NpLanguageCodes* codes = FindLanguageCodes(language); + if (codes == nullptr || code == nullptr) return NP_ERROR_INVALID_ARGUMENT_UTIL; + CopyText(code, codes->code, NP_LANGUAGE_CODE_SIZE); + return 0; +} + +int APS5_VABI sceNpUtilGetNpLanguageCode2(int language, char* tag) { + const NpLanguageCodes* codes = FindLanguageCodes(language); + if (codes == nullptr || tag == nullptr) return NP_ERROR_INVALID_ARGUMENT_UTIL; + CopyText(tag, codes->tag, NP_LANGUAGE_CODE2_SIZE); + return 0; +} + +const char* APS5_VABI sceNpUtilGetNpLanguageCodeStr(int language) { + const NpLanguageCodes* codes = FindLanguageCodes(language); + return codes != nullptr ? codes->code : nullptr; +} + +const char* APS5_VABI sceNpUtilGetNpLanguageCode2Str(int language) { + const NpLanguageCodes* codes = FindLanguageCodes(language); + return codes != nullptr ? codes->tag : nullptr; +} + +std::uint32_t APS5_VABI sceNpDbgAssignDebugId(void) { + std::uint32_t id = g_debugIds.fetch_add(1) + 1; + if (id == 0) id = g_debugIds.fetch_add(1) + 1; + return id; +} + +int APS5_VABI sceNpUtilBuildTitleId(const NpTitleIdParts* parts, char* titleId, std::size_t size) { + if (parts == nullptr || titleId == nullptr) return NP_ERROR_INVALID_ARGUMENT_UTIL; + if (size < NP_TITLE_ID_SIZE) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + if (parts->index > NP_TITLE_ID_INDEX_MAX) return NP_UTIL_ERROR_OUT_OF_RANGE; + std::memcpy(titleId, parts->id, sizeof(parts->id)); + titleId[9] = '_'; + titleId[10] = static_cast('0' + parts->index / 10); + titleId[11] = static_cast('0' + parts->index % 10); + titleId[12] = '\0'; + return 0; +} + +void APS5_VABI sceNpDbgDumpBinary(const void* data, std::size_t size) { + const auto* bytes = static_cast(data); + for (std::size_t line = 0; line < size; line += NP_DUMP_LINE_BYTES) { + const std::size_t count = std::min(NP_DUMP_LINE_BYTES, size - line); + for (std::size_t i = 0; i < NP_DUMP_LINE_BYTES; ++i) { + if (i < count) { + std::printf("%02x", bytes[line + i]); + } else { + std::printf(" "); + } + if ((i & 1) != 0) std::putchar(' '); + } + std::putchar(' '); + for (std::size_t i = 0; i < count; ++i) { + const std::uint8_t c = bytes[line + i]; + std::putchar(c >= 0x20 && c <= 0x7E ? c : '.'); + } + std::putchar('\n'); + } + std::putchar('\n'); +} + +} diff --git a/core/libs/prx/libSceNpCommon/Strings.cpp b/core/libs/prx/libSceNpCommon/Strings.cpp new file mode 100644 index 0000000000..08125a4c0f --- /dev/null +++ b/core/libs/prx/libSceNpCommon/Strings.cpp @@ -0,0 +1,630 @@ +#include "prx/libSceNpCommon/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "SceTypes.hpp" + +namespace { + +constexpr int NP_ERROR_INVALID_FORMAT = static_cast(0x80558003); +constexpr int NP_ERROR_INVALID_CHARACTER = static_cast(0x80558004); +constexpr int NP_ERROR_BUFFER_TOO_SMALL = static_cast(0x80558008); +constexpr int NP_ERROR_INVALID_ARGUMENT = static_cast(0x80558009); +constexpr int NP_ERROR_OUT_OF_RANGE = static_cast(0x8055800E); + +constexpr std::uint32_t NP_FORMAT_UPPERCASE = 1; +constexpr std::uint32_t NP_FORMAT_PREFIX = 2; +constexpr std::uint32_t NP_FORMAT_SIGN = 4; +constexpr std::uint32_t NP_RADIX_MAX = 36; + +constexpr char BASE64_ALPHABET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +constexpr char BASE64URL_ALPHABET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +constexpr char BASE64_PAD = '='; + +int Base64Value(char c, const char* alphabet) { + if (c == '\0') return -1; + const char* found = std::strchr(alphabet, c); + return found != nullptr ? static_cast(found - alphabet) : -1; +} + +struct Base64Block { + std::uint32_t bits; + int bytes; +}; + +int DecodeLastBlock(const char* src, std::size_t length, const char* alphabet, Base64Block* block) { + char c[4] = {BASE64_PAD, BASE64_PAD, BASE64_PAD, BASE64_PAD}; + std::memcpy(c, src, length); + const int v0 = Base64Value(c[0], alphabet); + const int v1 = Base64Value(c[1], alphabet); + if (v0 < 0 || v1 < 0) return NP_ERROR_INVALID_CHARACTER; + block->bits = (static_cast(v0) << 18) | (static_cast(v1) << 12); + if (c[2] == BASE64_PAD) { + if (c[3] != BASE64_PAD) return NP_ERROR_INVALID_CHARACTER; + block->bytes = 1; + return 0; + } + const int v2 = Base64Value(c[2], alphabet); + if (v2 < 0) return NP_ERROR_INVALID_CHARACTER; + block->bits |= static_cast(v2) << 6; + if (c[3] == BASE64_PAD) { + block->bytes = 2; + return 0; + } + const int v3 = Base64Value(c[3], alphabet); + if (v3 < 0) return NP_ERROR_INVALID_CHARACTER; + block->bits |= static_cast(v3); + block->bytes = 3; + return 0; +} + +int Base64Decode(const char* src, std::size_t length, std::uint8_t* dst, std::size_t capacity, const char* alphabet, + bool padded) { + if (padded && (length & 3) != 0) return NP_ERROR_INVALID_FORMAT; + int decoded = 0; + while (length > 4) { + std::uint32_t bits = 0; + for (int i = 0; i < 4; ++i) { + const int value = Base64Value(src[i], alphabet); + if (value < 0) return NP_ERROR_INVALID_CHARACTER; + bits = (bits << 6) | static_cast(value); + } + if (dst != nullptr) { + if (capacity < 3) return NP_ERROR_INVALID_FORMAT; + capacity -= 3; + *dst++ = static_cast(bits >> 16); + *dst++ = static_cast(bits >> 8); + *dst++ = static_cast(bits); + } + decoded += 3; + src += 4; + length -= 4; + } + if (padded && length != 4) return NP_ERROR_INVALID_FORMAT; + Base64Block block {}; + const int result = DecodeLastBlock(src, length, alphabet, &block); + if (result < 0) return result; + if (dst != nullptr) { + if (capacity < static_cast(block.bytes)) return NP_ERROR_INVALID_FORMAT; + for (int i = 0; i < block.bytes; ++i) dst[i] = static_cast(block.bits >> (16 - 8 * i)); + } + return decoded + block.bytes; +} + +int Base64Encode(const std::uint8_t* src, char* dst, std::size_t length, const char* alphabet, bool padded) { + char* out = dst; + for (; length >= 3; length -= 3, src += 3) { + const std::uint32_t bits = (src[0] << 16) | (src[1] << 8) | src[2]; + for (int shift = 18; shift >= 0; shift -= 6) *out++ = alphabet[(bits >> shift) & 0x3F]; + } + if (length != 0) { + const std::uint32_t bits = (src[0] << 16) | (length == 2 ? src[1] << 8 : 0); + *out++ = alphabet[(bits >> 18) & 0x3F]; + *out++ = alphabet[(bits >> 12) & 0x3F]; + if (length == 2) *out++ = alphabet[(bits >> 6) & 0x3F]; + if (padded) { + if (length == 1) *out++ = BASE64_PAD; + *out++ = BASE64_PAD; + } + } + return static_cast(out - dst); +} + +int HexValue(char 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 -1; +} + +char HexDigit(std::uint32_t value, bool uppercase) { + if (value < 10) return static_cast('0' + value); + return static_cast((uppercase ? 'A' : 'a') + value - 10); +} + +int ParseHex(const char* hex, std::size_t length, std::uint8_t* out, std::size_t capacity) { + if ((length & 1) != 0) return NP_ERROR_INVALID_ARGUMENT; + if (capacity < length / 2) return NP_ERROR_BUFFER_TOO_SMALL; + for (std::size_t i = 0; i < length; i += 2) { + const int high = HexValue(hex[i]); + const int low = HexValue(hex[i + 1]); + if (high < 0 || low < 0) return NP_ERROR_INVALID_ARGUMENT; + out[i / 2] = static_cast((high << 4) | low); + } + return 0; +} + +int FormatInteger(char* buf, std::size_t size, std::uint64_t magnitude, char sign, std::uint32_t radix, + std::uint32_t flags, std::size_t* length) { + if (radix < 1 || radix > NP_RADIX_MAX) return NP_ERROR_INVALID_ARGUMENT; + char prefix[3] = {}; + std::size_t prefixLength = 0; + if (sign != '\0') prefix[prefixLength++] = sign; + if ((flags & NP_FORMAT_PREFIX) != 0 && (radix == 8 || radix == 16)) { + prefix[prefixLength++] = '0'; + if (radix == 16) prefix[prefixLength++] = (flags & NP_FORMAT_UPPERCASE) != 0 ? 'X' : 'x'; + } + std::size_t digits = 1; + if (radix > 1) { + for (std::uint64_t rest = magnitude; rest >= radix; rest /= radix) ++digits; + } + if (buf != nullptr) { + if (size < prefixLength + digits + 1) return NP_ERROR_BUFFER_TOO_SMALL; + std::memcpy(buf, prefix, prefixLength); + char* end = buf + prefixLength + digits; + *end = '\0'; + std::uint64_t rest = magnitude; + for (std::size_t i = 0; i < digits; ++i) { + *--end = HexDigit(static_cast(rest % radix), (flags & NP_FORMAT_UPPERCASE) != 0); + rest /= radix; + } + } + if (length != nullptr) *length = prefixLength + digits; + return 0; +} + +std::uint64_t Magnitude(std::int64_t value) { + return value < 0 ? 0 - static_cast(value) : static_cast(value); +} + +char PlusSign(bool positive, std::uint32_t flags) { + return positive && (flags & NP_FORMAT_SIGN) != 0 ? '+' : '\0'; +} + +template +int ParseInteger(const char* str, std::size_t* parsedLength, Parse parse, bool* saturated) { + char* end = nullptr; + errno = 0; + parse(str, &end); + if (end == str) return NP_ERROR_OUT_OF_RANGE; + if (*saturated && errno == ERANGE) return NP_ERROR_OUT_OF_RANGE; + if (parsedLength != nullptr) *parsedLength = static_cast(end - str); + return 0; +} + +int CopyToBuffer(char* dst, std::size_t size, const char* src, std::size_t length) { + if (size < length + 1) return NP_ERROR_BUFFER_TOO_SMALL; + std::strncpy(dst, src, size - 1); + dst[size - 1] = '\0'; + return 0; +} + +constexpr int NP_UTIL_ERROR_INVALID_ARGUMENT = static_cast(0x80550601); +constexpr int NP_UTIL_ERROR_BUFFER_TOO_SMALL = static_cast(0x80550602); +constexpr int NP_UTIL_ERROR_INVALID_CHARACTER = static_cast(0x80550608); +constexpr std::size_t JSON_ESCAPE_MARGIN = 7; +constexpr std::size_t JSON_UNICODE_ESCAPE_LENGTH = 6; +constexpr std::size_t JSON_SURROGATE_PAIR_LENGTH = 12; +constexpr std::uint32_t SURROGATE_MASK = 0xFC00; +constexpr std::uint32_t HIGH_SURROGATE = 0xD800; +constexpr std::uint32_t LOW_SURROGATE = 0xDC00; +constexpr char URL_HEX_DIGITS[] = "0123456789ABCDEF"; + +const char* JsonShortEscape(unsigned char c) { + switch (c) { + case '\b': return "\\b"; + case '\t': return "\\t"; + case '\n': return "\\n"; + case '\f': return "\\f"; + case '\r': return "\\r"; + case '"': return "\\\""; + case '/': return "\\/"; + case '\\': return "\\\\"; + default: return nullptr; + } +} + +char JsonShortUnescape(char c) { + switch (c) { + case '"': return '"'; + case '\\': return '\\'; + case '/': return '/'; + case 'b': return '\b'; + case 'f': return '\f'; + case 'n': return '\n'; + case 'r': return '\r'; + case 't': return '\t'; + default: return '\0'; + } +} + +int ParseUtf16Unit(const char* text, std::uint32_t* unit) { + *unit = 0; + for (int i = 0; i < 4; ++i) { + const int value = HexValue(text[i]); + if (value < 0) { + *unit = 0; + return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + } + *unit = (*unit << 4) | static_cast(value); + } + return 0; +} + +std::size_t EncodeUtf8(std::uint32_t codePoint, char* out) { + if (codePoint <= 0x7F) { + out[0] = static_cast(codePoint); + return 1; + } + if (codePoint <= 0x7FF) { + out[0] = static_cast(0xC0 | (codePoint >> 6)); + out[1] = static_cast(0x80 | (codePoint & 0x3F)); + return 2; + } + if (codePoint <= 0xFFFF) { + out[0] = static_cast(0xE0 | (codePoint >> 12)); + out[1] = static_cast(0x80 | ((codePoint >> 6) & 0x3F)); + out[2] = static_cast(0x80 | (codePoint & 0x3F)); + return 3; + } + out[0] = static_cast(0xF0 | (codePoint >> 18)); + out[1] = static_cast(0x80 | ((codePoint >> 12) & 0x3F)); + out[2] = static_cast(0x80 | ((codePoint >> 6) & 0x3F)); + out[3] = static_cast(0x80 | (codePoint & 0x3F)); + return 4; +} + +std::uint32_t CombineSurrogates(std::uint32_t high, std::uint32_t low) { + return 0x10000 + (((high & 0x3FF) << 10) | (low & 0x3FF)); +} + +bool IsContinuation(char c) { + return (static_cast(c) & 0xC0) == 0x80; +} + +std::size_t Utf8SequenceLength(unsigned char lead) { + if (lead >= 0xC2 && lead <= 0xDF) return 2; + if ((lead & 0xF0) == 0xE0) return 3; + if ((lead & 0xF8) == 0xF0) return 4; + return 0; +} + +bool IsValidSequence(const char* text, std::size_t length) { + for (std::size_t i = 1; i < length; ++i) { + if (!IsContinuation(text[i])) return false; + } + return true; +} + +bool IsAllowedTextByte(unsigned char c) { + return (c >= 0x20 && c != 0x7F) || c == '\t' || c == '\n' || c == '\r'; +} + +bool IsUrlSafe(unsigned char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' + || c == '*'; +} + +} // namespace + +extern "C" { + +int APS5_VABI sceNpBase64GetDecodeSize(const char* src, std::size_t length) { + return Base64Decode(src, length, nullptr, 0, BASE64_ALPHABET, true); +} + +int APS5_VABI sceNpBase64Decoder(const char* src, std::size_t length, void* dst, std::size_t capacity) { + return Base64Decode(src, length, static_cast(dst), capacity, BASE64_ALPHABET, true); +} + +int APS5_VABI sceNpBase64Encoder(const void* src, char* dst, std::size_t length) { + return Base64Encode(static_cast(src), dst, length, BASE64_ALPHABET, true); +} + +int APS5_VABI sceNpBase64UrlGetDecodeSize(const char* src, std::size_t length) { + return Base64Decode(src, length, nullptr, 0, BASE64URL_ALPHABET, false); +} + +int APS5_VABI sceNpBase64UrlDecoder(const char* src, std::size_t length, void* dst, std::size_t capacity) { + return Base64Decode(src, length, static_cast(dst), capacity, BASE64URL_ALPHABET, false); +} + +int APS5_VABI sceNpBase64UrlEncoder(const void* src, char* dst, std::size_t length) { + return Base64Encode(static_cast(src), dst, length, BASE64URL_ALPHABET, false); +} + +int APS5_VABI sceNpHexToInt(int c) { + return HexValue(static_cast(c)); +} + +int APS5_VABI sceNpIntToHex(std::uint32_t value, bool uppercase) { + return HexDigit(value, uppercase); +} + +int APS5_VABI sceNpUInt32ToStr(char* buf, std::size_t size, std::uint32_t value, std::uint32_t radix, std::uint32_t flags, + std::size_t* length) { + return FormatInteger(buf, size, value, PlusSign(value != 0, flags), radix, flags, length); +} + +int APS5_VABI sceNpUInt64ToStr(char* buf, std::size_t size, std::uint64_t value, std::uint32_t radix, std::uint32_t flags, + std::size_t* length) { + return FormatInteger(buf, size, value, PlusSign(value != 0, flags), radix, flags, length); +} + +int APS5_VABI sceNpInt32ToStr(char* buf, std::size_t size, std::int32_t value, std::uint32_t radix, std::uint32_t flags, + std::size_t* length) { + const char sign = value < 0 ? '-' : PlusSign(value > 0, flags); + return FormatInteger(buf, size, Magnitude(value), sign, radix, flags, length); +} + +int APS5_VABI sceNpInt64ToStr(char* buf, std::size_t size, std::int64_t value, std::uint32_t radix, std::uint32_t flags, + std::size_t* length) { + const char sign = (flags & NP_FORMAT_SIGN) == 0 ? '\0' : value < 0 ? '-' : PlusSign(value > 0, flags); + return FormatInteger(buf, size, Magnitude(value), sign, radix, flags, length); +} + +int APS5_VABI sceNpStrToInt32(const char* str, std::int32_t* value, int base, std::size_t* parsedLength) { + long long parsed = 0; + bool saturated = false; + const int result = ParseInteger(str, parsedLength, [&](const char* s, char** end) { + parsed = std::strtoll(s, end, base); + saturated = parsed == LLONG_MIN || parsed == LLONG_MAX; + }, &saturated); + if (result < 0) return result; + if (parsed != static_cast(parsed)) return NP_ERROR_OUT_OF_RANGE; + *value = static_cast(parsed); + return 0; +} + +int APS5_VABI sceNpStrToInt64(const char* str, std::int64_t* value, int base, std::size_t* parsedLength) { + long long parsed = 0; + bool saturated = false; + const int result = ParseInteger(str, parsedLength, [&](const char* s, char** end) { + parsed = std::strtoll(s, end, base); + saturated = parsed == LLONG_MIN || parsed == LLONG_MAX; + }, &saturated); + if (result < 0) return result; + *value = parsed; + return 0; +} + +int APS5_VABI sceNpStrToUInt32(const char* str, std::uint32_t* value, int base, std::size_t* parsedLength) { + unsigned long long parsed = 0; + bool saturated = false; + const int result = ParseInteger(str, parsedLength, [&](const char* s, char** end) { + parsed = std::strtoull(s, end, base); + saturated = parsed == ULLONG_MAX; + }, &saturated); + if (result < 0) return result; + if ((parsed >> 32) != 0) return NP_ERROR_OUT_OF_RANGE; + *value = static_cast(parsed); + return 0; +} + +int APS5_VABI sceNpStrToUInt64(const char* str, std::uint64_t* value, int base, std::size_t* parsedLength) { + unsigned long long parsed = 0; + bool saturated = false; + const int result = ParseInteger(str, parsedLength, [&](const char* s, char** end) { + parsed = std::strtoull(s, end, base); + saturated = parsed == ULLONG_MAX; + }, &saturated); + if (result < 0) return result; + *value = parsed; + return 0; +} + +int APS5_VABI sceNpStrBuildHex(char* dst, std::size_t size, const void* src, std::size_t length, bool uppercase) { + if (size <= length * 2) return NP_ERROR_BUFFER_TOO_SMALL; + const auto* bytes = static_cast(src); + for (std::size_t i = 0; i < length; ++i) { + dst[i * 2] = HexDigit(bytes[i] >> 4, uppercase); + dst[i * 2 + 1] = HexDigit(bytes[i] & 0xF, uppercase); + } + dst[length * 2] = '\0'; + return 0; +} + +int APS5_VABI sceNpStrParseHex(const char* hex, void* out, std::size_t capacity) { + return ParseHex(hex, std::strlen(hex), static_cast(out), capacity); +} + +int APS5_VABI sceNpStrnParseHex(const char* hex, std::size_t length, void* out, std::size_t capacity) { + return ParseHex(hex, length, static_cast(out), capacity); +} + +int APS5_VABI sceNpStrcpyToBuf(char* dst, std::size_t size, const char* src) { + return CopyToBuffer(dst, size, src, std::strlen(src)); +} + +int APS5_VABI sceNpStrncpyToBuf(char* dst, std::size_t size, const char* src, std::size_t maxLength) { + return CopyToBuffer(dst, size, src, strnlen(src, maxLength)); +} + +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; + 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]); + if (c == 0) return 0; + char piece[JSON_ESCAPE_MARGIN] = {}; + if (const char* escape = JsonShortEscape(c)) { + std::strcpy(piece, escape); + } else if (c >= 0x20 && c != 0x7F) { + piece[0] = static_cast(c); + } else { + std::snprintf(piece, sizeof(piece), "\\u%04x", c); + } + std::strcat(dst, piece); + written += std::strlen(piece); + } + return 0; +} + +int APS5_VABI sceNpUtilJsonUnescape(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, + std::size_t* dstLength) { + if (src == nullptr || srcLength == 0) return NP_UTIL_ERROR_INVALID_ARGUMENT; + if (dst == nullptr && dstLength == nullptr) return NP_UTIL_ERROR_INVALID_ARGUMENT; + const char* end = src + srcLength; + std::size_t count = 0; + int status = 0; + char* out = dst; + auto emit = [&](const char* bytes, std::size_t length, std::size_t consumed) { + if (out != nullptr) { + std::memcpy(out, bytes, length); + out += length; + } + count += length; + src += consumed; + }; + while (src < end) { + if (dst != nullptr && count + 1 > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + const std::size_t remaining = static_cast(end - src); + if (src[0] != '\\' || remaining < 2) { + emit(src, 1, 1); + continue; + } + if (const char simple = JsonShortUnescape(src[1])) { + emit(&simple, 1, 2); + continue; + } + if (src[1] != 'u' || remaining < JSON_UNICODE_ESCAPE_LENGTH) { + emit(src, 1, 1); + continue; + } + std::uint32_t unit = 0; + int result = ParseUtf16Unit(src + 2, &unit); + if (result < 0) { + status = result; + emit(src, 1, 1); + continue; + } + status = 0; + char encoded[4] = {}; + if ((unit & SURROGATE_MASK) != HIGH_SURROGATE) { + const std::size_t length = EncodeUtf8(unit, encoded); + if (dst != nullptr && count + length > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + emit(encoded, length, JSON_UNICODE_ESCAPE_LENGTH); + continue; + } + std::uint32_t low = 0; + if (remaining < JSON_SURROGATE_PAIR_LENGTH || src[6] != '\\' || src[7] != 'u') { + emit(src, 1, 1); + continue; + } + result = ParseUtf16Unit(src + 8, &low); + if (result < 0) { + status = result; + emit(src, 1, 1); + continue; + } + if ((low & SURROGATE_MASK) != LOW_SURROGATE) { + emit(src, 1, 1); + continue; + } + const std::size_t length = EncodeUtf8(CombineSurrogates(unit, low), encoded); + if (dst != nullptr && count + length > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + emit(encoded, length, JSON_SURROGATE_PAIR_LENGTH); + } + ++count; + if (count > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + if (dstLength != nullptr) *dstLength = count; + if (out != nullptr) *out = '\0'; + return status; +} + +int APS5_VABI sceNpUtilJsonGetOneChar(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, + std::size_t* dstLength, std::size_t* consumed) { + if (src == nullptr || srcLength == 0) return NP_UTIL_ERROR_INVALID_ARGUMENT; + if (dst == nullptr && dstLength == nullptr) return NP_UTIL_ERROR_INVALID_ARGUMENT; + std::size_t decoded = 1; + std::size_t used = 1; + int status = 0; + char encoded[4] = {}; + const auto lead = static_cast(src[0]); + if (lead == '\\') { + used = 2; + if (srcLength < 2) { + status = NP_UTIL_ERROR_BUFFER_TOO_SMALL; + } else if (const char simple = JsonShortUnescape(src[1])) { + encoded[0] = simple; + } else if (src[1] != 'u') { + used = 1; + encoded[0] = '\\'; + } else { + used = JSON_UNICODE_ESCAPE_LENGTH; + std::uint32_t unit = 0; + if (srcLength < JSON_UNICODE_ESCAPE_LENGTH || ParseUtf16Unit(src + 2, &unit) < 0) { + status = NP_UTIL_ERROR_BUFFER_TOO_SMALL; + } else if ((unit & SURROGATE_MASK) != HIGH_SURROGATE) { + decoded = EncodeUtf8(unit, encoded); + } else { + used = JSON_SURROGATE_PAIR_LENGTH; + std::uint32_t low = 0; + if (srcLength < JSON_SURROGATE_PAIR_LENGTH || ParseUtf16Unit(src + 8, &low) < 0 + || (low & SURROGATE_MASK) != LOW_SURROGATE) { + status = NP_UTIL_ERROR_BUFFER_TOO_SMALL; + } else { + decoded = EncodeUtf8(CombineSurrogates(unit, low), encoded); + } + } + } + } else if (const std::size_t length = Utf8SequenceLength(lead); length != 0 && srcLength < length) { + decoded = 0; + used = length; + status = NP_UTIL_ERROR_BUFFER_TOO_SMALL; + } else if (length != 0 && IsValidSequence(src, length)) { + decoded = length; + used = length; + std::memcpy(encoded, src, length); + } else { + encoded[0] = src[0]; + } + if (status == 0 && dst != nullptr && dstSize < decoded) status = NP_UTIL_ERROR_BUFFER_TOO_SMALL; + if (status == 0 && dst != nullptr) std::memcpy(dst, encoded, decoded); + if (dstLength != nullptr) *dstLength = decoded; + if (consumed != nullptr) *consumed = used; + return status; +} + +int APS5_VABI sceNpUtilGetNthChar(std::size_t index, const char* text, const char** character) { + if (text == nullptr) return NP_UTIL_ERROR_INVALID_ARGUMENT; + const char* position = text; + for (std::size_t i = 0; i < index && *position != '\0'; ++i) { + const auto lead = static_cast(*position); + std::size_t length = 1; + if (lead < 0x80) { + if (!IsAllowedTextByte(lead)) return NP_UTIL_ERROR_INVALID_CHARACTER; + } else { + length = Utf8SequenceLength(lead); + if (length == 0 || !IsValidSequence(position, length)) return NP_UTIL_ERROR_INVALID_CHARACTER; + } + position += length; + } + *character = position; + return 0; +} + +int APS5_VABI sceNpUtilHttpUrlEncode(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, + std::size_t* dstLength) { + if (src == nullptr || srcLength == 0) return NP_UTIL_ERROR_INVALID_ARGUMENT; + if (dst == nullptr && dstLength == nullptr) return NP_UTIL_ERROR_INVALID_ARGUMENT; + std::size_t count = 0; + for (std::size_t i = 0; i < srcLength; ++i) { + const auto c = static_cast(src[i]); + const bool escaped = c != ' ' && !IsUrlSafe(c); + const std::size_t length = escaped ? 3 : 1; + if (dst != nullptr && count + length > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + if (dst != nullptr) { + if (escaped) { + dst[count] = '%'; + dst[count + 1] = URL_HEX_DIGITS[c >> 4]; + dst[count + 2] = URL_HEX_DIGITS[c & 0xF]; + } else { + dst[count] = c == ' ' ? '+' : static_cast(c); + } + } + count += length; + } + if (dst != nullptr && count + 1 > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + if (dst != nullptr) dst[count] = '\0'; + if (dstLength != nullptr) *dstLength = count + 1; + return 0; +} + +} diff --git a/core/libs/prx/libSceNpCommon/include/Api.hpp b/core/libs/prx/libSceNpCommon/include/Api.hpp new file mode 100644 index 0000000000..d4c373e182 --- /dev/null +++ b/core/libs/prx/libSceNpCommon/include/Api.hpp @@ -0,0 +1,222 @@ +#pragma once + +#include "SceTypes.hpp" + +struct NpAllocator { + void* (APS5_VABI *allocate)(std::size_t size, void* context); + void* (APS5_VABI *reallocate)(void* pointer, std::size_t size, void* context); + void (APS5_VABI *release)(void* pointer, void* context); + void* context; +}; + +struct NpAllocatorEx { + NpAllocator allocator; + int outOfMemoryError; +}; + +struct NpCallout { + NpCallout* next; + void (APS5_VABI *handler)(void* arg); + std::uint64_t deadline; + void* arg; + std::uint64_t reserved; +}; + +struct NpCalloutContext { + Pthread thread; + PthreadMutex mutex; + PthreadCond cond; + int initialized; + int stopping; + NpCallout* pending; +}; + +struct NpHeap { + void* mspace; +}; + +struct NpHeapStat { + std::uint64_t maxSystemSize; + std::uint64_t maxInuseSize; + std::uint64_t currentInuseSize; +}; + +struct NpIdFull { + NpOnlineId handle; + std::uint8_t opt[8]; + std::uint8_t reserved[8]; +}; + +struct NpKernelMemory { + std::uint64_t size; + std::uint64_t reserved; + void* address; +}; + +struct NpMemoryHeap { + NpKernelMemory memory; + NpHeap heap; +}; + +struct NpTitleIdParts { + char id[9]; + char reserved; + std::uint8_t index; +}; + +extern "C" { + +int APS5_VABI sceNpCmpOnlineId(const NpOnlineId* onlineId1, const NpOnlineId* onlineId2); +int APS5_VABI sceNpCmpNpIdInOrder(const NpIdFull* npId1, const NpIdFull* npId2, std::int32_t* order); +int APS5_VABI sceNpGetPlatformType(const NpIdFull* npId); +int APS5_VABI sceNpIntIsValidOnlineId(const NpOnlineId* onlineId); +int APS5_VABI sceNpMutexInit(PthreadMutex* mutex, const char* name, std::uint64_t flags); +int APS5_VABI sceNpMutexDestroy(PthreadMutex* mutex); +int APS5_VABI sceNpMutexLock(PthreadMutex* mutex); +int APS5_VABI sceNpMutexTryLock(PthreadMutex* mutex); +int APS5_VABI sceNpMutexUnlock(PthreadMutex* mutex); +int APS5_VABI sceNpLwMutexInit(PthreadMutex* mutex, const char* name, std::uint64_t flags); +int APS5_VABI sceNpLwMutexDestroy(PthreadMutex* mutex); +int APS5_VABI sceNpLwMutexLock(PthreadMutex* mutex); +int APS5_VABI sceNpLwMutexTryLock(PthreadMutex* mutex); +int APS5_VABI sceNpLwMutexUnlock(PthreadMutex* mutex); +int APS5_VABI sceNpCondInit(PthreadCond* cond, const char* name, std::uint64_t); +int APS5_VABI sceNpCondDestroy(PthreadCond* cond); +int APS5_VABI sceNpCondSignal(PthreadCond* cond); +int APS5_VABI sceNpCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, std::uint32_t usec); +int APS5_VABI sceNpJoinThread(Pthread thread, void** retval); +int APS5_VABI sceNpGetSystemClockUsec(std::int64_t* usec); +void APS5_VABI sceNpGetSdkVersion(char* version); +int APS5_VABI sceNpCloseSema(void); +int APS5_VABI sceNpGetNavSdkVersion(void); +int APS5_VABI sceNpIntGetPlatformType(void); +int APS5_VABI sceNpIntSetPlatformType(void); +int APS5_VABI sceNpJsonParse(void); +int APS5_VABI sceNpJsonParseBuf(void); +int APS5_VABI sceNpJsonParseBufInit(void); +int APS5_VABI sceNpJsonParseEx(void); +int APS5_VABI sceNpJsonParseExInit(void); +int APS5_VABI sceNpJsonParseInit(void); +int APS5_VABI sceNpOpenSema(void); +int APS5_VABI sceNpServerErrorJsonGetErrorCode(void); +int APS5_VABI sceNpServerErrorJsonMultiGetErrorCode(void); +int APS5_VABI sceNpServerErrorJsonParse(void); +int APS5_VABI sceNpServerErrorJsonParseInit(void); +int APS5_VABI sceNpServerErrorJsonParseMultiInit(void); +int APS5_VABI sceNpSetPlatformType(void); +int APS5_VABI sceNpUtilCanonicalizeNpIdForPs4(void); +int APS5_VABI sceNpUtilCanonicalizeNpIdForPsp2(void); +int APS5_VABI sceNpUtilGetDateSetAuto(void); +int APS5_VABI sceNpUtilGetDbgCommerce(void); +int APS5_VABI sceNpUtilGetEnv(void); +int APS5_VABI sceNpUtilGetFakeDisplayNameMode(void); +int APS5_VABI sceNpUtilGetFakeRateLimit(void); +int APS5_VABI sceNpUtilGetIgnoreNpTitleId(void); +int APS5_VABI sceNpUtilGetNpDebug(void); +int APS5_VABI sceNpUtilGetNpTestPatch(void); +int APS5_VABI sceNpUtilGetShareTitleCheck(void); +int APS5_VABI sceNpUtilGetSystemLanguage(void); +int APS5_VABI sceNpUtilGetTrcNotify(void); +int APS5_VABI sceNpUtilGetWebApi2FakeRateLimit(void); +int APS5_VABI sceNpUtilGetWebApi2FakeRateLimitTarget(void); +int APS5_VABI sceNpUtilGetWebTraceSetting(void); +int APS5_VABI sceNpUtilJidToNpId(void); +int APS5_VABI sceNpAtomicCas32(std::int32_t* value, std::int32_t expected, std::int32_t desired); +int APS5_VABI sceNpAtomicInc32(std::int32_t* value); +int APS5_VABI sceNpAtomicDec32(std::int32_t* value); +int APS5_VABI sceNpCondSignalAll(PthreadCond* cond); +int APS5_VABI sceNpCondSignalTo(PthreadCond* cond, Pthread thread); +int APS5_VABI sceNpCondWait(PthreadCond* cond, PthreadMutex* mutex); +int APS5_VABI sceNpLwCondInit(PthreadCond* cond, const char* name); +int APS5_VABI sceNpLwCondDestroy(PthreadCond* cond); +int APS5_VABI sceNpLwCondSignal(PthreadCond* cond); +int APS5_VABI sceNpLwCondSignalAll(PthreadCond* cond); +int APS5_VABI sceNpLwCondSignalTo(PthreadCond* cond, Pthread thread); +int APS5_VABI sceNpLwCondWait(PthreadCond* cond, PthreadMutex* mutex); +int APS5_VABI sceNpCreateEventFlag(KernelEventFlag* flag, const char* name, std::uint32_t attr, std::uint64_t pattern); +int APS5_VABI sceNpDeleteEventFlag(KernelEventFlag flag); +int APS5_VABI sceNpOpenEventFlag(KernelEventFlag* flag, const char* name); +int APS5_VABI sceNpCloseEventFlag(KernelEventFlag flag); +int APS5_VABI sceNpSetEventFlag(KernelEventFlag flag, std::uint64_t pattern); +int APS5_VABI sceNpClearEventFlag(KernelEventFlag flag, std::uint64_t pattern); +int APS5_VABI sceNpPollEventFlag(KernelEventFlag flag, std::uint64_t pattern, std::uint32_t mode, std::uint64_t* result); +int APS5_VABI sceNpCancelEventFlag(KernelEventFlag flag, std::uint64_t pattern, int* waiting); +int APS5_VABI sceNpCreateSema(KernelSema* sema, std::uint32_t flags, int initial, int maximum, const char* name); +int APS5_VABI sceNpDeleteSema(KernelSema sema); +int APS5_VABI sceNpPollSema(KernelSema sema); +int APS5_VABI sceNpSignalSema(KernelSema sema); +int APS5_VABI sceNpCreateThread(Pthread* thread, PthreadEntry entry, void* arg, int priority, std::size_t stackSize, KernelCpumask affinity, const char* name); +int APS5_VABI sceNpThreadGetId(Pthread* thread); +int APS5_VABI sceNpGetProcessId(void); +int APS5_VABI sceNpDbgDumpText(const char* text, int length); +int APS5_VABI sceNpIpc2ClientInit(void); +int APS5_VABI sceNpIpc2ClientTerm(void); +int APS5_VABI sceNpArchInit(void); +void APS5_VABI sceNpArchTerm(void); +int APS5_VABI sceNpHeapInit(NpHeap* heap, void* base, std::size_t size, const char* name); +void APS5_VABI sceNpHeapDestroy(NpHeap* heap); +void APS5_VABI sceNpHeapGetAllocator(NpHeap* heap, NpAllocator* allocator); +int APS5_VABI sceNpHeapGetStat(const NpHeap* heap, NpHeapStat* stat); +void APS5_VABI sceNpHeapShowStat(const NpHeap* heap); +int APS5_VABI sceNpMemoryHeapInit(NpMemoryHeap* heap, std::uint64_t size, const char* name); +void APS5_VABI sceNpMemoryHeapDestroy(NpMemoryHeap* heap); +void APS5_VABI sceNpMemoryHeapGetAllocator(NpMemoryHeap* heap, NpAllocator* allocator); +void APS5_VABI sceNpMemoryHeapGetAllocatorEx(NpMemoryHeap* heap, int outOfMemoryError, NpAllocatorEx* allocator); +void APS5_VABI sceNpGlobalHeapGetAllocator(NpAllocator* allocator); +void APS5_VABI sceNpGlobalHeapGetAllocatorEx(NpAllocatorEx* allocator); +NpAllocator* APS5_VABI sceNpGlobalHeapGetAllocatorPtr(void); +NpAllocatorEx* APS5_VABI sceNpGlobalHeapGetAllocatorExPtr(void); +int APS5_VABI sceNpAllocateKernelMemoryNoAlignment(std::uint64_t size, const char* name, void** address, NpKernelMemory* memory); +int APS5_VABI sceNpAllocateKernelMemoryWithAlignment(std::uint64_t size, const char* name, void** address, NpKernelMemory* memory); +int APS5_VABI sceNpFreeKernelMemory(NpKernelMemory* memory); +void APS5_VABI sceNpGetSdkVersionUInt(std::uint32_t* version); +int APS5_VABI sceNpRtcConvertToPosixTime(const RtcTick* tick, std::int64_t* seconds); +int APS5_VABI sceNpRtcFormatRFC3339(char* text, std::size_t size, const RtcTick* utc, int timeZoneMinutes); +int APS5_VABI sceNpUserGetUserIdList(int* users, std::size_t capacity, int* count); +int APS5_VABI sceNpRtcParseRFC3339(RtcTick* utc, const char* text); +int APS5_VABI sceNpCalloutInitCtx(NpCalloutContext* context, const char* name, std::size_t stackSize, int priority, KernelCpumask affinity); +void APS5_VABI sceNpCalloutTermCtx(NpCalloutContext* context); +int APS5_VABI sceNpCalloutStartOnCtx64(NpCalloutContext* context, NpCallout* callout, std::uint64_t usec, void (APS5_VABI *handler)(void* arg), void* arg); +int APS5_VABI sceNpCalloutStartOnCtx(NpCalloutContext* context, NpCallout* callout, std::uint32_t usec, void (APS5_VABI *handler)(void* arg), void* arg); +int APS5_VABI sceNpCalloutStopOnCtx(NpCalloutContext* context, NpCallout* callout, int* wasPending); +std::uint32_t APS5_VABI sceNpGetRandom(void); +int APS5_VABI sceNpUtilCmpAccountId(std::uint64_t left, std::uint64_t right); +int APS5_VABI sceNpCmpNpId(const void* npId1, const void* npId2); +bool APS5_VABI sceNpIntIsOnlineIdString(const char* text); +int APS5_VABI sceNpUtilGetNpLanguageCode(int language, char* code); +int APS5_VABI sceNpUtilGetNpLanguageCode2(int language, char* tag); +const char* APS5_VABI sceNpUtilGetNpLanguageCodeStr(int language); +const char* APS5_VABI sceNpUtilGetNpLanguageCode2Str(int language); +std::uint32_t APS5_VABI sceNpDbgAssignDebugId(void); +int APS5_VABI sceNpUtilBuildTitleId(const NpTitleIdParts* parts, char* titleId, std::size_t size); +void APS5_VABI sceNpDbgDumpBinary(const void* data, std::size_t size); +int APS5_VABI sceNpBase64GetDecodeSize(const char* src, std::size_t length); +int APS5_VABI sceNpBase64Decoder(const char* src, std::size_t length, void* dst, std::size_t capacity); +int APS5_VABI sceNpBase64Encoder(const void* src, char* dst, std::size_t length); +int APS5_VABI sceNpBase64UrlGetDecodeSize(const char* src, std::size_t length); +int APS5_VABI sceNpBase64UrlDecoder(const char* src, std::size_t length, void* dst, std::size_t capacity); +int APS5_VABI sceNpBase64UrlEncoder(const void* src, char* dst, std::size_t length); +int APS5_VABI sceNpHexToInt(int c); +int APS5_VABI sceNpIntToHex(std::uint32_t value, bool uppercase); +int APS5_VABI sceNpUInt32ToStr(char* buf, std::size_t size, std::uint32_t value, std::uint32_t radix, std::uint32_t flags, std::size_t* length); +int APS5_VABI sceNpUInt64ToStr(char* buf, std::size_t size, std::uint64_t value, std::uint32_t radix, std::uint32_t flags, std::size_t* length); +int APS5_VABI sceNpInt32ToStr(char* buf, std::size_t size, std::int32_t value, std::uint32_t radix, std::uint32_t flags, std::size_t* length); +int APS5_VABI sceNpInt64ToStr(char* buf, std::size_t size, std::int64_t value, std::uint32_t radix, std::uint32_t flags, std::size_t* length); +int APS5_VABI sceNpStrToInt32(const char* str, std::int32_t* value, int base, std::size_t* parsedLength); +int APS5_VABI sceNpStrToInt64(const char* str, std::int64_t* value, int base, std::size_t* parsedLength); +int APS5_VABI sceNpStrToUInt32(const char* str, std::uint32_t* value, int base, std::size_t* parsedLength); +int APS5_VABI sceNpStrToUInt64(const char* str, std::uint64_t* value, int base, std::size_t* parsedLength); +int APS5_VABI sceNpStrBuildHex(char* dst, std::size_t size, const void* src, std::size_t length, bool uppercase); +int APS5_VABI sceNpStrParseHex(const char* hex, void* out, std::size_t capacity); +int APS5_VABI sceNpStrnParseHex(const char* hex, std::size_t length, void* out, std::size_t capacity); +int APS5_VABI sceNpStrcpyToBuf(char* dst, std::size_t size, const char* src); +int APS5_VABI sceNpStrncpyToBuf(char* dst, std::size_t size, const char* src, std::size_t maxLength); +int APS5_VABI sceNpUtilJsonEscape(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength); +int APS5_VABI sceNpUtilJsonUnescape(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, std::size_t* dstLength); +int APS5_VABI sceNpUtilJsonGetOneChar(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, std::size_t* dstLength, std::size_t* consumed); +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); + +[[noreturn]] void APS5_VABI sceNpPanic(void); + +} diff --git a/core/libs/prx/libSceNpCommon/libSceNpCommon.hpp b/core/libs/prx/libSceNpCommon/libSceNpCommon.hpp new file mode 100644 index 0000000000..baf8782fd4 --- /dev/null +++ b/core/libs/prx/libSceNpCommon/libSceNpCommon.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpCommon/include/Api.hpp" diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 6238d389f8..6ab09331ae 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" #include #include #include @@ -272,11 +273,6 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12 return 0; } -struct IntrusivePtr { - void* object; - void (APS5_VABI* deleter)(void*); - void* libContext; -}; static_assert(sizeof(IntrusivePtr) == 0x18); static constexpr std::size_t ReferenceCountOffset = 0; @@ -463,12 +459,6 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12Intrusive return 0; } -struct CommonString { - std::int32_t referenceCount; - char* buffer; - std::uint64_t bufferSize; - void* libContext; -}; static_assert(sizeof(CommonString) == 0x20 && offsetof(CommonString, buffer) == 0x08); static_assert(offsetof(CommonString, bufferSize) == 0x10 && offsetof(CommonString, libContext) == 0x18); @@ -896,7 +886,6 @@ static int VectorPushBackString(IntrusivePtr* self, const CommonString* element) // libSceNpCppWebApi.prx. // -------------------------------------------------------------------------- - void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { CopyIntrusivePtr(self, source, 0x00); } @@ -1487,124 +1476,102 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2 return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEED2Ev(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEED2Ev(void* self) { + (void)self; return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEED2Ev(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEED2Ev(void* self) { + (void)self; return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEC1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEdeEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEptEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEED1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEEptEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEED1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEEptEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEED1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_17TitleCloudStorage2V110DataStatusEEEEEEdeEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_17TitleCloudStorage2V110DataStatusEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_17TitleCloudStorage2V110DataStatusEEEEEED1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_17TitleCloudStorage2V110DataStatusEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEC1ERS7_(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEptEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEED1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEEC1ERS7_(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEEptEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEED1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEptEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEC1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEptEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEED1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V15OwnerEEptEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V15OwnerEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V15OwnerEED1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V15OwnerEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); } bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEEneERKS9_(const void* self, const void* other) { @@ -1759,8 +1726,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToG return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer10initializeEPNS1_6Common10LibContextE(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer10initializeEPNS1_6Common10LibContextE() { return 0; } @@ -1775,19 +1741,17 @@ int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToG return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer9terminateEv(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer9terminateEv() { return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainerD1Ev(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainerD1Ev(void* self) { + (void)self; return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi12getContainerEiRKNS4_23ParameterToGetContainerERNS1_6Common11TransactionINS8_12IntrusivePtrINS8_6VectorINSA_INS3_9ContainerEEEEEEENSA_INS8_18ResponseHeaderBaseEEEEE(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi12getContainerEiRKNS4_23ParameterToGetContainerERNS1_6Common11TransactionINS8_12IntrusivePtrINS8_6VectorINSA_INS3_9ContainerEEEEEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } const char* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product8getLabelEv(const void* self) { @@ -1890,4 +1854,1378 @@ std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V15Owner12getAc NotImplemented_nid_no_patch(__func__); return 0; } + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi10getFriendsEiRKNS4_21ParameterToGetFriendsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_18GetFriendsResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi17getBasicPresencesEiRKNS4_28ParameterToGetBasicPresencesERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_25GetBasicPresencesResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V118GetFriendsResponse10getFriendsEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +// -------------------------------------------------------------------------- +// SessionManager2V1 PlayerSessionsApi + InGameCatalog2V5 ContainerApi and Common +// container helpers imported by titles whose dumps lack libSceNpCppWebApi.prx: +// request entry points and transactions fail SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN +// offline; parameter bookkeeping succeeds; response accessors stay unimplemented. +// -------------------------------------------------------------------------- + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBody14setInvitationsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_30RequestPlayerSessionInvitationEEEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28sendPlayerSessionInvitationsEiRKNS4_39ParameterToSendPlayerSessionInvitationsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_50PostPlayerSessionsSessionIdInvitationsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi17getPlayerSessionsEiRKNS4_28ParameterToGetPlayerSessionsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_29GetPlayerSessionsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession14setCustomData2EPKvm() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_39PatchPlayerSessionsSessionIdRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesC1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession14setCustomData1EPKvm() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData2IsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V124GetBlockingUsersResponse9getBlocksEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesD1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V146PatchPlayerSessionsSessionIdRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_39PatchPlayerSessionsSessionIdRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V137RequestPlayerSessionInvitationFactory6createEPNS1_6Common10LibContextENS5_12IntrusivePtrINS3_2ToEEEPNS8_INS3_30RequestPlayerSessionInvitationEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody19setJoinableUserTypeERKNS3_22CustomJoinableUserTypeE() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer11getPlatformEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetBasicPresencesResponse17getBasicPresencesEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9setfieldsENS1_6Common12IntrusivePtrINS6_6VectorINS6_6StringEEEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsD1Ev() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData1Ev() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi16getBlockingUsersEiRKNS4_27ParameterToGetBlockingUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetBlockingUsersResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers10initializeEPNS1_6Common10LibContextE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions9terminateEv() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData1IsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38addPlayerSessionJoinableSpecifiedUsersEiRKNS4_49ParameterToAddPlayerSessionJoinableSpecifiedUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_61PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead9getLeaderEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsC1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsD1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead28getExclusiveLeaderPrivilegesEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi26setPlayerSessionPropertiesEiRKNS4_37ParameterToSetPlayerSessionPropertiesERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi25changePlayerSessionLeaderEiRKNS4_36ParameterToChangePlayerSessionLeaderERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V150PutPlayerSessionsSessionIdLeaderRequestBodyFactory6createEPNS1_6Common10LibContextERKNS_4Json5ValueEPNS5_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V119JoinableUserFactory6createEPNS1_6Common10LibContextERKmPNS5_12IntrusivePtrINS3_12JoinableUserEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderD1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V156PostPlayerSessionsSessionIdInvitationsRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsC1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133ResponsePlayerSessionMemberPlayer10getPlayersEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody28setExclusiveLeaderPrivilegesERKNS1_6Common6VectorINS5_6StringEEE() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession9getMemberEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V19ToFactory6createEPNS1_6Common10LibContextERKmPNS5_12IntrusivePtrINS3_2ToEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersD1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V167PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_12JoinableUserEEEEEPNS9_INS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderC1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties9terminateEv() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V118LeaderWithOnlineId12getAccountIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody20setInvitableUserTypeERKNS3_23CustomInvitableUserTypeE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V129GetPlayerSessionsResponseBody17getPlayerSessionsEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersC1Ev() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData2Ev() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer12getAccountIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V112JoinableUserEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi15filterProfanityEiRKNS4_26ParameterToFilterProfanityERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23FilterProfanityResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity10initializeEPNS1_6Common10LibContextEPKcSA_NS6_12IntrusivePtrINS3_19WebApiFilterRequestEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V226WebApiFilterRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_19WebApiFilterRequestEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi33getCommunicationRestrictionStatusEiRKNS4_44ParameterToGetCommunicationRestrictionStatusERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_38CommunicationRestrictionStatusResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus10initializeEPNS1_6Common10LibContextEm() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, VectorReferenceCountOffset, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEED2Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V118LeaderWithOnlineIdEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V160PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V143PutPlayerSessionsSessionIdLeaderRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorImED2Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEaSERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + ReleaseIntrusivePtr(self, 0x00, __func__); + CopyIntrusivePtr(self, source, 0x00); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V12ToEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V160PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V120PlayerSessionForReadEEEEEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorImEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getAccountIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getInContextEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence15getOnlineStatusEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi15ProfanityFilter2V223FilterProfanityResponse10getMessageEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponse13getRestrictedEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEE3getEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE4sizeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5emptyEv(const IntrusivePtr* self) { + const auto* body = static_cast(self->object); + if (body == nullptr) return 1; + if (body->magic != VectorBodyMagic) { + NotImplemented_nid_no_patch(__func__); + return 0; + } + return body->size == 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V143PutPlayerSessionsSessionIdLeaderRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEEaSERKSA_(IntrusivePtr* self, const IntrusivePtr* source) { + ReleaseIntrusivePtr(self, 0x30, __func__); + CopyIntrusivePtr(self, source, 0x30); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V12ToEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEE3getEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEEE3getEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V160PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V12ToEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorImE4sizeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { + return 0; +} + +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEixEm(IntrusivePtr* self, unsigned long index) { + auto* body = static_cast(self->object); + if (body == nullptr || body->magic != VectorBodyMagic || body->elementKind != 1) { + NotImplemented_nid_no_patch(__func__); + return nullptr; + } + return static_cast(body->elements) + index; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEED2Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V143PutPlayerSessionsSessionIdLeaderRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEneERKS9_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEE3getEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V133ResponsePlayerSessionMemberPlayerEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V133ResponsePlayerSessionMemberPlayerEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V118LeaderWithOnlineIdEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEneERKS4_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE4sizeEv(const IntrusivePtr* self) { + const auto* body = static_cast(self->object); + if (body == nullptr) return 0; + if (body->magic != VectorBodyMagic) { + NotImplemented_nid_no_patch(__func__); + return 0; + } + return body->size; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V120PlayerSessionForReadEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10labelIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10priceIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getPriceEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku15getDisplayPriceEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer7setsortENS5_4SortE() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku18originalPriceIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDescriptionEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getReleaseDateEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku23getDisplayOriginalPriceEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDisplayNameEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku16getOriginalPriceEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku25displayOriginalPriceIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getLabelEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer9setoffsetEi() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product16releaseDateIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer15setcontainerIdsEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer8setlimitEi() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku17displayPriceIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product5getIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSE_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5emptyEv(const IntrusivePtr* self) { + const auto* body = static_cast(self->object); + if (body == nullptr) return 1; + if (body->magic != VectorBodyMagic) { + NotImplemented_nid_no_patch(__func__); + return 0; + } + return body->size == 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEneERKS9_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEneERKS9_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V53SkuEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEED2Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEC1ERKSA_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x30); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEC1ERKSE_(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEED2Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5emptyEv(const IntrusivePtr* self) { + const auto* body = static_cast(self->object); + if (body == nullptr) return 1; + if (body->magic != VectorBodyMagic) { + NotImplemented_nid_no_patch(__func__); + return 0; + } + return body->size == 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEaSERKSA_(IntrusivePtr* self, const IntrusivePtr* source) { + ReleaseIntrusivePtr(self, 0x30, __func__); + CopyIntrusivePtr(self, source, 0x30); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_6StringEED2Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8copyFromERKS5_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEE3getEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEneERKS5_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE11hasResponseEv() { + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringeqEPKc(const CommonString* self, const char* other) { + return other != nullptr && std::strcmp(self->buffer != nullptr ? self->buffer : "", other) == 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5getIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEaSERS6_(IntrusivePtr* self, const IntrusivePtr* source) { + ReleaseIntrusivePtr(self, 0x30, __func__); + CopyIntrusivePtr(self, source, 0x30); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5abortEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5emptyEv(const CommonString* self) { + return self->buffer == nullptr || self->buffer[0] == '\0'; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEE3getEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp new file mode 100644 index 0000000000..cdb32647a1 --- /dev/null +++ b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp @@ -0,0 +1,684 @@ +#pragma once + +#include "SceTypes.hpp" + +struct CommonString { + std::int32_t referenceCount; + char* buffer; + std::uint64_t bufferSize; + void* libContext; +}; + +struct IntrusivePtr { + void* object; + void (APS5_VABI* deleter)(void*); + void* libContext; +}; + +extern "C" { + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V111UserFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_4UserEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody18setStartSerialRankERKi(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setGroupERKNS3_5GroupE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setLimitERKi(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setUsersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_4UserEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody9setOffsetERKi(); +void* APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122GetRankingResponseBody10getEntriesEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody10setCommentEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody12setSmallDataEPKvm(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody15setNeedsTmpRankERKb(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody19setComparedDateTimeERK10SceRtcTick(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody7setPcIdERKi(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V128GetRankingRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_21GetRankingRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V129RecordScoreRequestBodyFactory6createEPNS1_6Common10LibContextElPNS5_12IntrusivePtrINS3_22RecordScoreRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V14User12setAccountIdERKm(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V14User7setPcIdERKi(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi10getRankingEiRKNS4_21ParameterToGetRankingERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_22GetRankingResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking10initializeEPNS1_6Common10LibContextEi(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking24setgetRankingRequestBodyENS1_6Common12IntrusivePtrINS3_21GetRankingRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRankingC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRankingD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi22getLargeDataByObjectIdEiRKNS4_33ParameterToGetLargeDataByObjectIdERNS1_6Common21DownStreamTransactionINS8_12IntrusivePtrINS4_37GetLargeDataByObjectIdResponseHeadersEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectId10initializeEPNS1_6Common10LibContextEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectId9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectIdC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectIdD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi18getBoardDefinitionEiRKNS4_29ParameterToGetBoardDefinitionERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_30GetBoardDefinitionResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinition10initializeEPNS1_6Common10LibContextEi(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinition9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinitionC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinitionD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi11recordScoreEiRKNS4_22ParameterToRecordScoreERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23RecordScoreResponseBodyEEENSA_INS4_26RecordScoreResponseHeadersEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi15recordLargeDataEiRKNS4_26ParameterToRecordLargeDataERNS1_6Common19UpStreamTransactionINS8_12IntrusivePtrINS3_27RecordLargeDataResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore10initializeEPNS1_6Common10LibContextEiNS6_12IntrusivePtrINS3_22RecordScoreRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScoreC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScoreD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeData10initializeEPNS1_6Common10LibContextEiNS5_19XPsnAtomicOperationEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeData9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeDataC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeDataD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEiRKNS4_23ParameterToDownloadDataERNS1_6Common21DownStreamTransactionINS8_12IntrusivePtrINS4_27DownloadDataResponseHeadersEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData10initializeEPNS1_6Common10LibContextEPKci(); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadDataC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadDataD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10InitParamsC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10InitParamsD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10LibContextC1Ev(uint64_t* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10initializeERKNS2_10InitParamsERNS2_10LibContextE(const void* params, uint64_t* context); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEED2Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextEm(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE8sendDataEPKvm(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE8readDataEPcm(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE8readDataEPcm(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEED1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(CommonString* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(CommonString* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEE8pushBackERKS8_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEEC1EPNS2_10LibContextE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEED1Ev(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEE3endEv(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEE5beginEv(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEppEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common9terminateERNS2_10LibContextE(uint64_t* context); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V122GetRankingResponseBody22getLastUpdatedDateTimeEv(const void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V123RecordScoreResponseBody10getTmpRankEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V123RecordScoreResponseBody16getTmpSerialRankEv(const void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody11getSortModeEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody13getEntryLimitEv(const void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody13getUpdateModeEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody13sortModeIsSetEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody15entryLimitIsSetEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody15updateModeIsSetEv(const void* self); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody16getMaxScoreLimitEv(const void* self); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody16getMinScoreLimitEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody18maxScoreLimitIsSetEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody18minScoreLimitIsSetEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody20getLargeDataNumLimitEv(const void* self); +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody21getLargeDataSizeLimitEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody22largeDataNumLimitIsSetEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody23largeDataSizeLimitIsSetEv(const void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry10getCommentEv(const void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry11getObjectIdEv(const void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry11getOnlineIdEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry12commentIsSetEv(const void* self); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry12getAccountIdEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry12getSmallDataEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry13getSerialRankEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry13objectIdIsSetEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry14getHighestRankEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry14smallDataIsSetEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry20getHighestSerialRankEv(const void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry7getPcIdEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry7getRankEv(const void* self); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry8getScoreEv(const void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V19RecordApi26RecordScoreResponseHeaders24getXPsnAtomicOperationIdEv(const void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE11getResponseERS8_(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE18getResponseHeadersERSB_(); +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv(const void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const CommonString* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEdeEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEneERKS9_(const void* self, const void* other); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEptEv(const IntrusivePtr* self); +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(); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V110FromMember11getOnlineIdEv(const void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations26setinvitationInvalidFilterEPKc(); +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 _ZN3sce2Np9CppWebApi14SessionManager2V122LocalizedStringFactory6createEPNS1_6Common10LibContextEPKcNS_4Json6ObjectEPNS5_12IntrusivePtrINS3_15LocalizedStringEEE(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEED2Ev(void* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEptEv(const IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession20setInvitableUserTypeERKNS3_17InvitableUserTypeE(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEE5beginEv(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEEEdeEv(const IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEEC1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi25joinPlayerSessionAsPlayerEiRKNS4_36ParameterToJoinPlayerSessionAsPlayerERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_52PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +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(); +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(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEC1Ev(IntrusivePtr* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead12getSessionIdEv(const void* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead7getFromEv(const void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V130PostPlayerSessionsResponseBody17getPlayerSessionsEv(void* self); +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 _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi27getPlayerSessionInvitationsEiRKNS4_38ParameterToGetPlayerSessionInvitationsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_54GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEEdeEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEC1Ev(IntrusivePtr* self); +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 _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(); +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(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBody16invitationsIsSetEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEEEixEm(void* self, std::uint64_t index); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession10initializeEPNS1_6Common10LibContextEPKcSA_(); +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); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations9setfieldsENS1_6Common12IntrusivePtrINS6_6VectorINS6_6StringEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15DefaultResponseD1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(IntrusivePtr* self, void* body, void (APS5_VABI* deleter)(void*), void* libContext); +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(); +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(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V131PlayerSessionPushContextFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEE(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEED1Ev(IntrusivePtr* self); +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(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBody14getInvitationsEv(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEC1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE6finishEv(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead22receivedTimestampIsSetEv(const void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE11getResponseERS4_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139RequestCreatePlayerSessionPlayerFactory6createEPNS1_6Common10LibContextEPKcS9_RKNS5_6VectorINS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEEEEPNSB_INS3_32RequestCreatePlayerSessionPlayerEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEC1Ev(IntrusivePtr* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession12getSessionIdEv(const void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V158PostPlayerSessionsSessionIdMemberPlayersRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_26RequestPlayerSessionPlayerEEEEEPNS9_INS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V136PostPlayerSessionsRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_20RequestPlayerSessionEEEEEPNS9_INS3_29PostPlayerSessionsRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession28setExclusiveLeaderPrivilegesERKNS1_6Common6VectorINS5_6StringEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEED2Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEED2Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEC1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEdeEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_17TitleCloudStorage2V110DataStatusEEEEEEdeEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_17TitleCloudStorage2V110DataStatusEEEEEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEC1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V15OwnerEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V15OwnerEED1Ev(IntrusivePtr* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEEneERKS9_(const void* self, const void* other); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEEdeEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEEppEv(void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEEneERKS9_(const void* self, const void* other); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEEdeEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEEppEv(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERSB_(const void* self, void* response); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(const void* self, void* response); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(void* self, void* context); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(void* self, void* context); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEixEm(void* self, std::uint64_t index); +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE4sizeEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5beginEv(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE3endEv(void* self); +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE4sizeEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V59Container11getChildrenEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainerC1Ev(void); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer10initializeEPNS1_6Common10LibContextE(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer13isInitializedEv(const void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer15setserviceLabelEi(void); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainerD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi12getContainerEiRKNS4_23ParameterToGetContainerERNS1_6Common11TransactionINS8_12IntrusivePtrINS8_6VectorINSA_INS3_9ContainerEEEEEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product8getLabelEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V57Product7getSkusEv(void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product7getTypeEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product9skusIsSetEv(const void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData11setobjectIdEPKc(void); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUserC1Ev(void); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUser10initializeEPNS1_6Common10LibContextEPKcSA_(void); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUser9setfieldsEPKc(void); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUser9terminateEv(void); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUserD1Ev(void); +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi26getMultiDataStatusesByUserEiRKNS4_37ParameterToGetMultiDataStatusesByUserERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_32GetMultiDataStatusesResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(void); +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus11getDataSizeEv(const void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus11getObjectIdEv(const void* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus8getOwnerEv(const void* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus9getSlotIdEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V132GetMultiDataStatusesResponseBody17getDataStatusListEv(void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V132GetMultiDataStatusesResponseBody23getTotalDataStatusCountEv(const void* self); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V15Owner12getAccountIdEv(const void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi10getFriendsEiRKNS4_21ParameterToGetFriendsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_18GetFriendsResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends10initializeEPNS1_6Common10LibContextEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi17getBasicPresencesEiRKNS4_28ParameterToGetBasicPresencesERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_25GetBasicPresencesResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences10initializeEPNS1_6Common10LibContextEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V118GetFriendsResponse10getFriendsEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBody14setInvitationsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_30RequestPlayerSessionInvitationEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28sendPlayerSessionInvitationsEiRKNS4_39ParameterToSendPlayerSessionInvitationsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_50PostPlayerSessionsSessionIdInvitationsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi17getPlayerSessionsEiRKNS4_28ParameterToGetPlayerSessionsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_29GetPlayerSessionsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession14setCustomData2EPKvm(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_39PatchPlayerSessionsSessionIdRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession14setCustomData1EPKvm(); +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData2IsSetEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V124GetBlockingUsersResponse9getBlocksEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V146PatchPlayerSessionsSessionIdRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_39PatchPlayerSessionsSessionIdRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V137RequestPlayerSessionInvitationFactory6createEPNS1_6Common10LibContextENS5_12IntrusivePtrINS3_2ToEEEPNS8_INS3_30RequestPlayerSessionInvitationEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody19setJoinableUserTypeERKNS3_22CustomJoinableUserTypeE(); +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer11getPlatformEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetBasicPresencesResponse17getBasicPresencesEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9setfieldsENS1_6Common12IntrusivePtrINS6_6VectorINS6_6StringEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv(); +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 _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData1IsSetEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38addPlayerSessionJoinableSpecifiedUsersEiRKNS4_49ParameterToAddPlayerSessionJoinableSpecifiedUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_61PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead9getLeaderEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead28getExclusiveLeaderPrivilegesEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi26setPlayerSessionPropertiesEiRKNS4_37ParameterToSetPlayerSessionPropertiesERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi25changePlayerSessionLeaderEiRKNS4_36ParameterToChangePlayerSessionLeaderERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V150PutPlayerSessionsSessionIdLeaderRequestBodyFactory6createEPNS1_6Common10LibContextERKNS_4Json5ValueEPNS5_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V119JoinableUserFactory6createEPNS1_6Common10LibContextERKmPNS5_12IntrusivePtrINS3_12JoinableUserEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V156PostPlayerSessionsSessionIdInvitationsRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions10initializeEPNS1_6Common10LibContextEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133ResponsePlayerSessionMemberPlayer10getPlayersEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody28setExclusiveLeaderPrivilegesERKNS1_6Common6VectorINS5_6StringEEE(); +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession9getMemberEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V19ToFactory6createEPNS1_6Common10LibContextERKmPNS5_12IntrusivePtrINS3_2ToEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V167PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_12JoinableUserEEEEEPNS9_INS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties9terminateEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V118LeaderWithOnlineId12getAccountIdEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody20setInvitableUserTypeERKNS3_23CustomInvitableUserTypeE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V129GetPlayerSessionsResponseBody17getPlayerSessionsEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersC1Ev(); +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData2Ev(); +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer12getAccountIdEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V112JoinableUserEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi15filterProfanityEiRKNS4_26ParameterToFilterProfanityERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23FilterProfanityResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity10initializeEPNS1_6Common10LibContextEPKcSA_NS6_12IntrusivePtrINS3_19WebApiFilterRequestEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V226WebApiFilterRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_19WebApiFilterRequestEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi33getCommunicationRestrictionStatusEiRKNS4_44ParameterToGetCommunicationRestrictionStatusERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_38CommunicationRestrictionStatusResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus10initializeEPNS1_6Common10LibContextEm(); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEED2Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEC1Ev(IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEEdeEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V118LeaderWithOnlineIdEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V160PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V143PutPlayerSessionsSessionIdLeaderRequestBodyEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorImED2Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBodyEEC1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEaSERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V12ToEEC1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V160PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V120PlayerSessionForReadEEEEEEptEv(const IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE3endEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE5beginEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE3endEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE5beginEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEppEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorImEppEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getAccountIdEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getInContextEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence15getOnlineStatusEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi15ProfanityFilter2V223FilterProfanityResponse10getMessageEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponse13getRestrictedEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEE3getEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEdeEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEdeEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEptEv(const IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE4sizeEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5emptyEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V143PutPlayerSessionsSessionIdLeaderRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEptEv(const IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEEaSERKSA_(IntrusivePtr* self, const IntrusivePtr* source); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V12ToEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEE3getEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBodyEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEEE3getEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V160PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEC1Ev(IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V12ToEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorImE4sizeEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEdeEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEixEm(IntrusivePtr* self, unsigned long index); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEED2Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V143PutPlayerSessionsSessionIdLeaderRequestBodyEEC1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEneERKS9_(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEC1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEE3getEv(const IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V133ResponsePlayerSessionMemberPlayerEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEC1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V133ResponsePlayerSessionMemberPlayerEEptEv(const IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEdeEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V118LeaderWithOnlineIdEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEneERKS4_(); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE4sizeEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V120PlayerSessionForReadEEEEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5beginEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEED1Ev(IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEptEv(const IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEEC1Ev(IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10labelIsSetEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10priceIsSetEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getPriceEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku15getDisplayPriceEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer7setsortENS5_4SortE(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku18originalPriceIsSetEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDescriptionEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getReleaseDateEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku23getDisplayOriginalPriceEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDisplayNameEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku16getOriginalPriceEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku25displayOriginalPriceIsSetEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getLabelEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer9setoffsetEi(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product16releaseDateIsSetEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer15setcontainerIdsEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer8setlimitEi(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku17displayPriceIsSetEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product5getIdEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSE_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEppEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5emptyEv(const IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEneERKS9_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE5beginEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEneERKS9_(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEEdeEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V53SkuEEptEv(const IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEED2Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE3endEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEdeEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEdeEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEC1ERKSA_(IntrusivePtr* self, const IntrusivePtr* source); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEC1ERKSE_(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEED2Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5beginEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE3endEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEEdeEv(const IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEppEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5emptyEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEaSERKSA_(IntrusivePtr* self, const IntrusivePtr* source); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEppEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_6StringEED2Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8copyFromERKS5_(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEE3getEv(const IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEneERKS5_(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE11hasResponseEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEptEv(const IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE3endEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringeqEPKc(const CommonString* self, const char* other); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5getIdEv(); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEaSERS6_(IntrusivePtr* self, const IntrusivePtr* source); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5abortEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE5beginEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5emptyEv(const CommonString* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEE3getEv(const IntrusivePtr* self); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEdeEv(); + +} diff --git a/core/libs/prx/libSceNpCppWebApi/libSceNpCppWebApi.hpp b/core/libs/prx/libSceNpCppWebApi/libSceNpCppWebApi.hpp new file mode 100644 index 0000000000..450f928feb --- /dev/null +++ b/core/libs/prx/libSceNpCppWebApi/libSceNpCppWebApi.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpCppWebApi/include/Api.hpp" diff --git a/core/libs/prx/libSceNpEntitlementAccess/Export.cpp b/core/libs/prx/libSceNpEntitlementAccess/Export.cpp index ba654ff02f..e0593d4afb 100644 --- a/core/libs/prx/libSceNpEntitlementAccess/Export.cpp +++ b/core/libs/prx/libSceNpEntitlementAccess/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpEntitlementAccess/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -23,8 +24,14 @@ static constexpr uint32_t SKU_FLAG_FULL = 3; namespace { +#ifdef _WIN32 +const wchar_t* ConfiguredEntitlements() { return _wgetenv(L"ANYPS5_ENTITLEMENTS"); } +#else +const char* ConfiguredEntitlements() { return std::getenv("ANYPS5_ENTITLEMENTS"); } +#endif + std::filesystem::path EntitlementsPath() { - if (const char* configured = std::getenv("ANYPS5_ENTITLEMENTS"); configured != nullptr && configured[0] != '\0') return configured; + if (const auto* configured = ConfiguredEntitlements(); configured != nullptr && configured[0] != 0) return configured; #ifdef _WIN32 wchar_t module[MAX_PATH]; const auto length = GetModuleFileNameW(nullptr, module, MAX_PATH); @@ -50,7 +57,7 @@ const std::vector& OwnedAddons() { const auto path = EntitlementsPath(); std::ifstream file(path); if (!file) { - if (std::getenv("ANYPS5_ENTITLEMENTS") != nullptr || std::filesystem::exists(path)) throw std::runtime_error("NpEntitlementAccess: cannot read " + path.string()); + if (ConfiguredEntitlements() != nullptr || std::filesystem::exists(path)) throw std::runtime_error("NpEntitlementAccess: cannot read " + path.string()); return addons; } std::string line; @@ -118,46 +125,44 @@ int APS5_VABI sceNpEntitlementAccessInitialize(const NpEntitlementAccessInitPara return 0; } -int APS5_VABI sceNpEntitlementAccessAbortRequest(void) { - return 0; +int APS5_VABI sceNpEntitlementAccessAbortRequest(std::int64_t requestId) { + throw std::logic_error("sceNpEntitlementAccessAbortRequest: asynchronous request tracking is unimplemented"); } -int APS5_VABI sceNpEntitlementAccessDeleteRequest(void) { - return 0; +int APS5_VABI sceNpEntitlementAccessDeleteRequest(std::int64_t requestId) { + throw std::logic_error("sceNpEntitlementAccessDeleteRequest: asynchronous request tracking is unimplemented"); } -int APS5_VABI sceNpEntitlementAccessGenerateTransactionId(void* transaction_id) { - if (!transaction_id) return SCE_NP_ENTITLEMENT_ACCESS_ERROR_PARAMETER; +int APS5_VABI sceNpEntitlementAccessGenerateTransactionId(NpEntitlementAccessTransactionId* transactionId) { + if (!transactionId) return SCE_NP_ENTITLEMENT_ACCESS_ERROR_PARAMETER; return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessPollConsumeEntitlement(void) { +int APS5_VABI sceNpEntitlementAccessPollConsumeEntitlement(std::int64_t requestId, std::int32_t* result, std::int32_t* countResult) { return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessRequestConsumeUnifiedEntitlement(void) { +int APS5_VABI sceNpEntitlementAccessRequestConsumeUnifiedEntitlement(std::int32_t userId, std::uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, const NpEntitlementAccessTransactionId* transactionId, std::int32_t count, std::int64_t* requestId) { return SCE_NP_ERROR_SIGNED_OUT; } - -int APS5_VABI sceNpEntitlementAccessRequestConsumeServiceEntitlement(void) { +int APS5_VABI sceNpEntitlementAccessRequestConsumeServiceEntitlement(std::int32_t userId, std::uint32_t serviceLabel, const NpServiceEntitlementLabel* entitlementLabel, const NpEntitlementAccessTransactionId* transactionId, std::int32_t count, std::int64_t* requestId) { return SCE_NP_ERROR_SIGNED_OUT; } - -int APS5_VABI sceNpEntitlementAccessPollServiceEntitlementInfo(void) { +int APS5_VABI sceNpEntitlementAccessPollServiceEntitlementInfo(std::int64_t requestId, std::int32_t* result, NpEntitlementAccessServiceInfo* info) { return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessPollUnifiedEntitlementInfo(void) { +int APS5_VABI sceNpEntitlementAccessPollUnifiedEntitlementInfo(std::int64_t requestId, std::int32_t* result, NpEntitlementAccessUnifiedInfo* info) { return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessRequestServiceEntitlementInfo(void) { +int APS5_VABI sceNpEntitlementAccessRequestServiceEntitlementInfo(std::int32_t userId, std::uint32_t serviceLabel, const NpServiceEntitlementLabel* entitlementLabel, std::int64_t* requestId) { return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessRequestUnifiedEntitlementInfo(void) { +int APS5_VABI sceNpEntitlementAccessRequestUnifiedEntitlementInfo(std::int32_t userId, std::uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, std::int64_t* requestId) { return SCE_NP_ERROR_SIGNED_OUT; } @@ -175,19 +180,19 @@ int APS5_VABI sceNpEntitlementAccessGetGameTrialsFlag(uint32_t* flag) { return 0; } -int APS5_VABI sceNpEntitlementAccessPollServiceEntitlementInfoList() { +int APS5_VABI sceNpEntitlementAccessPollServiceEntitlementInfoList(std::int64_t requestId, std::int32_t* result, NpEntitlementAccessServiceInfo* infos, std::uint32_t capacity, std::int32_t* firstResult, std::int32_t* secondResult, std::int32_t* thirdResult) { return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessPollUnifiedEntitlementInfoList() { +int APS5_VABI sceNpEntitlementAccessPollUnifiedEntitlementInfoList(std::int64_t requestId, std::int32_t* result, NpEntitlementAccessUnifiedInfo* infos, std::uint32_t capacity, std::int32_t* firstResult, std::int32_t* secondResult, std::int32_t* thirdResult) { return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessRequestServiceEntitlementInfoList() { +int APS5_VABI sceNpEntitlementAccessRequestServiceEntitlementInfoList(std::int32_t userId, std::uint32_t serviceLabel, const NpServiceEntitlementLabel* entitlementLabels, std::uint32_t labelCount, const NpEntitlementAccessListParameter* parameter, std::int64_t* requestId) { return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessRequestUnifiedEntitlementInfoList() { +int APS5_VABI sceNpEntitlementAccessRequestUnifiedEntitlementInfoList(std::int32_t userId, std::uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabels, std::uint32_t labelCount, const NpEntitlementAccessListParameter* parameter, std::int64_t* requestId) { return SCE_NP_ERROR_SIGNED_OUT; } diff --git a/core/libs/prx/libSceNpEntitlementAccess/include/Api.hpp b/core/libs/prx/libSceNpEntitlementAccess/include/Api.hpp new file mode 100644 index 0000000000..d68d483b87 --- /dev/null +++ b/core/libs/prx/libSceNpEntitlementAccess/include/Api.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpEntitlementAccessGetAddcontEntitlementInfo(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, NpEntitlementAccessAddcontEntitlementInfo* info); +int APS5_VABI sceNpEntitlementAccessGetAddcontEntitlementInfoList(uint32_t serviceLabel, NpEntitlementAccessAddcontEntitlementInfo* list, uint32_t listNum, uint32_t* hitNum); +int APS5_VABI sceNpEntitlementAccessGetSkuFlag(uint32_t* skuFlag); +int APS5_VABI sceNpEntitlementAccessGetEntitlementKey( uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, NpEntitlementAccessEntitlementKey* key); +int APS5_VABI sceNpEntitlementAccessInitialize(const NpEntitlementAccessInitParam* initParam, NpEntitlementAccessBootParam* bootParam); +int APS5_VABI sceNpEntitlementAccessAbortRequest(std::int64_t requestId); +int APS5_VABI sceNpEntitlementAccessDeleteRequest(std::int64_t requestId); +int APS5_VABI sceNpEntitlementAccessGenerateTransactionId(NpEntitlementAccessTransactionId* transactionId); +int APS5_VABI sceNpEntitlementAccessPollConsumeEntitlement(std::int64_t requestId, std::int32_t* result, std::int32_t* countResult); +int APS5_VABI sceNpEntitlementAccessRequestConsumeUnifiedEntitlement(std::int32_t userId, std::uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, const NpEntitlementAccessTransactionId* transactionId, std::int32_t count, std::int64_t* requestId); +int APS5_VABI sceNpEntitlementAccessRequestConsumeServiceEntitlement(std::int32_t userId, std::uint32_t serviceLabel, const NpServiceEntitlementLabel* entitlementLabel, const NpEntitlementAccessTransactionId* transactionId, std::int32_t count, std::int64_t* requestId); +int APS5_VABI sceNpEntitlementAccessPollServiceEntitlementInfo(std::int64_t requestId, std::int32_t* result, NpEntitlementAccessServiceInfo* info); +int APS5_VABI sceNpEntitlementAccessPollUnifiedEntitlementInfo(std::int64_t requestId, std::int32_t* result, NpEntitlementAccessUnifiedInfo* info); +int APS5_VABI sceNpEntitlementAccessRequestServiceEntitlementInfo(std::int32_t userId, std::uint32_t serviceLabel, const NpServiceEntitlementLabel* entitlementLabel, std::int64_t* requestId); +int APS5_VABI sceNpEntitlementAccessRequestUnifiedEntitlementInfo(std::int32_t userId, std::uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, std::int64_t* requestId); +int APS5_VABI sceNpEntitlementAccessGetPftFlag(uint32_t* flag); +int APS5_VABI sceNpEntitlementAccessGetGameTrialsFlag(uint32_t* flag); +int APS5_VABI sceNpEntitlementAccessPollServiceEntitlementInfoList(std::int64_t requestId, std::int32_t* result, NpEntitlementAccessServiceInfo* infos, std::uint32_t capacity, std::int32_t* firstResult, std::int32_t* secondResult, std::int32_t* thirdResult); +int APS5_VABI sceNpEntitlementAccessPollUnifiedEntitlementInfoList(std::int64_t requestId, std::int32_t* result, NpEntitlementAccessUnifiedInfo* infos, std::uint32_t capacity, std::int32_t* firstResult, std::int32_t* secondResult, std::int32_t* thirdResult); +int APS5_VABI sceNpEntitlementAccessRequestServiceEntitlementInfoList(std::int32_t userId, std::uint32_t serviceLabel, const NpServiceEntitlementLabel* entitlementLabels, std::uint32_t labelCount, const NpEntitlementAccessListParameter* parameter, std::int64_t* requestId); +int APS5_VABI sceNpEntitlementAccessRequestUnifiedEntitlementInfoList(std::int32_t userId, std::uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabels, std::uint32_t labelCount, const NpEntitlementAccessListParameter* parameter, std::int64_t* requestId); +int APS5_VABI sceNpEntitlementAccessGetAddcontEntitlementInfoIndividual(); +int APS5_VABI sceNpEntitlementAccessPollConsumableEntitlementInfo(); +int APS5_VABI sceNpEntitlementAccessRequestConsumableEntitlementInfo(); +int APS5_VABI sceNpEntitlementAccessRequestConsumeEntitlement(); + +} diff --git a/core/libs/prx/libSceNpEntitlementAccess/libSceNpEntitlementAccess.hpp b/core/libs/prx/libSceNpEntitlementAccess/libSceNpEntitlementAccess.hpp new file mode 100644 index 0000000000..e8590281af --- /dev/null +++ b/core/libs/prx/libSceNpEntitlementAccess/libSceNpEntitlementAccess.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpEntitlementAccess/include/Api.hpp" diff --git a/core/libs/prx/libSceNpEulaDialog/Export.cpp b/core/libs/prx/libSceNpEulaDialog/Export.cpp index 1b666cc199..99b4499d54 100644 --- a/core/libs/prx/libSceNpEulaDialog/Export.cpp +++ b/core/libs/prx/libSceNpEulaDialog/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpEulaDialog/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceNpEulaDialog/include/Api.hpp b/core/libs/prx/libSceNpEulaDialog/include/Api.hpp new file mode 100644 index 0000000000..fc29d6fa96 --- /dev/null +++ b/core/libs/prx/libSceNpEulaDialog/include/Api.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpEulaDialogCheckVersion(); +int APS5_VABI sceNpEulaDialogCheckVersionAbort(); +int APS5_VABI sceNpEulaDialogClose(); +int APS5_VABI sceNpEulaDialogGetResult(); +int APS5_VABI sceNpEulaDialogGetStatus(); +int APS5_VABI sceNpEulaDialogInitialize(); +int APS5_VABI sceNpEulaDialogOpen(); +int APS5_VABI sceNpEulaDialogTerminate(); +int APS5_VABI sceNpEulaDialogUpdateStatus(); + +} diff --git a/core/libs/prx/libSceNpEulaDialog/libSceNpEulaDialog.hpp b/core/libs/prx/libSceNpEulaDialog/libSceNpEulaDialog.hpp new file mode 100644 index 0000000000..6f2a34108c --- /dev/null +++ b/core/libs/prx/libSceNpEulaDialog/libSceNpEulaDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpEulaDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceNpFriendListDialog/CMakeLists.txt b/core/libs/prx/libSceNpFriendListDialog/CMakeLists.txt new file mode 100644 index 0000000000..219499b30b --- /dev/null +++ b/core/libs/prx/libSceNpFriendListDialog/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libSceNpFriendListDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpFriendListDialog PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpFriendListDialog) + +if(BUILD_TESTING) + add_test_executable(guest_np_friend_list_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpFriendListDialog.cpp) + target_include_directories(guest_np_friend_list_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_friend_list_dialog_tests PRIVATE libSceNpFriendListDialog) + add_test(NAME guest_np_friend_list_dialog COMMAND guest_np_friend_list_dialog_tests) + if(WIN32) + set_tests_properties(guest_np_friend_list_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpFriendListDialog/Export.cpp b/core/libs/prx/libSceNpFriendListDialog/Export.cpp index e744f30761..aee6fb1edf 100644 --- a/core/libs/prx/libSceNpFriendListDialog/Export.cpp +++ b/core/libs/prx/libSceNpFriendListDialog/Export.cpp @@ -1,12 +1,71 @@ +#include "prx/libSceNpFriendListDialog/include/Api.hpp" +#include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int COMMON_DIALOG_STATUS_NONE = 0; +constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; +constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; +constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; + +std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; + +} + extern "C" { -int APS5_VABI sceNpFriendListDialogClose() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpFriendListDialogInitialize(void) { + int expected = COMMON_DIALOG_STATUS_NONE; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + return 0; +} + +int APS5_VABI sceNpFriendListDialogOpenA(const void* param) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; +} + +int APS5_VABI sceNpFriendListDialogUpdateStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceNpFriendListDialogGetStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceNpFriendListDialogGetResultA(void* result) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; + std::memcpy(result, &canceled, sizeof(canceled)); + return 0; +} + +int APS5_VABI sceNpFriendListDialogClose(void) { + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; +} + +int APS5_VABI sceNpFriendListDialogTerminate(void) { + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; return 0; } @@ -15,39 +74,9 @@ int APS5_VABI sceNpFriendListDialogGetResult() { return 0; } -int APS5_VABI sceNpFriendListDialogGetResultA() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpFriendListDialogGetStatus() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpFriendListDialogInitialize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpFriendListDialogOpen() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpFriendListDialogOpenA() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpFriendListDialogTerminate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpFriendListDialogUpdateStatus() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceNpFriendListDialog/include/Api.hpp b/core/libs/prx/libSceNpFriendListDialog/include/Api.hpp new file mode 100644 index 0000000000..2bdd0742ab --- /dev/null +++ b/core/libs/prx/libSceNpFriendListDialog/include/Api.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpFriendListDialogInitialize(void); +int APS5_VABI sceNpFriendListDialogOpenA(const void* param); +int APS5_VABI sceNpFriendListDialogUpdateStatus(void); +int APS5_VABI sceNpFriendListDialogGetStatus(void); +int APS5_VABI sceNpFriendListDialogGetResultA(void* result); +int APS5_VABI sceNpFriendListDialogClose(void); +int APS5_VABI sceNpFriendListDialogTerminate(void); +int APS5_VABI sceNpFriendListDialogGetResult(); +int APS5_VABI sceNpFriendListDialogOpen(); + +} diff --git a/core/libs/prx/libSceNpFriendListDialog/libSceNpFriendListDialog.hpp b/core/libs/prx/libSceNpFriendListDialog/libSceNpFriendListDialog.hpp new file mode 100644 index 0000000000..d1b45137ee --- /dev/null +++ b/core/libs/prx/libSceNpFriendListDialog/libSceNpFriendListDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpFriendListDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceNpGameIntent/Export.cpp b/core/libs/prx/libSceNpGameIntent/Export.cpp index c8be4b9f09..dfea590bc3 100644 --- a/core/libs/prx/libSceNpGameIntent/Export.cpp +++ b/core/libs/prx/libSceNpGameIntent/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceNpGameIntent/include/Api.hpp" +#include #include #include #include @@ -7,44 +9,57 @@ namespace { +constexpr int NpGameIntentErrorNotInitialized = -2141898750; constexpr int NpGameIntentErrorInvalidArgument = -2141898748; constexpr int NpGameIntentErrorIntentNotFound = -2141898746; constexpr int NpGameIntentErrorValueNotFound = -2141898745; constexpr int NpGameIntentUserIdInvalid = -1; +std::atomic_bool& NpGameIntentInitialized() { + static std::atomic_bool initialized{false}; + return initialized; +} } extern "C" { int APS5_VABI sceNpGameIntentGetPropertyValueString(const NpGameIntentData* intentData, const char* key, char* valueBuf, size_t bufSize) { - if (intentData == nullptr || key == nullptr || valueBuf == nullptr || bufSize == 0) { - APS5_INVALID_ARG_EX; - } + if (!NpGameIntentInitialized()) { + return NpGameIntentErrorNotInitialized; + } + if (intentData == nullptr || key == nullptr || key[0] == '\0' || valueBuf == nullptr || bufSize == 0) { + return NpGameIntentErrorInvalidArgument; + } - valueBuf[0] = '\0'; - return NpGameIntentErrorValueNotFound; + valueBuf[0] = '\0'; + return NpGameIntentErrorValueNotFound; } int APS5_VABI sceNpGameIntentInitialize(const void* initParam) { - (void)initParam; - return 0; + (void)initParam; + NpGameIntentInitialized() = true; + return 0; } int APS5_VABI sceNpGameIntentReceiveIntent(NpGameIntentInfo* intentInfo) { - if (intentInfo == nullptr) { - APS5_INVALID_ARG_EX; - } + if (!NpGameIntentInitialized()) { + return NpGameIntentErrorNotInitialized; + } + if (intentInfo == nullptr || intentInfo->size != 0x4140) { + return NpGameIntentErrorInvalidArgument; + } - intentInfo->user_id = NpGameIntentUserIdInvalid; - std::memset(intentInfo->intent_type, 0, sizeof(intentInfo->intent_type)); - std::memset(&intentInfo->intent_data, 0, sizeof(intentInfo->intent_data)); + intentInfo->user_id = NpGameIntentUserIdInvalid; + std::memset(intentInfo->intent_type, 0, sizeof(intentInfo->intent_type)); + std::memset(&intentInfo->intent_data, 0, sizeof(intentInfo->intent_data)); - return NpGameIntentErrorIntentNotFound; + return NpGameIntentErrorIntentNotFound; } int APS5_VABI sceNpGameIntentTerminate(void) { - return 0; + NpGameIntentInitialized() = false; + return 0; } int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param) { diff --git a/core/libs/prx/libSceNpGameIntent/include/Api.hpp b/core/libs/prx/libSceNpGameIntent/include/Api.hpp new file mode 100644 index 0000000000..abd369f817 --- /dev/null +++ b/core/libs/prx/libSceNpGameIntent/include/Api.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpGameIntentGetPropertyValueString(const NpGameIntentData* intentData, const char* key, char* valueBuf, size_t bufSize); +int APS5_VABI sceNpGameIntentInitialize(const void* initParam); +int APS5_VABI sceNpGameIntentReceiveIntent(NpGameIntentInfo* intentInfo); +int APS5_VABI sceNpGameIntentTerminate(void); +int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param); +int APS5_VABI sceNpGameIntentInfoInit(NpGameIntentInfo* info); +int APS5_VABI sceNpGameIntentVshInitialize(void); +int APS5_VABI sceNpGameIntentVshTerminate(void); +int APS5_VABI sceNpAppLauncherInitialize(const void* initParam); +int APS5_VABI sceNpAppLauncherLaunchApp(); +int APS5_VABI sceNpAppLauncherTerminate(); +int APS5_VABI sceNpGameIntentGetUdsNpComId(); +int APS5_VABI sceNpGameIntentLaunchApp(); +int APS5_VABI sceNpGameIntentLaunchApp2(); +int APS5_VABI sceNpGameIntentNotifyAppLaunched(); +int APS5_VABI sceNpSessionServiceGetJoinedPartySession(); +int APS5_VABI sceNpSessionServiceGetRunningGamePlayerSession(); +int APS5_VABI sceNpSessionServiceSendPlayerSessionInvitations(); + +} diff --git a/core/libs/prx/libSceNpGameIntent/libSceNpGameIntent.hpp b/core/libs/prx/libSceNpGameIntent/libSceNpGameIntent.hpp new file mode 100644 index 0000000000..7c0f3b8b2e --- /dev/null +++ b/core/libs/prx/libSceNpGameIntent/libSceNpGameIntent.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpGameIntent/include/Api.hpp" diff --git a/core/libs/prx/libSceNpManager/Export.cpp b/core/libs/prx/libSceNpManager/Export.cpp index 1b45d38294..1abe3a9f3a 100644 --- a/core/libs/prx/libSceNpManager/Export.cpp +++ b/core/libs/prx/libSceNpManager/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpManager/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -14,11 +15,14 @@ static constexpr int SCE_NP_ERROR_SIGNED_OUT = static_cast(0x80550006); static constexpr int SCE_NP_ERROR_USER_NOT_FOUND = static_cast(0x80550007); static constexpr int SCE_NP_ERROR_CALLBACK_ALREADY_REGISTERED = static_cast(0x80550008); static constexpr int SCE_NP_ERROR_CALLBACK_NOT_REGISTERED = static_cast(0x80550009); +static constexpr int SCE_NP_ERROR_INVALID_SIZE = static_cast(0x80550011); static constexpr int SCE_NP_ERROR_REQUEST_NOT_FOUND = static_cast(0x80550014); +static constexpr int SCE_NP_ERROR_INVALID_ID = static_cast(0x80550015); static constexpr int SCE_NP_ERROR_CALLBACK_MAX = static_cast(0x8055001D); static constexpr uint32_t NP_STATE_SIGNED_OUT = 1; static constexpr int NP_POLL_ASYNC_FINISHED = 0; static constexpr uint32_t NP_REACHABILITY_STATE_UNAVAILABLE = 0; +static constexpr uint32_t NP_GAME_PRESENCE_STATUS_OFFLINE = 0; static std::atomic g_nextRequest{1}; @@ -36,8 +40,11 @@ int AddRequest() { return reqId; } -using NpStateCallbackA = void (APS5_VABI *)(int userId, uint32_t state, void* userdata); -using NpStateCallback = void (APS5_VABI *)(int userId, uint32_t state, NpId* npId, void* userdata); +int SignedOutRequest(int reqId) { + if (reqId == 0) return SCE_NP_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(requestMutex); + return liveRequests.count(reqId) ? SCE_NP_ERROR_SIGNED_OUT : SCE_NP_ERROR_REQUEST_NOT_FOUND; +} constexpr int NP_STATE_CALLBACK_MAX = 8; @@ -252,29 +259,31 @@ int APS5_VABI sceNpAsmTerminate(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpCheckNpAvailabilityA(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpCheckNpAvailabilityA(int reqId, int userId) { + if (userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + return SignedOutRequest(reqId); +} +int APS5_VABI sceNpGetAccountDateOfBirthA(int userId, void* dateOfBirth) { + if (!dateOfBirth || userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + return SCE_NP_ERROR_SIGNED_OUT; +} +int APS5_VABI sceNpGetAccountLanguageA(int reqId, int userId, void* language) { + if (!language || userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + return SignedOutRequest(reqId); +} +int APS5_VABI sceNpGetGamePresenceStatus(const NpOnlineId* onlineId, uint32_t* status) { + if (!onlineId || !status) return SCE_NP_ERROR_INVALID_ARGUMENT; + *status = NP_GAME_PRESENCE_STATUS_OFFLINE; return 0; } -int APS5_VABI sceNpGetAccountDateOfBirthA(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpGetGamePresenceStatusA(int userId, uint32_t* status) { + if (!status || userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + *status = NP_GAME_PRESENCE_STATUS_OFFLINE; return 0; } -int APS5_VABI sceNpGetAccountLanguageA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceNpGetGamePresenceStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceNpGetGamePresenceStatusA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceNpGetParentalControlInfoA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpGetParentalControlInfoA(int reqId, int userId, int8_t* age, void* info) { + if (!age || !info || userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + return SignedOutRequest(reqId); } int APS5_VABI sceNpIdMapperAbortRequest(void) { NotImplemented_nid_no_patch(__func__); @@ -348,8 +357,9 @@ int APS5_VABI sceNpIntRegisterGamePresenceCallback(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpIsPlusMember(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpIsPlusMember(int, bool* isMember) { + if (!isMember) return SCE_NP_ERROR_INVALID_ARGUMENT; + *isMember = false; return 0; } int APS5_VABI sceNpManagerIntAbortRequest(void) { @@ -662,8 +672,8 @@ int APS5_VABI sceNpManagerIntLoginGetAccessToken(void) { } int APS5_VABI sceNpAbortRequest(int req_id) { - (void)req_id; - return 0; + std::lock_guard lock(requestMutex); + return liveRequests.count(req_id) ? 0 : SCE_NP_ERROR_REQUEST_NOT_FOUND; } int APS5_VABI sceNpCheckCallback(void) { @@ -683,9 +693,9 @@ int APS5_VABI sceNpCheckCallback(void) { return 0; } -int APS5_VABI sceNpCheckNpAvailability(int req_id, const NpOnlineId* online_id) { - (void)req_id; - if (!online_id) return SCE_NP_ERROR_INVALID_ARGUMENT; +int APS5_VABI sceNpCheckNpAvailability(std::int32_t requestId, const NpOnlineId* onlineId, std::uint8_t* available) { + if (requestId == 0 || onlineId == nullptr) return SCE_NP_ERROR_INVALID_ARGUMENT; + if (available != nullptr) *available = 0; return SCE_NP_ERROR_USER_NOT_FOUND; } @@ -713,6 +723,7 @@ int APS5_VABI sceNpCreateRequest(void) { int APS5_VABI sceNpDeleteRequest(int req_id) { std::lock_guard lock(requestMutex); + if (!liveRequests.count(req_id)) return SCE_NP_ERROR_REQUEST_NOT_FOUND; liveRequests.erase(req_id); return 0; } @@ -823,8 +834,10 @@ int APS5_VABI sceNpHasSignedUp(int user_id, bool* has_signed_up) { } int APS5_VABI sceNpPollAsync(int req_id, int* result) { - (void)req_id; - if (result) *result = SCE_NP_ERROR_SIGNED_OUT; + if (!result) return SCE_NP_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(requestMutex); + if (!liveRequests.count(req_id)) return SCE_NP_ERROR_REQUEST_NOT_FOUND; + *result = SCE_NP_ERROR_SIGNED_OUT; return NP_POLL_ASYNC_FINISHED; } @@ -871,9 +884,19 @@ int APS5_VABI sceNpRegisterStateCallback(NpStateCallback callback, void* userdat } int APS5_VABI sceNpSetContentRestriction(const NpContentRestriction* restriction) { - (void)restriction; - NotImplemented_nid_no_patch(__func__); - return 0; + struct Fields { + std::uint64_t size; + std::int8_t defaultAgeRestriction; + std::uint8_t padding[3]; + std::int32_t ageRestrictionCount; + const void* ageRestrictions; + }; + if (!restriction) return SCE_NP_ERROR_INVALID_ARGUMENT; + const auto* fields = reinterpret_cast(restriction); + if (fields->size != sizeof(Fields)) return SCE_NP_ERROR_INVALID_SIZE; + if (fields->defaultAgeRestriction < 0 || fields->ageRestrictionCount > 0x100) return SCE_NP_ERROR_INVALID_ARGUMENT; + if (fields->ageRestrictionCount > 0 && !fields->ageRestrictions) return SCE_NP_ERROR_INVALID_ARGUMENT; + return 0; } int APS5_VABI sceNpSetNpTitleId(const NpTitleId* title_id, const NpTitleSecret* title_secret) { @@ -933,7 +956,7 @@ int APS5_VABI sceNpRegisterStateCallbackA(NpStateCallbackA callback, void* userd } int APS5_VABI sceNpUnregisterStateCallbackA(int callbackId) { - if (callbackId < 1 || callbackId > NP_STATE_CALLBACK_MAX) return SCE_NP_ERROR_INVALID_ARGUMENT; + if (static_cast(callbackId) - 1u >= static_cast(NP_STATE_CALLBACK_MAX)) return SCE_NP_ERROR_INVALID_ID; std::lock_guard lock(stateMutex); auto& entry = stateCallbacksA[callbackId - 1]; if (!entry.callback) return SCE_NP_ERROR_CALLBACK_NOT_REGISTERED; @@ -961,7 +984,7 @@ int APS5_VABI sceNpGetSandboxInfo(void* sandbox_info) { } APS5_EXPORT("8weiQyIdI3A", sceNpUnknown_8weiQyIdI3A); -int APS5_VABI sceNpUnknown_8weiQyIdI3A(int userId, void* buffer) { +int APS5_VABI sceNpUnknown_8weiQyIdI3A(int userId, const NpOnlineId* buffer) { (void)userId; (void)buffer; NotImplemented_nid_no_patch(__func__); @@ -974,8 +997,55 @@ int APS5_VABI sceNpUnknown_WxLcN76q19o(void) { return 0; } - int APS5_VABI sceNpCheckCallbackForLib() { + return sceNpCheckCallback(); +} + +} + +extern "C" { + +int APS5_VABI sceNpInGameMessageCreateHandle() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpInGameMessageDeleteHandle() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpInGameMessageInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpInGameMessagePrepare() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpInGameMessageSendData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpInGameMessageTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpSnsFacebookCreateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpSnsFacebookDeleteRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpSnsFacebookGetAccessToken() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceNpManager/include/Api.hpp b/core/libs/prx/libSceNpManager/include/Api.hpp new file mode 100644 index 0000000000..11a6828eb8 --- /dev/null +++ b/core/libs/prx/libSceNpManager/include/Api.hpp @@ -0,0 +1,218 @@ +#pragma once + +#include "SceTypes.hpp" + +using NpStateCallback = void (APS5_VABI *)(int userId, uint32_t state, NpId* npId, void* userdata); + +using NpStateCallbackA = void (APS5_VABI *)(int userId, uint32_t state, void* userdata); + +extern "C" { + +int APS5_VABI sceNpAsmAbort(void); +int APS5_VABI sceNpAsmClientAbortRequest(void); +int APS5_VABI sceNpAsmClientClearNpTitleToken(void); +int APS5_VABI sceNpAsmClientClearNpTitleTokenA(void); +int APS5_VABI sceNpAsmClientCreateRequest2(void); +int APS5_VABI sceNpAsmClientCreateResourceContext(void); +int APS5_VABI sceNpAsmClientCreateResourceContext2(void); +int APS5_VABI sceNpAsmClientDeleteRequest(void); +int APS5_VABI sceNpAsmClientDeleteResourceContext(void); +int APS5_VABI sceNpAsmClientDeleteResourceContext2(void); +int APS5_VABI sceNpAsmClientGetAppId(void); +int APS5_VABI sceNpAsmClientGetCacheControlMaxAge(void); +int APS5_VABI sceNpAsmClientGetGameNpTitleInfo(void); +int APS5_VABI sceNpAsmClientGetGameNpTitleToken(void); +int APS5_VABI sceNpAsmClientGetGameTitleBanInfo(void); +int APS5_VABI sceNpAsmClientGetNpComInfo2(void); +int APS5_VABI sceNpAsmClientGetNpComInfo2A(void); +int APS5_VABI sceNpAsmClientGetNpComInfo3(void); +int APS5_VABI sceNpAsmClientGetNpComInfo4(void); +int APS5_VABI sceNpAsmClientGetNpTitleId(void); +int APS5_VABI sceNpAsmClientGetNpTitleToken(void); +int APS5_VABI sceNpAsmClientGetNpTitleToken2(void); +int APS5_VABI sceNpAsmClientGetNpTitleTokenA(void); +int APS5_VABI sceNpAsmClientGetRelatedGameNpTitleIds(void); +int APS5_VABI sceNpAsmClientGetRelatedGameNpTitleIdsA(void); +int APS5_VABI sceNpAsmClientGetRelatedGameNpTitleIdsResult(void); +int APS5_VABI sceNpAsmClientGetServiceBaseUrl(void); +int APS5_VABI sceNpAsmClientGetServiceBaseUrlA(void); +int APS5_VABI sceNpAsmClientGetServiceBaseUrlWithNpTitleId(void); +int APS5_VABI sceNpAsmClientGetServiceBaseUrlWithNpTitleIdA(void); +int APS5_VABI sceNpAsmClientGetServiceIdInfo(void); +int APS5_VABI sceNpAsmClientGetServiceIdInfoA(void); +int APS5_VABI sceNpAsmClientInitialize(void); +int APS5_VABI sceNpAsmClientSetNpTitleId(void); +int APS5_VABI sceNpAsmClientTerminate(void); +int APS5_VABI sceNpAsmCreateConnection(void); +int APS5_VABI sceNpAsmCreateRequest(void); +int APS5_VABI sceNpAsmDeleteConnection(void); +int APS5_VABI sceNpAsmDeleteRequest(void); +int APS5_VABI sceNpAsmGenerateNpTitleToken(void); +int APS5_VABI sceNpAsmGenerateNpTitleToken2(void); +int APS5_VABI sceNpAsmGetNpCommInfo(void); +int APS5_VABI sceNpAsmGetNpCommInfo2(void); +int APS5_VABI sceNpAsmGetRelatedGameNpTitleIds(void); +int APS5_VABI sceNpAsmGetServiceBaseUrl(void); +int APS5_VABI sceNpAsmGetServiceIdInfo(void); +int APS5_VABI sceNpAsmInitialize(void); +int APS5_VABI sceNpAsmTerminate(void); +int APS5_VABI sceNpCheckNpAvailabilityA(int reqId, int userId); +int APS5_VABI sceNpGetAccountDateOfBirthA(int userId, void* dateOfBirth); +int APS5_VABI sceNpGetAccountLanguageA(int reqId, int userId, void* language); +int APS5_VABI sceNpGetGamePresenceStatus(const NpOnlineId* onlineId, uint32_t* status); +int APS5_VABI sceNpGetGamePresenceStatusA(int userId, uint32_t* status); +int APS5_VABI sceNpGetParentalControlInfoA(int reqId, int userId, int8_t* age, void* info); +int APS5_VABI sceNpIdMapperAbortRequest(void); +int APS5_VABI sceNpIdMapperAccountIdToNpId(void); +int APS5_VABI sceNpIdMapperAccountIdToOnlineId(void); +int APS5_VABI sceNpIdMapperCreateRequest(void); +int APS5_VABI sceNpIdMapperDeleteRequest(void); +int APS5_VABI sceNpIdMapperNpIdToAccountId(void); +int APS5_VABI sceNpIdMapperOnlineIdToAccountId(void); +int APS5_VABI sceNpInGameMessageAbortHandle(void); +int APS5_VABI sceNpInGameMessageCheckCallback(void); +int APS5_VABI sceNpInGameMessageGetMemoryPoolStatistics(void); +int APS5_VABI sceNpInGameMessagePrepareA(void); +int APS5_VABI sceNpInGameMessageSendDataA(void); +int APS5_VABI sceNpIntGetAppType(void); +int APS5_VABI sceNpIntGetGamePresenceStatus(void); +int APS5_VABI sceNpIntGetNpTitleId(void); +int APS5_VABI sceNpIntGetNpTitleIdSecret(void); +int APS5_VABI sceNpIntRegisterCheckCallback(void); +int APS5_VABI sceNpIntRegisterGamePresenceCallback(void); +int APS5_VABI sceNpIsPlusMember(int, bool* isMember); +int APS5_VABI sceNpManagerIntAbortRequest(void); +int APS5_VABI sceNpManagerIntAddActiveSigninStateCallback(void); +int APS5_VABI sceNpManagerIntAddOnlineIdChangeCallback(void); +int APS5_VABI sceNpManagerIntAddPlusMemberTypeCallback(void); +int APS5_VABI sceNpManagerIntAddSigninStateCallback(void); +int APS5_VABI sceNpManagerIntAuthGetAuthorizationCode(void); +int APS5_VABI sceNpManagerIntAuthGetIdToken(void); +int APS5_VABI sceNpManagerIntCheckNpAvailability(void); +int APS5_VABI sceNpManagerIntCheckNpAvailabilityByPid(void); +int APS5_VABI sceNpManagerIntCheckNpState(void); +int APS5_VABI sceNpManagerIntCheckNpStateA(void); +int APS5_VABI sceNpManagerIntClearGameAccessToken(void); +int APS5_VABI sceNpManagerIntClearOnlineIdChangeFlag(void); +int APS5_VABI sceNpManagerIntClearTicket(void); +int APS5_VABI sceNpManagerIntClearUsedFlag(void); +int APS5_VABI sceNpManagerIntClearVshAccessToken(void); +int APS5_VABI sceNpManagerIntCreateAuthenticationTicket(void); +int APS5_VABI sceNpManagerIntCreateLoginContext(void); +int APS5_VABI sceNpManagerIntCreateLoginRequest(void); +int APS5_VABI sceNpManagerIntCreateRequest(void); +int APS5_VABI sceNpManagerIntDeleteLoginContext(void); +int APS5_VABI sceNpManagerIntDeleteRequest(void); +int APS5_VABI sceNpManagerIntGetAccessTokenViaImplicitFlow(void); +int APS5_VABI sceNpManagerIntGetAccountAge(void); +int APS5_VABI sceNpManagerIntGetAccountCountry(void); +int APS5_VABI sceNpManagerIntGetAccountCountryA(void); +int APS5_VABI sceNpManagerIntGetAccountCountrySdk(void); +int APS5_VABI sceNpManagerIntGetAccountDateOfBirthSdk(void); +int APS5_VABI sceNpManagerIntGetAccountId(void); +int APS5_VABI sceNpManagerIntGetAccountIdSdk(void); +int APS5_VABI sceNpManagerIntGetAccountLanguage(void); +int APS5_VABI sceNpManagerIntGetAccountLanguageA(void); +int APS5_VABI sceNpManagerIntGetAccountNpEnv(void); +int APS5_VABI sceNpManagerIntGetAccountType(void); +int APS5_VABI sceNpManagerIntGetActiveSigninState(void); +int APS5_VABI sceNpManagerIntGetAuthServerErrorFlag(void); +int APS5_VABI sceNpManagerIntGetAuthorizationCode(void); +int APS5_VABI sceNpManagerIntGetAuthorizationCodeWithPsnoUri(void); +int APS5_VABI sceNpManagerIntGetClientCredentialAccessToken(void); +int APS5_VABI sceNpManagerIntGetCommunicationRestrictionStatus(void); +int APS5_VABI sceNpManagerIntGetGameAccessToken(void); +int APS5_VABI sceNpManagerIntGetIssuerId(void); +int APS5_VABI sceNpManagerIntGetNpEnv(void); +int APS5_VABI sceNpManagerIntGetNpId(void); +int APS5_VABI sceNpManagerIntGetNpIdByOnlineId(void); +int APS5_VABI sceNpManagerIntGetNpIdSdk(void); +int APS5_VABI sceNpManagerIntGetOfflineAccountId(void); +int APS5_VABI sceNpManagerIntGetOnlineId(void); +int APS5_VABI sceNpManagerIntGetOnlineIdChangeFlag(void); +int APS5_VABI sceNpManagerIntGetOnlineIdSdk(void); +int APS5_VABI sceNpManagerIntGetParentalControlFlag(void); +int APS5_VABI sceNpManagerIntGetParentalControlInfo(void); +int APS5_VABI sceNpManagerIntGetPlusMemberType(void); +int APS5_VABI sceNpManagerIntGetPlusMemberTypeNB(void); +int APS5_VABI sceNpManagerIntGetServerError(void); +int APS5_VABI sceNpManagerIntGetSigninState(void); +int APS5_VABI sceNpManagerIntGetSigninTelemetryInfo(void); +int APS5_VABI sceNpManagerIntGetTicket(void); +int APS5_VABI sceNpManagerIntGetTicketA(void); +int APS5_VABI sceNpManagerIntGetTitleToken(void); +int APS5_VABI sceNpManagerIntGetTitleTokenWithCheck(void); +int APS5_VABI sceNpManagerIntGetUserIdByAccountId(void); +int APS5_VABI sceNpManagerIntGetUserIdByNpId(void); +int APS5_VABI sceNpManagerIntGetUserIdByOnlineId(void); +int APS5_VABI sceNpManagerIntGetUserIdByOnlineIdSdk(void); +int APS5_VABI sceNpManagerIntGetUserList(void); +int APS5_VABI sceNpManagerIntGetUserNum(void); +int APS5_VABI sceNpManagerIntGetVshAccessToken(void); +int APS5_VABI sceNpManagerIntGetVshAccessTokenWithCheck(void); +int APS5_VABI sceNpManagerIntGetVshClientId(void); +int APS5_VABI sceNpManagerIntIsSubAccount(void); +int APS5_VABI sceNpManagerIntIsTemporarySignout(void); +int APS5_VABI sceNpManagerIntIsUnregisteredClientError(void); +int APS5_VABI sceNpManagerIntLoginAuthenticate(void); +int APS5_VABI sceNpManagerIntLoginBind(void); +int APS5_VABI sceNpManagerIntLoginCreateAuthenticationTicket(void); +int APS5_VABI sceNpManagerIntLoginGetAccessToken(void); +int APS5_VABI sceNpAbortRequest(int reqId); +int APS5_VABI sceNpCheckCallback(void); +int APS5_VABI sceNpCheckNpAvailability(std::int32_t requestId, const NpOnlineId* onlineId, std::uint8_t* available); +int APS5_VABI sceNpCheckNpReachability(int reqId, int userId); +int APS5_VABI sceNpCheckPremium(int reqId, const NpCheckPremiumParameter* param, NpCheckPremiumResult* result); +int APS5_VABI sceNpCreateAsyncRequest(const NpCreateAsyncRequestParameter* param); +int APS5_VABI sceNpCreateRequest(void); +int APS5_VABI sceNpDeleteRequest(int reqId); +int APS5_VABI sceNpGetAccountAge(int reqId, int userId, uint8_t* age); +int APS5_VABI sceNpGetAccountCountry(const NpOnlineId* onlineId, void* countryCode); +int APS5_VABI sceNpGetAccountDateOfBirth(const NpOnlineId* onlineId, void* dateOfBirth); +int APS5_VABI sceNpGetAccountLanguage(int reqId, const NpOnlineId* onlineId, void* language); +int APS5_VABI sceNpGetParentalControlInfo(int reqId, const NpOnlineId* onlineId, int8_t* age, void* info); +int APS5_VABI sceNpGetUserIdByOnlineId(const NpOnlineId* onlineId, int* userId); +int APS5_VABI sceNpCheckPlus(int reqId, const void* param, void* result); +int APS5_VABI sceNpNotifyPlusFeature(const void* param); +int APS5_VABI sceNpGetAccountId(const NpOnlineId* onlineId, uint64_t* accountId); +int APS5_VABI sceNpSetGamePresenceOnline(int reqId, const NpOnlineId* onlineId, uint32_t presence); +int APS5_VABI sceNpGetAccountCountryA(int userId, void* countryCode); +int APS5_VABI sceNpGetAccountIdA(int userId, uint64_t* accountId); +int APS5_VABI sceNpGetNpId(int userId, NpId* npId); +int APS5_VABI sceNpGetNpReachabilityState(int userId, uint32_t* state); +int APS5_VABI sceNpGetOnlineId(int userId, NpOnlineId* onlineId); +int APS5_VABI sceNpGetState(int userId, uint32_t* state); +int APS5_VABI sceNpHasSignedUp(int userId, bool* hasSignedUp); +int APS5_VABI sceNpPollAsync(int reqId, int* result); +void APS5_VABI sceNpRegisterGamePresenceCallback(void* callback, void* userdata); +int APS5_VABI sceNpRegisterNpReachabilityStateCallback(void* callback, void* userdata); +int APS5_VABI sceNpUnregisterNpReachabilityStateCallback(void); +int APS5_VABI sceNpRegisterPlusEventCallback(void* callback, void* userdata); +int APS5_VABI sceNpRegisterPremiumEventCallback(void* callback, void* userdata); +int APS5_VABI sceNpRegisterStateCallback(NpStateCallback callback, void* userdata); +int APS5_VABI sceNpSetContentRestriction(const NpContentRestriction* restriction); +int APS5_VABI sceNpSetNpTitleId(const NpTitleId* titleId, const NpTitleSecret* titleSecret); +int APS5_VABI sceNpSetTimeout(int reqId, int32_t resolveRetry, uint32_t resolveTimeout, uint32_t connTimeout, uint32_t sendTimeout, uint32_t recvTimeout); +int APS5_VABI sceNpUnregisterStateCallback(void); +int APS5_VABI sceNpGetAccountLanguage2(int reqId, int userId, void* language); +int APS5_VABI sceNpNotifyPremiumFeature(const void* param); +int APS5_VABI sceNpRegisterStateCallbackA(NpStateCallbackA callback, void* userdata); +int APS5_VABI sceNpUnregisterStateCallbackA(int callbackId); +int APS5_VABI sceNpUnregisterPremiumEventCallback(void); +int APS5_VABI sceNpUnregisterPlusEventCallback(void); +int APS5_VABI sceNpGetUserIdByAccountId(uint64_t accountId, int* userId); +int APS5_VABI sceNpGetSandboxInfo(void* sandboxInfo); +int APS5_VABI sceNpUnknown_8weiQyIdI3A(int userId, const NpOnlineId* buffer); +int APS5_VABI sceNpUnknown_WxLcN76q19o(void); +int APS5_VABI sceNpCheckCallbackForLib(); +int APS5_VABI sceNpInGameMessageCreateHandle(); +int APS5_VABI sceNpInGameMessageDeleteHandle(); +int APS5_VABI sceNpInGameMessageInitialize(); +int APS5_VABI sceNpInGameMessagePrepare(); +int APS5_VABI sceNpInGameMessageSendData(); +int APS5_VABI sceNpInGameMessageTerminate(); +int APS5_VABI sceNpSnsFacebookCreateRequest(); +int APS5_VABI sceNpSnsFacebookDeleteRequest(); +int APS5_VABI sceNpSnsFacebookGetAccessToken(); + +} diff --git a/core/libs/prx/libSceNpManager/libSceNpManager.hpp b/core/libs/prx/libSceNpManager/libSceNpManager.hpp new file mode 100644 index 0000000000..5fa5fa4390 --- /dev/null +++ b/core/libs/prx/libSceNpManager/libSceNpManager.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpManager/include/Api.hpp" diff --git a/core/libs/prx/libSceNpMatching2/CMakeLists.txt b/core/libs/prx/libSceNpMatching2/CMakeLists.txt new file mode 100644 index 0000000000..06978af7ad --- /dev/null +++ b/core/libs/prx/libSceNpMatching2/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceNpMatching2 SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpMatching2 PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpMatching2) + +if(BUILD_TESTING) + add_test_executable(guest_np_matching2_offline_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpMatching2Offline.cpp) + target_include_directories(guest_np_matching2_offline_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_matching2_offline_tests PRIVATE libSceNpMatching2) + configure_windows_unwind(guest_np_matching2_offline_tests) + add_test(NAME guest_np_matching2_offline COMMAND guest_np_matching2_offline_tests) + set_tests_properties(guest_np_matching2_offline PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_matching2_offline PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpMatching2/Export.cpp b/core/libs/prx/libSceNpMatching2/Export.cpp new file mode 100644 index 0000000000..3797bf7136 --- /dev/null +++ b/core/libs/prx/libSceNpMatching2/Export.cpp @@ -0,0 +1,218 @@ +#include "prx/libSceNpMatching2/include/Api.hpp" +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +static constexpr int SCE_NP_MATCHING2_ERROR_ALREADY_INITIALIZED = static_cast(0x80550C02); +static constexpr int SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED = static_cast(0x80550C03); +static constexpr int SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT = static_cast(0x80550C0A); +static constexpr int SCE_NP_MATCHING2_ERROR_CONTEXT_MAX = static_cast(0x80550C04); +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 int MAX_CONTEXTS = 10; +static constexpr std::uint64_t CREATE_CONTEXT_PARAMETER_SIZE = 0x28; + +namespace { + +static_assert(offsetof(NpMatching2InitializeParameter, size) == 0x20); +static_assert(sizeof(NpMatching2InitializeParameter) == 0x30); + +static_assert(sizeof(NpMatching2CreateContextParameter) == CREATE_CONTEXT_PARAMETER_SIZE); + +std::mutex matching2Mutex; +bool matching2Initialized = false; +std::unordered_set contexts; +int nextContextId = 1; + +int CheckContext(std::uint16_t ctxId) { + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + return contexts.contains(ctxId) ? 0 : SCE_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; +} + +} + +extern "C" { + +int APS5_VABI sceNpMatching2ContextStart(std::uint16_t ctxId, std::uint64_t timeout) { + (void)ctxId; + (void)timeout; + std::lock_guard lock(matching2Mutex); + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + return SCE_NET_ERROR_RESOLVER_ETIMEDOUT; +} + +int APS5_VABI sceNpMatching2ContextStop(std::uint16_t ctxId) { + std::lock_guard lock(matching2Mutex); + if (const int error = CheckContext(ctxId)) return error; + return SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED; +} + +int APS5_VABI sceNpMatching2CreateContext(const NpMatching2CreateContextParameter* param, std::uint16_t* ctxId) { + std::lock_guard lock(matching2Mutex); + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + if (!param || param->size != CREATE_CONTEXT_PARAMETER_SIZE || !ctxId) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + for (int tried = 0; tried < MAX_CONTEXTS; ++tried) { + const auto candidate = static_cast((nextContextId - 1 + tried) % MAX_CONTEXTS + 1); + if (contexts.contains(candidate)) continue; + contexts.insert(candidate); + nextContextId = candidate % MAX_CONTEXTS + 1; + *ctxId = candidate; + return 0; + } + return SCE_NP_MATCHING2_ERROR_CONTEXT_MAX; +} + +int APS5_VABI sceNpMatching2CreateJoinRoom() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2DestroyContext(std::uint16_t ctxId) { + std::lock_guard lock(matching2Mutex); + if (const int error = CheckContext(ctxId)) return error; + contexts.erase(ctxId); + return 0; +} + +int APS5_VABI sceNpMatching2GetRoomDataExternalList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2GetRoomDataInternal() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2GetRoomMemberDataInternal() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2GetServerId() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2GetUserInfoList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2GetWorldInfoList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2Initialize(const NpMatching2InitializeParameter* param) { + std::lock_guard lock(matching2Mutex); + if (matching2Initialized) return SCE_NP_MATCHING2_ERROR_ALREADY_INITIALIZED; + if (!param || (param->size != offsetof(NpMatching2InitializeParameter, sslPoolSize) && param->size != sizeof(NpMatching2InitializeParameter))) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + matching2Initialized = true; + return 0; +} + +int APS5_VABI sceNpMatching2JoinRoom() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2KickoutRoomMember() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2LeaveRoom() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2RegisterContextCallback(void* callback, void* userdata) { + (void)callback; + (void)userdata; + std::lock_guard lock(matching2Mutex); + return matching2Initialized ? 0 : SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; +} + +int APS5_VABI sceNpMatching2RegisterRoomEventCallback(std::uint16_t ctxId, void* callback, void* userdata) { + (void)callback; + (void)userdata; + std::lock_guard lock(matching2Mutex); + return CheckContext(ctxId); +} + +int APS5_VABI sceNpMatching2RegisterRoomMessageCallback(std::uint16_t ctxId, void* callback, void* userdata) { + (void)callback; + (void)userdata; + std::lock_guard lock(matching2Mutex); + return CheckContext(ctxId); +} + +int APS5_VABI sceNpMatching2RegisterSignalingCallback(std::uint16_t ctxId, void* callback, void* userdata) { + (void)callback; + (void)userdata; + std::lock_guard lock(matching2Mutex); + return CheckContext(ctxId); +} + +int APS5_VABI sceNpMatching2SearchRoom() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2SendRoomChatMessage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2SendRoomMessage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2SetDefaultRequestOptParam(std::uint16_t ctxId, const void* requestOpt) { + std::lock_guard lock(matching2Mutex); + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + if (!requestOpt) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + return CheckContext(ctxId); +} + +int APS5_VABI sceNpMatching2SetRoomDataExternal() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2SetRoomDataInternal() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2SetRoomMemberDataInternal() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2SetUserInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2SignalingGetConnectionInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpMatching2Terminate(void) { + std::lock_guard lock(matching2Mutex); + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + matching2Initialized = false; + contexts.clear(); + nextContextId = 1; + return 0; +} + +} diff --git a/core/libs/prx/libSceNpMatching2/include/Api.hpp b/core/libs/prx/libSceNpMatching2/include/Api.hpp new file mode 100644 index 0000000000..e7aca1bb5d --- /dev/null +++ b/core/libs/prx/libSceNpMatching2/include/Api.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "SceTypes.hpp" + +struct NpMatching2InitializeParameter { + std::uint64_t poolSize; + std::uint64_t cpuAffinityMask; + std::int32_t threadPriority; + std::uint8_t padding[4]; + std::uint64_t threadStackSize; + std::uint64_t size; + std::uint64_t sslPoolSize; +}; + +struct NpMatching2CreateContextParameter { + const void* npId; + const void* npCommunicationId; + const void* npPassphrase; + std::uint32_t serviceLabel; + std::uint64_t size; +}; + +extern "C" { + +int APS5_VABI sceNpMatching2ContextStart(std::uint16_t ctxId, std::uint64_t timeout); +int APS5_VABI sceNpMatching2ContextStop(std::uint16_t ctxId); +int APS5_VABI sceNpMatching2CreateContext(const NpMatching2CreateContextParameter* param, std::uint16_t* ctxId); +int APS5_VABI sceNpMatching2CreateJoinRoom(); +int APS5_VABI sceNpMatching2DestroyContext(std::uint16_t ctxId); +int APS5_VABI sceNpMatching2GetRoomDataExternalList(); +int APS5_VABI sceNpMatching2GetRoomDataInternal(); +int APS5_VABI sceNpMatching2GetRoomMemberDataInternal(); +int APS5_VABI sceNpMatching2GetServerId(); +int APS5_VABI sceNpMatching2GetUserInfoList(); +int APS5_VABI sceNpMatching2GetWorldInfoList(); +int APS5_VABI sceNpMatching2Initialize(const NpMatching2InitializeParameter* param); +int APS5_VABI sceNpMatching2JoinRoom(); +int APS5_VABI sceNpMatching2KickoutRoomMember(); +int APS5_VABI sceNpMatching2LeaveRoom(); +int APS5_VABI sceNpMatching2RegisterContextCallback(void* callback, void* userdata); +int APS5_VABI sceNpMatching2RegisterRoomEventCallback(std::uint16_t ctxId, void* callback, void* userdata); +int APS5_VABI sceNpMatching2RegisterRoomMessageCallback(std::uint16_t ctxId, void* callback, void* userdata); +int APS5_VABI sceNpMatching2RegisterSignalingCallback(std::uint16_t ctxId, void* callback, void* userdata); +int APS5_VABI sceNpMatching2SearchRoom(); +int APS5_VABI sceNpMatching2SendRoomChatMessage(); +int APS5_VABI sceNpMatching2SendRoomMessage(); +int APS5_VABI sceNpMatching2SetDefaultRequestOptParam(std::uint16_t ctxId, const void* requestOpt); +int APS5_VABI sceNpMatching2SetRoomDataExternal(); +int APS5_VABI sceNpMatching2SetRoomDataInternal(); +int APS5_VABI sceNpMatching2SetRoomMemberDataInternal(); +int APS5_VABI sceNpMatching2SetUserInfo(); +int APS5_VABI sceNpMatching2SignalingGetConnectionInfo(); +int APS5_VABI sceNpMatching2Terminate(void); + +} diff --git a/core/libs/prx/libSceNpMatching2/libSceNpMatching2.hpp b/core/libs/prx/libSceNpMatching2/libSceNpMatching2.hpp new file mode 100644 index 0000000000..9819896e2b --- /dev/null +++ b/core/libs/prx/libSceNpMatching2/libSceNpMatching2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpMatching2/include/Api.hpp" diff --git a/core/libs/prx/libSceNpPartner001/Export.cpp b/core/libs/prx/libSceNpPartner001/Export.cpp index d4dbeeefd9..2a4635c7c8 100644 --- a/core/libs/prx/libSceNpPartner001/Export.cpp +++ b/core/libs/prx/libSceNpPartner001/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpPartner001/include/Api.hpp" #include "prx/libc/include/General.hpp" #include #include diff --git a/core/libs/prx/libSceNpPartner001/include/Api.hpp b/core/libs/prx/libSceNpPartner001/include/Api.hpp new file mode 100644 index 0000000000..7254f21010 --- /dev/null +++ b/core/libs/prx/libSceNpPartner001/include/Api.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpEAAccessInitialize(void); +int APS5_VABI sceNpEAAccessTerminate(void); +int APS5_VABI sceNpHasEAAccessSubscription(int userId, bool* result); + +} diff --git a/core/libs/prx/libSceNpPartner001/libSceNpPartner001.hpp b/core/libs/prx/libSceNpPartner001/libSceNpPartner001.hpp new file mode 100644 index 0000000000..83c567edd0 --- /dev/null +++ b/core/libs/prx/libSceNpPartner001/libSceNpPartner001.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpPartner001/include/Api.hpp" diff --git a/core/libs/prx/libSceNpParty/Export.cpp b/core/libs/prx/libSceNpParty/Export.cpp index 67fdadc679..971b894470 100644 --- a/core/libs/prx/libSceNpParty/Export.cpp +++ b/core/libs/prx/libSceNpParty/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceNpParty/include/Api.hpp" +#include #include #include #include "SceTypes.hpp" @@ -5,9 +7,19 @@ namespace { +constexpr int NP_PARTY_ERROR_ALREADY_INITIALIZED = static_cast(0x80552502u); +constexpr int NP_PARTY_ERROR_NOT_INITIALIZED = static_cast(0x80552503u); constexpr int NP_PARTY_ERROR_INVALID_ARGUMENT = static_cast(0x80552504u); +constexpr int NP_PARTY_ERROR_NOT_IN_PARTY = static_cast(0x80552506u); constexpr std::uint16_t NP_PARTY_STATE_NOT_IN_PARTY = 2; +std::atomic g_initialized{false}; + +int notInParty(const void* out) { + if (!out) return NP_PARTY_ERROR_INVALID_ARGUMENT; + return NP_PARTY_ERROR_NOT_IN_PARTY; +} + } extern "C" { @@ -17,8 +29,9 @@ int APS5_VABI sceNpPartyGetState(std::uint16_t* state) { *state = NP_PARTY_STATE_NOT_IN_PARTY; return 0; } + int APS5_VABI sceNpPartyCheckCallback(void) { - NotImplemented_nid_no_patch(__func__); + if (!g_initialized) return NP_PARTY_ERROR_NOT_INITIALIZED; return 0; } @@ -32,39 +45,32 @@ int APS5_VABI sceNpPartyCreateA(void) { return 0; } -int APS5_VABI sceNpPartyGetId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetId(void* id) { + return notInParty(id); } -int APS5_VABI sceNpPartyGetMemberInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMemberInfo(std::uint16_t, void* memberInfo) { + return notInParty(memberInfo); } -int APS5_VABI sceNpPartyGetMemberInfoA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMemberInfoA(std::uint16_t, void* memberInfo) { + return notInParty(memberInfo); } -int APS5_VABI sceNpPartyGetMemberSessionInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMemberSessionInfo(std::uint16_t, void* memberGameSessionInfo) { + return notInParty(memberGameSessionInfo); } -int APS5_VABI sceNpPartyGetMemberVoiceInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMemberVoiceInfo(std::uint16_t, std::uint16_t, std::uint8_t* memberVoiceState) { + return notInParty(memberVoiceState); } -int APS5_VABI sceNpPartyGetMembers(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMembers(void* memberList) { + return notInParty(memberList); } -int APS5_VABI sceNpPartyGetMembersA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMembersA(void* memberList) { + return notInParty(memberList); } int APS5_VABI sceNpPartyGetStateAsUser(void) { @@ -72,9 +78,8 @@ int APS5_VABI sceNpPartyGetStateAsUser(void) { return 0; } -int APS5_VABI sceNpPartyGetStateAsUserA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetStateAsUserA(std::int32_t, std::uint16_t* state) { + return sceNpPartyGetState(state); } int APS5_VABI sceNpPartyGetVoiceChatPriority(void) { @@ -82,8 +87,10 @@ int APS5_VABI sceNpPartyGetVoiceChatPriority(void) { return 0; } -int APS5_VABI sceNpPartyInitialize(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpPartyInitialize(const void* param) { + if (!param) return NP_PARTY_ERROR_INVALID_ARGUMENT; + bool expected = false; + if (!g_initialized.compare_exchange_strong(expected, true)) return NP_PARTY_ERROR_ALREADY_INITIALIZED; return 0; } @@ -97,14 +104,14 @@ int APS5_VABI sceNpPartyLeave(void) { return 0; } -int APS5_VABI sceNpPartyRegisterHandler(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpPartyRegisterHandlerA(const void* handlers, void*) { + if (!g_initialized) return NP_PARTY_ERROR_NOT_INITIALIZED; + if (!handlers) return NP_PARTY_ERROR_INVALID_ARGUMENT; return 0; } -int APS5_VABI sceNpPartyRegisterHandlerA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyRegisterHandler(const void* handlers, void* userdata) { + return sceNpPartyRegisterHandlerA(handlers, userdata); } int APS5_VABI sceNpPartyRegisterPrivateHandler(void) { @@ -112,9 +119,8 @@ int APS5_VABI sceNpPartyRegisterPrivateHandler(void) { return 0; } -int APS5_VABI sceNpPartySendBinaryMessage(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartySendBinaryMessage(const void* sendParams) { + return notInParty(sendParams); } int APS5_VABI sceNpPartySetVoiceChatPriority(void) { @@ -133,7 +139,7 @@ int APS5_VABI sceNpPartyShowInvitationListA(void) { } int APS5_VABI sceNpPartyTerminate(void) { - NotImplemented_nid_no_patch(__func__); + if (!g_initialized.exchange(false)) return NP_PARTY_ERROR_NOT_INITIALIZED; return 0; } diff --git a/core/libs/prx/libSceNpParty/include/Api.hpp b/core/libs/prx/libSceNpParty/include/Api.hpp new file mode 100644 index 0000000000..8c8f33c8eb --- /dev/null +++ b/core/libs/prx/libSceNpParty/include/Api.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpPartyGetState(std::uint16_t* state); +int APS5_VABI sceNpPartyCheckCallback(void); +int APS5_VABI sceNpPartyCreate(void); +int APS5_VABI sceNpPartyCreateA(void); +int APS5_VABI sceNpPartyGetId(void* id); +int APS5_VABI sceNpPartyGetMemberInfo(std::uint16_t, void* memberInfo); +int APS5_VABI sceNpPartyGetMemberInfoA(std::uint16_t, void* memberInfo); +int APS5_VABI sceNpPartyGetMemberSessionInfo(std::uint16_t, void* memberGameSessionInfo); +int APS5_VABI sceNpPartyGetMemberVoiceInfo(std::uint16_t, std::uint16_t, std::uint8_t* memberVoiceState); +int APS5_VABI sceNpPartyGetMembers(void* memberList); +int APS5_VABI sceNpPartyGetMembersA(void* memberList); +int APS5_VABI sceNpPartyGetStateAsUser(void); +int APS5_VABI sceNpPartyGetStateAsUserA(std::int32_t, std::uint16_t* state); +int APS5_VABI sceNpPartyGetVoiceChatPriority(void); +int APS5_VABI sceNpPartyInitialize(const void* param); +int APS5_VABI sceNpPartyJoin(void); +int APS5_VABI sceNpPartyLeave(void); +int APS5_VABI sceNpPartyRegisterHandler(const void* handlers, void* userdata); +int APS5_VABI sceNpPartyRegisterHandlerA(const void* handlers, void*); +int APS5_VABI sceNpPartyRegisterPrivateHandler(void); +int APS5_VABI sceNpPartySendBinaryMessage(const void* sendParams); +int APS5_VABI sceNpPartySetVoiceChatPriority(void); +int APS5_VABI sceNpPartyShowInvitationList(void); +int APS5_VABI sceNpPartyShowInvitationListA(void); +int APS5_VABI sceNpPartyTerminate(void); +int APS5_VABI sceNpPartyUnregisterPrivateHandler(void); + +} diff --git a/core/libs/prx/libSceNpParty/libSceNpParty.hpp b/core/libs/prx/libSceNpParty/libSceNpParty.hpp new file mode 100644 index 0000000000..0b7c65e697 --- /dev/null +++ b/core/libs/prx/libSceNpParty/libSceNpParty.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpParty/include/Api.hpp" diff --git a/core/libs/prx/libSceNpProfileDialog/CMakeLists.txt b/core/libs/prx/libSceNpProfileDialog/CMakeLists.txt new file mode 100644 index 0000000000..6406591808 --- /dev/null +++ b/core/libs/prx/libSceNpProfileDialog/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceNpProfileDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpProfileDialog PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceNpProfileDialog PRIVATE libc) +configure_windows_unwind(libSceNpProfileDialog) + +if(BUILD_TESTING) + add_test_executable(guest_np_profile_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpProfileDialog.cpp) + target_include_directories(guest_np_profile_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_profile_dialog_tests PRIVATE libSceNpProfileDialog libc) + configure_windows_unwind(guest_np_profile_dialog_tests) + add_test(NAME guest_np_profile_dialog COMMAND guest_np_profile_dialog_tests) + if(WIN32) + set_tests_properties(guest_np_profile_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpProfileDialog/Export.cpp b/core/libs/prx/libSceNpProfileDialog/Export.cpp index b0dd450371..3ece49618f 100644 --- a/core/libs/prx/libSceNpProfileDialog/Export.cpp +++ b/core/libs/prx/libSceNpProfileDialog/Export.cpp @@ -1,48 +1,145 @@ +#include "prx/libSceNpProfileDialog/include/Api.hpp" #include #include +#include +#include +#include #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int COMMON_DIALOG_STATUS_NONE = 0; +constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; +constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; +constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; + +constexpr std::uint32_t NP_PROFILE_DIALOG_MODE_NORMAL = 1; +constexpr std::uint32_t NP_PROFILE_DIALOG_MODE_GRIEF_REPORT = 4; + +static_assert(offsetof(SceCommonDialogBaseParam, magic) == 44); +static_assert(sizeof(SceCommonDialogBaseParam) == 48); + +static_assert(offsetof(SceNpProfileDialogParam, size) == 48); +static_assert(offsetof(SceNpProfileDialogParam, mode) == 56); +static_assert(offsetof(SceNpProfileDialogParam, userId) == 60); +static_assert(offsetof(SceNpProfileDialogParam, targetOnlineId) == 64); +static_assert(offsetof(SceNpProfileDialogParam, userData) == 88); +static_assert(sizeof(SceNpProfileDialogParam) == 128); + +static_assert(offsetof(SceNpProfileDialogParamA, size) == 48); +static_assert(offsetof(SceNpProfileDialogParamA, mode) == 56); +static_assert(offsetof(SceNpProfileDialogParamA, userId) == 60); +static_assert(offsetof(SceNpProfileDialogParamA, targetAccountId) == 64); +static_assert(offsetof(SceNpProfileDialogParamA, userData) == 80); +static_assert(sizeof(SceNpProfileDialogParamA) == 120); + +static_assert(offsetof(SceNpProfileDialogResult, userAction) == 4); +static_assert(offsetof(SceNpProfileDialogResult, userData) == 8); +static_assert(offsetof(SceNpProfileDialogResult, reserved) == 16); +static_assert(sizeof(SceNpProfileDialogResult) == 48); + +struct DialogState { + std::mutex mutex; + int status = COMMON_DIALOG_STATUS_NONE; + void* userData = nullptr; +}; + +DialogState& Dialog() { + static DialogState state; + return state; +} + +[[noreturn]] void Unsettled(const char* function, const std::string& what) { + throw std::runtime_error(std::string(function) + ": " + what); +} + +template +int OpenDialog(const TParam* param, const char* function) { + DialogState& dialog = Dialog(); + std::lock_guard lock(dialog.mutex); + if (dialog.status != COMMON_DIALOG_STATUS_INITIALIZED && dialog.status != COMMON_DIALOG_STATUS_FINISHED) { + Unsettled(function, "status " + std::to_string(dialog.status) + " is neither INITIALIZED nor FINISHED, and the error for it is unsettled"); + } + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (param->size != sizeof(TParam)) { + Unsettled(function, "param size " + std::to_string(param->size) + " is not the " + std::to_string(sizeof(TParam)) + "-byte layout this library reads"); + } + if (param->mode < NP_PROFILE_DIALOG_MODE_NORMAL || param->mode > NP_PROFILE_DIALOG_MODE_GRIEF_REPORT) { + Unsettled(function, "mode " + std::to_string(param->mode) + " is not a known profile dialog mode"); + } + dialog.userData = param->userData; + dialog.status = COMMON_DIALOG_STATUS_FINISHED; + return COMMON_DIALOG_OK; +} + +} + extern "C" { -int APS5_VABI sceNpProfileDialogClose() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpProfileDialogInitialize(void) { + DialogState& dialog = Dialog(); + std::lock_guard lock(dialog.mutex); + if (dialog.status != COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + dialog.status = COMMON_DIALOG_STATUS_INITIALIZED; + dialog.userData = nullptr; + return COMMON_DIALOG_OK; } -int APS5_VABI sceNpProfileDialogGetResult() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpProfileDialogTerminate(void) { + DialogState& dialog = Dialog(); + std::lock_guard lock(dialog.mutex); + if (dialog.status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + dialog.status = COMMON_DIALOG_STATUS_NONE; + dialog.userData = nullptr; + return COMMON_DIALOG_OK; } -int APS5_VABI sceNpProfileDialogGetStatus() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpProfileDialogUpdateStatus(void) { + DialogState& dialog = Dialog(); + std::lock_guard lock(dialog.mutex); + return dialog.status; } -int APS5_VABI sceNpProfileDialogInitialize() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpProfileDialogGetStatus(void) { + DialogState& dialog = Dialog(); + std::lock_guard lock(dialog.mutex); + return dialog.status; } -int APS5_VABI sceNpProfileDialogOpen() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpProfileDialogOpen(const SceNpProfileDialogParam* param) { + return OpenDialog(param, __func__); } -int APS5_VABI sceNpProfileDialogOpenA() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpProfileDialogOpenA(const SceNpProfileDialogParamA* param) { + return OpenDialog(param, __func__); } -int APS5_VABI sceNpProfileDialogTerminate() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpProfileDialogClose(void) { + DialogState& dialog = Dialog(); + std::lock_guard lock(dialog.mutex); + if (dialog.status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + Unsettled(__func__, "status " + std::to_string(dialog.status) + " is not RUNNING, and the error for closing a dialog that is not running is unsettled"); } -int APS5_VABI sceNpProfileDialogUpdateStatus() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpProfileDialogGetResult(SceNpProfileDialogResult* result) { + DialogState& dialog = Dialog(); + std::lock_guard lock(dialog.mutex); + if (dialog.status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (dialog.status != COMMON_DIALOG_STATUS_FINISHED) { + if (result == nullptr) Unsettled(__func__, "a null result before the dialog finished has an unsettled error"); + return COMMON_DIALOG_ERROR_NOT_FINISHED; + } + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + *result = SceNpProfileDialogResult{}; + result->result = COMMON_DIALOG_OK; + result->userAction = COMMON_DIALOG_RESULT_USER_CANCELED; + result->userData = dialog.userData; + return COMMON_DIALOG_OK; } } diff --git a/core/libs/prx/libSceNpProfileDialog/include/Api.hpp b/core/libs/prx/libSceNpProfileDialog/include/Api.hpp new file mode 100644 index 0000000000..b505733fc0 --- /dev/null +++ b/core/libs/prx/libSceNpProfileDialog/include/Api.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +struct SceCommonDialogBaseParam { + std::uint64_t size; + std::uint8_t reserved[36]; + std::uint32_t magic; +}; + +struct SceNpProfileDialogParam { + SceCommonDialogBaseParam baseParam; + std::uint64_t size; + std::uint32_t mode; + std::int32_t userId; + NpOnlineId targetOnlineId; + std::uint8_t padding[4]; + void* userData; + std::uint8_t reserved[32]; +}; + +struct SceNpProfileDialogParamA { + SceCommonDialogBaseParam baseParam; + std::uint64_t size; + std::uint32_t mode; + std::int32_t userId; + std::uint64_t targetAccountId; + std::uint8_t padding[8]; + void* userData; + std::uint8_t reserved[32]; +}; + +struct SceNpProfileDialogResult { + std::int32_t result; + std::int32_t userAction; + void* userData; + std::uint8_t reserved[32]; +}; + +extern "C" { + +int APS5_VABI sceNpProfileDialogInitialize(void); +int APS5_VABI sceNpProfileDialogTerminate(void); +int APS5_VABI sceNpProfileDialogUpdateStatus(void); +int APS5_VABI sceNpProfileDialogGetStatus(void); +int APS5_VABI sceNpProfileDialogOpen(const SceNpProfileDialogParam* param); +int APS5_VABI sceNpProfileDialogOpenA(const SceNpProfileDialogParamA* param); +int APS5_VABI sceNpProfileDialogClose(void); +int APS5_VABI sceNpProfileDialogGetResult(SceNpProfileDialogResult* result); + +} diff --git a/core/libs/prx/libSceNpProfileDialog/libSceNpProfileDialog.hpp b/core/libs/prx/libSceNpProfileDialog/libSceNpProfileDialog.hpp new file mode 100644 index 0000000000..8cfbbcbcd4 --- /dev/null +++ b/core/libs/prx/libSceNpProfileDialog/libSceNpProfileDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpProfileDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp b/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp index 766da74191..a0f12e3e85 100644 --- a/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpRemotePlaySessionSignaling/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceNpRemotePlaySessionSignaling/include/Api.hpp b/core/libs/prx/libSceNpRemotePlaySessionSignaling/include/Api.hpp new file mode 100644 index 0000000000..9aeae0ac11 --- /dev/null +++ b/core/libs/prx/libSceNpRemotePlaySessionSignaling/include/Api.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpRemotePlaySessionSignalingCreateJoinSession(); +int APS5_VABI sceNpRemotePlaySessionSignalingGetConnectionStatus(); +int APS5_VABI sceNpRemotePlaySessionSignalingGetCustomProperty(); +int APS5_VABI sceNpRemotePlaySessionSignalingGetLocalNetInfo(); +int APS5_VABI sceNpRemotePlaySessionSignalingGetMemoryInfo(); +int APS5_VABI sceNpRemotePlaySessionSignalingInitialize(); +int APS5_VABI sceNpRemotePlaySessionSignalingJoinSession(); +int APS5_VABI sceNpRemotePlaySessionSignalingLeaveSession(); +int APS5_VABI sceNpRemotePlaySessionSignalingSetCustomProperty(); +int APS5_VABI sceNpRemotePlaySessionSignalingTerminate(); + +} diff --git a/core/libs/prx/libSceNpRemotePlaySessionSignaling/libSceNpRemotePlaySessionSignaling.hpp b/core/libs/prx/libSceNpRemotePlaySessionSignaling/libSceNpRemotePlaySessionSignaling.hpp new file mode 100644 index 0000000000..4c78dfe8e5 --- /dev/null +++ b/core/libs/prx/libSceNpRemotePlaySessionSignaling/libSceNpRemotePlaySessionSignaling.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpRemotePlaySessionSignaling/include/Api.hpp" diff --git a/core/libs/prx/libSceNpScoreRanking/CMakeLists.txt b/core/libs/prx/libSceNpScoreRanking/CMakeLists.txt new file mode 100644 index 0000000000..d73264a441 --- /dev/null +++ b/core/libs/prx/libSceNpScoreRanking/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceNpScoreRanking SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpScoreRanking PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpScoreRanking) + +if(BUILD_TESTING) + add_test_executable(guest_np_score_offline_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpScoreOffline.cpp) + target_include_directories(guest_np_score_offline_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_score_offline_tests PRIVATE libSceNpScoreRanking) + configure_windows_unwind(guest_np_score_offline_tests) + add_test(NAME guest_np_score_offline COMMAND guest_np_score_offline_tests) + set_tests_properties(guest_np_score_offline PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_score_offline PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpScoreRanking/Export.cpp b/core/libs/prx/libSceNpScoreRanking/Export.cpp new file mode 100644 index 0000000000..1edb9c64ca --- /dev/null +++ b/core/libs/prx/libSceNpScoreRanking/Export.cpp @@ -0,0 +1,225 @@ +#include "prx/libSceNpScoreRanking/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +static constexpr int SCE_NP_ERROR_SIGNED_OUT = static_cast(0x80550006); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT = static_cast(0x80550704); +static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = static_cast(0x80550706); +static constexpr int SCE_NP_COMMUNITY_ERROR_ABORTED = static_cast(0x80550707); +static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast(0x8055070C); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast(0x8055070E); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT = static_cast(0x80550714); +static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID = static_cast(0x80550719); +static constexpr std::uint32_t INVALID_SERVICE_LABEL = 0xFFFFFFFF; +static constexpr std::size_t MAX_OBJECTS = 32; +static constexpr std::uint64_t MAX_RANGE_PER_REQUEST = 100; +static constexpr std::uint64_t MAX_NPID_PER_REQUEST = 101; +static constexpr std::uint64_t GAME_INFO_MAX_SIZE = 189; +static constexpr std::uint64_t NP_ID_SIZE = 36; +static constexpr std::uint64_t RANK_DATA_SIZE = 0x80; +static constexpr std::uint64_t PLAYER_RANK_DATA_SIZE = 0x88; +static constexpr std::uint64_t COMMENT_SIZE = 64; +static constexpr std::uint64_t GAME_INFO_SIZE = 200; +static constexpr std::uint64_t FRIEND_RANKING_OPTION_SIZE = 24; +static constexpr int POLL_FINISHED = 0; +static constexpr int POLL_RUNNING = 1; + +static_assert(sizeof(NpScoreGameInfo) == GAME_INFO_SIZE); + +static_assert(sizeof(NpScoreFriendRankingOption) == FRIEND_RANKING_OPTION_SIZE); + +namespace { +struct Request { + int titleCtxId = 0; + std::optional result; +}; + +std::mutex scoreMutex; +std::unordered_set titleContexts; +std::unordered_map requests; +int nextTitleContext = 1; +int nextRequest = 1; + +int CheckArrays(std::uint64_t arrayNum, const void* commentArray, std::uint64_t commentArraySize, const void* infoArray, std::uint64_t infoArraySize) { + if (commentArray && commentArraySize != arrayNum * COMMENT_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + if (infoArray && infoArraySize != arrayNum * GAME_INFO_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + return 0; +} + +int Offline(int reqId) { + std::lock_guard lock(scoreMutex); + const auto request = requests.find(reqId); + if (request == requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (request->second.result == SCE_NP_COMMUNITY_ERROR_ABORTED) return SCE_NP_COMMUNITY_ERROR_ABORTED; + if (!request->second.result) request->second.result = SCE_NP_ERROR_SIGNED_OUT; + return SCE_NP_ERROR_SIGNED_OUT; +} + +int RecordScore(int reqId, const NpScoreGameInfo* gameInfo, void* option) { + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (gameInfo && gameInfo->infoSize > GAME_INFO_MAX_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return Offline(reqId); +} + +int GetRankingByRange(int reqId, std::uint32_t startSerialRank, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* option) { + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayNum > MAX_RANGE_PER_REQUEST) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (startSerialRank == 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!rankArray || arrayNum == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (rankArraySize != arrayNum * RANK_DATA_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + if (const int error = CheckArrays(arrayNum, commentArray, commentArraySize, infoArray, infoArraySize)) return error; + return Offline(reqId); +} + +int GetRankingByNpId(int reqId, const void* npIdArray, std::uint64_t npIdArraySize, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* option) { + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayNum > MAX_NPID_PER_REQUEST) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + if (!npIdArray || !rankArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (npIdArraySize != arrayNum * NP_ID_SIZE || rankArraySize != arrayNum * PLAYER_RANK_DATA_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + if (const int error = CheckArrays(arrayNum, commentArray, commentArraySize, infoArray, infoArraySize)) return error; + return Offline(reqId); +} + +int GetFriendsRanking(int reqId, int includeSelf, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, const NpScoreFriendRankingOption* option) { + if (!rankArray) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (rankArraySize != arrayNum * RANK_DATA_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + std::uint32_t startSerialRank = 1; + if (includeSelf == 0) { + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } else if (option) { + if (option->size != FRIEND_RANKING_OPTION_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (option->startSerialRank) startSerialRank = *option->startSerialRank; + } + if (arrayNum > MAX_RANGE_PER_REQUEST) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (startSerialRank == 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayNum == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (const int error = CheckArrays(arrayNum, commentArray, commentArraySize, infoArray, infoArraySize)) return error; + return Offline(reqId); +} +} + +extern "C" { + +int APS5_VABI sceNpScoreAbortRequest(int reqId) { + std::lock_guard lock(scoreMutex); + const auto request = requests.find(reqId); + if (request == requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (!request->second.result) request->second.result = SCE_NP_COMMUNITY_ERROR_ABORTED; + return 0; +} + +int APS5_VABI sceNpScoreCreateNpTitleCtx(std::uint32_t serviceLabel, const void* selfNpId) { + if (serviceLabel == INVALID_SERVICE_LABEL) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!selfNpId) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + std::lock_guard lock(scoreMutex); + if (titleContexts.size() >= MAX_OBJECTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int titleCtxId = nextTitleContext++; + titleContexts.insert(titleCtxId); + return titleCtxId; +} + +int APS5_VABI sceNpScoreCreateRequest(int titleCtxId) { + std::lock_guard lock(scoreMutex); + if (!titleContexts.contains(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (requests.size() >= MAX_OBJECTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int reqId = nextRequest++; + requests.emplace(reqId, Request{titleCtxId}); + return reqId; +} + +int APS5_VABI sceNpScoreDeleteNpTitleCtx(int titleCtxId) { + std::lock_guard lock(scoreMutex); + if (!titleContexts.erase(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + std::erase_if(requests, [titleCtxId](const auto& entry) { return entry.second.titleCtxId == titleCtxId; }); + return 0; +} + +int APS5_VABI sceNpScoreDeleteRequest(int reqId) { + std::lock_guard lock(scoreMutex); + return requests.erase(reqId) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID; +} + +int APS5_VABI sceNpScoreGetBoardInfo(int reqId, std::uint32_t boardId, void* boardInfo, void* option) { + (void)boardId; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!boardInfo) throw std::runtime_error("sceNpScoreGetBoardInfo: the answer for a null board info depends on the title's SDK version"); + return Offline(reqId); +} + +int APS5_VABI sceNpScoreGetFriendsRanking(int reqId, std::uint32_t boardId, int includeSelf, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, const NpScoreFriendRankingOption* option) { + (void)boardId; + (void)lastSortDate; + (void)totalRecord; + return GetFriendsRanking(reqId, includeSelf, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option); +} + +int APS5_VABI sceNpScoreGetFriendsRankingAsync(int reqId, std::uint32_t boardId, int includeSelf, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, const NpScoreFriendRankingOption* option) { + (void)boardId; + (void)lastSortDate; + (void)totalRecord; + return GetFriendsRanking(reqId, includeSelf, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option); +} + +int APS5_VABI sceNpScoreGetRankingByNpId(int reqId, std::uint32_t boardId, const void* npIdArray, std::uint64_t npIdArraySize, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, void* option) { + (void)boardId; + (void)lastSortDate; + (void)totalRecord; + return GetRankingByNpId(reqId, npIdArray, npIdArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option); +} + +int APS5_VABI sceNpScoreGetRankingByNpIdAsync(int reqId, std::uint32_t boardId, const void* npIdArray, std::uint64_t npIdArraySize, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, void* option) { + (void)boardId; + (void)lastSortDate; + (void)totalRecord; + return GetRankingByNpId(reqId, npIdArray, npIdArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option); +} + +int APS5_VABI sceNpScoreGetRankingByRange(int reqId, std::uint32_t boardId, std::uint32_t startSerialRank, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, void* option) { + (void)boardId; + (void)lastSortDate; + (void)totalRecord; + return GetRankingByRange(reqId, startSerialRank, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option); +} + +int APS5_VABI sceNpScoreGetRankingByRangeAsync(int reqId, std::uint32_t boardId, std::uint32_t startSerialRank, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, void* option) { + (void)boardId; + (void)lastSortDate; + (void)totalRecord; + return GetRankingByRange(reqId, startSerialRank, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option); +} + +int APS5_VABI sceNpScorePollAsync(int reqId, int* result) { + std::lock_guard lock(scoreMutex); + const auto request = requests.find(reqId); + if (request == requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (!request->second.result) return POLL_RUNNING; + if (result) *result = *request->second.result; + return POLL_FINISHED; +} + +int APS5_VABI sceNpScoreRecordScore(int reqId, std::uint32_t boardId, std::int64_t score, const void* scoreComment, const NpScoreGameInfo* gameInfo, std::uint32_t* tmpRank, const void* compareDate, void* option) { + (void)boardId; + (void)score; + (void)scoreComment; + (void)tmpRank; + (void)compareDate; + return RecordScore(reqId, gameInfo, option); +} + +int APS5_VABI sceNpScoreRecordScoreAsync(int reqId, std::uint32_t boardId, std::int64_t score, const void* scoreComment, const NpScoreGameInfo* gameInfo, std::uint32_t* tmpRank, const void* compareDate, void* option) { + (void)boardId; + (void)score; + (void)scoreComment; + (void)tmpRank; + (void)compareDate; + return RecordScore(reqId, gameInfo, option); +} + +} diff --git a/core/libs/prx/libSceNpScoreRanking/include/Api.hpp b/core/libs/prx/libSceNpScoreRanking/include/Api.hpp new file mode 100644 index 0000000000..3a665215ec --- /dev/null +++ b/core/libs/prx/libSceNpScoreRanking/include/Api.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "SceTypes.hpp" + +struct NpScoreFriendRankingOption { + std::uint64_t size; + const std::uint32_t* startSerialRank; + const std::uint32_t* hits; +}; + +struct NpScoreGameInfo { + std::uint64_t infoSize; + std::uint8_t data[189]; + std::uint8_t pad[3]; +}; + +extern "C" { + +int APS5_VABI sceNpScoreAbortRequest(int reqId); +int APS5_VABI sceNpScoreCreateNpTitleCtx(std::uint32_t serviceLabel, const void* selfNpId); +int APS5_VABI sceNpScoreCreateRequest(int titleCtxId); +int APS5_VABI sceNpScoreDeleteNpTitleCtx(int titleCtxId); +int APS5_VABI sceNpScoreDeleteRequest(int reqId); +int APS5_VABI sceNpScoreGetBoardInfo(int reqId, std::uint32_t boardId, void* boardInfo, void* option); +int APS5_VABI sceNpScoreGetFriendsRanking(int reqId, std::uint32_t boardId, int includeSelf, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, const NpScoreFriendRankingOption* option); +int APS5_VABI sceNpScoreGetFriendsRankingAsync(int reqId, std::uint32_t boardId, int includeSelf, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, const NpScoreFriendRankingOption* option); +int APS5_VABI sceNpScoreGetRankingByNpId(int reqId, std::uint32_t boardId, const void* npIdArray, std::uint64_t npIdArraySize, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, void* option); +int APS5_VABI sceNpScoreGetRankingByNpIdAsync(int reqId, std::uint32_t boardId, const void* npIdArray, std::uint64_t npIdArraySize, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, void* option); +int APS5_VABI sceNpScoreGetRankingByRange(int reqId, std::uint32_t boardId, std::uint32_t startSerialRank, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, void* option); +int APS5_VABI sceNpScoreGetRankingByRangeAsync(int reqId, std::uint32_t boardId, std::uint32_t startSerialRank, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* lastSortDate, void* totalRecord, void* option); +int APS5_VABI sceNpScorePollAsync(int reqId, int* result); +int APS5_VABI sceNpScoreRecordScore(int reqId, std::uint32_t boardId, std::int64_t score, const void* scoreComment, const NpScoreGameInfo* gameInfo, std::uint32_t* tmpRank, const void* compareDate, void* option); +int APS5_VABI sceNpScoreRecordScoreAsync(int reqId, std::uint32_t boardId, std::int64_t score, const void* scoreComment, const NpScoreGameInfo* gameInfo, std::uint32_t* tmpRank, const void* compareDate, void* option); + +} diff --git a/core/libs/prx/libSceNpScoreRanking/libSceNpScoreRanking.hpp b/core/libs/prx/libSceNpScoreRanking/libSceNpScoreRanking.hpp new file mode 100644 index 0000000000..312ac2276e --- /dev/null +++ b/core/libs/prx/libSceNpScoreRanking/libSceNpScoreRanking.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpScoreRanking/include/Api.hpp" diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index 7dfa95ff73..1123e55333 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -1,112 +1,239 @@ +#include "prx/libSceNpSessionSignaling/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -#include +#include +#include // Peer-to-peer signaling needs the network: contexts exist, but sessions never activate. static constexpr int SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT = static_cast(0x80553303); static constexpr int SCE_NP_SESSION_SIGNALING_ERROR_CONN_NOT_FOUND = static_cast(0x8055330C); static constexpr int SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE = static_cast(0x80552D06); -static std::atomic g_nextContext{1}; +static constexpr int SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED = static_cast(0x80553301); +static constexpr int SCE_NP_SESSION_SIGNALING_ERROR_ALREADY_INITIALIZED = static_cast(0x80553302); +static constexpr int SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_LIMIT = static_cast(0x80553307); +static constexpr int SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND = static_cast(0x80553308); +static constexpr int SCE_NP_SESSION_SIGNALING_ERROR_GROUP_NOT_FOUND = static_cast(0x8055330A); + +struct SessionSignalingContext { + std::uint32_t id; + std::int32_t userId; + bool active; +}; + +static std::mutex g_mutex; +static bool g_initialized = false; +static std::array g_contexts{}; + +static_assert(sizeof(SessionSignalingMemoryInfo) == 24); +static_assert(sizeof(SessionSignalingInitParam) == 40); +static_assert(offsetof(SessionSignalingInitParam, poolSize) == 8); +static_assert(offsetof(SessionSignalingInitParam, affinityMask) == 16); +static_assert(offsetof(SessionSignalingInitParam, threadPriority) == 24); +static_assert(offsetof(SessionSignalingInitParam, stackSize) == 32); +static_assert(offsetof(SessionSignalingMemoryInfo, usedSize) == 8); +static_assert(offsetof(SessionSignalingMemoryInfo, peakUsedSize) == 16); +static_assert(sizeof(SessionSignalingPeerAddress) == 16); +static_assert(sizeof(SessionSignalingSessionParam) == 8); +static_assert(offsetof(SessionSignalingSessionParam, option) == 4); +static_assert(sizeof(SessionSignalingNetAddress) == 8); +static_assert(offsetof(SessionSignalingNetAddress, port) == 4); +static_assert(sizeof(SessionSignalingConnectionInfo) == 16); +static_assert(sizeof(SessionSignalingLocalNetInfo) == 16); +static_assert(offsetof(SessionSignalingLocalNetInfo, mappedAddress) == 4); +static_assert(offsetof(SessionSignalingLocalNetInfo, mappedPort) == 8); +static_assert(offsetof(SessionSignalingLocalNetInfo, mappingStatus) == 12); +static_assert(sizeof(SessionSignalingContextParam1) == 56); +static_assert(sizeof(SessionSignalingContextParam2) == 56); +static_assert(offsetof(SessionSignalingContextParam1, prepareCallback) == 8); +static_assert(offsetof(SessionSignalingContextParam1, sessionCallback) == 24); +static_assert(offsetof(SessionSignalingContextParam1, connectionCallback) == 40); +static_assert(offsetof(SessionSignalingContextParam2, connectionCallback) == 40); +static_assert(offsetof(SessionSignalingContextParam2, connectionUserData) == 48); +static_assert(offsetof(SessionSignalingPeerAddress, platform) == 8); +static_assert(sizeof(SessionSignalingConnectionStatistics) == 16); +static_assert(offsetof(SessionSignalingConnectionStatistics, connectionCount) == 4); +static_assert(offsetof(SessionSignalingConnectionStatistics, connectingCount) == 8); +static_assert(offsetof(SessionSignalingConnectionStatistics, connectedCount) == 12); +static_assert(sizeof(SessionSignalingGroupInfo) == 264); +static_assert(offsetof(SessionSignalingGroupInfo, connectionCount) == 256); + +static SessionSignalingContext* FindContext(std::uint32_t contextId) { + auto& context = g_contexts[contextId % g_contexts.size()]; + return context.active && context.id == contextId ? &context : nullptr; +} + +template +static int CreateContext(const SessionSignalingContextParam* param, std::uint32_t* contextId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!param || !contextId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + for (const auto& context : g_contexts) { + if (context.active && context.userId == param->userId) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_LIMIT; + } + for (auto& context : g_contexts) { + if (!context.active) { + context.userId = param->userId; + context.active = true; + *contextId = context.id; + return 0; + } + } + return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_LIMIT; +} extern "C" { -int APS5_VABI sceNpSessionSignalingInitialize(void* param) { - (void)param; +int APS5_VABI sceNpSessionSignalingInitialize(const SessionSignalingInitParam* param) { + std::lock_guard lock(g_mutex); + if (g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_ALREADY_INITIALIZED; + if (!param || !param->poolSize) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + for (std::uint32_t index = 0; index < g_contexts.size(); ++index) { + g_contexts[index] = {1000 + index, 0, false}; + } + g_initialized = true; return 0; } -int APS5_VABI sceNpSessionSignalingActivateSession(void) { +int APS5_VABI sceNpSessionSignalingActivateSession(std::uint32_t contextId, const char* sessionId, std::uint32_t sessionType, const SessionSignalingSessionParam* param, std::uint32_t* groupId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!sessionId || !param || !groupId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + if ((param->mode == 1 && param->option != 0) || (param->mode == 2 && param->option == 0)) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; } -int APS5_VABI sceNpSessionSignalingCreateContext2(const void* param, uint32_t* context_id) { - (void)param; - if (!context_id) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; - *context_id = g_nextContext.fetch_add(1, std::memory_order_relaxed); +int APS5_VABI sceNpSessionSignalingCreateContext2(const SessionSignalingContextParam2* param, std::uint32_t* contextId) { + return CreateContext(param, contextId); +} + +int APS5_VABI sceNpSessionSignalingCreateContext(const SessionSignalingContextParam1* param, std::uint32_t* contextId) { + return CreateContext(param, contextId); +} + +int APS5_VABI sceNpSessionSignalingDeactivate(std::uint32_t contextId, std::uint32_t connectionId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_GROUP_NOT_FOUND; +} + +int APS5_VABI sceNpSessionSignalingDestroyContext(std::uint32_t contextId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + auto* context = FindContext(contextId); + if (!context) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + context->active = false; + const auto nextId = context->id + static_cast(g_contexts.size()); + context->id = nextId < 100001 ? nextId : 1000 + contextId % static_cast(g_contexts.size()); return 0; } -int APS5_VABI sceNpSessionSignalingCreateContext(const void* param, uint32_t* context_id) { - return sceNpSessionSignalingCreateContext2(param, context_id); +int APS5_VABI sceNpSessionSignalingGetConnectionInfo(std::uint32_t contextId, std::uint32_t connectionId, std::uint32_t code, SessionSignalingConnectionInfo* info) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!info) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_CONN_NOT_FOUND; } -int APS5_VABI sceNpSessionSignalingDeactivate(uint32_t context_id) { - (void)context_id; - return 0; -} - -int APS5_VABI sceNpSessionSignalingDestroyContext(uint32_t context_id) { - (void)context_id; - return 0; -} - -int APS5_VABI sceNpSessionSignalingGetConnectionInfo(void) { +int APS5_VABI sceNpSessionSignalingActivateUser(std::uint32_t contextId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* connectionId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!peerAddress || !connectionId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; } -int APS5_VABI sceNpSessionSignalingActivateUser(void) { - return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; -} - -int APS5_VABI sceNpSessionSignalingGetConnectionFromPeerAddress2(void) { - return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; +int APS5_VABI sceNpSessionSignalingGetConnectionFromPeerAddress2(std::uint32_t contextId, std::uint32_t groupId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* connectionId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!peerAddress || !connectionId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_GROUP_NOT_FOUND; } int APS5_VABI sceNpSessionSignalingTerminate(void) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + g_initialized = false; + for (auto& context : g_contexts) context.active = false; return 0; } -int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(int32_t context_id, int32_t connection_id, int32_t* status, void* peer_address, uint16_t* peer_port) { - (void)context_id; - (void)connection_id; - (void)peer_address; - (void)peer_port; +int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(std::uint32_t contextId, std::uint32_t connectionId, std::int32_t* status, std::uint32_t* peerAddress, std::uint16_t* peerPort) { + (void)connectionId; + (void)peerAddress; + (void)peerPort; + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; if (!status) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; return SCE_NP_SESSION_SIGNALING_ERROR_CONN_NOT_FOUND; } -int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* info) { - (void)context_id; +int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(std::uint32_t contextId, SessionSignalingLocalNetInfo* info) { + (void)contextId; + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; if (!info) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; } int APS5_VABI sceNpSessionSignalingRequestPrepare(uint32_t contextId, uint32_t* requestId) { - (void)contextId; + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; if (!requestId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; } -int APS5_VABI sceNpSessionSignalingGetMemoryInfo(void) { +int APS5_VABI sceNpSessionSignalingGetMemoryInfo(SessionSignalingMemoryInfo* memoryInfo) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpSessionSignalingGetConnectionStatistics(void) { +int APS5_VABI sceNpSessionSignalingGetConnectionStatistics(SessionSignalingConnectionStatistics* statistics) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpSessionSignalingGetConnectionFromPeerAddress(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpSessionSignalingGetConnectionFromPeerAddress(std::uint32_t contextId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* connectionId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!peerAddress || !connectionId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_CONN_NOT_FOUND; } -int APS5_VABI sceNpSessionSignalingGetGroupFromSessionId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpSessionSignalingGetGroupFromSessionId(std::uint32_t contextId, const char* sessionId, std::uint32_t* groupId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!sessionId || !groupId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_GROUP_NOT_FOUND; } -int APS5_VABI sceNpSessionSignalingGetGroupInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpSessionSignalingGetGroupInfo(std::uint32_t contextId, std::uint32_t groupId, SessionSignalingGroupInfo* groupInfo) { + (void)groupId; + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!groupInfo) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_GROUP_NOT_FOUND; } -int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress2() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress2(std::uint32_t contextId, std::uint32_t groupId, std::uint32_t address, std::uint16_t port, std::uint32_t* connectionId) { + (void)groupId; + (void)address; + (void)port; + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!connectionId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_GROUP_NOT_FOUND; } int APS5_VABI sceNpSessionSignalingCreateJoinActivate() { @@ -114,9 +241,14 @@ int APS5_VABI sceNpSessionSignalingCreateJoinActivate() { return 0; } -int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress(std::uint32_t contextId, std::uint32_t address, std::uint16_t port, std::uint32_t* connectionId) { + (void)address; + (void)port; + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!connectionId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_CONN_NOT_FOUND; } int APS5_VABI sceNpSessionSignalingGetCustomProperty() { diff --git a/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp b/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp new file mode 100644 index 0000000000..948b313253 --- /dev/null +++ b/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include + +struct SessionSignalingInitParam { + std::int32_t userId; + std::uint64_t poolSize; + std::uint64_t affinityMask; + std::int32_t threadPriority; + std::uint64_t stackSize; +}; + +struct SessionSignalingMemoryInfo { + std::uint64_t poolSize; + std::uint64_t usedSize; + std::uint64_t peakUsedSize; +}; + +struct SessionSignalingPeerAddress { + std::uint64_t accountId; + std::uint32_t platform; +}; + +struct SessionSignalingSessionParam { + std::uint32_t mode; + std::uint32_t option; +}; + +struct SessionSignalingNetAddress { + std::uint32_t address; + std::uint16_t port; +}; + +union SessionSignalingConnectionInfo { + std::uint32_t value; + SessionSignalingNetAddress netAddress; + SessionSignalingPeerAddress peerAddress; +}; + +struct SessionSignalingLocalNetInfo { + std::uint32_t localAddress; + std::uint32_t mappedAddress; + std::uint32_t mappedPort; + std::uint32_t mappingStatus; +}; + +using SessionSignalingEventCallback = void (APS5_VABI *)(std::uint32_t, std::uint32_t, std::int32_t, const SessionSignalingPeerAddress*, std::int32_t, void*); + +using SessionSignalingConnectionCallback = void (APS5_VABI *)(std::uint32_t, std::uint32_t, std::int32_t, std::int32_t, void*); + +using SessionSignalingConnectionCallback2 = void (APS5_VABI *)(std::uint32_t, std::uint32_t, std::uint32_t, std::int32_t, std::int32_t, void*); + +template +struct SessionSignalingContextParam { + std::int32_t userId; + std::uint32_t serviceLabel; + SessionSignalingEventCallback prepareCallback; + void* prepareUserData; + SessionSignalingEventCallback sessionCallback; + void* sessionUserData; + TConnectionCallback connectionCallback; + void* connectionUserData; +}; + +using SessionSignalingContextParam1 = SessionSignalingContextParam; + +using SessionSignalingContextParam2 = SessionSignalingContextParam; + +struct SessionSignalingConnectionStatistics { + std::uint32_t peakConnectionCount; + std::uint32_t connectionCount; + std::uint32_t connectingCount; + std::uint32_t connectedCount; +}; + +struct SessionSignalingGroupInfo { + std::uint32_t connectionIds[64]; + std::uint64_t connectionCount; +}; + +extern "C" { + +int APS5_VABI sceNpSessionSignalingInitialize(const SessionSignalingInitParam* param); +int APS5_VABI sceNpSessionSignalingActivateSession(std::uint32_t contextId, const char* sessionId, std::uint32_t sessionType, const SessionSignalingSessionParam* param, std::uint32_t* groupId); +int APS5_VABI sceNpSessionSignalingCreateContext2(const SessionSignalingContextParam2* param, std::uint32_t* contextId); +int APS5_VABI sceNpSessionSignalingCreateContext(const SessionSignalingContextParam1* param, std::uint32_t* contextId); +int APS5_VABI sceNpSessionSignalingDeactivate(std::uint32_t contextId, std::uint32_t connectionId); +int APS5_VABI sceNpSessionSignalingDestroyContext(std::uint32_t contextId); +int APS5_VABI sceNpSessionSignalingGetConnectionInfo(std::uint32_t contextId, std::uint32_t connectionId, std::uint32_t code, SessionSignalingConnectionInfo* info); +int APS5_VABI sceNpSessionSignalingActivateUser(std::uint32_t contextId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* connectionId); +int APS5_VABI sceNpSessionSignalingGetConnectionFromPeerAddress2(std::uint32_t contextId, std::uint32_t groupId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* connectionId); +int APS5_VABI sceNpSessionSignalingTerminate(void); +int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(std::uint32_t contextId, std::uint32_t connectionId, std::int32_t* status, std::uint32_t* peerAddress, std::uint16_t* peerPort); +int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(std::uint32_t contextId, SessionSignalingLocalNetInfo* info); +int APS5_VABI sceNpSessionSignalingRequestPrepare(uint32_t contextId, uint32_t* requestId); +int APS5_VABI sceNpSessionSignalingGetMemoryInfo(SessionSignalingMemoryInfo* memoryInfo); +int APS5_VABI sceNpSessionSignalingGetConnectionStatistics(SessionSignalingConnectionStatistics* statistics); +int APS5_VABI sceNpSessionSignalingGetConnectionFromPeerAddress(std::uint32_t contextId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* connectionId); +int APS5_VABI sceNpSessionSignalingGetGroupFromSessionId(std::uint32_t contextId, const char* sessionId, std::uint32_t* groupId); +int APS5_VABI sceNpSessionSignalingGetGroupInfo(std::uint32_t contextId, std::uint32_t groupId, SessionSignalingGroupInfo* groupInfo); +int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress2(std::uint32_t contextId, std::uint32_t groupId, std::uint32_t address, std::uint16_t port, std::uint32_t* connectionId); +int APS5_VABI sceNpSessionSignalingCreateJoinActivate(); +int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress(std::uint32_t contextId, std::uint32_t address, std::uint16_t port, std::uint32_t* connectionId); +int APS5_VABI sceNpSessionSignalingGetCustomProperty(); +int APS5_VABI sceNpSessionSignalingGetGroupFromPeerAddress(); +int APS5_VABI sceNpSessionSignalingInitializeWithPort(); +int APS5_VABI sceNpSessionSignalingJoinActivate(); +int APS5_VABI sceNpSessionSignalingSetCustomProperty(); + +} diff --git a/core/libs/prx/libSceNpSessionSignaling/libSceNpSessionSignaling.hpp b/core/libs/prx/libSceNpSessionSignaling/libSceNpSessionSignaling.hpp new file mode 100644 index 0000000000..8cdf796fb6 --- /dev/null +++ b/core/libs/prx/libSceNpSessionSignaling/libSceNpSessionSignaling.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpSessionSignaling/include/Api.hpp" diff --git a/core/libs/prx/libSceNpSignaling/CMakeLists.txt b/core/libs/prx/libSceNpSignaling/CMakeLists.txt new file mode 100644 index 0000000000..a5ecdbd944 --- /dev/null +++ b/core/libs/prx/libSceNpSignaling/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceNpSignaling SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpSignaling PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpSignaling) + +if(BUILD_TESTING) + add_test_executable(guest_np_signaling_offline_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpSignalingOffline.cpp) + target_include_directories(guest_np_signaling_offline_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_signaling_offline_tests PRIVATE libSceNpSignaling) + configure_windows_unwind(guest_np_signaling_offline_tests) + add_test(NAME guest_np_signaling_offline COMMAND guest_np_signaling_offline_tests) + set_tests_properties(guest_np_signaling_offline PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_signaling_offline PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpSignaling/Export.cpp b/core/libs/prx/libSceNpSignaling/Export.cpp new file mode 100644 index 0000000000..00f307beb5 --- /dev/null +++ b/core/libs/prx/libSceNpSignaling/Export.cpp @@ -0,0 +1,173 @@ +#include "prx/libSceNpSignaling/include/Api.hpp" +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +static constexpr int SCE_NP_ERROR_SIGNED_OUT = static_cast(0x80550006); +static constexpr int SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED = static_cast(0x80552701); +static constexpr int SCE_NP_SIGNALING_ERROR_ALREADY_INITIALIZED = static_cast(0x80552702); +static constexpr int SCE_NP_SIGNALING_ERROR_CTXID_NOT_AVAILABLE = static_cast(0x80552704); +static constexpr int SCE_NP_SIGNALING_ERROR_CTX_NOT_FOUND = static_cast(0x80552705); +static constexpr int SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND = static_cast(0x8055270E); +static constexpr int SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT = static_cast(0x80552715); +static constexpr int SCE_NP_SIGNALING_ERROR_OWN_NP_ID = static_cast(0x80552716); +static constexpr int CONTEXT_OPTION_FLAG = 1; +static constexpr int MAX_CONTEXT_ID = 8; +static constexpr std::uint32_t CONTEXT_FLAG_BIT = 0x2; + +namespace { +struct SignalingNpId { + char handle[16]; + char term; + char dummy[3]; + std::uint8_t opt[8]; + std::uint8_t reserved[8]; +}; +static_assert(sizeof(SignalingNpId) == 36); + +struct Context { + SignalingNpId owner{}; + std::uint32_t flags = CONTEXT_FLAG_BIT; +}; + +std::mutex signalingMutex; +bool initialized = false; +std::unordered_map contexts; +int lastContextId = 0; + +bool IsAsciiAlphanumeric(char value) { + return (value >= '0' && value <= '9') || (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'); +} + +bool ReadNpId(const void* npId, SignalingNpId& copy) { + std::memcpy(©, npId, sizeof(copy)); + if (copy.term != 0) return false; + const std::size_t length = strnlen(copy.handle, sizeof(copy.handle)); + if (length < 3 || !IsAsciiAlphanumeric(copy.handle[0])) return false; + for (std::size_t index = 1; index < length; ++index) { + const char value = copy.handle[index]; + if (!IsAsciiAlphanumeric(value) && value != '_' && value != '-') return false; + } + return true; +} + +int AllocateContextId() { + int candidate = lastContextId == MAX_CONTEXT_ID ? 1 : lastContextId + 1; + for (int tried = 0; tried < MAX_CONTEXT_ID; ++tried) { + if (!contexts.contains(candidate)) { + lastContextId = candidate; + return candidate; + } + candidate = candidate == MAX_CONTEXT_ID ? 1 : candidate + 1; + } + return -1; +} + +Context* FindContext(int ctxId) { + const auto context = contexts.find(ctxId); + return context == contexts.end() ? nullptr : &context->second; +} +} + +extern "C" { + +int APS5_VABI sceNpSignalingActivateConnection(int ctxId, const void* peerNpId, int* outConnId) { + std::lock_guard lock(signalingMutex); + if (!initialized) return SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED; + if (!peerNpId || !outConnId) return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT; + SignalingNpId peer{}; + if (!ReadNpId(peerNpId, peer)) return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT; + const Context* context = FindContext(ctxId); + if (!context) return SCE_NP_SIGNALING_ERROR_CTX_NOT_FOUND; + if (std::memcmp(&context->owner, &peer, sizeof(peer)) == 0) return SCE_NP_SIGNALING_ERROR_OWN_NP_ID; + return SCE_NP_ERROR_SIGNED_OUT; +} + +int APS5_VABI sceNpSignalingCreateContext(const void* npId, void* callback, void* callbackArg, int* outContextId) { + (void)callback; + (void)callbackArg; + std::lock_guard lock(signalingMutex); + if (!initialized) return SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED; + if (!npId || !outContextId) return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT; + SignalingNpId owner{}; + if (!ReadNpId(npId, owner)) return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT; + const int ctxId = AllocateContextId(); + if (ctxId < 0) return SCE_NP_SIGNALING_ERROR_CTXID_NOT_AVAILABLE; + contexts[ctxId] = Context{owner}; + *outContextId = ctxId; + return 0; +} + +int APS5_VABI sceNpSignalingDeleteContext(int ctxId) { + std::lock_guard lock(signalingMutex); + if (!initialized) return SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED; + contexts.erase(ctxId); + return 0; +} + +int APS5_VABI sceNpSignalingGetConnectionInfo(int ctxId, int connId, int infoCode, void* outInfo) { + (void)connId; + (void)infoCode; + std::lock_guard lock(signalingMutex); + if (!initialized) return SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED; + if (!outInfo) return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(ctxId)) return SCE_NP_SIGNALING_ERROR_CTX_NOT_FOUND; + return SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND; +} + +int APS5_VABI sceNpSignalingInitialize(std::int64_t memorySize, int threadPriority, int cpuAffinityMask, std::int64_t threadStackSize) { + (void)memorySize; + (void)threadPriority; + (void)cpuAffinityMask; + (void)threadStackSize; + std::lock_guard lock(signalingMutex); + if (initialized) return SCE_NP_SIGNALING_ERROR_ALREADY_INITIALIZED; + initialized = true; + return 0; +} + +int APS5_VABI sceNpSignalingSetContextOption(int ctxId, int optionId, int optionValue) { + std::lock_guard lock(signalingMutex); + if (!initialized) return SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED; + Context* context = FindContext(ctxId); + if (!context) return SCE_NP_SIGNALING_ERROR_CTX_NOT_FOUND; + if (optionId != CONTEXT_OPTION_FLAG) return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT; + if (optionValue == 0) { + context->flags &= ~CONTEXT_FLAG_BIT; + } else if (optionValue == 1) { + context->flags |= CONTEXT_FLAG_BIT; + } else { + return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT; + } + return 0; +} + +int APS5_VABI sceNpSignalingTerminate() { + std::lock_guard lock(signalingMutex); + if (!initialized) return SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED; + initialized = false; + contexts.clear(); + lastContextId = 0; + return 0; +} + +int APS5_VABI sceNpSignalingTerminateConnection(int ctxId, int connId) { + (void)connId; + std::lock_guard lock(signalingMutex); + if (!initialized) return SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED; + if (!FindContext(ctxId)) return SCE_NP_SIGNALING_ERROR_CTX_NOT_FOUND; + return SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND; +} + +int APS5_VABI sceNpSignalingDeactivateConnection(int ctxId, int connId) { + (void)connId; + std::lock_guard lock(signalingMutex); + if (!initialized) return SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED; + if (!FindContext(ctxId)) return SCE_NP_SIGNALING_ERROR_CTX_NOT_FOUND; + return SCE_NP_SIGNALING_ERROR_CONN_NOT_FOUND; +} + +} diff --git a/core/libs/prx/libSceNpSignaling/include/Api.hpp b/core/libs/prx/libSceNpSignaling/include/Api.hpp new file mode 100644 index 0000000000..d11753e6d8 --- /dev/null +++ b/core/libs/prx/libSceNpSignaling/include/Api.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpSignalingActivateConnection(int ctxId, const void* peerNpId, int* outConnId); +int APS5_VABI sceNpSignalingCreateContext(const void* npId, void* callback, void* callbackArg, int* outContextId); +int APS5_VABI sceNpSignalingDeleteContext(int ctxId); +int APS5_VABI sceNpSignalingGetConnectionInfo(int ctxId, int connId, int infoCode, void* outInfo); +int APS5_VABI sceNpSignalingInitialize(std::int64_t memorySize, int threadPriority, int cpuAffinityMask, std::int64_t threadStackSize); +int APS5_VABI sceNpSignalingSetContextOption(int ctxId, int optionId, int optionValue); +int APS5_VABI sceNpSignalingTerminate(); +int APS5_VABI sceNpSignalingTerminateConnection(int ctxId, int connId); +int APS5_VABI sceNpSignalingDeactivateConnection(int ctxId, int connId); + +} diff --git a/core/libs/prx/libSceNpSignaling/libSceNpSignaling.hpp b/core/libs/prx/libSceNpSignaling/libSceNpSignaling.hpp new file mode 100644 index 0000000000..4ab0459137 --- /dev/null +++ b/core/libs/prx/libSceNpSignaling/libSceNpSignaling.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpSignaling/include/Api.hpp" diff --git a/core/libs/prx/libSceNpSns/Export.cpp b/core/libs/prx/libSceNpSns/Export.cpp index c6647c709c..8fe590490d 100644 --- a/core/libs/prx/libSceNpSns/Export.cpp +++ b/core/libs/prx/libSceNpSns/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpSns/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceNpSns/include/Api.hpp b/core/libs/prx/libSceNpSns/include/Api.hpp new file mode 100644 index 0000000000..201afcfef4 --- /dev/null +++ b/core/libs/prx/libSceNpSns/include/Api.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpSnsFacebookAbortRequest(); +int APS5_VABI sceNpSnsIntAbortRequest(); +int APS5_VABI sceNpSnsIntCreateRequest(); +int APS5_VABI sceNpSnsIntDeleteRequest(); +int APS5_VABI sceNpSnsIntFbGetGameAccessToken(); +int APS5_VABI sceNpSnsIntFbGetGameAccessTokenAllowed(); +int APS5_VABI sceNpSnsIntFbGetSystemAccessToken(); +int APS5_VABI sceNpSnsIntFbGetTitleInfo(); +int APS5_VABI sceNpSnsIntLinkedStatus(); +int APS5_VABI sceNpSnsIntTwGetSystemAccessToken(); +int APS5_VABI sceNpSnsIntUnlink(); +int APS5_VABI sceNpSnsIntYtGetAccessToken(); +int APS5_VABI sceNpSnsIntYtRefreshMasterToken(); +int APS5_VABI sceNpSnsTwitchAbortRequest(); +int APS5_VABI sceNpSnsTwitchCreateRequest(); +int APS5_VABI sceNpSnsTwitchDeleteRequest(); +int APS5_VABI sceNpSnsTwitchGetAccessToken(); +int APS5_VABI sceNpSnsYouTubeAbortRequest(); +int APS5_VABI sceNpSnsYouTubeCreateRequest(); +int APS5_VABI sceNpSnsYouTubeDeleteRequest(); +int APS5_VABI sceNpSnsYouTubeGetAccessToken(); + +} diff --git a/core/libs/prx/libSceNpSns/libSceNpSns.hpp b/core/libs/prx/libSceNpSns/libSceNpSns.hpp new file mode 100644 index 0000000000..24cb3940ad --- /dev/null +++ b/core/libs/prx/libSceNpSns/libSceNpSns.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpSns/include/Api.hpp" diff --git a/core/libs/prx/libSceNpSnsFacebookDialog/CMakeLists.txt b/core/libs/prx/libSceNpSnsFacebookDialog/CMakeLists.txt new file mode 100644 index 0000000000..5445d0dddd --- /dev/null +++ b/core/libs/prx/libSceNpSnsFacebookDialog/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libSceNpSnsFacebookDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpSnsFacebookDialog PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpSnsFacebookDialog) + +if(BUILD_TESTING) + add_test_executable(guest_np_sns_facebook_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpSnsFacebookDialog.cpp) + target_include_directories(guest_np_sns_facebook_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_sns_facebook_dialog_tests PRIVATE libSceNpSnsFacebookDialog) + add_test(NAME guest_np_sns_facebook_dialog COMMAND guest_np_sns_facebook_dialog_tests) + if(WIN32) + set_tests_properties(guest_np_sns_facebook_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpSnsFacebookDialog/Export.cpp b/core/libs/prx/libSceNpSnsFacebookDialog/Export.cpp index 58bb27df19..ebad76b2f2 100644 --- a/core/libs/prx/libSceNpSnsFacebookDialog/Export.cpp +++ b/core/libs/prx/libSceNpSnsFacebookDialog/Export.cpp @@ -1,17 +1,71 @@ -#include +#include "prx/libSceNpSnsFacebookDialog/include/Api.hpp" +#include #include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int COMMON_DIALOG_STATUS_NONE = 0; +constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; +constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; +constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; + +std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; + +} + extern "C" { -int APS5_VABI sceNpSnsFacebookDialogClose(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpSnsFacebookDialogInitialize(void) { + int expected = COMMON_DIALOG_STATUS_NONE; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; return 0; } +int APS5_VABI sceNpSnsFacebookDialogOpen(const void* param) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; +} + +int APS5_VABI sceNpSnsFacebookDialogUpdateStatus(void) { + return g_status.load(); +} + int APS5_VABI sceNpSnsFacebookDialogGetStatus(void) { - NotImplemented_nid_no_patch(__func__); + return g_status.load(); +} + +int APS5_VABI sceNpSnsFacebookDialogGetResult(void* result) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; + std::memcpy(result, &canceled, sizeof(canceled)); + return 0; +} + +int APS5_VABI sceNpSnsFacebookDialogClose(void) { + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; +} + +int APS5_VABI sceNpSnsFacebookDialogTerminate(void) { + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; return 0; } diff --git a/core/libs/prx/libSceNpSnsFacebookDialog/include/Api.hpp b/core/libs/prx/libSceNpSnsFacebookDialog/include/Api.hpp new file mode 100644 index 0000000000..e201d488c8 --- /dev/null +++ b/core/libs/prx/libSceNpSnsFacebookDialog/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpSnsFacebookDialogInitialize(void); +int APS5_VABI sceNpSnsFacebookDialogOpen(const void* param); +int APS5_VABI sceNpSnsFacebookDialogUpdateStatus(void); +int APS5_VABI sceNpSnsFacebookDialogGetStatus(void); +int APS5_VABI sceNpSnsFacebookDialogGetResult(void* result); +int APS5_VABI sceNpSnsFacebookDialogClose(void); +int APS5_VABI sceNpSnsFacebookDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceNpSnsFacebookDialog/libSceNpSnsFacebookDialog.hpp b/core/libs/prx/libSceNpSnsFacebookDialog/libSceNpSnsFacebookDialog.hpp new file mode 100644 index 0000000000..0401a2b8be --- /dev/null +++ b/core/libs/prx/libSceNpSnsFacebookDialog/libSceNpSnsFacebookDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpSnsFacebookDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp b/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp index e1b07d4bfc..8aacd8784b 100644 --- a/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp +++ b/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpSnsYouTubeDialog/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceNpSnsYouTubeDialog/include/Api.hpp b/core/libs/prx/libSceNpSnsYouTubeDialog/include/Api.hpp new file mode 100644 index 0000000000..f2283cb2cf --- /dev/null +++ b/core/libs/prx/libSceNpSnsYouTubeDialog/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpSnsYouTubeDialogClose(); +int APS5_VABI sceNpSnsYouTubeDialogGetResult(); +int APS5_VABI sceNpSnsYouTubeDialogGetStatus(); +int APS5_VABI sceNpSnsYouTubeDialogInitialize(); +int APS5_VABI sceNpSnsYouTubeDialogOpen(); +int APS5_VABI sceNpSnsYouTubeDialogTerminate(); +int APS5_VABI sceNpSnsYouTubeDialogUpdateStatus(); + +} diff --git a/core/libs/prx/libSceNpSnsYouTubeDialog/libSceNpSnsYouTubeDialog.hpp b/core/libs/prx/libSceNpSnsYouTubeDialog/libSceNpSnsYouTubeDialog.hpp new file mode 100644 index 0000000000..138afadcec --- /dev/null +++ b/core/libs/prx/libSceNpSnsYouTubeDialog/libSceNpSnsYouTubeDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpSnsYouTubeDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceNpSpeechToText/libSceNpSpeechToText.hpp b/core/libs/prx/libSceNpSpeechToText/libSceNpSpeechToText.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceNpSpeechToText/libSceNpSpeechToText.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceNpTcs/Export.cpp b/core/libs/prx/libSceNpTcs/Export.cpp index 483b8fa844..f496f8ffbf 100644 --- a/core/libs/prx/libSceNpTcs/Export.cpp +++ b/core/libs/prx/libSceNpTcs/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpTcs/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceNpTcs/include/Api.hpp b/core/libs/prx/libSceNpTcs/include/Api.hpp new file mode 100644 index 0000000000..656b047bae --- /dev/null +++ b/core/libs/prx/libSceNpTcs/include/Api.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpTcsIntDownloadData(); +int APS5_VABI sceNpTcsIntUploadDataToNewSlotAbort(); +int APS5_VABI sceNpTcsIntUploadDataToNewSlotBegin(); + +} diff --git a/core/libs/prx/libSceNpTcs/libSceNpTcs.hpp b/core/libs/prx/libSceNpTcs/libSceNpTcs.hpp new file mode 100644 index 0000000000..cf84369ec2 --- /dev/null +++ b/core/libs/prx/libSceNpTcs/libSceNpTcs.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpTcs/include/Api.hpp" diff --git a/core/libs/prx/libSceNpTextToSpeech/libSceNpTextToSpeech.hpp b/core/libs/prx/libSceNpTextToSpeech/libSceNpTextToSpeech.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceNpTextToSpeech/libSceNpTextToSpeech.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceNpTrophy/CMakeLists.txt b/core/libs/prx/libSceNpTrophy/CMakeLists.txt new file mode 100644 index 0000000000..7301fa20ab --- /dev/null +++ b/core/libs/prx/libSceNpTrophy/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceNpTrophy SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpTrophy PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpTrophy) + +if(BUILD_TESTING) + add_test_executable(guest_np_trophy_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTrophy.cpp) + target_include_directories(guest_np_trophy_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_trophy_tests PRIVATE libSceNpTrophy) + configure_windows_unwind(guest_np_trophy_tests) + add_test(NAME guest_np_trophy COMMAND guest_np_trophy_tests) + set_tests_properties(guest_np_trophy PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_trophy PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpTrophy/Export.cpp b/core/libs/prx/libSceNpTrophy/Export.cpp new file mode 100644 index 0000000000..80fa87ee3f --- /dev/null +++ b/core/libs/prx/libSceNpTrophy/Export.cpp @@ -0,0 +1,347 @@ +#include "prx/libSceNpTrophy/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +namespace { + +constexpr int NP_TROPHY_ERROR_INVALID_ARGUMENT = static_cast(0x80551604u); +constexpr int NP_TROPHY_ERROR_INSUFFICIENT_BUFFER = static_cast(0x80551605u); +constexpr int NP_TROPHY_ERROR_INVALID_HANDLE = static_cast(0x80551608u); +constexpr int NP_TROPHY_ERROR_INVALID_CONTEXT = static_cast(0x80551609u); +constexpr int NP_TROPHY_ERROR_INVALID_TROPHY_ID = static_cast(0x8055160au); +constexpr int NP_TROPHY_ERROR_INVALID_GROUP_ID = static_cast(0x8055160bu); +constexpr int NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED = static_cast(0x8055160cu); +constexpr int NP_TROPHY_ERROR_NOT_REGISTERED = static_cast(0x8055160fu); +constexpr int NP_TROPHY_ERROR_ALREADY_REGISTERED = static_cast(0x80551610u); +constexpr int NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = static_cast(0x80551613u); +constexpr int NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND = static_cast(0x80551614u); +constexpr int NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT = static_cast(0x80551616u); +constexpr int NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED = static_cast(0x8055161au); +constexpr int NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED = static_cast(0x80551626u); +constexpr int NP_TROPHY_INVALID_ID = -1; + +constexpr int NP_TROPHY_GRADE_UNKNOWN = 0; +constexpr int NP_TROPHY_BASE_GAME_GROUP_ID = -1; +constexpr std::uint32_t TrpMagic = 0xdca24d00u; +constexpr std::size_t TrpHeaderBytes = 0x60; +constexpr std::size_t TrpEntryBytes = 0x40; + +static_assert(sizeof(GameDetails) == 0x4a0); + +static_assert(sizeof(GameData) == 0x20); + +static_assert(sizeof(GroupDetails) == 0x4a0); + +static_assert(sizeof(GroupData) == 0x28); + +static_assert(sizeof(TrophyDetails) == 0x498); + +static_assert(sizeof(TrophyData) == 0x18); + +struct TrpEntry { + std::uint64_t offset; + std::uint64_t size; +}; + +struct Context { + int userId; + std::uint32_t serviceLabel; + bool registered = false; + std::filesystem::path package; + std::map entries; + int trophies = 0; + std::filesystem::path state; + std::set unlocked; +}; + +std::mutex g_mutex; +std::map g_contexts; +std::set g_handles; +int g_nextContext = 1; +int g_nextHandle = 1; + +std::uint32_t BigEndian32(const std::uint8_t* bytes) { + return (std::uint32_t{bytes[0]} << 24) | (std::uint32_t{bytes[1]} << 16) | (std::uint32_t{bytes[2]} << 8) | bytes[3]; +} + +std::uint64_t BigEndian64(const std::uint8_t* bytes) { + return (std::uint64_t{BigEndian32(bytes)} << 32) | BigEndian32(bytes + 4); +} + +std::optional> ReadPackage(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + if (!file) return std::nullopt; + std::array header{}; + if (!file.read(reinterpret_cast(header.data()), header.size()) || BigEndian32(header.data()) != TrpMagic) return std::map{}; + const auto count = BigEndian32(header.data() + 0x10); + if (BigEndian32(header.data() + 0x14) != TrpEntryBytes) return std::map{}; + std::map entries; + for (std::uint32_t i = 0; i < count; ++i) { + std::array entry{}; + if (!file.read(reinterpret_cast(entry.data()), entry.size())) return std::map{}; + const std::string name(reinterpret_cast(entry.data()), strnlen(reinterpret_cast(entry.data()), 32)); + entries[name] = {BigEndian64(entry.data() + 32), BigEndian64(entry.data() + 40)}; + } + return entries; +} + +void LoadState(Context& context) { + std::ifstream file(context.state); + int id = 0; + while (file >> id) { + if (id >= 0 && id < context.trophies) context.unlocked.insert(id); + } +} + +void SaveState(const Context& context) { + 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'; + } + std::filesystem::rename(temporary, context.state); +} + +int Lookup(int contextId, int handle, Context*& context) { + if (contextId == NP_TROPHY_INVALID_ID) return NP_TROPHY_ERROR_INVALID_CONTEXT; + if (handle == NP_TROPHY_INVALID_ID) return NP_TROPHY_ERROR_INVALID_HANDLE; + const auto found = g_contexts.find(contextId); + if (found == g_contexts.end()) return NP_TROPHY_ERROR_INVALID_CONTEXT; + if (!g_handles.contains(handle)) return NP_TROPHY_ERROR_INVALID_HANDLE; + context = &found->second; + return 0; +} + +int Groups(const Context& context) { + int groups = 0; + for (int id = 0; id < NP_TROPHY_NUM_MAX; ++id) { + char icon[16]; + std::snprintf(icon, sizeof(icon), "GR%03d.PNG", id); + if (!context.entries.contains(icon)) break; + groups = id + 1; + } + return groups; +} + +std::uint32_t Progress(const Context& context) { + return context.trophies == 0 ? 0u : static_cast(context.unlocked.size() * 100 / static_cast(context.trophies)); +} + +int CopyIcon(const Context& context, const std::string& name, void* buffer, std::uint64_t* size) { + const auto entry = context.entries.find(name); + if (entry == context.entries.end()) return NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND; + if (buffer == nullptr) { + *size = entry->second.size; + return 0; + } + if (*size < entry->second.size) return NP_TROPHY_ERROR_INSUFFICIENT_BUFFER; + std::ifstream file(context.package, std::ios::binary); + file.seekg(static_cast(entry->second.offset)); + if (!file.read(static_cast(buffer), static_cast(entry->second.size))) return NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT; + *size = entry->second.size; + return 0; +} + +} + +extern "C" { + +int APS5_VABI sceNpTrophyCreateContext(int* context, int userId, std::uint32_t serviceLabel, std::uint64_t options) { + if (!context || options != 0) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + for (const auto& [id, existing] : g_contexts) { + if (existing.userId == userId && existing.serviceLabel == serviceLabel) return NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS; + } + const int id = g_nextContext++; + g_contexts[id] = Context{userId, serviceLabel}; + *context = id; + return 0; +} + +int APS5_VABI sceNpTrophyDestroyContext(int context) { + std::lock_guard lock(g_mutex); + return g_contexts.erase(context) == 1 ? 0 : NP_TROPHY_ERROR_INVALID_CONTEXT; +} + +int APS5_VABI sceNpTrophyCreateHandle(int* handle) { + if (!handle) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + const int id = g_nextHandle++; + g_handles.insert(id); + *handle = id; + return 0; +} + +int APS5_VABI sceNpTrophyDestroyHandle(int handle) { + std::lock_guard lock(g_mutex); + return g_handles.erase(handle) == 1 ? 0 : NP_TROPHY_ERROR_INVALID_HANDLE; +} + +int APS5_VABI sceNpTrophyRegisterContext(int contextId, int handle, std::uint64_t options) { + if (options != 0) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + Context* context = nullptr; + if (const int error = Lookup(contextId, handle, context)) return error; + if (context->registered) return NP_TROPHY_ERROR_ALREADY_REGISTERED; + char name[32]; + std::snprintf(name, sizeof(name), "trophy%02u.trp", context->serviceLabel); + context->package = ResolvePath_nid_no_patch((std::string("/app0/sce_sys/trophy/") + name).c_str()); + const auto entries = ReadPackage(context->package); + if (!entries) return NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED; + if (entries->empty()) return NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT; + context->entries = *entries; + for (int id = 0; id < NP_TROPHY_NUM_MAX; ++id) { + char icon[16]; + std::snprintf(icon, sizeof(icon), "TROP%03d.PNG", id); + if (!context->entries.contains(icon)) break; + context->trophies = id + 1; + } + std::snprintf(name, sizeof(name), "trophy%02u.%d", context->serviceLabel, context->userId); + context->state = std::filesystem::path("_trophy") / name; + LoadState(*context); + context->registered = true; + return 0; +} + +int APS5_VABI sceNpTrophyGetTrophyUnlockState(int contextId, int handle, TrophyFlagArray* flags, std::uint32_t* count) { + if (!flags || !count) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + Context* context = nullptr; + if (const int error = Lookup(contextId, handle, context)) return error; + if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED; + std::memset(flags, 0, sizeof(*flags)); + for (const auto id : context->unlocked) flags->bits[id >> 5] |= 1u << (id & 31); + *count = static_cast(context->trophies); + return 0; +} + +int APS5_VABI sceNpTrophyUnlockTrophy(int contextId, int handle, int trophyId, int* platinumId) { + if (!platinumId) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + Context* context = nullptr; + if (const int error = Lookup(contextId, handle, context)) return error; + 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); + *platinumId = NP_TROPHY_INVALID_ID; + return 0; +} + +int APS5_VABI sceNpTrophyGetGameIcon(int contextId, int handle, void* buffer, std::uint64_t* size) { + if (!size) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + Context* context = nullptr; + if (const int error = Lookup(contextId, handle, context)) return error; + if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED; + return CopyIcon(*context, "ICON0.PNG", buffer, size); +} + +int APS5_VABI sceNpTrophyGetGroupIcon(int contextId, int handle, int groupId, void* buffer, std::uint64_t* size) { + if (!size) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + Context* context = nullptr; + if (const int error = Lookup(contextId, handle, context)) return error; + if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED; + if (groupId < 0) return NP_TROPHY_ERROR_INVALID_GROUP_ID; + char icon[16]; + std::snprintf(icon, sizeof(icon), "GR%03d.PNG", groupId); + return CopyIcon(*context, icon, buffer, size); +} + +int APS5_VABI sceNpTrophyGetTrophyIcon(int contextId, int handle, int trophyId, void* buffer, std::uint64_t* size) { + if (!size) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + Context* context = nullptr; + if (const int error = Lookup(contextId, handle, context)) return error; + 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_NOT_UNLOCKED; + char icon[16]; + std::snprintf(icon, sizeof(icon), "TROP%03d.PNG", trophyId); + return CopyIcon(*context, icon, buffer, size); +} + +int APS5_VABI sceNpTrophyGetGameInfo(int contextId, int handle, GameDetails* details, GameData* data) { + if (!details && !data) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + if ((details && details->size != sizeof(GameDetails)) || (data && data->size != sizeof(GameData))) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + Context* context = nullptr; + if (const int error = Lookup(contextId, handle, context)) return error; + if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED; + if (details) { + std::memset(reinterpret_cast(details) + sizeof(details->size), 0, sizeof(GameDetails) - sizeof(details->size)); + details->numGroups = static_cast(Groups(*context)); + details->numTrophies = static_cast(context->trophies); + } + if (data) { + std::memset(reinterpret_cast(data) + sizeof(data->size), 0, sizeof(GameData) - sizeof(data->size)); + data->unlockedTrophies = static_cast(context->unlocked.size()); + data->progressPercentage = Progress(*context); + } + return 0; +} + +int APS5_VABI sceNpTrophyGetGroupInfo(int contextId, int handle, int groupId, GroupDetails* details, GroupData* data) { + if (!details && !data) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + if ((details && details->size != sizeof(GroupDetails)) || (data && data->size != sizeof(GroupData))) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + Context* context = nullptr; + if (const int error = Lookup(contextId, handle, context)) return error; + if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED; + if (groupId != NP_TROPHY_BASE_GAME_GROUP_ID) { + if (groupId < 0 || groupId >= Groups(*context)) return NP_TROPHY_ERROR_INVALID_GROUP_ID; + NotImplemented_nid_no_patch(__func__); + } + if (details) { + std::memset(reinterpret_cast(details) + sizeof(details->size), 0, sizeof(GroupDetails) - sizeof(details->size)); + details->groupId = groupId; + details->numTrophies = static_cast(context->trophies); + } + if (data) { + std::memset(reinterpret_cast(data) + sizeof(data->size), 0, sizeof(GroupData) - sizeof(data->size)); + data->groupId = groupId; + data->unlockedTrophies = static_cast(context->unlocked.size()); + data->progressPercentage = Progress(*context); + } + return 0; +} + +int APS5_VABI sceNpTrophyGetTrophyInfo(int contextId, int handle, int trophyId, TrophyDetails* details, TrophyData* data) { + if (!details && !data) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + if ((details && details->size != sizeof(TrophyDetails)) || (data && data->size != sizeof(TrophyData))) return NP_TROPHY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_mutex); + Context* context = nullptr; + if (const int error = Lookup(contextId, handle, context)) return error; + if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED; + if (trophyId < 0 || trophyId >= context->trophies) return NP_TROPHY_ERROR_INVALID_TROPHY_ID; + if (Groups(*context) != 0) NotImplemented_nid_no_patch(__func__); + if (details) { + std::memset(reinterpret_cast(details) + sizeof(details->size), 0, sizeof(TrophyDetails) - sizeof(details->size)); + details->trophyId = trophyId; + details->trophyGrade = NP_TROPHY_GRADE_UNKNOWN; + details->groupId = NP_TROPHY_BASE_GAME_GROUP_ID; + } + if (data) { + std::memset(reinterpret_cast(data) + sizeof(data->size), 0, sizeof(TrophyData) - sizeof(data->size)); + data->trophyId = trophyId; + data->unlocked = context->unlocked.contains(trophyId); + } + return 0; +} + +} diff --git a/core/libs/prx/libSceNpTrophy/include/Api.hpp b/core/libs/prx/libSceNpTrophy/include/Api.hpp new file mode 100644 index 0000000000..6360184288 --- /dev/null +++ b/core/libs/prx/libSceNpTrophy/include/Api.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include "SceTypes.hpp" + +inline constexpr int NP_TROPHY_NUM_MAX = 128; + +struct GameData { + std::uint64_t size; + std::uint32_t unlockedTrophies; + std::uint32_t unlockedPlatinum; + std::uint32_t unlockedGold; + std::uint32_t unlockedSilver; + std::uint32_t unlockedBronze; + std::uint32_t progressPercentage; +}; + +struct GameDetails { + std::uint64_t size; + std::uint32_t numGroups; + std::uint32_t numTrophies; + std::uint32_t numPlatinum; + std::uint32_t numGold; + std::uint32_t numSilver; + std::uint32_t numBronze; + char title[128]; + char description[1024]; +}; + +struct GroupData { + std::uint64_t size; + std::int32_t groupId; + std::uint32_t unlockedTrophies; + std::uint32_t unlockedPlatinum; + std::uint32_t unlockedGold; + std::uint32_t unlockedSilver; + std::uint32_t unlockedBronze; + std::uint32_t progressPercentage; + std::uint8_t reserved[4]; +}; + +struct GroupDetails { + std::uint64_t size; + std::int32_t groupId; + std::uint32_t numTrophies; + std::uint32_t numPlatinum; + std::uint32_t numGold; + std::uint32_t numSilver; + std::uint32_t numBronze; + char title[128]; + char description[1024]; +}; + +struct TrophyData { + std::uint64_t size; + std::int32_t trophyId; + bool unlocked; + std::uint8_t reserved[3]; + std::uint64_t timestamp; +}; + +struct TrophyDetails { + std::uint64_t size; + std::int32_t trophyId; + std::int32_t trophyGrade; + std::int32_t groupId; + bool hidden; + std::uint8_t reserved[3]; + char name[128]; + char description[1024]; +}; + +struct TrophyFlagArray { + std::uint32_t bits[NP_TROPHY_NUM_MAX / 32]; +}; + +extern "C" { + +int APS5_VABI sceNpTrophyCreateContext(int* context, int userId, std::uint32_t serviceLabel, std::uint64_t options); +int APS5_VABI sceNpTrophyDestroyContext(int context); +int APS5_VABI sceNpTrophyCreateHandle(int* handle); +int APS5_VABI sceNpTrophyDestroyHandle(int handle); +int APS5_VABI sceNpTrophyRegisterContext(int contextId, int handle, std::uint64_t options); +int APS5_VABI sceNpTrophyGetTrophyUnlockState(int contextId, int handle, TrophyFlagArray* flags, std::uint32_t* count); +int APS5_VABI sceNpTrophyUnlockTrophy(int contextId, int handle, int trophyId, int* platinumId); +int APS5_VABI sceNpTrophyGetGameIcon(int contextId, int handle, void* buffer, std::uint64_t* size); +int APS5_VABI sceNpTrophyGetGroupIcon(int contextId, int handle, int groupId, void* buffer, std::uint64_t* size); +int APS5_VABI sceNpTrophyGetTrophyIcon(int contextId, int handle, int trophyId, void* buffer, std::uint64_t* size); +int APS5_VABI sceNpTrophyGetGameInfo(int contextId, int handle, GameDetails* details, GameData* data); +int APS5_VABI sceNpTrophyGetGroupInfo(int contextId, int handle, int groupId, GroupDetails* details, GroupData* data); +int APS5_VABI sceNpTrophyGetTrophyInfo(int contextId, int handle, int trophyId, TrophyDetails* details, TrophyData* data); + +} diff --git a/core/libs/prx/libSceNpTrophy/libSceNpTrophy.hpp b/core/libs/prx/libSceNpTrophy/libSceNpTrophy.hpp new file mode 100644 index 0000000000..f808efbd11 --- /dev/null +++ b/core/libs/prx/libSceNpTrophy/libSceNpTrophy.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpTrophy/include/Api.hpp" diff --git a/core/libs/prx/libSceNpTrophy2/Export.cpp b/core/libs/prx/libSceNpTrophy2/Export.cpp index 9befeb73c9..8a46b8a803 100644 --- a/core/libs/prx/libSceNpTrophy2/Export.cpp +++ b/core/libs/prx/libSceNpTrophy2/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpTrophy2/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceNpTrophy2/include/Api.hpp b/core/libs/prx/libSceNpTrophy2/include/Api.hpp new file mode 100644 index 0000000000..052446ff82 --- /dev/null +++ b/core/libs/prx/libSceNpTrophy2/include/Api.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpTrophy2SystemAbortHandle(void); +int APS5_VABI sceNpTrophy2SystemBuildGroupIconUri(void); +int APS5_VABI sceNpTrophy2SystemBuildRewardIconUri(void); +int APS5_VABI sceNpTrophy2SystemBuildTrophyIconUri(void); +int APS5_VABI sceNpTrophy2SystemCheckCallback(void); +int APS5_VABI sceNpTrophy2SystemCheckNetSyncTitles(void); +int APS5_VABI sceNpTrophy2SystemCreateContext(void); +int APS5_VABI sceNpTrophy2SystemCreateHandle(void); +int APS5_VABI sceNpTrophy2SystemDebugLockTrophy(void); +int APS5_VABI sceNpTrophy2SystemDebugUnlockTrophy(void); +int APS5_VABI sceNpTrophy2SystemDestroyContext(void); +int APS5_VABI sceNpTrophy2SystemGetTrophyDetails(void); +int APS5_VABI sceNpTrophy2SystemGetTrophyGroupConf(void); +int APS5_VABI sceNpTrophy2SystemGetTrophySetArray(void); +int APS5_VABI sceNpTrophy2SystemGetTrophyTitleConf(void); +int APS5_VABI sceNpTrophy2SystemGetTrophyTitleDetails(void); +int APS5_VABI sceNpTrophy2SystemGetTrpIconByUri(void); +int APS5_VABI sceNpTrophy2SystemIsServerAvailable(void); +int APS5_VABI sceNpTrophy2SystemRegisterTitleSyncedCallback(void); +int APS5_VABI sceNpTrophy2SystemRegisterTitleUpdateCallback(void); +int APS5_VABI sceNpTrophy2SystemRemoveAll(void); +int APS5_VABI sceNpTrophy2SystemRemoveTitleData(void); +int APS5_VABI sceNpTrophy2SystemRemoveUserData(void); +int APS5_VABI sceNpTrophy2CreateContext(int* context, int userId, uint32_t serviceLabel, uint64_t options); +int APS5_VABI sceNpTrophy2DestroyContext(int context); +int APS5_VABI sceNpTrophy2RegisterContext(int context, int handle, uint64_t options); +int APS5_VABI sceNpTrophy2UnregisterUnlockCallback(); +int APS5_VABI sceNpTrophy2GetRewardIcon(int context, int handle, int rewardId, void* buffer, size_t* size); +int APS5_VABI sceNpTrophy2ShowTrophyList(int context); +int APS5_VABI sceNpTrophy2GetGameInfo(int context, int handle, NpTrophy2GameDetails* details, NpTrophy2GameData* data); +int APS5_VABI sceNpTrophy2GetGameIcon(int context, int handle, void* buffer, size_t* size); +int APS5_VABI sceNpTrophy2RegisterUnlockCallback(void* callback, void* userdata); +int APS5_VABI sceNpTrophy2GetGroupInfo(int context, int handle, int groupId, NpTrophy2GroupDetails* details, NpTrophy2GroupData* data); +int APS5_VABI sceNpTrophy2GetGroupInfoArray(int context, int handle, uint32_t offset, uint32_t limit, NpTrophy2GroupDetails* detailsArray, NpTrophy2GroupData* dataArray, uint32_t* count); +int APS5_VABI sceNpTrophy2GetGroupIcon(int context, int handle, int groupId, void* buffer, size_t* size); +int APS5_VABI sceNpTrophy2CreateHandle(int* handle); +int APS5_VABI sceNpTrophy2DestroyHandle(int handle); +int APS5_VABI sceNpTrophy2AbortHandle(int handle); +int APS5_VABI sceNpTrophy2GetTrophyInfo(int context, int handle, int trophyId, NpTrophy2Details* details, NpTrophy2Data* data); +int APS5_VABI sceNpTrophy2GetTrophyInfoArray(int context, int handle, uint32_t offset, uint32_t limit, NpTrophy2Details* detailsArray, NpTrophy2Data* dataArray, uint32_t* count); +int APS5_VABI sceNpTrophy2GetTrophyIcon(int context, int handle, int trophyId, void* buffer, size_t* size); + +} diff --git a/core/libs/prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp b/core/libs/prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp index 6a853b4c28..58ec7f5e33 100644 --- a/core/libs/prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp +++ b/core/libs/prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp @@ -4,6 +4,7 @@ #include static constexpr int SCE_NP_TROPHY2_OK = 0; +static constexpr int SCE_NP_TROPHY2_ERROR_INVALID_TROPHY_ID = -2141898486; static constexpr int SCE_NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND = -2141898479; static constexpr int NP_TROPHY2_CONTEXT_DEFAULT = 1; diff --git a/core/libs/prx/libSceNpTrophy2/include/TrophyPackage.hpp b/core/libs/prx/libSceNpTrophy2/include/TrophyPackage.hpp index 4be4238929..f493e67ea1 100644 --- a/core/libs/prx/libSceNpTrophy2/include/TrophyPackage.hpp +++ b/core/libs/prx/libSceNpTrophy2/include/TrophyPackage.hpp @@ -89,17 +89,27 @@ struct TrophyPackage { TrophyPackage ParseTrophyPackage(std::string_view ucp, std::string_view language); -enum class StatType { UInt32, UInt64 }; +enum class StatType { UInt32, UInt64, Int32 }; + +enum class StatAggregation { Latest, Max }; struct StatDefinition { StatType type = StatType::UInt64; + StatAggregation aggregation = StatAggregation::Latest; std::uint64_t minimum = 0; std::uint64_t maximum = UINT64_MAX; + std::uint64_t initial = 0; +}; + +struct StatFilter { + std::string path; + std::uint64_t value = 0; }; struct StatRule { std::string input; std::uint64_t statId = 0; + std::optional filter; }; struct StatRules { diff --git a/core/libs/prx/libSceNpTrophy2/include/TrophyStore.hpp b/core/libs/prx/libSceNpTrophy2/include/TrophyStore.hpp index d9ab341ae0..c69e678ba3 100644 --- a/core/libs/prx/libSceNpTrophy2/include/TrophyStore.hpp +++ b/core/libs/prx/libSceNpTrophy2/include/TrophyStore.hpp @@ -67,6 +67,7 @@ public: explicit StatStore(std::filesystem::path directory); void Set(std::int32_t userId, std::uint32_t serviceLabel, std::uint64_t statId, std::uint64_t value); + std::uint64_t Raise(std::int32_t userId, std::uint32_t serviceLabel, std::uint64_t statId, std::uint64_t value); std::optional Value(std::int32_t userId, std::uint32_t serviceLabel, std::uint64_t statId); std::map Values(std::int32_t userId, std::uint32_t serviceLabel); std::filesystem::path RecordPath(std::int32_t userId, std::uint32_t serviceLabel) const; diff --git a/core/libs/prx/libSceNpTrophy2/libSceNpTrophy2.hpp b/core/libs/prx/libSceNpTrophy2/libSceNpTrophy2.hpp new file mode 100644 index 0000000000..09c653d06d --- /dev/null +++ b/core/libs/prx/libSceNpTrophy2/libSceNpTrophy2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpTrophy2/include/Api.hpp" diff --git a/core/libs/prx/libSceNpTrophy2/src/Context.cpp b/core/libs/prx/libSceNpTrophy2/src/Context.cpp index ef3a23e905..8e623d8c36 100644 --- a/core/libs/prx/libSceNpTrophy2/src/Context.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/Context.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpTrophy2/include/Api.hpp" #include #include #include @@ -77,15 +78,15 @@ int APS5_VABI sceNpTrophy2UnregisterUnlockCallback() { return SCE_NP_TROPHY2_OK; } - int APS5_VABI sceNpTrophy2GetRewardIcon(int context, int handle, int reward_id, void* buffer, size_t* size) { (void)context; (void)handle; (void)reward_id; (void)buffer; - (void)size; - NotImplemented_nid_no_patch(__func__); - return 0; + if (size != nullptr) { + *size = NP_TROPHY2_ICON_SIZE_NONE; + } + return SCE_NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND; } int APS5_VABI sceNpTrophy2ShowTrophyList(int context) { diff --git a/core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp b/core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp index dc11f536ef..4e7c771003 100644 --- a/core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpTrophy2/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceNpTrophy2/src/GroupInfo.cpp b/core/libs/prx/libSceNpTrophy2/src/GroupInfo.cpp index 887423ddc8..d06dc60177 100644 --- a/core/libs/prx/libSceNpTrophy2/src/GroupInfo.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/GroupInfo.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpTrophy2/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceNpTrophy2/src/Handle.cpp b/core/libs/prx/libSceNpTrophy2/src/Handle.cpp index 3f65f5887e..64013be3d6 100644 --- a/core/libs/prx/libSceNpTrophy2/src/Handle.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/Handle.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpTrophy2/include/Api.hpp" #include #include diff --git a/core/libs/prx/libSceNpTrophy2/src/TitleTrophies.cpp b/core/libs/prx/libSceNpTrophy2/src/TitleTrophies.cpp index 813fa576f7..91fb5e816e 100644 --- a/core/libs/prx/libSceNpTrophy2/src/TitleTrophies.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/TitleTrophies.cpp @@ -139,6 +139,10 @@ StatChanges ApplyStatRules(const StatRules& rules, const NpTrophy2Event& event, const auto matching = rules.events.find(event.name); if (matching == rules.events.end()) return changes; for (const auto& rule : matching->second) { + if (rule.filter) { + const auto filtered = std::find_if(event.properties.begin(), event.properties.end(), [&rule](const NpTrophy2EventProperty& candidate) { return candidate.path == rule.filter->path; }); + if (filtered == event.properties.end() || filtered->value != rule.filter->value) continue; + } const auto& definition = rules.stats.at(rule.statId); const auto property = std::find_if(event.properties.begin(), event.properties.end(), [&rule](const NpTrophy2EventProperty& candidate) { return candidate.path == rule.input; }); const auto where = event.name + " $." + rule.input + " for stat " + std::to_string(rule.statId); @@ -148,6 +152,8 @@ StatChanges ApplyStatRules(const StatRules& rules, const NpTrophy2Event& event, changes.problems.push_back({where, property->text + " is not an unsigned integer"}); } else if (*property->value < definition.minimum || *property->value > definition.maximum) { changes.problems.push_back({where, property->text + " is not from " + std::to_string(definition.minimum) + " to " + std::to_string(definition.maximum)}); + } else if (definition.aggregation == StatAggregation::Max) { + changes.updates.push_back({rule.statId, stats.Raise(userId, serviceLabel, rule.statId, std::max(*property->value, definition.initial)), rule.input}); } else { stats.Set(userId, serviceLabel, rule.statId, *property->value); changes.updates.push_back({rule.statId, *property->value, rule.input}); diff --git a/core/libs/prx/libSceNpTrophy2/src/TrophyInfo.cpp b/core/libs/prx/libSceNpTrophy2/src/TrophyInfo.cpp index ce29da1513..1cabae19e7 100644 --- a/core/libs/prx/libSceNpTrophy2/src/TrophyInfo.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/TrophyInfo.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpTrophy2/include/Api.hpp" #include #include #include @@ -82,9 +83,7 @@ int APS5_VABI sceNpTrophy2GetTrophyInfo(int context, int handle, int trophy_id, return SCE_NP_TROPHY2_OK; } const auto trophy = package->trophies.find(trophy_id); - if (trophy == package->trophies.end()) { - throw std::invalid_argument(std::string(__func__) + ": trophy " + std::to_string(trophy_id) + " is not in " + NpTrophy2::PackagePath(owner->serviceLabel)); - } + if (trophy == package->trophies.end()) return SCE_NP_TROPHY2_ERROR_INVALID_TROPHY_ID; if (details != nullptr) fillDetails(trophy->second, details); if (data != nullptr) fillData(*owner, trophy->second, data); return SCE_NP_TROPHY2_OK; diff --git a/core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp b/core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp index 5da39d1cb4..91140de5cf 100644 --- a/core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp @@ -363,26 +363,34 @@ void readStat(const Json& definition, std::size_t index, StatRules& rules) { } const auto name = "stat " + std::to_string(*statId); const auto* aggregation = definition.Find("aggregation"); - if (aggregation == nullptr || aggregation->kind != Json::Kind::String || aggregation->text != "latest") { - rules.skipped.push_back(name + " has aggregation " + (aggregation != nullptr ? describe(*aggregation) : std::string("none")) + "; only \"latest\" is supported"); + const auto named = [](const Json* value, std::string_view text) { return value != nullptr && value->kind == Json::Kind::String && value->text == text; }; + StatDefinition stat; + if (named(aggregation, "latest")) { + stat.aggregation = StatAggregation::Latest; + } else if (named(aggregation, "max")) { + stat.aggregation = StatAggregation::Max; + } else { + rules.skipped.push_back(name + " has aggregation " + (aggregation != nullptr ? describe(*aggregation) : std::string("none")) + "; only \"latest\" and \"max\" are supported"); return; } const auto* type = definition.Find("dataType"); - StatDefinition stat; - if (type != nullptr && type->kind == Json::Kind::String && type->text == "uint32") { + if (named(type, "uint32")) { stat.type = StatType::UInt32; stat.maximum = UINT32_MAX; - } else if (type != nullptr && type->kind == Json::Kind::String && type->text == "uint64") { + } else if (named(type, "uint64")) { stat.type = StatType::UInt64; stat.maximum = UINT64_MAX; + } else if (named(type, "int32")) { + stat.type = StatType::Int32; + stat.maximum = INT32_MAX; } else { - rules.skipped.push_back(name + " has dataType " + (type != nullptr ? describe(*type) : std::string("none")) + "; only \"uint32\" and \"uint64\" are supported"); + rules.skipped.push_back(name + " has dataType " + (type != nullptr ? describe(*type) : std::string("none")) + "; only \"uint32\", \"uint64\" and \"int32\" are supported"); return; } if (const auto* minimum = definition.Find("minValue")) { const auto value = minimum->Unsigned(); if (!value || *value > stat.maximum) { - rules.skipped.push_back(name + " has minValue " + describe(*minimum) + ", which its " + type->text + " type cannot hold"); + rules.skipped.push_back(name + " has minValue " + describe(*minimum) + (stat.type == StatType::Int32 ? ", which is not from 0 to its int32 limit" : ", which its " + type->text + " type cannot hold")); return; } stat.minimum = *value; @@ -395,6 +403,15 @@ void readStat(const Json& definition, std::size_t index, StatRules& rules) { } stat.maximum = *value; } + stat.initial = stat.minimum; + if (const auto* initial = definition.Find("initialValue"); initial != nullptr && stat.aggregation == StatAggregation::Max) { + const auto value = initial->Unsigned(); + if (!value || *value < stat.minimum || *value > stat.maximum) { + rules.skipped.push_back(name + " has initialValue " + describe(*initial) + ", which is not from its minValue to its maxValue"); + return; + } + stat.initial = *value; + } if (!rules.stats.emplace(*statId, stat).second) rules.skipped.push_back(name + " is defined twice; the first definition is used"); } @@ -411,9 +428,20 @@ void readRule(const Json& rule, std::size_t index, StatRules& rules) { rules.skipped.push_back(where + " has no eventName"); return; } + std::optional filter; if (condition->members.size() != 1) { - rules.skipped.push_back(where + " filters " + event->text + " by more than its eventName; only eventName conditions are supported"); - return; + const auto* property = condition->Find("property"); + const auto* path = property != nullptr && property->kind == Json::Kind::Object ? property->Find("path") : nullptr; + const auto* comparator = property != nullptr && property->kind == Json::Kind::Object ? property->Find("comparator") : nullptr; + const auto* value = property != nullptr && property->kind == Json::Kind::Object ? property->Find("value") : nullptr; + const auto filterPath = path != nullptr && path->kind == Json::Kind::String ? propertyPath(path->text) : std::nullopt; + const auto filterValue = value != nullptr ? value->Unsigned() : std::nullopt; + const bool equal = comparator != nullptr && comparator->kind == Json::Kind::String && comparator->text == "=="; + if (condition->members.size() != 2 || property == nullptr || property->kind != Json::Kind::Object || property->members.size() != 3 || !filterPath || !equal || !filterValue) { + rules.skipped.push_back(where + " filters " + event->text + " by more than its eventName; only an eventName with one \"$.name\" property equal to an unsigned integer is supported"); + return; + } + filter = StatFilter{*filterPath, *filterValue}; } const auto* output = action->Find("output"); const auto* id = output != nullptr ? output->Find("statId") : nullptr; @@ -432,7 +460,7 @@ void readRule(const Json& rule, std::size_t index, StatRules& rules) { rules.skipped.push_back(where + " for " + event->text + " reads " + (input != nullptr ? describe(*input) : std::string("no input")) + "; only \"$.name\" property paths are supported"); return; } - rules.events[event->text].push_back({*path, *statId}); + rules.events[event->text].push_back({*path, *statId, std::move(filter)}); } } diff --git a/core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp b/core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp index b19804fe10..5091aad105 100644 --- a/core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp @@ -177,6 +177,18 @@ void StatStore::Set(std::int32_t userId, std::uint32_t serviceLabel, std::uint64 stats = std::move(updated); } +std::uint64_t StatStore::Raise(std::int32_t userId, std::uint32_t serviceLabel, std::uint64_t statId, std::uint64_t value) { + std::lock_guard lock(mutex); + const Key key{userId, serviceLabel}; + auto& stats = record(key); + if (const auto found = stats.find(statId); found != stats.end() && found->second >= value) return found->second; + auto updated = stats; + updated[statId] = value; + writeRecord(directory, RecordPath(userId, serviceLabel), updated, "stat record"); + stats = std::move(updated); + return value; +} + std::optional StatStore::Value(std::int32_t userId, std::uint32_t serviceLabel, std::uint64_t statId) { std::lock_guard lock(mutex); const auto& stats = record({userId, serviceLabel}); diff --git a/core/libs/prx/libSceNpTrophy2/tests/TrophyPackage.cpp b/core/libs/prx/libSceNpTrophy2/tests/TrophyPackage.cpp index d1dcdba734..0e7b0e4364 100644 --- a/core/libs/prx/libSceNpTrophy2/tests/TrophyPackage.cpp +++ b/core/libs/prx/libSceNpTrophy2/tests/TrophyPackage.cpp @@ -94,7 +94,11 @@ const char StatsDefinition[] = R"({"statDefinitionArray":[ {"statId":50,"dataType":"uint64","aggregation":"sum"}, {"statId":60,"dataType":"int32","aggregation":"latest"}, {"statId":70,"dataType":"uint32","aggregation":"latest","maxValue":"5000000000"}, - {"dataType":"uint32","aggregation":"latest"} + {"dataType":"uint32","aggregation":"latest"}, + {"statId":80,"dataType":"int32","aggregation":"max","minValue":"0","initialValue":"0"}, + {"statId":90,"dataType":"uint32","aggregation":"max","initialValue":"3"}, + {"statId":100,"dataType":"int32","aggregation":"max","initialValue":"-1"}, + {"statId":110,"dataType":"int32","aggregation":"latest","minValue":"-5"} ]})"; const char StatsExtraction[] = R"({"statsExtractionRuleArray":[ @@ -106,7 +110,12 @@ const char StatsExtraction[] = R"({"statsExtractionRuleArray":[ {"condition":{"eventName":"FILTERED","property":{"path":"$.level"}},"action":{"input":"$.counter","output":{"statId":10}}}, {"condition":{"eventName":"COUNT"},"action":{"output":{"statId":10}}}, {"condition":{"eventName":"BRACKETS"},"action":{"input":"$['counter']","output":{"statId":10}}}, - {"bad":1} + {"bad":1}, + {"condition":{"eventName":"PROGRESS","property":{"path":"$._trophy_id","comparator":"==","value":"5"}},"action":{"input":"$._trophy_progress","output":{"statId":80}}}, + {"condition":{"eventName":"PROGRESS","property":{"path":"$._trophy_id","comparator":"==","value":6}},"action":{"input":"$._trophy_progress","output":{"statId":90}}}, + {"condition":{"eventName":"RANGED","property":{"path":"$._trophy_id","comparator":">=","value":"5"}},"action":{"input":"$._trophy_progress","output":{"statId":80}}}, + {"condition":{"eventName":"NAMED","property":{"path":"$.mode","comparator":"==","value":"delta"}},"action":{"input":"$._trophy_progress","output":{"statId":80}}}, + {"condition":{"eventName":"EXTRA","property":{"path":"$._trophy_id","comparator":"==","value":"5"},"other":1},"action":{"input":"$._trophy_progress","output":{"statId":80}}} ]})"; std::string trophyPackage() { @@ -310,10 +319,13 @@ void testProgressAndPlatinum() { void testStatRules() { const auto rules = NpTrophy2::ParseStatRules(statPackage()); - check(rules.stats.size() == 4 && rules.stats.at(10).type == NpTrophy2::StatType::UInt64 && rules.stats.at(30).type == NpTrophy2::StatType::UInt32 && rules.stats.at(30).maximum == UINT32_MAX && - rules.stats.at(40).maximum == 50 && rules.stats.at(10).minimum == 0, + check(rules.stats.size() == 7 && rules.stats.at(10).type == NpTrophy2::StatType::UInt64 && rules.stats.at(30).type == NpTrophy2::StatType::UInt32 && rules.stats.at(30).maximum == UINT32_MAX && + rules.stats.at(40).maximum == 50 && rules.stats.at(10).minimum == 0 && rules.stats.at(10).aggregation == NpTrophy2::StatAggregation::Latest, "stat definitions were not read"); - check(rules.events.size() == 3 && rules.events.at("COLLECT").size() == 2 && rules.events.at("COLLECT")[1].input == "total" && rules.events.at("COLLECT")[1].statId == 20 && + check(rules.stats.at(60).type == NpTrophy2::StatType::Int32 && rules.stats.at(60).maximum == INT32_MAX && rules.stats.at(80).aggregation == NpTrophy2::StatAggregation::Max && + rules.stats.at(80).type == NpTrophy2::StatType::Int32 && rules.stats.at(80).initial == 0 && rules.stats.at(90).aggregation == NpTrophy2::StatAggregation::Max && rules.stats.at(90).initial == 3, + "int32 and max stat definitions were not read"); + check(rules.events.size() == 4 && rules.events.at("COLLECT").size() == 2 && rules.events.at("COLLECT")[1].input == "total" && rules.events.at("COLLECT")[1].statId == 20 && rules.events.at("STEPS")[0].input == "player.steps" && rules.events.at("TIME")[0].statId == 30, "extraction rules were not read"); std::set skipped(rules.skipped.begin(), rules.skipped.end()); @@ -323,11 +335,17 @@ void testStatRules() { } return false; }; - check(rules.skipped.size() == 9, "unsupported definitions and rules were not all reported"); - check(reported("stat 50 has aggregation \"sum\"") && reported("stat 60 has dataType \"int32\"") && reported("stat 70 has maxValue \"5000000000\"") && reported("statDefinitionArray[7] has no unsigned statId"), + check(rules.skipped.size() == 13, "unsupported definitions and rules were not all reported"); + check(reported("stat 50 has aggregation \"sum\"; only \"latest\" and \"max\" are supported") && reported("stat 70 has maxValue \"5000000000\"") && reported("statDefinitionArray[7] has no unsigned statId") && + reported("stat 100 has initialValue \"-1\"") && reported("stat 110 has minValue \"-5\", which is not from 0 to its int32 limit"), "unsupported definitions were not reported"); - check(reported("for SUM writes stat 50") && reported("filters FILTERED") && reported("for COUNT reads no input") && reported("for BRACKETS reads \"$['counter']\"") && reported("[8] has no condition"), + check(reported("for SUM writes stat 50") && reported("filters FILTERED") && reported("for COUNT reads no input") && reported("for BRACKETS reads \"$['counter']\"") && reported("[8] has no condition") && + reported("filters RANGED") && reported("filters NAMED") && reported("filters EXTRA"), "unsupported rules were not reported"); + const auto& progress = rules.events.at("PROGRESS"); + check(progress.size() == 2 && progress[0].filter && progress[0].filter->path == "_trophy_id" && progress[0].filter->value == 5 && progress[0].input == "_trophy_progress" && progress[0].statId == 80 && + progress[1].filter && progress[1].filter->value == 6 && progress[1].statId == 90 && !rules.events.at("COLLECT")[0].filter, + "property filters were not read"); for (const char* input : {"$", "$.", "$..a", "$.a.", "$.a-b", "counter", "$.a b"}) { const std::string extraction = std::string(R"({"statsExtractionRuleArray":[{"condition":{"eventName":"E"},"action":{"input":")") + input + R"(","output":{"statId":10}}}]})"; check(NpTrophy2::ParseStatRules(ucp({{"stats_definition.json", StatsDefinition}, {"stats_extraction.json", extraction}})).events.empty(), "an unsupported input path was accepted"); @@ -412,6 +430,37 @@ void testApplyStatRules() { check(latest.updates.size() == 2 && stats.Value(InitialUser, 0, 10) == std::optional(2), "a stat did not keep the latest value"); } +void testProgressRules() { + const auto rules = NpTrophy2::ParseStatRules(statPackage()); + NpTrophy2::StatStore stats("progress"); + const auto posted = [&](std::vector properties) { return NpTrophy2::ApplyStatRules(rules, event("PROGRESS", std::move(properties)), stats, InitialUser, 0); }; + const auto progress = [&](std::uint64_t trophy, std::uint64_t value) { return posted({{"_trophy_id", trophy, std::to_string(trophy)}, {"_trophy_progress", value, std::to_string(value)}}); }; + const auto first = progress(5, 4); + check(first.problems.empty() && first.updates.size() == 1 && first.updates[0].statId == 80 && first.updates[0].value == 4 && stats.Value(InitialUser, 0, 80) == std::optional(4) && + !stats.Value(InitialUser, 0, 90), + "a filtered rule did not take its trophy's progress, or another trophy's rule did"); + const auto lower = progress(5, 2); + check(lower.updates.size() == 1 && lower.updates[0].value == 4 && stats.Value(InitialUser, 0, 80) == std::optional(4), "a max stat went down"); + const auto higher = progress(5, 7); + check(higher.updates.size() == 1 && higher.updates[0].value == 7 && stats.Value(InitialUser, 0, 80) == std::optional(7), "a max stat did not rise"); + const auto floor = progress(6, 1); + check(floor.updates.size() == 1 && floor.updates[0].statId == 90 && floor.updates[0].value == 3 && stats.Value(InitialUser, 0, 90) == std::optional(3), "a max stat started below its initialValue"); + const auto signedMax = progress(5, 2147483648ull); + check(signedMax.updates.empty() && signedMax.problems.size() == 1 && signedMax.problems[0].reason == "2147483648 is not from 0 to 2147483647", "an int32 stat took a value it cannot hold"); + const auto other = progress(7, 9); + const auto unnamed = posted({{"_trophy_progress", 9, "9"}}); + const auto negative = posted({{"_trophy_id", std::nullopt, "-5"}, {"_trophy_progress", 9, "9"}}); + check(other.updates.empty() && other.problems.empty() && unnamed.updates.empty() && unnamed.problems.empty() && negative.updates.empty() && negative.problems.empty() && + stats.Value(InitialUser, 0, 80) == std::optional(7) && stats.Value(InitialUser, 0, 90) == std::optional(3), + "a filtered rule took an event for another trophy, or without its trophy id"); + const auto missing = posted({{"_trophy_id", 5, "5"}}); + check(missing.updates.empty() && missing.problems.size() == 1 && missing.problems[0].rule == "PROGRESS $._trophy_progress for stat 80", "a matching event without its input was not reported"); + NpTrophy2::StatStore restarted("progress"); + check(restarted.Raise(InitialUser, 0, 80, 6) == 7 && restarted.Raise(InitialUser, 0, 80, 8) == 8 && restarted.Raise(InitialUser, 0, 120, 1) == 1, "a raise did not keep the larger value"); + NpTrophy2::StatStore reread("progress"); + check(reread.Value(InitialUser, 0, 80) == std::optional(8) && reread.Value(InitialUser, 0, 120) == std::optional(1), "a raised stat was not stored"); +} + } int main() { @@ -427,6 +476,7 @@ int main() { testConditions(); testStatStore(); testApplyStatRules(); + testProgressRules(); std::filesystem::current_path(previous); std::filesystem::remove_all(root); std::puts("Trophy package tests passed"); diff --git a/core/libs/prx/libSceNpTrophy2/tests/TrophyStore.cpp b/core/libs/prx/libSceNpTrophy2/tests/TrophyStore.cpp index 10352c9edf..5a70852cc0 100644 --- a/core/libs/prx/libSceNpTrophy2/tests/TrophyStore.cpp +++ b/core/libs/prx/libSceNpTrophy2/tests/TrophyStore.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpTrophy2/include/Api.hpp" #include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp" #include "prx/libSceNpTrophy2/include/TitleTrophies.hpp" #include "prx/libSceNpTrophy2/include/TrophyStore.hpp" @@ -23,14 +24,6 @@ #include #endif -extern "C" { -int APS5_VABI sceNpTrophy2CreateContext(int*, int, uint32_t, uint64_t); -int APS5_VABI sceNpTrophy2DestroyContext(int); -int APS5_VABI sceNpTrophy2GetGameInfo(int, int, NpTrophy2GameDetails*, NpTrophy2GameData*); -int APS5_VABI sceNpTrophy2GetTrophyInfo(int, int, int, NpTrophy2Details*, NpTrophy2Data*); -int APS5_VABI sceNpTrophy2GetTrophyInfoArray(int, int, uint32_t, uint32_t, NpTrophy2Details*, NpTrophy2Data*, uint32_t*); -} - namespace { constexpr std::int32_t InitialUser = 0x10000000; @@ -246,7 +239,6 @@ void testQueries() { check(sceNpTrophy2DestroyContext(context) == 0 && sceNpTrophy2DestroyContext(other) == 0, "contexts were not destroyed"); } - void testPostedUnlockRules() { std::filesystem::create_directories("app0/sce_sys/trophy2"); std::filesystem::create_directories("app0/sce_sys/uds"); diff --git a/core/libs/prx/libSceNpTus/CMakeLists.txt b/core/libs/prx/libSceNpTus/CMakeLists.txt index 1cb8f4a676..6046093997 100644 --- a/core/libs/prx/libSceNpTus/CMakeLists.txt +++ b/core/libs/prx/libSceNpTus/CMakeLists.txt @@ -9,8 +9,14 @@ if(BUILD_TESTING) configure_windows_unwind(guest_np_tus_tests) add_test(NAME guest_np_tus COMMAND guest_np_tus_tests) set_tests_properties(guest_np_tus PROPERTIES TIMEOUT 20) + add_test_executable(guest_np_tus_offline_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTusOffline.cpp) + target_include_directories(guest_np_tus_offline_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_tus_offline_tests PRIVATE libSceNpTus) + configure_windows_unwind(guest_np_tus_offline_tests) + add_test(NAME guest_np_tus_offline COMMAND guest_np_tus_offline_tests) + set_tests_properties(guest_np_tus_offline PROPERTIES TIMEOUT 20) if(WIN32) - set_tests_properties(guest_np_tus PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_np_tus guest_np_tus_offline PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceNpTus/Export.cpp b/core/libs/prx/libSceNpTus/Export.cpp index a69e6cb5a6..2f632e6d18 100644 --- a/core/libs/prx/libSceNpTus/Export.cpp +++ b/core/libs/prx/libSceNpTus/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceNpTus/include/Api.hpp" +#include #include #include #include @@ -11,12 +13,24 @@ static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = static_cast( static constexpr int SCE_NP_COMMUNITY_ERROR_ABORTED = static_cast(0x80550707); static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast(0x8055070C); static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast(0x8055070E); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_TYPE = static_cast(0x80550711); static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID = static_cast(0x80550718); +static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID = static_cast(0x80550719); static constexpr int POLL_FINISHED = 0; static constexpr int POLL_RUNNING = 1; static constexpr std::size_t MAX_TITLE_CONTEXTS = 32; static constexpr std::size_t MAX_REQUESTS = 256; static constexpr int MAX_SLOTS = 64; +static constexpr int MAX_USERS = 101; +static constexpr int MAX_FRIENDS = 100; +static constexpr std::int32_t TSS_MAX_SLOT_ID = 15; +static constexpr std::uint64_t TSS_MAX_SIZE = 64 * 1024; +static constexpr std::int32_t OPETYPE_EQUAL = 1; +static constexpr std::int32_t OPETYPE_LESS_OR_EQUAL = 6; +static constexpr std::int32_t DATASTATUS_SORTTYPE_DESCENDING_DATE = 1; +static constexpr std::int32_t DATASTATUS_SORTTYPE_ASCENDING_DATE = 2; +static constexpr std::int32_t VARIABLE_SORTTYPE_DESCENDING_DATE = 1; +static constexpr std::int32_t VARIABLE_SORTTYPE_ASCENDING_VALUE = 4; namespace { struct Request { @@ -75,6 +89,16 @@ int Complete(int reqId, int asyncResult) { if (const int error = WaitOffline(reqId, &result)) return error; return result; } + +constexpr std::uint32_t INVALID_SERVICE_LABEL = 0xFFFFFFFF; + +int CreateTitleContext() { + std::lock_guard lock(tusMutex); + if (titleContexts.size() >= MAX_TITLE_CONTEXTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int contextId = nextTitleContext++; + titleContexts.insert(contextId); + return contextId; +} } extern "C" { @@ -82,11 +106,7 @@ extern "C" { int APS5_VABI sceNpTusCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId) { (void)serviceLabel; (void)userId; - std::lock_guard lock(tusMutex); - if (titleContexts.size() >= MAX_TITLE_CONTEXTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; - const int contextId = nextTitleContext++; - titleContexts.insert(contextId); - return contextId; + return CreateTitleContext(); } int APS5_VABI sceNpTusCreateRequest(int titleCtxId) { @@ -164,14 +184,14 @@ int APS5_VABI sceNpTusSetThreadParam(void) { return 0; } -int APS5_VABI sceNpTssCreateNpTitleCtx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTssCreateNpTitleCtx(std::uint32_t serviceLabel, const NpId* npId) { + if (!npId) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (serviceLabel == INVALID_SERVICE_LABEL) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return CreateTitleContext(); } -int APS5_VABI sceNpTssCreateNpTitleCtxA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTssCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId) { + return sceNpTusCreateNpTitleCtxA(serviceLabel, userId); } int APS5_VABI sceNpTusCreateTitleCtx(void) { @@ -292,176 +312,16 @@ int APS5_VABI sceNpTusDeleteMultiSlotVariableVUser(int reqId, const NpOnlineId* return Complete(reqId, sceNpTusDeleteMultiSlotVariableVUserAsync(reqId, targetVirtualUserId, slotIds, arrayLen, option)); } -int APS5_VABI sceNpTusGetMultiUserVariable(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetFriendsVariable(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetFriendsVariableAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariable(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariableVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariableAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariableVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusTryAndSetVariable(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusTryAndSetVariableVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusTryAndSetVariableAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusTryAndSetVariableVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusDeleteMultiSlotData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpTusDeleteMultiSlotDataVUser(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpTusDeleteMultiSlotDataAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpTusDeleteMultiSlotDataVUserAsync(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpTusSetDataAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusSetDataVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetDataAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetDataVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetFriendsDataStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetFriendsDataStatusAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTssGetSmallStorage(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTssGetSmallStorageAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpTssGetStorage(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -477,111 +337,6 @@ int APS5_VABI sceNpTssGetDataAsync(void) { return 0; } -int APS5_VABI sceNpTusAddAndGetVariableA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariableAVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariableAVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariableForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusAAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusAVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusAVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetDataA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetDataAAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetDataAVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetDataAVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetDataForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetDataForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpTusGetDataForCrossSaveVUser(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -592,86 +347,6 @@ int APS5_VABI sceNpTusGetDataForCrossSaveVUserAsync(void) { return 0; } -int APS5_VABI sceNpTusGetMultiUserVariableA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableAAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableAVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableAVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusAAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusAVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusAVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpTusGetFriendsVariableA(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -712,74 +387,655 @@ int APS5_VABI sceNpTusGetFriendsDataStatusForCrossSaveAsync(void) { return 0; } -int APS5_VABI sceNpTusSetDataA(void) { +int APS5_VABI sceNpTssGetSmallStorageAsync(int reqId, void* data, std::uint64_t maxSize, std::uint64_t* contentLength, void* option) { + (void)data; + (void)contentLength; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (maxSize > TSS_MAX_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusAddAndGetVariableAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + (void)inVariable; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)outVariableSize; + if (!targetVirtualUserId || !outVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusAddAndGetVariableAsync(int reqId, const void* targetNpId, std::int32_t slotId, std::int64_t inVariable, const void* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + (void)targetNpId; + (void)inVariable; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)outVariableSize; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!outVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + (void)targetAccountId; + (void)inVariable; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)outVariableSize; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!outVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + (void)inVariable; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)outVariableSize; + if (!targetVirtualUserId || !outVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusAddAndGetVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const void* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + (void)inVariable; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)outVariableSize; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!targetVirtualUserId || !outVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusDeleteMultiSlotDataAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option) { + (void)targetAccountId; + if (!slotIds) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + if (std::any_of(slotIds, slotIds + arrayLen, [](std::int32_t id) { return id < 0; })) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusDeleteMultiSlotDataAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, int arrayLen, void* option) { + if (!targetNpId || !slotIds || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + if (std::any_of(slotIds, slotIds + arrayLen, [](std::int32_t id) { return id < 0; })) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetDataAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) { + (void)targetAccountId; + (void)data; + (void)recvSize; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!dataStatus || dataStatusSize == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetDataAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) { + (void)data; + (void)recvSize; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!targetVirtualUserId || !dataStatus || dataStatusSize == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetDataAsync(int reqId, const void* targetNpId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) { + (void)targetNpId; + (void)data; + (void)recvSize; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!dataStatus || dataStatusSize == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetDataForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) { + (void)targetAccountId; + (void)data; + (void)recvSize; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!dataStatus || dataStatusSize == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetDataVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) { + (void)data; + (void)recvSize; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!targetVirtualUserId || !dataStatus || dataStatusSize == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetFriendsDataStatusAsync(int reqId, std::int32_t slotId, std::int32_t includeSelf, std::int32_t sortType, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)includeSelf; + (void)statusArraySize; + if (!statusArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_FRIENDS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + if (sortType < DATASTATUS_SORTTYPE_DESCENDING_DATE || sortType > DATASTATUS_SORTTYPE_ASCENDING_DATE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetFriendsVariableAsync(int reqId, std::int32_t slotId, std::int32_t includeSelf, std::int32_t sortType, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + (void)includeSelf; + (void)variableArraySize; + if (!variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_FRIENDS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + if (sortType < VARIABLE_SORTTYPE_DESCENDING_DATE || sortType > VARIABLE_SORTTYPE_ASCENDING_VALUE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)targetAccountId; + (void)statusArraySize; + if (!slotIds || !statusArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + if (std::any_of(slotIds, slotIds + arrayLen, [](std::int32_t id) { return id < 0; })) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetVirtualUserId || !slotIds || !statusArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + if (std::any_of(slotIds, slotIds + arrayLen, [](std::int32_t id) { return id < 0; })) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetNpId || !slotIds || !statusArray || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + if (std::any_of(slotIds, slotIds + arrayLen, [](std::int32_t id) { return id < 0; })) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)targetAccountId; + (void)statusArraySize; + if (!slotIds || !statusArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + if (std::any_of(slotIds, slotIds + arrayLen, [](std::int32_t id) { return id < 0; })) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetVirtualUserId || !slotIds || !statusArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + if (std::any_of(slotIds, slotIds + arrayLen, [](std::int32_t id) { return id < 0; })) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetVirtualUserId || !slotIds || !statusArray || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + if (std::any_of(slotIds, slotIds + arrayLen, [](std::int32_t id) { return id < 0; })) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusAAsync(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetAccountIdArray || !statusArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetVirtualUserIdArray || !statusArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + for (int index = 0; index < arrayLen; ++index) { + if (IsEmpty(&targetVirtualUserIdArray[index])) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusAsync(int reqId, const void* targetNpIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetNpIdArray || !statusArray || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveAsync(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetAccountIdArray || !statusArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetVirtualUserIdArray || !statusArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + for (int index = 0; index < arrayLen; ++index) { + if (IsEmpty(&targetVirtualUserIdArray[index])) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + (void)statusArraySize; + if (!targetVirtualUserIdArray || !statusArray || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + for (int index = 0; index < arrayLen; ++index) { + if (IsEmpty(&targetVirtualUserIdArray[index])) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserVariableAAsync(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + (void)variableArraySize; + if (!targetAccountIdArray || !variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserVariableAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + (void)variableArraySize; + if (!targetVirtualUserIdArray || !variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + for (int index = 0; index < arrayLen; ++index) { + if (IsEmpty(&targetVirtualUserIdArray[index])) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserVariableAsync(int reqId, const void* targetNpIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + (void)variableArraySize; + if (!targetNpIdArray || !variableArray || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveAsync(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + (void)variableArraySize; + if (!targetAccountIdArray || !variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserVariableForCrossSave(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserVariableForCrossSaveAsync(reqId, targetAccountIdArray, slotId, variableArray, variableArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + (void)variableArraySize; + if (!targetVirtualUserIdArray || !variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || arrayLen < 1 || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + for (int index = 0; index < arrayLen; ++index) { + if (IsEmpty(&targetVirtualUserIdArray[index])) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusGetMultiUserVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + (void)variableArraySize; + if (!targetVirtualUserIdArray || !variableArray || arrayLen < 1) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_USERS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + for (int index = 0; index < arrayLen; ++index) { + if (IsEmpty(&targetVirtualUserIdArray[index])) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusSetDataAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* option) { + (void)targetAccountId; + (void)info; + (void)infoStructSize; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!data || sendSize == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0 || sendSize > totalSize) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusSetDataAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* option) { + (void)info; + (void)infoStructSize; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!targetVirtualUserId || !data || sendSize == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0 || sendSize > totalSize) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusSetDataAsync(int reqId, const void* targetNpId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option) { + (void)targetNpId; + (void)info; + (void)infoStructSize; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!data || sendSize == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0 || sendSize > totalSize) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusSetDataVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option) { + (void)info; + (void)infoStructSize; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!targetVirtualUserId || !data || sendSize == 0) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0 || sendSize > totalSize) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusTryAndSetVariableAAsync(int reqId, std::uint64_t targetAccountId, 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) { + (void)targetAccountId; + (void)variable; + (void)compareValue; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)resultVariableSize; + if (!resultVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (opeType < OPETYPE_EQUAL || opeType > OPETYPE_LESS_OR_EQUAL) return SCE_NP_COMMUNITY_ERROR_INVALID_TYPE; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusTryAndSetVariableAVUserAsync(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) { + (void)variable; + (void)compareValue; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)resultVariableSize; + if (!targetVirtualUserId || !resultVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (opeType < OPETYPE_EQUAL || opeType > OPETYPE_LESS_OR_EQUAL) return SCE_NP_COMMUNITY_ERROR_INVALID_TYPE; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusTryAndSetVariableAsync(int reqId, const void* targetNpId, 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) { + (void)variable; + (void)compareValue; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)resultVariableSize; + if (!targetNpId || !resultVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (opeType < OPETYPE_EQUAL || opeType > OPETYPE_LESS_OR_EQUAL) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, 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) { + (void)targetAccountId; + (void)variable; + (void)compareValue; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)resultVariableSize; + if (!resultVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (opeType < OPETYPE_EQUAL || opeType > OPETYPE_LESS_OR_EQUAL) return SCE_NP_COMMUNITY_ERROR_INVALID_TYPE; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync(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) { + (void)variable; + (void)compareValue; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)resultVariableSize; + if (!targetVirtualUserId || !resultVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (opeType < OPETYPE_EQUAL || opeType > OPETYPE_LESS_OR_EQUAL) return SCE_NP_COMMUNITY_ERROR_INVALID_TYPE; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusTryAndSetVariableVUserAsync(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) { + (void)variable; + (void)compareValue; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)resultVariableSize; + if (!targetVirtualUserId || !resultVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option || slotId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (opeType < OPETYPE_EQUAL || opeType > OPETYPE_LESS_OR_EQUAL) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (IsEmpty(targetVirtualUserId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTssGetSmallStorage(int reqId, void* data, std::uint64_t maxSize, std::uint64_t* contentLength, void* option) { + return Complete(reqId, sceNpTssGetSmallStorageAsync(reqId, data, maxSize, contentLength, option)); +} + +int APS5_VABI sceNpTusAddAndGetVariable(int reqId, const void* targetNpId, std::int32_t slotId, std::int64_t inVariable, const void* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + return Complete(reqId, sceNpTusAddAndGetVariableAsync(reqId, targetNpId, slotId, inVariable, isLastChangedAuthor, isLastChangedDate, outVariable, outVariableSize, option)); +} + +int APS5_VABI sceNpTusAddAndGetVariableA(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + return Complete(reqId, sceNpTusAddAndGetVariableAAsync(reqId, targetAccountId, slotId, inVariable, isLastChangedAuthor, isLastChangedDate, outVariable, outVariableSize, option)); +} + +int APS5_VABI sceNpTusAddAndGetVariableAVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + return Complete(reqId, sceNpTusAddAndGetVariableAVUserAsync(reqId, targetVirtualUserId, slotId, inVariable, isLastChangedAuthor, isLastChangedDate, outVariable, outVariableSize, option)); +} + +int APS5_VABI sceNpTusAddAndGetVariableForCrossSave(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + return Complete(reqId, sceNpTusAddAndGetVariableForCrossSaveAsync(reqId, targetAccountId, slotId, inVariable, isLastChangedAuthor, isLastChangedDate, outVariable, outVariableSize, option)); +} + +int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + return Complete(reqId, sceNpTusAddAndGetVariableForCrossSaveVUserAsync(reqId, targetVirtualUserId, slotId, inVariable, isLastChangedAuthor, isLastChangedDate, outVariable, outVariableSize, option)); +} + +int APS5_VABI sceNpTusAddAndGetVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const void* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + return Complete(reqId, sceNpTusAddAndGetVariableVUserAsync(reqId, targetVirtualUserId, slotId, inVariable, isLastChangedAuthor, isLastChangedDate, outVariable, outVariableSize, option)); +} + +int APS5_VABI sceNpTusDeleteMultiSlotData(int reqId, const void* targetNpId, const std::int32_t* slotIds, int arrayLen, void* option) { + return Complete(reqId, sceNpTusDeleteMultiSlotDataAsync(reqId, targetNpId, slotIds, arrayLen, option)); +} + +int APS5_VABI sceNpTusDeleteMultiSlotDataA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option) { + return Complete(reqId, sceNpTusDeleteMultiSlotDataAAsync(reqId, targetAccountId, slotIds, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetDataA(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) { + return Complete(reqId, sceNpTusGetDataAAsync(reqId, targetAccountId, slotId, dataStatus, dataStatusSize, data, recvSize, option)); +} + +int APS5_VABI sceNpTusGetDataAVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) { + return Complete(reqId, sceNpTusGetDataAVUserAsync(reqId, targetVirtualUserId, slotId, dataStatus, dataStatusSize, data, recvSize, option)); +} + +int APS5_VABI sceNpTusGetDataForCrossSave(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) { + return Complete(reqId, sceNpTusGetDataForCrossSaveAsync(reqId, targetAccountId, slotId, dataStatus, dataStatusSize, data, recvSize, option)); +} + +int APS5_VABI sceNpTusGetFriendsDataStatus(int reqId, std::int32_t slotId, std::int32_t includeSelf, std::int32_t sortType, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetFriendsDataStatusAsync(reqId, slotId, includeSelf, sortType, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetFriendsVariable(int reqId, std::int32_t slotId, std::int32_t includeSelf, std::int32_t sortType, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetFriendsVariableAsync(reqId, slotId, includeSelf, sortType, variableArray, variableArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatus(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiSlotDataStatusAsync(reqId, targetNpId, slotIds, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiSlotDataStatusAAsync(reqId, targetAccountId, slotIds, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusAVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiSlotDataStatusAVUserAsync(reqId, targetVirtualUserId, slotIds, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSave(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(reqId, targetAccountId, slotIds, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(reqId, targetVirtualUserId, slotIds, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiSlotDataStatusVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiSlotDataStatusVUserAsync(reqId, targetVirtualUserId, slotIds, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatus(int reqId, const void* targetNpIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserDataStatusAsync(reqId, targetNpIdArray, slotId, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusA(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserDataStatusAAsync(reqId, targetAccountIdArray, slotId, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusAVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserDataStatusAVUserAsync(reqId, targetVirtualUserIdArray, slotId, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSave(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserDataStatusForCrossSaveAsync(reqId, targetAccountIdArray, slotId, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(reqId, targetVirtualUserIdArray, slotId, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserDataStatusVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserDataStatusVUserAsync(reqId, targetVirtualUserIdArray, slotId, statusArray, statusArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserVariable(int reqId, const void* targetNpIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserVariableAsync(reqId, targetNpIdArray, slotId, variableArray, variableArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserVariableA(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserVariableAAsync(reqId, targetAccountIdArray, slotId, variableArray, variableArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserVariableAVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserVariableAVUserAsync(reqId, targetVirtualUserIdArray, slotId, variableArray, variableArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(reqId, targetVirtualUserIdArray, slotId, variableArray, variableArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusGetMultiUserVariableVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiUserVariableVUserAsync(reqId, targetVirtualUserIdArray, slotId, variableArray, variableArraySize, arrayLen, option)); +} + +int APS5_VABI sceNpTusSetDataA(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* option) { + return Complete(reqId, sceNpTusSetDataAAsync(reqId, targetAccountId, slotId, totalSize, sendSize, data, info, infoStructSize, isLastChangedAuthor, isLastChangedDate, option)); +} + +int APS5_VABI sceNpTusSetDataAVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* option) { + return Complete(reqId, sceNpTusSetDataAVUserAsync(reqId, targetVirtualUserId, slotId, totalSize, sendSize, data, info, infoStructSize, isLastChangedAuthor, isLastChangedDate, option)); +} + +int APS5_VABI sceNpTusTryAndSetVariable(int reqId, const void* targetNpId, 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) { + return Complete(reqId, sceNpTusTryAndSetVariableAsync(reqId, targetNpId, slotId, opeType, variable, compareValue, isLastChangedAuthor, isLastChangedDate, resultVariable, resultVariableSize, option)); +} + +int APS5_VABI sceNpTusTryAndSetVariableA(int reqId, std::uint64_t targetAccountId, 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) { + return Complete(reqId, sceNpTusTryAndSetVariableAAsync(reqId, targetAccountId, slotId, opeType, variable, compareValue, isLastChangedAuthor, isLastChangedDate, resultVariable, resultVariableSize, option)); +} + +int APS5_VABI sceNpTusTryAndSetVariableAVUser(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) { + return Complete(reqId, sceNpTusTryAndSetVariableAVUserAsync(reqId, targetVirtualUserId, slotId, opeType, variable, compareValue, isLastChangedAuthor, isLastChangedDate, resultVariable, resultVariableSize, option)); +} + +int APS5_VABI sceNpTusTryAndSetVariableForCrossSave(int reqId, std::uint64_t targetAccountId, 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) { + return Complete(reqId, sceNpTusTryAndSetVariableForCrossSaveAsync(reqId, targetAccountId, slotId, opeType, variable, compareValue, isLastChangedAuthor, isLastChangedDate, resultVariable, resultVariableSize, option)); +} + +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) { + return Complete(reqId, sceNpTusTryAndSetVariableForCrossSaveVUserAsync(reqId, targetVirtualUserId, slotId, opeType, variable, compareValue, isLastChangedAuthor, isLastChangedDate, resultVariable, resultVariableSize, 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) { + return Complete(reqId, sceNpTusTryAndSetVariableVUserAsync(reqId, targetVirtualUserId, slotId, opeType, variable, compareValue, isLastChangedAuthor, isLastChangedDate, resultVariable, resultVariableSize, option)); +} + +int APS5_VABI sceNpTssGetData() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpTusSetDataAAsync(void) { +int APS5_VABI sceNpTusCreateNpTitleCtx() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpTusSetDataAVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +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) { + return Complete(reqId, sceNpTusGetDataAsync(reqId, targetNpId, slotId, dataStatus, dataStatusSize, data, recvSize, option)); } -int APS5_VABI sceNpTusSetDataAVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +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) { + return Complete(reqId, sceNpTusGetDataVUserAsync(reqId, targetVirtualUserId, slotId, dataStatus, dataStatusSize, data, recvSize, option)); } -int APS5_VABI sceNpTusDeleteMultiSlotDataA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTusGetMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiSlotVariableAsync(reqId, targetNpId, slotIds, variableArray, variableArraySize, arrayLen, option)); } -int APS5_VABI sceNpTusDeleteMultiSlotDataAAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTusGetMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + return Complete(reqId, sceNpTusGetMultiSlotVariableVUserAsync(reqId, targetVirtualUserId, slotIds, variableArray, variableArraySize, arrayLen, option)); } -int APS5_VABI sceNpTusTryAndSetVariableA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTusSetData(int reqId, const void* targetNpId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option) { + return Complete(reqId, sceNpTusSetDataAsync(reqId, targetNpId, slotId, totalSize, sendSize, data, info, infoStructSize, isLastChangedAuthor, isLastChangedDate, option)); } -int APS5_VABI sceNpTusTryAndSetVariableAAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTusSetDataVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option) { + return Complete(reqId, sceNpTusSetDataVUserAsync(reqId, targetVirtualUserId, slotId, totalSize, sendSize, data, info, infoStructSize, isLastChangedAuthor, isLastChangedDate, option)); } -int APS5_VABI sceNpTusTryAndSetVariableAVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTusSetMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option) { + return Complete(reqId, sceNpTusSetMultiSlotVariableAsync(reqId, targetNpId, slotIds, variableArray, arrayLen, option)); } -int APS5_VABI sceNpTusTryAndSetVariableAVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusTryAndSetVariableForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveVUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTusSetMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option) { + return Complete(reqId, sceNpTusSetMultiSlotVariableVUserAsync(reqId, targetVirtualUserId, slotIds, variableArray, arrayLen, option)); } } diff --git a/core/libs/prx/libSceNpTus/include/Api.hpp b/core/libs/prx/libSceNpTus/include/Api.hpp new file mode 100644 index 0000000000..eacd0fe74a --- /dev/null +++ b/core/libs/prx/libSceNpTus/include/Api.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpTusCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId); +int APS5_VABI sceNpTusCreateRequest(int titleCtxId); +int APS5_VABI sceNpTusDeleteNpTitleCtx(int titleCtxId); +int APS5_VABI sceNpTusDeleteRequest(int reqId); +int APS5_VABI sceNpTusAbortRequest(int reqId); +int APS5_VABI sceNpTusPollAsync(int reqId, int* result); +int APS5_VABI sceNpTusGetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusSetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusAddAndGetVariableAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusSetThreadParam(void); +int APS5_VABI sceNpTssCreateNpTitleCtx(std::uint32_t serviceLabel, const NpId* npId); +int APS5_VABI sceNpTssCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId); +int APS5_VABI sceNpTusCreateTitleCtx(void); +int APS5_VABI sceNpTusSetTimeout(void); +int APS5_VABI sceNpTusChangeModeForOtherSaveDataOwners(void); +int APS5_VABI sceNpTusWaitAsync(int reqId, int* result); +int APS5_VABI sceNpTusGetMultiSlotVariableA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotVariableAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotVariableAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotVariableAVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSave(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusSetMultiSlotVariableA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); +int APS5_VABI sceNpTusSetMultiSlotVariableAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); +int APS5_VABI sceNpTusSetMultiSlotVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotVariableA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotVariableAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotDataVUser(void); +int APS5_VABI sceNpTusDeleteMultiSlotDataVUserAsync(void); +int APS5_VABI sceNpTssGetStorage(void); +int APS5_VABI sceNpTssGetStorageAsync(void); +int APS5_VABI sceNpTssGetDataAsync(void); +int APS5_VABI sceNpTusGetDataForCrossSaveVUser(void); +int APS5_VABI sceNpTusGetDataForCrossSaveVUserAsync(void); +int APS5_VABI sceNpTusGetMultiUserVariableForCrossSave(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetFriendsVariableA(void); +int APS5_VABI sceNpTusGetFriendsVariableAAsync(void); +int APS5_VABI sceNpTusGetFriendsVariableForCrossSave(void); +int APS5_VABI sceNpTusGetFriendsVariableForCrossSaveAsync(void); +int APS5_VABI sceNpTusGetFriendsDataStatusA(void); +int APS5_VABI sceNpTusGetFriendsDataStatusAAsync(void); +int APS5_VABI sceNpTusGetFriendsDataStatusForCrossSave(void); +int APS5_VABI sceNpTusGetFriendsDataStatusForCrossSaveAsync(void); +int APS5_VABI sceNpTssGetSmallStorageAsync(int reqId, void* data, std::uint64_t maxSize, std::uint64_t* contentLength, void* option); +int APS5_VABI sceNpTusAddAndGetVariableAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusAddAndGetVariableAsync(int reqId, const void* targetNpId, std::int32_t slotId, std::int64_t inVariable, const void* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusAddAndGetVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const void* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotDataAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotDataAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusGetDataAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option); +int APS5_VABI sceNpTusGetDataAVUserAsync(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 sceNpTusGetDataAsync(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 sceNpTusGetDataForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option); +int APS5_VABI sceNpTusGetDataVUserAsync(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 sceNpTusGetFriendsDataStatusAsync(int reqId, std::int32_t slotId, std::int32_t includeSelf, std::int32_t sortType, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetFriendsVariableAsync(int reqId, std::int32_t slotId, std::int32_t includeSelf, std::int32_t sortType, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusAAsync(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusAsync(int reqId, const void* targetNpIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveAsync(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableAAsync(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableAsync(int reqId, const void* targetNpIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveAsync(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusSetDataAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* option); +int APS5_VABI sceNpTusSetDataAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* option); +int APS5_VABI sceNpTusSetDataAsync(int reqId, const void* targetNpId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option); +int APS5_VABI sceNpTusSetDataVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option); +int APS5_VABI sceNpTusTryAndSetVariableAAsync(int reqId, std::uint64_t targetAccountId, 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 sceNpTusTryAndSetVariableAVUserAsync(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 sceNpTusTryAndSetVariableAsync(int reqId, const void* targetNpId, 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 sceNpTusTryAndSetVariableForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, 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 sceNpTusTryAndSetVariableForCrossSaveVUserAsync(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 sceNpTusTryAndSetVariableVUserAsync(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 sceNpTssGetSmallStorage(int reqId, void* data, std::uint64_t maxSize, std::uint64_t* contentLength, void* option); +int APS5_VABI sceNpTusAddAndGetVariable(int reqId, const void* targetNpId, std::int32_t slotId, std::int64_t inVariable, const void* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusAddAndGetVariableA(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusAddAndGetVariableAVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusAddAndGetVariableForCrossSave(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusAddAndGetVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int64_t inVariable, const void* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotData(int reqId, const void* targetNpId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotDataA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusGetDataA(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option); +int APS5_VABI sceNpTusGetDataAVUser(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 sceNpTusGetDataForCrossSave(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option); +int APS5_VABI sceNpTusGetFriendsDataStatus(int reqId, std::int32_t slotId, std::int32_t includeSelf, std::int32_t sortType, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetFriendsVariable(int reqId, std::int32_t slotId, std::int32_t includeSelf, std::int32_t sortType, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatus(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusAVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSave(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiSlotDataStatusVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatus(int reqId, const void* targetNpIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusA(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusAVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSave(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserDataStatusVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* statusArray, std::uint64_t statusArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariable(int reqId, const void* targetNpIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableA(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableAVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusGetMultiUserVariableVUser(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusSetDataA(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* option); +int APS5_VABI sceNpTusSetDataAVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* option); +int APS5_VABI sceNpTusTryAndSetVariable(int reqId, const void* targetNpId, 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 sceNpTusTryAndSetVariableA(int reqId, std::uint64_t targetAccountId, 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 sceNpTusTryAndSetVariableAVUser(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 sceNpTusTryAndSetVariableForCrossSave(int reqId, std::uint64_t targetAccountId, 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 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 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); +int APS5_VABI sceNpTusGetMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusSetData(int reqId, const void* targetNpId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option); +int APS5_VABI sceNpTusSetDataVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option); +int APS5_VABI sceNpTusSetMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); +int APS5_VABI sceNpTusSetMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); + +} diff --git a/core/libs/prx/libSceNpTus/libSceNpTus.hpp b/core/libs/prx/libSceNpTus/libSceNpTus.hpp new file mode 100644 index 0000000000..5cf1eba7a1 --- /dev/null +++ b/core/libs/prx/libSceNpTus/libSceNpTus.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpTus/include/Api.hpp" diff --git a/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt b/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt index d9163d1dc7..62016d6a55 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt +++ b/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(libSceNpUniversalDataSystem SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/PendingExports.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/System.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Context.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Handle.cpp diff --git a/core/libs/prx/libSceNpUniversalDataSystem/Export.cpp b/core/libs/prx/libSceNpUniversalDataSystem/Export.cpp index 0231b0cf03..28b17e212b 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/Export.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp b/core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp new file mode 100644 index 0000000000..b19e95eb61 --- /dev/null +++ b/core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp @@ -0,0 +1,137 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceNpUniversalDataSystemIntCreateRecordArray() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntCreateRecordData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntCreateRecordObject() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntDeleteAllRecord() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntDestroyRecordArray() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntDestroyRecordData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntDestroyRecordObject() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntGetMemoryStat() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntPostRecordData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetArray() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetBinary() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetBool() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetFloat32() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetFloat64() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetInt32() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetInt64() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetObject() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetString() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetUInt32() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetUInt64() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordDataEstimateSize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetArray() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetBinary() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetBool() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetFloat32() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetFloat64() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetInt32() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetInt64() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetObject() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetString() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetUInt32() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetUInt64() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceNpUniversalDataSystem/include/Api.hpp b/core/libs/prx/libSceNpUniversalDataSystem/include/Api.hpp new file mode 100644 index 0000000000..c0d1ad66ea --- /dev/null +++ b/core/libs/prx/libSceNpUniversalDataSystem/include/Api.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include "prx/libSceNpUniversalDataSystem/include/UdsEvent.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpUniversalDataSystemIntAbortHandle(void); +int APS5_VABI sceNpUniversalDataSystemIntCheckNetSyncTitles(void); +int APS5_VABI sceNpUniversalDataSystemIntCreateContext(void); +int APS5_VABI sceNpUniversalDataSystemIntCreateHandle(void); +int APS5_VABI sceNpUniversalDataSystemIntDeleteAllData(void); +int APS5_VABI sceNpUniversalDataSystemIntDeleteUserDataWithServerData(void); +int APS5_VABI sceNpUniversalDataSystemIntDestroyContext(void); +int APS5_VABI sceNpUniversalDataSystemIntDestroyHandle(void); +int APS5_VABI sceNpUniversalDataSystemIntInitialize(void); +int APS5_VABI sceNpUniversalDataSystemIntNetSyncTitles(void); +int APS5_VABI sceNpUniversalDataSystemIntTerminate(void); +int APS5_VABI sceNpUniversalDataSystemCreateContext(int* context, int userId, uint32_t serviceLabel, uint64_t options); +int APS5_VABI sceNpUniversalDataSystemDestroyContext(int context); +int APS5_VABI sceNpUniversalDataSystemRegisterContext(int context, int handle, uint64_t options); +int APS5_VABI sceNpUniversalDataSystemCreateEvent( const char* eventName, const NpUniversalDataSystemEventPropertyObject* prop, NpUniversalDataSystemEvent** newEvent, NpUniversalDataSystemEventPropertyObject** propPtr); +int APS5_VABI sceNpUniversalDataSystemDestroyEvent(NpUniversalDataSystemEvent* event); +int APS5_VABI sceNpUniversalDataSystemPostEvent(int context, int handle, const void* event, uint64_t options); +int APS5_VABI sceNpUniversalDataSystemEventEstimateSize(const NpUniversalDataSystemEvent* event, size_t* size); +int APS5_VABI sceNpUniversalDataSystemEventToString( const NpUniversalDataSystemEvent* event, char* buf, size_t bufSize, size_t* stringSize); +int APS5_VABI sceNpUniversalDataSystemCreateEventPropertyArray(NpUniversalDataSystemEventPropertyArray** newArray); +int APS5_VABI sceNpUniversalDataSystemDestroyEventPropertyArray(NpUniversalDataSystemEventPropertyArray* array); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetString( NpUniversalDataSystemEventPropertyArray* array, const char* value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetInt32( NpUniversalDataSystemEventPropertyArray* array, int32_t value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetUInt32( NpUniversalDataSystemEventPropertyArray* array, uint32_t value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetInt64( NpUniversalDataSystemEventPropertyArray* array, int64_t value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetUInt64( NpUniversalDataSystemEventPropertyArray* array, uint64_t value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetFloat32( NpUniversalDataSystemEventPropertyArray* array, float value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetFloat64( NpUniversalDataSystemEventPropertyArray* array, double value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetBool( NpUniversalDataSystemEventPropertyArray* array, bool value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetBinary( NpUniversalDataSystemEventPropertyArray* array, const void* value, size_t valueSize); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetObject( NpUniversalDataSystemEventPropertyArray* array, const NpUniversalDataSystemEventPropertyObject* value, NpUniversalDataSystemEventPropertyObject** valuePtr); +int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetArray( NpUniversalDataSystemEventPropertyArray* array, const NpUniversalDataSystemEventPropertyArray* value, NpUniversalDataSystemEventPropertyArray** valuePtr); +int APS5_VABI sceNpUniversalDataSystemCreateEventPropertyObject(NpUniversalDataSystemEventPropertyObject** newObject); +int APS5_VABI sceNpUniversalDataSystemDestroyEventPropertyObject(NpUniversalDataSystemEventPropertyObject* object); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetString( NpUniversalDataSystemEventPropertyObject* object, const char* key, const char* value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetInt32( NpUniversalDataSystemEventPropertyObject* object, const char* key, int32_t value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetUInt32( NpUniversalDataSystemEventPropertyObject* object, const char* key, uint32_t value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetInt64( NpUniversalDataSystemEventPropertyObject* object, const char* key, int64_t value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetUInt64( NpUniversalDataSystemEventPropertyObject* object, const char* key, uint64_t value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetFloat32( NpUniversalDataSystemEventPropertyObject* object, const char* key, float value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetFloat64( NpUniversalDataSystemEventPropertyObject* object, const char* key, double value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetBool( NpUniversalDataSystemEventPropertyObject* object, const char* key, bool value); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetBinary( NpUniversalDataSystemEventPropertyObject* object, const char* key, const void* value, size_t valueSize); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetObject( NpUniversalDataSystemEventPropertyObject* object, const char* key, const NpUniversalDataSystemEventPropertyObject* value, NpUniversalDataSystemEventPropertyObject** valuePtr); +int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetArray( NpUniversalDataSystemEventPropertyObject* object, const char* key, const NpUniversalDataSystemEventPropertyArray* value, NpUniversalDataSystemEventPropertyArray** valuePtr); +int APS5_VABI sceNpUniversalDataSystemCreateHandle(int* handle); +int APS5_VABI sceNpUniversalDataSystemDestroyHandle(int handle); +int APS5_VABI sceNpUniversalDataSystemAbortHandle(int handle); +int APS5_VABI sceNpUniversalDataSystemInitialize(const NpUniversalDataSystemInitParam* param); +int APS5_VABI sceNpUniversalDataSystemTerminate(void); +int APS5_VABI sceNpUniversalDataSystemGetMemoryStat(NpUniversalDataSystemMemoryStat* stat); +int APS5_VABI sceNpUniversalDataSystemGetStorageStat(int context, NpUniversalDataSystemStorageStat* stat); + +int APS5_VABI sceNpUniversalDataSystemIntCreateRecordArray(); +int APS5_VABI sceNpUniversalDataSystemIntCreateRecordData(); +int APS5_VABI sceNpUniversalDataSystemIntCreateRecordObject(); +int APS5_VABI sceNpUniversalDataSystemIntDeleteAllRecord(); +int APS5_VABI sceNpUniversalDataSystemIntDestroyRecordArray(); +int APS5_VABI sceNpUniversalDataSystemIntDestroyRecordData(); +int APS5_VABI sceNpUniversalDataSystemIntDestroyRecordObject(); +int APS5_VABI sceNpUniversalDataSystemIntGetMemoryStat(); +int APS5_VABI sceNpUniversalDataSystemIntPostRecordData(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetArray(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetBinary(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetBool(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetFloat32(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetFloat64(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetInt32(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetInt64(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetObject(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetString(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetUInt32(); +int APS5_VABI sceNpUniversalDataSystemIntRecordArraySetUInt64(); +int APS5_VABI sceNpUniversalDataSystemIntRecordDataEstimateSize(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetArray(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetBinary(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetBool(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetFloat32(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetFloat64(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetInt32(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetInt64(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetObject(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetString(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetUInt32(); +int APS5_VABI sceNpUniversalDataSystemIntRecordObjectSetUInt64(); + +} diff --git a/core/libs/prx/libSceNpUniversalDataSystem/libSceNpUniversalDataSystem.hpp b/core/libs/prx/libSceNpUniversalDataSystem/libSceNpUniversalDataSystem.hpp new file mode 100644 index 0000000000..e72fd1c696 --- /dev/null +++ b/core/libs/prx/libSceNpUniversalDataSystem/libSceNpUniversalDataSystem.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" diff --git a/core/libs/prx/libSceNpUniversalDataSystem/src/Context.cpp b/core/libs/prx/libSceNpUniversalDataSystem/src/Context.cpp index f73c4a2cb2..e6f9c0a4c2 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/src/Context.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/src/Context.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp b/core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp index be7df0ae46..12bcbd69c4 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp @@ -1,11 +1,10 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" #include #include #include #include #include #include -#include -#include #include #include @@ -21,13 +20,6 @@ Uds::Owned& events() { return registry; } -bool firstProgress(std::int32_t trophyId) { - static std::mutex mutex; - static std::set logged; - std::lock_guard lock(mutex); - return logged.insert(trophyId).second; -} - void ignore(const NpUniversalDataSystemEvent& event, int context, const std::string& reason) { std::fprintf(stderr, "[trophy] ignored %s posted to context %d: %s\n", Uds::Describe(event).c_str(), context, reason.c_str()); } @@ -38,14 +30,12 @@ void post(const NpUniversalDataSystemEvent& event, int context) { const auto trophy = Uds::RecognizeTrophyEvent(event); switch (trophy.kind) { case Uds::TrophyEventKind::None: + case Uds::TrophyEventKind::Progress: if (const auto owner = Uds::FindContext(context)) NpTrophy2ApplyEvent_nid_no_patch(owner->userId, owner->serviceLabel, Uds::StatEvent(event)); return; case Uds::TrophyEventKind::Malformed: ignore(event, context, trophy.problem); return; - case Uds::TrophyEventKind::Progress: - if (firstProgress(trophy.trophyId)) ignore(event, context, "trophy progress is not recorded"); - return; case Uds::TrophyEventKind::Unlock: if (const auto owner = Uds::FindContext(context)) NpTrophy2RecordUnlock_nid_no_patch(owner->userId, owner->serviceLabel, trophy.trophyId); else ignore(event, context, "the context does not exist"); diff --git a/core/libs/prx/libSceNpUniversalDataSystem/src/EventPropertyArray.cpp b/core/libs/prx/libSceNpUniversalDataSystem/src/EventPropertyArray.cpp index 57e985ba8f..c7552e6a4b 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/src/EventPropertyArray.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/src/EventPropertyArray.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceNpUniversalDataSystem/src/EventPropertyObject.cpp b/core/libs/prx/libSceNpUniversalDataSystem/src/EventPropertyObject.cpp index d316c1ea30..20fd5f38e1 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/src/EventPropertyObject.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/src/EventPropertyObject.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceNpUniversalDataSystem/src/Handle.cpp b/core/libs/prx/libSceNpUniversalDataSystem/src/Handle.cpp index b2aa8ce9fd..331873010e 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/src/Handle.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/src/Handle.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" #include "NpUniversalDataSystem.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" diff --git a/core/libs/prx/libSceNpUniversalDataSystem/src/System.cpp b/core/libs/prx/libSceNpUniversalDataSystem/src/System.cpp index 92f91ceedf..6bc49f4f07 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/src/System.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/src/System.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceNpUniversalDataSystem/tests/Events.cpp b/core/libs/prx/libSceNpUniversalDataSystem/tests/Events.cpp index 73128d5ff0..992af0fe45 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/tests/Events.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/tests/Events.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" #include "NpUniversalDataSystem.hpp" #include "prx/libSceNpTrophy2/include/TrophyStore.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -13,40 +14,6 @@ #include #include -extern "C" { -int APS5_VABI sceNpUniversalDataSystemCreateContext(int*, int, uint32_t, uint64_t); -int APS5_VABI sceNpUniversalDataSystemDestroyContext(int); -int APS5_VABI sceNpUniversalDataSystemCreateEvent(const char*, const NpUniversalDataSystemEventPropertyObject*, NpUniversalDataSystemEvent**, NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemDestroyEvent(NpUniversalDataSystemEvent*); -int APS5_VABI sceNpUniversalDataSystemPostEvent(int, int, const void*, uint64_t); -int APS5_VABI sceNpUniversalDataSystemCreateEventPropertyObject(NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemDestroyEventPropertyObject(NpUniversalDataSystemEventPropertyObject*); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetString(NpUniversalDataSystemEventPropertyObject*, const char*, const char*); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetInt32(NpUniversalDataSystemEventPropertyObject*, const char*, int32_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetUInt32(NpUniversalDataSystemEventPropertyObject*, const char*, uint32_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetInt64(NpUniversalDataSystemEventPropertyObject*, const char*, int64_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetUInt64(NpUniversalDataSystemEventPropertyObject*, const char*, uint64_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetFloat32(NpUniversalDataSystemEventPropertyObject*, const char*, float); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetFloat64(NpUniversalDataSystemEventPropertyObject*, const char*, double); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetBool(NpUniversalDataSystemEventPropertyObject*, const char*, bool); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetBinary(NpUniversalDataSystemEventPropertyObject*, const char*, const void*, size_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetObject(NpUniversalDataSystemEventPropertyObject*, const char*, const NpUniversalDataSystemEventPropertyObject*, NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetArray(NpUniversalDataSystemEventPropertyObject*, const char*, const NpUniversalDataSystemEventPropertyArray*, NpUniversalDataSystemEventPropertyArray**); -int APS5_VABI sceNpUniversalDataSystemCreateEventPropertyArray(NpUniversalDataSystemEventPropertyArray**); -int APS5_VABI sceNpUniversalDataSystemDestroyEventPropertyArray(NpUniversalDataSystemEventPropertyArray*); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetString(NpUniversalDataSystemEventPropertyArray*, const char*); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetInt32(NpUniversalDataSystemEventPropertyArray*, int32_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetUInt32(NpUniversalDataSystemEventPropertyArray*, uint32_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetInt64(NpUniversalDataSystemEventPropertyArray*, int64_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetUInt64(NpUniversalDataSystemEventPropertyArray*, uint64_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetFloat32(NpUniversalDataSystemEventPropertyArray*, float); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetFloat64(NpUniversalDataSystemEventPropertyArray*, double); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetBool(NpUniversalDataSystemEventPropertyArray*, bool); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetBinary(NpUniversalDataSystemEventPropertyArray*, const void*, size_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetObject(NpUniversalDataSystemEventPropertyArray*, const NpUniversalDataSystemEventPropertyObject*, NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemEventPropertyArraySetArray(NpUniversalDataSystemEventPropertyArray*, const NpUniversalDataSystemEventPropertyArray*, NpUniversalDataSystemEventPropertyArray**); -} - namespace { constexpr int InitialUser = 0x10000000; @@ -172,6 +139,7 @@ void testEvents() { check(throwsInvalidArgument([] { NpUniversalDataSystemEvent* unnamed = nullptr; sceNpUniversalDataSystemCreateEvent(nullptr, nullptr, &unnamed, nullptr); + }), "an event without a name was created"); } diff --git a/core/libs/prx/libSceNpUtility/CMakeLists.txt b/core/libs/prx/libSceNpUtility/CMakeLists.txt new file mode 100644 index 0000000000..982e5ab620 --- /dev/null +++ b/core/libs/prx/libSceNpUtility/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceNpUtility SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpUtility PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpUtility) + +if(BUILD_TESTING) + add_test_executable(guest_np_utility_offline_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpUtilityOffline.cpp) + target_include_directories(guest_np_utility_offline_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_utility_offline_tests PRIVATE libSceNpUtility) + configure_windows_unwind(guest_np_utility_offline_tests) + add_test(NAME guest_np_utility_offline COMMAND guest_np_utility_offline_tests) + set_tests_properties(guest_np_utility_offline PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_utility_offline PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpUtility/Export.cpp b/core/libs/prx/libSceNpUtility/Export.cpp index 79f5389752..78daa1fe62 100644 --- a/core/libs/prx/libSceNpUtility/Export.cpp +++ b/core/libs/prx/libSceNpUtility/Export.cpp @@ -1,10 +1,159 @@ +#include "prx/libSceNpUtility/include/Api.hpp" #include #include +#include +#include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" static constexpr int SCE_NP_ERROR_INVALID_ARGUMENT = static_cast(0x80550003); static constexpr int SCE_NP_ERROR_SIGNED_OUT = static_cast(0x80550006); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT = static_cast(0x80550704); +static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = static_cast(0x80550706); +static constexpr int SCE_NP_COMMUNITY_ERROR_ABORTED = static_cast(0x80550707); +static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast(0x8055070C); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast(0x8055070E); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_TYPE = static_cast(0x80550711); +static constexpr int SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST = static_cast(0x80550307); +static constexpr int POLL_FINISHED = 0; +static constexpr int POLL_RUNNING = 1; +static constexpr std::size_t MAX_TITLE_CONTEXTS = 32; +static constexpr std::size_t MAX_REQUESTS = 32; +static constexpr int WORD_FILTER_TITLE_CTX_ID_OFFSET = 0x01000000; +static constexpr int WORD_FILTER_REQUEST_ID_OFFSET = 0x02000000; +static constexpr std::size_t WORD_FILTER_COMMENT_MAX_LENGTH = 0x400; +static constexpr std::uint64_t ASYNC_REQUEST_PARAMETER_SIZE = 0x18; +static constexpr std::uint32_t RESOLVE_TIMEOUT_MIN = 1000000; +static constexpr std::uint32_t TRANSFER_TIMEOUT_MIN = 10000000; + +namespace { +struct Request { + int titleCtxId = 0; + bool async = false; + bool used = false; + bool aborted = false; + bool finished = false; + int result = 0; +}; + +struct Service { + Service(int firstTitleContext, int firstRequest) : nextTitleContext(firstTitleContext), nextRequest(firstRequest) {} + std::mutex mutex; + std::condition_variable finished; + std::unordered_set titleContexts; + std::unordered_map requests; + int nextTitleContext; + int nextRequest; +}; + +Service lookup(1, 1); +Service wordFilter(WORD_FILTER_TITLE_CTX_ID_OFFSET + 1, WORD_FILTER_REQUEST_ID_OFFSET + 1); + +int CreateTitleContext(Service& service, const void* selfNpId) { + if (!selfNpId) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + std::lock_guard lock(service.mutex); + if (service.titleContexts.size() >= MAX_TITLE_CONTEXTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int titleCtxId = service.nextTitleContext++; + service.titleContexts.insert(titleCtxId); + return titleCtxId; +} + +int DeleteTitleContext(Service& service, int titleCtxId) { + std::lock_guard lock(service.mutex); + if (!service.titleContexts.erase(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + std::erase_if(service.requests, [titleCtxId](const auto& entry) { return entry.second.titleCtxId == titleCtxId; }); + service.finished.notify_all(); + return 0; +} + +int CreateRequest(Service& service, int titleCtxId, bool async) { + std::lock_guard lock(service.mutex); + if (!service.titleContexts.contains(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (service.requests.size() >= MAX_REQUESTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int reqId = service.nextRequest++; + service.requests.emplace(reqId, Request{titleCtxId, async}); + return reqId; +} + +int DeleteRequest(Service& service, int reqId) { + std::lock_guard lock(service.mutex); + if (!service.requests.erase(reqId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + service.finished.notify_all(); + return 0; +} + +int StartOffline(Service& service, int reqId) { + std::lock_guard lock(service.mutex); + const auto request = service.requests.find(reqId); + if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (request->second.aborted) return SCE_NP_COMMUNITY_ERROR_ABORTED; + if (request->second.used) return SCE_NP_COMMUNITY_ERROR_INVALID_TYPE; + request->second.used = true; + request->second.finished = true; + request->second.result = SCE_NP_ERROR_SIGNED_OUT; + service.finished.notify_all(); + return request->second.async ? 0 : SCE_NP_ERROR_SIGNED_OUT; +} + +int AbortRequest(Service& service, int reqId) { + std::lock_guard lock(service.mutex); + const auto request = service.requests.find(reqId); + if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + request->second.aborted = true; + if (!request->second.finished) { + request->second.finished = true; + request->second.result = SCE_NP_COMMUNITY_ERROR_ABORTED; + service.finished.notify_all(); + } + return 0; +} + +int PollAsync(Service& service, int reqId, int* result, int synchronousError) { + if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(service.mutex); + const auto request = service.requests.find(reqId); + if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (!request->second.async) return synchronousError; + if (!request->second.finished) return POLL_RUNNING; + *result = request->second.result; + return POLL_FINISHED; +} + +int WaitAsync(Service& service, int reqId, int* result, int synchronousError) { + if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + std::unique_lock lock(service.mutex); + auto request = service.requests.find(reqId); + if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (!request->second.async) return synchronousError; + service.finished.wait(lock, [&] { + request = service.requests.find(reqId); + return request == service.requests.end() || request->second.finished; + }); + if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + *result = request->second.result; + return 0; +} + +bool ValidTimeout(std::uint32_t timeout, std::uint32_t minimum) { + return timeout == 0 || timeout >= minimum; +} + +int SetTimeout(Service& service, int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) { + if (resolveRetry < 0 || !ValidTimeout(resolveTimeout, RESOLVE_TIMEOUT_MIN) || !ValidTimeout(connTimeout, TRANSFER_TIMEOUT_MIN) || + !ValidTimeout(sendTimeout, TRANSFER_TIMEOUT_MIN) || !ValidTimeout(recvTimeout, TRANSFER_TIMEOUT_MIN)) + return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (resolveRetry == 0 && resolveTimeout == 0 && connTimeout == 0 && sendTimeout == 0 && recvTimeout == 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(service.mutex); + return service.titleContexts.contains(id) || service.requests.contains(id) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID; +} + +int CheckComment(const char* comment) { + return strnlen(comment, WORD_FILTER_COMMENT_MAX_LENGTH + 1) > WORD_FILTER_COMMENT_MAX_LENGTH ? SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT : 0; +} +} extern "C" { @@ -37,4 +186,383 @@ int APS5_VABI sceNpBandwidthTestShutdown(int context_id, void* result) { return SCE_NP_ERROR_INVALID_ARGUMENT; } +int APS5_VABI sceNpAppInfoIntAbortRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntCheckAvailability() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntCheckAvailabilityA() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntCheckAvailabilityAll() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntCheckAvailabilityAllA() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntCheckServiceAvailability() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntCheckServiceAvailabilityA() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntCheckServiceAvailabilityAll() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntCheckServiceAvailabilityAllA() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntCreateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntDestroyRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntFinalize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppInfoIntInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLink2IntAbortRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLink2IntCreateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLink2IntDestroyRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLink2IntFinalize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLink2IntGetCompatibleTitleIdList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLink2IntGetCompatibleTitleIdNum() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLink2IntInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLinkIntAbortRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLinkIntCreateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLinkIntDestroyRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLinkIntFinalize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdNum() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpAppLaunchLinkIntInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpBandwidthTestShutdownWithDetailedInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupCreateTitleCtxA() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetAbortRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetCensorComment() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetConvertJidToNpId() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetConvertNpIdToJid() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetCreateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetCreateTitleCtx() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetDeleteRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetDeleteTitleCtx() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetInitWithFunctionPointer() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetInitWithMemoryPool() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetIsInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetNpId() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetSanitizeComment() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetSetTimeout() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpLookupNetTerm() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceChecker2IntAbortRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceChecker2IntCreateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceChecker2IntDestroyRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceChecker2IntFinalize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceChecker2IntInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceChecker2IntIsSetServiceType() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceCheckerIntAbortRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceCheckerIntCreateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceCheckerIntDestroyRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceCheckerIntFinalize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceCheckerIntGetAvailability() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceCheckerIntGetAvailabilityList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceCheckerIntInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceCheckerIntIsCached() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpTitleMetadataIntAbortRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpTitleMetadataIntCreateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpTitleMetadataIntDeleteRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpTitleMetadataIntGetInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpTitleMetadataIntGetNpTitleId() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUtilityInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUtilityTerm() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpWordFilterCreateTitleCtxA() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +} + +extern "C" { + +int APS5_VABI sceNpBandwidthTestInitStart() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpLookupAbortRequest(int reqId) { + return AbortRequest(lookup, reqId); +} + +int APS5_VABI sceNpLookupCreateAsyncRequest(int titleCtxId, const std::uint64_t* param) { + if (!param || *param != ASYNC_REQUEST_PARAMETER_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return CreateRequest(lookup, titleCtxId, true); +} + +int APS5_VABI sceNpLookupCreateRequest(int titleCtxId) { + return CreateRequest(lookup, titleCtxId, false); +} + +int APS5_VABI sceNpLookupCreateTitleCtx(const void* selfNpId) { + return CreateTitleContext(lookup, selfNpId); +} + +int APS5_VABI sceNpLookupDeleteRequest(int reqId) { + return DeleteRequest(lookup, reqId); +} + +int APS5_VABI sceNpLookupDeleteTitleCtx(int titleCtxId) { + return DeleteTitleContext(lookup, titleCtxId); +} + +int APS5_VABI sceNpLookupNpId(int reqId, const NpOnlineId* onlineId, void* npId, void* option) { + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!onlineId || !npId) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + return StartOffline(lookup, reqId); +} + +int APS5_VABI sceNpLookupPollAsync(int reqId, int* result) { + return PollAsync(lookup, reqId, result, SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST); +} + +int APS5_VABI sceNpLookupSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) { + return SetTimeout(lookup, id, resolveRetry, resolveTimeout, connTimeout, sendTimeout, recvTimeout); +} + +int APS5_VABI sceNpLookupWaitAsync(int reqId, int* result) { + return WaitAsync(lookup, reqId, result, SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST); +} + +int APS5_VABI sceNpWordFilterAbortRequest(int reqId) { + return AbortRequest(wordFilter, reqId); +} + +int APS5_VABI sceNpWordFilterCensorComment(int reqId, const char* comment, void* option) { + if (!comment) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (const int error = CheckComment(comment)) return error; + return StartOffline(wordFilter, reqId); +} + +int APS5_VABI sceNpWordFilterCreateAsyncRequest(int titleCtxId, const std::uint64_t* param) { + if (!param || *param != ASYNC_REQUEST_PARAMETER_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return CreateRequest(wordFilter, titleCtxId, true); +} + +int APS5_VABI sceNpWordFilterCreateRequest(int titleCtxId) { + return CreateRequest(wordFilter, titleCtxId, false); +} + +int APS5_VABI sceNpWordFilterCreateTitleCtx(const void* selfNpId) { + return CreateTitleContext(wordFilter, selfNpId); +} + +int APS5_VABI sceNpWordFilterDeleteRequest(int reqId) { + return DeleteRequest(wordFilter, reqId); +} + +int APS5_VABI sceNpWordFilterDeleteTitleCtx(int titleCtxId) { + return DeleteTitleContext(wordFilter, titleCtxId); +} + +int APS5_VABI sceNpWordFilterPollAsync(int reqId, int* result) { + return PollAsync(wordFilter, reqId, result, SCE_NP_COMMUNITY_ERROR_INVALID_ID); +} + +int APS5_VABI sceNpWordFilterSanitizeComment(int reqId, const char* comment, char* sanitizedComment, void* option) { + if (!comment || !sanitizedComment) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (const int error = CheckComment(comment)) return error; + return StartOffline(wordFilter, reqId); +} + +int APS5_VABI sceNpWordFilterSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) { + return SetTimeout(wordFilter, id, resolveRetry, resolveTimeout, connTimeout, sendTimeout, recvTimeout); +} + +int APS5_VABI sceNpWordFilterWaitAsync(int reqId, int* result) { + return WaitAsync(wordFilter, reqId, result, SCE_NP_COMMUNITY_ERROR_INVALID_ID); +} + } diff --git a/core/libs/prx/libSceNpUtility/include/Api.hpp b/core/libs/prx/libSceNpUtility/include/Api.hpp new file mode 100644 index 0000000000..0fe054556f --- /dev/null +++ b/core/libs/prx/libSceNpUtility/include/Api.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpBandwidthTestAbort(int contextId); +int APS5_VABI sceNpBandwidthTestGetStatus(int contextId, int* status); +int APS5_VABI sceNpBandwidthTestInitStartDownload(const void* param); +int APS5_VABI sceNpBandwidthTestInitStartUpload(const void* param); +int APS5_VABI sceNpBandwidthTestShutdown(int contextId, void* result); +int APS5_VABI sceNpBandwidthTestInitStart(); +int APS5_VABI sceNpLookupCreateRequest(int titleCtxId); +int APS5_VABI sceNpLookupCreateTitleCtx(const void* selfNpId); +int APS5_VABI sceNpLookupDeleteRequest(int reqId); +int APS5_VABI sceNpLookupDeleteTitleCtx(int titleCtxId); +int APS5_VABI sceNpLookupNpId(int reqId, const NpOnlineId* onlineId, void* npId, void* option); +int APS5_VABI sceNpWordFilterAbortRequest(int reqId); +int APS5_VABI sceNpWordFilterCensorComment(int reqId, const char* comment, void* option); +int APS5_VABI sceNpWordFilterCreateAsyncRequest(int titleCtxId, const std::uint64_t* param); +int APS5_VABI sceNpWordFilterCreateRequest(int titleCtxId); +int APS5_VABI sceNpWordFilterCreateTitleCtx(const void* selfNpId); +int APS5_VABI sceNpWordFilterDeleteRequest(int reqId); +int APS5_VABI sceNpWordFilterDeleteTitleCtx(int titleCtxId); +int APS5_VABI sceNpWordFilterPollAsync(int reqId, int* result); +int APS5_VABI sceNpLookupAbortRequest(int reqId); +int APS5_VABI sceNpLookupCreateAsyncRequest(int titleCtxId, const std::uint64_t* param); +int APS5_VABI sceNpLookupPollAsync(int reqId, int* result); +int APS5_VABI sceNpLookupSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout); +int APS5_VABI sceNpLookupWaitAsync(int reqId, int* result); +int APS5_VABI sceNpWordFilterSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout); +int APS5_VABI sceNpWordFilterWaitAsync(int reqId, int* result); +int APS5_VABI sceNpWordFilterSanitizeComment(int reqId, const char* comment, char* sanitizedComment, void* option); + +int APS5_VABI sceNpAppInfoIntAbortRequest(); +int APS5_VABI sceNpAppInfoIntCheckAvailability(); +int APS5_VABI sceNpAppInfoIntCheckAvailabilityA(); +int APS5_VABI sceNpAppInfoIntCheckAvailabilityAll(); +int APS5_VABI sceNpAppInfoIntCheckAvailabilityAllA(); +int APS5_VABI sceNpAppInfoIntCheckServiceAvailability(); +int APS5_VABI sceNpAppInfoIntCheckServiceAvailabilityA(); +int APS5_VABI sceNpAppInfoIntCheckServiceAvailabilityAll(); +int APS5_VABI sceNpAppInfoIntCheckServiceAvailabilityAllA(); +int APS5_VABI sceNpAppInfoIntCreateRequest(); +int APS5_VABI sceNpAppInfoIntDestroyRequest(); +int APS5_VABI sceNpAppInfoIntFinalize(); +int APS5_VABI sceNpAppInfoIntInitialize(); +int APS5_VABI sceNpAppLaunchLink2IntAbortRequest(); +int APS5_VABI sceNpAppLaunchLink2IntCreateRequest(); +int APS5_VABI sceNpAppLaunchLink2IntDestroyRequest(); +int APS5_VABI sceNpAppLaunchLink2IntFinalize(); +int APS5_VABI sceNpAppLaunchLink2IntGetCompatibleTitleIdList(); +int APS5_VABI sceNpAppLaunchLink2IntGetCompatibleTitleIdNum(); +int APS5_VABI sceNpAppLaunchLink2IntInitialize(); +int APS5_VABI sceNpAppLaunchLinkIntAbortRequest(); +int APS5_VABI sceNpAppLaunchLinkIntCreateRequest(); +int APS5_VABI sceNpAppLaunchLinkIntDestroyRequest(); +int APS5_VABI sceNpAppLaunchLinkIntFinalize(); +int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdList(); +int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdNum(); +int APS5_VABI sceNpAppLaunchLinkIntInitialize(); +int APS5_VABI sceNpBandwidthTestShutdownWithDetailedInfo(); +int APS5_VABI sceNpLookupCreateTitleCtxA(); +int APS5_VABI sceNpLookupNetAbortRequest(); +int APS5_VABI sceNpLookupNetCensorComment(); +int APS5_VABI sceNpLookupNetConvertJidToNpId(); +int APS5_VABI sceNpLookupNetConvertNpIdToJid(); +int APS5_VABI sceNpLookupNetCreateRequest(); +int APS5_VABI sceNpLookupNetCreateTitleCtx(); +int APS5_VABI sceNpLookupNetDeleteRequest(); +int APS5_VABI sceNpLookupNetDeleteTitleCtx(); +int APS5_VABI sceNpLookupNetInit(); +int APS5_VABI sceNpLookupNetInitWithFunctionPointer(); +int APS5_VABI sceNpLookupNetInitWithMemoryPool(); +int APS5_VABI sceNpLookupNetIsInit(); +int APS5_VABI sceNpLookupNetNpId(); +int APS5_VABI sceNpLookupNetSanitizeComment(); +int APS5_VABI sceNpLookupNetSetTimeout(); +int APS5_VABI sceNpLookupNetTerm(); +int APS5_VABI sceNpServiceChecker2IntAbortRequest(); +int APS5_VABI sceNpServiceChecker2IntCreateRequest(); +int APS5_VABI sceNpServiceChecker2IntDestroyRequest(); +int APS5_VABI sceNpServiceChecker2IntFinalize(); +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability(); +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA(); +int APS5_VABI sceNpServiceChecker2IntInitialize(); +int APS5_VABI sceNpServiceChecker2IntIsSetServiceType(); +int APS5_VABI sceNpServiceCheckerIntAbortRequest(); +int APS5_VABI sceNpServiceCheckerIntCreateRequest(); +int APS5_VABI sceNpServiceCheckerIntDestroyRequest(); +int APS5_VABI sceNpServiceCheckerIntFinalize(); +int APS5_VABI sceNpServiceCheckerIntGetAvailability(); +int APS5_VABI sceNpServiceCheckerIntGetAvailabilityList(); +int APS5_VABI sceNpServiceCheckerIntInitialize(); +int APS5_VABI sceNpServiceCheckerIntIsCached(); +int APS5_VABI sceNpTitleMetadataIntAbortRequest(); +int APS5_VABI sceNpTitleMetadataIntCreateRequest(); +int APS5_VABI sceNpTitleMetadataIntDeleteRequest(); +int APS5_VABI sceNpTitleMetadataIntGetInfo(); +int APS5_VABI sceNpTitleMetadataIntGetNpTitleId(); +int APS5_VABI sceNpUtilityInit(); +int APS5_VABI sceNpUtilityTerm(); +int APS5_VABI sceNpWordFilterCreateTitleCtxA(); + +} diff --git a/core/libs/prx/libSceNpUtility/libSceNpUtility.hpp b/core/libs/prx/libSceNpUtility/libSceNpUtility.hpp new file mode 100644 index 0000000000..01af5539aa --- /dev/null +++ b/core/libs/prx/libSceNpUtility/libSceNpUtility.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpUtility/include/Api.hpp" diff --git a/core/libs/prx/libSceNpWebApi/CMakeLists.txt b/core/libs/prx/libSceNpWebApi/CMakeLists.txt new file mode 100644 index 0000000000..0e3e193000 --- /dev/null +++ b/core/libs/prx/libSceNpWebApi/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceNpWebApi SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpWebApi PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpWebApi) + +if(BUILD_TESTING) + add_test_executable(guest_np_web_api_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpWebApi.cpp) + target_include_directories(guest_np_web_api_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_web_api_tests PRIVATE libSceNpWebApi) + configure_windows_unwind(guest_np_web_api_tests) + add_test(NAME guest_np_web_api COMMAND guest_np_web_api_tests) + set_tests_properties(guest_np_web_api PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_web_api PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpWebApi/Export.cpp b/core/libs/prx/libSceNpWebApi/Export.cpp new file mode 100644 index 0000000000..269bbec537 --- /dev/null +++ b/core/libs/prx/libSceNpWebApi/Export.cpp @@ -0,0 +1,307 @@ +#include "prx/libSceNpWebApi/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include +#include +#include +#include +#include +#include +#include + +static constexpr int SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT = static_cast(0x80552902); +static constexpr int SCE_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID = static_cast(0x80552903); +static constexpr int SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND = static_cast(0x80552904); +static constexpr int SCE_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND = static_cast(0x80552905); +static constexpr int SCE_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND = static_cast(0x80552906); +static constexpr int SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN = static_cast(0x80552907); +static constexpr int SCE_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER = static_cast(0x80552908); +static constexpr int SCE_NP_WEBAPI_ERROR_ABORTED = static_cast(0x80552909); +static constexpr int SCE_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST = static_cast(0x8055290A); +static constexpr int SCE_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND = static_cast(0x8055290B); +static constexpr int SCE_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND = static_cast(0x8055290C); +static constexpr int SCE_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND = static_cast(0x8055290D); +static constexpr int SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND = static_cast(0x8055290E); +static constexpr int SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND = static_cast(0x8055290F); +static constexpr int SCE_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND = static_cast(0x80552910); +static constexpr int SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX = static_cast(0x8055291A); + +static constexpr int SCE_NP_WEBAPI_HTTP_METHOD_DELETE = 3; +static constexpr std::uint32_t SCE_NP_INVALID_SERVICE_LABEL = 0xFFFFFFFF; +static constexpr std::uint32_t SDK_VERSION_1_00 = 0x01000000; +static constexpr std::uint32_t SDK_VERSION_2_00 = 0x02000000; +static constexpr std::uint32_t SDK_VERSION_2_50 = 0x02500000; + +namespace { + +struct Request { + bool aborted; +}; + +struct UserContext { + int userId; + std::unordered_map requests; + std::unordered_set pushEventCallbacks; + std::unordered_set servicePushEventCallbacks; +}; + +struct LibraryContext { + int libHttpCtxId; + std::size_t poolSize; + std::unordered_set handles; + std::unordered_set pushEventFilters; + std::unordered_map userContexts; +}; + +struct UserContextLookup { + int error; + LibraryContext* library; + UserContext* user; +}; + +struct RequestLookup { + int error; + UserContext* user; + Request* request; +}; + +std::mutex contextsMutex; +std::unordered_map contexts; +int lastContext = 0; +int lastObject = 0; +int lastUserContext = 0; +std::int64_t lastRequest = 0; + +int nextObject() { + lastObject = lastObject % 0xefffffff + 1; + return lastObject; +} + +std::uint32_t compiledSdkVersion() { + const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t version; + std::memcpy(&version, process + 16, sizeof(version)); + return version; +} + +UserContextLookup findUserContext(int titleUserCtxId) { + const auto library = contexts.find(titleUserCtxId >> 16); + if (library == contexts.end()) return {SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND, nullptr, nullptr}; + const auto user = library->second.userContexts.find(titleUserCtxId); + if (user == library->second.userContexts.end()) return {SCE_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND, &library->second, nullptr}; + return {0, &library->second, &user->second}; +} + +RequestLookup findRequest(std::int64_t requestId) { + const auto lookup = findUserContext(static_cast(requestId >> 32)); + if (lookup.error != 0) return {lookup.error, nullptr, nullptr}; + const auto request = lookup.user->requests.find(requestId); + if (request == lookup.user->requests.end()) return {SCE_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND, lookup.user, nullptr}; + return {0, lookup.user, &request->second}; +} + +} + +extern "C" { + +int APS5_VABI sceNpWebApiAbortRequest(std::int64_t requestId) { + std::lock_guard lock(contextsMutex); + const auto lookup = findRequest(requestId); + if (lookup.error != 0) return lookup.error; + lookup.request->aborted = true; + return 0; +} + +int APS5_VABI sceNpWebApiCreateContext(int libCtxId, const void* onlineId) { + if (libCtxId >= 0x8000) return SCE_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID; + if (!onlineId) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(contextsMutex); + const auto context = contexts.find(libCtxId); + if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + auto& userContexts = context->second.userContexts; + for (const auto& [id, user] : userContexts) { + if (user.userId == USER_SERVICE_INITIAL_USER_ID) return SCE_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST; + } + lastUserContext = lastUserContext % 0xffff + 1; + const int titleUserCtxId = (libCtxId << 16) | lastUserContext; + userContexts.emplace(titleUserCtxId, UserContext{USER_SERVICE_INITIAL_USER_ID, {}, {}, {}}); + return titleUserCtxId; +} + +int APS5_VABI sceNpWebApiCreateHandle(int libCtxId) { + std::lock_guard lock(contextsMutex); + const auto context = contexts.find(libCtxId); + if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + const auto handle = nextObject(); + context->second.handles.insert(handle); + return handle; +} + +int APS5_VABI sceNpWebApiCreatePushEventFilter(int libCtxId, const void* filterParam, std::size_t filterParamNum) { + if (!filterParam || filterParamNum == 0) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(contextsMutex); + const auto context = contexts.find(libCtxId); + if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + const auto filter = nextObject(); + context->second.pushEventFilters.insert(filter); + return filter; +} + +int APS5_VABI sceNpWebApiCreateRequest(int titleUserCtxId, const char* apiGroup, const char* path, int method, const NpWebApiContentParameter* contentParameter, std::int64_t* requestId) { + if (!apiGroup || !path) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + if (contentParameter && contentParameter->contentLength != 0 && !contentParameter->contentType) return SCE_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER; + if (method > SCE_NP_WEBAPI_HTTP_METHOD_DELETE && compiledSdkVersion() >= SDK_VERSION_2_50) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(contextsMutex); + const auto lookup = findUserContext(titleUserCtxId); + if (lookup.error != 0) return lookup.error; + lastRequest = lastRequest % 0xffffffff + 1; + const std::int64_t base = static_cast(titleUserCtxId) << 32; + std::int64_t id = base | lastRequest; + while (lookup.user->requests.contains(id)) --id; + if (id <= base) throw std::runtime_error("sceNpWebApiCreateRequest: every request id of the user context is in use"); + lookup.user->requests.emplace(id, Request{false}); + if (requestId) *requestId = id; + return 0; +} + +int APS5_VABI sceNpWebApiCreateServicePushEventFilter(int libCtxId, int handleId, const char* npServiceName, std::uint32_t npServiceLabel, const void* filterParam, std::size_t filterParamNum) { + if (!npServiceName || !filterParam || filterParamNum == 0) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + if (npServiceLabel == SCE_NP_INVALID_SERVICE_LABEL && compiledSdkVersion() >= SDK_VERSION_2_00) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(contextsMutex); + const auto context = contexts.find(libCtxId); + if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + if (!context->second.handles.contains(handleId)) return SCE_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND; + return SCE_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND; +} + +int APS5_VABI sceNpWebApiDeleteContext(int titleUserCtxId) { + std::lock_guard lock(contextsMutex); + const auto lookup = findUserContext(titleUserCtxId); + if (lookup.error != 0) return lookup.error; + lookup.library->userContexts.erase(titleUserCtxId); + return 0; +} + +int APS5_VABI sceNpWebApiDeleteHandle(int libCtxId, int handleId) { + std::lock_guard lock(contextsMutex); + const auto context = contexts.find(libCtxId); + if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + return context->second.handles.erase(handleId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND; +} + +int APS5_VABI sceNpWebApiDeletePushEventFilter(int libCtxId, int filterId) { + std::lock_guard lock(contextsMutex); + const auto context = contexts.find(libCtxId); + if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + return context->second.pushEventFilters.erase(filterId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND; +} + +int APS5_VABI sceNpWebApiDeleteRequest(std::int64_t requestId) { + std::lock_guard lock(contextsMutex); + const auto lookup = findRequest(requestId); + if (lookup.error != 0) return lookup.error; + lookup.user->requests.erase(requestId); + return 0; +} + +int APS5_VABI sceNpWebApiDeleteServicePushEventFilter(int libCtxId, int filterId) { + (void)filterId; + std::lock_guard lock(contextsMutex); + if (!contexts.contains(libCtxId)) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + return SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND; +} + +int APS5_VABI sceNpWebApiGetHttpResponseHeaderValue() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpWebApiGetHttpResponseHeaderValueLength() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpWebApiGetHttpStatusCode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpWebApiInitialize(int libHttpCtxId, std::size_t poolSize) { + std::lock_guard lock(contextsMutex); + for (int attempt = 0; attempt < 0x7fff; ++attempt) { + lastContext = lastContext % 0x7fff + 1; + if (contexts.contains(lastContext)) continue; + contexts[lastContext] = {libHttpCtxId, poolSize, {}, {}, {}}; + return lastContext; + } + return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX; +} + +int APS5_VABI sceNpWebApiReadData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpWebApiRegisterPushEventCallback(int titleUserCtxId, int filterId, void* callback, void* userArg) { + (void)userArg; + if (!callback && compiledSdkVersion() >= SDK_VERSION_1_00) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(contextsMutex); + const auto lookup = findUserContext(titleUserCtxId); + if (lookup.error != 0) return lookup.error; + if (!lookup.library->pushEventFilters.contains(filterId) && compiledSdkVersion() >= SDK_VERSION_2_50) return SCE_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND; + const auto callbackId = nextObject(); + lookup.user->pushEventCallbacks.insert(callbackId); + return callbackId; +} + +int APS5_VABI sceNpWebApiRegisterServicePushEventCallback(int titleUserCtxId, int filterId, void* callback, void* userArg) { + (void)filterId; + (void)userArg; + if (!callback && compiledSdkVersion() >= SDK_VERSION_1_00) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(contextsMutex); + const auto lookup = findUserContext(titleUserCtxId); + if (lookup.error != 0) return lookup.error; + if (compiledSdkVersion() >= SDK_VERSION_2_50) return SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND; + if (!callback) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + const auto callbackId = nextObject(); + lookup.user->servicePushEventCallbacks.insert(callbackId); + return callbackId; +} + +int APS5_VABI sceNpWebApiSendRequest(std::int64_t requestId, const void* data, std::size_t dataSize) { + (void)data; + (void)dataSize; + std::lock_guard lock(contextsMutex); + const auto lookup = findRequest(requestId); + if (lookup.error != 0) return lookup.error; + if (lookup.request->aborted) return SCE_NP_WEBAPI_ERROR_ABORTED; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI sceNpWebApiTerminate(int libCtxId) { + std::lock_guard lock(contextsMutex); + return contexts.erase(libCtxId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; +} + +int APS5_VABI sceNpWebApiUnregisterPushEventCallback(int titleUserCtxId, int callbackId) { + std::lock_guard lock(contextsMutex); + const auto lookup = findUserContext(titleUserCtxId); + if (lookup.error != 0) return lookup.error; + return lookup.user->pushEventCallbacks.erase(callbackId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND; +} + +int APS5_VABI sceNpWebApiUnregisterServicePushEventCallback(int titleUserCtxId, int callbackId) { + std::lock_guard lock(contextsMutex); + const auto lookup = findUserContext(titleUserCtxId); + if (lookup.error != 0) return lookup.error; + return lookup.user->servicePushEventCallbacks.erase(callbackId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND; +} + +int APS5_VABI sceNpWebApiUtilityParseNpId() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceNpWebApi/include/Api.hpp b/core/libs/prx/libSceNpWebApi/include/Api.hpp new file mode 100644 index 0000000000..ec9ce3c836 --- /dev/null +++ b/core/libs/prx/libSceNpWebApi/include/Api.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "SceTypes.hpp" + +struct NpWebApiContentParameter { + std::uint64_t contentLength; + const char* contentType; + std::uint8_t reserved[16]; +}; + +static_assert(sizeof(NpWebApiContentParameter) == 32); + +extern "C" { + +int APS5_VABI sceNpWebApiAbortRequest(std::int64_t requestId); +int APS5_VABI sceNpWebApiCreateContext(int libCtxId, const void* onlineId); +int APS5_VABI sceNpWebApiCreateHandle(int libCtxId); +int APS5_VABI sceNpWebApiCreatePushEventFilter(int libCtxId, const void* filterParam, std::size_t filterParamNum); +int APS5_VABI sceNpWebApiCreateRequest(int titleUserCtxId, const char* apiGroup, const char* path, int method, const NpWebApiContentParameter* contentParameter, std::int64_t* requestId); +int APS5_VABI sceNpWebApiCreateServicePushEventFilter(int libCtxId, int handleId, const char* npServiceName, std::uint32_t npServiceLabel, const void* filterParam, std::size_t filterParamNum); +int APS5_VABI sceNpWebApiDeleteContext(int titleUserCtxId); +int APS5_VABI sceNpWebApiDeleteHandle(int libCtxId, int handleId); +int APS5_VABI sceNpWebApiDeletePushEventFilter(int libCtxId, int filterId); +int APS5_VABI sceNpWebApiDeleteRequest(std::int64_t requestId); +int APS5_VABI sceNpWebApiDeleteServicePushEventFilter(int libCtxId, int filterId); +int APS5_VABI sceNpWebApiGetHttpResponseHeaderValue(); +int APS5_VABI sceNpWebApiGetHttpResponseHeaderValueLength(); +int APS5_VABI sceNpWebApiGetHttpStatusCode(); +int APS5_VABI sceNpWebApiInitialize(int libHttpCtxId, std::size_t poolSize); +int APS5_VABI sceNpWebApiReadData(); +int APS5_VABI sceNpWebApiRegisterPushEventCallback(int titleUserCtxId, int filterId, void* callback, void* userArg); +int APS5_VABI sceNpWebApiRegisterServicePushEventCallback(int titleUserCtxId, int filterId, void* callback, void* userArg); +int APS5_VABI sceNpWebApiSendRequest(std::int64_t requestId, const void* data, std::size_t dataSize); +int APS5_VABI sceNpWebApiTerminate(int libCtxId); +int APS5_VABI sceNpWebApiUnregisterPushEventCallback(int titleUserCtxId, int callbackId); +int APS5_VABI sceNpWebApiUnregisterServicePushEventCallback(int titleUserCtxId, int callbackId); +int APS5_VABI sceNpWebApiUtilityParseNpId(); + +} diff --git a/core/libs/prx/libSceNpWebApi/libSceNpWebApi.hpp b/core/libs/prx/libSceNpWebApi/libSceNpWebApi.hpp new file mode 100644 index 0000000000..3688862c87 --- /dev/null +++ b/core/libs/prx/libSceNpWebApi/libSceNpWebApi.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpWebApi/include/Api.hpp" diff --git a/core/libs/prx/libSceNpWebApi2/Export.cpp b/core/libs/prx/libSceNpWebApi2/Export.cpp index 125c7fb846..66a26fe70b 100644 --- a/core/libs/prx/libSceNpWebApi2/Export.cpp +++ b/core/libs/prx/libSceNpWebApi2/Export.cpp @@ -1,8 +1,10 @@ +#include "prx/libSceNpWebApi2/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include +#include #include #include #include @@ -10,14 +12,13 @@ // The PSN web API has no service behind it: handles can be created, but requests fail. static constexpr int SCE_NP_WEBAPI2_ERROR_INVALID_ARGUMENT = static_cast(0x80553402); static constexpr int SCE_NP_WEBAPI2_ERROR_UNAVAILABLE = static_cast(0x80553406); +static constexpr int SCE_NP_WEBAPI2_ERROR_INVALID_CONTEXT = static_cast(0x80553404); +static constexpr int SCE_NP_WEBAPI2_ERROR_INVALID_HANDLE = static_cast(0x8055340D); static std::atomic g_nextHandle{1}; -struct NpWebApi2MemoryPoolStats { - size_t pool_size; - size_t max_inuse_size; - size_t current_inuse_size; - int32_t reserved; -}; +static_assert(sizeof(NpWebApi2PushContextId) == 37); + +static_assert(sizeof(NpWebApi2MemoryPoolStats) == 32); static std::mutex g_poolsMutex; static std::unordered_map g_pools; @@ -29,6 +30,14 @@ static int CreateLibraryContext(size_t poolSize) { return libCtxId; } +struct PushHandle { + std::int32_t libraryContextId; + std::uint32_t timeout = 0; + bool aborted = false; +}; + +static std::unordered_map pushHandles; + extern "C" { int APS5_VABI sceNpWebApi2AbortRequest(int64_t request_id) { @@ -43,8 +52,8 @@ int APS5_VABI sceNpWebApi2AddHttpRequestHeader(int64_t request_id, const char* f return 0; } -int APS5_VABI sceNpWebApi2AddWebTraceTag(int64_t request_id, const char* value) { - (void)request_id; +int APS5_VABI sceNpWebApi2AddWebTraceTag(std::int64_t requestId, const char* value) { + (void)requestId; if (!value) return SCE_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; return 0; } @@ -99,12 +108,13 @@ int APS5_VABI sceNpWebApi2Initialize(int lib_http_ctx_id, size_t pool_size) { return CreateLibraryContext(pool_size); } -int APS5_VABI sceNpWebApi2GetMemoryPoolStats(int lib_ctx_id, NpWebApi2MemoryPoolStats* stats) { - if (stats == nullptr) APS5_INVALID_ARG_EX; +int APS5_VABI sceNpWebApi2GetMemoryPoolStats(int libraryContextId, NpWebApi2MemoryPoolStats* stats) { std::lock_guard lock(g_poolsMutex); - const auto pool = g_pools.find(lib_ctx_id); - if (pool == g_pools.end()) throw std::invalid_argument("sceNpWebApi2GetMemoryPoolStats: unknown context"); - *stats = {pool->second, 0, 0, 0}; + const auto pool = g_pools.find(libraryContextId); + if (pool == g_pools.end()) return SCE_NP_WEBAPI2_ERROR_INVALID_CONTEXT; + if (!stats) return 0; + std::memset(stats, 0, sizeof(*stats)); + stats->pool_size = pool->second; return 0; } @@ -118,21 +128,29 @@ int APS5_VABI sceNpWebApi2PushEventCreateFilter(int lib_ctx_id, int handle_id, c return g_nextHandle.fetch_add(1, std::memory_order_relaxed); } -int APS5_VABI sceNpWebApi2PushEventCreateHandle(int lib_ctx_id) { - (void)lib_ctx_id; - return g_nextHandle.fetch_add(1, std::memory_order_relaxed); +int APS5_VABI sceNpWebApi2PushEventCreateHandle(int libraryContextId) { + std::lock_guard lock(g_poolsMutex); + if (!g_pools.contains(libraryContextId)) return SCE_NP_WEBAPI2_ERROR_INVALID_CONTEXT; + const int handleId = g_nextHandle.fetch_add(1, std::memory_order_relaxed); + pushHandles.emplace(handleId, PushHandle{libraryContextId}); + return handleId; } -int APS5_VABI sceNpWebApi2PushEventAbortHandle(int libCtxId, int handleId) { - (void)handleId; +int APS5_VABI sceNpWebApi2PushEventAbortHandle(int libraryContextId, int handleId) { std::lock_guard lock(g_poolsMutex); - if (!g_pools.contains(libCtxId)) throw std::invalid_argument("sceNpWebApi2PushEventAbortHandle: unknown context"); + if (!g_pools.contains(libraryContextId)) return SCE_NP_WEBAPI2_ERROR_INVALID_CONTEXT; + const auto handle = pushHandles.find(handleId); + if (handle == pushHandles.end() || handle->second.libraryContextId != libraryContextId) return SCE_NP_WEBAPI2_ERROR_INVALID_HANDLE; + handle->second.aborted = true; return 0; } -int APS5_VABI sceNpWebApi2PushEventDeleteHandle(int lib_ctx_id, int handle_id) { - (void)lib_ctx_id; - (void)handle_id; +int APS5_VABI sceNpWebApi2PushEventDeleteHandle(int libraryContextId, int handleId) { + std::lock_guard lock(g_poolsMutex); + if (!g_pools.contains(libraryContextId)) return SCE_NP_WEBAPI2_ERROR_INVALID_CONTEXT; + const auto handle = pushHandles.find(handleId); + if (handle == pushHandles.end() || handle->second.libraryContextId != libraryContextId) return SCE_NP_WEBAPI2_ERROR_INVALID_HANDLE; + pushHandles.erase(handle); return 0; } @@ -167,23 +185,25 @@ int APS5_VABI sceNpWebApi2SendRequest(int64_t request_id, const void* data, size int APS5_VABI sceNpWebApi2Terminate(int lib_ctx_id) { std::lock_guard lock(g_poolsMutex); + std::erase_if(pushHandles, [lib_ctx_id](const auto& item) { return item.second.libraryContextId == lib_ctx_id; }); g_pools.erase(lib_ctx_id); return 0; } -int APS5_VABI sceNpWebApi2PushEventCreatePushContext() { +int APS5_VABI sceNpWebApi2PushEventCreatePushContext(std::int32_t userContextId, NpWebApi2PushContextId* pushContextId) { return SCE_NP_WEBAPI2_ERROR_UNAVAILABLE; } -int APS5_VABI sceNpWebApi2PushEventDeleteFilter() { +int APS5_VABI sceNpWebApi2PushEventDeleteFilter(std::int32_t libraryContextId, std::int32_t filterId) { return 0; } -int APS5_VABI sceNpWebApi2PushEventSetHandleTimeout(int lib_ctx_id, int handle_id, uint32_t timeout) { - (void)handle_id; - (void)timeout; +int APS5_VABI sceNpWebApi2PushEventSetHandleTimeout(int libraryContextId, int handleId, std::uint32_t timeout) { std::lock_guard lock(g_poolsMutex); - if (!g_pools.contains(lib_ctx_id)) throw std::invalid_argument("sceNpWebApi2PushEventSetHandleTimeout: unknown context"); + if (!g_pools.contains(libraryContextId)) return SCE_NP_WEBAPI2_ERROR_INVALID_CONTEXT; + const auto handle = pushHandles.find(handleId); + if (handle == pushHandles.end() || handle->second.libraryContextId != libraryContextId) return SCE_NP_WEBAPI2_ERROR_INVALID_HANDLE; + handle->second.timeout = timeout; return 0; } @@ -191,15 +211,15 @@ int APS5_VABI sceNpWebApi2PushEventRegisterPushContextCallback() { return g_nextHandle.fetch_add(1, std::memory_order_relaxed); } -int APS5_VABI sceNpWebApi2PushEventStartPushContextCallback() { +int APS5_VABI sceNpWebApi2PushEventStartPushContextCallback(std::int32_t userContextId, const NpWebApi2PushContextId* pushContextId) { return 0; } -int APS5_VABI sceNpWebApi2PushEventUnregisterCallback() { +int APS5_VABI sceNpWebApi2PushEventUnregisterCallback(std::int32_t userContextId, std::int32_t callbackId) { return 0; } -int APS5_VABI sceNpWebApi2PushEventUnregisterPushContextCallback() { +int APS5_VABI sceNpWebApi2PushEventUnregisterPushContextCallback(std::int32_t userContextId, std::int32_t callbackId) { return 0; } diff --git a/core/libs/prx/libSceNpWebApi2/include/Api.hpp b/core/libs/prx/libSceNpWebApi2/include/Api.hpp new file mode 100644 index 0000000000..c528b23c20 --- /dev/null +++ b/core/libs/prx/libSceNpWebApi2/include/Api.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "SceTypes.hpp" + +struct NpWebApi2MemoryPoolStats { + size_t pool_size; + size_t max_inuse_size; + size_t current_inuse_size; + int32_t reserved; +}; + +struct NpWebApi2PushContextId { + char value[37]; +}; + +extern "C" { + +int APS5_VABI sceNpWebApi2AbortRequest(int64_t requestId); +int APS5_VABI sceNpWebApi2AddHttpRequestHeader(int64_t requestId, const char* fieldName, const char* value); +int APS5_VABI sceNpWebApi2AddWebTraceTag(std::int64_t requestId, const char* value); +void APS5_VABI sceNpWebApi2CheckTimeout(void); +int APS5_VABI sceNpWebApi2CreateRequest(int userContextId, const char* apiGroup, const char* path, const char* method, const void* contentParameter, int64_t* requestId); +int APS5_VABI sceNpWebApi2CreateUserContext(int libCtxId, int userId); +int APS5_VABI sceNpWebApi2DeleteRequest(int64_t requestId); +int APS5_VABI sceNpWebApi2DeleteUserContext(int userContextId); +int APS5_VABI sceNpWebApi2GetHttpResponseHeaderValue(int64_t requestId, const char* fieldName, char* value, size_t valueSize); +int APS5_VABI sceNpWebApi2GetHttpResponseHeaderValueLength(int64_t requestId, const char* fieldName, size_t* valueLength); +int APS5_VABI sceNpWebApi2Initialize(int libHttpCtxId, size_t poolSize); +int APS5_VABI sceNpWebApi2GetMemoryPoolStats(int libraryContextId, NpWebApi2MemoryPoolStats* stats); +int APS5_VABI sceNpWebApi2PushEventCreateFilter(int libCtxId, int handleId, const char* npServiceName, uint32_t npServiceLabel, const void* filterParam, size_t filterParamNum); +int APS5_VABI sceNpWebApi2PushEventCreateHandle(int libraryContextId); +int APS5_VABI sceNpWebApi2PushEventAbortHandle(int libraryContextId, int handleId); +int APS5_VABI sceNpWebApi2PushEventDeleteHandle(int libraryContextId, int handleId); +int APS5_VABI sceNpWebApi2PushEventDeletePushContext(int userContextId, const void* pushContextId); +int APS5_VABI sceNpWebApi2PushEventRegisterCallback(int userContextId, int filterId, void* callback, void* userArg); +int APS5_VABI sceNpWebApi2ReadData(int64_t requestId, void* data, size_t size); +int APS5_VABI sceNpWebApi2SendRequest(int64_t requestId, const void* data, size_t dataSize, NpWebApi2ResponseInformationOption* responseInfoOption); +int APS5_VABI sceNpWebApi2Terminate(int libCtxId); +int APS5_VABI sceNpWebApi2PushEventCreatePushContext(std::int32_t userContextId, NpWebApi2PushContextId* pushContextId); +int APS5_VABI sceNpWebApi2PushEventDeleteFilter(std::int32_t libraryContextId, std::int32_t filterId); +int APS5_VABI sceNpWebApi2PushEventSetHandleTimeout(int libraryContextId, int handleId, std::uint32_t timeout); +int APS5_VABI sceNpWebApi2PushEventRegisterPushContextCallback(); +int APS5_VABI sceNpWebApi2PushEventStartPushContextCallback(std::int32_t userContextId, const NpWebApi2PushContextId* pushContextId); +int APS5_VABI sceNpWebApi2PushEventUnregisterCallback(std::int32_t userContextId, std::int32_t callbackId); +int APS5_VABI sceNpWebApi2PushEventUnregisterPushContextCallback(std::int32_t userContextId, std::int32_t callbackId); +int APS5_VABI sceNpWebApi2SetRequestTimeout(int64_t requestId, uint32_t timeout); +int APS5_VABI sceNpWebApi2AddMultipartPart(); +int APS5_VABI sceNpWebApi2CreateMultipartRequest(int userCtxId, const char* apiGroup, const char* path, const char* method, int64_t* requestId); +int APS5_VABI sceNpWebApi2InitializeForPresence(int libHttpCtxId, uint64_t poolSize); +int APS5_VABI sceNpWebApi2IntCreateRequest(); +int APS5_VABI sceNpWebApi2IntInitialize(const NpWebApi2IntInitializeArgs* args); +int APS5_VABI sceNpWebApi2IntInitialize2(const NpWebApi2IntInitialize2Args* args); +int APS5_VABI sceNpWebApi2IntPushEventCreateCtxIndFilter(); +int APS5_VABI sceNpWebApi2IntPushEventCreateDestPs4CtxIndFilter(); +int APS5_VABI sceNpWebApi2SendMultipartRequest(int64_t requestId, int partIndex, void* data, uint64_t dataSize, void* respInfoOption); +int APS5_VABI sceNpWebApi2SetMultipartContentType(); + +} diff --git a/core/libs/prx/libSceNpWebApi2/libSceNpWebApi2.hpp b/core/libs/prx/libSceNpWebApi2/libSceNpWebApi2.hpp new file mode 100644 index 0000000000..6c299c3731 --- /dev/null +++ b/core/libs/prx/libSceNpWebApi2/libSceNpWebApi2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceNpWebApi2/include/Api.hpp" diff --git a/core/libs/prx/libSceOpusCeltDec/CMakeLists.txt b/core/libs/prx/libSceOpusCeltDec/CMakeLists.txt index 32be01d2dd..38a71d09b5 100644 --- a/core/libs/prx/libSceOpusCeltDec/CMakeLists.txt +++ b/core/libs/prx/libSceOpusCeltDec/CMakeLists.txt @@ -1,12 +1,12 @@ add_library(libSceOpusCeltDec SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp) target_include_directories(libSceOpusCeltDec PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(libSceOpusCeltDec PRIVATE ffmpeg) +target_link_libraries(libSceOpusCeltDec PRIVATE opus) configure_windows_unwind(libSceOpusCeltDec) if(BUILD_TESTING) add_test_executable(guest_opus_celt_dec_tests ${LIBS_INCLUDE_DIR}/tests/GuestOpusCeltDec.cpp) target_include_directories(guest_opus_celt_dec_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_opus_celt_dec_tests PRIVATE libSceOpusCeltDec libc) + target_link_libraries(guest_opus_celt_dec_tests PRIVATE libSceOpusCeltDec libc opus) configure_windows_unwind(guest_opus_celt_dec_tests) add_test(NAME guest_opus_celt_dec COMMAND guest_opus_celt_dec_tests) if(WIN32) diff --git a/core/libs/prx/libSceOpusCeltDec/Export.cpp b/core/libs/prx/libSceOpusCeltDec/Export.cpp index 2b04cca7be..41ef6ba1a0 100644 --- a/core/libs/prx/libSceOpusCeltDec/Export.cpp +++ b/core/libs/prx/libSceOpusCeltDec/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceOpusCeltDec/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceOpusDec/include/OpusDecoder.hpp" #include @@ -13,11 +14,11 @@ extern "C" { int APS5_VABI sceOpusCeltDecInitialize(std::uint32_t* context) { return library.Initialize(context); } -int APS5_VABI sceOpusCeltDecTerminate(std::uint32_t* context) { return library.Terminate(context); } +int APS5_VABI sceOpusCeltDecTerminate(const std::uint32_t* context) { return library.Terminate(context); } int APS5_VABI sceOpusCeltDecGetSize(int channels) { return library.GetSize(channels); } -int APS5_VABI sceOpusCeltDecCreateEx(std::uint32_t* context, void* state, int sampleRate, int channels) { +int APS5_VABI sceOpusCeltDecCreateEx(const std::uint32_t* context, void* state, int sampleRate, int channels) { return library.CreateEx(context, state, sampleRate, channels); } diff --git a/core/libs/prx/libSceOpusCeltDec/include/Api.hpp b/core/libs/prx/libSceOpusCeltDec/include/Api.hpp new file mode 100644 index 0000000000..2cbb6f7599 --- /dev/null +++ b/core/libs/prx/libSceOpusCeltDec/include/Api.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceOpusCeltDecInitialize(std::uint32_t* context); +int APS5_VABI sceOpusCeltDecTerminate(const std::uint32_t* context); +int APS5_VABI sceOpusCeltDecGetSize(int channels); +int APS5_VABI sceOpusCeltDecCreateEx(const std::uint32_t* context, void* state, int sampleRate, int channels); +int APS5_VABI sceOpusCeltDecDecode(void* state, const std::uint8_t* data, int bytes, std::int16_t* pcm, int capacity); +int APS5_VABI sceOpusCeltDecDestroy(void* state); +int APS5_VABI sceOpusCeltDecCreate(); +int APS5_VABI sceOpusCeltDecCtl(); +int APS5_VABI sceOpusCeltDecDecodeFloat(); +int APS5_VABI sceOpusCeltDecDecodeFloatWithPriority(); +int APS5_VABI sceOpusCeltDecDecodeWithPriority(); + +} diff --git a/core/libs/prx/libSceOpusCeltDec/libSceOpusCeltDec.hpp b/core/libs/prx/libSceOpusCeltDec/libSceOpusCeltDec.hpp new file mode 100644 index 0000000000..47b7b5511c --- /dev/null +++ b/core/libs/prx/libSceOpusCeltDec/libSceOpusCeltDec.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceOpusCeltDec/include/Api.hpp" diff --git a/core/libs/prx/libSceOpusCeltEnc/Export.cpp b/core/libs/prx/libSceOpusCeltEnc/Export.cpp index df4e7d1854..5f837e73e4 100644 --- a/core/libs/prx/libSceOpusCeltEnc/Export.cpp +++ b/core/libs/prx/libSceOpusCeltEnc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceOpusCeltEnc/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,57 +6,57 @@ extern "C" { -int APS5_VABI sceOpusCeltEncCreate() { +int APS5_VABI sceOpusCeltEncCreate(OpusCeltEncoder* encoder, std::int32_t sampleRate, std::int32_t channels, std::int32_t application) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncCreateEx() { +int APS5_VABI sceOpusCeltEncCreateEx(const std::uint32_t* contextId, OpusCeltEncoder* encoder, std::int32_t sampleRate, std::int32_t channels, std::int32_t application) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncCtl() { +int APS5_VABI sceOpusCeltEncCtl(OpusCeltEncoder* encoder, std::int32_t request, ...) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncDestroy() { +int APS5_VABI sceOpusCeltEncDestroy(OpusCeltEncoder* encoder) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncEncode() { +int APS5_VABI sceOpusCeltEncEncode(OpusCeltEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncEncodeFloat() { +int APS5_VABI sceOpusCeltEncEncodeFloat(OpusCeltEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncEncodeFloatWithPriority() { +int APS5_VABI sceOpusCeltEncEncodeFloatWithPriority(OpusCeltEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncEncodeWithPriority() { +int APS5_VABI sceOpusCeltEncEncodeWithPriority(OpusCeltEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncGetSize() { +int APS5_VABI sceOpusCeltEncGetSize(std::int32_t channels) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncInitialize() { +int APS5_VABI sceOpusCeltEncInitialize(std::uint32_t* contextId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusCeltEncTerminate() { +int APS5_VABI sceOpusCeltEncTerminate(const std::uint32_t* contextId) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceOpusCeltEnc/include/Api.hpp b/core/libs/prx/libSceOpusCeltEnc/include/Api.hpp new file mode 100644 index 0000000000..5a38483d1d --- /dev/null +++ b/core/libs/prx/libSceOpusCeltEnc/include/Api.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "SceTypes.hpp" + +struct OpusCeltEncoder; + +extern "C" { + +int APS5_VABI sceOpusCeltEncCreate(OpusCeltEncoder* encoder, std::int32_t sampleRate, std::int32_t channels, std::int32_t application); +int APS5_VABI sceOpusCeltEncCreateEx(const std::uint32_t* contextId, OpusCeltEncoder* encoder, std::int32_t sampleRate, std::int32_t channels, std::int32_t application); +int APS5_VABI sceOpusCeltEncCtl(OpusCeltEncoder* encoder, std::int32_t request, ...); +int APS5_VABI sceOpusCeltEncDestroy(OpusCeltEncoder* encoder); +int APS5_VABI sceOpusCeltEncEncode(OpusCeltEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes); +int APS5_VABI sceOpusCeltEncEncodeFloat(OpusCeltEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes); +int APS5_VABI sceOpusCeltEncEncodeFloatWithPriority(OpusCeltEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority); +int APS5_VABI sceOpusCeltEncEncodeWithPriority(OpusCeltEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority); +int APS5_VABI sceOpusCeltEncGetSize(std::int32_t channels); +int APS5_VABI sceOpusCeltEncInitialize(std::uint32_t* contextId); +int APS5_VABI sceOpusCeltEncTerminate(const std::uint32_t* contextId); + +} diff --git a/core/libs/prx/libSceOpusCeltEnc/libSceOpusCeltEnc.hpp b/core/libs/prx/libSceOpusCeltEnc/libSceOpusCeltEnc.hpp new file mode 100644 index 0000000000..9a54c5f490 --- /dev/null +++ b/core/libs/prx/libSceOpusCeltEnc/libSceOpusCeltEnc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceOpusCeltEnc/include/Api.hpp" diff --git a/core/libs/prx/libSceOpusDec/CMakeLists.txt b/core/libs/prx/libSceOpusDec/CMakeLists.txt index 4179bce7ff..01bf008dd3 100644 --- a/core/libs/prx/libSceOpusDec/CMakeLists.txt +++ b/core/libs/prx/libSceOpusDec/CMakeLists.txt @@ -1,12 +1,12 @@ add_library(libSceOpusDec SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp) target_include_directories(libSceOpusDec PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(libSceOpusDec PRIVATE ffmpeg) +target_link_libraries(libSceOpusDec PRIVATE opus) configure_windows_unwind(libSceOpusDec) if(BUILD_TESTING) add_test_executable(guest_opus_dec_tests ${LIBS_INCLUDE_DIR}/tests/GuestOpusDec.cpp) target_include_directories(guest_opus_dec_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_opus_dec_tests PRIVATE libSceOpusDec libc) + target_link_libraries(guest_opus_dec_tests PRIVATE libSceOpusDec libc opus) configure_windows_unwind(guest_opus_dec_tests) add_test(NAME guest_opus_dec COMMAND guest_opus_dec_tests) if(WIN32) diff --git a/core/libs/prx/libSceOpusDec/Export.cpp b/core/libs/prx/libSceOpusDec/Export.cpp index a02f1d05be..f190172797 100644 --- a/core/libs/prx/libSceOpusDec/Export.cpp +++ b/core/libs/prx/libSceOpusDec/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceOpusDec/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceOpusDec/include/OpusDecoder.hpp" #include @@ -13,11 +14,11 @@ extern "C" { int APS5_VABI sceOpusDecInitialize(std::uint32_t* context) { return library.Initialize(context); } -int APS5_VABI sceOpusDecTerminate(std::uint32_t* context) { return library.Terminate(context); } +int APS5_VABI sceOpusDecTerminate(const std::uint32_t* context) { return library.Terminate(context); } int APS5_VABI sceOpusDecGetSize(int channels) { return library.GetSize(channels); } -int APS5_VABI sceOpusDecCreateEx(std::uint32_t* context, void* state, int sampleRate, int channels) { +int APS5_VABI sceOpusDecCreateEx(const std::uint32_t* context, void* state, int sampleRate, int channels) { return library.CreateEx(context, state, sampleRate, channels); } diff --git a/core/libs/prx/libSceOpusDec/include/Api.hpp b/core/libs/prx/libSceOpusDec/include/Api.hpp new file mode 100644 index 0000000000..3bf7ca2885 --- /dev/null +++ b/core/libs/prx/libSceOpusDec/include/Api.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceOpusDecInitialize(std::uint32_t* context); +int APS5_VABI sceOpusDecTerminate(const std::uint32_t* context); +int APS5_VABI sceOpusDecGetSize(int channels); +int APS5_VABI sceOpusDecCreateEx(const std::uint32_t* context, void* state, int sampleRate, int channels); +int APS5_VABI sceOpusDecDecode(void* state, const std::uint8_t* data, int bytes, std::int16_t* pcm, int capacity); +int APS5_VABI sceOpusDecDestroy(void* state); +int APS5_VABI sceOpusDecCancel(); +int APS5_VABI sceOpusDecCreate(); +int APS5_VABI sceOpusDecCtl(); +int APS5_VABI sceOpusDecDecodeFloat(); +int APS5_VABI sceOpusDecDecodeFloatWithPriority(); +int APS5_VABI sceOpusDecDecodeFloatWithPriorityAndTimeout(); +int APS5_VABI sceOpusDecDecodeWithPriority(); +int APS5_VABI sceOpusDecDecodeWithPriorityAndTimeout(); + +} diff --git a/core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp b/core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp index eef8d86d52..fa9cf66b31 100644 --- a/core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp +++ b/core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp @@ -1,12 +1,9 @@ #ifndef CORE_LIBS_PRX_LIBSCEOPUSDEC_INCLUDE_OPUSDECODER_HPP #define CORE_LIBS_PRX_LIBSCEOPUSDEC_INCLUDE_OPUSDECODER_HPP -#include -#include #include #include #include -#include #include #include #include @@ -15,10 +12,7 @@ #include #include -extern "C" { -#include -#include -} +#include namespace { @@ -27,7 +21,7 @@ public: OpusDecoderLibrary(const char* libraryName, bool celtPacketsOnly) : name(libraryName), celtOnly(celtPacketsOnly) {} int Initialize(std::uint32_t* context) { - if (!context) fail("null context output"); + if (!context) return error(13); std::lock_guard lock(mutex); for (const auto& [id, owner] : contexts) if (owner == context) fail("context already initialized"); @@ -38,7 +32,7 @@ public: return 0; } - int Terminate(std::uint32_t* context) { + int Terminate(const std::uint32_t* context) { if (!context) fail("null context"); std::lock_guard lock(mutex); if (!contexts.contains(*context)) fail("unknown context"); @@ -49,15 +43,15 @@ public: } int GetSize(int channels) { - if (channels != 1 && channels != 2) fail("only mono and stereo are supported"); + if (channels != 1 && channels != 2) return error(3); return 640; } - int CreateEx(std::uint32_t* context, void* state, int sampleRate, int channels) { - if (!context || !state) fail("null context or state"); - GetSize(channels); - if (sampleRate != 8000 && sampleRate != 12000 && sampleRate != 16000 && sampleRate != 24000 && sampleRate != 48000) - fail("sample rates other than 8000, 12000, 16000, 24000 and 48000 Hz are not supported"); + int CreateEx(const std::uint32_t* context, void* state, int sampleRate, int channels) { + if (!context || !state) return error(0); + if (celtOnly && channels != 1 && channels != 2) return error(3); + if (sampleRate != 8000 && sampleRate != 12000 && sampleRate != 16000 && sampleRate != 24000 && sampleRate != 48000) return error(2); + if (channels != 1 && channels != 2) return error(3); std::lock_guard lock(mutex); if (!contexts.contains(*context)) fail("unknown context"); if (decoders.contains(state)) fail("state already owns a decoder"); @@ -65,78 +59,42 @@ public: decoder->context = *context; decoder->channels = channels; decoder->sampleRate = sampleRate; - const auto* codec = avcodec_find_decoder(AV_CODEC_ID_OPUS); - if (!codec) fail("FFmpeg Opus decoder unavailable"); - decoder->codec = avcodec_alloc_context3(codec); - if (!decoder->codec) throw std::bad_alloc(); - av_channel_layout_default(&decoder->codec->ch_layout, channels); - decoder->codec->sample_rate = 48000; - if (avcodec_open2(decoder->codec, codec, nullptr) < 0) fail("cannot open FFmpeg decoder"); - if (sampleRate != 48000) { - AVChannelLayout layout{}; - av_channel_layout_default(&layout, channels); - if (swr_alloc_set_opts2(&decoder->resampler, &layout, AV_SAMPLE_FMT_FLT, sampleRate, &layout, AV_SAMPLE_FMT_FLT, 48000, 0, nullptr) < 0 || - swr_init(decoder->resampler) < 0) - fail("cannot open FFmpeg resampler"); - } + int error = OPUS_OK; + decoder->codec.reset(opus_decoder_create(sampleRate, channels, &error)); + if (error == OPUS_ALLOC_FAIL) throw std::bad_alloc(); + if (!decoder->codec || error != OPUS_OK) fail("cannot create libopus decoder"); decoders.emplace(state, std::move(decoder)); return 0; } int Decode(void* state, const std::uint8_t* data, int bytes, std::int16_t* pcm, int capacity) { - if (!data || bytes <= 0 || bytes > std::numeric_limits::max() - AV_INPUT_BUFFER_PADDING_SIZE || !pcm || capacity < 0) - fail("invalid packet or PCM buffer; packet loss concealment is not implemented"); + if (!state) return error(0); + if (bytes != 0 && !data) return error(6); + if (bytes < 0) return error(7); + if (!pcm) return error(8); + if (capacity < 0) return error(9); std::lock_guard lock(mutex); const auto found = decoders.find(state); - if (found == decoders.end()) fail("unknown decoder state"); + if (found == decoders.end()) return error(1); + if (bytes == 0) fail("packet loss concealment is not implemented"); auto& decoder = *found->second; if (celtOnly && (data[0] >> 3) < 16) fail("SILK and hybrid packets are not implemented"); - const int samples = packetSamples(data, bytes) / (48000 / decoder.sampleRate); + const int samples = opus_packet_get_nb_samples(data, bytes, decoder.sampleRate); + if (samples <= 0) fail("invalid packet duration"); const int outputBytes = samples * decoder.channels * 2; - if (capacity / 2 * 2 < outputBytes) fail("PCM buffer is too small"); - std::unique_ptr packet(av_packet_alloc(), [](AVPacket* p) { av_packet_free(&p); }); - std::unique_ptr frame(av_frame_alloc(), [](AVFrame* f) { av_frame_free(&f); }); - if (!packet || !frame || av_new_packet(packet.get(), bytes) < 0) throw std::bad_alloc(); - std::memcpy(packet->data, data, bytes); - if (avcodec_send_packet(decoder.codec, packet.get()) < 0 || avcodec_receive_frame(decoder.codec, frame.get()) < 0) - fail("FFmpeg rejected the packet"); - const auto format = static_cast(frame->format); - if (frame->ch_layout.nb_channels != decoder.channels || frame->sample_rate != 48000 || - (format != AV_SAMPLE_FMT_FLTP && format != AV_SAMPLE_FMT_FLT)) - fail("unexpected FFmpeg PCM format"); - std::vector decoded; - decoded.reserve(static_cast(frame->nb_samples) * decoder.channels); - for (int sample = 0; sample < frame->nb_samples; ++sample) { - for (int channel = 0; channel < decoder.channels; ++channel) { - const auto* source = reinterpret_cast(frame->extended_data[format == AV_SAMPLE_FMT_FLTP ? channel : 0]); - decoded.push_back(source[format == AV_SAMPLE_FMT_FLTP ? sample : sample * decoder.channels + channel]); - } - } - if (decoder.resampler) { - const int capacitySamples = swr_get_out_samples(decoder.resampler, frame->nb_samples); - if (capacitySamples < 0) fail("FFmpeg resampler rejected the frame"); - std::vector resampled(static_cast(capacitySamples) * decoder.channels); - auto* output = reinterpret_cast(resampled.data()); - const auto* input = reinterpret_cast(decoded.data()); - const int converted = swr_convert(decoder.resampler, &output, capacitySamples, &input, frame->nb_samples); - if (converted < 0) fail("FFmpeg resampler rejected the frame"); - resampled.resize(static_cast(converted) * decoder.channels); - decoded = std::move(resampled); - } - decoder.pending.insert(decoder.pending.end(), decoded.begin(), decoded.end()); - const auto count = static_cast(samples) * decoder.channels; - if (decoder.pending.size() < count) decoder.pending.insert(decoder.pending.begin(), count - decoder.pending.size(), 0.0f); - for (std::size_t index = 0; index < count; ++index) { - const auto converted = static_cast(std::lrint(std::clamp(decoder.pending[index] * 32768.0f, -32768.0f, 32767.0f))); - std::memcpy(reinterpret_cast(pcm) + index * 2, &converted, 2); - } - decoder.pending.erase(decoder.pending.begin(), decoder.pending.begin() + static_cast(count)); + if (capacity < outputBytes) fail("PCM buffer is too small"); + std::vector decoded(static_cast(samples) * decoder.channels); + const int result = opus_decode(decoder.codec.get(), data, bytes, decoded.data(), samples, 0); + if (result < 0) fail("libopus rejected the packet"); + if (result != samples) fail("unexpected libopus PCM duration"); + std::memcpy(pcm, decoded.data(), static_cast(outputBytes)); return outputBytes; } int Destroy(void* state) { + if (!state) return error(0); std::lock_guard lock(mutex); - if (decoders.erase(state) == 0) fail("unknown decoder state"); + if (decoders.erase(state) == 0) return error(1); return 0; } @@ -145,13 +103,7 @@ private: std::uint32_t context; int channels; int sampleRate; - AVCodecContext* codec = nullptr; - SwrContext* resampler = nullptr; - std::vector pending; - ~Decoder() { - swr_free(&resampler); - avcodec_free_context(&codec); - } + std::unique_ptr codec{nullptr, opus_decoder_destroy}; }; const char* name; @@ -161,25 +113,12 @@ private: std::unordered_map> decoders; std::uint32_t nextContext = 1; - [[noreturn]] void fail(const char* message) const { - throw std::runtime_error(std::string(name) + ": " + message); + int error(std::uint32_t offset) const { + return static_cast((celtOnly ? 0x81400000u : 0x81740000u) + offset); } - int packetSamples(const std::uint8_t* packet, int size) const { - const unsigned config = packet[0] >> 3; - const int frameSamples = config < 12 ? ((config & 3) == 3 ? 2880 : 480 << (config & 3)) - : config < 16 ? 480 << (config & 1) : 120 << (config & 3); - int frames = 1; - switch (packet[0] & 3) { - case 1: - case 2: frames = 2; break; - case 3: - if (size < 2) fail("truncated packet frame count"); - frames = packet[1] & 63; - break; - } - if (frames == 0 || frames * frameSamples > 5760) fail("invalid packet duration"); - return frames * frameSamples; + [[noreturn]] void fail(const char* message) const { + throw std::runtime_error(std::string(name) + ": " + message); } }; diff --git a/core/libs/prx/libSceOpusDec/libSceOpusDec.hpp b/core/libs/prx/libSceOpusDec/libSceOpusDec.hpp new file mode 100644 index 0000000000..fd17892819 --- /dev/null +++ b/core/libs/prx/libSceOpusDec/libSceOpusDec.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceOpusDec/include/Api.hpp" diff --git a/core/libs/prx/libSceOpusSilkEnc/Export.cpp b/core/libs/prx/libSceOpusSilkEnc/Export.cpp index 201dab2e4b..71bb17fe68 100644 --- a/core/libs/prx/libSceOpusSilkEnc/Export.cpp +++ b/core/libs/prx/libSceOpusSilkEnc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceOpusSilkEnc/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,72 +6,72 @@ extern "C" { -int APS5_VABI sceOpusSilkEncCancel() { +int APS5_VABI sceOpusSilkEncCancel(OpusSilkEncoder* encoder, std::uint32_t batchId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncCreate() { +int APS5_VABI sceOpusSilkEncCreate(OpusSilkEncoder* encoder, std::int32_t sampleRate, std::int32_t channels, std::int32_t application) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncCreateEx() { +int APS5_VABI sceOpusSilkEncCreateEx(const std::uint32_t* contextId, OpusSilkEncoder* encoder, std::int32_t sampleRate, std::int32_t channels, std::int32_t application) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncCtl() { +int APS5_VABI sceOpusSilkEncCtl(OpusSilkEncoder* encoder, std::int32_t request, ...) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncDestroy() { +int APS5_VABI sceOpusSilkEncDestroy(OpusSilkEncoder* encoder) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncEncode() { +int APS5_VABI sceOpusSilkEncEncode(OpusSilkEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncEncodeFloat() { +int APS5_VABI sceOpusSilkEncEncodeFloat(OpusSilkEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncEncodeFloatWithPriority() { +int APS5_VABI sceOpusSilkEncEncodeFloatWithPriority(OpusSilkEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncEncodeFloatWithPriorityAndTimeout() { +int APS5_VABI sceOpusSilkEncEncodeFloatWithPriorityAndTimeout(OpusSilkEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority, std::uint32_t timeout, std::uint32_t* batchId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncEncodeWithPriority() { +int APS5_VABI sceOpusSilkEncEncodeWithPriority(OpusSilkEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncEncodeWithPriorityAndTimeout() { +int APS5_VABI sceOpusSilkEncEncodeWithPriorityAndTimeout(OpusSilkEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority, std::uint32_t timeout, std::uint32_t* batchId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncGetSize() { +int APS5_VABI sceOpusSilkEncGetSize(std::int32_t channels) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncInitialize() { +int APS5_VABI sceOpusSilkEncInitialize(std::uint32_t* contextId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceOpusSilkEncTerminate() { +int APS5_VABI sceOpusSilkEncTerminate(const std::uint32_t* contextId) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceOpusSilkEnc/include/Api.hpp b/core/libs/prx/libSceOpusSilkEnc/include/Api.hpp new file mode 100644 index 0000000000..01202c6ad9 --- /dev/null +++ b/core/libs/prx/libSceOpusSilkEnc/include/Api.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "SceTypes.hpp" + +struct OpusSilkEncoder; + +extern "C" { + +int APS5_VABI sceOpusSilkEncCancel(OpusSilkEncoder* encoder, std::uint32_t batchId); +int APS5_VABI sceOpusSilkEncCreate(OpusSilkEncoder* encoder, std::int32_t sampleRate, std::int32_t channels, std::int32_t application); +int APS5_VABI sceOpusSilkEncCreateEx(const std::uint32_t* contextId, OpusSilkEncoder* encoder, std::int32_t sampleRate, std::int32_t channels, std::int32_t application); +int APS5_VABI sceOpusSilkEncCtl(OpusSilkEncoder* encoder, std::int32_t request, ...); +int APS5_VABI sceOpusSilkEncDestroy(OpusSilkEncoder* encoder); +int APS5_VABI sceOpusSilkEncEncode(OpusSilkEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes); +int APS5_VABI sceOpusSilkEncEncodeFloat(OpusSilkEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes); +int APS5_VABI sceOpusSilkEncEncodeFloatWithPriority(OpusSilkEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority); +int APS5_VABI sceOpusSilkEncEncodeFloatWithPriorityAndTimeout(OpusSilkEncoder* encoder, const float* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority, std::uint32_t timeout, std::uint32_t* batchId); +int APS5_VABI sceOpusSilkEncEncodeWithPriority(OpusSilkEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority); +int APS5_VABI sceOpusSilkEncEncodeWithPriorityAndTimeout(OpusSilkEncoder* encoder, const std::int16_t* samples, std::int32_t frameSize, std::uint8_t* data, std::int32_t maxDataBytes, std::uint32_t priority, std::uint32_t timeout, std::uint32_t* batchId); +int APS5_VABI sceOpusSilkEncGetSize(std::int32_t channels); +int APS5_VABI sceOpusSilkEncInitialize(std::uint32_t* contextId); +int APS5_VABI sceOpusSilkEncTerminate(const std::uint32_t* contextId); + +} diff --git a/core/libs/prx/libSceOpusSilkEnc/libSceOpusSilkEnc.hpp b/core/libs/prx/libSceOpusSilkEnc/libSceOpusSilkEnc.hpp new file mode 100644 index 0000000000..c11f4f27cd --- /dev/null +++ b/core/libs/prx/libSceOpusSilkEnc/libSceOpusSilkEnc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceOpusSilkEnc/include/Api.hpp" diff --git a/core/libs/prx/libSceOrbisCompat/libSceOrbisCompat.hpp b/core/libs/prx/libSceOrbisCompat/libSceOrbisCompat.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceOrbisCompat/libSceOrbisCompat.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp index 362519358f..0787a07dbf 100644 --- a/core/libs/prx/libScePad/Export.cpp +++ b/core/libs/prx/libScePad/Export.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -11,77 +12,163 @@ #include "prx/libScePad/include/Pad.hpp" #include "prx/libScePad/include/PadState.hpp" +static_assert(sizeof(PadVrControllerData) == 104); +static_assert(offsetof(PadVrControllerData, triggers) == 8); +static_assert(offsetof(PadVrControllerData, touchButtons) == 12); +static_assert(offsetof(PadVrControllerData, acceleration) == 20); +static_assert(offsetof(PadVrControllerData, angularVelocity) == 32); +static_assert(offsetof(PadVrControllerData, orientation) == 44); +static_assert(offsetof(PadVrControllerData, connected) == 64); +static_assert(offsetof(PadVrControllerData, timestamp) == 72); + +static_assert(sizeof(PadVirtualTriggerInternalState) == 12); +static_assert(offsetof(PadVirtualTriggerInternalState, mode) == 8); +static_assert(offsetof(PadVirtualTriggerInternalState, flags) == 9); + +static_assert(sizeof(PadLightBarBlinkingParameter) == 3); +static_assert(sizeof(PadButtonRemappingInfo) == 16); +static_assert(offsetof(PadButtonRemappingInfo, mode) == 15); + namespace { constexpr int PAD_ERROR_DEVICE_NOT_CONNECTED = static_cast(0x80920007); constexpr int PAD_ERROR_DEVICE_NO_HANDLE = static_cast(0x80920008); +constexpr int PAD_ERROR_ALREADY_OPENED = static_cast(0x80920004); +constexpr int PAD_ERROR_UNKNOWN_USER = static_cast(0x809b0081); +constexpr int PAD_ERROR_INVALID_USER_ID = static_cast(0x809b0001); +constexpr int PAD_ERROR_INVALID_PORT_INDEX = static_cast(0x809b0003); +constexpr int PAD_USER_ID_INVALID = -1; -bool g_opened = false; +std::array g_opened{false}; +std::array g_everOpened{false}; +bool g_remoteOpened = false; +std::array g_specialUsers{}; std::atomic_bool g_initialized{false}; +bool SpecialHandle(int handle) { + const int slot = handle - PAD_SPECIAL_HANDLE; + return slot >= 0 && slot < PAD_SPECIAL_HANDLE_COUNT && g_specialUsers[slot] != 0; +} + +int SpecialHandleOf(int userId) { + for (int slot = 0; slot < PAD_SPECIAL_HANDLE_COUNT; ++slot) { + if (g_specialUsers[slot] == userId) return PAD_SPECIAL_HANDLE + slot; + } + return PAD_ERROR_DEVICE_NO_HANDLE; +} + +int PortOf(int handle) { + const int port = handle - PAD_HANDLE_BASE; + if (port < 0 || port >= PAD_MAX_CONTROLLERS || !g_everOpened[port]) return -1; + return port; +} + +bool KnownHandle(int handle) { + return PortOf(handle) >= 0 || SpecialHandle(handle); +} + +PadData SpecialData() { + PadData data{}; + data.left_stick_x = data.left_stick_y = data.right_stick_x = data.right_stick_y = 0x80; + data.orientation_w = 1.0f; + data.acceleration_y = 1.0f; + return data; +} + +bool SystemRemote(int userId, int type) { + return userId == PAD_USER_ID_SYSTEM && type == PAD_PORT_TYPE_REMOTE; +} + +bool RemoteHandle(int handle) { + return g_remoteOpened && handle == PAD_REMOTE_HANDLE; +} + +int PortForUser(int userId, int type, int index) { + if (index != 0) return -1; + if (type == PAD_PORT_TYPE_STANDARD) { + const int port = userId - 0x10000000; + if (port >= 0 && port < PAD_MAX_CONTROLLERS) return port; + } + return -1; +} + bool ValidPort(int userId, int type, int index) { - const bool personalPort = type == PAD_PORT_TYPE_STANDARD || type == PAD_PORT_TYPE_SPECIAL; - const bool systemRemote = userId == PAD_USER_ID_SYSTEM && type == PAD_PORT_TYPE_REMOTE; - return index == 0 && (personalPort || systemRemote); + if (index != 0) return false; + if (type == PAD_PORT_TYPE_SPECIAL || SystemRemote(userId, type)) return true; + return PortForUser(userId, type, index) >= 0; } } +static_assert(sizeof(PadUpdateData) == 32776); +static_assert(offsetof(PadUpdateData, data) == 8); + +static_assert(sizeof(PadMotionSensorPosition) == 12); + extern "C" { int APS5_VABI scePadClose_nid_postfix(int handle) { - if (handle != PAD_HANDLE) { - return PAD_ERROR_INVALID_HANDLE; + if (RemoteHandle(handle)) { + g_remoteOpened = false; + return PAD_OK; } - g_opened = false; + if (SpecialHandle(handle)) { + g_specialUsers[handle - PAD_SPECIAL_HANDLE] = 0; + return PAD_OK; + } + const int port = PortOf(handle); + if (port < 0) return PAD_ERROR_INVALID_HANDLE; + g_opened[port] = false; return PAD_OK; } -int APS5_VABI scePadVrControllerSetVibration() { +int APS5_VABI scePadVrControllerSetVibration(std::int32_t handle, std::uint8_t intensity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVrControllerSetVibrationMode() { +int APS5_VABI scePadVrControllerSetVibrationMode(std::int32_t handle, std::uint32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVrControllerReadCombinedState() { +int APS5_VABI scePadVrControllerReadCombinedState(std::int32_t leftHandle, std::int32_t rightHandle, PadVrControllerCombinedState* state) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI scePadDeviceClassGetExtendedInformation(int handle, PadDeviceClassExtendedInformation* info) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (info == nullptr) return PAD_ERROR_INVALID_ARG; std::memset(info, 0, sizeof(*info)); - info->deviceClass = PAD_DEVICE_CLASS_STANDARD; + info->deviceClass = SpecialHandle(handle) ? -1 : PAD_DEVICE_CLASS_STANDARD; return PAD_OK; } int APS5_VABI scePadDeviceClassParseData(int handle, const PadData* data, PadDeviceClassData* class_data) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (data == nullptr || class_data == nullptr) return PAD_ERROR_INVALID_ARG; std::memset(class_data, 0, sizeof(*class_data)); + if (SpecialHandle(handle)) return PAD_OK; class_data->deviceClass = PAD_DEVICE_CLASS_STANDARD; class_data->dataValid = data->connected; return PAD_OK; } int APS5_VABI scePadGetControllerInformation(int handle, PadControllerInformation* info) { - if (handle != PAD_HANDLE) { + if (!KnownHandle(handle)) { return PAD_ERROR_INVALID_HANDLE; } if (info == nullptr) { return PAD_ERROR_INVALID_ARG; } std::memset(info, 0, sizeof(*info)); + if (SpecialHandle(handle)) return PAD_OK; info->touchPadInfo.pixelDensity = 44.86f; info->touchPadInfo.resolution.x = kTouchPadWidth; info->touchPadInfo.resolution.y = kTouchPadHeight; - info->stickInfo.deadZoneLeft = 2; - info->stickInfo.deadZoneRight = 2; + info->stickInfo.deadZoneLeft = 13; + info->stickInfo.deadZoneRight = 13; info->connectionType = PAD_CONNECTION_TYPE_LOCAL; info->connectedCount = 1; info->connected = true; @@ -90,42 +177,65 @@ int APS5_VABI scePadGetControllerInformation(int handle, PadControllerInformatio } int APS5_VABI scePadGetHandle(int user_id, int type, int index) { - if (!g_opened || !ValidPort(user_id, type, index) || type == PAD_PORT_TYPE_SPECIAL) return PAD_ERROR_DEVICE_NO_HANDLE; - return PAD_HANDLE; + if (!ValidPort(user_id, type, index)) return PAD_ERROR_DEVICE_NO_HANDLE; + if (type == PAD_PORT_TYPE_SPECIAL) return SpecialHandleOf(user_id); + if (SystemRemote(user_id, type)) return g_remoteOpened ? PAD_REMOTE_HANDLE : PAD_ERROR_DEVICE_NO_HANDLE; + const int port = PortForUser(user_id, type, index); + if (port < 0 || !g_opened[port]) return PAD_ERROR_DEVICE_NO_HANDLE; + return PAD_HANDLE_BASE + port; } int APS5_VABI scePadGetInfo_nid_postfix(PadInfo* info) { if (info == nullptr) return PAD_ERROR_INVALID_ARG; std::memset(info, 0, sizeof(*info)); - info->maxConnectCount = 4; - info->connectedCount[0] = 1; + info->maxConnectCount = PAD_MAX_CONTROLLERS; + std::uint8_t count = 0; + for (int port = 0; port < PAD_MAX_CONTROLLERS; ++port) { + if (Pad::IsConnected(port)) { + PadInfo::PadTypeInfo& slot = info->padTypeInfo[count]; + slot.connectPort = static_cast(port); + slot.status = 1; + slot.deviceType = PAD_DEVICE_TYPE_DUAL_SENSE; + slot.connectType = PAD_CONNECT_TYPE_BLUETOOTH; + std::uint8_t mac[6]; + std::memcpy(mac, PAD_BD_ADDRESS, sizeof(mac)); + mac[5] = static_cast(mac[5] + port); + std::memcpy(slot.bluetoothMacAddress, mac, sizeof(slot.bluetoothMacAddress)); + ++count; + } + } + if (count == 0) { + PadInfo::PadTypeInfo& slot = info->padTypeInfo[0]; + slot.connectPort = 0; + slot.status = 1; + slot.deviceType = PAD_DEVICE_TYPE_DUAL_SENSE; + slot.connectType = PAD_CONNECT_TYPE_BLUETOOTH; + std::memcpy(slot.bluetoothMacAddress, PAD_BD_ADDRESS, sizeof(slot.bluetoothMacAddress)); + count = 1; + } + info->connectedCount[0] = count; info->connectedCount[1] = 0; - PadInfo::PadTypeInfo& slot = info->padTypeInfo[0]; - slot.connectPort = 0; - slot.status = 1; - slot.deviceType = PAD_DEVICE_TYPE_DUAL_SENSE; - slot.connectType = PAD_CONNECT_TYPE_BLUETOOTH; - std::memcpy(slot.bluetoothMacAddress, PAD_BD_ADDRESS, sizeof(slot.bluetoothMacAddress)); return PAD_OK; } int APS5_VABI scePadGetTriggerEffectState(int handle, PadTriggerEffectStateInformation* info) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (info == nullptr) return PAD_ERROR_INVALID_ARG; std::memset(info, 0, sizeof(*info)); return PAD_OK; } int APS5_VABI scePadGetExtControllerInformation(int handle, void* info) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (info == nullptr) return PAD_ERROR_INVALID_ARG; std::memset(info, 0, 0x2c); + if (SpecialHandle(handle)) return PAD_OK; PadControllerInformation* base = static_cast(info); base->touchPadInfo.pixelDensity = 44.86f; base->touchPadInfo.resolution.x = kTouchPadWidth; base->touchPadInfo.resolution.y = kTouchPadHeight; - base->stickInfo.deadZoneLeft = 2; - base->stickInfo.deadZoneRight = 2; + base->stickInfo.deadZoneLeft = 13; + base->stickInfo.deadZoneRight = 13; base->connectionType = PAD_CONNECTION_TYPE_LOCAL; base->connectedCount = 1; base->connected = true; @@ -151,12 +261,33 @@ int APS5_VABI scePadInit_nid_postfix(void) { int APS5_VABI scePadOpen_nid_postfix(int userId, int type, int index, const void* param) { (void)param; - if (!ValidPort(userId, type, index)) { + const bool unknownType = type == 3 || type == 4; + const bool knownType = type == PAD_PORT_TYPE_STANDARD || type == PAD_PORT_TYPE_SPECIAL || SystemRemote(userId, type); + if (!unknownType && !knownType) return PAD_ERROR_INVALID_ARG; + if (userId == PAD_USER_ID_INVALID) return PAD_ERROR_INVALID_USER_ID; + if (unknownType) NotImplemented_nid_no_patch(__func__); + if (index != 0) return PAD_ERROR_INVALID_PORT_INDEX; + if (type == PAD_PORT_TYPE_SPECIAL) { + if (SpecialHandleOf(userId) != PAD_ERROR_DEVICE_NO_HANDLE) return PAD_ERROR_ALREADY_OPENED; + for (int slot = 0; slot < PAD_SPECIAL_HANDLE_COUNT; ++slot) { + if (g_specialUsers[slot] == 0) { + g_specialUsers[slot] = userId; + return PAD_SPECIAL_HANDLE + slot; + } + } + return PAD_ERROR_UNKNOWN_USER; + } + if (SystemRemote(userId, type)) { + g_remoteOpened = true; + return PAD_REMOTE_HANDLE; + } + const int port = PortForUser(userId, type, index); + if (port < 0) { return PAD_ERROR_INVALID_ARG; } - if (type == PAD_PORT_TYPE_SPECIAL) return PAD_ERROR_DEVICE_NOT_CONNECTED; - g_opened = true; - return PAD_HANDLE; + g_opened[port] = true; + g_everOpened[port] = true; + return PAD_HANDLE_BASE + port; } int APS5_VABI scePadOpenExt(int userId, int type, int index, const void* param) { @@ -167,69 +298,84 @@ int APS5_VABI scePadOpenExt(int userId, int type, int index, const void* param) return PAD_ERROR_DEVICE_NOT_CONNECTED; } -int APS5_VABI scePadReadExt() { NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI scePadReadExt(std::int32_t handle, PadDataExtended* data, std::int32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetFeatureReport() { NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI scePadGetFeatureReport(std::int32_t handle, std::uint8_t reportId, void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetFeatureReport() { NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI scePadSetFeatureReport(std::int32_t handle, std::uint8_t reportId, const void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadOutputReport() { NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI scePadOutputReport(std::int32_t handle, std::uint8_t reportId, const void* data, std::uint64_t size) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI scePadRead_nid_postfix(int handle, PadData* data, int num) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle) && !RemoteHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (data == nullptr || num <= 0) return PAD_ERROR_INVALID_ARG; - return Pad::Read(data, num); + if (RemoteHandle(handle)) { + data[0] = PadData{}; + return 1; + } + if (SpecialHandle(handle)) { + data[0] = SpecialData(); + return 1; + } + return Pad::Read(data, num, PortOf(handle)); } int APS5_VABI scePadReadState(int handle, PadData* data) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle) && !RemoteHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (data == nullptr) return PAD_ERROR_INVALID_ARG; - *data = Pad::ReadState(); + *data = RemoteHandle(handle) ? PadData{} : SpecialHandle(handle) ? SpecialData() : Pad::ReadState(PortOf(handle)); return 0; } int APS5_VABI scePadResetLightBar(int handle) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; - Pad::SetLightBar(false, 0, 0, 0); + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + if (SpecialHandle(handle)) return PAD_OK; + Pad::SetLightBar(PortOf(handle), false, 0, 0, 0); return PAD_OK; } int APS5_VABI scePadResetOrientation(int handle) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; - Pad::ResetOrientation(); + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + if (SpecialHandle(handle)) return PAD_OK; + Pad::ResetOrientation(PortOf(handle)); return PAD_OK; } int APS5_VABI scePadSetAngularVelocityDeadbandState(int handle, bool enable) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; - Pad::SetAngularVelocityDeadband(enable); + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + if (SpecialHandle(handle)) return PAD_OK; + Pad::SetAngularVelocityDeadband(PortOf(handle), enable); return PAD_OK; } int APS5_VABI scePadSetLightBar(int handle, const PadLightBarParam* param) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (param == nullptr) return PAD_ERROR_INVALID_ARG; - Pad::SetLightBar(true, param->r, param->g, param->b); + if (SpecialHandle(handle)) return PAD_OK; + Pad::SetLightBar(PortOf(handle), true, param->r, param->g, param->b); return PAD_OK; } int APS5_VABI scePadSetMotionSensorState(int handle, bool enable) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; - Pad::SetMotionEnabled(enable); + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + if (SpecialHandle(handle)) return PAD_OK; + Pad::SetMotionEnabled(PortOf(handle), enable); return PAD_OK; } int APS5_VABI scePadSetTiltCorrectionState(int handle, bool enabled) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; - Pad::SetTiltCorrection(enabled); + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + if (SpecialHandle(handle)) return PAD_OK; + Pad::SetTiltCorrection(PortOf(handle), enabled); return PAD_OK; } int APS5_VABI scePadSetTriggerEffect(int handle, const void* param) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (param == nullptr) return PAD_ERROR_INVALID_ARG; + if (SpecialHandle(handle)) return PAD_OK; const std::uint8_t* bytes = static_cast(param); const std::uint8_t mask = bytes[0]; if ((mask & ~0x3u) != 0) { @@ -240,25 +386,28 @@ int APS5_VABI scePadSetTriggerEffect(int handle, const void* param) { if (mode[0] > 6 || mode[1] > 6) { return PAD_ERROR_INVALID_ARG; } + const int port = PortOf(handle); for (int trigger = 0; trigger < 2; ++trigger) { - if ((mask & (1u << trigger)) != 0) Pad::SetTriggerCommand(trigger, bytes + 8 + 56 * trigger); + if ((mask & (1u << trigger)) != 0) Pad::SetTriggerCommand(port, trigger, bytes + 8 + 56 * trigger); } return PAD_OK; } int APS5_VABI scePadSetVibration(int handle, const PadVibrationParam* param) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (param == nullptr) return PAD_ERROR_INVALID_ARG; - Pad::SetVibration(param->large_motor, param->small_motor); + if (SpecialHandle(handle)) return PAD_OK; + Pad::SetVibration(PortOf(handle), param->large_motor, param->small_motor); return PAD_OK; } int APS5_VABI scePadSetVibrationMode(int handle, int mode) { if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; if (mode <= 0) return PAD_ERROR_INVALID_ARG; - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (mode > 3 && mode != 5) return PAD_ERROR_INVALID_ARG; - Pad::SetVibrationMode(mode); + if (SpecialHandle(handle)) return PAD_OK; + Pad::SetVibrationMode(PortOf(handle), mode); return PAD_OK; } @@ -267,48 +416,47 @@ int APS5_VABI scePadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enab return PAD_OK; } -int APS5_VABI scePadVrControllerGetDeviceInformation() { +int APS5_VABI scePadVrControllerGetDeviceInformation(std::int32_t handle, PadVrControllerDeviceInformation* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVrControllerRead() { +int APS5_VABI scePadVrControllerRead(std::int32_t handle, PadVrControllerData* data, std::int32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVrControllerGetTriggerEffectState() { +int APS5_VABI scePadVrControllerGetTriggerEffectState(std::int32_t leftHandle, std::int32_t rightHandle, PadTriggerEffectStateInformation* states) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVrControllerReadState(int handle, void* state) { +int APS5_VABI scePadVrControllerReadState(int handle, PadVrControllerData* state) { (void)handle; (void)state; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVrControllerSetTriggerEffect() { +int APS5_VABI scePadVrControllerSetTriggerEffect(std::int32_t handle, const PadVrTriggerEffect* effect) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVrControllerSetTriggerEffects() { +int APS5_VABI scePadVrControllerSetTriggerEffects(std::int32_t leftHandle, std::int32_t rightHandle, const PadVrTriggerEffects* effects) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI scePadIsRemoteController(int handle, bool* remote) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (remote == nullptr) return PAD_ERROR_INVALID_ARG; *remote = false; return 0; } - int APS5_VABI scePadSetAngularVelocityBiasCorrectionState(int handle, bool enabled) { - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; if (enabled) NotImplemented_nid_no_patch(__func__); return PAD_OK; } @@ -318,7 +466,7 @@ int APS5_VABI scePadUnknown_n3kSX62fgNo() { return g_initialized.load(std::memory_order_acquire) ? PAD_OK : PAD_ERROR_NOT_INITIALIZED; } -int APS5_VABI scePadVrControllerGetTriggerEffectStates(void) { +int APS5_VABI scePadVrControllerGetTriggerEffectStates(std::int32_t leftHandle, std::int32_t rightHandle, PadTriggerEffectStateInformation* states) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -326,411 +474,416 @@ int APS5_VABI scePadVrControllerGetTriggerEffectStates(void) { int APS5_VABI scePadGetAccessibilityControllerState(int handle, std::uint32_t* state) { if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; if (state == nullptr) return PAD_ERROR_INVALID_ARG; - if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; *state = 0; return PAD_OK; } -int APS5_VABI scePadSetTriggerEffectExt(void) { +int APS5_VABI scePadSetTriggerEffectExt(std::int32_t handle, const PadTriggerEffectExtended* effect) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadLastLotusButtonData(void) { +int APS5_VABI scePadLastLotusButtonData(std::int32_t handle, std::uint32_t parameter, PadLotusButtonData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadReadStateExt(void) { +int APS5_VABI scePadReadStateExt(std::int32_t handle, PadDataExtended* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetExtensionReport(void) { +int APS5_VABI scePadSetExtensionReport(std::int32_t handle, const PadExtensionReport* report) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetExtensionUnitInfo(void) { +int APS5_VABI scePadGetExtensionUnitInfo(std::int32_t handle, PadExtensionUnitInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadEnableExtensionPort(void) { +int APS5_VABI scePadEnableExtensionPort(bool enabled) { return 0; } -int APS5_VABI scePadEnableSpecificDeviceClass(void) { +int APS5_VABI scePadEnableSpecificDeviceClass(std::int32_t deviceClass) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadOpenExt2(void) { +int APS5_VABI scePadOpenExt2(std::uint64_t deviceId, std::int32_t type, const void* reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadDeviceOpen(void) { +int APS5_VABI scePadDeviceOpen(std::uint64_t deviceId, std::int32_t type, std::int32_t index) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetDataInternal(void) { +int APS5_VABI scePadGetDataInternal(std::uint8_t port, PadData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetUserColor(void) { +int APS5_VABI scePadSetUserColor(std::int32_t handle, std::uint32_t color) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadDisableVibration(void) { +int APS5_VABI scePadDisableVibration(std::int32_t handle, bool disabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetVibrationStrength(void) { +int APS5_VABI scePadSetVibrationStrength(std::int32_t handle, std::uint32_t strength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetLightBarBaseBrightness(void) { +int APS5_VABI scePadSetLightBarBaseBrightness(std::int32_t handle, std::uint32_t brightness) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetLightBarBlinking(void) { +int APS5_VABI scePadSetLightBarBlinking(std::int32_t handle, const PadLightBarBlinkingParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetIdleCount(void) { +std::uint64_t APS5_VABI scePadGetIdleCount(std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetControllerIdleCount(void) { +int APS5_VABI scePadGetControllerIdleCount(std::int32_t handle, std::uint64_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetPowerSavingState(void) { +int APS5_VABI scePadSetPowerSavingState(std::int32_t handle, std::uint8_t state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetAutoPowerOffCount(void) { +int APS5_VABI scePadSetAutoPowerOffCount(std::int32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadEnableUsbConnection(void) { +int APS5_VABI scePadEnableUsbConnection(bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadEnableAutoDetect(void) { +int APS5_VABI scePadEnableAutoDetect(bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadEnablePlayPauseEmulation(void) { +int APS5_VABI scePadEnablePlayPauseEmulation(bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadDisableTrackingLedBatterySaver(void) { +int APS5_VABI scePadDisableTrackingLedBatterySaver(bool disabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetLoginUserNumber(void) { +int APS5_VABI scePadSetLoginUserNumber(std::int32_t number) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetCapability(void) { +int APS5_VABI scePadGetCapability(std::int32_t deviceId, std::uint32_t* capability) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadShareOutputData(void) { +int APS5_VABI scePadShareOutputData(std::int32_t source, std::int32_t destination) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadShareOutputDataWithFunctionType(void) { +int APS5_VABI scePadShareOutputDataWithFunctionType(std::int32_t source, std::int32_t destination, std::uint32_t functionType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetVibrationForce(void) { +int APS5_VABI scePadSetVibrationForce(std::int32_t handle, const PadVibrationParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetVersionInfo(void) { +int APS5_VABI scePadGetVersionInfo(std::int32_t handle, PadVersionInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetControllerType(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePadGetControllerType(std::int32_t handle, std::int32_t* type) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (type == nullptr) return PAD_ERROR_INVALID_ARG; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + *type = SpecialHandle(handle) ? PAD_CONTROLLER_TYPE_UNKNOWN : PAD_CONTROLLER_TYPE_DUAL_SENSE; + return PAD_OK; } -int APS5_VABI scePadDisableDataConversion(void) { +int APS5_VABI scePadDisableDataConversion(std::int32_t handle, bool disabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetButtonRemappingInfo(void) { +int APS5_VABI scePadSetButtonRemappingInfo(std::int32_t handle, const PadButtonRemappingInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetProcessPrivilegeOfButtonRemapping(void) { +int APS5_VABI scePadSetProcessPrivilegeOfButtonRemapping(bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetForceIntercepted(void) { +int APS5_VABI scePadSetForceIntercepted(bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadIsLightBarBaseBrightnessControllable(void) { +int APS5_VABI scePadIsLightBarBaseBrightnessControllable(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSwitchConnection(void) { +int APS5_VABI scePadSwitchConnection(std::int32_t handle, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadDisableTriggerEffect(void) { +int APS5_VABI scePadDisableTriggerEffect(std::int32_t handle, bool disabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetTriggerEffectStrength(void) { +int APS5_VABI scePadSetTriggerEffectStrength(std::int32_t handle, std::uint32_t strength) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetTriggerEffectInternalState(void) { +int APS5_VABI scePadGetTriggerEffectInternalState(std::int32_t handle, PadTriggerInternalState* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetUserNumber(void) { +int APS5_VABI scePadSetUserNumber(std::int32_t handle, std::int32_t number) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadDisableTransportButtons(void) { +int APS5_VABI scePadDisableTransportButtons(bool disabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetLotusIrCodeSetStart(void) { +int APS5_VABI scePadSetLotusIrCodeSetStart(std::int32_t handle, std::uint8_t codeSet, std::uint16_t code, std::size_t dataSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetLotusIrCodeSetData(void) { +int APS5_VABI scePadSetLotusIrCodeSetData(std::int32_t handle, std::uint8_t packetIndex, const void* data, std::size_t dataSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetLotusIrCodeSetEnd(void) { +int APS5_VABI scePadSetLotusIrCodeSetEnd(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetLotusIrCodeSetResult(void) { +int APS5_VABI scePadGetLotusIrCodeSetResult(std::int32_t handle, std::int32_t* result, std::uint8_t* deviceResult) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetLotusEnableIrKey(void) { +int APS5_VABI scePadSetLotusEnableIrKey(std::int32_t handle) { + (void)handle; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetLotusDisableIrKey(void) { +int APS5_VABI scePadSetLotusDisableIrKey(std::int32_t handle) { + (void)handle; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetUsbDetectionState(void) { +int APS5_VABI scePadGetUsbDetectionState(std::int32_t deviceId, PadUsbDetectionState* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadUiReadState(void) { +int APS5_VABI scePadUiReadState(std::int32_t handle, PadUiData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadUiReadStateForTuning(void) { +int APS5_VABI scePadUiReadStateForTuning(std::int32_t handle, PadUiTuningData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetStickTriggerTuningMode(void) { +int APS5_VABI scePadSetStickTriggerTuningMode(std::int32_t handle, std::uint32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetStickTuningParam(void) { +int APS5_VABI scePadSetStickTuningParam(std::int32_t handle, std::uint32_t stick, const PadStickTuningParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetTriggerTuningParam(void) { +int APS5_VABI scePadSetTriggerTuningParam(std::int32_t handle, const PadTriggerTuningParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetProfileHunt(void) { +int APS5_VABI scePadGetProfileHunt(std::int32_t handle, std::int32_t profileId, PadHuntProfile* profile) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetProfileHunt(void) { +int APS5_VABI scePadSetProfileHunt(std::int32_t handle, std::int32_t profileId, PadHuntProfile* profile, std::uint32_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadResetHunt(void) { +int APS5_VABI scePadResetHunt(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetProfileInfo(void) { +int APS5_VABI scePadGetProfileInfo(std::int32_t deviceId, std::int32_t profileId, PadProfileInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetActiveProfileInfo(void) { +int APS5_VABI scePadGetActiveProfileInfo(std::int32_t handle, PadActiveProfileInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadDisableActiveProfileChange(void) { +int APS5_VABI scePadDisableActiveProfileChange(std::int32_t handle, bool disabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadDisableActiveProfileChangeByDeviceId(void) { +int APS5_VABI scePadDisableActiveProfileChangeByDeviceId(std::int32_t deviceId, bool disabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetActiveProfileSlot(void) { +int APS5_VABI scePadSetActiveProfileSlot(std::int32_t deviceId, std::uint32_t slot) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetSavedSettingInfo(void) { +int APS5_VABI scePadGetSavedSettingInfo(std::int32_t handle, PadSavedSettingInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetSavedSettingInfo(void) { +int APS5_VABI scePadSetSavedSettingInfo(std::int32_t handle, const PadSavedSettingInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVrControllerGetPhysicalUsbConnectionStatus(void) { +int APS5_VABI scePadVrControllerGetPhysicalUsbConnectionStatus(PadVrUsbConnectionStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadParseUniqueDataLeonardo(void) { +int APS5_VABI scePadParseUniqueDataLeonardo(std::int32_t handle, const PadLeonardoData* data, PadLeonardoUniqueData* uniqueData) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetControllerTypeLeonardo(void) { +int APS5_VABI scePadGetControllerTypeLeonardo(std::int32_t deviceId, std::int32_t* controllerType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetControllerTypeLeonardo(void) { +int APS5_VABI scePadSetControllerTypeLeonardo(std::int32_t deviceId, std::int32_t controllerType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetProfileLeonardo(void) { +int APS5_VABI scePadGetProfileLeonardo(std::int32_t handle, std::int32_t profileId, PadLeonardoProfile* profile) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetProfileLeonardo(void) { +int APS5_VABI scePadSetProfileLeonardo(std::int32_t handle, std::int32_t profileId, PadLeonardoProfile* profile, std::uint32_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadResetLeonardo(void) { +int APS5_VABI scePadResetLeonardo(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetProfileEditMode(void) { +int APS5_VABI scePadSetProfileEditMode(std::int32_t handle, bool enabled, std::uint32_t profile) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetProfileEditMode(void) { +int APS5_VABI scePadGetProfileEditMode(std::int32_t handle, std::int32_t* mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetProfileEditModeLeonardo(void) { +int APS5_VABI scePadGetProfileEditModeLeonardo(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetSelectButtonCustomizeMode(void) { +int APS5_VABI scePadSetSelectButtonCustomizeMode(std::int32_t handle, bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetSelectButtonCustomizeMode(void) { +int APS5_VABI scePadGetSelectButtonCustomizeMode(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetToggleAllReleaseLeonardo(void) { +int APS5_VABI scePadSetToggleAllReleaseLeonardo(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadEnableControllerIndicator(void) { +int APS5_VABI scePadEnableControllerIndicator(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadEnableProfileIndicator(void) { +int APS5_VABI scePadEnableProfileIndicator(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetProfileIndicatorAnimation(void) { +int APS5_VABI scePadSetProfileIndicatorAnimation(std::int32_t handle, std::uint8_t animation) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadIsCoPilot(void) { +int APS5_VABI scePadIsCoPilot(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetIsCoPilot(void) { +int APS5_VABI scePadSetIsCoPilot(std::int32_t handle, bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -740,22 +893,22 @@ int APS5_VABI scePadRegisterCollaboInputMgrProcess(void) { return 0; } -int APS5_VABI scePadGetTelemetryInfo(void) { +int APS5_VABI scePadGetTelemetryInfo(std::int32_t handle, PadTelemetryInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetActiveTime(void) { +int APS5_VABI scePadGetActiveTime(std::int32_t handle, std::int32_t type, std::uint64_t* activeTime) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetDeviceId(void) { +int APS5_VABI scePadGetDeviceId(std::int32_t handle, std::int32_t* deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetInfoByPortType(void) { +int APS5_VABI scePadGetInfoByPortType(std::int32_t type, PadPortInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -765,87 +918,87 @@ int APS5_VABI scePadResetLightBarAll(void) { return 0; } -int APS5_VABI scePadResetLightBarAllByPortType(void) { +int APS5_VABI scePadResetLightBarAllByPortType(std::uint32_t portType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetLightBarForTracker(void) { +int APS5_VABI scePadSetLightBarForTracker(std::int32_t handle, const PadLightBarParam* color) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadReadForTracker(void) { +int APS5_VABI scePadReadForTracker(std::int32_t handle, PadTrackerData* data, std::int32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadReadBlasterForTracker(void) { +int APS5_VABI scePadReadBlasterForTracker(std::int32_t handle, PadBlasterTrackerData* data, std::int32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadResetOrientationForTracker(void) { +int APS5_VABI scePadResetOrientationForTracker(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetMotionTimerUnit(void) { +int APS5_VABI scePadGetMotionTimerUnit(std::int32_t handle, float* unit) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetVrTrackingMode(void) { +int APS5_VABI scePadSetVrTrackingMode(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetMotionSensorPosition(void) { +int APS5_VABI scePadGetMotionSensorPosition(std::int32_t handle, PadMotionSensorPosition* position) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetSphereRadius(void) { +int APS5_VABI scePadGetSphereRadius(std::int32_t handle, float* radius) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadIsValidHandle(void) { +bool APS5_VABI scePadIsValidHandle(std::int32_t handle, std::int32_t portType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadIsDS4Connected(void) { +bool APS5_VABI scePadIsDS4Connected(std::int32_t handle) { + (void)handle; + return false; +} + +bool APS5_VABI scePadIsBlasterConnected(std::int32_t handle) { + (void)handle; + return false; +} + +bool APS5_VABI scePadIsMoveConnected(std::int32_t handle) { + (void)handle; + return false; +} + +int APS5_VABI scePadGetBluetoothAddress(std::int32_t handle, std::uint64_t* address) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadIsBlasterConnected(void) { +int APS5_VABI scePadIsMoveReproductionModel(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadIsMoveConnected(void) { +int APS5_VABI scePadReadAstonForTracker(std::int32_t handle, PadAstonTrackerData* data, std::int32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetBluetoothAddress(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadIsMoveReproductionModel(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadReadAstonForTracker(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetAstonModel(void) { +int APS5_VABI scePadGetAstonModel(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -860,97 +1013,100 @@ int APS5_VABI scePadMbusTerm(void) { return 0; } -int APS5_VABI scePadConnectPort(void) { +int APS5_VABI scePadConnectPort(std::int32_t userId, std::int32_t portType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadDisconnectPort(void) { +int APS5_VABI scePadDisconnectPort(std::int32_t userId, std::int32_t portType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadDisconnectDevice(void) { +int APS5_VABI scePadDisconnectDevice(std::int32_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetDeviceInfo(void) { +int APS5_VABI scePadGetDeviceInfo(std::int32_t deviceId, PadDeviceInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetProcessFocus(void) { +int APS5_VABI scePadSetProcessFocus(const PadProcessFocusParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVertualDeviceAddDevice(void) { +int APS5_VABI scePadVertualDeviceAddDevice(const PadVirtualDeviceParameter* parameter, PadVirtualDeviceId* deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVirtualDeviceAddDevice(void) { +int APS5_VABI scePadVirtualDeviceAddDevice(const PadVirtualDeviceParameter* parameter, PadVirtualDeviceId* deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVirtualDeviceDeleteDevice(void) { +int APS5_VABI scePadVirtualDeviceDeleteDevice(std::int32_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVirtualDeviceInsertData(void) { +int APS5_VABI scePadVirtualDeviceInsertData(std::int32_t deviceId, const PadData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVirtualDeviceInsertTriggerInternalState(void) { +int APS5_VABI scePadVirtualDeviceInsertTriggerInternalState(std::int32_t deviceId, const PadVirtualTriggerInternalState* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVirtualDeviceGetRemoteSetting(void) { +int APS5_VABI scePadVirtualDeviceGetRemoteSetting(std::int32_t deviceId, PadRemoteSetting* setting) { + (void)deviceId; + (void)setting; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVirtualDeviceSetTriggerEffectSetting(void) { +int APS5_VABI scePadVirtualDeviceSetTriggerEffectSetting(std::int32_t handle, const PadVirtualTriggerSetting* setting) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadVirtualDeviceDisableButtonRemapping(void) { +int APS5_VABI scePadVirtualDeviceDisableButtonRemapping(bool disabled) { + (void)disabled; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetShareButtonMaskForRemotePlay(void) { +int APS5_VABI scePadSetShareButtonMaskForRemotePlay(std::uint8_t mask) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetPS4BcVibrationMode(void) { +int APS5_VABI scePadSetPS4BcVibrationMode(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetVirtualDeviceComponentInformation(void) { +int APS5_VABI scePadSetVirtualDeviceComponentInformation(std::int32_t deviceId, const PadVirtualDeviceComponentInformation* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadEnablePs4CompatibleMode(void) { +int APS5_VABI scePadEnablePs4CompatibleMode(std::int32_t handle, bool enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetLicenseControllerInformation(void) { +int APS5_VABI scePadGetLicenseControllerInformation(std::int32_t handle, PadLicenseControllerInformation* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadReadHistory(void) { +int APS5_VABI scePadReadHistory(std::int32_t handle, PadHistoryData* data, std::int32_t count) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -965,27 +1121,27 @@ int APS5_VABI scePadStopRecording(void) { return 0; } -int APS5_VABI scePadRemoteControlInsertData(void) { +int APS5_VABI scePadRemoteControlInsertData(const std::uint8_t* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadRemoteControlInsertDataForAston(void) { +int APS5_VABI scePadRemoteControlInsertDataForAston(const std::uint8_t* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadGetFeatureReport2(void) { +int APS5_VABI scePadGetFeatureReport2(std::int32_t handle, std::uint8_t reportId, void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetFeatureReport2(void) { +int APS5_VABI scePadSetFeatureReport2(std::int32_t handle, std::uint8_t reportId, const void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadSetMuteLed(void) { +int APS5_VABI scePadSetMuteLed(std::int32_t handle, std::uint32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -995,32 +1151,32 @@ int APS5_VABI scePadBondUpdateInit(void) { return 0; } -int APS5_VABI scePadBondUpdateIsReady(void) { +int APS5_VABI scePadBondUpdateIsReady(std::int32_t deviceId, std::int32_t* ready) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadBondUpdateStart(void) { +int APS5_VABI scePadBondUpdateStart(std::int32_t handle, const PadUpdateParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadBondUpdateGetStatus(void) { +int APS5_VABI scePadBondUpdateGetStatus(std::int32_t deviceId, std::int32_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadBondUpdateSendData(void) { +int APS5_VABI scePadBondUpdateSendData(std::int32_t deviceId, const PadUpdateData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadBondUpdateResetBond(void) { +int APS5_VABI scePadBondUpdateResetBond(std::int32_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadBondUpdateGetReconnectStatus(void) { +int APS5_VABI scePadBondUpdateGetReconnectStatus(std::int32_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1035,37 +1191,37 @@ int APS5_VABI scePadControllerUpdateInit(void) { return 0; } -int APS5_VABI scePadControllerUpdateIsUpdateNeeded2(void) { +int APS5_VABI scePadControllerUpdateIsUpdateNeeded2(std::int32_t deviceId, const std::uint32_t* version, std::uint16_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadControllerUpdateIsReady(void) { +int APS5_VABI scePadControllerUpdateIsReady(std::int32_t deviceId, std::int32_t* ready) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadControllerUpdateStart(void) { +int APS5_VABI scePadControllerUpdateStart(std::int32_t handle, const PadUpdateParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadControllerUpdateGetStatus(void) { +int APS5_VABI scePadControllerUpdateGetStatus(std::int32_t deviceId, std::int32_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadControllerUpdateSendData(void) { +int APS5_VABI scePadControllerUpdateSendData(std::int32_t deviceId, const PadUpdateData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadControllerUpdateResetController(void) { +int APS5_VABI scePadControllerUpdateResetController(std::int32_t deviceId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI scePadControllerUpdateGetReconnectStatus(void) { +int APS5_VABI scePadControllerUpdateGetReconnectStatus(std::int32_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libScePad/include/Pad.hpp b/core/libs/prx/libScePad/include/Pad.hpp index 8a4a7406a5..49154aa264 100644 --- a/core/libs/prx/libScePad/include/Pad.hpp +++ b/core/libs/prx/libScePad/include/Pad.hpp @@ -2,6 +2,7 @@ #define CORE_LIBS_PRX_LIBSCEPAD_PAD_HPP #include +#include "SceTypes.hpp" constexpr int PAD_OK = 0; constexpr int PAD_ERROR_INVALID_ARG = -2137915391; @@ -12,7 +13,12 @@ constexpr int PAD_PORT_TYPE_STANDARD = 0; constexpr int PAD_PORT_TYPE_SPECIAL = 2; constexpr int PAD_PORT_TYPE_REMOTE = 16; constexpr int PAD_USER_ID_SYSTEM = 0xff; +constexpr int PAD_MAX_CONTROLLERS = 4; +constexpr int PAD_HANDLE_BASE = 1; constexpr int PAD_HANDLE = 1; +constexpr int PAD_REMOTE_HANDLE = 0x10; +constexpr int PAD_SPECIAL_HANDLE = 0x20; +constexpr int PAD_SPECIAL_HANDLE_COUNT = 16; constexpr int PAD_CONNECTION_TYPE_LOCAL = 0; constexpr int PAD_DEVICE_CLASS_STANDARD = 0; @@ -23,6 +29,9 @@ constexpr int PAD_DEVICE_TYPE_DUAL_SHOCK_4 = 2; constexpr int PAD_DEVICE_TYPE_DUAL_SENSE = 3; constexpr int PAD_DEVICE_TYPE_DUAL_SENSE_EDGE = 4; +constexpr int PAD_CONTROLLER_TYPE_UNKNOWN = 0; +constexpr int PAD_CONTROLLER_TYPE_DUAL_SENSE = 2; + constexpr int PAD_CONNECT_TYPE_UNREGISTERED = 0; constexpr int PAD_CONNECT_TYPE_USB = 1; constexpr int PAD_CONNECT_TYPE_BLUETOOTH = 2; @@ -44,4 +53,288 @@ struct PadInfo { }; static_assert(sizeof(PadInfo) == 0x59); +struct PadStickTuningParameter; +struct PadTriggerTuningParameter; +struct PadHuntProfile; +struct PadLeonardoProfile; +struct PadProfileInfo; +struct PadActiveProfileInfo; +struct PadSavedSettingInfo; +struct PadUsbDetectionState; +struct PadVrUsbConnectionStatus; +struct PadDeviceInfo; +struct PadLicenseControllerInformation; +struct PadRemoteSetting; +struct PadVirtualTriggerSetting; +struct PadProcessFocusParameter; +struct PadDataExtended; + +struct PadVrControllerData { + std::uint32_t buttons; + std::uint8_t stick[2]; + std::uint8_t unused6[2]; + std::uint8_t triggers[2]; + std::uint8_t unused10[2]; + std::uint32_t touchButtons; + std::uint8_t touchValues[4]; + float acceleration[3]; + float angularVelocity[3]; + float orientation[4]; + std::uint8_t unused60[4]; + std::uint8_t connected; + std::uint8_t connectedCount; + std::uint8_t unused66[6]; + std::uint64_t timestamp; + std::uint8_t unused80[24]; +}; + +struct PadVrControllerDeviceInformation; +struct PadVrTriggerEffect; +struct PadVrTriggerEffects; +struct PadTriggerEffectExtended; +struct PadExtensionReport; +struct PadExtensionUnitInfo; +struct PadTriggerInternalState; +struct PadTrackerData; +struct PadBlasterTrackerData; +struct PadAstonTrackerData; +struct PadUiData; +struct PadUiTuningData; +struct PadLotusButtonData; +struct PadVersionInfo; +struct PadTelemetryInfo; +struct PadPortInfo; +struct PadVrControllerCombinedState; +struct PadLeonardoData; +struct PadLeonardoUniqueData; +struct PadHistoryData; +struct PadUpdateParameter; +struct PadVirtualDeviceParameter; +struct PadVirtualDeviceId; +struct PadVirtualDeviceComponentInformation; + +struct PadVirtualTriggerInternalState { + std::uint32_t states[2]; + std::uint8_t mode; + std::uint8_t flags[2]; +}; + +struct PadLightBarBlinkingParameter { + std::uint8_t values[3]; +}; + +struct PadButtonRemappingInfo { + std::uint8_t mapping[14]; + std::uint8_t reserved; + std::uint8_t mode; +}; + +struct PadUpdateData { + std::size_t size; + std::uint8_t data[32768]; +}; + +struct PadMotionSensorPosition { + float x; + float y; + float z; +}; + +extern "C" { + +int APS5_VABI scePadClose_nid_postfix(int handle); +int APS5_VABI scePadVrControllerSetVibration(std::int32_t handle, std::uint8_t intensity); +int APS5_VABI scePadVrControllerSetVibrationMode(std::int32_t handle, std::uint32_t mode); +int APS5_VABI scePadVrControllerReadCombinedState(std::int32_t leftHandle, std::int32_t rightHandle, PadVrControllerCombinedState* state); +int APS5_VABI scePadDeviceClassGetExtendedInformation(int handle, PadDeviceClassExtendedInformation* info); +int APS5_VABI scePadDeviceClassParseData(int handle, const PadData* data, PadDeviceClassData* classData); +int APS5_VABI scePadGetControllerInformation(int handle, PadControllerInformation* info); +int APS5_VABI scePadGetHandle(int userId, int type, int index); +int APS5_VABI scePadGetInfo_nid_postfix(PadInfo* info); +int APS5_VABI scePadGetTriggerEffectState(int handle, PadTriggerEffectStateInformation* info); +int APS5_VABI scePadGetExtControllerInformation(int handle, void* info); +int APS5_VABI scePadSetProcessPrivilege(int privilege); +int APS5_VABI scePadSetParticularMode(bool mode); +int APS5_VABI scePadInit_nid_postfix(void); +int APS5_VABI scePadOpen_nid_postfix(int userId, int type, int index, const void* param); +int APS5_VABI scePadOpenExt(int userId, int type, int index, const void* param); +int APS5_VABI scePadReadExt(std::int32_t handle, PadDataExtended* data, std::int32_t count); +int APS5_VABI scePadGetFeatureReport(std::int32_t handle, std::uint8_t reportId, void* data, std::size_t size); +int APS5_VABI scePadSetFeatureReport(std::int32_t handle, std::uint8_t reportId, const void* data, std::size_t size); +int APS5_VABI scePadOutputReport(std::int32_t handle, std::uint8_t reportId, const void* data, std::uint64_t size); +int APS5_VABI scePadRead_nid_postfix(int handle, PadData* data, int num); +int APS5_VABI scePadReadState(int handle, PadData* data); +int APS5_VABI scePadResetLightBar(int handle); +int APS5_VABI scePadResetOrientation(int handle); +int APS5_VABI scePadSetAngularVelocityDeadbandState(int handle, bool enable); +int APS5_VABI scePadSetLightBar(int handle, const PadLightBarParam* param); +int APS5_VABI scePadSetMotionSensorState(int handle, bool enable); +int APS5_VABI scePadSetTiltCorrectionState(int handle, bool enabled); +int APS5_VABI scePadSetTriggerEffect(int handle, const void* param); +int APS5_VABI scePadSetVibration(int handle, const PadVibrationParam* param); +int APS5_VABI scePadSetVibrationMode(int handle, int mode); +int APS5_VABI scePadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enabled); +int APS5_VABI scePadVrControllerGetDeviceInformation(std::int32_t handle, PadVrControllerDeviceInformation* info); +int APS5_VABI scePadVrControllerRead(std::int32_t handle, PadVrControllerData* data, std::int32_t count); +int APS5_VABI scePadVrControllerGetTriggerEffectState(std::int32_t leftHandle, std::int32_t rightHandle, PadTriggerEffectStateInformation* states); +int APS5_VABI scePadVrControllerReadState(int handle, PadVrControllerData* state); +int APS5_VABI scePadVrControllerSetTriggerEffect(std::int32_t handle, const PadVrTriggerEffect* effect); +int APS5_VABI scePadVrControllerSetTriggerEffects(std::int32_t leftHandle, std::int32_t rightHandle, const PadVrTriggerEffects* effects); +int APS5_VABI scePadIsRemoteController(int handle, bool* remote); +int APS5_VABI scePadSetAngularVelocityBiasCorrectionState(int handle, bool enabled); +int APS5_VABI scePadUnknown_n3kSX62fgNo(); +int APS5_VABI scePadVrControllerGetTriggerEffectStates(std::int32_t leftHandle, std::int32_t rightHandle, PadTriggerEffectStateInformation* states); +int APS5_VABI scePadGetAccessibilityControllerState(int handle, std::uint32_t* state); +int APS5_VABI scePadSetTriggerEffectExt(std::int32_t handle, const PadTriggerEffectExtended* effect); +int APS5_VABI scePadLastLotusButtonData(std::int32_t handle, std::uint32_t parameter, PadLotusButtonData* data); +int APS5_VABI scePadReadStateExt(std::int32_t handle, PadDataExtended* data); +int APS5_VABI scePadSetExtensionReport(std::int32_t handle, const PadExtensionReport* report); +int APS5_VABI scePadGetExtensionUnitInfo(std::int32_t handle, PadExtensionUnitInfo* info); +int APS5_VABI scePadEnableExtensionPort(bool enabled); +int APS5_VABI scePadEnableSpecificDeviceClass(std::int32_t deviceClass); +int APS5_VABI scePadOpenExt2(std::uint64_t deviceId, std::int32_t type, const void* reserved); +int APS5_VABI scePadDeviceOpen(std::uint64_t deviceId, std::int32_t type, std::int32_t index); +int APS5_VABI scePadGetDataInternal(std::uint8_t port, PadData* data); +int APS5_VABI scePadSetUserColor(std::int32_t handle, std::uint32_t color); +int APS5_VABI scePadDisableVibration(std::int32_t handle, bool disabled); +int APS5_VABI scePadSetVibrationStrength(std::int32_t handle, std::uint32_t strength); +int APS5_VABI scePadSetLightBarBaseBrightness(std::int32_t handle, std::uint32_t brightness); +int APS5_VABI scePadSetLightBarBlinking(std::int32_t handle, const PadLightBarBlinkingParameter* parameter); +std::uint64_t APS5_VABI scePadGetIdleCount(std::int32_t userId); +int APS5_VABI scePadGetControllerIdleCount(std::int32_t handle, std::uint64_t* count); +int APS5_VABI scePadSetPowerSavingState(std::int32_t handle, std::uint8_t state); +int APS5_VABI scePadSetAutoPowerOffCount(std::int32_t count); +int APS5_VABI scePadEnableUsbConnection(bool enabled); +int APS5_VABI scePadEnableAutoDetect(bool enabled); +int APS5_VABI scePadEnablePlayPauseEmulation(bool enabled); +int APS5_VABI scePadDisableTrackingLedBatterySaver(bool disabled); +int APS5_VABI scePadSetLoginUserNumber(std::int32_t number); +int APS5_VABI scePadGetCapability(std::int32_t deviceId, std::uint32_t* capability); +int APS5_VABI scePadShareOutputData(std::int32_t source, std::int32_t destination); +int APS5_VABI scePadShareOutputDataWithFunctionType(std::int32_t source, std::int32_t destination, std::uint32_t functionType); +int APS5_VABI scePadSetVibrationForce(std::int32_t handle, const PadVibrationParam* param); +int APS5_VABI scePadGetVersionInfo(std::int32_t handle, PadVersionInfo* info); +int APS5_VABI scePadGetControllerType(std::int32_t handle, std::int32_t* type); +int APS5_VABI scePadDisableDataConversion(std::int32_t handle, bool disabled); +int APS5_VABI scePadSetButtonRemappingInfo(std::int32_t handle, const PadButtonRemappingInfo* info); +int APS5_VABI scePadSetProcessPrivilegeOfButtonRemapping(bool enabled); +int APS5_VABI scePadSetForceIntercepted(bool enabled); +int APS5_VABI scePadIsLightBarBaseBrightnessControllable(std::int32_t handle); +int APS5_VABI scePadSwitchConnection(std::int32_t handle, std::uint32_t flags); +int APS5_VABI scePadDisableTriggerEffect(std::int32_t handle, bool disabled); +int APS5_VABI scePadSetTriggerEffectStrength(std::int32_t handle, std::uint32_t strength); +int APS5_VABI scePadGetTriggerEffectInternalState(std::int32_t handle, PadTriggerInternalState* state); +int APS5_VABI scePadSetUserNumber(std::int32_t handle, std::int32_t number); +int APS5_VABI scePadDisableTransportButtons(bool disabled); +int APS5_VABI scePadSetLotusIrCodeSetStart(std::int32_t handle, std::uint8_t codeSet, std::uint16_t code, std::size_t dataSize); +int APS5_VABI scePadSetLotusIrCodeSetData(std::int32_t handle, std::uint8_t packetIndex, const void* data, std::size_t dataSize); +int APS5_VABI scePadSetLotusIrCodeSetEnd(std::int32_t handle); +int APS5_VABI scePadGetLotusIrCodeSetResult(std::int32_t handle, std::int32_t* result, std::uint8_t* deviceResult); +int APS5_VABI scePadSetLotusEnableIrKey(std::int32_t handle); +int APS5_VABI scePadSetLotusDisableIrKey(std::int32_t handle); +int APS5_VABI scePadGetUsbDetectionState(std::int32_t deviceId, PadUsbDetectionState* state); +int APS5_VABI scePadUiReadState(std::int32_t handle, PadUiData* data); +int APS5_VABI scePadUiReadStateForTuning(std::int32_t handle, PadUiTuningData* data); +int APS5_VABI scePadSetStickTriggerTuningMode(std::int32_t handle, std::uint32_t mode); +int APS5_VABI scePadSetStickTuningParam(std::int32_t handle, std::uint32_t stick, const PadStickTuningParameter* parameter); +int APS5_VABI scePadSetTriggerTuningParam(std::int32_t handle, const PadTriggerTuningParameter* parameter); +int APS5_VABI scePadGetProfileHunt(std::int32_t handle, std::int32_t profileId, PadHuntProfile* profile); +int APS5_VABI scePadSetProfileHunt(std::int32_t handle, std::int32_t profileId, PadHuntProfile* profile, std::uint32_t* result); +int APS5_VABI scePadResetHunt(std::int32_t handle); +int APS5_VABI scePadGetProfileInfo(std::int32_t deviceId, std::int32_t profileId, PadProfileInfo* info); +int APS5_VABI scePadGetActiveProfileInfo(std::int32_t handle, PadActiveProfileInfo* info); +int APS5_VABI scePadDisableActiveProfileChange(std::int32_t handle, bool disabled); +int APS5_VABI scePadDisableActiveProfileChangeByDeviceId(std::int32_t deviceId, bool disabled); +int APS5_VABI scePadSetActiveProfileSlot(std::int32_t deviceId, std::uint32_t slot); +int APS5_VABI scePadGetSavedSettingInfo(std::int32_t handle, PadSavedSettingInfo* info); +int APS5_VABI scePadSetSavedSettingInfo(std::int32_t handle, const PadSavedSettingInfo* info); +int APS5_VABI scePadVrControllerGetPhysicalUsbConnectionStatus(PadVrUsbConnectionStatus* status); +int APS5_VABI scePadParseUniqueDataLeonardo(std::int32_t handle, const PadLeonardoData* data, PadLeonardoUniqueData* uniqueData); +int APS5_VABI scePadGetControllerTypeLeonardo(std::int32_t deviceId, std::int32_t* controllerType); +int APS5_VABI scePadSetControllerTypeLeonardo(std::int32_t deviceId, std::int32_t controllerType); +int APS5_VABI scePadGetProfileLeonardo(std::int32_t handle, std::int32_t profileId, PadLeonardoProfile* profile); +int APS5_VABI scePadSetProfileLeonardo(std::int32_t handle, std::int32_t profileId, PadLeonardoProfile* profile, std::uint32_t* result); +int APS5_VABI scePadResetLeonardo(std::int32_t handle); +int APS5_VABI scePadSetProfileEditMode(std::int32_t handle, bool enabled, std::uint32_t profile); +int APS5_VABI scePadGetProfileEditMode(std::int32_t handle, std::int32_t* mode); +int APS5_VABI scePadGetProfileEditModeLeonardo(std::int32_t handle); +int APS5_VABI scePadSetSelectButtonCustomizeMode(std::int32_t handle, bool enabled); +int APS5_VABI scePadGetSelectButtonCustomizeMode(std::int32_t handle); +int APS5_VABI scePadSetToggleAllReleaseLeonardo(std::int32_t handle); +int APS5_VABI scePadEnableControllerIndicator(std::int32_t handle); +int APS5_VABI scePadEnableProfileIndicator(std::int32_t handle); +int APS5_VABI scePadSetProfileIndicatorAnimation(std::int32_t handle, std::uint8_t animation); +int APS5_VABI scePadIsCoPilot(std::int32_t handle); +int APS5_VABI scePadSetIsCoPilot(std::int32_t handle, bool enabled); +int APS5_VABI scePadRegisterCollaboInputMgrProcess(void); +int APS5_VABI scePadGetTelemetryInfo(std::int32_t handle, PadTelemetryInfo* info); +int APS5_VABI scePadGetActiveTime(std::int32_t handle, std::int32_t type, std::uint64_t* activeTime); +int APS5_VABI scePadGetDeviceId(std::int32_t handle, std::int32_t* deviceId); +int APS5_VABI scePadGetInfoByPortType(std::int32_t type, PadPortInfo* info); +int APS5_VABI scePadResetLightBarAll(void); +int APS5_VABI scePadResetLightBarAllByPortType(std::uint32_t portType); +int APS5_VABI scePadSetLightBarForTracker(std::int32_t handle, const PadLightBarParam* color); +int APS5_VABI scePadReadForTracker(std::int32_t handle, PadTrackerData* data, std::int32_t count); +int APS5_VABI scePadReadBlasterForTracker(std::int32_t handle, PadBlasterTrackerData* data, std::int32_t count); +int APS5_VABI scePadResetOrientationForTracker(std::int32_t handle); +int APS5_VABI scePadGetMotionTimerUnit(std::int32_t handle, float* unit); +int APS5_VABI scePadSetVrTrackingMode(std::uint8_t mode); +int APS5_VABI scePadGetMotionSensorPosition(std::int32_t handle, PadMotionSensorPosition* position); +int APS5_VABI scePadGetSphereRadius(std::int32_t handle, float* radius); +bool APS5_VABI scePadIsValidHandle(std::int32_t handle, std::int32_t portType); +bool APS5_VABI scePadIsDS4Connected(std::int32_t handle); +bool APS5_VABI scePadIsBlasterConnected(std::int32_t handle); +bool APS5_VABI scePadIsMoveConnected(std::int32_t handle); +int APS5_VABI scePadGetBluetoothAddress(std::int32_t handle, std::uint64_t* address); +int APS5_VABI scePadIsMoveReproductionModel(std::int32_t handle); +int APS5_VABI scePadReadAstonForTracker(std::int32_t handle, PadAstonTrackerData* data, std::int32_t count); +int APS5_VABI scePadGetAstonModel(std::int32_t handle); +int APS5_VABI scePadMbusInit(void); +int APS5_VABI scePadMbusTerm(void); +int APS5_VABI scePadConnectPort(std::int32_t userId, std::int32_t portType); +int APS5_VABI scePadDisconnectPort(std::int32_t userId, std::int32_t portType); +int APS5_VABI scePadDisconnectDevice(std::int32_t deviceId); +int APS5_VABI scePadGetDeviceInfo(std::int32_t deviceId, PadDeviceInfo* info); +int APS5_VABI scePadSetProcessFocus(const PadProcessFocusParameter* parameter); +int APS5_VABI scePadVertualDeviceAddDevice(const PadVirtualDeviceParameter* parameter, PadVirtualDeviceId* deviceId); +int APS5_VABI scePadVirtualDeviceAddDevice(const PadVirtualDeviceParameter* parameter, PadVirtualDeviceId* deviceId); +int APS5_VABI scePadVirtualDeviceDeleteDevice(std::int32_t deviceId); +int APS5_VABI scePadVirtualDeviceInsertData(std::int32_t deviceId, const PadData* data); +int APS5_VABI scePadVirtualDeviceInsertTriggerInternalState(std::int32_t deviceId, const PadVirtualTriggerInternalState* state); +int APS5_VABI scePadVirtualDeviceGetRemoteSetting(std::int32_t deviceId, PadRemoteSetting* setting); +int APS5_VABI scePadVirtualDeviceSetTriggerEffectSetting(std::int32_t handle, const PadVirtualTriggerSetting* setting); +int APS5_VABI scePadVirtualDeviceDisableButtonRemapping(bool disabled); +int APS5_VABI scePadSetShareButtonMaskForRemotePlay(std::uint8_t mask); +int APS5_VABI scePadSetPS4BcVibrationMode(std::uint8_t mode); +int APS5_VABI scePadSetVirtualDeviceComponentInformation(std::int32_t deviceId, const PadVirtualDeviceComponentInformation* info); +int APS5_VABI scePadEnablePs4CompatibleMode(std::int32_t handle, bool enabled); +int APS5_VABI scePadGetLicenseControllerInformation(std::int32_t handle, PadLicenseControllerInformation* info); +int APS5_VABI scePadReadHistory(std::int32_t handle, PadHistoryData* data, std::int32_t count); +int APS5_VABI scePadStartRecording(void); +int APS5_VABI scePadStopRecording(void); +int APS5_VABI scePadRemoteControlInsertData(const std::uint8_t* data); +int APS5_VABI scePadRemoteControlInsertDataForAston(const std::uint8_t* data); +int APS5_VABI scePadGetFeatureReport2(std::int32_t handle, std::uint8_t reportId, void* data, std::size_t size); +int APS5_VABI scePadSetFeatureReport2(std::int32_t handle, std::uint8_t reportId, const void* data, std::size_t size); +int APS5_VABI scePadSetMuteLed(std::int32_t handle, std::uint32_t mode); +int APS5_VABI scePadBondUpdateInit(void); +int APS5_VABI scePadBondUpdateIsReady(std::int32_t deviceId, std::int32_t* ready); +int APS5_VABI scePadBondUpdateStart(std::int32_t handle, const PadUpdateParameter* parameter); +int APS5_VABI scePadBondUpdateGetStatus(std::int32_t deviceId, std::int32_t* status); +int APS5_VABI scePadBondUpdateSendData(std::int32_t deviceId, const PadUpdateData* data); +int APS5_VABI scePadBondUpdateResetBond(std::int32_t deviceId); +int APS5_VABI scePadBondUpdateGetReconnectStatus(std::int32_t* status); +int APS5_VABI scePadBondUpdateTerm(void); +int APS5_VABI scePadControllerUpdateInit(void); +int APS5_VABI scePadControllerUpdateIsUpdateNeeded2(std::int32_t deviceId, const std::uint32_t* version, std::uint16_t* status); +int APS5_VABI scePadControllerUpdateIsReady(std::int32_t deviceId, std::int32_t* ready); +int APS5_VABI scePadControllerUpdateStart(std::int32_t handle, const PadUpdateParameter* parameter); +int APS5_VABI scePadControllerUpdateGetStatus(std::int32_t deviceId, std::int32_t* status); +int APS5_VABI scePadControllerUpdateSendData(std::int32_t deviceId, const PadUpdateData* data); +int APS5_VABI scePadControllerUpdateResetController(std::int32_t deviceId); +int APS5_VABI scePadControllerUpdateGetReconnectStatus(std::int32_t* status); +int APS5_VABI scePadControllerUpdateTerm(void); + +} + #endif diff --git a/core/libs/prx/libScePad/include/PadState.hpp b/core/libs/prx/libScePad/include/PadState.hpp index fb621171ee..aee2c05569 100644 --- a/core/libs/prx/libScePad/include/PadState.hpp +++ b/core/libs/prx/libScePad/include/PadState.hpp @@ -56,23 +56,36 @@ struct PadOutputState { namespace Pad { void Initialize(); bool IsInitialized(); -PadData ReadState(); -int Read(PadData* data, int num); +PadData ReadState(int port = 0); +int Read(PadData* data, int num, int port = 0); +void SetVibration(int port, std::uint8_t large, std::uint8_t small); void SetVibration(std::uint8_t large, std::uint8_t small); +void SetVibrationMode(int port, int mode); void SetVibrationMode(int mode); +void SetLightBar(int port, bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b); void SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b); +void SetTriggerCommand(int port, int trigger, const std::uint8_t* command); void SetTriggerCommand(int trigger, const std::uint8_t* command); -void ResetOrientation(); +void ResetOrientation(int port = 0); +void SetMotionEnabled(int port, bool enabled); void SetMotionEnabled(bool enabled); +void SetTiltCorrection(int port, bool enabled); void SetTiltCorrection(bool enabled); +void SetAngularVelocityDeadband(int port, bool enabled); void SetAngularVelocityDeadband(bool enabled); +void SetConnected(int port, bool connected); +bool IsConnected(int port); } extern "C" void PadPublishInput_nid_postfix(const PadInputState& input); +extern "C" void PadPublishInputForPort_nid_postfix(int port, const PadInputState& input); extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error); extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out); +extern "C" bool PadFetchOutputForPort_nid_postfix(int port, std::uint32_t* seenSequence, PadOutputState* out); extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8_t small); extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small); extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source); +extern "C" void PadSubmitAudioHapticsForPort_nid_postfix(int port, std::uint8_t large, std::uint8_t small); +extern "C" void PadSetPortConnected_nid_postfix(int port, bool connected); #endif diff --git a/core/libs/prx/libScePad/libScePad.hpp b/core/libs/prx/libScePad/libScePad.hpp new file mode 100644 index 0000000000..12aecdf76c --- /dev/null +++ b/core/libs/prx/libScePad/libScePad.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePad/include/Pad.hpp" diff --git a/core/libs/prx/libScePad/src/PadState.cpp b/core/libs/prx/libScePad/src/PadState.cpp index 5a6795f32e..4e1453dbfe 100644 --- a/core/libs/prx/libScePad/src/PadState.cpp +++ b/core/libs/prx/libScePad/src/PadState.cpp @@ -13,47 +13,57 @@ #include namespace { - std::mutex stateMutex; - PadInputState state; - std::uint32_t pressedSinceRead = 0; - std::uint8_t pressedAnalogL2 = 0; - std::uint8_t pressedAnalogR2 = 0; - std::uint64_t pressedAt = 0; + struct Quat { float w = 1.0f, x = 0.0f, y = 0.0f, z = 0.0f; }; constexpr std::uint64_t kLatchExpiry = 100000; - PadOutputState output; - std::chrono::steady_clock::time_point audioHapticEnd; - std::array audioHapticRumble{}; - PadAudioHapticQueue legacyHaptics; - std::uint64_t timestamp = 0; + std::uint64_t injectedTimestamp = 0; struct QueuedInput { PadInputState input; std::uint64_t timestamp = 0; }; constexpr std::size_t kQueueCapacity = 64; - std::deque queue; + + struct PadSlot { + PadInputState state; + PadOutputState output; + std::deque queue; + Quat orientation; + std::uint64_t lastFuseTime = 0; + float biasIntegral[3] = {0.0f, 0.0f, 0.0f}; + bool tiltCorrection = true; + bool angularVelocityDeadband = false; + std::uint8_t nextTouchId = 0; + bool prevTouchActive[2] = {false, false}; + std::uint8_t touchIds[2] = {0, 0}; + std::chrono::steady_clock::time_point audioHapticEnd{}; + std::array audioHapticRumble{}; + PadAudioHapticQueue legacyHaptics; + std::uint64_t timestamp = 0; + std::uint32_t pressedSinceRead = 0; + std::uint8_t pressedAnalogL2 = 0; + std::uint8_t pressedAnalogR2 = 0; + std::uint64_t pressedAt = 0; + bool connected = false; + }; + + std::mutex stateMutex; + std::array slots = [] { + std::array initialSlots{}; + initialSlots[0].connected = true; + return initialSlots; + }(); std::exception_ptr failure; bool initialized = false; - - struct Quat { float w = 1.0f, x = 0.0f, y = 0.0f, z = 0.0f; }; - Quat orientation; - std::uint64_t lastFuseTime = 0; - float biasIntegral[3] = {0.0f, 0.0f, 0.0f}; - bool tiltCorrection = true; - bool angularVelocityDeadband = false; constexpr float kAngularVelocityDeadband = 0.017453292f; - std::uint8_t nextTouchId = 0; - bool prevTouchActive[2] = {false, false}; - std::uint8_t touchIds[2] = {0, 0}; - bool RefreshAudioHaptics() { - if (std::chrono::steady_clock::now() >= audioHapticEnd) audioHapticRumble = {}; - const auto legacy = legacyHaptics.Read(sceKernelGetProcessTime()); - const auto large = std::max(audioHapticRumble[0], legacy[0]); - const auto small = std::max(audioHapticRumble[1], legacy[1]); - if (output.audioVibrationLarge == large && output.audioVibrationSmall == small) return false; - output.audioVibrationLarge = large; - output.audioVibrationSmall = small; + bool RefreshAudioHaptics(PadSlot& slot) { + if (std::chrono::steady_clock::now() >= slot.audioHapticEnd) slot.audioHapticRumble = {}; + const auto legacy = slot.legacyHaptics.Read(sceKernelGetProcessTime()); + const auto large = std::max(slot.audioHapticRumble[0], legacy[0]); + const auto small = std::max(slot.audioHapticRumble[1], legacy[1]); + if (slot.output.audioVibrationLarge == large && slot.output.audioVibrationSmall == small) return false; + slot.output.audioVibrationLarge = large; + slot.output.audioVibrationSmall = small; return true; } @@ -63,33 +73,33 @@ namespace { q.w /= n; q.x /= n; q.y /= n; q.z /= n; } - void fuse(float dt, const std::array& accel, const std::array& gyro) { + void fuse(PadSlot& slot, float dt, const std::array& accel, const std::array& gyro) { float gx = gyro[0], gy = gyro[1], gz = gyro[2]; const float an = std::sqrt(accel[0] * accel[0] + accel[1] * accel[1] + accel[2] * accel[2]); - if (tiltCorrection && an > 1.0f) { + if (slot.tiltCorrection && an > 1.0f) { const float ax = accel[0] / an, ay = accel[1] / an, az = accel[2] / an; - const float vx = 2.0f * (orientation.x * orientation.y + orientation.w * orientation.z); - const float vy = orientation.w * orientation.w - orientation.x * orientation.x + orientation.y * orientation.y - orientation.z * orientation.z; - const float vz = 2.0f * (orientation.y * orientation.z - orientation.w * orientation.x); + const float vx = 2.0f * (slot.orientation.x * slot.orientation.y + slot.orientation.w * slot.orientation.z); + const float vy = slot.orientation.w * slot.orientation.w - slot.orientation.x * slot.orientation.x + slot.orientation.y * slot.orientation.y - slot.orientation.z * slot.orientation.z; + const float vz = 2.0f * (slot.orientation.y * slot.orientation.z - slot.orientation.w * slot.orientation.x); const float ex = ay * vz - az * vy; const float ey = az * vx - ax * vz; const float ez = ax * vy - ay * vx; constexpr float kp = 1.5f; constexpr float ki = 0.02f; - biasIntegral[0] += ki * ex * dt; - biasIntegral[1] += ki * ey * dt; - biasIntegral[2] += ki * ez * dt; - gx += kp * ex + biasIntegral[0]; - gy += kp * ey + biasIntegral[1]; - gz += kp * ez + biasIntegral[2]; + slot.biasIntegral[0] += ki * ex * dt; + slot.biasIntegral[1] += ki * ey * dt; + slot.biasIntegral[2] += ki * ez * dt; + gx += kp * ex + slot.biasIntegral[0]; + gy += kp * ey + slot.biasIntegral[1]; + gz += kp * ez + slot.biasIntegral[2]; } const float hx = 0.5f * gx * dt, hy = 0.5f * gy * dt, hz = 0.5f * gz * dt; - const Quat q = orientation; - orientation.w = q.w - q.x * hx - q.y * hy - q.z * hz; - orientation.x = q.x + q.w * hx + q.y * hz - q.z * hy; - orientation.y = q.y + q.w * hy - q.x * hz + q.z * hx; - orientation.z = q.z + q.w * hz + q.x * hy - q.y * hx; - normalize(orientation); + const Quat q = slot.orientation; + slot.orientation.w = q.w - q.x * hx - q.y * hy - q.z * hz; + slot.orientation.x = q.x + q.w * hx + q.y * hz - q.z * hy; + slot.orientation.y = q.y + q.w * hy - q.x * hz + q.z * hx; + slot.orientation.z = q.z + q.w * hz + q.x * hy - q.y * hx; + normalize(slot.orientation); } void encodeZones(std::uint8_t* dst, std::uint8_t mode, const std::uint8_t* strengths, int count, std::uint8_t frequency) { @@ -124,10 +134,13 @@ void Pad::Initialize() { std::lock_guard lock(stateMutex); if (failure) std::rethrow_exception(failure); if (!initialized) { - timestamp = sceKernelGetProcessTime(); - lastFuseTime = timestamp; - queue.clear(); - pressedSinceRead = 0; + const std::uint64_t now = sceKernelGetProcessTime(); + for (std::size_t i = 0; i < slots.size(); ++i) { + slots[i].timestamp = now; + slots[i].lastFuseTime = now; + slots[i].queue.clear(); + slots[i].pressedSinceRead = 0; + } } initialized = true; } @@ -138,7 +151,7 @@ bool Pad::IsInitialized() { } namespace { -PadData BuildData(const PadInputState& state, std::uint64_t changeTime, std::uint64_t now, const Pad::InputCommandFile::Input& injected) { +PadData BuildData(PadSlot& slot, const PadInputState& state, std::uint64_t changeTime, std::uint64_t now, const Pad::InputCommandFile::Input& injected, int port) { PadData data{}; data.buttons = state.buttons | injected.buttons; const auto& sticks = injected.hasSticks ? injected.sticks : state.sticks; @@ -152,28 +165,28 @@ PadData BuildData(const PadInputState& state, std::uint64_t changeTime, std::uin data.analog_buttons_l2 = trigger(state.analogButtonsL2, 0x100); data.analog_buttons_r2 = trigger(state.analogButtonsR2, 0x200); - const bool live = state.hasMotion && output.motionEnabled; + const bool live = state.hasMotion && slot.output.motionEnabled; const std::array rest{0.0f, 9.80665f, 0.0f}; const std::array& accel = live ? state.accel : rest; std::array gyro = live ? state.gyro : std::array{0.0f, 0.0f, 0.0f}; - if (angularVelocityDeadband) { + if (slot.angularVelocityDeadband) { for (float& axis : gyro) { if (axis < kAngularVelocityDeadband && axis > -kAngularVelocityDeadband) axis = 0.0f; } } if (live) { - float dt = lastFuseTime != 0 && now > lastFuseTime ? static_cast(now - lastFuseTime) * 1e-6f : 0.0f; + float dt = slot.lastFuseTime != 0 && now > slot.lastFuseTime ? static_cast(now - slot.lastFuseTime) * 1e-6f : 0.0f; dt = std::min(dt, 0.1f); - fuse(dt, accel, gyro); + fuse(slot, dt, accel, gyro); } else { - orientation = Quat{}; - biasIntegral[0] = biasIntegral[1] = biasIntegral[2] = 0.0f; + slot.orientation = Quat{}; + slot.biasIntegral[0] = slot.biasIntegral[1] = slot.biasIntegral[2] = 0.0f; } - lastFuseTime = now; - data.orientation_x = orientation.x; - data.orientation_y = orientation.y; - data.orientation_z = orientation.z; - data.orientation_w = orientation.w; + slot.lastFuseTime = now; + data.orientation_x = slot.orientation.x; + data.orientation_y = slot.orientation.y; + data.orientation_z = slot.orientation.z; + data.orientation_w = slot.orientation.w; constexpr float kGravity = 9.80665f; data.acceleration_x = accel[0] / kGravity; data.acceleration_y = accel[1] / kGravity; @@ -182,20 +195,24 @@ PadData BuildData(const PadInputState& state, std::uint64_t changeTime, std::uin data.angular_velocity_y = gyro[1]; data.angular_velocity_z = gyro[2]; - data.connected = true; - data.connected_count = 1; + std::uint8_t connectedCount = 0; + for (const auto& s : slots) { + if (s.connected) ++connectedCount; + } + data.connected = slot.connected; + data.connected_count = connectedCount > 0 ? connectedCount : 1; data.timestamp = changeTime; std::uint8_t touchNum = 0; for (int i = 0; i < 2; ++i) { const PadTouchPoint& tp = state.touch[i]; - if (!tp.active) { prevTouchActive[i] = false; continue; } - if (!prevTouchActive[i]) touchIds[i] = static_cast(nextTouchId++ & 0x7F); - prevTouchActive[i] = true; + 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 (touchNum == 0) { - data.touch_data_touch0_x = tp.x; data.touch_data_touch0_y = tp.y; data.touch_data_touch0_id = touchIds[i]; + data.touch_data_touch0_x = tp.x; data.touch_data_touch0_y = tp.y; data.touch_data_touch0_id = slot.touchIds[i]; } else { - data.touch_data_touch1_x = tp.x; data.touch_data_touch1_y = tp.y; data.touch_data_touch1_id = touchIds[i]; + data.touch_data_touch1_x = tp.x; data.touch_data_touch1_y = tp.y; data.touch_data_touch1_id = slot.touchIds[i]; } ++touchNum; } @@ -223,66 +240,85 @@ Pad::InputCommandFile::Input InjectedInput(std::uint64_t now) { const auto injected = commands.Poll(now); if (injected != lastInjected) { lastInjected = injected; - timestamp = now; + slots[0].timestamp = now; + injectedTimestamp = now; } return injected; } } -PadData Pad::ReadState() { +PadData Pad::ReadState(int port) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) throw std::invalid_argument("Pad: invalid port"); std::lock_guard lock(stateMutex); if (failure) std::rethrow_exception(failure); if (!initialized) throw std::runtime_error("Pad: read before initialization"); const std::uint64_t now = sceKernelGetProcessTime(); - const auto injected = InjectedInput(now); - PadData data = BuildData(state, timestamp, now, injected); - if (pressedSinceRead != 0 && now - pressedAt <= kLatchExpiry) { - data.buttons |= pressedSinceRead; - if (pressedSinceRead & 0x100u) data.analog_buttons_l2 = std::max(data.analog_buttons_l2, pressedAnalogL2); - if (pressedSinceRead & 0x200u) data.analog_buttons_r2 = std::max(data.analog_buttons_r2, pressedAnalogR2); + const auto injected = port == 0 ? InjectedInput(now) : Pad::InputCommandFile::Input{}; + auto& slot = slots[port]; + PadData data = BuildData(slot, slot.state, slot.timestamp, now, injected, port); + if (slot.pressedSinceRead != 0 && now - slot.pressedAt <= kLatchExpiry) { + data.buttons |= slot.pressedSinceRead; + if (slot.pressedSinceRead & 0x100u) data.analog_buttons_l2 = std::max(data.analog_buttons_l2, slot.pressedAnalogL2); + if (slot.pressedSinceRead & 0x200u) data.analog_buttons_r2 = std::max(data.analog_buttons_r2, slot.pressedAnalogR2); } - pressedSinceRead = 0; + slot.pressedSinceRead = 0; return data; } -int Pad::Read(PadData* data, int num) { +int Pad::Read(PadData* data, int num, int port) { if (data == nullptr || num <= 0) throw std::invalid_argument("Pad: invalid read buffer"); + if (port < 0 || port >= PAD_MAX_CONTROLLERS) throw std::invalid_argument("Pad: invalid port"); std::lock_guard lock(stateMutex); if (failure) std::rethrow_exception(failure); if (!initialized) throw std::runtime_error("Pad: read before initialization"); - pressedSinceRead = 0; + auto& slot = slots[port]; + slot.pressedSinceRead = 0; const std::uint64_t now = sceKernelGetProcessTime(); - const auto injected = InjectedInput(now); - if (queue.empty()) { - data[0] = BuildData(state, timestamp, now, injected); + const auto injected = port == 0 ? InjectedInput(now) : Pad::InputCommandFile::Input{}; + if (slot.queue.empty()) { + data[0] = BuildData(slot, slot.state, slot.timestamp, now, injected, port); return 1; } int count = 0; - while (count < num && !queue.empty()) { - const QueuedInput& entry = queue.front(); - data[count++] = BuildData(entry.input, entry.timestamp, now, injected); - queue.pop_front(); + while (count < num && !slot.queue.empty()) { + const QueuedInput& entry = slot.queue.front(); + data[count++] = BuildData(slot, entry.input, port == 0 ? std::max(entry.timestamp, injectedTimestamp) : entry.timestamp, now, injected, port); + slot.queue.pop_front(); } return count; } -void Pad::SetVibration(std::uint8_t large, std::uint8_t small) { +void Pad::SetVibration(int port, std::uint8_t large, std::uint8_t small) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; std::lock_guard lock(stateMutex); + auto& output = slots[port].output; if (output.vibrationLarge == large && output.vibrationSmall == small) return; output.vibrationLarge = large; output.vibrationSmall = small; ++output.sequence; } -void Pad::SetVibrationMode(int mode) { +void Pad::SetVibration(std::uint8_t large, std::uint8_t small) { + SetVibration(0, large, small); +} + +void Pad::SetVibrationMode(int port, int mode) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; std::lock_guard lock(stateMutex); + auto& output = slots[port].output; if (output.vibrationMode == mode) return; output.vibrationMode = mode; ++output.sequence; } -void Pad::SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b) { +void Pad::SetVibrationMode(int mode) { + SetVibrationMode(0, mode); +} + +void Pad::SetLightBar(int port, bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; std::lock_guard lock(stateMutex); + auto& output = slots[port].output; output.lightBarValid = valid; output.lightBar[0] = r; output.lightBar[1] = g; @@ -290,8 +326,12 @@ void Pad::SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b ++output.sequence; } -void Pad::SetTriggerCommand(int trigger, const std::uint8_t* command) { - if (trigger < 0 || trigger > 1 || command == nullptr) return; +void Pad::SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b) { + SetLightBar(0, valid, r, g, b); +} + +void Pad::SetTriggerCommand(int port, int trigger, const std::uint8_t* command) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS || trigger < 0 || trigger > 1 || command == nullptr) return; std::uint32_t mode = 0; std::memcpy(&mode, command, 4); const std::uint8_t* p = command + 8; @@ -348,55 +388,95 @@ void Pad::SetTriggerCommand(int trigger, const std::uint8_t* command) { req.valid = req.effect[0] != 0x05; if (!req.valid) req.fallback = 0; std::lock_guard lock(stateMutex); + auto& output = slots[port].output; output.trigger[trigger] = req; output.triggerTouched = true; ++output.sequence; } -void Pad::ResetOrientation() { +void Pad::SetTriggerCommand(int trigger, const std::uint8_t* command) { + SetTriggerCommand(0, trigger, command); +} + +void Pad::ResetOrientation(int port) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; std::lock_guard lock(stateMutex); - orientation = Quat{}; - biasIntegral[0] = biasIntegral[1] = biasIntegral[2] = 0.0f; + slots[port].orientation = Quat{}; + slots[port].biasIntegral[0] = slots[port].biasIntegral[1] = slots[port].biasIntegral[2] = 0.0f; +} + +void Pad::SetTiltCorrection(int port, bool enabled) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; + std::lock_guard lock(stateMutex); + slots[port].tiltCorrection = enabled; + slots[port].biasIntegral[0] = slots[port].biasIntegral[1] = slots[port].biasIntegral[2] = 0.0f; } void Pad::SetTiltCorrection(bool enabled) { + SetTiltCorrection(0, enabled); +} + +void Pad::SetAngularVelocityDeadband(int port, bool enabled) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; std::lock_guard lock(stateMutex); - tiltCorrection = enabled; - biasIntegral[0] = biasIntegral[1] = biasIntegral[2] = 0.0f; + slots[port].angularVelocityDeadband = enabled; } void Pad::SetAngularVelocityDeadband(bool enabled) { - std::lock_guard lock(stateMutex); - angularVelocityDeadband = enabled; + SetAngularVelocityDeadband(0, enabled); } -void Pad::SetMotionEnabled(bool enabled) { +void Pad::SetMotionEnabled(int port, bool enabled) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; std::lock_guard lock(stateMutex); + auto& output = slots[port].output; if (output.motionEnabled == enabled) return; output.motionEnabled = enabled; ++output.sequence; } -extern "C" void PadPublishInput_nid_postfix(const PadInputState& input) { +void Pad::SetMotionEnabled(bool enabled) { + SetMotionEnabled(0, enabled); +} + +void Pad::SetConnected(int port, bool connected) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; + std::lock_guard lock(stateMutex); + slots[port].connected = connected; +} + +bool Pad::IsConnected(int port) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return false; + std::lock_guard lock(stateMutex); + return slots[port].connected; +} + +extern "C" void PadPublishInputForPort_nid_postfix(int port, const PadInputState& input) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; std::lock_guard lock(stateMutex); if (failure) std::rethrow_exception(failure); - if (state.buttons == input.buttons && state.sticks == input.sticks && - state.analogButtonsL2 == input.analogButtonsL2 && state.analogButtonsR2 == input.analogButtonsR2 && - state.touchLeft == input.touchLeft && state.touchRight == input.touchRight && - state.hasMotion == input.hasMotion && state.accel == input.accel && state.gyro == input.gyro && - state.touch == input.touch && state.deviceKind == input.deviceKind) return; - const std::uint32_t pressed = input.buttons & ~state.buttons; - timestamp = sceKernelGetProcessTime(); + auto& slot = slots[port]; + if (slot.state.buttons == input.buttons && slot.state.sticks == input.sticks && + slot.state.analogButtonsL2 == input.analogButtonsL2 && slot.state.analogButtonsR2 == input.analogButtonsR2 && + slot.state.touchLeft == input.touchLeft && slot.state.touchRight == input.touchRight && + slot.state.hasMotion == input.hasMotion && slot.state.accel == input.accel && slot.state.gyro == input.gyro && + slot.state.touch == input.touch && slot.state.deviceKind == input.deviceKind) return; + const std::uint32_t pressed = input.buttons & ~slot.state.buttons; + slot.timestamp = sceKernelGetProcessTime(); if (pressed != 0) { - if (pressedSinceRead != 0 && timestamp - pressedAt > kLatchExpiry) pressedSinceRead = 0; - pressedSinceRead |= pressed; - pressedAt = timestamp; - if (pressed & 0x100u) pressedAnalogL2 = input.analogButtonsL2; - if (pressed & 0x200u) pressedAnalogR2 = input.analogButtonsR2; + if (slot.pressedSinceRead != 0 && slot.timestamp - slot.pressedAt > kLatchExpiry) slot.pressedSinceRead = 0; + slot.pressedSinceRead |= pressed; + slot.pressedAt = slot.timestamp; + if (pressed & 0x100u) slot.pressedAnalogL2 = input.analogButtonsL2; + if (pressed & 0x200u) slot.pressedAnalogR2 = input.analogButtonsR2; } - state = input; - if (queue.size() == kQueueCapacity) queue.pop_front(); - queue.push_back({input, timestamp}); + slot.state = input; + if (slot.queue.size() == kQueueCapacity) slot.queue.pop_front(); + slot.queue.push_back({input, slot.timestamp}); +} + +extern "C" void PadPublishInput_nid_postfix(const PadInputState& input) { + PadPublishInputForPort_nid_postfix(0, input); } extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error) { @@ -405,35 +485,51 @@ extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error) { if (!failure) failure = error; } -extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out) { +extern "C" bool PadFetchOutputForPort_nid_postfix(int port, std::uint32_t* seenSequence, PadOutputState* out) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return false; std::lock_guard lock(stateMutex); - if (RefreshAudioHaptics()) ++output.sequence; - if (seenSequence == nullptr || out == nullptr || *seenSequence == output.sequence) return false; - *seenSequence = output.sequence; - *out = output; + auto& slot = slots[port]; + if (RefreshAudioHaptics(slot)) ++slot.output.sequence; + if (seenSequence == nullptr || out == nullptr || *seenSequence == slot.output.sequence) return false; + *seenSequence = slot.output.sequence; + *out = slot.output; return true; } -extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8_t small) { +extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out) { + return PadFetchOutputForPort_nid_postfix(0, seenSequence, out); +} + +extern "C" void PadSubmitAudioHapticsForPort_nid_postfix(int port, std::uint8_t large, std::uint8_t small) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; std::lock_guard lock(stateMutex); if (large == 0 && small == 0) return; const auto now = std::chrono::steady_clock::now(); - if (now >= audioHapticEnd) audioHapticRumble = {}; - audioHapticRumble[0] = std::max(audioHapticRumble[0], large); - audioHapticRumble[1] = std::max(audioHapticRumble[1], small); - audioHapticEnd = now + std::chrono::milliseconds(80); - static_cast(RefreshAudioHaptics()); - ++output.sequence; + auto& slot = slots[port]; + if (now >= slot.audioHapticEnd) slot.audioHapticRumble = {}; + slot.audioHapticRumble[0] = std::max(slot.audioHapticRumble[0], large); + slot.audioHapticRumble[1] = std::max(slot.audioHapticRumble[1], small); + slot.audioHapticEnd = now + std::chrono::milliseconds(80); + static_cast(RefreshAudioHaptics(slot)); + ++slot.output.sequence; } extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small) { std::lock_guard lock(stateMutex); - legacyHaptics.Queue(source, begin, end, large, small); - if (RefreshAudioHaptics()) ++output.sequence; + slots[0].legacyHaptics.Queue(source, begin, end, large, small); + if (RefreshAudioHaptics(slots[0])) ++slots[0].output.sequence; } extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source) { std::lock_guard lock(stateMutex); - legacyHaptics.Clear(source); - if (RefreshAudioHaptics()) ++output.sequence; + slots[0].legacyHaptics.Clear(source); + if (RefreshAudioHaptics(slots[0])) ++slots[0].output.sequence; +} + +extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8_t small) { + PadSubmitAudioHapticsForPort_nid_postfix(0, large, small); +} + +extern "C" void PadSetPortConnected_nid_postfix(int port, bool connected) { + Pad::SetConnected(port, connected); } diff --git a/core/libs/prx/libScePadTracker/Export.cpp b/core/libs/prx/libScePadTracker/Export.cpp index e43ebed776..e48d78bdda 100644 --- a/core/libs/prx/libScePadTracker/Export.cpp +++ b/core/libs/prx/libScePadTracker/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePadTracker/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libScePadTracker/include/Api.hpp b/core/libs/prx/libScePadTracker/include/Api.hpp new file mode 100644 index 0000000000..825991c114 --- /dev/null +++ b/core/libs/prx/libScePadTracker/include/Api.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePadTrackerCalibrate(); +int APS5_VABI scePadTrackerGetWorkingMemorySize(); +int APS5_VABI scePadTrackerInit(); +int APS5_VABI scePadTrackerReadState(); +int APS5_VABI scePadTrackerTerm(); +int APS5_VABI scePadTrackerUpdate(); + +} diff --git a/core/libs/prx/libScePadTracker/libScePadTracker.hpp b/core/libs/prx/libScePadTracker/libScePadTracker.hpp new file mode 100644 index 0000000000..18b6ca49c9 --- /dev/null +++ b/core/libs/prx/libScePadTracker/libScePadTracker.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePadTracker/include/Api.hpp" diff --git a/core/libs/prx/libScePatchCheckerClient/Export.cpp b/core/libs/prx/libScePatchCheckerClient/Export.cpp index 3cce60d2e7..e10db5258f 100644 --- a/core/libs/prx/libScePatchCheckerClient/Export.cpp +++ b/core/libs/prx/libScePatchCheckerClient/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePatchCheckerClient/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libScePatchCheckerClient/include/Api.hpp b/core/libs/prx/libScePatchCheckerClient/include/Api.hpp new file mode 100644 index 0000000000..25f7bdc95a --- /dev/null +++ b/core/libs/prx/libScePatchCheckerClient/include/Api.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePatchCheckerCancel(); +int APS5_VABI scePatchCheckerCheckPatch(); +int APS5_VABI scePatchCheckerCreateHandler(); +int APS5_VABI scePatchCheckerDestroyHandler(); +int APS5_VABI scePatchCheckerDisableAutoDownload(); +int APS5_VABI scePatchCheckerEnableAutoDownload(); +int APS5_VABI scePatchCheckerGetApplicableTick(); +int APS5_VABI scePatchCheckerGetPackageInfo(); +int APS5_VABI scePatchCheckerInitialize(); +int APS5_VABI scePatchCheckerRequestCheckPatch(); +int APS5_VABI scePatchCheckerRequestCheckPatchByType(); +int APS5_VABI scePatchCheckerTerminate(); + +} diff --git a/core/libs/prx/libScePatchCheckerClient/libScePatchCheckerClient.hpp b/core/libs/prx/libScePatchCheckerClient/libScePatchCheckerClient.hpp new file mode 100644 index 0000000000..dab24e28c7 --- /dev/null +++ b/core/libs/prx/libScePatchCheckerClient/libScePatchCheckerClient.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePatchCheckerClient/include/Api.hpp" diff --git a/core/libs/prx/libScePerf/libScePerf.hpp b/core/libs/prx/libScePerf/libScePerf.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libScePerf/libScePerf.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libScePlayGo/Export.cpp b/core/libs/prx/libScePlayGo/Export.cpp index 165a767c99..f03bd2e343 100644 --- a/core/libs/prx/libScePlayGo/Export.cpp +++ b/core/libs/prx/libScePlayGo/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePlayGo/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -106,13 +107,15 @@ static UnlistedChunkAnswer UnlistedChunks() { static bool IsKnownChunk(uint16_t id) { const auto& chunks = Chunks(); - if (chunks.defined) return chunks.ids.contains(id); - if (chunks.scenario || id == 0) return true; + if (chunks.ids.contains(id)) return true; + if (chunks.defined) return false; switch (UnlistedChunks()) { case UnlistedChunkAnswer::Installed: return true; case UnlistedChunkAnswer::Absent: return false; case UnlistedChunkAnswer::Stop: break; } + if (chunks.scenario) + throw std::runtime_error("scePlayGo: sce_sys/playgo-chunk.dat is missing and sce_sys/playgo-scenario.json lists no chunks, so chunk ID " + std::to_string(id) + " outside the listed ids 0 to " + std::to_string(PLAYGO_SCENARIO_CHUNK_COUNT - 1) + " cannot be checked; set APS5_PLAYGO_UNLISTED_CHUNKS=installed or =absent to answer for this dump"); throw std::runtime_error("scePlayGo: sce_sys/playgo-chunk.dat and sce_sys/playgo-scenario.json are missing, so chunk ID " + std::to_string(id) + " cannot be checked; set APS5_PLAYGO_UNLISTED_CHUNKS=installed or =absent to answer for this dump"); } @@ -131,20 +134,21 @@ int APS5_VABI scePlayGoClose(int handle) { return 0; } -int APS5_VABI scePlayGoGetChunkId(int handle, uint16_t* out_chunk_id_list, uint32_t number_of_entries, uint32_t* out_entries) { +int APS5_VABI scePlayGoGetChunkId(int handle, uint16_t* outChunkIdList, uint32_t numberOfEntries, uint32_t* outEntries) { if (handle != PLAYGO_HANDLE) return SCE_PLAYGO_ERROR_BAD_HANDLE; - if (!out_entries) return SCE_PLAYGO_ERROR_BAD_POINTER; + if (!outEntries) return SCE_PLAYGO_ERROR_BAD_POINTER; const auto& valid = Chunks().ids; - if (!out_chunk_id_list) { - *out_entries = static_cast(valid.size()); + if (!outChunkIdList) { + *outEntries = static_cast(valid.size()); return 0; } + if (numberOfEntries == 0 || numberOfEntries > 1000) return SCE_PLAYGO_ERROR_BAD_SIZE; uint32_t written = 0; for (const auto id : valid) { - if (written == number_of_entries) break; - out_chunk_id_list[written++] = id; + if (written == numberOfEntries) break; + outChunkIdList[written++] = id; } - *out_entries = written; + *outEntries = written; return 0; } @@ -174,18 +178,19 @@ int APS5_VABI scePlayGoGetLanguageMask(int handle, uint64_t* out_language_mask) return 0; } -int APS5_VABI scePlayGoGetLocus(int handle, const uint16_t* chunk_ids, uint32_t number_of_entries, int8_t* out_loci) { +int APS5_VABI scePlayGoGetLocus(int handle, const uint16_t* chunkIds, uint32_t numberOfEntries, int8_t* outLoci) { if (handle != PLAYGO_HANDLE) return SCE_PLAYGO_ERROR_BAD_HANDLE; - if (!out_loci) return SCE_PLAYGO_ERROR_BAD_POINTER; - if (const int error = ValidateChunks(chunk_ids, number_of_entries)) return error; - for (uint32_t index = 0; index < number_of_entries; ++index) out_loci[index] = PLAYGO_LOCUS_LOCAL_FAST; + if (!outLoci) return SCE_PLAYGO_ERROR_BAD_POINTER; + if (chunkIds && numberOfEntries > 1000) return SCE_PLAYGO_ERROR_BAD_SIZE; + if (const int error = ValidateChunks(chunkIds, numberOfEntries)) return error; + for (uint32_t index = 0; index < numberOfEntries; ++index) outLoci[index] = PLAYGO_LOCUS_LOCAL_FAST; return 0; } -int APS5_VABI scePlayGoGetOptionalChunk(int handle, int32_t type, PlayGoOptionalChunk* option) { +int APS5_VABI scePlayGoGetOptionalChunk(int handle, std::uint32_t type, PlayGoOptionalChunk* option) { if (handle != PLAYGO_HANDLE) return SCE_PLAYGO_ERROR_BAD_HANDLE; - (void)type; if (!option) return SCE_PLAYGO_ERROR_BAD_POINTER; + if (type > 2) return static_cast(0x80B20024); option->bitmask = PLAYGO_OPTIONAL_CHUNK_MASK_ALL; return 0; } @@ -199,10 +204,10 @@ int APS5_VABI scePlayGoGetProgress(int handle, const uint16_t* chunk_ids, uint32 return 0; } -int APS5_VABI scePlayGoGetSupportedOptionalChunk(int handle, int32_t type, PlayGoOptionalChunk* option) { +int APS5_VABI scePlayGoGetSupportedOptionalChunk(int handle, std::uint32_t type, PlayGoOptionalChunk* option) { if (handle != PLAYGO_HANDLE) return SCE_PLAYGO_ERROR_BAD_HANDLE; - (void)type; if (!option) return SCE_PLAYGO_ERROR_BAD_POINTER; + if (type > 2) return static_cast(0x80B20024); option->bitmask = PLAYGO_OPTIONAL_CHUNK_MASK_ALL; return 0; } diff --git a/core/libs/prx/libScePlayGo/include/Api.hpp b/core/libs/prx/libScePlayGo/include/Api.hpp new file mode 100644 index 0000000000..e7f2e0a879 --- /dev/null +++ b/core/libs/prx/libScePlayGo/include/Api.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePlayGoClose(int handle); +int APS5_VABI scePlayGoGetChunkId(int handle, uint16_t* outChunkIdList, uint32_t numberOfEntries, uint32_t* outEntries); +int APS5_VABI scePlayGoGetEta(int handle, const uint16_t* chunkIds, uint32_t numberOfEntries, int64_t* outEta); +int APS5_VABI scePlayGoGetInstallChunkId(int handle, uint16_t* outChunkIdList, uint32_t numberOfEntries, uint32_t* outEntries); +int APS5_VABI scePlayGoGetInstallSpeed(int handle, int32_t* outSpeed); +int APS5_VABI scePlayGoGetLanguageMask(int handle, uint64_t* outLanguageMask); +int APS5_VABI scePlayGoGetLocus(int handle, const uint16_t* chunkIds, uint32_t numberOfEntries, int8_t* outLoci); +int APS5_VABI scePlayGoGetOptionalChunk(int handle, std::uint32_t type, PlayGoOptionalChunk* option); +int APS5_VABI scePlayGoGetProgress(int handle, const uint16_t* chunkIds, uint32_t numberOfEntries, PlayGoProgress* outProgress); +int APS5_VABI scePlayGoGetSupportedOptionalChunk(int handle, std::uint32_t type, PlayGoOptionalChunk* option); +int APS5_VABI scePlayGoGetToDoList(int handle, PlayGoToDo* outTodoList, uint32_t numberOfEntries, uint32_t* outEntries); +int APS5_VABI scePlayGoInitialize(const PlayGoInitParams* init); +int APS5_VABI scePlayGoOpen(int* outHandle, const void* param); +int APS5_VABI scePlayGoPrefetch(int handle, const uint16_t* chunkIds, uint32_t numberOfEntries, int8_t minimumLocus); +int APS5_VABI scePlayGoPrefetchOptionalChunk(int handle, int32_t type, const PlayGoOptionalChunk* option); +int APS5_VABI scePlayGoSetInstallSpeed(int handle, int32_t speed); +int APS5_VABI scePlayGoSetToDoList(int handle, const PlayGoToDo* todoList, uint32_t numberOfEntries); +int APS5_VABI scePlayGoTerminate(void); +int APS5_VABI scePlayGoRequestNextChunk(); +int APS5_VABI scePlayGoSetLanguageMask(int handle, uint64_t languageMask); +int APS5_VABI scePlayGoSnapshot(); + +} diff --git a/core/libs/prx/libScePlayGo/libScePlayGo.hpp b/core/libs/prx/libScePlayGo/libScePlayGo.hpp new file mode 100644 index 0000000000..be60245c2e --- /dev/null +++ b/core/libs/prx/libScePlayGo/libScePlayGo.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePlayGo/include/Api.hpp" diff --git a/core/libs/prx/libScePlayGoDialog/Export.cpp b/core/libs/prx/libScePlayGoDialog/Export.cpp index 65b0b8eba1..084d94c5b3 100644 --- a/core/libs/prx/libScePlayGoDialog/Export.cpp +++ b/core/libs/prx/libScePlayGoDialog/Export.cpp @@ -1,4 +1,7 @@ +#include "prx/libScePlayGoDialog/include/Api.hpp" #include +#include +#include #include #include #include @@ -6,6 +9,8 @@ #include "prx/libc/include/General.hpp" static_assert(sizeof(PlayGoDialogResult) == 40); +static_assert(offsetof(PlayGoDialogResult, result) == 4); +static_assert(offsetof(PlayGoDialogResult, reserved) == 8); namespace { @@ -15,11 +20,13 @@ constexpr std::int32_t STATUS_RUNNING = 2; constexpr std::int32_t STATUS_FINISHED = 3; constexpr std::int32_t RESULT_OK = 0; +constexpr std::int32_t ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr std::int32_t ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); -constexpr std::int32_t ERROR_NOT_INITIALIZED = static_cast(0x80ED0001); -constexpr std::int32_t ERROR_ALREADY_INITIALIZED = static_cast(0x80ED0002); -constexpr std::int32_t ERROR_PARAM_INVALID = static_cast(0x80ED0003); -constexpr std::int32_t ERROR_INVALID_STATE = static_cast(0x80ED0005); +constexpr std::int32_t ERROR_NOT_INITIALIZED = static_cast(0x80B80003); +constexpr std::int32_t ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004); +constexpr std::int32_t ERROR_PARAM_INVALID = static_cast(0x80B8000A); +constexpr std::int32_t ERROR_INVALID_STATE = static_cast(0x80B80007); constexpr std::int32_t COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); std::atomic g_status{STATUS_NONE}; @@ -44,7 +51,8 @@ int APS5_VABI scePlayGoDialogTerminate(void) { } int APS5_VABI scePlayGoDialogOpen(const void* param) { - if (!param) return ERROR_PARAM_INVALID; + if (!param) return ERROR_ARG_NULL; + if (g_status.load() == STATUS_NONE) return ERROR_NOT_INITIALIZED; int expected = STATUS_INITIALIZED; if (!g_status.compare_exchange_strong(expected, STATUS_FINISHED)) { expected = STATUS_FINISHED; @@ -55,6 +63,7 @@ int APS5_VABI scePlayGoDialogOpen(const void* param) { int APS5_VABI scePlayGoDialogClose(void) { if (g_status.load() == STATUS_NONE) return ERROR_NOT_INITIALIZED; + if (g_status.load() != STATUS_RUNNING) return ERROR_NOT_RUNNING; g_status.store(STATUS_FINISHED); return 0; } @@ -65,13 +74,13 @@ int APS5_VABI scePlayGoDialogUpdateStatus(void) { return g_status.load(); } -int APS5_VABI scePlayGoDialogGetResult(void* result) { +int APS5_VABI scePlayGoDialogGetResult(PlayGoDialogResult* result) { + if (result == nullptr) return ERROR_ARG_NULL; + if (!std::all_of(std::begin(result->reserved), std::end(result->reserved), [](std::uint8_t value) { return value == 0; })) return ERROR_PARAM_INVALID; if (g_status.load() == STATUS_NONE) return ERROR_NOT_INITIALIZED; - if (!result) return ERROR_PARAM_INVALID; if (g_status.load() != STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - auto* r = static_cast(result); - std::memset(r, 0, sizeof(*r)); - r->result = RESULT_OK; + *result = {}; + result->result = RESULT_OK; return 0; } diff --git a/core/libs/prx/libScePlayGoDialog/include/Api.hpp b/core/libs/prx/libScePlayGoDialog/include/Api.hpp new file mode 100644 index 0000000000..99f0428e67 --- /dev/null +++ b/core/libs/prx/libScePlayGoDialog/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePlayGoDialogInitialize(void); +int APS5_VABI scePlayGoDialogTerminate(void); +int APS5_VABI scePlayGoDialogOpen(const void* param); +int APS5_VABI scePlayGoDialogClose(void); +int APS5_VABI scePlayGoDialogUpdateStatus(void); +int APS5_VABI scePlayGoDialogGetResult(PlayGoDialogResult* result); +int APS5_VABI scePlayGoDialogGetStatus(void); + +} diff --git a/core/libs/prx/libScePlayGoDialog/libScePlayGoDialog.hpp b/core/libs/prx/libScePlayGoDialog/libScePlayGoDialog.hpp new file mode 100644 index 0000000000..3b805577f0 --- /dev/null +++ b/core/libs/prx/libScePlayGoDialog/libScePlayGoDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePlayGoDialog/include/Api.hpp" diff --git a/core/libs/prx/libScePlayReady/Export.cpp b/core/libs/prx/libScePlayReady/Export.cpp index 59330d9d35..4c7e5fe395 100644 --- a/core/libs/prx/libScePlayReady/Export.cpp +++ b/core/libs/prx/libScePlayReady/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePlayReady/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -216,8 +217,7 @@ int APS5_VABI scePlayReadyMemFree() { } int APS5_VABI scePlayReadyMemGetStats() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80004001); } int APS5_VABI scePlayReadyMemRealloc() { diff --git a/core/libs/prx/libScePlayReady/include/Api.hpp b/core/libs/prx/libScePlayReady/include/Api.hpp new file mode 100644 index 0000000000..6fb478bbda --- /dev/null +++ b/core/libs/prx/libScePlayReady/include/Api.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceBufferAlloc(); +int APS5_VABI sceBufferDup(); +int APS5_VABI sceBufferFree(); +int APS5_VABI sceBufferInit(); +int APS5_VABI sceBufferRealloc(); +int APS5_VABI scePlayReadyApiInitializeInitParams(); +int APS5_VABI scePlayReadyApiIsStarted(); +int APS5_VABI scePlayReadyApiStart(); +int APS5_VABI scePlayReadyApiStop(); +int APS5_VABI scePlayReadyApiVersion(); +int APS5_VABI scePlayReadyApiVersionString(); +int APS5_VABI scePlayReadyBufferFree(); +int APS5_VABI scePlayReadyBufferInit(); +int APS5_VABI scePlayReadyBufferMalloc(); +int APS5_VABI scePlayReadyBufferRealloc(); +int APS5_VABI scePlayReadyContentGetProperty(); +int APS5_VABI scePlayReadyContentSetProperty(); +int APS5_VABI scePlayReadyDomainCertEnumFini(); +int APS5_VABI scePlayReadyDomainCertEnumInit(); +int APS5_VABI scePlayReadyDomainCertEnumNext(); +int APS5_VABI scePlayReadyDomainCertFind(); +int APS5_VABI scePlayReadyDomainJoinGenerateChallenge(); +int APS5_VABI scePlayReadyDomainJoinProcessResponse(); +int APS5_VABI scePlayReadyDomainLeaveGenerateChallenge(); +int APS5_VABI scePlayReadyDomainLeaveProcessResponse(); +int APS5_VABI scePlayReadyEnvelopeClose(); +int APS5_VABI scePlayReadyEnvelopeGetSize(); +int APS5_VABI scePlayReadyEnvelopeInitializeRead(); +int APS5_VABI scePlayReadyEnvelopeOpen(); +int APS5_VABI scePlayReadyEnvelopeRead(); +int APS5_VABI scePlayReadyEnvelopeSeek(); +int APS5_VABI scePlayReadyFinalize(); +int APS5_VABI scePlayReadyFreeDecryptContext(); +int APS5_VABI scePlayReadyInitialize(); +int APS5_VABI scePlayReadyInitializeInitParams(); +int APS5_VABI scePlayReadyLicenseAcqGenerateAck(); +int APS5_VABI scePlayReadyLicenseAcqGenerateChallenge(); +int APS5_VABI scePlayReadyLicenseAcqGetAdditionalResponseData(); +int APS5_VABI scePlayReadyLicenseAcqProcessAckResponse(); +int APS5_VABI scePlayReadyLicenseAcqProcessResponse(); +int APS5_VABI scePlayReadyMemAlloc(); +int APS5_VABI scePlayReadyMemFree(); +int APS5_VABI scePlayReadyMemGetStats(); +int APS5_VABI scePlayReadyMemRealloc(); +int APS5_VABI scePlayReadyNewDecryptContext(); +int APS5_VABI scePlayReadyReaderBind(); +int APS5_VABI scePlayReadyReaderCommit(); +int APS5_VABI scePlayReadyReaderDecrypt(); +int APS5_VABI scePlayReadyReset(); +int APS5_VABI scePlayReadyStoreCleanup(); + +} diff --git a/core/libs/prx/libScePlayReady/libScePlayReady.hpp b/core/libs/prx/libScePlayReady/libScePlayReady.hpp new file mode 100644 index 0000000000..3138b60f6b --- /dev/null +++ b/core/libs/prx/libScePlayReady/libScePlayReady.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePlayReady/include/Api.hpp" diff --git a/core/libs/prx/libScePlayReady2/Export.cpp b/core/libs/prx/libScePlayReady2/Export.cpp index 2b08a30037..0ad7e3f73d 100644 --- a/core/libs/prx/libScePlayReady2/Export.cpp +++ b/core/libs/prx/libScePlayReady2/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePlayReady2/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libScePlayReady2/include/Api.hpp b/core/libs/prx/libScePlayReady2/include/Api.hpp new file mode 100644 index 0000000000..3e388e5dc6 --- /dev/null +++ b/core/libs/prx/libScePlayReady2/include/Api.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePlayReadyBufferInitWithData(); +int APS5_VABI scePlayReadyCloneDecryptContext(); +int APS5_VABI scePlayReadyDebugPrintf(); +int APS5_VABI scePlayReadyDebugSetLevel(); +int APS5_VABI scePlayReadyLicenseDelete(); +int APS5_VABI scePlayReadyLicenseDeleteInMemory(); +int APS5_VABI scePlayReadyLicenseGetExpirationTime(); +int APS5_VABI scePlayReadyLicenseGetProperty(); +int APS5_VABI scePlayReadyReaderClose(); +int APS5_VABI scePlayReadyReaderDecryptMultiple(); +int APS5_VABI scePlayReadyReaderDecryptMultipleOpaque(); +int APS5_VABI scePlayReadyReaderDecryptOpaque(); +int APS5_VABI scePlayReadyReaderDecrypt_Legacy(); +int APS5_VABI scePlayReadyReaderFreeOpaqueContent(); +int APS5_VABI scePlayReadyReaderRetrieveOpaqueContent(); +int APS5_VABI scePlayReadyReinitialize(); + +} diff --git a/core/libs/prx/libScePlayReady2/libScePlayReady2.hpp b/core/libs/prx/libScePlayReady2/libScePlayReady2.hpp new file mode 100644 index 0000000000..48fc0e9766 --- /dev/null +++ b/core/libs/prx/libScePlayReady2/libScePlayReady2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePlayReady2/include/Api.hpp" diff --git a/core/libs/prx/libScePlayReady4/Export.cpp b/core/libs/prx/libScePlayReady4/Export.cpp index 0454139571..9d4bbeea93 100644 --- a/core/libs/prx/libScePlayReady4/Export.cpp +++ b/core/libs/prx/libScePlayReady4/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePlayReady4/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libScePlayReady4/include/Api.hpp b/core/libs/prx/libScePlayReady4/include/Api.hpp new file mode 100644 index 0000000000..cef37c1859 --- /dev/null +++ b/core/libs/prx/libScePlayReady4/include/Api.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePlayReadyApiPlatform(); +int APS5_VABI scePlayReadyCdmiClose(); +int APS5_VABI scePlayReadyCdmiCreateMediaKeySession(); +int APS5_VABI scePlayReadyCdmiCreateMediaKeySystemAccess(); +int APS5_VABI scePlayReadyCdmiCreateMediaKeys(); +int APS5_VABI scePlayReadyCdmiDecryptMultipleOpaque(); +int APS5_VABI scePlayReadyCdmiDecryptOpaque(); +int APS5_VABI scePlayReadyCdmiDecryptOpaque2(); +int APS5_VABI scePlayReadyCdmiDestroyMediaKeySession(); +int APS5_VABI scePlayReadyCdmiDestroyMediaKeySystemAccess(); +int APS5_VABI scePlayReadyCdmiDestroyMediaKeys(); +int APS5_VABI scePlayReadyCdmiFreeOpaqueDecryptedContent(); +int APS5_VABI scePlayReadyCdmiGenerateRequest(); +int APS5_VABI scePlayReadyCdmiGetConfiguration(); +int APS5_VABI scePlayReadyCdmiGetExpiration(); +int APS5_VABI scePlayReadyCdmiGetKeySystem(); +int APS5_VABI scePlayReadyCdmiGetMediaKeyCount(); +int APS5_VABI scePlayReadyCdmiGetMediaKeyStatus(); +int APS5_VABI scePlayReadyCdmiGetSessionAppContext(); +int APS5_VABI scePlayReadyCdmiGetSessionId(); +int APS5_VABI scePlayReadyCdmiIsLicenseAckRequired(); +int APS5_VABI scePlayReadyCdmiLoad(); +int APS5_VABI scePlayReadyCdmiRemove(); +int APS5_VABI scePlayReadyCdmiRetrieveOpaqueDecryptedContent(); +int APS5_VABI scePlayReadyCdmiSetServerCertificate(); +int APS5_VABI scePlayReadyCdmiSetSessionCallbacks(); +int APS5_VABI scePlayReadyCdmiUpdate(); +int APS5_VABI scePlayReadyLicenseCount(); +int APS5_VABI scePlayReadyProfileClear(); +int APS5_VABI scePlayReadyProfileEnter(); +int APS5_VABI scePlayReadyProfileLeave(); +int APS5_VABI scePlayReadyProfilePause(); +int APS5_VABI scePlayReadyProfileResume(); +int APS5_VABI scePlayReadyProfileStart(); +int APS5_VABI scePlayReadyProfileStop(); + +} diff --git a/core/libs/prx/libScePlayReady4/libScePlayReady4.hpp b/core/libs/prx/libScePlayReady4/libScePlayReady4.hpp new file mode 100644 index 0000000000..97c4cdd3e7 --- /dev/null +++ b/core/libs/prx/libScePlayReady4/libScePlayReady4.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePlayReady4/include/Api.hpp" diff --git a/core/libs/prx/libScePlayerInvitationDialog/Export.cpp b/core/libs/prx/libScePlayerInvitationDialog/Export.cpp index 86095da370..0f78112135 100644 --- a/core/libs/prx/libScePlayerInvitationDialog/Export.cpp +++ b/core/libs/prx/libScePlayerInvitationDialog/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.h" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.hpp b/core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.hpp new file mode 100644 index 0000000000..be74cdea73 --- /dev/null +++ b/core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.h" diff --git a/core/libs/prx/libScePlayerSelectionDialog/CMakeLists.txt b/core/libs/prx/libScePlayerSelectionDialog/CMakeLists.txt index 670b5b3b0a..0d85abead0 100644 --- a/core/libs/prx/libScePlayerSelectionDialog/CMakeLists.txt +++ b/core/libs/prx/libScePlayerSelectionDialog/CMakeLists.txt @@ -1,11 +1,12 @@ add_library(libScePlayerSelectionDialog SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libScePlayerSelectionDialog PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libScePlayerSelectionDialog) +target_link_libraries(libScePlayerSelectionDialog PRIVATE libSceUserService) if(BUILD_TESTING) add_test_executable(guest_player_selection_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestPlayerSelectionDialog.cpp) target_include_directories(guest_player_selection_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_player_selection_dialog_tests PRIVATE libScePlayerSelectionDialog libc) + target_link_libraries(guest_player_selection_dialog_tests PRIVATE libScePlayerSelectionDialog libSceUserService libc) configure_windows_unwind(guest_player_selection_dialog_tests) add_test(NAME guest_player_selection_dialog COMMAND guest_player_selection_dialog_tests) if(WIN32) diff --git a/core/libs/prx/libScePlayerSelectionDialog/Export.cpp b/core/libs/prx/libScePlayerSelectionDialog/Export.cpp index 4f9c50b9a7..52b3363adb 100644 --- a/core/libs/prx/libScePlayerSelectionDialog/Export.cpp +++ b/core/libs/prx/libScePlayerSelectionDialog/Export.cpp @@ -1,8 +1,11 @@ +#include "prx/libScePlayerSelectionDialog/include/Api.hpp" +#include "prx/libSceUserService/UserService.hpp" #include #include #include #include #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" namespace { @@ -12,9 +15,10 @@ constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_INVALID_ARGUMENT = static_cast(0x80B8000Au); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; @@ -30,17 +34,30 @@ int APS5_VABI scePlayerSelectionDialogInitialize(void) { return 0; } -int APS5_VABI scePlayerSelectionDialogOpen(const void* param) { +int APS5_VABI scePlayerSelectionDialogOpen(const PlayerSelectionDialogParam* param) { + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (!ValidateCommonDialogBaseParam(param->base)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (param->size != sizeof(*param) || param->userId == -1) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + UserServiceLoginUserIdList users{}; + if (sceUserServiceGetLoginUserIdList(&users) != 0 || std::find(std::begin(users.user_id), std::end(users.user_id), param->userId) == std::end(users.user_id)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (param->maxPlayers > 256 || param->mode > 3 || param->playerCount > 256) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (param->playerCount != 0 && param->players == nullptr) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + for (std::uint32_t index = 0; index < param->playerCount; ++index) { + const auto& player = param->players[index]; + if (player.accountId == 0 || player.option0 > 1 || player.option1 > 1 || player.mode > 2 || !std::all_of(std::begin(player.reserved), std::end(player.reserved), [](std::uint8_t value) { return value == 0; })) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (param->maxPlayers == 1 && player.option0 == 1) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + if (!std::all_of(std::begin(param->reserved), std::end(param->reserved), [](std::uint8_t value) { return value == 0; })) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; - if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } int APS5_VABI scePlayerSelectionDialogClose(void) { if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (g_status.load() != COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_NOT_RUNNING; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } @@ -53,14 +70,13 @@ int APS5_VABI scePlayerSelectionDialogGetStatus(void) { return g_status.load(); } -int APS5_VABI scePlayerSelectionDialogGetResult(void* result) { +int APS5_VABI scePlayerSelectionDialogGetResult(PlayerSelectionDialogResult* result) { + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; - std::memcpy(result, &canceled, sizeof(canceled)); - return 0; + result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + return COMMON_DIALOG_RESULT_USER_CANCELED; } int APS5_VABI scePlayerSelectionDialogParamInitialize(void* param) { diff --git a/core/libs/prx/libScePlayerSelectionDialog/include/Api.hpp b/core/libs/prx/libScePlayerSelectionDialog/include/Api.hpp new file mode 100644 index 0000000000..baf1617f53 --- /dev/null +++ b/core/libs/prx/libScePlayerSelectionDialog/include/Api.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePlayerSelectionDialogInitialize(void); +int APS5_VABI scePlayerSelectionDialogOpen(const PlayerSelectionDialogParam* param); +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 scePlayerSelectionDialogTerminate(void); + +} diff --git a/core/libs/prx/libScePlayerSelectionDialog/libScePlayerSelectionDialog.hpp b/core/libs/prx/libScePlayerSelectionDialog/libScePlayerSelectionDialog.hpp new file mode 100644 index 0000000000..2e72898b93 --- /dev/null +++ b/core/libs/prx/libScePlayerSelectionDialog/libScePlayerSelectionDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePlayerSelectionDialog/include/Api.hpp" diff --git a/core/libs/prx/libScePngDec/Export.cpp b/core/libs/prx/libScePngDec/Export.cpp index 419c7966f0..c4404f2e40 100644 --- a/core/libs/prx/libScePngDec/Export.cpp +++ b/core/libs/prx/libScePngDec/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePngDec/include/Api.hpp" #include #include #include @@ -28,8 +29,8 @@ constexpr std::int32_t SCE_PNG_DEC_ERROR_DECODE_ERROR = static_castthis_size != sizeof(PngDecCreateParam)) return SCE_PNG_DEC_ERROR_INVALID_SIZE; if (param->attribute != ATTRIBUTE_NONE && param->attribute != ATTRIBUTE_BIT_DEPTH_16) return SCE_PNG_DEC_ERROR_INVALID_PARAM; if (param->max_image_width - 1 >= MAX_IMAGE_WIDTH) return SCE_PNG_DEC_ERROR_INVALID_PARAM; @@ -102,42 +109,44 @@ std::span toBytes(const void* data, std::uint32_t size) { return {static_cast(data), size}; } -} // namespace +} // namespace extern "C" { -int32_t APS5_VABI scePngDecCreate(const PngDecCreateParam* param, void* memory_address, uint32_t memory_size, void** handle) { +int32_t APS5_VABI scePngDecCreate(const PngDecCreateParam* param, void* memoryAddress, uint32_t memorySize, void** handle) { const std::int32_t result = validateCreateParam(param); if (result != 0) return result; - if (!memory_address || !handle) return SCE_PNG_DEC_ERROR_INVALID_ADDR; - if (memory_size < MEMORY_SIZE) return SCE_PNG_DEC_ERROR_INVALID_WORK_MEMORY; - const auto address = reinterpret_cast(memory_address); + if (!memoryAddress) return SCE_PNG_DEC_ERROR_INVALID_ADDR; + if (memorySize < requiredMemory(*param)) return SCE_PNG_DEC_ERROR_INVALID_SIZE; + if (!handle) return SCE_PNG_DEC_ERROR_INVALID_ADDR; + const auto address = reinterpret_cast(memoryAddress); const std::uintptr_t aligned = (address + HANDLE_ALIGNMENT - 1) & ~(HANDLE_ALIGNMENT - 1); - auto* context = new (reinterpret_cast(aligned)) Context{}; + auto* context = new (reinterpret_cast(aligned)) Context; context->self = context; + context->size = sizeof(PngDecCreateParam); context->attribute = param->attribute; - context->maxImageWidth = param->max_image_width; + context->maxImageWidth = (param->max_image_width + 7u) & ~7u; + context->memorySize = requiredMemory(*param) - 144u; *handle = context; return 0; } -int32_t APS5_VABI scePngDecDecode(void* handle, const PngDecDecodeParam* param, PngDecImageInfo* image_info) { +int32_t APS5_VABI scePngDecDecode(void* handle, const PngDecDecodeParam* param, PngDecImageInfo* imageInfo) { const Context* context = toContext(handle); if (!context) return SCE_PNG_DEC_ERROR_INVALID_HANDLE; - if (!param) return SCE_PNG_DEC_ERROR_INVALID_PARAM; + if (!param) return SCE_PNG_DEC_ERROR_INVALID_ADDR; if (!param->png_mem_addr || !param->image_mem_addr || (reinterpret_cast(param->image_mem_addr) & 3u) != 0) return SCE_PNG_DEC_ERROR_INVALID_ADDR; if (param->png_mem_size == 0 || param->image_mem_size == 0) return SCE_PNG_DEC_ERROR_INVALID_SIZE; + if (param->pixel_format == 132) throw std::runtime_error("scePngDecDecode: pixel format 132 is not implemented"); if (param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) return SCE_PNG_DEC_ERROR_INVALID_PARAM; if (param->alpha_value > 0xFF) return SCE_PNG_DEC_ERROR_INVALID_PARAM; const std::span png = toBytes(param->png_mem_addr, param->png_mem_size); const std::optional header = Decoder::Png::ParseHeader(png); if (!header) return SCE_PNG_DEC_ERROR_INVALID_DATA; - if (image_info) fillImageInfo(*header, image_info); - if (header->bitDepth == 16 && context->attribute == ATTRIBUTE_BIT_DEPTH_16) { - throw std::runtime_error("scePngDecDecode: 16-bit output is not implemented"); - } - + if (imageInfo) fillImageInfo(*header, imageInfo); + const bool packedDimensions = header->width <= MAX_PACKED_DIMENSION && header->height <= MAX_PACKED_DIMENSION; + if (!imageInfo && !packedDimensions) return SCE_PNG_DEC_ERROR_INVALID_ADDR; const std::uint64_t rowSize = static_cast(header->width) * BYTES_PER_PIXEL; const std::uint64_t pitch = param->image_pitch == 0 ? rowSize : param->image_pitch; if (param->image_pitch != 0 && (pitch < rowSize || (pitch & 3u) != 0)) return SCE_PNG_DEC_ERROR_INVALID_PARAM; @@ -149,6 +158,7 @@ int32_t APS5_VABI scePngDecDecode(void* handle, const PngDecDecodeParam* param, const std::uint64_t alignedWidth = (static_cast(header->width) + 7u) & ~std::uint64_t{7u}; const std::uint64_t alignedMaxWidth = (static_cast(context->maxImageWidth) + 7u) & ~std::uint64_t{7u}; if (alignedWidth > alignedMaxWidth) return SCE_PNG_DEC_ERROR_INVALID_WORK_MEMORY; + if (header->bitDepth > (8u << (context->attribute & 31u))) return SCE_PNG_DEC_ERROR_INVALID_WORK_MEMORY; const std::optional image = Decoder::Png::Decode(png); if (!image || image->width != header->width || image->height != header->height) return SCE_PNG_DEC_ERROR_DECODE_ERROR; @@ -168,7 +178,7 @@ int32_t APS5_VABI scePngDecDecode(void* handle, const PngDecDecodeParam* param, } } - if (header->width > MAX_PACKED_DIMENSION || header->height > MAX_PACKED_DIMENSION) return 0; + if (!packedDimensions) return 0; return static_cast(header->width << 16 | header->height); } @@ -179,20 +189,22 @@ int32_t APS5_VABI scePngDecDelete(void* handle) { return 0; } -int32_t APS5_VABI scePngDecParseHeader(const PngDecParseParam* param, PngDecImageInfo* image_info) { - if (!param) return SCE_PNG_DEC_ERROR_INVALID_PARAM; - if (!param->png_mem_addr || !image_info) return SCE_PNG_DEC_ERROR_INVALID_ADDR; +int32_t APS5_VABI scePngDecParseHeader(const PngDecParseParam* param, PngDecImageInfo* imageInfo) { + if (!param) return SCE_PNG_DEC_ERROR_INVALID_ADDR; + if (!param->png_mem_addr) return SCE_PNG_DEC_ERROR_INVALID_ADDR; if (param->png_mem_size == 0) return SCE_PNG_DEC_ERROR_INVALID_SIZE; + if (param->reserved0 != 0) return SCE_PNG_DEC_ERROR_INVALID_PARAM; + if (!imageInfo) return SCE_PNG_DEC_ERROR_INVALID_ADDR; const std::optional header = Decoder::Png::ParseHeader(toBytes(param->png_mem_addr, param->png_mem_size)); if (!header) return SCE_PNG_DEC_ERROR_INVALID_DATA; - fillImageInfo(*header, image_info); + fillImageInfo(*header, imageInfo); return 0; } int32_t APS5_VABI scePngDecQueryMemorySize(const PngDecCreateParam* param) { const std::int32_t result = validateCreateParam(param); if (result != 0) return result; - return static_cast(MEMORY_SIZE); + return static_cast(requiredMemory(*param)); } int APS5_VABI scePngDecDecodeWithInputControl() { diff --git a/core/libs/prx/libScePngDec/include/Api.hpp b/core/libs/prx/libScePngDec/include/Api.hpp new file mode 100644 index 0000000000..1440fbcf5a --- /dev/null +++ b/core/libs/prx/libScePngDec/include/Api.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int32_t APS5_VABI scePngDecCreate(const PngDecCreateParam* param, void* memoryAddress, uint32_t memorySize, void** handle); +int32_t APS5_VABI scePngDecDecode(void* handle, const PngDecDecodeParam* param, PngDecImageInfo* imageInfo); +int32_t APS5_VABI scePngDecDelete(void* handle); +int32_t APS5_VABI scePngDecParseHeader(const PngDecParseParam* param, PngDecImageInfo* imageInfo); +int32_t APS5_VABI scePngDecQueryMemorySize(const PngDecCreateParam* param); +int APS5_VABI scePngDecDecodeWithInputControl(); + +} diff --git a/core/libs/prx/libScePngDec/libScePngDec.hpp b/core/libs/prx/libScePngDec/libScePngDec.hpp new file mode 100644 index 0000000000..27bf3452a2 --- /dev/null +++ b/core/libs/prx/libScePngDec/libScePngDec.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePngDec/include/Api.hpp" diff --git a/core/libs/prx/libScePngEnc/Export.cpp b/core/libs/prx/libScePngEnc/Export.cpp index 4ff0af04e3..75f4ad13f1 100644 --- a/core/libs/prx/libScePngEnc/Export.cpp +++ b/core/libs/prx/libScePngEnc/Export.cpp @@ -1,4 +1,7 @@ +#include "prx/libScePngEnc/include/Api.hpp" #include +#include +#include #include #include #include @@ -18,7 +21,7 @@ constexpr int PNG_ENC_ERROR_FATAL = static_cast(0x80690120); constexpr std::uint32_t MAX_IMAGE_WIDTH = 1000000; constexpr std::uint32_t MAX_IMAGE_HEIGHT = 1000000; -constexpr std::uint32_t MAX_FILTER_NUMBER = 5; +constexpr std::uint32_t MAX_FILTER_NUMBER = 4; constexpr std::uint16_t COLOR_SPACE_RGB = 3; constexpr std::uint16_t COLOR_SPACE_RGBA = 19; constexpr std::uint16_t PIXEL_FORMAT_R8G8B8A8 = 0; @@ -28,12 +31,15 @@ constexpr std::uint16_t FILTER_UP = 2; constexpr std::uint16_t FILTER_AVERAGE = 4; constexpr std::uint16_t FILTER_PAETH = 8; constexpr std::uint16_t FILTER_ALL = FILTER_SUB | FILTER_UP | FILTER_AVERAGE | FILTER_PAETH; -constexpr std::uint64_t CONTEXT_MAGIC = 0x434E45474E505341ull; +constexpr std::uintptr_t HANDLE_ALIGNMENT = 16; struct PngEncContext { - std::uint64_t magic; - std::uint32_t maxImageWidth; + PngEncContext* self; + std::uint32_t size; std::uint32_t reserved; + std::uint32_t maxImageWidth; + std::uint32_t maxFilterNumber; + std::uint32_t scratchSize; }; static_assert(sizeof(PngEncCreateParam) == 16); @@ -42,20 +48,25 @@ static_assert(sizeof(PngEncOutputInfo) == 8); int validateCreateParam(const PngEncCreateParam* param) { if (!param) return PNG_ENC_ERROR_INVALID_ADDR; - if (param->attribute != 0 || param->max_filter_number > MAX_FILTER_NUMBER) return PNG_ENC_ERROR_INVALID_PARAM; - if (param->max_image_width == 0 || param->max_image_width > MAX_IMAGE_WIDTH) return PNG_ENC_ERROR_INVALID_SIZE; + if (param->this_size != sizeof(PngEncCreateParam)) return PNG_ENC_ERROR_INVALID_SIZE; + if (param->attribute != 0 || param->max_image_width == 0 || param->max_image_width > MAX_IMAGE_WIDTH || param->max_filter_number > MAX_FILTER_NUMBER) return PNG_ENC_ERROR_INVALID_PARAM; return 0; } +std::uint32_t workMemorySize(const PngEncCreateParam& param) { + return 285808 + ((4 * param.max_image_width + 28) & ~std::uint32_t{31}) * (param.max_filter_number + 2); +} + PngEncContext* context(void* handle) { + if (reinterpret_cast(handle) % HANDLE_ALIGNMENT != 0) return nullptr; auto* ctx = static_cast(handle); - return ctx && ctx->magic == CONTEXT_MAGIC ? ctx : nullptr; + return ctx && ctx->self == ctx ? ctx : nullptr; } std::uint8_t filterSet(std::uint16_t filterType) { if (filterType == FILTER_ALL) return Decoder::Png::FILTER_ALL; if (filterType == 0) return Decoder::Png::FILTER_NONE; - std::uint8_t filters = 0; + std::uint8_t filters = Decoder::Png::FILTER_NONE; if (filterType & FILTER_SUB) filters |= Decoder::Png::FILTER_SUB; if (filterType & FILTER_UP) filters |= Decoder::Png::FILTER_UP; if (filterType & FILTER_AVERAGE) filters |= Decoder::Png::FILTER_AVERAGE; @@ -69,41 +80,50 @@ extern "C" { int APS5_VABI scePngEncQueryMemorySize(const PngEncCreateParam* param) { if (const int result = validateCreateParam(param); result != 0) return result; - return sizeof(PngEncContext); + return static_cast(workMemorySize(*param)); } int APS5_VABI scePngEncCreate(const PngEncCreateParam* param, void* memoryAddress, uint32_t memorySize, void** handle) { if (const int result = validateCreateParam(param); result != 0) return result; - if (!memoryAddress || !handle) return PNG_ENC_ERROR_INVALID_ADDR; - if (memorySize < sizeof(PngEncContext)) return PNG_ENC_ERROR_INVALID_SIZE; - *static_cast(memoryAddress) = {CONTEXT_MAGIC, param->max_image_width, 0}; - *handle = memoryAddress; + if (!memoryAddress) return PNG_ENC_ERROR_INVALID_ADDR; + const std::uint32_t requiredSize = workMemorySize(*param); + if (memorySize < requiredSize) return PNG_ENC_ERROR_INVALID_SIZE; + if (!handle) return PNG_ENC_ERROR_INVALID_ADDR; + const std::uintptr_t aligned = (reinterpret_cast(memoryAddress) + HANDLE_ALIGNMENT - 1) & ~(HANDLE_ALIGNMENT - 1); + auto* ctx = new (reinterpret_cast(aligned)) PngEncContext{}; + ctx->self = ctx; + ctx->size = sizeof(PngEncCreateParam); + ctx->maxImageWidth = (param->max_image_width + 7) & ~std::uint32_t{7}; + ctx->maxFilterNumber = param->max_filter_number; + ctx->scratchSize = requiredSize - 112; + *handle = ctx; return 0; } int APS5_VABI scePngEncDelete(void* handle) { PngEncContext* ctx = context(handle); if (!ctx) return PNG_ENC_ERROR_INVALID_HANDLE; - ctx->magic = 0; + ctx->self = nullptr; return 0; } int APS5_VABI scePngEncEncode(void* handle, const PngEncEncodeParam* param, PngEncOutputInfo* outputInfo) { const PngEncContext* ctx = context(handle); if (!ctx) return PNG_ENC_ERROR_INVALID_HANDLE; - if (!param) return PNG_ENC_ERROR_INVALID_PARAM; - if (!param->image_mem_addr || !param->png_mem_addr) return PNG_ENC_ERROR_INVALID_ADDR; - if ((param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) - || (param->color_space != COLOR_SPACE_RGB && param->color_space != COLOR_SPACE_RGBA) || param->bit_depth != 8 - || param->clut_number != 0 || (param->filter_type & ~FILTER_ALL) != 0 || param->compression_level > 9) { - return PNG_ENC_ERROR_INVALID_PARAM; - } - + if (!param) return PNG_ENC_ERROR_INVALID_ADDR; + if (!param->image_mem_addr || reinterpret_cast(param->image_mem_addr) % 4 != 0 || !param->png_mem_addr) return PNG_ENC_ERROR_INVALID_ADDR; + if (param->image_mem_size == 0 || param->png_mem_size == 0) return PNG_ENC_ERROR_INVALID_SIZE; const std::uint32_t width = param->image_width; const std::uint32_t height = param->image_height; - if (width == 0 || height == 0 || width > ctx->maxImageWidth || height > MAX_IMAGE_HEIGHT || param->png_mem_size == 0 - || param->image_pitch < width * 4 || static_cast(param->image_pitch) * (height - 1) + width * 4 > param->image_mem_size) { - return PNG_ENC_ERROR_INVALID_SIZE; + const std::uint64_t inputSize = static_cast(param->image_pitch) * height; + if (width == 0 || width > ctx->maxImageWidth || height == 0 || height > MAX_IMAGE_HEIGHT + || param->image_pitch == 0 || param->image_pitch % 4 != 0 || param->image_pitch > 0x0FFFFFFF || param->image_pitch / 4 < width + || inputSize > 0x7FFFFFFF || inputSize > param->image_mem_size) return PNG_ENC_ERROR_INVALID_PARAM; + if ((param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) + || (param->color_space != COLOR_SPACE_RGB && param->color_space != COLOR_SPACE_RGBA) || param->bit_depth != 8 + || param->clut_number != 0 || (param->filter_type & ~FILTER_ALL) != 0 + || static_cast(std::popcount(param->filter_type)) > ctx->maxFilterNumber || param->compression_level > 9) { + return PNG_ENC_ERROR_INVALID_PARAM; } const std::uint32_t channels = param->color_space == COLOR_SPACE_RGBA ? 4 : 3; diff --git a/core/libs/prx/libScePngEnc/include/Api.hpp b/core/libs/prx/libScePngEnc/include/Api.hpp new file mode 100644 index 0000000000..b279f174e8 --- /dev/null +++ b/core/libs/prx/libScePngEnc/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePngEncQueryMemorySize(const PngEncCreateParam* param); +int APS5_VABI scePngEncCreate(const PngEncCreateParam* param, void* memoryAddress, uint32_t memorySize, void** handle); +int APS5_VABI scePngEncDelete(void* handle); +int APS5_VABI scePngEncEncode(void* handle, const PngEncEncodeParam* param, PngEncOutputInfo* outputInfo); + +} diff --git a/core/libs/prx/libScePngEnc/libScePngEnc.hpp b/core/libs/prx/libScePngEnc/libScePngEnc.hpp new file mode 100644 index 0000000000..2355570756 --- /dev/null +++ b/core/libs/prx/libScePngEnc/libScePngEnc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePngEnc/include/Api.hpp" diff --git a/core/libs/prx/libScePngParser/libScePngParser.hpp b/core/libs/prx/libScePngParser/libScePngParser.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libScePngParser/libScePngParser.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libScePosixForWebKit/CMakeLists.txt b/core/libs/prx/libScePosixForWebKit/CMakeLists.txt index 036eb0577d..59d7ed5e61 100644 --- a/core/libs/prx/libScePosixForWebKit/CMakeLists.txt +++ b/core/libs/prx/libScePosixForWebKit/CMakeLists.txt @@ -45,4 +45,14 @@ if(BUILD_TESTING) set_tests_properties(guest_service_database PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_posix_thread_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixThreadName.cpp) + target_include_directories(guest_posix_thread_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_posix_thread_name_tests PRIVATE libScePosixForWebKit libkernel libc) + configure_windows_unwind(guest_posix_thread_name_tests) + add_test(NAME guest_posix_thread_name COMMAND guest_posix_thread_name_tests) + set_tests_properties(guest_posix_thread_name PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_posix_thread_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libScePosixForWebKit/Export.cpp b/core/libs/prx/libScePosixForWebKit/Export.cpp index ca47a2ecf7..9f7f7de9a5 100644 --- a/core/libs/prx/libScePosixForWebKit/Export.cpp +++ b/core/libs/prx/libScePosixForWebKit/Export.cpp @@ -1,14 +1,25 @@ +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libScePosixForWebKit/include/Api.hpp" +#include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/HostEntropy.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" #include #include #include #include +#include #include namespace { +constexpr int ThreadNameInvalid = 22; +constexpr int ThreadNameTooLong = 63; +constexpr std::size_t ThreadNameCapacity = 32; + constexpr int FnmNomatch = 1; constexpr int FnmNoescape = 0x01; constexpr int FnmPathname = 0x02; @@ -161,8 +172,8 @@ char* APS5_VABI strcasestr_nid_postfix(const char* text, const char* needle) { const char* candidate = text; const char* match = needle; while (*candidate != '\0' && *match != '\0' && - std::tolower(static_cast(*candidate)) == - std::tolower(static_cast(*match))) { + FoldCase(static_cast(*candidate), FnmCasefold) == + FoldCase(static_cast(*match), FnmCasefold)) { ++candidate; ++match; } @@ -178,17 +189,16 @@ void APS5_VABI arc4random_buf_nid_postfix(void* buffer, std::size_t size) { std::uint32_t APS5_VABI arc4random_nid_postfix() { std::uint32_t value; arc4random_buf_nid_postfix(&value, sizeof(value)); + return value; } -int APS5_VABI __wrap_access() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI __wrap_access(const char* path, int mode) { + return access_nid_postfix(path, mode); } -int APS5_VABI __wrap_clock_gettime() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI __wrap_clock_gettime(int clockId, KernelTimespec* tp) { + return clock_gettime_nid_postfix(clockId, tp); } int APS5_VABI __wrap_fchown() { @@ -196,14 +206,12 @@ int APS5_VABI __wrap_fchown() { return 0; } -int APS5_VABI __wrap_getcwd() { - NotImplemented_nid_no_patch(__func__); - return 0; +char* APS5_VABI __wrap_getcwd(char* buffer, std::size_t size) { + return getcwd_nid_postfix(buffer, size); } -int APS5_VABI __wrap_getenv() { - NotImplemented_nid_no_patch(__func__); - return 0; +char* APS5_VABI __wrap_getenv(const char* name) { + return getenv_nid_postfix(name); } int APS5_VABI __wrap_link() { @@ -211,14 +219,12 @@ int APS5_VABI __wrap_link() { return 0; } -int APS5_VABI __wrap_lstat() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI __wrap_lstat(const char* path, FileStat* sb) { + return lstat_nid_postfix(path, sb); } -int APS5_VABI __wrap_mmap() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI __wrap_mmap(void* address, std::size_t length, int protection, int flags, int descriptor, std::int64_t offset) { + return mmap_nid_postfix(address, length, protection, flags, descriptor, offset); } int APS5_VABI __wrap_readlink() { @@ -278,4 +284,12 @@ int APS5_VABI scePosixForWebKit_sysinfo() { return 0; } +int APS5_VABI pthread_setname_np_nid_postfix(Pthread thread, const char* name) { + if (thread == nullptr || name == nullptr) return ThreadNameInvalid; + if (std::strlen(name) >= ThreadNameCapacity) return ThreadNameTooLong; + std::lock_guard lock(thread->nameLock); + thread->name = name; + return 0; +} + } diff --git a/core/libs/prx/libScePosixForWebKit/Filesystem.cpp b/core/libs/prx/libScePosixForWebKit/Filesystem.cpp index a7e6f01754..10dfa7eed6 100644 --- a/core/libs/prx/libScePosixForWebKit/Filesystem.cpp +++ b/core/libs/prx/libScePosixForWebKit/Filesystem.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libScePosixForWebKit/include/Api.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -14,11 +17,6 @@ extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handle #include #endif -extern "C" { -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI open_nid_postfix(const char*, int, int); -} - namespace { constexpr char Characters[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; diff --git a/core/libs/prx/libScePosixForWebKit/Resolver.cpp b/core/libs/prx/libScePosixForWebKit/Resolver.cpp index f9b8a2acc2..6b0007a4b9 100644 --- a/core/libs/prx/libScePosixForWebKit/Resolver.cpp +++ b/core/libs/prx/libScePosixForWebKit/Resolver.cpp @@ -1,3 +1,5 @@ +#include "prx/libScePosixForWebKit/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -21,8 +23,6 @@ #include #include "GuestResolver.hpp" -extern "C" int* APS5_VABI sceNetErrnoLoc(); - namespace { struct HostLookup { GuestResolver::HostEntry entry{}; diff --git a/core/libs/prx/libScePosixForWebKit/include/Api.hpp b/core/libs/prx/libScePosixForWebKit/include/Api.hpp new file mode 100644 index 0000000000..bff7385004 --- /dev/null +++ b/core/libs/prx/libScePosixForWebKit/include/Api.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "prx/libScePosixForWebKit/GuestResolver.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI fnmatch_nid_postfix(const char* pattern, const char* string, int flags); +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(); +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(); +char* APS5_VABI __wrap_getcwd(char* buffer, std::size_t size); +char* APS5_VABI __wrap_getenv(const char* name); +int APS5_VABI __wrap_link(); +int APS5_VABI __wrap_lstat(const char* path, FileStat* sb); +void* APS5_VABI __wrap_mmap(void* address, std::size_t length, int protection, int flags, int descriptor, std::int64_t offset); +int APS5_VABI __wrap_readlink(); +int APS5_VABI __wrap_statvfs(); +int APS5_VABI __wrap_symlink(); +int APS5_VABI clock_gettime_np(); +int APS5_VABI getenv_np(); +int APS5_VABI scePosixForWebKit_gethostbyname_r(); +int APS5_VABI pthread_setname_np_nid_postfix(Pthread thread, const char* name); +int APS5_VABI mmap_np(); +int APS5_VABI msetname(); +int APS5_VABI pthread_setschedparam_np(); +int APS5_VABI setenv_np(); +int APS5_VABI scePosixForWebKit_sysinfo(); +int APS5_VABI mkstemp_nid_postfix(char* pattern); +int APS5_VABI isatty_nid_postfix(int descriptor); +GuestResolver::HostEntry* APS5_VABI gethostbyname_nid_postfix(const char* name); +GuestResolver::ServiceEntry* APS5_VABI getservbyname_nid_postfix(const char* name, const char* protocol); +void APS5_VABI freeaddrinfo_nid_postfix(GuestResolver::AddressInfo* first); +int APS5_VABI getaddrinfo_nid_postfix(const char* node, const char* service, const GuestResolver::AddressInfo* hints, GuestResolver::AddressInfo** output); +int APS5_VABI getnameinfo_nid_postfix(const void* address, std::uint32_t length, char* host, std::uint32_t hostLength, char* service, std::uint32_t serviceLength, int flags); +const char* APS5_VABI gai_strerror_nid_postfix(int error); + +} diff --git a/core/libs/prx/libScePosixForWebKit/libScePosixForWebKit.hpp b/core/libs/prx/libScePosixForWebKit/libScePosixForWebKit.hpp new file mode 100644 index 0000000000..0f4456a5e2 --- /dev/null +++ b/core/libs/prx/libScePosixForWebKit/libScePosixForWebKit.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePosixForWebKit/include/Api.hpp" diff --git a/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp index d1c11f4133..45c16b6916 100644 --- a/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp +++ b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceProprietaryVoiceChatHelperPrx/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/include/Api.hpp b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/include/Api.hpp new file mode 100644 index 0000000000..991a8ea2d2 --- /dev/null +++ b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceProprietaryVoiceChatHelperInitialize(void); +int APS5_VABI sceProprietaryVoiceChatHelperTerminate(void); +int APS5_VABI sceProprietaryVoiceChatHelperSetVoiceChatState(void); +int APS5_VABI sceProprietaryVoiceChatHelperGetVoiceChatUsageState(void); +int APS5_VABI sceProprietaryVoiceChatHelperGetVoiceChat(void); + +} diff --git a/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/libSceProprietaryVoiceChatHelperPrx.hpp b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/libSceProprietaryVoiceChatHelperPrx.hpp new file mode 100644 index 0000000000..9302848829 --- /dev/null +++ b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/libSceProprietaryVoiceChatHelperPrx.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceProprietaryVoiceChatHelperPrx/include/Api.hpp" diff --git a/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp b/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp index 99db1f489a..a27fb265fc 100644 --- a/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp +++ b/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePs2EmuMenuDialog/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libScePs2EmuMenuDialog/include/Api.hpp b/core/libs/prx/libScePs2EmuMenuDialog/include/Api.hpp new file mode 100644 index 0000000000..de66779d90 --- /dev/null +++ b/core/libs/prx/libScePs2EmuMenuDialog/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePs2EmuMenuDialogClose(); +int APS5_VABI scePs2EmuMenuDialogGetResult(); +int APS5_VABI scePs2EmuMenuDialogGetStatus(); +int APS5_VABI scePs2EmuMenuDialogInitialize(); +int APS5_VABI scePs2EmuMenuDialogOpen(); +int APS5_VABI scePs2EmuMenuDialogTerminate(); +int APS5_VABI scePs2EmuMenuDialogUpdateStatus(); + +} diff --git a/core/libs/prx/libScePs2EmuMenuDialog/libScePs2EmuMenuDialog.hpp b/core/libs/prx/libScePs2EmuMenuDialog/libScePs2EmuMenuDialog.hpp new file mode 100644 index 0000000000..f09948b438 --- /dev/null +++ b/core/libs/prx/libScePs2EmuMenuDialog/libScePs2EmuMenuDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePs2EmuMenuDialog/include/Api.hpp" diff --git a/core/libs/prx/libScePsm/libScePsm.hpp b/core/libs/prx/libScePsm/libScePsm.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libScePsm/libScePsm.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libScePsmUtil/Export.cpp b/core/libs/prx/libScePsmUtil/Export.cpp index 0a7d7939be..422f3863b3 100644 --- a/core/libs/prx/libScePsmUtil/Export.cpp +++ b/core/libs/prx/libScePsmUtil/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePsmUtil/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libScePsmUtil/include/Api.hpp b/core/libs/prx/libScePsmUtil/include/Api.hpp new file mode 100644 index 0000000000..15c45087d9 --- /dev/null +++ b/core/libs/prx/libScePsmUtil/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePsmUtilFinalize(); +int APS5_VABI scePsmUtilGetPsmIniFile(); +int APS5_VABI scePsmUtilInitialize(); +int APS5_VABI scePsmUtilRegisterCallbacks(); + +} diff --git a/core/libs/prx/libScePsmUtil/libScePsmUtil.hpp b/core/libs/prx/libScePsmUtil/libScePsmUtil.hpp new file mode 100644 index 0000000000..5c21b12f80 --- /dev/null +++ b/core/libs/prx/libScePsmUtil/libScePsmUtil.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePsmUtil/include/Api.hpp" diff --git a/core/libs/prx/libScePsml/libScePsml.hpp b/core/libs/prx/libScePsml/libScePsml.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libScePsml/libScePsml.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libScePsml_debug/Export.cpp b/core/libs/prx/libScePsml_debug/Export.cpp index 448d0c8a60..02677de85c 100644 --- a/core/libs/prx/libScePsml_debug/Export.cpp +++ b/core/libs/prx/libScePsml_debug/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libScePsml_debug/include/Api.hpp" +#include "prx/libkernel/libkernel.hpp" #include #include #include @@ -16,41 +18,36 @@ namespace { constexpr std::int32_t SCE_PSML_ERROR_NOT_INITIALIZED = static_cast(0x8A810001); constexpr std::int32_t SCE_PSML_ERROR_INVALID_ARGUMENT = static_cast(0x8A810005); constexpr std::int32_t SCE_PSML_ERROR_NULL_OUTPUT = static_cast(0x8A810009); +constexpr std::int32_t Mfsr2ErrorNotInitialized = static_cast(0x8A8A0001); +constexpr std::int32_t Mfsr2ErrorUnsupportedConsole = static_cast(0x8A8A0012); constexpr std::uint64_t MfsrBlockBytes = 0x200000; constexpr std::uint64_t MfsrBlockCount = 4; constexpr std::uint32_t MfsrDispatchPacketDwords = 0x600; +constexpr std::uint64_t MfsrSharedBlockCount = 52; -struct MfsrRequirement { - std::uint64_t blockBytes; - std::uint64_t alignment; - std::uint64_t blockCount; -}; +static_assert(sizeof(Mfsr2SharedResourcesParam) == 32); +static_assert(offsetof(Mfsr2SharedResourcesParam, blocks) == 8); +static_assert(offsetof(Mfsr2SharedResourcesParam, blockCount) == 16); +static_assert(offsetof(Mfsr2SharedResourcesParam, width) == 24); +static_assert(offsetof(Mfsr2SharedResourcesParam, reserved1) == 28); -struct MfsrBlock { - std::uint64_t address; - std::uint64_t bytes; -}; - -struct MfsrSharedResourcesParam { - std::uint32_t type; - std::uint32_t reserved; - const MfsrBlock* blocks; - std::uint64_t blockCount; - std::uint64_t reserved2; -}; - -struct MfsrContextParam { - std::uint32_t reserved0[2]; - const MfsrBlock* blocks; - std::uint64_t blockCount; - std::uint16_t inputWidth; - std::uint16_t inputHeight; - std::uint32_t reserved1; - const void* descriptor0; - const void* descriptor1; - std::uint32_t reserved2[2]; - void* sharedResources; -}; +static_assert(sizeof(Mfsr2ContextParam) == 48); +static_assert(offsetof(Mfsr2ContextParam, sharedResources) == 8); +static_assert(offsetof(Mfsr2ContextParam, blocks) == 16); +static_assert(offsetof(Mfsr2ContextParam, blockCount) == 24); +static_assert(offsetof(Mfsr2ContextParam, outputWidth) == 40); +static_assert(offsetof(Mfsr2ContextParam, inputColorSpace) == 44); +static_assert(sizeof(MfsrCommandBuffer) == 16); +static_assert(offsetof(MfsrCommandBuffer, sizeInDwords) == 8); +static_assert(sizeof(MfsrTextureDescriptor) == 32); +static_assert(sizeof(Mfsr2DispatchParam) == 112); +static_assert(offsetof(Mfsr2DispatchParam, preExposure) == 48); +static_assert(offsetof(Mfsr2DispatchParam, renderX) == 52); +static_assert(offsetof(Mfsr2DispatchParam, jitter) == 68); +static_assert(offsetof(Mfsr2DispatchParam, depthRange) == 92); +static_assert(offsetof(Mfsr2DispatchParam, resetHistory) == 100); +static_assert(offsetof(Mfsr2DispatchParam, optionFlags) == 104); +static_assert(offsetof(Mfsr2DispatchParam, sharpness) == 108); static_assert(sizeof(MfsrRequirement) == 0x18); static_assert(sizeof(MfsrSharedResourcesParam) == 0x20); @@ -78,10 +75,10 @@ void RequireMfsr(bool condition, const char* function, const char* what) { if (!condition) throw std::runtime_error(std::string(function) + ": " + what); } -void RequireBlocks(const MfsrBlock* blocks, std::uint64_t blockCount, const char* function) { - RequireMfsr(blockCount >= MfsrBlockCount, function, "fewer memory blocks than the requirement"); +void RequireBlocks(const MfsrBlock* blocks, std::uint64_t blockCount, std::uint64_t requiredCount, const char* function) { + RequireMfsr(blockCount >= requiredCount, function, "fewer memory blocks than the requirement"); RequireMfsr(blocks != nullptr, function, "null memory block list"); - for (std::uint64_t i = 0; i < MfsrBlockCount; ++i) { + for (std::uint64_t i = 0; i < requiredCount; ++i) { RequireMfsr(blocks[i].bytes >= MfsrBlockBytes && blocks[i].address % MfsrBlockBytes == 0, function, "memory block smaller or less aligned than the requirement"); } } @@ -162,7 +159,7 @@ std::int32_t APS5_VABI scePsmlMfsrGetSharedResourcesInitRequirement(MfsrRequirem if (!g_mfsrInitialized) return SCE_PSML_ERROR_NOT_INITIALIZED; RequireMfsr(requirement != nullptr && param != nullptr, __func__, "null requirement or parameter"); RequireMfsr(param->type == 0, __func__, ("shared resources type " + std::to_string(param->type) + " is not supported").c_str()); - *requirement = {MfsrBlockBytes, MfsrBlockBytes, MfsrBlockCount}; + *requirement = {MfsrBlockBytes, MfsrBlockBytes, MfsrSharedBlockCount}; return 0; } @@ -170,7 +167,7 @@ std::int32_t APS5_VABI scePsmlMfsrCreateSharedResources(void** sharedResources, if (!g_mfsrInitialized) return SCE_PSML_ERROR_NOT_INITIALIZED; RequireMfsr(sharedResources != nullptr && param != nullptr, __func__, "null handle or parameter"); RequireMfsr(param->type == 0, __func__, ("shared resources type " + std::to_string(param->type) + " is not supported").c_str()); - RequireBlocks(param->blocks, param->blockCount, __func__); + RequireBlocks(param->blocks, param->blockCount, MfsrSharedBlockCount, __func__); *sharedResources = CreateMfsrObject(MfsrObjectKind::SharedResources, nullptr, __func__); return 0; } @@ -191,7 +188,7 @@ std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement800M3_2(MfsrRequire std::int32_t APS5_VABI scePsmlMfsrCreateContext800M3_2(void** context, const MfsrContextParam* param) { if (!g_mfsrInitialized) return SCE_PSML_ERROR_NOT_INITIALIZED; RequireMfsr(context != nullptr && param != nullptr, __func__, "null handle or parameter"); - RequireBlocks(param->blocks, param->blockCount, __func__); + RequireBlocks(param->blocks, param->blockCount, MfsrBlockCount, __func__); const auto [inputWidth, inputHeight] = MfsrInputDimensions(param); *context = CreateMfsrObject(MfsrObjectKind::Context, param->sharedResources, __func__, inputWidth, inputHeight); return 0; @@ -274,49 +271,57 @@ std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket900(void* context, void* return SCE_PSML_ERROR_NOT_INITIALIZED; } -int APS5_VABI scePsmlMfsr2ReleaseSharedResources() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI scePsmlMfsr2ReleaseSharedResources(void* sharedResources) { + (void)sharedResources; + return Mfsr2ErrorNotInitialized; } -int APS5_VABI scePsmlMfsr2GetSharedResourcesInitRequirement() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI scePsmlMfsr2GetSharedResourcesInitRequirement(MfsrRequirement* requirement, const Mfsr2SharedResourcesParam* param) { + (void)requirement; + (void)param; + return Mfsr2ErrorNotInitialized; } -int APS5_VABI scePsmlMfsr2GetContextInitRequirement() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI scePsmlMfsr2GetContextInitRequirement(MfsrRequirement* requirement, const Mfsr2ContextParam* param) { + (void)requirement; + (void)param; + return Mfsr2ErrorNotInitialized; } -int APS5_VABI scePsmlMfsr2ReleaseContext() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI scePsmlMfsr2ReleaseContext(void* context) { + (void)context; + return Mfsr2ErrorNotInitialized; } -int APS5_VABI scePsmlMfsr2GetDispatchPacketsSizeInDwords() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI scePsmlMfsr2GetDispatchPacketsSizeInDwords(void* context, std::uint32_t* packetSizeInDwords) { + (void)context; + (void)packetSizeInDwords; + return Mfsr2ErrorNotInitialized; } -int APS5_VABI scePsmlMfsr2CreateSharedResources() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI scePsmlMfsr2CreateSharedResources(void** sharedResources, const Mfsr2SharedResourcesParam* param) { + (void)sharedResources; + (void)param; + return Mfsr2ErrorNotInitialized; } -int APS5_VABI scePsmlMfsr2GetDispatchPackets() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI scePsmlMfsr2GetDispatchPackets(void* context, const MfsrCommandBuffer* commandBuffer, const Mfsr2DispatchParam* param) { + (void)context; + (void)commandBuffer; + (void)param; + return Mfsr2ErrorNotInitialized; } -int APS5_VABI scePsmlMfsr2CreateContext() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI scePsmlMfsr2CreateContext(void** context, const Mfsr2ContextParam* param) { + (void)context; + (void)param; + return Mfsr2ErrorNotInitialized; } -int APS5_VABI scePsmlMfsr2Init() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI scePsmlMfsr2Init() { + if (sceKernelIsTrinityMode() == 0) return Mfsr2ErrorUnsupportedConsole; + NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI scePsmlMfsrEnableSpecularAA() { diff --git a/core/libs/prx/libScePsml_debug/include/Api.hpp b/core/libs/prx/libScePsml_debug/include/Api.hpp new file mode 100644 index 0000000000..4a79a346d5 --- /dev/null +++ b/core/libs/prx/libScePsml_debug/include/Api.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct MfsrRequirement { + std::uint64_t blockBytes; + std::uint64_t alignment; + std::uint64_t blockCount; +}; + +struct MfsrBlock { + std::uint64_t address; + std::uint64_t bytes; +}; + +struct MfsrSharedResourcesParam { + std::uint32_t type; + std::uint32_t reserved; + const MfsrBlock* blocks; + std::uint64_t blockCount; + std::uint64_t reserved2; +}; + +struct Mfsr2SharedResourcesParam { + std::uint32_t type; + std::uint32_t reserved0; + const MfsrBlock* blocks; + std::uint64_t blockCount; + std::uint16_t width; + std::uint16_t height; + std::uint32_t reserved1; +}; + +struct Mfsr2ContextParam { + std::uint32_t profile; + std::uint32_t reserved0; + void* sharedResources; + const MfsrBlock* blocks; + std::uint64_t blockCount; + std::uint64_t reserved1; + std::uint16_t outputWidth; + std::uint16_t outputHeight; + std::uint32_t inputColorSpace; +}; + +struct MfsrCommandBuffer { + std::uint32_t* cursor; + std::uint32_t sizeInDwords; +}; + +struct MfsrTextureDescriptor { + std::uint32_t words[8]; +}; + +struct Mfsr2DispatchParam { + const MfsrTextureDescriptor* outputColor; + const MfsrTextureDescriptor* color; + const MfsrTextureDescriptor* motionVectors; + const MfsrTextureDescriptor* depth; + const MfsrTextureDescriptor* exposure; + std::uint64_t reserved0; + float preExposure; + std::uint32_t renderX; + std::uint32_t renderY; + std::uint32_t renderWidth; + std::uint32_t renderHeight; + float jitter[2]; + float motionVectorScale[2]; + float motionVectorOffset[2]; + float depthRange[2]; + std::uint8_t resetHistory; + std::uint8_t reserved1[3]; + std::uint32_t optionFlags; + float sharpness; +}; + +struct MfsrContextParam { + std::uint32_t reserved0[2]; + const MfsrBlock* blocks; + std::uint64_t blockCount; + std::uint16_t inputWidth; + std::uint16_t inputHeight; + std::uint32_t reserved1; + const void* descriptor0; + const void* descriptor1; + std::uint32_t reserved2[2]; + void* sharedResources; +}; + +extern "C" { + +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param); +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param); +std::int32_t APS5_VABI scePsmlMfsrInit(); +std::int32_t APS5_VABI scePsmlMfsrGetSharedResourcesInitRequirement(MfsrRequirement* requirement, const MfsrSharedResourcesParam* param); +std::int32_t APS5_VABI scePsmlMfsrCreateSharedResources(void** sharedResources, const MfsrSharedResourcesParam* param); +std::int32_t APS5_VABI scePsmlMfsrReleaseSharedResources(void* sharedResources); +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement800M3_2(MfsrRequirement* requirement, const MfsrContextParam* param); +std::int32_t APS5_VABI scePsmlMfsrCreateContext800M3_2(void** context, const MfsrContextParam* param); +std::int32_t APS5_VABI scePsmlMfsrReleaseContext(void* context); +std::int32_t APS5_VABI scePsmlMfsrCreateContext1300(void** context, const void* param); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1300(void* context, void* commandBuffer, const void* param); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacketSizeInDwords(void* context, std::uint32_t* packetSizeInDwords); +std::int32_t APS5_VABI scePsmlMfsrSelectConfig(void* config); +std::int32_t APS5_VABI scePsmlMfsrGetMipmapBias(void* context, float* bias, std::uint32_t dimension); +std::int32_t APS5_VABI scePsmlMfsrRequestCapture(void* context); +std::int32_t APS5_VABI scePsmlMfsrIsCaptureInProgress(void* context); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1000(void* context, void* commandBuffer, const void* param); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket900(void* context, void* commandBuffer, const void* param); +std::int32_t APS5_VABI scePsmlMfsr2ReleaseSharedResources(void* sharedResources); +std::int32_t APS5_VABI scePsmlMfsr2GetSharedResourcesInitRequirement(MfsrRequirement* requirement, const Mfsr2SharedResourcesParam* param); +std::int32_t APS5_VABI scePsmlMfsr2GetContextInitRequirement(MfsrRequirement* requirement, const Mfsr2ContextParam* param); +std::int32_t APS5_VABI scePsmlMfsr2ReleaseContext(void* context); +std::int32_t APS5_VABI scePsmlMfsr2GetDispatchPacketsSizeInDwords(void* context, std::uint32_t* packetSizeInDwords); +std::int32_t APS5_VABI scePsmlMfsr2CreateSharedResources(void** sharedResources, const Mfsr2SharedResourcesParam* param); +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(); + +} diff --git a/core/libs/prx/libScePsml_debug/libScePsml_debug.hpp b/core/libs/prx/libScePsml_debug/libScePsml_debug.hpp new file mode 100644 index 0000000000..fc5eb2ba99 --- /dev/null +++ b/core/libs/prx/libScePsml_debug/libScePsml_debug.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libScePsml_debug/include/Api.hpp" diff --git a/core/libs/prx/libSceRandom/Export.cpp b/core/libs/prx/libSceRandom/Export.cpp index c61ffc4d53..2fcda9cfb5 100644 --- a/core/libs/prx/libSceRandom/Export.cpp +++ b/core/libs/prx/libSceRandom/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceRandom/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceRandom/include/Api.hpp b/core/libs/prx/libSceRandom/include/Api.hpp new file mode 100644 index 0000000000..ac26ff0d3b --- /dev/null +++ b/core/libs/prx/libSceRandom/include/Api.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceRandomGetRandomNumber(void* buf, size_t size); + +} diff --git a/core/libs/prx/libSceRandom/libSceRandom.hpp b/core/libs/prx/libSceRandom/libSceRandom.hpp new file mode 100644 index 0000000000..1697eb1bee --- /dev/null +++ b/core/libs/prx/libSceRandom/libSceRandom.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceRandom/include/Api.hpp" diff --git a/core/libs/prx/libSceRazorCpu/Export.cpp b/core/libs/prx/libSceRazorCpu/Export.cpp index 751f920cd1..5c8e4f8df9 100644 --- a/core/libs/prx/libSceRazorCpu/Export.cpp +++ b/core/libs/prx/libSceRazorCpu/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceRazorCpu/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -9,18 +10,30 @@ uint32_t APS5_VABI sceRazorCpuIsCapturing(void) { return 0; } -int APS5_VABI sceRazorCpuJobManagerDispatch(const void* args) { - (void)args; +int APS5_VABI sceRazorCpuJobManagerDispatch(const char* label, uint16_t firstId, uint16_t secondId, uint16_t firstCount, uint16_t secondCount, uint64_t firstValue, uint64_t secondValue) { + (void)label; + (void)firstId; + (void)secondId; + (void)firstCount; + (void)secondCount; + (void)firstValue; + (void)secondValue; return 0; } -int APS5_VABI sceRazorCpuJobManagerJob(const void* args) { - (void)args; +int APS5_VABI sceRazorCpuJobManagerJob(uint16_t firstId, uint16_t secondId, uint16_t thirdId, uint16_t event) { + (void)firstId; + (void)secondId; + (void)thirdId; + (void)event; return 0; } -int APS5_VABI sceRazorCpuJobManagerSequence(const void* args) { - (void)args; +int APS5_VABI sceRazorCpuJobManagerSequence(const char* label, uint16_t firstId, uint16_t secondId, uint16_t event) { + (void)label; + (void)firstId; + (void)secondId; + (void)event; return 0; } @@ -47,9 +60,9 @@ int APS5_VABI sceRazorCpuPopMarker(void) { return 0; } -int APS5_VABI sceRazorCpuFlushOccurred(uint64_t* timeSpentInFlush) { - if (timeSpentInFlush != nullptr) *timeSpentInFlush = 0; - return 0; +bool APS5_VABI sceRazorCpuFlushOccurred(uint64_t* timeSpentInFlush) { + (void)timeSpentInFlush; + return false; } int APS5_VABI sceRazorCpuPlotValue(const char* series, float value) { diff --git a/core/libs/prx/libSceRazorCpu/include/Api.hpp b/core/libs/prx/libSceRazorCpu/include/Api.hpp new file mode 100644 index 0000000000..1e6b94daff --- /dev/null +++ b/core/libs/prx/libSceRazorCpu/include/Api.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +uint32_t APS5_VABI sceRazorCpuIsCapturing(void); +int APS5_VABI sceRazorCpuJobManagerDispatch(const char* label, uint16_t firstId, uint16_t secondId, uint16_t firstCount, uint16_t secondCount, uint64_t firstValue, uint64_t secondValue); +int APS5_VABI sceRazorCpuJobManagerJob(uint16_t firstId, uint16_t secondId, uint16_t thirdId, uint16_t event); +int APS5_VABI sceRazorCpuJobManagerSequence(const char* label, uint16_t firstId, uint16_t secondId, uint16_t event); +int APS5_VABI sceRazorCpuPushMarker(const char* label, uint32_t color, uint32_t flags); +int APS5_VABI sceRazorCpuNamedSync(const char* label); +int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, uint32_t color, uint32_t flags); +int APS5_VABI sceRazorCpuPopMarker(void); +bool APS5_VABI sceRazorCpuFlushOccurred(uint64_t* timeSpentInFlush); +int APS5_VABI sceRazorCpuPlotValue(const char* series, float value); +int APS5_VABI sceRazorCpuWriteBookmark(const char* label, const char* description); +int APS5_VABI sceRazorCpuSync(void); +void APS5_VABI sceRazorCpuDisableFiberUserMarkers(void); +int APS5_VABI sceRazorCpuInitDataTags(void* buffer, size_t size); +int APS5_VABI sceRazorCpuShutdownDataTags(void); +size_t APS5_VABI sceRazorCpuGetDataTagStorageSize(uint32_t tagCount); +int APS5_VABI sceRazorCpuTagArray(const char* label, uint32_t category, void* array, size_t bufferSize, size_t elementSize); +int APS5_VABI sceRazorCpuTagBuffer(const char* label, uint32_t category, void* buffer, size_t bufferSize); +int APS5_VABI sceRazorCpuResizeTaggedBuffer(uint32_t category, void* buffer, size_t newBufferSize); +int APS5_VABI sceRazorCpuUnTagBuffer(uint32_t category, void* buffer); +int APS5_VABI sceRazorCpuBeginLogicalFileAccess(const char* filename, uint64_t tag, uint64_t size, uint8_t operation); +int APS5_VABI sceRazorCpuEndLogicalFileAccess(void); +int APS5_VABI sceRazorCpuFiberLogNameChange(void); +int APS5_VABI sceRazorCpuFiberSwitch(void); +int APS5_VABI sceRazorCpuGpuMarkerSync(void); +int APS5_VABI sceRazorCpuInitializeGpuMarkerContext(void); +int APS5_VABI sceRazorCpuInitializeInternal(void); +int APS5_VABI sceRazorCpuSetPopMarkerCallback(void); +int APS5_VABI sceRazorCpuSetPushMarkerCallback(void); +int APS5_VABI sceRazorCpuSetPushMarkerStaticCallback(void); +int APS5_VABI sceRazorCpuStartCaptureInternal(void); +int APS5_VABI sceRazorCpuStopCaptureInternal(void); +int APS5_VABI sceRazorCpuWorkloadRunBegin(void); +int APS5_VABI sceRazorCpuWorkloadRunEnd(void); +int APS5_VABI sceRazorCpuWorkloadSubmit(void); + +} diff --git a/core/libs/prx/libSceRazorCpu/libSceRazorCpu.hpp b/core/libs/prx/libSceRazorCpu/libSceRazorCpu.hpp new file mode 100644 index 0000000000..a734b0b85a --- /dev/null +++ b/core/libs/prx/libSceRazorCpu/libSceRazorCpu.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceRazorCpu/include/Api.hpp" diff --git a/core/libs/prx/libSceRazorCpu_debug/libSceRazorCpu_debug.hpp b/core/libs/prx/libSceRazorCpu_debug/libSceRazorCpu_debug.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceRazorCpu_debug/libSceRazorCpu_debug.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceRegMgr/Export.cpp b/core/libs/prx/libSceRegMgr/Export.cpp index e1e4aad2c1..84326f6c25 100644 --- a/core/libs/prx/libSceRegMgr/Export.cpp +++ b/core/libs/prx/libSceRegMgr/Export.cpp @@ -1,23 +1,95 @@ +#include "prx/libSceRegMgr/include/Api.hpp" #include #include +#include +#include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int SCE_REG_MGR_ERROR_INVALID_PARAM = static_cast(0x80820001); +constexpr int SCE_REG_MGR_ERROR_NOT_FOUND = static_cast(0x80820002); +constexpr int SCE_REG_MGR_ERROR_BUFFER_TOO_SMALL = static_cast(0x80820004); +constexpr int SCE_REG_MGR_ERROR_INVALID_TYPE = static_cast(0x80820006); + +enum EntryType { + TypeInt = 0, + TypeStr = 1, + TypeBin = 2, +}; + +struct RegEntry { + EntryType type; + int32_t intVal = 0; + std::string strVal; + std::vector binVal; +}; + +class RegistryStore { +public: + RegistryStore() = default; + + int getInt(int64_t key, int32_t* out) { + if (out == nullptr) return SCE_REG_MGR_ERROR_INVALID_PARAM; + std::lock_guard lock(mutex_); + auto it = entries_.find(key); + if (it == entries_.end()) return SCE_REG_MGR_ERROR_NOT_FOUND; + if (it->second.type != TypeInt) return SCE_REG_MGR_ERROR_INVALID_TYPE; + *out = it->second.intVal; + return 0; + } + + int getStr(int64_t key, char* out, int len) { + if (len == 0) NotImplemented_nid_no_patch(__func__); + if (out == nullptr || len < 0) return SCE_REG_MGR_ERROR_INVALID_PARAM; + std::lock_guard lock(mutex_); + auto it = entries_.find(key); + if (it == entries_.end()) return SCE_REG_MGR_ERROR_NOT_FOUND; + if (it->second.type != TypeStr) return SCE_REG_MGR_ERROR_INVALID_TYPE; + if (static_cast(len) <= it->second.strVal.size()) return SCE_REG_MGR_ERROR_BUFFER_TOO_SMALL; + std::memcpy(out, it->second.strVal.c_str(), it->second.strVal.size() + 1); + return 0; + } + + int getBin(int64_t key, void* out, int size) { + if (size == 0) NotImplemented_nid_no_patch(__func__); + if (out == nullptr || size < 0) return SCE_REG_MGR_ERROR_INVALID_PARAM; + std::lock_guard lock(mutex_); + auto it = entries_.find(key); + if (it == entries_.end()) return SCE_REG_MGR_ERROR_NOT_FOUND; + if (it->second.type != TypeBin) return SCE_REG_MGR_ERROR_INVALID_TYPE; + if (static_cast(size) < it->second.binVal.size()) return SCE_REG_MGR_ERROR_BUFFER_TOO_SMALL; + std::memcpy(out, it->second.binVal.data(), it->second.binVal.size()); + return 0; + } + +private: + std::mutex mutex_; + std::unordered_map entries_; +}; + +RegistryStore gStore; + +} // namespace + extern "C" { -int APS5_VABI sceRegMgrGetBin() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceRegMgrGetBin(std::int64_t optionId, void* data, int size) { + return gStore.getBin(optionId, data, size); } -int APS5_VABI sceRegMgrGetInt(int32_t optionId, int32_t* outInt) { - NotImplemented_nid_no_patch(__func__); - return 0; + +int APS5_VABI sceRegMgrGetInt(std::int64_t optionId, int32_t* outInt) { + return gStore.getInt(optionId, outInt); } -int APS5_VABI sceRegMgrGetStr(int32_t optionId, char* outString, std::size_t stringLength) { - NotImplemented_nid_no_patch(__func__); - return 0; + +int APS5_VABI sceRegMgrGetStr(std::int64_t optionId, char* outString, int stringLength) { + return gStore.getStr(optionId, outString, stringLength); } -int APS5_VABI sceRegMgrSetBin() { +int APS5_VABI sceRegMgrSetBin(std::uint32_t optionId, const void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceRegMgr/include/Api.hpp b/core/libs/prx/libSceRegMgr/include/Api.hpp new file mode 100644 index 0000000000..8d062d5377 --- /dev/null +++ b/core/libs/prx/libSceRegMgr/include/Api.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceRegMgrGetBin(std::int64_t optionId, void* data, int size); +int APS5_VABI sceRegMgrGetInt(std::int64_t optionId, int32_t* outInt); +int APS5_VABI sceRegMgrGetStr(std::int64_t optionId, char* outString, int stringLength); +int APS5_VABI sceRegMgrSetBin(std::uint32_t optionId, const void* data, std::size_t size); +int APS5_VABI sceRegMgrSetInt(int32_t optionId, int32_t inInt); +int APS5_VABI sceRegMgrSetStr(int32_t optionId, const char* inStirng, std::size_t stringLength); +std::int32_t APS5_VABI sceRegMgrNonSysGetInt(std::int64_t key, std::int32_t unk, std::int32_t* data); +int APS5_VABI sceRegMgrCheckError(void); +int APS5_VABI sceRegMgrCheckIntVal(void); +int APS5_VABI sceRegMgrGetBinInitVal(void); +int APS5_VABI sceRegMgrGetIntInitVal(void); +int APS5_VABI sceRegMgrGetStrInitVal(void); +int APS5_VABI sceRegMgrGetVersion(void); +int APS5_VABI sceRegMgrIsChange(void); +int APS5_VABI sceRegMgrEvtGetCnt(void); +int APS5_VABI sceRegMgrEvtGetRegId(void); +int APS5_VABI sceRegMgrResetVal(void); +int APS5_VABI sceRegMgrSetInitLevel(void); +int APS5_VABI sceRegMgrSrvCnvRegionInt(void); +int APS5_VABI sceRegMgrSrvCnvRegionStr(void); +int APS5_VABI sceRegMgrSrvGetMachineType(void); +int APS5_VABI sceRegMgrSrvGetQAFforReg(void); +int APS5_VABI sceRegMgrSrvGetRealMachineType(void); +int APS5_VABI sceRegMgrSrvGetRegion(void); +int APS5_VABI sceRegMgrSrvGetRegionStr(void); +int APS5_VABI sceRegMgrBackupNeedMem(void); +int APS5_VABI sceRegMgrBackupPullData(void); +int APS5_VABI sceRegMgrBackupPushData(void); +int APS5_VABI sceRegMgrBackupPullDataGkp(void); +int APS5_VABI sceRegMgrBackupPushDataGkp(void); +int APS5_VABI sceRegMgrCntlStart(void); +int APS5_VABI sceRegMgrCntlDeleteReg(void); +int APS5_VABI sceRegMgrCntlGetFilesCount(void); +int APS5_VABI sceRegMgrCntlGetFileName(void); +int APS5_VABI sceRegMgrCntSetInitReinst(void); +int APS5_VABI sceRegMgrIsInitOK(void); +int APS5_VABI sceRegMgrGetInitError(void); +int APS5_VABI sceRegMgrRecoverRegNvs(void); +int APS5_VABI sceRegMgrNonSysCheckError(void); +int APS5_VABI sceRegMgrNonSysGetBin(void); +int APS5_VABI sceRegMgrNonSysGetStr(void); +int APS5_VABI sceRegMgrNonSysSetBin(void); +int APS5_VABI sceRegMgrNonSysSetInt(void); +int APS5_VABI sceRegMgrNonSysSetStr(void); +int APS5_VABI sceRegMgrGetVersionForPS4(void); +int APS5_VABI sceRegMgrBackupNeedMemForPS4(void); +int APS5_VABI sceRegMgrBackupPullDataForPS4(void); +int APS5_VABI sceRegMgrBackupPushDataForPS4(void); +int APS5_VABI sceRegMgrEvtGetCntForPS4(void); +int APS5_VABI sceRegMgrEvtGetRegIdForPS4(void); +int APS5_VABI sceRegMgrStrncpy(void); +int APS5_VABI sceRegMgrMemset(void); +int APS5_VABI sceRegMgrSetbin(void); +int APS5_VABI sceRegMgrToolGetUpdateCnt(void); +int APS5_VABI sceRegMgrToolGetEntryCnt(void); +int APS5_VABI sceRegMgrToolDataCheckGet(void); +int APS5_VABI sceRegMgrDrvGetUpdateCnt(void); +int APS5_VABI sceRegMgrDrvGetEntCnt(void); +int APS5_VABI sceRegMgrDrvDataOpen(void); +int APS5_VABI sceRegMgrDrvDataCheckGet(void); +int APS5_VABI sceRegMgrDrvDataClose(void); +int APS5_VABI sceRegMgrToolGetInfo(void); +int APS5_VABI sceRegMgrLogPull(void); +int APS5_VABI sceRegMgrCntlStopHdd(void); +int APS5_VABI sceRegMgrPrintInfo(void); + +} diff --git a/core/libs/prx/libSceRegMgr/libSceRegMgr.hpp b/core/libs/prx/libSceRegMgr/libSceRegMgr.hpp new file mode 100644 index 0000000000..28b2427f86 --- /dev/null +++ b/core/libs/prx/libSceRegMgr/libSceRegMgr.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceRegMgr/include/Api.hpp" diff --git a/core/libs/prx/libSceRemotePlayClientIpc/libSceRemotePlayClientIpc.hpp b/core/libs/prx/libSceRemotePlayClientIpc/libSceRemotePlayClientIpc.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceRemotePlayClientIpc/libSceRemotePlayClientIpc.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceRemoteplay/Export.cpp b/core/libs/prx/libSceRemoteplay/Export.cpp index bc653c840f..c398ec2033 100644 --- a/core/libs/prx/libSceRemoteplay/Export.cpp +++ b/core/libs/prx/libSceRemoteplay/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceRemoteplay/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceRemoteplay/include/Api.hpp b/core/libs/prx/libSceRemoteplay/include/Api.hpp new file mode 100644 index 0000000000..a36c070176 --- /dev/null +++ b/core/libs/prx/libSceRemoteplay/include/Api.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceRemoteplayGetConnectionStatus(int userId, int* status); +int APS5_VABI sceRemoteplayInitialize(void* heap, size_t heapSize); +int APS5_VABI sceRemoteplayTerminate(void); +int APS5_VABI sceRemoteplayApprove(void); +int APS5_VABI sceRemoteplayClearAllRegistData(void); +int APS5_VABI sceRemoteplayClearConnectHistory(void); +int APS5_VABI sceRemoteplayConfirmDeviceRegist(void); +int APS5_VABI sceRemoteplayDisconnect(void); +int APS5_VABI sceRemoteplayGeneratePinCode(void); +int APS5_VABI sceRemoteplayGetApMode(void); +int APS5_VABI sceRemoteplayGetConnectHistory(void); +int APS5_VABI sceRemoteplayGetConnectUserId(void); +int APS5_VABI sceRemoteplayGetMbusDeviceInfo(void); +int APS5_VABI sceRemoteplayGetOperationStatus(void); +int APS5_VABI sceRemoteplayGetPrimaryUserId(void); +int APS5_VABI sceRemoteplayGetRemoteplayStatus(void); +int APS5_VABI sceRemoteplayGetRpMode(void); +int APS5_VABI sceRemoteplayImeClose(void); +int APS5_VABI sceRemoteplayImeFilterResult(void); +int APS5_VABI sceRemoteplayImeGetEvent(void); +int APS5_VABI sceRemoteplayImeNotify(void); +int APS5_VABI sceRemoteplayImeNotifyEventResult(void); +int APS5_VABI sceRemoteplayImeOpen(void); +int APS5_VABI sceRemoteplayImeSetCaret(void); +int APS5_VABI sceRemoteplayImeSetText(void); +int APS5_VABI sceRemoteplayIsRemoteOskReady(void); +int APS5_VABI sceRemoteplayIsRemotePlaying(void); +int APS5_VABI sceRemoteplayNotifyMbusDeviceRegistComplete(void); +int APS5_VABI sceRemoteplayNotifyNpPushWakeup(void); +int APS5_VABI sceRemoteplayNotifyPinCodeError(void); +int APS5_VABI sceRemoteplayNotifySubmitCode(void); +int APS5_VABI sceRemoteplayNotifyUserDelete(void); +int APS5_VABI sceRemoteplayPrintAllRegistData(void); +int APS5_VABI sceRemoteplayProhibit(void); +int APS5_VABI sceRemoteplayProhibitStreaming(void); +int APS5_VABI sceRemoteplayServerLock(void); +int APS5_VABI sceRemoteplayServerUnLock(void); +int APS5_VABI sceRemoteplaySetApMode(void); +int APS5_VABI sceRemoteplaySetLogLevel(void); +int APS5_VABI sceRemoteplaySetProhibition(void); +int APS5_VABI sceRemoteplaySetProhibitionForVsh(void); +int APS5_VABI sceRemoteplaySetRpMode(void); + +} diff --git a/core/libs/prx/libSceRemoteplay/libSceRemoteplay.hpp b/core/libs/prx/libSceRemoteplay/libSceRemoteplay.hpp new file mode 100644 index 0000000000..a04f3f394f --- /dev/null +++ b/core/libs/prx/libSceRemoteplay/libSceRemoteplay.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceRemoteplay/include/Api.hpp" diff --git a/core/libs/prx/libSceResourceArbitrator/libSceResourceArbitrator.hpp b/core/libs/prx/libSceResourceArbitrator/libSceResourceArbitrator.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceResourceArbitrator/libSceResourceArbitrator.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceRnpsAppMgr/Export.cpp b/core/libs/prx/libSceRnpsAppMgr/Export.cpp index d81f4a5e32..389f33546a 100644 --- a/core/libs/prx/libSceRnpsAppMgr/Export.cpp +++ b/core/libs/prx/libSceRnpsAppMgr/Export.cpp @@ -1,10 +1,144 @@ -#include +#include "prx/libSceRnpsAppMgr/include/Api.hpp" #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceRnpsAppMgrBlockAppInstall() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrCreateUpdateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrCreateUrlRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrDestroyBundle() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrDestroyUpdateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrFinishUrlRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetAppInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetAppInfoDebugString() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetAppInfoList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetEventForShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetUpdateProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrHasEventForShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrPushEventForShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRecoverUfsImage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRegisterShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRemoveApp() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRemoveAppOnSystemShutdown() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRemoveUfsImageOnSystemShutdown() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRequestUpdateCheck() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrSetCrlUrl() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrSetEventFlagForClients() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrSetUpdateUrl() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrStartService() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrUnblockAppInstall() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrUnregisterShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceRnpsAppMgr/include/Api.hpp b/core/libs/prx/libSceRnpsAppMgr/include/Api.hpp new file mode 100644 index 0000000000..95fc2a3856 --- /dev/null +++ b/core/libs/prx/libSceRnpsAppMgr/include/Api.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceRnpsAppMgrBlockAppInstall(); +int APS5_VABI sceRnpsAppMgrCreateUpdateRequest(); +int APS5_VABI sceRnpsAppMgrCreateUrlRequest(); +int APS5_VABI sceRnpsAppMgrDestroyBundle(); +int APS5_VABI sceRnpsAppMgrDestroyUpdateRequest(); +int APS5_VABI sceRnpsAppMgrFinishUrlRequest(); +int APS5_VABI sceRnpsAppMgrGetAppInfo(); +int APS5_VABI sceRnpsAppMgrGetAppInfoDebugString(); +int APS5_VABI sceRnpsAppMgrGetAppInfoList(); +int APS5_VABI sceRnpsAppMgrGetEventForShellUI(); +int APS5_VABI sceRnpsAppMgrGetUpdateProgress(); +int APS5_VABI sceRnpsAppMgrHasEventForShellUI(); +int APS5_VABI sceRnpsAppMgrInitialize(); +int APS5_VABI sceRnpsAppMgrPushEventForShellUI(); +int APS5_VABI sceRnpsAppMgrRecoverUfsImage(); +int APS5_VABI sceRnpsAppMgrRegisterShellUI(); +int APS5_VABI sceRnpsAppMgrRemoveApp(); +int APS5_VABI sceRnpsAppMgrRemoveAppOnSystemShutdown(); +int APS5_VABI sceRnpsAppMgrRemoveUfsImageOnSystemShutdown(); +int APS5_VABI sceRnpsAppMgrRequestUpdateCheck(); +int APS5_VABI sceRnpsAppMgrSetCrlUrl(); +int APS5_VABI sceRnpsAppMgrSetEventFlagForClients(); +int APS5_VABI sceRnpsAppMgrSetUpdateUrl(); +int APS5_VABI sceRnpsAppMgrStartService(); +int APS5_VABI sceRnpsAppMgrTerminate(); +int APS5_VABI sceRnpsAppMgrUnblockAppInstall(); +int APS5_VABI sceRnpsAppMgrUnregisterShellUI(); + +} diff --git a/core/libs/prx/libSceRnpsAppMgr/libSceRnpsAppMgr.hpp b/core/libs/prx/libSceRnpsAppMgr/libSceRnpsAppMgr.hpp new file mode 100644 index 0000000000..9fc9484de9 --- /dev/null +++ b/core/libs/prx/libSceRnpsAppMgr/libSceRnpsAppMgr.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceRnpsAppMgr/include/Api.hpp" diff --git a/core/libs/prx/libSceRnpsNKInjectedBundle/libSceRnpsNKInjectedBundle.hpp b/core/libs/prx/libSceRnpsNKInjectedBundle/libSceRnpsNKInjectedBundle.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceRnpsNKInjectedBundle/libSceRnpsNKInjectedBundle.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceRtc/CMakeLists.txt b/core/libs/prx/libSceRtc/CMakeLists.txt index e0b93b163e..1411d4f936 100644 --- a/core/libs/prx/libSceRtc/CMakeLists.txt +++ b/core/libs/prx/libSceRtc/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(libSceRtc SHARED EXCLUDE_FROM_ALL Export.cpp) +target_link_libraries(libSceRtc PRIVATE libkernel libc) target_include_directories(libSceRtc PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceRtc) @@ -8,8 +9,9 @@ if(BUILD_TESTING) target_link_libraries(guest_rtc_tests PRIVATE libSceRtc libc) configure_windows_unwind(guest_rtc_tests) add_test(NAME guest_rtc COMMAND guest_rtc_tests) + add_test(NAME guest_rtc_ps5_title COMMAND guest_rtc_tests ps5-title) if(WIN32) - set_tests_properties(guest_rtc PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_rtc guest_rtc_ps5_title PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index 43d049c976..91c6591395 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceRtc/include/Rtc.hpp" #include #include #include @@ -6,8 +7,12 @@ #include #include #include +#include +#include #include "SceTypes.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" namespace { @@ -31,6 +36,7 @@ constexpr std::int64_t WIN32_EPOCH_DAYS = 584388; constexpr std::uint64_t UNIX_EPOCH_TICK = UNIX_EPOCH_DAYS * TICKS_PER_DAY; constexpr std::uint64_t WIN32_EPOCH_TICK = WIN32_EPOCH_DAYS * TICKS_PER_DAY; constexpr std::uint64_t MAX_TICK = 3652059ull * TICKS_PER_DAY - 1; +constexpr std::uint32_t RANGE_CHECKED_SDK_VERSION = 0x03000000; struct ClockState { std::uint64_t tick = 0; @@ -100,6 +106,26 @@ RtcDateTime fromTick(std::uint64_t tick) { static_cast(timeOfDay % TICKS_PER_MINUTE / TICKS_PER_SECOND), static_cast(timeOfDay % TICKS_PER_SECOND)}; } +bool ps4Title() { + try { + const auto titleId = GetAppTitleId_nid_postfix(); + return std::string_view(titleId.value, strnlen(titleId.value, sizeof(titleId.value))).substr(0, 4) == "CUSA"; + } catch (const std::runtime_error&) { + return false; + } catch (const std::length_error&) { + return false; + } +} + +bool rangeChecked() { + static const bool ps4 = ps4Title(); + if (!ps4) return true; + const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t version; + std::memcpy(&version, process + 16, sizeof(version)); + return version >= RANGE_CHECKED_SDK_VERSION; +} + std::uint64_t currentTick(std::size_t clock = 0) { ClockState state; { @@ -270,14 +296,20 @@ bool parseZoneName(const char* cursor, std::int64_t& offsetMinutes) { const char* name; std::int64_t minutes; } zones[] = { - {"WETDST", 60}, {"METDST", 120}, {"EETDST", 180}, {"SWT", 60}, {"MEZ", 60}, {"AESST", 660}, {"ACSST", 630}, - {"CAST", 570}, {"CADT", 630}, {"SAST", 570}, {"SADT", 630}, {"WST", 480}, {"WDT", 540}, - {"GMT", 0}, {"WET", 0}, {"BST", 60}, {"WAT", -60}, {"AST", -240}, {"ADT", -180}, {"EST", -300}, {"EDT", -240}, - {"CST", -360}, {"CDT", -300}, {"MST", -420}, {"MDT", -360}, {"PST", -480}, {"PDT", -420}, {"YST", -540}, {"YDT", -480}, - {"HDT", -540}, {"CAT", -600}, {"AHST", -600}, {"NST", -150}, {"NFT", -150}, {"NDT", -90}, {"IDLW", -720}, {"CET", 60}, {"MET", 60}, - {"DNT", 60}, {"NOR", 60}, {"SET", 60}, {"MEWT", 60}, {"MEST", 120}, {"CEST", 120}, {"SST", 120}, {"FWT", 120}, {"FST", 60}, {"EET", 120}, {"IST", 120}, - {"WAST", 420}, {"WADT", 480}, {"CCT", 480}, {"AWST", 480}, {"JST", 540}, {"KST", 540}, {"ACST", 570}, {"EAST", 600}, - {"GST", 600}, {"AEST", 600}, {"NZT", 720}, {"NZST", 720}, {"NZDT", 780}, {"IDLE", 720}, {"JT", 450}, + {"GMT", 0}, {"EST", -300}, {"EDT", -240}, {"CST", -360}, {"CDT", -300}, + {"MST", -420}, {"MDT", -360}, {"PST", -480}, {"PDT", -420}, {"NZDT", 780}, + {"NZST", 720}, {"IDLE", 720}, {"NZT", 720}, {"AESST", 660}, {"ACSST", 630}, + {"CADT", 630}, {"SADT", 630}, {"AEST", 600}, {"EAST", 600}, {"GST", 600}, + {"LIGT", 600}, {"ACST", 570}, {"SAST", 570}, {"CAST", 570}, {"AWSST", 540}, + {"JST", 540}, {"KST", 540}, {"WDT", 540}, {"MT", 510}, {"AWST", 480}, + {"CCT", 480}, {"WADT", 480}, {"WST", 480}, {"JT", 450}, {"WAST", 420}, + {"IT", 210}, {"BT", 180}, {"EETDST", 180}, {"EET", 120}, {"CETDST", 120}, {"CEST", 120}, + {"FWT", 120}, {"IST", 120}, {"MEST", 120}, {"METDST", 120}, {"SST", 120}, + {"BST", 60}, {"CET", 60}, {"DNT", 60}, {"FST", 60}, {"MET", 60}, + {"MEWT", 60}, {"MEZ", 60}, {"NOR", 60}, {"SET", 60}, {"SWT", 60}, + {"WETDST", 60}, {"WET", 0}, {"WAT", -60}, {"NDT", -90}, {"ADT", -180}, + {"NFT", -150}, {"NST", -150}, {"AST", -240}, {"YDT", -480}, {"HDT", -540}, + {"YST", -540}, {"AHST", -600}, {"CAT", -600}, {"NT", -660}, {"IDLW", -720}, }; for (const auto& zone : zones) { const char* name = cursor; @@ -364,8 +396,13 @@ int APS5_VABI sceRtcGetDaysInMonth(int year, int month) { } int APS5_VABI sceRtcGetDayOfWeek(int year, int month, int day) { - if (year < 1 || year > 9999) return SCE_RTC_ERROR_INVALID_YEAR; - if (month < 1 || month > 12) return SCE_RTC_ERROR_INVALID_MONTH; + if (rangeChecked()) { + if (month < 1 || month > 12) return SCE_RTC_ERROR_INVALID_MONTH; + if (year < 1 || year > 9999) return SCE_RTC_ERROR_INVALID_YEAR; + } else { + if (year < 1) return SCE_RTC_ERROR_INVALID_YEAR; + if (month < 1 || month > 12) return SCE_RTC_ERROR_INVALID_MONTH; + } if (day < 1 || day > daysInMonth(year, month)) return SCE_RTC_ERROR_INVALID_DAY; return static_cast((daysFromCivil(year, static_cast(month), static_cast(day)) + 1) % 7); } @@ -383,7 +420,6 @@ int APS5_VABI sceRtcGetTick(const RtcDateTime* time, RtcTick* tick) { int APS5_VABI sceRtcSetTick(RtcDateTime* time, const RtcTick* tick) { if (!time || !tick) return SCE_RTC_ERROR_INVALID_POINTER; - if (tick->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; *time = fromTick(tick->tick); return 0; } @@ -436,15 +472,22 @@ int APS5_VABI sceRtcGetTime_t(const RtcDateTime* time, int64_t* seconds) { if (!seconds) return SCE_RTC_ERROR_INVALID_POINTER; if (const int result = validate(time); result != 0) return result; const std::uint64_t tick = toTick(*time); - if (tick < UNIX_EPOCH_TICK) return SCE_RTC_ERROR_INVALID_VALUE; + if (tick < UNIX_EPOCH_TICK) { + *seconds = 0; + return SCE_RTC_ERROR_INVALID_YEAR; + } *seconds = static_cast((tick - UNIX_EPOCH_TICK) / TICKS_PER_SECOND); return 0; } int APS5_VABI sceRtcSetTime_t(RtcDateTime* time, int64_t seconds) { if (!time) return SCE_RTC_ERROR_INVALID_POINTER; - if (seconds < 0 || static_cast(seconds) > (MAX_TICK - UNIX_EPOCH_TICK) / TICKS_PER_SECOND) return SCE_RTC_ERROR_INVALID_VALUE; - *time = fromTick(UNIX_EPOCH_TICK + static_cast(seconds) * TICKS_PER_SECOND); + if (rangeChecked()) { + if (seconds < 0) return SCE_RTC_ERROR_INVALID_VALUE; + *time = fromTick(UNIX_EPOCH_TICK + static_cast(seconds) * TICKS_PER_SECOND); + return 0; + } + *time = fromTick(UNIX_EPOCH_TICK + static_cast(static_cast(seconds)) * TICKS_PER_SECOND); return 0; } @@ -452,14 +495,16 @@ int APS5_VABI sceRtcGetWin32FileTime(const RtcDateTime* time, uint64_t* win32_ti if (!win32_time) return SCE_RTC_ERROR_INVALID_POINTER; if (const int result = validate(time); result != 0) return result; const std::uint64_t tick = toTick(*time); - if (tick < WIN32_EPOCH_TICK) return SCE_RTC_ERROR_INVALID_VALUE; + if (tick < WIN32_EPOCH_TICK) { + *win32_time = 0; + return SCE_RTC_ERROR_INVALID_YEAR; + } *win32_time = (tick - WIN32_EPOCH_TICK) * 10; return 0; } int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime* time, uint64_t win32_time) { if (!time) return SCE_RTC_ERROR_INVALID_POINTER; - if (win32_time / 10 > MAX_TICK - WIN32_EPOCH_TICK) return SCE_RTC_ERROR_INVALID_VALUE; *time = fromTick(WIN32_EPOCH_TICK + win32_time / 10); return 0; } @@ -573,64 +618,67 @@ int APS5_VABI sceRtcSetMicrosecond(RtcDateTime* time, int microsecond) { return 0; } -int APS5_VABI sceRtcFormatRFC3339(char* date_time, const RtcTick* utc, int time_zone_minutes) { - if (!date_time) return SCE_RTC_ERROR_INVALID_POINTER; +int APS5_VABI sceRtcFormatRFC3339(char* dateTime, const RtcTick* utc, int timeZoneMinutes) { + if (!dateTime) return SCE_RTC_ERROR_INVALID_POINTER; RtcTick now{}; if (!utc) { now.tick = currentTick(); utc = &now; } - if (time_zone_minutes < -1439 || time_zone_minutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; - RtcTick local{}; - if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; + if (timeZoneMinutes < -1439 || timeZoneMinutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; + const auto shift = static_cast(timeZoneMinutes) * TICKS_PER_MINUTE; + if (rangeChecked() && (shift < 0 ? utc->tick < static_cast(-shift) : utc->tick > std::numeric_limits::max() - static_cast(shift))) return SCE_RTC_ERROR_INVALID_VALUE; + const RtcTick local{utc->tick + static_cast(shift)}; + if (local.tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_YEAR; const RtcDateTime time = fromTick(local.tick); - int written = std::snprintf(date_time, 23, "%04u-%02u-%02uT%02u:%02u:%02u.%02u", time.year, time.month, time.day, time.hour, time.minute, time.second, time.microsecond / 10000); - if (time_zone_minutes == 0) { - std::snprintf(date_time + written, 2, "Z"); + int written = std::snprintf(dateTime, 23, "%04u-%02u-%02uT%02u:%02u:%02u.%02u", time.year, time.month, time.day, time.hour, time.minute, time.second, time.microsecond / 10000); + if (timeZoneMinutes == 0) { + std::snprintf(dateTime + written, 2, "Z"); } else { - const int offset = time_zone_minutes < 0 ? -time_zone_minutes : time_zone_minutes; - std::snprintf(date_time + written, 7, "%c%02d:%02d", time_zone_minutes < 0 ? '-' : '+', offset / 60, offset % 60); + const int offset = timeZoneMinutes < 0 ? -timeZoneMinutes : timeZoneMinutes; + std::snprintf(dateTime + written, 7, "%c%02d:%02d", timeZoneMinutes < 0 ? '-' : '+', offset / 60, offset % 60); } return 0; } -int APS5_VABI sceRtcFormatRFC3339LocalTime(char* date_time, const RtcTick* utc) { - if (!date_time) return SCE_RTC_ERROR_INVALID_POINTER; +int APS5_VABI sceRtcFormatRFC3339LocalTime(char* dateTime, const RtcTick* utc) { + if (!dateTime) return SCE_RTC_ERROR_INVALID_POINTER; RtcTick now{}; if (!utc) { now.tick = currentTick(); utc = &now; } if (utc->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; - return sceRtcFormatRFC3339(date_time, utc, localOffsetMinutes(*utc)); + return sceRtcFormatRFC3339(dateTime, utc, localOffsetMinutes(*utc)); } -int APS5_VABI sceRtcFormatRFC2822(char* date_time, const RtcTick* utc, int time_zone_minutes) { - if (!date_time) return SCE_RTC_ERROR_INVALID_POINTER; +int APS5_VABI sceRtcFormatRFC2822(char* dateTime, const RtcTick* utc, int timeZoneMinutes) { + if (!dateTime) return SCE_RTC_ERROR_INVALID_POINTER; RtcTick now{}; if (!utc) { now.tick = currentTick(); utc = &now; } - if (time_zone_minutes < -1439 || time_zone_minutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; - RtcTick local{}; - if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; + if (timeZoneMinutes < -1439 || timeZoneMinutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; + const RtcTick local{utc->tick + static_cast(static_cast(timeZoneMinutes) * TICKS_PER_MINUTE)}; + if (static_cast(local.tick) > static_cast(MAX_TICK)) return SCE_RTC_ERROR_INVALID_YEAR; + if (static_cast(utc->tick) < 0 || static_cast(local.tick) < 0) return SCE_RTC_ERROR_INVALID_VALUE; const RtcDateTime time = fromTick(local.tick); - const int offset = time_zone_minutes < 0 ? -time_zone_minutes : time_zone_minutes; - std::snprintf(date_time, 32, "%s, %02u %s %04u %02u:%02u:%02u %c%02d%02d", WEEKDAY_NAMES[(local.tick / TICKS_PER_DAY + 1) % 7], time.day, - MONTH_NAMES[time.month - 1], time.year, time.hour, time.minute, time.second, time_zone_minutes < 0 ? '-' : '+', offset / 60, offset % 60); + const int offset = timeZoneMinutes < 0 ? -timeZoneMinutes : timeZoneMinutes; + std::snprintf(dateTime, 32, "%s, %02u %s %04u %02u:%02u:%02u %c%02d%02d", WEEKDAY_NAMES[(local.tick / TICKS_PER_DAY + 1) % 7], time.day, + MONTH_NAMES[time.month - 1], time.year, time.hour, time.minute, time.second, timeZoneMinutes < 0 ? '-' : '+', offset / 60, offset % 60); return 0; } -int APS5_VABI sceRtcFormatRFC2822LocalTime(char* date_time, const RtcTick* utc) { - if (!date_time) return SCE_RTC_ERROR_INVALID_POINTER; +int APS5_VABI sceRtcFormatRFC2822LocalTime(char* dateTime, const RtcTick* utc) { + if (!dateTime) return SCE_RTC_ERROR_INVALID_POINTER; RtcTick now{}; if (!utc) { now.tick = currentTick(); utc = &now; } if (utc->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; - return sceRtcFormatRFC2822(date_time, utc, localOffsetMinutes(*utc)); + return sceRtcFormatRFC2822(dateTime, utc, localOffsetMinutes(*utc)); } int APS5_VABI sceRtcParseRFC3339(RtcTick* utc, const char* date_time) { diff --git a/core/libs/prx/libSceRtc/include/Rtc.hpp b/core/libs/prx/libSceRtc/include/Rtc.hpp new file mode 100644 index 0000000000..7e84acf6b7 --- /dev/null +++ b/core/libs/prx/libSceRtc/include/Rtc.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceRtcCheckValid(const RtcDateTime* time); +int APS5_VABI sceRtcIsLeapYear(int year); +int APS5_VABI sceRtcGetDaysInMonth(int year, int month); +int APS5_VABI sceRtcGetDayOfWeek(int year, int month, int day); +int APS5_VABI sceRtcGetTickResolution(void); +int APS5_VABI sceRtcGetTick(const RtcDateTime* time, RtcTick* tick); +int APS5_VABI sceRtcSetTick(RtcDateTime* time, const RtcTick* tick); +int APS5_VABI sceRtcGetCurrentTick(RtcTick* tick); +int APS5_VABI sceRtcGetCurrentNetworkTick(RtcTick* tick); +int APS5_VABI sceRtcGetCurrentClock(RtcDateTime* time, int timeZoneMinutes); +int APS5_VABI sceRtcGetCurrentClockLocalTime(RtcDateTime* time); +int APS5_VABI sceRtcConvertUtcToLocalTime(const RtcTick* utc, RtcTick* localTime); +int APS5_VABI sceRtcConvertLocalTimeToUtc(const RtcTick* localTime, RtcTick* utc); +int APS5_VABI sceRtcGetTime_t(const RtcDateTime* time, int64_t* seconds); +int APS5_VABI sceRtcSetTime_t(RtcDateTime* time, int64_t seconds); +int APS5_VABI sceRtcGetWin32FileTime(const RtcDateTime* time, uint64_t* win32Time); +int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime* time, uint64_t win32Time); +int APS5_VABI sceRtcGetDosTime(const RtcDateTime* time, uint32_t* dosTime); +int APS5_VABI sceRtcSetDosTime(RtcDateTime* time, uint32_t dosTime); +int APS5_VABI sceRtcGetYear(const RtcDateTime* time); +int APS5_VABI sceRtcSetYear(RtcDateTime* time, int year); +int APS5_VABI sceRtcGetMonth(const RtcDateTime* time); +int APS5_VABI sceRtcSetMonth(RtcDateTime* time, int month); +int APS5_VABI sceRtcGetDay(const RtcDateTime* time); +int APS5_VABI sceRtcSetDay(RtcDateTime* time, int day); +int APS5_VABI sceRtcGetHour(const RtcDateTime* time); +int APS5_VABI sceRtcSetHour(RtcDateTime* time, int hour); +int APS5_VABI sceRtcGetMinute(const RtcDateTime* time); +int APS5_VABI sceRtcSetMinute(RtcDateTime* time, int minute); +int APS5_VABI sceRtcGetSecond(const RtcDateTime* time); +int APS5_VABI sceRtcSetSecond(RtcDateTime* time, int second); +int APS5_VABI sceRtcGetMicrosecond(const RtcDateTime* time); +int APS5_VABI sceRtcSetMicrosecond(RtcDateTime* time, int microsecond); +int APS5_VABI sceRtcFormatRFC3339(char* dateTime, const RtcTick* utc, int timeZoneMinutes); +int APS5_VABI sceRtcFormatRFC3339LocalTime(char* dateTime, const RtcTick* utc); +int APS5_VABI sceRtcFormatRFC2822(char* dateTime, const RtcTick* utc, int timeZoneMinutes); +int APS5_VABI sceRtcFormatRFC2822LocalTime(char* dateTime, const RtcTick* utc); +int APS5_VABI sceRtcParseRFC3339(RtcTick* utc, const char* dateTime); +int APS5_VABI sceRtcParseDateTime(RtcTick* utc, const char* dateTime); +int APS5_VABI sceRtcTickAddTicks(RtcTick* dst, const RtcTick* src, int64_t ticks); +int APS5_VABI sceRtcTickAddMicroseconds(RtcTick* dst, const RtcTick* src, int64_t usec); +int APS5_VABI sceRtcTickAddSeconds(RtcTick* dst, const RtcTick* src, int64_t seconds); +int APS5_VABI sceRtcTickAddMinutes(RtcTick* dst, const RtcTick* src, int64_t minutes); +int APS5_VABI sceRtcTickAddHours(RtcTick* dst, const RtcTick* src, int32_t hours); +int APS5_VABI sceRtcTickAddDays(RtcTick* dst, const RtcTick* src, int32_t days); +int APS5_VABI sceRtcTickAddWeeks(RtcTick* dst, const RtcTick* src, int32_t weeks); +int APS5_VABI sceRtcTickAddMonths(RtcTick* dst, const RtcTick* src, int32_t months); +int APS5_VABI sceRtcTickAddYears(RtcTick* dst, const RtcTick* src, int16_t years); +int APS5_VABI sceRtcInit(void); +int APS5_VABI sceRtcEnd(void); +int APS5_VABI sceRtcCompareTick(const RtcTick* first, const RtcTick* second); +int APS5_VABI sceRtcGetCurrentRawNetworkTick(RtcTick* tick); +int APS5_VABI sceRtcGetCurrentAdNetworkTick(RtcTick* tick); +int APS5_VABI sceRtcGetCurrentDebugNetworkTick(RtcTick* tick); +int APS5_VABI sceRtcSetCurrentTick(const RtcTick* tick); +int APS5_VABI sceRtcSetCurrentNetworkTick(const RtcTick* tick); +int APS5_VABI sceRtcSetCurrentAdNetworkTick(const RtcTick* tick); +int APS5_VABI sceRtcSetCurrentDebugNetworkTick(const RtcTick* tick); +int APS5_VABI sceRtcSetConf(int first, int second, int third, int fourth); +int APS5_VABI sceRtcFormatRFC3339Precise(char* dateTime, const RtcTick* utc, int timeZoneMinutes, int precision); +int APS5_VABI sceRtcFormatRFC3339PreciseLocalTime(char* dateTime, const RtcTick* utc, int precision); + +} diff --git a/core/libs/prx/libSceRtc/libSceRtc.hpp b/core/libs/prx/libSceRtc/libSceRtc.hpp new file mode 100644 index 0000000000..6301e155c4 --- /dev/null +++ b/core/libs/prx/libSceRtc/libSceRtc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceRtc/include/Rtc.hpp" diff --git a/core/libs/prx/libSceRudp/CMakeLists.txt b/core/libs/prx/libSceRudp/CMakeLists.txt new file mode 100644 index 0000000000..962bd86e42 --- /dev/null +++ b/core/libs/prx/libSceRudp/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(libSceRudp SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../libkernel/Time/TimedWait.cpp +) +target_include_directories(libSceRudp PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceRudp) diff --git a/core/libs/prx/libSceRudp/Export.cpp b/core/libs/prx/libSceRudp/Export.cpp index 56df07b0ab..dd4dd2c260 100644 --- a/core/libs/prx/libSceRudp/Export.cpp +++ b/core/libs/prx/libSceRudp/Export.cpp @@ -1,6 +1,5 @@ +#include "prx/libSceRudp/include/Api.hpp" #include -#include -#include #include #include #include @@ -11,6 +10,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/Time/include/TimedWait.hpp" // Offline reliable-UDP: the library initialises and contexts can be created and bound, but no connection can // ever be established because there is no network. @@ -58,8 +58,6 @@ enum RudpOption { RUDP_OPTION_KEEP_ALIVE_TIMEOUT = 19, }; -using GuestRudpEventHandler = void(APS5_VABI*)(int, int, int, void*); - std::mutex g_mutex; bool g_inited = false; int g_next_ctx = 0; @@ -89,20 +87,13 @@ GuestRudpEventHandler g_handler = nullptr; void* g_handler_arg = nullptr; struct RudpPoll { - uint32_t capacity; + std::size_t capacity; std::map entries; }; -struct RudpPollEvent { - int ctxId; - uint16_t reqEvents; - uint16_t rtnEvents; -}; -static_assert(sizeof(RudpPollEvent) == 8); - int g_next_poll = 1; std::map g_polls; -std::condition_variable g_polls_changed; +TimedWait::Condition g_polls_changed; bool valid_ctx(int ctx) { return g_ctx.count(ctx) != 0; @@ -132,8 +123,12 @@ int APS5_VABI sceRudpInit_nid_postfix(void* mem_pool, int mem_pool_size) { return 0; } -int APS5_VABI sceRudpActivate() { - return 0; +int APS5_VABI sceRudpActivate(std::int32_t contextId, const void* address, std::uint32_t addressLength) { + std::lock_guard lock(g_mutex); + if (!g_inited) return RUDP_ERROR_NOT_INITIALIZED; + if (contextId < 0 || !address) return RUDP_ERROR_INVALID_ARGUMENT; + static_cast(addressLength); + throw std::runtime_error(__func__); } int APS5_VABI sceRudpGetStatus(void* status, std::size_t size) { @@ -159,7 +154,7 @@ int APS5_VABI sceRudpEnd(void) { g_inited = false; g_ctx.clear(); g_polls.clear(); - g_polls_changed.notify_all(); + g_polls_changed.NotifyAll(); return 0; } @@ -289,7 +284,7 @@ int APS5_VABI sceRudpGetContextStatus(int ctx_id, void* status, uint32_t size) { return 0; } -int APS5_VABI sceRudpGetRemoteInfo() { +int APS5_VABI sceRudpGetRemoteInfo(int contextId, void* address, std::uint32_t* addressSize, std::uint16_t* virtualPort) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -364,12 +359,12 @@ int APS5_VABI sceRudpNetFlush(void) { return g_inited ? 0 : RUDP_ERROR_NOT_INITIALIZED; } -int APS5_VABI sceRudpProcessEvents(void) { +int APS5_VABI sceRudpProcessEvents(std::uint64_t timeout) { std::lock_guard lk(g_mutex); return g_inited ? 0 : RUDP_ERROR_NOT_INITIALIZED; } -int APS5_VABI sceRudpNetReceived(void) { +int APS5_VABI sceRudpNetReceived(int socketId, const void* data, std::size_t dataSize, const void* sourceAddress, std::uint32_t sourceAddressSize) { std::lock_guard lk(g_mutex); return g_inited ? 0 : RUDP_ERROR_NOT_INITIALIZED; } @@ -386,7 +381,7 @@ int APS5_VABI sceRudpGetMaxSegmentSize(uint16_t* size) { return 0; } -int APS5_VABI sceRudpPollCreate(uint32_t size) { +int APS5_VABI sceRudpPollCreate(std::size_t size) { std::lock_guard lk(g_mutex); if (!g_inited) { return RUDP_ERROR_NOT_INITIALIZED; @@ -440,7 +435,7 @@ int APS5_VABI sceRudpPollControl(int pollId, int op, int ctxId, uint16_t events) return 0; } -int APS5_VABI sceRudpPollWait(int pollId, RudpPollEvent* events, uint32_t eventLen, uint64_t timeout) { +int APS5_VABI sceRudpPollWait(int pollId, RudpPollEvent* events, std::size_t eventLen, uint64_t timeout) { std::unique_lock lk(g_mutex); if (!g_inited) { return RUDP_ERROR_NOT_INITIALIZED; @@ -453,8 +448,8 @@ int APS5_VABI sceRudpPollWait(int pollId, RudpPollEvent* events, uint32_t eventL } 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.wait_for(lk, std::chrono::microseconds(timeout), closed)) { + g_polls_changed.Wait(lk, closed); + } else if (!g_polls_changed.WaitUntil(lk, TimedWait::DeadlineNanos(timeout), closed)) { return 0; } return g_inited ? RUDP_ERROR_INVALID_POLL_ID : RUDP_ERROR_NOT_INITIALIZED; @@ -471,7 +466,7 @@ int APS5_VABI sceRudpPollDestroy(int pollId) { if (g_polls.erase(pollId) == 0) { return RUDP_ERROR_INVALID_POLL_ID; } - g_polls_changed.notify_all(); + g_polls_changed.NotifyAll(); return 0; } diff --git a/core/libs/prx/libSceRudp/include/Api.hpp b/core/libs/prx/libSceRudp/include/Api.hpp new file mode 100644 index 0000000000..44b6881d58 --- /dev/null +++ b/core/libs/prx/libSceRudp/include/Api.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "SceTypes.hpp" + +using GuestRudpEventHandler = RudpEventHandler; + +extern "C" { + +int APS5_VABI sceRudpInit_nid_postfix(void* memPool, int memPoolSize); +int APS5_VABI sceRudpActivate(std::int32_t contextId, const void* address, std::uint32_t addressLength); +int APS5_VABI sceRudpGetStatus(void* status, std::size_t size); +int APS5_VABI sceRudpEnd(void); +int APS5_VABI sceRudpTerminate(int ctxId); +int APS5_VABI sceRudpEnableInternalIOThread(uint32_t stackSize, uint32_t priority); +int APS5_VABI sceRudpEnableInternalIOThread2(uint32_t stackSize, uint32_t priority, uint64_t affinity); +int APS5_VABI sceRudpSetEventHandler(GuestRudpEventHandler handler, void* arg); +int APS5_VABI sceRudpCreateContext(GuestRudpEventHandler handler, void* arg, int* ctxId); +int APS5_VABI sceRudpBind(int ctxId, uint16_t vport, int transport, uint16_t port); +int APS5_VABI sceRudpSetOption(int ctxId, int option, const void* value, uint32_t len); +int APS5_VABI sceRudpGetOption(int ctxId, int option, void* value, uint32_t len); +int APS5_VABI sceRudpInitiate(int ctxId, const void* to, uint32_t tolen, uint16_t vport); +int APS5_VABI sceRudpGetContextStatus(int ctxId, void* status, uint32_t size); +int APS5_VABI sceRudpGetRemoteInfo(int contextId, void* address, std::uint32_t* addressSize, std::uint16_t* virtualPort); +int APS5_VABI sceRudpGetNumberOfPacketsToRead(int ctxId); +int APS5_VABI sceRudpGetSizeWritable(int ctxId); +int APS5_VABI sceRudpGetSizeReadable(int ctxId); +int APS5_VABI sceRudpWrite(int ctxId, const void* data, uint32_t len, int flags); +int APS5_VABI sceRudpRead(int ctxId, void* data, uint32_t len, int flags, void* status); +int APS5_VABI sceRudpNetFlush(void); +int APS5_VABI sceRudpProcessEvents(std::uint64_t timeout); +int APS5_VABI sceRudpNetReceived(int socketId, const void* data, std::size_t dataSize, const void* sourceAddress, std::uint32_t sourceAddressSize); +int APS5_VABI sceRudpGetMaxSegmentSize(uint16_t* size); +int APS5_VABI sceRudpPollCreate(std::size_t size); +int APS5_VABI sceRudpPollControl(int pollId, int op, int ctxId, uint16_t events); +int APS5_VABI sceRudpPollWait(int pollId, RudpPollEvent* events, std::size_t eventLen, uint64_t timeout); +int APS5_VABI sceRudpPollDestroy(int pollId); +int APS5_VABI sceRudpAccept(); +int APS5_VABI sceRudpFlush(); +int APS5_VABI sceRudpGetLocalInfo(); +int APS5_VABI sceRudpListen(); +int APS5_VABI sceRudpPollCancel(); +int APS5_VABI sceRudpSetMaxSegmentSize(); + +} diff --git a/core/libs/prx/libSceRudp/libSceRudp.hpp b/core/libs/prx/libSceRudp/libSceRudp.hpp new file mode 100644 index 0000000000..934f85cb3c --- /dev/null +++ b/core/libs/prx/libSceRudp/libSceRudp.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceRudp/include/Api.hpp" diff --git a/core/libs/prx/libSceS3DConversion/libSceS3DConversion.hpp b/core/libs/prx/libSceS3DConversion/libSceS3DConversion.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceS3DConversion/libSceS3DConversion.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceS3da/Export.cpp b/core/libs/prx/libSceS3da/Export.cpp index 34b8330de5..fcf0f1f9af 100644 --- a/core/libs/prx/libSceS3da/Export.cpp +++ b/core/libs/prx/libSceS3da/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceS3da/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,22 +6,22 @@ extern "C" { -int APS5_VABI sceS3daClose() { +int APS5_VABI sceS3daClose(std::uint32_t port) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceS3daGetInfo() { +int APS5_VABI sceS3daGetInfo(std::uint32_t port, S3daInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceS3daOpen() { +int APS5_VABI sceS3daOpen(std::uint32_t port) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceS3daSendData() { +int APS5_VABI sceS3daSendData(std::uint32_t port, const void* data, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceS3da/include/Api.hpp b/core/libs/prx/libSceS3da/include/Api.hpp new file mode 100644 index 0000000000..d89f3a8dbb --- /dev/null +++ b/core/libs/prx/libSceS3da/include/Api.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "SceTypes.hpp" + +struct S3daInfo; + +extern "C" { + +int APS5_VABI sceS3daClose(std::uint32_t port); +int APS5_VABI sceS3daGetInfo(std::uint32_t port, S3daInfo* info); +int APS5_VABI sceS3daOpen(std::uint32_t port); +int APS5_VABI sceS3daSendData(std::uint32_t port, const void* data, std::uint32_t size); + +} diff --git a/core/libs/prx/libSceS3da/libSceS3da.hpp b/core/libs/prx/libSceS3da/libSceS3da.hpp new file mode 100644 index 0000000000..56ae159b9d --- /dev/null +++ b/core/libs/prx/libSceS3da/libSceS3da.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceS3da/include/Api.hpp" diff --git a/core/libs/prx/libSceSaveData.native/CMakeLists.txt b/core/libs/prx/libSceSaveData.native/CMakeLists.txt index 6b03a2c86c..5236fba466 100644 --- a/core/libs/prx/libSceSaveData.native/CMakeLists.txt +++ b/core/libs/prx/libSceSaveData.native/CMakeLists.txt @@ -16,6 +16,7 @@ if(BUILD_TESTING) add_test_executable(guest_savedata_delete_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataDelete.cpp) target_include_directories(guest_savedata_delete_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_savedata_delete_tests PRIVATE libSceSaveData.native libc) + configure_windows_unwind(guest_savedata_delete_tests) add_test(NAME guest_savedata_delete COMMAND guest_savedata_delete_tests) if(WIN32) set_tests_properties(guest_savedata_delete PROPERTIES ENVIRONMENT_MODIFICATION @@ -25,6 +26,7 @@ if(BUILD_TESTING) add_test_executable(guest_savedata_convert_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataConvert.cpp) target_include_directories(guest_savedata_convert_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_savedata_convert_tests PRIVATE libSceSaveData.native libc) + configure_windows_unwind(guest_savedata_convert_tests) add_test(NAME guest_savedata_convert COMMAND guest_savedata_convert_tests) if(WIN32) set_tests_properties(guest_savedata_convert PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index a95498f7d9..0627767b30 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceSaveData.native/include/Api.hpp" +#include "prx/libSceSaveData/include/Api.hpp" #include #include #include @@ -37,6 +39,7 @@ static std::string save_root() { static std::mutex g_state_mutex; static std::deque g_events; constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_BACKUP = 2; +constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_CONVERT = 5; static bool dir_name_match(const char* str, const char* pattern) { if (pattern == nullptr || pattern[0] == '\0') { @@ -280,7 +283,7 @@ extern "C" { int APS5_VABI sceSaveDataBackup(const SaveDataBackup* backup) { if (backup == nullptr || backup->dir_name == nullptr) { - throw std::runtime_error("sceSaveDataBackup: null argument"); + return SAVE_DATA_ERROR_PARAMETER; } // The system copies the save directory to its backup area asynchronously; the copy itself is // not observable by the title, only its completion event. @@ -327,7 +330,7 @@ static bool isMountedDirectory(const std::string& path) { static int deleteSave(const SaveDataDelete* del) { if (del == nullptr || del->dir_name == nullptr) { - throw std::runtime_error("sceSaveDataDelete: null argument"); + return SAVE_DATA_ERROR_PARAMETER; } const auto* nameEnd = static_cast(std::memchr(del->dir_name->data, '\0', sizeof(del->dir_name->data))); if (nameEnd == nullptr) { @@ -402,7 +405,7 @@ static SaveDataSearchInfo search_info(const std::string& real_path) { static int dirNameSearch(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { if (cond == nullptr || result == nullptr) { - throw std::runtime_error("sceSaveDataDirNameSearch: null argument"); + return SAVE_DATA_ERROR_PARAMETER; } result->hit_num = 0; result->set_num = 0; @@ -477,7 +480,7 @@ int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond, Sa int APS5_VABI sceSaveDataGetEventResult(const void* event_param, SaveDataEvent* event) { (void)event_param; if (event == nullptr) { - throw std::runtime_error("sceSaveDataGetEventResult: null event"); + return SAVE_DATA_ERROR_PARAMETER; } std::lock_guard lock(g_state_mutex); if (g_events.empty()) { @@ -605,7 +608,7 @@ int APS5_VABI sceSaveDataLoadIcon(const SaveDataMountPoint* mount_point, SaveDat static int mount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result) { if (mount == nullptr || mount_result == nullptr || mount->dir_name == nullptr) { - throw std::runtime_error("sceSaveDataMount3: null argument"); + return SAVE_DATA_ERROR_PARAMETER; } const bool create = (mount->mount_mode & SAVE_DATA_MOUNT_MODE_CREATE) != 0; const bool create2 = (mount->mount_mode & SAVE_DATA_MOUNT_MODE_CREATE2) != 0; @@ -952,6 +955,13 @@ static int convert(const SaveDataConvert* param) { return SAVE_DATA_ERROR_NOT_FOUND; } g_convertProgress = 1.0f; + SaveDataEvent event{}; + event.type = SAVE_DATA_EVENT_TYPE_CONVERT; + event.error_code = SAVE_DATA_OK; + event.user_id = param->user_id; + event.dir_name = *param->dir_name; + std::lock_guard lock(g_state_mutex); + g_events.push_back(event); return SAVE_DATA_OK; } @@ -978,7 +988,7 @@ int APS5_VABI sceSaveDataGetConvertProgress(float* progress) { return rc; } -int APS5_VABI sceSaveDataCancel() { +int APS5_VABI sceSaveDataCancel(std::uint32_t cancelArgument) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -989,7 +999,7 @@ int APS5_VABI sceSaveDataDirNameSearch2(const SaveDataDirNameSearchCond* cond, S return rc; } -int APS5_VABI sceSaveDataPrepareForTransferring() { +int APS5_VABI sceSaveDataPrepareForTransferring(const SaveDataMountPoint* mountPoint, const SaveDataPrepareParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1031,3 +1041,66 @@ int APS5_VABI sceSaveDataDebugTarget() { } } + +namespace { + +} + +extern "C" { + +int APS5_VABI sceSaveDataInitialize(const void* init) { + return sceSaveDataInitialize3(init); +} + +int APS5_VABI sceSaveDataMount(const SaveDataMountPs4* mount, SaveDataMountResult* mount_result) { + if (mount == nullptr || mount->dir_name == nullptr) return SAVE_DATA_ERROR_PARAMETER; + if (mount->title_id != nullptr) NotImplemented_nid_no_patch("sceSaveDataMount of another title's save data"); + SaveDataMount3 converted{}; + converted.user_id = mount->user_id; + converted.dir_name = mount->dir_name; + converted.blocks = mount->blocks; + converted.mount_mode = mount->mount_mode; + return sceSaveDataMount3(&converted, mount_result); +} + +int APS5_VABI sceSaveDataUmount(const SaveDataMountPoint* mount_point) { + return sceSaveDataUmount2(0, mount_point); +} + +int APS5_VABI sceSaveDataSetupSaveDataMemory(std::int32_t user_id, std::size_t memory_size, const SaveDataParam* param) { + SaveDataMemorySetup2 setup{}; + setup.user_id = user_id; + setup.memory_size = memory_size; + SaveDataMemorySetupResult result{}; + if (const int rc = sceSaveDataSetupSaveDataMemory2(&setup, &result); rc != SAVE_DATA_OK) return rc; + if (param == nullptr) return SAVE_DATA_OK; + SaveDataMemorySet2 set{}; + set.user_id = user_id; + set.param = param; + return sceSaveDataSetSaveDataMemory2(&set); +} + +int APS5_VABI sceSaveDataGetSaveDataMemory(std::int32_t user_id, void* buf, std::size_t buf_size, std::int64_t offset) { + SaveDataMemoryData data{}; + data.buf = buf; + data.buf_size = buf_size; + data.offset = static_cast(offset); + SaveDataMemoryGet2 get{}; + get.user_id = user_id; + get.data = &data; + return sceSaveDataGetSaveDataMemory2(&get); +} + +int APS5_VABI sceSaveDataSetSaveDataMemory(std::int32_t user_id, void* buf, std::size_t buf_size, std::int64_t offset) { + SaveDataMemoryData data{}; + data.buf = buf; + data.buf_size = buf_size; + data.offset = static_cast(offset); + SaveDataMemorySet2 set{}; + set.user_id = user_id; + set.data = &data; + set.data_num = 1; + return sceSaveDataSetSaveDataMemory2(&set); +} + +} diff --git a/core/libs/prx/libSceSaveData.native/include/Api.hpp b/core/libs/prx/libSceSaveData.native/include/Api.hpp new file mode 100644 index 0000000000..00283d8bfd --- /dev/null +++ b/core/libs/prx/libSceSaveData.native/include/Api.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "SceTypes.hpp" + +struct SaveDataMountPs4 { + std::int32_t user_id; + std::int32_t pad; + const void* title_id; + const SceSaveDataDirName* dir_name; + const void* fingerprint; + std::uint64_t blocks; + std::uint32_t mount_mode; + std::uint8_t reserved[32]; +}; + +extern "C" { + +int APS5_VABI sceSaveDataCommit(const SaveDataCommitParam* param); +int APS5_VABI sceSaveDataCreateTransactionResource(uint32_t size); +int APS5_VABI sceSaveDataDelete(const SaveDataDelete* del); +int APS5_VABI sceSaveDataDeleteTransactionResource(int32_t resource); +int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result); +int APS5_VABI sceSaveDataGetMountInfo(const SaveDataMountPoint* mountPoint, SaveDataMountInfo* info); +int APS5_VABI sceSaveDataGetParam(const SaveDataMountPoint* mountPoint, uint32_t paramType, void* paramBuf, size_t paramBufSize, size_t* gotSize); +int APS5_VABI sceSaveDataLoadIcon(const SaveDataMountPoint* mountPoint, SaveDataIcon* icon); +int APS5_VABI sceSaveDataMount3(const SaveDataMount3* mount, SaveDataMountResult* mountResult); +int APS5_VABI sceSaveDataPrepare(const SaveDataMountPoint* mountPoint, const SaveDataPrepareParam* param); +int APS5_VABI sceSaveDataSaveIcon(const SaveDataMountPoint* mountPoint, const SaveDataIcon* icon); +int APS5_VABI sceSaveDataSaveIconByPath(const SaveDataMountPoint* mountPoint, const char* path); +int APS5_VABI sceSaveDataSetParam(const SaveDataMountPoint* mountPoint, uint32_t paramType, const void* paramBuf, size_t paramBufSize); +int APS5_VABI sceSaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mountPoint); +int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount* mount, SaveDataMountResult* mountResult); +int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result); +int APS5_VABI sceSaveDataConvert(const SaveDataConvert* param); +int APS5_VABI sceSaveDataGetConvertProgress(float* progress); +int APS5_VABI sceSaveDataCancel(std::uint32_t cancelArgument); +int APS5_VABI sceSaveDataDirNameSearch2(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result); +int APS5_VABI sceSaveDataPrepareForTransferring(const SaveDataMountPoint* mountPoint, const SaveDataPrepareParam* param); +int APS5_VABI sceSaveDataCheckIpmiIfSize(); +int APS5_VABI sceSaveDataDebug(); +int APS5_VABI sceSaveDataDebugCheckBackupData(); +int APS5_VABI sceSaveDataDebugCleanMount(); +int APS5_VABI sceSaveDataDebugCompiledSdkVersion(); +int APS5_VABI sceSaveDataDebugCreateSaveDataRoot(); +int APS5_VABI sceSaveDataDebugProspero(); +int APS5_VABI sceSaveDataDebugRemoveSaveDataRoot(); +int APS5_VABI sceSaveDataDebugTarget(); +int APS5_VABI sceSaveDataInitialize(const void* init); +int APS5_VABI sceSaveDataMount(const SaveDataMountPs4* mount, SaveDataMountResult* mountResult); +int APS5_VABI sceSaveDataUmount(const SaveDataMountPoint* mountPoint); +int APS5_VABI sceSaveDataSetupSaveDataMemory(std::int32_t userId, std::size_t memorySize, const SaveDataParam* param); +int APS5_VABI sceSaveDataGetSaveDataMemory(std::int32_t userId, void* buf, std::size_t bufSize, std::int64_t offset); +int APS5_VABI sceSaveDataSetSaveDataMemory(std::int32_t userId, void* buf, std::size_t bufSize, std::int64_t offset); + +} diff --git a/core/libs/prx/libSceSaveData.native/libSceSaveData.native.hpp b/core/libs/prx/libSceSaveData.native/libSceSaveData.native.hpp new file mode 100644 index 0000000000..6cb55f8ca0 --- /dev/null +++ b/core/libs/prx/libSceSaveData.native/libSceSaveData.native.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "prx/libSceSaveData/include/Api.hpp" +#include "prx/libSceSaveData.native/include/Api.hpp" diff --git a/core/libs/prx/libSceSaveData/Export.cpp b/core/libs/prx/libSceSaveData/Export.cpp index f2a68ec989..6fbc7bb184 100644 --- a/core/libs/prx/libSceSaveData/Export.cpp +++ b/core/libs/prx/libSceSaveData/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSaveData/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceSaveData/SaveDataCore.hpp b/core/libs/prx/libSceSaveData/SaveDataCore.hpp index 58afdfa721..1da806ac51 100644 --- a/core/libs/prx/libSceSaveData/SaveDataCore.hpp +++ b/core/libs/prx/libSceSaveData/SaveDataCore.hpp @@ -21,20 +21,13 @@ #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 { -extern "C" { -int APS5_VABI sceKernelCreateEventFlag(KernelEventFlag* ef, const char* name, std::uint32_t attr, - std::uint64_t init_pattern, const void* param); -int APS5_VABI sceKernelDeleteEventFlag(KernelEventFlag ef); -int APS5_VABI sceKernelSetEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern); -int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, std::uint64_t set_pattern, int* num_wait_threads); -} - namespace { constexpr std::uint32_t SD_FACILITY = 0x809F0000u; diff --git a/core/libs/prx/libSceSaveData/include/Api.hpp b/core/libs/prx/libSceSaveData/include/Api.hpp new file mode 100644 index 0000000000..88067a68e5 --- /dev/null +++ b/core/libs/prx/libSceSaveData/include/Api.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSaveDataInitialize3(const void* init); +int APS5_VABI sceSaveDataTerminate(void); +int APS5_VABI sceSaveDataBackup(const SaveDataBackup* backup); +int APS5_VABI sceSaveDataGetEventResult(const void* eventParam, SaveDataEvent* event); +int APS5_VABI sceSaveDataGetSaveDataMemory2(SaveDataMemoryGet2* getParam); +int APS5_VABI sceSaveDataSetSaveDataMemory2(const SaveDataMemorySet2* setParam); +int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setupParam, SaveDataMemorySetupResult* result); +int APS5_VABI sceSaveDataSyncSaveDataMemory(const void* syncParam); +int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* mount, SaveDataMountResult* mountResult); +int APS5_VABI sceSaveDataAbort(); +int APS5_VABI sceSaveDataBindPsnAccount(void); +int APS5_VABI sceSaveDataBindPsnAccountForSystemBackup(void); +int APS5_VABI sceSaveDataChangeDatabase(void); +int APS5_VABI sceSaveDataChangeInternal(void); +int APS5_VABI sceSaveDataCheckBackupData(void); +int APS5_VABI sceSaveDataCheckBackupDataForCdlg(void); +int APS5_VABI sceSaveDataCheckBackupDataInternal(void); +int APS5_VABI sceSaveDataCheckBackupDataLight(void); +int APS5_VABI sceSaveDataCheckCloudData(void); +int APS5_VABI sceSaveDataCheckSaveDataBroken(void); +int APS5_VABI sceSaveDataCheckSaveDataVersion(void); +int APS5_VABI sceSaveDataCheckSaveDataVersionLatest(void); +int APS5_VABI sceSaveDataClearProgress(); +int APS5_VABI sceSaveDataCreateUploadData(void); +int APS5_VABI sceSaveDataDebugGetThreadId(void); +int APS5_VABI sceSaveDataDeleteAllUser(void); +int APS5_VABI sceSaveDataDeleteBackupData(void); +int APS5_VABI sceSaveDataDeleteCloudData(void); +int APS5_VABI sceSaveDataDeleteProspero(void); +int APS5_VABI sceSaveDataDeleteTitle(void); +int APS5_VABI sceSaveDataDeleteUser(void); +int APS5_VABI sceSaveDataDirNameSearchInternal(void); +int APS5_VABI sceSaveDataDownload(void); +int APS5_VABI sceSaveDataGetAllSize(); +int APS5_VABI sceSaveDataGetAppLaunchedUser(void); +int APS5_VABI sceSaveDataGetAutoUploadConditions(void); +int APS5_VABI sceSaveDataGetAutoUploadRequestInfo(void); +int APS5_VABI sceSaveDataGetAutoUploadSetting(void); +int APS5_VABI sceSaveDataGetBoundPsnAccountCount(void); +int APS5_VABI sceSaveDataGetClientThreadPriority(void); +int APS5_VABI sceSaveDataGetCloudQuotaInfo(void); +int APS5_VABI sceSaveDataGetEventInfo(); +int APS5_VABI sceSaveDataGetMountedSaveDataCount(); +int APS5_VABI sceSaveDataGetProgress(); +int APS5_VABI sceSaveDataGetSaveDataCount(); +int APS5_VABI sceSaveDataGetSaveDataRootDir(void); +int APS5_VABI sceSaveDataGetSaveDataRootPath(void); +int APS5_VABI sceSaveDataGetSaveDataRootUsbPath(void); +int APS5_VABI sceSaveDataGetSavePoint(void); +int APS5_VABI sceSaveDataGetUpdatedDataCount(void); +int APS5_VABI sceSaveDataInitialize2(); +int APS5_VABI sceSaveDataInitializeForCdlg(void); +int APS5_VABI sceSaveDataIsDeletingUsbDb(void); +int APS5_VABI sceSaveDataIsMounted(); +int APS5_VABI sceSaveDataManualDelete(void); +int APS5_VABI sceSaveDataManualDownload(void); +int APS5_VABI sceSaveDataManualUpload(void); +int APS5_VABI sceSaveDataMount5(); +int APS5_VABI sceSaveDataMountInternal(void); +int APS5_VABI sceSaveDataMountSys(void); +int APS5_VABI sceSaveDataRebuildDatabase(void); +int APS5_VABI sceSaveDataRegisterEventCallback(); +int APS5_VABI sceSaveDataRestoreBackupData(void); +int APS5_VABI sceSaveDataRestoreBackupDataForCdlg(void); +int APS5_VABI sceSaveDataRestoreLoadSaveDataMemory(void); +int APS5_VABI sceSaveDataSetAutoUploadSetting(void); +int APS5_VABI sceSaveDataSetEventInfo(); +int APS5_VABI sceSaveDataSetSaveDataLibraryUser(void); +int APS5_VABI sceSaveDataShutdownStart(void); +int APS5_VABI sceSaveDataSupportedFakeBrokenStatus(void); +int APS5_VABI sceSaveDataSyncCloudList(void); +int APS5_VABI sceSaveDataUmountSys(void); +int APS5_VABI sceSaveDataUmountWithBackup(void); +int APS5_VABI sceSaveDataUnregisterEventCallback(); +int APS5_VABI sceSaveDataUpload(void); + +} diff --git a/core/libs/prx/libSceSaveData/libSceSaveData.hpp b/core/libs/prx/libSceSaveData/libSceSaveData.hpp new file mode 100644 index 0000000000..72713a0e1f --- /dev/null +++ b/core/libs/prx/libSceSaveData/libSceSaveData.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSaveData/include/Api.hpp" diff --git a/core/libs/prx/libSceSaveDataDialog.native/Export.cpp b/core/libs/prx/libSceSaveDataDialog.native/Export.cpp index 54c4fd0926..4e173f20c5 100644 --- a/core/libs/prx/libSceSaveDataDialog.native/Export.cpp +++ b/core/libs/prx/libSceSaveDataDialog.native/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceSaveDataDialog.native/include/Api.hpp" +#include "prx/libSceSaveDataDialog/include/Api.hpp" #include #include #include @@ -44,7 +46,7 @@ int APS5_VABI sceSaveDataDialogGetResult(void* result) { r->result = SAVE_DATA_DIALOG_RESULT_OK; r->button_id = SAVE_DATA_DIALOG_BUTTON_ID_OK; r->user_data = g_user_data; - if (r->dir_name != nullptr && g_dir_name[0] != '\0') { + if (r->dir_name != nullptr) { std::snprintf(static_cast(r->dir_name)->data, sizeof(SaveDataDirName::data), "%s", g_dir_name); } diff --git a/core/libs/prx/libSceSaveDataDialog.native/include/Api.hpp b/core/libs/prx/libSceSaveDataDialog.native/include/Api.hpp new file mode 100644 index 0000000000..838a41d62b --- /dev/null +++ b/core/libs/prx/libSceSaveDataDialog.native/include/Api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSaveDataDialogGetStatus(void); +int APS5_VABI sceSaveDataDialogUpdateStatus(void); + +} diff --git a/core/libs/prx/libSceSaveDataDialog.native/libSceSaveDataDialog.native.hpp b/core/libs/prx/libSceSaveDataDialog.native/libSceSaveDataDialog.native.hpp new file mode 100644 index 0000000000..483cb011af --- /dev/null +++ b/core/libs/prx/libSceSaveDataDialog.native/libSceSaveDataDialog.native.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "prx/libSceSaveDataDialog/include/Api.hpp" +#include "prx/libSceSaveDataDialog.native/include/Api.hpp" diff --git a/core/libs/prx/libSceSaveDataDialog/Export.cpp b/core/libs/prx/libSceSaveDataDialog/Export.cpp index 068c96f145..1ad8a50883 100644 --- a/core/libs/prx/libSceSaveDataDialog/Export.cpp +++ b/core/libs/prx/libSceSaveDataDialog/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSaveDataDialog/include/Api.hpp" #include #include #include @@ -6,7 +7,6 @@ #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) { diff --git a/core/libs/prx/libSceSaveDataDialog/include/Api.hpp b/core/libs/prx/libSceSaveDataDialog/include/Api.hpp new file mode 100644 index 0000000000..40baa8c043 --- /dev/null +++ b/core/libs/prx/libSceSaveDataDialog/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSaveDataDialogClose(const void* closeParam); +int APS5_VABI sceSaveDataDialogGetResult(void* result); +int APS5_VABI sceSaveDataDialogInitialize(void); +int APS5_VABI sceSaveDataDialogIsReadyToDisplay(void); +int APS5_VABI sceSaveDataDialogOpen(const void* param); +int APS5_VABI sceSaveDataDialogProgressBarInc(int target, uint32_t delta); +int APS5_VABI sceSaveDataDialogProgressBarSetValue(int target, uint32_t rate); +int APS5_VABI sceSaveDataDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceSaveDataDialog/libSceSaveDataDialog.hpp b/core/libs/prx/libSceSaveDataDialog/libSceSaveDataDialog.hpp new file mode 100644 index 0000000000..06b3b4f0f0 --- /dev/null +++ b/core/libs/prx/libSceSaveDataDialog/libSceSaveDataDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSaveDataDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceScm/libSceScm.hpp b/core/libs/prx/libSceScm/libSceScm.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceScm/libSceScm.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceScreenShot/CMakeLists.txt b/core/libs/prx/libSceScreenShot/CMakeLists.txt new file mode 100644 index 0000000000..0300d678fa --- /dev/null +++ b/core/libs/prx/libSceScreenShot/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceScreenShot SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceScreenShot PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceScreenShot) + +if(BUILD_TESTING) + add_test_executable(guest_screenshot_tests ${LIBS_INCLUDE_DIR}/tests/GuestScreenShot.cpp) + target_include_directories(guest_screenshot_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_screenshot_tests PRIVATE libSceScreenShot libkernel libc) + configure_windows_unwind(guest_screenshot_tests) + add_test(NAME guest_screenshot COMMAND guest_screenshot_tests small) + add_test(NAME guest_screenshot_large_overlay COMMAND guest_screenshot_tests large) + if(WIN32) + set_tests_properties(guest_screenshot guest_screenshot_large_overlay PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceScreenShot/Export.cpp b/core/libs/prx/libSceScreenShot/Export.cpp index ae99fefcf6..b9335af525 100644 --- a/core/libs/prx/libSceScreenShot/Export.cpp +++ b/core/libs/prx/libSceScreenShot/Export.cpp @@ -1,35 +1,90 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceScreenShot/include/Api.hpp" +#include #include #include +#include #include "SceTypes.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/File/include/File.hpp" + +namespace { + +constexpr int ErrorArgument = static_cast(0x80BE0001); +constexpr int ErrorOverlayFile = static_cast(0x80BE0003); +constexpr int ErrorOverlaySize = static_cast(0x80BE000E); +constexpr std::uint32_t LargeOverlaySdkVersion = 0x3500000; +constexpr std::size_t MaxOverlayPath = 0x400; +constexpr std::uint16_t FileTypeMask = 0xF000; +constexpr std::uint16_t DirectoryType = 0x4000; + +std::atomic disabled{false}; + +std::uint32_t CompiledSdkVersion() { + static const auto* processParameters = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t version; + std::memcpy(&version, processParameters + 0x10, sizeof(version)); + return version; +} + +int SetOverlayImage(const char* path, int offsetX, int offsetY) { + const bool large = CompiledSdkVersion() >= LargeOverlaySdkVersion; + const int maxX = large ? 3839 : 1919; + const int maxY = large ? 2159 : 1079; + const std::int64_t maxSize = large ? 8 * 1024 * 1024 : 2 * 1024 * 1024; + if (!path || offsetX < 0 || offsetX > maxX || offsetY < 0 || offsetY > maxY) return ErrorArgument; + if (strnlen(path, MaxOverlayPath) >= MaxOverlayPath) return ErrorArgument; + const int descriptor = sceKernelOpen(path, 0, 0); + if (descriptor < 0) return ErrorOverlayFile; + FileStat stat{}; + int result = ErrorOverlayFile; + if (sceKernelFstat(descriptor, &stat) == 0 && (stat.st_mode & FileTypeMask) != DirectoryType) { + result = stat.st_size > 0 && stat.st_size <= maxSize ? 0 : ErrorOverlaySize; + } + sceKernelClose(descriptor); + + return result; +} + +} extern "C" { -int APS5_VABI sceScreenShotSetOverlayImage() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceScreenShotSetOverlayImage(const char* path, int offsetX, int offsetY) { + return SetOverlayImage(path, offsetX, offsetY); } -int APS5_VABI sceScreenShotSetOverlayImageWithOrigin() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceScreenShotCapture() { - NotImplemented_nid_no_patch(__func__); - return 0; + +int APS5_VABI sceScreenShotSetOverlayImageWithOrigin(const char* path, int offsetX, int offsetY, int origin) { + if (origin < 1 || origin > 9) return ErrorArgument; + return SetOverlayImage(path, offsetX, offsetY); } + int APS5_VABI sceScreenShotDisable() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceScreenShotDisableNotification() { - NotImplemented_nid_no_patch(__func__); + disabled = true; return 0; } + int APS5_VABI sceScreenShotEnable() { - NotImplemented_nid_no_patch(__func__); + disabled = false; return 0; } + +int APS5_VABI sceScreenShotIsDisabled(int id, int* isDisabled) { + if (id == -1 || !isDisabled) return ErrorArgument; + *isDisabled = disabled ? 1 : 0; + return 0; +} + +int APS5_VABI sceScreenShotDisableNotification() { + return 0; +} + int APS5_VABI sceScreenShotEnableNotification() { + return 0; +} + +int APS5_VABI sceScreenShotCapture() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -41,10 +96,6 @@ int APS5_VABI sceScreenShotGetDrcParam() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceScreenShotIsDisabled() { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI sceScreenShotIsVshScreenCaptureDisabled() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceScreenShot/include/Api.hpp b/core/libs/prx/libSceScreenShot/include/Api.hpp new file mode 100644 index 0000000000..98d5829a2c --- /dev/null +++ b/core/libs/prx/libSceScreenShot/include/Api.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceScreenShotSetOverlayImage(const char* path, int offsetX, int offsetY); +int APS5_VABI sceScreenShotSetOverlayImageWithOrigin(const char* path, int offsetX, int offsetY, int origin); +int APS5_VABI sceScreenShotDisable(); +int APS5_VABI sceScreenShotEnable(); +int APS5_VABI sceScreenShotIsDisabled(int id, int* isDisabled); +int APS5_VABI sceScreenShotDisableNotification(); +int APS5_VABI sceScreenShotEnableNotification(); +int APS5_VABI sceScreenShotCapture(); +int APS5_VABI sceScreenShotGetAppInfo(); +int APS5_VABI sceScreenShotGetDrcParam(); +int APS5_VABI sceScreenShotIsVshScreenCaptureDisabled(); +int APS5_VABI sceScreenShotSetDrcParam(); +int APS5_VABI sceScreenShotSetParam(); + +} diff --git a/core/libs/prx/libSceScreenShot/libSceScreenShot.hpp b/core/libs/prx/libSceScreenShot/libSceScreenShot.hpp new file mode 100644 index 0000000000..4deaf512f2 --- /dev/null +++ b/core/libs/prx/libSceScreenShot/libSceScreenShot.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceScreenShot/include/Api.hpp" diff --git a/core/libs/prx/libSceSdma/Export.cpp b/core/libs/prx/libSceSdma/Export.cpp index 97a2ea23cc..45a5fcc447 100644 --- a/core/libs/prx/libSceSdma/Export.cpp +++ b/core/libs/prx/libSceSdma/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSdma/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -92,7 +93,6 @@ int APS5_VABI sceSdmaFenceNonBlocking(void) { } int APS5_VABI sceSdmaFinalize(void) { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -102,12 +102,10 @@ int APS5_VABI sceSdmaFlushAll(void) { } int APS5_VABI sceSdmaFlushAllWithEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x809900c9); } int APS5_VABI sceSdmaInitialize(void) { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -122,8 +120,7 @@ int APS5_VABI sceSdmaOpen(void) { } int APS5_VABI sceSdmaQueryWorkingBufferSize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return 0x8000; } int APS5_VABI sceSdmaTryFlush(void) { @@ -132,8 +129,7 @@ int APS5_VABI sceSdmaTryFlush(void) { } int APS5_VABI sceSdmaWaitForEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x809900c9); } } diff --git a/core/libs/prx/libSceSdma/include/Api.hpp b/core/libs/prx/libSceSdma/include/Api.hpp new file mode 100644 index 0000000000..1245319f43 --- /dev/null +++ b/core/libs/prx/libSceSdma/include/Api.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSdmaBeginQueueMode(void); +int APS5_VABI sceSdmaCancelQueue(void); +int APS5_VABI sceSdmaClose(void); +int APS5_VABI sceSdmaConstantFill(void); +int APS5_VABI sceSdmaConstantFillNonBlocking(void); +int APS5_VABI sceSdmaCopyLinear(void); +int APS5_VABI sceSdmaCopyLinearNonBlocking(void); +int APS5_VABI sceSdmaCopyTiled(void); +int APS5_VABI sceSdmaCopyTiledNonBlocking(void); +int APS5_VABI sceSdmaCopyWindowL2L(void); +int APS5_VABI sceSdmaCopyWindowL2LNonBlocking(void); +int APS5_VABI sceSdmaCopyWindowT2T(void); +int APS5_VABI sceSdmaCopyWindowT2TNonBlocking(void); +int APS5_VABI sceSdmaCopyWindowTiled(void); +int APS5_VABI sceSdmaCopyWindowTiledNonBlocking(void); +int APS5_VABI sceSdmaEndQueueMode(void); +int APS5_VABI sceSdmaFenceNonBlocking(void); +int APS5_VABI sceSdmaFinalize(void); +int APS5_VABI sceSdmaFlushAll(void); +int APS5_VABI sceSdmaFlushAllWithEvent(void); +int APS5_VABI sceSdmaInitialize(void); +int APS5_VABI sceSdmaKickQueue(void); +int APS5_VABI sceSdmaOpen(void); +int APS5_VABI sceSdmaQueryWorkingBufferSize(void); +int APS5_VABI sceSdmaTryFlush(void); +int APS5_VABI sceSdmaWaitForEvent(void); + +} diff --git a/core/libs/prx/libSceSdma/libSceSdma.hpp b/core/libs/prx/libSceSdma/libSceSdma.hpp new file mode 100644 index 0000000000..fdaa4a04c9 --- /dev/null +++ b/core/libs/prx/libSceSdma/libSceSdma.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSdma/include/Api.hpp" diff --git a/core/libs/prx/libSceShare.native/libSceShare.native.hpp b/core/libs/prx/libSceShare.native/libSceShare.native.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceShare.native/libSceShare.native.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceShare/Export.cpp b/core/libs/prx/libSceShare/Export.cpp index a313edc364..e2f486e7f1 100644 --- a/core/libs/prx/libSceShare/Export.cpp +++ b/core/libs/prx/libSceShare/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceShare/include/Api.hpp" #include #include #include @@ -6,6 +7,12 @@ // Capture and sharing are not emulated; policy and parameter calls are accepted. +static_assert(sizeof(ShareScreenshotParamExtended) == 24); +static_assert(offsetof(ShareScreenshotParamExtended, text) == 16); +static_assert(sizeof(ShareVideoClipParamExtended) == 40); +static_assert(offsetof(ShareVideoClipParamExtended, mode) == 24); +static_assert(offsetof(ShareVideoClipParamExtended, text) == 32); + namespace { constexpr std::int32_t ERROR_INVALID_PARAM = static_cast(0x81960002); @@ -96,20 +103,16 @@ int APS5_VABI sceShareUnregisterContentEventCallback(void* callback) { return 0; } -int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, int32_t* req_id) { - (void)extended_param; - if (req_id != nullptr) { - *req_id = REQUEST_ID_INVALID; - } +int APS5_VABI sceShareCaptureScreenshotExtended(const ShareScreenshotParamExtended* param, std::int32_t* requestId) { + if (param == nullptr || param->size != sizeof(*param)) return ERROR_INVALID_PARAM; + if (requestId != nullptr) *requestId = REQUEST_ID_INVALID; return ERROR_NOT_SUPPORTED; } - -int APS5_VABI sceShareCaptureVideoClipExtended(const void* extended_param, int32_t* req_id) { - (void)extended_param; - if (req_id != nullptr) { - *req_id = REQUEST_ID_INVALID; - } +int APS5_VABI sceShareCaptureVideoClipExtended(const ShareVideoClipParamExtended* param, std::int32_t* requestId) { + if (param == nullptr || param->size != sizeof(*param)) return ERROR_INVALID_PARAM; + if (requestId != nullptr) *requestId = REQUEST_ID_INVALID; + if (param->reserved != 0 || (param->mode == 0 && param->value16 != 0)) return ERROR_INVALID_PARAM; return ERROR_NOT_SUPPORTED; } @@ -121,10 +124,11 @@ int APS5_VABI sceShareGetRunningStatus(uint32_t* status) { return 0; } -int APS5_VABI sceShareSetContentParamForApplicationTitle(const char* application_title) { - if (application_title == nullptr) { - return ERROR_INVALID_PARAM; - } +int APS5_VABI sceShareSetContentParamForApplicationTitle(const char* applicationTitle) { + if (applicationTitle == nullptr) return ERROR_INVALID_PARAM; + std::size_t length = 0; + while (length < 5120 && applicationTitle[length] != 0) ++length; + if (length == 0 || length == 5120) return ERROR_INVALID_PARAM; return 0; } diff --git a/core/libs/prx/libSceShare/include/Api.hpp b/core/libs/prx/libSceShare/include/Api.hpp new file mode 100644 index 0000000000..bb612024b1 --- /dev/null +++ b/core/libs/prx/libSceShare/include/Api.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "SceTypes.hpp" + +struct ShareScreenshotParamExtended { + std::uint64_t size; + std::uint32_t source; + std::uint32_t mode; + const char* text; +}; + +struct ShareVideoClipParamExtended { + std::uint64_t size; + std::uint64_t value8; + std::uint64_t value16; + std::uint32_t mode; + std::uint32_t reserved; + const char* text; +}; + +extern "C" { + +int APS5_VABI sceShareCaptureScreenshot(const void* param, int32_t* reqId); +int APS5_VABI sceShareCaptureVideoClip(const void* param, int32_t* reqId); +int APS5_VABI sceShareFeaturePermit(uint32_t featureFlags); +int APS5_VABI sceShareFeatureProhibit(uint32_t featureFlags); +int APS5_VABI sceShareGetCurrentStatus(uint32_t featureFlag, ShareCurrentStatus* status); +int APS5_VABI sceShareInitialize(size_t heapSize, int threadPriority, uint64_t affinityMask); +int APS5_VABI sceShareOpenMenuForContent(const void* contentId); +int APS5_VABI sceShareRegisterContentEventCallback(void* callback, void* userData); +int APS5_VABI sceShareSetCaptureSource(uint32_t featureFlags, const void* tapPoint); +int APS5_VABI sceShareSetContentParam(const char* contentParam); +int APS5_VABI sceShareSetScreenshotOverlayImage(const char* filePath, int32_t marginX, int32_t marginY, int32_t origin); +int APS5_VABI sceShareTerminate(void); +int APS5_VABI sceShareUnregisterContentEventCallback(void* callback); +int APS5_VABI sceShareCaptureScreenshotExtended(const ShareScreenshotParamExtended* param, std::int32_t* requestId); +int APS5_VABI sceShareCaptureVideoClipExtended(const ShareVideoClipParamExtended* param, std::int32_t* requestId); +int APS5_VABI sceShareGetRunningStatus(uint32_t* status); +int APS5_VABI sceShareSetContentParamForApplicationTitle(const char* applicationTitle); + +} diff --git a/core/libs/prx/libSceShare/libSceShare.hpp b/core/libs/prx/libSceShare/libSceShare.hpp new file mode 100644 index 0000000000..ee79c2f96a --- /dev/null +++ b/core/libs/prx/libSceShare/libSceShare.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceShare/include/Api.hpp" diff --git a/core/libs/prx/libSceShareFactoryUtil/Export.cpp b/core/libs/prx/libSceShareFactoryUtil/Export.cpp index ca9e3680ad..e4b1de17d3 100644 --- a/core/libs/prx/libSceShareFactoryUtil/Export.cpp +++ b/core/libs/prx/libSceShareFactoryUtil/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceShareFactoryUtil/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceShareFactoryUtil/include/Api.hpp b/core/libs/prx/libSceShareFactoryUtil/include/Api.hpp new file mode 100644 index 0000000000..23f6bad23e --- /dev/null +++ b/core/libs/prx/libSceShareFactoryUtil/include/Api.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceShareFactoryUtilBackToGameFromShareFactory(); +int APS5_VABI sceShareFactoryUtilGetLaunchStatus(); +int APS5_VABI sceShareFactoryUtilGetLaunchStatusOfShareFactory(); +int APS5_VABI sceShareFactoryUtilInitialize(); +int APS5_VABI sceShareFactoryUtilLaunchShareFactory(); +int APS5_VABI sceShareFactoryUtilTerminate(); + +} diff --git a/core/libs/prx/libSceShareFactoryUtil/libSceShareFactoryUtil.hpp b/core/libs/prx/libSceShareFactoryUtil/libSceShareFactoryUtil.hpp new file mode 100644 index 0000000000..7d666fde94 --- /dev/null +++ b/core/libs/prx/libSceShareFactoryUtil/libSceShareFactoryUtil.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceShareFactoryUtil/include/Api.hpp" diff --git a/core/libs/prx/libSceShareInternal.native/libSceShareInternal.native.hpp b/core/libs/prx/libSceShareInternal.native/libSceShareInternal.native.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceShareInternal.native/libSceShareInternal.native.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceSharePlay/Export.cpp b/core/libs/prx/libSceSharePlay/Export.cpp index 216ab34643..a19d94eb98 100644 --- a/core/libs/prx/libSceSharePlay/Export.cpp +++ b/core/libs/prx/libSceSharePlay/Export.cpp @@ -1,24 +1,37 @@ +#include "prx/libSceSharePlay/include/Api.hpp" #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" static constexpr int SCE_SHARE_PLAY_ERROR_INVALID_ARGS = static_cast(0x810E0001); +static constexpr int SCE_SHARE_PLAY_ERROR_ALREADY_INITIALIZED = static_cast(0x810E0003); +static constexpr int SCE_SHARE_PLAY_ERROR_NOT_INITIALIZED = static_cast(0x810E0004); +static std::mutex g_mutex; +static bool g_initialized = false; static constexpr std::int32_t SCE_USER_SERVICE_USER_ID_INVALID = -1; extern "C" { int APS5_VABI sceSharePlayInitialize(void* heap, size_t heapSize) { - (void)heap; - if (heapSize == 0) APS5_INVALID_ARG_EX; + std::lock_guard lock(g_mutex); + if (g_initialized) return SCE_SHARE_PLAY_ERROR_ALREADY_INITIALIZED; + if (heap && heapSize < 0x1800) return SCE_SHARE_PLAY_ERROR_INVALID_ARGS; + g_initialized = true; return 0; } int APS5_VABI sceSharePlayTerminate(void) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_SHARE_PLAY_ERROR_NOT_INITIALIZED; + g_initialized = false; return 0; } int APS5_VABI sceSharePlayGetCurrentConnectionInfoA(SharePlayConnectionInfoA* info) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_SHARE_PLAY_ERROR_NOT_INITIALIZED; if (info == nullptr) return SCE_SHARE_PLAY_ERROR_INVALID_ARGS; *info = {}; info->hostUserId = SCE_USER_SERVICE_USER_ID_INVALID; @@ -26,8 +39,10 @@ int APS5_VABI sceSharePlayGetCurrentConnectionInfoA(SharePlayConnectionInfoA* in return 0; } -int APS5_VABI sceSharePlayProhibitController(int prohibited) { - if (prohibited != 0 && prohibited != 1) APS5_INVALID_ARG_EX; +int APS5_VABI sceSharePlayProhibitController(std::uint8_t prohibited) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_SHARE_PLAY_ERROR_NOT_INITIALIZED; + if (prohibited != 0 && prohibited != 1) return SCE_SHARE_PLAY_ERROR_INVALID_ARGS; return 0; } @@ -36,8 +51,13 @@ int APS5_VABI sceSharePlayCrashDaemon(void) { return 0; } -int APS5_VABI sceSharePlayGetCurrentConnectionInfo(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSharePlayGetCurrentConnectionInfo(SharePlayConnectionInfo* info) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_SHARE_PLAY_ERROR_NOT_INITIALIZED; + if (info == nullptr) return SCE_SHARE_PLAY_ERROR_INVALID_ARGS; + *info = {}; + info->hostUserId = SCE_USER_SERVICE_USER_ID_INVALID; + info->visitorUserId = SCE_USER_SERVICE_USER_ID_INVALID; return 0; } diff --git a/core/libs/prx/libSceSharePlay/include/Api.hpp b/core/libs/prx/libSceSharePlay/include/Api.hpp new file mode 100644 index 0000000000..317c0dc43c --- /dev/null +++ b/core/libs/prx/libSceSharePlay/include/Api.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSharePlayInitialize(void* heap, size_t heapSize); +int APS5_VABI sceSharePlayTerminate(void); +int APS5_VABI sceSharePlayGetCurrentConnectionInfoA(SharePlayConnectionInfoA* info); +int APS5_VABI sceSharePlayProhibitController(std::uint8_t prohibited); +int APS5_VABI sceSharePlayCrashDaemon(void); +int APS5_VABI sceSharePlayGetCurrentConnectionInfo(SharePlayConnectionInfo* info); +int APS5_VABI sceSharePlayGetCurrentInfo(void); +int APS5_VABI sceSharePlayGetEvent(void); +int APS5_VABI sceSharePlayNotifyDialogOpen(void); +int APS5_VABI sceSharePlayNotifyForceCloseForCdlg(void); +int APS5_VABI sceSharePlayResumeScreenForCdlg(void); +int APS5_VABI sceSharePlayServerLock(void); +int APS5_VABI sceSharePlayServerUnLock(void); +int APS5_VABI sceSharePlaySetMode(void); +int APS5_VABI sceSharePlaySetProhibition(void); +int APS5_VABI sceSharePlaySetProhibitionModeWithAppId(void); +int APS5_VABI sceSharePlayStartStandby(void); +int APS5_VABI sceSharePlayStartStreaming(void); +int APS5_VABI sceSharePlayStopStandby(void); +int APS5_VABI sceSharePlayStopStreaming(void); + +} diff --git a/core/libs/prx/libSceSharePlay/libSceSharePlay.hpp b/core/libs/prx/libSceSharePlay/libSceSharePlay.hpp new file mode 100644 index 0000000000..c68e519720 --- /dev/null +++ b/core/libs/prx/libSceSharePlay/libSceSharePlay.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSharePlay/include/Api.hpp" diff --git a/core/libs/prx/libSceShareUtility/Export.cpp b/core/libs/prx/libSceShareUtility/Export.cpp index fb34206c21..a6cf891afd 100644 --- a/core/libs/prx/libSceShareUtility/Export.cpp +++ b/core/libs/prx/libSceShareUtility/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceShareUtility/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,6 +6,114 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceShareUtilityAdvanceFromFileWithContentIdList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceFromFileWithTitleIdList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceGetServiceInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceQueryServiceInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceQueryServiceInfoA() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceResetPlayingContent() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceSetPlayingContent() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceSetUploadContentData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceValidateContents() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenu() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenuBroadcast() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenuDefault() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenuForShareContent() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityInitializeEx2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityOpenShareMenu() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityOpenShareMenuBroadcast() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityOpenShareMenuDefault() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityOpenShareMenuForShareContent() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceShareUtility/include/Api.hpp b/core/libs/prx/libSceShareUtility/include/Api.hpp new file mode 100644 index 0000000000..9ea97606cc --- /dev/null +++ b/core/libs/prx/libSceShareUtility/include/Api.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceShareUtilityAdvanceFromFileWithContentIdList(); +int APS5_VABI sceShareUtilityAdvanceFromFileWithTitleIdList(); +int APS5_VABI sceShareUtilityAdvanceGetServiceInfo(); +int APS5_VABI sceShareUtilityAdvanceInitialize(); +int APS5_VABI sceShareUtilityAdvanceQueryServiceInfo(); +int APS5_VABI sceShareUtilityAdvanceQueryServiceInfoA(); +int APS5_VABI sceShareUtilityAdvanceResetPlayingContent(); +int APS5_VABI sceShareUtilityAdvanceSetPlayingContent(); +int APS5_VABI sceShareUtilityAdvanceSetUploadContentData(); +int APS5_VABI sceShareUtilityAdvanceTerminate(); +int APS5_VABI sceShareUtilityAdvanceValidateContents(); +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenu(); +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenuBroadcast(); +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenuDefault(); +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenuForShareContent(); +int APS5_VABI sceShareUtilityInitialize(); +int APS5_VABI sceShareUtilityInitializeEx2(); +int APS5_VABI sceShareUtilityOpenShareMenu(); +int APS5_VABI sceShareUtilityOpenShareMenuBroadcast(); +int APS5_VABI sceShareUtilityOpenShareMenuDefault(); +int APS5_VABI sceShareUtilityOpenShareMenuForShareContent(); +int APS5_VABI sceShareUtilityTerminate(); + +} diff --git a/core/libs/prx/libSceShareUtility/libSceShareUtility.hpp b/core/libs/prx/libSceShareUtility/libSceShareUtility.hpp new file mode 100644 index 0000000000..90968950c3 --- /dev/null +++ b/core/libs/prx/libSceShareUtility/libSceShareUtility.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceShareUtility/include/Api.hpp" diff --git a/core/libs/prx/libSceShellUIUtil/Export.cpp b/core/libs/prx/libSceShellUIUtil/Export.cpp index 4ff433cad7..3381c8bd7f 100644 --- a/core/libs/prx/libSceShellUIUtil/Export.cpp +++ b/core/libs/prx/libSceShellUIUtil/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceShellUIUtil/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -10,7 +11,7 @@ int APS5_VABI sceShellUIUtilCreateTaskToken() { return 0; } -int APS5_VABI sceShellUIUtilGetAppUrl() { +int APS5_VABI sceShellUIUtilGetAppUrl(const char* application, const char* parameter, char* output, std::uint32_t capacity, std::uint32_t* outputSize) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -20,12 +21,12 @@ int APS5_VABI sceShellUIUtilInitialize() { return 0; } -int APS5_VABI sceShellUIUtilLaunchByUri() { +int APS5_VABI sceShellUIUtilLaunchByUri(const char* uri, const ShellUIUtilLaunchParameter* parameter) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellUIUtilLaunchTask() { +int APS5_VABI sceShellUIUtilLaunchTask(std::int32_t taskToken, const char* uri, char* output, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceShellUIUtil/include/Api.hpp b/core/libs/prx/libSceShellUIUtil/include/Api.hpp new file mode 100644 index 0000000000..fe2e641916 --- /dev/null +++ b/core/libs/prx/libSceShellUIUtil/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +struct ShellUIUtilLaunchParameter; + +extern "C" { + +int APS5_VABI sceShellUIUtilCreateTaskToken(); +int APS5_VABI sceShellUIUtilGetAppUrl(const char* application, const char* parameter, char* output, std::uint32_t capacity, std::uint32_t* outputSize); +int APS5_VABI sceShellUIUtilInitialize(); +int APS5_VABI sceShellUIUtilLaunchByUri(const char* uri, const ShellUIUtilLaunchParameter* parameter); +int APS5_VABI sceShellUIUtilLaunchTask(std::int32_t taskToken, const char* uri, char* output, std::uint32_t capacity); +int APS5_VABI sceShellUIUtilTerminate(); + +} diff --git a/core/libs/prx/libSceShellUIUtil/libSceShellUIUtil.hpp b/core/libs/prx/libSceShellUIUtil/libSceShellUIUtil.hpp new file mode 100644 index 0000000000..0b7294088c --- /dev/null +++ b/core/libs/prx/libSceShellUIUtil/libSceShellUIUtil.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceShellUIUtil/include/Api.hpp" diff --git a/core/libs/prx/libSceSigninDialog/Export.cpp b/core/libs/prx/libSceSigninDialog/Export.cpp index 761bf33308..cd55802e70 100644 --- a/core/libs/prx/libSceSigninDialog/Export.cpp +++ b/core/libs/prx/libSceSigninDialog/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSigninDialog/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceSigninDialog/include/Api.hpp b/core/libs/prx/libSceSigninDialog/include/Api.hpp new file mode 100644 index 0000000000..5d9a45ed41 --- /dev/null +++ b/core/libs/prx/libSceSigninDialog/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSigninDialogClose(void); +int APS5_VABI sceSigninDialogGetResult(void* result); +int APS5_VABI sceSigninDialogGetStatus(void); +int APS5_VABI sceSigninDialogInitialize(void); +int APS5_VABI sceSigninDialogOpen(const void* param); +int APS5_VABI sceSigninDialogTerminate(void); +int APS5_VABI sceSigninDialogUpdateStatus(void); + +} diff --git a/core/libs/prx/libSceSigninDialog/libSceSigninDialog.hpp b/core/libs/prx/libSceSigninDialog/libSceSigninDialog.hpp new file mode 100644 index 0000000000..5791aba9bb --- /dev/null +++ b/core/libs/prx/libSceSigninDialog/libSceSigninDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSigninDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceSmart/libSceSmart.hpp b/core/libs/prx/libSceSmart/libSceSmart.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceSmart/libSceSmart.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceSocialScreen/Export.cpp b/core/libs/prx/libSceSocialScreen/Export.cpp index ebaf31823a..31e90003c8 100644 --- a/core/libs/prx/libSceSocialScreen/Export.cpp +++ b/core/libs/prx/libSceSocialScreen/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSocialScreen/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceSocialScreen/include/Api.hpp b/core/libs/prx/libSceSocialScreen/include/Api.hpp new file mode 100644 index 0000000000..77d98b1b85 --- /dev/null +++ b/core/libs/prx/libSceSocialScreen/include/Api.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSocialScreenCloseSeparateMode(); +int APS5_VABI sceSocialScreenConfigureSeparateMode(); +int APS5_VABI sceSocialScreenCrashDaemon(); +int APS5_VABI sceSocialScreenGetIgnoreSeparateMode(); +int APS5_VABI sceSocialScreenInitialize(); +int APS5_VABI sceSocialScreenInitializeSeparateModeParameter(); +int APS5_VABI sceSocialScreenOpenSeparateMode(); +int APS5_VABI sceSocialScreenReserveHighFrameRateVideoCaptureRequest(); +int APS5_VABI sceSocialScreenSetIgnoreSeparateMode(); +int APS5_VABI sceSocialScreenSetMode(); +int APS5_VABI sceSocialScreenTerminate(); + +} diff --git a/core/libs/prx/libSceSocialScreen/libSceSocialScreen.hpp b/core/libs/prx/libSceSocialScreen/libSceSocialScreen.hpp new file mode 100644 index 0000000000..c3678800c1 --- /dev/null +++ b/core/libs/prx/libSceSocialScreen/libSceSocialScreen.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSocialScreen/include/Api.hpp" diff --git a/core/libs/prx/libSceSocialScreenDialog/CMakeLists.txt b/core/libs/prx/libSceSocialScreenDialog/CMakeLists.txt new file mode 100644 index 0000000000..83af8dda26 --- /dev/null +++ b/core/libs/prx/libSceSocialScreenDialog/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libSceSocialScreenDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceSocialScreenDialog PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceSocialScreenDialog) + +if(BUILD_TESTING) + add_test_executable(guest_social_screen_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocialScreenDialog.cpp) + target_include_directories(guest_social_screen_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_social_screen_dialog_tests PRIVATE libSceSocialScreenDialog) + add_test(NAME guest_social_screen_dialog COMMAND guest_social_screen_dialog_tests) + if(WIN32) + set_tests_properties(guest_social_screen_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceSocialScreenDialog/Export.cpp b/core/libs/prx/libSceSocialScreenDialog/Export.cpp index 17987cec00..b12bb6631e 100644 --- a/core/libs/prx/libSceSocialScreenDialog/Export.cpp +++ b/core/libs/prx/libSceSocialScreenDialog/Export.cpp @@ -1,42 +1,71 @@ +#include "prx/libSceSocialScreenDialog/include/Api.hpp" +#include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int COMMON_DIALOG_STATUS_NONE = 0; +constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; +constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; +constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; + +std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; + +} + extern "C" { -int APS5_VABI sceSocialScreenDialogClose() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSocialScreenDialogInitialize(void) { + int expected = COMMON_DIALOG_STATUS_NONE; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; return 0; } -int APS5_VABI sceSocialScreenDialogGetResult() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSocialScreenDialogOpen(const void* param) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } -int APS5_VABI sceSocialScreenDialogGetStatus() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSocialScreenDialogUpdateStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceSocialScreenDialogGetStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceSocialScreenDialogGetResult(void* result) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; + std::memcpy(result, &canceled, sizeof(canceled)); return 0; } -int APS5_VABI sceSocialScreenDialogInitialize() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSocialScreenDialogClose(void) { + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } -int APS5_VABI sceSocialScreenDialogOpen() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSocialScreenDialogTerminate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSocialScreenDialogUpdateStatus() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSocialScreenDialogTerminate(void) { + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; return 0; } diff --git a/core/libs/prx/libSceSocialScreenDialog/include/Api.hpp b/core/libs/prx/libSceSocialScreenDialog/include/Api.hpp new file mode 100644 index 0000000000..1c02a641d8 --- /dev/null +++ b/core/libs/prx/libSceSocialScreenDialog/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSocialScreenDialogInitialize(void); +int APS5_VABI sceSocialScreenDialogOpen(const void* param); +int APS5_VABI sceSocialScreenDialogUpdateStatus(void); +int APS5_VABI sceSocialScreenDialogGetStatus(void); +int APS5_VABI sceSocialScreenDialogGetResult(void* result); +int APS5_VABI sceSocialScreenDialogClose(void); +int APS5_VABI sceSocialScreenDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceSocialScreenDialog/libSceSocialScreenDialog.hpp b/core/libs/prx/libSceSocialScreenDialog/libSceSocialScreenDialog.hpp new file mode 100644 index 0000000000..91b1893527 --- /dev/null +++ b/core/libs/prx/libSceSocialScreenDialog/libSceSocialScreenDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSocialScreenDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceSpSysCallWrapper/CMakeLists.txt b/core/libs/prx/libSceSpSysCallWrapper/CMakeLists.txt new file mode 100644 index 0000000000..5e407e9a37 --- /dev/null +++ b/core/libs/prx/libSceSpSysCallWrapper/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceSpSysCallWrapper SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceSpSysCallWrapper PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceSpSysCallWrapper PRIVATE libSceNet) +configure_windows_unwind(libSceSpSysCallWrapper) + +if(BUILD_TESTING) + add_test_executable(guest_sp_syscall_wrapper_tests ${LIBS_INCLUDE_DIR}/tests/GuestSpSysCallWrapper.cpp) + target_include_directories(guest_sp_syscall_wrapper_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sp_syscall_wrapper_tests PRIVATE libSceSpSysCallWrapper libSceNet libkernel libc) + configure_windows_unwind(guest_sp_syscall_wrapper_tests) + add_test(NAME guest_sp_syscall_wrapper COMMAND guest_sp_syscall_wrapper_tests) + if(WIN32) + set_tests_properties(guest_sp_syscall_wrapper PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceSpSysCallWrapper/Export.cpp b/core/libs/prx/libSceSpSysCallWrapper/Export.cpp index e15c0a9054..f0db9348f5 100644 --- a/core/libs/prx/libSceSpSysCallWrapper/Export.cpp +++ b/core/libs/prx/libSceSpSysCallWrapper/Export.cpp @@ -1,3 +1,6 @@ +#include "prx/libSceSpSysCallWrapper/libSceSpSysCallWrapper.hpp" +#include "prx/libSceNet/libSceNet.hpp" +#include "prx/libkernel/libkernel.hpp" #include #include #include "SceTypes.hpp" @@ -5,219 +8,179 @@ extern "C" { +std::int64_t APS5_VABI GuestNetRecv_nid_no_patch(int s, void* buf, std::size_t len, int flags); +std::int64_t APS5_VABI GuestNetSend_nid_no_patch(int s, const void* buf, std::size_t len, int flags); + int APS5_VABI sceSpGetPid() { - NotImplemented_nid_no_patch(__func__); - return 0; + return getpid_nid_postfix(); } -int APS5_VABI sceSpKernelGettimeofday() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpKernelGettimeofday(KernelTimeval* tp) { + return sceKernelGettimeofday(tp); } -int APS5_VABI sceSpKernelNanosleep() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmtp) { + return sceKernelNanosleep(rqtp, rmtp); } -int APS5_VABI sceSpNetConnect() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetConnect(int s, const void* addr, std::uint32_t addrlen) { + return sceNetConnect(s, addr, addrlen); } -int APS5_VABI sceSpNetEpollAbort() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetEpollAbort(int eid, int flags) { + return sceNetEpollAbort(eid, flags); } -int APS5_VABI sceSpNetEpollControl() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetEpollControl(int eid, int op, int id, const void* event) { + return sceNetEpollControl(eid, op, id, static_cast(event)); } -int APS5_VABI sceSpNetEpollCreate() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetEpollCreate(const char* name, int flags) { + return sceNetEpollCreate(name, flags); } -int APS5_VABI sceSpNetEpollDestroy() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetEpollDestroy(int eid) { + return sceNetEpollDestroy(eid); } -int APS5_VABI sceSpNetEpollWait() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetEpollWait(int eid, void* events, int maxevents, int timeout) { + return sceNetEpollWait(eid, static_cast(events), maxevents, timeout); } -int APS5_VABI sceSpNetErrnoLoc() { - NotImplemented_nid_no_patch(__func__); - return 0; +int* APS5_VABI sceSpNetErrnoLoc() { + return sceNetErrnoLoc(); } -int APS5_VABI sceSpNetPoolCreate() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetPoolCreate(const char* name, int size, int flags) { + return sceNetPoolCreate(name, size, flags); } -int APS5_VABI sceSpNetPoolDestroy() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetPoolDestroy(int memid) { + return sceNetPoolDestroy(memid); } -int APS5_VABI sceSpNetRecv() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceSpNetRecv(int s, void* buf, std::size_t len, int flags) { + return GuestNetRecv_nid_no_patch(s, buf, len, flags); } -int APS5_VABI sceSpNetResolverAbort() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetResolverAbort(int rid, int flags) { + return sceNetResolverAbort(rid, flags); } -int APS5_VABI sceSpNetResolverCreate() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetResolverCreate(const char* name, int memid, int flags) { + return sceNetResolverCreate(name, memid, flags); } -int APS5_VABI sceSpNetResolverDestroy() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetResolverDestroy(int rid) { + return sceNetResolverDestroy(rid); } -int APS5_VABI sceSpNetResolverGetError() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetResolverGetError(int rid, int* status) { + return sceNetResolverGetError(rid, status); } -int APS5_VABI sceSpNetResolverStartNtoa() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout, int retry, int flags) { + return sceNetResolverStartNtoa(rid, hostname, addr, timeout, retry, flags); } -int APS5_VABI sceSpNetSend() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceSpNetSend(int s, const void* buf, std::size_t len, int flags) { + return GuestNetSend_nid_no_patch(s, buf, len, flags); } -int APS5_VABI sceSpNetSetsockopt() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetSetsockopt(int s, int level, int optname, const void* optval, std::uint32_t optlen) { + return sceNetSetsockopt(s, level, optname, optval, optlen); } -int APS5_VABI sceSpNetSocket() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetSocket(const char* name, int family, int type, int protocol) { + return sceNetSocket(name, family, type, protocol); } -int APS5_VABI sceSpNetSocketClose() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpNetSocketClose(int s) { + return sceNetSocketClose(s); } -int APS5_VABI sceSpPthreadAttrDestroy() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadAttrDestroy(PthreadAttr* attr) { + return scePthreadAttrDestroy(attr); } -int APS5_VABI sceSpPthreadAttrInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadAttrInit(PthreadAttr* attr) { + return scePthreadAttrInit(attr); } -int APS5_VABI sceSpPthreadAttrSetinheritsched() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched) { + return scePthreadAttrSetinheritsched(attr, inheritSched); } -int APS5_VABI sceSpPthreadAttrSetschedparam() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param) { + return scePthreadAttrSetschedparam(attr, param); } -int APS5_VABI sceSpPthreadAttrSetstacksize() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize) { + return scePthreadAttrSetstacksize(attr, stackSize); } -int APS5_VABI sceSpPthreadCondDestroy() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadCondDestroy(PthreadCond* cond) { + return scePthreadCondDestroy(cond); } -int APS5_VABI sceSpPthreadCondInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name) { + return scePthreadCondInit(cond, attr, name); } -int APS5_VABI sceSpPthreadCondSignal() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadCondSignal(PthreadCond* cond) { + return scePthreadCondSignal(cond); } -int APS5_VABI sceSpPthreadCondWait() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadCondWait(PthreadCond* cond, PthreadMutex* mutex) { + return scePthreadCondWait(cond, mutex); } -int APS5_VABI sceSpPthreadCondattrDestroy() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadCondattrDestroy(PthreadCondattr* attr) { + return scePthreadCondattrDestroy(attr); } -int APS5_VABI sceSpPthreadCondattrInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadCondattrInit(PthreadCondattr* attr) { + return scePthreadCondattrInit(attr); } -int APS5_VABI sceSpPthreadCreate() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name) { + return scePthreadCreate(thread, attr, entry, arg, name); } -int APS5_VABI sceSpPthreadDetach() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadDetach(Pthread thread) { + return scePthreadDetach(thread); } -int APS5_VABI sceSpPthreadExit() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceSpPthreadExit(void* retval) { + scePthreadExit(retval); } -int APS5_VABI sceSpPthreadJoin() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadJoin(Pthread thread, void** retval) { + return scePthreadJoin(thread, retval); } -int APS5_VABI sceSpPthreadMutexDestroy() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadMutexDestroy(PthreadMutex* mutex) { + return scePthreadMutexDestroy(mutex); } -int APS5_VABI sceSpPthreadMutexInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name) { + return scePthreadMutexInit(mutex, attr, name); } -int APS5_VABI sceSpPthreadMutexLock() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadMutexLock(PthreadMutex* mutex) { + return scePthreadMutexLock(mutex); } -int APS5_VABI sceSpPthreadMutexUnlock() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadMutexUnlock(PthreadMutex* mutex) { + return scePthreadMutexUnlock(mutex); } -int APS5_VABI sceSpPthreadMutexattrDestroy() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadMutexattrDestroy(PthreadMutexattr* attr) { + return scePthreadMutexattrDestroy(attr); } -int APS5_VABI sceSpPthreadMutexattrInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadMutexattrInit(PthreadMutexattr* attr) { + return scePthreadMutexattrInit(attr); } int APS5_VABI sceSpPthreadMutexattrSetprioceiling() { @@ -225,24 +188,20 @@ int APS5_VABI sceSpPthreadMutexattrSetprioceiling() { return 0; } -int APS5_VABI sceSpPthreadMutexattrSetprotocol() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol) { + return scePthreadMutexattrSetprotocol(attr, protocol); } -int APS5_VABI sceSpPthreadMutexattrSettype() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadMutexattrSettype(PthreadMutexattr* attr, int type) { + return scePthreadMutexattrSettype(attr, type); } -int APS5_VABI sceSpPthreadSetprio() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpPthreadSetprio(Pthread thread, int prio) { + return scePthreadSetprio(thread, prio); } -int APS5_VABI sceSpSelect() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSpSelect(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout) { + return select_nid_postfix(nfds, readfds, writefds, exceptfds, timeout); } } diff --git a/core/libs/prx/libSceSpSysCallWrapper/include/Api.hpp b/core/libs/prx/libSceSpSysCallWrapper/include/Api.hpp new file mode 100644 index 0000000000..10979d9d31 --- /dev/null +++ b/core/libs/prx/libSceSpSysCallWrapper/include/Api.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSpGetPid(); +int APS5_VABI sceSpKernelGettimeofday(KernelTimeval* tp); +int APS5_VABI sceSpKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmtp); +int APS5_VABI sceSpNetConnect(int s, const void* addr, std::uint32_t addrlen); +int APS5_VABI sceSpNetEpollAbort(int eid, int flags); +int APS5_VABI sceSpNetEpollControl(int eid, int op, int id, const void* event); +int APS5_VABI sceSpNetEpollCreate(const char* name, int flags); +int APS5_VABI sceSpNetEpollDestroy(int eid); +int APS5_VABI sceSpNetEpollWait(int eid, void* events, int maxevents, int timeout); +int* APS5_VABI sceSpNetErrnoLoc(); +int APS5_VABI sceSpNetPoolCreate(const char* name, int size, int flags); +int APS5_VABI sceSpNetPoolDestroy(int memid); +std::int64_t APS5_VABI sceSpNetRecv(int s, void* buf, std::size_t len, int flags); +int APS5_VABI sceSpNetResolverAbort(int rid, int flags); +int APS5_VABI sceSpNetResolverCreate(const char* name, int memid, int flags); +int APS5_VABI sceSpNetResolverDestroy(int rid); +int APS5_VABI sceSpNetResolverGetError(int rid, int* status); +int APS5_VABI sceSpNetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout, int retry, int flags); +std::int64_t APS5_VABI sceSpNetSend(int s, const void* buf, std::size_t len, int flags); +int APS5_VABI sceSpNetSetsockopt(int s, int level, int optname, const void* optval, std::uint32_t optlen); +int APS5_VABI sceSpNetSocket(const char* name, int family, int type, int protocol); +int APS5_VABI sceSpNetSocketClose(int s); +int APS5_VABI sceSpPthreadAttrDestroy(PthreadAttr* attr); +int APS5_VABI sceSpPthreadAttrInit(PthreadAttr* attr); +int APS5_VABI sceSpPthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched); +int APS5_VABI sceSpPthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param); +int APS5_VABI sceSpPthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize); +int APS5_VABI sceSpPthreadCondDestroy(PthreadCond* cond); +int APS5_VABI sceSpPthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); +int APS5_VABI sceSpPthreadCondSignal(PthreadCond* cond); +int APS5_VABI sceSpPthreadCondWait(PthreadCond* cond, PthreadMutex* mutex); +int APS5_VABI sceSpPthreadCondattrDestroy(PthreadCondattr* attr); +int APS5_VABI sceSpPthreadCondattrInit(PthreadCondattr* attr); +int APS5_VABI sceSpPthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI sceSpPthreadDetach(Pthread thread); +void APS5_VABI sceSpPthreadExit(void* retval); +int APS5_VABI sceSpPthreadJoin(Pthread thread, void** retval); +int APS5_VABI sceSpPthreadMutexDestroy(PthreadMutex* mutex); +int APS5_VABI sceSpPthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); +int APS5_VABI sceSpPthreadMutexLock(PthreadMutex* mutex); +int APS5_VABI sceSpPthreadMutexUnlock(PthreadMutex* mutex); +int APS5_VABI sceSpPthreadMutexattrDestroy(PthreadMutexattr* attr); +int APS5_VABI sceSpPthreadMutexattrInit(PthreadMutexattr* attr); +int APS5_VABI sceSpPthreadMutexattrSetprioceiling(); +int APS5_VABI sceSpPthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol); +int APS5_VABI sceSpPthreadMutexattrSettype(PthreadMutexattr* attr, int type); +int APS5_VABI sceSpPthreadSetprio(Pthread thread, int prio); +int APS5_VABI sceSpSelect(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout); + +} diff --git a/core/libs/prx/libSceSpSysCallWrapper/libSceSpSysCallWrapper.hpp b/core/libs/prx/libSceSpSysCallWrapper/libSceSpSysCallWrapper.hpp new file mode 100644 index 0000000000..452ed7469c --- /dev/null +++ b/core/libs/prx/libSceSpSysCallWrapper/libSceSpSysCallWrapper.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSpSysCallWrapper/include/Api.hpp" diff --git a/core/libs/prx/libSceSrcUtl/Export.cpp b/core/libs/prx/libSceSrcUtl/Export.cpp index fac2eada51..3494cee76b 100644 --- a/core/libs/prx/libSceSrcUtl/Export.cpp +++ b/core/libs/prx/libSceSrcUtl/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSrcUtl/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceSrcUtl/include/Api.hpp b/core/libs/prx/libSceSrcUtl/include/Api.hpp new file mode 100644 index 0000000000..b4db4c7c48 --- /dev/null +++ b/core/libs/prx/libSceSrcUtl/include/Api.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSrcUtilityFinalize(); +int APS5_VABI sceSrcUtilityFlush(); +int APS5_VABI sceSrcUtilityGetDelaySamples(); +int APS5_VABI sceSrcUtilityGetHandleSize(); +int APS5_VABI sceSrcUtilityGetPriority(); +int APS5_VABI sceSrcUtilityInitialize(); +int APS5_VABI sceSrcUtilityInitializeEx(); +int APS5_VABI sceSrcUtilityResample(); +int APS5_VABI sceSrcUtilitySetPriority(); + +} diff --git a/core/libs/prx/libSceSrcUtl/libSceSrcUtl.hpp b/core/libs/prx/libSceSrcUtl/libSceSrcUtl.hpp new file mode 100644 index 0000000000..cf118c2ada --- /dev/null +++ b/core/libs/prx/libSceSrcUtl/libSceSrcUtl.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSrcUtl/include/Api.hpp" diff --git a/core/libs/prx/libSceSsl/CMakeLists.txt b/core/libs/prx/libSceSsl/CMakeLists.txt index cb39ff0e4c..8296a60735 100644 --- a/core/libs/prx/libSceSsl/CMakeLists.txt +++ b/core/libs/prx/libSceSsl/CMakeLists.txt @@ -90,9 +90,16 @@ if(BUILD_TESTING) target_link_libraries(guest_ssl_ps5_readers_tests PRIVATE libSceSsl libc) configure_windows_unwind(guest_ssl_ps5_readers_tests) 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) + 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) - set_tests_properties(guest_ssl guest_ssl_load guest_ssl_ps5_readers guest_ssl_transport_kernel guest_ssl_transport_net PROPERTIES ENVIRONMENT_MODIFICATION + 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 "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 4f697ed914..a3ae4afcec 100644 --- a/core/libs/prx/libSceSsl/Export.cpp +++ b/core/libs/prx/libSceSsl/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSsl/include/Ssl.hpp" #ifdef _WIN32 #include #else @@ -6,6 +7,7 @@ #endif #include "SslTypes.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/Socket/include/SocketPoll.hpp" @@ -205,9 +207,14 @@ struct Random { } }; +struct EncodedPrivateKey { + Block bytes; + std::size_t size; +}; + struct Identity { std::shared_ptr chain; - PrivateKey key; + std::shared_ptr key; }; struct Snapshot { @@ -222,6 +229,9 @@ struct CertNameEntry { }; struct CertName { + void* certificate = nullptr; + bool subject = false; + std::weak_ptr owner; std::vector entries; }; @@ -293,6 +303,9 @@ struct Session { std::vector alpnStrings; std::vector alpnList; std::string alpnSelected; + Block peekBuffer; + std::size_t peekOffset = 0; + std::size_t peekSize = 0; bool handshaken = false; bool verifying = false; bool verified = false; @@ -302,6 +315,7 @@ struct Session { int verifyResult = 0; unsigned int failedOptions = 0; Random random; + PrivateKey privateKey; mbedtls_x509_crt authorities; mbedtls_ssl_config config; mbedtls_ssl_context ssl; @@ -332,8 +346,18 @@ struct Session { mbedtls_ssl_conf_min_tls_version(&config, MBEDTLS_SSL_VERSION_TLS1_2); mbedtls_ssl_conf_ca_chain(&config, &authorities, nullptr); mbedtls_ssl_conf_rng(&config, mbedtls_ctr_drbg_random, &random.drbg); - if (identity) - CheckBackend(mbedtls_ssl_conf_own_cert(&config, &identity->chain->root, &identity->key.key), "SSL client certificate setup failed"); + if (identity) { + if (!identity->chain || !identity->key) throw std::logic_error("Incomplete SSL client identity is not supported"); + const auto& encoded = *identity->key; + auto bytes = Allocate(Add(encoded.size, 1)); + std::memcpy(bytes.get(), encoded.bytes.get(), encoded.size); + std::size_t first = 0; + while (first < encoded.size && (bytes.get()[first] == ' ' || bytes.get()[first] == '\r' || bytes.get()[first] == '\n' || bytes.get()[first] == '\t')) ++first; + const auto size = first < encoded.size && bytes.get()[first] == '-' ? Add(encoded.size, 1) : encoded.size; + CheckBackend(mbedtls_pk_parse_key(&privateKey.key, bytes.get(), size, nullptr, 0, mbedtls_ctr_drbg_random, &random.drbg), "Invalid SSL private key"); + CheckBackend(mbedtls_pk_check_pair(&identity->chain->root.pk, &privateKey.key, mbedtls_ctr_drbg_random, &random.drbg), "SSL client certificate and private key do not match"); + CheckBackend(mbedtls_ssl_conf_own_cert(&config, &identity->chain->root, &privateKey.key), "SSL client certificate setup failed"); + } for (std::size_t start = 0; start <= alpn.size();) { const auto end = alpn.find(',', start); const auto protocol = alpn.substr(start, end == std::string::npos ? std::string::npos : end - start); @@ -443,8 +467,6 @@ struct Session { } }; -using SslVerifyCallback = int (APS5_VABI *)(int, unsigned int, void**, int, void*); - constexpr int SslVersionSsl30 = 2; constexpr int SslVersionTls12 = 5; @@ -582,10 +604,17 @@ const char* LongAttributeName(const char* shortName) { void* ExtractCertName(const char* function, int sslCtxId, void* sslCert, bool subject) { std::lock_guard lock(State().mutex); + if (!sslCert || !State().contexts.contains(sslCtxId)) return nullptr; auto& context = GetContext(sslCtxId); const auto* crt = RequireCertificate(function, sslCtxId, sslCert); + for (const auto& [handle, stored] : context.certNames) { + if (stored->certificate == sslCert && stored->subject == subject) return handle; + } const auto* name = subject ? &crt->subject : &crt->issuer; auto certName = std::make_unique(); + certName->certificate = sslCert; + certName->subject = subject; + certName->owner = static_cast(sslCert)->owner; for (const auto* node = name; node; node = node->next) { if (!node->oid.p || node->oid.len == 0) continue; std::string oid; @@ -627,7 +656,9 @@ int APS5_VABI sceSslInit_nid_postfix(std::uint64_t poolSize) { int APS5_VABI sceSslTerm_nid_postfix(int sslCtxId) { auto& state = State(); std::lock_guard lock(state.mutex); - if (!state.contexts.erase(sslCtxId)) return ErrorInvalidId; + if (!state.contexts.contains(sslCtxId)) return ErrorInvalidId; + std::erase_if(state.connections, [sslCtxId](const auto& entry) { return entry.second.context == sslCtxId; }); + state.contexts.erase(sslCtxId); return 0; } @@ -647,15 +678,15 @@ int APS5_VABI sceSslGetMemoryPoolStats(int sslCtxId, SceSsl::MemoryPoolStats* st } int APS5_VABI sceSslLoadCert(int sslCtxId, int caCertNum, SceSsl::Data** caList, SceSsl::Data* cert, SceSsl::Data* privateKey) { - if (caCertNum < 0 || (caCertNum && !caList) || (!!cert != !!privateKey)) - throw std::invalid_argument("Invalid SSL certificate load arguments"); auto& state = State(); std::lock_guard lock(state.mutex); - auto& context = GetContext(sslCtxId); + const auto found = state.contexts.find(sslCtxId); + if (found == state.contexts.end()) return ErrorInvalidId; + auto& context = *found->second; PoolScope scope(context.pool); std::vector> authorities; std::vector> handles; - for (int index = 0; index < caCertNum; ++index) { + for (int index = 0; caList && index < caCertNum; ++index) { if (!caList[index]) throw std::invalid_argument("Missing SSL certificate descriptor"); auto chain = ParseCertificate(*caList[index]); for (auto* entry = &chain->root; entry; entry = entry->next) { @@ -665,40 +696,51 @@ int APS5_VABI sceSslLoadCert(int sslCtxId, int caCertNum, SceSsl::Data** caList, } authorities.push_back(std::move(chain)); } - std::shared_ptr identity; - if (cert) { - identity = std::make_shared(); - identity->chain = ParseCertificate(*cert); - const auto key = CopyInput(*privateKey); - Random random; - random.Seed(); - CheckBackend(mbedtls_pk_parse_key(&identity->key.key, key.bytes.get(), key.size, nullptr, 0, - mbedtls_ctr_drbg_random, &random.drbg), "Invalid SSL private key"); - CheckBackend(mbedtls_pk_check_pair(&identity->chain->root.pk, &identity->key.key, mbedtls_ctr_drbg_random, &random.drbg), - "SSL client certificate and private key do not match"); - } context.authorities.reserve(Add(context.authorities.size(), authorities.size())); context.certificateHandles.reserve(Add(context.certificateHandles.size(), handles.size())); for (auto& chain : authorities) context.authorities.push_back(std::move(chain)); for (auto& handle : handles) context.certificateHandles.push_back(std::move(handle)); - if (identity) context.identity = std::move(identity); + if (cert) { + if (!cert->ptr) return static_cast(0x80951771); + context.identity = std::make_shared(); + context.identity->chain = ParseCertificate(*cert); + } + if (privateKey) { + if (!privateKey->ptr) return static_cast(0x80951771); + if (!context.identity) context.identity = std::make_shared(); + auto identity = std::make_shared(*context.identity); + context.identity = identity; + identity->key.reset(); + auto bytes = Allocate(privateKey->size); + std::memcpy(bytes.get(), privateKey->ptr, privateKey->size); + identity->key = std::make_shared(EncodedPrivateKey{std::move(bytes), privateKey->size}); + } return 0; } int APS5_VABI sceSslUnloadCert(int sslCtxId) { auto& state = State(); std::lock_guard lock(state.mutex); - auto& context = GetContext(sslCtxId); + const auto found = state.contexts.find(sslCtxId); + if (found == state.contexts.end()) { + const auto* parameters = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t sdkVersion; + std::memcpy(&sdkVersion, parameters + 16, sizeof(sdkVersion)); + return sdkVersion < 0x07500000 ? 0 : ErrorInvalidId; + } + auto& context = *found->second; context.authorities.clear(); context.identity.reset(); return 0; } int APS5_VABI sceSslGetCaList(int sslCtxId, SceSsl::CaList* output) { - if (!output) return ErrorInvalidArg; + if (!output) return ErrorInvalidValue; auto& state = State(); std::lock_guard lock(state.mutex); - auto& context = GetContext(sslCtxId); + const auto found = state.contexts.find(sslCtxId); + if (found == state.contexts.end()) return ErrorInvalidId; + auto& context = *found->second; PoolScope scope(context.pool); const auto count = CertificateCount(context); if (!count) { @@ -730,14 +772,11 @@ int APS5_VABI sceSslFreeCaList(int sslCtxId, SceSsl::CaList* list) { } int APS5_VABI sceSslGetCaCerts(int sslCtxId, SceSsl::CaCerts* output) { - if (!output) return ErrorInvalidArg; auto& state = State(); std::lock_guard lock(state.mutex); const auto found = state.contexts.find(sslCtxId); - if (found == state.contexts.end()) { - *output = {}; - return ErrorNotFound; - } + if (found == state.contexts.end()) return ErrorInvalidId; + if (!output) return ErrorInvalidValue; auto& context = *found->second; PoolScope scope(context.pool); const auto count = CertificateCount(context); @@ -905,6 +944,7 @@ int APS5_VABI sceSslConnect(int sslConnectionId) { } int APS5_VABI sceSslRead(int sslConnectionId, void* buf, size_t len, int flags) { + if (static_cast(flags) > 3) return ErrorInvalidValue; if (!buf && len) throw std::invalid_argument("sceSslRead: null buffer"); std::shared_ptr session; if (const int code = Establish(__func__, sslConnectionId, session)) { @@ -922,7 +962,28 @@ int APS5_VABI sceSslRead(int sslConnectionId, void* buf, size_t len, int flags) const auto total = std::min(len, INT_MAX); std::size_t received = 0; while (received < total) { - const int result = mbedtls_ssl_read(&session->ssl, bytes + received, total - received); + if (session->peekOffset < session->peekSize) { + const auto count = std::min(total - received, session->peekSize - session->peekOffset); + std::memcpy(bytes + received, session->peekBuffer.get() + session->peekOffset, count); + received += count; + if (flags == 3) break; + session->peekOffset += count; + if (session->peekOffset == session->peekSize) { + session->peekOffset = 0; + session->peekSize = 0; + session->peekBuffer.reset(); + } + if (flags != 0) break; + continue; + } + if (flags == 3 && !session->peekBuffer) session->peekBuffer = Allocate(MBEDTLS_SSL_IN_CONTENT_LEN); + const int result = flags == 3 + ? mbedtls_ssl_read(&session->ssl, session->peekBuffer.get(), MBEDTLS_SSL_IN_CONTENT_LEN) + : mbedtls_ssl_read(&session->ssl, bytes + received, total - received); + if (result > 0 && flags == 3) { + session->peekSize = static_cast(result); + continue; + } if (result > 0) { received += static_cast(result); if (flags != 0) break; @@ -991,31 +1052,24 @@ int APS5_VABI sceSslClose(int sslConnectionId) { } int APS5_VABI sceSslGetSerialNumber(int sslCtxId, void* sslCert, unsigned char* sboData, std::size_t* sboLen) { - if (!sboLen) return ErrorInvalidArg; std::lock_guard lock(State().mutex); + if (!State().contexts.contains(sslCtxId)) return ErrorInvalidId; + if (!sslCert) return ErrorInvalidValue; const auto* crt = RequireCertificate(__func__, sslCtxId, sslCert); - const auto reqLen = crt->serial.len; - if (!sboData || *sboLen == 0) { - *sboLen = reqLen; + if (!sboData) { + if (sboLen) *sboLen = crt->serial.len; return 0; } - if (*sboLen < reqLen) { - *sboLen = reqLen; - return ErrorInvalidArg; - } - std::memcpy(sboData, crt->serial.p, reqLen); + if (!sboLen) throw std::invalid_argument("sceSslGetSerialNumber: output buffer requires a length pointer"); + const auto copyLength = std::min(*sboLen, crt->serial.len); + if (copyLength != 0) std::memcpy(sboData, crt->serial.p, copyLength); return 0; } int APS5_VABI sceSslFreeSslCertName(int sslCtxId, void* certName) { - if (!certName) return ErrorInvalidArg; - auto& state = State(); - std::lock_guard lock(state.mutex); - auto& context = GetContext(sslCtxId); - const auto found = context.certNames.find(certName); - if (found == context.certNames.end()) throw std::invalid_argument("sceSslFreeSslCertName: unknown cert name"); - context.certNames.erase(found); - return 0; + std::lock_guard lock(State().mutex); + if (!State().contexts.contains(sslCtxId)) return ErrorInvalidId; + return certName ? 0 : ErrorInvalidValue; } int APS5_VABI sceSslEnableVerifyOption(int sslConnectionId, std::uint32_t option) { @@ -1040,7 +1094,9 @@ const char* APS5_VABI sceSslGetAlpnSelected(int sslConnectionId) { std::shared_ptr session; { std::lock_guard lock(State().mutex); - session = RequireConnection(__func__, sslConnectionId).session; + const auto* connection = FindConnection(sslConnectionId); + if (!connection) return nullptr; + session = connection->session; } if (!session) return nullptr; std::lock_guard sessionLock(session->mutex); @@ -1049,12 +1105,24 @@ const char* APS5_VABI sceSslGetAlpnSelected(int sslConnectionId) { } int APS5_VABI sceSslSetAlpn(int sslConnectionId, const char* proto) { - if (!proto) throw std::invalid_argument("sceSslSetAlpn: null protocol list"); std::lock_guard lock(State().mutex); auto* connection = FindConnection(sslConnectionId); if (!connection) return ErrorInvalidId; - if (!*proto) return ErrorBadLength; - connection->alpn = proto; + connection->alpn.clear(); + if (!proto) return 0; + std::size_t length = 0; + while (length < 65535 && proto[length] != 0) ++length; + if (length >= 65535) return ErrorBadLength; + std::size_t tokenLength = 0; + for (std::size_t index = 0; index <= length; ++index) { + if (index == length || proto[index] == ',') { + if (tokenLength == 0 || tokenLength > 255) return ErrorBadLength; + tokenLength = 0; + } else { + ++tokenLength; + } + } + connection->alpn.assign(proto, length); return 0; } @@ -1072,58 +1140,59 @@ void* APS5_VABI sceSslGetIssuerName(int sslCtxId, void* sslCert) { } int APS5_VABI sceSslGetNameEntryCount(int sslCtxId, void* certName) { - if (!certName) return ErrorInvalidArg; auto& state = State(); std::lock_guard lock(state.mutex); + if (!state.contexts.contains(sslCtxId)) return ErrorInvalidId; + if (!certName) return ErrorInvalidValue; auto& context = GetContext(sslCtxId); const auto found = context.certNames.find(certName); - if (found == context.certNames.end()) throw std::invalid_argument("sceSslGetNameEntryCount: unknown cert name"); + if (found == context.certNames.end() || found->second->owner.expired()) throw std::invalid_argument("sceSslGetNameEntryCount: unknown or expired cert name"); return static_cast(found->second->entries.size()); } -int APS5_VABI sceSslGetNameEntryInfo(int sslCtxId, void* certName, int entryNum, char* oidname, std::size_t maxOidnameLen, - char* value, std::size_t maxValueLen, std::size_t* valueLen) { - if (!certName) return ErrorInvalidArg; +int APS5_VABI sceSslGetNameEntryInfo(int sslCtxId, void* certName, int entryNum, char* oidname, std::size_t maxOidnameLen, char* value, std::size_t maxValueLen, std::size_t* valueLen) { auto& state = State(); std::lock_guard lock(state.mutex); + if (!state.contexts.contains(sslCtxId)) return ErrorInvalidId; + if (!certName || !oidname || !value || !valueLen) return ErrorInvalidValue; auto& context = GetContext(sslCtxId); const auto found = context.certNames.find(certName); - if (found == context.certNames.end()) throw std::invalid_argument("sceSslGetNameEntryInfo: unknown cert name"); + if (found == context.certNames.end() || found->second->owner.expired()) throw std::invalid_argument("sceSslGetNameEntryInfo: unknown or expired cert name"); const auto& entries = found->second->entries; - if (entryNum < 0 || static_cast(entryNum) >= entries.size()) return ErrorInvalidArg; + if (entryNum < 0 || static_cast(entryNum) >= entries.size()) return ErrorInvalidValue; const auto& entry = entries[static_cast(entryNum)]; - if (oidname && maxOidnameLen > 0) { - const auto copyLen = std::min(entry.oid.size(), maxOidnameLen - 1); - std::memcpy(oidname, entry.oid.data(), copyLen); - oidname[copyLen] = '\0'; + const auto copyLength = std::min(entry.value.size(), maxValueLen); + if (copyLength != 0) std::memcpy(value, entry.value.data(), copyLength); + *valueLen = copyLength; + if (maxOidnameLen != 0) { + std::memset(oidname, 0, maxOidnameLen); + std::memcpy(oidname, entry.oid.data(), std::min(entry.oid.size(), maxOidnameLen)); } - if (value && maxValueLen > 0) { - const auto copyLen = std::min(entry.value.size(), maxValueLen - 1); - std::memcpy(value, entry.value.data(), copyLen); - value[copyLen] = '\0'; - } - if (valueLen) *valueLen = entry.value.size(); return 0; } int APS5_VABI sceSslGetNotBefore(int sslCtxId, void* sslCert, SceSsl::RtcTick* begin) { - if (!begin) return ErrorInvalidArg; std::lock_guard lock(State().mutex); + if (!State().contexts.contains(sslCtxId)) return ErrorInvalidId; + if (!sslCert || !begin) return ErrorInvalidValue; const auto* crt = RequireCertificate(__func__, sslCtxId, sslCert); begin->tick = TimeToTick(crt->valid_from); return 0; } int APS5_VABI sceSslGetNotAfter(int sslCtxId, void* sslCert, SceSsl::RtcTick* limit) { - if (!limit) return ErrorInvalidArg; std::lock_guard lock(State().mutex); + if (!State().contexts.contains(sslCtxId)) return ErrorInvalidId; + if (!sslCert || !limit) return ErrorInvalidValue; const auto* crt = RequireCertificate(__func__, sslCtxId, sslCert); limit->tick = TimeToTick(crt->valid_to); return 0; } int APS5_VABI sceSslGetFingerprint(int sslCtxId, void* sslCert, int hashType, unsigned char* fingerprint) { - if (!fingerprint) return ErrorInvalidArg; + std::lock_guard lock(State().mutex); + if (!State().contexts.contains(sslCtxId)) return ErrorInvalidId; + if (!sslCert || !fingerprint) return ErrorInvalidValue; mbedtls_md_type_t mdType; switch (hashType) { case 0: mdType = MBEDTLS_MD_MD5; break; @@ -1136,32 +1205,36 @@ int APS5_VABI sceSslGetFingerprint(int sslCtxId, void* sslCert, int hashType, un } const auto* info = mbedtls_md_info_from_type(mdType); if (!info) return ErrorInvalidArg; - std::lock_guard lock(State().mutex); const auto* crt = RequireCertificate(__func__, sslCtxId, sslCert); if (mbedtls_md(info, crt->raw.p, crt->raw.len, fingerprint) != 0) return ErrorInvalidArg; return 0; } int APS5_VABI sceSslGetPem(int sslCtxId, void* sslCert, char* buf, std::size_t* bufLen) { - if (!bufLen) return ErrorInvalidArg; + if (!sslCert || !bufLen) return ErrorInvalidValue; std::lock_guard lock(State().mutex); - auto& context = GetContext(sslCtxId); + if (!State().contexts.contains(sslCtxId)) return ErrorInvalidId; const auto* crt = RequireCertificate(__func__, sslCtxId, sslCert); - PoolScope scope(context.pool); - std::size_t required = 0; - const auto encode = [&](unsigned char* output, std::size_t capacity, std::size_t* written) { - return mbedtls_pem_write_buffer("-----BEGIN CERTIFICATE-----\n", "-----END CERTIFICATE-----\n", crt->raw.p, crt->raw.len, output, capacity, written); - }; - const int sizing = encode(nullptr, 0, &required); - if (sizing != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) CheckBackend(sizing, "SSL PEM sizing failed"); - if (!buf) { - *bufLen = required; - return 0; + const std::size_t encodedLength = Add(crt->raw.len, 2) / 3 * 4; + const std::size_t lineCount = Add(encodedLength, 63) / 64; + const std::size_t required = Add(57, Add(encodedLength, lineCount * 2)); + const std::size_t capacity = *bufLen; + if (buf && capacity != 0) std::memset(buf, 0, capacity); + *bufLen = required; + if (!buf) return 0; + if (capacity < required) return static_cast(0x8095f008); + constexpr char header[] = "-----BEGIN CERTIFICATE-----\r\n"; + constexpr char footer[] = "-----END CERTIFICATE-----\r\n"; + std::memcpy(buf, header, sizeof(header) - 1); + std::size_t offset = sizeof(header) - 1; + for (std::size_t position = 0; position < crt->raw.len; position += 48) { + std::size_t written = 0; + CheckBackend(mbedtls_base64_encode(reinterpret_cast(buf + offset), capacity - offset, &written, crt->raw.p + position, std::min(48, crt->raw.len - position)), "SSL PEM encoding failed"); + offset += written; + buf[offset++] = '\r'; + buf[offset++] = '\n'; } - if (*bufLen < required) throw std::length_error("SSL PEM buffer is too small"); - std::size_t written = 0; - CheckBackend(encode(reinterpret_cast(buf), required, &written), "SSL PEM encoding failed"); - *bufLen = written; + std::memcpy(buf + offset, footer, sizeof(footer)); return 0; } @@ -1190,7 +1263,6 @@ int APS5_VABI CRYPTO_initAsymmetricKey() { } int APS5_VABI CRYPTO_uninitAsymmetricKey() { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -1200,7 +1272,6 @@ int APS5_VABI RSA_verifySignature() { } int APS5_VABI VLONG_freeVlongQueue() { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -1265,8 +1336,7 @@ int APS5_VABI sceSslLoadRootCACert() { } int APS5_VABI sceSslRecv(int sslConnectionId, void* buf, std::size_t fulllen) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceSslRead(sslConnectionId, buf, fulllen, 0); } int APS5_VABI sceSslReuseConnection(int sslConnectionId, int sockId) { @@ -1275,8 +1345,7 @@ int APS5_VABI sceSslReuseConnection(int sslConnectionId, int sockId) { } int APS5_VABI sceSslSend(int sslConnectionId, void* msg, std::size_t fulllen) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceSslWrite(sslConnectionId, msg, fulllen, 0); } int APS5_VABI sceSslSetSslVersion() { @@ -1284,4 +1353,9 @@ int APS5_VABI sceSslSetSslVersion() { return 0; } +int APS5_VABI sceSslSetServerName() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceSsl/include/Ssl.hpp b/core/libs/prx/libSceSsl/include/Ssl.hpp new file mode 100644 index 0000000000..5de6ba4c60 --- /dev/null +++ b/core/libs/prx/libSceSsl/include/Ssl.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "SceTypes.hpp" +#include "prx/libSceSsl/SslTypes.hpp" + +using SslVerifyCallback = int (APS5_VABI *)(int, unsigned int, void**, int, void*); + +extern "C" { + +int APS5_VABI sceSslInit_nid_postfix(std::uint64_t poolSize); +int APS5_VABI sceSslTerm_nid_postfix(int sslCtxId); +int APS5_VABI sceSslGetMemoryPoolStats(int sslCtxId, SceSsl::MemoryPoolStats* stats); +int APS5_VABI sceSslLoadCert(int sslCtxId, int caCertNum, SceSsl::Data** caList, SceSsl::Data* cert, SceSsl::Data* privateKey); +int APS5_VABI sceSslUnloadCert(int sslCtxId); +int APS5_VABI sceSslGetCaList(int sslCtxId, SceSsl::CaList* output); +int APS5_VABI sceSslFreeCaList(int sslCtxId, SceSsl::CaList* list); +int APS5_VABI sceSslGetCaCerts(int sslCtxId, SceSsl::CaCerts* output); +int APS5_VABI sceSslFreeCaCerts(int sslCtxId, SceSsl::CaCerts* certificates); +int APS5_VABI sceSslCreateConnection(int sslCtxId, int sockId, const char* host); +int APS5_VABI sceSslDeleteConnection(int sslConnectionId); +int APS5_VABI sceSslSetVerifyCallback(int sslConnectionId, SslVerifyCallback cbfunc, void* userArg); +int APS5_VABI sceSslConnect(int sslConnectionId); +int APS5_VABI sceSslRead(int sslConnectionId, void* buf, size_t len, int flags); +int APS5_VABI sceSslWrite(int sslConnectionId, void* buf, size_t len, int flags); +int APS5_VABI sceSslClose(int sslConnectionId); +int APS5_VABI sceSslGetSerialNumber(int sslCtxId, void* sslCert, unsigned char* sboData, std::size_t* sboLen); +int APS5_VABI sceSslFreeSslCertName(int sslCtxId, void* certName); +int APS5_VABI sceSslEnableVerifyOption(int sslConnectionId, std::uint32_t option); +int APS5_VABI sceSslDisableVerifyOption(int sslConnectionId, std::uint32_t option); +const char* APS5_VABI sceSslGetAlpnSelected(int sslConnectionId); +int APS5_VABI sceSslSetAlpn(int sslConnectionId, const char* proto); +int APS5_VABI sceSslSetMinSslVersion(int sslConnectionId, int version); +void* APS5_VABI sceSslGetIssuerName(int sslCtxId, void* sslCert); +int APS5_VABI sceSslGetNameEntryCount(int sslCtxId, void* certName); +int APS5_VABI sceSslGetNameEntryInfo(int sslCtxId, void* certName, int entryNum, char* oidname, std::size_t maxOidnameLen, char* value, std::size_t maxValueLen, std::size_t* valueLen); +int APS5_VABI sceSslGetNotBefore(int sslCtxId, void* sslCert, SceSsl::RtcTick* begin); +int APS5_VABI sceSslGetNotAfter(int sslCtxId, void* sslCert, SceSsl::RtcTick* limit); +int APS5_VABI sceSslGetFingerprint(int sslCtxId, void* sslCert, int hashType, unsigned char* fingerprint); +int APS5_VABI sceSslGetPem(int sslCtxId, void* sslCert, char* buf, std::size_t* bufLen); +void* APS5_VABI sceSslGetSubjectName(int sslCtxId, void* sslCert); +int APS5_VABI CA_MGMT_extractKeyBlobEx(); +int APS5_VABI CA_MGMT_extractPublicKeyInfo(); +int APS5_VABI CA_MGMT_freeKeyBlob(); +int APS5_VABI CRYPTO_initAsymmetricKey(); +int APS5_VABI CRYPTO_uninitAsymmetricKey(); +int APS5_VABI RSA_verifySignature(); +int APS5_VABI VLONG_freeVlongQueue(); +int APS5_VABI sceSslCheckRecvPending(); +int APS5_VABI sceSslCreateSslConnection(); +int APS5_VABI sceSslDeleteSslConnection(); +int APS5_VABI sceSslDisableOption(); +int APS5_VABI sceSslDisableOptionInternal(); +int APS5_VABI sceSslDisableOptionInternalInsecure(); +int APS5_VABI sceSslEnableOption(); +int APS5_VABI sceSslEnableOptionInternal(); +int APS5_VABI sceSslGetNanoSSLModuleId(); +int APS5_VABI sceSslGetPeerCert(); +int APS5_VABI sceSslGetSslError(); +int APS5_VABI sceSslLoadRootCACert(); +int APS5_VABI sceSslRecv(int sslConnectionId, void* buf, std::size_t fulllen); +int APS5_VABI sceSslReuseConnection(int sslConnectionId, int sockId); +int APS5_VABI sceSslSend(int sslConnectionId, void* msg, std::size_t fulllen); +int APS5_VABI sceSslSetSslVersion(); +int APS5_VABI sceSslSetServerName(); + +} diff --git a/core/libs/prx/libSceSsl/libSceSsl.hpp b/core/libs/prx/libSceSsl/libSceSsl.hpp new file mode 100644 index 0000000000..731f181cb2 --- /dev/null +++ b/core/libs/prx/libSceSsl/libSceSsl.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSsl/include/Ssl.hpp" diff --git a/core/libs/prx/libSceSulphaDrv/CMakeLists.txt b/core/libs/prx/libSceSulphaDrv/CMakeLists.txt new file mode 100644 index 0000000000..649d75c4d0 --- /dev/null +++ b/core/libs/prx/libSceSulphaDrv/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libSceSulphaDrv SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceSulphaDrv PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceSulphaDrv) + +if(BUILD_TESTING) + add_test_executable(guest_sulpha_config_tests ${LIBS_INCLUDE_DIR}/tests/GuestSulphaConfig.cpp) + target_include_directories(guest_sulpha_config_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sulpha_config_tests PRIVATE libSceSulphaDrv libc) + configure_windows_unwind(guest_sulpha_config_tests) + add_test(NAME guest_sulpha_config COMMAND guest_sulpha_config_tests) + if(WIN32) + set_tests_properties(guest_sulpha_config PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceSulphaDrv/Export.cpp b/core/libs/prx/libSceSulphaDrv/Export.cpp index b956051655..a40021ac2d 100644 --- a/core/libs/prx/libSceSulphaDrv/Export.cpp +++ b/core/libs/prx/libSceSulphaDrv/Export.cpp @@ -1,5 +1,7 @@ +#include "prx/libSceSulphaDrv/include/Api.hpp" #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -16,8 +18,10 @@ int APS5_VABI sceSulphaGetConnectedClientCount(void) { return 0; } -int APS5_VABI sceSulphaGetDefaultConfig(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSulphaGetDefaultConfig(void* config) { + if (config == nullptr) return static_cast(0x804D0003u); + static constexpr std::uint64_t defaults[]{24, 0x400000, 0xFA100000003}; + std::memcpy(config, defaults, sizeof(defaults)); return 0; } diff --git a/core/libs/prx/libSceSulphaDrv/include/Api.hpp b/core/libs/prx/libSceSulphaDrv/include/Api.hpp new file mode 100644 index 0000000000..86b1f74a4a --- /dev/null +++ b/core/libs/prx/libSceSulphaDrv/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSulphaGetClientInfo(void); +int APS5_VABI sceSulphaGetConnectedClientCount(void); +int APS5_VABI sceSulphaGetDefaultConfig(void* config); +int APS5_VABI sceSulphaGetNeededMemory(void); +int APS5_VABI sceSulphaInit(void); +int APS5_VABI sceSulphaMessage(void); +int APS5_VABI sceSulphaSetBookmark(void); +int APS5_VABI sceSulphaShutdown(void); + +} diff --git a/core/libs/prx/libSceSulphaDrv/libSceSulphaDrv.hpp b/core/libs/prx/libSceSulphaDrv/libSceSulphaDrv.hpp new file mode 100644 index 0000000000..1f85062b91 --- /dev/null +++ b/core/libs/prx/libSceSulphaDrv/libSceSulphaDrv.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSulphaDrv/include/Api.hpp" diff --git a/core/libs/prx/libSceSysBridge/libSceSysBridge.hpp b/core/libs/prx/libSceSysBridge/libSceSysBridge.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceSysBridge/libSceSysBridge.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceSysCore/Export.cpp b/core/libs/prx/libSceSysCore/Export.cpp index 7c5cd4bbd6..7758a11ed7 100644 --- a/core/libs/prx/libSceSysCore/Export.cpp +++ b/core/libs/prx/libSceSysCore/Export.cpp @@ -1,13 +1,300 @@ +#include "prx/libSceSysCore/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +static_assert(sizeof(ApplicationInfo) == 152); + extern "C" { -int APS5_VABI sceApplicationGetAppInfoByAppId() { +int APS5_VABI sceApplicationGetAppInfoByAppId(std::uint32_t applicationId, ApplicationInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI _sceApplicationGetAppId() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationAddProcess2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationBeginSclkChange() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationBlockingKill2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationBlockingKill3() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationContinue() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationCrashSyscore() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationCrashSystemProcess() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationEndSclkChange() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationExitSpawn() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationExitSpawn2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationFinalize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetAppPackageInfoForCoredump() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetCompiledSdkVersionByAppId() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetCoredumpDirAndFileName() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetCoredumpState() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetCpuUsage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetCpuUsage2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetDbgExecutablePath() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetMode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetProcs() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetShellCoreAppId() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationGetStateForDebugger() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationInitializeForShellCore() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationIsCrashReportBeingProcessed() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationIsPrimaryProcess() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationIsResumable() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationIsSuspendable() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationKickCoredump() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationKickCoredump2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationKill() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationLocalProcessKill() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationLocalProcessKill2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationLocalProcessResume() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationLocalProcessSuspend() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationNotifyCoredumpRequestEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationNotifyCoredumpRequestProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationNotifyVshMainOnStandby() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationNotifyVshReady() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationOnAllResourceReleased() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationRaiseException() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationRaiseExceptionToLocalPid() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationRaiseExceptionToPid() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationRequestToChangeRenderingMode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationRestore() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationResume() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSave() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSendDebugSpawnResult2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSendResultOfDebuggerKillRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSendResultOfDebuggerResumeRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSendResultOfDebuggerSuspendRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSendResultOfDebuggerTitleIdLaunchRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSetApplicationFocus() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSetControllerFocus() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSetControllerFocusPermissionToSubProcess() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSetMemoryPstate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSignalShellCoreHeartBeat() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSpawn2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSuspend() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSuspendSystemForDebugger() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSwitchToBaseMode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSwitchToNeoMode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSwitchToNeoMode2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSystemReboot() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSystemShutdown2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSystemSuspend() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplicationSystemSuspend2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceApplictionGetStateForDebugger() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceSysCoreGetEventCount() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceSysCoreReceiveEvent() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceSysCore/include/Api.hpp b/core/libs/prx/libSceSysCore/include/Api.hpp new file mode 100644 index 0000000000..8f3529042c --- /dev/null +++ b/core/libs/prx/libSceSysCore/include/Api.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "SceTypes.hpp" + +struct ApplicationInfo { + std::byte data[152]; +}; + +extern "C" { + +int APS5_VABI sceApplicationGetAppInfoByAppId(std::uint32_t applicationId, ApplicationInfo* info); + +int APS5_VABI _sceApplicationGetAppId(); +int APS5_VABI sceApplicationAddProcess2(); +int APS5_VABI sceApplicationBeginSclkChange(); +int APS5_VABI sceApplicationBlockingKill2(); +int APS5_VABI sceApplicationBlockingKill3(); +int APS5_VABI sceApplicationContinue(); +int APS5_VABI sceApplicationCrashSyscore(); +int APS5_VABI sceApplicationCrashSystemProcess(); +int APS5_VABI sceApplicationEndSclkChange(); +int APS5_VABI sceApplicationExitSpawn(); +int APS5_VABI sceApplicationExitSpawn2(); +int APS5_VABI sceApplicationFinalize(); +int APS5_VABI sceApplicationGetAppPackageInfoForCoredump(); +int APS5_VABI sceApplicationGetCompiledSdkVersionByAppId(); +int APS5_VABI sceApplicationGetCoredumpDirAndFileName(); +int APS5_VABI sceApplicationGetCoredumpState(); +int APS5_VABI sceApplicationGetCpuUsage(); +int APS5_VABI sceApplicationGetCpuUsage2(); +int APS5_VABI sceApplicationGetDbgExecutablePath(); +int APS5_VABI sceApplicationGetMode(); +int APS5_VABI sceApplicationGetProcs(); +int APS5_VABI sceApplicationGetShellCoreAppId(); +int APS5_VABI sceApplicationGetStateForDebugger(); +int APS5_VABI sceApplicationInitialize(); +int APS5_VABI sceApplicationInitializeForShellCore(); +int APS5_VABI sceApplicationIsCrashReportBeingProcessed(); +int APS5_VABI sceApplicationIsPrimaryProcess(); +int APS5_VABI sceApplicationIsResumable(); +int APS5_VABI sceApplicationIsSuspendable(); +int APS5_VABI sceApplicationKickCoredump(); +int APS5_VABI sceApplicationKickCoredump2(); +int APS5_VABI sceApplicationKill(); +int APS5_VABI sceApplicationLocalProcessKill(); +int APS5_VABI sceApplicationLocalProcessKill2(); +int APS5_VABI sceApplicationLocalProcessResume(); +int APS5_VABI sceApplicationLocalProcessSuspend(); +int APS5_VABI sceApplicationNotifyCoredumpRequestEnd(); +int APS5_VABI sceApplicationNotifyCoredumpRequestProgress(); +int APS5_VABI sceApplicationNotifyVshMainOnStandby(); +int APS5_VABI sceApplicationNotifyVshReady(); +int APS5_VABI sceApplicationOnAllResourceReleased(); +int APS5_VABI sceApplicationRaiseException(); +int APS5_VABI sceApplicationRaiseExceptionToLocalPid(); +int APS5_VABI sceApplicationRaiseExceptionToPid(); +int APS5_VABI sceApplicationRequestToChangeRenderingMode(); +int APS5_VABI sceApplicationRestore(); +int APS5_VABI sceApplicationResume(); +int APS5_VABI sceApplicationSave(); +int APS5_VABI sceApplicationSendDebugSpawnResult2(); +int APS5_VABI sceApplicationSendResultOfDebuggerKillRequest(); +int APS5_VABI sceApplicationSendResultOfDebuggerResumeRequest(); +int APS5_VABI sceApplicationSendResultOfDebuggerSuspendRequest(); +int APS5_VABI sceApplicationSendResultOfDebuggerTitleIdLaunchRequest(); +int APS5_VABI sceApplicationSetApplicationFocus(); +int APS5_VABI sceApplicationSetControllerFocus(); +int APS5_VABI sceApplicationSetControllerFocusPermissionToSubProcess(); +int APS5_VABI sceApplicationSetMemoryPstate(); +int APS5_VABI sceApplicationSignalShellCoreHeartBeat(); +int APS5_VABI sceApplicationSpawn2(); +int APS5_VABI sceApplicationSuspend(); +int APS5_VABI sceApplicationSuspendSystemForDebugger(); +int APS5_VABI sceApplicationSwitchToBaseMode(); +int APS5_VABI sceApplicationSwitchToNeoMode(); +int APS5_VABI sceApplicationSwitchToNeoMode2(); +int APS5_VABI sceApplicationSystemReboot(); +int APS5_VABI sceApplicationSystemShutdown2(); +int APS5_VABI sceApplicationSystemSuspend(); +int APS5_VABI sceApplicationSystemSuspend2(); +int APS5_VABI sceApplictionGetStateForDebugger(); +int APS5_VABI sceSysCoreGetEventCount(); +int APS5_VABI sceSysCoreReceiveEvent(); + +} diff --git a/core/libs/prx/libSceSysCore/libSceSysCore.hpp b/core/libs/prx/libSceSysCore/libSceSysCore.hpp new file mode 100644 index 0000000000..c0d6d0db48 --- /dev/null +++ b/core/libs/prx/libSceSysCore/libSceSysCore.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSysCore/include/Api.hpp" diff --git a/core/libs/prx/libSceSysUtil/Export.cpp b/core/libs/prx/libSceSysUtil/Export.cpp index 6ff461210b..a6f707cd6e 100644 --- a/core/libs/prx/libSceSysUtil/Export.cpp +++ b/core/libs/prx/libSceSysUtil/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSysUtil/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceSysUtil/include/Api.hpp b/core/libs/prx/libSceSysUtil/include/Api.hpp new file mode 100644 index 0000000000..f07cd4a9b9 --- /dev/null +++ b/core/libs/prx/libSceSysUtil/include/Api.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSysUtilSendSystemNotificationWithText(int32_t type, const char* message); +int APS5_VABI sceSysUtilSendAddressingSystemNotification(); +int APS5_VABI sceSysUtilSendAddressingSystemNotificationWithDeviceId(); +int APS5_VABI sceSysUtilSendAddressingSystemNotificationWithUserId(); +int APS5_VABI sceSysUtilSendAddressingSystemNotificationWithUserName(); +int APS5_VABI sceSysUtilSendNotificationRequest(); +int APS5_VABI sceSysUtilSendNpDebugNotificationRequest(); +int APS5_VABI sceSysUtilSendSystemNotification(); +int APS5_VABI sceSysUtilSendSystemNotification2(); +int APS5_VABI sceSysUtilSendSystemNotificationParamInit(); +int APS5_VABI sceSysUtilSendSystemNotificationWithAppId(); +int APS5_VABI sceSysUtilSendSystemNotificationWithAppInfo(); +int APS5_VABI sceSysUtilSendSystemNotificationWithAppName(); +int APS5_VABI sceSysUtilSendSystemNotificationWithAppNameRelatedToUser(); +int APS5_VABI sceSysUtilSendSystemNotificationWithDeviceId(); +int APS5_VABI sceSysUtilSendSystemNotificationWithDeviceIdRelatedToUser(); +int APS5_VABI sceSysUtilSendSystemNotificationWithErrorCode(); +int APS5_VABI sceSysUtilSendSystemNotificationWithParams(); +int APS5_VABI sceSysUtilSendSystemNotificationWithTextRelatedToUser(); +int APS5_VABI sceSysUtilSendSystemNotificationWithUserId(); +int APS5_VABI sceSysUtilSendSystemNotificationWithUserName(); +int APS5_VABI sceSysUtilSendSystemNotificationWithUserNameInfo(); +int APS5_VABI sceSysUtilSendTrcCheckNotificationRequest(); +int APS5_VABI sceSysUtilSendWebDebugNotificationRequest(); + +} diff --git a/core/libs/prx/libSceSysUtil/libSceSysUtil.hpp b/core/libs/prx/libSceSysUtil/libSceSysUtil.hpp new file mode 100644 index 0000000000..15c98b717c --- /dev/null +++ b/core/libs/prx/libSceSysUtil/libSceSysUtil.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSysUtil/include/Api.hpp" diff --git a/core/libs/prx/libSceSysmodule/Export.cpp b/core/libs/prx/libSceSysmodule/Export.cpp index 78b4774cfa..272768fe00 100644 --- a/core/libs/prx/libSceSysmodule/Export.cpp +++ b/core/libs/prx/libSceSysmodule/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceSysmodule/include/Api.hpp" #include #include #include @@ -17,8 +19,6 @@ #include "ModuleTable.hpp" #include "prx/libc/include/General.hpp" -extern "C" void* APS5_VABI dlopen_nid_postfix(const char* path, int flags); - namespace { constexpr int SCE_SYSMODULE_ERROR_INVALID_VALUE = static_cast(0x805A1000); diff --git a/core/libs/prx/libSceSysmodule/include/Api.hpp b/core/libs/prx/libSceSysmodule/include/Api.hpp new file mode 100644 index 0000000000..db439f37b3 --- /dev/null +++ b/core/libs/prx/libSceSysmodule/include/Api.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSysmoduleGetModuleInfoForUnwind(std::uint64_t addr, int flags, ModuleInfoForUnwind* info); +int APS5_VABI sceSysmoduleGetModuleHandleInternal(std::uint32_t id, std::int32_t* handle); +int APS5_VABI sceSysmoduleIsLoaded(std::uint16_t id); +int APS5_VABI sceSysmoduleLoadModule(std::uint16_t id); +int APS5_VABI sceSysmoduleLoadModuleInternalWithArg(std::uint32_t id, int argc, void* argv, std::uint64_t unk, int* ret); +int APS5_VABI sceSysmoduleUnloadModule(std::uint16_t id); +int APS5_VABI sceSysmoduleLoadModuleInternal(std::uint32_t id); +int APS5_VABI sceSysmoduleUnloadModuleInternal(std::uint32_t id); +int APS5_VABI sceSysmoduleUnloadModuleInternalWithArg(std::uint32_t id, int argc, void* argv, std::uint64_t unk, int* ret); +int APS5_VABI sceSysmoduleIsLoadedInternal(std::uint32_t id); +int APS5_VABI sceSysmoduleIsCalledFromSysModule(); +int APS5_VABI sceSysmoduleIsCameraPreloaded(); +int APS5_VABI sceSysmoduleLoadModuleByNameInternal(std::uint8_t* fname, std::uint64_t a1, std::uint64_t a2, std::uint64_t a3, std::uint64_t a4, std::uint64_t a5); +int APS5_VABI sceSysmoduleMapLibcForLibkernel(); +int APS5_VABI sceSysmodulePreloadModuleForLibkernel(); +int APS5_VABI sceSysmoduleUnloadModuleByNameInternal(); + +} diff --git a/core/libs/prx/libSceSysmodule/libSceSysmodule.hpp b/core/libs/prx/libSceSysmodule/libSceSysmodule.hpp new file mode 100644 index 0000000000..235f65eecc --- /dev/null +++ b/core/libs/prx/libSceSysmodule/libSceSysmodule.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSysmodule/include/Api.hpp" diff --git a/core/libs/prx/libSceSystemGesture/Export.cpp b/core/libs/prx/libSceSystemGesture/Export.cpp index 4d74061f67..056318a3a4 100644 --- a/core/libs/prx/libSceSystemGesture/Export.cpp +++ b/core/libs/prx/libSceSystemGesture/Export.cpp @@ -1,18 +1,19 @@ +#include "prx/libSceSystemGesture/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include +#include #include #include #include #include #include -static constexpr int32_t GESTURE_HANDLE = 1; -static constexpr int SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT = static_cast(0x80D10002); -static constexpr int SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE = static_cast(0x80D10003); -static constexpr int SCE_SYSTEM_GESTURE_ERROR_INDEX_OUT_OF_ARRAY = static_cast(0x80D10005); +static constexpr int SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT = static_cast(0x80890001); +static constexpr int SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE = static_cast(0x80890006); +static constexpr int SCE_SYSTEM_GESTURE_ERROR_INDEX_OUT_OF_ARRAY = static_cast(0x80890004); namespace { @@ -54,7 +55,19 @@ struct TrackedTouch { }; std::mutex primitiveTouchMutex; -std::vector primitiveTouches; +struct GestureContext { + std::int32_t handle = 0; + std::vector touches; +}; + +std::array gestureContexts; +bool gestureInitialized = false; +std::uint16_t gestureGeneration = 0; + +GestureContext* findGestureContext(std::int32_t gestureHandle) { + const auto found = std::find_if(gestureContexts.begin(), gestureContexts.end(), [gestureHandle](const GestureContext& context) { return context.handle != 0 && context.handle == gestureHandle; }); + return found == gestureContexts.end() ? nullptr : &*found; +} struct TouchSample { std::uint16_t primitiveId; @@ -73,7 +86,7 @@ std::vector reportSamples(const PadData& report) { return samples; } -void sampleReport(const PadData& report) { +void sampleReport(std::vector& primitiveTouches, const PadData& report) { std::erase_if(primitiveTouches, [](const TrackedTouch& touch) { return touch.eventState == TOUCH_STATE_END; }); for (TrackedTouch& touch : primitiveTouches) { touch.sampled = false; @@ -132,19 +145,29 @@ SystemGesturePrimitiveTouchEvent buildPrimitiveTouchEvent(const TrackedTouch& to extern "C" { -int APS5_VABI sceSystemGestureAppendTouchRecognizer(int32_t gesture_handle, SystemGestureTouchRecognizer* recognizer) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureAppendTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; if (!recognizer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; return 0; } -int APS5_VABI sceSystemGestureClose(int32_t gesture_handle) { - return gesture_handle == GESTURE_HANDLE ? 0 : SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureClose(int32_t gestureHandle) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); + auto* context = findGestureContext(gestureHandle); + if (!context) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + context->touches.clear(); + context->handle = 0; + return 0; } -int APS5_VABI sceSystemGestureCreateTouchRecognizer(int32_t gesture_handle, SystemGestureTouchRecognizer* recognizer, int32_t type, const SystemGestureRectangle* rectangle, const void* param) { +int APS5_VABI sceSystemGestureCreateTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer, int32_t type, const SystemGestureRectangle* rectangle, const void* param) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); (void)param; - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; if (!recognizer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; RecognizerState state{}; state.magic = RECOGNIZER_STATE_MAGIC; @@ -157,81 +180,93 @@ int APS5_VABI sceSystemGestureCreateTouchRecognizer(int32_t gesture_handle, Syst int APS5_VABI sceSystemGestureFinalizePrimitiveTouchRecognizer(void) { std::lock_guard lock(primitiveTouchMutex); - primitiveTouches.clear(); + gestureInitialized = false; return 0; } -int APS5_VABI sceSystemGestureGetPrimitiveTouchEventByIndex(int32_t gesture_handle, uint32_t index, SystemGesturePrimitiveTouchEvent* event) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureGetPrimitiveTouchEventByIndex(int32_t gestureHandle, uint32_t index, SystemGesturePrimitiveTouchEvent* event) { std::lock_guard lock(primitiveTouchMutex); - if (index >= primitiveTouches.size()) return SCE_SYSTEM_GESTURE_ERROR_INDEX_OUT_OF_ARRAY; + if (!gestureInitialized) return static_cast(0x80890003); if (!event) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; - *event = buildPrimitiveTouchEvent(primitiveTouches[index]); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + if (index >= findGestureContext(gestureHandle)->touches.size()) return SCE_SYSTEM_GESTURE_ERROR_INDEX_OUT_OF_ARRAY; + *event = buildPrimitiveTouchEvent(findGestureContext(gestureHandle)->touches[index]); return 0; } -int APS5_VABI sceSystemGestureGetPrimitiveTouchEventByPrimitiveID(int32_t gesture_handle, uint16_t primitiveId, SystemGesturePrimitiveTouchEvent* event) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; - if (!event) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; +int APS5_VABI sceSystemGestureGetPrimitiveTouchEventByPrimitiveID(int32_t gestureHandle, uint16_t primitiveId, SystemGesturePrimitiveTouchEvent* event) { std::lock_guard lock(primitiveTouchMutex); - const auto tracked = std::find_if(primitiveTouches.begin(), primitiveTouches.end(), + if (!gestureInitialized) return static_cast(0x80890003); + if (!event) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + const auto tracked = std::find_if(findGestureContext(gestureHandle)->touches.begin(), findGestureContext(gestureHandle)->touches.end(), [&](const TrackedTouch& touch) { return touch.primitiveId == primitiveId; }); - if (tracked == primitiveTouches.end()) - throw std::runtime_error(std::string(__func__) + ": no primitive touch event with id " + std::to_string(primitiveId)); + if (tracked == findGestureContext(gestureHandle)->touches.end()) return static_cast(0x80890005); *event = buildPrimitiveTouchEvent(*tracked); return 0; } -int APS5_VABI sceSystemGestureGetPrimitiveTouchEvents(int32_t gesture_handle, SystemGesturePrimitiveTouchEvent* event_buffer, uint32_t capacity_of_buffer, uint32_t* number_of_event) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; - if (!number_of_event) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; +int APS5_VABI sceSystemGestureGetPrimitiveTouchEvents(int32_t gestureHandle, SystemGesturePrimitiveTouchEvent* eventBuffer, uint32_t capacityOfBuffer, uint32_t* numberOfEvent) { std::lock_guard lock(primitiveTouchMutex); - const std::uint32_t reported = static_cast(std::min(primitiveTouches.size(), capacity_of_buffer)); - if (reported > 0 && !event_buffer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; - for (std::uint32_t i = 0; i < reported; ++i) event_buffer[i] = buildPrimitiveTouchEvent(primitiveTouches[i]); - *number_of_event = reported; + if (!gestureInitialized) return static_cast(0x80890003); + if (!eventBuffer || !capacityOfBuffer || !numberOfEvent) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + const std::uint32_t reported = static_cast(std::min(findGestureContext(gestureHandle)->touches.size(), capacityOfBuffer)); + for (std::uint32_t i = 0; i < reported; ++i) eventBuffer[i] = buildPrimitiveTouchEvent(findGestureContext(gestureHandle)->touches[i]); + *numberOfEvent = reported; return 0; } -int APS5_VABI sceSystemGestureGetPrimitiveTouchEventsCount(int32_t gesture_handle) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureGetPrimitiveTouchEventsCount(int32_t gestureHandle) { std::lock_guard lock(primitiveTouchMutex); - return static_cast(primitiveTouches.size()); + if (!gestureInitialized) return static_cast(0x80890003); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + return static_cast(findGestureContext(gestureHandle)->touches.size()); } -int APS5_VABI sceSystemGestureGetTouchEventByEventID(int32_t gesture_handle, const SystemGestureTouchRecognizer* recognizer, uint32_t eventId, SystemGestureTouchEvent* event) { +int APS5_VABI sceSystemGestureGetTouchEventByEventID(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer, uint32_t eventId, SystemGestureTouchEvent* event) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); (void)eventId; (void)event; - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; if (!recognizer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; return SCE_SYSTEM_GESTURE_ERROR_INDEX_OUT_OF_ARRAY; } -int APS5_VABI sceSystemGestureGetTouchEventByIndex(int32_t gesture_handle, const SystemGestureTouchRecognizer* recognizer, uint32_t index, SystemGestureTouchEvent* event) { +int APS5_VABI sceSystemGestureGetTouchEventByIndex(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer, uint32_t index, SystemGestureTouchEvent* event) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); (void)index; (void)event; - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; if (!recognizer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; return SCE_SYSTEM_GESTURE_ERROR_INDEX_OUT_OF_ARRAY; } -int APS5_VABI sceSystemGestureGetTouchEvents(int32_t gesture_handle, const SystemGestureTouchRecognizer* recognizer, SystemGestureTouchEvent* event_buffer, uint32_t capacity_of_buffer, uint32_t* number_of_event) { +int APS5_VABI sceSystemGestureGetTouchEvents(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer, SystemGestureTouchEvent* eventBuffer, uint32_t capacityOfBuffer, uint32_t* numberOfEvent) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); (void)recognizer; - (void)event_buffer; - (void)capacity_of_buffer; - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; - if (!number_of_event) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; - *number_of_event = 0; + (void)eventBuffer; + (void)capacityOfBuffer; + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + if (!numberOfEvent) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; + *numberOfEvent = 0; return 0; } -int APS5_VABI sceSystemGestureGetTouchEventsCount(int32_t gesture_handle, const SystemGestureTouchRecognizer* recognizer) { +int APS5_VABI sceSystemGestureGetTouchEventsCount(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); (void)recognizer; - return gesture_handle == GESTURE_HANDLE ? 0 : SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + return findGestureContext(gestureHandle) != nullptr ? 0 : SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; } -int APS5_VABI sceSystemGestureGetTouchRecognizerInformation(int32_t gesture_handle, const SystemGestureTouchRecognizer* recognizer, SystemGestureTouchRecognizerInformation* information) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureGetTouchRecognizerInformation(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer, SystemGestureTouchRecognizerInformation* information) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; if (!recognizer || !information) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; RecognizerState state{}; if (!loadRecognizerState(recognizer, state)) @@ -244,61 +279,82 @@ int APS5_VABI sceSystemGestureGetTouchRecognizerInformation(int32_t gesture_hand return 0; } -int APS5_VABI sceSystemGestureInitializePrimitiveTouchRecognizer(const void* param) { - (void)param; +int APS5_VABI sceSystemGestureInitializePrimitiveTouchRecognizer() { std::lock_guard lock(primitiveTouchMutex); - primitiveTouches.clear(); + gestureInitialized = true; return 0; } -int32_t APS5_VABI sceSystemGestureOpen(int32_t input_type, const void* param) { - (void)input_type; - (void)param; - return GESTURE_HANDLE; +int32_t APS5_VABI sceSystemGestureOpen(int32_t inputType, const void* param) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); + if (inputType != 0 || param) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; + for (std::size_t index = 0; index < gestureContexts.size(); ++index) { + auto& context = gestureContexts[index]; + if (context.handle != 0) continue; + ++gestureGeneration; + if (gestureGeneration == 0) gestureGeneration = 1; + context.touches.clear(); + context.handle = static_cast(1191182336u + (static_cast(gestureGeneration) << 8) + index); + return context.handle; + } + return static_cast(0x8089000A); } -int APS5_VABI sceSystemGestureRemoveTouchRecognizer(int32_t gesture_handle, SystemGestureTouchRecognizer* recognizer) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureRemoveTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; if (!recognizer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; return 0; } -int APS5_VABI sceSystemGestureResetPrimitiveTouchRecognizer(int32_t gesture_handle) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureResetPrimitiveTouchRecognizer(int32_t gestureHandle) { std::lock_guard lock(primitiveTouchMutex); - primitiveTouches.clear(); + if (!gestureInitialized) return static_cast(0x80890003); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + findGestureContext(gestureHandle)->touches.clear(); return 0; } -int APS5_VABI sceSystemGestureResetTouchRecognizer(int32_t gesture_handle, SystemGestureTouchRecognizer* recognizer) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureResetTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; if (!recognizer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; return 0; } -int APS5_VABI sceSystemGestureUpdateAllTouchRecognizer(int32_t gesture_handle) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; - return 0; -} - -int APS5_VABI sceSystemGestureUpdatePrimitiveTouchRecognizer(int32_t gesture_handle, const void* param) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; - if (!param) return 0; - const auto* input = static_cast(param); - if (!input->pad_data_buffer || input->report_number <= 0) return 0; +int APS5_VABI sceSystemGestureUpdateAllTouchRecognizer(int32_t gestureHandle) { std::lock_guard lock(primitiveTouchMutex); - for (std::int32_t i = 0; i < input->report_number; ++i) sampleReport(input->pad_data_buffer[i]); + if (!gestureInitialized) return static_cast(0x80890003); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; return 0; } -int APS5_VABI sceSystemGestureUpdateTouchRecognizer(int32_t gesture_handle, SystemGestureTouchRecognizer* recognizer) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureUpdatePrimitiveTouchRecognizer(int32_t gestureHandle, const SystemGestureTouchPadData* input) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); + if (!input || !input->pad_data_buffer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; + auto* context = findGestureContext(gestureHandle); + if (!context) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; + if (input->report_number < 0 || input->report_number > 1) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; + if (input->report_number == 1) sampleReport(context->touches, *input->pad_data_buffer); + return 0; +} + +int APS5_VABI sceSystemGestureUpdateTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; if (!recognizer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; return 0; } -int APS5_VABI sceSystemGestureUpdateTouchRecognizerRectangle(int32_t gesture_handle, SystemGestureTouchRecognizer* recognizer, const SystemGestureRectangle* rectangle) { - if (gesture_handle != GESTURE_HANDLE) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; +int APS5_VABI sceSystemGestureUpdateTouchRecognizerRectangle(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer, const SystemGestureRectangle* rectangle) { + std::lock_guard lock(primitiveTouchMutex); + if (!gestureInitialized) return static_cast(0x80890003); + if (!findGestureContext(gestureHandle)) return SCE_SYSTEM_GESTURE_ERROR_INVALID_HANDLE; if (!recognizer) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; if (!rectangle) return SCE_SYSTEM_GESTURE_ERROR_INVALID_ARGUMENT; RecognizerState state{}; diff --git a/core/libs/prx/libSceSystemGesture/include/Api.hpp b/core/libs/prx/libSceSystemGesture/include/Api.hpp new file mode 100644 index 0000000000..ef1667c91e --- /dev/null +++ b/core/libs/prx/libSceSystemGesture/include/Api.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSystemGestureAppendTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer); +int APS5_VABI sceSystemGestureClose(int32_t gestureHandle); +int APS5_VABI sceSystemGestureCreateTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer, int32_t type, const SystemGestureRectangle* rectangle, const void* param); +int APS5_VABI sceSystemGestureFinalizePrimitiveTouchRecognizer(void); +int APS5_VABI sceSystemGestureGetPrimitiveTouchEventByIndex(int32_t gestureHandle, uint32_t index, SystemGesturePrimitiveTouchEvent* event); +int APS5_VABI sceSystemGestureGetPrimitiveTouchEventByPrimitiveID(int32_t gestureHandle, uint16_t primitiveId, SystemGesturePrimitiveTouchEvent* event); +int APS5_VABI sceSystemGestureGetPrimitiveTouchEvents(int32_t gestureHandle, SystemGesturePrimitiveTouchEvent* eventBuffer, uint32_t capacityOfBuffer, uint32_t* numberOfEvent); +int APS5_VABI sceSystemGestureGetPrimitiveTouchEventsCount(int32_t gestureHandle); +int APS5_VABI sceSystemGestureGetTouchEventByEventID(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer, uint32_t eventId, SystemGestureTouchEvent* event); +int APS5_VABI sceSystemGestureGetTouchEventByIndex(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer, uint32_t index, SystemGestureTouchEvent* event); +int APS5_VABI sceSystemGestureGetTouchEvents(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer, SystemGestureTouchEvent* eventBuffer, uint32_t capacityOfBuffer, uint32_t* numberOfEvent); +int APS5_VABI sceSystemGestureGetTouchEventsCount(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer); +int APS5_VABI sceSystemGestureGetTouchRecognizerInformation(int32_t gestureHandle, const SystemGestureTouchRecognizer* recognizer, SystemGestureTouchRecognizerInformation* information); +int APS5_VABI sceSystemGestureInitializePrimitiveTouchRecognizer(); +int32_t APS5_VABI sceSystemGestureOpen(int32_t inputType, const void* param); +int APS5_VABI sceSystemGestureRemoveTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer); +int APS5_VABI sceSystemGestureResetPrimitiveTouchRecognizer(int32_t gestureHandle); +int APS5_VABI sceSystemGestureResetTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer); +int APS5_VABI sceSystemGestureUpdateAllTouchRecognizer(int32_t gestureHandle); +int APS5_VABI sceSystemGestureUpdatePrimitiveTouchRecognizer(int32_t gestureHandle, const SystemGestureTouchPadData* input); +int APS5_VABI sceSystemGestureUpdateTouchRecognizer(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer); +int APS5_VABI sceSystemGestureUpdateTouchRecognizerRectangle(int32_t gestureHandle, SystemGestureTouchRecognizer* recognizer, const SystemGestureRectangle* rectangle); +int APS5_VABI sceSystemGestureDebugGetVersion(); + +} diff --git a/core/libs/prx/libSceSystemGesture/libSceSystemGesture.hpp b/core/libs/prx/libSceSystemGesture/libSceSystemGesture.hpp new file mode 100644 index 0000000000..1e6ac94f6a --- /dev/null +++ b/core/libs/prx/libSceSystemGesture/libSceSystemGesture.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSystemGesture/include/Api.hpp" diff --git a/core/libs/prx/libSceSystemLogger/Export.cpp b/core/libs/prx/libSceSystemLogger/Export.cpp index 0b82cdf1e0..ebad08c6b3 100644 --- a/core/libs/prx/libSceSystemLogger/Export.cpp +++ b/core/libs/prx/libSceSystemLogger/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSystemLogger/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceSystemLogger/include/Api.hpp b/core/libs/prx/libSceSystemLogger/include/Api.hpp new file mode 100644 index 0000000000..d5e47a68e5 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger/include/Api.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSystemLoggerInitialize(); +int APS5_VABI sceSystemLoggerSetLogData(); +int APS5_VABI sceSystemLoggerTerminate(); + +} diff --git a/core/libs/prx/libSceSystemLogger/libSceSystemLogger.hpp b/core/libs/prx/libSceSystemLogger/libSceSystemLogger.hpp new file mode 100644 index 0000000000..e47ab854c6 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger/libSceSystemLogger.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSystemLogger/include/Api.hpp" diff --git a/core/libs/prx/libSceSystemLogger2/Export.cpp b/core/libs/prx/libSceSystemLogger2/Export.cpp index d81f4a5e32..860ef63046 100644 --- a/core/libs/prx/libSceSystemLogger2/Export.cpp +++ b/core/libs/prx/libSceSystemLogger2/Export.cpp @@ -1,10 +1,34 @@ -#include +#include "prx/libSceSystemLogger2/include/Api.hpp" #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceSystemLogger2GetTimestamp() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSystemLogger2Initialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSystemLogger2SetEventAsync() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSystemLogger2SetEventAsyncEx() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSystemLogger2Terminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceSystemLogger2/include/Api.hpp b/core/libs/prx/libSceSystemLogger2/include/Api.hpp new file mode 100644 index 0000000000..4b8e19eaf0 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSystemLogger2GetTimestamp(); +int APS5_VABI sceSystemLogger2Initialize(); +int APS5_VABI sceSystemLogger2SetEventAsync(); +int APS5_VABI sceSystemLogger2SetEventAsyncEx(); +int APS5_VABI sceSystemLogger2Terminate(); + +} diff --git a/core/libs/prx/libSceSystemLogger2/libSceSystemLogger2.hpp b/core/libs/prx/libSceSystemLogger2/libSceSystemLogger2.hpp new file mode 100644 index 0000000000..931a124f24 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2/libSceSystemLogger2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSystemLogger2/include/Api.hpp" diff --git a/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp b/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp index a071ef1bed..c01af333e1 100644 --- a/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp +++ b/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSystemLogger2Delivery/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceSystemLogger2Delivery/include/Api.hpp b/core/libs/prx/libSceSystemLogger2Delivery/include/Api.hpp new file mode 100644 index 0000000000..c0263701f7 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2Delivery/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSystemLogger2DeliveryGetBatchResult(); +int APS5_VABI sceSystemLogger2DeliveryGetResult(); +int APS5_VABI sceSystemLogger2DeliverySetBatchDeliveryEvent(); +int APS5_VABI sceSystemLogger2DeliveryTerminate(); + +} diff --git a/core/libs/prx/libSceSystemLogger2Delivery/libSceSystemLogger2Delivery.hpp b/core/libs/prx/libSceSystemLogger2Delivery/libSceSystemLogger2Delivery.hpp new file mode 100644 index 0000000000..e91f676c51 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2Delivery/libSceSystemLogger2Delivery.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSystemLogger2Delivery/include/Api.hpp" diff --git a/core/libs/prx/libSceSystemLogger2Game/libSceSystemLogger2Game.hpp b/core/libs/prx/libSceSystemLogger2Game/libSceSystemLogger2Game.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2Game/libSceSystemLogger2Game.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp b/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp index 1087fa235d..ae54608dbe 100644 --- a/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp +++ b/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSystemLogger2NativeQueueClient/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceSystemLogger2NativeQueueClient/include/Api.hpp b/core/libs/prx/libSceSystemLogger2NativeQueueClient/include/Api.hpp new file mode 100644 index 0000000000..3631a92feb --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2NativeQueueClient/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSystemLogger2NativeQueueClientCheckAndMaskData(); +int APS5_VABI sceSystemLogger2NativeQueueClientGetEvent(); +int APS5_VABI sceSystemLogger2NativeQueueClientSetDeliveryEnable(); +int APS5_VABI sceSystemLogger2NativeQueueClientSetDeliveryResult(); +int APS5_VABI sceSystemLogger2NativeQueueClientSetNativeQueueConfig(); + +} diff --git a/core/libs/prx/libSceSystemLogger2NativeQueueClient/libSceSystemLogger2NativeQueueClient.hpp b/core/libs/prx/libSceSystemLogger2NativeQueueClient/libSceSystemLogger2NativeQueueClient.hpp new file mode 100644 index 0000000000..50e859ed9d --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2NativeQueueClient/libSceSystemLogger2NativeQueueClient.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSystemLogger2NativeQueueClient/include/Api.hpp" diff --git a/core/libs/prx/libSceSystemService/CMakeLists.txt b/core/libs/prx/libSceSystemService/CMakeLists.txt index 3542172d1b..02c5161b32 100644 --- a/core/libs/prx/libSceSystemService/CMakeLists.txt +++ b/core/libs/prx/libSceSystemService/CMakeLists.txt @@ -22,10 +22,14 @@ if(BUILD_TESTING) configure_windows_unwind(guest_loadexec_host) find_package(Python3 COMPONENTS Interpreter QUIET) if(Python3_FOUND) + set(loadexecLibraries $ $ $ $) + if(APS5_ENABLE_TRACY) + list(APPEND loadexecLibraries $) + endif() add_test(NAME loadexec_arguments COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/core/relinker/relinker/tests/test_loadexec_arguments.py $ $ - $ $ $ $ ${CMAKE_CXX_COMPILER}) + ${loadexecLibraries} ${CMAKE_CXX_COMPILER}) set_tests_properties(loadexec_arguments PROPERTIES TIMEOUT 30) add_asan_test_options(loadexec_arguments verify_asan_link_order=0) endif() @@ -94,6 +98,29 @@ 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_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) + configure_windows_unwind(guest_system_service_app_title_id_tests) + foreach(appTitleIdCase IN ITEMS known:PPSA00000 long:PPSA000000 missing:none) + string(REPLACE ":" ";" appTitleIdCase ${appTitleIdCase}) + list(GET appTitleIdCase 0 appTitleIdName) + list(GET appTitleIdCase 1 appTitleIdValue) + set(appTitleIdRun ${CMAKE_CURRENT_BINARY_DIR}/guest_system_service_app_title_id_${appTitleIdName}) + if(appTitleIdName STREQUAL "missing") + file(MAKE_DIRECTORY ${appTitleIdRun}) + else() + file(WRITE ${appTitleIdRun}/app0/sce_sys/param.json + "{\"titleId\":\"${appTitleIdValue}\",\"localizedParameters\":{\"en-US\":{\"titleName\":\"Example\"}}}") + endif() + add_test(NAME guest_system_service_app_title_id_${appTitleIdName} COMMAND guest_system_service_app_title_id_tests ${appTitleIdName} + WORKING_DIRECTORY ${appTitleIdRun}) + set_tests_properties(guest_system_service_app_title_id_${appTitleIdName} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_system_service_app_title_id_${appTitleIdName} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() add_test_executable(guest_system_kill_app_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemKillApp.cpp) target_include_directories(guest_system_kill_app_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_kill_app_tests PRIVATE libSceSystemService libc) diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index 6c37c6863d..0b9e30b645 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" +#include #include #include #include @@ -25,11 +27,23 @@ #include "LoadExec.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" #include "prx/libSceSystemService/SystemService.hpp" #include "prx/libSceAgcDriver/Execution/include/HostHdr.hpp" namespace { +struct MeasuredParameter { + int id; + int value; +}; + +constexpr MeasuredParameter RegistryParameters[] = { + {SYSTEM_SERVICE_PARAM_ID_100, 0}, {101, 1}, {102, 2}, {103, 100}, {104, 0x20202}, {105, 0}, {106, 0}, {107, 100}, {108, 0}, + {109, 100}, {110, 0x20202}, {111, 0}, {200, 0}, {201, 0}, {202, 0}, {203, 0}, {204, 0}, {205, 0}, {206, 0}, {207, 0}, + {208, 0}, {209, 1}, {210, 71}, {211, 0}, {212, 0}, {213, 0}, {300, 0}, +}; + struct ConsoleTimeZone { int minutes; bool summer; @@ -231,6 +245,21 @@ HostLaunch PrepareLaunch(const char*, const char* const*) { void StartHostExecutable(HostLaunch&) {} #endif +constexpr std::size_t TitleIdBufferSize = 10; + +std::string RunningTitleId() { + const AppTitleId titleId = GetAppTitleId_nid_postfix(); + return std::string(titleId.value); +} + +void CopyRunningTitleId(const std::string& caller, bool running, char* destination) { + if (!running) NotImplemented_nid_no_patch((caller + ": application other than the running title").c_str()); + if (destination == nullptr) NotImplemented_nid_no_patch((caller + ": null title ID buffer").c_str()); + const std::string titleId = RunningTitleId(); + if (titleId.size() >= TitleIdBufferSize) NotImplemented_nid_no_patch((caller + ": title ID that does not fit 10 bytes").c_str()); + std::memcpy(destination, titleId.c_str(), titleId.size() + 1); +} + } extern "C" { @@ -309,9 +338,11 @@ int APS5_VABI sceSystemServiceParamGetInt(int paramId, int* value) { case SYSTEM_SERVICE_PARAM_ID_TIME_FORMAT: *value = SYSTEM_SERVICE_PARAM_TIME_FORMAT_24HOUR; break; case SYSTEM_SERVICE_PARAM_ID_TIME_ZONE: *value = HostTimeZone().minutes; break; case SYSTEM_SERVICE_PARAM_ID_SUMMERTIME: *value = HostTimeZone().summer ? 1 : 0; break; - case SYSTEM_SERVICE_PARAM_ID_GAME_PARENTAL_LEVEL: *value = SYSTEM_SERVICE_PARAM_GAME_PARENTAL_OFF; break; - case SYSTEM_SERVICE_PARAM_ID_ENTER_BUTTON_ASSIGN: *value = SYSTEM_SERVICE_PARAM_ENTER_BUTTON_CROSS; break; - default: *value = 0; break; + default: { + const auto measured = std::ranges::find(RegistryParameters, paramId, &MeasuredParameter::id); + if (measured == std::ranges::end(RegistryParameters)) return SYSTEM_SERVICE_ERROR_PARAMETER; + *value = measured->value; + } } return SYSTEM_SERVICE_OK; } @@ -323,6 +354,7 @@ int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, size_t bufS constexpr char SystemName[] = "PS5"; std::memset(buf, 0, bufSize); std::memcpy(buf, SystemName, sizeof(SystemName)); + buf[SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH - 1] = '\0'; return SYSTEM_SERVICE_OK; } @@ -366,10 +398,13 @@ int APS5_VABI sceSystemServiceLaunchPlayerDialog(const void* param) { return SYSTEM_SERVICE_OK; } -int APS5_VABI sceSystemServiceLaunchWebBrowser(const char* uri, void* param) { - (void)uri; - (void)param; - return SCE_KERNEL_ERROR_EOPNOTSUPP; +int APS5_VABI sceSystemServiceLaunchWebBrowser(const char* uri) { + if (!uri) return static_cast(0x80a10003); + std::size_t length = 0; + while (length < 6016 && uri[length] != 0) ++length; + if (length > 6015) return static_cast(0x80a10007); + if (std::strncmp(uri, "http:", 5) != 0 && std::strncmp(uri, "https:", 6) != 0 && std::strncmp(uri, "psno:", 5) != 0) return static_cast(0x80a10008); + return SCE_KERNEL_ERROR_EOPNOTSUPP; } int APS5_VABI sceSystemServiceLaunchApp(const char* titleId, const char* const* arguments, const void* param) { @@ -388,7 +423,12 @@ int APS5_VABI sceSystemServiceOpenChallengeActivity(void) { return SYSTEM_SERVICE_OK; } -int APS5_VABI sceSystemServiceOpenTournamentOccurrence(void) { +static_assert(sizeof(SystemServiceTournamentOccurrenceParam) == 32); +static_assert(offsetof(SystemServiceTournamentOccurrenceParam, occurrenceId) == 8); +static_assert(offsetof(SystemServiceTournamentOccurrenceParam, userId) == 16); +static_assert(offsetof(SystemServiceTournamentOccurrenceParam, context) == 24); + +int APS5_VABI sceSystemServiceOpenTournamentOccurrence(const SystemServiceTournamentOccurrenceParam* param) { return SYSTEM_SERVICE_OK; } @@ -414,6 +454,26 @@ int APS5_VABI sceSystemServiceGetAppIdOfRunningBigApp(void) { return SYSTEM_SERVICE_RUNNING_APP_ID; } +int APS5_VABI sceSystemServiceGetAppId(const char* titleId) { + if (titleId == nullptr) { + NotImplemented_nid_no_patch("sceSystemServiceGetAppId: null title ID"); + } + if (RunningTitleId() != titleId) { + NotImplemented_nid_no_patch("sceSystemServiceGetAppId: title other than the running title"); + } + return SYSTEM_SERVICE_RUNNING_APP_ID; +} + +int APS5_VABI sceSystemServiceGetAppTitleId(int appId, char* titleId) { + CopyRunningTitleId("sceSystemServiceGetAppTitleId", appId == SYSTEM_SERVICE_RUNNING_APP_ID, titleId); + return SYSTEM_SERVICE_OK; +} + +int APS5_VABI sceLncUtilGetAppTitleId(std::int32_t appId, char* titleId) { + CopyRunningTitleId("sceLncUtilGetAppTitleId", appId == SYSTEM_SERVICE_RUNNING_APP_ID, titleId); + return SYSTEM_SERVICE_OK; +} + int APS5_VABI sceSystemServiceKillApp(int appId, int how, int reason, int coreDump) { if (appId != SYSTEM_SERVICE_RUNNING_APP_ID) { NotImplemented_nid_no_patch("sceSystemServiceKillApp: application other than the running title"); @@ -831,4 +891,12 @@ int APS5_VABI sceCoredumpSequenceHandlerCancelCoredump(void) { return 0; } +int APS5_VABI sceSystemServiceIsBgmPlaying(void) { + return 0; +} + +int APS5_VABI sceShellCoreUtilIsBgmPlaying(void) { + return 0; +} + } diff --git a/core/libs/prx/libSceSystemService/PendingExports.cpp b/core/libs/prx/libSceSystemService/PendingExports.cpp index 9ac7176e02..d2b7fca08e 100644 --- a/core/libs/prx/libSceSystemService/PendingExports.cpp +++ b/core/libs/prx/libSceSystemService/PendingExports.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" #include "prx/libc/include/General.hpp" namespace { @@ -7,44 +8,133 @@ constexpr int SystemServiceRejected = static_cast(0x80A10005); } +static_assert(sizeof(ShellCoreErrorDialogParam) == 24); +static_assert(offsetof(ShellCoreErrorDialogParam, message) == 0); +static_assert(offsetof(ShellCoreErrorDialogParam, formatArgs) == 8); +static_assert(offsetof(ShellCoreErrorDialogParam, flag) == 16); + +struct SystemServiceEvent; + +static_assert(sizeof(SystemServiceStoreParam) == 4); + +static_assert(sizeof(ShellCoreLaunchByUriParam) == 8); +static_assert(offsetof(ShellCoreLaunchByUriParam, option) == 0); +static_assert(offsetof(ShellCoreLaunchByUriParam, userId) == 4); + +static_assert(sizeof(ShellCoreUmmStatus) == 8); +static_assert(offsetof(ShellCoreUmmStatus, state) == 0); +static_assert(offsetof(ShellCoreUmmStatus, inDeviceMuteState) == 4); + +static_assert(sizeof(LncGpuCrashAppStatus) == 1320); +static_assert(offsetof(LncGpuCrashAppStatus, titleId) == 0); +static_assert(offsetof(LncGpuCrashAppStatus, appId) == 16); +static_assert(offsetof(LncGpuCrashAppStatus, appLocalPid) == 20); +static_assert(offsetof(LncGpuCrashAppStatus, path) == 24); +static_assert(offsetof(LncGpuCrashAppStatus, appType) == 1048); +static_assert(offsetof(LncGpuCrashAppStatus, coredumpReason) == 1052); +static_assert(offsetof(LncGpuCrashAppStatus, corefileName) == 1056); +static_assert(offsetof(LncGpuCrashAppStatus, exceptionCode) == 1312); +static_assert(offsetof(LncGpuCrashAppStatus, appCategoryType) == 1316); + +static_assert(sizeof(LncLaunchParam) == 40); +static_assert(offsetof(LncLaunchParam, size) == 0); +static_assert(offsetof(LncLaunchParam, userId) == 4); +static_assert(offsetof(LncLaunchParam, appAttr) == 8); +static_assert(offsetof(LncLaunchParam, enableCrashReport) == 12); +static_assert(offsetof(LncLaunchParam, checkFlag) == 16); +static_assert(offsetof(LncLaunchParam, contextId) == 24); +static_assert(offsetof(LncLaunchParam, isSpeculativeLaunch) == 32); + +static_assert(sizeof(LncLocalProcessStatus) == 36); +static_assert(offsetof(LncLocalProcessStatus, appLocalPid) == 0); +static_assert(offsetof(LncLocalProcessStatus, reserved) == 4); + +static_assert(sizeof(LncCoredumpState) == 12); +static_assert(offsetof(LncCoredumpState, progress) == 0); +static_assert(offsetof(LncCoredumpState, isFinished) == 4); +static_assert(offsetof(LncCoredumpState, result) == 8); + +static_assert(sizeof(LncVideoFinalizeState) == 8); +static_assert(offsetof(LncVideoFinalizeState, progress) == 0); +static_assert(offsetof(LncVideoFinalizeState, result) == 4); + +static_assert(sizeof(LncAppStatus) == 64); +static_assert(offsetof(LncAppStatus, appId) == 0); +static_assert(offsetof(LncAppStatus, launchRequestAppId) == 4); +static_assert(offsetof(LncAppStatus, appType) == 8); +static_assert(offsetof(LncAppStatus, appAttr) == 12); +static_assert(offsetof(LncAppStatus, titleId) == 16); +static_assert(offsetof(LncAppStatus, appCategoryType) == 32); +static_assert(offsetof(LncAppStatus, appBootMode) == 36); +static_assert(offsetof(LncAppStatus, appBootSubMode) == 40); +static_assert(offsetof(LncAppStatus, appVer) == 44); +static_assert(offsetof(LncAppStatus, contentVersion) == 50); +static_assert(offsetof(LncAppStatus, isSystemExApp) == 61); +static_assert(offsetof(LncAppStatus, isCdlgInitialized) == 62); + +static_assert(sizeof(LncShellUiAppStatus) == 1336); +static_assert(offsetof(LncShellUiAppStatus, appId) == 0); +static_assert(offsetof(LncShellUiAppStatus, appType) == 4); +static_assert(offsetof(LncShellUiAppStatus, appAttr) == 8); +static_assert(offsetof(LncShellUiAppStatus, launchRequestAppId) == 12); +static_assert(offsetof(LncShellUiAppStatus, userId) == 16); +static_assert(offsetof(LncShellUiAppStatus, isActiveCdlg) == 20); +static_assert(offsetof(LncShellUiAppStatus, path) == 24); +static_assert(offsetof(LncShellUiAppStatus, sceSysDir) == 1048); +static_assert(offsetof(LncShellUiAppStatus, isCoredumped) == 1304); +static_assert(offsetof(LncShellUiAppStatus, isPrxModuleLoadFailed) == 1308); +static_assert(offsetof(LncShellUiAppStatus, appLocalPid) == 1312); +static_assert(offsetof(LncShellUiAppStatus, crashReportMode) == 1316); +static_assert(offsetof(LncShellUiAppStatus, category) == 1320); +static_assert(offsetof(LncShellUiAppStatus, appCategoryType) == 1324); +static_assert(offsetof(LncShellUiAppStatus, appBootMode) == 1328); +static_assert(offsetof(LncShellUiAppStatus, appBootSubMode) == 1332); + +static_assert(sizeof(AppMessagingMessage) == 8216); +static_assert(offsetof(AppMessagingMessage, sender) == 0); +static_assert(offsetof(AppMessagingMessage, messageType) == 4); +static_assert(offsetof(AppMessagingMessage, payload) == 8); +static_assert(offsetof(AppMessagingMessage, payloadSize) == 8200); +static_assert(offsetof(AppMessagingMessage, timestamp) == 8208); + extern "C" { -int APS5_VABI sceAppMessagingClearEventFlag() { +int APS5_VABI sceAppMessagingClearEventFlag(std::int32_t appId, std::uint64_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppMessagingReceiveMsg() { +int APS5_VABI sceAppMessagingReceiveMsg(AppMessagingMessage* message) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppMessagingSendMsg() { +int APS5_VABI sceAppMessagingSendMsg(std::uint32_t appId, std::uint32_t messageId, const void* data, std::size_t size, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppMessagingSendMsgToShellCore() { +int APS5_VABI sceAppMessagingSendMsgToShellCore(std::uint32_t messageId, const void* data, std::size_t size, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppMessagingSendMsgToShellUI() { +int APS5_VABI sceAppMessagingSendMsgToShellUI(std::uint32_t messageId, const void* data, std::size_t size, std::uint32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppMessagingSetEventFlag() { +int APS5_VABI sceAppMessagingSetEventFlag(std::int32_t appId, std::uint64_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppMessagingTryGetEventFlag() { +int APS5_VABI sceAppMessagingTryGetEventFlag(std::uint64_t mask, std::uint64_t* flags, std::uint8_t clear) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppMessagingTryReceiveMsg() { +int APS5_VABI sceAppMessagingTryReceiveMsg(AppMessagingMessage* message) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -69,22 +159,22 @@ int APS5_VABI sceLncUtilActivateCdlg() { return 0; } -int APS5_VABI sceLncUtilAddLocalProcess() { +int APS5_VABI sceLncUtilAddLocalProcess(std::int32_t appId, const char* path, const char* const* argv, const LncLocalProcessParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilBlockAppSuspend() { +int APS5_VABI sceLncUtilBlockAppSuspend(const char* reason) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilBlockingGetEventForDaemon() { +int APS5_VABI sceLncUtilBlockingGetEventForDaemon(LncDaemonEvent* event, std::uint64_t receiveArgument) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilContinueApp() { +int APS5_VABI sceLncUtilContinueApp(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -119,37 +209,37 @@ int APS5_VABI sceLncUtilFinishSpecialResume() { return 0; } -int APS5_VABI sceLncUtilForceKillApp() { +int APS5_VABI sceLncUtilForceKillApp(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilForceKillLocalProcess() { +int APS5_VABI sceLncUtilForceKillLocalProcess(std::int32_t appId, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppCategory() { +int APS5_VABI sceLncUtilGetAppCategory(std::int32_t appId, char* category) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppCategoryType() { +int APS5_VABI sceLncUtilGetAppCategoryType(std::int32_t appId, std::uint32_t* categoryType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppFocusedAppStatus() { +int APS5_VABI sceLncUtilGetAppFocusedAppStatus(LncAppStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppId() { +int APS5_VABI sceLncUtilGetAppId(const char* titleId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppIdListOfBigApp() { +int APS5_VABI sceLncUtilGetAppIdListOfBigApp(std::int32_t* appIds, std::uint32_t capacity, std::uint32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -159,72 +249,67 @@ int APS5_VABI sceLncUtilGetAppIdOfRunningBigApp() { return 0; } -int APS5_VABI sceLncUtilGetAppLaunchedUser() { +int APS5_VABI sceLncUtilGetAppLaunchedUser(std::int32_t appId, std::int32_t* userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppStatus() { +int APS5_VABI sceLncUtilGetAppStatus(LncAppStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppStatusList() { +int APS5_VABI sceLncUtilGetAppStatusList(LncAppStatus* statuses, std::uint32_t capacity, std::uint32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppStatusListForShellUIReboot() { +int APS5_VABI sceLncUtilGetAppStatusListForShellUIReboot(LncShellUiAppStatus* statuses, std::uint32_t capacity, std::uint32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppTitleId() { +int APS5_VABI sceLncUtilGetAppType(std::int32_t appId, std::uint32_t* type) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetAppType() { +int APS5_VABI sceLncUtilGetCdlgExec(std::int32_t appId, bool* executing) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetCdlgExec() { +int APS5_VABI sceLncUtilGetCdlgType(std::int32_t appId, std::uint32_t* type) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetCdlgType() { +int APS5_VABI sceLncUtilGetCoredumpState(std::int32_t appId, std::int32_t processId, LncCoredumpState* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetCoredumpState() { +int APS5_VABI sceLncUtilGetDbgExecutablePath(char* path, std::uint64_t selector) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetDbgExecutablePath() { +int APS5_VABI sceLncUtilGetEventForDaemon(LncDaemonEvent* event) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetEventForDaemon() { +int APS5_VABI sceLncUtilGetEventForShellUI(LncShellUiEvent* event) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetEventForShellUI() { +int APS5_VABI sceLncUtilGetGpuCrashFullDumpAppStatus(LncGpuCrashAppStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetGpuCrashFullDumpAppStatus() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceLncUtilGetLocalProcessStatusList() { +int APS5_VABI sceLncUtilGetLocalProcessStatusList(LncLocalProcessStatus* statuses, std::uint32_t capacity, std::uint32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -234,17 +319,17 @@ int APS5_VABI sceLncUtilGetParentSocket() { return 0; } -int APS5_VABI sceLncUtilGetResultKillApp() { +int APS5_VABI sceLncUtilGetResultKillApp(std::uint32_t requestId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetResultLaunchAppByTitleId() { +int APS5_VABI sceLncUtilGetResultLaunchAppByTitleId(std::uint32_t requestId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilGetVideoFinalizeState() { +int APS5_VABI sceLncUtilGetVideoFinalizeState(std::int32_t appId, std::int32_t processId, LncVideoFinalizeState* state) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -254,97 +339,97 @@ int APS5_VABI sceLncUtilInitialize() { return 0; } -int APS5_VABI sceLncUtilIsActiveCdlg() { +int APS5_VABI sceLncUtilIsActiveCdlg(bool* active) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilIsAppLaunched() { +int APS5_VABI sceLncUtilIsAppLaunched(const char* titleId, bool* launched) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilIsAppSuspended() { +int APS5_VABI sceLncUtilIsAppSuspended(std::int32_t appId, bool* suspended) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilIsCdlgActivated() { +int APS5_VABI sceLncUtilIsCdlgActivated(std::uint8_t* activated) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilIsCpuBudgetOfExtraAudioDevicesAvailable() { +bool APS5_VABI sceLncUtilIsCpuBudgetOfExtraAudioDevicesAvailable() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilIsHostBoot() { +int APS5_VABI sceLncUtilIsHostBoot(std::int32_t appId, bool* hostBoot) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilIsPs2Emu() { +int APS5_VABI sceLncUtilIsPs2Emu(bool* emulated) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilIsShellUiFgAndGameBgCpuMode() { +bool APS5_VABI sceLncUtilIsShellUiFgAndGameBgCpuMode() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilIsSpeculativeLaunch() { +int APS5_VABI sceLncUtilIsSpeculativeLaunch(std::int32_t appId, bool* speculative) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilKickCoredumpOnlyProcMem() { +int APS5_VABI sceLncUtilKickCoredumpOnlyProcMem(std::int32_t appId, std::int32_t processId, const char* first, const char* second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilKillLocalProcess() { +int APS5_VABI sceLncUtilKillLocalProcess(std::int32_t appId, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilLaunchApp() { +int APS5_VABI sceLncUtilLaunchApp(const char* titleId, const char* const* argv, const LncLaunchParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilLoadExec() { +int APS5_VABI sceLncUtilLoadExec(const char* path, const char* const* argv) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilNotifyCoredumpRequestEnd() { +int APS5_VABI sceLncUtilNotifyCoredumpRequestEnd(std::int32_t appId, std::int32_t processId, std::int32_t result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilNotifyCoredumpRequestProgress() { +int APS5_VABI sceLncUtilNotifyCoredumpRequestProgress(std::int32_t appId, std::int32_t processId, std::int32_t progress) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilNotifyVshReady() { +int APS5_VABI sceLncUtilNotifyVshReady(std::uint32_t errorCode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilRaiseException() { +int APS5_VABI sceLncUtilRaiseException(std::int32_t appId, std::uint64_t exception) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilRaiseExceptionLocalProcess() { +int APS5_VABI sceLncUtilRaiseExceptionLocalProcess(std::int32_t appId, std::int32_t processId, std::uint64_t exception) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilRegisterCdlgSharedMemoryName() { +int APS5_VABI sceLncUtilRegisterCdlgSharedMemoryName(const char* name) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -374,57 +459,57 @@ int APS5_VABI sceLncUtilReleaseCpuBudgetOfInGameStore() { return 0; } -int APS5_VABI sceLncUtilResumeApp() { +int APS5_VABI sceLncUtilResumeApp(std::int32_t appId, std::uint64_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilResumeLocalProcess() { +int APS5_VABI sceLncUtilResumeLocalProcess(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilSetAppFocus() { +int APS5_VABI sceLncUtilSetAppFocus(std::int32_t appId, std::uint64_t flags, std::uint64_t contextId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilSetCdlgExec() { +int APS5_VABI sceLncUtilSetCdlgExec(std::int32_t appId, std::uint8_t executing) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilSetControllerFocus() { +int APS5_VABI sceLncUtilSetControllerFocus(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilSetControllerFocusPermission() { +int APS5_VABI sceLncUtilSetControllerFocusPermission(std::uint8_t permission) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilStartKillApp() { +int APS5_VABI sceLncUtilStartKillApp(std::int32_t appId, std::uint32_t* requestId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilStartLaunchAppByTitleId() { +int APS5_VABI sceLncUtilStartLaunchAppByTitleId(const char* titleId, const char* const* argv, const LncLaunchParam* param, std::uint32_t* requestId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilSuspendApp() { +int APS5_VABI sceLncUtilSuspendApp(std::int32_t appId, std::uint64_t flags, std::int32_t reason) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilSuspendBackgroundApp() { +int APS5_VABI sceLncUtilSuspendBackgroundApp(std::int32_t first, std::int32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilSuspendLocalProcess() { +int APS5_VABI sceLncUtilSuspendLocalProcess(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -434,17 +519,17 @@ int APS5_VABI sceLncUtilTerminate() { return 0; } -int APS5_VABI sceLncUtilTryBlockAppSuspend() { +int APS5_VABI sceLncUtilTryBlockAppSuspend(const char* reason) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilUnblockAppSuspend() { +int APS5_VABI sceLncUtilUnblockAppSuspend(const char* reason) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceLncUtilUnregisterCdlgSharedMemoryName() { +int APS5_VABI sceLncUtilUnregisterCdlgSharedMemoryName(const char* name) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -484,21 +569,21 @@ int APS5_VABI sceShellCoreUtilAcquireSharePlayCpuBudget() { return 0; } -int APS5_VABI sceShellCoreUtilActivateAbort() { +int APS5_VABI sceShellCoreUtilActivateAbort(std::intptr_t handle) { return 0; } -int APS5_VABI sceShellCoreUtilActivateGetStatus() { +int APS5_VABI sceShellCoreUtilActivateGetStatus(std::intptr_t handle, std::int32_t* status, std::int32_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilActivateInit() { +int APS5_VABI sceShellCoreUtilActivateInit(std::int32_t type, std::intptr_t* handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilActivateIsActivated() { +int APS5_VABI sceShellCoreUtilActivateIsActivated(std::intptr_t handle, bool* activated) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -507,19 +592,19 @@ int APS5_VABI sceShellCoreUtilActivateRecordActivation() { return 0; } -int APS5_VABI sceShellCoreUtilActivateStart() { +int APS5_VABI sceShellCoreUtilActivateStart(std::intptr_t handle) { return 0; } -int APS5_VABI sceShellCoreUtilActivateStartAsync() { +int APS5_VABI sceShellCoreUtilActivateStartAsync(std::intptr_t handle) { return 0; } -int APS5_VABI sceShellCoreUtilActivateTerm() { +int APS5_VABI sceShellCoreUtilActivateTerm(std::intptr_t handle) { return 0; } -int APS5_VABI sceShellCoreUtilChangeRunLevel() { +int APS5_VABI sceShellCoreUtilChangeRunLevel(std::int32_t runLevel) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -574,7 +659,7 @@ int APS5_VABI sceShellCoreUtilDeleteDiscInstalledTitleWorkaroundFile() { return 0; } -int APS5_VABI sceShellCoreUtilDeleteDownloadedHidConfigFile() { +int APS5_VABI sceShellCoreUtilDeleteDownloadedHidConfigFile(std::int32_t configIndex) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -589,12 +674,12 @@ int APS5_VABI sceShellCoreUtilDeleteDownloadedTitleWorkaroundFile() { return 0; } -int APS5_VABI sceShellCoreUtilDoFsck() { +int APS5_VABI sceShellCoreUtilDoFsck(const char* path) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilDownloadHidConfigFileFromServer() { +int APS5_VABI sceShellCoreUtilDownloadHidConfigFileFromServer(std::int32_t configIndex) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -624,7 +709,7 @@ int APS5_VABI sceShellCoreUtilExecuteCrashReport() { return 0; } -int APS5_VABI sceShellCoreUtilExfatFormatExternalHdd() { +int APS5_VABI sceShellCoreUtilExfatFormatExternalHdd(const char* path) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -639,12 +724,12 @@ int APS5_VABI sceShellCoreUtilExitMiniApp() { return 0; } -int APS5_VABI sceShellCoreUtilExitMiniAppWithValue() { +int APS5_VABI sceShellCoreUtilExitMiniAppWithValue(const char* value) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilFormatExternalHdd() { +int APS5_VABI sceShellCoreUtilFormatExternalHdd(const char* path) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -654,42 +739,42 @@ int APS5_VABI sceShellCoreUtilFormatHddForRestore() { return 0; } -int APS5_VABI sceShellCoreUtilGetAppData() { +int APS5_VABI sceShellCoreUtilGetAppData(const char* key, void* data, std::int32_t capacity, std::int32_t* size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetAppEnableTTS() { +int APS5_VABI sceShellCoreUtilGetAppEnableTTS(std::int32_t appId, bool* enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetAppEnterButtonAssign() { +int APS5_VABI sceShellCoreUtilGetAppEnterButtonAssign(std::int32_t appId, std::int32_t* assignment) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetAppLaunchTypeInfo() { +int APS5_VABI sceShellCoreUtilGetAppLaunchTypeInfo(std::int32_t appId, ShellCoreAppLaunchTypeInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetAppLaunchedParamInt() { +int APS5_VABI sceShellCoreUtilGetAppLaunchedParamInt(std::int32_t appId, const char* key, std::int32_t* value) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetAppLaunchedParamIntByBudgetType() { +int APS5_VABI sceShellCoreUtilGetAppLaunchedParamIntByBudgetType(std::int32_t budgetType, const char* key, std::int32_t* value) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetAppLaunchedParamString() { +int APS5_VABI sceShellCoreUtilGetAppLaunchedParamString(std::int32_t appId, const char* key, char* value, std::int32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetAppLaunchedParamStringByBudgetType() { +int APS5_VABI sceShellCoreUtilGetAppLaunchedParamStringByBudgetType(std::int32_t budgetType, const char* key, char* value, std::int32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -704,17 +789,17 @@ int APS5_VABI sceShellCoreUtilGetBasicProductShape() { return 0; } -int APS5_VABI sceShellCoreUtilGetCheckerString() { +int APS5_VABI sceShellCoreUtilGetCheckerString(const char* key, char* value, std::size_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetCheckerStringEx() { +int APS5_VABI sceShellCoreUtilGetCheckerStringEx(const char* firstKey, const char* secondKey, char* value, std::size_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetCloudClientStatus() { +int APS5_VABI sceShellCoreUtilGetCloudClientStatus(std::uint32_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -759,42 +844,42 @@ int APS5_VABI sceShellCoreUtilGetCrashReportUploadStatus() { return 0; } -int APS5_VABI sceShellCoreUtilGetDeviceIndexBehavior() { +int APS5_VABI sceShellCoreUtilGetDeviceIndexBehavior(std::uint32_t* behavior, std::int32_t* userId, std::uint32_t* scene) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetDeviceIndexBehaviorWithTimeout() { +int APS5_VABI sceShellCoreUtilGetDeviceIndexBehaviorWithTimeout(std::uint32_t* behavior, std::int32_t* userId, std::uint32_t* scene, std::uint8_t timeout) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetEffectiveTotalSizeOfUserPartition() { +int APS5_VABI sceShellCoreUtilGetEffectiveTotalSizeOfUserPartition(std::uint64_t* size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetFreeSizeOfAvContentsTmp() { +std::uint64_t APS5_VABI sceShellCoreUtilGetFreeSizeOfAvContentsTmp() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetFreeSizeOfUserPartition() { +int APS5_VABI sceShellCoreUtilGetFreeSizeOfUserPartition(std::uint64_t* freeSize, std::uint64_t* totalSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetFsckProgress() { +int APS5_VABI sceShellCoreUtilGetFsckProgress(const char* titleId, std::uint32_t* progress) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetGameLiveStreamingStatus() { +int APS5_VABI sceShellCoreUtilGetGameLiveStreamingStatus(bool* active) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetGnmCompositorOnScreenProfilerFlag() { +bool APS5_VABI sceShellCoreUtilGetGnmCompositorOnScreenProfilerFlag() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -804,37 +889,37 @@ int APS5_VABI sceShellCoreUtilGetGpuLoadEmulationMode() { return 0; } -int APS5_VABI sceShellCoreUtilGetGpuLoadEmulationModeByAppId() { +int APS5_VABI sceShellCoreUtilGetGpuLoadEmulationModeByAppId(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetHidConfigFileInfoString() { +int APS5_VABI sceShellCoreUtilGetHidConfigFileInfoString(char* value, std::size_t capacity, std::int32_t configIndex) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetHidConfigFileString() { +int APS5_VABI sceShellCoreUtilGetHidConfigFileString(char* value, std::size_t capacity, std::int32_t configIndex) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetHidConfigName() { +int APS5_VABI sceShellCoreUtilGetHidConfigName(char* value, std::size_t capacity, std::int32_t configIndex) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetHidConfigNum() { +int APS5_VABI sceShellCoreUtilGetHidConfigNum(std::int32_t* configNum) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetIDUMode() { +int APS5_VABI sceShellCoreUtilGetIDUMode(std::uint8_t* mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetImposeMenuFlagForPs2Emu() { +bool APS5_VABI sceShellCoreUtilGetImposeMenuFlagForPs2Emu(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -844,12 +929,12 @@ int APS5_VABI sceShellCoreUtilGetManifestFileStatus() { return 0; } -int APS5_VABI sceShellCoreUtilGetNeedSizeOfAppContent() { +int APS5_VABI sceShellCoreUtilGetNeedSizeOfAppContent(std::uint64_t* size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetNetEvConfigFileInfoString() { +int APS5_VABI sceShellCoreUtilGetNetEvConfigFileInfoString(char* value, std::size_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -864,52 +949,52 @@ int APS5_VABI sceShellCoreUtilGetPapcGamePcl() { return 0; } -int APS5_VABI sceShellCoreUtilGetPbtcUserInfoList() { +int APS5_VABI sceShellCoreUtilGetPbtcUserInfoList(ShellCorePbtcUserInfoList* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetPlatformPrivacyDefinitionEventData() { +int APS5_VABI sceShellCoreUtilGetPlatformPrivacyDefinitionEventData(const char* firstKey, const char* secondKey, ShellCorePrivacyEventData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetProgressOfFormatExternalHdd() { +float APS5_VABI sceShellCoreUtilGetProgressOfFormatExternalHdd() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetProgressOfFsck() { +float APS5_VABI sceShellCoreUtilGetProgressOfFsck() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetPs4AppCategoryDefaultsListId() { +int APS5_VABI sceShellCoreUtilGetPs4AppCategoryDefaultsListId(void* data, std::size_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetPs4AppCategoryForTitleId() { +int APS5_VABI sceShellCoreUtilGetPs4AppCategoryForTitleId(const char* titleId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetPsStoreIconLayout() { +int APS5_VABI sceShellCoreUtilGetPsStoreIconLayout(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetPsStoreIconState() { +int APS5_VABI sceShellCoreUtilGetPsStoreIconState(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetPsnAccountInfo() { +int APS5_VABI sceShellCoreUtilGetPsnAccountInfo(ShellCorePsnAccountInfoFirst* first, ShellCorePsnAccountInfoSecond* second, ShellCorePsnAccountInfoThird* third, ShellCorePsnAccountInfoFourth* fourth, ShellCorePsnAccountInfoFifth* fifth) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetRemotePlayStatus() { +int APS5_VABI sceShellCoreUtilGetRemotePlayStatus(bool* active) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -919,72 +1004,72 @@ int APS5_VABI sceShellCoreUtilGetRunLevel() { return 0; } -int APS5_VABI sceShellCoreUtilGetSharePlayStatus() { +int APS5_VABI sceShellCoreUtilGetSharePlayStatus(bool* active) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetSocialScreenStatus() { +int APS5_VABI sceShellCoreUtilGetSocialScreenStatus(bool* active) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetSplashScreenState() { +int APS5_VABI sceShellCoreUtilGetSplashScreenState(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetSupportSiteURL() { +int APS5_VABI sceShellCoreUtilGetSupportSiteURL(std::uint32_t first, std::uint32_t second, char* value, std::size_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetSuspendConfirmationDialogFlag() { +bool APS5_VABI sceShellCoreUtilGetSuspendConfirmationDialogFlag(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetSystemBGState() { +int APS5_VABI sceShellCoreUtilGetSystemBGState(std::int32_t appId, std::uint32_t* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetSystemBGWaveColor() { +int APS5_VABI sceShellCoreUtilGetSystemBGWaveColor(std::int32_t appId, std::uint32_t* color) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetSystemBGWaveState() { +int APS5_VABI sceShellCoreUtilGetSystemBGWaveState(std::int32_t appId, std::uint32_t* state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetTelemetryToken() { +int APS5_VABI sceShellCoreUtilGetTelemetryToken(ShellCoreTelemetryToken* token) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetTitleWorkaroundFileInfoString() { +int APS5_VABI sceShellCoreUtilGetTitleWorkaroundFileInfoString(char* value, std::size_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetTitleWorkaroundFileString() { +int APS5_VABI sceShellCoreUtilGetTitleWorkaroundFileString(char* value, std::size_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetUIStatus() { +int APS5_VABI sceShellCoreUtilGetUIStatus(std::uint64_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetUmmStatus() { +int APS5_VABI sceShellCoreUtilGetUmmStatus(std::int32_t userId, ShellCoreUmmStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetUserFocus() { +int APS5_VABI sceShellCoreUtilGetUserFocus(std::int32_t appId, std::int32_t* userId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -994,7 +1079,7 @@ int APS5_VABI sceShellCoreUtilGetUserIdOfMorpheusUser() { return 0; } -int APS5_VABI sceShellCoreUtilGetVMStatsForTelemetry() { +int APS5_VABI sceShellCoreUtilGetVMStatsForTelemetry(char* value, std::size_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1009,7 +1094,7 @@ int APS5_VABI sceShellCoreUtilGoBackToKratosCurrentSessionGame() { return 0; } -int APS5_VABI sceShellCoreUtilHideBlocksFromUser() { +int APS5_VABI sceShellCoreUtilHideBlocksFromUser(std::int64_t bytes, std::int32_t flags) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1019,37 +1104,32 @@ int APS5_VABI sceShellCoreUtilIncrementVersionNumberOfCameraCalibrationData() { return 0; } -int APS5_VABI sceShellCoreUtilIsAccessibilityZoomLocked() { +bool APS5_VABI sceShellCoreUtilIsAccessibilityZoomLocked(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsBgmCpuBudgetAcquired() { +bool APS5_VABI sceShellCoreUtilIsBgmCpuBudgetAcquired() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsBgmCpuBudgetAvailable() { +bool APS5_VABI sceShellCoreUtilIsBgmCpuBudgetAvailable() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsBgmPlaying() { +bool APS5_VABI sceShellCoreUtilIsExternalStorageAppMoveInProgress() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsExternalStorageAppMoveInProgress() { +int APS5_VABI sceShellCoreUtilIsEyeToEyeDistanceAdjusted(std::uint32_t userId, std::uint8_t* adjusted) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsEyeToEyeDistanceAdjusted() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceShellCoreUtilIsGameLiveStreamingOnAir() { +bool APS5_VABI sceShellCoreUtilIsGameLiveStreamingOnAir() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1059,12 +1139,12 @@ int APS5_VABI sceShellCoreUtilIsIDUState() { return 0; } -int APS5_VABI sceShellCoreUtilIsImposeScreenOverlaid() { +bool APS5_VABI sceShellCoreUtilIsImposeScreenOverlaid() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsInSystemSuspendBlackList() { +bool APS5_VABI sceShellCoreUtilIsInSystemSuspendBlackList(std::uint32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1074,7 +1154,7 @@ int APS5_VABI sceShellCoreUtilIsInternalKratosUser() { return 0; } -int APS5_VABI sceShellCoreUtilIsKilledOrSuspendedByLogout() { +int APS5_VABI sceShellCoreUtilIsKilledOrSuspendedByLogout(std::int32_t first, std::int32_t second, std::uint8_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1084,22 +1164,22 @@ int APS5_VABI sceShellCoreUtilIsNeededCrashReport() { return 0; } -int APS5_VABI sceShellCoreUtilIsPowerSaveAlertRequested() { +bool APS5_VABI sceShellCoreUtilIsPowerSaveAlertRequested() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsRemotePlayCpuBudgetAcquired() { +bool APS5_VABI sceShellCoreUtilIsRemotePlayCpuBudgetAcquired() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsScreenSaverOn() { +bool APS5_VABI sceShellCoreUtilIsScreenSaverOn() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsSharePlayCpuBudgetAcquired() { +bool APS5_VABI sceShellCoreUtilIsSharePlayCpuBudgetAcquired() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1114,17 +1194,17 @@ int APS5_VABI sceShellCoreUtilIsTemperatureDanger() { return 0; } -int APS5_VABI sceShellCoreUtilIsTitleWorkaroundEnabled() { +int APS5_VABI sceShellCoreUtilIsTitleWorkaroundEnabled(std::int32_t selector, const char* key) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilIsUsbMassStorageMounted() { +bool APS5_VABI sceShellCoreUtilIsUsbMassStorageMounted(std::uint32_t deviceIndex) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilLaunchByUri() { +int APS5_VABI sceShellCoreUtilLaunchByUri(const char* uri, const ShellCoreLaunchByUriParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1139,12 +1219,12 @@ int APS5_VABI sceShellCoreUtilMakeManifestFile() { return 0; } -int APS5_VABI sceShellCoreUtilMountAppRight() { +int APS5_VABI sceShellCoreUtilMountAppRight(const char* key) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilMountDownloadDataForShellUI() { +int APS5_VABI sceShellCoreUtilMountDownloadDataForShellUI(const char* key) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1159,27 +1239,27 @@ int APS5_VABI sceShellCoreUtilMountHddForRestore() { return 0; } -int APS5_VABI sceShellCoreUtilNavigateToAnotherApp() { +int APS5_VABI sceShellCoreUtilNavigateToAnotherApp(std::uint32_t target) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilNavigateToGoHome() { +int APS5_VABI sceShellCoreUtilNavigateToGoHome(std::uint32_t target) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilNavigateToLaunchedApp() { +int APS5_VABI sceShellCoreUtilNavigateToLaunchedApp(std::uint32_t target) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilNotificationCancelForIDU() { +int APS5_VABI sceShellCoreUtilNotificationCancelForIDU(std::int64_t notificationId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilNotificationRequestedForIDU() { +int APS5_VABI sceShellCoreUtilNotificationRequestedForIDU(std::int64_t notificationId, const char* text, std::uint32_t type) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1189,12 +1269,12 @@ int APS5_VABI sceShellCoreUtilNotifyBgmCoreTermination() { return 0; } -int APS5_VABI sceShellCoreUtilNotifyFsReadError() { +int APS5_VABI sceShellCoreUtilNotifyFsReadError(std::int32_t first, std::int32_t second, std::int32_t third, const char* detail) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilNotifyImeStatusChanged() { +int APS5_VABI sceShellCoreUtilNotifyImeStatusChanged(std::int32_t imeStatus, std::uint8_t isCalledFromGame) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1204,7 +1284,7 @@ int APS5_VABI sceShellCoreUtilNotifyPsnAccountInfoReceived() { return 0; } -int APS5_VABI sceShellCoreUtilNotifySystemSuspendTelemetryInfo() { +int APS5_VABI sceShellCoreUtilNotifySystemSuspendTelemetryInfo(std::uint32_t info) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1214,22 +1294,22 @@ int APS5_VABI sceShellCoreUtilPfAuthClientConsoleTokenClearCache() { return 0; } -int APS5_VABI sceShellCoreUtilPostActivityForPsNow() { +int APS5_VABI sceShellCoreUtilPostActivityForPsNow(std::int32_t type, const char* activity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilPostErrorLog() { +int APS5_VABI sceShellCoreUtilPostErrorLog(const char* titleId, std::int32_t internalErrorCode, const char* shortError, const char* messageBuffer, std::int32_t suggestionType, const char* logReport) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilPostLaunchConfirmResult() { +int APS5_VABI sceShellCoreUtilPostLaunchConfirmResult(std::uint32_t first, std::uint32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilPostPsmEventToShellUI() { +int APS5_VABI sceShellCoreUtilPostPsmEventToShellUI(std::int32_t eventId, const char* data) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1264,12 +1344,12 @@ int APS5_VABI sceShellCoreUtilReleaseSharePlayCpuBudget() { return 0; } -int APS5_VABI sceShellCoreUtilReportSessionErrorToGaikaiController() { +int APS5_VABI sceShellCoreUtilReportSessionErrorToGaikaiController(std::int32_t error, const char* detail) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilReportUnexpectedFatalErrorToSystemTelemetry() { +int APS5_VABI sceShellCoreUtilReportUnexpectedFatalErrorToSystemTelemetry(std::int32_t error, const char* detail) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1279,17 +1359,17 @@ int APS5_VABI sceShellCoreUtilRequestCameraCalibration() { return 0; } -int APS5_VABI sceShellCoreUtilRequestEjectDevice() { +int APS5_VABI sceShellCoreUtilRequestEjectDevice(const char* path) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilRequestRebootApp() { +int APS5_VABI sceShellCoreUtilRequestRebootApp(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilRequestShutdown() { +int APS5_VABI sceShellCoreUtilRequestShutdown(std::int32_t reason) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1304,132 +1384,132 @@ int APS5_VABI sceShellCoreUtilResetBgdcConfig() { return 0; } -int APS5_VABI sceShellCoreUtilSetAppData() { +int APS5_VABI sceShellCoreUtilSetAppData(const char* key, const char* value) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetBgmProhibition() { +int APS5_VABI sceShellCoreUtilSetBgmProhibition(std::uint32_t value) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetDeviceIndexBehavior() { +int APS5_VABI sceShellCoreUtilSetDeviceIndexBehavior(std::uint32_t behavior, std::int32_t userId, std::uint8_t scene) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetGameLiveStreamingOnAirFlag() { +int APS5_VABI sceShellCoreUtilSetGameLiveStreamingOnAirFlag(std::int32_t enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetGameLiveStreamingStatus() { +int APS5_VABI sceShellCoreUtilSetGameLiveStreamingStatus(std::int32_t active) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetGnmCompositorOnScreenProfilerFlag() { +int APS5_VABI sceShellCoreUtilSetGnmCompositorOnScreenProfilerFlag(std::int32_t enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationMode() { +int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationMode(std::uint32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationModeByAppId() { +int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationModeByAppId(std::int32_t appId, std::uint32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetIDUMode() { +int APS5_VABI sceShellCoreUtilSetIDUMode(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetImposeStatusFlag() { +int APS5_VABI sceShellCoreUtilSetImposeStatusFlag(std::int32_t enabled) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetPsStoreIconLayout() { +int APS5_VABI sceShellCoreUtilSetPsStoreIconLayout(std::uint32_t layout) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetPsStoreIconState() { +int APS5_VABI sceShellCoreUtilSetPsStoreIconState(std::uint32_t state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetRemotePlayStatus() { +int APS5_VABI sceShellCoreUtilSetRemotePlayStatus(std::int32_t active) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetSharePlayStatus() { +int APS5_VABI sceShellCoreUtilSetSharePlayStatus(std::int32_t active) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetSkipUpdateCheck() { +int APS5_VABI sceShellCoreUtilSetSkipUpdateCheck(std::uint8_t skip) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetSocialScreenStatus() { +int APS5_VABI sceShellCoreUtilSetSocialScreenStatus(std::int32_t active) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetSplashScreenState() { +int APS5_VABI sceShellCoreUtilSetSplashScreenState(std::uint32_t state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetSystemBGState() { +int APS5_VABI sceShellCoreUtilSetSystemBGState(std::uint32_t state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetSystemBGWaveColor() { +int APS5_VABI sceShellCoreUtilSetSystemBGWaveColor(std::uint32_t color) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetSystemBGWaveState() { +int APS5_VABI sceShellCoreUtilSetSystemBGWaveState(std::uint32_t state) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetUIStatus() { +int APS5_VABI sceShellCoreUtilSetUIStatus(std::uint64_t status, std::uint64_t mask) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilSetUserFocus() { +int APS5_VABI sceShellCoreUtilSetUserFocus(std::int32_t userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilShowCriticalErrorDialog() { +int APS5_VABI sceShellCoreUtilShowCriticalErrorDialog(std::uint32_t error, const char* message) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilShowErrorDialog() { +int APS5_VABI sceShellCoreUtilShowErrorDialog(std::uint32_t error, const char* message) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilShowErrorDialogWithFormatArgs() { +int APS5_VABI sceShellCoreUtilShowErrorDialogWithFormatArgs(std::uint32_t error, const char* message, const char* const* formatArgs) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilShowErrorDialogWithParam() { +int APS5_VABI sceShellCoreUtilShowErrorDialogWithParam(std::uint32_t error, const ShellCoreErrorDialogParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1444,22 +1524,22 @@ int APS5_VABI sceShellCoreUtilSignalUserInput() { return 0; } -int APS5_VABI sceShellCoreUtilStartPsNowGame() { +int APS5_VABI sceShellCoreUtilStartPsNowGame(const char* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilStopPsNowGame() { +int APS5_VABI sceShellCoreUtilStopPsNowGame(const char* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilTalkKeyEventReceived() { +int APS5_VABI sceShellCoreUtilTalkKeyEventReceived(std::int64_t deviceId, std::int32_t userId, std::int32_t keyEventType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilTestBusTransferSpeed() { +int APS5_VABI sceShellCoreUtilTestBusTransferSpeed(const char* path, std::uint8_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1469,7 +1549,7 @@ int APS5_VABI sceShellCoreUtilTickHeartBeat() { return 0; } -int APS5_VABI sceShellCoreUtilTriggerPapcUpdate() { +int APS5_VABI sceShellCoreUtilTriggerPapcUpdate(std::uint32_t value) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1479,12 +1559,12 @@ int APS5_VABI sceShellCoreUtilTurnOffScreenSaver() { return 0; } -int APS5_VABI sceShellCoreUtilUnmountAppRight() { +int APS5_VABI sceShellCoreUtilUnmountAppRight(const char* key) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilUnmountDownloadDataForShellUI() { +int APS5_VABI sceShellCoreUtilUnmountDownloadDataForShellUI(const char* key) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1504,12 +1584,12 @@ int APS5_VABI sceShellCoreutilGetCrashReportProcessInformation() { return 0; } -int APS5_VABI sceSystemModalDialogClose() { +int APS5_VABI sceSystemModalDialogClose(std::uint32_t dialogId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemModalDialogOpen() { +int APS5_VABI sceSystemModalDialogOpen(const char* message) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1528,22 +1608,22 @@ int APS5_VABI sceSystemServiceActivateHevc() { return 0; } -int APS5_VABI sceSystemServiceActivateHevcAbort() { +int APS5_VABI sceSystemServiceActivateHevcAbort(std::uintptr_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcGetStatus() { +int APS5_VABI sceSystemServiceActivateHevcGetStatus(std::uintptr_t handle, std::uint32_t* status, std::int32_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcInit() { +int APS5_VABI sceSystemServiceActivateHevcInit(std::uintptr_t* handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcIsActivated() { +int APS5_VABI sceSystemServiceActivateHevcIsActivated(std::uint8_t* activated) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1553,92 +1633,92 @@ int APS5_VABI sceSystemServiceActivateHevcSoft() { return 0; } -int APS5_VABI sceSystemServiceActivateHevcSoftAbort() { +int APS5_VABI sceSystemServiceActivateHevcSoftAbort(std::uintptr_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcSoftGetStatus() { +int APS5_VABI sceSystemServiceActivateHevcSoftGetStatus(std::uintptr_t handle, std::uint32_t* status, std::int32_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcSoftInit() { +int APS5_VABI sceSystemServiceActivateHevcSoftInit(std::uintptr_t* handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcSoftIsActivated() { +int APS5_VABI sceSystemServiceActivateHevcSoftIsActivated(std::uint8_t* activated) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcSoftStart() { +int APS5_VABI sceSystemServiceActivateHevcSoftStart(std::uintptr_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcSoftTerm() { +int APS5_VABI sceSystemServiceActivateHevcSoftTerm(std::uintptr_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcStart() { +int APS5_VABI sceSystemServiceActivateHevcStart(std::uintptr_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateHevcTerm() { +int APS5_VABI sceSystemServiceActivateHevcTerm(std::uintptr_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateMpeg2Abort() { +int APS5_VABI sceSystemServiceActivateMpeg2Abort(std::uintptr_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateMpeg2GetStatus() { +int APS5_VABI sceSystemServiceActivateMpeg2GetStatus(std::uintptr_t handle, std::uint32_t* status, std::int32_t* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateMpeg2Init() { +int APS5_VABI sceSystemServiceActivateMpeg2Init(std::uintptr_t* handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateMpeg2IsActivated() { +int APS5_VABI sceSystemServiceActivateMpeg2IsActivated(std::uint8_t* activated) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateMpeg2Start() { +int APS5_VABI sceSystemServiceActivateMpeg2Start(std::uintptr_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceActivateMpeg2Term() { +int APS5_VABI sceSystemServiceActivateMpeg2Term(std::uintptr_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceAddLocalProcess() { +int APS5_VABI sceSystemServiceAddLocalProcess(std::int32_t appId, const char* path, const char* const* argv, const SystemServiceLocalProcessParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceAddLocalProcessForJvm() { +int APS5_VABI sceSystemServiceAddLocalProcessForJvm(const char* path, const char* const* argv, const SystemServiceJvmProcessParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceAddLocalProcessForPs2Emu() { +int APS5_VABI sceSystemServiceAddLocalProcessForPs2Emu(const char* path, const char* const* argv, const SystemServicePs2ProcessParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceAppGetAppInstallStatus() { +int APS5_VABI sceSystemServiceAppGetAppInstallStatus(const char* titleId, SystemServiceAppInstallStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1647,7 +1727,7 @@ int APS5_VABI sceSystemServiceChangeAcpClock() { return 0; } -int APS5_VABI sceSystemServiceChangeClock() { +int APS5_VABI sceSystemServiceChangeClock(const char* clock, std::uint32_t value) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1656,7 +1736,7 @@ int APS5_VABI sceSystemServiceChangeCpuClock() { return 0; } -int APS5_VABI sceSystemServiceChangeCpuPstate() { +int APS5_VABI sceSystemServiceChangeCpuPstate(const SystemServiceCpuPstateParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1665,7 +1745,7 @@ int APS5_VABI sceSystemServiceChangeGpuClock() { return 0; } -int APS5_VABI sceSystemServiceChangeMemPstate() { +int APS5_VABI sceSystemServiceChangeMemPstate(std::uint32_t state) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1745,52 +1825,42 @@ int APS5_VABI sceSystemServiceEnableSuspendNotification() { return 0; } -int APS5_VABI sceSystemServiceGetAppCategoryType() { +int APS5_VABI sceSystemServiceGetAppCategoryType(std::int32_t appId, std::uint32_t* categoryType) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetAppFocusedAppStatus() { +int APS5_VABI sceSystemServiceGetAppFocusedAppStatus(SystemServiceAppStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetAppId() { +int APS5_VABI sceSystemServiceGetAppIdListOfBigApp(std::int32_t* appIds, std::uint32_t capacity, std::uint32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetAppIdListOfBigApp() { +int APS5_VABI sceSystemServiceGetAppLaunchedUser(std::int32_t appId, std::int32_t* userId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetAppLaunchedUser() { +int APS5_VABI sceSystemServiceGetAppStatus(SystemServiceAppStatus* status) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetAppStatus() { +int APS5_VABI sceSystemServiceGetAppType(std::int32_t appId, std::int32_t* type) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetAppTitleId() { +int APS5_VABI sceSystemServiceGetDbgExecutablePath(char* path, std::uint64_t selector) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetAppType() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSystemServiceGetDbgExecutablePath() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSystemServiceGetEventForDaemon() { +int APS5_VABI sceSystemServiceGetEventForDaemon(SystemServiceDaemonEvent* event) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1800,7 +1870,7 @@ int APS5_VABI sceSystemServiceGetGpuLoadEmulationMode() { return 0; } -int APS5_VABI sceSystemServiceGetLocalProcessStatusList() { +int APS5_VABI sceSystemServiceGetLocalProcessStatusList(SystemServiceLocalProcessStatus* statuses, std::uint32_t capacity, std::uint32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1810,7 +1880,7 @@ int APS5_VABI sceSystemServiceGetMainAppTitleId() { return 0; } -int APS5_VABI sceSystemServiceGetPSButtonEvent() { +int APS5_VABI sceSystemServiceGetPSButtonEvent(const SystemServiceEvent* event, SystemServicePSButtonEvent* buttonEvent) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1835,27 +1905,27 @@ int APS5_VABI sceSystemServiceGetParentSocketForPsmKit() { return 0; } -int APS5_VABI sceSystemServiceGetPlatformPrivacyDefinitionData() { +int APS5_VABI sceSystemServiceGetPlatformPrivacyDefinitionData(const char* key, SystemServicePrivacyDefinitionData* data) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetPlatformPrivacyDefinitionVersion() { +int APS5_VABI sceSystemServiceGetPlatformPrivacyDefinitionVersion(const char* key, SystemServicePrivacyDefinitionVersion* version) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetPlatformPrivacySetting() { +int APS5_VABI sceSystemServiceGetPlatformPrivacySetting(std::uint32_t* setting) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetRecentLotusPushedButton() { +int APS5_VABI sceSystemServiceGetRecentLotusPushedButton(std::uint32_t timeout, std::int32_t* deviceId, std::uint32_t* buttons) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetRemoteplayStatus() { +int APS5_VABI sceSystemServiceGetRemoteplayStatus(bool* active) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1875,12 +1945,12 @@ int APS5_VABI sceSystemServiceGetSubAppTitleIdForSP() { return 0; } -int APS5_VABI sceSystemServiceGetTitleWorkaroundInfo() { +int APS5_VABI sceSystemServiceGetTitleWorkaroundInfo(SystemServiceTitleWorkaroundInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceGetUIStatus() { +int APS5_VABI sceSystemServiceGetUIStatus(std::uint64_t* status) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1895,67 +1965,62 @@ int APS5_VABI sceSystemServiceInitializeForShellCore() { return 0; } -int APS5_VABI sceSystemServiceInvokeAppLaunchLink() { +int APS5_VABI sceSystemServiceInvokeAppLaunchLink(const char* uri) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceIsAppSuspended() { +int APS5_VABI sceSystemServiceIsAppSuspended(std::int32_t appId, bool* suspended) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceIsBgmCpuBudgetAvailable() { +bool APS5_VABI sceSystemServiceIsBgmCpuBudgetAvailable() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceIsBgmPlaying() { +int APS5_VABI sceSystemServiceIsEyeToEyeDistanceAdjusted(std::uint32_t userId, std::uint8_t* adjusted) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceIsEyeToEyeDistanceAdjusted() { +bool APS5_VABI sceSystemServiceIsGameLiveStreamingOnAir() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceIsGameLiveStreamingOnAir() { +bool APS5_VABI sceSystemServiceIsScreenSaverOn() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceIsScreenSaverOn() { +bool APS5_VABI sceSystemServiceIsShellUiFgAndGameBgCpuMode() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceIsShellUiFgAndGameBgCpuMode() { +int APS5_VABI sceSystemServiceKillLocalProcess(std::int32_t appId, std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceKillLocalProcess() { +int APS5_VABI sceSystemServiceKillLocalProcessForJvm(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceKillLocalProcessForJvm() { +int APS5_VABI sceSystemServiceKillLocalProcessForPs2Emu(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceKillLocalProcessForPs2Emu() { +int APS5_VABI sceSystemServiceLaunchEventDetails(std::uint64_t unusedSlot, const char* eventId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceLaunchEventDetails() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSystemServiceLaunchStore() { +int APS5_VABI sceSystemServiceLaunchStore(const char* uri, const SystemServiceStoreParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1965,7 +2030,7 @@ int APS5_VABI sceSystemServiceLaunchTournamentList() { return 0; } -int APS5_VABI sceSystemServiceLaunchTournamentsTeamProfile() { +int APS5_VABI sceSystemServiceLaunchTournamentsTeamProfile(std::uint64_t unusedSlot, const char* profile, const char* tournament, const char* context) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1974,7 +2039,7 @@ int APS5_VABI sceSystemServiceLaunchUdsApp() { return SystemServiceRejected; } -int APS5_VABI sceSystemServiceLaunchWebApp() { +int APS5_VABI sceSystemServiceLaunchWebApp(const char* argument) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1983,7 +2048,7 @@ int APS5_VABI sceSystemServiceLoadExecVideoServiceWebApp() { return SystemServiceUnavailable; } -int APS5_VABI sceSystemServiceNavigateToAnotherApp() { +int APS5_VABI sceSystemServiceNavigateToAnotherApp(std::uint32_t target) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1993,12 +2058,12 @@ int APS5_VABI sceSystemServiceNavigateToGoBack() { return 0; } -int APS5_VABI sceSystemServiceNavigateToGoBackWithValue() { +int APS5_VABI sceSystemServiceNavigateToGoBackWithValue(const char* value) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceNavigateToGoHome() { +int APS5_VABI sceSystemServiceNavigateToGoHome(std::uint32_t target) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2008,7 +2073,7 @@ int APS5_VABI sceSystemServiceNotifyBgmCoreTermination() { return 0; } -int APS5_VABI sceSystemServiceRaiseExceptionLocalProcess() { +int APS5_VABI sceSystemServiceRaiseExceptionLocalProcess(std::int32_t appId, std::int32_t processId, std::uint64_t exception) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2054,27 +2119,27 @@ int APS5_VABI sceSystemServiceRequestToChangeRenderingMode() { return SystemServiceRejected; } -int APS5_VABI sceSystemServiceResumeLocalProcess() { +int APS5_VABI sceSystemServiceResumeLocalProcess(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceSaveVideoToken() { +int APS5_VABI sceSystemServiceSaveVideoToken(std::int32_t userId, const char* token) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceSetControllerFocusPermission() { +int APS5_VABI sceSystemServiceSetControllerFocusPermission(std::uint8_t permission) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceSetGpuLoadEmulationMode() { +int APS5_VABI sceSystemServiceSetGpuLoadEmulationMode(std::uint32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceSetOutOfVrPlayAreaFlag() { +int APS5_VABI sceSystemServiceSetOutOfVrPlayAreaFlag(std::uint8_t flag) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2083,7 +2148,7 @@ int APS5_VABI sceSystemServiceSetOutOfVrPlayZoneWarning() { return 0; } -int APS5_VABI sceSystemServiceSetPowerSaveLevel() { +int APS5_VABI sceSystemServiceSetPowerSaveLevel(std::uint32_t level) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2098,7 +2163,7 @@ int APS5_VABI sceSystemServiceShowClosedCaptionSettings() { return 0; } -int APS5_VABI sceSystemServiceShowErrorDialog() { +int APS5_VABI sceSystemServiceShowErrorDialog(std::uint32_t error, const char* message) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2108,27 +2173,27 @@ int APS5_VABI sceSystemServiceShowEyeToEyeDistanceSetting() { return 0; } -int APS5_VABI sceSystemServiceShowImposeMenuForPs2Emu() { +int APS5_VABI sceSystemServiceShowImposeMenuForPs2Emu(std::uint8_t enabled, std::uint64_t reserved) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceSuspendBackgroundApp() { +int APS5_VABI sceSystemServiceSuspendBackgroundApp(std::int32_t first, std::int32_t second) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceSuspendLocalProcess() { +int APS5_VABI sceSystemServiceSuspendLocalProcess(std::int32_t processId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceTelemetrySetData() { +int APS5_VABI sceSystemServiceTelemetrySetData(const char* key, const void* data, std::size_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceTelemetrySetData2() { +int APS5_VABI sceSystemServiceTelemetrySetData2(const char* key, const char* data) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2143,67 +2208,67 @@ int APS5_VABI sceSystemServiceTurnOffScreenSaver() { return 0; } -int APS5_VABI sceSystemServiceUsbStorageGetDeviceInfo() { +int APS5_VABI sceSystemServiceUsbStorageGetDeviceInfo(std::uint32_t device, UsbStorageDeviceInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageGetDeviceList() { +int APS5_VABI sceSystemServiceUsbStorageGetDeviceList(std::uint32_t* devices, std::int32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageGetdentsClose() { +int APS5_VABI sceSystemServiceUsbStorageGetdentsClose(std::int32_t fd) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageGetdentsOpen() { +int APS5_VABI sceSystemServiceUsbStorageGetdentsOpen(std::uint32_t device, std::int32_t* fd, std::uint64_t* bufferSize) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageGetdentsRead() { +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; } -int APS5_VABI sceSystemServiceUsbStorageInit() { +int APS5_VABI sceSystemServiceUsbStorageInit(void* attr) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageIsExist() { +bool APS5_VABI sceSystemServiceUsbStorageIsExist(std::uint32_t device, const char* directory, std::uint8_t* exists) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageRegisterCallback() { +int APS5_VABI sceSystemServiceUsbStorageRegisterCallback(std::int32_t eventType, UsbStorageCallback callback, void* arg) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageRegisterCallbackForMapAvailable() { +int APS5_VABI sceSystemServiceUsbStorageRegisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* arg) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageRegisterCallbackForMapUnavailable() { +int APS5_VABI sceSystemServiceUsbStorageRegisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* arg) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageRequestMap() { +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; } -int APS5_VABI sceSystemServiceUsbStorageRequestMapWSB() { +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) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageRequestUnmap() { +int APS5_VABI sceSystemServiceUsbStorageRequestUnmap(std::uint32_t device, const char* directory) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2213,17 +2278,17 @@ int APS5_VABI sceSystemServiceUsbStorageTerm() { return 0; } -int APS5_VABI sceSystemServiceUsbStorageUnregisterCallback() { +int APS5_VABI sceSystemServiceUsbStorageUnregisterCallback(std::int32_t eventType, UsbStorageCallback callback) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageUnregisterCallbackForMapAvailable() { +int APS5_VABI sceSystemServiceUsbStorageUnregisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemServiceUsbStorageUnregisterCallbackForMapUnavailable() { +int APS5_VABI sceSystemServiceUsbStorageUnregisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2263,7 +2328,7 @@ int APS5_VABI sceSystemStateMgrGetTriggerCode() { return 0; } -int APS5_VABI sceSystemStateMgrIsBdDriveReady() { +bool APS5_VABI sceSystemStateMgrIsBdDriveReady() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2273,12 +2338,12 @@ int APS5_VABI sceSystemStateMgrIsGpuPerformanceNormal() { return 0; } -int APS5_VABI sceSystemStateMgrIsShellUIShutdownInProgress() { +bool APS5_VABI sceSystemStateMgrIsShellUIShutdownInProgress() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceSystemStateMgrIsStandbyModeEnabled() { +bool APS5_VABI sceSystemStateMgrIsStandbyModeEnabled() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2288,7 +2353,7 @@ int APS5_VABI sceSystemStateMgrLeaveMediaPlaybackMode() { return 0; } -int APS5_VABI sceSystemStateMgrNotifySystemSuspendResumeProgress() { +int APS5_VABI sceSystemStateMgrNotifySystemSuspendResumeProgress(const char* progress) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2298,7 +2363,7 @@ int APS5_VABI sceSystemStateMgrReboot() { return 0; } -int APS5_VABI sceSystemStateMgrRequestToKeepMainOnStandby() { +int APS5_VABI sceSystemStateMgrRequestToKeepMainOnStandby(const char* reason) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2308,7 +2373,7 @@ int APS5_VABI sceSystemStateMgrSendCecOneTouchPlayCommand() { return 0; } -int APS5_VABI sceSystemStateMgrStartRebootTimer() { +int APS5_VABI sceSystemStateMgrStartRebootTimer(std::int32_t mode) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2353,7 +2418,7 @@ int APS5_VABI sceSystemStateMgrWaitVshAutoUpdateVerifyDone() { return 0; } -int APS5_VABI sceSystemStateMgrWakeUp() { +int APS5_VABI sceSystemStateMgrWakeUp(std::uint16_t trigger) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceSystemService/SystemService.hpp b/core/libs/prx/libSceSystemService/SystemService.hpp index 7b1664cc90..22f85c1123 100644 --- a/core/libs/prx/libSceSystemService/SystemService.hpp +++ b/core/libs/prx/libSceSystemService/SystemService.hpp @@ -23,6 +23,7 @@ constexpr int SYSTEM_SERVICE_PARAM_ID_TIME_ZONE = 4; constexpr int SYSTEM_SERVICE_PARAM_ID_SUMMERTIME = 5; constexpr int SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME = 6; constexpr int SYSTEM_SERVICE_PARAM_ID_GAME_PARENTAL_LEVEL = 7; +constexpr int SYSTEM_SERVICE_PARAM_ID_100 = 100; constexpr int SYSTEM_SERVICE_PARAM_ID_400 = 400; constexpr int SYSTEM_SERVICE_PARAM_ID_ENTER_BUTTON_ASSIGN = 1000; @@ -34,8 +35,6 @@ constexpr int SYSTEM_SERVICE_PARAM_LANG_ARABIC = 21; constexpr int SYSTEM_SERVICE_PARAM_LANG_UKRAINIAN = 30; constexpr int SYSTEM_SERVICE_PARAM_DATE_FORMAT_DDMMYYYY = 1; constexpr int SYSTEM_SERVICE_PARAM_TIME_FORMAT_24HOUR = 1; -constexpr int SYSTEM_SERVICE_PARAM_GAME_PARENTAL_OFF = 0; -constexpr int SYSTEM_SERVICE_PARAM_ENTER_BUTTON_CROSS = 1; inline int SystemServiceConsoleLanguage(std::string_view caller) { const char* configured = std::getenv("ANYPS5_LANGUAGE"); diff --git a/core/libs/prx/libSceSystemService/include/SystemService.hpp b/core/libs/prx/libSceSystemService/include/SystemService.hpp new file mode 100644 index 0000000000..6c0120e66b --- /dev/null +++ b/core/libs/prx/libSceSystemService/include/SystemService.hpp @@ -0,0 +1,732 @@ +#pragma once + +#include "prx/libSceUsbStorage/UsbStorage.hpp" + +#include "SceTypes.hpp" + +struct SystemServiceTournamentOccurrenceParam { + std::uint64_t size; + const char* occurrenceId; + std::int32_t userId; + std::uint32_t padding; + const char* context; +}; + +struct ShellCoreErrorDialogParam { + const char* message; + const char* const* formatArgs; + std::uint8_t flag; +}; + +struct SystemServiceDaemonEvent; +struct SystemServiceLocalProcessStatus; +struct SystemServicePSButtonEvent; +struct SystemServicePrivacyDefinitionData; +struct SystemServicePrivacyDefinitionVersion; +struct SystemServiceTitleWorkaroundInfo; + +struct SystemServiceStoreParam { + std::uint32_t size; +}; + +struct SystemServiceCpuPstateParam; +struct SystemServiceLocalProcessParam; +struct SystemServiceJvmProcessParam; +struct SystemServicePs2ProcessParam; +struct SystemServiceAppInstallStatus; +struct SystemServiceAppStatus; + +struct ShellCoreLaunchByUriParam { + std::uint32_t option; + std::int32_t userId; +}; + +struct ShellCoreTelemetryToken; + +struct ShellCoreUmmStatus { + std::int32_t state; + std::int32_t inDeviceMuteState; +}; + +struct ShellCorePbtcUserInfoList; +struct ShellCorePrivacyEventData; +struct ShellCorePsnAccountInfoFirst; +struct ShellCorePsnAccountInfoSecond; +struct ShellCorePsnAccountInfoThird; +struct ShellCorePsnAccountInfoFourth; +struct ShellCorePsnAccountInfoFifth; +struct ShellCoreAppLaunchTypeInfo; + +struct LncGpuCrashAppStatus { + char titleId[16]; + std::int32_t appId; + std::int32_t appLocalPid; + char path[1024]; + std::int32_t appType; + std::int32_t coredumpReason; + char corefileName[256]; + std::uint32_t exceptionCode; + std::uint32_t appCategoryType; +}; + +struct LncLaunchParam { + std::uint32_t size; + std::int32_t userId; + std::int32_t appAttr; + std::int32_t enableCrashReport; + std::uint64_t checkFlag; + std::uint64_t contextId; + bool isSpeculativeLaunch; +}; + +struct LncDaemonEvent; +struct LncShellUiEvent; + +struct LncLocalProcessStatus { + std::int32_t appLocalPid; + std::uint8_t reserved[32]; +}; + +struct LncCoredumpState { + std::uint32_t progress; + std::uint32_t isFinished; + std::int32_t result; +}; + +struct LncVideoFinalizeState { + std::uint32_t progress; + std::int32_t result; +}; + +struct LncLocalProcessParam; + +struct LncAppStatus { + std::int32_t appId; + std::int32_t launchRequestAppId; + std::int32_t appType; + std::int32_t appAttr; + char titleId[16]; + std::uint32_t appCategoryType; + std::int32_t appBootMode; + std::int32_t appBootSubMode; + char appVer[6]; + char contentVersion[11]; + bool isSystemExApp; + bool isCdlgInitialized; +}; + +struct LncShellUiAppStatus { + std::int32_t appId; + std::int32_t appType; + std::int32_t appAttr; + std::int32_t launchRequestAppId; + std::int32_t userId; + std::int32_t isActiveCdlg; + char path[1024]; + char sceSysDir[256]; + std::int32_t isCoredumped; + std::int32_t isPrxModuleLoadFailed; + std::int32_t appLocalPid; + std::int32_t crashReportMode; + char category[4]; + std::uint32_t appCategoryType; + std::int32_t appBootMode; + std::int32_t appBootSubMode; +}; + +struct AppMessagingMessage { + std::int32_t sender; + std::uint32_t messageType; + std::uint8_t payload[8192]; + std::uint32_t payloadSize; + std::uint64_t timestamp; +}; + +extern "C" { + +int APS5_VABI sceSystemServiceLoadExec(const char* path, const char* const* arguments); +int APS5_VABI sceSystemServiceDisableNoticeScreenSkipFlagAutoSet(void); +int APS5_VABI sceSystemServiceGetDisplaySafeAreaInfo(SystemServiceDisplaySafeAreaInfo* info); +int APS5_VABI sceSystemServiceShowDisplaySafeAreaSettings(void); +int APS5_VABI sceSystemServiceGetHdrToneMapLuminance(SystemServiceHdrToneMapLuminance* luminance); +int APS5_VABI sceSystemServiceGetNoticeScreenSkipFlag(bool* value); +int APS5_VABI sceSystemServiceGetStatus(SystemServiceStatus* status); +int APS5_VABI sceSystemServiceHideSplashScreen(void); +int APS5_VABI sceSystemServiceParamGetInt(int paramId, int* value); +int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, size_t bufSize); +int APS5_VABI sceSystemServicePowerTick(void); +int APS5_VABI sceSystemServiceReceiveEvent(SystemServiceEvent* event); +int APS5_VABI sceSystemServiceReportAbnormalTermination(const void* info); +int APS5_VABI sceSystemServiceSetNoticeScreenSkipFlag(void); +void APS5_VABI sceSystemServiceInitializePlayerDialogParam(SystemServicePlayerDialogParam* param); +int APS5_VABI sceSystemServiceDisableMediaPlay(); +int APS5_VABI sceSystemServiceReenableMediaPlay(); +int APS5_VABI sceSystemServiceLaunchPlayerDialog(const void* param); +int APS5_VABI sceSystemServiceLaunchWebBrowser(const char* uri); +int APS5_VABI sceSystemServiceLaunchApp(const char* titleId, const char* const* arguments, const void* param); +int APS5_VABI sceSystemServiceDisableMusicPlayer(void); +int APS5_VABI sceSystemServiceOpenChallengeActivity(void); +int APS5_VABI sceSystemServiceOpenTournamentOccurrence(const SystemServiceTournamentOccurrenceParam* param); +int APS5_VABI sceSystemServiceAddLocalProcessForPsmKit(const char*, const char* const*, const void*); +int APS5_VABI sceSystemServiceKillLocalProcessForPsmKit(int); +int APS5_VABI sceSystemServiceReenableMusicPlayer(void); +int APS5_VABI sceSystemServiceShowControllerSettings(void); +int APS5_VABI sceSystemServiceGetAppIdOfRunningBigApp(void); +int APS5_VABI sceSystemServiceKillApp(int appId, int how, int reason, int coreDump); +int APS5_VABI sceLncUtilKillApp(uint32_t appId); +int APS5_VABI sceLncUtilKillAppWithReason(uint32_t appId, int reason); +int APS5_VABI sceSystemServiceLaunchSystemDeeplink(void); +int APS5_VABI sceShellCoreUtilLaunchByUriParamInit(void); +int APS5_VABI sceSystemStateMgrTickVideoPlayback(void); +int APS5_VABI sceSystemServiceTelemetrySetData2WithUserId(void); +int APS5_VABI sceShellCoreUtilStartUmmDictation(void); +int APS5_VABI sceSystemServiceGetSpotifyTitleId(void); +int APS5_VABI sceSystemServiceGetAppStatusListContainsDaemon(void); +int APS5_VABI sceSystemServiceSwitchToVRApp(void); +int APS5_VABI sceSystemServiceSwitchToNonVRApp(void); +int APS5_VABI sceLncUtilGetSpmemAllocatorConfigValue(void); +int APS5_VABI sceCsbMgrInitialize(void); +int APS5_VABI sceCsbMgrTerminate(void); +int APS5_VABI sceCsbMgrCreateLocalSandbox(void); +int APS5_VABI sceCsbMgrDestroyLocalSandbox(void); +int APS5_VABI sceCsbMgrSwitchLocalSandbox(void); +int APS5_VABI sceCsbMgrGetCurrentLocalSandboxId(void); +int APS5_VABI sceCsbMgrGetLocalSandboxIdList(void); +int APS5_VABI sceCsbMgrGetLocalSandboxInfo(void); +int APS5_VABI sceCsbMgrSetLocalSandboxInfo(void); +int APS5_VABI sceCsbMgrIssueStreamingSandboxInfo(void); +int APS5_VABI sceReportSequenceHandlerRequestReport(void); +int APS5_VABI sceReportSequenceHandlerCancelReport(void); +int APS5_VABI sceReportSequenceHandlerExistsEvent(void); +int APS5_VABI sceReportSequenceHandlerUpdateEventCache(void); +int APS5_VABI sceReportSequenceHandlerGetEventCache(void); +int APS5_VABI sceLncUtilRegisterCallEntries(void); +int APS5_VABI sceLncUtilGetSpmemConfigJson(void); +int APS5_VABI sceLncUtilExportSpmemConfigJson(void); +int APS5_VABI sceLncUtilImportSpmemConfigJson(void); +int APS5_VABI sceShellCoreUtilExitAppWithValue(void); +int APS5_VABI sceShellCoreUtilNpDebugSpecialNotification(void); +int APS5_VABI sceShellCoreUtilIsDddTemperatureDanger(void); +int APS5_VABI sceShellCoreUtilIsM2StorageTemperatureDanger(void); +int APS5_VABI sceShellCoreUtilGetAvailableSizeM2(void); +int APS5_VABI sceShellCoreUtilNotifyConnectAccountStatusChanged(void); +int APS5_VABI sceShellCoreUtilIsInternalCronosUser(void); +int APS5_VABI sceShellCoreUtilChangePbtcMode(void); +int APS5_VABI sceShellCoreUtilTelemetryCacheData(void); +int APS5_VABI sceShellCoreUtilMicMuteKeyPressed(void); +int APS5_VABI sceShellCoreUtilSwitchMicMute(void); +int APS5_VABI sceShellCoreUtilNotifyStartEventToUniversalMicMute(void); +int APS5_VABI sceShellCoreUtilIsLotusMicActivated(void); +int APS5_VABI sceShellCoreUtilResetAllNoticeScreenSkipFlag(void); +int APS5_VABI sceShellCoreUtilResetNoticeScreenSkipFlag(void); +int APS5_VABI sceShellCoreUtilGetNoticeScreenSkipFlagByAppId(void); +int APS5_VABI sceShellCoreUtilGetPasscodeInputType(void); +int APS5_VABI sceShellCoreUtilGetPasscodeDisplayInfo(void); +int APS5_VABI sceShellCoreUtilNotifySystemResumeTelemetryInfo(void); +int APS5_VABI sceShellCoreUtilUpdateTelemetryToken(void); +int APS5_VABI sceShellCoreUtilGetVRBaseMatRectangles(void); +int APS5_VABI sceShellCoreUtilSetVRBaseMatRectangles(void); +int APS5_VABI sceShellCoreUtilIsLaunchBlockDialog(void); +int APS5_VABI sceShellCoreUtilGetConsoleToken(void); +int APS5_VABI sceShellCoreUtilGetDeviceIdByDeviceUniqueId(void); +int APS5_VABI sceShellCoreUtilGetDeviceUniqueIdByDeviceId(void); +int APS5_VABI sceShellCoreUtilGetStreamingClientInfo(void); +int APS5_VABI sceShellCoreUtilRequestCffUpdate(void); +int APS5_VABI sceShellCoreUtilGetCffConfig(void); +int APS5_VABI sceShellCoreUtilRemoveCffConfigFile(void); +int APS5_VABI sceShellCoreUtilDebugGetNumberOfCffItems(void); +int APS5_VABI sceShellCoreUtilDebugGetCffInfo(void); +int APS5_VABI sceShellCoreUtilDebugSetCffStatus(void); +int APS5_VABI sceShellCoreUtilGetPeripheralConnectionSessionId(void); +int APS5_VABI sceShellCoreUtilGetCdlgEnabledFreezeRendering(void); +int APS5_VABI sceShellCoreUtilGetNpTitleId(void); +int APS5_VABI sceShellCoreUtilIsCronosInGameCommerceEnabled(void); +int APS5_VABI sceShellCoreUtilLemSettingGet(void); +int APS5_VABI sceShellCoreUtilLemSettingSet(void); +int APS5_VABI sceShellCoreUtilLemSettingGetList(void); +int APS5_VABI sceShellCoreUtilLemSettingReset(void); +int APS5_VABI sceShellCoreUtilIsEChassisVariant(void); +int APS5_VABI sceSystemStateMgrGetRebootCause(void); +int APS5_VABI sceCoredumpSequenceHandlerUpdateEventCache(void); +int APS5_VABI sceCoredumpSequenceHandlerGetEventCache(void); +int APS5_VABI sceCoredumpSequenceHandlerGetWorkingEventSummary(void); +int APS5_VABI sceCoredumpSequenceHandlerGetCoredumpStatus(void); +int APS5_VABI sceCoredumpSequenceHandlerGetCoredumpStatusByAppId(void); +int APS5_VABI sceCoredumpSequenceHandlerCancelCoredump(void); +int APS5_VABI sceSystemServiceIsBgmPlaying(void); +int APS5_VABI sceShellCoreUtilIsBgmPlaying(void); +int APS5_VABI sceAppMessagingClearEventFlag(std::int32_t appId, std::uint64_t flags); +int APS5_VABI sceAppMessagingReceiveMsg(AppMessagingMessage* message); +int APS5_VABI sceAppMessagingSendMsg(std::uint32_t appId, std::uint32_t messageId, const void* data, std::size_t size, std::uint32_t flags); +int APS5_VABI sceAppMessagingSendMsgToShellCore(std::uint32_t messageId, const void* data, std::size_t size, std::uint32_t flags); +int APS5_VABI sceAppMessagingSendMsgToShellUI(std::uint32_t messageId, const void* data, std::size_t size, std::uint32_t flags); +int APS5_VABI sceAppMessagingSetEventFlag(std::int32_t appId, std::uint64_t flags); +int APS5_VABI sceAppMessagingTryGetEventFlag(std::uint64_t mask, std::uint64_t* flags, std::uint8_t clear); +int APS5_VABI sceAppMessagingTryReceiveMsg(AppMessagingMessage* message); +int APS5_VABI sceLncUtilAcquireCpuBudgetOfExtraAudioDevices(); +int APS5_VABI sceLncUtilAcquireCpuBudgetOfImeForBigApp(); +int APS5_VABI sceLncUtilAcquireCpuBudgetOfInGameStore(); +int APS5_VABI sceLncUtilActivateCdlg(); +int APS5_VABI sceLncUtilAddLocalProcess(std::int32_t appId, const char* path, const char* const* argv, const LncLocalProcessParam* param); +int APS5_VABI sceLncUtilBlockAppSuspend(const char* reason); +int APS5_VABI sceLncUtilBlockingGetEventForDaemon(LncDaemonEvent* event, std::uint64_t receiveArgument); +int APS5_VABI sceLncUtilContinueApp(std::int32_t appId); +int APS5_VABI sceLncUtilCrashSyscore(); +int APS5_VABI sceLncUtilDeactivateCdlg(); +int APS5_VABI sceLncUtilDeclareReadyForSuspend(); +int APS5_VABI sceLncUtilDisableSuspendNotification(); +int APS5_VABI sceLncUtilEnableSuspendNotification(); +int APS5_VABI sceLncUtilFinishSpecialResume(); +int APS5_VABI sceLncUtilForceKillApp(std::int32_t appId); +int APS5_VABI sceLncUtilForceKillLocalProcess(std::int32_t appId, std::int32_t processId); +int APS5_VABI sceLncUtilGetAppCategory(std::int32_t appId, char* category); +int APS5_VABI sceLncUtilGetAppCategoryType(std::int32_t appId, std::uint32_t* categoryType); +int APS5_VABI sceLncUtilGetAppFocusedAppStatus(LncAppStatus* status); +int APS5_VABI sceLncUtilGetAppId(const char* titleId); +int APS5_VABI sceLncUtilGetAppIdListOfBigApp(std::int32_t* appIds, std::uint32_t capacity, std::uint32_t* count); +int APS5_VABI sceLncUtilGetAppIdOfRunningBigApp(); +int APS5_VABI sceLncUtilGetAppLaunchedUser(std::int32_t appId, std::int32_t* userId); +int APS5_VABI sceLncUtilGetAppStatus(LncAppStatus* status); +int APS5_VABI sceLncUtilGetAppStatusList(LncAppStatus* statuses, std::uint32_t capacity, std::uint32_t* count); +int APS5_VABI sceLncUtilGetAppStatusListForShellUIReboot(LncShellUiAppStatus* statuses, std::uint32_t capacity, std::uint32_t* count); +int APS5_VABI sceLncUtilGetAppTitleId(std::int32_t appId, char* titleId); +int APS5_VABI sceLncUtilGetAppType(std::int32_t appId, std::uint32_t* type); +int APS5_VABI sceLncUtilGetCdlgExec(std::int32_t appId, bool* executing); +int APS5_VABI sceLncUtilGetCdlgType(std::int32_t appId, std::uint32_t* type); +int APS5_VABI sceLncUtilGetCoredumpState(std::int32_t appId, std::int32_t processId, LncCoredumpState* state); +int APS5_VABI sceLncUtilGetDbgExecutablePath(char* path, std::uint64_t selector); +int APS5_VABI sceLncUtilGetEventForDaemon(LncDaemonEvent* event); +int APS5_VABI sceLncUtilGetEventForShellUI(LncShellUiEvent* event); +int APS5_VABI sceLncUtilGetGpuCrashFullDumpAppStatus(LncGpuCrashAppStatus* status); +int APS5_VABI sceLncUtilGetLocalProcessStatusList(LncLocalProcessStatus* statuses, std::uint32_t capacity, std::uint32_t* count); +int APS5_VABI sceLncUtilGetParentSocket(); +int APS5_VABI sceLncUtilGetResultKillApp(std::uint32_t requestId); +int APS5_VABI sceLncUtilGetResultLaunchAppByTitleId(std::uint32_t requestId); +int APS5_VABI sceLncUtilGetVideoFinalizeState(std::int32_t appId, std::int32_t processId, LncVideoFinalizeState* state); +int APS5_VABI sceLncUtilInitialize(); +int APS5_VABI sceLncUtilIsActiveCdlg(bool* active); +int APS5_VABI sceLncUtilIsAppLaunched(const char* titleId, bool* launched); +int APS5_VABI sceLncUtilIsAppSuspended(std::int32_t appId, bool* suspended); +int APS5_VABI sceLncUtilIsCdlgActivated(std::uint8_t* activated); +bool APS5_VABI sceLncUtilIsCpuBudgetOfExtraAudioDevicesAvailable(); +int APS5_VABI sceLncUtilIsHostBoot(std::int32_t appId, bool* hostBoot); +int APS5_VABI sceLncUtilIsPs2Emu(bool* emulated); +bool APS5_VABI sceLncUtilIsShellUiFgAndGameBgCpuMode(); +int APS5_VABI sceLncUtilIsSpeculativeLaunch(std::int32_t appId, bool* speculative); +int APS5_VABI sceLncUtilKickCoredumpOnlyProcMem(std::int32_t appId, std::int32_t processId, const char* first, const char* second); +int APS5_VABI sceLncUtilKillLocalProcess(std::int32_t appId, std::int32_t processId); +int APS5_VABI sceLncUtilLaunchApp(const char* titleId, const char* const* argv, const LncLaunchParam* param); +int APS5_VABI sceLncUtilLoadExec(const char* path, const char* const* argv); +int APS5_VABI sceLncUtilNotifyCoredumpRequestEnd(std::int32_t appId, std::int32_t processId, std::int32_t result); +int APS5_VABI sceLncUtilNotifyCoredumpRequestProgress(std::int32_t appId, std::int32_t processId, std::int32_t progress); +int APS5_VABI sceLncUtilNotifyVshReady(std::uint32_t errorCode); +int APS5_VABI sceLncUtilRaiseException(std::int32_t appId, std::uint64_t exception); +int APS5_VABI sceLncUtilRaiseExceptionLocalProcess(std::int32_t appId, std::int32_t processId, std::uint64_t exception); +int APS5_VABI sceLncUtilRegisterCdlgSharedMemoryName(const char* name); +int APS5_VABI sceLncUtilRegisterDaemon(); +int APS5_VABI sceLncUtilRegisterShellUI(); +int APS5_VABI sceLncUtilReleaseCpuBudgetOfExtraAudioDevices(); +int APS5_VABI sceLncUtilReleaseCpuBudgetOfImeForBigApp(); +int APS5_VABI sceLncUtilReleaseCpuBudgetOfInGameStore(); +int APS5_VABI sceLncUtilResumeApp(std::int32_t appId, std::uint64_t flags); +int APS5_VABI sceLncUtilResumeLocalProcess(std::int32_t processId); +int APS5_VABI sceLncUtilSetAppFocus(std::int32_t appId, std::uint64_t flags, std::uint64_t contextId); +int APS5_VABI sceLncUtilSetCdlgExec(std::int32_t appId, std::uint8_t executing); +int APS5_VABI sceLncUtilSetControllerFocus(std::int32_t appId); +int APS5_VABI sceLncUtilSetControllerFocusPermission(std::uint8_t permission); +int APS5_VABI sceLncUtilStartKillApp(std::int32_t appId, std::uint32_t* requestId); +int APS5_VABI sceLncUtilStartLaunchAppByTitleId(const char* titleId, const char* const* argv, const LncLaunchParam* param, std::uint32_t* requestId); +int APS5_VABI sceLncUtilSuspendApp(std::int32_t appId, std::uint64_t flags, std::int32_t reason); +int APS5_VABI sceLncUtilSuspendBackgroundApp(std::int32_t first, std::int32_t second); +int APS5_VABI sceLncUtilSuspendLocalProcess(std::int32_t processId); +int APS5_VABI sceLncUtilTerminate(); +int APS5_VABI sceLncUtilTryBlockAppSuspend(const char* reason); +int APS5_VABI sceLncUtilUnblockAppSuspend(const char* reason); +int APS5_VABI sceLncUtilUnregisterCdlgSharedMemoryName(const char* name); +int APS5_VABI sceLncUtilUnregisterDaemon(); +int APS5_VABI sceLncUtilUnregisterShellUI(); +int APS5_VABI sceShellCoreUtilAccessibilityZoomLock(); +int APS5_VABI sceShellCoreUtilAccessibilityZoomUnlock(); +int APS5_VABI sceShellCoreUtilAcquireBgmCpuBudget(); +int APS5_VABI sceShellCoreUtilAcquireRemotePlayCpuBudget(); +int APS5_VABI sceShellCoreUtilAcquireSharePlayCpuBudget(); +int APS5_VABI sceShellCoreUtilActivateAbort(std::intptr_t handle); +int APS5_VABI sceShellCoreUtilActivateGetStatus(std::intptr_t handle, std::int32_t* status, std::int32_t* result); +int APS5_VABI sceShellCoreUtilActivateInit(std::int32_t type, std::intptr_t* handle); +int APS5_VABI sceShellCoreUtilActivateIsActivated(std::intptr_t handle, bool* activated); +int APS5_VABI sceShellCoreUtilActivateRecordActivation(); +int APS5_VABI sceShellCoreUtilActivateStart(std::intptr_t handle); +int APS5_VABI sceShellCoreUtilActivateStartAsync(std::intptr_t handle); +int APS5_VABI sceShellCoreUtilActivateTerm(std::intptr_t handle); +int APS5_VABI sceShellCoreUtilChangeRunLevel(std::int32_t runLevel); +int APS5_VABI sceShellCoreUtilChangeToStaffModeForIDU(); +int APS5_VABI sceShellCoreUtilCheckerAbort(); +int APS5_VABI sceShellCoreUtilCleanupCrashReport(); +int APS5_VABI sceShellCoreUtilClearAppData(); +int APS5_VABI sceShellCoreUtilClearPs4AppCategoryDefaultsListUpdate(); +int APS5_VABI sceShellCoreUtilClearPsnAccountInfo(); +int APS5_VABI sceShellCoreUtilCrashReportRequestCancel(); +int APS5_VABI sceShellCoreUtilDeclareBeginOfExternalStorageAppMove(); +int APS5_VABI sceShellCoreUtilDeclareEndOfExternalStorageAppMove(); +int APS5_VABI sceShellCoreUtilDeleteDiscInstalledTitleWorkaroundFile(); +int APS5_VABI sceShellCoreUtilDeleteDownloadedHidConfigFile(std::int32_t configIndex); +int APS5_VABI sceShellCoreUtilDeleteDownloadedNetEvConfigFile(); +int APS5_VABI sceShellCoreUtilDeleteDownloadedTitleWorkaroundFile(); +int APS5_VABI sceShellCoreUtilDoFsck(const char* path); +int APS5_VABI sceShellCoreUtilDownloadHidConfigFileFromServer(std::int32_t configIndex); +int APS5_VABI sceShellCoreUtilDownloadNetEvConfigFileFromServer(); +int APS5_VABI sceShellCoreUtilDownloadTitleWorkaroundFileFromServer(); +int APS5_VABI sceShellCoreUtilEndUmmDictation(); +int APS5_VABI sceShellCoreUtilEnterPowerLockSection(); +int APS5_VABI sceShellCoreUtilExecuteCrashReport(); +int APS5_VABI sceShellCoreUtilExfatFormatExternalHdd(const char* path); +int APS5_VABI sceShellCoreUtilExitApp(); +int APS5_VABI sceShellCoreUtilExitMiniApp(); +int APS5_VABI sceShellCoreUtilExitMiniAppWithValue(const char* value); +int APS5_VABI sceShellCoreUtilFormatExternalHdd(const char* path); +int APS5_VABI sceShellCoreUtilFormatHddForRestore(); +int APS5_VABI sceShellCoreUtilGetAppData(const char* key, void* data, std::int32_t capacity, std::int32_t* size); +int APS5_VABI sceShellCoreUtilGetAppEnableTTS(std::int32_t appId, bool* enabled); +int APS5_VABI sceShellCoreUtilGetAppEnterButtonAssign(std::int32_t appId, std::int32_t* assignment); +int APS5_VABI sceShellCoreUtilGetAppLaunchTypeInfo(std::int32_t appId, ShellCoreAppLaunchTypeInfo* info); +int APS5_VABI sceShellCoreUtilGetAppLaunchedParamInt(std::int32_t appId, const char* key, std::int32_t* value); +int APS5_VABI sceShellCoreUtilGetAppLaunchedParamIntByBudgetType(std::int32_t budgetType, const char* key, std::int32_t* value); +int APS5_VABI sceShellCoreUtilGetAppLaunchedParamString(std::int32_t appId, const char* key, char* value, std::int32_t capacity); +int APS5_VABI sceShellCoreUtilGetAppLaunchedParamStringByBudgetType(std::int32_t budgetType, const char* key, char* value, std::int32_t capacity); +int APS5_VABI sceShellCoreUtilGetAutoPowerDownRemainingSeconds(); +int APS5_VABI sceShellCoreUtilGetBasicProductShape(); +int APS5_VABI sceShellCoreUtilGetCheckerString(const char* key, char* value, std::size_t capacity); +int APS5_VABI sceShellCoreUtilGetCheckerStringEx(const char* firstKey, const char* secondKey, char* value, std::size_t capacity); +int APS5_VABI sceShellCoreUtilGetCloudClientStatus(std::uint32_t* status); +int APS5_VABI sceShellCoreUtilGetCrashReportCoreFileSetSize(); +int APS5_VABI sceShellCoreUtilGetCrashReportFilterInfoStart(); +int APS5_VABI sceShellCoreUtilGetCrashReportInfoForBoot(); +int APS5_VABI sceShellCoreUtilGetCrashReportInfoForBootStart(); +int APS5_VABI sceShellCoreUtilGetCrashReportInfoStart(); +int APS5_VABI sceShellCoreUtilGetCrashReportResult(); +int APS5_VABI sceShellCoreUtilGetCrashReportStatus(); +int APS5_VABI sceShellCoreUtilGetCrashReportUploadStatus(); +int APS5_VABI sceShellCoreUtilGetDeviceIndexBehavior(std::uint32_t* behavior, std::int32_t* userId, std::uint32_t* scene); +int APS5_VABI sceShellCoreUtilGetDeviceIndexBehaviorWithTimeout(std::uint32_t* behavior, std::int32_t* userId, std::uint32_t* scene, std::uint8_t timeout); +int APS5_VABI sceShellCoreUtilGetEffectiveTotalSizeOfUserPartition(std::uint64_t* size); +std::uint64_t APS5_VABI sceShellCoreUtilGetFreeSizeOfAvContentsTmp(); +int APS5_VABI sceShellCoreUtilGetFreeSizeOfUserPartition(std::uint64_t* freeSize, std::uint64_t* totalSize); +int APS5_VABI sceShellCoreUtilGetFsckProgress(const char* titleId, std::uint32_t* progress); +int APS5_VABI sceShellCoreUtilGetGameLiveStreamingStatus(bool* active); +bool APS5_VABI sceShellCoreUtilGetGnmCompositorOnScreenProfilerFlag(); +int APS5_VABI sceShellCoreUtilGetGpuLoadEmulationMode(); +int APS5_VABI sceShellCoreUtilGetGpuLoadEmulationModeByAppId(std::int32_t appId); +int APS5_VABI sceShellCoreUtilGetHidConfigFileInfoString(char* value, std::size_t capacity, std::int32_t configIndex); +int APS5_VABI sceShellCoreUtilGetHidConfigFileString(char* value, std::size_t capacity, std::int32_t configIndex); +int APS5_VABI sceShellCoreUtilGetHidConfigName(char* value, std::size_t capacity, std::int32_t configIndex); +int APS5_VABI sceShellCoreUtilGetHidConfigNum(std::int32_t* configNum); +int APS5_VABI sceShellCoreUtilGetIDUMode(std::uint8_t* mode); +bool APS5_VABI sceShellCoreUtilGetImposeMenuFlagForPs2Emu(std::int32_t appId); +int APS5_VABI sceShellCoreUtilGetManifestFileStatus(); +int APS5_VABI sceShellCoreUtilGetNeedSizeOfAppContent(std::uint64_t* size); +int APS5_VABI sceShellCoreUtilGetNetEvConfigFileInfoString(char* value, std::size_t capacity); +int APS5_VABI sceShellCoreUtilGetOutOfVrPlayZoneWarning(); +int APS5_VABI sceShellCoreUtilGetPapcGamePcl(); +int APS5_VABI sceShellCoreUtilGetPbtcUserInfoList(ShellCorePbtcUserInfoList* info); +int APS5_VABI sceShellCoreUtilGetPlatformPrivacyDefinitionEventData(const char* firstKey, const char* secondKey, ShellCorePrivacyEventData* data); +float APS5_VABI sceShellCoreUtilGetProgressOfFormatExternalHdd(); +float APS5_VABI sceShellCoreUtilGetProgressOfFsck(); +int APS5_VABI sceShellCoreUtilGetPs4AppCategoryDefaultsListId(void* data, std::size_t capacity); +int APS5_VABI sceShellCoreUtilGetPs4AppCategoryForTitleId(const char* titleId); +int APS5_VABI sceShellCoreUtilGetPsStoreIconLayout(std::int32_t appId); +int APS5_VABI sceShellCoreUtilGetPsStoreIconState(std::int32_t appId); +int APS5_VABI sceShellCoreUtilGetPsnAccountInfo(ShellCorePsnAccountInfoFirst* first, ShellCorePsnAccountInfoSecond* second, ShellCorePsnAccountInfoThird* third, ShellCorePsnAccountInfoFourth* fourth, ShellCorePsnAccountInfoFifth* fifth); +int APS5_VABI sceShellCoreUtilGetRemotePlayStatus(bool* active); +int APS5_VABI sceShellCoreUtilGetRunLevel(); +int APS5_VABI sceShellCoreUtilGetSharePlayStatus(bool* active); +int APS5_VABI sceShellCoreUtilGetSocialScreenStatus(bool* active); +int APS5_VABI sceShellCoreUtilGetSplashScreenState(std::int32_t appId); +int APS5_VABI sceShellCoreUtilGetSupportSiteURL(std::uint32_t first, std::uint32_t second, char* value, std::size_t capacity); +bool APS5_VABI sceShellCoreUtilGetSuspendConfirmationDialogFlag(std::int32_t appId); +int APS5_VABI sceShellCoreUtilGetSystemBGState(std::int32_t appId, std::uint32_t* state); +int APS5_VABI sceShellCoreUtilGetSystemBGWaveColor(std::int32_t appId, std::uint32_t* color); +int APS5_VABI sceShellCoreUtilGetSystemBGWaveState(std::int32_t appId, std::uint32_t* state); +int APS5_VABI sceShellCoreUtilGetTelemetryToken(ShellCoreTelemetryToken* token); +int APS5_VABI sceShellCoreUtilGetTitleWorkaroundFileInfoString(char* value, std::size_t capacity); +int APS5_VABI sceShellCoreUtilGetTitleWorkaroundFileString(char* value, std::size_t capacity); +int APS5_VABI sceShellCoreUtilGetUIStatus(std::uint64_t* status); +int APS5_VABI sceShellCoreUtilGetUmmStatus(std::int32_t userId, ShellCoreUmmStatus* status); +int APS5_VABI sceShellCoreUtilGetUserFocus(std::int32_t appId, std::int32_t* userId); +int APS5_VABI sceShellCoreUtilGetUserIdOfMorpheusUser(); +int APS5_VABI sceShellCoreUtilGetVMStatsForTelemetry(char* value, std::size_t capacity); +int APS5_VABI sceShellCoreUtilGetVersionNumberOfCameraCalibrationData(); +int APS5_VABI sceShellCoreUtilGoBackToKratosCurrentSessionGame(); +int APS5_VABI sceShellCoreUtilHideBlocksFromUser(std::int64_t bytes, std::int32_t flags); +int APS5_VABI sceShellCoreUtilIncrementVersionNumberOfCameraCalibrationData(); +bool APS5_VABI sceShellCoreUtilIsAccessibilityZoomLocked(std::int32_t appId); +bool APS5_VABI sceShellCoreUtilIsBgmCpuBudgetAcquired(); +bool APS5_VABI sceShellCoreUtilIsBgmCpuBudgetAvailable(); +bool APS5_VABI sceShellCoreUtilIsExternalStorageAppMoveInProgress(); +int APS5_VABI sceShellCoreUtilIsEyeToEyeDistanceAdjusted(std::uint32_t userId, std::uint8_t* adjusted); +bool APS5_VABI sceShellCoreUtilIsGameLiveStreamingOnAir(); +int APS5_VABI sceShellCoreUtilIsIDUState(); +bool APS5_VABI sceShellCoreUtilIsImposeScreenOverlaid(); +bool APS5_VABI sceShellCoreUtilIsInSystemSuspendBlackList(std::uint32_t appId); +int APS5_VABI sceShellCoreUtilIsInternalKratosUser(); +int APS5_VABI sceShellCoreUtilIsKilledOrSuspendedByLogout(std::int32_t first, std::int32_t second, std::uint8_t* result); +int APS5_VABI sceShellCoreUtilIsNeededCrashReport(); +bool APS5_VABI sceShellCoreUtilIsPowerSaveAlertRequested(); +bool APS5_VABI sceShellCoreUtilIsRemotePlayCpuBudgetAcquired(); +bool APS5_VABI sceShellCoreUtilIsScreenSaverOn(); +bool APS5_VABI sceShellCoreUtilIsSharePlayCpuBudgetAcquired(); +int APS5_VABI sceShellCoreUtilIsShowCrashReport(); +int APS5_VABI sceShellCoreUtilIsTemperatureDanger(); +int APS5_VABI sceShellCoreUtilIsTitleWorkaroundEnabled(std::int32_t selector, const char* key); +bool APS5_VABI sceShellCoreUtilIsUsbMassStorageMounted(std::uint32_t deviceIndex); +int APS5_VABI sceShellCoreUtilLaunchByUri(const char* uri, const ShellCoreLaunchByUriParam* param); +int APS5_VABI sceShellCoreUtilLeavePowerLockSection(); +int APS5_VABI sceShellCoreUtilMakeManifestFile(); +int APS5_VABI sceShellCoreUtilMountAppRight(const char* key); +int APS5_VABI sceShellCoreUtilMountDownloadDataForShellUI(const char* key); +int APS5_VABI sceShellCoreUtilMountHddForBackup(); +int APS5_VABI sceShellCoreUtilMountHddForRestore(); +int APS5_VABI sceShellCoreUtilNavigateToAnotherApp(std::uint32_t target); +int APS5_VABI sceShellCoreUtilNavigateToGoHome(std::uint32_t target); +int APS5_VABI sceShellCoreUtilNavigateToLaunchedApp(std::uint32_t target); +int APS5_VABI sceShellCoreUtilNotificationCancelForIDU(std::int64_t notificationId); +int APS5_VABI sceShellCoreUtilNotificationRequestedForIDU(std::int64_t notificationId, const char* text, std::uint32_t type); +int APS5_VABI sceShellCoreUtilNotifyBgmCoreTermination(); +int APS5_VABI sceShellCoreUtilNotifyFsReadError(std::int32_t first, std::int32_t second, std::int32_t third, const char* detail); +int APS5_VABI sceShellCoreUtilNotifyImeStatusChanged(std::int32_t imeStatus, std::uint8_t isCalledFromGame); +int APS5_VABI sceShellCoreUtilNotifyPsnAccountInfoReceived(); +int APS5_VABI sceShellCoreUtilNotifySystemSuspendTelemetryInfo(std::uint32_t info); +int APS5_VABI sceShellCoreUtilPfAuthClientConsoleTokenClearCache(); +int APS5_VABI sceShellCoreUtilPostActivityForPsNow(std::int32_t type, const char* activity); +int APS5_VABI sceShellCoreUtilPostErrorLog(const char* titleId, std::int32_t internalErrorCode, const char* shortError, const char* messageBuffer, std::int32_t suggestionType, const char* logReport); +int APS5_VABI sceShellCoreUtilPostLaunchConfirmResult(std::uint32_t first, std::uint32_t second); +int APS5_VABI sceShellCoreUtilPostPsmEventToShellUI(std::int32_t eventId, const char* data); +int APS5_VABI sceShellCoreUtilPreNotifyOfGameLiveStreaming(); +int APS5_VABI sceShellCoreUtilPreNotifyOfRemotePlay(); +int APS5_VABI sceShellCoreUtilPreNotifyOfSharePlay(); +int APS5_VABI sceShellCoreUtilReleaseBgmCpuBudget(); +int APS5_VABI sceShellCoreUtilReleaseRemotePlayCpuBudget(); +int APS5_VABI sceShellCoreUtilReleaseSharePlayCpuBudget(); +int APS5_VABI sceShellCoreUtilReportSessionErrorToGaikaiController(std::int32_t error, const char* detail); +int APS5_VABI sceShellCoreUtilReportUnexpectedFatalErrorToSystemTelemetry(std::int32_t error, const char* detail); +int APS5_VABI sceShellCoreUtilRequestCameraCalibration(); +int APS5_VABI sceShellCoreUtilRequestEjectDevice(const char* path); +int APS5_VABI sceShellCoreUtilRequestRebootApp(std::int32_t appId); +int APS5_VABI sceShellCoreUtilRequestShutdown(std::int32_t reason); +int APS5_VABI sceShellCoreUtilResetAutoPowerDownTimer(); +int APS5_VABI sceShellCoreUtilResetBgdcConfig(); +int APS5_VABI sceShellCoreUtilSetAppData(const char* key, const char* value); +int APS5_VABI sceShellCoreUtilSetBgmProhibition(std::uint32_t value); +int APS5_VABI sceShellCoreUtilSetDeviceIndexBehavior(std::uint32_t behavior, std::int32_t userId, std::uint8_t scene); +int APS5_VABI sceShellCoreUtilSetGameLiveStreamingOnAirFlag(std::int32_t enabled); +int APS5_VABI sceShellCoreUtilSetGameLiveStreamingStatus(std::int32_t active); +int APS5_VABI sceShellCoreUtilSetGnmCompositorOnScreenProfilerFlag(std::int32_t enabled); +int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationMode(std::uint32_t mode); +int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationModeByAppId(std::int32_t appId, std::uint32_t mode); +int APS5_VABI sceShellCoreUtilSetIDUMode(std::uint8_t mode); +int APS5_VABI sceShellCoreUtilSetImposeStatusFlag(std::int32_t enabled); +int APS5_VABI sceShellCoreUtilSetPsStoreIconLayout(std::uint32_t layout); +int APS5_VABI sceShellCoreUtilSetPsStoreIconState(std::uint32_t state); +int APS5_VABI sceShellCoreUtilSetRemotePlayStatus(std::int32_t active); +int APS5_VABI sceShellCoreUtilSetSharePlayStatus(std::int32_t active); +int APS5_VABI sceShellCoreUtilSetSkipUpdateCheck(std::uint8_t skip); +int APS5_VABI sceShellCoreUtilSetSocialScreenStatus(std::int32_t active); +int APS5_VABI sceShellCoreUtilSetSplashScreenState(std::uint32_t state); +int APS5_VABI sceShellCoreUtilSetSystemBGState(std::uint32_t state); +int APS5_VABI sceShellCoreUtilSetSystemBGWaveColor(std::uint32_t color); +int APS5_VABI sceShellCoreUtilSetSystemBGWaveState(std::uint32_t state); +int APS5_VABI sceShellCoreUtilSetUIStatus(std::uint64_t status, std::uint64_t mask); +int APS5_VABI sceShellCoreUtilSetUserFocus(std::int32_t userId); +int APS5_VABI sceShellCoreUtilShowCriticalErrorDialog(std::uint32_t error, const char* message); +int APS5_VABI sceShellCoreUtilShowErrorDialog(std::uint32_t error, const char* message); +int APS5_VABI sceShellCoreUtilShowErrorDialogWithFormatArgs(std::uint32_t error, const char* message, const char* const* formatArgs); +int APS5_VABI sceShellCoreUtilShowErrorDialogWithParam(std::uint32_t error, const ShellCoreErrorDialogParam* param); +int APS5_VABI sceShellCoreUtilShowPsUnderLockIndicator(); +int APS5_VABI sceShellCoreUtilSignalUserInput(); +int APS5_VABI sceShellCoreUtilStartPsNowGame(const char* data); +int APS5_VABI sceShellCoreUtilStopPsNowGame(const char* data); +int APS5_VABI sceShellCoreUtilTalkKeyEventReceived(std::int64_t deviceId, std::int32_t userId, std::int32_t keyEventType); +int APS5_VABI sceShellCoreUtilTestBusTransferSpeed(const char* path, std::uint8_t* result); +int APS5_VABI sceShellCoreUtilTickHeartBeat(); +int APS5_VABI sceShellCoreUtilTriggerPapcUpdate(std::uint32_t value); +int APS5_VABI sceShellCoreUtilTurnOffScreenSaver(); +int APS5_VABI sceShellCoreUtilUnmountAppRight(const char* key); +int APS5_VABI sceShellCoreUtilUnmountDownloadDataForShellUI(const char* key); +int APS5_VABI sceShellCoreUtilUnmountHddForBackup(); +int APS5_VABI sceShellCoreUtilUnmountHddForRestore(); +int APS5_VABI sceShellCoreutilGetCrashReportProcessInformation(); +int APS5_VABI sceSystemModalDialogClose(std::uint32_t dialogId); +int APS5_VABI sceSystemModalDialogOpen(const char* message); +int APS5_VABI sceSystemServiceAcquireBgmCpuBudget(); +int APS5_VABI sceSystemServiceAcquireFb0(); +int APS5_VABI sceSystemServiceActivateHevc(); +int APS5_VABI sceSystemServiceActivateHevcAbort(std::uintptr_t handle); +int APS5_VABI sceSystemServiceActivateHevcGetStatus(std::uintptr_t handle, std::uint32_t* status, std::int32_t* result); +int APS5_VABI sceSystemServiceActivateHevcInit(std::uintptr_t* handle); +int APS5_VABI sceSystemServiceActivateHevcIsActivated(std::uint8_t* activated); +int APS5_VABI sceSystemServiceActivateHevcSoft(); +int APS5_VABI sceSystemServiceActivateHevcSoftAbort(std::uintptr_t handle); +int APS5_VABI sceSystemServiceActivateHevcSoftGetStatus(std::uintptr_t handle, std::uint32_t* status, std::int32_t* result); +int APS5_VABI sceSystemServiceActivateHevcSoftInit(std::uintptr_t* handle); +int APS5_VABI sceSystemServiceActivateHevcSoftIsActivated(std::uint8_t* activated); +int APS5_VABI sceSystemServiceActivateHevcSoftStart(std::uintptr_t handle); +int APS5_VABI sceSystemServiceActivateHevcSoftTerm(std::uintptr_t handle); +int APS5_VABI sceSystemServiceActivateHevcStart(std::uintptr_t handle); +int APS5_VABI sceSystemServiceActivateHevcTerm(std::uintptr_t handle); +int APS5_VABI sceSystemServiceActivateMpeg2Abort(std::uintptr_t handle); +int APS5_VABI sceSystemServiceActivateMpeg2GetStatus(std::uintptr_t handle, std::uint32_t* status, std::int32_t* result); +int APS5_VABI sceSystemServiceActivateMpeg2Init(std::uintptr_t* handle); +int APS5_VABI sceSystemServiceActivateMpeg2IsActivated(std::uint8_t* activated); +int APS5_VABI sceSystemServiceActivateMpeg2Start(std::uintptr_t handle); +int APS5_VABI sceSystemServiceActivateMpeg2Term(std::uintptr_t handle); +int APS5_VABI sceSystemServiceAddLocalProcess(std::int32_t appId, const char* path, const char* const* argv, const SystemServiceLocalProcessParam* param); +int APS5_VABI sceSystemServiceAddLocalProcessForJvm(const char* path, const char* const* argv, const SystemServiceJvmProcessParam* param); +int APS5_VABI sceSystemServiceAddLocalProcessForPs2Emu(const char* path, const char* const* argv, const SystemServicePs2ProcessParam* param); +int APS5_VABI sceSystemServiceAppGetAppInstallStatus(const char* titleId, SystemServiceAppInstallStatus* status); +int APS5_VABI sceSystemServiceChangeAcpClock(); +int APS5_VABI sceSystemServiceChangeClock(const char* clock, std::uint32_t value); +int APS5_VABI sceSystemServiceChangeCpuClock(); +int APS5_VABI sceSystemServiceChangeCpuPstate(const SystemServiceCpuPstateParam* param); +int APS5_VABI sceSystemServiceChangeGpuClock(); +int APS5_VABI sceSystemServiceChangeMemPstate(std::uint32_t state); +int APS5_VABI sceSystemServiceChangeMemoryClock(); +int APS5_VABI sceSystemServiceChangeMemoryClockToBaseMode(); +int APS5_VABI sceSystemServiceChangeMemoryClockToDefault(); +int APS5_VABI sceSystemServiceChangeMemoryClockToMultiMediaMode(); +int APS5_VABI sceSystemServiceChangeNumberOfGpuCu(); +int APS5_VABI sceSystemServiceChangeSamuClock(); +int APS5_VABI sceSystemServiceChangeUvdClock(); +int APS5_VABI sceSystemServiceChangeVceClock(); +int APS5_VABI sceSystemServiceDeclareReadyForSuspend(); +int APS5_VABI sceSystemServiceDisablePartyVoice(); +int APS5_VABI sceSystemServiceDisablePersonalEyeToEyeDistanceSetting(); +int APS5_VABI sceSystemServiceDisableSuspendConfirmationDialog(); +int APS5_VABI sceSystemServiceDisableSuspendNotification(); +int APS5_VABI sceSystemServiceDisableVoiceRecognition(); +int APS5_VABI sceSystemServiceEnablePersonalEyeToEyeDistanceSetting(); +int APS5_VABI sceSystemServiceEnableSuspendConfirmationDialog(); +int APS5_VABI sceSystemServiceEnableSuspendNotification(); +int APS5_VABI sceSystemServiceGetAppCategoryType(std::int32_t appId, std::uint32_t* categoryType); +int APS5_VABI sceSystemServiceGetAppFocusedAppStatus(SystemServiceAppStatus* status); +int APS5_VABI sceSystemServiceGetAppId(const char* titleId); +int APS5_VABI sceSystemServiceGetAppIdListOfBigApp(std::int32_t* appIds, std::uint32_t capacity, std::uint32_t* count); +int APS5_VABI sceSystemServiceGetAppLaunchedUser(std::int32_t appId, std::int32_t* userId); +int APS5_VABI sceSystemServiceGetAppStatus(SystemServiceAppStatus* status); +int APS5_VABI sceSystemServiceGetAppTitleId(std::int32_t appId, char* titleId); +int APS5_VABI sceSystemServiceGetAppType(std::int32_t appId, std::int32_t* type); +int APS5_VABI sceSystemServiceGetDbgExecutablePath(char* path, std::uint64_t selector); +int APS5_VABI sceSystemServiceGetEventForDaemon(SystemServiceDaemonEvent* event); +int APS5_VABI sceSystemServiceGetGpuLoadEmulationMode(); +int APS5_VABI sceSystemServiceGetLocalProcessStatusList(SystemServiceLocalProcessStatus* statuses, std::uint32_t capacity, std::uint32_t* count); +int APS5_VABI sceSystemServiceGetMainAppTitleId(); +int APS5_VABI sceSystemServiceGetPSButtonEvent(const SystemServiceEvent* event, SystemServicePSButtonEvent* buttonEvent); +int APS5_VABI sceSystemServiceGetParentSocket(); +int APS5_VABI sceSystemServiceGetParentSocketForJvm(); +int APS5_VABI sceSystemServiceGetParentSocketForPs2Emu(); +int APS5_VABI sceSystemServiceGetParentSocketForPsmKit(); +int APS5_VABI sceSystemServiceGetPlatformPrivacyDefinitionData(const char* key, SystemServicePrivacyDefinitionData* data); +int APS5_VABI sceSystemServiceGetPlatformPrivacyDefinitionVersion(const char* key, SystemServicePrivacyDefinitionVersion* version); +int APS5_VABI sceSystemServiceGetPlatformPrivacySetting(std::uint32_t* setting); +int APS5_VABI sceSystemServiceGetRecentLotusPushedButton(std::uint32_t timeout, std::int32_t* deviceId, std::uint32_t* buttons); +int APS5_VABI sceSystemServiceGetRemoteplayStatus(bool* active); +int APS5_VABI sceSystemServiceGetRenderingMode(); +int APS5_VABI sceSystemServiceGetSubAppTitleId(); +int APS5_VABI sceSystemServiceGetSubAppTitleIdForSP(); +int APS5_VABI sceSystemServiceGetTitleWorkaroundInfo(SystemServiceTitleWorkaroundInfo* info); +int APS5_VABI sceSystemServiceGetUIStatus(std::uint64_t* status); +int APS5_VABI sceSystemServiceGetVersionNumberOfCameraCalibrationData(); +int APS5_VABI sceSystemServiceInitializeForShellCore(); +int APS5_VABI sceSystemServiceInvokeAppLaunchLink(const char* uri); +int APS5_VABI sceSystemServiceIsAppSuspended(std::int32_t appId, bool* suspended); +bool APS5_VABI sceSystemServiceIsBgmCpuBudgetAvailable(); +int APS5_VABI sceSystemServiceIsEyeToEyeDistanceAdjusted(std::uint32_t userId, std::uint8_t* adjusted); +bool APS5_VABI sceSystemServiceIsGameLiveStreamingOnAir(); +bool APS5_VABI sceSystemServiceIsScreenSaverOn(); +bool APS5_VABI sceSystemServiceIsShellUiFgAndGameBgCpuMode(); +int APS5_VABI sceSystemServiceKillLocalProcess(std::int32_t appId, std::int32_t processId); +int APS5_VABI sceSystemServiceKillLocalProcessForJvm(std::int32_t processId); +int APS5_VABI sceSystemServiceKillLocalProcessForPs2Emu(std::int32_t processId); +int APS5_VABI sceSystemServiceLaunchEventDetails(std::uint64_t unusedSlot, const char* eventId); +int APS5_VABI sceSystemServiceLaunchStore(const char* uri, const SystemServiceStoreParam* param); +int APS5_VABI sceSystemServiceLaunchTournamentList(); +int APS5_VABI sceSystemServiceLaunchTournamentsTeamProfile(std::uint64_t unusedSlot, const char* profile, const char* tournament, const char* context); +int APS5_VABI sceSystemServiceLaunchUdsApp(); +int APS5_VABI sceSystemServiceLaunchWebApp(const char* argument); +int APS5_VABI sceSystemServiceLoadExecVideoServiceWebApp(); +int APS5_VABI sceSystemServiceNavigateToAnotherApp(std::uint32_t target); +int APS5_VABI sceSystemServiceNavigateToGoBack(); +int APS5_VABI sceSystemServiceNavigateToGoBackWithValue(const char* value); +int APS5_VABI sceSystemServiceNavigateToGoHome(std::uint32_t target); +int APS5_VABI sceSystemServiceNotifyBgmCoreTermination(); +int APS5_VABI sceSystemServiceRaiseExceptionLocalProcess(std::int32_t appId, std::int32_t processId, std::uint64_t exception); +int APS5_VABI sceSystemServiceReenablePartyVoice(); +int APS5_VABI sceSystemServiceReenableVoiceRecognition(); +int APS5_VABI sceSystemServiceRegisterDaemon(); +int APS5_VABI sceSystemServiceReleaseBgmCpuBudget(); +int APS5_VABI sceSystemServiceReleaseFb0(); +int APS5_VABI sceSystemServiceRequestCameraCalibration(); +int APS5_VABI sceSystemServiceRequestPowerOff(); +int APS5_VABI sceSystemServiceRequestReboot(); +int APS5_VABI sceSystemServiceRequestToChangeRenderingMode(); +int APS5_VABI sceSystemServiceResumeLocalProcess(std::int32_t processId); +int APS5_VABI sceSystemServiceSaveVideoToken(std::int32_t userId, const char* token); +int APS5_VABI sceSystemServiceSetControllerFocusPermission(std::uint8_t permission); +int APS5_VABI sceSystemServiceSetGpuLoadEmulationMode(std::uint32_t mode); +int APS5_VABI sceSystemServiceSetOutOfVrPlayAreaFlag(std::uint8_t flag); +int APS5_VABI sceSystemServiceSetOutOfVrPlayZoneWarning(); +int APS5_VABI sceSystemServiceSetPowerSaveLevel(std::uint32_t level); +int APS5_VABI sceSystemServiceShowClosedCaptionAdvancedSettings(); +int APS5_VABI sceSystemServiceShowClosedCaptionSettings(); +int APS5_VABI sceSystemServiceShowErrorDialog(std::uint32_t error, const char* message); +int APS5_VABI sceSystemServiceShowEyeToEyeDistanceSetting(); +int APS5_VABI sceSystemServiceShowImposeMenuForPs2Emu(std::uint8_t enabled, std::uint64_t reserved); +int APS5_VABI sceSystemServiceSuspendBackgroundApp(std::int32_t first, std::int32_t second); +int APS5_VABI sceSystemServiceSuspendLocalProcess(std::int32_t processId); +int APS5_VABI sceSystemServiceTelemetrySetData(const char* key, const void* data, std::size_t size); +int APS5_VABI sceSystemServiceTelemetrySetData2(const char* key, const char* data); +int APS5_VABI sceSystemServiceTickVideoPlayback(); +int APS5_VABI sceSystemServiceTurnOffScreenSaver(); +int APS5_VABI sceSystemServiceUsbStorageGetDeviceInfo(std::uint32_t device, UsbStorageDeviceInfo* info); +int APS5_VABI sceSystemServiceUsbStorageGetDeviceList(std::uint32_t* devices, std::int32_t* count); +int APS5_VABI sceSystemServiceUsbStorageGetdentsClose(std::int32_t fd); +int APS5_VABI sceSystemServiceUsbStorageGetdentsOpen(std::uint32_t device, std::int32_t* fd, std::uint64_t* bufferSize); +int APS5_VABI sceSystemServiceUsbStorageGetdentsRead(std::int32_t fd, void* buffer, std::uint64_t bufferSize, std::uint64_t* bytesRead); +int APS5_VABI sceSystemServiceUsbStorageInit(void* attr); +bool APS5_VABI sceSystemServiceUsbStorageIsExist(std::uint32_t device, const char* directory, std::uint8_t* exists); +int APS5_VABI sceSystemServiceUsbStorageRegisterCallback(std::int32_t eventType, UsbStorageCallback callback, void* arg); +int APS5_VABI sceSystemServiceUsbStorageRegisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* arg); +int APS5_VABI sceSystemServiceUsbStorageRegisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* 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); +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); +int APS5_VABI sceSystemServiceUsbStorageRequestUnmap(std::uint32_t device, const char* directory); +int APS5_VABI sceSystemServiceUsbStorageTerm(); +int APS5_VABI sceSystemServiceUsbStorageUnregisterCallback(std::int32_t eventType, UsbStorageCallback callback); +int APS5_VABI sceSystemServiceUsbStorageUnregisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback); +int APS5_VABI sceSystemServiceUsbStorageUnregisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback); +int APS5_VABI sceSystemStateMgrCancelShutdownTimer(); +int APS5_VABI sceSystemStateMgrEnterMediaPlaybackMode(); +int APS5_VABI sceSystemStateMgrEnterStandby(); +int APS5_VABI sceSystemStateMgrExtendShutdownTimer(); +int APS5_VABI sceSystemStateMgrExtendShutdownTimerForPostAutoUpdateProcess(); +int APS5_VABI sceSystemStateMgrGetCurrentState(); +int APS5_VABI sceSystemStateMgrGetTriggerCode(); +bool APS5_VABI sceSystemStateMgrIsBdDriveReady(); +int APS5_VABI sceSystemStateMgrIsGpuPerformanceNormal(); +bool APS5_VABI sceSystemStateMgrIsShellUIShutdownInProgress(); +bool APS5_VABI sceSystemStateMgrIsStandbyModeEnabled(); +int APS5_VABI sceSystemStateMgrLeaveMediaPlaybackMode(); +int APS5_VABI sceSystemStateMgrNotifySystemSuspendResumeProgress(const char* progress); +int APS5_VABI sceSystemStateMgrReboot(); +int APS5_VABI sceSystemStateMgrRequestToKeepMainOnStandby(const char* reason); +int APS5_VABI sceSystemStateMgrSendCecOneTouchPlayCommand(); +int APS5_VABI sceSystemStateMgrStartRebootTimer(std::int32_t mode); +int APS5_VABI sceSystemStateMgrStartShutdownTimer(); +int APS5_VABI sceSystemStateMgrStartStadbyTimer(); +int APS5_VABI sceSystemStateMgrStartVshAutoUpdateTimer(); +int APS5_VABI sceSystemStateMgrTickMusicPlayback(); +int APS5_VABI sceSystemStateMgrTickPartyChat(); +int APS5_VABI sceSystemStateMgrTurnOff(); +int APS5_VABI sceSystemStateMgrVshAutoUpdate(); +int APS5_VABI sceSystemStateMgrWaitVshAutoUpdateVerifyDone(); +int APS5_VABI sceSystemStateMgrWakeUp(std::uint16_t trigger); + +} diff --git a/core/libs/prx/libSceSystemService/libSceSystemService.hpp b/core/libs/prx/libSceSystemService/libSceSystemService.hpp new file mode 100644 index 0000000000..e3ff176277 --- /dev/null +++ b/core/libs/prx/libSceSystemService/libSceSystemService.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSystemService/include/SystemService.hpp" diff --git a/core/libs/prx/libSceSystemTts/Export.cpp b/core/libs/prx/libSceSystemTts/Export.cpp index d895b270e8..dc975b9b4a 100644 --- a/core/libs/prx/libSceSystemTts/Export.cpp +++ b/core/libs/prx/libSceSystemTts/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSystemTts/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceSystemTts/include/Api.hpp b/core/libs/prx/libSceSystemTts/include/Api.hpp new file mode 100644 index 0000000000..d0942cfe30 --- /dev/null +++ b/core/libs/prx/libSceSystemTts/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSystemTtsCancel(); +int APS5_VABI sceSystemTtsIsAccessibilityAvailable(); +int APS5_VABI sceSystemTtsRegisterCallback(); +int APS5_VABI sceSystemTtsSpeakRequest(); +int APS5_VABI sceSystemTtsUnregisterCallback(); + +} diff --git a/core/libs/prx/libSceSystemTts/libSceSystemTts.hpp b/core/libs/prx/libSceSystemTts/libSceSystemTts.hpp new file mode 100644 index 0000000000..f2c384d4c4 --- /dev/null +++ b/core/libs/prx/libSceSystemTts/libSceSystemTts.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSystemTts/include/Api.hpp" diff --git a/core/libs/prx/libSceTEEClient/Export.cpp b/core/libs/prx/libSceTEEClient/Export.cpp index 58c703695b..71faf0cb5f 100644 --- a/core/libs/prx/libSceTEEClient/Export.cpp +++ b/core/libs/prx/libSceTEEClient/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceTEEClient/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceTEEClient/include/Api.hpp b/core/libs/prx/libSceTEEClient/include/Api.hpp new file mode 100644 index 0000000000..2c38c96684 --- /dev/null +++ b/core/libs/prx/libSceTEEClient/include/Api.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI AMDTEE_DLM_FetchDebugStrings_nid_postfix(void); +int APS5_VABI AMDTEE_DLM_GetDebugToken_nid_postfix(void); +int APS5_VABI AMDTEE_DLM_StopTADebug_nid_postfix(void); +int APS5_VABI TEECI_InitASD_nid_postfix(void); +int APS5_VABI TEECI_SetTimeout_nid_postfix(void); +int APS5_VABI TEEC_CloseSession_nid_postfix(void); +int APS5_VABI TEEC_InvokeCommand_nid_postfix(void); +int APS5_VABI TEEC_OpenSession_nid_postfix(void); +int APS5_VABI TEEC_ReleaseSharedMemory_nid_postfix(void); +int APS5_VABI TEEC_RequestCancellation_nid_postfix(void); + +} diff --git a/core/libs/prx/libSceTEEClient/libSceTEEClient.hpp b/core/libs/prx/libSceTEEClient/libSceTEEClient.hpp new file mode 100644 index 0000000000..7b3b5682b7 --- /dev/null +++ b/core/libs/prx/libSceTEEClient/libSceTEEClient.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceTEEClient/include/Api.hpp" diff --git a/core/libs/prx/libSceTextToSpeech/Export.cpp b/core/libs/prx/libSceTextToSpeech/Export.cpp index 05c84e9df9..cd4a31d3f6 100644 --- a/core/libs/prx/libSceTextToSpeech/Export.cpp +++ b/core/libs/prx/libSceTextToSpeech/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceTextToSpeech/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceTextToSpeech/include/Api.hpp b/core/libs/prx/libSceTextToSpeech/include/Api.hpp new file mode 100644 index 0000000000..6f8845b245 --- /dev/null +++ b/core/libs/prx/libSceTextToSpeech/include/Api.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceTextToSpeechCancelImpl(); +int APS5_VABI sceTextToSpeechCloseImpl(); +int APS5_VABI sceTextToSpeechGetStatusImpl(); +int APS5_VABI sceTextToSpeechInitImpl(); +int APS5_VABI sceTextToSpeechOpenImpl(); +int APS5_VABI sceTextToSpeechPauseImpl(); +int APS5_VABI sceTextToSpeechResumeImpl(); +int APS5_VABI sceTextToSpeechSpeakContinuousImpl(); +int APS5_VABI sceTextToSpeechSpeakImpl(); +int APS5_VABI sceTextToSpeechTermImpl(); + +} diff --git a/core/libs/prx/libSceTextToSpeech/libSceTextToSpeech.hpp b/core/libs/prx/libSceTextToSpeech/libSceTextToSpeech.hpp new file mode 100644 index 0000000000..0238da246a --- /dev/null +++ b/core/libs/prx/libSceTextToSpeech/libSceTextToSpeech.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceTextToSpeech/include/Api.hpp" diff --git a/core/libs/prx/libSceTextToSpeech2/Export.cpp b/core/libs/prx/libSceTextToSpeech2/Export.cpp index 09950022bc..02aaefeadf 100644 --- a/core/libs/prx/libSceTextToSpeech2/Export.cpp +++ b/core/libs/prx/libSceTextToSpeech2/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceTextToSpeech2/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -9,13 +10,18 @@ constexpr std::int32_t SCE_KERNEL_ERROR_EOPNOTSUPP = static_cast(0 } +static_assert(sizeof(TextToSpeechInitParameter) == 64); +static_assert(sizeof(TextToSpeechOpenParameter) == 64); +static_assert(sizeof(TextToSpeechSpeakParameter) == 64); +static_assert(offsetof(TextToSpeechSpeakParameter, reserved) == 24); + extern "C" { -int APS5_VABI sceTextToSpeech2GetSystemStatus() { +int APS5_VABI sceTextToSpeech2GetSystemStatus(std::int32_t* status) { return SCE_KERNEL_ERROR_EOPNOTSUPP; } -std::int32_t APS5_VABI sceTextToSpeech2Initialize(const void* param) { +std::int32_t APS5_VABI sceTextToSpeech2Initialize(const TextToSpeechInitParameter* param) { (void)param; return SCE_KERNEL_ERROR_EOPNOTSUPP; } @@ -28,15 +34,15 @@ int APS5_VABI sceTextToSpeech2Close() { return SCE_KERNEL_ERROR_EOPNOTSUPP; } -int APS5_VABI sceTextToSpeech2GetSpeechStatus() { +int APS5_VABI sceTextToSpeech2GetSpeechStatus(std::int32_t* status) { return SCE_KERNEL_ERROR_EOPNOTSUPP; } -int APS5_VABI sceTextToSpeech2Open() { +int APS5_VABI sceTextToSpeech2Open(const TextToSpeechOpenParameter* parameter) { return SCE_KERNEL_ERROR_EOPNOTSUPP; } -int APS5_VABI sceTextToSpeech2Speak() { +int APS5_VABI sceTextToSpeech2Speak(const TextToSpeechSpeakParameter* parameter) { return SCE_KERNEL_ERROR_EOPNOTSUPP; } diff --git a/core/libs/prx/libSceTextToSpeech2/include/Api.hpp b/core/libs/prx/libSceTextToSpeech2/include/Api.hpp new file mode 100644 index 0000000000..23340118f7 --- /dev/null +++ b/core/libs/prx/libSceTextToSpeech2/include/Api.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "SceTypes.hpp" + +struct TextToSpeechInitParameter { + std::uint32_t version; + std::int32_t threadPriority; + std::uint64_t cpuAffinity; + std::uint8_t reserved[48]; +}; + +struct TextToSpeechOpenParameter { + std::uint32_t language; + std::uint32_t voiceType; + std::uint8_t reserved[56]; +}; + +struct TextToSpeechSpeakParameter { + const char16_t* text; + std::uint32_t length; + std::uint32_t mode; + std::uint32_t volume; + std::int32_t speed; + std::uint8_t reserved[40]; +}; + +extern "C" { + +int APS5_VABI sceTextToSpeech2GetSystemStatus(std::int32_t* status); +std::int32_t APS5_VABI sceTextToSpeech2Initialize(const TextToSpeechInitParameter* param); +int APS5_VABI sceTextToSpeech2Cancel(); +int APS5_VABI sceTextToSpeech2Close(); +int APS5_VABI sceTextToSpeech2GetSpeechStatus(std::int32_t* status); +int APS5_VABI sceTextToSpeech2Open(const TextToSpeechOpenParameter* parameter); +int APS5_VABI sceTextToSpeech2Speak(const TextToSpeechSpeakParameter* parameter); +int APS5_VABI sceTextToSpeech2Terminate(); + +} diff --git a/core/libs/prx/libSceTextToSpeech2/libSceTextToSpeech2.hpp b/core/libs/prx/libSceTextToSpeech2/libSceTextToSpeech2.hpp new file mode 100644 index 0000000000..998b2dca7a --- /dev/null +++ b/core/libs/prx/libSceTextToSpeech2/libSceTextToSpeech2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceTextToSpeech2/include/Api.hpp" diff --git a/core/libs/prx/libSceTtsCoreG3Bc/libSceTtsCoreG3Bc.hpp b/core/libs/prx/libSceTtsCoreG3Bc/libSceTtsCoreG3Bc.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceTtsCoreG3Bc/libSceTtsCoreG3Bc.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceUlt/CMakeLists.txt b/core/libs/prx/libSceUlt/CMakeLists.txt index b93928088b..89ba849dcd 100644 --- a/core/libs/prx/libSceUlt/CMakeLists.txt +++ b/core/libs/prx/libSceUlt/CMakeLists.txt @@ -41,6 +41,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + add_test_executable(guest_ult_finalize_allocation_tests ${LIBS_INCLUDE_DIR}/tests/UltFinalizeAllocation.cpp Export.cpp src/ReaderWriterLock.cpp) + target_include_directories(guest_ult_finalize_allocation_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ult_finalize_allocation_tests PRIVATE libc libkernel) + configure_windows_unwind(guest_ult_finalize_allocation_tests) + add_test(NAME guest_ult_finalize_allocation COMMAND guest_ult_finalize_allocation_tests) + set_tests_properties(guest_ult_finalize_allocation PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_ult_finalize_allocation PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_ult_sync_tests ${LIBS_INCLUDE_DIR}/tests/GuestUltSync.cpp) target_include_directories(guest_ult_sync_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_ult_sync_tests PRIVATE libSceUlt libc) diff --git a/core/libs/prx/libSceUlt/Export.cpp b/core/libs/prx/libSceUlt/Export.cpp index 41b191fc65..311390abdc 100644 --- a/core/libs/prx/libSceUlt/Export.cpp +++ b/core/libs/prx/libSceUlt/Export.cpp @@ -1,13 +1,19 @@ +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceUlt/include/Api.hpp" #include #include +#include #include #include #include #include +#include #include #include #include #include +#include #include #include @@ -16,14 +22,8 @@ #include "SceUltTypes.hpp" #include "prx/libSceUlt/src/ReaderWriterLock.hpp" -extern "C" int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -extern "C" int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -extern "C" void APS5_VABI scePthreadExit(void* retval); -extern "C" void APS5_VABI scePthreadYield(); - namespace { - std::mutex gMutex; std::unordered_map> gMutexes; std::unordered_map> gConditions; @@ -34,8 +34,14 @@ std::unordered_map> gQueues; std::unordered_map gRuntimes; std::unordered_map> gUlthreads; -std::uint64_t alignUp(std::uint64_t value, std::uint64_t alignment) { - return (value + alignment - 1u) & ~(alignment - 1u); +int validateCommonOptParam(const void* optParam) { + if (optParam == nullptr) return ULT_OK; + if ((reinterpret_cast(optParam) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + UltCommonOptParam value; + std::memcpy(&value, optParam, sizeof(value)); + if (value.header[0] != 0x6C696275 || value.header[1] != 0x6C746D61 || value.header[2] != 0x67696300) return ULT_ERROR_INVALID; + if (std::any_of(std::begin(value.reserved), std::end(value.reserved), [](std::uint8_t byte) { return byte != 0; })) return ULT_ERROR_INVALID; + return ULT_OK; } int semaphoreGetState(void* semaphore, std::shared_ptr* out) { @@ -143,6 +149,7 @@ int APS5_VABI sceUltFinalize() { return ULT_ERROR_BUSY; } semaphores.reserve(gSemaphores.size()); + mutexes.reserve(gMutexes.size()); for (auto& entry : gSemaphores) { auto& state = entry.second; std::lock_guard stateLock(state->_mutex); @@ -150,7 +157,6 @@ int APS5_VABI sceUltFinalize() { semaphores.push_back(state); } gSemaphores.clear(); - mutexes.reserve(gMutexes.size()); for (auto& entry : gMutexes) { auto& state = entry.second; std::lock_guard stateLock(state->_mutex); @@ -188,11 +194,17 @@ int APS5_VABI sceUltFinalize() { } int APS5_VABI sceUltMutexOptParamInitialize(UltMutexOptParam* optParam, std::uint32_t buildVersion) { - (void)buildVersion; if (optParam == nullptr) { return ULT_ERROR_NULL; } + if ((reinterpret_cast(optParam) & 7u) != 0) { + return ULT_ERROR_ALIGNMENT; + } std::memset(optParam, 0, sizeof(*optParam)); + optParam->header[0] = 0x6C696275; + optParam->header[1] = 0x6C746D61; + optParam->header[2] = 0x67696300; + optParam->buildVersion = buildVersion; return ULT_OK; } @@ -221,6 +233,15 @@ int APS5_VABI sceUltUlthreadOptParamInitialize(UltOptParam* optParam, std::uint3 return InitializeOptParam(optParam); } +int APS5_VABI sceUltConditionVariableOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { + (void)buildVersion; + return InitializeOptParam(optParam); +} + +int APS5_VABI _sceUltConditionVariableOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { + return sceUltConditionVariableOptParamInitialize(optParam, buildVersion); +} + int APS5_VABI sceUltMutexCreate(void* mutex, const char* name, void* waitingQueueResourcePool, const UltMutexOptParam* optParam, std::uint32_t buildVersion) { (void)name; (void)buildVersion; @@ -254,7 +275,7 @@ int APS5_VABI sceUltMutexDestroy(void* mutex) { auto state = it->second; std::lock_guard stateLock(state->_mutex); if (state->_lockCount != 0 || state->_waiters != 0 || state->_conditions != 0 || state->_conditionWaiters != 0) { - return ULT_ERROR_BUSY; + return ULT_ERROR_STATE; } state->_alive = false; gMutexes.erase(it); @@ -277,6 +298,9 @@ int APS5_VABI sceUltMutexLock(void* mutex) { return ULT_ERROR_STATE; } if (state->_owner == self) { + if (state->_attribute == 0) { + return ULT_ERROR_STATE; + } ++state->_lockCount; return ULT_OK; } @@ -337,9 +361,12 @@ int APS5_VABI sceUltMutexUnlock(void* mutex) { } const auto self = std::this_thread::get_id(); std::lock_guard lock(state->_mutex); - if (!state->_alive || state->_owner != self) { + if (!state->_alive) { return ULT_ERROR_STATE; } + if (state->_owner != self) { + return ULT_ERROR_PERMISSION; + } --state->_lockCount; if (state->_lockCount == 0) { state->_owner = {}; @@ -348,7 +375,9 @@ int APS5_VABI sceUltMutexUnlock(void* mutex) { return ULT_OK; } -int APS5_VABI _sceUltConditionVariableCreate(void* condition, const char*, void* mutex, const void* optParam, std::uint32_t) { +int APS5_VABI sceUltConditionVariableCreate(void* condition, const char* name, void* mutex, const void* optParam, std::uint32_t buildVersion) { + (void)name; + (void)buildVersion; if (condition == nullptr || mutex == nullptr) return ULT_ERROR_NULL; if (optParam != nullptr) throw std::runtime_error("ULT condition variable options are not implemented"); std::lock_guard lock(gMutex); @@ -364,6 +393,10 @@ int APS5_VABI _sceUltConditionVariableCreate(void* condition, const char*, void* return ULT_OK; } +int APS5_VABI _sceUltConditionVariableCreate(void* condition, const char* name, void* mutex, const void* optParam, std::uint32_t buildVersion) { + return sceUltConditionVariableCreate(condition, name, mutex, optParam, buildVersion); +} + int APS5_VABI sceUltConditionVariableDestroy(void* condition) { std::lock_guard lock(gMutex); const auto it = gConditions.find(condition); @@ -443,8 +476,8 @@ int APS5_VABI sceUltConditionVariableWait(void* condition) { return ULT_OK; } -int APS5_VABI sceUltWaitingQueueResourcePoolGetWorkAreaSize(std::uint32_t numThreads, std::uint32_t numSyncObjects) { - return static_cast(alignUp(static_cast(numThreads + numSyncObjects) * 256u, 8u)); +std::uint64_t APS5_VABI sceUltWaitingQueueResourcePoolGetWorkAreaSize(std::uint32_t numThreads, std::uint32_t numSyncObjects) { + return 32ull * (numSyncObjects + 2u * numThreads + 2u); } int APS5_VABI sceUltWaitingQueueResourcePoolCreate(void* pool, const char* name, std::uint32_t numThreads, std::uint32_t numSyncObjects, void* workArea, const void* optParam, std::uint32_t buildVersion) { @@ -473,7 +506,7 @@ int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void* pool) { if (entry.second->_waitingPool == pool) { std::lock_guard stateLock(entry.second->_mutex); if (entry.second->_waiters != 0) { - return ULT_ERROR_BUSY; + return ULT_ERROR_STATE; } } } @@ -481,7 +514,7 @@ int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void* pool) { if (entry.second->_waitingPool == pool) { std::lock_guard stateLock(entry.second->_mutex); if (entry.second->_waiters != 0) { - return ULT_ERROR_BUSY; + return ULT_ERROR_STATE; } } } @@ -489,7 +522,7 @@ int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void* pool) { if (entry.second->_pool && entry.second->_pool->_waitingPool == pool) { std::lock_guard poolLock(entry.second->_pool->_mutex); if (entry.second->_waiters != 0) { - return ULT_ERROR_BUSY; + return ULT_ERROR_STATE; } } } @@ -501,14 +534,11 @@ int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void* pool) { } std::uint64_t APS5_VABI sceUltQueueDataResourcePoolGetWorkAreaSize(std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject) { - if (dataSize > UINT64_MAX - 7u) throw std::out_of_range("ULT queue data size overflow"); - const auto stride = alignUp(dataSize, 8u); - const auto queueArea = static_cast(numQueueObject) * 512u; - if (stride != 0 && numData > (UINT64_MAX - queueArea) / stride) { + const auto nodeArea = 32ull * (numQueueObject + 2u * numData + 3u); + if (dataSize != 0 && numData > (UINT64_MAX - nodeArea) / dataSize) { throw std::out_of_range("ULT queue work area size overflow"); } - const std::uint64_t dataArea = static_cast(numData) * stride; - return alignUp(dataArea + queueArea, 8u); + return static_cast(numData) * dataSize + nodeArea; } int APS5_VABI sceUltQueueDataResourcePoolCreate(void* pool, const char* name, std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject, void* waitingQueueResourcePool, void* workArea, const void* optParam, std::uint32_t buildVersion) { @@ -579,7 +609,7 @@ int APS5_VABI sceUltQueueCreate(void* queue, const char* name, std::uint64_t dat auto& pool = *state->_pool; std::lock_guard stateLock(pool._mutex); if (dataSize > pool._dataSize || waitingQueueResourcePool != pool._waitingPool) return ULT_ERROR_INVALID; - if (pool._queues == pool._numQueueObject) return ULT_ERROR_AGAIN; + if (pool._queues == pool._numQueueObject) return ULT_ERROR_BUSY; gQueues.emplace(queue, std::move(state)); ++pool._queues; std::memset(queue, 0, 512); @@ -611,7 +641,7 @@ int APS5_VABI sceUltQueueDestroy(void* queue) { auto& pool = *state->_pool; { std::lock_guard stateLock(pool._mutex); - if (state->_waiters != 0) return ULT_ERROR_BUSY; + if (state->_waiters != 0) return ULT_ERROR_STATE; ReleaseQueue(*state); gQueues.erase(it); } @@ -619,7 +649,7 @@ int APS5_VABI sceUltQueueDestroy(void* queue) { return ULT_OK; } -int APS5_VABI sceUltSemaphoreCreate(void* semaphore, const char* name, std::int32_t numInitialResource, void* waitingQueueResourcePool, const void* optParam, std::uint32_t buildVersion) { +int APS5_VABI sceUltSemaphoreCreate(void* semaphore, const char* name, std::uint32_t numInitialResource, void* waitingQueueResourcePool, const void* optParam, std::uint32_t buildVersion) { (void)name; (void)optParam; (void)buildVersion; @@ -629,11 +659,8 @@ int APS5_VABI sceUltSemaphoreCreate(void* semaphore, const char* name, std::int3 if ((reinterpret_cast(semaphore) & 7u) != 0) { return ULT_ERROR_ALIGNMENT; } - if (numInitialResource < 0) { - return ULT_ERROR_RANGE; - } auto state = std::make_shared(); - state->_resources = numInitialResource; + state->_resources = std::bit_cast(numInitialResource); state->_waitingPool = waitingQueueResourcePool; std::lock_guard lock(gMutex); if (waitingQueueResourcePool != nullptr && gResourcePools.find(waitingQueueResourcePool) == gResourcePools.end()) { @@ -648,6 +675,8 @@ int APS5_VABI sceUltSemaphoreCreate(void* semaphore, const char* name, std::int3 } int APS5_VABI sceUltSemaphoreAcquire(void* semaphore, std::int32_t numResource) { + if (semaphore == nullptr) return ULT_ERROR_NULL; + if ((reinterpret_cast(semaphore) & 7u) != 0) return ULT_ERROR_ALIGNMENT; if (numResource <= 0) { return ULT_ERROR_RANGE; } @@ -670,6 +699,8 @@ int APS5_VABI sceUltSemaphoreAcquire(void* semaphore, std::int32_t numResource) } int APS5_VABI sceUltSemaphoreTryAcquire(void* semaphore, std::int32_t numResource) { + if (semaphore == nullptr) return ULT_ERROR_NULL; + if ((reinterpret_cast(semaphore) & 7u) != 0) return ULT_ERROR_ALIGNMENT; if (numResource <= 0) { return ULT_ERROR_RANGE; } @@ -689,6 +720,8 @@ int APS5_VABI sceUltSemaphoreTryAcquire(void* semaphore, std::int32_t numResourc } int APS5_VABI sceUltSemaphoreRelease(void* semaphore, std::int32_t numResource) { + if (semaphore == nullptr) return ULT_ERROR_NULL; + if ((reinterpret_cast(semaphore) & 7u) != 0) return ULT_ERROR_ALIGNMENT; if (numResource <= 0) { return ULT_ERROR_RANGE; } @@ -711,9 +744,8 @@ int APS5_VABI sceUltSemaphoreRelease(void* semaphore, std::int32_t numResource) } int APS5_VABI sceUltSemaphoreDestroy(void* semaphore) { - if (semaphore == nullptr) { - return ULT_ERROR_NULL; - } + if (semaphore == nullptr) return ULT_ERROR_NULL; + if ((reinterpret_cast(semaphore) & 7u) != 0) return ULT_ERROR_ALIGNMENT; std::lock_guard globalLock(gMutex); auto it = gSemaphores.find(semaphore); if (it == gSemaphores.end()) { @@ -722,7 +754,7 @@ int APS5_VABI sceUltSemaphoreDestroy(void* semaphore) { auto state = it->second; std::lock_guard stateLock(state->_mutex); if (state->_waiters != 0) { - return ULT_ERROR_BUSY; + return ULT_ERROR_STATE; } state->_alive = false; gSemaphores.erase(it); @@ -739,7 +771,11 @@ int APS5_VABI sceUltUlthreadRuntimeOptParamInitialize(UltUlthreadRuntimeOptParam } std::uint64_t APS5_VABI sceUltUlthreadRuntimeGetWorkAreaSize(std::uint32_t maxNumUlthread, std::uint32_t numWorkerThread) { - return alignUp(static_cast(maxNumUlthread) * 256u + static_cast(numWorkerThread) * 16u * 1024u, 8u); + const std::uint32_t totalThreads = maxNumUlthread + numWorkerThread; + const auto threadNodes = 32ull * (2u * maxNumUlthread + 4u); + const auto threadArea = (threadNodes + 4ull * maxNumUlthread + 7u) & 0x3FFFFFFFF8ull; + const auto schedulerArea = 32ull * (2u * numWorkerThread + 4u) + 32ull * (2u * totalThreads + 4u); + return threadArea + schedulerArea + 512ull * numWorkerThread + 8ull * (static_cast(maxNumUlthread) + totalThreads); } int APS5_VABI sceUltUlthreadRuntimeCreate(void* runtime, const char* name, std::uint32_t maxNumUlthread, std::uint32_t numWorkerThread, void* workArea, const void* optParam, std::uint32_t buildVersion) { @@ -779,7 +815,8 @@ int APS5_VABI sceUltUlthreadCreate(void* ulthread, const char* name, UltUlthread std::memset(ulthread, 0, 512); gUlthreads[ulthread] = state; } - const int result = scePthreadCreate(&state->_thread, nullptr, ulthreadRunner, state.get(), name != nullptr ? name : ""); + const std::string threadName = name != nullptr ? std::string(name).substr(0, 31) : std::string(); + const int result = scePthreadCreate(&state->_thread, nullptr, ulthreadRunner, state.get(), threadName.c_str()); if (result != 0) { std::lock_guard lock(gMutex); gUlthreads.erase(ulthread); @@ -824,7 +861,7 @@ int APS5_VABI sceUltUlthreadTryJoin(void* ulthread, std::int32_t* status) { return ULT_ERROR_STATE; } if (!it->second->_exited.load(std::memory_order_acquire)) { - return ULT_ERROR_BUSY; + return ULT_ERROR_AGAIN; } } return sceUltUlthreadJoin(ulthread, status); @@ -846,6 +883,7 @@ int APS5_VABI sceUltUlthreadYield() { return ULT_ERROR_STATE; } scePthreadYield(); + return ULT_OK; } @@ -855,6 +893,7 @@ int APS5_VABI sceUltUlthreadExit(std::int32_t status) { } gCurrentUlthread->_exited.store(true, std::memory_order_release); scePthreadExit(reinterpret_cast(static_cast(status))); + return ULT_OK; } @@ -869,7 +908,7 @@ int APS5_VABI sceUltUlthreadRuntimeDestroy(void* runtime) { } for (const auto& entry : gUlthreads) { if (entry.second->_runtime == runtime) { - return ULT_ERROR_BUSY; + return ULT_ERROR_STATE; } } gRuntimes.erase(it); @@ -880,104 +919,139 @@ int APS5_VABI _sceUltWaitingQueueResourcePoolOptParamInitialize(UltOptParam* opt return sceUltWaitingQueueResourcePoolOptParamInitialize(optParam, buildVersion); } -int APS5_VABI _sceUltWaitingQueueResourcePoolCreate(void* pool, const char* name, std::uint32_t numThreads, std::uint32_t numSyncObjects, void* workArea, const void* optParam, std::uint32_t buildVersion) { - return sceUltWaitingQueueResourcePoolCreate(pool, name, numThreads, numSyncObjects, workArea, optParam, buildVersion); +int APS5_VABI _sceUltWaitingQueueResourcePoolCreate(void* pool, const char* name, std::uint32_t numThreads, std::uint32_t numSyncObjects, void* workArea, const void* optParam) { + if (pool == nullptr) return ULT_ERROR_NULL; + if ((reinterpret_cast(pool) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + if (name == nullptr) return ULT_ERROR_NULL; + if (const int result = validateCommonOptParam(optParam); result != ULT_OK) return result; + if (numThreads == 0 || numSyncObjects == 0) return ULT_ERROR_INVALID; + if ((reinterpret_cast(workArea) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + if (workArea == nullptr) return ULT_ERROR_NULL; + return sceUltWaitingQueueResourcePoolCreate(pool, name, numThreads, numSyncObjects, workArea, optParam, 0); } int APS5_VABI _sceUltSemaphoreOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { return sceUltSemaphoreOptParamInitialize(optParam, buildVersion); } -int APS5_VABI _sceUltSemaphoreCreate(void* semaphore, const char* name, std::int32_t numInitialResource, void* waitingQueueResourcePool, const void* optParam, std::uint32_t buildVersion) { - return sceUltSemaphoreCreate(semaphore, name, numInitialResource, waitingQueueResourcePool, optParam, buildVersion); +int APS5_VABI _sceUltSemaphoreCreate(void* semaphore, const char* name, std::uint32_t numInitialResource, void* waitingQueueResourcePool, const void* optParam) { + if (semaphore == nullptr || name == nullptr) return ULT_ERROR_NULL; + if (((reinterpret_cast(semaphore) | reinterpret_cast(waitingQueueResourcePool)) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + if (const int result = validateCommonOptParam(optParam); result != ULT_OK) return result; + return sceUltSemaphoreCreate(semaphore, name, numInitialResource, waitingQueueResourcePool, optParam, 0); } int APS5_VABI _sceUltMutexOptParamInitialize(UltMutexOptParam* optParam, std::uint32_t buildVersion) { return sceUltMutexOptParamInitialize(optParam, buildVersion); } -int APS5_VABI _sceUltMutexCreate(void* mutex, const char* name, void* waitingQueueResourcePool, const UltMutexOptParam* optParam, std::uint32_t buildVersion) { - return sceUltMutexCreate(mutex, name, waitingQueueResourcePool, optParam, buildVersion); +int APS5_VABI _sceUltMutexCreate(void* mutex, const char* name, void* waitingQueueResourcePool, const UltMutexOptParam* optParam) { + if (mutex == nullptr || name == nullptr || waitingQueueResourcePool == nullptr) { + return ULT_ERROR_NULL; + } + if (((reinterpret_cast(mutex) | reinterpret_cast(optParam) | reinterpret_cast(waitingQueueResourcePool)) & 7u) != 0) { + return ULT_ERROR_ALIGNMENT; + } + if (optParam != nullptr && (optParam->header[0] != 0x6C696275 || optParam->header[1] != 0x6C746D61 || optParam->header[2] != 0x67696300 || optParam->attribute > 1 || std::any_of(std::begin(optParam->reserved), std::end(optParam->reserved), [](std::uint32_t value) { return value != 0; }))) { + return ULT_ERROR_INVALID; + } + return sceUltMutexCreate(mutex, name, waitingQueueResourcePool, optParam, 0); } int APS5_VABI _sceUltQueueDataResourcePoolOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { return sceUltQueueDataResourcePoolOptParamInitialize(optParam, buildVersion); } -int APS5_VABI _sceUltQueueDataResourcePoolCreate(void* pool, const char* name, std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject, void* waitingQueueResourcePool, void* workArea, const void* optParam, std::uint32_t buildVersion) { - return sceUltQueueDataResourcePoolCreate(pool, name, numData, dataSize, numQueueObject, waitingQueueResourcePool, workArea, optParam, buildVersion); +int APS5_VABI _sceUltQueueDataResourcePoolCreate(void* pool, const char* name, std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject, void* waitingQueueResourcePool, void* workArea, const void* optParam) { + if (pool == nullptr) return ULT_ERROR_NULL; + if ((reinterpret_cast(pool) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + if (const int result = validateCommonOptParam(optParam); result != ULT_OK) return result; + if (numData == 0 || dataSize == 0 || numQueueObject == 0) return ULT_ERROR_INVALID; + if ((reinterpret_cast(workArea) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + if (workArea == nullptr || name == nullptr) return ULT_ERROR_NULL; + if ((reinterpret_cast(waitingQueueResourcePool) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + return sceUltQueueDataResourcePoolCreate(pool, name, numData, dataSize, numQueueObject, waitingQueueResourcePool, workArea, optParam, 0); } int APS5_VABI _sceUltQueueOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { return sceUltQueueOptParamInitialize(optParam, buildVersion); } -int APS5_VABI _sceUltQueueCreate(void* queue, const char* name, std::uint64_t dataSize, void* waitingQueueResourcePool, void* queueDataResourcePool, const void* optParam, std::uint32_t buildVersion) { - return sceUltQueueCreate(queue, name, dataSize, waitingQueueResourcePool, queueDataResourcePool, optParam, buildVersion); +int APS5_VABI _sceUltQueueCreate(void* queue, const char* name, std::uint64_t dataSize, void* waitingQueueResourcePool, void* queueDataResourcePool, const void* optParam) { + if (queue == nullptr || name == nullptr || queueDataResourcePool == nullptr) return ULT_ERROR_NULL; + if (((reinterpret_cast(queue) | reinterpret_cast(waitingQueueResourcePool) | reinterpret_cast(queueDataResourcePool)) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + { + std::lock_guard lock(gMutex); + const auto it = gQueueDataPools.find(queueDataResourcePool); + if (it == gQueueDataPools.end()) return ULT_ERROR_INVALID; + if (dataSize == 0 || dataSize > it->second->_dataSize) return ULT_ERROR_INVALID; + } + if (const int result = validateCommonOptParam(optParam); result != ULT_OK) return result; + return sceUltQueueCreate(queue, name, dataSize, waitingQueueResourcePool, queueDataResourcePool, optParam, 0); } int APS5_VABI _sceUltUlthreadRuntimeOptParamInitialize(UltUlthreadRuntimeOptParam* optParam, std::uint32_t buildVersion) { return sceUltUlthreadRuntimeOptParamInitialize(optParam, buildVersion); } -int APS5_VABI _sceUltUlthreadRuntimeCreate(void* runtime, const char* name, std::uint32_t maxNumUlthread, std::uint32_t numWorkerThread, void* workArea, const void* optParam, std::uint32_t buildVersion) { - return sceUltUlthreadRuntimeCreate(runtime, name, maxNumUlthread, numWorkerThread, workArea, optParam, buildVersion); +int APS5_VABI _sceUltUlthreadRuntimeCreate(void* runtime, const char* name, std::uint32_t maxNumUlthread, std::uint32_t numWorkerThread, void* workArea, const void* optParam) { + return sceUltUlthreadRuntimeCreate(runtime, name, maxNumUlthread, numWorkerThread, workArea, optParam, 0); } int APS5_VABI _sceUltUlthreadOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { return sceUltUlthreadOptParamInitialize(optParam, buildVersion); } -int APS5_VABI _sceUltUlthreadCreate(void* ulthread, const char* name, UltUlthreadEntry entry, std::uint64_t arg, void* context, std::uint64_t sizeContext, void* runtime, const void* optParam, std::uint32_t buildVersion) { - return sceUltUlthreadCreate(ulthread, name, entry, arg, context, sizeContext, runtime, optParam, buildVersion); +int APS5_VABI _sceUltUlthreadCreate(void* ulthread, const char* name, UltUlthreadEntry entry, std::uint64_t arg, void* context, std::uint64_t sizeContext, void* runtime, const void* optParam) { + return sceUltUlthreadCreate(ulthread, name, entry, arg, context, sizeContext, runtime, optParam, 0); } -int APS5_VABI sceUltConditionVariableSignalAll() { +int APS5_VABI sceUltConditionVariableSignalAll(void* conditionVariable) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUltGetConditionVariableInfo() { +int APS5_VABI sceUltGetConditionVariableInfo(void* object, UltConditionVariableInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUltGetMutexInfo() { +int APS5_VABI sceUltGetMutexInfo(void* object, UltMutexInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUltGetQueueDataResourcePoolInfo() { +int APS5_VABI sceUltGetQueueDataResourcePoolInfo(void* object, UltQueueDataResourcePoolInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUltGetQueueInfo() { +int APS5_VABI sceUltGetQueueInfo(void* object, UltQueueInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUltGetReaderWriterLockInfo() { +int APS5_VABI sceUltGetReaderWriterLockInfo(void* object, UltReaderWriterLockInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUltGetSemaphoreInfo() { +int APS5_VABI sceUltGetSemaphoreInfo(void* object, UltSemaphoreInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUltGetUlthreadInfo() { +int APS5_VABI sceUltGetUlthreadInfo(void* object, UltUlthreadInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUltGetUlthreadRuntimeInfo() { +int APS5_VABI sceUltGetUlthreadRuntimeInfo(void* runtime, UltUlthreadRuntimeInfo* info, void** ulthreads, std::uint32_t ulthreadCapacity, Pthread* workerThreads, std::uint32_t workerCapacity) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUltGetWaitingQueueResourcePoolInfo() { +int APS5_VABI sceUltGetWaitingQueueResourcePoolInfo(void* object, UltWaitingQueueResourcePoolInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1042,20 +1116,4 @@ int APS5_VABI sceUltUlthreadFactoryInitialize() { return 0; } -int APS5_VABI sceUltConditionVariableCreate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceUltConditionVariableOptParamInitialize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - - -int APS5_VABI _sceUltConditionVariableOptParamInitialize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceUlt/SceUltTypes.hpp b/core/libs/prx/libSceUlt/SceUltTypes.hpp index efe53ec5a8..4c77af1649 100644 --- a/core/libs/prx/libSceUlt/SceUltTypes.hpp +++ b/core/libs/prx/libSceUlt/SceUltTypes.hpp @@ -5,6 +5,8 @@ #include #include #include + +#include #include #include #include @@ -17,6 +19,7 @@ constexpr int ULT_ERROR_NULL = -2139029503; constexpr int ULT_ERROR_ALIGNMENT = -2139029502; constexpr int ULT_ERROR_RANGE = -2139029501; constexpr int ULT_ERROR_INVALID = -2139029500; +constexpr int ULT_ERROR_PERMISSION = -2139029499; constexpr int ULT_ERROR_STATE = -2139029498; constexpr int ULT_ERROR_BUSY = -2139029497; constexpr int ULT_ERROR_AGAIN = -2139029496; @@ -33,6 +36,235 @@ inline int InitializeOptParam(UltOptParam* optParam) { return ULT_OK; } +struct alignas(8) UltCommonOptParam { + std::uint32_t header[3]; + std::uint32_t buildVersion; + std::uint8_t reserved[112]; +}; + +static_assert(sizeof(UltCommonOptParam) == 128); + +struct UltWaitingThreadInfo { + std::uint64_t size; + void* thread; + std::uint32_t threadType; + std::int32_t waitValue; + std::byte reserved[40]; +}; + +static_assert(sizeof(UltWaitingThreadInfo) == 64); +static_assert(offsetof(UltWaitingThreadInfo, size) == 0); +static_assert(offsetof(UltWaitingThreadInfo, thread) == 8); +static_assert(offsetof(UltWaitingThreadInfo, threadType) == 16); +static_assert(offsetof(UltWaitingThreadInfo, waitValue) == 20); +static_assert(offsetof(UltWaitingThreadInfo, reserved) == 24); + +struct UltConditionVariableInfo { + std::uint64_t size; + void* object; + char name[32]; + void* mutex; + std::uint32_t waitingThreads; + std::byte reserved[68]; +}; + +static_assert(sizeof(UltConditionVariableInfo) == 128); +static_assert(offsetof(UltConditionVariableInfo, size) == 0); +static_assert(offsetof(UltConditionVariableInfo, object) == 8); +static_assert(offsetof(UltConditionVariableInfo, name) == 16); +static_assert(offsetof(UltConditionVariableInfo, mutex) == 48); +static_assert(offsetof(UltConditionVariableInfo, waitingThreads) == 56); +static_assert(offsetof(UltConditionVariableInfo, reserved) == 60); + +struct UltMutexInfo { + std::uint64_t size; + void* object; + char name[32]; + void* waitingQueuePool; + void* owner; + std::uint32_t ownerType; + std::uint32_t attributes; + std::uint32_t lockCount; + std::uint32_t waitingThreads; + std::byte reserved[48]; +}; + +static_assert(sizeof(UltMutexInfo) == 128); +static_assert(offsetof(UltMutexInfo, size) == 0); +static_assert(offsetof(UltMutexInfo, object) == 8); +static_assert(offsetof(UltMutexInfo, name) == 16); +static_assert(offsetof(UltMutexInfo, waitingQueuePool) == 48); +static_assert(offsetof(UltMutexInfo, owner) == 56); +static_assert(offsetof(UltMutexInfo, ownerType) == 64); +static_assert(offsetof(UltMutexInfo, attributes) == 68); +static_assert(offsetof(UltMutexInfo, lockCount) == 72); +static_assert(offsetof(UltMutexInfo, waitingThreads) == 76); +static_assert(offsetof(UltMutexInfo, reserved) == 80); + +struct UltSemaphoreInfo { + std::uint64_t size; + void* object; + char name[32]; + void* waitingQueuePool; + std::uint32_t resources; + std::uint32_t waitingThreads; + std::byte reserved[64]; +}; + +static_assert(sizeof(UltSemaphoreInfo) == 128); +static_assert(offsetof(UltSemaphoreInfo, size) == 0); +static_assert(offsetof(UltSemaphoreInfo, object) == 8); +static_assert(offsetof(UltSemaphoreInfo, name) == 16); +static_assert(offsetof(UltSemaphoreInfo, waitingQueuePool) == 48); +static_assert(offsetof(UltSemaphoreInfo, resources) == 56); +static_assert(offsetof(UltSemaphoreInfo, waitingThreads) == 60); +static_assert(offsetof(UltSemaphoreInfo, reserved) == 64); + +struct UltReaderWriterLockInfo { + std::uint64_t size; + void* object; + char name[32]; + void* waitingQueuePool; + std::uint32_t lockType; + std::uint32_t readers; + void* owner; + std::uint32_t ownerType; + std::uint32_t waitingThreads; + std::byte reserved[48]; +}; + +static_assert(sizeof(UltReaderWriterLockInfo) == 128); +static_assert(offsetof(UltReaderWriterLockInfo, size) == 0); +static_assert(offsetof(UltReaderWriterLockInfo, object) == 8); +static_assert(offsetof(UltReaderWriterLockInfo, name) == 16); +static_assert(offsetof(UltReaderWriterLockInfo, waitingQueuePool) == 48); +static_assert(offsetof(UltReaderWriterLockInfo, lockType) == 56); +static_assert(offsetof(UltReaderWriterLockInfo, readers) == 60); +static_assert(offsetof(UltReaderWriterLockInfo, owner) == 64); +static_assert(offsetof(UltReaderWriterLockInfo, ownerType) == 72); +static_assert(offsetof(UltReaderWriterLockInfo, waitingThreads) == 76); +static_assert(offsetof(UltReaderWriterLockInfo, reserved) == 80); + +struct UltQueueInfo { + std::uint64_t size; + void* object; + char name[32]; + void* waitingQueuePool; + void* dataPool; + std::uint64_t dataSize; + std::uint32_t queuedItems; + std::uint32_t waitingPushers; + std::uint32_t waitingPoppers; + std::byte reserved[172]; +}; + +static_assert(sizeof(UltQueueInfo) == 256); +static_assert(offsetof(UltQueueInfo, size) == 0); +static_assert(offsetof(UltQueueInfo, object) == 8); +static_assert(offsetof(UltQueueInfo, name) == 16); +static_assert(offsetof(UltQueueInfo, waitingQueuePool) == 48); +static_assert(offsetof(UltQueueInfo, dataPool) == 56); +static_assert(offsetof(UltQueueInfo, dataSize) == 64); +static_assert(offsetof(UltQueueInfo, queuedItems) == 72); +static_assert(offsetof(UltQueueInfo, waitingPushers) == 76); +static_assert(offsetof(UltQueueInfo, waitingPoppers) == 80); +static_assert(offsetof(UltQueueInfo, reserved) == 84); + +struct UltWaitingQueueResourcePoolInfo { + std::uint64_t size; + void* object; + char name[32]; + std::uint32_t availableThreads; + std::uint32_t maxThreads; + std::uint32_t availableSyncObjects; + std::uint32_t maxSyncObjects; + std::byte reserved[64]; +}; + +static_assert(sizeof(UltWaitingQueueResourcePoolInfo) == 128); +static_assert(offsetof(UltWaitingQueueResourcePoolInfo, size) == 0); +static_assert(offsetof(UltWaitingQueueResourcePoolInfo, object) == 8); +static_assert(offsetof(UltWaitingQueueResourcePoolInfo, name) == 16); +static_assert(offsetof(UltWaitingQueueResourcePoolInfo, availableThreads) == 48); +static_assert(offsetof(UltWaitingQueueResourcePoolInfo, maxThreads) == 52); +static_assert(offsetof(UltWaitingQueueResourcePoolInfo, availableSyncObjects) == 56); +static_assert(offsetof(UltWaitingQueueResourcePoolInfo, maxSyncObjects) == 60); +static_assert(offsetof(UltWaitingQueueResourcePoolInfo, reserved) == 64); + +struct UltQueueDataResourcePoolInfo { + std::uint64_t size; + void* object; + char name[32]; + void* waitingQueuePool; + std::uint64_t dataSize; + std::uint32_t availableItems; + std::uint32_t maxItems; + std::uint32_t availableQueues; + std::uint32_t maxQueues; + std::byte reserved[48]; +}; + +static_assert(sizeof(UltQueueDataResourcePoolInfo) == 128); +static_assert(offsetof(UltQueueDataResourcePoolInfo, size) == 0); +static_assert(offsetof(UltQueueDataResourcePoolInfo, object) == 8); +static_assert(offsetof(UltQueueDataResourcePoolInfo, name) == 16); +static_assert(offsetof(UltQueueDataResourcePoolInfo, waitingQueuePool) == 48); +static_assert(offsetof(UltQueueDataResourcePoolInfo, dataSize) == 56); +static_assert(offsetof(UltQueueDataResourcePoolInfo, availableItems) == 64); +static_assert(offsetof(UltQueueDataResourcePoolInfo, maxItems) == 68); +static_assert(offsetof(UltQueueDataResourcePoolInfo, availableQueues) == 72); +static_assert(offsetof(UltQueueDataResourcePoolInfo, maxQueues) == 76); +static_assert(offsetof(UltQueueDataResourcePoolInfo, reserved) == 80); + +struct UltUlthreadRuntimeInfo { + std::uint64_t size; + void* object; + char name[32]; + std::uint32_t activeUlthreads; + std::uint32_t maxUlthreads; + std::uint32_t workerThreads; + std::uint32_t reserved60; + std::uint64_t workerStackSize; + std::byte reserved[56]; +}; + +static_assert(sizeof(UltUlthreadRuntimeInfo) == 128); +static_assert(offsetof(UltUlthreadRuntimeInfo, size) == 0); +static_assert(offsetof(UltUlthreadRuntimeInfo, object) == 8); +static_assert(offsetof(UltUlthreadRuntimeInfo, name) == 16); +static_assert(offsetof(UltUlthreadRuntimeInfo, maxUlthreads) == 52); +static_assert(offsetof(UltUlthreadRuntimeInfo, activeUlthreads) == 48); +static_assert(offsetof(UltUlthreadRuntimeInfo, workerThreads) == 56); +static_assert(offsetof(UltUlthreadRuntimeInfo, reserved60) == 60); +static_assert(offsetof(UltUlthreadRuntimeInfo, workerStackSize) == 64); +static_assert(offsetof(UltUlthreadRuntimeInfo, reserved) == 72); + +struct UltUlthreadInfo { + std::uint64_t size; + void* object; + char name[32]; + std::uint32_t contextAttribute; + std::uint32_t reserved52; + void* runtime; + FiberInfo fiber; + std::byte reserved[64]; +}; + +static_assert(sizeof(FiberInfo) == 128); +static_assert(offsetof(FiberInfo, entry) == 8); +static_assert(offsetof(FiberInfo, arg_on_initialize) == 16); +static_assert(offsetof(FiberInfo, addr_context) == 24); +static_assert(offsetof(FiberInfo, size_context) == 32); +static_assert(offsetof(FiberInfo, name) == 40); +static_assert(offsetof(FiberInfo, size_context_margin) == 72); +static_assert(sizeof(UltUlthreadInfo) == 256); +static_assert(offsetof(UltUlthreadInfo, object) == 8); +static_assert(offsetof(UltUlthreadInfo, name) == 16); +static_assert(offsetof(UltUlthreadInfo, contextAttribute) == 48); +static_assert(offsetof(UltUlthreadInfo, runtime) == 56); +static_assert(offsetof(UltUlthreadInfo, fiber) == 64); +static_assert(offsetof(UltUlthreadInfo, reserved) == 192); + struct UltMutexState { std::mutex _mutex; std::condition_variable _available; diff --git a/core/libs/prx/libSceUlt/include/Api.hpp b/core/libs/prx/libSceUlt/include/Api.hpp new file mode 100644 index 0000000000..c4c3213f0a --- /dev/null +++ b/core/libs/prx/libSceUlt/include/Api.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include "prx/libSceUlt/SceUltTypes.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceUltInitialize(); +int APS5_VABI sceUltFinalize(); +int APS5_VABI sceUltMutexOptParamInitialize(UltMutexOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltSemaphoreOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltQueueOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltQueueDataResourcePoolOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltWaitingQueueResourcePoolOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltUlthreadOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltConditionVariableOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI _sceUltConditionVariableOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltMutexCreate(void* mutex, const char* name, void* waitingQueueResourcePool, const UltMutexOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltMutexDestroy(void* mutex); +int APS5_VABI sceUltMutexLock(void* mutex); +int APS5_VABI sceUltMutexTryLock(void* mutex); +int APS5_VABI sceUltMutexUnlock(void* mutex); +int APS5_VABI sceUltConditionVariableCreate(void* condition, const char* name, void* mutex, const void* optParam, std::uint32_t buildVersion); +int APS5_VABI _sceUltConditionVariableCreate(void* condition, const char* name, void* mutex, const void* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltConditionVariableDestroy(void* condition); +int APS5_VABI sceUltConditionVariableSignal(void* condition); +int APS5_VABI sceUltConditionVariableWait(void* condition); +std::uint64_t APS5_VABI sceUltWaitingQueueResourcePoolGetWorkAreaSize(std::uint32_t numThreads, std::uint32_t numSyncObjects); +int APS5_VABI sceUltWaitingQueueResourcePoolCreate(void* pool, const char* name, std::uint32_t numThreads, std::uint32_t numSyncObjects, void* workArea, const void* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void* pool); +std::uint64_t APS5_VABI sceUltQueueDataResourcePoolGetWorkAreaSize(std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject); +int APS5_VABI sceUltQueueDataResourcePoolCreate(void* pool, const char* name, std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject, void* waitingQueueResourcePool, void* workArea, const void* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltQueueDataResourcePoolDestroy(void* pool); +int APS5_VABI sceUltQueueCreate(void* queue, const char* name, std::uint64_t dataSize, void* waitingQueueResourcePool, void* queueDataResourcePool, const void* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltQueuePush(void* queue, const void* data); +int APS5_VABI sceUltQueueTryPush(void* queue, const void* data); +int APS5_VABI sceUltQueuePop(void* queue, void* data); +int APS5_VABI sceUltQueueTryPop(void* queue, void* data); +int APS5_VABI sceUltQueueDestroy(void* queue); +int APS5_VABI sceUltSemaphoreCreate(void* semaphore, const char* name, std::uint32_t numInitialResource, void* waitingQueueResourcePool, const void* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltSemaphoreAcquire(void* semaphore, std::int32_t numResource); +int APS5_VABI sceUltSemaphoreTryAcquire(void* semaphore, std::int32_t numResource); +int APS5_VABI sceUltSemaphoreRelease(void* semaphore, std::int32_t numResource); +int APS5_VABI sceUltSemaphoreDestroy(void* semaphore); +int APS5_VABI sceUltUlthreadRuntimeOptParamInitialize(UltUlthreadRuntimeOptParam* optParam, std::uint32_t buildVersion); +std::uint64_t APS5_VABI sceUltUlthreadRuntimeGetWorkAreaSize(std::uint32_t maxNumUlthread, std::uint32_t numWorkerThread); +int APS5_VABI sceUltUlthreadRuntimeCreate(void* runtime, const char* name, std::uint32_t maxNumUlthread, std::uint32_t numWorkerThread, void* workArea, const void* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltUlthreadCreate(void* ulthread, const char* name, UltUlthreadEntry entry, std::uint64_t arg, void* context, std::uint64_t sizeContext, void* runtime, const void* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltUlthreadJoin(void* ulthread, std::int32_t* status); +int APS5_VABI sceUltUlthreadTryJoin(void* ulthread, std::int32_t* status); +int APS5_VABI sceUltUlthreadGetSelf(void** ulthread); +int APS5_VABI sceUltUlthreadYield(); +int APS5_VABI sceUltUlthreadExit(std::int32_t status); +int APS5_VABI sceUltUlthreadRuntimeDestroy(void* runtime); +int APS5_VABI _sceUltWaitingQueueResourcePoolCreate(void* pool, const char* name, std::uint32_t numThreads, std::uint32_t numSyncObjects, void* workArea, const void* optParam); +int APS5_VABI _sceUltSemaphoreCreate(void* semaphore, const char* name, std::uint32_t numInitialResource, void* waitingQueueResourcePool, const void* optParam); +int APS5_VABI _sceUltMutexOptParamInitialize(UltMutexOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI _sceUltMutexCreate(void* mutex, const char* name, void* waitingQueueResourcePool, const UltMutexOptParam* optParam); +int APS5_VABI _sceUltQueueDataResourcePoolCreate(void* pool, const char* name, std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject, void* waitingQueueResourcePool, void* workArea, const void* optParam); +int APS5_VABI _sceUltQueueCreate(void* queue, const char* name, std::uint64_t dataSize, void* waitingQueueResourcePool, void* queueDataResourcePool, const void* optParam); +int APS5_VABI _sceUltUlthreadRuntimeOptParamInitialize(UltUlthreadRuntimeOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI _sceUltUlthreadRuntimeCreate(void* runtime, const char* name, std::uint32_t maxNumUlthread, std::uint32_t numWorkerThread, void* workArea, const void* optParam); +int APS5_VABI _sceUltUlthreadCreate(void* ulthread, const char* name, UltUlthreadEntry entry, std::uint64_t arg, void* context, std::uint64_t sizeContext, void* runtime, const void* optParam); +int APS5_VABI sceUltConditionVariableSignalAll(void* conditionVariable); +int APS5_VABI sceUltGetConditionVariableInfo(void* object, UltConditionVariableInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity); +int APS5_VABI sceUltGetMutexInfo(void* object, UltMutexInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity); +int APS5_VABI sceUltGetQueueDataResourcePoolInfo(void* object, UltQueueDataResourcePoolInfo* info); +int APS5_VABI sceUltGetQueueInfo(void* object, UltQueueInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity); +int APS5_VABI sceUltGetReaderWriterLockInfo(void* object, UltReaderWriterLockInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity); +int APS5_VABI sceUltGetSemaphoreInfo(void* object, UltSemaphoreInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity); +int APS5_VABI sceUltGetUlthreadInfo(void* object, UltUlthreadInfo* info); +int APS5_VABI sceUltGetUlthreadRuntimeInfo(void* runtime, UltUlthreadRuntimeInfo* info, void** ulthreads, std::uint32_t ulthreadCapacity, Pthread* workerThreads, std::uint32_t workerCapacity); +int APS5_VABI sceUltGetWaitingQueueResourcePoolInfo(void* object, UltWaitingQueueResourcePoolInfo* info); +int APS5_VABI sceUltAbstractThreadFactoryCreateThread(); +int APS5_VABI sceUltAbstractThreadFactoryGetNumMaxThreads(); +int APS5_VABI sceUltAbstractThreadFactoryJoinThread(); +int APS5_VABI sceUltKernelThreadFactoryFinalize(); +int APS5_VABI sceUltKernelThreadFactoryGetFactoryPointer(); +int APS5_VABI sceUltKernelThreadFactoryInitialize(); +int APS5_VABI sceUltModuleFinalize(); +int APS5_VABI sceUltModuleInitialize(); +int APS5_VABI sceUltUlthreadFactoryFinalize(); +int APS5_VABI sceUltUlthreadFactoryGetFactoryPointer(); +int APS5_VABI sceUltUlthreadFactoryGetWorkAreaSize(); +int APS5_VABI sceUltUlthreadFactoryInitialize(); +int APS5_VABI sceUltReaderWriterLockOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltReaderWriterLockCreate(void* rwlock, const char* name, void* waitingQueueResourcePool, const UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI sceUltReaderWriterLockDestroy(void* rwlock); +int APS5_VABI sceUltReaderWriterLockLockRead(void* rwlock); +int APS5_VABI sceUltReaderWriterLockTryLockRead(void* rwlock); +int APS5_VABI sceUltReaderWriterLockUnlockRead(void* rwlock); +int APS5_VABI sceUltReaderWriterLockLockWrite(void* rwlock); +int APS5_VABI sceUltReaderWriterLockTryLockWrite(void* rwlock); +int APS5_VABI sceUltReaderWriterLockUnlockWrite(void* rwlock); + +} diff --git a/core/libs/prx/libSceUlt/libSceUlt.hpp b/core/libs/prx/libSceUlt/libSceUlt.hpp new file mode 100644 index 0000000000..260a68efda --- /dev/null +++ b/core/libs/prx/libSceUlt/libSceUlt.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceUlt/include/Api.hpp" diff --git a/core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp b/core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp index 71fd44dcbf..8c8bafa602 100644 --- a/core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp +++ b/core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceUlt/include/Api.hpp" #include "prx/libSceUlt/src/ReaderWriterLock.hpp" #include "prx/libSceUlt/SceUltTypes.hpp" #include @@ -107,7 +108,8 @@ int APS5_VABI sceUltReaderWriterLockDestroy(void* rwlock) { if (it == gLocks.end()) { return ULT_ERROR_STATE; } - auto& state = *it->second; + const auto owner = it->second; + auto& state = *owner; std::lock_guard stateLock(state._mutex); if (state._readers != 0 || state._writing || state._waiters != 0) { return ULT_ERROR_BUSY; diff --git a/core/libs/prx/libSceUpdateService/Export.cpp b/core/libs/prx/libSceUpdateService/Export.cpp index 7fc0488786..d434762726 100644 --- a/core/libs/prx/libSceUpdateService/Export.cpp +++ b/core/libs/prx/libSceUpdateService/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceUpdateService/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceUpdateService/include/Api.hpp b/core/libs/prx/libSceUpdateService/include/Api.hpp new file mode 100644 index 0000000000..9dce013b86 --- /dev/null +++ b/core/libs/prx/libSceUpdateService/include/Api.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceUpsrvGetCompletedTaskId(); +int APS5_VABI sceUpsrvGetRebootFlag(); +int APS5_VABI sceUpsrvGetSystemExUpdateTaskState(); +int APS5_VABI sceUpsrvInitialize(); +int APS5_VABI sceUpsrvPostErrorLog(); +int APS5_VABI sceUpsrvResumePausedTask(); +int APS5_VABI sceUpsrvSetFakeVersion(); +int APS5_VABI sceUpsrvStartUpdateSession(); +int APS5_VABI sceUpsrvSwitchSystemExBank(); +int APS5_VABI sceUpsrvSwitchSystemExBankWithoutVerify(); +int APS5_VABI sceUpsrvTerminate(); +int APS5_VABI sceUpsrvUpdateCancelBDUpdate(); +int APS5_VABI sceUpsrvUpdateCheckCancel(); +int APS5_VABI sceUpsrvUpdateCheckCreateHandler(); +int APS5_VABI sceUpsrvUpdateCheckDestroyHandler(); +int APS5_VABI sceUpsrvUpdateCheckDoCheckSystem(); +int APS5_VABI sceUpsrvUpdateCheckDoCheckSystemForDevKit(); +int APS5_VABI sceUpsrvUpdateCheckDoCheckSystemForSettings(); +int APS5_VABI sceUpsrvUpdateCheckDoCheckSystemWithPupInfo(); +int APS5_VABI sceUpsrvUpdateCreateUpdateTask(); +int APS5_VABI sceUpsrvUpdateDestroyUpdateTask(); +int APS5_VABI sceUpsrvUpdateDoCheckBootUpdate(); +int APS5_VABI sceUpsrvUpdateDoUpdateSystemEx(); +int APS5_VABI sceUpsrvUpdateDoUpdateWithEntitlement(); +int APS5_VABI sceUpsrvUpdateDoUpdateWithPupInfo(); +int APS5_VABI sceUpsrvUpdateDoUpdateWithPupPath2(); +int APS5_VABI sceUpsrvUpdateGetBetaAgreementUrl(); +int APS5_VABI sceUpsrvUpdateGetEulaDisplay(); +int APS5_VABI sceUpsrvUpdateGetEulaStr(); +int APS5_VABI sceUpsrvUpdateGetEulaStrLen(); +int APS5_VABI sceUpsrvUpdateGetFeatureListUrl(); +int APS5_VABI sceUpsrvUpdateGetFeatureListUrlForSystemExUpdate(); +int APS5_VABI sceUpsrvUpdateGetFeatureListUrlForUpdate(); +int APS5_VABI sceUpsrvUpdateGetImageWritingProgress(); +int APS5_VABI sceUpsrvUpdateGetProgress(); +int APS5_VABI sceUpsrvUpdateGetUpdateTaskInfo(); +int APS5_VABI sceUpsrvUpdateGetUpdateTaskMgrInfo(); +int APS5_VABI sceUpsrvUpdateSetEulaAccept(); +int APS5_VABI sceUpsrvUpdateStartUpdateTask(); +int APS5_VABI sceUpsrvVerifySystemExBank(); + +} diff --git a/core/libs/prx/libSceUpdateService/libSceUpdateService.hpp b/core/libs/prx/libSceUpdateService/libSceUpdateService.hpp new file mode 100644 index 0000000000..87a15733f7 --- /dev/null +++ b/core/libs/prx/libSceUpdateService/libSceUpdateService.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceUpdateService/include/Api.hpp" diff --git a/core/libs/prx/libSceUrlConfigResolver/Export.cpp b/core/libs/prx/libSceUrlConfigResolver/Export.cpp index 60d243d931..85b45cd6ad 100644 --- a/core/libs/prx/libSceUrlConfigResolver/Export.cpp +++ b/core/libs/prx/libSceUrlConfigResolver/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceUrlConfigResolver/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -25,7 +26,6 @@ int APS5_VABI sceUrlConfigResolverGetJscHeapSizeSoftLimit() { return 0; } - int APS5_VABI sceUrlConfigResolverGetReleaseVersion() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceUrlConfigResolver/include/Api.hpp b/core/libs/prx/libSceUrlConfigResolver/include/Api.hpp new file mode 100644 index 0000000000..e5eb270f75 --- /dev/null +++ b/core/libs/prx/libSceUrlConfigResolver/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceUrlConfigResolverGetAppConfig(); +int APS5_VABI sceUrlConfigResolverGetDefaultQueryParameter(); +int APS5_VABI sceUrlConfigResolverGetDeviceId(); +int APS5_VABI sceUrlConfigResolverGetJscHeapSizeSoftLimit(); +int APS5_VABI sceUrlConfigResolverGetReleaseVersion(); +int APS5_VABI sceUrlConfigResolverGetSysVer(); +int APS5_VABI sceUrlConfigResolverIsUpdated(); +int APS5_VABI sceUrlConfigResolverUpdate(); + +} diff --git a/core/libs/prx/libSceUrlConfigResolver/libSceUrlConfigResolver.hpp b/core/libs/prx/libSceUrlConfigResolver/libSceUrlConfigResolver.hpp new file mode 100644 index 0000000000..cd604928ca --- /dev/null +++ b/core/libs/prx/libSceUrlConfigResolver/libSceUrlConfigResolver.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceUrlConfigResolver/include/Api.hpp" diff --git a/core/libs/prx/libSceUsbStorage/CMakeLists.txt b/core/libs/prx/libSceUsbStorage/CMakeLists.txt new file mode 100644 index 0000000000..a4280361de --- /dev/null +++ b/core/libs/prx/libSceUsbStorage/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceUsbStorage SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceUsbStorage PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceUsbStorage PRIVATE libc) +configure_windows_unwind(libSceUsbStorage) + +if(BUILD_TESTING) + add_test_executable(guest_usb_storage_tests ${LIBS_INCLUDE_DIR}/tests/GuestUsbStorage.cpp) + target_include_directories(guest_usb_storage_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_usb_storage_tests PRIVATE libSceUsbStorage libc) + configure_windows_unwind(guest_usb_storage_tests) + add_test(NAME guest_usb_storage COMMAND guest_usb_storage_tests) + if(WIN32) + set_tests_properties(guest_usb_storage PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceUsbStorage/Export.cpp b/core/libs/prx/libSceUsbStorage/Export.cpp index 0704602439..978a9dc386 100644 --- a/core/libs/prx/libSceUsbStorage/Export.cpp +++ b/core/libs/prx/libSceUsbStorage/Export.cpp @@ -1,77 +1,153 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libSceUsbStorage/UsbStorage.hpp" + extern "C" { -int APS5_VABI sceUsbStorageInit(void* attr) { +std::int32_t APS5_VABI sceUsbStorageInit(void* attr) { + (void)attr; + return 0; +} + +std::int32_t APS5_VABI sceUsbStorageTerm(void) { + return 0; +} + +std::int32_t APS5_VABI sceUsbStorageRegisterCallback(std::int32_t eventType, UsbStorageCallback callback, void* arg) { + (void)arg; + if (callback == nullptr) { + return UsbStorage::InvalidArgument; + } + if (eventType < UsbStorage::CallbackEventTypeFirst || eventType > UsbStorage::CallbackEventTypeLast) { + return UsbStorage::InvalidArgument; + } NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUsbStorageTerm(void) { + +std::int32_t APS5_VABI sceUsbStorageUnregisterCallback(std::int32_t eventType, UsbStorageCallback callback) { + if (callback == nullptr) { + return UsbStorage::InvalidArgument; + } + if (eventType < UsbStorage::CallbackEventTypeFirst || eventType > UsbStorage::CallbackEventTypeLast) { + return UsbStorage::InvalidArgument; + } NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUsbStorageRegisterCallback(int32_t eventType, void* callback, void* arg) { + +std::int32_t APS5_VABI sceUsbStorageRegisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* arg) { + (void)device; + (void)arg; + if (directory == nullptr || callback == nullptr) { + return UsbStorage::InvalidArgument; + } NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUsbStorageUnregisterCallback(int32_t eventType, void* callback) { + +std::int32_t APS5_VABI sceUsbStorageUnregisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback) { + (void)device; + if (directory == nullptr || callback == nullptr) { + return UsbStorage::InvalidArgument; + } NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUsbStorageRegisterCallbackForMapAvailable(uint32_t device, const char* directory, void* callback, void* arg) { + +std::int32_t APS5_VABI sceUsbStorageRegisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* arg) { + (void)device; + (void)arg; + if (directory == nullptr || callback == nullptr) { + return UsbStorage::InvalidArgument; + } NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUsbStorageUnregisterCallbackForMapAvailable(uint32_t device, const char* directory, void* callback) { + +std::int32_t APS5_VABI sceUsbStorageUnregisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback) { + (void)device; + if (directory == nullptr || callback == nullptr) { + return UsbStorage::InvalidArgument; + } NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUsbStorageRegisterCallbackForMapUnavailable(uint32_t device, const char* directory, void* callback, void* arg) { - NotImplemented_nid_no_patch(__func__); - return 0; + +std::int32_t APS5_VABI sceUsbStorageGetDeviceInfo(std::uint32_t device, UsbStorageDeviceInfo* info) { + (void)device; + if (info == nullptr) { + return UsbStorage::InvalidArgument; + } + return UsbStorage::DeviceNotFound; } -int APS5_VABI sceUsbStorageUnregisterCallbackForMapUnavailable(uint32_t device, const char* directory, void* callback) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceUsbStorageGetDeviceInfo(uint32_t device, void* info) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceUsbStorageGetDeviceList(uint32_t* devices, int32_t* count) { - NotImplemented_nid_no_patch(__func__); + +std::int32_t APS5_VABI sceUsbStorageGetDeviceList(std::uint32_t* devices, std::int32_t* count) { + if (devices == nullptr || count == nullptr) { + return UsbStorage::InvalidArgument; + } + *count = 0; return 0; } + int APS5_VABI sceUsbStorageGetMountPointOfShellCore(uint32_t device, char* mountPoint) { + (void)device; + (void)mountPoint; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUsbStorageIsExist(uint32_t device) { + +bool APS5_VABI sceUsbStorageIsExist(std::uint32_t device, const char* directory, std::uint8_t* exists) { + (void)device; + (void)directory; + *exists = 0; + return true; +} + +std::int32_t APS5_VABI sceUsbStorageRequestMap(std::uint32_t device, const char* directory, std::int32_t flags, + std::uint64_t volumeSize, char* mountPoint, std::uint64_t* mountId) { + (void)device; + (void)directory; + (void)flags; + (void)volumeSize; + (void)mountPoint; + (void)mountId; + return UsbStorage::DeviceNotFound; +} + +std::int32_t APS5_VABI sceUsbStorageRequestUnmap(std::uint32_t device, const char* directory) { + (void)device; + if (directory == nullptr) { + return UsbStorage::InvalidArgument; + } NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceUsbStorageRequestMap(uint32_t device, const char* directory, int32_t flags, - uint64_t volumeSize, char* mountPoint, uint64_t* mountId, - const void* reserved, size_t reservedSize) { - NotImplemented_nid_no_patch(__func__); - return 0; + +std::int32_t APS5_VABI sceUsbStorageGetdentsOpen(std::uint32_t device, std::int32_t* fd, std::uint64_t* bufferSize) { + (void)device; + if (fd == nullptr || bufferSize == nullptr) { + return UsbStorage::InvalidArgument; + } + return UsbStorage::DeviceNotFound; } -int APS5_VABI sceUsbStorageRequestUnmap(uint32_t device, const char* directory) { - NotImplemented_nid_no_patch(__func__); - return 0; + +std::int32_t APS5_VABI sceUsbStorageGetdentsRead(std::int32_t fd, void* buffer, std::uint64_t bufferSize, std::uint64_t* bytesRead) { + (void)fd; + (void)bufferSize; + if (buffer == nullptr || bytesRead == nullptr) { + return UsbStorage::InvalidArgument; + } + *bytesRead = 0; + return UsbStorage::NotSupported; } -int APS5_VABI sceUsbStorageGetdentsOpen(uint32_t device, int32_t* fd, uint64_t* bufferSize) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceUsbStorageGetdentsRead(int32_t fd, void* buffer, uint64_t bufferSize, uint64_t* bytesRead) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceUsbStorageGetdentsClose(int32_t fd) { + +std::int32_t APS5_VABI sceUsbStorageGetdentsClose(std::int32_t fd) { + (void)fd; NotImplemented_nid_no_patch(__func__); return 0; } @@ -81,10 +157,15 @@ int APS5_VABI sceUsbStorageClearFakeWriteProtection() { return 0; } -int APS5_VABI sceUsbStorageRequestMapWSB(uint32_t device_id, const char* subpath, int32_t flags, - int32_t unknown, char* out_mount_path, int32_t* out_status) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbStorageRequestMapWSB(std::uint32_t device, const char* subpath, std::int32_t flags, + std::int32_t unknown, char* mountPath, std::int32_t* status) { + (void)device; + (void)subpath; + (void)flags; + (void)unknown; + (void)mountPath; + (void)status; + return UsbStorage::DeviceNotFound; } int APS5_VABI sceUsbStorageSetFakeMapLockForDebug() { diff --git a/core/libs/prx/libSceUsbStorage/UsbStorage.hpp b/core/libs/prx/libSceUsbStorage/UsbStorage.hpp new file mode 100644 index 0000000000..b3bc2d24ab --- /dev/null +++ b/core/libs/prx/libSceUsbStorage/UsbStorage.hpp @@ -0,0 +1,50 @@ +#ifndef CORE_LIBS_PRX_LIBSCEUSBSTORAGE_USBSTORAGE_HPP +#define CORE_LIBS_PRX_LIBSCEUSBSTORAGE_USBSTORAGE_HPP + +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include + +namespace UsbStorage { + +inline constexpr std::int32_t InvalidArgument = static_cast(0x80f40002); +inline constexpr std::int32_t DeviceNotFound = static_cast(0x80f40004); +inline constexpr std::int32_t NotSupported = static_cast(0x80f40005); + +inline constexpr std::int32_t CallbackEventTypeFirst = 1; +inline constexpr std::int32_t CallbackEventTypeLast = 2; + +} + +struct UsbStorageEvent; +struct UsbStorageDeviceInfo; +using UsbStorageCallback = void (APS5_VABI *)(const UsbStorageEvent* event, void* userData); + +extern "C" { +std::int32_t APS5_VABI sceUsbStorageInit(void* attr); +std::int32_t APS5_VABI sceUsbStorageTerm(void); +std::int32_t APS5_VABI sceUsbStorageRegisterCallback(std::int32_t eventType, UsbStorageCallback callback, void* arg); +std::int32_t APS5_VABI sceUsbStorageUnregisterCallback(std::int32_t eventType, UsbStorageCallback callback); +std::int32_t APS5_VABI sceUsbStorageRegisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* arg); +std::int32_t APS5_VABI sceUsbStorageUnregisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback); +std::int32_t APS5_VABI sceUsbStorageRegisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* arg); +std::int32_t APS5_VABI sceUsbStorageUnregisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback); +std::int32_t APS5_VABI sceUsbStorageGetDeviceInfo(std::uint32_t device, UsbStorageDeviceInfo* info); +std::int32_t APS5_VABI sceUsbStorageGetDeviceList(std::uint32_t* devices, std::int32_t* count); +std::int32_t APS5_VABI sceUsbStorageGetMountPointOfShellCore(std::uint32_t device, char* mountPoint); +bool APS5_VABI sceUsbStorageIsExist(std::uint32_t device, const char* directory, std::uint8_t* exists); +std::int32_t APS5_VABI sceUsbStorageRequestMap(std::uint32_t device, const char* directory, std::int32_t flags, + std::uint64_t volumeSize, char* mountPoint, std::uint64_t* mountId); +std::int32_t APS5_VABI sceUsbStorageRequestUnmap(std::uint32_t device, const char* directory); +std::int32_t APS5_VABI sceUsbStorageGetdentsOpen(std::uint32_t device, std::int32_t* fd, std::uint64_t* bufferSize); +std::int32_t APS5_VABI sceUsbStorageGetdentsRead(std::int32_t fd, void* buffer, std::uint64_t bufferSize, std::uint64_t* bytesRead); +std::int32_t APS5_VABI sceUsbStorageGetdentsClose(std::int32_t fd); +std::int32_t APS5_VABI sceUsbStorageRequestMapWSB(std::uint32_t device, const char* subpath, std::int32_t flags, + std::int32_t unknown, char* mountPath, std::int32_t* status); +std::int32_t APS5_VABI sceUsbStorageClearFakeWriteProtection(); +std::int32_t APS5_VABI sceUsbStorageSetFakeMapLockForDebug(); +std::int32_t APS5_VABI sceUsbStorageSetFakeWriteProtection(); +} + +#endif diff --git a/core/libs/prx/libSceUsbStorage/libSceUsbStorage.hpp b/core/libs/prx/libSceUsbStorage/libSceUsbStorage.hpp new file mode 100644 index 0000000000..bf34865160 --- /dev/null +++ b/core/libs/prx/libSceUsbStorage/libSceUsbStorage.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceUsbStorage/UsbStorage.hpp" diff --git a/core/libs/prx/libSceUsbStorageDialog/Export.cpp b/core/libs/prx/libSceUsbStorageDialog/Export.cpp index 78a410fe58..290ab47eca 100644 --- a/core/libs/prx/libSceUsbStorageDialog/Export.cpp +++ b/core/libs/prx/libSceUsbStorageDialog/Export.cpp @@ -1,8 +1,12 @@ +#include "prx/libSceUsbStorageDialog/include/Api.hpp" +#include "prx/libSceUserService/UserService.hpp" #include #include #include #include #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" namespace { @@ -12,7 +16,7 @@ constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_INVALID_ARGUMENT = static_cast(0x80B8000Au); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); @@ -20,22 +24,36 @@ constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; +std::uint32_t g_sdkVersion = 0; +std::atomic_bool g_closed{false}; } extern "C" { int APS5_VABI sceUsbStorageDialogInitialize(void) { + if (g_status.load() != COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t sdkVersion; + std::memcpy(&sdkVersion, process + 16, sizeof(sdkVersion)); int expected = COMMON_DIALOG_STATUS_NONE; if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + g_sdkVersion = sdkVersion; + g_closed = false; return 0; } -int APS5_VABI sceUsbStorageDialogOpen(const void* param) { +int APS5_VABI sceUsbStorageDialogOpen(const UsbStorageDialogParam* param) { + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (!ValidateCommonDialogBaseParam(param->base)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + UserServiceLoginUserIdList users{}; + if (param->userId == -1 || sceUserServiceGetLoginUserIdList(&users) != 0 || std::find(std::begin(users.user_id), std::end(users.user_id), param->userId) == std::end(users.user_id)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (param->path[1023] != 0 || param->path[0] == 0) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + if (g_sdkVersion >= 0x04500000 && (std::strpbrk(param->path, ",*?\"<>|") != nullptr || std::strstr(param->path, "..") != nullptr)) return static_cast(0x812D0003u); if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; - if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + g_closed = false; g_status = COMMON_DIALOG_STATUS_RUNNING; return 0; } @@ -50,20 +68,26 @@ int APS5_VABI sceUsbStorageDialogUpdateStatus(void) { return g_status.load(); } -int APS5_VABI sceUsbStorageDialogGetResult(void* result) { +int APS5_VABI sceUsbStorageDialogGetResult(UsbStorageDialogResult* result) { + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (g_closed.load()) { + result->result = COMMON_DIALOG_ERROR_NOT_FINISHED; + result->status = 0; + return 0; + } if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - const std::int32_t dialogResult = COMMON_DIALOG_RESULT_USER_CANCELED; - std::memcpy(result, &dialogResult, sizeof(dialogResult)); - return 0; + if (g_sdkVersion >= 0x04500000 && (result->size != sizeof(*result) || result->result != COMMON_DIALOG_ERROR_BUSY)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + return COMMON_DIALOG_RESULT_USER_CANCELED; } int APS5_VABI sceUsbStorageDialogClose(void) { const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (status != COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_NOT_RUNNING; + g_closed = true; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } diff --git a/core/libs/prx/libSceUsbStorageDialog/include/Api.hpp b/core/libs/prx/libSceUsbStorageDialog/include/Api.hpp new file mode 100644 index 0000000000..636ddf50f5 --- /dev/null +++ b/core/libs/prx/libSceUsbStorageDialog/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceUsbStorageDialogInitialize(void); +int APS5_VABI sceUsbStorageDialogOpen(const UsbStorageDialogParam* param); +int APS5_VABI sceUsbStorageDialogGetStatus(void); +int APS5_VABI sceUsbStorageDialogUpdateStatus(void); +int APS5_VABI sceUsbStorageDialogGetResult(UsbStorageDialogResult* result); +int APS5_VABI sceUsbStorageDialogClose(void); +int APS5_VABI sceUsbStorageDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceUsbStorageDialog/libSceUsbStorageDialog.hpp b/core/libs/prx/libSceUsbStorageDialog/libSceUsbStorageDialog.hpp new file mode 100644 index 0000000000..fdc47497d7 --- /dev/null +++ b/core/libs/prx/libSceUsbStorageDialog/libSceUsbStorageDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceUsbStorageDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceUsbd/Export.cpp b/core/libs/prx/libSceUsbd/Export.cpp index 6d791a3166..edc6a19de0 100644 --- a/core/libs/prx/libSceUsbd/Export.cpp +++ b/core/libs/prx/libSceUsbd/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceUsbd/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,6 +6,7 @@ #include +#include #include #include #include @@ -28,35 +30,8 @@ constexpr std::uint8_t TRANSFER_TYPE_BULK = 2; constexpr std::uint8_t TRANSFER_TYPE_INTERRUPT = 3; constexpr std::int32_t CONTROL_SETUP_SIZE = 8; constexpr std::uint8_t SUPPORTED_TRANSFER_FLAGS = LIBUSB_TRANSFER_SHORT_NOT_OK | LIBUSB_TRANSFER_ADD_ZERO_PACKET; - -struct UsbdTimeval { - std::int64_t seconds; - std::int64_t microseconds; -}; - -struct UsbdIsoPacketDescriptor { - std::uint32_t length; - std::uint32_t actualLength; - std::int32_t status; -}; - -using UsbdTransferCallback = void (APS5_VABI*)(struct UsbdTransfer*); - -struct UsbdTransfer { - void* deviceHandle; - std::uint8_t flags; - std::uint8_t endpoint; - std::uint8_t type; - std::uint32_t timeout; - std::int32_t status; - std::int32_t length; - std::int32_t actualLength; - UsbdTransferCallback callback; - void* userData; - std::uint8_t* buffer; - std::int32_t numIsoPackets; - UsbdIsoPacketDescriptor isoPacketDescriptors[1]; -}; +constexpr std::uint16_t STRING_DESCRIPTOR_SIZE = 255; +constexpr std::uint32_t STRING_DESCRIPTOR_TIMEOUT = 1000; static_assert(offsetof(UsbdTransfer, timeout) == 12 && offsetof(UsbdTransfer, callback) == 32 && offsetof(UsbdTransfer, numIsoPackets) == 56 && offsetof(UsbdTransfer, isoPacketDescriptors) == 60); static_assert(sizeof(libusb_device_descriptor) == 18); @@ -73,6 +48,7 @@ struct UsbdState { std::unordered_map devices; std::unordered_set lists; std::unordered_set handles; + std::unordered_map claimed; std::unordered_set configs; std::unordered_map inFlight; }; @@ -98,11 +74,15 @@ std::int32_t ToGuestError(int result) { return static_cast(0x80240000u - static_cast(result)); } +bool IsKnownDevice(const UsbdState& state, libusb_device* device) { + return state.devices.contains(device) || std::any_of(state.handles.begin(), state.handles.end(), [device](libusb_device_handle* handle) { return libusb_get_device(handle) == device; }); +} + libusb_device* KnownDevice(void* device, const char* function) { auto& state = State(); std::lock_guard lock(state.mutex); auto* host = static_cast(device); - if (!state.devices.contains(host)) throw std::runtime_error(AbsentDevice(function)); + if (!IsKnownDevice(state, host)) throw std::runtime_error(AbsentDevice(function)); return host; } @@ -141,6 +121,34 @@ void LIBUSB_CALL CompleteTransfer(libusb_transfer* host) { if (guest->callback != nullptr) guest->callback(guest); } +int MaxPacketSize(libusb_device* device, std::uint8_t endpoint) { + libusb_config_descriptor* config = nullptr; + const int result = libusb_get_active_config_descriptor(device, &config); + if (result < 0) return result; + int size = LIBUSB_ERROR_NOT_FOUND; + for (int index = 0; index < config->bNumInterfaces && size == LIBUSB_ERROR_NOT_FOUND; ++index) { + const auto& entry = config->interface[index]; + for (int alternate = 0; alternate < entry.num_altsetting && size == LIBUSB_ERROR_NOT_FOUND; ++alternate) { + const auto& setting = entry.altsetting[alternate]; + for (int item = 0; item < setting.bNumEndpoints; ++item) { + if (setting.endpoint[item].bEndpointAddress == endpoint) { + size = setting.endpoint[item].wMaxPacketSize; + break; + } + } + } + } + libusb_free_config_descriptor(config); + return size; +} + +bool RequestStringDescriptor(libusb_device_handle* handle, std::uint8_t descriptorIndex, std::uint16_t languageId, std::uint8_t* descriptor) { + std::memset(descriptor, 0, STRING_DESCRIPTOR_SIZE); + const auto value = static_cast(LIBUSB_DT_STRING << 8 | descriptorIndex); + const int result = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN, LIBUSB_REQUEST_GET_DESCRIPTOR, value, languageId, descriptor, STRING_DESCRIPTOR_SIZE, STRING_DESCRIPTOR_TIMEOUT); + return result >= 0 && descriptor[1] == LIBUSB_DT_STRING; +} + } extern "C" { @@ -162,6 +170,7 @@ void APS5_VABI sceUsbdExit() { if (state.initCount == 0 || --state.initCount != 0) return; if (!state.inFlight.empty()) throw std::runtime_error(std::string(__func__) + ": transfers are still in flight"); for (auto* handle : state.handles) libusb_close(handle); + state.claimed.clear(); for (auto* config : state.configs) libusb_free_config_descriptor(config); for (auto* list : state.lists) libusb_free_device_list(list, 0); for (const auto& [device, references] : state.devices) { @@ -377,7 +386,14 @@ std::int32_t APS5_VABI sceUsbdCheckConnected(void* deviceHandle) { std::int32_t APS5_VABI sceUsbdClaimInterface(void* deviceHandle, std::int32_t interfaceNumber) { if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; - return ToGuestError(libusb_claim_interface(KnownHandle(deviceHandle, __func__), interfaceNumber)); + auto* handle = KnownHandle(deviceHandle, __func__); + const int result = libusb_claim_interface(handle, interfaceNumber); + if (result == 0) { + auto& state = State(); + std::lock_guard lock(state.mutex); + state.claimed[handle] |= 1u << interfaceNumber; + } + return ToGuestError(result); } void APS5_VABI sceUsbdClose(void* deviceHandle) { @@ -386,6 +402,7 @@ void APS5_VABI sceUsbdClose(void* deviceHandle) { std::lock_guard lock(state.mutex); auto* host = static_cast(deviceHandle); if (state.handles.erase(host) == 0) throw std::runtime_error(UnopenedHandle(__func__)); + state.claimed.erase(host); libusb_close(host); } @@ -468,14 +485,21 @@ void* APS5_VABI sceUsbdRefDevice(void* device) { auto& state = State(); std::lock_guard lock(state.mutex); auto* host = static_cast(device); - if (!state.devices.contains(host)) throw std::runtime_error(AbsentDevice(__func__)); + if (!IsKnownDevice(state, host)) throw std::runtime_error(AbsentDevice(__func__)); AddDeviceReference(state, host); return libusb_ref_device(host); } std::int32_t APS5_VABI sceUsbdReleaseInterface(void* deviceHandle, std::int32_t interfaceNumber) { if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; - return ToGuestError(libusb_release_interface(KnownHandle(deviceHandle, __func__), interfaceNumber)); + auto* handle = KnownHandle(deviceHandle, __func__); + const int result = libusb_release_interface(handle, interfaceNumber); + if (result == 0) { + auto& state = State(); + std::lock_guard lock(state.mutex); + state.claimed[handle] &= ~(1u << interfaceNumber); + } + return ToGuestError(result); } std::int32_t APS5_VABI sceUsbdResetDevice(void* deviceHandle) { @@ -535,13 +559,16 @@ void APS5_VABI sceUsbdUnrefDevice(void* device) { } std::int32_t APS5_VABI sceUsbdClearHalt(void* deviceHandle, std::uint8_t endpoint) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr) throw std::runtime_error(std::string(__func__) + ": null handle"); + const int result = libusb_clear_halt(KnownHandle(deviceHandle, __func__), endpoint); + if (result == 0) return 0; + if (result == LIBUSB_ERROR_NOT_FOUND) return SCE_USBD_ERROR_OTHER; + throw std::runtime_error(std::string(__func__) + ": libusb error " + std::to_string(result) + " has no known console result"); } std::int32_t APS5_VABI sceUsbdDetachKernelDriver(void* deviceHandle, int interfaceNumber) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + return libusb_detach_kernel_driver(KnownHandle(deviceHandle, __func__), static_cast(interfaceNumber)) == 0 ? 0 : SCE_USBD_ERROR_OTHER; } std::int32_t APS5_VABI sceUsbdEventHandlerActive() { @@ -550,43 +577,87 @@ std::int32_t APS5_VABI sceUsbdEventHandlerActive() { } std::int32_t APS5_VABI sceUsbdGetConfigDescriptorByValue(void* device, std::uint8_t configurationValue, void** config) { - NotImplemented_nid_no_patch(__func__); + if (device == nullptr || config == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + libusb_config_descriptor* host = nullptr; + const int result = libusb_get_config_descriptor_by_value(KnownDevice(device, __func__), configurationValue, &host); + *config = nullptr; + if (result != 0) return ToGuestError(result); + auto& state = State(); + std::lock_guard lock(state.mutex); + state.configs.insert(host); + *config = host; return 0; } std::int32_t APS5_VABI sceUsbdGetConfiguration(void* deviceHandle, std::int32_t* configuration) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr || configuration == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + int host = 0; + const int result = libusb_get_configuration(KnownHandle(deviceHandle, __func__), &host); + if (result == 0) *configuration = host; + return ToGuestError(result); } std::int32_t APS5_VABI sceUsbdGetDescriptor(void* deviceHandle, std::uint8_t descriptorType, std::uint8_t descriptorIndex, std::uint8_t* data, std::int32_t length) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr || data == nullptr || length < 1) return SCE_USBD_ERROR_INVALID_ARG; + auto* handle = KnownHandle(deviceHandle, __func__); + if (length > 65535) length = 65535; + return ToGuestError(libusb_get_descriptor(handle, descriptorType, descriptorIndex, data, length)); } void* APS5_VABI sceUsbdGetDevice(void* deviceHandle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr) return nullptr; + return libusb_get_device(KnownHandle(deviceHandle, __func__)); } int APS5_VABI sceUsbdGetDeviceSpeed(void* device) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (device == nullptr) return LIBUSB_SPEED_UNKNOWN; + return std::min(libusb_get_device_speed(KnownDevice(device, __func__)), static_cast(LIBUSB_SPEED_SUPER)); } std::int32_t APS5_VABI sceUsbdGetMaxIsoPacketSize(void* device, std::uint8_t endpoint) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (device == nullptr) throw std::runtime_error(std::string(__func__) + ": null device"); + auto* host = KnownDevice(device, __func__); + int size = MaxPacketSize(host, endpoint); + const int speed = libusb_get_device_speed(host); + if (speed != LIBUSB_SPEED_LOW && speed != LIBUSB_SPEED_FULL && size >= 0) size = (size & 0x7FF) * std::min(1 + ((size >> 11) & 3), 3); + return ToGuestError(size); } std::int32_t APS5_VABI sceUsbdGetMaxPacketSize(void* device, std::uint8_t endpoint) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (device == nullptr) return ToGuestError(LIBUSB_ERROR_NO_DEVICE); + return ToGuestError(MaxPacketSize(KnownDevice(device, __func__), endpoint)); } std::int32_t APS5_VABI sceUsbdGetStringDescriptorAscii(void* deviceHandle, std::uint8_t descriptorIndex, std::uint8_t* data, std::int32_t length) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr || data == nullptr || length < 1) return SCE_USBD_ERROR_INVALID_ARG; + auto* handle = KnownHandle(deviceHandle, __func__); + if (length > 65535) length = 65535; + data[0] = 0; + std::uint8_t descriptor[STRING_DESCRIPTOR_SIZE]; + if (!RequestStringDescriptor(handle, 0, 0, descriptor)) return SCE_USBD_ERROR_OTHER; + const auto languageId = static_cast(descriptor[2] | descriptor[3] << 8); + if (!RequestStringDescriptor(handle, descriptorIndex, languageId, descriptor) || descriptor[0] < 2) return SCE_USBD_ERROR_OTHER; + std::int32_t units = descriptor[0] / 2 - 1; + if (units > length - 1) units = length - 1; + std::uint8_t byteOrder = 3; + std::int32_t written = 0; + for (std::int32_t unit = 0; unit < units; ++unit) { + const auto code = static_cast(descriptor[2 * unit + 2] | descriptor[2 * unit + 3] << 8); + std::uint8_t character; + if ((code & 0xFF00) == 0 && (byteOrder & 1) != 0) { + character = static_cast(code); + byteOrder = 1; + } else if ((code & 0x00FF) == 0 && (byteOrder & 2) != 0) { + character = static_cast(code >> 8); + byteOrder = 2; + } else { + continue; + } + if (character < 0x20 || character > 0x7E || character == '<' || character == '>') continue; + data[written++] = character; + } + data[written] = 0; + return written; } std::int32_t APS5_VABI sceUsbdHandleEvents() { @@ -608,13 +679,39 @@ void APS5_VABI sceUsbdLockEvents() { } void* APS5_VABI sceUsbdOpenDeviceWithVidPid(std::uint16_t vendorId, std::uint16_t productId) { - NotImplemented_nid_no_patch(__func__); - return 0; + auto& state = State(); + std::lock_guard lock(state.mutex); + if (state.context == nullptr) return nullptr; + libusb_device** devices = nullptr; + const ssize_t count = libusb_get_device_list(state.context, &devices); + if (count < 0) return nullptr; + libusb_device_handle* handle = nullptr; + for (ssize_t index = 0; index < count; ++index) { + libusb_device_descriptor descriptor{}; + if (libusb_get_device_descriptor(devices[index], &descriptor) == 0 && descriptor.idVendor == vendorId && descriptor.idProduct == productId) { + if (libusb_open(devices[index], &handle) != 0) handle = nullptr; + break; + } + } + libusb_free_device_list(devices, 1); + if (handle != nullptr) state.handles.insert(handle); + return handle; } std::int32_t APS5_VABI sceUsbdSetInterfaceAltSetting(void* deviceHandle, int interfaceNumber, int alternateSetting) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + auto* handle = KnownHandle(deviceHandle, __func__); + if (interfaceNumber < 0 || interfaceNumber > 31) return SCE_USBD_ERROR_INVALID_ARG; + { + auto& state = State(); + std::lock_guard lock(state.mutex); + const auto claimed = state.claimed.find(handle); + if (claimed == state.claimed.end() || (claimed->second & (1u << interfaceNumber)) == 0) return ToGuestError(LIBUSB_ERROR_NOT_FOUND); + for (const auto& [guest, host] : state.inFlight) { + if (host->dev_handle == handle) throw std::runtime_error(std::string(__func__) + ": the device has transfers in flight, which FreeBSD libusb cancels first"); + } + } + return libusb_set_interface_alt_setting(handle, interfaceNumber, static_cast(alternateSetting)) == 0 ? 0 : SCE_USBD_ERROR_OTHER; } std::int32_t APS5_VABI sceUsbdTryLockEvents() { diff --git a/core/libs/prx/libSceUsbd/include/Api.hpp b/core/libs/prx/libSceUsbd/include/Api.hpp new file mode 100644 index 0000000000..5b5aa49a05 --- /dev/null +++ b/core/libs/prx/libSceUsbd/include/Api.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct UsbdTimeval { + std::int64_t seconds; + std::int64_t microseconds; +}; + +struct UsbdIsoPacketDescriptor { + std::uint32_t length; + std::uint32_t actualLength; + std::int32_t status; +}; + +using UsbdTransferCallback = void (APS5_VABI*)(struct UsbdTransfer*); + +struct UsbdTransfer { + void* deviceHandle; + std::uint8_t flags; + std::uint8_t endpoint; + std::uint8_t type; + std::uint32_t timeout; + std::int32_t status; + std::int32_t length; + std::int32_t actualLength; + UsbdTransferCallback callback; + void* userData; + std::uint8_t* buffer; + std::int32_t numIsoPackets; + UsbdIsoPacketDescriptor isoPacketDescriptors[1]; +}; + +extern "C" { + +std::int32_t APS5_VABI sceUsbdInit(); +void APS5_VABI sceUsbdExit(); +std::int64_t APS5_VABI sceUsbdGetDeviceList(void*** list); +void APS5_VABI sceUsbdFreeDeviceList(void** list, std::int32_t unrefDevices); +std::int32_t APS5_VABI sceUsbdHandleEventsTimeout(const UsbdTimeval* timeout); +UsbdTransfer* APS5_VABI sceUsbdAllocTransfer(std::int32_t isoPackets); +void APS5_VABI sceUsbdFreeTransfer(UsbdTransfer* transfer); +void APS5_VABI sceUsbdFillInterruptTransfer(UsbdTransfer* transfer, void* deviceHandle, std::uint8_t endpoint, std::uint8_t* buffer, std::int32_t length, UsbdTransferCallback callback, void* userData, std::uint32_t timeout); +void APS5_VABI sceUsbdFillBulkTransfer(UsbdTransfer* transfer, void* deviceHandle, std::uint8_t endpoint, std::uint8_t* buffer, std::int32_t length, UsbdTransferCallback callback, void* userData, std::uint32_t timeout); +void APS5_VABI sceUsbdFillIsoTransfer(UsbdTransfer* transfer, void* deviceHandle, std::uint8_t endpoint, std::uint8_t* buffer, std::int32_t length, std::int32_t numIsoPackets, UsbdTransferCallback callback, void* userData, std::uint32_t timeout); +void APS5_VABI sceUsbdFillControlSetup(std::uint8_t* buffer, std::uint8_t requestType, std::uint8_t request, std::uint16_t value, std::uint16_t index, std::uint16_t length); +void APS5_VABI sceUsbdFillControlTransfer(UsbdTransfer* transfer, void* deviceHandle, std::uint8_t* buffer, UsbdTransferCallback callback, void* userData, std::uint32_t timeout); +std::uint8_t* APS5_VABI sceUsbdControlTransferGetData(UsbdTransfer* transfer); +std::uint8_t* APS5_VABI sceUsbdControlTransferGetSetup(UsbdTransfer* transfer); +void APS5_VABI sceUsbdSetIsoPacketLengths(UsbdTransfer* transfer, std::uint32_t length); +std::uint8_t* APS5_VABI sceUsbdGetIsoPacketBuffer(UsbdTransfer* transfer, std::uint32_t packet); +std::int32_t APS5_VABI sceUsbdBulkTransfer(void* deviceHandle, std::uint8_t endpoint, std::uint8_t* data, std::int32_t length, std::int32_t* transferred, std::uint32_t timeout); +std::int32_t APS5_VABI sceUsbdInterruptTransfer(void* deviceHandle, std::uint8_t endpoint, std::uint8_t* data, std::int32_t length, std::int32_t* transferred, std::uint32_t timeout); +std::int32_t APS5_VABI sceUsbdCancelTransfer(UsbdTransfer* transfer); +std::int32_t APS5_VABI sceUsbdEventHandlingOk(); +void APS5_VABI sceUsbdFreeConfigDescriptor(void* config); +std::int32_t APS5_VABI sceUsbdAttachKernelDriver(void* deviceHandle, std::int32_t interfaceNumber); +std::int32_t APS5_VABI sceUsbdCheckConnected(void* deviceHandle); +std::int32_t APS5_VABI sceUsbdClaimInterface(void* deviceHandle, std::int32_t interfaceNumber); +void APS5_VABI sceUsbdClose(void* deviceHandle); +std::int32_t APS5_VABI sceUsbdControlTransfer(void* deviceHandle, std::uint8_t requestType, std::uint8_t request, std::uint16_t value, std::uint16_t index, std::uint8_t* data, std::uint16_t length, std::uint32_t timeout); +std::int32_t APS5_VABI sceUsbdGetActiveConfigDescriptor(void* device, void** config); +std::uint8_t APS5_VABI sceUsbdGetBusNumber(void* device); +std::int32_t APS5_VABI sceUsbdGetConfigDescriptor(void* device, std::uint8_t configIndex, void** config); +std::uint8_t APS5_VABI sceUsbdGetDeviceAddress(void* device); +std::int32_t APS5_VABI sceUsbdGetDeviceDescriptor(void* device, void* descriptor); +std::int32_t APS5_VABI sceUsbdGetStringDescriptor(void* deviceHandle, std::uint8_t descriptorIndex, std::uint16_t languageId, std::uint8_t* data, std::int32_t length); +std::int32_t APS5_VABI sceUsbdKernelDriverActive(void* deviceHandle, std::int32_t interfaceNumber); +std::int32_t APS5_VABI sceUsbdOpen(void* device, void** deviceHandle); +void* APS5_VABI sceUsbdRefDevice(void* device); +std::int32_t APS5_VABI sceUsbdReleaseInterface(void* deviceHandle, std::int32_t interfaceNumber); +std::int32_t APS5_VABI sceUsbdResetDevice(void* deviceHandle); +std::int32_t APS5_VABI sceUsbdSetConfiguration(void* deviceHandle, std::int32_t configuration); +std::int32_t APS5_VABI sceUsbdSubmitTransfer(UsbdTransfer* transfer); +void APS5_VABI sceUsbdUnrefDevice(void* device); +std::int32_t APS5_VABI sceUsbdClearHalt(void* deviceHandle, std::uint8_t endpoint); +std::int32_t APS5_VABI sceUsbdDetachKernelDriver(void* deviceHandle, int interfaceNumber); +std::int32_t APS5_VABI sceUsbdEventHandlerActive(); +std::int32_t APS5_VABI sceUsbdGetConfigDescriptorByValue(void* device, std::uint8_t configurationValue, void** config); +std::int32_t APS5_VABI sceUsbdGetConfiguration(void* deviceHandle, std::int32_t* configuration); +std::int32_t APS5_VABI sceUsbdGetDescriptor(void* deviceHandle, std::uint8_t descriptorType, std::uint8_t descriptorIndex, std::uint8_t* data, std::int32_t length); +void* APS5_VABI sceUsbdGetDevice(void* deviceHandle); +int APS5_VABI sceUsbdGetDeviceSpeed(void* device); +std::int32_t APS5_VABI sceUsbdGetMaxIsoPacketSize(void* device, std::uint8_t endpoint); +std::int32_t APS5_VABI sceUsbdGetMaxPacketSize(void* device, std::uint8_t endpoint); +std::int32_t APS5_VABI sceUsbdGetStringDescriptorAscii(void* deviceHandle, std::uint8_t descriptorIndex, std::uint8_t* data, std::int32_t length); +std::int32_t APS5_VABI sceUsbdHandleEvents(); +std::int32_t APS5_VABI sceUsbdHandleEventsLocked(UsbdTimeval* timeout); +void APS5_VABI sceUsbdLockEventWaiters(); +void APS5_VABI sceUsbdLockEvents(); +void* APS5_VABI sceUsbdOpenDeviceWithVidPid(std::uint16_t vendorId, std::uint16_t productId); +std::int32_t APS5_VABI sceUsbdSetInterfaceAltSetting(void* deviceHandle, int interfaceNumber, int alternateSetting); +std::int32_t APS5_VABI sceUsbdTryLockEvents(); +void APS5_VABI sceUsbdUnlockEventWaiters(); +void APS5_VABI sceUsbdUnlockEvents(); +std::int32_t APS5_VABI sceUsbdWaitForEvent(UsbdTimeval* timeout); + +} diff --git a/core/libs/prx/libSceUsbd/libSceUsbd.hpp b/core/libs/prx/libSceUsbd/libSceUsbd.hpp new file mode 100644 index 0000000000..6e1c2333f9 --- /dev/null +++ b/core/libs/prx/libSceUsbd/libSceUsbd.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceUsbd/include/Api.hpp" diff --git a/core/libs/prx/libSceUserService/CMakeLists.txt b/core/libs/prx/libSceUserService/CMakeLists.txt index 6d606a67e8..282c8e98a3 100644 --- a/core/libs/prx/libSceUserService/CMakeLists.txt +++ b/core/libs/prx/libSceUserService/CMakeLists.txt @@ -1,11 +1,12 @@ add_library(libSceUserService SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceUserService PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceUserService PRIVATE libSceSystemService) configure_windows_unwind(libSceUserService) if(BUILD_TESTING) add_test_executable(guest_user_service_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserService.cpp) target_include_directories(guest_user_service_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_user_service_tests PRIVATE libSceUserService) + target_link_libraries(guest_user_service_tests PRIVATE libSceUserService libSceSystemService libc) configure_windows_unwind(guest_user_service_tests) add_test(NAME guest_user_service COMMAND guest_user_service_tests) if(WIN32) @@ -17,7 +18,7 @@ endif() if(BUILD_TESTING) add_test_executable(guest_user_service_user_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserServiceUserName.cpp) target_include_directories(guest_user_service_user_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_user_service_user_name_tests PRIVATE libSceUserService) + target_link_libraries(guest_user_service_user_name_tests PRIVATE libSceUserService libSceSystemService) add_test(NAME guest_user_service_user_name COMMAND guest_user_service_user_name_tests) if(WIN32) set_tests_properties(guest_user_service_user_name PROPERTIES ENVIRONMENT_MODIFICATION @@ -28,7 +29,7 @@ endif() if(BUILD_TESTING) add_test_executable(guest_user_service_lifecycle_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserServiceLifecycle.cpp) target_include_directories(guest_user_service_lifecycle_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_user_service_lifecycle_tests PRIVATE libSceUserService) + target_link_libraries(guest_user_service_lifecycle_tests PRIVATE libSceUserService libSceSystemService) add_test(NAME guest_user_service_lifecycle COMMAND guest_user_service_lifecycle_tests) if(WIN32) set_tests_properties(guest_user_service_lifecycle PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index 7a4c11d33e..fa8edb8b62 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -1,16 +1,46 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include +#include #include +#include +#include #include "prx/libSceUserService/UserService.hpp" -static std::atomic initialized{false}; -static std::atomic loginReported{false}; +namespace { -static int ReportAccessibilitySetting(int user_id, int32_t* setting, int32_t value) { - if (user_id != USER_SERVICE_INITIAL_USER_ID) { +std::atomic initialized{false}; +std::mutex userMutex; + +struct UserEvent { + std::uint32_t eventType; + int userId; +}; + +std::deque eventQueue; +std::array userConnected{true, false, false, false}; + +int UserIndex(int userId) { + if (userId >= USER_SERVICE_INITIAL_USER_ID && userId < USER_SERVICE_INITIAL_USER_ID + USER_SERVICE_MAX_USERS) { + return userId - USER_SERVICE_INITIAL_USER_ID; + } + return -1; +} + +bool IsUserLoggedIn(int userId) { + const int index = UserIndex(userId); + if (index < 0) { + return false; + } + std::lock_guard lock(userMutex); + return userConnected[index]; +} + +int ReportAccessibilitySetting(int user_id, int32_t* setting, int32_t value) { + if (!IsUserLoggedIn(user_id)) { return USER_SERVICE_ERROR_NOT_LOGGED_IN; } if (setting == nullptr) { @@ -20,13 +50,85 @@ static int ReportAccessibilitySetting(int user_id, int32_t* setting, int32_t val return USER_SERVICE_OK; } +void PopulateInitialLoginEvents() { + std::lock_guard lock(userMutex); + eventQueue.clear(); + constexpr std::uint32_t EventTypeLogin = 0; + eventQueue.push_back({EventTypeLogin, USER_SERVICE_INITIAL_USER_ID}); + for (int i = 1; i < USER_SERVICE_MAX_USERS; ++i) { + if (userConnected[i]) { + eventQueue.push_back({EventTypeLogin, USER_SERVICE_INITIAL_USER_ID + i}); + } + } +} + + +// Per-user keyboard settings. Nothing in AnyPS5 stores a keyboard type or input variant, so every known user reports +// the keyboard type of the configured system language and 0 for the input variants (see +// docs/dev/debt/libSceUserService.md). +static int32_t KeyboardTypeForSystemLanguage() { + constexpr int param_id_lang = 1; + constexpr int32_t fallback = 20; + constexpr int32_t keyboard_types[] = {37, 20, 26, 21, 17, 28, 29, 33, 34, 38, 39, 40, 23, 35, 16, + 30, 31, 32, 19, 36, 22, 41, 25, 44, 48, 43, 47, 42, 46, 45}; + int language = -1; + if (sceSystemServiceParamGetInt(param_id_lang, &language) != 0 || language < 0 || + language >= static_cast(sizeof(keyboard_types) / sizeof(keyboard_types[0]))) { + return fallback; + } + return keyboard_types[language]; +} + +static int32_t NoInputVariant() { + return 0; +} + +static int ReportKeyboardSetting(int user_id, int32_t* type, int32_t (*value)()) { + if (type == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (UserIndex(user_id) < 0) { + return USER_SERVICE_ERROR_UNKNOWN_USER; + } + *type = value(); + return USER_SERVICE_OK; +} +} + extern "C" { +void UserServiceSetUserConnected_nid_postfix(int userIndex, bool connected) { + if (userIndex <= 0 || userIndex >= USER_SERVICE_MAX_USERS) return; + std::lock_guard lock(userMutex); + if (userConnected[userIndex] != connected) { + userConnected[userIndex] = connected; + if (initialized.load()) { + constexpr std::uint32_t EventTypeLogin = 0; + constexpr std::uint32_t EventTypeLogout = 1; + const std::uint32_t eventType = connected ? EventTypeLogin : EventTypeLogout; + const int userId = USER_SERVICE_INITIAL_USER_ID + userIndex; + bool collapsed = false; + for (auto it = eventQueue.begin(); it != eventQueue.end(); ++it) { + if (it->userId == userId) { + if (it->eventType != eventType) { + eventQueue.erase(it); + } + collapsed = true; + break; + } + } + if (!collapsed) { + eventQueue.push_back({eventType, userId}); + } + } + } +} + int APS5_VABI sceUserServiceGetAccessibilityChatTranscription(int user_id, int32_t* chat_transcription) { return ReportAccessibilitySetting(user_id, chat_transcription, 0); } -int APS5_VABI sceUserServiceGetAccessibilityKeyremapEnable() { +int APS5_VABI sceUserServiceGetAccessibilityKeyremapEnable(std::int32_t userId, std::int32_t* enabled) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -43,7 +145,7 @@ int APS5_VABI sceUserServiceGetAccessibilityVibration(int user_id, int32_t* vibr return ReportAccessibilitySetting(user_id, vibration, 1); } -int APS5_VABI sceUserServiceGetAccessibilityZoom() { +int APS5_VABI sceUserServiceGetAccessibilityZoom(std::int32_t userId, std::int32_t* zoom) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -57,7 +159,7 @@ int APS5_VABI sceUserServiceGetAccessibilityZoomFollowFocus(int user_id, int32_t } int APS5_VABI sceUserServiceGetAgeLevel(int user_id, uint32_t* age_level) { - if (user_id != USER_SERVICE_INITIAL_USER_ID) { + if (!IsUserLoggedIn(user_id)) { return USER_SERVICE_ERROR_NOT_LOGGED_IN; } if (age_level == nullptr) { @@ -76,12 +178,14 @@ int APS5_VABI sceUserServiceGetEvent(SceUserServiceEvent* event) { if (event == nullptr) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } - if (loginReported.exchange(true)) { + std::lock_guard lock(userMutex); + if (eventQueue.empty()) { return USER_SERVICE_ERROR_NO_EVENT; } - constexpr std::uint32_t EventTypeLogin = 0; - event->event_type = EventTypeLogin; - event->user_id = USER_SERVICE_INITIAL_USER_ID; + const auto item = eventQueue.front(); + eventQueue.pop_front(); + event->event_type = item.eventType; + event->user_id = item.userId; return USER_SERVICE_OK; } @@ -89,7 +193,8 @@ int APS5_VABI sceUserServiceGetGamePresets(int user_id, UserServiceGamePresets* // System-wide game presets (difficulty, view inversion, subtitles, audio language) of the user's // profile. A fresh console has none set: every field is 0, "not specified", and the title falls // back to its own defaults. this_size is the caller's. - if (presets == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) { + const int index = UserIndex(user_id); + if (presets == nullptr || index < 0) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } presets->difficulty = 0; @@ -118,10 +223,14 @@ int APS5_VABI sceUserServiceGetLoginUserIdList(UserServiceLoginUserIdList* user_ if (user_id_list == nullptr) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } - user_id_list->user_id[0] = USER_SERVICE_INITIAL_USER_ID; - user_id_list->user_id[1] = USER_SERVICE_USER_ID_INVALID; - user_id_list->user_id[2] = USER_SERVICE_USER_ID_INVALID; - user_id_list->user_id[3] = USER_SERVICE_USER_ID_INVALID; + std::lock_guard lock(userMutex); + for (int i = 0; i < 4; ++i) { + if (i < USER_SERVICE_MAX_USERS && userConnected[i]) { + user_id_list->user_id[i] = USER_SERVICE_INITIAL_USER_ID + i; + } else { + user_id_list->user_id[i] = USER_SERVICE_USER_ID_INVALID; + } + } return USER_SERVICE_OK; } @@ -129,22 +238,24 @@ int APS5_VABI sceUserServiceGetUserName(int user_id, char* name, size_t size) { if (name == nullptr) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } - if (user_id != USER_SERVICE_INITIAL_USER_ID) { + const int index = UserIndex(user_id); + if (index < 0) { return USER_SERVICE_ERROR_UNKNOWN_USER; } if (size < USER_SERVICE_MAX_USER_NAME_LENGTH + 1) { std::memset(name, 0, size); return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; } - std::strncpy(name, USER_SERVICE_INITIAL_USER_NAME, size); + std::strncpy(name, USER_SERVICE_USER_NAMES[index], size); return USER_SERVICE_OK; } int APS5_VABI sceUserServiceGetUserNumber(int user_id, int32_t* number) { - if (number == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) { + const int index = UserIndex(user_id); + if (number == nullptr || index < 0) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } - *number = 1; + *number = index + 1; return USER_SERVICE_OK; } @@ -156,22 +267,20 @@ static int MarkInitialized() { if (initialized.exchange(true)) { return USER_SERVICE_ERROR_ALREADY_INITIALIZED; } - loginReported.store(false); + PopulateInitialLoginEvents(); return USER_SERVICE_OK; } -int APS5_VABI sceUserServiceInitialize(const void* params) { +int APS5_VABI sceUserServiceInitialize(const UserServiceInitParam* params) { if (params != nullptr) { - int priority = 0; - std::memcpy(&priority, params, sizeof(priority)); - if (!ValidThreadPriority(priority)) { + if (!ValidThreadPriority(params->priority)) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } } return MarkInitialized(); } -int APS5_VABI sceUserServiceInitialize2(int priority) { +int APS5_VABI sceUserServiceInitialize2(std::int32_t priority, KernelCpumask affinityMask) { if (!ValidThreadPriority(priority)) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } @@ -182,6 +291,8 @@ int APS5_VABI sceUserServiceTerminate(void) { if (!initialized.exchange(false)) { return USER_SERVICE_ERROR_NOT_INITIALIZED; } + std::lock_guard lock(userMutex); + eventQueue.clear(); return USER_SERVICE_OK; } @@ -205,23 +316,41 @@ int APS5_VABI sceUserServiceGetRegisteredUserIdList(UserServiceRegisteredUserIdL if (user_id_list == nullptr) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } - user_id_list->user_id[0] = USER_SERVICE_INITIAL_USER_ID; - for (int i = 1; i < 16; ++i) { - user_id_list->user_id[i] = USER_SERVICE_USER_ID_INVALID; + std::lock_guard lock(userMutex); + for (int i = 0; i < 16; ++i) { + if (i < USER_SERVICE_MAX_USERS && userConnected[i]) { + user_id_list->user_id[i] = USER_SERVICE_INITIAL_USER_ID + i; + } else { + user_id_list->user_id[i] = USER_SERVICE_USER_ID_INVALID; + } } return USER_SERVICE_OK; } int APS5_VABI sceUserServiceGetUserColor(int user_id, int* color) { - if (color == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) { + const int index = UserIndex(user_id); + if (color == nullptr || index < 0) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } - *color = 0; + *color = index; return USER_SERVICE_OK; } +int APS5_VABI sceUserServiceGetKeyboardType(int user_id, int32_t* type) { + return ReportKeyboardSetting(user_id, type, KeyboardTypeForSystemLanguage); +} + +int APS5_VABI sceUserServiceGetJapaneseInputType(int user_id, int32_t* type) { + return ReportKeyboardSetting(user_id, type, NoInputVariant); +} + +int APS5_VABI sceUserServiceGetTraditionalChineseInputType(int user_id, int32_t* type) { + return ReportKeyboardSetting(user_id, type, NoInputVariant); +} + int APS5_VABI sceUserServiceGetNpAccountId(int user_id, uint64_t* account_id) { - if (account_id == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) { + const int index = UserIndex(user_id); + if (account_id == nullptr || index < 0) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } *account_id = 0; diff --git a/core/libs/prx/libSceUserService/UserService.hpp b/core/libs/prx/libSceUserService/UserService.hpp index 1e0f926aa3..3169ed503c 100644 --- a/core/libs/prx/libSceUserService/UserService.hpp +++ b/core/libs/prx/libSceUserService/UserService.hpp @@ -3,6 +3,13 @@ #include #include +#include "SceTypes.hpp" + +struct UserServiceInitParam { + std::int32_t priority; +}; + +static_assert(sizeof(UserServiceInitParam) == 4); constexpr int USER_SERVICE_OK = 0; constexpr int USER_SERVICE_ERROR_NOT_INITIALIZED = static_cast(0x80960002u); @@ -18,5 +25,42 @@ constexpr int USER_SERVICE_ERROR_BUFFER_TOO_SHORT = -2137653238; constexpr int USER_SERVICE_ERROR_UNKNOWN_USER = static_cast(0x80960105u); constexpr std::size_t USER_SERVICE_MAX_USER_NAME_LENGTH = 16; constexpr const char* USER_SERVICE_INITIAL_USER_NAME = "Player"; +constexpr int USER_SERVICE_MAX_USERS = 4; +constexpr const char* USER_SERVICE_USER_NAMES[USER_SERVICE_MAX_USERS] = { + "Player", "Player2", "Player3", "Player4" +}; + +extern "C" void UserServiceSetUserConnected_nid_postfix(int userIndex, bool connected); + +extern "C" { + +int APS5_VABI sceUserServiceGetAccessibilityChatTranscription(int userId, int32_t* chatTranscription); +int APS5_VABI sceUserServiceGetAccessibilityKeyremapEnable(std::int32_t userId, std::int32_t* enabled); +int APS5_VABI sceUserServiceGetAccessibilityPressAndHoldDelay(int userId, int32_t* pressAndHoldDelay); +int APS5_VABI sceUserServiceGetAccessibilityTriggerEffect(int userId, int32_t* triggerEffect); +int APS5_VABI sceUserServiceGetAccessibilityVibration(int userId, int32_t* vibration); +int APS5_VABI sceUserServiceGetAccessibilityZoom(std::int32_t userId, std::int32_t* zoom); +int APS5_VABI sceUserServiceGetAccessibilityZoomEnabled(int userId, int32_t* zoomEnabled); +int APS5_VABI sceUserServiceGetAccessibilityZoomFollowFocus(int userId, int32_t* zoomFollowFocus); +int APS5_VABI sceUserServiceGetAgeLevel(int userId, uint32_t* ageLevel); +int APS5_VABI sceUserServiceGetEvent(SceUserServiceEvent* event); +int APS5_VABI sceUserServiceGetGamePresets(int userId, UserServiceGamePresets* presets); +int APS5_VABI sceUserServiceGetInitialUser(int* userId); +int APS5_VABI sceUserServiceGetLoginUserIdList(UserServiceLoginUserIdList* userIdList); +int APS5_VABI sceUserServiceGetUserName(int userId, char* name, size_t size); +int APS5_VABI sceUserServiceGetUserNumber(int userId, int32_t* number); +int APS5_VABI sceUserServiceInitialize(const UserServiceInitParam* params); +int APS5_VABI sceUserServiceInitialize2(std::int32_t priority, KernelCpumask affinityMask); +int APS5_VABI sceUserServiceTerminate(void); +int APS5_VABI sceUserServiceGetPlatformPrivacyWs1(int32_t userId, int32_t* value); +int APS5_VABI sceUserServiceGetForegroundUser(int* userId); +int APS5_VABI sceUserServiceGetRegisteredUserIdList(UserServiceRegisteredUserIdList* userIdList); +int APS5_VABI sceUserServiceGetUserColor(int userId, int* color); +int APS5_VABI sceUserServiceGetKeyboardType(int userId, int32_t* type); +int APS5_VABI sceUserServiceGetJapaneseInputType(int userId, int32_t* type); +int APS5_VABI sceUserServiceGetTraditionalChineseInputType(int userId, int32_t* type); +int APS5_VABI sceUserServiceGetNpAccountId(int userId, uint64_t* accountId); + +} #endif diff --git a/core/libs/prx/libSceUserService/libSceUserService.hpp b/core/libs/prx/libSceUserService/libSceUserService.hpp new file mode 100644 index 0000000000..32a9d2d7ae --- /dev/null +++ b/core/libs/prx/libSceUserService/libSceUserService.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceUserService/UserService.hpp" diff --git a/core/libs/prx/libSceVcodec/libSceVcodec.hpp b/core/libs/prx/libSceVcodec/libSceVcodec.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVcodec/libSceVcodec.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVdecCore.native/Export.cpp b/core/libs/prx/libSceVdecCore.native/Export.cpp index f619cb820b..3ee8802672 100644 --- a/core/libs/prx/libSceVdecCore.native/Export.cpp +++ b/core/libs/prx/libSceVdecCore.native/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVdecCore.native/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVdecCore.native/include/Api.hpp b/core/libs/prx/libSceVdecCore.native/include/Api.hpp new file mode 100644 index 0000000000..e7166aa853 --- /dev/null +++ b/core/libs/prx/libSceVdecCore.native/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVdecCoreCreateDecoderBid(); +int APS5_VABI sceVdecCoreCreateDecoderSvp(); +int APS5_VABI sceVdecCoreMapMemory(); +int APS5_VABI sceVdecCoreSetDecodeInputSvp(); + +} diff --git a/core/libs/prx/libSceVdecCore.native/libSceVdecCore.native.hpp b/core/libs/prx/libSceVdecCore.native/libSceVdecCore.native.hpp new file mode 100644 index 0000000000..ad02da52c4 --- /dev/null +++ b/core/libs/prx/libSceVdecCore.native/libSceVdecCore.native.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVdecCore.native/include/Api.hpp" diff --git a/core/libs/prx/libSceVdecCore/Export.cpp b/core/libs/prx/libSceVdecCore/Export.cpp index fb34206c21..0c25b209ec 100644 --- a/core/libs/prx/libSceVdecCore/Export.cpp +++ b/core/libs/prx/libSceVdecCore/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVdecCore/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,6 +6,104 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceVdecCoreAnalyzeStream() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreCreateDecoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreDeleteDecoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreFinalizeComputeResource() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreFlushDecodeOutput() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreGetDecodeOutput() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreInitializeComputeResource() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreMapMemoryBlock() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreQueryComputeResourceInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreQueryFrameBufferInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreQueryInstanceSize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreResetDecoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSetDecodeInput() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSetDecodeOutputSw() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSyncDecode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSyncDecodeOutputSw() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSyncDecodeWptr() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreTrySyncDecode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreTrySyncDecodeOutputSw() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreTrySyncDecodeWptr() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceVdecCore/include/Api.hpp b/core/libs/prx/libSceVdecCore/include/Api.hpp new file mode 100644 index 0000000000..9b1aaeed6c --- /dev/null +++ b/core/libs/prx/libSceVdecCore/include/Api.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVdecCoreAnalyzeStream(); +int APS5_VABI sceVdecCoreCreateDecoder(); +int APS5_VABI sceVdecCoreDeleteDecoder(); +int APS5_VABI sceVdecCoreFinalizeComputeResource(); +int APS5_VABI sceVdecCoreFlushDecodeOutput(); +int APS5_VABI sceVdecCoreGetDecodeOutput(); +int APS5_VABI sceVdecCoreInitializeComputeResource(); +int APS5_VABI sceVdecCoreMapMemoryBlock(); +int APS5_VABI sceVdecCoreQueryComputeResourceInfo(); +int APS5_VABI sceVdecCoreQueryFrameBufferInfo(); +int APS5_VABI sceVdecCoreQueryInstanceSize(); +int APS5_VABI sceVdecCoreResetDecoder(); +int APS5_VABI sceVdecCoreSetDecodeInput(); +int APS5_VABI sceVdecCoreSetDecodeOutputSw(); +int APS5_VABI sceVdecCoreSyncDecode(); +int APS5_VABI sceVdecCoreSyncDecodeOutputSw(); +int APS5_VABI sceVdecCoreSyncDecodeWptr(); +int APS5_VABI sceVdecCoreTrySyncDecode(); +int APS5_VABI sceVdecCoreTrySyncDecodeOutputSw(); +int APS5_VABI sceVdecCoreTrySyncDecodeWptr(); + +} diff --git a/core/libs/prx/libSceVdecCore/libSceVdecCore.hpp b/core/libs/prx/libSceVdecCore/libSceVdecCore.hpp new file mode 100644 index 0000000000..347e124988 --- /dev/null +++ b/core/libs/prx/libSceVdecCore/libSceVdecCore.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVdecCore/include/Api.hpp" diff --git a/core/libs/prx/libSceVdecSavc/Export.cpp b/core/libs/prx/libSceVdecSavc/Export.cpp index c30e4f9c37..72330582cd 100644 --- a/core/libs/prx/libSceVdecSavc/Export.cpp +++ b/core/libs/prx/libSceVdecSavc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVdecSavc/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVdecSavc/include/Api.hpp b/core/libs/prx/libSceVdecSavc/include/Api.hpp new file mode 100644 index 0000000000..cd352d8c30 --- /dev/null +++ b/core/libs/prx/libSceVdecSavc/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSdecCreateSw(); +int APS5_VABI sceSdecDestroySw(); +int APS5_VABI sceSdecGetVersionSw(); +int APS5_VABI sceSdecQueryMemorySw(); + +} diff --git a/core/libs/prx/libSceVdecSavc/libSceVdecSavc.hpp b/core/libs/prx/libSceVdecSavc/libSceVdecSavc.hpp new file mode 100644 index 0000000000..658d88c565 --- /dev/null +++ b/core/libs/prx/libSceVdecSavc/libSceVdecSavc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVdecSavc/include/Api.hpp" diff --git a/core/libs/prx/libSceVdecSavc2.native/libSceVdecSavc2.native.hpp b/core/libs/prx/libSceVdecSavc2.native/libSceVdecSavc2.native.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVdecSavc2.native/libSceVdecSavc2.native.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVdecSavc2/Export.cpp b/core/libs/prx/libSceVdecSavc2/Export.cpp index 6f217fc959..6ca11f6ef7 100644 --- a/core/libs/prx/libSceVdecSavc2/Export.cpp +++ b/core/libs/prx/libSceVdecSavc2/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVdecSavc2/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVdecSavc2/include/Api.hpp b/core/libs/prx/libSceVdecSavc2/include/Api.hpp new file mode 100644 index 0000000000..985cdcd141 --- /dev/null +++ b/core/libs/prx/libSceVdecSavc2/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSdecCreateSw2(); +int APS5_VABI sceSdecDestroySw2(); +int APS5_VABI sceSdecGetVersionSw2(); +int APS5_VABI sceSdecQueryMemorySw2(); + +} diff --git a/core/libs/prx/libSceVdecSavc2/libSceVdecSavc2.hpp b/core/libs/prx/libSceVdecSavc2/libSceVdecSavc2.hpp new file mode 100644 index 0000000000..10fff4393c --- /dev/null +++ b/core/libs/prx/libSceVdecSavc2/libSceVdecSavc2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVdecSavc2/include/Api.hpp" diff --git a/core/libs/prx/libSceVdecShevc.native/libSceVdecShevc.native.hpp b/core/libs/prx/libSceVdecShevc.native/libSceVdecShevc.native.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVdecShevc.native/libSceVdecShevc.native.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVdecShevc/Export.cpp b/core/libs/prx/libSceVdecShevc/Export.cpp index 8f63aa61a9..43e4936066 100644 --- a/core/libs/prx/libSceVdecShevc/Export.cpp +++ b/core/libs/prx/libSceVdecShevc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVdecShevc/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVdecShevc/include/Api.hpp b/core/libs/prx/libSceVdecShevc/include/Api.hpp new file mode 100644 index 0000000000..f6685c6a64 --- /dev/null +++ b/core/libs/prx/libSceVdecShevc/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSdecCreateSwHevc(); +int APS5_VABI sceSdecDestroySwHevc(); +int APS5_VABI sceSdecExecuteSwHevc(); +int APS5_VABI sceSdecGetFrameSwHevc(); +int APS5_VABI sceSdecGetVersionSwHevc(); +int APS5_VABI sceSdecQueryMemorySwHevc(); +int APS5_VABI sceSdecResetSwHevc(); + +} diff --git a/core/libs/prx/libSceVdecShevc/libSceVdecShevc.hpp b/core/libs/prx/libSceVdecShevc/libSceVdecShevc.hpp new file mode 100644 index 0000000000..e6f868789a --- /dev/null +++ b/core/libs/prx/libSceVdecShevc/libSceVdecShevc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVdecShevc/include/Api.hpp" diff --git a/core/libs/prx/libSceVdecSvp9.native/libSceVdecSvp9.native.hpp b/core/libs/prx/libSceVdecSvp9.native/libSceVdecSvp9.native.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVdecSvp9.native/libSceVdecSvp9.native.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVdecsw/Export.cpp b/core/libs/prx/libSceVdecsw/Export.cpp index 4c70113346..d15ec5c2fa 100644 --- a/core/libs/prx/libSceVdecsw/Export.cpp +++ b/core/libs/prx/libSceVdecsw/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceVideodec2/include/Api.hpp" +#include "prx/libSceVdecsw/include/Api.hpp" #include #include #include @@ -8,136 +10,28 @@ #include #include #include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" namespace { +using namespace SceVdecsw; + constexpr int VdecswErrorOutputPending = static_cast(0x81510115u); constexpr int VdecswErrorInputQueueEmpty = static_cast(0x81510116u); constexpr int VdecswErrorDecodePending = static_cast(0x81510117u); constexpr std::uint32_t CodecVp9 = 2382845; constexpr std::size_t MaxQueuedPictures = 4; +constexpr std::uint32_t CodecAvc = 1; +constexpr std::uint32_t CodecHevc = 974921; -struct InputData { - std::uint64_t thisSize; - const std::uint8_t* auData; - std::uint64_t auSize; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; -}; - -struct InputResult { - std::uint64_t thisSize; - const void* decodedAu; - std::uint32_t outputFrameCount; - std::uint32_t reserved; -}; - -struct FrameBuffer { - std::uint64_t thisSize; - void* frameBuffer; - std::uint64_t frameBufferSize; -}; - -struct OutputInfo { - std::uint64_t thisSize; - bool isValid; - bool isLastFrame; - bool isErrorFrame; - std::uint8_t pictureCount; - std::uint32_t codecType; - std::uint32_t frameWidth; - std::uint32_t framePitch; - std::uint32_t frameHeight; - bool isDiscardedFrame; - void* frameBuffer; - std::uint64_t frameBufferSize; - std::uint32_t frameFormat; - std::uint32_t framePitchInBytes; -}; - -struct Vp9PictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint32_t profile; - std::uint32_t level; - std::uint32_t keyFrameFlag; - std::uint32_t intraOnly; - std::uint8_t bitDepth; - std::uint8_t colorRange; - std::uint32_t colorSpace; - std::uint32_t numFrames; - std::uint32_t frameWidth; - std::uint32_t frameHeight; - std::uint32_t renderWidth; - std::uint32_t renderHeight; -}; - -struct AvcPictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint8_t idrPictureFlag; - std::uint8_t profileIdc; - std::uint8_t levelIdc; - std::uint32_t picWidthInMbsMinus1; - std::uint32_t picHeightInMapUnitsMinus1; - std::uint8_t frameMbsOnlyFlag; - std::uint8_t frameCroppingFlag; - std::uint32_t frameCropLeftOffset; - std::uint32_t frameCropRightOffset; - std::uint32_t frameCropTopOffset; - std::uint32_t frameCropBottomOffset; - std::uint8_t vui[44]; -}; - -static_assert(sizeof(InputData) == 0x30 && sizeof(InputResult) == 0x18 && sizeof(FrameBuffer) == 0x18); -static_assert(sizeof(Vp9PictureInfo) == 0x58 && offsetof(Vp9PictureInfo, ptsData) == 0x10 && offsetof(Vp9PictureInfo, profile) == 0x28 && offsetof(Vp9PictureInfo, bitDepth) == 0x38 && offsetof(Vp9PictureInfo, colorSpace) == 0x3c && offsetof(Vp9PictureInfo, renderHeight) == 0x50); +static_assert(sizeof(SceVideodec2::InputData) == 0x30 && sizeof(InputResult) == 0x18 && sizeof(FrameBuffer) == 0x18); +static_assert(sizeof(SceVideodec2::Vp9PictureInfo) == 0x58 && offsetof(SceVideodec2::Vp9PictureInfo, ptsData) == 0x10 && offsetof(SceVideodec2::Vp9PictureInfo, profile) == 0x28 && offsetof(SceVideodec2::Vp9PictureInfo, bitDepth) == 0x38 && offsetof(SceVideodec2::Vp9PictureInfo, colorSpace) == 0x3c && offsetof(SceVideodec2::Vp9PictureInfo, renderHeight) == 0x50); static_assert(sizeof(OutputInfo) == 0x38 && offsetof(OutputInfo, isLastFrame) == 9 && offsetof(OutputInfo, pictureCount) == 0xb && offsetof(OutputInfo, isDiscardedFrame) == 0x1c); static_assert(sizeof(AvcPictureInfo) == 0x78 && offsetof(AvcPictureInfo, picWidthInMbsMinus1) == 0x2c && offsetof(AvcPictureInfo, frameCropBottomOffset) == 0x44); - -struct Videodec2FrameBuffer { - std::uint64_t thisSize; - void* frameBuffer; - std::uint64_t frameBufferSize; - bool isAccepted; -}; - -struct Videodec2OutputInfo { - std::uint64_t thisSize; - bool isValid; - bool isErrorFrame; - std::uint8_t pictureCount; - std::uint32_t codecType; - std::uint32_t frameWidth; - std::uint32_t framePitch; - std::uint32_t frameHeight; - void* frameBuffer; - std::uint64_t frameBufferSize; - std::uint32_t frameFormat; - std::uint32_t framePitchInBytes; -}; - -struct Videodec2AvcPictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint8_t idrPictureFlag; - std::uint8_t profileIdc; - std::uint8_t levelIdc; - std::uint32_t picWidthInLumaSamples; - std::uint32_t picHeightInLumaSamples; -}; +static_assert(sizeof(HevcPictureInfo) == 0xb8 && offsetof(HevcPictureInfo, picWidthInLumaSamples) == 0x28 && offsetof(HevcPictureInfo, sarWidth) == 0x42 && offsetof(HevcPictureInfo, picStruct) == 0x50 && offsetof(HevcPictureInfo, conformanceWindowFlag) == 0x5c && offsetof(HevcPictureInfo, idrPictureFlag) == 0x93 && offsetof(HevcPictureInfo, generalProfileIdc) == 0x97 && offsetof(HevcPictureInfo, generalLevelIdc) == 0x9b && offsetof(HevcPictureInfo, frameCroppingFlag) == 0xa5 && offsetof(HevcPictureInfo, frameCropBottomOffset) == 0xb4); struct Videodec2Crop { std::uint32_t left; @@ -146,39 +40,29 @@ struct Videodec2Crop { std::uint32_t bottom; }; -struct DecoderMemoryInfo { - std::uint64_t thisSize; - std::uint64_t cpuMemorySize; - void* cpuMemory; - std::uint64_t gpuMemorySize; - void* gpuMemory; - std::uint64_t cpuGpuMemorySize; - void* cpuGpuMemory; - std::uint64_t maxFrameBufferSize; - std::uint32_t frameBufferAlignment; +struct Videodec2PictureHeader { + std::uint64_t pts; + std::uint64_t dts; + std::uint64_t attached; + bool keyFrame; + bool hevcIdr; + std::uint32_t profile; + std::uint32_t level; + Videodec2Crop crop; + std::uint8_t bitDepthMinus8; }; } extern "C" { -int APS5_VABI sceVideodec2QueryComputeMemoryInfo_nid_postfix(void* info); -int APS5_VABI sceVideodec2AllocateComputeQueue_nid_postfix(const void* config, const void* memory, std::uint64_t* queue); -int APS5_VABI sceVideodec2ReleaseComputeQueue_nid_postfix(std::uint64_t queue); -int APS5_VABI sceVideodec2QueryDecoderMemoryInfo_nid_postfix(const void* config, void* memory); -int APS5_VABI sceVideodec2CreateDecoder_nid_postfix(const void* config, const void* memory, std::uint64_t* handle); -int APS5_VABI sceVideodec2DeleteDecoder_nid_postfix(std::uint64_t handle); -int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputData* input, Videodec2FrameBuffer* frame, Videodec2OutputInfo* output); -int APS5_VABI sceVideodec2Flush_nid_postfix(std::uint64_t handle, Videodec2FrameBuffer* frame, Videodec2OutputInfo* output); -int APS5_VABI sceVideodec2GetVp9PictureInfo_nid_postfix(const Videodec2OutputInfo* output, Vp9PictureInfo* info); -int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t handle); -int APS5_VABI sceVideodec2GetAvcPictureInfo_nid_postfix(const Videodec2OutputInfo* output, Videodec2AvcPictureInfo* first, Videodec2AvcPictureInfo* second); -void Videodec2PictureCrop_nid_no_patch(std::uint64_t handle, const void* frameBuffer, Videodec2Crop* crop); + +void Videodec2PictureHeader_nid_no_patch(std::uint64_t handle, const void* frameBuffer, Videodec2PictureHeader* header); } namespace { struct PendingInput { - InputData input; + SceVideodec2::InputData input; std::vector data; }; @@ -187,10 +71,12 @@ struct ConsumedInput { std::uint32_t frames; }; +using PictureInfo = std::variant; + struct Picture { std::vector pixels; - Videodec2OutputInfo output; - AvcPictureInfo info; + SceVideodec2::OutputInfo output; + PictureInfo info; }; struct Decoder { @@ -200,10 +86,10 @@ struct Decoder { std::deque consumed; std::deque outputs; std::deque pictures; - std::map delivered; + std::map delivered; bool finalized = false; bool drained = false; - std::map deliveredVp9; + std::map deliveredVp9; }; std::mutex decodersMutex; @@ -216,27 +102,54 @@ std::shared_ptr findDecoder(std::uint64_t handle) { return found->second; } -AvcPictureInfo pictureInfo(std::uint64_t handle, const Videodec2OutputInfo& output) { - Videodec2AvcPictureInfo source{sizeof(Videodec2AvcPictureInfo)}; - sceVideodec2GetAvcPictureInfo_nid_postfix(&output, &source, nullptr); - if (!source.isValid) throw std::runtime_error("Vdecsw: decoded picture has no picture info"); - Videodec2Crop crop{}; - Videodec2PictureCrop_nid_no_patch(handle, output.frameBuffer, &crop); +PictureInfo pictureInfo(std::uint64_t handle, const SceVideodec2::OutputInfo& output) { + Videodec2PictureHeader header{}; + Videodec2PictureHeader_nid_no_patch(handle, output.frameBuffer, &header); + const auto& crop = header.crop; + const bool cropped = crop.left != 0 || crop.right != 0 || crop.top != 0 || crop.bottom != 0; + if (output.codecType == CodecHevc) { + HevcPictureInfo info{}; + info.thisSize = sizeof(HevcPictureInfo); + info.isValid = true; + info.ptsData = header.pts; + info.dtsData = header.dts; + info.attachedData = header.attached; + info.picWidthInLumaSamples = output.frameWidth; + info.picHeightInLumaSamples = output.frameHeight; + info.bitDepthLumaMinus8 = header.bitDepthMinus8; + info.bitDepthChromaMinus8 = header.bitDepthMinus8; + info.conformanceWindowFlag = cropped; + info.confWinLeftOffset = crop.left / 2u; + info.confWinRightOffset = crop.right / 2u; + info.confWinTopOffset = crop.top / 2u; + info.confWinBottomOffset = crop.bottom / 2u; + info.idrPictureFlag = header.hevcIdr; + info.irapPictureFlag = header.keyFrame; + info.generalProfileIdc = static_cast(header.profile); + info.generalLevelIdc = static_cast(header.level); + info.frameCroppingFlag = cropped; + info.frameCropLeftOffset = info.confWinLeftOffset; + info.frameCropRightOffset = info.confWinRightOffset; + info.frameCropTopOffset = info.confWinTopOffset; + info.frameCropBottomOffset = info.confWinBottomOffset; + return info; + } + if (output.codecType != CodecAvc) throw std::runtime_error("Vdecsw: picture info is only implemented for H.264 and HEVC"); const auto widthInMbs = (output.frameWidth + 15u) / 16u; const auto heightInMbs = (output.frameHeight + 15u) / 16u; AvcPictureInfo info{}; info.thisSize = sizeof(AvcPictureInfo); info.isValid = true; - info.ptsData = source.ptsData; - info.dtsData = source.dtsData; - info.attachedData = source.attachedData; - info.idrPictureFlag = source.idrPictureFlag; - info.profileIdc = source.profileIdc; - info.levelIdc = source.levelIdc; + info.ptsData = header.pts; + info.dtsData = header.dts; + info.attachedData = header.attached; + info.idrPictureFlag = header.keyFrame; + info.profileIdc = static_cast(header.profile); + info.levelIdc = static_cast(header.level); info.picWidthInMbsMinus1 = widthInMbs - 1u; info.picHeightInMapUnitsMinus1 = heightInMbs - 1u; info.frameMbsOnlyFlag = 1; - info.frameCroppingFlag = crop.left != 0 || crop.right != 0 || crop.top != 0 || crop.bottom != 0; + info.frameCroppingFlag = cropped; info.frameCropLeftOffset = crop.left / 2u; info.frameCropRightOffset = crop.right / 2u; info.frameCropTopOffset = crop.top / 2u; @@ -244,33 +157,59 @@ AvcPictureInfo pictureInfo(std::uint64_t handle, const Videodec2OutputInfo& outp return info; } -Vp9PictureInfo vp9PictureInfo(const Videodec2OutputInfo& output) { - Vp9PictureInfo info{sizeof(Vp9PictureInfo)}; +SceVideodec2::Vp9PictureInfo vp9PictureInfo(const SceVideodec2::OutputInfo& output) { + SceVideodec2::Vp9PictureInfo info{sizeof(SceVideodec2::Vp9PictureInfo)}; sceVideodec2GetVp9PictureInfo_nid_postfix(&output, &info); + if (!info.isValid) throw std::runtime_error("Vdecsw: decoded picture has no VP9 picture info"); return info; } +template +int deliveredPictureInfo(const OutputInfo* output, TInfo* info) { + if (output == nullptr || info == nullptr || info->thisSize < sizeof(std::uint64_t)) throw std::runtime_error("Vdecsw: invalid argument pointer"); + if (output->pictureCount > 1) throw std::runtime_error("Vdecsw: second field picture info is not implemented"); + if (output->pictureCount == 0) return 0; + std::vector> all; + { + std::lock_guard lock(decodersMutex); + for (const auto& [handle, decoder] : decoders) all.push_back(decoder); + } + for (const auto& decoder : all) { + std::lock_guard lock(decoder->mutex); + const auto found = decoder->delivered.find(output->frameBuffer); + if (found == decoder->delivered.end()) continue; + const auto* source = std::get_if(&found->second); + if (source == nullptr) throw std::runtime_error("Vdecsw: picture info requested for another codec"); + const auto bytes = std::min(info->thisSize, sizeof(TInfo)) - sizeof(std::uint64_t); + std::memcpy(reinterpret_cast(info) + sizeof(std::uint64_t), reinterpret_cast(source) + sizeof(std::uint64_t), bytes); + return 0; + } + throw std::runtime_error("Vdecsw: picture info requested for a frame buffer no decoder returned"); +} + void advance(std::uint64_t handle, Decoder& decoder) { while (decoder.pictures.size() < MaxQueuedPictures) { std::vector pixels(decoder.frameBytes); - Videodec2FrameBuffer frame{sizeof(Videodec2FrameBuffer), pixels.data(), pixels.size(), false}; - Videodec2OutputInfo output{sizeof(Videodec2OutputInfo)}; + SceVideodec2::FrameBuffer frame{sizeof(SceVideodec2::FrameBuffer), pixels.data(), pixels.size(), false}; + SceVideodec2::OutputInfo output{sizeof(SceVideodec2::OutputInfo)}; if (!decoder.inputs.empty()) { const auto* au = static_cast(decoder.inputs.front().input.auData); auto input = decoder.inputs.front().input; input.auData = decoder.inputs.front().data.data(); sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output); + decoder.inputs.pop_front(); decoder.consumed.push_back({au, output.isValid ? 1u : 0u}); } else if (decoder.finalized && !decoder.drained) { sceVideodec2Flush_nid_postfix(handle, &frame, &output); + decoder.drained = !output.isValid; } else { return; } if (output.isValid) { - const auto info = output.codecType == CodecVp9 ? AvcPictureInfo{} : pictureInfo(handle, output); + const auto info = output.codecType == CodecVp9 ? PictureInfo{} : pictureInfo(handle, output); decoder.pictures.push_back({std::move(pixels), output, info}); } } @@ -280,11 +219,11 @@ void advance(std::uint64_t handle, Decoder& decoder) { extern "C" { -int APS5_VABI sceVdecswQueryComputeMemoryInfo(void* info) { +int APS5_VABI sceVdecswQueryComputeMemoryInfo(SceVideodec2::ComputeMemoryInfo* info) { return sceVideodec2QueryComputeMemoryInfo_nid_postfix(info); } -int APS5_VABI sceVdecswAllocateComputeQueue(const void* config, const void* memory, std::uint64_t* queue) { +int APS5_VABI sceVdecswAllocateComputeQueue(const SceVideodec2::ComputeConfigInfo* config, const SceVideodec2::ComputeMemoryInfo* memory, std::uint64_t* queue) { return sceVideodec2AllocateComputeQueue_nid_postfix(config, memory, queue); } @@ -292,13 +231,13 @@ int APS5_VABI sceVdecswReleaseComputeQueue(std::uint64_t queue) { return sceVideodec2ReleaseComputeQueue_nid_postfix(queue); } -int APS5_VABI sceVdecswQueryDecoderMemoryInfo(const void* config, void* memory) { - return sceVideodec2QueryDecoderMemoryInfo_nid_postfix(config, memory); +int APS5_VABI sceVdecswQueryDecoderMemoryInfo(const void* config, SceVideodec2::DecoderMemoryInfo* memory) { + return sceVideodec2QueryDecoderMemoryInfo_nid_postfix(static_cast(config), memory); } -int APS5_VABI sceVdecswCreateDecoder(const void* config, const DecoderMemoryInfo* memory, std::uint64_t* handle) { - if (handle == nullptr || memory == nullptr || memory->thisSize < sizeof(DecoderMemoryInfo) || memory->maxFrameBufferSize == 0) throw std::runtime_error("Vdecsw: invalid decoder creation arguments"); - const auto result = sceVideodec2CreateDecoder_nid_postfix(config, memory, handle); +int APS5_VABI sceVdecswCreateDecoder(const void* config, const SceVideodec2::DecoderMemoryInfo* memory, std::uint64_t* handle) { + if (handle == nullptr || memory == nullptr || memory->thisSize < sizeof(SceVideodec2::DecoderMemoryInfo) || memory->maxFrameBufferSize == 0) throw std::runtime_error("Vdecsw: invalid decoder creation arguments"); + const auto result = sceVideodec2CreateDecoder_nid_postfix(static_cast(config), memory, handle); if (result != 0) return result; auto decoder = std::make_shared(); decoder->frameBytes = memory->maxFrameBufferSize; @@ -315,8 +254,8 @@ int APS5_VABI sceVdecswDeleteDecoder(std::uint64_t handle) { return sceVideodec2DeleteDecoder_nid_postfix(handle); } -int APS5_VABI sceVdecswSetDecodeInput(std::uint64_t handle, const InputData* input) { - if (input == nullptr || input->thisSize < sizeof(InputData)) throw std::runtime_error("Vdecsw: invalid decode input"); +int APS5_VABI sceVdecswSetDecodeInput(std::uint64_t handle, const SceVideodec2::InputData* input) { + if (input == nullptr || input->thisSize < sizeof(SceVideodec2::InputData)) throw std::runtime_error("Vdecsw: invalid decode input"); const auto decoder = findDecoder(handle); std::lock_guard lock(decoder->mutex); if (decoder->finalized) throw std::runtime_error("Vdecsw: decode input after the sequence was finalized"); @@ -406,18 +345,18 @@ int APS5_VABI sceVdecswSyncDecodeOutput(std::uint64_t handle, OutputInfo* output } int APS5_VABI sceVdecswGetPictureInfo(const OutputInfo* output, void* first, void* second) { - (void)output; - (void)first; - (void)second; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVdecswGetAvcPictureInfo(const OutputInfo* output, AvcPictureInfo* first, AvcPictureInfo* second) { - if (output == nullptr || first == nullptr || first->thisSize < sizeof(std::uint64_t)) throw std::runtime_error("Vdecsw: invalid argument pointer"); - if (output->pictureCount > 1) throw std::runtime_error("Vdecsw: second field picture info is not implemented"); - if (output->pictureCount == 0) return 0; static_cast(second); + if (output == nullptr || (output->thisSize | 8u) != sizeof(OutputInfo)) throw std::runtime_error("Vdecsw: invalid output info"); + if (output->pictureCount == 0) return 0; + if (output->pictureCount > 1) NotImplemented_nid_no_patch(__func__); + if (first == nullptr) return 0; + std::uint64_t size; + std::memcpy(&size, first, sizeof(size)); + if (size < sizeof(size)) throw std::runtime_error("Vdecsw: picture info is smaller than its size field"); + const auto copy = [first, size](const auto& source) { + const auto bytes = std::min(size, sizeof(source)) - sizeof(size); + std::memcpy(static_cast(first) + sizeof(size), reinterpret_cast(&source) + sizeof(size), bytes); + }; std::vector> all; { std::lock_guard lock(decodersMutex); @@ -425,23 +364,28 @@ int APS5_VABI sceVdecswGetAvcPictureInfo(const OutputInfo* output, AvcPictureInf } for (const auto& decoder : all) { std::lock_guard lock(decoder->mutex); - const auto found = decoder->delivered.find(output->frameBuffer); - if (found == decoder->delivered.end()) continue; - const auto bytes = std::min(first->thisSize, sizeof(AvcPictureInfo)) - sizeof(std::uint64_t); - std::memcpy(reinterpret_cast(first) + sizeof(std::uint64_t), reinterpret_cast(&found->second) + sizeof(std::uint64_t), bytes); - return 0; + if (const auto found = decoder->delivered.find(output->frameBuffer); found != decoder->delivered.end()) { + std::visit(copy, found->second); + return 0; + } + if (const auto found = decoder->deliveredVp9.find(output->frameBuffer); found != decoder->deliveredVp9.end()) { + copy(found->second); + return 0; + } } throw std::runtime_error("Vdecsw: picture info requested for a frame buffer no decoder returned"); } -int APS5_VABI sceVdecswGetHevcPictureInfo(const OutputInfo* output, void* picture) { - (void)output; - (void)picture; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVdecswGetAvcPictureInfo(const OutputInfo* output, AvcPictureInfo* first, AvcPictureInfo* second) { + static_cast(second); + return deliveredPictureInfo(output, first); } -int APS5_VABI sceVdecswGetVp9PictureInfo(const OutputInfo* output, Vp9PictureInfo* picture) { +int APS5_VABI sceVdecswGetHevcPictureInfo(const OutputInfo* output, HevcPictureInfo* picture) { + return deliveredPictureInfo(output, picture); +} + +int APS5_VABI sceVdecswGetVp9PictureInfo(const OutputInfo* output, SceVideodec2::Vp9PictureInfo* picture) { if (output == nullptr || picture == nullptr || picture->thisSize < sizeof(std::uint64_t)) throw std::runtime_error("Vdecsw: invalid argument pointer"); if (output->pictureCount > 1) throw std::runtime_error("Vdecsw: more than one VP9 picture info is not implemented"); if (output->pictureCount == 0) return 0; @@ -454,7 +398,7 @@ int APS5_VABI sceVdecswGetVp9PictureInfo(const OutputInfo* output, Vp9PictureInf std::lock_guard lock(decoder->mutex); const auto found = decoder->deliveredVp9.find(output->frameBuffer); if (found == decoder->deliveredVp9.end()) continue; - const auto bytes = std::min(picture->thisSize, sizeof(Vp9PictureInfo)) - sizeof(std::uint64_t); + const auto bytes = std::min(picture->thisSize, sizeof(SceVideodec2::Vp9PictureInfo)) - sizeof(std::uint64_t); std::memcpy(reinterpret_cast(picture) + sizeof(std::uint64_t), reinterpret_cast(&found->second) + sizeof(std::uint64_t), bytes); return 0; } diff --git a/core/libs/prx/libSceVdecsw/include/Api.hpp b/core/libs/prx/libSceVdecsw/include/Api.hpp new file mode 100644 index 0000000000..7b5fdf595a --- /dev/null +++ b/core/libs/prx/libSceVdecsw/include/Api.hpp @@ -0,0 +1,162 @@ +#include "prx/libSceVideodec2/include/Api.hpp" +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SceVdecsw { + +struct DecoderConfigInfo { + SceVideodec2::DecoderConfigInfo decoder; + std::uint64_t computeQueue; + std::uint64_t cpuAffinityMask; + std::int32_t cpuThreadPriority; + bool optimizeProgressiveVideo; + std::uint8_t reserved[19]; +}; + +struct InputResult { + std::uint64_t thisSize; + const void* decodedAu; + std::uint32_t outputFrameCount; + std::uint32_t reserved; +}; + +} + +namespace SceVdecsw { + +struct FrameBuffer { + std::uint64_t thisSize; + void* frameBuffer; + std::uint64_t frameBufferSize; +}; + +} + +namespace SceVdecsw { + +struct OutputInfo { + std::uint64_t thisSize; + bool isValid; + bool isLastFrame; + bool isErrorFrame; + std::uint8_t pictureCount; + std::uint32_t codecType; + std::uint32_t frameWidth; + std::uint32_t framePitch; + std::uint32_t frameHeight; + bool isDiscardedFrame; + void* frameBuffer; + std::uint64_t frameBufferSize; + std::uint32_t frameFormat; + std::uint32_t framePitchInBytes; +}; + +} + +namespace SceVdecsw { + +struct AvcPictureInfo { + std::uint64_t thisSize; + bool isValid; + std::uint64_t ptsData; + std::uint64_t dtsData; + std::uint64_t attachedData; + std::uint8_t idrPictureFlag; + std::uint8_t profileIdc; + std::uint8_t levelIdc; + std::uint32_t picWidthInMbsMinus1; + std::uint32_t picHeightInMapUnitsMinus1; + std::uint8_t frameMbsOnlyFlag; + std::uint8_t frameCroppingFlag; + std::uint32_t frameCropLeftOffset; + std::uint32_t frameCropRightOffset; + std::uint32_t frameCropTopOffset; + std::uint32_t frameCropBottomOffset; + std::uint8_t vui[44]; +}; + +} + +namespace SceVdecsw { + +struct HevcPictureInfo { + std::uint64_t thisSize; + bool isValid; + std::uint64_t ptsData; + std::uint64_t dtsData; + std::uint64_t attachedData; + std::uint32_t picWidthInLumaSamples; + std::uint32_t picHeightInLumaSamples; + std::uint8_t bitDepthLumaMinus8; + std::uint8_t bitDepthChromaMinus8; + std::uint8_t timingInfoPresentFlag; + std::uint32_t numUnitsInTick; + std::uint32_t timeScale; + std::uint32_t aspectRatioInfoPresentFlag; + std::uint8_t aspectRatioIdc; + std::uint16_t sarWidth; + std::uint16_t sarHeight; + std::uint8_t videoSignal[8]; + std::uint32_t picStruct; + std::uint32_t sourceScanType; + std::uint32_t duplicateFlag; + std::uint32_t conformanceWindowFlag; + std::uint32_t confWinLeftOffset; + std::uint32_t confWinRightOffset; + std::uint32_t confWinTopOffset; + std::uint32_t confWinBottomOffset; + std::uint32_t defaultDisplayWindow[5]; + std::uint8_t chromaLocation[4]; + std::uint8_t presentFlags[11]; + std::uint8_t idrPictureFlag; + std::uint8_t irapPictureFlag; + std::uint8_t generalProfileSpace; + std::uint8_t generalTierFlag; + std::uint8_t generalProfileIdc; + std::uint8_t generalSourceFlags[3]; + std::uint8_t generalLevelIdc; + std::uint8_t subLayer[9]; + std::uint8_t frameCroppingFlag; + std::uint32_t frameCropLeftOffset; + std::uint32_t frameCropRightOffset; + std::uint32_t frameCropTopOffset; + std::uint32_t frameCropBottomOffset; +}; + +} + +extern "C" { + +int APS5_VABI sceVdecswQueryComputeMemoryInfo(SceVideodec2::ComputeMemoryInfo* info); +int APS5_VABI sceVdecswAllocateComputeQueue(const SceVideodec2::ComputeConfigInfo* config, const SceVideodec2::ComputeMemoryInfo* memory, std::uint64_t* queue); +int APS5_VABI sceVdecswReleaseComputeQueue(std::uint64_t queue); +int APS5_VABI sceVdecswQueryDecoderMemoryInfo(const void* config, SceVideodec2::DecoderMemoryInfo* memory); +int APS5_VABI sceVdecswCreateDecoder(const void* config, const SceVideodec2::DecoderMemoryInfo* memory, std::uint64_t* handle); +int APS5_VABI sceVdecswDeleteDecoder(std::uint64_t handle); +int APS5_VABI sceVdecswSetDecodeInput(std::uint64_t handle, const SceVideodec2::InputData* input); +int APS5_VABI sceVdecswTrySyncDecodeInput(std::uint64_t handle, SceVdecsw::InputResult* result); +int APS5_VABI sceVdecswSyncDecodeInput(std::uint64_t handle, SceVdecsw::InputResult* result); +int APS5_VABI sceVdecswSetDecodeOutput(std::uint64_t handle, const SceVdecsw::FrameBuffer* frame); +int APS5_VABI sceVdecswTrySyncDecodeOutput(std::uint64_t handle, SceVdecsw::OutputInfo* output); +int APS5_VABI sceVdecswSyncDecodeOutput(std::uint64_t handle, SceVdecsw::OutputInfo* output); +int APS5_VABI sceVdecswGetPictureInfo(const SceVdecsw::OutputInfo* output, void* first, void* second); +int APS5_VABI sceVdecswGetAvcPictureInfo(const SceVdecsw::OutputInfo* output, SceVdecsw::AvcPictureInfo* first, SceVdecsw::AvcPictureInfo* second); +int APS5_VABI sceVdecswGetHevcPictureInfo(const SceVdecsw::OutputInfo* output, SceVdecsw::HevcPictureInfo* picture); +int APS5_VABI sceVdecswGetVp9PictureInfo(const SceVdecsw::OutputInfo* output, SceVideodec2::Vp9PictureInfo* picture); +int APS5_VABI sceVdecswFinalizeDecodeSequence(std::uint64_t handle); +int APS5_VABI sceVdecswResetDecoder(std::uint64_t handle); + +} diff --git a/core/libs/prx/libSceVdecsw/libSceVdecsw.hpp b/core/libs/prx/libSceVdecsw/libSceVdecsw.hpp new file mode 100644 index 0000000000..c080ab901a --- /dev/null +++ b/core/libs/prx/libSceVdecsw/libSceVdecsw.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVdecsw/include/Api.hpp" diff --git a/core/libs/prx/libSceVdecwrap/Export.cpp b/core/libs/prx/libSceVdecwrap/Export.cpp index ae14644219..923c85002b 100644 --- a/core/libs/prx/libSceVdecwrap/Export.cpp +++ b/core/libs/prx/libSceVdecwrap/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVdecwrap/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVdecwrap/include/Api.hpp b/core/libs/prx/libSceVdecwrap/include/Api.hpp new file mode 100644 index 0000000000..44f275d760 --- /dev/null +++ b/core/libs/prx/libSceVdecwrap/include/Api.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVdecwrapCreateDecoder(); +int APS5_VABI sceVdecwrapDeleteDecoder(); +int APS5_VABI sceVdecwrapFlushDecodeOutput(); +int APS5_VABI sceVdecwrapGetDecodeOutput(); +int APS5_VABI sceVdecwrapMapDirectMemory(); +int APS5_VABI sceVdecwrapMapMemory(); +int APS5_VABI sceVdecwrapQueryDecoderMemoryInfo(); +int APS5_VABI sceVdecwrapQueryFrameBufferInfo(); +int APS5_VABI sceVdecwrapResetDecoder(); +int APS5_VABI sceVdecwrapSetDecodeInput(); +int APS5_VABI sceVdecwrapSyncDecode(); + +} diff --git a/core/libs/prx/libSceVdecwrap/libSceVdecwrap.hpp b/core/libs/prx/libSceVdecwrap/libSceVdecwrap.hpp new file mode 100644 index 0000000000..65c7e47394 --- /dev/null +++ b/core/libs/prx/libSceVdecwrap/libSceVdecwrap.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVdecwrap/include/Api.hpp" diff --git a/core/libs/prx/libSceVenc/Export.cpp b/core/libs/prx/libSceVenc/Export.cpp index fb34206c21..c423ca85fb 100644 --- a/core/libs/prx/libSceVenc/Export.cpp +++ b/core/libs/prx/libSceVenc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVenc/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,6 +6,74 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceVencCreateEncoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencDeleteEncoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencGetConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencMapMemory() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencQueryHeader() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencQueryMemorySize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencQueryPreset() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetDecodedFrame() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetPictureConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetRateControlConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetReferenceFrameInvalidationConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencStartEncode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencStopEncode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceVenc/include/Api.hpp b/core/libs/prx/libSceVenc/include/Api.hpp new file mode 100644 index 0000000000..26d25b4a07 --- /dev/null +++ b/core/libs/prx/libSceVenc/include/Api.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVencCreateEncoder(); +int APS5_VABI sceVencDeleteEncoder(); +int APS5_VABI sceVencGetConfig(); +int APS5_VABI sceVencMapMemory(); +int APS5_VABI sceVencQueryHeader(); +int APS5_VABI sceVencQueryMemorySize(); +int APS5_VABI sceVencQueryPreset(); +int APS5_VABI sceVencSetConfig(); +int APS5_VABI sceVencSetDecodedFrame(); +int APS5_VABI sceVencSetPictureConfig(); +int APS5_VABI sceVencSetRateControlConfig(); +int APS5_VABI sceVencSetReferenceFrameInvalidationConfig(); +int APS5_VABI sceVencStartEncode(); +int APS5_VABI sceVencStopEncode(); + +} diff --git a/core/libs/prx/libSceVenc/libSceVenc.hpp b/core/libs/prx/libSceVenc/libSceVenc.hpp new file mode 100644 index 0000000000..cae676b0b2 --- /dev/null +++ b/core/libs/prx/libSceVenc/libSceVenc.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVenc/include/Api.hpp" diff --git a/core/libs/prx/libSceVideoArbitration/Export.cpp b/core/libs/prx/libSceVideoArbitration/Export.cpp index 2825147db8..23b1eaa853 100644 --- a/core/libs/prx/libSceVideoArbitration/Export.cpp +++ b/core/libs/prx/libSceVideoArbitration/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoArbitration/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVideoArbitration/include/Api.hpp b/core/libs/prx/libSceVideoArbitration/include/Api.hpp new file mode 100644 index 0000000000..126dfd0696 --- /dev/null +++ b/core/libs/prx/libSceVideoArbitration/include/Api.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideoArbitrationAcceptEvent(); +int APS5_VABI sceVideoArbitrationEnable(); +int APS5_VABI sceVideoArbitrationInitialize(); + +} diff --git a/core/libs/prx/libSceVideoArbitration/libSceVideoArbitration.hpp b/core/libs/prx/libSceVideoArbitration/libSceVideoArbitration.hpp new file mode 100644 index 0000000000..46377c3aca --- /dev/null +++ b/core/libs/prx/libSceVideoArbitration/libSceVideoArbitration.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVideoArbitration/include/Api.hpp" diff --git a/core/libs/prx/libSceVideoCoreInterface/Export.cpp b/core/libs/prx/libSceVideoCoreInterface/Export.cpp index 69f5eb3f02..d81f5a18e0 100644 --- a/core/libs/prx/libSceVideoCoreInterface/Export.cpp +++ b/core/libs/prx/libSceVideoCoreInterface/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoCoreInterface/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -10,7 +11,6 @@ int APS5_VABI sceVideoCoreIfSetFunctionTableUnlimited() { return 0; } - int APS5_VABI sceVideoCoreIfInitializeInterface() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceVideoCoreInterface/include/Api.hpp b/core/libs/prx/libSceVideoCoreInterface/include/Api.hpp new file mode 100644 index 0000000000..7b01f1539c --- /dev/null +++ b/core/libs/prx/libSceVideoCoreInterface/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideoCoreIfSetFunctionTableUnlimited(); +int APS5_VABI sceVideoCoreIfInitializeInterface(); +int APS5_VABI sceVideoCoreIfMainLoop(); +int APS5_VABI sceVideoCoreIfSetFunctionTable(); + +} diff --git a/core/libs/prx/libSceVideoCoreInterface/libSceVideoCoreInterface.hpp b/core/libs/prx/libSceVideoCoreInterface/libSceVideoCoreInterface.hpp new file mode 100644 index 0000000000..0a1d280f24 --- /dev/null +++ b/core/libs/prx/libSceVideoCoreInterface/libSceVideoCoreInterface.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVideoCoreInterface/include/Api.hpp" diff --git a/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp b/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp index 37c16803eb..0c92606c7e 100644 --- a/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp +++ b/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoCoreServerInterface/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVideoCoreServerInterface/include/Api.hpp b/core/libs/prx/libSceVideoCoreServerInterface/include/Api.hpp new file mode 100644 index 0000000000..1b44bc62ac --- /dev/null +++ b/core/libs/prx/libSceVideoCoreServerInterface/include/Api.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideoCoreAbortRequest(); +int APS5_VABI sceVideoCoreAcquireDecoderResource(); +int APS5_VABI sceVideoCoreAcquireDecoderResourceForBeWrapper(); +int APS5_VABI sceVideoCoreAddStream(); +int APS5_VABI sceVideoCoreAsyncAddStream(); +int APS5_VABI sceVideoCoreAsyncSendRequest(); +int APS5_VABI sceVideoCoreCancelThumbnail(); +int APS5_VABI sceVideoCoreChangeActiveStatus(); +int APS5_VABI sceVideoCoreChangeAudioStream(); +int APS5_VABI sceVideoCoreChangeDisplaySize(); +int APS5_VABI sceVideoCoreChangeHdcpSetting(); +int APS5_VABI sceVideoCoreChangeSubtitle(); +int APS5_VABI sceVideoCoreDeleteStream(); +int APS5_VABI sceVideoCoreGenerateThumbnail(); +int APS5_VABI sceVideoCoreGenerateThumbnail2(); +int APS5_VABI sceVideoCoreGetCanvasHandle(); +int APS5_VABI sceVideoCoreGetPlayStatus(); +int APS5_VABI sceVideoCoreGetPlayStatus2(); +int APS5_VABI sceVideoCoreGetStreamUid(); +int APS5_VABI sceVideoCoreGetThumbnail(); +int APS5_VABI sceVideoCoreGetThumbnail2(); +int APS5_VABI sceVideoCoreLaunchVideoCore(); +int APS5_VABI sceVideoCoreMediaSourceAbortSourceBuffer(); +int APS5_VABI sceVideoCoreMediaSourceAddSourceBuffer(); +int APS5_VABI sceVideoCoreMediaSourceAppendData(); +int APS5_VABI sceVideoCoreMediaSourceEndOfStream(); +int APS5_VABI sceVideoCoreMediaSourceGetBufferedTimeRanges(); +int APS5_VABI sceVideoCoreMediaSourceGetInputBufferEmptySize(); +int APS5_VABI sceVideoCoreMediaSourceGetPlaybackQuality(); +int APS5_VABI sceVideoCoreMediaSourceGetSourceBufferEvent(); +int APS5_VABI sceVideoCoreMediaSourceRemoveSourceBuffer(); +int APS5_VABI sceVideoCoreMediaSourceSetDuration(); +int APS5_VABI sceVideoCoreMediaSourceSetTimestampOffset(); +int APS5_VABI sceVideoCorePlayControl(); +int APS5_VABI sceVideoCoreRegistEventCallback(); +int APS5_VABI sceVideoCoreReleaseDecoderResource(); +int APS5_VABI sceVideoCoreReleaseDecoderResourceForBeWrapper(); +int APS5_VABI sceVideoCoreSetAudioMute(); +int APS5_VABI sceVideoCoreSetAudioVolume(); +int APS5_VABI sceVideoCoreSetFullScreen(); +int APS5_VABI sceVideoCoreSetInitialInfo(); +int APS5_VABI sceVideoCoreSetInitializeInfo(); +int APS5_VABI sceVideoCoreSetPlaybackRange(); +int APS5_VABI sceVideoCoreSetThumbnailInfo(); +int APS5_VABI sceVideoCoreSyncAddStream(); +int APS5_VABI sceVideoCoreSyncSendRequest(); +int APS5_VABI sceVideoCoreUnregistEventCallback(); + +} diff --git a/core/libs/prx/libSceVideoCoreServerInterface/libSceVideoCoreServerInterface.hpp b/core/libs/prx/libSceVideoCoreServerInterface/libSceVideoCoreServerInterface.hpp new file mode 100644 index 0000000000..5a97dc597f --- /dev/null +++ b/core/libs/prx/libSceVideoCoreServerInterface/libSceVideoCoreServerInterface.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVideoCoreServerInterface/include/Api.hpp" diff --git a/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp b/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp index 4e2ab88329..3dc080db54 100644 --- a/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp +++ b/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoDecoderArbitration/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVideoDecoderArbitration/include/Api.hpp b/core/libs/prx/libSceVideoDecoderArbitration/include/Api.hpp new file mode 100644 index 0000000000..0029460ffa --- /dev/null +++ b/core/libs/prx/libSceVideoDecoderArbitration/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideoDecoderArbitrationAcceptEvent(); +int APS5_VABI sceVideoDecoderArbitrationEnable(); +int APS5_VABI sceVideoDecoderArbitrationEnableSuspendMode(); +int APS5_VABI sceVideoDecoderArbitrationInitialize(); + +} diff --git a/core/libs/prx/libSceVideoDecoderArbitration/libSceVideoDecoderArbitration.hpp b/core/libs/prx/libSceVideoDecoderArbitration/libSceVideoDecoderArbitration.hpp new file mode 100644 index 0000000000..93ab58e19f --- /dev/null +++ b/core/libs/prx/libSceVideoDecoderArbitration/libSceVideoDecoderArbitration.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVideoDecoderArbitration/include/Api.hpp" diff --git a/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp b/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp index 2c3047328f..83c4dda32a 100644 --- a/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp +++ b/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoNativeExtEssential/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVideoNativeExtEssential/include/Api.hpp b/core/libs/prx/libSceVideoNativeExtEssential/include/Api.hpp new file mode 100644 index 0000000000..0801f28d5d --- /dev/null +++ b/core/libs/prx/libSceVideoNativeExtEssential/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideoNativeExtEssential_CreateDecInstance(); +int APS5_VABI sceVideoNativeExtEssential_DestroyDecInstance(); +int APS5_VABI sceVideoNativeExtEssential_Finalize(); +int APS5_VABI sceVideoNativeExtEssential_Initialize(); +int APS5_VABI sceVideoNativeExtEssential_IsInit(); + +} diff --git a/core/libs/prx/libSceVideoNativeExtEssential/libSceVideoNativeExtEssential.hpp b/core/libs/prx/libSceVideoNativeExtEssential/libSceVideoNativeExtEssential.hpp new file mode 100644 index 0000000000..07fbad2f88 --- /dev/null +++ b/core/libs/prx/libSceVideoNativeExtEssential/libSceVideoNativeExtEssential.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVideoNativeExtEssential/include/Api.hpp" diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index 2b3dd27800..14b0d27a7e 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -23,7 +23,7 @@ target_include_directories(libSceVideoOut PRIVATE ${CMAKE_BINARY_DIR}/core/libs/prx/libSceAgcDriver/generated ) add_dependencies(libSceVideoOut agc_trace_revision) -target_link_libraries(libSceVideoOut PRIVATE libSceAgcDriver libScePad libSceMouse libSceKeyboard libSceIme libc libkernel decoder_png SDL2-static) +target_link_libraries(libSceVideoOut PRIVATE libSceAgcDriver libScePad libSceUserService libSceMouse libSceKeyboard libSceIme libc libkernel decoder_png SDL2-static) if(WIN32) target_link_libraries(libSceVideoOut PRIVATE comctl32 dxgi) endif() @@ -84,7 +84,12 @@ if(BUILD_TESTING) set_tests_properties(guest_video_out_cursor PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() - set(ANYPS5_VIDEO_OUT_TEST_DRIVER "offscreen" CACHE STRING + if(WIN32) + set(videoOutTestDriver "windows") + else() + set(videoOutTestDriver "offscreen") + endif() + set(ANYPS5_VIDEO_OUT_TEST_DRIVER "${videoOutTestDriver}" CACHE STRING "SDL video driver for VideoOut flip tests") foreach(flipMode IN ITEMS labels labels_main) @@ -97,7 +102,7 @@ if(BUILD_TESTING) endforeach() set_target_properties(video_out_flip_tests PROPERTIES EXCLUDE_FROM_ALL OFF) - foreach(flipMode IN ITEMS lifetime reopen decode controls overlay unopened ps4) + foreach(flipMode IN ITEMS lifetime reopen decode controls overlay unopened ps4 resolution resolution_override) if(flipMode STREQUAL "lifetime") add_test(NAME video_out_flip_${flipMode} COMMAND video_out_flip_tests) else() @@ -133,12 +138,21 @@ if(BUILD_TESTING AND NOT WIN32) add_test(NAME video_out_frame_dump_queue COMMAND video_out_frame_dump_queue_tests) set_tests_properties(video_out_frame_dump_queue PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0 ENVIRONMENT "SDL_VIDEODRIVER=${ANYPS5_VIDEO_OUT_TEST_DRIVER};APS5_DUMP_FRAMES=4;APS5_DUMP_SCALE=4") + add_test(NAME video_out_frame_dump_wait COMMAND video_out_frame_dump_queue_tests wait) + set_tests_properties(video_out_frame_dump_wait PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0 + ENVIRONMENT "SDL_VIDEODRIVER=${ANYPS5_VIDEO_OUT_TEST_DRIVER};APS5_DUMP_FRAMES=4;APS5_DUMP_SCALE=1") endif() if(BUILD_TESTING) add_test_executable(pad_sdl_tests tests/PadSdl.cpp) - target_link_libraries(pad_sdl_tests PRIVATE SDL2-static) + target_include_directories(pad_sdl_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(pad_sdl_tests PRIVATE libSceVideoOut libScePad libSceUserService libc SDL2-static) + configure_windows_unwind(pad_sdl_tests) add_test(NAME pad_sdl COMMAND pad_sdl_tests) + if(WIN32) + set_tests_properties(pad_sdl PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() add_test_executable(pad_input_mapping_tests tests/InputMapping.cpp src/InputMapping.cpp) target_include_directories(pad_input_mapping_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include) target_link_libraries(pad_input_mapping_tests PRIVATE SDL2-static libc) @@ -277,13 +291,18 @@ if(BUILD_TESTING) endif() endif() -if(BUILD_TESTING AND NOT WIN32) +if(BUILD_TESTING) add_test_executable(video_out_telemetry_tests tests/Telemetry.cpp) target_include_directories(video_out_telemetry_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) target_link_libraries(video_out_telemetry_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc SDL2-static) set_target_properties(video_out_telemetry_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(video_out_telemetry_tests) add_test(NAME video_out_telemetry COMMAND video_out_telemetry_tests) add_test(NAME video_out_telemetry_caption COMMAND video_out_telemetry_tests caption) set_tests_properties(video_out_telemetry PROPERTIES TIMEOUT 30) set_tests_properties(video_out_telemetry_caption PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0 ENVIRONMENT "SDL_VIDEODRIVER=${ANYPS5_VIDEO_OUT_TEST_DRIVER}") + if(WIN32) + set_tests_properties(video_out_telemetry video_out_telemetry_caption PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceVideoOut/Unimplemented.cpp b/core/libs/prx/libSceVideoOut/Unimplemented.cpp index dabea7e04a..bd6d217e9f 100644 --- a/core/libs/prx/libSceVideoOut/Unimplemented.cpp +++ b/core/libs/prx/libSceVideoOut/Unimplemented.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/VideoOut.hpp" #include #include #include "SceTypes.hpp" @@ -7,12 +8,12 @@ extern "C" { -int APS5_VABI sceVideoOutSetFlipMaster() { +int APS5_VABI sceVideoOutSetFlipMaster(std::int32_t handle, std::int32_t masterHandle) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVideoOutClearFlipMaster() { +int APS5_VABI sceVideoOutClearFlipMaster(std::int32_t handle) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -37,35 +38,8 @@ int APS5_VABI sceVideoOutCursorSetPosition(int32_t, int32_t, uint32_t, uint32_t) return 0; } -int APS5_VABI sceVideoOutAdjustColor_(int handle, const VideoOutColorSettings* settings, uint32_t settings_size) { - if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutAdjustColor_: VIDEO_OUT_ERROR_INVALID_VALUE"); - return sceVideoOutAdjustColor(handle, settings); -} - -int APS5_VABI sceVideoOutColorSettingsSetGamma_(VideoOutColorSettings* settings, float gamma, uint32_t settings_size) { - if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutColorSettingsSetGamma_: VIDEO_OUT_ERROR_INVALID_VALUE"); - return sceVideoOutColorSettingsSetGamma(settings, gamma); -} - -int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) { - if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE"); - return sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion(settings); -} - -int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option, uint32_t settings_size) { - if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE"); - return sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion(settings, lumaScale, option); -} - -int APS5_VABI sceVideoOutAdjustColorSpaceConversion_(int handle, const VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) { - if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutAdjustColorSpaceConversion_: VIDEO_OUT_ERROR_INVALID_VALUE"); - return sceVideoOutAdjustColorSpaceConversion(handle, settings); -} - - int APS5_VABI sceDbgVideoOutAddOutputModeVr60Privilege() { NotImplemented_nid_no_patch(__func__); return 0; } - } diff --git a/core/libs/prx/libSceVideoOut/include/Buffer.hpp b/core/libs/prx/libSceVideoOut/include/Buffer.hpp index 5ae8013ae6..112e9cc682 100644 --- a/core/libs/prx/libSceVideoOut/include/Buffer.hpp +++ b/core/libs/prx/libSceVideoOut/include/Buffer.hpp @@ -3,23 +3,22 @@ #include -#include "prx/libc/include/General.hpp" #include "SceTypes.hpp" extern "C" { -int APS5_VABI sceVideoOutRegisterBuffers(int handle, int buffer_index_start, void* const* addresses, int buffer_num, const VideoOutBufferAttribute* attribute); -void APS5_VABI sceVideoOutSetBufferAttribute(VideoOutBufferAttribute* attribute, uint32_t pixel_format, uint32_t tiling_mode, uint32_t aspect_ratio, uint32_t width, uint32_t height, uint32_t pitch_in_pixel); -int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int set_index, int buffer_index_start, const VideoOutBuffers* buffers, int buffer_num, const VideoOutBufferAttribute2* attribute, int category, void* option); -void APS5_VABI sceVideoOutSetBufferAttribute2(VideoOutBufferAttribute2* attribute, uint64_t pixel_format, uint32_t tiling_mode, uint32_t width, uint32_t height, uint64_t option, uint32_t dcc_control, uint64_t dcc_cb_register_clear_color); +int APS5_VABI sceVideoOutRegisterBuffers(int handle, int bufferIndexStart, void* const* addresses, int bufferNum, const VideoOutBufferAttribute* attribute); +void APS5_VABI sceVideoOutSetBufferAttribute(VideoOutBufferAttribute* attribute, uint32_t pixelFormat, uint32_t tilingMode, uint32_t aspectRatio, uint32_t width, uint32_t height, uint32_t pitchInPixel); +int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIndexStart, const VideoOutBuffers* buffers, int bufferNum, const VideoOutBufferAttribute2* attribute, int category, void* option); +void APS5_VABI sceVideoOutSetBufferAttribute2(VideoOutBufferAttribute2* attribute, uint64_t pixelFormat, uint32_t tilingMode, uint32_t width, uint32_t height, uint64_t option, uint32_t dccControl, uint64_t dccCbRegisterClearColor); void APS5_VABI sceVideoOutAddBufferAttributeOption(VideoOutBufferAttribute2* attribute, uint64_t option); void APS5_VABI sceVideoOutRemoveBufferAttributeOption(VideoOutBufferAttribute2* attribute, uint64_t option); -int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int set_index, const VideoOutBufferAttribute2* attribute, void* option); -int APS5_VABI sceVideoOutSubmitEopFlip(int handle, int index, int flip_mode, int64_t flip_arg, std::uint32_t* token); -int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flip_mode, int64_t flip_arg); -int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int set_index); +int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int setIndex, const VideoOutBufferAttribute2* attribute, void* option); +int APS5_VABI sceVideoOutSubmitEopFlip(int handle, int index, int flipMode, int64_t flipArg, std::uint32_t* token); +int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t flipArg); +int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int setIndex); int APS5_VABI sceVideoOutGetBufferLabelAddress(int handle, uintptr_t* address); -#endif - } + +#endif diff --git a/core/libs/prx/libSceVideoOut/include/Event.hpp b/core/libs/prx/libSceVideoOut/include/Event.hpp index ca5e4f09f7..aea9cf2186 100644 --- a/core/libs/prx/libSceVideoOut/include/Event.hpp +++ b/core/libs/prx/libSceVideoOut/include/Event.hpp @@ -3,7 +3,6 @@ #include -#include "prx/libc/include/General.hpp" #include "SceTypes.hpp" extern "C" { @@ -24,6 +23,6 @@ int APS5_VABI sceVideoOutGetEventCount(const KernelEvent* ev); int APS5_VABI sceVideoOutGetEventData(const KernelEvent* ev, int64_t* data); int APS5_VABI sceVideoOutGetEventId(const KernelEvent* ev); -#endif - } + +#endif diff --git a/core/libs/prx/libSceVideoOut/include/Output.hpp b/core/libs/prx/libSceVideoOut/include/Output.hpp index 569e97ee77..caebc2514a 100644 --- a/core/libs/prx/libSceVideoOut/include/Output.hpp +++ b/core/libs/prx/libSceVideoOut/include/Output.hpp @@ -2,17 +2,16 @@ #define CORE_LIBS_PRX_LIBSCEVIDEOUOUT_INCLUDE_OUTPUT_HPP #include -#include "prx/libc/include/General.hpp" #include "SceTypes.hpp" int ValidateVideoOutOutputMode_nid_no_patch(uint64_t mode, const VideoOutOutputOptions* options, int displayRefreshRate); extern "C" { -int APS5_VABI sceVideoOutOpen(int user_id, int bus_type, int index, const void* param); +int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param); int APS5_VABI sceVideoOutClose(int handle); -int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reserved_ptr, uint64_t reserved); -int APS5_VABI sceVideoOutIsOutputSupported(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reserved_ptr, uint64_t reserved); +int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved); +int APS5_VABI sceVideoOutIsOutputSupported(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved); int APS5_VABI sceVideoOutInitializeOutputOptions(VideoOutOutputOptions* options); int APS5_VABI sceVideoOutAllowOutputResolutionWqhdDetection(int handle); int APS5_VABI sceVideoOutSetFlipRate(int handle, int rate); @@ -31,9 +30,9 @@ int APS5_VABI sceVideoOutAdjustColor(int handle, const VideoOutColorSettings* se int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion(VideoOutColorSpaceConversionSettings* settings); int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option); int APS5_VABI sceVideoOutAdjustColorSpaceConversion(int handle, const VideoOutColorSpaceConversionSettings* settings); -int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size); -int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option, uint32_t settings_size); -int APS5_VABI sceVideoOutAdjustColorSpaceConversion_(int handle, const VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size); +int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, uint32_t settingsSize); +int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option, uint32_t settingsSize); +int APS5_VABI sceVideoOutAdjustColorSpaceConversion_(int handle, const VideoOutColorSpaceConversionSettings* settings, uint32_t settingsSize); int APS5_VABI sceVideoOutSetGlobalBlendSpace(int handle, uint32_t space, uint32_t option); int APS5_VABI sceVideoOutVrrPegToFixedRate(int handle, uint64_t arg1, uint64_t arg2); int APS5_VABI sceVideoOutVrrUnpegFromFixedRate(int handle); diff --git a/core/libs/prx/libSceVideoOut/include/PadInput.hpp b/core/libs/prx/libSceVideoOut/include/PadInput.hpp index dfa068d16d..2f5eddb56c 100644 --- a/core/libs/prx/libSceVideoOut/include/PadInput.hpp +++ b/core/libs/prx/libSceVideoOut/include/PadInput.hpp @@ -5,7 +5,9 @@ #include "SDL_gamecontroller.h" #include "prx/libScePad/include/KeyboardMouse.hpp" #include "prx/libScePad/include/PadState.hpp" +#include "prx/libScePad/include/Pad.hpp" #include "prx/libSceVideoOut/include/PadOutputPacing.hpp" +#include #include #include "prx/libSceVideoOut/include/Telemetry.hpp" @@ -18,28 +20,37 @@ public: void HandleEvent(const SDL_Event& event, DisplayWindow& window); void Update(const DisplayWindow& window); bool MouseModeEnabled() const { return keyboardMouse.MouseMode(); } + bool SlotOccupied(int slot) const { + return slot >= 0 && slot < PAD_MAX_CONTROLLERS && slots[slot].controller != nullptr; + } private: + struct ControllerSlot { + SDL_GameController* controller = nullptr; + SDL_JoystickID instanceId = -1; + PadInputState state{}; + std::uint32_t outputSequence = 0; + PadOutputState outputState{}; + bool outputPending = false; + std::chrono::steady_clock::time_point nextRumbleRefresh{}; + LightBarPacer lightBar; + TriggerEffectCache triggerEffect; + }; + explicit PadInput(Pad::InputConfiguration configuration); void publish(std::uint64_t frame); + void publish(std::uint64_t frame, int slot); void setMouseMode(bool enabled); - void openFirstAvailableController(); + void openAvailableControllers(); void openController(int deviceIndex); - void closeController(); - PadInputState sampleController() const; + void closeController(int slot); + PadInputState sampleController(int slot) const; void applyOutput(); - void enableSensors(); + void enableSensors(int slot); Pad::KeyboardMouse keyboardMouse; - SDL_GameController* controller = nullptr; - PadInputState controllerState{}; - std::uint32_t outputSequence = 0; - PadOutputState outputState{}; - bool outputPending = false; + std::array slots{}; VideoOutTelemetry::PadChangeFilter telemetryFilter; - std::chrono::steady_clock::time_point nextRumbleRefresh{}; - LightBarPacer lightBar; - TriggerEffectCache triggerEffect; }; #endif diff --git a/core/libs/prx/libSceVideoOut/include/VideoOut.hpp b/core/libs/prx/libSceVideoOut/include/VideoOut.hpp new file mode 100644 index 0000000000..8f86fea2b9 --- /dev/null +++ b/core/libs/prx/libSceVideoOut/include/VideoOut.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideoOutSetFlipMaster(std::int32_t handle, std::int32_t masterHandle); +int APS5_VABI sceVideoOutClearFlipMaster(std::int32_t handle); +int APS5_VABI sceVideoOutCursorEnable(int32_t, int32_t, const void*); +int APS5_VABI sceVideoOutCursorDisable(int32_t, int32_t); +int APS5_VABI sceVideoOutCursorSetImageAddress(int32_t, int32_t, const void*); +int APS5_VABI sceVideoOutCursorSetPosition(int32_t, int32_t, uint32_t, uint32_t); +int APS5_VABI sceVideoOutAdjustColor_(int handle, const VideoOutColorSettings* settings, uint32_t settingsSize); +int APS5_VABI sceVideoOutColorSettingsSetGamma_(VideoOutColorSettings* settings, float gamma, uint32_t settingsSize); +int APS5_VABI sceDbgVideoOutAddOutputModeVr60Privilege(); +int APS5_VABI sceVideoOutAddVrrStatusFlagsPrivilege(); + +} diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index 7c868f3ce2..156e03c66d 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -96,6 +96,9 @@ static constexpr std::uint64_t VIDEO_OUT_OUTPUT_STATUS_FLAG_HDR = 1; static constexpr uint64_t VIDEO_OUT_REFRESH_RATE_59_94HZ = 3; static constexpr uint64_t VIDEO_OUT_REFRESH_RATE_119_88HZ = 13; +static constexpr std::uint32_t VIDEO_OUT_RESOLUTION_1080P = 1; +static constexpr std::uint32_t VIDEO_OUT_RESOLUTION_2160P = 2; + struct VideoOutBuffer { int groupIndex = -1; uint64_t dataAddress = 0; @@ -226,6 +229,7 @@ public: bool HasConfig(int handle); bool SupportsHighFrameRate(); int GetDisplayRefreshRate(); + std::uint32_t GetDisplayResolution(); // 0, or VIDEO_OUT_ERROR_FLIP_QUEUE_FULL when the title has VIDEO_OUT_FLIP_QUEUE_CAPACITY flips pending. int SubmitFlip(int handle, int index, int flipMode, int64_t flipArg); @@ -252,6 +256,7 @@ private: HostHdrProbe hdrProbe; std::uint64_t hdrGeneration = 0; std::atomic displayRefreshRate{0}; + std::atomic displayResolution{VIDEO_OUT_RESOLUTION_1080P}; std::jthread presentThread; std::jthread vblankThread; diff --git a/core/libs/prx/libSceVideoOut/libSceVideoOut.hpp b/core/libs/prx/libSceVideoOut/libSceVideoOut.hpp new file mode 100644 index 0000000000..d3328b1410 --- /dev/null +++ b/core/libs/prx/libSceVideoOut/libSceVideoOut.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "prx/libSceVideoOut/include/Buffer.hpp" +#include "prx/libSceVideoOut/include/Event.hpp" +#include "prx/libSceVideoOut/include/Output.hpp" +#include "prx/libSceVideoOut/include/VideoOut.hpp" diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index 7197986ca7..75c4e8a067 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/Buffer.hpp" #include #include #include diff --git a/core/libs/prx/libSceVideoOut/src/Event.cpp b/core/libs/prx/libSceVideoOut/src/Event.cpp index d13e6494fb..dde7b34821 100644 --- a/core/libs/prx/libSceVideoOut/src/Event.cpp +++ b/core/libs/prx/libSceVideoOut/src/Event.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/Event.hpp" #include #include #include diff --git a/core/libs/prx/libSceVideoOut/src/Output.cpp b/core/libs/prx/libSceVideoOut/src/Output.cpp index 24f9b6f15c..0ad46a4ed8 100644 --- a/core/libs/prx/libSceVideoOut/src/Output.cpp +++ b/core/libs/prx/libSceVideoOut/src/Output.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/VideoOut.hpp" #include #include #include @@ -19,7 +20,7 @@ static constexpr std::size_t VIDEO_OUT_OUTPUT_OPTIONS_FREE_WORD = 3; static VideoOutOutputStatus outputStatus(const auto& cfg) { VideoOutOutputStatus status{}; - status.resolution = (cfg.width >= 3840 || cfg.height >= 2160) ? 2u : 1u; + status.resolution = VideoOutDriver::Get().GetDisplayResolution(); const bool hdr = AgcDriver::CurrentHostHdr().available; status.dynamicRange = hdr ? VIDEO_OUT_DYNAMIC_RANGE_HDR : VIDEO_OUT_DYNAMIC_RANGE_SDR; status.refreshRate = (cfg.outputMode == VIDEO_OUT_OUTPUT_MODE_119_88HZ) ? VIDEO_OUT_REFRESH_RATE_119_88HZ : VIDEO_OUT_REFRESH_RATE_59_94HZ; @@ -370,6 +371,31 @@ int APS5_VABI sceVideoOutAdjustColorSpaceConversion(int handle, const VideoOutCo LibcAwaitExit_nid_postfix(); } +int APS5_VABI sceVideoOutAdjustColor_(int handle, const VideoOutColorSettings* settings, uint32_t settings_size) { + if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutAdjustColor_: VIDEO_OUT_ERROR_INVALID_VALUE"); + return sceVideoOutAdjustColor(handle, settings); +} + +int APS5_VABI sceVideoOutColorSettingsSetGamma_(VideoOutColorSettings* settings, float gamma, uint32_t settings_size) { + if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutColorSettingsSetGamma_: VIDEO_OUT_ERROR_INVALID_VALUE"); + return sceVideoOutColorSettingsSetGamma(settings, gamma); +} + +int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) { + if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE"); + return sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion(settings); +} + +int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option, uint32_t settings_size) { + if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE"); + return sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion(settings, lumaScale, option); +} + +int APS5_VABI sceVideoOutAdjustColorSpaceConversion_(int handle, const VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) { + if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutAdjustColorSpaceConversion_: VIDEO_OUT_ERROR_INVALID_VALUE"); + return sceVideoOutAdjustColorSpaceConversion(handle, settings); +} + int APS5_VABI sceVideoOutSetGlobalBlendSpace(int handle, uint32_t space, uint32_t option) try { auto cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { diff --git a/core/libs/prx/libSceVideoOut/src/PadInput.cpp b/core/libs/prx/libSceVideoOut/src/PadInput.cpp index d9abeeb048..5e9ac3fcf7 100644 --- a/core/libs/prx/libSceVideoOut/src/PadInput.cpp +++ b/core/libs/prx/libSceVideoOut/src/PadInput.cpp @@ -14,7 +14,9 @@ #include "prx/libSceAgcDriver/Execution/include/Presentation.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libScePad/include/PadState.hpp" +#include "prx/libScePad/include/Pad.hpp" #include "prx/libScePad/include/PadInputTypes.hpp" +#include "prx/libSceUserService/UserService.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceIme/include/ImeInput.hpp" @@ -22,15 +24,16 @@ PadInput::PadInput() : PadInput(Pad::LoadInputMapping()) { } PadInput::PadInput(Pad::InputConfiguration configuration) : keyboardMouse(std::move(configuration)) { - openFirstAvailableController(); + openAvailableControllers(); } PadInput::~PadInput() { - closeController(); + for (int i = 0; i < PAD_MAX_CONTROLLERS; ++i) { + closeController(i); + } } -void PadInput::openFirstAvailableController() { - if (controller != nullptr) return; +void PadInput::openAvailableControllers() { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); if ((SDL_WasInit(SDL_INIT_GAMECONTROLLER) & SDL_INIT_GAMECONTROLLER) == 0) { @@ -42,82 +45,122 @@ void PadInput::openFirstAvailableController() { for (int deviceIndex = 0; deviceIndex < SDL_NumJoysticks(); ++deviceIndex) { if (!SDL_IsGameController(deviceIndex)) continue; openController(deviceIndex); - if (controller != nullptr) return; } } void PadInput::openController(int deviceIndex) { - if (controller != nullptr || !SDL_IsGameController(deviceIndex)) return; - controller = SDL_GameControllerOpen(deviceIndex); - if (controller == nullptr) { + if (!SDL_IsGameController(deviceIndex)) return; + const auto instanceId = SDL_JoystickGetDeviceInstanceID(deviceIndex); + for (int i = 0; i < PAD_MAX_CONTROLLERS; ++i) { + if (slots[i].controller != nullptr && slots[i].instanceId == instanceId) return; + } + int slot = -1; + for (int i = 0; i < PAD_MAX_CONTROLLERS; ++i) { + if (slots[i].controller == nullptr) { + slot = i; + break; + } + } + if (slot == -1) return; + SDL_GameController* gc = SDL_GameControllerOpen(deviceIndex); + if (gc == nullptr) { APS5_LOG_ERR("Pad: could not open game controller %d: %s", deviceIndex, SDL_GetError()); return; } - const char* name = SDL_GameControllerName(controller); - APS5_LOG_OUT("Pad: connected game controller: %s (type %d, sensors accel=%d gyro=%d, touchpads=%d, led=%d, trigger rumble=%d)", - name != nullptr ? name : "unknown", static_cast(SDL_GameControllerGetType(controller)), - SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE, SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE, - SDL_GameControllerGetNumTouchpads(controller), SDL_GameControllerHasLED(controller) == SDL_TRUE, SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE); - enableSensors(); - outputPending = true; - lightBar.Reset(); - triggerEffect.Reset(); + slots[slot].controller = gc; + slots[slot].instanceId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gc)); + slots[slot].state = {}; + slots[slot].outputSequence = 0; + slots[slot].outputState = {}; + slots[slot].outputPending = true; + slots[slot].nextRumbleRefresh = {}; + slots[slot].lightBar.Reset(); + slots[slot].triggerEffect.Reset(); + const char* name = SDL_GameControllerName(gc); + APS5_LOG_OUT("Pad: connected game controller (slot %d): %s (type %d, sensors accel=%d gyro=%d, touchpads=%d, led=%d, trigger rumble=%d)", + slot, name != nullptr ? name : "unknown", static_cast(SDL_GameControllerGetType(gc)), + SDL_GameControllerHasSensor(gc, SDL_SENSOR_ACCEL) == SDL_TRUE, SDL_GameControllerHasSensor(gc, SDL_SENSOR_GYRO) == SDL_TRUE, + SDL_GameControllerGetNumTouchpads(gc), SDL_GameControllerHasLED(gc) == SDL_TRUE, SDL_GameControllerHasRumbleTriggers(gc) == SDL_TRUE); + enableSensors(slot); + PadSetPortConnected_nid_postfix(slot, true); + UserServiceSetUserConnected_nid_postfix(slot, true); } -void PadInput::enableSensors() { +void PadInput::enableSensors(int slot) { + if (slot < 0 || slot >= PAD_MAX_CONTROLLERS) return; + SDL_GameController* controller = slots[slot].controller; if (controller == nullptr) return; - const SDL_bool wanted = outputState.motionEnabled ? SDL_TRUE : SDL_FALSE; + const SDL_bool wanted = slots[slot].outputState.motionEnabled ? SDL_TRUE : SDL_FALSE; if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE) SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, wanted); if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE) SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, wanted); } -void PadInput::closeController() { - if (controller == nullptr) return; - SDL_GameControllerClose(controller); - controller = nullptr; - controllerState = {}; +void PadInput::closeController(int slot) { + if (slot < 0 || slot >= PAD_MAX_CONTROLLERS) return; + if (slots[slot].controller == nullptr) return; + SDL_GameControllerClose(slots[slot].controller); + slots[slot].controller = nullptr; + slots[slot].instanceId = -1; + slots[slot].state = {}; + slots[slot].outputState = {}; + slots[slot].outputPending = false; + if (slot > 0) { + PadSetPortConnected_nid_postfix(slot, false); + UserServiceSetUserConnected_nid_postfix(slot, false); + } } void PadInput::applyOutput() { - PadOutputState fetched; - if (PadFetchOutput_nid_postfix(&outputSequence, &fetched)) { - const bool motionChanged = fetched.motionEnabled != outputState.motionEnabled; - outputState = fetched; - outputPending = true; - if (motionChanged) enableSensors(); - } - if (controller == nullptr) return; - const auto now = std::chrono::steady_clock::now(); - const bool isPs5 = SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_PS5; - const auto large = std::max(outputState.vibrationLarge, outputState.audioVibrationLarge); - const auto small = std::max(outputState.vibrationSmall, outputState.audioVibrationSmall); - const bool rumbling = large != 0 || small != 0; - const bool triggerRumble = outputState.trigger[0].fallback != 0 || outputState.trigger[1].fallback != 0; - if (SDL_GameControllerHasLED(controller) == SDL_TRUE) { - const LightBarPacer::Color color = outputState.lightBarValid - ? LightBarPacer::Color{outputState.lightBar[0], outputState.lightBar[1], outputState.lightBar[2]} - : LightBarPacer::Color{0, 64, 255}; - if (lightBar.Due(color, now)) lightBar.Result(color, now, SDL_GameControllerSetLED(controller, color[0], color[1], color[2]) == 0); - } - if (!outputPending) { - if ((rumbling || (triggerRumble && !isPs5)) && now >= nextRumbleRefresh) outputPending = true; - else return; - } - outputPending = false; - nextRumbleRefresh = now + std::chrono::milliseconds(700); - constexpr Uint32 rumbleMs = 2000; - SDL_GameControllerRumble(controller, static_cast(large * 257), static_cast(small * 257), rumbling ? rumbleMs : 0); - if (outputState.triggerTouched) { - if (isPs5) { - TriggerEffectCache::Effect effect{}; - effect[0] = 0x04 | 0x08; - std::memcpy(effect.data() + 10, outputState.trigger[1].effect, 11); - std::memcpy(effect.data() + 21, outputState.trigger[0].effect, 11); - if (triggerEffect.NeedsSend(effect)) { - triggerEffect.Result(effect, SDL_GameControllerSendEffect(controller, effect.data(), static_cast(effect.size())) == 0); + for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) { + PadOutputState fetched; + if (PadFetchOutputForPort_nid_postfix(slot, &slots[slot].outputSequence, &fetched)) { + const bool motionChanged = fetched.motionEnabled != slots[slot].outputState.motionEnabled; + slots[slot].outputState = fetched; + slots[slot].outputPending = true; + if (motionChanged) enableSensors(slot); + } + SDL_GameController* controller = slots[slot].controller; + if (controller == nullptr) continue; + const auto now = std::chrono::steady_clock::now(); + const bool isPs5 = SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_PS5; + const auto large = std::max(slots[slot].outputState.vibrationLarge, slots[slot].outputState.audioVibrationLarge); + const auto small = std::max(slots[slot].outputState.vibrationSmall, slots[slot].outputState.audioVibrationSmall); + const bool rumbling = large != 0 || small != 0; + const bool triggerRumble = slots[slot].outputState.trigger[0].fallback != 0 || slots[slot].outputState.trigger[1].fallback != 0; + if (SDL_GameControllerHasLED(controller) == SDL_TRUE) { + struct PlayerLedColor { Uint8 r, g, b; }; + static constexpr PlayerLedColor defaultColors[4] = { + {0, 64, 255}, + {255, 0, 0}, + {0, 255, 0}, + {255, 0, 128} + }; + const auto& def = defaultColors[slot % 4]; + const LightBarPacer::Color color = slots[slot].outputState.lightBarValid + ? LightBarPacer::Color{slots[slot].outputState.lightBar[0], slots[slot].outputState.lightBar[1], slots[slot].outputState.lightBar[2]} + : LightBarPacer::Color{def.r, def.g, def.b}; + if (slots[slot].lightBar.Due(color, now)) slots[slot].lightBar.Result(color, now, SDL_GameControllerSetLED(controller, color[0], color[1], color[2]) == 0); + } + if (!slots[slot].outputPending) { + if ((rumbling || (triggerRumble && !isPs5)) && now >= slots[slot].nextRumbleRefresh) slots[slot].outputPending = true; + else continue; + } + slots[slot].outputPending = false; + slots[slot].nextRumbleRefresh = now + std::chrono::milliseconds(700); + constexpr Uint32 rumbleMs = 2000; + SDL_GameControllerRumble(controller, static_cast(large * 257), static_cast(small * 257), rumbling ? rumbleMs : 0); + if (slots[slot].outputState.triggerTouched) { + if (isPs5) { + TriggerEffectCache::Effect effect{}; + effect[0] = 0x04 | 0x08; + std::memcpy(effect.data() + 10, slots[slot].outputState.trigger[1].effect, 11); + std::memcpy(effect.data() + 21, slots[slot].outputState.trigger[0].effect, 11); + if (slots[slot].triggerEffect.NeedsSend(effect)) { + slots[slot].triggerEffect.Result(effect, SDL_GameControllerSendEffect(controller, effect.data(), static_cast(effect.size())) == 0); + } + } else if (SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE) { + SDL_GameControllerRumbleTriggers(controller, static_cast(slots[slot].outputState.trigger[0].fallback * 257), static_cast(slots[slot].outputState.trigger[1].fallback * 257), triggerRumble ? rumbleMs : 0); } - } else if (SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE) { - SDL_GameControllerRumbleTriggers(controller, static_cast(outputState.trigger[0].fallback * 257), static_cast(outputState.trigger[1].fallback * 257), triggerRumble ? rumbleMs : 0); } } } @@ -129,10 +172,12 @@ void PadInput::setMouseMode(bool enabled) { keyboardMouse.SetMouseMode(enabled); } -PadInputState PadInput::sampleController() const { +PadInputState PadInput::sampleController(int slot) const { PadInputState result; + if (slot < 0 || slot >= PAD_MAX_CONTROLLERS) return result; + SDL_GameController* controller = slots[slot].controller; if (controller == nullptr) return result; - const auto readButton = [this](SDL_GameControllerButton button) { + const auto readButton = [controller](SDL_GameControllerButton button) { return SDL_GameControllerGetButton(controller, button) != 0; }; const auto addButton = [&result, &readButton](SDL_GameControllerButton source, Pad::PadButton button) { @@ -156,7 +201,7 @@ PadInputState PadInput::sampleController() const { addButton(SDL_CONTROLLER_BUTTON_DPAD_LEFT, Pad::PadButton::Left); addButton(SDL_CONTROLLER_BUTTON_TOUCHPAD, Pad::PadButton::TouchPad); - const auto triggerValue = [this](SDL_GameControllerAxis axis) { + const auto triggerValue = [controller](SDL_GameControllerAxis axis) { const auto value = std::clamp(SDL_GameControllerGetAxis(controller, axis), 0, 32767); return static_cast((value * 255 + 16383) / 32767); }; @@ -165,7 +210,7 @@ PadInputState PadInput::sampleController() const { if (result.analogButtonsL2 != 0) result.buttons |= static_cast(Pad::PadButton::L2); if (result.analogButtonsR2 != 0) result.buttons |= static_cast(Pad::PadButton::R2); - const auto stick = [this](SDL_GameControllerAxis xAxis, SDL_GameControllerAxis yAxis, int deadzonePercent) { + const auto stick = [this, controller](SDL_GameControllerAxis xAxis, SDL_GameControllerAxis yAxis, int deadzonePercent) { return Pad::StickWithDeadzone(SDL_GameControllerGetAxis(controller, xAxis), SDL_GameControllerGetAxis(controller, yAxis), deadzonePercent); }; const auto left = stick(SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, keyboardMouse.Configuration().settings.leftStickDeadzonePercent); @@ -207,23 +252,34 @@ void PadInput::HandleEvent(const SDL_Event& event, DisplayWindow& window) { openController(event.cdevice.which); return; } - if (event.type == SDL_CONTROLLERDEVICEREMOVED && controller != nullptr) { - const auto instanceId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)); - if (instanceId == event.cdevice.which) { - closeController(); - openFirstAvailableController(); - publish(window.FrameNumber()); + if (event.type == SDL_CONTROLLERDEVICEREMOVED) { + for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) { + if (slots[slot].controller != nullptr && slots[slot].instanceId == event.cdevice.which) { + closeController(slot); + publish(window.FrameNumber(), slot); + break; + } } return; } if (event.type == SDL_CONTROLLERBUTTONDOWN || event.type == SDL_CONTROLLERBUTTONUP || event.type == SDL_CONTROLLERAXISMOTION || event.type == SDL_CONTROLLERDEVICEREMAPPED) { - publish(window.FrameNumber()); + SDL_JoystickID which = -1; + if (event.type == SDL_CONTROLLERBUTTONDOWN || event.type == SDL_CONTROLLERBUTTONUP) which = event.cbutton.which; + else if (event.type == SDL_CONTROLLERAXISMOTION) which = event.caxis.which; + else if (event.type == SDL_CONTROLLERDEVICEREMAPPED) which = event.cdevice.which; + for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) { + if (slots[slot].controller != nullptr && slots[slot].instanceId == which) { + slots[slot].state = sampleController(slot); + publish(window.FrameNumber(), slot); + return; + } + } return; } if (event.type == SDL_WINDOWEVENT) { if (keyboardMouse.HandleEvent(event, std::chrono::steady_clock::now()).releaseMouse) setMouseMode(false); - publish(window.FrameNumber()); + publish(window.FrameNumber(), 0); return; } // APS5_NO_PAD_INPUT=1 keeps a measurement run from reacting to keys or mouse buttons that reach @@ -239,25 +295,54 @@ void PadInput::HandleEvent(const SDL_Event& event, DisplayWindow& window) { if (requests.togglePerformanceOverlay && window.Handle() != nullptr && event.key.windowID == SDL_GetWindowID(window.Handle())) AgcDriverTogglePerformanceOverlay_nid_postfix(); if (requests.screenshot && window.Handle() != nullptr && event.key.windowID == SDL_GetWindowID(window.Handle())) AgcDriverRequestScreenshot_nid_postfix(); if (requests.toggleMouse) setMouseMode(!keyboardMouse.MouseMode()); - if (event.type != SDL_MOUSEMOTION) publish(window.FrameNumber()); + if (event.type != SDL_MOUSEMOTION) publish(window.FrameNumber(), 0); } void PadInput::Update(const DisplayWindow& window) { - if (controller != nullptr) SDL_GameControllerUpdate(); + bool anyController = false; + for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) { + if (slots[slot].controller != nullptr) { + anyController = true; + break; + } + } + if (anyController) SDL_GameControllerUpdate(); applyOutput(); - if (controller != nullptr) controllerState = sampleController(); + for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) { + if (slots[slot].controller != nullptr) { + slots[slot].state = sampleController(slot); + } + } if (keyboardMouse.MouseMode() && SDL_GetKeyboardFocus() == nullptr) { keyboardMouse.Release(); setMouseMode(false); - publish(window.FrameNumber()); + publish(window.FrameNumber(), 0); return; } const bool changed = keyboardMouse.Advance(std::chrono::steady_clock::now()); - if (controller != nullptr || keyboardMouse.MouseMode() || changed) publish(window.FrameNumber()); + if (slots[0].controller != nullptr || keyboardMouse.MouseMode() || changed) { + publish(window.FrameNumber(), 0); + } + for (int slot = 1; slot < PAD_MAX_CONTROLLERS; ++slot) { + if (slots[slot].controller != nullptr) { + publish(window.FrameNumber(), slot); + } + } } void PadInput::publish(std::uint64_t frame) { - const auto state = keyboardMouse.Compose(controllerState); - PadPublishInput_nid_postfix(state); - if (auto* telemetry = VideoOutTelemetry::Instance(); telemetry != nullptr) VideoOutTelemetry::EmitPadIfChanged(*telemetry, telemetryFilter, frame, state); + for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) { + publish(frame, slot); + } +} + +void PadInput::publish(std::uint64_t frame, int slot) { + if (slot < 0 || slot >= PAD_MAX_CONTROLLERS) return; + if (slot == 0) { + const auto state = keyboardMouse.Compose(slots[0].state); + PadPublishInput_nid_postfix(state); + if (auto* telemetry = VideoOutTelemetry::Instance(); telemetry != nullptr) VideoOutTelemetry::EmitPadIfChanged(*telemetry, telemetryFilter, frame, state); + } else { + PadPublishInputForPort_nid_postfix(slot, slots[slot].state); + } } diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index f308f42739..377fb3548b 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -6,6 +7,7 @@ #include #include #include +#include #include #include @@ -41,6 +43,26 @@ void require(bool condition, const char* reason) { if (!condition) throw std::runtime_error(std::string("VideoOut: ") + reason); } +std::uint32_t resolutionIndex(std::uint32_t width, std::uint32_t height) { + return (width >= 3840 || height >= 2160) ? VIDEO_OUT_RESOLUTION_2160P : VIDEO_OUT_RESOLUTION_1080P; +} + +std::optional configuredResolution() { + const char* value = std::getenv("APS5_OUTPUT_RESOLUTION"); + if (value == nullptr || *value == '\0') return std::nullopt; + const std::string text(value); + const char* end = text.data() + text.size(); + std::uint32_t width = 0; + std::uint32_t height = 0; + const auto widthResult = std::from_chars(text.data(), end, width); + const bool separated = widthResult.ec == std::errc{} && widthResult.ptr != end && (*widthResult.ptr == 'x' || *widthResult.ptr == 'X'); + const auto heightResult = separated ? std::from_chars(widthResult.ptr + 1, end, height) : std::from_chars_result{end, std::errc::invalid_argument}; + if (!separated || heightResult.ec != std::errc{} || heightResult.ptr != end || width == 0 || height == 0) { + throw std::runtime_error("VideoOut: APS5_OUTPUT_RESOLUTION must be x with positive integers, got \"" + text + "\""); + } + return resolutionIndex(width, height); +} + std::vector windowExtensions(SDL_Window* window) { unsigned extensionCount = 0; if (!SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr)) throw std::runtime_error(std::string("SDL_Vulkan_GetInstanceExtensions failed: ") + SDL_GetError()); @@ -489,12 +511,18 @@ int VideoOutDriver::GetDisplayRefreshRate() { return displayRefreshRate.load(); } +std::uint32_t VideoOutDriver::GetDisplayResolution() { + if (const auto configured = configuredResolution()) return *configured; + return displayResolution.load(); +} + void VideoOutDriver::updateDisplayRefreshRate() { const int display = SDL_GetWindowDisplayIndex(window.Handle()); if (display < 0) throw std::runtime_error(std::string("SDL_GetWindowDisplayIndex failed: ") + SDL_GetError()); SDL_DisplayMode mode{}; if (SDL_GetCurrentDisplayMode(display, &mode) != 0) throw std::runtime_error(std::string("SDL_GetCurrentDisplayMode failed: ") + SDL_GetError()); displayRefreshRate.store(mode.refresh_rate); + displayResolution.store(resolutionIndex(mode.w, mode.h)); } void VideoOutDriver::pollHostHdr() { diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index 59241e643b..e7ce91e7b8 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -1,6 +1,8 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libSceVideoOut/include/VideoOutDriver.hpp" #include "prx/libSceVideoOut/include/Buffer.hpp" #include "prx/libSceVideoOut/include/Output.hpp" +#include "prx/libSceVideoOut/include/VideoOut.hpp" #include "prx/libSceVideoOut/include/Event.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" @@ -25,11 +27,6 @@ #undef main -extern "C" int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -extern "C" int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); -extern "C" int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -extern "C" int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); - namespace { void check(bool condition, const char* reason) { @@ -449,6 +446,22 @@ void testControls() { } VideoOutColorSettings settings{}; expectFailure([&] { sceVideoOutColorSettingsSetGamma(&settings, std::numeric_limits::quiet_NaN()); }); + expectFailure([&] { sceVideoOutColorSettingsSetGamma_(&settings, 1.0f, sizeof(VideoOutColorSettings) - 1); }); + check(sceVideoOutColorSettingsSetGamma_(&settings, 1.2f, sizeof(VideoOutColorSettings)) == 0, "gamma wrapper rejected valid size"); + check(settings.gamma == 1.2f, "gamma wrapper failed to forward value"); + + expectFailure([&] { sceVideoOutAdjustColor_(handle, &settings, sizeof(VideoOutColorSettings) - 1); }); + check(sceVideoOutAdjustColor_(handle, &settings, sizeof(VideoOutColorSettings)) == 0, "adjust color wrapper rejected valid size"); + + VideoOutColorSpaceConversionSettings cscSettings{}; + expectFailure([&] { sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(&cscSettings, sizeof(VideoOutColorSpaceConversionSettings) - 1); }); + check(sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(&cscSettings, sizeof(VideoOutColorSpaceConversionSettings)) == 0, "default SDR to HDR wrapper rejected valid size"); + + expectFailure([&] { sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(&cscSettings, 1.0f, 0, sizeof(VideoOutColorSpaceConversionSettings) - 1); }); + check(sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(&cscSettings, 1.0f, 0, sizeof(VideoOutColorSpaceConversionSettings)) == 0, "SDR to HDR conversion wrapper rejected valid size"); + + expectFailure([&] { sceVideoOutAdjustColorSpaceConversion_(handle, &cscSettings, sizeof(VideoOutColorSpaceConversionSettings) - 1); }); + check(sceVideoOutAdjustColorSpaceConversion_(handle, &cscSettings, sizeof(VideoOutColorSpaceConversionSettings)) == 0, "adjust color space conversion wrapper rejected valid size"); KernelEqueue queue = 0; check(sceKernelCreateEqueue(&queue, "VideoOut test") == 0, "event queue creation failed"); auto owner = EqueuePin_nid_postfix(queue); @@ -479,10 +492,12 @@ void testControls() { check(owner->GetTriggeredEvents(&event, 1) == 1 && event.udata == &settings && sceVideoOutGetEventId(&event) == VIDEO_OUT_EVENT_VBLANK, "vblank event or updated user data missing"); check(sceVideoOutAddPreVblankStartEvent(queue, handle, &mode) == 0, "pre-vblank subscription failed"); check(sceVideoOutWaitVblank(handle) == 0, "vblank wait failed"); - KernelEvent events[2]{}; - check(owner->GetTriggeredEvents(events, 2) == 2, "pre-vblank or vblank event missing"); - const auto& preVblank = sceVideoOutGetEventId(&events[0]) == VIDEO_OUT_EVENT_PRE_VBLANK_START ? events[0] : events[1]; - check(sceVideoOutGetEventId(&preVblank) == VIDEO_OUT_EVENT_PRE_VBLANK_START && preVblank.udata == &mode, "pre-vblank event missing"); + std::array events{}; + const int collected = owner->GetTriggeredEvents(events.data(), static_cast(events.size())); + check(collected >= 2, "pre-vblank or vblank event missing"); + const auto end = events.begin() + collected; + check(std::any_of(events.begin(), end, [&](KernelEvent& item) { return sceVideoOutGetEventId(&item) == VIDEO_OUT_EVENT_VBLANK && item.udata == &settings; }), "vblank event missing"); + check(std::any_of(events.begin(), end, [&](KernelEvent& item) { return sceVideoOutGetEventId(&item) == VIDEO_OUT_EVENT_PRE_VBLANK_START && item.udata == &mode; }), "pre-vblank event missing"); check(sceVideoOutDeletePreVblankStartEvent(queue, handle) == 0, "pre-vblank unsubscription failed"); std::vector allocation(65536 + 65535); const auto storage = alignedBuffer(allocation); @@ -1020,6 +1035,81 @@ void testOneDevice() { check(next.Serial() == 2, "the GPU work and the flip used more than one device"); } +void testOutputResolution() { + const int handle = sceVideoOutOpen(255, 0, 0, nullptr); + auto cfg = VideoOutDriver::Get().GetConfig(handle); + SDL_DisplayMode mode{}; + check(SDL_GetCurrentDisplayMode(0, &mode) == 0, "the display mode could not be read"); + const std::uint32_t expected = (mode.w >= 3840 || mode.h >= 2160) ? 2u : 1u; + VideoOutOutputStatus status{}; + check(sceVideoOutGetOutputStatus(handle, &status) == 0 && status.resolution == expected, "a port without buffers did not report the display's resolution"); + const std::uint32_t width = expected == 2u ? 1920u : 3840u; + const std::uint32_t height = expected == 2u ? 1080u : 2160u; + std::vector allocation(static_cast(width) * height * 4 + 65535); + const auto storage = alignedBuffer(allocation); + VideoOutBuffers buffer{storage.data(), nullptr, {nullptr, nullptr}}; + VideoOutBufferAttribute2 attribute{}; + attribute.width = width; + attribute.height = height; + attribute.tiling_mode = 1; + attribute.pitch_in_pixel = width; + attribute.pixel_format = 0x8000000000000000ull; + sceVideoOutRegisterBuffers2(handle, 0, 0, &buffer, 1, &attribute, 0, nullptr); + check(sceVideoOutSubmitFlip(handle, 0, 1, 1) == 0, "the flip was not accepted"); + { + std::unique_lock lock(cfg->mutex); + const bool done = cfg->vblankCond.wait_for(lock, std::chrono::seconds(15), [&] { return cfg->failure || cfg->flipStatus.count == 1; }); + if (cfg->failure) std::rethrow_exception(cfg->failure); + check(done, "the flip did not complete"); + check(cfg->width == width && cfg->height == height, "the port did not record the buffer size"); + } + check(sceVideoOutGetOutputStatus(handle, &status) == 0 && status.resolution == expected, "the flipped buffer's size changed the reported resolution"); + sceVideoOutUnregisterBuffers(handle, 0); + sceVideoOutClose(handle); + LibcRunShutdown_nid_postfix(); +} + +void setOutputResolution(const char* value) { +#ifdef _WIN32 + check(_putenv_s("APS5_OUTPUT_RESOLUTION", value) == 0, "APS5_OUTPUT_RESOLUTION was not set"); +#else + check((*value != '\0' ? setenv("APS5_OUTPUT_RESOLUTION", value, 1) : unsetenv("APS5_OUTPUT_RESOLUTION")) == 0, "APS5_OUTPUT_RESOLUTION was not set"); +#endif +} + +void testOutputResolutionOverride() { + const int handle = sceVideoOutOpen(255, 0, 0, nullptr); + SDL_DisplayMode mode{}; + check(SDL_GetCurrentDisplayMode(0, &mode) == 0, "the display mode could not be read"); + const std::uint32_t following = (mode.w >= 3840 || mode.h >= 2160) ? 2u : 1u; + const auto reported = [&] { + VideoOutOutputStatus status{}; + check(sceVideoOutGetOutputStatus(handle, &status) == 0, "the output status was not returned"); + return status.resolution; + }; + setOutputResolution(""); + check(reported() == following, "an unset override changed the display's resolution"); + const std::pair overrides[] = {{"1920x1080", 1u}, {"1280x720", 1u}, {"3840x2160", 2u}, {"2560x2160", 2u}, {"3840X1", 2u}}; + for (const auto& [value, expected] : overrides) { + setOutputResolution(value); + check(reported() == expected, (std::string("the override ") + value + " did not set the reported resolution").c_str()); + std::array raw{}; + VideoOutOutputStatus withVrr{}; + check(sceVideoOutGetOutputStatusWithVrr(handle, raw.data()) == 0, "the VRR status was not returned"); + std::memcpy(&withVrr, raw.data(), sizeof(withVrr)); + check(withVrr.resolution == expected,(std::string("the override ") + value + " did not reach the VRR status").c_str()); + } + for (const char* value : {"1920", "1920x", "x1080", "0x1080", "1920x0", "1920x1080x2", " 1920x1080", "1920x1080 ", "-1920x1080", "1920*1080", "4294967296x1080", "high"}) { + setOutputResolution(value); + const auto message = expectFailure([&] { reported(); }); + check(message.find("APS5_OUTPUT_RESOLUTION") != std::string::npos && message.find(value) != std::string::npos, (std::string("the malformed value ") + value + " was not rejected by name").c_str()); + } + setOutputResolution(""); + check(reported() == following, "clearing the override did not restore the display's resolution"); + sceVideoOutClose(handle); + LibcRunShutdown_nid_postfix(); +} + } int main(int argc, char** argv) { @@ -1045,6 +1135,8 @@ int main(int argc, char** argv) { else if (argc == 2 && std::string(argv[1]) == "unregistered") testUnregisteredBuffer(); else if (argc == 2 && std::string(argv[1]) == "ps4") testPs4Buffers(); else if (argc == 2 && std::string(argv[1]) == "cpuflip") testCpuFlipOrder(); + else if (argc == 2 && std::string(argv[1]) == "resolution") testOutputResolution(); + else if (argc == 2 && std::string(argv[1]) == "resolution_override") testOutputResolutionOverride(); else testLifetime(argc == 2 && std::string(argv[1]) == "reopen"); std::puts("VideoOut flip tests passed"); return 0; @@ -1052,7 +1144,9 @@ int main(int argc, char** argv) { std::fprintf(stderr, "%s\n", error.what()); try { LibcRunShutdown_nid_postfix(); } catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); } - if (std::string(error.what()).find("Vulkan support") != std::string::npos && !std::getenv("ANYPS5_REQUIRE_DISPLAY")) { + const std::string what = error.what(); + const bool noVulkanWindow = what.find("Vulkan support") != std::string::npos || (what.starts_with("DisplayWindow: ") && what.find("Vulkan") != std::string::npos); + if (noVulkanWindow && !std::getenv("ANYPS5_REQUIRE_DISPLAY")) { std::printf("skipped, no display or Vulkan device: %s\n", error.what()); return 77; } diff --git a/core/libs/prx/libSceVideoOut/tests/FrameDumpQueue.cpp b/core/libs/prx/libSceVideoOut/tests/FrameDumpQueue.cpp index a5105da79f..b98c07e1be 100644 --- a/core/libs/prx/libSceVideoOut/tests/FrameDumpQueue.cpp +++ b/core/libs/prx/libSceVideoOut/tests/FrameDumpQueue.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -28,11 +29,16 @@ namespace { constexpr std::uint32_t Width = 3840; constexpr std::uint32_t Height = 2160; -constexpr std::uint32_t Scale = 4; -constexpr std::uint32_t DumpWidth = Width / Scale; -constexpr std::uint32_t DumpHeight = Height / Scale; -constexpr std::size_t DumpBytes = 54 + static_cast(DumpWidth) * DumpHeight * 4; +std::uint32_t scaleFromEnvironment() { + const char* value = std::getenv("APS5_DUMP_SCALE"); + return value != nullptr ? static_cast(std::strtoul(value, nullptr, 10)) : 4u; +} +const std::uint32_t Scale = scaleFromEnvironment(); +const std::uint32_t DumpWidth = Width / Scale; +const std::uint32_t DumpHeight = Height / Scale; +const std::size_t DumpBytes = 54 + static_cast(DumpWidth) * DumpHeight * 4; constexpr int Frames = 4; +bool WaitMode = false; void check(bool condition, const char* reason) { if (!condition) throw std::runtime_error(reason); @@ -137,6 +143,13 @@ void testDumpQueue() { check(sceVideoOutRegisterBuffers2(handle, 1, 1, &linearBuffer, 1, &linearAttribute, 0, nullptr) == 0, "linear buffer registration failed"); const HeldDump first(0); const HeldDump last(Frames - 1); + std::vector firstDump; + std::thread reader; + const auto started = std::chrono::steady_clock::now(); + if (WaitMode) reader = std::thread([&] { + std::this_thread::sleep_for(std::chrono::seconds(2)); + firstDump = first.Release(); + }); std::exception_ptr failure; try { for (int frame = 0; frame < Frames; ++frame) { @@ -149,8 +162,10 @@ void testDumpQueue() { } catch (...) { failure = std::current_exception(); } - const auto firstDump = first.Release(); + if (WaitMode) reader.join(); + else firstDump = first.Release(); if (failure) std::rethrow_exception(failure); + if (WaitMode) check(std::chrono::steady_clock::now() - started >= std::chrono::milliseconds(1500), "the presenter did not wait for the writer"); checkDump(firstDump, tiledPixels); const auto lastDump = last.Release(); checkDump(lastDump, linearPixels); @@ -164,11 +179,13 @@ void testDumpQueue() { } -int main() { +int main(int argc, char** argv) { + WaitMode = argc > 1 && std::strcmp(argv[1], "wait") == 0; VideoOutTestEnvironment environment; try { + if (WaitMode) check(Scale == 1, "the wait test needs APS5_DUMP_SCALE=1"); testDumpQueue(); - std::puts("VideoOut frame dump queue test passed"); + std::puts(WaitMode ? "VideoOut frame dump wait test passed" : "VideoOut frame dump queue test passed"); return 0; } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); diff --git a/core/libs/prx/libSceVideoOut/tests/InputMapping.cpp b/core/libs/prx/libSceVideoOut/tests/InputMapping.cpp index 3497443fcc..6b0c4348ed 100644 --- a/core/libs/prx/libSceVideoOut/tests/InputMapping.cpp +++ b/core/libs/prx/libSceVideoOut/tests/InputMapping.cpp @@ -18,6 +18,14 @@ void require(bool condition, const char* message) { std::filesystem::path root; +void setConfigPath(const std::filesystem::path& path) { +#ifdef _WIN32 + require(_wputenv_s(L"ANYPS5_INPUT_CONFIG", path.wstring().c_str()) == 0, "set ANYPS5_INPUT_CONFIG"); +#else + require(::setenv("ANYPS5_INPUT_CONFIG", path.string().c_str(), 1) == 0, "set ANYPS5_INPUT_CONFIG"); +#endif +} + void useConfig(const std::string& text) { const auto path = root / "anyps5-input.ini"; { @@ -25,11 +33,7 @@ void useConfig(const std::string& text) { file << text; require(file.good(), "write the input mapping"); } -#ifdef _WIN32 - require(_putenv_s("ANYPS5_INPUT_CONFIG", path.string().c_str()) == 0, "set ANYPS5_INPUT_CONFIG"); -#else - require(::setenv("ANYPS5_INPUT_CONFIG", path.string().c_str(), 1) == 0, "set ANYPS5_INPUT_CONFIG"); -#endif + setConfigPath(path); } bool rejects(const std::string& text) { @@ -140,14 +144,6 @@ void mouseLookReject() { require(rejects("MouseDecayMs = 20\nmousedecayms = 30\n"), "a mouse setting given twice is rejected"); } -void setConfigPath(const std::filesystem::path& path) { -#ifdef _WIN32 - require(_wputenv_s(L"ANYPS5_INPUT_CONFIG", path.wstring().c_str()) == 0, "set ANYPS5_INPUT_CONFIG"); -#else - require(::setenv("ANYPS5_INPUT_CONFIG", path.string().c_str(), 1) == 0, "set ANYPS5_INPUT_CONFIG"); -#endif -} - void nonAsciiPaths() { const std::u8string name = u8"anyps5-input-\u00fc-\u65e5\u672c-" + std::u8string(root.filename().u8string()); const auto directory = root / std::filesystem::path(name); diff --git a/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp b/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp index 759c9708e7..12c9119998 100644 --- a/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp +++ b/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMouse/include/Api.hpp" #include "prx/libSceVideoOut/include/MouseInput.hpp" #include "prx/libSceMouse/include/mouse_structs.h" #include "prx/libc/include/general/VabiMacros.hpp" @@ -9,13 +10,6 @@ #include #undef main -extern "C" { -int APS5_VABI sceMouseInit(); -int APS5_VABI sceMouseOpen(int userId, std::int32_t type, std::int32_t index, const void* param); -int APS5_VABI sceMouseRead(std::int32_t handle, MouseData* data, std::int32_t num); -int APS5_VABI sceMouseClose(std::int32_t handle); -} - namespace { void Require(bool condition, const char* reason) { diff --git a/core/libs/prx/libSceVideoOut/tests/OutputModePolicy.cpp b/core/libs/prx/libSceVideoOut/tests/OutputModePolicy.cpp index 5a4a600944..59fafc5235 100644 --- a/core/libs/prx/libSceVideoOut/tests/OutputModePolicy.cpp +++ b/core/libs/prx/libSceVideoOut/tests/OutputModePolicy.cpp @@ -1,6 +1,7 @@ #include "prx/libSceVideoOut/include/Output.hpp" #include #include +#include #include static void Require(bool value, const char* reason) { diff --git a/core/libs/prx/libSceVideoOut/tests/PadSdl.cpp b/core/libs/prx/libSceVideoOut/tests/PadSdl.cpp index cec6638fbd..3bdec619d1 100644 --- a/core/libs/prx/libSceVideoOut/tests/PadSdl.cpp +++ b/core/libs/prx/libSceVideoOut/tests/PadSdl.cpp @@ -1,13 +1,99 @@ #include "SDL_config.h" - +#include "SDL.h" +#include "SDL_joystick.h" +#include "SDL_gamecontroller.h" +#include "prx/libSceVideoOut/include/PadInput.hpp" +#include "prx/libSceVideoOut/include/DisplayWindow.hpp" +#include "prx/libScePad/include/PadState.hpp" #include +#include +#undef main + +namespace { + +static void Require(bool condition) { + if (!condition) { + std::abort(); + } +} + +} int main() { #ifdef SDL_JOYSTICK_HIDAPI std::puts("pad_sdl: SDL drives controllers through HIDAPI"); - return 0; #else std::fputs("pad_sdl: SDL is built without HIDAPI, so a DualSense gets no light bar, touchpad or trigger effects\n", stderr); return 1; #endif + + Require(SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK) == 0); + + SDL_VirtualJoystickDesc desc{}; + desc.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION; + desc.type = SDL_JOYSTICK_TYPE_GAMECONTROLLER; + desc.naxes = 6; + desc.nbuttons = 16; + desc.name = "Virtual Controller 0"; + const int dev0 = SDL_JoystickAttachVirtualEx(&desc); + Require(dev0 >= 0); + Require(SDL_IsGameController(dev0)); + + DisplayWindow window; + PadInput padInput; + Require(padInput.SlotOccupied(0)); + Require(!padInput.SlotOccupied(1)); + + SDL_Event add0{}; + add0.type = SDL_CONTROLLERDEVICEADDED; + add0.cdevice.which = dev0; + padInput.HandleEvent(add0, window); + Require(padInput.SlotOccupied(0)); + Require(!padInput.SlotOccupied(1)); + + desc.name = "Virtual Controller 1"; + const int dev1 = SDL_JoystickAttachVirtualEx(&desc); + Require(dev1 >= 0); + Require(SDL_IsGameController(dev1)); + + SDL_Event add1{}; + add1.type = SDL_CONTROLLERDEVICEADDED; + add1.cdevice.which = dev1; + padInput.HandleEvent(add1, window); + Require(padInput.SlotOccupied(0)); + Require(padInput.SlotOccupied(1)); + Require(!padInput.SlotOccupied(2)); + Require(Pad::IsConnected(1)); + + const SDL_JoystickID id0 = SDL_JoystickGetDeviceInstanceID(dev0); + SDL_Event rem0{}; + rem0.type = SDL_CONTROLLERDEVICEREMOVED; + rem0.cdevice.which = id0; + padInput.HandleEvent(rem0, window); + Require(!padInput.SlotOccupied(0)); + Require(padInput.SlotOccupied(1)); + Require(Pad::IsConnected(1)); + + padInput.HandleEvent(add0, window); + Require(padInput.SlotOccupied(0)); + Require(padInput.SlotOccupied(1)); + + const SDL_JoystickID id1 = SDL_JoystickGetDeviceInstanceID(dev1); + SDL_Event rem1{}; + rem1.type = SDL_CONTROLLERDEVICEREMOVED; + rem1.cdevice.which = id1; + padInput.HandleEvent(rem1, window); + Require(padInput.SlotOccupied(0)); + Require(!padInput.SlotOccupied(1)); + Require(!Pad::IsConnected(1)); + + padInput.HandleEvent(rem0, window); + Require(!padInput.SlotOccupied(0)); + Require(!padInput.SlotOccupied(1)); + + Require(SDL_JoystickDetachVirtual(dev1) == 0); + Require(SDL_JoystickDetachVirtual(dev0) == 0); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK); + + return 0; } diff --git a/core/libs/prx/libSceVideoOut/tests/Telemetry.cpp b/core/libs/prx/libSceVideoOut/tests/Telemetry.cpp index d3fc3ce183..ba808eb7ba 100644 --- a/core/libs/prx/libSceVideoOut/tests/Telemetry.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Telemetry.cpp @@ -3,7 +3,12 @@ #include #include +#ifdef _WIN32 +#include +#define getpid _getpid +#else #include +#endif #include #include #include @@ -109,7 +114,7 @@ void queueOverflowIsCounted() { void contractPadAndPresentLines() { const auto directory = freshDirectory("contract"); { - auto writer = VideoOutTelemetry::OpenFromEnvironment(directory.c_str(), "abc123"); + auto writer = VideoOutTelemetry::OpenFromEnvironment(directory.string().c_str(), "abc123"); check(writer != nullptr, "variable must open a writer"); PadInputState state; state.buttons = 0x4000; diff --git a/core/libs/prx/libSceVideoOutSecondary/libSceVideoOutSecondary.hpp b/core/libs/prx/libSceVideoOutSecondary/libSceVideoOutSecondary.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVideoOutSecondary/libSceVideoOutSecondary.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVideoRecording.native/libSceVideoRecording.native.hpp b/core/libs/prx/libSceVideoRecording.native/libSceVideoRecording.native.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVideoRecording.native/libSceVideoRecording.native.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVideoRecording/libSceVideoRecording.hpp b/core/libs/prx/libSceVideoRecording/libSceVideoRecording.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVideoRecording/libSceVideoRecording.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVideoRecordingP/Export.cpp b/core/libs/prx/libSceVideoRecordingP/Export.cpp index a5cdee3d3a..6166139756 100644 --- a/core/libs/prx/libSceVideoRecordingP/Export.cpp +++ b/core/libs/prx/libSceVideoRecordingP/Export.cpp @@ -1,23 +1,30 @@ +#include "prx/libSceVideoRecordingP/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -#include -#include -#include -#include -#include -#include + +static_assert(sizeof(VideoRecordingQueryParam) == 64); +static_assert(offsetof(VideoRecordingQueryParam, mode) == 12); +static_assert(offsetof(VideoRecordingQueryParam, option) == 28); + +static_assert(sizeof(VideoRecordingSupportInfo) == 18); + +static_assert(sizeof(VideoRecordingParam) == 32); +static_assert(offsetof(VideoRecordingParam, duration) == 12); +static_assert(offsetof(VideoRecordingParam, format) == 28); +static_assert(sizeof(VideoRecordingParam2) == 40); +static_assert(offsetof(VideoRecordingParam2, threadPriority) == 16); +static_assert(offsetof(VideoRecordingParam2, duration) == 20); +static_assert(offsetof(VideoRecordingParam2, format) == 36); namespace { -std::mutex g_infoMutex; -std::map> g_info; constexpr int SceVideoRecordingStatusNone = 0; } extern "C" { -void APS5_VABI _sceVideoRecordingQueryParam_nid_postfix(void* param) { +void APS5_VABI _sceVideoRecordingQueryParam_nid_postfix(VideoRecordingQueryParam* param) { (void)param; NotImplemented_nid_no_patch("_sceVideoRecordingQueryParam"); } @@ -28,7 +35,7 @@ int APS5_VABI sceVideoRecordingClose(int discard) { return 0; } -int APS5_VABI sceVideoRecordingCloseByHandle(int handle, int discard) { +int APS5_VABI sceVideoRecordingCloseByHandle(VideoRecordingContext* handle, int discard) { (void)handle; (void)discard; NotImplemented_nid_no_patch(__func__); @@ -44,17 +51,22 @@ int APS5_VABI sceVideoRecordingCopyBGRAtoNV12(void* dst, const void* src, uint32 return 0; } -int APS5_VABI sceVideoRecordingGetInfo(int32_t info, void* data, size_t size) { - if (data == nullptr) APS5_INVALID_ARG_EX; - std::lock_guard lock(g_infoMutex); - const auto found = g_info.find(info); - if (found == g_info.end()) throw std::logic_error(std::string(__func__) + ": info " + std::to_string(info) + " was never set"); - if (size < found->second.size()) APS5_INVALID_ARG_EX; - std::memcpy(data, found->second.data(), found->second.size()); - return 0; +int APS5_VABI sceVideoRecordingGetInfo(std::int32_t info, void*, std::size_t) { + switch (info) { + case 0xa001: + case 0xa002: + case 0xa003: + case 0xa005: + case 0xa00a: + case 0xa00b: + case 0xa00c: + return static_cast(0x80a80006); + default: + return static_cast(0x80a80003); + } } -int APS5_VABI sceVideoRecordingGetInfoByHandle(int handle, int32_t info, void* data, size_t size) { +int APS5_VABI sceVideoRecordingGetInfoByHandle(VideoRecordingContext* handle, int32_t info, void* data, size_t size) { (void)handle; (void)info; (void)data; @@ -67,13 +79,13 @@ int APS5_VABI sceVideoRecordingGetStatus(void) { return SceVideoRecordingStatusNone; } -int APS5_VABI sceVideoRecordingGetSupportInfo(void* info) { +int APS5_VABI sceVideoRecordingGetSupportInfo(VideoRecordingSupportInfo* info) { (void)info; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVideoRecordingGetVideoOutMode(void* mode) { +int APS5_VABI sceVideoRecordingGetVideoOutMode(std::uint32_t* mode) { (void)mode; NotImplemented_nid_no_patch(__func__); return 0; @@ -89,7 +101,7 @@ int APS5_VABI sceVideoRecordingInitialize(void* param) { return 0; } -int APS5_VABI sceVideoRecordingOpen(const char* path, const void* param, void* heap, int heapSize) { +int APS5_VABI sceVideoRecordingOpen(const char* path, const VideoRecordingParam* param, void* heap, int heapSize) { (void)path; (void)param; (void)heap; @@ -98,7 +110,7 @@ int APS5_VABI sceVideoRecordingOpen(const char* path, const void* param, void* h return 0; } -int APS5_VABI sceVideoRecordingOpen2(const char* path, const void* param, void* heap, int heapSize) { +int APS5_VABI sceVideoRecordingOpen2(const char* path, const VideoRecordingParam2* param, void* heap, int heapSize) { (void)path; (void)param; (void)heap; @@ -107,19 +119,19 @@ int APS5_VABI sceVideoRecordingOpen2(const char* path, const void* param, void* return 0; } -int APS5_VABI sceVideoRecordingQueryHddSize2(const void* param) { +std::int64_t APS5_VABI sceVideoRecordingQueryHddSize2(const VideoRecordingParam2* param) { (void)param; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVideoRecordingQueryMemSize(const void* param) { +int APS5_VABI sceVideoRecordingQueryMemSize(const VideoRecordingParam* param) { (void)param; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVideoRecordingQueryMemSize2(const void* param) { +int APS5_VABI sceVideoRecordingQueryMemSize2(const VideoRecordingParam2* param) { (void)param; NotImplemented_nid_no_patch(__func__); return 0; @@ -143,12 +155,18 @@ int APS5_VABI sceVideoRecordingSendCommand(int cmd, const void* param) { return 0; } -int APS5_VABI sceVideoRecordingSetInfo(int32_t info, const void* data, size_t size) { - if (data == nullptr && size != 0) APS5_INVALID_ARG_EX; - const auto* bytes = static_cast(data); - std::lock_guard lock(g_infoMutex); - g_info[info].assign(bytes, bytes + size); - return 0; +int APS5_VABI sceVideoRecordingSetInfo(std::int32_t info, const void* data, std::size_t size) { + switch (info) { + case 0x8002: + case 0x8003: + if (!data || size < sizeof(float)) return static_cast(0x80a80003); + return static_cast(0x80a80002); + case 0xa004: + case 0xa006: + return static_cast(0x80a80006); + default: + return static_cast(0x80a80003); + } } int APS5_VABI sceVideoRecordingStart(void) { diff --git a/core/libs/prx/libSceVideoRecordingP/include/Api.hpp b/core/libs/prx/libSceVideoRecordingP/include/Api.hpp new file mode 100644 index 0000000000..2f831a0c7e --- /dev/null +++ b/core/libs/prx/libSceVideoRecordingP/include/Api.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "SceTypes.hpp" + +struct VideoRecordingContext; + +struct VideoRecordingParam { + std::uint64_t size; + std::int32_t threadPriority; + std::uint32_t duration; + std::uint32_t mode; + std::int32_t outputMode; + std::uint32_t option; + std::uint32_t format; +}; + +struct VideoRecordingParam2 { + std::uint64_t size; + std::uint64_t affinityMask; + std::int32_t threadPriority; + std::uint32_t duration; + std::uint32_t mode; + std::int32_t outputMode; + std::uint32_t option; + std::uint32_t format; +}; + +struct VideoRecordingQueryParam { + std::uint64_t size; + std::uint32_t reserved8; + std::int32_t mode; + std::uint8_t reserved16[12]; + std::uint32_t option; + std::uint8_t reserved32[32]; +}; + +struct VideoRecordingSupportInfo { + std::uint8_t data[18]; +}; + +extern "C" { + +void APS5_VABI _sceVideoRecordingQueryParam_nid_postfix(VideoRecordingQueryParam* param); +int APS5_VABI sceVideoRecordingClose(int discard); +int APS5_VABI sceVideoRecordingCloseByHandle(VideoRecordingContext* handle, int discard); +int APS5_VABI sceVideoRecordingCopyBGRAtoNV12(void* dst, const void* src, uint32_t width, uint32_t height); +int APS5_VABI sceVideoRecordingGetInfo(std::int32_t info, void*, std::size_t); +int APS5_VABI sceVideoRecordingGetInfoByHandle(VideoRecordingContext* handle, int32_t info, void* data, size_t size); +int APS5_VABI sceVideoRecordingGetStatus(void); +int APS5_VABI sceVideoRecordingGetSupportInfo(VideoRecordingSupportInfo* info); +int APS5_VABI sceVideoRecordingGetVideoOutMode(std::uint32_t* mode); +int APS5_VABI sceVideoRecordingInit(void); +int APS5_VABI sceVideoRecordingInitialize(void* param); +int APS5_VABI sceVideoRecordingOpen(const char* path, const VideoRecordingParam* param, void* heap, int heapSize); +int APS5_VABI sceVideoRecordingOpen2(const char* path, const VideoRecordingParam2* param, void* heap, int heapSize); +std::int64_t APS5_VABI sceVideoRecordingQueryHddSize2(const VideoRecordingParam2* param); +int APS5_VABI sceVideoRecordingQueryMemSize(const VideoRecordingParam* param); +int APS5_VABI sceVideoRecordingQueryMemSize2(const VideoRecordingParam2* param); +int APS5_VABI sceVideoRecordingRestModeProcessGetStatus(void* status); +int APS5_VABI sceVideoRecordingRestModeProcessStart(void* param); +int APS5_VABI sceVideoRecordingSendCommand(int cmd, const void* param); +int APS5_VABI sceVideoRecordingSetInfo(std::int32_t info, const void* data, std::size_t size); +int APS5_VABI sceVideoRecordingStart(void); +int APS5_VABI sceVideoRecordingStop(void); +int APS5_VABI sceVideoRecordingTerm(void); +int APS5_VABI videoRecordingUnknown_iQS6DUtLybE(void); + +} diff --git a/core/libs/prx/libSceVideoRecordingP/libSceVideoRecordingP.hpp b/core/libs/prx/libSceVideoRecordingP/libSceVideoRecordingP.hpp new file mode 100644 index 0000000000..33ebd6f0fc --- /dev/null +++ b/core/libs/prx/libSceVideoRecordingP/libSceVideoRecordingP.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVideoRecordingP/include/Api.hpp" diff --git a/core/libs/prx/libSceVideoStreamingEngine/Export.cpp b/core/libs/prx/libSceVideoStreamingEngine/Export.cpp index 1a09e3b9df..f773806a45 100644 --- a/core/libs/prx/libSceVideoStreamingEngine/Export.cpp +++ b/core/libs/prx/libSceVideoStreamingEngine/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoStreamingEngine/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceVideoStreamingEngine/include/Api.hpp b/core/libs/prx/libSceVideoStreamingEngine/include/Api.hpp new file mode 100644 index 0000000000..2b807d7c25 --- /dev/null +++ b/core/libs/prx/libSceVideoStreamingEngine/include/Api.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideoStreamingEngineDestroyMediaKeySystemAccess(); +int APS5_VABI sceVideoStreamingEngineMediaKeySessionGenerateRequest(); +int APS5_VABI sceVideoStreamingEngineMediaKeySessionGetExpiration(); +int APS5_VABI sceVideoStreamingEngineMediaKeySessionGetKeyStatuses(); +int APS5_VABI sceVideoStreamingEngineMediaKeySessionGetSessionId(); +int APS5_VABI sceVideoStreamingEngineMediaKeySessionLoad(); +int APS5_VABI sceVideoStreamingEngineMediaKeySessionSetEventHandler(); +int APS5_VABI sceVideoStreamingEngineMediaKeySystemAccessCreateMediaKeys(); +int APS5_VABI sceVideoStreamingEngineMediaKeySystemAccessDestroyMediaKeys(); +int APS5_VABI sceVideoStreamingEngineMediaKeySystemAccessGetKeySystem(); +int APS5_VABI sceVideoStreamingEngineMediaKeySystemAccessSetCdmStorageConfig(); +int APS5_VABI sceVideoStreamingEngineMediaKeysCreateSession(); + +} diff --git a/core/libs/prx/libSceVideoStreamingEngine/libSceVideoStreamingEngine.hpp b/core/libs/prx/libSceVideoStreamingEngine/libSceVideoStreamingEngine.hpp new file mode 100644 index 0000000000..f0f435d15e --- /dev/null +++ b/core/libs/prx/libSceVideoStreamingEngine/libSceVideoStreamingEngine.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVideoStreamingEngine/include/Api.hpp" diff --git a/core/libs/prx/libSceVideoStreamingEngine_sys/libSceVideoStreamingEngine_sys.hpp b/core/libs/prx/libSceVideoStreamingEngine_sys/libSceVideoStreamingEngine_sys.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVideoStreamingEngine_sys/libSceVideoStreamingEngine_sys.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVideodec/Export.cpp b/core/libs/prx/libSceVideodec/Export.cpp index 56919ce362..efb762dbfe 100644 --- a/core/libs/prx/libSceVideodec/Export.cpp +++ b/core/libs/prx/libSceVideodec/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceVideodec2/include/Api.hpp" +#include "prx/libSceVideodec/include/Api.hpp" #include #include #include @@ -10,6 +12,8 @@ namespace { +using namespace SceVideodec; + constexpr int VideodecErrorApiFail = static_cast(0x80C10000); constexpr int VideodecErrorStructSize = static_cast(0x80C10002); constexpr int VideodecErrorHandle = static_cast(0x80C10003); @@ -24,142 +28,6 @@ constexpr int VideodecErrorDecodeAu = static_cast(0x80C10011); constexpr std::uint32_t CodecAvc = 1; constexpr std::uint32_t PitchAlignment = 64; -struct ConfigInfo { - std::uint64_t thisSize; - std::uint32_t codecType; - std::uint32_t profile; - std::uint32_t maxLevel; - std::int32_t maxFrameWidth; - std::int32_t maxFrameHeight; - std::int32_t maxDpbFrameCount; - std::uint64_t videodecFlags; -}; - -struct ResourceInfo { - std::uint64_t thisSize; - std::uint64_t cpuMemorySize; - void* cpuMemory; - std::uint64_t cpuGpuMemorySize; - void* cpuGpuMemory; - std::uint64_t maxFrameBufferSize; - std::uint32_t frameBufferAlignment; -}; - -struct Ctrl { - std::uint64_t thisSize; - void* handle; - std::uint64_t version; -}; - -struct FrameBuffer { - std::uint64_t thisSize; - void* frameBuffer; - std::uint64_t frameBufferSize; -}; - -struct AvcInfo { - std::uint32_t numUnitsInTick; - std::uint32_t timeScale; - std::uint8_t fixedFrameRateFlag; - std::uint8_t aspectRatioIdc; - std::uint16_t sarWidth; - std::uint16_t sarHeight; - std::uint8_t colourPrimaries; - std::uint8_t transferCharacteristics; - std::uint8_t matrixCoefficients; - std::uint8_t videoFullRangeFlag; - std::uint32_t frameCropLeftOffset; - std::uint32_t frameCropRightOffset; - std::uint32_t frameCropTopOffset; - std::uint32_t frameCropBottomOffset; -}; - -union CodecInfo { - std::uint8_t reserved[64]; - AvcInfo avc; -}; - -struct PictureInfo { - std::uint64_t thisSize; - std::uint32_t isValid; - std::uint32_t codecType; - std::uint32_t frameWidth; - std::uint32_t framePitch; - std::uint32_t frameHeight; - std::uint32_t isErrorPic; - std::uint64_t ptsData; - std::uint64_t attachedData; - CodecInfo codec; -}; - -struct InputData { - std::uint64_t thisSize; - const std::uint8_t* auData; - std::uint64_t auSize; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; -}; - -struct Decoder2ConfigInfo { - std::uint64_t thisSize; - std::uint32_t resourceType; - std::uint32_t codecType; - std::uint32_t profile; - std::uint32_t maxLevel; - std::int32_t maxFrameWidth; - std::int32_t maxFrameHeight; - std::int32_t maxDpbFrameCount; - std::uint32_t decodePipelineDepth; -}; - -struct Decoder2MemoryInfo { - std::uint64_t thisSize; - std::uint64_t cpuMemorySize; - void* cpuMemory; - std::uint64_t gpuMemorySize; - void* gpuMemory; - std::uint64_t cpuGpuMemorySize; - void* cpuGpuMemory; - std::uint64_t maxFrameBufferSize; - std::uint32_t frameBufferAlignment; -}; - -struct Decoder2FrameBuffer { - std::uint64_t thisSize; - void* frameBuffer; - std::uint64_t frameBufferSize; - bool isAccepted; -}; - -struct Decoder2OutputInfo { - std::uint64_t thisSize; - bool isValid; - bool isErrorFrame; - std::uint8_t pictureCount; - std::uint32_t codecType; - std::uint32_t frameWidth; - std::uint32_t framePitch; - std::uint32_t frameHeight; - void* frameBuffer; - std::uint64_t frameBufferSize; - std::uint32_t frameFormat; - std::uint32_t framePitchInBytes; -}; - -struct Decoder2AvcPictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint8_t idrPictureFlag; - std::uint8_t profileIdc; - std::uint8_t levelIdc; - std::uint32_t picWidthInLumaSamples; - std::uint32_t picHeightInLumaSamples; -}; - struct Decoder2Crop { std::uint32_t left; std::uint32_t right; @@ -168,7 +36,11 @@ struct Decoder2Crop { }; static_assert(sizeof(ConfigInfo) == 40 && sizeof(ResourceInfo) == 56 && sizeof(Ctrl) == 24); -static_assert(sizeof(FrameBuffer) == 24 && sizeof(InputData) == 48 && sizeof(PictureInfo) == 112); +static_assert(offsetof(ConfigInfo, maxFrameWidth) == 20 && offsetof(ConfigInfo, videodecFlags) == 32); +static_assert(offsetof(ResourceInfo, cpuGpuMemorySize) == 24 && offsetof(ResourceInfo, maxFrameBufferSize) == 40); +static_assert(offsetof(ResourceInfo, frameBufferAlignment) == 48); +static_assert(sizeof(SceVideodec2::DecoderConfigInfo) == 40 && sizeof(SceVideodec2::DecoderMemoryInfo) == 72); +static_assert(sizeof(FrameBuffer) == 24 && sizeof(SceVideodec2::InputData) == 48 && sizeof(PictureInfo) == 112); static_assert(sizeof(CodecInfo) == 64 && offsetof(PictureInfo, codec) == 48); struct Decoder { @@ -193,13 +65,7 @@ std::uint32_t AlignUp(std::uint32_t value, std::uint32_t alignment) { } extern "C" { -int APS5_VABI sceVideodec2QueryDecoderMemoryInfo_nid_postfix(const Decoder2ConfigInfo*, Decoder2MemoryInfo*); -int APS5_VABI sceVideodec2CreateDecoder_nid_postfix(const Decoder2ConfigInfo*, const Decoder2MemoryInfo*, std::uint64_t*); -int APS5_VABI sceVideodec2DeleteDecoder_nid_postfix(std::uint64_t); -int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t, const InputData*, Decoder2FrameBuffer*, Decoder2OutputInfo*); -int APS5_VABI sceVideodec2Flush_nid_postfix(std::uint64_t, Decoder2FrameBuffer*, Decoder2OutputInfo*); -int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t); -int APS5_VABI sceVideodec2GetAvcPictureInfo_nid_postfix(const Decoder2OutputInfo*, Decoder2AvcPictureInfo*, Decoder2AvcPictureInfo*); + void Videodec2PictureCrop_nid_no_patch(std::uint64_t, const void*, Decoder2Crop*); } @@ -212,7 +78,21 @@ struct Geometry { Decoder2Crop crop{}; }; -Geometry Measure(const Decoder2OutputInfo& output, const Decoder2Crop& crop) { +SceVideodec2::DecoderConfigInfo MapConfig(const ConfigInfo& config) { + SceVideodec2::DecoderConfigInfo decoderConfig{}; + decoderConfig.thisSize = sizeof(SceVideodec2::DecoderConfigInfo); + decoderConfig.resourceType = 1; + decoderConfig.codecType = CodecAvc; + decoderConfig.profile = config.profile; + decoderConfig.maxLevel = config.maxLevel; + decoderConfig.maxFrameWidth = config.maxFrameWidth; + decoderConfig.maxFrameHeight = config.maxFrameHeight; + decoderConfig.maxDpbFrameCount = config.maxDpbFrameCount; + decoderConfig.decodePipelineDepth = 1; + return decoderConfig; +} + +Geometry Measure(const SceVideodec2::OutputInfo& output, const Decoder2Crop& crop) { Geometry geometry; geometry.width = output.frameWidth; geometry.height = output.frameHeight; @@ -221,7 +101,7 @@ Geometry Measure(const Decoder2OutputInfo& output, const Decoder2Crop& crop) { return geometry; } -void FillPictureInfo(const Geometry& geometry, const Decoder2OutputInfo& output, PictureInfo* picture) { +void FillPictureInfo(const Geometry& geometry, const SceVideodec2::OutputInfo& output, PictureInfo* picture) { picture->isValid = 0; picture->codecType = 0; picture->frameWidth = 0; @@ -243,7 +123,7 @@ void FillPictureInfo(const Geometry& geometry, const Decoder2OutputInfo& output, picture->codec.avc.frameCropBottomOffset = geometry.crop.bottom / 2; } -int CopyPicture(const Decoder2OutputInfo& output, const Geometry& geometry, const FrameBuffer* frameBuffer) { +int CopyPicture(const SceVideodec2::OutputInfo& output, const Geometry& geometry, const FrameBuffer* frameBuffer) { const auto bytes = static_cast(geometry.pitch) * geometry.height * 3 / 2; if (frameBuffer->frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; if (frameBuffer->frameBufferSize < bytes) return VideodecErrorFrameBufferSize; @@ -266,18 +146,9 @@ extern "C" { int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceInfo* resource, Ctrl* ctrl) { if (config == nullptr || resource == nullptr || ctrl == nullptr) return VideodecErrorArgumentPointer; if (config->thisSize < sizeof(ConfigInfo) || resource->thisSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; - Decoder2ConfigInfo decoderConfig{}; - decoderConfig.thisSize = sizeof(Decoder2ConfigInfo); - decoderConfig.resourceType = 1; - decoderConfig.codecType = CodecAvc; - decoderConfig.profile = config->profile; - decoderConfig.maxLevel = config->maxLevel; - decoderConfig.maxFrameWidth = config->maxFrameWidth; - decoderConfig.maxFrameHeight = config->maxFrameHeight; - decoderConfig.maxDpbFrameCount = config->maxDpbFrameCount; - decoderConfig.decodePipelineDepth = 1; - Decoder2MemoryInfo decoderMemory{}; - decoderMemory.thisSize = sizeof(Decoder2MemoryInfo); + const auto decoderConfig = MapConfig(*config); + SceVideodec2::DecoderMemoryInfo decoderMemory{}; + decoderMemory.thisSize = sizeof(SceVideodec2::DecoderMemoryInfo); decoderMemory.cpuMemorySize = resource->cpuMemorySize; decoderMemory.cpuMemory = resource->cpuMemory; decoderMemory.cpuGpuMemorySize = resource->cpuGpuMemorySize; @@ -286,6 +157,7 @@ int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceI decoderMemory.frameBufferAlignment = resource->frameBufferAlignment; try { sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&decoderConfig, &decoderMemory); + } catch (const std::exception&) { return VideodecErrorConfigInfo; } @@ -304,11 +176,11 @@ int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceI return 0; } -int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const InputData* input, FrameBuffer* frameBuffer, PictureInfo* picture) { +int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const SceVideodec2::InputData* input, FrameBuffer* frameBuffer, PictureInfo* picture) { if (ctrl == nullptr || input == nullptr || picture == nullptr) return VideodecErrorArgumentPointer; if (frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; if (ctrl->thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; - if (input->thisSize < sizeof(InputData)) return VideodecErrorStructSize; + if (input->thisSize < sizeof(SceVideodec2::InputData)) return VideodecErrorStructSize; if (picture->thisSize < sizeof(PictureInfo)) return VideodecErrorStructSize; if (frameBuffer->thisSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; auto* decoder = FindDecoder(ctrl->handle); @@ -316,20 +188,21 @@ int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const InputData* input, FrameBuffer* if (input->auData == nullptr) return VideodecErrorAuPointer; if (input->auSize == 0) return VideodecErrorAuSize; if (frameBuffer->frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; - Decoder2FrameBuffer target{}; - target.thisSize = sizeof(Decoder2FrameBuffer); + SceVideodec2::FrameBuffer target{}; + target.thisSize = sizeof(SceVideodec2::FrameBuffer); target.frameBuffer = decoder->picture.data(); target.frameBufferSize = decoder->picture.size(); - Decoder2OutputInfo output{sizeof(Decoder2OutputInfo)}; + SceVideodec2::OutputInfo output{sizeof(SceVideodec2::OutputInfo)}; try { sceVideodec2Decode_nid_postfix(decoder->handle, input, &target, &output); + } catch (const std::exception&) { return VideodecErrorDecodeAu; } Decoder2Crop crop{}; if (output.isValid) Videodec2PictureCrop_nid_no_patch(decoder->handle, output.frameBuffer, &crop); const auto geometry = Measure(output, crop); - Decoder2AvcPictureInfo avc{sizeof(Decoder2AvcPictureInfo)}; + SceVideodec2::AvcPictureInfo avc{sizeof(SceVideodec2::AvcPictureInfo)}; if (output.isValid) sceVideodec2GetAvcPictureInfo_nid_postfix(&output, &avc, nullptr); FillPictureInfo(geometry, output, picture); picture->ptsData = avc.ptsData; @@ -353,6 +226,7 @@ int APS5_VABI sceVideodecDeleteDecoder(Ctrl* ctrl) { delete decoder; try { sceVideodec2DeleteDecoder_nid_postfix(handle); + } catch (const std::exception&) { return VideodecErrorApiFail; } @@ -368,20 +242,21 @@ int APS5_VABI sceVideodecFlush(Ctrl* ctrl, FrameBuffer* frameBuffer, PictureInfo auto* decoder = FindDecoder(ctrl->handle); if (decoder == nullptr) return VideodecErrorHandle; if (frameBuffer->frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; - Decoder2FrameBuffer target{}; - target.thisSize = sizeof(Decoder2FrameBuffer); + SceVideodec2::FrameBuffer target{}; + target.thisSize = sizeof(SceVideodec2::FrameBuffer); target.frameBuffer = decoder->picture.data(); target.frameBufferSize = decoder->picture.size(); - Decoder2OutputInfo output{sizeof(Decoder2OutputInfo)}; + SceVideodec2::OutputInfo output{sizeof(SceVideodec2::OutputInfo)}; try { sceVideodec2Flush_nid_postfix(decoder->handle, &target, &output); + } catch (const std::exception&) { return VideodecErrorDecodeAu; } Decoder2Crop crop{}; if (output.isValid) Videodec2PictureCrop_nid_no_patch(decoder->handle, output.frameBuffer, &crop); const auto geometry = Measure(output, crop); - Decoder2AvcPictureInfo avc{sizeof(Decoder2AvcPictureInfo)}; + SceVideodec2::AvcPictureInfo avc{sizeof(SceVideodec2::AvcPictureInfo)}; if (output.isValid) sceVideodec2GetAvcPictureInfo_nid_postfix(&output, &avc, nullptr); FillPictureInfo(geometry, output, picture); picture->ptsData = avc.ptsData; @@ -397,6 +272,7 @@ int APS5_VABI sceVideodecReset(Ctrl* ctrl) { if (decoder == nullptr) return VideodecErrorHandle; try { sceVideodec2Reset_nid_postfix(decoder->handle); + } catch (const std::exception&) { return VideodecErrorApiFail; } @@ -407,8 +283,22 @@ int APS5_VABI sceVideodecMapMemory(void) { return 0; } -int APS5_VABI sceVideodecQueryResourceInfo(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideodecQueryResourceInfo(const ConfigInfo* config, ResourceInfo* resource) { + if (config == nullptr || resource == nullptr) return VideodecErrorArgumentPointer; + if (config->thisSize < sizeof(ConfigInfo) || resource->thisSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; + const auto decoderConfig = MapConfig(*config); + SceVideodec2::DecoderMemoryInfo decoderMemory{}; + decoderMemory.thisSize = sizeof(SceVideodec2::DecoderMemoryInfo); + try { + sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&decoderConfig, &decoderMemory); + + } catch (const std::exception&) { + return VideodecErrorConfigInfo; + } + resource->cpuMemorySize = decoderMemory.cpuMemorySize; + resource->cpuGpuMemorySize = decoderMemory.cpuGpuMemorySize; + resource->maxFrameBufferSize = decoderMemory.maxFrameBufferSize; + resource->frameBufferAlignment = decoderMemory.frameBufferAlignment; return 0; } diff --git a/core/libs/prx/libSceVideodec/include/Api.hpp b/core/libs/prx/libSceVideodec/include/Api.hpp new file mode 100644 index 0000000000..46fd7ef835 --- /dev/null +++ b/core/libs/prx/libSceVideodec/include/Api.hpp @@ -0,0 +1,119 @@ +#include "prx/libSceVideodec2/include/Api.hpp" +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace SceVideodec { + +struct ConfigInfo { + std::uint64_t thisSize; + std::uint32_t codecType; + std::uint32_t profile; + std::uint32_t maxLevel; + std::int32_t maxFrameWidth; + std::int32_t maxFrameHeight; + std::int32_t maxDpbFrameCount; + std::uint64_t videodecFlags; +}; + +} + +namespace SceVideodec { + +struct ResourceInfo { + std::uint64_t thisSize; + std::uint64_t cpuMemorySize; + void* cpuMemory; + std::uint64_t cpuGpuMemorySize; + void* cpuGpuMemory; + std::uint64_t maxFrameBufferSize; + std::uint32_t frameBufferAlignment; +}; + +} + +namespace SceVideodec { + +struct Ctrl { + std::uint64_t thisSize; + void* handle; + std::uint64_t version; +}; + +} + +namespace SceVideodec { + +struct FrameBuffer { + std::uint64_t thisSize; + void* frameBuffer; + std::uint64_t frameBufferSize; +}; + +} + +namespace SceVideodec { + +struct AvcInfo { + std::uint32_t numUnitsInTick; + std::uint32_t timeScale; + std::uint8_t fixedFrameRateFlag; + std::uint8_t aspectRatioIdc; + std::uint16_t sarWidth; + std::uint16_t sarHeight; + std::uint8_t colourPrimaries; + std::uint8_t transferCharacteristics; + std::uint8_t matrixCoefficients; + std::uint8_t videoFullRangeFlag; + std::uint32_t frameCropLeftOffset; + std::uint32_t frameCropRightOffset; + std::uint32_t frameCropTopOffset; + std::uint32_t frameCropBottomOffset; +}; + +} + +namespace SceVideodec { + +union CodecInfo { + std::uint8_t reserved[64]; + AvcInfo avc; +}; + +} + +namespace SceVideodec { + +struct PictureInfo { + std::uint64_t thisSize; + std::uint32_t isValid; + std::uint32_t codecType; + std::uint32_t frameWidth; + std::uint32_t framePitch; + std::uint32_t frameHeight; + std::uint32_t isErrorPic; + std::uint64_t ptsData; + std::uint64_t attachedData; + CodecInfo codec; +}; + +} + +extern "C" { + +int APS5_VABI sceVideodecCreateDecoder(const SceVideodec::ConfigInfo* config, const SceVideodec::ResourceInfo* resource, SceVideodec::Ctrl* ctrl); +int APS5_VABI sceVideodecDecode(SceVideodec::Ctrl* ctrl, const SceVideodec2::InputData* input, SceVideodec::FrameBuffer* frameBuffer, SceVideodec::PictureInfo* picture); +int APS5_VABI sceVideodecDeleteDecoder(SceVideodec::Ctrl* ctrl); +int APS5_VABI sceVideodecFlush(SceVideodec::Ctrl* ctrl, SceVideodec::FrameBuffer* frameBuffer, SceVideodec::PictureInfo* picture); +int APS5_VABI sceVideodecReset(SceVideodec::Ctrl* ctrl); +int APS5_VABI sceVideodecMapMemory(void); +int APS5_VABI sceVideodecQueryResourceInfo(const SceVideodec::ConfigInfo* config, SceVideodec::ResourceInfo* resource); + +} diff --git a/core/libs/prx/libSceVideodec/libSceVideodec.hpp b/core/libs/prx/libSceVideodec/libSceVideodec.hpp new file mode 100644 index 0000000000..8fa57dc2dd --- /dev/null +++ b/core/libs/prx/libSceVideodec/libSceVideodec.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVideodec/include/Api.hpp" diff --git a/core/libs/prx/libSceVideodec2/Export.cpp b/core/libs/prx/libSceVideodec2/Export.cpp index db8822b8eb..de23de7b72 100644 --- a/core/libs/prx/libSceVideodec2/Export.cpp +++ b/core/libs/prx/libSceVideodec2/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideodec2/include/Api.hpp" #include #include #include @@ -24,113 +25,16 @@ extern "C" { namespace { +using namespace SceVideodec2; + constexpr std::uint64_t WorkMemoryBytes = 1u << 20u; constexpr std::uint32_t PitchAlignment = 256; constexpr std::uint32_t HeightAlignment = 32; constexpr std::uint32_t CodecAvc = 1; constexpr std::uint32_t CodecHevc = 974921; constexpr std::uint32_t CodecVp9 = 2382845; - -struct ComputeMemoryInfo { - std::uint64_t thisSize; - std::uint64_t cpuGpuMemorySize; - void* cpuGpuMemory; -}; - -struct ComputeConfigInfo { - std::uint64_t thisSize; - std::uint16_t computePipeId; - std::uint16_t computeQueueId; - bool checkMemoryType; -}; - -struct DecoderConfigInfo { - std::uint64_t thisSize; - std::uint32_t resourceType; - std::uint32_t codecType; - std::uint32_t profile; - std::uint32_t maxLevel; - std::int32_t maxFrameWidth; - std::int32_t maxFrameHeight; - std::int32_t maxDpbFrameCount; - std::uint32_t decodePipelineDepth; -}; - -struct DecoderMemoryInfo { - std::uint64_t thisSize; - std::uint64_t cpuMemorySize; - void* cpuMemory; - std::uint64_t gpuMemorySize; - void* gpuMemory; - std::uint64_t cpuGpuMemorySize; - void* cpuGpuMemory; - std::uint64_t maxFrameBufferSize; - std::uint32_t frameBufferAlignment; -}; - -struct InputData { - std::uint64_t thisSize; - const std::uint8_t* auData; - std::uint64_t auSize; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; -}; - -struct FrameBuffer { - std::uint64_t thisSize; - void* frameBuffer; - std::uint64_t frameBufferSize; - bool isAccepted; -}; - -struct OutputInfo { - std::uint64_t thisSize; - bool isValid; - bool isErrorFrame; - std::uint8_t pictureCount; - std::uint32_t codecType; - std::uint32_t frameWidth; - std::uint32_t framePitch; - std::uint32_t frameHeight; - void* frameBuffer; - std::uint64_t frameBufferSize; - std::uint32_t frameFormat; - std::uint32_t framePitchInBytes; -}; - -struct AvcPictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint8_t idrPictureFlag; - std::uint8_t profileIdc; - std::uint8_t levelIdc; - std::uint32_t picWidthInLumaSamples; - std::uint32_t picHeightInLumaSamples; -}; - -struct Vp9PictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint32_t profile; - std::uint32_t level; - std::uint32_t keyFrameFlag; - std::uint32_t intraOnly; - std::uint8_t bitDepth; - std::uint8_t colorRange; - std::uint32_t colorSpace; - std::uint32_t numFrames; - std::uint32_t frameWidth; - std::uint32_t frameHeight; - std::uint32_t renderWidth; - std::uint32_t renderHeight; -}; +constexpr int Videodec2ErrorAccessUnitSize = static_cast(0x811D010Du); +constexpr int Videodec2ErrorAccessUnitPointer = static_cast(0x811D010Eu); static_assert(sizeof(Vp9PictureInfo) == 88); static_assert(offsetof(Vp9PictureInfo, colorSpace) == 60); @@ -281,6 +185,20 @@ struct Picture { bool idr = false; Vp9PictureInfo vp9{}; Crop crop{}; + bool hevcIdr = false; + std::uint8_t bitDepthMinus8 = 0; +}; + +struct PictureHeader { + std::uint64_t pts; + std::uint64_t dts; + std::uint64_t attached; + bool keyFrame; + bool hevcIdr; + std::uint32_t profile; + std::uint32_t level; + Crop crop; + std::uint8_t bitDepthMinus8; }; struct FrameDeleter { @@ -435,6 +353,16 @@ bool CarriesAvcSlice(const std::vector& unit) { return false; } +bool StartsHevcIdr(const std::vector& unit) { + for (std::size_t index = 0; index + 3 < unit.size(); ++index) { + if (unit[index] != 0 || unit[index + 1] != 0 || unit[index + 2] != 1) continue; + const auto type = (unit[index + 3] >> 1u) & 0x3fu; + if (type < 32) return type == 19 || type == 20; + index += 2; + } + return false; +} + void Receive(Decoder& decoder) { for (;;) { FramePointer frame(av_frame_alloc()); @@ -498,12 +426,16 @@ bool WriteNv12(Decoder& decoder, const AVFrame& frame, std::uint8_t* target, std return sws_scale(decoder.scaler, frame.data, frame.linesize, 0, frame.height, planes, strides) == frame.height; } -void EmitDecoded(std::uint64_t handle, Decoder& decoder, FrameBuffer* frame, OutputInfo* output) { +void ClearOutput(const Decoder& decoder, FrameBuffer* frame, OutputInfo* output) { output->isValid = false; output->isErrorFrame = false; output->pictureCount = 0; output->codecType = decoder.codec; if (frame) frame->isAccepted = false; +} + +void EmitDecoded(std::uint64_t handle, Decoder& decoder, FrameBuffer* frame, OutputInfo* output) { + ClearOutput(decoder, frame, output); if (decoder.ready.empty()) return; if (!frame || !frame->frameBuffer) throw std::runtime_error("Videodec2: missing output buffer"); const auto picture = std::move(decoder.ready.front()); @@ -520,6 +452,7 @@ void EmitDecoded(std::uint64_t handle, Decoder& decoder, FrameBuffer* frame, Out if (decoder.context->profile > 0) decoder.profile = static_cast(decoder.context->profile) & 0xffu; if (decoder.context->level > 0) decoder.level = static_cast(decoder.context->level); if (!WriteNv12(decoder, *picture, static_cast(frame->frameBuffer), frame->frameBufferSize)) throw std::runtime_error("Videodec2: NV12 conversion failed"); + timestamps.bitDepthMinus8 = decoder.sampleBytes == 2 ? 2 : 0; const auto pitch = PitchBytes(decoder.width, decoder.sampleBytes); frame->isAccepted = true; output->isValid = true; @@ -603,6 +536,10 @@ int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputDa if (!found) throw std::runtime_error("Videodec2: invalid decoder handle"); auto& decoder = *found; std::lock_guard guard(decoder.mutex); + if (!input->auData || input->auSize == 0) { + ClearOutput(decoder, frame, output); + return input->auData ? Videodec2ErrorAccessUnitSize : Videodec2ErrorAccessUnitPointer; + } if (Trace() && decoder.decoded < 3) std::fprintf(stderr, "[videodec2] Decode input=%llu frame=%llu output=%llu au=%llu pts=%llu size=%ux%u fb=%p/%llu\n", static_cast(input->thisSize), static_cast(frame ? frame->thisSize : 0), static_cast(output->thisSize), static_cast(input->auSize), static_cast(input->ptsData), decoder.width, decoder.height, frame ? frame->frameBuffer : nullptr, static_cast(frame ? frame->frameBufferSize : 0)); ++decoder.decoded; const Picture picture{input->ptsData, input->dtsData, input->attachedData}; @@ -610,7 +547,7 @@ int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputDa avcodec_flush_buffers(decoder.context); decoder.flushing = false; } - if (!input->auData || input->auSize == 0 || input->auSize > static_cast(std::numeric_limits::max())) throw std::runtime_error("Videodec2: invalid access unit"); + if (input->auSize > static_cast(std::numeric_limits::max())) throw std::runtime_error("Videodec2: invalid access unit"); if (decoder.codec == CodecVp9) { auto references = decoder.vp9References; auto colors = decoder.vp9Colors; @@ -654,7 +591,9 @@ int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputDa std::memcpy(packet->data + decoder.parameterSets.size(), unit->data(), unit->size()); decoder.parameterSets.clear(); packet->pts = decoder.nextKey; - decoder.inputs.emplace(decoder.nextKey++, picture); + auto tagged = picture; + tagged.hevcIdr = decoder.codec == CodecHevc && StartsHevcIdr(*unit); + decoder.inputs.emplace(decoder.nextKey++, tagged); Send(decoder, packet); EmitDecoded(handle, decoder, frame, output); return 0; @@ -666,7 +605,7 @@ int APS5_VABI sceVideodec2Flush_nid_postfix(std::uint64_t handle, FrameBuffer* f if (!found) throw std::runtime_error("Videodec2: invalid decoder handle"); auto& decoder = *found; std::lock_guard guard(decoder.mutex); - if (!decoder.flushing) { + if (!decoder.flushing && decoder.ready.empty() && !decoder.inputs.empty()) { Send(decoder, nullptr); decoder.flushing = true; } @@ -766,15 +705,23 @@ int APS5_VABI sceVideodec2GetVp9PictureInfo_nid_postfix(const OutputInfo* output return 0; } -void Videodec2PictureCrop_nid_no_patch(std::uint64_t handle, const void* frameBuffer, Crop* crop) { - if (!crop) throw std::runtime_error("Videodec2: invalid argument pointer"); +void Videodec2PictureHeader_nid_no_patch(std::uint64_t handle, const void* frameBuffer, PictureHeader* header) { + if (!header) throw std::runtime_error("Videodec2: invalid argument pointer"); const auto found = FindDecoder(handle); if (!found) throw std::runtime_error("Videodec2: invalid decoder handle"); auto& decoder = *found; std::lock_guard guard(decoder.mutex); const auto picture = decoder.pictures.find(frameBuffer); if (picture == decoder.pictures.end()) throw std::runtime_error("Videodec2: unknown frame buffer"); - *crop = picture->second.crop; + const auto& stored = picture->second; + *header = {stored.pts, stored.dts, stored.attached, stored.idr, stored.hevcIdr, decoder.profile, decoder.level, stored.crop, stored.bitDepthMinus8}; +} + +void Videodec2PictureCrop_nid_no_patch(std::uint64_t handle, const void* frameBuffer, Crop* crop) { + if (!crop) throw std::runtime_error("Videodec2: invalid argument pointer"); + PictureHeader header{}; + Videodec2PictureHeader_nid_no_patch(handle, frameBuffer, &header); + *crop = header.crop; } } diff --git a/core/libs/prx/libSceVideodec2/Unimplemented.cpp b/core/libs/prx/libSceVideodec2/Unimplemented.cpp index 1cad37fd28..62a9691332 100644 --- a/core/libs/prx/libSceVideodec2/Unimplemented.cpp +++ b/core/libs/prx/libSceVideodec2/Unimplemented.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideodec2/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -55,4 +56,7 @@ int APS5_VABI sceVideodec2GetHevcPictureInfo() { NotImplemented_nid_no_patch(__func__); } +int APS5_VABI sceVideodec2InitLibrary() { + NotImplemented_nid_no_patch(__func__); +} } diff --git a/core/libs/prx/libSceVideodec2/include/Api.hpp b/core/libs/prx/libSceVideodec2/include/Api.hpp new file mode 100644 index 0000000000..ff71178c8c --- /dev/null +++ b/core/libs/prx/libSceVideodec2/include/Api.hpp @@ -0,0 +1,186 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SceVideodec2 { + +struct ComputeMemoryInfo { + std::uint64_t thisSize; + std::uint64_t cpuGpuMemorySize; + void* cpuGpuMemory; +}; + +} + +namespace SceVideodec2 { + +struct ComputeConfigInfo { + std::uint64_t thisSize; + std::uint16_t computePipeId; + std::uint16_t computeQueueId; + bool checkMemoryType; +}; + +} + +namespace SceVideodec2 { + +struct DecoderConfigInfo { + std::uint64_t thisSize; + std::uint32_t resourceType; + std::uint32_t codecType; + std::uint32_t profile; + std::uint32_t maxLevel; + std::int32_t maxFrameWidth; + std::int32_t maxFrameHeight; + std::int32_t maxDpbFrameCount; + std::uint32_t decodePipelineDepth; +}; + +} + +namespace SceVideodec2 { + +struct DecoderMemoryInfo { + std::uint64_t thisSize; + std::uint64_t cpuMemorySize; + void* cpuMemory; + std::uint64_t gpuMemorySize; + void* gpuMemory; + std::uint64_t cpuGpuMemorySize; + void* cpuGpuMemory; + std::uint64_t maxFrameBufferSize; + std::uint32_t frameBufferAlignment; +}; + +} + +namespace SceVideodec2 { + +struct InputData { + std::uint64_t thisSize; + const std::uint8_t* auData; + std::uint64_t auSize; + std::uint64_t ptsData; + std::uint64_t dtsData; + std::uint64_t attachedData; +}; + +} + +namespace SceVideodec2 { + +struct FrameBuffer { + std::uint64_t thisSize; + void* frameBuffer; + std::uint64_t frameBufferSize; + bool isAccepted; +}; + +} + +namespace SceVideodec2 { + +struct OutputInfo { + std::uint64_t thisSize; + bool isValid; + bool isErrorFrame; + std::uint8_t pictureCount; + std::uint32_t codecType; + std::uint32_t frameWidth; + std::uint32_t framePitch; + std::uint32_t frameHeight; + void* frameBuffer; + std::uint64_t frameBufferSize; + std::uint32_t frameFormat; + std::uint32_t framePitchInBytes; +}; + +} + +namespace SceVideodec2 { + +struct AvcPictureInfo { + std::uint64_t thisSize; + bool isValid; + std::uint64_t ptsData; + std::uint64_t dtsData; + std::uint64_t attachedData; + std::uint8_t idrPictureFlag; + std::uint8_t profileIdc; + std::uint8_t levelIdc; + std::uint32_t picWidthInLumaSamples; + std::uint32_t picHeightInLumaSamples; +}; + +} + +namespace SceVideodec2 { + +struct Vp9PictureInfo { + std::uint64_t thisSize; + bool isValid; + std::uint64_t ptsData; + std::uint64_t dtsData; + std::uint64_t attachedData; + std::uint32_t profile; + std::uint32_t level; + std::uint32_t keyFrameFlag; + std::uint32_t intraOnly; + std::uint8_t bitDepth; + std::uint8_t colorRange; + std::uint32_t colorSpace; + std::uint32_t numFrames; + std::uint32_t frameWidth; + std::uint32_t frameHeight; + std::uint32_t renderWidth; + std::uint32_t renderHeight; +}; + +} + +extern "C" { + +int APS5_VABI sceVideodec2QueryComputeMemoryInfo_nid_postfix(SceVideodec2::ComputeMemoryInfo* info); +int APS5_VABI sceVideodec2AllocateComputeQueue_nid_postfix(const SceVideodec2::ComputeConfigInfo* config, const SceVideodec2::ComputeMemoryInfo* memory, std::uint64_t* queue); +int APS5_VABI sceVideodec2ReleaseComputeQueue_nid_postfix(std::uint64_t queue); +int APS5_VABI sceVideodec2QueryDecoderMemoryInfo_nid_postfix(const SceVideodec2::DecoderConfigInfo* config, SceVideodec2::DecoderMemoryInfo* memory); +int APS5_VABI sceVideodec2CreateDecoder_nid_postfix(const SceVideodec2::DecoderConfigInfo* config, const SceVideodec2::DecoderMemoryInfo* memory, std::uint64_t* handle); +int APS5_VABI sceVideodec2DeleteDecoder_nid_postfix(std::uint64_t handle); +int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const SceVideodec2::InputData* input, SceVideodec2::FrameBuffer* frame, SceVideodec2::OutputInfo* output); +int APS5_VABI sceVideodec2Flush_nid_postfix(std::uint64_t handle, SceVideodec2::FrameBuffer* frame, SceVideodec2::OutputInfo* output); +int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t handle); +int APS5_VABI sceVideodec2GetPictureInfo_nid_postfix(const SceVideodec2::OutputInfo* output, SceVideodec2::AvcPictureInfo* first, void* second); +int APS5_VABI sceVideodec2GetAvcPictureInfo_nid_postfix(const SceVideodec2::OutputInfo* output, SceVideodec2::AvcPictureInfo* first, SceVideodec2::AvcPictureInfo* second); +int APS5_VABI sceVideodec2GetVp9PictureInfo_nid_postfix(const SceVideodec2::OutputInfo* output, SceVideodec2::Vp9PictureInfo* info); +int APS5_VABI sceVideodec2TermLibrary(); +int APS5_VABI sceVideodec2QueryLibraryInfo(); +int APS5_VABI sceVideodec2QueryDecoderInfo(); +int APS5_VABI sceVideodec2MapMemory(); +int APS5_VABI sceVideodec2MapDirectMemory(void*, const void*); +int APS5_VABI sceVideodec2PresetDecoderConfigInfo(); +int APS5_VABI sceVideodec2PresetDecoderConfigInfoAvc(); +int APS5_VABI sceVideodec2PresetDecoderConfigInfoHevc(); +int APS5_VABI sceVideodec2PresetDecoderConfigInfoVp9(); +int APS5_VABI sceVideodec2CreateDecoderBid(); +int APS5_VABI sceVideodec2CreateHevcDecoder(); +int APS5_VABI sceVideodec2QueryHevcDecoderMemoryInfo(); +int APS5_VABI sceVideodec2GetHevcPictureInfo(); +int APS5_VABI sceVideodec2InitLibrary(); + +} diff --git a/core/libs/prx/libSceVideodec2/libSceVideodec2.hpp b/core/libs/prx/libSceVideodec2/libSceVideodec2.hpp new file mode 100644 index 0000000000..85a17c3882 --- /dev/null +++ b/core/libs/prx/libSceVideodec2/libSceVideodec2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVideodec2/include/Api.hpp" diff --git a/core/libs/prx/libSceVisionManager/Export.cpp b/core/libs/prx/libSceVisionManager/Export.cpp index 91dd5c2463..e329980232 100644 --- a/core/libs/prx/libSceVisionManager/Export.cpp +++ b/core/libs/prx/libSceVisionManager/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVisionManager/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -16,8 +17,7 @@ int APS5_VABI sceVisionManagerCancelRequest() { } int APS5_VABI sceVisionManagerClose() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerFinalize() { @@ -106,23 +106,19 @@ int APS5_VABI sceVisionManagerGetResultOfFindPadCorrespondRegion() { } int APS5_VABI sceVisionManagerGetResultOfFindUser() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerGetResultOfFindUserInRegion() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerGetResultOfFindUserWithoutIdentify() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerGetResultOfRegisterUserInRegion() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerGetStoreCameraImgYPtr() { @@ -141,13 +137,11 @@ int APS5_VABI sceVisionManagerInitialize() { } int APS5_VABI sceVisionManagerLoadSettings() { - NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceVisionManagerOpen() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerRegistryClear() { @@ -191,18 +185,15 @@ int APS5_VABI sceVisionManagerRequestFindPadCorrespondRegion() { } int APS5_VABI sceVisionManagerRequestFindUser() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerRequestFindUserInRegion() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerRequestFindUserWithoutIdentify() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerRequestHeadDetection() { @@ -231,8 +222,7 @@ int APS5_VABI sceVisionManagerRequestRecogUtility() { } int APS5_VABI sceVisionManagerRequestRegisterUserInRegion() { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80e70004); } int APS5_VABI sceVisionManagerRequestStoreCameraImgY() { diff --git a/core/libs/prx/libSceVisionManager/include/Api.hpp b/core/libs/prx/libSceVisionManager/include/Api.hpp new file mode 100644 index 0000000000..95c8fb3a85 --- /dev/null +++ b/core/libs/prx/libSceVisionManager/include/Api.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVisionManagerApplyCalibrateHmdResult(); +int APS5_VABI sceVisionManagerCancelRequest(); +int APS5_VABI sceVisionManagerClose(); +int APS5_VABI sceVisionManagerFinalize(); +int APS5_VABI sceVisionManagerGenerateRegisterUserData(); +int APS5_VABI sceVisionManagerGenerateRegisterUserDataFinalize(); +int APS5_VABI sceVisionManagerGenerateRegisterUserDataInitialize(); +int APS5_VABI sceVisionManagerGetCalibrateTrackingLedInfo(); +int APS5_VABI sceVisionManagerGetCalibrateTrackingLedNecessity(); +int APS5_VABI sceVisionManagerGetHitCountOfRegisterUserData(); +int APS5_VABI sceVisionManagerGetLibraryVersion(); +int APS5_VABI sceVisionManagerGetNumOfRegisterUserDataSlot(); +int APS5_VABI sceVisionManagerGetRegisterUserDataSize(); +int APS5_VABI sceVisionManagerGetRegisterUserDataVersion(); +int APS5_VABI sceVisionManagerGetResultOfCalibrateHmd(); +int APS5_VABI sceVisionManagerGetResultOfCalibrateStereoCamera(); +int APS5_VABI sceVisionManagerGetResultOfCalibrateTrackingLed(); +int APS5_VABI sceVisionManagerGetResultOfFindFace(); +int APS5_VABI sceVisionManagerGetResultOfFindPad(); +int APS5_VABI sceVisionManagerGetResultOfFindPadCorrespondRegion(); +int APS5_VABI sceVisionManagerGetResultOfFindUser(); +int APS5_VABI sceVisionManagerGetResultOfFindUserInRegion(); +int APS5_VABI sceVisionManagerGetResultOfFindUserWithoutIdentify(); +int APS5_VABI sceVisionManagerGetResultOfRegisterUserInRegion(); +int APS5_VABI sceVisionManagerGetStoreCameraImgYPtr(); +int APS5_VABI sceVisionManagerGetWorkingMemorySize(); +int APS5_VABI sceVisionManagerInitialize(); +int APS5_VABI sceVisionManagerLoadSettings(); +int APS5_VABI sceVisionManagerOpen(); +int APS5_VABI sceVisionManagerRegistryClear(); +int APS5_VABI sceVisionManagerRequestCalibrateHmd(); +int APS5_VABI sceVisionManagerRequestCalibrateStereoCamera(); +int APS5_VABI sceVisionManagerRequestCalibrateTrackingLed(); +int APS5_VABI sceVisionManagerRequestClose(); +int APS5_VABI sceVisionManagerRequestFindFace(); +int APS5_VABI sceVisionManagerRequestFindPad(); +int APS5_VABI sceVisionManagerRequestFindPadCorrespondRegion(); +int APS5_VABI sceVisionManagerRequestFindUser(); +int APS5_VABI sceVisionManagerRequestFindUserInRegion(); +int APS5_VABI sceVisionManagerRequestFindUserWithoutIdentify(); +int APS5_VABI sceVisionManagerRequestHeadDetection(); +int APS5_VABI sceVisionManagerRequestInitializeRegisterUserData(); +int APS5_VABI sceVisionManagerRequestOpen(); +int APS5_VABI sceVisionManagerRequestPadTracking(); +int APS5_VABI sceVisionManagerRequestRecogUtility(); +int APS5_VABI sceVisionManagerRequestRegisterUserInRegion(); +int APS5_VABI sceVisionManagerRequestStoreCameraImgY(); +int APS5_VABI sceVisionManagerSetCallbackForUpdateCalibrateHmdResult(); +int APS5_VABI sceVisionManagerSetCallbackForUpdateCalibrateStereoCameraResult(); +int APS5_VABI sceVisionManagerSetCallbackForUpdateCalibrateTrackingLedResult(); +int APS5_VABI sceVisionManagerSetCallbackForUpdateCameraFrame(); +int APS5_VABI sceVisionManagerSetCallbackForUpdateCtrlCore(); +int APS5_VABI sceVisionManagerSetCallbackForUpdateRecog(); +int APS5_VABI sceVisionManagerSetCallbackForUpdateRunningStatus(); +int APS5_VABI sceVisionManagerSetCallbackForUpdateStoreFrame(); +int APS5_VABI sceVisionManagerWaitResponse(); + +} diff --git a/core/libs/prx/libSceVisionManager/libSceVisionManager.hpp b/core/libs/prx/libSceVisionManager/libSceVisionManager.hpp new file mode 100644 index 0000000000..e2529647b8 --- /dev/null +++ b/core/libs/prx/libSceVisionManager/libSceVisionManager.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVisionManager/include/Api.hpp" diff --git a/core/libs/prx/libSceVnaInternal/libSceVnaInternal.hpp b/core/libs/prx/libSceVnaInternal/libSceVnaInternal.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVnaInternal/libSceVnaInternal.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVnaWebsocket/libSceVnaWebsocket.hpp b/core/libs/prx/libSceVnaWebsocket/libSceVnaWebsocket.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVnaWebsocket/libSceVnaWebsocket.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVoice/Export.cpp b/core/libs/prx/libSceVoice/Export.cpp index b2ae0e88ac..18c75c1db4 100644 --- a/core/libs/prx/libSceVoice/Export.cpp +++ b/core/libs/prx/libSceVoice/Export.cpp @@ -1,6 +1,9 @@ +#include "prx/libSceVoice/include/Api.hpp" #include #include #include +#include +#include #include #include #include @@ -19,24 +22,32 @@ constexpr std::int32_t PortOutPcm = 3; constexpr std::int32_t PortOutVoice = 4; constexpr std::int32_t PortOutDevice = 5; constexpr std::int32_t PortStateReady = 1; -constexpr std::uint32_t VoiceFrameMs = 20; -constexpr std::int32_t PortAttrVoiceActivity = 1001; constexpr int VoiceErrorNotInitialized = static_cast(0x804E0801u); -constexpr int VoiceErrorAlreadyInitialized = static_cast(0x804E0802u); +constexpr auto NotInitialized = static_cast(0x804e0801u); +constexpr auto InvalidPort = static_cast(0x804e0804u); +constexpr auto InvalidArgument = static_cast(0x804e0805u); +constexpr auto PortNotFound = static_cast(0x804e0807u); +constexpr std::int32_t ThreadPriorityHighest = 256; +constexpr std::int32_t ThreadPriorityLowest = 767; +constexpr std::uint64_t ThreadAffinityAll = 0x1fff; struct Port { std::int32_t type; float volume; std::uint32_t bitrate; + std::uint16_t bufferSize; + std::uint16_t frameSize; bool muted = false; + bool paused = false; }; struct Voice { std::mutex mutex; bool initialized = false; bool started = false; - bool chatEnabled = false; - std::uint32_t nextId = 0; + std::int32_t appType = 0; + bool mutedAll = false; + std::deque freePorts; std::map ports; std::set> connections; }; @@ -50,6 +61,10 @@ Voice& State() { throw std::logic_error(std::string(function) + ": " + why); } +bool IsThreadSetting(std::int32_t priority, std::uint64_t affinity) { + return priority >= ThreadPriorityHighest && priority <= ThreadPriorityLowest && affinity != 0 && (affinity & ~ThreadAffinityAll) == 0; +} + Port& RequirePort(Voice& voice, std::uint32_t id, const char* function) { if (!voice.initialized) Fail(function, "library not initialized"); const auto found = voice.ports.find(id); @@ -59,30 +74,60 @@ Port& RequirePort(Voice& voice, std::uint32_t id, const char* function) { bool IsInput(std::int32_t type) { return type == PortInDevice || type == PortInPcm || type == PortInVoice; } +bool VoiceFrame(std::int32_t bitrate, std::uint16_t& frameSize, std::uint16_t& frameDuration) { + switch (bitrate) { + case 3850: frameSize = 20; frameDuration = 40; return true; + case 4650: frameSize = 24; frameDuration = 40; return true; + case 5700: frameSize = 15; frameDuration = 20; return true; + case 7300: frameSize = 19; frameDuration = 20; return true; + case 11800: frameSize = 15; frameDuration = 10; return true; + case 12200: frameSize = 16; frameDuration = 10; return true; + case 14400: frameSize = 27; frameDuration = 15; return true; + case 16000: frameSize = 20; frameDuration = 10; return true; + case 22533: frameSize = 43; frameDuration = 15; return true; + default: return false; + } +} + } extern "C" { -int APS5_VABI sceVoiceInit(VoiceInitParam* param, int32_t version) { - (void)version; - if (param == nullptr) APS5_INVALID_ARG_EX; +int APS5_VABI sceVoiceInit(const VoiceInitParam* param, std::int32_t version) { auto& voice = State(); std::lock_guard lock(voice.mutex); - if (voice.initialized) return VoiceErrorAlreadyInitialized; + if (voice.initialized) return static_cast(0x804e0802u); + if (!param || version != 100) return InvalidArgument; + if (param->app_type != 4 && param->app_type != 0x08000000 && param->app_type != 0x10000000 && param->app_type != 0x20000000) return InvalidArgument; + voice.appType = param->app_type; + voice.freePorts.clear(); + for (std::uint32_t id = 0; id < 128; ++id) voice.freePorts.push_back(id); voice.initialized = true; return 0; } -int APS5_VABI sceVoiceSetThreadsParams(void* params) { - if (params == nullptr) APS5_INVALID_ARG_EX; +int APS5_VABI sceVoiceSetThreadsParams(const VoiceThreadsParams* params) { + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) return NotInitialized; + if (!params) return InvalidArgument; + if (voice.started) return static_cast(0x804e080bu); + if (params->mode == 0) return 0; + if (params->mode != 1) return InvalidArgument; + if (!IsThreadSetting(params->firstPriority, params->firstAffinity) || !IsThreadSetting(params->secondPriority, params->secondAffinity) || !IsThreadSetting(params->thirdPriority, params->thirdAffinity)) { + Fail(__func__, "the result for a priority outside 256-767 or an affinity mask outside 0x1fff is unknown"); + } return 0; } int APS5_VABI sceVoiceStart(const VoiceStartParam* param) { auto& voice = State(); std::lock_guard lock(voice.mutex); - if (!voice.initialized) return VoiceErrorNotInitialized; - if (param == nullptr) APS5_INVALID_ARG_EX; + if (!voice.initialized) return NotInitialized; + if (!param || !param->container) return InvalidArgument; + if (voice.started) return 0; + const std::uint32_t memorySize = voice.appType == 0x10000000 || voice.appType == 0x20000000 ? 0x40000 : 0xc0000; + if (param->mem_size != memorySize) return static_cast(0x804e0806u); voice.started = true; return 0; } @@ -90,7 +135,7 @@ int APS5_VABI sceVoiceStart(const VoiceStartParam* param) { int APS5_VABI sceVoiceStop(void) { auto& voice = State(); std::lock_guard lock(voice.mutex); - if (!voice.initialized) Fail(__func__, "library not initialized"); + if (!voice.initialized) return NotInitialized; voice.started = false; return 0; } @@ -98,34 +143,78 @@ int APS5_VABI sceVoiceStop(void) { int APS5_VABI sceVoiceEnd_nid_postfix(void) { auto& voice = State(); std::lock_guard lock(voice.mutex); - if (!voice.initialized) Fail(__func__, "library not initialized"); + if (!voice.initialized) return NotInitialized; voice.ports.clear(); voice.connections.clear(); voice.started = false; - voice.chatEnabled = false; + voice.appType = 0; + voice.mutedAll = false; voice.initialized = false; return 0; } -int APS5_VABI sceVoiceCreatePort(uint32_t* port_id, const VoicePortParam* param) { +int APS5_VABI sceVoiceCreatePort(std::uint32_t* portId, const VoicePortParam* param) { auto& voice = State(); std::lock_guard lock(voice.mutex); - if (!voice.initialized) return VoiceErrorNotInitialized; - if (port_id == nullptr || param == nullptr) APS5_INVALID_ARG_EX; - if (param->port_type < PortInDevice || param->port_type > PortOutDevice) Fail(__func__, "unknown port type " + std::to_string(param->port_type)); - const bool voicePort = param->port_type == PortInVoice || param->port_type == PortOutVoice; - const auto id = voice.nextId++; - voice.ports.emplace(id, Port{param->port_type, param->volume, voicePort ? static_cast(param->voice.bitrate) : 0u}); - *port_id = id; + if (!voice.initialized) return NotInitialized; + if (!portId || !param) return InvalidArgument; + *portId = 255; + std::uint16_t frameSize = 512; + std::uint16_t frameDuration = 16; + const auto type = param->port_type; + if (type < PortInDevice || type > PortOutDevice) return InvalidArgument; + if (type == PortInPcm || type == PortOutPcm) { + if (param->pcmaudio.sample_rate != 16000 || param->pcmaudio.data_type != 0) return InvalidArgument; + } else if (type == PortInVoice || type == PortOutVoice) { + if (!VoiceFrame(param->voice.bitrate, frameSize, frameDuration)) return InvalidArgument; + } + constexpr auto resourceFull = static_cast(0x804e0808u); + if (voice.freePorts.empty()) return resourceFull; + std::uint32_t count = 0; + std::uint32_t pcmCount = 0; + std::uint32_t pcmBytes = 0; + for (const auto& [id, port] : voice.ports) { + if (port.type == type) ++count; + if (port.type == PortInPcm || port.type == PortOutPcm) { + ++pcmCount; + pcmBytes += 2u * port.bufferSize; + } + } + std::uint16_t bufferSize = 2048; + if (type == PortInDevice || type == PortOutDevice) { + const std::uint32_t limit = type == PortOutDevice && voice.appType == 4 ? 5 : 4; + if (count == limit) return resourceFull; + if (type == PortInDevice ? param->device.type != 0 : param->device.type != 2 && param->device.type != 14) return InvalidArgument; + bufferSize = 4096; + } else if (type == PortInPcm || type == PortOutPcm) { + bufferSize = static_cast(param->pcmaudio.buffer_size - param->pcmaudio.buffer_size % frameSize); + if (bufferSize < 1024 || bufferSize > 64512) return InvalidArgument; + if (type == PortInPcm && bufferSize / frameSize * frameDuration < param->threshold) return resourceFull; + const std::uint32_t limit = voice.appType == 4 ? 128 : voice.appType == 0x08000000 ? 30 : 14; + const std::uint32_t byteLimit = voice.appType == 4 || voice.appType == 0x08000000 ? 0x80000 : voice.appType == 0x10000000 ? 0x5000 : 0x10000; + if (pcmCount == limit || pcmBytes + 2u * bufferSize > byteLimit) return resourceFull; + } else { + if (type == PortInVoice && bufferSize / frameSize * frameDuration < param->threshold) return resourceFull; + const std::uint32_t limit = voice.appType == 4 ? 0 : type == PortOutVoice ? 4 : voice.appType == 0x20000000 ? 7 : 15; + if (count == limit) return resourceFull; + } + const auto id = voice.freePorts.front(); + const bool voicePort = type == PortInVoice || type == PortOutVoice; + voice.ports.emplace(id, Port{type, param->volume, voicePort ? static_cast(param->voice.bitrate) : 0u, bufferSize, frameSize, param->mute != 0}); + voice.freePorts.pop_front(); + *portId = id; return 0; } int APS5_VABI sceVoiceDeletePort(uint32_t port_id) { auto& voice = State(); std::lock_guard lock(voice.mutex); - RequirePort(voice, port_id, __func__); + if (!voice.initialized) return NotInitialized; + if (port_id > 127) return InvalidPort; + if (!voice.ports.contains(port_id)) return PortNotFound; std::erase_if(voice.connections, [port_id](const auto& link) { return link.first == port_id || link.second == port_id; }); voice.ports.erase(port_id); + voice.freePorts.push_back(port_id); return 0; } @@ -147,28 +236,40 @@ int APS5_VABI sceVoiceDisconnectIPortFromOPort(uint32_t input_port_id, uint32_t return 0; } -int APS5_VABI sceVoiceGetBitRate(uint32_t port_id, uint32_t* bitrate) { - if (bitrate == nullptr) APS5_INVALID_ARG_EX; +int APS5_VABI sceVoiceGetBitRate(std::uint32_t portId, std::uint32_t* bitrate) { auto& voice = State(); std::lock_guard lock(voice.mutex); - const auto& port = RequirePort(voice, port_id, __func__); - if (port.type != PortInVoice && port.type != PortOutVoice) Fail(__func__, "port does not carry encoded voice"); + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + if (!bitrate) return InvalidArgument; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + const auto& port = found->second; + if (port.type != PortInVoice && port.type != PortOutVoice) return PortNotFound; *bitrate = port.bitrate; return 0; } -int APS5_VABI sceVoiceSetVolume(uint32_t port_id, float volume) { +int APS5_VABI sceVoiceSetVolume(std::uint32_t portId, float volume) { auto& voice = State(); std::lock_guard lock(voice.mutex); - RequirePort(voice, port_id, __func__).volume = volume; + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + found->second.volume = std::isfinite(volume) ? volume : 0.0f; return 0; } -int APS5_VABI sceVoiceGetVolume(uint32_t port_id, float* volume) { - if (volume == nullptr) APS5_INVALID_ARG_EX; +int APS5_VABI sceVoiceGetVolume(std::uint32_t portId, float* volume) { auto& voice = State(); std::lock_guard lock(voice.mutex); - *volume = RequirePort(voice, port_id, __func__).volume; + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + if (!volume) return InvalidArgument; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + *volume = found->second.volume; return 0; } @@ -178,23 +279,43 @@ int APS5_VABI sceVoiceGetPortInfo(uint32_t port_id, VoicePortInfo* info) { if (!voice.initialized) return VoiceErrorNotInitialized; if (info == nullptr) APS5_INVALID_ARG_EX; const auto& port = RequirePort(voice, port_id, __func__); - *info = VoicePortInfo{}; info->port_type = port.type; info->state = PortStateReady; - if (port.bitrate != 0) info->frame_size = (port.bitrate * VoiceFrameMs + 7999u) / 8000u; + info->byte_count = 0; + info->frame_size = port.bitrate != 0 ? port.frameSize : 2; return 0; } -int APS5_VABI sceVoiceGetPortAttr(uint32_t port_id, int32_t attr, void* value, int32_t size) { +int APS5_VABI sceVoiceGetPortAttr(std::uint32_t portId, std::int32_t attr, void* value, std::int32_t size) { auto& voice = State(); std::lock_guard lock(voice.mutex); - RequirePort(voice, port_id, __func__); - if (attr == PortAttrVoiceActivity) { - if (value == nullptr || size != 1) APS5_INVALID_ARG_EX; - std::memset(value, 0, 1); - return 0; + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + if (value == nullptr) return InvalidArgument; + std::int32_t expectedSize; + switch (attr) { + case 1000: + case 1001: + case 2000: + case 2002: + expectedSize = 1; + break; + case 1002: + case 2001: + expectedSize = 4; + break; + case 1003: + expectedSize = 2; + break; + default: + return InvalidArgument; } - Fail(__func__, "attribute " + std::to_string(attr) + " not implemented"); + if (size != expectedSize) return InvalidArgument; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return InvalidPort; + const auto requiredType = attr == 1003 ? PortOutDevice : PortInDevice; + if (found->second.type != requiredType) return InvalidArgument; + Fail(__func__, "device attribute backend is not implemented"); } int APS5_VABI sceVoiceReadFromOPort(uint32_t output_port_id, void* data, uint32_t* size) { @@ -215,74 +336,100 @@ int APS5_VABI sceVoiceWriteToIPort(uint32_t input_port_id, const void* data, uin return 0; } -int APS5_VABI sceVoiceSetMuteFlag(uint32_t port_id, bool muted) { +int APS5_VABI sceVoiceSetMuteFlag(std::uint32_t portId, std::uint8_t muted) { auto& voice = State(); std::lock_guard lock(voice.mutex); - RequirePort(voice, port_id, __func__).muted = muted; + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + found->second.muted = muted != 0; return 0; } -int APS5_VABI sceVoiceGetMuteFlag(uint32_t port_id, bool* muted) { - if (muted == nullptr) APS5_INVALID_ARG_EX; +int APS5_VABI sceVoiceGetMuteFlag(std::uint32_t portId, bool* muted) { auto& voice = State(); std::lock_guard lock(voice.mutex); - *muted = RequirePort(voice, port_id, __func__).muted; + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + if (!muted) return InvalidArgument; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + *muted = found->second.muted || voice.mutedAll; return 0; } int APS5_VABI sceVoiceGetResourceInfo(VoiceResourceInfo* info) { - if (info == nullptr) APS5_INVALID_ARG_EX; auto& voice = State(); std::lock_guard lock(voice.mutex); - if (!voice.initialized) Fail(__func__, "library not initialized"); - info->max_voice_input_ports = 7; - info->max_voice_output_ports = 7; - info->max_device_input_ports = 1; - info->max_device_output_ports = 1; - info->max_ports = 64; + if (!voice.initialized) return NotInitialized; + if (info == nullptr) return InvalidArgument; + switch (voice.appType) { + case 4: + *info = {0, 0, 4, 5}; + break; + case 0x08000000: + case 0x10000000: + *info = {15, 4, 4, 4}; + break; + case 0x20000000: + *info = {7, 4, 4, 4}; + break; + default: + Fail(__func__, "unsupported initialization mode"); + } return 0; } int APS5_VABI sceVoiceEnableChat(void) { auto& voice = State(); std::lock_guard lock(voice.mutex); - if (!voice.initialized) Fail(__func__, "library not initialized"); - voice.chatEnabled = true; + if (!voice.initialized) return NotInitialized; + Fail(__func__, "audio chat backend is not implemented"); +} + +int APS5_VABI sceVoiceResetPort(std::uint32_t portId) { + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + if (voice.started && (found->second.type == PortInDevice || found->second.type == PortOutDevice)) Fail(__func__, "device reset backend is not implemented"); return 0; } -int APS5_VABI sceVoiceResetPort(uint32_t port_id) { +int APS5_VABI sceVoicePausePort(std::uint32_t portId) { auto& voice = State(); std::lock_guard lock(voice.mutex); - RequirePort(voice, port_id, __func__); + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + found->second.paused = true; return 0; } -int APS5_VABI sceVoicePausePort(uint32_t port_id) { +int APS5_VABI sceVoiceResumePort(std::uint32_t portId) { auto& voice = State(); std::lock_guard lock(voice.mutex); - RequirePort(voice, port_id, __func__); - return 0; -} - -int APS5_VABI sceVoiceResumePort(uint32_t port_id) { - auto& voice = State(); - std::lock_guard lock(voice.mutex); - RequirePort(voice, port_id, __func__); + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + found->second.paused = false; return 0; } int APS5_VABI sceVoiceDisableChat(void) { auto& voice = State(); std::lock_guard lock(voice.mutex); - if (!voice.initialized) Fail(__func__, "library not initialized"); - voice.chatEnabled = false; - return 0; + if (!voice.initialized) return NotInitialized; + Fail(__func__, "audio chat backend is not implemented"); } -int APS5_VABI sceVoiceInitHQ(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceInitHQ(const VoiceInitParam* param, std::int32_t version) { + return sceVoiceInit(param, version); } int APS5_VABI sceVoiceGetMemorySize(void) { @@ -300,7 +447,7 @@ int APS5_VABI sceVoiceQAEnableRecording(void) { return 0; } -int APS5_VABI sceVoiceVADAdjustment(void) { +int APS5_VABI sceVoiceVADAdjustment(float, float) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -310,23 +457,55 @@ int APS5_VABI sceVoiceVADPVTAdjustment(void) { return 0; } -int APS5_VABI sceVoiceVADSetVersion(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVoiceVADSetVersion(std::int32_t version) { + return version == 0 || version == 1 ? 0 : InvalidArgument; +} + +int APS5_VABI sceVoiceSetMuteFlagAll(bool muted) { + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) return NotInitialized; + voice.mutedAll = muted; return 0; } -int APS5_VABI sceVoiceSetMuteFlagAll(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVoiceSetBitRate(std::uint32_t portId, std::uint32_t bitrate) { + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + auto& port = found->second; + if (port.type != PortInVoice && port.type != PortOutVoice) Fail(__func__, "port does not carry encoded voice"); + std::uint16_t frameSize = 0; + std::uint16_t frameDuration = 0; + if (bitrate > INT32_MAX || !VoiceFrame(static_cast(bitrate), frameSize, frameDuration)) Fail(__func__, "unsupported bitrate " + std::to_string(bitrate)); + port.bitrate = bitrate; + port.frameSize = frameSize; return 0; } -int APS5_VABI sceVoiceSetBitRate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceUpdatePort(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVoiceUpdatePort(std::uint32_t portId, const VoicePortParam* param) { + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + if (!param) Fail(__func__, "null port parameters"); + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + auto& port = found->second; + if (param->port_type != port.type) Fail(__func__, "port type " + std::to_string(param->port_type) + " differs from the port's " + std::to_string(port.type)); + std::uint16_t frameSize = port.frameSize; + std::uint16_t frameDuration = 0; + const bool voicePort = port.type == PortInVoice || port.type == PortOutVoice; + if (voicePort && !VoiceFrame(param->voice.bitrate, frameSize, frameDuration)) Fail(__func__, "unsupported bitrate " + std::to_string(param->voice.bitrate)); + port.muted = param->mute != 0; + port.volume = std::isfinite(param->volume) ? param->volume : 0.0f; + if (voicePort) { + port.bitrate = static_cast(param->voice.bitrate); + port.frameSize = frameSize; + } return 0; } diff --git a/core/libs/prx/libSceVoice/include/Api.hpp b/core/libs/prx/libSceVoice/include/Api.hpp new file mode 100644 index 0000000000..4ac2972a27 --- /dev/null +++ b/core/libs/prx/libSceVoice/include/Api.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVoiceInit(const VoiceInitParam* param, std::int32_t version); +int APS5_VABI sceVoiceSetThreadsParams(const VoiceThreadsParams* params); +int APS5_VABI sceVoiceStart(const VoiceStartParam* param); +int APS5_VABI sceVoiceStop(void); +int APS5_VABI sceVoiceEnd_nid_postfix(void); +int APS5_VABI sceVoiceCreatePort(std::uint32_t* portId, const VoicePortParam* param); +int APS5_VABI sceVoiceDeletePort(uint32_t portId); +int APS5_VABI sceVoiceConnectIPortToOPort(uint32_t inputPortId, uint32_t outputPortId); +int APS5_VABI sceVoiceDisconnectIPortFromOPort(uint32_t inputPortId, uint32_t outputPortId); +int APS5_VABI sceVoiceGetBitRate(std::uint32_t portId, std::uint32_t* bitrate); +int APS5_VABI sceVoiceSetVolume(std::uint32_t portId, float volume); +int APS5_VABI sceVoiceGetVolume(std::uint32_t portId, float* volume); +int APS5_VABI sceVoiceGetPortInfo(uint32_t portId, VoicePortInfo* info); +int APS5_VABI sceVoiceGetPortAttr(std::uint32_t portId, std::int32_t attr, void* value, std::int32_t size); +int APS5_VABI sceVoiceReadFromOPort(uint32_t outputPortId, void* data, uint32_t* size); +int APS5_VABI sceVoiceWriteToIPort(uint32_t inputPortId, const void* data, uint32_t* size, int16_t frameGaps); +int APS5_VABI sceVoiceSetMuteFlag(std::uint32_t portId, std::uint8_t muted); +int APS5_VABI sceVoiceGetMuteFlag(std::uint32_t portId, bool* muted); +int APS5_VABI sceVoiceGetResourceInfo(VoiceResourceInfo* info); +int APS5_VABI sceVoiceEnableChat(void); +int APS5_VABI sceVoiceResetPort(std::uint32_t portId); +int APS5_VABI sceVoicePausePort(std::uint32_t portId); +int APS5_VABI sceVoiceResumePort(std::uint32_t portId); +int APS5_VABI sceVoiceDisableChat(void); +int APS5_VABI sceVoiceInitHQ(const VoiceInitParam* param, std::int32_t version); +int APS5_VABI sceVoiceGetMemorySize(void); +int APS5_VABI sceVoiceDeviceInCustomerData(void); +int APS5_VABI sceVoiceQAEnableRecording(void); +int APS5_VABI sceVoiceVADAdjustment(float param1, float param2); +int APS5_VABI sceVoiceVADPVTAdjustment(void); +int APS5_VABI sceVoiceVADSetVersion(std::int32_t version); +int APS5_VABI sceVoiceSetMuteFlagAll(bool muted); +int APS5_VABI sceVoiceSetBitRate(std::uint32_t portId, std::uint32_t bitrate); +int APS5_VABI sceVoiceUpdatePort(std::uint32_t portId, const VoicePortParam* param); +int APS5_VABI sceVoicePausePortAll(void); +int APS5_VABI sceVoiceResumePortAll(void); + +} diff --git a/core/libs/prx/libSceVoice/libSceVoice.hpp b/core/libs/prx/libSceVoice/libSceVoice.hpp new file mode 100644 index 0000000000..5892fc763a --- /dev/null +++ b/core/libs/prx/libSceVoice/libSceVoice.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVoice/include/Api.hpp" diff --git a/core/libs/prx/libSceVoiceChat/Export.cpp b/core/libs/prx/libSceVoiceChat/Export.cpp index 49e582df5f..b1fde4c767 100644 --- a/core/libs/prx/libSceVoiceChat/Export.cpp +++ b/core/libs/prx/libSceVoiceChat/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVoiceChat/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libSceVoiceChat/include/Api.hpp b/core/libs/prx/libSceVoiceChat/include/Api.hpp new file mode 100644 index 0000000000..5d5676dff3 --- /dev/null +++ b/core/libs/prx/libSceVoiceChat/include/Api.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVoiceChatInitialize(void* param); +int APS5_VABI sceVoiceChatTerminate(void); +int APS5_VABI sceVoiceChatRegisterHandlers(void* a); +int APS5_VABI sceVoiceChatRegisterMicEventHandler(void* a, void* b); +int APS5_VABI sceVoiceChatProcessEvent(void); +int APS5_VABI sceVoiceChatDeleteRequest(int requestId); +int APS5_VABI sceVoiceChatCreateRequest(void* a, void* b); +int APS5_VABI NAME(int requestId, void* a, void* b); +int APS5_VABI sceVoiceChatAbortRequest(int requestId); +int APS5_VABI sceVoiceChatGetActiveChannel(void* channel); +int APS5_VABI sceVoiceChatGetChannelMemberActiveChannelMatchingState(void* a, void* b); +int APS5_VABI sceVoiceChatGetChannelMemberVoiceConnectionState(void* a, void* b); +int APS5_VABI sceVoiceChatGetChatChannelMemberInfoList(void* a, void* b); +int APS5_VABI sceVoiceChatGetChatGroupMemberInfoList(void* a, void* b); +int APS5_VABI sceVoiceChatGetMicState(void* state); +int APS5_VABI sceVoiceChatGetUserVoiceVolumeGain(void* a, void* gain); +int APS5_VABI sceVoiceChatSetUserVoiceVolumeGain(void* a, int32_t gain); +int APS5_VABI sceVoiceChatRegisterChannelEventHandler(void* a, void* b); +int APS5_VABI sceVoiceChatRegisterDaemonEventHandler(void* a, void* b); +int APS5_VABI sceVoiceChatRegisterMemberEventHandler(void* a, void* b); +int APS5_VABI sceVoiceChatRegisterPartyUserStateChangedHandler(void* a, void* b); +int APS5_VABI sceVoiceChatRegisterVoiceEventHandler(void* a, void* b); +int APS5_VABI sceVoiceChatStartAccessibilityEvent(void* a); +int APS5_VABI sceVoiceChatStartChannelEvent(void* a); +int APS5_VABI sceVoiceChatStartDaemonEvent(void* a); +int APS5_VABI sceVoiceChatStartMemberEvent(void* a); +int APS5_VABI sceVoiceChatStartRangedMemberEvent(void* a); +int APS5_VABI sceVoiceChatStartVoiceEvent(void* a); +int APS5_VABI sceVoiceChatStopAccessibilityEvent(void* a); +int APS5_VABI sceVoiceChatStopChannelEvent(void* a); +int APS5_VABI sceVoiceChatStopDaemonEvent(void* a); +int APS5_VABI sceVoiceChatStopMemberEvent(void* a); +int APS5_VABI sceVoiceChatStopRangedMemberEvent(void* a); +int APS5_VABI sceVoiceChatStopVoiceEvent(void* a); +int APS5_VABI sceVoiceChatUnregisterAccessibilityEventHandler(void* a); +int APS5_VABI sceVoiceChatUnregisterChannelEventHandler(void* a); +int APS5_VABI sceVoiceChatUnregisterDaemonEventHandler(void* a); +int APS5_VABI sceVoiceChatUnregisterMemberEventHandler(void* a); +int APS5_VABI sceVoiceChatUnregisterMicEventHandler(void* a); +int APS5_VABI sceVoiceChatUnregisterPartyUserStateChangedHandler(void* a); +int APS5_VABI sceVoiceChatUnregisterRangedMemberEventHandler(void* a); +int APS5_VABI sceVoiceChatUnregisterVoiceEventHandler(void* a); + +} diff --git a/core/libs/prx/libSceVoiceChat/libSceVoiceChat.hpp b/core/libs/prx/libSceVoiceChat/libSceVoiceChat.hpp new file mode 100644 index 0000000000..81d8245cc3 --- /dev/null +++ b/core/libs/prx/libSceVoiceChat/libSceVoiceChat.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVoiceChat/include/Api.hpp" diff --git a/core/libs/prx/libSceVoiceCommand/libSceVoiceCommand.hpp b/core/libs/prx/libSceVoiceCommand/libSceVoiceCommand.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceVoiceCommand/libSceVoiceCommand.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceVoiceQoS/Export.cpp b/core/libs/prx/libSceVoiceQoS/Export.cpp index 96053a9e69..7781cd7ec8 100644 --- a/core/libs/prx/libSceVoiceQoS/Export.cpp +++ b/core/libs/prx/libSceVoiceQoS/Export.cpp @@ -1,12 +1,11 @@ +#include "prx/libSceVoiceQoS/include/Api.hpp" #include #include #include -#include -#include +#include #include #include #include -#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -17,9 +16,10 @@ constexpr std::int32_t VOICE_QOS_APP_TYPE_10000000 = 0x10000000; constexpr std::uint32_t VOICE_QOS_MEMORY_SIZE = 0x40000; constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_NOT_INIT = static_cast(0x804E0901); constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID = static_cast(0x804E0902); +constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_LIMIT = static_cast(0x804E0907); constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_INITIALIZED = static_cast(0x804E0905); -using Endpoint = std::map>; +struct Endpoint {}; struct Connection { std::int32_t local; @@ -29,7 +29,7 @@ struct Connection { struct QoS { std::mutex mutex; bool initialized = false; - std::int32_t nextId = 1; + std::int32_t endpointLimit = 0; std::map locals; std::map remotes; std::map connections; @@ -40,53 +40,25 @@ QoS& State() { return qos; } -std::int32_t NewId(QoS& qos) { - const std::int32_t id = qos.nextId; - qos.nextId = qos.nextId == std::numeric_limits::max() ? 1 : qos.nextId + 1; - return id; -} - -int CreateEndpoint(std::int32_t* id, std::map& endpoints, QoS& qos) { +int CreateEndpoint(std::int32_t* id, std::map& endpoints, std::int32_t limit) { if (!id) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - *id = NewId(qos); - endpoints.emplace(*id, Endpoint{}); - return 0; + for (std::int32_t candidate = 0; candidate < limit; ++candidate) { + if (!endpoints.contains(candidate)) { + endpoints.emplace(candidate, Endpoint{}); + *id = candidate; + return 0; + } + } + *id = -1; + return SCE_VOICE_ERROR_LIBVOICEQOS_LIMIT; } int DeleteEndpoint(std::int32_t id, std::map& endpoints) { return endpoints.erase(id) != 0 ? 0 : SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; } -int SetAttribute(std::map& endpoints, std::int32_t id, std::int32_t attribute, const void* value, std::int32_t size) { - const auto endpoint = endpoints.find(id); - if (endpoint == endpoints.end() || !value) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - if (size > 0) { - const auto* bytes = static_cast(value); - endpoint->second[attribute].assign(bytes, bytes + size); - } else { - endpoint->second[attribute].clear(); - } - return 0; -} - -int GetAttribute(std::map& endpoints, std::int32_t id, std::int32_t attribute, void* value, std::int32_t size) { - const auto endpoint = endpoints.find(id); - if (endpoint == endpoints.end() || !value || size <= 0) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - const auto attr = endpoint->second.find(attribute); - if (attr != endpoint->second.end() && !attr->second.empty()) { - const auto toCopy = std::min(static_cast(size), attr->second.size()); - std::memcpy(value, attr->second.data(), toCopy); - if (static_cast(size) > toCopy) { - std::memset(static_cast(value) + toCopy, 0, static_cast(size) - toCopy); - } - } else { - std::memset(value, 0, static_cast(size)); - } - return 0; -} - -template -int Locked(Body&& body) { +template +int Locked(TBody&& body) { auto& qos = State(); std::lock_guard lock(qos.mutex); if (!qos.initialized) return SCE_VOICE_ERROR_LIBVOICEQOS_NOT_INIT; @@ -102,6 +74,7 @@ int APS5_VABI sceVoiceQoSInit(void* memBlock, uint32_t memSize, int32_t appType) std::lock_guard lock(qos.mutex); if (qos.initialized) return SCE_VOICE_ERROR_LIBVOICEQOS_INITIALIZED; if (!memBlock || memSize != VOICE_QOS_MEMORY_SIZE || (appType != VOICE_QOS_APP_TYPE_GAME && appType != VOICE_QOS_APP_TYPE_10000000)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + qos.endpointLimit = appType == VOICE_QOS_APP_TYPE_10000000 ? 15 : 7; qos.initialized = true; return 0; } @@ -117,10 +90,15 @@ int APS5_VABI sceVoiceQoSEnd() { } int APS5_VABI sceVoiceQoSCreateLocalEndpoint(int32_t* localId, int32_t userId, int32_t deviceInId, int32_t deviceOutId) { - (void)userId; (void)deviceInId; (void)deviceOutId; - return Locked([&](QoS& qos) { return CreateEndpoint(localId, qos.locals, qos); }); + return Locked([&](QoS& qos) { + if (localId && userId == -1) { + *localId = -1; + return SCE_VOICE_ERROR_LIBVOICEQOS_LIMIT; + } + return CreateEndpoint(localId, qos.locals, 1); + }); } int APS5_VABI sceVoiceQoSDeleteLocalEndpoint(int32_t localId) { @@ -128,37 +106,64 @@ int APS5_VABI sceVoiceQoSDeleteLocalEndpoint(int32_t localId) { } int APS5_VABI sceVoiceQoSCreateRemoteEndpoint(int32_t* remoteId) { - return Locked([&](QoS& qos) { return CreateEndpoint(remoteId, qos.remotes, qos); }); + return Locked([&](QoS& qos) { return CreateEndpoint(remoteId, qos.remotes, qos.endpointLimit); }); } int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint(int32_t remoteId) { - return Locked([&](QoS& qos) { return DeleteEndpoint(remoteId, qos.remotes); }); -} - -int APS5_VABI sceVoiceQoSConnect(int32_t* connectionId, int32_t localId, int32_t remoteId) { return Locked([&](QoS& qos) { - if (!connectionId || !qos.locals.contains(localId) || !qos.remotes.contains(remoteId)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - *connectionId = NewId(qos); - qos.connections.emplace(*connectionId, Connection{localId, remoteId}); + const int result = DeleteEndpoint(remoteId, qos.remotes); + if (result != 0) return result; + for (auto it = qos.connections.begin(); it != qos.connections.end(); ++it) { + if (it->second.remote == remoteId) { + qos.connections.erase(it); + break; + } + } return 0; }); } -int APS5_VABI sceVoiceQoSDisconnect(int32_t connectionId) { - return Locked([&](QoS& qos) { return qos.connections.erase(connectionId) != 0 ? 0 : SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; }); +int APS5_VABI sceVoiceQoSConnect(int32_t* connectionId, int32_t localId, int32_t remoteId) { + return Locked([&](QoS& qos) { + if (!connectionId || localId != 0 || remoteId < 0 || remoteId >= qos.endpointLimit) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + if (!qos.locals.contains(localId) || !qos.remotes.contains(remoteId)) throw std::runtime_error("sceVoiceQoSConnect: missing voice port error behavior is not implemented"); + for (std::int32_t candidate = 0; candidate < qos.endpointLimit; ++candidate) { + if (!qos.connections.contains(candidate)) { + qos.connections.emplace(candidate, Connection{localId, remoteId}); + *connectionId = candidate; + return 0; + } + } + *connectionId = -1; + return SCE_VOICE_ERROR_LIBVOICEQOS_LIMIT; + }); } -int APS5_VABI sceVoiceQoSSetConnectionAttribute() { +int APS5_VABI sceVoiceQoSDisconnect(int32_t connectionId) { + return Locked([&](QoS& qos) { + if (connectionId < 0 || connectionId >= qos.endpointLimit) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + qos.connections.erase(connectionId); + return 0; + }); +} + +int APS5_VABI sceVoiceQoSSetConnectionAttribute(std::int32_t connectionId, std::int32_t attributeId, const void* value, std::int32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute(int32_t localId, int32_t attributeId, const void* value, int32_t size) { - return Locked([&](QoS& qos) { return SetAttribute(qos.locals, localId, attributeId, value, size); }); + return Locked([&](QoS&) -> int { + if (localId != 0 || !value || attributeId < 0 || attributeId > 3 || size != (attributeId % 2 == 0 ? 4 : 1)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + throw std::runtime_error("sceVoiceQoSSetLocalEndpointAttribute: voice port attributes are not implemented"); + }); } int APS5_VABI sceVoiceQoSSetRemoteEndpointAttribute(int32_t remoteId, int32_t attributeId, const void* value, int32_t size) { - return Locked([&](QoS& qos) { return SetAttribute(qos.remotes, remoteId, attributeId, value, size); }); + return Locked([&](QoS& qos) -> int { + if (remoteId < 0 || remoteId >= qos.endpointLimit || !value || attributeId != 7 || size != 1) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + throw std::runtime_error("sceVoiceQoSSetRemoteEndpointAttribute: voice port attributes are not implemented"); + }); } int APS5_VABI sceVoiceQoSReadPacket(int32_t connectionId, void* buffer, uint32_t* size) { @@ -177,10 +182,11 @@ int APS5_VABI sceVoiceQoSWritePacket(int32_t connectionId, const void* buffer, u } int APS5_VABI sceVoiceQoSGetLocalEndpointAttribute(int32_t localId, int32_t attributeId, void* value, int32_t size) { - return Locked([&](QoS& qos) { return GetAttribute(qos.locals, localId, attributeId, value, size); }); + NotImplemented_nid_no_patch(__func__); + return 0; } -int APS5_VABI sceVoiceQoSGetRemoteEndpointAttribute(int32_t remoteId, int32_t attributeId, const void* value, int32_t size) { +int APS5_VABI sceVoiceQoSGetRemoteEndpointAttribute(int32_t remoteId, int32_t attributeId, void* value, int32_t size) { (void)remoteId; (void)attributeId; (void)value; @@ -200,20 +206,18 @@ int APS5_VABI sceVoiceQoSGetConnectionAttribute(int32_t connectionId, int32_t at int APS5_VABI sceVoiceQoSGetLocalEndpoint(int32_t connectionId, int32_t* localId) { return Locked([&](QoS& qos) { - if (!localId) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + if (!localId || connectionId < 0 || connectionId >= qos.endpointLimit) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; const auto it = qos.connections.find(connectionId); - if (it == qos.connections.end()) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - *localId = it->second.local; + *localId = it == qos.connections.end() ? -1 : it->second.local; return 0; }); } int APS5_VABI sceVoiceQoSGetRemoteEndpoint(int32_t connectionId, int32_t* remoteId) { return Locked([&](QoS& qos) { - if (!remoteId) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + if (!remoteId || connectionId < 0 || connectionId >= qos.endpointLimit) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; const auto it = qos.connections.find(connectionId); - if (it == qos.connections.end()) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - *remoteId = it->second.remote; + *remoteId = it == qos.connections.end() ? -1 : it->second.remote; return 0; }); } @@ -237,12 +241,12 @@ int APS5_VABI sceVoiceQoSGetStatus(int32_t connectionId, int32_t statusId, void* return 0; } -int APS5_VABI sceVoiceQoSDebugGetStatus() { +int APS5_VABI sceVoiceQoSDebugGetStatus(std::int32_t connectionId, std::int32_t statusId, void* value, std::int32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVoiceQoSInitHQ() { +int APS5_VABI sceVoiceQoSInitHQ(void* memory, std::uint32_t memorySize, std::int32_t appType) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceVoiceQoS/include/Api.hpp b/core/libs/prx/libSceVoiceQoS/include/Api.hpp new file mode 100644 index 0000000000..a37b2d9239 --- /dev/null +++ b/core/libs/prx/libSceVoiceQoS/include/Api.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVoiceQoSInit(void* memBlock, uint32_t memSize, int32_t appType); +int APS5_VABI sceVoiceQoSEnd(); +int APS5_VABI sceVoiceQoSCreateLocalEndpoint(int32_t* localId, int32_t userId, int32_t deviceInId, int32_t deviceOutId); +int APS5_VABI sceVoiceQoSDeleteLocalEndpoint(int32_t localId); +int APS5_VABI sceVoiceQoSCreateRemoteEndpoint(int32_t* remoteId); +int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint(int32_t remoteId); +int APS5_VABI sceVoiceQoSConnect(int32_t* connectionId, int32_t localId, int32_t remoteId); +int APS5_VABI sceVoiceQoSDisconnect(int32_t connectionId); +int APS5_VABI sceVoiceQoSSetConnectionAttribute(std::int32_t connectionId, std::int32_t attributeId, const void* value, std::int32_t size); +int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute(int32_t localId, int32_t attributeId, const void* value, int32_t size); +int APS5_VABI sceVoiceQoSSetRemoteEndpointAttribute(int32_t remoteId, int32_t attributeId, const void* value, int32_t size); +int APS5_VABI sceVoiceQoSReadPacket(int32_t connectionId, void* buffer, uint32_t* size); +int APS5_VABI sceVoiceQoSWritePacket(int32_t connectionId, const void* buffer, uint32_t* size); +int APS5_VABI sceVoiceQoSGetLocalEndpointAttribute(int32_t localId, int32_t attributeId, void* value, int32_t size); +int APS5_VABI sceVoiceQoSGetRemoteEndpointAttribute(int32_t remoteId, int32_t attributeId, void* value, int32_t size); +int APS5_VABI sceVoiceQoSGetConnectionAttribute(int32_t connectionId, int32_t attributeId, void* value, int32_t size); +int APS5_VABI sceVoiceQoSGetLocalEndpoint(int32_t connectionId, int32_t* localId); +int APS5_VABI sceVoiceQoSGetRemoteEndpoint(int32_t connectionId, int32_t* remoteId); +int APS5_VABI sceVoiceQoSEnableChat(); +int APS5_VABI sceVoiceQoSDisableChat(); +int APS5_VABI sceVoiceQoSGetStatus(int32_t connectionId, int32_t statusId, void* value, int32_t size); +int APS5_VABI sceVoiceQoSDebugGetStatus(std::int32_t connectionId, std::int32_t statusId, void* value, std::int32_t size); +int APS5_VABI sceVoiceQoSInitHQ(void* memory, std::uint32_t memorySize, std::int32_t appType); +int APS5_VABI sceVoiceQoSSetMode(); +int APS5_VABI sceVoiceQoSTerminate(); + +} diff --git a/core/libs/prx/libSceVoiceQoS/libSceVoiceQoS.hpp b/core/libs/prx/libSceVoiceQoS/libSceVoiceQoS.hpp new file mode 100644 index 0000000000..e6e7967b46 --- /dev/null +++ b/core/libs/prx/libSceVoiceQoS/libSceVoiceQoS.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVoiceQoS/include/Api.hpp" diff --git a/core/libs/prx/libSceVorbisDec/CMakeLists.txt b/core/libs/prx/libSceVorbisDec/CMakeLists.txt new file mode 100644 index 0000000000..0da3f0a43e --- /dev/null +++ b/core/libs/prx/libSceVorbisDec/CMakeLists.txt @@ -0,0 +1,77 @@ +enable_language(C) + +set(OGG_DIR ${CMAKE_SOURCE_DIR}/3rdparty/ogg) +set(VORBIS_DIR ${CMAKE_SOURCE_DIR}/3rdparty/vorbis) +set(OGG_GENERATED_DIR ${CMAKE_CURRENT_BINARY_DIR}/ogg/include) + +include(CheckIncludeFiles) +check_include_files(inttypes.h INCLUDE_INTTYPES_H) +check_include_files(stdint.h INCLUDE_STDINT_H) +check_include_files(sys/types.h INCLUDE_SYS_TYPES_H) +set(SIZE16 int16_t) +set(USIZE16 uint16_t) +set(SIZE32 int32_t) +set(USIZE32 uint32_t) +set(SIZE64 int64_t) +set(USIZE64 uint64_t) +configure_file(${OGG_DIR}/include/ogg/config_types.h.in ${OGG_GENERATED_DIR}/ogg/config_types.h @ONLY) + +add_library(ogg STATIC EXCLUDE_FROM_ALL + ${OGG_DIR}/src/bitwise.c + ${OGG_DIR}/src/framing.c +) +target_include_directories(ogg PUBLIC ${OGG_DIR}/include ${OGG_GENERATED_DIR}) + +add_library(vorbis STATIC EXCLUDE_FROM_ALL + ${VORBIS_DIR}/lib/analysis.c + ${VORBIS_DIR}/lib/bitrate.c + ${VORBIS_DIR}/lib/block.c + ${VORBIS_DIR}/lib/codebook.c + ${VORBIS_DIR}/lib/envelope.c + ${VORBIS_DIR}/lib/floor0.c + ${VORBIS_DIR}/lib/floor1.c + ${VORBIS_DIR}/lib/info.c + ${VORBIS_DIR}/lib/lookup.c + ${VORBIS_DIR}/lib/lpc.c + ${VORBIS_DIR}/lib/lsp.c + ${VORBIS_DIR}/lib/mapping0.c + ${VORBIS_DIR}/lib/mdct.c + ${VORBIS_DIR}/lib/psy.c + ${VORBIS_DIR}/lib/registry.c + ${VORBIS_DIR}/lib/res0.c + ${VORBIS_DIR}/lib/sharedbook.c + ${VORBIS_DIR}/lib/smallft.c + ${VORBIS_DIR}/lib/synthesis.c + ${VORBIS_DIR}/lib/window.c +) +target_include_directories(vorbis PUBLIC ${VORBIS_DIR}/include PRIVATE ${VORBIS_DIR}/lib) +target_link_libraries(vorbis PUBLIC ogg) +if(NOT WIN32) + target_link_libraries(vorbis PRIVATE m) +endif() + +add_library(vorbisfile STATIC EXCLUDE_FROM_ALL ${VORBIS_DIR}/lib/vorbisfile.c) +target_include_directories(vorbisfile PRIVATE ${VORBIS_DIR}/lib) +target_link_libraries(vorbisfile PUBLIC vorbis) +set_target_properties(ogg vorbis vorbisfile PROPERTIES POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden) +if(MINGW) + foreach(library IN ITEMS ogg vorbis vorbisfile) + target_compile_options(${library} PRIVATE -fexceptions) + endforeach() +endif() + +add_library(libSceVorbisDec SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceVorbisDec PRIVATE ${LIBS_INCLUDE_DIR}) +target_compile_definitions(libSceVorbisDec PRIVATE OV_EXCLUDE_STATIC_CALLBACKS) +target_link_libraries(libSceVorbisDec PRIVATE vorbisfile vorbis ogg) +if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(libSceVorbisDec PRIVATE LINKER:--exclude-libs,ALL) +endif() +configure_windows_unwind(libSceVorbisDec) + +if(BUILD_TESTING) + add_library(vorbisenc STATIC EXCLUDE_FROM_ALL ${VORBIS_DIR}/lib/vorbisenc.c) + target_include_directories(vorbisenc PRIVATE ${VORBIS_DIR}/lib) + target_link_libraries(vorbisenc PUBLIC vorbis) + set_target_properties(vorbisenc PROPERTIES POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden) +endif() diff --git a/core/libs/prx/libSceVorbisDec/Export.cpp b/core/libs/prx/libSceVorbisDec/Export.cpp index 44ae4fcc5b..44b39a6c58 100644 --- a/core/libs/prx/libSceVorbisDec/Export.cpp +++ b/core/libs/prx/libSceVorbisDec/Export.cpp @@ -1,33 +1,203 @@ +#include "prx/libSceVorbisDec/include/Api.hpp" +#include "prx/libSceVorbisDec/include/GuestVorbisFile.hpp" +#include "prx/libc/include/General.hpp" + +#include + #include #include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace VorbisDec; + +static_assert(sizeof(vorbis_comment) == sizeof(GuestComment)); +static_assert(offsetof(vorbis_comment, user_comments) == offsetof(GuestComment, user_comments)); +static_assert(offsetof(vorbis_comment, comment_lengths) == offsetof(GuestComment, comment_lengths)); +static_assert(offsetof(vorbis_comment, comments) == offsetof(GuestComment, comments)); +static_assert(offsetof(vorbis_comment, vendor) == offsetof(GuestComment, vendor)); +static_assert(SEEK_SET == 0 && SEEK_CUR == 1 && SEEK_END == 2); + +namespace { + +struct Handle { + OggVorbis_File file{}; + void* datasource = nullptr; + GuestCallbacks callbacks{}; + std::vector infos; + bool tellOutOfRange = false; + + ~Handle() { + file.datasource = nullptr; + ov_clear(&file); + } +}; + +struct Table { + std::mutex mutex; + std::unordered_map> handles; +}; + +Table& Handles() { + static auto* table = new Table; + return *table; +} + +Handle* Find(const void* vf) { + auto& table = Handles(); + const std::lock_guard lock(table.mutex); + const auto found = table.handles.find(vf); + return found == table.handles.end() ? nullptr : found->second.get(); +} + +std::unique_ptr Take(const void* vf) { + auto& table = Handles(); + const std::lock_guard lock(table.mutex); + const auto found = table.handles.find(vf); + if (found == table.handles.end()) return nullptr; + auto handle = std::move(found->second); + table.handles.erase(found); + return handle; +} + +void Store(const void* vf, std::unique_ptr handle) { + auto& table = Handles(); + const std::lock_guard lock(table.mutex); + table.handles[vf] = std::move(handle); +} + +void SyncInfos(Handle& handle) { + for (std::size_t link = 0; link < handle.infos.size(); ++link) { + const vorbis_info& info = handle.file.vi[link]; + handle.infos[link] = {info.version, info.channels, info.rate, info.bitrate_upper, info.bitrate_nominal, info.bitrate_lower, info.bitrate_window, info.codec_setup}; + } +} + +void RequireFile(const void* vf, const char* function) { + if (!vf) throw std::invalid_argument(std::string(function) + ": a null OggVorbis_File is not implemented"); +} + +template +auto WithHandle(const void* vf, const char* function, TCall call) { + RequireFile(vf, function); + if (Handle* handle = Find(vf)) { + const auto result = call(*handle); + SyncInfos(*handle); + return result; + } + Handle closed; + return call(closed); +} + +std::size_t ReadSource(void* buffer, std::size_t size, std::size_t count, void* source) { + const auto* handle = static_cast(source); + return handle->callbacks.read_func(buffer, size, count, handle->datasource); +} + +int SeekSource(void* source, ogg_int64_t offset, int whence) { + const auto* handle = static_cast(source); + return handle->callbacks.seek_func(handle->datasource, offset, whence); +} + +int CloseSource(void* source) { + const auto* handle = static_cast(source); + return handle->callbacks.close_func(handle->datasource); +} + +long TellSource(void* source) { + auto* handle = static_cast(source); + const std::int64_t position = handle->callbacks.tell_func(handle->datasource); + if (position < std::numeric_limits::min() || position > std::numeric_limits::max()) { + handle->tellOutOfRange = true; + return -1; + } + return static_cast(position); +} + +struct FilterContext { + GuestFilter filter; + void* param; +}; + +void RunFilter(float** pcm, long channels, long samples, void* context) { + const auto* filter = static_cast(context); + filter->filter(pcm, channels, samples, filter->param); +} + +} extern "C" { -int APS5_VABI sceVorbisDecBitrate(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVorbisDecOpenCallbacks(void* datasource, void* vf, const char* initial, std::int64_t ibytes, GuestCallbacks callbacks) { + RequireFile(vf, __func__); + if (initial && (ibytes < std::numeric_limits::min() || ibytes > std::numeric_limits::max())) { + throw std::runtime_error(std::string(__func__) + ": an initial byte count outside the host long range is not implemented"); + } + Take(vf).reset(); + auto handle = std::make_unique(); + handle->datasource = datasource; + handle->callbacks = callbacks; + const ov_callbacks host{ + callbacks.read_func ? ReadSource : nullptr, + callbacks.seek_func ? SeekSource : nullptr, + callbacks.close_func ? CloseSource : nullptr, + callbacks.tell_func ? TellSource : nullptr, + }; + const int result = ov_open_callbacks(datasource ? handle.get() : nullptr, &handle->file, initial, initial ? static_cast(ibytes) : 0, host); + if (handle->tellOutOfRange) { + throw std::runtime_error(std::string(__func__) + ": a stream length outside the host long range is not implemented"); + } + if (result != 0) return result; + handle->infos.resize(static_cast(handle->file.links)); + SyncInfos(*handle); + Store(vf, std::move(handle)); return 0; } -int APS5_VABI sceVorbisDecBitrateInstant(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecClear(void* vf) { + const std::unique_ptr handle = Take(vf); + if (!handle) { + Handle closed; + return ov_clear(&closed.file); + } + void* const source = handle->file.datasource; + handle->file.datasource = nullptr; + const int result = ov_clear(&handle->file); + if (source && handle->callbacks.close_func) handle->callbacks.close_func(handle->datasource); + return result; } -int APS5_VABI sceVorbisDecClear(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecBitrate(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_bitrate(&handle.file, link); }); } -int APS5_VABI sceVorbisDecComment(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecBitrateInstant(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_bitrate_instant(&handle.file); }); } -int APS5_VABI sceVorbisDecCrossLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +GuestComment* APS5_VABI sceVorbisDecComment(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) { return reinterpret_cast(ov_comment(&handle.file, link)); }); +} + +int APS5_VABI sceVorbisDecCrossLap(void* first, void* second) { + if (first != second) RequireFile(first, __func__); + Handle* from = Find(first); + if (first != second && from) RequireFile(second, __func__); + Handle* to = second == first ? from : Find(second); + Handle closedFrom; + Handle closedTo; + OggVorbis_File* fromFile = from ? &from->file : &closedFrom.file; + OggVorbis_File* toFile = second == first ? fromFile : to ? &to->file : &closedTo.file; + const int result = ov_crosslap(fromFile, toFile); + if (from) SyncInfos(*from); + if (to) SyncInfos(*to); + return result; } int APS5_VABI sceVorbisDecGetDecodePriority(void) { @@ -40,99 +210,84 @@ int APS5_VABI sceVorbisDecGetLastDecodeError(void) { return 0; } -int APS5_VABI sceVorbisDecHalfrate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecHalfrate(void* vf, int flag) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_halfrate(&handle.file, flag); }); } -int APS5_VABI sceVorbisDecHalfrateP(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecHalfrateP(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) { return ov_halfrate_p(&handle.file); }); } -int APS5_VABI sceVorbisDecInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +GuestInfo* APS5_VABI sceVorbisDecInfo(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> GuestInfo* { + const vorbis_info* info = ov_info(&handle.file, link); + return info ? &handle.infos.at(static_cast(info - handle.file.vi)) : nullptr; + }); } -int APS5_VABI sceVorbisDecOpenCallbacks(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecPcmSeek(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_pcm_seek(&handle.file, position); }); } -int APS5_VABI sceVorbisDecPcmSeek(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecPcmSeekLap(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_pcm_seek_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecPcmSeekLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecPcmSeekPage(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_pcm_seek_page(&handle.file, position); }); } -int APS5_VABI sceVorbisDecPcmSeekPage(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecPcmSeekPageLap(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_pcm_seek_page_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecPcmSeekPageLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecPcmTell(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_pcm_tell(&handle.file); }); } -int APS5_VABI sceVorbisDecPcmTell(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecPcmTotal(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_pcm_total(&handle.file, link); }); } -int APS5_VABI sceVorbisDecPcmTotal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecRawSeek(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_raw_seek(&handle.file, position); }); } -int APS5_VABI sceVorbisDecRawSeek(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecRawSeekLap(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_raw_seek_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecRawSeekLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecRawTell(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_raw_tell(&handle.file); }); } -int APS5_VABI sceVorbisDecRawTell(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecRawTotal(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_raw_total(&handle.file, link); }); } -int APS5_VABI sceVorbisDecRawTotal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecRead(void* vf, char* buffer, int length, int bigEndian, int word, int isSigned, int* bitstream) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { + return ov_read(&handle.file, buffer, length, bigEndian, word, isSigned, bitstream); + }); } -int APS5_VABI sceVorbisDecRead(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecReadFilter(void* vf, char* buffer, int length, int bigEndian, int word, int isSigned, int* bitstream, GuestFilter filter, void* filterParam) { + FilterContext context{filter, filterParam}; + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { + return ov_read_filter(&handle.file, buffer, length, bigEndian, word, isSigned, bitstream, filter ? RunFilter : nullptr, &context); + }); } -int APS5_VABI sceVorbisDecReadFilter(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecReadFloat(void* vf, float*** pcmChannels, int samples, int* bitstream) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_read_float(&handle.file, pcmChannels, samples, bitstream); }); } -int APS5_VABI sceVorbisDecReadFloat(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecSeekable(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_seekable(&handle.file); }); } -int APS5_VABI sceVorbisDecSeekable(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVorbisDecSerialNumber(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecSerialNumber(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_serialnumber(&handle.file, link); }); } int APS5_VABI sceVorbisDecSetDecodePriority(void) { @@ -140,39 +295,32 @@ int APS5_VABI sceVorbisDecSetDecodePriority(void) { return 0; } -int APS5_VABI sceVorbisDecStreams(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecStreams(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_streams(&handle.file); }); } -int APS5_VABI sceVorbisDecTimeSeek(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecTimeSeek(void* vf, double position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_seek(&handle.file, position); }); } -int APS5_VABI sceVorbisDecTimeSeekLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecTimeSeekLap(void* vf, double position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_seek_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecTimeSeekPage(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecTimeSeekPage(void* vf, double position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_seek_page(&handle.file, position); }); } -int APS5_VABI sceVorbisDecTimeSeekPageLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecTimeSeekPageLap(void* vf, double position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_seek_page_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecTimeTell(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +double APS5_VABI sceVorbisDecTimeTell(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) { return ov_time_tell(&handle.file); }); } -int APS5_VABI sceVorbisDecTimeTotal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +double APS5_VABI sceVorbisDecTimeTotal(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_total(&handle.file, link); }); } } diff --git a/core/libs/prx/libSceVorbisDec/include/Api.hpp b/core/libs/prx/libSceVorbisDec/include/Api.hpp new file mode 100644 index 0000000000..7f0e484c62 --- /dev/null +++ b/core/libs/prx/libSceVorbisDec/include/Api.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "prx/libSceVorbisDec/include/GuestVorbisFile.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { + +int APS5_VABI sceVorbisDecOpenCallbacks(void* datasource, void* vf, const char* initial, std::int64_t ibytes, VorbisDec::GuestCallbacks callbacks); +int APS5_VABI sceVorbisDecClear(void* vf); +std::int64_t APS5_VABI sceVorbisDecBitrate(void* vf, int link); +std::int64_t APS5_VABI sceVorbisDecBitrateInstant(void* vf); +VorbisDec::GuestComment* APS5_VABI sceVorbisDecComment(void* vf, int link); +int APS5_VABI sceVorbisDecCrossLap(void* first, void* second); +int APS5_VABI sceVorbisDecGetDecodePriority(void); +int APS5_VABI sceVorbisDecGetLastDecodeError(void); +int APS5_VABI sceVorbisDecHalfrate(void* vf, int flag); +int APS5_VABI sceVorbisDecHalfrateP(void* vf); +VorbisDec::GuestInfo* APS5_VABI sceVorbisDecInfo(void* vf, int link); +int APS5_VABI sceVorbisDecPcmSeek(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecPcmSeekLap(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecPcmSeekPage(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecPcmSeekPageLap(void* vf, std::int64_t position); +std::int64_t APS5_VABI sceVorbisDecPcmTell(void* vf); +std::int64_t APS5_VABI sceVorbisDecPcmTotal(void* vf, int link); +int APS5_VABI sceVorbisDecRawSeek(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecRawSeekLap(void* vf, std::int64_t position); +std::int64_t APS5_VABI sceVorbisDecRawTell(void* vf); +std::int64_t APS5_VABI sceVorbisDecRawTotal(void* vf, int link); +std::int64_t APS5_VABI sceVorbisDecRead(void* vf, char* buffer, int length, int bigEndian, int word, int isSigned, int* bitstream); +std::int64_t APS5_VABI sceVorbisDecReadFilter(void* vf, char* buffer, int length, int bigEndian, int word, int isSigned, int* bitstream, VorbisDec::GuestFilter filter, void* filterParam); +std::int64_t APS5_VABI sceVorbisDecReadFloat(void* vf, float*** pcmChannels, int samples, int* bitstream); +std::int64_t APS5_VABI sceVorbisDecSeekable(void* vf); +std::int64_t APS5_VABI sceVorbisDecSerialNumber(void* vf, int link); +int APS5_VABI sceVorbisDecSetDecodePriority(void); +std::int64_t APS5_VABI sceVorbisDecStreams(void* vf); +int APS5_VABI sceVorbisDecTimeSeek(void* vf, double position); +int APS5_VABI sceVorbisDecTimeSeekLap(void* vf, double position); +int APS5_VABI sceVorbisDecTimeSeekPage(void* vf, double position); +int APS5_VABI sceVorbisDecTimeSeekPageLap(void* vf, double position); +double APS5_VABI sceVorbisDecTimeTell(void* vf); +double APS5_VABI sceVorbisDecTimeTotal(void* vf, int link); + +} diff --git a/core/libs/prx/libSceVorbisDec/include/GuestVorbisFile.hpp b/core/libs/prx/libSceVorbisDec/include/GuestVorbisFile.hpp new file mode 100644 index 0000000000..9da5f3e1d5 --- /dev/null +++ b/core/libs/prx/libSceVorbisDec/include/GuestVorbisFile.hpp @@ -0,0 +1,46 @@ +#ifndef CORE_LIBS_PRX_LIBSCEVORBISDEC_INCLUDE_GUESTVORBISFILE_HPP +#define CORE_LIBS_PRX_LIBSCEVORBISDEC_INCLUDE_GUESTVORBISFILE_HPP + +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include + +namespace VorbisDec { + +struct GuestCallbacks { + std::size_t (APS5_VABI *read_func)(void*, std::size_t, std::size_t, void*); + int (APS5_VABI *seek_func)(void*, std::int64_t, int); + int (APS5_VABI *close_func)(void*); + std::int64_t (APS5_VABI *tell_func)(void*); +}; + +struct GuestInfo { + std::int32_t version; + std::int32_t channels; + std::int64_t rate; + std::int64_t bitrate_upper; + std::int64_t bitrate_nominal; + std::int64_t bitrate_lower; + std::int64_t bitrate_window; + void* codec_setup; +}; + +struct GuestComment { + char** user_comments; + std::int32_t* comment_lengths; + std::int32_t comments; + char* vendor; +}; + +using GuestFilter = void (APS5_VABI *)(float**, std::int64_t, std::int64_t, void*); + +static_assert(sizeof(GuestCallbacks) == 32); +static_assert(offsetof(GuestInfo, rate) == 8 && offsetof(GuestInfo, bitrate_window) == 40 && offsetof(GuestInfo, codec_setup) == 48); +static_assert(sizeof(GuestInfo) == 56); +static_assert(offsetof(GuestComment, comment_lengths) == 8 && offsetof(GuestComment, comments) == 16 && offsetof(GuestComment, vendor) == 24); +static_assert(sizeof(GuestComment) == 32); + +} + +#endif diff --git a/core/libs/prx/libSceVorbisDec/libSceVorbisDec.hpp b/core/libs/prx/libSceVorbisDec/libSceVorbisDec.hpp new file mode 100644 index 0000000000..4e737bab8b --- /dev/null +++ b/core/libs/prx/libSceVorbisDec/libSceVorbisDec.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVorbisDec/include/Api.hpp" diff --git a/core/libs/prx/libSceVrServiceDialog/CMakeLists.txt b/core/libs/prx/libSceVrServiceDialog/CMakeLists.txt new file mode 100644 index 0000000000..56c0611c39 --- /dev/null +++ b/core/libs/prx/libSceVrServiceDialog/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libSceVrServiceDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceVrServiceDialog PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceVrServiceDialog) + +if(BUILD_TESTING) + add_test_executable(guest_vr_service_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestVrServiceDialog.cpp) + target_include_directories(guest_vr_service_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_vr_service_dialog_tests PRIVATE libSceVrServiceDialog) + add_test(NAME guest_vr_service_dialog COMMAND guest_vr_service_dialog_tests) + if(WIN32) + set_tests_properties(guest_vr_service_dialog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceVrServiceDialog/Export.cpp b/core/libs/prx/libSceVrServiceDialog/Export.cpp index 355ae01310..961e74e397 100644 --- a/core/libs/prx/libSceVrServiceDialog/Export.cpp +++ b/core/libs/prx/libSceVrServiceDialog/Export.cpp @@ -1,42 +1,71 @@ +#include "prx/libSceVrServiceDialog/include/Api.hpp" +#include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int COMMON_DIALOG_STATUS_NONE = 0; +constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; +constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; +constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; + +std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; + +} + extern "C" { -int APS5_VABI sceVrServiceDialogClose() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVrServiceDialogInitialize(void) { + int expected = COMMON_DIALOG_STATUS_NONE; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; return 0; } -int APS5_VABI sceVrServiceDialogGetResult() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVrServiceDialogOpen(const void* param) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } -int APS5_VABI sceVrServiceDialogGetStatus() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVrServiceDialogUpdateStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceVrServiceDialogGetStatus(void) { + return g_status.load(); +} + +int APS5_VABI sceVrServiceDialogGetResult(void* result) { + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; + std::memcpy(result, &canceled, sizeof(canceled)); return 0; } -int APS5_VABI sceVrServiceDialogInitialize() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVrServiceDialogClose(void) { + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } -int APS5_VABI sceVrServiceDialogOpen() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVrServiceDialogTerminate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVrServiceDialogUpdateStatus() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVrServiceDialogTerminate(void) { + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; return 0; } diff --git a/core/libs/prx/libSceVrServiceDialog/include/Api.hpp b/core/libs/prx/libSceVrServiceDialog/include/Api.hpp new file mode 100644 index 0000000000..a68e4b7059 --- /dev/null +++ b/core/libs/prx/libSceVrServiceDialog/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVrServiceDialogInitialize(void); +int APS5_VABI sceVrServiceDialogOpen(const void* param); +int APS5_VABI sceVrServiceDialogUpdateStatus(void); +int APS5_VABI sceVrServiceDialogGetStatus(void); +int APS5_VABI sceVrServiceDialogGetResult(void* result); +int APS5_VABI sceVrServiceDialogClose(void); +int APS5_VABI sceVrServiceDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceVrServiceDialog/libSceVrServiceDialog.hpp b/core/libs/prx/libSceVrServiceDialog/libSceVrServiceDialog.hpp new file mode 100644 index 0000000000..b0f8e5c411 --- /dev/null +++ b/core/libs/prx/libSceVrServiceDialog/libSceVrServiceDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVrServiceDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceVrSetupDialog/Export.cpp b/core/libs/prx/libSceVrSetupDialog/Export.cpp index f4b2718a30..f9196d0109 100644 --- a/core/libs/prx/libSceVrSetupDialog/Export.cpp +++ b/core/libs/prx/libSceVrSetupDialog/Export.cpp @@ -1,8 +1,10 @@ +#include "prx/libSceVrSetupDialog/include/Api.hpp" #include #include #include #include #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" namespace { @@ -11,11 +13,10 @@ constexpr int COMMON_DIALOG_STATUS_NONE = 0; constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; -constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); -constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); -constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); -constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); +constexpr int VR_SETUP_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x8A7C0001u); +constexpr int VR_SETUP_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x8A7C0002u); +constexpr int VR_SETUP_DIALOG_ERROR_INVALID_ARGUMENT = static_cast(0x8A7C0003u); +constexpr int VR_SETUP_DIALOG_ERROR_BUSY = static_cast(0x8A7C0005u); constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; std::atomic g_status{COMMON_DIALOG_STATUS_NONE}; @@ -26,15 +27,15 @@ extern "C" { int APS5_VABI sceVrSetupDialogInitialize(void) { int expected = COMMON_DIALOG_STATUS_NONE; - if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_INITIALIZED)) return VR_SETUP_DIALOG_ERROR_ALREADY_INITIALIZED; return 0; } -int APS5_VABI sceVrSetupDialogOpen(const void* param) { +int APS5_VABI sceVrSetupDialogOpen(const VrSetupDialogParam* param) { + if (param == nullptr || param->size != sizeof(*param) || param->reserved8 != 0 || param->reserved9 != 0) return VR_SETUP_DIALOG_ERROR_INVALID_ARGUMENT; const int status = g_status.load(); - if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; - if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (status == COMMON_DIALOG_STATUS_NONE) return VR_SETUP_DIALOG_ERROR_NOT_INITIALIZED; + if (status == COMMON_DIALOG_STATUS_RUNNING) return VR_SETUP_DIALOG_ERROR_BUSY; g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } @@ -47,24 +48,23 @@ int APS5_VABI sceVrSetupDialogGetStatus(void) { return g_status.load(); } -int APS5_VABI sceVrSetupDialogGetResult(void* result) { - const int status = g_status.load(); - if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; - if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; - std::memcpy(result, &canceled, sizeof(canceled)); - return 0; +int APS5_VABI sceVrSetupDialogGetResult(std::int32_t* result) { + if (result == nullptr) return VR_SETUP_DIALOG_ERROR_INVALID_ARGUMENT; + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return VR_SETUP_DIALOG_ERROR_NOT_INITIALIZED; + *result = COMMON_DIALOG_RESULT_USER_CANCELED; + if (status != COMMON_DIALOG_STATUS_FINISHED) return VR_SETUP_DIALOG_ERROR_BUSY; + return 0; } int APS5_VABI sceVrSetupDialogClose(void) { - if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - g_status = COMMON_DIALOG_STATUS_FINISHED; + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return VR_SETUP_DIALOG_ERROR_NOT_INITIALIZED; + if (g_status.load() == COMMON_DIALOG_STATUS_RUNNING) g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } int APS5_VABI sceVrSetupDialogTerminate(void) { - if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return VR_SETUP_DIALOG_ERROR_NOT_INITIALIZED; return 0; } diff --git a/core/libs/prx/libSceVrSetupDialog/include/Api.hpp b/core/libs/prx/libSceVrSetupDialog/include/Api.hpp new file mode 100644 index 0000000000..0ee132a7e1 --- /dev/null +++ b/core/libs/prx/libSceVrSetupDialog/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVrSetupDialogInitialize(void); +int APS5_VABI sceVrSetupDialogOpen(const VrSetupDialogParam* param); +int APS5_VABI sceVrSetupDialogUpdateStatus(void); +int APS5_VABI sceVrSetupDialogGetStatus(void); +int APS5_VABI sceVrSetupDialogGetResult(std::int32_t* result); +int APS5_VABI sceVrSetupDialogClose(void); +int APS5_VABI sceVrSetupDialogTerminate(void); + +} diff --git a/core/libs/prx/libSceVrSetupDialog/libSceVrSetupDialog.hpp b/core/libs/prx/libSceVrSetupDialog/libSceVrSetupDialog.hpp new file mode 100644 index 0000000000..85cdf54080 --- /dev/null +++ b/core/libs/prx/libSceVrSetupDialog/libSceVrSetupDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVrSetupDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceVrTracker/Export.cpp b/core/libs/prx/libSceVrTracker/Export.cpp index b68062f19b..7e66fd197e 100644 --- a/core/libs/prx/libSceVrTracker/Export.cpp +++ b/core/libs/prx/libSceVrTracker/Export.cpp @@ -1,33 +1,109 @@ +#include "prx/libSceVrTracker/include/Api.hpp" +#include +#include #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +static_assert(sizeof(VrTrackerMemoryBlock) == 16); +static_assert(sizeof(VrTrackerInitConfig) == 32); +static_assert(sizeof(VrTrackerInitParam) == 144); +static_assert(offsetof(VrTrackerInitParam, config) == 32); +static_assert(offsetof(VrTrackerInitParam, memory) == 64); +static_assert(offsetof(VrTrackerInitParam, option112) == 112); + +static_assert(sizeof(VrTrackerCpuProcessParam) == 32); +static_assert(offsetof(VrTrackerCpuProcessParam, mode) == 4); +static_assert(offsetof(VrTrackerCpuProcessParam, argument) == 8); +static_assert(sizeof(VrTrackerGpuWaitParam) == 32); +static_assert(sizeof(VrTrackerGetResultParam) == 56); +static_assert(offsetof(VrTrackerGetResultParam, time) == 16); +static_assert(offsetof(VrTrackerGetResultParam, statusMode) == 24); +static_assert(offsetof(VrTrackerGetResultParam, outputValue) == 36); +static_assert(offsetof(VrTrackerGetResultParam, reserved1) == 44); + +namespace { + +using namespace VrTrackerLegacy; + +constexpr int NotInit = static_cast(0x81260801u); +constexpr int AlreadyInitialized = static_cast(0x81260802u); +constexpr int DeviceNotRegistered = static_cast(0x81260803u); +constexpr int DeviceAlreadyRegistered = static_cast(0x81260804u); +constexpr int ArgumentInvalid = static_cast(0x81260806u); +constexpr int NotExecuteGpuSubmit = static_cast(0x81260815u); + +constexpr std::uint32_t MemoryAlignment = 0x10000; +constexpr std::uint32_t BaseOnionSize = 0x400000; +constexpr std::uint32_t GarlicSize = 0x3000000; +constexpr std::uint32_t WorkSize = 0x1000000; +constexpr std::uint32_t CalibrationManual = 0; +constexpr std::uint32_t CalibrationAuto = 1; + +static_assert(sizeof(QueryMemoryParam) == 0x40); + +bool ValidCalibration(const CalibrationSettings& settings) { + return settings.pad_position <= CalibrationAuto && settings.hmd_position <= CalibrationManual && + settings.move_position <= CalibrationAuto && settings.gun_position <= CalibrationAuto; +} + +std::uint32_t OnionSize(const CalibrationSettings& settings) { + return settings.move_position == CalibrationAuto ? BaseOnionSize * 2 : BaseOnionSize; +} + +constexpr std::int32_t ComputePipes = 7; +constexpr std::int32_t QueuesPerPipe = 8; + +static_assert(sizeof(InitParam) == 0x80); + +std::atomic g_initialized{false}; + +constexpr std::uint32_t DeviceHmd = 0; +constexpr std::uint32_t DeviceGun = 3; + +std::mutex g_devicesMutex; +std::int32_t g_deviceHandles[DeviceGun + 1] = {-1, -1, -1, -1}; + +} // namespace + extern "C" { -int APS5_VABI sceVrTrackerCpuProcess() { +int APS5_VABI sceVrTrackerCpuProcess(const VrTrackerCpuProcessParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerGetResult() { +int APS5_VABI sceVrTrackerGetResult(const VrTrackerGetResultParam* param, VrTrackerResult* result) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerGetTime() { +int APS5_VABI sceVrTrackerGetTime(std::uint64_t* time) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerGpuSubmit() { +int APS5_VABI sceVrTrackerGpuSubmit(const VrTrackerGpuSubmitParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerGpuWait() { +int APS5_VABI sceVrTrackerGpuWait(const VrTrackerGpuWaitParam* param) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerInit() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVrTrackerInit(const VrTrackerInitParam* param) { + if (g_initialized.load()) return AlreadyInitialized; + if (param && param->size == sizeof(VrTrackerInitParam)) NotImplemented_nid_no_patch(__func__); + const auto* init = reinterpret_cast(param); + if (!init || init->size != sizeof(InitParam) || !init->direct_memory_garlic || + init->direct_memory_garlic_alignment != MemoryAlignment || init->direct_memory_garlic_size != GarlicSize || + !init->direct_memory_onion || init->direct_memory_onion_alignment != MemoryAlignment || + init->direct_memory_onion_size != OnionSize(init->calibration_settings) || !init->work_memory || + init->work_memory_alignment != MemoryAlignment || init->work_memory_size != WorkSize || + init->gpu_pipe_id >= ComputePipes || init->gpu_queue_id >= QueuesPerPipe || + !ValidCalibration(init->calibration_settings)) { + return ArgumentInvalid; + } + bool expected = false; + return g_initialized.compare_exchange_strong(expected, true) ? 0 : AlreadyInitialized; } int APS5_VABI sceVrTrackerCpuPopMarker() { NotImplemented_nid_no_patch(__func__); @@ -46,31 +122,48 @@ int APS5_VABI sceVrTrackerGetPlayAreaWarningInfo(void* info) { return 0; } int APS5_VABI sceVrTrackerGpuWaitAndCpuProcess() { - NotImplemented_nid_no_patch(__func__); - return 0; + return g_initialized.load() ? NotExecuteGpuSubmit : NotInit; } int APS5_VABI sceVrTrackerNotifyEndOfCpuProcess(const void* param) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceVrTrackerQueryMemory(const void* param, void* result) { - NotImplemented_nid_no_patch(__func__); + const auto* query = static_cast(param); + auto* memory = static_cast(result); + if (!query || !memory || query->size != sizeof(QueryMemoryParam) || (query->profile != 0 && query->profile != 100) || + !ValidCalibration(query->calibration_settings)) { + return ArgumentInvalid; + } + memory->direct_memory_onion_size = OnionSize(query->calibration_settings); + memory->direct_memory_onion_alignment = MemoryAlignment; + memory->direct_memory_garlic_size = GarlicSize; + memory->direct_memory_garlic_alignment = MemoryAlignment; + memory->work_memory_size = WorkSize; + memory->work_memory_alignment = MemoryAlignment; return 0; } int APS5_VABI sceVrTrackerRecalibrate(const void* param) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (!g_initialized.load()) return NotInit; + const auto* recalibrate = static_cast(param); + if (!recalibrate || recalibrate->size != sizeof(RecalibrateParam) || recalibrate->device_type > DeviceGun) return ArgumentInvalid; + if (recalibrate->device_type == DeviceHmd) return DeviceNotRegistered; + std::lock_guard lock(g_devicesMutex); + return g_deviceHandles[recalibrate->device_type] == -1 ? DeviceNotRegistered : 0; } int APS5_VABI sceVrTrackerRegisterDevice(int device_type, int handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceVrTrackerRegisterDeviceInternal(device_type, handle, -1, 0); } int APS5_VABI sceVrTrackerRegisterDevice2(int device_type, int handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceVrTrackerRegisterDeviceInternal(device_type, handle, -1, 1); } -int APS5_VABI sceVrTrackerRegisterDeviceInternal(int device_type, int handle, int unk0, int unk1) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVrTrackerRegisterDeviceInternal(int device_type, int handle, int unk0, int) { + if (!g_initialized.load()) return NotInit; + const auto type = static_cast(device_type); + if (type > DeviceGun || unk0 > 4) return ArgumentInvalid; + std::lock_guard lock(g_devicesMutex); + if (g_deviceHandles[type] != -1) return DeviceAlreadyRegistered; + g_deviceHandles[type] = handle; return 0; } int APS5_VABI sceVrTrackerResetAll() { @@ -89,9 +182,9 @@ int APS5_VABI sceVrTrackerSetDeviceRejection() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerSetDurationUntilStatusNotTracking(int device_type, std::uint32_t duration_camera_frames) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVrTrackerSetDurationUntilStatusNotTracking(int device_type, std::uint32_t) { + if (!g_initialized.load()) return NotInit; + return static_cast(device_type) > DeviceGun ? ArgumentInvalid : 0; } int APS5_VABI sceVrTrackerSetExtendedMode() { NotImplemented_nid_no_patch(__func__); @@ -114,12 +207,20 @@ int APS5_VABI sceVrTrackerStopLiveCapture() { return 0; } int APS5_VABI sceVrTrackerTerm() { - NotImplemented_nid_no_patch(__func__); - return 0; + bool expected = true; + return g_initialized.compare_exchange_strong(expected, false) ? 0 : NotInit; } int APS5_VABI sceVrTrackerUnregisterDevice(int handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (!g_initialized.load()) return NotInit; + if (handle < 0) return ArgumentInvalid; + std::lock_guard lock(g_devicesMutex); + for (auto& registered : g_deviceHandles) { + if (registered == handle) { + registered = -1; + return 0; + } + } + return ArgumentInvalid; } int APS5_VABI sceVrTrackerUpdateMotionSensorData(const void* param) { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceVrTracker/include/Api.hpp b/core/libs/prx/libSceVrTracker/include/Api.hpp new file mode 100644 index 0000000000..0481e61d60 --- /dev/null +++ b/core/libs/prx/libSceVrTracker/include/Api.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include + +struct VrTrackerMemoryBlock { + void* address; + std::uint32_t size; + std::uint32_t alignment; +}; + +struct VrTrackerInitConfig { + std::uint32_t type; + std::uint32_t option; + std::uint32_t mode; + std::uint32_t parameter; + std::uint32_t reserved[4]; +}; + +struct VrTrackerInitParam { + std::uint32_t size; + std::int32_t mode; + std::uint32_t reserved[6]; + VrTrackerInitConfig config; + VrTrackerMemoryBlock memory[3]; + std::uint32_t option112; + std::uint32_t option116; + std::uint32_t reservedTail[6]; +}; + +struct VrTrackerGpuSubmitParam; + +struct VrTrackerResult; + +struct VrTrackerCpuProcessParam { + std::uint32_t size; + std::uint32_t mode; + std::uint32_t argument; + std::uint32_t reserved[5]; +}; + +struct VrTrackerGpuWaitParam { + std::uint32_t size; + std::byte payload[28]; +}; + +struct VrTrackerGetResultParam { + std::uint32_t size; + std::int32_t handle; + std::uint32_t mode; + std::uint32_t extendedResult; + std::uint64_t time; + std::uint32_t statusMode; + std::uint32_t reserved0; + std::uint32_t option32; + std::byte outputValue[4]; + std::uint32_t option40; + std::uint32_t reserved1[2]; + std::byte unused[4]; +}; + +namespace VrTrackerLegacy { + +struct CalibrationSettings { + std::uint32_t hmd_position; + std::uint32_t pad_position; + std::uint32_t move_position; + std::uint32_t gun_position; + std::uint32_t reserved[4]; +}; + +struct QueryMemoryParam { + std::uint32_t size; + std::uint32_t profile; + std::uint32_t reserved[6]; + CalibrationSettings calibration_settings; +}; + +struct QueryMemoryResult { + std::uint32_t size; + std::uint32_t direct_memory_onion_size; + std::uint32_t direct_memory_onion_alignment; + std::uint32_t direct_memory_garlic_size; + std::uint32_t direct_memory_garlic_alignment; + std::uint32_t work_memory_size; + std::uint32_t work_memory_alignment; + std::uint32_t reserved[9]; +}; + +struct InitParam { + std::uint32_t size; + std::uint32_t profile; + std::uint32_t execution_mode; + std::int32_t hmd_thread_priority; + std::int32_t pad_thread_priority; + std::int32_t move_thread_priority; + std::int32_t gun_thread_priority; + std::int32_t reserved; + std::uint64_t cpu_mask; + CalibrationSettings calibration_settings; + void* direct_memory_onion; + std::uint32_t direct_memory_onion_size; + std::uint32_t direct_memory_onion_alignment; + void* direct_memory_garlic; + std::uint32_t direct_memory_garlic_size; + std::uint32_t direct_memory_garlic_alignment; + void* work_memory; + std::uint32_t work_memory_size; + std::uint32_t work_memory_alignment; + std::int32_t gpu_pipe_id; + std::int32_t gpu_queue_id; +}; + +struct RecalibrateParam { + std::uint32_t size; + std::uint32_t device_type; + std::uint32_t calibration_type; + std::uint32_t reserved[5]; +}; + +} + +extern "C" { + +int APS5_VABI sceVrTrackerCpuProcess(const VrTrackerCpuProcessParam* param); +int APS5_VABI sceVrTrackerGetResult(const VrTrackerGetResultParam* param, VrTrackerResult* result); +int APS5_VABI sceVrTrackerGetTime(std::uint64_t* time); +int APS5_VABI sceVrTrackerGpuSubmit(const VrTrackerGpuSubmitParam* param); +int APS5_VABI sceVrTrackerGpuWait(const VrTrackerGpuWaitParam* param); +int APS5_VABI sceVrTrackerInit(const VrTrackerInitParam* param); +int APS5_VABI sceVrTrackerCpuPopMarker(); +int APS5_VABI sceVrTrackerCpuPushMarker(); +int APS5_VABI sceVrTrackerGetLiveCaptureId(); +int APS5_VABI sceVrTrackerGetPlayAreaWarningInfo(void* info); +int APS5_VABI sceVrTrackerGpuWaitAndCpuProcess(); +int APS5_VABI sceVrTrackerNotifyEndOfCpuProcess(const void* param); +int APS5_VABI sceVrTrackerQueryMemory(const void* param, void* result); +int APS5_VABI sceVrTrackerRecalibrate(const void* param); +int APS5_VABI sceVrTrackerRegisterDevice(int deviceType, int handle); +int APS5_VABI sceVrTrackerRegisterDevice2(int deviceType, int handle); +int APS5_VABI sceVrTrackerRegisterDeviceInternal(int deviceType, int handle, int unk0, int); +int APS5_VABI sceVrTrackerResetAll(); +int APS5_VABI sceVrTrackerResetOrientationRelative(int deviceType, int handle); +int APS5_VABI sceVrTrackerSaveInternalBuffers(); +int APS5_VABI sceVrTrackerSetDeviceRejection(); +int APS5_VABI sceVrTrackerSetDurationUntilStatusNotTracking(int deviceType, std::uint32_t); +int APS5_VABI sceVrTrackerSetExtendedMode(); +int APS5_VABI sceVrTrackerSetLEDBrightness(); +int APS5_VABI sceVrTrackerSetRestingMode(); +int APS5_VABI sceVrTrackerStartLiveCapture(); +int APS5_VABI sceVrTrackerStopLiveCapture(); +int APS5_VABI sceVrTrackerTerm(); +int APS5_VABI sceVrTrackerUnregisterDevice(int handle); +int APS5_VABI sceVrTrackerUpdateMotionSensorData(const void* param); + +} diff --git a/core/libs/prx/libSceVrTracker/libSceVrTracker.hpp b/core/libs/prx/libSceVrTracker/libSceVrTracker.hpp new file mode 100644 index 0000000000..81621f7084 --- /dev/null +++ b/core/libs/prx/libSceVrTracker/libSceVrTracker.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVrTracker/include/Api.hpp" diff --git a/core/libs/prx/libSceVrTracker2/CMakeLists.txt b/core/libs/prx/libSceVrTracker2/CMakeLists.txt index aaf8e867d1..cccd8e8455 100644 --- a/core/libs/prx/libSceVrTracker2/CMakeLists.txt +++ b/core/libs/prx/libSceVrTracker2/CMakeLists.txt @@ -1,12 +1,12 @@ add_library(libSceVrTracker2 SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceVrTracker2 PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(libSceVrTracker2 PRIVATE libc) +target_link_libraries(libSceVrTracker2 PRIVATE libSceHmd2 libc) configure_windows_unwind(libSceVrTracker2) if(BUILD_TESTING) add_test_executable(guest_vr_tracker2_tests ${LIBS_INCLUDE_DIR}/tests/GuestVrTracker2.cpp) target_include_directories(guest_vr_tracker2_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_vr_tracker2_tests PRIVATE libSceVrTracker2 libc) + target_link_libraries(guest_vr_tracker2_tests PRIVATE libSceVrTracker2 libkernel libc) configure_windows_unwind(guest_vr_tracker2_tests) add_test(NAME guest_vr_tracker2 COMMAND guest_vr_tracker2_tests) if(WIN32) diff --git a/core/libs/prx/libSceVrTracker2/Export.cpp b/core/libs/prx/libSceVrTracker2/Export.cpp index e323b3c719..f9246da310 100644 --- a/core/libs/prx/libSceVrTracker2/Export.cpp +++ b/core/libs/prx/libSceVrTracker2/Export.cpp @@ -1,16 +1,145 @@ +#include "prx/libSceVrTracker2/include/Api.hpp" +#include "prx/libSceHmd2/libSceHmd2.hpp" +#include #include #include #include #include #include #include "prx/libc/include/General.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" + +static_assert(sizeof(VrTracker2InitializeParameter) == 72); +static_assert(offsetof(VrTracker2InitializeParameter, threadAffinityMasks) == 16); +static_assert(offsetof(VrTracker2InitializeParameter, workMemory) == 32); +static_assert(offsetof(VrTracker2InitializeParameter, coordinateSystem) == 52); +static_assert(offsetof(VrTracker2InitializeParameter, reserved) == 58); +static_assert(sizeof(VrTracker2MemoryParameter) == 32); +static_assert(sizeof(VrTracker2MemoryRequirement) == 32); + +static_assert(sizeof(VrTracker2LegacyParameter12) == 12); +static_assert(sizeof(VrTracker2LegacyParameter20) == 20); + +static_assert(sizeof(VrTracker2MeshRequirement) == 16); +static_assert(sizeof(VrTracker2Pose) == 32); +static_assert(offsetof(VrTracker2Pose, orientation) == 16); + +struct VrTracker2LegacyResultParameter { + std::uint32_t size; + std::int32_t deviceHandle; + std::uint32_t reserved; + std::uint8_t padding[4]; + std::uint64_t timestamp; + std::uint32_t userTag; + std::uint8_t padding2[4]; +}; + +static_assert(sizeof(VrTracker2ResultParameter) == 48); +static_assert(offsetof(VrTracker2ResultParameter, timestamp) == 16); +static_assert(offsetof(VrTracker2ResultParameter, userTag) == 32); +static_assert(sizeof(VrTracker2LegacyResultParameter) == 32); +static_assert(offsetof(VrTracker2LegacyResultParameter, userTag) == 24); + +static_assert(sizeof(VrTracker2BoundaryGeometry) == 3080); +static_assert(offsetof(VrTracker2BoundaryGeometry, points) == 8); +static_assert(sizeof(VrTracker2OrientedBoundingBox) == 80); +static_assert(offsetof(VrTracker2OrientedBoundingBox, transform) == 4); +static_assert(offsetof(VrTracker2OrientedBoundingBox, extents) == 68); namespace { constexpr std::int32_t SCE_OK = 0; constexpr std::int32_t VR_TRACKER2_ERROR_NOT_INITIALIZED = static_cast(0x8A740001u); +constexpr std::int32_t VR_TRACKER2_ERROR_ALREADY_INITIALIZED = static_cast(0x8A740002u); constexpr std::int32_t VR_TRACKER2_ERROR_INVALID_ARGUMENT = static_cast(0x8A740006u); +constexpr std::uint32_t TrackerMode = 0; +constexpr std::uint32_t SystemTrackingMode = 1000; +constexpr std::uint32_t SystemClientMode = 1001; +constexpr std::uint32_t MinimumWorkMemoryAlignment = 0x10000; +constexpr std::int32_t MinimumThreadPriority = 256; +constexpr std::int32_t MaximumThreadPriority = 767; +constexpr std::uint64_t TrackerAffinityLimit = 0x2000; +constexpr std::uint64_t SystemAffinityMask = 0xEA00; +constexpr std::uint32_t OptionModeSdkVersion = 0x7000000; + +std::atomic state{0}; + +std::uint32_t WorkMemorySize(std::uint32_t mode) { + if (mode == SystemTrackingMode) return 7u << 20; + if (mode == SystemClientMode) return 3u << 20; + return 32u << 20; +} + +std::uint32_t CompiledSdkVersion() { + const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t sdkVersion; + std::memcpy(&sdkVersion, process + 0x10, sizeof(sdkVersion)); + return sdkVersion; +} + +std::int32_t ValidateMode(std::uint32_t mode) { + if (mode == SystemTrackingMode || mode == SystemClientMode) NotImplemented_nid_no_patch("sceVrTracker2Initialize: application category of a system mode"); + return mode == TrackerMode ? SCE_OK : VR_TRACKER2_ERROR_INVALID_ARGUMENT; +} + +bool PriorityAllowed(std::int32_t priority) { + return priority >= MinimumThreadPriority && priority <= MaximumThreadPriority; +} + +bool AffinityAllowed(std::uint32_t mode, std::uint64_t mask) { + if (mode == TrackerMode) return mask < TrackerAffinityLimit; + return (mask & ~SystemAffinityMask) == 0; +} + +bool OptionModeAllowed(std::int8_t optionMode) { + if (optionMode == -1 || optionMode == 0) return true; + return optionMode == 1 && CompiledSdkVersion() >= OptionModeSdkVersion; +} + +class InitializeSession { +public: + std::int32_t OpenTracking() { + return track(sceHmd2InternalTrackingOpen(), trackingOpened); + } + + std::int32_t StartTracking() { + return track(sceHmd2InternalTrackingStart(), trackingStarted); + } + + std::int32_t OpenLedDetector() { + return track(sceHmd2InternalLedDetectorOpen(), ledDetectorOpened); + } + + std::int32_t StartLedDetector() { + return track(sceHmd2InternalLedDetectorStart(), ledDetectorStarted); + } + + void Rollback() { + if (ledDetectorStarted) sceHmd2InternalLedDetectorStop(); + if (ledDetectorOpened) sceHmd2InternalLedDetectorClose(); + if (trackingStarted) sceHmd2InternalTrackingStop(); + if (trackingOpened) sceHmd2InternalTrackingClose(); + state.store(0); + } + + std::int32_t Abort(std::int32_t result) { + Rollback(); + return result; + } + +private: + static std::int32_t track(std::int32_t result, bool& done) { + done = result == SCE_OK; + return result; + } + + bool trackingOpened = false; + bool trackingStarted = false; + bool ledDetectorOpened = false; + bool ledDetectorStarted = false; +}; + std::int32_t ClearThenReportNotInitialized(void* output, std::size_t size) { if (output == nullptr) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; std::memset(output, 0, size); @@ -19,94 +148,227 @@ std::int32_t ClearThenReportNotInitialized(void* output, std::size_t size) { } +static_assert(sizeof(VrTracker2RelativePoseResult) == 64); + +static_assert(sizeof(VrTracker2ScalarResult) == 8); +static_assert(sizeof(VrTracker2TrackingOptions) == 28); +static_assert(offsetof(VrTracker2TrackingOptions, vector) == 16); +static_assert(sizeof(VrTracker2TrackingOptionsResult) == 40); +static_assert(offsetof(VrTracker2TrackingOptionsResult, position) == 28); + +static_assert(sizeof(VrTracker2ModeParameter) == 12); +static_assert(sizeof(VrTracker2DiagnosticText) == 3072); + +static_assert(offsetof(VrTracker2CameraFrame, pixels) == 24); +static_assert(offsetof(VrTracker2CameraFrame, bufferIndex) == 32); +static_assert(sizeof(VrTracker2CameraFrame) == 40); + +static_assert(sizeof(VrTracker2SpatialQueryResult) == 36); +static_assert(offsetof(VrTracker2SpatialQueryResult, vectors) == 8); +static_assert(offsetof(VrTracker2SpatialQueryResult, value) == 32); + +static_assert(sizeof(VrTracker2CoordinateResultParameter) == 56); +static_assert(offsetof(VrTracker2CoordinateResultParameter, parameter) == 8); +static_assert(sizeof(VrTracker2BoundaryFitResult) == 20); +static_assert(sizeof(VrTracker2VectorResult) == 16); + extern "C" { std::int32_t APS5_VABI sceVrTracker2Finalize() { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2GetCoordinateSystem() { +std::int32_t APS5_VABI sceVrTracker2GetCoordinateSystem(std::uint32_t* coordinateSystem) { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2GetPlayAreaBoundaryGeometry() { +std::int32_t APS5_VABI sceVrTracker2GetPlayAreaBoundaryGeometry(VrTracker2BoundaryGeometry* geometry) { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2GetPlayAreaOrientedBoundingBox() { +std::int32_t APS5_VABI sceVrTracker2GetPlayAreaOrientedBoundingBox(VrTracker2OrientedBoundingBox* box) { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2GetResult() { +std::int32_t APS5_VABI sceVrTracker2GetResult(const VrTracker2ResultParameter* parameter, VrTracker2Result* result) { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2LocateCoordinateSystem() { +std::int32_t APS5_VABI sceVrTracker2LocateCoordinateSystem(std::uint32_t coordinateSystem, VrTracker2CoordinateLocation* location) { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2RegisterDevice() { +std::int32_t APS5_VABI sceVrTracker2RegisterDevice(std::uint32_t deviceType, std::int32_t deviceHandle) { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2ResetLocalCoordinate() { +std::int32_t APS5_VABI sceVrTracker2ResetLocalCoordinate(std::int32_t deviceHandle) { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2SetCoordinateSystem() { +std::int32_t APS5_VABI sceVrTracker2SetCoordinateSystem(std::uint32_t coordinateSystem) { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2UnregisterDevice() { +std::int32_t APS5_VABI sceVrTracker2UnregisterDevice(std::int32_t deviceHandle) { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } -std::int32_t APS5_VABI sceVrTracker2QueryMemory(const void* parameter, void* requirement) { +std::int32_t APS5_VABI sceVrTracker2QueryMemory(const VrTracker2MemoryParameter* parameter, VrTracker2MemoryRequirement* requirement) { if (parameter == nullptr || requirement == nullptr) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; - const auto* bytes = static_cast(parameter); - std::uint32_t size = 0; - std::memcpy(&size, bytes, sizeof size); - if (size != 0x20) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; - for (std::size_t offset = sizeof size; offset < 0x20; ++offset) - if (bytes[offset] != 0) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; - const std::uint32_t fields[3] = {0x20, 32u << 20, 64u << 10}; - std::memset(requirement, 0, 0x20); - std::memcpy(requirement, fields, sizeof fields); + if (parameter->size != sizeof(VrTracker2MemoryParameter)) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + if (parameter->mode == SystemTrackingMode || parameter->mode == SystemClientMode) { + if (CompiledSdkVersion() < 0x1000000) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + } else if (parameter->mode != TrackerMode) { + return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + } + const std::uint32_t memorySize = WorkMemorySize(parameter->mode); + for (const auto value : parameter->reserved) + if (value != 0) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + *requirement = {sizeof(VrTracker2MemoryRequirement), memorySize, 64u << 10, {}}; return SCE_OK; } -std::int32_t APS5_VABI sceVrTracker2Initialize() { +std::int32_t APS5_VABI sceVrTracker2Initialize(const VrTracker2InitializeParameter* parameter) { + if (parameter == nullptr || parameter->size != sizeof(VrTracker2InitializeParameter)) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + const std::uint32_t mode = parameter->mode; + if (const std::int32_t result = ValidateMode(mode); result != SCE_OK) return result; + if (parameter->workMemory == nullptr) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + if (parameter->workMemorySize < WorkMemorySize(mode)) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + if (parameter->workMemoryAlignment < MinimumWorkMemoryAlignment) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + for (const auto value : parameter->reserved) + if (value != 0) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + for (const auto priority : parameter->threadPriorities) + if (!PriorityAllowed(priority)) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + for (const auto mask : parameter->threadAffinityMasks) + if (!AffinityAllowed(mode, mask)) return VR_TRACKER2_ERROR_INVALID_ARGUMENT; + std::uint32_t expected = 0; + if (!state.compare_exchange_strong(expected, 1)) return VR_TRACKER2_ERROR_ALREADY_INITIALIZED; + InitializeSession session; + if (mode == TrackerMode) std::memset(parameter->workMemory, 0, WorkMemorySize(mode)); + if (mode != SystemClientMode) { + if (!OptionModeAllowed(parameter->optionMode)) return session.Abort(VR_TRACKER2_ERROR_INVALID_ARGUMENT); + if (const std::int32_t result = session.OpenTracking(); result != SCE_OK) return session.Abort(result); + if (const std::int32_t result = session.StartTracking(); result != SCE_OK) return session.Abort(result); + if (const std::int32_t result = session.OpenLedDetector(); result != SCE_OK) return session.Abort(result); + if (const std::int32_t result = session.StartLedDetector(); result != SCE_OK) return session.Abort(result); + } + session.Rollback(); NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("1m6DD9BBmSE", VrTracker2NotInitialized); -APS5_EXPORT("SgunslIO-C0", VrTracker2NotInitialized); -APS5_EXPORT("KMIToVnHrhc", VrTracker2NotInitialized); -APS5_EXPORT("XfEBVemeJyE", VrTracker2NotInitialized); -APS5_EXPORT("y79S3-zrJKg", VrTracker2NotInitialized); -APS5_EXPORT("b777Kpfe2J8", VrTracker2NotInitialized); -APS5_EXPORT("UfUYgiT7VK0", VrTracker2NotInitialized); -APS5_EXPORT("MB7B6396K6U", VrTracker2NotInitialized); +APS5_EXPORT("1m6DD9BBmSE", VrTracker2GetRelativePose); +std::int32_t APS5_VABI VrTracker2GetRelativePose(std::uint32_t firstCoordinateSystem, std::uint32_t secondCoordinateSystem, VrTracker2RelativePoseResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("SgunslIO-C0", VrTracker2GetDiagnosticResult); +std::int32_t APS5_VABI VrTracker2GetDiagnosticResult(VrTracker2DiagnosticResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("KMIToVnHrhc", VrTracker2SetTrackingOptions); +std::int32_t APS5_VABI VrTracker2SetTrackingOptions(const VrTracker2TrackingOptions* options) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("XfEBVemeJyE", VrTracker2GetTrackingOptions); +std::int32_t APS5_VABI VrTracker2GetTrackingOptions(VrTracker2TrackingOptionsResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("y79S3-zrJKg", VrTracker2GetExtendedResult); +std::int32_t APS5_VABI VrTracker2GetExtendedResult(VrTracker2ExtendedResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("b777Kpfe2J8", VrTracker2GetCompositePose); +std::int32_t APS5_VABI VrTracker2GetCompositePose(const VrTracker2ResultParameter* parameter, VrTracker2CompositePoseResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("UfUYgiT7VK0", VrTracker2GetCounter); +std::int32_t APS5_VABI VrTracker2GetCounter(std::uint64_t* counter, std::uint32_t selector) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("MB7B6396K6U", VrTracker2SetTrackingConfiguration); +std::int32_t APS5_VABI VrTracker2SetTrackingConfiguration(const VrTracker2TrackingConfiguration* configuration) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("H2Mnx7gz4ac", VrTracker2NotInitialized); -APS5_EXPORT("SgNHzN6XaEc", VrTracker2NotInitialized); -APS5_EXPORT("sYBNLrMxjAM", VrTracker2NotInitialized); -APS5_EXPORT("3gQncidodmw", VrTracker2NotInitialized); -APS5_EXPORT("qnCmEbUGqWY", VrTracker2NotInitialized); -APS5_EXPORT("oGNn7JIdMHw", VrTracker2NotInitialized); +APS5_EXPORT("SgNHzN6XaEc", VrTracker2GetTrackingConfiguration); +std::int32_t APS5_VABI VrTracker2GetTrackingConfiguration(VrTracker2TrackingConfiguration* configuration) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("sYBNLrMxjAM", VrTracker2GetCameraImages); +std::int32_t APS5_VABI VrTracker2GetCameraImages(VrTracker2CameraImages* images) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("3gQncidodmw", VrTracker2GetCameraImage); +std::int32_t APS5_VABI VrTracker2GetCameraImage(void* image, std::uint32_t size, std::uint32_t* frameId) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("qnCmEbUGqWY", VrTracker2GetDiagnosticText); +std::int32_t APS5_VABI VrTracker2GetDiagnosticText(VrTracker2DiagnosticText* text) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("oGNn7JIdMHw", VrTracker2GetSensorFrame); +std::int32_t APS5_VABI VrTracker2GetSensorFrame(VrTracker2SensorFrame* frame) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("80IxOn8swIA", VrTracker2NotInitialized); APS5_EXPORT("NTbJNAZA4ro", VrTracker2NotInitialized); -APS5_EXPORT("914+9oJxNts", VrTracker2NotInitialized); -APS5_EXPORT("c6SZtdEjH0E", VrTracker2NotInitialized); -APS5_EXPORT("74IXIMe+Lto", VrTracker2NotInitialized); -APS5_EXPORT("hmahOYMiYNY", VrTracker2NotInitialized); -APS5_EXPORT("pNXc6ZlLq-8", VrTracker2NotInitialized); -APS5_EXPORT("omJ7b8FqAN0", VrTracker2NotInitialized); -APS5_EXPORT("G2PWj1HLqn8", VrTracker2NotInitialized); +APS5_EXPORT("914+9oJxNts", VrTracker2GetTrackingStatus); +std::int32_t APS5_VABI VrTracker2GetTrackingStatus(VrTracker2TrackingStatus* status) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("c6SZtdEjH0E", VrTracker2GetBoundaryParameter); +std::int32_t APS5_VABI VrTracker2GetBoundaryParameter(VrTracker2BoundaryParameter* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("74IXIMe+Lto", VrTracker2SetBoundaryParameter); +std::int32_t APS5_VABI VrTracker2SetBoundaryParameter(const VrTracker2BoundaryParameter* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("hmahOYMiYNY", VrTracker2SetMode); +std::int32_t APS5_VABI VrTracker2SetMode(const VrTracker2ModeParameter* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("pNXc6ZlLq-8", VrTracker2GetScalarResult); +std::int32_t APS5_VABI VrTracker2GetScalarResult(VrTracker2ScalarResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("omJ7b8FqAN0", VrTracker2LegacyQuery12); +std::int32_t APS5_VABI VrTracker2LegacyQuery12(const VrTracker2LegacyParameter12* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("G2PWj1HLqn8", VrTracker2LegacyQuery20); +std::int32_t APS5_VABI VrTracker2LegacyQuery20(const VrTracker2LegacyParameter20* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("73zcxAFawYk", VrTracker2NotInitialized); APS5_EXPORT("vegrWFNiQwc", VrTracker2NotInitialized); APS5_EXPORT("6r01IawmypA", VrTracker2NotInitialized); -APS5_EXPORT("zeof7sF2fqE", VrTracker2NotInitialized); +APS5_EXPORT("zeof7sF2fqE", VrTracker2UnsupportedCoordinateOperation); +std::int32_t APS5_VABI VrTracker2UnsupportedCoordinateOperation(const VrTracker2UnsupportedCoordinateParameter* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("Z0rfHOHQV1w", VrTracker2NotInitialized); APS5_EXPORT("4l7x+uy3dWw", VrTracker2NotInitialized); APS5_EXPORT("ZXlt-Xb3da4", VrTracker2NotInitialized); @@ -117,42 +379,178 @@ APS5_EXPORT("4+B+TcHEHks", VrTracker2NotInitialized); APS5_EXPORT("cjDz4kYJKTw", VrTracker2NotInitialized); APS5_EXPORT("BjVg0fKV9VA", VrTracker2NotInitialized); APS5_EXPORT("oDV5j-2Luic", VrTracker2NotInitialized); -APS5_EXPORT("t+86o9sCZ-Y", VrTracker2NotInitialized); -APS5_EXPORT("TIpYP35JzUs", VrTracker2NotInitialized); -APS5_EXPORT("LrSc+KuxIkg", VrTracker2NotInitialized); -APS5_EXPORT("Ur+M5wy+QBY", VrTracker2NotInitialized); -APS5_EXPORT("OvKC351Udqo", VrTracker2NotInitialized); +APS5_EXPORT("t+86o9sCZ-Y", VrTracker2AcquireCameraFrame); +std::int32_t APS5_VABI VrTracker2AcquireCameraFrame(std::uint32_t selector, VrTracker2CameraFrame* frame) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} +; +APS5_EXPORT("TIpYP35JzUs", VrTracker2AcquireCameraFrameWithMetadata); +std::int32_t APS5_VABI VrTracker2AcquireCameraFrameWithMetadata(std::uint32_t selector, VrTracker2CameraFrame* frame, VrTracker2CameraFrameMetadata* metadata) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} +; +APS5_EXPORT("LrSc+KuxIkg", VrTracker2ReleaseCameraFrame); +std::int32_t APS5_VABI VrTracker2ReleaseCameraFrame(const VrTracker2CameraFrame* frame) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} +; +APS5_EXPORT("Ur+M5wy+QBY", VrTracker2SetScalarOption); +std::int32_t APS5_VABI VrTracker2SetScalarOption(float value) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} +; +APS5_EXPORT("OvKC351Udqo", VrTracker2GetScalarOption); +std::int32_t APS5_VABI VrTracker2GetScalarOption(float* value) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} +; APS5_EXPORT("LQV4nz6wteY", VrTracker2NotInitialized); APS5_EXPORT("x0pGthEwf88", VrTracker2NotInitialized); -APS5_EXPORT("d5Fjs0rHkBg", VrTracker2NotInitialized); -APS5_EXPORT("Yed3ZONcuMk", VrTracker2NotInitialized); -APS5_EXPORT("fkptW6uRdWc", VrTracker2NotInitialized); -APS5_EXPORT("HDlD-xE1Xuk", VrTracker2NotInitialized); -APS5_EXPORT("IGdE6nsMTAY", VrTracker2NotInitialized); -APS5_EXPORT("DSTergmOvvE", VrTracker2NotInitialized); -APS5_EXPORT("B7hPwvvg79E", VrTracker2NotInitialized); -APS5_EXPORT("ZwnIsDe1eNE", VrTracker2NotInitialized); -APS5_EXPORT("B2vw+s-0Dfo", VrTracker2NotInitialized); -APS5_EXPORT("4LNTLivfRuA", VrTracker2NotInitialized); -APS5_EXPORT("edajhZhz9Uo", VrTracker2NotInitialized); -APS5_EXPORT("QS+7oqiszuc", VrTracker2NotInitialized); -APS5_EXPORT("-dDyvzd2jf0", VrTracker2NotInitialized); -APS5_EXPORT("k8JDLkCrOgs", VrTracker2NotInitialized); -APS5_EXPORT("-HFx+dnDSOI", VrTracker2NotInitialized); -APS5_EXPORT("wgDeDXirl0g", VrTracker2NotInitialized); -APS5_EXPORT("a1kWSTYGBxg", VrTracker2NotInitialized); -APS5_EXPORT("zUwxBEKr3fc", VrTracker2NotInitialized); -APS5_EXPORT("TC7VTVzj0xc", VrTracker2NotInitialized); -APS5_EXPORT("1DwROxtuANs", VrTracker2NotInitialized); -APS5_EXPORT("wwbt4s8cbMQ", VrTracker2NotInitialized); -APS5_EXPORT("eGxBJ0IolOA", VrTracker2NotInitialized); -APS5_EXPORT("9BaFhspHF+E", VrTracker2NotInitialized); -APS5_EXPORT("lb9q5nJ4vzk", VrTracker2NotInitialized); -APS5_EXPORT("ZsD1-thjXS4", VrTracker2NotInitialized); -APS5_EXPORT("QdHQnM0XICI", VrTracker2NotInitialized); -APS5_EXPORT("Ek4WMOvwv6g", VrTracker2NotInitialized); -APS5_EXPORT("W1YAO6G+hNc", VrTracker2NotInitialized); -APS5_EXPORT("HAJB7irjl+0", VrTracker2NotInitialized); +APS5_EXPORT("d5Fjs0rHkBg", VrTracker2SelectCoordinateSystem); +std::int32_t APS5_VABI VrTracker2SelectCoordinateSystem(std::uint32_t coordinateSystem) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} +; +APS5_EXPORT("Yed3ZONcuMk", VrTracker2GetSelectedCoordinateSystem); +std::int32_t APS5_VABI VrTracker2GetSelectedCoordinateSystem(std::uint32_t* coordinateSystem) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} +; +APS5_EXPORT("fkptW6uRdWc", VrTracker2ResetDevicePose); +std::int32_t APS5_VABI VrTracker2ResetDevicePose(std::int32_t deviceHandle) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("HDlD-xE1Xuk", VrTracker2ResetPose); +std::int32_t APS5_VABI VrTracker2ResetPose(const VrTracker2ResetPoseParameter* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("IGdE6nsMTAY", VrTracker2QueryPosition); +std::int32_t APS5_VABI VrTracker2QueryPosition(const float* position, VrTracker2SpatialQueryResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("DSTergmOvvE", VrTracker2QueryDevicePosition); +std::int32_t APS5_VABI VrTracker2QueryDevicePosition(std::int32_t deviceHandle, VrTracker2SpatialQueryResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("B7hPwvvg79E", VrTracker2GetTrackingBounds); +std::int32_t APS5_VABI VrTracker2GetTrackingBounds(VrTracker2OrientedBoundingBox* bounds) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("ZwnIsDe1eNE", VrTracker2GetDeviceState); +std::int32_t APS5_VABI VrTracker2GetDeviceState(std::int32_t deviceHandle, std::int32_t* state) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("B2vw+s-0Dfo", VrTracker2GetDeviceCalibrationStatus); +std::int32_t APS5_VABI VrTracker2GetDeviceCalibrationStatus(std::int32_t deviceHandle, VrTracker2DeviceCalibrationStatus* status) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("4LNTLivfRuA", VrTracker2SetDeviceBoundary); +std::int32_t APS5_VABI VrTracker2SetDeviceBoundary(std::int32_t deviceHandle, std::int32_t selector, const VrTracker2DeviceBoundaryParameter* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("edajhZhz9Uo", VrTracker2GetDeviceBoundary); +std::int32_t APS5_VABI VrTracker2GetDeviceBoundary(std::int32_t deviceHandle, std::int32_t selector, VrTracker2DeviceBoundaryParameter* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("QS+7oqiszuc", VrTracker2ApplyDeviceBoundary); +std::int32_t APS5_VABI VrTracker2ApplyDeviceBoundary(std::int32_t deviceHandle, std::int32_t selector) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("-dDyvzd2jf0", VrTracker2GetDeviceMapState); +std::int32_t APS5_VABI VrTracker2GetDeviceMapState(std::int32_t deviceHandle, std::int32_t* state) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("k8JDLkCrOgs", VrTracker2InitializeMap); +std::int32_t APS5_VABI VrTracker2InitializeMap(std::int32_t deviceHandle, const float* parameter) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("-HFx+dnDSOI", VrTracker2GetCoordinateTrackingResult); +std::int32_t APS5_VABI VrTracker2GetCoordinateTrackingResult(const VrTracker2CoordinateResultParameter* parameter, VrTracker2ExtendedTrackingResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("wgDeDXirl0g", VrTracker2GetExtendedTrackingResult); +std::int32_t APS5_VABI VrTracker2GetExtendedTrackingResult(const VrTracker2ResultParameter* parameter, VrTracker2ExtendedTrackingResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("a1kWSTYGBxg", VrTracker2FitBoundary); +std::int32_t APS5_VABI VrTracker2FitBoundary(const VrTracker2BoundaryGeometry* boundary, VrTracker2BoundaryFitResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("zUwxBEKr3fc", VrTracker2FitBoundaryWithBounds); +std::int32_t APS5_VABI VrTracker2FitBoundaryWithBounds(const VrTracker2BoundaryGeometry* boundary, VrTracker2OrientedBoundingBox* firstBounds, VrTracker2OrientedBoundingBox* secondBounds, VrTracker2BoundaryFitResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("TC7VTVzj0xc", VrTracker2GetDeviceCenter); +std::int32_t APS5_VABI VrTracker2GetDeviceCenter(std::int32_t deviceHandle, VrTracker2VectorResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("1DwROxtuANs", VrTracker2GetRuntimeStatus); +std::int32_t APS5_VABI VrTracker2GetRuntimeStatus(VrTracker2RuntimeStatus* status) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("wwbt4s8cbMQ", VrTracker2GetTrackingFlags); +std::int32_t APS5_VABI VrTracker2GetTrackingFlags(bool* active, std::uint8_t* flags) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("eGxBJ0IolOA", VrTracker2GetDiagnosticState); +std::int32_t APS5_VABI VrTracker2GetDiagnosticState(VrTracker2DiagnosticState* state) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("9BaFhspHF+E", VrTracker2GetExtendedDiagnosticState); +std::int32_t APS5_VABI VrTracker2GetExtendedDiagnosticState(VrTracker2ExtendedDiagnosticState* state) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("lb9q5nJ4vzk", VrTracker2GetSensorDiagnostics); +std::int32_t APS5_VABI VrTracker2GetSensorDiagnostics(VrTracker2SensorDiagnostics* diagnostics) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("ZsD1-thjXS4", VrTracker2GetDeviceDiagnostics); +std::int32_t APS5_VABI VrTracker2GetDeviceDiagnostics(VrTracker2DeviceDiagnostics* diagnostics) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("QdHQnM0XICI", VrTracker2GetTimingDiagnostics); +std::int32_t APS5_VABI VrTracker2GetTimingDiagnostics(VrTracker2TimingDiagnostics* diagnostics) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("Ek4WMOvwv6g", VrTracker2GetMapConfiguration); +std::int32_t APS5_VABI VrTracker2GetMapConfiguration(VrTracker2MapConfiguration* configuration) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("W1YAO6G+hNc", VrTracker2SetMapConfiguration); +std::int32_t APS5_VABI VrTracker2SetMapConfiguration(const VrTracker2MapConfiguration* configuration) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("HAJB7irjl+0", VrTracker2GetMapStatus); +std::int32_t APS5_VABI VrTracker2GetMapStatus(VrTracker2MapStatus* status) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("x2mN50nKScs", VrTracker2NotInitialized); APS5_EXPORT("cCKExge5d3A", VrTracker2NotInitialized); APS5_EXPORT("gn6e5I4g9bw", VrTracker2NotInitialized); @@ -161,14 +559,46 @@ APS5_EXPORT("loU5lT92Zrw", VrTracker2NotInitialized); APS5_EXPORT("o6fsgA3R2Yc", VrTracker2NotInitialized); APS5_EXPORT("AeBtEIE0idk", VrTracker2NotInitialized); APS5_EXPORT("DBa3wov7dKQ", VrTracker2NotInitialized); -APS5_EXPORT("ojTGClQ52r4", VrTracker2NotInitialized); -APS5_EXPORT("oOLE62H66z0", VrTracker2NotInitialized); -APS5_EXPORT("MHekhnRBKAw", VrTracker2NotInitialized); -APS5_EXPORT("JnSmj+5E8WY", VrTracker2NotInitialized); -APS5_EXPORT("N1P+melAgb8", VrTracker2NotInitialized); -APS5_EXPORT("-e7A3rAM3Yk", VrTracker2NotInitialized); -APS5_EXPORT("RefT1spBleg", VrTracker2NotInitialized); -APS5_EXPORT("DsOfryhbm4U", VrTracker2NotInitialized); +APS5_EXPORT("ojTGClQ52r4", VrTracker2BuildMesh); +std::int32_t APS5_VABI VrTracker2BuildMesh(VrTracker2MeshVertex* vertices, std::uint16_t* indices, std::uint32_t format) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("oOLE62H66z0", VrTracker2GetMapScalar); +std::int32_t APS5_VABI VrTracker2GetMapScalar(float* value, const VrTracker2MapData* data) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("MHekhnRBKAw", VrTracker2GetTimedPose); +std::int32_t APS5_VABI VrTracker2GetTimedPose(VrTracker2TimedPoseResult* result, std::uint64_t timestamp, std::uint64_t userTag) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("JnSmj+5E8WY", VrTracker2GetMapData); +std::int32_t APS5_VABI VrTracker2GetMapData(VrTracker2MapData* data, std::uint32_t size) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("N1P+melAgb8", VrTracker2GetPoseScalar); +std::int32_t APS5_VABI VrTracker2GetPoseScalar(float* value) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("-e7A3rAM3Yk", VrTracker2SubmitImageFrame); +std::int32_t APS5_VABI VrTracker2SubmitImageFrame(const VrTracker2ImageFrame* frame) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("RefT1spBleg", VrTracker2GetImagePose); +std::int32_t APS5_VABI VrTracker2GetImagePose(VrTracker2ImagePoseResult* result) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + +APS5_EXPORT("DsOfryhbm4U", VrTracker2GetSensorSummary); +std::int32_t APS5_VABI VrTracker2GetSensorSummary(VrTracker2SensorSummary* summary) { + return VR_TRACKER2_ERROR_NOT_INITIALIZED; +} + std::int32_t APS5_VABI VrTracker2NotInitialized() { return VR_TRACKER2_ERROR_NOT_INITIALIZED; } @@ -217,43 +647,43 @@ std::int32_t APS5_VABI VrTracker2ClearWord(void* output) { } APS5_EXPORT("jRmuXjsknsI", VrTracker2Unknown01); -std::int32_t APS5_VABI VrTracker2Unknown01() { +std::int32_t APS5_VABI VrTracker2Unknown01(VrTracker2CalibrationResult* result, std::uint32_t capacity) { NotImplemented_nid_no_patch("jRmuXjsknsI"); return 0; } APS5_EXPORT("NBhWy+Gl0tY", VrTracker2Unknown02); -std::int32_t APS5_VABI VrTracker2Unknown02() { +std::int32_t APS5_VABI VrTracker2Unknown02(VrTracker2MeshRequirement* requirement, std::uint32_t format) { NotImplemented_nid_no_patch("NBhWy+Gl0tY"); return 0; } APS5_EXPORT("9R6W33Au4B4", VrTracker2Unknown03); -std::int32_t APS5_VABI VrTracker2Unknown03() { +std::int32_t APS5_VABI VrTracker2Unknown03(std::uint32_t mode, void* output, std::size_t capacity, std::size_t* writtenSize) { NotImplemented_nid_no_patch("9R6W33Au4B4"); return 0; } APS5_EXPORT("O8ZkTFtvUL4", VrTracker2Unknown04); -std::int32_t APS5_VABI VrTracker2Unknown04() { +std::int32_t APS5_VABI VrTracker2Unknown04(const VrTracker2ResultParameter* parameter, VrTracker2Result* result) { NotImplemented_nid_no_patch("O8ZkTFtvUL4"); return 0; } APS5_EXPORT("940WPg+WwIk", VrTracker2Unknown05); -std::int32_t APS5_VABI VrTracker2Unknown05() { +std::int32_t APS5_VABI VrTracker2Unknown05(VrTracker2Pose* pose, std::uint64_t coordinateId) { NotImplemented_nid_no_patch("940WPg+WwIk"); return 0; } APS5_EXPORT("oZDm1bynCaA", VrTracker2Unknown06); -std::int32_t APS5_VABI VrTracker2Unknown06() { +std::int32_t APS5_VABI VrTracker2Unknown06(const VrTracker2TelemetryData* data) { NotImplemented_nid_no_patch("oZDm1bynCaA"); return 0; } APS5_EXPORT("U1VYczwqukY", VrTracker2Unknown07); -std::int32_t APS5_VABI VrTracker2Unknown07() { +std::int32_t APS5_VABI VrTracker2Unknown07(const VrTracker2PoseParameter* parameter, VrTracker2PoseResult* result) { NotImplemented_nid_no_patch("U1VYczwqukY"); return 0; } diff --git a/core/libs/prx/libSceVrTracker2/include/Api.hpp b/core/libs/prx/libSceVrTracker2/include/Api.hpp new file mode 100644 index 0000000000..871f89b821 --- /dev/null +++ b/core/libs/prx/libSceVrTracker2/include/Api.hpp @@ -0,0 +1,310 @@ +#pragma once + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +struct VrTracker2InitializeParameter { + std::uint32_t size; + std::uint32_t mode; + std::int32_t threadPriorities[2]; + std::uint64_t threadAffinityMasks[2]; + void* workMemory; + std::uint32_t workMemorySize; + std::uint32_t workMemoryAlignment; + std::uint32_t option; + std::uint32_t coordinateSystem; + std::uint8_t disableOption; + std::int8_t optionMode; + std::uint8_t reserved[14]; +}; + +struct VrTracker2MemoryParameter { + std::uint32_t size; + std::uint32_t mode; + std::uint8_t reserved[24]; +}; + +struct VrTracker2MemoryRequirement { + std::uint32_t size; + std::uint32_t memorySize; + std::uint32_t memoryAlignment; + std::uint8_t reserved[20]; +}; + +struct VrTracker2LegacyParameter12 { + std::uint32_t size; + std::uint8_t opaque[8]; +}; + +struct VrTracker2LegacyParameter20 { + std::uint32_t size; + std::uint8_t opaque[16]; +}; + +struct VrTracker2UnsupportedCoordinateParameter; + +struct VrTracker2CalibrationResult; + +struct VrTracker2TelemetryData; + +struct VrTracker2PoseParameter; + +struct VrTracker2PoseResult; + +struct VrTracker2MeshRequirement { + std::uint32_t vertexCount; + std::uint32_t indexCount; + std::uint32_t vertexDataSize; + std::uint32_t indexDataSize; +}; + +struct VrTracker2Pose { + float position[3]; + std::uint8_t padding[4]; + float orientation[4]; +}; + +struct VrTracker2ResultParameter { + std::uint32_t size; + std::int32_t deviceHandle; + std::uint32_t reserved; + std::uint8_t padding[4]; + std::uint64_t timestamp; + std::uint32_t reserved2; + std::uint8_t padding2[4]; + std::uint64_t userTag; + std::uint32_t reserved3[2]; +}; + +struct VrTracker2Result; + +struct VrTracker2CoordinateLocation; + +struct VrTracker2BoundaryGeometry { + std::uint32_t size; + std::uint32_t pointCount; + float points[256][3]; +}; + +struct VrTracker2OrientedBoundingBox { + std::uint32_t size; + float transform[16]; + float extents[3]; +}; + +struct VrTracker2RelativePoseResult { + VrTracker2Pose pose; + std::uint8_t reserved[32]; +}; + +struct VrTracker2ScalarResult { + std::uint32_t size; + float value; +}; + +struct VrTracker2TrackingOptions { + std::int32_t options[3]; + std::uint8_t reset; + std::uint8_t reserved[3]; + float vector[3]; +}; + +struct VrTracker2TrackingOptionsResult { + VrTracker2TrackingOptions options; + float position[3]; +}; + +struct VrTracker2DiagnosticResult; + +struct VrTracker2ExtendedResult; + +struct VrTracker2TrackingConfiguration; + +struct VrTracker2ModeParameter { + std::uint32_t size; + std::uint32_t mode; + float value; +}; + +struct VrTracker2DiagnosticText { + char sections[6][512]; +}; + +struct VrTracker2CompositePoseResult; + +struct VrTracker2CameraImages; + +struct VrTracker2SensorFrame; + +struct VrTracker2TrackingStatus; + +struct VrTracker2BoundaryParameter; + +struct VrTracker2CameraFrame { + std::uint32_t selector; + std::uint32_t identifiers[2]; + std::uint32_t width; + std::uint32_t height; + std::uint32_t pitch; + const void* pixels; + std::int32_t bufferIndex; +}; + +struct VrTracker2CameraFrameMetadata; + +struct VrTracker2SpatialQueryResult { + std::uint32_t size; + std::int32_t status; + float vectors[2][3]; + float value; +}; + +struct VrTracker2DeviceCalibrationStatus; + +struct VrTracker2DeviceBoundaryParameter; + +struct VrTracker2ResetPoseParameter; + +struct VrTracker2MapData; + +struct VrTracker2MapStatus; + +struct VrTracker2RuntimeStatus; + +struct VrTracker2DiagnosticState; + +struct VrTracker2ExtendedDiagnosticState; + +struct VrTracker2SensorDiagnostics; + +struct VrTracker2DeviceDiagnostics; + +struct VrTracker2TimingDiagnostics; + +struct VrTracker2MapConfiguration; + +struct VrTracker2SensorSummary; + +struct VrTracker2CoordinateResultParameter { + std::uint32_t coordinateSystem; + std::uint32_t reserved; + VrTracker2ResultParameter parameter; +}; + +struct VrTracker2BoundaryFitResult { + std::uint8_t flags[4]; + float dimensions[4]; +}; + +struct VrTracker2VectorResult { + std::uint32_t size; + float value[3]; +}; + +struct VrTracker2ExtendedTrackingResult; + +struct VrTracker2MeshVertex; + +struct VrTracker2TimedPoseResult; + +struct VrTracker2ImagePoseResult; + +struct VrTracker2ImageFrame; + +extern "C" { + +std::int32_t APS5_VABI sceVrTracker2Finalize(); +std::int32_t APS5_VABI sceVrTracker2GetCoordinateSystem(std::uint32_t* coordinateSystem); +std::int32_t APS5_VABI sceVrTracker2GetPlayAreaBoundaryGeometry(VrTracker2BoundaryGeometry* geometry); +std::int32_t APS5_VABI sceVrTracker2GetPlayAreaOrientedBoundingBox(VrTracker2OrientedBoundingBox* box); +std::int32_t APS5_VABI sceVrTracker2GetResult(const VrTracker2ResultParameter* parameter, VrTracker2Result* result); +std::int32_t APS5_VABI sceVrTracker2LocateCoordinateSystem(std::uint32_t coordinateSystem, VrTracker2CoordinateLocation* location); +std::int32_t APS5_VABI sceVrTracker2RegisterDevice(std::uint32_t deviceType, std::int32_t deviceHandle); +std::int32_t APS5_VABI sceVrTracker2ResetLocalCoordinate(std::int32_t deviceHandle); +std::int32_t APS5_VABI sceVrTracker2SetCoordinateSystem(std::uint32_t coordinateSystem); +std::int32_t APS5_VABI sceVrTracker2UnregisterDevice(std::int32_t deviceHandle); +std::int32_t APS5_VABI sceVrTracker2QueryMemory(const VrTracker2MemoryParameter* parameter, VrTracker2MemoryRequirement* requirement); +std::int32_t APS5_VABI sceVrTracker2Initialize(const VrTracker2InitializeParameter* parameter); +std::int32_t APS5_VABI VrTracker2GetRelativePose(std::uint32_t firstCoordinateSystem, std::uint32_t secondCoordinateSystem, VrTracker2RelativePoseResult* result); +std::int32_t APS5_VABI VrTracker2GetDiagnosticResult(VrTracker2DiagnosticResult* result); +std::int32_t APS5_VABI VrTracker2SetTrackingOptions(const VrTracker2TrackingOptions* options); +std::int32_t APS5_VABI VrTracker2GetTrackingOptions(VrTracker2TrackingOptionsResult* result); +std::int32_t APS5_VABI VrTracker2GetExtendedResult(VrTracker2ExtendedResult* result); +std::int32_t APS5_VABI VrTracker2GetCompositePose(const VrTracker2ResultParameter* parameter, VrTracker2CompositePoseResult* result); +std::int32_t APS5_VABI VrTracker2GetCounter(std::uint64_t* counter, std::uint32_t selector); +std::int32_t APS5_VABI VrTracker2SetTrackingConfiguration(const VrTracker2TrackingConfiguration* configuration); +std::int32_t APS5_VABI VrTracker2GetTrackingConfiguration(VrTracker2TrackingConfiguration* configuration); +std::int32_t APS5_VABI VrTracker2GetCameraImages(VrTracker2CameraImages* images); +std::int32_t APS5_VABI VrTracker2GetCameraImage(void* image, std::uint32_t size, std::uint32_t* frameId); +std::int32_t APS5_VABI VrTracker2GetDiagnosticText(VrTracker2DiagnosticText* text); +std::int32_t APS5_VABI VrTracker2GetSensorFrame(VrTracker2SensorFrame* frame); +std::int32_t APS5_VABI VrTracker2GetTrackingStatus(VrTracker2TrackingStatus* status); +std::int32_t APS5_VABI VrTracker2GetBoundaryParameter(VrTracker2BoundaryParameter* parameter); +std::int32_t APS5_VABI VrTracker2SetBoundaryParameter(const VrTracker2BoundaryParameter* parameter); +std::int32_t APS5_VABI VrTracker2SetMode(const VrTracker2ModeParameter* parameter); +std::int32_t APS5_VABI VrTracker2GetScalarResult(VrTracker2ScalarResult* result); +std::int32_t APS5_VABI VrTracker2LegacyQuery12(const VrTracker2LegacyParameter12* parameter); +std::int32_t APS5_VABI VrTracker2LegacyQuery20(const VrTracker2LegacyParameter20* parameter); +std::int32_t APS5_VABI VrTracker2UnsupportedCoordinateOperation(const VrTracker2UnsupportedCoordinateParameter* parameter); +std::int32_t APS5_VABI VrTracker2AcquireCameraFrame(std::uint32_t selector, VrTracker2CameraFrame* frame); +std::int32_t APS5_VABI VrTracker2AcquireCameraFrameWithMetadata(std::uint32_t selector, VrTracker2CameraFrame* frame, VrTracker2CameraFrameMetadata* metadata); +std::int32_t APS5_VABI VrTracker2ReleaseCameraFrame(const VrTracker2CameraFrame* frame); +std::int32_t APS5_VABI VrTracker2SetScalarOption(float value); +std::int32_t APS5_VABI VrTracker2GetScalarOption(float* value); +std::int32_t APS5_VABI VrTracker2SelectCoordinateSystem(std::uint32_t coordinateSystem); +std::int32_t APS5_VABI VrTracker2GetSelectedCoordinateSystem(std::uint32_t* coordinateSystem); +std::int32_t APS5_VABI VrTracker2ResetDevicePose(std::int32_t deviceHandle); +std::int32_t APS5_VABI VrTracker2ResetPose(const VrTracker2ResetPoseParameter* parameter); +std::int32_t APS5_VABI VrTracker2QueryPosition(const float* position, VrTracker2SpatialQueryResult* result); +std::int32_t APS5_VABI VrTracker2QueryDevicePosition(std::int32_t deviceHandle, VrTracker2SpatialQueryResult* result); +std::int32_t APS5_VABI VrTracker2GetTrackingBounds(VrTracker2OrientedBoundingBox* bounds); +std::int32_t APS5_VABI VrTracker2GetDeviceState(std::int32_t deviceHandle, std::int32_t* state); +std::int32_t APS5_VABI VrTracker2GetDeviceCalibrationStatus(std::int32_t deviceHandle, VrTracker2DeviceCalibrationStatus* status); +std::int32_t APS5_VABI VrTracker2SetDeviceBoundary(std::int32_t deviceHandle, std::int32_t selector, const VrTracker2DeviceBoundaryParameter* parameter); +std::int32_t APS5_VABI VrTracker2GetDeviceBoundary(std::int32_t deviceHandle, std::int32_t selector, VrTracker2DeviceBoundaryParameter* parameter); +std::int32_t APS5_VABI VrTracker2ApplyDeviceBoundary(std::int32_t deviceHandle, std::int32_t selector); +std::int32_t APS5_VABI VrTracker2GetDeviceMapState(std::int32_t deviceHandle, std::int32_t* state); +std::int32_t APS5_VABI VrTracker2InitializeMap(std::int32_t deviceHandle, const float* parameter); +std::int32_t APS5_VABI VrTracker2GetCoordinateTrackingResult(const VrTracker2CoordinateResultParameter* parameter, VrTracker2ExtendedTrackingResult* result); +std::int32_t APS5_VABI VrTracker2GetExtendedTrackingResult(const VrTracker2ResultParameter* parameter, VrTracker2ExtendedTrackingResult* result); +std::int32_t APS5_VABI VrTracker2FitBoundary(const VrTracker2BoundaryGeometry* boundary, VrTracker2BoundaryFitResult* result); +std::int32_t APS5_VABI VrTracker2FitBoundaryWithBounds(const VrTracker2BoundaryGeometry* boundary, VrTracker2OrientedBoundingBox* firstBounds, VrTracker2OrientedBoundingBox* secondBounds, VrTracker2BoundaryFitResult* result); +std::int32_t APS5_VABI VrTracker2GetDeviceCenter(std::int32_t deviceHandle, VrTracker2VectorResult* result); +std::int32_t APS5_VABI VrTracker2GetRuntimeStatus(VrTracker2RuntimeStatus* status); +std::int32_t APS5_VABI VrTracker2GetTrackingFlags(bool* active, std::uint8_t* flags); +std::int32_t APS5_VABI VrTracker2GetDiagnosticState(VrTracker2DiagnosticState* state); +std::int32_t APS5_VABI VrTracker2GetExtendedDiagnosticState(VrTracker2ExtendedDiagnosticState* state); +std::int32_t APS5_VABI VrTracker2GetSensorDiagnostics(VrTracker2SensorDiagnostics* diagnostics); +std::int32_t APS5_VABI VrTracker2GetDeviceDiagnostics(VrTracker2DeviceDiagnostics* diagnostics); +std::int32_t APS5_VABI VrTracker2GetTimingDiagnostics(VrTracker2TimingDiagnostics* diagnostics); +std::int32_t APS5_VABI VrTracker2GetMapConfiguration(VrTracker2MapConfiguration* configuration); +std::int32_t APS5_VABI VrTracker2SetMapConfiguration(const VrTracker2MapConfiguration* configuration); +std::int32_t APS5_VABI VrTracker2GetMapStatus(VrTracker2MapStatus* status); +std::int32_t APS5_VABI VrTracker2BuildMesh(VrTracker2MeshVertex* vertices, std::uint16_t* indices, std::uint32_t format); +std::int32_t APS5_VABI VrTracker2GetMapScalar(float* value, const VrTracker2MapData* data); +std::int32_t APS5_VABI VrTracker2GetTimedPose(VrTracker2TimedPoseResult* result, std::uint64_t timestamp, std::uint64_t userTag); +std::int32_t APS5_VABI VrTracker2GetMapData(VrTracker2MapData* data, std::uint32_t size); +std::int32_t APS5_VABI VrTracker2GetPoseScalar(float* value); +std::int32_t APS5_VABI VrTracker2SubmitImageFrame(const VrTracker2ImageFrame* frame); +std::int32_t APS5_VABI VrTracker2GetImagePose(VrTracker2ImagePoseResult* result); +std::int32_t APS5_VABI VrTracker2GetSensorSummary(VrTracker2SensorSummary* summary); +std::int32_t APS5_VABI VrTracker2NotInitialized(); +std::int32_t APS5_VABI VrTracker2ClearWideResult(void* result); +std::int32_t APS5_VABI VrTracker2ClearNarrowResult(void* result); +std::int32_t APS5_VABI VrTracker2CheckSecondThenReportNotInitialized(const void* first, const void* second); +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 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); +std::int32_t APS5_VABI VrTracker2Unknown06(const VrTracker2TelemetryData* data); +std::int32_t APS5_VABI VrTracker2Unknown07(const VrTracker2PoseParameter* parameter, VrTracker2PoseResult* result); + +} diff --git a/core/libs/prx/libSceVrTracker2/libSceVrTracker2.hpp b/core/libs/prx/libSceVrTracker2/libSceVrTracker2.hpp new file mode 100644 index 0000000000..667a8cf90f --- /dev/null +++ b/core/libs/prx/libSceVrTracker2/libSceVrTracker2.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceVrTracker2/include/Api.hpp" diff --git a/core/libs/prx/libSceWeb/libSceWeb.hpp b/core/libs/prx/libSceWeb/libSceWeb.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceWeb/libSceWeb.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceWebBrowserDialog/CMakeLists.txt b/core/libs/prx/libSceWebBrowserDialog/CMakeLists.txt index 2c498e5df1..641f60463e 100644 --- a/core/libs/prx/libSceWebBrowserDialog/CMakeLists.txt +++ b/core/libs/prx/libSceWebBrowserDialog/CMakeLists.txt @@ -12,3 +12,14 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_web_browser_dialog_cookies_tests ${LIBS_INCLUDE_DIR}/tests/GuestWebBrowserDialogCookies.cpp) + target_include_directories(guest_web_browser_dialog_cookies_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_web_browser_dialog_cookies_tests PRIVATE libSceWebBrowserDialog) + add_test(NAME guest_web_browser_dialog_cookies COMMAND guest_web_browser_dialog_cookies_tests) + if(WIN32) + set_tests_properties(guest_web_browser_dialog_cookies PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceWebBrowserDialog/Export.cpp b/core/libs/prx/libSceWebBrowserDialog/Export.cpp index 8ccf318370..d5961d6211 100644 --- a/core/libs/prx/libSceWebBrowserDialog/Export.cpp +++ b/core/libs/prx/libSceWebBrowserDialog/Export.cpp @@ -1,21 +1,44 @@ +#include "prx/libSceWebBrowserDialog/include/Api.hpp" #include #include #include +#include #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" +static_assert(sizeof(WebBrowserDialogCookieParam) == 280); +static_assert(offsetof(WebBrowserDialogCookieParam, address) == 8); +static_assert(offsetof(WebBrowserDialogCookieParam, cookie) == 16); +static_assert(sizeof(WebBrowserDialogResetCookieParam) == 264); + namespace { std::atomic g_status{0}; +std::atomic g_open{false}; constexpr int COMMON_DIALOG_STATUS_NONE = 0; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_INVALID_STATE = static_cast(0x80B80006u); constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); +constexpr int COMMON_DIALOG_ERROR_PARAMETER = static_cast(0x80B8000Au); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); + +bool ReservedClear(const std::uint8_t (&reserved)[256]) { + for (const auto byte : reserved) + if (byte != 0) return false; + return true; +} + +bool LengthWithin(const char* text, std::size_t limit) { + if (!text) return false; + const auto length = strnlen(text, limit); + return length != 0 && length < limit; +} } extern "C" { @@ -28,26 +51,42 @@ int APS5_VABI sceWebBrowserDialogInitialize(void) { int APS5_VABI sceWebBrowserDialogTerminate(void) { int expected = 1; - if (g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_NONE)) return 0; + if (g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_NONE)) { + g_open = false; + return 0; + } expected = COMMON_DIALOG_STATUS_FINISHED; - if (g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_NONE)) return 0; + if (g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_NONE)) { + g_open = false; + return 0; + } if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; return COMMON_DIALOG_ERROR_BUSY; } int APS5_VABI sceWebBrowserDialogClose(void) { - if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - g_status = COMMON_DIALOG_STATUS_FINISHED; - return 0; + g_open = false; + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status != COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_NOT_RUNNING; + g_status = COMMON_DIALOG_STATUS_FINISHED; + return 0; } -int APS5_VABI sceWebBrowserDialogGetResult(void* result) { - const int status = g_status.load(); - if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; - if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - *static_cast(result) = COMMON_DIALOG_RESULT_USER_CANCELED; - return 0; +int APS5_VABI sceWebBrowserDialogGetResult(WebBrowserDialogResult* result) { + if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (std::any_of(std::begin(result->reserved), std::end(result->reserved), [](std::uint8_t value) { return value != 0; })) return COMMON_DIALOG_ERROR_PARAMETER; + if (result->extended != nullptr) { + const auto& extended = *result->extended; + if (extended.size != sizeof(extended)) return COMMON_DIALOG_ERROR_PARAMETER; + if (std::any_of(std::begin(extended.reserved0), std::end(extended.reserved0), [](std::uint8_t value) { return value != 0; })) return COMMON_DIALOG_ERROR_PARAMETER; + if (std::any_of(std::begin(extended.reserved1), std::end(extended.reserved1), [](std::uint8_t value) { return value != 0; })) return COMMON_DIALOG_ERROR_PARAMETER; + } + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + return 0; } int APS5_VABI sceWebBrowserDialogGetStatus(void) { @@ -60,30 +99,52 @@ int APS5_VABI sceWebBrowserDialogOpen(const void* param) { if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; g_status = COMMON_DIALOG_STATUS_FINISHED; + g_open = true; return 0; } int APS5_VABI sceWebBrowserDialogUpdateStatus(void) { - return g_status.load(); + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_FINISHED) g_open = false; + return status; } - -int APS5_VABI sceWebBrowserDialogSetCookie(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceWebBrowserDialogSetCookie(const WebBrowserDialogCookieParam* param) { + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (g_open.load()) return COMMON_DIALOG_ERROR_INVALID_STATE; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (param->size != sizeof(WebBrowserDialogCookieParam) || !LengthWithin(param->address, 0x800) || !LengthWithin(param->cookie, 0x1000) || + !ReservedClear(param->reserved)) + return COMMON_DIALOG_ERROR_PARAMETER; return 0; } -int APS5_VABI sceWebBrowserDialogOpenForPredeterminedContent(const void* param) { +int APS5_VABI sceWebBrowserDialogOpenForPredeterminedContent(const void* param, const WebBrowserDialogAllowList* allowList) { const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; - if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; + if (allowList == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (allowList->size != sizeof(*allowList)) return COMMON_DIALOG_ERROR_PARAMETER; + bool hasAddress = false; + for (const char* address : allowList->addresses) { + if (address == nullptr) continue; + hasAddress = true; + std::size_t length = 0; + while (length < 256 && address[length] != '\0') ++length; + if (length == 0 || length == 256) return COMMON_DIALOG_ERROR_PARAMETER; + } + if (!hasAddress || std::any_of(std::begin(allowList->reserved), std::end(allowList->reserved), [](std::uint8_t value) { return value != 0; })) return COMMON_DIALOG_ERROR_PARAMETER; if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; g_status = COMMON_DIALOG_STATUS_FINISHED; + g_open = true; return 0; } -int APS5_VABI sceWebBrowserDialogResetCookie() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceWebBrowserDialogResetCookie(const WebBrowserDialogResetCookieParam* param) { + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (g_open.load()) return COMMON_DIALOG_ERROR_INVALID_STATE; + if (param->size != sizeof(WebBrowserDialogResetCookieParam) || !ReservedClear(param->reserved)) return COMMON_DIALOG_ERROR_PARAMETER; return 0; } diff --git a/core/libs/prx/libSceWebBrowserDialog/include/Api.hpp b/core/libs/prx/libSceWebBrowserDialog/include/Api.hpp new file mode 100644 index 0000000000..e291592e7f --- /dev/null +++ b/core/libs/prx/libSceWebBrowserDialog/include/Api.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "SceTypes.hpp" + +struct WebBrowserDialogCookieParam { + std::uint64_t size; + const char* address; + const char* cookie; + std::uint8_t reserved[256]; +}; + +struct WebBrowserDialogResetCookieParam { + std::uint64_t size; + std::uint8_t reserved[256]; +}; + +extern "C" { + +int APS5_VABI sceWebBrowserDialogInitialize(void); +int APS5_VABI sceWebBrowserDialogTerminate(void); +int APS5_VABI sceWebBrowserDialogClose(void); +int APS5_VABI sceWebBrowserDialogGetResult(WebBrowserDialogResult* result); +int APS5_VABI sceWebBrowserDialogGetStatus(void); +int APS5_VABI sceWebBrowserDialogOpen(const void* param); +int APS5_VABI sceWebBrowserDialogUpdateStatus(void); +int APS5_VABI sceWebBrowserDialogSetCookie(const WebBrowserDialogCookieParam* param); +int APS5_VABI sceWebBrowserDialogOpenForPredeterminedContent(const void* param, const WebBrowserDialogAllowList* allowList); +int APS5_VABI sceWebBrowserDialogResetCookie(const WebBrowserDialogResetCookieParam* param); +int APS5_VABI sceWebBrowserDialogGetEvent(); +int APS5_VABI sceWebBrowserDialogNavigate(); +int APS5_VABI sceWebBrowserDialogSetZoom(); + +} diff --git a/core/libs/prx/libSceWebBrowserDialog/libSceWebBrowserDialog.hpp b/core/libs/prx/libSceWebBrowserDialog/libSceWebBrowserDialog.hpp new file mode 100644 index 0000000000..d9b740e440 --- /dev/null +++ b/core/libs/prx/libSceWebBrowserDialog/libSceWebBrowserDialog.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceWebBrowserDialog/include/Api.hpp" diff --git a/core/libs/prx/libSceWebKit2/libSceWebKit2.hpp b/core/libs/prx/libSceWebKit2/libSceWebKit2.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceWebKit2/libSceWebKit2.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceWebKit2Secure/libSceWebKit2Secure.hpp b/core/libs/prx/libSceWebKit2Secure/libSceWebKit2Secure.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceWebKit2Secure/libSceWebKit2Secure.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceWebmParserMdrw/libSceWebmParserMdrw.hpp b/core/libs/prx/libSceWebmParserMdrw/libSceWebmParserMdrw.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceWebmParserMdrw/libSceWebmParserMdrw.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceWkFontConfig/Export.cpp b/core/libs/prx/libSceWkFontConfig/Export.cpp index 08ae01f5ec..7257bc7ef7 100644 --- a/core/libs/prx/libSceWkFontConfig/Export.cpp +++ b/core/libs/prx/libSceWkFontConfig/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceWkFontConfig/include/Api.hpp" #include #include #include "SceTypes.hpp" diff --git a/core/libs/prx/libSceWkFontConfig/include/Api.hpp b/core/libs/prx/libSceWkFontConfig/include/Api.hpp new file mode 100644 index 0000000000..94402813b4 --- /dev/null +++ b/core/libs/prx/libSceWkFontConfig/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceWkFontConfigGetAttr(void); +int APS5_VABI sceWkFontConfigGetFont(void); +int APS5_VABI sceWkFontConfigGetFontFromFontName(void); +int APS5_VABI sceWkFontConfigGetFontName(void); +int APS5_VABI sceWkFontConfigGetFontPath(void); + +} diff --git a/core/libs/prx/libSceWkFontConfig/libSceWkFontConfig.hpp b/core/libs/prx/libSceWkFontConfig/libSceWkFontConfig.hpp new file mode 100644 index 0000000000..0d45867b73 --- /dev/null +++ b/core/libs/prx/libSceWkFontConfig/libSceWkFontConfig.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceWkFontConfig/include/Api.hpp" diff --git a/core/libs/prx/libSceWorkspace/libSceWorkspace.hpp b/core/libs/prx/libSceWorkspace/libSceWorkspace.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceWorkspace/libSceWorkspace.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceXml/libSceXml.hpp b/core/libs/prx/libSceXml/libSceXml.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libSceXml/libSceXml.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libSceZlib/Export.cpp b/core/libs/prx/libSceZlib/Export.cpp index 22135e465c..f908068b6b 100644 --- a/core/libs/prx/libSceZlib/Export.cpp +++ b/core/libs/prx/libSceZlib/Export.cpp @@ -1,31 +1,39 @@ +#include "prx/libSceZlib/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "zlib.h" +#include +#include #include +#include #include #include -#include #include +#include namespace { static_assert(sizeof(std::uint32_t) == 4 && sizeof(std::uint64_t) == 8 && sizeof(std::int32_t) == 4); +constexpr std::int32_t NOT_FOUND = static_cast(0x81120002); constexpr std::int32_t NOT_INITIALIZED = static_cast(0x81120032); constexpr std::int32_t ALREADY_INITIALIZED = static_cast(0x81120033); +constexpr std::int32_t TIMED_OUT = static_cast(0x81120027); constexpr std::int32_t NO_SPACE = static_cast(0x8112001C); +constexpr std::int32_t INVALID_ARGUMENT = static_cast(0x81120016); constexpr std::int32_t FATAL = static_cast(0x811200FF); -struct Result { - std::uint64_t id; +struct InflateResult { std::uint32_t produced; std::int32_t status; - bool retrieved = false; }; std::mutex serviceMutex; bool initialized = false; std::uint64_t nextId = 1; -std::optional result; +std::uint64_t session = 0; +std::unordered_map results; +std::deque completed; +std::condition_variable completion; } // namespace @@ -34,7 +42,8 @@ extern "C" { std::int32_t APS5_VABI sceZlibInitialize(const void* buffer, std::uint64_t length) { std::lock_guard lock(serviceMutex); if (initialized) return ALREADY_INITIALIZED; - if ((buffer == nullptr) != (length == 0)) throw std::runtime_error("sceZlibInitialize: invalid work buffer"); + if ((buffer == nullptr) != (length == 0)) return INVALID_ARGUMENT; + if (buffer) throw std::runtime_error("sceZlibInitialize: work memory registration is not implemented"); initialized = true; return 0; } @@ -42,21 +51,21 @@ std::int32_t APS5_VABI sceZlibInitialize(const void* buffer, std::uint64_t lengt std::int32_t APS5_VABI sceZlibFinalize() { std::lock_guard lock(serviceMutex); if (!initialized) return NOT_INITIALIZED; - if (result && !result->retrieved) throw std::runtime_error("sceZlibFinalize: outstanding result semantics unknown"); - result.reset(); + results.clear(); + completed.clear(); initialized = false; + ++session; + completion.notify_all(); return 0; } -std::int32_t APS5_VABI sceZlibInflate(const void* source, std::uint32_t sourceLength, - void* destination, std::uint32_t destinationLength, - std::uint64_t* requestId) { +std::int32_t APS5_VABI sceZlibInflate(const void* source, std::uint32_t sourceLength, void* destination, std::uint32_t destinationLength, std::uint64_t* requestId) { std::lock_guard lock(serviceMutex); if (!initialized) return NOT_INITIALIZED; if (!source || !sourceLength || !destination || !requestId || !destinationLength || - destinationLength > 65536 || reinterpret_cast(destination) % 2048 != 0) - throw std::runtime_error("sceZlibInflate: invalid buffer or size"); - if (result && !result->retrieved) throw std::runtime_error("sceZlibInflate: multiple outstanding requests unsupported"); + destinationLength > 65536 || destinationLength % 2048 != 0) return INVALID_ARGUMENT; + if (reinterpret_cast(destination) % 2048 != 0) + throw std::runtime_error("sceZlibInflate: unaligned destination driver behavior is not implemented"); if (nextId == std::numeric_limits::max()) throw std::runtime_error("sceZlibInflate: request IDs exhausted"); z_stream stream{}; stream.next_in = const_cast(static_cast(source)); @@ -70,30 +79,36 @@ std::int32_t APS5_VABI sceZlibInflate(const void* source, std::uint32_t sourceLe (decoded == Z_BUF_ERROR && stream.avail_out == 0 && stream.avail_in != 0 ? NO_SPACE : FATAL); const int ended = inflateEnd(&stream); if (ended != Z_OK) throw std::runtime_error("sceZlibInflate: decoder cleanup failed"); - result = Result{nextId++, produced, status}; - *requestId = result->id; + const std::uint64_t id = nextId++; + results[id] = InflateResult{produced, status}; + completed.push_back(id); + completion.notify_one(); + *requestId = id; return 0; } std::int32_t APS5_VABI sceZlibWaitForDone(std::uint64_t* requestId, std::uint32_t* timeout) { - std::lock_guard lock(serviceMutex); + std::unique_lock lock(serviceMutex); if (!initialized) return NOT_INITIALIZED; - if (timeout) throw std::runtime_error("sceZlibWaitForDone: timeout semantics unknown"); - if (!requestId || !result || result->retrieved) throw std::runtime_error("sceZlibWaitForDone: no outstanding request"); - *requestId = result->id; + if (!requestId) return INVALID_ARGUMENT; + const std::uint64_t waitSession = session; + const auto finished = [waitSession] { return !completed.empty() || session != waitSession; }; + if (!timeout) completion.wait(lock, finished); + else if (!completion.wait_for(lock, std::chrono::microseconds(*timeout), finished)) return TIMED_OUT; + if (session != waitSession) throw std::runtime_error("sceZlibWaitForDone: finalization during a wait semantics unknown"); + *requestId = completed.front(); + completed.pop_front(); return 0; } -std::int32_t APS5_VABI sceZlibGetResult(std::uint64_t requestId, std::uint32_t* destinationLength, - std::int32_t* status) { +std::int32_t APS5_VABI sceZlibGetResult(std::uint64_t requestId, std::uint32_t* destinationLength, std::int32_t* status) { std::lock_guard lock(serviceMutex); if (!initialized) return NOT_INITIALIZED; - if (!destinationLength || !status || !result || result->id != requestId) - throw std::runtime_error("sceZlibGetResult: invalid request or output"); - if (result->retrieved) throw std::runtime_error("sceZlibGetResult: repeated retrieval semantics unknown"); - *destinationLength = result->produced; - *status = result->status; - result->retrieved = true; + if (!destinationLength || !status) return INVALID_ARGUMENT; + const auto found = results.find(requestId); + if (found == results.end()) return NOT_FOUND; + *destinationLength = found->second.produced; + *status = found->second.status; return 0; } diff --git a/core/libs/prx/libSceZlib/include/Api.hpp b/core/libs/prx/libSceZlib/include/Api.hpp new file mode 100644 index 0000000000..ec57e94915 --- /dev/null +++ b/core/libs/prx/libSceZlib/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +std::int32_t APS5_VABI sceZlibInitialize(const void* buffer, std::uint64_t length); +std::int32_t APS5_VABI sceZlibFinalize(); +std::int32_t APS5_VABI sceZlibInflate(const void* source, std::uint32_t sourceLength, void* destination, std::uint32_t destinationLength, std::uint64_t* requestId); +std::int32_t APS5_VABI sceZlibWaitForDone(std::uint64_t* requestId, std::uint32_t* timeout); +std::int32_t APS5_VABI sceZlibGetResult(std::uint64_t requestId, std::uint32_t* destinationLength, std::int32_t* status); + +} diff --git a/core/libs/prx/libSceZlib/libSceZlib.hpp b/core/libs/prx/libSceZlib/libSceZlib.hpp new file mode 100644 index 0000000000..2740f392a8 --- /dev/null +++ b/core/libs/prx/libSceZlib/libSceZlib.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceZlib/include/Api.hpp" diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index f06b05d6af..a307744beb 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -62,6 +62,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Backtrace.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareAtomics.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareInternals.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/TimeExtras.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AtomicBuiltins.cpp @@ -114,19 +115,56 @@ target_include_directories(libc PRIVATE target_link_libraries(libc PRIVATE zarchive) +if(BUILD_TESTING) + add_test_executable(guest_update_env_tests ${LIBS_INCLUDE_DIR}/tests/GuestUpdateEnv.cpp) + target_include_directories(guest_update_env_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_update_env_tests PRIVATE libc ${CMAKE_DL_LIBS}) + configure_windows_unwind(guest_update_env_tests) + add_test(NAME guest_update_env COMMAND guest_update_env_tests $) + foreach(bit RANGE 0 5) + foreach(source RANGE 0 2) + add_test(NAME guest_update_env_pending_${bit}_${source} COMMAND guest_update_env_tests $ ${bit} ${source}) + set_tests_properties(guest_update_env_pending_${bit}_${source} PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_update_env_pending_${bit}_${source} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() + if(WIN32) + set_tests_properties(guest_update_env PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatingPoint.cpp) if(BUILD_TESTING) add_test_executable(guest_floating_point_tests ${LIBS_INCLUDE_DIR}/tests/GuestFloatingPoint.cpp) target_include_directories(guest_floating_point_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_floating_point_tests PRIVATE libc) + target_link_libraries(guest_floating_point_tests PRIVATE libc ${CMAKE_DL_LIBS}) configure_windows_unwind(guest_floating_point_tests) - add_test(NAME guest_floating_point COMMAND guest_floating_point_tests) + add_test(NAME guest_floating_point COMMAND guest_floating_point_tests $) set_tests_properties(guest_floating_point PROPERTIES TIMEOUT 10) if(WIN32) set_tests_properties(guest_floating_point PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + enable_language(ASM) + add_test_executable(guest_sce_v2_exceptions_tests + ${LIBS_INCLUDE_DIR}/tests/GuestSceV2Exceptions.cpp + ${LIBS_INCLUDE_DIR}/tests/GuestSceV2Exceptions.S) + target_include_directories(guest_sce_v2_exceptions_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sce_v2_exceptions_tests PRIVATE libc) + configure_windows_unwind(guest_sce_v2_exceptions_tests) + add_test(NAME guest_sce_v2_exceptions COMMAND guest_sce_v2_exceptions_tests) + set_tests_properties(guest_sce_v2_exceptions PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_sce_v2_exceptions PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endif() endif() if(WIN32) @@ -299,6 +337,15 @@ if(BUILD_TESTING) set_tests_properties(guest_atomic_builtins PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_multibyte_locale_tests ${LIBS_INCLUDE_DIR}/tests/GuestMultibyteLocale.cpp) + target_include_directories(guest_multibyte_locale_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_multibyte_locale_tests PRIVATE libc) + configure_windows_unwind(guest_multibyte_locale_tests) + add_test(NAME guest_multibyte_locale COMMAND guest_multibyte_locale_tests) + if(WIN32) + set_tests_properties(guest_multibyte_locale PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_wide_conversion_tests ${LIBS_INCLUDE_DIR}/tests/GuestWideConversion.cpp) target_include_directories(guest_wide_conversion_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_wide_conversion_tests PRIVATE libc) @@ -354,6 +401,24 @@ if(BUILD_TESTING) set_tests_properties(guest_random_state PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_libc_random_seeding_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcRandomSeeding.cpp) + target_include_directories(guest_libc_random_seeding_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_random_seeding_tests PRIVATE libc) + configure_windows_unwind(guest_libc_random_seeding_tests) + add_test(NAME guest_libc-random-seeding COMMAND guest_libc_random_seeding_tests) + if(WIN32) + set_tests_properties(guest_libc-random-seeding PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_random_device_seed_tests ${LIBS_INCLUDE_DIR}/tests/GuestRandomDeviceSeed.cpp src/Random.cpp) + target_include_directories(guest_random_device_seed_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_random_device_seed_tests PRIVATE libc) + configure_windows_unwind(guest_random_device_seed_tests) + add_test(NAME guest_random_device_seed COMMAND guest_random_device_seed_tests) + if(WIN32) + set_tests_properties(guest_random_device_seed PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() 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) @@ -441,6 +506,16 @@ if(BUILD_TESTING) set_tests_properties(guest_libc_threads PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_std_thread_hardware_concurrency_tests ${LIBS_INCLUDE_DIR}/tests/GuestStdThreadHardwareConcurrency.cpp) + target_include_directories(guest_std_thread_hardware_concurrency_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_std_thread_hardware_concurrency_tests PRIVATE libc) + configure_windows_unwind(guest_std_thread_hardware_concurrency_tests) + add_test(NAME guest_std_thread_hardware_concurrency COMMAND guest_std_thread_hardware_concurrency_tests) + set_tests_properties(guest_std_thread_hardware_concurrency PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_std_thread_hardware_concurrency PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_libc_static_imports_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcStaticImports.cpp) target_include_directories(guest_libc_static_imports_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libc_static_imports_tests PRIVATE libc) @@ -526,6 +601,15 @@ if(BUILD_TESTING) set_tests_properties(guest_dinkumware_atomics PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_dinkumware_internals_tests ${LIBS_INCLUDE_DIR}/tests/GuestDinkumwareInternals.cpp) + target_include_directories(guest_dinkumware_internals_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_dinkumware_internals_tests PRIVATE libc) + configure_windows_unwind(guest_dinkumware_internals_tests) + add_test(NAME guest_dinkumware_internals COMMAND guest_dinkumware_internals_tests) + if(WIN32) + set_tests_properties(guest_dinkumware_internals PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_hostname_tests ${LIBS_INCLUDE_DIR}/tests/GuestHostname.cpp) target_include_directories(guest_hostname_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_hostname_tests PRIVATE libc) @@ -554,6 +638,15 @@ if(BUILD_TESTING) set_tests_properties(guest_memrchr PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_string_compare_tests ${LIBS_INCLUDE_DIR}/tests/GuestStringCompare.cpp) + target_include_directories(guest_string_compare_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_string_compare_tests PRIVATE libc) + configure_windows_unwind(guest_string_compare_tests) + add_test(NAME guest_string_compare COMMAND guest_string_compare_tests) + if(WIN32) + set_tests_properties(guest_string_compare PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_daemon_tests ${LIBS_INCLUDE_DIR}/tests/GuestDaemon.cpp) target_include_directories(guest_daemon_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_daemon_tests PRIVATE libc) @@ -642,6 +735,16 @@ if(BUILD_TESTING) set_tests_properties(guest_standard_streams guest_standard_streams_closed_stdout guest_standard_streams_exclusive_stdout PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_gets_tests ${LIBS_INCLUDE_DIR}/tests/GuestGets.cpp) + target_include_directories(guest_gets_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_gets_tests PRIVATE libc ${CMAKE_DL_LIBS}) + configure_windows_unwind(guest_gets_tests) + add_test(NAME guest_gets COMMAND guest_gets_tests $ guest_gets_error.tmp) + set_tests_properties(guest_gets PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_gets 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) @@ -652,6 +755,16 @@ if(BUILD_TESTING) set_tests_properties(file_streams PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_freopen_host_standard_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreopenHostStandard.cpp) + target_include_directories(guest_freopen_host_standard_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freopen_host_standard_tests PRIVATE libc) + configure_windows_unwind(guest_freopen_host_standard_tests) + add_test(NAME guest_freopen_host_standard COMMAND guest_freopen_host_standard_tests) + set_tests_properties(guest_freopen_host_standard PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freopen_host_standard PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_error_tests ${LIBS_INCLUDE_DIR}/tests/GuestErrors.cpp) target_include_directories(guest_error_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_error_tests PRIVATE libc) @@ -679,6 +792,17 @@ if(BUILD_TESTING) set_tests_properties(guest_call_trace_abort_race PROPERTIES TIMEOUT 20 PASS_REGULAR_EXPRESSION "last traced system library calls[^~]*#102 thread [0-9]+ libTraced.prx Integers from[^~]*most called traced imports \\(102 calls in total\\):[^~]* 101 libTraced.prx Integers[^~]* 1 libOther.prx Blocks") endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_test_executable(guest_call_trace_fixed_base_tests ${LIBS_INCLUDE_DIR}/tests/GuestCallTrace.cpp) + target_include_directories(guest_call_trace_fixed_base_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_call_trace_fixed_base_tests PRIVATE libc) + set_target_properties(guest_call_trace_fixed_base_tests PROPERTIES POSITION_INDEPENDENT_CODE OFF) + target_compile_options(guest_call_trace_fixed_base_tests PRIVATE -fno-pie) + target_link_options(guest_call_trace_fixed_base_tests PRIVATE -no-pie -fuse-ld=bfd) + add_test(NAME guest_call_trace_fixed_base_abort COMMAND guest_call_trace_fixed_base_tests abort) + set_tests_properties(guest_call_trace_fixed_base_abort PROPERTIES TIMEOUT 20 PASS_REGULAR_EXPRESSION + "#1 thread [0-9]+ libTraced.prx Integers from 0x00000000004[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f] guest_call_trace_fixed_base_tests\\+0x[0-9a-f]?[0-9a-f]?[0-9a-f]?[0-9a-f]?[0-9a-f][^0-9a-f]") + endif() endif() add_test_executable(guest_syslog_tests ${LIBS_INCLUDE_DIR}/tests/GuestSyslog.cpp) target_include_directories(guest_syslog_tests PRIVATE ${LIBS_INCLUDE_DIR}) @@ -805,11 +929,16 @@ if(BUILD_TESTING) set_tests_properties(guest_bad_optional_access PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_underflow_error_tests ${LIBS_INCLUDE_DIR}/tests/GuestUnderflowError.cpp) + target_include_directories(guest_underflow_error_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_underflow_error_tests PRIVATE libc) + configure_windows_unwind(guest_underflow_error_tests) + add_test(NAME guest_underflow_error COMMAND guest_underflow_error_tests) + if(WIN32) + set_tests_properties(guest_underflow_error PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(UNIX) - add_test_executable(exception_runtime_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionRuntime.cpp) - target_include_directories(exception_runtime_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(exception_runtime_tests PRIVATE libc) - add_test(NAME exception_runtime COMMAND exception_runtime_tests) add_test_executable(exception_personality_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionPersonality.cpp) target_include_directories(exception_personality_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(exception_personality_tests PRIVATE libc) @@ -824,8 +953,13 @@ if(BUILD_TESTING) add_test(NAME guest_path_lifetime COMMAND guest_path_lifetime_tests) set_tests_properties(guest_path_lifetime PROPERTIES TIMEOUT 20) if(NOT APPLE) + add_test_executable(exception_runtime_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionRuntime.cpp) + target_include_directories(exception_runtime_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(exception_runtime_tests PRIVATE libc) + add_test(NAME exception_runtime COMMAND exception_runtime_tests) enable_language(C) add_test_executable(exception_c_cleanup_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionCCleanup.cpp ${LIBS_INCLUDE_DIR}/tests/ExceptionCCleanup.c) + target_include_directories(exception_c_cleanup_tests PRIVATE ${LIBS_INCLUDE_DIR}) set_source_files_properties(${LIBS_INCLUDE_DIR}/tests/ExceptionCCleanup.c PROPERTIES COMPILE_OPTIONS -fexceptions) target_link_libraries(exception_c_cleanup_tests PRIVATE libc) add_test(NAME exception_c_cleanup COMMAND exception_c_cleanup_tests) @@ -1041,7 +1175,7 @@ if(MINGW) target_include_directories(guest_message_destructor_abi_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_message_destructor_abi_tests PRIVATE libc) configure_windows_unwind(guest_message_destructor_abi_tests) - foreach(mode IN ITEMS control decoy zero release regex-decoy regex-zero + foreach(mode IN ITEMS control hybrid decoy zero release regex-decoy regex-zero runtime-decoy runtime-zero doraise-decoy doraise-zero unshared-decoy unshared-zero regex-unshared-decoy regex-unshared-zero) add_test(NAME guest_message_destructor_${mode} COMMAND guest_message_destructor_abi_tests ${mode}) @@ -1072,6 +1206,7 @@ if(MINGW) set_tests_properties(windows_exceptions PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") add_test_executable(windows_backtrace_report_tests ${LIBS_INCLUDE_DIR}/tests/WindowsBacktraceReport.cpp) target_link_libraries(windows_backtrace_report_tests PRIVATE libc) + target_include_directories(windows_backtrace_report_tests PRIVATE ${LIBS_INCLUDE_DIR}) add_test(NAME windows_backtrace_report COMMAND windows_backtrace_report_tests) set_tests_properties(windows_backtrace_report PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$" diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 6056428c98..cb7055cfd5 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -8,8 +9,6 @@ uint32_t Need_sceLibc = 1; extern "C" { - int APS5_VABI _ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1__nid_postfix(int*, int (APS5_VABI *)(void*, void*, void**), void*); - int APS5_VABI std_execute_once_nid_postfix(int* flag, int (APS5_VABI *func)(void*, void*, void**), void* arg) { return _ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1__nid_postfix(flag, func, arg); } @@ -25,7 +24,6 @@ int APS5_VABI _ZSt14_Atomic_assertPKcS0__nid_postfix() { return 0; } - APS5_EXPORT("H2e8t5ScQGc", libc___cxa_finalize); void APS5_VABI libc___cxa_finalize(void* dsoHandle) { CxaFinalize_nid_no_patch(dsoHandle); diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp new file mode 100644 index 0000000000..6e7907eeff --- /dev/null +++ b/core/libs/prx/libc/include/Api.hpp @@ -0,0 +1,1427 @@ +#pragma once + +#include "prx/libc/include/specifics/linux/ElfTypes.hpp" +#include "prx/libc/include/specifics/itanium/UnwindAbi.hpp" +#include "prx/libc/include/MallocManagedSize.hpp" +#include "prx/libc/include/GuestTime.hpp" +#include "prx/libc/include/GuestLocale.hpp" +#include "prx/libc/include/GuestDirectory.hpp" +#include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/exceptions/Unwind.hpp" +#include "prx/libc/include/exceptions/Runtime.hpp" +#include "SceTypes.hpp" +#include +#include + +#ifdef _LIBCPP_VERSION +struct GuestExceptionPointer { + void* object {}; + explicit GuestExceptionPointer(void* primary) noexcept : object(primary) {} + GuestExceptionPointer(const GuestExceptionPointer&) = delete; + ~GuestExceptionPointer() {} +}; +using GuestExceptionPointerArgument = GuestExceptionPointer&; +#else +using GuestExceptionPointer = std::exception_ptr; +using GuestExceptionPointerArgument = GuestExceptionPointer; +#endif + +using GuestCompare = int (APS5_VABI *)(const void*, const void*); + +using GuestContextCompare = int (APS5_VABI*)(const void*, const void*, void*); + +using GuestExitCallback = void (APS5_VABI*)(); + +struct GuestFloatingPointEnvironment { + std::uint32_t control; + std::uint32_t status; + std::uint32_t tag; + unsigned char other[16]; + std::uint32_t mxcsr; +}; + +struct GuestInterfaceAddress { + GuestInterfaceAddress* next; + char* name; + std::uint32_t flags; + void* address; + void* netmask; + void* destination; + void* data; +}; + +struct GuestLocaleFacade { + GuestLocale::Implementation* implementation; +}; + +using GuestNewHandler = void (APS5_VABI*)(); + +struct GuestPad { + unsigned (APS5_VABI* const* vtable)(GuestPad*); + void* condition; + void* mutex; + bool started; +}; + +using Int128 = __int128; + +using LibcTerminateHandler = void(*)(); + +struct MinMaxPointers { + const void* minimum; + const void* maximum; +}; + +struct StreamManipulator { + GuestLocale::IosBase* (APS5_VABI* function)(GuestLocale::IosBase*, std::int32_t); + std::int32_t argument; +}; + +using Uint128 = unsigned __int128; + +struct Xtime { + std::int64_t sec; + std::int64_t nsec; +}; + +namespace LibcException { + +} + +namespace LibcException { + +struct CategoryObject { const void* vtable; }; + +struct ExceptionObject { const void* vtable; const char* message; }; + +struct ErrorCode { int reserved; int value; const CategoryObject* category; }; + +struct SystemErrorObject { ExceptionObject base; ErrorCode code; }; + +} + +extern "C" { + +int APS5_VABI std_execute_once_nid_postfix(int* flag, int (APS5_VABI *func)(void*, void*, void**), void* arg); +void APS5_VABI LibcHeapGetTraceInfo_nid_postfix(LibcHeapInfo* info); +int APS5_VABI _ZSt14_Atomic_assertPKcS0__nid_postfix(); +void APS5_VABI libc___cxa_finalize(void* dsoHandle); +char* APS5_VABI strndup_nid_postfix(const char* source, std::size_t limit); +char* APS5_VABI strdup_nid_postfix(const char* source); +int APS5_VABI asprintf_nid_postfix(char** destination, const char* format, ...); +void* APS5_VABI scriptingGetMem_nid_postfix(std::size_t alignment, std::size_t bytes); +void APS5_VABI scriptingFreeMem_nid_postfix(void* pointer); +void APS5_VABI __atomic_load_nid_postfix(std::size_t, void*, void*, int); +void APS5_VABI __atomic_store_nid_postfix(std::size_t, void*, void*, int); +void APS5_VABI __atomic_exchange_nid_postfix(std::size_t, void*, void*, void*, int); +bool APS5_VABI __atomic_compare_exchange_nid_postfix(std::size_t, void*, void*, void*, int, int); +bool APS5_VABI __atomic_is_lock_free_nid_postfix(std::size_t size, const void* pointer); +Uint128 APS5_VABI __sync_fetch_and_add_16_nid_postfix(Uint128* target, Uint128 value); +Uint128 APS5_VABI __sync_fetch_and_sub_16_nid_postfix(Uint128* target, Uint128 value); +Uint128 APS5_VABI __sync_fetch_and_and_16_nid_postfix(Uint128* target, Uint128 value); +Uint128 APS5_VABI __sync_fetch_and_or_16_nid_postfix(Uint128* target, Uint128 value); +Uint128 APS5_VABI __sync_fetch_and_xor_16_nid_postfix(Uint128* target, Uint128 value); +Uint128 APS5_VABI __sync_lock_test_and_set_16_nid_postfix(Uint128* target, Uint128 value); +int APS5_VABI sceLibcBacktraceGetBufferSize_nid_postfix(int limit, int* count, std::size_t* size); +int APS5_VABI sceLibcBacktraceSelf_nid_postfix(int limit, void* buffer, std::size_t size, int* count); +double APS5_VABI j0_nid_postfix(double x); +double APS5_VABI y0_nid_postfix(double x); +double APS5_VABI j1_nid_postfix(double x); +double APS5_VABI y1_nid_postfix(double x); +double APS5_VABI jn_nid_postfix(int order, double x); +double APS5_VABI yn_nid_postfix(int order, double x); +int APS5_VABI getw_nid_postfix(FileStream* stream); +int APS5_VABI putw_nid_postfix(int value, FileStream* stream); +void APS5_VABI abort_handler_s_nid_postfix(const char* message, void*, int); +void* APS5_VABI bsearch_s_nid_postfix(const void* key, const void* base, std::size_t count, std::size_t size, GuestContextCompare compare, void* context); +std::uint16_t* APS5_VABI wcstok_s_nid_postfix(std::uint16_t* str, std::size_t* remaining, const std::uint16_t* delimiters, std::uint16_t** context); +int APS5_VABI wctomb_s_nid_postfix(int* status, char* destination, std::size_t capacity, std::uint16_t wide); +int APS5_VABI wcrtomb_s_nid_postfix(std::size_t* result, char* destination, std::size_t capacity, std::uint16_t wide, void* state); +int APS5_VABI mbstowcs_s_nid_postfix(std::size_t* result, std::uint16_t* destination, std::size_t capacity, const char* source, std::size_t limit); +int APS5_VABI mbsrtowcs_s_nid_postfix(std::size_t* result, std::uint16_t* destination, std::size_t capacity, const char** source, std::size_t limit, void* state); +char* APS5_VABI gets_s_nid_postfix(char* buffer, std::size_t size); +void* APS5_VABI __cxa_demangle_nid_postfix(const char* mangled, char* buf, std::size_t* len, int* status); +int APS5_VABI __cxa_thread_atexit_impl_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dso); +int APS5_VABI __cxa_thread_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dso); +int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (APS5_VABI *destructor)(void*), void* object, void* moduleId); +int APS5_VABI _ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1__nid_postfix( int* flag, int (APS5_VABI *callback)(void*, void*, void**), void* arg ); +void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +void APS5_VABI _Atomic_store_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +void APS5_VABI _Atomic_store_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +void APS5_VABI _Atomic_store_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int); +std::uint8_t APS5_VABI _Atomic_load_1_nid_postfix(volatile std::uint8_t* target, int); +std::uint16_t APS5_VABI _Atomic_load_2_nid_postfix(volatile std::uint16_t* target, int); +std::uint64_t APS5_VABI _Atomic_load_8_nid_postfix(volatile std::uint64_t* target, int order); +std::uint8_t APS5_VABI _Atomic_exchange_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI _Atomic_exchange_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI _Atomic_exchange_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI _Atomic_exchange_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order); +int APS5_VABI _Atomic_compare_exchange_strong_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int); +int APS5_VABI _Atomic_compare_exchange_strong_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int); +int APS5_VABI _Atomic_compare_exchange_strong_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t* expected, std::uint32_t desired, int, int); +int APS5_VABI _Atomic_compare_exchange_strong_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int); +int APS5_VABI _Atomic_compare_exchange_weak_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int); +int APS5_VABI _Atomic_compare_exchange_weak_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int); +int APS5_VABI _Atomic_compare_exchange_weak_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int); +std::uint8_t APS5_VABI _Fetch_and_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value); +std::uint16_t APS5_VABI _Fetch_and_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value); +std::uint32_t APS5_VABI _Fetch_and_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value); +std::uint8_t APS5_VABI _Fetch_or_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value); +std::uint16_t APS5_VABI _Fetch_or_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value); +std::uint32_t APS5_VABI _Fetch_or_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value); +std::uint8_t APS5_VABI _Fetch_xor_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value); +std::uint16_t APS5_VABI _Fetch_xor_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value); +std::uint32_t APS5_VABI _Fetch_xor_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value); +std::uint8_t APS5_VABI _Atomic_fetch_add_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI _Atomic_fetch_add_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint64_t APS5_VABI _Atomic_fetch_add_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order); +std::uint8_t APS5_VABI _Atomic_fetch_sub_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI _Atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint64_t APS5_VABI _Atomic_fetch_sub_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order); +std::uint8_t APS5_VABI _Atomic_fetch_and_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI _Atomic_fetch_and_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI _Atomic_fetch_and_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI _Atomic_fetch_and_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order); +std::uint8_t APS5_VABI _Atomic_fetch_or_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI _Atomic_fetch_or_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI _Atomic_fetch_or_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI _Atomic_fetch_or_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order); +std::uint8_t APS5_VABI _Atomic_fetch_xor_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI _Atomic_fetch_xor_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI _Atomic_fetch_xor_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI _Atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order); +int APS5_VABI _Atomic_is_lock_free_1_nid_postfix(); +int APS5_VABI _Atomic_is_lock_free_2_nid_postfix(); +int APS5_VABI _Atomic_is_lock_free_4_nid_postfix(); +int APS5_VABI _Atomic_is_lock_free_8_nid_postfix(); +int APS5_VABI _Atomic_flag_test_and_set_nid_postfix(volatile unsigned char* flag, int); +void APS5_VABI _Atomic_flag_clear_nid_postfix(volatile unsigned char* flag, int); +void APS5_VABI _Atomic_thread_fence_nid_postfix(int order); +void APS5_VABI _Atomic_signal_fence_nid_postfix(int); +void APS5_VABI _Atomic_copy_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, const volatile void* source, int); +void APS5_VABI _Atomic_exchange_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, volatile void* value, int); +int APS5_VABI _Atomic_compare_exchange_strong_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired, int, int); +int APS5_VABI _Atomic_compare_exchange_weak_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired, int, int); +double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pten); +long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base); +double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant); +float APS5_VABI _FSin_nid_postfix(float x, unsigned int quadrant); +long double APS5_VABI _LSin_nid_postfix(long double x, unsigned int quadrant); +double APS5_VABI _Log_nid_postfix(double x, int base); +float APS5_VABI _FLog_nid_postfix(float x, int base); +long double APS5_VABI _LLog_nid_postfix(long double x, int base); +long double APS5_VABI _LDivide_nid_postfix(long double x, long double y); +long double APS5_VABI _LRecip_nid_postfix(long double x); +void* APS5_VABI opendir_nid_postfix(const char* path); +GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void* handle); +int APS5_VABI readdir_r_nid_postfix(void* handle, GuestDirectoryEntry* entry, GuestDirectoryEntry** result); +int APS5_VABI closedir_nid_postfix(void* handle); +void APS5_VABI rewinddir_nid_postfix(void* handle); +std::int64_t APS5_VABI telldir_nid_postfix(void* handle); +void APS5_VABI seekdir_nid_postfix(void* handle, std::int64_t location); +char* APS5_VABI getenv_nid_postfix(const char* name); +int APS5_VABI setenv_nid_postfix(const char* name, const char* value, int overwrite); +int APS5_VABI unsetenv_nid_postfix(const char* name); +int APS5_VABI putenv_nid_postfix(char* text); +char* APS5_VABI strerror_nid_postfix(int error); +int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length); +void APS5_VABI perror_nid_postfix(const char* prefix); +FileStream* APS5_VABI fdopen_nid_postfix(int descriptor, const char* mode); +FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, FileStream* stream); +int APS5_VABI freopen_s_nid_postfix(FileStream** result, const char* filename, const char* mode, FileStream* stream); +FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode); +int APS5_VABI fopen_s_nid_postfix(FileStream** result, const char* filename, const char* mode); +int APS5_VABI fclose_nid_postfix(FileStream* stream); +FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const char* filename, int mode, int protection); +size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, FileStream* stream); +size_t APS5_VABI fwrite_nid_postfix(const void* buffer, size_t size, size_t count, FileStream* stream); +int APS5_VABI fseeko_nid_postfix(FileStream* stream, std::int64_t offset, int origin); +std::int64_t APS5_VABI ftello_nid_postfix(FileStream* stream); +int APS5_VABI fseek_nid_postfix(FileStream* stream, std::int64_t offset, int origin); +std::int64_t APS5_VABI ftell_nid_postfix(FileStream* stream); +int APS5_VABI fgetpos_nid_postfix(FileStream* stream, std::int64_t* position); +int APS5_VABI fsetpos_nid_postfix(FileStream* stream, const std::int64_t* position); +void APS5_VABI rewind_nid_postfix(FileStream* stream); +int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream); +int APS5_VABI fflush_nid_postfix(FileStream* stream); +int APS5_VABI malloc_stats_fast_nid_postfix(void* stats); +size_t APS5_VABI malloc_usable_size_nid_postfix(void* pointer); +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 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); +void* APS5_VABI calloc_nid_postfix(size_t count, size_t size); +int APS5_VABI posix_memalign_nid_postfix(void** pointer, size_t alignment, size_t size); +void* APS5_VABI bsearch_nid_postfix(const void* key, const void* base, size_t count, size_t size, int (APS5_VABI *compare)(const void*, const void*)); +int APS5_VABI mergesort_nid_postfix(void* base, size_t count, size_t size, GuestCompare compare); +void APS5_VABI qsort_nid_postfix(void* base, size_t count, size_t size, int (APS5_VABI *compare)(const void*, const void*)); +int APS5_VABI qsort_s_nid_postfix(void* base, size_t count, size_t size, GuestContextCompare compare, void* context); +int APS5_VABI access_nid_postfix(const char* path, int mode); +int APS5_VABI remove_nid_postfix(const char* path); +double APS5_VABI __adddf3_nid_postfix(double a, double b); +float APS5_VABI __addsf3_nid_postfix(float a, float b); +double APS5_VABI __subdf3_nid_postfix(double a, double b); +float APS5_VABI __subsf3_nid_postfix(float a, float b); +double APS5_VABI __muldf3_nid_postfix(double a, double b); +float APS5_VABI __mulsf3_nid_postfix(float a, float b); +double APS5_VABI __divdf3_nid_postfix(double a, double b); +float APS5_VABI __divsf3_nid_postfix(float a, float b); +double APS5_VABI __negdf2_nid_postfix(double a); +float APS5_VABI __negsf2_nid_postfix(float a); +int APS5_VABI __eqdf2_nid_postfix(double a, double b); +int APS5_VABI __nedf2_nid_postfix(double a, double b); +int APS5_VABI __ltdf2_nid_postfix(double a, double b); +int APS5_VABI __ledf2_nid_postfix(double a, double b); +int APS5_VABI __gtdf2_nid_postfix(double a, double b); +int APS5_VABI __gedf2_nid_postfix(double a, double b); +int APS5_VABI __unorddf2_nid_postfix(double a, double b); +int APS5_VABI __eqsf2_nid_postfix(float a, float b); +int APS5_VABI __nesf2_nid_postfix(float a, float b); +int APS5_VABI __ltsf2_nid_postfix(float a, float b); +int APS5_VABI __lesf2_nid_postfix(float a, float b); +int APS5_VABI __gtsf2_nid_postfix(float a, float b); +int APS5_VABI __gesf2_nid_postfix(float a, float b); +int APS5_VABI __unordsf2_nid_postfix(float a, float b); +double APS5_VABI __extendsfdf2_nid_postfix(float a); +float APS5_VABI __truncdfsf2_nid_postfix(double a); +std::int32_t APS5_VABI __fixsfsi_nid_postfix(float a); +std::int64_t APS5_VABI __fixsfdi_nid_postfix(float a); +Int128 APS5_VABI __fixsfti_nid_postfix(float a); +std::int32_t APS5_VABI __fixdfsi_nid_postfix(double a); +std::int64_t APS5_VABI __fixdfdi_nid_postfix(double a); +Int128 APS5_VABI __fixdfti_nid_postfix(double a); +std::uint32_t APS5_VABI __fixunssfsi_nid_postfix(float a); +std::uint64_t APS5_VABI __fixunssfdi_nid_postfix(float a); +Uint128 APS5_VABI __fixunssfti_nid_postfix(float a); +std::uint32_t APS5_VABI __fixunsdfsi_nid_postfix(double a); +std::uint64_t APS5_VABI __fixunsdfdi_nid_postfix(double a); +Uint128 APS5_VABI __fixunsdfti_nid_postfix(double a); +std::int64_t APS5_VABI __fixxfdi_nid_postfix(long double a); +Int128 APS5_VABI __fixxfti_nid_postfix(long double a); +std::uint32_t APS5_VABI __fixunsxfsi_nid_postfix(long double a); +std::uint64_t APS5_VABI __fixunsxfdi_nid_postfix(long double a); +Uint128 APS5_VABI __fixunsxfti_nid_postfix(long double a); +double APS5_VABI __floatsidf_nid_postfix(std::int32_t a); +double APS5_VABI __floatunsidf_nid_postfix(std::uint32_t a); +float APS5_VABI __floatsisf_nid_postfix(std::int32_t a); +float APS5_VABI __floatunsisf_nid_postfix(std::uint32_t a); +double APS5_VABI __floatdidf_nid_postfix(std::int64_t a); +float APS5_VABI __floatdisf_nid_postfix(std::int64_t a); +long double APS5_VABI __floatdixf_nid_postfix(std::int64_t a); +double APS5_VABI __floatundidf_nid_postfix(std::uint64_t a); +float APS5_VABI __floatundisf_nid_postfix(std::uint64_t a); +long double APS5_VABI __floatundixf_nid_postfix(std::uint64_t a); +double APS5_VABI __floattidf_nid_postfix(Int128 a); +float APS5_VABI __floattisf_nid_postfix(Int128 a); +long double APS5_VABI __floattixf_nid_postfix(Int128 a); +double APS5_VABI __floatuntidf_nid_postfix(Uint128 a); +float APS5_VABI __floatuntisf_nid_postfix(Uint128 a); +long double APS5_VABI __floatuntixf_nid_postfix(Uint128 a); +__complex__ float APS5_VABI __mulsc3_nid_postfix(float a, float b, float c, float d); +__complex__ double APS5_VABI __muldc3_nid_postfix(double a, double b, double c, double d); +__complex__ long double APS5_VABI __mulxc3_nid_postfix(long double a, long double b, long double c, long double d); +__complex__ float APS5_VABI __divsc3_nid_postfix(float a, float b, float c, float d); +__complex__ double APS5_VABI __divdc3_nid_postfix(double a, double b, double c, double d); +__complex__ long double APS5_VABI __divxc3_nid_postfix(long double a, long double b, long double c, long double d); +long double APS5_VABI __powixf2_nid_postfix(long double base, int exponent); +int APS5_VABI fegetround_nid_postfix(); +int APS5_VABI fesetround_nid_postfix(int rounding); +int APS5_VABI fetestexcept_nid_postfix(int exceptions); +int APS5_VABI fegetexceptflag_nid_postfix(std::uint16_t* flags, int exceptions); +void APS5_VABI _Feraise_nid_postfix(int exceptions); +int APS5_VABI fesetexceptflag_nid_postfix(const std::uint16_t* flags, int exceptions); +int APS5_VABI feclearexcept_nid_postfix(int exceptions); +int APS5_VABI feenableexcept_nid_postfix(int exceptions); +int APS5_VABI fedisableexcept_nid_postfix(int exceptions); +int APS5_VABI __feenableexcept_nid_postfix(int exceptions); +int APS5_VABI __fedisableexcept_nid_postfix(int exceptions); +int APS5_VABI fegettrapenable_nid_postfix(); +int APS5_VABI fegetexcept_nid_postfix(); +int APS5_VABI fesettrapenable_nid_postfix(int exceptions); +int APS5_VABI vasprintf_nid_postfix(char** destination, const char* format, VaList* args); +int APS5_VABI vfprintf_nid_postfix(FileStream* stream, const char* format, VaList* args); +int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...); +int APS5_VABI fscanf_nid_postfix(FileStream* stream, const char* format, ...); +int APS5_VABI printf_nid_postfix(const char* format, ...); +int APS5_VABI libc_printf_nid_postfix(const char* format, ...); +int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...); +int APS5_VABI sprintf_nid_postfix(char* buffer, const char* format, ...); +int APS5_VABI vsscanf_nid_postfix(const char* input, const char* format, VaList* args); +int APS5_VABI sscanf_nid_postfix(const char* input, const char* format, ...); +int APS5_VABI sscanf_s_nid_postfix(const char* buffer, const char* format, ...); +int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c); +int APS5_VABI vsprintf_nid_postfix(char* str, const char* format, VaList* args); +int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, VaList* c); +int APS5_VABI vsnprintf_s_nid_postfix(char* buffer, size_t size, const char* format, VaList* args); +int APS5_VABI vsscanf_s_nid_postfix(const char* buffer, const char* format, VaList* args); +int APS5_VABI snprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...); +int APS5_VABI printf_s_nid_postfix(const char* format, ...); +int APS5_VABI vsprintf_s_nid_postfix(char* buffer, size_t size, const char* format, VaList* args); +int APS5_VABI sprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...); +int APS5_VABI puts_nid_postfix(const char* s); +int APS5_VABI vfscanf_nid_postfix(FileStream* stream, const char* format, VaList* args); +int APS5_VABI vscanf_nid_postfix(const char* format, VaList* args); +int APS5_VABI scanf_nid_postfix(const char* format, ...); +int APS5_VABI vfprintf_s_nid_postfix(FileStream* stream, const char* format, VaList* args); +int APS5_VABI vprintf_s_nid_postfix(const char* format, VaList* args); +int APS5_VABI fprintf_s_nid_postfix(FileStream* stream, const char* format, ...); +int APS5_VABI vfscanf_s_nid_postfix(FileStream* stream, const char* format, VaList* args); +int APS5_VABI vscanf_s_nid_postfix(const char* format, VaList* args); +int APS5_VABI fscanf_s_nid_postfix(FileStream* stream, const char* format, ...); +int APS5_VABI scanf_s_nid_postfix(const char* format, ...); +int APS5_VABI vswprintf_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, VaList* args); +int APS5_VABI swprintf_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, ...); +int APS5_VABI snwprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, ...); +int APS5_VABI vswprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, VaList* args); +int APS5_VABI swprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, ...); +int APS5_VABI vsnwprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, VaList* args); +int APS5_VABI wprintf_nid_postfix(const char16_t* format, ...); +int APS5_VABI vfwprintf_nid_postfix(FileStream* stream, const char16_t* format, VaList* args); +int APS5_VABI fwprintf_nid_postfix(FileStream* stream, const char16_t* format, ...); +int APS5_VABI vfwprintf_s_nid_postfix(FileStream* stream, const char16_t* format, VaList* args); +int APS5_VABI vwprintf_s_nid_postfix(const char16_t* format, VaList* args); +int APS5_VABI fwprintf_s_nid_postfix(FileStream* stream, const char16_t* format, ...); +int APS5_VABI wprintf_s_nid_postfix(const char16_t* format, ...); +int APS5_VABI vwprintf_nid_postfix(const char16_t* format, VaList* args); +int APS5_VABI fputwc_nid_postfix(char16_t value, FileStream* stream); +int APS5_VABI fputws_nid_postfix(const char16_t* str, FileStream* stream); +int APS5_VABI putwc_nid_postfix(char16_t value, FileStream* stream); +int APS5_VABI putwchar_nid_postfix(char16_t value); +char16_t* APS5_VABI fgetws_nid_postfix(char16_t* buffer, int size, FileStream* stream); +int APS5_VABI chdir_nid_postfix(const char* path); +char* APS5_VABI getcwd_nid_postfix(char* buffer, std::size_t size); +void APS5_VABI sceLibcInternalBacktraceForGame_nid_postfix(const char* heapName); +void APS5_VABI sceLibcInternalHeapErrorReportForGame_nid_postfix(void* heap, void* block, std::uint32_t error); +int APS5_VABI gethostname_nid_postfix(char* name, std::size_t length); +std::int64_t APS5_VABI __ashldi3_nid_postfix(std::int64_t value, int shift); +std::int64_t APS5_VABI __ashrdi3_nid_postfix(std::int64_t value, int shift); +std::int64_t APS5_VABI __lshrdi3_nid_postfix(std::int64_t value, int shift); +Int128 APS5_VABI __ashlti3_nid_postfix(Int128 value, int shift); +Int128 APS5_VABI __ashrti3_nid_postfix(Int128 value, int shift); +Int128 APS5_VABI __lshrti3_nid_postfix(Int128 value, int shift); +int APS5_VABI __clzsi2_nid_postfix(std::uint32_t value); +int APS5_VABI __clzdi2_nid_postfix(std::uint64_t value); +int APS5_VABI __clzti2_nid_postfix(Uint128 value); +int APS5_VABI __ctzsi2_nid_postfix(std::uint32_t value); +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); +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); +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); +int APS5_VABI __ucmpti2_nid_postfix(Uint128 a, Uint128 b); +std::int64_t APS5_VABI __negdi2_nid_postfix(std::int64_t value); +Int128 APS5_VABI __negti2_nid_postfix(Int128 value); +std::int64_t APS5_VABI __muldi3_nid_postfix(std::int64_t a, std::int64_t b); +Int128 APS5_VABI __multi3_nid_postfix(Int128 a, Int128 b); +std::int32_t APS5_VABI __divsi3_nid_postfix(std::int32_t a, std::int32_t b); +std::int64_t APS5_VABI __divdi3_nid_postfix(std::int64_t a, std::int64_t b); +std::int32_t APS5_VABI __modsi3_nid_postfix(std::int32_t a, std::int32_t b); +std::int64_t APS5_VABI __moddi3_nid_postfix(std::int64_t a, std::int64_t b); +std::uint32_t APS5_VABI __udivsi3_nid_postfix(std::uint32_t a, std::uint32_t b); +std::uint64_t APS5_VABI __udivdi3_nid_postfix(std::uint64_t a, std::uint64_t b); +std::uint32_t APS5_VABI __umodsi3_nid_postfix(std::uint32_t a, std::uint32_t b); +std::uint64_t APS5_VABI __umoddi3_nid_postfix(std::uint64_t a, std::uint64_t b); +std::int32_t APS5_VABI __divmodsi4_nid_postfix(std::int32_t a, std::int32_t b, std::int32_t* remainder); +std::int64_t APS5_VABI __divmoddi4_nid_postfix(std::int64_t a, std::int64_t b, std::int64_t* remainder); +std::uint32_t APS5_VABI __udivmodsi4_nid_postfix(std::uint32_t a, std::uint32_t b, std::uint32_t* remainder); +std::uint64_t APS5_VABI __udivmoddi4_nid_postfix(std::uint64_t a, std::uint64_t b, std::uint64_t* remainder); +Uint128 APS5_VABI __udivmodti4_nid_postfix(Uint128 a, Uint128 b, Uint128* remainder); +std::int32_t APS5_VABI __mulosi4_nid_postfix(std::int32_t a, std::int32_t b, int* overflow); +std::int64_t APS5_VABI __mulodi4_nid_postfix(std::int64_t a, std::int64_t b, int* overflow); +Int128 APS5_VABI __muloti4_nid_postfix(Int128 a, Int128 b, int* overflow); +std::int32_t APS5_VABI __mulvsi3_nid_postfix(std::int32_t a, std::int32_t b); +std::int64_t APS5_VABI __mulvdi3_nid_postfix(std::int64_t a, std::int64_t b); +Int128 APS5_VABI __mulvti3_nid_postfix(Int128 a, Int128 b); +std::int32_t APS5_VABI __addvsi3_nid_postfix(std::int32_t a, std::int32_t b); +std::int64_t APS5_VABI __addvdi3_nid_postfix(std::int64_t a, std::int64_t b); +Int128 APS5_VABI __addvti3_nid_postfix(Int128 a, Int128 b); +std::int32_t APS5_VABI __subvsi3_nid_postfix(std::int32_t a, std::int32_t b); +std::int64_t APS5_VABI __subvdi3_nid_postfix(std::int64_t a, std::int64_t b); +Int128 APS5_VABI __subvti3_nid_postfix(Int128 a, Int128 b); +std::int32_t APS5_VABI __negvsi2_nid_postfix(std::int32_t value); +std::int64_t APS5_VABI __negvdi2_nid_postfix(std::int64_t value); +Int128 APS5_VABI __negvti2_nid_postfix(Int128 value); +std::int32_t APS5_VABI __absvsi2_nid_postfix(std::int32_t value); +std::int64_t APS5_VABI __absvdi2_nid_postfix(std::int64_t value); +Int128 APS5_VABI __absvti2_nid_postfix(Int128 value); +int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list); +void APS5_VABI freeifaddrs_nid_postfix(GuestInterfaceAddress* list); +int APS5_VABI isupper_nid_postfix(int c); +int APS5_VABI islower_nid_postfix(int c); +int APS5_VABI isalpha_nid_postfix(int c); +int APS5_VABI isdigit_nid_postfix(int c); +int APS5_VABI isalnum_nid_postfix(int c); +int APS5_VABI isspace_nid_postfix(int c); +int APS5_VABI isblank_nid_postfix(int c); +int APS5_VABI iscntrl_nid_postfix(int c); +int APS5_VABI isprint_nid_postfix(int c); +int APS5_VABI isgraph_nid_postfix(int c); +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 toupper_nid_postfix(int c); +int APS5_VABI tolower_nid_postfix(int c); +void APS5_VABI _ZNSt8ios_baseD2Ev_nid_postfix(GuestLocale::IosBase* self); +GuestLocale::IosBase* APS5_VABI SetStreamWidth(GuestLocale::IosBase* stream, std::int32_t width); +StreamManipulator APS5_VABI _ZSt4setwi_nid_postfix(std::int32_t width); +void APS5_VABI _ZNSt8ios_base7_AddstdEPS__nid_postfix(GuestLocale::IosBase* self); +GuestLocale::Implementation* APS5_VABI _ZNSt6locale5_InitEv_nid_postfix(); +void APS5_VABI _ZNSt6locale5facet9_RegisterEv_nid_postfix(GuestLocale::Facet* self); +GuestLocale::Implementation* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_postfix(); +GuestLocaleFacade* APS5_VABI _ZNSt6locale7classicEv_nid_postfix(); +std::size_t APS5_VABI _ZNSt7collateIwE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const* locale); +std::size_t APS5_VABI _ZNSt7collateIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const* locale); +void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(GuestLocale::LocinfoStorage* self, const char* localeName); +void APS5_VABI _ZNSt8_LocinfoD1Ev_nid_postfix(GuestLocale::LocinfoStorage* self); +int APS5_VABI _Mbtowcx_nid_postfix(std::uint16_t* dst, const char* src, std::size_t count, mbstate_t* st); +int APS5_VABI _Wctombx_nid_postfix(char* dst, std::uint16_t src, mbstate_t* st); +const short* APS5_VABI _Getpctype_nid_postfix(); +const short* APS5_VABI _Getptolower_nid_postfix(); +const short* APS5_VABI _Getptoupper_nid_postfix(); +mbstate_t* APS5_VABI _Getpmbstate_nid_postfix(); +mbstate_t* APS5_VABI _Getpwcstate_nid_postfix(); +std::uint32_t APS5_VABI _Towctrans_nid_postfix(std::uint32_t c, std::uint64_t desc); +void APS5_VABI _ZNSt6locale7_LocimpC1ERKS0__nid_postfix(GuestLocale::Implementation* self, const GuestLocale::Implementation* source); +void APS5_VABI _ZNSt6locale7_Locimp7_AddfacEPNS_5facetEm_nid_postfix(GuestLocale::Implementation* self, GuestLocale::Facet* facet, std::size_t id); +void APS5_VABI _init_env_nid_postfix(); +void APS5_VABI init_env_nid_postfix(const InitEnvParams* params); +SonyLconv* APS5_VABI localeconv_nid_postfix(void); +std::lldiv_t APS5_VABI lldiv_nid_postfix(long long numerator, long long denominator); +std::lldiv_t APS5_VABI ldiv_nid_postfix(std::int64_t numerator, std::int64_t denominator); +std::lldiv_t APS5_VABI imaxdiv_nid_postfix(std::int64_t numerator, std::int64_t denominator); +std::int64_t APS5_VABI labs_nid_postfix(std::int64_t x); +std::int64_t APS5_VABI llabs_nid_postfix(std::int64_t x); +std::int64_t APS5_VABI imaxabs_nid_postfix(std::int64_t x); +float APS5_VABI fmodf_nid_postfix(float x, float y); +float APS5_VABI asinf_nid_postfix(float x); +float APS5_VABI acosf_nid_postfix(float x); +float APS5_VABI atan2f_nid_postfix(float y, float x); +float APS5_VABI hypotf_nid_postfix(float x, float y); +double APS5_VABI hypot_nid_postfix(double x, double y); +float APS5_VABI log10f_nid_postfix(float x); +float APS5_VABI logbf_nid_postfix(float x); +double APS5_VABI exp2_nid_postfix(double x); +double APS5_VABI ldexp_nid_postfix(double x, int exponent); +double APS5_VABI scalbn_nid_postfix(double x, int exponent); +float APS5_VABI scalbnf_nid_postfix(float x, int exponent); +long double APS5_VABI scalbnl_nid_postfix(long double x, int exponent); +float APS5_VABI significandf_nid_postfix(float x); +double APS5_VABI significand_nid_postfix(double x); +double APS5_VABI frexp_nid_postfix(double x, int* exponent); +float APS5_VABI frexpf_nid_postfix(float x, int* exponent); +std::int64_t APS5_VABI lround_nid_postfix(double x); +std::int64_t APS5_VABI lroundf_nid_postfix(float x); +std::int64_t APS5_VABI llround_nid_postfix(double x); +std::int64_t APS5_VABI llroundf_nid_postfix(float x); +std::int64_t APS5_VABI llroundl_nid_postfix(long double x); +int APS5_VABI __isfinitef_nid_postfix(float x); +int APS5_VABI __isnormal_nid_postfix(double x); +int APS5_VABI __isnormalf_nid_postfix(float x); +int APS5_VABI __isinff_nid_postfix(float x); +float APS5_VABI nearbyintf_nid_postfix(float x); +long double APS5_VABI nearbyintl_nid_postfix(long double x); +double APS5_VABI nearbyint_nid_postfix(double x); +double APS5_VABI cbrt_nid_postfix(double x); +double APS5_VABI asin_nid_postfix(double x); +double APS5_VABI acos_nid_postfix(double x); +double APS5_VABI sqrt_nid_postfix(double x); +float APS5_VABI sqrtf_nid_postfix(float x); +double APS5_VABI fabs_nid_postfix(double x); +float APS5_VABI fabsf_nid_postfix(float x); +long double APS5_VABI fabsl_nid_postfix(long double x); +double APS5_VABI copysign_nid_postfix(double x, double y); +float APS5_VABI copysignf_nid_postfix(float x, float y); +int APS5_VABI abs_nid_postfix(int x); +double APS5_VABI trunc_nid_postfix(double x); +double APS5_VABI ceil_nid_postfix(double x); +double APS5_VABI floor_nid_postfix(double x); +float APS5_VABI truncf_nid_postfix(float x); +float APS5_VABI ceilf_nid_postfix(float x); +float APS5_VABI floorf_nid_postfix(float x); +double APS5_VABI fma_nid_postfix(double x, double y, double z); +float APS5_VABI fmaf_nid_postfix(float x, float y, float z); +double APS5_VABI fmin_nid_postfix(double x, double y); +float APS5_VABI fminf_nid_postfix(float x, float y); +long double APS5_VABI fminl_nid_postfix(long double x, long double y); +double APS5_VABI fmax_nid_postfix(double x, double y); +float APS5_VABI fmaxf_nid_postfix(float x, float y); +long double APS5_VABI fmaxl_nid_postfix(long double x, long double y); +double APS5_VABI fdim_nid_postfix(double x, double y); +float APS5_VABI fdimf_nid_postfix(float x, float y); +long double APS5_VABI acosl_nid_postfix(long double x); +long double APS5_VABI acoshl_nid_postfix(long double x); +long double APS5_VABI asinhl_nid_postfix(long double x); +long double APS5_VABI atanhl_nid_postfix(long double x); +long double APS5_VABI atan2l_nid_postfix(long double y, long double x); +long double APS5_VABI cbrtl_nid_postfix(long double x); +long double APS5_VABI erfl_nid_postfix(long double x); +long double APS5_VABI erfcl_nid_postfix(long double x); +long double APS5_VABI expm1l_nid_postfix(long double x); +long double APS5_VABI log1pl_nid_postfix(long double x); +long double APS5_VABI log2l_nid_postfix(long double x); +long double APS5_VABI lgammal_nid_postfix(long double x); +long double APS5_VABI tgammal_nid_postfix(long double x); +float APS5_VABI tgammaf_nid_postfix(float x); +long double APS5_VABI hypotl_nid_postfix(long double x, long double y); +float APS5_VABI hypot3f_nid_postfix(float x, float y, float z); +double APS5_VABI hypot3_nid_postfix(double x, double y, double z); +long double APS5_VABI hypot3l_nid_postfix(long double x, long double y, long double z); +float APS5_VABI j0f_nid_postfix(float x); +float APS5_VABI j1f_nid_postfix(float x); +float APS5_VABI jnf_nid_postfix(int order, float x); +float APS5_VABI y0f_nid_postfix(float x); +float APS5_VABI y1f_nid_postfix(float x); +float APS5_VABI ynf_nid_postfix(int order, float x); +long double APS5_VABI asinl_nid_postfix(long double x); +long double APS5_VABI frexpl_nid_postfix(long double x, int* exponent); +double APS5_VABI exp_nid_postfix(double x); +double APS5_VABI atan_nid_postfix(double x); +double APS5_VABI tan_nid_postfix(double x); +double APS5_VABI log2_nid_postfix(double x); +double APS5_VABI log_nid_postfix(double x); +double APS5_VABI log1p_nid_postfix(double x); +float APS5_VABI log1pf_nid_postfix(float x); +double APS5_VABI expm1_nid_postfix(double x); +float APS5_VABI expm1f_nid_postfix(float x); +float APS5_VABI erff_nid_postfix(float x); +double APS5_VABI erf_nid_postfix(double x); +double APS5_VABI erfc_nid_postfix(double x); +float APS5_VABI erfcf_nid_postfix(float x); +double APS5_VABI lgamma_nid_postfix(double x); +float APS5_VABI sinf_nid_postfix(float x); +float APS5_VABI cosf_nid_postfix(float x); +float APS5_VABI tanf_nid_postfix(float x); +void APS5_VABI sincosf_nid_postfix(float x, float* sinp, float* cosp); +double APS5_VABI sin_nid_postfix(double x); +double APS5_VABI cos_nid_postfix(double x); +double APS5_VABI sinh_nid_postfix(double x); +double APS5_VABI cosh_nid_postfix(double x); +float APS5_VABI coshf_nid_postfix(float x); +float APS5_VABI sinhf_nid_postfix(float x); +double APS5_VABI asinh_nid_postfix(double x); +float APS5_VABI asinhf_nid_postfix(float x); +double APS5_VABI acosh_nid_postfix(double x); +float APS5_VABI acoshf_nid_postfix(float x); +double APS5_VABI atanh_nid_postfix(double x); +float APS5_VABI atanhf_nid_postfix(float x); +void APS5_VABI sincos_nid_postfix(double x, double* sinp, double* cosp); +float APS5_VABI atanf_nid_postfix(float x); +double APS5_VABI atan2_nid_postfix(double y, double x); +float APS5_VABI powf_nid_postfix(float base, float exp); +double APS5_VABI pow_nid_postfix(double base, double exp); +float APS5_VABI __powisf2_nid_postfix(float base, int exponent); +double APS5_VABI __powidf2_nid_postfix(double base, int exponent); +float APS5_VABI expf_nid_postfix(float x); +float APS5_VABI exp2f_nid_postfix(float x); +short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset); +short APS5_VABI _FExp_nid_postfix(float* value, float y, short offset); +float APS5_VABI logf_nid_postfix(float x); +float APS5_VABI log2f_nid_postfix(float x); +double APS5_VABI log10_nid_postfix(double x); +float APS5_VABI ldexpf_nid_postfix(float x, int exp); +double APS5_VABI fmod_nid_postfix(double x, double y); +float APS5_VABI roundf_nid_postfix(float x); +double APS5_VABI round_nid_postfix(double x); +float APS5_VABI cbrtf_nid_postfix(float x); +float APS5_VABI remainderf_nid_postfix(float x, float y); +double APS5_VABI _Divide_nid_postfix(double x, double y); +float APS5_VABI _FDivide_nid_postfix(float x, float y); +double APS5_VABI _Recip_nid_postfix(double x); +float APS5_VABI _FRecip_nid_postfix(float x); +float APS5_VABI nextafterf_nid_postfix(float x, float y); +double APS5_VABI nextafter_nid_postfix(double x, double y); +int APS5_VABI __isfinite_nid_postfix(double x); +int APS5_VABI __isnan_nid_postfix(double x); +int APS5_VABI __isinf_nid_postfix(double x); +int APS5_VABI __signbit_nid_postfix(double x); +float APS5_VABI nanf_nid_postfix(const char* tagp); +double APS5_VABI nan_nid_postfix(const char* tagp); +double APS5_VABI modf_nid_postfix(double x, double* integral); +float APS5_VABI modff_nid_postfix(float x, float* integral); +double APS5_VABI tanh_nid_postfix(double x); +float APS5_VABI tanhf_nid_postfix(float x); +long double APS5_VABI atanl_nid_postfix(long double x); +long double APS5_VABI sinl_nid_postfix(long double x); +long double APS5_VABI cosl_nid_postfix(long double x); +long double APS5_VABI tanl_nid_postfix(long double x); +long double APS5_VABI sinhl_nid_postfix(long double x); +long double APS5_VABI coshl_nid_postfix(long double x); +long double APS5_VABI tanhl_nid_postfix(long double x); +long double APS5_VABI expl_nid_postfix(long double x); +long double APS5_VABI exp2l_nid_postfix(long double x); +long double APS5_VABI logl_nid_postfix(long double x); +long double APS5_VABI log10l_nid_postfix(long double x); +long double APS5_VABI powl_nid_postfix(long double base, long double exp); +long double APS5_VABI ldexpl_nid_postfix(long double x, int exp); +long double APS5_VABI truncl_nid_postfix(long double x); +long double APS5_VABI ceill_nid_postfix(long double x); +long double APS5_VABI floorl_nid_postfix(long double x); +double APS5_VABI logb_nid_postfix(double x); +int APS5_VABI ilogb_nid_postfix(double x); +int APS5_VABI ilogbf_nid_postfix(float x); +double APS5_VABI remainder_nid_postfix(double x, double y); +double APS5_VABI remquo_nid_postfix(double x, double y, int* quo); +float APS5_VABI remquof_nid_postfix(float x, float y, int* quo); +float APS5_VABI _FSinh_nid_postfix(float x, float y); +float APS5_VABI _FCosh_nid_postfix(float x, float y); +short APS5_VABI _FDtest_nid_postfix(const float* value); +short APS5_VABI _Dtest_nid_postfix(const double* value); +int APS5_VABI __isnanf_nid_postfix(float x); +int APS5_VABI isnanf_nid_postfix(float x); +int APS5_VABI isnan_nid_postfix(double x); +int APS5_VABI isinf_nid_postfix(double x); +int APS5_VABI __fpclassifyf_nid_postfix(float x); +int APS5_VABI __signbitf_nid_postfix(float x); +int APS5_VABI __signbitl_nid_postfix(long double x); +int APS5_VABI __isfinitel_nid_postfix(long double x); +int APS5_VABI rand_nid_postfix(); +int APS5_VABI rand_r_nid_postfix(unsigned int* state); +void APS5_VABI srand_nid_postfix(unsigned int seed); +int APS5_VABI __fpclassifyd_nid_postfix(double value); +double APS5_VABI _Sinh_nid_postfix(double x, double y); +double APS5_VABI _Cosh_nid_postfix(double x, double y); +int APS5_VABI _Fltrounds_nid_postfix(void); +int APS5_VABI fegetenv_nid_postfix(GuestFloatingPointEnvironment* environment); +long double APS5_VABI copysignl_nid_postfix(long double x, long double y); +long double APS5_VABI fdiml_nid_postfix(long double x, long double y); +long double APS5_VABI fmodl_nid_postfix(long double x, long double y); +long double APS5_VABI remainderl_nid_postfix(long double x, long double y); +long double APS5_VABI remquol_nid_postfix(long double x, long double y, int* quotient); +long double APS5_VABI logbl_nid_postfix(long double x); +int APS5_VABI ilogbl_nid_postfix(long double x); +long double APS5_VABI rintl_nid_postfix(long double x); +long double APS5_VABI roundl_nid_postfix(long double x); +std::int64_t APS5_VABI lroundl_nid_postfix(long double x); +std::int64_t APS5_VABI lrintl_nid_postfix(long double x); +long long APS5_VABI llrintl_nid_postfix(long double x); +long double APS5_VABI sqrtl_nid_postfix(long double x); +long double APS5_VABI modfl_nid_postfix(long double x, long double* integral); +long double APS5_VABI nanl_nid_postfix(const char*); +long double APS5_VABI scalblnl_nid_postfix(long double x, std::int64_t exponent); +long double APS5_VABI fmal_nid_postfix(long double x, long double y, long double z); +double APS5_VABI rint_nid_postfix(double x); +float APS5_VABI rintf_nid_postfix(float x); +std::int64_t APS5_VABI lrint_nid_postfix(double x); +std::int64_t APS5_VABI lrintf_nid_postfix(float x); +long long APS5_VABI llrint_nid_postfix(double x); +long long APS5_VABI llrintf_nid_postfix(float x); +double APS5_VABI drem_nid_postfix(double x, double y); +float APS5_VABI dremf_nid_postfix(float x, float y); +double APS5_VABI scalbln_nid_postfix(double x, std::int64_t exponent); +float APS5_VABI scalblnf_nid_postfix(float x, std::int64_t exponent); +double APS5_VABI scalb_nid_postfix(double x, double exponent); +float APS5_VABI scalbf_nid_postfix(float x, float exponent); +int APS5_VABI finite_nid_postfix(double x); +int APS5_VABI finitef_nid_postfix(float x); +int APS5_VABI __fpclassifyl_nid_postfix(long double x); +int APS5_VABI __isinfl_nid_postfix(long double x); +int APS5_VABI __isnanl_nid_postfix(long double x); +int APS5_VABI __isnormall_nid_postfix(long double x); +short APS5_VABI _LDtest_nid_postfix(const long double* value); +int APS5_VABI _Dclass_nid_postfix(double x); +int APS5_VABI _FDclass_nid_postfix(float x); +int APS5_VABI _LDclass_nid_postfix(long double x); +short APS5_VABI _LDsign_nid_postfix(long double x); +void* APS5_VABI sceLibcMspaceCreate_nid_postfix(const char* name, void* base, std::size_t size, unsigned flags); +int APS5_VABI sceLibcMspaceDestroy_nid_postfix(void* handle); +void* APS5_VABI sceLibcMspaceMalloc_nid_postfix(void* handle, std::size_t size); +void APS5_VABI sceLibcMspaceFree_nid_postfix(void* handle, void* pointer); +void* APS5_VABI sceLibcMspaceCalloc_nid_postfix(void* handle, std::size_t count, std::size_t size); +void* APS5_VABI sceLibcMspaceRealloc_nid_postfix(void* handle, void* pointer, std::size_t size); +int APS5_VABI sceLibcMspacePosixMemalign_nid_postfix(void* handle, void** result, std::size_t alignment, std::size_t size); +void* APS5_VABI sceLibcMspaceMemalign_nid_postfix(void* handle, std::size_t alignment, std::size_t size); +void* APS5_VABI sceLibcMspaceAlignedAlloc_nid_postfix(void* handle, std::size_t alignment, std::size_t size); +void* APS5_VABI sceLibcMspaceReallocalign_nid_postfix(void* handle, void* pointer, std::size_t alignment, std::size_t size); +int APS5_VABI sceLibcMspaceMallocStats_nid_postfix(void* handle, MallocManagedSize* stats); +int APS5_VABI sceLibcMspaceMallocStatsFast_nid_postfix(void* handle, MallocManagedSize* stats); +std::size_t APS5_VABI sceLibcMspaceMallocUsableSize_nid_postfix(const void* pointer); +void* APS5_VABI sceLibcPafMspaceCreate_nid_postfix(const char* name, void* base, std::size_t size, unsigned flags, const void* options); +int APS5_VABI sceLibcPafMspaceDestroy_nid_postfix(void* handle); +void* APS5_VABI sceLibcPafMspaceMalloc_nid_postfix(void* handle, std::size_t size, const void* owner); +void* APS5_VABI sceLibcPafMspaceMemalign_nid_postfix(void* handle, std::size_t alignment, std::size_t size, const void* owner); +void APS5_VABI sceLibcPafMspaceFree_nid_postfix(void* handle, void* pointer, const void* owner); +void* APS5_VABI sceLibcPafMspaceCalloc_nid_postfix(void* handle, std::size_t count, std::size_t size, const void* owner); +void* APS5_VABI sceLibcPafMspaceRealloc_nid_postfix(void* handle, void* pointer, std::size_t size, const void* owner); +void* APS5_VABI sceLibcPafMspaceReallocalign_nid_postfix(void* handle, void* pointer, std::size_t alignment, std::size_t size, const void* owner); +int APS5_VABI sceLibcPafMspacePosixMemalign_nid_postfix(void* handle, void** result, std::size_t alignment, std::size_t size, const void* owner); +int APS5_VABI sceLibcPafMspaceMallocStats_nid_postfix(void* handle, MallocManagedSize* stats); +int APS5_VABI sceLibcPafMspaceMallocStatsFast_nid_postfix(void* handle, MallocManagedSize* stats); +int APS5_VABI ___mb_cur_max_nid_postfix(); +int APS5_VABI _Getmbcurmax_nid_postfix(); +int APS5_VABI mbsinit_nid_postfix(const void* state); +std::size_t APS5_VABI mbrtowc_nid_postfix(std::uint16_t* destination, const char* source, std::size_t count, void* state); +std::size_t APS5_VABI mbrlen_nid_postfix(const char* source, std::size_t count, void* state); +int APS5_VABI mbtowc_nid_postfix(std::uint16_t* destination, const char* source, std::size_t count); +std::size_t APS5_VABI wcrtomb_nid_postfix(char* destination, std::uint16_t value, void*); +int APS5_VABI mblen_nid_postfix(const char* source, std::size_t count); +int APS5_VABI wctomb_nid_postfix(char* destination, std::uint16_t value); +std::size_t APS5_VABI c32rtomb_nid_postfix(char* destination, std::uint32_t value, void*); +std::size_t APS5_VABI c16rtomb_nid_postfix(char* destination, std::uint16_t unit, void* state); +std::size_t APS5_VABI mbrtoc32_nid_postfix(std::uint32_t* destination, const char* source, std::size_t count, void* state); +std::size_t APS5_VABI mbrtoc16_nid_postfix(std::uint16_t* destination, const char* source, std::size_t count, void* state); +std::size_t APS5_VABI wcsrtombs_nid_postfix(char* destination, const std::uint16_t** source, std::size_t capacity, void*); +int APS5_VABI wcsrtombs_s_nid_postfix( std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t** source, std::size_t limit, void* state ); +int APS5_VABI wcstombs_s_nid_postfix( std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t* source, std::size_t limit ); +std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const char** source, std::size_t capacity, void* state); +std::size_t APS5_VABI mbstowcs_nid_postfix(std::uint16_t* dest, const char* src, std::size_t size); +void APS5_VABI exit_nid_postfix(int code); +int* APS5_VABI libc_error_nid_postfix(); +int* APS5_VABI __error_nid_postfix(); +int APS5_VABI atexit_nid_postfix(GuestExitCallback func); +int APS5_VABI at_quick_exit_nid_postfix(GuestExitCallback func); +int APS5_VABI daemon_nid_postfix(int noChdir, int noClose); +double APS5_VABI erand48_nid_postfix(std::uint16_t* seed); +std::int64_t APS5_VABI nrand48_nid_postfix(std::uint16_t* seed); +std::int64_t APS5_VABI jrand48_nid_postfix(std::uint16_t* seed); +double APS5_VABI drand48_nid_postfix(); +std::int64_t APS5_VABI lrand48_nid_postfix(); +std::int64_t APS5_VABI mrand48_nid_postfix(); +void APS5_VABI srand48_nid_postfix(std::int64_t seedval); +std::uint16_t* APS5_VABI seed48_nid_postfix(const std::uint16_t* seed); +void APS5_VABI lcong48_nid_postfix(const std::uint16_t* parameters); +std::int64_t APS5_VABI random_nid_postfix(); +void APS5_VABI srandom_nid_postfix(std::uint64_t seed); +char* APS5_VABI initstate_nid_postfix(std::uint64_t seed, char* state, std::int64_t size); +char* APS5_VABI setstate_nid_postfix(char* state); +char* APS5_VABI realpath_nid_postfix(const char* path, char* resolved); +int APS5_VABI rename_nid_postfix(const char* from, const char* to); +int APS5_VABI iswalnum_nid_postfix(std::uint32_t c); +int APS5_VABI iswalpha_nid_postfix(std::uint32_t c); +int APS5_VABI iswblank_nid_postfix(std::uint32_t c); +int APS5_VABI iswcntrl_nid_postfix(std::uint32_t c); +int APS5_VABI iswdigit_nid_postfix(std::uint32_t c); +int APS5_VABI iswgraph_nid_postfix(std::uint32_t c); +int APS5_VABI iswlower_nid_postfix(std::uint32_t c); +int APS5_VABI iswprint_nid_postfix(std::uint32_t c); +int APS5_VABI iswpunct_nid_postfix(std::uint32_t c); +int APS5_VABI iswspace_nid_postfix(std::uint32_t c); +int APS5_VABI iswupper_nid_postfix(std::uint32_t c); +int APS5_VABI iswxdigit_nid_postfix(std::uint32_t c); +int APS5_VABI iswascii_nid_postfix(std::uint32_t c); +int APS5_VABI iswctype_nid_postfix(std::uint32_t c, std::uint64_t mask); +int APS5_VABI _Iswctype_nid_postfix(std::uint32_t c, std::uint64_t type); +std::uint64_t APS5_VABI wctype_nid_postfix(const char* property); +std::uint32_t APS5_VABI towlower_nid_postfix(std::uint32_t c); +std::uint32_t APS5_VABI towupper_nid_postfix(std::uint32_t c); +std::uint32_t APS5_VABI btowc_nid_postfix(int c); +int APS5_VABI wctob_nid_postfix(std::uint32_t c); +std::uint64_t APS5_VABI wctrans_nid_postfix(const char* property); +std::uint32_t APS5_VABI towctrans_nid_postfix(std::uint32_t c, std::uint64_t desc); +std::uint32_t APS5_VABI _Towlower_nid_postfix(std::uint32_t c, const void*); +std::uint32_t APS5_VABI _Towupper_nid_postfix(std::uint32_t c, const void*); +int APS5_VABI _Tolower_nid_postfix(int c, const void*); +int APS5_VABI _Toupper_nid_postfix(int c, const void*); +int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dsoHandle); +int APS5_VABI cxa_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dsoHandle); +void APS5_VABI cxa_finalize_nid_postfix(void* dsoHandle); +void APS5_VABI _Vacopy_nid_postfix(unsigned char* destination, const unsigned char* source); +unsigned int APS5_VABI _Atomic_fetch_add_4_nid_postfix(volatile unsigned int* target, unsigned int value, int memoryOrder); +unsigned int APS5_VABI _Atomic_fetch_sub_4_nid_postfix(volatile unsigned int* target, unsigned int value, int memoryOrder); +int APS5_VABI _Atomic_compare_exchange_weak_4_nid_postfix(volatile unsigned int* target, unsigned int* expected, unsigned int desired, int successOrder, int failureOrder); +unsigned int APS5_VABI _Atomic_load_4_nid_postfix(volatile unsigned int* target, int memoryOrder); +std::intmax_t APS5_VABI strtoimax_nid_postfix(const char* str, char** endptr, int base); +std::uintmax_t APS5_VABI strtoumax_nid_postfix(const char* str, char** endptr, int base); +unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix(); +void APS5_VABI _Lockfilelock_nid_postfix(FileStream* stream); +void APS5_VABI _Unlockfilelock_nid_postfix(FileStream* stream); +void APS5_VABI flockfile_nid_postfix(FileStream* stream); +void APS5_VABI funlockfile_nid_postfix(FileStream* stream); +int APS5_VABI ftrylockfile_nid_postfix(FileStream* stream); +std::uint64_t APS5_VABI _Stoul_nid_postfix(const char* str, char** endptr, int base); +void APS5_VABI _Locksyslock_nid_postfix(); +void APS5_VABI _Unlocksyslock_nid_postfix(); +__int128 APS5_VABI __divti3_nid_postfix(__int128 dividend, __int128 divisor); +unsigned __int128 APS5_VABI __udivti3_nid_postfix(unsigned __int128 dividend, unsigned __int128 divisor); +unsigned __int128 APS5_VABI __umodti3_nid_postfix(unsigned __int128 dividend, unsigned __int128 divisor); +__int128 APS5_VABI __modti3_nid_postfix(__int128 dividend, __int128 divisor); +int APS5_VABI swscanf_nid_postfix(const char16_t* buffer, const char16_t* format, ...); +int APS5_VABI swscanf_s_nid_postfix(const char16_t* buffer, const char16_t* format, ...); +int APS5_VABI vswscanf_nid_postfix(const char16_t* buffer, const char16_t* format, VaList* args); +int APS5_VABI vswscanf_s_nid_postfix(const char16_t* buffer, const char16_t* format, VaList* args); +int APS5_VABI vfwscanf_nid_postfix(FileStream* stream, const char16_t* format, VaList* args); +int APS5_VABI vfwscanf_s_nid_postfix(FileStream* stream, const char16_t* format, VaList* args); +int APS5_VABI vwscanf_nid_postfix(const char16_t* format, VaList* args); +int APS5_VABI vwscanf_s_nid_postfix(const char16_t* format, VaList* args); +int APS5_VABI fwscanf_nid_postfix(FileStream* stream, const char16_t* format, ...); +int APS5_VABI fwscanf_s_nid_postfix(FileStream* stream, const char16_t* format, ...); +int APS5_VABI wscanf_nid_postfix(const char16_t* format, ...); +int APS5_VABI wscanf_s_nid_postfix(const char16_t* format, ...); +int APS5_VABI fgetc_nid_postfix(FileStream* stream); +std::int32_t APS5_VABI fgetwc_nid_postfix(FileStream* stream); +std::int32_t APS5_VABI ungetwc_nid_postfix(std::int32_t value, FileStream* stream); +int APS5_VABI getc_nid_postfix(FileStream* stream); +int APS5_VABI getc_unlocked_nid_postfix(FileStream* stream); +int APS5_VABI __srget_nid_postfix(FileStream* stream); +int APS5_VABI getchar_nid_postfix(); +char* APS5_VABI gets_nid_postfix(char* buffer); +std::int32_t APS5_VABI getwc_nid_postfix(FileStream* stream); +std::int32_t APS5_VABI getwchar_nid_postfix(); +int APS5_VABI getchar_unlocked_nid_postfix(); +int APS5_VABI fputc_nid_postfix(int value, FileStream* stream); +int APS5_VABI putc_nid_postfix(int value, FileStream* stream); +int APS5_VABI putc_unlocked_nid_postfix(int value, FileStream* stream); +int APS5_VABI __swbuf_nid_postfix(int value, FileStream* stream); +int APS5_VABI putchar_nid_postfix(int value); +int APS5_VABI putchar_unlocked_nid_postfix(int value); +int APS5_VABI ungetc_nid_postfix(int value, FileStream* stream); +char* APS5_VABI fgets_nid_postfix(char* buffer, int size, FileStream* stream); +int APS5_VABI feof_nid_postfix(FileStream* stream); +int APS5_VABI ferror_nid_postfix(FileStream* stream); +int APS5_VABI feof_unlocked_nid_postfix(FileStream* stream); +int APS5_VABI ferror_unlocked_nid_postfix(FileStream* stream); +void APS5_VABI clearerr_nid_postfix(FileStream* stream); +int APS5_VABI fileno_nid_postfix(FileStream* stream); +void APS5_VABI clearerr_unlocked_nid_postfix(FileStream* stream); +int APS5_VABI fileno_unlocked_nid_postfix(FileStream* stream); +int APS5_VABI setvbuf_nid_postfix(FileStream* stream, char* buffer, int mode, std::size_t size); +void APS5_VABI setbuf_nid_postfix(FileStream* stream, char* buffer); +const char* APS5_VABI setlocale_nid_postfix(int, const char* locale); +GuestNewHandler APS5_VABI _ZSt15set_new_handlerPFvvE_nid_postfix(GuestNewHandler handler); +GuestNewHandler APS5_VABI _ZSt15get_new_handlerv_nid_postfix(); +void* APS5_VABI _Znwm_nid_postfix(std::size_t size); +void* APS5_VABI _Znam_nid_postfix(std::size_t size); +void* APS5_VABI _ZnwmSt11align_val_t_nid_postfix(std::size_t size, std::size_t alignment); +void* APS5_VABI _ZnwmRKSt9nothrow_t_nid_postfix(std::size_t size, const void*) noexcept; +void* APS5_VABI _ZnamRKSt9nothrow_t_nid_postfix(std::size_t size, const void*) noexcept; +void APS5_VABI _ZdlPv_nid_postfix(void* pointer); +void APS5_VABI _ZdlPvRKSt9nothrow_t_nid_postfix(void* pointer, const void*) noexcept; +void APS5_VABI _ZdaPv_nid_postfix(void* pointer); +void APS5_VABI _ZdlPvm_nid_postfix(void* pointer, std::size_t); +void APS5_VABI _ZdaPvm_nid_postfix(void* pointer, std::size_t); +void APS5_VABI _ZdlPvSt11align_val_t_nid_postfix(void* pointer, std::size_t alignment); +void APS5_VABI _ZdlPvmSt11align_val_t_nid_postfix(void* pointer, std::size_t, std::size_t alignment); +long long APS5_VABI strtonum_nid_postfix(const char* text, long long minimum, long long maximum, const char** error); +char* APS5_VABI basename_r_nid_postfix(const char* path, char* buffer); +char* APS5_VABI basename_nid_postfix(const char* path); +char* APS5_VABI __inet_ntoa_nid_postfix(unsigned int address); +char* APS5_VABI dirname_nid_postfix(const char* path); +std::size_t APS5_VABI strnlen_nid_postfix(const char* text, std::size_t limit); +std::size_t APS5_VABI strnlen_s_nid_postfix(const char* text, std::size_t limit); +char* APS5_VABI strsep_nid_postfix(char** text, const char* delimiters); +char* APS5_VABI strncat_nid_postfix(char* destination, const char* source, std::size_t limit); +char* APS5_VABI strpbrk_nid_postfix(const char* text, const char* accept); +std::size_t APS5_VABI strcspn_nid_postfix(const char* text, const char* reject); +std::size_t APS5_VABI strlcat_nid_postfix(char* destination, const char* source, std::size_t capacity); +char* APS5_VABI stpcpy_nid_postfix(char* destination, const char* source); +char* APS5_VABI strtok_r_nid_postfix(char* text, const char* delimiters, char** state); +char* APS5_VABI strtok_nid_postfix(char* text, const char* delimiters); +int APS5_VABI __inet_aton_nid_postfix(const char* text, void* address); +std::uint32_t APS5_VABI __inet_addr_nid_postfix(const char* text); +void* APS5_VABI memset_nid_postfix(void* s, int c, size_t n); +void APS5_VABI bzero_nid_postfix(void* destination, size_t count); +void APS5_VABI bcopy_nid_postfix(const void* source, void* destination, size_t count); +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); +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); +void* APS5_VABI memmem_nid_postfix(const void* haystack, size_t haystacklen, const void* needle, size_t needlelen); +int APS5_VABI strcmp_nid_postfix(const char* s1, const char* s2); +int APS5_VABI strncmp_nid_postfix(const char* s1, const char* s2, size_t n); +size_t APS5_VABI strlen_nid_postfix(const char* s); +char* APS5_VABI strcpy_nid_postfix(char* dest, const char* src); +char* APS5_VABI strncpy_nid_postfix(char* dest, const char* src, size_t count); +char* APS5_VABI strcat_nid_postfix(char* dest, const char* src); +const char* APS5_VABI strchr_nid_postfix(const char* s, int c); +char* APS5_VABI index_nid_postfix(const char* s, int c); +char* APS5_VABI strrchr_nid_postfix(const char* s, int c); +char* APS5_VABI rindex_nid_postfix(const char* s, int c); +int APS5_VABI flsl_nid_postfix(std::int64_t mask); +char* APS5_VABI strstr_nid_postfix(const char* haystack, const char* needle); +size_t APS5_VABI strlcpy_nid_postfix(char* dest, const char* src, size_t size); +std::int64_t APS5_VABI strtol_nid_postfix(const char* str, char** endptr, int base); +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); +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); +float APS5_VABI strtof_nid_postfix(const char* str, char** endptr); +long double APS5_VABI strtold_nid_postfix(const char* str, char** endptr); +int APS5_VABI atoi_nid_postfix(const char* str); +std::int64_t APS5_VABI atol_nid_postfix(const char* str); +long long APS5_VABI atoll_nid_postfix(const char* str); +std::div_t APS5_VABI div_nid_postfix(int numerator, int denominator); +const char16_t* APS5_VABI wmemchr_nid_postfix(const char16_t* s, char16_t c, size_t n); +int APS5_VABI wmemcmp_nid_postfix(const char16_t* s1, const char16_t* s2, size_t n); +char16_t* APS5_VABI wmemcpy_nid_postfix(char16_t* dest, const char16_t* src, size_t n); +char16_t* APS5_VABI wmemmove_nid_postfix(char16_t* dest, const char16_t* src, size_t n); +int APS5_VABI strcasecmp_nid_postfix(const char* s1, const char* s2); +int APS5_VABI strncasecmp_nid_postfix(const char* s1, const char* s2, size_t n); +int APS5_VABI bcmp_nid_postfix(const void* s1, const void* s2, size_t n); +size_t APS5_VABI strspn_nid_postfix(const char* s, const char* accept); +int APS5_VABI strcoll_nid_postfix(const char* s1, const char* s2); +int APS5_VABI strncpy_s_nid_postfix(char* dest, size_t destsz, const char* src, size_t count); +int APS5_VABI strcpy_s_nid_postfix(char* dest, size_t destsz, const char* src); +int APS5_VABI strncat_s_nid_postfix(char* dest, size_t destsz, const char* src, size_t count); +int APS5_VABI strcat_s_nid_postfix(char* dest, size_t destsz, const char* src); +int APS5_VABI memcpy_s_nid_postfix(void* dest, size_t destsz, const void* src, size_t count); +int APS5_VABI memmove_s_nid_postfix(void* dest, size_t destsz, const void* src, size_t count); +int APS5_VABI memset_s_nid_postfix(void* dest, size_t destsz, int value, size_t count); +char* APS5_VABI strnstr_nid_postfix(const char* haystack, const char* needle, size_t length); +unsigned long long APS5_VABI _Stoull_nid_postfix(const char* str, char** endptr, int base); +size_t APS5_VABI wcslen_nid_postfix(const char16_t* s); +size_t APS5_VABI wcsnlen_s_nid_postfix(const char16_t* s, size_t maxlen); +int APS5_VABI wcscmp_nid_postfix(const char16_t* s1, const char16_t* s2); +int APS5_VABI wcsncmp_nid_postfix(const char16_t* s1, const char16_t* s2, size_t n); +char16_t* APS5_VABI wcscpy_nid_postfix(char16_t* dest, const char16_t* src); +char16_t* APS5_VABI wcscat_nid_postfix(char16_t* dest, const char16_t* src); +char16_t* APS5_VABI wcsncpy_nid_postfix(char16_t* dest, const char16_t* src, size_t n); +const char16_t* APS5_VABI wcschr_nid_postfix(const char16_t* s, char16_t c); +const char16_t* APS5_VABI wcsrchr_nid_postfix(const char16_t* s, char16_t c); +const char16_t* APS5_VABI wcsstr_nid_postfix(const char16_t* haystack, const char16_t* needle); +const char16_t* APS5_VABI wcspbrk_nid_postfix(const char16_t* s, const char16_t* accept); +size_t APS5_VABI wcsspn_nid_postfix(const char16_t* s, const char16_t* accept); +size_t APS5_VABI wcscspn_nid_postfix(const char16_t* s, const char16_t* reject); +char16_t* APS5_VABI wcstok_nid_postfix(char16_t* text, const char16_t* delimiters, char16_t** last); +char16_t* APS5_VABI wmemset_nid_postfix(char16_t* s, char16_t c, size_t n); +double APS5_VABI wcstod_nid_postfix(const char16_t* str, char16_t** endptr); +float APS5_VABI wcstof_nid_postfix(const char16_t* str, char16_t** endptr); +long double APS5_VABI wcstold_nid_postfix(const char16_t* str, char16_t** endptr); +unsigned long long APS5_VABI wcstoul_nid_postfix(const char16_t* str, char16_t** endptr, int base); +unsigned long long APS5_VABI wcstoull_nid_postfix(const char16_t* str, char16_t** endptr, int base); +long long APS5_VABI wcstoll_nid_postfix(const char16_t* str, char16_t** endptr, int base); +long long APS5_VABI wcstol_nid_postfix(const char16_t* str, char16_t** endptr, int base); +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 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); +char* APS5_VABI strtok_s_nid_postfix(char* str, const char* delim, char** context); +int APS5_VABI wmemcpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); +int APS5_VABI wmemmove_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); +int APS5_VABI wcsncpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); +char16_t* APS5_VABI wcsncat_nid_postfix(char16_t* dest, const char16_t* src, size_t n); +int APS5_VABI wcsncat_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); +int APS5_VABI wcscat_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src); +void APS5_VABI openlog_nid_postfix(const char* identity, int options, int facility); +void APS5_VABI closelog_nid_postfix(); +int APS5_VABI setlogmask_nid_postfix(int mask); +void APS5_VABI vsyslog_nid_postfix(int priority, const char* format, VaList* args); +void APS5_VABI syslog_nid_postfix(int priority, const char* format, ...); +int APS5_VABI _Mtx_init_nid_postfix(void** handle, int type); +int APS5_VABI _Mtx_init_with_name_nid_postfix(void** handle, int type, const char*); +void APS5_VABI _Mtx_destroy_nid_postfix(void** handle); +int APS5_VABI _Mtx_lock_nid_postfix(void** handle); +int APS5_VABI _Mtx_trylock_nid_postfix(void** handle); +int APS5_VABI _Mtx_timedlock_nid_postfix(void** handle, const Xtime* target); +int APS5_VABI _Mtx_unlock_nid_postfix(void** handle); +int APS5_VABI _Cnd_init_nid_postfix(void** handle); +int APS5_VABI _Cnd_init_with_default_name_override_nid_postfix(void** handle, const char*); +void APS5_VABI _Cnd_destroy_nid_postfix(void** handle); +int APS5_VABI _Cnd_wait_nid_postfix(void** conditionHandle, void** mutexHandle); +int APS5_VABI _Cnd_timedwait_nid_postfix(void** conditionHandle, void** mutexHandle, const Xtime* target); +int APS5_VABI _Cnd_broadcast_nid_postfix(void** handle); +unsigned APS5_VABI _ZNSt6thread20hardware_concurrencyEv_nid_postfix(); +int APS5_VABI _Thrd_sleep_nid_postfix(const Xtime* target, Xtime*); +int APS5_VABI _Thrd_yield_nid_postfix(); +int APS5_VABI _Cnd_signal_nid_postfix(void** handle); +void APS5_VABI _ZNSt4_PadC2Ev_nid_postfix(GuestPad* pad); +void APS5_VABI _ZNSt4_PadD2Ev_nid_postfix(GuestPad* pad); +void APS5_VABI _ZNSt4_Pad8_ReleaseEv_nid_postfix(GuestPad* pad); +void APS5_VABI _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(GuestPad* pad, void** thread); +void APS5_VABI _ZNSt4_Pad7_LaunchEPP12pthread_attrPP7pthread_nid_postfix(GuestPad* pad, void** attrSlot, void** thread); +int APS5_VABI _Thrd_join_nid_postfix(void* thread, int* code); +int APS5_VABI _Thrd_detach_nid_postfix(void* thread); +void* APS5_VABI _Thrd_id_nid_postfix(void); +void APS5_VABI _Call_once_nid_postfix(std::int32_t* flag, void (APS5_VABI* function)(void)); +int APS5_VABI _Tss_create_nid_postfix(int* key, void (APS5_VABI* destructor)(void*)); +int APS5_VABI _Tss_delete_nid_postfix(int key); +void* APS5_VABI _Tss_get_nid_postfix(int key); +int APS5_VABI _Tss_set_nid_postfix(int key, void* value); +int APS5_VABI _Thrd_equal_nid_postfix(void* first, void* second); +void* APS5_VABI _Thrd_current_nid_postfix(void); +int APS5_VABI _Mtx_current_owns_nid_postfix(void**); +int APS5_VABI _ZNSt8__sce_v219_Mtx_init_with_nameEPP13pthread_mutexiPKc_nid_postfix(void** mutex, int type, const char* name); +int APS5_VABI _ZNSt8__sce_v29_Mtx_initEPP13pthread_mutexi_nid_postfix(void** mutex, int type); +int APS5_VABI _ZNSt8__sce_v212_Mtx_destroyEPP13pthread_mutex_nid_postfix(void** mutex); +int APS5_VABI _ZNSt8__sce_v29_Mtx_lockEPP13pthread_mutex_nid_postfix(void** mutex); +int APS5_VABI _ZNSt8__sce_v212_Mtx_trylockEPP13pthread_mutex_nid_postfix(void** mutex); +int APS5_VABI _ZNSt8__sce_v211_Mtx_unlockEPP13pthread_mutex_nid_postfix(void** mutex); +int APS5_VABI _ZNSt8__sce_v219_Cnd_init_with_nameEPP12pthread_condPKc_nid_postfix(void** condition, const char* name); +int APS5_VABI _ZNSt8__sce_v29_Cnd_initEPP12pthread_cond_nid_postfix(void** condition); +int APS5_VABI _ZNSt8__sce_v212_Cnd_destroyEPP12pthread_cond_nid_postfix(void** condition); +int APS5_VABI _ZNSt8__sce_v29_Cnd_waitEPP12pthread_condPP13pthread_mutex_nid_postfix(void** condition, void** mutex); +int APS5_VABI _ZNSt8__sce_v211_Cnd_signalEPP12pthread_cond_nid_postfix(void** condition); +int APS5_VABI _ZNSt8__sce_v214_Cnd_broadcastEPP12pthread_cond_nid_postfix(void** condition); +int APS5_VABI _ZNSt8__sce_v212_Thrd_detachEP7pthread_nid_postfix(void* thread); +int APS5_VABI _ZNSt8__sce_v210_Thrd_joinEP7pthreadPi_nid_postfix(void* thread, int* code); +void APS5_VABI _ZNSt8__sce_v211_Thrd_yieldEv_nid_postfix(); +void* APS5_VABI _ZNSt8__sce_v28_Thrd_idEv_nid_postfix(); +void APS5_VABI tzset_nid_postfix(void); +int64_t APS5_VABI libc_time_nid_postfix(int64_t* timer); +int64_t APS5_VABI time_nid_postfix(int64_t* timer); +int64_t APS5_VABI _Xtime_get_ticks_nid_postfix(); +double APS5_VABI libc_difftime_nid_postfix(int64_t time1, int64_t time0); +double APS5_VABI difftime_nid_postfix(int64_t time1, int64_t time0); +GuestTm* APS5_VABI libc_gmtime_nid_postfix(const int64_t* timer); +GuestTm* APS5_VABI libc_localtime_nid_postfix(const int64_t* timer); +GuestTm* APS5_VABI localtime_nid_postfix(const int64_t* timer); +GuestTm* APS5_VABI gmtime_s_nid_postfix(const int64_t* timer, GuestTm* result); +GuestTm* APS5_VABI gmtime_nid_postfix(const int64_t* timer); +int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr); +int64_t APS5_VABI mktime_nid_postfix(GuestTm* timeptr); +size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr); +char* APS5_VABI asctime_nid_postfix(const GuestTm* timeptr); +char* APS5_VABI ctime_nid_postfix(const int64_t* timer); +size_t APS5_VABI strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr); +size_t APS5_VABI wcsftime_nid_postfix(std::uint16_t* str, size_t count, const std::uint16_t* format, const GuestTm* timeptr); +int64_t APS5_VABI clock_nid_postfix(); +int APS5_VABI ctime_s_nid_postfix(char* buffer, std::size_t size, const std::int64_t* time); +int APS5_VABI asctime_s_nid_postfix(char* buffer, std::size_t size, const GuestTm* time); +GuestTm* APS5_VABI gmtime_r_nid_postfix(const std::int64_t* timer, GuestTm* result); +GuestTm* APS5_VABI localtime_r_nid_postfix(const std::int64_t* timer, GuestTm* result); +std::int64_t APS5_VABI timegm_nid_postfix(GuestTm* time); +int APS5_VABI xtime_get_nid_postfix(Xtime* time, int base); +Xtime APS5_VABI _Xtime_to_ts_nid_postfix(const Xtime* time); +Xtime APS5_VABI _Xtime_diff_to_ts_nid_postfix(const Xtime* target); +const void* APS5_VABI __std_find_trivial_1_nid_postfix(const void* first, const void* last, std::uint8_t value); +const void* APS5_VABI __std_find_trivial_2_nid_postfix(const void* first, const void* last, std::uint16_t value); +const void* APS5_VABI __std_find_trivial_4_nid_postfix(const void* first, const void* last, std::uint32_t value); +const void* APS5_VABI __std_find_trivial_8_nid_postfix(const void* first, const void* last, std::uint64_t value); +const void* APS5_VABI __std_find_trivial_unsized_1_nid_postfix(const void* first, std::uint8_t value); +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::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); +std::size_t APS5_VABI __std_count_trivial_8_nid_postfix(const void* first, const void* last, std::uint64_t value); +const void* APS5_VABI __std_min_element_1_nid_postfix(const void* first, const void* last, bool isSigned); +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); +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); +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); +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); +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); +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); +int APS5_VABI wcscpy_s_nid_postfix(std::uint16_t* dest, std::size_t size, const std::uint16_t* src); +std::size_t APS5_VABI strerrorlen_s_nid_postfix(int error); +int APS5_VABI strerror_s_nid_postfix(char* buffer, std::size_t capacity, int error); +void APS5_VABI __cxa_vec_cleanup_nid_postfix(void* array, std::size_t count, std::size_t size, void (APS5_VABI *destroy)(void*)); +void APS5_VABI __cxa_vec_ctor_nid_postfix(void* array, std::size_t count, std::size_t size, void (APS5_VABI *construct)(void*), void (APS5_VABI *destroy)(void*)); +void APS5_VABI __cxa_vec_cctor_nid_postfix(void* destination, void* source, std::size_t count, std::size_t size, void (APS5_VABI *copy)(void*, void*), void (APS5_VABI *destroy)(void*)); +void APS5_VABI __cxa_vec_dtor_nid_postfix(void* array, std::size_t count, std::size_t size, void (APS5_VABI *destroy)(void*)); +void* APS5_VABI __cxa_vec_new_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (APS5_VABI *ctor)(void*), void (APS5_VABI *dtor)(void*)); +void* APS5_VABI __cxa_vec_new2_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (APS5_VABI *ctor)(void*), void (APS5_VABI *dtor)(void*), void* (APS5_VABI *alloc)(std::size_t), void (APS5_VABI *dealloc)(void*)); +void* APS5_VABI __cxa_vec_new3_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (APS5_VABI *ctor)(void*), void (APS5_VABI *dtor)(void*), void* (APS5_VABI *alloc)(std::size_t), void (APS5_VABI *dealloc)(void*, std::size_t)); +void APS5_VABI __cxa_vec_delete_nid_postfix(void* array, std::size_t s, std::size_t p, void (APS5_VABI *dtor)(void*)); +void APS5_VABI __cxa_vec_delete2_nid_postfix(void* array, std::size_t s, std::size_t p, void (APS5_VABI *dtor)(void*), void (APS5_VABI *dealloc)(void*)); +void APS5_VABI __cxa_vec_delete3_nid_postfix(void* array, std::size_t s, std::size_t p, void (APS5_VABI *dtor)(void*), void (APS5_VABI *dealloc)(void*, std::size_t)); +#ifndef _LIBCPP_VERSION +void APS5_VABI _ZNSt13__future_base12_Result_baseD2Ev_nid_postfix(std::__future_base::_Result_base* self) noexcept; +#endif +void APS5_VABI _ZNSt15__exception_ptr13exception_ptr9_M_addrefEv_nid_postfix(GuestExceptionPointer* self) noexcept; +void APS5_VABI _ZNSt15__exception_ptr13exception_ptr10_M_releaseEv_nid_postfix(GuestExceptionPointer* self) noexcept; +void* APS5_VABI _ZNKSt15__exception_ptr13exception_ptr6_M_getEv_nid_postfix(const GuestExceptionPointer* self) noexcept; +void APS5_VABI _ZNSt15__exception_ptr13exception_ptrC1EPv_nid_postfix(GuestExceptionPointer* self, void* exception) noexcept; +void APS5_VABI _ZNSt15__exception_ptr13exception_ptrC2EPv_nid_postfix(GuestExceptionPointer* self, void* exception) noexcept; +const std::type_info* APS5_VABI _ZNKSt15__exception_ptr13exception_ptr20__cxa_exception_typeEv_nid_postfix(const GuestExceptionPointer* self) noexcept; +GuestExceptionPointer APS5_VABI _ZSt17current_exceptionv_nid_postfix() noexcept; +int APS5_VABI __cxa_guard_acquire_nid_postfix(std::uint64_t* guardObject); +void APS5_VABI __cxa_guard_release_nid_postfix(std::uint64_t* guardObject); +void APS5_VABI __cxa_guard_abort_nid_postfix(std::uint64_t* guardObject); +LibcException::Header* APS5_VABI __cxa_init_primary_exception_nid_postfix(void* object, std::type_info* type, void (*destructor)(void*)); +void* APS5_VABI __cxa_get_exception_ptr_nid_postfix(void* exception); +std::type_info* APS5_VABI __cxa_current_exception_type_nid_postfix(); +void* APS5_VABI __cxa_get_globals_nid_postfix(); +void* APS5_VABI __cxa_get_globals_fast_nid_postfix(); +bool APS5_VABI __cxa_uncaught_exception_nid_postfix(); +unsigned APS5_VABI __cxa_uncaught_exceptions_nid_postfix(); +bool APS5_VABI _ZSt18uncaught_exceptionv_nid_postfix(); +int APS5_VABI _ZSt19uncaught_exceptionsv_nid_postfix(); +LibcTerminateHandler APS5_VABI _ZSt13set_terminatePFvvE_nid_postfix(LibcTerminateHandler handler); +LibcTerminateHandler APS5_VABI _ZSt13get_terminatev_nid_postfix(); +void APS5_VABI __cxa_increment_exception_refcount_nid_postfix(void* object); +void APS5_VABI __cxa_decrement_exception_refcount_nid_postfix(void* object); +void* APS5_VABI __cxa_current_primary_exception_nid_postfix(); +void* APS5_VABI __cxa_allocate_dependent_exception_nid_postfix(); +void APS5_VABI __cxa_free_dependent_exception_nid_postfix(void* storage); +void APS5_VABI __cxa_rethrow_primary_exception_nid_postfix(void* object); +void* APS5_VABI __dynamic_cast_nid_postfix(const void* source, const __cxxabiv1::__class_type_info* sourceType, const __cxxabiv1::__class_type_info* destinationType, std::ptrdiff_t); +const char* APS5_VABI _ZSt13_Syserror_mapi_nid_postfix(int value); +const LibcException::CategoryObject* APS5_VABI _ZSt16generic_categoryv_nid_postfix(); +const LibcException::CategoryObject* APS5_VABI _ZSt15system_categoryv_nid_postfix(); +GuestLocale::String* APS5_VABI _ZNKSt23_Generic_error_category7messageEi_nid_postfix( GuestLocale::String* result, const LibcException::CategoryObject* self, int value); +void APS5_VABI _ZNSt9exceptionD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt9exceptionD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt9exceptionD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt9exception4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt9exceptionC1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt9exceptionC2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt8bad_castD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt8bad_castD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt8bad_castD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt8bad_cast4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt8bad_castC1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt8bad_castC2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt10bad_typeidD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt10bad_typeidD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt10bad_typeidD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt10bad_typeid4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt10bad_typeidC1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt10bad_typeidC2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt9bad_allocD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt9bad_allocD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt9bad_allocD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt9bad_alloc4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt9bad_allocC1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt9bad_allocC2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt20bad_array_new_lengthD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt20bad_array_new_lengthD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt20bad_array_new_lengthD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt20bad_array_new_length4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt20bad_array_new_lengthC1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt20bad_array_new_lengthC2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt17bad_function_callD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt17bad_function_callD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt17bad_function_callD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt17bad_function_call4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt17bad_function_callC1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt17bad_function_callC2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12bad_weak_ptrD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12bad_weak_ptrD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12bad_weak_ptrD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt12bad_weak_ptr4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt19bad_optional_accessD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt19bad_optional_accessD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt19bad_optional_accessD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt19bad_optional_access4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt19bad_optional_accessC1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt19bad_optional_accessC2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt11logic_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt11logic_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt11logic_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt11logic_error4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt11logic_errorC1EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt11logic_errorC2EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt11logic_errorC1ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt11logic_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +LibcException::ExceptionObject* APS5_VABI _ZNSt11logic_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt13runtime_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt13runtime_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt13runtime_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt13runtime_error4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt13runtime_errorC1EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt13runtime_errorC2EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt13runtime_errorC1ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt13runtime_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +LibcException::ExceptionObject* APS5_VABI _ZNSt13runtime_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt16invalid_argumentD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt16invalid_argumentD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt16invalid_argumentD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt16invalid_argument4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt16invalid_argumentC1EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt16invalid_argumentC2EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt16invalid_argumentC1ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt16invalid_argumentC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +LibcException::ExceptionObject* APS5_VABI _ZNSt16invalid_argumentaSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt12out_of_rangeD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12out_of_rangeD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12out_of_rangeD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt12out_of_range4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12out_of_rangeC1EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt12out_of_rangeC2EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt12out_of_rangeC1ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt12out_of_rangeC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +LibcException::ExceptionObject* APS5_VABI _ZNSt12out_of_rangeaSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt12domain_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12domain_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12domain_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt12domain_error4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12domain_errorC1EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt12domain_errorC2EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt12domain_errorC1ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt12domain_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +LibcException::ExceptionObject* APS5_VABI _ZNSt12domain_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt12length_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12length_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12length_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt12length_error4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12length_errorC1EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt12length_errorC2EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt12length_errorC1ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt12length_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +LibcException::ExceptionObject* APS5_VABI _ZNSt12length_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt12system_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12system_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12system_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt12system_error4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12system_errorC1ERKS__nid_postfix(LibcException::SystemErrorObject* self, const LibcException::SystemErrorObject* source); +void APS5_VABI _ZNSt12system_errorC2ERKS__nid_postfix(LibcException::SystemErrorObject* self, const LibcException::SystemErrorObject* source); +LibcException::SystemErrorObject* APS5_VABI _ZNSt12system_erroraSERKS__nid_postfix(LibcException::SystemErrorObject* self, const LibcException::SystemErrorObject* source); +void APS5_VABI _ZNSt11regex_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt11regex_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt11regex_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt11regex_error4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt11regex_errorC1ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt11regex_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +LibcException::ExceptionObject* APS5_VABI _ZNSt11regex_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt11range_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt11range_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt11range_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt14overflow_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt15underflow_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt8ios_base7failureD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt8ios_base7failureD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt8ios_base7failureD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +_Unwind_Word APS5_VABI _Unwind_GetGR_nid_postfix(_Unwind_Context* context, int index); +void APS5_VABI _Unwind_SetGR_nid_postfix(_Unwind_Context* context, int index, _Unwind_Word value); +_Unwind_Ptr APS5_VABI _Unwind_GetIP_nid_postfix(_Unwind_Context* context); +void APS5_VABI _Unwind_SetIP_nid_postfix(_Unwind_Context* context, _Unwind_Ptr value); +_Unwind_Ptr APS5_VABI _Unwind_GetIPInfo_nid_postfix(_Unwind_Context* context, int* before); +_Unwind_Word APS5_VABI _Unwind_GetCFA_nid_postfix(_Unwind_Context* context); +_Unwind_Ptr APS5_VABI _Unwind_GetLanguageSpecificData_nid_postfix(_Unwind_Context* context); +_Unwind_Ptr APS5_VABI _Unwind_GetRegionStart_nid_postfix(_Unwind_Context* context); +_Unwind_Ptr APS5_VABI _Unwind_GetDataRelBase_nid_postfix(_Unwind_Context* context); +_Unwind_Ptr APS5_VABI _Unwind_GetTextRelBase_nid_postfix(_Unwind_Context* context); +_Unwind_Reason_Code APS5_VABI _Unwind_ForcedUnwind_nid_postfix(_Unwind_Exception* exception, _Unwind_Stop_Fn stop, void* argument); +_Unwind_Reason_Code APS5_VABI _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn trace, void* argument); +int APS5_VABI dl_iterate_phdr_nid_postfix(int (*callback)(dl_phdr_info*, std::size_t, void*), void* data); + +[[noreturn]] void APS5_VABI _Exit_nid_postfix(int code); +[[noreturn]] void APS5_VABI catchReturnFromMain_nid_postfix(int status); +[[noreturn]] void APS5_VABI quick_exit_nid_postfix(int status); +[[noreturn]] void APS5_VABI _ZSt14_Throw_C_errori_nid_postfix(int code); +[[noreturn]] char* APS5_VABI fgetln_nid_postfix(FileStream*, std::size_t*); +[[noreturn]] void APS5_VABI _Assert_nid_postfix(const char* message, const char* location); +[[noreturn]] void APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(int code); +[[noreturn]] void APS5_VABI _Thrd_exit_nid_postfix(int code); +[[noreturn]] void APS5_VABI _ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE_nid_postfix(GuestExceptionPointerArgument exception); +[[noreturn]] void APS5_VABI _ZSt9terminatev_nid_postfix(); +[[noreturn]] void APS5_VABI __cxa_call_terminate_nid_postfix(void* exception); +[[noreturn]] void APS5_VABI __cxa_pure_virtual_nid_postfix(); +[[noreturn]] void APS5_VABI __cxa_deleted_virtual_nid_postfix(); +[[noreturn]] void APS5_VABI _ZNKSt9exception8_DoraiseEv_nid_postfix(const LibcException::ExceptionObject* self); +[[noreturn]] void APS5_VABI _ZNKSt9exception6_RaiseEv_nid_postfix(const LibcException::ExceptionObject* self); +[[noreturn]] void APS5_VABI _ZNKSt8bad_cast8_DoraiseEv_nid_postfix(const LibcException::ExceptionObject* self); +[[noreturn]] void APS5_VABI _ZNKSt11logic_error8_DoraiseEv_nid_postfix(const LibcException::ExceptionObject* self); +[[noreturn]] void APS5_VABI __cxa_bad_cast_nid_postfix(); +[[noreturn]] void APS5_VABI __cxa_bad_typeid_nid_postfix(); +[[noreturn]] void APS5_VABI __cxa_throw_bad_array_new_length_nid_postfix(); +[[noreturn]] void APS5_VABI _ZSt11_Xbad_allocv_nid_postfix(); +[[noreturn]] void APS5_VABI _ZSt14_Xout_of_rangePKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZSt14_Xlength_errorPKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZSt18_Xinvalid_argumentPKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZSt15_Xruntime_errorPKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZSt13_Xrange_errorPKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZSt19_Xbad_function_callv_nid_postfix(); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix(); +[[noreturn]] void APS5_VABI _ZSt19_Throw_bad_weak_ptrv_nid_postfix(); +[[noreturn]] void APS5_VABI _ZSt26__throw_bad_variant_accessv_nid_postfix(); +[[noreturn]] void APS5_VABI _ZSt26_Throw_bad_optional_accessv_nid_postfix(); +[[noreturn]] void APS5_VABI _ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix(std::regex_constants::error_type code); + +[[gnu::returns_twice]] int APS5_VABI setjmp_nid_postfix(std::uint64_t* buffer); +[[noreturn]] void APS5_VABI longjmp_nid_postfix(std::uint64_t* buffer, int value); + +std::uint8_t APS5_VABI __atomic_load_1_nid_postfix(const volatile std::uint8_t* target, int); +std::uint16_t APS5_VABI __atomic_load_2_nid_postfix(const volatile std::uint16_t* target, int); +std::uint32_t APS5_VABI __atomic_load_4_nid_postfix(const volatile std::uint32_t* target, int); +std::uint64_t APS5_VABI __atomic_load_8_nid_postfix(const volatile std::uint64_t* target, int); +void APS5_VABI __atomic_store_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +void APS5_VABI __atomic_store_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +void APS5_VABI __atomic_store_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +void APS5_VABI __atomic_store_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int); +std::uint8_t APS5_VABI __atomic_exchange_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI __atomic_exchange_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI __atomic_exchange_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI __atomic_exchange_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int); +int APS5_VABI __atomic_compare_exchange_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int); +int APS5_VABI __atomic_compare_exchange_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int); +int APS5_VABI __atomic_compare_exchange_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t* expected, std::uint32_t desired, int, int); +int APS5_VABI __atomic_compare_exchange_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int); +std::uint8_t APS5_VABI __atomic_fetch_add_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI __atomic_fetch_add_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI __atomic_fetch_add_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI __atomic_fetch_add_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int); +std::uint8_t APS5_VABI __atomic_fetch_sub_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI __atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI __atomic_fetch_sub_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI __atomic_fetch_sub_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int); +std::uint8_t APS5_VABI __atomic_fetch_and_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI __atomic_fetch_and_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI __atomic_fetch_and_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI __atomic_fetch_and_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int); +std::uint8_t APS5_VABI __atomic_fetch_or_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI __atomic_fetch_or_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI __atomic_fetch_or_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI __atomic_fetch_or_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int); +std::uint8_t APS5_VABI __atomic_fetch_xor_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int); +std::uint16_t APS5_VABI __atomic_fetch_xor_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int); +std::uint32_t APS5_VABI __atomic_fetch_xor_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int); +std::uint64_t APS5_VABI __atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int); +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionC1EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionC2EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message); +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionC1ERKS0__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionC2ERKS0__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionD2Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionD0Ev_nid_postfix(LibcException::ExceptionObject* self); +const char* APS5_VABI _ZNKSt13__sce_cxxvers16_Error_exception4whatEv_nid_postfix(const LibcException::ExceptionObject* self); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v214_Xlength_errorEPKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v214_Xout_of_rangeEPKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v218_Xinvalid_argumentEPKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v215_Xruntime_errorEPKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v216_Xoverflow_errorEPKc_nid_postfix(const char* message); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v211_Xbad_allocEv_nid_postfix(); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_19bad_optional_accessE_nid_postfix(const LibcException::ExceptionObject*); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_12bad_weak_ptrE_nid_postfix(const LibcException::ExceptionObject*); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_18bad_variant_accessE_nid_postfix(const LibcException::ExceptionObject*); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_11range_errorE_nid_postfix(const LibcException::ExceptionObject* error); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_12system_errorE_nid_postfix(const LibcException::SystemErrorObject* error); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v28ios_base6_ThrowERKNS0_7failureE_nid_postfix(const LibcException::SystemErrorObject* failure); +[[noreturn]] void APS5_VABI _ZNSt8__sce_v216_Throw_Cpp_errorEi_nid_postfix(int code); +std::int64_t APS5_VABI _ZNSt8__sce_v216_Xtime_get_ticksEv_nid_postfix(); +const char* APS5_VABI _ZNSt8__sce_v213_Syserror_mapEi_nid_postfix(int value); +int APS5_VABI feupdateenv_nid_postfix(const GuestFloatingPointEnvironment* environment); +int APS5_VABI feholdexcept_nid_postfix(GuestFloatingPointEnvironment* environment); +void APS5_VABI srandomdev_nid_postfix(); + +double APS5_VABI _Stodx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error); +float APS5_VABI _Stofx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error); +long double APS5_VABI _Stoldx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error); +float APS5_VABI _Stof_nid_postfix(const char* text, char** end, std::int64_t pten); +long double APS5_VABI _Stold_nid_postfix(const char* text, char** end, std::int64_t pten); +long long APS5_VABI _Stollx_nid_postfix(const char* text, char** end, int base, int* error); +unsigned long long APS5_VABI _Stoullx_nid_postfix(const char* text, char** end, int base, int* error); +std::uint64_t APS5_VABI _Stoulx_nid_postfix(const char* text, char** end, int base, int* error); +std::int64_t APS5_VABI _Stolx_nid_postfix(const char* text, char** end, int base, int* error); +std::size_t APS5_VABI _ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPKS2__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*); +std::size_t APS5_VABI _ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*); +std::size_t APS5_VABI _ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*); +std::size_t APS5_VABI _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*); +int APS5_VABI siginterrupt_nid_postfix(int guest, int flag); + +} diff --git a/core/libs/prx/libc/include/ApplicationHeap.hpp b/core/libs/prx/libc/include/ApplicationHeap.hpp index 00033a1d59..bd604dd95a 100644 --- a/core/libs/prx/libc/include/ApplicationHeap.hpp +++ b/core/libs/prx/libc/include/ApplicationHeap.hpp @@ -9,6 +9,7 @@ const void* ApplicationProcessParameters_nid_no_patch(); bool ApplicationGuestHeapApi_nid_no_patch(void** api); void ApplicationHeapInitialize_nid_no_patch(const void* processParameters); void ApplicationHeapRegister_nid_no_patch(void* const* api); +bool ApplicationHeapDefault_nid_no_patch(); void* ApplicationHeapAllocate_nid_no_patch(std::size_t bytes); void ApplicationHeapFree_nid_no_patch(void* pointer); void* ApplicationHeapReallocate_nid_no_patch(void* pointer, std::size_t bytes); diff --git a/core/libs/prx/libc/include/CallTrace.hpp b/core/libs/prx/libc/include/CallTrace.hpp index b53c1ba96e..430700f0b5 100644 --- a/core/libs/prx/libc/include/CallTrace.hpp +++ b/core/libs/prx/libc/include/CallTrace.hpp @@ -23,6 +23,7 @@ void* TraceImport_nid_no_patch(void* target, const char* name, const char* libra std::size_t CopyCallTrace_nid_no_patch(CallTraceEntry* entries, std::size_t capacity); std::size_t CopyThreadCallTrace_nid_no_patch(std::uint64_t thread, CallTraceEntry* entries, std::size_t capacity); std::size_t CopyCallCounts_nid_no_patch(CallTraceCount* entries, std::size_t capacity, std::uint64_t* total); +bool WriteCallCounts_nid_no_patch(const char* path); } #endif diff --git a/core/libs/prx/libc/include/ChildProcesses.hpp b/core/libs/prx/libc/include/ChildProcesses.hpp new file mode 100644 index 0000000000..9554bfb475 --- /dev/null +++ b/core/libs/prx/libc/include/ChildProcesses.hpp @@ -0,0 +1,11 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_CHILDPROCESSES_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_CHILDPROCESSES_HPP + +#ifdef _WIN32 +#include + +extern "C" void RegisterChildProcess_nid_no_patch(std::int32_t processId, std::int64_t processHandle); +extern "C" std::int64_t WaitChildProcess_nid_no_patch(std::int32_t processId, unsigned options, int* status); +#endif + +#endif diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index 1b9954d873..a97f626f49 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -17,12 +17,15 @@ #include "prx/libc/include/FileLock.hpp" extern "C" void* HostStandardHandle_nid_no_patch(int descriptor); extern "C" void KeepStandardHandle_nid_no_patch(int descriptor, void* handle); +extern "C" void PrepareStandardStreams_nid_no_patch(); struct UcrtStreamState { char* pointer; char* base; int count; long flags; }; +extern "C" void LibcRegisterDirectoryStreams_nid_no_patch(int (*open)(const wchar_t* path), void (*forget)(int descriptor)); +extern "C" void ForgetDirectoryStream_nid_no_patch(int descriptor); #else #include #endif @@ -66,10 +69,14 @@ class FileStream { bool _dynamic; bool guestError = false; bool randomDevice = false; + bool directory = false; public: explicit FileStream(std::FILE* handle, bool dynamic = false) : _handle(handle), _dynamic(dynamic) { if (!_handle) throw std::runtime_error("FileStream: null handle"); +#ifdef _WIN32 + PrepareStandardStreams_nid_no_patch(); +#endif _guest.flags = handle == stdin ? 4 : handle == stdout || handle == stderr ? 8 : 0x10; #ifdef _WIN32 const int descriptor = _fileno(handle); @@ -130,6 +137,14 @@ public: return randomDevice; } + void MarkDirectory() { + directory = true; + } + + bool IsDirectory() const { + return directory; + } + bool Reopen(const std::filesystem::path& filename, const char* mode, [[maybe_unused]] std::FILE* (*open)(const std::filesystem::path&, const std::string&) = nullptr) { auto* previous = GetHandle(); @@ -140,6 +155,7 @@ public: std::wstring wideMode; for (const char character : std::string_view(mode)) wideMode.push_back(static_cast(character)); const int replaced = previous ? _fileno(previous) : -1; + if (std::exchange(directory, false)) ForgetDirectoryStream_nid_no_patch(replaced); auto* const standard = HostStandardHandle_nid_no_patch(replaced); GuestFileLockForget_nid_no_patch(replaced); if (open && !filename.empty()) { @@ -200,27 +216,32 @@ public: _guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast(descriptor) : -1; return true; } + void PublishStatus(bool endOfFile, bool error) { + __atomic_store_n(&_guest.readRemaining, 0, __ATOMIC_RELAXED); + __atomic_store_n(&_guest.writeRemaining, 0, __ATOMIC_RELAXED); + auto old = __atomic_load_n(&_guest.flags, __ATOMIC_RELAXED); + std::int16_t next; + do next = static_cast((old & ~0x60) | (endOfFile ? 0x20 : 0) | (error ? 0x40 : 0)); + while (!__atomic_compare_exchange_n(&_guest.flags, &old, next, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)); + } void SyncStatus() { if (!_handle) return; - _guest.readRemaining = 0; - _guest.writeRemaining = 0; - _guest.flags = static_cast((_guest.flags & ~0x60) | - (std::feof(GetHandle()) ? 0x20 : 0) | (std::ferror(GetHandle()) || guestError ? 0x40 : 0)); + PublishStatus(std::feof(GetHandle()) != 0, std::ferror(GetHandle()) != 0 || guestError); } void SyncUnlockedStatus() { if (!_handle) return; - _guest.readRemaining = 0; - _guest.writeRemaining = 0; #ifdef _WIN32 const bool endOfFile = std::feof(GetHandle()) != 0; const bool error = std::ferror(GetHandle()) != 0; +#elif defined(__APPLE__) + const bool endOfFile = feof_unlocked(GetHandle()) != 0; + const bool error = ferror_unlocked(GetHandle()) != 0; #else - const bool endOfFile = ::feof_unlocked(GetHandle()) != 0; - const bool error = ::ferror_unlocked(GetHandle()) != 0; + const bool endOfFile = feof_unlocked(GetHandle()) != 0; + const bool error = ferror_unlocked(GetHandle()) != 0; #endif - _guest.flags = static_cast((_guest.flags & ~0x60) | - (endOfFile ? 0x20 : 0) | (error || guestError ? 0x40 : 0)); + PublishStatus(endOfFile, error || guestError); } bool SwitchToReading() { @@ -260,6 +281,10 @@ public: #endif } + void KeepDirectoryReadError(bool failed) { + if (failed && directory && (_guest.flags & 0x40) != 0) errno = 21; + } + void SetError() { guestError = true; SyncStatus(); @@ -276,6 +301,7 @@ public: GetHandle(); #ifdef _WIN32 GuestFileLockForget_nid_no_patch(::_fileno(_handle)); + if (std::exchange(directory, false)) ForgetDirectoryStream_nid_no_patch(::_fileno(_handle)); #endif const int result = std::fclose(std::exchange(_handle, nullptr)); _guest.flags = 0; diff --git a/core/libs/prx/libc/include/General.hpp b/core/libs/prx/libc/include/General.hpp index f22c2e8556..4d4db4d068 100644 --- a/core/libs/prx/libc/include/General.hpp +++ b/core/libs/prx/libc/include/General.hpp @@ -16,10 +16,10 @@ extern "C" [[noreturn]] void NotImplemented_nid_no_patch(const char* funcName); extern "C" void CxaFinalize_nid_no_patch(void* dsoHandle); extern "C" bool StopAtPrefixLetter_nid_no_patch(const char* str, char** endptr, int base); extern "C" bool RejectIntegerBase_nid_no_patch(const char* str, char** endptr, int base, bool setError); -#ifndef _WIN32 extern "C" int KeepHostStandardError_nid_no_patch(); -extern "C" int HostStandardError_nid_no_patch(); extern "C" int KeepHostStandardOutput_nid_no_patch(); +#ifndef _WIN32 +extern "C" int HostStandardError_nid_no_patch(); #else extern "C" bool AdoptHostStandardStream_nid_no_patch(int standard, int saved); extern "C" std::FILE* HostStandardOutputFile_nid_no_patch(); @@ -27,8 +27,10 @@ extern "C" std::FILE* HostStandardErrorFile_nid_no_patch(); #endif extern "C" bool CopyGuestEnvironment_nid_no_patch(const char* name, std::string& value); +extern "C" int CopyGuestPath_nid_no_patch(const char* source, std::string& destination); 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" 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); @@ -70,6 +72,28 @@ struct ThreadForwards_nid_no_patch { void (APS5_VABI* exit)(void*); }; extern "C" void RegisterThreadForwards_nid_no_patch(const ThreadForwards_nid_no_patch* forwards); +struct PthreadSyncForwards_nid_no_patch { + int (APS5_VABI* mutexattrInit)(void**); + int (APS5_VABI* mutexattrSettype)(void**, int); + int (APS5_VABI* mutexattrDestroy)(void**); + int (APS5_VABI* mutexInit)(void**, void* const*, const char*); + int (APS5_VABI* mutexDestroy)(void**); + int (APS5_VABI* mutexLock)(void**); + int (APS5_VABI* mutexTrylock)(void**); + int (APS5_VABI* mutexUnlock)(void**); + int (APS5_VABI* condInit)(void**, void* const*, const char*); + int (APS5_VABI* condDestroy)(void**); + int (APS5_VABI* condWait)(void**, void**); + int (APS5_VABI* condSignal)(void**); + int (APS5_VABI* condBroadcast)(void**); + int (APS5_VABI* detach)(void*); + int (APS5_VABI* join)(void*, void**); + void (APS5_VABI* yield)(); +}; +extern "C" void RegisterPthreadSyncForwards_nid_no_patch(const PthreadSyncForwards_nid_no_patch* forwards); + +using SigactionFn_nid_no_patch = int (APS5_VABI*)(int, const void*, void*); +extern "C" void RegisterSigaction_nid_no_patch(SigactionFn_nid_no_patch fn, std::uint32_t* interruptSet); #define APS5_INVALID_ARG_EX throw std::invalid_argument(std::string(__func__) + ": invalid argument") diff --git a/core/libs/prx/libc/include/GuestAllocations.hpp b/core/libs/prx/libc/include/GuestAllocations.hpp index 7811725142..c94fd68bd1 100644 --- a/core/libs/prx/libc/include/GuestAllocations.hpp +++ b/core/libs/prx/libc/include/GuestAllocations.hpp @@ -40,6 +40,7 @@ public: const value_type& front() const { return held().front(); } const value_type& back() const { return held().back(); } void clear() { ranges.reset(); } + const std::shared_ptr& Snapshot() const { return ranges; } private: const Ranges& held() const { diff --git a/core/libs/prx/libc/include/GuestArena.hpp b/core/libs/prx/libc/include/GuestArena.hpp index bf0d756d40..49719e365b 100644 --- a/core/libs/prx/libc/include/GuestArena.hpp +++ b/core/libs/prx/libc/include/GuestArena.hpp @@ -55,6 +55,10 @@ void GuestArenaMapFile_nid_postfix(void* pointer, std::size_t bytes, void* secti void* GuestArenaMapAlias_nid_postfix(std::uintptr_t address, std::size_t bytes); void GuestArenaUnmapAlias_nid_postfix(void* alias); bool GuestArenaResidentSpan_nid_postfix(std::uintptr_t address, std::size_t bytes, std::uintptr_t* start, std::uintptr_t* end); +void GuestArenaCompareWhenShared_nid_no_patch(std::uintptr_t address, std::size_t bytes); +void GuestArenaCompareResidentWhenShared_nid_no_patch(std::uintptr_t address, std::size_t bytes); +void GuestArenaComparedPages_nid_no_patch(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count); +void GuestArenaTakeUnreportedPages_nid_no_patch(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count); void GuestArenaForgetSection_nid_postfix(void* section); bool GuestArenaBeginHostImport_nid_no_patch(void* pointer, std::size_t bytes); void GuestArenaEndHostImport_nid_no_patch(void* pointer, std::size_t bytes); diff --git a/core/libs/prx/libc/include/GuestLocale.hpp b/core/libs/prx/libc/include/GuestLocale.hpp index 45e29df3f1..92ca9f13b8 100644 --- a/core/libs/prx/libc/include/GuestLocale.hpp +++ b/core/libs/prx/libc/include/GuestLocale.hpp @@ -58,6 +58,40 @@ struct CollateVtable { std::int64_t (APS5_VABI *hash)(const CollateFacet* self, const char* first, const char* last); }; +struct CodecvtVtable { + FacetVtable facet; + bool (APS5_VABI *alwaysNoconv)(const Facet* self); + int (APS5_VABI *maxLength)(const Facet* self); + int (APS5_VABI *encoding)(const Facet* self); + int (APS5_VABI *in)(Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3); + int (APS5_VABI *out)(Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3); + int (APS5_VABI *unshift)(Facet* self, std::uint64_t* state, char* first3, char* last3, char** next3); + std::size_t (APS5_VABI *length)(Facet* self, std::uint64_t* state, const char* first1, const char* last1, std::size_t count); +}; + +struct NumpunctFacet { + Facet base; + const char* grouping; + char decimalPoint; + char thousandsSep; + const char* falsename; + const char* truename; +}; + +struct NumpunctVtable { + FacetVtable facet; + char (APS5_VABI *decimalPoint)(const NumpunctFacet* self); + char (APS5_VABI *thousandsSep)(const NumpunctFacet* self); + String* (APS5_VABI *grouping)(String* result, const NumpunctFacet* self); + String* (APS5_VABI *falsename)(String* result, const NumpunctFacet* self); + String* (APS5_VABI *truename)(String* result, const NumpunctFacet* self); +}; + +struct UnsupportedMethodsVtable { + FacetVtable facet; + void (APS5_VABI *methods[8])(); +}; + struct IosBase { const void* vtable; std::int64_t standardStream; @@ -89,6 +123,16 @@ static_assert(offsetof(CollateFacet, collation) == 0x10); static_assert(sizeof(CollateFacet) == 0x20); static_assert(offsetof(CollateVtable, compare) == 0x20); static_assert(offsetof(CollateVtable, hash) == 0x30); +static_assert(offsetof(CodecvtVtable, alwaysNoconv) == 0x20); +static_assert(offsetof(CodecvtVtable, in) == 0x38); +static_assert(offsetof(CodecvtVtable, length) == 0x50); +static_assert(sizeof(NumpunctFacet) == 0x30); +static_assert(offsetof(NumpunctFacet, grouping) == 0x10); +static_assert(offsetof(NumpunctFacet, decimalPoint) == 0x18); +static_assert(offsetof(NumpunctFacet, falsename) == 0x20); +static_assert(offsetof(NumpunctFacet, truename) == 0x28); +static_assert(offsetof(NumpunctVtable, decimalPoint) == 0x20); +static_assert(offsetof(NumpunctVtable, truename) == 0x40); static_assert(alignof(LocinfoStorage) == 8); } diff --git a/core/libs/prx/libc/include/HostThreadSlot.hpp b/core/libs/prx/libc/include/HostThreadSlot.hpp new file mode 100644 index 0000000000..39e0e0804d --- /dev/null +++ b/core/libs/prx/libc/include/HostThreadSlot.hpp @@ -0,0 +1,79 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_HOSTTHREADSLOT_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_HOSTTHREADSLOT_HPP + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(__x86_64__) && defined(__GNUC__) +#define HOST_THREAD_SLOT_TEB 1 +#include +#endif + +template +class HostThreadSlot { + static_assert(std::is_trivially_copyable_v && sizeof(TValue) <= sizeof(std::uintptr_t)); + +public: + static TValue Get() noexcept { + const auto bits = load(); + TValue value; + std::memcpy(&value, &bits, sizeof(TValue)); + return value; + } + + static void Set(TValue value) noexcept { + std::uintptr_t bits = 0; + std::memcpy(&bits, &value, sizeof(TValue)); + store(bits); + } + +private: +#ifdef HOST_THREAD_SLOT_TEB + static constexpr unsigned long DirectSlots = 64; + static constexpr std::uintptr_t DirectSlotsOffset = 0x1480; + static constexpr std::uintptr_t ExpansionSlotsOffset = 0x1780; + + static unsigned long index() noexcept { + static const unsigned long allocated = [] { + const auto value = TlsAlloc(); + if (value == 0xffffffffu) std::abort(); + return value; + }(); + return allocated; + } + + static std::uintptr_t readTeb(std::uintptr_t offset) noexcept { + std::uintptr_t value; + __asm__ __volatile__("movq %%gs:(%1), %0" : "=r"(value) : "r"(offset)); + return value; + } + + static std::uintptr_t load() noexcept { + const auto slot = index(); + if (slot < DirectSlots) return readTeb(DirectSlotsOffset + slot * sizeof(void*)); + const auto* expansion = reinterpret_cast(readTeb(ExpansionSlotsOffset)); + return expansion != nullptr ? expansion[slot - DirectSlots] : 0; + } + + static void store(std::uintptr_t bits) noexcept { + const auto slot = index(); + if (slot < DirectSlots) { + __asm__ __volatile__("movq %0, %%gs:(%1)" : : "r"(bits), "r"(DirectSlotsOffset + slot * sizeof(void*)) : "memory"); + } else if (!TlsSetValue(slot, reinterpret_cast(bits))) { + std::abort(); + } + } +#else + static std::uintptr_t& bitsOfThread() noexcept { + static thread_local std::uintptr_t bits = 0; + return bits; + } + + static std::uintptr_t load() noexcept { return bitsOfThread(); } + static void store(std::uintptr_t bits) noexcept { bitsOfThread() = bits; } +#endif +}; + +#endif diff --git a/core/libs/prx/libc/include/JumpBuffer.hpp b/core/libs/prx/libc/include/JumpBuffer.hpp new file mode 100644 index 0000000000..07e272b61e --- /dev/null +++ b/core/libs/prx/libc/include/JumpBuffer.hpp @@ -0,0 +1,18 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_JUMPBUFFER_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_JUMPBUFFER_HPP + +#include "prx/libc/include/general/VabiMacros.hpp" + +namespace JumpBuffer { + +using Sigprocmask = int (APS5_VABI*)(int how, const void* set, void* previousSet); + +extern "C" { + +void GuestSigprocmaskRegister_nid_no_patch(Sigprocmask sigprocmask); + +} + +} + +#endif diff --git a/core/libs/prx/libc/include/RenameDot.hpp b/core/libs/prx/libc/include/RenameDot.hpp index 4364a80d39..a792ac7f32 100644 --- a/core/libs/prx/libc/include/RenameDot.hpp +++ b/core/libs/prx/libc/include/RenameDot.hpp @@ -5,6 +5,13 @@ #include #include "FilesystemError.hpp" +inline std::string TrimTrailingSeparators_nid_no_patch(std::string path) { + const auto separator = [](char character) { return character == '/' || character == '\\'; }; + const std::size_t root = std::filesystem::path(path).root_path().native().size(); + while (path.size() > root && path.size() > 1 && separator(path.back())) path.pop_back(); + return path; +} + inline int RenameDotError_nid_no_patch(const char* path) { std::size_t length = std::char_traits::length(path); const auto separator = [](char character) { return character == '/' || character == '\\'; }; @@ -15,7 +22,7 @@ inline int RenameDotError_nid_no_patch(const char* path) { const bool dot = name == 1 && path[start] == '.'; const bool dotDot = name == 2 && path[start] == '.' && path[start + 1] == '.'; if (!dot && !dotDot) return 0; - const std::string parent = start == 0 ? std::string(".") : std::string(path, start); + const std::string parent = start == 0 ? std::string(".") : TrimTrailingSeparators_nid_no_patch(std::string(path, start)); std::filesystem::path resolved; if (const int error = ResolvePathError_nid_no_patch(parent.c_str(), resolved)) return error; std::error_code status; diff --git a/core/libs/prx/libc/include/SonyCrt.hpp b/core/libs/prx/libc/include/SonyCrt.hpp index cf1879e2c0..fc97c753f1 100644 --- a/core/libs/prx/libc/include/SonyCrt.hpp +++ b/core/libs/prx/libc/include/SonyCrt.hpp @@ -88,7 +88,7 @@ inline int SonyFormatAsctime(char* buffer, std::size_t size, const GuestTm& time static const char* const days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static const char* const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; if (!buffer || size < 26) return EINVAL; - const int year = 1900 + time.tm_year; + const std::int64_t year = std::int64_t{time.tm_year} + 1900; if (time.tm_wday < 0 || time.tm_wday > 6 || time.tm_mon < 0 || time.tm_mon > 11 || time.tm_mday < 1 || time.tm_mday > 31 || time.tm_hour < 0 || time.tm_hour > 23 || time.tm_min < 0 || time.tm_min > 59 || time.tm_sec < 0 || time.tm_sec > 60 || @@ -97,7 +97,7 @@ inline int SonyFormatAsctime(char* buffer, std::size_t size, const GuestTm& time return EINVAL; } std::snprintf(buffer, size, "%.3s %.3s %2d %.2d:%.2d:%.2d %4d\n", days[time.tm_wday], months[time.tm_mon], - time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec, year); + time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec, static_cast(year)); return 0; } @@ -138,20 +138,38 @@ inline void* SonySetConstraintHandlerS(void* handler) { } inline int SonyWcscpyS(std::uint16_t* dest, std::size_t size, const std::uint16_t* src) { - if (!dest || !src || size == 0) { - SonyInvokeConstraintHandler("wcscpy_s", dest, EINVAL); - return EINVAL; + constexpr int GuestEinval = 22; + constexpr int GuestErange = 34; + constexpr std::size_t rsizeMax = (SIZE_MAX >> 1) / sizeof(std::uint16_t); + if (!dest || size == 0) { + SonyInvokeConstraintHandler("wcscpy_s", dest, GuestEinval); + return GuestEinval; } - std::size_t i = 0; - while (i + 1 < size && src[i] != 0) { - dest[i] = src[i]; - ++i; + if (size > rsizeMax) { + SonyInvokeConstraintHandler("wcscpy_s", dest, GuestErange); + return GuestErange; } - dest[i] = 0; - if (src[i] != 0) { - SonyInvokeConstraintHandler("wcscpy_s", dest, ERANGE); - return ERANGE; + if (!src) { + dest[0] = 0; + SonyInvokeConstraintHandler("wcscpy_s", dest, GuestEinval); + return GuestEinval; } + std::size_t length = 0; + while (length < size && src[length] != 0) ++length; + if (length >= size) { + dest[0] = 0; + SonyInvokeConstraintHandler("wcscpy_s", dest, GuestErange); + return GuestErange; + } + const auto destination = reinterpret_cast(dest); + const auto source = reinterpret_cast(src); + const auto distance = destination < source ? source - destination : destination - source; + if (length != 0 && distance < (length + 1) * sizeof(std::uint16_t)) { + dest[0] = 0; + SonyInvokeConstraintHandler("wcscpy_s", dest, GuestEinval); + return GuestEinval; + } + std::memcpy(dest, src, (length + 1) * sizeof(std::uint16_t)); return 0; } diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp index 0c8dd2861f..2023452e25 100644 --- a/core/libs/prx/libc/include/WindowsFormatting.hpp +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -2,7 +2,9 @@ #define CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSFORMATTING_HPP #include "SceTypes.hpp" +#include #include +#include #include #include #include @@ -15,7 +17,6 @@ #include #include -extern "C" int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length); extern "C" std::FILE* GuestStandardOutputFile_nid_no_patch(); namespace LibcDetail { @@ -161,7 +162,279 @@ inline void AppendUtf8(std::string& utf8, char32_t code) { } } -inline std::string NanSpec(const std::string& spec) { +struct FloatSpec { + bool left = false; + bool plus = false; + bool space = false; + bool alternate = false; + bool zero = false; + std::size_t width = 0; + int precision = -1; +}; + +inline FloatSpec ParseFloatSpec(const std::string& spec) { + FloatSpec result; + std::size_t index = 1; + for (; index < spec.size() && std::strchr("-+ #0", spec[index]); ++index) { + result.left |= spec[index] == '-'; + result.plus |= spec[index] == '+'; + result.space |= spec[index] == ' '; + result.alternate |= spec[index] == '#'; + result.zero |= spec[index] == '0'; + } + for (; index < spec.size() && spec[index] >= '0' && spec[index] <= '9'; ++index) { + const auto digit = static_cast(spec[index] - '0'); + if (result.width > (static_cast(INT_MAX) - digit) / 10) + throw std::overflow_error("Formatted output exceeds INT_MAX"); + result.width = result.width * 10 + digit; + } + if (index < spec.size() && spec[index] == '.') { + result.precision = 0; + for (++index; index < spec.size(); ++index) { + const int digit = spec[index] - '0'; + if (result.precision > (INT_MAX - digit) / 10) + throw std::overflow_error("Floating precision exceeds int range"); + result.precision = result.precision * 10 + digit; + } + } + return result; +} + +inline std::string PadFloat(const FloatSpec& spec, const std::string& prefix, const std::string& body) { + const std::size_t length = prefix.size() + body.size(); + if (length >= spec.width) return prefix + body; + const std::string padding(spec.width - length, spec.left || !spec.zero ? ' ' : '0'); + if (spec.left) return prefix + body + padding; + return spec.zero ? prefix + padding + body : padding + prefix + body; +} + +inline std::string HexFloat(const std::string& spec, char conversion, double value, std::size_t maximumLength = INT_MAX) { + const FloatSpec parsed = ParseFloatSpec(spec); + const bool plus = parsed.plus, space = parsed.space, alternate = parsed.alternate; + const std::size_t width = parsed.width; + const int precision = parsed.precision; + const bool upper = conversion == 'A'; + const std::uint64_t bits = std::bit_cast(value); + int exponent = static_cast((bits >> 52) & 0x7ff); + std::uint64_t fraction = bits & ((std::uint64_t{1} << 52) - 1); + std::uint64_t lead = 1; + if (exponent != 0) { + exponent -= 1023; + } else if (fraction == 0) { + lead = 0; + } else { + exponent = -1022; + while ((fraction & (std::uint64_t{1} << 52)) == 0) { + fraction <<= 1; + --exponent; + } + fraction &= (std::uint64_t{1} << 52) - 1; + } + int digits = 13; + if (precision >= 0 && precision < 13) { + const int dropped = (13 - precision) * 4; + const std::uint64_t remainder = fraction & ((std::uint64_t{1} << dropped) - 1); + fraction >>= dropped; + if (remainder >= std::uint64_t{1} << (dropped - 1) && ++fraction >> (precision * 4)) { + fraction &= (std::uint64_t{1} << (precision * 4)) - 1; + ++lead; + } + digits = precision; + } else if (precision < 0) { + for (; digits > 0 && (fraction & 0xf) == 0; --digits) fraction >>= 4; + } + const char* hex = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + std::string suffix = upper ? "P" : "p"; + suffix += exponent < 0 ? '-' : '+'; + suffix += std::to_string(exponent < 0 ? -exponent : exponent); + std::string prefix = (bits >> 63) != 0 ? "-" : plus ? "+" : space ? " " : ""; + prefix += upper ? "0X" : "0x"; + const auto fractionalDigits = static_cast(precision > 13 ? precision : digits); + const bool point = fractionalDigits != 0 || alternate; + const std::size_t length = prefix.size() + 1 + point + fractionalDigits + suffix.size(); + if (length > maximumLength || width > maximumLength) + throw std::overflow_error("Formatted output exceeds INT_MAX"); + std::string body(1, hex[lead]); + if (point) body += '.'; + for (int digit = digits - 1; digit >= 0; --digit) body += hex[(fraction >> (digit * 4)) & 0xf]; + if (precision > 13) body.append(static_cast(precision - 13), '0'); + body += suffix; + return PadFloat(parsed, prefix, body); +} + +struct DecimalDigits { + std::string digits; + long long exponent = 0; + bool truncated = false; + bool tiesDown = false; +}; + +inline std::string DecimalChunks(std::vector words) { + std::string text; + while (!words.empty()) { + std::uint64_t remainder = 0; + for (std::size_t index = words.size(); index-- > 0;) { + const std::uint64_t current = (remainder << 32) | words[index]; + words[index] = static_cast(current / 1000000000u); + remainder = current % 1000000000u; + } + while (!words.empty() && words.back() == 0) words.pop_back(); + std::string chunk = std::to_string(remainder); + if (!words.empty()) chunk.insert(0, 9 - chunk.size(), '0'); + text.insert(0, chunk); + } + return text; +} + +inline DecimalDigits ConsoleDecimalDigits(long double value) { + DecimalDigits result; + if (value == 0) return result; + int binary = 0; + const auto mantissa = static_cast(std::ldexp(std::frexp(std::fabs(value), &binary), 64)); + const long long estimate = static_cast(binary) * 30103 / 100000; + const long long last = (estimate >= 0 ? estimate / 4 : -((3 - estimate) / 4)) * 4 - 20; + result.tiesDown = estimate >= 8; + binary -= 64; + std::string integer; + std::string fraction; + bool remaining = false; + if (binary >= 0) { + const auto word = static_cast(binary) / 32; + const auto bit = static_cast(binary) % 32; + std::vector words(word + 3, 0); + for (std::size_t part = 0; part < 2; ++part) { + const std::uint64_t shifted = ((mantissa >> (32 * part)) & 0xffffffffu) << bit; + words[word + part] |= static_cast(shifted); + words[word + part + 1] |= static_cast(shifted >> 32); + } + while (!words.empty() && words.back() == 0) words.pop_back(); + integer = DecimalChunks(std::move(words)); + } else { + const auto shift = static_cast(-static_cast(binary)); + const std::uint64_t whole = shift >= 64 ? 0 : mantissa >> shift; + if (whole != 0) integer = std::to_string(whole); + std::vector words(shift / 32 + 2, 0); + const std::uint64_t remainder = shift >= 64 ? mantissa : mantissa & ((std::uint64_t{1} << shift) - 1); + words[0] = static_cast(remainder); + words[1] = static_cast(remainder >> 32); + const std::size_t top = shift / 32; + const auto bit = static_cast(shift % 32); + const auto significant = [&] { + const std::size_t leading = fraction.find_first_not_of('0'); + return integer.size() + (leading == std::string::npos ? 0 : fraction.size() - leading); + }; + remaining = remainder != 0; + while (remaining && significant() < 30) { + std::uint64_t carry = 0; + for (auto& word : words) { + const std::uint64_t product = static_cast(word) * 1000000000u + carry; + word = static_cast(product); + carry = product >> 32; + } + const std::uint64_t window = (static_cast(words[top + 1]) << 32) | words[top]; + const std::string chunk = std::to_string(static_cast(window >> bit)); + words[top + 1] = 0; + words[top] &= bit == 0 ? 0 : (std::uint32_t{1} << bit) - 1; + fraction += std::string(9 - chunk.size(), '0') + chunk; + remaining = std::any_of(words.begin(), words.end(), [](std::uint32_t word) { return word != 0; }); + } + } + if (!integer.empty()) { + result.exponent = static_cast(integer.size()) - 1; + result.digits = integer + fraction; + } else { + const std::size_t leading = fraction.find_first_not_of('0'); + result.exponent = -static_cast(leading) - 1; + result.digits = fraction.substr(leading); + } + const long long kept = result.exponent - last + 1; + if (static_cast(result.digits.size()) > kept) { + result.truncated = result.digits.find_first_not_of('0', static_cast(kept)) != std::string::npos; + result.digits.resize(static_cast(kept)); + } + result.truncated |= remaining; + return result; +} + +inline DecimalDigits RoundDecimal(const DecimalDigits& value, long long count) { + if (value.digits.empty()) return value; + if (count <= 0) { + if (count == 0 && value.digits[0] >= '5') return {"1", value.exponent + 1}; + return {}; + } + if (count >= static_cast(value.digits.size())) return value; + DecimalDigits result{value.digits.substr(0, static_cast(count)), value.exponent}; + const auto next = static_cast(count); + if (value.digits[next] < '5') return result; + if (value.digits[next] == '5' && value.tiesDown && !value.truncated && value.digits.find_first_not_of('0', next + 1) == std::string::npos) + return result; + for (std::size_t index = result.digits.size(); index-- > 0;) { + if (result.digits[index] != '9') { + ++result.digits[index]; + return result; + } + result.digits[index] = '0'; + } + result.digits.insert(result.digits.begin(), '1'); + result.digits.pop_back(); + ++result.exponent; + return result; +} + +inline char DigitAt(const DecimalDigits& value, long long position) { + const long long index = value.exponent - position; + if (value.digits.empty() || index < 0 || index >= static_cast(value.digits.size())) return '0'; + return value.digits[static_cast(index)]; +} + +inline std::string DecimalFloat(const std::string& spec, char conversion, long double value, std::size_t maximumLength = INT_MAX) { + const FloatSpec parsed = ParseFloatSpec(spec); + const DecimalDigits exact = ConsoleDecimalDigits(value); + const bool upper = conversion == 'E' || conversion == 'G'; + const char lower = static_cast(conversion | 0x20); + long long precision = parsed.precision < 0 ? 6 : parsed.precision; + bool exponential = lower == 'e'; + bool strip = false; + if (lower == 'g') { + if (precision == 0) precision = 1; + const long long exponent = exact.digits.empty() ? 0 : RoundDecimal(exact, precision).exponent; + exponential = !(precision > exponent && exponent >= -4); + precision = exponential ? precision - 1 : precision - 1 - exponent; + strip = !parsed.alternate; + } + const DecimalDigits rounded = RoundDecimal(exact, exponential ? precision + 1 : exact.exponent + 1 + precision); + const long long exponent = rounded.digits.empty() ? 0 : rounded.exponent; + if (strip) { + const long long last = rounded.digits.empty() ? 0 : rounded.exponent - static_cast(rounded.digits.size()) + 1; + long long needed = exponential ? exponent - last : -last; + while (needed > 0 && DigitAt(rounded, (exponential ? exponent : 0) - needed) == '0') --needed; + precision = std::max(0LL, std::min(precision, needed)); + } + const std::string prefix = std::signbit(value) ? "-" : parsed.plus ? "+" : parsed.space ? " " : ""; + const bool point = precision > 0 || parsed.alternate; + const std::string magnitude = std::to_string(exponent < 0 ? -exponent : exponent); + const long long whole = exponential || exponent < 0 ? 1 : exponent + 1; + const long long exponentLength = exponential ? 2 + static_cast(std::max(magnitude.size(), 2)) : 0; + const long long length = static_cast(prefix.size()) + whole + point + precision + exponentLength; + if (length > static_cast(maximumLength) || parsed.width > maximumLength) + throw std::overflow_error("Formatted output exceeds INT_MAX"); + std::string body; + body.reserve(static_cast(length)); + const long long first = exponential ? exponent : std::max(exponent, 0LL); + for (long long position = first; position >= (exponential ? exponent : 0); --position) body += DigitAt(rounded, position); + if (point) body += '.'; + const long long start = exponential ? exponent - 1 : -1; + for (long long position = start; position > start - precision; --position) body += DigitAt(rounded, position); + if (exponential) { + body += upper ? 'E' : 'e'; + body += exponent < 0 ? '-' : '+'; + if (magnitude.size() < 2) body += '0'; + body += magnitude; + } + return PadFloat(parsed, prefix, body); +} + +inline std::string PaddedStringSpec(const std::string& spec) { std::string result = "%"; std::size_t index = 1; for (; index < spec.size() && std::strchr("-+ #0", spec[index]); ++index) @@ -170,6 +443,14 @@ inline std::string NanSpec(const std::string& spec) { return result + 's'; } +inline std::string NanText(const std::string& spec, bool negative, bool upper) { + std::string sign; + if (negative) sign = "-"; + else if (spec.find('+') != std::string::npos) sign = "+"; + else if (spec.find(' ') != std::string::npos) sign = " "; + return sign + (upper ? "NAN" : "nan"); +} + inline std::string FormatPointer(const std::string& spec, const void* pointer) { std::size_t index = 1; bool left = false; @@ -205,7 +486,8 @@ inline std::string FormatPointer(const std::string& spec, const void* pointer) { return std::string(padding, ' ') + digits; } -constexpr size_t maximumArgumentPosition = 99; +inline constexpr char KnownConversions[] = "diouxXeEfFgGaAcCsSpn"; +constexpr size_t maximumArgumentPosition = 65535; inline size_t ArgumentPosition(const char*& format) { const char* end = format; @@ -214,7 +496,8 @@ inline size_t ArgumentPosition(const char*& format) { size_t position = 0; for (const char* digit = format; digit != end && position <= maximumArgumentPosition; ++digit) position = position * 10 + static_cast(*digit - '0'); - if (position == 0 || position > maximumArgumentPosition) throw std::invalid_argument("Format argument position out of range"); + if (position == 0) return 0; + if (position > maximumArgumentPosition) throw std::invalid_argument("Format argument position out of range"); format = end + 1; return position; } @@ -258,7 +541,6 @@ inline std::vector NumberedArgumentClasses(const char* format) { inline int FormatWindows(char* buffer, size_t size, const char* format, const void* source, std::string* complete = nullptr, bool secure = false) { if (!format || !source) throw std::invalid_argument("Null formatting argument"); - const int savedError = errno; const char* const formatStart = format; FormatArguments args(source); args.Number(NumberedArgumentClasses(format)); @@ -279,16 +561,24 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo } if (const size_t position = ArgumentPosition(format)) args.Seek(position); std::string spec = "%"; - for (; *format && std::strchr("-+ #0'", *format); ++format) + bool unmeasuredUnknownFlag = false; + bool zeroFlag = false; + for (; *format && std::strchr("-+ #0'", *format); ++format) { if (*format != '\'') spec += *format; + if (std::strchr("+ '", *format)) unmeasuredUnknownFlag = true; + if (*format == '0') zeroFlag = true; + } + bool widthGiven = false; if (*format == '*') { ++format; const int width = star(); if (width < 0) spec += '-'; spec += std::to_string(width < 0 ? -static_cast(width) : width); + widthGiven = true; } else { - while (*format >= '0' && *format <= '9') spec += *format++; + for (; *format >= '0' && *format <= '9'; widthGiven = true) spec += *format++; } + if (zeroFlag && widthGiven) unmeasuredUnknownFlag = true; size_t precisionLimit = std::numeric_limits::max(); if (*format == '.') { ++format; @@ -319,6 +609,13 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo const char conversion = *format; if (!conversion) throw std::invalid_argument("Incomplete format conversion"); ++format; + if (!std::strchr(KnownConversions, conversion)) { + if (secure || unmeasuredUnknownFlag) + throw std::invalid_argument("Unsupported format conversion '" + spec + length + conversion + "' in \"" + std::string(formatStart).substr(0, 160) + "\""); + const char text[] = {'%', conversion, '\0'}; + output.Value(PaddedStringSpec(spec), conversion == '%' ? "%" : text); + continue; + } const bool integerLength = length.empty() || length == "h" || length == "hh" || length == "l" || length == "ll" || length == "j" || length == "z" || length == "t"; if (conversion == 'd' || conversion == 'i') { @@ -346,18 +643,32 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo spec = spec.substr(0, spec.find('.')) + ".1"; output.Value(spec + "ll" + conversion, value); } else if (std::strchr("aAeEfFgG", conversion)) { - const char* nan = conversion >= 'a' ? "nan" : "NAN"; if (length == "L") { static_assert(sizeof(long double) == 16); static_assert(std::numeric_limits::digits == 64); const long double value = args.Next(); - if (std::isnan(value)) output.Value(NanSpec(spec), nan); - else output.Value(spec + "L" + conversion, value); + if (std::isnan(value)) { + output.Value(PaddedStringSpec(spec), NanText(spec, std::signbit(value), conversion < 'a').c_str()); + } else if (conversion != 'a' && conversion != 'A' && std::isfinite(value)) { + const std::string text = DecimalFloat(spec, conversion, value, static_cast(INT_MAX - output.Count())); + output.Append(text.data(), text.size()); + } else { + output.Value(spec + "L" + conversion, value); + } } else { if (!length.empty() && length != "l") throw std::invalid_argument("Invalid floating length"); const double value = args.Next(); - if (std::isnan(value)) output.Value(NanSpec(spec), nan); - else output.Value(spec + conversion, value); + if (std::isnan(value)) { + output.Value(PaddedStringSpec(spec), NanText(spec, std::signbit(value), conversion < 'a').c_str()); + } else if ((conversion == 'a' || conversion == 'A') && std::isfinite(value)) { + const std::string text = HexFloat(spec, conversion, value, static_cast(INT_MAX - output.Count())); + output.Append(text.data(), text.size()); + } else if (std::isfinite(value)) { + const std::string text = DecimalFloat(spec, conversion, value, static_cast(INT_MAX - output.Count())); + output.Append(text.data(), text.size()); + } else { + output.Value(spec + conversion, value); + } } } else if (conversion == 'c' && (length.empty() || length == "h")) { output.Value(spec + conversion, args.Next()); @@ -377,8 +688,7 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo if (!value) value = u"(null)"; std::string utf8; while (utf8.size() < precisionLimit && *value) { - char32_t code = *value++; - if (code >= 0xd800 && code < 0xdc00 && *value >= 0xdc00 && *value < 0xe000) code = 0x10000 + ((code - 0xd800) << 10) + (*value++ - 0xdc00); + const char32_t code = *value++; const auto previousSize = utf8.size(); AppendUtf8(utf8, code); if (utf8.size() > precisionLimit) { @@ -390,10 +700,6 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo } else if (conversion == 'p' && (length.empty() || length == "l")) { const std::string text = FormatPointer(spec, args.Next()); output.Append(text.data(), text.size()); - } else if (conversion == 'm') { - char message[128]; - strerror_r_nid_postfix(savedError, message, sizeof(message)); - output.Value(spec + 's', static_cast(message)); } else if (conversion == 'n' && secure) { throw FormatConstraintViolation("Format count conversion"); } else if (conversion == 'n' && integerLength && spec == "%") { diff --git a/core/libs/prx/libc/include/WindowsMappings.hpp b/core/libs/prx/libc/include/WindowsMappings.hpp index 624685e8a9..7a7a3f7869 100644 --- a/core/libs/prx/libc/include/WindowsMappings.hpp +++ b/core/libs/prx/libc/include/WindowsMappings.hpp @@ -5,6 +5,9 @@ #include #include #include +#ifdef APS5_COUNT_MAPPING_QUERIES +#include +#endif #include #include #include @@ -28,18 +31,48 @@ public: return *mappings; } +#ifdef APS5_COUNT_MAPPING_QUERIES + static std::uint64_t MemoryQueries() { + return queries.load(std::memory_order_relaxed); + } +#endif + void* Reserve(void* address, std::size_t bytes) { return allocate(GetCurrentProcess(), address, bytes, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0); } std::vector> Commit(void* address, std::size_t bytes, DWORD protection, std::size_t granule, bool watched) { + if (granule == 0) throw std::invalid_argument("guest memory commit granule is zero"); std::vector> created; std::lock_guard lock(mutex); collectableRanges.clear(); + const DWORD flags = MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER | (watched ? MEM_WRITE_WATCH : 0); + const auto cut = [](std::uintptr_t at, std::size_t size, const char* operation) { + if (!VirtualFree(reinterpret_cast(at), size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) fail(operation); + }; + const auto replace = [&](std::uintptr_t at, std::size_t size) { + if (!allocate(GetCurrentProcess(), reinterpret_cast(at), size, flags, protection, nullptr, 0)) fail("replace guest placeholder with private memory"); + if (!created.empty() && created.back().first + created.back().second == at) created.back().second += size; + else created.emplace_back(at, size); + }; const auto end = reinterpret_cast(address) + bytes; for (auto cursor = reinterpret_cast(address); cursor < end;) { const auto memory = query(cursor); - const auto stop = std::min(end, reinterpret_cast(memory.BaseAddress) + memory.RegionSize); + const auto regionEnd = reinterpret_cast(memory.BaseAddress) + memory.RegionSize; + const auto stop = std::min(end, regionEnd); + if (memory.State == MEM_RESERVE && std::min(end, cursor + granule) <= regionEnd) { + forgetClean(cursor, stop); + const auto holder = reinterpret_cast(memory.AllocationBase); + if (holder != cursor) cut(holder, cursor - holder, "split guest placeholder prefix"); + if (stop != regionEnd) cut(cursor, stop - cursor, "split guest placeholder suffix"); + while (cursor < stop && std::min(end, cursor + granule) <= stop) { + const auto size = std::min(end, cursor + granule) - cursor; + if (cursor + size != stop) cut(cursor, size, "split guest placeholder suffix"); + replace(cursor, size); + cursor += size; + } + continue; + } if (memory.State == MEM_RESERVE) { const auto limit = std::min(end, cursor + granule); auto placeholderEnd = stop; @@ -50,10 +83,7 @@ public: } const auto size = std::min(limit, placeholderEnd) - cursor; reset(cursor, size); - const DWORD flags = MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER | (watched ? MEM_WRITE_WATCH : 0); - if (!allocate(GetCurrentProcess(), reinterpret_cast(cursor), size, flags, protection, nullptr, 0)) fail("replace guest placeholder with private memory"); - if (!created.empty() && created.back().first + created.back().second == cursor) created.back().second += size; - else created.emplace_back(cursor, size); + replace(cursor, size); cursor += size; } else { if (memory.State != MEM_COMMIT) throw std::runtime_error("guest memory is not committed"); @@ -96,6 +126,7 @@ public: auto shared = slot.page.lock(); if (!shared) { shared = std::make_shared(); + shared->comparedWhenShared = slot.comparedWhenShared; slot.page = shared; } return shared; @@ -105,6 +136,8 @@ public: const auto shared = slot.page.lock(); return resident && !slot.stored && (!shared || shared->aliases.empty()); }; + const bool perPage = !resident && sectionBytes == SIZE_MAX; + if (perPage) commitSection(section, offset, bytes); for (std::size_t done = 0; done < bytes;) { const auto base = cursor + done; if (single(done)) { @@ -134,21 +167,56 @@ public: done += runBytes; continue; } - const auto slot = slotOf(done); - const auto shared = pageOf(*slot); - const auto aliases = shared->aliases; - for (const auto alias : aliases) { - if (views.at(alias).resident) demote(alias); + if (sectionBytes != SIZE_MAX) { + const auto slot = slotOf(done); + const auto shared = pageOf(*slot); + const auto aliases = shared->aliases; + for (const auto alias : aliases) { + if (views.at(alias).resident) demote(alias); + views.at(alias).aliasDemoted = false; + } + const auto viewBytes = std::min(pageBytes, sectionBytes - done); + split(base, pageBytes); + mapShared(base, section, offset + done, protection, viewProtection, viewBytes); + slot->stored = true; + shared->aliases.push_back(base); + const auto created = views.emplace(base, View{shared, protection, {}, 0, owned, offset + done, 0, 0, false, slot, hostPages(viewBytes)}).first; + if (shared->comparedWhenShared) created->second.unreported = allSubpages; + remember(freshRanges, base, base + pageBytes); + invalidate(*shared); + done += pageBytes; + continue; } - const auto viewBytes = std::min(pageBytes, sectionBytes - done); - split(base, pageBytes); - mapShared(base, section, offset + done, protection, viewProtection, viewBytes); - slot->stored = true; - shared->aliases.push_back(base); - views.emplace(base, View{shared, protection, {}, 0, owned, offset + done, 0, 0, false, slot, hostPages(viewBytes)}); - remember(freshRanges, base, base + pageBytes); - invalidate(*shared); - done += pageBytes; + std::vector, std::shared_ptr>> pages; + pages.reserve((bytes - done) / pageBytes); + auto runBytes = std::size_t{0}; + do { + const auto slot = slotOf(done + runBytes); + const auto shared = pageOf(*slot); + const auto aliases = shared->aliases; + for (const auto alias : aliases) { + if (views.at(alias).resident) demote(alias); + views.at(alias).aliasDemoted = false; + } + pages.emplace_back(slot, shared); + runBytes += pageBytes; + } while (done + runBytes < bytes && !single(done + runBytes)); + if (!perPage) commitSection(section, offset + done, runBytes); + split(base, runBytes); + mapRun(base, runBytes, section, offset + done); + DWORD previous; + if (!VirtualProtect(reinterpret_cast(base), runBytes, protection, &previous)) fail("protect shared guest pages"); + collectableRanges.clear(); + for (std::size_t at = 0; at < runBytes; at += pageBytes) { + auto& [slot, shared] = pages[at / pageBytes]; + slot->stored = true; + shared->aliases.push_back(base + at); + invalidate(*shared); + const auto created = views.emplace(base + at, View{std::move(shared), protection, {}, 0, owned, offset + done + at, 0, 0, false, std::move(slot)}).first; + if (created->second.page->comparedWhenShared) created->second.unreported = allSubpages; + } + remember(freshRanges, base, base + runBytes); + done += runBytes; } } @@ -186,21 +254,56 @@ public: } } + void CompareWhenShared(std::uintptr_t address, std::size_t bytes, bool residentOnly = false) { + std::lock_guard lock(mutex); + collectableRanges.clear(); + for (auto it = views.lower_bound(address & ~(pageBytes - 1)); it != views.end() && it->first < address + bytes; ++it) { + if (residentOnly && !it->second.resident && !it->second.aliasDemoted) continue; + auto& page = *it->second.page; + const bool marked = page.comparedWhenShared; + page.comparedWhenShared = true; + it->second.slot->comparedWhenShared = true; + for (const auto alias : page.aliases) { + auto& view = views.at(alias); + if (view.resident) continue; + if (!marked) view.unreported = allSubpages; + if (view.armed == 0) continue; + protectSubpages(alias, view.armed, view.protection, "stop arming a compared shared page", view.viewBytes); + view.armed = 0; + } + } + } + + void ComparedPages(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count, bool unreported) { + std::lock_guard lock(mutex); + const auto capacity = *count; + *count = 0; + const auto end = address + bytes; + for (auto it = views.lower_bound(address & ~(pageBytes - 1)); it != views.end() && it->first < end; ++it) { + auto& view = it->second; + if (view.resident || !view.page->comparedWhenShared) continue; + const auto listed = static_cast(subpagesOf(it->first, address, end) & (unreported ? view.unreported : allSubpages)); + for (std::size_t sub = 0; sub < subpages; ++sub) { + const auto bit = static_cast(1u << sub); + if ((listed & bit) == 0) continue; + if (*count == capacity) return; + pages[(*count)++] = reinterpret_cast(it->first + sub * subpageBytes); + if (unreported) view.unreported &= static_cast(~bit); + } + } + } + bool HandleWrite(std::uintptr_t address) { std::lock_guard lock(mutex); const auto base = address & ~(pageBytes - 1); const auto found = views.find(base); if (found == views.end() || !writable(found->second.protection)) return false; auto& view = found->second; - const auto written = subpagesOf(base, address, address + 1); - if (view.resident && (view.armed & written) == 0) { + if (view.resident) { const auto memory = query(address); return memory.State == MEM_COMMIT && writable(memory.Protect & 0xffu); } - if (view.resident) { - resumeResidentWrites(base); - return true; - } + const auto written = subpagesOf(base, address, address + 1); invalidate(*view.page, written); protectSubpages(base, written, view.protection, "resume shared memory write", view.viewBytes); view.armed &= static_cast(~written); @@ -286,7 +389,7 @@ public: } for (std::size_t done = 0; done < bytes; done += pageBytes) { const auto found = views.find(address + done); - if (found != views.end() && found->second.resident) demote(address + done); + if (found != views.end() && found->second.resident) demote(address + done, true); } auto view = views.find(address); if (view == views.end()) throw refuse("the range does not start at a shared view"); @@ -350,6 +453,7 @@ public: if (!watchResident) return false; continue; } + if (it->second.page->comparedWhenShared) return false; if (it->second.protection != PAGE_NOACCESS) continue; for (auto at = std::max(address, it->first); at < std::min(end, it->first + pageBytes); at += 4096) { if ((it->second.collected & (1u << ((at - it->first) / 4096))) == 0) return false; @@ -496,7 +600,7 @@ private: for (auto cursor = address; cursor < end;) { const auto base = cursor & ~(pageBytes - 1); if (const auto found = viewAt(base, position); found != views.end()) { - if (found->second.protection == PAGE_NOACCESS || (found->second.resident && !watchResident)) return false; + if (found->second.protection == PAGE_NOACCESS || (found->second.resident ? !watchResident : found->second.page->comparedWhenShared)) return false; cursor = std::min(end, base + pageBytes); continue; } @@ -522,6 +626,7 @@ private: }(); std::vector aliases; std::uint32_t pins = 0; + bool comparedWhenShared = false; }; struct Section { HANDLE handle; @@ -553,6 +658,7 @@ private: struct Physical { std::weak_ptr page; bool stored = false; + bool comparedWhenShared = false; std::shared_ptr saved; std::size_t savedOffset = 0; }; @@ -569,12 +675,13 @@ private: std::shared_ptr slot; std::size_t viewBytes = pageBytes; std::uint32_t hostImports = 0; + bool aliasDemoted = false; + std::uint8_t unreported = 0; }; struct Run { std::uintptr_t end; std::array collected; std::uint32_t fresh; - bool writeObserved = false; }; struct Window { void* view = nullptr; @@ -588,14 +695,12 @@ private: void collectResident(std::uintptr_t start, Run& run, std::uintptr_t from, std::uintptr_t stop, void** pages, std::size_t* count, bool clear, Collection collection) { const auto first = from & ~std::uintptr_t{4095}; - const bool armed = clear && run.fresh == 0 && !run.writeObserved && collection != Collection::Fresh && run.end - start >= 0x40000 && armResident(first, stop); if (run.fresh == 0 && collection != Collection::Fresh) { ULONG_PTR reported = (stop - first + 4095) / 4096; DWORD granularity = 0; if (GetWriteWatch(clear ? WRITE_WATCH_FLAG_RESET : 0, reinterpret_cast(first), stop - first, pages + *count, &reported, &granularity) != 0) fail("collect resident guest writes"); for (ULONG_PTR index = 0; index < reported; ++index) pages[*count + index] = reinterpret_cast(std::max(from, reinterpret_cast(pages[*count + index]))); *count += reported; - if (armed) rememberClean(from, stop); return; } std::vector written; @@ -633,44 +738,6 @@ private: if (collection == Collection::Fresh) flushReset(stop); } - void resumeResidentWrites(std::uintptr_t address) { - const auto run = std::prev(residentRuns.upper_bound(address)); - run->second.writeObserved = true; - forgetClean(run->first, run->second.end); - auto first = views.lower_bound(run->first); - while (first != views.end() && first->first < run->second.end) { - const auto begin = first->first; - auto end = begin; - const auto protection = first->second.protection; - auto next = first; - bool armed = false; - do { - armed = armed || next->second.armed != 0; - end = next->first + pageBytes; - ++next; - } while (next != views.end() && next->first == end && end < run->second.end && next->second.protection == protection); - if (armed) { - DWORD previous; - if (!VirtualProtect(reinterpret_cast(begin), end - begin, protection, &previous)) fail("resume resident guest writes"); - } - for (; first != next; ++first) first->second.armed = 0; - } - } - - bool armResident(std::uintptr_t from, std::uintptr_t stop) { - const auto first = views.lower_bound(from & ~(pageBytes - 1)); - if (first == views.end() || !writable(first->second.protection)) return false; - const auto protection = first->second.protection; - for (auto it = first; it != views.end() && it->first < stop; ++it) { - if (!it->second.resident || it->second.protection != protection || it->second.hostWrites != 0 || it->second.hostImports != 0 || it->second.page->pins != 0) return false; - } - DWORD previous; - const auto readOnly = protection == PAGE_EXECUTE_READWRITE ? PAGE_EXECUTE_READ : PAGE_READONLY; - if (!VirtualProtect(reinterpret_cast(from), stop - from, readOnly, &previous)) fail("arm resident memory write tracking"); - for (auto it = first; it != views.end() && it->first < stop; ++it) it->second.armed |= subpagesOf(it->first, from, stop); - return true; - } - Window open(HANDLE section, std::uint64_t offset, std::size_t bytes) { const auto lead = offset % granularity; Window window; @@ -694,12 +761,23 @@ private: return (bytes + subpageBytes - 1) & ~(subpageBytes - 1); } - void mapShared(std::uintptr_t base, HANDLE section, std::uint64_t offset, DWORD protection, DWORD viewProtection = PAGE_EXECUTE_READWRITE, std::size_t sectionBytes = pageBytes) { + void commitSection(HANDLE section, std::uint64_t offset, std::size_t bytes) { + auto window = open(section, offset, bytes); + const auto memory = query(reinterpret_cast(window.bytes)); + if ((memory.State != MEM_COMMIT || memory.RegionSize < bytes) && !VirtualAlloc(window.bytes, bytes, MEM_COMMIT, PAGE_READWRITE)) { + const auto error = GetLastError(); + close(window); + throw std::system_error(static_cast(error), std::system_category(), "commit direct memory pages"); + } + close(window); + } + + void mapShared(std::uintptr_t base, HANDLE section, std::uint64_t offset, DWORD protection, DWORD viewProtection = PAGE_EXECUTE_READWRITE, std::size_t sectionBytes = pageBytes, bool committed = false) { void* page = reinterpret_cast(base); const auto viewBytes = hostPages(sectionBytes); if (viewBytes != pageBytes) split(base, viewBytes); if (!map(section, GetCurrentProcess(), page, offset, sectionBytes, MEM_REPLACE_PLACEHOLDER, viewProtection, nullptr, 0)) fail("map shared guest page"); - if (query(base).State == MEM_RESERVE && !VirtualAlloc(page, viewBytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) fail("commit shared guest page"); + if (!committed && query(base).State == MEM_RESERVE && !VirtualAlloc(page, viewBytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) fail("commit shared guest page"); DWORD previous; if (!VirtualProtect(page, viewBytes, protection, &previous)) fail("protect shared guest page"); if (viewBytes == pageBytes) return; @@ -708,6 +786,47 @@ private: if (!VirtualProtect(zeros, pageBytes - viewBytes, protection, &previous)) fail("protect guest pages past the end of a file"); } + void mapRun(std::uintptr_t base, std::size_t bytes, HANDLE section, std::uint64_t offset) { + if (!map(section, GetCurrentProcess(), reinterpret_cast(base), offset, bytes, MEM_REPLACE_PLACEHOLDER, PAGE_EXECUTE_READWRITE, nullptr, 0)) fail("map a run of shared guest pages"); + } + + void breakView(std::uintptr_t at) { + const auto memory = query(at); + if (memory.State == MEM_FREE || memory.Type != MEM_MAPPED) return; + const auto start = reinterpret_cast(memory.AllocationBase); + if (start == at) return; + const auto first = views.find(start); + if (first == views.end()) return; + struct Part { + std::uintptr_t base; + std::uintptr_t end; + DWORD protection; + bool committed; + }; + std::vector parts; + auto stop = start; + for (;;) { + const auto part = query(stop); + if (part.AllocationBase != memory.AllocationBase) break; + const auto partEnd = reinterpret_cast(part.BaseAddress) + part.RegionSize; + parts.push_back({stop, partEnd, part.Protect, part.State == MEM_COMMIT}); + stop = partEnd; + } + const auto section = first->second.section; + const auto offset = first->second.offset; + if (!unmap(GetCurrentProcess(), reinterpret_cast(start), MEM_PRESERVE_PLACEHOLDER)) fail("unmap a run of shared guest pages to split it"); + split(start, at - start); + mapRun(start, at - start, section->handle, offset); + mapRun(at, stop - at, section->handle, offset + (at - start)); + for (const auto& part : parts) { + if (!part.committed) continue; + for (const auto [from, to] : {std::pair{part.base, std::min(part.end, at)}, std::pair{std::max(part.base, at), part.end}}) { + DWORD previous; + if (from < to && !VirtualProtect(reinterpret_cast(from), to - from, part.protection, &previous)) fail("protect a split run of shared guest pages"); + } + } + } + static bool blank(std::uintptr_t page) { const auto* words = reinterpret_cast(page); for (std::size_t i = 0; i < pageBytes / sizeof(std::uint64_t); ++i) { @@ -716,7 +835,7 @@ private: return true; } - void demote(std::uintptr_t address) { + void demote(std::uintptr_t address, bool alias = false) { const auto run = std::prev(residentRuns.upper_bound(address)); const auto start = run->first; const auto stop = run->second.end; @@ -734,6 +853,8 @@ private: mapShared(page, view.section->handle, view.offset, view.protection); view.slot->stored = true; view.resident = false; + view.aliasDemoted = alias; + view.unreported = view.page->comparedWhenShared ? allSubpages : std::uint8_t{0}; view.seen.fill(0); view.armed = 0; view.collected = 0; @@ -890,11 +1011,14 @@ private: static MEMORY_BASIC_INFORMATION query(std::uintptr_t address) { MEMORY_BASIC_INFORMATION memory{}; +#ifdef APS5_COUNT_MAPPING_QUERIES + queries.fetch_add(1, std::memory_order_relaxed); +#endif if (VirtualQuery(reinterpret_cast(address), &memory, sizeof(memory)) != sizeof(memory)) fail("query guest memory"); return memory; } - static void split(std::uintptr_t address, std::size_t bytes) { + static std::uintptr_t split(std::uintptr_t address, std::size_t bytes) { auto memory = query(address); memory = query(reinterpret_cast(memory.AllocationBase)); if (memory.State != MEM_RESERVE) throw std::runtime_error("guest mapping requires a placeholder"); @@ -905,6 +1029,7 @@ private: } if (memory.RegionSize < bytes) throw std::runtime_error("guest placeholder is too small"); if (memory.RegionSize != bytes && !VirtualFree(reinterpret_cast(address), bytes, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) fail("split guest placeholder suffix"); + return address + memory.RegionSize; } void reset(std::uintptr_t address, std::size_t bytes) { @@ -913,6 +1038,8 @@ private: forgetClean(address, end); breakRun(address); breakRun(end); + breakView(address); + breakView(end); if (const auto run = residentRuns.find(address); run != residentRuns.end() && run->second.end == end) { const auto memory = query(address); if (reinterpret_cast(memory.AllocationBase) != address) throw std::runtime_error("cannot release part of a host allocation"); @@ -944,12 +1071,11 @@ private: if (allocationEnd > end || query(allocationEnd).AllocationBase == memory.AllocationBase) throw std::runtime_error("guest release truncates a host allocation"); if (memory.Type == MEM_MAPPED) { if (!unmap(GetCurrentProcess(), reinterpret_cast(cursor), MEM_PRESERVE_PLACEHOLDER)) fail("unmap shared guest page"); - const auto found = views.find(cursor); - if (found != views.end()) { - std::erase(found->second.page->aliases, cursor); - views.erase(found); - forget(freshRanges, cursor, cursor + pageBytes); + for (auto found = views.lower_bound(cursor); found != views.end() && found->first < allocationEnd;) { + std::erase(found->second.page->aliases, found->first); + found = views.erase(found); } + forget(freshRanges, cursor, allocationEnd); } else if (memory.Type == MEM_PRIVATE) { if (const auto run = residentRuns.find(cursor); run != residentRuns.end()) evict(run); if (!VirtualFree(reinterpret_cast(cursor), allocationEnd - cursor, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) fail("release private guest memory"); @@ -1023,6 +1149,9 @@ private: std::map splitAliases; std::size_t granularity = 0x10000; bool watchResident = false; +#ifdef APS5_COUNT_MAPPING_QUERIES + static inline std::atomic queries{0}; +#endif std::mutex mutex; AllocateFunction allocate = nullptr; MapFunction map = nullptr; diff --git a/core/libs/prx/libc/include/WindowsScanning.hpp b/core/libs/prx/libc/include/WindowsScanning.hpp index 77bbf1541e..ab3f6ef625 100644 --- a/core/libs/prx/libc/include/WindowsScanning.hpp +++ b/core/libs/prx/libc/include/WindowsScanning.hpp @@ -16,15 +16,30 @@ inline int ScanWindowsArguments_nid_no_patch(const char* format, const void* sou std::vector pointers; std::vector suppressedEnds(static_cast(std::count(format, format + std::strlen(format), '%')), -1); size_t suppressedCount = 0; + struct NarrowInteger { + void* destination; + size_t size; + bool isUnsigned; + long long value; + unsigned long long unsignedValue; + int end; + }; + std::vector narrowIntegers; + narrowIntegers.reserve(suppressedEnds.size()); FormatArguments args(source); while (*format) { const char value = *format++; translated += value; if (value != '%') continue; if (*format == '%') { translated += *format++; continue; } + const size_t directive = translated.size() - 1; const bool suppressed = *format == '*'; if (suppressed) translated += *format++; while (*format >= '0' && *format <= '9') translated += *format++; + if (*format == 'q') { + translated.resize(directive); + break; + } std::string length; if (*format && std::strchr("hljztL", *format)) { length += *format++; @@ -37,7 +52,8 @@ inline int ScanWindowsArguments_nid_no_patch(const char* format, const void* sou return EOF; } ++format; - if (std::strchr("diouxXn", conversion) && (length == "l" || length == "j" || length == "z" || length == "t")) + const bool narrow = !suppressed && std::strchr("diouxX", conversion) && (length.empty() || length == "h" || length == "hh"); + if (narrow || (std::strchr("diouxXn", conversion) && (length == "l" || length == "j" || length == "z" || length == "t"))) translated += "ll"; else translated += length; translated += conversion; @@ -48,13 +64,30 @@ inline int ScanWindowsArguments_nid_no_patch(const char* format, const void* sou if (*format != ']') { errno = 22; return EOF; } translated += *format++; } - if (!suppressed) pointers.push_back(args.Next()); + if (narrow) { + const bool isUnsigned = std::strchr("ouxX", conversion) != nullptr; + narrowIntegers.push_back({args.Next(), length.empty() ? sizeof(int) : length == "h" ? sizeof(short) : sizeof(char), isUnsigned, 0, 0, -1}); + translated += "%n"; + auto& integer = narrowIntegers.back(); + pointers.push_back(isUnsigned ? static_cast(&integer.unsignedValue) : static_cast(&integer.value)); + pointers.push_back(&integer.end); + } else if (!suppressed) pointers.push_back(args.Next()); else if (conversion != 'n') { translated += "%n"; pointers.push_back(&suppressedEnds[suppressedCount++]); } } const int result = scan(translated.c_str(), reinterpret_cast(pointers.data())); + for (const auto& integer : narrowIntegers) { + if (integer.end < 0) continue; + if (integer.isUnsigned) { + if (integer.size == sizeof(char)) *static_cast(integer.destination) = static_cast(integer.unsignedValue); + else if (integer.size == sizeof(short)) *static_cast(integer.destination) = static_cast(integer.unsignedValue); + else *static_cast(integer.destination) = static_cast(integer.unsignedValue); + } else if (integer.size == sizeof(char)) *static_cast(integer.destination) = static_cast(integer.value); + else if (integer.size == sizeof(short)) *static_cast(integer.destination) = static_cast(integer.value); + else *static_cast(integer.destination) = static_cast(integer.value); + } if (result == EOF && std::any_of(suppressedEnds.begin(), suppressedEnds.begin() + suppressedCount, [](int end) { return end >= 0; })) return 0; return result; diff --git a/core/libs/prx/libc/include/general/ExportMacros.hpp b/core/libs/prx/libc/include/general/ExportMacros.hpp index 49ab7fbf8c..c175c74f2b 100644 --- a/core/libs/prx/libc/include/general/ExportMacros.hpp +++ b/core/libs/prx/libc/include/general/ExportMacros.hpp @@ -1,7 +1,11 @@ #ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GENERAL_EXPORTMACROS_HPP #define CORE_LIBS_PRX_LIBC_INCLUDE_GENERAL_EXPORTMACROS_HPP -#if defined(__GNUC__) || defined(__clang__) +#if defined(__APPLE__) + #define APS5_EXPORT(exportName, funcName) \ + __asm__(".globl \"_" exportName "_nid_no_patch_cut\"\n\t" \ + ".set \"_" exportName "_nid_no_patch_cut\", _" #funcName) +#elif defined(__GNUC__) || defined(__clang__) #define APS5_EXPORT(exportName, funcName) \ __asm__(".globl \"" exportName "_nid_no_patch_cut\"\n\t" \ ".set \"" exportName "_nid_no_patch_cut\", " #funcName) diff --git a/core/libs/prx/libc/include/specifics/linux/ElfTypes.hpp b/core/libs/prx/libc/include/specifics/linux/ElfTypes.hpp index 89dd7d3bcd..d61f017608 100644 --- a/core/libs/prx/libc/include/specifics/linux/ElfTypes.hpp +++ b/core/libs/prx/libc/include/specifics/linux/ElfTypes.hpp @@ -1,6 +1,10 @@ #ifndef PRX_LIBC_INCLUDE_SPECIFICS_LINUX_ELFTYPES_HPP #define PRX_LIBC_INCLUDE_SPECIFICS_LINUX_ELFTYPES_HPP +#ifdef __linux__ +#include +#else + #include #include @@ -34,3 +38,5 @@ extern "C" int dl_iterate_phdr(int (*callback)(dl_phdr_info*, std::size_t, void* #endif #endif + +#endif diff --git a/core/libs/prx/libc/libc.hpp b/core/libs/prx/libc/libc.hpp new file mode 100644 index 0000000000..f5bfb1510b --- /dev/null +++ b/core/libs/prx/libc/libc.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/exceptions/Runtime.hpp" +#include "prx/libc/include/exceptions/Unwind.hpp" +#include "prx/libc/include/SonyCrt.hpp" +#include "prx/libc/include/WindowsFormatting.hpp" diff --git a/core/libs/prx/libc/src/AllocatingStrings.cpp b/core/libs/prx/libc/src/AllocatingStrings.cpp index 1605fbd125..9f346a90f3 100644 --- a/core/libs/prx/libc/src/AllocatingStrings.cpp +++ b/core/libs/prx/libc/src/AllocatingStrings.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" #include "SceTypes.hpp" @@ -8,8 +9,6 @@ extern "C" { -int APS5_VABI vasprintf_nid_postfix(char** destination, const char* format, VaList* args); - char* APS5_VABI strndup_nid_postfix(const char* source, std::size_t limit) { if (!source) { errno = 22; return nullptr; } std::size_t length = 0; diff --git a/core/libs/prx/libc/src/ApplicationHeap.cpp b/core/libs/prx/libc/src/ApplicationHeap.cpp index 84a8f46eb7..af64afc313 100644 --- a/core/libs/prx/libc/src/ApplicationHeap.cpp +++ b/core/libs/prx/libc/src/ApplicationHeap.cpp @@ -1,7 +1,9 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/GuestHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include @@ -27,6 +29,7 @@ using UsableSize = std::size_t (APS5_VABI *)(void*); std::mutex heapMutex; std::array heapApi{}; +std::atomic heapKind{0}; std::once_flag heapInitialization; std::exception_ptr heapFailure; Initialize heapFinalize = nullptr; @@ -145,6 +148,18 @@ void ApplicationHeapRegister_nid_no_patch(void* const* api) { if (heapFinalized) throw std::runtime_error("application heap: allocator has been finalized"); if (heapApi[0] != nullptr && heapApi != replacement) throw std::runtime_error("application heap: cannot replace an active allocator"); heapApi = replacement; + heapKind.store(replacement == defaultApi() ? 1 : 2, std::memory_order_release); +} + +bool ApplicationHeapDefault_nid_no_patch() { + int kind = heapKind.load(std::memory_order_acquire); + if (kind == 0) { + std::unique_lock lock(heapMutex); + ensureInitialized(lock); + if (heapFailure) std::rethrow_exception(heapFailure); + kind = heapKind.load(std::memory_order_acquire); + } + return kind == 1; } void ApplicationHeapInitialize_nid_no_patch(const void* processParameters) { @@ -192,7 +207,7 @@ void ApplicationHeapFree_nid_no_patch(void* pointer) { } void* ApplicationHeapReallocate_nid_no_patch(void* pointer, std::size_t bytes) { - if (bytes == 0 && pointer != nullptr) { + if (bytes == 0 && pointer != nullptr && !ApplicationHeapDefault_nid_no_patch()) { ApplicationHeapFree_nid_no_patch(pointer); return nullptr; } @@ -227,7 +242,7 @@ extern "C" void APS5_VABI scriptingFreeMem_nid_postfix(void* pointer) { } void* ApplicationHeapRealign_nid_no_patch(void* pointer, std::size_t bytes, std::size_t alignment) { - if (bytes == 0) { + if (bytes == 0 && !ApplicationHeapDefault_nid_no_patch()) { ApplicationHeapFree_nid_no_patch(pointer); return nullptr; } diff --git a/core/libs/prx/libc/src/AtomicBuiltins.cpp b/core/libs/prx/libc/src/AtomicBuiltins.cpp index 93df860262..bca7b11a4d 100644 --- a/core/libs/prx/libc/src/AtomicBuiltins.cpp +++ b/core/libs/prx/libc/src/AtomicBuiltins.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -7,8 +8,6 @@ namespace { -using Uint128 = unsigned __int128; - [[noreturn]] void ConsoleTrap(const char* function) { throw std::runtime_error(std::string(function) + ": the console implementation traps (ud2)"); } diff --git a/core/libs/prx/libc/src/Backtrace.cpp b/core/libs/prx/libc/src/Backtrace.cpp index a07a476b4a..ad454b77cc 100644 --- a/core/libs/prx/libc/src/Backtrace.cpp +++ b/core/libs/prx/libc/src/Backtrace.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -14,8 +15,7 @@ #include #include "prx/libc/include/exceptions/Unwind.hpp" extern "C" { -_Unwind_Reason_Code APS5_VABI _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn, void*); -_Unwind_Ptr APS5_VABI _Unwind_GetIP_nid_postfix(_Unwind_Context*); + std::string BacktraceModulePath_nid_no_patch(HMODULE handle, std::size_t initialCapacity) { std::string module; std::size_t capacity = initialCapacity == 0 ? 1 : initialCapacity; @@ -74,6 +74,7 @@ static_assert(sizeof(FrameRecord) == NameOffset); }; TraceState state{addresses, MaxFrames, 0}; _Unwind_Backtrace_nid_postfix(traceCallback, &state); + const int captured = state.count; #endif std::vector frames; diff --git a/core/libs/prx/libc/src/Bessel.cpp b/core/libs/prx/libc/src/Bessel.cpp index 7ba3585ad3..3ef4643d2a 100644 --- a/core/libs/prx/libc/src/Bessel.cpp +++ b/core/libs/prx/libc/src/Bessel.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libc/src/BinaryWordIo.cpp b/core/libs/prx/libc/src/BinaryWordIo.cpp index 733aadff22..c3e3be0748 100644 --- a/core/libs/prx/libc/src/BinaryWordIo.cpp +++ b/core/libs/prx/libc/src/BinaryWordIo.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -7,13 +8,6 @@ static_assert(sizeof(int) == 4 && std::endian::native == std::endian::little); -extern "C" { -std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*); -int APS5_VABI putc_unlocked_nid_postfix(int, FileStream*); -void APS5_VABI flockfile_nid_postfix(FileStream*); -void APS5_VABI funlockfile_nid_postfix(FileStream*); -} - namespace { class LockedStream { diff --git a/core/libs/prx/libc/src/BoundsChecking.cpp b/core/libs/prx/libc/src/BoundsChecking.cpp index a29df4a74f..634f860c06 100644 --- a/core/libs/prx/libc/src/BoundsChecking.cpp +++ b/core/libs/prx/libc/src/BoundsChecking.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -8,13 +9,7 @@ #include "prx/libc/include/SonyCrt.hpp" extern "C" { -std::size_t APS5_VABI mbrtowc_nid_postfix(std::uint16_t* destination, const char* source, std::size_t count, void* state); -std::size_t APS5_VABI wcrtomb_nid_postfix(char* destination, std::uint16_t value, void* state); -int APS5_VABI wctomb_nid_postfix(char* destination, std::uint16_t value); -int APS5_VABI ___mb_cur_max_nid_postfix(); -int APS5_VABI getchar_nid_postfix(); -int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream); -int APS5_VABI fputc_nid_postfix(int value, FileStream* stream); + extern FileStream* __stderrp_nid_postfix; [[noreturn]] void abort_nid_postfix(std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t); } @@ -29,8 +24,6 @@ constexpr std::size_t failed = static_cast(-1); constexpr std::size_t rsizeMax = (std::size_t{1} << 61) - 1; constexpr std::size_t guestStateSize = 128; -using GuestContextCompare = int (APS5_VABI*)(const void*, const void*, void*); - bool usableSize(std::size_t size) { return size != 0 && size <= rsizeMax; } @@ -51,6 +44,7 @@ extern "C" { void APS5_VABI abort_handler_s_nid_postfix(const char* message, void*, int) { fputs_nid_postfix(message, __stderrp_nid_postfix); fputc_nid_postfix('\n', __stderrp_nid_postfix); + abort_nid_postfix(0, 0, 0, 0, 0, 0); } @@ -127,7 +121,7 @@ std::uint16_t* APS5_VABI wcstok_s_nid_postfix(std::uint16_t* str, std::size_t* r int APS5_VABI wctomb_s_nid_postfix(int* status, char* destination, std::size_t capacity, std::uint16_t wide) { if (!status || (!destination && capacity) || (destination && capacity > rsizeMax)) return violation("wctomb_s: bad argument", guestEinval); - if (destination && static_cast(___mb_cur_max_nid_postfix()) > capacity) { + if (destination && static_cast(_Getmbcurmax_nid_postfix()) > capacity) { char bytes[MB_LEN_MAX]; const int length = wctomb_nid_postfix(bytes, wide); if (length > 0 && static_cast(length) > capacity) return violation("wctomb_s: buffer too small", guestErange); @@ -154,7 +148,7 @@ int APS5_VABI wcrtomb_s_nid_postfix(std::size_t* result, char* destination, std: std::size_t length = 0; if (!destination) { length = wcrtomb_nid_postfix(bytes, 0, state); - } else if (static_cast(___mb_cur_max_nid_postfix()) > capacity) { + } else if (static_cast(_Getmbcurmax_nid_postfix()) > capacity) { length = wcrtomb_nid_postfix(bytes, wide, state); if (length != failed && length > capacity) { *result = failed; diff --git a/core/libs/prx/libc/src/CxxAbiSupport.cpp b/core/libs/prx/libc/src/CxxAbiSupport.cpp index 1cbe9c38cd..c8fa3ef0de 100644 --- a/core/libs/prx/libc/src/CxxAbiSupport.cpp +++ b/core/libs/prx/libc/src/CxxAbiSupport.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include #ifndef _UNWIND_H @@ -74,18 +75,22 @@ int APS5_VABI __cxa_thread_atexit_impl_nid_postfix(void (APS5_VABI *func)(void*) auto* context = new (std::nothrow) ThreadAtexitContext{func, arg}; if (context == nullptr) return -1; - const int result = __cxxabiv1::__cxa_thread_atexit( - [](void* opaque) { - auto* context = static_cast(opaque); - const auto destructor = context->destructor; - void* object = context->object; - delete context; - destructor(object); - }, - context, dso); + const auto run = [](void* opaque) { + auto* context = static_cast(opaque); + const auto destructor = context->destructor; + void* object = context->object; + delete context; + destructor(object); + }; +#ifdef __APPLE__ + __cxxabiv1::__cxa_thread_atexit(run, context, dso); + return 0; +#else + const int result = __cxxabiv1::__cxa_thread_atexit(run, context, dso); if (result != 0) delete context; return result; +#endif } int APS5_VABI __cxa_thread_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dso) { diff --git a/core/libs/prx/libc/src/DinkumwareAtomics.cpp b/core/libs/prx/libc/src/DinkumwareAtomics.cpp index f8aecb774e..fc480d7c02 100644 --- a/core/libs/prx/libc/src/DinkumwareAtomics.cpp +++ b/core/libs/prx/libc/src/DinkumwareAtomics.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -62,6 +63,18 @@ int LockedCompareExchange(volatile unsigned char* flag, std::size_t size, volati extern "C" { +std::uint8_t APS5_VABI _Fetch_and_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Fetch_and_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI _Fetch_and_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } + +std::uint8_t APS5_VABI _Fetch_or_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Fetch_or_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI _Fetch_or_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } + +std::uint8_t APS5_VABI _Fetch_xor_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Fetch_xor_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI _Fetch_xor_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } + void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { Store(target, value); } void APS5_VABI _Atomic_store_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { Store(target, value); } void APS5_VABI _Atomic_store_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { Store(target, value); } @@ -172,4 +185,61 @@ int APS5_VABI _Atomic_compare_exchange_weak_nid_postfix(volatile unsigned char* return LockedCompareExchange(flag, size, target, expected, desired); } + +std::uint8_t APS5_VABI __atomic_load_1_nid_postfix(const volatile std::uint8_t* target, int) { return __atomic_load_n(target, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI __atomic_load_2_nid_postfix(const volatile std::uint16_t* target, int) { return __atomic_load_n(target, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI __atomic_load_4_nid_postfix(const volatile std::uint32_t* target, int) { return __atomic_load_n(target, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI __atomic_load_8_nid_postfix(const volatile std::uint64_t* target, int) { return __atomic_load_n(target, __ATOMIC_SEQ_CST); } + +void APS5_VABI __atomic_store_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { Store(target, value); } +void APS5_VABI __atomic_store_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { Store(target, value); } +void APS5_VABI __atomic_store_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { Store(target, value); } +void APS5_VABI __atomic_store_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { Store(target, value); } + +std::uint8_t APS5_VABI __atomic_exchange_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return Exchange(target, value); } +std::uint16_t APS5_VABI __atomic_exchange_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return Exchange(target, value); } +std::uint32_t APS5_VABI __atomic_exchange_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return Exchange(target, value); } +std::uint64_t APS5_VABI __atomic_exchange_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { return Exchange(target, value); } + +int APS5_VABI __atomic_compare_exchange_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI __atomic_compare_exchange_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI __atomic_compare_exchange_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t* expected, std::uint32_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI __atomic_compare_exchange_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +std::uint8_t APS5_VABI __atomic_fetch_add_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI __atomic_fetch_add_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI __atomic_fetch_add_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI __atomic_fetch_add_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); } + +std::uint8_t APS5_VABI __atomic_fetch_sub_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI __atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI __atomic_fetch_sub_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI __atomic_fetch_sub_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); } + +std::uint8_t APS5_VABI __atomic_fetch_and_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI __atomic_fetch_and_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI __atomic_fetch_and_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI __atomic_fetch_and_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } + +std::uint8_t APS5_VABI __atomic_fetch_or_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI __atomic_fetch_or_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI __atomic_fetch_or_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI __atomic_fetch_or_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } + +std::uint8_t APS5_VABI __atomic_fetch_xor_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI __atomic_fetch_xor_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI __atomic_fetch_xor_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI __atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } + } diff --git a/core/libs/prx/libc/src/DinkumwareInternals.cpp b/core/libs/prx/libc/src/DinkumwareInternals.cpp new file mode 100644 index 0000000000..dc45984759 --- /dev/null +++ b/core/libs/prx/libc/src/DinkumwareInternals.cpp @@ -0,0 +1,444 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" + +namespace { + +constexpr long long ExponentLimit = 1000000000; +constexpr int InvalidArgument = 22; +constexpr int RangeError = 34; + +bool Hexadecimal(const char* text) { + while (std::isspace(static_cast(*text))) ++text; + if (*text == '+' || *text == '-') ++text; + return text[0] == '0' && (text[1] == 'x' || text[1] == 'X'); +} + +template +TFloat ScaledFloat(const char* text, char** end, std::int64_t pten, int* error, TFloat (APS5_VABI *parse)(const char*, char**), const char* function) { + int& guestErrno = *__error_nid_postfix(); + const int saved = guestErrno; + if (error != nullptr) *error = 0; + guestErrno = 0; + char* stop = nullptr; + TFloat value = parse(text, &stop); + if (end != nullptr) *end = stop; + const bool overflowed = std::isinf(value) && guestErrno == RangeError; + if (pten != 0 && stop != text && (std::isfinite(value) || overflowed)) { + if (Hexadecimal(text)) NotImplemented_nid_no_patch(function); + std::string number(text, static_cast(stop)); + long long exponent = 0; + const auto marker = number.find_first_of("eE"); + if (marker != std::string::npos) { + exponent = std::strtoll(number.c_str() + marker + 1, nullptr, 10); + number.resize(marker); + } + exponent = std::clamp(exponent, -ExponentLimit, ExponentLimit) + std::clamp(pten, -ExponentLimit, ExponentLimit); + number += 'e'; + number += std::to_string(exponent); + guestErrno = 0; + value = parse(number.c_str(), nullptr); + } + if (guestErrno == RangeError && (value == 0 || std::isinf(value))) { + if (error != nullptr) *error = 1; + } else if (guestErrno == 0 || guestErrno == RangeError) { + guestErrno = saved; + } + return value; +} + +bool InvalidBase(const char* text, char** end, int base, int* error) { + if (base >= 0 && base != 1 && base <= 36) return false; + if (end != nullptr) *end = const_cast(text); + if (error != nullptr) *error = InvalidArgument; + return true; +} + +template +TInteger ParsedInteger(const char* text, char** end, int base, int* error, TInteger (APS5_VABI *parse)(const char*, char**, int)) { + if (error != nullptr) *error = 0; + if (InvalidBase(text, end, base, error)) return 0; + int& guestErrno = *__error_nid_postfix(); + const int saved = guestErrno; + guestErrno = 0; + const TInteger value = parse(text, end, base); + if (error != nullptr) *error = guestErrno; + if (guestErrno == 0) guestErrno = saved; + return value; +} + +constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; + +void RaiseLibcError(int exceptions) { + if ((exceptions & (FE_OVERFLOW | FE_UNDERFLOW)) != 0) exceptions |= FE_INEXACT; + std::feraiseexcept(exceptions); + const bool range = (exceptions & FE_INVALID) == 0 && + (exceptions & (FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW)) != 0; + errno = range ? ERANGE : EDOM; +} + +template +short ClassifyValue(TFloat value) { + switch (std::fpclassify(value)) { + case FP_NAN: + return NotANumber; + case FP_INFINITE: + return Infinite; + case FP_ZERO: + return Zero; + case FP_SUBNORMAL: + return Denormal; + default: + return Finite; + } +} + +template +short ScaleProduct(TFloat& value, TFloat factor, TFloat multiplier, long exponent) { + std::fexcept_t saved; + std::fegetexceptflag(&saved, FE_ALL_EXCEPT); + const int savedErrno = errno; + TFloat result = factor * multiplier; + if (!std::isfinite(result) || result == static_cast(0)) { + int factorExponent = 0; + int multiplierExponent = 0; + const TFloat factorMantissa = std::frexp(factor, &factorExponent); + const TFloat multiplierMantissa = std::frexp(multiplier, &multiplierExponent); + const long long combinedExponent = static_cast(factorExponent) + multiplierExponent + exponent; + result = std::scalbn(factorMantissa * multiplierMantissa, static_cast(combinedExponent)); + } else { + result = std::scalbn(result, static_cast(exponent)); + } + value = result; + const short kind = ClassifyValue(result); + const int raised = std::fetestexcept(FE_ALL_EXCEPT) & ~static_cast(saved) & FE_ALL_EXCEPT; + std::fesetexceptflag(&saved, FE_ALL_EXCEPT); + errno = savedErrno; + if (kind == Infinite) { + RaiseLibcError(FE_OVERFLOW); + return Infinite; + } + if (kind == Zero) { + RaiseLibcError(FE_UNDERFLOW); + return Zero; + } + if (kind == Denormal) return Finite; + if (kind == NotANumber) return NotANumber; + if ((raised & FE_INEXACT) != 0) std::feraiseexcept(FE_INEXACT); + return Finite; +} + +} + +extern "C" { + +double APS5_VABI _Stodx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error) { + return ScaledFloat(text, end, pten, error, strtod_nid_postfix, "_Stodx (hexadecimal text with a power-of-ten scale)"); +} + +float APS5_VABI _Stofx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error) { + return ScaledFloat(text, end, pten, error, strtof_nid_postfix, "_Stofx (hexadecimal text with a power-of-ten scale)"); +} + +long double APS5_VABI _Stoldx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error) { + return ScaledFloat(text, end, pten, error, strtold_nid_postfix, "_Stoldx (hexadecimal text with a power-of-ten scale)"); +} + +double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pten) { + return _Stodx_nid_postfix(text, end, pten, nullptr); +} + +float APS5_VABI _Stof_nid_postfix(const char* text, char** end, std::int64_t pten) { + return _Stofx_nid_postfix(text, end, pten, nullptr); +} + +long double APS5_VABI _Stold_nid_postfix(const char* text, char** end, std::int64_t pten) { + return _Stoldx_nid_postfix(text, end, pten, nullptr); +} + +long long APS5_VABI _Stollx_nid_postfix(const char* text, char** end, int base, int* error) { + return ParsedInteger(text, end, base, error, strtoll_nid_postfix); +} + +unsigned long long APS5_VABI _Stoullx_nid_postfix(const char* text, char** end, int base, int* error) { + return ParsedInteger(text, end, base, error, strtoull_nid_postfix); +} + +std::uint64_t APS5_VABI _Stoulx_nid_postfix(const char* text, char** end, int base, int* error) { + return _Stoullx_nid_postfix(text, end, base, error); +} + +std::int64_t APS5_VABI _Stolx_nid_postfix(const char* text, char** end, int base, int* error) { + if (error != nullptr) *error = 0; + if (InvalidBase(text, end, base, error)) return 0; + int& guestErrno = *__error_nid_postfix(); + const int saved = guestErrno; + guestErrno = 0; + const long long value = strtoll_nid_postfix(text, end, base); + if (error != nullptr) *error = guestErrno; + guestErrno = 0; + static_cast(strtoull_nid_postfix(text, nullptr, base)); + if (guestErrno == 0) guestErrno = saved; + return value; +} + +long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base) { + return _Stollx_nid_postfix(text, end, base, nullptr); +} + +short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset) { + if (value == nullptr) throw std::invalid_argument("_Exp: null value"); + std::fexcept_t entryFlags; + std::fegetexceptflag(&entryFlags, FE_ALL_EXCEPT); + const int entryErrno = errno; + const double x = *value; + 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 E = 0x1.5bf0a8b145769p+1; + const short xKind = ClassifyValue(x); + const short yKind = ClassifyValue(y); + if (xKind == NotANumber) return NotANumber; + if (yKind == NotANumber) return NotANumber; + if (xKind == Infinite) { + if (x > 0.0) { + if (yKind == Zero) { + *value = std::numeric_limits::quiet_NaN(); + RaiseLibcError(FE_INVALID); + return NotANumber; + } + if (yKind == Infinite) { + *value = y; + } else { + *value = std::copysign(std::numeric_limits::infinity(), y); + } + return Infinite; + } + if (yKind == Infinite) { + *value = std::numeric_limits::quiet_NaN(); + RaiseLibcError(FE_INVALID); + return NotANumber; + } + *value = std::copysign(0.0, y); + return Zero; + } + if (yKind == Zero) { + *value = y; + return Zero; + } + if (yKind == Infinite) { + *value = y; + return Infinite; + } + if (xKind == Zero) { + if (offset == 0) { + *value = y; + return Finite; + } + const short result = ScaleProduct(*value, 1.0, y, offset); + if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { + std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT); + errno = entryErrno; + } + return result; + } + if (offset == 0 && x == 1.0 && y == 1.0) { + *value = E; + return Finite; + } + if (x < -HugeExp) { + *value = std::copysign(0.0, y); + return Zero; + } + if (x > HugeExp) { + *value = std::copysign(std::numeric_limits::infinity(), y); + RaiseLibcError(FE_OVERFLOW); + return Infinite; + } + double g = x * InverseLn2; + long exponent = static_cast(g + (g < 0.0 ? -0.5 : 0.5)); + g = static_cast(exponent); + g = (x - g * Ln2High) - g * Ln2Low; + double factor; + if (-Eps < g && g < Eps) { + 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 short result = ScaleProduct(*value, factor, y, exponent + offset); + if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { + std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT); + errno = entryErrno; + } + return result; +} + +short APS5_VABI _FExp_nid_postfix(float* value, float y, short offset) { + if (value == nullptr) throw std::invalid_argument("_FExp: null value"); + std::fexcept_t entryFlags; + std::fegetexceptflag(&entryFlags, FE_ALL_EXCEPT); + const int entryErrno = errno; + const float x = *value; + constexpr float HugeExp = 229.0f; + constexpr float Eps = std::numeric_limits::epsilon(); + constexpr float InverseLn2 = 1.4426950216f; + constexpr float Ln2High = 0.69287109375f; + constexpr float Ln2Low = 0.00027608682f; + constexpr float Q = 0.09997485578f; + constexpr float P0 = 0.5f; + constexpr float P1 = 0.0083208261f; + constexpr float E = 0x1.5bf0a8p+1f; + const short xKind = ClassifyValue(x); + const short yKind = ClassifyValue(y); + if (xKind == NotANumber) return NotANumber; + if (yKind == NotANumber) return NotANumber; + if (xKind == Infinite) { + if (x > 0.0f) { + if (yKind == Zero) { + *value = std::numeric_limits::quiet_NaN(); + RaiseLibcError(FE_INVALID); + return NotANumber; + } + if (yKind == Infinite) { + *value = y; + } else { + *value = std::copysign(std::numeric_limits::infinity(), y); + } + return Infinite; + } + if (yKind == Infinite) { + *value = std::numeric_limits::quiet_NaN(); + RaiseLibcError(FE_INVALID); + return NotANumber; + } + *value = std::copysign(0.0f, y); + return Zero; + } + if (yKind == Zero) { + *value = y; + return Zero; + } + if (yKind == Infinite) { + *value = y; + return Infinite; + } + if (xKind == Zero) { + if (offset == 0) { + *value = y; + return Finite; + } + const short result = ScaleProduct(*value, 1.0f, y, offset); + if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { + std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT); + errno = entryErrno; + } + return result; + } + if (offset == 0 && x == 1.0f && y == 1.0f) { + *value = E; + return Finite; + } + if (x < -HugeExp) { + *value = std::copysign(0.0f, y); + return Zero; + } + if (x > HugeExp) { + *value = std::copysign(std::numeric_limits::infinity(), y); + RaiseLibcError(FE_OVERFLOW); + return Infinite; + } + float g = x * InverseLn2; + long exponent = static_cast(g + (g < 0.0f ? -0.5f : 0.5f)); + g = static_cast(exponent); + g = (x - g * Ln2High) - g * Ln2Low; + float factor; + if (-Eps < g && g < Eps) { + 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 short result = ScaleProduct(*value, factor, y, exponent + offset); + if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { + std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT); + errno = entryErrno; + } + return result; +} + +double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant) { + return quadrant != 0 ? cos_nid_postfix(x) : sin_nid_postfix(x); +} + +float APS5_VABI _FSin_nid_postfix(float x, unsigned int quadrant) { + return quadrant != 0 ? cosf_nid_postfix(x) : sinf_nid_postfix(x); +} + +long double APS5_VABI _LSin_nid_postfix(long double x, unsigned int quadrant) { + return quadrant != 0 ? cosl_nid_postfix(x) : sinl_nid_postfix(x); +} + +double APS5_VABI _Divide_nid_postfix(double x, double y) { + return x / y; +} + +float APS5_VABI _FDivide_nid_postfix(float x, float y) { + return x / y; +} + +double APS5_VABI _Recip_nid_postfix(double x) { + return 1.0 / x; +} + +float APS5_VABI _FRecip_nid_postfix(float x) { + return 1.0f / x; +} + +double APS5_VABI _Log_nid_postfix(double x, int base) { + if (base > 0) return log10_nid_postfix(x); + return base == 0 ? log_nid_postfix(x) : log2_nid_postfix(x); +} + +float APS5_VABI _FLog_nid_postfix(float x, int base) { + if (base > 0) return log10f_nid_postfix(x); + return base == 0 ? logf_nid_postfix(x) : log2f_nid_postfix(x); +} + +long double APS5_VABI _LLog_nid_postfix(long double x, int base) { + if (base > 0) return log10l_nid_postfix(x); + return base == 0 ? logl_nid_postfix(x) : log2l_nid_postfix(x); +} + +long double APS5_VABI _LDivide_nid_postfix(long double x, long double y) { + return x / y; +} + +long double APS5_VABI _LRecip_nid_postfix(long double x) { + return 1.0L / x; +} + +} diff --git a/core/libs/prx/libc/src/Directory.cpp b/core/libs/prx/libc/src/Directory.cpp index 425669cdcf..69d3670e80 100644 --- a/core/libs/prx/libc/src/Directory.cpp +++ b/core/libs/prx/libc/src/Directory.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/GuestDirectory.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/FilesystemError.hpp" diff --git a/core/libs/prx/libc/src/Environment.cpp b/core/libs/prx/libc/src/Environment.cpp index f77b8727f9..a04c872b46 100644 --- a/core/libs/prx/libc/src/Environment.cpp +++ b/core/libs/prx/libc/src/Environment.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libc/src/Errors.cpp b/core/libs/prx/libc/src/Errors.cpp index 4e2d3f08bb..04d0187df9 100644 --- a/core/libs/prx/libc/src/Errors.cpp +++ b/core/libs/prx/libc/src/Errors.cpp @@ -1,10 +1,10 @@ +#include "prx/libc/include/WindowsFormatting.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { // Guest FreeBSD errno numbering, not the host CRT's numbering. constexpr const char* messages[] = { @@ -18,7 +18,7 @@ constexpr const char* messages[] = { "Is a directory", "Invalid argument", "Too many open files in system", "Too many open files", "Inappropriate ioctl for device", "Text file busy", "File too large", "No space left on device", "Illegal seek", - "Read-only file system", "Too many links", "Broken pipe", + "Read-only filesystem", "Too many links", "Broken pipe", "Numerical argument out of domain", "Result too large", "Resource temporarily unavailable", "Operation now in progress", "Operation already in progress", "Socket operation on non-socket", "Destination address required", "Message too long", "Protocol wrong type for socket", @@ -44,15 +44,15 @@ constexpr const char* messages[] = { }; static_assert(sizeof(messages) / sizeof(messages[0]) == 97); bool Known(int error) { return error >= 0 && error < 97; } +bool NamedByStrerror(int error) { return error > 0 && error < 95; } } extern "C" { char* APS5_VABI strerror_nid_postfix(int error) { thread_local char buffer[128]; - const int saved = *__error_nid_postfix(); - if (Known(error)) std::snprintf(buffer, sizeof(buffer), "%s", messages[error]); - else std::snprintf(buffer, sizeof(buffer), "Unknown error: %d", error); - *__error_nid_postfix() = Known(error) ? saved : 22; + if (error == 0) std::snprintf(buffer, sizeof(buffer), "No error"); + else if (NamedByStrerror(error)) std::snprintf(buffer, sizeof(buffer), "%s", messages[error]); + else std::snprintf(buffer, sizeof(buffer), "error_code=0x%08x", static_cast(error)); return buffer; } @@ -77,6 +77,7 @@ void APS5_VABI perror_nid_postfix(const char* prefix) { const int saved = *__error_nid_postfix(); char message[128]; strerror_r_nid_postfix(saved, message, sizeof(message)); + auto* stream = GetNativeStream(&_Stderr_nid_postfix); if (prefix && *prefix) std::fprintf(stream, "%s: %s\n", prefix, message); else std::fprintf(stream, "%s\n", message); diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 410fa6f309..51550e1dac 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -1,4 +1,6 @@ +#include "prx/libc/include/Api.hpp" #include +#include #include #include #include @@ -65,6 +67,19 @@ static std::optional ParseFileMode(const char* mode) { #ifdef _WIN32 static constexpr DWORD AppendOnlyAccess = FILE_GENERIC_WRITE & ~FILE_WRITE_DATA; + +static std::optional HostOpenName(const std::filesystem::path& path) { + auto filename = path.native(); + if (!filename.starts_with(L"\\\\?\\") && !filename.starts_with(L"\\\\.\\")) { + std::error_code pathError; + auto absolutePath = std::filesystem::absolute(path, pathError); + if (pathError) { errno = pathError.default_error_condition().value(); return std::nullopt; } + const auto absolute = absolutePath.lexically_normal().make_preferred().native(); + if (absolute.size() >= MAX_PATH) + filename = absolute.starts_with(L"\\\\") ? L"\\\\?\\UNC\\" + absolute.substr(2) : L"\\\\?\\" + absolute; + } + return filename; +} #endif static std::FILE* OpenNative(const std::filesystem::path& path, const std::string& nativeMode) { @@ -76,16 +91,9 @@ static std::FILE* OpenNative(const std::filesystem::path& path, const std::strin const DWORD disposition = access == 'r' ? OPEN_EXISTING : nativeMode.find('x') != std::string::npos ? CREATE_NEW : access == 'w' ? CREATE_ALWAYS : OPEN_ALWAYS; SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; - auto filename = path.native(); - if (!filename.starts_with(L"\\\\?\\") && !filename.starts_with(L"\\\\.\\")) { - std::error_code pathError; - auto absolutePath = std::filesystem::absolute(path, pathError); - if (pathError) { errno = pathError.default_error_condition().value(); return nullptr; } - const auto absolute = absolutePath.lexically_normal().make_preferred().native(); - if (absolute.size() >= MAX_PATH) - filename = absolute.starts_with(L"\\\\") ? L"\\\\?\\UNC\\" + absolute.substr(2) : L"\\\\?\\" + absolute; - } - const auto handle = CreateFileW(filename.c_str(), desired, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &security, + const auto filename = HostOpenName(path); + if (!filename) return nullptr; + const auto handle = CreateFileW(filename->c_str(), desired, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &security, disposition, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle == INVALID_HANDLE_VALUE) { errno = WindowsOpenError_nid_no_patch(GetLastError()); @@ -151,9 +159,6 @@ static bool IsPipeStream(std::FILE* native) { } #endif -using GuestCompare = int (APS5_VABI *)(const void*, const void*); -using GuestContextCompare = int (APS5_VABI *)(const void*, const void*, void*); - static void SwapElements(unsigned char* left, unsigned char* right, size_t size) { for (size_t index = 0; index < size; ++index) std::swap(left[index], right[index]); } @@ -252,13 +257,13 @@ static void SortElements(unsigned char* base, size_t count, size_t size, Compare namespace { template -void* allocationOrNull(TAllocate allocate) { +void* allocationOrNull(TAllocate allocate, int error = 12) { try { return allocate(); } catch (const std::bad_alloc&) { } catch (const std::length_error&) { } - errno = 12; + errno = error; return nullptr; } @@ -266,12 +271,36 @@ bool validAlignment(std::size_t alignment) { return alignment != 0 && (alignment & (alignment - 1)) == 0; } +constexpr std::size_t DefaultHeapAlignment = 32; +constexpr std::size_t DefaultHeapAlignmentLimit = std::size_t{1} << 63; +constexpr std::size_t DefaultHeapOverhead = 0x1008f; +constexpr std::size_t DefaultHeapAlignedOverhead = 0x100af; + +std::size_t defaultHeapAlignment(std::size_t alignment) { + return std::bit_ceil(std::max(alignment, DefaultHeapAlignment)); +} + +bool defaultHeapRequestOverflows(std::size_t bytes, std::size_t alignment) { + const std::size_t overhead = alignment <= DefaultHeapAlignment ? DefaultHeapOverhead : defaultHeapAlignment(alignment) + DefaultHeapAlignedOverhead; + return bytes > std::numeric_limits::max() - overhead; +} + +template +void* defaultHeapAllocation(std::size_t bytes, std::size_t alignment, TAllocate allocate) { + if (defaultHeapRequestOverflows(bytes, alignment)) return nullptr; + return allocationOrNull(allocate, 22); +} + +template +void* heapAllocation(std::size_t bytes, TAllocate allocate) { + if (!ApplicationHeapDefault_nid_no_patch()) return allocationOrNull(allocate); + return defaultHeapAllocation(bytes, 0, allocate); +} + } extern "C" { -[[noreturn]] void APS5_VABI _ZSt11_Xbad_allocv_nid_postfix(); - FileStream* APS5_VABI fdopen_nid_postfix(int descriptor, const char* mode) { if (descriptor < 0) { errno = 9; return nullptr; } if (!mode) { errno = 22; return nullptr; } @@ -307,6 +336,47 @@ static int OpenFailure(const std::filesystem::path& path, bool writes, int hostE return writes && error == 13 && std::filesystem::is_directory(path, status) ? 21 : error; } +#ifdef _WIN32 +static int (*g_openDirectoryDescriptor)(const wchar_t*) = nullptr; +static void (*g_forgetDirectoryDescriptor)(int) = nullptr; + +void LibcRegisterDirectoryStreams_nid_no_patch(int (*open)(const wchar_t*), void (*forget)(int)) { + g_openDirectoryDescriptor = open; + g_forgetDirectoryDescriptor = forget; +} + +void ForgetDirectoryStream_nid_no_patch(int descriptor) { + if (!g_forgetDirectoryDescriptor) throw std::runtime_error("ForgetDirectoryStream: libkernel has not registered its directory descriptors"); + g_forgetDirectoryDescriptor(descriptor); +} + +static FileStream* OpenDirectoryStream(const std::filesystem::path& path) { + if (!g_openDirectoryDescriptor) throw std::runtime_error("fopen: libkernel has not registered its directory descriptors"); + const auto filename = HostOpenName(path); + if (!filename) { errno = GuestErrnoFromHost_nid_no_patch(errno); return nullptr; } + const auto release = [](void* pointer) { ::operator delete(pointer); }; + std::unique_ptr storage(::operator new(sizeof(FileStream)), release); + const int descriptor = g_openDirectoryDescriptor(filename->c_str()); + if (descriptor < 0) { + const int error = GuestErrnoFromHost_nid_no_patch(errno); + errno = error == 2 ? NotFoundError_nid_no_patch(path) : error; + return nullptr; + } + auto* native = ::_fdopen(descriptor, "rb"); + if (!native) { + const int error = GuestErrnoFromHost_nid_no_patch(errno); + ForgetDirectoryStream_nid_no_patch(descriptor); + ::_close(descriptor); + errno = error; + return nullptr; + } + auto* stream = new (storage.get()) FileStream(native, true); + storage.release(); + stream->MarkDirectory(); + return stream; +} +#endif + FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, FileStream* stream) { if (!stream || !mode) { errno = 22; return nullptr; } if (!filename) { errno = 45; return nullptr; } // Mode-only reopening is not supported. @@ -314,14 +384,12 @@ FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode if (!parsed) { errno = 22; return nullptr; } try { std::filesystem::path path; - if (*filename) { - if (const int error = ResolvePathError_nid_no_patch(filename, path)) { - if (stream->IsHostStandard()) stream->Detach(); - else stream->Close(); - if (stream->IsDynamic()) delete stream; - errno = error; - return nullptr; - } + if (const int error = *filename ? ResolvePathError_nid_no_patch(filename, path) : 2) { + if (stream->IsHostStandard()) stream->Detach(); + else stream->Close(); + if (stream->IsDynamic()) delete stream; + errno = error; + return nullptr; } if (stream->Reopen(path, parsed->native.c_str(), OpenNative)) { #ifdef _WIN32 @@ -370,7 +438,13 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) if (const int error = ResolvePathError_nid_no_patch(filename, fpath)) { errno = error; return nullptr; } std::unique_ptr handle(OpenNative(fpath, parsed->native), std::fclose); if (!handle) { - errno = OpenFailure(fpath, parsed->writes, errno); + const int hostError = errno; +#ifdef _WIN32 + std::error_code status; + if (!parsed->writes && GuestErrnoFromHost_nid_no_patch(hostError) == 13 && std::filesystem::is_directory(fpath, status)) + return OpenDirectoryStream(fpath); +#endif + errno = OpenFailure(fpath, parsed->writes, hostError); if (errno == 2) errno = NotFoundError_nid_no_patch(fpath); return nullptr; } @@ -391,6 +465,15 @@ int APS5_VABI fopen_s_nid_postfix(FileStream** result, const char* filename, con return *result ? 0 : errno; } +int APS5_VABI freopen_s_nid_postfix(FileStream** result, const char* filename, const char* mode, FileStream* stream) { + constexpr int GuestEinval = 22; + if (!result) return GuestEinval; + *result = nullptr; + if (!mode || !stream) return GuestEinval; + *result = freopen_nid_postfix(filename, mode, stream); + return *result ? 0 : errno; +} + int APS5_VABI fclose_nid_postfix(FileStream* stream) { if (stream && !stream->IsOpen()) { std::unique_ptr owner(stream->IsDynamic() ? stream : nullptr); @@ -402,8 +485,6 @@ int APS5_VABI fclose_nid_postfix(FileStream* stream) { return stream->Close(); } -int APS5_VABI fseek_nid_postfix(FileStream* stream, std::int64_t offset, int origin); - FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const char* filename, int mode, int protection) { static_cast(protection); constexpr int In = 0x01, Out = 0x02, Ate = 0x04, App = 0x08, Trunc = 0x10, Nocreate = 0x20, Noreplace = 0x40, @@ -424,15 +505,16 @@ FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const auto* stream = fopen_nid_postfix(filename, openMode); if (!stream || !(mode & Ate) || fseek_nid_postfix(stream, 0, SEEK_END) == 0) return stream; fclose_nid_postfix(stream); + return nullptr; } size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, FileStream* stream) { + if (size != 0 && count > std::numeric_limits::max() / size) { errno = EINVAL; return 0; } if (stream && !stream->IsOpen()) { errno = 9; return 0; } auto* handle = GetStreamHandle(stream); if (size == 0 || count == 0) return 0; if (!buffer) throw std::runtime_error("fread: null buffer"); - if (count > std::numeric_limits::max() / size) throw std::overflow_error("fread: buffer size overflow"); const auto bytes = size * count; if (bytes > std::numeric_limits::max() - reinterpret_cast(buffer)) throw std::overflow_error("fread: buffer address overflow"); const GuestArena::HostWrite destination(buffer, bytes); @@ -445,6 +527,7 @@ size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, File if (!stream->SwitchToReading()) return 0; const auto result = std::fread(buffer, size, count, handle); stream->SyncStatus(); + stream->KeepDirectoryReadError(result < count); return result; } @@ -529,9 +612,11 @@ int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream) { int APS5_VABI fflush_nid_postfix(FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } std::FILE* native = stream ? GetStreamHandle(stream) : nullptr; - if (stream && stream->IsRandomDevice()) return 0; + if (stream && (stream->IsRandomDevice() || stream->IsDirectory())) return 0; #ifdef __linux__ if (native && !__fwriting(native)) return 0; +#elif defined(__APPLE__) + if (native && !(native->_flags & __SWR)) return 0; #endif const int result = std::fflush(native); if (stream) stream->SyncStatus(); @@ -547,7 +632,7 @@ size_t APS5_VABI malloc_usable_size_nid_postfix(void* pointer) { } void* APS5_VABI malloc_nid_postfix(size_t size) { - return allocationOrNull([size] { return ApplicationHeapAllocate_nid_no_patch(size); }); + return heapAllocation(size, [size] { return ApplicationHeapAllocate_nid_no_patch(size); }); } void APS5_VABI free_nid_postfix(void* ptr) { @@ -555,49 +640,60 @@ void APS5_VABI free_nid_postfix(void* ptr) { } void* APS5_VABI realloc_nid_postfix(void* ptr, size_t newSize) { - return allocationOrNull([ptr, newSize] { return ApplicationHeapReallocate_nid_no_patch(ptr, newSize); }); + return heapAllocation(newSize, [ptr, newSize] { return ApplicationHeapReallocate_nid_no_patch(ptr, newSize); }); } void* APS5_VABI reallocf_nid_postfix(void* ptr, size_t newSize) { - try { - return ApplicationHeapReallocate_nid_no_patch(ptr, newSize); - } catch (const std::bad_alloc&) { - } catch (const std::length_error&) { - } - ApplicationHeapFree_nid_no_patch(ptr); - errno = 12; - return nullptr; + void* result = realloc_nid_postfix(ptr, newSize); + if (result == nullptr && ptr != nullptr && newSize != 0) ApplicationHeapFree_nid_no_patch(ptr); + return result; } void* APS5_VABI memalign_nid_postfix(size_t alignment, size_t size) { - if (!validAlignment(alignment)) { - errno = 22; - return nullptr; + if (!ApplicationHeapDefault_nid_no_patch()) { + if (!validAlignment(alignment)) { + errno = 22; + return nullptr; + } + return allocationOrNull([alignment, size] { return ApplicationHeapAlign_nid_no_patch(alignment, size); }); } - return allocationOrNull([alignment, size] { return ApplicationHeapAlign_nid_no_patch(alignment, size); }); + if (alignment >= DefaultHeapAlignmentLimit) return nullptr; + const std::size_t rounded = defaultHeapAlignment(alignment); + return defaultHeapAllocation(size, alignment, [rounded, size] { return ApplicationHeapAlign_nid_no_patch(rounded, size); }); } void* APS5_VABI aligned_alloc_nid_postfix(size_t alignment, size_t size) { + if (ApplicationHeapDefault_nid_no_patch() && !validAlignment(alignment)) return nullptr; return memalign_nid_postfix(alignment, size); } void* APS5_VABI reallocalign_nid_postfix(void* ptr, size_t size, size_t alignment) { - if (size != 0 && !validAlignment(alignment)) { - errno = 22; - return nullptr; + if (!ApplicationHeapDefault_nid_no_patch()) { + if (size != 0 && !validAlignment(alignment)) { + errno = 22; + return nullptr; + } + return allocationOrNull([ptr, size, alignment] { return ApplicationHeapRealign_nid_no_patch(ptr, size, alignment); }); } - return allocationOrNull([ptr, size, alignment] { return ApplicationHeapRealign_nid_no_patch(ptr, size, alignment); }); + if (alignment >= DefaultHeapAlignmentLimit) return nullptr; + const std::size_t rounded = defaultHeapAlignment(alignment); + return defaultHeapAllocation(size, alignment, [ptr, size, rounded] { return ApplicationHeapRealign_nid_no_patch(ptr, size, rounded); }); } void* APS5_VABI calloc_nid_postfix(size_t count, size_t size) { - return allocationOrNull([count, size] { return ApplicationHeapCalloc_nid_no_patch(count, size); }); + const auto allocate = [count, size] { return ApplicationHeapCalloc_nid_no_patch(count, size); }; + if (!ApplicationHeapDefault_nid_no_patch()) return allocationOrNull(allocate); + if (size != 0 && count > std::numeric_limits::max() / size) return nullptr; + return defaultHeapAllocation(count * size, 0, allocate); } int APS5_VABI posix_memalign_nid_postfix(void** pointer, size_t alignment, size_t size) { if (!pointer || alignment < sizeof(void*) || (alignment & (alignment - 1)) != 0) return 22; const int savedError = errno; const int result = ApplicationHeapPosixAlign_nid_no_patch(pointer, alignment, size); - errno = savedError; + const bool failureSetsError = result != 0 && ApplicationHeapDefault_nid_no_patch() && alignment < DefaultHeapAlignmentLimit && + !defaultHeapRequestOverflows(size, alignment); + errno = failureSetsError ? 22 : savedError; return result; } @@ -652,6 +748,7 @@ int APS5_VABI mergesort_nid_postfix(void* base, size_t count, size_t size, Guest } if (source != base) std::memcpy(base, source, count * size); free_nid_postfix(scratch); + return 0; } diff --git a/core/libs/prx/libc/src/Filesystem.cpp b/core/libs/prx/libc/src/Filesystem.cpp index 510477eb7a..053d0a8b7f 100644 --- a/core/libs/prx/libc/src/Filesystem.cpp +++ b/core/libs/prx/libc/src/Filesystem.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/General.hpp" #include @@ -11,6 +12,30 @@ #include #endif +#ifdef _WIN32 +static std::filesystem::path FollowedPath(const std::filesystem::path& path) { + const DWORD attributes = GetFileAttributesW(path.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) return path; + const HANDLE handle = CreateFileW(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) return {}; + std::wstring target(MAX_PATH, L'\0'); + DWORD length = GetFinalPathNameByHandleW(handle, target.data(), static_cast(target.size()), FILE_NAME_NORMALIZED); + if (length >= target.size()) { + target.resize(length); + length = GetFinalPathNameByHandleW(handle, target.data(), length, FILE_NAME_NORMALIZED); + } + const DWORD error = GetLastError(); + CloseHandle(handle); + if (length == 0 || length >= target.size()) { + SetLastError(length == 0 ? error : ERROR_FILENAME_EXCED_RANGE); + return {}; + } + target.resize(length); + return target; +} +#endif + extern "C" int APS5_VABI access_nid_postfix(const char* path, int mode) { if (mode < 0 || (mode & ~7)) { errno = 22; return -1; } if (!path) { errno = 14; return -1; } @@ -18,9 +43,11 @@ extern "C" int APS5_VABI access_nid_postfix(const char* path, int mode) { try { const auto resolved = ResolvePath_nid_no_patch(path); #ifdef _WIN32 - const DWORD attributes = GetFileAttributesW(resolved.c_str()); + const auto target = FollowedPath(resolved); + const DWORD attributes = target.empty() ? INVALID_FILE_ATTRIBUTES : GetFileAttributesW(target.c_str()); if (attributes == INVALID_FILE_ATTRIBUTES) { - errno = FilesystemError(std::error_code(static_cast(GetLastError()), std::system_category())); + const DWORD failure = GetLastError(); + errno = failure == ERROR_CANT_RESOLVE_FILENAME ? 62 : FilesystemError(std::error_code(static_cast(failure), std::system_category())); if (errno == 2) errno = NotFoundError_nid_no_patch(resolved); return -1; } @@ -30,10 +57,10 @@ extern "C" int APS5_VABI access_nid_postfix(const char* path, int mode) { } DWORD size = 0; constexpr auto information = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; - GetFileSecurityW(resolved.c_str(), information, nullptr, 0, &size); + GetFileSecurityW(target.c_str(), information, nullptr, 0, &size); if (!size) { errno = 13; return -1; } std::vector descriptor(size); - if (!GetFileSecurityW(resolved.c_str(), information, descriptor.data(), size, &size)) { + if (!GetFileSecurityW(target.c_str(), information, descriptor.data(), size, &size)) { errno = 13; return -1; } struct Token { diff --git a/core/libs/prx/libc/src/FloatBuiltins.cpp b/core/libs/prx/libc/src/FloatBuiltins.cpp index b059652259..235c74f27a 100644 --- a/core/libs/prx/libc/src/FloatBuiltins.cpp +++ b/core/libs/prx/libc/src/FloatBuiltins.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -8,9 +9,6 @@ namespace { -using Int128 = __int128; -using Uint128 = unsigned __int128; - constexpr std::uint32_t DefaultMxcsr = 0x1f80; template diff --git a/core/libs/prx/libc/src/FloatingPoint.cpp b/core/libs/prx/libc/src/FloatingPoint.cpp index e3717ef61b..ea910f0f3b 100644 --- a/core/libs/prx/libc/src/FloatingPoint.cpp +++ b/core/libs/prx/libc/src/FloatingPoint.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -61,8 +62,6 @@ int APS5_VABI fegetexceptflag_nid_postfix(std::uint16_t* flags, int exceptions) return 0; } -int* APS5_VABI __error_nid_postfix(); - void APS5_VABI _Feraise_nid_postfix(int exceptions) { constexpr unsigned Invalid = 0x01, Ranges = 0x1c, Overflows = 0x18, Inexact = 0x20; constexpr int GuestEdom = 33, GuestErange = 34; @@ -96,7 +95,6 @@ int APS5_VABI feclearexcept_nid_postfix(int exceptions) { return 0; } - int APS5_VABI feenableexcept_nid_postfix(int exceptions) { return ChangeTraps(exceptions, true); } @@ -119,6 +117,10 @@ int APS5_VABI fegettrapenable_nid_postfix() { return ~control & Exceptions; } +int APS5_VABI fegetexcept_nid_postfix() { + return fegettrapenable_nid_postfix(); +} + int APS5_VABI fesettrapenable_nid_postfix(int exceptions) { const auto mask = static_cast(exceptions); if (mask > Exceptions) return 1; diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 07fc5468a8..a67a0b877e 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -40,13 +41,20 @@ bool RequiresGuestFormatting(const char* format) { ++format; if (*format == '%') { ++format; continue; } while (*format && std::strchr("-+ #0'0123456789.*$", *format)) ++format; - if (*format == 'S' || *format == 'C' || *format == 'm' || *format == 'p' || + if (*format == 'S' || *format == 'C' || *format == 'p' || (*format == 'l' && (format[1] == 's' || format[1] == 'c' || format[1] == 'p'))) return true; + while (*format && std::strchr("hljztL", *format)) ++format; + if (*format && !std::strchr(LibcDetail::KnownConversions, *format)) return true; + if (*format && std::strchr("aAeEfFgG", *format)) return true; if (*format) ++format; } return false; } +std::size_t HostFormatSize(std::size_t size) { + return std::min(size, static_cast(INT_MAX) + 1); +} + bool HasGuestWideScan(const char* format) { if (format == nullptr) return false; while ((format = std::strchr(format, '%')) != nullptr) { @@ -80,7 +88,6 @@ bool IsWideScan(const std::string& length, char conversion) { return conversion == 'S' || conversion == 'C' || (length == "l" && (conversion == 's' || conversion == 'c' || conversion == '[')); } - #endif std::string HostLength(const std::string& length, char conversion) { @@ -118,10 +125,69 @@ int HostVfscanf(std::FILE* stream, const char* format, std::va_list args) { } #endif -template -int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer nextPointer, NextCapacity nextCapacity) { - if (buffer == nullptr || format == nullptr) return EOF; - const char* input = buffer; +int HostFscanf(std::FILE* stream, const char* format, ...) { + std::va_list args; + va_start(args, format); +#ifdef _WIN32 + const int result = std::vfscanf(stream, format, args); +#else + const int result = HostVfscanf(stream, format, args); +#endif + va_end(args); + return result; +} + +class StringSource { + const char* const start; + const char* position; + +public: + static constexpr bool Contiguous = true; + + explicit StringSource(const char* text) : start(text), position(text) {} + + int Peek() const { return *position == '\0' ? EOF : static_cast(*position); } + void Skip() { ++position; } + void Advance(std::ptrdiff_t count) { position += count; } + std::ptrdiff_t Consumed() const { return position - start; } + const char* Remaining() const { return position; } + + int Scan(const char* directive, bool suppress, void* target, int* consumed) const { + return suppress ? HostSscanf(position, directive, consumed) : HostSscanf(position, directive, target, consumed); + } +}; + +class StreamSource { + std::FILE* const file; + std::ptrdiff_t consumed = 0; + +public: + static constexpr bool Contiguous = false; + + explicit StreamSource(std::FILE* native) : file(native) {} + + int Peek() const { + const int next = std::fgetc(file); + if (next != EOF) std::ungetc(next, file); + return next; + } + + void Skip() { + std::fgetc(file); + ++consumed; + } + + void Advance(std::ptrdiff_t count) { consumed += count; } + std::ptrdiff_t Consumed() const { return consumed; } + + int Scan(const char* directive, bool suppress, void* target, int* count) const { + return suppress ? HostFscanf(file, directive, count) : HostFscanf(file, directive, target, count); + } +}; + +template +int ScanSource(TSource source, const char* format, bool secure, NextPointer nextPointer, NextCapacity nextCapacity) { + if (format == nullptr) return EOF; int assigned = 0; bool converted = false; const char* cursor = format; @@ -129,15 +195,15 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n while (*cursor != '\0') { if (std::isspace(static_cast(*cursor))) { while (std::isspace(static_cast(*cursor))) ++cursor; - while (std::isspace(static_cast(*input))) ++input; + while (std::isspace(source.Peek())) source.Skip(); continue; } if (*cursor != '%' || cursor[1] == '%') { const char expected = *cursor == '%' ? '%' : *cursor; cursor += *cursor == '%' ? 2 : 1; - if (expected == '%') while (std::isspace(static_cast(*input))) ++input; - if (*input != expected) return finish(!converted && *input == '\0' ? EOF : assigned); - ++input; + if (expected == '%') while (std::isspace(source.Peek())) source.Skip(); + if (source.Peek() != static_cast(expected)) return finish(!converted && source.Peek() == EOF ? EOF : assigned); + source.Skip(); continue; } ++cursor; @@ -164,7 +230,7 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n if (!suppress) { void* target = nextPointer(); if (target == nullptr) return EOF; - const auto count = input - buffer; + const auto count = source.Consumed(); if (length == "hh") *static_cast(target) = static_cast(count); else if (length == "h") *static_cast(target) = static_cast(count); else if (length.empty()) *static_cast(target) = static_cast(count); @@ -176,7 +242,8 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n continue; } #ifndef _WIN32 - if (IsWideScan(length, conversion)) { + if constexpr (TSource::Contiguous) if (IsWideScan(length, conversion)) { + const char* const input = source.Remaining(); const char kind = conversion == 'S' ? 's' : conversion == 'C' ? 'c' : conversion; const char* field = input; if (kind == 's') { @@ -222,15 +289,14 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n if (kind != 'c') target[text.size()] = u'\0'; ++assigned; } - input = field; + source.Advance(field - input); converted = true; continue; } #endif if (conversion != 'c' && conversion != '[') { - const char* probe = input; - while (std::isspace(static_cast(*probe))) ++probe; - if (*probe == '\0') return finish(converted ? assigned : EOF); + while (std::isspace(source.Peek())) source.Skip(); + if (source.Peek() == EOF) return finish(converted ? assigned : EOF); } const bool sized = secure && (conversion == 's' || conversion == 'c' || conversion == '['); bool capped = false; @@ -257,29 +323,50 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n directive += specifier; directive += "%n"; int consumed = -1; - const int matched = suppress ? HostSscanf(input, directive.c_str(), &consumed) : HostSscanf(input, directive.c_str(), target, &consumed); - if (consumed < 0 || (!suppress && matched != 1)) return finish(!converted && *input == '\0' ? EOF : assigned); + const int matched = source.Scan(directive.c_str(), suppress, target, &consumed); + if (consumed < 0 || (!suppress && matched != 1)) return finish(!converted && source.Peek() == EOF ? EOF : assigned); + source.Advance(consumed); if (capped && conversion != 'c') { - const char next = input[consumed]; + const int next = source.Peek(); bool overflow = false; - if (conversion == 's') overflow = next != '\0' && !std::isspace(static_cast(next)); - else if (next != '\0') { + if (conversion == 's') overflow = next != EOF && !std::isspace(next); + else if (next != EOF) { char probe[2]; + const char text[2] = {static_cast(next), '\0'}; const std::string test = "%1" + specifier; - overflow = std::sscanf(input + consumed, test.c_str(), probe) == 1; + overflow = std::sscanf(text, test.c_str(), probe) == 1; } if (overflow) { static_cast(target)[0] = '\0'; return finish(assigned); } } - input += consumed; converted = true; if (!suppress) ++assigned; } return finish(assigned); } +template +int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer nextPointer, NextCapacity nextCapacity) { + if (buffer == nullptr) return EOF; + return ScanSource(StringSource(buffer), format, secure, nextPointer, nextCapacity); +} + +template +int ScanGuestStream(FileStream* stream, const char* format, NextPointer nextPointer, NextCapacity nextCapacity) { + if (stream == nullptr || format == nullptr) return EOF; + if (!stream->IsOpen()) { errno = 9; return EOF; } + auto* native = GetNativeStream(stream); + if (!stream->SwitchToReading()) return EOF; +#ifndef _WIN32 + RejectGuestWideScan(format); +#endif + const int result = ScanSource(StreamSource(native), format, true, nextPointer, nextCapacity); + stream->SyncStatus(); + return result; +} + } extern "C" { @@ -378,6 +465,7 @@ int APS5_VABI fscanf_nid_postfix(FileStream* stream, const char* format, ...) { va_end(args); #endif stream->SyncStatus(); + stream->KeepDirectoryReadError(result == -1); return result; } @@ -440,7 +528,7 @@ int APS5_VABI libc_printf_nid_postfix(const char* format, ...) { int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...) { std::va_list args; va_start(args, format); - const int result = RequiresGuestFormatting(format) ? LibcDetail::FormatWindows(buffer, size, format, args) : std::vsnprintf(buffer, size, format, args); + const int result = RequiresGuestFormatting(format) ? LibcDetail::FormatWindows(buffer, size, format, args) : std::vsnprintf(buffer, HostFormatSize(size), format, args); va_end(args); return result; } @@ -546,7 +634,7 @@ int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, if (RequiresGuestFormatting(format)) return LibcDetail::FormatWindows(str, size, format, c); std::va_list copy; va_copy(copy, *reinterpret_cast(c)); - const int result = std::vsnprintf(str, size, format, copy); + const int result = std::vsnprintf(str, HostFormatSize(size), format, copy); va_end(copy); return result; #endif @@ -569,6 +657,47 @@ int APS5_VABI vsnprintf_s_nid_postfix(char* buffer, size_t size, const char* for return FormatSecure(buffer, size, format, args); } +static int WriteSecure(FileStream* stream, const char* format, const void* args) { + if (stream == nullptr || format == nullptr || args == nullptr) return -1; + if (!stream->IsOpen()) { errno = 9; return -1; } + auto* native = GetNativeStream(stream); + std::string text; + int count; + try { + count = LibcDetail::FormatWindows(nullptr, 0, format, args, &text, true); + } catch (const LibcDetail::FormatConstraintViolation&) { + return -1; + } + const int result = std::fwrite(text.data(), 1, static_cast(count), native) == static_cast(count) ? count : -1; + stream->SyncStatus(); + return result; +} + +int APS5_VABI vfprintf_s_nid_postfix(FileStream* stream, const char* format, VaList* args) { + return WriteSecure(stream, format, args); +} + +int APS5_VABI vprintf_s_nid_postfix(const char* format, VaList* args) { + return WriteSecure(&_Stdout_nid_postfix, format, args); +} + +int APS5_VABI fprintf_s_nid_postfix(FileStream* stream, const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + const int result = vfprintf_s_nid_postfix(stream, format, reinterpret_cast(args)); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} + #ifdef _WIN32 int APS5_VABI vsscanf_s_nid_postfix(const char* buffer, const char* format, VaList* args) { @@ -646,6 +775,7 @@ extern FileStream* __stdinp_nid_postfix; int APS5_VABI vfscanf_nid_postfix(FileStream* stream, const char* format, VaList* args) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); + if (!stream->SwitchToReading()) return EOF; #ifdef _WIN32 const int result = LibcDetail::ScanFileWindows_nid_no_patch(native, format, args); #else @@ -656,6 +786,7 @@ int APS5_VABI vfscanf_nid_postfix(FileStream* stream, const char* format, VaList va_end(copy); #endif stream->SyncStatus(); + stream->KeepDirectoryReadError(result == -1); return result; } @@ -680,4 +811,47 @@ int APS5_VABI scanf_nid_postfix(const char* format, ...) { return result; } +int APS5_VABI vfscanf_s_nid_postfix(FileStream* stream, const char* format, VaList* args) { + LibcDetail::FormatArguments arguments(args); + return ScanGuestStream(stream, format, [&] { return arguments.Next(); }, [&] { return arguments.Next(); }); +} + +int APS5_VABI vscanf_s_nid_postfix(const char* format, VaList* args) { + return vfscanf_s_nid_postfix(__stdinp_nid_postfix, format, args); +} + +int APS5_VABI fscanf_s_nid_postfix(FileStream* stream, const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + const int result = vfscanf_s_nid_postfix(stream, format, reinterpret_cast(args)); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} + +int APS5_VABI scanf_s_nid_postfix(const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + const int result = vfscanf_s_nid_postfix(__stdinp_nid_postfix, format, reinterpret_cast(args)); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} + } diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp index a2b3d0fe7d..03591ae318 100644 --- a/core/libs/prx/libc/src/FormattingWide.cpp +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -218,8 +219,6 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = extern "C" { extern FileStream* __stdoutp_nid_postfix; -std::int32_t APS5_VABI fgetwc_nid_postfix(FileStream* stream); -int APS5_VABI ferror_nid_postfix(FileStream* stream); int APS5_VABI vswprintf_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, VaList* args) { if (buffer == nullptr || size == 0) { errno = 22; return -1; } @@ -322,6 +321,7 @@ int APS5_VABI vsnwprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const } int APS5_VABI wprintf_nid_postfix(const char16_t* format, ...) { + if (!__stdoutp_nid_postfix->IsOpen()) { errno = 9; return -1; } APS5_VA_BEGIN(format); int result = -1; try { @@ -361,8 +361,42 @@ int APS5_VABI vwprintf_nid_postfix(const char16_t* format, VaList* args) { return vfwprintf_nid_postfix(__stdoutp_nid_postfix, format, args); } +int APS5_VABI vfwprintf_s_nid_postfix(FileStream* stream, const char16_t* format, VaList* args) { + if (stream == nullptr || format == nullptr || args == nullptr) return -1; + if (!stream->IsOpen()) { errno = 9; return -1; } + int result = -1; + try { + const std::u16string text = FormatWide(format, args, true); + const std::string bytes = ToUtf8(text); + if (text.size() <= static_cast(INT_MAX) && + std::fwrite(bytes.data(), 1, bytes.size(), GetNativeStream(stream)) == bytes.size()) result = static_cast(text.size()); + } catch (const std::invalid_argument&) { + } + stream->SyncStatus(); + return result; +} + +int APS5_VABI vwprintf_s_nid_postfix(const char16_t* format, VaList* args) { + return vfwprintf_s_nid_postfix(__stdoutp_nid_postfix, format, args); +} + +int APS5_VABI fwprintf_s_nid_postfix(FileStream* stream, const char16_t* format, ...) { + APS5_VA_BEGIN(format); + const int result = vfwprintf_s_nid_postfix(stream, format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + +int APS5_VABI wprintf_s_nid_postfix(const char16_t* format, ...) { + APS5_VA_BEGIN(format); + const int result = vfwprintf_s_nid_postfix(__stdoutp_nid_postfix, format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + int APS5_VABI fputwc_nid_postfix(char16_t value, FileStream* stream) { if (stream == nullptr) { errno = 22; return -1; } + if (!stream->IsOpen()) { errno = 9; return -1; } try { const std::string bytes = ToUtf8(std::u16string(1, value)); auto* native = GetNativeStream(stream); @@ -377,6 +411,7 @@ int APS5_VABI fputwc_nid_postfix(char16_t value, FileStream* stream) { int APS5_VABI fputws_nid_postfix(const char16_t* str, FileStream* stream) { if (str == nullptr || stream == nullptr) { errno = 22; return -1; } + if (!stream->IsOpen()) { errno = 9; return -1; } try { const std::u16string text(str); const std::string bytes = ToUtf8(text); @@ -399,7 +434,8 @@ int APS5_VABI putwchar_nid_postfix(char16_t value) { } char16_t* APS5_VABI fgetws_nid_postfix(char16_t* buffer, int size, FileStream* stream) { - if (buffer == nullptr || stream == nullptr || size <= 0) { + if (size < 2) return nullptr; + if (buffer == nullptr || stream == nullptr) { if (stream != nullptr) stream->SetError(); errno = 22; return nullptr; @@ -407,14 +443,11 @@ char16_t* APS5_VABI fgetws_nid_postfix(char16_t* buffer, int size, FileStream* s int count = 0; while (count < size - 1) { const std::int32_t character = fgetwc_nid_postfix(stream); - if (character == -1) { - if (count == 0) return nullptr; - break; - } + if (character == -1) break; buffer[count++] = static_cast(character); if (character == static_cast(u'\n')) break; } - if (ferror_nid_postfix(stream) != 0) return nullptr; + if (count == 0) return nullptr; buffer[count] = 0; return buffer; } diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index 26c1935c5a..f905c3ff30 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -1,4 +1,7 @@ +#include "prx/libc/include/Api.hpp" +#include #include +#include #include #include #include @@ -7,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -28,12 +32,26 @@ #include #include #include +#ifdef __linux__ +#include +#elif defined(__APPLE__) +#include +#include +#endif #include +#ifdef __linux__ +#include +#elif defined(__APPLE__) +#include +#include +#endif #endif #include "prx/libc/include/General.hpp" #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/AppArchive.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/GuestArena.hpp" namespace { // Guest prefixes (without leading slashes) mapped to host directories, e.g. save-data mount points: @@ -50,6 +68,41 @@ PathAliases& Aliases() { return *aliases; } +std::size_t HostPageSize() { + static const std::size_t size = [] { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + return static_cast(info.dwPageSize); +#else + const long value = sysconf(_SC_PAGESIZE); + if (value <= 0) throw std::runtime_error("cannot query the host page size"); + return static_cast(value); +#endif + }(); + return size; +} + +bool CopyGuestBytes(void* destination, const void* source, std::size_t bytes) { + GuestArena::GuestArenaPrepareHostRead_nid_no_patch(source, bytes); +#ifdef _WIN32 + SIZE_T copied = 0; + return ReadProcessMemory(GetCurrentProcess(), source, destination, bytes, &copied) != 0 && copied == bytes; +#elif defined(__linux__) + iovec local{destination, bytes}; + iovec remote{const_cast(source), bytes}; + const auto copied = process_vm_readv(getpid(), &local, 1, &remote, 1, 0); + if (copied < 0 && errno != EFAULT) throw std::runtime_error("guest path: process_vm_readv failed with errno " + std::to_string(errno)); + return copied >= 0 && static_cast(copied) == bytes; +#elif defined(__APPLE__) + mach_vm_size_t copied = 0; + return mach_vm_read_overwrite(mach_task_self(), reinterpret_cast(source), bytes, + reinterpret_cast(destination), &copied) == KERN_SUCCESS && copied == bytes; +#else + throw std::runtime_error("checked guest path copies are not implemented on this host"); +#endif +} + std::string TrimSlashes(const char* path) { std::string s(path); std::size_t start = 0; @@ -255,13 +308,11 @@ std::optional ResolveAlias(const std::string& guestPath, const auto result = std::filesystem::path(host).make_preferred(); return module ? PathOrRelinkedModule(result) : result; } -#ifndef _WIN32 if (guestPath.back() == '/') { - auto result = ResolveHostPath(host, SplitNames(std::string_view(relative).substr(prefix.size() + 1))); + auto result = ResolveHostPath(host, SplitNames(std::string_view(relative).substr(prefix.size() + 1))).make_preferred(); result /= ""; return module ? PathOrRelinkedModule(result) : result; } -#endif return ResolveHostPath(host, SplitNames(std::string_view(relative).substr(prefix.size() + 1)), module).make_preferred(); } return std::nullopt; @@ -318,6 +369,14 @@ void RequireHostDirectory(WorkingDirectory& state, const std::vector WithTrailingSeparator(std::vector names, bool trailing) { if (trailing && !names.empty()) names.emplace_back(); return names; @@ -360,7 +419,10 @@ std::filesystem::path Resolve(WorkingDirectory& state, const char* path, bool mo if (!trailing) names.push_back(name); } if (endsWithDot && !names.empty() && !(archived && AppArchiveHasDirectory_nid_no_patch(HostPath(state, names)))) RequireHostDirectory(state, names); - return HostPath(state, WithTrailingSeparator(std::move(names), trailing), module); + const bool directory = trailing && !names.empty(); + auto resolved = HostPath(state, WithTrailingSeparator(std::move(names), trailing), module); + if (directory) RejectNonDirectory(resolved); + return resolved; } #ifdef _WIN32 using WriteMark = FILETIME; @@ -376,7 +438,11 @@ bool NotBefore(const WriteMark& time, const WriteMark& mark) { using WriteMark = timespec; WriteMark Now() { timespec now; +#ifdef __APPLE__ + clock_gettime(CLOCK_REALTIME, &now); +#else clock_gettime(CLOCK_REALTIME_COARSE, &now); +#endif return now; } bool NotBefore(const WriteMark& time, const WriteMark& mark) { @@ -415,7 +481,11 @@ void FlushPath(const std::filesystem::path& path) { #else std::optional WriteTime(const std::filesystem::path& path) { struct stat data; +#ifdef __APPLE__ + if (::stat(path.c_str(), &data) == 0) return data.st_mtimespec; +#else if (::stat(path.c_str(), &data) == 0) return data.st_mtim; +#endif if (errno == ENOENT || errno == ENOTDIR) return std::nullopt; throw std::system_error(errno, std::generic_category(), "Reading the write time of " + path.string()); } @@ -471,6 +541,31 @@ int SetWorkingDirectory(WorkingDirectory& state, const std::filesystem::path& re } } +extern "C" int CopyGuestPath_nid_no_patch(const char* source, std::string& destination) { + constexpr std::size_t maximum = 1024; + if (source == nullptr) return 14; + const auto address = reinterpret_cast(source); + std::array bytes{}; + std::size_t offset = 0; + while (offset < bytes.size()) { + if (address > std::numeric_limits::max() - offset) return 14; + const auto current = address + offset; + const auto pageRemaining = HostPageSize() - current % HostPageSize(); + const auto count = std::min(pageRemaining, bytes.size() - offset); + if (!CopyGuestBytes(bytes.data() + offset, reinterpret_cast(current), count)) return 14; + if (const auto* end = static_cast(std::memchr(bytes.data() + offset, 0, count))) { + try { + destination.assign(bytes.data(), static_cast(end - bytes.data())); + } catch (const std::bad_alloc&) { + return 12; + } + return 0; + } + offset += count; + } + return 63; +} + #ifndef _WIN32 namespace { std::mutex g_hostStandardErrorMutex; @@ -557,6 +652,36 @@ extern "C" std::FILE* HostStandardErrorFile_nid_no_patch() { if (std::FILE* kept = g_hostStandardErrorFile.load(std::memory_order_acquire)) return kept; return __acrt_iob_func(2); } + +namespace { +std::mutex g_hostStandardStreamMutex; + +int keepHostStandardStream(int standard) { + auto& slot = standard == 1 ? g_hostStandardOutputFile : g_hostStandardErrorFile; + if (auto* stream = slot.load(std::memory_order_acquire)) return ::_fileno(stream); + const std::unique_ptr unlock(__acrt_iob_func(standard), ::_unlock_file); + ::_lock_file(unlock.get()); + const std::lock_guard lock(g_hostStandardStreamMutex); + if (auto* stream = slot.load(std::memory_order_acquire)) return ::_fileno(stream); + const int saved = DuplicateHostDescriptor_nid_no_patch(standard); + if (saved < 0) return -1; + if (!AdoptHostStandardStream_nid_no_patch(standard, saved)) { + const int error = errno; + ::_close(saved); + errno = error; + return -1; + } + return saved; +} +} + +extern "C" int KeepHostStandardError_nid_no_patch() { + return keepHostStandardStream(2); +} + +extern "C" int KeepHostStandardOutput_nid_no_patch() { + return keepHostStandardStream(1); +} #endif extern "C" void AddPathAlias_nid_no_patch(const char* guestPrefix, const char* hostPath) { @@ -679,6 +804,39 @@ extern "C" std::filesystem::path ResolveArchivedPath_nid_no_patch(const char* pa return ResolveLocked(path, module, true); } +extern "C" bool GuestCopyOut_nid_no_patch(void* destination, const void* source, std::size_t bytes) { + if (bytes == 0) return true; + const auto start = reinterpret_cast(destination); + if (start == 0 || bytes > UINTPTR_MAX - start) return false; + const GuestArena::HostWrite guard(destination, bytes); + if (!guard.Open()) return false; +#ifdef _WIN32 + for (auto address = start; address < start + bytes;) { + MEMORY_BASIC_INFORMATION memory{}; + if (VirtualQuery(reinterpret_cast(address), &memory, sizeof(memory)) != sizeof(memory) || memory.State != MEM_COMMIT || + (memory.Protect & PAGE_GUARD) != 0 || + (memory.Protect & (PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) == 0) return false; + address = reinterpret_cast(memory.BaseAddress) + memory.RegionSize; + } + std::memcpy(destination, source, bytes); + return true; +#elif defined(__linux__) + iovec local{const_cast(source), bytes}; + iovec remote{destination, bytes}; + const ssize_t copied = ::process_vm_writev(::getpid(), &local, 1, &remote, 1, 0); + if (copied < 0) { + if (errno == EFAULT) return false; + throw std::runtime_error("GuestCopyOut: process_vm_writev failed with errno " + std::to_string(errno)); + } + return static_cast(copied) == bytes; +#elif defined(__APPLE__) + return ::mach_vm_write(::mach_task_self(), reinterpret_cast(destination), + reinterpret_cast(source), static_cast(bytes)) == KERN_SUCCESS; +#else + throw std::runtime_error("GuestCopyOut: no checked write is available on this host"); +#endif +} + 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"); @@ -695,9 +853,15 @@ extern "C" std::filesystem::path RelinkedModulePath_nid_no_patch(const std::file const auto relinked = RelinkedModuleName(path); std::error_code error; if (std::filesystem::is_regular_file(relinked, error)) return relinked; + const auto parent = relinked.parent_path().empty() ? std::filesystem::path(".") : relinked.parent_path(); NameMatch names[] = {{relinked.filename().string()}, {path.filename().string(), std::nullopt, false, true}}; - MatchNames(relinked.parent_path().empty() ? "." : relinked.parent_path(), names); + MatchNames(parent, names); if (names[0].ambiguous || names[0].path) return MatchedModuleOrPath(path, relinked, names[0]); + for (const char* directory : {"prx", "sce_module", "sce_modules"}) { + NameMatch nested[] = {{relinked.filename().string()}}; + MatchNames(parent / directory, nested); + if (nested[0].ambiguous || nested[0].path) return MatchedModuleOrPath(path, relinked, nested[0]); + } return names[1].path ? *names[1].path : path; } diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index 20cdbc1820..65f574aded 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -46,7 +46,12 @@ void noteAccess(const void* pointer, std::size_t bytes) { } constexpr std::uintptr_t GuestArenaOffset = GuestArena::GuestAddressOffset; -constexpr std::uintptr_t ArenaStart = 0x0000000200000000ull + GuestArenaOffset; +constexpr std::uintptr_t DefaultAllocationStart = 0x0000000200000000ull + GuestArenaOffset; +#ifdef __APPLE__ +constexpr std::uintptr_t ArenaStart = DefaultAllocationStart; +#else +constexpr std::uintptr_t ArenaStart = GuestArenaOffset == 0 ? 0x10000ull : DefaultAllocationStart; +#endif constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull + GuestArenaOffset; constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull + GuestArenaOffset; constexpr std::uintptr_t ApplicationAreaEnd = 0x000000FC00000000ull + GuestArenaOffset; @@ -86,7 +91,7 @@ public: RequireAllocatable(alignment); std::lock_guard lock(_lock); auto candidate = hint < _end ? FindAtOrAbove(hint, bytes, alignment) : 0; - if (candidate == 0 && hint > _base) candidate = FindAtOrAbove(_base, bytes, alignment); + if (candidate == 0 && hint > _base) candidate = FindAtOrAbove(hint < DefaultAllocationStart ? _base : DefaultAllocationStart, bytes, alignment); return Claim(candidate, bytes); } @@ -273,6 +278,10 @@ private: const int failed = ReserveNoReplace(cursor, end - cursor); if (failed == 0) continue; if (failed != EEXIST) { + if (cursor < DefaultAllocationStart && (failed == EACCES || failed == EPERM)) { + occupied.emplace_back(cursor, end); + continue; + } char message[128]; std::snprintf(message, sizeof(message), "guest arena: cannot reserve 0x%llx+0x%llx", static_cast(cursor), static_cast(end - cursor)); @@ -309,7 +318,7 @@ private: } std::uintptr_t FindAtOrAbove(std::uintptr_t hint, std::size_t bytes, std::size_t alignment) const { - std::uintptr_t candidate = alignUp(std::max(_base, hint), alignment); + std::uintptr_t candidate = alignUp(hint == 0 ? DefaultAllocationStart : std::max(_base, hint), alignment); auto it = _used.upper_bound(candidate); if (it != _used.begin() && std::prev(it)->second > candidate) --it; for (; it != _used.end(); ++it) { @@ -510,6 +519,22 @@ bool GuestArenaResidentSpan_nid_postfix(std::uintptr_t address, std::size_t byte return WindowsMappings::Get().ResidentSpan(address, bytes, start, end); } +void GuestArenaCompareWhenShared_nid_no_patch(std::uintptr_t address, std::size_t bytes) { + WindowsMappings::Get().CompareWhenShared(address, bytes); +} + +void GuestArenaCompareResidentWhenShared_nid_no_patch(std::uintptr_t address, std::size_t bytes) { + WindowsMappings::Get().CompareWhenShared(address, bytes, true); +} + +void GuestArenaComparedPages_nid_no_patch(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count) { + WindowsMappings::Get().ComparedPages(address, bytes, pages, count, false); +} + +void GuestArenaTakeUnreportedPages_nid_no_patch(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count) { + WindowsMappings::Get().ComparedPages(address, bytes, pages, count, true); +} + void GuestArenaForgetSection_nid_postfix(void* section) { WindowsMappings::Get().ForgetSection(section); } diff --git a/core/libs/prx/libc/src/GuestHeap.cpp b/core/libs/prx/libc/src/GuestHeap.cpp index 96c95d1063..6277174a21 100644 --- a/core/libs/prx/libc/src/GuestHeap.cpp +++ b/core/libs/prx/libc/src/GuestHeap.cpp @@ -280,6 +280,12 @@ void* allocate(GuestAllocations::Mutation& mutation, std::size_t alignment, std: return pointer; } +void* emptyInPlace(GuestAllocations::Mutation& mutation, void* pointer, bool gpu) { + mutation.Remove(pointer); + mutation.Add(pointer, 0, true, true, gpu); + return pointer; +} + void free(GuestAllocations::Mutation& mutation, void* pointer) { mutation.Remove(pointer); const auto blockBytes = reinterpret_cast(pointer)[-1]; @@ -307,10 +313,7 @@ void* GuestHeapReallocate_nid_postfix(void* pointer, std::size_t bytes) { GuestAllocations::Mutation mutation; const auto range = mutation.Find(pointer); mutation.RequireUnpinned(pointer, range.bytes); - if (bytes == 0) { - free(mutation, pointer); - return nullptr; - } + if (bytes == 0) return emptyInPlace(mutation, pointer, range.gpu); void* result = allocate(mutation, MinimumAlignment, bytes, range.gpu); std::memcpy(result, pointer, std::min(bytes, range.bytes)); free(mutation, pointer); @@ -323,8 +326,10 @@ void* GuestHeapRealign_nid_postfix(void* pointer, std::size_t bytes, std::size_t const auto range = mutation.Find(pointer); mutation.RequireUnpinned(pointer, range.bytes); if (bytes == 0) { - free(mutation, pointer); - return nullptr; + if (alignment == 0 || (alignment & (alignment - 1)) != 0) throw std::invalid_argument("invalid guest heap alignment"); + if (reinterpret_cast(pointer) % alignment != 0) + throw std::invalid_argument("reallocalign to 0 bytes with an alignment the block does not have is not measured on the console"); + return emptyInPlace(mutation, pointer, range.gpu); } void* result = allocate(mutation, alignment, bytes, range.gpu); std::memcpy(result, pointer, std::min(bytes, range.bytes)); diff --git a/core/libs/prx/libc/src/HeapDiagnostics.cpp b/core/libs/prx/libc/src/HeapDiagnostics.cpp index 04324b8d39..15f420f9c7 100644 --- a/core/libs/prx/libc/src/HeapDiagnostics.cpp +++ b/core/libs/prx/libc/src/HeapDiagnostics.cpp @@ -1,11 +1,26 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HeapDiagnostics.hpp" #include #include #include #include +#include #include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#include +#endif + +extern "C" std::FILE* GuestStandardOutputFile_nid_no_patch(); + namespace { std::uint64_t mspaceAtomicIdMask = 0; @@ -19,6 +34,26 @@ static_assert(sizeof(LibcHeapInfo) == 32); static_assert(offsetof(LibcHeapInfo, mspace_atomic_id_mask) == 16); static_assert(offsetof(LibcHeapInfo, mstate_table) == 24); +constexpr std::size_t MspaceNameOffset = 0x3B0; +constexpr std::size_t MspaceNumberOffset = 0x3F4; +constexpr std::uint32_t SegmentOutOfRange = 1; + +unsigned long CurrentProcessId() { +#ifdef _WIN32 + return GetCurrentProcessId(); +#else + return static_cast(::getpid()); +#endif +} + +unsigned long CurrentThreadId() { +#ifdef _WIN32 + return GetCurrentThreadId(); +#else + return static_cast(::syscall(SYS_gettid)); +#endif +} + } extern "C" { @@ -39,6 +74,19 @@ void APS5_VABI sceLibcInternalBacktraceForGame_nid_postfix(const char* heapName) } void APS5_VABI sceLibcInternalHeapErrorReportForGame_nid_postfix(void* heap, void* block, std::uint32_t error) { + if (heap == nullptr) throw std::invalid_argument("sceLibcInternalHeapErrorReportForGame: null heap"); + const auto* state = static_cast(heap); + std::uint32_t number = 0; + std::memcpy(&number, state + MspaceNumberOffset, sizeof(number)); + if (_Stdout_nid_postfix.IsOpen()) { + std::FILE* output = GuestStandardOutputFile_nid_no_patch(); + std::fprintf(output, "[SceLibc] A heap error is detected.\n[SceLibc] -- Mspace Name --\n[SceLibc] %s", state + MspaceNameOffset); + if (number != 0) std::fprintf(output, " (%2u)", number); + std::fputc('\n', output); + if (error == SegmentOutOfRange) std::fprintf(output, "[SceLibc] -- Error reason --\n[SceLibc] segment out of range.\n"); + std::fprintf(output, "[SceLibc] pid:<0x%lx> tid:<0x%lx>\n", CurrentProcessId(), CurrentThreadId()); + std::fflush(output); + } std::fprintf(stderr, "[libc] heap error 0x%x: heap %p, block %p, reported from guest 0x%llx\n", error, heap, block, static_cast(reinterpret_cast(__builtin_return_address(0)))); } diff --git a/core/libs/prx/libc/src/Hostname.cpp b/core/libs/prx/libc/src/Hostname.cpp index 7b205e3659..b2e7999984 100644 --- a/core/libs/prx/libc/src/Hostname.cpp +++ b/core/libs/prx/libc/src/Hostname.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,7 +6,7 @@ #include extern "C" int APS5_VABI gethostname_nid_postfix(char* name, std::size_t length) { - constexpr char hostname[] = "AnyPS5"; + constexpr char hostname[] = ""; if (name == nullptr) return 0; const int savedError = errno; const auto count = std::min(length, sizeof(hostname)); diff --git a/core/libs/prx/libc/src/IntegerBuiltins.cpp b/core/libs/prx/libc/src/IntegerBuiltins.cpp index 2df42c3913..44a6e6df13 100644 --- a/core/libs/prx/libc/src/IntegerBuiltins.cpp +++ b/core/libs/prx/libc/src/IntegerBuiltins.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -8,9 +9,6 @@ namespace { -using Int128 = __int128; -using Uint128 = unsigned __int128; - constexpr Uint128 Int128SignBit = static_cast(1) << 127; constexpr Int128 Int128Max = static_cast(~Int128SignBit); constexpr Int128 Int128Min = static_cast(Int128SignBit); diff --git a/core/libs/prx/libc/src/Interfaces.cpp b/core/libs/prx/libc/src/Interfaces.cpp index 8e39cfa29b..f9ec682ec7 100644 --- a/core/libs/prx/libc/src/Interfaces.cpp +++ b/core/libs/prx/libc/src/Interfaces.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -5,11 +6,13 @@ #include #else #include -#include #include -#include #include +#ifdef __linux__ +#include +#include #include +#endif #include #include #include @@ -29,17 +32,6 @@ #include #include -extern "C" int* APS5_VABI __error_nid_postfix(); - -struct GuestInterfaceAddress { - GuestInterfaceAddress* next; - char* name; - std::uint32_t flags; - void* address; - void* netmask; - void* destination; - void* data; -}; static_assert(sizeof(GuestInterfaceAddress) == 56); static_assert(offsetof(GuestInterfaceAddress, flags) == 16); static_assert(offsetof(GuestInterfaceAddress, address) == 24); @@ -88,6 +80,22 @@ std::vector GuestAddress(const sockaddr* address) { return GuestIpv6(reinterpret_cast(&v6->sin6_addr), v6->sin6_scope_id); } +#ifndef _WIN32 +std::vector GuestNetmask(const sockaddr* netmask, int family) { + sockaddr_storage mask{}; + const std::size_t size = family == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); +#ifdef __APPLE__ + const std::size_t length = std::min(netmask->sa_len, size); +#else + const std::size_t length = size; +#endif + if (length > offsetof(sockaddr, sa_data)) + std::memcpy(reinterpret_cast(&mask) + offsetof(sockaddr, sa_data), netmask->sa_data, length - offsetof(sockaddr, sa_data)); + mask.ss_family = static_cast(family); + return GuestAddress(reinterpret_cast(&mask)); +} +#endif + std::string CheckedName(const char* name) { const std::string checked(name); if (checked.empty() || checked.size() >= guestNameCapacity) @@ -160,6 +168,7 @@ int HostEntries(std::vector& entries) { return 0; } #else +#ifdef __linux__ constexpr std::uint8_t guestLinkFamily = 18; constexpr std::size_t guestLinkNameArea = 8 + 16; constexpr std::size_t guestLinkMinimum = 54; @@ -171,6 +180,7 @@ constexpr std::uint8_t guestLinkStateDown = 1; constexpr std::uint8_t guestLinkStateUp = 2; constexpr std::size_t guestInterfaceDataSize = 152; constexpr unsigned int hostLowerUp = 0x10000; +#endif struct HostInterface { std::string name; @@ -179,6 +189,7 @@ struct HostInterface { std::vector addresses; }; +#ifdef __linux__ template void Store(std::vector& bytes, std::size_t offset, TValue value) { std::memcpy(bytes.data() + offset, &value, sizeof(value)); @@ -253,6 +264,7 @@ std::vector GuestInterfaceData(const std::string& name, unsigned i } return data; } +#endif std::uint32_t InterfaceFlags(unsigned int host) { constexpr std::pair flags[] = { @@ -288,6 +300,7 @@ int HostEntries(std::vector& entries) { try { for (auto* item = host; item; item = item->ifa_next) { if (!item->ifa_addr) continue; +#ifdef __linux__ if (item->ifa_addr->sa_family == AF_PACKET) { const auto& link = *reinterpret_cast(item->ifa_addr); HostInterface& owner = interfaceFor(CheckedName(item->ifa_name)); @@ -302,9 +315,10 @@ int HostEntries(std::vector& entries) { owner.linked = true; continue; } +#endif if (item->ifa_addr->sa_family != AF_INET && item->ifa_addr->sa_family != AF_INET6) continue; if (!item->ifa_netmask) throw std::runtime_error(std::string("getifaddrs: host address without a netmask on ") + item->ifa_name); - Entry entry{CheckedName(item->ifa_name), InterfaceFlags(item->ifa_flags), GuestAddress(item->ifa_addr), GuestAddress(item->ifa_netmask), {}}; + Entry entry{CheckedName(item->ifa_name), InterfaceFlags(item->ifa_flags), GuestAddress(item->ifa_addr), GuestNetmask(item->ifa_netmask, item->ifa_addr->sa_family), {}}; const int family = item->ifa_addr->sa_family; if (item->ifa_flags & IFF_POINTOPOINT) { if (item->ifa_dstaddr && item->ifa_dstaddr->sa_family == family) entry.destination = GuestAddress(item->ifa_dstaddr); diff --git a/core/libs/prx/libc/src/JumpBuffer.cpp b/core/libs/prx/libc/src/JumpBuffer.cpp index 77dca484e4..17c34cf9d0 100644 --- a/core/libs/prx/libc/src/JumpBuffer.cpp +++ b/core/libs/prx/libc/src/JumpBuffer.cpp @@ -1,12 +1,62 @@ +#include "prx/libc/include/JumpBuffer.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/AsmMacros.hpp" +#include +#include +#include +#include + +namespace JumpBuffer { +namespace { + +constexpr std::size_t MaskOffset = 72; +constexpr std::size_t MaskSavedOffset = 88; +constexpr int SigBlock = 1; +constexpr int SigSetmask = 3; + +std::atomic guestSigprocmask{nullptr}; + +} + +extern "C" { + +void GuestSigprocmaskRegister_nid_no_patch(Sigprocmask sigprocmask) { + guestSigprocmask.store(sigprocmask); +} + +void APS5_VABI JumpBufferSaveMask_nid_no_patch(unsigned char* buffer) { + const auto sigprocmask = guestSigprocmask.load(); + const std::int32_t saved = sigprocmask != nullptr && sigprocmask(SigBlock, nullptr, buffer + MaskOffset) == 0; + std::memcpy(buffer + MaskSavedOffset, &saved, sizeof(saved)); +} + +void APS5_VABI JumpBufferRestoreMask_nid_no_patch(const unsigned char* buffer) { + std::int32_t saved = 0; + std::memcpy(&saved, buffer + MaskSavedOffset, sizeof(saved)); + const auto sigprocmask = guestSigprocmask.load(); + if (saved != 0 && sigprocmask != nullptr) sigprocmask(SigSetmask, buffer + MaskOffset, nullptr); +} + +} + +} // setjmp/longjmp must capture the guest's own frame, so they are written directly in assembly with // the guest (System V) calling convention. The saved state fits the guest's 96-byte jmp_buf: -// return address, rbx, rsp, rbp, r12-r15, MXCSR and the x87 control word. +// return address, rbx, rsp, rbp, r12-r15, MXCSR, the x87 control word and, as FreeBSD's do, the +// signal mask at 72 with the flag sigsetjmp keeps at 88, once libkernel has registered sigprocmask. + +#if defined(_WIN32) || defined(__APPLE__) +#define APS5_JUMP_ASM_CALL(name) " call " APS5_ASM_SYMBOL(name) "\n" +#else +#define APS5_JUMP_ASM_CALL(name) " call " name "@PLT\n" +#endif asm(".text\n" APS5_ASM_FUNCTION("setjmp_nid_postfix") + " push %rdi\n" + APS5_JUMP_ASM_CALL("JumpBufferSaveMask_nid_no_patch") + " pop %rdi\n" " mov (%rsp), %rax\n" " mov %rax, 0(%rdi)\n" " mov %rbx, 8(%rdi)\n" @@ -22,15 +72,16 @@ asm(".text\n" " xor %eax, %eax\n" " ret\n" APS5_ASM_FUNCTION("longjmp_nid_postfix") -#ifdef _WIN32 " mov %rdi, %rbx\n" " mov %esi, %r12d\n" - " mov 16(%rdi), %rdi\n" " and $-16, %rsp\n" + APS5_JUMP_ASM_CALL("JumpBufferRestoreMask_nid_no_patch") +#ifdef _WIN32 + " mov 16(%rbx), %rdi\n" " call GuestStackSwitch_nid_no_patch\n" +#endif " mov %rbx, %rdi\n" " mov %r12d, %esi\n" -#endif " mov %esi, %eax\n" " test %eax, %eax\n" " jnz 1f\n" diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index a4388c8dfe..7173c10cbf 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -24,11 +25,6 @@ #include "prx/libc/include/GuestLocale.hpp" #include "SceTypes.hpp" -extern "C" { -void* APS5_VABI _Znwm_nid_postfix(std::size_t size); -void APS5_VABI _ZdlPv_nid_postfix(void* pointer); -} - namespace { std::mutex g_localeInitMutex; @@ -110,6 +106,7 @@ void APS5_VABI DestroyCollate(GuestLocale::Facet*) {} void APS5_VABI DeleteCollate(GuestLocale::Facet* self) { if (self == nullptr) throw std::invalid_argument("collate delete: null facet"); _ZdlPv_nid_postfix(self); + } void APS5_VABI RetainFacet(GuestLocale::Facet* self) { @@ -220,6 +217,83 @@ std::int64_t APS5_VABI WideCollateHash(const GuestLocale::CollateFacet*, const c const GuestLocale::CollateVtable g_wideCollateVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, WideCollateCompare, WideCollateTransform, WideCollateHash}; +bool APS5_VABI CodecvtAlwaysNoconv(const GuestLocale::Facet*) { + return true; +} + +int APS5_VABI CodecvtConstantOne(const GuestLocale::Facet*) { + return 1; +} + +int APS5_VABI CodecvtNoConversion(GuestLocale::Facet*, std::uint64_t*, const char* first1, const char*, const char** next1, char* first3, char*, char** next3) { + *next1 = first1; + *next3 = first3; + return 3; +} + +int APS5_VABI CodecvtUnshift(GuestLocale::Facet*, std::uint64_t*, char* first3, char*, char** next3) { + *next3 = first3; + return 0; +} + +std::size_t APS5_VABI CodecvtLength(GuestLocale::Facet*, std::uint64_t*, const char* first1, const char* last1, std::size_t count) { + return std::min(static_cast(last1 - first1), count); +} + +const GuestLocale::CodecvtVtable g_codecvtVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, CodecvtAlwaysNoconv, CodecvtConstantOne, CodecvtConstantOne, CodecvtNoConversion, CodecvtNoConversion, CodecvtUnshift, CodecvtLength}; + +GuestLocale::String* APS5_VABI AssignCString(GuestLocale::String* result, const char* source) { + if (result == nullptr || source == nullptr) throw std::invalid_argument("numpunct string: null argument"); + const std::size_t length = std::strlen(source); + char* data = result->buffer; + std::size_t capacity = GuestStringInlineCapacity; + if (length > GuestStringInlineCapacity) { + capacity = length | GuestStringCapacityMask; + data = static_cast(AllocateStringStorage(capacity + 1)); + } + if (length != 0) std::memcpy(data, source, length); + data[length] = 0; + if (data != result->buffer) result->pointer = data; + result->size = length; + result->capacity = capacity; + return result; +} + +char APS5_VABI NumpunctDecimalPoint(const GuestLocale::NumpunctFacet* self) { + return self->decimalPoint; +} + +char APS5_VABI NumpunctThousandsSep(const GuestLocale::NumpunctFacet* self) { + return self->thousandsSep; +} + +GuestLocale::String* APS5_VABI NumpunctGrouping(GuestLocale::String* result, const GuestLocale::NumpunctFacet* self) { + return AssignCString(result, self->grouping); +} + +GuestLocale::String* APS5_VABI NumpunctFalsename(GuestLocale::String* result, const GuestLocale::NumpunctFacet* self) { + return AssignCString(result, self->falsename); +} + +GuestLocale::String* APS5_VABI NumpunctTruename(GuestLocale::String* result, const GuestLocale::NumpunctFacet* self) { + return AssignCString(result, self->truename); +} + +const GuestLocale::NumpunctVtable g_numpunctVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, NumpunctDecimalPoint, NumpunctThousandsSep, NumpunctGrouping, NumpunctFalsename, NumpunctTruename}; + +void APS5_VABI NumPutMethodUnsupported() { + NotImplemented_nid_no_patch("num_put::do_put"); +} + +void APS5_VABI TimePutMethodUnsupported() { + NotImplemented_nid_no_patch("time_put::do_put"); +} + +const GuestLocale::UnsupportedMethodsVtable g_numPutVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, {NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported}}; +const GuestLocale::UnsupportedMethodsVtable g_timePutVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, {TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported}}; + +constexpr std::size_t GuestTimePutFacetSize = 0xb8; + constexpr std::array MakeClassificationTable() { std::array table{}; for (unsigned int value = 0; value < 256; ++value) { @@ -227,6 +301,7 @@ constexpr std::array MakeClassificationTable() { if (value < 32 || value == 127) mask |= 0x80; if (value == ' ') mask |= 0x04; if (value >= '\t' && value <= '\r') mask |= 0x40; + if (value == '\t') mask |= 0x400; if (value >= 'A' && value <= 'Z') mask |= 0x02; if (value >= 'a' && value <= 'z') mask |= 0x10; if (value >= '0' && value <= '9') mask |= 0x20; @@ -254,18 +329,18 @@ constexpr auto g_lowerTable = MakeCaseTable(false); constexpr auto g_upperTable = MakeCaseTable(true); void ValidateCharacter(int value) { - if (value != EOF && (value < 0 || value > UCHAR_MAX)) throw std::invalid_argument("Invalid character value"); + if (value < SCHAR_MIN || value > UCHAR_MAX) throw std::invalid_argument("Invalid character value"); } -int ClassifyCharacter(int value, std::ctype_base::mask mask) { +int ClassifyCharacter(int value, short mask) { ValidateCharacter(value); if (value == EOF) return 0; - return std::use_facet>(std::locale::classic()).is(mask, static_cast(value)); + return g_classificationTable[static_cast(value < 0 ? value + UCHAR_MAX + 1 : value) + 1] & mask; } int ConvertCharacter(int value, bool upper) { ValidateCharacter(value); - if (value == EOF) return EOF; + if (value < 0) return value; const auto& facet = std::use_facet>(std::locale::classic()); const auto character = static_cast(value); return static_cast(upper ? facet.toupper(character) : facet.tolower(character)); @@ -275,18 +350,20 @@ int ConvertCharacter(int value, bool upper) { extern "C" { -int APS5_VABI isupper_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::upper); } -int APS5_VABI islower_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::lower); } -int APS5_VABI isalpha_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alpha); } -int APS5_VABI isdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::digit); } -int APS5_VABI isalnum_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alnum); } -int APS5_VABI isspace_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::space); } -int APS5_VABI isblank_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::blank); } -int APS5_VABI iscntrl_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::cntrl); } -int APS5_VABI isprint_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::print); } -int APS5_VABI isgraph_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::graph); } -int APS5_VABI ispunct_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::punct); } -int APS5_VABI isxdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::xdigit); } +int APS5_VABI isupper_nid_postfix(int c) { return ClassifyCharacter(c, 0x02); } +int APS5_VABI islower_nid_postfix(int c) { return ClassifyCharacter(c, 0x10); } +int APS5_VABI isalpha_nid_postfix(int c) { return ClassifyCharacter(c, 0x12); } +int APS5_VABI isdigit_nid_postfix(int c) { return ClassifyCharacter(c, 0x20); } +int APS5_VABI isalnum_nid_postfix(int c) { return ClassifyCharacter(c, 0x32); } +int APS5_VABI isspace_nid_postfix(int c) { return ClassifyCharacter(c, 0x44); } +int APS5_VABI isblank_nid_postfix(int c) { return ClassifyCharacter(c, 0x404); } +int APS5_VABI iscntrl_nid_postfix(int c) { return ClassifyCharacter(c, 0x80); } +int APS5_VABI isprint_nid_postfix(int c) { return ClassifyCharacter(c, 0x3e); } +int APS5_VABI isgraph_nid_postfix(int c) { return ClassifyCharacter(c, 0x3a); } +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 toupper_nid_postfix(int c) { return ConvertCharacter(c, true); } int APS5_VABI tolower_nid_postfix(int c) { return ConvertCharacter(c, false); } @@ -294,10 +371,20 @@ std::uint64_t _ZNSt5ctypeIcE2idE_nid_postfix = 0; std::uint64_t _ZNSt5ctypeIwE2idE_nid_postfix = 0; std::uint64_t _ZNSt7collateIwE2idE_nid_postfix = 0; std::uint64_t _ZNSt7collateIcE2idE_nid_postfix = 0; +std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] {}; std::uint64_t _ZNSt7codecvtIcc9_MbstatetE2idE_nid_postfix = 0; std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[16] {}; +std::uintptr_t _ZTVSt7codecvtIwc9_MbstatetE_nid_postfix[13] {}; std::uint64_t _ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[12] {}; +std::uint64_t _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; +std::uintptr_t _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[17] {}; +std::uint64_t _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; +std::uint64_t _ZNSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE2idE_nid_postfix = 0; +std::uintptr_t _ZTVSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[14] {}; +std::uint64_t _ZNSt8numpunctIcE2idE_nid_postfix = 0; +std::uint64_t _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; +std::uintptr_t _ZTVSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[13] {}; std::streamoff _ZSt7_BADOFF_nid_postfix = -1; std::fpos_t _ZSt4_Fpz_nid_postfix {}; @@ -323,11 +410,6 @@ GuestLocale::IosBase* APS5_VABI SetStreamWidth(GuestLocale::IosBase* stream, std return stream; } -struct StreamManipulator { - GuestLocale::IosBase* (APS5_VABI* function)(GuestLocale::IosBase*, std::int32_t); - std::int32_t argument; -}; - static_assert(sizeof(StreamManipulator) == 0x10); StreamManipulator APS5_VABI _ZSt4setwi_nid_postfix(std::int32_t width) { @@ -367,9 +449,7 @@ GuestLocale::Implementation* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_post } namespace { -struct GuestLocaleFacade { - GuestLocale::Implementation* implementation; -}; + GuestLocaleFacade g_classicLocaleFacade{&g_classicLocale}; } @@ -397,6 +477,43 @@ std::size_t APS5_VABI _ZNSt7collateIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_pos return 1; } +std::size_t APS5_VABI _ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPKS2__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) { + if (facet != nullptr && *facet == nullptr) { + auto* object = static_cast(_Znwm_nid_postfix(sizeof(GuestLocale::Facet))); + *object = {&g_codecvtVtable.facet, 0, 0}; + *facet = object; + } + return 2; +} + +std::size_t APS5_VABI _ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) { + if (facet != nullptr && *facet == nullptr) { + auto* punct = static_cast(_Znwm_nid_postfix(sizeof(GuestLocale::NumpunctFacet))); + *punct = {{&g_numpunctVtable.facet, 0, 0}, "", '.', ',', "false", "true"}; + *facet = &punct->base; + } + return 4; +} + +std::size_t APS5_VABI _ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) { + if (facet != nullptr && *facet == nullptr) { + auto* object = static_cast(_Znwm_nid_postfix(sizeof(GuestLocale::Facet))); + *object = {&g_numPutVtable.facet, 0, 0}; + *facet = object; + } + return 4; +} + +std::size_t APS5_VABI _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) { + if (facet != nullptr && *facet == nullptr) { + auto* object = static_cast(_Znwm_nid_postfix(GuestTimePutFacetSize)); + std::memset(object, 0, GuestTimePutFacetSize); + object->vtable = &g_timePutVtable.facet; + *facet = object; + } + return 5; +} + void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(GuestLocale::LocinfoStorage* self, const char* localeName) { if (self == nullptr || localeName == nullptr || std::strcmp(localeName, "C") != 0) throw std::invalid_argument("_Locinfo: only the C locale is supported"); new (self) GuestLocale::LocinfoStorage{}; @@ -464,9 +581,11 @@ std::uint32_t APS5_VABI _Towctrans_nid_postfix(std::uint32_t c, std::uint64_t de std::uint64_t _ZNSt7codecvtIwc9_MbstatetE2idE_nid_postfix = 0; alignas(16) unsigned char _ZSt4cout_nid_postfix[0x400] {}; alignas(16) unsigned char _ZSt4cerr_nid_postfix[0x400] {}; +alignas(16) unsigned char _ZSt4clog_nid_postfix[0x400] {}; alignas(16) unsigned char _ZSt3cin_nid_postfix[0x400] {}; alignas(16) unsigned char _ZSt5wcout_nid_postfix[0x400] {}; alignas(16) unsigned char _ZSt5wcerr_nid_postfix[0x400] {}; +alignas(16) unsigned char _ZSt5wclog_nid_postfix[0x400] {}; alignas(16) unsigned char _ZSt4wcin_nid_postfix[0x400] {}; // Facet vectors are read by inlined guest code, so they come from the guest application heap. @@ -511,6 +630,7 @@ void APS5_VABI _init_env_nid_postfix() { void APS5_VABI init_env_nid_postfix(const InitEnvParams* params) { (void)params; _init_env_nid_postfix(); + } SonyLconv* APS5_VABI localeconv_nid_postfix(void) { diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index bfed7a8a4d..b7635d462d 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1,4 +1,6 @@ +#include "prx/libc/include/Api.hpp" #include +#include #include #include #include @@ -67,6 +69,13 @@ float SinhfExpm1(float x) { return (oneMinusTwoPowMinusK - (e - x)) * twoPowK; } +volatile float AcosfPio2Low = 7.5497894159e-08f; + +float AcosfRational(float z) { + constexpr float P0 = 1.6666586697e-01f, P1 = -4.2743422091e-02f, P2 = -8.6563630030e-03f, Q1 = -7.0662963390e-01f; + return z * (P0 + z * (P1 + z * P2)) / (1.0f + z * Q1); +} + float SinhfExp(float x) { constexpr float Ln2High = 6.9314575195e-01f, Ln2Low = 1.4286067653e-06f, InverseLn2 = 1.4426950216e+00f; constexpr float P1 = 1.6666625440e-1f, P2 = -2.7667332906e-3f; @@ -226,7 +235,6 @@ T GuestFmax(T x, T y) { } float GuestBesselSecondKindZero() { - std::feraiseexcept(FE_DIVBYZERO); return -std::numeric_limits::infinity(); } @@ -290,7 +298,12 @@ std::int64_t APS5_VABI imaxabs_nid_postfix(std::int64_t x) { return labs_nid_postfix(x); } -float APS5_VABI fmodf_nid_postfix(float x, float y) { return std::fmod(x, y); } +float APS5_VABI fmodf_nid_postfix(float x, float y) { + const int saved_errno = errno; + const float result = std::fmod(x, y); + errno = saved_errno; + return result; +} float APS5_VABI asinf_nid_postfix(float x) { const int saved_errno = errno; const float result = std::asin(x); @@ -304,10 +317,23 @@ float APS5_VABI acosf_nid_postfix(float x) { if (std::fabs(x) > 1.0f) { return std::bit_cast(0xffc00000u); } - const int saved_errno = errno; - const float result = std::acos(x); - errno = saved_errno; - return result; + constexpr float Pi = 3.1415925026e+00f, Pio2High = 1.5707962513e+00f; + const std::uint32_t magnitude = std::bit_cast(x) & 0x7fffffffu; + if (magnitude == 0x3f800000u) return std::signbit(x) ? Pi + 2.0f * AcosfPio2Low : 0.0f; + if (magnitude < 0x3f000000u) { + if (magnitude <= 0x32800000u) return Pio2High + AcosfPio2Low; + return Pio2High - (x - (AcosfPio2Low - x * AcosfRational(x * x))); + } + if (std::signbit(x)) { + const float z = (1.0f + x) * 0.5f; + const float s = std::sqrt(z); + return Pi - 2.0f * (s + (AcosfRational(z) * s - AcosfPio2Low)); + } + const float z = (1.0f - x) * 0.5f; + const float s = std::sqrt(z); + const float high = std::bit_cast(std::bit_cast(s) & 0xfffff000u); + const float correction = (z - high * high) / (s + high); + return 2.0f * (high + (AcosfRational(z) * s + correction)); } float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); } float APS5_VABI hypotf_nid_postfix(float x, float y) { return static_cast(std::hypot(static_cast(x), static_cast(y))); } @@ -321,9 +347,51 @@ double APS5_VABI hypot_nid_postfix(double x, double y) { } return std::hypot(x, y); } -float APS5_VABI tanf_nid_postfix(float x) { return std::tan(x); } float APS5_VABI log10f_nid_postfix(float x) { return std::log10(x); } float APS5_VABI logbf_nid_postfix(float x) { return std::logb(x); } +float APS5_VABI exp2f_nid_postfix(float x) { + static constexpr double exp2ft[16] = { + 0x1.6a09e667f3bcdp-1, 0x1.7a11473eb0187p-1, 0x1.8ace5422aa0dbp-1, 0x1.9c49182a3f090p-1, + 0x1.ae89f995ad3adp-1, 0x1.c199bdd85529cp-1, 0x1.d5818dcfba487p-1, 0x1.ea4afa2a490dap-1, + 0x1.0000000000000p+0, 0x1.0b5586cf9890fp+0, 0x1.172b83c7d517bp+0, 0x1.2387a6e756238p+0, + 0x1.306fe0a31b715p+0, 0x1.3dea64c123422p+0, 0x1.4bfdad5362a27p+0, 0x1.5ab07dd485429p+0, + }; + static volatile float huge = 0x1p100f; + static volatile float twom100 = 0x1p-100f; + const float redux = 0x1.8p23f / 16; + const float P1 = 0x1.62e430p-1f; + const float P2 = 0x1.ebfbe0p-3f; + const float P3 = 0x1.c6b348p-5f; + const float P4 = 0x1.3b2c9cp-7f; + const std::uint32_t hx = std::bit_cast(x); + const std::uint32_t ix = hx & 0x7fffffff; + if (ix >= 0x43000000) { + if (ix >= 0x7f800000) { + return (ix & 0x7fffff) != 0 || (hx & 0x80000000) == 0 ? x + x : 0.0f; + } + if (x >= 0x1.0p7f) { + errno = ERANGE; + return huge * huge; + } + if (x <= -0x1.2cp7f) { + errno = ERANGE; + return twom100 * twom100; + } + } else if (ix <= 0x33000000) { + return 1.0f + x; + } + volatile float rounded = x + redux; + float t = rounded; + std::uint32_t i0 = std::bit_cast(t) + 8; + const std::int32_t k = static_cast(i0 >> 4) << 20; + i0 &= 15; + t -= redux; + const double z = x - t; + const double twopk = std::bit_cast(static_cast(static_cast(0x3ff00000 + k)) << 32); + const double u = exp2ft[i0] * z; + const double tv = exp2ft[i0] + u * (P1 + z * P2) + u * (z * z) * (P3 + z * P4); + return static_cast(tv * twopk); +} double APS5_VABI exp2_nid_postfix(double x) { int saved_errno = errno; double result = std::exp2(x); @@ -348,9 +416,9 @@ int APS5_VABI __isfinitef_nid_postfix(float x) { return std::isfinite(x) ? 1 : 0 int APS5_VABI __isnormal_nid_postfix(double x) { return std::isnormal(x) ? 1 : 0; } int APS5_VABI __isnormalf_nid_postfix(float x) { return std::isnormal(x) ? 1 : 0; } int APS5_VABI __isinff_nid_postfix(float x) { return std::isinf(x) ? 1 : 0; } -float APS5_VABI nearbyintf_nid_postfix(float x) { return std::nearbyintf(x); } +float APS5_VABI nearbyintf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::nearbyintf(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::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 asin_nid_postfix(double x) { @@ -373,12 +441,12 @@ float APS5_VABI copysignf_nid_postfix(float x, float y) { return std::copysign(x, y); } int APS5_VABI abs_nid_postfix(int x) { return x < 0 ? static_cast(0u - static_cast(x)) : x; } -double APS5_VABI trunc_nid_postfix(double x) { return std::trunc(x); } -double APS5_VABI ceil_nid_postfix(double x) { return std::ceil(x); } -double APS5_VABI floor_nid_postfix(double x) { return std::floor(x); } -float APS5_VABI truncf_nid_postfix(float x) { return std::trunc(x); } -float APS5_VABI ceilf_nid_postfix(float x) { return std::ceil(x); } -float APS5_VABI floorf_nid_postfix(float x) { return std::floor(x); } +double APS5_VABI trunc_nid_postfix(double x) { return std::isnan(x) ? x + x : std::trunc(x); } +double APS5_VABI ceil_nid_postfix(double x) { return std::isnan(x) ? x + x : std::ceil(x); } +double APS5_VABI floor_nid_postfix(double x) { return std::isnan(x) ? x + x : std::floor(x); } +float APS5_VABI truncf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::trunc(x); } +float APS5_VABI ceilf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::ceil(x); } +float APS5_VABI floorf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::floor(x); } double APS5_VABI fma_nid_postfix(double x, double y, double z) { return std::fma(x, y, z); } float APS5_VABI fmaf_nid_postfix(float x, float y, float z) { return std::fma(x, y, z); } double APS5_VABI fmin_nid_postfix(double x, double y) { return GuestFmin(x, y); } @@ -456,9 +524,195 @@ double APS5_VABI atan_nid_postfix(double x) { return std::atan(x); } double APS5_VABI tan_nid_postfix(double x) { return std::tan(x); } double APS5_VABI log2_nid_postfix(double x) { return std::log2(x); } double APS5_VABI log_nid_postfix(double x) { return std::log(x); } -double APS5_VABI log1p_nid_postfix(double x) { return std::log1p(x); } +double APS5_VABI log1p_nid_postfix(double x) { + const double ln2_hi = 6.93147180369123816490e-01; + const double ln2_lo = 1.90821492927058770002e-10; + const double two54 = 1.80143985094819840000e+16; + const double Lp1 = 6.666666666666735130e-01; + const double Lp2 = 3.999999999940941908e-01; + const double Lp3 = 2.857142874366239149e-01; + const double Lp4 = 2.222219843214978396e-01; + const double Lp5 = 1.818357216161805012e-01; + const double Lp6 = 1.531383769920937332e-01; + const double Lp7 = 1.479819860511658591e-01; + static volatile double vzero = 0.0; + const std::int32_t hx = static_cast(std::bit_cast(x) >> 32); + const std::int32_t ax = hx & 0x7fffffff; + std::int32_t k = 1; + std::int32_t hu = 0; + double f = 0.0; + double c = 0.0; + if (hx < 0x3fda827a) { + if (ax >= 0x3ff00000) { + if (x == -1.0) { + return -two54 / vzero; + } + errno = EDOM; + return (x - x) / (x - x); + } + if (ax < 0x3e200000) { + if (two54 + x > 0.0 && ax < 0x3c900000) { + return x; + } + return x - x * x * 0.5; + } + if (hx > 0 || hx <= static_cast(0xbfd2bec4)) { + k = 0; + f = x; + hu = 1; + } + } + if (hx >= 0x7ff00000) { + return x + x; + } + if (k != 0) { + double u; + if (hx < 0x43400000) { + u = 1.0 + x; + hu = static_cast(std::bit_cast(u) >> 32); + k = (hu >> 20) - 1023; + c = k > 0 ? 1.0 - (u - x) : x - (u - 1.0); + c /= u; + } else { + u = x; + hu = static_cast(std::bit_cast(u) >> 32); + k = (hu >> 20) - 1023; + c = 0; + } + hu &= 0x000fffff; + const std::uint64_t ulow = std::bit_cast(u) & 0xffffffffull; + if (hu < 0x6a09e) { + u = std::bit_cast((static_cast(static_cast(hu | 0x3ff00000)) << 32) | ulow); + } else { + k += 1; + u = std::bit_cast((static_cast(static_cast(hu | 0x3fe00000)) << 32) | ulow); + hu = (0x00100000 - hu) >> 2; + } + f = u - 1.0; + } + const double hfsq = 0.5 * f * f; + if (hu == 0) { + if (f == 0.0) { + if (k == 0) { + return 0.0; + } + c += k * ln2_lo; + return k * ln2_hi + c; + } + const double R = hfsq * (1.0 - 0.66666666666666666 * f); + if (k == 0) { + return f - R; + } + return k * ln2_hi - ((R - (k * ln2_lo + c)) - f); + } + const double s = f / (2.0 + f); + const double z = s * s; + const double R = z * (Lp1 + z * (Lp2 + z * (Lp3 + z * (Lp4 + z * (Lp5 + z * (Lp6 + z * Lp7)))))); + if (k == 0) { + return f - (hfsq - s * (hfsq + R)); + } + return k * ln2_hi - ((hfsq - (s * (hfsq + R) + (k * ln2_lo + c))) - f); +} float APS5_VABI log1pf_nid_postfix(float x) { return std::log1p(x); } -double APS5_VABI expm1_nid_postfix(double x) { return std::expm1(x); } +static double Expm1(double x) { + const double tiny = 1.0e-300; + const double o_threshold = 7.09782712893383973096e+02; + const double ln2_hi = 6.93147180369123816490e-01; + const double ln2_lo = 1.90821492927058770002e-10; + const double invln2 = 1.44269504088896338700e+00; + const double Q1 = -3.33333333333331316428e-02; + const double Q2 = 1.58730158725481460165e-03; + const double Q3 = -7.93650757867487942473e-05; + const double Q4 = 4.00821782732936239552e-06; + const double Q5 = -2.01099218183624371326e-07; + static volatile double huge = 1.0e+300; + const std::uint64_t bits = std::bit_cast(x); + std::uint32_t hx = static_cast(bits >> 32); + const std::uint32_t xsb = hx & 0x80000000u; + hx &= 0x7fffffff; + if (hx >= 0x4043687a) { + if (hx >= 0x40862e42) { + if (hx >= 0x7ff00000) { + if (((hx & 0xfffff) | static_cast(bits)) != 0) return x + x; + return xsb == 0 ? x : -1.0; + } + if (x > o_threshold) return huge * huge; + } + if (xsb != 0 && x + tiny < 0.0) return tiny - 1.0; + } + double c = 0.0; + std::int32_t k; + if (hx > 0x3fd62e42) { + double hi; + double lo; + if (hx < 0x3ff0a2b2) { + if (xsb == 0) { + hi = x - ln2_hi; + lo = ln2_lo; + k = 1; + } else { + hi = x + ln2_hi; + lo = -ln2_lo; + k = -1; + } + } else { + k = static_cast(invln2 * x + (xsb == 0 ? 0.5 : -0.5)); + const double t = k; + hi = x - t * ln2_hi; + lo = t * ln2_lo; + } + x = hi - lo; + c = (hi - x) - lo; + } else if (hx < 0x3c900000) { + const double t = huge + x; + return x - (t - (huge + x)); + } else { + k = 0; + } + const double hfx = 0.5 * x; + const double hxs = x * hfx; + const double r1 = 1.0 + hxs * (Q1 + hxs * (Q2 + hxs * (Q3 + hxs * (Q4 + hxs * Q5)))); + double t = 3.0 - r1 * hfx; + double e = hxs * ((r1 - t) / (6.0 - x * t)); + if (k == 0) return x - (x * e - hxs); + const double twopk = std::bit_cast(static_cast(static_cast(0x3ff + k) << 20) << 32); + e = (x * (e - c) - c); + e -= hxs; + if (k == -1) return 0.5 * (x - e) - 0.5; + if (k == 1) { + if (x < -0.25) return -2.0 * (e - (x + 0.5)); + return 1.0 + 2.0 * (x - e); + } + double y; + if (k <= -2 || k > 56) { + y = 1.0 - (e - x); + if (k == 1024) { + y = y * 2.0 * 0x1p1023; + } else { + y = y * twopk; + } + return y - 1.0; + } + if (k < 20) { + t = std::bit_cast(static_cast(0x3ff00000u - (0x200000u >> k)) << 32); + y = t - (e - x); + y = y * twopk; + } else { + t = std::bit_cast(static_cast(static_cast((0x3ff - k) << 20)) << 32); + y = x - (e + t); + y += 1.0; + y = y * twopk; + } + return y; +} + +double APS5_VABI expm1_nid_postfix(double x) { + const double result = Expm1(x); + if (std::isinf(result) && std::isfinite(x)) { + errno = ERANGE; + } + return result; +} float APS5_VABI expm1f_nid_postfix(float x) { return std::expm1(x); } float APS5_VABI erff_nid_postfix(float x) { return std::erf(x); } double APS5_VABI erf_nid_postfix(double x) { return std::erf(x); } @@ -475,19 +729,311 @@ double APS5_VABI lgamma_nid_postfix(double x) { return std::lgamma(x); } -float APS5_VABI sinf_nid_postfix(float x) { return std::sin(x); } -float APS5_VABI cosf_nid_postfix(float x) { return std::cos(x); } +static const std::int32_t TrigfIpio2[] = { + 0xA2F983, 0x6E4E44, 0x1529FC, 0x2757D1, 0xF534DD, 0xC0DB62, 0x95993C, 0x439041, 0xFE5163, 0xABDEBB, 0xC561B7, 0x246E3A, + 0x424DD2, 0xE00649, 0x2EEA09, 0xD1921C, 0xFE1DEB, 0x1CB129, 0xA73EE8, 0x8235F5, 0x2EBB44, 0x84E99C, 0x7026B4, 0x5F7E41, + 0x3991D6, 0x398353, 0x39F49C, 0x845F8B, 0xBDF928, 0x3B1FF8, 0x97FFDE, 0x05980F, 0xEF2F11, 0x8B5A0A, 0x6D1F6D, 0x367ECF, + 0x27CB09, 0xB74F46, 0x3F669E, 0x5FEA2D, 0x7527BA, 0xC7EBE5, 0xF17B3D, 0x0739F7, 0x8A5292, 0xEA6BFB, 0x5FB11F, 0x8D5D08, + 0x560330, 0x46FC7B, 0x6BABF0, 0xCFBC20, 0x9AF436, 0x1DA9E3, 0x91615E, 0xE61B08, 0x659985, 0x5F14A0, 0x68408D, 0xFFD880, + 0x4D7327, 0x310606, 0x1556CA, 0x73A8C9, 0x60E27B, 0xC08C6B, +}; + +static const double TrigfPio2Parts[] = { + 1.57079625129699707031e+00, 7.54978941586159635335e-08, 5.39030252995776476554e-15, 3.28200341580791294123e-22, + 1.27065575308067607349e-29, 1.22933308981111328932e-36, 2.73370053816464559624e-44, 2.16741683877804819444e-51, +}; + +static int TrigfKernelRemPio2(const double* x, double* y, int e0) { + const double two24 = 1.67772160000000000000e+07, twon24 = 5.96046447753906250000e-08; + std::int32_t jz, jv, jp, jk, carry, n, iq[20], i, j, k, m, q0, ih; + double z, fw, f[20], fq[20], q[20]; + const std::int32_t jx = 0; + jk = 3; + jp = jk; + jv = (e0 - 3) / 24; + if (jv < 0) jv = 0; + q0 = e0 - 24 * (jv + 1); + j = jv - jx; + m = jx + jk; + for (i = 0; i <= m; i++, j++) f[i] = j < 0 ? 0.0 : static_cast(TrigfIpio2[j]); + for (i = 0; i <= jk; i++) { + for (j = 0, fw = 0.0; j <= jx; j++) fw += x[j] * f[jx + i - j]; + q[i] = fw; + } + jz = jk; +recompute: + for (i = 0, j = jz, z = q[jz]; j > 0; i++, j--) { + fw = static_cast(static_cast(twon24 * z)); + iq[i] = static_cast(z - two24 * fw); + z = q[j - 1] + fw; + } + z = std::scalbn(z, q0); + z -= 8.0 * std::floor(z * 0.125); + n = static_cast(z); + z -= static_cast(n); + ih = 0; + if (q0 > 0) { + i = iq[jz - 1] >> (24 - q0); + n += i; + iq[jz - 1] -= i << (24 - q0); + ih = iq[jz - 1] >> (23 - q0); + } else if (q0 == 0) { + ih = iq[jz - 1] >> 23; + } else if (z >= 0.5) { + ih = 2; + } + if (ih > 0) { + n += 1; + carry = 0; + for (i = 0; i < jz; i++) { + j = iq[i]; + if (carry == 0) { + if (j != 0) { + carry = 1; + iq[i] = 0x1000000 - j; + } + } else { + iq[i] = 0xffffff - j; + } + } + if (q0 > 0) { + switch (q0) { + case 1: iq[jz - 1] &= 0x7fffff; break; + case 2: iq[jz - 1] &= 0x3fffff; break; + } + } + if (ih == 2) { + z = 1.0 - z; + if (carry != 0) z -= std::scalbn(1.0, q0); + } + } + if (z == 0.0) { + j = 0; + for (i = jz - 1; i >= jk; i--) j |= iq[i]; + if (j == 0) { + for (k = 1; iq[jk - k] == 0; k++) { + } + for (i = jz + 1; i <= jz + k; i++) { + f[jx + i] = static_cast(TrigfIpio2[jv + i]); + for (j = 0, fw = 0.0; j <= jx; j++) fw += x[j] * f[jx + i - j]; + q[i] = fw; + } + jz += k; + goto recompute; + } + } + if (z == 0.0) { + jz -= 1; + q0 -= 24; + while (iq[jz] == 0) { + jz--; + q0 -= 24; + } + } else { + z = std::scalbn(z, -q0); + if (z >= two24) { + fw = static_cast(static_cast(twon24 * z)); + iq[jz] = static_cast(z - two24 * fw); + jz += 1; + q0 += 24; + iq[jz] = static_cast(fw); + } else { + iq[jz] = static_cast(z); + } + } + fw = std::scalbn(1.0, q0); + for (i = jz; i >= 0; i--) { + q[i] = fw * static_cast(iq[i]); + fw *= twon24; + } + for (i = jz; i >= 0; i--) { + for (fw = 0.0, k = 0; k <= jp && k <= jz - i; k++) fw += TrigfPio2Parts[k] * q[i + k]; + fq[jz - i] = fw; + } + fw = 0.0; + for (i = jz; i >= 0; i--) fw += fq[i]; + y[0] = ih == 0 ? fw : -fw; + return n & 7; +} + +static int TrigfRemPio2(float x, double* y) { + const double invpio2 = 6.36619772367581382433e-01, pio2_1 = 1.57079631090164184570e+00, pio2_1t = 1.58932547735281966916e-08; + const std::int32_t hx = std::bit_cast(x); + const std::int32_t ix = hx & 0x7fffffff; + if (ix < 0x4dc90fdb) { + volatile double rounded = x * invpio2 + 0x1.8p52; + double fn = rounded; + fn = fn - 0x1.8p52; + const std::int32_t n = static_cast(fn); + const double r = x - fn * pio2_1; + const double w = fn * pio2_1t; + *y = r - w; + return n; + } + if (ix >= 0x7f800000) { + *y = x - x; + return 0; + } + const std::int32_t e0 = (ix >> 23) - 150; + const float z = std::bit_cast(ix - static_cast(static_cast(e0) << 23)); + double tx[1] = {z}, ty[1]; + const int n = TrigfKernelRemPio2(tx, ty, e0); + if (hx < 0) { + *y = -ty[0]; + return -n; + } + *y = ty[0]; + return n; +} + +static float TrigfKernelSin(double x) { + const double S1 = -0x15555554cbac77.0p-55, S2 = 0x111110896efbb2.0p-59, S3 = -0x1a00f9e2cae774.0p-65, S4 = 0x16cd878c3b46a7.0p-71; + const double z = x * x, w = z * z, r = S3 + z * S4, s = z * x; + return static_cast((x + s * (S1 + z * S2)) + s * w * r); +} + +static float TrigfKernelCos(double x) { + const double C0 = -0x1ffffffd0c5e81.0p-54, C1 = 0x155553e1053a42.0p-57, C2 = -0x16c087e80f1e27.0p-62, C3 = 0x199342e0ee5069.0p-68; + const double z = x * x, w = z * z, r = C2 + z * C3; + return static_cast(((1.0 + z * C0) + w * C1) + (w * z) * r); +} + +float APS5_VABI sinf_nid_postfix(float x) { + const double s1pio2 = std::bit_cast(0x3FF921FB54442D18ull), s2pio2 = std::bit_cast(0x400921FB54442D18ull); + const double s3pio2 = std::bit_cast(0x4012D97C7F3321D2ull), s4pio2 = std::bit_cast(0x401921FB54442D18ull); + double y; + const std::int32_t hx = std::bit_cast(x); + const std::int32_t ix = hx & 0x7fffffff; + if (ix <= 0x3f490fda) { + if (ix < 0x39800000) + if (static_cast(x) == 0) return x; + return TrigfKernelSin(x); + } + if (ix <= 0x407b53d1) { + if (ix <= 0x4016cbe3) return hx > 0 ? TrigfKernelCos(x - s1pio2) : -TrigfKernelCos(x + s1pio2); + return TrigfKernelSin((hx > 0 ? s2pio2 : -s2pio2) - x); + } + if (ix <= 0x40e231d5) { + if (ix <= 0x40afeddf) return hx > 0 ? -TrigfKernelCos(x - s3pio2) : TrigfKernelCos(x + s3pio2); + return TrigfKernelSin(x + (hx > 0 ? -s4pio2 : s4pio2)); + } + if (ix >= 0x7f800000) { + if (ix == 0x7f800000) errno = EDOM; + return x - x; + } + const int n = TrigfRemPio2(x, &y); + switch (n & 3) { + case 0: return TrigfKernelSin(y); + case 1: return TrigfKernelCos(y); + case 2: return TrigfKernelSin(-y); + default: return -TrigfKernelCos(y); + } +} + +static const double TrigfPio2[5] = {0.0, std::bit_cast(0x3FF921FB54442D18ull), std::bit_cast(0x400921FB54442D18ull), + std::bit_cast(0x4012D97C7F3321D2ull), std::bit_cast(0x401921FB54442D18ull)}; + +float APS5_VABI cosf_nid_postfix(float x) { + double y; + const std::int32_t hx = std::bit_cast(x); + const std::int32_t ix = hx & 0x7fffffff; + if (ix <= 0x3f490fda) { + if (ix < 0x39800000) + if (static_cast(x) == 0) return 1.0f; + return TrigfKernelCos(x); + } + if (ix <= 0x407b53d1) { + if (ix > 0x4016cbe3) return -TrigfKernelCos(x + (hx > 0 ? -TrigfPio2[2] : TrigfPio2[2])); + return hx > 0 ? TrigfKernelSin(TrigfPio2[1] - x) : TrigfKernelSin(x + TrigfPio2[1]); + } + if (ix <= 0x40e231d5) { + if (ix > 0x40afeddf) return TrigfKernelCos(x + (hx > 0 ? -TrigfPio2[4] : TrigfPio2[4])); + return hx > 0 ? TrigfKernelSin(x - TrigfPio2[3]) : TrigfKernelSin(-TrigfPio2[3] - x); + } + if (ix >= 0x7f800000) { + if (ix == 0x7f800000) errno = EDOM; + return x - x; + } + const int n = TrigfRemPio2(x, &y); + switch (n & 3) { + case 0: return TrigfKernelCos(y); + case 1: return TrigfKernelSin(-y); + case 2: return -TrigfKernelCos(y); + default: return TrigfKernelSin(y); + } +} + +static float TrigfKernelTan(double x, int iy) { + static const double T[] = {0x15554d3418c99f.0p-54, 0x1112fd38999f72.0p-55, 0x1b54c91d865afe.0p-57, + 0x191df3908c33ce.0p-58, 0x185dadfcecf44e.0p-61, 0x1362b9bf971bcd.0p-59}; + const double z = x * x; + double r = T[4] + z * T[5]; + const double t = T[2] + z * T[3]; + const double w = z * z; + const double s = z * x; + const double u = T[0] + z * T[1]; + r = (x + s * u) + (s * w) * (t + w * r); + if (iy == 1) return static_cast(r); + return static_cast(-1.0 / r); +} + +float APS5_VABI tanf_nid_postfix(float x) { + double y; + const std::int32_t hx = std::bit_cast(x); + const std::int32_t ix = hx & 0x7fffffff; + if (ix <= 0x3f490fda) { + if (ix < 0x39800000) + if (static_cast(x) == 0) return x; + return TrigfKernelTan(x, 1); + } + if (ix <= 0x407b53d1) { + if (ix <= 0x4016cbe3) return TrigfKernelTan(x + (hx > 0 ? -TrigfPio2[1] : TrigfPio2[1]), -1); + return TrigfKernelTan(x + (hx > 0 ? -TrigfPio2[2] : TrigfPio2[2]), 1); + } + if (ix <= 0x40e231d5) { + if (ix <= 0x40afeddf) return TrigfKernelTan(x + (hx > 0 ? -TrigfPio2[3] : TrigfPio2[3]), -1); + return TrigfKernelTan(x + (hx > 0 ? -TrigfPio2[4] : TrigfPio2[4]), 1); + } + if (ix >= 0x7f800000) { + if (ix == 0x7f800000) errno = EDOM; + return x - x; + } + const int n = TrigfRemPio2(x, &y); + return TrigfKernelTan(y, 1 - ((n & 1) << 1)); +} void APS5_VABI sincosf_nid_postfix(float x, float* sinp, float* cosp) { - *sinp = std::sin(x); - *cosp = std::cos(x); + *sinp = sinf_nid_postfix(x); + *cosp = cosf_nid_postfix(x); } 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); } double APS5_VABI cosh_nid_postfix(double x) { return std::cosh(x); } -float APS5_VABI coshf_nid_postfix(float x) { return std::cosh(x); } +float APS5_VABI coshf_nid_postfix(float x) { + const std::uint32_t magnitude = std::bit_cast(x) & 0x7fffffffu; + if (magnitude >= 0x7f800000u) return x * x; + const float absolute = std::fabs(x); + if (magnitude < 0x3eb17218u) { + const float t = SinhfExpm1(absolute); + const float w = 1.0f + t; + if (magnitude < 0x39800000u) return 1.0f; + return 1.0f + (t * t) / (w + w); + } + if (magnitude < 0x41100000u) { + const float t = SinhfExp(absolute); + return 0.5f * t + 0.5f / t; + } + if (magnitude < 0x42b17217u) return 0.5f * SinhfExp(absolute); + if (magnitude <= 0x42b2d4fcu) { + const float w = SinhfExp(0.5f * absolute); + const float t = 0.5f * w; + return t * w; + } + volatile float huge = 1.0e30f; + return huge * huge; +} float APS5_VABI sinhf_nid_postfix(float x) { const std::uint32_t magnitude = std::bit_cast(x) & 0x7fffffffu; @@ -512,7 +1058,25 @@ float APS5_VABI sinhf_nid_postfix(float x) { double APS5_VABI asinh_nid_postfix(double x) { return std::asinh(x); } float APS5_VABI asinhf_nid_postfix(float x) { return std::asinh(x); } double APS5_VABI acosh_nid_postfix(double x) { return std::acosh(x); } -float APS5_VABI acoshf_nid_postfix(float x) { return std::acosh(x); } +float APS5_VABI acoshf_nid_postfix(float x) { + constexpr float ln2 = 6.9314718246e-01f; + const std::uint32_t bits = std::bit_cast(x); + const std::uint32_t magnitude = bits & 0x7fffffffu; + if ((bits & 0x80000000u) != 0 || magnitude < 0x3f800000u) { + _Feraise_nid_postfix(0x01); + errno = EDOM; + return (x - x) / (x - x); + } + if (magnitude >= 0x7f800000u) return x + x; + if (magnitude == 0x3f800000u) return 0.0f; + if (magnitude >= 0x4d800000u) return logf_nid_postfix(x) + ln2; + if (magnitude > 0x40000000u) { + const float square = x * x; + return logf_nid_postfix(2.0f * x + (-1.0f / (x + std::sqrt(square - 1.0f)))); + } + const float t = x - 1.0f; + return AtanhfLog1p(t + std::sqrt(2.0f * t + t * t)); +} double APS5_VABI atanh_nid_postfix(double x) { return std::atanh(x); } float APS5_VABI atanhf_nid_postfix(float x) { @@ -539,7 +1103,50 @@ void APS5_VABI sincos_nid_postfix(double x, double* sinp, double* cosp) { *cosp = std::cos(x); } -float APS5_VABI atanf_nid_postfix(float x) { return std::atan(x); } +float APS5_VABI atanf_nid_postfix(float x) { + static constexpr float atanhi[] = {static_cast(4.6364760399e-01), static_cast(7.8539812565e-01), static_cast(9.8279368877e-01), static_cast(1.5707962513e+00)}; + static constexpr float atanlo[] = {static_cast(5.0121582440e-09), static_cast(3.7748947079e-08), static_cast(3.4473217170e-08), static_cast(7.5497894159e-08)}; + static constexpr float aT[] = {static_cast(3.3333328366e-01), static_cast(-1.9999158382e-01), static_cast(1.4253635705e-01), static_cast(-1.0648017377e-01), static_cast(6.1687607318e-02)}; + const std::int32_t hx = std::bit_cast(x); + const std::int32_t ix = hx & 0x7fffffff; + if (ix >= 0x4c800000) { + if (ix > 0x7f800000) { + return x + x; + } + const volatile float lo = atanlo[3]; + return hx > 0 ? atanhi[3] + lo : -atanhi[3] - lo; + } + int id = -1; + if (ix < 0x3ee00000) { + if (ix < 0x39800000) { + return x; + } + } else { + x = std::fabs(x); + if (ix < 0x3f300000) { + id = 0; + x = (2.0f * x - 1.0f) / (2.0f + x); + } else if (ix < 0x3f980000) { + id = 1; + x = (x - 1.0f) / (x + 1.0f); + } else if (ix < 0x401c0000) { + id = 2; + x = (x - 1.5f) / (1.0f + 1.5f * x); + } else { + id = 3; + x = -1.0f / x; + } + } + const float z = x * x; + const float w = z * z; + const float s1 = z * (aT[0] + w * (aT[2] + w * aT[4])); + const float s2 = w * (aT[1] + w * aT[3]); + if (id < 0) { + return x - x * (s1 + s2); + } + const float result = atanhi[id] - ((x * (s1 + s2) - atanlo[id]) - x); + return hx < 0 ? -result : result; +} double APS5_VABI atan2_nid_postfix(double y, double x) { return std::atan2(y, x); } float APS5_VABI powf_nid_postfix(float base, float exp) { if (base == 1.0f) return 1.0f; @@ -571,8 +1178,62 @@ double APS5_VABI __powidf2_nid_postfix(double base, int exponent) { return reciprocal ? 1.0 / result : result; } float APS5_VABI expf_nid_postfix(float x) { return std::exp(x); } -float APS5_VABI exp2f_nid_postfix(float x) { return std::exp2(x); } -float APS5_VABI logf_nid_postfix(float x) { return std::log(x); } +float APS5_VABI logf_nid_postfix(float x) { + const float ln2_hi = static_cast(6.9313812256e-01); + const float ln2_lo = static_cast(9.0580006145e-06); + const float two25 = static_cast(3.355443200e+07); + const float Lg1 = 0xaaaaaa.0p-24f; + const float Lg2 = 0xccce13.0p-25f; + const float Lg3 = 0x91e9ee.0p-25f; + const float Lg4 = 0xf89e26.0p-26f; + static volatile float vzero = 0.0f; + std::int32_t k = 0; + std::int32_t ix = std::bit_cast(x); + if (ix < 0x00800000) { + if ((ix & 0x7fffffff) == 0) { + errno = ERANGE; + return -two25 / vzero; + } + if (ix < 0) { + errno = EDOM; + return (x - x) / 0.0f; + } + k -= 25; + x *= two25; + ix = std::bit_cast(x); + } + if (ix >= 0x7f800000) { + return x + x; + } + k += (ix >> 23) - 127; + ix &= 0x007fffff; + std::int32_t i = (ix + (0x95f64 << 3)) & 0x800000; + x = std::bit_cast(ix | (i ^ 0x3f800000)); + k += i >> 23; + const float f = x - 1.0f; + const float dk = static_cast(k); + if ((0x007fffff & (0x8000 + ix)) < 0xc000) { + if (f == 0.0f) { + return k == 0 ? 0.0f : dk * ln2_hi + dk * ln2_lo; + } + const float R = f * f * (0.5f - static_cast(0.33333333333333333) * f); + return k == 0 ? f - R : dk * ln2_hi - ((R - dk * ln2_lo) - f); + } + const float s = f / (2.0f + f); + const float z = s * s; + i = ix - (0x6147a << 3); + const float w = z * z; + const std::int32_t j = (0x6b851 << 3) - ix; + const float t1 = w * (Lg2 + w * Lg4); + const float t2 = z * (Lg1 + w * Lg3); + i |= j; + const float R = t2 + t1; + if (i > 0) { + const float hfsq = 0.5f * f * f; + return k == 0 ? f - (hfsq - s * (hfsq + R)) : dk * ln2_hi - ((hfsq - (s * (hfsq + R) + dk * ln2_lo)) - f); + } + return k == 0 ? f - s * (f - R) : dk * ln2_hi - ((s * (f - R) - dk * ln2_lo) - f); +} float APS5_VABI log2f_nid_postfix(float x) { return std::log2(x); } double APS5_VABI log10_nid_postfix(double x) { return std::log10(x); } float APS5_VABI ldexpf_nid_postfix(float x, int exp) { return std::ldexp(x, exp); } @@ -584,10 +1245,86 @@ double APS5_VABI fmod_nid_postfix(double x, double y) { } float APS5_VABI roundf_nid_postfix(float x) { return std::round(x); } double APS5_VABI round_nid_postfix(double x) { return std::round(x); } -float APS5_VABI cbrtf_nid_postfix(float x) { return std::cbrt(x); } +float APS5_VABI cbrtf_nid_postfix(float x) { + const std::uint32_t B1 = 709958130; + const std::uint32_t B2 = 642849266; + std::uint32_t hx = std::bit_cast(x); + const std::uint32_t sign = hx & 0x80000000u; + hx ^= sign; + if (hx >= 0x7f800000u) { + return x + x; + } + float t; + if (hx < 0x00800000u) { + if (hx == 0) { + return x; + } + t = 0x1p24f * x; + const std::uint32_t high = std::bit_cast(t); + t = std::bit_cast(sign | ((high & 0x7fffffffu) / 3 + B2)); + } else { + t = std::bit_cast(sign | (hx / 3 + B1)); + } + double T = t; + double r = T * T * T; + T = T * (static_cast(x) + x + r) / (x + r + r); + r = T * T * T; + T = T * (static_cast(x) + x + r) / (x + r + r); + return static_cast(T); +} float APS5_VABI remainderf_nid_postfix(float x, float y) { return std::remainder(x, y); } -float APS5_VABI nextafterf_nid_postfix(float x, float y) { return std::nextafter(x, y); } -double APS5_VABI nextafter_nid_postfix(double x, double y) { return std::nextafter(x, y); } +float APS5_VABI nextafterf_nid_postfix(float x, float y) { + const std::uint32_t ux = std::bit_cast(x); + const std::uint32_t uy = std::bit_cast(y); + if ((ux & 0x7fffffffu) > 0x7f800000u || (uy & 0x7fffffffu) > 0x7f800000u) { + volatile float sum = x + y; + (void)sum; + return std::bit_cast(((ux & 0x7fffffffu) > 0x7f800000u ? ux : uy) | 0x00400000u); + } + if (x == y) { + return y; + } + if ((ux & 0x7fffffffu) == 0) { + return std::bit_cast((uy & 0x80000000u) | 1u); + } + const bool towardZero = (x > 0.0f) == (x > y); + const std::uint32_t next = towardZero ? ux - 1 : ux + 1; + const std::uint32_t exponent = next & 0x7f800000u; + if (exponent == 0x7f800000u) { + errno = ERANGE; + return x + x; + } + if (exponent == 0) { + errno = ERANGE; + } + return std::bit_cast(next); +} +double APS5_VABI nextafter_nid_postfix(double x, double y) { + const std::uint64_t ux = std::bit_cast(x); + const std::uint64_t uy = std::bit_cast(y); + if ((ux & 0x7fffffffffffffffull) > 0x7ff0000000000000ull || (uy & 0x7fffffffffffffffull) > 0x7ff0000000000000ull) { + volatile double sum = x + y; + (void)sum; + return std::bit_cast(((ux & 0x7fffffffffffffffull) > 0x7ff0000000000000ull ? ux : uy) | 0x0008000000000000ull); + } + if (x == y) { + return y; + } + if ((ux & 0x7fffffffffffffffull) == 0) { + return std::bit_cast((uy & 0x8000000000000000ull) | 1u); + } + const bool towardZero = (x > 0.0) == (x > y); + const std::uint64_t next = towardZero ? ux - 1 : ux + 1; + const std::uint64_t exponent = next & 0x7ff0000000000000ull; + if (exponent == 0x7ff0000000000000ull) { + errno = ERANGE; + return x + x; + } + if (exponent == 0) { + errno = ERANGE; + } + return std::bit_cast(next); +} int APS5_VABI __isfinite_nid_postfix(double x) { return std::isfinite(x) ? 1 : 0; } int APS5_VABI __isnan_nid_postfix(double x) { return std::isnan(x) ? 1 : 0; } int APS5_VABI __isinf_nid_postfix(double x) { return std::isinf(x) ? 1 : 0; } @@ -603,7 +1340,31 @@ double APS5_VABI nan_nid_postfix(const char* tagp) { double APS5_VABI modf_nid_postfix(double x, double* integral) { return std::modf(x, integral); } float APS5_VABI modff_nid_postfix(float x, float* integral) { return std::modf(x, integral); } -double APS5_VABI tanh_nid_postfix(double x) { return std::tanh(x); } +double APS5_VABI tanh_nid_postfix(double x) { + static volatile double tiny = 1.0e-300; + const double huge = 1.0e300; + const std::int32_t jx = static_cast(std::bit_cast(x) >> 32); + const std::int32_t ix = jx & 0x7fffffff; + if (ix >= 0x7ff00000) { + return jx >= 0 ? 1.0 / x + 1.0 : 1.0 / x - 1.0; + } + double z; + if (ix < 0x40360000) { + if (ix < 0x3e300000 && huge + x > 1.0) { + return x; + } + if (ix >= 0x3ff00000) { + const double t = Expm1(2.0 * std::fabs(x)); + z = 1.0 - 2.0 / (t + 2.0); + } else { + const double t = Expm1(-2.0 * std::fabs(x)); + z = -t / (t + 2.0); + } + } else { + z = 1.0 - tiny; + } + return jx >= 0 ? z : -z; +} float APS5_VABI tanhf_nid_postfix(float x) { return std::tanh(x); } long double APS5_VABI atanl_nid_postfix(long double x) { return std::atan(x); } long double APS5_VABI sinl_nid_postfix(long double x) { return std::sin(x); } @@ -750,18 +1511,53 @@ double APS5_VABI _Sinh_nid_postfix(double x, double y) { return SonySinh(x, y); } +double APS5_VABI _Cosh_nid_postfix(double x, double y) { + if (!std::isfinite(x) || !std::isfinite(y)) return y * std::cosh(x); + if (y == 0.0) return std::copysign(0.0, y); + constexpr double Ln2High = 0x1.62e42fefa0000p-1; + constexpr double Ln2Low = 0x1.cf79abc9e3b3ap-40; + constexpr double InverseLn2 = 0x1.71547652b82fep+0; + const double magnitude = std::abs(x); + if (magnitude <= (std::numeric_limits::max_exponent - 1) * (Ln2High + Ln2Low)) + return y * std::cosh(x); + int scaleExponent; + const double scale = std::frexp(y, &scaleExponent); + constexpr int MaximumExponentSpan = std::numeric_limits::max_exponent - + std::numeric_limits::min_exponent + std::numeric_limits::digits + 4; + if (magnitude > MaximumExponentSpan * (Ln2High + Ln2Low)) + return std::scalbn(scale, INT_MAX); + const int exponent = static_cast(magnitude * InverseLn2); + const double reduced = std::fma(-exponent, Ln2Low, std::fma(-exponent, Ln2High, magnitude)); + return std::scalbn(scale * (0.5 * std::exp(reduced)), scaleExponent + exponent); +} + int APS5_VABI _Fltrounds_nid_postfix(void) { return SonyFltrounds(); } -struct GuestFloatingPointEnvironment { - std::uint32_t control; - std::uint32_t status; - std::uint32_t tag; - unsigned char other[16]; - std::uint32_t mxcsr; -}; -static_assert(sizeof(GuestFloatingPointEnvironment) == 32 && offsetof(GuestFloatingPointEnvironment, mxcsr) == 28); +static_assert(sizeof(GuestFloatingPointEnvironment) == 32 && alignof(GuestFloatingPointEnvironment) == 4 && offsetof(GuestFloatingPointEnvironment, mxcsr) == 28); + +int APS5_VABI feupdateenv_nid_postfix(const GuestFloatingPointEnvironment* environment) { + if (environment == nullptr) throw std::invalid_argument("feupdateenv: null environment"); + const auto saved = *environment; + alignas(16) std::array state; + asm volatile("fxsave %0" : "=m"(state)); + const auto supported = state[7] != 0 ? state[7] : 0xffbfu; + if ((saved.mxcsr & ~supported) != 0) throw std::invalid_argument("feupdateenv: unsupported MXCSR bits"); + std::uint16_t status; + asm volatile("fnstsw %0" : "=am"(status)); + const auto pending = (status | _mm_getcsr()) & 0x3f; + asm volatile("fldenv %0" : : "m"(saved) : "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"); + _mm_setcsr(saved.mxcsr); + GuestFloatingPointEnvironment raised; + asm volatile("fnstenv %0" : "=m"(raised)); + raised.status |= pending; + const auto mxcsr = _mm_getcsr() | pending; + asm volatile("fldenv %0" : : "m"(raised) : "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"); + _mm_setcsr(mxcsr); + asm volatile("fwait"); + return 0; +} int APS5_VABI fegetenv_nid_postfix(GuestFloatingPointEnvironment* environment) { if (environment == nullptr) throw std::invalid_argument("fegetenv: null environment"); @@ -770,6 +1566,15 @@ int APS5_VABI fegetenv_nid_postfix(GuestFloatingPointEnvironment* environment) { return 0; } +int APS5_VABI feholdexcept_nid_postfix(GuestFloatingPointEnvironment* environment) { + if (environment == nullptr) throw std::invalid_argument("feholdexcept: null environment"); + const auto csr = _mm_getcsr(); + asm volatile("fnstenv %0\n\tfnclex" : "=m"(*environment)); + environment->mxcsr = csr; + _mm_setcsr((csr & ~0x3fu) | 0x1f80u); + return 0; +} + union SonyDconst { unsigned short _Word[8]; float _Float; diff --git a/core/libs/prx/libc/src/MathExact.cpp b/core/libs/prx/libc/src/MathExact.cpp index 1217ca4a4c..bbfe5114ef 100644 --- a/core/libs/prx/libc/src/MathExact.cpp +++ b/core/libs/prx/libc/src/MathExact.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libc/src/Mspace.cpp b/core/libs/prx/libc/src/Mspace.cpp index 8830e5c75c..53e55f6199 100644 --- a/core/libs/prx/libc/src/Mspace.cpp +++ b/core/libs/prx/libc/src/Mspace.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/MallocManagedSize.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -13,8 +14,6 @@ #include #include -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { constexpr unsigned MspaceThreadUnsafe = 1; @@ -322,6 +321,7 @@ void* APS5_VABI sceLibcPafMspaceMemalign_nid_postfix(void* handle, std::size_t a void APS5_VABI sceLibcPafMspaceFree_nid_postfix(void* handle, void* pointer, const void* owner) { (void)owner; sceLibcMspaceFree_nid_postfix(handle, pointer); + } void* APS5_VABI sceLibcPafMspaceCalloc_nid_postfix(void* handle, std::size_t count, std::size_t size, const void* owner) { diff --git a/core/libs/prx/libc/src/Multibyte.cpp b/core/libs/prx/libc/src/Multibyte.cpp index e7dc400ac3..330701624b 100644 --- a/core/libs/prx/libc/src/Multibyte.cpp +++ b/core/libs/prx/libc/src/Multibyte.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/SonyCrt.hpp" #include @@ -53,6 +54,64 @@ bool DecodeUtf8Byte(unsigned char byte, std::uint64_t& value, std::int8_t& pendi return true; } +constexpr auto Invalid = static_cast(-1); +constexpr auto Incomplete = static_cast(-2); +constexpr std::size_t StateSize = 16; +constexpr std::size_t LocaleMaxLength = 3; + +std::size_t LocaleToWide(std::uint16_t* destination, const char* source, std::size_t count, unsigned char* state) { + if (!source) { + std::memset(state, 0, StateSize); + return 0; + } + if (!count) return Incomplete; + std::uint64_t value = 0; + std::memcpy(&value, state, sizeof(value)); + auto pending = static_cast(state[PendingOffset]); + for (std::size_t consumed = 0; consumed < count;) { + const auto byte = static_cast(source[consumed++]); + if ((pending == 0 && byte >= 0xf0) || !DecodeUtf8Byte(byte, value, pending)) { + errno = 86; + return Invalid; + } + if (pending == 0) { + if (destination) *destination = static_cast(value); + StorePending(state, 0); + return value == 0 ? 0 : consumed; + } + } + std::memcpy(state, &value, sizeof(value)); + StorePending(state, pending); + return Incomplete; +} + +std::size_t LocaleStringToWide(std::uint16_t* destination, const char** source, std::size_t capacity, unsigned char* state) { + const char* cursor = *source; + std::size_t converted = 0; + while (!destination || converted < capacity) { + std::uint16_t value = 0; + const std::size_t length = LocaleToWide(&value, cursor, LocaleMaxLength, state); + if (length == Invalid || length == Incomplete) { + if (destination) *source = cursor; + errno = 86; + return Invalid; + } + if (destination) destination[converted] = value; + if (length == 0) { + if (destination) *source = nullptr; + return converted; + } + cursor += length; + ++converted; + } + *source = cursor; + return converted; +} + +int ToInt(std::size_t result) { + return result == Invalid || result == Incomplete ? -1 : static_cast(result); +} + } extern "C" { @@ -63,51 +122,47 @@ int APS5_VABI ___mb_cur_max_nid_postfix() { return 1; } -int APS5_VABI _Getmbcurmax_nid_postfix() { return ___mb_cur_max_nid_postfix(); } +int APS5_VABI _Getmbcurmax_nid_postfix() { return static_cast(LocaleMaxLength); } -int APS5_VABI mbsinit_nid_postfix(const void*) { - return 1; +int APS5_VABI mbsinit_nid_postfix(const void* state) { + if (!state) return 1; + std::int16_t pending = 0; + std::memcpy(&pending, static_cast(state) + PendingOffset, sizeof(pending)); + return pending == 0; } -std::size_t APS5_VABI mbrtowc_nid_postfix(std::uint16_t* destination, const char* source, std::size_t count, void*) { - if (!source) return 0; - if (!count) return static_cast(-2); - const auto value = static_cast(*source); - if (destination) *destination = value; - return value != 0; +std::size_t APS5_VABI mbrtowc_nid_postfix(std::uint16_t* destination, const char* source, std::size_t count, void* state) { + thread_local unsigned char internal[StateSize]{}; + if (!source) { + destination = nullptr; + source = ""; + } + return LocaleToWide(destination, source, count, state ? static_cast(state) : internal); } std::size_t APS5_VABI mbrlen_nid_postfix(const char* source, std::size_t count, void* state) { - return mbrtowc_nid_postfix(nullptr, source, count, state); + thread_local unsigned char internal[StateSize]{}; + return LocaleToWide(nullptr, source, count, state ? static_cast(state) : internal); } int APS5_VABI mbtowc_nid_postfix(std::uint16_t* destination, const char* source, std::size_t count) { - const auto result = mbrtowc_nid_postfix(destination, source, count, nullptr); - if (result == static_cast(-2)) { - errno = 86; - return -1; - } - return static_cast(result); + thread_local unsigned char internal[StateSize]{}; + return ToInt(LocaleToWide(destination, source, count < LocaleMaxLength ? count : LocaleMaxLength, internal)); } std::size_t APS5_VABI wcrtomb_nid_postfix(char* destination, std::uint16_t value, void*) { if (!destination) return 1; - if (value > 255) { - errno = 86; - return static_cast(-1); - } - *destination = static_cast(value); - return 1; + return EncodeUtf8(destination, value); } int APS5_VABI mblen_nid_postfix(const char* source, std::size_t count) { - return mbtowc_nid_postfix(nullptr, source, count); + unsigned char state[StateSize]{}; + return ToInt(LocaleToWide(nullptr, source, count, state)); } int APS5_VABI wctomb_nid_postfix(char* destination, std::uint16_t value) { if (!destination) return 0; - const auto result = wcrtomb_nid_postfix(destination, value, nullptr); - return result == static_cast(-1) ? -1 : static_cast(result); + return static_cast(EncodeUtf8(destination, value)); } std::size_t APS5_VABI c32rtomb_nid_postfix(char* destination, std::uint32_t value, void*) { @@ -212,29 +267,30 @@ std::size_t APS5_VABI mbrtoc16_nid_postfix(std::uint16_t* destination, const cha return static_cast(-2); } -std::size_t APS5_VABI wcsrtombs_nid_postfix(char* destination, const std::uint16_t** source, std::size_t capacity, void* state) { +std::size_t APS5_VABI wcsrtombs_nid_postfix(char* destination, const std::uint16_t** source, std::size_t capacity, void*) { const auto* wide = *source; - std::size_t converted = 0; - while (!destination || converted < capacity) { - char byte; - if (wcrtomb_nid_postfix(&byte, wide[converted], state) == static_cast(-1)) { - if (destination) *source = wide + converted; - return static_cast(-1); + std::size_t written = 0; + for (;; ++wide) { + char bytes[LocaleMaxLength]; + const std::size_t length = EncodeUtf8(bytes, *wide); + if (destination) { + if (length > capacity - written) { + *source = wide; + return written; + } + std::memcpy(destination + written, bytes, length); } - if (destination) destination[converted] = byte; - if (byte == '\0') { + if (*wide == 0) { if (destination) *source = nullptr; - return converted; + return written; } - ++converted; + written += length; } - *source = wide + converted; - return converted; } -int APS5_VABI wcsrtombs_s_nid_postfix( +static int WideToLocaleChecked( std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t** source, std::size_t limit, - void* state + void* state, bool stopAtLimit ) { constexpr int GuestEinval = 22; constexpr int GuestErange = 34; @@ -275,6 +331,7 @@ int APS5_VABI wcsrtombs_s_nid_postfix( *result = Failed; return GuestEilseq; } + if (length > limit && stopAtLimit) break; if (length > limit || length > capacity) { *source = wide; destination[0] = '\0'; @@ -304,31 +361,30 @@ int APS5_VABI wcsrtombs_s_nid_postfix( return 0; } +int APS5_VABI wcsrtombs_s_nid_postfix( + std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t** source, std::size_t limit, + void* state +) { + return WideToLocaleChecked(result, destination, capacity, source, limit, state, false); +} + int APS5_VABI wcstombs_s_nid_postfix( std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t* source, std::size_t limit ) { std::array state{}; - return wcsrtombs_s_nid_postfix(result, destination, capacity, &source, limit, state.data()); + return WideToLocaleChecked(result, destination, capacity, &source, limit, state.data(), limit < capacity); } -std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const char** source, std::size_t capacity, void*) { - if (!destination) return std::strlen(*source); - std::size_t converted = 0; - while (converted < capacity) { - const auto value = static_cast((*source)[converted]); - destination[converted] = value; - if (value == 0) { - *source = nullptr; - return converted; - } - ++converted; - } - *source += converted; - return converted; +std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const char** source, std::size_t capacity, void* state) { + thread_local unsigned char internal[StateSize]{}; + if (destination) *destination = 0; + return LocaleStringToWide(destination, source, capacity, state ? static_cast(state) : internal); } std::size_t APS5_VABI mbstowcs_nid_postfix(std::uint16_t* dest, const char* src, std::size_t size) { - return SonyMbstowcs(dest, src, size); + if (!src) return 0; + unsigned char state[StateSize]{}; + return LocaleStringToWide(dest, &src, size, state); } } diff --git a/core/libs/prx/libc/src/Process.cpp b/core/libs/prx/libc/src/Process.cpp index 9d2f6909b0..8d7c88c0d9 100644 --- a/core/libs/prx/libc/src/Process.cpp +++ b/core/libs/prx/libc/src/Process.cpp @@ -1,9 +1,12 @@ +#include "prx/libc/include/Api.hpp" #include +#include #include #include #include #include #include +#include #include #include #include @@ -20,8 +23,6 @@ #include "prx/libc/include/General.hpp" #include "SceTypes.hpp" -extern "C" int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dsoHandle); - namespace { std::mutex shutdownMutex; @@ -34,7 +35,7 @@ std::exception_ptr shutdownFailure; std::stop_source shutdownSource; std::atomic exitRequested{false}; std::atomic exitStarted{false}; -using GuestExitCallback = void (APS5_VABI*)(); + thread_local bool runningExit = false; void APS5_VABI RunGuestExitCallback(void* callback) { @@ -271,6 +272,7 @@ int APS5_VABI at_quick_exit_nid_postfix(GuestExitCallback func) { } for (auto it = callbacks.rbegin(); it != callbacks.rend(); ++it) (*it)(); _Exit_nid_postfix(status); + } int APS5_VABI daemon_nid_postfix(int noChdir, int noClose) { @@ -280,4 +282,127 @@ int APS5_VABI daemon_nid_postfix(int noChdir, int noClose) { return -1; } +#ifdef _WIN32 +namespace { +struct ChildProcess { + std::int32_t processId; + std::int64_t processHandle; +}; + +std::mutex childProcessMutex; +std::vector childProcesses; + +int ChildProcessWaitStatus(DWORD code) { + switch (code) { + case 0xC0000005u: + case 0xC00000FDu: return 11; + case 0xC000001Du: + case 0xC0000096u: return 4; + case 0x80000003u: return 5; + case 0xC0000094u: + case 0xC0000095u: return 8; + case 0xC0000409u: + case 0xC0000374u: return 6; + default: return static_cast(code & 0xffu) << 8; + } +} +} + +extern "C" void RegisterChildProcess_nid_no_patch(std::int32_t processId, std::int64_t processHandle) { + std::lock_guard lock(childProcessMutex); + childProcesses.push_back({processId, processHandle}); +} + +extern "C" std::int64_t WaitChildProcess_nid_no_patch(std::int32_t processId, unsigned options, int* status) { + const bool noWait = (options & 1u) != 0; + const auto matches = [&](const ChildProcess& child) { return processId <= 0 || child.processId == processId; }; + { + std::lock_guard lock(childProcessMutex); + if (std::none_of(childProcesses.begin(), childProcesses.end(), matches)) return -2; + } + int encoded = 0; + while (true) { + std::int32_t reaped = 0; + { + std::lock_guard lock(childProcessMutex); + for (std::size_t index = 0; index < childProcesses.size(); ++index) { + auto& child = childProcesses[index]; + if (!matches(child)) continue; + const HANDLE handle = reinterpret_cast(child.processHandle); + if (WaitForSingleObject(handle, 0) != WAIT_OBJECT_0) continue; + DWORD code = 0; + if (!GetExitCodeProcess(handle, &code)) { + *__error_nid_postfix() = 14; + return -1; + } + encoded = ChildProcessWaitStatus(code); + CloseHandle(handle); + reaped = child.processId; + childProcesses.erase(childProcesses.begin() + static_cast(index)); + break; + } + } + if (reaped != 0) { + if (status != nullptr) *status = encoded; + return reaped; + } + std::vector handles; + { + const std::lock_guard lock(childProcessMutex); + for (const auto& child : childProcesses) { + if (!matches(child)) continue; + handles.push_back(reinterpret_cast(child.processHandle)); + } + } + if (handles.empty()) return -2; + if (noWait) return 0; + constexpr std::size_t groupSize = MAXIMUM_WAIT_OBJECTS; + bool timedOut = true; + for (std::size_t start = 0; start < handles.size() && timedOut; start += groupSize) { + const std::size_t count = std::min(handles.size() - start, groupSize); + timedOut = WaitForMultipleObjects(static_cast(count), handles.data() + start, FALSE, 250) == WAIT_TIMEOUT; + } + } +} +#endif + +} + +namespace { +constexpr int SaRestart = 0x2; +std::atomic registeredSigaction{nullptr}; +std::atomic registeredInterruptSet{nullptr}; + +struct InterruptSigaction { + std::uintptr_t handler; + int flags; + std::uint32_t mask[4]; +}; +static_assert(sizeof(InterruptSigaction) == 32 && offsetof(InterruptSigaction, flags) == 8 && offsetof(InterruptSigaction, mask) == 12); +} + +extern "C" { + +void RegisterSigaction_nid_no_patch(SigactionFn_nid_no_patch fn, std::uint32_t* interruptSet) { + registeredInterruptSet.store(interruptSet, std::memory_order_release); + registeredSigaction.store(fn, std::memory_order_release); +} + +int APS5_VABI siginterrupt_nid_postfix(int guest, int flag) { + const auto sigaction = registeredSigaction.load(std::memory_order_acquire); + auto* const interruptSet = registeredInterruptSet.load(std::memory_order_acquire); + if (sigaction == nullptr || interruptSet == nullptr) throw std::runtime_error("siginterrupt: libkernel has not registered sigaction"); + InterruptSigaction action{}; + if (sigaction(guest, nullptr, &action) != 0) return -1; + const auto index = static_cast(guest - 1); + if (flag) { + interruptSet[index >> 5] |= 1u << (index & 31); + action.flags &= ~SaRestart; + } else { + interruptSet[index >> 5] &= ~(1u << (index & 31)); + action.flags |= SaRestart; + } + return sigaction(guest, &action, nullptr); +} + } diff --git a/core/libs/prx/libc/src/Random.cpp b/core/libs/prx/libc/src/Random.cpp index 06cf1d789d..67bd066466 100644 --- a/core/libs/prx/libc/src/Random.cpp +++ b/core/libs/prx/libc/src/Random.cpp @@ -1,4 +1,6 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/HostEntropy.hpp" #include #include #include @@ -17,15 +19,7 @@ constexpr std::array MinimumStateBytes {8, 32, 64, 128, constexpr std::array Degrees {0, 7, 15, 31, 63}; constexpr std::array Separations {0, 3, 1, 3, 1}; -std::uint32_t g_defaultTable[] { - 3, - 0x2cf41758, 0x27bb3711, 0x4916d4d1, 0x7b02f59f, 0x9b8e28eb, 0xc0e80269, - 0x696f5c16, 0x878f1ff5, 0x52d9c07f, 0x916a06cd, 0xb50b3a20, 0x2776970a, - 0xee4eb2a6, 0xe94640ec, 0xb1d65612, 0x9d1ed968, 0x1043f6b7, 0xa3432a76, - 0x17eacbb9, 0x3c09e2eb, 0x04f8c2b3, 0x708a1f57, 0xee341814, 0x95d0e4d2, - 0xb06f216c, 0x8bd2e72e, 0x8f7c38d7, 0xcfc6a8fc, 0x02a59495, 0xa20d2a69, - 0xe29d12d1 -}; +std::uint32_t g_defaultTable[32]{}; struct AdditiveFeedbackState { char* table; @@ -50,12 +44,13 @@ void StoreWord(char* table, int index, std::uint32_t value) { } std::uint32_t GoodRand(std::uint32_t context) { - std::int32_t x = static_cast(context % 0x7ffffffeu) + 1; - const std::int32_t high = x / 127773; - const std::int32_t low = x % 127773; + std::int64_t x = static_cast(context); + if (x == 0) x = 123459876; + const std::int64_t high = x / 127773; + const std::int64_t low = x % 127773; x = 16807 * low - 2836 * high; if (x < 0) x += 0x7fffffff; - return static_cast(x - 1); + return static_cast(x); } std::uint32_t NextLocked() { @@ -95,6 +90,14 @@ void SaveTypeWordLocked() { StoreWord(state.table, 0, static_cast(word)); } +bool SeedDefaultState() { + SeedLocked(1); + SaveTypeWordLocked(); + return true; +} + +[[maybe_unused]] const bool g_defaultStateSeeded = SeedDefaultState(); + std::uint16_t g_rand48Seed[3]{0x330e, 0xabcd, 0x1234}; constexpr std::uint64_t DefaultRand48Multiplier = 0x5deece66dull; constexpr std::uint16_t DefaultRand48Addend = 0xb; @@ -118,7 +121,7 @@ std::uint64_t NextRand48(std::uint16_t* seed, const char* function) { extern "C" { double APS5_VABI erand48_nid_postfix(std::uint16_t* seed) { - return std::ldexp(static_cast(NextRand48(seed, __func__)), -48); + return std::ldexp(static_cast(NextRand48(seed, __func__) >> 16), -32); } std::int64_t APS5_VABI nrand48_nid_postfix(std::uint16_t* seed) { @@ -131,7 +134,7 @@ std::int64_t APS5_VABI jrand48_nid_postfix(std::uint16_t* seed) { double APS5_VABI drand48_nid_postfix() { std::lock_guard lock(g_randomLock); - return std::ldexp(static_cast(NextRand48(g_rand48Seed, __func__)), -48); + return std::ldexp(static_cast(NextRand48(g_rand48Seed, __func__) >> 16), -32); } std::int64_t APS5_VABI lrand48_nid_postfix() { @@ -180,6 +183,16 @@ void APS5_VABI srandom_nid_postfix(std::uint64_t seed) { SeedLocked(static_cast(seed)); } +void APS5_VABI srandomdev_nid_postfix() { + std::lock_guard lock(g_randomLock); + const auto words = g_random.type == LinearCongruentialType ? 1 : g_random.degree; + HostEntropy_nid_no_patch(g_random.table + sizeof(std::uint32_t), static_cast(words) * sizeof(std::uint32_t)); + if (g_random.type != LinearCongruentialType) { + g_random.front = g_random.separation; + g_random.rear = 0; + } +} + char* APS5_VABI initstate_nid_postfix(std::uint64_t seed, char* state, std::int64_t size) { if (size < MinimumStateBytes[0]) return nullptr; if (state == nullptr) throw std::invalid_argument("initstate: null state"); diff --git a/core/libs/prx/libc/src/RealPath.cpp b/core/libs/prx/libc/src/RealPath.cpp index 10a0193b94..92c58204d9 100644 --- a/core/libs/prx/libc/src/RealPath.cpp +++ b/core/libs/prx/libc/src/RealPath.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -10,8 +11,6 @@ #include #include -extern "C" char* APS5_VABI getcwd_nid_postfix(char* buffer, std::size_t size); - namespace { constexpr std::size_t PathMax = 1024; constexpr std::string_view Separators = "/\\"; diff --git a/core/libs/prx/libc/src/Rename.cpp b/core/libs/prx/libc/src/Rename.cpp index 973c142ff6..cdad0eff61 100644 --- a/core/libs/prx/libc/src/Rename.cpp +++ b/core/libs/prx/libc/src/Rename.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/RenameDot.hpp" #include "prx/libc/include/General.hpp" @@ -41,6 +42,16 @@ std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, c return error; } +#ifdef _WIN32 +static bool BelowSymbolicLinkLoop(const std::filesystem::path& directory) { + const auto handle = CreateFileW(directory.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) return GetLastError() == ERROR_CANT_RESOLVE_FILENAME; + CloseHandle(handle); + return false; +} +#endif + static int RenamePath_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination) { constexpr int GuestEnoent = 2; constexpr int GuestEnotdir = 20; @@ -71,6 +82,9 @@ static int RenamePath_nid_no_patch(const std::filesystem::path& source, const st error.clear(); sameFile = fs::equivalent(source, destination, error); if (error == std::errc::no_such_file_or_directory) error.clear(); +#ifdef __APPLE__ + if (error == std::errc::not_supported) error.clear(); +#endif if (error) return FilesystemError(error); if (!sameFile && sourceIsDirectory && !destinationIsDirectory) return GuestEnotdir; if (!sameFile && !sourceIsDirectory && destinationIsDirectory) return GuestEisdir; @@ -103,10 +117,17 @@ static int SourceLookupError_nid_no_patch(const std::filesystem::path& source) { } extern "C" int RenameGuestPath_nid_no_patch(const char* from, const char* to) { - if (!from) { errno = 14; return -1; } - if (!*from) { errno = 2; return -1; } + std::string fromCopy; + if (const int error = CopyGuestPath_nid_no_patch(from, fromCopy)) { errno = error; return -1; } + from = fromCopy.c_str(); + if (fromCopy.empty()) { errno = 2; return -1; } if (const int dot = RenameDotError_nid_no_patch(from)) { errno = dot; return -1; } - const int destinationError = !to ? 14 : !*to ? 2 : RenameDotError_nid_no_patch(to); + std::string toCopy; + int destinationError = CopyGuestPath_nid_no_patch(to, toCopy); + if (destinationError == 0) { + to = toCopy.c_str(); + destinationError = toCopy.empty() ? 2 : RenameDotError_nid_no_patch(to); + } try { const auto source = ResolvePath_nid_no_patch(from); if (destinationError) { @@ -120,6 +141,13 @@ extern "C" int RenameGuestPath_nid_no_patch(const char* from, const char* to) { std::error_code status; if (HasNonDirectoryPrefix_nid_no_patch(source) || (std::filesystem::exists(source, status) && HasNonDirectoryPrefix_nid_no_patch(destination))) error = 20; } +#ifdef _WIN32 + constexpr int GuestEloop = 62; + if (error == 2 || error == 20) { + if (BelowSymbolicLinkLoop(source.parent_path())) error = GuestEloop; + else if (SourceLookupError_nid_no_patch(source) == 0 && BelowSymbolicLinkLoop(destination.parent_path())) error = GuestEloop; + } +#endif if (error) { errno = error; return -1; } RecordWrittenPath_nid_no_patch(source); RecordWrittenPath_nid_no_patch(destination); diff --git a/core/libs/prx/libc/src/RuneLocale.cpp b/core/libs/prx/libc/src/RuneLocale.cpp index fa25f0412c..f038c5cd42 100644 --- a/core/libs/prx/libc/src/RuneLocale.cpp +++ b/core/libs/prx/libc/src/RuneLocale.cpp @@ -1,4 +1,6 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -108,6 +110,7 @@ int APS5_VABI iswpunct_nid_postfix(std::uint32_t c) { return GuestRune::Is(c, Gu int APS5_VABI iswspace_nid_postfix(std::uint32_t c) { return GuestRune::Is(c, GuestRune::Space); } int APS5_VABI iswupper_nid_postfix(std::uint32_t c) { return GuestRune::Is(c, GuestRune::Upper); } int APS5_VABI iswxdigit_nid_postfix(std::uint32_t c) { return GuestRune::Is(c, GuestRune::Hex); } +int APS5_VABI iswascii_nid_postfix(std::uint32_t c) { return (c & ~0x7Fu) == 0; } int APS5_VABI iswctype_nid_postfix(std::uint32_t c, std::uint64_t mask) { return GuestRune::Is(c, mask); } int APS5_VABI _Iswctype_nid_postfix(std::uint32_t c, std::uint64_t type) { @@ -147,17 +150,18 @@ std::uint32_t APS5_VABI towupper_nid_postfix(std::uint32_t c) { } std::uint32_t APS5_VABI btowc_nid_postfix(int c) { - return c == -1 ? GuestRune::Weof : static_cast(c); + const auto byte = static_cast(c); + if (c == -1) return GuestRune::Weof; + if (byte < 0x80) return byte; + if (byte < 0xc0 || byte >= 0xf0) errno = 86; + return GuestRune::Weof; } int APS5_VABI wctob_nid_postfix(std::uint32_t c) { - return c < 256 ? static_cast(c) : -1; + const auto unit = static_cast(c); + return unit < 0x80 ? static_cast(unit) : -1; } -std::uint32_t APS5_VABI _Towctrans_nid_postfix(std::uint32_t c, std::uint64_t desc); -const short* APS5_VABI _Getptolower_nid_postfix(); -const short* APS5_VABI _Getptoupper_nid_postfix(); - std::uint64_t APS5_VABI wctrans_nid_postfix(const char* property) { if (std::strcmp(property, "tolower") == 0) return 1; if (std::strcmp(property, "toupper") == 0) return 2; diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index f7204ea7f1..dbaf710627 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -95,6 +96,15 @@ void APS5_VABI cxa_finalize_nid_postfix(void* dsoHandle) { CxaFinalize_nid_no_patch(dsoHandle); } +void APS5_VABI _Vacopy_nid_postfix(unsigned char* destination, const unsigned char* source) { + std::uint64_t overflowArea; + std::memcpy(&overflowArea, source + 16, sizeof(overflowArea)); + std::memcpy(destination + 16, &overflowArea, sizeof(overflowArea)); + unsigned char offsets[16]; + std::memcpy(offsets, source, sizeof(offsets)); + std::memcpy(destination, offsets, sizeof(offsets)); +} + unsigned int APS5_VABI _Atomic_fetch_add_4_nid_postfix(volatile unsigned int* target, unsigned int value, int memoryOrder) { (void)memoryOrder; return GccAtomicFetchAdd(target, value); @@ -156,10 +166,12 @@ void APS5_VABI _Unlockfilelock_nid_postfix(FileStream* stream) { void APS5_VABI flockfile_nid_postfix(FileStream* stream) { _Lockfilelock_nid_postfix(stream); + } void APS5_VABI funlockfile_nid_postfix(FileStream* stream) { _Unlockfilelock_nid_postfix(stream); + } int APS5_VABI ftrylockfile_nid_postfix(FileStream* stream) { diff --git a/core/libs/prx/libc/src/ScanningWide.cpp b/core/libs/prx/libc/src/ScanningWide.cpp index 0b95de6e78..0390ecd6cb 100644 --- a/core/libs/prx/libc/src/ScanningWide.cpp +++ b/core/libs/prx/libc/src/ScanningWide.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -22,13 +23,6 @@ #define APS5_VA_END() va_end(args) #endif -extern "C" int APS5_VABI iswspace_nid_postfix(std::uint32_t c); -extern "C" std::size_t APS5_VABI wcrtomb_nid_postfix(char* destination, std::uint16_t value, void* state); -extern "C" std::int32_t APS5_VABI fgetwc_nid_postfix(FileStream* stream); -extern "C" int APS5_VABI ungetc_nid_postfix(int value, FileStream* stream); -extern "C" void APS5_VABI flockfile_nid_postfix(FileStream* stream); -extern "C" void APS5_VABI funlockfile_nid_postfix(FileStream* stream); - namespace { enum class Fit { Invalid, Prefix, Valid }; @@ -127,8 +121,9 @@ Fit FloatFit(std::string_view text) { if (digits == 0) return Fit::Invalid; if (Lower(text[index]) != (hex ? 'p' : 'e')) return Fit::Invalid; ++index; - if (index < text.size() && (text[index] == '+' || text[index] == '-')) ++index; - if (index == text.size()) return Fit::Prefix; + if (index == text.size()) return Fit::Valid; + if (text[index] == '+' || text[index] == '-') ++index; + if (index == text.size()) return Fit::Valid; for (; index < text.size(); ++index) { if (!IsDecimal(text[index])) return Fit::Invalid; } @@ -256,7 +251,7 @@ private: } Outcome Literal(char16_t expected) { - if (input.Peek() < 0) return Outcome::Input; + if (input.Peek() < 0) return Outcome::Matching; if (input.Peek() != expected) return Outcome::Matching; input.Advance(); return Outcome::Done; @@ -303,6 +298,7 @@ private: if (*format == u'%') { ++format; SkipSpace(); + if (input.Peek() < 0) return Outcome::Input; return Literal(u'%'); } const Directive directive = Parse(); @@ -319,6 +315,8 @@ private: case u's': case u'c': case u'[': return Text(directive); + case u'S': + case u'C': return Outcome::Matching; case u'n': return Count(directive); default: Fail("unsupported conversion " + std::to_string(directive.conversion)); } diff --git a/core/libs/prx/libc/src/StandardStreams.cpp b/core/libs/prx/libc/src/StandardStreams.cpp index 03cdaed4cd..b40f822753 100644 --- a/core/libs/prx/libc/src/StandardStreams.cpp +++ b/core/libs/prx/libc/src/StandardStreams.cpp @@ -1,18 +1,71 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #ifdef _WIN32 +#include #include +#include #else #include #include #endif +#ifdef _WIN32 +extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); +#endif + namespace { -#ifndef _WIN32 +#ifdef _WIN32 +constexpr std::intptr_t NoStreamHandle = -2; + +std::intptr_t StandardHandle(int descriptor) { + const auto previous = _set_thread_local_invalid_parameter_handler([](const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {}); + const std::intptr_t handle = ::_get_osfhandle(descriptor); + _set_thread_local_invalid_parameter_handler(previous); + return handle; +} + +} + +extern "C" void PrepareStandardStreams_nid_no_patch() { + static const bool ready = [] { + for (int descriptor = 0; descriptor <= 2; ++descriptor) { + const std::intptr_t handle = StandardHandle(descriptor); + if (handle != -1 && handle != NoStreamHandle) continue; + auto* const stream = __acrt_iob_func(descriptor); + const int existing = ::_fileno(stream); + if (existing >= 0) { + const std::intptr_t existingHandle = StandardHandle(existing); + if (existingHandle != -1 && existingHandle != NoStreamHandle) { + if (::_dup2(existing, descriptor) != 0) + throw std::runtime_error("cannot restore a standard descriptor from its stream"); + continue; + } + } + const int opened = ::_open("NUL", (descriptor == 0 ? _O_RDONLY : _O_WRONLY) | _O_BINARY); + if (opened < 0) throw std::runtime_error("cannot open NUL for a missing standard descriptor"); + if (opened > descriptor) { + const int moved = ::_dup2(opened, descriptor); + ::_close(opened); + if (moved != 0) throw std::runtime_error("cannot restore a missing standard descriptor"); + } + if (::_fileno(stream) != descriptor && ::_close(descriptor) != 0) + throw std::runtime_error("cannot release a restored standard descriptor"); + if (::_wfreopen(L"NUL", descriptor == 0 ? L"rb" : L"wb", stream) != stream || ::_fileno(stream) != descriptor) + throw std::runtime_error("cannot attach a standard stream to its NUL descriptor"); + } + return true; + }(); + static_cast(ready); +} + +namespace { +#else const bool standardDescriptorsOpen = [] { for (int descriptor = 0; descriptor <= 2; ++descriptor) { if (::fcntl(descriptor, F_GETFD) != -1 || errno != EBADF) continue; @@ -28,6 +81,7 @@ const bool standardDescriptorsOpen = [] { FileStream input{stdin}; } extern "C" { + FileStream* __stdinp_nid_postfix = &input; FileStream* __stdoutp_nid_postfix = &_Stdout_nid_postfix; FileStream* __stderrp_nid_postfix = &_Stderr_nid_postfix; @@ -39,6 +93,7 @@ int APS5_VABI fgetc_nid_postfix(FileStream* stream) { if (!stream->SwitchToReading()) return -1; const int result = std::fgetc(native); stream->SyncStatus(); + stream->KeepDirectoryReadError(result == -1); return result; } std::int32_t APS5_VABI fgetwc_nid_postfix(FileStream* stream) { @@ -69,8 +124,8 @@ std::int32_t APS5_VABI fgetwc_nid_postfix(FileStream* stream) { return static_cast(code); } - std::int32_t APS5_VABI ungetwc_nid_postfix(std::int32_t value, FileStream* stream) { + if (stream && !stream->IsOpen()) { errno = 9; return -1; } if (value == -1) return -1; if (value < 0 || value > 255) { errno = 86; @@ -91,13 +146,39 @@ int APS5_VABI getc_unlocked_nid_postfix(FileStream* stream) { #ifdef _WIN32 const int result = _fgetc_nolock(native); #else - const int result = ::getc_unlocked(native); + const int result = getc_unlocked(native); #endif stream->SyncUnlockedStatus(); + stream->KeepDirectoryReadError(result == -1); return result; } int APS5_VABI __srget_nid_postfix(FileStream* stream) { return fgetc_nid_postfix(stream); } int APS5_VABI getchar_nid_postfix() { return fgetc_nid_postfix(__stdinp_nid_postfix); } +char* APS5_VABI gets_nid_postfix(char* buffer) { + if (buffer == nullptr) throw std::invalid_argument("gets: null buffer"); + auto* stream = __stdinp_nid_postfix; + if (stream && !stream->IsOpen()) { errno = 9; return nullptr; } + flockfile_nid_postfix(stream); + + struct Unlock { + FileStream* stream; + ~Unlock() { funlockfile_nid_postfix(stream); } + } unlock{stream}; + char* cursor = buffer; + for (;;) { + const int value = fgetc_nid_postfix(stream); + if (value == EOF) { + if (cursor == buffer) return nullptr; + *cursor = 0; + return buffer; + } + if (value == '\n') { + *cursor = 0; + return buffer; + } + *cursor++ = static_cast(value); + } +} std::int32_t APS5_VABI getwc_nid_postfix(FileStream* stream) { return fgetwc_nid_postfix(stream); } std::int32_t APS5_VABI getwchar_nid_postfix() { return fgetwc_nid_postfix(__stdinp_nid_postfix); } int APS5_VABI getchar_unlocked_nid_postfix() { return getc_unlocked_nid_postfix(__stdinp_nid_postfix); } @@ -113,7 +194,7 @@ int APS5_VABI putc_unlocked_nid_postfix(int value, FileStream* stream) { #ifdef _WIN32 const int result = _fputc_nolock(value, GetNativeStream(stream)); #else - const int result = ::putc_unlocked(value, GetNativeStream(stream)); + const int result = putc_unlocked(value, GetNativeStream(stream)); #endif stream->SyncUnlockedStatus(); return result; @@ -140,6 +221,7 @@ char* APS5_VABI fgets_nid_postfix(char* buffer, int size, FileStream* stream) { if (!stream->SwitchToReading()) return nullptr; auto* result = std::fgets(buffer, size, native); stream->SyncStatus(); + stream->KeepDirectoryReadError(result == nullptr); return result; } int APS5_VABI feof_nid_postfix(FileStream* stream) { @@ -191,9 +273,15 @@ int APS5_VABI setvbuf_nid_postfix(FileStream* stream, char* buffer, int mode, st buffer = nullptr; size = BUFSIZ; } - return std::setvbuf(GetStreamHandle(stream), buffer, native, size); + auto* handle = GetStreamHandle(stream); +#ifdef _WIN32 + if (handle == __acrt_iob_func(1) && KeepHostStandardOutput_nid_no_patch() < 0) return -1; + if (handle == __acrt_iob_func(2) && KeepHostStandardError_nid_no_patch() < 0) return -1; +#endif + return std::setvbuf(handle, buffer, native, size); } void APS5_VABI setbuf_nid_postfix(FileStream* stream, char* buffer) { setvbuf_nid_postfix(stream, buffer, buffer ? 0 : 2, buffer ? 1024 : 0); + } } diff --git a/core/libs/prx/libc/src/StdioExtras.cpp b/core/libs/prx/libc/src/StdioExtras.cpp index 28b5f7b33b..f6ebcc9784 100644 --- a/core/libs/prx/libc/src/StdioExtras.cpp +++ b/core/libs/prx/libc/src/StdioExtras.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -18,8 +19,6 @@ namespace { -using GuestNewHandler = void (APS5_VABI*)(); - std::atomic g_newHandler{nullptr}; void* Allocate(std::size_t size) { @@ -83,9 +82,8 @@ int APS5_VABI sprintf_s_nid_postfix(char* buffer, size_t size, const char* forma } // Only the "C" locale exists. -const char* APS5_VABI setlocale_nid_postfix(int category, const char* locale) { - if (category < 0 || category > 6) return nullptr; - if (locale == nullptr || locale[0] == 0 || std::strcmp(locale, "C") == 0 || std::strcmp(locale, "POSIX") == 0) return "C"; +const char* APS5_VABI setlocale_nid_postfix(int, const char* locale) { + if (locale == nullptr || locale[0] == 0 || std::strcmp(locale, "C") == 0) return "C"; return nullptr; } @@ -131,6 +129,11 @@ void APS5_VABI _ZdlPv_nid_postfix(void* pointer) { if (pointer != nullptr) ApplicationHeapFree_nid_no_patch(pointer); } +void APS5_VABI _ZdlPvRKSt9nothrow_t_nid_postfix(void* pointer, const void*) noexcept { + _ZdlPv_nid_postfix(pointer); + +} + void APS5_VABI _ZdaPv_nid_postfix(void* pointer) { if (pointer != nullptr) ApplicationHeapFree_nid_no_patch(pointer); } diff --git a/core/libs/prx/libc/src/StringExtensions.cpp b/core/libs/prx/libc/src/StringExtensions.cpp index 18b9cb6f18..35eed9a5f4 100644 --- a/core/libs/prx/libc/src/StringExtensions.cpp +++ b/core/libs/prx/libc/src/StringExtensions.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -169,7 +170,6 @@ char* APS5_VABI strtok_nid_postfix(char* text, const char* delimiters) { return strtok_r_nid_postfix(text, delimiters, &state); } - int APS5_VABI __inet_aton_nid_postfix(const char* text, void* address) { std::uint8_t parts[3]; std::size_t count = 0; diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 55ee19e5b1..508da2aa04 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -20,6 +21,16 @@ int CompareUnits(char16_t left, char16_t right) { return left < right ? -1 : 1; } +int ByteDifference(const void* s1, const void* s2, size_t n, bool stopAtNul) { + const auto* left = static_cast(s1); + const auto* right = static_cast(s2); + for (size_t index = 0; index < n; ++index) { + if (left[index] != right[index]) return left[index] - right[index]; + if (stopAtNul && left[index] == 0) return 0; + } + return 0; +} + bool Contains(const char16_t* set, char16_t character) { for (; *set != 0; ++set) { if (*set == character) return true; @@ -153,7 +164,7 @@ TFloat NanValue(const NanSubject& subject, const char*& end) { 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(0x7fc00000u); + return std::bit_cast((subject.negative ? 0x80000000u : 0) | 0x7fc00000u); } else { end = NanSequenceEnd(subject.tail); TFloat value{}; @@ -187,7 +198,36 @@ TFloat ParseFloat(const char* text, char** end, TFloat (*parse)(const char*, cha const TFloat value = parse(text, &parsedEnd); if (errno == ERANGE && value != 0 && (value < 0 ? -value : value) < std::numeric_limits::min()) errno = saved; if (end != nullptr) *end = parsedEnd; - return parsedEnd == text ? TFloat{0} : value; + const char* subject = text; + while (std::isspace(static_cast(*subject))) ++subject; + if (parsedEnd == text) { + if (subject[0] != '-') return TFloat{0}; + if constexpr (std::is_same_v) return subject[1] == '\0' ? TFloat{0} : -TFloat{0}; + const bool special = std::strchr("iInN", subject[1]) != nullptr && subject[1] != '\0'; + return std::is_same_v && !special ? -TFloat{0} : TFloat{0}; + } + if (*subject == '+' || *subject == '-') ++subject; + const bool hexadecimal = subject[0] == '0' && (subject[1] == 'x' || subject[1] == 'X') && parsedEnd > subject + 2; + if constexpr (std::is_same_v) { + const float magnitude = value < 0 ? -value : value; + if (value != 0 && magnitude < std::numeric_limits::min()) { + const long double exact = std::strtold(text, nullptr); + const bool roundedUp = magnitude == std::numeric_limits::denorm_min() && (exact < 0 ? -exact : exact) < magnitude; + errno = hexadecimal ? (roundedUp ? ERANGE : saved) : (static_cast(value) != exact ? ERANGE : saved); + } + return value; + } + if (!hexadecimal) return value; + if constexpr (std::is_same_v) { + constexpr double smallest = std::numeric_limits::denorm_min(); + if ((value < 0 ? -value : value) == smallest) { + const long double exact = std::strtold(text, nullptr); + if ((exact < 0 ? -exact : exact) < smallest) errno = ERANGE; + } + } else if (errno == ERANGE) { + errno = saved; + } + return value; } } @@ -216,7 +256,7 @@ 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) { - return std::memcmp(s1, s2, n); + return std::memcmp(s1, s2, n) == 0 ? 0 : ByteDifference(s1, s2, n, false); } const void* APS5_VABI memchr_nid_postfix(const void* s, int c, size_t n) { @@ -260,11 +300,11 @@ void* APS5_VABI memmem_nid_postfix(const void* haystack, size_t haystacklen, con } int APS5_VABI strcmp_nid_postfix(const char* s1, const char* s2) { - return std::strcmp(s1, s2); + return std::strcmp(s1, s2) == 0 ? 0 : ByteDifference(s1, s2, std::numeric_limits::max(), true); } int APS5_VABI strncmp_nid_postfix(const char* s1, const char* s2, size_t n) { - return std::strncmp(s1, s2, n); + return std::strncmp(s1, s2, n) == 0 ? 0 : ByteDifference(s1, s2, n, true); } size_t APS5_VABI strlen_nid_postfix(const char* s) { @@ -418,22 +458,21 @@ char16_t* APS5_VABI wmemmove_nid_postfix(char16_t* dest, const char16_t* src, si } - extern "C" { int APS5_VABI strcasecmp_nid_postfix(const char* s1, const char* s2) { for (;; ++s1, ++s2) { - const int a = std::tolower(static_cast(*s1)); - const int b = std::tolower(static_cast(*s2)); - if (a != b || *s1 == 0) return a - b; + const int a = tolower_nid_postfix(static_cast(*s1)); + const int b = tolower_nid_postfix(static_cast(*s2)); + if (a != b || *s1 == 0) return (a > b) - (a < b); } } int APS5_VABI strncasecmp_nid_postfix(const char* s1, const char* s2, size_t n) { for (; n != 0; ++s1, ++s2, --n) { - const int a = std::tolower(static_cast(*s1)); - const int b = std::tolower(static_cast(*s2)); - if (a != b) return a - b; + const int a = tolower_nid_postfix(static_cast(*s1)); + const int b = tolower_nid_postfix(static_cast(*s2)); + if (a != b) return a < b ? -1 : 1; if (*s1 == 0) break; } return 0; @@ -448,7 +487,11 @@ size_t APS5_VABI strspn_nid_postfix(const char* s, const char* accept) { } int APS5_VABI strcoll_nid_postfix(const char* s1, const char* s2) { - return std::strcmp(s1, s2); + for (;; ++s1, ++s2) { + const int a = static_cast(*s1); + const int b = static_cast(*s2); + if (a != b || a == 0) return a - b; + } } int APS5_VABI strncpy_s_nid_postfix(char* dest, size_t destsz, const char* src, size_t count) { @@ -467,6 +510,12 @@ int APS5_VABI strncpy_s_nid_postfix(char* dest, size_t destsz, const char* src, dest[0] = '\0'; return GuestErange; } + const auto destination = reinterpret_cast(dest); + const auto source = reinterpret_cast(src); + if (destination < source + (length + 1) && source < destination + length + 1) { + dest[0] = '\0'; + return GuestEinval; + } std::memcpy(dest, src, length); dest[length] = '\0'; return 0; @@ -494,6 +543,12 @@ int APS5_VABI strncat_s_nid_postfix(char* dest, size_t destsz, const char* src, dest[0] = '\0'; return GuestErange; } + const auto destination = reinterpret_cast(dest); + const auto source = reinterpret_cast(src); + if (destination < source + (length + 1) && source < destination + used + length + 1) { + dest[0] = '\0'; + return GuestEinval; + } std::memcpy(dest + used, src, length); dest[used + length] = '\0'; return 0; @@ -768,6 +823,7 @@ int APS5_VABI wmemcpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_ if (destsz > RsizeMax) return GuestErange; if (!src || count > destsz) { wmemset_nid_postfix(dest, 0, destsz); + return src ? GuestErange : GuestEinval; } const auto destination = reinterpret_cast(dest); @@ -775,6 +831,7 @@ int APS5_VABI wmemcpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_ const auto distance = destination < source ? source - destination : destination - source; if (count != 0 && distance < count * sizeof(char16_t)) { wmemset_nid_postfix(dest, 0, destsz); + return GuestEinval; } if (count != 0) std::memcpy(dest, src, count * sizeof(char16_t)); @@ -789,6 +846,7 @@ int APS5_VABI wmemmove_s_nid_postfix(char16_t* dest, size_t destsz, const char16 if (destsz > RsizeMax) return GuestErange; if (!src || count > destsz) { wmemset_nid_postfix(dest, 0, destsz); + return src ? GuestErange : GuestEinval; } if (count != 0) std::memmove(dest, src, count * sizeof(char16_t)); @@ -851,8 +909,8 @@ int APS5_VABI wcsncat_s_nid_postfix(char16_t* dest, size_t destsz, const char16_ } const auto destination = reinterpret_cast(dest + used); const auto source = reinterpret_cast(src); - const auto distance = destination < source ? source - destination : destination - source; - if (length != 0 && distance < (length + 1) * sizeof(char16_t)) { + const auto bytes = length * sizeof(char16_t); + if (destination < source + bytes && source < destination + bytes) { dest[0] = 0; return GuestEinval; } diff --git a/core/libs/prx/libc/src/Syslog.cpp b/core/libs/prx/libc/src/Syslog.cpp index a94f34e141..137566b52f 100644 --- a/core/libs/prx/libc/src/Syslog.cpp +++ b/core/libs/prx/libc/src/Syslog.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include @@ -13,12 +14,6 @@ #include #endif -extern "C" { -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length); -int APS5_VABI vsnprintf_nid_postfix(char* buffer, std::size_t size, const char* format, VaList* args); -} - namespace { constexpr int priorityMask = 0x07; constexpr int facilityMask = 0x3f8; @@ -57,6 +52,7 @@ std::string ExpandErrorText(const char* format, int error) { if (cursor[0] == '%' && cursor[1] == 'm') { char text[128]; strerror_r_nid_postfix(error, text, sizeof(text)); + for (const char* character = text; *character; ++character) { expanded += *character; if (*character == '%') expanded += '%'; @@ -82,6 +78,7 @@ std::string Format(const char* format, VaList* args) { if (size <= 0) return {}; std::string buffer(static_cast(size) + 1, '\0'); vsnprintf_nid_postfix(buffer.data(), buffer.size(), format, args); + buffer.resize(static_cast(size)); return buffer; #endif @@ -135,6 +132,7 @@ void APS5_VABI syslog_nid_postfix(int priority, const char* format, ...) { va_start(args, format); #endif vsyslog_nid_postfix(priority, format, reinterpret_cast(args)); + #ifdef _WIN32 __builtin_sysv_va_end(args); #else diff --git a/core/libs/prx/libc/src/Threads.cpp b/core/libs/prx/libc/src/Threads.cpp index a00ba39463..7b67acbcdb 100644 --- a/core/libs/prx/libc/src/Threads.cpp +++ b/core/libs/prx/libc/src/Threads.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/General.hpp" #ifdef _WIN32 @@ -26,11 +27,6 @@ namespace { -struct Xtime { - std::int64_t sec; - std::int64_t nsec; -}; - constexpr std::int64_t nanosPerSecond = 1000000000; constexpr std::int64_t longestSleepSeconds = 86400; @@ -52,11 +48,27 @@ timespec XtimeDeadline(const Xtime& time, const char* function) { return {static_cast(seconds), static_cast(nanos)}; } +std::chrono::system_clock::time_point XtimePoint(const Xtime& time, const char* function) { + const auto deadline = XtimeDeadline(time, function); + constexpr auto maxDuration = std::chrono::system_clock::duration::max(); + constexpr auto minDuration = std::chrono::system_clock::duration::min(); + constexpr auto maxSeconds = std::chrono::duration_cast(maxDuration).count(); + constexpr auto minSeconds = std::chrono::duration_cast(minDuration).count(); + constexpr auto maxNanos = std::chrono::duration_cast(maxDuration - std::chrono::seconds(maxSeconds)).count(); + if (deadline.tv_sec > maxSeconds || (deadline.tv_sec == maxSeconds && deadline.tv_nsec > maxNanos) || + deadline.tv_sec < minSeconds) + throw std::overflow_error(std::string(function) + ": xtime seconds overflow"); + return std::chrono::system_clock::time_point( + std::chrono::seconds(deadline.tv_sec) + + std::chrono::ceil(std::chrono::nanoseconds(deadline.tv_nsec))); +} + constexpr int threadSuccess = 0; constexpr int threadNomem = 1; constexpr int threadTimedout = 2; constexpr int threadBusy = 3; constexpr int threadError = 4; +constexpr int threadTimedlockError = 5; constexpr int mutexRecursive = 0x100; @@ -76,14 +88,61 @@ struct DinkumwareNativeMutex { void lock() { EnterCriticalSection(&value); } void unlock() { LeaveCriticalSection(&value); } bool try_lock() { return TryEnterCriticalSection(&value) != FALSE; } + + template + bool try_lock_until(const std::chrono::time_point& deadline) { + if (try_lock()) + return true; + for (;;) { + const auto now = Clock::now(); + if (now >= deadline) + return false; + const auto remaining = std::chrono::ceil(deadline - now).count(); + Sleep(static_cast(std::min(remaining, 1))); + if (try_lock()) { + if (Clock::now() <= deadline) + return true; + unlock(); + return false; + } + } + } }; struct DinkumwareNativeCondition { CONDITION_VARIABLE value = CONDITION_VARIABLE_INIT; + + void notify_one() { WakeConditionVariable(&value); } + void notify_all() { WakeAllConditionVariable(&value); } + + void wait(std::unique_lock& lock) { + if (SleepConditionVariableCS(&value, &lock.mutex()->value, INFINITE) == FALSE) + throw std::system_error(static_cast(GetLastError()), std::system_category(), "_Cnd_wait: condition wait failed"); + } + + template + std::cv_status wait_until(std::unique_lock& lock, + const std::chrono::time_point& deadline) { + for (;;) { + const auto now = Clock::now(); + if (now >= deadline) + return std::cv_status::timeout; + const auto remaining = std::chrono::ceil(deadline - now).count(); + const auto largest = static_cast(std::numeric_limits::max() - 1U); + const auto milliseconds = static_cast(std::min(remaining, largest)); + if (SleepConditionVariableCS(&value, &lock.mutex()->value, milliseconds) != FALSE) + return std::cv_status::no_timeout; + const DWORD error = GetLastError(); + if (error != ERROR_TIMEOUT) + throw std::system_error(static_cast(error), std::system_category(), "_Cnd_timedwait: condition wait failed"); + if (Clock::now() >= deadline) + return std::cv_status::timeout; + } + } }; #else -using DinkumwareNativeMutex = std::mutex; -using DinkumwareNativeCondition = std::condition_variable; +using DinkumwareNativeMutex = std::timed_mutex; +using DinkumwareNativeCondition = std::condition_variable_any; #endif struct DinkumwareMutex { @@ -99,32 +158,6 @@ struct DinkumwareCondition { DinkumwareNativeCondition native; }; -#ifdef _WIN32 -DWORD WaitMillisecondsUntil(std::chrono::system_clock::time_point deadline) { - const auto now = std::chrono::system_clock::now(); - if (now >= deadline) - return 0; - const auto remaining = std::chrono::ceil(deadline - now).count(); - constexpr auto largest = static_cast(std::numeric_limits::max() - 1U); - return static_cast(std::min(remaining, largest)); -} - -bool WaitUntil(DinkumwareCondition* condition, DinkumwareMutex* mutex, std::chrono::system_clock::time_point deadline) { - for (;;) { - const DWORD milliseconds = WaitMillisecondsUntil(deadline); - if (milliseconds == 0) - return false; - if (SleepConditionVariableCS(&condition->native.value, &mutex->native.value, milliseconds) != FALSE) - return true; - const DWORD error = GetLastError(); - if (error != ERROR_TIMEOUT) - throw std::system_error(static_cast(error), std::system_category(), "_Cnd_timedwait: condition wait failed"); - if (std::chrono::system_clock::now() >= deadline) - return false; - } -} -#endif - DinkumwareMutex* ResolveMutex(void** handle, const char* function) { if (!handle) throw std::invalid_argument(std::string(function) + ": null mutex handle pointer"); @@ -223,6 +256,31 @@ int APS5_VABI _Mtx_trylock_nid_postfix(void** handle) { return threadSuccess; } +int APS5_VABI _Mtx_timedlock_nid_postfix(void** handle, const Xtime* target) { + if (!target) + APS5_INVALID_ARG_EX; + const auto deadline = XtimePoint(*target, "_Mtx_timedlock"); + auto* mutex = ResolveMutex(handle, "_Mtx_timedlock"); + const auto self = std::this_thread::get_id(); + if (mutex->owner.load(std::memory_order_acquire) == self) { + if ((mutex->type & mutexRecursive) == 0) + return threadBusy; + if (mutex->count == std::numeric_limits::max()) + throw std::overflow_error("_Mtx_timedlock: recursive lock count overflow"); + ++mutex->count; + return threadSuccess; + } + try { + if (!mutex->native.try_lock_until(deadline)) + return threadTimedout; + } catch (const std::system_error&) { + return threadTimedlockError; + } + mutex->owner.store(self, std::memory_order_release); + mutex->count = 1; + return threadSuccess; +} + int APS5_VABI _Mtx_unlock_nid_postfix(void** handle) { auto* mutex = ResolveMutex(handle, "_Mtx_unlock"); if (!IsOwnedByCaller(mutex)) @@ -271,12 +329,7 @@ int APS5_VABI _Cnd_wait_nid_postfix(void** conditionHandle, void** mutexHandle) mutex->owner.store(std::thread::id{}, std::memory_order_release); std::unique_lock lock(mutex->native, std::adopt_lock); try { -#ifdef _WIN32 - if (SleepConditionVariableCS(&condition->native.value, &mutex->native.value, INFINITE) == FALSE) - throw std::system_error(static_cast(GetLastError()), std::system_category(), "_Cnd_wait: condition wait failed"); -#else condition->native.wait(lock); -#endif } catch (const std::system_error&) { lock.release(); mutex->owner.store(std::this_thread::get_id(), std::memory_order_release); @@ -292,15 +345,7 @@ int APS5_VABI _Cnd_wait_nid_postfix(void** conditionHandle, void** mutexHandle) int APS5_VABI _Cnd_timedwait_nid_postfix(void** conditionHandle, void** mutexHandle, const Xtime* target) { if (!target) APS5_INVALID_ARG_EX; - const auto deadline = XtimeDeadline(*target, "_Cnd_timedwait"); - constexpr auto maxDuration = std::chrono::system_clock::duration::max(); - constexpr auto minDuration = std::chrono::system_clock::duration::min(); - constexpr auto maxSeconds = std::chrono::duration_cast(maxDuration).count(); - constexpr auto minSeconds = std::chrono::duration_cast(minDuration).count(); - constexpr auto maxNanos = std::chrono::duration_cast(maxDuration - std::chrono::seconds(maxSeconds)).count(); - if (deadline.tv_sec > maxSeconds || (deadline.tv_sec == maxSeconds && deadline.tv_nsec > maxNanos) || - deadline.tv_sec < minSeconds) - throw std::overflow_error("_Cnd_timedwait: xtime seconds overflow"); + const auto deadlineTime = XtimePoint(*target, "_Cnd_timedwait"); auto* condition = ResolveCondition(conditionHandle, "_Cnd_timedwait"); auto* mutex = ResolveMutex(mutexHandle, "_Cnd_timedwait"); if (!IsOwnedByCaller(mutex)) @@ -312,14 +357,7 @@ int APS5_VABI _Cnd_timedwait_nid_postfix(void** conditionHandle, void** mutexHan std::unique_lock lock(mutex->native, std::adopt_lock); bool timeout = false; try { - const auto deadlineTime = std::chrono::system_clock::time_point( - std::chrono::seconds(deadline.tv_sec) + - std::chrono::ceil(std::chrono::nanoseconds(deadline.tv_nsec))); -#ifdef _WIN32 - timeout = !WaitUntil(condition, mutex, deadlineTime); -#else timeout = (condition->native.wait_until(lock, deadlineTime) == std::cv_status::timeout); -#endif } catch (const std::system_error&) { lock.release(); mutex->owner.store(std::this_thread::get_id(), std::memory_order_release); @@ -333,23 +371,19 @@ int APS5_VABI _Cnd_timedwait_nid_postfix(void** conditionHandle, void** mutexHan } int APS5_VABI _Cnd_broadcast_nid_postfix(void** handle) { -#ifdef _WIN32 - WakeAllConditionVariable(&ResolveCondition(handle, "_Cnd_broadcast")->native.value); -#else ResolveCondition(handle, "_Cnd_broadcast")->native.notify_all(); -#endif return threadSuccess; } int SignalCondition_nid_no_patch(void** handle) { -#ifdef _WIN32 - WakeConditionVariable(&ResolveCondition(handle, "_Cnd_signal")->native.value); -#else ResolveCondition(handle, "_Cnd_signal")->native.notify_one(); -#endif return threadSuccess; } +unsigned APS5_VABI _ZNSt6thread20hardware_concurrencyEv_nid_postfix() { + return std::thread::hardware_concurrency(); +} + int APS5_VABI _Thrd_sleep_nid_postfix(const Xtime* target, Xtime*) { if (!target) APS5_INVALID_ARG_EX; @@ -379,18 +413,15 @@ int APS5_VABI _Thrd_sleep_nid_postfix(const Xtime* target, Xtime*) { } } +int APS5_VABI _Thrd_yield_nid_postfix() { + std::this_thread::yield(); + return threadSuccess; +} int APS5_VABI _Cnd_signal_nid_postfix(void** handle) { return SignalCondition_nid_no_patch(handle); } -struct GuestPad { - unsigned (APS5_VABI* const* vtable)(GuestPad*); - void* condition; - void* mutex; - bool started; -}; - static_assert(offsetof(GuestPad, condition) == 0x8 && offsetof(GuestPad, mutex) == 0x10 && offsetof(GuestPad, started) == 0x18 && sizeof(GuestPad) == 0x20); [[noreturn]] void ThrowCppError_nid_no_patch(int code); @@ -438,6 +469,7 @@ void APS5_VABI _ZNSt4_PadC2Ev_nid_postfix(GuestPad* pad) { const int created = _Mtx_init_nid_postfix(&pad->mutex, 1); if (created != threadSuccess) { _Cnd_destroy_nid_postfix(&pad->condition); + RequireThrd(created, "_Pad::_Pad: _Mtx_init"); } pad->started = false; @@ -445,6 +477,7 @@ void APS5_VABI _ZNSt4_PadC2Ev_nid_postfix(GuestPad* pad) { if (locked != threadSuccess) { _Mtx_destroy_nid_postfix(&pad->mutex); _Cnd_destroy_nid_postfix(&pad->condition); + RequireThrd(locked, "_Pad::_Pad: _Mtx_lock"); } } @@ -454,6 +487,7 @@ void APS5_VABI _ZNSt4_PadD2Ev_nid_postfix(GuestPad* pad) { RequireThrd(_Mtx_unlock_nid_postfix(&pad->mutex), "_Pad::~_Pad: _Mtx_unlock"); _Mtx_destroy_nid_postfix(&pad->mutex); _Cnd_destroy_nid_postfix(&pad->condition); + } void APS5_VABI _ZNSt4_Pad8_ReleaseEv_nid_postfix(GuestPad* pad) { @@ -464,15 +498,23 @@ void APS5_VABI _ZNSt4_Pad8_ReleaseEv_nid_postfix(GuestPad* pad) { RequireThrd(_Mtx_unlock_nid_postfix(&pad->mutex), "_Pad::_Release: _Mtx_unlock"); } -void APS5_VABI _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(GuestPad* pad, void** thread) { +static void LaunchPad_nid_no_patch(GuestPad* pad, void** attrSlot, void** thread) { if (!pad || !thread) APS5_INVALID_ARG_EX; const auto create = registeredPthreadCreate.load(std::memory_order_acquire); if (!create) throw std::runtime_error("_Pad::_Launch: pthread_create is not registered"); - const int created = create(thread, nullptr, PadEntry_nid_no_patch, pad); + const int created = create(thread, attrSlot, PadEntry_nid_no_patch, pad); if (created != 0) ThrowCppError_nid_no_patch(6); while (!pad->started) RequireThrd(_Cnd_wait_nid_postfix(&pad->condition, &pad->mutex), "_Pad::_Launch: _Cnd_wait"); } +void APS5_VABI _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(GuestPad* pad, void** thread) { + LaunchPad_nid_no_patch(pad, nullptr, thread); +} + +void APS5_VABI _ZNSt4_Pad7_LaunchEPP12pthread_attrPP7pthread_nid_postfix(GuestPad* pad, void** attrSlot, void** thread) { + LaunchPad_nid_no_patch(pad, attrSlot, thread); +} + int APS5_VABI _Thrd_join_nid_postfix(void* thread, int* code) { const auto join = registeredPthreadJoin.load(std::memory_order_acquire); if (!join) throw std::runtime_error("_Thrd_join: pthread_join is not registered"); @@ -488,11 +530,6 @@ int APS5_VABI _Thrd_detach_nid_postfix(void* thread) { return detach(thread) == 0 ? threadSuccess : threadError; } -int APS5_VABI _Thrd_id_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - [[noreturn]] void APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(int code) { ThrowCppError_nid_no_patch(code); } @@ -537,6 +574,10 @@ void* APS5_VABI _Thrd_current_nid_postfix(void) { return ThreadForwards(__func__).self(); } +void* APS5_VABI _Thrd_id_nid_postfix(void) { + return ThreadForwards(__func__).self(); +} + [[noreturn]] void APS5_VABI _Thrd_exit_nid_postfix(int code) { ThreadForwards(__func__).exit(reinterpret_cast(static_cast(static_cast(code)))); std::abort(); @@ -546,5 +587,122 @@ int APS5_VABI _Mtx_current_owns_nid_postfix(void**) { return 1; } +static std::atomic registeredPthreadSyncForwards{nullptr}; + +void RegisterPthreadSyncForwards_nid_no_patch(const PthreadSyncForwards_nid_no_patch* forwards) { + registeredPthreadSyncForwards.store(forwards, std::memory_order_release); } +static const PthreadSyncForwards_nid_no_patch& PthreadSync(const char* function) { + const auto* forwards = registeredPthreadSyncForwards.load(std::memory_order_acquire); + if (!forwards) throw std::runtime_error(std::string(function) + ": libkernel synchronization is not registered"); + return *forwards; +} + +constexpr int KernelDeadlock = static_cast(0x8002000b); +constexpr int KernelNoMemory = static_cast(0x8002000c); +constexpr int KernelBusy = static_cast(0x80020010); +constexpr int RecursiveMutexType = 0x100; +constexpr int KernelRecursiveMutex = 2; + +static int CreateResult(int result) { + if (result == 0) return threadSuccess; + return result == KernelNoMemory ? threadNomem : threadError; +} + +static const char* SyncObjectName(const char* name) { + return name != nullptr && name[0] != '\0' ? name : nullptr; +} + +int APS5_VABI _ZNSt8__sce_v219_Mtx_init_with_nameEPP13pthread_mutexiPKc_nid_postfix(void** mutex, int type, const char* name) { + const auto& sync = PthreadSync(__func__); + *mutex = nullptr; + void* attr = nullptr; + const int created = sync.mutexattrInit(&attr); + if (created != 0) return CreateResult(created); + if ((type & RecursiveMutexType) != 0 && sync.mutexattrSettype(&attr, KernelRecursiveMutex) != 0) { + sync.mutexattrDestroy(&attr); + return threadError; + } + const int initialized = sync.mutexInit(mutex, &attr, SyncObjectName(name)); + const int released = sync.mutexattrDestroy(&attr); + if (initialized != 0) return CreateResult(initialized); + if (released != 0) { + sync.mutexDestroy(mutex); + return threadError; + } + return threadSuccess; +} + +int APS5_VABI _ZNSt8__sce_v29_Mtx_initEPP13pthread_mutexi_nid_postfix(void** mutex, int type) { + return _ZNSt8__sce_v219_Mtx_init_with_nameEPP13pthread_mutexiPKc_nid_postfix(mutex, type, "SceLibcI_Mtx"); +} + +int APS5_VABI _ZNSt8__sce_v212_Mtx_destroyEPP13pthread_mutex_nid_postfix(void** mutex) { + return PthreadSync(__func__).mutexDestroy(mutex); +} + +int APS5_VABI _ZNSt8__sce_v29_Mtx_lockEPP13pthread_mutex_nid_postfix(void** mutex) { + const int result = PthreadSync(__func__).mutexLock(mutex); + if (result == 0) return threadSuccess; + return result == KernelDeadlock ? threadBusy : threadError; +} + +int APS5_VABI _ZNSt8__sce_v212_Mtx_trylockEPP13pthread_mutex_nid_postfix(void** mutex) { + const int result = PthreadSync(__func__).mutexTrylock(mutex); + if (result == 0) return threadSuccess; + return result == KernelBusy ? threadBusy : threadError; +} + +int APS5_VABI _ZNSt8__sce_v211_Mtx_unlockEPP13pthread_mutex_nid_postfix(void** mutex) { + PthreadSync(__func__).mutexUnlock(mutex); + return threadSuccess; +} + +int APS5_VABI _ZNSt8__sce_v219_Cnd_init_with_nameEPP12pthread_condPKc_nid_postfix(void** condition, const char* name) { + return CreateResult(PthreadSync(__func__).condInit(condition, nullptr, SyncObjectName(name))); +} + +int APS5_VABI _ZNSt8__sce_v29_Cnd_initEPP12pthread_cond_nid_postfix(void** condition) { + return CreateResult(PthreadSync(__func__).condInit(condition, nullptr, "SceLibcI_Cnd")); +} + +int APS5_VABI _ZNSt8__sce_v212_Cnd_destroyEPP12pthread_cond_nid_postfix(void** condition) { + return PthreadSync(__func__).condDestroy(condition); +} + +int APS5_VABI _ZNSt8__sce_v29_Cnd_waitEPP12pthread_condPP13pthread_mutex_nid_postfix(void** condition, void** mutex) { + PthreadSync(__func__).condWait(condition, mutex); + return threadSuccess; +} + +int APS5_VABI _ZNSt8__sce_v211_Cnd_signalEPP12pthread_cond_nid_postfix(void** condition) { + PthreadSync(__func__).condSignal(condition); + return threadSuccess; +} + +int APS5_VABI _ZNSt8__sce_v214_Cnd_broadcastEPP12pthread_cond_nid_postfix(void** condition) { + PthreadSync(__func__).condBroadcast(condition); + return threadSuccess; +} + +int APS5_VABI _ZNSt8__sce_v212_Thrd_detachEP7pthread_nid_postfix(void* thread) { + return PthreadSync(__func__).detach(thread) == 0 ? threadSuccess : threadError; +} + +int APS5_VABI _ZNSt8__sce_v210_Thrd_joinEP7pthreadPi_nid_postfix(void* thread, int* code) { + void* result = nullptr; + if (PthreadSync(__func__).join(thread, &result) != 0) return threadError; + if (code) *code = static_cast(reinterpret_cast(result)); + return threadSuccess; +} + +void APS5_VABI _ZNSt8__sce_v211_Thrd_yieldEv_nid_postfix() { + PthreadSync(__func__).yield(); +} + +void* APS5_VABI _ZNSt8__sce_v28_Thrd_idEv_nid_postfix() { + return _Thrd_current_nid_postfix(); +} + +} diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index c3499f8047..14a1b8bc68 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -20,6 +21,7 @@ #include "prx/libc/include/SonyCrt.hpp" extern "C" char* tzname_nid_postfix[2]; +extern "C" int64_t timezone_nid_postfix; namespace { @@ -101,7 +103,11 @@ std::tm toHostTm(const GuestTm& guest) { host.tm_isdst = guest.tm_isdst; #ifndef _WIN32 host.tm_gmtoff = static_cast(guest.tm_gmtoff); +#ifdef __APPLE__ + host.tm_zone = const_cast(guest.tm_zone); +#else host.tm_zone = guest.tm_zone; +#endif #endif return host; } @@ -157,8 +163,6 @@ const bool timeZoneFixed = [] { } // namespace #endif -extern "C" int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr); - namespace { bool setHostZone(const char* zone) { @@ -183,6 +187,7 @@ std::recursive_mutex& zoneMutex() { } char** HostZoneNames(); +char* GuestZoneName(int index); void applyGuestZone() { static bool initialized = false; @@ -205,9 +210,8 @@ void applyGuestZone() { #else ::tzset(); #endif - char** names = HostZoneNames(); - tzname_nid_postfix[0] = names[0]; - tzname_nid_postfix[1] = names[1]; + tzname_nid_postfix[0] = GuestZoneName(0); + tzname_nid_postfix[1] = GuestZoneName(1); } int& zoneDepth() { @@ -240,19 +244,6 @@ constexpr const char* MONTH_FULL_NAMES[] = {"January", "February", "March", "Apr "October", "November", "December"}; constexpr int YEAR_BASE = 1900; -enum class Padding { Default, Less, Space, Zero }; - -const char* paddingFormat(int width, Padding padding, bool spaced) { - static constexpr const char* formats[][4] = { - {"%02d", "%d", "%2d", "%02d"}, - {"%2d", "%d", "%2d", "%02d"}, - {"%03d", "%d", "%3d", "%03d"}, - {"%04d", "%d", "%4d", "%04d"}, - }; - const int row = width == 2 ? (spaced ? 1 : 0) : width == 3 ? 2 : 3; - return formats[row][static_cast(padding)]; -} - bool isLeapYearSum(int year, int base) { return isLeapYear(year % 400 + base % 400); } @@ -260,248 +251,151 @@ bool isLeapYearSum(int year, int base) { struct FormatBuffer { char* cursor; char* limit; + bool full; }; -void addText(FormatBuffer& buffer, const char* text) { - while (buffer.cursor < buffer.limit && (*buffer.cursor = *text++) != '\0') ++buffer.cursor; +void addPiece(FormatBuffer& buffer, const char* text, std::size_t length) { + if (buffer.full) return; + if (length > static_cast(buffer.limit - buffer.cursor)) { + buffer.full = true; + return; + } + std::memcpy(buffer.cursor, text, length); + buffer.cursor += length; } -void addNumber(FormatBuffer& buffer, int value, const char* format) { +void addText(FormatBuffer& buffer, const char* text) { + addPiece(buffer, text, std::strlen(text)); +} + +int clampedField(std::int64_t value, int modulus) { + return value < 0 ? 0 : static_cast(value % modulus); +} + +void addNumber(FormatBuffer& buffer, std::int64_t value, int modulus, const char* format) { char text[16]; - std::snprintf(text, sizeof(text), format, value); + std::snprintf(text, sizeof(text), format, clampedField(value, modulus)); addText(buffer, text); } -void addYear(FormatBuffer& buffer, int year, int base, bool century, bool lastDigits) { - int trail = year % 100 + base % 100; - int lead = year / 100 + base / 100 + trail / 100; - trail %= 100; - if (trail < 0 && lead > 0) { - trail += 100; - --lead; - } else if (lead < 0 && trail > 0) { - trail -= 100; - ++lead; - } - if (century) { - if (lead == 0 && trail < 0) addText(buffer, "-0"); - else addNumber(buffer, lead, "%02d"); - } - if (lastDigits) addNumber(buffer, trail < 0 ? -trail : trail, "%02d"); +const char* tableName(int index, const char* const* names, const char* const* shortNames, int count) { + if (index < 0) return shortNames[0]; + return index < count ? names[index] : ""; } -const char* guestZoneName(const GuestTm& guest) { - if (guest.tm_zone != nullptr) return guest.tm_zone; - if (guest.tm_isdst < 0) return ""; -#ifdef _WIN32 - return _tzname[guest.tm_isdst != 0 ? 1 : 0]; -#else - return tzname[guest.tm_isdst != 0 ? 1 : 0]; -#endif +int twoDigitYear(std::int64_t year) { + return static_cast((year % 100 + 100) % 100); } -void addIsoWeek(FormatBuffer& buffer, char conversion, Padding padding, const GuestTm& time) { +std::int64_t weekOfYear(const GuestTm& time, int shift) { + return (static_cast(time.tm_yday) + 7 - (static_cast(time.tm_wday) + shift) % 7) / 7; +} + +struct IsoWeek { + std::int64_t year; + int week; +}; + +IsoWeek isoWeek(const GuestTm& time) { + if (time.tm_yday < 0) NotImplemented_nid_no_patch("strftime: ISO week of a negative tm_yday"); + if (time.tm_yday >= (isLeapYearSum(time.tm_year, YEAR_BASE) ? 366 : 365)) NotImplemented_nid_no_patch("strftime: ISO week of a tm_yday past the year"); + if (time.tm_wday < -6) NotImplemented_nid_no_patch("strftime: ISO week of a tm_wday below -6"); int base = YEAR_BASE; - int yday = time.tm_yday; - int week = 0; + std::int64_t yday = time.tm_yday; for (;;) { const int length = isLeapYearSum(time.tm_year, base) ? 366 : 365; - const int bottom = (yday + 11 - time.tm_wday) % 7 - 3; - int top = bottom - length % 7; + const std::int64_t bottom = (yday + 11 - time.tm_wday) % 7 - 3; + std::int64_t top = bottom - length % 7; if (top < -3) top += 7; top += length; - if (yday >= top) { - ++base; - week = 1; - break; - } - if (yday >= bottom) { - week = 1 + (yday - bottom) / 7; - break; - } + if (yday >= top) return {static_cast(time.tm_year) + base + 1, 1}; + if (yday >= bottom) return {static_cast(time.tm_year) + base, static_cast(1 + (yday - bottom) / 7)}; --base; yday += isLeapYearSum(time.tm_year, base) ? 366 : 365; } - if (conversion == 'V') addNumber(buffer, week, paddingFormat(2, padding, false)); - else addYear(buffer, time.tm_year, base, conversion == 'G', true); +} + +void formatTime(FormatBuffer& buffer, const char* format, const GuestTm& time); + +void addZoneOffset(FormatBuffer& buffer, const GuestTm& time) { + std::int64_t east = -timezone_nid_postfix + (time.tm_isdst > 0 ? 3600 : 0); + const char sign = east < 0 ? '-' : '+'; + if (east < 0) east = -east; + char text[32]; + std::snprintf(text, sizeof(text), "%c%02lld%02lld", sign, static_cast(east / 3600), static_cast(east / 60 % 60)); + addText(buffer, text); +} + +bool addConversion(FormatBuffer& buffer, char conversion, const GuestTm& time) { + const std::int64_t year = static_cast(time.tm_year) + YEAR_BASE; + switch (conversion) { + case 'a': addText(buffer, tableName(time.tm_wday, WEEKDAY_NAMES, WEEKDAY_NAMES, 7)); return true; + case 'A': addText(buffer, tableName(time.tm_wday, WEEKDAY_FULL_NAMES, WEEKDAY_NAMES, 7)); return true; + case 'b': + case 'h': addText(buffer, tableName(time.tm_mon, MONTH_NAMES, MONTH_NAMES, 12)); return true; + case 'B': addText(buffer, tableName(time.tm_mon, MONTH_FULL_NAMES, MONTH_NAMES, 12)); return true; + case 'c': formatTime(buffer, "%a %b %e %H:%M:%S %Y", time); return true; + case 'C': addNumber(buffer, time.tm_year / 100 + YEAR_BASE / 100, 100, "%02d"); return true; + case 'd': addNumber(buffer, time.tm_mday, 100, "%02d"); return true; + case 'D': + case 'x': formatTime(buffer, "%m/%d/%y", time); return true; + case 'e': addNumber(buffer, time.tm_mday, 100, "%2d"); return true; + case 'F': formatTime(buffer, "%Y-%m-%d", time); return true; + case 'g': addNumber(buffer, twoDigitYear(isoWeek(time).year), 100, "%02d"); return true; + case 'G': addNumber(buffer, isoWeek(time).year, 10000, "%04d"); return true; + case 'H': addNumber(buffer, time.tm_hour, 100, "%02d"); return true; + case 'I': addNumber(buffer, (static_cast(time.tm_hour) + 11) % 12 + 1, 100, "%02d"); return true; + case 'j': addNumber(buffer, static_cast(time.tm_yday) + 1, 1000, "%03d"); return true; + case 'm': addNumber(buffer, static_cast(time.tm_mon) + 1, 100, "%02d"); return true; + case 'M': addNumber(buffer, time.tm_min, 100, "%02d"); return true; + case 'n': addText(buffer, "\n"); return true; + case 'p': addText(buffer, time.tm_hour < 12 ? "AM" : "PM"); return true; + case 'r': formatTime(buffer, "%I:%M:%S %p", time); return true; + case 'R': formatTime(buffer, "%H:%M", time); return true; + case 'S': addNumber(buffer, time.tm_sec, 100, "%02d"); return true; + case 't': addText(buffer, "\t"); return true; + case 'T': + case 'X': formatTime(buffer, "%H:%M:%S", time); return true; + case 'u': addNumber(buffer, time.tm_wday == 0 ? 7 : time.tm_wday, 10, "%d"); return true; + case 'U': addNumber(buffer, weekOfYear(time, 7), 100, "%02d"); return true; + case 'V': addNumber(buffer, isoWeek(time).week, 100, "%02d"); return true; + case 'w': addNumber(buffer, time.tm_wday, 10, "%d"); return true; + case 'W': addNumber(buffer, weekOfYear(time, 6), 100, "%02d"); return true; + case 'y': addNumber(buffer, twoDigitYear(year), 100, "%02d"); return true; + case 'Y': addNumber(buffer, year, 10000, "%04d"); return true; + case 'z': addZoneOffset(buffer, time); return true; + case 'Z': { + const char* name = tzname_nid_postfix[time.tm_isdst > 0 ? 1 : 0]; + addText(buffer, name != nullptr ? name : ""); + return true; + } + case '%': addText(buffer, "%"); return true; + default: return false; + } } void formatTime(FormatBuffer& buffer, const char* format, const GuestTm& time) { - for (; *format != '\0'; ++format) { - if (*format == '%') { - bool alternative = false; - Padding padding = Padding::Default; - bool handled = true; - for (bool modifier = true; modifier;) { - modifier = false; - switch (*++format) { - case '\0': - --format; - handled = false; - break; - case 'A': - addText(buffer, time.tm_wday < 0 || time.tm_wday >= 7 ? "?" : WEEKDAY_FULL_NAMES[time.tm_wday]); - break; - case 'a': - addText(buffer, time.tm_wday < 0 || time.tm_wday >= 7 ? "?" : WEEKDAY_NAMES[time.tm_wday]); - break; - case 'B': - addText(buffer, time.tm_mon < 0 || time.tm_mon >= 12 ? "?" : MONTH_FULL_NAMES[time.tm_mon]); - break; - case 'b': - case 'h': - addText(buffer, time.tm_mon < 0 || time.tm_mon >= 12 ? "?" : MONTH_NAMES[time.tm_mon]); - break; - case 'C': - addYear(buffer, time.tm_year, YEAR_BASE, true, false); - break; - case 'c': - formatTime(buffer, "%a %b %e %H:%M:%S %Y", time); - break; - case 'D': - formatTime(buffer, "%m/%d/%y", time); - break; - case 'd': - addNumber(buffer, time.tm_mday, paddingFormat(2, padding, false)); - break; - case 'E': - case 'O': - if (alternative) { - handled = false; - break; - } - alternative = true; - modifier = true; - break; - case 'e': - addNumber(buffer, time.tm_mday, paddingFormat(2, padding, true)); - break; - case 'F': - formatTime(buffer, "%Y-%m-%d", time); - break; - case 'H': - addNumber(buffer, time.tm_hour, paddingFormat(2, padding, false)); - break; - case 'I': - addNumber(buffer, time.tm_hour % 12 != 0 ? time.tm_hour % 12 : 12, paddingFormat(2, padding, false)); - break; - case 'j': - addNumber(buffer, time.tm_yday + 1, paddingFormat(3, padding, false)); - break; - case 'k': - addNumber(buffer, time.tm_hour, paddingFormat(2, padding, true)); - break; - case 'l': - addNumber(buffer, time.tm_hour % 12 != 0 ? time.tm_hour % 12 : 12, paddingFormat(2, padding, true)); - break; - case 'M': - addNumber(buffer, time.tm_min, paddingFormat(2, padding, false)); - break; - case 'm': - addNumber(buffer, time.tm_mon + 1, paddingFormat(2, padding, false)); - break; - case 'n': - addText(buffer, "\n"); - break; - case 'p': - addText(buffer, time.tm_hour >= 12 ? "PM" : "AM"); - break; - case 'R': - formatTime(buffer, "%H:%M", time); - break; - case 'r': - formatTime(buffer, "%I:%M:%S %p", time); - break; - case 'S': - addNumber(buffer, time.tm_sec, paddingFormat(2, padding, false)); - break; - case 's': { - GuestTm copy = time; - char text[24]; - std::snprintf(text, sizeof(text), "%lld", static_cast(libc_mktime_nid_postfix(©))); - addText(buffer, text); - break; - } - case 'T': - formatTime(buffer, "%H:%M:%S", time); - break; - case 't': - addText(buffer, "\t"); - break; - case 'U': - addNumber(buffer, (time.tm_yday + 7 - time.tm_wday) / 7, paddingFormat(2, padding, false)); - break; - case 'u': - addNumber(buffer, time.tm_wday == 0 ? 7 : time.tm_wday, "%d"); - break; - case 'V': - case 'G': - case 'g': - addIsoWeek(buffer, *format, padding, time); - break; - case 'v': - formatTime(buffer, "%e-%b-%Y", time); - break; - case 'W': - addNumber(buffer, (time.tm_yday + 7 - (time.tm_wday != 0 ? time.tm_wday - 1 : 6)) / 7, - paddingFormat(2, padding, false)); - break; - case 'w': - addNumber(buffer, time.tm_wday, "%d"); - break; - case 'X': - formatTime(buffer, "%H:%M:%S", time); - break; - case 'x': - formatTime(buffer, "%m/%d/%y", time); - break; - case 'y': - addYear(buffer, time.tm_year, YEAR_BASE, false, true); - break; - case 'Y': - addYear(buffer, time.tm_year, YEAR_BASE, true, true); - break; - case 'Z': - addText(buffer, guestZoneName(time)); - break; - case 'z': { - if (time.tm_isdst < 0) break; - int difference = static_cast(time.tm_gmtoff); - addText(buffer, difference < 0 ? "-" : "+"); - if (difference < 0) difference = -difference; - difference /= 60; - addNumber(buffer, difference / 60 * 100 + difference % 60, paddingFormat(4, padding, false)); - break; - } - case '+': - formatTime(buffer, "%a %b %e %H:%M:%S %Z %Y", time); - break; - case '-': - case '_': - case '0': - if (padding != Padding::Default) { - handled = false; - break; - } - padding = *format == '-' ? Padding::Less : *format == '_' ? Padding::Space : Padding::Zero; - modifier = true; - break; - default: - handled = false; - break; - } - } - if (handled) continue; + for (; *format != '\0' && !buffer.full; ++format) { + if (*format != '%' || format[1] == '\0') { + addPiece(buffer, format, 1); + continue; + } + ++format; + if (*format == 'E' || *format == 'O') { + if (format[1] == '\0') throw std::invalid_argument("strftime: the format ends after an E or O modifier"); + ++format; + } + if (!addConversion(buffer, *format, time)) { + const char unknown[] = {'%', *format}; + addPiece(buffer, unknown, sizeof(unknown)); } - if (buffer.cursor == buffer.limit) break; - *buffer.cursor++ = *format; } } std::size_t formatGuestTime(char* destination, std::size_t size, const char* format, const GuestTm& time) { - FormatBuffer buffer{destination, destination + size}; + FormatBuffer buffer{destination, destination + size, false}; formatTime(buffer, format == nullptr ? "%c" : format, time); - if (buffer.cursor == buffer.limit) return 0; + if (buffer.full || buffer.cursor == buffer.limit) return 0; *buffer.cursor = '\0'; return static_cast(buffer.cursor - destination); } @@ -515,32 +409,35 @@ char** HostZoneNames() { return ::tzname; #endif } + +char* GuestZoneName(int index) { + static char unnamed[] = " "; + char** names = HostZoneNames(); +#ifdef _WIN32 + const bool daylightSaving = _daylight != 0; +#else + const bool daylightSaving = ::daylight != 0; +#endif + return index == 1 && !daylightSaving ? unnamed : names[index]; +} } extern "C" { -char* tzname_nid_postfix[2] = {HostZoneNames()[0], HostZoneNames()[1]}; +char* tzname_nid_postfix[2] = {GuestZoneName(0), GuestZoneName(1)}; int64_t timezone_nid_postfix = 0; int daylight_nid_postfix = 0; -GuestTm* APS5_VABI localtime_s_nid_postfix(const int64_t* timer, GuestTm* result); -GuestTm* APS5_VABI gmtime_s_nid_postfix(const int64_t* timer, GuestTm* result); -int64_t APS5_VABI libc_time_nid_postfix(int64_t* timer); -double APS5_VABI libc_difftime_nid_postfix(int64_t time1, int64_t time0); -std::size_t APS5_VABI wcsrtombs_nid_postfix(char* destination, const std::uint16_t** source, std::size_t capacity, void* state); -std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const char** source, std::size_t capacity, void* state); -int APS5_VABI ___mb_cur_max_nid_postfix(); - void APS5_VABI tzset_nid_postfix(void) { const ZoneScope zone; - char** names = HostZoneNames(); - tzname_nid_postfix[0] = names[0]; - tzname_nid_postfix[1] = names[1]; + tzname_nid_postfix[0] = GuestZoneName(0); + tzname_nid_postfix[1] = GuestZoneName(1); const int64_t now = libc_time_nid_postfix(nullptr); GuestTm universal{}; GuestTm local{}; gmtime_s_nid_postfix(&now, &universal); localtime_s_nid_postfix(&now, &local); + daylight_nid_postfix = local.tm_isdst >= 0 ? 1 : 0; timezone_nid_postfix = static_cast(libc_difftime_nid_postfix(libc_mktime_nid_postfix(&universal), libc_mktime_nid_postfix(&local))); } @@ -688,6 +585,7 @@ int64_t APS5_VABI mktime_nid_postfix(GuestTm* timeptr) { size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr) { const ZoneScope zone; tzset_nid_postfix(); + return formatGuestTime(str, count, format, *timeptr); } @@ -698,12 +596,9 @@ char* APS5_VABI asctime_nid_postfix(const GuestTm* timeptr) { std::memcpy(result, "??? ??? ?? ??:??:?? ????\n", sizeof("??? ??? ?? ??:??:?? ????\n")); return result; } - const char* weekday = timeptr->tm_wday >= 0 && timeptr->tm_wday < 7 ? WEEKDAY_NAMES[timeptr->tm_wday] : "???"; - const char* month = timeptr->tm_mon >= 0 && timeptr->tm_mon < 12 ? MONTH_NAMES[timeptr->tm_mon] : "???"; - char year[16]; - formatGuestTime(year, sizeof(year), "%Y", *timeptr); - std::snprintf(result, sizeof(result), std::strlen(year) <= 4 ? "%s %s%3d %.2d:%.2d:%.2d %-4s\n" : "%s %s%3d %.2d:%.2d:%.2d %s\n", - weekday, month, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, year); + const std::int64_t year = static_cast(timeptr->tm_year) + YEAR_BASE; + std::snprintf(result, sizeof(result), "%s %s%3d %02d:%02d:%02d %04d\n", tableName(timeptr->tm_wday, WEEKDAY_NAMES, WEEKDAY_NAMES, 7), tableName(timeptr->tm_mon, MONTH_NAMES, MONTH_NAMES, 12), + clampedField(timeptr->tm_mday, 100), clampedField(timeptr->tm_hour, 100), clampedField(timeptr->tm_min, 100), clampedField(timeptr->tm_sec, 100), clampedField(year, 10000)); return result; } @@ -725,6 +620,7 @@ size_t APS5_VABI wcsftime_nid_postfix(std::uint16_t* str, size_t count, const st constexpr auto Failed = static_cast(-1); if (format == nullptr) throw std::invalid_argument("wcsftime: null format"); if (timeptr == nullptr) throw std::invalid_argument("wcsftime: null time"); + if (str == nullptr) return 0; const std::uint16_t* formatCursor = format; const std::size_t formatLength = wcsrtombs_nid_postfix(nullptr, &formatCursor, 0, nullptr); if (formatLength == Failed) return 0; @@ -734,7 +630,8 @@ size_t APS5_VABI wcsftime_nid_postfix(std::uint16_t* str, size_t count, const st return 0; } wcsrtombs_nid_postfix(narrowFormat.get(), &formatCursor, formatLength + 1, nullptr); - const auto maxCharacterBytes = static_cast(___mb_cur_max_nid_postfix()); + + const auto maxCharacterBytes = static_cast(_Getmbcurmax_nid_postfix()); if (std::numeric_limits::max() / maxCharacterBytes <= count) { errno = GuestEinval; return 0; @@ -745,13 +642,15 @@ size_t APS5_VABI wcsftime_nid_postfix(std::uint16_t* str, size_t count, const st return 0; } const int callerError = errno; - if (strftime_nid_postfix(narrow.get(), count, narrowFormat.get(), timeptr) == 0) { + if (strftime_nid_postfix(narrow.get(), count * maxCharacterBytes, narrowFormat.get(), timeptr) == 0) { errno = callerError; return 0; } const char* narrowCursor = narrow.get(); - const std::size_t length = mbsrtowcs_nid_postfix(str, &narrowCursor, count, nullptr); - if (length == static_cast(-2) || length == Failed || narrowCursor != nullptr) return 0; + const std::size_t length = mbsrtowcs_nid_postfix(nullptr, &narrowCursor, 0, nullptr); + if (length == Failed || length >= count) return 0; + mbsrtowcs_nid_postfix(str, &narrowCursor, count, nullptr); + return length; } diff --git a/core/libs/prx/libc/src/TimeExtras.cpp b/core/libs/prx/libc/src/TimeExtras.cpp index ee2a225b25..8b4baa0823 100644 --- a/core/libs/prx/libc/src/TimeExtras.cpp +++ b/core/libs/prx/libc/src/TimeExtras.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/SonyCrt.hpp" +#include "prx/libc/include/Api.hpp" #include #include "prx/libc/include/General.hpp" @@ -5,11 +7,6 @@ namespace { -struct Xtime { - std::int64_t sec; - std::int64_t nsec; -}; - constexpr int timeUtc = 1; constexpr std::int64_t nanosPerSecond = 1000000000; constexpr std::int64_t secondsPerDay = 86400; @@ -35,10 +32,6 @@ std::int64_t wrappingAdd(std::int64_t left, std::int64_t right) { extern "C" { -GuestTm* APS5_VABI gmtime_s_nid_postfix(const std::int64_t* timer, GuestTm* result); -GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t* timer, GuestTm* result); -std::int64_t APS5_VABI _Xtime_get_ticks_nid_postfix(); - GuestTm* APS5_VABI gmtime_r_nid_postfix(const std::int64_t* timer, GuestTm* result) { return gmtime_s_nid_postfix(timer, result); } @@ -74,6 +67,7 @@ Xtime APS5_VABI _Xtime_to_ts_nid_postfix(const Xtime* time) { Xtime APS5_VABI _Xtime_diff_to_ts_nid_postfix(const Xtime* target) { Xtime now{}; xtime_get_nid_postfix(&now, timeUtc); + std::int64_t seconds = wrappingAdd(target->sec, target->nsec / nanosPerSecond); std::int64_t nanos = target->nsec % nanosPerSecond; if (nanos < 0) { diff --git a/core/libs/prx/libc/src/VectorAlgorithms.cpp b/core/libs/prx/libc/src/VectorAlgorithms.cpp index 169c0a23d8..80b1a8f99c 100644 --- a/core/libs/prx/libc/src/VectorAlgorithms.cpp +++ b/core/libs/prx/libc/src/VectorAlgorithms.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -7,11 +8,6 @@ namespace { -struct MinMaxPointers { - const void* minimum; - const void* maximum; -}; - template TValue Read(const unsigned char* address) { TValue value; diff --git a/core/libs/prx/libc/src/WideConversion.cpp b/core/libs/prx/libc/src/WideConversion.cpp index 09fb1fd1c9..109dae7f16 100644 --- a/core/libs/prx/libc/src/WideConversion.cpp +++ b/core/libs/prx/libc/src/WideConversion.cpp @@ -1,6 +1,9 @@ +#include "prx/libc/include/WindowsFormatting.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/SonyCrt.hpp" #include +#include #include #include #include @@ -19,25 +22,22 @@ extern "C" void* APS5_VABI set_constraint_handler_s_nid_postfix(void* handler) { extern "C" std::size_t APS5_VABI wcstombs_nid_postfix(char* destination, const std::uint16_t* source, std::size_t capacity) { std::size_t count = 0; - while (!destination || count < capacity) { - const auto value = source[count]; - if (value > 255) { - errno = 86; - return static_cast(-1); + for (;; ++source) { + char bytes[MB_LEN_MAX]; + const std::size_t length = wcrtomb_nid_postfix(bytes, *source, nullptr); + for (std::size_t index = 0; index < length; ++index) { + if (destination && count == capacity) return count; + if (destination) destination[count] = bytes[index]; + if (bytes[index] == '\0') return count; + ++count; } - if (destination) destination[count] = static_cast(value); - if (value == 0) break; - ++count; } - return count; } extern "C" int APS5_VABI wcscpy_s_nid_postfix(std::uint16_t* dest, std::size_t size, const std::uint16_t* src) { return SonyWcscpyS(dest, size, src); } -extern "C" int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t capacity); - namespace { std::size_t ReadErrorMessage(int error, char (&message)[128]) { diff --git a/core/libs/prx/libc/src/exception/ArraySupport.cpp b/core/libs/prx/libc/src/exception/ArraySupport.cpp index 6b5302ac9e..961f834282 100644 --- a/core/libs/prx/libc/src/exception/ArraySupport.cpp +++ b/core/libs/prx/libc/src/exception/ArraySupport.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include @@ -15,6 +16,7 @@ void APS5_VABI __cxa_vec_ctor_nid_postfix(void* array, std::size_t count, std::s for (; complete < count; ++complete) construct(static_cast(array) + complete * size); } catch (...) { __cxa_vec_cleanup_nid_postfix(array, complete, size, destroy); + throw; } } @@ -26,6 +28,7 @@ void APS5_VABI __cxa_vec_cctor_nid_postfix(void* destination, void* source, std: copy(static_cast(destination) + complete * size, static_cast(source) + complete * size); } catch (...) { __cxa_vec_cleanup_nid_postfix(destination, complete, size, destroy); + throw; } } @@ -37,6 +40,7 @@ void APS5_VABI __cxa_vec_dtor_nid_postfix(void* array, std::size_t count, std::s } catch (...) { if (unwinding) LibcException::Terminate(); __cxa_vec_cleanup_nid_postfix(array, count, size, destroy); + throw; } } diff --git a/core/libs/prx/libc/src/exception/ExceptionPointer.cpp b/core/libs/prx/libc/src/exception/ExceptionPointer.cpp index 447d85ac45..6b808a870c 100644 --- a/core/libs/prx/libc/src/exception/ExceptionPointer.cpp +++ b/core/libs/prx/libc/src/exception/ExceptionPointer.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #ifndef _LIBCPP_VERSION @@ -62,6 +63,7 @@ exception_ptr current_exception() noexcept { void rethrow_exception(exception_ptr exception) { __cxa_rethrow_primary_exception_nid_postfix(exception._M_exception_object); + LibcException::Terminate(); } @@ -73,10 +75,12 @@ exception_ptr::exception_ptr(void* exception) noexcept : _M_exception_object(exc void exception_ptr::_M_addref() noexcept { __cxa_increment_exception_refcount_nid_postfix(_M_exception_object); + } void exception_ptr::_M_release() noexcept { __cxa_decrement_exception_refcount_nid_postfix(_M_exception_object); + _M_exception_object = nullptr; } @@ -94,22 +98,15 @@ const type_info* exception_ptr::__cxa_exception_type() const noexcept { #else -namespace { -struct GuestExceptionPointer { - void* object {}; - explicit GuestExceptionPointer(void* primary) noexcept : object(primary) {} - GuestExceptionPointer(const GuestExceptionPointer&) = delete; - ~GuestExceptionPointer() {} -}; -} - extern "C" { void APS5_VABI _ZNSt15__exception_ptr13exception_ptr9_M_addrefEv_nid_postfix(GuestExceptionPointer* self) noexcept { __cxa_increment_exception_refcount_nid_postfix(self->object); + } void APS5_VABI _ZNSt15__exception_ptr13exception_ptr10_M_releaseEv_nid_postfix(GuestExceptionPointer* self) noexcept { __cxa_decrement_exception_refcount_nid_postfix(self->object); + self->object = nullptr; } @@ -120,10 +117,12 @@ void* APS5_VABI _ZNKSt15__exception_ptr13exception_ptr6_M_getEv_nid_postfix(cons void APS5_VABI _ZNSt15__exception_ptr13exception_ptrC1EPv_nid_postfix(GuestExceptionPointer* self, void* exception) noexcept { self->object = exception; __cxa_increment_exception_refcount_nid_postfix(exception); + } void APS5_VABI _ZNSt15__exception_ptr13exception_ptrC2EPv_nid_postfix(GuestExceptionPointer* self, void* exception) noexcept { _ZNSt15__exception_ptr13exception_ptrC1EPv_nid_postfix(self, exception); + } const std::type_info* APS5_VABI _ZNKSt15__exception_ptr13exception_ptr20__cxa_exception_typeEv_nid_postfix(const GuestExceptionPointer* self) noexcept { @@ -136,6 +135,7 @@ GuestExceptionPointer APS5_VABI _ZSt17current_exceptionv_nid_postfix() noexcept [[noreturn]] void APS5_VABI _ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE_nid_postfix(GuestExceptionPointer& exception) { __cxa_rethrow_primary_exception_nid_postfix(exception.object); + LibcException::Terminate(); } } diff --git a/core/libs/prx/libc/src/exception/GuardSupport.cpp b/core/libs/prx/libc/src/exception/GuardSupport.cpp index 7eaab8684f..09f9cbf02d 100644 --- a/core/libs/prx/libc/src/exception/GuardSupport.cpp +++ b/core/libs/prx/libc/src/exception/GuardSupport.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include diff --git a/core/libs/prx/libc/src/exception/Personality.cpp b/core/libs/prx/libc/src/exception/Personality.cpp index 87826a2dc8..d4ed0a681f 100644 --- a/core/libs/prx/libc/src/exception/Personality.cpp +++ b/core/libs/prx/libc/src/exception/Personality.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/exceptions/Unwind.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" extern "C" _Unwind_Reason_Code APS5_VABI __gxx_personality_v0_nid_postfix( diff --git a/core/libs/prx/libc/src/exception/Runtime.cpp b/core/libs/prx/libc/src/exception/Runtime.cpp index e790d1a2db..faba094aaf 100644 --- a/core/libs/prx/libc/src/exception/Runtime.cpp +++ b/core/libs/prx/libc/src/exception/Runtime.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/exceptions/Unwind.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include #include @@ -117,7 +119,7 @@ void Release(void* object) { } } -void APS5_VABI Cleanup(_Unwind_Reason_Code reason, _Unwind_Exception* exception) { +static void APS5_VABI Cleanup(_Unwind_Reason_Code reason, _Unwind_Exception* exception) { if (reason != _URC_FOREIGN_EXCEPTION_CAUGHT) Terminate(); auto* header = FromUnwind(exception); if (exception->exception_class == DependentClass) { @@ -200,12 +202,15 @@ void VisitBases(const std::type_info* type, BaseLocation location, bool publicPa void Bases(const std::type_info* type, void* object, Search& search) { std::uintptr_t identity = 0; + bool reachedPublicly = false; auto visitor = [&](const std::type_info* candidate, BaseLocation location, bool publicPath) { - if (!publicPath || !Equal(candidate, search.wanted)) return; - if (!search.count) { search.found = location.object; identity = location.identity; search.count = 1; } + if (!Equal(candidate, search.wanted)) return; + if (!search.count) { search.found = location.object; identity = location.identity; search.count = 1; reachedPublicly = publicPath; } else if (search.found != location.object || identity != location.identity) search.count = 2; + else if (publicPath) reachedPublicly = true; }; VisitBases(type, {object, 0}, true, visitor); + if (search.count == 1 && !reachedPublicly) search.count = 0; } bool PointerMatch(const __cxxabiv1::__pbase_type_info* caught, const __cxxabiv1::__pbase_type_info* thrown, @@ -285,8 +290,10 @@ void APS5_VABI __cxa_free_exception_nid_postfix(void* object) { header->unwind.exception_cleanup = Cleanup; ++globals.uncaught; _Unwind_RaiseException_nid_postfix(&header->unwind); + RawLog("[libc] unhandled exception '%s' thrown from %p\n", type ? type->name() : "?", throwSite); __cxa_begin_catch_nid_postfix(&header->unwind); + InvokeTerminate(header->terminate); } @@ -343,6 +350,7 @@ void APS5_VABI __cxa_end_catch_nid_postfix() { globals.caught = header->next; bool foreign = !Native(header->unwind.exception_class); _Unwind_DeleteException_nid_postfix(UnwindOf(header)); + if (foreign) DeleteForeignHeader(header); } } @@ -355,6 +363,7 @@ void APS5_VABI __cxa_end_catch_nid_postfix() { if (Native(header->unwind.exception_class)) ++globals.uncaught; _Unwind_RaiseException_nid_postfix(UnwindOf(header)); __cxa_begin_catch_nid_postfix(UnwindOf(header)); + InvokeTerminate(header->terminate ? header->terminate : terminateHandler.load()); } @@ -373,7 +382,7 @@ unsigned APS5_VABI __cxa_uncaught_exceptions_nid_postfix() { return LibcExceptio bool APS5_VABI _ZSt18uncaught_exceptionv_nid_postfix() { return __cxa_uncaught_exception_nid_postfix(); } int APS5_VABI _ZSt19uncaught_exceptionsv_nid_postfix() { return static_cast(__cxa_uncaught_exceptions_nid_postfix()); } [[noreturn]] void APS5_VABI _ZSt9terminatev_nid_postfix() { LibcException::Terminate(); } -using LibcTerminateHandler = void(*)(); + LibcTerminateHandler APS5_VABI _ZSt13set_terminatePFvvE_nid_postfix(LibcTerminateHandler handler) { return LibcException::terminateHandler.exchange(handler ? handler : std::abort); } @@ -388,6 +397,7 @@ void* APS5_VABI __cxa_current_primary_exception_nid_postfix() { if (!globals.caught || !Native(globals.caught->unwind.exception_class)) return nullptr; void* object = Primary(globals.caught) + 1; __cxa_increment_exception_refcount_nid_postfix(object); + return object; } void* APS5_VABI __cxa_allocate_dependent_exception_nid_postfix() { @@ -410,9 +420,11 @@ void APS5_VABI __cxa_rethrow_primary_exception_nid_postfix(void* object) { header->unwind.exception_class = DependentClass; header->unwind.exception_cleanup = Cleanup; __cxa_increment_exception_refcount_nid_postfix(object); + ++globals.uncaught; _Unwind_RaiseException_nid_postfix(&header->unwind); __cxa_begin_catch_nid_postfix(&header->unwind); + InvokeTerminate(header->terminate); } @@ -450,6 +462,7 @@ void* APS5_VABI __dynamic_cast_nid_postfix(const void* source, const __cxxabiv1: [[noreturn]] void APS5_VABI __cxa_call_terminate_nid_postfix(void* exception) { __cxa_begin_catch_nid_postfix(exception); + LibcException::Terminate(); } [[noreturn]] void APS5_VABI __cxa_pure_virtual_nid_postfix() { LibcException::Terminate(); } diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index 31feeb3d8e..83ffdb0b78 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include "prx/libc/include/GuestLocale.hpp" #include @@ -7,11 +8,9 @@ #include #include -extern "C" void* APS5_VABI _Znwm_nid_postfix(std::size_t size); - namespace LibcException { struct TypeRecord { const void* const* vtable; const char* name; const TypeRecord* base; }; -struct ExceptionObject { const void* vtable; const char* message; }; + struct ExceptionVtable { std::ptrdiff_t offset; const TypeRecord* type; @@ -128,10 +127,8 @@ ExceptionObject* Assign(ExceptionObject* object, const ExceptionObject* source) __cxa_throw_nid_postfix(object, reinterpret_cast(const_cast(table.type)), [](void* p) { DestroyPlain(static_cast(p)); }); } -struct CategoryObject { const void* vtable; }; -struct ErrorCode { int value; const CategoryObject* category; }; -struct SystemErrorObject { ExceptionObject base; ErrorCode code; }; -static_assert(sizeof(ErrorCode) == 16 && sizeof(SystemErrorObject) == 32 && offsetof(SystemErrorObject, code) == 16); +static_assert(sizeof(ErrorCode) == 16 && offsetof(ErrorCode, value) == 4 && offsetof(ErrorCode, category) == 8); +static_assert(sizeof(SystemErrorObject) == 32 && offsetof(SystemErrorObject, code) == 16); struct CppError { int value; const char* message; }; static const char* GenericErrorMessage(int value) { @@ -216,13 +213,15 @@ static const char* GenericErrorMessage(int value) { const TypeRecord ErrorCategoryType {ClassTypeVtable + 2, "St14error_category", nullptr}; const TypeRecord GenericCategoryType {SingleTypeVtable + 2, "St23_Generic_error_category", &ErrorCategoryType}; -static void APS5_VABI GenericCategoryDestroy_nid_no_patch(CategoryObject*) {} -static void APS5_VABI GenericCategoryDelete_nid_no_patch(CategoryObject*) { - throw std::invalid_argument("std::_Generic_error_category: the generic category object is not heap allocated"); +const TypeRecord SystemCategoryType {SingleTypeVtable + 2, "St22_System_error_category", &ErrorCategoryType}; +static void APS5_VABI CategoryDestroy_nid_no_patch(CategoryObject*) {} +static void APS5_VABI CategoryDelete_nid_no_patch(CategoryObject*) { + throw std::invalid_argument("std::error_category: the generic and system category objects are not heap allocated"); } static const char* APS5_VABI GenericCategoryName_nid_no_patch(const CategoryObject*) { return "generic"; } -static GuestLocale::String* APS5_VABI GenericCategoryMessage_nid_no_patch(GuestLocale::String* result, const CategoryObject*, int value) { - if (!result) throw std::invalid_argument("std::_Generic_error_category::message: null result"); +static const char* APS5_VABI SystemCategoryName_nid_no_patch(const CategoryObject*) { return "system"; } +static GuestLocale::String* APS5_VABI CategoryMessage_nid_no_patch(GuestLocale::String* result, const CategoryObject*, int value) { + if (!result) throw std::invalid_argument("std::error_category::message: null result"); const char* message = GenericErrorMessage(value); if (!message) message = "unknown error"; const auto length = std::strlen(message); @@ -235,22 +234,61 @@ static GuestLocale::String* APS5_VABI GenericCategoryMessage_nid_no_patch(GuestL result->capacity = capacity; return result; } -static ErrorCode APS5_VABI GenericCategoryCondition_nid_no_patch(const CategoryObject* self, int value) { return {value, self}; } -static bool APS5_VABI GenericCategoryEquivalentCondition_nid_no_patch(const CategoryObject* self, int value, const ErrorCode* condition) { - return condition->category == self && condition->value == value; +static ErrorCode APS5_VABI CategoryCondition_nid_no_patch(const CategoryObject* self, int value) { return {0, value, self}; } +static bool APS5_VABI CategoryEquivalentCondition_nid_no_patch(const CategoryObject* self, int value, const ErrorCode* condition) { + using Condition = ErrorCode (APS5_VABI *)(const CategoryObject*, int); +#ifdef __APPLE__ + const auto mapped = reinterpret_cast(const_cast(static_cast(self->vtable)[4]))(self, value); +#else + const auto mapped = reinterpret_cast(static_cast(self->vtable)[4])(self, value); +#endif + return condition->category == mapped.category && condition->value == mapped.value; } -static bool APS5_VABI GenericCategoryEquivalentCode_nid_no_patch(const CategoryObject* self, const ErrorCode* code, int value) { +static bool APS5_VABI CategoryEquivalentCode_nid_no_patch(const CategoryObject* self, const ErrorCode* code, int value) { return code->category == self && code->value == value; } const void* const GenericCategoryVtable[] { nullptr, &GenericCategoryType, - reinterpret_cast(GenericCategoryDestroy_nid_no_patch), reinterpret_cast(GenericCategoryDelete_nid_no_patch), - reinterpret_cast(GenericCategoryName_nid_no_patch), reinterpret_cast(GenericCategoryMessage_nid_no_patch), - reinterpret_cast(GenericCategoryCondition_nid_no_patch), - reinterpret_cast(GenericCategoryEquivalentCondition_nid_no_patch), - reinterpret_cast(GenericCategoryEquivalentCode_nid_no_patch) + reinterpret_cast(CategoryDestroy_nid_no_patch), reinterpret_cast(CategoryDelete_nid_no_patch), + reinterpret_cast(GenericCategoryName_nid_no_patch), reinterpret_cast(CategoryMessage_nid_no_patch), + reinterpret_cast(CategoryCondition_nid_no_patch), + reinterpret_cast(CategoryEquivalentCondition_nid_no_patch), + reinterpret_cast(CategoryEquivalentCode_nid_no_patch) }; const CategoryObject GenericCategory {GenericCategoryVtable + 2}; +static ErrorCode APS5_VABI SystemCategoryCondition_nid_no_patch(const CategoryObject* self, int value) { + return {0, value, GenericErrorMessage(value) ? &GenericCategory : self}; +} +const void* const SystemCategoryVtable[] { + nullptr, &SystemCategoryType, + reinterpret_cast(CategoryDestroy_nid_no_patch), reinterpret_cast(CategoryDelete_nid_no_patch), + reinterpret_cast(SystemCategoryName_nid_no_patch), reinterpret_cast(CategoryMessage_nid_no_patch), + reinterpret_cast(SystemCategoryCondition_nid_no_patch), + reinterpret_cast(CategoryEquivalentCondition_nid_no_patch), + reinterpret_cast(CategoryEquivalentCode_nid_no_patch) +}; +const CategoryObject SystemCategory {SystemCategoryVtable + 2}; +[[noreturn]] void ThrowCppError(int code, const ExceptionVtable& table) { + static constexpr CppError errors[] { + {16, "device or resource busy"}, + {22, "invalid argument"}, + {3, "no such process"}, + {12, "not enough memory"}, + {1, "operation not permitted"}, + {11, "resource deadlock would occur"}, + {35, "resource unavailable try again"} + }; + if (code < 0 || code >= static_cast(std::size(errors))) + throw std::out_of_range("_Throw_Cpp_error: " + std::to_string(code) + " is not a _Cpp_error code"); + const auto& error = errors[code]; + char message[128]; + std::snprintf(message, sizeof(message), "%s: %s", error.message, GenericErrorMessage(error.value)); + auto* object = static_cast(__cxa_allocate_exception_nid_postfix(sizeof(SystemErrorObject))); + Construct(&object->base, table, message); + object->code = {0, error.value, &GenericCategory}; + __cxa_throw_nid_postfix(object, reinterpret_cast(const_cast(table.type)), + reinterpret_cast(DestroyGuestMessage_nid_no_patch)); +} } extern "C" { @@ -260,9 +298,12 @@ const char* APS5_VABI _ZSt13_Syserror_mapi_nid_postfix(int value) { const LibcException::CategoryObject* APS5_VABI _ZSt16generic_categoryv_nid_postfix() { return &LibcException::GenericCategory; } +const LibcException::CategoryObject* APS5_VABI _ZSt15system_categoryv_nid_postfix() { + return &LibcException::SystemCategory; +} GuestLocale::String* APS5_VABI _ZNKSt23_Generic_error_category7messageEi_nid_postfix( GuestLocale::String* result, const LibcException::CategoryObject* self, int value) { - return LibcException::GenericCategoryMessage_nid_no_patch(result, self, value); + return LibcException::CategoryMessage_nid_no_patch(result, self, value); } LibcException::TypeRecord _ZTISt8ios_base_nid_postfix {LibcException::ClassTypeVtable + 2, "St8ios_base", nullptr}; LibcException::TypeRecord _ZTISt9exception_nid_postfix {LibcException::ClassTypeVtable + 2, "St9exception", nullptr}; @@ -291,6 +332,10 @@ void APS5_VABI _ZNSt8bad_castD0Ev_nid_postfix(LibcException::ExceptionObject* se const char* APS5_VABI _ZNKSt8bad_cast4whatEv_nid_postfix(const LibcException::ExceptionObject* self) { return LibcException::PlainWhat(self); } void APS5_VABI _ZNSt8bad_castC1Ev_nid_postfix(LibcException::ExceptionObject* self) { self->vtable = &_ZTVSt8bad_cast_nid_postfix.destroy; } void APS5_VABI _ZNSt8bad_castC2Ev_nid_postfix(LibcException::ExceptionObject* self) { self->vtable = &_ZTVSt8bad_cast_nid_postfix.destroy; } +[[noreturn]] void APS5_VABI _ZNKSt8bad_cast8_DoraiseEv_nid_postfix(const LibcException::ExceptionObject* self) { + if (!self) throw std::invalid_argument("exception object is null"); + LibcException::ThrowPlain(_ZTVSt8bad_cast_nid_postfix); +} LibcException::TypeRecord _ZTISt10bad_typeid_nid_postfix {LibcException::SingleTypeVtable + 2, "St10bad_typeid", &_ZTISt9exception_nid_postfix}; LibcException::ExceptionVtable _ZTVSt10bad_typeid_nid_postfix {0, &_ZTISt10bad_typeid_nid_postfix, LibcException::GuestDestroyPlain_nid_no_patch, LibcException::GuestDeletePlain_nid_no_patch, LibcException::GuestPlainWhat_nid_no_patch}; @@ -463,6 +508,10 @@ LibcException::TypeRecord _ZTISt14overflow_error_nid_postfix {LibcException::Sin LibcException::ExceptionVtable _ZTVSt14overflow_error_nid_postfix {0, &_ZTISt14overflow_error_nid_postfix, LibcException::GuestDestroyMessage_nid_no_patch, LibcException::GuestDeleteMessage_nid_no_patch, LibcException::GuestMessageWhat_nid_no_patch}; void APS5_VABI _ZNSt14overflow_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +LibcException::TypeRecord _ZTISt15underflow_error_nid_postfix {LibcException::SingleTypeVtable + 2, "St15underflow_error", &_ZTISt13runtime_error_nid_postfix}; +LibcException::ExceptionVtable _ZTVSt15underflow_error_nid_postfix {0, &_ZTISt15underflow_error_nid_postfix, LibcException::GuestDestroyMessage_nid_no_patch, LibcException::GuestDeleteMessage_nid_no_patch, LibcException::GuestMessageWhat_nid_no_patch}; +void APS5_VABI _ZNSt15underflow_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } + LibcException::TypeRecord _ZTINSt8ios_base7failureE_nid_postfix {LibcException::SingleTypeVtable + 2, "NSt8ios_base7failureE", &_ZTISt12system_error_nid_postfix}; LibcException::ExceptionVtable _ZTVNSt8ios_base7failureE_nid_postfix {0, &_ZTINSt8ios_base7failureE_nid_postfix, LibcException::GuestDestroyMessage_nid_no_patch, LibcException::GuestDeleteMessage_nid_no_patch, LibcException::GuestMessageWhat_nid_no_patch}; void APS5_VABI _ZNSt8ios_base7failureD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } @@ -480,7 +529,6 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep [[noreturn]] void APS5_VABI _ZSt15_Xruntime_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt13runtime_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt13_Xrange_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt11range_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt19_Xbad_function_callv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt17bad_function_call_nid_postfix); } -[[noreturn]] void APS5_VABI _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt17bad_function_call_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt19_Throw_bad_weak_ptrv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt12bad_weak_ptr_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt26__throw_bad_variant_accessv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt18bad_variant_access_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt26_Throw_bad_optional_accessv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt19bad_optional_access_nid_postfix); } @@ -493,24 +541,100 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep reinterpret_cast(LibcException::DestroyGuestMessage_nid_no_patch)); } [[noreturn]] void ThrowCppError_nid_no_patch(int code) { - static constexpr LibcException::CppError errors[] { - {16, "device or resource busy"}, - {22, "invalid argument"}, - {3, "no such process"}, - {12, "not enough memory"}, - {1, "operation not permitted"}, - {11, "resource deadlock would occur"}, - {35, "resource unavailable try again"} - }; - if (code < 0 || code >= static_cast(std::size(errors))) - throw std::out_of_range("_Throw_Cpp_error: " + std::to_string(code) + " is not a _Cpp_error code"); - const auto& error = errors[code]; - char message[128]; - std::snprintf(message, sizeof(message), "%s: %s", error.message, LibcException::GenericErrorMessage(error.value)); - auto* object = static_cast(__cxa_allocate_exception_nid_postfix(sizeof(LibcException::SystemErrorObject))); - LibcException::Construct(&object->base, _ZTVSt12system_error_nid_postfix, message); - object->code = {error.value, &LibcException::GenericCategory}; - __cxa_throw_nid_postfix(object, reinterpret_cast(&_ZTISt12system_error_nid_postfix), - reinterpret_cast(LibcException::DestroyGuestMessage_nid_no_patch)); + LibcException::ThrowCppError(code, _ZTVSt12system_error_nid_postfix); +} +} + +namespace LibcException::SceV2 { +static const char* APS5_VABI BadOptionalAccessWhat_nid_no_patch(const ExceptionObject*) { return "Bad optional access"; } +static const char* APS5_VABI BadWeakPtrWhat_nid_no_patch(const ExceptionObject*) { return "bad_weak_ptr"; } +static const char* APS5_VABI BadVariantAccessWhat_nid_no_patch(const ExceptionObject*) { return "bad variant access"; } +static const char* APS5_VABI BadFunctionCallWhat_nid_no_patch(const ExceptionObject*) { return "bad function call"; } +} + +extern "C" { +LibcException::TypeRecord _ZTINSt13__sce_cxxvers16_Error_exceptionE_nid_postfix {LibcException::SingleTypeVtable + 2, "NSt13__sce_cxxvers16_Error_exceptionE", &_ZTISt9exception_nid_postfix}; +} + +namespace LibcException::SceV2 { +constexpr ExceptionVtable MessageTable(const TypeRecord& type) { + return {0, &type, GuestDestroyMessage_nid_no_patch, GuestDeleteMessage_nid_no_patch, GuestMessageWhat_nid_no_patch}; +} +constexpr ExceptionVtable PlainTable(const TypeRecord& type, const char* (APS5_VABI *what)(const ExceptionObject*)) { + return {0, &type, GuestDestroyPlain_nid_no_patch, GuestDeletePlain_nid_no_patch, what}; +} +const ExceptionVtable ErrorException = MessageTable(_ZTINSt13__sce_cxxvers16_Error_exceptionE_nid_postfix); +const TypeRecord LogicErrorType {SingleTypeVtable + 2, "NSt8__sce_v211logic_errorE", &_ZTINSt13__sce_cxxvers16_Error_exceptionE_nid_postfix}; +const TypeRecord LengthErrorType {SingleTypeVtable + 2, "NSt8__sce_v212length_errorE", &LogicErrorType}; +const TypeRecord OutOfRangeType {SingleTypeVtable + 2, "NSt8__sce_v212out_of_rangeE", &LogicErrorType}; +const TypeRecord InvalidArgumentType {SingleTypeVtable + 2, "NSt8__sce_v216invalid_argumentE", &LogicErrorType}; +const TypeRecord RuntimeErrorType {SingleTypeVtable + 2, "NSt8__sce_v213runtime_errorE", &_ZTINSt13__sce_cxxvers16_Error_exceptionE_nid_postfix}; +const TypeRecord OverflowErrorType {SingleTypeVtable + 2, "NSt8__sce_v214overflow_errorE", &RuntimeErrorType}; +const TypeRecord RangeErrorType {SingleTypeVtable + 2, "NSt8__sce_v211range_errorE", &RuntimeErrorType}; +const TypeRecord SystemErrorBaseType {SingleTypeVtable + 2, "NSt8__sce_v213_System_errorE", &RuntimeErrorType}; +const TypeRecord SystemErrorType {SingleTypeVtable + 2, "NSt8__sce_v212system_errorE", &SystemErrorBaseType}; +const TypeRecord IosFailureType {SingleTypeVtable + 2, "NSt8__sce_v28ios_base7failureE", &SystemErrorType}; +const TypeRecord BadOptionalAccessType {SingleTypeVtable + 2, "NSt8__sce_v219bad_optional_accessE", &_ZTISt9exception_nid_postfix}; +const TypeRecord BadWeakPtrType {SingleTypeVtable + 2, "NSt8__sce_v212bad_weak_ptrE", &_ZTISt9exception_nid_postfix}; +const TypeRecord BadVariantAccessType {SingleTypeVtable + 2, "NSt8__sce_v218bad_variant_accessE", &_ZTISt9exception_nid_postfix}; +const TypeRecord BadFunctionCallType {SingleTypeVtable + 2, "NSt8__sce_v217bad_function_callE", &_ZTISt9exception_nid_postfix}; +const ExceptionVtable LengthError = MessageTable(LengthErrorType); +const ExceptionVtable OutOfRange = MessageTable(OutOfRangeType); +const ExceptionVtable InvalidArgument = MessageTable(InvalidArgumentType); +const ExceptionVtable RuntimeError = MessageTable(RuntimeErrorType); +const ExceptionVtable OverflowError = MessageTable(OverflowErrorType); +const ExceptionVtable RangeError = MessageTable(RangeErrorType); +const ExceptionVtable SystemError = MessageTable(SystemErrorType); +const ExceptionVtable IosFailure = MessageTable(IosFailureType); +const ExceptionVtable BadOptionalAccess = PlainTable(BadOptionalAccessType, BadOptionalAccessWhat_nid_no_patch); +const ExceptionVtable BadWeakPtr = PlainTable(BadWeakPtrType, BadWeakPtrWhat_nid_no_patch); +const ExceptionVtable BadVariantAccess = PlainTable(BadVariantAccessType, BadVariantAccessWhat_nid_no_patch); +const ExceptionVtable BadFunctionCall = PlainTable(BadFunctionCallType, BadFunctionCallWhat_nid_no_patch); + +[[noreturn]] void ThrowCopy(const ExceptionObject* source, const ExceptionVtable& table) { + auto* object = static_cast(__cxa_allocate_exception_nid_postfix(sizeof(ExceptionObject))); + Copy(object, source, table); + __cxa_throw_nid_postfix(object, reinterpret_cast(const_cast(table.type)), + reinterpret_cast(DestroyGuestMessage_nid_no_patch)); +} +[[noreturn]] void ThrowSystemCopy(const SystemErrorObject* source, const ExceptionVtable& table) { + auto* object = static_cast(__cxa_allocate_exception_nid_postfix(sizeof(SystemErrorObject))); + Copy(&object->base, &source->base, table); + object->code = source->code; + __cxa_throw_nid_postfix(object, reinterpret_cast(const_cast(table.type)), + reinterpret_cast(DestroyGuestMessage_nid_no_patch)); +} +} + +extern "C" { +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionC1EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message) { LibcException::Construct(self, LibcException::SceV2::ErrorException, message); } +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionC2EPKc_nid_postfix(LibcException::ExceptionObject* self, const char* message) { LibcException::Construct(self, LibcException::SceV2::ErrorException, message); } +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionC1ERKS0__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { LibcException::Copy(self, source, LibcException::SceV2::ErrorException); } +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionC2ERKS0__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { LibcException::Copy(self, source, LibcException::SceV2::ErrorException); } +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionD2Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt13__sce_cxxvers16_Error_exceptionD0Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DeleteMessage(self); } +const char* APS5_VABI _ZNKSt13__sce_cxxvers16_Error_exception4whatEv_nid_postfix(const LibcException::ExceptionObject* self) { return LibcException::MessageWhat(self); } + +[[noreturn]] void APS5_VABI _ZNSt8__sce_v214_Xlength_errorEPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(LibcException::SceV2::LengthError, message); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v214_Xout_of_rangeEPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(LibcException::SceV2::OutOfRange, message); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v218_Xinvalid_argumentEPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(LibcException::SceV2::InvalidArgument, message); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v215_Xruntime_errorEPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(LibcException::SceV2::RuntimeError, message); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v216_Xoverflow_errorEPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(LibcException::SceV2::OverflowError, message); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v211_Xbad_allocEv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt9bad_alloc_nid_postfix); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix() { LibcException::ThrowPlain(LibcException::SceV2::BadFunctionCall); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_19bad_optional_accessE_nid_postfix(const LibcException::ExceptionObject*) { LibcException::ThrowPlain(LibcException::SceV2::BadOptionalAccess); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_12bad_weak_ptrE_nid_postfix(const LibcException::ExceptionObject*) { LibcException::ThrowPlain(LibcException::SceV2::BadWeakPtr); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_18bad_variant_accessE_nid_postfix(const LibcException::ExceptionObject*) { LibcException::ThrowPlain(LibcException::SceV2::BadVariantAccess); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_11range_errorE_nid_postfix(const LibcException::ExceptionObject* error) { LibcException::SceV2::ThrowCopy(error, LibcException::SceV2::RangeError); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v26_ThrowERKNS_12system_errorE_nid_postfix(const LibcException::SystemErrorObject* error) { LibcException::SceV2::ThrowSystemCopy(error, LibcException::SceV2::SystemError); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v28ios_base6_ThrowERKNS0_7failureE_nid_postfix(const LibcException::SystemErrorObject* failure) { LibcException::SceV2::ThrowSystemCopy(failure, LibcException::SceV2::IosFailure); } +[[noreturn]] void APS5_VABI _ZNSt8__sce_v216_Throw_Cpp_errorEi_nid_postfix(int code) { LibcException::ThrowCppError(code, LibcException::SceV2::SystemError); } + +std::int64_t APS5_VABI _ZNSt8__sce_v216_Xtime_get_ticksEv_nid_postfix() { return _Xtime_get_ticks_nid_postfix(); } +const char* APS5_VABI _ZNSt8__sce_v213_Syserror_mapEi_nid_postfix(int value) { + if (value == 0) return "success"; + const char* message = LibcException::GenericErrorMessage(value); + return message ? message : "unknown error"; } } diff --git a/core/libs/prx/libc/src/exception/TypeInfo.cpp b/core/libs/prx/libc/src/exception/TypeInfo.cpp index 49ba0e47ee..a1d58f019b 100644 --- a/core/libs/prx/libc/src/exception/TypeInfo.cpp +++ b/core/libs/prx/libc/src/exception/TypeInfo.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include diff --git a/core/libs/prx/libc/src/exception/Unwind.cpp b/core/libs/prx/libc/src/exception/Unwind.cpp index c86389adfe..a690410bee 100644 --- a/core/libs/prx/libc/src/exception/Unwind.cpp +++ b/core/libs/prx/libc/src/exception/Unwind.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Unwind.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include "prx/libc/include/specifics/linux/ElfTypes.hpp" @@ -105,14 +106,21 @@ extern "C" void _Unwind_DeleteException(_Unwind_Exception*); #endif namespace LibcUnwind { -_Unwind_Reason_Code CallPersonality(Word personality, _Unwind_Action actions, _Unwind_Exception* exception, _Unwind_Context* context) { #ifdef _WIN32 - const auto* code = reinterpret_cast(personality); +static Word ResolveImport(Word address) { + const auto* code = reinterpret_cast(address); if (code[0] == 0xff && code[1] == 0x25) { std::int32_t displacement; - std::memcpy(&displacement, code + 2, 4); - std::memcpy(&personality, code + 6 + displacement, sizeof(personality)); + std::memcpy(&displacement, code + 2, sizeof(displacement)); + std::memcpy(&address, code + 6 + displacement, sizeof(address)); } + return address; +} +#endif + +_Unwind_Reason_Code CallPersonality(Word personality, _Unwind_Action actions, _Unwind_Exception* exception, _Unwind_Context* context) { +#ifdef _WIN32 + personality = ResolveImport(personality); #endif if (personality == reinterpret_cast(__gxx_personality_v0_nid_postfix) || personality == reinterpret_cast(__gxx_personality_v0)) return __gxx_personality_v0_nid_postfix(1, actions, exception->exception_class, exception, context); @@ -257,14 +265,20 @@ const IMAGE_NT_HEADERS64* ImageHeaders(Word pc, const Byte*& base) { return nt->Signature == IMAGE_NT_SIGNATURE ? nt : nullptr; } -bool VirtualUnwind(const _Unwind_Context& context, _Unwind_Context& caller) { +bool VirtualUnwind(_Unwind_Context& context, Frame& frame) { const Word pc = context.registers[16]; const Byte* base = nullptr; const auto* nt = ImageHeaders(pc, base); if (!nt) return false; const auto* sections = IMAGE_FIRST_SECTION(nt); - for (unsigned i = 0; i < nt->FileHeader.NumberOfSections; ++i) - if (std::memcmp(sections[i].Name, ".ehmeta", 8) == 0 || std::memcmp(sections[i].Name, ".ehfram", 8) == 0) return false; + const PEXCEPTION_ROUTINE* personalities = nullptr; + bool dwarf = false; + for (unsigned i = 0; i < nt->FileHeader.NumberOfSections; ++i) { + if (std::memcmp(sections[i].Name, ".ehmeta", 8) == 0 || std::memcmp(sections[i].Name, ".ehfram", 8) == 0) dwarf = true; + if (std::memcmp(sections[i].Name, ".sehgcc", 8) == 0 && sections[i].Misc.VirtualSize >= 2 * sizeof(PEXCEPTION_ROUTINE)) + personalities = reinterpret_cast(base + sections[i].VirtualAddress); + } + if (dwarf && !personalities) return false; DWORD64 imageBase = 0; auto* function = RtlLookupFunctionEntry(pc, &imageBase, nullptr); if (!function) return false; @@ -275,11 +289,27 @@ bool VirtualUnwind(const _Unwind_Context& context, _Unwind_Context& caller) { for (unsigned i = 0; i < 16; ++i) std::memcpy(&state.FltSave.XmmRegisters[i], context.vectorRegisters[i], 16); void* handlerData = nullptr; DWORD64 establisher = 0; - if (RtlVirtualUnwind(UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER, imageBase, pc, function, &state, &handlerData, &establisher, nullptr)) return false; + const auto handler = RtlVirtualUnwind(UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER, imageBase, pc, function, &state, &handlerData, &establisher, nullptr); + frame.start = imageBase + function->BeginAddress; + if (handler) { + if (!personalities) return false; + const Word resolved = ResolveImport(reinterpret_cast(handler)); + if (resolved == ResolveImport(reinterpret_cast(personalities[0]))) frame.personality = reinterpret_cast(__gxx_personality_v0_nid_postfix); + else if (resolved == ResolveImport(reinterpret_cast(personalities[1]))) frame.personality = reinterpret_cast(__gcc_personality_v0_nid_postfix); + else return false; + frame.lsda = reinterpret_cast(handlerData); + } + auto& caller = frame.caller; caller = context; for (unsigned i = 0; i < 17; ++i) caller.registers[i] = *registers[i]; for (unsigned i = 0; i < 16; ++i) std::memcpy(caller.vectorRegisters[i], &state.FltSave.XmmRegisters[i], 16); caller.signalFrame = false; + context.cfa = caller.registers[7]; + context.region = frame.start; + context.lsda = frame.lsda; + context.personality = frame.personality; + context.textBase = imageBase; + context.dataBase = 0; return true; } #endif @@ -414,10 +444,8 @@ bool GetRules(_Unwind_Context& context, Frame& frame, Rules& rules) { if (!DecodeFrame(context, frame)) { #ifdef _WIN32 frame = {}; - if (!VirtualUnwind(context, frame.caller)) return false; + if (!VirtualUnwind(context, frame)) return false; frame.foreign = true; - context.cfa = frame.caller.registers[7]; - context.region = context.lsda = context.personality = context.textBase = context.dataBase = 0; return true; #else return false; diff --git a/core/libs/prx/libc/src/specifics/gcc/StackGuard.cpp b/core/libs/prx/libc/src/specifics/gcc/StackGuard.cpp index bae022e001..a18ff70dc9 100644 --- a/core/libs/prx/libc/src/specifics/gcc/StackGuard.cpp +++ b/core/libs/prx/libc/src/specifics/gcc/StackGuard.cpp @@ -1,4 +1,8 @@ #include #include +#ifdef __APPLE__ +extern "C" std::uintptr_t __stack_chk_guard_nid_postfix = 0xDEADBEEFCAFEBABEull; +#else std::uintptr_t __stack_chk_guard = 0xDEADBEEFCAFEBABEull; +#endif diff --git a/core/libs/prx/libc/src/specifics/linux/DlIteratePhdr.cpp b/core/libs/prx/libc/src/specifics/linux/DlIteratePhdr.cpp index cc2ff7b9e3..516a53933a 100644 --- a/core/libs/prx/libc/src/specifics/linux/DlIteratePhdr.cpp +++ b/core/libs/prx/libc/src/specifics/linux/DlIteratePhdr.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include diff --git a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp index 7b4cc729ed..1d031c17a1 100644 --- a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp +++ b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp @@ -235,6 +235,14 @@ constexpr DWORD GccException = 0x20474343u; constexpr std::uint64_t GccExceptionClass = 0x474e5543432b2b00ull; constexpr std::uint64_t CxaExceptionTypeOffset = 80; +PWSTR ThreadDescription(HANDLE thread) { + using GetDescription = HRESULT (WINAPI *)(HANDLE, PWSTR*); + static const auto getDescription = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetThreadDescription")); + PWSTR description = nullptr; + if (!getDescription || FAILED(getDescription(thread, &description))) return nullptr; + return description; +} + std::uint64_t FrameWithoutUnwindData(const CONTEXT& start) { const HMODULE runtime[] = {GetModuleHandleA("KERNELBASE.dll"), GetModuleHandleA("libgcc_s_seh-1.dll"), GetModuleHandleA("libstdc++-6.dll")}; CONTEXT context = start; @@ -363,8 +371,7 @@ LONG WINAPI ReportCrash(EXCEPTION_POINTERS* info) { const auto* context = info->ContextRecord; char line[MAX_PATH + 64]; char threadName[128] = ""; - PWSTR description = nullptr; - if (SUCCEEDED(GetThreadDescription(GetCurrentThread(), &description)) && description) { + if (PWSTR description = ThreadDescription(GetCurrentThread())) { WideCharToMultiByte(CP_UTF8, 0, description, -1, threadName, sizeof(threadName), nullptr, nullptr); LocalFree(description); } @@ -582,8 +589,7 @@ void ReportAllThreads() { const std::size_t candidateCount = haveContext && frameCount < 2 ? CaptureStackCandidates(context.Rsp, candidates, sizeof(candidates) / sizeof(candidates[0])) : 0; ResumeThread(thread); char name[128] = ""; - PWSTR description = nullptr; - if (SUCCEEDED(GetThreadDescription(thread, &description)) && description) { + if (PWSTR description = ThreadDescription(thread)) { WideCharToMultiByte(CP_UTF8, 0, description, -1, name, sizeof(name), nullptr, nullptr); LocalFree(description); } diff --git a/core/libs/prx/libc/src/specifics/windows/SehPersonalities.cpp b/core/libs/prx/libc/src/specifics/windows/SehPersonalities.cpp new file mode 100644 index 0000000000..cd4c856063 --- /dev/null +++ b/core/libs/prx/libc/src/specifics/windows/SehPersonalities.cpp @@ -0,0 +1,11 @@ +#include + +extern "C" { +EXCEPTION_DISPOSITION __gxx_personality_seh0(EXCEPTION_RECORD*, void*, CONTEXT*, void*); +EXCEPTION_DISPOSITION __gcc_personality_seh0(EXCEPTION_RECORD*, void*, CONTEXT*, void*); + +[[gnu::section(".sehgcc"), gnu::used]] extern const PEXCEPTION_ROUTINE LibcSehPersonalities_nid_no_patch[] { + __gxx_personality_seh0, + __gcc_personality_seh0, +}; +} diff --git a/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp b/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp index 96d6c872a9..694896a3b0 100644 --- a/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp +++ b/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp @@ -14,14 +14,21 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include +#include +#include +#include #include #include #include #include #include +#include #include +#include +#include extern "C" void CallTraceEnter_nid_no_patch(); @@ -70,6 +77,39 @@ bool Selected(std::string_view library) { return false; } +bool WriteCallCounts(const std::filesystem::path& path) { + const std::size_t recorded = g_recordCount.load(std::memory_order_acquire); + const ImportRecord* records = g_records.load(std::memory_order_acquire); + std::vector> called; + for (std::size_t index = 0; index < recorded; ++index) { + const std::uint64_t calls = records[index].calls.load(std::memory_order_relaxed); + if (calls != 0) called.emplace_back(calls, &records[index]); + } + std::stable_sort(called.begin(), called.end(), [](const auto& left, const auto& right) { return left.first > right.first; }); + std::ofstream file(path, std::ios::binary | std::ios::trunc); + for (const auto& [calls, record] : called) file << calls << ' ' << record->library << ' ' << record->name << '\n'; + file.flush(); + return static_cast(file); +} + +void StartCallCountWriter() { +#ifdef _WIN32 + const wchar_t* value = _wgetenv(L"APS5_TRACE_CALLS_FILE"); + if (value == nullptr || *value == L'\0') return; + std::filesystem::path path(value); +#else + const char* value = std::getenv("APS5_TRACE_CALLS_FILE"); + if (value == nullptr || *value == '\0') return; + std::filesystem::path path(value); +#endif + std::thread([path = std::move(path)] { + for (;;) { + std::this_thread::sleep_for(std::chrono::seconds(10)); + WriteCallCounts(path); + } + }).detach(); +} + std::uint64_t CurrentThread() { #ifdef _WIN32 return GetCurrentThreadId(); @@ -117,6 +157,7 @@ struct ModuleSegment { struct ModuleMap { std::size_t count; + std::uint64_t pageSize; ModuleSegment segments[256]; }; @@ -135,16 +176,20 @@ void SnapshotModules() { if (changes == g_moduleChanges) return; g_moduleChanges = changes; auto* map = new ModuleMap{}; + map->pageSize = static_cast(sysconf(_SC_PAGESIZE)); dl_iterate_phdr([](dl_phdr_info* info, std::size_t, void* data) { auto& modules = *static_cast(data); const char* path = info->dlpi_name != nullptr && *info->dlpi_name != '\0' ? info->dlpi_name : program_invocation_short_name; const char* slash = std::strrchr(path, '/'); const char* name = slash == nullptr ? path : slash + 1; + std::uint64_t first = std::numeric_limits::max(); + for (int index = 0; index < info->dlpi_phnum; ++index) + if (info->dlpi_phdr[index].p_type == PT_LOAD) first = std::min(first, info->dlpi_phdr[index].p_vaddr & ~(modules.pageSize - 1)); for (int index = 0; index < info->dlpi_phnum && modules.count < std::size(modules.segments); ++index) { const auto& header = info->dlpi_phdr[index]; if (header.p_type != PT_LOAD || header.p_memsz == 0) continue; auto& segment = modules.segments[modules.count++]; - segment.base = info->dlpi_addr; + segment.base = info->dlpi_addr + first; segment.start = info->dlpi_addr + header.p_vaddr; segment.end = segment.start + header.p_memsz; std::strncpy(segment.name, name, sizeof(segment.name) - 1); @@ -328,6 +373,7 @@ void* TraceImport_nid_no_patch(void* target, const char* name, const char* libra #ifdef __linux__ InstallAbortReport(); #endif + StartCallCountWriter(); } #ifdef __linux__ SnapshotModules(); @@ -380,6 +426,11 @@ std::size_t CopyThreadCallTrace_nid_no_patch(std::uint64_t thread, CallTraceEntr return count; } +bool WriteCallCounts_nid_no_patch(const char* path) { + if (path == nullptr || *path == '\0') return false; + return WriteCallCounts(std::filesystem::path(reinterpret_cast(path))); +} + std::size_t CopyCallCounts_nid_no_patch(CallTraceCount* entries, std::size_t capacity, std::uint64_t* total) { if (total != nullptr) *total = g_sequence.load(std::memory_order_acquire); if (entries == nullptr || capacity == 0) return 0; diff --git a/core/libs/prx/libkernel/Aio/src/Aio.cpp b/core/libs/prx/libkernel/Aio/src/Aio.cpp index 9926ac1dd6..54fb54921f 100644 --- a/core/libs/prx/libkernel/Aio/src/Aio.cpp +++ b/core/libs/prx/libkernel/Aio/src/Aio.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include @@ -5,7 +6,6 @@ #include #include #include -#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestArena.hpp" @@ -25,6 +25,7 @@ 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; constexpr std::int32_t AioIdNumMax = 128; @@ -34,6 +35,11 @@ constexpr std::int32_t AioSchedulingWindowMax = 128; constexpr std::int32_t AioDelayedCountMax = 128; constexpr std::uint32_t AioSplitSizeMax = 0x1000000; constexpr std::uint32_t AioSplitChunkSizeMax = 0x1000000; +constexpr std::int32_t AioPriorityLow = 1; +constexpr std::int32_t AioPriorityHigh = 3; +constexpr std::int32_t AioDefaultSchedulingWindow = 32; +constexpr std::int32_t AioDefaultDelayedCountLimit = 32; +constexpr std::uint32_t AioDefaultSplitSize = 0x100000; std::mutex g_mutex; std::array g_states{}; @@ -57,6 +63,30 @@ void SetState(std::int32_t id, std::int32_t state) { g_states[id] = state; } +std::int32_t StateLocked(std::int32_t id) { + return ValidId(id) && g_states[id] != 0 ? g_states[id] : 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; + 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; +} + +std::int32_t DeleteLocked(std::int32_t id) { + if (StateLocked(id) == SCE_KERNEL_ERROR_ESRCH) return SCE_KERNEL_ERROR_ESRCH; + g_states[id] = 0; + return 0; +} + std::int64_t NativePread(std::int32_t fd, void* buf, std::size_t nbyte, std::int64_t offset) { const GuestArena::HostWrite destination(buf, nbyte); if (!destination.Open()) { @@ -93,7 +123,7 @@ std::int64_t NativePwrite(std::int32_t fd, const void* buf, std::size_t nbyte, s #endif } -bool RunRequest(KernelAioRwRequest& req, bool write) { +void RunRequest(KernelAioRwRequest& req, bool write) { const std::int64_t done = write ? NativePwrite(req.fd, req.buf, req.nbyte, req.offset) : NativePread(req.fd, req.buf, req.nbyte, req.offset); @@ -103,55 +133,47 @@ bool RunRequest(KernelAioRwRequest& req, bool write) { if (error == EBADF) req.result->return_value = static_cast(SCE_KERNEL_ERROR_EBADF); if (error == EFAULT) req.result->return_value = static_cast(SCE_KERNEL_ERROR_EFAULT); if (error == EINVAL) req.result->return_value = static_cast(SCE_KERNEL_ERROR_EINVAL); - req.result->state = AioAborted; - return false; + req.result->state = AioCompleted; + return; } req.result->return_value = done; req.result->state = AioCompleted; - return true; } -int ValidateRequests(KernelAioRwRequest* req, std::int32_t size, const void* id) { +int ValidateRequests(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, const void* id) { if (req == nullptr || id == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (size <= 0) return SCE_KERNEL_ERROR_EINVAL; + if (size <= 0 || size > AioRequestNumMax || prio < AioPriorityLow || prio > AioPriorityHigh) return SCE_KERNEL_ERROR_EINVAL; for (std::int32_t i = 0; i < size; ++i) { if (req[i].result == nullptr) return SCE_KERNEL_ERROR_EFAULT; } return 0; } -int SubmitCommands(KernelAioRwRequest* req, std::int32_t size, bool write, std::int32_t* id) { - const int error = ValidateRequests(req, size, id); +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(); - bool aborted = false; - for (std::int32_t i = 0; i < size; ++i) { - if (!RunRequest(req[i], write)) aborted = true; - } - SetState(queue, aborted ? AioAborted : AioCompleted); + for (std::int32_t i = 0; i < size; ++i) RunRequest(req[i], write); + SetState(queue, AioCompleted); *id = queue; return 0; } -int SubmitCommandsMultiple(KernelAioRwRequest* req, std::int32_t size, bool write, std::int32_t* id, const char* name) { - if (req != nullptr && id != nullptr && size > AioRequestNumMax) throw std::runtime_error(std::string(name) + ": more than 128 requests per batch is not modelled"); - const int error = ValidateRequests(req, size, id); +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; for (std::int32_t i = 0; i < size; ++i) { const std::int32_t queue = AllocateId(); - SetState(queue, RunRequest(req[i], write) ? AioCompleted : AioAborted); + RunRequest(req[i], write); + SetState(queue, AioCompleted); id[i] = queue; } return 0; } -int ValidateIds(const std::int32_t* id, std::int32_t num, const void* out, const char* name) { +int ValidateIds(const std::int32_t* id, std::int32_t num, const void* out) { if (id == nullptr || out == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (num < 0) return SCE_KERNEL_ERROR_EINVAL; - if (num > AioIdNumMax) throw std::runtime_error(std::string(name) + ": more than 128 ids per batch is not modelled"); - for (std::int32_t i = 0; i < num; ++i) { - if (!ValidId(id[i])) return SCE_KERNEL_ERROR_EINVAL; - } + if (num <= 0 || num > AioIdNumMax) return SCE_KERNEL_ERROR_EINVAL; return 0; } @@ -161,49 +183,31 @@ extern "C" { int APS5_VABI sceKernelAioDeleteRequest(int32_t id, int32_t* ret) { if (ret == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL; - SetState(id, AioAborted); - *ret = 0; + std::lock_guard lock(g_mutex); + *ret = DeleteLocked(id); return 0; } int APS5_VABI sceKernelAioDeleteRequests(int32_t* id, int32_t num, int32_t* ret) { - const int error = ValidateIds(id, num, ret, "sceKernelAioDeleteRequests"); + const int error = ValidateIds(id, num, ret); if (error != 0) return error; - for (int32_t i = 0; i < num; ++i) { - SetState(id[i], AioAborted); - ret[i] = 0; - } + std::lock_guard lock(g_mutex); + for (int32_t i = 0; i < num; ++i) ret[i] = DeleteLocked(id[i]); return 0; } int APS5_VABI sceKernelAioCancelRequest(int32_t id, int32_t* state) { if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (id == 0) { - *state = AioProcessing; - return 0; - } - if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL; - SetState(id, AioAborted); - *state = AioAborted; + std::lock_guard lock(g_mutex); + *state = CancelLocked(id); return 0; } int APS5_VABI sceKernelAioCancelRequests(int32_t* id, int32_t num, int32_t* state) { - if (id == nullptr || state == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (num < 0) return SCE_KERNEL_ERROR_EINVAL; - if (num > AioIdNumMax) throw std::runtime_error("sceKernelAioCancelRequests: more than 128 ids per batch is not modelled"); - for (int32_t i = 0; i < num; ++i) { - if (id[i] != 0 && !ValidId(id[i])) return SCE_KERNEL_ERROR_EINVAL; - } - for (int32_t i = 0; i < num; ++i) { - if (id[i] == 0) { - state[i] = AioProcessing; - continue; - } - SetState(id[i], AioAborted); - state[i] = AioAborted; - } + 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) state[i] = CancelLocked(id[i]); return 0; } @@ -215,14 +219,18 @@ int APS5_VABI sceKernelAioInitializeImpl(void* param, int32_t size) { void APS5_VABI sceKernelAioInitializeParam(void* param) { if (param == nullptr) throw std::invalid_argument("sceKernelAioInitializeParam: param is null"); + auto* scheduling = static_cast(param); + for (int index = 0; index < 3; ++index) { + const bool split = index < 2; + scheduling[index] = {AioDefaultSchedulingWindow, AioDefaultDelayedCountLimit, split ? 1u : 0u, split ? AioDefaultSplitSize : 0u, split ? AioDefaultSplitSize : 0u}; + } } int APS5_VABI sceKernelAioSetParam(KernelAioSchedulingParam* param, int32_t schedulingWindowSize, int32_t delayedCountLimit, uint32_t enableSplit, uint32_t splitSize, uint32_t splitChunkSize) { if (param == nullptr) throw std::invalid_argument("sceKernelAioSetParam: param is null"); - if (schedulingWindowSize < 0 || schedulingWindowSize > AioSchedulingWindowMax) throw std::runtime_error("sceKernelAioSetParam: a scheduling window outside 0..128 is not modelled"); - if (delayedCountLimit < 0 || delayedCountLimit > AioDelayedCountMax) throw std::runtime_error("sceKernelAioSetParam: a delayed count limit outside 0..128 is not modelled"); - if (enableSplit > 1) throw std::runtime_error("sceKernelAioSetParam: a split flag other than 0 or 1 is not modelled"); - if (splitSize > AioSplitSizeMax || splitChunkSize > AioSplitChunkSizeMax) throw std::runtime_error("sceKernelAioSetParam: a split size above 0x1000000 is not modelled"); + if (schedulingWindowSize < 0 || schedulingWindowSize > AioSchedulingWindowMax || delayedCountLimit < 0 || delayedCountLimit > AioDelayedCountMax || enableSplit > 1) + return SCE_KERNEL_ERROR_EINVAL; + if (enableSplit == 1 && (splitSize == 0 || splitSize > AioSplitSizeMax || splitChunkSize == 0 || splitChunkSize > AioSplitChunkSizeMax)) return SCE_KERNEL_ERROR_EINVAL; param->schedulingWindowSize = schedulingWindowSize; param->delayedCountLimit = delayedCountLimit; param->enableSplit = enableSplit; @@ -232,37 +240,32 @@ int APS5_VABI sceKernelAioSetParam(KernelAioSchedulingParam* param, int32_t sche } int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest* req, int32_t size, int32_t prio, int32_t* id) { - (void)prio; - return SubmitCommands(req, size, false, id); + return SubmitCommands(req, size, prio, false, id); } int APS5_VABI sceKernelAioSubmitWriteCommands(KernelAioRwRequest* req, int32_t size, int32_t prio, int32_t* id) { - (void)prio; - return SubmitCommands(req, size, true, id); + return SubmitCommands(req, size, prio, true, id); } int APS5_VABI sceKernelAioSubmitReadCommandsMultiple(KernelAioRwRequest* req, int32_t size, int32_t prio, int32_t* id) { - (void)prio; - return SubmitCommandsMultiple(req, size, false, id, "sceKernelAioSubmitReadCommandsMultiple"); + return SubmitCommandsMultiple(req, size, prio, false, id); } int APS5_VABI sceKernelAioSubmitWriteCommandsMultiple(KernelAioRwRequest* req, int32_t size, int32_t prio, int32_t* id) { - (void)prio; - return SubmitCommandsMultiple(req, size, true, id, "sceKernelAioSubmitWriteCommandsMultiple"); + return SubmitCommandsMultiple(req, size, prio, true, id); } int APS5_VABI sceKernelAioPollRequest(int32_t id, int32_t* state) { if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL; std::lock_guard lock(g_mutex); - *state = g_states[id]; + *state = ReportLocked(id); return 0; } int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state, uint32_t mode, uint32_t* usec) { - const int error = ValidateIds(id, num, state, "sceKernelAioWaitRequests"); + const int error = ValidateIds(id, num, state); if (error != 0) return error; - if (mode != AioWaitAnd && mode != AioWaitOr) throw std::runtime_error("sceKernelAioWaitRequests: unsupported wait mode"); + if (mode != AioWaitAnd && mode != AioWaitOr) return SCE_KERNEL_ERROR_EINVAL; const auto start = std::chrono::steady_clock::now(); for (;;) { bool allDone = true; @@ -270,15 +273,22 @@ int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state, { std::lock_guard lock(g_mutex); for (int32_t i = 0; i < num; ++i) { - state[i] = g_states[id[i]]; - if (state[i] == AioProcessing) allDone = false; - if (state[i] == AioCompleted) anyCompleted = true; + const std::int32_t current = StateLocked(id[i]); + if (current == AioProcessing) allDone = false; + if ((current & ~AioReported) == AioCompleted) anyCompleted = true; + } + if (allDone || (mode == AioWaitOr && anyCompleted)) { + for (int32_t i = 0; i < num; ++i) state[i] = ReportLocked(id[i]); + return 0; } } - if (allDone || (mode == AioWaitOr && anyCompleted)) return 0; if (usec != nullptr && *usec != 0) { const auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); - if (elapsed > static_cast(*usec)) return SCE_KERNEL_ERROR_ETIMEDOUT; + if (elapsed > static_cast(*usec)) { + std::lock_guard lock(g_mutex); + for (int32_t i = 0; i < num; ++i) state[i] = ReportLocked(id[i]); + return SCE_KERNEL_ERROR_ETIMEDOUT; + } } PreciseSleepUs(10); } @@ -286,13 +296,12 @@ 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; - if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL; const auto start = std::chrono::steady_clock::now(); for (;;) { std::int32_t current; { std::lock_guard lock(g_mutex); - current = g_states[id]; + current = ReportLocked(id); } if (current != AioProcessing) { *state = current; @@ -310,10 +319,10 @@ int APS5_VABI sceKernelAioWaitRequest(int32_t id, int32_t* state, uint32_t* usec } int APS5_VABI sceKernelAioPollRequests(int32_t* id, int32_t num, int32_t* state) { - const int error = ValidateIds(id, num, state, "sceKernelAioPollRequests"); + 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) state[i] = g_states[id[i]]; + for (int32_t i = 0; i < num; ++i) state[i] = ReportLocked(id[i]); return 0; } diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index 42e4274d9e..49676567f6 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include #include #include @@ -35,8 +37,6 @@ #include #endif -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { constexpr int GUEST_ENOENT = 2; @@ -674,6 +674,7 @@ std::optional _execute(const Apr::CommandBufferObject& buffer, std::ui case Apr::Opcode::WriteKernelEventQueue: { const auto command = _read(buffer, cursor); EqueueTriggerEvent_nid_postfix(static_cast(command.equeue), static_cast(command.ident), EVFILT_AMPR, reinterpret_cast(command.data)); + break; } case Apr::Opcode::WriteAddressFromTimeCounter: { @@ -701,8 +702,6 @@ std::optional _execute(const Apr::CommandBufferObject& buffer, std::ui extern "C" { -int APS5_VABI sceKernelAllocateDirectMemory(int64_t searchStart, int64_t searchEnd, size_t length, size_t alignment, int memoryType, int64_t* offset); - int AmmGiveDirectMemory_nid_no_patch(std::int64_t searchStart, std::int64_t searchEnd, std::size_t size, std::size_t alignment, int usage, std::int64_t* offset) { if (!offset || (usage != 0 && usage != 1)) return SCE_KERNEL_ERROR_EINVAL; const int result = sceKernelAllocateDirectMemory(searchStart, searchEnd, size, alignment, 0, offset); @@ -826,7 +825,6 @@ bool AmmSubmitted_nid_no_patch(std::uint32_t id) { return true; } - int APS5_VABI sceKernelAprResolveFilepathsToIds(const char** paths, uint32_t count, uint32_t* ids, uint32_t* error_index) { if (!paths || !ids) return _fail(GUEST_EINVAL); for (uint32_t index = 0; index < count; ++index) { diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 8613c79da0..0fb93c7772 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -46,6 +46,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Tsd.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Sem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Spinlock.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/Pselect.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/Socket.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/Udp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/SocketOptions.cpp @@ -55,6 +56,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Module/src/Module.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Module/src/DynamicLoader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Module/src/ModuleInfo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Module/src/Tls.cpp ${LIBS_INCLUDE_DIR}/nid/src/NidCompute.cpp ${LIBS_INCLUDE_DIR}/nid/src/Sha1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Aio/src/Aio.cpp @@ -67,6 +69,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/System/src/HostCpu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/SystemConfiguration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Login.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Getlogin.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/SoftwareVersion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/ThermalSensors.cpp @@ -74,6 +77,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/System/src/SignalSets.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Notification.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/UserContext.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Time/Ktimer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Time/SleepWake.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Time/Time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Time/TimedWait.cpp @@ -186,6 +190,16 @@ if(BUILD_TESTING) set_tests_properties(guest_posix_sem_init guest_posix_sem_post PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_posix_named_sem_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixNamedSem.cpp) + target_include_directories(guest_posix_named_sem_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_posix_named_sem_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_posix_named_sem_tests) + add_test(NAME guest_posix_named_sem COMMAND guest_posix_named_sem_tests) + set_tests_properties(guest_posix_named_sem PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_posix_named_sem PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_process_time_tests ${LIBS_INCLUDE_DIR}/tests/GuestProcessTime.cpp) target_include_directories(guest_process_time_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_process_time_tests PRIVATE libkernel libc) @@ -212,6 +226,17 @@ if(BUILD_TESTING) set_tests_properties(guest_process_arguments PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + find_package(Python3 QUIET COMPONENTS Interpreter) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND Python3_Interpreter_FOUND) + add_test_executable(guest_runtime_lifecycle_tests ${LIBS_INCLUDE_DIR}/tests/GuestRuntimeLifecycle.cpp) + target_include_directories(guest_runtime_lifecycle_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_runtime_lifecycle_tests PRIVATE libkernel libc ${CMAKE_DL_LIBS}) + set_target_properties(guest_runtime_lifecycle_tests PROPERTIES ENABLE_EXPORTS ON) + add_dependencies(guest_runtime_lifecycle_tests relinker) + add_test(NAME guest_runtime_lifecycle COMMAND ${Python3_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/core/relinker/relinker/tests/test_guest_runtime_lifecycle.py + $ $ $) + endif() add_library(guest_module_fixture SHARED ${LIBS_INCLUDE_DIR}/tests/GuestModuleFixture.cpp) target_include_directories(guest_module_fixture PRIVATE ${LIBS_INCLUDE_DIR}) add_custom_command(TARGET guest_module_fixture POST_BUILD @@ -220,11 +245,12 @@ if(BUILD_TESTING) add_dependencies(guest_module_fixture nid_patcher) configure_windows_unwind(guest_module_fixture) set_target_properties(guest_module_fixture PROPERTIES BUILD_WITH_INSTALL_RPATH ON INSTALL_RPATH "$ORIGIN") - find_package(Python3 QUIET COMPONENTS Interpreter) - if(Python3_Interpreter_FOUND AND NOT WIN32) - target_link_options(guest_module_fixture PRIVATE -Wl,--no-as-needed) - add_test(NAME nid_patcher_dynamic_strings COMMAND ${Python3_EXECUTABLE} - ${LIBS_INCLUDE_DIR}/nid/tests/test_dynamic_strings.py $) + if(Python3_Interpreter_FOUND) + if(NOT WIN32) + target_link_options(guest_module_fixture PRIVATE -Wl,--no-as-needed) + add_test(NAME nid_patcher_dynamic_strings COMMAND ${Python3_EXECUTABLE} + ${LIBS_INCLUDE_DIR}/nid/tests/test_dynamic_strings.py $) + endif() add_test(NAME nid_patcher_symbol_versions COMMAND ${Python3_EXECUTABLE} ${LIBS_INCLUDE_DIR}/nid/tests/test_symbol_versions.py $) endif() @@ -239,7 +265,7 @@ if(BUILD_TESTING) target_sources(guest_dynamic_loader_tests PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/GuestDynamicLoader.def") endif() add_test(NAME guest_dynamic_loader COMMAND guest_dynamic_loader_tests $) - set_tests_properties(guest_dynamic_loader PROPERTIES + set_tests_properties(guest_dynamic_loader PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "$" RESOURCE_LOCK guest_dynamic_loader_files) if(WIN32) set_tests_properties(guest_dynamic_loader PROPERTIES ENVIRONMENT_MODIFICATION @@ -357,8 +383,27 @@ if(BUILD_TESTING) target_link_libraries(guest_host_standard_error_windows_tests PRIVATE libkernel libc) configure_windows_unwind(guest_host_standard_error_windows_tests) add_test(NAME guest_host_standard_error_windows COMMAND guest_host_standard_error_windows_tests) - set_tests_properties(guest_host_standard_error_windows PROPERTIES TIMEOUT 10 ENVIRONMENT_MODIFICATION + add_test(NAME guest_host_standard_buffering_windows COMMAND guest_host_standard_error_windows_tests --buffering) + set_tests_properties(guest_host_standard_error_windows guest_host_standard_buffering_windows PROPERTIES TIMEOUT 10 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + add_test_executable(guest_closed_standard_descriptors_windows_tests ${LIBS_INCLUDE_DIR}/tests/GuestClosedStandardDescriptorsWindows.cpp) + target_include_directories(guest_closed_standard_descriptors_windows_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_closed_standard_descriptors_windows_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_closed_standard_descriptors_windows_tests) + add_test(NAME guest_closed_standard_descriptors_windows COMMAND guest_closed_standard_descriptors_windows_tests) + set_tests_properties(guest_closed_standard_descriptors_windows PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + add_test_executable(guest_closed_standard_streams_load_windows_tests ${LIBS_INCLUDE_DIR}/tests/GuestClosedStandardStreamsLoadWindows.cpp) + target_include_directories(guest_closed_standard_streams_load_windows_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_closed_standard_streams_load_windows_tests PRIVATE shell32) + if(MINGW) + target_compile_options(guest_closed_standard_streams_load_windows_tests PRIVATE -fasynchronous-unwind-tables) + endif() + add_dependencies(guest_closed_standard_streams_load_windows_tests libc) + configure_windows_unwind(guest_closed_standard_streams_load_windows_tests) + add_test(NAME guest_closed_standard_streams_load_windows COMMAND guest_closed_standard_streams_load_windows_tests "$") + set_tests_properties(guest_closed_standard_streams_load_windows PROPERTIES TIMEOUT 10 + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test_executable(guest_truncate_tests ${LIBS_INCLUDE_DIR}/tests/GuestTruncate.cpp) target_include_directories(guest_truncate_tests PRIVATE ${LIBS_INCLUDE_DIR}) @@ -521,6 +566,16 @@ if(BUILD_TESTING) set_tests_properties(guest_kernel_stack_protect PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_tls_get_addr_tests ${LIBS_INCLUDE_DIR}/tests/GuestTlsGetAddr.cpp) + target_include_directories(guest_tls_get_addr_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_tls_get_addr_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_tls_get_addr_tests) + add_test(NAME guest_tls_get_addr COMMAND guest_tls_get_addr_tests) + set_tests_properties(guest_tls_get_addr PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_tls_get_addr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_thread_self_tests ${LIBS_INCLUDE_DIR}/tests/GuestThreadSelf.cpp) target_include_directories(guest_thread_self_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_thread_self_tests PRIVATE libkernel libc) @@ -783,6 +838,16 @@ if(BUILD_TESTING) set_tests_properties(guest_sleep_wake guest_sleep_wake_coarse PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_nanosleep_long_request_tests ${LIBS_INCLUDE_DIR}/tests/GuestNanosleepLongRequest.cpp) + target_include_directories(guest_nanosleep_long_request_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_nanosleep_long_request_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_nanosleep_long_request_tests) + add_test(NAME guest_nanosleep_long_request COMMAND guest_nanosleep_long_request_tests) + set_tests_properties(guest_nanosleep_long_request PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_nanosleep_long_request PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_pthread_sem_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadSem.cpp) target_include_directories(guest_pthread_sem_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_pthread_sem_tests PRIVATE libkernel libc) @@ -937,10 +1002,30 @@ if(BUILD_TESTING) target_link_libraries(guest_signal_tests PRIVATE libkernel libc) configure_windows_unwind(guest_signal_tests) add_test(NAME guest_signals COMMAND guest_signal_tests) + add_test_executable(guest_signal_interrupt_tests ${LIBS_INCLUDE_DIR}/tests/GuestSignalInterrupt.cpp) + target_include_directories(guest_signal_interrupt_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_signal_interrupt_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_signal_interrupt_tests) + add_test(NAME guest_signal_interrupt COMMAND guest_signal_interrupt_tests) + set_tests_properties(guest_signal_interrupt PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_signal_interrupt PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(WIN32) set_tests_properties(guest_signals PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_signal_handler_mask_tests ${LIBS_INCLUDE_DIR}/tests/GuestSignalHandlerMask.cpp) + target_include_directories(guest_signal_handler_mask_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_signal_handler_mask_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_signal_handler_mask_tests) + add_test(NAME guest_signal_handler_mask COMMAND guest_signal_handler_mask_tests) + set_tests_properties(guest_signal_handler_mask PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_signal_handler_mask PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_sigtimedwait_tests ${LIBS_INCLUDE_DIR}/tests/GuestSigtimedwait.cpp) target_include_directories(guest_sigtimedwait_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_sigtimedwait_tests PRIVATE libkernel libc) @@ -1023,7 +1108,7 @@ if(BUILD_TESTING) target_link_libraries(guest_raise_exception_tests PRIVATE libkernel libc) configure_windows_unwind(guest_raise_exception_tests) add_test(NAME guest_raise_exception COMMAND guest_raise_exception_tests) - set_tests_properties(guest_raise_exception PROPERTIES TIMEOUT 30) + set_tests_properties(guest_raise_exception PROPERTIES TIMEOUT 60) add_asan_test_options(guest_raise_exception detect_stack_use_after_return=0) if(WIN32) set_tests_properties(guest_raise_exception PROPERTIES ENVIRONMENT_MODIFICATION @@ -1149,7 +1234,7 @@ if(BUILD_TESTING) endif() add_test_executable(guest_sched_priority_tests ${LIBS_INCLUDE_DIR}/tests/GuestSchedPriority.cpp) target_include_directories(guest_sched_priority_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_sched_priority_tests PRIVATE libkernel libc) + target_link_libraries(guest_sched_priority_tests PRIVATE libkernel libkernel_sys libc) configure_windows_unwind(guest_sched_priority_tests) add_test(NAME guest_sched_priority COMMAND guest_sched_priority_tests) set_tests_properties(guest_sched_priority PROPERTIES TIMEOUT 20) @@ -1157,6 +1242,16 @@ if(BUILD_TESTING) set_tests_properties(guest_sched_priority PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_rtprio_thread_tests ${LIBS_INCLUDE_DIR}/tests/GuestRtprioThread.cpp) + target_include_directories(guest_rtprio_thread_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_rtprio_thread_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_rtprio_thread_tests) + add_test(NAME guest_rtprio_thread COMMAND guest_rtprio_thread_tests) + set_tests_properties(guest_rtprio_thread PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_rtprio_thread PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_operation_mode_tests ${LIBS_INCLUDE_DIR}/tests/GuestOperationMode.cpp) target_include_directories(guest_operation_mode_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_operation_mode_tests PRIVATE libkernel libc) @@ -1283,6 +1378,19 @@ if(BUILD_TESTING) set_tests_properties(guest_named_shm_concurrency PROPERTIES TIMEOUT 20) endif() if(WIN32) + add_test_executable(guest_named_shm_windows_tests ${LIBS_INCLUDE_DIR}/tests/GuestNamedShmWindows.cpp) + target_include_directories(guest_named_shm_windows_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include) + target_link_libraries(guest_named_shm_windows_tests PRIVATE libkernel libc advapi32) + configure_windows_unwind(guest_named_shm_windows_tests) + add_test(NAME guest_named_shm_windows COMMAND guest_named_shm_windows_tests) + set_tests_properties(guest_named_shm_windows PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + add_test(NAME guest_named_shm_windows_short_temp COMMAND guest_named_shm_windows_tests --short-temp) + set_tests_properties(guest_named_shm_windows_short_temp PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + add_test(NAME guest_named_shm_windows_long_short_temp COMMAND guest_named_shm_windows_tests --long-short-temp) + set_tests_properties(guest_named_shm_windows_long_short_temp PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") set_tests_properties(guest_shm_open PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() @@ -1349,9 +1457,10 @@ if(BUILD_TESTING) target_link_libraries(guest_socket_options_tests PRIVATE libkernel libc) configure_windows_unwind(guest_socket_options_tests) add_test(NAME guest_socket_options COMMAND guest_socket_options_tests) - set_tests_properties(guest_socket_options PROPERTIES TIMEOUT 30) + add_test(NAME guest_socket_options_multicast COMMAND guest_socket_options_tests multicast) + set_tests_properties(guest_socket_options guest_socket_options_multicast PROPERTIES TIMEOUT 30) if(WIN32) - set_tests_properties(guest_socket_options PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_socket_options guest_socket_options_multicast PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test_executable(guest_socket_stream_options_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocketStreamOptions.cpp) @@ -1549,6 +1658,16 @@ if(BUILD_TESTING) set_tests_properties(guest_socket_connect_listening PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_underscore_entry_tests ${LIBS_INCLUDE_DIR}/tests/GuestUnderscoreEntries.cpp) + target_include_directories(guest_underscore_entry_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_underscore_entry_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_underscore_entry_tests) + add_test(NAME guest_underscore_entries COMMAND guest_underscore_entry_tests) + set_tests_properties(guest_underscore_entries PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_underscore_entries PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_elf_phdr_match_tests ${LIBS_INCLUDE_DIR}/tests/GuestElfPhdrMatch.cpp) target_include_directories(guest_elf_phdr_match_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_elf_phdr_match_tests PRIVATE libkernel libc) @@ -1600,6 +1719,18 @@ if(BUILD_TESTING) set_tests_properties(guest_memory guest_mmap_fixed_anonymous PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + if(NOT ANYPS5_ENABLE_ASAN AND (WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")) + add_test_executable(guest_low_address_memory_tests ${LIBS_INCLUDE_DIR}/tests/GuestLowAddressMemory.cpp) + target_include_directories(guest_low_address_memory_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_low_address_memory_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_low_address_memory_tests) + add_test(NAME guest_low_address_memory COMMAND guest_low_address_memory_tests) + set_tests_properties(guest_low_address_memory PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_low_address_memory PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endif() add_test_executable(guest_mprotect_unmapped_tests ${LIBS_INCLUDE_DIR}/tests/GuestMprotectUnmapped.cpp) target_include_directories(guest_mprotect_unmapped_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_mprotect_unmapped_tests PRIVATE libkernel libc) @@ -1625,6 +1756,7 @@ if(BUILD_TESTING) target_link_libraries(guest_flexible_memory_budget_tests PRIVATE libkernel libc) configure_windows_unwind(guest_flexible_memory_budget_tests) add_library(guest_flexible_memory_module SHARED ${LIBS_INCLUDE_DIR}/tests/GuestFlexibleMemoryModule.cpp) + target_include_directories(guest_flexible_memory_module PRIVATE ${LIBS_INCLUDE_DIR}) set_target_properties(guest_flexible_memory_module PROPERTIES PREFIX "" SUFFIX ".guest.prx") add_dependencies(guest_flexible_memory_budget_tests guest_flexible_memory_module) target_compile_definitions(guest_flexible_memory_budget_tests PRIVATE GUEST_FLEXIBLE_MEMORY_MODULE="$") @@ -1679,6 +1811,13 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() if(WIN32) + add_test_executable(guest_windows_directory_link_types_tests ${LIBS_INCLUDE_DIR}/tests/GuestWindowsDirectoryLinkTypes.cpp) + target_include_directories(guest_windows_directory_link_types_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_windows_directory_link_types_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_windows_directory_link_types_tests) + add_test(NAME guest_windows_directory_link_types COMMAND guest_windows_directory_link_types_tests) + set_tests_properties(guest_windows_directory_link_types PROPERTIES TIMEOUT 15 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") add_test_executable(guest_directory_buffer_tests ${LIBS_INCLUDE_DIR}/tests/GuestDirectoryBuffer.cpp) target_include_directories(guest_directory_buffer_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_directory_buffer_tests PRIVATE libkernel libc) @@ -1696,6 +1835,13 @@ if(BUILD_TESTING) set_tests_properties(guest_directory_rewind PROPERTIES TIMEOUT 15 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_test_executable(guest_directory_link_type_tests ${LIBS_INCLUDE_DIR}/tests/GuestDirectoryLinkTypes.cpp) + target_include_directories(guest_directory_link_type_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_directory_link_type_tests PRIVATE libkernel libc) + add_test(NAME guest_directory_link_types COMMAND guest_directory_link_type_tests) + set_tests_properties(guest_directory_link_types PROPERTIES TIMEOUT 15) + endif() add_test_executable(guest_directory_entry_tests ${LIBS_INCLUDE_DIR}/tests/GuestDirectoryEntries.cpp) target_include_directories(guest_directory_entry_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_directory_entry_tests PRIVATE libkernel libc) @@ -1819,6 +1965,16 @@ if(BUILD_TESTING) configure_windows_unwind(guest_append_writes_tests) add_test(NAME guest_append_writes COMMAND guest_append_writes_tests) set_tests_properties(guest_append_writes PROPERTIES TIMEOUT 20) + add_test_executable(guest_kernel_fcntl_sync_tests ${LIBS_INCLUDE_DIR}/tests/GuestKernelFcntlSync.cpp) + target_include_directories(guest_kernel_fcntl_sync_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_kernel_fcntl_sync_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_kernel_fcntl_sync_tests) + add_test(NAME guest_kernel_fcntl_sync COMMAND guest_kernel_fcntl_sync_tests) + set_tests_properties(guest_kernel_fcntl_sync PROPERTIES TIMEOUT 15) + if(WIN32) + set_tests_properties(guest_kernel_fcntl_sync PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(WIN32) set_tests_properties(guest_kernel_fcntl guest_append_writes PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") @@ -1826,7 +1982,7 @@ if(BUILD_TESTING) target_include_directories(guest_file_lock_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_file_lock_tests PRIVATE libkernel libc) configure_windows_unwind(guest_file_lock_tests) - foreach(fileLockCase IN ITEMS duplicate lifetime conversion threads streams process) + foreach(fileLockCase IN ITEMS duplicate lifetime conversion threads streams process directory) add_test(NAME guest_file_lock_${fileLockCase} COMMAND guest_file_lock_tests ${fileLockCase}) set_tests_properties(guest_file_lock_${fileLockCase} PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") @@ -1855,6 +2011,11 @@ if(BUILD_TESTING) add_test(NAME guest_raise_in_system_call COMMAND guest_raise_in_system_call_tests) set_tests_properties(guest_raise_in_system_call PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + foreach(delay IN ITEMS raise return) + add_test(NAME guest_raise_in_system_call_delay_${delay} COMMAND guest_raise_in_system_call_tests --delay-${delay}) + set_tests_properties(guest_raise_in_system_call_delay_${delay} PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endforeach() add_test_executable(guest_raise_in_host_code_tests ${LIBS_INCLUDE_DIR}/tests/GuestRaiseInHostCode.cpp) target_include_directories(guest_raise_in_host_code_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_raise_in_host_code_tests PRIVATE libkernel libc) @@ -1903,6 +2064,30 @@ if(BUILD_TESTING) endif() if(BUILD_TESTING) + add_test_executable(guest_timed_join_tests ${LIBS_INCLUDE_DIR}/tests/GuestTimedJoin.cpp) + target_include_directories(guest_timed_join_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_timed_join_tests PRIVATE libkernel libc ${CMAKE_DL_LIBS}) + configure_windows_unwind(guest_timed_join_tests) + if(WIN32 AND Python3_Interpreter_FOUND) + foreach(joinProvider IN ITEMS libkernel_sys libkernel_web) + add_test(NAME guest_timed_join_import_${joinProvider} COMMAND ${Python3_EXECUTABLE} + ${LIBS_INCLUDE_DIR}/tests/test_windows_timed_join_imports.py $ ${PATCHED_DIR} + ${joinProvider}.prx $) + set_tests_properties(guest_timed_join_import_${joinProvider} PROPERTIES TIMEOUT 25 + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endforeach() + endif() + foreach(joinMode IN ITEMS normal coarse) + add_test(NAME guest_timed_join_${joinMode} COMMAND guest_timed_join_tests $) + set_tests_properties(guest_timed_join_${joinMode} PROPERTIES TIMEOUT 15) + if(joinMode STREQUAL coarse) + set_tests_properties(guest_timed_join_${joinMode} PROPERTIES ENVIRONMENT "APS5_COARSE_TIMED_WAITS=1") + endif() + if(WIN32) + set_tests_properties(guest_timed_join_${joinMode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() add_test_executable(guest_sleep_cancel_tests ${LIBS_INCLUDE_DIR}/tests/GuestSleepCancel.cpp) target_include_directories(guest_sleep_cancel_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_sleep_cancel_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 82e570403e..4247868360 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -1216,6 +1216,9 @@ int DoMapShared(void** addr, size_t len, int prot, HostSharedMemory object) { try { #ifdef _WIN32 GuestArena::GuestArenaMap_nid_postfix(mapped, len, object, 0, WinProtFromPosix(nativeProtection)); +#elif defined(__APPLE__) + if (::mmap(mapped, len, nativeProtection & ~PROT_EXEC, MAP_SHARED | MAP_FIXED, object, 0) == MAP_FAILED) throw std::system_error(errno, std::generic_category(), "map shared memory"); + if ((nativeProtection & PROT_EXEC) != 0 && ::mprotect(mapped, len, nativeProtection) != 0) throw std::system_error(errno, std::generic_category(), "make shared memory executable"); #else if (::mmap(mapped, len, nativeProtection, MAP_SHARED | MAP_FIXED, object, 0) == MAP_FAILED) throw std::system_error(errno, std::generic_category(), "map shared memory"); #endif diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 79d07266cb..93101cb207 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include #include #include @@ -32,8 +34,6 @@ #include extern "C" std::uint64_t GuestImageSegmentBytes_nid_no_patch(); -extern "C" void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protection, int flags, int descriptor, std::int64_t offset) noexcept; -extern "C" int* APS5_VABI __error_nid_postfix(); namespace { @@ -218,13 +218,12 @@ int APS5_VABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, in } int APS5_VABI sceKernelAvailableDirectMemorySize(int64_t search_start, int64_t search_end, size_t alignment, int64_t* phys_addr_out, size_t* size_out) { - if (!phys_addr_out || !size_out) return SCE_KERNEL_ERROR_EINVAL; int64_t start = 0; size_t size = 0; const int result = DirectMemoryLargestFree(search_start, search_end, alignment, &start, &size); - if (result != 0) { *phys_addr_out = 0; *size_out = 0; return result; } - *phys_addr_out = start; - *size_out = size; + if (result != 0) return result; + if (phys_addr_out) *phys_addr_out = start; + if (size_out) *size_out = size; return 0; } @@ -277,8 +276,6 @@ int APS5_VABI sceKernelMapFlexibleMemory(void** addr_in_out, size_t len, int pro return _mapFlexible(addr_in_out, len, prot, flags & ~IgnoredHeapFlag); } -int APS5_VABI sceKernelSetVirtualRangeName(const void* addr, uint64_t len, const char* name); - int APS5_VABI sceKernelMapNamedDirectMemory(void** addr, size_t len, int prot, int flags, int64_t direct_memory_start, size_t alignment, const char* name) { const int result = DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); if (result == 0) _releaseFlexible(reinterpret_cast(*addr), len); @@ -320,7 +317,11 @@ int APS5_VABI sceKernelMmap(void* addr, size_t len, int prot, int flags, int fd, } int APS5_VABI sceKernelReleaseFlexibleMemory(void* addr, size_t len) { - return sceKernelMunmap(reinterpret_cast(addr), len); + const auto start = reinterpret_cast(addr); + if (start == 0 || (start & (PS5_PAGE_SIZE - 1)) != 0 || (len & (PS5_PAGE_SIZE - 1)) != 0 || len > std::numeric_limits::max() - start) + return SCE_KERNEL_ERROR_EINVAL; + if (len != 0) _releaseFlexible(start, len); + return 0; } int APS5_VABI sceKernelReleaseDirectMemory(int64_t start, size_t len) { @@ -465,10 +466,9 @@ int APS5_VABI sceKernelConfiguredFlexibleMemorySize(size_t* size) { return 0; } -int APS5_VABI sceKernelInternalMemoryGetAvailableSize(size_t* size) { - if (!size) return SCE_KERNEL_ERROR_EINVAL; - *size = 0; - return 0; +int APS5_VABI sceKernelInternalMemoryGetAvailableSize(std::size_t* size) { + if (!size) return -1; + throw std::logic_error("sceKernelInternalMemoryGetAvailableSize: internal pool statistics are not implemented"); } int APS5_VABI sceKernelSetVirtualRangeName(const void* addr, uint64_t len, const char* name) { @@ -502,6 +502,12 @@ int APS5_VABI sceKernelGetPageTableStats(int* cpu_total, int* cpu_available, int return 0; } +int APS5_VABI sceKernelGetTraceMemoryStats(std::uint64_t* first, std::uint64_t* second) { + if (first) *first = 0; + if (second) *second = 0; + return 0; +} + int APS5_VABI sceKernelGetPrtAperture(int index, void** addr, size_t* len) { if (index < 0 || index >= PRT_APERTURE_COUNT || !addr || !len) return SCE_KERNEL_ERROR_EINVAL; std::lock_guard lock(g_prtLock); @@ -517,8 +523,6 @@ int APS5_VABI sceKernelSetPrtAperture(int index, void* addr, size_t len) { return 0; } -int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int num_entries, int* num_entries_out, int flags); - int APS5_VABI sceKernelBatchMap(KernelBatchMapEntry* entries, int num_entries, int* num_entries_out) { constexpr int MapFixed = 0x10; return sceKernelBatchMap2(entries, num_entries, num_entries_out, MapFixed); diff --git a/core/libs/prx/libkernel/DirectMemory/Jit.cpp b/core/libs/prx/libkernel/DirectMemory/Jit.cpp index fb3ad0405c..60e266a543 100644 --- a/core/libs/prx/libkernel/DirectMemory/Jit.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Jit.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" @@ -26,6 +27,9 @@ #include #include #include +#ifdef __APPLE__ +#include +#endif #endif namespace { @@ -140,8 +144,19 @@ int DuplicateDescriptor(int descriptor) { } #else int CreateBacking(std::size_t bytes, int* descriptor, std::shared_ptr* object) { +#ifdef __APPLE__ + auto path = (std::filesystem::temp_directory_path() / "anyps5-jit-XXXXXX").string(); + const int file = mkstemp(path.data()); + if (file < 0) return HostFailure("create JIT shared memory", errno); + if (unlink(path.c_str()) != 0 || fcntl(file, F_SETFD, FD_CLOEXEC) != 0) { + const int error = errno; + ::close(file); + return HostFailure("create JIT shared memory", error); + } +#else const int file = memfd_create("anyps5 jit shared memory", MFD_CLOEXEC); if (file < 0) return HostFailure("create JIT shared memory", errno); +#endif const bool representable = bytes <= static_cast(std::numeric_limits::max()); if (!representable || ftruncate(file, static_cast(bytes)) != 0) { const int error = !representable || errno == EFBIG ? ENOMEM : errno; diff --git a/core/libs/prx/libkernel/DirectMemory/MemoryPool.cpp b/core/libs/prx/libkernel/DirectMemory/MemoryPool.cpp index 54cdbaf12d..44a02c7f12 100644 --- a/core/libs/prx/libkernel/DirectMemory/MemoryPool.cpp +++ b/core/libs/prx/libkernel/DirectMemory/MemoryPool.cpp @@ -154,14 +154,10 @@ struct PhysicalMemoryPool { } int LargestFree(int64_t searchStart, int64_t searchEnd, size_t alignment, int64_t* startOut, size_t* sizeOut) { - if (!startOut || !sizeOut) return SCE_KERNEL_ERROR_EINVAL; - if (searchStart < 0 || searchEnd <= searchStart) return SCE_KERNEL_ERROR_EAGAIN; - uint64_t align; - if (!NormalizeAlignment(alignment, align)) return SCE_KERNEL_ERROR_EAGAIN; - - uint64_t cur; - if (!RoundUp(static_cast(searchStart), kPage, cur)) return SCE_KERNEL_ERROR_EAGAIN; - const uint64_t limit = std::min(static_cast(searchEnd), kPoolBytes) & ~(kPage - 1); + if ((alignment & (alignment - 1)) != 0) return SCE_KERNEL_ERROR_EINVAL; + const uint64_t alignMask = std::max(alignment, kPage) - 1; + uint64_t cur = searchStart > 0 ? std::min(static_cast(searchStart) & ~(kPage - 1), kPoolBytes) : 0; + const uint64_t limit = searchEnd > 0 ? std::min((static_cast(searchEnd) + kPage - 1) & ~(kPage - 1), kPoolBytes) : 0; std::lock_guard lock(_mutex); uint64_t bestStart = 0; @@ -175,11 +171,13 @@ struct PhysicalMemoryPool { uint64_t runEnd = cur; while (runEnd < limit && !_used[runEnd / kPage]) runEnd += kPage; - uint64_t alignedStart; - if (!RoundUp(cur, align, alignedStart)) break; - if (alignedStart < runEnd && runEnd - alignedStart > bestSize) { - bestStart = alignedStart; - bestSize = runEnd - alignedStart; + const int64_t low = std::max(static_cast(cur), searchStart); + const int64_t high = std::min(static_cast(runEnd), searchEnd); + const int64_t alignedStart = static_cast((static_cast(low) + alignMask) & ~alignMask); + const uint64_t size = static_cast(high) - static_cast(alignedStart); + if (low < high && alignedStart < high && size > bestSize) { + bestStart = static_cast(alignedStart); + bestSize = size; } cur = runEnd; } @@ -267,7 +265,15 @@ private: bool splitHi = false; try { splitHi = _splitAt(hi); - ForgetDirectMemory(static_cast(lo), static_cast(hi - lo)); + for (size_t page = first; page < first + count;) { + if (!_used[page]) { + ++page; + continue; + } + const size_t runStart = page; + while (page < first + count && _used[page]) ++page; + ForgetDirectMemory(static_cast(runStart * kPage), static_cast((page - runStart) * kPage)); + } } catch (...) { if (splitHi) _unsplitAt(hi); if (splitLo) _unsplitAt(lo); diff --git a/core/libs/prx/libkernel/DirectMemory/MemoryPoolExport.cpp b/core/libs/prx/libkernel/DirectMemory/MemoryPoolExport.cpp index 7f15f884a1..d20fb3c364 100644 --- a/core/libs/prx/libkernel/DirectMemory/MemoryPoolExport.cpp +++ b/core/libs/prx/libkernel/DirectMemory/MemoryPoolExport.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include diff --git a/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp b/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp index 55d3b242d1..24ad4c0331 100644 --- a/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "DirectMemory.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -21,16 +23,6 @@ #include "prx/libkernel/File/include/RandomDevice.hpp" #endif -extern "C" int* APS5_VABI __error_nid_postfix(); -extern "C" int APS5_VABI sceKernelMunmap(std::uint64_t address, std::size_t length); -extern "C" int APS5_VABI sceKernelMlock_nid_postfix(void* address, std::uint64_t length); -extern "C" int APS5_VABI sceKernelMunlock_nid_postfix(void* address, std::uint64_t length); -extern "C" int APS5_VABI sceKernelMunlockall_nid_postfix(); -extern "C" int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, uint64_t info_size); -extern "C" std::int64_t APS5_VABI sceKernelPread(int descriptor, void* buffer, std::size_t length, std::int64_t offset); -extern "C" int APS5_VABI munmap_nid_postfix(void* address, std::size_t length) noexcept; -extern "C" int APS5_VABI mprotect_nid_postfix(void* address, std::size_t length, int protection) noexcept; - namespace { // FreeBSD/PS5 ABI values, independent of the host's errno and mmap constants. constexpr int GuestInvalid = 22; @@ -38,6 +30,7 @@ constexpr int GuestNoMemory = 12; constexpr int GuestBadDescriptor = 9; constexpr int GuestAccess = 13; constexpr int GuestFault = 14; +constexpr int GuestBusy = 16; constexpr int GuestIoError = 5; constexpr int GuestNotSupported = 45; constexpr int GuestShared = 0x1; @@ -53,6 +46,7 @@ constexpr int GuestAdviceCore = 9; constexpr int GuestAdviceProtect = 10; constexpr int GuestLockCurrent = 0x1; constexpr int GuestLockFuture = 0x2; +constexpr int GuestDefinedProtection = 0x3F7; constexpr std::uintptr_t GuestUserAddressEnd = 0x800000000000; constexpr int GuestAlignedSuper = 0x1000000; constexpr std::size_t SuperpageSize = std::size_t{1} << 21; @@ -129,6 +123,7 @@ void* MapPrivateFileCopy(void* address, std::size_t rounded, int protection, int const auto read = sceKernelPread(descriptor, static_cast(mapped) + done, static_cast(copied - done), static_cast(start + done)); if (read <= 0) { munmap_nid_postfix(mapped, rounded); + return failed(read < 0 ? static_cast(read & 0xffff) : GuestIoError); } done += static_cast(read); @@ -136,6 +131,7 @@ void* MapPrivateFileCopy(void* address, std::size_t rounded, int protection, int if (protection != 3 && mprotect_nid_postfix(mapped, rounded, protection) != 0) { const int error = *__error_nid_postfix(); munmap_nid_postfix(mapped, rounded); + return failed(error); } return mapped; @@ -155,7 +151,9 @@ void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protecti if (!RoundLength(length, rounded) || (protection & ~7) != 0) return failed(GuestInvalid); if ((flags & (GuestMapExclusive | GuestMapFixed)) == GuestMapExclusive) return failed(GuestInvalid); - const int mappingFlags = flags & ~GuestAlignedSuper; + if ((flags & (GuestShared | GuestPrivate)) == (GuestShared | GuestPrivate)) return failed(GuestInvalid); + int mappingFlags = flags & ~GuestAlignedSuper; + if ((mappingFlags & (GuestShared | GuestPrivate | GuestAnonymous)) == GuestAnonymous) mappingFlags |= GuestPrivate; const bool fixedAnonymous = (mappingFlags & ~(GuestMapFixed | GuestMapExclusive)) == (GuestPrivate | GuestAnonymous) && (mappingFlags & GuestMapFixed) != 0; if (mappingFlags == (GuestPrivate | GuestAnonymous) || fixedAnonymous) { @@ -168,7 +166,7 @@ void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protecti #endif const bool exclusive = fixedAnonymous && (mappingFlags & GuestMapExclusive) != 0; const int placement = fixedAnonymous ? GuestMapFixed | (exclusive ? GuestMapNoOverwrite : 0) : 0; - void* mapped = fixedAnonymous ? address : nullptr; + void* mapped = address; const auto alignment = !fixedAnonymous && (flags & GuestAlignedSuper) != 0 ? SuperpageSize : PS5_PAGE_SIZE; try { const auto result = DoMapAnon(&mapped, rounded, protection, placement, alignment, exclusive, fixedAnonymous); @@ -279,7 +277,7 @@ int APS5_VABI mprotect_nid_postfix(void* address, std::size_t length, int protec if (start == 0 || length > std::numeric_limits::max() - start) return failed(GuestInvalid); try { - if (DoMprotect(address, length, protection) != 0) return failed(GuestInvalid); + if (DoMprotect(address, length, protection & GuestDefinedProtection) != 0) return failed(GuestInvalid); return 0; } catch (const std::bad_alloc&) { return failed(GuestNoMemory); @@ -334,7 +332,7 @@ int APS5_VABI msync_nid_postfix(void* address, std::size_t length, int flags) { if (end < start) return failed(GuestInvalid); if ((flags & (GuestSyncAsync | GuestSyncInvalidate)) == (GuestSyncAsync | GuestSyncInvalidate)) return failed(GuestInvalid); if (start == end ? !Mapped(start) : !GuestRangeMapped(start, end)) return failed(GuestNoMemory); - if ((flags & GuestSyncInvalidate) != 0) throw std::runtime_error("msync: MS_INVALIDATE is not implemented"); + if ((flags & GuestSyncInvalidate) != 0) return failed(GuestBusy); return 0; } @@ -365,6 +363,7 @@ int APS5_VABI mincore_nid_postfix(const void* address, std::size_t length, char* for (auto cursor = first; cursor < end;) { VirtualQueryInfo info{}; sceKernelVirtualQuery(reinterpret_cast(cursor), 0, &info, sizeof(info)); + const auto extentEnd = std::min(info.end, end); std::fill_n(vec + (cursor - first) / PS5_PAGE_SIZE, (extentEnd - cursor) / PS5_PAGE_SIZE, static_cast(info.is_committed != 0 ? 1 : 0)); cursor = extentEnd; diff --git a/core/libs/prx/libkernel/Equeue/Equeue.cpp b/core/libs/prx/libkernel/Equeue/Equeue.cpp index 1256cc9391..059f00798c 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.cpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Equeue/Equeue.hpp" #include "Equeue.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libc/include/General.hpp" @@ -12,6 +14,62 @@ #include #include +static constexpr std::uint64_t FileRepollNs = 1000000; + +struct UnreadFile { + bool measured = false; + std::int64_t bytes = 0; +}; + +static UnreadFile UnreadFileBytes(int file) { + FileStat status{}; + if (sceKernelFstat(file, &status) != 0) { + return {}; + } + const std::int64_t offset = sceKernelLseek(file, 0, 1); + if (offset < 0) { + return {}; + } + return {true, status.st_size - offset}; +} + +static bool RegularFile(int descriptor) { + if (File::IsHostStream(descriptor)) { + return false; + } + FileStat status{}; + return sceKernelFstat(descriptor, &status) == 0 && (status.st_mode & 0170000) == 0100000; +} + +static KernelEqueueEvent FileEvent(int descriptor, int16_t filter, uint16_t flags, void* udata) { + KernelEqueueEvent event{}; + event.event.ident = static_cast(descriptor); + event.event.filter = filter; + event.event.flags = flags; + event.event.udata = udata; + event.filter.repoll = true; + if (filter == EVFILT_WRITE) { + event.filter.pollFunc = [](KernelEqueueEvent* e) { + e->event.data = 0; + return true; + }; + } else { + event.filter.owner = std::make_shared(); + event.filter.pollFunc = [](KernelEqueueEvent* e) { + const auto* unread = static_cast(e->filter.owner.get()); + if (!unread->measured || unread->bytes == 0) { + return false; + } + e->event.data = static_cast(unread->bytes); + return true; + }; + } + event.filter.resetFunc = [](KernelEqueueEvent* e) { + e->triggered = false; + }; + return event; +} + static std::unordered_map g_equeues; static std::mutex g_equeueMutex; static uint64_t g_nextEqueue = 1; @@ -21,6 +79,7 @@ struct KernelEqueuePrivate::WaitSet { std::vector> held; std::vector> queues; uint64_t nearestTimerNs = std::numeric_limits::max(); + bool repoll = false; }; extern "C" { @@ -98,6 +157,28 @@ bool KernelEqueuePrivate::PollEvents() { return polled; } +void KernelEqueuePrivate::MeasureFiles(std::unique_lock& lock) { + std::vector>> files; + for (const auto& e : m_events) { + if (e.enabled && e.filter.repoll && e.event.filter == EVFILT_READ) { + files.emplace_back(static_cast(e.event.ident), e.filter.owner); + } + } + if (files.empty()) { + return; + } + lock.unlock(); + std::vector measured; + measured.reserve(files.size()); + for (const auto& [file, owner] : files) { + measured.push_back(UnreadFileBytes(file)); + } + lock.lock(); + for (std::size_t i = 0; i < files.size(); ++i) { + *static_cast(files[i].second.get()) = measured[i]; + } +} + void KernelEqueuePrivate::WakePollers() { for (const auto waker : m_pollers) { GuestSockets::Wake(waker); @@ -117,6 +198,8 @@ void KernelEqueuePrivate::GatherInterests(WaitSet& wait, std::uintptr_t waker, b } if (e.filter.watched) { GatherWatched(wait, e.filter.watched, waker); + } else if (e.filter.repoll) { + wait.repoll = true; } else if (e.filter.waitDescriptor >= 0) { wait.interests.push_back({e.filter.waitDescriptor, false}); wait.held.push_back(e.filter.owner); @@ -151,6 +234,12 @@ void KernelEqueuePrivate::WaitForDescriptors(std::unique_lock& lock, const auto waker = GuestSockets::CurrentWaker(); WaitSet wait; GatherInterests(wait, waker, false); + if (wait.repoll) { + const std::uint64_t soon = TimedWait::NowNanos() + FileRepollNs; + if (deadlineNanos == 0 || deadlineNanos > soon) { + deadlineNanos = soon; + } + } m_pollers.push_back(waker); if (!wait.queues.empty()) { PollEvents(); @@ -187,9 +276,10 @@ int KernelEqueuePrivate::CollectTriggered(KernelEvent* ev, int num) { return CollectOncePerWait(ev, num); } int ret = 0; + std::list requeued; for (auto it = m_events.begin(); it != m_events.end() && ret < num;) { auto& e = *it; - if (!e.enabled) { + if (!e.triggered || !e.enabled) { ++it; continue; } @@ -197,6 +287,10 @@ int KernelEqueuePrivate::CollectTriggered(KernelEvent* ev, int num) { while (e.triggered && ret < num) { ev[ret++] = e.event; if ((e.event.flags & EV_ONESHOT) != 0) { + if (e.filter.deleteEventFunc != nullptr) { + auto owner = e.filter.owner; + e.filter.deleteEventFunc(m_handle, &e); + } erase = true; break; } @@ -221,8 +315,15 @@ int KernelEqueuePrivate::CollectTriggered(KernelEvent* ev, int num) { e.pendingEvents.pop_front(); e.triggered = true; } - it = erase ? m_events.erase(it) : std::next(it); + const auto next = std::next(it); + if (erase) { + m_events.erase(it); + } else { + requeued.splice(requeued.end(), m_events, it); + } + it = next; } + m_events.splice(m_events.end(), requeued); return ret; } @@ -257,9 +358,7 @@ int KernelEqueuePrivate::CollectOncePerWait(KernelEvent* ev, int num) { e.event.data = 0; } const auto next = std::next(it); - if (e.triggered) { - requeued.splice(requeued.end(), m_events, it); - } + requeued.splice(requeued.end(), m_events, it); it = next; } m_events.splice(m_events.end(), requeued); @@ -271,6 +370,10 @@ int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) { if (m_closed) { return SCE_KERNEL_ERROR_EBADF; } + MeasureFiles(lock); + if (m_closed) { + return SCE_KERNEL_ERROR_EBADF; + } TriggerExpiredTimers(MonotonicNs()); PollEvents(); return CollectTriggered(ev, num); @@ -285,6 +388,10 @@ std::size_t KernelEqueuePrivate::CountTriggeredEvents() { if (m_closed) { return 0; } + MeasureFiles(lock); + if (m_closed) { + return 0; + } TriggerExpiredTimers(MonotonicNs()); PollEvents(); return static_cast(std::count_if(m_events.begin(), m_events.end(), [](const KernelEqueueEvent& event) { return event.enabled && event.triggered; })); @@ -311,6 +418,10 @@ int KernelEqueuePrivate::WaitForEventsUntil(KernelEvent* ev, int num, std::uint6 return SCE_KERNEL_ERROR_EBADF; } for (;;) { + MeasureFiles(lock); + if (m_closed) { + return SCE_KERNEL_ERROR_EBADF; + } TriggerExpiredTimers(MonotonicNs()); const bool polling = PollEvents(); const int ret = CollectTriggered(ev, num); @@ -414,6 +525,9 @@ int KernelEqueuePrivate::EditEvent(uintptr_t ident, int16_t filter, const std::f if (m_closed) { return SCE_KERNEL_ERROR_EBADF; } + if (filter == EVFILT_TIMER) { + TriggerExpiredTimers(MonotonicNs()); + } auto it = std::find_if(m_events.begin(), m_events.end(), [ident, filter](const auto& e) { return e.event.ident == ident && e.event.filter == filter; @@ -503,7 +617,6 @@ int APS5_VABI EqueueDeleteEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, in return owner->DeleteEvent(ident, filter); } - int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name) { if (eq == nullptr || name == nullptr) { return SCE_KERNEL_ERROR_EINVAL; @@ -685,6 +798,7 @@ int APS5_VABI sceKernelAddAmprEvent(KernelEqueue eq, int id, void* udata) { } }; EqueueAddEvent_nid_postfix(eq, event); + return EQUEUE_OK; } @@ -697,6 +811,7 @@ int APS5_VABI sceKernelDeleteAmprEvent(KernelEqueue eq, int id) { return EQUEUE_OK; } EqueueDeleteEvent_nid_postfix(eq, static_cast(id), EVFILT_AMPR); + return EQUEUE_OK; } @@ -709,15 +824,19 @@ static int AddDescriptorEvent(KernelEqueue eq, int fd, std::size_t size, void* u std::int64_t available = 0; bool eof = false; const bool isPipe = !isSocket && filter == EVFILT_READ && !File::IsHostStream(fd) && GuestSockets::PipeReadState(fd, &available, &eof); - if (!isSocket && !isPipe && DescriptorIsOpen_nid_no_patch(fd)) { - NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: descriptor that is neither a socket nor the read end of a pipe" : "sceKernelAddWriteEvent: non-socket descriptor"); + const bool isFile = !isSocket && !isPipe && RegularFile(fd); + if (!isSocket && !isPipe && !isFile && DescriptorIsOpen_nid_no_patch(fd)) { + NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: descriptor that is neither a socket, the read end of a pipe nor a regular file" : "sceKernelAddWriteEvent: descriptor that is neither a socket nor a regular file"); } if (size > 1 && !isPipe) { NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: low watermark" : "sceKernelAddWriteEvent: low watermark"); } - if (!isSocket && !isPipe) { + if (!isSocket && !isPipe && !isFile) { return SCE_KERNEL_ERROR_EBADF; } + if (isFile) { + return EqueueAddEvent_nid_postfix(eq, FileEvent(fd, filter, EV_ADD, udata)); + } return EqueueAddEvent_nid_postfix(eq, EqueueDescriptorEvent(fd, filter, EV_ADD, udata)); } diff --git a/core/libs/prx/libkernel/Equeue/Equeue.hpp b/core/libs/prx/libkernel/Equeue/Equeue.hpp index ef217632d6..6b9194be56 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.hpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.hpp @@ -57,6 +57,7 @@ struct KernelFilter { EqueueDeleteFunc deleteEventFunc = nullptr; EqueuePollFunc pollFunc = nullptr; int waitDescriptor = -1; + bool repoll = false; std::shared_ptr watched; }; @@ -104,6 +105,7 @@ private: int CollectTriggered(KernelEvent* ev, int num); int CollectOncePerWait(KernelEvent* ev, int num); bool PollEvents(); + void MeasureFiles(std::unique_lock& lock); void WakePollers(); void WaitForDescriptors(std::unique_lock& lock, std::uint64_t deadlineNanos); void GatherInterests(WaitSet& wait, std::uintptr_t waker, bool nested); diff --git a/core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp b/core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp index e72bfe3900..9065ffd156 100644 --- a/core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp +++ b/core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include @@ -21,7 +22,13 @@ intptr_t APS5_VABI sceKernelGetEventData(const KernelEvent* ev) { int APS5_VABI sceKernelGetEventError(const KernelEvent* ev) { const KernelEvent& event = requireEvent(ev, __func__); - return (event.flags & EV_ERROR) != 0 ? static_cast(event.data) : 0; + if (event.fflags == 0) { + return 0; + } + if (event.fflags > 0xFFFFu) { + throw std::runtime_error(std::string(__func__) + ": fflags " + std::to_string(event.fflags) + " above 0xFFFF"); + } + return static_cast(0x80020000u | event.fflags); } intptr_t APS5_VABI sceKernelGetEventFflags(const KernelEvent* ev) { diff --git a/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp b/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp index 04fe98539f..a0b5e49fd3 100644 --- a/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp +++ b/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/File/include/File.hpp" @@ -165,6 +166,16 @@ struct Snapshot { }; bool TakeSnapshot(HANDLE handle, Snapshot& snapshot) { + std::wstring path(MAX_PATH, L'\0'); + auto length = ::GetFinalPathNameByHandleW(handle, path.data(), static_cast(path.size()), FILE_NAME_NORMALIZED); + if (length >= path.size()) { + path.resize(length); + length = ::GetFinalPathNameByHandleW(handle, path.data(), length, FILE_NAME_NORMALIZED); + } + if (length != 0 && length < path.size()) { + path.resize(length); + snapshot.path = std::move(path); + } FILE_BASIC_INFO basic{}; FILE_STANDARD_INFO standard{}; if (!::GetFileInformationByHandleEx(handle, FileBasicInfo, &basic, sizeof(basic)) || @@ -178,21 +189,11 @@ bool TakeSnapshot(HANDLE handle, Snapshot& snapshot) { snapshot.creation = basic.CreationTime.QuadPart; snapshot.lastWrite = basic.LastWriteTime.QuadPart; snapshot.change = basic.ChangeTime.QuadPart; - std::wstring path(MAX_PATH, L'\0'); - auto length = ::GetFinalPathNameByHandleW(handle, path.data(), static_cast(path.size()), FILE_NAME_NORMALIZED); - if (length >= path.size()) { - path.resize(length); - length = ::GetFinalPathNameByHandleW(handle, path.data(), length, FILE_NAME_NORMALIZED); - } - if (length != 0 && length < path.size()) { - path.resize(length); - snapshot.path = std::move(path); - } return true; } bool IsDeleted(const Snapshot& snapshot) { - return snapshot.deletePending || snapshot.links == 0; + return snapshot.deletePending || snapshot.links == 0 || snapshot.path.find(L"\\$Extend\\$Deleted\\") != std::wstring::npos; } std::vector SnapshotHints(const Snapshot& before, const Snapshot& after, bool directory) { @@ -212,7 +213,7 @@ std::vector SnapshotHints(const Snapshot& before, const Snapshot& } else if (!directory && hints.empty() && after.change != before.change) { hints.push_back(NOTE_ATTRIB); } - if ((after.deletePending && !before.deletePending) || after.links < before.links) { + if ((after.deletePending && !before.deletePending) || after.links < before.links || (!IsDeleted(before) && IsDeleted(after))) { hints.push_back(NOTE_DELETE); } if (!after.path.empty() && after.path != before.path && !IsDeleted(after)) { diff --git a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp index 917bc4a8f8..cbddf855ce 100644 --- a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp +++ b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp @@ -1,8 +1,14 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" #include "prx/libkernel/Equeue/include/EdgeWatch.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/File/include/ArchivedFile.hpp" +#include "prx/libkernel/File/include/DirectoryDescriptor.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/HostStreams.hpp" +#include "prx/libkernel/File/include/RandomDevice.hpp" +#include "prx/libkernel/Socket/include/SocketPoll.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" @@ -19,17 +25,18 @@ #include #ifdef _WIN32 +#include "prx/libc/include/WindowsDescriptor.hpp" #include #include #else #include +#include #include #endif -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { +constexpr int GUEST_EPERM = 1; constexpr int GUEST_ENOENT = 2; constexpr int GUEST_EBADF = 9; constexpr int GUEST_EFAULT = 14; @@ -39,6 +46,10 @@ constexpr int GUEST_EMFILE = 24; constexpr int GUEST_EPIPE = 32; constexpr int KQ_NEVENTS = 8; +constexpr std::int16_t EVFILT_AIO = -3; +constexpr std::int16_t EVFILT_PROCDESC = -8; +constexpr std::int16_t EVFILT_LIO = -10; +constexpr std::int16_t EVFILT_SENDFILE = -12; constexpr std::uint32_t NOTE_FFCTRLMASK = 0xc0000000; constexpr std::uint32_t NOTE_FFLAGSMASK = 0x00ffffff; constexpr std::uint32_t NOTE_FFAND = 0x40000000; @@ -128,6 +139,27 @@ int DescriptorFilterError(int kqueueDescriptor, std::uintptr_t ident) { return descriptor == kqueueDescriptor ? GUEST_EINVAL : 0; } +bool MeasuredProcdescDescriptor(int descriptor) { + std::shared_ptr owner; + std::uintptr_t native = 0; + if (GuestSockets::IsOpen(descriptor) || KernelLookupSocketNative_nid_no_patch(descriptor, &owner, &native) || FindKqueue(descriptor)) { + return true; + } + if (descriptor < 3 || File::IsRandomDevice(descriptor)) { + return false; + } + if (File::IsArchivedFile(descriptor) || File::DirectoryDescriptorPath(descriptor)) { + return true; + } +#ifdef _WIN32 + const DWORD type = ::GetFileType(WindowsDescriptorHandle_nid_no_patch(descriptor)); + return type == FILE_TYPE_DISK || type == FILE_TYPE_PIPE; +#else + struct stat status{}; + return ::fstat(descriptor, &status) == 0 && (S_ISREG(status.st_mode) || S_ISDIR(status.st_mode) || S_ISFIFO(status.st_mode) || S_ISSOCK(status.st_mode)); +#endif +} + std::int64_t DescriptorLowWater(const KernelEvent& change) { if ((change.fflags & NOTE_LOWAT) == 0 || !GuestSockets::IsOpen(static_cast(change.ident))) { return 0; @@ -327,8 +359,49 @@ void TouchUserKnote(KernelEqueueEvent& event, KernelEvent& change) { } } +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); + 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) { + if (event.intervalNs == 0) { + oneShot = true; + return; + } + if (!event.enabled) { + disabled = true; + return; + } + if (*periodNs > std::numeric_limits::max() - event.deadlineNs) { + overflow = true; + return; + } + event.intervalNs = *periodNs; + event.event.udata = change.udata; + })); + 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"); + } + change.fflags = 0; + change.data = 0; + return error; +} + int ModifyKnote(const KernelEqueueRef& queue, KernelEvent& change) { - if (change.filter == EVFILT_TIMER) { + if (change.filter == EVFILT_TIMER && ((change.flags & EV_ADD) != 0 || change.data != 0 || change.fflags != 0)) { NotImplemented_nid_no_patch("kevent: changing an existing EVFILT_TIMER"); } const bool descriptor = change.filter == EVFILT_READ || change.filter == EVFILT_WRITE; @@ -369,6 +442,28 @@ int Register(const KernelEqueueRef& queue, int kqueueDescriptor, KernelEvent& ch if (change.filter > 0) { return GUEST_EINVAL; } + if (change.filter == EVFILT_AIO || change.filter == EVFILT_LIO || change.filter == EVFILT_SENDFILE) { + if ((change.flags & EV_ADD) == 0) { + return GUEST_ENOENT; + } + change.fflags = 0; + change.data = 0; + return GUEST_EPERM; + } + if (change.filter == EVFILT_PROCDESC) { + if (const int error = DescriptorFilterError(kqueueDescriptor, change.ident)) { + return error; + } + if ((change.flags & EV_ADD) == 0) { + return GUEST_ENOENT; + } + if (!MeasuredProcdescDescriptor(static_cast(change.ident))) { + NotImplemented_nid_no_patch("kevent: EVFILT_PROCDESC on a kind of descriptor that was not measured"); + } + change.fflags = 0; + change.data = 0; + return GUEST_EINVAL; + } if (change.filter != EVFILT_READ && change.filter != EVFILT_WRITE && change.filter != EVFILT_VNODE && change.filter != EVFILT_TIMER && change.filter != EVFILT_USER) { NotImplemented_nid_no_patch(("kevent: filter " + std::to_string(change.filter)).c_str()); } @@ -403,6 +498,9 @@ int Register(const KernelEqueueRef& queue, int kqueueDescriptor, KernelEvent& ch if ((change.flags & EV_DELETE) != 0) { return GuestErrno(queue->DeleteEvent(change.ident, change.filter)); } + if (change.filter == EVFILT_TIMER && (change.flags & EV_ADD) != 0) { + return ModifyTimerKnote(queue, change); + } return ModifyKnote(queue, change); } diff --git a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp index a6403ce990..29f13e6df2 100644 --- a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp +++ b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceSaveData/SaveDataCore.hpp" +#include "prx/libkernel/include/Api.hpp" #include #include #include @@ -14,21 +16,27 @@ #include #include #include +#include #include static constexpr int SCE_OK = 0; static constexpr int SCE_KERNEL_ERROR_EPERM = 0x80020001; -static constexpr int SCE_KERNEL_ERROR_ENOENT = 0x80020002; static constexpr int SCE_KERNEL_ERROR_ESRCH = 0x80020003; static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C; static constexpr int SCE_KERNEL_ERROR_EACCES = 0x8002000D; static constexpr int SCE_KERNEL_ERROR_EBUSY = 0x80020010; static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x8002003C; +static constexpr int SCE_KERNEL_ERROR_EFAULT = 0x8002000E; +static constexpr int SCE_KERNEL_ERROR_ENAMETOOLONG = 0x8002003F; static constexpr int SCE_KERNEL_ERROR_ECANCELED = 0x80020055; +static constexpr uint32_t EVF_ATTR_TH_FIFO = 0x01; +static constexpr uint32_t EVF_ATTR_TH_PRIO = 0x02; static constexpr uint32_t EVF_ATTR_SINGLE = 0x10; static constexpr uint32_t EVF_ATTR_MULTI = 0x20; +static constexpr uint32_t EVF_ATTR_SHARED = 0x100; +static constexpr size_t EVF_NAME_CAPACITY = 32; static constexpr uint32_t EVF_WAITMODE_AND = 0x01; static constexpr uint32_t EVF_WAITMODE_OR = 0x02; static constexpr uint32_t EVF_WAITMODE_CLEAR_ALL = 0x10; @@ -49,6 +57,7 @@ struct KernelEventFlagPrivate { std::string _name; uint64_t _pattern = 0; bool _multipleWaiters = false; + bool _shared = false; bool _deleted = false; int _waiters = 0; uint64_t _cancelGeneration = 0; @@ -84,20 +93,33 @@ struct KernelEventFlagPrivate { namespace { -std::shared_mutex eventFlagRegistryMutex; -std::unordered_map> eventFlagRegistry; +struct EventFlagHandle { + std::shared_ptr flag; + bool opened = false; +}; -std::shared_ptr ResolveEventFlag(KernelEventFlag handle) { +std::shared_mutex eventFlagRegistryMutex; +std::unordered_map> eventFlagRegistry; + +std::shared_ptr ResolveEventFlag(KernelEventFlag handle) { if (!handle) return {}; std::shared_lock lock(eventFlagRegistryMutex); const auto it = eventFlagRegistry.find(handle); - return it == eventFlagRegistry.end() ? std::shared_ptr{} : it->second; + return it == eventFlagRegistry.end() ? std::shared_ptr{} : it->second; } -void RetireEventFlag(KernelEventFlag handle, const std::shared_ptr& owner) { +KernelEventFlag RegisterEventFlag(const std::shared_ptr& entry) { + const auto handle = reinterpret_cast(entry.get()); + eventFlagRegistry.emplace(handle, entry); + return handle; +} + +bool RetireEventFlag(KernelEventFlag handle, const std::shared_ptr& owner) { std::unique_lock lock(eventFlagRegistryMutex); const auto it = eventFlagRegistry.find(handle); - if (it != eventFlagRegistry.end() && it->second == owner) eventFlagRegistry.erase(it); + if (it == eventFlagRegistry.end() || it->second != owner) return false; + eventFlagRegistry.erase(it); + return true; } } @@ -110,7 +132,6 @@ static bool TraceSync() { static bool IsValidWaitMode(uint32_t waitMode) { const uint32_t match = waitMode & (EVF_WAITMODE_AND | EVF_WAITMODE_OR); const uint32_t clear = waitMode & (EVF_WAITMODE_CLEAR_ALL | EVF_WAITMODE_CLEAR_PAT); - if ((waitMode & ~(EVF_WAITMODE_AND | EVF_WAITMODE_OR | EVF_WAITMODE_CLEAR_ALL | EVF_WAITMODE_CLEAR_PAT)) != 0) return false; return (match == EVF_WAITMODE_AND || match == EVF_WAITMODE_OR) && clear != (EVF_WAITMODE_CLEAR_ALL | EVF_WAITMODE_CLEAR_PAT); } @@ -130,130 +151,178 @@ int APS5_VABI sceKernelCreateEventFlag(KernelEventFlag* ef, const char* name, ui (void)param; if (!ef || !name) return SCE_KERNEL_ERROR_EINVAL; if ((attr & EVF_ATTR_SINGLE) && (attr & EVF_ATTR_MULTI)) return SCE_KERNEL_ERROR_EINVAL; - if ((attr & ~0x33u) != 0) return SCE_KERNEL_ERROR_EINVAL; + if ((attr & EVF_ATTR_TH_FIFO) && (attr & EVF_ATTR_TH_PRIO)) return SCE_KERNEL_ERROR_EINVAL; + if ((attr & ~(EVF_ATTR_TH_FIFO | EVF_ATTR_TH_PRIO | EVF_ATTR_SINGLE | EVF_ATTR_MULTI | EVF_ATTR_SHARED)) != 0) return SCE_KERNEL_ERROR_EINVAL; + if (std::strlen(name) >= EVF_NAME_CAPACITY) return SCE_KERNEL_ERROR_ENAMETOOLONG; + KernelEventFlag handle = nullptr; std::shared_ptr flag; try { flag = std::make_shared(); flag->_name = name; flag->_pattern = init_pattern; flag->_multipleWaiters = (attr & EVF_ATTR_MULTI) != 0; + flag->_shared = (attr & EVF_ATTR_SHARED) != 0; + auto entry = std::make_shared(EventFlagHandle{flag, false}); std::unique_lock lock(eventFlagRegistryMutex); - eventFlagRegistry.emplace(flag.get(), flag); + if (flag->_shared) { + for (const auto& [other, otherEntry] : eventFlagRegistry) { + if (otherEntry->opened || !otherEntry->flag->_shared) continue; + std::lock_guard flagLock(otherEntry->flag->_mutex); + if (!otherEntry->flag->_deleted && otherEntry->flag->_name == flag->_name) return SCE_KERNEL_ERROR_EPERM; + } + } + handle = RegisterEventFlag(entry); } catch (const std::bad_alloc&) { return SCE_KERNEL_ERROR_ENOMEM; } - *ef = flag.get(); - if (TraceSync()) std::fprintf(stderr, "[evf] create %p '%s' attr=0x%x init=0x%llx\n", static_cast(flag.get()), name, attr, static_cast(init_pattern)); + *ef = handle; + if (TraceSync()) std::fprintf(stderr, "[evf] create %p '%s' attr=0x%x init=0x%llx\n", static_cast(handle), name, attr, static_cast(init_pattern)); return SCE_OK; } int APS5_VABI sceKernelOpenEventFlag(KernelEventFlag* ef, const char* name) { - if (!ef || !name) return SCE_KERNEL_ERROR_EINVAL; - std::shared_lock lock(eventFlagRegistryMutex); - for (const auto& [handle, flag] : eventFlagRegistry) { - std::lock_guard flagLock(flag->_mutex); - if (flag->_deleted || flag->_name != name) continue; - *ef = handle; - return SCE_OK; + if (!name) return SCE_KERNEL_ERROR_EFAULT; + if (std::strlen(name) >= EVF_NAME_CAPACITY) return SCE_KERNEL_ERROR_ENAMETOOLONG; + try { + std::unique_lock lock(eventFlagRegistryMutex); + std::shared_ptr target; + for (const auto& [handle, entry] : eventFlagRegistry) { + if (entry->opened || !entry->flag->_shared) continue; + std::lock_guard flagLock(entry->flag->_mutex); + if (entry->flag->_deleted || entry->flag->_name != name) continue; + target = entry->flag; + break; + } + if (!target) return SCE_KERNEL_ERROR_ESRCH; + if (!ef) return SCE_KERNEL_ERROR_EFAULT; + *ef = RegisterEventFlag(std::make_shared(EventFlagHandle{std::move(target), true})); + } catch (const std::bad_alloc&) { + return SCE_KERNEL_ERROR_ENOMEM; } - return SCE_KERNEL_ERROR_ENOENT; + return SCE_OK; +} + +int APS5_VABI sceKernelCloseEventFlag(KernelEventFlag ef) { + const auto entry = ResolveEventFlag(ef); + if (!entry) return SCE_KERNEL_ERROR_ESRCH; + if (!entry->opened) return SCE_KERNEL_ERROR_EPERM; + return RetireEventFlag(ef, entry) ? SCE_OK : SCE_KERNEL_ERROR_ESRCH; } int APS5_VABI sceKernelDeleteEventFlag(KernelEventFlag ef) { - if (!ef) return SCE_KERNEL_ERROR_ESRCH; - const auto keepAlive = ResolveEventFlag(ef); - if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; - std::unique_lock lock(ef->_mutex); - if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; - ef->_deleted = true; - ef->_changed.NotifyAll(); - ef->_changed.Wait(lock, [&] { return ef->_waiters == 0; }); + const auto entry = ResolveEventFlag(ef); + if (!entry) return SCE_KERNEL_ERROR_ESRCH; + if (entry->opened) return SCE_KERNEL_ERROR_EPERM; + KernelEventFlagPrivate* flag = entry->flag.get(); + std::unique_lock lock(flag->_mutex); + if (flag->_deleted) return SCE_KERNEL_ERROR_ESRCH; + flag->_deleted = true; + flag->_changed.NotifyAll(); + flag->_changed.Wait(lock, [&] { return flag->_waiters == 0; }); lock.unlock(); - RetireEventFlag(ef, keepAlive); + RetireEventFlag(ef, entry); return SCE_OK; } int APS5_VABI sceKernelSetEventFlag(KernelEventFlag ef, uint64_t bit_pattern) { - if (!ef) return SCE_KERNEL_ERROR_ESRCH; - const auto keepAlive = ResolveEventFlag(ef); - if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; - std::lock_guard lock(ef->_mutex); - if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; - ef->_pattern |= bit_pattern; - if (TraceSync()) std::fprintf(stderr, "[evf] set %p '%s' |=0x%llx -> 0x%llx\n", static_cast(ef), ef->_name.c_str(), static_cast(bit_pattern), static_cast(ef->_pattern)); - for (auto* waiter = ef->_waiterHead; waiter != nullptr; waiter = waiter->next) { - if (!waiter->satisfied && IsSatisfied(ef->_pattern, waiter->bitPattern, waiter->waitMode)) { + const auto entry = ResolveEventFlag(ef); + if (!entry) return SCE_KERNEL_ERROR_ESRCH; + KernelEventFlagPrivate* flag = entry->flag.get(); + std::lock_guard lock(flag->_mutex); + if (flag->_deleted) return entry->opened ? SCE_OK : SCE_KERNEL_ERROR_ESRCH; + flag->_pattern |= bit_pattern; + if (TraceSync()) std::fprintf(stderr, "[evf] set %p '%s' |=0x%llx -> 0x%llx\n", static_cast(ef), flag->_name.c_str(), static_cast(bit_pattern), static_cast(flag->_pattern)); + for (auto* waiter = flag->_waiterHead; waiter != nullptr; waiter = waiter->next) { + if (!waiter->satisfied && IsSatisfied(flag->_pattern, waiter->bitPattern, waiter->waitMode)) { waiter->satisfied = true; - waiter->resultPattern = ef->_pattern; + waiter->resultPattern = flag->_pattern; if (waiter->waitMode & EVF_WAITMODE_CLEAR_ALL) { - ef->_pattern = 0; + flag->_pattern = 0; } else if (waiter->waitMode & EVF_WAITMODE_CLEAR_PAT) { - ef->_pattern &= ~waiter->bitPattern; + flag->_pattern &= ~waiter->bitPattern; } } } - ef->_changed.NotifyAll(); + flag->_changed.NotifyAll(); return SCE_OK; } int APS5_VABI sceKernelClearEventFlag(KernelEventFlag ef, uint64_t bit_pattern) { - if (!ef) return SCE_KERNEL_ERROR_ESRCH; - const auto keepAlive = ResolveEventFlag(ef); - if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; - std::lock_guard lock(ef->_mutex); - if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; - ef->_pattern &= bit_pattern; + const auto entry = ResolveEventFlag(ef); + if (!entry) return SCE_KERNEL_ERROR_ESRCH; + KernelEventFlagPrivate* flag = entry->flag.get(); + std::lock_guard lock(flag->_mutex); + if (flag->_deleted) return entry->opened ? SCE_OK : SCE_KERNEL_ERROR_ESRCH; + flag->_pattern &= bit_pattern; return SCE_OK; } int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, uint64_t set_pattern, int* num_wait_threads) { - if (!ef) return SCE_KERNEL_ERROR_ESRCH; - const auto keepAlive = ResolveEventFlag(ef); - if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; - std::lock_guard lock(ef->_mutex); - if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; - if (num_wait_threads) *num_wait_threads = ef->_waiters; - ef->_pattern = set_pattern; - ++ef->_cancelGeneration; - ef->_changed.NotifyAll(); + const auto entry = ResolveEventFlag(ef); + if (!entry) return SCE_KERNEL_ERROR_ESRCH; + KernelEventFlagPrivate* flag = entry->flag.get(); + std::lock_guard lock(flag->_mutex); + if (flag->_deleted) { + if (!entry->opened) return SCE_KERNEL_ERROR_ESRCH; + if (num_wait_threads) *num_wait_threads = 0; + return SCE_OK; + } + if (num_wait_threads) *num_wait_threads = flag->_waiters; + flag->_pattern = set_pattern; + ++flag->_cancelGeneration; + flag->_changed.NotifyAll(); return SCE_OK; } int APS5_VABI sceKernelPollEventFlag(KernelEventFlag ef, uint64_t bit_pattern, uint32_t wait_mode, uint64_t* result_pat) { - if (!ef) return SCE_KERNEL_ERROR_ESRCH; - const auto keepAlive = ResolveEventFlag(ef); - if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; + const auto entry = ResolveEventFlag(ef); + if (!entry) return SCE_KERNEL_ERROR_ESRCH; if (bit_pattern == 0 || !IsValidWaitMode(wait_mode)) return SCE_KERNEL_ERROR_EINVAL; - std::lock_guard lock(ef->_mutex); - if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; - if (!ef->_multipleWaiters && ef->_waiters != 0) return SCE_KERNEL_ERROR_EPERM; - if (!IsSatisfied(ef->_pattern, bit_pattern, wait_mode)) { - if (result_pat) *result_pat = ef->_pattern; + KernelEventFlagPrivate* flag = entry->flag.get(); + std::lock_guard lock(flag->_mutex); + if (flag->_deleted) { + if (!entry->opened) return SCE_KERNEL_ERROR_ESRCH; + if (result_pat) *result_pat = 0; + return SCE_KERNEL_ERROR_EACCES; + } + if (!flag->_multipleWaiters && flag->_waiters != 0) { + if (result_pat) *result_pat = 0; + return SCE_KERNEL_ERROR_EPERM; + } + if (!IsSatisfied(flag->_pattern, bit_pattern, wait_mode)) { + if (result_pat) *result_pat = flag->_pattern; return SCE_KERNEL_ERROR_EBUSY; } - Consume(ef, bit_pattern, wait_mode, result_pat); + Consume(flag, bit_pattern, wait_mode, result_pat); return SCE_OK; } int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, uint64_t bit_pattern, uint32_t wait_mode, uint64_t* result_pat, KernelUseconds* timeout) { - if (!ef) return SCE_KERNEL_ERROR_ESRCH; - const auto keepAlive = ResolveEventFlag(ef); - if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; + const auto entry = ResolveEventFlag(ef); + if (!entry) return SCE_KERNEL_ERROR_ESRCH; if (bit_pattern == 0 || !IsValidWaitMode(wait_mode)) return SCE_KERNEL_ERROR_EINVAL; - std::unique_lock lock(ef->_mutex); - if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; - if (!ef->_multipleWaiters && ef->_waiters != 0) return SCE_KERNEL_ERROR_EPERM; - if (IsSatisfied(ef->_pattern, bit_pattern, wait_mode)) { - Consume(ef, bit_pattern, wait_mode, result_pat); + KernelEventFlagPrivate* flag = entry->flag.get(); + std::unique_lock lock(flag->_mutex); + if (flag->_deleted) { + if (!entry->opened) return SCE_KERNEL_ERROR_ESRCH; + if (result_pat) *result_pat = 0; + return SCE_KERNEL_ERROR_EACCES; + } + if (!flag->_multipleWaiters && flag->_waiters != 0) { + if (result_pat) *result_pat = 0; + return SCE_KERNEL_ERROR_EPERM; + } + if (IsSatisfied(flag->_pattern, bit_pattern, wait_mode)) { + Consume(flag, bit_pattern, wait_mode, result_pat); return SCE_OK; } - const uint64_t generation = ef->_cancelGeneration; - if (TraceSync()) std::fprintf(stderr, "[evf] wait %p '%s' want=0x%llx mode=0x%x have=0x%llx timeout=%d caller=%p outer=%p\n", static_cast(ef), ef->_name.c_str(), static_cast(bit_pattern), wait_mode, static_cast(ef->_pattern), timeout ? static_cast(*timeout) : -1, __builtin_return_address(0), static_cast(static_cast(__builtin_frame_address(0))[0])[1]); - ++ef->_waiters; + const uint64_t generation = flag->_cancelGeneration; + if (TraceSync()) std::fprintf(stderr, "[evf] wait %p '%s' want=0x%llx mode=0x%x have=0x%llx timeout=%d caller=%p outer=%p\n", static_cast(ef), flag->_name.c_str(), static_cast(bit_pattern), wait_mode, static_cast(flag->_pattern), timeout ? static_cast(*timeout) : -1, __builtin_return_address(0), static_cast(static_cast(__builtin_frame_address(0))[0])[1]); + ++flag->_waiters; WaiterNode waiter; waiter.bitPattern = bit_pattern; waiter.waitMode = wait_mode; - ef->pushWaiter(&waiter); + flag->pushWaiter(&waiter); struct WaiterGuard { KernelEventFlagPrivate* flag; WaiterNode* node; @@ -262,27 +331,28 @@ int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, uint64_t bit_pattern, u --flag->_waiters; flag->_changed.NotifyAll(); } - } waiterGuard{ef, &waiter}; - const auto released = [&] { return ef->_deleted || ef->_cancelGeneration != generation || waiter.satisfied; }; + } waiterGuard{flag, &waiter}; + const auto released = [&] { return flag->_deleted || flag->_cancelGeneration != generation || waiter.satisfied; }; bool timedOut = false; const auto waitStart = std::chrono::steady_clock::now(); if (timeout) { const std::uint64_t deadline = TimedWait::DeadlineNanos(*timeout); - timedOut = !ef->_changed.WaitUntil(lock, deadline, released); + timedOut = !flag->_changed.WaitUntil(lock, deadline, released); *timeout = static_cast(TimedWait::RemainingMicros(deadline)); } else { - ef->_changed.Wait(lock, released); + flag->_changed.Wait(lock, released); } KernelTraceWait_nid_postfix("evf", __builtin_return_address(0), static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - waitStart).count()), timedOut); - if (ef->_deleted) { + if (flag->_deleted) { + if (result_pat) *result_pat = 0; return SCE_KERNEL_ERROR_EACCES; } - if (ef->_cancelGeneration != generation) { - if (result_pat) *result_pat = ef->_pattern; + if (flag->_cancelGeneration != generation) { + if (result_pat) *result_pat = 0; return SCE_KERNEL_ERROR_ECANCELED; } if (timedOut && !waiter.satisfied) { - if (result_pat) *result_pat = ef->_pattern; + if (result_pat) *result_pat = flag->_pattern; return SCE_KERNEL_ERROR_ETIMEDOUT; } if (result_pat) *result_pat = waiter.resultPattern; @@ -292,8 +362,8 @@ int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, uint64_t bit_pattern, u } extern "C" int APS5_Test_GetEventFlagWaiterCount(KernelEventFlag handle) { - const auto keepAlive = ResolveEventFlag(handle); - if (!keepAlive) return -1; - std::lock_guard lock(keepAlive->_mutex); - return keepAlive->_waiters; + const auto entry = ResolveEventFlag(handle); + if (!entry) return -1; + std::lock_guard lock(entry->flag->_mutex); + return entry->flag->_waiters; } diff --git a/core/libs/prx/libkernel/File/include/DescriptorControl.hpp b/core/libs/prx/libkernel/File/include/DescriptorControl.hpp index 341324638c..1d307391d5 100644 --- a/core/libs/prx/libkernel/File/include/DescriptorControl.hpp +++ b/core/libs/prx/libkernel/File/include/DescriptorControl.hpp @@ -2,6 +2,7 @@ #define CORE_LIBS_PRX_LIBKERNEL_FILE_DESCRIPTORCONTROL_HPP #include +#include namespace File { @@ -13,11 +14,28 @@ bool IoctlRequestMalformed(std::uint64_t request); int Ioctl(int fd, std::uint64_t request, void* argument); int DuplicateFrom(int fd, int start); void RememberDescriptor(int fd, int openFlags); +void RememberPipe(int fd); void ShareDescriptor(int from, int to); void ForgetDescriptor(int fd); bool Appends(int fd); +void SyncWrites(int fd); + +class WriteSync { +public: + explicit WriteSync(int fd) : _fd(fd), _exceptions(std::uncaught_exceptions()) {} + ~WriteSync() noexcept(false) { + if (std::uncaught_exceptions() == _exceptions) SyncWrites(_fd); + } + +private: + int _fd; + int _exceptions; +}; bool IsVnode(int fd); +bool IsCharacterDevice(int fd); } +extern "C" bool DescriptorHasNoVnode_nid_no_patch(int fd); + #endif diff --git a/core/libs/prx/libkernel/File/include/SharedMemory.hpp b/core/libs/prx/libkernel/File/include/SharedMemory.hpp index 5c9ffc9f47..7d9be97385 100644 --- a/core/libs/prx/libkernel/File/include/SharedMemory.hpp +++ b/core/libs/prx/libkernel/File/include/SharedMemory.hpp @@ -14,6 +14,15 @@ std::int64_t WriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, 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); int TruncateSharedMemory_nid_no_patch(int descriptor, std::int64_t length); +#ifdef _WIN32 +int SetSharedMemoryMode_nid_no_patch(int descriptor, int mode); +int SharedMemoryDescriptorWritable_nid_no_patch(int descriptor); +void TrackSharedMemoryDescriptor_nid_no_patch(int descriptor, bool writable); +void ForgetSharedMemoryDescriptor_nid_no_patch(int descriptor); +void DuplicateSharedMemoryDescriptor_nid_no_patch(int source, int destination); +int CloseSharedMemoryDescriptor_nid_no_patch(int descriptor); +int ReplaceSharedMemoryDescriptor_nid_no_patch(int source, int destination); +#endif #ifndef _WIN32 std::int64_t WritevSharedMemory_nid_no_patch(int descriptor, const struct iovec* vectors, int count); std::int64_t PwritevSharedMemory_nid_no_patch(int descriptor, const struct iovec* vectors, int count, std::int64_t offset); diff --git a/core/libs/prx/libkernel/File/include/WindowsSymbolicLink.hpp b/core/libs/prx/libkernel/File/include/WindowsSymbolicLink.hpp index cdc089e731..e8f2a2f06b 100644 --- a/core/libs/prx/libkernel/File/include/WindowsSymbolicLink.hpp +++ b/core/libs/prx/libkernel/File/include/WindowsSymbolicLink.hpp @@ -47,6 +47,20 @@ class WindowsSymbolicLink { } public: + static bool IsNameSurrogate(const std::filesystem::path& native, const char* operation) { + const HANDLE handle = CreateFileW(native.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) + throw std::system_error(static_cast(GetLastError()), std::system_category(), std::string(operation) + ": CreateFileW"); + FILE_ATTRIBUTE_TAG_INFO information{}; + const BOOL read = GetFileInformationByHandleEx(handle, FileAttributeTagInfo, &information, sizeof(information)); + const DWORD code = GetLastError(); + CloseHandle(handle); + if (!read) + throw std::system_error(static_cast(code), std::system_category(), std::string(operation) + ": GetFileInformationByHandleEx"); + return (information.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && IsReparseTagNameSurrogate(information.ReparseTag); + } + static bool Read(const std::filesystem::path& native, std::string& target, const char* operation) { const HANDLE handle = CreateFileW(native.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); @@ -108,6 +122,14 @@ public: } }; +inline bool WindowsSymbolicLinkLoop(const std::filesystem::path& path) { + const auto handle = CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) return GetLastError() == ERROR_CANT_RESOLVE_FILENAME; + CloseHandle(handle); + return false; +} + } #endif diff --git a/core/libs/prx/libkernel/File/src/DescriptorControl.cpp b/core/libs/prx/libkernel/File/src/DescriptorControl.cpp index a83f899801..f66cee5134 100644 --- a/core/libs/prx/libkernel/File/src/DescriptorControl.cpp +++ b/core/libs/prx/libkernel/File/src/DescriptorControl.cpp @@ -1,8 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libkernel/File/include/DescriptorControl.hpp" #include "prx/libkernel/File/include/DirectoryDescriptor.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/RandomDevice.hpp" #include "prx/libkernel/File/include/ArchivedFile.hpp" +#include "prx/libkernel/Socket/include/SocketRuntime.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/FileLock.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -21,8 +24,10 @@ #include #ifdef _WIN32 +#include #include #include +#include "prx/libc/include/General.hpp" #include "prx/libc/include/WindowsDescriptor.hpp" #else #include @@ -31,9 +36,6 @@ #include #endif -extern "C" int* APS5_VABI __error_nid_postfix(); -extern "C" int APS5_VABI dup2_nid_postfix(int from, int to); - namespace { constexpr int GuestEintr = 4; @@ -114,6 +116,7 @@ enum class Kind { File, Directory, Pipe, Socket, Device, RandomDevice, ArchivedF struct Entry { bool closeOnExec = false; std::shared_ptr status; + bool pipe = false; }; std::mutex g_mutex; @@ -178,8 +181,33 @@ Entry InitialEntry(int fd) { return {false, std::make_shared(status)}; } +void SetHostAppend(int fd, bool append, const char* function) { + const int translation = ::_setmode(fd, _O_BINARY); + if (translation == -1 || ::_setmode(fd, translation) == -1) + throw std::runtime_error(std::string(function) + ": cannot read the translation mode of descriptor " + std::to_string(fd) + ", errno=" + std::to_string(errno)); + HANDLE copy = nullptr; + if (!::DuplicateHandle(::GetCurrentProcess(), WindowsDescriptorHandle_nid_no_patch(fd), ::GetCurrentProcess(), ©, 0, TRUE, DUPLICATE_SAME_ACCESS)) + throw std::runtime_error(std::string(function) + ": cannot duplicate the handle of descriptor " + std::to_string(fd) + ", error=" + std::to_string(::GetLastError())); + const int replacement = ::_open_osfhandle(reinterpret_cast(copy), (append ? _O_APPEND : 0) | translation); + if (replacement < 0) { + ::CloseHandle(copy); + throw std::runtime_error(std::string(function) + ": cannot open a replacement for descriptor " + std::to_string(fd) + ", errno=" + std::to_string(errno)); + } + auto* const standard = HostStandardHandle_nid_no_patch(fd); + const int moved = ::_dup2(replacement, fd); + const int error = errno; + ::_close(replacement); + KeepStandardHandle_nid_no_patch(fd, standard); + if (moved != 0) + throw std::runtime_error(std::string(function) + ": cannot replace descriptor " + std::to_string(fd) + ", errno=" + std::to_string(error)); +} + void ApplyHostStatus(int fd, Kind kind, int changed, int next, const char* function) { - if ((changed & Append) && kind == Kind::File) Unsupported(function, "changing O_APPEND", kind, fd); + if ((changed & Append) && kind == Kind::File) { + const auto status = g_entries.at(fd).status; + for (const auto& [descriptor, entry] : g_entries) + if (entry.status == status && DescriptorIsOpen_nid_no_patch(descriptor)) SetHostAppend(descriptor, (next & Append) != 0, function); + } if ((changed & Nonblocking) && kind == Kind::Device) Unsupported(function, "changing O_NONBLOCK", kind, fd); if ((changed & Nonblocking) && kind == Kind::Pipe) SetPipeNonblocking(fd, (next & Nonblocking) != 0, function); } @@ -350,7 +378,6 @@ int ChangeStatus(int fd, Kind kind, int mask, int value, const char* function) { const int next = (status & ~mask) | (value & mask); const int changed = status ^ next; if ((changed & Asynchronous) && (kind == Kind::Pipe || kind == Kind::Socket || kind == Kind::Device)) Unsupported(function, "O_ASYNC", kind, fd); - if ((changed & Synchronous) && kind == Kind::File) Unsupported(function, "changing O_FSYNC", kind, fd); ApplyHostStatus(fd, kind, changed, next, function); status = next; return 0; @@ -361,6 +388,12 @@ int CurrentStatus(int fd) { return *Materialize(fd).status; } +bool CreatedByPipe(int fd) { + std::lock_guard lock(g_mutex); + const auto found = g_entries.find(fd); + return found != g_entries.end() && found->second.pipe; +} + void SetCloseOnExec(int fd, bool value) { std::lock_guard lock(g_mutex); Materialize(fd).closeOnExec = value; @@ -481,7 +514,7 @@ int Fcntl(int fd, int command, std::intptr_t argument) { return 0; case GetStatusFlags: { if (!Classify(fd, "fcntl")) return Fail(GuestEbadf); - const int access = AccessMode(fd, "fcntl"); + const int access = CreatedByPipe(fd) ? ReadWrite : AccessMode(fd, "fcntl"); return access | CurrentStatus(fd); } case SetStatusFlags: { @@ -560,11 +593,17 @@ void RememberDescriptor(int fd, int openFlags) { #endif } +void RememberPipe(int fd) { + RememberDescriptor(fd, 0); + std::lock_guard lock(g_mutex); + g_entries[fd].pipe = true; +} + void ShareDescriptor(int from, int to) { File::ShareArchivedFile(from, to); std::lock_guard lock(g_mutex); - auto status = Materialize(from).status; - g_entries[to] = Entry{false, std::move(status)}; + const auto& source = Materialize(from); + g_entries[to] = Entry{false, source.status, source.pipe}; #ifdef _WIN32 File::SharePositionedTransfer(from, to); GuestFileLockShare_nid_no_patch(from, to); @@ -577,12 +616,37 @@ void ForgetDescriptor(int fd) { g_entries.erase(fd); } +std::uint64_t g_synchronousWrites = 0; + +void SyncWrites(int fd) { + { + std::lock_guard lock(g_mutex); + const auto found = g_entries.find(fd); + if (found == g_entries.end() || !found->second.status || (*found->second.status & Synchronous) == 0) return; + } + if (Classify(fd, "write") != Kind::File) return; + if (const int result = sceKernelFsync(fd); result != 0) + throw std::runtime_error("write: syncing an O_FSYNC file failed, fd=" + std::to_string(fd) + ", error=" + std::to_string(result & 0xffff)); + std::lock_guard lock(g_mutex); + ++g_synchronousWrites; +} + bool Appends(int fd) { std::lock_guard lock(g_mutex); const auto found = g_entries.find(fd); return found != g_entries.end() && found->second.status && (*found->second.status & Append) != 0; } +bool IsCharacterDevice(int fd) { + if (fd < 0) return false; +#ifdef _WIN32 + return DescriptorIsOpen_nid_no_patch(fd) && !GuestSockets::IsOpen(fd) && !File::DirectoryDescriptorPath(fd) && ::GetFileType(WindowsDescriptorHandle_nid_no_patch(fd)) == FILE_TYPE_CHAR; +#else + struct stat status{}; + return ::fstat(fd, &status) == 0 && S_ISCHR(status.st_mode); +#endif +} + bool IsVnode(int fd) { if (fd < 0 || File::IsRandomDevice(fd)) return false; if (File::IsArchivedFile(fd)) return true; @@ -596,3 +660,12 @@ bool IsVnode(int fd) { } } + +extern "C" bool DescriptorHasNoVnode_nid_no_patch(int fd) { + return !File::IsVnode(fd) && !File::IsCharacterDevice(fd) && !File::IsRandomDevice(fd); +} + +extern "C" std::uint64_t FileSynchronousWritesForTest_nid_no_patch() { + std::lock_guard lock(g_mutex); + return File::g_synchronousWrites; +} diff --git a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp index 7727bb9000..58e97e8c80 100644 --- a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp +++ b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp @@ -8,7 +8,9 @@ #ifdef _WIN32 +#include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/File/include/NativeStat.hpp" #include #include @@ -29,8 +31,10 @@ namespace { +constexpr std::uint8_t GuestUnknownType = 0; constexpr std::uint8_t GuestDirectoryType = 4; constexpr std::uint8_t GuestRegularType = 8; +constexpr std::uint8_t GuestSymbolicLinkType = 10; struct DirectoryState { std::mutex mutex; @@ -49,20 +53,81 @@ std::shared_ptr Find(int fd) { return found == g_directories.end() ? nullptr : found->second; } +bool InsideApp0(const std::filesystem::path& directory) { + const auto root = ResolveDiskPath_nid_no_patch("/app0"); + const auto handle = CreateFileW(root.c_str(), FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) return false; + const int descriptor = ::_open_osfhandle(reinterpret_cast(handle), _O_RDONLY | _O_BINARY); + if (descriptor < 0) { + CloseHandle(handle); + return false; + } + const auto app0 = File::WindowsDescriptorPath_nid_no_patch(descriptor); + ::_close(descriptor); + if (!app0) return false; + auto entry = directory.begin(); + for (const auto& component : *app0) { + if (entry == directory.end()) return false; + const auto& left = entry->native(); + const auto& right = component.native(); + if (CompareStringOrdinal(left.c_str(), static_cast(left.size()), + right.c_str(), static_cast(right.size()), TRUE) != CSTR_EQUAL) return false; + ++entry; + } + return true; +} + +std::uint8_t EntryType(const std::filesystem::path& path, bool followLinks) { + FileStat status{}; + if (!File::FillLinkStat(path, &status)) + return GuestUnknownType; + if (followLinks && (status.st_mode & 0170000) == 0120000) { + FileStat target{}; + const int savedErrno = errno; + const bool found = File::TryFillFileStat(path, &target); + errno = savedErrno; + if (found && ((target.st_mode & 0170000) == 0040000 || (target.st_mode & 0170000) == 0100000)) + status = target; + } + switch (status.st_mode & 0170000) { + case 0040000: return GuestDirectoryType; + case 0100000: return GuestRegularType; + case 0120000: return GuestSymbolicLinkType; + default: NotImplemented_nid_no_patch("getdents of a Windows file type other than a directory, regular file or symbolic link"); + } +} + void Load(DirectoryState& state, int fd) { if (state.loaded) return; const auto path = File::WindowsDescriptorPath_nid_no_patch(fd); if (!path) throw std::system_error(errno, std::generic_category(), "directory descriptor path"); - state.loaded = true; - state.entries.emplace_back(".", GuestDirectoryType); - state.entries.emplace_back("..", GuestDirectoryType); + const int savedErrno = errno; + const bool followLinks = InsideApp0(*path); + errno = savedErrno; + std::vector> entries{{".", GuestDirectoryType}, {"..", GuestDirectoryType}}; std::error_code error; for (const auto& entry : std::filesystem::directory_iterator(*path, error)) { - std::error_code typeError; - state.entries.emplace_back(entry.path().filename().string(), entry.is_directory(typeError) ? GuestDirectoryType : GuestRegularType); + entries.emplace_back(entry.path().filename().string(), EntryType(entry.path(), followLinks)); } + state.entries = std::move(entries); + state.loaded = true; } +int OpenStreamDirectory(const wchar_t* path) { + return File::OpenDirectoryDescriptor(path); +} + +struct StreamRegistration { + StreamRegistration() { LibcRegisterDirectoryStreams_nid_no_patch(&OpenStreamDirectory, &File::ForgetDirectoryDescriptor); } + ~StreamRegistration() { LibcRegisterDirectoryStreams_nid_no_patch(nullptr, nullptr); } + StreamRegistration(const StreamRegistration&) = delete; + StreamRegistration& operator=(const StreamRegistration&) = delete; +}; + +const StreamRegistration g_streamRegistration; + } namespace File { diff --git a/core/libs/prx/libkernel/File/src/NativeStat.cpp b/core/libs/prx/libkernel/File/src/NativeStat.cpp index 75e7d82619..1fda5aabf7 100644 --- a/core/libs/prx/libkernel/File/src/NativeStat.cpp +++ b/core/libs/prx/libkernel/File/src/NativeStat.cpp @@ -53,15 +53,20 @@ static int DoFstat(int fd, NativeStat* st) { static int DoLstat(const std::filesystem::path& p, NativeStat* st) { const auto attributes = GetFileAttributesW(p.c_str()); if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) return DoStat(p, st); + if (!File::WindowsSymbolicLink::IsNameSurrogate(p, "lstat")) return DoStat(p, st); std::string target; - if (!File::WindowsSymbolicLink::Read(p, target, "lstat") && !File::WindowsSymbolicLink::ReadJunction(p, target, "lstat")) - NotImplemented_nid_no_patch("lstat of a Windows reparse point other than a symbolic link"); + const bool link = File::WindowsSymbolicLink::Read(p, target, "lstat") || File::WindowsSymbolicLink::ReadJunction(p, target, "lstat"); const auto handle = CreateFileW(p.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (handle == INVALID_HANDLE_VALUE) return File::WindowsFileTime::Failure(GetLastError()); const int result = ReadHandleInfo(handle, st); CloseHandle(handle); if (result != 0) return result; + if (!link) { + st->st_mode = (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ? (_S_IFDIR | 0111) : _S_IFREG; + st->st_mode |= (attributes & FILE_ATTRIBUTE_READONLY) != 0 ? 0444 : 0666; + return 0; + } st->st_mode = SymbolicLinkMode; st->st_size = static_cast<__int64>(target.size()); st->isSymbolicLink = true; diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 7743c86733..08ce35c238 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -1,7 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include "prx/libkernel/File/include/NativeStat.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/FilesystemError.hpp" +#include "prx/libc/include/RenameDot.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include "prx/libkernel/File/include/File.hpp" @@ -86,6 +89,8 @@ static int NativeWrite(int fd, const void* buf, std::size_t n) { extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); static void IgnoreInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {} static int NativeClose(int fd) { + const int shared = File::CloseSharedMemoryDescriptor_nid_no_patch(fd); + if (shared != -2) return shared; const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreInvalidParameter); const int result = ::_close(fd); _set_thread_local_invalid_parameter_handler(previous); @@ -114,11 +119,7 @@ static std::filesystem::path NativeMetadataPath(const std::filesystem::path& pat : std::filesystem::path(L"\\\\?\\" + native); } static bool NativeSymbolicLinkLoop(const std::filesystem::path& path) { - const auto handle = CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); - if (handle == INVALID_HANDLE_VALUE) return GetLastError() == ERROR_CANT_RESOLVE_FILENAME; - CloseHandle(handle); - return false; + return File::WindowsSymbolicLinkLoop(path); } static int MapFlags(int sceFlags) { int f = 0; @@ -172,9 +173,6 @@ static int MapFlags(int sceFlags) { } #endif -extern "C" int* APS5_VABI __error_nid_postfix(); -extern "C" int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...); - static int SceErrorFromErrno(int error) { const int guest = error > 0 && error <= 34 ? error : FilesystemError(std::error_code(error, std::generic_category())); return static_cast(0x80020000u | static_cast(guest)); @@ -215,6 +213,10 @@ static int OpenNative(const std::filesystem::path& native, int flags, std::uint1 const auto status = std::filesystem::status(native, error); if (std::filesystem::exists(status) && !std::filesystem::is_directory(status)) return SceErrorFromErrno(ENOTDIR); } + if ((flags & (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL)) == (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL) && native.has_filename()) { + const DWORD attributes = GetFileAttributesW(native.c_str()); + if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) return SceErrorFromErrno(EEXIST); + } #endif if ((flags & SCE_KERNEL_O_NOFOLLOW) != 0 && (flags & (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL)) != (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL)) { constexpr int GuestEmlink = 31; @@ -264,7 +266,10 @@ extern "C" { int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) { if (File::InvalidOpenFlags(flags)) return SCE_KERNEL_ERROR_EINVAL; - if (path != nullptr && *path == '\0') return SCE_KERNEL_ERROR_ENOENT; + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(error); + path = copy.c_str(); + if (copy.empty()) return SCE_KERNEL_ERROR_ENOENT; APS5_LOG_OUT("path=%s flags=0x%X nativeFlags=0x%X mode=0%o", path, flags, MapFlags(flags), mode); if (path != nullptr && File::IsRandomDevicePath(path)) { if ((flags & SCE_KERNEL_O_ACCMODE) != SCE_KERNEL_O_RDONLY) throw std::runtime_error(std::string(__func__) + ": writing to " + path + " is not implemented"); @@ -297,12 +302,12 @@ int OpenInDirectory_nid_no_patch(int directory, const char* path, int flags, std } int APS5_VABI sceKernelClose(int d) { - if (File::IsHostStream(d)) return SCE_KERNEL_ERROR_EBADF; + if (File::IsHostStream(d)) return SCE_KERNEL_ERROR_EPERM; if (GuestSockets::IsOpen(d)) return GuestSockets::Close(d) == 0 ? 0 : SceKernelError(*__error_nid_postfix()); + EqueueDescriptorClosed(d); #ifdef _WIN32 const auto positioned = File::LockPositionedTransfer(d); #endif - EqueueDescriptorClosed(d); File::ForgetRandomDevice(d); File::ForgetDescriptor(d); ForgetJitSharedMemory(d); @@ -323,7 +328,7 @@ std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes) { return n < 0 ? SceKernelError(*__error_nid_postfix()) : n; } if (buf == nullptr) { - if (nbytes != 0) return SceIoErrorFromErrno(EFAULT); + if (nbytes != 0) return SceIoErrorFromErrno(DescriptorIsOpen_nid_no_patch(d) ? EFAULT : EBADF); char empty = 0; return sceKernelRead(d, &empty, 0); } @@ -342,16 +347,29 @@ std::int64_t APS5_VABI sceKernelWrite(int d, const void* buf, std::size_t nbytes return n < 0 ? SceKernelError(*__error_nid_postfix()) : n; } if (buf == nullptr) { - if (nbytes != 0) return SceIoErrorFromErrno(EFAULT); + if (nbytes != 0) return SceIoErrorFromErrno(DescriptorIsOpen_nid_no_patch(d) ? EFAULT : EBADF); const char empty = 0; return sceKernelWrite(d, &empty, 0); } + const File::WriteSync sync(d); GuestArena::GuestArenaPrepareHostRead_nid_no_patch(buf, nbytes); auto n = NativeWrite(d, buf, nbytes); if (n < 0) return SceIoErrorFromErrno(errno); return static_cast(n); } +static bool SeekOverflows(int d, std::int64_t offset, int whence) { + const std::int64_t current = NativeLseek(d, 0, 1); + if (current < 0) return false; + std::int64_t base = current; + if (whence == 2) { + base = NativeLseek(d, 0, 2); + NativeLseek(d, current, 0); + if (base < 0) return false; + } + return base > std::numeric_limits::max() - offset; +} + std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence) { if (GuestSockets::IsOpen(d)) return SCE_KERNEL_ERROR_ESPIPE; if (whence < 0 || whence > 2) { @@ -365,7 +383,11 @@ std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence) { } #endif std::int64_t result = NativeLseek(d, offset, whence); - if (result < 0) return SceIoErrorFromErrno(errno); + if (result < 0) { + const int error = errno; + if (error == EINVAL && offset > 0 && (whence == 1 || whence == 2) && SeekOverflows(d, offset, whence)) return SceIoErrorFromErrno(EOVERFLOW); + return SceIoErrorFromErrno(error); + } return result; } @@ -397,13 +419,17 @@ static int StatNative(const std::filesystem::path& native, bool directory, FileS } if (directory && !std::filesystem::is_directory(status)) return SceErrorFromErrno(GuestEnotdir); if (sb == nullptr) return SceErrorFromErrno(GuestEfault); - if (!File::TryFillFileStat(metadataPath, sb)) return SceStatErrorFromErrno(errno); + FileStat result{}; + if (!File::TryFillFileStat(metadataPath, &result)) return SceStatErrorFromErrno(errno); + if (!GuestCopyOut_nid_no_patch(sb, &result, sizeof(result))) return SceErrorFromErrno(GuestEfault); return 0; } int APS5_VABI sceKernelStat(const char* path, FileStat* sb) { - if (path == nullptr) return SceErrorFromErrno(GuestEfault); - if (*path == '\0') return SceErrorFromErrno(2); + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + path = copy.c_str(); + if (copy.empty()) return SceErrorFromErrno(2); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native, true, true)) return SceKernelError(error); return StatNative(native, std::string_view(path).back() == '/', sb); @@ -421,14 +447,20 @@ int StatInDirectory_nid_no_patch(int directory, const char* path, FileStat* sb, } int APS5_VABI sceKernelUnlink(const char* path) { - if (path == nullptr) return SceErrorFromErrno(GuestEfault); - if (*path == '\0') return SceErrorFromErrno(2); + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + path = copy.c_str(); + if (copy.empty()) return SceErrorFromErrno(2); + 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); constexpr int GuestEperm = 1; std::error_code error; if (std::filesystem::is_directory(std::filesystem::symlink_status(native, error))) return SceErrorFromErrno(GuestEperm); if (NativeUnlink(native) != 0) { +#ifdef _WIN32 + if (NativeSymbolicLinkLoop(native.parent_path())) return SCE_KERNEL_ERROR_ELOOP; +#endif return SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno); } RecordWrittenPath_nid_no_patch(native); diff --git a/core/libs/prx/libkernel/File/src/SandboxCertificates.cpp b/core/libs/prx/libkernel/File/src/SandboxCertificates.cpp index a5af2341a3..40ed2effdb 100644 --- a/core/libs/prx/libkernel/File/src/SandboxCertificates.cpp +++ b/core/libs/prx/libkernel/File/src/SandboxCertificates.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/General.hpp" #include @@ -21,8 +22,6 @@ #include #endif -extern "C" const char* APS5_VABI sceKernelGetFsSandboxRandomWord(); - namespace { constexpr char CertificateBegin[] = "-----BEGIN CERTIFICATE-----"; constexpr char CertificateEnd[] = "-----END CERTIFICATE-----"; diff --git a/core/libs/prx/libkernel/File/src/SharedMemory.cpp b/core/libs/prx/libkernel/File/src/SharedMemory.cpp index c5b133d941..8e18c4bcb6 100644 --- a/core/libs/prx/libkernel/File/src/SharedMemory.cpp +++ b/core/libs/prx/libkernel/File/src/SharedMemory.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include "prx/libkernel/File/include/SharedMemory.hpp" @@ -14,6 +16,8 @@ #include #include #include +#include +#include #ifdef _WIN32 #include @@ -22,6 +26,11 @@ #include #include #include +#include +#include +#include +#include "nid/Sha1.hpp" +extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); #else #include "nid/Sha1.hpp" #include @@ -33,10 +42,34 @@ #include #endif -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { +#ifdef _WIN32 +std::mutex sharedDescriptorAccessMutex; +std::unordered_map sharedDescriptorAccess; + +const std::wstring& WindowsNamedRoot() { + static const std::wstring root = [] { + const auto directory = std::filesystem::temp_directory_path(); + std::vector pathBuffer(32768); + const HANDLE handle = ::CreateFileW(directory.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) + throw std::system_error(static_cast(::GetLastError()), std::system_category(), "opening the named shared-memory temporary root"); + const DWORD length = ::GetFinalPathNameByHandleW(handle, pathBuffer.data(), static_cast(pathBuffer.size()), + FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + const DWORD error = length == 0 ? ::GetLastError() : length >= pathBuffer.size() ? ERROR_FILENAME_EXCED_RANGE : ERROR_SUCCESS; + ::CloseHandle(handle); + if (error != ERROR_SUCCESS) + throw std::system_error(static_cast(error), std::system_category(), "resolving the named shared-memory temporary root"); + return (std::filesystem::path(std::wstring(pathBuffer.data(), length)) / L"AnyPS5" / L"shm").wstring(); + }(); + return root; +} + +void IgnoreDescriptorParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {} +#endif + bool IsHostSharedMemoryDescriptor(int descriptor) { #ifdef __linux__ char descriptorPath[64]; @@ -47,6 +80,44 @@ bool IsHostSharedMemoryDescriptor(int descriptor) { objectPath[length] = '\0'; return std::string_view(objectPath).starts_with("/dev/shm/anyps5-shm-") || std::string_view(objectPath).starts_with("/memfd:anyps5 shm_open"); +#elif defined(_WIN32) + { + std::lock_guard lock(sharedDescriptorAccessMutex); + if (!sharedDescriptorAccess.contains(descriptor)) return false; + } + const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreDescriptorParameter); + const auto handleValue = ::_get_osfhandle(descriptor); + _set_thread_local_invalid_parameter_handler(previous); + if (handleValue == -1) return false; + const auto handle = reinterpret_cast(handleValue); + std::vector pathBuffer(32768); + const DWORD length = ::GetFinalPathNameByHandleW(handle, pathBuffer.data(), static_cast(pathBuffer.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + if (length == 0 || length >= pathBuffer.size()) return false; + std::wstring objectPath(pathBuffer.data(), length); + std::wstring_view root; + try { + root = WindowsNamedRoot(); + } catch (...) { + return false; + } + if (objectPath.size() <= root.size() || objectPath[root.size()] != L'\\') return false; + if (::CompareStringOrdinal(objectPath.data(), static_cast(root.size()), root.data(), static_cast(root.size()), TRUE) != CSTR_EQUAL) return false; + const std::wstring_view relative(objectPath.data() + root.size() + 1, objectPath.size() - root.size() - 1); + if (relative.find_first_of(L"\\/") != std::wstring_view::npos) return false; + const auto validHex = [](wchar_t character) { + return (character >= L'0' && character <= L'9') || (character >= L'a' && character <= L'f'); + }; + if (relative.size() < 44 || !std::all_of(relative.begin(), relative.begin() + 40, validHex)) return false; + if (relative.substr(40) == L".dat") return true; + constexpr std::wstring_view marker = L"-unlinked-"; + if (!relative.substr(40).starts_with(marker) || !relative.ends_with(L".dat")) return false; + const auto suffix = relative.substr(40 + marker.size(), relative.size() - 40 - marker.size() - 4); + const auto separator = suffix.find(L'-'); + if (separator == std::wstring_view::npos || separator == 0 || separator + 1 == suffix.size()) return false; + const auto decimal = [](std::wstring_view value) { + return std::all_of(value.begin(), value.end(), [](wchar_t character) { return character >= L'0' && character <= L'9'; }); + }; + return decimal(suffix.substr(0, separator)) && decimal(suffix.substr(separator + 1)); #else (void)descriptor; return false; @@ -107,7 +178,6 @@ int GuestError(int error) { } } -#ifndef _WIN32 int NamedGuestError(int error) { switch (error) { case ENOENT: @@ -133,14 +203,17 @@ int NamedGuestError(int error) { } } -std::string NamedHostPath(const char* path, int& error) { +std::string NamedHostName(const char* path, int& error) { if (path == nullptr) { error = EFAULT; return {}; } std::size_t length = 0; while (length < 1024 && path[length] != '\0') ++length; if (length == 1024) { error = ENAMETOOLONG; return {}; } if (length == 0 || path[0] != '/') { error = EINVAL; return {}; } const auto digest = Nid::Sha1(std::vector(path, path + length)); - std::string hostName = "/anyps5-shm-"; + std::string hostName; +#ifndef _WIN32 + hostName = "/anyps5-shm-"; +#endif for (const auto byte : digest) { hostName += "0123456789abcdef"[byte >> 4]; hostName += "0123456789abcdef"[byte & 15]; @@ -149,9 +222,384 @@ std::string NamedHostPath(const char* path, int& error) { return hostName; } +#ifdef _WIN32 +int WindowsNamedError(DWORD error) { + switch (error) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: return ENOENT; + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: return EACCES; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: return EEXIST; + case ERROR_INVALID_NAME: return EINVAL; + case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; + case ERROR_DISK_FULL: return ENOSPC; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: return ENOMEM; + default: return 0; + } +} + +std::filesystem::path WindowsNamedPath(const std::string& name) { + return std::filesystem::path(WindowsNamedRoot()) / std::filesystem::path(name + ".dat"); +} + +void EnsurePrivateWindowsDirectory(const std::filesystem::path& path, bool objectDirectory = false) { + const std::wstring ownerRights = objectDirectory ? L"0x001701ff" : L"FA"; + const std::wstring policy = L"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;" + ownerRights + L";;;OW)"; + PSECURITY_DESCRIPTOR descriptor = nullptr; + if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW( + policy.c_str(), SDDL_REVISION_1, &descriptor, nullptr)) + throw std::system_error(static_cast(::GetLastError()), std::system_category(), "creating the named shared-memory access policy"); + PACL dacl = nullptr; + BOOL present = FALSE; + BOOL defaulted = FALSE; + if (!::GetSecurityDescriptorDacl(descriptor, &present, &dacl, &defaulted) || !present || dacl == nullptr) { + const DWORD error = ::GetLastError(); + ::LocalFree(descriptor); + throw std::system_error(static_cast(error == ERROR_SUCCESS ? ERROR_INVALID_SECURITY_DESCR : error), + std::system_category(), "reading the named shared-memory access policy"); + } + + SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), descriptor, FALSE}; + if (!::CreateDirectoryW(path.c_str(), &security) && ::GetLastError() != ERROR_ALREADY_EXISTS) { + const DWORD error = ::GetLastError(); + ::LocalFree(descriptor); + throw std::system_error(static_cast(error), std::system_category(), "creating the named shared-memory directory"); + } + const DWORD attributes = ::GetFileAttributesW(path.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0 || + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + const DWORD error = attributes == INVALID_FILE_ATTRIBUTES ? ::GetLastError() : ERROR_ACCESS_DENIED; + ::LocalFree(descriptor); + throw std::system_error(static_cast(error), std::system_category(), "validating the named shared-memory directory"); + } + const DWORD result = ::SetNamedSecurityInfoW(const_cast(path.c_str()), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, + nullptr, nullptr, dacl, nullptr); + ::LocalFree(descriptor); + if (result != ERROR_SUCCESS) throw std::system_error(static_cast(result), std::system_category(), "securing the named shared-memory directory"); +} + +DWORD SetWindowsNamedPermissions(HANDLE handle, int mode) { + std::wstring policy = L"D:P(A;OICI;FA;;;SY)(A;;WD;;;OW)"; + if ((mode & 0400) != 0) policy += L"(A;;GRWD;;;OW)"; + if ((mode & 0200) != 0) policy += L"(A;;GWSDWD;;;OW)"; + PSECURITY_DESCRIPTOR descriptor = nullptr; + if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(policy.c_str(), SDDL_REVISION_1, &descriptor, nullptr)) + return ::GetLastError(); + PACL dacl = nullptr; + BOOL present = FALSE; + BOOL defaulted = FALSE; + if (!::GetSecurityDescriptorDacl(descriptor, &present, &dacl, &defaulted) || !present || dacl == nullptr) { + const DWORD error = ::GetLastError(); + ::LocalFree(descriptor); + return error == ERROR_SUCCESS ? ERROR_INVALID_SECURITY_DESCR : error; + } + const DWORD result = ::SetSecurityInfo(handle, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, nullptr, nullptr, dacl, nullptr); + ::LocalFree(descriptor); + return result; +} + +void CleanupUnlinkedWindowsObjects(const std::filesystem::path& objectPath, const std::string& name) { + const auto pattern = objectPath.parent_path() / (std::wstring(name.begin(), name.end()) + L"-unlinked-*.dat"); + WIN32_FIND_DATAW data{}; + const HANDLE search = ::FindFirstFileW(pattern.c_str(), &data); + if (search == INVALID_HANDLE_VALUE) return; + do { + if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + const auto orphanPath = objectPath.parent_path() / data.cFileName; + HANDLE orphan = ::CreateFileW(orphanPath.c_str(), DELETE, 0, nullptr, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT, nullptr); + if (orphan != INVALID_HANDLE_VALUE) { + FILE_DISPOSITION_INFO deletion{TRUE}; + ::SetFileInformationByHandle(orphan, FileDispositionInfo, &deletion, sizeof(deletion)); + ::CloseHandle(orphan); + } + } + } while (::FindNextFileW(search, &data)); + ::FindClose(search); +} + +void ReapUnlinkedWindowsObject(const std::filesystem::path& orphanPath) { + HANDLE orphan = ::CreateFileW(orphanPath.c_str(), DELETE, 0, nullptr, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT, nullptr); + if (orphan == INVALID_HANDLE_VALUE) return; + FILE_DISPOSITION_INFO deletion{TRUE}; + ::SetFileInformationByHandle(orphan, FileDispositionInfo, &deletion, sizeof(deletion)); + ::CloseHandle(orphan); +} + +class WindowsNamedLock { +public: + explicit WindowsNamedLock(const std::string& name) { + Initialize(WindowsNamedPath(name).wstring()); + } + + explicit WindowsNamedLock(HANDLE object) { + BY_HANDLE_FILE_INFORMATION status{}; + if (!::GetFileInformationByHandle(object, &status)) { error = ::GetLastError(); return; } + Initialize(L"object-" + std::to_wstring(status.dwVolumeSerialNumber) + L"-" + + std::to_wstring(status.nFileIndexHigh) + L"-" + std::to_wstring(status.nFileIndexLow)); + } + + ~WindowsNamedLock() { + if (locked) ::ReleaseMutex(handle); + if (handle != nullptr) ::CloseHandle(handle); + } + + DWORD Error() const { return error; } + +private: + void Initialize(const std::wstring& identity) { + const auto bytes = std::vector(reinterpret_cast(identity.data()), + reinterpret_cast(identity.data() + identity.size())); + const auto digest = Nid::Sha1(bytes); + std::wstring lockName = L"Global\\AnyPS5NamedShm-"; + for (const auto byte : digest) { + lockName += L"0123456789abcdef"[byte >> 4]; + lockName += L"0123456789abcdef"[byte & 15]; + } + handle = ::CreateMutexW(nullptr, FALSE, lockName.c_str()); + if (handle == nullptr) { error = ::GetLastError(); return; } + const DWORD result = ::WaitForSingleObject(handle, INFINITE); + if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED) locked = true; + else error = result == WAIT_FAILED ? ::GetLastError() : ERROR_GEN_FAILURE; + } + HANDLE handle = nullptr; + DWORD error = ERROR_SUCCESS; + bool locked = false; +}; + +int OpenNamedFile(const char* path, int flags, int mode) { + const int savedError = errno; + int validationError = 0; + const auto hostName = NamedHostName(path, validationError); + if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid); + + WindowsNamedLock namespaceLock(hostName); + if (namespaceLock.Error() != ERROR_SUCCESS) { + if (const int guest = WindowsNamedError(namespaceLock.Error())) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_open: locking the host named-object namespace failed, error=" + std::to_string(namespaceLock.Error())); + } + + std::filesystem::path objectPath; + try { + objectPath = WindowsNamedPath(hostName); + EnsurePrivateWindowsDirectory(objectPath.parent_path().parent_path()); + EnsurePrivateWindowsDirectory(objectPath.parent_path(), true); + CleanupUnlinkedWindowsObjects(objectPath, hostName); + } catch (const std::system_error& error) { + const auto nativeError = static_cast(error.code().value()); + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_open: preparing the host named-object namespace failed: " + std::string(error.what())); + } + + const bool readWrite = (flags & SCE_KERNEL_O_ACCMODE) == SCE_KERNEL_O_RDWR; + const DWORD access = GENERIC_READ | (readWrite ? GENERIC_WRITE : 0) | WRITE_DAC; + const bool create = (flags & SCE_KERNEL_O_CREAT) != 0; + const DWORD creationAccess = access | (create ? WRITE_DAC | DELETE : 0); + ::SetLastError(ERROR_SUCCESS); + HANDLE handle = ::CreateFileW(objectPath.c_str(), creationAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, create ? CREATE_NEW : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); + DWORD createResult = ::GetLastError(); + bool created = handle != INVALID_HANDLE_VALUE && create; + if (handle == INVALID_HANDLE_VALUE && create && createResult == ERROR_FILE_EXISTS && !(flags & SCE_KERNEL_O_EXCL)) { + handle = ::CreateFileW(objectPath.c_str(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); + createResult = ::GetLastError(); + } + if (handle == INVALID_HANDLE_VALUE) { + const DWORD nativeError = createResult; + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_open: opening the host named object failed, error=" + std::to_string(nativeError)); + } + + BY_HANDLE_FILE_INFORMATION status{}; + if (!::GetFileInformationByHandle(handle, &status) || (status.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + const DWORD lastError = ::GetLastError(); + const DWORD nativeError = lastError == ERROR_SUCCESS ? ERROR_INVALID_NAME : lastError; + if (created) { + FILE_DISPOSITION_INFO deletion{TRUE}; + ::SetFileInformationByHandle(handle, FileDispositionInfo, &deletion, sizeof(deletion)); + } + ::CloseHandle(handle); + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_open: refusing a reparse-point named object, error=" + std::to_string(nativeError)); + } + + if (created) { + const int permissions = (mode & 0777) & ~File::GetCreationMask_nid_no_patch(); + const DWORD nativeError = SetWindowsNamedPermissions(handle, permissions); + if (nativeError != ERROR_SUCCESS) { + FILE_DISPOSITION_INFO deletion{TRUE}; + ::SetFileInformationByHandle(handle, FileDispositionInfo, &deletion, sizeof(deletion)); + ::CloseHandle(handle); + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_open: setting guest creation permissions failed, error=" + std::to_string(nativeError)); + } + } + + if ((flags & SCE_KERNEL_O_TRUNC) && readWrite) { + WindowsNamedLock objectLock(handle); + if (objectLock.Error() != ERROR_SUCCESS) { + const DWORD nativeError = objectLock.Error(); + ::CloseHandle(handle); + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_open: locking the host named object for truncation failed, error=" + std::to_string(nativeError)); + } + LARGE_INTEGER zero{}; + if (!::SetFilePointerEx(handle, zero, nullptr, FILE_BEGIN) || !::SetEndOfFile(handle)) { + const DWORD nativeError = ::GetLastError(); + ::CloseHandle(handle); + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_open: truncating the host named object failed, error=" + std::to_string(nativeError)); + } + } + + const int nativeFlags = _O_BINARY | (readWrite ? _O_RDWR : _O_RDONLY); + const int descriptor = ::_open_osfhandle(reinterpret_cast(handle), nativeFlags); + if (descriptor >= 0) { + File::TrackSharedMemoryDescriptor_nid_no_patch(descriptor, readWrite); + errno = savedError; + return descriptor; + } + const int nativeError = errno; + ::CloseHandle(handle); + if (const int guest = GuestError(nativeError)) return Fail(guest); + throw std::runtime_error("shm_open: converting the host named object to a descriptor failed, errno=" + std::to_string(nativeError)); +} + +int UnlinkNamedFile(const char* path) { + const int savedError = errno; + int validationError = 0; + const auto hostName = NamedHostName(path, validationError); + if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid); + WindowsNamedLock namespaceLock(hostName); + if (namespaceLock.Error() != ERROR_SUCCESS) { + if (const int guest = WindowsNamedError(namespaceLock.Error())) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_unlink: locking the host named-object namespace failed, error=" + std::to_string(namespaceLock.Error())); + } + + std::filesystem::path objectPath; + try { + objectPath = WindowsNamedPath(hostName); + } catch (const std::filesystem::filesystem_error& error) { + const auto nativeError = static_cast(error.code().value()); + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_unlink: locating the host named object failed: " + std::string(error.what())); + } + + HANDLE objectHandle = ::CreateFileW(objectPath.c_str(), FILE_READ_ATTRIBUTES | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT, nullptr); + if (objectHandle == INVALID_HANDLE_VALUE) { + const DWORD nativeError = ::GetLastError(); + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_unlink: opening the host named object failed, error=" + std::to_string(nativeError)); + } + BY_HANDLE_FILE_INFORMATION status{}; + if (!::GetFileInformationByHandle(objectHandle, &status) || + (status.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) != 0) { + const DWORD lastError = ::GetLastError(); + const DWORD nativeError = lastError == ERROR_SUCCESS ? ERROR_INVALID_NAME : lastError; + ::CloseHandle(objectHandle); + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_unlink: refusing an invalid host named object, error=" + std::to_string(nativeError)); + } + + static std::atomic sequence{0}; + for (unsigned attempt = 0; attempt < 64; ++attempt) { + const auto orphanPath = objectPath.parent_path() / + (objectPath.stem().wstring() + L"-unlinked-" + std::to_wstring(::GetCurrentProcessId()) + + L"-" + std::to_wstring(sequence.fetch_add(1, std::memory_order_relaxed)) + objectPath.extension().wstring()); + if (!::MoveFileExW(objectPath.c_str(), orphanPath.c_str(), MOVEFILE_WRITE_THROUGH)) { + const DWORD nativeError = ::GetLastError(); + if (nativeError == ERROR_ALREADY_EXISTS || nativeError == ERROR_FILE_EXISTS) continue; + ::CloseHandle(objectHandle); + if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest)); + throw std::runtime_error("shm_unlink: detaching the host named object failed, error=" + std::to_string(nativeError)); + } + ::CloseHandle(objectHandle); + ReapUnlinkedWindowsObject(orphanPath); + errno = savedError; + return 0; + } + ::CloseHandle(objectHandle); + return Fail(NamedGuestError(EEXIST)); +} + +int SetSharedMemoryModeInternal(int descriptor, int mode) { + if (!IsHostSharedMemoryDescriptor(descriptor)) return -2; + const auto rawHandle = ::_get_osfhandle(descriptor); + if (rawHandle == -1) { errno = EBADF; return -1; } + const auto handle = reinterpret_cast(rawHandle); + WindowsNamedLock objectLock(handle); + if (objectLock.Error() != ERROR_SUCCESS) { + const int error = WindowsNamedError(objectLock.Error()); + errno = error ? error : EIO; + return -1; + } + const DWORD nativeError = SetWindowsNamedPermissions(handle, mode & 0777); + if (nativeError == ERROR_SUCCESS) return 0; + const int error = WindowsNamedError(nativeError); + errno = error ? error : EACCES; + return -1; +} + +std::filesystem::path SharedMemoryDescriptorPath(int descriptor) { + const auto rawHandle = ::_get_osfhandle(descriptor); + std::filesystem::path finalPath; + if (rawHandle != -1) { + std::vector path(32768); + const DWORD length = ::GetFinalPathNameByHandleW(reinterpret_cast(rawHandle), path.data(), + static_cast(path.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + if (length > 4 && length < path.size()) { + std::wstring value(path.data(), length); + finalPath = std::filesystem::path(std::move(value)); + } + } + return finalPath; +} + +void ReapSharedMemoryDescriptorPath(const std::filesystem::path& path) { + if (!path.empty() && path.filename().wstring().find(L"-unlinked-") != std::wstring::npos) + ReapUnlinkedWindowsObject(path); +} + +int CloseSharedMemoryDescriptorInternal(int descriptor) { + if (!IsHostSharedMemoryDescriptor(descriptor)) return -2; + const int savedError = errno; + const auto finalPath = SharedMemoryDescriptorPath(descriptor); + int result; + { + std::lock_guard lock(sharedDescriptorAccessMutex); + result = ::_close(descriptor); + if (result == 0) sharedDescriptorAccess.erase(descriptor); + } + if (result == 0) ReapSharedMemoryDescriptorPath(finalPath); + if (result == 0) errno = savedError; + return result; +} + +int ReplaceSharedMemoryDescriptorInternal(int source, int destination) { + if (!IsHostSharedMemoryDescriptor(destination)) return -2; + const int savedError = errno; + const auto finalPath = SharedMemoryDescriptorPath(destination); + const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreDescriptorParameter); + const int result = ::_dup2(source, destination); + _set_thread_local_invalid_parameter_handler(previous); + if (result == 0) { + ReapSharedMemoryDescriptorPath(finalPath); + errno = savedError; + } + return result; +} +#else int OpenNamedFile(const char* path, int flags, int mode) { int validationError = 0; - const auto hostName = NamedHostPath(path, validationError); + const auto hostName = NamedHostName(path, validationError); if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid); HostShmLock namespaceLock; if (namespaceLock.Error() != 0) { @@ -222,6 +670,44 @@ bool IsSharedMemoryDescriptor_nid_no_patch(int descriptor) { return IsHostSharedMemoryDescriptor(descriptor); } +#ifdef _WIN32 +int SetSharedMemoryMode_nid_no_patch(int descriptor, int mode) { + return SetSharedMemoryModeInternal(descriptor, mode); +} + +int CloseSharedMemoryDescriptor_nid_no_patch(int descriptor) { + return CloseSharedMemoryDescriptorInternal(descriptor); +} + +int ReplaceSharedMemoryDescriptor_nid_no_patch(int source, int destination) { + return ReplaceSharedMemoryDescriptorInternal(source, destination); +} + +int SharedMemoryDescriptorWritable_nid_no_patch(int descriptor) { + if (!IsHostSharedMemoryDescriptor(descriptor)) return -2; + std::lock_guard lock(sharedDescriptorAccessMutex); + const auto found = sharedDescriptorAccess.find(descriptor); + return found != sharedDescriptorAccess.end() && found->second ? 1 : 0; +} + +void TrackSharedMemoryDescriptor_nid_no_patch(int descriptor, bool writable) { + std::lock_guard lock(sharedDescriptorAccessMutex); + sharedDescriptorAccess[descriptor] = writable; +} + +void ForgetSharedMemoryDescriptor_nid_no_patch(int descriptor) { + std::lock_guard lock(sharedDescriptorAccessMutex); + sharedDescriptorAccess.erase(descriptor); +} + +void DuplicateSharedMemoryDescriptor_nid_no_patch(int source, int destination) { + std::lock_guard lock(sharedDescriptorAccessMutex); + sharedDescriptorAccess.erase(destination); + const auto found = sharedDescriptorAccess.find(source); + if (found != sharedDescriptorAccess.end()) sharedDescriptorAccess[destination] = found->second; +} +#endif + namespace { class SharedMemoryLock { @@ -230,6 +716,18 @@ public: #ifdef __linux__ struct stat originalStatus{}; if (::fstat(value, &originalStatus) != 0) { shared = true; error = errno; return; } +#elif defined(_WIN32) + if (!IsHostSharedMemoryDescriptor(value)) return; + const auto originalValue = ::_get_osfhandle(value); + if (originalValue == -1) return; + BY_HANDLE_FILE_INFORMATION originalStatus{}; + if (!::GetFileInformationByHandle(reinterpret_cast(originalValue), &originalStatus)) { + shared = true; + error = EBADF; + return; + } + windowsObjectLock = std::make_unique(reinterpret_cast(originalValue)); + if (windowsObjectLock->Error() != ERROR_SUCCESS) { shared = true; error = WindowsNamedError(windowsObjectLock->Error()); if (!error) error = EIO; return; } #endif if (!IsHostSharedMemoryDescriptor(value)) { #ifdef __linux__ @@ -247,10 +745,20 @@ public: if (hostLock->Error() != 0) { error = hostLock->Error(); return; } descriptor = Duplicate(value); if (descriptor < 0) { error = errno; return; } +#ifdef _WIN32 + DuplicateSharedMemoryDescriptor_nid_no_patch(value, descriptor); +#endif if (!IsHostSharedMemoryDescriptor(descriptor)) { error = EBADF; return; } if (!Refresh()) { error = errno; return; } #ifdef __linux__ if (status.st_dev != originalStatus.st_dev || status.st_ino != originalStatus.st_ino) error = EBADF; +#elif defined(_WIN32) + BY_HANDLE_FILE_INFORMATION duplicatedStatus{}; + const auto duplicatedValue = ::_get_osfhandle(descriptor); + if (duplicatedValue == -1 || !::GetFileInformationByHandle(reinterpret_cast(duplicatedValue), &duplicatedStatus) || + duplicatedStatus.dwVolumeSerialNumber != originalStatus.dwVolumeSerialNumber || + duplicatedStatus.nFileIndexHigh != originalStatus.nFileIndexHigh || + duplicatedStatus.nFileIndexLow != originalStatus.nFileIndexLow) error = EBADF; #endif } @@ -258,7 +766,7 @@ public: if (descriptor >= 0) { const int savedError = errno; #ifdef _WIN32 - ::_close(descriptor); + CloseSharedMemoryDescriptor_nid_no_patch(descriptor); #else ::close(descriptor); #endif @@ -292,6 +800,9 @@ private: int descriptor = -1; std::optional hostLock; +#ifdef _WIN32 + std::unique_ptr windowsObjectLock; +#endif bool shared = false; int error = 0; }; @@ -303,7 +814,9 @@ std::int64_t WriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, if (guard.Error() != 0) { errno = guard.Error(); return -1; } if (!guard.Refresh()) return -1; const auto fd = guard.Descriptor(); -#ifndef _WIN32 +#ifdef _WIN32 + if (SharedMemoryDescriptorWritable_nid_no_patch(fd) == 0) { errno = EBADF; return -1; } +#else const int access = ::fcntl(fd, F_GETFL); if (access < 0) return -1; if ((access & O_ACCMODE) == O_RDONLY) { errno = EBADF; return -1; } @@ -331,7 +844,9 @@ std::int64_t PwriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, if (guard.Error() != 0) { errno = guard.Error(); return -1; } if (!guard.Refresh()) return -1; const auto fd = guard.Descriptor(); -#ifndef _WIN32 +#ifdef _WIN32 + if (SharedMemoryDescriptorWritable_nid_no_patch(fd) == 0) { errno = EBADF; return -1; } +#else const int access = ::fcntl(fd, F_GETFL); if (access < 0) return -1; if ((access & O_ACCMODE) == O_RDONLY) { errno = EBADF; return -1; } @@ -341,7 +856,19 @@ std::int64_t PwriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, if (offset >= status.st_size) return 0; const auto count = std::min(size, static_cast(status.st_size - offset)); #ifdef _WIN32 - const auto result = NativePositioned_nid_no_patch(fd, const_cast(buffer), static_cast(count), offset, true); + const auto handle = reinterpret_cast(::_get_osfhandle(fd)); + if (handle == INVALID_HANDLE_VALUE || ::GetFileType(handle) != FILE_TYPE_DISK) { errno = EBADF; return -1; } + LARGE_INTEGER previous{}; + if (!::SetFilePointerEx(handle, LARGE_INTEGER{}, &previous, FILE_CURRENT)) { errno = EIO; return -1; } + OVERLAPPED overlapped{}; + overlapped.Offset = static_cast(offset); + overlapped.OffsetHigh = static_cast(static_cast(offset) >> 32u); + DWORD written = 0; + const BOOL succeeded = ::WriteFile(handle, buffer, static_cast(std::min(count, std::numeric_limits::max())), &written, &overlapped); + const DWORD writeError = succeeded ? ERROR_SUCCESS : ::GetLastError(); + if (!::SetFilePointerEx(handle, previous, nullptr, FILE_BEGIN)) throw std::runtime_error("pwrite: restoring the host file position failed"); + if (!succeeded) { errno = writeError == ERROR_ACCESS_DENIED ? EBADF : EIO; return -1; } + const auto result = written; #else const auto result = ::pwrite(fd, buffer, static_cast(count), static_cast(offset)); #endif @@ -436,9 +963,19 @@ std::int64_t PwritevSharedMemory_nid_no_patch(int descriptor, const struct iovec #endif int TruncateSharedMemory_nid_no_patch(int descriptor, std::int64_t length) { +#ifdef _WIN32 + const auto positioned = LockPositionedTransfer(descriptor); +#endif SharedMemoryLock guard(descriptor); if (!guard.IsShared()) return -2; - if (guard.Error() != 0) { errno = guard.Error(); return -1; } + if (guard.Error() != 0) { + errno = guard.Error(); +#ifdef _WIN32 + return errno; +#else + return -1; +#endif + } const auto fd = guard.Descriptor(); #ifdef _WIN32 const int result = static_cast(::_chsize_s(fd, length)); @@ -455,8 +992,11 @@ extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int m if (accessMode != SCE_KERNEL_O_RDONLY && accessMode != SCE_KERNEL_O_RDWR) return Fail(GuestInvalid); if ((flags & ~AcceptedFlags) != 0) return Fail(GuestInvalid); if (path != anonymousObject) { + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return Fail(error); + path = copy.c_str(); #ifdef _WIN32 - NotImplemented_nid_no_patch("shm_open of a named object"); + return OpenNamedFile(path, flags, mode); #else return OpenNamedFile(path, flags, mode); #endif @@ -470,13 +1010,14 @@ extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int m } extern "C" int APS5_VABI shm_unlink_nid_postfix(const char* path) { + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return Fail(error); + path = copy.c_str(); #ifdef _WIN32 - (void)path; - NotImplemented_nid_no_patch("shm_unlink of a named object"); - return 0; + return UnlinkNamedFile(path); #else int validationError = 0; - const auto hostName = NamedHostPath(path, validationError); + const auto hostName = NamedHostName(path, validationError); if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid); HostShmLock namespaceLock; if (namespaceLock.Error() != 0) { diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 2146bbe2d6..a0d83cf50b 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include #include #include @@ -43,10 +45,6 @@ #include #endif -struct KernelIovec { - void* base; - std::size_t length; -}; static_assert(sizeof(KernelIovec) == sizeof(GuestSockets::Iovec)); static_assert(sizeof(KernelIovec) == sizeof(AppArchiveBuffer) && offsetof(KernelIovec, length) == offsetof(AppArchiveBuffer, Length)); @@ -59,6 +57,7 @@ static constexpr std::uint64_t KERNEL_MNT_NOSUID = 0x8; #ifdef _WIN32 #include +#include "prx/libkernel/File/include/WindowsSymbolicLink.hpp" #include #include #include @@ -90,16 +89,7 @@ static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) { (void)mode; return ::_wmkdir(path.wstring().c_str()); } -static int NativeChmod(const std::filesystem::path& path, int mode) { - RecordWrittenPath_nid_no_patch(path); - return ::_wchmod(path.wstring().c_str(), mode); -} -std::optional File::WindowsDescriptorPath_nid_no_patch(int descriptor) { - const auto handle = reinterpret_cast(::_get_osfhandle(descriptor)); - if (handle == INVALID_HANDLE_VALUE) { - errno = EBADF; - return std::nullopt; - } +static std::optional HandlePath(HANDLE handle) { std::wstring path(MAX_PATH, L'\0'); auto length = ::GetFinalPathNameByHandleW(handle, path.data(), static_cast(path.size()), FILE_NAME_NORMALIZED); if (length >= path.size()) { @@ -119,6 +109,32 @@ std::optional File::WindowsDescriptorPath_nid_no_patch(in else if (path.starts_with(LR"(\\?\)") && path.size() > 6 && path[5] == L':' && path[6] == L'\\') path.erase(0, 4); return path; } +std::optional File::WindowsDescriptorPath_nid_no_patch(int descriptor) { + const auto handle = reinterpret_cast(::_get_osfhandle(descriptor)); + if (handle == INVALID_HANDLE_VALUE) { + errno = EBADF; + return std::nullopt; + } + return HandlePath(handle); +} +static int NativeChmod(const std::filesystem::path& path, int mode) { + const DWORD attributes = GetFileAttributesW(path.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) { + RecordWrittenPath_nid_no_patch(path); + return ::_wchmod(path.wstring().c_str(), mode); + } + const HANDLE handle = CreateFileW(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + errno = WindowsOpenError_nid_no_patch(GetLastError()); + return -1; + } + const auto target = HandlePath(handle); + CloseHandle(handle); + if (!target) return -1; + RecordWrittenPath_nid_no_patch(*target); + return ::_wchmod(target->wstring().c_str(), mode); +} static int NativeFchmod(int descriptor, int mode) { const auto path = File::WindowsDescriptorPath_nid_no_patch(descriptor); return path ? NativeChmod(*path, mode) : -1; @@ -172,10 +188,13 @@ static int SetTimes(HANDLE handle, const KernelTimeval* times) { errno = EINVAL; return -1; } - return SetFileTime(handle, nullptr, &access, &modified) ? 0 : File::WindowsFileTime::Failure(GetLastError()); + FILETIME creation{}; + if (!GetFileTime(handle, &creation, nullptr, nullptr)) return File::WindowsFileTime::Failure(GetLastError()); + const auto birth = CompareFileTime(&modified, &creation) < 0 ? &modified : nullptr; + return SetFileTime(handle, birth, &access, &modified) ? 0 : File::WindowsFileTime::Failure(GetLastError()); } static int NativeUtimes(const std::filesystem::path& path, const KernelTimeval* times, bool followLink = true) { - const auto handle = CreateFileW(path.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + const auto handle = CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | (followLink ? 0 : FILE_FLAG_OPEN_REPARSE_POINT), nullptr); if (handle == INVALID_HANDLE_VALUE) return File::WindowsFileTime::Failure(GetLastError()); const int result = SetTimes(handle, times); @@ -188,7 +207,7 @@ static int NativeFutimes(int descriptor, const KernelTimeval* times) { const auto original = reinterpret_cast(::_get_osfhandle(descriptor)); if (original == INVALID_HANDLE_VALUE) { errno = EBADF; return -1; } if (GetFileType(original) != FILE_TYPE_DISK) { errno = EINVAL; return -1; } - const auto handle = ReOpenFile(original, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0); + const auto handle = ReOpenFile(original, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0); if (handle == INVALID_HANDLE_VALUE) return File::WindowsFileTime::Failure(GetLastError()); const int result = SetTimes(handle, times); CloseHandle(handle); @@ -263,9 +282,10 @@ static std::int64_t NativePositionedTransfer(int descriptor, void* buf, std::siz errno = ESPIPE; return -1; } + const auto at = write && File::Appends(descriptor) ? ~std::uint64_t{} : static_cast(offset); OVERLAPPED overlapped{}; - overlapped.Offset = static_cast(offset); - overlapped.OffsetHigh = static_cast(static_cast(offset) >> 32u); + overlapped.Offset = static_cast(at); + overlapped.OffsetHigh = static_cast(at >> 32u); DWORD done = 0; const BOOL ok = write ? ::WriteFile(handle, buf, static_cast(nbytes), &done, &overlapped) : ::ReadFile(handle, buf, static_cast(nbytes), &done, &overlapped); @@ -432,6 +452,28 @@ static bool FailPosixPositionedGuestSocket(int descriptor) { return true; } +static bool IsPipeDescriptor(int descriptor) { +#ifdef _WIN32 + const auto handle = reinterpret_cast(WithoutParameterHandler([descriptor] { return ::_get_osfhandle(descriptor); })); + return handle != INVALID_HANDLE_VALUE && ::GetFileType(handle) == FILE_TYPE_PIPE; +#else + struct stat status; + return ::fstat(descriptor, &status) == 0 && S_ISFIFO(status.st_mode); +#endif +} + +static int PositionedArgumentError(int descriptor, bool nullBuffer, std::int64_t offset) { + if (!nullBuffer && offset >= 0) return 0; + if (!GuestSockets::IsOpen(descriptor) && !DescriptorIsOpen_nid_no_patch(descriptor)) return GUEST_EBADF; + if (GuestSockets::IsOpen(descriptor) || IsPipeDescriptor(descriptor)) return GUEST_ESPIPE; + return offset < 0 ? GUEST_EINVAL : GUEST_EFAULT; +} + +static int TransferArgumentError(int descriptor, bool nullBuffer) { + if (!nullBuffer) return 0; + return DescriptorIsOpen_nid_no_patch(descriptor) ? GUEST_EFAULT : GUEST_EBADF; +} + static int SceErrorFromErrno(int error) { int guest = error > 0 && error <= 34 ? error : error == GUEST_ENOTEMPTY ? error : GUEST_EIO; if (error == EAGAIN) guest = GUEST_EWOULDBLOCK; @@ -457,8 +499,6 @@ static int PathLookupFailure(const std::error_code& error) { return lookup ? SceErrorFromErrno(error.default_error_condition().value()) : 0; } -extern "C" int* APS5_VABI __error_nid_postfix(); - static int PosixFailure(int error) { *__error_nid_postfix() = error; return -1; @@ -476,6 +516,14 @@ static std::int64_t PosixResult(std::int64_t result) { return result < 0 ? PosixFailure(static_cast(result) & 0xffff) : result; } +static int DeviceFilesystem(KernelStatfs* fs) { + fs->f_bsize = 512; + fs->f_iosize = 512; + fs->f_blocks = 2; + fs->f_namemax = 255; + return 0; +} + #ifdef _WIN32 static int NativeFstatfs(int descriptor, KernelStatfs* fs) { if (!File::IsDirectoryDescriptor(descriptor)) { @@ -483,6 +531,7 @@ static int NativeFstatfs(int descriptor, KernelStatfs* fs) { if (handle == INVALID_HANDLE_VALUE) return GUEST_EBADF; const DWORD type = ::GetFileType(handle); if (type == FILE_TYPE_PIPE) return GUEST_EINVAL; + if (type == FILE_TYPE_CHAR) return DeviceFilesystem(fs); if (type != FILE_TYPE_DISK) throw std::runtime_error("_fstatfs: descriptor " + std::to_string(descriptor) + " is not a file on a Windows volume"); } @@ -530,6 +579,7 @@ static int NativeFstatfs(int descriptor, KernelStatfs* fs) { if (::fstat(descriptor, &file) != 0) return SceErrorFromErrno(errno) & 0xffff; if (S_ISSOCK(file.st_mode) && (::fcntl(descriptor, F_GETFL) & O_PATH) == 0) return GUEST_EINVAL; if (NativeIsPipe(descriptor)) return GUEST_EINVAL; + if (S_ISCHR(file.st_mode)) return DeviceFilesystem(fs); struct statvfs host{}; if (::fstatvfs(descriptor, &host) != 0) return SceErrorFromErrno(errno) & 0xffff; if (host.f_flag & ST_RDONLY) fs->f_flags |= KERNEL_MNT_RDONLY; @@ -558,15 +608,15 @@ extern "C" int APS5_VABI pipe_nid_postfix(int* descriptors) { const int result = ::pipe(native); #endif if (result != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); - File::RememberDescriptor(native[0], 0); - File::RememberDescriptor(native[1], 0); + File::RememberPipe(native[0]); + File::RememberPipe(native[1]); std::memcpy(descriptors, native, sizeof(native)); return 0; } -static int PathError(const char* path) { - if (path == nullptr) return GUEST_EFAULT; - return *path == '\0' ? GUEST_ENOENT : 0; +static int PathError(const char* path, std::string& copy) { + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return error; + return copy.empty() ? GUEST_ENOENT : 0; } #ifdef _WIN32 @@ -592,11 +642,10 @@ static bool HasNonDirectoryPrefix(const std::filesystem::path& native) { return false; } -extern "C" int APS5_VABI dup_nid_postfix(int d); -extern "C" int APS5_VABI close_nid_postfix(int d); - int File::DuplicateFrom(int fd, int start) { #ifdef _WIN32 + GuestResourceLimit openFileLimit{}; + if (getrlimit_nid_postfix(8, &openFileLimit) == 0 && start >= openFileLimit.rlim_cur) return PosixFailure(GUEST_EINVAL); std::vector below; int duplicate; while ((duplicate = dup_nid_postfix(fd)) >= 0 && duplicate < start) below.push_back(duplicate); @@ -613,6 +662,15 @@ int File::DuplicateFrom(int fd, int start) { } static int LookupError(const std::filesystem::path& native) { +#ifdef _WIN32 + const auto named = native.has_filename() ? native : WithoutTrailingSeparator(native); + const DWORD attributes = GetFileAttributesW(named.c_str()); + if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + FileStat target{}; + if (!File::TryFillFileStat(named, &target)) return SceErrorFromErrno(errno) & 0xffff; + return native.has_filename() || (target.st_mode & 0170000) == 0040000 ? 0 : GUEST_ENOTDIR; + } +#endif std::error_code error; const auto status = std::filesystem::status(native, error); if (std::filesystem::exists(status)) return native.has_filename() || std::filesystem::is_directory(status) ? 0 : GUEST_ENOTDIR; @@ -652,7 +710,9 @@ static int SetFileFlags(const char* function, const std::string& target, std::ui extern "C" { int APS5_VABI chmod_nid_postfix(const char* path, int mode) { - if (const int error = PathError(path)) return PosixFailure(error); + 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 (NativeChmod(native, mode & 07777) != 0) return PosixResult(SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno)); @@ -660,7 +720,9 @@ int APS5_VABI chmod_nid_postfix(const char* path, int mode) { } int APS5_VABI chflags_nid_postfix(const char* path, std::uint64_t flags) { - if (const int error = PathError(path)) return PosixFailure(error); + 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); @@ -669,7 +731,7 @@ int APS5_VABI chflags_nid_postfix(const char* path, std::uint64_t flags) { } int APS5_VABI fchflags_nid_postfix(int d, std::uint64_t flags) { - if (GuestSockets::IsOpen(d) || File::IsSharedMemoryDescriptor_nid_no_patch(d)) return PosixFailure(GUEST_EINVAL); + if (GuestSockets::IsOpen(d) || IsKqueueDescriptor(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); @@ -689,7 +751,7 @@ int APS5_VABI nmount_nid_postfix(const KernelIovec* iov, unsigned int niov, int } int APS5_VABI close_nid_postfix(int d) { - if (File::IsHostStream(d)) return PosixFailure(GUEST_EBADF); + if (File::IsHostStream(d)) return PosixFailure(GUEST_EPERM); int socketError = 0; if (KernelCloseSocket_nid_no_patch(d, &socketError)) { if (socketError == 0) return 0; @@ -697,16 +759,18 @@ int APS5_VABI close_nid_postfix(int d) { return -1; } if (GuestSockets::IsOpen(d)) return GuestSockets::Close(d); + EqueueDescriptorClosed(d); #ifdef _WIN32 const auto positioned = File::LockPositionedTransfer(d); #endif - EqueueDescriptorClosed(d); File::ForgetRandomDevice(d); File::ForgetDescriptor(d); ForgetJitSharedMemory(d); #ifdef _WIN32 File::ForgetDirectoryDescriptor(d); File::ForgetFileLock(d); + const int shared = File::CloseSharedMemoryDescriptor_nid_no_patch(d); + if (shared != -2) return shared; return WithoutParameterHandler([d] { return ::_close(d); }); #else return ::close(d); @@ -725,26 +789,9 @@ std::atomic g_hostStandardOutput{-1}; void PreserveHostStream(int standard, std::atomic& slot) { if (slot.load() >= 0) return; -#ifdef _WIN32 - const int saved = WithoutParameterHandler([standard] { return ::_dup(standard); }); -#else const int saved = standard == 1 ? KeepHostStandardOutput_nid_no_patch() : KeepHostStandardError_nid_no_patch(); -#endif if (saved < 0) throw std::runtime_error(standard == 1 ? "dup2: cannot keep the host standard output" : "dup2: cannot keep the host standard error"); - int expected = -1; - if (!slot.compare_exchange_strong(expected, saved)) { -#ifdef _WIN32 - _close(saved); -#endif - return; - } -#ifdef _WIN32 - if (!AdoptHostStandardStream_nid_no_patch(standard, saved)) { - slot.store(-1); - _close(saved); - throw std::runtime_error(standard == 1 ? "dup2: cannot keep the host standard output" : "dup2: cannot keep the host standard error"); - } -#endif + slot.store(saved); } void PreserveHostStandardError() { PreserveHostStream(2, g_hostStandardError); } @@ -764,6 +811,7 @@ int APS5_VABI dup_nid_postfix(int d) { #ifdef _WIN32 const int duplicate = WithoutParameterHandler([d] { return ::_dup(d); }); if (duplicate >= 0) File::DuplicateDirectoryDescriptor(d, duplicate); + if (duplicate >= 0) File::DuplicateSharedMemoryDescriptor_nid_no_patch(d, duplicate); #else const int duplicate = ::dup(d); #endif @@ -796,8 +844,10 @@ int APS5_VABI dup2_nid_postfix(int from, int to) { if (from == to) return to; if (to == 1) PreserveHostStandardOutput(); if (to == 2) PreserveHostStandardError(); + auto positioned = File::LockPositionedTransfer(to); auto* const standard = HostStandardHandle_nid_no_patch(to); - const int duplicated = WithoutParameterHandler([from, to] { return ::_dup2(from, 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); @@ -806,7 +856,10 @@ int APS5_VABI dup2_nid_postfix(int from, int to) { File::DuplicateDirectoryDescriptor(from, to); File::ForgetFileLock(to); File::ForgetRandomDevice(to); + File::ForgetSharedMemoryDescriptor_nid_no_patch(to); + File::DuplicateSharedMemoryDescriptor_nid_no_patch(from, to); if (sourceIsRandom) File::RememberRandomDevice(to); + positioned.unlock(); File::ShareDescriptor(from, to); DuplicateJitSharedMemory(from, to); return to; @@ -843,6 +896,9 @@ int APS5_VABI flock_nid_postfix(int d, int operation) { #endif const int type = operation & 8 ? 8 : operation & 2 ? 2 : operation & 1 ? 1 : 0; if (type == 0) return PosixFailure(GUEST_EBADF); +#ifdef _WIN32 + if (File::IsDirectoryDescriptor(d)) return 0; +#endif if (NativeFlock(d, type | (operation & 4)) != 0) { #ifdef _WIN32 const auto error = ::GetLastError(); @@ -858,16 +914,13 @@ int APS5_VABI flock_nid_postfix(int d, int operation) { } int APS5_VABI _fstatfs_nid_postfix(int d, KernelStatfs* buf) { - if (GuestSockets::IsOpen(d)) return PosixFailure(GUEST_EINVAL); + if (GuestSockets::IsOpen(d) || IsKqueueDescriptor(d)) return PosixFailure(GUEST_EINVAL); if (d < 0) return PosixFailure(GUEST_EBADF); File::RefuseArchivedFile(d, "fstatfs"); KernelStatfs result{}; result.f_version = KERNEL_STATFS_VERSION; if (const int error = NativeFstatfs(d, &result)) return PosixFailure(error); - if (buf == nullptr) return PosixFailure(GUEST_EFAULT); - const GuestArena::HostWrite destination(buf, sizeof(result)); - if (!destination.Open()) return PosixFailure(GUEST_EFAULT); - std::memcpy(buf, &result, sizeof(result)); + if (!GuestCopyOut_nid_no_patch(buf, &result, sizeof(result))) return PosixFailure(GUEST_EFAULT); return 0; } @@ -877,11 +930,13 @@ int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb) { if (fstat_nid_disambig1_nid_postfix(d, &ignored) != 0) return -1; return PosixFailure(GUEST_EFAULT); } + FileStat result{}; if (GuestSockets::IsOpen(d)) { - if (const int error = GuestSockets::Stat(d, sb)) return PosixFailure(error); - return 0; + if (const int error = GuestSockets::Stat(d, &result)) return PosixFailure(error); + } else if (!File::FillFileStatFromDescriptor(d, &result)) { + return PosixFailure(SceErrorFromErrno(errno) & 0xffff); } - if (!File::FillFileStatFromDescriptor(d, sb)) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); + if (!GuestCopyOut_nid_no_patch(sb, &result, sizeof(result))) return PosixFailure(GUEST_EFAULT); return 0; } @@ -919,11 +974,16 @@ int64_t APS5_VABI lseek_nid_postfix(int d, int64_t offset, int whence) { } int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) { - if (const int error = PathError(path)) return PosixFailure(error); + 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 (NativeMkdir(native, mode) != 0) { const int nativeError = errno; +#ifdef _WIN32 + if (File::WindowsSymbolicLinkLoop(WithoutTrailingSeparator(native).parent_path())) return PosixFailure(GUEST_ELOOP); +#endif const bool blockedByFile = (nativeError == ENOENT || nativeError == EINVAL) && HasNonDirectoryPrefix(native); return PosixResult(SceErrorFromErrno(blockedByFile ? GUEST_ENOTDIR : nativeError)); } @@ -933,7 +993,9 @@ int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) { int APS5_VABI open_nid_postfix(const char* path, int flags, int mode) { if (File::InvalidOpenFlags(flags)) return PosixFailure(GUEST_EINVAL); - if (const int error = PathError(path)) return PosixFailure(error); + std::string copy; + if (const int error = PathError(path, copy)) return PosixFailure(error); + path = copy.c_str(); return PosixResult(sceKernelOpen(path, flags, static_cast(mode))); } @@ -961,8 +1023,7 @@ int APS5_VABI _open_nid_postfix(const char* path, int flags, ...) { int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t offset) { if (ExceedsTransferLimit(nbytes)) return PosixFailure(GUEST_EINVAL); - if (buf == nullptr && nbytes != 0) return PosixFailure(GUEST_EFAULT); - if (offset < 0) return PosixFailure(GUEST_EINVAL); + if (const int error = PositionedArgumentError(d, buf == nullptr && nbytes != 0, offset)) return PosixFailure(error); if (FailPosixPositionedGuestSocket(d)) return -1; const GuestArena::HostWrite destination(buf, nbytes); if (!destination.Open()) return PosixFailure(GUEST_EFAULT); @@ -975,9 +1036,9 @@ int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t off int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_t nbytes, int64_t offset) { if (ExceedsTransferLimit(nbytes)) return PosixFailure(GUEST_EINVAL); - if (buf == nullptr && nbytes != 0) return PosixFailure(GUEST_EFAULT); - if (offset < 0) return PosixFailure(GUEST_EINVAL); + if (const int error = PositionedArgumentError(d, buf == nullptr && nbytes != 0, offset)) return PosixFailure(error); if (FailPosixPositionedGuestSocket(d)) return -1; + const File::WriteSync sync(d); GuestArena::GuestArenaPrepareHostRead_nid_no_patch(buf, nbytes); char empty = 0; const auto result = NativePwrite(d, buf == nullptr ? &empty : buf, nbytes, offset); @@ -987,7 +1048,7 @@ int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_ int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) { if (ExceedsTransferLimit(nbytes)) return PosixFailure(GUEST_EINVAL); if (GuestSockets::IsOpen(d)) return GuestSockets::Read(d, buf, static_cast(nbytes)); - if (buf == nullptr && nbytes != 0) return PosixFailure(GUEST_EFAULT); + if (const int error = TransferArgumentError(d, buf == nullptr && nbytes != 0)) return PosixFailure(error); char emptyBuffer = 0; return PosixResult(sceKernelRead(d, buf == nullptr ? &emptyBuffer : buf, static_cast(nbytes))); } @@ -995,7 +1056,7 @@ int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) { std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size_t count) { if (ExceedsTransferLimit(count)) return PosixFailure(GUEST_EINVAL); if (GuestSockets::IsOpen(descriptor)) return GuestSockets::Read(descriptor, buffer, count); - if (buffer == nullptr && count != 0) return PosixFailure(GUEST_EFAULT); + if (const int error = TransferArgumentError(descriptor, buffer == nullptr && count != 0)) return PosixFailure(error); char emptyBuffer = 0; return PosixResult(sceKernelRead(descriptor, buffer == nullptr ? &emptyBuffer : buffer, count)); } @@ -1003,7 +1064,7 @@ std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size) { if (size < 0) return PosixFailure(GUEST_EINVAL); if (GuestSockets::IsOpen(d)) return GuestSockets::Write(d, str, static_cast(size)); - if (str == nullptr && size != 0) return PosixFailure(GUEST_EFAULT); + if (const int error = TransferArgumentError(d, str == nullptr && size != 0)) return PosixFailure(error); const char emptyBuffer = 0; return PosixResult(sceKernelWrite(d, str == nullptr ? &emptyBuffer : str, static_cast(size))); } @@ -1011,34 +1072,45 @@ int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size) { std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count) { if (ExceedsTransferLimit(count)) return PosixFailure(GUEST_EINVAL); if (GuestSockets::IsOpen(descriptor)) return GuestSockets::Write(descriptor, buffer, count); - if (buffer == nullptr && count != 0) return PosixFailure(GUEST_EFAULT); + if (const int error = TransferArgumentError(descriptor, buffer == nullptr && count != 0)) return PosixFailure(error); const char emptyBuffer = 0; return PosixResult(sceKernelWrite(descriptor, buffer == nullptr ? &emptyBuffer : buffer, count)); } int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb) { if (sb == nullptr) return PosixFailure(GUEST_EFAULT); - if (const int error = PathError(path)) return PosixFailure(error); + std::string copy; + if (const int error = PathError(path, copy)) return PosixFailure(error); + path = copy.c_str(); return PosixResult(sceKernelStat(path, sb)); } int APS5_VABI lstat_nid_postfix(const char* path, FileStat* sb) { if (sb == nullptr) return PosixFailure(GUEST_EFAULT); - if (const int error = PathError(path)) return PosixFailure(error); + 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 (!File::FillLinkStat(native, sb)) return PosixResult(SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno)); + if (const int error = ResolvePathError_nid_no_patch(path, native, true)) return PosixFailure(error); + FileStat result{}; + if (!File::FillLinkStat(native, &result)) return PosixResult(SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno)); + if (!GuestCopyOut_nid_no_patch(sb, &result, sizeof(result))) return PosixFailure(GUEST_EFAULT); return 0; } int APS5_VABI unlink_nid_postfix(const char* path) { - if (const int error = PathError(path)) return PosixFailure(error); + std::string copy; + if (const int error = PathError(path, copy)) return PosixFailure(error); + path = copy.c_str(); return PosixResult(sceKernelUnlink(path)); } int APS5_VABI sceKernelCheckReachability(const char* path) { if (path == nullptr) return SCE_KERNEL_ERROR_EINVAL; - if (*path == '\0') return SCE_KERNEL_ERROR_ENOENT; + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(error); + path = copy.c_str(); + if (copy.empty()) return SCE_KERNEL_ERROR_ENOENT; if (std::strlen(path) > 255) return SCE_KERNEL_ERROR_ENAMETOOLONG; std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native, true, true)) return SceKernelError(error); @@ -1053,11 +1125,14 @@ int APS5_VABI sceKernelFstat(int d, FileStat* sb) { if (const int error = sceKernelFstat(d, &ignored)) return error; return SceErrorFromErrno(GUEST_EFAULT); } + FileStat result{}; if (GuestSockets::IsOpen(d)) { - const int error = GuestSockets::Stat(d, sb); - return error == 0 ? 0 : SceErrorFromErrno(error); + const int error = GuestSockets::Stat(d, &result); + if (error != 0) return SceErrorFromErrno(error); + } else if (!File::FillFileStatFromDescriptor(d, &result)) { + return SceErrorFromErrno(errno); } - if (!File::FillFileStatFromDescriptor(d, sb)) return SceErrorFromErrno(errno); + if (!GuestCopyOut_nid_no_patch(sb, &result, sizeof(result))) return SceErrorFromErrno(GUEST_EFAULT); return 0; } @@ -1115,6 +1190,35 @@ int APS5_VABI sceKernelGetdents(int fd, char* buf, int nbytes) { #else +static bool InsideApp0(int fd) { + const int savedErrno = errno; + const bool inside = [fd] { + const auto directory = File::DirectoryDescriptorPath(fd); + if (!directory) return false; + std::error_code error; + const auto app0 = std::filesystem::weakly_canonical(ResolveDiskPath_nid_no_patch("/app0"), error); + if (error || app0.empty()) return false; + const auto relative = directory->lexically_relative(app0); + return !relative.empty() && *relative.begin() != ".."; + }(); + errno = savedErrno; + return inside; +} + +static unsigned char GuestEntryType(int fd, const struct dirent64& entry, bool followLinks) { + if (entry.d_type != DT_LNK && entry.d_type != DT_UNKNOWN) return entry.d_type; + if (entry.d_type == DT_LNK && !followLinks) return DT_LNK; + const int savedErrno = errno; + struct stat target{}; + const bool found = ::fstatat(fd, entry.d_name, &target, followLinks ? 0 : AT_SYMLINK_NOFOLLOW) == 0; + errno = savedErrno; + if (!found) return entry.d_type; + if (S_ISDIR(target.st_mode)) return DT_DIR; + if (S_ISREG(target.st_mode)) return DT_REG; + if (S_ISLNK(target.st_mode)) return DT_LNK; + return entry.d_type; +} + int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* basep) { if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL); File::RefuseArchivedFile(fd, "getdirentries"); @@ -1136,6 +1240,7 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas if (read < 0) return SceErrorFromErrno(errno); std::size_t written = 0; off_t resume = base; + const bool followLinks = InsideApp0(fd); for (long offset = 0; offset < read;) { const auto* entry = reinterpret_cast(host.data() + offset); const auto nameLength = std::strlen(entry->d_name); @@ -1152,7 +1257,7 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas const auto nameBytes = static_cast(nameLength); std::memcpy(out, &fileNumber, sizeof(fileNumber)); std::memcpy(out + 4, &recordLength, sizeof(recordLength)); - out[6] = static_cast(entry->d_type); + out[6] = static_cast(GuestEntryType(fd, *entry, followLinks)); out[7] = static_cast(nameBytes); std::memcpy(out + GuestHeaderBytes, entry->d_name, nameLength); written += record; @@ -1173,14 +1278,20 @@ int APS5_VABI getdirentries_nid_postfix(int fd, char* buf, int nbytes, int64_t* return PosixResult(sceKernelGetdirentries(fd, buf, nbytes, basep)); } +int APS5_VABI _getdirentries_nid_postfix(int fd, char* buf, int nbytes, int64_t* basep) { + return getdirentries_nid_postfix(fd, buf, nbytes, basep); +} + int APS5_VABI getdents_nid_postfix(int fd, char* buf, int nbytes) { return PosixResult(sceKernelGetdents(fd, buf, nbytes)); } int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode) { (void)mode; - if (!path) return SceErrorFromErrno(GUEST_EFAULT); - if (!*path) return SceErrorFromErrno(GUEST_ENOENT); + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + path = copy.c_str(); + if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); while (!native.has_filename() && native.has_relative_path()) native = native.parent_path(); @@ -1188,6 +1299,9 @@ int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode) { if (std::filesystem::exists(native, error)) return SceErrorFromErrno(GUEST_EEXIST); if (const int failure = PathLookupFailure(error)) return failure; if (!std::filesystem::is_directory(WithoutTrailingSeparator(native).parent_path(), error)) { +#ifdef _WIN32 + if (File::WindowsSymbolicLinkLoop(WithoutTrailingSeparator(native).parent_path())) return SceKernelError(GUEST_ELOOP); +#endif return SceErrorFromErrno(HasNonDirectoryPrefix(native) ? GUEST_ENOTDIR : GUEST_ENOENT); } if (!std::filesystem::create_directory(native, error)) return SceErrorFromErrno(error.value() ? error.value() : GUEST_EIO); @@ -1245,8 +1359,7 @@ static void PrepareWriteIovecs(const KernelIovec* iov, int iovcnt) { #ifdef _WIN32 int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset) { - if (buf == nullptr && nbytes != 0) return SceErrorFromErrno(GUEST_EFAULT); - if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL); + if (const int error = PositionedArgumentError(d, buf == nullptr && nbytes != 0, offset)) return SceErrorFromErrno(error); if (const auto error = PositionedGuestSocketError(d); error != 0) return error; const GuestArena::HostWrite destination(buf, nbytes); if (!destination.Open()) return SceErrorFromErrno(GUEST_EFAULT); @@ -1258,9 +1371,9 @@ int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset } int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset) { - if (buf == nullptr && nbytes != 0) return SceErrorFromErrno(GUEST_EFAULT); - if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL); + if (const int error = PositionedArgumentError(d, buf == nullptr && nbytes != 0, offset)) return SceErrorFromErrno(error); if (const auto error = PositionedGuestSocketError(d); error != 0) return error; + const File::WriteSync sync(d); GuestArena::GuestArenaPrepareHostRead_nid_no_patch(buf, nbytes); char empty = 0; const auto result = NativePwrite(d, nbytes == 0 && buf == nullptr ? &empty : buf, nbytes, offset); @@ -1289,6 +1402,7 @@ static std::int64_t TransferIovecs(int d, const KernelIovec* guestIov, int iovcn : GuestSockets::Readv(d, reinterpret_cast(iov), iovcnt); return result < 0 ? SceKernelError(*__error_nid_postfix()) : result; } + const File::WriteSync sync(write ? d : -1); if (write) PrepareWriteIovecs(iov, iovcnt); if (!write && File::IsRandomDevice(d)) return ReadRandomIovecs(d, iov, iovcnt); if (!write) { @@ -1301,14 +1415,15 @@ static std::int64_t TransferIovecs(int d, const KernelIovec* guestIov, int iovcn return result < 0 ? SceErrorFromErrno(errno) : 0; } if (!write && offset == nullptr && File::IsDirectoryDescriptor(d)) return SceErrorFromErrno(GUEST_EISDIR); - if (write && offset == nullptr && iovcnt > 1 && total <= static_cast(std::numeric_limits::max()) && File::Appends(d)) { + if (write && iovcnt > 1 && total <= static_cast(std::numeric_limits::max()) && File::Appends(d)) { std::vector gathered; gathered.reserve(total); for (int i = 0; i < iovcnt; ++i) { const auto* base = static_cast(iov[i].base); gathered.insert(gathered.end(), base, base + iov[i].length); } - const auto result = NativeTransferBuffer(d, gathered.data(), gathered.size(), true); + const auto result = offset != nullptr ? NativePositionedTransfer(d, gathered.data(), gathered.size(), *offset, true) + : NativeTransferBuffer(d, gathered.data(), gathered.size(), true); return result < 0 ? SceErrorFromErrno(errno) : result; } const bool whole = write || offset != nullptr || NativeIsDisk(d); @@ -1346,8 +1461,7 @@ int64_t APS5_VABI sceKernelPwritev(int d, const KernelIovec* iov, int iovcnt, in #else int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset) { - if (buf == nullptr && nbytes != 0) return SceErrorFromErrno(GUEST_EFAULT); - if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL); + if (const int error = PositionedArgumentError(d, buf == nullptr && nbytes != 0, offset)) return SceErrorFromErrno(error); if (const auto error = PositionedGuestSocketError(d); error != 0) return error; const GuestArena::HostWrite destination(buf, nbytes); if (!destination.Open()) return SceErrorFromErrno(GUEST_EFAULT); @@ -1358,9 +1472,9 @@ int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset } int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset) { - if (buf == nullptr && nbytes != 0) return SceErrorFromErrno(GUEST_EFAULT); - if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL); + if (const int error = PositionedArgumentError(d, buf == nullptr && nbytes != 0, offset)) return SceErrorFromErrno(error); if (const auto error = PositionedGuestSocketError(d); error != 0) return error; + const File::WriteSync sync(d); GuestArena::GuestArenaPrepareHostRead_nid_no_patch(buf, nbytes); const auto result = NativePwrite(d, buf, nbytes, offset); return result < 0 ? SceErrorFromErrno(errno) : result; @@ -1390,6 +1504,7 @@ int64_t APS5_VABI sceKernelWritev(int d, const KernelIovec* guestIov, int iovcnt const auto result = GuestSockets::Writev(d, reinterpret_cast(iov), iovcnt); return result < 0 ? SceKernelError(*__error_nid_postfix()) : result; } + const File::WriteSync sync(d); PrepareWriteIovecs(iov, iovcnt); const auto sharedResult = File::WritevSharedMemory_nid_no_patch(d, NativeIovecs(iov), iovcnt); if (sharedResult != -2) return sharedResult < 0 ? SceErrorFromErrno(errno) : sharedResult; @@ -1417,6 +1532,7 @@ int64_t APS5_VABI sceKernelPwritev(int d, const KernelIovec* guestIov, int iovcn const auto* iov = vectors.data(); if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL); if (const auto error = PositionedGuestSocketError(d); error != 0) return error; + const File::WriteSync sync(d); PrepareWriteIovecs(iov, iovcnt); const auto sharedResult = File::PwritevSharedMemory_nid_no_patch(d, NativeIovecs(iov), iovcnt, offset); if (sharedResult != -2) return sharedResult < 0 ? SceErrorFromErrno(errno) : sharedResult; @@ -1440,23 +1556,26 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) { if (!from) return SceErrorFromErrno(GUEST_EFAULT); const int result = RenameGuestPath_nid_no_patch(from, to); return result == 0 ? 0 : SceKernelError(errno); - if (const int dot = RenameDotError_nid_no_patch(from)) return SceKernelError(dot); - if (const int dot = RenameDotError_nid_no_patch(to)) return SceKernelError(dot); } int APS5_VABI sceKernelRmdir(const char* path) { - if (!path) return SceErrorFromErrno(GUEST_EFAULT); - if (*path == '\0') return SceErrorFromErrno(GUEST_ENOENT); + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + path = copy.c_str(); + if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT); + 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); std::error_code error; if (std::filesystem::is_symlink(native, error)) return SceErrorFromErrno(GUEST_ENOTDIR); if (const int failure = PathLookupFailure(error)) return failure; if (!std::filesystem::is_directory(native, error)) { +#ifdef _WIN32 + if (File::WindowsSymbolicLinkLoop(WithoutTrailingSeparator(native).parent_path())) return SceKernelError(GUEST_ELOOP); +#endif const bool blockedByFile = HasNonDirectoryPrefix(native) || std::filesystem::exists(WithoutTrailingSeparator(native), error); return SceErrorFromErrno(blockedByFile ? GUEST_ENOTDIR : GUEST_ENOENT); } - if (FinalPathComponentIsDot_nid_no_patch(path)) return SceErrorFromErrno(GUEST_EINVAL); if (!std::filesystem::is_empty(native, error)) return SceErrorFromErrno(GUEST_ENOTEMPTY); if (!RemoveEmptyDirectory(native)) return SceErrorFromErrno(GUEST_EIO); RecordWrittenPath_nid_no_patch(native); @@ -1464,7 +1583,9 @@ int APS5_VABI sceKernelRmdir(const char* path) { } int APS5_VABI rmdir_nid_postfix(const char* path) { - if (const int error = PathError(path)) return PosixFailure(error); + std::string copy; + if (const int error = PathError(path, copy)) return PosixFailure(error); + path = copy.c_str(); return PosixResult(sceKernelRmdir(path)); } @@ -1473,8 +1594,10 @@ int APS5_VABI rmdir_nid_postfix(const char* path) { extern "C" { int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) { - if (!path) return SceErrorFromErrno(GUEST_EFAULT); - if (!*path) return SceErrorFromErrno(GUEST_ENOENT); + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + path = copy.c_str(); + if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); if (NativeChmod(native, mode & 07777) != 0) return SceErrorFromErrno(errno); @@ -1483,7 +1606,12 @@ int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) { int APS5_VABI sceKernelFchmod(int d, std::uint16_t mode) { if (GuestSockets::IsOpen(d)) return SceErrorFromErrno(GUEST_EINVAL); + if (IsPipeDescriptor(d)) return SceErrorFromErrno(GUEST_EINVAL); File::RefuseArchivedFile(d, "fchmod"); +#ifdef _WIN32 + const int sharedMemoryResult = File::SetSharedMemoryMode_nid_no_patch(d, mode); + if (sharedMemoryResult != -2) return sharedMemoryResult == 0 ? 0 : SceErrorFromErrno(errno); +#endif if (NativeFchmod(d, mode & 07777) != 0) return SceErrorFromErrno(errno); return 0; } @@ -1498,8 +1626,10 @@ static bool InvalidTimes(const KernelTimeval* times) { int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t length) { if (length < 0) return SceErrorFromErrno(GUEST_EINVAL); - if (!path) return SceErrorFromErrno(GUEST_EFAULT); - if (*path == '\0') return SceErrorFromErrno(GUEST_ENOENT); + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + path = copy.c_str(); + if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); std::error_code error; @@ -1507,21 +1637,30 @@ int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t lengt if (const int failure = PathLookupFailure(error)) return failure; if (!std::filesystem::exists(status)) return SceErrorFromErrno(NotFoundError_nid_no_patch(native)); if (std::filesystem::is_directory(status)) return SceErrorFromErrno(GUEST_EISDIR); - if (const int failure = NativeTruncate(native, length)) return SceErrorFromErrno(failure); + if (const int failure = NativeTruncate(native, length)) { +#ifdef _WIN32 + if (File::WindowsSymbolicLinkLoop(native)) return SceKernelError(GUEST_ELOOP); +#endif + return SceErrorFromErrno(failure); + } RecordWrittenPath_nid_no_patch(native); return 0; } int APS5_VABI truncate_nid_postfix(const char* path, std::int64_t length) { if (length < 0) return PosixFailure(GUEST_EINVAL); - if (const int error = PathError(path)) return PosixFailure(error); + std::string copy; + if (const int error = PathError(path, copy)) return PosixFailure(error); + path = copy.c_str(); return PosixResult(sceKernelTruncate_nid_postfix(path, length)); } int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* times) { if (InvalidTimes(times)) return SCE_KERNEL_ERROR_EINVAL; - if (!path) return SceErrorFromErrno(GUEST_EFAULT); - if (*path == '\0') return SceErrorFromErrno(GUEST_ENOENT); + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + path = copy.c_str(); + if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); if (NativeUtimes(native, times) != 0) return SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno); @@ -1530,13 +1669,17 @@ int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* int APS5_VABI utimes_nid_postfix(const char* path, const KernelTimeval* times) { if (InvalidTimes(times)) return PosixFailure(GUEST_EINVAL); - if (const int error = PathError(path)) return PosixFailure(error); + std::string copy; + if (const int error = PathError(path, copy)) return PosixFailure(error); + path = copy.c_str(); return PosixResult(sceKernelUtimes_nid_postfix(path, times)); } int APS5_VABI lutimes_nid_postfix(const char* path, const KernelTimeval* times) { if (InvalidTimes(times)) return PosixFailure(GUEST_EINVAL); - if (const int error = PathError(path)) return PosixFailure(error); + 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 (NativeUtimes(native, times, false) != 0) return PosixResult(SceErrorFromErrno(errno)); diff --git a/core/libs/prx/libkernel/File/src/Terminal.cpp b/core/libs/prx/libkernel/File/src/Terminal.cpp index 25f8a85526..d8220e00bb 100644 --- a/core/libs/prx/libkernel/File/src/Terminal.cpp +++ b/core/libs/prx/libkernel/File/src/Terminal.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" #include @@ -13,7 +15,6 @@ #include #endif -extern "C" int* APS5_VABI __error_nid_postfix(); #ifdef _WIN32 extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); #endif diff --git a/core/libs/prx/libkernel/KernelErrors.hpp b/core/libs/prx/libkernel/KernelErrors.hpp index b7d37ba4f2..e3e5b759fd 100644 --- a/core/libs/prx/libkernel/KernelErrors.hpp +++ b/core/libs/prx/libkernel/KernelErrors.hpp @@ -99,5 +99,7 @@ inline constexpr int SCE_KERNEL_ERROR_ENOLINK = SceKernelError(91); inline constexpr int SCE_KERNEL_ERROR_EPROTO = SceKernelError(92); inline constexpr int SCE_KERNEL_ERROR_ENOTCAPABLE = SceKernelError(93); inline constexpr int SCE_KERNEL_ERROR_ECAPMODE = SceKernelError(94); +inline constexpr int SCE_KERNEL_ERROR_ENOTRECOVERABLE = SceKernelError(95); +inline constexpr int SCE_KERNEL_ERROR_EOWNERDEAD = SceKernelError(96); #endif diff --git a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp index fdead458de..b9feacb7d1 100644 --- a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp +++ b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libkernel/Module/ModuleArgs.hpp" @@ -11,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +25,9 @@ #else #include #include +#ifdef __linux__ +#include +#endif #endif #ifdef _WIN32 @@ -36,6 +41,29 @@ void Error(const char* message) { std::snprintf(loaderError.data(), loaderError.size(), "%s", message); pendingError = true; } +void LoadFailure(const std::filesystem::path& source, const std::filesystem::path& resolved, const std::string& reason) { + std::error_code status; + const auto message = resolved == source && std::filesystem::is_regular_file(source, status) + ? "dlopen: " + source.string() + " was not relinked: no " + source.filename().string() + ".guest.prx was found (" + reason + + "); relink its directory with the relinker's --module-dir option" + : "dlopen: cannot load " + resolved.string() + " (" + reason + ")"; + Error(message.c_str()); + static std::mutex reportedLock; + static std::set reported; + std::lock_guard lock(reportedLock); + if (reported.insert(resolved).second) std::fprintf(stderr, "[module] %s\n", message.c_str()); +} +#ifdef _WIN32 +HMODULE LoadWithoutErrorDialog(const std::filesystem::path& path) { + const auto mode = GetThreadErrorMode(); + SetThreadErrorMode(mode | SEM_FAILCRITICALERRORS, nullptr); + const auto loaded = LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + const auto error = GetLastError(); + SetThreadErrorMode(mode, nullptr); + SetLastError(error); + return loaded; +} +#endif struct Module { void* native = nullptr; bool owned = true; @@ -113,6 +141,56 @@ char* APS5_VABI dlerror_nid_postfix() { pendingError = false; return loaderError.data(); } +#ifdef __linux__ +static void InitializeLinuxGuestModule(void* native) { + auto* table = static_cast(::dlsym(native, "__aps5_guest_lifecycle_nid_no_patch")); + if (!table) return; + link_map* requested = nullptr; + link_map* owner = nullptr; + Dl_info info{}; + if (::dlinfo(native, RTLD_DI_LINKMAP, &requested) != 0 || requested == nullptr || + !::dladdr1(table, &info, reinterpret_cast(&owner), RTLD_DL_LINKMAP) || owner == nullptr) + throw std::runtime_error("dlopen: cannot identify guest lifecycle metadata owner"); + if (owner != requested) return; + if (table[0] != 0x415053354c494658ull) throw std::runtime_error("dlopen: unsupported guest lifecycle metadata"); + using Entry = int (APS5_VABI *)(std::size_t, const void*, void*); + using Initializer = void (APS5_VABI *)(int, char**, char**); + static std::mutex initializedLock; + static std::condition_variable initializedChanged; + const auto thread = static_cast(::gettid()); + std::unique_lock lock(initializedLock); + while (table[6] == 1) { + if (table[7] == thread) return; + initializedChanged.wait(lock); + } + if (table[6] == 2) return; + if (table[6] != 0) throw std::runtime_error("dlopen: deferred guest initialization failed or finalized"); + table[6] = 1; + table[7] = thread; + lock.unlock(); + try { + const auto pending = *static_cast(__aps5_get_pending_module_args_nid_no_patch()); + const int result = table[2] == 0 ? 0 : reinterpret_cast(table[2])(pending.args, pending.argp, nullptr); + for (std::uint64_t index = 0; index < table[4]; ++index) { + const auto initializer = *reinterpret_cast(table[8 + index]); + if (initializer) initializer(0, nullptr, nullptr); + } + __aps5_set_module_init_result_nid_no_patch(result); + lock.lock(); + table[6] = 2; + table[7] = 0; + lock.unlock(); + initializedChanged.notify_all(); + } catch (...) { + lock.lock(); + table[6] = 3; + table[7] = 0; + lock.unlock(); + initializedChanged.notify_all(); + throw; + } +} +#endif #ifdef _WIN32 static void InitializeDeferredModule(HMODULE native) { using Entry = int (APS5_VABI *)(std::size_t, const void*, void*); @@ -179,9 +257,15 @@ void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) { module->owned = false; } else { if (!*path) { Error("dlopen: empty module path"); return nullptr; } - const auto resolved = RelinkedModulePath_nid_no_patch(ResolvePath_nid_no_patch(path)); - module->native = LoadLibraryExW(resolved.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); - if (module->native) InitializeDeferredModule(static_cast(module->native)); + const auto source = ResolvePath_nid_no_patch(path); + const auto resolved = RelinkedModulePath_nid_no_patch(source); + module->native = LoadWithoutErrorDialog(resolved); + if (!module->native) { + const auto error = GetLastError(); + LoadFailure(source, resolved, "Windows loader error " + std::to_string(error)); + return nullptr; + } + InitializeDeferredModule(static_cast(module->native)); } if (!module->native) { char message[128]; @@ -189,11 +273,18 @@ void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) { Error(message); return nullptr; } #else - const auto resolved = path ? RelinkedModulePath_nid_no_patch(ResolvePath_nid_no_patch(path)).string() : std::string{}; + const auto source = path ? ResolvePath_nid_no_patch(path) : std::filesystem::path{}; + const auto resolved = path ? RelinkedModulePath_nid_no_patch(source) : std::filesystem::path{}; const int nativeFlags = ((flags & 3) == 1 ? RTLD_LAZY : RTLD_NOW) | ((flags & 0x100) ? RTLD_GLOBAL : RTLD_LOCAL); module->native = ::dlopen(path ? resolved.c_str() : nullptr, nativeFlags); - if (!module->native) { Error(::dlerror()); return nullptr; } + if (!module->native) { + if (path) LoadFailure(source, resolved, ::dlerror()); else Error(::dlerror()); + return nullptr; + } +#ifdef __linux__ + if (path) InitializeLinuxGuestModule(module->native); +#endif #endif std::lock_guard lock(modulesMutex); const auto handle = nextHandle++; diff --git a/core/libs/prx/libkernel/Module/src/Module.cpp b/core/libs/prx/libkernel/Module/src/Module.cpp index 9e71a3183c..9a9b712fcc 100644 --- a/core/libs/prx/libkernel/Module/src/Module.cpp +++ b/core/libs/prx/libkernel/Module/src/Module.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include @@ -111,7 +112,6 @@ extern "C" void ForgetUnloadedUnwindImages_nid_no_patch() { for (const auto base : unloaded) UnwindImages().erase(base); } #elif !defined(__APPLE__) -extern "C" int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, ModuleInfoEx* info); namespace { struct ImageSearch { @@ -142,7 +142,7 @@ bool IsRelinkedImage(std::uint64_t address) { void FillGuestUnwindInfo(std::uint64_t address, ModuleInfoForUnwind* info) { ModuleInfoEx module{}; module.st_size = sizeof(ModuleInfoEx); - if (sceKernelGetModuleInfoFromAddr(address, 2, &module) != 0) + if (sceKernelGetModuleInfoFromAddr(address, 1, &module) != 0) throw std::runtime_error("sceKernelGetModuleInfoForUnwind: failed to query guest module information"); info->eh_frame_hdr_addr = module.eh_frame_hdr_addr; info->eh_frame_addr = module.eh_frame_addr; @@ -151,13 +151,6 @@ void FillGuestUnwindInfo(std::uint64_t address, ModuleInfoForUnwind* info) { } #endif -extern "C" { -void* APS5_VABI dlopen_nid_postfix(const char* path, int flags); -void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name); -int APS5_VABI dlclose_nid_postfix(void* handle); -const char* APS5_VABI sceKernelGetFsSandboxRandomWord(); -} - namespace { constexpr int kRtldNow = 2; diff --git a/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp b/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp index 63451dbcad..2df8293175 100644 --- a/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp +++ b/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include @@ -356,20 +357,22 @@ bool SystemImage(const GuestImage& image) { } #endif -int FillModuleInfo(KernelModule handle, ModuleInfo* info, bool titleOnly) { +template +int FillModuleInfo(KernelModule handle, TInfo* info, bool titleOnly) { if (!info) return SCE_KERNEL_ERROR_EFAULT; - if (info->st_size != sizeof(ModuleInfo)) return SCE_KERNEL_ERROR_EINVAL; + if (info->st_size != sizeof(TInfo)) return SCE_KERNEL_ERROR_EINVAL; for (const auto& image : GuestImages()) { if (ModuleIdForImage_nid_no_patch(image.native) != handle) continue; if (titleOnly && SystemImage(image)) return SCE_KERNEL_ERROR_EPERM; - ModuleInfo result{}; - result.st_size = sizeof(ModuleInfo); + TInfo result{}; + result.st_size = sizeof(TInfo); #ifdef _WIN32 if (!Fill(image.address, result)) return SCE_KERNEL_ERROR_ESRCH; #else - ImageSearch search{image.address, &result, false}; - dl_iterate_phdr(FindImage, &search); + ImageSearch search{image.address, &result, false}; + dl_iterate_phdr(FindImage, &search); if (!search.found) return SCE_KERNEL_ERROR_ESRCH; + if constexpr (std::is_same_v) result.id = handle; #endif *info = result; return 0; @@ -392,22 +395,32 @@ int FillModuleList(KernelModule* handles, std::size_t count, std::size_t* actual extern "C" { int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, ModuleInfoEx* info) { + if (flags != 1 && flags != 2) { + if (!info) throw std::invalid_argument("sceKernelGetModuleInfoFromAddr: null info with flags " + std::to_string(flags)); + *info = {}; + return SCE_KERNEL_ERROR_EINVAL; + } if (!info) return SCE_KERNEL_ERROR_EFAULT; - if (flags != 2) throw std::invalid_argument("sceKernelGetModuleInfoFromAddr: unsupported flags " + std::to_string(flags)); ModuleInfoEx result{}; - result.st_size = sizeof(ModuleInfoEx); #ifdef _WIN32 - if (!Fill(static_cast(address), result)) return SCE_KERNEL_ERROR_ESRCH; + MEMORY_BASIC_INFORMATION memory{}; + const bool found = Fill(static_cast(address), result) && + VirtualQuery(reinterpret_cast(address), &memory, sizeof(memory)) == sizeof(memory); + const GuestImage image{nullptr, found ? reinterpret_cast(memory.AllocationBase) : 0}; #else Dl_info symbol{}; link_map* native = nullptr; - if (!dladdr1(reinterpret_cast(address), &symbol, reinterpret_cast(&native), RTLD_DL_LINKMAP) || !native) - return SCE_KERNEL_ERROR_ESRCH; ImageSearch search{static_cast(address), &result, false}; - dl_iterate_phdr(FindImage, &search); - if (!search.found) return SCE_KERNEL_ERROR_ESRCH; - result.id = ModuleIdForImage_nid_no_patch(native); + if (dladdr1(reinterpret_cast(address), &symbol, reinterpret_cast(&native), RTLD_DL_LINKMAP) && native) + dl_iterate_phdr(FindImage, &search); + const bool found = search.found; + if (found) result.id = ModuleIdForImage_nid_no_patch(native); + const GuestImage image{native, 0}; #endif + if (!found || (flags == 2 && SystemImage(image))) { + *info = {}; + return SCE_KERNEL_ERROR_ESRCH; + } *info = result; return 0; } @@ -428,6 +441,10 @@ int APS5_VABI sceKernelGetModuleInfo2(KernelModule handle, ModuleInfo* info) { return FillModuleInfo(handle, info, true); } +int GetModuleInfoExByHandle_nid_no_patch(KernelModule handle, ModuleInfoEx* info) { + return FillModuleInfo(handle, info, false); +} + std::int32_t APS5_VABI sceKernelGetLibkernelTextLocation_nid_postfix(void** address, std::size_t* size) { if (!address || !size) throw std::invalid_argument("sceKernelGetLibkernelTextLocation: null argument"); const auto images = GuestImages(); diff --git a/core/libs/prx/libkernel/Module/src/Tls.cpp b/core/libs/prx/libkernel/Module/src/Tls.cpp new file mode 100644 index 0000000000..bc3f81240f --- /dev/null +++ b/core/libs/prx/libkernel/Module/src/Tls.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libkernel/include/Api.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +namespace { + +#ifdef _WIN32 +constexpr std::size_t ThreadLocalStoragePointerOffset = 0x58; +#endif + +} + +#if defined(__linux__) +extern "C" void* __tls_get_addr(void* index); +#endif + +extern "C" { + +void* APS5_VABI __tls_get_addr_nid_postfix(const KernelTlsIndex* index) { + if (index == nullptr || index->module == 0) throw std::invalid_argument("__tls_get_addr: null TLS index or module"); +#if defined(_WIN32) + const auto slot = *reinterpret_cast(static_cast(index->module)); + auto* blocks = *reinterpret_cast(reinterpret_cast(NtCurrentTeb()) + ThreadLocalStoragePointerOffset); + if (blocks == nullptr || blocks[slot] == nullptr) throw std::logic_error("__tls_get_addr: thread has no TLS block for the module"); + return blocks[slot] + index->offset; +#elif defined(__linux__) + KernelTlsIndex host = *index; + return __tls_get_addr(&host); +#else + NotImplemented_nid_no_patch("__tls_get_addr on this host"); + return nullptr; +#endif +} + +} diff --git a/core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp b/core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp index 2becc20064..1f49be53c4 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/Pthread/include/Atfork.hpp" #include "../include/Atfork.hpp" #include #include diff --git a/core/libs/prx/libkernel/Pthread/Posix/Attr.cpp b/core/libs/prx/libkernel/Pthread/Posix/Attr.cpp index 86fd01c134..1dae3e0f74 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Attr.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Attr.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include @@ -7,16 +8,6 @@ #include "../include/Pthread.hpp" #include "Common.hpp" -extern "C" { -int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksize); -int APS5_VABI scePthreadAttrSetstack(PthreadAttr* attr, void* addr, std::size_t size); -int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched); -int APS5_VABI sched_get_priority_max_nid_postfix(int policy); -int APS5_VABI sched_get_priority_min_nid_postfix(int policy); -} - static constexpr int GuestSchedFifo = 1; static constexpr int GuestSchedRr = 3; @@ -33,12 +24,15 @@ int APS5_VABI pthread_attr_destroy_nid_postfix(PthreadAttr* attr) { int APS5_VABI pthread_attr_get_np_nid_postfix(Pthread thread, PthreadAttr* attr) { if (!thread || !Valid(attr)) return PosixThread::GUEST_EINVAL; + const auto live = LockLiveGuestThread(thread); + if (!live) return PosixThread::GUEST_ESRCH; auto* p = *attr; p->_stacksize = thread->stackSize; p->stackAddress = thread->stackAddress; p->_detachstate = thread->_detached ? 1 : 0; p->_schedpriority = thread->priority.load(std::memory_order_relaxed); p->_schedpolicy = thread->schedPolicy.load(std::memory_order_relaxed); + p->_createSuspended = thread->createdSuspended; p->_affinity = thread->affinity.load(std::memory_order_relaxed); p->_affinityExplicit = true; p->_scope = thread->scope; diff --git a/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp index 8a4c613626..9d98ca6274 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp @@ -1,4 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/General.hpp" +#include #include #include #include @@ -6,25 +8,12 @@ #include #include -struct PthreadBarrierPrivate { - std::mutex mutex; - std::condition_variable changed; - unsigned count; - unsigned arrived = 0; - unsigned active = 0; - std::uint64_t generation = 0; -}; - namespace { constexpr int PROCESS_PRIVATE = 0; } -struct PthreadBarrierattrPrivate { - int pshared = PROCESS_PRIVATE; -}; - extern "C" { int APS5_VABI pthread_barrierattr_init_nid_postfix(PthreadBarrierattrPrivate** attr) { @@ -60,7 +49,7 @@ int APS5_VABI pthread_barrierattr_setpshared_nid_postfix(PthreadBarrierattrPriva int APS5_VABI pthread_barrier_init_nid_postfix(PthreadBarrierPrivate** barrier, PthreadBarrierattrPrivate* const* attr, unsigned count) { (void)attr; - if (!barrier || count == 0) return 22; + if (!barrier || count == 0 || count > INT_MAX) return 22; try { auto* value = new PthreadBarrierPrivate; value->count = count; diff --git a/core/libs/prx/libkernel/Pthread/Posix/Common.hpp b/core/libs/prx/libkernel/Pthread/Posix/Common.hpp index b89859ae7e..e16aa453c6 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Common.hpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Common.hpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/Time/include/Time.hpp" #ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_POSIX_COMMON_HPP #define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_POSIX_COMMON_HPP @@ -6,8 +7,6 @@ #include #include "SceTypes.hpp" -extern "C" int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); - namespace PosixThread { constexpr int GUEST_ESRCH = 3; @@ -20,11 +19,16 @@ inline int ToErrno(int sceResult) { return sceResult == 0 ? 0 : static_cast(static_cast(sceResult) & 0xFFFFu); } +inline int ToSce(int error) { + return error == 0 ? 0 : static_cast(0x80020000u | static_cast(error)); +} + // Converts an absolute deadline on the guest clock into a relative wait, clamped to what the sce layer accepts. inline bool RelativeMicroseconds(int clockId, const KernelTimespec* abstime, KernelUseconds* usec) { if (!abstime || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) return false; KernelTimespec now{}; clock_gettime_nid_postfix(clockId, &now); + constexpr auto maximum = std::numeric_limits::max(); if (abstime->tv_sec < now.tv_sec - 1) { *usec = 0; diff --git a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp index 6dd1e09509..43f18c44ce 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libkernel/Pthread/include/Cond.hpp" #include "Common.hpp" @@ -39,7 +40,7 @@ int APS5_VABI pthread_cond_signal_nid_postfix(PthreadCond* cond) { } int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime) { - if (!abstime || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) return PosixThread::GUEST_EINVAL; + if (!abstime || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) return PosixThread::GUEST_EINVAL; return toPosix(CondOperations::AbsoluteTimedwait(cond, mutex, abstime)); } @@ -80,4 +81,16 @@ int APS5_VABI pthread_condattr_getpshared_nid_postfix(const PthreadCondattr* att return 0; } +int APS5_VABI pthread_cond_signalto_np_nid_postfix(PthreadCond* cond, Pthread thread) { + return toPosix(scePthreadCondSignalto(cond, thread)); +} + +int APS5_VABI scePthreadCondattrSetpshared(PthreadCondattr* attr, int pshared) { + return PosixThread::ToSce(pthread_condattr_setpshared_nid_postfix(attr, pshared)); +} + +int APS5_VABI scePthreadCondattrGetpshared(const PthreadCondattr* attr, int* pshared) { + return PosixThread::ToSce(pthread_condattr_getpshared_nid_postfix(attr, pshared)); +} + } diff --git a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp index 346b447bb8..6f41a86fde 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -38,8 +40,6 @@ void _initializeStatic(PthreadMutex* mutex, const char* funcName) { extern "C" { -int APS5_VABI sched_get_priority_max_nid_postfix(int policy); - int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex) { if (!mutex) throw std::runtime_error("pthread_mutex_destroy: null mutex"); if (_isStaticInitializer(*mutex)) { @@ -169,4 +169,51 @@ int APS5_VABI pthread_mutexattr_gettype_nid_postfix(const PthreadMutexattr* attr return 0; } +int APS5_VABI pthread_mutexattr_setkind_np_nid_postfix(PthreadMutexattr* attr, int kind) { + if (!attr || !*attr) { + *__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); + return 0; +} + +int APS5_VABI pthread_mutexattr_getkind_np_nid_postfix(PthreadMutexattr attr) { + if (!attr) { + *__error_nid_postfix() = POSIX_EINVAL; + return -1; + } + return static_cast(attr->type); +} + +int APS5_VABI scePthreadMutexattrGettype(const PthreadMutexattr* attr, int* type) { + return PosixThread::ToSce(pthread_mutexattr_gettype_nid_postfix(attr, type)); +} + +int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol) { + return PosixThread::ToSce(pthread_mutexattr_setprotocol_nid_postfix(attr, protocol)); +} + +int APS5_VABI scePthreadMutexattrGetprotocol(const PthreadMutexattr* attr, int* protocol) { + return PosixThread::ToSce(pthread_mutexattr_getprotocol_nid_postfix(attr, protocol)); +} + +int APS5_VABI scePthreadMutexattrSetprioceiling(PthreadMutexattr* attr, int prioceiling) { + return PosixThread::ToSce(pthread_mutexattr_setprioceiling_nid_postfix(attr, prioceiling)); +} + +int APS5_VABI scePthreadMutexattrGetprioceiling(const PthreadMutexattr* attr, int* prioceiling) { + return PosixThread::ToSce(pthread_mutexattr_getprioceiling_nid_postfix(attr, prioceiling)); +} + +int APS5_VABI scePthreadMutexattrSetpshared(PthreadMutexattr* attr, int pshared) { + return PosixThread::ToSce(pthread_mutexattr_setpshared_nid_postfix(attr, pshared)); +} + +int APS5_VABI scePthreadMutexattrGetpshared(const PthreadMutexattr* attr, int* pshared) { + return PosixThread::ToSce(pthread_mutexattr_getpshared_nid_postfix(attr, pshared)); +} + } diff --git a/core/libs/prx/libkernel/Pthread/Posix/Once.cpp b/core/libs/prx/libkernel/Pthread/Posix/Once.cpp index 6919e008be..220e54a0a1 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Once.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Once.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/Pthread/include/Cancel.hpp" @@ -8,10 +9,7 @@ namespace { // FreeBSD guest ABI: PTHREAD_ONCE_INIT is {0, nullptr}; completed state is 1. -struct GuestOnce { - std::int32_t state; - void* mutex; -}; + static_assert(offsetof(GuestOnce, mutex) == 8 && sizeof(GuestOnce) == 16); std::mutex onceMutex; std::condition_variable onceChanged; @@ -39,6 +37,7 @@ extern "C" int APS5_VABI pthread_once_nid_postfix( CleanupHandler reset{}; void* const mark = ThreadCancel::CleanupMark(); __pthread_cleanup_push_imp_nid_postfix(ResetOnce, control, &reset); + try { initialize(); } catch (...) { diff --git a/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp index c07eadf645..c82db18c88 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -37,8 +38,6 @@ void initializeStatic(PthreadRwlock* rwlock, const char* funcName) { extern "C" { -int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock); - int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock* rwlock) { if (!rwlock) throw std::runtime_error("pthread_rwlock_destroy: null rwlock"); if (*rwlock == nullptr) return 0; @@ -70,6 +69,25 @@ int APS5_VABI pthread_rwlockattr_setpshared_nid_postfix(PthreadRwlockattr* attr, return 0; } +int APS5_VABI pthread_rwlockattr_gettype_np_nid_postfix(const PthreadRwlockattr* attr, int* type) { + if (!attr || !*attr) throw std::runtime_error("pthread_rwlockattr_gettype_np: null attr"); + if (!type) throw std::runtime_error("pthread_rwlockattr_gettype_np: null type"); + *type = (*attr)->type; + return 0; +} + +int APS5_VABI scePthreadRwlockattrGettype(const PthreadRwlockattr* attr, int* type) { + return PosixThread::ToSce(pthread_rwlockattr_gettype_np_nid_postfix(attr, type)); +} + +int APS5_VABI scePthreadRwlockattrSetpshared(PthreadRwlockattr* attr, int pshared) { + return PosixThread::ToSce(pthread_rwlockattr_setpshared_nid_postfix(attr, pshared)); +} + +int APS5_VABI scePthreadRwlockattrGetpshared(const PthreadRwlockattr* attr, int* pshared) { + return PosixThread::ToSce(pthread_rwlockattr_getpshared_nid_postfix(attr, pshared)); +} + int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock* rwlock) { initializeStatic(rwlock, __func__); return toPosix(scePthreadRwlockRdlock(rwlock)); @@ -93,6 +111,16 @@ int APS5_VABI pthread_rwlock_timedwrlock_nid_postfix(PthreadRwlock* rwlock, cons return toPosix(scePthreadRwlockTimedwrlock(rwlock, usec)); } +int APS5_VABI pthread_rwlock_reltimedrdlock_np_nid_postfix(PthreadRwlock* rwlock, KernelUseconds usec) { + initializeStatic(rwlock, __func__); + return toPosix(scePthreadRwlockTimedrdlock(rwlock, usec)); +} + +int APS5_VABI pthread_rwlock_reltimedwrlock_np_nid_postfix(PthreadRwlock* rwlock, KernelUseconds usec) { + initializeStatic(rwlock, __func__); + return toPosix(scePthreadRwlockTimedwrlock(rwlock, usec)); +} + int APS5_VABI pthread_rwlock_tryrdlock_nid_postfix(PthreadRwlock* rwlock) { initializeStatic(rwlock, __func__); return toPosix(scePthreadRwlockTryrdlock(rwlock)); diff --git a/core/libs/prx/libkernel/Pthread/Posix/Sem.cpp b/core/libs/prx/libkernel/Pthread/Posix/Sem.cpp index e780d7f59f..fc5ea88197 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Sem.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Sem.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -7,8 +8,14 @@ #include "prx/libkernel/Time/include/TimedWait.hpp" #include #include +#include #include +#include +#include #include +#include +#include +#include // The guest sem_t is at least 8 bytes; its first word holds a pointer to the host semaphore. struct PosixSemaphore { @@ -17,10 +24,19 @@ struct PosixSemaphore { unsigned int count; }; +static constexpr int GUEST_ENOENT = 2; +static constexpr int GUEST_EEXIST = 17; static constexpr int GUEST_EAGAIN = 35; +static constexpr int GUEST_ENAMETOOLONG = 63; static constexpr int GUEST_EOVERFLOW = 84; static constexpr unsigned int GUEST_SEM_VALUE_MAX = 0x7fffffffu; static constexpr int GUEST_REALTIME_CLOCK = 0; +static constexpr int GUEST_O_CREAT = 0x200; +static constexpr int GUEST_O_EXCL = 0x800; +static constexpr std::size_t GUEST_PATH_MAX = 1024; +static constexpr std::size_t GUEST_NAME_MAX = 255; +static constexpr std::string_view NAMED_SEMAPHORE_PREFIX = "/tmp/SEMD"; +static constexpr std::string_view NAMED_SEMAPHORE_FILE_PREFIX = "SEMD"; static int Fail(int error) { errno = error; @@ -34,6 +50,49 @@ static PosixSemaphore* Get(void* sem) { return semaphore; } +struct NamedSemaphoreMapping { + PosixSemaphore* semaphore; + std::uint64_t reserved; +}; + +struct NamedSemaphore { + std::unique_ptr semaphore; + std::unique_ptr mapping; + int openCount = 0; +}; + +struct NamedSemaphores { + std::mutex lock; + std::map> linked; + std::map> open; +}; + +static NamedSemaphores& Named() { + static NamedSemaphores named; + return named; +} + +static bool IsNamed(const void* sem) { + auto& named = Named(); + std::lock_guard lock(named.lock); + return named.open.count(sem) != 0; +} + +static int NamedPathError(const char* function, const char* name, int notAbsolute) { + if (!name) throw std::invalid_argument(std::string(function) + ": null name"); + if (name[0] != '/') return notAbsolute; + if (NAMED_SEMAPHORE_PREFIX.size() + std::strlen(name + 1) >= GUEST_PATH_MAX) return GUEST_ENAMETOOLONG; + return 0; +} + +static int NamedFileError(const char* function, const char* name) { + const std::string_view file(name + 1); + if (file.find('/') != std::string_view::npos) + throw std::runtime_error(std::string(function) + ": the name " + name + " would resolve below /tmp, which is not modelled"); + if (NAMED_SEMAPHORE_FILE_PREFIX.size() + file.size() > GUEST_NAME_MAX) return GUEST_ENAMETOOLONG; + return 0; +} + static int WaitFor(PosixSemaphore* semaphore, const KernelUseconds* usec) { ThreadCancel::Check(); std::unique_lock lock(semaphore->lock); @@ -60,7 +119,7 @@ int APS5_VABI sem_init_nid_postfix(void* sem, int pshared, unsigned int value) { int APS5_VABI sem_destroy_nid_postfix(void* sem) { auto* semaphore = Get(sem); - if (!semaphore) return Fail(PosixThread::GUEST_EINVAL); + if (!semaphore || IsNamed(sem)) return Fail(PosixThread::GUEST_EINVAL); delete semaphore; std::memset(sem, 0, sizeof(semaphore)); return 0; @@ -108,11 +167,9 @@ int APS5_VABI sem_reltimedwait_np_nid_postfix(void* sem, uint32_t usec) { int APS5_VABI sem_post_nid_postfix(void* sem) { auto* semaphore = Get(sem); if (!semaphore) return Fail(PosixThread::GUEST_EINVAL); - { - std::lock_guard lock(semaphore->lock); - if (semaphore->count == GUEST_SEM_VALUE_MAX) return Fail(GUEST_EOVERFLOW); - ++semaphore->count; - } + std::lock_guard lock(semaphore->lock); + if (semaphore->count == GUEST_SEM_VALUE_MAX) return Fail(GUEST_EOVERFLOW); + ++semaphore->count; semaphore->available.NotifyOne(); return 0; } @@ -125,4 +182,86 @@ int APS5_VABI sem_getvalue_nid_postfix(void* sem, int* value) { return 0; } +void* APS5_VABI sem_open_nid_postfix(const char* name, int flags, ...) { + if (const int error = NamedPathError("sem_open", name, PosixThread::GUEST_EINVAL)) { + Fail(error); + return nullptr; + } + if ((flags & ~(GUEST_O_CREAT | GUEST_O_EXCL)) != 0) { + Fail(PosixThread::GUEST_EINVAL); + return nullptr; + } + unsigned int value = 0; + if ((flags & GUEST_O_CREAT) != 0) { +#ifdef _WIN32 + __builtin_sysv_va_list arguments; + __builtin_sysv_va_start(arguments, flags); + static_cast(__builtin_va_arg(arguments, int)); + value = static_cast(__builtin_va_arg(arguments, int)); + __builtin_sysv_va_end(arguments); +#else + std::va_list arguments; + va_start(arguments, flags); + static_cast(va_arg(arguments, int)); + value = static_cast(va_arg(arguments, int)); + va_end(arguments); +#endif + } + if (const int error = NamedFileError("sem_open", name)) { + Fail(error); + return nullptr; + } + auto& named = Named(); + std::lock_guard lock(named.lock); + const std::string file(name + 1); + std::shared_ptr object; + if (const auto found = named.linked.find(file); found != named.linked.end()) { + if ((flags & (GUEST_O_CREAT | GUEST_O_EXCL)) == (GUEST_O_CREAT | GUEST_O_EXCL)) { + Fail(GUEST_EEXIST); + return nullptr; + } + object = found->second; + } else { + if ((flags & GUEST_O_CREAT) == 0) { + Fail(GUEST_ENOENT); + return nullptr; + } + if (value > GUEST_SEM_VALUE_MAX) + throw std::runtime_error("sem_open: initial value " + std::to_string(value) + " is above SEM_VALUE_MAX, which FreeBSD stores unchecked"); + object = std::make_shared(); + object->semaphore = std::make_unique(); + object->semaphore->count = value; + named.linked.emplace(file, object); + } + if (!object->mapping) { + object->mapping = std::make_unique(NamedSemaphoreMapping{object->semaphore.get(), 0}); + named.open.emplace(object->mapping.get(), object); + } + ++object->openCount; + return object->mapping.get(); +} + +int APS5_VABI sem_close_nid_postfix(void* sem) { + if (!sem) throw std::invalid_argument("sem_close: null semaphore"); + auto& named = Named(); + std::lock_guard lock(named.lock); + const auto found = named.open.find(sem); + if (found == named.open.end()) return Fail(PosixThread::GUEST_EINVAL); + const auto object = found->second; + if (--object->openCount == 0) { + named.open.erase(found); + object->mapping.reset(); + } + return 0; +} + +int APS5_VABI sem_unlink_nid_postfix(const char* name) { + if (const int error = NamedPathError("sem_unlink", name, GUEST_ENOENT)) return Fail(error); + if (const int error = NamedFileError("sem_unlink", name)) return Fail(error); + auto& named = Named(); + std::lock_guard lock(named.lock); + if (named.linked.erase(std::string(name + 1)) == 0) return Fail(GUEST_ENOENT); + return 0; +} + } diff --git a/core/libs/prx/libkernel/Pthread/Posix/Spinlock.cpp b/core/libs/prx/libkernel/Pthread/Posix/Spinlock.cpp index 9101c62da8..92c36969eb 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Spinlock.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Spinlock.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include "Common.hpp" #include @@ -5,10 +6,6 @@ #include #include -struct PthreadSpinlockPrivate { - std::atomic owner{}; -}; - namespace { constexpr int GUEST_EPERM = 1; diff --git a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp index 06705a3130..160ce82945 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp @@ -1,3 +1,8 @@ +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include #include #include @@ -14,26 +19,9 @@ #include extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadDetach(Pthread thread); -void APS5_VABI scePthreadExit(void* retval); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadRename(Pthread thread, const char* name); -int APS5_VABI scePthreadGetname(Pthread thread, char* name); -Pthread APS5_VABI scePthreadSelf(); -int APS5_VABI scePthreadEqual(Pthread thread1, Pthread thread2); -int APS5_VABI scePthreadOnce(int32_t* once, void (APS5_VABI* init)(void)); -int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); -int APS5_VABI scePthreadKeyDelete(PthreadKey key); -void* APS5_VABI scePthreadGetspecific(PthreadKey key); -int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); -int APS5_VABI scePthreadCancel(Pthread thread); -int APS5_VABI scePthreadSetcancelstate(int state, int* old_state); -int APS5_VABI scePthreadSetcanceltype(int type, int* old_type); -void APS5_VABI scePthreadTestcancel(); -int APS5_VABI scePthreadSetprio(Pthread thread, int prio); -int APS5_VABI scePthreadGetprio(Pthread thread, int* prio); -void APS5_VABI scePthreadYield(); + +int APS5_VABI JoinThread_nid_no_patch(Pthread thread, void** retval, const KernelTimespec* abstime); + } static constexpr int GUEST_SCHED_FIFO = 1; @@ -51,11 +39,6 @@ static constexpr std::size_t GUEST_CPUSET_BYTES = 32; static constexpr int GUEST_ERANGE = 34; extern "C" { -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI scePthreadGetaffinity(Pthread thread, KernelCpumask* mask); -int APS5_VABI scePthreadSetaffinity(Pthread thread, KernelCpumask mask); -KernelCpumask APS5_VABI sceKernelGetAvailableCpumask(void); -int APS5_VABI getpid_nid_postfix(void); int APS5_VABI cpuset_getaffinity_nid_postfix(int level, int which, std::int64_t id, std::size_t size, void* mask) { constexpr int LevelWhich = 3, WhichThread = 1, WhichProcess = 2; @@ -124,33 +107,30 @@ int APS5_VABI cpuset_setaffinity_nid_postfix(int level, int which, std::int64_t return 0; } -struct GuestRtPrio { - std::uint16_t type; - std::uint16_t prio; -}; - int APS5_VABI rtprio_thread_nid_postfix(int function, int lwpid, GuestRtPrio* priority) { constexpr int Lookup = 0, Set = 1; - constexpr int GuestEsrch = 3, GuestEfault = 14, GuestEinval = 22; - constexpr std::uint16_t Normal = 3; + constexpr int GuestEsrch = 3, GuestEfault = 14; + constexpr int GuestSchedFifo = 1, GuestSchedRr = 3; + constexpr std::uint16_t Realtime = 2, Normal = 3, Fifo = 10; const auto fail = [](int error) { *__error_nid_postfix() = error; return -1; }; - if (function != Lookup && function != Set) return fail(GuestEinval); + if (function != Lookup && function != Set) return fail(GuestEsrch); if (priority == nullptr) return fail(GuestEfault); auto* self = scePthreadSelf(); - if (lwpid != 0 && static_cast(lwpid) != static_cast(self->tid)) { - if (!LiveGuestThreadExists(lwpid)) return fail(GuestEsrch); - NotImplemented_nid_no_patch("rtprio_thread: a thread other than the calling one"); - } - const int policy = self->schedPolicy.load(std::memory_order_relaxed); - if (policy == 1 || policy == 3) NotImplemented_nid_no_patch("rtprio_thread: a guest thread's real-time class"); + int policy = self->schedPolicy.load(std::memory_order_relaxed); + int schedPriority = self->priority.load(std::memory_order_relaxed); + const bool other = lwpid != 0 && static_cast(lwpid) != static_cast(self->tid); + if (other && !GetLiveGuestThreadSched_nid_no_patch(lwpid, policy, schedPriority)) return fail(GuestEsrch); + const bool realTime = policy == GuestSchedFifo || policy == GuestSchedRr; if (function == Lookup) { - priority->type = Normal; - priority->prio = 0; + priority->type = realTime ? (policy == GuestSchedFifo ? Fifo : Realtime) : Normal; + priority->prio = realTime ? static_cast(schedPriority) : 0; return 0; } + if (other) NotImplemented_nid_no_patch("rtprio_thread: a thread other than the calling one"); + if (realTime) NotImplemented_nid_no_patch("rtprio_thread: a guest thread's real-time class"); if (priority->type == Normal && priority->prio == 0) return 0; NotImplemented_nid_no_patch("rtprio_thread: a real-time priority change"); } @@ -180,6 +160,7 @@ int APS5_VABI pthread_detach_nid_postfix(Pthread thread) { void APS5_VABI pthread_exit_nid_postfix(void* value) { scePthreadExit(value); + } int APS5_VABI scePthreadGetschedparam(Pthread thread, int* policy, KernelSchedParam* param) { @@ -241,6 +222,12 @@ int APS5_VABI pthread_join_nid_postfix(Pthread thread, void** value) { return PosixThread::ToErrno(scePthreadJoin(thread, value)); } +int APS5_VABI pthread_timedjoin_np_nid_postfix(Pthread thread, void** value, const KernelTimespec* abstime) { + if (abstime == nullptr || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) + return PosixThread::GUEST_EINVAL; + return PosixThread::ToErrno(JoinThread_nid_no_patch(thread, value, abstime)); +} + namespace { struct RegisterPthreadOnLoad { RegisterPthreadOnLoad() { @@ -258,6 +245,25 @@ struct RegisterPthreadOnLoad { scePthreadExit, }; RegisterThreadForwards_nid_no_patch(&forwards); + static const PthreadSyncForwards_nid_no_patch sync{ + reinterpret_cast(scePthreadMutexattrInit), + reinterpret_cast(scePthreadMutexattrSettype), + reinterpret_cast(scePthreadMutexattrDestroy), + reinterpret_cast(scePthreadMutexInit), + reinterpret_cast(scePthreadMutexDestroy), + reinterpret_cast(scePthreadMutexLock), + reinterpret_cast(scePthreadMutexTrylock), + reinterpret_cast(scePthreadMutexUnlock), + reinterpret_cast(scePthreadCondInit), + reinterpret_cast(scePthreadCondDestroy), + reinterpret_cast(scePthreadCondWait), + reinterpret_cast(scePthreadCondSignal), + reinterpret_cast(scePthreadCondBroadcast), + reinterpret_cast(scePthreadDetach), + reinterpret_cast(scePthreadJoin), + scePthreadYield, + }; + RegisterPthreadSyncForwards_nid_no_patch(&sync); } }; const RegisterPthreadOnLoad gRegisterPthread; @@ -279,14 +285,13 @@ int APS5_VABI pthread_getname_np_nid_postfix(Pthread thread, char* name) { void APS5_VABI pthread_set_name_np_nid_postfix(Pthread thread, const char* name) { scePthreadRename(thread, name != nullptr ? name : ""); + } Pthread APS5_VABI pthread_self_nid_postfix(void) { return scePthreadSelf(); } -int APS5_VABI scePthreadGetthreadid(void); - int APS5_VABI pthread_getthreadid_np_nid_postfix(void) { return scePthreadGetthreadid(); } @@ -305,6 +310,7 @@ int APS5_VABI pthread_getcpuclockid_nid_postfix(Pthread thread, int* clockId) { int APS5_VABI sched_yield_nid_postfix(void) { scePthreadYield(); + return 0; } @@ -331,10 +337,12 @@ int APS5_VABI pthread_setschedparam_nid_postfix(Pthread thread, int policy, cons void APS5_VABI pthread_testcancel_nid_postfix(void) { scePthreadTestcancel(); + } void APS5_VABI pthread_yield_nid_postfix(void) { scePthreadYield(); + } unsigned int APS5_VABI sleep_nid_postfix(unsigned int seconds) { diff --git a/core/libs/prx/libkernel/Pthread/Posix/Tsd.cpp b/core/libs/prx/libkernel/Pthread/Posix/Tsd.cpp index d46ac1d42d..f715d153be 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Tsd.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Tsd.cpp @@ -1,16 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "Common.hpp" -extern "C" { -int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); -int APS5_VABI scePthreadKeyDelete(PthreadKey key); -void* APS5_VABI scePthreadGetspecific(PthreadKey key); -int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); -} - extern "C" { void* APS5_VABI pthread_getspecific_nid_postfix(PthreadKey key) { diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index 0cef7ba498..097d609562 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -110,6 +110,7 @@ struct PthreadAttrPrivate { int _schedpriority; int _schedpolicy; int _inheritsched; + bool _createSuspended = false; KernelCpumask _affinity = DEFAULT_THREAD_AFFINITY; bool _affinityExplicit = false; std::size_t _guardsize = 0x1000; @@ -130,6 +131,8 @@ struct PthreadPrivate { std::atomic references{2}; std::atomic inWait{false}; std::atomic pendingException{0}; + std::atomic suspended{false}; + bool createdSuspended = false; std::atomic schedPolicy{1}; void* wakeEvent = nullptr; void* stackAddress = nullptr; @@ -179,8 +182,13 @@ void SetLiveGuestThreadsAffinity(KernelCpumask mask); KernelCpumask LiveGuestThreadsAffinity(); bool GuestThreadIsLive(const PthreadPrivate* thread); std::unique_lock LockLiveGuestThread(const PthreadPrivate* thread); +extern "C" int ResumeGuestThread_nid_no_patch(PthreadPrivate* thread); +extern "C" void ResumeAllGuestThreads_nid_no_patch(); bool LiveGuestThreadExists(std::int64_t tid); bool GetLiveGuestThreadAffinity_nid_no_patch(std::int64_t tid, KernelCpumask& mask); +bool GetLiveGuestThreadSched_nid_no_patch(std::int64_t tid, int& policy, int& priority); bool SetLiveGuestThreadAffinity_nid_no_patch(std::int64_t tid, KernelCpumask mask); +extern "C" std::size_t GetDefaultStackSize_nid_no_patch(); +extern "C" bool SetDefaultStackSize_nid_no_patch(std::size_t size); #endif diff --git a/core/libs/prx/libkernel/Pthread/src/Attr.cpp b/core/libs/prx/libkernel/Pthread/src/Attr.cpp index ce6ccac93c..9b7cb2e521 100644 --- a/core/libs/prx/libkernel/Pthread/src/Attr.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Attr.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/KernelErrors.hpp" @@ -12,6 +13,18 @@ static constexpr int DETACH_JOINABLE = 0; static constexpr int DETACH_DETACHED = 1; static constexpr int SCHED_FIFO_PS5 = 1; +static std::atomic defaultStackSize{DEFAULT_STACK_SIZE}; + +std::size_t GetDefaultStackSize_nid_no_patch() { + return defaultStackSize.load(std::memory_order_relaxed); +} + +bool SetDefaultStackSize_nid_no_patch(std::size_t size) { + if (size <= MIN_STACK_SIZE) return false; + defaultStackSize.store(size, std::memory_order_relaxed); + return true; +} + #ifdef _WIN32 #include #include @@ -23,7 +36,7 @@ int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) { if (!attr) throw std::runtime_error(std::string(__func__) + ": null attr"); auto* p = new (std::nothrow) PthreadAttrPrivate{}; if (!p) return SCE_KERNEL_ERROR_ENOMEM; - p->_stacksize = DEFAULT_STACK_SIZE; + p->_stacksize = GetDefaultStackSize_nid_no_patch(); p->_detachstate = DETACH_JOINABLE; p->_schedpriority = 700; p->_schedpolicy = SCHED_FIFO_PS5; @@ -85,6 +98,7 @@ int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr) { (*attr)->_schedpriority = thread->priority.load(std::memory_order_relaxed); (*attr)->_schedpolicy = thread->schedPolicy.load(std::memory_order_relaxed); (*attr)->_inheritsched = 4; + (*attr)->_createSuspended = thread->createdSuspended; (*attr)->_affinity = thread->affinity.load(std::memory_order_relaxed); (*attr)->_affinityExplicit = true; (*attr)->_scope = thread->scope; diff --git a/core/libs/prx/libkernel/Pthread/src/Cond.cpp b/core/libs/prx/libkernel/Pthread/src/Cond.cpp index 609acd98aa..e763a5aa8a 100644 --- a/core/libs/prx/libkernel/Pthread/src/Cond.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Cond.cpp @@ -19,10 +19,13 @@ namespace { constexpr int sceTimedOut = static_cast(0x8002003cu); -std::mutex condInitializationMutex; +std::mutex& InitializationMutex() { + static std::mutex mutex; + return mutex; +} PthreadCond destroyedCond() { - return reinterpret_cast(std::uintptr_t{2}); + return reinterpret_cast(std::uintptr_t{1}); } PthreadMutex destroyedMutex() { @@ -36,7 +39,7 @@ PthreadMutex adaptiveInitializer() { std::shared_ptr resolveCond(PthreadCond* cond, int* clockId = nullptr) { if (!cond) throw std::invalid_argument("Condition variable pointer is null"); - std::lock_guard lock(condInitializationMutex); + std::lock_guard lock(InitializationMutex()); std::atomic_ref slot(*cond); auto current = slot.load(std::memory_order_acquire); if (current == destroyedCond()) @@ -150,7 +153,9 @@ int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr) { } int APS5_VABI scePthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clockId) { + constexpr KernelClockid realtime = 0, virtualTime = 1, profiling = 2, monotonic = 4; if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL; + if (clockId != realtime && clockId != virtualTime && clockId != profiling && clockId != monotonic) return SCE_KERNEL_ERROR_EINVAL; (*attr)->_clockid = static_cast(clockId); return 0; } @@ -161,7 +166,7 @@ int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, auto replacement = std::make_unique(); if (attr && *attr) replacement->_clockid = (*attr)->_clockid; - std::lock_guard lock(condInitializationMutex); + std::lock_guard lock(InitializationMutex()); std::atomic_ref(*cond).store(replacement.release(), std::memory_order_release); return 0; } @@ -169,9 +174,11 @@ int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, int APS5_VABI scePthreadCondDestroy(PthreadCond* cond) { if (!cond) throw std::invalid_argument("Condition variable pointer is null"); - std::lock_guard lock(condInitializationMutex); + std::lock_guard lock(InitializationMutex()); if (*cond == destroyedCond()) return SCE_KERNEL_ERROR_EINVAL; + if (*cond == nullptr) + return 0; delete *cond; std::atomic_ref(*cond).store(destroyedCond(), std::memory_order_release); return 0; diff --git a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp index dccc979442..84eee73602 100644 --- a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/Pthread/include/Mutex.hpp" #include "../include/Pthread.hpp" #include "../include/Mutex.hpp" #include "prx/libkernel/KernelErrors.hpp" @@ -16,7 +17,10 @@ constexpr int sceNotPermitted = static_cast(0x80020001u); constexpr int sceDeadlock = static_cast(0x8002000bu); constexpr int sceBusy = static_cast(0x80020010u); constexpr int sceTimedOut = static_cast(0x8002003cu); -std::mutex initializationMutex; +std::mutex& InitializationMutex() { + static std::mutex mutex; + return mutex; +} PthreadMutex destroyedMutex() { return reinterpret_cast(std::uintptr_t{2}); @@ -36,7 +40,7 @@ PthreadMutex resolveMutex(PthreadMutex* mutex, bool initialize) { std::atomic_ref slot(*mutex); if (const auto current = slot.load(std::memory_order_acquire); isInitializedMutex(current)) return current; - std::lock_guard lock(initializationMutex); + std::lock_guard lock(InitializationMutex()); const auto current = slot.load(std::memory_order_acquire); if (current == destroyedMutex()) return nullptr; @@ -146,23 +150,13 @@ int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type) { return 0; } -int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol) { - if (!attr || !*attr) - return SCE_KERNEL_ERROR_EINVAL; - constexpr int PrioNone = 0; - constexpr int PrioProtect = 2; - if (protocol < PrioNone || protocol > PrioProtect) - return SCE_KERNEL_ERROR_EINVAL; - return 0; -} - int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) { if (!mutex) throw std::invalid_argument("Mutex pointer is null"); auto replacement = std::make_unique(); if (attr && *attr) replacement->SetType((*attr)->type); - std::lock_guard lock(initializationMutex); + std::lock_guard lock(InitializationMutex()); std::atomic_ref(*mutex).store(replacement.release(), std::memory_order_release); return 0; } @@ -170,7 +164,7 @@ int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* a int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) { if (!mutex) throw std::invalid_argument("Mutex pointer is null"); - std::lock_guard lock(initializationMutex); + std::lock_guard lock(InitializationMutex()); if (*mutex == destroyedMutex()) return SCE_KERNEL_ERROR_EINVAL; if (*mutex == adaptiveInitializer()) { @@ -198,10 +192,12 @@ int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) { } int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) { - auto* current = resolveMutex(mutex, false); - if (!current) + if (!mutex) + throw std::invalid_argument("Mutex pointer is null"); + auto* current = std::atomic_ref(*mutex).load(std::memory_order_acquire); + if (current == destroyedMutex()) return SCE_KERNEL_ERROR_EINVAL; - if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id()) + if (!isInitializedMutex(current) || current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id()) return sceNotPermitted; bool released = true; if (current->_type == MutexType::Recursive) { @@ -224,6 +220,10 @@ int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) { int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec) { const auto deadline = TimedWait::DeadlineNanos(usec); auto* current = resolveMutex(mutex, true); + if (current && current->_type != MutexType::Recursive && current->_owner.load(std::memory_order_acquire) == std::this_thread::get_id()) { + TimedWait::SleepUntil(deadline); + return sceTimedOut; + } return acquireMutex(current, [=](auto& native) { return TimedWait::AcquireUntil(current, deadline, [&] { return native.try_lock(); }, [&](std::uint64_t micros) { return native.try_lock_for(std::chrono::microseconds(micros)); }); }, sceTimedOut, false); diff --git a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp index 35a043a8e9..8d06b7e9ea 100644 --- a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/Pthread/include/Rwlock.hpp" #include "../include/Pthread.hpp" #include "../include/Rwlock.hpp" #include "prx/libc/include/General.hpp" @@ -14,6 +15,8 @@ static constexpr int SCE_KERNEL_ERROR_EDEADLK = 0x8002000B; static constexpr int SCE_KERNEL_ERROR_EBUSY = 0x80020010; static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x8002003C; static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; +static constexpr int FIRST_STORED_RWLOCK_TYPE = 1; +static constexpr int LAST_STORED_RWLOCK_TYPE = 2; static PthreadRwlockPrivate* const DestroyedRwlock = reinterpret_cast(std::uintptr_t{1}); @@ -102,8 +105,11 @@ int APS5_VABI scePthreadRwlockTryrdlock(PthreadRwlock* rwlock) { int APS5_VABI scePthreadRwlockTimedrdlock(PthreadRwlock* rwlock, KernelUseconds usec) { auto* lock = RequireRwlock(rwlock, __func__); if (!lock) return SCE_KERNEL_ERROR_EINVAL; - if (OwnsWrite(lock)) return SCE_KERNEL_ERROR_EDEADLK; const std::uint64_t deadline = TimedWait::DeadlineNanos(usec); + if (OwnsWrite(lock)) { + TimedWait::SleepUntil(deadline); + return SCE_KERNEL_ERROR_ETIMEDOUT; + } std::unique_lock state(lock->_state); if (!lock->_changed.WaitUntil(state, deadline, [&] { return ReaderMayEnter(lock); })) return SCE_KERNEL_ERROR_ETIMEDOUT; return EnterRead(lock); @@ -131,8 +137,11 @@ int APS5_VABI scePthreadRwlockTrywrlock(PthreadRwlock* rwlock) { int APS5_VABI scePthreadRwlockTimedwrlock(PthreadRwlock* rwlock, KernelUseconds usec) { auto* lock = RequireRwlock(rwlock, __func__); if (!lock) return SCE_KERNEL_ERROR_EINVAL; - if (OwnsWrite(lock)) return SCE_KERNEL_ERROR_EDEADLK; const std::uint64_t deadline = TimedWait::DeadlineNanos(usec); + if (OwnsWrite(lock)) { + TimedWait::SleepUntil(deadline); + return SCE_KERNEL_ERROR_ETIMEDOUT; + } std::unique_lock state(lock->_state); ++lock->_waitingWriters; const bool acquired = lock->_changed.WaitUntil(state, deadline, [&] { return WriterMayEnter(lock); }); @@ -174,7 +183,7 @@ int APS5_VABI scePthreadRwlockattrInit(PthreadRwlockattr* attr) { } int APS5_VABI scePthreadRwlockattrSettype(PthreadRwlockattr* attr, int type) { - if (!attr || !*attr) throw std::runtime_error("scePthreadRwlockattrSettype: null attr"); + if (!attr || !*attr || type < FIRST_STORED_RWLOCK_TYPE || type > LAST_STORED_RWLOCK_TYPE) return SCE_KERNEL_ERROR_EINVAL; (*attr)->type = type; return SCE_OK; } diff --git a/core/libs/prx/libkernel/Pthread/src/Sem.cpp b/core/libs/prx/libkernel/Pthread/src/Sem.cpp index ea9006380e..5d6c44715d 100644 --- a/core/libs/prx/libkernel/Pthread/src/Sem.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Sem.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "../include/Pthread.hpp" #include "../include/Cancel.hpp" #include "prx/libc/include/General.hpp" diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 8bc51e38db..71680fca25 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -1,7 +1,11 @@ +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" +#include "prx/libkernel/include/Api.hpp" #include "../include/Pthread.hpp" #include "../include/ThreadLifecycle.hpp" #include "../include/WindowsThreadLocal.hpp" #include "../include/Cancel.hpp" +#include "prx/libkernel/Time/include/Time.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/AsmMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" @@ -33,9 +37,23 @@ #include #endif +#ifdef __linux__ +#include +#else +static const auto initialThreadId = std::this_thread::get_id(); +#endif + +extern "C" bool IsInitialPthread_nid_no_patch() { +#ifdef __linux__ + return ::syscall(SYS_gettid) == ::getpid(); +#else + return std::this_thread::get_id() == initialThreadId; +#endif +} + static constexpr int SCE_OK = 0; +static constexpr std::size_t GUEST_THREAD_NAME_CAPACITY = 32; -static constexpr std::size_t DEFAULT_STACK_SIZE = 1u << 20; static constexpr std::size_t HOST_STACK_MARGIN = 1u << 20; static constexpr int DETACH_DETACHED = 1; static constexpr std::size_t THREAD_NAME_CAPACITY = 32; @@ -75,9 +93,9 @@ static GuestThreadState& CurrentGuestState() { return state; #endif } -static std::mutex stackLock; -static std::map> liveStacks; -static std::map protectedStackPages; +static std::mutex& stackLock = *new std::mutex(); +static auto& liveStacks = *new std::map>(); +static auto& protectedStackPages = *new std::map(); #if defined(__linux__) struct GuestStackMapping { std::uintptr_t mappingEnd; @@ -85,7 +103,7 @@ struct GuestStackMapping { std::uintptr_t stackEnd; }; static std::mutex guestStackMappingsLock; -static std::map guestStackMappings; +static auto& guestStackMappings = *new std::map(); static void RegisterGuestStackMapping(PthreadPrivate* thread) { const auto mappingStart = reinterpret_cast(thread->guestStack); @@ -109,14 +127,103 @@ struct RetiredStack { int hostTid; }; +struct FinishedStack { + void* address; + std::size_t bytes; +}; + static std::mutex retiredStacksLock; static std::vector retiredStacks; +static std::vector spareStacks; +static std::size_t spareStackBytes = 0; + +static std::size_t SpareStackLimit() { + static const auto limit = [] { + const auto pages = sysconf(_SC_PHYS_PAGES); + const auto pageSize = sysconf(_SC_PAGESIZE); + if (pages <= 0 || pageSize <= 0) return std::size_t{4u << 20}; + const auto total = static_cast(pages) * static_cast(pageSize); + return std::clamp(static_cast(total / 1024), 4u << 20, 64u << 20); + }(); + return limit; +} + +static void ReleaseFinishedStack(const FinishedStack& stack) { + UnregisterGuestStackMapping(stack.address); + DoMunmap(stack.address, stack.bytes); +} + +static bool DiscardFinishedStackPages(const FinishedStack& stack) { + auto* usable = static_cast(stack.address) + GUEST_STACK_GUARD; + const auto bytes = stack.bytes - GUEST_STACK_GUARD; + return madvise(usable, bytes, MADV_DONTNEED) == 0; +} + +static void CollectFinishedStack(void* address, std::size_t bytes) { + UnregisterGuestStackMapping(address); + const FinishedStack finished{address, bytes}; + if (!DiscardFinishedStackPages(finished)) { + ReleaseFinishedStack(finished); + return; + } + std::vector release; + { + std::lock_guard lock(retiredStacksLock); + spareStacks.push_back({address, bytes}); + spareStackBytes += bytes; + const auto limit = SpareStackLimit(); + while (spareStackBytes > limit && !spareStacks.empty()) { + spareStackBytes -= spareStacks.front().bytes; + release.push_back(spareStacks.front()); + spareStacks.erase(spareStacks.begin()); + } + } + if (!release.empty()) { + static std::atomic_flag logged = ATOMIC_FLAG_INIT; + if (!logged.test_and_set(std::memory_order_relaxed)) + std::fprintf(stderr, "scePthread: spare stack cache reached its host-memory limit; releasing cached mappings\n"); + for (const auto& stack : release) ReleaseFinishedStack(stack); + } +} + +static bool TakeSpareStack(std::size_t bytes, FinishedStack* stack) { + std::lock_guard lock(retiredStacksLock); + const auto found = std::find_if(spareStacks.begin(), spareStacks.end(), [bytes](const FinishedStack& candidate) { return candidate.bytes == bytes; }); + if (found == spareStacks.end()) return false; + *stack = *found; + spareStackBytes -= found->bytes; + spareStacks.erase(found); + return true; +} + +static void ReleaseSpareStacks() { + std::vector release; + { + std::lock_guard lock(retiredStacksLock); + release.swap(spareStacks); + spareStackBytes = 0; + } + for (const auto& stack : release) ReleaseFinishedStack(stack); +} static void AllocateGuestStack(PthreadPrivate* thread, std::size_t usableBytes) { const std::size_t bytes = (usableBytes + PS5_PAGE_SIZE - 1) / PS5_PAGE_SIZE * PS5_PAGE_SIZE + GUEST_STACK_GUARD; + FinishedStack spare{}; + if (TakeSpareStack(bytes, &spare)) { + if (DoMprotect(spare.address, spare.bytes, 3) == 0 && DoMprotect(spare.address, GUEST_STACK_GUARD, 0) == 0) { + thread->guestStack = spare.address; + thread->guestStackBytes = spare.bytes; + return; + } + UnregisterGuestStackMapping(spare.address); + DoMunmap(spare.address, spare.bytes); + } void* stack = nullptr; - if (DoMapAnon(&stack, bytes, 3, 0) != 0) - throw std::runtime_error("scePthreadCreate: cannot map the guest thread stack"); + if (DoMapAnon(&stack, bytes, 3, 0) != 0) { + ReleaseSpareStacks(); + if (DoMapAnon(&stack, bytes, 3, 0) != 0) + throw std::runtime_error("scePthreadCreate: cannot map the guest thread stack"); + } if (DoMprotect(stack, GUEST_STACK_GUARD, 0) != 0) { DoMunmap(stack, bytes); throw std::runtime_error("scePthreadCreate: cannot protect the guest thread stack guard"); @@ -127,9 +234,7 @@ static void AllocateGuestStack(PthreadPrivate* thread, std::size_t usableBytes) static void FreeGuestStack(PthreadPrivate* thread) { if (thread->guestStack == nullptr) return; - void* const mapping = thread->guestStack; - UnregisterGuestStackMapping(mapping); - DoMunmap(thread->guestStack, thread->guestStackBytes); + CollectFinishedStack(thread->guestStack, thread->guestStackBytes); thread->guestStack = nullptr; thread->guestStackBytes = 0; } @@ -154,10 +259,7 @@ static void ReapRetiredStacks() { } } } - for (const auto& stack : ready) { - UnregisterGuestStackMapping(stack.address); - DoMunmap(stack.address, stack.bytes); - } + for (const auto& stack : ready) CollectFinishedStack(stack.address, stack.bytes); } static void RetireGuestStack(PthreadPrivate* thread) { @@ -453,6 +555,26 @@ bool GuestThreadIsLive(const PthreadPrivate* thread) { return LockLiveGuestThread(thread).owns_lock(); } +extern "C" int ResumeGuestThread_nid_no_patch(PthreadPrivate* thread) { + constexpr int GuestEsrch = 3; + const auto live = LockLiveGuestThread(thread); + if (!live.owns_lock()) return GuestEsrch; + thread->suspended.store(false, std::memory_order_release); + thread->suspended.notify_all(); + return 0; +} + +extern "C" void ResumeAllGuestThreads_nid_no_patch() { + const auto* self = CurrentGuestState().current; + auto& registry = CpuClocks(); + std::lock_guard lock(registry.lock); + for (const auto& [id, thread] : registry.threads) { + if (thread == self) continue; + thread->suspended.store(false, std::memory_order_release); + thread->suspended.notify_all(); + } +} + bool GetLiveGuestThreadAffinity_nid_no_patch(std::int64_t tid, KernelCpumask& mask) { auto& registry = CpuClocks(); std::lock_guard lock(registry.lock); @@ -464,6 +586,18 @@ bool GetLiveGuestThreadAffinity_nid_no_patch(std::int64_t tid, KernelCpumask& ma return false; } +bool GetLiveGuestThreadSched_nid_no_patch(std::int64_t tid, int& policy, int& priority) { + auto& registry = CpuClocks(); + std::lock_guard lock(registry.lock); + for (const auto& [id, thread] : registry.threads) { + if (thread->tid != tid || thread->_finished.load(std::memory_order_acquire)) continue; + policy = thread->schedPolicy.load(std::memory_order_relaxed); + priority = thread->priority.load(std::memory_order_relaxed); + return true; + } + return false; +} + bool SetLiveGuestThreadAffinity_nid_no_patch(std::int64_t tid, KernelCpumask mask) { auto& registry = CpuClocks(); std::lock_guard lock(registry.lock); @@ -661,6 +795,7 @@ static void RunThread(std::unique_ptr args) { const sigset_t signalMask = args->signalMask; #endif args.reset(); + self->suspended.wait(true, std::memory_order_acquire); #ifndef _WIN32 pthread_sigmask(SIG_SETMASK, &signalMask, nullptr); #endif @@ -820,12 +955,13 @@ extern "C" { int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name) { if (!thread || !entry) throw std::runtime_error("scePthreadCreate: null arg"); + if (name && std::strlen(name) >= GUEST_THREAD_NAME_CAPACITY) return SCE_KERNEL_ERROR_ENAMETOOLONG; if (attr && !*attr) attr = nullptr; auto p = std::make_unique(attr ? *attr : nullptr); bool detached = false; if (attr && *attr) detached = ((*attr)->_detachstate == DETACH_DETACHED); p->_detached = detached; - p->stackSize = attr ? (*attr)->_stacksize : DEFAULT_STACK_SIZE; + p->stackSize = attr ? (*attr)->_stacksize : GetDefaultStackSize_nid_no_patch(); if (attr) { if ((*attr)->_inheritsched != GUEST_INHERIT_SCHED) p->scope = (*attr)->_scope; @@ -835,6 +971,8 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread p->scope = CurrentGuestState().current->scope; } if (name) p->name = name; + p->createdSuspended = attr && (*attr)->_createSuspended; + p->suspended.store(p->createdSuspended, std::memory_order_relaxed); std::promise start; auto args = std::make_unique(ThreadArgs{entry, arg, p.get()}); #ifdef _WIN32 @@ -927,7 +1065,7 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread return SCE_OK; } -int APS5_VABI scePthreadJoin(Pthread thread, void** retval) { +int APS5_VABI JoinThread_nid_no_patch(Pthread thread, void** retval, const KernelTimespec* abstime) { if (!thread) return SCE_KERNEL_ERROR_EINVAL; if (thread == CurrentGuestState().current) return SCE_KERNEL_ERROR_EDEADLK; if (thread->_detached) return SCE_KERNEL_ERROR_EINVAL; @@ -939,7 +1077,22 @@ int APS5_VABI scePthreadJoin(Pthread thread, void** retval) { if (thread->_joining) return SCE_KERNEL_ERROR_EOPNOTSUPP; thread->_joining = true; while (!thread->_finished.load()) { - ThreadCancel::Wait(thread->_join_cv, lock, std::nullopt); + std::optional deadline; + if (abstime != nullptr) { + KernelTimespec now{}; + if (clock_gettime_nid_postfix(0, &now) != 0) { + thread->_joining = false; + throw std::runtime_error("Joining guest thread: realtime clock unavailable"); + } + if (abstime->tv_sec < now.tv_sec || (abstime->tv_sec == now.tv_sec && abstime->tv_nsec <= now.tv_nsec)) { + thread->_joining = false; + return SCE_KERNEL_ERROR_ETIMEDOUT; + } + const auto seconds = static_cast(abstime->tv_sec) - static_cast(now.tv_sec); + const auto nanos = seconds > 1 ? std::int64_t{1000000000} : static_cast(seconds) * 1000000000 + abstime->tv_nsec - now.tv_nsec; + deadline = TimedWait::DeadlineNanos(static_cast((nanos + 999) / 1000)); + } + ThreadCancel::Wait(thread->_join_cv, lock, deadline); if (ThreadCancel::Requested()) { thread->_joining = false; lock.unlock(); @@ -965,11 +1118,16 @@ int APS5_VABI scePthreadJoin(Pthread thread, void** retval) { return SCE_OK; } +int APS5_VABI scePthreadJoin(Pthread thread, void** retval) { + return JoinThread_nid_no_patch(thread, retval, nullptr); +} + int APS5_VABI scePthreadDetach(Pthread thread) { if (!thread) return SCE_KERNEL_ERROR_EINVAL; { std::lock_guard lock(thread->_join_mtx); - if (thread->_detached || thread->_joining || thread->_joined.load(std::memory_order_acquire)) return SCE_KERNEL_ERROR_EINVAL; + if (thread->_joined.load(std::memory_order_acquire)) return SCE_KERNEL_ERROR_ESRCH; + if (thread->_detached || thread->_joining) return SCE_KERNEL_ERROR_EINVAL; thread->_detached = true; } #ifdef _WIN32 @@ -1067,6 +1225,7 @@ int APS5_VABI scePthreadCancel(Pthread thread) { thread->cancelWait->NotifyAll(); } scePthreadYield(); + } } @@ -1094,6 +1253,7 @@ void APS5_VABI pthread_cleanup_push_nid_postfix(void (APS5_VABI* routine)(void*) void APS5_VABI pthread_cleanup_pop_nid_postfix(int execute) { __pthread_cleanup_pop_imp_nid_postfix(execute); + } int APS5_VABI scePthreadEqual(Pthread thread1, Pthread thread2) { @@ -1193,6 +1353,7 @@ int APS5_VABI scePthreadOnce(int32_t* once, void (APS5_VABI* init)(void)) { CleanupHandler reset{}; void* const mark = ThreadCancel::CleanupMark(); __pthread_cleanup_push_imp_nid_postfix(ResetOnce, once, &reset); + try { init(); } catch (...) { diff --git a/core/libs/prx/libkernel/Pthread/src/Tsd.cpp b/core/libs/prx/libkernel/Pthread/src/Tsd.cpp index 03c706c202..2cf90e324a 100644 --- a/core/libs/prx/libkernel/Pthread/src/Tsd.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Tsd.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "../include/Pthread.hpp" #include "../include/ThreadLifecycle.hpp" #include "../include/WindowsThreadLocal.hpp" diff --git a/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp b/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp index 725f297e67..a1b4e1bd60 100644 --- a/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp +++ b/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp @@ -21,9 +21,12 @@ extern "C" { int APS5_VABI sceKernelCreateSema(KernelSema* sem, const char* name, uint32_t attr, int init, int max, void* opt) { (void)opt; - if (sem == nullptr || name == nullptr || attr > 2 || init < 0 || max <= 0 || init > max) { + if (sem == nullptr || attr > 2 || max < 0) { APS5_INVALID_ARG_EX; } + if (name == nullptr || init < 0 || max == 0 || init > max) { + return SCE_KERNEL_ERROR_EINVAL; + } *sem = new KernelSemaPrivate(init, max, std::string(name), attr == 1); if (TraceSync()) std::fprintf(stderr, "[sema] create %p '%s' attr=0x%x init=%d max=%d\n", static_cast(*sem), name, attr, init, max); @@ -31,8 +34,11 @@ int APS5_VABI sceKernelCreateSema(KernelSema* sem, const char* name, uint32_t at } int APS5_VABI sceKernelPollSema(KernelSema sem, int need) { - if (sem == nullptr || need <= 0) { - APS5_INVALID_ARG_EX; + if (sem == nullptr) { + return SCE_KERNEL_ERROR_ESRCH; + } + if (need <= 0 || need > sem->maxCount) { + return SCE_KERNEL_ERROR_EINVAL; } std::lock_guard lock(sem->mutex); @@ -44,9 +50,15 @@ int APS5_VABI sceKernelPollSema(KernelSema sem, int need) { } int APS5_VABI sceKernelSignalSema(KernelSema sem, int count) { - if (sem == nullptr || count < 0) { + if (sem == nullptr) { + return SCE_KERNEL_ERROR_ESRCH; + } + if (count < 0) { APS5_INVALID_ARG_EX; } + if (count == 0) { + return SCE_KERNEL_ERROR_EINVAL; + } std::lock_guard lock(sem->mutex); if (count > sem->maxCount - sem->tokenCount) { @@ -60,7 +72,10 @@ int APS5_VABI sceKernelSignalSema(KernelSema sem, int count) { } int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time) { - if (sem == nullptr || need <= 0) { + if (sem == nullptr) { + return SCE_KERNEL_ERROR_ESRCH; + } + if (need <= 0) { APS5_INVALID_ARG_EX; } @@ -129,7 +144,7 @@ int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time) int APS5_VABI sceKernelCancelSema(KernelSema sem, int count, int* threads) { if (sem == nullptr) { - APS5_INVALID_ARG_EX; + return SCE_KERNEL_ERROR_ESRCH; } std::lock_guard lock(sem->mutex); @@ -148,7 +163,7 @@ int APS5_VABI sceKernelCancelSema(KernelSema sem, int count, int* threads) { int APS5_VABI sceKernelDeleteSema(KernelSema sem) { if (sem == nullptr) { - APS5_INVALID_ARG_EX; + return SCE_KERNEL_ERROR_ESRCH; } std::unique_lock lock(sem->mutex); diff --git a/core/libs/prx/libkernel/Socket/include/SocketOptions.hpp b/core/libs/prx/libkernel/Socket/include/SocketOptions.hpp index 56bbe00efe..44990b4654 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketOptions.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketOptions.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include namespace GuestSockets { @@ -9,13 +10,32 @@ struct SocketOptionState { bool noSignal = false; std::int64_t sendTimeout = 0; std::int64_t receiveTimeout = 0; + int sendBuffer = 0; + int receiveBuffer = 0; + int sendLowWater = 0; + int receiveLowWater = 0; + int ipTimeToLive = 64; + int ipTypeOfService = 0; + int multicastTimeToLive = 1; + bool multicastLoop = true; + int tcpMaximumSegment = 536; + unsigned int tcpKeepIdle = 0; + unsigned int tcpKeepInterval = 0; + unsigned int tcpKeepCount = 0; + int keptFlags = 0; + int tcpFlags = 0; + int ipReceiveOptions = 0; + std::uint32_t multicastInterface = 0; + std::vector memberships; }; bool NativeListening(std::uintptr_t native, bool& listening); -int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int option, const void* value, std::uint32_t length); -int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int type, int option, void* value, std::uint32_t* length); -int SetTcpLevelOption(std::uintptr_t native, bool tcp, int option, const void* value, std::uint32_t length); -int GetTcpLevelOption(std::uintptr_t native, bool tcp, int option, void* value, std::uint32_t* length); +int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int family, int type, int option, const void* value, std::uint32_t length); +int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int family, int type, int option, void* value, std::uint32_t* length); +int SetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, int option, const void* value, std::uint32_t length); +int GetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, int option, void* value, std::uint32_t* length); +int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, bool datagram, int option, const void* value, std::uint32_t length); +int GetIpLevelOption(SocketOptionState& state, bool ipv4, bool datagram, int option, void* value, std::uint32_t* length); int SetIpv6LevelOption(std::uintptr_t native, bool ipv6, int option, const void* value, std::uint32_t length); int GetIpv6LevelOption(std::uintptr_t native, bool ipv6, int option, void* value, std::uint32_t* length); diff --git a/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp b/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp index d59cfc4304..c5343daede 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp @@ -41,3 +41,4 @@ std::int64_t Writev(int descriptor, const Iovec* iov, int iovcnt); } extern "C" bool GuestSocketIsOpen_nid_no_patch(int descriptor); +extern "C" bool GuestSocketReadGuest_nid_no_patch(void* destination, const void* source, std::size_t bytes); diff --git a/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp b/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp index bbcb17e637..5ffab9d013 100644 --- a/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp +++ b/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp @@ -1,11 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { constexpr int kGuestInet = 2; constexpr int kGuestInet6 = 28; diff --git a/core/libs/prx/libkernel/Socket/src/Pselect.cpp b/core/libs/prx/libkernel/Socket/src/Pselect.cpp new file mode 100644 index 0000000000..f30cde4a91 --- /dev/null +++ b/core/libs/prx/libkernel/Socket/src/Pselect.cpp @@ -0,0 +1,70 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace { + +constexpr int SetMask = 3; +constexpr int Eintr = 4; +constexpr int DescriptorLimit = 1024; + +struct Timeval { + std::int64_t seconds; + std::int64_t microseconds; +}; + +using SavedSet = std::array; + +void Save(SavedSet& saved, const void* set, std::size_t bytes) { + if (set != nullptr) std::memcpy(saved.data(), set, bytes); +} + +void Restore(void* set, const SavedSet& saved, std::size_t bytes) { + if (set != nullptr) std::memcpy(set, saved.data(), bytes); +} + +int ScanAfterCaughtSignal(int nfds, void* readfds, void* writefds, void* exceptfds, const Timeval* wait) { + const bool validWait = wait == nullptr || (wait->seconds >= 0 && wait->microseconds >= 0 && wait->microseconds < 1000000); + if (nfds < 0 || nfds > DescriptorLimit || !validWait) return select_nid_postfix(nfds, readfds, writefds, exceptfds, wait); + const std::size_t bytes = static_cast((nfds + 63) / 64) * sizeof(std::uint64_t); + SavedSet read{}, write{}, except{}; + Save(read, readfds, bytes); + Save(write, writefds, bytes); + Save(except, exceptfds, bytes); + const Timeval zero{}; + const int result = select_nid_postfix(nfds, readfds, writefds, exceptfds, &zero); + if (result != 0 || (wait != nullptr && wait->seconds == 0 && wait->microseconds == 0)) return result; + Restore(readfds, read, bytes); + Restore(writefds, write, bytes); + Restore(exceptfds, except, bytes); + *__error_nid_postfix() = Eintr; + return -1; +} + +} + +extern "C" { + +int APS5_VABI pselect_nid_postfix(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout, const void* sigmask) { + Timeval limit{}; + const Timeval* wait = nullptr; + if (timeout != nullptr) { + const auto* spec = static_cast(timeout); + limit = {spec[0], spec[1] / 1000}; + wait = &limit; + } + if (sigmask == nullptr) return select_nid_postfix(nfds, readfds, writefds, exceptfds, wait); + GuestSignalSet previous{}; + const bool caught = SetSignalMaskCatching_nid_no_patch(static_cast(sigmask), &previous) != 0; + const int result = caught ? ScanAfterCaughtSignal(nfds, readfds, writefds, exceptfds, wait) + : select_nid_postfix(nfds, readfds, writefds, exceptfds, wait); + const int error = *__error_nid_postfix(); + sigprocmask_nid_postfix(SetMask, &previous, nullptr); + *__error_nid_postfix() = error; + return result; +} + +} diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index 6cc428f617..399e530a79 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -49,6 +51,14 @@ void ClearDescriptor(void* set, int descriptor) { if (set != nullptr) static_cast(set)[descriptor / 64] &= ~(std::uint64_t{1} << (descriptor % 64)); } +void ClearDescriptorsFrom(int nfds, void* readfds, void* writefds, void* exceptfds) { + if (nfds % 64 == 0) return; + const std::uint64_t kept = (std::uint64_t{1} << (nfds % 64)) - 1; + for (void* set : {readfds, writefds, exceptfds}) { + if (set != nullptr) static_cast(set)[nfds / 64] &= kept; + } +} + struct PollGroup { KernelSocketPoll::Poller poll; std::vector entries; @@ -131,9 +141,6 @@ int HostPoll(KernelSocketPoll::Entry* descriptors, int count, int timeoutMillise extern "C" { -const char* APS5_VABI __inet_ntop_nid_postfix(int family, const void* source, char* destination, std::uint32_t capacity); -int APS5_VABI __inet_pton_nid_postfix(int family, const char* text, void* destination); - const char* APS5_VABI inet_ntop_nid_postfix(int af, const void* src, char* dst, uint32_t size) { return __inet_ntop_nid_postfix(af, src, dst, size); } @@ -142,8 +149,6 @@ int APS5_VABI inet_pton_nid_postfix(int af, const char* src, void* dst) { return __inet_pton_nid_postfix(af, src, dst); } -int* APS5_VABI __error_nid_postfix(); - void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller) { g_socketPoller.store(poller); } @@ -210,8 +215,8 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* kqueues.slots.push_back(requested.size()); requested.push_back({descriptor, events, 0}); } else if (File::IsRandomDevice(descriptor)) devices.push_back({descriptor, events, static_cast((events & KernelSocketPoll::Readable) != 0 ? KernelSocketPoll::Readable : events)}); - else if (descriptor <= 2) devices.push_back({descriptor, events, static_cast(descriptor == 0 ? 0 : events & KernelSocketPoll::Writable)}); else if (File::IsVnode(descriptor)) devices.push_back({descriptor, events, static_cast(events & (KernelSocketPoll::Readable | KernelSocketPoll::Writable))}); + else if (descriptor <= 2) devices.push_back({descriptor, events, static_cast(descriptor == 0 ? 0 : events & KernelSocketPoll::Writable)}); else { net.entries.push_back({descriptor, events, 0}); net.slots.push_back(requested.size()); @@ -314,6 +319,7 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* if ((entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) == 0) ClearDescriptor(writefds, entry.descriptor); if ((entry.revents & KernelSocketPoll::Urgent) == 0) ClearDescriptor(exceptfds, entry.descriptor); } + ClearDescriptorsFrom(nfds, readfds, writefds, exceptfds); return ready; } if (limit != nullptr && std::chrono::steady_clock::now() >= deadline) { @@ -322,6 +328,7 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* ClearDescriptor(writefds, entry.descriptor); ClearDescriptor(exceptfds, entry.descriptor); } + ClearDescriptorsFrom(nfds, readfds, writefds, exceptfds); return 0; } } diff --git a/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp b/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp index 37fce2af2e..ff0fb47fb7 100644 --- a/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp +++ b/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp @@ -16,6 +16,7 @@ #include "prx/libkernel/Socket/include/SocketOptions.hpp" #include #include +#include namespace GuestSockets { namespace { @@ -30,18 +31,54 @@ constexpr int GuestOobInline = 0x100; constexpr int GuestNoSigpipe = 0x800; constexpr int GuestSendBuffer = 0x1001; constexpr int GuestReceiveBuffer = 0x1002; +constexpr int GuestSendLowWater = 0x1003; +constexpr int GuestReceiveLowWater = 0x1004; constexpr int GuestSendTimeout = 0x1005; constexpr int GuestReceiveTimeout = 0x1006; constexpr int GuestError = 0x1007; constexpr int GuestType = 0x1008; constexpr int GuestNonBlocking = 0x1200; +constexpr int GuestNonBlockingState = 0x100; +constexpr int GuestUnix = 1; constexpr int GuestTcpNoDelay = 1; +constexpr int GuestReusePort = 0x200; +constexpr int KeptFlags = 0x1 | GuestReuseAddress | 0x40 | GuestReusePort | 0x400 | 0x2000 | 0x10000; +constexpr int WriteOnlyFlags = 0x4000 | 0x8000; constexpr int GuestTcpNoDelayFlag = 4; +constexpr int GuestTcpMaximumSegment = 2; +constexpr int GuestTcpKeepIdle = 0x100; +constexpr int GuestTcpKeepInterval = 0x200; +constexpr int GuestTcpKeepCount = 0x400; +constexpr int GuestTcpNoPush = 4; +constexpr int GuestTcpNoOptions = 8; +constexpr int GuestTcpOption80 = 0x80; +constexpr int MinimumSegment = 216; +constexpr unsigned int TicksPerSecond = 1000; +constexpr unsigned int DefaultKeepIdle = 7200; +constexpr unsigned int DefaultKeepInterval = 75; +constexpr unsigned int DefaultKeepCount = 8; constexpr int GuestIpv6Only = 27; +constexpr int GuestIpTypeOfService = 3; +constexpr int GuestIpTimeToLive = 4; +constexpr int GuestIpMulticastTimeToLive = 10; +constexpr int GuestIpMulticastLoop = 11; +constexpr int GuestIpMulticastInterface = 9; +constexpr int GuestIpAddMembership = 12; +constexpr int GuestIpDropMembership = 13; +constexpr int GuestIpReceiveDestination = 7; +constexpr int GuestIpReceiveInterface = 20; +constexpr int GuestIpReceiveTimeToLive = 65; +constexpr std::uint32_t MulticastRequestWithIndex = 12; +constexpr int ErrorNotSupported = 45; +constexpr int ErrorAddressInUse = 48; +constexpr int ErrorAddressNotAvailable = 49; constexpr int ErrorInvalid = 22; constexpr int ErrorDomain = 33; constexpr int ErrorNoProtocolOption = 42; +constexpr int ErrorNoBuffers = 55; +constexpr int MaximumReservation = 5592405; +constexpr int MaximumBuffer = 524288; constexpr int MaximumLinger = 65535; constexpr std::int64_t LongestTimeout = 0x7fffffffffffffffLL; @@ -78,7 +115,6 @@ int HostError() { return GuestErrorFromHost(errno); } int NativeBoolean(int option) { switch (option) { - case GuestReuseAddress: return SO_REUSEADDR; case GuestKeepAlive: return SO_KEEPALIVE; case GuestDontRoute: return SO_DONTROUTE; case GuestBroadcast: return SO_BROADCAST; @@ -87,9 +123,39 @@ int NativeBoolean(int option) { } } +void EnsureBuffers(SocketOptionState& state, int family, int type) { + if (state.sendBuffer != 0) return; + if (family == GuestUnix) { + state.sendBuffer = type == 2 ? 2048 : 8192; + state.receiveBuffer = type == 2 ? 4096 : 8192; + } else { + state.sendBuffer = type == 1 ? 32768 : 9216; + state.receiveBuffer = type == 1 ? 65536 : 42080; + } + state.sendLowWater = std::min(2048, state.sendBuffer); + state.receiveLowWater = 1; +} + +int TcpFlagBit(int option) { + switch (option) { + case GuestTcpNoPush: return 0x1000; + case GuestTcpNoOptions: return 0x8; + case GuestTcpOption80: return 0x10000; + default: return 0; + } +} + +int IpReceiveBit(int option) { + switch (option) { + case GuestIpReceiveDestination: return 1; + case GuestIpReceiveInterface: return 2; + case GuestIpReceiveTimeToLive: return 4; + default: return 0; + } +} + int Copy(void* output, std::uint32_t* length, const void* value, std::uint32_t size) { - const std::uint32_t count = std::min(*length, size); - if (count != 0 && !output) return ErrorInvalid; + const std::uint32_t count = output ? std::min(*length, size) : 0; if (count != 0) std::memcpy(output, value, count); *length = count; return 0; @@ -97,11 +163,10 @@ int Copy(void* output, std::uint32_t* length, const void* value, std::uint32_t s } -int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int option, const void* value, std::uint32_t length) { +int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int family, int type, int option, const void* value, std::uint32_t length) { const auto handle = static_cast(native); if (option == GuestLinger) { if (length < sizeof(GuestLingerValue)) return ErrorInvalid; - if (!value) return ErrorInvalid; GuestLingerValue guest; std::memcpy(&guest, value, sizeof(guest)); if (guest.seconds < 0 || guest.seconds > MaximumLinger) return ErrorDomain; @@ -119,7 +184,6 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int op } if (option == GuestSendTimeout || option == GuestReceiveTimeout) { if (length < sizeof(GuestTimeval)) return ErrorInvalid; - if (!value) return ErrorInvalid; GuestTimeval time; std::memcpy(&time, value, sizeof(time)); if (time.seconds < 0 || time.microseconds < 0 || time.microseconds >= 1000000) return ErrorDomain; @@ -128,7 +192,6 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int op } if (option == GuestNoSigpipe || option == GuestNonBlocking) { if (length < sizeof(int)) return ErrorInvalid; - if (!value) return ErrorInvalid; int number; std::memcpy(&number, value, sizeof(number)); if (option == GuestNoSigpipe) { @@ -146,21 +209,52 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int op return 0; } const bool native_boolean = NativeBoolean(option) != -1; - if (native_boolean || option == GuestSendBuffer || option == GuestReceiveBuffer) { + const bool buffer = option == GuestSendBuffer || option == GuestReceiveBuffer; + const bool lowWater = option == GuestSendLowWater || option == GuestReceiveLowWater; + if (native_boolean || buffer || lowWater) { if (length < sizeof(int)) return ErrorInvalid; - if (!value) return ErrorInvalid; int number; std::memcpy(&number, value, sizeof(number)); - if (option == GuestSendBuffer || option == GuestReceiveBuffer) { + if (buffer || lowWater) { if (number < 1) return ErrorInvalid; - const int nativeOption = option == GuestSendBuffer ? SO_SNDBUF : SO_RCVBUF; + EnsureBuffers(state, family, type); + const bool send = option == GuestSendBuffer || option == GuestSendLowWater; + int& size = send ? state.sendBuffer : state.receiveBuffer; + int& mark = send ? state.sendLowWater : state.receiveLowWater; + if (lowWater) { + mark = std::min(number, size); + return 0; + } + if (number > MaximumReservation) return ErrorNoBuffers; + const int nativeOption = send ? SO_SNDBUF : SO_RCVBUF; if (::setsockopt(handle, SOL_SOCKET, nativeOption, reinterpret_cast(&number), sizeof(number))) return HostError(); + size = std::min(number, MaximumBuffer); + mark = std::min(mark, size); return 0; } const int enabled = number != 0 ? 1 : 0; if (::setsockopt(handle, SOL_SOCKET, NativeBoolean(option), reinterpret_cast(&enabled), sizeof(enabled))) return HostError(); return 0; } + if ((option & (KeptFlags | WriteOnlyFlags)) != 0 && (option & (option - 1)) == 0) { + if (length < sizeof(int)) return ErrorInvalid; + int number; + std::memcpy(&number, value, sizeof(number)); + if ((option & KeptFlags) == 0) return 0; + const int flags = number != 0 ? state.keptFlags | option : state.keptFlags & ~option; + if (option == GuestReuseAddress || option == GuestReusePort) { +#ifdef _WIN32 + const int reuseAddress = (flags & (GuestReuseAddress | GuestReusePort)) != 0 ? 1 : 0; +#else + const int reuseAddress = (flags & GuestReuseAddress) != 0 ? 1 : 0; + const int reusePort = (flags & GuestReusePort) != 0 ? 1 : 0; + if (::setsockopt(handle, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&reusePort), sizeof(reusePort))) return HostError(); +#endif + if (::setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuseAddress), sizeof(reuseAddress))) return HostError(); + } + state.keptFlags = flags; + return 0; + } return ErrorNoProtocolOption; } @@ -178,7 +272,7 @@ bool NativeListening(std::uintptr_t native, bool& listening) { return true; } -int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int type, int option, void* value, std::uint32_t* length) { +int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int family, int type, int option, void* value, std::uint32_t* length) { const auto handle = static_cast(native); if (option == GuestLinger) { linger current{}; @@ -203,10 +297,12 @@ int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int ty number = listening ? GuestAcceptConnection : 0; } else if (option == GuestType) { number = type; + } else if ((option & KeptFlags) != 0 && (option & (option - 1)) == 0) { + number = state.keptFlags & option; } else if (option == GuestNoSigpipe) { number = state.noSignal ? GuestNoSigpipe : 0; } else if (option == GuestNonBlocking) { - number = state.nonblocking ? 1 : 0; + number = state.nonblocking ? GuestNonBlockingState : 0; } else if (option == GuestError) { int pending = 0; socklen_t size = sizeof(pending); @@ -216,29 +312,67 @@ int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int ty else if (pending != 0) state.reportedError = pending; #endif number = pending == 0 ? 0 : GuestErrorFromHost(pending); - } else if (option == GuestSendBuffer || option == GuestReceiveBuffer) { - socklen_t size = sizeof(number); - if (::getsockopt(handle, SOL_SOCKET, option == GuestSendBuffer ? SO_SNDBUF : SO_RCVBUF, reinterpret_cast(&number), &size)) return HostError(); + } else if (option == GuestSendBuffer || option == GuestReceiveBuffer || option == GuestSendLowWater || option == GuestReceiveLowWater) { + EnsureBuffers(state, family, type); + number = option == GuestSendBuffer ? state.sendBuffer : option == GuestReceiveBuffer ? state.receiveBuffer + : option == GuestSendLowWater ? state.sendLowWater : state.receiveLowWater; } else { return ErrorNoProtocolOption; } return Copy(value, length, &number, sizeof(number)); } -int SetTcpLevelOption(std::uintptr_t native, bool tcp, int option, const void* value, std::uint32_t length) { +bool HasPeer(std::uintptr_t native) { + sockaddr_storage peer{}; + socklen_t size = sizeof(peer); + return ::getpeername(static_cast(native), reinterpret_cast(&peer), &size) == 0; +} + +int SetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, int option, const void* value, std::uint32_t length) { if (!tcp) return ErrorInvalid; - if (option != GuestTcpNoDelay) return ErrorNoProtocolOption; + if (option != GuestTcpNoDelay && option != GuestTcpMaximumSegment && option != GuestTcpKeepIdle && option != GuestTcpKeepInterval && + option != GuestTcpKeepCount && TcpFlagBit(option) == 0) + return ErrorNoProtocolOption; if (length < sizeof(int)) return ErrorInvalid; - if (!value) return ErrorInvalid; int number; std::memcpy(&number, value, sizeof(number)); + if (const int bit = TcpFlagBit(option); bit != 0) { + state.tcpFlags = number != 0 ? state.tcpFlags | bit : state.tcpFlags & ~bit; + return 0; + } + const auto unsignedNumber = static_cast(number); + if (option == GuestTcpMaximumSegment) { + if (!HasPeer(native) || number <= 0 || number > state.tcpMaximumSegment || number + 40 < MinimumSegment) return ErrorInvalid; + state.tcpMaximumSegment = std::max(number, MinimumSegment); + return 0; + } + if (option == GuestTcpKeepCount) { + state.tcpKeepCount = unsignedNumber; + return 0; + } + if (option == GuestTcpKeepIdle || option == GuestTcpKeepInterval) { + if (unsignedNumber > UINT32_MAX / TicksPerSecond) return ErrorInvalid; + (option == GuestTcpKeepIdle ? state.tcpKeepIdle : state.tcpKeepInterval) = unsignedNumber; + return 0; + } const int enabled = number != 0 ? 1 : 0; if (::setsockopt(static_cast(native), IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&enabled), sizeof(enabled))) return HostError(); return 0; } -int GetTcpLevelOption(std::uintptr_t native, bool tcp, int option, void* value, std::uint32_t* length) { +int GetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, int option, void* value, std::uint32_t* length) { if (!tcp) return ErrorInvalid; + if (option == GuestTcpMaximumSegment || option == GuestTcpKeepIdle || option == GuestTcpKeepInterval || option == GuestTcpKeepCount) { + const unsigned int number = option == GuestTcpMaximumSegment ? static_cast(state.tcpMaximumSegment) + : option == GuestTcpKeepIdle ? (state.tcpKeepIdle != 0 ? state.tcpKeepIdle : DefaultKeepIdle) + : option == GuestTcpKeepInterval ? (state.tcpKeepInterval != 0 ? state.tcpKeepInterval : DefaultKeepInterval) + : (state.tcpKeepCount != 0 ? state.tcpKeepCount : DefaultKeepCount); + return Copy(value, length, &number, sizeof(number)); + } + if (const int bit = TcpFlagBit(option); bit != 0) { + const int number = state.tcpFlags & bit; + return Copy(value, length, &number, sizeof(number)); + } if (option != GuestTcpNoDelay) return ErrorNoProtocolOption; int enabled = 0; socklen_t size = sizeof(enabled); @@ -247,11 +381,125 @@ int GetTcpLevelOption(std::uintptr_t native, bool tcp, int option, void* value, return Copy(value, length, &number, sizeof(number)); } +bool MulticastOption(int option) { + return option == GuestIpMulticastInterface || option == GuestIpMulticastTimeToLive || option == GuestIpMulticastLoop || + option == GuestIpAddMembership || option == GuestIpDropMembership; +} + +int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, bool datagram, int option, const void* value, std::uint32_t length) { + if (!ipv4) return ErrorInvalid; + if (MulticastOption(option) && !datagram) return ErrorNotSupported; + const auto handle = static_cast(native); + if (option == GuestIpAddMembership || option == GuestIpDropMembership) { + if (length < sizeof(ip_mreq)) return ErrorInvalid; + ip_mreq request{}; + std::memcpy(&request, value, sizeof(request)); + const auto* group = reinterpret_cast(&request.imr_multiaddr); + if ((group[0] & 0xf0) != 0xe0) return ErrorInvalid; + std::uint32_t groupBits; + std::uint32_t interfaceBits; + std::memcpy(&groupBits, &request.imr_multiaddr, sizeof(groupBits)); + std::memcpy(&interfaceBits, &request.imr_interface, sizeof(interfaceBits)); + const std::uint64_t key = (std::uint64_t{groupBits} << 32) | interfaceBits; + const bool join = option == GuestIpAddMembership; + const auto found = join || interfaceBits != 0 + ? std::find(state.memberships.begin(), state.memberships.end(), key) + : std::find_if(state.memberships.begin(), state.memberships.end(), [groupBits](std::uint64_t joined) { return joined >> 32 == groupBits; }); + if (join && found != state.memberships.end()) return ErrorAddressInUse; + if (!join && found == state.memberships.end()) return ErrorAddressNotAvailable; + if (!join) { + const auto joinedInterface = static_cast(*found); + std::memcpy(&request.imr_interface, &joinedInterface, sizeof(joinedInterface)); + } + if (::setsockopt(handle, IPPROTO_IP, join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, reinterpret_cast(&request), sizeof(request))) + return HostError(); + if (join) state.memberships.push_back(key); + else state.memberships.erase(found); + return 0; + } + if (option == GuestIpMulticastInterface) { + if (length < sizeof(in_addr)) return ErrorInvalid; + in_addr address{}; + if (length == MulticastRequestWithIndex) { + std::int32_t index; + std::memcpy(&index, static_cast(value) + 8, sizeof(index)); + if (index != 0) throw std::runtime_error("IP_MULTICAST_IF: an interface index is not supported"); + const auto* addresses = static_cast(value); + if (std::any_of(addresses, addresses + 8, [](unsigned char byte) { return byte != 0; })) + throw std::runtime_error("IP_MULTICAST_IF: an ip_mreqn with a non-zero address is not supported"); + } else { + std::memcpy(&address, value, sizeof(address)); + } + if (::setsockopt(handle, IPPROTO_IP, IP_MULTICAST_IF, reinterpret_cast(&address), sizeof(address))) return HostError(); + std::memcpy(&state.multicastInterface, &address, sizeof(address)); + return 0; + } + if (const int bit = IpReceiveBit(option); bit != 0) { + if (length < sizeof(int)) return ErrorInvalid; + int number; + std::memcpy(&number, value, sizeof(number)); + state.ipReceiveOptions = number != 0 ? state.ipReceiveOptions | bit : state.ipReceiveOptions & ~bit; + return 0; + } + if (option == GuestIpTimeToLive || option == GuestIpTypeOfService) { + if (length < sizeof(int)) return ErrorInvalid; + int number; + std::memcpy(&number, value, sizeof(number)); + const int stored = number & 0xff; + if (option == GuestIpTypeOfService) { + state.ipTypeOfService = stored; + return 0; + } + if (stored != 0 && ::setsockopt(handle, IPPROTO_IP, IP_TTL, reinterpret_cast(&stored), sizeof(stored))) return HostError(); + state.ipTimeToLive = stored; + return 0; + } + if (option == GuestIpMulticastTimeToLive || option == GuestIpMulticastLoop) { + unsigned int number; + if (length == 1) { + unsigned char byte; + std::memcpy(&byte, value, 1); + number = byte; + } else { + if (length < sizeof(number)) return ErrorInvalid; + std::memcpy(&number, value, sizeof(number)); + if (option == GuestIpMulticastTimeToLive && number > 255) return ErrorInvalid; + } + const int host = option == GuestIpMulticastLoop ? ((number & 0xff) != 0 ? 1 : 0) : static_cast(number); + const int nativeOption = option == GuestIpMulticastLoop ? IP_MULTICAST_LOOP : IP_MULTICAST_TTL; + if (::setsockopt(handle, IPPROTO_IP, nativeOption, reinterpret_cast(&host), sizeof(host))) return HostError(); + if (option == GuestIpMulticastLoop) state.multicastLoop = host != 0; + else state.multicastTimeToLive = host; + return 0; + } + return ErrorNoProtocolOption; +} + +int GetIpLevelOption(SocketOptionState& state, bool ipv4, bool datagram, int option, void* value, std::uint32_t* length) { + if (!ipv4) return ErrorInvalid; + if (MulticastOption(option) && option != GuestIpAddMembership && option != GuestIpDropMembership && !datagram) return ErrorNotSupported; + if (option == GuestIpMulticastInterface) { + if (*length == MulticastRequestWithIndex) throw std::runtime_error("IP_MULTICAST_IF: reading an ip_mreqn is not supported"); + return Copy(value, length, &state.multicastInterface, sizeof(state.multicastInterface)); + } + int number; + if (const int bit = IpReceiveBit(option); bit != 0) number = (state.ipReceiveOptions & bit) != 0 ? 1 : 0; + else if (option == GuestIpTimeToLive) number = state.ipTimeToLive; + else if (option == GuestIpTypeOfService) number = state.ipTypeOfService; + else if (option == GuestIpMulticastTimeToLive) number = state.multicastTimeToLive; + else if (option == GuestIpMulticastLoop) number = state.multicastLoop ? 1 : 0; + else return ErrorNoProtocolOption; + if ((option == GuestIpMulticastTimeToLive || option == GuestIpMulticastLoop) && *length == 1) { + const auto byte = static_cast(number); + return Copy(value, length, &byte, 1); + } + return Copy(value, length, &number, sizeof(number)); +} + int SetIpv6LevelOption(std::uintptr_t native, bool ipv6, int option, const void* value, std::uint32_t length) { if (!ipv6) return ErrorInvalid; if (option != GuestIpv6Only) return ErrorNoProtocolOption; if (length != sizeof(int)) return ErrorInvalid; - if (!value) return ErrorInvalid; int number; std::memcpy(&number, value, sizeof(number)); const int enabled = number != 0 ? 1 : 0; diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index fbf308a2e4..e32fd3acaf 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -1,7 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include +#include #include #include #else @@ -38,6 +41,7 @@ #include "prx/libkernel/Equeue/Equeue.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/RandomDevice.hpp" +#include "prx/libkernel/File/include/SharedMemory.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" #include "prx/libkernel/Socket/include/SocketPoll.hpp" #include "prx/libkernel/Socket/include/SocketConnect.hpp" @@ -57,8 +61,6 @@ #include #include -extern "C" int* APS5_VABI __error_nid_postfix(); -extern "C" int APS5_VABI dup2_nid_postfix(int from, int to); namespace { #ifdef _WIN32 using NativeSocket = SOCKET; @@ -70,10 +72,11 @@ constexpr auto Invalid = -1; int Fail(int error) { *__error_nid_postfix() = error; return -1; } constexpr int GuestSocketDescriptorLimit = 1024; constexpr int GuestPeek = 0x2; +constexpr int GuestWaitAll = 0x40; constexpr int GuestDontWait = 0x80; constexpr int GuestNoSignal = 0x20000; constexpr int GuestSendFlags = GuestNoSignal | GuestDontWait; -constexpr int GuestReceiveFlags = GuestPeek | GuestDontWait; +constexpr int GuestReceiveFlags = GuestPeek | GuestWaitAll | GuestDontWait; int NativeSendFlags(int flags) { #ifdef _WIN32 static_cast(flags); @@ -82,11 +85,12 @@ int NativeSendFlags(int flags) { return (flags & GuestNoSignal ? MSG_NOSIGNAL : 0) | (flags & GuestDontWait ? MSG_DONTWAIT : 0); #endif } -int NativeReceiveFlags(int flags) { +int NativeReceiveFlags(int flags, int type) { + const int waitAll = type == 1 && (flags & GuestWaitAll) != 0 ? MSG_WAITALL : 0; #ifdef _WIN32 - return flags & GuestPeek ? MSG_PEEK : 0; + return (flags & GuestPeek ? MSG_PEEK : 0) | waitAll; #else - return (flags & GuestPeek ? MSG_PEEK : 0) | (flags & GuestDontWait ? MSG_DONTWAIT : 0); + return (flags & GuestPeek ? MSG_PEEK : 0) | (flags & GuestDontWait ? MSG_DONTWAIT : 0) | waitAll; #endif } int NativeError() { @@ -104,7 +108,9 @@ int ConnectError() { } int SendError(int type) { #ifdef _WIN32 - if (type == 2 && WSAGetLastError() == WSAENOTCONN) return 39; + const int error = WSAGetLastError(); + if (type == 2 && error == WSAENOTCONN) return 39; + if (type == 1 && (error == WSAECONNABORTED || error == WSAECONNRESET)) return 32; #else static_cast(type); #endif @@ -118,6 +124,7 @@ struct Socket : KernelSocketPoll::NativeOwner { GuestSockets::SocketOptionState options; bool receiveShut = false; bool sendShut = false; + std::weak_ptr peer; Socket(NativeSocket value, int family, int type) : value(value), family(family), type(type) {} ~Socket() { if (value == Invalid) return; @@ -263,13 +270,19 @@ std::shared_ptr Lookup(int descriptor) { Fail(descriptor >= 0 && DescriptorIsOpen_nid_no_patch(descriptor) ? 38 : 9); return {}; } -bool Address(const void* input, std::uint32_t length, sockaddr_storage& native, socklen_t& size) { - if (length > 255) { Fail(63); return false; } - if (length < 2) { Fail(22); return false; } - if (!input) { Fail(14); return false; } +int AddressLengthError(const void* input, std::uint32_t length) { + if (length > 255) return 63; + if (length < 2) return 22; + return input ? 0 : 14; +} +bool Address(int family, const void* input, std::uint32_t length, sockaddr_storage& native, socklen_t& size, bool lengthFirst = false) { + if (const int error = AddressLengthError(input, length)) { Fail(error); return false; } const auto* bytes = static_cast(input); - if ((bytes[1] == 2 && length < 16) || (bytes[1] == 28 && length < 28)) { Fail(22); return false; } - if (bytes[1] == 2 && length >= 16) { + const bool wrongLength = (family == 2 && length != 16) || (family == 28 && length != 28); + if (lengthFirst && wrongLength) { Fail(22); return false; } + if (family == 2 && bytes[1] != 2) { Fail(47); return false; } + if (wrongLength) { Fail(22); return false; } + if (family == 2) { auto& v4 = reinterpret_cast(native); v4.sin_family = AF_INET; std::memcpy(&v4.sin_port, bytes + 2, 2); @@ -277,7 +290,7 @@ bool Address(const void* input, std::uint32_t length, sockaddr_storage& native, size = sizeof(v4); return true; } - if (bytes[1] == 28 && length >= 28) { + if (family == 28 && bytes[1] == 28) { auto& v6 = reinterpret_cast(native); v6.sin6_family = AF_INET6; std::memcpy(&v6.sin6_port, bytes + 2, 2); @@ -290,6 +303,16 @@ bool Address(const void* input, std::uint32_t length, sockaddr_storage& native, Fail(47); return false; } +int LongMappedDualStack(const Socket& socket, const void* address, std::uint32_t length) { + if (socket.family != 28 || socket.type != 2 || address == nullptr || length <= 28 || length > 255) return 0; + const auto* bytes = static_cast(address); + constexpr unsigned char mapped[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}; + if (bytes[1] != 28 || std::memcmp(bytes + 8, mapped, sizeof(mapped)) != 0) return 0; + int ipv6Only = 0; + socklen_t size = sizeof(ipv6Only); + if (::getsockopt(socket.value, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&ipv6Only), &size)) return Fail(NativeError()); + return ipv6Only ? 0 : 1; +} std::uint16_t& PortOf(sockaddr_storage& address) { return address.ss_family == AF_INET ? reinterpret_cast(address).sin_port : reinterpret_cast(address).sin6_port; } @@ -297,15 +320,25 @@ bool Listening(const Socket& socket) { bool listening = false; return GuestSockets::NativeListening(static_cast(socket.value), listening) && listening; } -bool ZeroPortConnect(const Socket& socket, const void* address, std::uint32_t length) { - if (!address || (socket.type != 1 && socket.type != 2) || (socket.family != 2 && socket.family != 28)) return false; - const auto* bytes = static_cast(address); - if (length < (socket.family == 2 ? 16u : 28u) || bytes[1] != socket.family || (bytes[2] | bytes[3]) != 0) return false; +bool PeerConnected(const Socket& socket) { + sockaddr_storage peer{}; + socklen_t size = sizeof(peer); + return ::getpeername(socket.value, reinterpret_cast(&peer), &size) == 0; +} +constexpr std::int64_t NotAnswered = -2; +std::int64_t ZeroPortSend(const Socket& socket, const sockaddr_storage& destination, std::uint64_t length) { + if (socket.type != 2 || socket.family != 2 || destination.ss_family != AF_INET) return NotAnswered; + const auto& remote = reinterpret_cast(destination); + if (remote.sin_port != 0) return NotAnswered; sockaddr_storage peer{}; socklen_t peerSize = sizeof(peer); - if (::getpeername(socket.value, reinterpret_cast(&peer), &peerSize) == 0) return false; - Fail(49); - return true; + if (::getpeername(socket.value, reinterpret_cast(&peer), &peerSize) == 0) return NotAnswered; + sockaddr_in local{}; + socklen_t localSize = sizeof(local); + const bool bound = ::getsockname(socket.value, reinterpret_cast(&local), &localSize) == 0 + && local.sin_port != 0 && local.sin_addr.s_addr != htonl(INADDR_ANY); + if (bound && remote.sin_addr.s_addr != htonl(INADDR_ANY) && remote.sin_addr.s_addr != htonl(INADDR_BROADCAST)) return static_cast(length); + return Fail(49); } int Disconnect(const Socket& socket) { sockaddr_storage peer{}; @@ -333,6 +366,20 @@ int Disconnect(const Socket& socket) { if (::bind(socket.value, reinterpret_cast(&wildcard), localSize) != 0) return Fail(NativeError()); return 0; } +bool ZeroPortConnect(const Socket& socket, const void* address, std::uint32_t length) { + if (!address || (socket.type != 1 && socket.type != 2) || (socket.family != 2 && socket.family != 28)) return false; + const auto* bytes = static_cast(address); + if (length < (socket.family == 2 ? 16u : 28u) || bytes[1] != socket.family || (bytes[2] | bytes[3]) != 0) return false; + if (socket.type == 2) { + if (Disconnect(socket) != 0) return true; + } else { + sockaddr_storage peer{}; + socklen_t peerSize = sizeof(peer); + if (::getpeername(socket.value, reinterpret_cast(&peer), &peerSize) == 0) return false; + } + Fail(49); + return true; +} void GuestAddress(const sockaddr_storage& native, void* output, std::uint32_t* length) { unsigned char bytes[28]{}; bytes[0] = native.ss_family == AF_INET ? 16 : 28; @@ -501,11 +548,7 @@ using NativePollDescriptor = WSAPOLLFD; #else using NativePollDescriptor = pollfd; #endif -struct GuestPollDescriptor { - int descriptor; - short events; - short revents; -}; + struct PollFlag { short guest; short native; @@ -533,6 +576,16 @@ constexpr short GuestPollWritable = 0x4; constexpr short GuestPollVnodeReady = GuestPollReadable | GuestPollWritable; constexpr short GuestPollHangUp = 0x10; constexpr short GuestPollError = 0x8; +#ifdef _WIN32 +short ClosedStreamEvents(int descriptor, short revents, short requested) { + if ((revents & (POLLHUP | POLLERR)) != POLLHUP) return revents; + const auto socket = Find(descriptor); + if (!socket || socket->type != 1) return revents; + revents = static_cast(revents | (requested & POLLRDNORM)); + std::lock_guard lock(socket->modeMutex); + return socket->sendShut ? revents : static_cast(revents & ~POLLHUP); +} +#endif [[noreturn]] void ThrowReadEndWritable(int descriptor) { throw std::runtime_error("poll: POLLOUT on the read end of pipe descriptor " + std::to_string(descriptor) + " is not supported"); } @@ -671,6 +724,30 @@ int SetNonBlocking(Socket& socket) { socket.options.nonblocking = true; return 0; } +bool ReadableNow(const Socket& socket) { + NativePollDescriptor entry{}; + entry.fd = socket.value; +#ifdef _WIN32 + entry.events = POLLRDNORM; + return WSAPoll(&entry, 1, 0) > 0; +#else + entry.events = POLLIN; + return ::poll(&entry, 1, 0) > 0; +#endif +} +int ReceiveAnsweredHere(Socket& socket, std::uint64_t length) { +#ifdef _WIN32 + { + std::lock_guard lock(socket.modeMutex); + if (socket.receiveShut) return 0; + } +#endif + if (length != 0) return -1; + if (socket.type != 1) return ReadableNow(socket) ? -1 : 0; + sockaddr_storage peer{}; + socklen_t size = sizeof(peer); + return ::getpeername(socket.value, reinterpret_cast(&peer), &size) == 0 ? 0 : NativeError(); +} int SocketSendFlags(Socket& socket, int flags) { std::lock_guard lock(socket.modeMutex); return NativeSendFlags(socket.options.noSignal ? flags | GuestNoSignal : flags); @@ -1069,31 +1146,35 @@ int APS5_VABI _fcntl_nid_postfix(int descriptor, int command, ...) { } int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option, const void* value, std::uint32_t length) { + if (!value && length != 0) return Fail(14); int netError = 0; if (!GuestSockets::IsOpen(descriptor) && KernelSetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) { return netError == 0 ? 0 : Fail(netError); } const auto socket = Lookup(descriptor); if (!socket) return -1; - if (level != 0xffff && level != 6 && level != 41) return Fail(42); + if (level != 0xffff && level != 0 && level != 6 && level != 41) return Fail(22); std::lock_guard lock(socket->modeMutex); const auto native = static_cast(socket->value); - const int error = level == 6 ? GuestSockets::SetTcpLevelOption(native, socket->family != GuestUnix && socket->type == 1, option, value, length) + const int error = level == 0 ? GuestSockets::SetIpLevelOption(native, socket->options, socket->family == 2, socket->type == 2, option, value, length) + : level == 6 ? GuestSockets::SetTcpLevelOption(native, socket->options, socket->family != GuestUnix && socket->type == 1, option, value, length) : level == 41 ? GuestSockets::SetIpv6LevelOption(native, socket->family == 28, option, value, length) - : GuestSockets::SetSocketLevelOption(native, socket->options, option, value, length); + : GuestSockets::SetSocketLevelOption(native, socket->options, socket->family, socket->type, option, value, length); return error == 0 ? 0 : Fail(error); } int APS5_VABI getsockopt_nid_postfix(int descriptor, int level, int option, void* value, std::uint32_t* length) { const auto socket = Lookup(descriptor); if (!socket) return -1; - if (level != 0xffff && level != 6 && level != 41) return Fail(42); + if (level != 0xffff && level != 0 && level != 6 && level != 41) return Fail(22); if (!length) return Fail(22); + if (!value) *length = 0; std::lock_guard lock(socket->modeMutex); const auto native = static_cast(socket->value); - const int error = level == 6 ? GuestSockets::GetTcpLevelOption(native, socket->family != GuestUnix && socket->type == 1, option, value, length) + const int error = level == 0 ? GuestSockets::GetIpLevelOption(socket->options, socket->family == 2, socket->type == 2, option, value, length) + : level == 6 ? GuestSockets::GetTcpLevelOption(native, socket->options, socket->family != GuestUnix && socket->type == 1, option, value, length) : level == 41 ? GuestSockets::GetIpv6LevelOption(native, socket->family == 28, option, value, length) - : GuestSockets::GetSocketLevelOption(native, socket->options, socket->type, option, value, length); + : GuestSockets::GetSocketLevelOption(native, socket->options, socket->family, socket->type, option, value, length); return error == 0 ? 0 : Fail(error); } int APS5_VABI socket_nid_postfix(int family, int type, int protocol) { @@ -1102,7 +1183,7 @@ int APS5_VABI socket_nid_postfix(int family, int type, int protocol) { if (family != 2 && family != 28) return Fail(47); const int nativeType = plainType == 1 ? SOCK_STREAM : plainType == 2 ? SOCK_DGRAM : -1; const int expectedProtocol = plainType == 1 ? 6 : 17; - const bool inetKnowsType = plainType == 3 || plainType == 5; + const bool inetKnowsType = plainType == 3; if (nativeType == -1) return Fail(protocol == 0 && plainType != 0 && !inetKnowsType ? 41 : 43); if (protocol != 0 && protocol != expectedProtocol) return Fail(43); #ifdef _WIN32 @@ -1115,9 +1196,17 @@ int APS5_VABI socket_nid_postfix(int family, int type, int protocol) { const int ipv6Only = 1; if (family == 28 && ::setsockopt(native, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&ipv6Only), sizeof(ipv6Only))) return Fail(NativeError()); +#ifdef _WIN32 + BOOL reportUnreachable = FALSE; + DWORD returned = 0; + if (nativeType == SOCK_DGRAM && WSAIoctl(native, SIO_UDP_CONNRESET, &reportUnreachable, sizeof(reportUnreachable), nullptr, 0, + &returned, nullptr, nullptr) != 0) + return Fail(NativeError()); +#endif try { auto socket = std::make_shared(native, family, plainType); guard.value = Invalid; + if (family == 28) socket->options.tcpMaximumSegment = 1220; if (nonblocking && SetNonBlocking(*socket)) return -1; const int number = Install(std::move(socket)); if (number >= 0 && (type & GuestCloseOnExec) != 0) { @@ -1156,6 +1245,8 @@ int APS5_VABI socketpair_nid_postfix(int family, int type, int protocol, int* de firstGuard.value = Invalid; auto second = std::make_shared(native[1], GuestUnix, plainType); secondGuard.value = Invalid; + first->peer = second; + second->peer = first; if (nonblocking && (SetNonBlocking(*first) || SetNonBlocking(*second))) return -1; const int firstDescriptor = Install(std::move(first)); if (firstDescriptor < 0) return -1; @@ -1178,20 +1269,22 @@ int APS5_VABI socketpair_nid_postfix(int family, int type, int protocol, int* de } } int APS5_VABI connect_nid_postfix(int descriptor, const void* address, std::uint32_t length) { + if (const int error = AddressLengthError(address, length)) return Fail(error); const auto socket = Lookup(descriptor); if (!socket) return -1; if (socket->family != GuestUnix && socket->type == 2 && address && length >= 2 && static_cast(address)[1] == 0 && Disconnect(*socket) != 0) return -1; + if (socket->type == 1 && Listening(*socket)) return Fail(45); + const int mapped = LongMappedDualStack(*socket, address, length); + if (mapped < 0) return -1; sockaddr_storage native{}; socklen_t size; - if (!Address(address, length, native, size)) return -1; + if (!Address(socket->family, address, mapped ? 28 : length, native, size)) return -1; #ifdef _WIN32 if (socket->type == 1) RefuseClosedLoopbackAtOnce_nid_no_patch(socket->value, reinterpret_cast(&native)); #endif - if (socket->type == 1 && Listening(*socket)) return Fail(45); if (ZeroPortConnect(*socket, address, length)) return -1; - if (native.ss_family != (socket->family == 2 ? AF_INET : AF_INET6)) return Fail(47); { std::lock_guard lock(socket->modeMutex); socket->options.reportedError = 0; @@ -1219,13 +1312,25 @@ int APS5_VABI accept_nid_postfix(int descriptor, void* address, std::uint32_t* l try { auto accepted = std::make_shared(native, listener->family, listener->type); guard.value = Invalid; + if (listener->family == 28) accepted->options.tcpMaximumSegment = 1220; bool inheritNonblocking; { std::lock_guard modeLock(listener->modeMutex); inheritNonblocking = listener->options.nonblocking; accepted->options.noSignal = listener->options.noSignal; + accepted->options.keptFlags = listener->options.keptFlags; accepted->options.sendTimeout = listener->options.sendTimeout; accepted->options.receiveTimeout = listener->options.receiveTimeout; + accepted->options.sendBuffer = listener->options.sendBuffer; + accepted->options.receiveBuffer = listener->options.receiveBuffer; + accepted->options.sendLowWater = listener->options.sendLowWater; + accepted->options.receiveLowWater = listener->options.receiveLowWater; + accepted->options.ipTimeToLive = listener->options.ipTimeToLive; + accepted->options.ipTypeOfService = listener->options.ipTypeOfService; + accepted->options.tcpKeepIdle = listener->options.tcpKeepIdle; + accepted->options.tcpKeepInterval = listener->options.tcpKeepInterval; + accepted->options.tcpKeepCount = listener->options.tcpKeepCount; + accepted->options.tcpFlags = listener->options.tcpFlags & 0x1000; } if (inheritNonblocking && SetNonBlocking(*accepted)) return -1; const int acceptedDescriptor = Install(std::move(accepted)); @@ -1254,27 +1359,50 @@ std::int64_t APS5_VABI recv_nid_postfix(int descriptor, void* buffer, std::uint6 if (!buffer && length) return Fail(14); const GuestArena::HostWrite destination(length == 0 ? nullptr : buffer, static_cast(length)); if (!destination.Open()) return Fail(14); + if (const int answered = ReceiveAnsweredHere(*socket, length); answered >= 0) return answered == 0 ? 0 : Fail(answered); if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); const auto result = ::recv(socket->value, static_cast(buffer), static_cast(length), - NativeReceiveFlags(flags)); + NativeReceiveFlags(flags, socket->type)); #ifdef _WIN32 if (result < 0 && socket->type == 2 && WSAGetLastError() == WSAEMSGSIZE) return static_cast(length); #endif return result < 0 ? Fail(NativeError()) : result; } int APS5_VABI bind_nid_postfix(int descriptor, const void* address, std::uint32_t length) { + if (const int error = AddressLengthError(address, length)) return Fail(error); const auto socket = Lookup(descriptor); if (!socket) return -1; sockaddr_storage native{}; socklen_t size; - if (!Address(address, length, native, size)) return -1; + const int mapped = LongMappedDualStack(*socket, address, length); + if (mapped < 0) return -1; + if (!Address(socket->family, address, mapped ? 28 : length, native, size)) return -1; return ::bind(socket->value, reinterpret_cast(&native), size) ? Fail(NativeError()) : 0; } +namespace { +bool AlreadyShut(NativeSocket socket, int how) { +#ifdef __APPLE__ + if (errno != ENOTCONN) return false; + sockaddr_storage peer{}; + socklen_t size = sizeof(peer); + if (::getpeername(socket, reinterpret_cast(&peer), &size)) { + errno = ENOTCONN; + return false; + } + return how != SHUT_RDWR || ::shutdown(socket, SHUT_WR) == 0 || errno == ENOTCONN; +#else + (void)socket; + (void)how; + return false; +#endif +} +} + int APS5_VABI shutdown_nid_postfix(int descriptor, int how) { const auto socket = Lookup(descriptor); if (!socket) return -1; if (how < 0 || how > 2) return Fail(22); - if (::shutdown(socket->value, how)) return Fail(NativeError()); + if (::shutdown(socket->value, how) && !AlreadyShut(socket->value, how)) return Fail(NativeError()); std::lock_guard lock(socket->modeMutex); if (how != 1) socket->receiveShut = true; if (how != 0) socket->sendShut = true; @@ -1302,6 +1430,7 @@ int APS5_VABI getsockname_nid_postfix(int descriptor, void* address, std::uint32 int APS5_VABI getpeername_nid_postfix(int descriptor, void* address, std::uint32_t* length) { const auto socket = Lookup(descriptor); if (!socket) return -1; + if (socket->family == GuestUnix && socket->peer.expired()) return Fail(57); if (!address || !length) return Fail(14); if (socket->family == GuestUnix) return UnnamedAddress(address, length); sockaddr_storage native{}; @@ -1337,6 +1466,9 @@ int APS5_VABI _ioctl_nid_postfix(int descriptor, std::uint64_t request, void* ar } std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength) { + if (address) { + if (const int error = AddressLengthError(address, addressLength)) return Fail(error); + } const auto socket = Lookup(descriptor); if (!socket) return -1; if ((flags & ~GuestSendFlags) != 0) return Fail(45); @@ -1344,14 +1476,14 @@ std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, st if (!buffer && length) return Fail(14); if (const int waited = WaitForTransfer(*socket, true, flags)) return Fail(waited); int result; - if (!address) { - if (addressLength != 0) return Fail(22); + if (!address || (socket->type == 1 && PeerConnected(*socket))) { + if (!address && addressLength != 0) return Fail(22); result = ::send(socket->value, static_cast(buffer), static_cast(length), SocketSendFlags(*socket, flags)); } else { sockaddr_storage native{}; socklen_t size; - if (!Address(address, addressLength, native, size)) return -1; - if (native.ss_family != (socket->family == 2 ? AF_INET : AF_INET6)) return Fail(47); + if (!Address(socket->family, address, addressLength, native, size, true)) return -1; + if (const auto answered = ZeroPortSend(*socket, native, length); answered != NotAnswered) return answered; result = static_cast(::sendto(socket->value, static_cast(buffer), static_cast(length), SocketSendFlags(*socket, flags), reinterpret_cast(&native), size)); } @@ -1366,11 +1498,16 @@ std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::u if ((!buffer && length) || (address && !addressLength)) return Fail(14); const GuestArena::HostWrite destination(length == 0 ? nullptr : buffer, static_cast(length)); if (!destination.Open()) return Fail(14); + if (const int answered = ReceiveAnsweredHere(*socket, length); answered >= 0) { + if (answered > 0) return Fail(answered); + if (address) *addressLength = 0; + return 0; + } sockaddr_storage native{}; socklen_t size = sizeof(native); if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); auto result = ::recvfrom(socket->value, static_cast(buffer), static_cast(length), - NativeReceiveFlags(flags), reinterpret_cast(&native), &size); + NativeReceiveFlags(flags, socket->type), reinterpret_cast(&native), &size); #ifdef _WIN32 if (result < 0 && socket->type == 2 && WSAGetLastError() == WSAEMSGSIZE) result = static_cast(length); #endif @@ -1385,19 +1522,7 @@ std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::u } return result; } -struct GuestIovec { - void* base; - std::uint64_t length; -}; -struct GuestMsghdr { - void* name; - std::uint32_t nameLength; - GuestIovec* iov; - int iovLength; - void* control; - std::uint32_t controlLength; - int flags; -}; + static_assert(sizeof(GuestMsghdr) == 48 && offsetof(GuestMsghdr, iov) == 16 && offsetof(GuestMsghdr, control) == 32 && offsetof(GuestMsghdr, flags) == 44); static std::int64_t MessageLength(const GuestMsghdr* message) { if (!message) return Fail(14); @@ -1499,8 +1624,8 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, GuestMsghdr header{}; if (!message || !ReadGuest(&header, message, sizeof(header))) return Fail(14); if (header.iovLength < 0 || header.iovLength > 1024) return Fail(40); - std::vector vectors(static_cast(header.iovLength)); - if (!ReadGuest(vectors.data(), header.iov, vectors.size() * sizeof(GuestIovec))) return Fail(14); + std::vector vectors(static_cast(header.iovLength)); + if (!ReadGuest(vectors.data(), header.iov, vectors.size() * sizeof(KernelIovec))) return Fail(14); header.iov = vectors.data(); const auto total = MessageLength(&header); if (total < 0) return -1; @@ -1515,22 +1640,26 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, return Fail(55); } sockaddr_storage native{}; - socklen_t size = sizeof(native); - if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); - std::int64_t received = ::recvfrom(socket->value, buffer.data(), static_cast(buffer.size()), - NativeReceiveFlags(flags), reinterpret_cast(&native), &size); - if (received < 0) { + socklen_t size = 0; + std::int64_t received = 0; + if (total != 0 || socket->type == 1 || ReadableNow(*socket)) { + size = sizeof(native); + if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); + received = ::recvfrom(socket->value, buffer.data(), static_cast(buffer.size()), + NativeReceiveFlags(flags, socket->type), reinterpret_cast(&native), &size); + if (received < 0) { #ifdef _WIN32 - if (!records || WSAGetLastError() != WSAEMSGSIZE) return Fail(NativeError()); - received = static_cast(buffer.size()); + if (!records || WSAGetLastError() != WSAEMSGSIZE) return Fail(NativeError()); + received = static_cast(buffer.size()); #else - return Fail(NativeError()); + return Fail(NativeError()); #endif + } } - header.flags = 0; + header.flags = flags; if (received > total) { received = total; - header.flags = 0x10; + header.flags |= 0x10; } std::size_t offset = 0; for (const auto& vector : vectors) { @@ -1556,15 +1685,10 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, if (!WriteGuest(reinterpret_cast(message) + results, reinterpret_cast(&header) + results, sizeof(header) - results)) return Fail(14); return received; } -struct GuestSendfileHeaders { - GuestIovec* headers; - int headerCount; - GuestIovec* trailers; - int trailerCount; -}; + static_assert(sizeof(GuestSendfileHeaders) == 32 && offsetof(GuestSendfileHeaders, trailers) == 16); -std::int64_t APS5_VABI sceKernelPread(int descriptor, void* buffer, std::size_t length, std::int64_t offset); -static bool SendfileIovecs(const GuestIovec* iov, int count) { + +static bool SendfileIovecs(const KernelIovec* iov, int count) { if (!iov) return true; if (count < 0 || count > 1024) return false; std::uint64_t total = 0; @@ -1586,7 +1710,7 @@ static int SendAll(const Socket& socket, const void* data, std::uint64_t length, } return 0; } -static int SendIovecs(const Socket& socket, const GuestIovec* iov, int count, std::int64_t& sent) { +static int SendIovecs(const Socket& socket, const KernelIovec* iov, int count, std::int64_t& sent) { for (int i = 0; iov && i < count; ++i) if (const int error = SendAll(socket, iov[i].base, iov[i].length, sent)) return error; return 0; @@ -1597,6 +1721,12 @@ int APS5_VABI sendfile_nid_postfix(int file, int descriptor, std::int64_t offset if (offset < 0) return Fail(22); if (headers && (!SendfileIovecs(headers->headers, headers->headerCount) || !SendfileIovecs(headers->trailers, headers->trailerCount))) return Fail(22); + std::int64_t sent = 0; + const auto finish = [&](int error) { + if (sbytes) *sbytes = sent; + return error ? Fail(error) : 0; + }; + if (DescriptorIsOpen_nid_no_patch(file) && !File::IsVnode(file) && !File::IsSharedMemoryDescriptor_nid_no_patch(file)) return finish(22); std::vector chunk; try { chunk.resize(0x10000); @@ -1610,14 +1740,9 @@ int APS5_VABI sendfile_nid_postfix(int file, int descriptor, std::int64_t offset std::uint64_t remaining = length ? length : UINT64_MAX; auto received = read(offset, remaining); if (received == -9) return Fail(9); - std::int64_t sent = 0; - const auto finish = [&](int error) { - if (sbytes) *sbytes = sent; - return error ? Fail(error) : 0; - }; if (received < 0) return finish(received == -21 || received == -29 ? 22 : static_cast(-received)); const auto socket = Lookup(descriptor); - if (!socket) return finish(9); + if (!socket) return finish(*__error_nid_postfix()); if (socket->type != 1) return finish(22); sockaddr_storage peer{}; socklen_t size = sizeof(peer); @@ -1643,6 +1768,7 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c std::vector native; std::vector owners; std::vector pipes; + std::vector kqueues; int ready = 0; for (std::uint32_t i = 0; i < count; ++i) { auto& entry = descriptors[i]; @@ -1661,19 +1787,23 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c ++ready; continue; } + if (IsKqueueDescriptor(entry.descriptor)) { + kqueues.push_back(i); + continue; + } if (File::IsRandomDevice(entry.descriptor)) { const short readable = static_cast(entry.events & GuestPollReadable); entry.revents = readable != 0 ? readable : entry.events; if (entry.revents) ++ready; continue; } - if (entry.descriptor <= 2) { - entry.revents = static_cast(entry.descriptor == 0 ? 0 : entry.events & GuestPollWritable); + if (File::IsVnode(entry.descriptor)) { + entry.revents = static_cast(entry.events & GuestPollVnodeReady); if (entry.revents) ++ready; continue; } - if (File::IsVnode(entry.descriptor)) { - entry.revents = static_cast(entry.events & GuestPollVnodeReady); + if (entry.descriptor <= 2) { + entry.revents = static_cast(entry.descriptor == 0 ? 0 : entry.events & GuestPollWritable); if (entry.revents) ++ready; continue; } @@ -1683,7 +1813,7 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c #else if (!IsPipe(entry.descriptor, entry.events)) #endif - throw std::runtime_error("poll: descriptor " + std::to_string(entry.descriptor) + " is neither a socket, a pipe, a file nor a directory"); + throw std::runtime_error("poll: descriptor " + std::to_string(entry.descriptor) + " is neither a socket, a pipe, a file, a directory nor a kqueue"); pipes.push_back(i); continue; } @@ -1706,7 +1836,7 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c owners.push_back(index); } #endif - if (native.empty() && pipes.empty()) { + if (native.empty() && pipes.empty() && kqueues.empty()) { if (ready || timeout == 0) return ready; if (timeout < 0) throw std::runtime_error("poll: an infinite wait without sockets or pipes never returns"); PreciseSleepUs(static_cast(timeout) * 1000); @@ -1714,6 +1844,9 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c } const auto collect = [&](std::size_t i, bool pipe) { auto& entry = descriptors[owners[i]]; +#ifdef _WIN32 + if (!pipe) native[i].revents = ClosedStreamEvents(entry.descriptor, native[i].revents, native[i].events); +#endif for (const auto& flag : RequestFlags) if ((entry.events & flag.guest) && (native[i].revents & flag.native)) entry.revents = static_cast(entry.revents | flag.guest); @@ -1722,20 +1855,40 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c if (pipe && (entry.revents & GuestPollError)) entry.revents = static_cast((entry.revents & ~GuestPollError) | GuestPollHangUp); if (pipe && (entry.revents & GuestPollHangUp)) entry.revents = static_cast((entry.revents & ~GuestPollWritable) | (entry.events & GuestPollReadable)); +#ifndef _WIN32 + if (entry.revents & GuestPollHangUp) entry.revents = static_cast(entry.revents & ~GuestPollWritable); +#endif if (entry.revents) ++ready; }; -#ifdef _WIN32 const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout < 0 ? 0 : timeout); +#ifdef _WIN32 + const bool sliced = !pipes.empty() || !kqueues.empty(); +#else + const bool sliced = !kqueues.empty(); +#endif for (;;) { - int pipeReady = 0; + int checkedReady = 0; +#ifdef _WIN32 for (const auto index : pipes) { auto& entry = descriptors[index]; PipeReadiness(entry.descriptor, entry.events, entry.revents); - if (entry.revents) ++pipeReady; + if (entry.revents) ++checkedReady; + } +#endif + for (const auto index : kqueues) { + auto& entry = descriptors[index]; + const auto readable = KqueueReadable(entry.descriptor); + if (!readable && DescriptorIsOpen_nid_no_patch(entry.descriptor)) { + if (timeout < 0) return poll_nid_postfix(descriptors, count, -1); + const auto left = std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()).count(); + return poll_nid_postfix(descriptors, count, static_cast(std::max(0, (left + 999) / 1000))); + } + entry.revents = !readable ? GuestPollInvalid : *readable ? static_cast(entry.events & GuestPollReadable) : short{0}; + if (entry.revents) ++checkedReady; } int slice = 0; - if (!ready && !pipeReady) { - if (pipes.empty()) slice = timeout; + if (!ready && !checkedReady) { + if (!sliced) slice = timeout; else if (timeout < 0) slice = 1; else { const auto left = std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()).count(); @@ -1744,8 +1897,12 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c } if (!native.empty()) { for (auto& request : native) request.revents = 0; +#ifdef _WIN32 if (WSAPoll(native.data(), static_cast(native.size()), slice) < 0) return Fail(NativeError()); - } else if (!ready && !pipeReady) { +#else + if (::poll(native.data(), static_cast(native.size()), slice) < 0) return Fail(NativeError()); +#endif + } else if (!ready && !checkedReady) { const auto wake = std::chrono::steady_clock::now() + std::chrono::milliseconds(1); PreciseSleepUntil(timeout < 0 ? wake : std::min(deadline, wake)); } @@ -1753,22 +1910,23 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c for (const auto& request : native) if (request.revents) ++socketReady; const bool expired = timeout >= 0 && std::chrono::steady_clock::now() >= deadline; - if (ready || pipeReady || socketReady || expired || pipes.empty()) { - ready += pipeReady; + if (ready || checkedReady || socketReady || expired || !sliced) { + ready += checkedReady; +#ifdef _WIN32 for (std::size_t i = 0; i < native.size(); ++i) collect(i, false); +#else + for (std::size_t i = 0; i < native.size(); ++i) collect(i, i >= socketCount); +#endif return ready; } } -#else - const int wait = ready ? 0 : timeout; - const int result = ::poll(native.data(), static_cast(native.size()), wait); - if (result < 0) return Fail(NativeError()); - for (std::size_t i = 0; i < native.size(); ++i) collect(i, i >= socketCount); - return ready; -#endif } } +extern "C" bool GuestSocketReadGuest_nid_no_patch(void* destination, const void* source, std::size_t bytes) { + return ::ReadGuest(destination, source, bytes); +} + std::int64_t GuestSockets::Read(int descriptor, void* buffer, std::size_t length) { if (length == 0) return IsOpen(descriptor) ? 0 : Fail(9); return recv_nid_postfix(descriptor, buffer, std::min(length, INT_MAX), 0); @@ -1824,3 +1982,44 @@ std::int64_t GuestSockets::Writev(int descriptor, const Iovec* iov, int iovcnt) } return Write(descriptor, buffer.data(), total); } + +extern "C" { +int APS5_VABI _setsockopt_nid_postfix(int descriptor, int level, int option, const void* value, std::uint32_t length) { + return setsockopt_nid_postfix(descriptor, level, option, value, length); +} +int APS5_VABI _getsockopt_nid_postfix(int descriptor, int level, int option, void* value, std::uint32_t* length) { + return getsockopt_nid_postfix(descriptor, level, option, value, length); +} +int APS5_VABI _connect_nid_postfix(int descriptor, const void* address, std::uint32_t length) { + return connect_nid_postfix(descriptor, address, length); +} +int APS5_VABI _listen_nid_postfix(int descriptor, int backlog) { + return listen_nid_postfix(descriptor, backlog); +} +int APS5_VABI _accept_nid_postfix(int descriptor, void* address, std::uint32_t* length) { + return accept_nid_postfix(descriptor, address, length); +} +int APS5_VABI _bind_nid_postfix(int descriptor, const void* address, std::uint32_t length) { + return bind_nid_postfix(descriptor, address, length); +} +int APS5_VABI _getsockname_nid_postfix(int descriptor, void* address, std::uint32_t* length) { + return getsockname_nid_postfix(descriptor, address, length); +} +int APS5_VABI _getpeername_nid_postfix(int descriptor, void* address, std::uint32_t* length) { + return getpeername_nid_postfix(descriptor, address, length); +} +std::int64_t APS5_VABI _sendto_nid_postfix(int descriptor, const void* buffer, std::uint64_t length, + int flags, const void* address, std::uint32_t addressLength) { + return sendto_nid_postfix(descriptor, buffer, length, flags, address, addressLength); +} +std::int64_t APS5_VABI _recvfrom_nid_postfix(int descriptor, void* buffer, std::uint64_t length, + int flags, void* address, std::uint32_t* addressLength) { + return recvfrom_nid_postfix(descriptor, buffer, length, flags, address, addressLength); +} +std::int64_t APS5_VABI _sendmsg_nid_postfix(int descriptor, const GuestMsghdr* message, int flags) { + return sendmsg_nid_postfix(descriptor, message, flags); +} +std::int64_t APS5_VABI _recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, int flags) { + return recvmsg_nid_postfix(descriptor, message, flags); +} +} diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index 3955668c7f..a83f3e3c2d 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include #include #include #include @@ -15,8 +18,6 @@ #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { constexpr int SYNC_ON_ADDRESS_OK = 0; @@ -55,12 +56,23 @@ struct UserSemaphore { }; struct AddressWaiter { - TimedWait::Condition condition; + TimedWait::SingleWaiterCondition condition; bool woken = false; }; -std::mutex g_waitersLock; -std::unordered_map> g_waiters; +constexpr std::size_t kBucketCount = 64; + +struct alignas(64) WaiterBucket { + std::mutex mutex; + std::unordered_map> waiters; +}; + +std::array g_buckets; + +WaiterBucket& BucketOf(std::uintptr_t address) { + const auto hashed = (address >> 2) * UINT64_C(0x9e3779b97f4a7c15); + return g_buckets[hashed >> 58]; +} template bool IsAlignedAddress(std::uintptr_t address) { @@ -69,27 +81,24 @@ bool IsAlignedAddress(std::uintptr_t address) { template void DeferringDelivery(TFunction function) { -#ifdef _WIN32 { TimedWait::DeliveryDeferral deferral; function(); } TimedWait::DeliverDeferred(); -#else - function(); -#endif } template std::optional WaitOnKeyLocked(std::uintptr_t key, TReady ready, std::optional deadlineNanos, bool& woken) { - std::unique_lock lock(g_waitersLock); + auto& bucket = BucketOf(key); + std::unique_lock lock(bucket.mutex); if (ready()) { woken = true; return std::nullopt; } AddressWaiter waiter; - auto& queue = g_waiters[key]; + auto& queue = bucket.waiters[key]; const auto position = queue.insert(queue.end(), &waiter); const auto isWoken = [&] { return waiter.woken; }; const auto waitStart = std::chrono::steady_clock::now(); @@ -104,7 +113,7 @@ std::optional WaitOnKeyLocked(std::uintptr_t key, TRea if (!woken) { queue.erase(position); if (queue.empty()) { - g_waiters.erase(key); + bucket.waiters.erase(key); } } return waited; @@ -133,9 +142,10 @@ int WaitOnAddress(TValue* address, TValue expected, const KernelUseconds* timeou } void WakeAddressLocked(std::uintptr_t key, std::int32_t count, bool wakesAtLeastOne) { - std::lock_guard lock(g_waitersLock); - const auto entry = g_waiters.find(key); - if (entry == g_waiters.end()) { + auto& bucket = BucketOf(key); + std::lock_guard lock(bucket.mutex); + const auto entry = bucket.waiters.find(key); + if (entry == bucket.waiters.end()) { return; } auto& queue = entry->second; @@ -146,7 +156,7 @@ void WakeAddressLocked(std::uintptr_t key, std::int32_t count, bool wakesAtLeast waiter->condition.NotifyOne(); } if (queue.empty()) { - g_waiters.erase(entry); + bucket.waiters.erase(entry); } } @@ -191,6 +201,7 @@ int UmtxDeadline(const void* sizeArgument, const void* timeArgument, std::option } else { KernelTimespec now{}; clock_gettime_nid_postfix(static_cast(time.clockId), &now); + remaining = SaturatingNanos(time.timeout.tv_sec - now.tv_sec, time.timeout.tv_nsec - now.tv_nsec); } if (remaining < UNBOUNDED_WAIT_NANOS) { @@ -241,9 +252,10 @@ int UmtxNwakePrivate(void* object, std::uint64_t value) { } void WakeSemaphoreLocked(std::uintptr_t key, UserSemaphore* semaphore) { - std::lock_guard lock(g_waitersLock); - const auto entry = g_waiters.find(key); - if (entry == g_waiters.end()) { + auto& bucket = BucketOf(key); + std::lock_guard lock(bucket.mutex); + const auto entry = bucket.waiters.find(key); + if (entry == bucket.waiters.end()) { return; } auto& queue = entry->second; @@ -255,7 +267,7 @@ void WakeSemaphoreLocked(std::uintptr_t key, UserSemaphore* semaphore) { waiter->woken = true; waiter->condition.NotifyOne(); if (queue.empty()) { - g_waiters.erase(entry); + bucket.waiters.erase(entry); } } diff --git a/core/libs/prx/libkernel/System/src/Coredump.cpp b/core/libs/prx/libkernel/System/src/Coredump.cpp index 3bc229914c..7f041e1b61 100644 --- a/core/libs/prx/libkernel/System/src/Coredump.cpp +++ b/core/libs/prx/libkernel/System/src/Coredump.cpp @@ -1,43 +1,43 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include #include +#include #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" // The handler is recorded but never invoked: host crashes are not turned into guest core dumps. static std::atomic g_coredumpHandler{0}; static std::atomic g_coredumpContext{0}; +static std::atomic coredumpSdkVersion{0}; static constexpr int SCE_COREDUMP_ERROR_INVALID_ARGUMENT = static_cast(0x81180000u); static constexpr int SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER = static_cast(0x81180003u); -static void requireRegionOutsideHandler(const char* function, const void* mem, size_t size) { - if (mem == nullptr || size == 0 || size > UINTPTR_MAX - reinterpret_cast(mem)) - throw std::runtime_error(std::string(function) + ": null, empty or wrapping region outside the core dump handler"); -} - -static int attachOutsideCoredumpHandler(const char* function, const void* mem, size_t size) { - requireRegionOutsideHandler(function, mem, size); - return SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; -} - -static void requireTextOutsideHandler(const char* function, const char* argument, const char* text) { - if (text == nullptr) throw std::runtime_error(std::string(function) + ": null " + argument + " outside the core dump handler"); +static bool validUserFileFlags(std::uint32_t userValue) { + return coredumpSdkVersion.load(std::memory_order_relaxed) < 0x06500000 || (userValue & 0xc0000000u) != 0x40000000u; } extern "C" { int APS5_VABI sceCoredumpRegisterCoredumpHandler(uint64_t handler, size_t stack_size, uint64_t context) { - (void)stack_size; + if (g_coredumpHandler.load(std::memory_order_relaxed)) return static_cast(0x81180002u); + if (!handler || stack_size < 0x4000 || stack_size >= 0x20000000) return SCE_COREDUMP_ERROR_INVALID_ARGUMENT; + const auto* parameters = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t sdkVersion; + std::memcpy(&sdkVersion, parameters + 16, sizeof(sdkVersion)); + coredumpSdkVersion.store(sdkVersion, std::memory_order_relaxed); g_coredumpHandler.store(handler, std::memory_order_relaxed); g_coredumpContext.store(context, std::memory_order_relaxed); return 0; } int APS5_VABI sceCoredumpUnregisterCoredumpHandler(void) { + if (!g_coredumpHandler.load(std::memory_order_relaxed)) return static_cast(0x81180001u); g_coredumpHandler.store(0, std::memory_order_relaxed); g_coredumpContext.store(0, std::memory_order_relaxed); return 0; @@ -49,10 +49,9 @@ int APS5_VABI sceKernelDebugWriteCppExceptionInfo(const void* exception, uint64_ return 0; } - -int APS5_VABI sceCoredumpAttachUserFile(uint32_t userValue, const char* path) { - (void)userValue; - requireTextOutsideHandler(__func__, "path", path); +int APS5_VABI sceCoredumpAttachUserFile(std::uint32_t userValue, const char* path) { + if (!path || !validUserFileFlags(userValue)) return SCE_COREDUMP_ERROR_INVALID_ARGUMENT; + if (static_cast(std::strlen(path)) < 0) return 0; return SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; } @@ -62,15 +61,14 @@ int APS5_VABI sceCoredumpGetStopInfoGpu_Agc(void* info, size_t size) { return SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; } -int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(uint32_t userValue, const void* mem, size_t size, const char* name) { - (void)userValue; - requireRegionOutsideHandler(__func__, mem, size); - requireTextOutsideHandler(__func__, "name", name); - return SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; +int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(std::uint32_t userValue, const void* mem, std::int32_t size, const char* name) { + (void)mem; + if (!name || !validUserFileFlags(userValue)) return SCE_COREDUMP_ERROR_INVALID_ARGUMENT; + return size < 0 ? 0 : SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; } -int APS5_VABI sceCoredumpSetUserDataType(void) { - NotImplemented_nid_no_patch("Uxqkdta7wEg"); +int APS5_VABI sceCoredumpSetUserDataType(std::int32_t type) { + (void)type; return 0; } @@ -83,21 +81,17 @@ void APS5_VABI sceCoredumpDebugTextOut(const char* str, int len) { } int64_t APS5_VABI sceCoredumpWriteUserData(const void* data, size_t size) { - requireRegionOutsideHandler(__func__, data, size); + if (!data || size > INT32_MAX) return SCE_COREDUMP_ERROR_INVALID_ARGUMENT; return SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; } int APS5_VABI sceCoredumpGetStopInfoCpu(void* info, size_t size) { - const auto address = reinterpret_cast(info); - if (info == nullptr || size != 16 || address % alignof(uint64_t) != 0 || size > UINTPTR_MAX - address) - throw std::runtime_error(std::string(__func__) + ": null, unaligned, wrapping or unsupported CPU stop-info buffer outside the core dump handler"); + if (!info || (size != 8 && size != 16)) return SCE_COREDUMP_ERROR_INVALID_ARGUMENT; return SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; } int APS5_VABI sceCoredumpGetThreadContextInfo(void* threadContextInfo, size_t threadContextInfoSize) { - const auto address = reinterpret_cast(threadContextInfo); - if (threadContextInfo == nullptr || threadContextInfoSize != 144 || address % alignof(uint64_t) != 0 || threadContextInfoSize > UINTPTR_MAX - address) - throw std::runtime_error(std::string(__func__) + ": null, unaligned, wrapping or unsupported thread context buffer outside the core dump handler"); + if (!threadContextInfo || threadContextInfoSize != 144) return SCE_COREDUMP_ERROR_INVALID_ARGUMENT; return SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; } @@ -110,13 +104,15 @@ int APS5_VABI sceCoredumpConfigDumpMode(int mode) { return mode == 1 ? 0 : SCE_COREDUMP_ERROR_INVALID_ARGUMENT; } -int APS5_VABI sceCoredumpAttachUserMemoryFile(uint32_t userValue, const void* mem, size_t size) { +int APS5_VABI sceCoredumpAttachUserMemoryFile(std::uint32_t userValue, const void* mem, std::int32_t size) { (void)userValue; - return attachOutsideCoredumpHandler(__func__, mem, size); + (void)mem; + return size < 0 ? 0 : SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; } -int APS5_VABI sceCoredumpAttachMemoryRegion(uint32_t userValue, const void* mem, size_t size) { +int APS5_VABI sceCoredumpAttachMemoryRegion(std::uint32_t userValue, const void* mem, std::int32_t size) { (void)userValue; - return attachOutsideCoredumpHandler(__func__, mem, size); + (void)mem; + return size < 0 ? 0 : SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER; } } diff --git a/core/libs/prx/libkernel/System/src/Error.cpp b/core/libs/prx/libkernel/System/src/Error.cpp index a0eb19f9b6..5b3452fd77 100644 --- a/core/libs/prx/libkernel/System/src/Error.cpp +++ b/core/libs/prx/libkernel/System/src/Error.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include diff --git a/core/libs/prx/libkernel/System/src/Exception.cpp b/core/libs/prx/libkernel/System/src/Exception.cpp index b7eb4f65f7..73b40090f1 100644 --- a/core/libs/prx/libkernel/System/src/Exception.cpp +++ b/core/libs/prx/libkernel/System/src/Exception.cpp @@ -1,9 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/Time/include/TimedWait.hpp" #include #include #include @@ -26,7 +28,6 @@ #include #endif -extern "C" Pthread APS5_VABI scePthreadSelf(); #ifdef _WIN32 extern "C" void Aps5RedirectedEntryStub(); extern "C" [[noreturn]] void Aps5RedirectedExit(CONTEXT* context, const void* vectors, void (*restore)(CONTEXT*, EXCEPTION_RECORD*)); @@ -193,6 +194,7 @@ constexpr std::size_t RedZone = 128; constexpr std::size_t HomeArea = 32; constexpr auto RetryLimit = std::chrono::seconds(1); constexpr auto HostCodeLimit = std::chrono::milliseconds(50); +constexpr auto HostCodeResample = std::chrono::microseconds(20); constexpr std::size_t VectorBytes = 16 * 32; struct Delivery { @@ -426,7 +428,9 @@ bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) { delivery.context.ContextFlags = CONTEXT_FULL | CONTEXT_SEGMENTS; if (!GetThreadContext(native, &delivery.context)) { ResumeThread(native); - throw std::runtime_error("sceKernelRaiseException: cannot read the target thread context"); + if (std::chrono::steady_clock::now() >= retryDeadline) throw std::runtime_error("sceKernelRaiseException: cannot read the target thread context"); + SwitchToThread(); + continue; } if (thread->waitCount.load(std::memory_order_seq_cst) > 0) { const bool accepted = QueueUserAPC(WaitingEntry, native, reinterpret_cast(queued.get())) != 0; @@ -447,7 +451,12 @@ bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) { continue; } if (restoring && std::chrono::steady_clock::now() >= retryDeadline) throw std::runtime_error("sceKernelRaiseException: the target thread stayed inside NtContinue or winpthreads for 1 s"); - SwitchToThread(); + if (running == CodeKind::Host) { + const auto resample = std::chrono::steady_clock::now() + HostCodeResample; + while (std::chrono::steady_clock::now() < resample) YieldProcessor(); + } else { + SwitchToThread(); + } } const DWORD64 slot = (delivery.context.Rsp - RedZone - sizeof(Delivery)) & ~static_cast(15); if (!StackWritable(slot - HomeArea - 8, delivery.context.Rsp - RedZone)) { @@ -519,7 +528,8 @@ void ToHost(const GuestMcontext& m, mcontext_t& host) { if (host.fpregs != nullptr) std::memcpy(host.fpregs, m.fpstate, FxsaveRegisterBytes); } -void Deliver(int, siginfo_t*, void* context) { +void Deliver(int signum, siginfo_t*, void* context) { + if (TimedWait::DeferDelivery(signum)) return; const auto handler = Handler(RaisedSignal); if (handler == nullptr) { static constexpr char message[] = "sceKernelRaiseException: the handler was removed before the signal was delivered\n"; @@ -671,6 +681,7 @@ int APS5_VABI sceKernelRaiseException(Pthread thread, int signum) { const auto handler = Handler(signum); if (handler == nullptr) throw std::runtime_error("sceKernelRaiseException: no handler installed for the signal"); #ifdef _WIN32 + if (thread->suspended.load(std::memory_order_acquire)) throw std::runtime_error("sceKernelRaiseException: the target thread is still suspended at creation"); return RaiseOn(thread, handler, signum) ? 0 : SCE_KERNEL_ERROR_ESRCH; #else return RaiseOn(thread) ? 0 : SCE_KERNEL_ERROR_ESRCH; diff --git a/core/libs/prx/libkernel/System/src/Getlogin.cpp b/core/libs/prx/libkernel/System/src/Getlogin.cpp new file mode 100644 index 0000000000..c795555bcb --- /dev/null +++ b/core/libs/prx/libkernel/System/src/Getlogin.cpp @@ -0,0 +1,12 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +extern "C" char* APS5_VABI getlogin_nid_postfix(void) { + static char logname[33]; + if (const int error = getlogin_r_nid_postfix(logname, static_cast(sizeof(logname))); error != 0) { + *__error_nid_postfix() = error; + return nullptr; + } + return logname[0] != '\0' ? logname : nullptr; +} diff --git a/core/libs/prx/libkernel/System/src/Login.cpp b/core/libs/prx/libkernel/System/src/Login.cpp index 80f9eaabc9..82f71f218b 100644 --- a/core/libs/prx/libkernel/System/src/Login.cpp +++ b/core/libs/prx/libkernel/System/src/Login.cpp @@ -1,7 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include +#include +#include +#include #include #ifdef _WIN32 #include @@ -12,7 +16,16 @@ #endif namespace { +std::optional g_loginForTest; + int ReadLogin(char* output, int size) { + if (g_loginForTest) { + if (g_loginForTest->empty()) throw std::runtime_error("getlogin_r: host effective user has no account name"); + const std::size_t length = g_loginForTest->size() + 1; + if (length > static_cast(size)) return 34; + std::memcpy(output, g_loginForTest->c_str(), length); + return 0; + } #ifdef _WIN32 std::array name{}; DWORD count = static_cast(name.size()); @@ -43,6 +56,11 @@ int ReadLogin(char* output, int size) { } } +extern "C" void LoginNameForTest_nid_no_patch(const char* name) { + if (name == nullptr) g_loginForTest.reset(); + else g_loginForTest = name; +} + extern "C" int APS5_VABI getlogin_r_nid_postfix(char* output, int size) { if (size <= 0) return 34; if (output == nullptr) return 14; diff --git a/core/libs/prx/libkernel/System/src/Notification.cpp b/core/libs/prx/libkernel/System/src/Notification.cpp index 9d94d2b291..df976eac7f 100644 --- a/core/libs/prx/libkernel/System/src/Notification.cpp +++ b/core/libs/prx/libkernel/System/src/Notification.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/File.hpp" #include diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index 348464a177..42a4926f4c 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX @@ -8,16 +10,28 @@ #else #include #include +#ifdef __linux__ +#include +#elif defined(__APPLE__) +#include +#include +#endif #endif #if defined(__linux__) && defined(__x86_64__) #include #include #endif +#ifdef _WIN32 +#include "prx/libc/include/ChildProcesses.hpp" +#else +#include +#endif #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/HostEntropy.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" @@ -44,12 +58,10 @@ #include #endif -extern "C" int* APS5_VABI __error_nid_postfix(); -extern "C" int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb); - namespace { constexpr int sceInvalidArgument = static_cast(0x80020016u); +constexpr int errnoOperationNotPermitted = 1; constexpr int errnoNoSuchProcess = 3; constexpr int errnoNoChild = 10; constexpr int errnoBadAddress = 14; @@ -59,6 +71,86 @@ std::mutex supplementaryGroupsMutex; std::vector supplementaryGroups{0}; constexpr unsigned freebsdWaitOptions = 0x8000003Fu; +bool StoreGuestId(std::uint32_t* output) { + if (output == nullptr) return true; + const auto address = reinterpret_cast(output); + if (address > std::numeric_limits::max() - sizeof(*output)) return false; + const auto end = address + sizeof(*output); + for (auto cursor = address; cursor < end;) { + VirtualQueryInfo info{}; + if (sceKernelVirtualQuery(reinterpret_cast(cursor), 0, &info, sizeof(info)) != 0 || + info.end <= cursor || (info.protection & 2) == 0) + return false; + cursor = std::min(end, info.end); + } + const GuestArena::HostWrite guard(output, sizeof(*output)); + if (!guard.Open()) return false; + constexpr std::uint32_t zero = 0; +#ifdef _WIN32 + SIZE_T written = 0; + return WriteProcessMemory(GetCurrentProcess(), output, &zero, sizeof(zero), &written) != 0 && written == sizeof(zero); +#elif defined(__linux__) + iovec local{const_cast(&zero), sizeof(zero)}; + iovec remote{output, sizeof(zero)}; + return ::process_vm_writev(::getpid(), &local, 1, &remote, 1, 0) == static_cast(sizeof(zero)); +#elif defined(__APPLE__) + return ::mach_vm_write(::mach_task_self(), reinterpret_cast(output), + reinterpret_cast(const_cast(&zero)), sizeof(zero)) == KERN_SUCCESS; +#else + *output = zero; + return true; +#endif +} + +bool GuestRange(const void* pointer, std::size_t bytes) { + const auto start = reinterpret_cast(pointer); + return start != 0 && bytes <= std::numeric_limits::max() - start; +} + +bool ReadGuest(void* destination, const void* source, std::size_t bytes) { + if (bytes == 0) return true; + if (!GuestRange(source, bytes)) return false; + GuestArena::GuestArenaPrepareHostRead_nid_no_patch(source, bytes); +#ifdef _WIN32 + SIZE_T copied = 0; + return ReadProcessMemory(GetCurrentProcess(), source, destination, bytes, &copied) != 0 && copied == bytes; +#elif defined(__linux__) + iovec local{destination, bytes}; + iovec remote{const_cast(source), bytes}; + const auto copied = ::process_vm_readv(::getpid(), &local, 1, &remote, 1, 0); + if (copied < 0 && errno != EFAULT) throw std::runtime_error("credentials: process_vm_readv failed with errno " + std::to_string(errno)); + return copied >= 0 && static_cast(copied) == bytes; +#elif defined(__APPLE__) + mach_vm_size_t copied = 0; + return ::mach_vm_read_overwrite(::mach_task_self(), reinterpret_cast(source), bytes, + reinterpret_cast(destination), &copied) == KERN_SUCCESS && copied == bytes; +#else + throw std::runtime_error("credentials: checked guest reads are not implemented on this host"); +#endif +} + +bool WriteGuest(void* destination, const void* source, std::size_t bytes) { + if (bytes == 0) return true; + if (!GuestRange(destination, bytes)) return false; + const GuestArena::HostWrite guard(destination, bytes); + if (!guard.Open()) return false; +#ifdef _WIN32 + SIZE_T copied = 0; + return WriteProcessMemory(GetCurrentProcess(), destination, source, bytes, &copied) != 0 && copied == bytes; +#elif defined(__linux__) + iovec local{const_cast(source), bytes}; + iovec remote{destination, bytes}; + const auto copied = ::process_vm_writev(::getpid(), &local, 1, &remote, 1, 0); + if (copied < 0 && errno != EFAULT) throw std::runtime_error("credentials: process_vm_writev failed with errno " + std::to_string(errno)); + return copied >= 0 && static_cast(copied) == bytes; +#elif defined(__APPLE__) + return ::mach_vm_write(::mach_task_self(), reinterpret_cast(destination), + reinterpret_cast(source), static_cast(bytes)) == KERN_SUCCESS; +#else + throw std::runtime_error("credentials: checked guest writes are not implemented on this host"); +#endif +} + std::atomic gpoBits{0}; constexpr std::array openPsId{'A', 'n', 'y', 'P', 'S', '5', 'O', 'p', 'e', 'n', 'P', 's', 'I', 'd', 0, 1}; @@ -158,6 +250,7 @@ constexpr int guestRlimitCount = 15; constexpr std::int64_t guestRlimitInfinity = std::numeric_limits::max(); constexpr std::int64_t guestStackLimit = 0x200000; constexpr std::int64_t guestProcessLimit = 1604; +constexpr std::int64_t guestProcessLimitPerUser = 1443; #ifdef _WIN32 std::int64_t hostMemoryLimit() { @@ -183,39 +276,13 @@ std::int64_t toGuestLimit(rlim_t value) { } -extern "C" int* APS5_VABI __error_nid_postfix(); - -struct GuestResourceLimit { - std::int64_t rlim_cur; - std::int64_t rlim_max; -}; - namespace { std::mutex openFileLimitMutex; std::optional guestOpenFileLimit; +std::mutex processLimitMutex; +GuestResourceLimit guestProcessResourceLimit{guestProcessLimit, guestProcessLimit}; } -struct GuestResourceUsage { - KernelTimeval ru_utime; - KernelTimeval ru_stime; - std::int64_t ru_maxrss; - std::int64_t ru_ixrss; - std::int64_t ru_idrss; - std::int64_t ru_isrss; - std::int64_t ru_minflt; - std::int64_t ru_majflt; - std::int64_t ru_nswap; - std::int64_t ru_inblock; - std::int64_t ru_oublock; - std::int64_t ru_msgsnd; - std::int64_t ru_msgrcv; - std::int64_t ru_nsignals; - std::int64_t ru_nvcsw; - std::int64_t ru_nivcsw; -}; - -extern "C" Pthread APS5_VABI scePthreadSelf(); - extern "C" { // unknown data @@ -253,6 +320,24 @@ int APS5_VABI getsid_nid_postfix(int pid) { return -1; } +int APS5_VABI setpgid_nid_postfix(int pid, int pgid) { + if (pgid < 0) { + *__error_nid_postfix() = errnoInvalidArgument; + return -1; + } + const int self = getpid_nid_postfix(); + if (pid != 0 && pid != self) { + *__error_nid_postfix() = errnoNoSuchProcess; + return -1; + } + *__error_nid_postfix() = errnoOperationNotPermitted; + return -1; +} + +int APS5_VABI setpgrp_nid_postfix(int pid, int pgrp) { + return setpgid_nid_postfix(pid, pgrp); +} + int APS5_VABI getppid_nid_postfix(void) { #ifdef _WIN32 const DWORD self = GetCurrentProcessId(); @@ -314,6 +399,22 @@ int APS5_VABI issetugid_nid_postfix(void) { return 0; } +int APS5_VABI getresuid_nid_postfix(std::uint32_t* ruid, std::uint32_t* euid, std::uint32_t* suid) { + if (!StoreGuestId(ruid) || !StoreGuestId(euid) || !StoreGuestId(suid)) { + *__error_nid_postfix() = errnoBadAddress; + return -1; + } + return 0; +} + +int APS5_VABI getresgid_nid_postfix(std::uint32_t* rgid, std::uint32_t* egid, std::uint32_t* sgid) { + if (!StoreGuestId(rgid) || !StoreGuestId(egid) || !StoreGuestId(sgid)) { + *__error_nid_postfix() = errnoBadAddress; + return -1; + } + return 0; +} + void APS5_VABI exit_nid_postfix(int code) { LibcExit_nid_no_patch(code); } @@ -328,14 +429,60 @@ int APS5_VABI system_nid_postfix(const char* command) { } int APS5_VABI waitpid_nid_postfix(int pid, int* status, int options) { - (void)pid; - (void)status; - *__error_nid_postfix() = (static_cast(options) & ~freebsdWaitOptions) != 0 ? errnoInvalidArgument : errnoNoChild; - return -1; + if ((static_cast(options) & ~freebsdWaitOptions) != 0) { + *__error_nid_postfix() = errnoInvalidArgument; + return -1; + } +#ifdef _WIN32 + int encoded = 0; + const std::int64_t reaped = WaitChildProcess_nid_no_patch(pid, static_cast(options), &encoded); + if (reaped == -2) { + *__error_nid_postfix() = errnoNoChild; + return -1; + } + if (reaped == -1) return -1; + if (reaped == 0) return 0; + if (status != nullptr) *status = encoded; + return static_cast(reaped); +#else + unsigned hostOptions = static_cast(options) & 3u; + if ((static_cast(options) & 4u) != 0) hostOptions |= static_cast(WCONTINUED); + int encoded = 0; + const int result = ::waitpid(pid, &encoded, static_cast(hostOptions)); + if (result < 0) { + *__error_nid_postfix() = errno; + return -1; + } + if (result == 0) return 0; + if (WIFCONTINUED(encoded)) { + encoded = 0x13; + } else if (WIFSIGNALED(encoded) || WIFSTOPPED(encoded)) { + const int hostSignal = WIFSIGNALED(encoded) ? WTERMSIG(encoded) : WSTOPSIG(encoded); + int guestSignal = hostSignal; + switch (hostSignal) { + case 7: guestSignal = 10; break; + case 10: guestSignal = 30; break; + case 12: guestSignal = 31; break; + case 17: guestSignal = 20; break; + case 18: guestSignal = 19; break; + case 19: guestSignal = 17; break; + case 20: guestSignal = 18; break; + case 23: guestSignal = 16; break; + case 29: guestSignal = 23; break; + case 31: guestSignal = 12; break; + case 16: + case 30: + NotImplemented_nid_no_patch("waitpid: a child stopped or died with a signal without a guest equivalent"); + return -1; + default: break; + } + encoded = WIFSTOPPED(encoded) ? ((encoded & ~0xff00) | (guestSignal << 8)) : ((encoded & ~0x7f) | guestSignal); + } + if (status != nullptr) *status = encoded; + return result; +#endif } -int APS5_VABI getrlimit_nid_postfix(int resource, GuestResourceLimit* limit); - int APS5_VABI setrlimit_nid_postfix(int resource, const GuestResourceLimit* limit) { if (limit == nullptr) { *__error_nid_postfix() = guestFault; @@ -356,6 +503,7 @@ int APS5_VABI setrlimit_nid_postfix(int resource, const GuestResourceLimit* limi GuestResourceLimit current{}; getrlimit_nid_postfix(resource, ¤t); + if (resource == guestRlimitNofile) { if (requested.rlim_max <= current.rlim_max && requested.rlim_cur <= requested.rlim_max) { std::lock_guard lock(openFileLimitMutex); @@ -363,8 +511,13 @@ int APS5_VABI setrlimit_nid_postfix(int resource, const GuestResourceLimit* limi return 0; } } - if (resource == guestRlimitNproc) - throw std::runtime_error("setrlimit: RLIMIT_NPROC is not stored"); + if (resource == guestRlimitNproc) { + requested.rlim_cur = std::clamp(requested.rlim_cur, std::int64_t{1}, guestProcessLimitPerUser); + requested.rlim_max = std::clamp(requested.rlim_max, std::int64_t{1}, guestProcessLimitPerUser); + std::lock_guard lock(processLimitMutex); + guestProcessResourceLimit = requested; + return 0; + } if (requested.rlim_cur != current.rlim_cur || requested.rlim_max != current.rlim_max) throw std::runtime_error("setrlimit: changing the limit of resource " + std::to_string(resource) + " is not supported"); return 0; @@ -469,6 +622,7 @@ void APS5_VABI sceKernelSync(void) { void APS5_VABI sync_nid_postfix(void) { sceKernelSync(); + } int APS5_VABI sched_get_priority_max_nid_postfix(int policy) { @@ -543,8 +697,12 @@ int APS5_VABI getrusage_nid_postfix(int who, GuestResourceUsage* usage) { *__error_nid_postfix() = guestFault; return -1; } + GuestResourceUsage result{}; if (who == -1) { - *usage = {}; + if (!WriteGuest(usage, &result, sizeof(result))) { + *__error_nid_postfix() = guestFault; + return -1; + } return 0; } #ifdef _WIN32 @@ -564,47 +722,37 @@ int APS5_VABI getrusage_nid_postfix(int who, GuestResourceUsage* usage) { }; const auto userMicros = toMicros(user); const auto kernelMicros = toMicros(kernel); - usage->ru_utime.tv_sec = static_cast(userMicros / 1000000ULL); - usage->ru_utime.tv_usec = static_cast(userMicros % 1000000ULL); - usage->ru_stime.tv_sec = static_cast(kernelMicros / 1000000ULL); - usage->ru_stime.tv_usec = static_cast(kernelMicros % 1000000ULL); - usage->ru_maxrss = 0; - usage->ru_ixrss = 0; - usage->ru_idrss = 0; - usage->ru_isrss = 0; - usage->ru_minflt = 0; - usage->ru_majflt = 0; - usage->ru_nswap = 0; - usage->ru_inblock = 0; - usage->ru_oublock = 0; - usage->ru_msgsnd = 0; - usage->ru_msgrcv = 0; - usage->ru_nsignals = 0; - usage->ru_nvcsw = 0; - usage->ru_nivcsw = 0; + result.ru_utime.tv_sec = static_cast(userMicros / 1000000ULL); + result.ru_utime.tv_usec = static_cast(userMicros % 1000000ULL); + result.ru_stime.tv_sec = static_cast(kernelMicros / 1000000ULL); + result.ru_stime.tv_usec = static_cast(kernelMicros % 1000000ULL); #else struct rusage native{}; if (::getrusage(who == 0 ? RUSAGE_SELF : RUSAGE_THREAD, &native) != 0) throw std::system_error(errno, std::generic_category(), "getrusage: getrusage failed"); - usage->ru_utime.tv_sec = static_cast(native.ru_utime.tv_sec); - usage->ru_utime.tv_usec = static_cast(native.ru_utime.tv_usec); - usage->ru_stime.tv_sec = static_cast(native.ru_stime.tv_sec); - usage->ru_stime.tv_usec = static_cast(native.ru_stime.tv_usec); - usage->ru_maxrss = static_cast(native.ru_maxrss); - usage->ru_ixrss = static_cast(native.ru_ixrss); - usage->ru_idrss = static_cast(native.ru_idrss); - usage->ru_isrss = static_cast(native.ru_isrss); - usage->ru_minflt = static_cast(native.ru_minflt); - usage->ru_majflt = static_cast(native.ru_majflt); - usage->ru_nswap = static_cast(native.ru_nswap); - usage->ru_inblock = static_cast(native.ru_inblock); - usage->ru_oublock = static_cast(native.ru_oublock); - usage->ru_msgsnd = static_cast(native.ru_msgsnd); - usage->ru_msgrcv = static_cast(native.ru_msgrcv); - usage->ru_nsignals = static_cast(native.ru_nsignals); - usage->ru_nvcsw = static_cast(native.ru_nvcsw); - usage->ru_nivcsw = static_cast(native.ru_nivcsw); + result.ru_utime.tv_sec = static_cast(native.ru_utime.tv_sec); + result.ru_utime.tv_usec = static_cast(native.ru_utime.tv_usec); + result.ru_stime.tv_sec = static_cast(native.ru_stime.tv_sec); + result.ru_stime.tv_usec = static_cast(native.ru_stime.tv_usec); + result.ru_maxrss = static_cast(native.ru_maxrss); + result.ru_ixrss = static_cast(native.ru_ixrss); + result.ru_idrss = static_cast(native.ru_idrss); + result.ru_isrss = static_cast(native.ru_isrss); + result.ru_minflt = static_cast(native.ru_minflt); + result.ru_majflt = static_cast(native.ru_majflt); + result.ru_nswap = static_cast(native.ru_nswap); + result.ru_inblock = static_cast(native.ru_inblock); + result.ru_oublock = static_cast(native.ru_oublock); + result.ru_msgsnd = static_cast(native.ru_msgsnd); + result.ru_msgrcv = static_cast(native.ru_msgrcv); + result.ru_nsignals = static_cast(native.ru_nsignals); + result.ru_nvcsw = static_cast(native.ru_nvcsw); + result.ru_nivcsw = static_cast(native.ru_nivcsw); #endif + if (!WriteGuest(usage, &result, sizeof(result))) { + *__error_nid_postfix() = guestFault; + return -1; + } return 0; } @@ -617,20 +765,21 @@ int APS5_VABI getrlimit_nid_postfix(int resource, GuestResourceLimit* limit) { *__error_nid_postfix() = guestFault; return -1; } + GuestResourceLimit result{}; switch (resource) { case guestRlimitData: { #ifdef _WIN32 const std::int64_t memory = hostMemoryLimit(); - limit->rlim_cur = memory; - limit->rlim_max = memory; + result.rlim_cur = memory; + result.rlim_max = memory; #else rlimit native{}; if (::getrlimit(RLIMIT_DATA, &native) != 0) throw std::system_error(errno, std::generic_category(), "getrlimit: getrlimit failed"); - limit->rlim_cur = toGuestLimit(native.rlim_cur); - limit->rlim_max = toGuestLimit(native.rlim_max); + result.rlim_cur = toGuestLimit(native.rlim_cur); + result.rlim_max = toGuestLimit(native.rlim_max); #endif - return 0; + break; } case guestRlimitNofile: { std::lock_guard lock(openFileLimitMutex); @@ -645,17 +794,18 @@ int APS5_VABI getrlimit_nid_postfix(int resource, GuestResourceLimit* limit) { guestOpenFileLimit = GuestResourceLimit{toGuestLimit(native.rlim_cur), toGuestLimit(native.rlim_max)}; #endif } - *limit = *guestOpenFileLimit; - return 0; + result = *guestOpenFileLimit; + break; } case guestRlimitStack: - limit->rlim_cur = guestStackLimit; - limit->rlim_max = guestStackLimit; - return 0; - case guestRlimitNproc: - limit->rlim_cur = guestProcessLimit; - limit->rlim_max = guestProcessLimit; - return 0; + result.rlim_cur = guestStackLimit; + result.rlim_max = guestStackLimit; + break; + case guestRlimitNproc: { + std::lock_guard lock(processLimitMutex); + result = guestProcessResourceLimit; + break; + } case guestRlimitCpu: case guestRlimitFsize: case guestRlimitCore: @@ -667,12 +817,17 @@ int APS5_VABI getrlimit_nid_postfix(int resource, GuestResourceLimit* limit) { case guestRlimitSwap: case guestRlimitKqueues: case guestRlimitUmtxp: - limit->rlim_cur = guestRlimitInfinity; - limit->rlim_max = guestRlimitInfinity; - return 0; + result.rlim_cur = guestRlimitInfinity; + result.rlim_max = guestRlimitInfinity; + break; default: throw std::runtime_error("getrlimit: unsupported resource " + std::to_string(resource)); } + if (!WriteGuest(limit, &result, sizeof(result))) { + *__error_nid_postfix() = guestFault; + return -1; + } + return 0; } int APS5_VABI getpriority_nid_postfix(int which, int who) { @@ -708,6 +863,7 @@ int APS5_VABI setpriority_nid_postfix(int which, int who, int niceval) { int APS5_VABI getdtablesize_nid_postfix(void) { GuestResourceLimit limit{}; getrlimit_nid_postfix(guestRlimitNofile, &limit); + std::int64_t tableSize = limit.rlim_cur; #ifndef _WIN32 tableSize = std::min(tableSize, static_cast(::getdtablesize())); @@ -770,6 +926,28 @@ int APS5_VABI setregid_nid_postfix(std::uint32_t rgid, std::uint32_t egid) { return 0; } +int APS5_VABI setresuid_nid_postfix(std::uint32_t ruid, std::uint32_t euid, std::uint32_t suid) { + constexpr std::uint32_t unchanged = 0xffffffffu; + if (ruid != 0 && ruid != unchanged) + throw std::runtime_error("setresuid: unsupported real user id " + std::to_string(ruid)); + if (euid != 0 && euid != unchanged) + throw std::runtime_error("setresuid: unsupported effective user id " + std::to_string(euid)); + if (suid != 0 && suid != unchanged) + throw std::runtime_error("setresuid: unsupported saved user id " + std::to_string(suid)); + return 0; +} + +int APS5_VABI setresgid_nid_postfix(std::uint32_t rgid, std::uint32_t egid, std::uint32_t sgid) { + constexpr std::uint32_t unchanged = 0xffffffffu; + if (rgid != 0 && rgid != unchanged) + throw std::runtime_error("setresgid: unsupported real group id " + std::to_string(rgid)); + if (egid != 0 && egid != unchanged) + throw std::runtime_error("setresgid: unsupported effective group id " + std::to_string(egid)); + if (sgid != 0 && sgid != unchanged) + throw std::runtime_error("setresgid: unsupported saved group id " + std::to_string(sgid)); + return 0; +} + int APS5_VABI setgroups_nid_postfix(int count, const std::uint32_t* groups) { if (count < 0 || count > freebsdGroupsMax) { *__error_nid_postfix() = errnoInvalidArgument; @@ -779,14 +957,22 @@ int APS5_VABI setgroups_nid_postfix(int count, const std::uint32_t* groups) { *__error_nid_postfix() = errnoBadAddress; return -1; } - if (count > 0 && groups[0] != 0) - throw std::runtime_error("setgroups: unsupported effective group id " + std::to_string(groups[0])); + std::vector requested; + if (count > 0) { + requested.resize(static_cast(count)); + if (!ReadGuest(requested.data(), groups, requested.size() * sizeof(requested[0]))) { + *__error_nid_postfix() = errnoBadAddress; + return -1; + } + } + if (count > 0 && requested[0] != 0) + throw std::runtime_error("setgroups: unsupported effective group id " + std::to_string(requested[0])); std::lock_guard lock(supplementaryGroupsMutex); if (count == 0) { supplementaryGroups.resize(1); return 0; } - supplementaryGroups.assign(groups, groups + count); + supplementaryGroups.swap(requested); return 0; } @@ -802,7 +988,10 @@ int APS5_VABI getgroups_nid_postfix(int capacity, std::uint32_t* groups) { *__error_nid_postfix() = errnoBadAddress; return -1; } - std::copy(supplementaryGroups.begin(), supplementaryGroups.end(), groups); + if (!WriteGuest(groups, supplementaryGroups.data(), supplementaryGroups.size() * sizeof(supplementaryGroups[0]))) { + *__error_nid_postfix() = errnoBadAddress; + return -1; + } return count; } diff --git a/core/libs/prx/libkernel/System/src/Rtld.cpp b/core/libs/prx/libkernel/System/src/Rtld.cpp index 90472da544..cbce3c4e09 100644 --- a/core/libs/prx/libkernel/System/src/Rtld.cpp +++ b/core/libs/prx/libkernel/System/src/Rtld.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include @@ -35,15 +36,13 @@ const void* imageContaining(const void* address, bool reference, void*& handle, #ifdef _WIN32 const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | (reference ? 0 : GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT); HMODULE module = nullptr; - if (!GetModuleHandleExW(flags, reinterpret_cast(address), &module)) - throw std::runtime_error(std::string(caller) + ": address outside every loaded image"); + if (!GetModuleHandleExW(flags, reinterpret_cast(address), &module)) return nullptr; if (reference) handle = module; return module; #else Dl_info info{}; link_map* image = nullptr; - if (!dladdr1(address, &info, reinterpret_cast(&image), RTLD_DL_LINKMAP) || !image) - throw std::runtime_error(std::string(caller) + ": address outside every loaded image"); + if (!dladdr1(address, &info, reinterpret_cast(&image), RTLD_DL_LINKMAP) || !image) return nullptr; if (reference && image->l_name && *image->l_name) { handle = dlopen(image->l_name, RTLD_LAZY | RTLD_NOLOAD); if (!handle) throw std::runtime_error(std::string(caller) + ": cannot reference " + image->l_name); @@ -55,6 +54,7 @@ const void* imageContaining(const void* address, bool reference, void*& handle, int referenceThreadAtexitImage(const void* address, const char* caller) { void* handle = nullptr; const void* image = imageContaining(address, true, handle, caller); + if (!image) return -1; std::lock_guard lock(threadAtexitMutex); threadAtexitReferences.emplace(image, handle); return 0; @@ -63,6 +63,7 @@ int referenceThreadAtexitImage(const void* address, const char* caller) { int releaseThreadAtexitImage(const void* address, const char* caller) { void* handle = nullptr; const void* image = imageContaining(address, false, handle, caller); + if (!image) return 0; { std::lock_guard lock(threadAtexitMutex); const auto found = threadAtexitReferences.find(image); diff --git a/core/libs/prx/libkernel/System/src/Sandbox.cpp b/core/libs/prx/libkernel/System/src/Sandbox.cpp index dd3918c68a..e904dc5942 100644 --- a/core/libs/prx/libkernel/System/src/Sandbox.cpp +++ b/core/libs/prx/libkernel/System/src/Sandbox.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" extern "C" const char* APS5_VABI sceKernelGetFsSandboxRandomWord() { diff --git a/core/libs/prx/libkernel/System/src/Sanitizer.cpp b/core/libs/prx/libkernel/System/src/Sanitizer.cpp index 0586eef7d7..15ff3e040f 100644 --- a/core/libs/prx/libkernel/System/src/Sanitizer.cpp +++ b/core/libs/prx/libkernel/System/src/Sanitizer.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" extern "C" { diff --git a/core/libs/prx/libkernel/System/src/SignalSets.cpp b/core/libs/prx/libkernel/System/src/SignalSets.cpp index 688ad49ce8..8444422f5d 100644 --- a/core/libs/prx/libkernel/System/src/SignalSets.cpp +++ b/core/libs/prx/libkernel/System/src/SignalSets.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/AsmMacros.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,11 +7,6 @@ #include #include -extern "C" int* APS5_VABI __error_nid_postfix(); - -struct GuestSignalSet { - std::uint32_t bits[4]; -}; static_assert(sizeof(GuestSignalSet) == 16); namespace { diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index 231d3d2643..091f1db7d4 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -1,5 +1,8 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/JumpBuffer.hpp" #include "prx/libkernel/Pthread/Posix/Common.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -17,34 +20,33 @@ #include #include -extern "C" int* APS5_VABI __error_nid_postfix(); -extern "C" int APS5_VABI getpid_nid_postfix(void); -extern "C" Pthread APS5_VABI scePthreadSelf(void); - -struct GuestSignalSet { - std::uint32_t bits[4]; -}; - -struct GuestSigaction { - std::uintptr_t handler; - int flags; - GuestSignalSet mask; -}; static_assert(sizeof(GuestSigaction) == 32 && offsetof(GuestSigaction, flags) == 8 && offsetof(GuestSigaction, mask) == 12); +extern "C" GuestSignalSet _sigintr_nid_postfix{}; + namespace { -using GuestHandler = void (APS5_VABI *)(int); + constexpr int MaxSignal = 128; constexpr int GuestSigkill = 9; constexpr int GuestSigstop = 17; +constexpr std::uint32_t UnblockableSignals = (1u << (GuestSigkill - 1)) | (1u << (GuestSigstop - 1)); constexpr int GuestSigalrm = 14; constexpr int GuestSigcont = 19; constexpr int SaRestart = 0x2; +constexpr int SaResethand = 0x4; +constexpr int SaNodefer = 0x10; constexpr int SaSiginfo = 0x40; +constexpr int SaDefinedFlags = 0x7f; std::atomic handlers[MaxSignal + 1]{}; +std::atomic handlerMasks[MaxSignal + 1][4]{}; +std::atomic handlerFlags[MaxSignal + 1]{}; +std::atomic handlerReset[MaxSignal + 1]{}; static_assert(std::atomic::is_always_lock_free); std::atomic blockedMask{0}; std::atomic blockedMaskExtra[3]{}; +std::atomic handlerHolds[MaxSignal + 1]{}; +constexpr std::uint64_t HoldCount = 0xffffffffu; +thread_local std::uint32_t holdReleases[MaxSignal + 1]{}; static_assert(std::atomic::is_always_lock_free); std::mutex registration; GuestSigaction dispositions[MaxSignal + 1]{}; @@ -71,11 +73,8 @@ struct GuestSiginfo { static_assert(sizeof(GuestSiginfo) == 80 && offsetof(GuestSiginfo, code) == 8 && offsetof(GuestSiginfo, pid) == 12 && offsetof(GuestSiginfo, uid) == 16 && offsetof(GuestSiginfo, address) == 24 && offsetof(GuestSiginfo, value) == 32 && offsetof(GuestSiginfo, band) == 40); -struct GuestTimespec { - std::int64_t seconds; - std::int64_t nanoseconds; -}; -static_assert(sizeof(GuestTimespec) == 16 && offsetof(GuestTimespec, nanoseconds) == 8); + +static_assert(sizeof(KernelTimespec) == 16 && offsetof(KernelTimespec, tv_nsec) == 8); bool IgnoredByDefault(int guest) { return guest == 16 || guest == GuestSigcont || guest == 20 || guest == 23 || guest == 28 || guest == 29; } @@ -83,11 +82,29 @@ std::uint32_t SignalBit(int guest) { return 1u << ((guest - 1) % 32); } std::uint32_t SignalWord(int guest) { return static_cast((guest - 1) / 32); } bool SignalInSet(const GuestSignalSet& set, int guest) { return (set.bits[SignalWord(guest)] & SignalBit(guest)) != 0; } bool SetHasSignal(const GuestSignalSet& set) { return (set.bits[0] | set.bits[1] | set.bits[2] | set.bits[3]) != 0; } +bool HeldByHandler(int guest) { return (handlerHolds[guest].load() & HoldCount) != 0; } +void ReleaseHold(int guest, std::uint32_t epoch) { + auto value = handlerHolds[guest].load(); + while ((value >> 32) == epoch && (value & HoldCount) != 0 && !handlerHolds[guest].compare_exchange_weak(value, value - 1)) {} +} +void ClearHold(int guest) { + auto value = handlerHolds[guest].load(); + while ((value & HoldCount) != 0 && !handlerHolds[guest].compare_exchange_weak(value, ((value >> 32) + 1) << 32)) {} +} +std::uint32_t HeldWord(int word) { + std::uint32_t held = 0; + for (int bit = 0; bit < 32; ++bit) + if (HeldByHandler(word * 32 + bit + 1)) held |= 1u << bit; + return held; +} +std::uint32_t BlockedWord(int word) { + return (word == 0 ? blockedMask.load() : blockedMaskExtra[word - 1].load()) | HeldWord(word); +} bool SignalBlocked(int guest) { if (guest < 1 || guest > MaxSignal) return false; const auto word = SignalWord(guest); const auto mask = word == 0 ? blockedMask.load() : blockedMaskExtra[word - 1].load(); - return (mask & SignalBit(guest)) != 0; + return (mask & SignalBit(guest)) != 0 || HeldByHandler(guest); } struct PendingSignals { @@ -120,6 +137,10 @@ bool TakePending(int guest) { return (word.fetch_and(~bit, std::memory_order_relaxed) & bit) != 0; } +void DiscardPendingIfIgnored(int guest, const GuestSigaction& action) { + if (action.handler == 1 || (action.handler == 0 && IgnoredByDefault(guest))) TakePending(guest); +} + bool PendingInSet(const GuestSignalSet& set, int* guest) { for (int candidate = 1; candidate <= MaxSignal; ++candidate) { if (!SignalInSet(set, candidate) || !TakePending(candidate)) continue; @@ -148,6 +169,41 @@ int NativeSignal(int guest) { default: return 0; } } +void ResetToDefault(int guest) { + handlerReset[guest].store(true); + handlers[guest].store(nullptr); + if (const int native = NativeSignal(guest)) std::signal(native, SIG_DFL); +} +void RunHandler(int guest, GuestHandler handler) { + const int flags = handlerFlags[guest].load(); + std::uint32_t mask[4]; + for (int word = 0; word < 4; ++word) mask[word] = handlerMasks[guest][word].load(); + if ((flags & SaNodefer) == 0) mask[SignalWord(guest)] |= SignalBit(guest); + mask[0] &= ~UnblockableSignals; + if ((flags & SaResethand) != 0) ResetToDefault(guest); + std::uint32_t before[4]; + for (int word = 0; word < 4; ++word) before[word] = (word == 0 ? blockedMask.load() : blockedMaskExtra[word - 1].load()) & mask[word]; + std::uint32_t epochs[MaxSignal + 1]{}; + std::uint32_t releases[MaxSignal + 1]{}; + for (int held = 1; held <= MaxSignal; ++held) { + if ((mask[SignalWord(held)] & SignalBit(held)) == 0) continue; + releases[held] = holdReleases[held]; + epochs[held] = static_cast(handlerHolds[held].fetch_add(1) >> 32); + } + handler(guest); + std::uint32_t replaced[4]{}; + for (int held = 1; held <= MaxSignal; ++held) { + if ((mask[SignalWord(held)] & SignalBit(held)) == 0) continue; + if (holdReleases[held] != releases[held]) replaced[SignalWord(held)] |= SignalBit(held); + ReleaseHold(held, epochs[held]); + } + for (int word = 0; word < 4; ++word) { + if (replaced[word] == 0) continue; + auto& target = word == 0 ? blockedMask : blockedMaskExtra[word - 1]; + target.fetch_and(~(replaced[word] & ~before[word])); + target.fetch_or(replaced[word] & before[word]); + } +} void Dispatch(int native) { int guest = 0; for (int candidate : {2, 4, 6, 8, 11, 15}) @@ -162,12 +218,15 @@ void Dispatch(int native) { return; } const auto callback = handlers[guest].load(); - if (reinterpret_cast(callback) > 1) callback(guest); + if (reinterpret_cast(callback) > 1) RunHandler(guest, callback); } bool Delivered(const GuestSigaction& action) { return action.handler > 1 && (action.flags & SaSiginfo) == 0; } bool Install(int guest, const GuestSigaction& action) { + handlerReset[guest].store(false); + for (int word = 0; word < 4; ++word) handlerMasks[guest][word].store(action.mask.bits[word]); + handlerFlags[guest].store(action.flags); const int native = NativeSignal(guest); if (!native) return true; const auto previous = handlers[guest].exchange(Delivered(action) ? reinterpret_cast(action.handler) : nullptr); @@ -182,8 +241,15 @@ bool Install(int guest, const GuestSigaction& action) { namespace { +void SettleReset(int guest) { + if (!handlerReset[guest].exchange(false)) return; + dispositions[guest].handler = 0; + dispositions[guest].flags &= ~SaSiginfo; +} + GuestSigaction Disposition(int guest) { std::lock_guard lock(registration); + SettleReset(guest); return dispositions[guest]; } @@ -226,13 +292,22 @@ bool DeliverPendingSignal(int guest) { return action.handler > 1; } if (action.handler > 1) { - reinterpret_cast(action.handler)(guest); + RunHandler(guest, reinterpret_cast(action.handler)); return true; } return false; } -int WaitForPendingInSet(const GuestSignalSet& set, bool waitForever, const GuestTimespec* timeout, int* guest) { +bool DeliverUnblockedPending() { + bool caught = false; + for (int guest = 1; guest <= MaxSignal; ++guest) { + if (SignalBlocked(guest) || !Deliverable(guest) || !TakePending(guest)) continue; + if (DeliverPendingSignal(guest)) caught = true; + } + return caught; +} + +int WaitForPendingInSet(const GuestSignalSet& set, bool waitForever, const KernelTimespec* timeout, int* guest) { auto& pending = Pending(); std::unique_lock lock(pending.lock); std::optional deadline; @@ -242,8 +317,8 @@ int WaitForPendingInSet(const GuestSignalSet& set, bool waitForever, const Guest pending.changed.wait(lock); continue; } - if (!deadline) deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout->seconds) + - std::chrono::nanoseconds(timeout->nanoseconds); + if (!deadline) deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout->tv_sec) + + std::chrono::nanoseconds(timeout->tv_nsec); if (pending.changed.wait_until(lock, *deadline) == std::cv_status::no_timeout) continue; return PendingInSet(set, guest) ? 0 : GuestEagain; } @@ -271,8 +346,6 @@ void WaitForDeliverableSignal() { } } -extern "C" int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet); - } namespace { @@ -369,10 +442,6 @@ extern "C" unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int seconds) return remaining; } -struct GuestItimerval { - KernelTimeval interval; - KernelTimeval value; -}; static_assert(sizeof(GuestItimerval) == 32 && offsetof(GuestItimerval, value) == 16); extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output) { @@ -464,11 +533,6 @@ extern "C" int APS5_VABI setitimer_nid_postfix(int which, const GuestItimerval* return 0; } -struct GuestStack { - void* sp; - std::size_t size; - int flags; -}; static_assert(sizeof(GuestStack) == 24 && offsetof(GuestStack, flags) == 16); namespace { constexpr int SsDisable = 4; @@ -476,29 +540,86 @@ constexpr std::size_t MinSignalStackSize = 2048; thread_local GuestStack alternateStack{nullptr, 0, SsDisable}; } +namespace { + +int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previousSet, bool releaseHolds, bool* caught = nullptr) { + if (set != nullptr && (how < 1 || how > 3)) { *__error_nid_postfix() = 22; return -1; } + { + std::lock_guard lock(registration); + if (previousSet != nullptr) + for (int word = 0; word < 4; ++word) previousSet->bits[word] = BlockedWord(word); + if (set != nullptr) { + switch (how) { + case 1: + blockedMask.fetch_or(set->bits[0] & ~UnblockableSignals); + for (int word = 1; word < 4; ++word) blockedMaskExtra[word - 1].fetch_or(set->bits[word]); + break; + case 2: + blockedMask.fetch_and(~set->bits[0]); + for (int word = 1; word < 4; ++word) blockedMaskExtra[word - 1].fetch_and(~set->bits[word]); + if (releaseHolds) + for (int guest = 1; guest <= MaxSignal; ++guest) { + if (!SignalInSet(*set, guest)) continue; + ClearHold(guest); + ++holdReleases[guest]; + } + break; + case 3: + blockedMask.store(set->bits[0] & ~UnblockableSignals); + for (int word = 1; word < 4; ++word) blockedMaskExtra[word - 1].store(set->bits[word]); + if (releaseHolds) + for (int guest = 1; guest <= MaxSignal; ++guest) { + ClearHold(guest); + ++holdReleases[guest]; + } + break; + } + } + } + std::atomic_thread_fence(std::memory_order_seq_cst); + if (DeliverUnblockedPending() && caught != nullptr) *caught = true; + return 0; +} + +} + +extern "C" int APS5_VABI SetSignalMaskCatching_nid_no_patch(const GuestSignalSet* set, GuestSignalSet* previousSet) { + bool caught = false; + ChangeBlockedMask(GuestSigsetmask, set, previousSet, true, &caught); + return caught ? 1 : 0; +} + extern "C" { GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) { const auto invalid = reinterpret_cast(static_cast(-1)); if (guest < 1 || guest > MaxSignal || ((guest == GuestSigkill || guest == GuestSigstop) && handler != nullptr)) { *__error_nid_postfix() = 22; return invalid; } std::lock_guard lock(registration); - const GuestSigaction action{reinterpret_cast(handler), SaRestart, {}}; + SettleReset(guest); + const GuestSigaction action{reinterpret_cast(handler), SignalInSet(_sigintr_nid_postfix, guest) ? 0 : SaRestart, {}}; if (!Install(guest, action)) { *__error_nid_postfix() = 22; return invalid; } const auto previous = reinterpret_cast(dispositions[guest].handler); dispositions[guest] = action; + DiscardPendingIfIgnored(guest, action); return previous; } int APS5_VABI sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous) { 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; } std::lock_guard lock(registration); + SettleReset(guest); const GuestSigaction old = dispositions[guest]; if (action) { if (!Install(guest, *action)) { *__error_nid_postfix() = 22; return -1; } dispositions[guest] = *action; + DiscardPendingIfIgnored(guest, *action); } if (previous) *previous = old; return 0; } +int APS5_VABI _sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous) { + return sigaction_nid_postfix(guest, action, previous); +} int APS5_VABI raise_nid_postfix(int guest) { if (guest == 0) return 0; if (guest < 1 || guest > MaxSignal) { *__error_nid_postfix() = 22; return -1; } @@ -513,11 +634,13 @@ int APS5_VABI raise_nid_postfix(int guest) { const int native = NativeSignal(guest); if (native != 0) { const int result = std::raise(native); - if (result) *__error_nid_postfix() = 22; - return result ? -1 : 0; + if (result) { *__error_nid_postfix() = 22; return -1; } + DeliverUnblockedPending(); + return 0; } if (!CaughtByGuest(guest)) { *__error_nid_postfix() = 22; return -1; } DeliverPendingSignal(guest); + DeliverUnblockedPending(); return 0; } int APS5_VABI kill_nid_postfix(int pid, int guest) { @@ -552,36 +675,7 @@ int APS5_VABI sigaltstack_nid_postfix(const GuestStack* stack, GuestStack* previ return 0; } int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) { - if (set != nullptr && (how < 1 || how > 3)) { *__error_nid_postfix() = 22; return -1; } - { - std::lock_guard lock(registration); - if (previousSet != nullptr) { - previousSet->bits[0] = blockedMask.load(); - for (int word = 1; word < 4; ++word) previousSet->bits[word] = blockedMaskExtra[word - 1].load(); - } - if (set != nullptr) { - switch (how) { - case 1: - blockedMask.fetch_or(set->bits[0]); - for (int word = 1; word < 4; ++word) blockedMaskExtra[word - 1].fetch_or(set->bits[word]); - break; - case 2: - blockedMask.fetch_and(~set->bits[0]); - for (int word = 1; word < 4; ++word) blockedMaskExtra[word - 1].fetch_and(~set->bits[word]); - break; - case 3: - blockedMask.store(set->bits[0]); - for (int word = 1; word < 4; ++word) blockedMaskExtra[word - 1].store(set->bits[word]); - break; - } - } - } - std::atomic_thread_fence(std::memory_order_seq_cst); - for (int guest = 1; guest <= MaxSignal; ++guest) { - if (SignalBlocked(guest) || !Deliverable(guest) || !TakePending(guest)) continue; - DeliverPendingSignal(guest); - } - return 0; + return ChangeBlockedMask(how, set, previousSet, true); } int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet) { @@ -594,9 +688,9 @@ int APS5_VABI pthread_sigmask_nid_postfix(int how, const GuestSignalSet* set, Gu return _sigprocmask_nid_postfix(how, set, previousSet); } -int APS5_VABI sigtimedwait_nid_postfix(const GuestSignalSet* set, void* info, const GuestTimespec* timeout) { +int APS5_VABI sigtimedwait_nid_postfix(const GuestSignalSet* set, void* info, const KernelTimespec* timeout) { if (set == nullptr || !SetHasSignal(*set)) { *__error_nid_postfix() = 22; return -1; } - if (timeout != nullptr && (timeout->nanoseconds < 0 || timeout->nanoseconds >= 1000000000) && !PendingInSetPresent(*set)) { + if (timeout != nullptr && (timeout->tv_nsec < 0 || timeout->tv_nsec >= 1000000000) && !PendingInSetPresent(*set)) { *__error_nid_postfix() = 22; return -1; } @@ -635,22 +729,26 @@ int APS5_VABI sigsuspend_nid_postfix(const GuestSignalSet* set) { GuestSignalSet previous{{0, 0, 0, 0}}; { std::lock_guard lock(registration); - previous.bits[0] = blockedMask.exchange(set->bits[0]); + previous.bits[0] = blockedMask.exchange(set->bits[0] & ~UnblockableSignals); for (int word = 1; word < 4; ++word) previous.bits[word] = blockedMaskExtra[word - 1].exchange(set->bits[word]); } WaitForDeliverableSignal(); - _sigprocmask_nid_postfix(GuestSigsetmask, &previous, nullptr); + ChangeBlockedMask(GuestSigsetmask, &previous, nullptr, false); + *__error_nid_postfix() = GuestEintr; return -1; } +int APS5_VABI _sigsuspend_nid_postfix(const GuestSignalSet* set) { + return sigsuspend_nid_postfix(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); - set->bits[0] = pending.words[0].load(std::memory_order_relaxed) & blockedMask.load(); - for (int word = 1; word < 4; ++word) - set->bits[word] = pending.words[word].load(std::memory_order_relaxed) & blockedMaskExtra[word - 1].load(); + for (int word = 0; word < 4; ++word) + set->bits[word] = pending.words[word].load(std::memory_order_relaxed) & BlockedWord(word); return 0; } } @@ -663,3 +761,18 @@ int APS5_VABI _is_signal_return_nid_postfix(std::uint64_t programCounter) { } } + +namespace { + +[[maybe_unused]] const bool RegisteredForJumps = [] { + JumpBuffer::GuestSigprocmaskRegister_nid_no_patch(sigprocmask_nid_postfix); + return true; +}(); + +struct RegisterSignalsOnLoad { + RegisterSignalsOnLoad() { + RegisterSigaction_nid_no_patch(reinterpret_cast(sigaction_nid_postfix), _sigintr_nid_postfix.bits); + } +}; +const RegisterSignalsOnLoad gRegisterSignals; +} diff --git a/core/libs/prx/libkernel/System/src/SoftwareVersion.cpp b/core/libs/prx/libkernel/System/src/SoftwareVersion.cpp index 6f834ce2ff..9c6bf6029c 100644 --- a/core/libs/prx/libkernel/System/src/SoftwareVersion.cpp +++ b/core/libs/prx/libkernel/System/src/SoftwareVersion.cpp @@ -1,15 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include #include #include -struct SceKernelProsperoSystemSwVersion { - std::uint64_t size; - char versionString[0x1C]; - std::uint32_t version; -}; - static_assert(offsetof(SceKernelProsperoSystemSwVersion, size) == 0x00); static_assert(offsetof(SceKernelProsperoSystemSwVersion, versionString) == 0x08); static_assert(offsetof(SceKernelProsperoSystemSwVersion, version) == 0x24); diff --git a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp index da87fe7bb2..33e7920ef8 100644 --- a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp +++ b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp @@ -1,8 +1,12 @@ +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/System/include/HostCpu.hpp" +#include "prx/libkernel/Time/include/Time.hpp" #include "SceTypes.hpp" #include #include @@ -20,22 +24,6 @@ #include #endif -extern "C" int* APS5_VABI __error_nid_postfix(); -extern "C" int APS5_VABI getpid_nid_postfix(); -extern "C" std::uint64_t APS5_VABI sceKernelGetTscFrequency(void); -struct GuestRlimit { - std::int64_t rlim_cur; - std::int64_t rlim_max; -}; -extern "C" int APS5_VABI getrlimit_nid_postfix(int resource, GuestRlimit* limit); - -struct SceKernelSystemSwVersion { - std::uint64_t size; - char versionString[0x1C]; - std::uint32_t version; -}; -extern "C" int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, uint64_t info_size); - namespace { int CopyOut(const void* value, std::size_t size, void* oldValue, std::size_t* oldLength) { @@ -44,7 +32,7 @@ int CopyOut(const void* value, std::size_t size, void* oldValue, std::size_t* ol return 0; } const std::size_t copied = std::min(oldLength != nullptr ? *oldLength : 0, size); - std::memcpy(oldValue, value, copied); + if (copied != 0) std::memcpy(oldValue, value, copied); if (oldLength != nullptr) *oldLength = copied; if (copied < size) { *__error_nid_postfix() = 12; @@ -92,7 +80,18 @@ const SystemInteger* FindSystemInteger(const int* name) { } bool IsLeafMib(const int* name) { - return FindSystemInteger(name) != nullptr || (name[0] == 6 && (name[1] == 2 || name[1] == 3 || name[1] == 5 || name[1] == 7)) || (name[0] == 1 && (name[1] == 37 || name[1] == 50)); + return FindSystemInteger(name) != nullptr || (name[0] == 6 && (name[1] == 2 || name[1] == 3 || name[1] == 5 || name[1] == 7)) || (name[0] == 1 && (name[1] == 21 || name[1] == 37 || name[1] == 50)); +} + +KernelTimeval BootTime() { + static const KernelTimeval bootTime = [] { + KernelTimespec now{}; + KernelTimespec uptime{}; + if (clock_gettime_nid_postfix(0, &now) != 0 || clock_gettime_nid_postfix(5, &uptime) != 0) throw std::runtime_error("kern.boottime: reading CLOCK_REALTIME or CLOCK_UPTIME failed"); + const std::int64_t nanos = (now.tv_sec - uptime.tv_sec) * 1000000000LL + now.tv_nsec - uptime.tv_nsec; + return KernelTimeval{nanos / 1000000000LL, nanos % 1000000000LL / 1000LL}; + }(); + return bootTime; } bool IsMeasuredAbsentMib(const int* name, std::uint32_t nameLength) { @@ -192,7 +191,7 @@ std::int64_t APS5_VABI sysconf_nid_postfix(int name) { result = 128; break; case 5: { // _SC_OPEN_MAX - GuestRlimit limit{}; + GuestResourceLimit limit{}; if (getrlimit_nid_postfix(8, &limit) == 0) { result = limit.rlim_cur; } @@ -323,6 +322,21 @@ int APS5_VABI sysctl_nid_postfix(const int* name, std::uint32_t nameLength, void static constexpr char path[] = "/app0/eboot.bin"; return CopyOut(path, sizeof(path), oldValue, oldLength); } + if (nameLength >= 3 && name[0] == 1 && name[1] == 14 && name[2] == 35) { + if (newValue != nullptr) { + *__error_nid_postfix() = 1; + return -1; + } + if (nameLength != 4) { + *__error_nid_postfix() = 22; + return -1; + } + if (name[3] != getpid_nid_postfix()) { + *__error_nid_postfix() = 3; + return -1; + } + return CopyOut(nullptr, 0, oldValue, oldLength); + } if (nameLength >= 3 && name[0] == 1 && name[1] == 14 && name[2] == 32) { if (newValue != nullptr) { *__error_nid_postfix() = 1; @@ -339,6 +353,14 @@ int APS5_VABI sysctl_nid_postfix(const int* name, std::uint32_t nameLength, void const auto entries = GuestVmEntries(); return CopyOut(entries.data(), entries.size() * sizeof(GuestVmEntry), oldValue, oldLength); } + if (nameLength == 2 && name[0] == 1 && name[1] == 21) { + if (newValue != nullptr) { + *__error_nid_postfix() = 1; + return -1; + } + const KernelTimeval bootTime = BootTime(); + return CopyOut(&bootTime, sizeof(bootTime), oldValue, oldLength); + } if (nameLength == 2 && name[0] == 1 && name[1] == 37) { if (newValue != nullptr) { *__error_nid_postfix() = 1; @@ -396,6 +418,10 @@ int APS5_VABI sysctlbyname_nid_postfix(const char* name, void* oldValue, std::si static constexpr int mib[] = {1, 14, 12}; return sysctl_nid_postfix(mib, 3, oldValue, oldLength, newValue, newLength); } + if (std::strcmp(name, "kern.boottime") == 0) { + static constexpr int mib[] = {1, 21}; + return sysctl_nid_postfix(mib, 2, oldValue, oldLength, newValue, newLength); + } if (std::strcmp(name, "kern.arandom") == 0) { static constexpr int mib[] = {1, 37}; return sysctl_nid_postfix(mib, 2, oldValue, oldLength, newValue, newLength); @@ -452,7 +478,8 @@ int APS5_VABI sysctlbyname_nid_postfix(const char* name, void* oldValue, std::si static constexpr int mib[] = {6, 12}; return sysctl_nid_postfix(mib, 2, oldValue, oldLength, newValue, newLength); } - throw std::runtime_error(std::string(__func__) + ": unsupported name " + name); + *__error_nid_postfix() = 2; + return -1; } int APS5_VABI sceKernelGetSystemSwVersion_nid_postfix(SceKernelSystemSwVersion* version) { @@ -465,6 +492,31 @@ int APS5_VABI sceKernelGetSystemSwVersion_nid_postfix(SceKernelSystemSwVersion* return 0; } +int APS5_VABI sceKernelGetSystemExVersion(char* first, char* second) { + if (first == nullptr || second == nullptr) { + return 22; + } + *first = '\0'; + *second = '\0'; + return 0; +} + +int APS5_VABI sysKernelGetLowerLimitSysexVersion_nid_postfix(char* version) { + if (version == nullptr) { + return SCE_KERNEL_ERROR_EINVAL; + } + *version = '\0'; + return 0; +} + +int APS5_VABI sysKernelGetLowerLimitUpdVersion_nid_postfix(std::uint32_t* version) { + if (version == nullptr) { + return SCE_KERNEL_ERROR_EINVAL; + } + *version = 0; + return 0; +} + int APS5_VABI sysctlnametomib_nid_postfix(const char* name, int* mib, std::size_t* size) { struct KnownName { const char* name; @@ -480,6 +532,7 @@ int APS5_VABI sysctlnametomib_nid_postfix(const char* name, int* mib, std::size_ {"hw.physmem", {6, 5}, 2}, {"hw.realmem", {6, 12}, 2}, {"kern.arandom", {1, 37}, 2}, + {"kern.boottime", {1, 21}, 2}, {"kern.clockrate", {1, 12}, 2}, {"kern.maxfiles", {1, 7}, 2}, {"kern.maxfilesperproc", {1, 27}, 2}, @@ -514,6 +567,7 @@ int APS5_VABI sysctlnametomib_nid_postfix(const char* name, int* mib, std::size_ for (const auto& entry : SystemIntegers) { if (std::strcmp(name, entry.name) == 0) return copyMib(entry.mib, 2); } - throw std::runtime_error(std::string(__func__) + ": unsupported name " + name); + *__error_nid_postfix() = 2; + return -1; } } diff --git a/core/libs/prx/libkernel/System/src/ThermalSensors.cpp b/core/libs/prx/libkernel/System/src/ThermalSensors.cpp index bd83c4282c..fcff781e17 100644 --- a/core/libs/prx/libkernel/System/src/ThermalSensors.cpp +++ b/core/libs/prx/libkernel/System/src/ThermalSensors.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/System/include/ThermalSensors.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/general/VabiMacros.hpp" diff --git a/core/libs/prx/libkernel/System/src/UserContext.cpp b/core/libs/prx/libkernel/System/src/UserContext.cpp index 92eaa7a389..0d3d7f5937 100644 --- a/core/libs/prx/libkernel/System/src/UserContext.cpp +++ b/core/libs/prx/libkernel/System/src/UserContext.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/GuestStacks.hpp" #include "prx/libc/include/general/AsmMacros.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -7,9 +9,6 @@ #include #include -extern "C" int* APS5_VABI __error_nid_postfix(); -extern "C" int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet); - namespace { struct RegisterFrame { @@ -71,47 +70,58 @@ std::uint32_t HostMxcsrMask() { extern "C" [[noreturn]] void APS5_VABI Aps5ContextRestore_nid_no_patch(const RegisterFrame* frame); +#define APS5_CONTEXT_CAPTURE \ + " pushfq\n" \ + " push %rbp\n" \ + " mov %rsp, %rbp\n" \ + " sub $0x2a0, %rsp\n" \ + " and $-16, %rsp\n" \ + " mov %rax, 0x00(%rsp)\n" \ + " mov %rbx, 0x08(%rsp)\n" \ + " mov %rcx, 0x10(%rsp)\n" \ + " mov %rdx, 0x18(%rsp)\n" \ + " mov %rsi, 0x20(%rsp)\n" \ + " mov %rdi, 0x28(%rsp)\n" \ + " mov 0(%rbp), %rax\n" \ + " mov %rax, 0x30(%rsp)\n" \ + " lea 24(%rbp), %rax\n" \ + " mov %rax, 0x38(%rsp)\n" \ + " mov %r8, 0x40(%rsp)\n" \ + " mov %r9, 0x48(%rsp)\n" \ + " mov %r10, 0x50(%rsp)\n" \ + " mov %r11, 0x58(%rsp)\n" \ + " mov %r12, 0x60(%rsp)\n" \ + " mov %r13, 0x68(%rsp)\n" \ + " mov %r14, 0x70(%rsp)\n" \ + " mov %r15, 0x78(%rsp)\n" \ + " mov 16(%rbp), %rax\n" \ + " mov %rax, 0x80(%rsp)\n" \ + " mov 8(%rbp), %rax\n" \ + " mov %rax, 0x88(%rsp)\n" \ + " xor %eax, %eax\n" \ + " mov %cs, %ax\n" \ + " mov %rax, 0x90(%rsp)\n" \ + " mov %ss, %ax\n" \ + " mov %rax, 0x98(%rsp)\n" \ + " fxsave64 0xa0(%rsp)\n" + +#define APS5_CONTEXT_RETURN \ + " mov %rbp, %rsp\n" \ + " pop %rbp\n" \ + " lea 8(%rsp), %rsp\n" \ + " ret\n" + asm(".text\n" APS5_ASM_FUNCTION("getcontext_nid_postfix") - " pushfq\n" - " push %rbp\n" - " mov %rsp, %rbp\n" - " sub $0x2a0, %rsp\n" - " and $-16, %rsp\n" - " mov %rax, 0x00(%rsp)\n" - " mov %rbx, 0x08(%rsp)\n" - " mov %rcx, 0x10(%rsp)\n" - " mov %rdx, 0x18(%rsp)\n" - " mov %rsi, 0x20(%rsp)\n" - " mov %rdi, 0x28(%rsp)\n" - " mov 0(%rbp), %rax\n" - " mov %rax, 0x30(%rsp)\n" - " lea 24(%rbp), %rax\n" - " mov %rax, 0x38(%rsp)\n" - " mov %r8, 0x40(%rsp)\n" - " mov %r9, 0x48(%rsp)\n" - " mov %r10, 0x50(%rsp)\n" - " mov %r11, 0x58(%rsp)\n" - " mov %r12, 0x60(%rsp)\n" - " mov %r13, 0x68(%rsp)\n" - " mov %r14, 0x70(%rsp)\n" - " mov %r15, 0x78(%rsp)\n" - " mov 16(%rbp), %rax\n" - " mov %rax, 0x80(%rsp)\n" - " mov 8(%rbp), %rax\n" - " mov %rax, 0x88(%rsp)\n" - " xor %eax, %eax\n" - " mov %cs, %ax\n" - " mov %rax, 0x90(%rsp)\n" - " mov %ss, %ax\n" - " mov %rax, 0x98(%rsp)\n" - " fxsave64 0xa0(%rsp)\n" + APS5_CONTEXT_CAPTURE " mov %rsp, %rsi\n" " call " APS5_ASM_SYMBOL("Aps5GetcontextFinish_nid_no_patch") "\n" - " mov %rbp, %rsp\n" - " pop %rbp\n" - " lea 8(%rsp), %rsp\n" - " ret\n" + APS5_CONTEXT_RETURN + APS5_ASM_FUNCTION("swapcontext_nid_postfix") + APS5_CONTEXT_CAPTURE + " mov %rsp, %rdx\n" + " call " APS5_ASM_SYMBOL("Aps5SwapcontextFinish_nid_no_patch") "\n" + APS5_CONTEXT_RETURN APS5_ASM_FUNCTION("Aps5ContextRestore_nid_no_patch") " mov %rdi, %r12\n" " xor %eax, %eax\n" @@ -157,6 +167,7 @@ int APS5_VABI Aps5GetcontextFinish_nid_no_patch(UserUcontext* context, const Reg if (context == nullptr) return Fail(GuestEinval); UserUcontext captured{}; sigprocmask_nid_postfix(GuestSigBlock, nullptr, captured.sigmask); + auto& machine = captured.mcontext; machine.rdi = frame->rdi; machine.rsi = frame->rsi; @@ -213,7 +224,14 @@ int APS5_VABI setcontext_nid_postfix(const UserUcontext* context) { mxcsr &= HostMxcsrMask(); std::memcpy(frame.fxsave + FxsaveMxcsr, &mxcsr, sizeof(mxcsr)); sigprocmask_nid_postfix(GuestSigSetmask, context->sigmask, nullptr); + Aps5ContextRestore_nid_no_patch(&frame); } +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); + return setcontext_nid_postfix(next); +} + } diff --git a/core/libs/prx/libkernel/Time/Ktimer.cpp b/core/libs/prx/libkernel/Time/Ktimer.cpp new file mode 100644 index 0000000000..933a20bc60 --- /dev/null +++ b/core/libs/prx/libkernel/Time/Ktimer.cpp @@ -0,0 +1,151 @@ +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" +#include +#include + +namespace { + +constexpr int GuestEfault = 14; +constexpr int GuestEinval = 22; +constexpr int GuestEagain = 35; + +std::int64_t Fail(int error) { + *__error_nid_postfix() = error; + return -1; +} + +constexpr int ClockRealtime = 0; +constexpr int ClockMonotonic = 4; +constexpr int SigevNone = 0; +constexpr int SigevSignal = 1; +constexpr int SigevThreadId = 4; +constexpr int SignalMax = 128; +constexpr int TimerAbstime = 1; +constexpr int FirstTimer = 3; +constexpr int TimerMax = 32; +constexpr long long NanosecondsPerSecond = 1000000000; +constexpr long long TickNanoseconds = 1000000; + +using Nanoseconds = __int128; + +struct Ktimer { + bool live = false; + int clock = ClockRealtime; + Nanoseconds value = 0; + Nanoseconds interval = 0; +}; + +std::mutex g_ktimerLock; +Ktimer g_ktimers[TimerMax]; + +Nanoseconds ToNanoseconds(const KernelTimespec& time) { + return static_cast(time.tv_sec) * NanosecondsPerSecond + time.tv_nsec; +} + +KernelTimespec ToTimespec(Nanoseconds time) { + return {static_cast(time / NanosecondsPerSecond), + static_cast(time % NanosecondsPerSecond)}; +} + +Nanoseconds ClockNow(int clock) { + KernelTimespec now{}; + clock_gettime_nid_postfix(clock, &now); + + return ToNanoseconds(now); +} + +bool FixTimespec(KernelTimespec& time) { + if (time.tv_sec < 0 || time.tv_nsec < 0 || time.tv_nsec >= NanosecondsPerSecond) return false; + if (time.tv_sec == 0 && time.tv_nsec != 0 && time.tv_nsec < TickNanoseconds) time.tv_nsec = TickNanoseconds; + return true; +} + +Ktimer* FindTimer(int id) { + if (id < FirstTimer || id >= TimerMax || !g_ktimers[id].live) return nullptr; + return &g_ktimers[id]; +} + +void Expire(Ktimer& timer, Nanoseconds now) { + if (timer.value == 0 || now < timer.value) return; + if (timer.interval == 0) { + timer.value = 0; + return; + } + timer.value += ((now - timer.value) / timer.interval + 1) * timer.interval; +} + +GuestItimerspec Remaining(Ktimer& timer) { + const Nanoseconds now = ClockNow(timer.clock); + Expire(timer, now); + GuestItimerspec result{ToTimespec(timer.interval), {}}; + if (timer.value != 0) result.it_value = timer.value > now ? ToTimespec(timer.value - now) : KernelTimespec{0, 1}; + return result; +} + +} + +extern "C" { + +int APS5_VABI ktimer_create_nid_postfix(int clock, const GuestSigevent* event, int* timerid) { + if (clock != ClockRealtime && clock != ClockMonotonic) return static_cast(Fail(GuestEinval)); + if (event != nullptr) { + if (event->notify != SigevNone && event->notify != SigevSignal && event->notify != SigevThreadId) + return static_cast(Fail(GuestEinval)); + if (event->notify != SigevNone && (event->signo <= 0 || event->signo > SignalMax)) + return static_cast(Fail(GuestEinval)); + } + std::lock_guard lock(g_ktimerLock); + int id = FirstTimer; + while (id < TimerMax && g_ktimers[id].live) ++id; + if (id == TimerMax) return static_cast(Fail(GuestEagain)); + if (timerid == nullptr) return static_cast(Fail(GuestEfault)); + if (event == nullptr || event->notify != SigevNone) NotImplemented_nid_no_patch("ktimer_create delivering a signal"); + g_ktimers[id] = {true, clock, 0, 0}; + *timerid = id; + return 0; +} + +int APS5_VABI ktimer_delete_nid_postfix(int id) { + std::lock_guard lock(g_ktimerLock); + Ktimer* timer = FindTimer(id); + if (timer == nullptr) return static_cast(Fail(GuestEinval)); + *timer = {}; + return 0; +} + +int APS5_VABI ktimer_settime_nid_postfix(int id, int flags, const GuestItimerspec* value, GuestItimerspec* oldValue) { + if (value == nullptr) return static_cast(Fail(GuestEfault)); + std::lock_guard lock(g_ktimerLock); + Ktimer* timer = FindTimer(id); + if (timer == nullptr) return static_cast(Fail(GuestEinval)); + GuestItimerspec next = *value; + if (!FixTimespec(next.it_value)) return static_cast(Fail(GuestEinval)); + const bool armed = next.it_value.tv_sec != 0 || next.it_value.tv_nsec != 0; + if (armed && !FixTimespec(next.it_interval)) return static_cast(Fail(GuestEinval)); + if (!armed) next.it_interval = {}; + if (oldValue != nullptr) *oldValue = Remaining(*timer); + timer->interval = ToNanoseconds(next.it_interval); + timer->value = ToNanoseconds(next.it_value); + if (armed && (flags & TimerAbstime) == 0) timer->value += ClockNow(timer->clock); + return 0; +} + +int APS5_VABI ktimer_gettime_nid_postfix(int id, GuestItimerspec* value) { + std::lock_guard lock(g_ktimerLock); + Ktimer* timer = FindTimer(id); + if (timer == nullptr) return static_cast(Fail(GuestEinval)); + if (value == nullptr) return static_cast(Fail(GuestEfault)); + *value = Remaining(*timer); + return 0; +} + +int APS5_VABI ktimer_getoverrun_nid_postfix(int id) { + std::lock_guard lock(g_ktimerLock); + if (FindTimer(id) == nullptr) return static_cast(Fail(GuestEinval)); + return 0; +} + +} diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index 67ed023339..60703eeb0e 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -1,3 +1,7 @@ +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" #include "prx/libkernel/Time/include/SleepWake.hpp" @@ -33,23 +37,19 @@ #ifdef __linux__ #include #include +#include +#include #endif // __linux__ #endif -extern "C" int* APS5_VABI __error_nid_postfix(); - static constexpr int GUEST_EFAULT = 14; static constexpr int GUEST_EINVAL = 22; static constexpr int GUEST_CLOCK_REALTIME = 0; static constexpr int GUEST_CLOCK_THREAD_CPUTIME_ID = 14; +static constexpr int GUEST_CLOCK_PROCESS_CPUTIME_ID = 15; static bool IsCpuClock(int clockId) { - const auto bits = static_cast(clockId); - if ((bits & CPU_CLOCK_BIT) == 0) - return false; - if ((bits & CPU_CLOCK_PROCESS_BIT) != 0) - throw std::runtime_error("clock: unsupported process CPU clock_id " + std::to_string(clockId)); - return true; + return (static_cast(clockId) & CPU_CLOCK_BIT) != 0; } static std::uint64_t RawMonotonicNanos() { @@ -119,16 +119,6 @@ static const bool g_timerResolutionRaised = [] { return raised; }(); -extern "C" { -int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); -int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); -int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -} - static void ReportTimedWait(const char* name, const std::function& body) { std::vector samples(200); for (auto& sample : samples) { @@ -152,11 +142,13 @@ static const bool g_timerSelfTest = [] { scePthreadCondInit(&cond, nullptr, nullptr); scePthreadMutexInit(&guestMutex, nullptr, nullptr); scePthreadMutexLock(&guestMutex); + ReportTimedWait("scePthreadCondTimedwait(1000 us)", [&] { scePthreadCondTimedwait(&cond, &guestMutex, 1000); }); ReportTimedWait("scePthreadCondTimedwait(13000 us)", [&] { scePthreadCondTimedwait(&cond, &guestMutex, 13000); }); scePthreadMutexUnlock(&guestMutex); scePthreadMutexDestroy(&guestMutex); scePthreadCondDestroy(&cond); + ReportTimedWait("sceKernelUsleep(1000)", [&] { sceKernelUsleep_nid_postfix(1000); }); ReportTimedWait("Sleep(1)", [&] { Sleep(1); }); HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr); @@ -169,6 +161,45 @@ static const bool g_timerSelfTest = [] { }(); #endif +namespace { + +class CurrentCpuPin { +public: + CurrentCpuPin() { +#ifdef _WIN32 + previous = SetThreadAffinityMask(GetCurrentThread(), DWORD_PTR{1} << GetCurrentProcessorNumber()); +#elif defined(__linux__) + const int cpu = sched_getcpu(); + if (cpu < 0 || cpu >= CPU_SETSIZE || pthread_getaffinity_np(pthread_self(), sizeof(previous), &previous) != 0) return; + cpu_set_t one; + CPU_ZERO(&one); + CPU_SET(cpu, &one); + pinned = pthread_setaffinity_np(pthread_self(), sizeof(one), &one) == 0; +#endif + } + + ~CurrentCpuPin() { +#ifdef _WIN32 + if (previous != 0) SetThreadAffinityMask(GetCurrentThread(), previous); +#elif defined(__linux__) + if (pinned) pthread_setaffinity_np(pthread_self(), sizeof(previous), &previous); +#endif + } + + CurrentCpuPin(const CurrentCpuPin&) = delete; + CurrentCpuPin& operator=(const CurrentCpuPin&) = delete; + +private: +#ifdef _WIN32 + DWORD_PTR previous = 0; +#elif defined(__linux__) + cpu_set_t previous{}; + bool pinned = false; +#endif +}; + +} + extern "C" { std::uint64_t APS5_VABI sceKernelGetProcessTime() { @@ -291,8 +322,9 @@ static int SleepForRequest(const KernelTimespec* rqtp) { 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(rqtp->tv_sec) * 1000000000ULL + + CancellableSleep(static_cast(std::min(static_cast(rqtp->tv_sec), longestSleepSeconds)) * 1000000000ULL + static_cast(rqtp->tv_nsec)); } return 0; @@ -396,8 +428,11 @@ static bool ProcessCpuResolutionNanos(std::uint64_t* nanos) { static int ReadClock(int clockId, KernelTimespec* tp) { if (IsCpuClock(clockId)) { + const int id = static_cast(static_cast(clockId) & CPU_CLOCK_ID_MASK); + const bool process = (static_cast(clockId) & CPU_CLOCK_PROCESS_BIT) != 0; + if (process && id == getpid_nid_postfix()) return ReadClock(GUEST_CLOCK_PROCESS_CPUTIME_ID, tp); std::uint64_t nanos = 0; - if (!GuestThreadCpuNanos(static_cast(static_cast(clockId) & CPU_CLOCK_ID_MASK), &nanos)) { + if (process || !GuestThreadCpuNanos(id, &nanos)) { *__error_nid_postfix() = GUEST_EINVAL; return -1; } @@ -506,6 +541,9 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { return 0; } +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; @@ -525,8 +563,8 @@ int APS5_VABI gettimeofday_nid_postfix(KernelTimeval* tv, KernelTimezone* tz) { tv->tv_usec = static_cast(native.tv_usec); #endif if (tz != nullptr) { - tz->tz_minuteswest = 0; - tz->tz_dsttime = 0; + std::lock_guard lock(guestTimezoneMutex); + *tz = guestTimezone; } return 0; } @@ -629,6 +667,23 @@ int APS5_VABI clock_settime_nid_postfix(int clockId, const KernelTimespec* tp) { throw std::runtime_error(std::string("clock_settime: setting CLOCK_REALTIME to ") + requested + " is not supported, the host clock is not changed"); } +int SetTimeOfDay_nid_no_patch(const KernelTimeval* tv, const KernelTimezone* tz) { + if (tv != nullptr && (tv->tv_usec < 0 || tv->tv_usec >= 1000000 || tv->tv_sec < 0)) { + *__error_nid_postfix() = GUEST_EINVAL; + return -1; + } + if (tv != nullptr) { + char requested[48]; + std::snprintf(requested, sizeof(requested), "%lld.%06lld", static_cast(tv->tv_sec), static_cast(tv->tv_usec)); + throw std::runtime_error(std::string("settimeofday: setting the time to ") + requested + " is not supported, the host clock is not changed"); + } + if (tz != nullptr) { + std::lock_guard lock(guestTimezoneMutex); + guestTimezone = *tz; + } + return 0; +} + // --------------------------------------------------------------------------- // Moved as-is (not yet implemented) from the monolithic libkernel/Export.cpp. // --------------------------------------------------------------------------- @@ -727,6 +782,7 @@ uint64_t APS5_VABI sceKernelReadTsc(void) { uint64_t APS5_VABI sceKernelGetTscFrequency(void) { static const std::uint64_t frequency = [] { + const CurrentCpuPin pin; const std::uint64_t startNanos = RawMonotonicNanos(); const std::uint64_t startTicks = __rdtsc(); TimedWait::SleepNanos(20000000ULL); diff --git a/core/libs/prx/libkernel/Time/TimedWait.cpp b/core/libs/prx/libkernel/Time/TimedWait.cpp index 7c171db399..bb5ba2cdca 100644 --- a/core/libs/prx/libkernel/Time/TimedWait.cpp +++ b/core/libs/prx/libkernel/Time/TimedWait.cpp @@ -11,6 +11,9 @@ #include #else #include +#include +#include +#include #include #endif @@ -389,6 +392,91 @@ void Condition::NotifyAll() { coarse.notify_all(); } +namespace { + +constinit thread_local std::atomic deferralDepth{0}; +constinit thread_local std::atomic deferredSignal{0}; + +class DeliveryAllowance { +public: + DeliveryAllowance() : depth(deferralDepth.exchange(0)) { DeliverDeferred(); } + ~DeliveryAllowance() { deferralDepth.store(depth); } + DeliveryAllowance(const DeliveryAllowance&) = delete; + DeliveryAllowance& operator=(const DeliveryAllowance&) = delete; + +private: + int depth; +}; + +} + +DeliveryDeferral::DeliveryDeferral() { + deferralDepth.fetch_add(1); +} + +DeliveryDeferral::~DeliveryDeferral() { + deferralDepth.fetch_sub(1); +} + +void DeliverDeferred() { + if (deferralDepth.load() != 0) return; + const int signum = deferredSignal.exchange(0); + if (signum != 0) pthread_kill(pthread_self(), signum); +} + +bool DeferDelivery(int signum) { + if (deferralDepth.load() == 0) return false; + deferredSignal.store(signum); + return true; +} + +#ifdef __APPLE__ +SingleWaiterCondition::SingleWaiterCondition() : semaphore(dispatch_semaphore_create(0)) { + if (semaphore == nullptr) throw std::runtime_error("TimedWait: cannot create a wait semaphore"); +} + +SingleWaiterCondition::~SingleWaiterCondition() { + dispatch_release(semaphore); +} + +void SingleWaiterCondition::NotifyOne() { + dispatch_semaphore_signal(semaphore); +} + +bool SingleWaiterCondition::waitSignal(const std::uint64_t* deadlineNanos) { + DeliveryAllowance allowance; + if (deadlineNanos == nullptr) return dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) == 0; + const std::uint64_t now = NowNanos(); + const std::uint64_t remaining = *deadlineNanos > now ? *deadlineNanos - now : 0; + const dispatch_time_t deadline = remaining >= static_cast(std::numeric_limits::max()) ? DISPATCH_TIME_FOREVER : dispatch_time(DISPATCH_TIME_NOW, static_cast(remaining)); + return dispatch_semaphore_wait(semaphore, deadline) == 0; +} +#else +SingleWaiterCondition::SingleWaiterCondition() { + if (sem_init(&semaphore, 0, 0) != 0) throw std::system_error(errno, std::generic_category(), "TimedWait: cannot create a wait semaphore"); +} + +SingleWaiterCondition::~SingleWaiterCondition() { + sem_destroy(&semaphore); +} + +void SingleWaiterCondition::NotifyOne() { + sem_post(&semaphore); +} + +bool SingleWaiterCondition::waitSignal(const std::uint64_t* deadlineNanos) { + DeliveryAllowance allowance; + timespec deadline{}; + if (deadlineNanos != nullptr) deadline = {static_cast(*deadlineNanos / 1000000000ULL), static_cast(*deadlineNanos % 1000000000ULL)}; + for (;;) { + const int result = deadlineNanos == nullptr ? sem_wait(&semaphore) : sem_clockwait(&semaphore, CLOCK_MONOTONIC, &deadline); + if (result == 0) return true; + if (errno == ETIMEDOUT) return false; + if (errno != EINTR) throw std::system_error(errno, std::generic_category(), "TimedWait: cannot wait for a wait semaphore"); + } +} +#endif + #endif void BindThreadWaitState(std::atomic* state) { diff --git a/core/libs/prx/libkernel/Time/include/TimedWait.hpp b/core/libs/prx/libkernel/Time/include/TimedWait.hpp index 2f1725819d..9ad4e09f03 100644 --- a/core/libs/prx/libkernel/Time/include/TimedWait.hpp +++ b/core/libs/prx/libkernel/Time/include/TimedWait.hpp @@ -7,6 +7,12 @@ #include #include +#ifdef __APPLE__ +#include +#elif !defined(_WIN32) +#include +#endif + namespace TimedWait { bool Coarse(); @@ -21,9 +27,6 @@ void WakeChangeSleepers(); struct Waiter; -#ifdef _WIN32 -constexpr unsigned PARK_SPINS = 1000; - class DeliveryDeferral { public: DeliveryDeferral(); @@ -33,6 +36,10 @@ public: }; void DeliverDeferred(); + +#ifdef _WIN32 +constexpr unsigned PARK_SPINS = 1000; + void SpinPause(); Waiter* BeginPark(const void* key); bool FinishPark(const void* key, Waiter* waiter, const std::uint64_t* deadlineNanos); @@ -132,6 +139,51 @@ private: std::condition_variable_any coarse; }; +#ifdef _WIN32 +using SingleWaiterCondition = Condition; +#else +bool DeferDelivery(int signum); + +class SingleWaiterCondition { +public: + SingleWaiterCondition(); + ~SingleWaiterCondition(); + SingleWaiterCondition(const SingleWaiterCondition&) = delete; + SingleWaiterCondition& operator=(const SingleWaiterCondition&) = delete; + + void NotifyOne(); + + template + void Wait(TLock& lock, TPredicate predicate) { + while (!predicate()) { + lock.unlock(); + waitSignal(nullptr); + lock.lock(); + } + } + + template + bool WaitUntil(TLock& lock, std::uint64_t deadlineNanos, TPredicate predicate) { + while (!predicate()) { + lock.unlock(); + const bool signaled = waitSignal(&deadlineNanos); + lock.lock(); + if (!signaled) return predicate(); + } + return true; + } + +private: + bool waitSignal(const std::uint64_t* deadlineNanos); + +#ifdef __APPLE__ + dispatch_semaphore_t semaphore; +#else + sem_t semaphore; +#endif +}; +#endif + } // namespace TimedWait #endif diff --git a/core/libs/prx/libkernel/Unimplemented.cpp b/core/libs/prx/libkernel/Unimplemented.cpp index 3dd75f72d8..20c5d80c94 100644 --- a/core/libs/prx/libkernel/Unimplemented.cpp +++ b/core/libs/prx/libkernel/Unimplemented.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,11 +6,6 @@ extern "C" { -int APS5_VABI __tls_get_addr_nid_postfix(void) { - NotImplemented_nid_no_patch("vNe1w4diLCs"); - return 0; -} - int APS5_VABI physhm_open_nid_postfix(void) { NotImplemented_nid_no_patch("physhm_open"); return 0; @@ -133,3 +129,12 @@ int APS5_VABI sceKernelGetProcessAbiType() { } } + +extern "C" { + +int APS5_VABI __Ux86_64_setcontext() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libkernel/include/Api.hpp b/core/libs/prx/libkernel/include/Api.hpp new file mode 100644 index 0000000000..c9d62fe917 --- /dev/null +++ b/core/libs/prx/libkernel/include/Api.hpp @@ -0,0 +1,807 @@ +#pragma once + +#include "prx/libSceLibcInternal/include/Spawn.hpp" +#include "prx/libkernel/System/include/UserContext.hpp" +#include "prx/libkernel/File/include/KernelStatfs.hpp" +#include "prx/libkernel/Apr/include/AprCommandBuffer.hpp" +#include "prx/libc/include/specifics/linux/ElfTypes.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include + +struct KernelIovec { + void* base; + std::size_t length; +}; + +struct KernelTlsIndex { + std::uint64_t module; + std::uint64_t offset; +}; + +using GuestHandler = void (APS5_VABI *)(int); + +struct GuestItimerspec { + KernelTimespec it_interval; + KernelTimespec it_value; +}; + +struct GuestItimerval { + KernelTimeval interval; + KernelTimeval value; +}; + +struct GuestMsghdr { + void* name; + std::uint32_t nameLength; + KernelIovec* iov; + int iovLength; + void* control; + std::uint32_t controlLength; + int flags; +}; + +struct GuestOnce { + std::int32_t state; + void* mutex; +}; + +struct GuestPollDescriptor { + int descriptor; + short events; + short revents; +}; + +struct GuestResourceLimit { + std::int64_t rlim_cur; + std::int64_t rlim_max; +}; + +struct GuestResourceUsage { + KernelTimeval ru_utime; + KernelTimeval ru_stime; + std::int64_t ru_maxrss; + std::int64_t ru_ixrss; + std::int64_t ru_idrss; + std::int64_t ru_isrss; + std::int64_t ru_minflt; + std::int64_t ru_majflt; + std::int64_t ru_nswap; + std::int64_t ru_inblock; + std::int64_t ru_oublock; + std::int64_t ru_msgsnd; + std::int64_t ru_msgrcv; + std::int64_t ru_nsignals; + std::int64_t ru_nvcsw; + std::int64_t ru_nivcsw; +}; + +struct GuestRtPrio { + std::uint16_t type; + std::uint16_t prio; +}; + +struct GuestSendfileHeaders { + KernelIovec* headers; + int headerCount; + KernelIovec* trailers; + int trailerCount; +}; + +struct GuestSigaction { + std::uintptr_t handler; + int flags; + GuestSignalSet mask; +}; + +struct GuestSigevent { + int notify; + int signo; + std::int64_t value; + std::int64_t extra[8]; +}; + +struct GuestStack { + void* sp; + std::size_t size; + int flags; +}; + +struct PthreadBarrierPrivate { + std::mutex mutex; + std::condition_variable changed; + unsigned count; + unsigned arrived = 0; + unsigned active = 0; + std::uint64_t generation = 0; +}; + +struct PthreadBarrierattrPrivate { + int pshared = 0; +}; + +struct PthreadSpinlockPrivate { + std::atomic owner{}; +}; + +struct SceKernelProsperoSystemSwVersion { + std::uint64_t size; + char versionString[0x1C]; + std::uint32_t version; +}; + +struct SceKernelSystemSwVersion { + std::uint64_t size; + char versionString[0x1C]; + std::uint32_t version; +}; + +extern "C" { +int APS5_VABI sceKernelCreateEventFlag(KernelEventFlag* ef, const char* name, std::uint32_t attr, std::uint64_t initPattern, const void* param); +int APS5_VABI sceKernelDeleteEventFlag(KernelEventFlag ef); +int APS5_VABI sceKernelSetEventFlag(KernelEventFlag ef, std::uint64_t bitPattern); +int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, std::uint64_t setPattern, int* numWaitThreads); + +} + +extern "C" { + +void* APS5_VABI __tls_get_addr_nid_postfix(const KernelTlsIndex* index); +int APS5_VABI physhm_open_nid_postfix(void); +int APS5_VABI physhm_unlink_nid_postfix(void); +int APS5_VABI sceKernelUnknown03(void); +int APS5_VABI sceKernelUnknown04(void); +int APS5_VABI sceKernelUnknown05(void); +int APS5_VABI sceKernelUnknown06(void); +int APS5_VABI sceKernelUnknown07(void); +int APS5_VABI sceKernelUnknown08(void); +int APS5_VABI sceKernelUnknown09(void); +int APS5_VABI sceKernelUnknown10(void); +int APS5_VABI sceKernelUnknown11(void); +int APS5_VABI sceKernelUnknown12(void); +int APS5_VABI sceKernelUnknown13(void); +int APS5_VABI sceKernelUnknown14(void); +int APS5_VABI sceKernelUnknown15(void); +int APS5_VABI sceKernelUnknown16(void); +int APS5_VABI sceKernelUnknown17(void); +int APS5_VABI sceKernelUnknown18(void); +int APS5_VABI sceKernelUnknown19(void); +int APS5_VABI sceKernelPrintBacktraceWithModuleInfo(void); +int APS5_VABI sceKernelGetProcessAbiType(); +int APS5_VABI __Ux86_64_setcontext(); +int APS5_VABI sceKernelAioDeleteRequest(int32_t id, int32_t* ret); +int APS5_VABI sceKernelAioDeleteRequests(int32_t* id, int32_t num, int32_t* ret); +int APS5_VABI sceKernelAioCancelRequest(int32_t id, int32_t* state); +int APS5_VABI sceKernelAioCancelRequests(int32_t* id, int32_t num, int32_t* state); +int APS5_VABI sceKernelAioInitializeImpl(void* param, int32_t size); +void APS5_VABI sceKernelAioInitializeParam(void* param); +int APS5_VABI sceKernelAioSetParam(KernelAioSchedulingParam* param, int32_t schedulingWindowSize, int32_t delayedCountLimit, uint32_t enableSplit, uint32_t splitSize, uint32_t splitChunkSize); +int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest* req, int32_t size, int32_t prio, int32_t* id); +int APS5_VABI sceKernelAioSubmitWriteCommands(KernelAioRwRequest* req, int32_t size, int32_t prio, int32_t* id); +int APS5_VABI sceKernelAioSubmitReadCommandsMultiple(KernelAioRwRequest* req, int32_t size, int32_t prio, int32_t* id); +int APS5_VABI sceKernelAioSubmitWriteCommandsMultiple(KernelAioRwRequest* req, int32_t size, int32_t prio, int32_t* id); +int APS5_VABI sceKernelAioPollRequest(int32_t id, int32_t* state); +int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state, uint32_t mode, uint32_t* usec); +int APS5_VABI sceKernelAioWaitRequest(int32_t id, int32_t* state, uint32_t* usec); +int APS5_VABI sceKernelAioPollRequests(int32_t* id, int32_t num, int32_t* state); +int APS5_VABI sceKernelAprResolveFilepathsToIds(const char** paths, uint32_t count, uint32_t* ids, uint32_t* errorIndex); +int APS5_VABI sceKernelAprResolveFilepathsToIdsAndFileSizes(const char** paths, uint32_t count, uint32_t* ids, uint64_t* sizes, uint32_t* errorIndex); +int APS5_VABI sceKernelAprResolveFilepathsWithPrefixToIdsAndFileSizes(const char* prefix, const char** paths, uint32_t count, uint32_t* ids, uint64_t* sizes, uint32_t* errorIndex); +int APS5_VABI sceKernelAprResolveFilepathsWithPrefixToIds(const char* prefix, const char** paths, uint32_t count, uint32_t* ids, uint32_t* errorIndex); +int APS5_VABI sceKernelAprGetFileSize(uint32_t id, uint64_t* size); +int APS5_VABI sceKernelAprGetFileStat(uint32_t id, FileStat* stat); +int APS5_VABI sceKernelAprSubmitCommandBuffer(const Apr::CommandBufferObject* buffer, uint32_t priority); +int APS5_VABI sceKernelAprSubmitCommandBufferAndGetId(const Apr::CommandBufferObject* buffer, uint32_t priority, uint32_t* id); +int APS5_VABI sceKernelAprSubmitCommandBufferAndGetResult(const Apr::CommandBufferObject* buffer, uint32_t priority, uint32_t* result, uint32_t* id); +int APS5_VABI sceKernelAprWaitCommandBuffer(uint32_t id); +int APS5_VABI sceKernelAprResolveFilepathsToIdsForEach(const char** paths, uint32_t count, uint32_t* ids, int* results); +int APS5_VABI sceKernelAprResolveFilepathsToIdsAndFileSizesForEach(const char** paths, uint32_t count, uint32_t* ids, uint64_t* sizes, int* results); +int APS5_VABI sceKernelAprResolveFilepathsWithPrefixToIdsForEach(const char* prefix, const char** paths, uint32_t count, uint32_t* ids, int* results); +int APS5_VABI sceKernelAprResolveFilepathsWithPrefixToIdsAndFileSizesForEach(const char* prefix, const char** paths, uint32_t count, uint32_t* ids, uint64_t* sizes, int* results); +int APS5_VABI sceKernelWriteModifyMtypeProtectCommand(void* dst, std::uint64_t address, std::uint64_t size, std::uint64_t type, std::uint64_t protection, std::uint64_t mask, std::uint64_t* outSize); +int APS5_VABI sceKernelWriteRemapIntoPrtCommand(std::uint32_t* dst, std::uint64_t address, std::uint64_t source, std::uint64_t size, int protection, int opcode, std::uint64_t* outSize); +int APS5_VABI sceKernelAllocateDirectMemory(int64_t searchStart, int64_t searchEnd, size_t len, size_t alignment, int memoryType, int64_t* physAddrOut); +int APS5_VABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, int64_t* physAddrOut); +int APS5_VABI sceKernelAvailableDirectMemorySize(int64_t searchStart, int64_t searchEnd, size_t alignment, int64_t* physAddrOut, size_t* sizeOut); +int APS5_VABI sceKernelDirectMemoryQuery(int64_t offset, int flags, void* info, size_t infoSize); +int APS5_VABI sceKernelGetDirectMemoryType(int64_t offset, int* memoryType, int64_t* start, int64_t* end); +size_t APS5_VABI sceKernelGetDirectMemorySize(void); +int APS5_VABI sceKernelEnableDmemAliasing(void); +int APS5_VABI sceKernelMapDirectMemory(void** addr, size_t len, int prot, int flags, int64_t directMemoryStart, size_t alignment); +int APS5_VABI sceKernelMapDirectMemory2(void** addr, size_t len, int type, int prot, int flags, int64_t directMemoryStart, size_t alignment); +int APS5_VABI sceKernelMapFlexibleMemory(void** addrInOut, size_t len, int prot, int flags); +int APS5_VABI sceKernelMapNamedDirectMemory(void** addr, size_t len, int prot, int flags, int64_t directMemoryStart, size_t alignment, const char* name); +int32_t APS5_VABI sceKernelMapNamedFlexibleMemory(void** addrInOut, size_t len, int prot, int flags, const char* name); +int32_t APS5_VABI sceKernelMapNamedFlexibleMemoryInternal(void** addrInOut, size_t len, int prot, int flags, const char* name); +int APS5_VABI sceKernelMprotect(const void* addr, size_t len, int prot); +int APS5_VABI sceKernelMunmap(uint64_t vaddr, size_t len); +int APS5_VABI sceKernelMmap(void* addr, size_t len, int prot, int flags, int fd, int64_t offset, void** res); +int APS5_VABI sceKernelReleaseFlexibleMemory(void* addr, size_t len); +int APS5_VABI sceKernelReleaseDirectMemory(int64_t start, size_t len); +int APS5_VABI sceKernelReserveVirtualRange(void** addr, size_t len, int flags, size_t alignment); +int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, uint64_t infoSize); +int APS5_VABI sceKernelCheckedReleaseDirectMemory(int64_t start, size_t len); +int APS5_VABI sceKernelMtypeprotect(const void* addr, size_t len, int type, int prot); +int APS5_VABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, int* prot); +int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end); +int APS5_VABI sceKernelAvailableFlexibleMemorySize(size_t* size); +int APS5_VABI sceKernelConfiguredFlexibleMemorySize(size_t* size); +int APS5_VABI sceKernelInternalMemoryGetAvailableSize(std::size_t* size); +int APS5_VABI sceKernelSetVirtualRangeName(const void* addr, uint64_t len, const char* name); +int APS5_VABI sceKernelClearVirtualRangeName(const void* addr, uint64_t len); +int APS5_VABI sceKernelGetPageTableStats(int* cpuTotal, int* cpuAvailable, int* gpuTotal, int* gpuAvailable); +int APS5_VABI sceKernelGetPrtAperture(int index, void** addr, size_t* len); +int APS5_VABI sceKernelSetPrtAperture(int index, void* addr, size_t len); +int APS5_VABI sceKernelBatchMap(KernelBatchMapEntry* entries, int numEntries, int* numEntriesOut); +int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int numEntries, int* numEntriesOut, int flags); +int APS5_VABI sceKernelMlock_nid_postfix(void* address, std::uint64_t length); +int APS5_VABI sceKernelMunlock_nid_postfix(void* address, std::uint64_t length); +int APS5_VABI sceKernelMunlockall_nid_postfix(); +int APS5_VABI sceKernelJitCreateSharedMemory(const char* name, std::size_t len, int maxProt, int* fdOut); +int APS5_VABI sceKernelJitCreateAliasOfSharedMemory(int fd, int maxProt, int* fdOut); +int APS5_VABI sceKernelJitMapSharedMemory(int fd, int prot, void** startOut); +int APS5_VABI sceKernelMemoryPoolBatch(const KernelMemoryPoolBatchEntry* entries, int numEntries, int* numEntriesOut, int flags); +int APS5_VABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags); +int APS5_VABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags); +int APS5_VABI sceKernelMemoryPoolExpand(int64_t searchStart, int64_t searchEnd, size_t len, size_t alignment, int64_t* physAddrOut); +int APS5_VABI sceKernelMemoryPoolGetBlockStats(KernelMemoryPoolBlockStats* output, size_t outputSize); +int APS5_VABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags, void** addrOut); +void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protection, int flags, int descriptor, std::int64_t offset) noexcept; +int APS5_VABI munmap_nid_postfix(void* address, std::size_t length) noexcept; +int APS5_VABI mprotect_nid_postfix(void* address, std::size_t length, int protection) noexcept; +int APS5_VABI mlock_nid_postfix(const void* address, std::size_t length); +int APS5_VABI munlock_nid_postfix(const void* address, std::size_t length); +int APS5_VABI mlockall_nid_postfix(int flags); +int APS5_VABI munlockall_nid_postfix(); +int APS5_VABI msync_nid_postfix(void* address, std::size_t length, int flags); +int APS5_VABI madvise_nid_postfix(void* address, std::size_t length, int advice); +int APS5_VABI mincore_nid_postfix(const void* address, std::size_t length, char* vec); +int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); +int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); +int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo); +int APS5_VABI sceKernelAddUserEvent(KernelEqueue eq, int id); +int APS5_VABI sceKernelAddUserEventEdge(KernelEqueue eq, int id); +int APS5_VABI sceKernelTriggerUserEvent(KernelEqueue eq, int id, void* udata); +int APS5_VABI sceKernelDeleteUserEvent(KernelEqueue eq, int id); +int APS5_VABI sceKernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts, void* udata); +int APS5_VABI sceKernelDeleteHRTimerEvent(KernelEqueue eq, int id); +int APS5_VABI sceKernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec, void* udata); +int APS5_VABI sceKernelDeleteTimerEvent(KernelEqueue eq, int id); +int APS5_VABI sceKernelAddAmprEvent(KernelEqueue eq, int id, void* udata); +int APS5_VABI sceKernelAddAmprSystemEvent(KernelEqueue eq, int id, void* udata); +int APS5_VABI sceKernelDeleteAmprEvent(KernelEqueue eq, int id); +int APS5_VABI sceKernelDeleteAmprSystemEvent(KernelEqueue eq, int id); +int APS5_VABI sceKernelAddReadEvent(KernelEqueue eq, int fd, std::size_t size, void* udata); +int APS5_VABI sceKernelDeleteReadEvent(KernelEqueue eq, int fd); +int APS5_VABI sceKernelAddWriteEvent(KernelEqueue eq, int fd, std::size_t size, void* udata); +int APS5_VABI sceKernelDeleteWriteEvent(KernelEqueue eq, int fd); +intptr_t APS5_VABI sceKernelGetEventData(const KernelEvent* ev); +int APS5_VABI sceKernelGetEventError(const KernelEvent* ev); +intptr_t APS5_VABI sceKernelGetEventFflags(const KernelEvent* ev); +int APS5_VABI sceKernelGetEventFilter(const KernelEvent* ev); +uintptr_t APS5_VABI sceKernelGetEventId(const KernelEvent* ev); +void* APS5_VABI sceKernelGetEventUserData(const KernelEvent* ev); +int APS5_VABI sceKernelAddFileEvent(KernelEqueue eq, int fd, int watch, void* udata); +int APS5_VABI sceKernelDeleteFileEvent(KernelEqueue eq, int fd); +int APS5_VABI kqueue_nid_postfix(); +int APS5_VABI kevent_nid_postfix(int descriptor, const KernelEvent* changelist, int nchanges, KernelEvent* eventlist, int nevents, const KernelTimespec* timeout); +int APS5_VABI sceKernelOpenEventFlag(KernelEventFlag* ef, const char* name); +int APS5_VABI sceKernelCloseEventFlag(KernelEventFlag ef); +int APS5_VABI sceKernelClearEventFlag(KernelEventFlag ef, uint64_t bitPattern); +int APS5_VABI sceKernelPollEventFlag(KernelEventFlag ef, uint64_t bitPattern, uint32_t waitMode, uint64_t* resultPat); +int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, uint64_t bitPattern, uint32_t waitMode, uint64_t* resultPat, KernelUseconds* timeout); +int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int mode); +int APS5_VABI shm_unlink_nid_postfix(const char* path); +int APS5_VABI pipe_nid_postfix(int* descriptors); +int APS5_VABI chmod_nid_postfix(const char* path, int mode); +int APS5_VABI chflags_nid_postfix(const char* path, std::uint64_t flags); +int APS5_VABI fchflags_nid_postfix(int d, std::uint64_t flags); +int APS5_VABI nmount_nid_postfix(const KernelIovec* iov, unsigned int niov, int flags); +int APS5_VABI close_nid_postfix(int d); +int APS5_VABI _close_nid_postfix(int descriptor); +int APS5_VABI dup_nid_postfix(int d); +int APS5_VABI dup2_nid_postfix(int from, int to); +int APS5_VABI flock_nid_postfix(int d, int operation); +int APS5_VABI _fstatfs_nid_postfix(int d, KernelStatfs* buf); +int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb); +int64_t APS5_VABI _fstat_nid_postfix(int d, FileStat* sb); +int APS5_VABI ftruncate_nid_postfix(int d, int64_t length); +int APS5_VABI sceKernelFtruncate(int d, int64_t length); +int64_t APS5_VABI lseek_nid_postfix(int d, int64_t offset, int whence); +int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode); +int APS5_VABI open_nid_postfix(const char* path, int flags, int mode); +int APS5_VABI creat_nid_postfix(const char* path, int mode); +int APS5_VABI _open_nid_postfix(const char* path, int flags, ...); +int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t offset); +int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_t nbytes, int64_t offset); +int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes); +std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size_t count); +int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size); +std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count); +int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb); +int APS5_VABI lstat_nid_postfix(const char* path, FileStat* sb); +int APS5_VABI unlink_nid_postfix(const char* path); +int APS5_VABI sceKernelCheckReachability(const char* path); +int APS5_VABI sceKernelFstat(int d, FileStat* sb); +int APS5_VABI sceKernelFsync(int fd); +int APS5_VABI sceKernelFdatasync(int fd); +int APS5_VABI sceKernelWriteThrottlingStatus(std::uint64_t* status); +int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* basep); +int APS5_VABI sceKernelGetdents(int fd, char* buf, int nbytes); +int APS5_VABI getdirentries_nid_postfix(int fd, char* buf, int nbytes, int64_t* basep); +int APS5_VABI _getdirentries_nid_postfix(int fd, char* buf, int nbytes, int64_t* basep); +int APS5_VABI getdents_nid_postfix(int fd, char* buf, int nbytes); +int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode); +int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset); +int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset); +int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* iov, int iovcnt); +int64_t APS5_VABI sceKernelWritev(int d, const KernelIovec* iov, int iovcnt); +int64_t APS5_VABI sceKernelPreadv(int d, const KernelIovec* iov, int iovcnt, int64_t offset); +int64_t APS5_VABI sceKernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset); +int64_t APS5_VABI preadv_nid_postfix(int d, const KernelIovec* iov, int iovcnt, int64_t offset); +int64_t APS5_VABI pwritev_nid_postfix(int d, const KernelIovec* iov, int iovcnt, int64_t offset); +int APS5_VABI sceKernelRename(const char* from, const char* to); +int APS5_VABI sceKernelRmdir(const char* path); +int APS5_VABI rmdir_nid_postfix(const char* path); +int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode); +int APS5_VABI sceKernelFchmod(int d, std::uint16_t mode); +int APS5_VABI fchmod_nid_postfix(int d, int mode); +int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t length); +int APS5_VABI truncate_nid_postfix(const char* path, std::int64_t length); +int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* times); +int APS5_VABI utimes_nid_postfix(const char* path, const KernelTimeval* times); +int APS5_VABI lutimes_nid_postfix(const char* path, const KernelTimeval* times); +int APS5_VABI sceKernelFutimes(int d, const KernelTimeval* times); +int APS5_VABI futimes_nid_postfix(int d, const KernelTimeval* times); +int APS5_VABI fsync_nid_postfix(int fd); +int APS5_VABI fdatasync_nid_postfix(int fd); +int64_t APS5_VABI readv_nid_postfix(int d, const KernelIovec* iov, int iovcnt); +int64_t APS5_VABI writev_nid_postfix(int d, const KernelIovec* iov, int iovcnt); +int64_t APS5_VABI _readv_nid_postfix(int d, const KernelIovec* iov, int iovcnt); +int64_t APS5_VABI _writev_nid_postfix(int d, const KernelIovec* iov, int iovcnt); +int APS5_VABI cfsetspeed_nid_postfix(void* attributes, std::uint32_t speed); +int APS5_VABI tcgetattr_nid_postfix(int descriptor, void* attributes); +int APS5_VABI tcsetattr_nid_postfix(int descriptor, int action, const void* attributes); +int APS5_VABI tcsendbreak_nid_postfix(int descriptor, int duration); +int APS5_VABI tcdrain_nid_postfix(int descriptor); +int APS5_VABI tcflush_nid_postfix(int descriptor, int queue); +int APS5_VABI tcflow_nid_postfix(int descriptor, int action); +int APS5_VABI tcgetpgrp_nid_postfix(int descriptor); +int APS5_VABI tcsetpgrp_nid_postfix(int descriptor, int group); +char* APS5_VABI dlerror_nid_postfix(); +void* APS5_VABI dlopen_nid_postfix(const char* path, int flags); +void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name); +int APS5_VABI dlclose_nid_postfix(void* handle); +int APS5_VABI sceKernelDlsym(KernelModule handle, const char* symbol, void** addr); +int APS5_VABI sceKernelGetModuleInfoForUnwind(uint64_t addr, int flags, ModuleInfoForUnwind* info); +KernelModule APS5_VABI sceKernelLoadStartModule(const char* moduleFileName, size_t args, const void* argp, uint32_t flags, const KernelLoadModuleOpt* opt, int* res); +int APS5_VABI sceKernelStopUnloadModule(KernelModule handle, size_t args, const void* argp, uint32_t flags, const KernelUnloadModuleOpt* opt, int* res); +int APS5_VABI __elf_phdr_match_addr_nid_postfix(dl_phdr_info* phdrInfo, void* addr); +int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, ModuleInfoEx* info); +int APS5_VABI sceKernelGetModuleList(KernelModule* handles, std::size_t count, std::size_t* actual); +int APS5_VABI sceKernelGetModuleList2(KernelModule* handles, std::size_t count, std::size_t* actual); +int APS5_VABI sceKernelGetModuleInfo(KernelModule handle, ModuleInfo* info); +int APS5_VABI sceKernelGetModuleInfo2(KernelModule handle, ModuleInfo* info); +std::int32_t APS5_VABI sceKernelGetLibkernelTextLocation_nid_postfix(void** address, std::size_t* size); +std::int32_t APS5_VABI sceKernelInternalMemoryGetModuleSegmentInfo_nid_postfix(InternalSegmentInfo* result); +int APS5_VABI pthread_attr_destroy_nid_postfix(PthreadAttr* attr); +int APS5_VABI pthread_attr_get_np_nid_postfix(Pthread thread, PthreadAttr* attr); +int APS5_VABI pthread_attr_getdetachstate_nid_postfix(const PthreadAttr* attr, int* state); +int APS5_VABI pthread_attr_getguardsize_nid_postfix(const PthreadAttr* attr, size_t* guardSize); +int APS5_VABI pthread_attr_getinheritsched_nid_postfix(const PthreadAttr* attr, int* inheritSched); +int APS5_VABI pthread_attr_getschedparam_nid_postfix(const PthreadAttr* attr, KernelSchedParam* param); +int APS5_VABI pthread_attr_getschedpolicy_nid_postfix(const PthreadAttr* attr, int* policy); +int APS5_VABI pthread_attr_getscope_nid_postfix(const PthreadAttr* attr, int* scope); +int APS5_VABI pthread_attr_getstack_nid_postfix(const PthreadAttr* __restrict attr, void** __restrict stackAddr, size_t* __restrict stackSize); +int APS5_VABI pthread_attr_getstackaddr_nid_postfix(const PthreadAttr* __restrict attr, void** __restrict stackAddr); +int APS5_VABI pthread_attr_getstacksize_nid_postfix(const PthreadAttr* attr, size_t* stackSize); +int APS5_VABI pthread_attr_init_nid_postfix(PthreadAttr* attr); +int APS5_VABI pthread_attr_setdetachstate_nid_postfix(PthreadAttr* attr, int state); +int APS5_VABI pthread_attr_setguardsize_nid_postfix(PthreadAttr* attr, size_t guardSize); +int APS5_VABI pthread_attr_setinheritsched_nid_postfix(PthreadAttr* attr, int inheritSched); +int APS5_VABI pthread_attr_setschedparam_nid_postfix(PthreadAttr* attr, const KernelSchedParam* param); +int APS5_VABI pthread_attr_setschedpolicy_nid_postfix(PthreadAttr* attr, int policy); +int APS5_VABI pthread_attr_setscope_nid_postfix(PthreadAttr* attr, int scope); +int APS5_VABI pthread_attr_setstacksize_nid_postfix(PthreadAttr* attr, size_t stackSize); +int APS5_VABI pthread_attr_setstack_nid_postfix(PthreadAttr* attr, void* stackAddr, size_t stackSize); +int APS5_VABI pthread_attr_setsolosched_np_nid_postfix(PthreadAttr* attr, int solosched); +int APS5_VABI pthread_barrierattr_init_nid_postfix(PthreadBarrierattrPrivate** attr); +int APS5_VABI pthread_barrierattr_destroy_nid_postfix(PthreadBarrierattrPrivate** attr); +int APS5_VABI pthread_barrierattr_getpshared_nid_postfix(PthreadBarrierattrPrivate* const* attr, int* pshared); +int APS5_VABI pthread_barrierattr_setpshared_nid_postfix(PthreadBarrierattrPrivate** attr, int pshared); +int APS5_VABI pthread_barrier_init_nid_postfix(PthreadBarrierPrivate** barrier, PthreadBarrierattrPrivate* const* attr, unsigned count); +int APS5_VABI pthread_barrier_wait_nid_postfix(PthreadBarrierPrivate** barrier); +int APS5_VABI pthread_barrier_destroy_nid_postfix(PthreadBarrierPrivate** barrier); +int APS5_VABI pthread_cond_destroy_nid_postfix(PthreadCond* cond); +int APS5_VABI pthread_cond_broadcast_nid_postfix(PthreadCond* cond); +int APS5_VABI pthread_cond_init_nid_postfix(PthreadCond* cond, const PthreadCondattr* attr); +int APS5_VABI pthread_cond_signal_nid_postfix(PthreadCond* cond); +int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime); +int APS5_VABI pthread_cond_reltimedwait_np_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec); +int APS5_VABI pthread_cond_wait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex); +int APS5_VABI pthread_condattr_destroy_nid_postfix(PthreadCondattr* attr); +int APS5_VABI pthread_condattr_init_nid_postfix(PthreadCondattr* attr); +int APS5_VABI pthread_condattr_setclock_nid_postfix(PthreadCondattr* attr, KernelClockid clockId); +int APS5_VABI pthread_condattr_setpshared_nid_postfix(PthreadCondattr* attr, int pshared); +int APS5_VABI pthread_condattr_getpshared_nid_postfix(const PthreadCondattr* attr, int* pshared); +int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex); +int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex* mutex, const PthreadMutexattr* attr); +int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex); +int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const KernelTimespec* abstime); +int APS5_VABI pthread_mutex_trylock_nid_postfix(PthreadMutex* mutex); +int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex); +int APS5_VABI pthread_mutex_reltimedlock_np_nid_postfix(PthreadMutex* mutex, KernelUseconds usec); +int APS5_VABI pthread_mutex_isowned_np_nid_postfix(PthreadMutex* mutex); +int APS5_VABI pthread_mutex_getspinloops_np_nid_postfix(PthreadMutex* mutex, int* count); +int APS5_VABI pthread_mutex_setspinloops_np_nid_postfix(PthreadMutex* mutex, int count); +int APS5_VABI pthread_mutex_getyieldloops_np_nid_postfix(PthreadMutex* mutex, int* count); +int APS5_VABI pthread_mutex_setyieldloops_np_nid_postfix(PthreadMutex* mutex, int count); +int APS5_VABI pthread_mutexattr_destroy_nid_postfix(PthreadMutexattr* attr); +int APS5_VABI pthread_mutexattr_init_nid_postfix(PthreadMutexattr* attr); +int APS5_VABI pthread_mutexattr_getpshared_nid_postfix(const PthreadMutexattr* attr, int* pshared); +int APS5_VABI pthread_mutexattr_setpshared_nid_postfix(PthreadMutexattr* attr, int pshared); +int APS5_VABI pthread_mutexattr_setprotocol_nid_postfix(PthreadMutexattr* attr, int protocol); +int APS5_VABI pthread_mutexattr_getprotocol_nid_postfix(const PthreadMutexattr* attr, int* protocol); +int APS5_VABI pthread_mutexattr_setprioceiling_nid_postfix(PthreadMutexattr* attr, int prioceiling); +int APS5_VABI pthread_mutexattr_getprioceiling_nid_postfix(const PthreadMutexattr* attr, int* prioceiling); +int APS5_VABI pthread_mutexattr_settype_nid_postfix(PthreadMutexattr* attr, int type); +int APS5_VABI pthread_mutexattr_gettype_nid_postfix(const PthreadMutexattr* attr, int* type); +int APS5_VABI pthread_once_nid_postfix( GuestOnce* control, void (APS5_VABI *initialize)()); +int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_init_nid_postfix(PthreadRwlock* rwlock, const PthreadRwlockattr* attr); +int APS5_VABI pthread_rwlockattr_destroy_nid_postfix(PthreadRwlockattr* attr); +int APS5_VABI pthread_rwlockattr_init_nid_postfix(PthreadRwlockattr* attr); +int APS5_VABI pthread_rwlockattr_getpshared_nid_postfix(const PthreadRwlockattr* attr, int* pshared); +int APS5_VABI pthread_rwlockattr_setpshared_nid_postfix(PthreadRwlockattr* attr, int pshared); +int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_timedrdlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime); +int APS5_VABI pthread_rwlock_timedwrlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime); +int APS5_VABI pthread_rwlock_reltimedrdlock_np_nid_postfix(PthreadRwlock* rwlock, KernelUseconds usec); +int APS5_VABI pthread_rwlock_reltimedwrlock_np_nid_postfix(PthreadRwlock* rwlock, KernelUseconds usec); +int APS5_VABI pthread_rwlock_tryrdlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_trywrlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI sem_init_nid_postfix(void* sem, int pshared, unsigned int value); +int APS5_VABI sem_destroy_nid_postfix(void* sem); +int APS5_VABI sem_wait_nid_postfix(void* sem); +int APS5_VABI sem_trywait_nid_postfix(void* sem); +int APS5_VABI sem_timedwait_nid_postfix(void* sem, const KernelTimespec* abstime); +int APS5_VABI sem_reltimedwait_np_nid_postfix(void* sem, uint32_t usec); +int APS5_VABI sem_post_nid_postfix(void* sem); +int APS5_VABI sem_getvalue_nid_postfix(void* sem, int* value); +void* APS5_VABI sem_open_nid_postfix(const char* name, int flags, ...); +int APS5_VABI sem_close_nid_postfix(void* sem); +int APS5_VABI sem_unlink_nid_postfix(const char* name); +int APS5_VABI pthread_spin_init_nid_postfix(PthreadSpinlockPrivate** lock, int pshared); +int APS5_VABI pthread_spin_destroy_nid_postfix(PthreadSpinlockPrivate** lock); +int APS5_VABI pthread_spin_trylock_nid_postfix(PthreadSpinlockPrivate** lock); +int APS5_VABI pthread_spin_lock_nid_postfix(PthreadSpinlockPrivate** lock); +int APS5_VABI pthread_spin_unlock_nid_postfix(PthreadSpinlockPrivate** lock); +int APS5_VABI cpuset_getaffinity_nid_postfix(int level, int which, std::int64_t id, std::size_t size, void* mask); +int APS5_VABI cpuset_setaffinity_nid_postfix(int level, int which, std::int64_t id, std::size_t size, const void* mask); +int APS5_VABI rtprio_thread_nid_postfix(int function, int lwpid, GuestRtPrio* priority); +int APS5_VABI pthread_setconcurrency_nid_postfix(int newLevel); +int APS5_VABI pthread_getconcurrency_nid_postfix(); +int APS5_VABI pthread_create_nid_postfix(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg); +int APS5_VABI pthread_create_name_np_nid_postfix(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI pthread_detach_nid_postfix(Pthread thread); +void APS5_VABI pthread_exit_nid_postfix(void* value); +int APS5_VABI scePthreadGetschedparam(Pthread thread, int* policy, KernelSchedParam* param); +int APS5_VABI scePthreadSetschedparam(Pthread thread, int policy, const KernelSchedParam* param); +int APS5_VABI pthread_getschedparam_nid_postfix(Pthread thread, int* policy, KernelSchedParam* param); +int APS5_VABI pthread_getprio_nid_postfix(Pthread thread); +int APS5_VABI pthread_getaffinity_np_nid_postfix(Pthread thread, std::size_t cpusetSize, void* cpuset); +int APS5_VABI pthread_setaffinity_np_nid_postfix(Pthread thread, std::size_t cpusetSize, const void* cpuset); +int APS5_VABI pthread_join_nid_postfix(Pthread thread, void** value); +int APS5_VABI pthread_timedjoin_np_nid_postfix(Pthread thread, void** value, const KernelTimespec* abstime); +int APS5_VABI pthread_rename_np_nid_postfix(Pthread thread, const char* name); +int APS5_VABI pthread_getname_np_nid_postfix(Pthread thread, char* name); +void APS5_VABI pthread_set_name_np_nid_postfix(Pthread thread, const char* name); +Pthread APS5_VABI pthread_self_nid_postfix(void); +int APS5_VABI pthread_getthreadid_np_nid_postfix(void); +int APS5_VABI pthread_equal_nid_postfix(Pthread first, Pthread second); +int APS5_VABI pthread_getcpuclockid_nid_postfix(Pthread thread, int* clockId); +int APS5_VABI sched_yield_nid_postfix(void); +int APS5_VABI pthread_cancel_nid_postfix(Pthread thread); +int APS5_VABI pthread_setcancelstate_nid_postfix(int state, int* oldState); +int APS5_VABI pthread_setcanceltype_nid_postfix(int type, int* oldType); +int APS5_VABI pthread_setprio_nid_postfix(Pthread thread, int prio); +int APS5_VABI pthread_setschedparam_nid_postfix(Pthread thread, int policy, const KernelSchedParam* param); +void APS5_VABI pthread_testcancel_nid_postfix(void); +void APS5_VABI pthread_yield_nid_postfix(void); +unsigned int APS5_VABI sleep_nid_postfix(unsigned int seconds); +void* APS5_VABI pthread_getspecific_nid_postfix(PthreadKey key); +int APS5_VABI pthread_setspecific_nid_postfix(PthreadKey key, void* value); +int APS5_VABI pthread_key_create_nid_postfix(PthreadKey* key, pthread_key_destructor_func_t destructor); +int APS5_VABI pthread_key_delete_nid_postfix(PthreadKey key); +int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); +int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); +int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int detachstate); +int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param); +int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksize); +int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackaddr, std::size_t* stacksize); +int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr); +int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask* mask); +int APS5_VABI scePthreadAttrGetdetachstate(const PthreadAttr* attr, int* state); +int APS5_VABI scePthreadAttrGetguardsize(const PthreadAttr* attr, size_t* guardSize); +int APS5_VABI scePthreadAttrGetinheritsched(const PthreadAttr* attr, int* inheritSched); +int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param); +int APS5_VABI scePthreadAttrGetschedpolicy(const PthreadAttr* attr, int* policy); +int APS5_VABI scePthreadAttrGetsolosched(const PthreadAttr* attr, int* solosched); +int APS5_VABI scePthreadAttrGetstackaddr(const PthreadAttr* attr, void** stackAddr); +int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, size_t* stackSize); +int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask); +int APS5_VABI scePthreadAttrSetguardsize(PthreadAttr* attr, size_t guardSize); +int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched); +int APS5_VABI scePthreadAttrSetschedpolicy(PthreadAttr* attr, int policy); +int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched); +int APS5_VABI scePthreadAttrSetstack(PthreadAttr* attr, void* addr, size_t size); +int APS5_VABI scePthreadAttrSetstackaddr(PthreadAttr* attr, void* addr); +int APS5_VABI scePthreadSemInit(PthreadSem* sem, int flag, unsigned int value, const char* name); +int APS5_VABI scePthreadSemDestroy(PthreadSem* sem); +int APS5_VABI scePthreadSemPost(PthreadSem* sem); +int APS5_VABI scePthreadSemWait(PthreadSem* sem); +int APS5_VABI scePthreadSemTrywait(PthreadSem* sem); +int APS5_VABI scePthreadSemTimedwait(PthreadSem* sem, KernelUseconds usec); +int APS5_VABI scePthreadSemGetvalue(PthreadSem* sem, int* value); +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +int APS5_VABI scePthreadDetach(Pthread thread); +Pthread APS5_VABI scePthreadSelf(); +void APS5_VABI scePthreadYield(); +int APS5_VABI scePthreadCancel(Pthread thread); +int APS5_VABI scePthreadEqual(Pthread thread1, Pthread thread2); +KernelCpumask APS5_VABI sceKernelGetAvailableCpumask(void); +int APS5_VABI scePthreadGetaffinity(Pthread thread, KernelCpumask* mask); +int APS5_VABI scePthreadGetname(Pthread thread, char* name); +int APS5_VABI scePthreadGetprio(Pthread thread, int* prio); +int APS5_VABI scePthreadGetthreadid(void); +int APS5_VABI scePthreadRename(Pthread thread, const char* name); +int APS5_VABI scePthreadSetaffinity(Pthread thread, KernelCpumask mask); +int APS5_VABI scePthreadSetcancelstate(int state, int* oldState); +int APS5_VABI scePthreadSetcanceltype(int type, int* oldType); +void APS5_VABI scePthreadTestcancel(); +int APS5_VABI scePthreadSetprio(Pthread thread, int prio); +int APS5_VABI scePthreadOnce(int32_t* once, void (APS5_VABI* init)(void)); +void APS5_VABI __pthread_cxa_finalize_nid_postfix(void* argument); +int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); +int APS5_VABI scePthreadKeyDelete(PthreadKey key); +void* APS5_VABI scePthreadGetspecific(PthreadKey key); +int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); +int APS5_VABI __inet_pton_nid_postfix(int family, const char* text, void* destination); +const char* APS5_VABI __inet_ntop_nid_postfix(int family, const void* source, char* destination, std::uint32_t capacity); +std::uint32_t APS5_VABI htonl_nid_postfix(std::uint32_t value); +std::uint32_t APS5_VABI ntohl_nid_postfix(std::uint32_t value); +std::uint16_t APS5_VABI htons_nid_postfix(std::uint16_t value); +std::uint16_t APS5_VABI ntohs_nid_postfix(std::uint16_t value); +int APS5_VABI pselect_nid_postfix(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout, const void* sigmask); +const char* APS5_VABI inet_ntop_nid_postfix(int af, const void* src, char* dst, uint32_t size); +int APS5_VABI inet_pton_nid_postfix(int af, const char* src, void* dst); +int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout); +int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...); +int APS5_VABI _fcntl_nid_postfix(int descriptor, int command, ...); +int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option, const void* value, std::uint32_t length); +int APS5_VABI getsockopt_nid_postfix(int descriptor, int level, int option, void* value, std::uint32_t* length); +int APS5_VABI socket_nid_postfix(int family, int type, int protocol); +int APS5_VABI __sys_socketex_nid_postfix(const char* name, int family, int type, int protocol); +int APS5_VABI socketpair_nid_postfix(int family, int type, int protocol, int* descriptors); +int APS5_VABI connect_nid_postfix(int descriptor, const void* address, std::uint32_t length); +int APS5_VABI listen_nid_postfix(int descriptor, int backlog); +int APS5_VABI accept_nid_postfix(int descriptor, void* address, std::uint32_t* length); +std::int64_t APS5_VABI send_nid_postfix(int descriptor, const void* buffer, std::uint64_t length, int flags); +std::int64_t APS5_VABI recv_nid_postfix(int descriptor, void* buffer, std::uint64_t length, int flags); +int APS5_VABI bind_nid_postfix(int descriptor, const void* address, std::uint32_t length); +int APS5_VABI shutdown_nid_postfix(int descriptor, int how); +int APS5_VABI getsockname_nid_postfix(int descriptor, void* address, std::uint32_t* length); +int APS5_VABI getpeername_nid_postfix(int descriptor, void* address, std::uint32_t* length); +int APS5_VABI ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument); +int APS5_VABI _ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument); +std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength); +std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength); +std::int64_t APS5_VABI sendmsg_nid_postfix(int descriptor, const GuestMsghdr* message, int flags); +std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, int flags); +int APS5_VABI sendfile_nid_postfix(int file, int descriptor, std::int64_t offset, std::uint64_t length, const GuestSendfileHeaders* headers, std::int64_t* sbytes, int flags); +int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t count, int timeout); +int APS5_VABI _setsockopt_nid_postfix(int descriptor, int level, int option, const void* value, std::uint32_t length); +int APS5_VABI _getsockopt_nid_postfix(int descriptor, int level, int option, void* value, std::uint32_t* length); +int APS5_VABI _connect_nid_postfix(int descriptor, const void* address, std::uint32_t length); +int APS5_VABI _listen_nid_postfix(int descriptor, int backlog); +int APS5_VABI _accept_nid_postfix(int descriptor, void* address, std::uint32_t* length); +int APS5_VABI _bind_nid_postfix(int descriptor, const void* address, std::uint32_t length); +int APS5_VABI _getsockname_nid_postfix(int descriptor, void* address, std::uint32_t* length); +int APS5_VABI _getpeername_nid_postfix(int descriptor, void* address, std::uint32_t* length); +std::int64_t APS5_VABI _sendto_nid_postfix(int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength); +std::int64_t APS5_VABI _recvfrom_nid_postfix(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength); +std::int64_t APS5_VABI _sendmsg_nid_postfix(int descriptor, const GuestMsghdr* message, int flags); +std::int64_t APS5_VABI _recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, int flags); +int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count); +int APS5_VABI _umtx_op_nid_postfix(void* object, int operation, std::uint64_t value, void* sizeArgument, void* timeArgument); +int APS5_VABI sceCoredumpRegisterCoredumpHandler(uint64_t handler, size_t stackSize, uint64_t context); +int APS5_VABI sceCoredumpUnregisterCoredumpHandler(void); +int APS5_VABI sceKernelDebugWriteCppExceptionInfo(const void* exception, uint64_t unknown, const char* typeName, const char* what); +int APS5_VABI sceCoredumpAttachUserFile(std::uint32_t userValue, const char* path); +int APS5_VABI sceCoredumpGetStopInfoGpu_Agc(void* info, size_t size); +int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(std::uint32_t userValue, const void* mem, std::int32_t size, const char* name); +int APS5_VABI sceCoredumpSetUserDataType(std::int32_t type); +void APS5_VABI sceCoredumpDebugTextOut(const char* str, int len); +int64_t APS5_VABI sceCoredumpWriteUserData(const void* data, size_t size); +int APS5_VABI sceCoredumpGetStopInfoCpu(void* info, size_t size); +int APS5_VABI sceCoredumpGetThreadContextInfo(void* threadContextInfo, size_t threadContextInfoSize); +int APS5_VABI sceCoredumpWriteUserString(uint32_t first, const char* text, int third); +int APS5_VABI sceCoredumpConfigDumpMode(int mode); +int APS5_VABI sceCoredumpAttachUserMemoryFile(std::uint32_t userValue, const void* mem, std::int32_t size); +int APS5_VABI sceCoredumpAttachMemoryRegion(std::uint32_t userValue, const void* mem, std::int32_t size); +std::int32_t APS5_VABI sceKernelError(std::int32_t posixError); +int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler); +int APS5_VABI sceKernelRemoveExceptionHandler(int signum); +int APS5_VABI sceKernelRaiseException(Pthread thread, int signum); +void APS5_VABI sceKernelDebugRaiseException(int c1, int c2); +void APS5_VABI sceKernelDebugRaiseExceptionOnReleaseMode(int c1, int c2); +int APS5_VABI getlogin_r_nid_postfix(char* output, int size); +char* APS5_VABI getlogin_nid_postfix(void); +int APS5_VABI sceKernelDebugOutText(int channel, const char* text); +int APS5_VABI sceKernelSendNotificationRequest(int device, const void* request, std::size_t size, int flags); +int APS5_VABI getargc_nid_postfix(void); +const char** APS5_VABI getargv_nid_postfix(void); +int APS5_VABI getpagesize_nid_postfix(void); +int APS5_VABI getpid_nid_postfix(void); +int APS5_VABI getsid_nid_postfix(int pid); +int APS5_VABI getppid_nid_postfix(void); +int APS5_VABI sceKernelGetAppInfo(int pid, void* info); +int APS5_VABI getuid_nid_postfix(void); +int APS5_VABI geteuid_nid_postfix(void); +int APS5_VABI getgid_nid_postfix(void); +int APS5_VABI getegid_nid_postfix(void); +int APS5_VABI issetugid_nid_postfix(void); +int APS5_VABI getresuid_nid_postfix(std::uint32_t* ruid, std::uint32_t* euid, std::uint32_t* suid); +int APS5_VABI getresgid_nid_postfix(std::uint32_t* rgid, std::uint32_t* egid, std::uint32_t* sgid); +int APS5_VABI system_nid_postfix(const char* command); +int APS5_VABI waitpid_nid_postfix(int pid, int* status, int options); +int APS5_VABI setrlimit_nid_postfix(int resource, const GuestResourceLimit* limit); +int APS5_VABI wait_nid_postfix(int* status); +int APS5_VABI execvp_nid_postfix(const char* file, char* const* arguments); +int APS5_VABI execv_nid_postfix(const char* path, char* const* arguments); +int APS5_VABI execve_nid_postfix(const char* path, char* const* arguments, char* const* environment); +int APS5_VABI sceKernelGetCurrentCpu(void); +int APS5_VABI sceKernelGetMainSocId(void); +int APS5_VABI sceKernelGetCpumode(void); +std::uint64_t APS5_VABI sceKernelGetGPI(void); +void APS5_VABI sceKernelSetGPO(std::uint32_t bits); +int APS5_VABI sceKernelGetOpenPsId(void* openPsIdOutput); +void* APS5_VABI sceKernelGetProcParam(void); +int APS5_VABI sceKernelUuidCreate(std::uint32_t* uuid); +int APS5_VABI sceKernelCreateUuidV4(std::uint8_t* uuid); +void APS5_VABI sceKernelSync(void); +void APS5_VABI sync_nid_postfix(void); +int APS5_VABI sched_get_priority_max_nid_postfix(int policy); +int APS5_VABI sched_get_priority_min_nid_postfix(int policy); +int APS5_VABI sysarch(int operation, void* parameters); +int APS5_VABI getrusage_nid_postfix(int who, GuestResourceUsage* usage); +int APS5_VABI getrlimit_nid_postfix(int resource, GuestResourceLimit* limit); +int APS5_VABI getpriority_nid_postfix(int which, int who); +int APS5_VABI setpriority_nid_postfix(int which, int who, int niceval); +int APS5_VABI getdtablesize_nid_postfix(void); +int APS5_VABI sceKernelIsTrinityMode(void); +void APS5_VABI sceKernelGetOperationMode(int* mode, int* submode); +int APS5_VABI seteuid_nid_postfix(std::uint32_t euid); +int APS5_VABI setegid_nid_postfix(std::uint32_t egid); +int APS5_VABI setuid_nid_postfix(std::uint32_t uid); +int APS5_VABI setgid_nid_postfix(std::uint32_t gid); +int APS5_VABI setreuid_nid_postfix(std::uint32_t ruid, std::uint32_t euid); +int APS5_VABI setregid_nid_postfix(std::uint32_t rgid, std::uint32_t egid); +int APS5_VABI setresuid_nid_postfix(std::uint32_t ruid, std::uint32_t euid, std::uint32_t suid); +int APS5_VABI setresgid_nid_postfix(std::uint32_t rgid, std::uint32_t egid, std::uint32_t sgid); +int APS5_VABI setgroups_nid_postfix(int count, const std::uint32_t* groups); +int APS5_VABI getgroups_nid_postfix(int capacity, std::uint32_t* groups); +void APS5_VABI sceKernelRtldSetApplicationHeapAPI(void* api[]); +int APS5_VABI sceKernelRtldThreadAtexitDecrement(const void* dsoSymbol); +int APS5_VABI sceKernelRtldThreadAtexitIncrement(const void* dsoSymbol); +void APS5_VABI sceKernelSetThreadAtexitCount(get_thread_atexit_count_func_t func); +void APS5_VABI sceKernelSetThreadAtexitReport(thread_atexit_report_func_t func); +void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors); +void APS5_VABI _sceKernelRtldSetApplicationHeapAPI_nid_postfix(void* api[]); +int APS5_VABI _sceKernelRtldThreadAtexitDecrement_nid_postfix(const void* dsoSymbol); +int APS5_VABI _sceKernelRtldThreadAtexitIncrement_nid_postfix(const void* dsoSymbol); +void APS5_VABI _sceKernelSetThreadAtexitCount_nid_postfix(get_thread_atexit_count_func_t callback); +void APS5_VABI _sceKernelSetThreadAtexitReport_nid_postfix(thread_atexit_report_func_t callback); +void APS5_VABI _sceKernelSetThreadDtors_nid_postfix(thread_dtors_func_t callback); +const char* APS5_VABI sceKernelGetFsSandboxRandomWord(); +int APS5_VABI sceKernelIsAddressSanitizerEnabled(void); +int APS5_VABI sceKernelIsThreadSanitizerEnabled(void); +MallocReplace* APS5_VABI sceKernelGetSanitizerMallocReplaceExternal(void); +NewReplace* APS5_VABI sceKernelGetSanitizerNewReplaceExternal(void); +int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output); +int APS5_VABI setitimer_nid_postfix(int which, const GuestItimerval* value, GuestItimerval* previous); +GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler); +int APS5_VABI sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous); +int APS5_VABI _sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous); +int APS5_VABI raise_nid_postfix(int guest); +int APS5_VABI kill_nid_postfix(int pid, int guest); +int APS5_VABI killpg_nid_postfix(int pgrp, int guest); +int APS5_VABI pthread_kill_nid_postfix(Pthread thread, int guest); +int APS5_VABI sigaltstack_nid_postfix(const GuestStack* stack, GuestStack* previous); +int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet); +int APS5_VABI SetSignalMaskCatching_nid_no_patch(const GuestSignalSet* set, GuestSignalSet* previousSet); +int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet); +int APS5_VABI pthread_sigmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet); +int APS5_VABI sigtimedwait_nid_postfix(const GuestSignalSet* set, void* info, const KernelTimespec* timeout); +int APS5_VABI sigwaitinfo_nid_postfix(const GuestSignalSet* set, void* info); +int APS5_VABI sigwait_nid_postfix(const GuestSignalSet* set, int* received); +int APS5_VABI pause_nid_postfix(void); +int APS5_VABI sigsuspend_nid_postfix(const GuestSignalSet* set); +int APS5_VABI _sigsuspend_nid_postfix(const GuestSignalSet* set); +int APS5_VABI sigpending_nid_postfix(GuestSignalSet* set); +int APS5_VABI _is_signal_return_nid_postfix(std::uint64_t programCounter); +int APS5_VABI sigemptyset_nid_postfix(GuestSignalSet* set); +int APS5_VABI sigfillset_nid_postfix(GuestSignalSet* set); +int APS5_VABI sigaddset_nid_postfix(GuestSignalSet* set, int signal); +int APS5_VABI sigdelset_nid_postfix(GuestSignalSet* set, int signal); +int APS5_VABI sigismember_nid_postfix(const GuestSignalSet* set, int signal); +int APS5_VABI sceKernelGetProsperoSystemSwVersion_nid_postfix(SceKernelProsperoSystemSwVersion* version); +int APS5_VABI sceKernelGetAllowedSdkVersionOnSystem_nid_postfix(std::uint32_t* version); +std::int64_t APS5_VABI sysconf_nid_postfix(int name); +int APS5_VABI sysctl_nid_postfix(const int* name, std::uint32_t nameLength, void* oldValue, std::size_t* oldLength, const void* newValue, std::size_t newLength); +int APS5_VABI sysctlbyname_nid_postfix(const char* name, void* oldValue, std::size_t* oldLength, const void* newValue, std::size_t newLength); +int APS5_VABI sceKernelGetSystemSwVersion_nid_postfix(SceKernelSystemSwVersion* version); +int APS5_VABI sysctlnametomib_nid_postfix(const char* name, int* mib, std::size_t* size); +int APS5_VABI sceKernelGetCpuTemperature(int* outCelsius); +int APS5_VABI sceKernelGetSocSensorTemperature(int sensorId, int* outCelsius); +int APS5_VABI sceKernelGetSocPowerConsumption(void* consumption); +int APS5_VABI setcontext_nid_postfix(const UserUcontext* context); +int APS5_VABI ktimer_create_nid_postfix(int clock, const GuestSigevent* event, int* timerid); +int APS5_VABI ktimer_delete_nid_postfix(int id); +int APS5_VABI ktimer_settime_nid_postfix(int id, int flags, const GuestItimerspec* value, GuestItimerspec* oldValue); +int APS5_VABI ktimer_gettime_nid_postfix(int id, GuestItimerspec* value); +int APS5_VABI ktimer_getoverrun_nid_postfix(int id); +int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp); +int APS5_VABI usleep_nid_postfix(KernelUseconds microseconds); +int APS5_VABI clock_settime_nid_postfix(int clockId, const KernelTimespec* tp); +int APS5_VABI sceKernelClockGetres(KernelClockid clockId, KernelTimespec* tp); +int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp); +int APS5_VABI sceKernelConvertLocaltimeToUtc(int64_t localTime, int64_t reserved, int64_t* utcTime, KernelTimesec* timezone, int32_t* dstSeconds); +int APS5_VABI sceKernelConvertUtcToLocaltime(int64_t utcTime, int64_t* localTime, KernelTimesec* st, int32_t* dstSec); +int APS5_VABI sceKernelGettimeofday(KernelTimeval* tp); +int APS5_VABI sceKernelGettimezone(KernelTimezone* tz); +uint64_t APS5_VABI sceKernelReadTsc(void); +std::int64_t APS5_VABI sceKernelGetCpuFrequency(void); +unsigned int APS5_VABI sceKernelSleep(unsigned int seconds); + +[[noreturn]] void APS5_VABI _exit_nid_postfix(int status); + +[[gnu::returns_twice]] int APS5_VABI getcontext_nid_postfix(UserUcontext* context); +[[gnu::returns_twice]] int APS5_VABI swapcontext_nid_postfix(UserUcontext* oucp, const UserUcontext* ucp); + +[[gnu::returns_twice]] int APS5_VABI sigsetjmp_nid_postfix(std::uint64_t* buffer, int saveMask); +[[noreturn]] void APS5_VABI siglongjmp_nid_postfix(std::uint64_t* buffer, int value); + +int APS5_VABI sceKernelGetTraceMemoryStats(std::uint64_t* first, std::uint64_t* second); +int APS5_VABI pthread_cond_signalto_np_nid_postfix(PthreadCond* cond, Pthread thread); +int APS5_VABI scePthreadCondattrSetpshared(PthreadCondattr* attr, int pshared); +int APS5_VABI scePthreadCondattrGetpshared(const PthreadCondattr* attr, int* pshared); +int APS5_VABI pthread_mutexattr_setkind_np_nid_postfix(PthreadMutexattr* attr, int kind); +int APS5_VABI pthread_mutexattr_getkind_np_nid_postfix(PthreadMutexattr attr); +int APS5_VABI scePthreadMutexattrGettype(const PthreadMutexattr* attr, int* type); +int APS5_VABI scePthreadMutexattrGetprotocol(const PthreadMutexattr* attr, int* protocol); +int APS5_VABI scePthreadMutexattrSetprioceiling(PthreadMutexattr* attr, int prioceiling); +int APS5_VABI scePthreadMutexattrGetprioceiling(const PthreadMutexattr* attr, int* prioceiling); +int APS5_VABI scePthreadMutexattrSetpshared(PthreadMutexattr* attr, int pshared); +int APS5_VABI scePthreadMutexattrGetpshared(const PthreadMutexattr* attr, int* pshared); +int APS5_VABI pthread_rwlockattr_gettype_np_nid_postfix(const PthreadRwlockattr* attr, int* type); +int APS5_VABI scePthreadRwlockattrGettype(const PthreadRwlockattr* attr, int* type); +int APS5_VABI scePthreadRwlockattrSetpshared(PthreadRwlockattr* attr, int pshared); +int APS5_VABI scePthreadRwlockattrGetpshared(const PthreadRwlockattr* attr, int* pshared); +int APS5_VABI setpgid_nid_postfix(int pid, int pgid); +int APS5_VABI setpgrp_nid_postfix(int pid, int pgrp); +int APS5_VABI sceKernelGetSystemExVersion(char* first, char* second); +int APS5_VABI sysKernelGetLowerLimitSysexVersion_nid_postfix(char* version); +int APS5_VABI sysKernelGetLowerLimitUpdVersion_nid_postfix(std::uint32_t* version); + +} diff --git a/core/libs/prx/libkernel/libkernel.hpp b/core/libs/prx/libkernel/libkernel.hpp new file mode 100644 index 0000000000..3a32592846 --- /dev/null +++ b/core/libs/prx/libkernel/libkernel.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/Equeue/Equeue.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Atfork.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Rwlock.hpp" +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include "prx/libkernel/Semaphore/include/Semaphore.hpp" +#include "prx/libkernel/Time/include/Time.hpp" diff --git a/core/libs/prx/libkernel_sys/Aio.cpp b/core/libs/prx/libkernel_sys/Aio.cpp new file mode 100644 index 0000000000..f81b5842de --- /dev/null +++ b/core/libs/prx/libkernel_sys/Aio.cpp @@ -0,0 +1,314 @@ +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +namespace { + +constexpr int GuestEbadf = 9; +constexpr int GuestEfault = 14; +constexpr int GuestEinval = 22; +constexpr int GuestEagain = 35; +constexpr int GuestEinprogress = 36; +constexpr int GuestEopnotsupp = 45; +constexpr int GuestEcanceled = 85; +constexpr int LioWrite = 1; +constexpr int LioRead = 2; +constexpr int LioSync = 3; +constexpr int AioCanceled = 1; +constexpr int AioNotCanceled = 2; +constexpr int AioAllDone = 3; +constexpr int SigevNone = 0; +constexpr int SigevSignal = 1; +constexpr int SigevKevent = 3; +constexpr int SigevThreadId = 4; +constexpr int SignalMax = 128; +constexpr int GuestOSync = 0x80; +constexpr int GetFlags = 3; +constexpr int AccessMode = 3; +constexpr int WriteOnly = 1; +constexpr int ReadWrite = 2; +constexpr std::uint16_t TypeMask = 0170000; +constexpr std::uint16_t TypeFifo = 0010000; +constexpr std::uint16_t TypeDirectory = 0040000; +constexpr std::uint16_t TypeRegular = 0100000; +constexpr std::uint16_t TypeSocket = 0140000; +constexpr std::size_t QueuePerProcess = 256; +constexpr std::uint64_t IoSizeMax = std::numeric_limits::max(); +constexpr std::int64_t NanosecondsPerSecond = 1000000000; +constexpr std::int64_t TickNanoseconds = 1000000; + +static_assert(offsetof(GuestAiocb, lioOpcode) == 48 && offsetof(GuestAiocb, status) == 56 && offsetof(GuestAiocb, sigevent) == 80); +static_assert(sizeof(GuestAiocb) == 160); + +enum class State { Queued, Running, Done }; + +struct Job { + GuestAiocb* user; + int descriptor; + int opcode; + std::int64_t offset; + void* buffer; + std::uint64_t bytes; + State state = State::Queued; + std::int64_t status = -1; + int error = 0; +}; + +struct Aio { + std::mutex lock; + std::condition_variable changed; + bool used = false; + bool held = false; + bool started = false; + std::intptr_t nextId = 0; + std::deque> queued; + std::list> all; + std::list> done; +}; + +Aio& Instance() { + static auto* aio = new Aio; + return *aio; +} + +int Fail(int error) { + *__error_nid_postfix() = error; + return -1; +} + +void Run(Job& job) { + std::int64_t result = 0; + if (job.opcode == LioRead) result = job.bytes == 0 ? 0 : pread_nid_postfix(job.descriptor, job.buffer, job.bytes, job.offset); + else if (job.opcode == LioWrite) result = pwrite_nid_disambig1_nid_postfix(job.descriptor, job.buffer, job.bytes, job.offset); + else result = fsync_nid_postfix(job.descriptor); + job.status = result < 0 ? -1 : result; + job.error = result < 0 ? *__error_nid_postfix() : 0; +} + +void Worker() { + auto& aio = Instance(); + std::unique_lock lock(aio.lock); + for (;;) { + aio.changed.wait(lock, [&] { return !aio.held && !aio.queued.empty(); }); + auto job = aio.queued.front(); + aio.queued.pop_front(); + job->state = State::Running; + lock.unlock(); + Run(*job); + lock.lock(); + job->state = State::Done; + aio.done.push_back(job); + aio.changed.notify_all(); + } +} + +void Complete(Aio& aio, const std::shared_ptr& job, std::int64_t status, int error) { + job->status = status; + job->error = error; + job->state = State::Done; + aio.done.push_back(job); + aio.changed.notify_all(); +} + +std::optional ReadyError(const std::shared_ptr& job) { + if (job->state != State::Done) return std::nullopt; + return job->error; +} + +std::int64_t Consume(Aio& aio, std::list>::iterator found) { + const auto job = *found; + aio.done.erase(found); + aio.all.remove(job); + job->user->error = job->error; + job->user->status = job->status; + return job->error != 0 ? Fail(job->error) : job->status; +} + +std::optional WaitTime(const KernelTimespec& time) { + if (time.tv_sec < 0 || time.tv_nsec < 0 || time.tv_nsec >= NanosecondsPerSecond) return std::nullopt; + const auto microseconds = time.tv_nsec / 1000; + const std::int64_t whole = microseconds * 1000; + const std::int64_t rounded = time.tv_sec == 0 && whole < TickNanoseconds ? TickNanoseconds : whole; + const auto seconds = std::min(time.tv_sec, std::chrono::duration_cast(std::chrono::hours(24 * 365)).count()); + return std::chrono::seconds(seconds) + std::chrono::nanoseconds(rounded); +} + +int Queue(GuestAiocb* user, int opcode) { + if (user == nullptr) return Fail(GuestEfault); + auto& aio = Instance(); + std::unique_lock lock(aio.lock); + aio.used = true; + user->status = -1; + user->error = 0; + user->kernelinfo = reinterpret_cast(std::intptr_t{-1}); + const auto fail = [&](int error) { + user->error = error; + return Fail(error); + }; + if (aio.all.size() >= QueuePerProcess) return fail(GuestEagain); + const GuestAiocb request = *user; + if (request.nbytes > IoSizeMax) return Fail(GuestEinval); + const int notify = request.sigevent.notify; + if (notify != SigevKevent && notify != SigevSignal && notify != SigevThreadId && notify != SigevNone) return fail(GuestEinval); + if ((notify == SigevSignal || notify == SigevThreadId) && (request.sigevent.signo <= 0 || request.sigevent.signo > SignalMax)) + return Fail(GuestEinval); + const int flags = fcntl_nid_postfix(request.fildes, GetFlags); + if (flags < 0) return fail(GuestEbadf); + const int access = flags & AccessMode; + if (opcode == LioRead && access != 0 && access != ReadWrite) return fail(GuestEbadf); + if (opcode == LioWrite && access != WriteOnly && access != ReadWrite) return fail(GuestEbadf); + FileStat status{}; + if (_fstat_nid_postfix(request.fildes, &status) != 0) return fail(GuestEbadf); + const auto type = static_cast(status.st_mode & TypeMask); + if (type == TypeSocket) NotImplemented_nid_no_patch("aio on a socket"); + const bool vnode = type != 0 && type != TypeFifo; + if (opcode == LioSync && !vnode) return fail(GuestEinval); + if (opcode != LioSync && request.offset < 0) return fail(GuestEinval); + user->kernelinfo = reinterpret_cast(aio.nextId++); + if (notify != SigevNone) NotImplemented_nid_no_patch("aio completion delivering a signal or a kevent"); + user->error = GuestEinprogress; + if (type != TypeRegular && type != TypeDirectory) return fail(GuestEopnotsupp); + auto job = std::make_shared(Job{user, request.fildes, opcode, request.offset, request.buf, request.nbytes}); + aio.all.push_back(job); + aio.queued.push_back(job); + if (!aio.started) { + aio.started = true; + std::thread(Worker).detach(); + } + aio.changed.notify_all(); + return 0; +} + +} + +extern "C" { + +void AioHoldWorkerForTest_nid_no_patch(bool hold) { + auto& aio = Instance(); + std::lock_guard lock(aio.lock); + aio.held = hold; + aio.changed.notify_all(); +} + +int APS5_VABI aio_read_nid_postfix(GuestAiocb* aiocb) { + return Queue(aiocb, LioRead); +} + +int APS5_VABI aio_write_nid_postfix(GuestAiocb* aiocb) { + return Queue(aiocb, LioWrite); +} + +int APS5_VABI aio_fsync_nid_postfix(int operation, GuestAiocb* aiocb) { + if (operation != GuestOSync) return Fail(GuestEinval); + return Queue(aiocb, LioSync); +} + +int APS5_VABI aio_error_nid_postfix(const GuestAiocb* aiocb) { + auto& aio = Instance(); + std::lock_guard lock(aio.lock); + if (!aio.used || aiocb == nullptr) return GuestEinval; + for (const auto& job : aio.all) { + if (job->user == aiocb) return ReadyError(job).value_or(GuestEinprogress); + } + if (aiocb->status == -1) return static_cast(aiocb->error); + return GuestEinval; +} + +std::int64_t APS5_VABI aio_return_nid_postfix(GuestAiocb* aiocb) { + auto& aio = Instance(); + std::lock_guard lock(aio.lock); + if (!aio.used) return Fail(GuestEinval); + for (auto job = aio.done.begin(); job != aio.done.end(); ++job) { + if ((*job)->user == aiocb) return Consume(aio, job); + } + return Fail(GuestEinval); +} + +int APS5_VABI aio_suspend_nid_postfix(const GuestAiocb* const* list, int count, const KernelTimespec* timeout) { + if (count < 0 || count > static_cast(QueuePerProcess)) return Fail(GuestEinval); + std::optional wait; + if (timeout != nullptr) { + wait = WaitTime(*timeout); + if (!wait) return Fail(GuestEinval); + } + auto& aio = Instance(); + std::unique_lock lock(aio.lock); + if (!aio.used) return Fail(GuestEagain); + if (count == 0) return 0; + for (;;) { + bool listed = false; + for (const auto& job : aio.all) { + for (int index = 0; index < count; ++index) { + if (job->user != list[index]) continue; + listed = true; + if (job->state == State::Done) return 0; + } + } + if (!listed) return 0; + if (!wait) aio.changed.wait(lock); + else if (aio.changed.wait_for(lock, *wait) == std::cv_status::timeout) return Fail(GuestEagain); + } +} + +std::int64_t APS5_VABI aio_waitcomplete_nid_postfix(GuestAiocb** result, const KernelTimespec* timeout) { + if (result != nullptr) *result = nullptr; + std::optional wait; + bool poll = false; + if (timeout != nullptr) { + poll = timeout->tv_sec == 0 && timeout->tv_nsec == 0; + if (!poll) { + wait = WaitTime(*timeout); + if (!wait) return Fail(GuestEinval); + } + } + auto& aio = Instance(); + std::unique_lock lock(aio.lock); + aio.used = true; + while (aio.done.empty()) { + if (poll) return Fail(GuestEagain); + if (!wait) aio.changed.wait(lock); + else if (aio.changed.wait_for(lock, *wait) == std::cv_status::timeout && aio.done.empty()) return Fail(GuestEagain); + } + if (result != nullptr) *result = aio.done.front()->user; + return Consume(aio, aio.done.begin()); +} + +int APS5_VABI aio_cancel_nid_postfix(int descriptor, GuestAiocb* aiocb) { + if (fcntl_nid_postfix(descriptor, GetFlags) < 0) return Fail(GuestEbadf); + auto& aio = Instance(); + std::lock_guard lock(aio.lock); + int cancelled = 0; + int notCancelled = 0; + for (const auto& job : std::list>(aio.all)) { + if (job->state == State::Done || job->descriptor != descriptor || (aiocb != nullptr && job->user != aiocb)) continue; + if (job->state == State::Queued) { + std::erase(aio.queued, job); + Complete(aio, job, -1, GuestEcanceled); + ++cancelled; + } else { + ++notCancelled; + } + if (aiocb != nullptr) break; + } + if (aiocb != nullptr && cancelled != 0) return AioCanceled; + if (notCancelled != 0) return AioNotCanceled; + if (cancelled != 0) return AioCanceled; + return AioAllDone; +} + +} diff --git a/core/libs/prx/libkernel_sys/CMakeLists.txt b/core/libs/prx/libkernel_sys/CMakeLists.txt index 6938d0c456..70631309ca 100644 --- a/core/libs/prx/libkernel_sys/CMakeLists.txt +++ b/core/libs/prx/libkernel_sys/CMakeLists.txt @@ -1,3 +1,3 @@ -add_library(${LIB_NAME} SHARED EXCLUDE_FROM_ALL Export.cpp Export.Discovery.cpp) +add_library(${LIB_NAME} SHARED EXCLUDE_FROM_ALL Export.cpp Export.Discovery.cpp Aio.cpp ThreadQueries.cpp) target_include_directories(${LIB_NAME} PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(${LIB_NAME}) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 8b3da583e4..d8678eb1f1 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -1,48 +1,24 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Atfork.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include + +namespace { + +int SceResult(int error) { + return error == 0 ? 0 : SceKernelError(error); +} + +} extern "C" { -int APS5_VABI aio_cancel_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI aio_error_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI aio_fsync_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI aio_read_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI aio_return_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI aio_suspend_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI aio_waitcomplete_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI aio_write_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI amd64_set_fsbase_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -68,11 +44,6 @@ int APS5_VABI blockpool_unmap_nid_postfix(void) { return 0; } -int APS5_VABI chown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI cpuset_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -93,26 +64,11 @@ int APS5_VABI dynlib_get_obj_member_nid_postfix(void) { return 0; } -int APS5_VABI fchownat_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI fstat_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI futimesat_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI get_authinfo_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI get_module_info_list_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -143,11 +99,6 @@ int APS5_VABI getfsstat_nid_postfix(void) { return 0; } -int APS5_VABI getlogin_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI ipmimgr_call_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -278,31 +229,6 @@ int APS5_VABI ksem_wait_nid_postfix(void) { return 0; } -int APS5_VABI ktimer_create_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ktimer_delete_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ktimer_getoverrun_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ktimer_gettime_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ktimer_settime_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI ktrace_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -313,21 +239,6 @@ int APS5_VABI lchflags_nid_postfix(void) { return 0; } -int APS5_VABI lchmod_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI lchown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI linkat_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; @@ -353,11 +264,6 @@ int APS5_VABI profil_nid_postfix(void) { return 0; } -int APS5_VABI pselect_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI pthread_attr_getaffinity_np_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -373,8 +279,10 @@ int APS5_VABI pthread_attr_setaffinity_np_nid_postfix(void) { return 0; } -int APS5_VABI pthread_attr_setcreatesuspend_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI pthread_attr_setcreatesuspend_np_nid_postfix(PthreadAttr* attr) { + constexpr int GuestEinval = 22; + if (!attr || !*attr) return GuestEinval; + (*attr)->_createSuspended = true; return 0; } @@ -408,13 +316,8 @@ int APS5_VABI pthread_getstack_np_nid_postfix(void) { return 0; } -int APS5_VABI pthread_main_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI pthread_multi_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); + pthread_resume_all_np_nid_postfix(); return 0; } @@ -448,14 +351,15 @@ int APS5_VABI pthread_mutexattr_setgen_np_nid_postfix(void) { return 0; } -int APS5_VABI pthread_resume_all_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI pthread_resume_all_np_nid_postfix(void) { + ResumeAllGuestThreads_nid_no_patch(); + scePthreadTestcancel(); } -int APS5_VABI pthread_resume_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI pthread_resume_np_nid_postfix(Pthread thread) { + constexpr int GuestEinval = 22; + if (!thread) return GuestEinval; + return ResumeGuestThread_nid_no_patch(thread); } int APS5_VABI pthread_resume_user_context_np_nid_postfix(void) { @@ -463,16 +367,6 @@ int APS5_VABI pthread_resume_user_context_np_nid_postfix(void) { return 0; } -int APS5_VABI pthread_rwlock_reltimedrdlock_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI pthread_rwlock_reltimedwrlock_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI pthread_rwlock_setname_np_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -483,11 +377,6 @@ int APS5_VABI pthread_rwlockattr_settype_np_nid_postfix(void) { return 0; } -int APS5_VABI pthread_set_defaultstacksize_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI pthread_set_user_context_np_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -514,18 +403,11 @@ int APS5_VABI pthread_suspend_user_context_np_nid_postfix(void) { } int APS5_VABI pthread_switch_add_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return 45; } int APS5_VABI pthread_switch_delete_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI pthread_timedjoin_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return 45; } int APS5_VABI ptrace_nid_postfix(void) { @@ -599,7 +481,6 @@ int APS5_VABI sceKernelAddProcessToCanvasMap(void) { } int APS5_VABI sceKernelAddResource(void) { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -679,18 +560,11 @@ int APS5_VABI sceKernelClearBootReqNotifyCount(void) { } int APS5_VABI sceKernelClearGameDirectMemory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x8002004e); } int APS5_VABI sceKernelCloseEport(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceKernelCloseEventFlag(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80020001); } int APS5_VABI sceKernelCloseSema(void) { @@ -704,8 +578,7 @@ int APS5_VABI sceKernelCreateBudget(void) { } int APS5_VABI sceKernelCreateEport(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80020001); } int APS5_VABI sceKernelDebugAcquireAndUpdateDebugRegister(void) { @@ -784,8 +657,7 @@ int APS5_VABI sceKernelDeleteCpumodeEvent(void) { } int APS5_VABI sceKernelDeleteEport(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80020001); } int APS5_VABI sceKernelDeleteGpuExceptionEvent(void) { @@ -829,7 +701,6 @@ int APS5_VABI sceKernelEndAppMount2(void) { } int APS5_VABI sceKernelEventLogInit(void) { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -938,11 +809,6 @@ int APS5_VABI sceKernelGetChildApp(void) { return 0; } -int APS5_VABI sceKernelGetCompiledSdkVersion(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelGetCompiledSdkVersionByPath(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1108,21 +974,6 @@ int APS5_VABI sceKernelGetMemoryPstate(void) { return 0; } -int APS5_VABI sceKernelGetModuleInfoInternal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceKernelGetModuleListInternal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceKernelGetOpenPsIdForSystem(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelGetPagingStatsOfAllObjects(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1148,11 +999,6 @@ int APS5_VABI sceKernelGetProcessName(void) { return 0; } -int APS5_VABI sceKernelGetProcessType(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelGetProcessTypeOfBudget(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1268,11 +1114,6 @@ int APS5_VABI sceKernelGetSubsysSubId(void) { return 0; } -int APS5_VABI sceKernelGetSystemExVersion(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelGetSystemLevelDebuggerModeForRcmgr(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1288,11 +1129,6 @@ int APS5_VABI sceKernelGetThreadName(void) { return 0; } -int APS5_VABI sceKernelGetTraceMemoryStats(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelGetUniversalMode(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1369,8 +1205,7 @@ int APS5_VABI sceKernelGiveDirectMemoryToMapper(void) { } int APS5_VABI sceKernelHasNeoMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return 1; } int APS5_VABI sceKernelHwGetBdDriveInfo(void) { @@ -1409,8 +1244,7 @@ int APS5_VABI sceKernelIccGetBootTimestamp(void) { } int APS5_VABI sceKernelIccGetCPMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x8002002d); } int APS5_VABI sceKernelIccGetCountTime(void) { @@ -1504,8 +1338,7 @@ int APS5_VABI sceKernelIccSetBuzzer(void) { } int APS5_VABI sceKernelIccSetCPMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x8002002d); } int APS5_VABI sceKernelIccSetCpuInfoBit(void) { @@ -1559,13 +1392,7 @@ int APS5_VABI sceKernelIsAllowedToSelectDvdRegion(void) { } int APS5_VABI sceKernelIsAuthenticNeo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceKernelIsCEX(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return 1; } int APS5_VABI sceKernelIsDebuggerAttached(void) { @@ -1573,11 +1400,6 @@ int APS5_VABI sceKernelIsDebuggerAttached(void) { return 0; } -int APS5_VABI sceKernelIsDevKit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelIsExperimentalBeta(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1594,13 +1416,11 @@ int APS5_VABI sceKernelIsGenuineDevKit(void) { } int APS5_VABI sceKernelIsGenuineKratosCex(void) { - NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceKernelIsGenuineN(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return 1; } int APS5_VABI sceKernelIsGenuineTestKit(void) { @@ -1668,10 +1488,6 @@ int APS5_VABI sceKernelKernelHeapUsage(void) { return 0; } -int APS5_VABI sceKernelLoadStartModuleForSysmodule(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI sceKernelLoadStartModuleInternalForMono(void) { NotImplemented_nid_no_patch(__func__); @@ -1719,8 +1535,7 @@ int APS5_VABI sceKernelMapToolMemory(void) { } int APS5_VABI sceKernelMapTraceMemory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x8002004e); } int APS5_VABI sceKernelMapperGetParam(void) { @@ -1738,9 +1553,8 @@ int APS5_VABI sceKernelMlockall(void) { return 0; } -int APS5_VABI sceKernelMsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceKernelMsync(void* address, std::size_t length, int flags) { + return msync_nid_postfix(address, length, flags) == 0 ? 0 : SceKernelError(*__error_nid_postfix()); } int APS5_VABI sceKernelNormalizePath(void) { @@ -1769,8 +1583,7 @@ int APS5_VABI sceKernelNotifySystemSuspendStart(void) { } int APS5_VABI sceKernelOpenEport(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80020001); } int APS5_VABI sceKernelOpenInternal(void) { @@ -1814,8 +1627,7 @@ int APS5_VABI sceKernelQueryToolMemory(void) { } int APS5_VABI sceKernelQueryTraceMemory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80020002); } int APS5_VABI sceKernelRandomizedPath(void) { @@ -1839,7 +1651,6 @@ int APS5_VABI sceKernelReleaseToolMemory(void) { } int APS5_VABI sceKernelReleaseTraceDirectMemory(void) { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -2148,24 +1959,17 @@ int APS5_VABI sceKernelTerminateSysCore(void) { return 0; } -int APS5_VABI sceKernelTitleWorkaroundIsEnabled(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelTitleWorkdaroundIsEnabled(void) { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceKernelTraceMemoryTypeProtect(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x8002004e); } int APS5_VABI sceKernelTriggerEport(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(0x80020001); } int APS5_VABI sceKernelVirtualQueryAll(void) { @@ -2288,84 +2092,65 @@ int APS5_VABI sceLibcMspaceCreateForMonoMutex(void) { return 0; } -int APS5_VABI scePthreadAtfork(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAtfork(void (APS5_VABI* prepare)(), void (APS5_VABI* parent)(), void (APS5_VABI* child)()) { + return SceResult(pthread_atfork_nid_postfix(prepare, parent, child)); } -int APS5_VABI scePthreadAttrGetscope(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAttrGetscope(const PthreadAttr* attr, int* scope) { + return SceResult(pthread_attr_getscope_nid_postfix(attr, scope)); } -int APS5_VABI scePthreadAttrSetcreatesuspend(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAttrSetcreatesuspend(PthreadAttr* attr) { + const int error = pthread_attr_setcreatesuspend_np_nid_postfix(attr); + return error == 0 ? 0 : SceKernelError(error); } -int APS5_VABI scePthreadAttrSetscope(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAttrSetscope(PthreadAttr* attr, int scope) { + return SceResult(pthread_attr_setscope_nid_postfix(attr, scope)); } -int APS5_VABI scePthreadBarrierDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierDestroy(PthreadBarrierPrivate** barrier) { + return SceResult(pthread_barrier_destroy_nid_postfix(barrier)); } -int APS5_VABI scePthreadBarrierInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierInit(PthreadBarrierPrivate** barrier, PthreadBarrierattrPrivate* const* attr, unsigned count, const char* name) { + (void)name; + return SceResult(pthread_barrier_init_nid_postfix(barrier, attr, count)); } -int APS5_VABI scePthreadBarrierWait(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierWait(PthreadBarrierPrivate** barrier) { + const int result = pthread_barrier_wait_nid_postfix(barrier); + return result > 0 ? SceKernelError(result) : result; } -int APS5_VABI scePthreadBarrierattrDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierattrDestroy(PthreadBarrierattrPrivate** attr) { + return SceResult(pthread_barrierattr_destroy_nid_postfix(attr)); } -int APS5_VABI scePthreadBarrierattrGetpshared(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierattrGetpshared(PthreadBarrierattrPrivate* const* attr, int* pshared) { + return SceResult(pthread_barrierattr_getpshared_nid_postfix(attr, pshared)); } -int APS5_VABI scePthreadBarrierattrInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierattrInit(PthreadBarrierattrPrivate** attr) { + return SceResult(pthread_barrierattr_init_nid_postfix(attr)); } -int APS5_VABI scePthreadBarrierattrSetpshared(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePthreadCondattrGetclock(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierattrSetpshared(PthreadBarrierattrPrivate** attr, int pshared) { + return SceResult(pthread_barrierattr_setpshared_nid_postfix(attr, pshared)); } int APS5_VABI scePthreadGetconcurrency(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return pthread_getconcurrency_nid_postfix(); } -int APS5_VABI scePthreadGetcpuclockid(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadGetcpuclockid(Pthread thread, int* clockId) { + return SceResult(pthread_getcpuclockid_nid_postfix(thread, clockId)); } -int APS5_VABI scePthreadMain(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI scePthreadMulti(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + const int error = pthread_multi_np_nid_postfix(); + return error == 0 ? 0 : SceKernelError(error); } int APS5_VABI scePthreadMutexGetprioceiling(void) { @@ -2413,14 +2198,13 @@ int APS5_VABI scePthreadMutexattrSetkind(void) { return 0; } -int APS5_VABI scePthreadResume(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadResume(Pthread thread) { + const int error = pthread_resume_np_nid_postfix(thread); + return error == 0 ? 0 : SceKernelError(error); } -int APS5_VABI scePthreadResumeAll(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI scePthreadResumeAll(void) { + pthread_resume_all_np_nid_postfix(); } int APS5_VABI scePthreadSetBesteffort(void) { @@ -2428,19 +2212,8 @@ int APS5_VABI scePthreadSetBesteffort(void) { return 0; } -int APS5_VABI scePthreadSetDefaultstacksize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePthreadSetName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePthreadSetconcurrency(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadSetconcurrency(int level) { + return SceResult(pthread_setconcurrency_nid_postfix(level)); } int APS5_VABI scePthreadSingle(void) { @@ -2473,11 +2246,6 @@ int APS5_VABI sched_getscheduler_nid_postfix(void) { return 0; } -int APS5_VABI sched_rr_get_interval_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sched_setparam_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -2488,26 +2256,11 @@ int APS5_VABI sched_setscheduler_nid_postfix(void) { return 0; } -int APS5_VABI sem_close_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sem_open_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sem_setname_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sem_unlink_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI set_phys_fmem_limit_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -2518,11 +2271,6 @@ int APS5_VABI setlogin_nid_postfix(void) { return 0; } -int APS5_VABI settimeofday_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sigqueue_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -2538,11 +2286,6 @@ int APS5_VABI statfs_nid_postfix(void) { return 0; } -int APS5_VABI swapcontext_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI swapon_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -2563,16 +2306,6 @@ int APS5_VABI sysKernelGetIntdevModeForRcmgr_nid_postfix(void) { return 0; } -int APS5_VABI sysKernelGetLowerLimitSysexVersion_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sysKernelGetLowerLimitUpdVersion_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sysKernelGetManufacturingMode_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 9c7d6fc2b6..baaa8f84bc 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include #include #include @@ -11,6 +14,7 @@ #include #include #include +#include #ifndef _WIN32 #include #include @@ -19,10 +23,16 @@ #endif #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/FilesystemError.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libkernel/File/include/DirectoryDescriptor.hpp" +#include "prx/libkernel/File/include/DescriptorControl.hpp" #include "prx/libc/include/CreationMask.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" + #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX @@ -36,23 +46,23 @@ #endif #endif -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { #ifdef _WIN32 extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); static void IgnoreInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {} -static int GuestFstat(int fd) { +static int GuestFstat(int fd, bool* withoutVnode = nullptr) { const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreInvalidParameter); struct _stat64 status{}; const int result = ::_fstat64(fd, &status); _set_thread_local_invalid_parameter_handler(previous); + if (result == 0 && withoutVnode != nullptr) *withoutVnode = (status.st_mode & _S_IFMT) == _S_IFIFO; return result; } #endif constexpr int GuestEnoent = 2; constexpr int GuestEperm = 1; +constexpr int GuestEsrch = 3; constexpr int GuestEio = 5; constexpr int GuestEbadf = 9; constexpr int GuestEfault = 14; @@ -90,6 +100,7 @@ constexpr int PcRecXferAlign = 17; constexpr int PcSymlinkMax = 18; constexpr int PcMinHoleSize = 21; constexpr int PcAsyncIo = 53; +constexpr std::int64_t PosixAsynchronousIo = 200112; constexpr int PcPrioIo = 54; constexpr int PcSyncIo = 55; constexpr int PcAclExtended = 59; @@ -97,6 +108,7 @@ constexpr int PcAclPathMax = 60; constexpr int PcMacPresent = 63; constexpr int PcAclNfs4 = 64; constexpr int GuestAtSymlinkNofollow = 0x0200; +constexpr int GuestAtSymlinkFollow = 0x0400; int GuestFileError(int error) { switch (error) { @@ -120,6 +132,43 @@ std::int64_t Fail(int error) { return -1; } +std::int64_t PathConfigurationValue(int name, const char* function) { + switch (name) { + case PcLinkMax: return 32767; + case PcNameMax: return static_cast(GuestNameMax); + case PcPathMax: return static_cast(GuestPathMax); + case PcPipeBuf: return 512; + case PcChownRestricted: return 1; + case PcNoTrunc: return 1; + case PcFileSizeBits: return 64; + case PcRecMaxXferSize: return -1; + case PcSymlinkMax: return static_cast(GuestPathMax); + case PcPrioIo: return 0; + case PcSyncIo: return 0; + case PcMaxCanon: + case PcMaxInput: + case PcVdisable: + case PcAllocSizeMin: + case PcRecIncrXferSize: + case PcRecMinXferSize: + case PcRecXferAlign: + case PcMinHoleSize: + case PcAsyncIo: + case PcAclExtended: + case PcAclPathMax: + case PcMacPresent: + case PcAclNfs4: + throw std::runtime_error(std::string(function) + ": name " + std::to_string(name) + " depends on the console filesystem or kernel configuration"); + default: return Fail(GuestEinval); + } +} + +int CopyPath(const char*& path, std::string& copy) { + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return error; + path = copy.c_str(); + return copy.empty() ? GuestEnoent : 0; +} + int GuestError(const std::error_code& error) { const auto condition = error.default_error_condition(); if (condition == std::errc::resource_unavailable_try_again) return GuestEagain; @@ -148,15 +197,29 @@ bool HasNonDirectoryPrefix(const std::filesystem::path& native) { return false; } +void RequireGuestOwner(const char* call, std::uint32_t uid, std::uint32_t gid) { + constexpr std::uint32_t Unchanged = 0xFFFFFFFFu; + if (uid != 0 && uid != Unchanged) NotImplemented_nid_no_patch((std::string(call) + ": an owner other than the guest user").c_str()); + if (gid != 0 && gid != Unchanged) NotImplemented_nid_no_patch((std::string(call) + ": a group other than the guest group").c_str()); +} + +bool IsSymbolicLink(const std::filesystem::path& native) { +#ifdef _WIN32 + const DWORD attributes = GetFileAttributesW(native.wstring().c_str()); + return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; +#else + std::error_code error; + return std::filesystem::is_symlink(std::filesystem::symlink_status(native, error)); +#endif +} + std::int64_t ReadNativeLink(const std::filesystem::path& native, char* buffer, std::size_t size) { #ifdef _WIN32 const DWORD attributes = GetFileAttributesW(native.wstring().c_str()); if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { std::string target; - if (!File::WindowsSymbolicLink::Read(native, target, "readlink") && !File::WindowsSymbolicLink::ReadJunction(native, target, "readlink")) { - NotImplemented_nid_no_patch("readlink of a Windows reparse point other than a symbolic link"); - return -1; - } + if (!File::WindowsSymbolicLink::Read(native, target, "readlink") && !File::WindowsSymbolicLink::ReadJunction(native, target, "readlink")) + return Fail(GuestEinval); return CopyLinkTarget(target, buffer, size); } #endif @@ -255,6 +318,13 @@ int RenamePath(const std::filesystem::path& source, const std::filesystem::path& extern "C" { +int APS5_VABI sceKernelGetCompiledSdkVersion(std::int32_t* version) { + if (version == nullptr) return SCE_KERNEL_ERROR_EINVAL; + static const auto* parameters = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::memcpy(version, parameters + 0x10, sizeof(*version)); + return 0; +} + int APS5_VABI fork_nid_postfix(void) { NotImplemented_nid_no_patch("fork: child processes"); return -1; @@ -268,16 +338,53 @@ std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t mask) { return File::ExchangeCreationMask_nid_no_patch(mask); } -int APS5_VABI _fstatfs_nid_postfix(int d, void* buf); +int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id) { + if (id == nullptr) return GuestEinval; + return sceKernelGetOpenPsId(id); +} -int APS5_VABI fstatfs_nid_postfix(int d, void* buf) { +int APS5_VABI sceKernelIsCEX(void) { + return 1; +} + +int APS5_VABI sceKernelIsDevKit(void) { + return 0; +} + +int APS5_VABI get_authinfo_nid_postfix(int pid, KernelAuthInfo* info) { + if (info == nullptr) return static_cast(Fail(GuestEperm)); + if (pid != 0 && pid != getpid_nid_postfix()) return static_cast(Fail(GuestEsrch)); + *info = {}; + info->capabilities[0] = 0x2000000000000000; + return 0; +} + +int APS5_VABI fstatfs_nid_postfix(int d, KernelStatfs* buf) { return _fstatfs_nid_postfix(d, buf); } +int GetModuleInfoExByHandle_nid_no_patch(KernelModule handle, ModuleInfoEx* info); + +int APS5_VABI sceKernelGetModuleInfoInternal(KernelModule handle, ModuleInfoEx* info) { + return GetModuleInfoExByHandle_nid_no_patch(handle, info); +} + +int APS5_VABI sceKernelGetModuleListInternal(KernelModule* handles, std::size_t count, std::size_t* actual) { + return sceKernelGetModuleList(handles, count, actual); +} + +KernelModule APS5_VABI sceKernelLoadStartModuleForSysmodule(const char* moduleFileName, std::size_t args, const void* argp, std::uint32_t flags, const KernelLoadModuleOpt* opt, int* res) { + return sceKernelLoadStartModule(moduleFileName, args, argp, flags, opt, res); +} + +int APS5_VABI fstat_nid_postfix(int d, FileStat* sb) { + return static_cast(_fstat_nid_postfix(d, sb)); +} + std::int64_t APS5_VABI readlink_nid_postfix(const char* path, char* buffer, std::size_t size) { if (size > static_cast(std::numeric_limits::max())) return Fail(GuestEinval); - if (path == nullptr) return Fail(GuestEfault); - if (*path == '\0') return Fail(GuestEnoent); + std::string copy; + if (const int error = CopyPath(path, copy)) return Fail(error); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error); return ReadNativeLink(native, buffer, size); @@ -285,8 +392,8 @@ std::int64_t APS5_VABI readlink_nid_postfix(const char* path, char* buffer, std: std::int64_t APS5_VABI readlinkat_nid_postfix(int fd, const char* path, char* buffer, std::size_t size) { if (size > static_cast(std::numeric_limits::max())) return Fail(GuestEinval); - if (path == nullptr) return Fail(GuestEfault); - if (*path == '\0') return Fail(GuestEnoent); + std::string copy; + if (const int error = CopyPath(path, copy)) return Fail(error); if (fd == GuestAtFdcwd || *path == '/') return readlink_nid_postfix(path, buffer, size); int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); @@ -296,58 +403,90 @@ std::int64_t APS5_VABI readlinkat_nid_postfix(int fd, const char* path, char* bu } std::int64_t APS5_VABI pathconf_nid_postfix(const char* path, int name) { - if (path == nullptr) return Fail(GuestEfault); - if (*path == '\0') return Fail(GuestEnoent); + std::string copy; + if (const int error = CopyPath(path, copy)) return Fail(error); if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return Fail(GuestEnametoolong); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error); std::error_code error; const auto status = std::filesystem::status(native, error); - if (error) return Fail(GuestError(error)); - if (!std::filesystem::exists(status)) return Fail(GuestEnoent); - switch (name) { - case PcLinkMax: return 32767; - case PcNameMax: return static_cast(GuestNameMax); - case PcPathMax: return static_cast(GuestPathMax); - case PcPipeBuf: return 512; - case PcChownRestricted: return 1; - case PcNoTrunc: return 1; - case PcFileSizeBits: return 64; - case PcRecMaxXferSize: return -1; - case PcSymlinkMax: return static_cast(GuestPathMax); - case PcPrioIo: return 0; - case PcSyncIo: return 0; - case PcMaxCanon: - case PcMaxInput: - case PcVdisable: - case PcAllocSizeMin: - case PcRecIncrXferSize: - case PcRecMinXferSize: - case PcRecXferAlign: - case PcMinHoleSize: - case PcAsyncIo: - case PcAclExtended: - case PcAclPathMax: - case PcMacPresent: - case PcAclNfs4: - throw std::runtime_error("pathconf: name " + std::to_string(name) + " depends on the console filesystem or kernel configuration"); - default: return Fail(GuestEinval); - } + if (error) return Fail(error == std::errc::no_such_file_or_directory && HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestError(error)); + if (!std::filesystem::exists(status)) return Fail(HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestEnoent); + return PathConfigurationValue(name, "pathconf"); } -int APS5_VABI link_nid_postfix(const char* existing, const char* created) { - if (existing == nullptr || created == nullptr) return static_cast(Fail(GuestEfault)); - if (*existing == '\0' || *created == '\0') return static_cast(Fail(GuestEnoent)); - std::filesystem::path source, destination; - if (const int error = ResolvePathError_nid_no_patch(existing, source)) return static_cast(Fail(error)); - if (const int error = ResolvePathError_nid_no_patch(created, destination)) return static_cast(Fail(error)); +std::int64_t APS5_VABI _fpathconf_nid_postfix(int fd, int name) { + if (fd < 0) return Fail(GuestEbadf); + bool withoutVnode = false; +#ifdef _WIN32 + if (GuestFstat(fd, &withoutVnode) != 0) return Fail(GuestEbadf); +#else + struct stat status{}; + if (::fstat(fd, &status) != 0) return Fail(GuestEbadf); + withoutVnode = S_ISFIFO(status.st_mode) || S_ISSOCK(status.st_mode); +#endif + if (name == PcAsyncIo) return PosixAsynchronousIo; + if (withoutVnode) return name == PcPipeBuf ? 512 : Fail(GuestEinval); + return PathConfigurationValue(name, "fpathconf"); +} + +std::int64_t APS5_VABI fpathconf_nid_postfix(int fd, int name) { + return _fpathconf_nid_postfix(fd, name); +} + +#ifdef _WIN32 +static int FollowLinkPath(const std::filesystem::path& source, std::filesystem::path& target) { + target = source; + const DWORD attributes = GetFileAttributesW(source.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) return 0; + const HANDLE handle = CreateFileW(source.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + const DWORD code = GetLastError(); + if (code == ERROR_FILE_NOT_FOUND || code == ERROR_PATH_NOT_FOUND) return GuestEnoent; + if (code == ERROR_CANT_RESOLVE_FILENAME) return GuestEloop; + throw std::system_error(static_cast(code), std::system_category(), "link: CreateFileW"); + } + std::wstring name(MAX_PATH, L'\0'); + DWORD length = GetFinalPathNameByHandleW(handle, name.data(), static_cast(name.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + if (length >= name.size()) { + name.resize(length); + length = GetFinalPathNameByHandleW(handle, name.data(), static_cast(name.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + } + const DWORD code = GetLastError(); + CloseHandle(handle); + if (length == 0 || length >= name.size()) + throw std::system_error(static_cast(code), std::system_category(), "link: GetFinalPathNameByHandleW"); + name.resize(length); + target = std::filesystem::path(name); + return 0; +} +#endif + +static int LinkSourceError(const std::filesystem::path& source) { std::error_code error; const auto status = std::filesystem::status(source, error); - if (!std::filesystem::exists(status)) return static_cast(Fail(GuestEnoent)); - if (std::filesystem::is_directory(status)) return static_cast(Fail(GuestEperm)); + if (!std::filesystem::exists(status)) { +#ifdef _WIN32 + std::filesystem::path target; + if (const int followError = FollowLinkPath(source, target)) return followError; +#endif + return HasNonDirectoryPrefix(source) ? GuestEnotdir : GuestEnoent; + } + return std::filesystem::is_directory(status) ? GuestEperm : 0; +} + +static int LinkNative(const std::filesystem::path& source, const std::filesystem::path& destination) { + 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(GuestEnoent)); + 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)); + if (std::filesystem::is_directory(target, error)) return static_cast(Fail(GuestEperm)); +#else const auto target = std::filesystem::is_symlink(source, error) ? std::filesystem::canonical(source, error) : source; +#endif if (!error) std::filesystem::create_hard_link(target, destination, error); if (error == std::errc::cross_device_link) return static_cast(Fail(GuestExdev)); if (error) throw std::system_error(error, "link"); @@ -355,9 +494,44 @@ int APS5_VABI link_nid_postfix(const char* existing, const char* created) { return 0; } +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)); + std::filesystem::path source, destination; + if (const int error = ResolvePathError_nid_no_patch(existing, source)) return static_cast(Fail(error)); + if (const int error = LinkSourceError(source)) return static_cast(Fail(error)); + if (const int error = CopyPath(created, createdCopy)) return static_cast(Fail(error)); + if (const int error = ResolvePathError_nid_no_patch(created, destination)) return static_cast(Fail(error)); + return LinkNative(source, destination); +} + +static int ResolveAtPath(int fd, const char* path, std::filesystem::path& native) { + if (fd == GuestAtFdcwd || *path == '/') return ResolvePathError_nid_no_patch(path, native); + if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return GuestEnametoolong; + int error = 0; + const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); + if (base == nullptr) return error; + native = std::filesystem::path(reinterpret_cast(base)) / std::filesystem::path(reinterpret_cast(path)); + return 0; +} + +int APS5_VABI linkat_nid_postfix(int fd1, const char* path1, int fd2, const char* path2, int flag) { + if (flag & ~GuestAtSymlinkFollow) return static_cast(Fail(GuestEinval)); + std::string copy1, copy2; + 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)); + 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)); + return LinkNative(source, destination); +} + int APS5_VABI fchdir_nid_postfix(int fd) { int error = 0; const char* path = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); + if (path == nullptr && error == GuestEnotdir && DescriptorHasNoVnode_nid_no_patch(fd)) return static_cast(Fail(GuestEinval)); if (path == nullptr) return static_cast(Fail(error)); #ifndef _WIN32 if (::faccessat(fd, ".", X_OK, AT_EACCESS) != 0) return static_cast(Fail(errno)); @@ -367,9 +541,15 @@ int APS5_VABI fchdir_nid_postfix(int fd) { return 0; } +int SetTimeOfDay_nid_no_patch(const KernelTimeval* tv, const KernelTimezone* tz); + +int APS5_VABI settimeofday_nid_postfix(const KernelTimeval* tv, const KernelTimezone* tz) { + return SetTimeOfDay_nid_no_patch(tv, tz); +} + int APS5_VABI chroot_nid_postfix(const char* path) { - if (path == nullptr) return static_cast(Fail(GuestEfault)); - if (*path == '\0') return static_cast(Fail(GuestEnoent)); + std::string copy; + if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast(Fail(GuestEnametoolong)); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return static_cast(Fail(error)); @@ -385,8 +565,9 @@ int APS5_VABI chroot_nid_postfix(const char* path) { } int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { - if (target == nullptr || created == nullptr) return static_cast(Fail(GuestEfault)); - if (*target == '\0' || *created == '\0') return static_cast(Fail(GuestEnoent)); + std::string targetCopy, createdCopy; + if (const int error = CopyPath(target, targetCopy)) return static_cast(Fail(error)); + if (const int error = CopyPath(created, createdCopy)) return static_cast(Fail(error)); const std::filesystem::path contents(target); if (contents.has_root_name() || contents.has_root_directory()) { NotImplemented_nid_no_patch("symlink: absolute targets"); @@ -406,7 +587,7 @@ int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { #else if (std::filesystem::exists(std::filesystem::symlink_status(native, error))) return static_cast(Fail(GuestEexist)); #endif - if (!std::filesystem::is_directory(native.parent_path(), error)) return static_cast(Fail(GuestEnoent)); + if (!std::filesystem::is_directory(native.parent_path(), error)) return static_cast(Fail(HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestEnoent)); const bool directory = std::filesystem::is_directory(native.parent_path() / contents, error); #ifdef _WIN32 DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; @@ -429,8 +610,6 @@ int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { return 0; } -int APS5_VABI open_nid_postfix(const char* path, int flags, int mode); - int APS5_VABI openat_nid_postfix(int fd, const char* path, int flags, ...) { int mode = 0; if (flags & SCE_KERNEL_O_CREAT) { @@ -447,19 +626,36 @@ int APS5_VABI openat_nid_postfix(int fd, const char* path, int flags, ...) { #endif } if (File::InvalidOpenFlags(flags)) return static_cast(Fail(GuestEinval)); - if (path == nullptr) return static_cast(Fail(GuestEfault)); - if (*path == '\0') return static_cast(Fail(GuestEnoent)); + std::string copy; + if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); if (fd == GuestAtFdcwd || *path == '/') return open_nid_postfix(path, flags, mode); const int result = OpenInDirectory_nid_no_patch(fd, path, flags, static_cast(mode)); if (result < 0) return static_cast(Fail(result & 0xffff)); return result; } -int APS5_VABI mkdir_nid_postfix(const char* path, std::uint16_t mode); +int APS5_VABI _openat_nid_postfix(int fd, const char* path, int flags, ...) { + int mode = 0; + if (flags & SCE_KERNEL_O_CREAT) { +#ifdef _WIN32 + __builtin_sysv_va_list arguments; + __builtin_sysv_va_start(arguments, flags); + mode = __builtin_va_arg(arguments, int); + __builtin_sysv_va_end(arguments); +#else + std::va_list arguments; + va_start(arguments, flags); + mode = va_arg(arguments, int); + va_end(arguments); +#endif + } + return openat_nid_postfix(fd, path, flags, mode); +} + int APS5_VABI mkdirat_nid_postfix(int fd, const char* path, std::uint16_t mode) { - if (path == nullptr) return static_cast(Fail(GuestEfault)); - if (*path == '\0') return static_cast(Fail(GuestEnoent)); + std::string copy; + if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); if (fd == GuestAtFdcwd || *path == '/') return mkdir_nid_postfix(path, mode); if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast(Fail(GuestEnametoolong)); int error = 0; @@ -474,6 +670,7 @@ int APS5_VABI mkdirat_nid_postfix(int fd, const char* path, std::uint16_t mode) if (code == ERROR_ALREADY_EXISTS || code == ERROR_FILE_EXISTS) return static_cast(Fail(GuestEexist)); if (code == ERROR_ACCESS_DENIED) return static_cast(Fail(GuestEperm)); if (code == ERROR_FILENAME_EXCED_RANGE) return static_cast(Fail(GuestEnametoolong)); + if (code == ERROR_CANT_RESOLVE_FILENAME) return static_cast(Fail(GuestEloop)); if (code == ERROR_PATH_NOT_FOUND) { if (HasNonDirectoryPrefix(native)) return static_cast(Fail(GuestEnotdir)); return static_cast(Fail(GuestEnoent)); @@ -494,8 +691,8 @@ 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) { if (flag != 0 && flag != GuestAtSymlinkNofollow) return static_cast(Fail(GuestEinval)); - if (path == nullptr) return static_cast(Fail(GuestEfault)); - if (*path == '\0') return static_cast(Fail(GuestEnoent)); + std::string copy; + if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); std::filesystem::path native; if (fd == GuestAtFdcwd || *path == '/') { if (const int error = ResolvePathError_nid_no_patch(path, native)) return static_cast(Fail(error)); @@ -524,47 +721,92 @@ int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag) } } #ifdef _WIN32 - if (::_wchmod(native.wstring().c_str(), mode & 07777) != 0) return static_cast(Fail(GuestFileError(errno))); + if (::_wchmod(native.wstring().c_str(), mode & 07777) != 0) return static_cast(Fail(errno == ENOENT && HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestFileError(errno))); #else - if (::chmod(native.c_str(), static_cast(mode & 07777)) != 0) return static_cast(Fail(GuestFileError(errno))); + if (::chmod(native.c_str(), static_cast(mode & 07777)) != 0) return static_cast(Fail(errno == ENOENT && HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestFileError(errno))); #endif RecordWrittenPath_nid_no_patch(native); return 0; } +int APS5_VABI lchmod_nid_postfix(const char* path, std::uint16_t mode) { + std::string copy; + if (path != nullptr) { + if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); + } +#ifndef __APPLE__ + std::filesystem::path native; + if (path != nullptr && ResolvePathError_nid_no_patch(path, native) == 0) { +#ifdef _WIN32 + const DWORD attributes = GetFileAttributesW(native.wstring().c_str()); + const bool symbolic = attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; +#else + std::error_code status; + const bool symbolic = std::filesystem::is_symlink(std::filesystem::symlink_status(native, status)); +#endif + if (symbolic) NotImplemented_nid_no_patch("lchmod: the mode of a symbolic link on a host that cannot change it"); + } +#endif + return fchmodat_nid_postfix(GuestAtFdcwd, path, mode, GuestAtSymlinkNofollow); +} + int APS5_VABI fchown_nid_postfix(int fd, std::uint32_t uid, std::uint32_t gid) { - constexpr std::uint32_t Unchanged = 0xFFFFFFFFu; - if (uid != 0 && uid != Unchanged) NotImplemented_nid_no_patch("fchown: an owner other than the guest user"); - if (gid != 0 && gid != Unchanged) NotImplemented_nid_no_patch("fchown: a group other than the guest group"); #ifdef _WIN32 if (GuestFstat(fd) != 0) return static_cast(Fail(GuestEbadf)); #else struct stat status{}; if (::fstat(fd, &status) != 0) return static_cast(Fail(GuestEbadf)); #endif + RequireGuestOwner("fchown", uid, gid); return 0; } -int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb); -int APS5_VABI lstat_nid_postfix(const char* path, FileStat* sb); +int APS5_VABI sched_rr_get_interval_nid_postfix(int pid, KernelTimespec* interval) { + constexpr int GuestEsrch = 3; + const int self = getpid_nid_postfix(); + if (pid != 0 && pid != self) return static_cast(Fail(GuestEsrch)); + if (interval == nullptr) return static_cast(Fail(GuestEfault)); + interval->tv_sec = 0; + interval->tv_nsec = 94000000; + return 0; +} + +int APS5_VABI chown_nid_postfix(const char* path, std::uint32_t uid, std::uint32_t gid) { + FileStat status{}; + if (stat_nid_postfix(path, &status) != 0) return -1; + RequireGuestOwner("chown", uid, gid); + return 0; +} + +int APS5_VABI lchown_nid_postfix(const char* path, std::uint32_t uid, std::uint32_t gid) { + FileStat status{}; + if (lstat_nid_postfix(path, &status) != 0) return -1; + RequireGuestOwner("lchown", uid, gid); + return 0; +} + +int APS5_VABI fchownat_nid_postfix(int fd, const char* path, std::uint32_t uid, std::uint32_t gid, int flag) { + if (flag & ~GuestAtSymlinkNofollow) return static_cast(Fail(GuestEinval)); + FileStat status{}; + if (fstatat_nid_postfix(fd, path, &status, flag) != 0) return -1; + RequireGuestOwner("fchownat", uid, gid); + return 0; +} int APS5_VABI fstatat_nid_postfix(int fd, const char* path, FileStat* sb, int flag) { if (flag & ~GuestAtSymlinkNofollow) return static_cast(Fail(GuestEinval)); - if (path == nullptr) return static_cast(Fail(GuestEfault)); - if (*path == '\0') return static_cast(Fail(GuestEnoent)); + std::string copy; + if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); if (fd == GuestAtFdcwd || *path == '/') return flag ? lstat_nid_postfix(path, sb) : stat_nid_postfix(path, sb); const int result = StatInDirectory_nid_no_patch(fd, path, sb, flag == 0); if (result < 0) return static_cast(Fail(result & 0xffff)); return 0; } -int APS5_VABI unlink_nid_postfix(const char* path); -int APS5_VABI rmdir_nid_postfix(const char* path); - int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) { if (flag & ~GuestAtRemovedir) return static_cast(Fail(GuestEinval)); - if (path == nullptr) return static_cast(Fail(GuestEfault)); - if (*path == '\0') return static_cast(Fail(GuestEnoent)); + std::string copy; + if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); if (fd == GuestAtFdcwd || *path == '/') return (flag & GuestAtRemovedir) ? rmdir_nid_postfix(path) : unlink_nid_postfix(path); if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast(Fail(GuestEnametoolong)); int error = 0; @@ -576,8 +818,8 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) { while (!native.has_filename() && native.has_relative_path()) native = native.parent_path(); std::error_code statusError; const auto symlinkStatus = std::filesystem::symlink_status(native, statusError); + if (IsDotOrDotDot(path)) return static_cast(Fail(GuestEinval)); if (flag & GuestAtRemovedir) { - if (IsDotOrDotDot(path)) return static_cast(Fail(GuestEinval)); if (std::filesystem::is_symlink(symlinkStatus)) return static_cast(Fail(GuestEnotdir)); if (!std::filesystem::is_directory(symlinkStatus)) { if (HasNonDirectoryPrefix(native) || std::filesystem::exists(symlinkStatus)) @@ -632,15 +874,21 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) { return 0; } +int APS5_VABI scePthreadCondattrGetclock(const PthreadCondattr* attr, KernelClockid* clockId) { + if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL; + if (!clockId) throw std::runtime_error("scePthreadCondattrGetclock: null clock id"); + *clockId = (*attr)->_clockid; + return 0; +} + int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const char* to) { - if (from == nullptr || to == nullptr) return static_cast(Fail(GuestEfault)); - if (*from == '\0' || *to == '\0') return static_cast(Fail(GuestEnoent)); - if ((oldfd == GuestAtFdcwd || *from == '/') && (newfd == GuestAtFdcwd || *to == '/')) + std::string fromCopy, toCopy; + if (const int error = CopyPath(from, fromCopy)) return static_cast(Fail(error)); + if ((oldfd == GuestAtFdcwd || *from == '/') && newfd == GuestAtFdcwd) return RenameGuestPath_nid_no_patch(from, to); - if (std::strlen(from) >= GuestPathMax || HasLongComponent(from) || - std::strlen(to) >= GuestPathMax || HasLongComponent(to)) + if (std::strlen(from) >= GuestPathMax || HasLongComponent(from)) return static_cast(Fail(GuestEnametoolong)); - if (IsDotOrDotDot(from) || IsDotOrDotDot(to)) return static_cast(Fail(GuestEinval)); + if (IsDotOrDotDot(from)) return static_cast(Fail(GuestEinval)); std::filesystem::path source; if (oldfd == GuestAtFdcwd || *from == '/') { @@ -652,6 +900,20 @@ int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const source = std::filesystem::path(reinterpret_cast(base)) / std::filesystem::path(reinterpret_cast(from)); } + const std::string_view fromView(from); + const bool oldTrailingSlash = !fromView.empty() && fromView.back() == '/'; + while (!source.has_filename() && source.has_relative_path()) source = source.parent_path(); + std::error_code statusError; + const auto sourceStatus = std::filesystem::symlink_status(source, statusError); + if (statusError && statusError != std::errc::no_such_file_or_directory) + return static_cast(Fail(FilesystemError(statusError))); + if (sourceStatus.type() == std::filesystem::file_type::not_found) + return static_cast(Fail(HasNonDirectoryPrefix(source) ? GuestEnotdir : GuestEnoent)); + if (oldTrailingSlash && !std::filesystem::is_directory(sourceStatus)) return static_cast(Fail(GuestEnotdir)); + + if (const int error = CopyPath(to, toCopy)) return static_cast(Fail(error)); + if (std::strlen(to) >= GuestPathMax || HasLongComponent(to)) return static_cast(Fail(GuestEnametoolong)); + if (IsDotOrDotDot(to)) return static_cast(Fail(GuestEinval)); std::filesystem::path destination; if (newfd == GuestAtFdcwd || *to == '/') { @@ -664,21 +926,11 @@ int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const std::filesystem::path(reinterpret_cast(to)); } - const std::string_view fromView(from); const std::string_view toView(to); - const bool oldTrailingSlash = !fromView.empty() && fromView.back() == '/'; const bool newTrailingSlash = !toView.empty() && toView.back() == '/'; - while (!source.has_filename() && source.has_relative_path()) source = source.parent_path(); while (!destination.has_filename() && destination.has_relative_path()) destination = destination.parent_path(); - std::error_code statusError; - const auto sourceStatus = std::filesystem::symlink_status(source, statusError); - if (oldTrailingSlash) { - if (sourceStatus.type() == std::filesystem::file_type::not_found) - return static_cast(Fail(HasNonDirectoryPrefix(source) ? GuestEnotdir : GuestEnoent)); - if (!std::filesystem::is_directory(sourceStatus)) return static_cast(Fail(GuestEnotdir)); - } if (newTrailingSlash && sourceStatus.type() != std::filesystem::file_type::not_found && !std::filesystem::is_directory(sourceStatus)) return static_cast(Fail(GuestEnotdir)); @@ -690,4 +942,31 @@ int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const return 0; } +bool SetDefaultStackSize_nid_no_patch(std::size_t size); + +int APS5_VABI pthread_set_defaultstacksize_np_nid_postfix(std::size_t size) { + return SetDefaultStackSize_nid_no_patch(size) ? 0 : GuestEinval; +} + +int APS5_VABI scePthreadSetDefaultstacksize(std::size_t size) { + return SetDefaultStackSize_nid_no_patch(size) ? 0 : SceKernelError(GuestEinval); +} + +void APS5_VABI scePthreadSetName(Pthread thread, const char* name) { + pthread_set_name_np_nid_postfix(thread, name); + +} + +int APS5_VABI sceKernelTitleWorkaroundIsEnabled(const KernelTitleWorkaround* workaround, std::int32_t bit, std::int32_t* enabled) { + if (!workaround || !enabled) return static_cast(0x8002000E); + if (static_cast(bit) >= 0x3A) return static_cast(0x80020016); + *enabled = static_cast((workaround->ids[bit >> 6] >> (bit & 0x3F)) & 1); + return 0; +} + +int APS5_VABI sceKernelGetProcessType(int pid) { + if (pid != getpid_nid_postfix() && pid != -1 && pid != 0) return static_cast(0x80020003); + return 0; +} + } diff --git a/core/libs/prx/libkernel_sys/ThreadQueries.cpp b/core/libs/prx/libkernel_sys/ThreadQueries.cpp new file mode 100644 index 0000000000..397716777c --- /dev/null +++ b/core/libs/prx/libkernel_sys/ThreadQueries.cpp @@ -0,0 +1,14 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +extern "C" { +bool IsInitialPthread_nid_no_patch(); + +int APS5_VABI pthread_main_np_nid_postfix() { + return IsInitialPthread_nid_no_patch() ? 1 : 0; +} + +int APS5_VABI scePthreadMain(void) { + return pthread_main_np_nid_postfix(); +} + +} diff --git a/core/libs/prx/libkernel_sys/include/Api.hpp b/core/libs/prx/libkernel_sys/include/Api.hpp new file mode 100644 index 0000000000..474fd996ae --- /dev/null +++ b/core/libs/prx/libkernel_sys/include/Api.hpp @@ -0,0 +1,568 @@ +#pragma once + +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" + +struct GuestAiocb { + int fildes; + std::int64_t offset; + void* buf; + std::uint64_t nbytes; + int spare[2]; + void* spare2; + int lioOpcode; + int reqprio; + std::int64_t status; + std::int64_t error; + void* kernelinfo; + GuestSigevent sigevent; +}; + +struct KernelTitleWorkaround { + std::int32_t version; + std::int32_t align; + std::uint64_t ids[2]; +}; + +struct KernelAuthInfo { + std::uint64_t programAuthorityId; + std::uint64_t capabilities[4]; + std::uint64_t attributes[4]; + std::uint64_t credentials[8]; +}; +static_assert(sizeof(KernelAuthInfo) == 0x88); + +extern "C" { + +int APS5_VABI aio_read_nid_postfix(GuestAiocb* aiocb); +int APS5_VABI aio_write_nid_postfix(GuestAiocb* aiocb); +int APS5_VABI aio_fsync_nid_postfix(int operation, GuestAiocb* aiocb); +int APS5_VABI aio_error_nid_postfix(const GuestAiocb* aiocb); +std::int64_t APS5_VABI aio_return_nid_postfix(GuestAiocb* aiocb); +int APS5_VABI aio_suspend_nid_postfix(const GuestAiocb* const* list, int count, const KernelTimespec* timeout); +std::int64_t APS5_VABI aio_waitcomplete_nid_postfix(GuestAiocb** result, const KernelTimespec* timeout); +int APS5_VABI aio_cancel_nid_postfix(int descriptor, GuestAiocb* aiocb); +int APS5_VABI sceKernelGetCompiledSdkVersion(std::int32_t* version); +int APS5_VABI fork_nid_postfix(void); +int APS5_VABI setsid_nid_postfix(void); +std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t mask); +int APS5_VABI fstatfs_nid_postfix(int d, KernelStatfs* buf); +int APS5_VABI sceKernelGetModuleInfoInternal(KernelModule handle, ModuleInfoEx* info); +int APS5_VABI sceKernelGetModuleListInternal(KernelModule* handles, std::size_t count, std::size_t* actual); +KernelModule APS5_VABI sceKernelLoadStartModuleForSysmodule(const char* moduleFileName, std::size_t args, const void* argp, std::uint32_t flags, const KernelLoadModuleOpt* opt, int* res); +int APS5_VABI fstat_nid_postfix(int d, FileStat* sb); +std::int64_t APS5_VABI readlink_nid_postfix(const char* path, char* buffer, std::size_t size); +std::int64_t APS5_VABI readlinkat_nid_postfix(int fd, const char* path, char* buffer, std::size_t size); +std::int64_t APS5_VABI pathconf_nid_postfix(const char* path, int name); +std::int64_t APS5_VABI _fpathconf_nid_postfix(int fd, int name); +std::int64_t APS5_VABI fpathconf_nid_postfix(int fd, int name); +int APS5_VABI link_nid_postfix(const char* existing, const char* created); +int APS5_VABI linkat_nid_postfix(int fd1, const char* path1, int fd2, const char* path2, int flag); +int APS5_VABI fchdir_nid_postfix(int fd); +int APS5_VABI chroot_nid_postfix(const char* path); +int APS5_VABI symlink_nid_postfix(const char* target, const char* created); +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 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); +int APS5_VABI lchown_nid_postfix(const char* path, std::uint32_t uid, std::uint32_t gid); +int APS5_VABI fchownat_nid_postfix(int fd, const char* path, std::uint32_t uid, std::uint32_t gid, int flag); +int APS5_VABI fstatat_nid_postfix(int fd, const char* path, FileStat* sb, int flag); +int APS5_VABI settimeofday_nid_postfix(const KernelTimeval* tv, const KernelTimezone* tz); +int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag); +int APS5_VABI scePthreadCondattrGetclock(const PthreadCondattr* attr, KernelClockid* clockId); +int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const char* to); +int APS5_VABI pthread_set_defaultstacksize_np_nid_postfix(std::size_t size); +int APS5_VABI scePthreadSetDefaultstacksize(std::size_t size); +void APS5_VABI scePthreadSetName(Pthread thread, const char* name); +int APS5_VABI sceKernelTitleWorkaroundIsEnabled(const KernelTitleWorkaround* workaround, std::int32_t bit, std::int32_t* enabled); +int APS5_VABI sceKernelGetProcessType(int pid); +int APS5_VABI amd64_set_fsbase_nid_postfix(void); +int APS5_VABI blockpool_batch_nid_postfix(void); +int APS5_VABI blockpool_map_nid_postfix(void); +int APS5_VABI blockpool_open_nid_postfix(void); +int APS5_VABI blockpool_unmap_nid_postfix(void); +int APS5_VABI cpuset_nid_postfix(void); +int APS5_VABI cpuset_getid_nid_postfix(void); +int APS5_VABI cpuset_setid_nid_postfix(void); +int APS5_VABI dynlib_get_obj_member_nid_postfix(void); +int APS5_VABI futimesat_nid_postfix(void); +int APS5_VABI get_authinfo_nid_postfix(int pid, KernelAuthInfo* info); +int APS5_VABI get_module_info_list_nid_postfix(void); +int APS5_VABI get_page_table_stats_nid_postfix(void); +int APS5_VABI get_sdk_compiled_version_nid_postfix(void); +int APS5_VABI get_self_auth_info_nid_postfix(void); +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 kldfind_nid_postfix(void); +int APS5_VABI kldfirstmod_nid_postfix(void); +int APS5_VABI kldload_nid_postfix(void); +int APS5_VABI kldnext_nid_postfix(void); +int APS5_VABI kldstat_nid_postfix(void); +int APS5_VABI kldsym_nid_postfix(void); +int APS5_VABI kldunload_nid_postfix(void); +int APS5_VABI kmq_notify_nid_postfix(void); +int APS5_VABI kmq_open_nid_postfix(void); +int APS5_VABI kmq_setattr_nid_postfix(void); +int APS5_VABI kmq_timedreceive_nid_postfix(void); +int APS5_VABI kmq_timedsend_nid_postfix(void); +int APS5_VABI kmq_unlink_nid_postfix(void); +int APS5_VABI ksem_close_nid_postfix(void); +int APS5_VABI ksem_destroy_nid_postfix(void); +int APS5_VABI ksem_getvalue_nid_postfix(void); +int APS5_VABI ksem_init_nid_postfix(void); +int APS5_VABI ksem_open_nid_postfix(void); +int APS5_VABI ksem_post_nid_postfix(void); +int APS5_VABI ksem_timedwait_nid_postfix(void); +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); +int APS5_VABI openintr_nid_postfix(void); +int APS5_VABI profil_nid_postfix(void); +int APS5_VABI pthread_attr_getaffinity_np_nid_postfix(void); +int APS5_VABI pthread_attr_getsolosched_np_nid_postfix(void); +int APS5_VABI pthread_attr_setaffinity_np_nid_postfix(void); +int APS5_VABI pthread_attr_setcreatesuspend_np_nid_postfix(PthreadAttr* attr); +int APS5_VABI pthread_attr_setstackaddr_nid_postfix(void); +int APS5_VABI pthread_barrier_setname_np_nid_postfix(void); +int APS5_VABI pthread_cond_setname_np_nid_postfix(void); +int APS5_VABI pthread_get_specificarray_np_nid_postfix(void); +int APS5_VABI pthread_get_user_context_np_nid_postfix(void); +int APS5_VABI pthread_getstack_np_nid_postfix(void); +int APS5_VABI pthread_main_np_nid_postfix(void); +int APS5_VABI pthread_multi_np_nid_postfix(void); +int APS5_VABI pthread_mutex_getprioceiling_nid_postfix(void); +int APS5_VABI pthread_mutex_init_for_mono_nid_postfix(void); +int APS5_VABI pthread_mutex_setname_np_nid_postfix(void); +int APS5_VABI pthread_mutex_setprioceiling_nid_postfix(void); +int APS5_VABI pthread_mutexattr_getgen_np_nid_postfix(void); +int APS5_VABI pthread_mutexattr_setgen_np_nid_postfix(void); +void APS5_VABI pthread_resume_all_np_nid_postfix(void); +int APS5_VABI pthread_resume_np_nid_postfix(Pthread thread); +int APS5_VABI pthread_resume_user_context_np_nid_postfix(void); +int APS5_VABI pthread_rwlock_setname_np_nid_postfix(void); +int APS5_VABI pthread_rwlockattr_settype_np_nid_postfix(void); +int APS5_VABI pthread_set_user_context_np_nid_postfix(void); +int APS5_VABI pthread_single_np_nid_postfix(void); +int APS5_VABI pthread_suspend_all_np_nid_postfix(void); +int APS5_VABI pthread_suspend_np_nid_postfix(void); +int APS5_VABI pthread_suspend_user_context_np_nid_postfix(void); +int APS5_VABI pthread_switch_add_np_nid_postfix(void); +int APS5_VABI pthread_switch_delete_np_nid_postfix(void); +int APS5_VABI ptrace_nid_postfix(void); +int APS5_VABI pwrite_nid_postfix(void); +int APS5_VABI reboot_nid_postfix(void); +int APS5_VABI revoke_nid_postfix(void); +int APS5_VABI rfork_thread_nid_postfix(void); +int APS5_VABI rtld_printf_nid_postfix(void); +int APS5_VABI sceCoredumpDebugForceCoredumpOnAppClose(void); +int APS5_VABI sceCoredumpDebugTriggerCoredump(void); +int APS5_VABI sceCoredumpGetStopInfoGpu(void); +int APS5_VABI sceCoredumpInternalGetThreadContextInfo(void); +int APS5_VABI sceKernelAddACInfo(void); +int APS5_VABI sceKernelAddCpumodeEvent(void); +int APS5_VABI sceKernelAddGpuExceptionEvent(void); +int APS5_VABI sceKernelAddProcessToCanvasMap(void); +int APS5_VABI sceKernelAddResource(void); +int APS5_VABI sceKernelAllocateDirectMemory2(void); +int APS5_VABI sceKernelAllocateDirectMemoryForApp(void); +int APS5_VABI sceKernelAllocateDirectMemoryForMiniApp(void); +int APS5_VABI sceKernelAllocateToolMemory(void); +int APS5_VABI sceKernelAllocateTraceDirectMemory(void); +int APS5_VABI sceKernelAprCtrl(void); +int APS5_VABI sceKernelAprSubmitCommandBufferAndGetResult_TEST(void); +int APS5_VABI sceKernelAprSubmitCommandBuffer_TEST(void); +int APS5_VABI sceKernelAvailableToolMemorySize(void); +int APS5_VABI sceKernelBacktraceSelf(void); +int APS5_VABI sceKernelBeginAppMount(void); +int APS5_VABI sceKernelBeginAppMount2(void); +int APS5_VABI sceKernelCallIndirectBuffer(void); +int APS5_VABI sceKernelCallIndirectBuffer2(void); +int APS5_VABI sceKernelClearBootReqNotifyCount(void); +int APS5_VABI sceKernelClearGameDirectMemory(void); +int APS5_VABI sceKernelCloseEport(void); +int APS5_VABI sceKernelCloseSema(void); +int APS5_VABI sceKernelCreateBudget(void); +int APS5_VABI sceKernelCreateEport(void); +int APS5_VABI sceKernelDebugAcquireAndUpdateDebugRegister(void); +int APS5_VABI sceKernelDebugGetAppStatus(void); +int APS5_VABI sceKernelDebugGetPauseCount(void); +int APS5_VABI sceKernelDebugGetPrivateLogText(void); +int APS5_VABI sceKernelDebugGetSchedLockMode(void); +int APS5_VABI sceKernelDebugGetSdkLogText(void); +int APS5_VABI sceKernelDebugGpuPaDebugIsInProgress(void); +int APS5_VABI sceKernelDebugInjectProcessEvent(void); +int APS5_VABI sceKernelDebugPackageCorrupted(void); +int APS5_VABI sceKernelDebugRaiseExceptionWithContext(void); +int APS5_VABI sceKernelDebugRaiseExceptionWithInfo(void); +int APS5_VABI sceKernelDebugReleaseDebugContext(void); +int APS5_VABI sceKernelDeleteACInfo(void); +int APS5_VABI sceKernelDeleteBudget(void); +int APS5_VABI sceKernelDeleteCpumodeEvent(void); +int APS5_VABI sceKernelDeleteEport(void); +int APS5_VABI sceKernelDeleteGpuExceptionEvent(void); +int APS5_VABI sceKernelDeleteProcessFromCanvasMap(void); +int APS5_VABI sceKernelDeleteSavedApp(void); +int APS5_VABI sceKernelDirectMemoryQueryForId(void); +int APS5_VABI sceKernelEnableDmemAliasing2(void); +int APS5_VABI sceKernelEnablePthreadObjectCheck(void); +int APS5_VABI sceKernelEndAppMount(void); +int APS5_VABI sceKernelEndAppMount2(void); +int APS5_VABI sceKernelEventLogInit(void); +int APS5_VABI sceKernelEventLogPread(void); +int APS5_VABI sceKernelEventLogRead(void); +int APS5_VABI sceKernelEventLogWrite(void); +int APS5_VABI sceKernelExpectedHddFormat(void); +int APS5_VABI sceKernelFlock(void); +int APS5_VABI sceKernelGetACInfoAllForCoredump(void); +int APS5_VABI sceKernelGetAppCategoryType(void); +int APS5_VABI sceKernelGetAppInfoByAppId(void); +int APS5_VABI sceKernelGetAppState(void); +int APS5_VABI sceKernelGetAslrStatus(void); +int APS5_VABI sceKernelGetAutoUpdateVersion(void); +int APS5_VABI sceKernelGetBackupRestoreMode(void); +int APS5_VABI sceKernelGetBackupRestoreModeOfNextBoot(void); +int APS5_VABI sceKernelGetBasicProductShape(void); +int APS5_VABI sceKernelGetBetaUpdateTestForRcmgr(void); +int APS5_VABI sceKernelGetBioUsageAll(void); +int APS5_VABI sceKernelGetBootReqNotifyCount(void); +int APS5_VABI sceKernelGetBudget(void); +int APS5_VABI sceKernelGetBuildNumber(void); +int APS5_VABI sceKernelGetCallRecord(void); +int APS5_VABI sceKernelGetChildApp(void); +int APS5_VABI sceKernelGetCompiledSdkVersionByPath(void); +int APS5_VABI sceKernelGetCompiledSdkVersionByPid(void); +int APS5_VABI sceKernelGetCoredumpAppInfo(void); +int APS5_VABI sceKernelGetCpuUsage(void); +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 sceKernelGetCurrentFanDuty(void); +int APS5_VABI sceKernelGetDataTransferMode(void); +int APS5_VABI sceKernelGetDebugMenuMiniModeForRcmgr(void); +int APS5_VABI sceKernelGetDebugMenuModeForPsmForRcmgr(void); +int APS5_VABI sceKernelGetDebugMenuModeForRcmgr(void); +int APS5_VABI sceKernelGetDefaultToolMemorySize(void); +int APS5_VABI sceKernelGetEmergencyErrorLog(void); +int APS5_VABI sceKernelGetExecutableModuleHandle(void); +int APS5_VABI sceKernelGetExtLibcHandle(void); +int APS5_VABI sceKernelGetFakeFinalizeMenuForRcmgr(void); +int APS5_VABI sceKernelGetFirstImageAddr(void); +int APS5_VABI sceKernelGetFlagedUpdaterForRcmgr(void); +int APS5_VABI sceKernelGetForceUpdateModeForRcmgr(void); +int APS5_VABI sceKernelGetGPO(void); +int APS5_VABI sceKernelGetHwFeatureInfoForDecid(void); +int APS5_VABI sceKernelGetHwModelName(void); +int APS5_VABI sceKernelGetHwSerialNumber(void); +int APS5_VABI sceKernelGetIdPs(void); +int APS5_VABI sceKernelGetIdTableCurrentCount(void); +int APS5_VABI sceKernelGetIoreq(void); +int APS5_VABI sceKernelGetIpcPath(void); +int APS5_VABI sceKernelGetKnobs(void); +int APS5_VABI sceKernelGetKqueueFromEqueue(void); +int APS5_VABI sceKernelGetMemoryPstate(void); +int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id); +int APS5_VABI sceKernelGetPagingStatsOfAllObjects(void); +int APS5_VABI sceKernelGetPagingStatsOfAllThreads(void); +int APS5_VABI sceKernelGetParentApp(void); +int APS5_VABI sceKernelGetPhysPageSize(void); +int APS5_VABI sceKernelGetProcessName(void); +int APS5_VABI sceKernelGetProcessTypeOfBudget(void); +int APS5_VABI sceKernelGetProductCode(void); +int APS5_VABI sceKernelGetProductStr(void); +int APS5_VABI sceKernelGetPs4SystemSwVersion(void); +int APS5_VABI sceKernelGetPsmIntdevModeForRcmgr(void); +int APS5_VABI sceKernelGetPsnAccessTraceLogForRcmgr(void); +int APS5_VABI sceKernelGetQafExpirationTimeNotafterForRcmgr(void); +int APS5_VABI sceKernelGetQafExpirationTimeNotbeforeForRcmgr(void); +int APS5_VABI sceKernelGetQafGenerationForRcmgr(void); +int APS5_VABI sceKernelGetQafNameForRcmgr(void); +int APS5_VABI sceKernelGetRenderingMode(void); +int APS5_VABI sceKernelGetResidentCount(void); +int APS5_VABI sceKernelGetResidentFmemCount(void); +int APS5_VABI sceKernelGetResource(void); +int APS5_VABI sceKernelGetResourceLimit(void); +int APS5_VABI sceKernelGetResourceOne(void); +int APS5_VABI sceKernelGetSafemode(void); +int APS5_VABI sceKernelGetSanitizerMallocReplace(void); +int APS5_VABI sceKernelGetSanitizerNewReplace(void); +int APS5_VABI sceKernelGetSocClock(void); +int APS5_VABI sceKernelGetSpecialIForRcmgr(void); +int APS5_VABI sceKernelGetSubsysId(void); +int APS5_VABI sceKernelGetSubsysSubId(void); +int APS5_VABI sceKernelGetSystemLevelDebuggerModeForRcmgr(void); +int APS5_VABI sceKernelGetSystemSwBeta(void); +int APS5_VABI sceKernelGetThreadName(void); +int APS5_VABI sceKernelGetUniversalMode(void); +int APS5_VABI sceKernelGetUpdVersion(void); +int APS5_VABI sceKernelGetUtokenDataExecutionForRcmgr(void); +int APS5_VABI sceKernelGetUtokenExpirationTimeNotafterForRcmgr(void); +int APS5_VABI sceKernelGetUtokenExpirationTimeNotbeforeForRcmgr(void); +int APS5_VABI sceKernelGetUtokenFakeSharefactoryForRcmgr(void); +int APS5_VABI sceKernelGetUtokenFlagedUpdaterForRcmgr(void); +int APS5_VABI sceKernelGetUtokenNpEnvSwitchingForRcmgr(void); +int APS5_VABI sceKernelGetUtokenSaveDataRepairForRcmgr(void); +int APS5_VABI sceKernelGetUtokenStoreModeForRcmgr(void); +int APS5_VABI sceKernelGetUtokenUseSoftwagnerForAcmgr(void); +int APS5_VABI sceKernelGetUtokenUseSoftwagnerForRcmgr(void); +int APS5_VABI sceKernelGetUtokenWeakenedPortRestrictionForRcmgr(void); +int APS5_VABI sceKernelGetVrCaptureSize(void); +int APS5_VABI sceKernelGiveDirectMemoryToMapper(void); +int APS5_VABI sceKernelHasNeoMode(void); +int APS5_VABI sceKernelHwGetBdDriveInfo(void); +int APS5_VABI sceKernelHwHasOpticalOut(void); +int APS5_VABI sceKernelHwHasWlanBt(void); +int APS5_VABI sceKernelIccControlBDPowerState(void); +int APS5_VABI sceKernelIccControlUSBPowerState(void); +int APS5_VABI sceKernelIccGetBDPowerState(void); +int APS5_VABI sceKernelIccGetBootTimestamp(void); +int APS5_VABI sceKernelIccGetCPMode(void); +int APS5_VABI sceKernelIccGetCountTime(void); +int APS5_VABI sceKernelIccGetCpuInfoBit(void); +int APS5_VABI sceKernelIccGetErrLog(void); +int APS5_VABI sceKernelIccGetHwInfo(void); +int APS5_VABI sceKernelIccGetPowerNumberOfBootShutdown(void); +int APS5_VABI sceKernelIccGetPowerOperatingTime(void); +int APS5_VABI sceKernelIccGetPowerUpCause(void); +int APS5_VABI sceKernelIccGetSysEventLog(void); +int APS5_VABI sceKernelIccGetUSBPowerState(void); +int APS5_VABI sceKernelIccIndicatorBootDone(void); +int APS5_VABI sceKernelIccIndicatorShutdown(void); +int APS5_VABI sceKernelIccIndicatorStandby(void); +int APS5_VABI sceKernelIccIndicatorStandbyBoot(void); +int APS5_VABI sceKernelIccIndicatorStandbyShutdown(void); +int APS5_VABI sceKernelIccNotifyBootStatus(void); +int APS5_VABI sceKernelIccNvsFlush(void); +int APS5_VABI sceKernelIccReadPowerBootMessage(void); +int APS5_VABI sceKernelIccSetBuzzer(void); +int APS5_VABI sceKernelIccSetCPMode(void); +int APS5_VABI sceKernelIccSetCpuInfoBit(void); +int APS5_VABI sceKernelIccSetDownloadMode(void); +int APS5_VABI sceKernelInternalGetKmemStatistics(void); +int APS5_VABI sceKernelInternalGetMapStatistics(void); +int APS5_VABI sceKernelInternalHeapPrintBacktraceWithModuleInfo(void); +int APS5_VABI sceKernelInternalMapDirectMemory(void); +int APS5_VABI sceKernelInternalMapNamedDirectMemory(void); +int APS5_VABI sceKernelInternalResumeDirectMemoryRelease(void); +int APS5_VABI sceKernelInternalSuspendDirectMemoryRelease(void); +int APS5_VABI sceKernelIsAllowedToSelectDvdRegion(void); +int APS5_VABI sceKernelIsAuthenticNeo(void); +int APS5_VABI sceKernelIsCEX(void); +int APS5_VABI sceKernelIsDebuggerAttached(void); +int APS5_VABI sceKernelIsDevKit(void); +int APS5_VABI sceKernelIsExperimentalBeta(void); +int APS5_VABI sceKernelIsGenuineCEX(void); +int APS5_VABI sceKernelIsGenuineDevKit(void); +int APS5_VABI sceKernelIsGenuineKratosCex(void); +int APS5_VABI sceKernelIsGenuineN(void); +int APS5_VABI sceKernelIsGenuineTestKit(void); +int APS5_VABI sceKernelIsInSandbox(void); +int APS5_VABI sceKernelIsKratos(void); +int APS5_VABI sceKernelIsM2DeviceAttached(void); +int APS5_VABI sceKernelIsMainOnStanbyMode(void); +int APS5_VABI sceKernelIsMainOnStandbyMode(void); +int APS5_VABI sceKernelIsModuleLoaded(void); +int APS5_VABI sceKernelIsNeoMode(void); +int APS5_VABI sceKernelIsProspero(void); +int APS5_VABI sceKernelIsPs4Process(void); +int APS5_VABI sceKernelIsTestKit(void); +int APS5_VABI sceKernelJitGetSharedMemoryInfo(void); +int APS5_VABI sceKernelKernelHeapUsage(void); +int APS5_VABI sceKernelLoadStartModuleInternalForMono(void); +int APS5_VABI sceKernelLwfsAllocateBlock(void); +int APS5_VABI sceKernelLwfsLseek(void); +int APS5_VABI sceKernelLwfsSetAttribute(void); +int APS5_VABI sceKernelLwfsTrimBlock(void); +int APS5_VABI sceKernelLwfsWrite(void); +int APS5_VABI sceKernelMapNamedSystemFlexibleMemory(void); +int APS5_VABI sceKernelMapSanitizerShadowMemory(void); +int APS5_VABI sceKernelMapToolMemory(void); +int APS5_VABI sceKernelMapTraceMemory(void); +int APS5_VABI sceKernelMapperGetParam(void); +int APS5_VABI sceKernelMemoryPoolMove(void); +int APS5_VABI sceKernelMlockall(void); +int APS5_VABI sceKernelMsync(void* address, std::size_t length, int flags); +int APS5_VABI sceKernelNormalizePath(void); +int APS5_VABI sceKernelNotifyAppEvent(void); +int APS5_VABI sceKernelNotifyAppStateChanged(void); +int APS5_VABI sceKernelNotifySystemSuspendResumeProgress(void); +int APS5_VABI sceKernelNotifySystemSuspendStart(void); +int APS5_VABI sceKernelOpenEport(void); +int APS5_VABI sceKernelOpenInternal(void); +int APS5_VABI sceKernelOpenSema(void); +int APS5_VABI sceKernelPrepareDirectMemorySwap(void); +int APS5_VABI sceKernelPrepareToResumeProcess(void); +int APS5_VABI sceKernelPrepareToSuspendProcess(void); +int APS5_VABI sceKernelProtectDirectMemory(void); +int APS5_VABI sceKernelProtectDirectMemoryForPID(void); +int APS5_VABI sceKernelQueryToolMemory(void); +int APS5_VABI sceKernelQueryTraceMemory(void); +int APS5_VABI sceKernelRandomizedPath(void); +int APS5_VABI sceKernelRdup(void); +int APS5_VABI sceKernelReboot(void); +int APS5_VABI sceKernelReleaseToolMemory(void); +int APS5_VABI sceKernelReleaseTraceDirectMemory(void); +int APS5_VABI sceKernelReportUnpatchedFunctionCall(void); +int APS5_VABI sceKernelReserve2mbPage(void); +int APS5_VABI sceKernelReserveSystemDirectMemory(void); +int APS5_VABI sceKernelRestoreApp(void); +int APS5_VABI sceKernelResumeDirectMemoryRelease(void); +int APS5_VABI sceKernelResumeProcess(void); +int APS5_VABI sceKernelRtldControl(void); +int APS5_VABI sceKernelSandboxPath(void); +int APS5_VABI sceKernelSaveApp(void); +int APS5_VABI sceKernelSetAppInfo(void); +int APS5_VABI sceKernelSetAppState(void); +int APS5_VABI sceKernelSetBackupRestoreMode(void); +int APS5_VABI sceKernelSetBaseModeClock(void); +int APS5_VABI sceKernelSetBesteffort(void); +int APS5_VABI sceKernelSetBootReqNotify(void); +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 sceKernelSetDataTransferMode(void); +int APS5_VABI sceKernelSetEmergencyErrorLog(void); +int APS5_VABI sceKernelSetFsstParam(void); +int APS5_VABI sceKernelSetGPI(void); +int APS5_VABI sceKernelSetGameDirectMemoryLimit(void); +int APS5_VABI sceKernelSetGpuCu(void); +int APS5_VABI sceKernelSetIoreq(void); +int APS5_VABI sceKernelSetKnobs(void); +int APS5_VABI sceKernelSetMemoryPstate(void); +int APS5_VABI sceKernelSetNeoModeClock(void); +int APS5_VABI sceKernelSetPackageInfo(void); +int APS5_VABI sceKernelSetPhysFmemLimit(void); +int APS5_VABI sceKernelSetProcessName(void); +int APS5_VABI sceKernelSetProcessProperty(void); +int APS5_VABI sceKernelSetProcessPropertyString(void); +int APS5_VABI sceKernelSetSafemode(void); +int APS5_VABI sceKernelSetSafemodeDirect(void); +int APS5_VABI sceKernelSetSuspendState(void); +int APS5_VABI sceKernelSetTimezoneInfo(void); +int APS5_VABI sceKernelSetUpdatemode(void); +int APS5_VABI sceKernelSetVmContainer(void); +int APS5_VABI sceKernelSettimeofday(void); +int APS5_VABI sceKernelSflashGetWritePrio(void); +int APS5_VABI sceKernelSflashSetWritePrio(void); +int APS5_VABI sceKernelSlvNotifyError(void); +int APS5_VABI sceKernelSpawn(void); +int APS5_VABI sceKernelStreamWriteActivate(void); +int APS5_VABI sceKernelStreamWriteCreate(void); +int APS5_VABI sceKernelStreamWriteDeactivate(void); +int APS5_VABI sceKernelStreamWriteDelete(void); +int APS5_VABI sceKernelStreamWriteRead(void); +int APS5_VABI sceKernelStreamWriteStat(void); +int APS5_VABI sceKernelStreamWriteWrite(void); +int APS5_VABI sceKernelSuspendDirectMemoryRelease(void); +int APS5_VABI sceKernelSuspendProcess(void); +int APS5_VABI sceKernelSuspendSystem(void); +int APS5_VABI sceKernelSwitchToBaseMode(void); +int APS5_VABI sceKernelSwitchToNativeMode(void); +int APS5_VABI sceKernelSwitchToNeoMode(void); +int APS5_VABI sceKernelTerminateProcess(void); +int APS5_VABI sceKernelTerminateSysCore(void); +int APS5_VABI sceKernelTitleWorkdaroundIsEnabled(void); +int APS5_VABI sceKernelTraceMemoryTypeProtect(void); +int APS5_VABI sceKernelTriggerEport(void); +int APS5_VABI sceKernelVirtualQueryAll(void); +int APS5_VABI sceKernelWaitCommandBufferCompletion(void); +int APS5_VABI sceKernelWriteEventQueueOnCompletionCommand(void); +int APS5_VABI sceKernelWriteMapCommand(void); +int APS5_VABI sceKernelWriteMapCommand2(void); +int APS5_VABI sceKernelWriteMapDirectCommand(void); +int APS5_VABI sceKernelWriteMapDirectWithGpuMaskIdCommand(void); +int APS5_VABI sceKernelWriteMapWithGpuMaskIdCommand(void); +int APS5_VABI sceKernelWriteModifyMtypeProtectWithGpuMaskIdCommand(void); +int APS5_VABI sceKernelWriteModifyProtectCommand(void); +int APS5_VABI sceKernelWriteModifyProtectWithGpuMaskIdCommand(void); +int APS5_VABI sceKernelWriteMultiMapCommand(void); +int APS5_VABI sceKernelWriteMultiMapWithGpuMaskIdCommand(void); +int APS5_VABI sceKernelWriteRemapCommand(void); +int APS5_VABI sceKernelWriteRemapWithGpuMaskIdCommand(void); +int APS5_VABI sceKernelWriteSdkEventLog(void); +int APS5_VABI sceKernelWriteSdkEventLogV(void); +int APS5_VABI sceKernelWriteUnmapCommand(void); +int APS5_VABI sceKernelWriteWaitOnAddressCommand(void); +int APS5_VABI sceKernelWriteWaitOnCounterCommand(void); +int APS5_VABI sceKernelWriteWriteAddressCommand(void); +int APS5_VABI sceKernelWriteWriteCounterCommand(void); +int APS5_VABI sceKernelYieldCpumode(void); +int APS5_VABI sceLibcMspaceCreateForMonoMutex(void); +int APS5_VABI scePthreadAtfork(void (APS5_VABI* prepare)(), void (APS5_VABI* parent)(), void (APS5_VABI* child)()); +int APS5_VABI scePthreadAttrGetscope(const PthreadAttr* attr, int* scope); +int APS5_VABI scePthreadAttrSetcreatesuspend(PthreadAttr* attr); +int APS5_VABI scePthreadAttrSetscope(PthreadAttr* attr, int scope); +int APS5_VABI scePthreadBarrierDestroy(PthreadBarrierPrivate** barrier); +int APS5_VABI scePthreadBarrierInit(PthreadBarrierPrivate** barrier, PthreadBarrierattrPrivate* const* attr, unsigned count, const char* name); +int APS5_VABI scePthreadBarrierWait(PthreadBarrierPrivate** barrier); +int APS5_VABI scePthreadBarrierattrDestroy(PthreadBarrierattrPrivate** attr); +int APS5_VABI scePthreadBarrierattrGetpshared(PthreadBarrierattrPrivate* const* attr, int* pshared); +int APS5_VABI scePthreadBarrierattrInit(PthreadBarrierattrPrivate** attr); +int APS5_VABI scePthreadBarrierattrSetpshared(PthreadBarrierattrPrivate** attr, int pshared); +int APS5_VABI scePthreadGetconcurrency(void); +int APS5_VABI scePthreadGetcpuclockid(Pthread thread, int* clockId); +int APS5_VABI scePthreadMain(void); +int APS5_VABI scePthreadMulti(void); +int APS5_VABI scePthreadMutexGetprioceiling(void); +int APS5_VABI scePthreadMutexInitForInternalLibc(void); +int APS5_VABI scePthreadMutexIsowned(void); +int APS5_VABI scePthreadMutexSetprioceiling(void); +int APS5_VABI scePthreadMutexattrGetgen(void); +int APS5_VABI scePthreadMutexattrGetkind(void); +int APS5_VABI scePthreadMutexattrInitForInternalLibc(void); +int APS5_VABI scePthreadMutexattrSetgen(void); +int APS5_VABI scePthreadMutexattrSetkind(void); +int APS5_VABI scePthreadResume(Pthread thread); +void APS5_VABI scePthreadResumeAll(void); +int APS5_VABI scePthreadSetBesteffort(void); +int APS5_VABI scePthreadSetconcurrency(int level); +int APS5_VABI scePthreadSingle(void); +int APS5_VABI scePthreadSuspend(void); +int APS5_VABI scePthreadSuspendAll(void); +int APS5_VABI scePthreadTimedjoin(void); +int APS5_VABI sched_getparam_nid_postfix(void); +int APS5_VABI sched_getscheduler_nid_postfix(void); +int APS5_VABI sched_setparam_nid_postfix(void); +int APS5_VABI sched_setscheduler_nid_postfix(void); +int APS5_VABI sem_setname_nid_postfix(void); +int APS5_VABI set_phys_fmem_limit_nid_postfix(void); +int APS5_VABI setlogin_nid_postfix(void); +int APS5_VABI sigqueue_nid_postfix(void); +int APS5_VABI sigreturn_nid_postfix(void); +int APS5_VABI statfs_nid_postfix(void); +int APS5_VABI swapon_nid_postfix(void); +int APS5_VABI symlinkat_nid_postfix(void); +int APS5_VABI sysKernelGetIntdevModeForInternalLibc_nid_postfix(void); +int APS5_VABI sysKernelGetIntdevModeForRcmgr_nid_postfix(void); +int APS5_VABI sysKernelGetManufacturingMode_nid_postfix(void); +int APS5_VABI sysKernelGetUpdVersion_nid_postfix(void); +int APS5_VABI sysc_e00_nid_postfix(void); +int APS5_VABI sysc_m00_nid_postfix(void); +int APS5_VABI sysc_s00_nid_postfix(void); +int APS5_VABI tcgetsid_nid_postfix(void); +int APS5_VABI tcsetsid_nid_postfix(void); +int APS5_VABI unmount_nid_postfix(void); +int APS5_VABI utrace_nid_postfix(void); +int APS5_VABI uuidgen_nid_postfix(void); +int APS5_VABI vfork_nid_postfix(void); +int APS5_VABI wait3_nid_postfix(void); +int APS5_VABI wait4_nid_postfix(void); + +int APS5_VABI _openat_nid_postfix(int fd, const char* path, int flags, ...); + +} diff --git a/core/libs/prx/libkernel_sys/libkernel_sys.hpp b/core/libs/prx/libkernel_sys/libkernel_sys.hpp new file mode 100644 index 0000000000..d1700db775 --- /dev/null +++ b/core/libs/prx/libkernel_sys/libkernel_sys.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libkernel_sys/include/Api.hpp" diff --git a/core/libs/prx/libkernel_web/libkernel_web.hpp b/core/libs/prx/libkernel_web/libkernel_web.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libkernel_web/libkernel_web.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libmdbg_syscore/Export.cpp b/core/libs/prx/libmdbg_syscore/Export.cpp index 195bb3c119..98fb4bdf87 100644 --- a/core/libs/prx/libmdbg_syscore/Export.cpp +++ b/core/libs/prx/libmdbg_syscore/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libmdbg_syscore/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -226,8 +227,7 @@ int APS5_VABI sceDebugGetProcessCoredumpHandlerResult() { } int APS5_VABI sceDebugGetProcessEventCntlFlag() { - NotImplemented_nid_no_patch(__func__); - return 0; + return 1; } int APS5_VABI sceDebugGetProcessInfo() { @@ -651,8 +651,7 @@ int APS5_VABI sceDebugResumeThread() { } int APS5_VABI sceDebugSetProcessEventCntlFlag() { - NotImplemented_nid_no_patch(__func__); - return 0; + return 1; } int APS5_VABI sceDebugSetStepThread() { diff --git a/core/libs/prx/libmdbg_syscore/include/Api.hpp b/core/libs/prx/libmdbg_syscore/include/Api.hpp new file mode 100644 index 0000000000..9008d00bb7 --- /dev/null +++ b/core/libs/prx/libmdbg_syscore/include/Api.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceDebugAcquireAndUpdateDebugRegister(); +int APS5_VABI sceDebugAttachProcess(); +int APS5_VABI sceDebugCancelCoredump(); +int APS5_VABI sceDebugClearStepThread(); +int APS5_VABI sceDebugCreatePerfScratchDataArea(); +int APS5_VABI sceDebugCreatePerfScratchExecutableArea(); +int APS5_VABI sceDebugCreateScratchDataArea(); +int APS5_VABI sceDebugCreateScratchDataAreaForPrx(); +int APS5_VABI sceDebugCreateScratchExecutableArea(); +int APS5_VABI sceDebugCreateScratchExecutableAreaForPrx(); +int APS5_VABI sceDebugDestroyPerfScratchDataArea(); +int APS5_VABI sceDebugDestroyPerfScratchExecutableArea(); +int APS5_VABI sceDebugDestroyScratchDataArea(); +int APS5_VABI sceDebugDestroyScratchExecutableArea(); +int APS5_VABI sceDebugDetachProcess(); +int APS5_VABI sceDebugGetApplicationIdByTitleId(); +int APS5_VABI sceDebugGetApplicationInfo(); +int APS5_VABI sceDebugGetApplicationList(); +int APS5_VABI sceDebugGetCrashInfoDetailForCoredump(); +int APS5_VABI sceDebugGetCrashInfoForCoredump(); +int APS5_VABI sceDebugGetDLLoadFlag(); +int APS5_VABI sceDebugGetDataListOfUltQueue(); +int APS5_VABI sceDebugGetDebugRegisterStatusMap(); +int APS5_VABI sceDebugGetEventList(); +int APS5_VABI sceDebugGetEventListForEQueueFd(); +int APS5_VABI sceDebugGetEventSubscriptionList(); +int APS5_VABI sceDebugGetEventSubscriptionListForEQueueFd(); +int APS5_VABI sceDebugGetFiberInfo(); +int APS5_VABI sceDebugGetFileInfo(); +int APS5_VABI sceDebugGetFileInfoForCoredump(); +int APS5_VABI sceDebugGetFileList(); +int APS5_VABI sceDebugGetFileListForCoredump(); +int APS5_VABI sceDebugGetGpuInfoArea(); +int APS5_VABI sceDebugGetJobManagerInfo(); +int APS5_VABI sceDebugGetJobManagerSequenceInfo(); +int APS5_VABI sceDebugGetJobManagerSequenceList(); +int APS5_VABI sceDebugGetModuleInfo(); +int APS5_VABI sceDebugGetModuleList(); +int APS5_VABI sceDebugGetModuleMetaData(); +int APS5_VABI sceDebugGetMonoVMInfo(); +int APS5_VABI sceDebugGetMonoVMList(); +int APS5_VABI sceDebugGetProcessCoredumpHandlerEventBlob(); +int APS5_VABI sceDebugGetProcessCoredumpHandlerEventInfo(); +int APS5_VABI sceDebugGetProcessCoredumpHandlerResult(); +int APS5_VABI sceDebugGetProcessEventCntlFlag(); +int APS5_VABI sceDebugGetProcessInfo(); +int APS5_VABI sceDebugGetProcessList(); +int APS5_VABI sceDebugGetProcessPropertyForCoredump(); +int APS5_VABI sceDebugGetProcessResourceStatCount(); +int APS5_VABI sceDebugGetProcessResourceStatData(); +int APS5_VABI sceDebugGetProcessTime(); +int APS5_VABI sceDebugGetProcessTimeCounter(); +int APS5_VABI sceDebugGetSyncExclusiveWaiterList(); +int APS5_VABI sceDebugGetSyncObjectData(); +int APS5_VABI sceDebugGetSyncObjectList(); +int APS5_VABI sceDebugGetSyncWaiterList(); +int APS5_VABI sceDebugGetSyncWaiterListForEQueueFd(); +int APS5_VABI sceDebugGetSystemStatusBlob(); +int APS5_VABI sceDebugGetSystemStatusCount(); +int APS5_VABI sceDebugGetThreadInfo(); +int APS5_VABI sceDebugGetThreadInfoByIdent(); +int APS5_VABI sceDebugGetThreadList(); +int APS5_VABI sceDebugGetThreadListAsIdent(); +int APS5_VABI sceDebugGetUlObjectList(); +int APS5_VABI sceDebugGetUltCondvarInfo(); +int APS5_VABI sceDebugGetUltInfo(); +int APS5_VABI sceDebugGetUltListOfUltRuntime(); +int APS5_VABI sceDebugGetUltMutexInfo(); +int APS5_VABI sceDebugGetUltQueueDataResourcePoolInfo(); +int APS5_VABI sceDebugGetUltQueueInfo(); +int APS5_VABI sceDebugGetUltRuntimeInfo(); +int APS5_VABI sceDebugGetUltRwlockInfo(); +int APS5_VABI sceDebugGetUltSemaphoreInfo(); +int APS5_VABI sceDebugGetUltWaitingQueueResourcePoolInfo(); +int APS5_VABI sceDebugGetVirtualMemoryDetailInfoForCoredump(); +int APS5_VABI sceDebugGetVirtualMemoryInfo(); +int APS5_VABI sceDebugGetVirtualMemoryInfoForCoredump(); +int APS5_VABI sceDebugGetWaitingListOfUltCondvar(); +int APS5_VABI sceDebugGetWaitingListOfUltMutex(); +int APS5_VABI sceDebugGetWaitingListOfUltQueue(); +int APS5_VABI sceDebugGetWaitingListOfUltRwlock(); +int APS5_VABI sceDebugGetWaitingListOfUltSemaphore(); +int APS5_VABI sceDebugGetWorkerThreadListOfUltRuntime(); +int APS5_VABI sceDebugInit(); +int APS5_VABI sceDebugInitForCoredump(); +int APS5_VABI sceDebugInitForTest(); +int APS5_VABI sceDebugIpmiGetBlockTimeInfoList(); +int APS5_VABI sceDebugIpmiGetBlockedIpcInfo(); +int APS5_VABI sceDebugIpmiGetChannelInfo(); +int APS5_VABI sceDebugIpmiGetChannelKidList(); +int APS5_VABI sceDebugIpmiGetChannelWaitingThreadList(); +int APS5_VABI sceDebugIpmiGetClientInfo(); +int APS5_VABI sceDebugIpmiGetClientKidList(); +int APS5_VABI sceDebugIpmiGetClientKidListByDump(); +int APS5_VABI sceDebugIpmiGetClientKidListByServerKid(); +int APS5_VABI sceDebugIpmiGetConnectRequestInfoList(); +int APS5_VABI sceDebugIpmiGetConnectionInfoList(); +int APS5_VABI sceDebugIpmiGetConnectionWaitingThreadListByClientKid(); +int APS5_VABI sceDebugIpmiGetConnectionWaitingThreadListBySessionKid(); +int APS5_VABI sceDebugIpmiGetDump(); +int APS5_VABI sceDebugIpmiGetEncryptedInfoAllForCoredump(); +int APS5_VABI sceDebugIpmiGetKidInfoListForCoredump(); +int APS5_VABI sceDebugIpmiGetServerDispatchInfo(); +int APS5_VABI sceDebugIpmiGetServerInfo(); +int APS5_VABI sceDebugIpmiGetServerKidList(); +int APS5_VABI sceDebugIpmiGetServerKidListByDump(); +int APS5_VABI sceDebugIpmiGetServerWaitingThreadList(); +int APS5_VABI sceDebugIpmiGetSessionInfo(); +int APS5_VABI sceDebugIpmiGetSessionKidList(); +int APS5_VABI sceDebugIpmiGetSessionKidListByServerKid(); +int APS5_VABI sceDebugIpmiGetTidListByDump(); +int APS5_VABI sceDebugKillApplication(); +int APS5_VABI sceDebugKillProcess(); +int APS5_VABI sceDebugNoStopChildProcesses(); +int APS5_VABI sceDebugNoStopOnDLLoad(); +int APS5_VABI sceDebugProcessSpawn(); +int APS5_VABI sceDebugReadEvent(); +int APS5_VABI sceDebugReadEventForTest(); +int APS5_VABI sceDebugReadProcessMemory(); +int APS5_VABI sceDebugReadProcessMemoryForSDBGP(); +int APS5_VABI sceDebugReadProcessRegister(); +int APS5_VABI sceDebugReadProcessRegisterForSDBGP(); +int APS5_VABI sceDebugReadProcessResourceStatData(); +int APS5_VABI sceDebugReadThreadRegister(); +int APS5_VABI sceDebugReadThreadRegisterForSDBGP(); +int APS5_VABI sceDebugReleaseDebugRegister(); +int APS5_VABI sceDebugResumeApplication(); +int APS5_VABI sceDebugResumeProcess(); +int APS5_VABI sceDebugResumeThread(); +int APS5_VABI sceDebugSetProcessEventCntlFlag(); +int APS5_VABI sceDebugSetStepThread(); +int APS5_VABI sceDebugSpawnApplication(); +int APS5_VABI sceDebugStopChildProcesses(); +int APS5_VABI sceDebugStopOnDLLoad(); +int APS5_VABI sceDebugSuspendApplication(); +int APS5_VABI sceDebugSuspendProcess(); +int APS5_VABI sceDebugSuspendThread(); +int APS5_VABI sceDebugTriggerCoredump(); +int APS5_VABI sceDebugTriggerCoredumpForSystem(); +int APS5_VABI sceDebugTriggerVrCaptureDump(); +int APS5_VABI sceDebugWriteProcessMemory(); +int APS5_VABI sceDebugWriteProcessRegister(); +int APS5_VABI sceDebugWriteThreadRegister(); + +} diff --git a/core/libs/prx/libmdbg_syscore/libmdbg_syscore.hpp b/core/libs/prx/libmdbg_syscore/libmdbg_syscore.hpp new file mode 100644 index 0000000000..8a0fe08e5f --- /dev/null +++ b/core/libs/prx/libmdbg_syscore/libmdbg_syscore.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libmdbg_syscore/include/Api.hpp" diff --git a/core/libs/prx/ulobjmgr/Export.cpp b/core/libs/prx/ulobjmgr/Export.cpp index 25b7f547b2..31328f15db 100644 --- a/core/libs/prx/ulobjmgr/Export.cpp +++ b/core/libs/prx/ulobjmgr/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/ulobjmgr/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -23,4 +24,12 @@ int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id) { return 0; } +int APS5_VABI _sceUlobjmgrRegisterObject_nid_postfix(std::uint64_t object, std::int32_t kind, std::uint32_t* id) { + return _sceUlobjmgrRegisterObject(object, kind, id); +} + +int APS5_VABI _sceUlobjmgrUnregisterObject_nid_postfix(std::uint32_t id) { + return _sceUlobjmgrUnregisterObject(id); +} + } diff --git a/core/libs/prx/ulobjmgr/include/Api.hpp b/core/libs/prx/ulobjmgr/include/Api.hpp new file mode 100644 index 0000000000..f551d92f17 --- /dev/null +++ b/core/libs/prx/ulobjmgr/include/Api.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id); +int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id); + +int APS5_VABI _sceUlobjmgrRegisterObject_nid_postfix(std::uint64_t object, std::int32_t kind, std::uint32_t* id); +int APS5_VABI _sceUlobjmgrUnregisterObject_nid_postfix(std::uint32_t id); + +} diff --git a/core/libs/prx/ulobjmgr/ulobjmgr.hpp b/core/libs/prx/ulobjmgr/ulobjmgr.hpp new file mode 100644 index 0000000000..3a6f8594df --- /dev/null +++ b/core/libs/prx/ulobjmgr/ulobjmgr.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/ulobjmgr/include/Api.hpp" diff --git a/core/libs/tests/AppArchiveFixture.hpp b/core/libs/tests/AppArchiveFixture.hpp index b6c5e4529c..234c647e3c 100644 --- a/core/libs/tests/AppArchiveFixture.hpp +++ b/core/libs/tests/AppArchiveFixture.hpp @@ -20,8 +20,11 @@ inline std::vector Pattern(std::size_t size) { } inline std::filesystem::path EnterFreshRoot(const char* name) { - const auto root = std::filesystem::temp_directory_path() / - (std::string(name) + "-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + std::filesystem::path root; + do { + root = std::filesystem::temp_directory_path() / + (std::string(name) + "-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + } while (!std::filesystem::create_directory(root)); std::filesystem::create_directories(root / "app0" / "sce_sys"); std::filesystem::current_path(root); return std::filesystem::canonical(root); diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index be99345d50..8c02b16146 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -22,24 +23,9 @@ #endif extern "C" { -void APS5_VABI _init_env_nid_postfix(); -int APS5_VABI posix_memalign_nid_postfix(void**, std::size_t, std::size_t); -void* APS5_VABI reallocf_nid_postfix(void*, std::size_t); -void* APS5_VABI _Znwm_nid_postfix(std::size_t); -void* APS5_VABI _ZnwmRKSt9nothrow_t_nid_postfix(std::size_t, const void*) noexcept; -void* APS5_VABI _ZnamRKSt9nothrow_t_nid_postfix(std::size_t, const void*) noexcept; -void APS5_VABI _ZdlPv_nid_postfix(void*); -void APS5_VABI _ZdaPv_nid_postfix(void*); -void APS5_VABI _ZdlPvSt11align_val_t_nid_postfix(void*, std::size_t); -void APS5_VABI _ZdlPvmSt11align_val_t_nid_postfix(void*, std::size_t, std::size_t); + void* ApplicationHeapRealign_nid_no_patch(void*, std::size_t, std::size_t); -char* APS5_VABI strdup_nid_postfix(const char*); -char* APS5_VABI strndup_nid_postfix(const char*, std::size_t); -char* APS5_VABI getcwd_nid_postfix(char*, std::size_t); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI atexit_nid_postfix(void (APS5_VABI*)()); -void* APS5_VABI scriptingGetMem_nid_postfix(std::size_t, std::size_t); -void APS5_VABI scriptingFreeMem_nid_postfix(void*); + } namespace { @@ -256,8 +242,10 @@ int main(int argc, char** argv) { require(initializes == 0); require(_Znwm_nid_postfix(40) == storage.data() && lastSize == 40 && initializes == 1); _ZdlPv_nid_postfix(storage.data()); + require(frees == 1); _init_env_nid_postfix(); + require(initializes == 1); return 0; } @@ -275,11 +263,14 @@ int main(int argc, char** argv) { require(first[blockBytes - 1] == std::byte{0x5a} && second[0] == std::byte{0xa5}); scriptingFreeMem_nid_postfix(first); scriptingFreeMem_nid_postfix(second); + require(initializes == 0 && frees == 0); _init_env_nid_postfix(); + require(initializes == 1); require(_Znwm_nid_postfix(40) == storage.data() && lastSize == 40); _ZdlPv_nid_postfix(storage.data()); + require(frees == 1); return 0; } @@ -303,7 +294,8 @@ int main(int argc, char** argv) { for (unsigned i = 0; i < 63; ++i) require(pointer[i] == 0x5a); pointer = static_cast(ApplicationHeapReallocate_nid_no_patch(pointer, 11)); for (unsigned i = 0; i < 11; ++i) require(pointer[i] == 0x5a); - require(ApplicationHeapReallocate_nid_no_patch(pointer, 0) == nullptr); + require(ApplicationHeapReallocate_nid_no_patch(pointer, 0) == pointer && ApplicationHeapUsableSize_nid_no_patch(pointer) == 0); + ApplicationHeapFree_nid_no_patch(pointer); pointer = static_cast(ApplicationHeapReallocate_nid_no_patch(nullptr, 32)); require(pointer != nullptr); ApplicationHeapFree_nid_no_patch(pointer); @@ -317,14 +309,20 @@ int main(int argc, char** argv) { require(pointer != nullptr && ApplicationHeapUsableSize_nid_no_patch(pointer) == 9); for (unsigned i = 0; i < 9; ++i) require(pointer[i] == 0x3c); *__error_nid_postfix() = 0; - require(reallocf_nid_postfix(pointer, 0) == nullptr && *__error_nid_postfix() == 0); + require(reallocf_nid_postfix(pointer, 0) == pointer && *__error_nid_postfix() == 0 && ApplicationHeapUsableSize_nid_no_patch(pointer) == 0); + ApplicationHeapFree_nid_no_patch(pointer); reject([pointer] { ApplicationHeapUsableSize_nid_no_patch(pointer); }); pointer = static_cast(reallocf_nid_postfix(nullptr, 40)); require(pointer != nullptr); - require(reallocf_nid_postfix(pointer, SIZE_MAX - 4096) == nullptr && *__error_nid_postfix() == 12); + *__error_nid_postfix() = 77; + require(reallocf_nid_postfix(pointer, SIZE_MAX - 4096) == nullptr && *__error_nid_postfix() == 77); reject([pointer] { ApplicationHeapUsableSize_nid_no_patch(pointer); }); - *__error_nid_postfix() = 0; - require(reallocf_nid_postfix(nullptr, SIZE_MAX) == nullptr && *__error_nid_postfix() == 12); + pointer = static_cast(reallocf_nid_postfix(nullptr, 40)); + require(pointer != nullptr); + require(reallocf_nid_postfix(pointer, SIZE_MAX - 0x1008f) == nullptr && *__error_nid_postfix() == 22); + reject([pointer] { ApplicationHeapUsableSize_nid_no_patch(pointer); }); + *__error_nid_postfix() = 77; + require(reallocf_nid_postfix(nullptr, SIZE_MAX) == nullptr && *__error_nid_postfix() == 77); void* empty = ApplicationHeapAllocate_nid_no_patch(0); require(empty != nullptr); ApplicationHeapFree_nid_no_patch(empty); @@ -335,6 +333,7 @@ int main(int argc, char** argv) { auto* aligned = ApplicationHeapAlign_nid_no_patch(alignment, 37); require(reinterpret_cast(aligned) % alignment == 0); _ZdlPvSt11align_val_t_nid_postfix(aligned, alignment); + } void* aligned = nullptr; require(ApplicationHeapPosixAlign_nid_no_patch(&aligned, 256, 99) == 0); @@ -378,21 +377,27 @@ int main(int argc, char** argv) { require(_Znwm_nid_postfix(0) == storage.data() && lastSize == 1); _ZdlPv_nid_postfix(storage.data()); _ZdlPv_nid_postfix(nullptr); + require(frees == 3); require(_ZnamRKSt9nothrow_t_nid_postfix(24, nullptr) == storage.data() && lastSize == 24); _ZdaPv_nid_postfix(storage.data()); + require(frees == 4); require(ApplicationHeapRealign_nid_no_patch(storage.data(), 48, 32) == storage.data() && lastSize == 48 && lastAlignment == 32); reject([] { ApplicationHeapRealign_nid_no_patch(storage.data(), 16, 4096); }); pointer = ApplicationHeapAlign_nid_no_patch(64, 37); _ZdlPvSt11align_val_t_nid_postfix(pointer, 64); + require(frees == 5); _ZdlPvSt11align_val_t_nid_postfix(nullptr, 64); + require(frees == 5); pointer = ApplicationHeapAlign_nid_no_patch(64, 37); _ZdlPvmSt11align_val_t_nid_postfix(pointer, 37, 64); + require(frees == 6); _ZdlPvmSt11align_val_t_nid_postfix(nullptr, 37, 64); + require(frees == 6); require(reallocf_nid_postfix(storage.data(), 80) == storage.data() && lastSize == 80 && frees == 6); require(reallocf_nid_postfix(storage.data(), 16) == storage.data() && lastSize == 16 && frees == 6); @@ -473,6 +478,7 @@ int main(int argc, char** argv) { const auto beforeScripting = frees; const auto sizeBeforeScripting = lastSize; scriptingFreeMem_nid_postfix(nullptr); + auto* small = static_cast(scriptingGetMem_nid_postfix(0, 31)); auto* aligned = static_cast(scriptingGetMem_nid_postfix(64, 37)); require(small != nullptr && small != storage.data() && reinterpret_cast(small) % 16 == 0); @@ -482,12 +488,14 @@ int main(int argc, char** argv) { require(small + 31 <= aligned || aligned + 37 <= small); scriptingFreeMem_nid_postfix(small); scriptingFreeMem_nid_postfix(aligned); + require(scriptingGetMem_nid_postfix(24, 80) == nullptr); require(scriptingGetMem_nid_postfix(16, std::numeric_limits::max()) == nullptr); fail = true; auto* unaffected = scriptingGetMem_nid_postfix(16, 64); require(unaffected != nullptr); scriptingFreeMem_nid_postfix(unaffected); + fail = false; require(frees == beforeScripting && lastSize == sizeBeforeScripting); } diff --git a/core/libs/tests/ApplicationHeapStats.cpp b/core/libs/tests/ApplicationHeapStats.cpp index 1345d218d1..8844a5c835 100644 --- a/core/libs/tests/ApplicationHeapStats.cpp +++ b/core/libs/tests/ApplicationHeapStats.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -6,11 +7,6 @@ #include #include -extern "C" { -int APS5_VABI malloc_stats_fast_nid_postfix(void*); -std::size_t APS5_VABI malloc_usable_size_nid_postfix(void*); -} - namespace { alignas(64) std::array storage{}; diff --git a/core/libs/tests/ExceptionCCleanup.cpp b/core/libs/tests/ExceptionCCleanup.cpp index cca7677edb..9612a8aaa3 100644 --- a/core/libs/tests/ExceptionCCleanup.cpp +++ b/core/libs/tests/ExceptionCCleanup.cpp @@ -1,10 +1,10 @@ +#include "prx/libc/include/Api.hpp" #include #include #include extern "C" void CallWithCleanup(void (*function)(), int* count); extern "C" void CallAroundCleanup(void (*before)(), void (*within)(), int* count); -extern "C" void (*_ZSt13set_terminatePFvvE_nid_postfix(void (*)()))(); int cleanups; diff --git a/core/libs/tests/ExceptionRuntime.cpp b/core/libs/tests/ExceptionRuntime.cpp index 54111f196c..633a2d2005 100644 --- a/core/libs/tests/ExceptionRuntime.cpp +++ b/core/libs/tests/ExceptionRuntime.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #undef NDEBUG #include #include @@ -19,28 +20,6 @@ #ifdef __linux__ #include #endif -#include - -extern "C" unsigned __cxa_uncaught_exceptions_nid_postfix(); -extern "C" void* __cxa_current_primary_exception_nid_postfix(); -extern "C" void __cxa_decrement_exception_refcount_nid_postfix(void*); -extern "C" void __cxa_rethrow_primary_exception_nid_postfix(void*); - -extern "C" [[noreturn]] void _ZSt14_Xout_of_rangePKc_nid_postfix(const char*); -extern "C" [[noreturn]] void _ZSt13_Xrange_errorPKc_nid_postfix(const char*); -extern "C" [[noreturn]] void _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix(); -extern "C" [[noreturn]] void __cxa_bad_cast_nid_postfix(); -extern "C" [[noreturn]] void _ZSt19_Throw_bad_weak_ptrv_nid_postfix(); -extern "C" [[noreturn]] void _ZSt26__throw_bad_variant_accessv_nid_postfix(); -extern "C" [[noreturn]] void _ZSt26_Throw_bad_optional_accessv_nid_postfix(); -extern "C" [[noreturn]] void _ZNKSt9exception6_RaiseEv_nid_postfix(const void*); -extern "C" void* __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, void(*)(void*), void(*)(void*), void*(*)(std::size_t), void(*)(void*, std::size_t)); -extern "C" void __cxa_vec_delete3_nid_postfix(void*, std::size_t, std::size_t, void(*)(void*), void(*)(void*, std::size_t)); -extern "C" _Unwind_Reason_Code _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn, void*); -extern "C" _Unwind_Reason_Code _Unwind_ForcedUnwind_nid_postfix(_Unwind_Exception*, _Unwind_Stop_Fn, void*); -extern "C" std::uintptr_t _Unwind_GetIP_nid_postfix(_Unwind_Context*); -extern "C" std::uintptr_t _Unwind_GetGR_nid_postfix(_Unwind_Context*, int); -extern "C" void (*_ZSt13set_terminatePFvvE_nid_postfix(void(*)()))(); thread_local int destroyed = 0; struct Guard { @@ -61,6 +40,7 @@ struct Virtual : virtual Base {}; struct Left : Base {}; struct Right : Base {}; struct Repeated : Left, Right {}; +struct MixedRepeated : Left, private Right {}; struct LeftVirtual : virtual Base {}; struct RightVirtual : virtual Base {}; struct Diamond : LeftVirtual, RightVirtual {}; @@ -80,6 +60,9 @@ void CheckRtti() { try { throw static_cast(nullptr); } catch (Base*) { assert(false); } catch (Repeated* p) { assert(p == nullptr); } + try { throw MixedRepeated(); } + catch (Base&) { assert(false); } + catch (MixedRepeated&) {} try { throw static_cast(nullptr); } catch (Base* p) { assert(p == nullptr); } try { throw nullptr; } @@ -99,7 +82,7 @@ void CheckForeign() { assert(reason == _URC_FOREIGN_EXCEPTION_CAUGHT); ++foreignDeleted; delete p; }; try { - try { _Unwind_RaiseException(exception); assert(false); } + try { _Unwind_RaiseException_nid_postfix(exception); assert(false); } catch (...) { assert(__cxa_current_primary_exception_nid_postfix() == nullptr); throw; } } catch (...) {} assert(foreignDeleted == 1); @@ -134,6 +117,7 @@ void CheckArrays() { failArray = true; try { __cxa_vec_new3_nid_postfix(4, sizeof(int), sizeof(std::size_t), ArrayConstruct, ArrayDestroy, std::malloc, ArrayFree); + assert(false); } catch (int value) { assert(value == 73); } assert(arrayConstructed == 2 && arrayDestroyed == 2 && arrayFreed == 1); @@ -141,6 +125,7 @@ void CheckArrays() { failArray = false; void* array = __cxa_vec_new3_nid_postfix(4, sizeof(int), sizeof(std::size_t), ArrayConstruct, ArrayDestroy, std::malloc, ArrayFree); __cxa_vec_delete3_nid_postfix(array, sizeof(int), sizeof(std::size_t), ArrayDestroy, ArrayFree); + assert(arrayConstructed == 4 && arrayDestroyed == 4 && arrayFreed == 1); } void* ThreadTest(void*) { @@ -226,6 +211,7 @@ int main() { try { __cxa_rethrow_primary_exception_nid_postfix(retained); assert(false); } catch (int value) { assert(value == 27); } __cxa_decrement_exception_refcount_nid_postfix(retained); + { auto made = std::make_exception_ptr(Counted()); try { std::rethrow_exception(made); assert(false); } catch (const Counted&) {} @@ -248,8 +234,17 @@ int main() { catch (const std::range_error&) {} bool badFunctionCall = false; try { _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix(); } - catch (const std::bad_function_call&) { badFunctionCall = true; } + catch (const std::bad_function_call&) { std::abort(); } + catch (const std::exception& value) { + if (std::strcmp(typeid(value).name(), "NSt8__sce_v217bad_function_callE") != 0) std::abort(); + if (std::strcmp(value.what(), "bad function call") != 0) std::abort(); + badFunctionCall = true; + } if (!badFunctionCall) std::abort(); + bool plainBadFunctionCall = false; + try { _ZSt19_Xbad_function_callv_nid_postfix(); } + catch (const std::bad_function_call&) { plainBadFunctionCall = true; } + if (!plainBadFunctionCall) std::abort(); try { __cxa_bad_cast_nid_postfix(); } catch (const std::exception& value) { assert(value.what() != nullptr); } try { _ZSt19_Throw_bad_weak_ptrv_nid_postfix(); std::abort(); } @@ -260,8 +255,17 @@ int main() { catch (const std::bad_optional_access& value) { if (std::strcmp(value.what(), "bad_optional_access") != 0) std::abort(); } try { _ZNKSt9exception6_RaiseEv_nid_postfix(nullptr); assert(false); } catch (const std::invalid_argument&) {} - try { _ZNKSt9exception6_RaiseEv_nid_postfix(&object); assert(false); } + LibcException::ExceptionObject exceptionObject {}; + _ZNSt9exceptionC1Ev_nid_postfix(&exceptionObject); + try { _ZNKSt9exception6_RaiseEv_nid_postfix(&exceptionObject); assert(false); } catch (const std::exception& value) { assert(std::strcmp(value.what(), "std::exception") == 0); } + _ZNSt9exceptionD1Ev_nid_postfix(&exceptionObject); + try { _ZNKSt8bad_cast8_DoraiseEv_nid_postfix(nullptr); assert(false); } + catch (const std::invalid_argument&) {} + _ZNSt8bad_castC1Ev_nid_postfix(&exceptionObject); + try { _ZNKSt8bad_cast8_DoraiseEv_nid_postfix(&exceptionObject); assert(false); } + catch (const std::bad_cast&) {} + _ZNSt8bad_castD1Ev_nid_postfix(&exceptionObject); CheckRtti(); CheckForeign(); CheckStaticInitialization(); diff --git a/core/libs/tests/Fiber.cpp b/core/libs/tests/Fiber.cpp index 73b6a9afb1..b66f020ff5 100644 --- a/core/libs/tests/Fiber.cpp +++ b/core/libs/tests/Fiber.cpp @@ -1,9 +1,11 @@ +#include "prx/libSceFiber/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include +#include #include #include #ifdef _WIN32 @@ -12,20 +14,6 @@ using Entry = void (APS5_VABI*)(std::uint64_t, std::uint64_t); -extern "C" { -std::int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject*, const char*, Entry, std::uint64_t, void*, std::uint64_t, const void*, std::uint32_t); -std::int32_t APS5_VABI sceFiberFinalize(FiberObject*); -std::int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject*, std::uint64_t, std::uint64_t*); -std::int32_t APS5_VABI sceFiberSwitch(FiberObject*, std::uint64_t, std::uint64_t*); -std::int32_t APS5_VABI sceFiberReturnToThread(std::uint64_t, std::uint64_t*); -std::int32_t APS5_VABI sceFiberGetSelf(FiberObject**); -std::int32_t APS5_VABI sceFiberGetInfo(FiberObject*, FiberInfo*); -std::int32_t APS5_VABI sceFiberGetThreadFramePointerAddress(std::uint64_t*); -std::int32_t APS5_VABI _sceFiberGetThreadFramePointerAddress(std::uint64_t*); -std::int32_t APS5_VABI sceFiberStartContextSizeCheck(std::uint32_t); -std::int32_t APS5_VABI sceFiberStopContextSizeCheck(void); -} - static void Require(bool value, const char* what) { if (!value) { std::fprintf(stderr, "Fiber check failed: %s\n", what); std::abort(); } } @@ -33,6 +21,8 @@ static void Require(bool value, const char* what) { namespace { constexpr std::int32_t FiberErrorNull = static_cast(0x80590001); +constexpr std::int32_t FiberErrorAlignment = static_cast(0x80590002); +constexpr std::int32_t FiberErrorRange = static_cast(0x80590003); constexpr std::int32_t FiberErrorInvalid = static_cast(0x80590004); constexpr std::int32_t FiberErrorPermission = static_cast(0x80590005); constexpr std::int32_t FiberErrorState = static_cast(0x80590006); @@ -225,6 +215,7 @@ void APS5_VABI LargeContextEntry(std::uint64_t, std::uint64_t) { const auto* address = const_cast(&local); g_largeOnContext = address >= g_largeContext.data() && address < g_largeContext.data() + g_largeContext.size(); sceFiberReturnToThread(0, nullptr); + } void RunOnLargeContext() { @@ -235,6 +226,280 @@ void RunOnLargeContext() { Require(sceFiberFinalize(fiber) == 0, "finalize large fiber"); } +alignas(16) std::array g_attachedContext; +alignas(16) std::array g_attachedSmallContext; +FiberStorage g_attachedStorage; +FiberStorage g_attachedTargetStorage; +auto* const g_attached = reinterpret_cast(&g_attachedStorage); +auto* const g_attachedTarget = reinterpret_cast(&g_attachedTargetStorage); + +void APS5_VABI AttachedTargetEntry(std::uint64_t argOnInitialize, std::uint64_t argOnRun) { + Require(argOnInitialize == 44 && argOnRun == 500, "attached switch target arguments"); + CheckActiveContext(g_attachedTarget, g_attachedSmallContext.size()); + std::uint64_t received = 0; + Require(sceFiberSwitch(g_attached, 600, &received) == 0, "switch back from the attached target"); + Require(false, "attached switch target resumed after its last switch"); +} + +void APS5_VABI AttachedEntry(std::uint64_t argOnInitialize, std::uint64_t argOnRun) { + Require(argOnInitialize == 33 && argOnRun == 400, "attached fiber arguments"); + FiberObject* self = nullptr; + Require(sceFiberGetSelf(&self) == 0 && self == g_attached, "attached fiber self"); + CheckActiveContext(g_attached, g_attachedContext.size()); + Require(_sceFiberAttachContextAndRun(g_attachedTarget, g_attachedSmallContext.data(), g_attachedSmallContext.size(), 0, nullptr) == FiberErrorPermission, "attach and run inside a fiber"); + std::uint64_t received = 0; + Require(_sceFiberAttachContextAndSwitch(g_attached, g_attachedContext.data(), g_attachedContext.size(), 0, nullptr) == FiberErrorState, "attach and switch to self"); + Require(_sceFiberAttachContextAndSwitch(g_attachedTarget, g_attachedSmallContext.data() + 8, g_attachedSmallContext.size() - 16, 500, nullptr) == FiberErrorAlignment, "attach and switch with a misaligned context"); + Require(_sceFiberAttachContextAndSwitch(g_attachedTarget, g_attachedSmallContext.data(), g_attachedSmallContext.size(), 500, &received) == 0 && received == 600, "attach and switch"); + Require(sceFiberReturnToThread(700, &received) == 0 && received == 800, "attached fiber resumed"); + for (;;) sceFiberReturnToThread(received + 1, &received); +} + +void APS5_VABI DefaultFpuEntry(std::uint64_t, std::uint64_t) { + std::uint16_t control = 0; + asm volatile("fnstcw %0" : "=m"(control)); + std::uint64_t received = 0; + for (;;) sceFiberReturnToThread((static_cast(control) << 32) | _mm_getcsr(), &received); +} + +void RunAttachedContext() { + Require(_sceFiberInitializeImpl_nid_postfix(g_attached, "attached", AttachedEntry, 33, nullptr, 0, nullptr, 0) == 0, "initialize without a context"); + Require(_sceFiberInitializeImpl_nid_postfix(g_attachedTarget, "attached target", AttachedTargetEntry, 44, nullptr, 0, nullptr, 0) == 0, "initialize switch target without a context"); + FiberInfo info{}; + info.size = sizeof(info); + Require(sceFiberGetInfo(g_attached, &info) == 0 && info.addr_context == nullptr && info.size_context == 0, "fiber without a context reports none"); + Require(_sceFiberAttachContextAndRun(nullptr, g_attachedContext.data(), g_attachedContext.size(), 0, nullptr) == FiberErrorNull, "attach and run a null fiber"); + Require(_sceFiberAttachContextAndRun(g_attached, nullptr, g_attachedContext.size(), 0, nullptr) == FiberErrorInvalid, "attach a null context with a size"); + Require(_sceFiberAttachContextAndRun(g_attached, g_attachedContext.data(), 0, 0, nullptr) == FiberErrorRange, "attach a context without a size"); + Require(_sceFiberAttachContextAndRun(g_attached, g_attachedContext.data(), 256, 0, nullptr) == FiberErrorRange, "attach a context below the minimum size"); + Require(_sceFiberAttachContextAndRun(g_attached, g_attachedContext.data(), 1000, 0, nullptr) == FiberErrorAlignment, "attach a context of an unaligned size"); + Require(_sceFiberAttachContextAndSwitch(g_attached, g_attachedContext.data(), g_attachedContext.size(), 0, nullptr) == FiberErrorPermission, "attach and switch outside a fiber"); + std::uint64_t returned = 0; + Require(_sceFiberAttachContextAndRun(g_attached, g_attachedContext.data(), g_attachedContext.size(), 400, &returned) == 0 && returned == 700, "attach and run"); + Require(_sceFiberAttachContextAndRun(g_attached, g_attachedContext.data(), g_attachedContext.size(), 800, &returned) == FiberErrorInvalid, "attach to a fiber that has a context"); + Require(_sceFiberAttachContextAndRun(g_attached, nullptr, 0, 800, &returned) == 0 && returned == 801, "run an attached fiber without a new context"); + Require(sceFiberRun_nid_postfix(g_attached, 802, &returned) == 0 && returned == 803, "run an attached fiber"); + Require(sceFiberFinalize(g_attachedTarget) == 0, "finalize a suspended attached target"); + Require(sceFiberFinalize(g_attached) == 0, "finalize attached fiber"); + Require(_sceFiberInitializeImpl_nid_postfix(g_attachedTarget, "attached target", AttachedTargetEntry, 44, nullptr, 0, nullptr, 0) == 0, "reinitialize attached target"); + Require(sceFiberFinalize(g_attachedTarget) == 0, "finalize fiber without a context"); +} + +void RunInternalOptions() { + auto* fiber = reinterpret_cast(&g_checkedStorage); + Require(_sceFiberInitializeWithInternalOptionImpl(nullptr, "options", CheckedEntry, 0, g_checkedContext.data(), g_checkedContext.size(), nullptr, 0, 0) == FiberErrorNull, "internal option null fiber"); + Require(_sceFiberInitializeWithInternalOptionImpl(fiber, "options", CheckedEntry, 0, g_checkedContext.data(), 256, nullptr, 0, 0) == FiberErrorRange, "internal option small context"); + Require(_sceFiberInitializeWithInternalOptionImpl(fiber, "options", CheckedEntry, 0, g_checkedContext.data(), g_checkedContext.size(), nullptr, 0x1, 0) == 0, "internal option without object registration"); + FiberInfo info{}; + info.size = sizeof(info); + Require(sceFiberGetInfo(fiber, &info) == 0 && info.size_context_margin == ~0ull, "no margin without the size check flag"); + Require(sceFiberFinalize(fiber) == 0, "finalize internal option fiber"); + Require(_sceFiberInitializeWithInternalOptionImpl(fiber, "options", CheckedEntry, 0, g_checkedContext.data(), g_checkedContext.size(), nullptr, 0x10, 0) == 0, "internal option size check"); + Require(sceFiberGetInfo(fiber, &info) == 0 && info.size_context_margin == g_checkedContext.size(), "size check flag fills the context"); + Require(sceFiberRun_nid_postfix(fiber, 0, nullptr) == 0, "run size checked fiber"); + Require(sceFiberGetInfo(fiber, &info) == 0 && info.size_context_margin < g_checkedContext.size() - 4096, "size check flag measures the margin"); + Require(sceFiberFinalize(fiber) == 0, "finalize size checked fiber"); + + Require(_sceFiberInitializeWithInternalOptionImpl(fiber, "fpu", DefaultFpuEntry, 0, nullptr, 0, nullptr, 0x100, 0) == 0, "internal option fpu defaults without a context"); + const auto threadCsr = _mm_getcsr(); + _mm_setcsr((threadCsr & ~0x8040u) | 0x6000u); + std::uint64_t returned = 0; + Require(_sceFiberAttachContextAndRun(fiber, g_checkedContext.data(), g_checkedContext.size(), 0, &returned) == 0, "run fpu defaults fiber"); + _mm_setcsr(threadCsr); + Require((returned & 0xffffffffu) == 0x9fc0u, "fpu defaults flag starts with the default MXCSR"); + Require((returned >> 32) == 0x037fu, "fpu defaults flag starts with the default x87 control word"); + Require(sceFiberFinalize(fiber) == 0, "finalize fpu defaults fiber"); +} + +std::array g_contextlessStorage; +std::array g_contextlessEntries{}; +alignas(16) std::array g_transitionContext; +FiberStorage g_transitionStorage; +auto* const g_transition = reinterpret_cast(&g_transitionStorage); +std::uintptr_t g_callerStack = 0; +std::uint64_t g_contextlessFramePointer = 0; +unsigned g_contextlessHops = 0; +#ifdef _WIN32 +void* g_callerBase = nullptr; +void* g_callerLimit = nullptr; +#endif + +FiberObject* Contextless(unsigned index) { + return reinterpret_cast(&g_contextlessStorage[index]); +} + +void APS5_VABI ContextlessEntry(std::uint64_t index, std::uint64_t run) { + ++g_contextlessEntries[index]; + auto* self = Contextless(index); + FiberObject* current = nullptr; + Require(sceFiberGetSelf(¤t) == 0 && current == self, "contextless entry self"); + FiberInfo info{}; + info.size = sizeof(info); + Require(sceFiberGetInfo(self, &info) == 0, "contextless entry info"); + if (info.addr_context) { + Require(run == 90, "attaching after contextless execution restarts entry"); + for (;;) sceFiberReturnToThread(run + 1, &run); + } + Require(info.size_context == 0 && info.size_context_margin == ~0ull, "contextless info has no stack or margin"); + std::uint64_t framePointer = 0; + Require(sceFiberGetThreadFramePointerAddress(&framePointer) == 0 && framePointer == g_contextlessFramePointer, "contextless entry preserves the caller frame pointer"); + Require(sceFiberRun_nid_postfix(self, 0, nullptr) == FiberErrorPermission, "contextless entry cannot run a fiber"); + Require(sceFiberSwitch(self, 0, nullptr) == FiberErrorState, "contextless entry cannot switch to itself"); + Require(sceFiberFinalize(self) == FiberErrorState, "contextless entry cannot finalize itself"); + volatile std::uint64_t marker = 0; + const auto address = reinterpret_cast(&marker); + Require(address < g_callerStack && g_callerStack - address < 32768, "contextless execution uses calling thread stack"); + _mm_setcsr(_mm_getcsr() ^ 0x8000u); +#ifdef _WIN32 + const auto* tib = reinterpret_cast(NtCurrentTeb()); + Require(tib->StackBase == g_callerBase && tib->StackLimit == g_callerLimit, "contextless execution restores thread stack bounds"); +#endif + if (run == 2) { + sceFiberSwitch(Contextless(1), 1, nullptr); + } else if (run == 3) { + _sceFiberAttachContextAndSwitch(g_transition, g_transitionContext.data(), g_transitionContext.size(), 50, nullptr); + } else if (run == 4) { + sceFiberSwitch(g_transition, 70, nullptr); + } else if (run == 5) { + if (++g_contextlessHops < 1024) sceFiberSwitch(Contextless(1 - index), 5, nullptr); + sceFiberReturnToThread(999, nullptr); + } else { + Require(run == 1, "contextless run argument"); + sceFiberReturnToThread(200 + index, nullptr); + } + Require(false, "contextless fiber must restart rather than resume after yielding"); +} + +void APS5_VABI TransitionEntry(std::uint64_t, std::uint64_t run) { + Require(run == 50, "contextless source attaches target"); + Require(sceFiberReturnToThread(777, &run) == 0 && run == 55, "attached target resumes after contextless source"); + Require(_sceFiberAttachContextAndSwitch(Contextless(1), nullptr, 0, 1, &run) == 0 && run == 60, "attached source resumes after contextless target"); + Require(sceFiberSwitch(Contextless(0), 4, &run) == 0 && run == 70, "contextless target switches back to suspended attached source"); + for (;;) sceFiberReturnToThread(778, nullptr); +} + +[[gnu::noinline]] std::int32_t RunContextless(FiberObject* fiber, std::uint64_t run, std::uint64_t* returned, bool attach = false) { + register std::uint64_t savedRbx asm("rbx") = 0x123456789abcdef0ull; + register std::uint64_t savedR12 asm("r12") = 0x23456789abcdef01ull; + register std::uint64_t savedR13 asm("r13") = 0x3456789abcdef012ull; + register std::uint64_t savedR14 asm("r14") = 0x456789abcdef0123ull; + register std::uint64_t savedR15 asm("r15") = 0x56789abcdef01234ull; + asm volatile("" : "+r"(savedRbx), "+r"(savedR12), "+r"(savedR13), "+r"(savedR14), "+r"(savedR15)); + g_contextlessFramePointer = reinterpret_cast(__builtin_frame_address(0)); + const auto result = attach ? _sceFiberAttachContextAndRun(fiber, nullptr, 0, run, returned) : sceFiberRun_nid_postfix(fiber, run, returned); + asm volatile("" : "+r"(savedRbx), "+r"(savedR12), "+r"(savedR13), "+r"(savedR14), "+r"(savedR15) : : "memory"); + Require(savedRbx == 0x123456789abcdef0ull && savedR12 == 0x23456789abcdef01ull && savedR13 == 0x3456789abcdef012ull && savedR14 == 0x456789abcdef0123ull && savedR15 == 0x56789abcdef01234ull, "contextless return restores caller nonvolatile registers"); + return result; +} + +void RunContextlessTransitions() { + volatile std::uint64_t marker = 0; + g_callerStack = reinterpret_cast(&marker); +#ifdef _WIN32 + const auto* tib = reinterpret_cast(NtCurrentTeb()); + g_callerBase = tib->StackBase; + g_callerLimit = tib->StackLimit; +#endif + for (unsigned index = 0; index < 2; ++index) { + Require(_sceFiberInitializeWithInternalOptionImpl(Contextless(index), "contextless", ContextlessEntry, index, nullptr, 0, nullptr, 0x10, 0) == 0, "initialize contextless transition fiber with size checking"); + } + Require(_sceFiberInitializeImpl_nid_postfix(g_transition, "transition", TransitionEntry, 0, nullptr, 0, nullptr, 0) == 0, "initialize transition target"); + std::uint64_t returned = 0; + const auto csr = _mm_getcsr(); + Require(RunContextless(Contextless(0), 1, &returned) == 0 && returned == 200, "first contextless run"); + Require(_mm_getcsr() == csr, "contextless return restores thread MXCSR"); + Require(RunContextless(Contextless(0), 1, &returned) == 0 && returned == 200 && g_contextlessEntries[0] == 2, "contextless return discards continuation"); + Require(RunContextless(Contextless(0), 2, &returned, true) == 0 && returned == 201, "contextless to contextless switch"); + Require(RunContextless(Contextless(1), 1, &returned) == 0 && returned == 201 && g_contextlessEntries[1] == 2, "contextless switch target restarts"); + Require(RunContextless(Contextless(0), 3, &returned) == 0 && returned == 777, "contextless to attached switch"); + Require(RunContextless(g_transition, 55, &returned) == 0 && returned == 201, "attached to contextless switch"); + Require(RunContextless(g_transition, 60, &returned) == 0 && returned == 778, "switch through contextless entry back to attached fiber"); + Require(_sceFiberAttachContextAndRun(Contextless(0), g_firstContext.data(), g_firstContext.size(), 90, &returned) == 0 && returned == 91, "attach after abandoning contextless stack"); + Require(sceFiberRun_nid_postfix(Contextless(0), 100, &returned) == 0 && returned == 101 && g_contextlessEntries[0] == 6, "attached continuation persists after contextless runs"); + std::thread([&] { + volatile std::uint64_t workerMarker = 0; + g_callerStack = reinterpret_cast(&workerMarker); +#ifdef _WIN32 + const auto* workerTib = reinterpret_cast(NtCurrentTeb()); + g_callerBase = workerTib->StackBase; + g_callerLimit = workerTib->StackLimit; +#endif + Require(RunContextless(Contextless(1), 1, &returned) == 0 && returned == 201 && g_contextlessEntries[1] == 4, "contextless fiber restarts on another thread's stack"); + }).join(); + Require(sceFiberFinalize(Contextless(0)) == 0 && sceFiberFinalize(Contextless(1)) == 0 && sceFiberFinalize(g_transition) == 0, "finalize contextless transition fibers"); + g_callerStack = reinterpret_cast(&marker); +#ifdef _WIN32 + g_callerBase = tib->StackBase; + g_callerLimit = tib->StackLimit; +#endif + for (unsigned index = 0; index < 2; ++index) { + Require(_sceFiberInitializeImpl_nid_postfix(Contextless(index), "restart loop", ContextlessEntry, index, nullptr, 0, nullptr, 0) == 0, "initialize contextless restart loop"); + } + Require(RunContextless(Contextless(0), 5, &returned) == 0 && returned == 999 && g_contextlessHops == 1024, "contextless switch loop reuses the caller stack"); + Require(RunContextless(Contextless(1), 1, nullptr, true) == 0, "contextless run permits a null return output"); + Require(sceFiberFinalize(Contextless(0)) == 0 && sceFiberFinalize(Contextless(1)) == 0, "finalize contextless restart loop"); +} + +std::uint16_t ReadFpuControl() { + std::uint16_t control = 0; + asm volatile("fnstcw %0" : "=m"(control)); + return control; +} + +void WriteFpuControl(std::uint16_t control) { + asm volatile("fldcw %0" : : "m"(control)); +} + +constexpr std::uint64_t ContextlessInitializeArgument = 0xfedcba9876543210ull; +constexpr std::uint64_t ContextlessRunArgument = 0x8123456789abcdefull; +constexpr std::uint32_t ContextlessCsr = 0x7f80; +constexpr std::uint16_t ContextlessControl = 0x0b7f; +unsigned g_contextlessOptionEntries = 0; + +void APS5_VABI ContextlessOptionsEntry(std::uint64_t initialized, std::uint64_t run) { + Require(initialized == ContextlessInitializeArgument && run == ContextlessRunArgument, "contextless entry keeps 64-bit arguments"); + ++g_contextlessOptionEntries; + const auto result = (static_cast(ReadFpuControl()) << 32) | _mm_getcsr(); + _mm_setcsr(0x1f80); + WriteFpuControl(0x037f); + sceFiberReturnToThread(result, nullptr); + Require(false, "contextless options entry cannot resume after returning"); +} + +void APS5_VABI ContextlessFpuSource(std::uint64_t, std::uint64_t target) { + _mm_setcsr(ContextlessCsr); + WriteFpuControl(ContextlessControl); + sceFiberSwitch(reinterpret_cast(target), ContextlessRunArgument, nullptr); + Require(false, "contextless FPU source is not resumed"); +} + +void RunContextlessOptions() { + FiberStorage storage{}; + FiberStorage sourceStorage{}; + auto* fiber = reinterpret_cast(&storage); + auto* source = reinterpret_cast(&sourceStorage); + const auto callerCsr = _mm_getcsr(); + const auto callerControl = ReadFpuControl(); + for (const auto flags : {0u, 0x100u}) { + Require(_sceFiberInitializeWithInternalOptionImpl(fiber, "contextless options", ContextlessOptionsEntry, ContextlessInitializeArgument, nullptr, 0, nullptr, flags, 0) == 0, "initialize contextless FPU options"); + _mm_setcsr(ContextlessCsr); + WriteFpuControl(ContextlessControl); + const auto expected = flags ? (0x037full << 32) | 0x9fc0u : (static_cast(ContextlessControl) << 32) | ContextlessCsr; + std::uint64_t returned = 0; + for (unsigned restart = 0; restart < 2; ++restart) { + Require(sceFiberRun_nid_postfix(fiber, ContextlessRunArgument, &returned) == 0 && returned == expected, "contextless FPU policy applies on each run"); + Require(_mm_getcsr() == ContextlessCsr && ReadFpuControl() == ContextlessControl, "contextless return restores caller FPU registers"); + } + _mm_setcsr(callerCsr); + WriteFpuControl(callerControl); + Require(_sceFiberInitializeImpl_nid_postfix(source, "FPU source", ContextlessFpuSource, 0, g_transitionContext.data(), g_transitionContext.size(), nullptr, 0) == 0, "initialize attached FPU source"); + Require(sceFiberRun_nid_postfix(source, reinterpret_cast(fiber), &returned) == 0 && returned == expected, "contextless switch inherits or resets the source FPU registers"); + Require(_mm_getcsr() == callerCsr && ReadFpuControl() == callerControl, "contextless switch restores the original caller FPU registers"); + Require(sceFiberFinalize(fiber) == 0 && sceFiberFinalize(source) == 0, "finalize contextless FPU options"); + } + Require(g_contextlessOptionEntries == 6, "contextless FPU entry restarts on runs and switches"); +} + [[gnu::noinline]] std::int32_t RunFirst(std::uint64_t* returned) { g_threadFramePointer = reinterpret_cast(__builtin_frame_address(0)); const std::int32_t result = sceFiberRun_nid_postfix(g_first, 100, returned); @@ -244,7 +509,13 @@ void RunOnLargeContext() { } -int main() { +int main(int argc, char** argv) { + if (argc == 2 && std::string_view(argv[1]) == "--contextless") { + RunContextlessTransitions(); + RunContextlessOptions(); + return 0; + } + Require(argc == 1, "known test arguments"); RunSmallContextFirst(); FiberObject* self = nullptr; Require(sceFiberGetSelf(&self) == FiberErrorPermission, "no fiber on the thread"); @@ -270,6 +541,7 @@ int main() { Require(sceFiberStartContextSizeCheck(1) == FiberErrorInvalid, "start with flags"); Require(_sceFiberInitializeImpl_nid_postfix(checked, "unchecked", CheckedEntry, 0, g_checkedContext.data(), g_checkedContext.size(), nullptr, 0) == 0, "initialize unchecked"); Require(sceFiberGetInfo(checked, &info) == 0 && info.size_context_margin == ~0ull, "no margin without size check"); + Require(info.entry == CheckedEntry, "info preserves the guest entry callback type"); Require(sceFiberFinalize(checked) == 0, "finalize unchecked"); Require(sceFiberStartContextSizeCheck(0) == 0, "start size check"); Require(sceFiberStartContextSizeCheck(0) == FiberErrorState, "start twice"); @@ -284,4 +556,6 @@ int main() { RunHostCallOnSmallContext(); RunFrameChain(); RunOnLargeContext(); + RunAttachedContext(); + RunInternalOptions(); } diff --git a/core/libs/tests/FilePosition.cpp b/core/libs/tests/FilePosition.cpp index 0337c58932..9bd695cc15 100644 --- a/core/libs/tests/FilePosition.cpp +++ b/core/libs/tests/FilePosition.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -12,12 +13,6 @@ #include #endif -extern "C" { -int APS5_VABI fgetpos_nid_postfix(FileStream*, std::int64_t*); -int APS5_VABI fsetpos_nid_postfix(FileStream*, const std::int64_t*); -std::int64_t APS5_VABI ftello_nid_postfix(FileStream*); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "File position check failed at line %d\n", line); diff --git a/core/libs/tests/FileStreams.cpp b/core/libs/tests/FileStreams.cpp index f53419748c..fc8af8e721 100644 --- a/core/libs/tests/FileStreams.cpp +++ b/core/libs/tests/FileStreams.cpp @@ -1,8 +1,11 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include +#include +#include #include #include #include @@ -21,29 +24,6 @@ #include #endif -extern "C" { -FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode); -FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const char*, int, int); -FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, FileStream* stream); -int APS5_VABI fclose_nid_postfix(FileStream* stream); -std::size_t APS5_VABI fread_nid_postfix(void* buffer, std::size_t size, std::size_t count, FileStream* stream); -std::size_t APS5_VABI fwrite_nid_postfix(const void* buffer, std::size_t size, std::size_t count, FileStream* stream); -int APS5_VABI fseek_nid_postfix(FileStream* stream, std::int64_t offset, int origin); -std::int64_t APS5_VABI ftell_nid_postfix(FileStream* stream); -int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream); -int APS5_VABI fflush_nid_postfix(FileStream* stream); -int APS5_VABI remove_nid_postfix(const char* path); -int APS5_VABI rename_nid_postfix(const char* from, const char* to); -int APS5_VABI ferror_nid_postfix(FileStream* stream); -int APS5_VABI feof_nid_postfix(FileStream* stream); -void APS5_VABI clearerr_nid_postfix(FileStream* stream); -int APS5_VABI fgetc_nid_postfix(FileStream* stream); -int APS5_VABI getc_unlocked_nid_postfix(FileStream* stream); -char* APS5_VABI fgets_nid_postfix(char* buffer, int size, FileStream* stream); -int APS5_VABI ungetc_nid_postfix(int value, FileStream* stream); -int APS5_VABI fscanf_nid_postfix(FileStream* stream, const char* format, ...); -} - static void Require(bool condition, const char* message = "File stream check failed") { if (!condition) throw std::runtime_error(message); } @@ -123,6 +103,23 @@ static void CheckExclusiveCppOpen(const std::string& base) { } } +static int APS5_VABI ScanList(FileStream* stream, const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + const int result = vfscanf_nid_postfix(stream, format, reinterpret_cast(args)); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} + static std::string ReadWhole(const std::string& path) { auto* stream = fopen_nid_postfix(path.c_str(), "rb"); Require(stream != nullptr); @@ -179,6 +176,11 @@ static void CheckUpdateStreamReadsAfterWrites(const std::string& base) { Require(fscanf_nid_postfix(stream, "%3s", buffer.data()) == 1, "fscanf after fwrite on an r+ stream"); Require(std::strcmp(buffer.data(), "234") == 0, "fscanf after fwrite on an r+ stream read other bytes"); }); + CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) { + std::array buffer{}; + Require(ScanList(stream, "%3s", buffer.data()) == 1, "vfscanf after fwrite on an r+ stream"); + Require(std::strcmp(buffer.data(), "234") == 0, "vfscanf after fwrite on an r+ stream read other bytes"); + }); CheckReadAfterWrite(path, "w+b", "AB", [](FileStream* stream) { std::array buffer{}; Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == 0 && feof_nid_postfix(stream) != 0, "fread after fwrite on a w+ stream"); @@ -222,6 +224,11 @@ int main(int argc, char** argv) { ExpectException([&] { fputs_nid_postfix(nullptr, stream); }); ExpectException([&] { fwrite_nid_postfix(nullptr, 1, sizeof(payload), stream); }); errno = 0; + Require(fread_nid_postfix(buffer.data(), 2, SIZE_MAX / 2 + 1, stream) == 0 && errno == EINVAL && ferror_nid_postfix(stream) == 0); + errno = 0; + Require(fread_nid_postfix(buffer.data(), 2, SIZE_MAX / 2 + 1, nullptr) == 0 && errno == EINVAL); + ExpectException([&] { fwrite_nid_postfix(buffer.data(), SIZE_MAX / 2 + 1, 2, stream); }); + errno = 0; Require(fseek_nid_postfix(stream, 0, -1) == -1); Require(errno == EINVAL); Require(fclose_nid_postfix(stream) == 0); @@ -234,18 +241,11 @@ int main(int argc, char** argv) { errno = 0; Require(fopen_nid_postfix(argv[1], "rb") == nullptr); Require(errno == ENOENT); -#ifdef _WIN32 - const std::string directoryMode = "rb"; - constexpr int directoryError = EACCES; -#else - const std::string directoryMode = "wb"; - constexpr int directoryError = EISDIR; -#endif const std::string directoryName = std::string(argv[1]) + ".dir"; Require(std::filesystem::create_directory(directoryName)); errno = 0; - Require(fopen_nid_postfix(directoryName.c_str(), directoryMode.c_str()) == nullptr); - Require(errno == directoryError); + Require(fopen_nid_postfix(directoryName.c_str(), "wb") == nullptr); + Require(errno == EISDIR); Require(std::filesystem::remove(directoryName)); const std::u8string unicodeName = u8"セーブé.tmp"; const std::string guestName(unicodeName.begin(), unicodeName.end()); diff --git a/core/libs/tests/FontFixture.hpp b/core/libs/tests/FontFixture.hpp index 601512cd98..e6e7c11e2b 100644 --- a/core/libs/tests/FontFixture.hpp +++ b/core/libs/tests/FontFixture.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace FontFixture { @@ -20,6 +21,31 @@ inline std::vector Words(std::initializer_list values) { return out; } +inline std::vector BitmapFont() { + const std::string_view font = + "STARTFONT 2.1\n" + "FONT -anyps5-fixture-medium-r-normal--1-10-75-75-c-10-iso10646-1\n" + "SIZE 1 75 75\n" + "FONTBOUNDINGBOX 1 1 0 0\n" + "STARTPROPERTIES 4\n" + "FONT_ASCENT 1\n" + "FONT_DESCENT 0\n" + "CHARSET_REGISTRY \"ISO10646\"\n" + "CHARSET_ENCODING \"1\"\n" + "ENDPROPERTIES\n" + "CHARS 1\n" + "STARTCHAR A\n" + "ENCODING 65\n" + "SWIDTH 1000 0\n" + "DWIDTH 1 0\n" + "BBX 1 1 0 0\n" + "BITMAP\n" + "80\n" + "ENDCHAR\n" + "ENDFONT\n"; + return {font.begin(), font.end()}; +} + inline std::vector BuildFont(int glyphCount, std::map> tables) { tables["glyf"] = Words({0, 0}); tables["head"] = Words({1, 0, 1, 0, 0, 0, 0x5F0F, 0x3CF5, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0}); diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index f22d7880dd..ad74c98f20 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -1,8 +1,10 @@ +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/GuestHeap.hpp" #include #include +#include #include #include #include @@ -10,23 +12,13 @@ #include #include #include +#include -extern "C" { -int APS5_VABI snprintf_nid_postfix(char*, size_t, const char*, ...); -int APS5_VABI sprintf_nid_postfix(char*, const char*, ...); -int APS5_VABI printf_nid_postfix(const char*, ...); -int APS5_VABI libc_printf_nid_postfix(const char*, ...); -int APS5_VABI sscanf_nid_postfix(const char*, const char*, ...); -int APS5_VABI vsnprintf_nid_postfix(char*, size_t, const char*, VaList*); -int APS5_VABI vsnprintf_s_nid_postfix(char*, size_t, const char*, VaList*); -int APS5_VABI snprintf_s_nid_postfix(char*, size_t, const char*, ...); -int APS5_VABI vsscanf_s_nid_postfix(const char*, const char*, VaList*); -int APS5_VABI vsscanf_nid_postfix(const char*, const char*, VaList*); -int APS5_VABI vprintf_nid_postfix(const char*, VaList*); -int APS5_VABI asprintf_nid_postfix(char**, const char*, ...); -void APS5_VABI free_nid_postfix(void*); -int* APS5_VABI __error_nid_postfix(); -} +#if defined(__clang__) && !defined(_WIN32) +#define __builtin_sysv_va_list va_list +#define __builtin_sysv_va_start va_start +#define __builtin_sysv_va_end va_end +#endif static void Require(bool condition) { if (!condition) throw std::runtime_error("Formatting check failed"); @@ -84,11 +76,39 @@ static int APS5_VABI PrintList(const char* format, ...) { return result; } +static void CheckSizeLimits() { + using Formatter = int (APS5_VABI*)(char*, size_t, const char*, ...); + for (const Formatter format : {&snprintf_nid_postfix, &FormatList}) { + char output[8]; + for (const std::size_t oversized : {static_cast(INT_MAX) + 2, static_cast(SIZE_MAX)}) { + for (const bool wide : {false, true}) { + std::memset(output, 'Z', sizeof(output)); + int consumed = -1; + *__error_nid_postfix() = 0; + const int result = wide ? format(output, oversized, "%ls%n", u"x", &consumed) + : format(output, oversized, "x%n", &consumed); + Require(result == 1 && *__error_nid_postfix() == 0 && std::strcmp(output, "x") == 0 && consumed == 1); + Require(std::all_of(output + 2, output + sizeof(output), [](char value) { return value == 'Z'; })); + } + } + for (const std::size_t capacity : {std::size_t{0}, std::size_t{1}, static_cast(INT_MAX), static_cast(INT_MAX) + 1}) { + std::memset(output, 'Z', sizeof(output)); + *__error_nid_postfix() = 35; + Require(format(output, capacity, "x") == 1 && *__error_nid_postfix() == 35); + Require(output[0] == (capacity == 0 ? 'Z' : capacity == 1 ? '\0' : 'x')); + const std::size_t written = capacity < 2 ? capacity : 2; + if (capacity > 1) Require(output[1] == 0); + Require(std::all_of(output + written, output + sizeof(output), [](char value) { return value == 'Z'; })); + } + Require(format(nullptr, 0, "x") == 1); + } +} + static bool CheckWidePrecision() { const char16_t input[] = u"A\u00e9\u20ac\U0001f600Z"; const char* expected[] = {"", "A", "A", "A\xc3\xa9", "A\xc3\xa9", "A\xc3\xa9", "A\xc3\xa9\xe2\x82\xac", - "A\xc3\xa9\xe2\x82\xac", "A\xc3\xa9\xe2\x82\xac", "A\xc3\xa9\xe2\x82\xac", - "A\xc3\xa9\xe2\x82\xac\xf0\x9f\x98\x80", "A\xc3\xa9\xe2\x82\xac\xf0\x9f\x98\x80Z"}; + "A\xc3\xa9\xe2\x82\xac", "A\xc3\xa9\xe2\x82\xac", "A\xc3\xa9\xe2\x82\xac\xed\xa0\xbd", + "A\xc3\xa9\xe2\x82\xac\xed\xa0\xbd", "A\xc3\xa9\xe2\x82\xac\xed\xa0\xbd"}; bool correct = true; for (int precision = 0; precision < 12; ++precision) { char output[32]; @@ -103,7 +123,8 @@ static bool CheckWidePrecision() { char output[32]; Require(snprintf_nid_postfix(output, sizeof(output), "[%5.1ls]", u"\u00e9") == 7 && std::strcmp(output, "[ ]") == 0); Require(snprintf_nid_postfix(output, sizeof(output), "[%-5.2ls]", u"\u00e9") == 7 && std::strcmp(output, "[\xc3\xa9 ]") == 0); - Require(FormatList(output, sizeof(output), "%.3ls", u"\U0001f600") == 0 && output[0] == 0); + Require(FormatList(output, sizeof(output), "%.3ls", u"\U0001f600") == 3 && std::strcmp(output, "\xed\xa0\xbd") == 0); + Require(FormatList(output, sizeof(output), "%ls", u"\U0001f600") == 6 && std::strcmp(output, "\xed\xa0\xbd\xed\xb8\x80") == 0); Require(FormatList(output, sizeof(output), "%ls[% u][% 4u][%+ lu][%- 3hhu][% x][%+o]", u"", 5u, 5u, 5ULL, 5u, 255u, 8u) == 25 && std::strcmp(output, "[5][ 5][5][5 ][ff][10]") == 0); Require(FormatList(output, sizeof(output), "%.*ls:%d", -1, u"\u00e9", 7) == 4 && std::strcmp(output, "\xc3\xa9:7") == 0); @@ -116,10 +137,11 @@ static bool CheckWidePrecision() { Require(snprintf_nid_postfix(output, sizeof(output), "[%ls]", static_cast(nullptr)) == 8 && std::strcmp(output, "[(null)]") == 0); Require(snprintf_nid_postfix(output, sizeof(output), "a%lcb%C", 0, 0) == 4 && std::memcmp(output, "a\0b\0", 5) == 0); Require(FormatList(output, sizeof(output), "%%ls:%2$d:%1$d", 3, 7) == 7 && std::strcmp(output, "%ls:7:3") == 0); - Require(FormatList(output, sizeof(output), "%ls[%f][%+f][% F][%5.1e]", u"", -NAN, -NAN, NAN, -NAN) == 22 - && std::strcmp(output, "[nan][nan][NAN][ nan]") == 0); - Require(FormatList(output, sizeof(output), "%ls[%-6g][%06a][%LG][%+f]", u"", NAN, -NAN, -static_cast(NAN), -INFINITY) == 27 - && std::strcmp(output, "[nan ][ nan][NAN][-inf]") == 0); + Require(FormatList(output, sizeof(output), "%ls[%f][%+f][% F][%5.1e]", u"", -NAN, -NAN, NAN, -NAN) == 25 + && std::strcmp(output, "[-nan][-nan][ NAN][ -nan]") == 0); + Require(FormatList(output, sizeof(output), "%ls[%-6g][%06a][%LG][%+f]", u"", NAN, -NAN, -static_cast(NAN), -INFINITY) == 28 + && std::strcmp(output, "[nan ][ -nan][-NAN][-inf]") == 0); + Require(FormatList(output, sizeof(output), "%ls[%+f][%8.3f|]", u"", NAN, -NAN) == 17 && std::strcmp(output, "[+nan][ -nan|]") == 0); for (std::size_t capacity = 0; capacity <= 6; ++capacity) { std::memset(output, '!', sizeof(output)); int count = -1; @@ -131,6 +153,53 @@ static bool CheckWidePrecision() { return correct; } +static void CheckIntegerScanWidths() { + int value = 7; + *__error_nid_postfix() = 0; + Require(sscanf_nid_postfix("2147483648", "%d", &value) == 1 && value == -2147483647 - 1 && *__error_nid_postfix() == 0); + Require(sscanf_nid_postfix("0xFFFFFFFF", "%i", &value) == 1 && value == -1 && *__error_nid_postfix() == 0); + Require(sscanf_nid_postfix("99999999999999999999", "%d", &value) == 1 && value == -1 && *__error_nid_postfix() == 34); + unsigned unsignedValue = 7; + Require(sscanf_nid_postfix("4294967296", "%u", &unsignedValue) == 1 && unsignedValue == 0); + Require(sscanf_nid_postfix("100000001", "%x", &unsignedValue) == 1 && unsignedValue == 1); + Require(sscanf_nid_postfix("40000000001", "%o", &unsignedValue) == 1 && unsignedValue == 1); + short shortValue = 7; + Require(sscanf_nid_postfix("5000000000", "%hd", &shortValue) == 1 && shortValue == -3584); + signed char charValue = 7; + Require(sscanf_nid_postfix("300", "%hhd", &charValue) == 1 && charValue == 44); + Require(sscanf_nid_postfix("4294967295", "%u", &unsignedValue) == 1 && unsignedValue == 4294967295u); + unsigned short unsignedShort = 7; + Require(sscanf_nid_postfix("40000", "%hu", &unsignedShort) == 1 && unsignedShort == 40000); + Require(sscanf_nid_postfix("70000", "%hu", &unsignedShort) == 1 && unsignedShort == 4464); + Require(sscanf_nid_postfix("ffff", "%hx", &unsignedShort) == 1 && unsignedShort == 0xffff); + unsigned char unsignedChar = 7; + Require(sscanf_nid_postfix("200", "%hhu", &unsignedChar) == 1 && unsignedChar == 200); + Require(sscanf_nid_postfix("300", "%hhu", &unsignedChar) == 1 && unsignedChar == 44); + Require(sscanf_nid_postfix("377", "%hho", &unsignedChar) == 1 && unsignedChar == 255); + *__error_nid_postfix() = 0; + Require(sscanf_nid_postfix("-2147483649", "%d", &value) == 1 && value == 0x7fffffff && *__error_nid_postfix() == 0); + Require(sscanf_nid_postfix("-1", "%u", &unsignedValue) == 1 && unsignedValue == 0xffffffffu && *__error_nid_postfix() == 0); + Require(sscanf_nid_postfix("99999999999", "%x", &unsignedValue) == 1 && unsignedValue == 0x99999999u && *__error_nid_postfix() == 0); + Require(sscanf_nid_postfix("70000", "%hd", &shortValue) == 1 && shortValue == 0x1170 && *__error_nid_postfix() == 0); + long long quad = 0x5555555555555555ll; + Require(sscanf_nid_postfix("9223372036854775808", "%lld", &quad) == 1 && quad == 0x7fffffffffffffffll && *__error_nid_postfix() == 34); + unsigned long long unsignedQuad = 0x5555555555555555ull; + *__error_nid_postfix() = 0; + Require(sscanf_nid_postfix("18446744073709551616", "%llu", &unsignedQuad) == 1 && unsignedQuad == 0xffffffffffffffffull && *__error_nid_postfix() == 34); +#ifdef _WIN32 + quad = 0x5555555555555555ll; + *__error_nid_postfix() = 0; + Require(sscanf_nid_postfix("5", "%qd", &quad) == 0 && quad == 0x5555555555555555ll && *__error_nid_postfix() == 0); +#endif + int first = 7; + int second = 7; + int position = 0; + Require(sscanf_nid_postfix("12 34", "%d %d%n", &first, &second, &position) == 2 && first == 12 && second == 34 && position == 5); + value = 7; + Require(sscanf_nid_postfix("x", "%d", &value) == 0 && value == 7); + Require(sscanf_nid_postfix("1 x", "%d %d", &first, &value) == 1 && first == 1 && value == 7); +} + static void CheckSecureFormat() { constexpr size_t rsizeMax = SIZE_MAX >> 1; char buffer[16]; @@ -171,19 +240,24 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(std::strcmp(buffer, "Mount requested: 0") == 0); for (int index = 0; index < 10000; ++index) { snprintf_nid_postfix(buffer, sizeof(buffer), "%d %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7, 8); + Require(std::strcmp(buffer, "1 2 3 4 5 6 7 8") == 0); FormatList(buffer, sizeof(buffer), "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %d", 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11); Require(std::strcmp(buffer, "1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11") == 0); snprintf_nid_postfix(buffer, sizeof(buffer), "%ld %lu %lld %zu %td %jd", -4294967297LL, 4294967297ULL, -5LL, 7ULL, -8LL, 9LL); + Require(std::strcmp(buffer, "-4294967297 4294967297 -5 7 -8 9") == 0); snprintf_nid_postfix(buffer, sizeof(buffer), "%s:%*.*f:%d", "test", -8, 2, 1.25, 7); + Require(std::strcmp(buffer, "test:1.25 :7") == 0); FormatList(buffer, sizeof(buffer), "%d %d %d %d %d %d %d %.3Lf %.1f", 1, 2, 3, 4, 5, 6, 7, 1.125L, 2.5); Require(std::strcmp(buffer, "1 2 3 4 5 6 7 1.125 2.5") == 0); } snprintf_nid_postfix(buffer, sizeof(buffer), "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f", 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.); + Require(std::strcmp(buffer, "1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0") == 0); snprintf_nid_postfix(buffer, sizeof(buffer), "%.3Lf %d %.1f", 1.125L, 7, 2.5); + Require(std::strcmp(buffer, "1.125 7 2.5") == 0); Require(snprintf_nid_postfix(buffer, sizeof(buffer), "%'d|%'u|%'.2f|%'-8ld|%'+05d|%'g", 1234567, 4000000000u, 1234567.891, 1234567LL, 42, 1e6) == 50); Require(std::strcmp(buffer, "1234567|4000000000|1234567.89|1234567 |+0042|1e+06") == 0); @@ -202,20 +276,12 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(FormatList(buffer, sizeof(buffer), "%2$ls|%1$C|%3$d", 0x41, u"\u00e9", 7) == 6 && std::strcmp(buffer, "\xc3\xa9|A|7") == 0); Require(FormatList(buffer, sizeof(buffer), "%2$d %d %ls|%*5$d|%1$d", 1, 2, 3, u"x", 4) == 12 && std::strcmp(buffer, "2 3 x| 4|1") == 0); Require(FormatList(buffer, sizeof(buffer), "[%*3$d]%ls", 5, u"", 3) == 5 && std::strcmp(buffer, "[ 5]") == 0); - const char* const invalidPositions[] = {"%0$d%ls", "%100$d%ls", "%*0$d%ls"}; - for (const char* format : invalidPositions) { - bool threw = false; - try { - FormatList(buffer, sizeof(buffer), format, 1, u""); - } catch (const std::invalid_argument&) { - threw = true; - } - Require(threw); - } + Require(FormatList(buffer, sizeof(buffer), "%100$d %2$d%1$ls", u"", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100) == 4 && std::strcmp(buffer, "99 1") == 0); char expected[1024]; std::snprintf(expected, sizeof(expected), "%#08x %.3e %a %g %llx", 42u, 1.25, 1.25, 1.25, static_cast(reinterpret_cast(buffer))); snprintf_nid_postfix(buffer, sizeof(buffer), "%#08x %.3e %a %g %p", 42u, 1.25, 1.25, 1.25, static_cast(buffer)); + Require(std::strcmp(buffer, expected) == 0); char pointers[128]; const auto address = [](std::uintptr_t value) { return reinterpret_cast(value); }; @@ -236,6 +302,7 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(snprintf_nid_postfix(pointers, sizeof(pointers), "%p", address(0xffffffffffffffffull)) == 16 && std::strcmp(pointers, "ffffffffffffffff") == 0); snprintf_nid_postfix(buffer, sizeof(buffer), "%.*s", -1, "unlimited"); + Require(std::strcmp(buffer, "unlimited") == 0); buffer[5] = '!'; Require(snprintf_nid_postfix(buffer, 5, "%s", "abcdef") == 6); @@ -328,6 +395,21 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { } char narrow[8] = {}; Require(sscanf_nid_postfix("ab%", "%7[^%ls]", narrow) == 1 && std::strcmp(narrow, "ab") == 0); +#endif +#ifdef _WIN32 + char freebsd[64]; + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%qd|%qu|%qx|%qo") == 15 && + std::strcmp(freebsd, "%qd|%qu|%qx|%qo") == 0); + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%qd|%d", 7) == 5 && std::strcmp(freebsd, "%qd|7") == 0); + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%D|%hD|%O|%U|%jD") == 14 && + std::strcmp(freebsd, "%D|%D|%O|%U|%D") == 0); + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "[%5%][%-5%][%y][%5y][%-3y][%hhz]") == 34 && + std::strcmp(freebsd, "[ %][% ][%y][ %y][%y ][%z]") == 0); + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "[%#y][%.2y][%-#4.1y]") == 14 && + std::strcmp(freebsd, "[%y][%y][%y ]") == 0); + bool incomplete = false; + try { snprintf_nid_postfix(freebsd, sizeof(freebsd), "100%"); } catch (const std::invalid_argument&) { incomplete = true; } + Require(incomplete); #endif long long count = -1; int smallCount = -1; @@ -336,11 +418,12 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(snprintf_nid_postfix(buffer, sizeof(buffer), "a%cb", 0) == 3); Require(buffer[0] == 'a' && buffer[1] == 0 && buffer[2] == 'b' && buffer[3] == 0); *__error_nid_postfix() = 35; - Require(snprintf_nid_postfix(buffer, sizeof(buffer), "[%m][%.8m][%-10.5m][%d]", 7) == 59); - Require(std::strcmp(buffer, "[Resource temporarily unavailable][Resource][Resou ][7]") == 0); + Require(snprintf_nid_postfix(buffer, sizeof(buffer), "[%m][%.8m][%-10.5m][%d]", 7) == 23); + Require(std::strcmp(buffer, "[%m][%m][%m ][7]") == 0); Require(*__error_nid_postfix() == 35); - *__error_nid_postfix() = 63; - Require(FormatList(buffer, sizeof(buffer), "%s: %m", "open") == 24 && std::strcmp(buffer, "open: File name too long") == 0); + Require(FormatList(buffer, sizeof(buffer), "%s: %m", "open") == 8 && std::strcmp(buffer, "open: %m") == 0); + Require(snprintf_nid_postfix(buffer, sizeof(buffer), "[%5k][%-3y][%lk][%#.2k][%y%d][%0$d]", 42) == 31); + Require(std::strcmp(buffer, "[ %k][%y ][%k][%k][%y42][%$d]") == 0); Require(printf_nid_postfix("printf: %d %.1f\n", 7, 2.5) == 14); Require(libc_printf_nid_postfix("libc_printf: %s\n", "OK") == 16); Require(libc_printf_nid_postfix("%d %d %d %d %d %d %d\n", 1, 22, 333, 4444, 55555, 666666, 7777777) == 35); @@ -352,6 +435,7 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(ScanListS("123 test", "%d %s", &scanNum, scanWord, 8u) == 2 && scanNum == 123 && std::strcmp(scanWord, "test") == 0); char tooSmall[4] = {'x', 'x', 'x', 'x'}; Require(ScanListS("abcdef", "%s", tooSmall, 4u) == 0 && tooSmall[0] == '\0'); + CheckIntegerScanWidths(); std::puts("Formatting checks passed: 10000 iterations"); } @@ -366,6 +450,94 @@ static void CheckAllocatingFormat() { char* allocated = nullptr; Require(asprintf_nid_postfix(&allocated, "%ls:%S:%lc:%d", u"\u00e9", u"ab", 0x20ac, 7) == 11 && std::strcmp(allocated, "\xc3\xa9:ab:\xe2\x82\xac:7") == 0); free_nid_postfix(allocated); + } -int main() { RunChecks(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; } +static void RequireFloat(const char* format, double value, const char* expected) { + char buffer[64]; + const int written = snprintf_nid_postfix(buffer, sizeof(buffer), format, value); + if (written != static_cast(std::strlen(expected)) || std::strcmp(buffer, expected) != 0) { + std::fprintf(stderr, "%s of %a gave \"%s\", expected \"%s\"\n", format, value, buffer, expected); + Require(false); + } +} + +static void CheckHexFloat() { + RequireFloat("[%-010a]", 1.5, "[0x1.8p+0 ]"); + RequireFloat("[%-0#12A]", 0.75, "[0X1.8P-1 ]"); + RequireFloat("[%0-10.2a]", 1.0, "[0x1.00p+0 ]"); + RequireFloat("[%010a]", -1.5, "[-0x01.8p+0]"); + RequireFloat("[%a]", 0x1p-1074, "[0x1p-1074]"); + RequireFloat("[%a]", 0x0.fffffffffffffp-1022, "[0x1.ffffffffffffep-1023]"); + RequireFloat("[%.3a]", 0x0.fffffffffffffp-1022, "[0x2.000p-1023]"); + RequireFloat("[%A]", 0x0.012688b70e62bp-1022, "[0X1.2688B70E62BP-1030]"); + RequireFloat("[%.0a]", 0x0.012688b70e62bp-1022, "[0x1p-1030]"); + RequireFloat("[%.1a]", 0x1.08p0, "[0x1.1p+0]"); + RequireFloat("[%.0a]", 0x1.8p0, "[0x2p+0]"); + RequireFloat("[%#.0a]", 1.0, "[0x1.p+0]"); + RequireFloat("[%.20a]", 0.1, "[0x1.999999999999a0000000p-4]"); + RequireFloat("[% a]", -0.0, "[-0x0p+0]"); + char buffer[32]; + Require(snprintf_nid_postfix(buffer, sizeof(buffer), "[%*a]", -12, 1.5) == 14 && std::strcmp(buffer, "[0x1.8p+0 ]") == 0); + Require(snprintf_nid_postfix(buffer, 6, "%a", 1.5) == 8 && std::strcmp(buffer, "0x1.8") == 0); +#ifdef _WIN32 + for (const char* format : {"%.2147483648a", "%.2147483649A", "%.9999999999a", + "%.9999999999999999999999999999999999999999A", "%.0002147483648a", "%-+20.2147483648A", + "%2147483648a", "%999999999999999999999999a", "%.2147483647a", "%.2147483641a", "%+.2147483640A", + "x%2147483647a", "xx%.2147483640a", "x%+.2147483639A"}) { + bool rejected = false; + try { + snprintf_nid_postfix(buffer, sizeof(buffer), format, 1.5); + } catch (const std::overflow_error&) { + rejected = true; + } + Require(rejected); + } +#endif +} + +static void CheckDecimalFloat() { + RequireFloat("[%.0f]", 0.5, "[1]"); + RequireFloat("[%.0f]", 2.5, "[3]"); + RequireFloat("[%.0f]", -0.5, "[-1]"); + RequireFloat("[%.1f]", 0.25, "[0.3]"); + RequireFloat("[%#.3g]", 2025.0, "[2.03e+03]"); + RequireFloat("[%.2e]", 1125.0, "[1.13e+03]"); + RequireFloat("[%.2f]", 0.015, "[0.01]"); + RequireFloat("[%.1f]", 99.95, "[100.0]"); + RequireFloat("[%.40e]", 0.1, "[1.0000000000000000555000000000000000000000e-01]"); + RequireFloat("[%.20f]", 0.1, "[0.10000000000000000555]"); + RequireFloat("[%.0f]", 0x1p100, "[1267650600228229401496700000000]"); + RequireFloat("[%.25e]", 0x1p-1074, "[4.9406564584124654417600000e-324]"); + RequireFloat("[%.23e]", 0x1.0f6b8p+1023, "[9.52987754335065669050000e+307]"); + RequireFloat("[%.17e]", -0x1.16a55a5cd7c73p+48, "[-3.06374418159559187e+14]"); + RequireFloat("[%.17g]", 0x1.e5dec6655f463p+50, "[2136879802973464.7]"); + RequireFloat("[%.0f]", 67108865.5, "[67108865]"); + RequireFloat("[%.0f]", 33554433.5, "[33554434]"); + RequireFloat("[%+08.1f]", 2.25, "[+00002.3]"); + RequireFloat("[%#.0e]", 2.5, "[3.e+00]"); + RequireFloat("[%g]", 0.0001, "[0.0001]"); + RequireFloat("[%G]", 0.00001, "[1E-05]"); + RequireFloat("[%.3g]", 999.5, "[1e+03]"); + RequireFloat("[%e]", 0.0, "[0.000000e+00]"); + RequireFloat("[%#g]", 0.0, "[0.00000]"); + RequireFloat("[%.0f]", -0.0, "[-0]"); + RequireFloat("[%-12.3E]", -1.25e-5, "[-1.250E-05 ]"); + char buffer[64]; + Require(snprintf_nid_postfix(buffer, sizeof(buffer), "[%.40Le]", 1.0L / 3) == 48 && std::strcmp(buffer, "[3.3333333333333333334000000000000000000000e-01]") == 0); + Require(snprintf_nid_postfix(buffer, sizeof(buffer), "[%.2Lf]", 0.125L) == 6 && std::strcmp(buffer, "[0.13]") == 0); + Require(snprintf_nid_postfix(buffer, sizeof(buffer), "[%.30Le]", 0x1p-16445L) == 40 && std::strcmp(buffer, "[3.645199531882474602528000000000e-4951]") == 0); + for (const char* format : {"%.2147483647e", "%+.2147483647E", "%.2147483647f", "% .2147483646F", "%#.2147483647g", + "%.2147483643e", "x%.2147483645f", "x%-2147483647e", "%.2147483647Le"}) { + bool rejected = false; + try { + if (std::strstr(format, "L")) snprintf_nid_postfix(buffer, sizeof(buffer), format, 1.5L); + else snprintf_nid_postfix(buffer, sizeof(buffer), format, 1.5); + } catch (const std::overflow_error&) { + rejected = true; + } + Require(rejected); + } +} + +int main() { CheckSizeLimits(); RunChecks(); CheckHexFloat(); CheckDecimalFloat(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; } diff --git a/core/libs/tests/FreeTypeFontFixture.hpp b/core/libs/tests/FreeTypeFontFixture.hpp index 9458d09a81..b876a19139 100644 --- a/core/libs/tests/FreeTypeFontFixture.hpp +++ b/core/libs/tests/FreeTypeFontFixture.hpp @@ -20,7 +20,8 @@ static void PutAll16(std::vector& out, std::initializer_list for (const int value : values) Put16(out, static_cast(static_cast(value))); } -static std::vector SquareGlyphFont(std::uint32_t codepoint, int width, bool symbolMap = false) { +static std::vector SquareGlyphFont(std::uint32_t codepoint, int width, bool symbolMap = false, + const std::vector* gasp = nullptr) { std::vector head; Put32(head, 0x00010000u); Put32(head, 0x00010000u); @@ -53,11 +54,19 @@ static std::vector SquareGlyphFont(std::uint32_t codepoint, int w const int symbol = static_cast(codepoint + 0xF000); PutAll16(cmap, {4, 32, 0, 4, 4, 1, 0, symbol, 0xFFFF, 0, symbol, 0xFFFF, 1 - symbol, 1, 0, 0}); } - const std::vector*>> tables = { + std::vector*>> tables = { {"cmap", &cmap}, {"glyf", &glyf}, {"head", &head}, {"hhea", &hhea}, {"hmtx", &hmtx}, {"loca", &loca}, {"maxp", &maxp}}; + if (gasp) tables.insert(tables.begin() + 1, {"gasp", gasp}); + unsigned power = 1; + unsigned selector = 0; + while (power <= tables.size() / 2) { + power *= 2; + ++selector; + } std::vector font; Put32(font, 0x00010000u); - PutAll16(font, {static_cast(tables.size()), 64, 2, static_cast(tables.size()) * 16 - 64}); + PutAll16(font, {static_cast(tables.size()), static_cast(power * 16), static_cast(selector), + static_cast(tables.size() * 16 - power * 16)}); std::uint32_t offset = 12 + static_cast(tables.size()) * 16; for (const auto& [tag, data] : tables) { font.insert(font.end(), tag, tag + 4); diff --git a/core/libs/tests/GuestAccess.cpp b/core/libs/tests/GuestAccess.cpp index f4fa4fb8e8..1c1b6ae651 100644 --- a/core/libs/tests/GuestAccess.cpp +++ b/core/libs/tests/GuestAccess.cpp @@ -1,5 +1,7 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include +#include #include #include #ifdef _WIN32 @@ -10,12 +12,6 @@ #include #endif -extern "C" { -int APS5_VABI access_nid_postfix(const char*, int); -int APS5_VABI chdir_nid_postfix(const char*); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } int main() { @@ -76,6 +72,46 @@ int main() { Require(access_nid_postfix("sample.txt", 1) == -1 && *__error_nid_postfix() == 13); std::filesystem::permissions(file, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); Require(access_nid_postfix("sample.txt", 1) == 0); +#endif + const auto dangling = directory / "dangling.link"; + const auto pointing = directory / "sample.link"; + const auto loop = directory / "loop.link"; +#ifdef _WIN32 + const DWORD linkFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + const bool linked = CreateSymbolicLinkW(dangling.c_str(), L"missing.bin", linkFlags) && + CreateSymbolicLinkW(pointing.c_str(), L"sample.txt", linkFlags) && CreateSymbolicLinkW(loop.c_str(), L"loop.link", linkFlags); +#else + std::error_code linkError; + std::filesystem::create_symlink("missing.bin", dangling, linkError); + if (!linkError) std::filesystem::create_symlink("sample.txt", pointing, linkError); + if (!linkError) std::filesystem::create_symlink("loop.link", loop, linkError); + const bool linked = !linkError; +#endif + if (linked) { + Require(access_nid_postfix("dangling.link", 0) == -1 && *__error_nid_postfix() == 2); + Require(access_nid_postfix("dangling.link", 4) == -1 && *__error_nid_postfix() == 2); + Require(access_nid_postfix("loop.link", 0) == -1 && *__error_nid_postfix() == 62); + Require(access_nid_postfix("sample.link", 6) == 0); +#ifdef _WIN32 + Require(SetFileAttributesW(file.c_str(), FILE_ATTRIBUTE_READONLY)); + Require(access_nid_postfix("sample.link", 4) == 0); + Require(access_nid_postfix("sample.link", 2) == -1 && *__error_nid_postfix() == 13); + Require(SetFileAttributesW(file.c_str(), FILE_ATTRIBUTE_NORMAL)); + Require(SetFileAttributesW(pointing.c_str(), FILE_ATTRIBUTE_READONLY)); + Require(access_nid_postfix("sample.link", 2) == 0); + Require(SetFileAttributesW(pointing.c_str(), FILE_ATTRIBUTE_NORMAL)); +#endif + } else { + std::fprintf(stderr, "symbolic links unavailable: the access link cases not tested\n"); + } +#ifdef _WIN32 + DeleteFileW(dangling.c_str()); + DeleteFileW(pointing.c_str()); + DeleteFileW(loop.c_str()); +#else + std::filesystem::remove(dangling, linkError); + std::filesystem::remove(pointing, linkError); + std::filesystem::remove(loop, linkError); #endif Require(chdir_nid_postfix("/") == 0); std::filesystem::remove(file); diff --git a/core/libs/tests/GuestAcm.cpp b/core/libs/tests/GuestAcm.cpp index c6691f898d..80d918bda6 100644 --- a/core/libs/tests/GuestAcm.cpp +++ b/core/libs/tests/GuestAcm.cpp @@ -1,18 +1,11 @@ +#include "prx/libSceAcm/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include - -extern "C" { -int APS5_VABI sceAcmContextCreate(std::uint32_t*); -int APS5_VABI sceAcmContextDestroy(std::uint32_t); -int APS5_VABI sceAcmBatchStartBuffer(std::uint32_t, const void*, std::size_t, void*, std::uint32_t*); -int APS5_VABI sceAcmBatchStartBuffers(std::uint32_t, std::uint32_t, const void* const[], void*, std::uint32_t*); -int APS5_VABI sceAcmBatchWait(std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAcm_ConvReverb_SharedInput(void); -int APS5_VABI sceAcm_FFT(void); -int APS5_VABI sceAcm_Panner(void); -} +#include +#include +#include "SceTypes.hpp" static void Require(bool value) { if (!value) std::abort(); @@ -50,9 +43,70 @@ int main() { Require(sceAcmBatchStartBuffers(context2, 0, nullptr, nullptr, &batch2) == 0 && batch2 != 0); Require(sceAcmBatchWait(context2, batch2, 0) == 0); - Require(sceAcm_ConvReverb_SharedInput() == 0); - Require(sceAcm_FFT() == 0); - Require(sceAcm_Panner() == 0); + Require(sceAcm_ConvReverb_SharedInput(nullptr, 0, nullptr, 0, nullptr, nullptr, nullptr) == 0); + Require(sceAcm_FFT(nullptr, 0, 0, 0, nullptr, 0, nullptr, 0) == BadPointer); + alignas(128) std::byte commands[256]{}; + alignas(128) std::uint32_t sourceData[32]{}; + alignas(128) const void* sources[16]{sourceData}; + alignas(128) void* outputs[16]{sourceData}; + AcmBatchInfo commandBatch{commands, 0, sizeof(commands)}; + Require(sceAcm_Panner(&commandBatch, 0, sources, 0, 0, 1, sources, sources, nullptr, outputs) == BadPointer); + sourceData[0] = 2; + Require(sceAcm_Panner(&commandBatch, 1, sources, 0, 0, 1, sources, sources, nullptr, outputs) == BadPointer); + sourceData[0] = 0; + outputs[0] = reinterpret_cast(sourceData) + 4; + Require(sceAcm_Panner(&commandBatch, 1, sources, 0, 0, 1, sources, sources, nullptr, outputs) == static_cast(0x8194000A)); + outputs[0] = sourceData; + Require(sceAcm_Panner(&commandBatch, 1, sources, 2, 1, 1, sources, sources, nullptr, outputs) == 0); + Require(commandBatch.offset == 32 && commandBatch.buffer_size == 128); + std::uint64_t commandWords[4]; + std::memcpy(commandWords, commands, sizeof(commandWords)); + Require(commandWords[0] == ((std::uint64_t{1} << 34) | (std::uint64_t{1} << 36) | 0x01010300)); + Require(commandWords[1] == 0x8000000000000D && commandWords[2] == reinterpret_cast(commands + 128) && commandWords[3] == 10); + std::uint64_t descriptor[16]; + std::memcpy(descriptor, commands + 128, sizeof(descriptor)); + Require(descriptor[0] == 1 && descriptor[1] == 0 && descriptor[2] == reinterpret_cast(outputs)); + Require(descriptor[3] == reinterpret_cast(sources) && descriptor[4] == 0x100000002 && descriptor[5] == 1); + Require(descriptor[6] == reinterpret_cast(sources) && descriptor[7] == reinterpret_cast(sources)); + for (std::size_t index = 8; index < 16; ++index) Require(descriptor[index] == 0); + commandBatch = {commands, 0, sizeof(commands)}; + Require(sceAcm_FFT(&commandBatch, 2048, 1, 0, sources, 0, outputs, 0) == BadPointer); + Require(sceAcm_FFT(&commandBatch, 512, 1, 0, sources, 0, outputs, 3) == BadPointer); + Require(sceAcm_FFT(&commandBatch, 512, 1, 0, sources, 0, outputs, 0) == 0); + Require(commandBatch.offset == 72 && commandBatch.buffer_size == sizeof(commands)); + std::uint64_t fftWords[9]; + std::memcpy(fftWords, commands, sizeof(fftWords)); + Require(fftWords[0] == 0x1C030400 && fftWords[1] == 0xC0A); + Require(fftWords[2] == 0x400000006 && fftWords[3] == reinterpret_cast(sourceData)); + Require(fftWords[4] == 0x90A && fftWords[5] == 0xB0A && fftWords[6] == 0x40A); + Require(fftWords[7] == 0x400000008 && fftWords[8] == reinterpret_cast(sourceData)); + commandBatch = {commands, 0, 71}; + Require(sceAcm_FFT(&commandBatch, 512, 1, 0, sources, 0, outputs, 0) == static_cast(0x81940009)); + Require(commandBatch.offset == 0); + commandBatch = {commands, 0, sizeof(commands)}; + Require(sceAcm_FFT(&commandBatch, 2048, 1, 1, sources, 1, outputs, 9) == 0); + std::memcpy(fftWords, commands, sizeof(fftWords)); + Require(fftWords[0] == 0x600 && fftWords[1] == 0x80A && fftWords[4] == 0x20A && fftWords[5] == 0x60A && fftWords[6] == 0x50A); + sources[1] = sourceData; + outputs[1] = sourceData; + for (const auto size : {512, 1024, 2048}) { + for (std::int32_t inputFormat = 0; inputFormat < 2; ++inputFormat) { + for (std::int32_t outputFormat = 0; outputFormat < 2; ++outputFormat) { + for (std::int32_t flags = 0; flags < 16; ++flags) { + commandBatch = {commands, 0, sizeof(commands)}; + const auto result = sceAcm_FFT(&commandBatch, size, 1, inputFormat, sources, outputFormat, outputs, flags); + if ((flags & 3) == 3 || (size == 2048 && !(flags & 1))) { + Require(result == BadPointer && commandBatch.offset == 0); + } else { + Require(result == 0 && commandBatch.offset == 72 + ((flags & 2) ? 8 : 0) + ((flags & 4) ? 8 : 0)); + } + } + } + } + } + commandBatch = {commands, 0, 152}; + Require(sceAcm_Panner(&commandBatch, 1, sources, 0, 0, 1, sources, sources, nullptr, outputs) == static_cast(0x81940009)); + Require(commandBatch.offset == 0 && commandBatch.buffer_size == 152); Require(sceAcmContextDestroy(context1) == 0); Require(sceAcmContextDestroy(context1) == BadContext); diff --git a/core/libs/tests/GuestAddressConversion.cpp b/core/libs/tests/GuestAddressConversion.cpp index 4d44b0a41b..4b6b4f252a 100644 --- a/core/libs/tests/GuestAddressConversion.cpp +++ b/core/libs/tests/GuestAddressConversion.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,17 +7,6 @@ #include #include -extern "C" { -int APS5_VABI __inet_pton_nid_postfix(int, const char*, void*); -const char* APS5_VABI __inet_ntop_nid_postfix(int, const void*, char*, std::uint32_t); -int APS5_VABI inet_pton_nid_postfix(int, const char*, void*); -const char* APS5_VABI inet_ntop_nid_postfix(int, const void*, char*, std::uint32_t); -int* APS5_VABI __error_nid_postfix(); -std::uint32_t APS5_VABI htonl_nid_postfix(std::uint32_t); -std::uint32_t APS5_VABI ntohl_nid_postfix(std::uint32_t); -std::uint16_t APS5_VABI htons_nid_postfix(std::uint16_t); -std::uint16_t APS5_VABI ntohs_nid_postfix(std::uint16_t); -} static void Require(bool value) { if (!value) std::abort(); } using Ntop = const char* (APS5_VABI *)(int, const void*, char*, std::uint32_t); @@ -470,6 +461,7 @@ static void CheckConsolePton() { *__error_nid_postfix() = 0; const int result = sample.plain ? inet_pton_nid_postfix(sample.family, sample.text, destination) : __inet_pton_nid_postfix(sample.family, sample.text, destination); + Require(result == sample.result); Require(*__error_nid_postfix() == sample.error); Require(SameBytes(destination, sample.bytes)); @@ -484,6 +476,7 @@ static void CheckConsoleNtop() { *__error_nid_postfix() = 0; const char* result = sample.plain ? inet_ntop_nid_postfix(sample.family, source, Wide, sample.size) : __inet_ntop_nid_postfix(sample.family, source, Wide, sample.size); + Require(result == (sample.succeeds ? Wide : nullptr)); Require(*__error_nid_postfix() == sample.error); Require(SameBytes(Wide, sample.bytes)); diff --git a/core/libs/tests/GuestAgcDriverDebugUnavailable.cpp b/core/libs/tests/GuestAgcDriverDebugUnavailable.cpp index 72b97bd56f..8e807d6996 100644 --- a/core/libs/tests/GuestAgcDriverDebugUnavailable.cpp +++ b/core/libs/tests/GuestAgcDriverDebugUnavailable.cpp @@ -1,38 +1,9 @@ +#include "prx/libSceAgcDriver/include/AgcDriver.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI sceAgcDriverGetShaderDebuggingStatus(); -int APS5_VABI sceAgcDriverSetValidationErrorOutputFrequency(std::uint32_t); -int APS5_VABI sceAgcDriverSetSubmitValidationMode(std::uint32_t); -int APS5_VABI sceAgcDriverGetSubmitValidationMode(std::uint32_t*); -int APS5_VABI sceAgcDriverSetSubmitValidationConfig(const void*); -int APS5_VABI sceAgcDriverGetSubmitValidationConfig(void*); -int APS5_VABI sceAgcDriverSetValidationDisabled(); -int APS5_VABI sceAgcDriverGetCaptureConfig(); -int APS5_VABI sceAgcDriverSetCaptureConfig(); -int APS5_VABI sceAgcDriverGetGpuPrintfWorkArea(); -int APS5_VABI sceAgcDriverSetGpuPrintfEnabled(); -int APS5_VABI sceAgcDriverDbgReadRegister(); -int APS5_VABI sceAgcDriverDbgWriteRegister(); -int APS5_VABI sceAgcDriverGetGpuRefClks(); -int APS5_VABI sceAgcDriverIsSuspendPointInFlight(); -int APS5_VABI sceAgcDriverIsPaDebug(); -int APS5_VABI sceAgcDriverGetExceptionStatus(); -int APS5_VABI sceAgcDriverGetTraceInitiator(); -int APS5_VABI sceAgcDriverRegisterGpuResetCallbacks(); -int APS5_VABI sceAgcDriverUnregisterGpuResetCallbacks(); -int APS5_VABI sceAgdResetGpu(); -int APS5_VABI sceAgcDriverSysSubmitFlipHandleProxy(); -int APS5_VABI sceAgcDriverUserDataImmediateWrite(); -int APS5_VABI sceAgcDriverUserDataImmediateWritePacket(); -int APS5_VABI sceAgcDriverGetCoredumpMode(std::uint32_t*); -int APS5_VABI sceAgcDriverAllocateToolMemoryForGpuReset(std::uint64_t, std::uint64_t, std::uint64_t, void*); -int APS5_VABI sceAgcDriverGetAllocatedToolMemoryForGpuReset(std::uint64_t, void*); -} - static constexpr int DebugUnavailable = static_cast(0x8A6C1000); static constexpr int FeatureUnavailable = static_cast(0x8A6D0001); static void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestAgcDriverResources.cpp b/core/libs/tests/GuestAgcDriverResources.cpp index fc55b15a8a..eafe67b86e 100644 --- a/core/libs/tests/GuestAgcDriverResources.cpp +++ b/core/libs/tests/GuestAgcDriverResources.cpp @@ -1,3 +1,6 @@ +#include "prx/libSceAgcDriver/State/include/Status.hpp" +#include "prx/libSceAgcDriver/Resource/include/Registration.hpp" +#include "prx/libSceAgcDriver/include/AgcDriver.hpp" #include "SceTypes.hpp" #include #include @@ -6,33 +9,6 @@ #include #include -extern "C" { -int APS5_VABI sceAgcDriverRegisterDefaultOwner(std::uint32_t); -int APS5_VABI sceAgcDriverRegisterOwner(std::uint32_t*, const char*); -int APS5_VABI sceAgcDriverRegisterResource(std::uint32_t*, std::uint32_t, const void*, std::size_t, const char*, std::uint32_t, std::uint64_t); -int APS5_VABI sceAgcDriverRegisterMultipleResources(std::uint32_t*, std::uint32_t, const void*, std::uint32_t); -int APS5_VABI sceAgcDriverRegisterGdsResource(std::uint32_t*, std::uint32_t, std::uint32_t, std::uint32_t, const char*, std::uint32_t, std::uint64_t); -int APS5_VABI sceAgcDriverUnregisterAllResourcesForOwner(std::uint32_t); -int APS5_VABI sceAgcDriverRegisterWorkloadStream(std::uint32_t, const void*); -int APS5_VABI sceAgcDriverUnregisterWorkloadStream(std::uint32_t); -int APS5_VABI sceAgcDriverGetDefaultOwner(std::uint32_t*); -int APS5_VABI sceAgcDriverGetResourceRegistrationMaxNameLength(std::uint32_t*); -int APS5_VABI sceAgcDriverGetOwnerName(std::uint32_t, const char**); -int APS5_VABI sceAgcDriverGetResourceName(std::uint32_t, const char**); -int APS5_VABI sceAgcDriverGetResourceType(std::uint32_t, std::uint32_t*); -int APS5_VABI sceAgcDriverGetResourceUserData(std::uint32_t, std::uint64_t*); -int APS5_VABI sceAgcDriverSetResourceUserData(std::uint32_t, std::uint64_t); -int APS5_VABI sceAgcDriverGetResourceBaseAddressAndSizeInBytes(std::uint32_t, void**, std::size_t*); -int APS5_VABI sceAgcDriverGetResourceShaderGuid(std::uint32_t, void*); -int APS5_VABI sceAgcDriverFindResourcesPublic(const void*, void*); -bool APS5_VABI sceAgcDriverIsCaptureInProgress(void); -bool APS5_VABI sceAgcDriverIsTraceInProgress(void); -bool APS5_VABI sceAgcDriverIsSubmitValidationEnabled(void); -int APS5_VABI sceAgcDriverRegisterOwnerForSystem(); -int APS5_VABI sceAgcDriverInitResourceRegistrationInterface(); -int APS5_VABI sceAgcDriverSetResourceRegistrationInfo(); -} - static constexpr int Unavailable = static_cast(0x8A6C9018); static void Require(bool value) { if (!value) std::abort(); } @@ -92,8 +68,25 @@ int main() { Require(sceAgcDriverFindResourcesPublic(nullptr, nullptr) == Unavailable); Require(sceAgcDriverUnregisterAllResourcesForOwner(owner) == Unavailable); Require(std::string_view(name) == "unchanged"); + constexpr int InvalidStream = static_cast(0x8A6C0033); + constexpr int NullStream = static_cast(0x8A6C0035); + constexpr int StreamNotRegistered = static_cast(0x8A6C003A); + for (const std::uint32_t id : {0u, 32u, 0x101u, 0xffffffffu}) { + Require(sceAgcDriverRegisterWorkloadStream(id, memory) == InvalidStream); + Require(sceAgcDriverRegisterWorkloadStream(id, nullptr) == InvalidStream); + Require(sceAgcDriverUnregisterWorkloadStream(id) == InvalidStream); + } + Require(sceAgcDriverRegisterWorkloadStream(2u, nullptr) == NullStream); + Require(sceAgcDriverUnregisterWorkloadStream(2u) == StreamNotRegistered); + Require(sceAgcDriverRegisterWorkloadStream(1u, memory) == 0); + Require(sceAgcDriverRegisterWorkloadStream(1u, memory) == InvalidStream); + Require(sceAgcDriverRegisterWorkloadStream(1u, nullptr) == InvalidStream); + Require(sceAgcDriverRegisterWorkloadStream(31u, reinterpret_cast(memory) + 1) == 0); + Require(sceAgcDriverUnregisterWorkloadStream(1u) == 0); + Require(sceAgcDriverUnregisterWorkloadStream(1u) == StreamNotRegistered); Require(sceAgcDriverRegisterWorkloadStream(1u, memory) == 0); Require(sceAgcDriverUnregisterWorkloadStream(1u) == 0); + Require(sceAgcDriverUnregisterWorkloadStream(31u) == 0); Require(!sceAgcDriverIsCaptureInProgress()); Require(!sceAgcDriverIsTraceInProgress()); Require(!sceAgcDriverIsSubmitValidationEnabled()); diff --git a/core/libs/tests/GuestAio.cpp b/core/libs/tests/GuestAio.cpp index cad5b3c0b7..79dfd69a61 100644 --- a/core/libs/tests/GuestAio.cpp +++ b/core/libs/tests/GuestAio.cpp @@ -1,3 +1,5 @@ +#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" @@ -19,28 +21,7 @@ #include extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); #endif -extern "C" { -int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode); -int APS5_VABI sceKernelClose(int d); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence); -int APS5_VABI sceKernelAioDeleteRequest(std::int32_t id, std::int32_t* ret); -int APS5_VABI sceKernelAioInitializeImpl(void* param, std::int32_t size); -void APS5_VABI sceKernelAioInitializeParam(void* param); -int APS5_VABI sceKernelAioSetParam(KernelAioSchedulingParam* param, std::int32_t schedulingWindowSize, std::int32_t delayedCountLimit, std::uint32_t enableSplit, std::uint32_t splitSize, std::uint32_t splitChunkSize); -int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, std::int32_t* id); -int APS5_VABI sceKernelAioSubmitWriteCommands(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, std::int32_t* id); -int APS5_VABI sceKernelAioPollRequest(std::int32_t id, std::int32_t* state); -int APS5_VABI sceKernelAioWaitRequest(std::int32_t id, std::int32_t* state, std::uint32_t* usec); -int APS5_VABI sceKernelAioSubmitReadCommandsMultiple(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, std::int32_t* id); -int APS5_VABI sceKernelAioSubmitWriteCommandsMultiple(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, std::int32_t* id); -int APS5_VABI sceKernelAioWaitRequests(std::int32_t* id, std::int32_t num, std::int32_t* state, std::uint32_t mode, std::uint32_t* usec); -int APS5_VABI sceKernelAioPollRequests(std::int32_t* id, std::int32_t num, std::int32_t* state); -int APS5_VABI sceKernelAioCancelRequest(std::int32_t id, std::int32_t* state); -int APS5_VABI sceKernelAioCancelRequests(std::int32_t* id, std::int32_t num, std::int32_t* state); -int APS5_VABI sceKernelAioDeleteRequests(std::int32_t* id, std::int32_t num, std::int32_t* ret); -} + static void Require(bool value, int line) { if (!value) { std::fprintf(stderr, "Aio check failed at line %d\n", line); @@ -69,9 +50,9 @@ static void CheckBadPositionedDescriptors() { KernelAioResult result{-1, 0}; KernelAioRwRequest request{0, count, &buffer, &result, descriptor}; std::int32_t id = 0; - Check((write ? sceKernelAioSubmitWriteCommands(&request, 1, 0, &id) - : sceKernelAioSubmitReadCommands(&request, 1, 0, &id)) == 0); - Check(result.state == 4 && result.return_value == SCE_KERNEL_ERROR_EBADF); + Check((write ? sceKernelAioSubmitWriteCommands(&request, 1, 1, &id) + : sceKernelAioSubmitReadCommands(&request, 1, 1, &id)) == 0); + Check(result.state == 3 && result.return_value == SCE_KERNEL_ERROR_EBADF); std::int32_t removed = -1; Check(sceKernelAioDeleteRequest(id, &removed) == 0); } @@ -109,26 +90,42 @@ int main(int argc, char** argv) { Check(sceKernelAioSetParam(&schedulingParams[2], 128, 128, 0, 0x1000000, 0x1000000) == 0); Check(schedulingParams[2].schedulingWindowSize == 128 && schedulingParams[2].delayedCountLimit == 128); Check(schedulingParams[2].enableSplit == 0 && schedulingParams[2].splitSize == 0x1000000 && schedulingParams[2].splitChunkSize == 0x1000000); - const auto setParamThrows = [&](std::int32_t window, std::int32_t delayed, std::uint32_t split, std::uint32_t size, std::uint32_t chunk) { + const auto setParamRefuses = [&](std::int32_t window, std::int32_t delayed, std::uint32_t split, std::uint32_t size, std::uint32_t chunk) { KernelAioSchedulingParam untouchedParam{7, 7, 7, 7, 7}; - bool thrown = false; - try { sceKernelAioSetParam(&untouchedParam, window, delayed, split, size, chunk); } catch (const std::exception&) { thrown = true; } - return thrown && untouchedParam.schedulingWindowSize == 7 && untouchedParam.delayedCountLimit == 7 && + return sceKernelAioSetParam(&untouchedParam, window, delayed, split, size, chunk) == SCE_KERNEL_ERROR_EINVAL && + untouchedParam.schedulingWindowSize == 7 && untouchedParam.delayedCountLimit == 7 && untouchedParam.enableSplit == 7 && untouchedParam.splitSize == 7 && untouchedParam.splitChunkSize == 7; }; - Check(setParamThrows(129, 32, 1, 0x100000, 0x100000)); - Check(setParamThrows(-1, 32, 1, 0x100000, 0x100000)); - Check(setParamThrows(32, 129, 1, 0x100000, 0x100000)); - Check(setParamThrows(32, -1, 1, 0x100000, 0x100000)); - Check(setParamThrows(32, 32, 2, 0x100000, 0x100000)); - Check(setParamThrows(32, 32, 1, 0x1000001, 0x100000)); - Check(setParamThrows(32, 32, 1, 0x100000, 0x1000001)); + Check(setParamRefuses(129, 32, 1, 0x100000, 0x100000)); + Check(setParamRefuses(0x7fffffff, 32, 0, 0, 0)); + Check(setParamRefuses(-1, 32, 1, 0x100000, 0x100000)); + Check(setParamRefuses(32, 129, 1, 0x100000, 0x100000)); + Check(setParamRefuses(32, -1, 1, 0x100000, 0x100000)); + Check(setParamRefuses(32, 32, 2, 0x100000, 0x100000)); + Check(setParamRefuses(32, 32, 0xffffffffu, 0x100000, 0x100000)); + Check(setParamRefuses(32, 32, 1, 0x1000001, 0x100000)); + Check(setParamRefuses(32, 32, 1, 0x100000, 0x1000001)); + Check(setParamRefuses(32, 32, 1, 0, 0x100000)); + Check(setParamRefuses(32, 32, 1, 0x100000, 0)); + KernelAioSchedulingParam accepted{7, 7, 7, 7, 7}; + Check(sceKernelAioSetParam(&accepted, 0, 0, 0, 0, 0) == 0 && accepted.schedulingWindowSize == 0 && accepted.splitSize == 0); + Check(sceKernelAioSetParam(&accepted, 1, 1, 0, 0xffffffffu, 0x1000001) == 0 && accepted.splitSize == 0xffffffffu && accepted.splitChunkSize == 0x1000001); + Check(sceKernelAioSetParam(&accepted, 1, 1, 1, 1, 0x20000) == 0 && accepted.splitSize == 1 && accepted.splitChunkSize == 0x20000); + Check(sceKernelAioSetParam(&accepted, 1, 1, 1, 0x10001, 0x1000000) == 0 && accepted.splitSize == 0x10001); bool nullThrew = false; try { sceKernelAioSetParam(nullptr, 32, 32, 1, 0x100000, 0x100000); } catch (const std::invalid_argument&) { nullThrew = true; } Check(nullThrew); Check(sceKernelAioInitializeImpl(schedulingParams.data(), static_cast(sizeof(schedulingParams))) == 0); - int initParam = 0; - sceKernelAioInitializeParam(&initParam); + std::array initParam{}; + initParam.fill({7, 7, 7, 7, 7}); + sceKernelAioInitializeParam(initParam.data()); + for (int index = 0; index < 2; ++index) { + Check(initParam[index].schedulingWindowSize == 32 && initParam[index].delayedCountLimit == 32 && initParam[index].enableSplit == 1 && + initParam[index].splitSize == 0x100000 && initParam[index].splitChunkSize == 0x100000); + } + Check(initParam[2].schedulingWindowSize == 32 && initParam[2].delayedCountLimit == 32 && initParam[2].enableSplit == 0 && + initParam[2].splitSize == 0 && initParam[2].splitChunkSize == 0); + Check(initParam[3].schedulingWindowSize == 7 && initParam[3].splitChunkSize == 7); const int fd = sceKernelOpen(file.string().c_str(), SCE_KERNEL_O_RDONLY, 0); Check(fd >= 0); std::array first{}; @@ -140,7 +137,7 @@ int main(int argc, char** argv) { {32, second.size(), second.data(), &secondResult, fd}, }; std::int32_t id = 0; - Check(sceKernelAioSubmitReadCommands(requests, 2, 0, &id) == 0); + Check(sceKernelAioSubmitReadCommands(requests, 2, 1, &id) == 0); Check(id > 0); std::int32_t state = 0; Check(sceKernelAioWaitRequest(id, &state, nullptr) == 0); @@ -152,11 +149,11 @@ int main(int argc, char** argv) { KernelAioResult invalidReadResult{-1, 0}; KernelAioRwRequest invalidReadRequest{-1, first.size(), first.data(), &invalidReadResult, fd}; std::int32_t invalidReadId = 0; - Check(sceKernelAioSubmitReadCommands(&invalidReadRequest, 1, 0, &invalidReadId) == 0); + Check(sceKernelAioSubmitReadCommands(&invalidReadRequest, 1, 1, &invalidReadId) == 0); std::int32_t invalidReadState = 0; Check(sceKernelAioWaitRequest(invalidReadId, &invalidReadState, nullptr) == 0); - Check(invalidReadState == 4); - Check(invalidReadResult.state == 4 && invalidReadResult.return_value == SCE_KERNEL_ERROR_EINVAL); + Check(invalidReadState == 3); + Check(invalidReadResult.state == 3 && invalidReadResult.return_value == SCE_KERNEL_ERROR_EINVAL); std::array aggregateReadBuffer{}; KernelAioResult aggregateReadSuccessResult{-1, 0}; KernelAioResult aggregateReadErrorResult{-1, 0}; @@ -165,16 +162,26 @@ int main(int argc, char** argv) { {-1, aggregateReadBuffer.size(), aggregateReadBuffer.data(), &aggregateReadErrorResult, fd}, }; std::int32_t aggregateReadId = 0; - Check(sceKernelAioSubmitReadCommands(aggregateReads, 2, 0, &aggregateReadId) == 0); + Check(sceKernelAioSubmitReadCommands(aggregateReads, 2, 1, &aggregateReadId) == 0); std::int32_t aggregateReadState = 0; Check(sceKernelAioWaitRequest(aggregateReadId, &aggregateReadState, nullptr) == 0); - Check(aggregateReadState == 4); + Check(aggregateReadState == 3); Check(aggregateReadSuccessResult.state == 3 && aggregateReadSuccessResult.return_value == 4); - Check(aggregateReadErrorResult.state == 4 && aggregateReadErrorResult.return_value == SCE_KERNEL_ERROR_EINVAL); + Check(aggregateReadErrorResult.state == 3 && aggregateReadErrorResult.return_value == SCE_KERNEL_ERROR_EINVAL); Check(std::memcmp(aggregateReadBuffer.data(), seed.data() + 4, aggregateReadBuffer.size()) == 0); + std::int32_t pollFirstId = 0; + Check(sceKernelAioSubmitReadCommands(requests, 1, 1, &pollFirstId) == 0); + std::int32_t pollFirst = 0; + std::int32_t cancelFirst = 0; + Check(sceKernelAioCancelRequest(pollFirstId, &cancelFirst) == 0 && cancelFirst == 3); + Check(sceKernelAioPollRequest(pollFirstId, &pollFirst) == 0 && pollFirst == 3); + Check(sceKernelAioPollRequest(pollFirstId, &pollFirst) == 0 && pollFirst == 0x10003); + Check(sceKernelAioCancelRequest(pollFirstId, &cancelFirst) == 0 && cancelFirst == 3); + Check(sceKernelAioDeleteRequest(pollFirstId, &cancelFirst) == 0 && cancelFirst == 0); std::int32_t polled = 0; Check(sceKernelAioPollRequest(id, &polled) == 0); - Check(polled == 3); + Check(polled == 0x10003); + Check(sceKernelAioWaitRequest(id, &state, nullptr) == 0 && state == 0x10003); std::int32_t deleted = -1; Check(sceKernelAioDeleteRequest(id, &deleted) == 0); Check(deleted == 0); @@ -185,18 +192,18 @@ int main(int argc, char** argv) { KernelAioResult writeResult{-1, 0}; KernelAioRwRequest writeRequest{0, written.size(), written.data(), &writeResult, writeFd}; std::int32_t writeId = 0; - Check(sceKernelAioSubmitWriteCommands(&writeRequest, 1, 0, &writeId) == 0); + Check(sceKernelAioSubmitWriteCommands(&writeRequest, 1, 1, &writeId) == 0); std::int32_t writeState = 0; Check(sceKernelAioWaitRequest(writeId, &writeState, nullptr) == 0); Check(writeState == 3 && writeResult.state == 3 && writeResult.return_value == 5); KernelAioResult invalidWriteResult{-1, 0}; KernelAioRwRequest invalidWriteRequest{-1, written.size(), written.data(), &invalidWriteResult, writeFd}; std::int32_t invalidWriteId = 0; - Check(sceKernelAioSubmitWriteCommands(&invalidWriteRequest, 1, 0, &invalidWriteId) == 0); + Check(sceKernelAioSubmitWriteCommands(&invalidWriteRequest, 1, 1, &invalidWriteId) == 0); std::int32_t invalidWriteState = 0; Check(sceKernelAioWaitRequest(invalidWriteId, &invalidWriteState, nullptr) == 0); - Check(invalidWriteState == 4); - Check(invalidWriteResult.state == 4 && invalidWriteResult.return_value == SCE_KERNEL_ERROR_EINVAL); + Check(invalidWriteState == 3); + Check(invalidWriteResult.state == 3 && invalidWriteResult.return_value == SCE_KERNEL_ERROR_EINVAL); std::array aggregateWriteBuffer = {'G', 'O', 'O', 'D'}; KernelAioResult aggregateWriteSuccessResult{-1, 0}; KernelAioResult aggregateWriteErrorResult{-1, 0}; @@ -205,12 +212,12 @@ int main(int argc, char** argv) { {-1, aggregateWriteBuffer.size(), aggregateWriteBuffer.data(), &aggregateWriteErrorResult, writeFd}, }; std::int32_t aggregateWriteId = 0; - Check(sceKernelAioSubmitWriteCommands(aggregateWrites, 2, 0, &aggregateWriteId) == 0); + Check(sceKernelAioSubmitWriteCommands(aggregateWrites, 2, 1, &aggregateWriteId) == 0); std::int32_t aggregateWriteState = 0; Check(sceKernelAioWaitRequest(aggregateWriteId, &aggregateWriteState, nullptr) == 0); - Check(aggregateWriteState == 4); + Check(aggregateWriteState == 3); Check(aggregateWriteSuccessResult.state == 3 && aggregateWriteSuccessResult.return_value == 4); - Check(aggregateWriteErrorResult.state == 4 && aggregateWriteErrorResult.return_value == SCE_KERNEL_ERROR_EINVAL); + Check(aggregateWriteErrorResult.state == 3 && aggregateWriteErrorResult.return_value == SCE_KERNEL_ERROR_EINVAL); Check(sceKernelClose(writeFd) == 0); { std::ifstream stream(file, std::ios::binary); std::string contents; std::getline(stream, contents); Check(contents.rfind("HELLO", 0) == 0); } @@ -220,13 +227,17 @@ int main(int argc, char** argv) { Check(contents.size() == seed.size()); Check(contents.substr(8, aggregateWriteBuffer.size()) == "GOOD"); Check(contents.substr(12, 4) == seed.substr(12, 4)); } - Check(sceKernelAioSubmitReadCommands(nullptr, 1, 0, &id) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioSubmitReadCommands(requests, 0, 0, &id) == SCE_KERNEL_ERROR_EINVAL); - Check(sceKernelAioSubmitReadCommands(requests, 1, 0, nullptr) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioWaitRequest(9999, &state, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioSubmitReadCommands(nullptr, 1, 1, &id) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioSubmitReadCommands(requests, 0, 1, &id) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioSubmitReadCommands(requests, 1, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); + state = 7; + Check(sceKernelAioWaitRequest(9999, &state, nullptr) == 0 && state == SCE_KERNEL_ERROR_ESRCH); Check(sceKernelAioWaitRequest(id, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioPollRequest(id, nullptr) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioPollRequest(0, &polled) == SCE_KERNEL_ERROR_EINVAL); + for (const std::int32_t unknown : {0, -1, 511, 512, 100000}) { + polled = 7; + Check(sceKernelAioPollRequest(unknown, &polled) == 0 && polled == SCE_KERNEL_ERROR_ESRCH); + } Check(sceKernelAioDeleteRequest(id, nullptr) == SCE_KERNEL_ERROR_EFAULT); const int batchFd = sceKernelOpen(file.string().c_str(), SCE_KERNEL_O_RDWR, 0); Check(batchFd >= 0); @@ -239,13 +250,13 @@ int main(int argc, char** argv) { {-1, batchReadBuffer.size(), batchReadBuffer.data(), &batchReadErrorResult, batchFd}, }; std::int32_t batchReadIds[2] = {0, 0}; - Check(sceKernelAioSubmitReadCommandsMultiple(batchReads, 2, 0, batchReadIds) == 0); + Check(sceKernelAioSubmitReadCommandsMultiple(batchReads, 2, 1, batchReadIds) == 0); Check(batchReadIds[0] > 0 && batchReadIds[1] > 0 && batchReadIds[0] != batchReadIds[1]); Check(batchReadSuccessResult.state == 3 && batchReadSuccessResult.return_value == 4); - Check(batchReadErrorResult.state == 4 && batchReadErrorResult.return_value == SCE_KERNEL_ERROR_EINVAL); + Check(batchReadErrorResult.state == 3 && batchReadErrorResult.return_value == SCE_KERNEL_ERROR_EINVAL); std::int32_t batchReadStates[2] = {0, 0}; Check(sceKernelAioWaitRequests(batchReadIds, 2, batchReadStates, 1, nullptr) == 0); - Check(batchReadStates[0] == 3 && batchReadStates[1] == 4); + Check(batchReadStates[0] == 3 && batchReadStates[1] == 3); std::array batchWriteData = {'A', 'B', 'C', 'D'}; KernelAioResult batchWriteSuccessResult{-1, 0}; KernelAioResult batchWriteErrorResult{-1, 0}; @@ -254,13 +265,13 @@ int main(int argc, char** argv) { {-1, batchWriteData.size(), batchWriteData.data(), &batchWriteErrorResult, batchFd}, }; std::int32_t batchWriteIds[2] = {0, 0}; - Check(sceKernelAioSubmitWriteCommandsMultiple(batchWrites, 2, 0, batchWriteIds) == 0); + Check(sceKernelAioSubmitWriteCommandsMultiple(batchWrites, 2, 1, batchWriteIds) == 0); Check(batchWriteIds[0] > 0 && batchWriteIds[1] > 0 && batchWriteIds[0] != batchWriteIds[1]); Check(batchWriteSuccessResult.state == 3 && batchWriteSuccessResult.return_value == 4); - Check(batchWriteErrorResult.state == 4 && batchWriteErrorResult.return_value == SCE_KERNEL_ERROR_EINVAL); + Check(batchWriteErrorResult.state == 3 && batchWriteErrorResult.return_value == SCE_KERNEL_ERROR_EINVAL); std::int32_t batchWriteStates[2] = {0, 0}; Check(sceKernelAioWaitRequests(batchWriteIds, 2, batchWriteStates, 1, nullptr) == 0); - Check(batchWriteStates[0] == 3 && batchWriteStates[1] == 4); + Check(batchWriteStates[0] == 3 && batchWriteStates[1] == 3); std::array patch = {'W', 'X', 'Y', 'Z'}; KernelAioResult patchResult{-1, 0}; KernelAioResult badResult{-1, 0}; @@ -269,17 +280,17 @@ int main(int argc, char** argv) { {0, patch.size(), patch.data(), &badResult, -1}, }; std::int32_t writeIds[2] = {0, 0}; - Check(sceKernelAioSubmitWriteCommandsMultiple(writeBatch, 2, 0, writeIds) == 0); + Check(sceKernelAioSubmitWriteCommandsMultiple(writeBatch, 2, 1, writeIds) == 0); Check(sceKernelLseek(batchFd, 0, 1) == 2); Check(writeIds[0] > 0 && writeIds[1] > 0 && writeIds[0] != writeIds[1]); Check(patchResult.state == 3 && patchResult.return_value == 4); - Check(badResult.state == 4 && badResult.return_value == SCE_KERNEL_ERROR_EBADF); + Check(badResult.state == 3 && badResult.return_value == SCE_KERNEL_ERROR_EBADF); std::int32_t writeStates[2] = {0, 0}; Check(sceKernelAioWaitRequests(writeIds, 2, writeStates, 1, nullptr) == 0); - Check(writeStates[0] == 3 && writeStates[1] == 4); + Check(writeStates[0] == 3 && writeStates[1] == 3); std::int32_t polledStates[2] = {0, 0}; Check(sceKernelAioPollRequests(writeIds, 2, polledStates) == 0); - Check(polledStates[0] == 3 && polledStates[1] == 4); + Check(polledStates[0] == 0x10003 && polledStates[1] == 0x10003); std::array readBack{}; std::array tail{}; KernelAioResult readBackResult{-1, 0}; @@ -289,7 +300,7 @@ int main(int argc, char** argv) { {60, tail.size(), tail.data(), &tailResult, batchFd}, }; std::int32_t readIds[2] = {0, 0}; - Check(sceKernelAioSubmitReadCommandsMultiple(readBatch, 2, 0, readIds) == 0); + Check(sceKernelAioSubmitReadCommandsMultiple(readBatch, 2, 1, readIds) == 0); Check(readIds[0] > 0 && readIds[1] > 0 && readIds[0] != readIds[1]); Check(readBackResult.state == 3 && readBackResult.return_value == 4); Check(std::memcmp(readBack.data(), "WXYZ", 4) == 0); @@ -301,74 +312,87 @@ int main(int argc, char** argv) { Check(readStates[0] == 3 && readStates[1] == 3); std::int32_t cancelled = 0; Check(sceKernelAioCancelRequest(readIds[0], &cancelled) == 0); - Check(cancelled == 4); + Check(cancelled == 3); Check(sceKernelAioPollRequest(readIds[0], &polled) == 0); - Check(polled == 4); + Check(polled == 0x10003); Check(sceKernelAioCancelRequest(0, &cancelled) == 0); - Check(cancelled == 2); + Check(cancelled == SCE_KERNEL_ERROR_ESRCH); std::int32_t cancelIds[2] = {0, readIds[1]}; std::int32_t cancelStates[2] = {0, 0}; Check(sceKernelAioCancelRequests(cancelIds, 2, cancelStates) == 0); - Check(cancelStates[0] == 2 && cancelStates[1] == 4); + Check(cancelStates[0] == SCE_KERNEL_ERROR_ESRCH && cancelStates[1] == 3); Check(sceKernelAioPollRequest(readIds[1], &polled) == 0); - Check(polled == 4); + Check(polled == 0x10003); + Check(sceKernelAioCancelRequest(readIds[1], &cancelled) == 0 && cancelled == 3); Check(sceKernelAioPollRequests(readIds, 2, polledStates) == 0); - Check(polledStates[0] == 4 && polledStates[1] == 4); + Check(polledStates[0] == 0x10003 && polledStates[1] == 0x10003); std::int32_t deletedRets[2] = {-1, -1}; Check(sceKernelAioDeleteRequests(writeIds, 2, deletedRets) == 0); Check(deletedRets[0] == 0 && deletedRets[1] == 0); Check(sceKernelAioPollRequest(writeIds[0], &polled) == 0); - Check(polled == 4); - Check(sceKernelAioDeleteRequests(writeIds, 0, deletedRets) == 0); + Check(polled == SCE_KERNEL_ERROR_ESRCH); + Check(sceKernelAioDeleteRequests(writeIds, 2, deletedRets) == 0); + Check(deletedRets[0] == SCE_KERNEL_ERROR_ESRCH && deletedRets[1] == SCE_KERNEL_ERROR_ESRCH); + std::int32_t deletedRet = 7; + Check(sceKernelAioDeleteRequest(writeIds[0], &deletedRet) == 0 && deletedRet == SCE_KERNEL_ERROR_ESRCH); + std::uint32_t unknownTimeout = 1000; + Check(sceKernelAioWaitRequests(writeIds, 2, polledStates, 1, &unknownTimeout) == 0); + Check(polledStates[0] == SCE_KERNEL_ERROR_ESRCH && polledStates[1] == SCE_KERNEL_ERROR_ESRCH); + Check(sceKernelAioDeleteRequests(writeIds, 0, deletedRets) == SCE_KERNEL_ERROR_EINVAL); Check(sceKernelClose(batchFd) == 0); std::int32_t badIds[2] = {readIds[0], 9999}; std::int32_t untouched[2] = {7, 7}; - Check(sceKernelAioSubmitReadCommandsMultiple(nullptr, 1, 0, readIds) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioSubmitReadCommandsMultiple(readBatch, 1, 0, nullptr) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioSubmitWriteCommandsMultiple(writeBatch, 0, 0, writeIds) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioSubmitReadCommandsMultiple(nullptr, 1, 1, readIds) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioSubmitReadCommandsMultiple(readBatch, 1, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioSubmitWriteCommandsMultiple(writeBatch, 0, 1, writeIds) == SCE_KERNEL_ERROR_EINVAL); KernelAioRwRequest noResult{0, 1, readBack.data(), nullptr, batchFd}; - Check(sceKernelAioSubmitWriteCommandsMultiple(&noResult, 1, 0, writeIds) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioSubmitWriteCommandsMultiple(&noResult, 1, 1, writeIds) == SCE_KERNEL_ERROR_EFAULT); std::array unread{}; std::array unreadIds{}; - for (const auto submit : {sceKernelAioSubmitReadCommandsMultiple, sceKernelAioSubmitWriteCommandsMultiple}) { - bool limited = false; - try { submit(unread.data(), 129, 0, unreadIds.data()); } catch (const std::runtime_error&) { limited = true; } - Check(limited); + unreadIds.fill(7); + for (const auto submit : {sceKernelAioSubmitReadCommandsMultiple, sceKernelAioSubmitWriteCommandsMultiple, sceKernelAioSubmitReadCommands, sceKernelAioSubmitWriteCommands}) { + Check(submit(unread.data(), 129, 1, unreadIds.data()) == SCE_KERNEL_ERROR_EINVAL); + for (const std::int32_t priority : {0, 4, -1}) Check(submit(unread.data(), 1, priority, unreadIds.data()) == SCE_KERNEL_ERROR_EINVAL); + Check(unreadIds[0] == 7); } - std::int32_t unreadId = 0; - Check(sceKernelAioSubmitReadCommands(unread.data(), 129, 0, &unreadId) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioSubmitWriteCommands(unread.data(), 129, 0, &unreadId) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(readIds, 2, nullptr, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(nullptr, 2, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(readIds, -1, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); - Check(sceKernelAioWaitRequests(badIds, 2, untouched, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); - Check(untouched[0] == 7 && untouched[1] == 7); + Check(sceKernelAioWaitRequests(badIds, 2, untouched, 1, nullptr) == 0); + Check(untouched[0] == 0x10003 && untouched[1] == SCE_KERNEL_ERROR_ESRCH); + untouched[0] = untouched[1] = 7; Check(sceKernelAioPollRequests(readIds, 2, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioPollRequests(nullptr, 2, readStates) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioPollRequests(readIds, -1, readStates) == SCE_KERNEL_ERROR_EINVAL); - Check(sceKernelAioPollRequests(badIds, 2, untouched) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioPollRequests(badIds, 2, untouched) == 0); + Check(untouched[0] == 0x10003 && untouched[1] == SCE_KERNEL_ERROR_ESRCH); + untouched[0] = untouched[1] = 7; + Check(sceKernelAioPollRequests(readIds, 0, untouched) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioWaitRequests(readIds, 0, untouched, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioCancelRequests(readIds, 0, untouched) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioDeleteRequests(readIds, 0, untouched) == SCE_KERNEL_ERROR_EINVAL); Check(untouched[0] == 7 && untouched[1] == 7); - Check(sceKernelAioPollRequests(readIds, 0, untouched) == 0); - Check(untouched[0] == 7 && untouched[1] == 7); - bool threw = false; - try { sceKernelAioWaitRequests(readIds, 2, readStates, 3, nullptr); } catch (const std::runtime_error&) { threw = true; } - Check(threw); - threw = false; + for (const std::uint32_t mode : {0u, 3u, 4u, 0x100u, 0xffffffffu}) { + Check(sceKernelAioWaitRequests(readIds, 2, untouched, mode, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Check(untouched[0] == 7 && untouched[1] == 7); + } std::array manyIds{}; std::array manyStates{}; manyIds.fill(readIds[0]); - try { sceKernelAioDeleteRequests(manyIds.data(), 129, manyStates.data()); } catch (const std::runtime_error&) { threw = true; } - Check(threw); - threw = false; - try { sceKernelAioPollRequests(manyIds.data(), 129, manyStates.data()); } catch (const std::runtime_error&) { threw = true; } - Check(threw); - Check(sceKernelAioCancelRequest(9999, &cancelled) == SCE_KERNEL_ERROR_EINVAL); + manyStates.fill(7); + Check(sceKernelAioDeleteRequests(manyIds.data(), 129, manyStates.data()) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioPollRequests(manyIds.data(), 129, manyStates.data()) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioWaitRequests(manyIds.data(), 129, manyStates.data(), 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioCancelRequests(manyIds.data(), 129, manyStates.data()) == SCE_KERNEL_ERROR_EINVAL); + Check(manyStates[0] == 7); + Check(sceKernelAioPollRequests(manyIds.data(), 128, manyStates.data()) == 0 && manyStates[127] == manyStates[0]); + Check(sceKernelAioCancelRequest(9999, &cancelled) == 0 && cancelled == SCE_KERNEL_ERROR_ESRCH); Check(sceKernelAioCancelRequest(readIds[0], nullptr) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioCancelRequests(badIds, 2, untouched) == SCE_KERNEL_ERROR_EINVAL); - Check(untouched[0] == 7 && untouched[1] == 7); + Check(sceKernelAioCancelRequests(badIds, 2, untouched) == 0); + Check(untouched[0] == 3 && untouched[1] == SCE_KERNEL_ERROR_ESRCH); Check(sceKernelAioCancelRequests(cancelIds, 2, nullptr) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioDeleteRequests(badIds, 2, untouched) == SCE_KERNEL_ERROR_EINVAL); - Check(untouched[0] == 7 && untouched[1] == 7); + Check(sceKernelAioDeleteRequests(badIds, 2, untouched) == 0); + Check(untouched[0] == 0 && untouched[1] == SCE_KERNEL_ERROR_ESRCH); Check(sceKernelAioDeleteRequests(writeIds, 2, nullptr) == SCE_KERNEL_ERROR_EFAULT); const auto shared = root / "shared.bin"; std::string sharedContents; @@ -385,7 +409,7 @@ int main(int argc, char** argv) { KernelAioResult result{-1, 0}; KernelAioRwRequest request{offset, bytes.size(), bytes.data(), &result, sharedFd}; std::int32_t requestId = 0; - if (sceKernelAioSubmitReadCommands(&request, 1, 0, &requestId) != 0 || result.state != 3 || + if (sceKernelAioSubmitReadCommands(&request, 1, 1, &requestId) != 0 || result.state != 3 || result.return_value != 16 || std::memcmp(bytes.data(), sharedContents.data() + offset, 16) != 0) { misread = true; } @@ -395,5 +419,21 @@ int main(int argc, char** argv) { for (auto& reader : readers) reader.join(); Check(!misread); Check(sceKernelClose(sharedFd) == 0); + const auto appendFile = root / "append.bin"; + { std::ofstream stream(appendFile, std::ios::binary); stream << seed; } + const int appendFd = sceKernelOpen(appendFile.string().c_str(), SCE_KERNEL_O_RDWR | SCE_KERNEL_O_APPEND, 0); + Check(appendFd >= 0); + std::array appendBuffer = {'T', 'A', 'I', 'L'}; + KernelAioResult appendResult{-1, 0}; + KernelAioRwRequest appendRequest{0, appendBuffer.size(), appendBuffer.data(), &appendResult, appendFd}; + std::int32_t appendId = 0; + Check(sceKernelAioSubmitWriteCommands(&appendRequest, 1, 1, &appendId) == 0); + std::int32_t appendState = 0; + Check(sceKernelAioWaitRequest(appendId, &appendState, nullptr) == 0); + Check(appendState == 3 && appendResult.state == 3 && appendResult.return_value == 4); + Check(sceKernelClose(appendFd) == 0); + { std::ifstream stream(appendFile, std::ios::binary); + std::string contents{std::istreambuf_iterator(stream), std::istreambuf_iterator()}; + Check(contents == seed + "TAIL"); } Check(std::filesystem::remove_all(root) > 0); } diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index 017b6304a8..1aece55d9e 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAjm.native/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -16,34 +17,6 @@ #include #endif -extern "C" { -int APS5_VABI sceAjmInitialize(std::int64_t, std::uint32_t*); -int APS5_VABI sceAjmFinalize(std::uint32_t); -int APS5_VABI sceAjmDecAt9ParseConfigData(const void*, AjmDecAt9ConfigDataInfo*); -int APS5_VABI sceAjmDecMp3ParseFrame(const std::uint8_t*, std::uint32_t, int, AjmDecMp3ParseFrame*); -int APS5_VABI sceAjmInstanceCreate(std::uint32_t, std::uint32_t, std::uint64_t, std::uint32_t*); -int APS5_VABI sceAjmInstanceDestroy(std::uint32_t, std::uint32_t); -int APS5_VABI sceAjmBatchInitialize(void*, std::size_t, AjmBatchInfo*); -int APS5_VABI sceAjmBatchJobInitialize(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*); -int APS5_VABI sceAjmBatchJobDecode(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*, std::size_t, void*); -int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*, std::size_t, void*); -int APS5_VABI sceAjmBatchJobRun(AjmBatchInfo*, std::uint32_t, std::uint64_t, const void*, std::size_t, void*, std::size_t, void*, std::size_t); -int APS5_VABI sceAjmBatchJobRunSplit(AjmBatchInfo*, std::uint32_t, std::uint64_t, const AjmBuffer*, std::size_t, const AjmBuffer*, std::size_t, void*, std::size_t); -int APS5_VABI sceAjmBatchJobSetGaplessDecode(AjmBatchInfo*, std::uint32_t, const void*, int, void*); -int APS5_VABI sceAjmBatchJobControl(AjmBatchInfo*, std::uint32_t, std::uint64_t, const void*, std::size_t, void*, std::size_t); -int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo*, std::uint32_t, void*); -int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo*, std::uint32_t, void*, std::size_t); -int APS5_VABI sceAjmBatchJobGetInfo(AjmBatchInfo*, std::uint32_t, void*); -int APS5_VABI sceAjmBatchStart(std::uint32_t, const AjmBatchInfo*, int, AjmBatchError*, std::uint32_t*); -int APS5_VABI sceAjmBatchWait(std::uint32_t, std::uint32_t, std::uint32_t, AjmBatchError*); -int APS5_VABI sceAjmBatchCancel(std::uint32_t, std::uint32_t); -int APS5_VABI sceAjmBatchJobClearContext(AjmBatchInfo*, std::uint32_t, void*); -int APS5_VABI sceAjmBatchJobSetResampleParameters(AjmBatchInfo*, std::uint32_t, float, std::uint32_t, void*); -int APS5_VABI sceAjmBatchJobGetResampleInfo(AjmBatchInfo*, std::uint32_t, void*); -int APS5_VABI sceAjmBatchJobSetResampleParametersEx(AjmBatchInfo*, std::uint32_t, float, float, std::uint32_t, void*); -int APS5_VABI sceAjmBatchJobDecodeSplit(AjmBatchInfo*, std::uint32_t, const AjmBuffer*, std::size_t, const AjmBuffer*, std::size_t, void*); -} - static void Require(bool value) { if (!value) std::abort(); } namespace { @@ -391,6 +364,21 @@ void TestMp3ParseFrame() { Require(sceAjmDecMp3ParseFrame(reservedSampleRate, 4, 0, &frame) == invalidParameter); const std::uint8_t mpeg25Above64Kbps[4] = {0xFF, 0xE3, 0x98, 0xC0}; Require(sceAjmDecMp3ParseFrame(mpeg25Above64Kbps, 4, 0, &frame) == invalidParameter); + for (const std::uint8_t layer : {0u, 2u, 3u}) { + const std::uint8_t header[4] = {0xFF, static_cast(0xF9u | (layer << 1u)), 0x90, 0x04}; + if (layer == 0) { + std::memset(&frame, 0xA5, sizeof(frame)); + const auto previous = frame; + Require(sceAjmDecMp3ParseFrame(header, sizeof(header), 0, &frame) == invalidParameter); + Require(std::memcmp(&frame, &previous, sizeof(frame)) == 0); + } else { + Require(ParsesTo(header, sizeof(header), 417, 2, 1152, 128000, 44100)); + } + std::memset(&frame, 0xA5, sizeof(frame)); + const auto previous = frame; + Require(sceAjmDecMp3ParseFrame(header, sizeof(header), 1, &frame) == invalidParameter); + Require(std::memcmp(&frame, &previous, sizeof(frame)) == 0); + } } const std::uint8_t LAME_MPEG1_STEREO_VBR[] = { @@ -457,14 +445,17 @@ bool OflParsesTo(const std::vector& stream, std::uint32_t streamSi return frame.num_frames == frames && frame.encoder_delay == delay && frame.total_samples == total && frame.ofl_type == type; } -bool OflThrows(const std::vector& stream) { - AjmDecMp3ParseFrame frame{}; - try { - sceAjmDecMp3ParseFrame(stream.data(), static_cast(stream.size()), 1, &frame); - } catch (const std::runtime_error&) { - return true; +std::vector ProtectedMp3Frame(std::vector frame, bool tagAfterCrc) { + frame[1] &= 0xFE; + if (tagAfterCrc) { + const auto size = frame.size(); + frame.insert(frame.begin() + 4, {0xA5, 0x5A}); + frame.resize(size); + } else { + frame[4] = 0xA5; + frame[5] = 0x5A; } - return false; + return frame; } void TestMp3ParseOfl() { @@ -481,21 +472,75 @@ void TestMp3ParseOfl() { const auto fgh = Mp3Frame(FGH_FRAME, sizeof(FGH_FRAME), 417); Require(OflParsesTo(fgh, 417, 0, 1105, 441000, 3)); Require(OflParsesTo(Mp3Frame(FGH_BAD_CRC_FRAME, sizeof(FGH_BAD_CRC_FRAME), 417), 417, 0, 0, 0, 0)); + const auto marker = std::find(fgh.begin() + 36, fgh.end(), 0xB4u); + Require(marker != fgh.end() && static_cast(fgh.end() - marker) >= 10); + std::vector misleadingFgh(417, 0); + std::copy_n(fgh.begin(), 4, misleadingFgh.begin()); + const auto markerOffset = static_cast(marker - fgh.begin()); + std::copy_n(fgh.begin() + static_cast(markerOffset), 10, misleadingFgh.begin() + 36); + misleadingFgh[45] ^= 0x01u; + std::copy_n(fgh.begin() + static_cast(markerOffset), 10, misleadingFgh.begin() + 48); + Require(OflParsesTo(misleadingFgh, 417, 0, 0, 0, 0)); auto vbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), 417); Require(OflParsesTo(vbri, 417, 0, 576, 0, 2)); vbri.insert(vbri.end(), fgh.begin(), fgh.end()); Require(OflParsesTo(vbri, 834, 0, 576 + 1105, 441000, 4)); Require(OflParsesTo(vbri, 480, 0, 576, 0, 2)); - auto protectedFrame = stereo; - protectedFrame[1] = 0xFA; - Require(OflThrows(protectedFrame)); - auto layer2 = stereo; - layer2[1] = 0xFD; - Require(OflThrows(layer2)); + for (const bool tagAfterCrc : {false, true}) { + const auto protectedStereo = ProtectedMp3Frame(stereo, tagAfterCrc); + Require(OflParsesTo(protectedStereo, 417, 10, 1152 + 576 + 529, 10000, 1)); + const auto protectedMono = ProtectedMp3Frame(mono, tagAfterCrc); + Require(OflParsesTo(protectedMono, 576, 10, 1152 + 576 + 529, 10000, 1)); + const auto protectedMpeg2 = ProtectedMp3Frame(mpeg2, tagAfterCrc); + Require(OflParsesTo(protectedMpeg2, 208, 20, 576 + 576 + 529, 10000, 1)); + } + auto protectedNonzeroSideInfo = ProtectedMp3Frame(stereo, true); + protectedNonzeroSideInfo[8] = 0x00; + protectedNonzeroSideInfo[9] = 0x10; + std::move_backward(protectedNonzeroSideInfo.begin() + 38, protectedNonzeroSideInfo.end() - 2, protectedNonzeroSideInfo.end()); + protectedNonzeroSideInfo[38] = 0; + protectedNonzeroSideInfo[39] = 0; + Require(OflParsesTo(protectedNonzeroSideInfo, 417, 10, 1152 + 576 + 529, 10000, 1)); + const auto protectedFgh = ProtectedMp3Frame(fgh, true); + Require(OflParsesTo(protectedFgh, 417, 0, 1105, 441000, 3)); auto monoVbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), 417); monoVbri[3] = 0xC4; - Require(OflThrows(monoVbri)); + Require(OflParsesTo(monoVbri, 417, 0, 576, 0, 2)); + for (const std::uint8_t version : {0xF3u, 0xE3u}) { + for (const std::uint8_t mode : {0x04u, 0xC4u}) { + auto otherVbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), version == 0xF3 ? 208 : 417); + otherVbri[1] = version; + otherVbri[2] = 0x80; + otherVbri[3] = mode; + Require(OflParsesTo(otherVbri, static_cast(otherVbri.size()), 0, 576, 0, 2)); + } + } + const auto protectedVbri = ProtectedMp3Frame(monoVbri, false); + Require(OflParsesTo(protectedVbri, 417, 0, 576, 0, 2)); + auto monoVbriAndFgh = monoVbri; + monoVbriAndFgh.insert(monoVbriAndFgh.end(), protectedFgh.begin(), protectedFgh.end()); + Require(OflParsesTo(monoVbriAndFgh, 834, 0, 576 + 1105, 441000, 4)); +} + +void TestMp3MetadataBounds() { + const auto stereo = Mp3Frame(LAME_MPEG1_STEREO_VBR, sizeof(LAME_MPEG1_STEREO_VBR), 417); + const auto protectedStereo = ProtectedMp3Frame(stereo, true); + const auto protectedLame = ProtectedMp3Frame(stereo, false); + auto vbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), 417); + vbri[3] = 0xC4; + for (const auto& complete : {stereo, protectedStereo, protectedLame, vbri}) { + for (std::size_t length = 0; length <= complete.size(); ++length) { + const std::vector prefix(complete.begin(), complete.begin() + length); + AjmDecMp3ParseFrame frame{}; + const int result = sceAjmDecMp3ParseFrame(prefix.data(), static_cast(prefix.size()), 1, &frame); + Require(result == (length < 4 ? static_cast(0x80930005) : 0)); + if (length >= 4) Require(frame.frame_size == 417); + } + } + std::vector chain; + for (std::size_t i = 0; i < 4096; ++i) chain.insert(chain.end(), vbri.begin(), vbri.end()); + Require(OflParsesTo(chain, static_cast(chain.size()), 0, 576, 0, 2)); } struct GaplessDecode { @@ -780,7 +825,7 @@ void TestGetInfo(std::uint32_t context) { Require(sceAjmBatchJobGetInfo(&info, instance, &at9) == 0); Submit(context, info); Require(initResult[0] == 0); - Require(at9.result == 0 && at9.internalResult == 0 && at9.numChannels == 2 && at9.channelMask == 0x3 && at9.sampleRate == 48000 && at9.sampleEncoding == 0 && at9.reserved == 0); + Require(at9.result == 0 && at9.internalResult == 0 && at9.numChannels == 2 && at9.channelMask == 0x3 && at9.sampleRate == 48000 && at9.sampleEncoding == 0 && at9.bitrate == 1024 * 8 * 48000 / (4 * 256) && at9.reserved == 0); Require(sceAjmInstanceDestroy(context, instance) == 0); Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); @@ -796,6 +841,69 @@ void TestGetInfo(std::uint32_t context) { Require(sceAjmInstanceDestroy(context, instance) == 0); } +void TestBatchBounds(std::uint32_t context) { + constexpr int invalidParameter = static_cast(0x80930005); + std::vector storage(128); + AjmBatchInfo info{}; + Require(sceAjmBatchInitialize(storage.data(), storage.size(), &info) == 0); + const std::uint32_t parameters[3] = {2, 48000, 0}; + std::int32_t result[2] = {-1, -1}; + Require(sceAjmBatchJobInitialize(&info, 0, parameters, sizeof(parameters), result) == 0); + const auto completeBytes = info.offset; +#ifdef _WIN32 + SYSTEM_INFO system{}; + GetSystemInfo(&system); + const std::size_t pageSize = system.dwPageSize; + auto* pages = static_cast(VirtualAlloc(nullptr, 2 * pageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(pages != nullptr); + DWORD oldProtection = 0; + Require(VirtualProtect(pages + pageSize, pageSize, PAGE_NOACCESS, &oldProtection) != 0); +#else + const long hostPageSize = sysconf(_SC_PAGESIZE); + Require(hostPageSize > 0); + const std::size_t pageSize = static_cast(hostPageSize); + auto* pages = static_cast(mmap(nullptr, 2 * pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(pages != MAP_FAILED); + Require(mprotect(pages + pageSize, pageSize, PROT_NONE) == 0); +#endif + Require(completeBytes < pageSize); + for (std::size_t bytes = 1; bytes < completeBytes; ++bytes) { + auto* truncated = pages + pageSize - bytes; + std::memcpy(truncated, storage.data(), bytes); + AjmBatchInfo partial{truncated, bytes, bytes}; + std::uint32_t batch = 0xdeadbeef; + bool threw = false; + try { + sceAjmBatchStart(context, &partial, 0, nullptr, &batch); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw && batch == 0xdeadbeef && result[0] == -1 && result[1] == -1); + } + for (std::uint8_t value : {std::uint8_t{0}, std::uint8_t{0xff}}) { + std::fill(storage.begin(), storage.end(), value); + std::uint32_t batch = 0xdeadbeef; + bool threw = false; + try { + sceAjmBatchStart(context, &info, 0, nullptr, &batch); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw && batch == 0xdeadbeef && result[0] == -1 && result[1] == -1); + } + std::uint32_t batch = 0xdeadbeef; + AjmBatchInfo invalid{pages + pageSize, 1, 0}; + Require(sceAjmBatchStart(context, &invalid, 0, nullptr, &batch) == invalidParameter); + invalid = {nullptr, 1, 1}; + Require(sceAjmBatchStart(context, &invalid, 0, nullptr, &batch) == invalidParameter); + Require(batch == 0xdeadbeef); +#ifdef _WIN32 + Require(VirtualFree(pages, 0, MEM_RELEASE) != 0); +#else + Require(munmap(pages, 2 * pageSize) == 0); +#endif +} + void TestBatchWaitRelease(std::uint32_t context) { constexpr int invalidBatch = static_cast(0x80930004); std::vector batch(64); @@ -943,7 +1051,7 @@ std::vector DecodeAt9Superframe(std::uint32_t context, const std:: Require(sceAjmBatchJobRun(&info, instance, sidebandStream | runMultipleFrames, superframe.data(), superframe.size(), pcm.data(), pcm.size() * sizeof(std::int16_t), &decoded, sizeof(decoded)) == 0); Submit(context, info); Require(initResult[0] == 0); - Require(decoded.stream.result == 0 && static_cast(decoded.stream.inputConsumed) == superframe.size()); + Require(decoded.stream.result == 0x18 && static_cast(decoded.stream.inputConsumed) == superframe.size()); Require(static_cast(decoded.stream.outputWritten) == pcm.size() * sizeof(std::int16_t) && decoded.stream.totalDecodedSamples == 1024 && decoded.frames == 4); Require(sceAjmInstanceDestroy(context, instance) == 0); return pcm; @@ -1470,7 +1578,7 @@ ResampledStream Stream(std::uint32_t context, std::uint32_t instance, const std: Require(sceAjmBatchJobDecode(&info, instance, stream + offset, size - offset, pcm.data(), pcm.size() * sizeof(std::int16_t), &sideband) == 0); Submit(context, info); if (ratio > 0) Require(setResult[0] == 0); - Require(sideband.result == 0 || (offset == size && sideband.outputWritten == 0)); + Require(sideband.result == 0 || sideband.result == 0x8 || sideband.result == 0x10 || sideband.result == 0x18 || (offset == size && sideband.outputWritten == 0)); offset += static_cast(sideband.inputConsumed); if (offset < size && static_cast(sideband.outputWritten) < pcm.size() * sizeof(std::int16_t)) ++out.shortJobs; out.pcm.insert(out.pcm.end(), pcm.begin(), pcm.begin() + sideband.outputWritten / static_cast(sizeof(std::int16_t))); @@ -1690,7 +1798,7 @@ At9Run RunAt9Job(std::uint32_t context, std::uint32_t instance, std::uint64_t fl const std::size_t sidebandSize = (flags & runMultipleFrames) != 0 ? sizeof(decoded) : sizeof(decoded.stream); Require(sceAjmBatchJobRun(&info, instance, sidebandStream | flags, input, inputSize, pcm.data(), pcm.size() * sizeof(std::int16_t), &decoded, sidebandSize) == 0); Submit(context, info); - Require(decoded.stream.result == 0); + Require(decoded.stream.result == 0 || decoded.stream.result == 0x8 || decoded.stream.result == 0x18); return decoded; } @@ -1724,7 +1832,318 @@ void TestAt9RunDecodesWholeInput(std::uint32_t context) { RunControl(context, instance, CONTROL_START, &start, sizeof(start), result); Require(result[0] == 0); const auto run = RunAt9Job(context, instance, 0, stream.data(), stream.size()); - Require(static_cast(run.stream.inputConsumed) == stream.size() && run.stream.outputWritten == 1024 * 2); + Require(run.stream.result == 0x18 && static_cast(run.stream.inputConsumed) == stream.size() && run.stream.outputWritten == 1024 * 2); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestAt9FlagRules(std::uint32_t context) { + constexpr std::uint64_t runMultipleFrames = 1ull << 12; + constexpr std::uint64_t sidebandStream = 1ull << 47; + std::vector stream(2048, 0); + std::memcpy(stream.data(), AT9_MONO_SILENT_SUPERFRAME, sizeof(AT9_MONO_SILENT_SUPERFRAME)); + std::memcpy(stream.data() + 1024, AT9_MONO_SILENT_SUPERFRAME, sizeof(AT9_MONO_SILENT_SUPERFRAME)); + const At9Control start{{0, 0, 0}, {0xFE, 0x70, 0x1F, 0xF0}, 0}; + const auto run = [&](std::size_t inputSize, std::size_t roomSamples, At9Run& decoded) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + std::int32_t result[2] = {-1, -1}; + RunControl(context, instance, CONTROL_START, &start, sizeof(start), result); + Require(result[0] == 0); + std::vector batch(4096); + std::vector pcm(roomSamples + 1); + AjmBatchInfo info{}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobRun(&info, instance, sidebandStream | runMultipleFrames, stream.data(), inputSize, pcm.data(), roomSamples * sizeof(std::int16_t), &decoded, sizeof(decoded)) == 0); + Submit(context, info); + Require(sceAjmInstanceDestroy(context, instance) == 0); + }; + At9Run inputRanOut{{-1, -1, 0, 0, 0}, 0, 0}; + run(1024, 2048, inputRanOut); + Require(inputRanOut.stream.result == 0x8 && inputRanOut.stream.inputConsumed == 1024 && inputRanOut.stream.outputWritten == 1024 * 2 && inputRanOut.frames == 4); + At9Run roomRanOut{{-1, -1, 0, 0, 0}, 0, 0}; + run(2048, 1024, roomRanOut); + Require(roomRanOut.stream.result == 0x10 && roomRanOut.stream.inputConsumed == 1024 && roomRanOut.stream.outputWritten == 1024 * 2 && roomRanOut.frames == 4); + At9Run halfRoom{{-1, -1, 0, 0, 0}, 0, 0}; + run(1024, 512, halfRoom); + Require(halfRoom.stream.result == 0x10 && halfRoom.stream.inputConsumed > 0 && halfRoom.stream.inputConsumed < 1024 && halfRoom.stream.outputWritten == 512 * 2 && halfRoom.frames == 2); + At9Run noRoom{{-1, -1, 0, 0, 0}, 0, 0}; + run(1024, 0, noRoom); + Require(noRoom.stream.result == 0x10 && noRoom.stream.inputConsumed == 0 && noRoom.stream.outputWritten == 0 && noRoom.frames == 0); + At9Run both{{-1, -1, 0, 0, 0}, 0, 0}; + run(1024, 1024, both); + Require(both.stream.result == 0x18 && both.stream.inputConsumed == 1024 && both.stream.outputWritten == 1024 * 2 && both.frames == 4); +} + +int StartBuffer(std::uint32_t context, const void* batch, std::size_t size) { + std::uint32_t id = 0; + AjmBatchError error{}; + const int started = sceAjmBatchStartBuffer(context, static_cast(batch), static_cast(size), 0, &error, &id); + if (started == 0) Require(sceAjmBatchWait(context, id, 0, &error) == 0); + return started; +} + +std::uint32_t BatchWord(const std::vector& batch, std::size_t offset) { + std::uint32_t word = 0; + std::memcpy(&word, batch.data() + offset, sizeof(word)); + return word; +} + +void TestConsoleBufferJobs(std::uint32_t context) { + constexpr std::uint64_t version = 1; + constexpr std::uint64_t runMultipleFrames = 1ull << 12; + constexpr std::uint64_t sidebandStream = 1ull << 47; + std::vector stream(2048, 0); + std::memcpy(stream.data(), AT9_MONO_SILENT_SUPERFRAME, sizeof(AT9_MONO_SILENT_SUPERFRAME)); + std::memcpy(stream.data() + 1024, AT9_MONO_SILENT_SUPERFRAME, sizeof(AT9_MONO_SILENT_SUPERFRAME)); + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + const At9Control start{{0, 0, 0}, {0xFE, 0x70, 0x1F, 0xF0}, 0}; + std::int32_t result[2] = {-1, -1}; + std::vector pcm(2048, 0x55); + At9Run single{{-1, -1, 0, 0, 0}, 0, 0}; + At9Run split{{-1, -1, 0, 0, 0}, 0, 0}; + const AjmBuffer inputs[2] = {{stream.data() + 1024, 300}, {stream.data() + 1324, 724}}; + const AjmBuffer outputs[2] = {{pcm.data() + 1024, 600 * 2}, {pcm.data() + 1624, 424 * 2}}; + std::vector batch(4096, 0xCD); + + void* at = sceAjmBatchJobControlBufferRa(batch.data(), instance, version | CONTROL_START, &start, sizeof(start), result, sizeof(result), &context); + Require(at == batch.data() + 64); + Require(BatchWord(batch, 0) == instance << 6 && BatchWord(batch, 4) == 56); + Require(BatchWord(batch, 8) == 6 && BatchWord(batch, 24) == 2 && BatchWord(batch, 28) == sizeof(start)); + Require(BatchWord(batch, 40) == (3u | (0x2000u << 6)) && BatchWord(batch, 44) == 0x6001 && BatchWord(batch, 48) == 18); + at = sceAjmBatchJobRunBufferRa(at, instance, version | sidebandStream | runMultipleFrames, stream.data(), 1024, pcm.data(), 1024 * 2, &single, sizeof(single), nullptr); + Require(at == batch.data() + 128); + Require(BatchWord(batch, 68) == 56 && BatchWord(batch, 72) == 1 && BatchWord(batch, 88) == (4u | (0x8000u << 6)) && BatchWord(batch, 96) == 17 && BatchWord(batch, 112) == 18); + at = sceAjmBatchJobRunSplitBufferRa(at, instance, version | sidebandStream | runMultipleFrames, inputs, 2, outputs, 2, &split, sizeof(split), nullptr); + Require(at == batch.data() + 224 && BatchWord(batch, 132) == 88); + Require(StartBuffer(context, batch.data(), static_cast(at) - batch.data()) == 0); + + Require(result[0] == 0 && result[1] == 0); + constexpr std::int32_t exactFit = 0x18; + Require(single.stream.result == exactFit && single.stream.inputConsumed == 1024 && single.stream.outputWritten == 1024 * 2 && single.frames == 4 && single.stream.totalDecodedSamples == 1024); + Require(split.stream.result == exactFit && split.stream.inputConsumed == 1024 && split.stream.outputWritten == 1024 * 2 && split.frames == 4 && split.stream.totalDecodedSamples == 2048); + Require(std::all_of(pcm.begin(), pcm.end(), [](std::int16_t sample) { return sample == 0; })); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +class ConsoleBatch { +public: + void Word(std::uint32_t word) { words.push_back(word); } + void Chunk(std::uint32_t kind, std::uint32_t payload, std::uint32_t word) { + Word(kind | (payload << 6)); + Word(word); + } + void Buffer(std::uint32_t kind, const void* address, std::size_t size) { + Chunk(kind, 0, static_cast(size)); + const auto value = static_cast(reinterpret_cast(address)); + Word(static_cast(value)); + Word(static_cast(value >> 32)); + } + const std::uint32_t* Data() const { return words.data(); } + std::size_t Bytes() const { return words.size() * sizeof(std::uint32_t); } + +private: + std::vector words; +}; + +void TestConsoleHandWrittenJobs(std::uint32_t context) { + std::vector stream(1024, 0); + std::memcpy(stream.data(), AT9_MONO_SILENT_SUPERFRAME, sizeof(AT9_MONO_SILENT_SUPERFRAME)); + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + std::int32_t result[2] = {-1, -1}; + std::int32_t statistics[6] = {-1, -1, -1, -1, -1, -1}; + std::vector pcm(1024, 0x55); + At9Run decoded{{-1, -1, 0, 0, 0}, 0, 0}; + const std::uint8_t initialize[8] = {0xFE, 0x70, 0x1F, 0xF0, 0, 0, 0, 0}; + std::uint32_t inlined[2]; + std::memcpy(inlined, initialize, sizeof(initialize)); + + ConsoleBatch batch; + batch.Chunk(7, 0, sizeof(initialize)); + batch.Word(inlined[0]); + batch.Word(inlined[1]); + batch.Chunk(0, instance, 40); + batch.Buffer(2, nullptr, sizeof(initialize)); + batch.Chunk(3, 0, 0x6001); + batch.Buffer(18, result, sizeof(result)); + batch.Chunk(0, instance, 56); + batch.Buffer(1, stream.data(), stream.size()); + batch.Chunk(4, 0x8000, 0x1001); + batch.Buffer(17, pcm.data(), pcm.size() * sizeof(std::int16_t)); + batch.Buffer(18, &decoded, sizeof(decoded)); + batch.Chunk(0, 0x80000, 24); + batch.Chunk(3, 0, 0x8007); + batch.Buffer(18, statistics, sizeof(statistics)); + std::vector words(batch.Data(), batch.Data() + batch.Bytes() / sizeof(std::uint32_t)); + const auto inlineAddress = static_cast(reinterpret_cast(words.data() + 2)); + words[8] = static_cast(inlineAddress); + words[9] = static_cast(inlineAddress >> 32); + Require(StartBuffer(context, words.data(), words.size() * sizeof(std::uint32_t)) == 0); + Require(result[0] == 0 && result[1] == 0 && statistics[0] == 0 && statistics[1] == 0); + Require(decoded.stream.result == 0x18 && decoded.stream.inputConsumed == 1024 && decoded.stream.outputWritten == 1024 * 2 && decoded.frames == 4); + Require(std::all_of(pcm.begin(), pcm.end(), [](std::int16_t sample) { return sample == 0; })); + + constexpr int malformedBatch = static_cast(0x80930011); + Require(StartBuffer(context, words.data(), 12) == malformedBatch); + ConsoleBatch flagless; + flagless.Chunk(0, instance, 16); + flagless.Buffer(18, result, sizeof(result)); + Require(StartBuffer(context, flagless.Data(), flagless.Bytes()) == malformedBatch); + ConsoleBatch overrun; + overrun.Chunk(0, instance, 32); + overrun.Chunk(3, 0, 0x2001); + overrun.Buffer(18, result, sizeof(result)); + Require(StartBuffer(context, overrun.Data(), overrun.Bytes()) == malformedBatch); + ConsoleBatch unknown; + unknown.Chunk(0, instance, 16); + unknown.Chunk(3, 0, 0x2001); + unknown.Chunk(5, 0, 0); + bool unknownThrows = false; + try { + static_cast(StartBuffer(context, unknown.Data(), unknown.Bytes())); + } catch (const std::runtime_error&) { + unknownThrows = true; + } + Require(unknownThrows); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +int StartBufferRefused(std::uint32_t context, const void* batch, std::size_t size, AjmBatchError& error) { + std::uint32_t id = 0; + error = AjmBatchError{}; + return sceAjmBatchStartBuffer(context, static_cast(batch), static_cast(size), 0, &error, &id); +} + +void TestConsoleBufferEdges(std::uint32_t context) { + constexpr int malformedBatch = static_cast(0x80930011); + constexpr int invalidAddress = static_cast(0x80930016); + std::vector stream(1024, 0); + std::memcpy(stream.data(), AT9_MONO_SILENT_SUPERFRAME, sizeof(AT9_MONO_SILENT_SUPERFRAME)); + const std::uint8_t initialize[8] = {0xFE, 0x70, 0x1F, 0xF0, 0, 0, 0, 0}; + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + std::vector pcm(1024, 0x55); + std::int32_t result[2] = {-1, -1}; + const auto control = [&](ConsoleBatch& batch) { + result[0] = result[1] = -1; + batch.Chunk(0, instance, 40); + batch.Buffer(2, initialize, sizeof(initialize)); + batch.Chunk(3, 0, 0x6001); + batch.Buffer(18, result, sizeof(result)); + }; + + At9Run dropped{{-1, -1, 0, 0, 0}, 0, 0}; + ConsoleBatch zeroSizes; + control(zeroSizes); + zeroSizes.Chunk(0, instance, 104); + zeroSizes.Buffer(1, stream.data(), 0); + zeroSizes.Buffer(1, stream.data(), stream.size()); + zeroSizes.Chunk(4, 0x8000, 0x1001); + zeroSizes.Buffer(17, pcm.data(), 0); + zeroSizes.Buffer(17, pcm.data(), pcm.size() * sizeof(std::int16_t)); + zeroSizes.Buffer(17, nullptr, 0); + zeroSizes.Buffer(18, &dropped, sizeof(dropped)); + Require(StartBuffer(context, zeroSizes.Data(), zeroSizes.Bytes()) == 0); + Require(result[0] == 0 && dropped.stream.result == 0x18 && dropped.stream.inputConsumed == 1024 && dropped.stream.outputWritten == 1024 * 2 && dropped.frames == 4); + Require(std::all_of(pcm.begin(), pcm.end(), [](std::int16_t sample) { return sample == 0; })); + + At9Run noRoom{{-1, -1, 0, 0, 0}, 0, 0}; + std::fill(pcm.begin(), pcm.end(), std::int16_t{0x55}); + ConsoleBatch zeroRoom; + control(zeroRoom); + zeroRoom.Chunk(0, instance, 56); + zeroRoom.Buffer(1, stream.data(), stream.size()); + zeroRoom.Chunk(4, 0x8000, 0x1001); + zeroRoom.Buffer(17, pcm.data(), 0); + zeroRoom.Buffer(18, &noRoom, sizeof(noRoom)); + Require(StartBuffer(context, zeroRoom.Data(), zeroRoom.Bytes()) == 0); + Require(result[0] == 0 && noRoom.stream.result == 0x10 && noRoom.stream.inputConsumed == 0 && noRoom.stream.outputWritten == 0 && noRoom.frames == 0); + Require(std::all_of(pcm.begin(), pcm.end(), [](std::int16_t sample) { return sample == 0x55; })); + + const void* returnAddress = &stream; + for (const bool nullInput : {true, false}) { + At9Run untouched{{-1, -1, 0, 0, 0}, 0, 0}; + ConsoleBatch refused; + control(refused); + refused.Chunk(0, instance, 72); + refused.Buffer(6, returnAddress, 0); + refused.Buffer(1, nullInput ? nullptr : stream.data(), stream.size()); + refused.Chunk(4, 0x8000, 0x1001); + refused.Buffer(17, nullInput ? pcm.data() : nullptr, pcm.size() * sizeof(std::int16_t)); + refused.Buffer(18, &untouched, sizeof(untouched)); + AjmBatchError error{}; + Require(StartBufferRefused(context, refused.Data(), refused.Bytes(), error) == malformedBatch); + Require(error.error_code == invalidAddress && error.job_addr == reinterpret_cast(refused.Data()) + 48); + Require(error.cmd_offset == (nullInput ? 24u : 48u) && error.job_ra == returnAddress); + Require(result[0] == -1 && untouched.stream.result == -1); + } + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestConsoleEmptyBatch(std::uint32_t context) { + constexpr int invalidParameter = static_cast(0x80930005); + constexpr int invalidBatch = static_cast(0x80930004); + std::uint32_t id = 0; + AjmBatchError error{}; + Require(sceAjmBatchStartBuffer(context, nullptr, 0, 0, &error, &id) == invalidParameter); + Require(sceAjmBatchStartBuffer(context, nullptr, 16, 0, &error, &id) == invalidParameter); + const std::uint64_t words[2] = {0, 0}; + Require(sceAjmBatchStartBuffer(context, reinterpret_cast(words), 0, 0, &error, nullptr) == invalidParameter); + error.error_code = -1; + Require(sceAjmBatchStartBuffer(context, reinterpret_cast(words), 0, 0, &error, &id) == 0); + Require(error.error_code == 0 && id != 0 && sceAjmBatchWait(context, id, 0, &error) == 0); + Require(sceAjmBatchWait(context, id, 0, &error) == invalidBatch); +} + +void TestConsoleSingleFrameRun(std::uint32_t context) { + constexpr std::uint64_t version = 1; + constexpr std::uint64_t sidebandStream = 1ull << 47; + std::vector stream(1024, 0); + std::memcpy(stream.data(), AT9_MONO_SILENT_SUPERFRAME, sizeof(AT9_MONO_SILENT_SUPERFRAME)); + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + const At9Control start{{0, 0, 0}, {0xFE, 0x70, 0x1F, 0xF0}, 0}; + std::int32_t result[2] = {-1, -1}; + std::vector pcm(1024, 0x55); + DecodeSideband single{-1, -1, 0, 0, 0}; + std::vector batch(4096, 0xCD); + void* at = sceAjmBatchJobControlBufferRa(batch.data(), instance, version | CONTROL_START, &start, sizeof(start), result, sizeof(result), nullptr); + at = sceAjmBatchJobRunBufferRa(at, instance, version | sidebandStream, stream.data(), stream.size(), pcm.data(), pcm.size() * sizeof(std::int16_t), &single, sizeof(single), nullptr); + Require(StartBuffer(context, batch.data(), static_cast(at) - batch.data()) == 0); + Require(result[0] == 0 && single.result == 0 && single.inputConsumed > 0 && single.inputConsumed < 1024); + Require(single.outputWritten == 256 * 2 && single.totalDecodedSamples == 256); + Require(std::all_of(pcm.begin(), pcm.begin() + 256, [](std::int16_t sample) { return sample == 0; })); + Require(std::all_of(pcm.begin() + 256, pcm.end(), [](std::int16_t sample) { return sample == 0x55; })); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestConsoleStaleHeaderBits(std::uint32_t context) { + std::vector stream(1024, 0); + std::memcpy(stream.data(), AT9_MONO_SILENT_SUPERFRAME, sizeof(AT9_MONO_SILENT_SUPERFRAME)); + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + std::int32_t result[2] = {-1, -1}; + std::vector pcm(1024, 0x55); + At9Run decoded{{-1, -1, 0, 0, 0}, 0, 0}; + const std::uint8_t initialize[8] = {0xFE, 0x70, 0x1F, 0xF0, 0, 0, 0, 0}; + + ConsoleBatch batch; + batch.Chunk(0x8C000030u, instance, 56); + batch.Buffer(0xFFFFFFF6u, &context, 0); + batch.Buffer(0xFFFFFFE2u, initialize, sizeof(initialize)); + batch.Chunk(0x04000033u, 0, 0x6001); + batch.Buffer(0xFFFFFFF2u, result, sizeof(result)); + batch.Chunk(0x8C000020u, instance, 72); + batch.Buffer(0xFFFFFFF6u, &context, 0); + batch.Buffer(0xFFFFFFE1u, stream.data(), stream.size()); + batch.Chunk(0x04000034u, 0x8000, 0x1001); + batch.Buffer(0xFFFFFFF1u, pcm.data(), pcm.size() * sizeof(std::int16_t)); + batch.Buffer(0xFFFFFFF2u, &decoded, sizeof(decoded)); + Require(StartBuffer(context, batch.Data(), batch.Bytes()) == 0); + Require(result[0] == 0 && result[1] == 0); + Require(decoded.stream.result == 0x18 && decoded.stream.inputConsumed == 1024 && decoded.stream.outputWritten == 1024 * 2 && decoded.frames == 4); + Require(std::all_of(pcm.begin(), pcm.end(), [](std::int16_t sample) { return sample == 0; })); Require(sceAjmInstanceDestroy(context, instance) == 0); } @@ -1927,12 +2346,14 @@ int main() { bool nullBuffersThrow = false; try { sceAjmBatchJobRunSplit(&batchInfo, 0, 0, nullptr, 1, nullptr, 0, nullptr, 0); + } catch (const std::runtime_error&) { nullBuffersThrow = true; } Require(nullBuffersThrow && batchInfo.offset == 0); TestMp3ParseFrame(); TestMp3ParseOfl(); + TestMp3MetadataBounds(); TestMp3(context); TestOpus(context); TestOpus(context, 1); @@ -1956,6 +2377,7 @@ int main() { TestGaplessDecode(context); TestCodecInfo(context); TestGetInfo(context); + TestBatchBounds(context); TestBatchWaitRelease(context); TestInstanceHandleLifetime(context); TestBatchCancel(context); @@ -1977,6 +2399,14 @@ int main() { TestResampleAt9(context); TestAt9GaplessSegments(context); TestAt9RunDecodesWholeInput(context); + TestAt9FlagRules(context); TestPcmResample(context); + TestConsoleBufferJobs(context); + TestConsoleHandWrittenJobs(context); + TestConsoleBufferEdges(context); + TestConsoleEmptyBatch(context); + TestConsoleSingleFrameRun(context); + TestConsoleStaleHeaderBits(context); + Require(sceAjmDecWVorbisCreateHeaderPacket(nullptr) == static_cast(0x8A6E0001)); Require(sceAjmFinalize(context) == 0); } diff --git a/core/libs/tests/GuestAjmVibration.cpp b/core/libs/tests/GuestAjmVibration.cpp index 11351dcb6f..8157596435 100644 --- a/core/libs/tests/GuestAjmVibration.cpp +++ b/core/libs/tests/GuestAjmVibration.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAjm.native/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -8,19 +9,6 @@ #include #include -extern "C" { -int APS5_VABI sceAjmInitialize(std::int64_t, std::uint32_t*); -int APS5_VABI sceAjmFinalize(std::uint32_t); -int APS5_VABI sceAjmDecAt9ParseConfigData(const void*, AjmDecAt9ConfigDataInfo*); -int APS5_VABI sceAjmInstanceCreate(std::uint32_t, std::uint32_t, std::uint64_t, std::uint32_t*); -int APS5_VABI sceAjmInstanceDestroy(std::uint32_t, std::uint32_t); -int APS5_VABI sceAjmBatchInitialize(void*, std::size_t, AjmBatchInfo*); -int APS5_VABI sceAjmBatchJobInitialize(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*); -int APS5_VABI sceAjmBatchJobRun(AjmBatchInfo*, std::uint32_t, std::uint64_t, const void*, std::size_t, void*, std::size_t, void*, std::size_t); -int APS5_VABI sceAjmBatchStart(std::uint32_t, const AjmBatchInfo*, int, AjmBatchError*, std::uint32_t*); -int APS5_VABI sceAjmBatchWait(std::uint32_t, std::uint32_t, std::uint32_t, AjmBatchError*); -} - namespace { void Require(bool value, const char* message) { if (!value) throw std::runtime_error(message); @@ -110,7 +98,7 @@ std::vector Decode(std::uint32_t context, unsigned channels, unsigned encodin Require((stream.result & 2) != 0 && stream.consumed == 0 && stream.produced == 0, "invalid first-frame flag"); } const auto stream = run(packet, packet.size()); - Require(stream.result == 0 && stream.internalResult == 0, "decode vibration superframe"); + Require(stream.result == 0x18 && stream.internalResult == 0, "decode vibration superframe"); Require(stream.consumed == 128 && stream.produced == static_cast(pcm.size() * sizeof(T)), "consumed/produced accounting"); Require(stream.samples == static_cast(samples) && stream.frames == 4, "decoded sample/frame accounting"); Require(sceAjmInstanceDestroy(context, instance) == 0, "destroy instance"); diff --git a/core/libs/tests/GuestAjmWVorbis.cpp b/core/libs/tests/GuestAjmWVorbis.cpp new file mode 100644 index 0000000000..b017065e10 --- /dev/null +++ b/core/libs/tests/GuestAjmWVorbis.cpp @@ -0,0 +1,92 @@ +#include "prx/libSceAjm.native/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include +#include + +static_assert(sizeof(WVorbisHeaderPacketParameter) == 0x48); + +namespace { + +constexpr int InvalidParameter = static_cast(0x8A6E0001); +constexpr int InvalidSize = static_cast(0x8A6E0002); +constexpr int OutputTooSmall = static_cast(0x8A6E0003); +constexpr int InvalidSetup = static_cast(0x8A6E0004); + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +constexpr std::array Setup {5, 'v', 'o', 'r', 'b', 'i', 's', 9, 0, 0xAA, 0xBB, 0xCC}; + +WVorbisHeaderPacketParameter Valid(std::uint8_t* output, std::size_t outputSize) { + return {sizeof(WVorbisHeaderPacketParameter), 2, 48000, 16000, 8, 11, 0, Setup.data(), Setup.size(), output, outputSize, 0}; +} + +int Create(WVorbisHeaderPacketParameter param) { return sceAjmDecWVorbisCreateHeaderPacket(¶m); } + +void TestValidation() { + std::array output {}; + Require(sceAjmDecWVorbisCreateHeaderPacket(nullptr) == InvalidParameter, "a null parameter is refused"); + auto param = Valid(output.data(), output.size()); + param.size = 0x40; + Require(Create(param) == InvalidSize, "a wrong structure size is refused"); + param = Valid(output.data(), output.size()); + param.bytesPerSecond = -1; + Require(Create(param) == InvalidParameter, "a negative byte rate is refused"); + param = Valid(output.data(), output.size()); + param.shortBlockExponent = 5; + Require(Create(param) == InvalidParameter, "a block size below 64 is refused"); + param.shortBlockExponent = 38; + param.longBlockExponent = 38; + Require(Create(param) == 0, "block size exponents are taken modulo 32 as the console's shift does"); + param = Valid(output.data(), output.size()); + param.longBlockExponent = 7; + Require(Create(param) == InvalidParameter, "block size 1 below block size 0 is refused"); + param.longBlockExponent = 14; + Require(Create(param) == InvalidParameter, "a block size above 8192 is refused"); + param = Valid(output.data(), output.size()); + param.setupPacket = nullptr; + param.setupPacketSize = 0; + Require(Create(param) == InvalidParameter, "a null setup packet is refused before its size"); + param = Valid(output.data(), output.size()); + param.setupPacketSize = 8; + Require(Create(param) == InvalidSetup, "a setup packet below 9 bytes is refused"); + param = Valid(nullptr, 0); + Require(Create(param) == InvalidParameter, "a null output is refused before its size"); + param = Valid(output.data(), 9); + Require(Create(param) == OutputTooSmall, "an output below 10 bytes is refused"); + param = Valid(output.data(), 56 + Setup.size() - 1); + Require(Create(param) == OutputTooSmall, "an output that cannot hold the setup packet is refused"); +} + +void TestPacket() { + std::array output {}; + output.fill(0xEE); + auto param = Valid(output.data(), output.size()); + Require(sceAjmDecWVorbisCreateHeaderPacket(¶m) == 0, "a valid header packet is built"); + Require(param.outputSize == 56 + Setup.size(), "the written size covers the table, both headers and the setup packet"); + constexpr std::uint8_t Expected[] { + 0, 0, 30, 0, 16, 0, 9, 0, 3, 0, + 1, 'v', 'o', 'r', 'b', 'i', 's', 0, 0, 0, 0, 2, 0x80, 0xBB, 0, 0, 0, 0, 0, 0, 0x00, 0xF4, 0x01, 0x00, 0, 0, 0, 0, 0xB8, 1, + 3, 'v', 'o', 'r', 'b', 'i', 's', 0, 0, 0, 0, 0, 0, 0, 0, 1, + }; + static_assert(sizeof(Expected) == 56); + Require(std::memcmp(output.data(), Expected, sizeof(Expected)) == 0, "the table, identification and comment headers match"); + Require(std::memcmp(output.data() + 56, Setup.data(), Setup.size()) == 0, "the setup packet follows unchanged"); + Require(output[56 + Setup.size()] == 0xEE, "nothing is written past the packet"); +} + +} + +int main() { + TestValidation(); + TestPacket(); + std::puts("guest ajm wvorbis header packet: PASS"); +} diff --git a/core/libs/tests/GuestAllocatingStrings.cpp b/core/libs/tests/GuestAllocatingStrings.cpp index 8bf053914b..9bcaf27685 100644 --- a/core/libs/tests/GuestAllocatingStrings.cpp +++ b/core/libs/tests/GuestAllocatingStrings.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" @@ -8,15 +9,6 @@ #include #include -extern "C" { -char* APS5_VABI strndup_nid_postfix(const char*, std::size_t); -char* APS5_VABI strdup_nid_postfix(const char*); -int APS5_VABI asprintf_nid_postfix(char**, const char*, ...); -int APS5_VABI vasprintf_nid_postfix(char**, const char*, VaList*); -void APS5_VABI free_nid_postfix(void*); -int* APS5_VABI __error_nid_postfix(); -} - namespace { void Require(bool value) { diff --git a/core/libs/tests/GuestAllocatorFailures.cpp b/core/libs/tests/GuestAllocatorFailures.cpp index 5312249a50..f38e1a1e2d 100644 --- a/core/libs/tests/GuestAllocatorFailures.cpp +++ b/core/libs/tests/GuestAllocatorFailures.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -12,23 +13,14 @@ #include #endif -extern "C" { -void* APS5_VABI malloc_nid_postfix(std::size_t); -void* APS5_VABI calloc_nid_postfix(std::size_t, std::size_t); -void* APS5_VABI realloc_nid_postfix(void*, std::size_t); -void* APS5_VABI memalign_nid_postfix(std::size_t, std::size_t); -void* APS5_VABI aligned_alloc_nid_postfix(std::size_t, std::size_t); -int APS5_VABI posix_memalign_nid_postfix(void**, std::size_t, std::size_t); -void APS5_VABI free_nid_postfix(void*); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int NoMemory = 12; constexpr int InvalidArgument = 22; +constexpr int Untouched = 77; constexpr std::size_t Max = std::numeric_limits::max(); constexpr std::size_t HalfPlusOne = static_cast(std::numeric_limits::max()) + 1; +constexpr std::size_t LastPlainFailure = Max - 0x1008f; int failures = 0; @@ -50,7 +42,7 @@ void report(const char* what, const char* detail) { template void expectNull(const char* what, int expectedError, TCall call) { - *__error_nid_postfix() = 0; + *__error_nid_postfix() = Untouched; void* result = nullptr; try { result = call(); @@ -65,6 +57,7 @@ void expectNull(const char* what, int expectedError, TCall call) { if (result != nullptr) { report(what, "returned a block"); free_nid_postfix(result); + return; } if (*__error_nid_postfix() != expectedError) { @@ -95,27 +88,59 @@ int main() { void* api[10]{}; ApplicationHeapRegister_nid_no_patch(api); - expectNull("malloc(SIZE_MAX)", NoMemory, [] { return malloc_nid_postfix(Max); }); - expectNull("malloc(PTRDIFF_MAX + 1)", NoMemory, [] { return malloc_nid_postfix(HalfPlusOne); }); - expectNull("calloc(SIZE_MAX, 2)", NoMemory, [] { return calloc_nid_postfix(Max, 2); }); - expectNull("calloc(2^33, 2^33)", NoMemory, [] { return calloc_nid_postfix(std::size_t{1} << 33, std::size_t{1} << 33); }); - expectNull("calloc(PTRDIFF_MAX + 1, 1)", NoMemory, [] { return calloc_nid_postfix(HalfPlusOne, 1); }); - expectNull("realloc(NULL, SIZE_MAX)", NoMemory, [] { return realloc_nid_postfix(nullptr, Max); }); - expectNull("aligned_alloc(64, SIZE_MAX)", NoMemory, [] { return aligned_alloc_nid_postfix(64, Max); }); - expectNull("memalign(64, PTRDIFF_MAX + 1)", NoMemory, [] { return memalign_nid_postfix(64, HalfPlusOne); }); - expectNull("aligned_alloc(24, 64)", InvalidArgument, [] { return aligned_alloc_nid_postfix(24, 64); }); - expectNull("aligned_alloc(0, 64)", InvalidArgument, [] { return aligned_alloc_nid_postfix(0, 64); }); - expectNull("memalign(24, 64)", InvalidArgument, [] { return memalign_nid_postfix(24, 64); }); + expectNull("malloc(SIZE_MAX)", Untouched, [] { return malloc_nid_postfix(Max); }); + expectNull("malloc(SIZE_MAX - 0x1008e)", Untouched, [] { return malloc_nid_postfix(LastPlainFailure + 1); }); + expectNull("malloc(SIZE_MAX - 0x1008f)", InvalidArgument, [] { return malloc_nid_postfix(LastPlainFailure); }); + expectNull("malloc(PTRDIFF_MAX + 1)", InvalidArgument, [] { return malloc_nid_postfix(HalfPlusOne); }); + expectNull("calloc(SIZE_MAX, 2)", Untouched, [] { return calloc_nid_postfix(Max, 2); }); + expectNull("calloc(2^33, 2^33)", Untouched, [] { return calloc_nid_postfix(std::size_t{1} << 33, std::size_t{1} << 33); }); + expectNull("calloc(1, SIZE_MAX - 0x1008e)", Untouched, [] { return calloc_nid_postfix(1, LastPlainFailure + 1); }); + expectNull("calloc(PTRDIFF_MAX + 1, 1)", InvalidArgument, [] { return calloc_nid_postfix(HalfPlusOne, 1); }); + expectNull("realloc(NULL, SIZE_MAX)", Untouched, [] { return realloc_nid_postfix(nullptr, Max); }); + expectNull("realloc(NULL, PTRDIFF_MAX + 1)", InvalidArgument, [] { return realloc_nid_postfix(nullptr, HalfPlusOne); }); + expectNull("aligned_alloc(64, SIZE_MAX)", Untouched, [] { return aligned_alloc_nid_postfix(64, Max); }); + expectNull("aligned_alloc(16, PTRDIFF_MAX + 1)", InvalidArgument, [] { return aligned_alloc_nid_postfix(16, HalfPlusOne); }); + expectNull("memalign(64, PTRDIFF_MAX + 1)", InvalidArgument, [] { return memalign_nid_postfix(64, HalfPlusOne); }); + expectNull("memalign(4096, SIZE_MAX - 0x110af)", InvalidArgument, [] { return memalign_nid_postfix(4096, Max - 0x110af); }); + expectNull("memalign(4096, SIZE_MAX - 0x110ae)", Untouched, [] { return memalign_nid_postfix(4096, Max - 0x110ae); }); + expectNull("memalign(100, SIZE_MAX - 0x1012f)", InvalidArgument, [] { return memalign_nid_postfix(100, Max - 0x1012f); }); + expectNull("memalign(100, SIZE_MAX - 0x1012e)", Untouched, [] { return memalign_nid_postfix(100, Max - 0x1012e); }); + expectNull("memalign(3, SIZE_MAX - 0x1008f)", InvalidArgument, [] { return memalign_nid_postfix(3, LastPlainFailure); }); + expectNull("memalign(3, SIZE_MAX - 0x1008e)", Untouched, [] { return memalign_nid_postfix(3, LastPlainFailure + 1); }); + expectNull("memalign(2^63, 16)", Untouched, [] { return memalign_nid_postfix(HalfPlusOne, 16); }); + expectNull("memalign(SIZE_MAX, 16)", Untouched, [] { return memalign_nid_postfix(Max, 16); }); + expectNull("memalign(2^62 + 1, 16)", InvalidArgument, [] { return memalign_nid_postfix((std::size_t{1} << 62) + 1, 16); }); + expectNull("aligned_alloc(24, 64)", Untouched, [] { return aligned_alloc_nid_postfix(24, 64); }); + expectNull("aligned_alloc(0, 64)", Untouched, [] { return aligned_alloc_nid_postfix(0, 64); }); + expectNull("aligned_alloc(2^63, 16)", Untouched, [] { return aligned_alloc_nid_postfix(HalfPlusOne, 16); }); + + struct RoundedAlignment { + std::size_t requested; + std::size_t guaranteed; + }; + for (const RoundedAlignment& alignment : {RoundedAlignment{0, 32}, RoundedAlignment{3, 32}, RoundedAlignment{24, 32}, RoundedAlignment{48, 64}, + RoundedAlignment{100, 128}, RoundedAlignment{3000, 4096}}) { + void* aligned = expectBlock("memalign with an alignment that is not a power of two", [&alignment] { return memalign_nid_postfix(alignment.requested, 40); }); + if (aligned != nullptr && reinterpret_cast(aligned) % alignment.guaranteed != 0) report("memalign", "did not round the alignment up to a power of two"); + free_nid_postfix(aligned); + + } auto* block = static_cast(expectBlock("malloc(40)", [] { return malloc_nid_postfix(40); })); if (block != nullptr) { std::memset(block, 0x6b, 40); - expectNull("realloc(block, SIZE_MAX)", NoMemory, [block] { return realloc_nid_postfix(block, Max); }); - expectNull("realloc(block, PTRDIFF_MAX + 1)", NoMemory, [block] { return realloc_nid_postfix(block, HalfPlusOne); }); + expectNull("realloc(block, SIZE_MAX)", Untouched, [block] { return realloc_nid_postfix(block, Max); }); + expectNull("realloc(block, PTRDIFF_MAX + 1)", InvalidArgument, [block] { return realloc_nid_postfix(block, HalfPlusOne); }); for (unsigned index = 0; index < 40; ++index) { if (block[index] != 0x6b) report("realloc failure", "damaged the original block"); } - free_nid_postfix(block); + void* emptied = expectBlock("realloc(block, 0)", [block] { return realloc_nid_postfix(block, 0); }); + if (emptied != block) report("realloc(block, 0)", "did not return the same block"); + if (emptied != nullptr && malloc_usable_size_nid_postfix(emptied) != 0) report("realloc(block, 0)", "left usable bytes in the block"); + void* regrown = expectBlock("realloc(emptied, 64)", [emptied] { return realloc_nid_postfix(emptied, 64); }); + if (regrown != nullptr && malloc_usable_size_nid_postfix(regrown) != 64) report("realloc(emptied, 64)", "did not hold 64 bytes"); + free_nid_postfix(regrown); + } #ifdef ANYPS5_ENABLE_ASAN @@ -153,9 +178,10 @@ int main() { } if (huge != nullptr) { free_nid_postfix(huge); + committed = true; - } else if (*__error_nid_postfix() != NoMemory) { - report("malloc(300 GiB)", "returned NULL without ENOMEM"); + } else if (*__error_nid_postfix() != InvalidArgument) { + report("malloc(300 GiB)", "returned NULL without EINVAL"); } else { ++failedCommits; } @@ -177,7 +203,7 @@ int main() { std::fputs("skipped: the host can commit 300 GiB, so the failed commit and arena release checks did not run\n", stderr); #endif } else { - if (failedCommits != 10) report("failed heap commits", "did not return ENOMEM for all ten attempts"); + if (failedCommits != 10) report("failed heap commits", "did not return EINVAL for all ten attempts"); void* after = nullptr; try { after = GuestArena::GuestArenaAllocate_nid_postfix(probeBytes, 0x4000); @@ -192,18 +218,35 @@ int main() { void* small = malloc_nid_postfix(100); if (small == nullptr) report("malloc(100) after a failed huge malloc", "returned NULL"); free_nid_postfix(small); + void* large = malloc_nid_postfix(std::size_t{1} << 20); if (large == nullptr) report("malloc(1 MiB) after a failed huge malloc", "returned NULL"); free_nid_postfix(large); + } #endif - void* untouched = &failures; - *__error_nid_postfix() = 77; - if (posix_memalign_nid_postfix(&untouched, HalfPlusOne, 8) != NoMemory) report("posix_memalign(PTRDIFF_MAX + 1)", "did not return ENOMEM"); - if (posix_memalign_nid_postfix(&untouched, 64, HalfPlusOne) != NoMemory) report("posix_memalign(64, PTRDIFF_MAX + 1)", "did not return ENOMEM"); - if (posix_memalign_nid_postfix(&untouched, 64, Max) != NoMemory) report("posix_memalign(64, SIZE_MAX)", "did not return ENOMEM"); - if (untouched != &failures || *__error_nid_postfix() != 77) report("posix_memalign failure", "touched the output or errno"); + struct PosixCase { + const char* what; + std::size_t alignment; + std::size_t size; + int result; + int error; + }; + for (const PosixCase& check : {PosixCase{"posix_memalign(PTRDIFF_MAX + 1, 8)", HalfPlusOne, 8, NoMemory, Untouched}, + PosixCase{"posix_memalign(64, PTRDIFF_MAX + 1)", 64, HalfPlusOne, NoMemory, InvalidArgument}, + PosixCase{"posix_memalign(64, SIZE_MAX)", 64, Max, NoMemory, Untouched}, + PosixCase{"posix_memalign(64, SIZE_MAX - 0x100ef)", 64, Max - 0x100ef, NoMemory, InvalidArgument}, + PosixCase{"posix_memalign(64, SIZE_MAX - 0x100ee)", 64, Max - 0x100ee, NoMemory, Untouched}, + PosixCase{"posix_memalign(4, 8)", 4, 8, InvalidArgument, Untouched}, PosixCase{"posix_memalign(24, 8)", 24, 8, InvalidArgument, Untouched}, + PosixCase{"posix_memalign(PTRDIFF_MAX + 2, 8)", HalfPlusOne + 1, 8, InvalidArgument, Untouched}}) { + void* untouched = &failures; + *__error_nid_postfix() = Untouched; + const int result = posix_memalign_nid_postfix(&untouched, check.alignment, check.size); + if (result != check.result) report(check.what, "returned the wrong error number"); + if (*__error_nid_postfix() != check.error) report(check.what, "left the wrong errno"); + if (untouched != &failures) report(check.what, "touched the output"); + } free_nid_postfix(expectBlock("calloc(3, 5)", [] { return calloc_nid_postfix(3, 5); })); free_nid_postfix(expectBlock("calloc(0, 5)", [] { return calloc_nid_postfix(0, 5); })); diff --git a/core/libs/tests/GuestAmmPlacement.cpp b/core/libs/tests/GuestAmmPlacement.cpp index db65b897bf..9c05f42929 100644 --- a/core/libs/tests/GuestAmmPlacement.cpp +++ b/core/libs/tests/GuestAmmPlacement.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAmpr/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestArena.hpp" #include @@ -5,10 +6,6 @@ #include #include -extern "C" { -int APS5_VABI sceAmprAmmGetVirtualAddressRanges(std::uint64_t*, std::uint64_t*, std::uint64_t*, std::uint64_t*); -} - static void Require(bool condition, const char* message) { if (!condition) throw std::runtime_error(message); } diff --git a/core/libs/tests/GuestAmpr.cpp b/core/libs/tests/GuestAmpr.cpp index ca17fc2c23..7b5bc20439 100644 --- a/core/libs/tests/GuestAmpr.cpp +++ b/core/libs/tests/GuestAmpr.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceAmpr/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestArena.hpp" @@ -20,132 +23,6 @@ #include #endif -extern "C" { -int APS5_VABI sceAmprCommandBufferConstructor(Apr::CommandBufferObject*); -int APS5_VABI sceAmprAprCommandBufferConstructor(Apr::CommandBufferObject*, std::uint64_t*, std::uint64_t*); -int APS5_VABI sceAmprCommandBufferSetBuffer(Apr::CommandBufferObject*, void*, std::uint32_t); -int APS5_VABI sceAmprCommandBufferReset(Apr::CommandBufferObject*); -void* APS5_VABI sceAmprCommandBufferClearBuffer(Apr::CommandBufferObject*); -std::uint32_t APS5_VABI sceAmprCommandBufferGetCurrentOffset(const Apr::CommandBufferObject*); -std::uint32_t APS5_VABI sceAmprCommandBufferGetNumCommands(const Apr::CommandBufferObject*); -int APS5_VABI sceAmprCommandBufferWriteAddressOnCompletion(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint64_t); -int APS5_VABI sceAmprCommandBufferPushMarker(Apr::CommandBufferObject*, const char*); -int APS5_VABI sceAmprCommandBufferPushMarkerWithColor(Apr::CommandBufferObject*, const char*, std::uint32_t); -int APS5_VABI sceAmprCommandBufferPopMarker(Apr::CommandBufferObject*); -int APS5_VABI sceAmprCommandBufferSetMarker(Apr::CommandBufferObject*, const char*); -int APS5_VABI sceAmprCommandBufferSetMarkerWithColor(Apr::CommandBufferObject*, const char*, const std::uint32_t*); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizePushMarker(const char*); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizePushMarkerWithColor(const char*, std::uint32_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizePopMarker(); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeSetMarker(const char*); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeSetMarkerWithColor(const char*, std::uint32_t); -int APS5_VABI sceKernelAprSubmitCommandBuffer(const Apr::CommandBufferObject*, std::uint32_t); -int APS5_VABI sceKernelAprSubmitCommandBufferAndGetId(const Apr::CommandBufferObject*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceKernelAprWaitCommandBuffer(std::uint32_t); -int APS5_VABI sceAmprCommandBufferWaitOnAddress(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint64_t, std::uint8_t, std::uint8_t); -int APS5_VABI sceAmprCommandBufferWaitOnCounter(Apr::CommandBufferObject*, std::uint8_t, std::uint32_t, std::uint8_t, std::uint8_t); -int APS5_VABI sceAmprCommandBufferWriteCounterOnCompletion(Apr::CommandBufferObject*, std::uint8_t, std::uint32_t); -int APS5_VABI sceAmprCommandBufferWriteAddressFromTimeCounterOnCompletion(Apr::CommandBufferObject*, volatile std::uint64_t*); -int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterOnCompletion(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint8_t); -int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterPairOnCompletion(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint8_t); -int APS5_VABI sceAmprCommandBufferWriteKernelEventQueueOnCompletion(Apr::CommandBufferObject*, std::uint64_t, std::int32_t, std::uint64_t); -int APS5_VABI sceAmprCommandBufferNop(Apr::CommandBufferObject*, std::uint32_t); -int APS5_VABI sceAmprCommandBufferNopWithData(Apr::CommandBufferObject*, std::uint32_t, const std::uint32_t*); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeNop(std::uint32_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeNopWithData(std::uint32_t); -int APS5_VABI sceAmprCommandBufferWaitOnAddress_04_00(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint64_t, std::uint8_t, std::uint8_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnAddress_04_00(volatile std::uint64_t*, std::uint64_t, std::uint8_t, std::uint8_t); -int APS5_VABI sceAmprCommandBufferWriteAddressFromTimeCounter_04_00(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint64_t); -int APS5_VABI sceAmprCommandBufferWriteAddressFromCounter_04_00(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint8_t, std::uint64_t); -int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterPair_04_00(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint8_t, std::uint64_t); -int APS5_VABI sceAmprCommandBufferWriteKernelEventQueue_04_00(Apr::CommandBufferObject*, std::uint64_t, std::int32_t, std::uint64_t, std::uint64_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromTimeCounter_04_00(volatile std::uint64_t*); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounter_04_00(volatile std::uint64_t*, std::uint8_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounterPair_04_00(volatile std::uint64_t*, std::uint8_t); -int APS5_VABI sceAmprCommandBufferWaitOnCounter_04_00(Apr::CommandBufferObject*, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t); -int APS5_VABI sceAmprCommandBufferWriteCounter_04_00(Apr::CommandBufferObject*, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t, std::uint8_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnCounter_04_00(std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteCounter_04_00(std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t); -int APS5_VABI sceAmprCommandBufferConstructNop(Apr::CommandBufferObject*, std::int16_t, const void*, std::uint32_t, const std::uint32_t*); -int APS5_VABI sceAmprCommandBufferConstructMarker(Apr::CommandBufferObject*, std::uint32_t, const char*, const std::uint32_t*); -int APS5_VABI sceKernelAprResolveFilepathsToIds(const char**, std::uint32_t, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceKernelAprResolveFilepathsToIdsAndFileSizes(const char**, std::uint32_t, std::uint32_t*, std::uint64_t*, std::uint32_t*); -int APS5_VABI sceKernelAprResolveFilepathsToIdsAndFileSizesForEach(const char**, std::uint32_t, std::uint32_t*, std::uint64_t*, int*); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceAmprAprCommandBufferReadFile(Apr::CommandBufferObject*, std::uint64_t*, std::uint64_t*, std::uint32_t, void*, std::uint64_t, std::uint64_t); -std::uint32_t APS5_VABI sceAmprMeasureCommandSizeReadFile(); -int APS5_VABI sceAmprAprCommandBufferReadFileGather(Apr::CommandBufferObject*, std::uint64_t*, std::uint64_t*, std::uint64_t, std::uint64_t); -int APS5_VABI sceAmprAprCommandBufferReadFileScatter(Apr::CommandBufferObject*, std::uint64_t*, std::uint64_t*, void*, std::uint64_t); -int APS5_VABI sceAmprAprCommandBufferReadFileGatherScatter(Apr::CommandBufferObject*, std::uint64_t*, std::uint64_t*, void*, std::uint64_t, std::uint64_t); -int APS5_VABI sceAmprAprCommandBufferResetGatherScatterState(Apr::CommandBufferObject*); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileGather(std::uint64_t, std::uint64_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileScatter(void*, std::uint64_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileGatherScatter(void*, std::uint64_t, std::uint64_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeResetGatherScatterState(); -std::size_t APS5_VABI sceKernelGetDirectMemorySize(); -int APS5_VABI sceAmprAmmGetUsageStatsData(void*); -int APS5_VABI sceAmprAmmCommandBufferConstructor(Apr::CommandBufferObject*); -int APS5_VABI sceAmprAmmCommandBufferMap(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -int APS5_VABI sceAmprAmmCommandBufferMapWithGpuMaskId(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::uint8_t); -int APS5_VABI sceAmprAmmCommandBufferMapDirect(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -int APS5_VABI sceAmprAmmCommandBufferMapDirectWithGpuMaskId(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::uint8_t); -int APS5_VABI sceAmprAmmCommandBufferUnmap(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMap(std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMapWithGpuMaskId(std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::uint8_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMapDirect(std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMapDirectWithGpuMaskId(std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::uint8_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeUnmap(std::uint64_t, std::uint64_t); -int APS5_VABI sceAmprAmmGiveDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceAmprAmmGetVirtualAddressRanges(std::uint64_t*, std::uint64_t*, std::uint64_t*, std::uint64_t*); -int APS5_VABI sceAmprAmmSubmitCommandBuffer(void*, std::uint32_t, std::uint32_t); -int APS5_VABI sceAmprAmmSubmitCommandBuffer2(void*, std::uint32_t, std::uint32_t, std::uint64_t*, std::uint32_t*); -int APS5_VABI sceAmprAmmSubmitCommandBuffer3(void*, std::uint32_t, std::uint32_t, std::uint32_t*); -int APS5_VABI sceAmprAmmWaitCommandBufferCompletion(std::uint32_t); -int APS5_VABI sceAmprAprCommandBufferMapBegin(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -int APS5_VABI sceAmprAprCommandBufferMapDirectBegin(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -int APS5_VABI sceAmprAprCommandBufferMapEnd(Apr::CommandBufferObject*); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeMapBegin(std::uint64_t, std::uint64_t, std::uint32_t, std::uint32_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeMapDirectBegin(std::uint64_t, std::uint64_t, std::uint64_t, std::uint32_t, std::uint32_t); -std::uint64_t APS5_VABI sceAmprMeasureCommandSizeMapEnd(); -int APS5_VABI sceKernelQueryMemoryProtection(void*, void**, void**, int*); -int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t); -int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int APS5_VABI sceAmprAmmCommandBufferMapAsPrt(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t); -int APS5_VABI sceAmprAmmCommandBufferAllocatePaForPrt(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -int APS5_VABI sceAmprAmmCommandBufferRemapIntoPrt(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::uint32_t); -int APS5_VABI sceKernelWriteModifyMtypeProtectCommand(void*, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t*); -int APS5_VABI sceKernelWriteRemapIntoPrtCommand(std::uint32_t*, std::uint64_t, std::uint64_t, std::uint64_t, int, int, std::uint64_t*); -int APS5_VABI sceAmprAmmCommandBufferUnmapToPrt(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMapAsPrt(std::uint64_t, std::uint64_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeAllocatePaForPrt(std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -int APS5_VABI sceAmprAmmCommandBufferRemap(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t); -int APS5_VABI sceAmprAmmCommandBufferRemapWithGpuMaskId(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::uint8_t); -int APS5_VABI sceAmprAmmCommandBufferMultiMap(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t); -int APS5_VABI sceAmprAmmCommandBufferMultiMapWithGpuMaskId(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::uint8_t); -int APS5_VABI sceAmprAmmCommandBufferModifyProtect(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -int APS5_VABI sceAmprAmmCommandBufferModifyProtectWithGpuMaskId(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::uint8_t); -int APS5_VABI sceAmprAmmCommandBufferModifyMtypeProtect(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::int32_t); -int APS5_VABI sceAmprAmmCommandBufferModifyMtypeProtectWithGpuMaskId(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::int32_t, std::uint8_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeRemap(std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeRemapWithGpuMaskId(std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::uint8_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMultiMap(std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMultiMapWithGpuMaskId(std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::uint8_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeModifyProtect(std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeModifyProtectWithGpuMaskId(std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::uint8_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeModifyMtypeProtect(std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::int32_t); -std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeModifyMtypeProtectWithGpuMaskId(std::uint64_t, std::uint64_t, std::int32_t, std::int32_t, std::int32_t, std::uint8_t); -int APS5_VABI sceKernelCreateEqueue(KernelEqueue*, const char*); -int APS5_VABI sceKernelDeleteEqueue(KernelEqueue); -int APS5_VABI sceKernelWaitEqueue(KernelEqueue, KernelEvent*, int, int*, const KernelUseconds*); -int APS5_VABI sceKernelAddAmprEvent(KernelEqueue, int, void*); -int APS5_VABI sceKernelDeleteAmprEvent(KernelEqueue, int); -int APS5_VABI sceKernelGetEventFilter(const KernelEvent*); -std::uintptr_t APS5_VABI sceKernelGetEventId(const KernelEvent*); -std::intptr_t APS5_VABI sceKernelGetEventData(const KernelEvent*); -void* APS5_VABI sceKernelGetEventUserData(const KernelEvent*); -} - static void RequireAt(bool value, int line) { if (value) return; std::fprintf(stderr, "GuestAmpr.cpp:%d: requirement failed\n", line); @@ -470,6 +347,10 @@ void TestNops() { RequireAppended(recorder, offset, commands, Apr::Opcode::Nop, sceAmprMeasureCommandSizeNopWithData(4)); Require(std::memcmp(recorder.memory.data() + offset + sizeof(Apr::CommandHeader), data, sizeof(data)) == 0); Require(sceAmprCommandBufferNopWithData(&recorder.buffer, 0, nullptr) == 0); + const auto emptyPayloadOffset = recorder.Offset(); + std::memset(recorder.memory.data() + emptyPayloadOffset, 0x5A, sceAmprMeasureCommandSizeNopWithData(4)); + Require(sceAmprCommandBufferNopWithData(&recorder.buffer, 3, nullptr) == 0); + for (std::size_t index = 0; index < 12; ++index) Require(recorder.memory[emptyPayloadOffset + sizeof(Apr::CommandHeader) + index] == 0x5A); Require(sceKernelAprSubmitCommandBuffer(&recorder.buffer, 0) == 0); const auto rejected = static_cast(static_cast(invalidArgument)); @@ -578,6 +459,10 @@ void TestVersionedCounters() { void TestClearBuffer() { Recorder recorder; + Require(sceAmprMeasureCommandSizeWriteAddressOnCompletion() == sizeof(Apr::WriteAddressCommand)); + Require(sceAmprMeasureCommandSizeWriteCounterOnCompletion() == sizeof(Apr::WriteCounterCommand)); + Require(sceAmprMeasureCommandSizeWriteKernelEventQueueOnCompletion() == sizeof(Apr::WriteKernelEventQueueCommand)); + Require(sceAmprCommandBufferGetBufferBaseAddress(&recorder.buffer) == recorder.buffer.base); Require(sceAmprCommandBufferNop(&recorder.buffer, 1) == 0); Require(recorder.Offset() != 0 && recorder.Commands() == 1); Require(sceAmprCommandBufferClearBuffer(&recorder.buffer) == recorder.memory.data()); @@ -737,6 +622,7 @@ void TestReadAtUnseekableOffsetThrows() { bool rejected = false; try { sceKernelAprSubmitCommandBuffer(&buffer, 0); + } catch (const std::runtime_error& error) { rejected = std::string(error.what()).find("returned 0 of 4 bytes") != std::string::npos; } @@ -774,6 +660,7 @@ void TestMalformedCommandSizes() { bool rejected = false; try { sceKernelAprSubmitCommandBuffer(&buffer, 0); + } catch (const std::runtime_error& error) { rejected = std::string(error.what()).starts_with(expected); } @@ -793,6 +680,7 @@ void TestMalformedCommandSizes() { bool rejected = false; try { sceKernelAprSubmitCommandBuffer(&buffer, 0); + } catch (const std::runtime_error& error) { rejected = std::string(error.what()).starts_with("APR: malformed command"); } @@ -920,7 +808,7 @@ void TestAmmRangesAboveHintedMaps() { constexpr std::uint64_t step = 0x400000; void* span = nullptr; Require(sceKernelReserveVirtualRange(&span, 4 * step, 0, 0) == 0); - Require(sceKernelMunmap(span, 4 * step) == 0); + Require(sceKernelMunmap(reinterpret_cast(span), 4 * step) == 0); const auto heap = reinterpret_cast(span); void* placed = span; Require(sceKernelMapFlexibleMemory(&placed, page, 3, 0) == 0 && placed == span); @@ -940,26 +828,26 @@ void TestAmmRangesAboveHintedMaps() { Require(sceKernelMapFlexibleMemory(&mapped, page, 3, 0) == 0); Require(reinterpret_cast(mapped) == hint); Require(hint + page <= start || hint >= multimapEnd); - Require(sceKernelMunmap(mapped, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page) == 0); } - Require(sceKernelMunmap(span, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(span), page) == 0); } void TestAmmUsageStats() { std::array stats{}; Require(sceAmprAmmGetUsageStatsData(nullptr) == static_cast(0x80020016)); stats[0] = 0x19; - Require(sceAmprAmmGetUsageStatsData(stats.data()) == static_cast(0x80020016)); + Require(sceAmprAmmGetUsageStatsData(reinterpret_cast(stats.data())) == static_cast(0x80020016)); stats = {0x18, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef}; - Require(sceAmprAmmGetUsageStatsData(stats.data()) == 0); + Require(sceAmprAmmGetUsageStatsData(reinterpret_cast(stats.data())) == 0); Require(stats[0] == 0x18); Require((stats[1] & 0xffffu) == 512u && (stats[1] >> 16u) == 0u); Require((stats[2] & 0xffffffffu) == 7u && (stats[2] >> 32u) == 0u); stats = {0x8, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef}; - Require(sceAmprAmmGetUsageStatsData(stats.data()) == 0); + Require(sceAmprAmmGetUsageStatsData(reinterpret_cast(stats.data())) == 0); Require(stats[0] == 0x8 && stats[1] == 0xdeadbeef && stats[2] == 0xdeadbeef); stats = {0x0, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef}; - Require(sceAmprAmmGetUsageStatsData(stats.data()) == 0); + Require(sceAmprAmmGetUsageStatsData(reinterpret_cast(stats.data())) == 0); Require(stats[0] == 0 && stats[1] == 0xdeadbeef); } @@ -1017,7 +905,7 @@ void TestAmm() { RequireAppended(remaps, offset, commands, Apr::Opcode::AmmUnmap, sceAmprAmmMeasureAmmCommandSizeUnmap(automatic, 2 * page)); Require(sceAmprAmmCommandBufferMapWithGpuMaskId(&remaps.buffer, reused, 6 * page, 0, cpuReadWrite, 1) == 0); std::uint64_t result = ~0ull; - Require(sceAmprAmmSubmitCommandBuffer2(remaps.memory.data(), remaps.Offset(), 0, &result, &id) == 0 && result == 0); + Require(sceAmprAmmSubmitCommandBuffer2(remaps.memory.data(), remaps.Offset(), 0, reinterpret_cast(&result), &id) == 0 && result == 0); Require(sceAmprAmmWaitCommandBufferCompletion(id) == 0); At(reused + 6 * page - 8) = 0x5555; Require(At(reused + 6 * page - 8) == 0x5555); diff --git a/core/libs/tests/GuestAmprCppError.cpp b/core/libs/tests/GuestAmprCppError.cpp index 8e2407c199..62d9843363 100644 --- a/core/libs/tests/GuestAmprCppError.cpp +++ b/core/libs/tests/GuestAmprCppError.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestLocale.hpp" #include "prx/libc/include/ApplicationHeap.hpp" @@ -11,18 +12,16 @@ #include struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; }; -struct Category { const void* const* vtable; }; -struct ErrorCode { int value; const Category* category; }; -struct SystemError { const void* const* vtable; const char* message; ErrorCode code; }; -static_assert(sizeof(SystemError) == 32 && offsetof(SystemError, code) == 16); -using GuestWhat = const char* (APS5_VABI *)(const SystemError*); -using CategoryDelete = void (APS5_VABI *)(const Category*); -using CategoryName = const char* (APS5_VABI *)(const Category*); -using CategoryMessage = void* (APS5_VABI *)(void*, const Category*, int); -using CategoryCondition = ErrorCode (APS5_VABI *)(const Category*, int); -using CategoryEquivalentCondition = bool (APS5_VABI *)(const Category*, int, const ErrorCode*); -using CategoryEquivalentCode = bool (APS5_VABI *)(const Category*, const ErrorCode*, int); +static_assert(sizeof(LibcException::SystemErrorObject) == 32 && offsetof(LibcException::SystemErrorObject, code) == 16); + +using GuestWhat = const char* (APS5_VABI *)(const LibcException::SystemErrorObject*); +using CategoryDelete = void (APS5_VABI *)(const LibcException::CategoryObject*); +using CategoryName = const char* (APS5_VABI *)(const LibcException::CategoryObject*); +using CategoryMessage = void* (APS5_VABI *)(void*, const LibcException::CategoryObject*, int); +using CategoryCondition = LibcException::ErrorCode (APS5_VABI *)(const LibcException::CategoryObject*, int); +using CategoryEquivalentCondition = bool (APS5_VABI *)(const LibcException::CategoryObject*, int, const LibcException::ErrorCode*); +using CategoryEquivalentCode = bool (APS5_VABI *)(const LibcException::CategoryObject*, const LibcException::ErrorCode*, int); struct Expected { int value; const char* what; }; static constexpr Expected Errors[] { @@ -40,11 +39,7 @@ extern const unsigned char _ZTISt9exception_nid_postfix[]; extern const unsigned char _ZTISt11logic_error_nid_postfix[]; extern const unsigned char _ZTISt13runtime_error_nid_postfix[]; extern const unsigned char _ZTISt12system_error_nid_postfix[]; -[[noreturn]] void APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(int code); -void APS5_VABI _ZNSt12system_errorC1ERKS__nid_postfix(SystemError* self, const SystemError* source); -SystemError* APS5_VABI _ZNSt12system_erroraSERKS__nid_postfix(SystemError* self, const SystemError* source); -void APS5_VABI _ZNSt12system_errorD1Ev_nid_postfix(SystemError* self); -void APS5_VABI _ZdlPv_nid_postfix(void* pointer); + void APS5_VABI GuestCatch(int code); const void* FixtureCatchType = nullptr; @@ -71,58 +66,61 @@ static const TypeRecord* TypeOf(const void* const* vtable, int destroySlot) { return static_cast(vtable[destroySlot - 1]); } -static void CheckCategory(const SystemError& error) { - const Category* category = error.code.category; +static void CheckCategory(const LibcException::SystemErrorObject& error) { + const LibcException::CategoryObject* category = error.code.category; Require(category != nullptr, 20); - const TypeRecord* type = TypeOf(category->vtable, 0); + const TypeRecord* type = TypeOf(static_cast(category->vtable), 0); Require(std::strcmp(type->name, "St23_Generic_error_category") == 0, 21); Require(type->base && std::strcmp(type->base->name, "St14error_category") == 0, 22); - Require(std::strcmp(Slot(category->vtable, 2)(category), "generic") == 0, 23); - const ErrorCode condition = Slot(category->vtable, 4)(category, error.code.value); + Require(std::strcmp(Slot(reinterpret_cast(category->vtable), 2)(category), "generic") == 0, 23); + const LibcException::ErrorCode condition = Slot(reinterpret_cast(category->vtable), 4)(category, error.code.value); Require(condition.value == error.code.value && condition.category == category, 24); - const ErrorCode other {error.code.value + 1, category}; - const ErrorCode foreign {error.code.value, nullptr}; - auto equivalentCondition = Slot(category->vtable, 5); + const LibcException::ErrorCode other {0, error.code.value + 1, category}; + const LibcException::ErrorCode foreign {0, error.code.value, nullptr}; + auto equivalentCondition = Slot(reinterpret_cast(category->vtable), 5); Require(equivalentCondition(category, error.code.value, &condition), 25); Require(!equivalentCondition(category, error.code.value, &other), 26); Require(!equivalentCondition(category, error.code.value, &foreign), 27); - auto equivalentCode = Slot(category->vtable, 6); + auto equivalentCode = Slot(reinterpret_cast(category->vtable), 6); Require(equivalentCode(category, &error.code, error.code.value), 28); Require(!equivalentCode(category, &error.code, error.code.value + 1), 29); Require(!equivalentCode(category, &foreign, error.code.value), 30); GuestLocale::String result {}; - Require(Slot(category->vtable, 3)(&result, category, error.code.value) == &result, 33); + Require(Slot(reinterpret_cast(category->vtable), 3)(&result, category, error.code.value) == &result, 33); const char* data = result.capacity <= 15 ? result.buffer : result.pointer; Require(std::strcmp(data, std::strstr(FixtureExpected->what, ": ") + 2) == 0, 34); if (result.capacity > 15) _ZdlPv_nid_postfix(result.pointer); bool deleteThrew = false; try { - Slot(category->vtable, 1)(category); + Slot(reinterpret_cast(category->vtable), 1)(category); } catch (const std::invalid_argument&) { deleteThrew = true; } Require(deleteThrew, 32); } -static void CheckCopies(const SystemError& error) { - SystemError copy {}; +static void CheckCopies(const LibcException::SystemErrorObject& error) { + LibcException::SystemErrorObject copy {}; _ZNSt12system_errorC1ERKS__nid_postfix(©, &error); - Require(copy.vtable == error.vtable && copy.message == error.message, 40); + + Require(copy.base.vtable == error.base.vtable && copy.base.message == error.base.message, 40); Require(copy.code.value == error.code.value && copy.code.category == error.code.category, 41); - SystemError assigned {}; + LibcException::SystemErrorObject assigned {}; _ZNSt12system_errorC1ERKS__nid_postfix(&assigned, &error); - assigned.code = {0, nullptr}; + + assigned.code = {0, 0, nullptr}; Require(_ZNSt12system_erroraSERKS__nid_postfix(&assigned, &error) == &assigned, 42); Require(assigned.code.value == error.code.value && assigned.code.category == error.code.category, 43); - _ZNSt12system_errorD1Ev_nid_postfix(&assigned); - _ZNSt12system_errorD1Ev_nid_postfix(©); - Require(copy.message == nullptr && assigned.message == nullptr, 44); + _ZNSt12system_errorD1Ev_nid_postfix(&assigned.base); + _ZNSt12system_errorD1Ev_nid_postfix(©.base); + + Require(copy.base.message == nullptr && assigned.base.message == nullptr, 44); } extern "C" void APS5_VABI FixtureInspect(void* object) { - const auto& error = *static_cast(object); - Require(std::strcmp(TypeOf(error.vtable, 0)->name, "St12system_error") == 0, 10); - const char* what = Slot(error.vtable, 2)(&error); + const auto& error = *static_cast(object); + Require(std::strcmp(TypeOf(static_cast(error.base.vtable), 0)->name, "St12system_error") == 0, 10); + const char* what = Slot(reinterpret_cast(error.base.vtable), 2)(&error); Require(what && std::strcmp(what, FixtureExpected->what) == 0, 11); Require(error.code.value == FixtureExpected->value, 12); CheckCategory(error); @@ -142,6 +140,7 @@ static void CheckOutOfRange(int code) { bool threw = false; try { _ZSt16_Throw_Cpp_errori_nid_postfix(code); + } catch (const std::out_of_range& error) { threw = std::string(error.what()).find(std::to_string(code)) != std::string::npos; } diff --git a/core/libs/tests/GuestAmprThreads.cpp b/core/libs/tests/GuestAmprThreads.cpp index 403c445304..653c2cb941 100644 --- a/core/libs/tests/GuestAmprThreads.cpp +++ b/core/libs/tests/GuestAmprThreads.cpp @@ -1,3 +1,7 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceAmpr/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -17,11 +21,6 @@ #include #endif -struct GuestXtime { - std::int64_t sec; - std::int64_t nsec; -}; - struct PadTarget { std::atomic* order; std::atomic* destroyed; @@ -38,48 +37,19 @@ struct GuestTypeInfo { struct GuestSystemError { const void* vtable; const char* message; + int reserved; int code; const void* category; }; -using GuestTerminate = void (APS5_VABI*)(); - -struct TestPad { - unsigned (APS5_VABI* const* vtable)(TestPad*); - void* condition; - void* mutex; - bool started; +struct TestPad : GuestPad { PadTarget target; }; +using GuestTerminate = void (APS5_VABI*)(); + extern "C" { -void APS5_VABI _ZNSt4_PadC2Ev_nid_postfix(TestPad* pad); -void APS5_VABI _ZNSt4_PadD2Ev_nid_postfix(TestPad* pad); -void APS5_VABI _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(TestPad* pad, Pthread* thread); -void APS5_VABI _ZNSt4_Pad8_ReleaseEv_nid_postfix(TestPad* pad); -int APS5_VABI _Thrd_join_nid_postfix(Pthread thread, int* code); -int APS5_VABI _Thrd_sleep_nid_postfix(const GuestXtime* target, GuestXtime* remaining); -void APS5_VABI _Lock_shared_ptr_spin_lock_nid_postfix(void); -void APS5_VABI _Unlock_shared_ptr_spin_lock_nid_postfix(void); -int APS5_VABI sceAmprWcstombsS(std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t* source, std::size_t limit); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI pthread_detach_nid_postfix(Pthread thread); -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); -int APS5_VABI _Cnd_signal_nid_postfix(void** handle); -int APS5_VABI _Cnd_init_with_name_nid_postfix(void** handle, const char* name); -int APS5_VABI _Cnd_init_nid_postfix(void** handle); -void APS5_VABI _Cnd_destroy_nid_postfix(void** handle); -int APS5_VABI _Cnd_wait_nid_postfix(void** condition, void** mutex); -int APS5_VABI _Cnd_broadcast_nid_postfix(void** handle); -int APS5_VABI _Mtx_init_nid_postfix(void** handle, int type); -void APS5_VABI _Mtx_destroy_nid_postfix(void** handle); -int APS5_VABI _Mtx_lock_nid_postfix(void** handle); -int APS5_VABI _Mtx_unlock_nid_postfix(void** handle); -GuestTerminate APS5_VABI _ZSt13set_terminatePFvvE_nid_postfix(GuestTerminate handler); -const GuestTypeInfo* APS5_VABI __cxa_current_exception_type_nid_postfix(); -void* APS5_VABI __cxa_current_primary_exception_nid_postfix(); -LibcException::Header* APS5_VABI __cxa_init_primary_exception_nid_postfix(void* object, std::type_info* type, void (*destructor)(void*)); -[[noreturn]] void APS5_VABI __cxa_call_unexpected_nid_postfix(void* unwindException); + extern const unsigned char _ZTVN10__cxxabiv117__class_type_infoE_nid_postfix[]; } @@ -106,7 +76,7 @@ static std::int64_t RealtimeNanos() { return now.tv_sec * NanosPerSecond + now.tv_nsec; } -static GuestXtime XtimeAfter(std::int64_t nanos) { +static Xtime XtimeAfter(std::int64_t nanos) { const auto target = RealtimeNanos() + nanos; return {target / NanosPerSecond, target % NanosPerSecond}; } @@ -134,10 +104,12 @@ static void* APS5_VABI SpinWorker(void* arg) { auto& context = *static_cast(arg); for (int iteration = 0; iteration < SpinIterations; ++iteration) { _Lock_shared_ptr_spin_lock_nid_postfix(); + if (context.inside.fetch_add(1) != 0) context.overlapped = true; ++context.counter; context.inside.fetch_sub(1); _Unlock_shared_ptr_spin_lock_nid_postfix(); + } return nullptr; } @@ -180,12 +152,12 @@ static void TestThreadSleep() { Require(RealtimeNanos() - before < NanosPerSecond / 2); const auto carried = XtimeAfter(30000000); - const GuestXtime unnormalized{carried.sec - 2, carried.nsec + 2 * NanosPerSecond}; + const Xtime unnormalized{carried.sec - 2, carried.nsec + 2 * NanosPerSecond}; Require(_Thrd_sleep_nid_postfix(&unnormalized, nullptr) == 0); Require(RealtimeNanos() >= carried.sec * NanosPerSecond + carried.nsec); const auto borrowed = XtimeAfter(20000000); - const GuestXtime negative{borrowed.sec + 1, borrowed.nsec - NanosPerSecond}; + const Xtime negative{borrowed.sec + 1, borrowed.nsec - NanosPerSecond}; Require(_Thrd_sleep_nid_postfix(&negative, nullptr) == 0); Require(RealtimeNanos() >= borrowed.sec * NanosPerSecond + borrowed.nsec); @@ -209,6 +181,7 @@ static void TestThreadSleep() { bool rejected = false; try { _Thrd_sleep_nid_postfix(nullptr, nullptr); + } catch (const std::invalid_argument&) { rejected = true; } @@ -224,6 +197,7 @@ static void TestSpinLock() { Require(context.counter == SpinThreads * SpinIterations); _Lock_shared_ptr_spin_lock_nid_postfix(); _Unlock_shared_ptr_spin_lock_nid_postfix(); + } static void TestWcstombs() { @@ -265,11 +239,11 @@ static void TestWcstombs() { Require(result == Failed && buffer[0] == 'x'); std::memset(buffer, 'x', sizeof(buffer)); - Require(sceAmprWcstombsS(&result, buffer, sizeof(buffer), wide, sizeof(buffer)) == GuestEilseq); - Require(result == Failed && buffer[0] == 'a' && buffer[1] == '\0'); + Require(sceAmprWcstombsS(&result, buffer, sizeof(buffer), wide, sizeof(buffer)) == 0); + Require(result == 3 && std::memcmp(buffer, "a\xc4\x80\0x", 5) == 0); - Require(sceAmprWcstombsS(&result, nullptr, 0, wide, 0) == GuestEilseq); - Require(result == Failed); + Require(sceAmprWcstombsS(&result, nullptr, 0, wide, 0) == 0); + Require(result == 3); } template @@ -366,6 +340,7 @@ static void TestConditionSignal() { RequireThrows([&] { _Cnd_signal_nid_postfix(&context.mutex); }); RequireThrows([] { _Cnd_init_with_name_nid_postfix(nullptr, "AmprSignal"); }); _Cnd_destroy_nid_postfix(&context.condition); + Require(context.condition == nullptr); RequireThrows([&] { _Cnd_signal_nid_postfix(&context.condition); }); @@ -373,24 +348,28 @@ static void TestConditionSignal() { Require(_Cnd_init_with_name_nid_postfix(&unnamed, nullptr) == ThrdSuccess && unnamed != nullptr); Require(_Cnd_signal_nid_postfix(&unnamed) == ThrdSuccess); _Cnd_destroy_nid_postfix(&unnamed); + void* plain = nullptr; Require(_Cnd_init_nid_postfix(&plain) == ThrdSuccess); Require(_Cnd_signal_nid_postfix(&plain) == ThrdSuccess); _Cnd_destroy_nid_postfix(&plain); _Mtx_destroy_nid_postfix(&context.mutex); + } -static unsigned APS5_VABI PadGo(TestPad* pad) { +static unsigned APS5_VABI PadGo(GuestPad* base) { + auto* pad = static_cast(base); const auto local = pad->target; std::this_thread::sleep_for(std::chrono::milliseconds(20)); local.order->store(local.beforeRelease); _ZNSt4_Pad8_ReleaseEv_nid_postfix(pad); + while (!local.destroyed->load()) std::this_thread::yield(); local.order->store(local.afterRelease); return local.exitCode; } -static constexpr std::array PadVtable{PadGo}; +static constexpr std::array PadVtable{PadGo}; template static bool Throws(TCall call) { @@ -406,6 +385,7 @@ static void ConstructPad(TestPad& pad, std::atomic& order, std::atomic(&thread)); + Require(thread != nullptr); Require(pad.started); Require(order.load() == 1); _ZNSt4_PadD2Ev_nid_postfix(&pad); + Require(!pad.condition && !pad.mutex); Require(order.load() == 1); destroyed = true; @@ -439,9 +421,11 @@ static void TestPadLaunchMany() { std::array threads{}; for (int index = 0; index < Count; ++index) { ConstructPad(pads[index], orders[index], destroyed, index, 100u + index); - _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(&pads[index], &threads[index]); + _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(&pads[index], reinterpret_cast(&threads[index])); + Require(orders[index].load() == 2 * index + 1); _ZNSt4_PadD2Ev_nid_postfix(&pads[index]); + } destroyed = true; for (int index = 0; index < Count; ++index) { @@ -452,10 +436,12 @@ static void TestPadLaunchMany() { } } -static unsigned APS5_VABI PadGoImmediate(TestPad* pad) { +static unsigned APS5_VABI PadGoImmediate(GuestPad* base) { + auto* pad = static_cast(base); const auto local = pad->target; local.order->store(local.beforeRelease); _ZNSt4_Pad8_ReleaseEv_nid_postfix(pad); + while (!local.destroyed->load()) std::this_thread::yield(); local.order->store(local.afterRelease); return local.exitCode; @@ -463,7 +449,7 @@ static unsigned APS5_VABI PadGoImmediate(TestPad* pad) { static void TestPadConcurrentLaunch() { constexpr int Count = 4; - constexpr std::array immediateVtable{PadGoImmediate}; + constexpr std::array immediateVtable{PadGoImmediate}; std::atomic ready{0}; std::array parents; for (int index = 0; index < Count; ++index) { @@ -476,9 +462,11 @@ static void TestPadConcurrentLaunch() { ready.fetch_add(1); while (ready.load() != Count) std::this_thread::yield(); Pthread thread = nullptr; - _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(&pad, &thread); + _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(&pad, reinterpret_cast(&thread)); + Require(thread != nullptr && pad.started && order.load() == 2 * index + 1); _ZNSt4_PadD2Ev_nid_postfix(&pad); + std::memset(&pad, 0xcd, sizeof(pad)); destroyed = true; int code = 0; @@ -505,7 +493,7 @@ static void* APS5_VABI DestroyPad(void* arg) { static void APS5_VABI ExitWithSystemErrorCode() { const auto* type = __cxa_current_exception_type_nid_postfix(); const auto* error = static_cast(__cxa_current_primary_exception_nid_postfix()); - _exit(type && error && std::strcmp(type->name, "St12system_error") == 0 ? error->code : 1); + _exit(type && error && std::strcmp(type->name(), "St12system_error") == 0 ? error->code : 1); } template @@ -514,6 +502,7 @@ static int TerminateExitCode(GuestTerminate handler, TCall call) { Require(child >= 0); if (child == 0) { _ZSt13set_terminatePFvvE_nid_postfix(handler); + call(); _exit(2); } @@ -526,7 +515,7 @@ static const GuestTypeInfo UnexpectedType{_ZTVN10__cxxabiv117__class_type_infoE_ static void APS5_VABI ExitWithCaughtType() { const auto* type = __cxa_current_exception_type_nid_postfix(); - _exit(type == &UnexpectedType ? 42 : type == nullptr ? 43 : 1); + _exit(type == reinterpret_cast(&UnexpectedType) ? 42 : type == nullptr ? 43 : 1); } static void TestCallUnexpected() { @@ -535,6 +524,7 @@ static void TestCallUnexpected() { auto* type = reinterpret_cast(const_cast(&UnexpectedType)); LibcException::Header* header = __cxa_init_primary_exception_nid_postfix(object, type, nullptr); __cxa_call_unexpected_nid_postfix(&header->unwind); + }) == 42); Require(TerminateExitCode(ExitWithCaughtType, [] { __cxa_call_unexpected_nid_postfix(nullptr); }) == 43); } @@ -545,7 +535,7 @@ static void TestPadErrors() { Require(Throws([] { _ZNSt4_PadD2Ev_nid_postfix(nullptr); })); Require(Throws([] { _ZNSt4_Pad8_ReleaseEv_nid_postfix(nullptr); })); Pthread thread = nullptr; - Require(Throws([&] { _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(nullptr, &thread); })); + Require(Throws([&] { _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(nullptr, reinterpret_cast(&thread)); })); std::atomic order{0}; std::atomic destroyed{true}; @@ -563,6 +553,7 @@ static void TestPadErrors() { Require(context.rejected); Require(pad.condition && pad.mutex); _ZNSt4_PadD2Ev_nid_postfix(&pad); + Require(!pad.condition && !pad.mutex); Require(Throws([&] { _ZNSt4_PadD2Ev_nid_postfix(&pad); })); } diff --git a/core/libs/tests/GuestAppContent.cpp b/core/libs/tests/GuestAppContent.cpp index 6d3745cd70..e1197f4a94 100644 --- a/core/libs/tests/GuestAppContent.cpp +++ b/core/libs/tests/GuestAppContent.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceAppContent/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include @@ -5,26 +7,8 @@ #include #include #include - -extern "C" { -int APS5_VABI sceAppContentInitialize(const AppContentInitParam*, AppContentBootParam*); -int APS5_VABI sceAppContentAddcontMount(uint32_t, const NpUnifiedEntitlementLabel*, AppContentMountPoint*); -int APS5_VABI sceAppContentAddcontUnmount(const AppContentMountPoint*); -int APS5_VABI sceAppContentAddcontEnqueueDownload(uint32_t, const NpUnifiedEntitlementLabel*); -int APS5_VABI sceAppContentAddcontDelete(uint32_t, const NpUnifiedEntitlementLabel*); -int APS5_VABI sceAppContentGetAddcontDownloadProgress(uint32_t, const NpUnifiedEntitlementLabel*, void*); -int APS5_VABI sceAppContentTemporaryDataMount2(uint32_t, AppContentMountPoint*); -int APS5_VABI sceAppContentTemporaryDataUnmount(const AppContentMountPoint*); -int APS5_VABI sceAppContentTemporaryDataFormat(const AppContentMountPoint*); -int APS5_VABI sceAppContentTemporaryDataGetAvailableSpaceKb(const AppContentMountPoint*, size_t*); -int APS5_VABI access_nid_postfix(const char*, int); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceAppContentGetAddcontInfo(uint32_t, const NpUnifiedEntitlementLabel*, void*); -int APS5_VABI sceAppContentGetAddcontInfoList(uint32_t, void*, uint32_t, uint32_t*); -int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMountPoint*, size_t*); -int APS5_VABI sceAppContentDownloadDataFormat(const AppContentMountPoint*); -int APS5_VABI sceAppContentAppParamGetInt(uint32_t, int32_t*); -} +#include +#include "ProcessParametersFixture.hpp" static constexpr int ErrorParameter = static_cast(0x80D90002); static constexpr int ErrorNotFound = static_cast(0x80D90005); @@ -34,6 +18,29 @@ static constexpr int ErrorNotMounted = static_cast(0x80D90004); static void Require(bool value) { if (!value) std::abort(); } int main() { + static std::array process{}; + Require(ProcessParametersFixture::Install(process.data(), process.size())); + std::uint32_t sdkVersion = 0x01500000; + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + constexpr int notInitialized = static_cast(0x80D90001u); + Require(sceAppContentAddcontMount(0, nullptr, nullptr) == notInitialized); + Require(sceAppContentAddcontUnmount(nullptr) == notInitialized); + Require(sceAppContentGetAddcontInfo(0, nullptr, nullptr) == notInitialized); + Require(sceAppContentGetAddcontInfoList(0, nullptr, 0, nullptr) == notInitialized); + Require(sceAppContentGetEntitlementKey(0, nullptr, nullptr) == notInitialized); + Require(sceAppContentAppParamGetInt(0, nullptr) == notInitialized); + AppContentMountPoint uninitializedMount{}; + Require(sceAppContentTemporaryDataMount(nullptr) == notInitialized); + Require(sceAppContentTemporaryDataMount(&uninitializedMount) == notInitialized); + Require(sceAppContentTemporaryDataMount2(0, nullptr) == notInitialized); + Require(sceAppContentTemporaryDataUnmount(nullptr) == notInitialized); + Require(sceAppContentTemporaryDataFormat(nullptr) == notInitialized); + Require(sceAppContentTemporaryDataGetAvailableSpaceKb(nullptr, nullptr) == notInitialized); + Require(sceAppContentDownloadDataFormat(nullptr) == notInitialized); + Require(sceAppContentDownloadDataGetAvailableSpaceKb(nullptr, nullptr) == notInitialized); + Require(sceAppContentAddcontDelete(0, nullptr) == notInitialized); + Require(sceAppContentAddcontEnqueueDownload(0, nullptr) == notInitialized); + Require(sceAppContentGetAddcontDownloadProgress(0, nullptr, nullptr) == notInitialized); const auto original = std::filesystem::current_path(); const auto directory = original / ("appcontent-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); Require(std::filesystem::create_directory(directory)); @@ -45,13 +52,36 @@ int main() { R"("userDefinedParam1":23566,"userDefinedParam2":-7,"userDefinedParam3":0,"userDefinedParam4":2147483647})"; Require(static_cast(param)); } + AppContentInitParam init{}; + AppContentBootParam boot{}; + Require(sceAppContentInitialize(nullptr, &boot) == ErrorParameter); + Require(sceAppContentInitialize(&init, nullptr) == ErrorParameter); + init.reserved[31] = 1; + Require(sceAppContentInitialize(&init, &boot) == ErrorParameter); + init.reserved[31] = 0; + boot.reserved1[3] = 1; + Require(sceAppContentInitialize(&init, &boot) == ErrorParameter); + boot.reserved1[3] = 0; + boot.reserved2[31] = 1; + Require(sceAppContentInitialize(&init, &boot) == ErrorParameter); + boot.reserved2[31] = 0; + boot.attr = 0xffffffffu; + Require(sceAppContentInitialize(&init, &boot) == 0); + Require(boot.attr == 0); + Require(sceAppContentInitialize(nullptr, nullptr) == ErrorBusy); + sdkVersion = 0x01400000; + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + Require(sceAppContentInitialize(nullptr, &boot) == 0); AppContentMountPoint download{}; std::memcpy(download.data, "/download0", 11); size_t availableKb = 12345; Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download, &availableKb) == 0); Require(availableKb == 0); Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download, nullptr) == ErrorParameter); + availableKb = 12345; Require(sceAppContentDownloadDataGetAvailableSpaceKb(nullptr, &availableKb) == ErrorParameter); + Require(availableKb == 12345); + Require(sceAppContentDownloadDataGetAvailableSpaceKb(nullptr, nullptr) == ErrorParameter); AppContentMountPoint invalidMount{}; Require(sceAppContentDownloadDataGetAvailableSpaceKb(&invalidMount, &availableKb) == ErrorNotMounted); const auto downloadData = ResolvePath_nid_no_patch("/download0"); @@ -107,7 +137,6 @@ int main() { std::filesystem::create_directory(directory / "temp0"); { std::ofstream file(directory / "temp0" / "old.txt"); file << "previous run"; } - AppContentBootParam boot{}; Require(sceAppContentInitialize(nullptr, &boot) == 0); Require(access_nid_postfix("/temp0/old.txt", 0) == -1); Require(sceAppContentTemporaryDataUnmount(nullptr) == ErrorParameter); @@ -148,31 +177,72 @@ int main() { Require(sceAppContentTemporaryDataMount2(1, &mountPoint) == 0); Require(std::filesystem::is_empty(backing)); Require(sceAppContentTemporaryDataUnmount(&mountPoint) == 0); + Require(sceAppContentTemporaryDataMount(nullptr) == ErrorParameter); + { std::ofstream file(backing / "remove.txt"); file << "format"; } + std::memset(&mountPoint, 0x5a, sizeof(mountPoint)); + Require(sceAppContentTemporaryDataMount(&mountPoint) == 0); + Require(std::strcmp(mountPoint.data, "/temp0") == 0); + Require(std::filesystem::is_empty(backing)); + Require(sceAppContentTemporaryDataMount(&second) == ErrorBusy); + Require(sceAppContentTemporaryDataMount2(0, &second) == ErrorBusy); + Require(sceAppContentTemporaryDataUnmount(&mountPoint) == 0); + Require(sceAppContentTemporaryDataMount(&mountPoint) == 0); + Require(sceAppContentTemporaryDataUnmount(&mountPoint) == 0); unsigned char info[24]; std::memset(info, 0x5a, sizeof(info)); unsigned char untouchedInfo[24]; std::memcpy(untouchedInfo, info, sizeof(info)); - Require(sceAppContentGetAddcontInfo(0, &label, info) == ErrorDrmNoEntitlement); + Require(sceAppContentGetAddcontInfo(0, &label, reinterpret_cast(info)) == ErrorDrmNoEntitlement); Require(std::memcmp(info, untouchedInfo, sizeof(info)) == 0); - Require(sceAppContentGetAddcontInfo(0, nullptr, info) == ErrorParameter); + Require(sceAppContentGetAddcontInfo(0, nullptr, reinterpret_cast(info)) == ErrorParameter); Require(sceAppContentGetAddcontInfo(0, &label, nullptr) == ErrorParameter); + AppContentEntitlementKey key; + std::memset(&key, 0x5a, sizeof(key)); + const AppContentEntitlementKey untouchedKey = key; + for (const std::uint32_t serviceLabel : {0u, 7u, 8u, 0xffffffffu}) { + Require(sceAppContentGetEntitlementKey(serviceLabel, &label, &key) == ErrorDrmNoEntitlement); + Require(std::memcmp(&key, &untouchedKey, sizeof(key)) == 0); + } + Require(sceAppContentGetEntitlementKey(0, nullptr, &key) == ErrorParameter); + Require(std::memcmp(&key, &untouchedKey, sizeof(key)) == 0); + Require(sceAppContentGetEntitlementKey(0, &label, nullptr) == ErrorParameter); + + for (const std::uint32_t version : {0x014fffffu, 0x01500000u, 0x034fffffu, 0x03500000u}) { + std::memcpy(process.data() + 16, &version, sizeof(version)); + for (char& padding : label.padding) { + padding = 1; + const int earlyError = version >= 0x01500000 ? ErrorParameter : ErrorNotFound; + const int infoError = version >= 0x01500000 ? ErrorParameter : ErrorDrmNoEntitlement; + const int lateError = version >= 0x03500000 ? ErrorParameter : ErrorNotFound; + Require(sceAppContentAddcontMount(0, &label, &mountPoint) == earlyError); + Require(sceAppContentGetAddcontInfo(0, &label, reinterpret_cast(info)) == infoError); + Require(sceAppContentGetEntitlementKey(0, &label, &key) == infoError); + Require(std::memcmp(&key, &untouchedKey, sizeof(key)) == 0); + Require(sceAppContentAddcontEnqueueDownload(0, &label) == earlyError); + Require(sceAppContentAddcontDelete(0, &label) == lateError); + Require(sceAppContentGetAddcontDownloadProgress(0, &label, progress) == lateError); + Require(std::memcmp(info, untouchedInfo, sizeof(info)) == 0); + Require(std::memcmp(progress, progressBefore, sizeof(progress)) == 0); + padding = 0; + } + } uint32_t hitNum = 0x5a5a5a5a; Require(sceAppContentGetAddcontInfoList(0, nullptr, 0, &hitNum) == 0); Require(hitNum == 0); hitNum = 0x5a5a5a5a; - Require(sceAppContentGetAddcontInfoList(0, info, 0, &hitNum) == 0); + Require(sceAppContentGetAddcontInfoList(0, reinterpret_cast(info), 0, &hitNum) == 0); Require(hitNum == 0); hitNum = 0x5a5a5a5a; Require(sceAppContentGetAddcontInfoList(0, nullptr, 1, &hitNum) == 0); Require(hitNum == 0); hitNum = 0x5a5a5a5a; - Require(sceAppContentGetAddcontInfoList(0, info, 1, &hitNum) == 0); + Require(sceAppContentGetAddcontInfoList(0, reinterpret_cast(info), 1, &hitNum) == 0); Require(hitNum == 0); Require(std::memcmp(info, untouchedInfo, sizeof(info)) == 0); - Require(sceAppContentGetAddcontInfoList(0, info, 1, nullptr) == 0); + Require(sceAppContentGetAddcontInfoList(0, reinterpret_cast(info), 1, nullptr) == ErrorParameter); Require(sceAppContentGetAddcontInfoList(0, nullptr, 0, nullptr) == ErrorParameter); - Require(sceAppContentGetAddcontInfoList(0, info, 0, nullptr) == ErrorParameter); + Require(sceAppContentGetAddcontInfoList(0, reinterpret_cast(info), 0, nullptr) == ErrorParameter); Require(sceAppContentGetAddcontInfoList(0, nullptr, 1, nullptr) == ErrorParameter); std::filesystem::current_path(original); std::filesystem::remove_all(directory); diff --git a/core/libs/tests/GuestAppInfo.cpp b/core/libs/tests/GuestAppInfo.cpp index 860ff13718..e1220222b5 100644 --- a/core/libs/tests/GuestAppInfo.cpp +++ b/core/libs/tests/GuestAppInfo.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -8,16 +10,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelGetAppInfo(int pid, void* info); -int APS5_VABI getpid_nid_postfix(void); -int APS5_VABI sceKernelUuidCreate(std::uint32_t* uuid); -int APS5_VABI sceKernelCreateUuidV4(std::uint8_t* uuid); -} - -static constexpr int SCE_KERNEL_ERROR_EPERM = static_cast(0x80020001); -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); - static void Require(bool value) { if (!value) std::abort(); } struct AppInfo { diff --git a/core/libs/tests/GuestAppendStreams.cpp b/core/libs/tests/GuestAppendStreams.cpp index 2fb3939330..ee44d84e7f 100644 --- a/core/libs/tests/GuestAppendStreams.cpp +++ b/core/libs/tests/GuestAppendStreams.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -13,21 +15,6 @@ #include #include -extern "C" { -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); -std::size_t APS5_VABI fwrite_nid_postfix(const void*, std::size_t, std::size_t, FileStream*); -std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*); -int APS5_VABI fseek_nid_postfix(FileStream*, std::int64_t, int); -std::int64_t APS5_VABI ftell_nid_postfix(FileStream*); -int APS5_VABI fflush_nid_postfix(FileStream*); -int APS5_VABI fclose_nid_postfix(FileStream*); -int APS5_VABI fileno_nid_postfix(FileStream*); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -int APS5_VABI flock_nid_postfix(int, int); -int* APS5_VABI __error_nid_postfix(); -} - static void Check(bool condition, int line) { if (!condition) { std::fprintf(stderr, "append stream check failed at line %d\n", line); diff --git a/core/libs/tests/GuestAppendWrites.cpp b/core/libs/tests/GuestAppendWrites.cpp index 0780e303eb..806611cae1 100644 --- a/core/libs/tests/GuestAppendWrites.cpp +++ b/core/libs/tests/GuestAppendWrites.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -12,21 +14,6 @@ #include #include -struct GuestIovec { - const void* base; - std::size_t length; -}; - -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t); -std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -std::int64_t APS5_VABI writev_nid_postfix(int, const GuestIovec*, int); -int APS5_VABI dup_nid_postfix(int); -} - static void Check(bool condition, int line) { if (!condition) { std::fprintf(stderr, "append check failed at line %d\n", line); @@ -40,7 +27,7 @@ static constexpr int CreateWriteAppend = 0x0209; static constexpr int Truncate = 0x0400; static constexpr int FromStart = 0; static constexpr int FromCurrent = 1; -static constexpr int Writers = 4; +static constexpr int Writers = 5; static constexpr int LinesPerWriter = 1500; static constexpr std::size_t LineLength = 32; @@ -52,15 +39,18 @@ static std::string Line(int writer, int index) { static void WriteLines(int fd, int writer) { for (int index = 0; index < LinesPerWriter; ++index) { - const std::string line = Line(writer, index); + std::string line = Line(writer, index); std::int64_t written = -1; if (writer == 0 || writer == 2) { written = sceKernelWrite(fd, line.data(), line.size()); } else if (writer == 1) { written = write_nid_postfix(fd, line.data(), static_cast(line.size())); - } else { - const GuestIovec parts[2]{{line.data(), 7}, {line.data() + 7, line.size() - 7}}; + } else if (writer == 3) { + const KernelIovec parts[2]{{line.data(), 7}, {line.data() + 7, line.size() - 7}}; written = writev_nid_postfix(fd, parts, 2); + } else { + const KernelIovec parts[2]{{line.data(), 7}, {line.data() + 7, line.size() - 7}}; + written = pwritev_nid_postfix(fd, parts, 2, 0); } Require(written == static_cast(LineLength)); } @@ -77,9 +67,10 @@ int main() { const int second = sceKernelOpen(Path, CreateWriteAppend, 0644); const int duplicate = dup_nid_postfix(first); const int vectored = sceKernelOpen(Path, CreateWriteAppend, 0644); - Require(second >= 0 && duplicate >= 0 && vectored >= 0); + const int positionedVector = sceKernelOpen(Path, CreateWriteAppend, 0644); + Require(second >= 0 && duplicate >= 0 && vectored >= 0 && positionedVector >= 0); - const int descriptors[Writers]{first, second, duplicate, vectored}; + const int descriptors[Writers]{first, second, duplicate, vectored, positionedVector}; std::vector threads; for (int writer = 0; writer < Writers; ++writer) threads.emplace_back(WriteLines, descriptors[writer], writer); for (auto& thread : threads) thread.join(); @@ -88,7 +79,7 @@ int main() { Require(contents.size() == static_cast(Writers) * LinesPerWriter * LineLength); std::set> seen; for (std::size_t offset = 0; offset < contents.size(); offset += LineLength) { - const std::string line = contents.substr(offset, LineLength); + std::string line = contents.substr(offset, LineLength); int writer = -1; int index = -1; Require(std::sscanf(line.c_str(), "w%d %d ", &writer, &index) == 2); @@ -105,6 +96,20 @@ int main() { Require(sceKernelLseek(first, 0, FromCurrent) == size + static_cast(LineLength)); Require(Contents().substr(static_cast(size)) == tail); + const std::string head = Contents().substr(0, LineLength); + const std::string positioned = Line(9, 1); + Require(sceKernelLseek(first, 0, FromStart) == 0); + Require(sceKernelPwrite(first, positioned.data(), positioned.size(), 0) == static_cast(LineLength)); + Require(sceKernelLseek(first, 0, FromCurrent) == 0); + std::string gathered = Line(9, 2); + const KernelIovec pieces[2]{{gathered.data(), 7}, {gathered.data() + 7, gathered.size() - 7}}; + Require(pwritev_nid_postfix(first, pieces, 2, static_cast(LineLength)) == static_cast(LineLength)); + Require(sceKernelLseek(first, 0, FromCurrent) == 0); + const std::string appended = Contents(); + Require(appended.size() == static_cast(size) + 3 * LineLength); + Require(appended.substr(0, LineLength) == head); + Require(appended.substr(static_cast(size) + LineLength) == positioned + gathered); + for (const int fd : descriptors) Require(sceKernelClose(fd) == 0); std::remove(Path); } diff --git a/core/libs/tests/GuestAprRead.cpp b/core/libs/tests/GuestAprRead.cpp index a8ba636dc3..004346dbcd 100644 --- a/core/libs/tests/GuestAprRead.cpp +++ b/core/libs/tests/GuestAprRead.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/Apr/include/AprCommandBuffer.hpp" #include @@ -14,18 +16,6 @@ #include #endif -extern "C" { -int APS5_VABI sceKernelAprResolveFilepathsToIds(const char**, std::uint32_t, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceKernelAprSubmitCommandBuffer(const Apr::CommandBufferObject*, std::uint32_t); -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); -int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); -void* APS5_VABI fopen_nid_postfix(const char*, const char*); -int APS5_VABI fclose_nid_postfix(void*); -} - namespace { constexpr std::size_t FileBytes = (16u << 20u) + 257u; @@ -81,13 +71,15 @@ public: Require(sceKernelAllocateDirectMemory(0, 13824ll << 20, MappingBytes, 0x4000, 0, &physical) == 0, "cannot allocate direct memory"); if (sceKernelMapDirectMemory(&pointer, MappingBytes, 0x33, 0, physical, 0x4000) != 0) { sceKernelReleaseDirectMemory(physical, MappingBytes); + throw std::runtime_error("cannot map direct memory"); } } ~DirectBuffer() { - sceKernelMunmap(pointer, MappingBytes); + sceKernelMunmap(reinterpret_cast(pointer), MappingBytes); sceKernelReleaseDirectMemory(physical, MappingBytes); + } std::uint8_t* Data() const { return static_cast(pointer); } @@ -161,9 +153,9 @@ void TestErrors(const Fixture& fixture) { void TestStreams(const Fixture& fixture) { constexpr int StreamCount = 600; - std::vector streams; + std::vector streams; for (int index = 0; index < StreamCount; ++index) { - void* stream = fopen_nid_postfix(("apr_stream_" + std::to_string(index) + ".tmp").c_str(), "w"); + FileStream* stream = fopen_nid_postfix(("apr_stream_" + std::to_string(index) + ".tmp").c_str(), "w"); if (stream == nullptr) break; streams.push_back(stream); } @@ -175,7 +167,7 @@ void TestStreams(const Fixture& fixture) { } catch (const std::runtime_error& failure) { error = failure.what(); } - for (void* stream : streams) fclose_nid_postfix(stream); + for (FileStream* stream : streams) fclose_nid_postfix(stream); for (int index = 0; index < StreamCount; ++index) std::filesystem::remove("apr_stream_" + std::to_string(index) + ".tmp"); Require(error.empty(), ("APR read failed while " + std::to_string(streams.size()) + " guest streams were open: " + error).c_str()); Require(held, "cannot hold 600 guest fopen streams"); diff --git a/core/libs/tests/GuestArrayCallbacks.cpp b/core/libs/tests/GuestArrayCallbacks.cpp index 92bfdfdef7..cdb9079455 100644 --- a/core/libs/tests/GuestArrayCallbacks.cpp +++ b/core/libs/tests/GuestArrayCallbacks.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -11,19 +12,6 @@ using AllocateCallback = void* (APS5_VABI *)(std::size_t); using FreeCallback = void (APS5_VABI *)(void*); using SizedFreeCallback = void (APS5_VABI *)(void*, std::size_t); -extern "C" { -void APS5_VABI __cxa_vec_ctor_nid_postfix(void*, std::size_t, std::size_t, ElementCallback, ElementCallback); -void APS5_VABI __cxa_vec_cctor_nid_postfix(void*, void*, std::size_t, std::size_t, CopyCallback, ElementCallback); -void APS5_VABI __cxa_vec_dtor_nid_postfix(void*, std::size_t, std::size_t, ElementCallback); -void APS5_VABI __cxa_vec_cleanup_nid_postfix(void*, std::size_t, std::size_t, ElementCallback); -void* APS5_VABI __cxa_vec_new_nid_postfix(std::size_t, std::size_t, std::size_t, ElementCallback, ElementCallback); -void* APS5_VABI __cxa_vec_new2_nid_postfix(std::size_t, std::size_t, std::size_t, ElementCallback, ElementCallback, AllocateCallback, FreeCallback); -void* APS5_VABI __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, ElementCallback, ElementCallback, AllocateCallback, SizedFreeCallback); -void APS5_VABI __cxa_vec_delete_nid_postfix(void*, std::size_t, std::size_t, ElementCallback); -void APS5_VABI __cxa_vec_delete2_nid_postfix(void*, std::size_t, std::size_t, ElementCallback, FreeCallback); -void APS5_VABI __cxa_vec_delete3_nid_postfix(void*, std::size_t, std::size_t, ElementCallback, SizedFreeCallback); -} - namespace { constexpr std::size_t Count = 4; @@ -104,18 +92,23 @@ void checkCookie(const void* array) { void checkElements() { reset(source.data()); __cxa_vec_ctor_nid_postfix(source.data(), Count, ElementBytes, construct, destroy); + require(constructed == Count, "constructor count is wrong"); __cxa_vec_dtor_nid_postfix(source.data(), Count, ElementBytes, destroy); + require(destroyed == Count, "destructor count is wrong"); reset(storage.data()); __cxa_vec_cctor_nid_postfix(storage.data(), source.data(), Count, ElementBytes, copy, destroy); + require(copied == Count && std::memcmp(storage.data(), source.data(), source.size()) == 0, "copy construction failed"); __cxa_vec_cleanup_nid_postfix(storage.data(), Count, ElementBytes, destroy); + require(destroyed == Count, "cleanup count is wrong"); __cxa_vec_ctor_nid_postfix(nullptr, Count, ElementBytes, nullptr, nullptr); __cxa_vec_cctor_nid_postfix(nullptr, nullptr, Count, ElementBytes, nullptr, nullptr); __cxa_vec_dtor_nid_postfix(nullptr, Count, ElementBytes, nullptr); __cxa_vec_cleanup_nid_postfix(nullptr, Count, ElementBytes, nullptr); + } void checkAllocation(bool sized) { @@ -123,6 +116,7 @@ void checkAllocation(bool sized) { void* array = sized ? __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized) : __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release); + require(array == destination && constructed == Count && allocated == 1, "array allocation failed"); checkCookie(array); if (sized) __cxa_vec_delete3_nid_postfix(array, ElementBytes, Padding, destroy, releaseSized); @@ -133,6 +127,7 @@ void checkAllocation(bool sized) { array = sized ? __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized) : __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release); + require(array == nullptr && allocated == 1 && constructed == 0 && freed == 0, "failed allocation invoked element callbacks"); if (sized) __cxa_vec_delete3_nid_postfix(nullptr, ElementBytes, Padding, destroy, releaseSized); else __cxa_vec_delete2_nid_postfix(nullptr, ElementBytes, Padding, destroy, release); @@ -146,8 +141,10 @@ void checkDefaultAllocation() { reset(static_cast(array)); __cxa_vec_ctor_nid_postfix(array, Count, ElementBytes, construct, destroy); __cxa_vec_delete_nid_postfix(array, ElementBytes, Padding, destroy); + require(constructed == Count && destroyed == Count, "default array deletion failed"); __cxa_vec_delete_nid_postfix(nullptr, ElementBytes, Padding, destroy); + } template @@ -170,6 +167,7 @@ void checkRollback(bool sized) { void* array = sized ? __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized) : __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release); + failDestroy = true; expectException([&] { if (sized) __cxa_vec_delete3_nid_postfix(array, ElementBytes, Padding, destroy, releaseSized); @@ -183,6 +181,7 @@ void checkCopyRollback() { failCopy = true; expectException([] { __cxa_vec_cctor_nid_postfix(storage.data(), source.data(), Count, ElementBytes, copy, destroy); + }); require(copied == 2 && destroyed == 2, "copy constructor rollback failed"); } diff --git a/core/libs/tests/GuestAt9Enc.cpp b/core/libs/tests/GuestAt9Enc.cpp index 2da60d41b3..470baa0b57 100644 --- a/core/libs/tests/GuestAt9Enc.cpp +++ b/core/libs/tests/GuestAt9Enc.cpp @@ -1,16 +1,9 @@ +#include "prx/libSceAt9Enc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -int32_t APS5_VABI sceAt9EncClearContext(void* pAt9EncWorkArea, int32_t* pInternalResult); -int32_t APS5_VABI sceAt9EncCreateEncoder(void* pAt9EncWorkArea, void* pAt9EncParam, int32_t memSize, int32_t* pInternalResult); -int32_t APS5_VABI sceAt9EncEncode(void* pAt9EncWorkArea, const void* pInputBuf, void* pOutputBuf, uint32_t inputBufSize, uint32_t outputBufSize, uint32_t* pOutputDataByte, uint32_t* pUsedBufSize, int32_t* pInternalResult); -int32_t APS5_VABI sceAt9EncFlush(void* pAt9EncWorkArea, void* pOutputBuf, uint32_t outputBufSize, uint32_t* pOutputDataByte, int32_t* pInternalResult); -int32_t APS5_VABI sceAt9EncQueryMemSize(void* pAt9EncParam, int32_t* pMemSize, int32_t* pInternalResult); -} - namespace { void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestAtoi.cpp b/core/libs/tests/GuestAtoi.cpp index 31d63e6ccd..0d7be20f60 100644 --- a/core/libs/tests/GuestAtoi.cpp +++ b/core/libs/tests/GuestAtoi.cpp @@ -1,12 +1,8 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include -extern "C" { -int APS5_VABI atoi_nid_postfix(const char*); -int* APS5_VABI __error_nid_postfix(); -} - int main() { struct Case { const char* text; diff --git a/core/libs/tests/GuestAtomicBuiltins.cpp b/core/libs/tests/GuestAtomicBuiltins.cpp index 43d9a0f010..7ac75e5ea4 100644 --- a/core/libs/tests/GuestAtomicBuiltins.cpp +++ b/core/libs/tests/GuestAtomicBuiltins.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,22 +6,6 @@ #include #include -using Uint128 = unsigned __int128; - -extern "C" { -void APS5_VABI __atomic_load_nid_postfix(std::size_t, void*, void*, int); -void APS5_VABI __atomic_store_nid_postfix(std::size_t, void*, void*, int); -void APS5_VABI __atomic_exchange_nid_postfix(std::size_t, void*, void*, void*, int); -bool APS5_VABI __atomic_compare_exchange_nid_postfix(std::size_t, void*, void*, void*, int, int); -bool APS5_VABI __atomic_is_lock_free_nid_postfix(std::size_t, const void*); -Uint128 APS5_VABI __sync_fetch_and_add_16_nid_postfix(Uint128*, Uint128); -Uint128 APS5_VABI __sync_fetch_and_sub_16_nid_postfix(Uint128*, Uint128); -Uint128 APS5_VABI __sync_fetch_and_and_16_nid_postfix(Uint128*, Uint128); -Uint128 APS5_VABI __sync_fetch_and_or_16_nid_postfix(Uint128*, Uint128); -Uint128 APS5_VABI __sync_fetch_and_xor_16_nid_postfix(Uint128*, Uint128); -Uint128 APS5_VABI __sync_lock_test_and_set_16_nid_postfix(Uint128*, Uint128); -} - static void Require(bool value) { if (!value) std::abort(); } template diff --git a/core/libs/tests/GuestAudio3dParameters.cpp b/core/libs/tests/GuestAudio3dParameters.cpp index 4335f1ff0f..8382df155a 100644 --- a/core/libs/tests/GuestAudio3dParameters.cpp +++ b/core/libs/tests/GuestAudio3dParameters.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudio3d/include/Audio3d.hpp" #include "SceTypes.hpp" #include #include @@ -5,8 +6,6 @@ #include #include -extern "C" void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* parameters); - static_assert(sizeof(Audio3dOpenParameters) == 0x28); static_assert(offsetof(Audio3dOpenParameters, num_beds) == 0x20); @@ -74,4 +73,5 @@ int main() { CheckInvalidDestination(); CheckShortBuffer(); CheckExtendedBuffer(); + Require(sceAudio3dInitialize(-1) == static_cast(0x80EA0004), "non-zero reserved parameter rejected in sceAudio3dInitialize"); } diff --git a/core/libs/tests/GuestAudio3dPort.cpp b/core/libs/tests/GuestAudio3dPort.cpp index 5b5467437d..5833c4a051 100644 --- a/core/libs/tests/GuestAudio3dPort.cpp +++ b/core/libs/tests/GuestAudio3dPort.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudio3d/include/Audio3d.hpp" #include "SceTypes.hpp" #include #include @@ -9,29 +10,6 @@ #include #include -extern "C" { -void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* parameters); -int APS5_VABI sceAudio3dInitialize(std::int64_t reserved); -int APS5_VABI sceAudio3dTerminate(); -int APS5_VABI sceAudio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters, std::uint32_t* id); -int APS5_VABI sceAudio3dPortClose(std::uint32_t port_id); -int APS5_VABI sceAudio3dPortSetAttribute(std::uint32_t port_id, std::uint32_t attribute_id, const void* attribute, std::size_t attribute_size); -int APS5_VABI sceAudio3dPortGetAttributesSupported(std::uint32_t port_id, std::uint32_t* attributes, std::uint32_t* num_attributes); -int APS5_VABI sceAudio3dPortGetQueueLevel(std::uint32_t port_id, std::uint32_t* queue_level, std::uint32_t* queue_available); -int APS5_VABI sceAudio3dPortAdvance(std::uint32_t port_id); -int APS5_VABI sceAudio3dPortPush(std::uint32_t port_id, std::uint32_t blocking); -int APS5_VABI sceAudio3dPortFlush(std::uint32_t port_id); -int APS5_VABI sceAudio3dObjectReserve(std::uint32_t port_id, std::uint32_t* object_id); -int APS5_VABI sceAudio3dObjectUnreserve(std::uint32_t port_id, std::uint32_t object_id); -int APS5_VABI sceAudio3dObjectSetAttributes(std::uint32_t port_id, std::uint32_t object_id, std::uint64_t num_attributes, const Audio3dAttribute* attribute_array); -int APS5_VABI sceAudio3dBedWrite(std::uint32_t port_id, std::uint32_t num_channels, std::uint32_t format, const void* buffer, std::uint32_t num_samples); -int APS5_VABI sceAudio3dBedWrite2(std::uint32_t port_id, std::uint32_t num_channels, std::uint32_t format, const void* buffer, std::uint32_t num_samples, std::uint32_t output_route, bool restricted); -int APS5_VABI sceAudio3dAudioOutOpen(std::uint32_t port_id, int user_id, std::int32_t type, std::int32_t index, std::uint32_t len, std::uint32_t freq, std::uint32_t param); -int APS5_VABI sceAudio3dAudioOutClose(std::int32_t handle); -int APS5_VABI sceAudio3dAudioOutOutput(std::int32_t handle, const void* ptr); -int APS5_VABI sceAudio3dAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num); -} - namespace { constexpr int INVALID_PORT = static_cast(0x80EA0002); @@ -155,10 +133,11 @@ void CheckAttributes() { Require(sceAudio3dPortGetAttributesSupported(0, nullptr, &num_attributes) == 0 && num_attributes == 3, "null output reports the supported attribute count"); Require(sceAudio3dPortGetAttributesSupported(1, nullptr, &num_attributes) == INVALID_PORT, "supported attributes reject an invalid port"); Require(sceAudio3dPortGetAttributesSupported(0, nullptr, nullptr) == INVALID_PARAMETER, "supported attributes require a count pointer"); - num_attributes = 2; - std::uint32_t partial[3]{0, 0, 99}; - Require(sceAudio3dPortGetAttributesSupported(0, partial, &num_attributes) == 0 && num_attributes == 2, "supported attributes respect the caller capacity"); - Require(partial[0] == supported[0] && partial[1] == supported[1] && partial[2] == 99, "supported attributes copy only the requested entries"); + Require(sceAudio3dPortGetAttributesSupported(1, nullptr, nullptr) == INVALID_PORT, "port validation precedes count validation"); + num_attributes = 0; + std::uint32_t output[4]{0, 0, 0, 99}; + Require(sceAudio3dPortGetAttributesSupported(0, output, &num_attributes) == 0 && num_attributes == 3, "the count is output only"); + Require(output[0] == supported[0] && output[1] == supported[1] && output[2] == supported[2] && output[3] == 99, "the complete list is written without touching the following element"); num_attributes = 3; std::uint32_t all[3]{}; Require(sceAudio3dPortGetAttributesSupported(0, all, &num_attributes) == 0 && num_attributes == 3, "all supported attributes are returned"); diff --git a/core/libs/tests/GuestAudio3dSpeakerArray.cpp b/core/libs/tests/GuestAudio3dSpeakerArray.cpp new file mode 100644 index 0000000000..4a70481074 --- /dev/null +++ b/core/libs/tests/GuestAudio3dSpeakerArray.cpp @@ -0,0 +1,62 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" +#include "prx/libSceAudio3d/include/Audio3d.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +namespace { + +constexpr int INVALID_PARAMETER = static_cast(0x80EA0004); + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +template +void RequireThrows(F f, const char* message) { + bool threw = false; + try { + f(); + } catch (const std::exception&) { + threw = true; + } + Require(threw, message); +} + +} + +int main() { + Require(sceAudio3dGetSpeakerArrayMemorySize(4, 1) == sceAudioOut2GetSpeakerArrayMemorySize(4, 1, 0), "memory size forwards to AudioOut2 without ambisonics"); + RequireThrows([] { sceAudio3dGetSpeakerArrayMemorySize(0, 0); }, "zero speakers throws invalid_argument"); + + const AudioOut2Position positions[4] = {{1, 1, 0}, {1, -1, 0}, {-1, 1, 0}, {-1, -1, 0}}; + alignas(16) unsigned char memory[1024] = {}; + const std::size_t size = sceAudio3dGetSpeakerArrayMemorySize(4, 0); + Require(size <= sizeof(memory), "speaker array fits the test buffer"); + const Audio3dSpeakerArrayParameters parameters{positions, 4, 0, memory, size}; + + AudioOut2SpeakerArrayHandle handle = nullptr; + Require(sceAudio3dCreateSpeakerArray(nullptr, ¶meters) == INVALID_PARAMETER, "null handle is an invalid parameter"); + Require(sceAudio3dCreateSpeakerArray(&handle, nullptr) == INVALID_PARAMETER, "null parameters are an invalid parameter"); + Require(handle == nullptr, "a rejected create leaves the handle"); + Require(sceAudio3dCreateSpeakerArray(&handle, ¶meters) == 0, "create succeeds without ambisonics parameters"); + Require(handle != nullptr, "create stores the handle"); + + float coefficients[4] = {-1, -1, -1, -1}; + Require(sceAudio3dGetSpeakerArrayMixCoefficients(handle, AudioOut2Position{1, 0, 0}, 0.0f, coefficients, 4) == 0, "mix coefficients succeed"); + Require(coefficients[2] == 0.0f && coefficients[3] == 0.0f, "mix coefficients are written"); + float coefficients2[4] = {-1, -1, -1, -1}; + Require(sceAudio3dGetSpeakerArrayMixCoefficients2(handle, AudioOut2Position{1, 0, 0}, 0.0f, coefficients2, 4, 1, 0.5f) == 0, "mix coefficients 2 succeed"); + Require(coefficients2[3] == 0.0f, "mix coefficients 2 are written"); + RequireThrows([&] { sceAudio3dGetSpeakerArrayMixCoefficients(handle, AudioOut2Position{1, 0, 0}, 0.0f, coefficients, 3); }, "a coefficient count that differs from the array is rejected"); + RequireThrows([&] { sceAudio3dGetSpeakerArrayMixCoefficients(handle, AudioOut2Position{1, 0, 0}, 0.0f, nullptr, 4); }, "null coefficients pointer throws invalid_argument"); + + Require(sceAudio3dDeleteSpeakerArray(handle) == 0, "delete succeeds"); + RequireThrows([&] { sceAudio3dDeleteSpeakerArray(handle); }, "a deleted array is no longer valid"); + return 0; +} diff --git a/core/libs/tests/GuestAudioIn.cpp b/core/libs/tests/GuestAudioIn.cpp index 124bc9a880..f968624ea3 100644 --- a/core/libs/tests/GuestAudioIn.cpp +++ b/core/libs/tests/GuestAudioIn.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudioIn/include/AudioIn.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -11,19 +12,6 @@ #include #include -extern "C" { -int APS5_VABI sceAudioInInit(); -int APS5_VABI sceAudioInOpen(int, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioInHqOpen(int, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAudioInAsyncOpen(int, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t); -int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t); -int32_t APS5_VABI sceAudioInGetGain(int32_t, float*); -int32_t APS5_VABI sceAudioInGetRerouteCount(int32_t); -int APS5_VABI sceAudioInInput(int, void*); -int APS5_VABI sceAudioInGetSilentState(int); -int APS5_VABI sceAudioInClose(int); -} - static void Require(bool value) { if (!value) std::abort(); } template @@ -36,16 +24,6 @@ static bool ReportsNotImplemented(const char* name, TCall call) { return false; } -template -static bool ThrowsRuntimeError(TCall call) { - try { - call(); - } catch (const std::runtime_error&) { - return true; - } - return false; -} - static void SetEnvironment(const char* name, const std::string& value) { #ifdef _WIN32 _putenv_s(name, value.c_str()); @@ -58,11 +36,27 @@ namespace { constexpr int user = 0x10000000; +void TestOpenEx() { + constexpr int invalidSize = static_cast(0x80260102); + constexpr int invalidFreq = static_cast(0x80260103); + constexpr int invalidType = static_cast(0x80260104); + Require(sceAudioInOpenEx(user, 2, 0, 7, 256, 48000, 2) == invalidType); + Require(sceAudioInOpenEx(user, 0, 0, 7, 512, 48000, 2) == invalidSize); + Require(sceAudioInOpenEx(user, 0, 0, 7, 256, 44100, 2) == invalidFreq); + Require(sceAudioInHqOpenEx(user, 0, 0, 7, 128, 44100, 2) == invalidFreq); + int port = sceAudioInOpenEx(user, 0, 0, 7, 256, 48000, 2); + Require(port > 0 && sceAudioInClose(port) == 0); + port = sceAudioInHqOpenEx(user, 0, 0, 7, 256, 16000, 2); + Require(port > 0 && sceAudioInClose(port) == 0); + Require(sceAudioInSetCompressorPreGain() == 0 && sceAudioInSetNoiseGateThreshold() == 0); +} + void TestValidation() { constexpr int invalidHandle = static_cast(0x80260101); constexpr int invalidSize = static_cast(0x80260102); constexpr int invalidFreq = static_cast(0x80260103); constexpr int invalidType = static_cast(0x80260104); + constexpr int invalidPointer = static_cast(0x80260105); constexpr int invalidParam = static_cast(0x80260106); Require(sceAudioInOpen(user, 2, 0, 256, 48000, 2) == invalidType); Require(sceAudioInOpen(user, 0, 1, 256, 48000, 2) == invalidParam); @@ -80,37 +74,43 @@ void TestValidation() { Require(sceAudioInAsyncOpen(user, 0, 0, 385, 48000, 2) == invalidSize); Require(sceAudioInAsyncOpen(user, 0, 0, 200, 48000, 2) == invalidSize); Require(sceAudioInAsyncOpen(user, 0, 0, 256, 44100, 2) == invalidFreq); - Require(sceAudioInAsyncOpen(user, 0, 0, 256, 48000, 0) == invalidParam); + Require(sceAudioInAsyncOpen(user, 0, 0, 256, 48000, 0) == invalidSize); Require(sceAudioInHqOpen(user, 0, 0, 256, 48000, 2) == invalidSize); Require(sceAudioInHqOpen(user, 0, 0, 128, 16000, 2) == invalidFreq); for (const int badHandle : {-1, 0, 1, 9}) { float gain = -2.5f; Require(sceAudioInGetGain(badHandle, &gain) == invalidHandle && gain == 1.0f); Require(sceAudioInGetRerouteCount(badHandle) == invalidHandle); - Require(ThrowsRuntimeError([badHandle] { sceAudioInGetGain(badHandle, nullptr); })); + Require(sceAudioInGetGain(badHandle, nullptr) == invalidPointer); } - int port = sceAudioInAsyncOpen(user, 0, 0, 384, 48000, 2); + int port = sceAudioInAsyncOpen(user, 0, 0, 128, 48000, 2); float gain = 0.0f; Require(port > 0 && sceAudioInGetGain(port, &gain) == 0 && gain == 1.0f && sceAudioInGetRerouteCount(port) == 0); - Require(ThrowsRuntimeError([port] { sceAudioInGetGain(port, nullptr); })); + Require(sceAudioInGetGain(port, nullptr) == invalidPointer); Require(sceAudioInClose(port) == 0); gain = 0.25f; Require(sceAudioInGetGain(port, &gain) == invalidHandle && gain == 1.0f && sceAudioInGetRerouteCount(port) == invalidHandle); port = sceAudioInOpen(user, 0, 0, 256, 48000, 0x10); gain = -1.0f; Require(port > 0 && sceAudioInGetGain(port, &gain) == 0 && gain == 1.0f && sceAudioInGetRerouteCount(port) == 0 && sceAudioInClose(port) == 0); - port = sceAudioInHqOpen(user, 0, 0, 128, 48000, 0x10); - gain = 2.0f; - Require(port > 0 && sceAudioInGetGain(port, &gain) == 0 && gain == 1.0f && sceAudioInGetRerouteCount(port) == 0 && sceAudioInClose(port) == 0); - port = sceAudioInAsyncOpen(user, 0, 0, 384, 48000, 0x10); + for (const std::uint32_t format : {0u, 1u, 3u, 0x10u, 0x11u, 0x12u}) Require(sceAudioInHqOpen(user, 0, 0, 128, 48000, format) == invalidParam); + for (const std::uint32_t size : {0u, 127u, 129u, 256u, 384u}) Require(sceAudioInAsyncOpen(user, 0, 0, size, 48000, 2) == invalidSize); + Require(sceAudioInHqOpen(user, 2, 1, 256, 16000, 0) == invalidFreq); + Require(sceAudioInAsyncOpen(user, 2, 1, 256, 16000, 0) == invalidFreq); + Require(sceAudioInHqOpen(user, 2, 1, 256, 48000, 0) == invalidSize); + Require(sceAudioInAsyncOpen(user, 2, 1, 256, 48000, 0) == invalidSize); + port = sceAudioInAsyncOpen(user, 0, 0, 128, 48000, 0x10); gain = 0.5f; Require(port > 0 && sceAudioInGetGain(port, &gain) == 0 && gain == 1.0f && sceAudioInGetRerouteCount(port) == 0 && sceAudioInClose(port) == 0); - Require(ReportsNotImplemented("sceAudioInIsPadEmbeddedMic", [] { sceAudioInIsPadEmbeddedMic(1); })); + Require(sceAudioInIsPadEmbeddedMic(1) == static_cast(0x80260101)); + const int mic = sceAudioInOpen(user, 0, 0, 256, 48000, 0x10); + Require(mic > 0 && sceAudioInIsPadEmbeddedMic(mic) == 0 && sceAudioInClose(mic) == 0); + Require(sceAudioInIsPadEmbeddedMic(mic) == static_cast(0x80260101)); } -void InputSound(int handle, std::vector& block) { +void InputSound(int handle, std::vector& block, int samples = 256) { for (int attempt = 0; attempt < 200; ++attempt) { - Require(sceAudioInInput(handle, block.data()) == 256); + Require(sceAudioInInput(handle, block.data()) == samples); if (std::any_of(block.begin(), block.end() - 1, [](std::int16_t sample) { return sample != 0; })) return; } Require(false); @@ -125,6 +125,7 @@ void TestCapture() { SetEnvironment("SDL_DISKAUDIOFILEIN", path.string()); TestValidation(); + TestOpenEx(); const int handle = sceAudioInOpen(user, 0, 0, 256, 48000, 2); Require(handle > 0 && sceAudioInGetSilentState(handle) == 0); std::vector block(256 * 2 + 1, 0x5555); @@ -138,9 +139,10 @@ void TestCapture() { Require(sceAudioInInput(handle, block.data()) == 256); for (std::size_t i = 0; i < 256 * 2; ++i) Require(block[i] == 0); Require(sceAudioInClose(handle) == 0); - const int async = sceAudioInAsyncOpen(user, 0, 0, 256, 48000, 2); + const int async = sceAudioInAsyncOpen(user, 0, 0, 128, 48000, 2); Require(async == handle && sceAudioInGetSilentState(async) == 0); - InputSound(async, block); + block.assign(128 * 2 + 1, 0x5555); + InputSound(async, block, 128); Require(std::equal(block.begin(), block.end() - 1, recorded.begin()) && block.back() == 0x5555); Require(sceAudioInClose(async) == 0); std::filesystem::remove(path); @@ -152,6 +154,7 @@ void TestNoDevice() { SetEnvironment("SDL_AUDIODRIVER", "none"); TestValidation(); + TestOpenEx(); const int handle = sceAudioInOpen(user, 0, 0, 256, 16000, 0x12); Require(handle > 0 && sceAudioInGetSilentState(handle) == 1); Require(sceAudioInGetSilentState(handle + 1) == invalidHandle); @@ -165,7 +168,7 @@ void TestNoDevice() { Require(buffer[256 * 8] == 0xCC); Require(sceAudioInInput(handle, nullptr) == 0); const int hq = sceAudioInHqOpen(user, 0, 0, 128, 48000, 2); - const int async = sceAudioInAsyncOpen(user, 1, 0, 256, 16000, 0x11); + const int async = sceAudioInAsyncOpen(user, 1, 0, 128, 48000, 0x11); Require(hq > 0 && async > 0 && hq != handle && async != handle && hq != async); Require(sceAudioInGetSilentState(hq) == 1 && sceAudioInGetSilentState(async) == 1); std::fill(buffer.begin(), buffer.end(), 0xCC); @@ -173,13 +176,13 @@ void TestNoDevice() { for (std::size_t i = 0; i < 128 * 4; ++i) Require(buffer[i] == 0); Require(buffer[128 * 4] == 0xCC); std::fill(buffer.begin(), buffer.end(), 0xCC); - Require(sceAudioInInput(async, buffer.data()) == 256); - for (std::size_t i = 0; i < 256 * 4; ++i) Require(buffer[i] == 0); - Require(buffer[256 * 4] == 0xCC); + Require(sceAudioInInput(async, buffer.data()) == 128); + for (std::size_t i = 0; i < 128 * 4; ++i) Require(buffer[i] == 0); + Require(buffer[128 * 4] == 0xCC); Require(sceAudioInInput(async, nullptr) == 0); for (int port = 3; port < 8; ++port) Require(sceAudioInOpen(user, 1, 0, 128, 48000, 1) > 0); Require(sceAudioInOpen(user, 1, 0, 128, 48000, 1) == portFull); - Require(sceAudioInHqOpen(user, 1, 0, 128, 48000, 1) == portFull); + Require(sceAudioInHqOpen(user, 1, 0, 128, 48000, 2) == portFull); Require(sceAudioInAsyncOpen(user, 1, 0, 128, 48000, 1) == portFull); Require(sceAudioInClose(hq) == 0); Require(sceAudioInClose(hq) == invalidHandle); @@ -187,7 +190,7 @@ void TestNoDevice() { Require(sceAudioInAsyncOpen(user, 0, 0, 128, 48000, 2) == hq); Require(sceAudioInClose(async) == 0); Require(sceAudioInInput(async, buffer.data()) == invalidHandle); - Require(sceAudioInHqOpen(user, 1, 0, 128, 48000, 0x12) == async); + Require(sceAudioInHqOpen(user, 1, 0, 128, 48000, 2) == async); Require(sceAudioInClose(handle) == 0); Require(sceAudioInClose(handle) == invalidHandle); Require(sceAudioInInput(handle, buffer.data()) == invalidHandle); diff --git a/core/libs/tests/GuestAudioPropagation.cpp b/core/libs/tests/GuestAudioPropagation.cpp index c4d112d342..b1435209ae 100644 --- a/core/libs/tests/GuestAudioPropagation.cpp +++ b/core/libs/tests/GuestAudioPropagation.cpp @@ -1,16 +1,19 @@ +#include "prx/libSceAudioPropagation/include/Api.hpp" #include "prx/libSceAudioPropagation/AudioPropagation.hpp" #include +#include #include #include #include +#include #include -extern "C" { -int APS5_VABI sceAudioPropagationPathGetNumPoints(); -int APS5_VABI sceAudioPropagationSystemLock(); -int APS5_VABI sceAudioPropagationMaterialInit(); -} +#ifdef _WIN32 +#include +#else +#include +#endif namespace { @@ -33,6 +36,16 @@ void RequireThrows(TCall call, const char* message) { Require(false, message); } +void* Resolve(const char* name) { +#ifdef _WIN32 + const HMODULE module = GetModuleHandleA("libSceAudioPropagation.prx"); + Require(module != nullptr, "library module"); + return reinterpret_cast(GetProcAddress(module, name)); +#else + return dlsym(RTLD_DEFAULT, name); +#endif +} + AudioPropagationSystemMemory Memory() { AudioPropagationSystemMemory memory{}; memory.desc = {AudioPropagation::SystemMemoryId, AudioPropagation::SystemMemorySize}; @@ -50,38 +63,69 @@ AudioPropagation::Attribute Attribute(const void* value, std::size_t size) { } int main() { - const std::uint64_t options[7]{}; - alignas(16) std::uint8_t arena[64]{}; + const AudioPropagation::SystemOptions options{{AudioPropagation::SystemOptionsId, AudioPropagation::SystemOptionsSize}, 2, 2, 6, 1, 256, 343.0f, 343.0f, 0, {}}; auto memory = Memory(); - Require(sceAudioPropagationSystemQueryMemory(options, &memory) == 0, "query memory failed"); - Require(memory.size_cpu_mem != 0 && memory.size_cpu_mem <= sizeof(arena), "cpu size outside the arena"); + Require(sceAudioPropagationSystemQueryMemory(&options, &memory) == 0, "query memory failed"); + Require(memory.size_cpu_mem == 557264, "native CPU memory size mismatch"); + std::vector arenaStorage((memory.size_cpu_mem + 7) / 8); + auto* arena = reinterpret_cast(arenaStorage.data()); Require(memory.size_gpu_mem == 0 && memory.p_cpu_mem == nullptr && memory.p_gpu_mem == nullptr, "query wrote more than the sizes"); - RequireThrows([&] { sceAudioPropagationSystemQueryMemory(nullptr, &memory); }, "null options"); - RequireThrows([&] { sceAudioPropagationSystemQueryMemory(options, nullptr); }, "null memory"); + Require(sceAudioPropagationSystemQueryMemory(nullptr, &memory) == AudioPropagation::ErrorNullPointer, "null options"); + Require(sceAudioPropagationSystemQueryMemory(&options, nullptr) == AudioPropagation::ErrorNullPointer, "null memory"); auto wrong = Memory(); wrong.desc.size = 0x28; - RequireThrows([&] { sceAudioPropagationSystemQueryMemory(options, &wrong); }, "wrong descriptor size"); + Require(sceAudioPropagationSystemQueryMemory(&options, &wrong) == static_cast(0x8A700007), "wrong descriptor size"); wrong = Memory(); wrong.desc.id = AudioPropagation::RayId; - RequireThrows([&] { sceAudioPropagationSystemQueryMemory(options, &wrong); }, "wrong descriptor id"); + Require(sceAudioPropagationSystemQueryMemory(&options, &wrong) == static_cast(0x8A700007), "wrong descriptor id"); + + auto probeOptions = options; + auto probeMemory = Memory(); + probeMemory.p_cpu_mem = arena; + probeMemory.p_gpu_mem = arena; + probeOptions.maxSources = 0; + Require(sceAudioPropagationSystemQueryMemory(&probeOptions, &probeMemory) == AudioPropagation::ErrorInvalidArgument, "zero source capacity accepted"); + Require(probeMemory.p_cpu_mem == nullptr && probeMemory.p_gpu_mem == nullptr && probeMemory.size_cpu_mem == 0 && probeMemory.size_gpu_mem == 0, "invalid options did not clear memory output"); + probeOptions = options; + probeOptions.parameter24 = 7; + Require(sceAudioPropagationSystemQueryMemory(&probeOptions, &probeMemory) == AudioPropagation::ErrorInvalidArgument, "unsupported parameter accepted"); + probeOptions = options; + probeOptions.frameSamples = 768; + Require(sceAudioPropagationSystemQueryMemory(&probeOptions, &probeMemory) == AudioPropagation::ErrorInvalidArgument, "unsupported frame size accepted"); + probeOptions = options; + probeOptions.propagationSpeed = 0x1p-23f; + Require(sceAudioPropagationSystemQueryMemory(&probeOptions, &probeMemory) == AudioPropagation::ErrorInvalidArgument, "invalid propagation speed accepted"); + probeOptions = options; + probeOptions.frameSamples = 512; + Require(sceAudioPropagationSystemQueryMemory(&probeOptions, &probeMemory) == 0 && probeMemory.size_cpu_mem == 524432, "512 sample memory size mismatch"); + probeOptions = options; + probeOptions.reflectionOrder = 2; + Require(sceAudioPropagationSystemQueryMemory(&probeOptions, &probeMemory) == 0 && probeMemory.size_cpu_mem == 1110608, "second order memory size mismatch"); + probeOptions = options; + probeOptions.flags = 30; + Require(sceAudioPropagationSystemQueryMemory(&probeOptions, &probeMemory) == 0 && probeMemory.size_cpu_mem == 2604064, "option memory sizes mismatch"); + probeOptions.flags = 1; + Require(sceAudioPropagationSystemQueryMemory(&probeOptions, &probeMemory) == AudioPropagation::ErrorInvalidArgument, "GPU frame size constraint ignored"); + probeOptions.frameSamples = 512; + RequireThrows([&] { sceAudioPropagationSystemQueryMemory(&probeOptions, &probeMemory); }, "unknown GPU memory query returned a size"); AudioPropagationHandle system = 0; - RequireThrows([&] { sceAudioPropagationSystemCreate(options, &memory, &system); }, "create without cpu memory"); + RequireThrows([&] { sceAudioPropagationSystemCreate(&options, &memory, &system); }, "create without cpu memory"); memory.p_cpu_mem = arena + 1; - RequireThrows([&] { sceAudioPropagationSystemCreate(options, &memory, &system); }, "misaligned cpu memory"); + RequireThrows([&] { sceAudioPropagationSystemCreate(&options, &memory, &system); }, "misaligned cpu memory"); memory.p_cpu_mem = arena; memory.size_gpu_mem = 16; - RequireThrows([&] { sceAudioPropagationSystemCreate(options, &memory, &system); }, "unrequested gpu memory"); + RequireThrows([&] { sceAudioPropagationSystemCreate(&options, &memory, &system); }, "unrequested gpu memory"); memory.size_gpu_mem = 0; const auto required = memory.size_cpu_mem; memory.size_cpu_mem = required - 1; - RequireThrows([&] { sceAudioPropagationSystemCreate(options, &memory, &system); }, "cpu memory too small"); + RequireThrows([&] { sceAudioPropagationSystemCreate(&options, &memory, &system); }, "cpu memory too small"); memory.size_cpu_mem = required; - Require(sceAudioPropagationSystemCreate(options, &memory, &system) == 0, "create failed"); + Require(sceAudioPropagationSystemCreate(&options, &memory, &system) == 0, "create failed"); Require(system == reinterpret_cast(arena), "system handle is not its memory"); AudioPropagationHandle twice = 0; - RequireThrows([&] { sceAudioPropagationSystemCreate(options, &memory, &twice); }, "second system in the same memory"); + RequireThrows([&] { sceAudioPropagationSystemCreate(&options, &memory, &twice); }, "second system in the same memory"); const std::uint64_t value = 1; const auto attribute = Attribute(&value, sizeof(value)); @@ -120,9 +164,54 @@ int main() { RequireThrows([&] { sceAudioPropagationSourceCreate(system, nullptr); }, "source without output"); Require(sceAudioPropagationSourceSetAttributes(source, portalAttributes, 3) == 0, "source attributes failed"); std::uint32_t count = 7; - Require(sceAudioPropagationSourceGetAudioPathCount(source, &count) == 0 && count == 0, "source reports audio paths"); + Require(sceAudioPropagationSourceGetAudioPathCount(source, &count) == 0 && count == AudioPropagation::MaxPaths, "source path pool size"); AudioPropagationHandle path = 0; - RequireThrows([&] { sceAudioPropagationSourceGetAudioPath(source, 0, &path); }, "audio path without paths"); + Require(sceAudioPropagationSourceGetAudioPath(source, 0, &path) == 0 && path != 0, "path lookup immediately after source creation"); + AudioPropagationHandle repeated = 0; + Require(sceAudioPropagationSourceGetAudioPath(source, 0, &repeated) == 0 && repeated == path, "path handle is unstable"); + AudioPropagationHandle lastPath = 0; + Require(sceAudioPropagationSourceGetAudioPath(source, AudioPropagation::MaxPaths - 1, &lastPath) == 0 && lastPath != path, "last path slot"); + Require(sceAudioPropagationSourceGetAudioPath(source, AudioPropagation::MaxPaths, &repeated) == AudioPropagation::ErrorInvalidArgument, "out of range path slot"); + Require(repeated == path, "rejected path lookup changed the output"); + Require(sceAudioPropagationSourceGetAudioPath(source, 0, nullptr) == AudioPropagation::ErrorNullPointer, "null path output"); + Require(sceAudioPropagationSourceGetAudioPath(0, 0, nullptr) == AudioPropagation::ErrorInvalidHandle, "path lookup handle precedence"); + Require(sceAudioPropagationPathGetNumPoints(path, &count) == 0 && count == 0, "initial path has points"); + RequireThrows([&] { sceAudioPropagationPathGetNumPoints(source, &count); }, "source used as a path"); + RequireThrows([&] { sceAudioPropagationPathGetNumPoints(path, nullptr); }, "null point-count output"); + AudioPropagation::AudioPath configured{}; + configured.desc = {AudioPropagation::PathId, sizeof(configured)}; + configured.pointCount = 2; + configured.flags = AudioPropagation::PathInvalid; + for (auto& point : configured.points) { + point.desc = {AudioPropagation::PathPointId, sizeof(point)}; + point.gain = 1.0f; + } + configured.points[0].room = rooms[0]; + configured.points[0].opaque[0] = std::byte{0x7f}; + configured.opaque[0] = std::byte{0x55}; + configured.points[0].position[0] = 3.0f; + Require(sceAudioPropagationSourceSetAudioPath(path, &configured, 1.0f) == 0, "path configuration"); + configured.pointCount = 0; + Require(sceAudioPropagationPathGetNumPoints(path, &count) == 0 && count == 2, "path configuration aliases caller memory"); + configured.pointCount = 2; + auto badPath = configured; + badPath.desc.size--; + RequireThrows([&] { sceAudioPropagationSourceSetAudioPath(path, &badPath, 1.0f); }, "wrong path size"); + badPath = configured; + badPath.pointCount = AudioPropagation::MaxPathPoints + 1; + RequireThrows([&] { sceAudioPropagationSourceSetAudioPath(path, &badPath, 1.0f); }, "too many path points"); + badPath = configured; + AudioPropagationHandle pathMaterial = 0; + Require(sceAudioPropagationSystemRegisterMaterial(system, material, &pathMaterial) == 0, "path material creation"); + badPath.points[0].room = pathMaterial; + RequireThrows([&] { sceAudioPropagationSourceSetAudioPath(path, &badPath, 1.0f); }, "material used as a path room"); + Require(sceAudioPropagationSystemUnregisterMaterial(pathMaterial) == 0, "path material cleanup"); + badPath = configured; + badPath.points[0].position[0] = std::numeric_limits::infinity(); + RequireThrows([&] { sceAudioPropagationSourceSetAudioPath(path, &badPath, 1.0f); }, "nonfinite point position"); + RequireThrows([&] { sceAudioPropagationSourceSetAudioPath(path, &configured, std::numeric_limits::quiet_NaN()); }, "nonfinite path gain"); + RequireThrows([&] { sceAudioPropagationSourceSetAudioPath(path, nullptr, 1.0f); }, "null path data"); + Require(sceAudioPropagationPathGetNumPoints(path, &count) == 0 && count == 2, "rejected update changed path state"); std::uint8_t rays[AudioPropagation::RaySize * 2]{}; count = 7; @@ -132,27 +221,40 @@ int main() { RequireThrows([&] { sceAudioPropagationSourceGetRays(source, rays, nullptr); }, "rays without count"); Require(sceAudioPropagationSystemSetRays(system, rays, 0) == 0, "empty ray results failed"); RequireThrows([&] { sceAudioPropagationSystemSetRays(system, rays, 1); }, "unrequested ray results"); - Require(sceAudioPropagationSourceCalculateAudioPaths(source, rays, 0, 1, nullptr, 0) == 0, "empty path calculation failed"); - RequireThrows([&] { sceAudioPropagationSourceCalculateAudioPaths(source, rays, 1, 1, nullptr, 0); }, "path calculation with rays"); + Require(sceAudioPropagationSourceCalculateAudioPaths(source, rays, 0, 1, nullptr, 0) == AudioPropagation::ErrorNullPointer, "path calculation null output"); + Require(sceAudioPropagationSourceCalculateAudioPaths(0, nullptr, 0, 0, nullptr, 0) == AudioPropagation::ErrorInvalidHandle, "path calculation handle precedence"); + Require(sceAudioPropagationSourceCalculateAudioPaths(source, rays, 0, 1, rays, 6) == AudioPropagation::ErrorInvalidArgument, "path calculation empty rays"); + Require(sceAudioPropagationSourceCalculateAudioPaths(source, rays, 6, 0, rays, 6) == AudioPropagation::ErrorInvalidArgument, "path calculation zero reflection order"); + Require(sceAudioPropagationSourceCalculateAudioPaths(source, rays, 6, 1, rays, 0) == AudioPropagation::ErrorInvalidArgument, "path calculation zero output capacity"); + RequireThrows([&] { sceAudioPropagationSourceCalculateAudioPaths(source, rays, 6, 1, rays, 6); }, "unsupported path calculation"); Require(sceAudioPropagationSourceSetAudioPaths(source, nullptr, 0) == 0, "empty audio paths failed"); RequireThrows([&] { sceAudioPropagationSourceSetAudioPaths(source, rays, 1); }, "set audio paths"); - alignas(16) std::uint8_t otherArena[64]{}; + std::vector otherArenaStorage(arenaStorage.size()); + auto* otherArena = reinterpret_cast(otherArenaStorage.data()); auto otherMemory = Memory(); - Require(sceAudioPropagationSystemQueryMemory(options, &otherMemory) == 0, "second query memory failed"); + Require(sceAudioPropagationSystemQueryMemory(&options, &otherMemory) == 0, "second query memory failed"); otherMemory.p_cpu_mem = otherArena; AudioPropagationHandle otherSystem = 0; - Require(sceAudioPropagationSystemCreate(options, &otherMemory, &otherSystem) == 0, "second system create failed"); + Require(sceAudioPropagationSystemCreate(&options, &otherMemory, &otherSystem) == 0, "second system create failed"); AudioPropagationHandle otherSource = 0; Require(sceAudioPropagationSourceCreate(otherSystem, &otherSource) == 0, "second system source create failed"); + AudioPropagationHandle foreignRoom = 0; + Require(sceAudioPropagationRoomCreate(otherSystem, &foreignRoom) == 0, "foreign room create failed"); + badPath = configured; + badPath.points[0].room = foreignRoom; + RequireThrows([&] { sceAudioPropagationSourceSetAudioPath(path, &badPath, 1.0f); }, "room from another system"); + Require(sceAudioPropagationRoomDestroy(otherSystem, foreignRoom) == 0, "foreign room cleanup"); + Require(sceAudioPropagationPathGetNumPoints(path, &count) == 0 && count == 2, "cross-system rejection changed path state"); AudioPropagationHandle secondSource = 0; Require(sceAudioPropagationSourceCreate(system, &secondSource) == 0, "second source create failed"); - float outputs[2][64]; - float expected[2][64]; + constexpr std::size_t frameValues = 256 * 16; + float outputs[2][frameValues + 16]; + float expected[2][frameValues + 16]; const auto fill = [&] { for (std::size_t channel = 0; channel < 2; ++channel) { - for (std::size_t sample = 0; sample < 64; ++sample) { + for (std::size_t sample = 0; sample < frameValues + 16; ++sample) { outputs[channel][sample] = static_cast(sample + 1) / (channel == 0 ? 128.0f : -128.0f); } } @@ -168,8 +270,15 @@ int main() { reset(); Require(sceAudioPropagationSourceRender(system, infos, 1) == 0, "render failed"); Require(untouched(), "empty source erased an existing mix"); + infos[0].channelCount = 99; + Require(sceAudioPropagationSourceRender(system, infos, 1) == 0 && infos[0].channelCount == 0, "fixed render format clears the channel count"); + Require(untouched(), "render wrote past the output size"); reset(); - infos[0].outputSize = sizeof(float) * 16; + infos[0].outputSize = sizeof(float) * (frameValues - 1); + RequireThrows([&] { sceAudioPropagationSourceRender(system, infos, 2); }, "short frame output accepted"); + Require(untouched(), "short output modified a render buffer"); + reset(); + infos[0].outputSize = sizeof(float) * frameValues; Require(sceAudioPropagationSourceRender(system, infos, 2) == 0, "render of two sources failed"); Require(untouched(), "empty sources modified separate destination mixes or their tails"); reset(); @@ -177,9 +286,9 @@ int main() { Require(sceAudioPropagationSourceRender(system, infos, 2) == 0, "render into a shared mix failed"); Require(untouched(), "empty sources modified a shared destination mix"); reset(); - infos[0].outputSize = sizeof(float) * 32; + infos[0].outputSize = sizeof(float) * frameValues; infos[1].output = outputs[0] + 16; - infos[1].outputSize = sizeof(float) * 32; + infos[1].outputSize = sizeof(float) * frameValues; Require(sceAudioPropagationSourceRender(system, infos, 2) == 0, "render into overlapping mixes failed"); Require(untouched(), "empty sources modified overlapping destination mixes"); reset(); @@ -190,6 +299,7 @@ int main() { reset(); RequireThrows([&] { sceAudioPropagationSourceRender(system, infos, 0); }, "render without infos"); + RequireThrows([&] { sceAudioPropagationSourceRender(system, infos, 3); }, "render exceeds source capacity"); RequireThrows([&] { sceAudioPropagationSourceRender(system, nullptr, 1); }, "render with null infos"); RequireThrows([&] { sceAudioPropagationSourceRender(system + 16, infos, 1); }, "render on an unknown system"); infos[1].desc.id = AudioPropagation::RayId; @@ -210,10 +320,36 @@ int main() { reset(); infos[0].outputSize = 0; RequireThrows([&] { sceAudioPropagationSourceRender(system, infos, 1); }, "render with an empty output"); + for (const std::uint32_t format : {0u, 1u, 2u, 4u, 5u}) { + reset(); + infos[0].format = format; + const std::size_t channels = format == 0 ? 4 : format == 1 ? 9 : format == 2 ? 16 : 8; + infos[0].outputSize = sizeof(float) * options.frameSamples * channels; + Require(sceAudioPropagationSourceRender(system, infos, 1) == 0, "render in a known format failed"); + Require(untouched(), "empty source modified a mix in a known format"); + infos[0].outputSize -= sizeof(float) * channels; + RequireThrows([&] { sceAudioPropagationSourceRender(system, infos, 1); }, "known format accepted an undersized frame"); + Require(untouched(), "undersized known-format output modified a mix"); + } reset(); - infos[1].format = 1; - RequireThrows([&] { sceAudioPropagationSourceRender(system, infos, 2); }, "render with an unknown format"); - Require(untouched(), "render with an unknown format wrote an output"); + infos[0].format = 1; + infos[0].outputSize = sizeof(float) * (options.frameSamples * 9 + 1); + RequireThrows([&] { sceAudioPropagationSourceRender(system, infos, 1); }, "render of a partial nine-channel frame"); + Require(untouched(), "render of a partial frame wrote an output"); + for (const std::uint32_t format : {3u, 6u}) { + reset(); + infos[1].format = format; + RequireThrows([&] { sceAudioPropagationSourceRender(system, infos, 2); }, "render with an unknown format"); + Require(untouched(), "render with an unknown format wrote an output"); + } + + reset(); + configured.flags = 0; + Require(sceAudioPropagationSourceSetAudioPath(path, &configured, 1.0f) == 0, "active path configuration"); + RequireThrows([&] { sceAudioPropagationSourceRender(system, infos, 2); }, "active path silently rendered"); + Require(untouched(), "unsupported active-path render modified output"); + configured.flags = AudioPropagation::PathInvalid; + Require(sceAudioPropagationSourceSetAudioPath(path, &configured, 1.0f) == 0, "invalidating path"); Require(sceAudioPropagationSourceDestroy(system, secondSource) == 0, "second source destroy failed"); reset(); @@ -224,13 +360,18 @@ int main() { RequireThrows([&] { sceAudioPropagationSourceDestroy(system + 16, source); }, "source destroy with another system"); Require(sceAudioPropagationPortalDestroy(system, portal) == 0, "portal destroy failed"); Require(sceAudioPropagationSourceDestroy(system, source) == 0, "source destroy failed"); + RequireThrows([&] { sceAudioPropagationPathGetNumPoints(path, &count); }, "path survived source destruction"); + RequireThrows([&] { sceAudioPropagationSourceSetAudioPath(lastPath, &configured, 1.0f); }, "path update after source destruction"); Require(sceAudioPropagationRoomDestroy(system, rooms[0]) == 0, "room destroy failed"); AudioPropagationHandle lateSource = 0; AudioPropagationHandle lateMaterial = 0; Require(sceAudioPropagationSourceCreate(system, &lateSource) == 0, "late source create failed"); + AudioPropagationHandle latePath = 0; + Require(sceAudioPropagationSourceGetAudioPath(lateSource, 0, &latePath) == 0 && latePath != path, "path handle reuse"); Require(sceAudioPropagationSystemRegisterMaterial(system, material, &lateMaterial) == 0, "late material register failed"); Require(sceAudioPropagationSystemDestroy(system) == 0, "system destroy failed"); + RequireThrows([&] { sceAudioPropagationPathGetNumPoints(latePath, &count); }, "path survived system destruction"); RequireThrows([&] { sceAudioPropagationSourceSetAttributes(lateSource, &attribute, 1); }, "source used after its system"); RequireThrows([&] { sceAudioPropagationRoomDestroy(system, lateSource); }, "orphan destroyed as the wrong kind"); RequireThrows([&] { sceAudioPropagationSourceDestroy(system + 16, lateSource); }, "orphan destroyed with another system"); @@ -239,12 +380,15 @@ int main() { Require(sceAudioPropagationSystemUnregisterMaterial(lateMaterial) == 0, "orphaned material unregister is not a no-op"); RequireThrows([&] { sceAudioPropagationSourceDestroy(system, lateSource); }, "orphan destroyed twice"); RequireThrows([&] { sceAudioPropagationSystemDestroy(system); }, "destroy twice"); - Require(sceAudioPropagationSystemCreate(options, &memory, &system) == 0, "memory reuse after destroy failed"); + Require(sceAudioPropagationSystemCreate(&options, &memory, &system) == 0, "memory reuse after destroy failed"); Require(sceAudioPropagationSystemDestroy(system) == 0, "second destroy failed"); - RequireThrows([&] { sceAudioPropagationPathGetNumPoints(); }, "path get num points throw"); - RequireThrows([&] { sceAudioPropagationSystemLock(); }, "system lock throw"); - RequireThrows([&] { sceAudioPropagationMaterialInit(); }, "material init throw"); + RequireThrows([&] { sceAudioPropagationSystemLock(0); }, "system lock throw"); + + Require(Resolve("sceAudioPropagationSystemCreate") != nullptr && Resolve("sceAudioPropagationSourceSetAudioPath") != nullptr, "export lookup"); + for (const char* name : {"sceAudioPropagationAudioPathInit", "sceAudioPropagationAudioPathPointInit", "sceAudioPropagationMaterialInit", "sceAudioPropagationPortalSettingsInit", "sceAudioPropagationRayInit", "sceAudioPropagationSourceRenderInfoInit", "sceAudioPropagationSourceSetAudioPathsParamInit", "sceAudioPropagationSystemMemoryInit", "sceAudioPropagationSystemOptionInit", "sceAudioPropagationSystem"}) { + Require(Resolve(name) == nullptr, name); + } std::puts("AudioPropagation tests passed"); return 0; diff --git a/core/libs/tests/GuestAudiodec.cpp b/core/libs/tests/GuestAudiodec.cpp index 404e7fce05..ed04f8ee5d 100644 --- a/core/libs/tests/GuestAudiodec.cpp +++ b/core/libs/tests/GuestAudiodec.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAudiodec/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,17 +6,9 @@ #include #include #include +#include #include -extern "C" { -std::int32_t APS5_VABI sceAudiodecInitLibrary(std::uint32_t); -std::int32_t APS5_VABI sceAudiodecTermLibrary(std::uint32_t); -std::int32_t APS5_VABI sceAudiodecCreateDecoder(AudiodecCtrl*, std::uint32_t); -std::int32_t APS5_VABI sceAudiodecDeleteDecoder(std::int32_t); -std::int32_t APS5_VABI sceAudiodecDecode(std::int32_t, AudiodecCtrl*); -std::int32_t APS5_VABI sceAudiodecClearContext(std::int32_t); -} - static void Require(bool value) { if (!value) std::abort(); } namespace { @@ -410,6 +403,39 @@ int main() { aac.i_bw_pcm = 2; const std::int32_t floatAac = sceAudiodecCreateDecoder(&aacCtrl, 3); Require(floatAac > 0 && sceAudiodecDeleteDecoder(floatAac) == 0); + for (const std::uint32_t channels : {0u, 3u, 4u, 5u, 7u, 9u}) { + aac.ui_max_channels = channels; + Require(sceAudiodecCreateDecoder(&aacCtrl, 3) == static_cast(0x807F0304)); + } + aac.ui_max_channels = 6; + aac.ui_enable_heaac = 1; + Require(sceAudiodecCreateDecoder(&aacCtrl, 3) == static_cast(0x807F0302)); + struct ExtendedParam { + AudiodecParamM4aac base; + std::uint32_t extensionMode; + std::uint32_t outputMode; + } extended{{32, 1, 1, 3, 2, 0}, 0, 0}; + aacCtrl.pParam = &extended; + for (const std::uint32_t size : {25u, 27u, 29u, 31u, 33u}) { + extended.base.ui_size = size; + Require(sceAudiodecCreateDecoder(&aacCtrl, 3) == errorInvalidParamSize); + } + for (const std::uint32_t size : {28u, 32u}) { + extended.base.ui_size = size; + extended.extensionMode = 2; + Require(sceAudiodecCreateDecoder(&aacCtrl, 3) == static_cast(0x807F0305)); + extended.extensionMode = 1; + bool threw = false; + try { sceAudiodecCreateDecoder(&aacCtrl, 3); } + catch (const std::runtime_error&) { threw = true; } + Require(threw); + extended.extensionMode = 0; + const auto extendedHandle = sceAudiodecCreateDecoder(&aacCtrl, 3); + Require(extendedHandle > 0); + Require(sceAudiodecDeleteDecoder(extendedHandle) == 0); + } + extended.outputMode = 3; + Require(sceAudiodecCreateDecoder(&aacCtrl, 3) == static_cast(0x807F0306)); const std::int32_t handle = sceAudiodecCreateDecoder(&ctrl, 2); Require(handle > 0); diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index efa5ff1e00..6b757d9121 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAvPlayer/include/Api.hpp" #include #include #include @@ -21,38 +22,10 @@ #include #include #include "SceTypes.hpp" +#include "prx/libSceAvPlayer/include/AvPlayer.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceAvPlayer/include/AvPlayer.hpp" -extern "C" { -AvPlayerInternal* APS5_VABI sceAvPlayerInit(AvPlayerInitData*); -int APS5_VABI sceAvPlayerInitEx(const AvPlayerInitDataEx*, AvPlayerInternal**); -int APS5_VABI sceAvPlayerPostInit(AvPlayerInternal*, const AvPlayerPostInitData*); -int APS5_VABI sceAvPlayerSetLogCallback(void*, void*); -int APS5_VABI sceAvPlayerAddSource(AvPlayerInternal*, const char*); -int APS5_VABI sceAvPlayerStreamCount(AvPlayerInternal*); -int APS5_VABI sceAvPlayerGetStreamInfo(AvPlayerInternal*, std::uint32_t, AvPlayerStreamInfo*); -int APS5_VABI sceAvPlayerGetStreamInfoEx(AvPlayerInternal*, std::uint32_t, AvPlayerStreamInfoEx*); -int APS5_VABI sceAvPlayerEnableStream(AvPlayerInternal*, std::uint32_t); -int APS5_VABI sceAvPlayerChangeStream(AvPlayerInternal*, std::uint32_t, std::uint32_t); -int APS5_VABI sceAvPlayerSetAvailableBandwidth(AvPlayerInternal*, std::uint32_t, std::uint32_t, std::uint32_t); -int APS5_VABI sceAvPlayerStart(AvPlayerInternal*); -int APS5_VABI sceAvPlayerStartEx(AvPlayerInternal*, const void*); -int APS5_VABI sceAvPlayerStop(AvPlayerInternal*); -int APS5_VABI sceAvPlayerPause(AvPlayerInternal*); -int APS5_VABI sceAvPlayerResume(AvPlayerInternal*); -int APS5_VABI sceAvPlayerJumpToTime(AvPlayerInternal*, std::uint64_t); -int APS5_VABI sceAvPlayerSetLooping(AvPlayerInternal*, Bool); -int APS5_VABI sceAvPlayerSetAvSyncMode(AvPlayerInternal*, std::uint32_t); -int APS5_VABI sceAvPlayerSetTrickSpeed(AvPlayerInternal*, std::int32_t); -Bool APS5_VABI sceAvPlayerGetVideoDataEx(AvPlayerInternal*, AvPlayerFrameInfoEx*); -Bool APS5_VABI sceAvPlayerGetVideoData(AvPlayerInternal*, AvPlayerFrameInfo*); -Bool APS5_VABI sceAvPlayerGetAudioData(AvPlayerInternal*, AvPlayerFrameInfo*); -Bool APS5_VABI sceAvPlayerIsActive(AvPlayerInternal*); -std::uint64_t APS5_VABI sceAvPlayerCurrentTime(AvPlayerInternal*); -int APS5_VABI sceAvPlayerClose(AvPlayerInternal*); -} - namespace { constexpr int Width = 100; @@ -833,6 +806,8 @@ void TestPlayback() { Check(sceAvPlayerPostInit(player, &post) == 0, "post init failed"); Check(sceAvPlayerPostInit(player, nullptr) == InvalidParams, "post init accepted null data"); Check(sceAvPlayerStreamCount(player) == OperationFailed, "stream count without a source"); + Check(sceAvPlayerSetTrickSpeed(player, 100) == OperationFailed, "trick speed accepted without a source"); + Check(sceAvPlayerSetTrickSpeed(player, 0) == OperationFailed, "trick speed checked the speed before the source"); Check(!sceAvPlayerIsActive(player), "active without a source"); Check(sceAvPlayerAddSource(player, "/app0/missing.mp4") == OperationFailed, "missing file accepted"); Check(sceAvPlayerAddSource(player, "/app0/missing/../avplayer.mp4") == OperationFailed, "missing parent accepted"); @@ -1068,16 +1043,30 @@ void TestExtendedExports() { Check(sceAvPlayerSetAvailableBandwidth(player, 4000, 1000, 8000) == 0, "bandwidth rejected"); Check(sceAvPlayerSetAvailableBandwidth(nullptr, 4000, 1000, 8000) == InvalidParams, "bandwidth accepted for a null player"); Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "add source failed"); + Check(sceAvPlayerSetAvailableBandwidth(player, 4000, 1000, 8000) == OperationFailed, "bandwidth accepted after a source"); Check(WaitFor([&] { return events.Seen(EventReady); }), "ready event missing"); CheckStreamInfoEx(player); Check(sceAvPlayerEnableStream(player, VideoStream) == 0, "enable video failed"); Check(sceAvPlayerStartEx(nullptr, nullptr) == InvalidParams, "start accepted a null player"); - Check(sceAvPlayerStartEx(player, nullptr) == 0, "extended start failed"); + Check(sceAvPlayerStartEx(player, nullptr) == InvalidParams, "start accepted null parameters"); + AvPlayer::StartInfo startInfo{0, 700}; + Check(sceAvPlayerStartEx(player, &startInfo) == InvalidParams, "start accepted an invalid parameter size"); + startInfo.size = sizeof(startInfo); + Check(sceAvPlayerStartEx(player, &startInfo) == 0, "extended start failed"); Check(WaitFor([&] { return events.Seen(EventPlay); }), "play event missing"); AvPlayerFrameInfoEx frame{}; Check(WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }), "no frame after the extended start"); CheckVideoFrame(frame); + Check(frame.timestamp >= 700 && frame.timestamp <= 767, "extended start ignored the requested position"); + Check(!events.Seen(EventWarning, JumpComplete), "extended start reported a jump"); + Check(sceAvPlayerStop(player) == 0, "extended stop failed"); + startInfo.startTime = 0; + Check(sceAvPlayerStartEx(player, &startInfo) == 0, "extended restart failed"); + Check(WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }), "no frame after extended restart"); + Check(frame.timestamp <= 67, "zero-position start did not restart playback"); Check(sceAvPlayerClose(player) == 0, "close failed"); + Check(sceAvPlayerAddSourceEx(nullptr, 0, nullptr) == InvalidParams, "null player add source rejected"); + Check(sceAvPlayerDisableStream(nullptr, 0) == InvalidParams, "null player disable stream rejected"); } void TestWithoutAllocators() { @@ -1596,15 +1585,446 @@ void TestVideoSyncTolerance() { Check(WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }), "sync tolerance first frame missing"); Check(FrameIndex(frame.timestamp) == 0, "first frame not index 0"); AvPlayer::SetAvPlayerClockForTest_nid_no_patch(player, 5.0); - Check(!sceAvPlayerGetVideoDataEx(player, &frame), "frame beyond sync tolerance delivered prematurely"); + Check(!WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }, 200), "frame beyond sync tolerance delivered prematurely"); AvPlayer::SetAvPlayerClockForTest_nid_no_patch(player, 10.0); - Check(sceAvPlayerGetVideoDataEx(player, &frame), "frame within sync tolerance was not delivered"); + Check(WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }), "frame within sync tolerance was not delivered"); Check(FrameIndex(frame.timestamp) == 1, "unexpected second frame index"); Check(sceAvPlayerClose(player) == 0, "sync tolerance close failed"); std::lock_guard lock(allocations.mutex); Check(allocations.blocks.empty(), "sync tolerance allocations leaked"); } +void TestAudioClockDriftGating() { + const auto open = [](Events& events) { + AvPlayerInitData init = InitData(&events); + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "audio drift gating init failed"); + Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "audio drift gating source failed"); + Check(WaitFor([&] { return events.Seen(EventReady); }), "audio drift gating ready missing"); + Check(sceAvPlayerEnableStream(player, EnglishAudioStream) == 0, "audio drift gating audio failed"); + Check(sceAvPlayerStart(player) == 0, "audio drift gating start failed"); + Check(WaitFor([&] { return events.Seen(EventPlay); }), "audio drift gating play missing"); + return player; + }; + const auto close = [](AvPlayerInternal* player) { + Check(sceAvPlayerClose(player) == 0, "audio drift gating close failed"); + std::lock_guard lock(allocations.mutex); + Check(allocations.blocks.empty(), "audio drift gating allocations leaked"); + }; + const auto take = [](AvPlayerInternal* player, AvPlayerFrameInfo& sound, const char* missing) { + Check(WaitFor([&] { return sceAvPlayerGetAudioData(player, &sound) != 0; }), missing); + }; + + std::uint64_t secondTs = 0; + std::uint64_t beyondTs = 0; + { + Events events; + auto* player = open(events); + AvPlayerFrameInfo sound{}; + take(player, sound, "probe first audio frame missing"); + Check(sound.timestamp == 0, "first audio frame timestamp unexpected"); + take(player, sound, "probe second audio frame missing"); + Check(sound.timestamp > 0 && sound.timestamp <= 30, "second audio frame timestamp unexpected"); + secondTs = sound.timestamp; + Check(WaitFor([&] { + while (sceAvPlayerGetAudioData(player, &sound)) { + if (sound.timestamp > 50) { + beyondTs = sound.timestamp; + return true; + } + } + return false; + }), "probe audio beyond 50ms missing"); + close(player); + } + Check(beyondTs > 50, "probe timestamps unsuitable for drift gating"); + + { + Events events; + auto* player = open(events); + AvPlayerFrameInfo sound{}; + take(player, sound, "within-threshold first audio missing"); + AvPlayer::SetAvPlayerClockForTest_nid_no_patch(player, 0.0); + std::this_thread::sleep_for(std::chrono::milliseconds(40)); + take(player, sound, "within-threshold second audio missing"); + Check(sound.timestamp == secondTs, "within-threshold second timestamp changed"); + Check(sceAvPlayerCurrentTime(player) == 0, "clock rebased despite audio being within drift threshold"); + close(player); + } + + { + Events events; + auto* player = open(events); + AvPlayerFrameInfo sound{}; + take(player, sound, "clock-lead boundary first audio missing"); + AvPlayer::SetAvPlayerClockForTest_nid_no_patch(player, static_cast(secondTs + 50)); + std::this_thread::sleep_for(std::chrono::milliseconds(40)); + take(player, sound, "clock-lead boundary second audio missing"); + Check(sound.timestamp == secondTs, "clock-lead boundary second timestamp changed"); + Check(sceAvPlayerCurrentTime(player) == secondTs + 50, "exact 50ms clock-lead boundary rebased"); + close(player); + } + + { + Events events; + auto* player = open(events); + AvPlayerFrameInfo sound{}; + take(player, sound, "audio-lead boundary first audio missing"); + AvPlayer::SetAvPlayerClockForTest_nid_no_patch(player, static_cast(beyondTs - 50)); + std::this_thread::sleep_for(std::chrono::milliseconds(40)); + Check(WaitFor([&] { + while (sceAvPlayerGetAudioData(player, &sound)) { + if (sound.timestamp >= beyondTs) return true; + } + return false; + }), "audio-lead boundary frame missing"); + Check(sound.timestamp == beyondTs, "audio-lead boundary timestamp unexpected"); + Check(sceAvPlayerCurrentTime(player) == beyondTs - 50, "exact 50ms audio-lead boundary rebased"); + close(player); + } + + { + Events events; + auto* player = open(events); + AvPlayerFrameInfo sound{}; + take(player, sound, "clock-lead rebase first audio missing"); + AvPlayer::SetAvPlayerClockForTest_nid_no_patch(player, static_cast(secondTs + 51)); + std::this_thread::sleep_for(std::chrono::milliseconds(40)); + take(player, sound, "clock-lead rebase second audio missing"); + Check(sound.timestamp == secondTs, "clock-lead rebase second timestamp changed"); + Check(sceAvPlayerCurrentTime(player) == secondTs, "clock-lead beyond 50ms did not rebase"); + close(player); + } + + { + Events events; + auto* player = open(events); + AvPlayerFrameInfo sound{}; + take(player, sound, "audio-lead rebase first audio missing"); + AvPlayer::SetAvPlayerClockForTest_nid_no_patch(player, 0.0); + std::this_thread::sleep_for(std::chrono::milliseconds(40)); + Check(WaitFor([&] { + while (sceAvPlayerGetAudioData(player, &sound)) { + if (sound.timestamp > 50) return true; + } + return false; + }), "audio-lead rebase frame missing"); + Check(sound.timestamp == beyondTs, "audio-lead rebase timestamp unexpected"); + Check(sceAvPlayerCurrentTime(player) == beyondTs, "audio-lead beyond 50ms did not rebase"); + close(player); + } +} + +constexpr std::array ShortAudioMovie{ + 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d, 0x00, 0x00, 0x02, 0x00, + 0x69, 0x73, 0x6f, 0x6d, 0x69, 0x73, 0x6f, 0x32, 0x61, 0x76, 0x63, 0x31, 0x6d, 0x70, 0x34, 0x31, + 0x00, 0x00, 0x06, 0x77, 0x6d, 0x6f, 0x6f, 0x76, 0x00, 0x00, 0x00, 0x6c, 0x6d, 0x76, 0x68, 0x64, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8, + 0x00, 0x00, 0x03, 0xe8, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x02, 0xe8, 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c, + 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73, + 0x00, 0x00, 0x00, 0x1c, 0x65, 0x6c, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x60, + 0x6d, 0x64, 0x69, 0x61, 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3c, 0x00, + 0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, + 0x00, 0x00, 0x00, 0x02, 0x0b, 0x6d, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x14, 0x76, 0x6d, 0x68, + 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x01, 0xcb, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00, 0xb7, 0x73, 0x74, 0x73, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa7, 0x61, 0x76, 0x63, + 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x48, 0x00, + 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x2d, 0x61, 0x76, 0x63, 0x43, 0x01, 0x42, 0xc0, 0x0a, 0xff, 0xe1, 0x00, 0x16, 0x67, + 0x42, 0xc0, 0x0a, 0xda, 0x10, 0x9b, 0x01, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, + 0x03, 0xc0, 0xf1, 0x22, 0x6a, 0x01, 0x00, 0x04, 0x68, 0xce, 0x0f, 0xc8, 0x00, 0x00, 0x00, 0x10, + 0x70, 0x61, 0x73, 0x70, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x74, 0x72, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x20, 0x00, 0x00, 0x1c, 0x20, + 0x00, 0x00, 0x00, 0x18, 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x73, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, + 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x8c, 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x02, 0x62, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0x2c, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0x00, 0x00, 0x06, 0xbf, 0x00, 0x00, 0x09, 0x2d, 0x00, 0x00, 0x09, 0x43, 0x00, 0x00, 0x09, 0x53, + 0x00, 0x00, 0x09, 0x69, 0x00, 0x00, 0x09, 0x79, 0x00, 0x00, 0x09, 0x8f, 0x00, 0x00, 0x02, 0xb9, + 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c, 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73, 0x00, 0x00, 0x00, 0x1c, 0x65, 0x6c, 0x73, 0x74, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x31, 0x6d, 0x64, 0x69, 0x61, 0x00, 0x00, 0x00, 0x20, + 0x6d, 0x64, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xbb, 0x80, 0x00, 0x00, 0x25, 0x80, 0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, + 0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x6f, 0x75, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x6f, 0x75, 0x6e, + 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01, 0xdc, 0x6d, 0x69, 0x6e, + 0x66, 0x00, 0x00, 0x00, 0x10, 0x73, 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65, + 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, + 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xa0, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00, + 0x7e, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x6e, 0x6d, 0x70, 0x34, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x36, 0x65, 0x73, 0x64, 0x73, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x80, + 0x80, 0x25, 0x00, 0x02, 0x00, 0x04, 0x80, 0x80, 0x80, 0x17, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7d, 0x00, 0x00, 0x00, 0x0b, 0xdc, 0x05, 0x80, 0x80, 0x80, 0x05, 0x11, 0x90, 0x56, 0xe5, + 0x00, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02, 0x00, 0x00, 0x00, 0x14, 0x62, 0x74, 0x72, 0x74, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x0b, 0xdc, 0x00, 0x00, 0x00, 0x20, 0x73, + 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x58, 0x73, + 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2c, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x00, 0x06, 0xa7, 0x00, 0x00, 0x09, 0x21, 0x00, 0x00, 0x09, 0x37, 0x00, + 0x00, 0x09, 0x4d, 0x00, 0x00, 0x09, 0x5d, 0x00, 0x00, 0x09, 0x73, 0x00, 0x00, 0x09, 0x83, 0x00, + 0x00, 0x00, 0x1a, 0x73, 0x67, 0x70, 0x64, 0x01, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x6c, 0x6c, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1c, 0x73, 0x62, 0x67, + 0x70, 0x00, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x62, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, + 0x5a, 0x6d, 0x65, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6c, + 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, + 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x69, 0x6c, + 0x73, 0x74, 0x00, 0x00, 0x00, 0x25, 0xa9, 0x74, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x1d, 0x64, 0x61, + 0x74, 0x61, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x76, 0x66, 0x35, 0x38, + 0x2e, 0x37, 0x36, 0x2e, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x08, 0x66, 0x72, 0x65, 0x65, 0x00, + 0x00, 0x03, 0xe0, 0x6d, 0x64, 0x61, 0x74, 0xde, 0x04, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x38, + 0x2e, 0x31, 0x33, 0x34, 0x2e, 0x31, 0x30, 0x30, 0x00, 0x42, 0x20, 0x08, 0xc1, 0x18, 0x38, 0x00, + 0x00, 0x02, 0x44, 0x06, 0x05, 0xff, 0xff, 0x40, 0xdc, 0x45, 0xe9, 0xbd, 0xe6, 0xd9, 0x48, 0xb7, + 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef, 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, + 0x6f, 0x72, 0x65, 0x20, 0x31, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34, 0x2f, + 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x63, + 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, 0x33, + 0x2d, 0x32, 0x30, 0x32, 0x31, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, + 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e, 0x2e, 0x6f, 0x72, 0x67, 0x2f, + 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, + 0x3d, 0x31, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, 0x30, 0x3a, 0x30, 0x3a, 0x30, + 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x65, 0x3d, 0x30, 0x3a, 0x30, 0x20, 0x6d, 0x65, 0x3d, + 0x64, 0x69, 0x61, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 0x3d, 0x30, 0x20, 0x70, 0x73, 0x79, 0x3d, + 0x31, 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x3a, 0x30, 0x2e, + 0x30, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x66, 0x3d, 0x30, 0x20, 0x6d, + 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, + 0x61, 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69, 0x73, 0x3d, 0x30, + 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x30, 0x20, 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, + 0x64, 0x65, 0x61, 0x64, 0x7a, 0x6f, 0x6e, 0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x66, + 0x61, 0x73, 0x74, 0x5f, 0x70, 0x73, 0x6b, 0x69, 0x70, 0x3d, 0x31, 0x20, 0x63, 0x68, 0x72, 0x6f, + 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x30, 0x20, 0x74, + 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x32, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, + 0x61, 0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x31, 0x20, 0x73, 0x6c, 0x69, + 0x63, 0x65, 0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x30, 0x20, 0x6e, 0x72, + 0x3d, 0x30, 0x20, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x6c, 0x75, 0x72, 0x61, + 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, + 0x72, 0x61, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x62, + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x30, 0x20, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x70, + 0x3d, 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x33, 0x30, 0x20, 0x6b, 0x65, 0x79, + 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d, 0x33, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, + 0x75, 0x74, 0x3d, 0x30, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x63, 0x72, 0x66, 0x20, 0x6d, 0x62, 0x74, 0x72, + 0x65, 0x65, 0x3d, 0x30, 0x20, 0x63, 0x72, 0x66, 0x3d, 0x32, 0x33, 0x2e, 0x30, 0x20, 0x71, 0x63, + 0x6f, 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d, 0x30, + 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, + 0x3d, 0x34, 0x20, 0x69, 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, + 0x20, 0x61, 0x71, 0x3d, 0x30, 0x00, 0x80, 0x00, 0x00, 0x00, 0x16, 0x65, 0x88, 0x84, 0x3a, 0x26, + 0x28, 0x00, 0x09, 0x02, 0xc9, 0xc9, 0xc9, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, 0xd7, 0x5d, 0x75, + 0xe0, 0x21, 0x10, 0x04, 0x60, 0x8c, 0x1c, 0x21, 0x10, 0x04, 0x60, 0x8c, 0x1c, 0x00, 0x00, 0x00, + 0x06, 0x41, 0x9a, 0x20, 0x36, 0x82, 0x30, 0x21, 0x10, 0x04, 0x60, 0x8c, 0x1c, 0x21, 0x10, 0x04, + 0x60, 0x8c, 0x1c, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0x40, 0x3a, 0x82, 0x30, 0x21, 0x10, 0x04, + 0x60, 0x8c, 0x1c, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0x60, 0x3a, 0x82, 0x30, 0x21, 0x10, 0x04, + 0x60, 0x8c, 0x1c, 0x21, 0x10, 0x04, 0x60, 0x8c, 0x1c, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0x80, + 0x3a, 0x82, 0x30, 0x21, 0x10, 0x04, 0x60, 0x8c, 0x1c, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0xa0, + 0x3a, 0x82, 0x30, 0x21, 0x10, 0x04, 0x60, 0x8c, 0x1c, 0x21, 0x10, 0x04, 0x60, 0x8c, 0x1c, 0x00, + 0x00, 0x00, 0x06, 0x41, 0x9a, 0xc0, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0xe0, + 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9b, 0x00, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, + 0x06, 0x41, 0x9b, 0x20, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9b, 0x40, 0x3e, 0x82, + 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9b, 0x60, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, + 0x9b, 0x80, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9b, 0xa0, 0x3e, 0x82, 0x30, 0x00, + 0x00, 0x00, 0x06, 0x41, 0x9b, 0xc0, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9b, 0xe0, + 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0x00, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, + 0x06, 0x41, 0x9a, 0x20, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0x40, 0x3e, 0x82, + 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0x60, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, + 0x9a, 0x80, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0xa0, 0x3e, 0x82, 0x30, 0x00, + 0x00, 0x00, 0x06, 0x41, 0x9a, 0xc0, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9a, 0xe0, + 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9b, 0x00, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, + 0x06, 0x41, 0x9b, 0x20, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9b, 0x40, 0x3e, 0x82, + 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9b, 0x60, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, + 0x9b, 0x80, 0x3e, 0x82, 0x30, 0x00, 0x00, 0x00, 0x06, 0x41, 0x9b, 0xa0, 0x3e, 0x82, 0x30 +}; + +void WriteShortAudioMovie() { + std::filesystem::create_directories("app0"); + std::ofstream file("app0/short-audio.mp4", std::ios::binary | std::ios::trunc); + file.write(reinterpret_cast(ShortAudioMovie.data()), static_cast(ShortAudioMovie.size())); + Check(static_cast(file), "cannot write the short audio movie"); +} + +void TestAudioEofClockRebase() { + const auto open = [](Events& events, bool hold) { + AvPlayerInitData init = InitData(&events); + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "audio eof clock init failed"); + Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "audio eof clock source failed"); + Check(WaitFor([&] { return events.Seen(EventReady); }), "audio eof clock ready missing"); + Check(sceAvPlayerEnableStream(player, EnglishAudioStream) == 0, "audio eof clock audio failed"); + if (hold) AvPlayer::HoldAvPlayerAudioEofForTest_nid_no_patch(player, true); + Check(sceAvPlayerStart(player) == 0, "audio eof clock start failed"); + Check(WaitFor([&] { return events.Seen(EventPlay); }), "audio eof clock play missing"); + return player; + }; + const auto close = [](AvPlayerInternal* player) { + AvPlayer::HoldAvPlayerAudioEofForTest_nid_no_patch(player, false); + Check(sceAvPlayerClose(player) == 0, "audio eof clock close failed"); + std::lock_guard lock(allocations.mutex); + Check(allocations.blocks.empty(), "audio eof clock allocations leaked"); + }; + const auto pullQueued = [](AvPlayerInternal* player) { + std::uint64_t timestamp = 0; + Check(AvPlayer::NextQueuedAvPlayerAudioTimestampForTest_nid_no_patch(player, ×tamp), "audio eof clock queue empty"); + AvPlayer::SetAvPlayerClockForTest_nid_no_patch(player, static_cast(timestamp)); + AvPlayerFrameInfo sound{}; + Check(sceAvPlayerGetAudioData(player, &sound) != 0, "audio eof clock drain failed"); + Check(sound.timestamp == timestamp, "audio eof clock drained an unexpected frame"); + }; + const auto leaveFinalFrame = [&](AvPlayerInternal* player, bool afterEof) { + Check(WaitFor([&] { + const auto queued = AvPlayer::QueuedAvPlayerAudioForTest_nid_no_patch(player); + const auto ready = afterEof ? AvPlayer::AvPlayerAudioEndedForTest_nid_no_patch(player) : AvPlayer::AvPlayerAudioEofHeldForTest_nid_no_patch(player); + if (ready && queued >= 1) return true; + if (queued > 1) pullQueued(player); + return false; + }), afterEof ? "audio eof before the final pull missing" : "audio eof hold missing"); + while (AvPlayer::QueuedAvPlayerAudioForTest_nid_no_patch(player) > 1) pullQueued(player); + }; + const auto pullFinal = [](AvPlayerInternal* player, AvPlayerFrameInfo& sound) { + std::uint64_t last = 0; + Check(AvPlayer::NextQueuedAvPlayerAudioTimestampForTest_nid_no_patch(player, &last), "final audio frame was not queued"); + Check(last > 50, "final audio timestamp is inside the drift gate"); + AvPlayer::SetAvPlayerClockForTest_nid_no_patch(player, static_cast(last - 42)); + std::this_thread::sleep_for(std::chrono::milliseconds(40)); + Check(sceAvPlayerGetAudioData(player, &sound) != 0, "final audio frame missing"); + Check(sound.timestamp == last, "final audio timestamp changed"); + return last; + }; + + { + Events events; + auto* player = open(events, true); + leaveFinalFrame(player, false); + AvPlayerFrameInfo sound{}; + const auto last = pullFinal(player, sound); + Check(sceAvPlayerCurrentTime(player) == last - 42, "clock rebased before audio eof was marked"); + AvPlayer::HoldAvPlayerAudioEofForTest_nid_no_patch(player, false); + Check(WaitFor([&] { return AvPlayer::AvPlayerAudioEndedForTest_nid_no_patch(player); }), "audio eof was not marked"); + Check(sceAvPlayerCurrentTime(player) == last, "clock was not reconciled after audio eof"); + close(player); + } + + { + Events events; + auto* player = open(events, false); + leaveFinalFrame(player, true); + AvPlayerFrameInfo sound{}; + const auto last = pullFinal(player, sound); + Check(sceAvPlayerCurrentTime(player) == last, "clock was not reconciled when the final frame followed eof"); + close(player); + } +} + +void TestAudioClockAdvancesAfterEof() { + WriteShortAudioMovie(); + Events events; + AvPlayerInitData init = InitData(&events); + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "audio eof advance init failed"); + Check(sceAvPlayerAddSource(player, "/app0/short-audio.mp4") == 0, "audio eof advance source failed"); + Check(WaitFor([&] { return events.Seen(EventReady); }), "audio eof advance ready missing"); + Check(sceAvPlayerEnableStream(player, EnglishAudioStream) == 0, "audio eof advance audio failed"); + Check(sceAvPlayerStart(player) == 0, "audio eof advance start failed"); + Check(WaitFor([&] { return events.Seen(EventPlay); }), "audio eof advance play missing"); + AvPlayerFrameInfo sound{}; + std::uint64_t last = 0; + Check(WaitFor([&] { + while (sceAvPlayerGetAudioData(player, &sound)) last = sound.timestamp; + return AvPlayer::AvPlayerAudioEndedForTest_nid_no_patch(player); + }), "short audio did not end"); + Check(last > 0 && last < 500, "short audio timestamp unexpected"); + const auto before = sceAvPlayerCurrentTime(player); + std::this_thread::sleep_for(std::chrono::milliseconds(80)); + for (int poll = 0; poll < 5; ++poll) sceAvPlayerGetAudioData(player, &sound); + const auto after = sceAvPlayerCurrentTime(player); + Check(after >= before + 40, "clock stopped after audio eof: " + std::to_string(before) + " to " + std::to_string(after)); + Check(sceAvPlayerClose(player) == 0, "audio eof advance close failed"); + std::lock_guard lock(allocations.mutex); + Check(allocations.blocks.empty(), "audio eof advance allocations leaked"); +} + +void TestTrailingVideoAfterShortAudio() { + WriteShortAudioMovie(); + Events events; + AvPlayerInitData init = InitData(&events); + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "trailing video init failed"); + Check(sceAvPlayerAddSource(player, "/app0/short-audio.mp4") == 0, "trailing video source failed"); + Check(WaitFor([&] { return events.Seen(EventReady); }), "trailing video ready missing"); + Check(sceAvPlayerEnableStream(player, VideoStream) == 0, "trailing video stream failed"); + Check(sceAvPlayerEnableStream(player, EnglishAudioStream) == 0, "trailing video audio failed"); + Check(sceAvPlayerStart(player) == 0, "trailing video start failed"); + Check(WaitFor([&] { return events.Seen(EventPlay); }), "trailing video play missing"); + AvPlayerFrameInfo sound{}; + std::uint64_t last = 0; + Check(WaitFor([&] { + while (sceAvPlayerGetAudioData(player, &sound)) last = sound.timestamp; + return AvPlayer::AvPlayerAudioEndedForTest_nid_no_patch(player); + }), "trailing video audio did not end"); + Check(last > 0 && last < 500, "trailing video audio timestamp unexpected"); + std::uint64_t latest = 0; + Check(WaitFor([&] { + sceAvPlayerGetAudioData(player, &sound); + sceAvPlayerCurrentTime(player); + AvPlayerFrameInfoEx frame{}; + if (sceAvPlayerGetVideoDataEx(player, &frame)) latest = std::max(latest, frame.timestamp); + return latest > last + 100; + }, 3000), "trailing video was not delivered after audio eof"); + Check(sceAvPlayerClose(player) == 0, "trailing video close failed"); + std::lock_guard lock(allocations.mutex); + Check(allocations.blocks.empty(), "trailing video allocations leaked"); +} + } int main(int argc, char** argv) { @@ -1644,6 +2064,10 @@ int main(int argc, char** argv) { TestLoopWarningWaitsForPlayback(); TestEofStopLatency(); TestVideoSyncTolerance(); + TestAudioClockDriftGating(); + TestAudioEofClockRebase(); + TestAudioClockAdvancesAfterEof(); + TestTrailingVideoAfterShortAudio(); std::puts("AvPlayer tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/tests/GuestAvailableCpumask.cpp b/core/libs/tests/GuestAvailableCpumask.cpp index 31775180e5..1450a36bce 100644 --- a/core/libs/tests/GuestAvailableCpumask.cpp +++ b/core/libs/tests/GuestAvailableCpumask.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -9,28 +11,6 @@ #include #include -extern "C" { -KernelCpumask APS5_VABI sceKernelGetAvailableCpumask(void); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -Pthread APS5_VABI scePthreadSelf(); -int APS5_VABI scePthreadGetaffinity(Pthread thread, KernelCpumask* mask); -int APS5_VABI scePthreadSetaffinity(Pthread thread, KernelCpumask mask); -int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask* mask); -int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask); -int APS5_VABI cpuset_getaffinity_nid_postfix(int level, int which, std::int64_t id, std::size_t size, void* mask); -int APS5_VABI cpuset_setaffinity_nid_postfix(int level, int which, std::int64_t id, std::size_t size, const void* mask); -int APS5_VABI getpid_nid_postfix(void); -int* APS5_VABI __error_nid_postfix(); -Pthread APS5_VABI pthread_self_nid_postfix(void); -int APS5_VABI pthread_getaffinity_np_nid_postfix(Pthread thread, std::size_t cpusetSize, void* cpuset); -int APS5_VABI pthread_setaffinity_np_nid_postfix(Pthread thread, std::size_t cpusetSize, const void* cpuset); -int APS5_VABI scePthreadGetthreadid(void); -int APS5_VABI sceKernelGetCurrentCpu(void); -} - static constexpr int SCE_OK = 0; static constexpr int PS5_LOGICAL_CPUS = 16; @@ -120,6 +100,7 @@ static void* APS5_VABI SetCpuset(void* arg) { std::uint64_t set[4] = {cpu | (1ull << 40), 0, 0, 0}; result->results[0] = cpuset_setaffinity_nid_postfix(3, 1, -1, 8, set); scePthreadGetaffinity(scePthreadSelf(), &result->affinity[0]); + set[1] = 1; result->results[1] = cpuset_setaffinity_nid_postfix(3, 1, -1, 16, set) == -1 && *__error_nid_postfix() == 22; set[1] = 0; @@ -130,9 +111,11 @@ static void* APS5_VABI SetCpuset(void* arg) { const std::uint64_t unavailable[1] = {1ull << 40}; result->results[6] = cpuset_setaffinity_nid_postfix(3, 1, -1, 8, unavailable) == -1 && *__error_nid_postfix() == 11; scePthreadGetaffinity(scePthreadSelf(), &result->affinity[1]); + const std::uint64_t process[1] = {cpu}; result->results[7] = cpuset_setaffinity_nid_postfix(3, 2, getpid_nid_postfix(), 8, process); scePthreadGetaffinity(scePthreadSelf(), &result->affinity[2]); + return nullptr; } diff --git a/core/libs/tests/GuestBacktrace.cpp b/core/libs/tests/GuestBacktrace.cpp index 7ae55383e3..8d3f597480 100644 --- a/core/libs/tests/GuestBacktrace.cpp +++ b/core/libs/tests/GuestBacktrace.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -13,8 +14,7 @@ #include extern "C" { -int APS5_VABI sceLibcBacktraceGetBufferSize_nid_postfix(int, int*, std::size_t*); -int APS5_VABI sceLibcBacktraceSelf_nid_postfix(int, void*, std::size_t, int*); + #if defined(_WIN32) std::string BacktraceModulePath_nid_no_patch(HMODULE, std::size_t); #endif diff --git a/core/libs/tests/GuestBadOptionalAccess.cpp b/core/libs/tests/GuestBadOptionalAccess.cpp index a4280e3ef5..0b9d8edfe9 100644 --- a/core/libs/tests/GuestBadOptionalAccess.cpp +++ b/core/libs/tests/GuestBadOptionalAccess.cpp @@ -1,19 +1,18 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include struct TypeRecord { const void* const* vtable; const char* name; const TypeRecord* base; }; -struct Object { const void* vtable; const char* message; }; -struct Vtable { std::ptrdiff_t offset; const TypeRecord* type; const void* destroy; const void* deleteObject; const char* (APS5_VABI *what)(const Object*); }; + +struct Vtable { std::ptrdiff_t offset; const TypeRecord* type; const void* destroy; const void* deleteObject; const char* (APS5_VABI *what)(const LibcException::ExceptionObject*); }; extern "C" { extern const TypeRecord _ZTISt9exception_nid_postfix; extern const TypeRecord _ZTISt19bad_optional_access_nid_postfix; extern const Vtable _ZTVSt19bad_optional_access_nid_postfix; -void APS5_VABI _ZNSt19bad_optional_accessC1Ev_nid_postfix(Object*); -void APS5_VABI _ZNSt19bad_optional_accessD1Ev_nid_postfix(Object*); -[[noreturn]] void APS5_VABI _ZSt26_Throw_bad_optional_accessv_nid_postfix(); + } static void Require(bool value) { if (!value) std::abort(); } @@ -24,14 +23,17 @@ int main() { Require(type.base == &_ZTISt9exception_nid_postfix); const auto& table = _ZTVSt19bad_optional_access_nid_postfix; Require(table.offset == 0 && table.type == &type && table.what != nullptr); - Object object{}; + LibcException::ExceptionObject object{}; _ZNSt19bad_optional_accessC1Ev_nid_postfix(&object); + Require(object.vtable == &table.destroy); Require(std::strcmp(table.what(&object), "bad_optional_access") == 0); _ZNSt19bad_optional_accessD1Ev_nid_postfix(&object); + bool thrown = false; try { _ZSt26_Throw_bad_optional_accessv_nid_postfix(); + } catch (...) { thrown = true; } diff --git a/core/libs/tests/GuestBessel.cpp b/core/libs/tests/GuestBessel.cpp index 75c94ec4ac..4ff093ad18 100644 --- a/core/libs/tests/GuestBessel.cpp +++ b/core/libs/tests/GuestBessel.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -10,16 +11,6 @@ #include #include -extern "C" { -double APS5_VABI j0_nid_postfix(double); -double APS5_VABI j1_nid_postfix(double); -double APS5_VABI jn_nid_postfix(int, double); -double APS5_VABI y0_nid_postfix(double); -double APS5_VABI y1_nid_postfix(double); -double APS5_VABI yn_nid_postfix(int, double); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } static double Value(std::uint64_t bits) { return std::bit_cast(bits); } @@ -96,10 +87,35 @@ static void CheckSecondKind() { y0_nid_postfix(input); y1_nid_postfix(input); yn_nid_postfix(3, input); + Require(std::fetestexcept(FE_DIVBYZERO | FE_INVALID) == 0); } } +static void CheckFloatSecondKindZero() { + const char* names[] = {"y0f", "y1f", "ynf"}; + for (int function = 0; function < 3; ++function) { + for (const int order : {INT_MIN, -3, -2, -1, 0, 1, 2, 3, INT_MAX}) { + for (const float input : {0.0f, -0.0f}) { + for (const int flags : {0, FE_INEXACT, FE_DIVBYZERO}) { + std::feclearexcept(FE_ALL_EXCEPT); + std::feraiseexcept(flags); + const int expectedFlags = std::fetestexcept(FE_ALL_EXCEPT); + *__error_nid_postfix() = 12345; + const float result = function == 0 ? y0f_nid_postfix(input) : function == 1 ? y1f_nid_postfix(input) : ynf_nid_postfix(order, input); + const int actualFlags = std::fetestexcept(FE_ALL_EXCEPT); + if (std::bit_cast(result) != 0xff800000u || actualFlags != expectedFlags || *__error_nid_postfix() != 12345) { + std::fprintf(stderr, "Guest %s(%d, %08x): result %08x, flags %x, expected %x, errno %d\n", names[function], order, + std::bit_cast(input), std::bit_cast(result), actualFlags, expectedFlags, *__error_nid_postfix()); + std::abort(); + } + } + } + } + } + std::feclearexcept(FE_ALL_EXCEPT); +} + static void CheckIntegerOrders() { const OrderCase first[] = { {2, 0x3ddb7cdfd9d7bdbbull, 0x3b979ca10c924224ull}, {5, 0xbddb7cdfd9d7bdbbull, 0xb4cfed4f3ec0c466ull}, {33, 0x3e00000000000000ull, 0x0000000000000000ull}, @@ -181,6 +197,7 @@ static void CheckValues() { int main() { CheckFirstKind(); CheckSecondKind(); + CheckFloatSecondKindZero(); CheckIntegerOrders(); CheckValues(); return 0; diff --git a/core/libs/tests/GuestBinaryWord.cpp b/core/libs/tests/GuestBinaryWord.cpp index 2720a1f894..cfb2f004f0 100644 --- a/core/libs/tests/GuestBinaryWord.cpp +++ b/core/libs/tests/GuestBinaryWord.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -10,22 +11,6 @@ #include #include -extern "C" { -int APS5_VABI getw_nid_postfix(FileStream*); -int APS5_VABI putw_nid_postfix(int, FileStream*); -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -int APS5_VABI fclose_nid_postfix(FileStream*); -std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*); -std::size_t APS5_VABI fwrite_nid_postfix(const void*, std::size_t, std::size_t, FileStream*); -int APS5_VABI fseek_nid_postfix(FileStream*, std::int64_t, int); -std::int64_t APS5_VABI ftell_nid_postfix(FileStream*); -int APS5_VABI ungetc_nid_postfix(int, FileStream*); -int APS5_VABI fgetc_nid_postfix(FileStream*); -void APS5_VABI clearerr_nid_postfix(FileStream*); -void APS5_VABI flockfile_nid_postfix(FileStream*); -void APS5_VABI funlockfile_nid_postfix(FileStream*); -} - namespace { void Require(bool condition, const char* message) { @@ -75,6 +60,7 @@ void CheckShortReads(const char* path) { Require((stream->GuestState().flags & 0x60) == 0x20, "short-read EOF flag"); Require(ftell_nid_postfix(stream) == static_cast(length), "consume incomplete word bytes"); clearerr_nid_postfix(stream); + Require((stream->GuestState().flags & 0x60) == 0, "clear short-read status"); Require(fclose_nid_postfix(stream) == 0, "close incomplete word"); } @@ -107,8 +93,10 @@ void CheckConcurrentWrites(const char* path) { auto* stream = fopen_nid_postfix(path, "w+b"); Require(stream != nullptr, "open shared stream"); flockfile_nid_postfix(stream); + Require(putw_nid_postfix(0, stream) == 0, "putw accepts a lock held by its caller"); funlockfile_nid_postfix(stream); + const auto writer = [stream](int value) { for (int count = 0; count < 256; ++count) Require(putw_nid_postfix(value, stream) == 0, "concurrent word write"); diff --git a/core/libs/tests/GuestBluetoothHid.cpp b/core/libs/tests/GuestBluetoothHid.cpp index e118532944..fbc4db06e2 100644 --- a/core/libs/tests/GuestBluetoothHid.cpp +++ b/core/libs/tests/GuestBluetoothHid.cpp @@ -1,29 +1,17 @@ +#include "prx/libSceBluetoothHid/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include -extern "C" { -std::int32_t APS5_VABI sceBluetoothHidInit(); -std::int32_t APS5_VABI sceBluetoothHidRegisterCallback(void* callback, std::int32_t flags, void* param); -std::int32_t APS5_VABI sceBluetoothHidUnregisterCallback(); -std::int32_t APS5_VABI sceBluetoothHidRegisterDevice(std::uint16_t vendorId, std::uint16_t productId); -std::int32_t APS5_VABI sceBluetoothHidUnregisterDevice(std::uint16_t vendorId, std::uint16_t productId); -int APS5_VABI sceBluetoothHidGetInputReport(); -int APS5_VABI sceBluetoothHidDebugGetVersion(); -int APS5_VABI sceBluetoothHidGetDeviceInfo(); -int APS5_VABI sceBluetoothHidDisconnectDevice(); -int APS5_VABI sceBluetoothHidGetFeatureReport(); -int APS5_VABI sceBluetoothHidSetFeatureReport(); -} - namespace { int callbackCalls = 0; -void APS5_VABI Callback() { +void APS5_VABI Callback(const BluetoothHidEvent*, std::int32_t, void*) { ++callbackCalls; } @@ -37,21 +25,35 @@ void Require(bool condition, const char* message) { } int main() { + constexpr auto notInitialized = static_cast(0x81540004u); + constexpr auto invalidArgument = static_cast(0x81540001u); + constexpr auto noCallback = static_cast(0x81540006u); + Require(sceBluetoothHidRegisterCallback(nullptr, nullptr, nullptr) == notInitialized, "initialization check order"); + Require(sceBluetoothHidRegisterDevice(1, 2) == notInitialized, "registration before initialization"); + Require(sceBluetoothHidUnregisterDevice(1, 2) == notInitialized, "unregistration before initialization"); + Require(sceBluetoothHidUnregisterCallback() == notInitialized, "callback removal before initialization"); Require(sceBluetoothHidInit() == 0, "initialization failed"); - std::uint8_t param[16]{}; - Require(sceBluetoothHidRegisterCallback(reinterpret_cast(&Callback), 0, param) == 0, "callback registration failed"); + Require(sceBluetoothHidInit() == 0, "repeated initialization failed"); + Require(sceBluetoothHidRegisterDevice(1, 2) == noCallback, "registration without callback"); + Require(sceBluetoothHidUnregisterCallback() == noCallback, "removing absent callback"); + BluetoothHidParam param{}; + Require(sceBluetoothHidRegisterCallback(&Callback, &callbackCalls, ¶m) == 0, "callback registration failed"); Require(sceBluetoothHidRegisterDevice(0x44f, 0x1000) == 0, "device registration failed"); Require(callbackCalls == 0, "a device connection was reported"); + Require(sceBluetoothHidRegisterCallback(nullptr, nullptr, ¶m) == invalidArgument, "null callback validation"); + param.reserved = 1; + Require(sceBluetoothHidRegisterCallback(&Callback, nullptr, ¶m) == invalidArgument, "reserved field validation"); + param.reserved = 0; + BluetoothHidThreadParam threadParam{}; + threadParam.reserved = 1; + param.threadParam = &threadParam; + Require(sceBluetoothHidRegisterCallback(&Callback, nullptr, ¶m) == invalidArgument, "thread reserved field validation"); + Require(sceBluetoothHidRegisterCallback(&Callback, nullptr, nullptr) == 0, "optional parameters rejected"); bool threw = false; - try { - sceBluetoothHidRegisterCallback(nullptr, 0, param); - } catch (const std::invalid_argument&) { - threw = true; - } - Require(threw, "a null callback was accepted"); threw = false; try { - sceBluetoothHidGetInputReport(); + sceBluetoothHidGetInputReport(0, 0); + } catch (const std::runtime_error&) { threw = true; } @@ -59,28 +61,32 @@ int main() { Require(sceBluetoothHidDebugGetVersion() == 0x1e, "debug get version did not return the firmware version"); threw = false; try { - sceBluetoothHidGetDeviceInfo(); + sceBluetoothHidGetDeviceInfo(0, nullptr); + } catch (const std::runtime_error&) { threw = true; } Require(threw, "get device info did not throw"); threw = false; try { - sceBluetoothHidDisconnectDevice(); + sceBluetoothHidDisconnectDevice(0); + } catch (const std::runtime_error&) { threw = true; } Require(threw, "disconnect device did not throw"); threw = false; try { - sceBluetoothHidGetFeatureReport(); + sceBluetoothHidGetFeatureReport(0, 0); + } catch (const std::runtime_error&) { threw = true; } Require(threw, "get feature report did not throw"); threw = false; try { - sceBluetoothHidSetFeatureReport(); + sceBluetoothHidSetFeatureReport(0, 0, nullptr, 0); + } catch (const std::runtime_error&) { threw = true; } diff --git a/core/libs/tests/GuestBoundsChecking.cpp b/core/libs/tests/GuestBoundsChecking.cpp index 4021e99e48..fb7977eaa8 100644 --- a/core/libs/tests/GuestBoundsChecking.cpp +++ b/core/libs/tests/GuestBoundsChecking.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -8,19 +9,8 @@ #include #include -using GuestContextCompare = int (APS5_VABI*)(const void*, const void*, void*); - extern "C" { -void* APS5_VABI set_constraint_handler_s_nid_postfix(void*); -void* APS5_VABI bsearch_s_nid_postfix(const void*, const void*, std::size_t, std::size_t, GuestContextCompare, void*); -std::uint16_t* APS5_VABI wcstok_s_nid_postfix(std::uint16_t*, std::size_t*, const std::uint16_t*, std::uint16_t**); -int APS5_VABI wctomb_s_nid_postfix(int*, char*, std::size_t, std::uint16_t); -int APS5_VABI wcrtomb_s_nid_postfix(std::size_t*, char*, std::size_t, std::uint16_t, void*); -int APS5_VABI mbstowcs_s_nid_postfix(std::size_t*, std::uint16_t*, std::size_t, const char*, std::size_t); -int APS5_VABI mbsrtowcs_s_nid_postfix(std::size_t*, std::uint16_t*, std::size_t, const char**, std::size_t, void*); -char* APS5_VABI gets_s_nid_postfix(char*, std::size_t); -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -int APS5_VABI fclose_nid_postfix(FileStream*); + extern FileStream* __stdinp_nid_postfix; } @@ -130,7 +120,14 @@ void CheckWideToMultibyte() { int status = 7; char bytes[4] = {'x', 'x', 'x', 'x'}; Require(wctomb_s_nid_postfix(&status, bytes, sizeof(bytes), 'A') == 0 && status == 1 && bytes[0] == 'A', "wctomb_s did not convert"); - Require(wctomb_s_nid_postfix(&status, bytes, sizeof(bytes), 0x263a) == 86 && status == -1, "wctomb_s converted an unrepresentable character"); + Require(wctomb_s_nid_postfix(&status, bytes, sizeof(bytes), 0x263a) == 0 && status == 3 && std::memcmp(bytes, "\xE2\x98\xBA", 3) == 0, + "wctomb_s did not convert U+263A to UTF-8"); + std::memset(bytes, 'x', sizeof(bytes)); + status = 7; + int smallBefore = constraintCalls; + Require(wctomb_s_nid_postfix(&status, bytes, 2, 0x263a) == 34 && status == 7 && std::memcmp(bytes, "xxxx", 4) == 0, + "wctomb_s wrote past a two-byte buffer"); + RequireViolation(smallBefore, "wctomb_s: buffer too small", 34); Require(wctomb_s_nid_postfix(&status, nullptr, 0, 'A') == 0 && status == 0, "wctomb_s reported state dependence"); status = 7; int before = constraintCalls; @@ -144,7 +141,13 @@ void CheckWideToMultibyte() { std::size_t result = 7; Require(wcrtomb_s_nid_postfix(&result, bytes, sizeof(bytes), 'B', state) == 0 && result == 1 && bytes[0] == 'B', "wcrtomb_s did not convert"); Require(wcrtomb_s_nid_postfix(&result, nullptr, 0, 'B', state) == 0 && result == 1, "wcrtomb_s did not measure the reset sequence"); - Require(wcrtomb_s_nid_postfix(&result, bytes, sizeof(bytes), 0x263a, state) == 86 && result == failed, "wcrtomb_s converted an unrepresentable character"); + Require(wcrtomb_s_nid_postfix(&result, bytes, sizeof(bytes), 0x263a, state) == 0 && result == 3 && std::memcmp(bytes, "\xE2\x98\xBA", 3) == 0, + "wcrtomb_s did not convert U+263A to UTF-8"); + std::memset(bytes, 'x', sizeof(bytes)); + before = constraintCalls; + Require(wcrtomb_s_nid_postfix(&result, bytes, 2, 0x263a, state) == 34 && result == failed && bytes[0] == 0 && std::memcmp(bytes + 1, "xxx", 3) == 0, + "wcrtomb_s wrote past a two-byte buffer"); + RequireViolation(before, "wcrtomb_s: buffer too small", 34); before = constraintCalls; bytes[0] = 'x'; Require(wcrtomb_s_nid_postfix(&result, bytes, sizeof(bytes), 'B', nullptr) == 22 && result == failed && bytes[0] == 0, "wcrtomb_s accepted a null state"); @@ -225,6 +228,7 @@ void CheckGets(const char* path) { RequireViolation(before, "gets_s: bad argument", 22); __stdinp_nid_postfix = saved; fclose_nid_postfix(stream); + std::remove(path); } @@ -233,6 +237,7 @@ void CheckGets(const char* path) { int main(int argc, char** argv) { Require(argc > 1, "usage: guest_bounds_checking_tests "); set_constraint_handler_s_nid_postfix(reinterpret_cast(RecordConstraint)); + CheckBsearch(); CheckWcstok(); CheckWideToMultibyte(); diff --git a/core/libs/tests/GuestCallTrace.cpp b/core/libs/tests/GuestCallTrace.cpp index 4b7e2091e9..6721aaddac 100644 --- a/core/libs/tests/GuestCallTrace.cpp +++ b/core/libs/tests/GuestCallTrace.cpp @@ -12,8 +12,12 @@ #include #include #include +#include +#include +#include #include #include +#include #include #include @@ -106,8 +110,8 @@ int main(int argc, char** argv) { while (!locked.load()) std::this_thread::yield(); if (std::strcmp(argv[1], "abort-race") == 0) { for (int call = 0; call < 100; ++call) Require(first(1, 2, 3, 4, 5, 6, 7, 8) == 204); - std::atomic go{false}; - std::thread([&go] { + static std::atomic go{false}; + std::thread([] { while (!go.load()) { } std::abort(); @@ -199,4 +203,16 @@ int main(int argc, char** argv) { Require(owned == Window); Require(CopyCallCounts_nid_no_patch(counts, 1, &total) == 1 && total == 8 + Workers * Calls); Require(counts[0].calls == 1 + Workers * Calls && std::strcmp(counts[0].name, "Integers") == 0); + + const auto path = std::filesystem::temp_directory_path() / ("guest_call_trace_counts_" + std::to_string(CurrentThread()) + ".txt"); + const auto utf8 = path.u8string(); + Require(WriteCallCounts_nid_no_patch(reinterpret_cast(utf8.c_str()))); + std::ifstream written(path, std::ios::binary); + const std::string text{std::istreambuf_iterator(written), std::istreambuf_iterator()}; + written.close(); + Require(text == std::to_string(1 + Workers * Calls) + " libTraced.prx Integers\n3 libTraced.prx Blocks\n2 libTraced Doubles\n1 libOther.prx Mixed\n1 libOther Variadic\n"); + const auto unwritable = (path / "file").u8string(); + Require(!WriteCallCounts_nid_no_patch(reinterpret_cast(unwritable.c_str()))); + Require(!WriteCallCounts_nid_no_patch(nullptr) && !WriteCallCounts_nid_no_patch("")); + std::filesystem::remove(path); } diff --git a/core/libs/tests/GuestCamera.cpp b/core/libs/tests/GuestCamera.cpp new file mode 100644 index 0000000000..c074795e23 --- /dev/null +++ b/core/libs/tests/GuestCamera.cpp @@ -0,0 +1,48 @@ +#include "prx/libSceCamera/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +namespace { + +constexpr int ErrorParam = static_cast(0x802E0000u); + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "Camera: %s\n", message); + std::abort(); + } +} + +template +void RequireNotImplemented(TCall call, const std::string& name) { + try { + call(); + } catch (const std::runtime_error& error) { + Require(std::string(error.what()) == name + " not implemented", "a declared camera function threw another error"); + return; + } + Require(false, "a declared camera function returned"); +} + +} + +int main() { + Require(sceCameraIsAttached(0) == 0, "camera 0 must be reported as not attached"); + Require(sceCameraIsAttached(1) == ErrorParam, "camera index 1 must be rejected"); + Require(sceCameraIsAttached(-1) == ErrorParam, "a negative camera index must be rejected"); + RequireNotImplemented([] { sceCameraOpen(); }, "sceCameraOpen"); + RequireNotImplemented([] { sceCameraClose(); }, "sceCameraClose"); + RequireNotImplemented([] { sceCameraStart(); }, "sceCameraStart"); + RequireNotImplemented([] { sceCameraStop(); }, "sceCameraStop"); + RequireNotImplemented([] { sceCameraGetConfig(); }, "sceCameraGetConfig"); + RequireNotImplemented([] { sceCameraSetConfig(); }, "sceCameraSetConfig"); + RequireNotImplemented([] { sceCameraSetAttribute(); }, "sceCameraSetAttribute"); + RequireNotImplemented([] { sceCameraGetDeviceInfo(); }, "sceCameraGetDeviceInfo"); + RequireNotImplemented([] { sceCameraGetFrameData(); }, "sceCameraGetFrameData"); + RequireNotImplemented([] { sceCameraIsValidFrameData(); }, "sceCameraIsValidFrameData"); + return 0; +} diff --git a/core/libs/tests/GuestCes.cpp b/core/libs/tests/GuestCes.cpp index 7e5238dce7..b978d9f935 100644 --- a/core/libs/tests/GuestCes.cpp +++ b/core/libs/tests/GuestCes.cpp @@ -1,28 +1,12 @@ +#include "prx/libSceCesCs/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include +#include #include +#include #include #include -extern "C" { -const std::uint8_t* APS5_VABI sceCesRefersUcsProfileCp1252(void); -const std::uint8_t* APS5_VABI sceCesRefersUcsProfileCp1251(void); -const std::uint8_t* APS5_VABI sceCesRefersUcsProfileCp1250(void); -int APS5_VABI sceCesMbcsUcsContextInit(void*, const void*); -void* APS5_VABI sceCesUcsProfileInitEucJpCp51932(void*); -int APS5_VABI sceCesUtf8StrToMbcsStr(void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesUtf8StrGetMbcsLen(void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceCesMbcsStrToUtf8Str(void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesMbcsStrGetUtf8Len(void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceCesUcs2StrToMbcsStr(void*, const std::uint16_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesUcs2StrGetMbcsLen(void*, const std::uint16_t*, std::uint32_t, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceCesMbcsStrToUcs2Str(void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint16_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesMbcsStrGetUcs2Len(void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceCesEucJpToUcs2(const void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint16_t*); -int APS5_VABI sceCesSbcToUtf8(const std::uint8_t*, std::uint8_t, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesUtf8ToSbc(const std::uint8_t*, std::uint32_t, std::uint32_t*, const std::uint8_t*, std::uint8_t*); -void* APS5_VABI sceCesUcsProfileInitSJis1997Cp932(void*); -} - static void Require(bool value) { if (!value) std::abort(); } static void RequireSbcToUtf8(const std::uint8_t* profile, std::uint8_t sbc, const char* expected) { @@ -46,6 +30,489 @@ static int Utf8ToSbc(const std::uint8_t* profile, const char* utf8, std::uint32_ return sceCesUtf8ToSbc(reinterpret_cast(utf8), utf8max, &length, profile, &sbc); } +static void RequireSbcRoundTrip(const std::uint8_t* profile, std::initializer_list unassigned) { + constexpr int unassignedCode = static_cast(0x805C0020); + for (int sbc = 1; sbc < 256; ++sbc) { + std::uint8_t utf8[4] = {}; + std::uint32_t length = 7; + std::uint8_t back = 0; + if (std::find(unassigned.begin(), unassigned.end(), sbc) != unassigned.end()) { + Require(sceCesSbcToUtf8(profile, static_cast(sbc), utf8, sizeof(utf8), &length) == unassignedCode && length == 0); + continue; + } + Require(sceCesSbcToUtf8(profile, static_cast(sbc), utf8, sizeof(utf8), &length) == 0); + Require(sceCesUtf8ToSbc(utf8, length, &length, profile, &back) == 0 && back == sbc); + } +} + +struct MeasuredUtf8 { + const char* bytes; + std::uint32_t available; + int result; + std::uint32_t length; +}; + +static void RequireMeasuredUtf8ToSbc(const std::uint8_t* profile) { + constexpr MeasuredUtf8 cases[] = { + {"\xE2\x41", 1, static_cast(0x805C0011), 3}, + {"\xE2\x41", 2, static_cast(0x805C0014), 2}, + {"\xE2\x41\xAC", 3, static_cast(0x805C0014), 1}, + {"\xE2\x82\x41", 3, static_cast(0x805C0014), 2}, + {"\xE2\x82", 2, static_cast(0x805C0011), 3}, + {"\xE2\xC0", 2, static_cast(0x805C0014), 2}, + {"\xF0\x41\x98", 3, static_cast(0x805C0014), 2}, + {"\xF0\x9F\x41", 3, static_cast(0x805C0014), 3}, + {"\xF0\x41\x98\x80", 4, static_cast(0x805C0014), 1}, + {"\xF0\x9F\x98\x41", 4, static_cast(0x805C0014), 3}, + {"\xF0\x9F\x98", 3, static_cast(0x805C0011), 4}, + {"\xC3\x41", 2, static_cast(0x805C0014), 1}, + {"\xC3", 1, static_cast(0x805C0011), 2}, + {"\x80", 1, static_cast(0x805C0014), 0}, + {"\xFE", 1, static_cast(0x805C0014), 0}, + {"\xFF", 1, static_cast(0x805C0014), 0}, + {"\xC0\x80", 2, static_cast(0x805C0015), 2}, + {"\xC1\xBF", 2, static_cast(0x805C0015), 2}, + {"\xE0\x80\x80", 3, static_cast(0x805C0015), 3}, + {"\xE0\x9F\xBF", 3, static_cast(0x805C0015), 3}, + {"\xED\xA0\x80", 3, static_cast(0x805C0015), 3}, + {"\xF0\x8F\xBF\xBF", 4, static_cast(0x805C0015), 4}, + {"\xF8\x80\x80\x80\x80", 5, static_cast(0x805C0015), 5}, + {"\xFC\x80\x80\x80\x80\x80", 6, static_cast(0x805C0015), 6}, + {"\xE0\xA0\x80", 3, static_cast(0x805C0020), 3}, + {"\xED\x9F\xBF", 3, static_cast(0x805C0020), 3}, + {"\xEF\xBF\xBF", 3, static_cast(0x805C0020), 3}, + {"\xF0\x90\x80\x80", 4, static_cast(0x805C0020), 4}, + {"\xF4\x90\x80\x80", 4, static_cast(0x805C0020), 4}, + {"\xF5\x80\x80\x80", 4, static_cast(0x805C0020), 4}, + {"\xF7\xBF\xBF\xBF", 4, static_cast(0x805C0020), 4}, + {"\xF8\x88\x80\x80\x80", 4, static_cast(0x805C0011), 5}, + {"\xF8\x88\x80\x80\x80", 5, static_cast(0x805C0020), 5}, + {"\xFB\xBF\xBF\xBF\xBF", 5, static_cast(0x805C0020), 5}, + {"\xFC\x84\x80", 3, static_cast(0x805C0011), 6}, + {"\xFC\x84\x80\x80\x80\x80", 6, static_cast(0x805C0020), 6}, + {"\xFD\xBF\xBF\xBF\xBF\xBF", 6, static_cast(0x805C0020), 6}, + {"\xC2\x80", 2, static_cast(0x805C0020), 2}, + {"\xC2\x81", 2, static_cast(0x805C0020), 2}, + {"\xC2\x8D", 2, static_cast(0x805C0020), 2}, + {"\xC2\xA0", 2, 0, 2}, + {"\xC5\x92", 2, 0, 2}, + }; + for (const MeasuredUtf8& measured : cases) { + std::uint32_t length = 0xAAAAAAAA; + std::uint8_t sbc = 0xBB; + Require(sceCesUtf8ToSbc(reinterpret_cast(measured.bytes), measured.available, &length, profile, &sbc) == measured.result); + Require(length == measured.length); + Require(measured.result != 0 ? sbc == 0 : sbc != 0); + } +} + +constexpr std::uint32_t MeasuredEucJpPairs = 0x9295D4B1; +constexpr std::uint32_t MeasuredEucJpSupplementary = 0x75256134; + +struct MeasuredString { + const char* source; + std::uint32_t available; + std::uint32_t capacity; + int result; + std::uint32_t read; + std::uint32_t written; + const char* output; +}; + +static void RequireMeasuredStrings(void* latin, void* eucJp) { + constexpr int dstBufferEnd = static_cast(0x805C0031); + constexpr int invalidEncode = static_cast(0x805C0014); + constexpr int unassignedCode = static_cast(0x805C0020); + constexpr MeasuredString toUtf8[] = { + {"ABC", 3, 16, 0, 3, 3, "ABC"}, + {"ABC\0D", 5, 16, 0, 3, 3, "ABC"}, + {"ABC", 0, 16, 0, 3, 3, "ABC"}, + {"ABC", 3, 4, 0, 3, 3, "ABC"}, + {"ABC", 3, 3, dstBufferEnd, 2, 2, "AB"}, + {"ABC", 3, 2, dstBufferEnd, 1, 1, "A"}, + {"A\xE9" "B", 3, 16, 0, 3, 4, "A\xC3\xA9" "B"}, + {"A\xE9" "B", 3, 3, dstBufferEnd, 1, 1, "A"}, + {"\x80\x80\x80", 3, 7, dstBufferEnd, 2, 6, "\xE2\x82\xAC\xE2\x82\xAC"}, + {"A\x81" "B", 3, 16, unassignedCode, 1, 1, "A"}, + {"", 2, 16, 0, 0, 0, ""}, + }; + for (const MeasuredString& measured : toUtf8) { + std::uint8_t output[16]; + std::memset(output, 0xBB, sizeof(output)); + std::uint32_t read = 0xAAAAAAAA; + std::uint32_t written = 0xCCCCCCCC; + Require(sceCesMbcsStrToUtf8Str(latin, reinterpret_cast(measured.source), measured.available, &read, output, measured.capacity, &written) == measured.result); + Require(read == measured.read && written == measured.written); + Require(std::memcmp(output, measured.output, measured.written + 1) == 0 && output[measured.written + 1] == 0xBB); + Require(sceCesMbcsStrGetUtf8Len(latin, reinterpret_cast(measured.source), measured.available, &read, &written) == (measured.result == dstBufferEnd ? 0 : measured.result)); + } + constexpr MeasuredString fromUtf8[] = { + {"ABC", 3, 3, dstBufferEnd, 2, 2, "AB"}, + {"A\xC3\xA9" "B", 4, 16, 0, 4, 3, "A\xE9" "B"}, + {"A\xE4\xB8\x80" "B", 5, 16, unassignedCode, 1, 1, "A"}, + {"A\xC3", 2, 16, static_cast(0x805C0011), 1, 1, "A"}, + {"A\xFF" "B", 3, 16, invalidEncode, 1, 1, "A"}, + {"A\xC3\0B", 0, 16, invalidEncode, 1, 1, "A"}, + {"A\xC0\x80" "B", 4, 16, static_cast(0x805C0015), 1, 1, "A"}, + {"A\xED\xA0\x80" "B", 5, 16, static_cast(0x805C0015), 1, 1, "A"}, + {"A\xC2\x81" "B", 4, 16, unassignedCode, 1, 1, "A"}, + {"A\xF8\x88\x80\x80\x80" "B", 7, 16, unassignedCode, 1, 1, "A"}, + {"A\xE2\x41\xAC" "B", 5, 16, invalidEncode, 1, 1, "A"}, + {"ABC", 0, 16, 0, 3, 3, "ABC"}, + }; + for (const MeasuredString& measured : fromUtf8) { + std::uint8_t output[16]; + std::memset(output, 0xBB, sizeof(output)); + std::uint32_t read = 0xAAAAAAAA; + std::uint32_t written = 0xCCCCCCCC; + Require(sceCesUtf8StrToMbcsStr(latin, reinterpret_cast(measured.source), measured.available, &read, output, measured.capacity, &written) == measured.result); + Require(read == measured.read && written == measured.written); + Require(std::memcmp(output, measured.output, measured.written + 1) == 0 && output[measured.written + 1] == 0xBB); + } + constexpr MeasuredString eucToUtf8[] = { + {"A\xA4\xA2" "B", 0, 16, 0, 4, 5, "A\xE3\x81\x82" "B"}, + {"A\xA4\xA2" "B", 0, 4, dstBufferEnd, 1, 1, "A"}, + {"A\xA4\xA2" "B", 0, 5, dstBufferEnd, 3, 4, "A\xE3\x81\x82"}, + {"A\xA4", 2, 16, static_cast(0x805C0011), 1, 1, "A"}, + {"A\x0E" "B", 3, 16, invalidEncode, 1, 1, "A"}, + {"A\x80" "B", 0, 16, 0, 3, 4, "A\xC2\x80" "B"}, + {"A\xA4\0B", 0, 16, invalidEncode, 1, 1, "A"}, + }; + for (const MeasuredString& measured : eucToUtf8) { + std::uint8_t output[16]; + std::memset(output, 0xBB, sizeof(output)); + std::uint32_t read = 0xAAAAAAAA; + std::uint32_t written = 0xCCCCCCCC; + Require(sceCesMbcsStrToUtf8Str(eucJp, reinterpret_cast(measured.source), measured.available, &read, output, measured.capacity, &written) == measured.result); + Require(read == measured.read && written == measured.written); + Require(std::memcmp(output, measured.output, measured.written + 1) == 0 && output[measured.written + 1] == 0xBB); + } + + const auto* letters = reinterpret_cast("ABC"); + std::uint8_t output[4] = {0xBB, 0xBB, 0xBB, 0xBB}; + std::uint32_t read = 7; + std::uint32_t written = 7; + Require(sceCesMbcsStrToUtf8Str(latin, letters, 3, &read, output, 0, &written) == dstBufferEnd && read == 0 && written == 0 && output[0] == 0xBB); + Require(sceCesMbcsStrToUtf8Str(latin, letters, 3, &read, nullptr, 16, &written) == static_cast(0x805C0030) && read == 0 && written == 0); + Require(sceCesMbcsStrToUtf8Str(nullptr, letters, 3, &read, nullptr, 16, &written) == static_cast(0x805C0001)); + Require(sceCesMbcsStrToUtf8Str(latin, nullptr, 3, &read, nullptr, 16, &written) == static_cast(0x805C0010)); + Require(sceCesMbcsStrToUtf8Str(nullptr, letters, 3, &read, output, sizeof(output), &written) == static_cast(0x805C0001) && output[0] == 0 && output[1] == 0xBB); + output[0] = 0xBB; + Require(sceCesMbcsStrToUtf8Str(latin, nullptr, 3, &read, output, sizeof(output), &written) == static_cast(0x805C0010) && output[0] == 0 && read == 0 && written == 0); + output[0] = 0xBB; + Require(sceCesMbcsStrToUtf8Str(latin, nullptr, 3, &read, output, 0, &written) == static_cast(0x805C0010) && output[0] == 0xBB); + Require(sceCesMbcsStrGetUtf8Len(latin, reinterpret_cast("A\x81" "B"), 3, &read, &written) == unassignedCode && read == 1 && written == 1); + Require(sceCesMbcsStrGetUtf8Len(latin, nullptr, 3, &read, &written) == static_cast(0x805C0010) && read == 0 && written == 0); + Require(sceCesUtf8StrGetMbcsLen(latin, reinterpret_cast("A\xE4\xB8\x80" "B"), 5, &read, &written) == unassignedCode && read == 1 && written == 1); + Require(sceCesUtf8StrGetMbcsLen(latin, reinterpret_cast("A\xC3"), 2, &read, &written) == static_cast(0x805C0011) && read == 1 && written == 1); + + std::uint16_t wide[8]; + for (std::uint16_t& unit : wide) unit = 0xBBBB; + Require(sceCesMbcsStrToUcs2Str(latin, letters, 0, &read, wide, 3, &written) == dstBufferEnd && read == 2 && written == 2); + Require(wide[0] == u'A' && wide[1] == u'B' && wide[2] == 0 && wide[3] == 0xBBBB); + Require(sceCesMbcsStrToUcs2Str(latin, reinterpret_cast("A\x81" "B"), 3, &read, wide, 8, &written) == unassignedCode && read == 1 && written == 1 && wide[1] == 0); + const std::uint16_t han[] = {u'A', 0x4E00, u'B', 0}; + const std::uint16_t surrogate[] = {u'A', 0xD800, u'B', 0}; + const std::uint16_t control[] = {u'A', 0x0081, u'B', 0}; + const std::uint16_t plain[] = {u'A', u'B', u'C', 0}; + std::memset(output, 0xBB, sizeof(output)); + Require(sceCesUcs2StrToMbcsStr(latin, plain, 2, &read, output, sizeof(output), &written) == 0 && read == 2 && written == 2 && std::memcmp(output, "AB", 3) == 0); + Require(sceCesUcs2StrToMbcsStr(latin, plain, 0, &read, output, 3, &written) == dstBufferEnd && read == 2 && written == 2); + Require(sceCesUcs2StrToMbcsStr(latin, han, 0, &read, output, sizeof(output), &written) == unassignedCode && read == 1 && written == 1); + Require(sceCesUcs2StrToMbcsStr(latin, surrogate, 0, &read, output, sizeof(output), &written) == static_cast(0x805C0015) && read == 1 && written == 1); + Require(sceCesUcs2StrToMbcsStr(latin, control, 0, &read, output, sizeof(output), &written) == unassignedCode && read == 1 && written == 1); + Require(sceCesUcs2StrGetMbcsLen(latin, han, 0, &read, &written) == unassignedCode && read == 1 && written == 1); +} + +static std::uint32_t Fnv(std::uint32_t hash, std::uint32_t word) { + for (int k = 0; k < 4; ++k) { + hash ^= (word >> (8 * k)) & 0xFFu; + hash *= 16777619u; + } + return hash; +} + +constexpr std::uint32_t MeasuredEucJpEncoder = 0x3AF3CD39; +constexpr std::uint32_t MeasuredSjisEncoder = 0x2FB8906B; + +static std::uint32_t EncoderSweep(void* context) { + std::uint32_t hash = 2166136261u; + for (std::uint32_t value = 0; value <= 0xFFFF; ++value) { + const std::uint16_t source[1] = {static_cast(value)}; + std::uint8_t output[4] = {0xBB, 0xBB, 0xBB, 0xBB}; + std::uint32_t used = 0xAAAAAAAA; + std::uint32_t produced = 0xCCCCCCCC; + const int result = sceCesUcs2StrToMbcsStr(context, source, 1, &used, output, sizeof(output), &produced); + std::uint32_t bytes = 0; + std::memcpy(&bytes, output, sizeof(bytes)); + hash = Fnv(Fnv(Fnv(Fnv(hash, static_cast(result)), produced), bytes), used); + } + return hash; +} + +constexpr std::uint32_t MeasuredSjisDecoder = 0xCA99178C; + +static std::uint32_t SjisDecoderSweep(void* sjis) { + std::uint32_t hash = 2166136261u; + for (std::uint32_t lead = 0; lead < 256; ++lead) { + for (std::uint32_t trail = 0; trail < 256; ++trail) { + const std::uint8_t source[2] = {static_cast(lead), static_cast(trail)}; + std::uint16_t output[3] = {0xBBBB, 0xBBBB, 0xBBBB}; + std::uint32_t used = 0xAAAAAAAA; + std::uint32_t produced = 0xCCCCCCCC; + const int result = sceCesMbcsStrToUcs2Str(sjis, source, 2, &used, output, 3, &produced); + hash = Fnv(Fnv(Fnv(Fnv(Fnv(hash, static_cast(result)), used), produced), static_cast(output[0]) | static_cast(output[1]) << 16), output[2]); + } + } + return hash; +} + +static void RequireMeasuredEncoders(void* eucJp, void* sjis) { + Require(SjisDecoderSweep(sjis) == MeasuredSjisDecoder); + Require(EncoderSweep(eucJp) == MeasuredEucJpEncoder); + Require(EncoderSweep(sjis) == MeasuredSjisEncoder); + const std::uint16_t aliases[] = {0x00A2, 0x00A3, 0x00AC, 0x2014, 0x2016, 0x203E, 0x2212, 0x301C, 0xFEFF, 0x0080, 0}; + std::uint8_t output[32] = {}; + std::uint32_t used = 0; + std::uint32_t produced = 0; + Require(sceCesUcs2StrToMbcsStr(eucJp, aliases, 0, &used, output, sizeof(output), &produced) == 0 && used == 10 && produced == 17); + Require(std::memcmp(output, "\xA1\xF1\xA1\xF2\xA2\xCC\xA1\xBD\xA1\xC2\xA1\xB1\xA1\xDD\xA1\xC1\x80", 18) == 0); + Require(sceCesUcs2StrToMbcsStr(sjis, aliases, 0, &used, output, sizeof(output), &produced) == static_cast(0x805C0024) && used == 9 && produced == 16); + Require(std::memcmp(output, "\x81\x91\x81\x92\x81\xCA\x81\x5C\x81\x61\x81\x50\x81\x7C\x81\x60", 17) == 0); + const std::uint16_t shift[] = {u'A', 0x000E, 0}; + Require(sceCesUcs2StrToMbcsStr(sjis, shift, 0, &used, output, sizeof(output), &produced) == 0 && produced == 2); + Require(sceCesUcs2StrToMbcsStr(eucJp, shift, 0, &used, output, sizeof(output), &produced) == static_cast(0x805C0020) && used == 1 && produced == 1); +} + +static void RequireMeasuredEucJp(void* eucJp) { + constexpr int invalidProfile = static_cast(0x805C0004); + constexpr int invalidSrcBuffer = static_cast(0x805C0010); + constexpr int srcBufferEnd = static_cast(0x805C0011); + constexpr int invalidEncode = static_cast(0x805C0014); + constexpr int unassignedCode = static_cast(0x805C0020); + constexpr int invalidDstBuffer = static_cast(0x805C0030); + constexpr int dstBufferEnd = static_cast(0x805C0031); + std::uint32_t pairs = 2166136261u; + for (int lead = 0; lead < 256; ++lead) { + for (int trail = 0; trail < 256; ++trail) { + const std::uint8_t cell[2] = {static_cast(lead), static_cast(trail)}; + std::uint32_t used = 0xAAAAAAAA; + std::uint16_t code = 0xBBBB; + const int result = sceCesEucJpToUcs2(eucJp, cell, 2, &used, &code); + pairs = Fnv(Fnv(Fnv(Fnv(pairs, static_cast(cell[1])), static_cast(result)), used), code); + } + } + Require(pairs == MeasuredEucJpPairs); + std::uint32_t supplementary = 2166136261u; + for (int second = 0; second < 256; ++second) { + for (int third = 0; third < 256; ++third) { + const std::uint8_t cell[3] = {0x8F, static_cast(second), static_cast(third)}; + std::uint32_t used = 0xAAAAAAAA; + std::uint16_t code = 0xBBBB; + const int result = sceCesEucJpToUcs2(eucJp, cell, 3, &used, &code); + supplementary = Fnv(Fnv(Fnv(Fnv(supplementary, static_cast(third)), static_cast(result)), used), code); + } + } + Require(supplementary == MeasuredEucJpSupplementary); + + const auto* hiragana = reinterpret_cast("\xA4\xA2"); + std::uint32_t used = 7; + std::uint32_t produced = 7; + std::uint16_t code = 0xBBBB; + std::uint8_t utf8[4] = {0xBB, 0xBB, 0xBB, 0xBB}; + Require(sceCesEucJpToUcs2(nullptr, hiragana, 2, &used, &code) == invalidProfile && used == 0 && code == 0); + Require(sceCesEucJpToUcs2(eucJp, nullptr, 2, &used, &code) == invalidSrcBuffer && used == 0); + Require(sceCesEucJpToUcs2(eucJp, hiragana, 0, &used, nullptr) == invalidSrcBuffer && used == 0); + Require(sceCesEucJpToUcs2(eucJp, hiragana, 1, &used, nullptr) == srcBufferEnd && used == 2); + Require(sceCesEucJpToUcs2(eucJp, hiragana, 2, &used, nullptr) == invalidDstBuffer && used == 2); + Require(sceCesEucJpToUcs2(eucJp, hiragana, 2, nullptr, &code) == 0 && code == 0x3042); + Require(sceCesEucJpToUtf8(eucJp, hiragana, 2, &used, utf8, sizeof(utf8), &produced) == 0 && used == 2 && produced == 3 && std::memcmp(utf8, "\xE3\x81\x82", 3) == 0); + Require(sceCesEucJpToUtf8(eucJp, reinterpret_cast("\x8E\xB1"), 2, &used, utf8, sizeof(utf8), &produced) == 0 && used == 2 && produced == 3 && std::memcmp(utf8, "\xEF\xBD\xB1", 3) == 0); + Require(sceCesEucJpToUtf8(eucJp, reinterpret_cast("\x80"), 1, &used, utf8, sizeof(utf8), &produced) == 0 && used == 1 && produced == 2 && std::memcmp(utf8, "\xC2\x80", 2) == 0); + std::memset(utf8, 0xBB, sizeof(utf8)); + Require(sceCesEucJpToUtf8(nullptr, nullptr, 2, &used, utf8, sizeof(utf8), &produced) == invalidProfile && used == 0 && produced == 0); + Require(sceCesEucJpToUtf8(eucJp, nullptr, 0, &used, utf8, sizeof(utf8), &produced) == invalidSrcBuffer && used == 0 && produced == 0); + Require(sceCesEucJpToUtf8(eucJp, hiragana, 1, &used, nullptr, sizeof(utf8), &produced) == srcBufferEnd && used == 2 && produced == 0); + Require(sceCesEucJpToUtf8(eucJp, reinterpret_cast("\xA4\x20"), 2, &used, utf8, sizeof(utf8), &produced) == invalidEncode && used == 2 && produced == 0); + Require(sceCesEucJpToUtf8(eucJp, reinterpret_cast("\x8E\xE0"), 2, &used, utf8, sizeof(utf8), &produced) == unassignedCode && used == 2 && produced == 0); + Require(sceCesEucJpToUtf8(eucJp, reinterpret_cast("\xA9\xA1"), 2, &used, utf8, sizeof(utf8), &produced) == unassignedCode && used == 2 && produced == 0); + Require(sceCesEucJpToUtf8(eucJp, reinterpret_cast("\xFF\xA1"), 2, &used, utf8, sizeof(utf8), &produced) == invalidEncode && used == 1 && produced == 0); + Require(sceCesEucJpToUtf8(eucJp, hiragana, 2, &used, nullptr, sizeof(utf8), &produced) == invalidDstBuffer && used == 2 && produced == 3); + Require(sceCesEucJpToUtf8(eucJp, hiragana, 2, &used, utf8, 2, &produced) == dstBufferEnd && used == 2 && produced == 3); + Require(sceCesEucJpToUtf8(eucJp, hiragana, 2, &used, utf8, 0, &produced) == dstBufferEnd && used == 2 && produced == 3); + Require(utf8[0] == 0xBB); + Require(sceCesEucJpToUtf8(eucJp, hiragana, 2, nullptr, utf8, sizeof(utf8), nullptr) == 0 && std::memcmp(utf8, "\xE3\x81\x82", 3) == 0); +} + +static void RequireSbcCode(const std::uint8_t* profile, std::uint8_t sbc, std::uint32_t code) { + char expected[4] = {}; + if (code < 0x80) { + expected[0] = static_cast(code); + } else if (code < 0x800) { + expected[0] = static_cast(0xC0 | (code >> 6)); + expected[1] = static_cast(0x80 | (code & 0x3F)); + } else { + expected[0] = static_cast(0xE0 | (code >> 12)); + expected[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + expected[2] = static_cast(0x80 | (code & 0x3F)); + } + RequireSbcToUtf8(profile, sbc, expected); +} + +static void CheckSingleByteProfiles() { + constexpr int unassignedCode = static_cast(0x805C0020); + const auto getters = {sceCesRefersUcsProfileIso8859_1, sceCesRefersUcsProfileIso8859_2, sceCesRefersUcsProfileIso8859_4, sceCesRefersUcsProfileIso8859_5, sceCesRefersUcsProfileIso8859_9, sceCesRefersUcsProfileIso8859_10, sceCesRefersUcsProfileIso8859_13, sceCesRefersUcsProfileIso8859_14, sceCesRefersUcsProfileIso8859_15, sceCesRefersUcsProfileIso8859_16, sceCesRefersUcsProfileCp437, sceCesRefersUcsProfileCp737, sceCesRefersUcsProfileCp775, sceCesRefersUcsProfileCp850, sceCesRefersUcsProfileCp852, sceCesRefersUcsProfileCp855, sceCesRefersUcsProfileCp860, sceCesRefersUcsProfileCp861, sceCesRefersUcsProfileCp862, sceCesRefersUcsProfileCp863, sceCesRefersUcsProfileCp865, sceCesRefersUcsProfileCp866, sceCesRefersUcsProfileKoi8R, sceCesRefersUcsProfileKoi8U, sceCesRefersUcsProfileCp1254, sceCesRefersUcsProfileCp1256, sceCesRefersUcsProfileCp1258}; + const void* existing[] = {sceCesRefersUcsProfileCp1252(), sceCesRefersUcsProfileCp1251(), sceCesRefersUcsProfileCp1250()}; + std::size_t index = 0; + for (const auto getter : getters) { + const std::uint8_t* profile = getter(); + Require(profile != nullptr && profile == getter()); + for (const void* other : existing) Require(profile != other); + std::size_t inner = 0; + for (const auto earlier : getters) { + if (inner++ == index) break; + Require(profile != earlier()); + } + ++index; + for (int sbc = 1; sbc < 256; ++sbc) { + std::uint8_t cp[4] = {}; + std::uint32_t cpLength = 0; + std::uint8_t back = 0; + Require(sceCesSbcToUtf8(profile, static_cast(sbc), cp, sizeof(cp), &cpLength) == 0); + if (sbc < 0x80) Require(cpLength == 1 && cp[0] == sbc); + Require(sceCesUtf8ToSbc(cp, cpLength, &cpLength, profile, &back) == 0 && back == sbc); + } + Require(Utf8ToSbc(profile, "\xE3\x81\x82", 3) == unassignedCode); + } + struct Check { + decltype(&sceCesRefersUcsProfileIso8859_1) profile; + std::uint8_t sbc; + std::uint32_t code; + }; + const Check checks[] = { + {sceCesRefersUcsProfileIso8859_1, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_1, 0xA4, 0x00A4}, + {sceCesRefersUcsProfileIso8859_1, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileIso8859_1, 0xFF, 0x00FF}, + {sceCesRefersUcsProfileIso8859_2, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_2, 0xA4, 0x00A4}, + {sceCesRefersUcsProfileIso8859_2, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileIso8859_2, 0xFF, 0x02D9}, + {sceCesRefersUcsProfileIso8859_4, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_4, 0xA4, 0x00A4}, + {sceCesRefersUcsProfileIso8859_4, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileIso8859_4, 0xFF, 0x02D9}, + {sceCesRefersUcsProfileIso8859_5, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_5, 0xA4, 0x0404}, + {sceCesRefersUcsProfileIso8859_5, 0xC1, 0x0421}, + {sceCesRefersUcsProfileIso8859_5, 0xFF, 0x045F}, + {sceCesRefersUcsProfileIso8859_9, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_9, 0xA4, 0x00A4}, + {sceCesRefersUcsProfileIso8859_9, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileIso8859_9, 0xFF, 0x00FF}, + {sceCesRefersUcsProfileIso8859_10, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_10, 0xA4, 0x012A}, + {sceCesRefersUcsProfileIso8859_10, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileIso8859_10, 0xFF, 0x0138}, + {sceCesRefersUcsProfileIso8859_13, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_13, 0xA4, 0x00A4}, + {sceCesRefersUcsProfileIso8859_13, 0xC1, 0x012E}, + {sceCesRefersUcsProfileIso8859_13, 0xFF, 0x2019}, + {sceCesRefersUcsProfileIso8859_14, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_14, 0xA4, 0x010A}, + {sceCesRefersUcsProfileIso8859_14, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileIso8859_14, 0xFF, 0x00FF}, + {sceCesRefersUcsProfileIso8859_15, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_15, 0xA4, 0x20AC}, + {sceCesRefersUcsProfileIso8859_15, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileIso8859_15, 0xFF, 0x00FF}, + {sceCesRefersUcsProfileIso8859_16, 0x80, 0x0080}, + {sceCesRefersUcsProfileIso8859_16, 0xA4, 0x20AC}, + {sceCesRefersUcsProfileIso8859_16, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileIso8859_16, 0xFF, 0x00FF}, + {sceCesRefersUcsProfileCp437, 0x80, 0x00C7}, + {sceCesRefersUcsProfileCp437, 0xA4, 0x00F1}, + {sceCesRefersUcsProfileCp437, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp437, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp737, 0x80, 0x0391}, + {sceCesRefersUcsProfileCp737, 0xA4, 0x03BD}, + {sceCesRefersUcsProfileCp737, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp737, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp775, 0x80, 0x0106}, + {sceCesRefersUcsProfileCp775, 0xA4, 0x017C}, + {sceCesRefersUcsProfileCp775, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp775, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp850, 0x80, 0x00C7}, + {sceCesRefersUcsProfileCp850, 0xA4, 0x00F1}, + {sceCesRefersUcsProfileCp850, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp850, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp852, 0x80, 0x00C7}, + {sceCesRefersUcsProfileCp852, 0xA4, 0x0104}, + {sceCesRefersUcsProfileCp852, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp852, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp855, 0x80, 0x0452}, + {sceCesRefersUcsProfileCp855, 0xA4, 0x0446}, + {sceCesRefersUcsProfileCp855, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp855, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp860, 0x80, 0x00C7}, + {sceCesRefersUcsProfileCp860, 0xA4, 0x00F1}, + {sceCesRefersUcsProfileCp860, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp860, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp861, 0x80, 0x00C7}, + {sceCesRefersUcsProfileCp861, 0xA4, 0x00C1}, + {sceCesRefersUcsProfileCp861, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp861, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp862, 0x80, 0x05D0}, + {sceCesRefersUcsProfileCp862, 0xA4, 0x00F1}, + {sceCesRefersUcsProfileCp862, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp862, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp863, 0x80, 0x00C7}, + {sceCesRefersUcsProfileCp863, 0xA4, 0x00A8}, + {sceCesRefersUcsProfileCp863, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp863, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp865, 0x80, 0x00C7}, + {sceCesRefersUcsProfileCp865, 0xA4, 0x00F1}, + {sceCesRefersUcsProfileCp865, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp865, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileCp866, 0x80, 0x0410}, + {sceCesRefersUcsProfileCp866, 0xA4, 0x0434}, + {sceCesRefersUcsProfileCp866, 0xC1, 0x2534}, + {sceCesRefersUcsProfileCp866, 0xFF, 0x00A0}, + {sceCesRefersUcsProfileKoi8R, 0x80, 0x2500}, + {sceCesRefersUcsProfileKoi8R, 0xA4, 0x2553}, + {sceCesRefersUcsProfileKoi8R, 0xC1, 0x0430}, + {sceCesRefersUcsProfileKoi8R, 0xFF, 0x042A}, + {sceCesRefersUcsProfileKoi8U, 0x80, 0x2500}, + {sceCesRefersUcsProfileKoi8U, 0xA4, 0x0454}, + {sceCesRefersUcsProfileKoi8U, 0xC1, 0x0430}, + {sceCesRefersUcsProfileKoi8U, 0xFF, 0x042A}, + {sceCesRefersUcsProfileCp1254, 0x80, 0x20AC}, + {sceCesRefersUcsProfileCp1254, 0xA4, 0x00A4}, + {sceCesRefersUcsProfileCp1254, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileCp1254, 0xFF, 0x00FF}, + {sceCesRefersUcsProfileCp1256, 0x80, 0x20AC}, + {sceCesRefersUcsProfileCp1256, 0xA4, 0x00A4}, + {sceCesRefersUcsProfileCp1256, 0xC1, 0x0621}, + {sceCesRefersUcsProfileCp1256, 0xFF, 0x06D2}, + {sceCesRefersUcsProfileCp1258, 0x80, 0x20AC}, + {sceCesRefersUcsProfileCp1258, 0xA4, 0x00A4}, + {sceCesRefersUcsProfileCp1258, 0xC1, 0x00C1}, + {sceCesRefersUcsProfileCp1258, 0xFF, 0x00FF}, + }; + for (const Check& check : checks) RequireSbcCode(check.profile(), check.sbc, check.code); + + char context[256] = {}; + Require(sceCesMbcsUcsContextInit(context, sceCesRefersUcsProfileKoi8R()) == 0); + std::uint8_t koi8[16] = {}; + std::uint32_t used = 0; + std::uint32_t produced = 0; + Require(sceCesUtf8StrToMbcsStr(context, reinterpret_cast("\xD0\x9F\xD1\x80\xD0\xB8\xD0\xB2\xD0\xB5\xD1\x82"), 12, &used, koi8, sizeof(koi8), &produced) == 0); + Require(used == 12 && produced == 6 && std::memcmp(koi8, "\xF0\xD2\xC9\xD7\xC5\xD4", 6) == 0); + std::uint8_t utf8[32] = {}; + Require(sceCesMbcsStrToUtf8Str(context, koi8, 6, &used, utf8, sizeof(utf8), &produced) == 0); + Require(used == 6 && produced == 12 && std::memcmp(utf8, "\xD0\x9F\xD1\x80\xD0\xB8\xD0\xB2\xD0\xB5\xD1\x82", 12) == 0); +} + int main() { constexpr int invalidParameter = static_cast(0x805C0001); constexpr int invalidSrcBuffer = static_cast(0x805C0010); @@ -57,11 +524,11 @@ int main() { const std::uint8_t* profile = sceCesRefersUcsProfileCp1252(); Require(profile != nullptr && profile == sceCesRefersUcsProfileCp1252()); + CheckSingleByteProfiles(); RequireSbcToUtf8(profile, 'A', "A"); RequireSbcToUtf8(profile, 0x80, "\xE2\x82\xAC"); RequireSbcToUtf8(profile, 0x9F, "\xC5\xB8"); - RequireSbcToUtf8(profile, 0x81, "\xC2\x81"); RequireSbcToUtf8(profile, 0xE9, "\xC3\xA9"); RequireSbcToUtf8(profile, 0xFF, "\xC3\xBF"); @@ -69,29 +536,48 @@ int main() { RequireUtf8ToSbc(profile, "\xE2\x82\xAC", 0x80); RequireUtf8ToSbc(profile, "\xE2\x84\xA2", 0x99); RequireUtf8ToSbc(profile, "\xC3\xA9", 0xE9); - RequireUtf8ToSbc(profile, "\xC2\x8D", 0x8D); - for (int sbc = 1; sbc < 256; ++sbc) { - std::uint8_t utf8[4] = {}; - std::uint32_t length = 0; - std::uint8_t back = 0; - Require(sceCesSbcToUtf8(profile, static_cast(sbc), utf8, sizeof(utf8), &length) == 0); - Require(sceCesUtf8ToSbc(utf8, length, &length, profile, &back) == 0 && back == sbc); - } + RequireSbcRoundTrip(profile, {0x81, 0x8D, 0x8F, 0x90, 0x9D}); + RequireMeasuredUtf8ToSbc(profile); std::uint8_t utf8[4] = {}; std::uint32_t length = 0; std::uint8_t sbc = 0; - Require(sceCesSbcToUtf8(nullptr, 'A', utf8, sizeof(utf8), &length) == invalidParameter); - Require(sceCesSbcToUtf8(profile, 'A', nullptr, sizeof(utf8), &length) == invalidDstBuffer); - Require(sceCesSbcToUtf8(profile, 0x80, utf8, 2, &length) == dstBufferEnd); - Require(sceCesUtf8ToSbc(utf8, 1, &length, nullptr, &sbc) == invalidParameter); - Require(sceCesUtf8ToSbc(nullptr, 1, &length, profile, &sbc) == invalidSrcBuffer); - Require(sceCesUtf8ToSbc(utf8, 1, &length, profile, nullptr) == invalidDstBuffer); - Require(Utf8ToSbc(profile, "A", 0) == srcBufferEnd); + constexpr int invalidProfile = static_cast(0x805C0004); + constexpr int illegalCode = static_cast(0x805C0015); + length = 7; + Require(sceCesSbcToUtf8(nullptr, 'A', utf8, sizeof(utf8), &length) == invalidProfile && length == 0); + Require(sceCesSbcToUtf8(nullptr, 0x81, utf8, sizeof(utf8), &length) == invalidProfile && length == 0); + Require(sceCesSbcToUtf8(profile, 0x80, nullptr, sizeof(utf8), &length) == invalidDstBuffer && length == 3); + Require(sceCesSbcToUtf8(profile, 0x81, nullptr, sizeof(utf8), &length) == unassignedCode && length == 0); + Require(sceCesSbcToUtf8(profile, 0x81, utf8, 0, &length) == unassignedCode && length == 0); + utf8[0] = 0xBB; + Require(sceCesSbcToUtf8(profile, 0x80, utf8, 2, &length) == dstBufferEnd && length == 3 && utf8[0] == 0xBB); + Require(sceCesSbcToUtf8(profile, 'A', utf8, 0, &length) == dstBufferEnd && length == 1 && utf8[0] == 0xBB); + Require(sceCesSbcToUtf8(profile, 0xE9, utf8, 1, nullptr) == dstBufferEnd); + const auto* euro = reinterpret_cast("\xE2\x82\xAC"); + const auto* han = reinterpret_cast("\xE4\xB8\x80"); + const auto* letter = reinterpret_cast("A"); + sbc = 0xBB; + Require(sceCesUtf8ToSbc(letter, 1, &length, nullptr, &sbc) == invalidProfile && length == 1 && sbc == 0); + Require(sceCesUtf8ToSbc(letter, 1, &length, nullptr, nullptr) == invalidProfile && length == 1); + Require(sceCesUtf8ToSbc(euro, 3, &length, nullptr, &sbc) == invalidProfile && length == 3); + Require(sceCesUtf8ToSbc(euro, 2, &length, nullptr, &sbc) == srcBufferEnd && length == 3); + Require(sceCesUtf8ToSbc(reinterpret_cast("\xFF"), 1, &length, nullptr, &sbc) == invalidEncode && length == 0); + sbc = 0xBB; + Require(sceCesUtf8ToSbc(nullptr, 1, &length, nullptr, &sbc) == invalidSrcBuffer && length == 0 && sbc == 0); + Require(sceCesUtf8ToSbc(nullptr, 1, &length, profile, nullptr) == invalidSrcBuffer && length == 0); + sbc = 0xBB; + Require(sceCesUtf8ToSbc(letter, 0, &length, profile, &sbc) == invalidSrcBuffer && length == 0 && sbc == 0); + Require(sceCesUtf8ToSbc(euro, 3, &length, profile, nullptr) == invalidDstBuffer && length == 3); + Require(sceCesUtf8ToSbc(han, 3, &length, profile, nullptr) == unassignedCode && length == 3); + sbc = 0xBB; + Require(sceCesUtf8ToSbc(han, 3, &length, profile, &sbc) == unassignedCode && length == 3 && sbc == 0); + Require(sceCesUtf8ToSbc(euro, 3, nullptr, profile, &sbc) == 0 && sbc == 0x80); + Require(Utf8ToSbc(profile, "A", 0) == invalidSrcBuffer); Require(Utf8ToSbc(profile, "\xE2\x82\xAC", 2) == srcBufferEnd); Require(Utf8ToSbc(profile, "\x80", 1) == invalidEncode); - Require(Utf8ToSbc(profile, "\xC0\xAF", 2) == invalidEncode); - Require(Utf8ToSbc(profile, "\xED\xA0\x80", 3) == invalidEncode); + Require(Utf8ToSbc(profile, "\xC0\xAF", 2) == illegalCode); + Require(Utf8ToSbc(profile, "\xED\xA0\x80", 3) == illegalCode); Require(Utf8ToSbc(profile, "\xE3\x81\x82", 3) == unassignedCode); Require(Utf8ToSbc(profile, "\xF0\x9F\x98\x80", 4) == unassignedCode); @@ -105,21 +591,14 @@ int main() { RequireSbcToUtf8(cyrillic, 0xB9, "\xE2\x84\x96"); RequireUtf8ToSbc(cyrillic, "\xD0\x9F", 0xCF); RequireUtf8ToSbc(cyrillic, "\xD1\x91", 0xB8); - for (int sbc = 1; sbc < 256; ++sbc) { - std::uint8_t cp[4] = {}; - std::uint32_t cpLength = 0; - std::uint8_t back = 0; - Require(sceCesSbcToUtf8(cyrillic, static_cast(sbc), cp, sizeof(cp), &cpLength) == 0); - Require(sceCesUtf8ToSbc(cp, cpLength, &cpLength, cyrillic, &back) == 0 && back == sbc); - } + RequireSbcRoundTrip(cyrillic, {0x98}); Require(Utf8ToSbc(cyrillic, "\xC3\xA9", 2) == unassignedCode); Require(Utf8ToSbc(profile, "\xD0\x90", 2) == unassignedCode); - const std::uint8_t* latin2 = sceCesRefersUcsProfileCp1250(); + const auto* latin2 = static_cast(sceCesRefersUcsProfileCp1250()); Require(latin2 != nullptr && latin2 == sceCesRefersUcsProfileCp1250() && latin2 != profile && latin2 != cyrillic); RequireSbcToUtf8(latin2, 'A', "A"); RequireSbcToUtf8(latin2, 0x80, "\xE2\x82\xAC"); - RequireSbcToUtf8(latin2, 0x81, "\xC2\x81"); RequireSbcToUtf8(latin2, 0x8A, "\xC5\xA0"); RequireSbcToUtf8(latin2, 0xA3, "\xC5\x81"); RequireSbcToUtf8(latin2, 0xA5, "\xC4\x84"); @@ -129,13 +608,7 @@ int main() { RequireUtf8ToSbc(latin2, "\xC5\xA0", 0x8A); RequireUtf8ToSbc(latin2, "\xC5\x81", 0xA3); RequireUtf8ToSbc(latin2, "\xC3\xA9", 0xE9); - for (int sbc = 1; sbc < 256; ++sbc) { - std::uint8_t cp[4] = {}; - std::uint32_t cpLength = 0; - std::uint8_t back = 0; - Require(sceCesSbcToUtf8(latin2, static_cast(sbc), cp, sizeof(cp), &cpLength) == 0); - Require(sceCesUtf8ToSbc(cp, cpLength, &cpLength, latin2, &back) == 0 && back == sbc); - } + RequireSbcRoundTrip(latin2, {0x81, 0x83, 0x88, 0x90, 0x98}); Require(Utf8ToSbc(latin2, "\xD0\x90", 2) == unassignedCode); char latin2Context[256] = {}; Require(sceCesMbcsUcsContextInit(latin2Context, latin2) == 0); @@ -156,7 +629,7 @@ int main() { char sjisContext[256] = {}; Require(sceCesMbcsUcsContextInit(sjisContext, sjisSheet) == 0); const auto* eucSource = reinterpret_cast("Go \xEF\xBD\xB1!"); - std::uint8_t mbcs[16] = {}; + std::uint8_t mbcs[32] = {}; std::uint32_t used = 0; std::uint32_t produced = 0; char sheet[1024] = {}; @@ -169,8 +642,12 @@ int main() { Require(used == 7 && produced == 6 && std::memcmp(mbcs, "Go \x8E\xB1!", 6) == 0); Require(sceCesUtf8StrGetMbcsLen(eucContext, eucSource, 16, &used, &produced) == 0 && used == 7 && produced == 6); Require(sceCesUtf8StrToMbcsStr(eucContext, eucSource, 16, &used, mbcs, 4, &produced) == dstBufferEnd); + Require(used == 3 && produced == 3 && std::memcmp(mbcs, "Go ", 4) == 0); + Require(sceCesUtf8StrToMbcsStr(eucContext, eucSource, 16, &used, mbcs, sizeof(mbcs), &produced) == 0); Require(sceCesUtf8StrToMbcsStr(nullptr, eucSource, 16, &used, mbcs, sizeof(mbcs), &produced) == invalidParameter); Require(sceCesUtf8StrToMbcsStr(eucContext, reinterpret_cast("\xF0\x9F\x98\x80"), 4, &used, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(used == 0 && produced == 0 && mbcs[0] == 0); + Require(sceCesUtf8StrToMbcsStr(eucContext, eucSource, 16, &used, mbcs, sizeof(mbcs), &produced) == 0); std::uint8_t back[16] = {}; Require(sceCesMbcsStrToUtf8Str(nullptr, mbcs, 6, &used, back, sizeof(back), &produced) == invalidParameter); @@ -185,8 +662,8 @@ int main() { Require(sceCesUcs2StrToMbcsStr(eucContext, ucs2, 4, &used, mbcs, sizeof(mbcs), &produced) == 0); Require(used == 3 && produced == 4 && std::memcmp(mbcs, "OK\x8E\xDF", 4) == 0); Require(sceCesUcs2StrGetMbcsLen(eucContext, ucs2, 4, &used, &produced) == 0 && produced == 4); - std::uint16_t wide[8] = {}; - Require(sceCesMbcsStrToUcs2Str(eucContext, mbcs, 4, &used, wide, 8, &produced) == 0); + std::uint16_t wide[16] = {}; + Require(sceCesMbcsStrToUcs2Str(eucContext, mbcs, 4, &used, wide, 16, &produced) == 0); Require(used == 4 && produced == 3 && wide[0] == u'O' && wide[1] == u'K' && wide[2] == 0xFF9F); Require(sceCesMbcsStrGetUcs2Len(eucContext, mbcs, 4, &used, &produced) == 0 && produced == 3); std::uint16_t single = 0; @@ -198,15 +675,15 @@ int main() { Require(used == 24 && produced == 16 && std::memcmp(mbcs, "\xA4\xB3\xA4\xF3\xA4\xCB\xA4\xC1\xA4\xCF\xC6\xFC\xCB\xDC\xB8\xEC", 16) == 0); std::uint8_t japaneseBack[32] = {}; Require(sceCesMbcsStrToUtf8Str(eucContext, mbcs, 16, &used, japaneseBack, sizeof(japaneseBack), &produced) == 0 && produced == 24 && std::memcmp(japaneseBack, japanese, 24) == 0); - Require(sceCesMbcsStrToUcs2Str(eucContext, mbcs, 16, &used, wide, 8, &produced) == 0 && used == 16 && produced == 8 && wide[0] == 0x3053 && wide[5] == 0x65E5 && wide[7] == 0x8A9E); - Require(sceCesMbcsStrToUcs2Str(eucContext, mbcs, 15, &used, wide, 8, &produced) == srcBufferEnd); + Require(sceCesMbcsStrToUcs2Str(eucContext, mbcs, 16, &used, wide, 16, &produced) == 0 && used == 16 && produced == 8 && wide[0] == 0x3053 && wide[5] == 0x65E5 && wide[7] == 0x8A9E); + Require(sceCesMbcsStrToUcs2Str(eucContext, mbcs, 15, &used, wide, 16, &produced) == srcBufferEnd); const std::uint16_t extensions[] = {0x2460, 0xFF5E, 0x2252, 0}; Require(sceCesUcs2StrToMbcsStr(eucContext, extensions, 4, &used, mbcs, sizeof(mbcs), &produced) == 0 && produced == 6 && std::memcmp(mbcs, "\xAD\xA1\xA1\xC1\xA2\xE2", 6) == 0); - Require(sceCesMbcsStrToUcs2Str(eucContext, reinterpret_cast("\xA9\xA1"), 2, &used, wide, 8, &produced) == unassignedCode); - Require(sceCesMbcsStrToUcs2Str(eucContext, reinterpret_cast("\xA4\x41"), 2, &used, wide, 8, &produced) == invalidEncode); - Require(sceCesMbcsStrToUcs2Str(eucContext, reinterpret_cast("\xA4"), 1, &used, wide, 8, &produced) == srcBufferEnd); + Require(sceCesMbcsStrToUcs2Str(eucContext, reinterpret_cast("\xA9\xA1"), 2, &used, wide, 16, &produced) == unassignedCode); + Require(sceCesMbcsStrToUcs2Str(eucContext, reinterpret_cast("\xA4\x41"), 2, &used, wide, 16, &produced) == invalidEncode); + Require(sceCesMbcsStrToUcs2Str(eucContext, reinterpret_cast("\xA4"), 1, &used, wide, 16, &produced) == srcBufferEnd); const std::uint16_t yen[] = {0x00A5, 0}; - Require(sceCesUcs2StrToMbcsStr(eucContext, yen, 2, &used, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(sceCesUcs2StrToMbcsStr(eucContext, yen, 2, &used, mbcs, sizeof(mbcs), &produced) == 0 && used == 1 && produced == 2 && std::memcmp(mbcs, "\xA1\xEF", 3) == 0); std::uint32_t assigned = 0; for (std::uint32_t lead = 0xA1; lead <= 0xFE; ++lead) { for (std::uint32_t trail = 0xA1; trail <= 0xFE; ++trail) { @@ -225,47 +702,36 @@ int main() { Require(assigned == 7336); std::uint16_t supplementary = 0; - Require(sceCesEucJpToUcs2(eucJp, reinterpret_cast("\x8F\xA2\xAF"), 3, &used, &supplementary) == 0 && used == 3 && supplementary == 0x02D8); - Require(sceCesEucJpToUcs2(eucJp, reinterpret_cast("\x8F\xA2"), 2, &used, &supplementary) == srcBufferEnd); + Require(sceCesEucJpToUcs2(eucJp, reinterpret_cast("\x8F\xA2\xAF"), 3, &used, &supplementary) == invalidEncode && used == 3 && supplementary == 0); + Require(sceCesEucJpToUcs2(eucJp, reinterpret_cast("\x8F\xA2"), 2, &used, &supplementary) == srcBufferEnd && used == 3); Require(sceCesEucJpToUcs2(eucJp, reinterpret_cast("\x8F\x41\xA1"), 3, &used, &supplementary) == invalidEncode); Require(sceCesEucJpToUcs2(eucJp, reinterpret_cast("\x8F\xA2\x41"), 3, &used, &supplementary) == invalidEncode); Require(sceCesEucJpToUcs2(eucJp, reinterpret_cast("\x8F\xA1\xA1"), 3, &used, &supplementary) == unassignedCode); Require(sceCesEucJpToUcs2(eucJp, reinterpret_cast("\x8F\xF5\xA1"), 3, &used, &supplementary) == unassignedCode); - const std::uint16_t supplementaryText[] = {0x02D8, 0x0386, 0x0402, 0x6600, 0}; - Require(sceCesUcs2StrToMbcsStr(eucContext, supplementaryText, 5, &used, mbcs, sizeof(mbcs), &produced) == 0); - Require(used == 4 && produced == 11 && std::memcmp(mbcs, "\x8F\xA2\xAF\x8F\xA6\xE1\x8F\xA7\xC2\xFA\xB4", 11) == 0); - Require(sceCesUcs2StrGetMbcsLen(eucContext, supplementaryText, 5, &used, &produced) == 0 && produced == 11); - std::uint16_t supplementaryBack[8] = {}; - Require(sceCesMbcsStrToUcs2Str(eucContext, mbcs, 11, &used, supplementaryBack, 8, &produced) == 0); - Require(used == 11 && produced == 4 && supplementaryBack[0] == 0x02D8 && supplementaryBack[1] == 0x0386 && supplementaryBack[2] == 0x0402 && supplementaryBack[3] == 0x6600); - Require(sceCesMbcsStrGetUcs2Len(eucContext, mbcs, 11, &used, &produced) == 0 && produced == 4); - Require(sceCesMbcsStrToUtf8Str(eucContext, mbcs, 11, &used, japaneseBack, sizeof(japaneseBack), &produced) == 0); - Require(produced == 9 && std::memcmp(japaneseBack, "\xCB\x98\xCE\x86\xD0\x82\xE6\x98\x80", 9) == 0); - Require(sceCesMbcsStrGetUtf8Len(eucContext, mbcs, 11, &used, &produced) == 0 && produced == 9); - Require(sceCesMbcsStrToUcs2Str(eucContext, mbcs, 10, &used, supplementaryBack, 8, &produced) == srcBufferEnd); - std::uint32_t supplementaryAssigned = 0; - for (std::uint32_t lead = 0xA2; lead <= 0xF4; ++lead) { - for (std::uint32_t trail = 0xA1; trail <= 0xFE; ++trail) { - const std::uint8_t cell[3] = {0x8F, static_cast(lead), static_cast(trail)}; - std::uint16_t decoded[2] = {}; - if (sceCesEucJpToUcs2(eucJp, cell, 3, &used, decoded) != 0) continue; - ++supplementaryAssigned; - decoded[1] = 0; - std::uint8_t again[4] = {}; - std::uint16_t second[2] = {}; - Require(sceCesUcs2StrToMbcsStr(eucContext, decoded, 2, &used, again, sizeof(again), &produced) == 0); - if (produced == 3) Require(again[1] == cell[1] && again[2] == cell[2]); - Require(sceCesEucJpToUcs2(eucJp, again, produced, &used, second) == 0 && second[0] == decoded[0]); - } - } - Require(supplementaryAssigned == 6067); + const std::uint16_t supplementaryText[] = {u'A', 0x02D8, 0}; + Require(sceCesUcs2StrToMbcsStr(eucContext, supplementaryText, 0, &used, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(used == 1 && produced == 1 && std::memcmp(mbcs, "A", 2) == 0); + const std::uint16_t ibmText[] = {0x6600, 0x2170, 0}; + Require(sceCesUcs2StrToMbcsStr(eucContext, ibmText, 0, &used, mbcs, sizeof(mbcs), &produced) == 0); + Require(used == 2 && produced == 4 && std::memcmp(mbcs, "\xFA\xB4\xFC\xF1", 5) == 0); + Require(sceCesMbcsStrToUtf8Str(eucContext, reinterpret_cast("A\x8F\xA2\xAF" "B"), 0, &used, japaneseBack, sizeof(japaneseBack), &produced) == invalidEncode); + Require(used == 1 && produced == 1 && std::memcmp(japaneseBack, "A", 2) == 0); + RequireMeasuredEucJp(eucJp); + char measuredLatin[256] = {}; + Require(sceCesMbcsUcsContextInit(measuredLatin, profile) == 0); + RequireMeasuredStrings(measuredLatin, eucContext); + Require(sceCesMbcsUcsContextInit(measuredLatin, nullptr) == invalidParameter); const auto* sjisSource = reinterpret_cast("Go \xEF\xBD\xB1!"); Require(sceCesUtf8StrToMbcsStr(sjisContext, sjisSource, 16, &used, mbcs, sizeof(mbcs), &produced) == 0); Require(used == 7 && produced == 5 && std::memcmp(mbcs, "Go \xB1!", 5) == 0); Require(sceCesUtf8StrGetMbcsLen(sjisContext, sjisSource, 16, &used, &produced) == 0 && used == 7 && produced == 5); Require(sceCesUtf8StrToMbcsStr(sjisContext, sjisSource, 16, &used, mbcs, 4, &produced) == dstBufferEnd); + Require(used == 3 && produced == 3 && std::memcmp(mbcs, "Go ", 4) == 0); + Require(sceCesUtf8StrToMbcsStr(sjisContext, sjisSource, 16, &used, mbcs, sizeof(mbcs), &produced) == 0); Require(sceCesUtf8StrToMbcsStr(sjisContext, reinterpret_cast("\xF0\x9F\x98\x80"), 4, &used, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(used == 0 && produced == 0 && mbcs[0] == 0); + Require(sceCesUtf8StrToMbcsStr(sjisContext, sjisSource, 16, &used, mbcs, sizeof(mbcs), &produced) == 0); std::uint8_t sjisBack[16] = {}; Require(sceCesMbcsStrToUtf8Str(sjisContext, mbcs, 5, &used, sjisBack, sizeof(sjisBack), &produced) == 0); @@ -279,8 +745,8 @@ int main() { Require(sceCesUcs2StrToMbcsStr(sjisContext, sjisUcs2, 4, &used, mbcs, sizeof(mbcs), &produced) == 0); Require(used == 3 && produced == 3 && std::memcmp(mbcs, "OK\xDF", 3) == 0); Require(sceCesUcs2StrGetMbcsLen(sjisContext, sjisUcs2, 4, &used, &produced) == 0 && produced == 3); - std::uint16_t sjisWide[8] = {}; - Require(sceCesMbcsStrToUcs2Str(sjisContext, mbcs, 3, &used, sjisWide, 8, &produced) == 0); + std::uint16_t sjisWide[16] = {}; + Require(sceCesMbcsStrToUcs2Str(sjisContext, mbcs, 3, &used, sjisWide, 16, &produced) == 0); Require(used == 3 && produced == 3 && sjisWide[0] == u'O' && sjisWide[1] == u'K' && sjisWide[2] == 0xFF9F); Require(sceCesMbcsStrGetUcs2Len(sjisContext, mbcs, 3, &used, &produced) == 0 && produced == 3); @@ -288,19 +754,20 @@ int main() { Require(used == 24 && produced == 16 && std::memcmp(mbcs, "\x82\xB1\x82\xF1\x82\xC9\x82\xBF\x82\xCD\x93\xFA\x96\x7B\x8C\xEA", 16) == 0); std::uint8_t sjisJapaneseBack[32] = {}; Require(sceCesMbcsStrToUtf8Str(sjisContext, mbcs, 16, &used, sjisJapaneseBack, sizeof(sjisJapaneseBack), &produced) == 0 && produced == 24 && std::memcmp(sjisJapaneseBack, japanese, 24) == 0); - Require(sceCesMbcsStrToUcs2Str(sjisContext, mbcs, 16, &used, sjisWide, 8, &produced) == 0 && used == 16 && produced == 8 && sjisWide[0] == 0x3053 && sjisWide[5] == 0x65E5 && sjisWide[7] == 0x8A9E); - Require(sceCesMbcsStrToUcs2Str(sjisContext, mbcs, 15, &used, sjisWide, 8, &produced) == srcBufferEnd); + Require(sceCesMbcsStrToUcs2Str(sjisContext, mbcs, 16, &used, sjisWide, 16, &produced) == 0 && used == 16 && produced == 8 && sjisWide[0] == 0x3053 && sjisWide[5] == 0x65E5 && sjisWide[7] == 0x8A9E); + Require(sceCesMbcsStrToUcs2Str(sjisContext, mbcs, 15, &used, sjisWide, 16, &produced) == srcBufferEnd); const std::uint16_t sjisExtensions[] = {0x2460, 0xFF5E, 0x2252, 0}; Require(sceCesUcs2StrToMbcsStr(sjisContext, sjisExtensions, 4, &used, mbcs, sizeof(mbcs), &produced) == 0 && produced == 6 && std::memcmp(mbcs, "\x87\x40\x81\x60\x81\xE0", 6) == 0); - Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\x85\x40"), 2, &used, sjisWide, 8, &produced) == unassignedCode); - Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\x80"), 1, &used, sjisWide, 8, &produced) == invalidEncode); - Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xA0"), 1, &used, sjisWide, 8, &produced) == invalidEncode); - Require(sceCesUcs2StrToMbcsStr(sjisContext, yen, 2, &used, mbcs, sizeof(mbcs), &produced) == unassignedCode); - Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xFB\xFC"), 2, &used, sjisWide, 8, &produced) == 0 && used == 2 && produced == 1 && sjisWide[0] == 0x9AD9); - Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xFA\x40"), 2, &used, sjisWide, 8, &produced) == 0 && used == 2 && produced == 1 && sjisWide[0] == 0x2170); - Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xFC\x4B"), 2, &used, sjisWide, 8, &produced) == 0 && used == 2 && produced == 1 && sjisWide[0] == 0x9ED1); - Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xFC\x4C"), 2, &used, sjisWide, 8, &produced) == unassignedCode); - Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xF0\x40"), 2, &used, sjisWide, 8, &produced) == unassignedCode); + Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\x85\x40"), 2, &used, sjisWide, 16, &produced) == unassignedCode); + Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\x80"), 1, &used, sjisWide, 16, &produced) == invalidEncode); + Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xA0"), 1, &used, sjisWide, 16, &produced) == invalidEncode); + Require(sceCesUcs2StrToMbcsStr(sjisContext, yen, 2, &used, mbcs, sizeof(mbcs), &produced) == 0 && used == 1 && produced == 2 && std::memcmp(mbcs, "\x81\x8F", 3) == 0); + RequireMeasuredEncoders(eucContext, sjisContext); + Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xFB\xFC"), 2, &used, sjisWide, 16, &produced) == 0 && used == 2 && produced == 1 && sjisWide[0] == 0x9AD9); + Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xFA\x40"), 2, &used, sjisWide, 16, &produced) == 0 && used == 2 && produced == 1 && sjisWide[0] == 0x2170); + Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xFC\x4B"), 2, &used, sjisWide, 16, &produced) == 0 && used == 2 && produced == 1 && sjisWide[0] == 0x9ED1); + Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xFC\x4C"), 2, &used, sjisWide, 16, &produced) == unassignedCode); + Require(sceCesMbcsStrToUcs2Str(sjisContext, reinterpret_cast("\xF0\x40"), 2, &used, sjisWide, 16, &produced) == unassignedCode); std::uint32_t sjisAssigned = 0; for (std::uint32_t lead = 0x81; lead <= 0xFC; ++lead) { if (lead > 0x9F && lead < 0xE0) continue; diff --git a/core/libs/tests/GuestCesChinese.cpp b/core/libs/tests/GuestCesChinese.cpp new file mode 100644 index 0000000000..424a2a27bd --- /dev/null +++ b/core/libs/tests/GuestCesChinese.cpp @@ -0,0 +1,350 @@ +#include "prx/libSceCesCs/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +static const std::uint8_t* Bytes(const char* text) { return reinterpret_cast(text); } + +static constexpr int invalidParameter = static_cast(0x805C0001); +static constexpr int invalidSrcBuffer = static_cast(0x805C0010); +static constexpr int srcBufferEnd = static_cast(0x805C0011); +static constexpr int invalidEncode = static_cast(0x805C0014); + constexpr int outOfCodeSpace = static_cast(0x805C0015); +static constexpr int unassignedCode = static_cast(0x805C0020); +static constexpr int invalidDstBuffer = static_cast(0x805C0030); +static constexpr int dstBufferEnd = static_cast(0x805C0031); + +struct Gb18030Sequence { + std::uint32_t bytes; + std::uint32_t code2000; + std::uint32_t code2005; +}; + +struct Gb18030Difference { + std::uint32_t bytes; + std::uint32_t code2000; + std::uint32_t code2005; + bool fallback; +}; + +static constexpr Gb18030Sequence GB18030_BOUNDARIES[88] = { + {0x81308130, 0x80, 0x80}, {0x8130D239, 0x450, 0x450}, {0x8130D330, 0x452, 0x452}, {0x8135F436, 0x1E3E, 0x1E3E}, + {0x8135F437, 0x1E3F, 0xE7C7}, {0x8135F438, 0x1E40, 0x1E40}, {0x8136A531, 0x200F, 0x200F}, {0x8136A532, 0x2011, 0x2011}, + {0x8137A838, 0x2641, 0x2641}, {0x8137A839, 0x2643, 0x2643}, {0x8138FD38, 0x2E80, 0x2E80}, {0x8138FD39, 0x2E82, 0x2E82}, + {0x8230A632, 0x3619, 0x3619}, {0x8230A633, 0x361B, 0x361B}, {0x8230F237, 0x3917, 0x3917}, {0x8230F238, 0x3919, 0x3919}, + {0x8231D437, 0x3CDF, 0x3CDF}, {0x8231D438, 0x3CE1, 0x3CE1}, {0x8232AF32, 0x4055, 0x4055}, {0x8232AF33, 0x4057, 0x4057}, + {0x8232C936, 0x415E, 0x415E}, {0x8232C937, 0x4160, 0x4160}, {0x8232F837, 0x4336, 0x4336}, {0x8232F838, 0x4338, 0x4338}, + {0x8233A338, 0x44D5, 0x44D5}, {0x8233A339, 0x44D7, 0x44D7}, {0x8233C931, 0x464B, 0x464B}, {0x8233C932, 0x464D, 0x464D}, + {0x8233E837, 0x478C, 0x478C}, {0x8233E838, 0x478E, 0x478E}, {0x82349638, 0x4946, 0x4946}, {0x82349639, 0x4948, 0x4948}, + {0x8234A130, 0x49B5, 0x49B5}, {0x8234A131, 0x49B8, 0x49B8}, {0x8234E733, 0x4C76, 0x4C76}, {0x8234E734, 0x4C78, 0x4C78}, + {0x82358F32, 0x4DFF, 0x4DFF}, {0x82358F33, 0x9FA6, 0x9FA6}, {0x8336C738, 0xD7FF, 0xD7FF}, {0x8336C739, 0xE76C, 0xE76C}, + {0x8336CF39, 0xE863, 0xE863}, {0x8336D030, 0xE865, 0xE865}, {0x84308534, 0xF92B, 0xF92B}, {0x84308535, 0xF92D, 0xF92D}, + {0x84309C37, 0xFA26, 0xFA26}, {0x84309C38, 0xFA2A, 0xFA2A}, {0x84318537, 0xFE2F, 0xFE2F}, {0x84318538, 0xFE32, 0xFE32}, + {0x8431A233, 0xFFDF, 0xFFDF}, {0x8431A234, 0xFFE6, 0xFFE6}, {0x8431A439, 0xFFFF, 0xFFFF}, {0x8431A530, 0x0, 0x0}, + {0x8F39FE39, 0x0, 0x0}, {0x90308130, 0x10000, 0x10000}, {0x95328235, 0x1FFFF, 0x1FFFF}, {0x95328236, 0x20000, 0x20000}, + {0x9A348431, 0x2FFFF, 0x2FFFF}, {0x9A348432, 0x30000, 0x30000}, {0x9F368537, 0x3FFFF, 0x3FFFF}, {0x9F368538, 0x40000, 0x40000}, + {0xA4388733, 0x4FFFF, 0x4FFFF}, {0xA4388734, 0x50000, 0x50000}, {0xAA308839, 0x5FFFF, 0x5FFFF}, {0xAA308930, 0x60000, 0x60000}, + {0xAF328A35, 0x6FFFF, 0x6FFFF}, {0xAF328A36, 0x70000, 0x70000}, {0xB4348C31, 0x7FFFF, 0x7FFFF}, {0xB4348C32, 0x80000, 0x80000}, + {0xB9368D37, 0x8FFFF, 0x8FFFF}, {0xB9368D38, 0x90000, 0x90000}, {0xBE388F33, 0x9FFFF, 0x9FFFF}, {0xBE388F34, 0xA0000, 0xA0000}, + {0xC4309039, 0xAFFFF, 0xAFFFF}, {0xC4309130, 0xB0000, 0xB0000}, {0xC9329235, 0xBFFFF, 0xBFFFF}, {0xC9329236, 0xC0000, 0xC0000}, + {0xCE349431, 0xCFFFF, 0xCFFFF}, {0xCE349432, 0xD0000, 0xD0000}, {0xD3369537, 0xDFFFF, 0xDFFFF}, {0xD3369538, 0xE0000, 0xE0000}, + {0xD8389733, 0xEFFFF, 0xEFFFF}, {0xD8389734, 0xF0000, 0xF0000}, {0xDE309839, 0xFFFFF, 0xFFFFF}, {0xDE309930, 0x100000, 0x100000}, + {0xE3329A35, 0x10FFFF, 0x10FFFF}, {0xE3329A36, 0x0, 0x0}, {0xFD308130, 0x0, 0x0}, {0xFE39FE39, 0x0, 0x0}, +}; +static constexpr Gb18030Difference GB18030_DIFFERENCES[26] = { + {0xA6D9, 0xE78D, 0xFE10, true}, {0xA6DA, 0xE78E, 0xFE12, true}, {0xA6DB, 0xE78F, 0xFE11, true}, {0xA6DC, 0xE790, 0xFE13, true}, + {0xA6DD, 0xE791, 0xFE14, true}, {0xA6DE, 0xE792, 0xFE15, true}, {0xA6DF, 0xE793, 0xFE16, true}, {0xA6EC, 0xE794, 0xFE17, true}, + {0xA6ED, 0xE795, 0xFE18, true}, {0xA6F3, 0xE796, 0xFE19, true}, {0xA8BC, 0xE7C7, 0x1E3F, false}, {0xFE51, 0xE816, 0x20087, true}, + {0xFE52, 0xE817, 0x20089, true}, {0xFE53, 0xE818, 0x200CC, true}, {0xFE59, 0xE81E, 0x9FB4, true}, {0xFE61, 0xE826, 0x9FB5, true}, + {0xFE66, 0xE82B, 0x9FB6, true}, {0xFE67, 0xE82C, 0x9FB7, true}, {0xFE6C, 0xE831, 0x215D7, true}, {0xFE6D, 0xE832, 0x9FB8, true}, + {0xFE76, 0xE83B, 0x2298F, true}, {0xFE7E, 0xE843, 0x9FB9, true}, {0xFE90, 0xE854, 0x9FBA, true}, {0xFE91, 0xE855, 0x241FE, true}, + {0xFEA0, 0xE864, 0x9FBB, true}, {0x8135F437, 0x1E3F, 0xE7C7, false}, +}; +static std::uint32_t Gb18030Bytes(std::uint32_t linear) { + return (0x81 + linear / 12600) << 24 | (0x30 + linear / 1260 % 10) << 16 | (0x81 + linear / 10 % 126) << 8 | (0x30 + linear % 10); +} + +static void Fnv(std::uint32_t& hash, std::uint32_t value) { + for (int i = 0; i < 4; ++i) { + hash ^= (value >> (8 * i)) & 0xFF; + hash *= 0x01000193; + } +} + +static std::uint32_t Gb18030Decode(const void* gb, std::uint32_t packed) { + std::uint8_t bytes[4] = {}; + const std::uint32_t size = packed > 0xFFFF ? 4 : 2; + for (std::uint32_t i = 0; i < size; ++i) bytes[i] = static_cast(packed >> (8 * (size - 1 - i))); + std::uint32_t consumed = 0x55; + std::uint32_t code = 0xDEADBEEF; + const int result = sceCesGbToUtf32(gb, bytes, size, &consumed, &code); + if (result != 0) { + Require(result == unassignedCode && consumed == size && code == 0); + return 0; + } + Require(consumed == size); + return code; +} + +static std::uint32_t Gb18030Encode(const void* gb, std::uint32_t code) { + std::uint8_t bytes[4] = {}; + std::uint32_t size = 0; + Require(sceCesUtf32ToGb(code, gb, bytes, sizeof(bytes), &size) == 0 && (size == 2 || size == 4)); + std::uint32_t packed = 0; + for (std::uint32_t i = 0; i < size; ++i) packed = packed << 8 | bytes[i]; + return packed; +} + +static void RequireGb18030Error(const void* gb, const char* bytes, std::uint32_t size, int error, std::uint32_t length) { + std::uint32_t consumed = 0x55; + std::uint32_t utf32 = 0xDEADBEEF; + Require(sceCesGbToUtf32(gb, Bytes(bytes), size, &consumed, &utf32) == error && consumed == length && utf32 == 0); + std::uint16_t ucs2 = 0xBEEF; + consumed = 0x55; + Require(sceCesMbcToUcs2(gb, Bytes(bytes), size, &consumed, &ucs2) == error && consumed == length && ucs2 == 0); +} + +static void CheckGb18030(const void* gb, bool revision2005, std::uint32_t twoByteHash, std::uint32_t fourByteHash, std::uint32_t encodeHash, int fourByteOneWay, int codeOneWay) { + std::uint32_t hash = 0x811C9DC5; + for (std::uint32_t cell = 0; cell < 126 * 190; ++cell) { + const std::uint32_t column = cell % 190; + const std::uint32_t bytes = (0x81 + cell / 190) << 8 | (column < 0x3F ? 0x40 + column : 0x41 + column); + const std::uint32_t code = Gb18030Decode(gb, bytes); + Require(code != 0 && Gb18030Encode(gb, code) == bytes); + Fnv(hash, code); + } + Require(hash == twoByteHash); + + hash = 0x811C9DC5; + int oneWay = 0; + for (std::uint32_t linear = 0; linear < 39420; ++linear) { + const std::uint32_t bytes = Gb18030Bytes(linear); + const std::uint32_t code = Gb18030Decode(gb, bytes); + Require(code >= 0x80 && code <= 0xFFFF); + const std::uint32_t back = Gb18030Encode(gb, code); + if (back != bytes) { + Require(back <= 0xFFFF && Gb18030Decode(gb, back) == code); + ++oneWay; + } + Fnv(hash, code); + } + Require(hash == fourByteHash && oneWay == fourByteOneWay); + + hash = 0x811C9DC5; + oneWay = 0; + for (std::uint32_t code = 0x80; code < 0x10000; ++code) { + if (code >= 0xD800 && code <= 0xDFFF) continue; + const std::uint32_t bytes = Gb18030Encode(gb, code); + if (Gb18030Decode(gb, bytes) != code) ++oneWay; + Fnv(hash, bytes); + } + Require(hash == encodeHash && oneWay == codeOneWay); + + hash = 0x811C9DC5; + int samples = 0; + for (std::uint32_t linear = 189000; linear <= 189000 + 0xFFFFF; linear += 997) { + Fnv(hash, Gb18030Decode(gb, Gb18030Bytes(linear))); + ++samples; + } + Require(hash == 0x9E77B552 && samples == 1052); + hash = 0x811C9DC5; + for (std::uint32_t code = 0x10000; code < 0x110000; code += 997) Fnv(hash, Gb18030Encode(gb, code)); + Require(hash == 0x3D088D7E); + + for (const Gb18030Sequence& entry : GB18030_BOUNDARIES) { + const std::uint32_t code = revision2005 ? entry.code2005 : entry.code2000; + Require(Gb18030Decode(gb, entry.bytes) == code); + if (code != 0) Require(Gb18030Encode(gb, code) == entry.bytes); + } + for (const Gb18030Difference& entry : GB18030_DIFFERENCES) { + const std::uint32_t code = revision2005 ? entry.code2005 : entry.code2000; + Require(Gb18030Decode(gb, entry.bytes) == code && Gb18030Encode(gb, code) == entry.bytes); + if (revision2005 && entry.fallback) Require(Gb18030Encode(gb, entry.code2000) == entry.bytes); + } + + RequireGb18030Error(gb, "\x80", 1, invalidEncode, 1); + RequireGb18030Error(gb, "\xFF", 1, invalidEncode, 1); + RequireGb18030Error(gb, "\x81", 1, srcBufferEnd, 2); + RequireGb18030Error(gb, "\x81\x30", 2, srcBufferEnd, 4); + RequireGb18030Error(gb, "\x81\x30\x81", 3, srcBufferEnd, 4); + RequireGb18030Error(gb, "\x81\x00", 2, invalidEncode, 1); + RequireGb18030Error(gb, "\x81\x2F", 2, invalidEncode, 2); + RequireGb18030Error(gb, "\x81\x3A", 2, invalidEncode, 2); + RequireGb18030Error(gb, "\x81\x7F", 2, invalidEncode, 2); + RequireGb18030Error(gb, "\x81\xFF", 2, invalidEncode, 2); + RequireGb18030Error(gb, "\x81\x30\x80\x30", 4, invalidEncode, 4); + RequireGb18030Error(gb, "\x81\x30\xFF\x30", 4, invalidEncode, 4); + RequireGb18030Error(gb, "\x81\x30\x30\x30", 4, invalidEncode, 4); + RequireGb18030Error(gb, "\x81\x30\x81\x2F", 4, invalidEncode, 4); + RequireGb18030Error(gb, "\x81\x30\x81\x3A", 4, invalidEncode, 4); + RequireGb18030Error(gb, "\x81\x30\x81\x81", 4, invalidEncode, 4); + RequireGb18030Error(gb, "\x84\x31\xA5\x30", 4, unassignedCode, 4); + RequireGb18030Error(gb, "\xE3\x32\x9A\x36", 4, unassignedCode, 4); + RequireGb18030Error(gb, "\xFE\x39\xFE\x39", 4, unassignedCode, 4); + + std::uint32_t consumed = 0x55; + std::uint32_t produced = 0x55; + std::uint16_t ucs2 = 0xBEEF; + Require(sceCesGbToUcs2(gb, Bytes("\x90\x30\x81\x30"), 4, &consumed, &ucs2) == unassignedCode && consumed == 0x55 && ucs2 == 0xBEEF); + std::uint8_t utf8[4] = {1, 2, 3, 4}; + Require(sceCesGbToUtf8(gb, Bytes("\x90\x30\x81\x30"), 4, &consumed, utf8, 3, &produced) == dstBufferEnd && consumed == 0x55 && produced == 0x55); + Require(std::memcmp(utf8, "\x01\x02\x03\x04", 4) == 0); + Require(sceCesGbToUtf8(gb, Bytes("\x90\x30\x81\x30"), 4, &consumed, utf8, 4, &produced) == 0 && consumed == 4 && produced == 4); + Require(std::memcmp(utf8, "\xF0\x90\x80\x80", 4) == 0); + std::uint16_t utf16[2] = {0xBEEF, 0xBEEF}; + produced = 0x55; + Require(sceCesGbToUtf16(gb, Bytes("\xE3\x32\x9A\x35"), 4, &consumed, utf16, 1, &produced) == dstBufferEnd && produced == 0x55 && utf16[0] == 0xBEEF); + Require(sceCesGbToUtf16(gb, Bytes("\xE3\x32\x9A\x35"), 4, &consumed, utf16, 2, &produced) == 0 && produced == 2 && utf16[0] == 0xDBFF && utf16[1] == 0xDFFF); + Require(sceCesGbToUtf16be(gb, Bytes("\x90\x30\x81\x30"), 4, &consumed, utf16, 2, &produced) == 0 && std::memcmp(utf16, "\xD8\x00\xDC\x00", 4) == 0); + Require(sceCesMbcToUtf16le(gb, Bytes("\x94\x39\xFC\x36"), 4, &consumed, utf16, 2, &produced) == 0 && consumed == 4 && std::memcmp(utf16, "\x3D\xD8\x00\xDE", 4) == 0); + Require(sceCesGbToUcs2(gb, Bytes("\xA2\xE3"), 2, &consumed, &ucs2) == 0 && consumed == 2 && ucs2 == 0x20AC); + + std::uint8_t mbcs[4] = {1, 2, 3, 4}; + produced = 0x55; + Require(sceCesUtf32ToGb(0x10000, gb, mbcs, 3, &produced) == dstBufferEnd && produced == 4 && std::memcmp(mbcs, "\x01\x02\x03\x04", 4) == 0); + Require(sceCesUtf32ToGb(0xD800, gb, mbcs, sizeof(mbcs), &produced) == outOfCodeSpace); + Require(sceCesUtf32ToGb(0x110000, gb, mbcs, sizeof(mbcs), &produced) == invalidEncode); + Require(sceCesUcs2ToGb(0x80, gb, mbcs, sizeof(mbcs), &produced) == 0 && produced == 4 && std::memcmp(mbcs, "\x81\x30\x81\x30", 4) == 0); + Require(sceCesUcs2ToGb(0x20AC, gb, mbcs, sizeof(mbcs), &produced) == 0 && produced == 2 && std::memcmp(mbcs, "\xA2\xE3", 2) == 0); + Require(sceCesUtf8ToGb(Bytes("\xF0\x9F\x98\x80"), 4, &consumed, gb, mbcs, sizeof(mbcs), &produced) == 0 && consumed == 4 && produced == 4); + Require(std::memcmp(mbcs, "\x94\x39\xFC\x36", 4) == 0); + const std::uint16_t pair[2] = {0xD83D, 0xDE00}; + Require(sceCesUtf16ToGb(pair, 2, &consumed, gb, mbcs, sizeof(mbcs), &produced) == 0 && consumed == 2 && produced == 4 && std::memcmp(mbcs, "\x94\x39\xFC\x36", 4) == 0); + Require(sceCesUtf16ToGb(pair, 1, &consumed, gb, mbcs, sizeof(mbcs), &produced) == srcBufferEnd); + + alignas(8) std::uint8_t context[64] = {}; + Require(sceCesMbcsUcsContextInit(context, gb) == 0); + const char* utf8Text = "A\xE4\xB8\xAD\xC2\x80\xF0\x9F\x98\x80\xEF\xB8\x90"; + const char* gbText = revision2005 ? "A\xD6\xD0\x81\x30\x81\x30\x94\x39\xFC\x36\xA6\xD9" : "A\xD6\xD0\x81\x30\x81\x30\x94\x39\xFC\x36\x84\x31\x82\x36"; + const std::uint32_t gbSize = revision2005 ? 13 : 15; + std::uint8_t text[16] = {}; + Require(sceCesUtf8StrToMbcsStr(context, Bytes(utf8Text), 13, &consumed, text, sizeof(text), &produced) == 0 && consumed == 13 && produced == gbSize); + Require(std::memcmp(text, gbText, gbSize) == 0); + std::uint8_t backUtf8[16] = {}; + Require(sceCesMbcsStrToUtf8Str(context, text, gbSize, &consumed, backUtf8, sizeof(backUtf8), &produced) == 0 && consumed == gbSize && produced == 13); + Require(std::memcmp(backUtf8, utf8Text, 13) == 0); + std::uint16_t backUtf16[8] = {}; + Require(sceCesMbcsStrToUtf16Str(context, text, gbSize, &consumed, backUtf16, 8, &produced) == 0 && consumed == gbSize && produced == 6); + Require(backUtf16[0] == 'A' && backUtf16[1] == 0x4E2D && backUtf16[2] == 0x80 && backUtf16[3] == 0xD83D && backUtf16[4] == 0xDE00 && backUtf16[5] == 0xFE10); + std::uint16_t shortUtf16[4] = {}; + Require(sceCesMbcsStrToUtf16Str(context, text, gbSize, &consumed, shortUtf16, 4, &produced) == dstBufferEnd); + Require(sceCesMbcsStrGetUtf16Len(context, text, gbSize, &consumed, &produced) == 0 && consumed == gbSize && produced == 6); + std::uint8_t again[16] = {}; + Require(sceCesUtf16StrToMbcsStr(context, backUtf16, 6, &consumed, again, sizeof(again), &produced) == 0 && consumed == 6 && produced == gbSize); + Require(std::memcmp(again, gbText, gbSize) == 0); + std::uint16_t ucs2Text[8] = {}; + Require(sceCesMbcsStrToUcs2Str(context, text, gbSize, &consumed, ucs2Text, 8, &produced) == unassignedCode); + Require(sceCesUtf8StrToMbcsStr(context, Bytes("\xC2\x80"), 2, &consumed, text, 3, &produced) == dstBufferEnd); +} + +int main() { + alignas(8) std::uint8_t eucSheet[64] = {}; + alignas(8) std::uint8_t gbkSheet[64] = {}; + alignas(8) std::uint8_t sjisSheet[64] = {}; + const void* euc = sceCesUcsProfileInitEucCnGb2312(eucSheet); + const void* gbk = sceCesUcsProfileInitGbkCp936(gbkSheet); + const void* sjis = sceCesUcsProfileInitSJis1997Cp932(sjisSheet); + Require(euc == eucSheet && gbk == gbkSheet); + Require(sceCesUcsProfileInitEucCnGb2312(nullptr) == nullptr && sceCesUcsProfileInitGbkCp936(nullptr) == nullptr); + + std::uint32_t consumed = 0; + std::uint32_t produced = 0; + std::uint16_t ucs2 = 0; + Require(sceCesEucCnToUcs2(euc, Bytes("\xD6\xD0"), 2, &consumed, &ucs2) == 0 && consumed == 2 && ucs2 == 0x4E2D); + Require(sceCesEucCnToUcs2(euc, Bytes("A"), 1, &consumed, &ucs2) == 0 && consumed == 1 && ucs2 == 'A'); + Require(sceCesEucCnToUcs2(euc, Bytes("\xA1\xA4"), 2, &consumed, &ucs2) == 0 && ucs2 == 0x00B7); + Require(sceCesEucCnToUcs2(euc, Bytes("\x80"), 1, &consumed, &ucs2) == invalidEncode); + Require(sceCesEucCnToUcs2(euc, Bytes("\x81\x40"), 2, &consumed, &ucs2) == invalidEncode); + Require(sceCesEucCnToUcs2(euc, Bytes("\xD6\x40"), 2, &consumed, &ucs2) == invalidEncode); + Require(sceCesEucCnToUcs2(euc, Bytes("\xD6\xD0"), 1, &consumed, &ucs2) == srcBufferEnd); + Require(sceCesEucCnToUcs2(euc, Bytes("\xAA\xA1"), 2, &consumed, &ucs2) == unassignedCode); + Require(sceCesEucCnToUcs2(gbk, Bytes("\xD6\xD0"), 2, &consumed, &ucs2) == invalidParameter); + Require(sceCesEucCnToUcs2(euc, nullptr, 2, &consumed, &ucs2) == invalidSrcBuffer); + Require(sceCesEucCnToUcs2(euc, Bytes("\xD6\xD0"), 2, &consumed, nullptr) == invalidDstBuffer); + + std::uint8_t utf8[4] = {}; + Require(sceCesEucCnToUtf8(euc, Bytes("\xCE\xC4"), 2, &consumed, utf8, sizeof(utf8), &produced) == 0); + Require(produced == 3 && std::memcmp(utf8, "\xE6\x96\x87", 3) == 0); + std::uint16_t utf16 = 0; + Require(sceCesEucCnToUtf16be(euc, Bytes("\xD6\xD0"), 2, &consumed, &utf16, 1, &produced) == 0 && std::memcmp(&utf16, "\x4E\x2D", 2) == 0); + std::uint32_t utf32 = 0; + Require(sceCesEucCnToUtf32(euc, Bytes("\xD6\xD0"), 2, &consumed, &utf32) == 0 && utf32 == 0x4E2D); + + Require(sceCesGbToUcs2(gbk, Bytes("\x80"), 1, &consumed, &ucs2) == 0 && consumed == 1 && ucs2 == 0x20AC); + Require(sceCesGbToUcs2(gbk, Bytes("\x81\x40"), 2, &consumed, &ucs2) == 0 && consumed == 2 && ucs2 == 0x4E02); + Require(sceCesGbToUcs2(gbk, Bytes("\xD6\xD0"), 2, &consumed, &ucs2) == 0 && ucs2 == 0x4E2D); + Require(sceCesGbToUcs2(gbk, Bytes("\xFF"), 1, &consumed, &ucs2) == invalidEncode); + Require(sceCesGbToUcs2(gbk, Bytes("\x81\x7F"), 2, &consumed, &ucs2) == invalidEncode); + Require(sceCesGbToUcs2(gbk, Bytes("\x81\x3F"), 2, &consumed, &ucs2) == invalidEncode); + Require(sceCesGbToUcs2(euc, Bytes("\x81\x40"), 2, &consumed, &ucs2) == invalidParameter); + Require(sceCesGbToUcs2(sjis, Bytes("A"), 1, &consumed, &ucs2) == invalidParameter); + Require(sceCesGbToUtf8(gbk, Bytes("\x80"), 1, &consumed, utf8, sizeof(utf8), &produced) == 0); + Require(produced == 3 && std::memcmp(utf8, "\xE2\x82\xAC", 3) == 0); + Require(sceCesGbToUtf8(gbk, Bytes("\x80"), 1, &consumed, utf8, 2, &produced) == dstBufferEnd); + Require(sceCesGbToUtf16(gbk, Bytes("\x81\x40"), 2, &consumed, &utf16, 1, &produced) == 0 && utf16 == 0x4E02 && produced == 1); + Require(sceCesGbToUtf32le(gbk, Bytes("\x81\x40"), 2, &consumed, &utf32) == 0 && std::memcmp(&utf32, "\x02\x4E\x00\x00", 4) == 0); + + std::uint8_t mbcs[4] = {}; + Require(sceCesUcs2ToEucCn(0x4E2D, euc, mbcs, sizeof(mbcs), &produced) == 0 && produced == 2 && std::memcmp(mbcs, "\xD6\xD0", 2) == 0); + Require(sceCesUcs2ToEucCn(0x4E02, euc, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(sceCesUcs2ToEucCn(0x20AC, euc, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(sceCesUcs2ToEucCn(0x4E2D, euc, mbcs, 1, &produced) == dstBufferEnd); + Require(sceCesUcs2ToEucCn(0xDC00, euc, mbcs, sizeof(mbcs), &produced) == outOfCodeSpace); + Require(sceCesUcs2ToGb(0x4E02, gbk, mbcs, sizeof(mbcs), &produced) == 0 && produced == 2 && std::memcmp(mbcs, "\x81\x40", 2) == 0); + Require(sceCesUcs2ToGb(0x20AC, gbk, mbcs, sizeof(mbcs), &produced) == 0 && produced == 1 && mbcs[0] == 0x80); + Require(sceCesUcs2ToGb(0x3042, euc, mbcs, sizeof(mbcs), &produced) == invalidParameter); + Require(sceCesUcs2ToGb(0x3042, gbk, nullptr, sizeof(mbcs), &produced) == invalidDstBuffer); + Require(sceCesUtf8ToEucCn(Bytes("\xE6\x96\x87"), 3, &consumed, euc, mbcs, sizeof(mbcs), &produced) == 0); + Require(consumed == 3 && std::memcmp(mbcs, "\xCE\xC4", 2) == 0); + Require(sceCesUtf8ToGb(Bytes("\xF0\x9F\x98\x80"), 4, &consumed, gbk, mbcs, sizeof(mbcs), &produced) == unassignedCode); + std::uint16_t zhongLe = 0; + std::memcpy(&zhongLe, "\x2D\x4E", 2); + Require(sceCesUtf16leToGb(&zhongLe, 1, &consumed, gbk, mbcs, sizeof(mbcs), &produced) == 0 && std::memcmp(mbcs, "\xD6\xD0", 2) == 0); + Require(sceCesUtf32ToEucCn(0x110000, euc, mbcs, sizeof(mbcs), &produced) == invalidEncode); + std::uint32_t zhongBe = 0; + std::memcpy(&zhongBe, "\x00\x00\x4E\x2D", 4); + Require(sceCesUtf32beToGb(&zhongBe, gbk, mbcs, sizeof(mbcs), &produced) == 0 && std::memcmp(mbcs, "\xD6\xD0", 2) == 0); + + int gbkCount = 0; + int eucCount = 0; + for (int code = 1; code < 0x10000; ++code) { + if (code >= 0xD800 && code <= 0xDFFF) continue; + std::uint8_t encoded[4] = {}; + std::uint32_t size = 0; + std::uint16_t back = 0; + if (sceCesUcs2ToGb(static_cast(code), gbk, encoded, sizeof(encoded), &size) == 0) { + Require(sceCesGbToUcs2(gbk, encoded, size, &consumed, &back) == 0 && consumed == size && back == code); + ++gbkCount; + } + if (sceCesUcs2ToEucCn(static_cast(code), euc, encoded, sizeof(encoded), &size) == 0) { + Require(size == 1 || (encoded[0] >= 0xA1 && encoded[1] >= 0xA1)); + Require(sceCesEucCnToUcs2(euc, encoded, size, &consumed, &back) == 0 && consumed == size && back == code); + ++eucCount; + } + } + Require(gbkCount == 127 + 1 + 21791 && eucCount > 127 + 7000 && eucCount < gbkCount); + + alignas(8) std::uint8_t context[64] = {}; + Require(sceCesMbcsUcsContextInit(context, gbk) == 0); + std::uint8_t text[16] = {}; + Require(sceCesUtf8StrToMbcsStr(context, Bytes("A\xE4\xB8\xAD\xE2\x82\xAC"), 16, &consumed, text, sizeof(text), &produced) == 0); + Require(consumed == 7 && produced == 4 && std::memcmp(text, "A\xD6\xD0\x80", 4) == 0); + std::uint8_t back[16] = {}; + Require(sceCesMbcsStrToUtf8Str(context, text, produced, &consumed, back, sizeof(back), &produced) == 0); + Require(consumed == 4 && produced == 7 && std::memcmp(back, "A\xE4\xB8\xAD\xE2\x82\xAC", 7) == 0); + + alignas(8) std::uint8_t gb2000Sheet[64] = {}; + alignas(8) std::uint8_t gb2005Sheet[64] = {}; + const void* gb2000 = sceCesUcsProfileInitGb18030_2000(gb2000Sheet); + const void* gb2005 = sceCesUcsProfileInitGb18030_2005(gb2005Sheet); + Require(gb2000 == gb2000Sheet && gb2005 == gb2005Sheet); + Require(sceCesUcsProfileInitGb18030_2000(nullptr) == nullptr && sceCesUcsProfileInitGb18030_2005(nullptr) == nullptr); + Require(sceCesEucCnToUcs2(gb2000, Bytes("\xD6\xD0"), 2, &consumed, &ucs2) == invalidParameter); + Require(sceCesGbToUcs2(gb2005, Bytes("\xD6\xD0"), 2, &consumed, &ucs2) == 0 && consumed == 2 && ucs2 == 0x4E2D); + CheckGb18030(gb2000, false, 0xE61D3BD3, 0x83476043, 0x469CFCE5, 0, 0); + CheckGb18030(gb2005, true, 0x83EDDA69, 0x8D188DFC, 0x31CD0B55, 18, 24); + return 0; +} diff --git a/core/libs/tests/GuestCesDbcs.cpp b/core/libs/tests/GuestCesDbcs.cpp index 552f5193e9..bd3edaa068 100644 --- a/core/libs/tests/GuestCesDbcs.cpp +++ b/core/libs/tests/GuestCesDbcs.cpp @@ -1,29 +1,46 @@ +#include "prx/libSceCesCs/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -void* APS5_VABI sceCesUcsProfileInitEucKr(void*); -void* APS5_VABI sceCesUcsProfileInitUhc(void*); -void* APS5_VABI sceCesUcsProfileInitBig5Cp950(void*); -void* APS5_VABI sceCesUcsProfileInitEucJpCp51932(void*); -int APS5_VABI sceCesEucKrToUtf8(const void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesUhcToUtf8(const void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesBig5ToUtf8(const void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesMbcsUcsContextInit(void*, const void*); -int APS5_VABI sceCesUtf8StrToMbcsStr(void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesMbcsStrToUtf8Str(void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesUcs2StrToMbcsStr(void*, const std::uint16_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceCesMbcsStrToUcs2Str(void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint16_t*, std::uint32_t, std::uint32_t*); -} - using ToUtf8 = int (APS5_VABI*)(const void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint8_t*, std::uint32_t, std::uint32_t*); +using ToUcs2 = int (APS5_VABI*)(const void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint16_t*); +using ToUtf16 = int (APS5_VABI*)(const void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint16_t*, std::uint32_t, std::uint32_t*); +using ToUtf32 = int (APS5_VABI*)(const void*, const std::uint8_t*, std::uint32_t, std::uint32_t*, std::uint32_t*); +using FromUcs2 = int (APS5_VABI*)(std::uint16_t, const void*, std::uint8_t*, std::uint32_t, std::uint32_t*); +using FromUtf8 = int (APS5_VABI*)(const std::uint8_t*, std::uint32_t, std::uint32_t*, const void*, std::uint8_t*, std::uint32_t, std::uint32_t*); +using FromUtf16 = int (APS5_VABI*)(const std::uint16_t*, std::uint32_t, std::uint32_t*, const void*, std::uint8_t*, std::uint32_t, std::uint32_t*); +using FromUtf32 = int (APS5_VABI*)(std::uint32_t, const void*, std::uint8_t*, std::uint32_t, std::uint32_t*); +using FromUtf32Unit = int (APS5_VABI*)(const std::uint32_t*, const void*, std::uint8_t*, std::uint32_t, std::uint32_t*); + +struct Decoders { + ToUtf8 utf8; + ToUcs2 ucs2; + ToUtf16 utf16[3]; + ToUtf32 utf32[3]; +}; + +struct Encoders { + FromUcs2 ucs2; + FromUtf8 utf8; + FromUtf16 utf16[3]; + FromUtf32 utf32; + FromUtf32Unit utf32Unit[3]; +}; + +constexpr Decoders eucKrDecoders = {sceCesEucKrToUtf8, sceCesEucKrToUcs2, {sceCesEucKrToUtf16, sceCesEucKrToUtf16be, sceCesEucKrToUtf16le}, {sceCesEucKrToUtf32, sceCesEucKrToUtf32be, sceCesEucKrToUtf32le}}; +constexpr Decoders uhcDecoders = {sceCesUhcToUtf8, sceCesUhcToUcs2, {sceCesUhcToUtf16, sceCesUhcToUtf16be, sceCesUhcToUtf16le}, {sceCesUhcToUtf32, sceCesUhcToUtf32be, sceCesUhcToUtf32le}}; +constexpr Decoders big5Decoders = {sceCesBig5ToUtf8, sceCesBig5ToUcs2, {sceCesBig5ToUtf16, sceCesBig5ToUtf16be, sceCesBig5ToUtf16le}, {sceCesBig5ToUtf32, sceCesBig5ToUtf32be, sceCesBig5ToUtf32le}}; +constexpr Encoders eucKrEncoders = {sceCesUcs2ToEucKr, sceCesUtf8ToEucKr, {sceCesUtf16ToEucKr, sceCesUtf16beToEucKr, sceCesUtf16leToEucKr}, sceCesUtf32ToEucKr, {nullptr, sceCesUtf32beToEucKr, sceCesUtf32leToEucKr}}; +constexpr Encoders uhcEncoders = {sceCesUcs2ToUhc, sceCesUtf8ToUhc, {sceCesUtf16ToUhc, sceCesUtf16beToUhc, sceCesUtf16leToUhc}, sceCesUtf32ToUhc, {nullptr, sceCesUtf32beToUhc, sceCesUtf32leToUhc}}; +constexpr Encoders big5Encoders = {sceCesUcs2ToBig5, sceCesUtf8ToBig5, {sceCesUtf16ToBig5, sceCesUtf16beToBig5, sceCesUtf16leToBig5}, sceCesUtf32ToBig5, {nullptr, sceCesUtf32beToBig5, sceCesUtf32leToBig5}}; constexpr int invalidParameter = static_cast(0x805C0001); constexpr int invalidSrcBuffer = static_cast(0x805C0010); constexpr int srcBufferEnd = static_cast(0x805C0011); constexpr int invalidEncode = static_cast(0x805C0014); +constexpr int illegalCode = static_cast(0x805C0015); constexpr int unassignedCode = static_cast(0x805C0020); constexpr int invalidDstBuffer = static_cast(0x805C0030); constexpr int dstBufferEnd = static_cast(0x805C0031); @@ -69,7 +86,8 @@ static int Scalar(ToUtf8 convert, const void* profile, const char* source, std:: std::uint32_t consumed = 77; std::uint32_t produced = 77; const int result = convert(profile, Bytes(source), sourceMax, &consumed, utf8, utf8Max, &produced); - if (result != 0) Require(consumed == 77 && produced == 77 && std::memcmp(utf8, "\x55\x55\x55\x55", 4) == 0); + const bool decodeError = source && sourceMax > 0 && (result == static_cast(0x805C0011) || result == static_cast(0x805C0014) || result == static_cast(0x805C0020)); + if (result != 0) Require((decodeError ? consumed != 77 && produced == 0 : consumed == 77 && produced == 77) && std::memcmp(utf8, "\x55\x55\x55\x55", 4) == 0); return result; } @@ -96,20 +114,151 @@ static std::uint32_t RoundTripMismatches(void* context) { for (std::uint32_t lead = 0x81; lead < 0xFF; ++lead) { for (std::uint32_t trail = 0x40; trail < 0xFF; ++trail) { const std::uint8_t source[2] = {static_cast(lead), static_cast(trail)}; - std::uint16_t ucs2 = 0; + std::uint16_t ucs2[2] = {}; std::uint32_t read = 0; std::uint32_t written = 0; - if (sceCesMbcsStrToUcs2Str(context, source, sizeof(source), &read, &ucs2, 1, &written) != 0) continue; - Require(read == 2 && written == 1); - std::uint8_t back[2] = {}; - Require(sceCesUcs2StrToMbcsStr(context, &ucs2, 1, &read, back, sizeof(back), &written) == 0); - Require(read == 1 && written == 2); - if (std::memcmp(source, back, sizeof(back)) != 0) ++mismatches; + if (sceCesMbcsStrToUcs2Str(context, source, sizeof(source), &read, ucs2, 2, &written) != 0) continue; + Require(read == 2 && written == 1 && ucs2[1] == 0); + std::uint8_t back[3] = {}; + Require(sceCesUcs2StrToMbcsStr(context, ucs2, 1, &read, back, sizeof(back), &written) == 0); + Require(read == 1 && written == 2 && back[2] == 0); + if (std::memcmp(source, back, sizeof(source)) != 0) ++mismatches; } } return mismatches; } +static std::uint32_t Load(const void* data, std::uint32_t size, int order) { + if (order == 0) { + std::uint16_t unit = 0; + std::uint32_t wide = 0; + if (size == 2) std::memcpy(&unit, data, 2); + else std::memcpy(&wide, data, 4); + return size == 2 ? unit : wide; + } + const auto* bytes = static_cast(data); + std::uint32_t value = 0; + for (std::uint32_t i = 0; i < size; ++i) value |= static_cast(bytes[i]) << (8 * (order == 1 ? size - 1 - i : i)); + return value; +} + +static void Store(void* data, std::uint32_t size, int order, std::uint32_t value) { + if (order == 0) { + const auto unit = static_cast(value); + if (size == 2) std::memcpy(data, &unit, 2); + else std::memcpy(data, &value, 4); + return; + } + auto* bytes = static_cast(data); + for (std::uint32_t i = 0; i < size; ++i) bytes[i] = static_cast(value >> (8 * (order == 1 ? size - 1 - i : i))); +} + +static std::uint32_t Utf8Code(const std::uint8_t* utf8, std::uint32_t length) { + if (length == 1) return utf8[0]; + if (length == 2) return (utf8[0] & 0x1Fu) << 6 | (utf8[1] & 0x3Fu); + return (utf8[0] & 0x0Fu) << 12 | (utf8[1] & 0x3Fu) << 6 | (utf8[2] & 0x3Fu); +} + +static std::uint32_t Utf8Bytes(std::uint32_t code, std::uint8_t* utf8) { + if (code < 0x80) { + utf8[0] = static_cast(code); + return 1; + } + const std::uint32_t length = code < 0x800 ? 2 : code < 0x10000 ? 3 : 4; + for (std::uint32_t i = length - 1; i > 0; --i) { + utf8[i] = static_cast(0x80 | (code & 0x3F)); + code >>= 6; + } + utf8[0] = static_cast((length == 2 ? 0xC0 : length == 3 ? 0xE0 : 0xF0) | code); + return length; +} + +static void RequireDecoders(const Decoders& decoders, const void* profile) { + for (std::uint32_t lead = 0; lead < 256; ++lead) { + for (std::uint32_t trail = 0; trail < 256; ++trail) { + const std::uint8_t source[2] = {static_cast(lead), static_cast(trail)}; + std::uint8_t utf8[4] = {}; + std::uint32_t consumed = 0; + std::uint32_t produced = 0; + const int expected = decoders.utf8(profile, source, sizeof(source), &consumed, utf8, sizeof(utf8), &produced); + const std::uint32_t code = expected == 0 ? Utf8Code(utf8, produced) : 0; + std::uint16_t ucs2 = 0x5555; + std::uint32_t read = 77; + Require(decoders.ucs2(profile, source, sizeof(source), &read, &ucs2) == expected); + Require(read == consumed && ucs2 == (expected == 0 ? code : 0)); + for (int order = 0; order < 3; ++order) { + std::uint16_t utf16 = 0x5555; + std::uint32_t units = 77; + read = 77; + Require(decoders.utf16[order](profile, source, sizeof(source), &read, &utf16, 1, &units) == expected); + Require(read == consumed && (expected == 0 ? units == 1 && Load(&utf16, 2, order) == code : units == 0 && utf16 == 0x5555)); + std::uint32_t utf32 = 0x55555555; + read = 77; + Require(decoders.utf32[order](profile, source, sizeof(source), &read, &utf32) == expected); + Require(read == consumed && (expected == 0 ? Load(&utf32, 4, order) == code : utf32 == 0)); + } + } + } +} + +struct Encoded { + std::uint8_t bytes[2] = {0x55, 0x55}; + std::uint32_t size = 77; + std::uint32_t read = 77; +}; + +static int Encode(const Encoders& encoders, int form, std::uint32_t code, const void* profile, Encoded& out) { + std::uint8_t utf8[4] = {}; + std::uint16_t utf16[2] = {}; + std::uint32_t utf32 = 0; + if (form == 0) return encoders.ucs2(static_cast(code), profile, out.bytes, sizeof(out.bytes), &out.size); + if (form == 1) return encoders.utf8(utf8, Utf8Bytes(code, utf8), &out.read, profile, out.bytes, sizeof(out.bytes), &out.size); + if (form <= 4) { + const int order = form - 2; + if (code < 0x10000) Store(utf16, 2, order, code); + else { + Store(utf16, 2, order, 0xD800 | (code - 0x10000) >> 10); + Store(utf16 + 1, 2, order, 0xDC00 | (code & 0x3FF)); + } + return encoders.utf16[order](utf16, code < 0x10000 ? 1 : 2, &out.read, profile, out.bytes, sizeof(out.bytes), &out.size); + } + if (form == 5) return encoders.utf32(code, profile, out.bytes, sizeof(out.bytes), &out.size); + Store(&utf32, 4, form - 5, code); + return encoders.utf32Unit[form - 5](&utf32, profile, out.bytes, sizeof(out.bytes), &out.size); +} + +static std::uint32_t SourceUnits(int form, std::uint32_t code) { + if (form == 1) return code < 0x80 ? 1 : code < 0x800 ? 2 : code < 0x10000 ? 3 : 4; + if (form >= 2 && form <= 4) return code < 0x10000 ? 1 : 2; + return 77; +} + +static void RequireEncode(const Encoders& encoders, const void* profile, std::uint32_t code, int expected, const std::uint8_t* bytes, std::uint32_t size) { + for (int form = 0; form < 8; ++form) { + if (form == 0 && code > 0xFFFF) continue; + Encoded out; + Require(Encode(encoders, form, code, profile, out) == expected); + if (expected == 0) Require(out.read == SourceUnits(form, code) && out.size == size && std::memcmp(out.bytes, bytes, size) == 0); + else Require(out.read == 77 && out.size == ((form == 0 || form >= 5) && expected != static_cast(0x805C0001) ? 0u : 77u) && out.bytes[0] == 0x55 && out.bytes[1] == 0x55); + } +} + +static void RequireEncoders(const Encoders& encoders, const void* profile, void* context) { + Require(sceCesMbcsUcsContextInit(context, profile) == 0); + for (std::uint32_t code = 1; code < 0x10000; ++code) { + if (code >= 0xD800 && code <= 0xDFFF) continue; + const auto ucs2 = static_cast(code); + std::uint8_t bytes[3] = {}; + std::uint32_t read = 0; + std::uint32_t written = 0; + const int expected = sceCesUcs2StrToMbcsStr(context, &ucs2, 1, &read, bytes, sizeof(bytes), &written); + RequireEncode(encoders, profile, code, expected, bytes, written); + } + RequireEncode(encoders, profile, 0, 0, Bytes("\0"), 1); + RequireEncode(encoders, profile, 0x20000, unassignedCode, nullptr, 0); + RequireEncode(encoders, profile, 0x10FFFF, unassignedCode, nullptr, 0); +} + int main() { alignas(8) std::uint8_t eucKrSheet[64] = {}; alignas(8) std::uint8_t uhcSheet[64] = {}; @@ -124,8 +273,10 @@ int main() { void* eucJp = sceCesUcsProfileInitEucJpCp51932(eucJpSheet); Require(eucKr == eucKrSheet && uhc == uhcSheet && big5 == big5Sheet && eucJp == eucJpSheet); - RequireTable(sceCesEucKrToUtf8, eucKr, 0x01FF720E36F4AD67ull, 8226); - RequireTable(sceCesUhcToUtf8, uhc, 0x3B861A7E911985ABull, 17048); + RequireTable(sceCesEucKrToUtf8, eucKr, 0x18F4D7FD5B554273ull, 8227); + RequireTable(sceCesUhcToUtf8, uhc, 0x33B12AA5FAB04C43ull, 17049); + RequireScalar(sceCesEucKrToUtf8, eucKr, "\xA2\xE8", "\xE3\x89\xBE"); + RequireScalar(sceCesUhcToUtf8, uhc, "\xA2\xE8", "\xE3\x89\xBE"); RequireTable(sceCesBig5ToUtf8, big5, 0xE4D0D6A8019F35D9ull, 13503); RequireScalar(sceCesEucKrToUtf8, eucKr, "\xB0\xA1", "\xEA\xB0\x80"); @@ -171,9 +322,94 @@ int main() { Require(sceCesMbcsUcsContextInit(context, big5) == 0); RequireString(context, "\xA4\xA4\xA4\xE5", "\xE4\xB8\xAD\xE6\x96\x87"); - RequireString(context, "\xF9\xF9\xA2\x7E\xA4\x51\xA4\xCA", "\xE2\x95\x90\xE2\x95\xAD\xE5\x8D\x81\xE5\x8D\x85"); - RequireString(context, "\xF9\xE9\xF9\xEB\xF9\xEA\xA2\xA1\xA2\xA3\xA2\xA2", "\xE2\x95\x9E\xE2\x95\xA1\xE2\x95\xAA\xE2\x95\xAE\xE2\x95\xAF\xE2\x95\xB0"); + RequireString(context, "\xA2\xA4\xA2\x7E\xA4\x51\xA4\xCA", "\xE2\x95\x90\xE2\x95\xAD\xE5\x8D\x81\xE5\x8D\x85"); + RequireString(context, "\xA2\xA5\xA2\xA7\xA2\xA6\xA2\xA1\xA2\xA3\xA2\xA2", "\xE2\x95\x9E\xE2\x95\xA1\xE2\x95\xAA\xE2\x95\xAE\xE2\x95\xAF\xE2\x95\xB0"); Require(Utf8ToMbcs(context, "\xEA\xB0\x80") == unassignedCode); Require(RoundTripMismatches(context) == 10); + + RequireDecoders(eucKrDecoders, eucKr); + RequireDecoders(uhcDecoders, uhc); + RequireDecoders(big5Decoders, big5); + RequireEncoders(eucKrEncoders, eucKr, context); + RequireEncoders(uhcEncoders, uhc, context); + RequireEncoders(big5Encoders, big5, context); + + const std::uint8_t* ga = Bytes("\xB0\xA1"); + std::uint16_t unit = 0; + std::uint32_t wide = 0; + std::uint32_t read = 0; + std::uint32_t units = 0; + Require(sceCesEucKrToUtf16be(eucKr, ga, 2, &read, &unit, 1, &units) == 0 && read == 2 && units == 1 && std::memcmp(&unit, "\xAC\x00", 2) == 0); + Require(sceCesEucKrToUtf16le(eucKr, ga, 2, &read, &unit, 1, &units) == 0 && std::memcmp(&unit, "\x00\xAC", 2) == 0); + Require(sceCesEucKrToUtf16(eucKr, ga, 2, nullptr, &unit, 1, nullptr) == 0 && unit == 0xAC00); + Require(sceCesEucKrToUtf32be(eucKr, ga, 2, &read, &wide) == 0 && read == 2 && std::memcmp(&wide, "\x00\x00\xAC\x00", 4) == 0); + Require(sceCesEucKrToUtf32le(eucKr, ga, 2, &read, &wide) == 0 && std::memcmp(&wide, "\x00\xAC\x00\x00", 4) == 0); + Require(sceCesEucKrToUtf32(eucKr, ga, 2, nullptr, &wide) == 0 && wide == 0xAC00); + Require(sceCesEucKrToUcs2(eucKr, Bytes("\0"), 1, &read, &unit) == 0 && read == 1 && unit == 0); + Require(sceCesUhcToUcs2(uhc, Bytes("\x8C\x63"), 2, nullptr, &unit) == 0 && unit == 0xB620); + Require(sceCesBig5ToUtf32le(big5, Bytes("\xA4\xA4"), 2, &read, &wide) == 0 && std::memcmp(&wide, "\x2D\x4E\x00\x00", 4) == 0); + + Require(sceCesEucKrToUtf16(uhc, nullptr, 0, nullptr, nullptr, 0, nullptr) == invalidParameter); + Require(sceCesEucKrToUtf16(eucKr, nullptr, 0, nullptr, nullptr, 0, nullptr) == invalidSrcBuffer); + Require(sceCesEucKrToUtf16(eucKr, ga, 0, nullptr, nullptr, 0, nullptr) == srcBufferEnd); + Require(sceCesEucKrToUtf16(eucKr, ga, 2, nullptr, nullptr, 0, nullptr) == invalidDstBuffer); + Require(sceCesEucKrToUtf16(eucKr, Bytes("\x8C\x63"), 2, nullptr, &unit, 0, nullptr) == invalidEncode); + unit = 0x5555; + read = 77; + units = 77; + Require(sceCesEucKrToUtf16(eucKr, ga, 2, &read, &unit, 0, &units) == dstBufferEnd && unit == 0x5555 && read == 77 && units == 77); + Require(sceCesUhcToUcs2(uhc, ga, 2, nullptr, nullptr) == invalidDstBuffer); + Require(sceCesBig5ToUtf32(big5, Bytes("\xA4\xA4"), 1, nullptr, &wide) == srcBufferEnd); + Require(sceCesBig5ToUtf32be(eucJp, Bytes("\xA4\xA4"), 2, nullptr, &wide) == invalidParameter); + Require(sceCesBig5ToUtf32le(big5, Bytes("\xA4\xA4"), 2, nullptr, nullptr) == invalidDstBuffer); + + RequireEncode(eucKrEncoders, eucKr, 0x20AC, 0, Bytes("\xA2\xE6"), 2); + RequireEncode(eucKrEncoders, eucKr, 0xB620, unassignedCode, nullptr, 0); + RequireEncode(uhcEncoders, uhc, 0xB620, 0, Bytes("\x8C\x63"), 2); + RequireEncode(big5Encoders, big5, 0x2550, 0, Bytes("\xA2\xA4"), 2); + RequireEncode(big5Encoders, big5, 0x255E, 0, Bytes("\xA2\xA5"), 2); + RequireEncode(big5Encoders, big5, 0x2561, 0, Bytes("\xA2\xA7"), 2); + RequireEncode(big5Encoders, big5, 0x256A, 0, Bytes("\xA2\xA6"), 2); + RequireEncode(big5Encoders, big5, 0x5341, 0, Bytes("\xA4\x51"), 2); + RequireEncode(big5Encoders, big5, 0xAC00, unassignedCode, nullptr, 0); + RequireEncode(eucKrEncoders, uhc, 0xAC00, invalidParameter, nullptr, 0); + RequireEncode(uhcEncoders, nullptr, 0xAC00, invalidParameter, nullptr, 0); + RequireEncode(big5Encoders, eucJp, 0x4E2D, invalidParameter, nullptr, 0); + + std::uint8_t out[2] = {0x55, 0x55}; + std::uint32_t size = 77; + read = 77; + const std::uint32_t big = 0x0000AC00; + Require(sceCesUcs2ToEucKr(0xAC00, uhc, nullptr, 0, nullptr) == invalidParameter); + Require(sceCesUcs2ToEucKr(0xD800, eucKr, nullptr, 0, nullptr) == invalidDstBuffer); + Require(sceCesUcs2ToEucKr(0xD800, eucKr, out, 2, &size) == illegalCode); + Require(sceCesUtf32ToUhc(0x110000, uhc, out, 2, &size) == invalidEncode); + Require(sceCesUtf32ToUhc(0xDFFF, uhc, out, 2, &size) == illegalCode); + Require(sceCesUtf32beToBig5(nullptr, eucKr, nullptr, 0, nullptr) == invalidParameter); + Require(sceCesUtf32beToBig5(nullptr, big5, nullptr, 0, nullptr) == invalidSrcBuffer); + Require(sceCesUtf32leToBig5(&big, big5, nullptr, 0, nullptr) == invalidDstBuffer); + Require(sceCesUtf8ToEucKr(nullptr, 0, nullptr, uhc, nullptr, 0, nullptr) == invalidParameter); + Require(sceCesUtf8ToEucKr(nullptr, 0, nullptr, eucKr, nullptr, 0, nullptr) == invalidSrcBuffer); + Require(sceCesUtf8ToEucKr(Bytes("\xEA\xB0\x80"), 0, nullptr, eucKr, nullptr, 0, nullptr) == srcBufferEnd); + Require(sceCesUtf8ToEucKr(Bytes("\xEA\xB0\x80"), 3, nullptr, eucKr, nullptr, 0, nullptr) == invalidDstBuffer); + Require(sceCesUtf8ToEucKr(Bytes("\xEA\xB0\x80"), 2, &read, eucKr, out, 2, &size) == srcBufferEnd); + Require(sceCesUtf8ToEucKr(Bytes("\xC0\x80"), 2, &read, eucKr, out, 2, &size) == illegalCode); + Require(sceCesUtf8ToEucKr(Bytes("\xEA\xB0\x80"), 3, &read, eucKr, out, 1, &size) == dstBufferEnd); + const std::uint16_t lowSurrogate[1] = {0xDC00}; + const std::uint16_t highSurrogate[2] = {0xD800, 0x0041}; + Require(sceCesUtf16ToUhc(lowSurrogate, 1, &read, uhc, out, 2, &size) == invalidEncode); + Require(sceCesUtf16ToUhc(highSurrogate, 1, &read, uhc, out, 2, &size) == srcBufferEnd); + Require(sceCesUtf16ToUhc(highSurrogate, 2, &read, uhc, out, 2, &size) == invalidEncode); + Require(out[0] == 0x55 && out[1] == 0x55 && size == 0 && read == 77); + + std::memcpy(&unit, "\xAC\x00", 2); + Require(sceCesUtf16beToEucKr(&unit, 1, &read, eucKr, out, 2, &size) == 0 && read == 1 && size == 2 && std::memcmp(out, "\xB0\xA1", 2) == 0); + std::memcpy(&unit, "\x00\xAC", 2); + Require(sceCesUtf16leToUhc(&unit, 1, nullptr, uhc, out, 2, nullptr) == 0 && std::memcmp(out, "\xB0\xA1", 2) == 0); + std::memcpy(&wide, "\x00\x00\x4E\x2D", 4); + Require(sceCesUtf32beToBig5(&wide, big5, out, 2, &size) == 0 && size == 2 && std::memcmp(out, "\xA4\xA4", 2) == 0); + std::memcpy(&wide, "\x2D\x4E\x00\x00", 4); + Require(sceCesUtf32leToBig5(&wide, big5, out, 2, nullptr) == 0 && std::memcmp(out, "\xA4\xA4", 2) == 0); + Require(sceCesUcs2ToEucKr(0, eucKr, out, 1, &size) == 0 && size == 1 && out[0] == 0); return 0; } diff --git a/core/libs/tests/GuestCesJapanese.cpp b/core/libs/tests/GuestCesJapanese.cpp new file mode 100644 index 0000000000..42b8e2c16e --- /dev/null +++ b/core/libs/tests/GuestCesJapanese.cpp @@ -0,0 +1,369 @@ +#include "prx/libSceCesCs/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +static const std::uint8_t* Bytes(const char* text) { return reinterpret_cast(text); } + +constexpr int invalidParameter = static_cast(0x805C0001); +constexpr int srcBufferEnd = static_cast(0x805C0011); +constexpr int invalidEncode = static_cast(0x805C0014); +constexpr int outOfCodeSpace = static_cast(0x805C0015); +constexpr int unassignedCode = static_cast(0x805C0020); +constexpr int dstBufferEnd = static_cast(0x805C0031); +constexpr std::uint32_t jisX0208Cells = 6879; +constexpr std::uint32_t eucJisX0208Digest = 0xC1C98702; +constexpr std::uint32_t sjisJisX0208Digest = 0x0CE46B27; +constexpr std::uint32_t jisX0212Cells = 6067; +constexpr std::uint32_t jisX0212Digest = 0x7C193A73; + +static std::uint32_t Digest(std::uint32_t hash, std::uint16_t value) { + hash = (hash ^ (value & 0xFF)) * 0x01000193u; + return (hash ^ (value >> 8)) * 0x01000193u; +} + +static int EucToUcs2(const void* profile, const char* bytes, std::uint32_t size, std::uint16_t& ucs2) { + std::uint32_t used = 0; + return sceCesEucJpToUcs2(profile, Bytes(bytes), size, &used, &ucs2); +} + +static int SJisToUcs2(const void* profile, const char* bytes, std::uint32_t size, std::uint16_t& ucs2) { + std::uint32_t used = 0; + return sceCesSJisToUcs2(profile, Bytes(bytes), size, &used, &ucs2); +} + +static bool EucEncodes(const void* profile, std::uint16_t ucs2, const char* expected) { + std::uint8_t mbcs[4] = {}; + std::uint32_t size = 0; + return sceCesUcs2ToEucJp(ucs2, profile, mbcs, sizeof(mbcs), &size) == 0 && size == std::strlen(expected) && std::memcmp(mbcs, expected, size) == 0; +} + +static bool SJisEncodes(const void* profile, std::uint16_t ucs2, const char* expected) { + std::uint8_t mbcs[4] = {}; + std::uint32_t size = 0; + return sceCesUcs2ToSJis(ucs2, profile, mbcs, sizeof(mbcs), &size) == 0 && size == std::strlen(expected) && std::memcmp(mbcs, expected, size) == 0; +} + +static bool EncodeLeavesOutputs(int expected, const void* profile, std::uint16_t ucs2, bool sjis) { + std::uint8_t mbcs[4] = {0xEE, 0xEE, 0xEE, 0xEE}; + std::uint32_t size = 77; + const int result = sjis ? sceCesUcs2ToSJis(ucs2, profile, mbcs, sizeof(mbcs), &size) : sceCesUcs2ToEucJp(ucs2, profile, mbcs, sizeof(mbcs), &size); + return result == expected && size == (expected == static_cast(0x805C0001) ? 77u : 0u) && mbcs[0] == 0xEE && mbcs[1] == 0xEE && mbcs[2] == 0xEE; +} + +static bool DecodeRejects(int expected, std::uint32_t length, const void* profile, const char* bytes, std::uint32_t size, bool sjis) { + std::uint32_t used = 77; + std::uint16_t ucs2 = 0xEEEE; + const int result = sjis ? sceCesSJisToUcs2(profile, Bytes(bytes), size, &used, &ucs2) : sceCesEucJpToUcs2(profile, Bytes(bytes), size, &used, &ucs2); + if (!sjis) return result == expected && ucs2 == 0; + return result == expected && used == length && ucs2 == 0; +} + +static void SweepEucJisX0208(const void* profile) { + std::uint32_t assigned = 0; + std::uint32_t hash = 0x811C9DC5u; + for (std::uint32_t lead = 0xA1; lead <= 0xFE; ++lead) { + for (std::uint32_t trail = 0xA1; trail <= 0xFE; ++trail) { + const std::uint8_t cell[2] = {static_cast(lead), static_cast(trail)}; + std::uint32_t used = 77; + std::uint16_t ucs2 = 0xEEEE; + std::uint16_t value = 0; + if (const int result = sceCesEucJpToUcs2(profile, cell, 2, &used, &ucs2); result == 0) { + Require(used == 2); + value = ucs2; + ++assigned; + std::uint8_t again[4] = {}; + std::uint32_t size = 0; + Require(sceCesUcs2ToEucJp(ucs2, profile, again, sizeof(again), &size) == 0 && size == 2 && again[0] == lead && again[1] == trail); + } else { + Require(result == unassignedCode && used == 2 && ucs2 == 0); + } + hash = Digest(hash, value); + } + } + Require(assigned == jisX0208Cells && hash == eucJisX0208Digest); +} + +static void SweepSJisJisX0208(const void* profile) { + std::uint32_t assigned = 0; + std::uint32_t hash = 0x811C9DC5u; + for (int row = 1; row <= 94; ++row) { + for (int column = 1; column <= 94; ++column) { + std::uint16_t code = 0; + Require(sceCesJisX0208ToSJisCode(static_cast(row), static_cast(column), &code) == 0); + const std::uint8_t cell[2] = {static_cast(code >> 8), static_cast(code)}; + std::uint32_t used = 77; + std::uint16_t ucs2 = 0xEEEE; + std::uint16_t value = 0; + if (const int result = sceCesSJisToUcs2(profile, cell, 2, &used, &ucs2); result == 0) { + Require(used == 2); + value = ucs2; + ++assigned; + std::uint8_t again[4] = {}; + std::uint32_t size = 0; + Require(sceCesUcs2ToSJis(ucs2, profile, again, sizeof(again), &size) == 0 && size == 2 && again[0] == cell[0] && again[1] == cell[1]); + } else { + Require(result == unassignedCode && used == 2 && ucs2 == 0); + } + hash = Digest(hash, value); + } + } + Require(assigned == jisX0208Cells && hash == sjisJisX0208Digest); +} + +static void TestJisX0208Profiles(const void* cp932, const void* cp51932) { + alignas(8) std::uint8_t sheets[4][256] = {}; + Require(sceCesUcsProfileInitEucJpX0208(nullptr) == nullptr && sceCesUcsProfileInitEucJpX0208Ss2(nullptr) == nullptr); + Require(sceCesUcsProfileInitEucJpX0208Ss2Ss3(nullptr) == nullptr && sceCesUcsProfileInitSJis1997X0208(nullptr) == nullptr); + const void* euc = sceCesUcsProfileInitEucJpX0208(sheets[0]); + const void* ss2 = sceCesUcsProfileInitEucJpX0208Ss2(sheets[1]); + const void* ss3 = sceCesUcsProfileInitEucJpX0208Ss2Ss3(sheets[2]); + const void* sjis = sceCesUcsProfileInitSJis1997X0208(sheets[3]); + Require(euc == sheets[0] && ss2 == sheets[1] && ss3 == sheets[2] && sjis == sheets[3]); + const void* strictEuc[3] = {euc, ss2, ss3}; + + std::uint16_t ucs2 = 0; + Require(EucToUcs2(sjis, "\xA4\xA2", 2, ucs2) == invalidParameter); + Require(SJisToUcs2(euc, "\x82\xA0", 2, ucs2) == invalidParameter); + Require(EncodeLeavesOutputs(invalidParameter, sjis, 0x3042, false) && EncodeLeavesOutputs(invalidParameter, ss3, 0x3042, true)); + + for (const void* profile : strictEuc) { + SweepEucJisX0208(profile); + Require(EucToUcs2(profile, "\\~", 2, ucs2) == 0 && ucs2 == 0x5C && EucEncodes(profile, 0x5C, "\\") && EucEncodes(profile, 0x7E, "~")); + Require(EucToUcs2(profile, "\xA1\xC1", 2, ucs2) == 0 && ucs2 == 0x301C && EucEncodes(profile, 0x301C, "\xA1\xC1")); + Require(EucToUcs2(profile, "\xA1\xC2", 2, ucs2) == 0 && ucs2 == 0x2016); + Require(EucToUcs2(profile, "\xA1\xDD", 2, ucs2) == 0 && ucs2 == 0x2212); + Require(EucToUcs2(profile, "\xA1\xF1", 2, ucs2) == 0 && ucs2 == 0x00A2); + Require(EucToUcs2(profile, "\xA1\xF2", 2, ucs2) == 0 && ucs2 == 0x00A3); + Require(EucToUcs2(profile, "\xA2\xCC", 2, ucs2) == 0 && ucs2 == 0x00AC); + for (const std::uint16_t cp932Form : {0xFF5E, 0x2225, 0xFF0D, 0xFFE0, 0xFFE1, 0xFFE2, 0x2460, 0x2170}) Require(EncodeLeavesOutputs(unassignedCode, profile, cp932Form, false)); + Require(EucToUcs2(profile, "\xA1\xC0", 2, ucs2) == 0 && ucs2 == 0xFF3C && EucEncodes(profile, 0xFF3C, "\xA1\xC0")); + Require(EucToUcs2(profile, "\xA1\xBD", 2, ucs2) == 0 && ucs2 == 0x2014 && EucEncodes(profile, 0x2014, "\xA1\xBD") && EncodeLeavesOutputs(unassignedCode, profile, 0x2015, false)); + Require(EucEncodes(profile, 0x00A5, "\xA1\xEF") && EucEncodes(profile, 0x203E, "\xA1\xB1")); + Require(EucToUcs2(profile, "\xA1\xEF", 2, ucs2) == 0 && ucs2 == 0xFFE5 && EucToUcs2(profile, "\xA1\xB1", 2, ucs2) == 0 && ucs2 == 0xFFE3); + Require(DecodeRejects(unassignedCode, 2, profile, "\xAD\xA1", 2, false) && DecodeRejects(unassignedCode, 2, profile, "\xF9\xA1", 2, false)); + Require(DecodeRejects(unassignedCode, 2, profile, "\xA9\xA1", 2, false) && DecodeRejects(unassignedCode, 2, profile, "\xFE\xFE", 2, false)); + Require(DecodeRejects(invalidEncode, 2, profile, "\xA4\x41", 2, false) && DecodeRejects(invalidEncode, 2, profile, "\xA4\xFF", 2, false)); + Require(DecodeRejects(invalidEncode, 1, profile, "\x80", 1, false) && DecodeRejects(invalidEncode, 1, profile, "\xFF", 1, false)); + Require(DecodeRejects(srcBufferEnd, 2, profile, "\xA4\xA2", 1, false)); + std::uint8_t mbcs[4] = {0xEE, 0xEE, 0xEE, 0xEE}; + std::uint32_t size = 77; + Require(sceCesUcs2ToEucJp(0x3042, profile, mbcs, 1, &size) == dstBufferEnd && size == 2 && mbcs[0] == 0xEE); + } + Require(EucToUcs2(cp51932, "\xA1\xC0", 2, ucs2) == 0 && ucs2 == 0xFF3C && EucEncodes(cp51932, 0xFF3C, "\xA1\xC0")); + Require(EucToUcs2(cp51932, "\xA1\xC1", 2, ucs2) == 0 && ucs2 == 0xFF5E && EucToUcs2(cp51932, "\xAD\xA1", 2, ucs2) == 0 && ucs2 == 0x2460); + Require(EucEncodes(cp51932, 0x7E8A, "\xF9\xA1") && EucEncodes(ss3, 0x7E8A, "\x8F\xD4\xE3")); + Require(EncodeLeavesOutputs(unassignedCode, euc, 0x7E8A, false) && EncodeLeavesOutputs(unassignedCode, ss2, 0x7E8A, false)); + + Require(DecodeRejects(invalidEncode, 1, euc, "\x8E\xB1", 2, false) && DecodeRejects(invalidEncode, 1, euc, "\x8E", 1, false)); + Require(DecodeRejects(invalidEncode, 1, euc, "\x8F\xB0\xA1", 3, false) && DecodeRejects(invalidEncode, 1, ss2, "\x8F\xB0\xA1", 3, false)); + Require(EncodeLeavesOutputs(unassignedCode, euc, 0xFF71, false)); + Require(EncodeLeavesOutputs(unassignedCode, euc, 0x4E02, false) && EncodeLeavesOutputs(unassignedCode, ss2, 0x4E02, false)); + for (const void* profile : {ss2, ss3}) { + for (std::uint32_t kana = 0xA1; kana <= 0xDF; ++kana) { + const char bytes[3] = {'\x8E', static_cast(kana), 0}; + Require(EucToUcs2(profile, bytes, 2, ucs2) == 0 && ucs2 == 0xFF61 + kana - 0xA1 && EucEncodes(profile, ucs2, bytes)); + } + Require(DecodeRejects(invalidEncode, 2, profile, "\x8E\xE0", 2, false) && DecodeRejects(invalidEncode, 2, profile, "\x8E\xA0", 2, false)); + Require(DecodeRejects(srcBufferEnd, 2, profile, "\x8E\xB1", 1, false)); + } + std::uint32_t supplementary = 0; + std::uint32_t hash = 0x811C9DC5u; + for (std::uint32_t lead = 0xA1; lead <= 0xFE; ++lead) { + for (std::uint32_t trail = 0xA1; trail <= 0xFE; ++trail) { + const char bytes[4] = {'\x8F', static_cast(lead), static_cast(trail), 0}; + std::uint16_t value = 0; + if (const int result = EucToUcs2(ss3, bytes, 3, ucs2); result == 0) { + value = ucs2; + ++supplementary; + Require(EucEncodes(ss3, ucs2, ucs2 == 0x7E ? "~" : bytes)); + } else { + Require(result == unassignedCode); + } + hash = Digest(hash, value); + } + } + Require(supplementary == jisX0212Cells && hash == jisX0212Digest); + Require(DecodeRejects(srcBufferEnd, 3, ss3, "\x8F\xB0", 2, false) && DecodeRejects(invalidEncode, 3, ss3, "\x8F\xB0\x41", 3, false)); + Require(EucEncodes(ss3, 0x4E02, "\x8F\xB0\xA1") && EucEncodes(ss2, 0xFF71, "\x8E\xB1")); + + std::uint8_t utf8[4] = {}; + std::uint32_t used = 0; + std::uint32_t produced = 0; + Require(sceCesEucJpToUtf8(euc, Bytes("\xA1\xC1"), 2, &used, utf8, sizeof(utf8), &produced) == 0 && used == 2 && produced == 3 && std::memcmp(utf8, "\xE3\x80\x9C", 3) == 0); + alignas(8) std::uint8_t context[256] = {}; + std::uint16_t wide[8] = {}; + Require(sceCesMbcsUcsContextInit(context, ss2) == 0); + Require(sceCesMbcsStrToUcs2Str(context, Bytes("A\xA4\xA2\x8E\xB1"), 5, &used, wide, 8, &produced) == 0 && used == 5 && produced == 3 && wide[1] == 0x3042 && wide[2] == 0xFF71); + Require(sceCesMbcsUcsContextInit(context, euc) == 0); + Require(sceCesMbcsStrToUcs2Str(context, Bytes("A\xA4\xA2\x8E\xB1"), 5, &used, wide, 8, &produced) == invalidEncode); + + SweepSJisJisX0208(sjis); + Require(SJisToUcs2(sjis, "\x81\x60", 2, ucs2) == 0 && ucs2 == 0x301C && SJisEncodes(sjis, 0x301C, "\x81\x60")); + Require(SJisToUcs2(sjis, "\x81\x91", 2, ucs2) == 0 && ucs2 == 0x00A2 && SJisEncodes(sjis, 0x00AC, "\x81\xCA")); + for (const std::uint16_t cp932Form : {0xFF5E, 0x2225, 0xFF0D, 0xFFE0, 0xFFE1, 0xFFE2, 0x2460, 0x2170, 0x7E8A, 0x4E02}) Require(EncodeLeavesOutputs(unassignedCode, sjis, cp932Form, true)); + for (const char* removed : {"\x87\x40", "\xED\x40", "\xEE\xFC", "\xFA\x40", "\xFC\x4B", "\xF0\x40", "\xEF\xFC"}) Require(DecodeRejects(unassignedCode, 2, sjis, removed, 2, true)); + Require(SJisToUcs2(cp932, "\x87\x40", 2, ucs2) == 0 && ucs2 == 0x2460 && SJisToUcs2(cp932, "\xFA\x40", 2, ucs2) == 0 && ucs2 == 0x2170); + Require(SJisToUcs2(cp932, "\x81\x5F", 2, ucs2) == 0 && ucs2 == 0xFF3C && SJisToUcs2(cp932, "\\", 1, ucs2) == 0 && ucs2 == 0x5C); + Require(DecodeRejects(invalidEncode, 2, sjis, "\x82\x7F", 2, true) && DecodeRejects(invalidEncode, 2, sjis, "\x82\xFD", 2, true)); + Require(DecodeRejects(srcBufferEnd, 2, sjis, "\x82\xA0", 1, true)); + for (std::uint32_t byte = 0; byte <= 0xFF; ++byte) { + const char bytes[2] = {static_cast(byte), 0}; + if (byte == 0x5C || byte == 0x7E) { + const std::uint16_t roman = byte == 0x5C ? 0x00A5 : 0x203E; + Require(SJisToUcs2(sjis, bytes, 1, ucs2) == 0 && ucs2 == roman && SJisEncodes(sjis, roman, bytes)); + } else if (byte < 0x80) { + Require(SJisToUcs2(sjis, bytes, 1, ucs2) == 0 && ucs2 == byte && (byte == 0 || SJisEncodes(sjis, ucs2, bytes))); + } else if (byte >= 0xA1 && byte <= 0xDF) { + Require(SJisToUcs2(sjis, bytes, 1, ucs2) == 0 && ucs2 == 0xFF61 + byte - 0xA1 && SJisEncodes(sjis, ucs2, bytes)); + } else { + Require(DecodeRejects(byte == 0x80 || byte == 0xA0 || byte >= 0xFD ? invalidEncode : srcBufferEnd, byte == 0x80 || byte == 0xA0 || byte >= 0xFD ? 1u : 2u, sjis, bytes, 1, true)); + } + } + Require(SJisToUcs2(sjis, "\x81\x5F", 2, ucs2) == 0 && ucs2 == 0x005C && SJisEncodes(sjis, 0x005C, "\x81\x5F")); + Require(SJisToUcs2(sjis, "\x81\x5C", 2, ucs2) == 0 && ucs2 == 0x2014 && SJisEncodes(sjis, 0x2014, "\x81\x5C")); + for (const std::uint16_t unmapped : {0x007E, 0xFF3C, 0x2015}) Require(EncodeLeavesOutputs(unassignedCode, sjis, unmapped, true)); + Require(sceCesMbcsUcsContextInit(context, sjis) == 0); + const std::uint16_t text[] = {0x3042, 0x301C, 0xFF71, 0}; + std::uint8_t mbcs[8] = {}; + Require(sceCesUcs2StrToMbcsStr(context, text, 4, &used, mbcs, sizeof(mbcs), &produced) == 0 && used == 3 && produced == 5 && std::memcmp(mbcs, "\x82\xA0\x81\x60\xB1", 5) == 0); + Require(sceCesMbcsStrToUcs2Str(context, Bytes("A\\"), 2, &used, wide, 8, &produced) == 0 && used == 2 && produced == 2 && wide[1] == 0x00A5); +} + +int main() { + constexpr int invalidSrcBuffer = static_cast(0x805C0010); + constexpr int invalidDstBuffer = static_cast(0x805C0030); + + alignas(8) std::uint8_t sjisSheet[64] = {}; + alignas(8) std::uint8_t eucSheet[64] = {}; + const void* sjis = sceCesUcsProfileInitSJis1997Cp932(sjisSheet); + const void* euc = sceCesUcsProfileInitEucJpCp51932(eucSheet); + Require(sjis != nullptr && euc != nullptr); + + std::uint32_t consumed = 0; + std::uint32_t produced = 0; + std::uint16_t ucs2 = 0; + Require(sceCesSJisToUcs2(sjis, Bytes("\x82\xA0"), 2, &consumed, &ucs2) == 0 && consumed == 2 && ucs2 == 0x3042); + Require(sceCesSJisToUcs2(sjis, Bytes("\xB1"), 1, &consumed, &ucs2) == 0 && consumed == 1 && ucs2 == 0xFF71); + Require(sceCesSJisToUcs2(sjis, Bytes("A"), 1, &consumed, &ucs2) == 0 && consumed == 1 && ucs2 == 'A'); + Require(sceCesSJisToUcs2(sjis, Bytes("\x81\x60"), 2, &consumed, &ucs2) == 0 && ucs2 == 0xFF5E); + Require(sceCesSJisToUcs2(sjis, Bytes("\xFA\x40"), 2, &consumed, &ucs2) == 0 && ucs2 == 0x2170); + Require(sceCesSJisToUcs2(euc, Bytes("\x82\xA0"), 2, &consumed, &ucs2) == invalidParameter); + Require(sceCesSJisToUcs2(sjis, nullptr, 2, &consumed, &ucs2) == invalidSrcBuffer); + Require(sceCesSJisToUcs2(sjis, Bytes("\x82\xA0"), 0, &consumed, &ucs2) == srcBufferEnd); + Require(sceCesSJisToUcs2(sjis, Bytes("\x82\xA0"), 1, &consumed, &ucs2) == srcBufferEnd); + Require(sceCesSJisToUcs2(sjis, Bytes("\x82\xA0"), 2, &consumed, nullptr) == invalidDstBuffer); + Require(sceCesSJisToUcs2(sjis, Bytes("\x82\x7F"), 2, &consumed, &ucs2) == invalidEncode); + Require(sceCesSJisToUcs2(sjis, Bytes("\xF0\x40"), 2, &consumed, &ucs2) == unassignedCode); + + std::uint8_t utf8[4] = {}; + Require(sceCesSJisToUtf8(sjis, Bytes("\x93\xFA"), 2, &consumed, utf8, sizeof(utf8), &produced) == 0); + Require(consumed == 2 && produced == 3 && std::memcmp(utf8, "\xE6\x97\xA5", 3) == 0); + Require(sceCesSJisToUtf8(sjis, Bytes("\x93\xFA"), 2, &consumed, utf8, 2, &produced) == dstBufferEnd); + + std::uint16_t utf16 = 0; + Require(sceCesSJisToUtf16(sjis, Bytes("\x82\xA0"), 2, &consumed, &utf16, 1, &produced) == 0 && utf16 == 0x3042 && produced == 1); + Require(sceCesSJisToUtf16(sjis, Bytes("\x82\xA0"), 2, &consumed, &utf16, 0, &produced) == dstBufferEnd); + Require(sceCesSJisToUtf16be(sjis, Bytes("\x82\xA0"), 2, &consumed, &utf16, 1, &produced) == 0); + Require(std::memcmp(&utf16, "\x30\x42", 2) == 0); + Require(sceCesSJisToUtf16le(sjis, Bytes("\x82\xA0"), 2, &consumed, &utf16, 1, &produced) == 0); + Require(std::memcmp(&utf16, "\x42\x30", 2) == 0); + + std::uint32_t utf32 = 0; + Require(sceCesSJisToUtf32(sjis, Bytes("\x82\xA0"), 2, &consumed, &utf32) == 0 && utf32 == 0x3042); + Require(sceCesSJisToUtf32be(sjis, Bytes("\x82\xA0"), 2, &consumed, &utf32) == 0); + Require(std::memcmp(&utf32, "\x00\x00\x30\x42", 4) == 0); + Require(sceCesSJisToUtf32le(sjis, Bytes("\x82\xA0"), 2, &consumed, &utf32) == 0); + Require(std::memcmp(&utf32, "\x42\x30\x00\x00", 4) == 0); + + Require(sceCesEucJpToUtf16(euc, Bytes("\xA4\xA2"), 2, &consumed, &utf16, 1, &produced) == 0 && consumed == 2 && utf16 == 0x3042); + Require(sceCesEucJpToUtf16be(euc, Bytes("\x8E\xB1"), 2, &consumed, &utf16, 1, &produced) == 0); + Require(std::memcmp(&utf16, "\xFF\x71", 2) == 0); + Require(sceCesEucJpToUtf32(euc, Bytes("\x8F\xB0\xA1"), 3, &consumed, &utf32) == invalidEncode); + Require(sceCesEucJpToUtf32le(euc, Bytes("\xA4\xA2"), 2, &consumed, &utf32) == 0); + Require(std::memcmp(&utf32, "\x42\x30\x00\x00", 4) == 0); + Require(sceCesEucJpToUtf32(sjis, Bytes("\xA4\xA2"), 2, &consumed, &utf32) == invalidParameter); + + std::uint8_t mbcs[4] = {}; + Require(sceCesUcs2ToSJis(0x3042, sjis, mbcs, sizeof(mbcs), &produced) == 0 && produced == 2 && std::memcmp(mbcs, "\x82\xA0", 2) == 0); + Require(sceCesUcs2ToSJis(0xFF71, sjis, mbcs, sizeof(mbcs), &produced) == 0 && produced == 1 && mbcs[0] == 0xB1); + Require(sceCesUcs2ToSJis(0x3042, sjis, mbcs, 1, &produced) == dstBufferEnd); + Require(sceCesUcs2ToSJis(0xD800, sjis, mbcs, sizeof(mbcs), &produced) == outOfCodeSpace); + Require(sceCesUcs2ToSJis(0x4E02, sjis, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(sceCesUcs2ToSJis(0x3042, euc, mbcs, sizeof(mbcs), &produced) == invalidParameter); + Require(sceCesUcs2ToSJis(0x3042, sceCesRefersUcsProfileCp1252(), mbcs, sizeof(mbcs), &produced) == invalidParameter); + Require(sceCesUcs2ToSJis(0x3042, sjis, nullptr, sizeof(mbcs), &produced) == invalidDstBuffer); + Require(sceCesUcs2ToEucJp(0x4E02, euc, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(sceCesUcs2ToEucJp(0xFF71, euc, mbcs, sizeof(mbcs), &produced) == 0 && produced == 2 && std::memcmp(mbcs, "\x8E\xB1", 2) == 0); + + Require(sceCesUtf8ToSJis(Bytes("\xE6\x97\xA5"), 3, &consumed, sjis, mbcs, sizeof(mbcs), &produced) == 0); + Require(consumed == 3 && produced == 2 && std::memcmp(mbcs, "\x93\xFA", 2) == 0); + Require(sceCesUtf8ToSJis(Bytes("\xE6\x97\xA5"), 2, &consumed, sjis, mbcs, sizeof(mbcs), &produced) == srcBufferEnd); + Require(sceCesUtf8ToSJis(Bytes("\xF0\x9F\x98\x80"), 4, &consumed, sjis, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(sceCesUtf8ToSJis(nullptr, 3, &consumed, sjis, mbcs, sizeof(mbcs), &produced) == invalidSrcBuffer); + Require(sceCesUtf8ToEucJp(Bytes("\xE6\x97\xA5"), 3, &consumed, euc, mbcs, sizeof(mbcs), &produced) == 0); + Require(produced == 2 && std::memcmp(mbcs, "\xC6\xFC", 2) == 0); + + const std::uint16_t hiragana[] = {0x3042}; + const std::uint16_t pair[] = {0xD83D, 0xDE00}; + const std::uint16_t lone[] = {0xDC00}; + Require(sceCesUtf16ToSJis(hiragana, 1, &consumed, sjis, mbcs, sizeof(mbcs), &produced) == 0 && consumed == 1 && std::memcmp(mbcs, "\x82\xA0", 2) == 0); + Require(sceCesUtf16ToSJis(pair, 2, &consumed, sjis, mbcs, sizeof(mbcs), &produced) == unassignedCode); + Require(sceCesUtf16ToSJis(pair, 1, &consumed, sjis, mbcs, sizeof(mbcs), &produced) == srcBufferEnd); + Require(sceCesUtf16ToSJis(lone, 1, &consumed, sjis, mbcs, sizeof(mbcs), &produced) == invalidEncode); + std::uint16_t hiraganaBe = 0; + std::memcpy(&hiraganaBe, "\x30\x42", 2); + Require(sceCesUtf16beToEucJp(&hiraganaBe, 1, &consumed, euc, mbcs, sizeof(mbcs), &produced) == 0 && std::memcmp(mbcs, "\xA4\xA2", 2) == 0); + + Require(sceCesUtf32ToSJis(0x65E5, sjis, mbcs, sizeof(mbcs), &produced) == 0 && std::memcmp(mbcs, "\x93\xFA", 2) == 0); + Require(sceCesUtf32ToSJis(0x110000, sjis, mbcs, sizeof(mbcs), &produced) == invalidEncode); + std::uint32_t utf32Be = 0; + 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); + 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); + + constexpr int microsoftForms[] = {0x00A2, 0x00A3, 0x00A5, 0x00AC, 0x2014, 0x2016, 0x203E, 0x2212, 0x301C, 0xFEFF}; + for (int code = 1; code < 0x10000; ++code) { + if (code >= 0xD800 && code <= 0xDFFF) continue; + if (std::find(std::begin(microsoftForms), std::end(microsoftForms), code) != std::end(microsoftForms)) continue; + std::uint8_t encoded[4] = {}; + std::uint32_t size = 0; + if (sceCesUcs2ToSJis(static_cast(code), sjis, encoded, sizeof(encoded), &size) == 0) { + std::uint16_t back = 0; + Require(sceCesSJisToUcs2(sjis, encoded, size, &consumed, &back) == 0 && consumed == size && back == code); + } + } + + std::uint8_t ku = 0; + std::uint8_t ten = 0; + std::uint16_t sjisCode = 0; + Require(sceCesSJisCodeToJisX0208(0x82A0, &ku, &ten) == 0 && ku == 4 && ten == 2); + Require(sceCesSJisCodeToJisX0208(0x93FA, &ku, &ten) == 0 && ku == 38 && ten == 92); + Require(sceCesSJisCodeToJisX0208(0xEFFC, &ku, &ten) == 0 && ku == 94 && ten == 94); + Require(sceCesSJisCodeToJisX0208(0xF040, &ku, &ten) == invalidEncode); + Require(sceCesSJisCodeToJisX0208(0x0041, &ku, &ten) == invalidEncode); + Require(sceCesSJisCodeToJisX0208(0x827F, &ku, &ten) == invalidEncode); + Require(sceCesSJisCodeToJisX0208(0x82A0, nullptr, &ten) == invalidDstBuffer); + Require(sceCesJisX0208ToSJisCode(4, 2, &sjisCode) == 0 && sjisCode == 0x82A0); + Require(sceCesJisX0208ToSJisCode(0, 2, &sjisCode) == invalidEncode); + Require(sceCesJisX0208ToSJisCode(4, 95, &sjisCode) == invalidEncode); + Require(sceCesJisX0208ToSJisCode(4, 2, nullptr) == invalidDstBuffer); + for (int row = 1; row <= 94; ++row) { + for (int cell = 1; cell <= 94; ++cell) { + Require(sceCesJisX0208ToSJisCode(static_cast(row), static_cast(cell), &sjisCode) == 0); + Require(sceCesSJisCodeToJisX0208(sjisCode, &ku, &ten) == 0 && ku == row && ten == cell); + } + } + TestJisX0208Profiles(sjis, euc); + return 0; +} diff --git a/core/libs/tests/GuestCesMbc.cpp b/core/libs/tests/GuestCesMbc.cpp new file mode 100644 index 0000000000..9d1e17c780 --- /dev/null +++ b/core/libs/tests/GuestCesMbc.cpp @@ -0,0 +1,434 @@ +#include "prx/libSceCesCs/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +constexpr int invalidParameter = static_cast(0x805C0001); +constexpr int invalidSrcBuffer = static_cast(0x805C0010); +constexpr int srcBufferEnd = static_cast(0x805C0011); +constexpr int invalidEncode = static_cast(0x805C0014); +constexpr int outOfCodeSpace = static_cast(0x805C0015); +constexpr int unassignedCode = static_cast(0x805C0020); +constexpr int unsupported = static_cast(0x805C0024); +constexpr int invalidDstBuffer = static_cast(0x805C0030); +constexpr int dstBufferEnd = static_cast(0x805C0031); +constexpr std::uint32_t untouched = 0xEEEEEEEE; + +enum Form { Ucs2, Utf8, Utf16, Utf16be, Utf16le, Utf32, Utf32be, Utf32le, FormCount }; + +struct Sequence { + std::uint32_t count; + std::uint32_t units[4]; + int expected; +}; + +static void Require(bool value) { if (!value) std::abort(); } + +static const std::uint8_t* Bytes(const char* text) { return reinterpret_cast(text); } + +static bool IsBuffer(int form) { return form == Utf8 || form == Utf16 || form == Utf16be || form == Utf16le; } + +static std::uint32_t Units(int form, std::uint32_t code, std::uint32_t* units) { + constexpr std::uint32_t leads[5] = {0, 0, 0xC0, 0xE0, 0xF0}; + if (form == Utf8 && code >= 0x80) { + const std::uint32_t count = code < 0x800 ? 2 : code < 0x10000 ? 3 : 4; + for (std::uint32_t i = count - 1; i > 0; --i, code >>= 6) units[i] = 0x80 | (code & 0x3F); + units[0] = leads[count] | code; + return count; + } + if (IsBuffer(form) && code >= 0x10000) { + units[0] = 0xD800 + ((code - 0x10000) >> 10); + units[1] = 0xDC00 + ((code - 0x10000) & 0x3FF); + return 2; + } + units[0] = code; + return 1; +} + +static int Call(int form, const std::uint32_t* units, std::uint32_t count, const void* profile, std::uint8_t* mbc, std::uint32_t mbcMax, std::uint32_t* mbcLen, std::uint32_t* sourceLen) { + const bool bigEndian = form == Utf16be || form == Utf32be || ((form == Utf16 || form == Utf32) && std::endian::native == std::endian::big); + const std::uint32_t width = form == Utf8 ? 1 : form >= Utf32 ? 4 : 2; + alignas(4) std::uint8_t bytes[16] = {}; + for (std::uint32_t i = 0; units && i < count; ++i) { + for (std::uint32_t b = 0; b < width; ++b) bytes[i * width + b] = static_cast(units[i] >> (8 * (bigEndian ? width - 1 - b : b))); + } + const std::uint8_t* utf8 = units ? bytes : nullptr; + const auto* utf16 = reinterpret_cast(utf8); + const auto* utf32 = reinterpret_cast(utf8); + const std::uint32_t value = units ? units[0] : 0; + switch (form) { + case Ucs2: return sceCesUcs2ToMbc(static_cast(value), profile, mbc, mbcMax, mbcLen); + case Utf8: return sceCesUtf8ToMbc(utf8, count, sourceLen, profile, mbc, mbcMax, mbcLen); + case Utf16: return sceCesUtf16ToMbc(utf16, count, sourceLen, profile, mbc, mbcMax, mbcLen); + case Utf16be: return sceCesUtf16beToMbc(utf16, count, sourceLen, profile, mbc, mbcMax, mbcLen); + case Utf16le: return sceCesUtf16leToMbc(utf16, count, sourceLen, profile, mbc, mbcMax, mbcLen); + case Utf32: return sceCesUtf32ToMbc(value, profile, mbc, mbcMax, mbcLen); + case Utf32be: return sceCesUtf32beToMbc(utf32, profile, mbc, mbcMax, mbcLen); + default: return sceCesUtf32leToMbc(utf32, profile, mbc, mbcMax, mbcLen); + } +} + +static void RequireFailure(int form, const std::uint32_t* units, std::uint32_t count, const void* profile, bool target, std::uint32_t mbcMax, int expected) { + std::uint8_t mbc[4] = {0xEE, 0xEE, 0xEE, 0xEE}; + std::uint32_t mbcLen = untouched; + std::uint32_t sourceLen = untouched; + Require(Call(form, units, count, profile, target ? mbc : nullptr, mbcMax, &mbcLen, &sourceLen) == expected); + const bool refused = !IsBuffer(form) && expected != invalidParameter && expected != invalidSrcBuffer && expected != invalidDstBuffer; + Require(mbc[0] == 0xEE && mbc[1] == 0xEE && mbc[2] == 0xEE && mbc[3] == 0xEE && mbcLen == (refused ? 0 : untouched) && sourceLen == untouched); +} + +static void RequireAll(std::uint32_t code, const void* profile, int expected, const std::uint8_t* bytes, std::uint32_t size) { + for (int form = code > 0xFFFF ? Utf8 : Ucs2; form < FormCount; ++form) { + std::uint32_t units[4] = {}; + const std::uint32_t count = Units(form, code, units); + if (expected != 0) { + RequireFailure(form, units, count, profile, true, 4, expected); + continue; + } + std::uint8_t mbc[4] = {0xEE, 0xEE, 0xEE, 0xEE}; + std::uint32_t mbcLen = untouched; + std::uint32_t sourceLen = untouched; + Require(Call(form, units, count, profile, mbc, sizeof(mbc), &mbcLen, &sourceLen) == 0); + Require(mbcLen == size && std::memcmp(mbc, bytes, size) == 0 && sourceLen == (IsBuffer(form) ? count : untouched)); + } +} + +static std::uint32_t SweepCodes(const void* profile) { + constexpr std::uint32_t supplementary[] = {0x10000, 0x1F600, 0x10FFFF}; + std::uint32_t mapped = 0; + for (std::uint32_t code = 0; code < 0x10000; ++code) { + if (code >= 0xD800 && code <= 0xDFFF) continue; + std::uint8_t bytes[4] = {}; + std::uint32_t size = 0; + const int result = sceCesUtf32ToMbc(code, profile, bytes, sizeof(bytes), &size); + Require(result == 0 || result == unassignedCode || result == unsupported); + if (result == 0 && size > 0) { + std::uint32_t consumed = 0; + std::uint32_t back = 0; + Require(sceCesMbcToUtf32(profile, bytes, size, &consumed, &back) == 0 && consumed == size); + if (back == code) ++mapped; + } + RequireAll(code, profile, result, bytes, size); + } + for (const std::uint32_t code : supplementary) RequireAll(code, profile, unassignedCode, nullptr, 0); + return mapped; +} + +static std::uint32_t RoundTrip(const void* profile, const std::uint8_t* source, std::uint32_t size) { + std::uint32_t consumed = 0; + std::uint32_t produced = 0; + std::uint32_t code = 0; + std::uint16_t ucs2 = 0; + std::uint8_t utf8[4] = {}; + std::uint32_t utf8Size = 0; + std::uint16_t utf16[3] = {}; + std::uint32_t utf32[2] = {}; + Require(sceCesMbcToUtf32(profile, source, size, &consumed, &code) == 0 && consumed == size); + Require(sceCesMbcToUcs2(profile, source, size, &consumed, &ucs2) == 0 && consumed == size); + Require(sceCesMbcToUtf8(profile, source, size, &consumed, utf8, sizeof(utf8), &utf8Size) == 0 && consumed == size); + Require(sceCesMbcToUtf16(profile, source, size, &consumed, &utf16[0], 1, &produced) == 0 && consumed == size); + Require(sceCesMbcToUtf16be(profile, source, size, &consumed, &utf16[1], 1, &produced) == 0 && consumed == size); + Require(sceCesMbcToUtf16le(profile, source, size, &consumed, &utf16[2], 1, &produced) == 0 && consumed == size); + Require(sceCesMbcToUtf32be(profile, source, size, &consumed, &utf32[0]) == 0 && consumed == size); + Require(sceCesMbcToUtf32le(profile, source, size, &consumed, &utf32[1]) == 0 && consumed == size); + std::uint8_t mbc[8][4] = {}; + std::uint32_t mbcLen[8] = {}; + std::uint32_t sourceLen[4] = {}; + Require(sceCesUtf32ToMbc(code, profile, mbc[0], 4, &mbcLen[0]) == 0); + Require(sceCesUcs2ToMbc(ucs2, profile, mbc[1], 4, &mbcLen[1]) == 0); + Require(sceCesUtf8ToMbc(utf8, utf8Size, &sourceLen[0], profile, mbc[2], 4, &mbcLen[2]) == 0 && sourceLen[0] == utf8Size); + Require(sceCesUtf16ToMbc(&utf16[0], 1, &sourceLen[1], profile, mbc[3], 4, &mbcLen[3]) == 0 && sourceLen[1] == 1); + Require(sceCesUtf16beToMbc(&utf16[1], 1, &sourceLen[2], profile, mbc[4], 4, &mbcLen[4]) == 0 && sourceLen[2] == 1); + Require(sceCesUtf16leToMbc(&utf16[2], 1, &sourceLen[3], profile, mbc[5], 4, &mbcLen[5]) == 0 && sourceLen[3] == 1); + Require(sceCesUtf32beToMbc(&utf32[0], profile, mbc[6], 4, &mbcLen[6]) == 0); + Require(sceCesUtf32leToMbc(&utf32[1], profile, mbc[7], 4, &mbcLen[7]) == 0); + for (std::uint32_t i = 1; i < 8; ++i) Require(mbcLen[i] == mbcLen[0] && std::memcmp(mbc[i], mbc[0], mbcLen[0]) == 0); + return mbcLen[0] == size && std::memcmp(mbc[0], source, size) == 0 ? 1 : 0; +} + +static std::uint32_t SweepBytes(const void* profile) { + std::uint32_t identical = 0; + std::uint8_t source[3] = {}; + std::uint32_t consumed = 0; + std::uint32_t code = 0; + for (std::uint32_t lead = 0; lead < 256; ++lead) { + source[0] = static_cast(lead); + const int single = sceCesMbcToUtf32(profile, source, 1, &consumed, &code); + if (single == 0) identical += RoundTrip(profile, source, 1); + if (single != srcBufferEnd) continue; + for (std::uint32_t trail = 0; trail < 256; ++trail) { + source[1] = static_cast(trail); + const int pair = sceCesMbcToUtf32(profile, source, 2, &consumed, &code); + if (pair == 0) identical += RoundTrip(profile, source, 2); + for (std::uint32_t third = 0; pair == srcBufferEnd && third < 256; ++third) { + source[2] = static_cast(third); + if (sceCesMbcToUtf32(profile, source, 3, &consumed, &code) == 0) identical += RoundTrip(profile, source, 3); + } + } + } + return identical; +} + +static void RequireInvalid(const void* profile) { + constexpr int scalarForms[] = {Ucs2, Utf32, Utf32be, Utf32le}; + const Sequence utf8[] = { + {2, {0xC0, 0x80}, outOfCodeSpace}, {2, {0xC1, 0xBF}, outOfCodeSpace}, {3, {0xE0, 0x80, 0x80}, outOfCodeSpace}, + {3, {0xE0, 0x9F, 0xBF}, outOfCodeSpace}, {4, {0xF0, 0x80, 0x80, 0x80}, outOfCodeSpace}, {4, {0xF0, 0x8F, 0xBF, 0xBF}, outOfCodeSpace}, + {3, {0xED, 0xA0, 0x80}, outOfCodeSpace}, {3, {0xED, 0xBF, 0xBF}, outOfCodeSpace}, {4, {0xF4, 0x90, 0x80, 0x80}, unassignedCode}, + {4, {0xF7, 0xBF, 0xBF, 0xBF}, unassignedCode}, {1, {0x80}, invalidEncode}, {1, {0xFF}, invalidEncode}, + {3, {0xE3, 0x41, 0x42}, invalidEncode}, {2, {0xE3, 0x81}, srcBufferEnd}, {3, {0xF0, 0x9F, 0x98}, srcBufferEnd}, + }; + const Sequence utf16[] = { + {1, {0xDC00}, invalidEncode}, {1, {0xDFFF}, invalidEncode}, {2, {0xD800, 0x0041}, invalidEncode}, + {2, {0xDBFF, 0xDBFF}, invalidEncode}, {2, {0xDC00, 0xD800}, invalidEncode}, {1, {0xD800}, srcBufferEnd}, + }; + const Sequence utf32[] = {{1, {0xD800}, outOfCodeSpace}, {1, {0xDFFF}, outOfCodeSpace}, {1, {0x110000}, invalidEncode}, {1, {0xFFFFFFFF}, invalidEncode}}; + for (const Sequence& sequence : utf8) RequireFailure(Utf8, sequence.units, sequence.count, profile, true, 4, sequence.expected); + for (const Sequence& sequence : utf16) { + for (int form = Utf16; form <= Utf16le; ++form) RequireFailure(form, sequence.units, sequence.count, profile, true, 4, sequence.expected); + } + for (const Sequence& sequence : utf32) { + for (const int form : scalarForms) { + if (form != Ucs2 || sequence.units[0] <= 0xFFFF) RequireFailure(form, sequence.units, sequence.count, profile, true, 4, sequence.expected); + } + } +} + +struct Rejected { + const void* profile; + const char* bytes; + std::uint32_t size; + int expected; + std::uint32_t length; +}; + +static void RequireRejected(const Rejected& sequence) { + for (int form = 0; form < 8; ++form) { + std::uint32_t consumed = 0xEEEEEEEE; + std::uint32_t produced = 0; + std::uint16_t units[2] = {}; + std::uint8_t utf8[4] = {}; + std::uint32_t utf32 = 0; + const auto* bytes = Bytes(sequence.bytes); + int result = 0; + switch (form) { + case 0: result = sceCesMbcToUcs2(sequence.profile, bytes, sequence.size, &consumed, units); break; + case 1: result = sceCesMbcToUtf8(sequence.profile, bytes, sequence.size, &consumed, utf8, sizeof(utf8), &produced); break; + case 2: result = sceCesMbcToUtf16(sequence.profile, bytes, sequence.size, &consumed, units, 2, &produced); break; + case 3: result = sceCesMbcToUtf16be(sequence.profile, bytes, sequence.size, &consumed, units, 2, &produced); break; + case 4: result = sceCesMbcToUtf16le(sequence.profile, bytes, sequence.size, &consumed, units, 2, &produced); break; + case 5: result = sceCesMbcToUtf32(sequence.profile, bytes, sequence.size, &consumed, &utf32); break; + case 6: result = sceCesMbcToUtf32be(sequence.profile, bytes, sequence.size, &consumed, &utf32); break; + default: result = sceCesMbcToUtf32le(sequence.profile, bytes, sequence.size, &consumed, &utf32); break; + } + Require(result == sequence.expected && consumed == sequence.length); + } +} + +static void RequireX0213(std::uint16_t sjis, std::uint8_t men, std::uint8_t ku, std::uint8_t ten) { + std::uint8_t m = 0; + std::uint8_t k = 0; + std::uint8_t t = 0; + Require(sceCesSJisCodeToJisX0213(sjis, &m, &k, &t) == 0 && m == men && k == ku && t == ten); + std::uint16_t back = 0; + Require(sceCesJisX0213ToSJisCode(men, ku, ten, &back) == 0 && back == sjis); +} + +int main() { + alignas(8) std::uint8_t sjisSheet[64] = {}; + alignas(8) std::uint8_t eucSheet[64] = {}; + alignas(8) std::uint8_t gbkSheet[64] = {}; + alignas(8) std::uint8_t blankSheet[64] = {}; + const void* sjis = sceCesUcsProfileInitSJis1997Cp932(sjisSheet); + const void* euc = sceCesUcsProfileInitEucJpCp51932(eucSheet); + const void* gbk = sceCesUcsProfileInitGbkCp936(gbkSheet); + const void* cp1252 = sceCesRefersUcsProfileCp1252(); + + std::uint32_t consumed = 0; + std::uint32_t produced = 0; + std::uint16_t ucs2 = 0; + Require(sceCesMbcToUcs2(sjis, Bytes("\x82\xA0"), 2, &consumed, &ucs2) == 0 && consumed == 2 && ucs2 == 0x3042); + Require(sceCesMbcToUcs2(sjis, Bytes("\xB1"), 1, &consumed, &ucs2) == 0 && consumed == 1 && ucs2 == 0xFF71); + Require(sceCesMbcToUcs2(euc, Bytes("\xA4\xA2"), 2, &consumed, &ucs2) == 0 && consumed == 2 && ucs2 == 0x3042); + Require(sceCesMbcToUcs2(gbk, Bytes("\xD6\xD0"), 2, &consumed, &ucs2) == 0 && consumed == 2 && ucs2 == 0x4E2D); + Require(sceCesMbcToUcs2(cp1252, Bytes("\x80"), 1, &consumed, &ucs2) == 0 && consumed == 1 && ucs2 == 0x20AC); + Require(sceCesMbcToUcs2(nullptr, Bytes("A"), 1, &consumed, &ucs2) == invalidParameter); + Require(sceCesMbcToUcs2(blankSheet, Bytes("A"), 1, &consumed, &ucs2) == invalidParameter); + Require(sceCesMbcToUcs2(sjis, nullptr, 1, &consumed, &ucs2) == invalidSrcBuffer); + Require(sceCesMbcToUcs2(sjis, Bytes("A"), 0, &consumed, &ucs2) == srcBufferEnd); + Require(sceCesMbcToUcs2(sjis, Bytes("A"), 1, &consumed, nullptr) == invalidDstBuffer); + + alignas(8) std::uint8_t gb18030Sheet[64] = {}; + alignas(8) std::uint8_t uhcSheet[64] = {}; + alignas(8) std::uint8_t big5Sheet[64] = {}; + const void* gb18030 = sceCesUcsProfileInitGb18030_2000(gb18030Sheet); + const void* uhc = sceCesUcsProfileInitUhc(uhcSheet); + const void* big5 = sceCesUcsProfileInitBig5Cp950(big5Sheet); + const Rejected rejected[] = { + {sjis, "\x82", 1, srcBufferEnd, 2}, {sjis, "\x80", 1, invalidEncode, 1}, {sjis, "\xA0", 1, invalidEncode, 1}, + {sjis, "\x82\x00", 2, invalidEncode, 1}, {sjis, "\x82\x7F", 2, invalidEncode, 2}, {sjis, "\xF0\x40", 2, unassignedCode, 2}, + {euc, "\xA4", 1, srcBufferEnd, 2}, {euc, "\x8F\xA1", 2, srcBufferEnd, 3}, {euc, "\xFF", 1, invalidEncode, 1}, + {euc, "\xA4\x00", 2, invalidEncode, 1}, {euc, "\xA4\x41", 2, invalidEncode, 2}, {euc, "\x8F\x00", 2, invalidEncode, 1}, + {gbk, "\x81", 1, srcBufferEnd, 2}, {gbk, "\xFF", 1, invalidEncode, 1}, {gbk, "\x81\x00", 2, invalidEncode, 1}, {gbk, "\x81\x7F", 2, invalidEncode, 2}, + {gb18030, "\x81\x30", 2, srcBufferEnd, 4}, {gb18030, "\x81\x30\x81", 3, srcBufferEnd, 4}, {gb18030, "\x81\x00", 2, invalidEncode, 1}, + {gb18030, "\x81\x30\x00", 3, invalidEncode, 2}, {gb18030, "\x81\x30\x20\x00", 4, invalidEncode, 3}, {gb18030, "\x81\x30\x20\x30", 4, invalidEncode, 4}, + {uhc, "\xC9", 1, srcBufferEnd, 2}, {uhc, "\xC9\xFF", 2, invalidEncode, 2}, {uhc, "\xC9\x41", 2, unassignedCode, 2}, + {big5, "\xA1\x00", 2, invalidEncode, 1}, {big5, "\xA1\x7F", 2, invalidEncode, 2}, {big5, "\x81\x40", 2, unassignedCode, 2}, + }; + for (const Rejected& sequence : rejected) RequireRejected(sequence); + + std::uint8_t utf8[4] = {}; + Require(sceCesMbcToUtf8(gbk, Bytes("\xD6\xD0"), 2, &consumed, utf8, sizeof(utf8), &produced) == 0 && consumed == 2 && produced == 3); + Require(std::memcmp(utf8, "\xE4\xB8\xAD", 3) == 0); + Require(sceCesMbcToUtf8(gbk, Bytes("\xD6\xD0"), 2, &consumed, utf8, 2, &produced) == dstBufferEnd); + + std::uint16_t utf16 = 0; + Require(sceCesMbcToUtf16(sjis, Bytes("\x82\xA0"), 2, &consumed, &utf16, 1, &produced) == 0 && produced == 1 && utf16 == 0x3042); + Require(sceCesMbcToUtf16le(euc, Bytes("\xA4\xA2"), 2, &consumed, &utf16, 1, &produced) == 0 && std::memcmp(&utf16, "\x42\x30", 2) == 0); + Require(sceCesMbcToUtf16be(euc, Bytes("\xA4\xA2"), 2, &consumed, &utf16, 1, &produced) == 0 && std::memcmp(&utf16, "\x30\x42", 2) == 0); + Require(sceCesMbcToUtf16(sjis, Bytes("\x82\xA0"), 2, &consumed, &utf16, 0, &produced) == dstBufferEnd); + + std::uint32_t utf32 = 0; + Require(sceCesMbcToUtf32(gbk, Bytes("A"), 1, &consumed, &utf32) == 0 && consumed == 1 && utf32 == 'A'); + Require(sceCesMbcToUtf32le(gbk, Bytes("\xD6\xD0"), 2, &consumed, &utf32) == 0 && std::memcmp(&utf32, "\x2D\x4E\x00\x00", 4) == 0); + Require(sceCesMbcToUtf32be(gbk, Bytes("\xD6\xD0"), 2, &consumed, &utf32) == 0 && std::memcmp(&utf32, "\x00\x00\x4E\x2D", 4) == 0); + + std::uint16_t code = 0; + std::uint32_t size = 0; + Require(sceCesSJisGetCode(Bytes("\x82\xA0"), 2, &size, &code) == 0 && size == 2 && code == 0x82A0); + Require(sceCesSJisGetCode(Bytes("A\x82"), 2, &size, &code) == 0 && size == 1 && code == 'A'); + Require(sceCesSJisGetCode(Bytes("\xDF"), 1, &size, &code) == 0 && size == 1 && code == 0xDF); + Require(sceCesSJisGetCode(Bytes("\xFC\xFC"), 2, nullptr, &code) == 0 && code == 0xFCFC); + Require(sceCesSJisGetCode(Bytes("\x82"), 1, &size, &code) == srcBufferEnd); + Require(sceCesSJisGetCode(Bytes("\x80"), 1, &size, &code) == invalidEncode); + Require(sceCesSJisGetCode(Bytes("\xA0"), 1, &size, &code) == invalidEncode); + Require(sceCesSJisGetCode(Bytes("\xFD\x40"), 2, &size, &code) == invalidEncode); + Require(sceCesSJisGetCode(Bytes("\x82\x7F"), 2, &size, &code) == invalidEncode); + Require(sceCesSJisGetCode(nullptr, 2, &size, &code) == invalidSrcBuffer); + Require(sceCesSJisGetCode(Bytes("A"), 0, &size, &code) == srcBufferEnd); + Require(sceCesSJisGetCode(Bytes("A"), 1, &size, nullptr) == invalidDstBuffer); + + std::uint8_t sjisOut[2] = {}; + Require(sceCesSJisPutCode(0x82A0, sjisOut, 2, &size) == 0 && size == 2 && sjisOut[0] == 0x82 && sjisOut[1] == 0xA0); + Require(sceCesSJisPutCode('A', sjisOut, 1, &size) == 0 && size == 1 && sjisOut[0] == 'A'); + Require(sceCesSJisPutCode(0xB1, sjisOut, 1, nullptr) == 0 && sjisOut[0] == 0xB1); + Require(sceCesSJisPutCode(0x82A0, sjisOut, 1, &size) == dstBufferEnd); + Require(sceCesSJisPutCode('A', sjisOut, 0, &size) == dstBufferEnd); + Require(sceCesSJisPutCode(0x80, sjisOut, 2, &size) == invalidEncode); + Require(sceCesSJisPutCode(0x0141, sjisOut, 2, &size) == invalidEncode); + Require(sceCesSJisPutCode(0x827F, sjisOut, 2, &size) == invalidEncode); + Require(sceCesSJisPutCode(0x82A0, nullptr, 2, &size) == invalidDstBuffer); + + RequireX0213(0x8140, 1, 1, 1); + RequireX0213(0x889F, 1, 16, 1); + RequireX0213(0xEFFC, 1, 94, 94); + RequireX0213(0xF040, 2, 1, 1); + RequireX0213(0xF09F, 2, 8, 1); + RequireX0213(0xF140, 2, 3, 1); + RequireX0213(0xF19F, 2, 4, 1); + RequireX0213(0xF240, 2, 5, 1); + RequireX0213(0xF29F, 2, 12, 1); + RequireX0213(0xF340, 2, 13, 1); + RequireX0213(0xF39F, 2, 14, 1); + RequireX0213(0xF440, 2, 15, 1); + RequireX0213(0xF49F, 2, 78, 1); + RequireX0213(0xF540, 2, 79, 1); + RequireX0213(0xFCFC, 2, 94, 94); + std::uint8_t men = 0; + std::uint8_t ku = 0; + std::uint8_t ten = 0; + Require(sceCesSJisCodeToJisX0213(0x8040, &men, &ku, &ten) == invalidEncode); + Require(sceCesSJisCodeToJisX0213(0xFD40, &men, &ku, &ten) == invalidEncode); + Require(sceCesSJisCodeToJisX0213(0x827F, &men, &ku, &ten) == invalidEncode); + Require(sceCesSJisCodeToJisX0213(0x82A0, nullptr, &ku, &ten) == invalidDstBuffer); + Require(sceCesJisX0213ToSJisCode(2, 2, 1, &code) == invalidEncode); + Require(sceCesJisX0213ToSJisCode(2, 77, 1, &code) == invalidEncode); + Require(sceCesJisX0213ToSJisCode(3, 1, 1, &code) == invalidEncode); + Require(sceCesJisX0213ToSJisCode(0, 1, 1, &code) == invalidEncode); + Require(sceCesJisX0213ToSJisCode(1, 95, 1, &code) == invalidEncode); + Require(sceCesJisX0213ToSJisCode(1, 1, 0, &code) == invalidEncode); + Require(sceCesJisX0213ToSJisCode(1, 1, 1, nullptr) == invalidDstBuffer); + + alignas(8) std::uint8_t context[64] = {}; + alignas(8) std::uint8_t copy[64] = {}; + Require(sceCesMbcsUcsContextInit(context, gbk) == 0); + Require(sceCesMbcsUcsContextInitCopy(copy, context) == 0); + Require(sceCesMbcsStrToUtf8Str(copy, Bytes("\xD6\xD0"), 2, &consumed, utf8, sizeof(utf8), &produced) == 0 && consumed == 2 && produced == 3); + Require(std::memcmp(utf8, "\xE4\xB8\xAD", 3) == 0); + Require(sceCesMbcsUcsContextInitCopy(nullptr, context) == invalidParameter); + Require(sceCesMbcsUcsContextInitCopy(copy, nullptr) == invalidParameter); + + alignas(8) std::uint8_t eucCnSheet[64] = {}; + alignas(8) std::uint8_t eucKrSheet[64] = {}; + const void* eucCn = sceCesUcsProfileInitEucCnGb2312(eucCnSheet); + const void* eucKr = sceCesUcsProfileInitEucKr(eucKrSheet); + const void* cp1251 = sceCesRefersUcsProfileCp1251(); + const void* cp1250 = sceCesRefersUcsProfileCp1250(); + + RequireAll(0x20AC, cp1252, 0, Bytes("\x80"), 1); + RequireAll(0x00E9, cp1252, 0, Bytes("\xE9"), 1); + RequireAll(0x0100, cp1252, unassignedCode, nullptr, 0); + RequireAll(0x0416, cp1251, 0, Bytes("\xC6"), 1); + RequireAll(0x0141, cp1250, 0, Bytes("\xA3"), 1); + RequireAll(0x3042, sjis, 0, Bytes("\x82\xA0"), 2); + RequireAll(0xFF71, sjis, 0, Bytes("\xB1"), 1); + RequireAll(0x4E02, sjis, unassignedCode, nullptr, 0); + RequireAll(0x3042, euc, 0, Bytes("\xA4\xA2"), 2); + RequireAll(0xFF71, euc, 0, Bytes("\x8E\xB1"), 2); + RequireAll(0x4E02, euc, unassignedCode, nullptr, 0); + RequireAll(0x4E2D, gbk, 0, Bytes("\xD6\xD0"), 2); + RequireAll(0x4E02, gbk, 0, Bytes("\x81\x40"), 2); + RequireAll(0x20AC, gbk, 0, Bytes("\x80"), 1); + RequireAll(0x4E2D, eucCn, 0, Bytes("\xD6\xD0"), 2); + RequireAll(0x4E02, eucCn, unassignedCode, nullptr, 0); + RequireAll(0xAC00, eucKr, 0, Bytes("\xB0\xA1"), 2); + RequireAll(0xAC02, eucKr, unassignedCode, nullptr, 0); + RequireAll(0xAC02, uhc, 0, Bytes("\x81\x41"), 2); + RequireAll(0x4E00, big5, 0, Bytes("\xA4\x40"), 2); + + std::uint32_t units[4] = {}; + for (int form = Ucs2; form < FormCount; ++form) { + std::uint32_t count = Units(form, 0xDC00, units); + RequireFailure(form, nullptr, 0, nullptr, false, 0, invalidParameter); + RequireFailure(form, units, count, blankSheet, true, 4, invalidParameter); + if (form != Ucs2 && form != Utf32) RequireFailure(form, nullptr, 0, sjis, false, 0, invalidSrcBuffer); + if (IsBuffer(form)) RequireFailure(form, units, 0, sjis, false, 0, srcBufferEnd); + RequireFailure(form, units, count, sjis, false, 0, invalidDstBuffer); + RequireFailure(form, units, count, sjis, true, 0, IsBuffer(form) && form != Utf8 ? invalidEncode : outOfCodeSpace); + count = Units(form, 0x4E02, units); + RequireFailure(form, units, count, sjis, true, 0, unassignedCode); + count = Units(form, 0x3042, units); + std::uint8_t full[4] = {0xEE, 0xEE, 0xEE, 0xEE}; + std::uint32_t needed = untouched; + std::uint32_t sourceLen = untouched; + Require(Call(form, units, count, sjis, full, 1, &needed, &sourceLen) == dstBufferEnd && needed == (IsBuffer(form) ? untouched : 2) && full[0] == 0xEE && sourceLen == untouched); + std::uint8_t mbc[4] = {}; + Require(Call(form, units, count, sjis, mbc, sizeof(mbc), nullptr, nullptr) == 0 && std::memcmp(mbc, "\x82\xA0", 2) == 0); + } + + std::uint8_t mbc[4] = {}; + 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(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(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'); + + const void* singleByte[] = {cp1252, cp1251, cp1250}; + constexpr std::uint32_t singleByteMapped[] = {251, 255, 251}; + const void* multiByte[] = {sjis, euc, eucCn, gbk, eucKr, uhc, big5}; + for (std::size_t index = 0; index < 3; ++index) { + Require(SweepCodes(singleByte[index]) == singleByteMapped[index] && SweepBytes(singleByte[index]) == singleByteMapped[index]); + RequireInvalid(singleByte[index]); + } + for (const void* profile : multiByte) { + const std::uint32_t mapped = SweepCodes(profile); + Require(mapped > 256 && SweepBytes(profile) == mapped); + RequireInvalid(profile); + } +} diff --git a/core/libs/tests/GuestCesUnicode.cpp b/core/libs/tests/GuestCesUnicode.cpp index 1603f09810..fa8fee7f88 100644 --- a/core/libs/tests/GuestCesUnicode.cpp +++ b/core/libs/tests/GuestCesUnicode.cpp @@ -1,31 +1,12 @@ +#include "prx/libSceCesCs/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceCesCs/JisX0208.hpp" -#include "prx/libSceCesCs/JisX0212.hpp" #include #include #include #include #include -extern "C" { -int APS5_VABI sceCesUcs2ToUtf8(uint16_t, uint8_t*, uint32_t, uint32_t*); -int APS5_VABI sceCesUtf8ToUcs2(const uint8_t*, uint32_t, uint32_t*, uint16_t*); -int APS5_VABI sceCesUtf32ToUtf8(uint32_t, uint8_t*, uint32_t, uint32_t*); -int APS5_VABI sceCesUtf32beToUtf8(const uint32_t*, uint8_t*, uint32_t, uint32_t*); -int APS5_VABI sceCesUtf32leToUtf8(const uint32_t*, uint8_t*, uint32_t, uint32_t*); -int APS5_VABI sceCesUtf8ToUtf32(const uint8_t*, uint32_t, uint32_t*, uint32_t*); -int APS5_VABI sceCesUtf8ToUtf32be(const uint8_t*, uint32_t, uint32_t*, uint32_t*); -int APS5_VABI sceCesUtf8ToUtf32le(const uint8_t*, uint32_t, uint32_t*, uint32_t*); -int APS5_VABI sceCesUtf8ToUtf16(const uint8_t*, uint32_t, uint32_t*, uint16_t*, uint32_t, uint32_t*); -int APS5_VABI sceCesUtf8ToUtf16be(const uint8_t*, uint32_t, uint32_t*, uint16_t*, uint32_t, uint32_t*); -int APS5_VABI sceCesUtf8ToUtf16le(const uint8_t*, uint32_t, uint32_t*, uint16_t*, uint32_t, uint32_t*); -int APS5_VABI sceCesUtf16ToUtf8(const uint16_t*, uint32_t, uint32_t*, uint8_t*, uint32_t, uint32_t*); -int APS5_VABI sceCesUtf16beToUtf8(const uint16_t*, uint32_t, uint32_t*, uint8_t*, uint32_t, uint32_t*); -int APS5_VABI sceCesUtf16leToUtf8(const uint16_t*, uint32_t, uint32_t*, uint8_t*, uint32_t, uint32_t*); -void* APS5_VABI sceCesUcsProfileInitEucJpCp51932(void*); -int APS5_VABI sceCesEucJpToUtf8(const void*, const uint8_t*, uint32_t, uint32_t*, uint8_t*, uint32_t, uint32_t*); -} - namespace { constexpr int INVALID_PARAMETER = static_cast(0x805C0001); @@ -178,6 +159,7 @@ void TestErrors() { } void TestEucJp() { + constexpr int INVALID_PROFILE = static_cast(0x805C0004); std::array sheet{}; const void* profile = sceCesUcsProfileInitEucJpCp51932(sheet.data()); auto check = [&](const uint8_t* source, uint32_t length, uint32_t code) { @@ -195,10 +177,11 @@ void TestEucJp() { output.fill(0xA5); consumed = produced = SENTINEL; Require(sceCesEucJpToUtf8(profile, source, length, &consumed, output.data(), cap, &produced) == DST_BUFFER_END); - Require(consumed == SENTINEL && produced == SENTINEL && output[0] == 0xA5); + Require(consumed == length && produced == expectedLength && output[0] == 0xA5); } }; - for (uint32_t code = 0; code < 128; ++code) { + for (uint32_t code = 0; code < 0xA0; ++code) { + if (code == 0x0E || code == 0x8E || code == 0x8F) continue; const uint8_t source[] = {static_cast(code)}; check(source, 1, code); } @@ -207,39 +190,41 @@ void TestEucJp() { check(source, 2, 0xFF61 + cell); } uint32_t assigned = 0; - for (uint32_t supplementary = 0; supplementary < 2; ++supplementary) { - for (uint32_t cell = 0; cell < 94 * 94; ++cell) { - const uint32_t code = supplementary ? JisX0212::TO_UCS2[cell] : JisX0208::TO_UCS2[cell]; - const uint8_t source[] = {0x8F, static_cast(0xA1 + cell / 94), static_cast(0xA1 + cell % 94)}; - const uint8_t* start = source + (supplementary ? 0 : 1); - const uint32_t length = supplementary ? 3 : 2; - if (code) { check(start, length, code); ++assigned; } - else { - uint8_t output[4] = {0xA5, 0xA5, 0xA5, 0xA5}; - uint32_t consumed = SENTINEL; - uint32_t produced = SENTINEL; - Require(sceCesEucJpToUtf8(profile, start, length, &consumed, output, 4, &produced) == UNASSIGNED_CODE); - Require(consumed == SENTINEL && produced == SENTINEL && output[0] == 0xA5); - } + for (uint32_t cell = 0; cell < 94 * 94; ++cell) { + const uint32_t code = JisX0208::TO_UCS2[cell]; + const uint8_t source[] = {static_cast(0xA1 + cell / 94), static_cast(0xA1 + cell % 94)}; + if (code) { check(source, 2, code); ++assigned; } + else { + uint8_t output[4] = {0xA5, 0xA5, 0xA5, 0xA5}; + uint32_t consumed = SENTINEL; + uint32_t produced = SENTINEL; + Require(sceCesEucJpToUtf8(profile, source, 2, &consumed, output, 4, &produced) == UNASSIGNED_CODE); + Require(consumed == 2 && produced == 0 && output[0] == 0xA5); } } - Require(assigned == 13403); - const uint8_t source[] = {0x8F, 0xA2, 0xAF, 'X'}; + Require(assigned == 7336); + const uint8_t source[] = {0xA4, 0xA2, 'X'}; uint8_t output[4] = {0xA5, 0xA5, 0xA5, 0xA5}; uint32_t consumed = SENTINEL; uint32_t produced = SENTINEL; - Require(sceCesEucJpToUtf8(nullptr, source, 3, &consumed, output, 4, &produced) == INVALID_PARAMETER); + Require(sceCesEucJpToUtf8(nullptr, source, 3, &consumed, output, 4, &produced) == INVALID_PROFILE); + Require(consumed == 0 && produced == 0); Require(sceCesEucJpToUtf8(profile, nullptr, 3, &consumed, output, 4, &produced) == INVALID_SRC_BUFFER); - Require(sceCesEucJpToUtf8(profile, source, 0, &consumed, output, 4, &produced) == SRC_BUFFER_END); + Require(sceCesEucJpToUtf8(profile, source, 0, &consumed, output, 4, &produced) == INVALID_SRC_BUFFER); Require(sceCesEucJpToUtf8(profile, source, 3, &consumed, nullptr, 4, &produced) == INVALID_DST_BUFFER); - for (uint32_t size : {1u, 2u}) Require(sceCesEucJpToUtf8(profile, source, size, &consumed, output, 4, &produced) == SRC_BUFFER_END); + Require(consumed == 2 && produced == 3); + Require(sceCesEucJpToUtf8(profile, source, 1, &consumed, output, 4, &produced) == SRC_BUFFER_END); + Require(consumed == 2 && produced == 0); const uint8_t malformed[] = {0x8E, 0x41}; Require(sceCesEucJpToUtf8(profile, malformed, 2, &consumed, output, 4, &produced) == INVALID_ENCODE); - Require(consumed == SENTINEL && produced == SENTINEL && output[0] == 0xA5); - Require(sceCesEucJpToUtf8(profile, source, 4, &consumed, output, 4, &produced) == 0); - Require(consumed == 3 && produced == 2 && output[0] == 0xCB && output[1] == 0x98 && output[2] == 0xA5); - Require(sceCesEucJpToUtf8(profile, source, 4, nullptr, output, 4, nullptr) == 0); - std::puts("13403 assigned JIS cells plus ASCII, half-width katakana and unassigned cells checked"); + Require(consumed == 2 && produced == 0 && output[0] == 0xA5); + const uint8_t supplementary[] = {0x8F, 0xA2, 0xAF}; + Require(sceCesEucJpToUtf8(profile, supplementary, 3, &consumed, output, 4, &produced) == INVALID_ENCODE); + Require(consumed == 3 && produced == 0 && output[0] == 0xA5); + Require(sceCesEucJpToUtf8(profile, source, 3, &consumed, output, 4, &produced) == 0); + Require(consumed == 2 && produced == 3 && output[0] == 0xE3 && output[1] == 0x81 && output[2] == 0x82 && output[3] == 0xA5); + Require(sceCesEucJpToUtf8(profile, source, 3, nullptr, output, 4, nullptr) == 0); + std::puts("7336 assigned JIS X 0208 cells plus single bytes and half-width katakana checked"); } } diff --git a/core/libs/tests/GuestCesWide.cpp b/core/libs/tests/GuestCesWide.cpp new file mode 100644 index 0000000000..e644f825e5 --- /dev/null +++ b/core/libs/tests/GuestCesWide.cpp @@ -0,0 +1,115 @@ +#include "prx/libSceCesCs/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +static const std::uint8_t* Bytes(const char* text) { return reinterpret_cast(text); } + +int main() { + constexpr int invalidParameter = static_cast(0x805C0001); + constexpr int invalidSrcBuffer = static_cast(0x805C0010); + constexpr int srcBufferEnd = static_cast(0x805C0011); + constexpr int invalidEncode = static_cast(0x805C0014); + constexpr int unassignedCode = static_cast(0x805C0020); + constexpr int invalidDstBuffer = static_cast(0x805C0030); + constexpr int dstBufferEnd = static_cast(0x805C0031); + + const std::uint8_t* cp1252 = sceCesRefersUcsProfileCp1252(); + const std::uint8_t* cp1251 = sceCesRefersUcsProfileCp1251(); + alignas(8) std::uint8_t sjisSheet[64] = {}; + const void* sjis = sceCesUcsProfileInitSJis1997Cp932(sjisSheet); + + std::uint16_t ucs2 = 0; + std::uint16_t utf16 = 0; + std::uint32_t utf32 = 0; + std::uint32_t length = 0; + Require(sceCesSbcToUcs2(cp1252, 0x80, &ucs2) == 0 && ucs2 == 0x20AC); + Require(sceCesSbcToUcs2(cp1251, 0xC0, &ucs2) == 0 && ucs2 == 0x0410); + Require(sceCesSbcToUcs2(static_cast(sjis), 0x41, &ucs2) == invalidParameter); + Require(sceCesSbcToUcs2(cp1252, 0x41, nullptr) == invalidDstBuffer); + Require(sceCesSbcToUtf16(cp1252, 0x80, &utf16, 1, &length) == 0 && utf16 == 0x20AC && length == 1); + Require(sceCesSbcToUtf16(cp1252, 0x80, &utf16, 0, &length) == dstBufferEnd); + Require(sceCesSbcToUtf16be(cp1252, 0x80, &utf16, 1, &length) == 0 && std::memcmp(&utf16, "\x20\xAC", 2) == 0); + Require(sceCesSbcToUtf16le(cp1252, 0x80, &utf16, 1, &length) == 0 && std::memcmp(&utf16, "\xAC\x20", 2) == 0); + Require(sceCesSbcToUtf32(cp1252, 0x9F, &utf32) == 0 && utf32 == 0x0178); + Require(sceCesSbcToUtf32be(cp1252, 0x9F, &utf32) == 0 && std::memcmp(&utf32, "\x00\x00\x01\x78", 4) == 0); + Require(sceCesSbcToUtf32le(cp1252, 0x9F, &utf32) == 0 && std::memcmp(&utf32, "\x78\x01\x00\x00", 4) == 0); + + std::uint8_t sbc = 0; + Require(sceCesUcs2ToSbc(0x20AC, cp1252, &sbc) == 0 && sbc == 0x80); + Require(sceCesUcs2ToSbc(0x0410, cp1251, &sbc) == 0 && sbc == 0xC0); + Require(sceCesUcs2ToSbc(0x3042, cp1252, &sbc) == unassignedCode); + Require(sceCesUcs2ToSbc(0xD800, cp1252, &sbc) == invalidEncode); + Require(sceCesUcs2ToSbc(0x41, cp1252, nullptr) == invalidDstBuffer); + Require(sceCesUcs2ToSbc(0x41, static_cast(sjis), &sbc) == invalidParameter); + const std::uint16_t euro[] = {0x20AC}; + const std::uint16_t pair[] = {0xD83D, 0xDE00}; + Require(sceCesUtf16ToSbc(euro, 1, &length, cp1252, &sbc) == 0 && sbc == 0x80 && length == 1); + Require(sceCesUtf16ToSbc(pair, 2, &length, cp1252, &sbc) == unassignedCode); + Require(sceCesUtf16ToSbc(pair, 1, &length, cp1252, &sbc) == srcBufferEnd); + Require(sceCesUtf16ToSbc(euro, 0, &length, cp1252, &sbc) == srcBufferEnd); + Require(sceCesUtf16ToSbc(nullptr, 1, &length, cp1252, &sbc) == invalidSrcBuffer); + std::uint16_t euroBe = 0; + std::memcpy(&euroBe, "\x20\xAC", 2); + Require(sceCesUtf16beToSbc(&euroBe, 1, &length, cp1252, &sbc) == 0 && sbc == 0x80); + std::uint16_t euroLe = 0; + std::memcpy(&euroLe, "\xAC\x20", 2); + Require(sceCesUtf16leToSbc(&euroLe, 1, &length, cp1252, &sbc) == 0 && sbc == 0x80); + Require(sceCesUtf32ToSbc(0x0178, cp1252, &sbc) == 0 && sbc == 0x9F); + Require(sceCesUtf32ToSbc(0x110000, cp1252, &sbc) == invalidEncode); + std::uint32_t yBe = 0; + std::memcpy(&yBe, "\x00\x00\x01\x78", 4); + Require(sceCesUtf32beToSbc(&yBe, cp1252, &sbc) == 0 && sbc == 0x9F); + std::uint32_t yLe = 0; + std::memcpy(&yLe, "\x78\x01\x00\x00", 4); + Require(sceCesUtf32leToSbc(&yLe, cp1252, &sbc) == 0 && sbc == 0x9F); + Require(sceCesUtf32leToSbc(nullptr, cp1252, &sbc) == invalidSrcBuffer); + for (int value = 0; value < 256; ++value) { + if (value == 0x98) { + Require(sceCesSbcToUtf32(cp1251, static_cast(value), &utf32) == unassignedCode); + continue; + } + Require(sceCesSbcToUtf32(cp1251, static_cast(value), &utf32) == 0); + Require(sceCesUtf32ToSbc(utf32, cp1251, &sbc) == 0 && sbc == value); + } + + alignas(8) std::uint8_t context[64] = {}; + Require(sceCesMbcsUcsContextInit(context, sjis) == 0); + const std::uint16_t wide[] = {0x3042, 'A', 0xFF71, 0}; + std::uint8_t mbcs[8] = {}; + std::uint32_t mbcsLength = 0; + Require(sceCesUtf16StrToMbcsStr(context, wide, 4, &length, mbcs, sizeof(mbcs), &mbcsLength) == 0); + Require(length == 3 && mbcsLength == 4 && std::memcmp(mbcs, "\x82\xA0" "A\xB1", 4) == 0); + Require(sceCesUtf16StrGetMbcsLen(context, wide, 4, &length, &mbcsLength) == 0 && length == 3 && mbcsLength == 4); + Require(sceCesUtf16StrToMbcsStr(context, wide, 4, &length, mbcs, 3, &mbcsLength) == dstBufferEnd); + Require(sceCesUtf16StrToMbcsStr(context, wide, 4, &length, nullptr, 3, &mbcsLength) == invalidDstBuffer); + Require(sceCesUtf16StrToMbcsStr(context, pair, 2, &length, mbcs, sizeof(mbcs), &mbcsLength) == unassignedCode); + const std::uint32_t wide32[] = {0x65E5, 'B', 0}; + Require(sceCesUtf32StrToMbcsStr(context, wide32, 3, &length, mbcs, sizeof(mbcs), &mbcsLength) == 0); + Require(length == 2 && mbcsLength == 3 && std::memcmp(mbcs, "\x93\xFA" "B", 3) == 0); + Require(sceCesUtf32StrGetMbcsLen(context, wide32, 1, &length, &mbcsLength) == 0 && length == 1 && mbcsLength == 2); + const std::uint32_t bad32[] = {0xDC00}; + Require(sceCesUtf32StrGetMbcsLen(context, bad32, 1, &length, &mbcsLength) == invalidEncode); + + std::uint16_t utf16Out[4] = {}; + std::uint32_t outLength = 0; + Require(sceCesMbcsStrToUtf16Str(context, Bytes("\x82\xA0" "A\xB1"), 8, &length, utf16Out, 4, &outLength) == 0); + Require(length == 4 && outLength == 3 && utf16Out[0] == 0x3042 && utf16Out[1] == 'A' && utf16Out[2] == 0xFF71); + Require(sceCesMbcsStrGetUtf16Len(context, Bytes("\x82\xA0" "A"), 3, &length, &outLength) == 0 && length == 3 && outLength == 2); + Require(sceCesMbcsStrToUtf16Str(context, Bytes("\x82\xA0" "A"), 3, &length, utf16Out, 1, &outLength) == dstBufferEnd); + Require(sceCesMbcsStrToUtf16Str(context, Bytes("\x82"), 1, &length, utf16Out, 4, &outLength) == srcBufferEnd); + std::uint32_t utf32Out[4] = {}; + Require(sceCesMbcsStrToUtf32Str(context, Bytes("\x93\xFA" "B"), 3, &length, utf32Out, 4, &outLength) == 0); + Require(length == 3 && outLength == 2 && utf32Out[0] == 0x65E5 && utf32Out[1] == 'B'); + Require(sceCesMbcsStrToUtf32Str(context, Bytes("A"), 1, &length, nullptr, 4, &outLength) == invalidDstBuffer); + Require(sceCesMbcsStrGetUtf32Len(context, Bytes("AB"), 2, &length, &outLength) == 0 && outLength == 2); + Require(sceCesMbcsStrGetUtf32Len(nullptr, Bytes("AB"), 2, &length, &outLength) == invalidParameter); + + Require(sceCesMbcsUcsContextInit(context, cp1252) == 0); + Require(sceCesMbcsStrToUtf16Str(context, Bytes("\x80\xE9"), 2, &length, utf16Out, 4, &outLength) == 0); + Require(outLength == 2 && utf16Out[0] == 0x20AC && utf16Out[1] == 0x00E9); + return 0; +} diff --git a/core/libs/tests/GuestCharacter.cpp b/core/libs/tests/GuestCharacter.cpp index 7601716b77..7f900acea7 100644 --- a/core/libs/tests/GuestCharacter.cpp +++ b/core/libs/tests/GuestCharacter.cpp @@ -1,44 +1,72 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include #include #include -extern "C" { -int APS5_VABI isupper_nid_postfix(int); -int APS5_VABI islower_nid_postfix(int); -int APS5_VABI isalpha_nid_postfix(int); -int APS5_VABI isdigit_nid_postfix(int); -int APS5_VABI isalnum_nid_postfix(int); -int APS5_VABI isspace_nid_postfix(int); -int APS5_VABI isblank_nid_postfix(int); -int APS5_VABI iscntrl_nid_postfix(int); -int APS5_VABI isprint_nid_postfix(int); -int APS5_VABI isgraph_nid_postfix(int); -int APS5_VABI ispunct_nid_postfix(int); -int APS5_VABI isxdigit_nid_postfix(int); -int APS5_VABI toupper_nid_postfix(int); -int APS5_VABI tolower_nid_postfix(int); -const short* APS5_VABI _Getptolower_nid_postfix(); -const short* APS5_VABI _Getptoupper_nid_postfix(); -const char* APS5_VABI setlocale_nid_postfix(int, const char*); -} + static void Require(bool value) { if (!value) std::abort(); } +static int ConsoleClass(int c) { + if (c < 0 || c > 255) return 0; + int bits = 0; + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) bits |= 0x01; + if (c >= 'A' && c <= 'Z') bits |= 0x02; + if (c == ' ') bits |= 0x04; + if (c >= 33 && c <= 126 && !(c >= '0' && c <= '9') && !(c >= 'A' && c <= 'Z') && !(c >= 'a' && c <= 'z')) bits |= 0x08; + if (c >= 'a' && c <= 'z') bits |= 0x10; + if (c >= '0' && c <= '9') bits |= 0x20; + if (c >= '\t' && c <= '\r') bits |= 0x40; + if (c < 32 || c == 127) bits |= 0x80; + if (c == '\t') bits |= 0x400; + return bits; +} +static void CheckConsoleClassValues() { + Require(isspace_nid_postfix(' ') == 0x04 && isspace_nid_postfix('\t') == 0x40 && isspace_nid_postfix('\n') == 0x40); + Require(isblank_nid_postfix(' ') == 0x04 && isblank_nid_postfix('\t') == 0x400 && iscntrl_nid_postfix('\t') == 0x80); + Require(isdigit_nid_postfix('0') == 0x20 && isalnum_nid_postfix('0') == 0x20 && isxdigit_nid_postfix('0') == 0x01); + Require(isupper_nid_postfix('A') == 0x02 && isalpha_nid_postfix('A') == 0x02 && isxdigit_nid_postfix('F') == 0x01 && isxdigit_nid_postfix('G') == 0); + Require(islower_nid_postfix('a') == 0x10 && isalpha_nid_postfix('a') == 0x10 && isprint_nid_postfix(' ') == 0x04 && isgraph_nid_postfix(' ') == 0); + Require(ispunct_nid_postfix('_') == 0x08 && isgraph_nid_postfix('_') == 0x08 && isprint_nid_postfix('_') == 0x08 && iscntrl_nid_postfix(127) == 0x80); + for (int c = EOF; c <= 255; ++c) { + const int bits = ConsoleClass(c); + Require(isupper_nid_postfix(c) == (bits & 0x02) && islower_nid_postfix(c) == (bits & 0x10) && isalpha_nid_postfix(c) == (bits & 0x12)); + Require(isdigit_nid_postfix(c) == (bits & 0x20) && isalnum_nid_postfix(c) == (bits & 0x32) && isxdigit_nid_postfix(c) == (bits & 0x01)); + Require(isspace_nid_postfix(c) == (bits & 0x44) && isblank_nid_postfix(c) == (bits & 0x404) && iscntrl_nid_postfix(c) == (bits & 0x80)); + Require(isprint_nid_postfix(c) == (bits & 0x3e) && isgraph_nid_postfix(c) == (bits & 0x3a) && ispunct_nid_postfix(c) == (bits & 0x08)); + } +} +static void CheckSignedCharValues() { + for (int c = -128; c <= -2; ++c) { + Require(isupper_nid_postfix(c) == 0 && islower_nid_postfix(c) == 0 && isalpha_nid_postfix(c) == 0 && isdigit_nid_postfix(c) == 0); + Require(isalnum_nid_postfix(c) == 0 && isspace_nid_postfix(c) == 0 && isblank_nid_postfix(c) == 0 && iscntrl_nid_postfix(c) == 0); + Require(isprint_nid_postfix(c) == 0 && isgraph_nid_postfix(c) == 0 && ispunct_nid_postfix(c) == 0 && isxdigit_nid_postfix(c) == 0); + Require(toupper_nid_postfix(c) == c && tolower_nid_postfix(c) == c); + } +} + int main() { + CheckConsoleClassValues(); + CheckSignedCharValues(); Require(std::setlocale(LC_CTYPE, "C") != nullptr); for (int category = 0; category <= 6; ++category) { const char* result = setlocale_nid_postfix(category, "C"); Require(result != nullptr && std::strcmp(result, "C") == 0); - result = setlocale_nid_postfix(category, "POSIX"); - Require(result != nullptr && std::strcmp(result, "C") == 0); + Require(setlocale_nid_postfix(category, "POSIX") == nullptr); result = setlocale_nid_postfix(category, nullptr); Require(result != nullptr && std::strcmp(result, "C") == 0); result = setlocale_nid_postfix(category, ""); Require(result != nullptr && std::strcmp(result, "C") == 0); Require(setlocale_nid_postfix(category, "en_US.UTF-8") == nullptr); } - Require(setlocale_nid_postfix(-1, "C") == nullptr); - Require(setlocale_nid_postfix(7, "C") == nullptr); + for (const int category : std::array{-1, 7, 100, 1 << 30}) { + const char* result = setlocale_nid_postfix(category, "C"); + Require(result != nullptr && std::strcmp(result, "C") == 0); + result = setlocale_nid_postfix(category, nullptr); + Require(result != nullptr && std::strcmp(result, "C") == 0); + Require(setlocale_nid_postfix(category, "POSIX") == nullptr && setlocale_nid_postfix(category, "x") == nullptr); + } for (int c = EOF; c <= 255; ++c) { Require(bool(isupper_nid_postfix(c)) == bool(std::isupper(c))); Require(bool(islower_nid_postfix(c)) == bool(std::islower(c))); @@ -52,9 +80,16 @@ int main() { Require(bool(isgraph_nid_postfix(c)) == bool(std::isgraph(c))); Require(bool(ispunct_nid_postfix(c)) == bool(std::ispunct(c))); Require(bool(isxdigit_nid_postfix(c)) == bool(std::isxdigit(c))); + Require(bool(isascii_nid_postfix(c)) == (c >= 0 && c <= 127)); + Require(toascii_nid_postfix(c) == (c & 0x7F)); Require(toupper_nid_postfix(c) == std::toupper(c)); Require(tolower_nid_postfix(c) == std::tolower(c)); Require(toupper_nid_postfix(c) == _Getptoupper_nid_postfix()[c]); Require(tolower_nid_postfix(c) == _Getptolower_nid_postfix()[c]); } + Require(!isascii_nid_postfix(-100)); + Require(!isascii_nid_postfix(128)); + Require(!isascii_nid_postfix(256)); + Require(toascii_nid_postfix(258) == 2); + Require(toascii_nid_postfix(-1) == 0x7F); } diff --git a/core/libs/tests/GuestClosedStandardDescriptors.cpp b/core/libs/tests/GuestClosedStandardDescriptors.cpp index 4cb0deece1..de4c2500e5 100644 --- a/core/libs/tests/GuestClosedStandardDescriptors.cpp +++ b/core/libs/tests/GuestClosedStandardDescriptors.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -6,10 +7,6 @@ #include #include -extern "C" { -int APS5_VABI fcntl_nid_postfix(int, int, ...); -} - namespace { constexpr int GuestGetFlags = 3; diff --git a/core/libs/tests/GuestClosedStandardDescriptorsWindows.cpp b/core/libs/tests/GuestClosedStandardDescriptorsWindows.cpp new file mode 100644 index 0000000000..b5633d602a --- /dev/null +++ b/core/libs/tests/GuestClosedStandardDescriptorsWindows.cpp @@ -0,0 +1,93 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { + +extern FileStream* __stdinp_nid_postfix; +extern FileStream* __stdoutp_nid_postfix; +extern FileStream* __stderrp_nid_postfix; + +} + +namespace { + +constexpr int GuestGetDescriptorFlags = 1; +constexpr int GuestGetFlags = 3; +constexpr int GuestAccessMode = 3; +constexpr int GuestReadOnly = 0; +constexpr int GuestWriteOnly = 1; + +int Child(const char* closed) { + for (int descriptor = 0; descriptor <= 2; ++descriptor) { + if (closed[descriptor] != '1') continue; + const auto handle = reinterpret_cast(::_get_osfhandle(descriptor)); + if (handle == INVALID_HANDLE_VALUE || handle == reinterpret_cast(-2)) return 70 + descriptor; + if (::GetFileType(handle) != FILE_TYPE_CHAR) return 73 + descriptor; + if (fcntl_nid_postfix(descriptor, GuestGetDescriptorFlags) != 0) return 76 + descriptor; + const int expected = descriptor == 0 ? GuestReadOnly : GuestWriteOnly; + if ((fcntl_nid_postfix(descriptor, GuestGetFlags) & GuestAccessMode) != expected) return 79 + descriptor; + std::FILE* const host = descriptor == 0 ? stdin : descriptor == 1 ? stdout : stderr; + FileStream* const guest = descriptor == 0 ? __stdinp_nid_postfix : descriptor == 1 ? __stdoutp_nid_postfix : __stderrp_nid_postfix; + if (::_fileno(host) != descriptor) return 82 + descriptor; + if (guest->GuestState().descriptor != descriptor) return 96 + descriptor; + if (fileno_nid_postfix(guest) != descriptor) return 85 + descriptor; + if (descriptor == 0) { + if (std::fgetc(host) != EOF || std::feof(host) == 0 || std::ferror(host) != 0) return 88; + std::clearerr(host); + if (fgetc_nid_postfix(guest) != EOF || feof_nid_postfix(guest) == 0 || ferror_nid_postfix(guest) != 0) return 89; + clearerr_nid_postfix(guest); + } else { + if (std::fputc('x', host) == EOF || std::fflush(host) != 0 || std::ferror(host) != 0) return 90 + descriptor; + if (fputc_nid_postfix('x', guest) == EOF || fflush_nid_postfix(guest) != 0 || ferror_nid_postfix(guest) != 0) return 93 + descriptor; + } + } + return 0; +} + +DWORD RunWithout(const wchar_t* self, bool input, bool output, bool error) { + std::wstring command = std::wstring(L"\"") + self + L"\" --closed-child " + (input ? L"1" : L"0") + (output ? L"1" : L"0") + (error ? L"1" : L"0"); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + startup.dwFlags = STARTF_USESTDHANDLES; + startup.hStdInput = input ? nullptr : GetStdHandle(STD_INPUT_HANDLE); + startup.hStdOutput = output ? nullptr : GetStdHandle(STD_OUTPUT_HANDLE); + startup.hStdError = error ? nullptr : GetStdHandle(STD_ERROR_HANDLE); + PROCESS_INFORMATION process{}; + if (!CreateProcessW(self, command.data(), nullptr, nullptr, TRUE, DETACHED_PROCESS, nullptr, nullptr, &startup, &process)) return 0xffffffffu; + DWORD code = 0xfffffffeu; + if (WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0) GetExitCodeProcess(process.hProcess, &code); + else TerminateProcess(process.hProcess, code); + CloseHandle(process.hThread); + CloseHandle(process.hProcess); + return code; +} + +} + +int main(int argc, char** argv) { + if (argc > 2 && std::strcmp(argv[1], "--closed-child") == 0 && std::strlen(argv[2]) == 3) return Child(argv[2]); + wchar_t self[MAX_PATH]; + if (GetModuleFileNameW(nullptr, self, MAX_PATH) == 0) return 2; + const struct { bool input, output, error; const char* name; } cases[] = { + {true, false, false, "stdin"}, + {false, true, false, "stdout"}, + {false, false, true, "stderr"}, + {true, true, true, "all three"}, + }; + for (const auto& test : cases) { + const DWORD result = RunWithout(self, test.input, test.output, test.error); + if (result != 0) { + std::fprintf(stderr, "missing standard handles, %s: child exited with %lu\n", test.name, result); + return 1; + } + } + std::puts("missing standard handles are opened on NUL"); + return 0; +} diff --git a/core/libs/tests/GuestClosedStandardStreamsLoadWindows.cpp b/core/libs/tests/GuestClosedStandardStreamsLoadWindows.cpp new file mode 100644 index 0000000000..da5ce1a579 --- /dev/null +++ b/core/libs/tests/GuestClosedStandardStreamsLoadWindows.cpp @@ -0,0 +1,104 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +_invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); + +} + +namespace { +int invalidCalls = 0; + +void InvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) { + ++invalidCalls; +} + +DWORD RunPrebound(const std::wstring& library, HANDLE report) { + wchar_t self[MAX_PATH]; + const DWORD length = GetModuleFileNameW(nullptr, self, MAX_PATH); + if (length == 0 || length >= MAX_PATH) return 0xffffffffu; + HANDLE inherited = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), report, GetCurrentProcess(), &inherited, 0, TRUE, DUPLICATE_SAME_ACCESS)) return 0xfffffffeu; + std::wstring command = L"\"" + std::wstring(self) + L"\" \"" + library + L"\" --prebound"; + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + startup.dwFlags = STARTF_USESTDHANDLES; + startup.hStdError = inherited; + PROCESS_INFORMATION process{}; + const bool created = CreateProcessW(self, command.data(), nullptr, nullptr, TRUE, DETACHED_PROCESS, nullptr, nullptr, &startup, &process) != 0; + CloseHandle(inherited); + if (!created) return 0xfffffffdu; + DWORD code = 0xfffffffcu; + if (WaitForSingleObject(process.hProcess, 5000) == WAIT_OBJECT_0) GetExitCodeProcess(process.hProcess, &code); + else TerminateProcess(process.hProcess, code); + CloseHandle(process.hThread); + CloseHandle(process.hProcess); + return code; +} +} + +int main() { + HANDLE report = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE), GetCurrentProcess(), &report, 0, FALSE, DUPLICATE_SAME_ACCESS)) return 1; + const auto finish = [&](int code, const std::string& message) { + DWORD written = 0; + WriteFile(report, message.data(), static_cast(message.size()), &written, nullptr); + CloseHandle(report); + return code; + }; + int count = 0; + auto** const arguments = CommandLineToArgvW(GetCommandLineW(), &count); + if (!arguments || (count != 2 && count != 3) || (count == 3 && std::wcscmp(arguments[2], L"--prebound") != 0)) { + if (arguments) LocalFree(arguments); + return finish(2, "expected one libc path argument\n"); + } + const std::wstring library(arguments[1]); + const bool prebound = count == 3; + LocalFree(arguments); + int outputDescriptor = 1; + if (prebound) { + if (::_fileno(stdout) != -2 || ::_wfreopen(L"NUL", L"wb", stdout) != stdout) + return finish(11, "cannot prepare an already rebound stdout\n"); + outputDescriptor = ::_fileno(stdout); + if (outputDescriptor < 3 || ::_get_osfhandle(1) != -2) + return finish(11, "stdout did not retain its separate descriptor\n"); + } else { + const DWORD child = RunPrebound(library, report); + if (child != 0) return finish(12, "prebound stdout child exited with " + std::to_string(child) + "\n"); + for (int descriptor : {1, 2}) { + if (::_fileno(__acrt_iob_func(descriptor)) != descriptor || ::_close(descriptor) != 0) + return finish(4, "cannot close an inherited standard descriptor\n"); + } + } + const auto previous = _set_thread_local_invalid_parameter_handler(InvalidParameter); + const auto module = LoadLibraryW(library.c_str()); + const DWORD loadError = module == nullptr ? GetLastError() : ERROR_SUCCESS; + _set_thread_local_invalid_parameter_handler(previous); + if (module == nullptr) return finish(5, "cannot load libc: " + std::to_string(loadError) + "\n"); + if (invalidCalls != 0) return finish(6, "restoring bound standard streams invoked the invalid parameter handler " + std::to_string(invalidCalls) + " times\n"); + const auto put = reinterpret_cast(GetProcAddress(module, "fputc_nid_postfix")); + const auto flush = reinterpret_cast(GetProcAddress(module, "fflush_nid_postfix")); + if (!put || !flush) return finish(7, "cannot find guest stream operations\n"); + if (::_write(1, "x", 1) != 1) return finish(13, "the restored stdout descriptor cannot write\n"); + for (int descriptor : {1, 2}) { + const auto pointer = GetProcAddress(module, descriptor == 1 ? "__stdoutp_nid_postfix" : "__stderrp_nid_postfix"); + if (!pointer) return finish(8, "cannot find a guest standard stream\n"); + auto* const guest = *reinterpret_cast(pointer); + auto* const host = __acrt_iob_func(descriptor); + const int expected = descriptor == 1 ? outputDescriptor : descriptor; + if (!guest || guest->GuestState().descriptor != expected || ::_fileno(host) != expected) + return finish(9, "a restored stream has the wrong descriptor\n"); + if (std::fputc('x', host) != 'x' || std::fflush(host) != 0 || put('x', guest) != 'x' || flush(guest) != 0) + return finish(10, "a restored stream cannot write to NUL\n"); + } + return finish(0, "bound standard streams are restored without invalid parameter callbacks\n"); +} diff --git a/core/libs/tests/GuestCollate.cpp b/core/libs/tests/GuestCollate.cpp index 7dec7aabab..f886933573 100644 --- a/core/libs/tests/GuestCollate.cpp +++ b/core/libs/tests/GuestCollate.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/GuestLocale.hpp" #include @@ -10,8 +11,7 @@ extern "C" { extern GuestLocale::Implementation* _ZSt21_sceLibcClassicLocale_nid_postfix; -std::size_t APS5_VABI _ZNSt7collateIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const* locale); -std::size_t APS5_VABI _ZNSt7collateIwE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const* locale); + } namespace { diff --git a/core/libs/tests/GuestCommonDialog.cpp b/core/libs/tests/GuestCommonDialog.cpp index 1638caf9ec..b0d74a3b47 100644 --- a/core/libs/tests/GuestCommonDialog.cpp +++ b/core/libs/tests/GuestCommonDialog.cpp @@ -1,24 +1,23 @@ +#include "prx/libSceCommonDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceCommonDialog/CommonDialog.hpp" #include -extern "C" { -int APS5_VABI sceCommonDialogInitialize(void); -bool APS5_VABI sceCommonDialogIsUsed(void); -} - namespace { void Require(bool value) { if (!value) std::abort(); } +constexpr int kErrAlreadySystemInitialized = static_cast(0x80B80002); + } int main() { Require(!sceCommonDialogIsUsed()); Require(sceCommonDialogInitialize() == COMMON_DIALOG_OK); Require(!sceCommonDialogIsUsed()); - Require(sceCommonDialogInitialize() == COMMON_DIALOG_ERROR_ALREADY_INITIALIZED); + Require(sceCommonDialogInitialize() == kErrAlreadySystemInitialized); + Require(sceCommonDialogInitialize() == kErrAlreadySystemInitialized); Require(!sceCommonDialogIsUsed()); } diff --git a/core/libs/tests/GuestCompanionHttpd.cpp b/core/libs/tests/GuestCompanionHttpd.cpp new file mode 100644 index 0000000000..afe5b08822 --- /dev/null +++ b/core/libs/tests/GuestCompanionHttpd.cpp @@ -0,0 +1,110 @@ +#include "prx/libSceCompanionHttpd/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int InvalidParam = static_cast(0x80E40004u); +constexpr int NotInitialized = static_cast(0x80E40006u); +constexpr int AlreadyInitialized = static_cast(0x80E40007u); +constexpr int NoEvent = static_cast(0x80E40008u); +constexpr int AlreadyStarted = static_cast(0x80E4000Au); +constexpr int NotStarted = static_cast(0x80E4000Bu); +constexpr int AlreadyRegistered = static_cast(0x80E4000Cu); + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "CompanionHttpd: %s\n", message); + std::abort(); + } +} + +template +void RequireNotImplemented(TCall call, const std::string& name) { + try { + call(); + } catch (const std::runtime_error& error) { + Require(std::string(error.what()) == name + " not implemented", "a companion function threw another error"); + return; + } + Require(false, "a companion function that should throw returned"); +} + +int APS5_VABI RequestCallback(int, int, const void*, void*, void*) { + std::fprintf(stderr, "CompanionHttpd: the request callback was called\n"); + std::abort(); +} + +void CheckOptionBlock() { + unsigned char block[0x70]; + std::memset(block, 0x5a, sizeof(block)); + Require(sceCompanionHttpdOptParamInitialize(nullptr) == InvalidParam, "a null option block must be rejected"); + Require(sceCompanionHttpdOptParamInitialize(reinterpret_cast(block)) == 0, "sceCompanionHttpdOptParamInitialize failed"); + for (std::size_t index = 0; index < sizeof(block); ++index) { + Require(block[index] == (index < 0x60 ? 0u : 0x5au), "the option block must be zeroed over its 0x60 bytes and nothing past them"); + } +} + +void CheckStates() { + unsigned char param[0x60]{}; + unsigned char event[0x104]; + std::memset(event, 0x5a, sizeof(event)); + unsigned char untouched[0x104]; + std::memset(untouched, 0x5a, sizeof(untouched)); + auto* callback = reinterpret_cast(&RequestCallback); + Require(sceCompanionHttpdGetEvent(event) == NotInitialized, "an event before initialization"); + Require(sceCompanionHttpdStart() == NotInitialized, "a start before initialization"); + Require(sceCompanionHttpdStop() == NotInitialized, "a stop before initialization"); + Require(sceCompanionHttpdTerminate() == NotInitialized, "a termination before initialization"); + Require(sceCompanionHttpdRegisterRequestCallback(reinterpret_cast(callback), nullptr) == NotInitialized, "a callback before initialization"); + Require(sceCompanionHttpdUnregisterRequestCallback() == NotInitialized, "an unregistration before initialization"); + Require(sceCompanionHttpdInitialize(nullptr) == InvalidParam, "a null parameter must be rejected"); + Require(sceCompanionHttpdInitialize(reinterpret_cast(param)) == 0, "sceCompanionHttpdInitialize failed"); + Require(sceCompanionHttpdInitialize(reinterpret_cast(param)) == AlreadyInitialized, "a second initialization"); + Require(sceCompanionHttpdRegisterRequestCallback(nullptr, nullptr) == InvalidParam, "a null callback must be rejected"); + Require(sceCompanionHttpdRegisterRequestCallback(reinterpret_cast(callback), param) == 0, "registering the request callback failed"); + Require(sceCompanionHttpdRegisterRequestCallback(reinterpret_cast(callback), param) == AlreadyRegistered, "a second callback"); + Require(sceCompanionHttpdGetEvent(nullptr) == InvalidParam, "a null event must be rejected"); + Require(sceCompanionHttpdGetEvent(event) == NoEvent, "no companion device connects before the start"); + Require(sceCompanionHttpdStop() == NotStarted, "a stop before the start"); + Require(sceCompanionHttpdStart() == 0, "sceCompanionHttpdStart failed"); + Require(sceCompanionHttpdStart() == AlreadyStarted, "a second start"); + Require(sceCompanionHttpdGetEvent(event) == NoEvent, "no companion device connects after the start"); + Require(std::memcmp(event, untouched, sizeof(event)) == 0, "sceCompanionHttpdGetEvent wrote the event"); + RequireNotImplemented([] { sceCompanionHttpdTerminate(); }, "sceCompanionHttpdTerminate"); + Require(sceCompanionHttpdStop() == 0, "sceCompanionHttpdStop failed"); + Require(sceCompanionHttpdUnregisterRequestCallback() == 0, "unregistering the request callback failed"); + RequireNotImplemented([] { sceCompanionHttpdUnregisterRequestCallback(); }, "sceCompanionHttpdUnregisterRequestCallback"); + Require(sceCompanionHttpdTerminate() == 0, "sceCompanionHttpdTerminate failed"); + Require(sceCompanionHttpdTerminate() == NotInitialized, "a second termination"); + Require(sceCompanionHttpdInitialize(reinterpret_cast(param)) == 0, "initializing again after the termination failed"); + Require(sceCompanionHttpdRegisterRequestCallback(reinterpret_cast(callback), nullptr) == 0, "the termination kept the callback"); + Require(sceCompanionHttpdTerminate() == 0, "the last termination failed"); +} + +void CheckResponseBuilders() { + unsigned char response[0x20]{}; + RequireNotImplemented([&] { sceCompanionHttpdAddHeader("key", "value", response); }, "sceCompanionHttpdAddHeader"); + RequireNotImplemented([&] { sceCompanionHttpdSetStatus(200, response); }, "sceCompanionHttpdSetStatus"); + RequireNotImplemented([&] { sceCompanionHttpdSetBody("body", 4, response); }, "sceCompanionHttpdSetBody"); + RequireNotImplemented([] { sceCompanionHttpdGet2ndScreenStatus(); }, "sceCompanionHttpdGet2ndScreenStatus"); + RequireNotImplemented([] { sceCompanionHttpdGetUserId(); }, "sceCompanionHttpdGetUserId"); + RequireNotImplemented([] { sceCompanionHttpdInitialize2(); }, "sceCompanionHttpdInitialize2"); + RequireNotImplemented([] { sceCompanionHttpdRegisterRequestBodyReceptionCallback(); }, "sceCompanionHttpdRegisterRequestBodyReceptionCallback"); + RequireNotImplemented([] { sceCompanionHttpdRegisterRequestCallback2(); }, "sceCompanionHttpdRegisterRequestCallback2"); + RequireNotImplemented([] { sceCompanionHttpdUnregisterRequestBodyReceptionCallback(); }, "sceCompanionHttpdUnregisterRequestBodyReceptionCallback"); +} + +} + +int main() { + CheckOptionBlock(); + CheckStates(); + CheckResponseBuilders(); + return 0; +} diff --git a/core/libs/tests/GuestCondRecursiveMutex.cpp b/core/libs/tests/GuestCondRecursiveMutex.cpp index 92f7166071..e21300d818 100644 --- a/core/libs/tests/GuestCondRecursiveMutex.cpp +++ b/core/libs/tests/GuestCondRecursiveMutex.cpp @@ -1,29 +1,12 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" #include "SceTypes.hpp" #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); -int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); -int APS5_VABI scePthreadCondSignal(PthreadCond* cond); -int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex); -int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec); -int APS5_VABI pthread_cond_wait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex); -int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime); -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EPERM = static_cast(0x80020001); -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); static constexpr int POSIX_ETIMEDOUT = 60; static constexpr int MUTEX_TYPE_RECURSIVE = 2; diff --git a/core/libs/tests/GuestCondTimedwait.cpp b/core/libs/tests/GuestCondTimedwait.cpp index 91c18ac93e..70db4b4e2b 100644 --- a/core/libs/tests/GuestCondTimedwait.cpp +++ b/core/libs/tests/GuestCondTimedwait.cpp @@ -1,36 +1,20 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex); -int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); -int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); -int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec); -int APS5_VABI scePthreadCondSignal(PthreadCond* cond); -int APS5_VABI pthread_cond_init_nid_postfix(PthreadCond* cond, const PthreadCondattr* attr); -int APS5_VABI pthread_cond_destroy_nid_postfix(PthreadCond* cond); -int APS5_VABI pthread_cond_reltimedwait_np_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec); -int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI *)(void*), void*, void*); -void APS5_VABI __pthread_cxa_finalize_nid_postfix(void*); -unsigned int APS5_VABI sceKernelSleep(unsigned int seconds); -} - static int cleanupAt = -1; static void APS5_VABI Cleanup(void*) { cleanupAt = 0; } static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); -static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast(0x80020010); static constexpr int POSIX_EPERM = 1; +static constexpr int POSIX_EINVAL = 22; static constexpr int POSIX_ETIMEDOUT = 60; static void Require(bool value) { if (!value) std::abort(); } @@ -53,6 +37,7 @@ struct Signalled { PthreadCond cond = nullptr; bool ready = false; int trylock = SCE_OK; + Pthread target = nullptr; }; static void* APS5_VABI Trylocker(void* arg) { @@ -70,6 +55,15 @@ static void* APS5_VABI Signaller(void* arg) { return nullptr; } +static void* APS5_VABI SignallerTo(void* arg) { + auto& signalled = *static_cast(arg); + Require(scePthreadMutexLock(&signalled.mutex) == SCE_OK); + signalled.ready = true; + Require(pthread_cond_signalto_np_nid_postfix(&signalled.cond, signalled.target) == 0); + Require(scePthreadMutexUnlock(&signalled.mutex) == SCE_OK); + return nullptr; +} + static void RequireHeldByCaller(Signalled& signalled) { Pthread thread = nullptr; Require(scePthreadCreate(&thread, nullptr, Trylocker, &signalled, nullptr) == SCE_OK); @@ -106,8 +100,29 @@ static void RelativeWait() { Require(scePthreadMutexDestroy(&signalled.mutex) == SCE_OK); } +static void SignalTo() { + Signalled signalled; + signalled.target = scePthreadSelf(); + Require(scePthreadMutexInit(&signalled.mutex, nullptr, nullptr) == SCE_OK); + Require(scePthreadCondInit(&signalled.cond, nullptr, nullptr) == SCE_OK); + Require(pthread_cond_signalto_np_nid_postfix(&signalled.cond, signalled.target) == 0); + Require(scePthreadMutexLock(&signalled.mutex) == SCE_OK); + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, SignallerTo, &signalled, nullptr) == SCE_OK); + const auto waitStart = std::chrono::steady_clock::now(); + while (!signalled.ready) + Require(pthread_cond_reltimedwait_np_nid_postfix(&signalled.cond, &signalled.mutex, 10000000) == 0); + Require(std::chrono::steady_clock::now() - waitStart < std::chrono::seconds(5)); + Require(scePthreadMutexUnlock(&signalled.mutex) == SCE_OK); + Require(scePthreadJoin(thread, nullptr) == SCE_OK); + Require(scePthreadCondDestroy(&signalled.cond) == SCE_OK); + Require(pthread_cond_signalto_np_nid_postfix(&signalled.cond, signalled.target) == 22); + Require(scePthreadMutexDestroy(&signalled.mutex) == SCE_OK); +} + int main() { RelativeWait(); + SignalTo(); Context context; PthreadCond cond = nullptr; @@ -131,6 +146,14 @@ int main() { Require(scePthreadMutexUnlock(&context.mutex) == SCE_OK); Require(scePthreadCondDestroy(&cond) == SCE_OK); Require(pthread_cond_init_nid_postfix(&cond, &unset) == 0); + { + const KernelTimespec negativeSeconds{-1, 0}; + const KernelTimespec negativeNanoseconds{1, -1}; + Require(scePthreadMutexLock(&context.mutex) == SCE_OK); + Require(pthread_cond_timedwait_nid_postfix(&cond, &context.mutex, &negativeSeconds) == POSIX_EINVAL); + Require(pthread_cond_timedwait_nid_postfix(&cond, &context.mutex, &negativeNanoseconds) == POSIX_EINVAL); + Require(scePthreadMutexUnlock(&context.mutex) == SCE_OK); + } Require(pthread_cond_destroy_nid_postfix(&cond) == 0); Require(scePthreadMutexDestroy(&context.mutex) == SCE_OK); @@ -138,5 +161,6 @@ int main() { void* finalizeHandle = reinterpret_cast(7); Require(__cxa_atexit_nid_postfix(Cleanup, nullptr, finalizeHandle) == SCE_OK); __pthread_cxa_finalize_nid_postfix(finalizeHandle); + Require(cleanupAt == 0); } diff --git a/core/libs/tests/GuestCondUnownedMutex.cpp b/core/libs/tests/GuestCondUnownedMutex.cpp index f68fe17391..124b772719 100644 --- a/core/libs/tests/GuestCondUnownedMutex.cpp +++ b/core/libs/tests/GuestCondUnownedMutex.cpp @@ -1,28 +1,15 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); -int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); -int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex); -int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec); -int APS5_VABI pthread_cond_wait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex); -int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime); -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EPERM = static_cast(0x80020001); -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static constexpr int POSIX_EPERM = 1; static constexpr int POSIX_EINVAL = 22; static constexpr std::uintptr_t ADAPTIVE_INITIALIZER = 1; diff --git a/core/libs/tests/GuestContentDelete.cpp b/core/libs/tests/GuestContentDelete.cpp index 9353f3a578..caafe4a5bd 100644 --- a/core/libs/tests/GuestContentDelete.cpp +++ b/core/libs/tests/GuestContentDelete.cpp @@ -1,14 +1,10 @@ +#include "prx/libSceContentDelete/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" int APS5_VABI sceContentDeleteInitialize(const ContentDeleteInitParam* init_param); -extern "C" int APS5_VABI sceContentDeleteTerminate(void); -extern "C" int APS5_VABI sceContentDeleteById(std::int64_t contentId); -extern "C" int APS5_VABI sceContentDeleteByPath(const char* contentPath); - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -30,16 +26,20 @@ bool Contains(const std::string& text, const std::string& part) { return text.fi } int main() { - Require(LogicErrorMessage([] { sceContentDeleteTerminate(); }) == "sceContentDeleteTerminate: not initialized"); - Require(LogicErrorMessage([] { sceContentDeleteInitialize(nullptr); }) == "invalid argument"); - Require(LogicErrorMessage([] { sceContentDeleteById(1); }) == "sceContentDeleteById: not initialized"); - Require(LogicErrorMessage([] { sceContentDeleteByPath("/user/av_contents/photo/a.png"); }) == "sceContentDeleteByPath: not initialized"); - Require(LogicErrorMessage([] { sceContentDeleteByPath(nullptr); }) == "invalid argument"); + Require(sceContentDeleteTerminate() == static_cast(0x809D5002u)); + Require(sceContentDeleteInitialize(nullptr) == static_cast(0x809D5001u)); + Require(sceContentDeleteById(1) == static_cast(0x809D5002u)); + Require(sceContentDeleteByPath("/user/av_contents/photo/a.png") == static_cast(0x809D5002u)); + Require(sceContentDeleteByPath(nullptr) == static_cast(0x809D5002u)); ContentDeleteInitParam param{}; param.heap_size = 0x10000; + Require(sceContentDeleteInitialize(¶m) == static_cast(0x809D5001u)); + param.heap_size = 0x4000; Require(sceContentDeleteInitialize(¶m) == 0); - Require(LogicErrorMessage([&] { sceContentDeleteInitialize(¶m); }) == "sceContentDeleteInitialize: already initialized"); + Require(sceContentDeleteInitialize(¶m) == static_cast(0x809D5003u)); + Require(sceContentDeleteInitialize(nullptr) == static_cast(0x809D5001u)); + Require(sceContentDeleteByPath("") == static_cast(0x809D5001u)); Require(LogicErrorMessage([] { sceContentDeleteByPath(nullptr); }) == "invalid argument"); Require(Contains(LogicErrorMessage([] { sceContentDeleteByPath("/user/av_contents/photo/a.png"); }), "sceContentDeleteByPath: /user/av_contents/photo/a.png is not in the host content library")); @@ -47,7 +47,7 @@ int main() { Require(Contains(LogicErrorMessage([] { sceContentDeleteById(-1); }), "sceContentDeleteById: content id -1 is not in the host content library")); Require(sceContentDeleteTerminate() == 0); - Require(LogicErrorMessage([] { sceContentDeleteTerminate(); }) == "sceContentDeleteTerminate: not initialized"); - Require(LogicErrorMessage([] { sceContentDeleteById(1); }) == "sceContentDeleteById: not initialized"); + Require(sceContentDeleteTerminate() == static_cast(0x809D5002u)); + Require(sceContentDeleteById(1) == static_cast(0x809D5002u)); return 0; } diff --git a/core/libs/tests/GuestContentExport.cpp b/core/libs/tests/GuestContentExport.cpp index 40c6aa941d..07fa418b6d 100644 --- a/core/libs/tests/GuestContentExport.cpp +++ b/core/libs/tests/GuestContentExport.cpp @@ -1,31 +1,10 @@ +#include "prx/libSceContentExport/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" int APS5_VABI sceContentExportAddToFavorites(void); -extern "C" int APS5_VABI sceContentExportDataProvideFunction(void); -extern "C" int APS5_VABI sceContentExportFree(void); -extern "C" int APS5_VABI sceContentExportFrom(void); -extern "C" int APS5_VABI sceContentExportFromFileForSf(void); -extern "C" int APS5_VABI sceContentExportFromFileWithContentIdList(void); -extern "C" int APS5_VABI sceContentExportFromFileWithTitleIdList(void); -extern "C" int APS5_VABI sceContentExportInit(void); -extern "C" int APS5_VABI sceContentExportMalloc(void); -extern "C" int APS5_VABI sceContentExportRemoveFromFavorites(void); -extern "C" int APS5_VABI sceContentExportRenameContent(void); -extern "C" int APS5_VABI sceContentExportValidateContents(void); - -extern "C" int APS5_VABI sceContentExportInit2(const ContentExportInitParam2* init_param); -extern "C" int APS5_VABI sceContentExportTerm(void); -extern "C" int APS5_VABI sceContentExportStart(void); -extern "C" int APS5_VABI sceContentExportFinish(std::int32_t expId); -extern "C" int APS5_VABI sceContentExportGetProgress(std::int32_t expId); -extern "C" int APS5_VABI sceContentExportCancel(std::int32_t expId); -extern "C" int APS5_VABI sceContentExportFromDataWithThumbnail(std::int32_t expId, const ContentExportParam* param, const ContentExportCallbackParam* contentCallbackParam, - const char* thumbnailType, const ContentExportCallbackParam* thumbnailCallbackParam, char* path, std::size_t pathBufferLength); - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -48,6 +27,7 @@ void Export(std::int32_t expId) { ContentExportCallbackParam content{}; ContentExportCallbackParam thumbnail{}; sceContentExportFromDataWithThumbnail(expId, ¶m, &content, "image/png", &thumbnail, path, sizeof(path)); + } } @@ -55,8 +35,8 @@ void Export(std::int32_t expId) { int main() { ContentExportInitParam2 param{&allocator, &allocator, nullptr, 0x10000, 0, 0}; Require(Throws([] { sceContentExportInit2(nullptr); })); - Require(Throws([] { sceContentExportTerm(); })); - Require(Throws([] { sceContentExportStart(); })); + Require(sceContentExportTerm() == static_cast(0x809D3004u)); + Require(sceContentExportStart() == static_cast(0x809D3004u)); Require(Throws([] { sceContentExportFinish(1); })); ContentExportInitParam2 missing = param; missing.free_func = nullptr; @@ -67,20 +47,20 @@ int main() { Require(sceContentExportInit2(¶m) == 0); Require(Throws([&] { sceContentExportInit2(¶m); })); Require(sceContentExportTerm() == 0); - Require(Throws([] { sceContentExportTerm(); })); + Require(sceContentExportTerm() == static_cast(0x809D3004u)); Require(sceContentExportInit2(¶m) == 0); std::set sessions; for (int i = 0; i < 10; ++i) { int session = sceContentExportStart(); - Require(session > 0); + Require(session == i); Require(sessions.insert(session).second); } - Require(Throws([] { sceContentExportStart(); })); - Require(Throws([] { sceContentExportTerm(); })); + Require(sceContentExportStart() == static_cast(0x809D3018u)); + Require(sceContentExportTerm() == static_cast(0x809D3003u)); int first = *sessions.begin(); Require(Throws([&] { sceContentExportGetProgress(first); })); - Require(Throws([&] { sceContentExportCancel(first); })); + Require(sceContentExportCancel(first) == static_cast(0x809D3002u)); Require(Throws([&] { Export(first); })); Require(path[0] == 0); Require(sceContentExportFinish(first) == 0); @@ -92,23 +72,28 @@ int main() { Require(Throws([] { sceContentExportFinish(-1); })); sessions.erase(first); int reopened = sceContentExportStart(); - Require(reopened > 0 && reopened != first); + Require(reopened == first); Require(sessions.insert(reopened).second); + Require(sceContentExportStart() == static_cast(0x809D3018u)); + Require(sceContentExportFinish(7) == 0); + Require(sceContentExportFinish(3) == 0); + Require(sceContentExportStart() == 3); + Require(sceContentExportStart() == 7); for (int session : sessions) Require(sceContentExportFinish(session) == 0); Require(sceContentExportTerm() == 0); - Require(Throws([] { sceContentExportStart(); })); + Require(sceContentExportStart() == static_cast(0x809D3004u)); Require(Throws([&] { sceContentExportFinish(reopened); })); - Require(Throws([] { sceContentExportAddToFavorites(); })); + Require(Throws([] { sceContentExportAddToFavorites(0); })); Require(Throws([] { sceContentExportDataProvideFunction(); })); Require(Throws([] { sceContentExportFree(); })); Require(Throws([] { sceContentExportFrom(); })); - Require(Throws([] { sceContentExportFromFileForSf(); })); - Require(Throws([] { sceContentExportFromFileWithContentIdList(); })); - Require(Throws([] { sceContentExportFromFileWithTitleIdList(); })); - Require(Throws([] { sceContentExportInit(); })); + Require(Throws([] { sceContentExportFromFileForSf(0, nullptr, nullptr, nullptr, nullptr, nullptr, 0); })); + Require(Throws([] { sceContentExportFromFileWithContentIdList(0, nullptr, nullptr, nullptr, nullptr, 0, nullptr, 0); })); + Require(Throws([] { sceContentExportFromFileWithTitleIdList(0, nullptr, nullptr, nullptr, nullptr, nullptr, 0); })); + Require(Throws([] { sceContentExportInit(nullptr); })); Require(Throws([] { sceContentExportMalloc(); })); - Require(Throws([] { sceContentExportRemoveFromFavorites(); })); - Require(Throws([] { sceContentExportRenameContent(); })); - Require(Throws([] { sceContentExportValidateContents(); })); + Require(Throws([] { sceContentExportRemoveFromFavorites(0); })); + Require(Throws([] { sceContentExportRenameContent(0, nullptr); })); + Require(Throws([] { sceContentExportValidateContents(nullptr, 0); })); } diff --git a/core/libs/tests/GuestContentSearch.cpp b/core/libs/tests/GuestContentSearch.cpp index 2447383112..c316de1c76 100644 --- a/core/libs/tests/GuestContentSearch.cpp +++ b/core/libs/tests/GuestContentSearch.cpp @@ -1,26 +1,26 @@ +#include "prx/libSceContentSearch/include/Api.hpp" #include "SceTypes.hpp" #include #include +#ifdef __APPLE__ +#include +#endif #include -extern "C" { -int APS5_VABI sceContentSearchInit(const ContentSearchInitParam* init_param); -int APS5_VABI sceContentSearchTerm(void); -int APS5_VABI sceContentSearchGetContentLastUpdateId(std::int64_t* lastUpdateId); -int APS5_VABI sceContentSearchGetNumOfContent(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, std::int64_t* numOfContent, std::int64_t* lastUpdateId); -int APS5_VABI sceContentSearchGetTotalContentSize(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, std::int64_t* size, std::int64_t* lastUpdateId); -int APS5_VABI sceContentSearchSearchContent(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::uint32_t offset, std::uint32_t limit, std::int64_t* numOfContent, void* infos, std::int64_t* lastUpdateId); -int APS5_VABI sceContentSearchOpenMetadata(const char* filePath, std::int32_t* metadataId); -int APS5_VABI sceContentSearchOpenMetadataByContentId(std::int64_t contentId, std::int32_t* metadataId); -int APS5_VABI sceContentSearchCloseMetadata(std::int32_t metadataId); -int APS5_VABI sceContentSearchGetMetadataFieldInfo(std::int32_t metadataId, const char* field, std::int32_t* metadataType, std::int32_t* size); -int APS5_VABI sceContentSearchGetMetadataValue(std::int32_t metadataId, const char* field, void* value); -} - namespace { void Require(bool value) { if (!value) std::abort(); } +template +bool ThrowsRuntimeError(TAction action) { + try { + action(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + template bool Throws(TAction action) { try { @@ -32,6 +32,8 @@ bool Throws(TAction action) { } constexpr int ErrorNotInitialized = static_cast(0x809D1001u); +constexpr int ErrorInvalidArgument = static_cast(0x809D1003u); +constexpr int ErrorAlreadyInitialized = static_cast(0x809D1002u); constexpr int ErrorLimitTooBig = static_cast(0x809D100Au); struct MetadataValue { @@ -66,7 +68,16 @@ int main() { Require(sceContentSearchCloseMetadata(1) == ErrorNotInitialized); Require(count == -1 && size == -1 && updateId == -1 && metadataId == -1); + Require(sceContentSearchTerm() == ErrorNotInitialized); + Require(sceContentSearchInit(nullptr) == ErrorInvalidArgument); + for (std::size_t memorySize : {std::size_t{0}, std::size_t{1}, std::size_t{16383}, param.memory_size - 1}) { + ContentSearchInitParam invalidParam{memorySize}; + Require(sceContentSearchInit(&invalidParam) == ErrorInvalidArgument); + Require(sceContentSearchTerm() == ErrorNotInitialized); + } Require(sceContentSearchInit(¶m) == 0); + Require(sceContentSearchInit(nullptr) == ErrorAlreadyInitialized); + Require(sceContentSearchInit(¶m) == ErrorAlreadyInitialized); Require(sceContentSearchGetContentLastUpdateId(&updateId) == 0); Require(updateId == 0); @@ -89,11 +100,11 @@ int main() { Require(sceContentSearchSearchContent(&columnSet, 1, &orderBy, 1, 0, 93, &count, infos, &updateId) == ErrorLimitTooBig); Require(count == -1 && updateId == -1); - Require(Throws([&] { sceContentSearchGetContentLastUpdateId(nullptr); })); - Require(Throws([&] { sceContentSearchGetNumOfContent(&columnSet, 1, nullptr, &updateId); })); - Require(Throws([&] { sceContentSearchGetNumOfContent(&columnSet, 1, &count, nullptr); })); - Require(Throws([&] { sceContentSearchGetTotalContentSize(nullptr, 1, &size, &updateId); })); - Require(Throws([&] { sceContentSearchSearchContent(&columnSet, 1, &orderBy, 1, 0, 2, &count, nullptr, &updateId); })); + Require(sceContentSearchGetContentLastUpdateId(nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfContent(&columnSet, 1, nullptr, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfContent(&columnSet, 1, &count, nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetTotalContentSize(nullptr, 1, &size, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchSearchContent(&columnSet, 1, &orderBy, 1, 0, 2, &count, nullptr, &updateId) == ErrorInvalidArgument); ContentSearchContentColumn unknownColumn{8, 0, &photo, 2}; ContentSearchContentColumnSet unknownColumnSet{&unknownColumn, 1, 2}; Require(Throws([&] { sceContentSearchGetNumOfContent(&unknownColumnSet, 1, &count, &updateId); })); @@ -106,11 +117,18 @@ int main() { ContentSearchContentOrderByCondition unknownSort{3, 0}; Require(Throws([&] { sceContentSearchSearchContent(&columnSet, 1, &unknownSort, 1, 0, 2, &count, infos, &updateId); })); Require(count == -1 && updateId == -1); + Require(sceContentSearchSearchContent(nullptr, 0, nullptr, 0, 0, 0, nullptr, nullptr, nullptr) == ErrorInvalidArgument); + Require(sceContentSearchSearchContent(nullptr, 0, nullptr, 0, 0, 93, nullptr, nullptr, nullptr) == ErrorLimitTooBig); + Require(sceContentSearchSearchContent(&columnSet, 0, nullptr, 0, 0, 93, nullptr, nullptr, nullptr) == ErrorInvalidArgument); + Require(sceContentSearchSearchContent(nullptr, 0, &orderBy, 0, 0, 92, &count, infos, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfContent(&columnSet, 0, &count, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchGetTotalContentSize(&columnSet, 0, &size, &updateId) == ErrorInvalidArgument); + Require(count == -1 && updateId == -1); Require(Throws([&] { sceContentSearchOpenMetadata("/data/photo.jpg", &metadataId); })); Require(Throws([&] { sceContentSearchOpenMetadataByContentId(1, &metadataId); })); Require(metadataId == -1); - Require(Throws([] { sceContentSearchCloseMetadata(1); })); + Require(sceContentSearchCloseMetadata(1) == ErrorInvalidArgument); Require(Throws([&] { sceContentSearchGetMetadataFieldInfo(1, "title", &metadataType, &fieldSize); })); Require(Throws([&] { sceContentSearchGetMetadataValue(1, "title", &value); })); Require(metadataType == -1 && fieldSize == -1 && value.size == -1); @@ -118,4 +136,18 @@ int main() { Require(sceContentSearchTerm() == 0); Require(sceContentSearchGetContentLastUpdateId(&updateId) == ErrorNotInitialized); Require(updateId == -1); + constexpr int binderResult = static_cast(0x809D000F); + Require(sceContentBinderInitialize() == binderResult); + Require(sceContentBinderStart() == binderResult); + Require(sceContentBinderReset() == binderResult); + Require(sceContentBinderIsUpdated() == binderResult); + Require(sceContentBinderGetNumOfDifference() == binderResult); + Require(sceContentBinderSearchDifference() == binderResult); + Require(sceContentBinderEnd() == binderResult); + Require(sceContentBinderTerminate() == binderResult); + + Require(ThrowsRuntimeError([] { sceContentSearchGetApplicationLastUpdateId(); })); + Require(ThrowsRuntimeError([] { sceContentSearchGetMyApplicationIndex(); })); + Require(ThrowsRuntimeError([] { sceContentSearchGetNumOfApplication(); })); + Require(ThrowsRuntimeError([] { sceContentSearchSearchApplication(); })); } diff --git a/core/libs/tests/GuestConvertKeycode.cpp b/core/libs/tests/GuestConvertKeycode.cpp index 0e7095e097..d2d3cb463f 100644 --- a/core/libs/tests/GuestConvertKeycode.cpp +++ b/core/libs/tests/GuestConvertKeycode.cpp @@ -1,59 +1,217 @@ +#include "prx/libSceConvertKeycode/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "ProcessParametersFixture.hpp" +#include "prx/libSceConvertKeycode/ImeKeyboardType.hpp" +#include +#include +#include #include #include #include - -extern "C" { -int APS5_VABI sceConvertKeycodeGetImeKeyboardType(std::int32_t, std::uint32_t*); -int APS5_VABI sceConvertKeycodeGetCharacter(std::uint16_t, std::uint32_t, std::uint32_t, char16_t*); -} +#include +#include +#include static void Require(bool value) { if (!value) std::abort(); } +static_assert(ImeKeyboardType::LookUp(INT32_MIN, 0, 0) == 0); +static_assert(ImeKeyboardType::LookUp(INT32_MAX, 0, 0) == 0); + +static void SetLanguage(const char* value) { +#ifdef _WIN32 + Require(_putenv_s("ANYPS5_LANGUAGE", value != nullptr ? value : "") == 0); +#else + Require((value != nullptr ? ::setenv("ANYPS5_LANGUAGE", value, 1) : ::unsetenv("ANYPS5_LANGUAGE")) == 0); +#endif +} + +namespace { + +struct FakeUser { + int keyboardResult = 0; + std::int32_t keyboardType = 0; + int japaneseResult = 0; + std::int32_t japaneseVariant = 0; + int chineseResult = 0; + std::int32_t chineseVariant = 0; + int calls = 0; +}; + +int Resolve(const FakeUser& user, std::uint32_t* type, std::uint32_t sdkVersion = 0xFFFFFFFFu, bool ps4Client = false) { + return ImeKeyboardType::Resolve( + 7, type, sdkVersion, ps4Client, + [&](std::int32_t id, std::int32_t* value) { Require(id == 7); *value = user.keyboardType; return user.keyboardResult; }, + [&](std::int32_t id, std::int32_t* value) { Require(id == 7); *value = user.japaneseVariant; return user.japaneseResult; }, + [&](std::int32_t id, std::int32_t* value) { Require(id == 7); *value = user.chineseVariant; return user.chineseResult; }); +} + +std::uint32_t Map(std::int32_t keyboardType, std::int32_t japanese = 0, std::int32_t chinese = 0, std::uint32_t sdkVersion = 0xFFFFFFFFu, bool ps4Client = false) { + FakeUser user; + user.keyboardType = keyboardType; + user.japaneseVariant = japanese; + user.chineseVariant = chinese; + std::uint32_t type = 0xFFFFFFFFu; + Require(Resolve(user, &type, sdkVersion, ps4Client) == 0); + return type; +} + +void RequireImeKeyboardTypeMapping() { + // The owner's table: UserService keyboard type -> IME keyboard type. + constexpr std::pair table[] = { + {16, 1}, {17, 2}, {18, 3}, {19, 5}, {20, 4}, {21, 6}, {22, 7}, {23, 8}, {24, 10}, {25, 11}, {26, 9}, {27, 12}, + {28, 13}, {29, 14}, {30, 15}, {31, 16}, {32, 17}, {33, 18}, {34, 19}, {35, 20}, {36, 21}, {38, 24}, {40, 25}, + {41, 30}, {42, 31}, {43, 33}, {44, 32}, {45, 34}, {46, 35}, {47, 36}, {48, 37}, {49, 38}}; + for (const auto& [keyboard, ime] : table) { + // 49 -> 38 is compatibility-sensitive; the table row is checked on a new SDK, the rule separately below. + Require(Map(keyboard) == static_cast(ime)); + } + // Unmatched keyboard types succeed with IME type 0 (including 0, the gaps, and out-of-range values). + for (int keyboard : {-1, 0, 1, 15, 50, 51, 1000, 0x7FFFFFFF, static_cast(0x80000000u)}) Require(Map(keyboard) == 0); + + // Type 37: Japanese input variant; anything else is unmatched. + Require(Map(37, 0) == 22 && Map(37, 1) == 23); + Require(Map(37, 2) == 0 && Map(37, -1) == 0); + Require(Map(37, 0, 4) == 22); // the Traditional Chinese variant is ignored for 37 + // Type 39: Traditional Chinese input variant. + Require(Map(39, 0, 0) == 26 && Map(39, 0, 1) == 27 && Map(39, 0, 2) == 28 && Map(39, 0, 3) == 29 && Map(39, 0, 4) == 39); + Require(Map(39, 0, 5) == 0 && Map(39, 0, -1) == 0); + Require(Map(39, 1, 0) == 26); // the Japanese variant is ignored for 39 + + // Variant query failure falls back to variant 0 and still succeeds; other types never query the variants. + FakeUser user; + std::uint32_t type = 0xFFFFFFFFu; + user.keyboardType = 37; user.japaneseResult = static_cast(0x80960001u); user.japaneseVariant = 1; + Require(Resolve(user, &type) == 0 && type == 22); + user = {}; + type = 0xFFFFFFFFu; + user.keyboardType = 39; user.chineseResult = static_cast(0x80960001u); user.chineseVariant = 4; + Require(Resolve(user, &type) == 0 && type == 26); + user = {}; + type = 0xFFFFFFFFu; + user.keyboardType = 20; user.japaneseResult = 1; user.chineseResult = 1; + Require(Resolve(user, &type) == 0 && type == 4); + + // SDK compatibility, applied after the lookup. IME 38 (from 49): PS5 SDK < 0x06000000, PS4 SDK < 0x10000000 -> 4. + Require(Map(49, 0, 0, 0x05FFFFFFu, false) == 4); + Require(Map(49, 0, 0, 0x06000000u, false) == 38); + Require(Map(49, 0, 0, 0x0FFFFFFFu, true) == 4); + Require(Map(49, 0, 0, 0x10000000u, true) == 38); + // IME 39 (from 39 variant 4): PS5 SDK < 0x08000000 and every PS4 client -> 29. + Require(Map(39, 0, 4, 0x07FFFFFFu, false) == 29); + Require(Map(39, 0, 4, 0x08000000u, false) == 39); + Require(Map(39, 0, 4, 0xFFFFFFFFu, true) == 29); + // Other IME types are never rewritten. + Require(Map(20, 0, 0, 0, false) == 4 && Map(36, 0, 0, 0, true) == 21 && Map(39, 0, 3, 0, true) == 29); + + // Errors leave the output untouched. + type = 0x1234u; + user = {}; + Require(ImeKeyboardType::Resolve(7, nullptr, 0, false, [](std::int32_t, std::int32_t*) { Require(false); return 0; }, [](std::int32_t, std::int32_t*) { return 0; }, [](std::int32_t, std::int32_t*) { return 0; }) == static_cast(0x80BC0031u)); + user.keyboardResult = static_cast(0x80960105u); + Require(Resolve(user, &type) == static_cast(0x80BC0010u) && type == 0x1234u); + for (int other : {1, -1, static_cast(0x80960002u), static_cast(0x80960005u), static_cast(0x80BC0010u)}) { + user.keyboardResult = other; + Require(Resolve(user, &type) == static_cast(0x80BC00FFu) && type == 0x1234u); + } +} + +} // namespace + int main() { + static std::array process{}; + Require(ProcessParametersFixture::Install(process.data(), process.size())); + constexpr std::uint32_t sdkVersion = 0x09000000u; + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); constexpr int invalidAddress = static_cast(0x80BC0031u); constexpr int invalidUserId = static_cast(0x80BC0010u); Require(sceConvertKeycodeGetImeKeyboardType(0, nullptr) == invalidAddress); Require(sceConvertKeycodeGetImeKeyboardType(-1, nullptr) == invalidAddress); + std::uint16_t virtualKey = 0xABCD; + Require(sceConvertKeycodeGetVirtualKeycode(4, 0, nullptr) == invalidAddress); + Require(sceConvertKeycodeGetVirtualKeycode(4, 0, &virtualKey) == static_cast(0x80BC001Du)); + Require(virtualKey == 0xABCD); + Require(sceConvertKeycodeGetVirtualKeycode(4, 40, &virtualKey) == static_cast(0x80BC001Du)); + Require(sceConvertKeycodeGetVirtualKeycode(4, 4, &virtualKey) == 0 && virtualKey == 65); + Require(sceConvertKeycodeGetVirtualKeycode(4, 9, &virtualKey) == 0 && virtualKey == 81); + Require(sceConvertKeycodeGetVirtualKeycode(100, 4, &virtualKey) == 0 && virtualKey == 255); + Require(sceConvertKeycodeGetVirtualKeycode(136, 24, &virtualKey) == 0 && virtualKey == 21); + Require(sceConvertKeycodeGetVirtualKeycode(65535, 4, &virtualKey) == 0 && virtualKey == 255); + for (std::uint32_t layout : {2u, 3u, 12u, 31u, 39u}) { + virtualKey = 0xABCD; + bool rejected = false; + try { + sceConvertKeycodeGetVirtualKeycode(28, layout, &virtualKey); + + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected && virtualKey == 0xABCD); + } + + // The exported function with the real UserService: unknown users fail, the initial user reports the US-English default (keyboard type 20 -> IME 4). std::uint32_t type = 0xFFFFFFFFu; Require(sceConvertKeycodeGetImeKeyboardType(-1, &type) == invalidUserId); + Require(sceConvertKeycodeGetImeKeyboardType(0, &type) == invalidUserId); Require(type == 0xFFFFFFFFu); + SetLanguage(nullptr); + Require(sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == 4); + constexpr std::uint32_t imeTypeByLanguage[] = {22, 4, 9, 6, 2, 13, 14, 18, 19, 24, 26, 25, 8, 20, 1, 15, 16, 17, 5, 21, 7, 30, 11, 32, 37, 33, 36, 31, 35, 34}; + for (int language = 0; language < 30; ++language) { + SetLanguage(std::to_string(language).c_str()); + type = 0xFFFFFFFFu; + Require(sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == imeTypeByLanguage[language]); + } + SetLanguage("30"); + Require(sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == 4); + SetLanguage(nullptr); - Require(sceConvertKeycodeGetImeKeyboardType(0, &type) == 0); - Require(type == 0); + Require(ImeKeyboardType::IsPs4TitleId("CUSA00000")); + Require(ImeKeyboardType::IsPs4TitleId("CUSA")); + Require(!ImeKeyboardType::IsPs4TitleId("PPSA00000")); + Require(!ImeKeyboardType::IsPs4TitleId("")); + Require(!ImeKeyboardType::IsPs4TitleId("CUS")); + Require(!ImeKeyboardType::IsPs4TitleId("cusa00000")); + Require(!ImeKeyboardType::IsPs4TitleId("XCUSA0000")); + Require(!ImeKeyboardType::IsPs4TitleId("HOMEBREW1")); - type = 0xFFFFFFFFu; - Require(sceConvertKeycodeGetImeKeyboardType(1, &type) == 0); - Require(type == 0); - - Require(sceConvertKeycodeGetCharacter(0x04, 0, 0, nullptr) == invalidAddress); + RequireImeKeyboardTypeMapping(); + Require(sceConvertKeycodeGetCharacter(4, 0, 0, nullptr) == invalidAddress); + char16_t character = u'?'; + Require(sceConvertKeycodeGetCharacter(4, 0, 0, &character) == static_cast(0x80BC001Du)); + Require(character == u'?'); + Require(sceConvertKeycodeGetCharacter(4, 0, 40, &character) == static_cast(0x80BC001Du)); for (std::uint32_t state : {0u, 0x4000000u}) { - char16_t character[3] = {u'?', 0, 0}; - Require(sceConvertKeycodeGetCharacter(0x04, state, 0, character) == 0); - Require(character[0] == u'a'); - Require(sceConvertKeycodeGetCharacter(0x1E, state, 0, character) == 0); - Require(character[0] == u'1'); - Require(sceConvertKeycodeGetCharacter(0x3A, state, 0, character) == 0); - Require(character[0] == 0); - Require(sceConvertKeycodeGetCharacter(0x64, state, 0, character) == 0); - Require(character[0] == u'\\'); - Require(sceConvertKeycodeGetCharacter(0x32, state, 0, character) == 0); - Require(character[0] == 0); - Require(sceConvertKeycodeGetCharacter(0x27, state, 0, character) == 0); - Require(character[0] == u'0'); - Require(sceConvertKeycodeGetCharacter(0x2C, state, 0, character) == 0); - Require(character[0] == u' '); - Require(sceConvertKeycodeGetCharacter(0x2D, state, 0, character) == 0); - Require(character[0] == u'-'); - Require(sceConvertKeycodeGetCharacter(0x54, state, 0, character) == 0); - Require(character[0] == u'/'); - Require(sceConvertKeycodeGetCharacter(0x59, state, 0, character) == 0); - Require(character[0] == u'1'); - Require(sceConvertKeycodeGetCharacter(0x63, state, 0, character) == 0); - Require(character[0] == u'.'); + Require(sceConvertKeycodeGetCharacter(4, state, 4, &character) == 0 && character == u'a'); + Require(sceConvertKeycodeGetCharacter(30, state, 4, &character) == 0 && character == u'1'); + Require(sceConvertKeycodeGetCharacter(58, state, 4, &character) == 0 && character == 0); + Require(sceConvertKeycodeGetCharacter(65535, state, 4, &character) == 0 && character == 0); + } + Require(sceConvertKeycodeGetCharacter(4, 0x200, 4, &character) == 0 && character == u'A'); + Require(sceConvertKeycodeGetCharacter(4, 0x20000, 4, &character) == 0 && character == u'A'); + Require(sceConvertKeycodeGetCharacter(4, 0x20200, 4, &character) == 0 && character == u'a'); + Require(sceConvertKeycodeGetCharacter(30, 0x200, 4, &character) == 0 && character == u'!'); + Require(sceConvertKeycodeGetCharacter(89, 0, 4, &character) == 0 && character == 0); + Require(sceConvertKeycodeGetCharacter(89, 0x10000, 4, &character) == 0 && character == u'1'); + Require(sceConvertKeycodeGetCharacter(89, 0x10200, 4, &character) == 0 && character == u'1'); + Require(sceConvertKeycodeGetCharacter(4, 0, 9, &character) == 0 && character == u'q'); + Require(sceConvertKeycodeGetCharacter(28, 0, 2, &character) == 0 && character == u'z'); + Require(sceConvertKeycodeGetCharacter(20, 0x4000, 2, &character) == 0 && character == u'@'); + Require(sceConvertKeycodeGetCharacter(47, 0x20000, 3, &character) == 0 && character == 220); + Require(sceConvertKeycodeGetCharacter(47, 0x20200, 3, &character) == 0 && character == 200); + Require(sceConvertKeycodeGetCharacter(4, 0x4000000, 24, &character) == 0 && character == 0x3141); + Require(sceConvertKeycodeGetCharacter(5, 0x4000000, 30, &character) == 0 && character == 1575); + Require(sceConvertKeycodeGetCharacter(5, 0x4000200, 30, &character) == 0 && character == 1570); + Require(sceConvertKeycodeGetCharacter(30, 0x4000300, 30, &character) == 0 && character == 0x200D); + Require(sceConvertKeycodeGetCharacter(31, 0x4000300, 30, &character) == 0 && character == 0x200C); + Require(sceConvertKeycodeGetCharacter(32, 0x4000300, 30, &character) == 0 && character == 0x200E); + Require(sceConvertKeycodeGetCharacter(33, 0x4000300, 30, &character) == 0 && character == 0x200F); + for (std::uint32_t layout = 1; layout <= 30; ++layout) { + for (std::uint16_t key : {83, 224, 225, 226, 227, 228, 229, 230, 231}) { + Require(sceConvertKeycodeGetCharacter(key, 0xFFFFFFFFu, layout, &character) == 0 && character == 0); + } } return 0; } diff --git a/core/libs/tests/GuestConvertKeycodeTitle.cpp b/core/libs/tests/GuestConvertKeycodeTitle.cpp new file mode 100644 index 0000000000..21ccc29fa5 --- /dev/null +++ b/core/libs/tests/GuestConvertKeycodeTitle.cpp @@ -0,0 +1,28 @@ +#include "prx/libSceConvertKeycode/include/Api.hpp" +#include "SceTypes.hpp" +#include "ProcessParametersFixture.hpp" +#include +#include +#include +#include +#include + +int main() { +#ifdef _WIN32 + if (_putenv_s("ANYPS5_LANGUAGE", "") != 0) return 2; +#else + if (::unsetenv("ANYPS5_LANGUAGE") != 0) return 2; +#endif + static std::array process{}; + constexpr std::uint32_t sdkVersion = 0x09000000u; + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + if (!ProcessParametersFixture::Install(process.data(), process.size())) return 2; + std::uint32_t type = 0xFFFFFFFFu; + std::uint16_t virtualKey = 0; + 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 (sceConvertKeycodeGetCharacter(0, 0, 0, nullptr) != static_cast(0x80BC0031u)) return 1; + if (sceConvertKeycodeGetCharacter(0, 0, 0, &character) != static_cast(0x80BC001Du)) return 1; + return sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == 4 ? 0 : 1; +} diff --git a/core/libs/tests/GuestCoredumpAttach.cpp b/core/libs/tests/GuestCoredumpAttach.cpp index 43e32238e0..e1948026e4 100644 --- a/core/libs/tests/GuestCoredumpAttach.cpp +++ b/core/libs/tests/GuestCoredumpAttach.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "ProcessParametersFixture.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -7,19 +9,8 @@ #include #include -extern "C" { -int APS5_VABI sceCoredumpRegisterCoredumpHandler(uint64_t handler, size_t stackSize, uint64_t context); -int APS5_VABI sceCoredumpUnregisterCoredumpHandler(void); -int APS5_VABI sceCoredumpAttachMemoryRegion(uint32_t userValue, const void* mem, size_t size); -int APS5_VABI sceCoredumpAttachUserMemoryFile(uint32_t userValue, const void* mem, size_t size); -int APS5_VABI sceCoredumpGetStopInfoCpu(void* info, size_t size); -int APS5_VABI sceCoredumpGetThreadContextInfo(void* threadContextInfo, size_t threadContextInfoSize); -int APS5_VABI sceCoredumpGetStopInfoGpu_Agc(void* info, size_t size); -int APS5_VABI sceCoredumpWriteUserString(uint32_t first, const char* text, int third); -int APS5_VABI sceCoredumpConfigDumpMode(int mode); -} - namespace { +alignas(16) std::array process{}; constexpr int InvalidArgument = static_cast(0x81180000u); constexpr int NotInCoredumpHandler = static_cast(0x81180003u); constexpr uint32_t UserFileTruncateIfExceedLimit = 0x80000000u; @@ -42,6 +33,13 @@ bool Throws(TAttach attach, const char* function) { } int main() { + const std::uint64_t processSize = process.size(); + const std::uint32_t processMagic = 0x4942524f; + const std::uint32_t sdkVersion = 0x06500000; + std::memcpy(process.data(), &processSize, sizeof(processSize)); + std::memcpy(process.data() + 8, &processMagic, sizeof(processMagic)); + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + Require(ProcessParametersFixture::Install(process.data(), process.size())); alignas(uint64_t) std::array region{}; for (size_t index = 0; index < region.size(); ++index) region[index] = static_cast(index * 7 + 3); const auto original = region; @@ -52,6 +50,7 @@ int main() { Require(sceCoredumpAttachMemoryRegion(1, region.data(), region.size()) == NotInCoredumpHandler); Require(sceCoredumpAttachUserMemoryFile(UserFileTruncateIfExceedLimit | 2, region.data(), region.size()) == NotInCoredumpHandler); Require(sceCoredumpGetStopInfoCpu(region.data() + 16, 16) == NotInCoredumpHandler); + Require(sceCoredumpGetStopInfoCpu(region.data() + 16, 8) == NotInCoredumpHandler); Require(sceCoredumpGetThreadContextInfo(context.data() + 8, 144) == NotInCoredumpHandler); Require(region == original && context == originalContext); @@ -70,23 +69,30 @@ int main() { Require(region == original && context == originalContext); Require(!handlerRan); - Require(Throws([&] { sceCoredumpAttachMemoryRegion(1, nullptr, region.size()); }, "sceCoredumpAttachMemoryRegion")); - Require(Throws([&] { sceCoredumpAttachMemoryRegion(1, region.data(), 0); }, "sceCoredumpAttachMemoryRegion")); - Require(Throws([&] { sceCoredumpAttachMemoryRegion(1, region.data(), SIZE_MAX); }, "sceCoredumpAttachMemoryRegion")); - Require(Throws([&] { sceCoredumpAttachUserMemoryFile(1, nullptr, region.size()); }, "sceCoredumpAttachUserMemoryFile")); - Require(Throws([&] { sceCoredumpAttachUserMemoryFile(1, region.data(), 0); }, "sceCoredumpAttachUserMemoryFile")); - Require(Throws([&] { sceCoredumpAttachUserMemoryFile(1, region.data(), SIZE_MAX); }, "sceCoredumpAttachUserMemoryFile")); - Require(Throws([&] { sceCoredumpGetStopInfoCpu(nullptr, 16); }, "sceCoredumpGetStopInfoCpu")); - Require(Throws([&] { sceCoredumpGetStopInfoCpu(region.data() + 1, 16); }, "sceCoredumpGetStopInfoCpu")); - Require(Throws([&] { sceCoredumpGetStopInfoCpu(reinterpret_cast(UINTPTR_MAX - 7u), 16); }, "sceCoredumpGetStopInfoCpu")); + Require(sceCoredumpAttachMemoryRegion(1, nullptr, region.size()) == NotInCoredumpHandler); + Require(sceCoredumpAttachMemoryRegion(1, region.data(), 0) == NotInCoredumpHandler); + Require(sceCoredumpAttachMemoryRegion(1, region.data(), -1) == 0); + Require(sceCoredumpAttachUserMemoryFile(1, nullptr, region.size()) == NotInCoredumpHandler); + Require(sceCoredumpAttachUserMemoryFile(1, region.data(), 0) == NotInCoredumpHandler); + Require(sceCoredumpAttachUserMemoryFile(1, region.data(), -1) == 0); + Require(sceCoredumpGetStopInfoCpu(nullptr, 16) == InvalidArgument); + Require(sceCoredumpGetStopInfoCpu(region.data() + 1, 16) == NotInCoredumpHandler); + Require(sceCoredumpGetStopInfoCpu(reinterpret_cast(UINTPTR_MAX - 7u), 16) == NotInCoredumpHandler); for (const size_t size : {size_t{0}, size_t{1}, size_t{15}, size_t{17}, SIZE_MAX}) - Require(Throws([&] { sceCoredumpGetStopInfoCpu(region.data() + 16, size); }, "sceCoredumpGetStopInfoCpu")); - Require(Throws([&] { sceCoredumpGetThreadContextInfo(nullptr, 144); }, "sceCoredumpGetThreadContextInfo")); - Require(Throws([&] { sceCoredumpGetThreadContextInfo(context.data() + 1, 144); }, "sceCoredumpGetThreadContextInfo")); - Require(Throws([&] { sceCoredumpGetThreadContextInfo(reinterpret_cast(UINTPTR_MAX - 7u), 144); }, "sceCoredumpGetThreadContextInfo")); + Require(sceCoredumpGetStopInfoCpu(region.data() + 16, size) == InvalidArgument); + Require(sceCoredumpGetThreadContextInfo(nullptr, 144) == InvalidArgument); + Require(sceCoredumpGetThreadContextInfo(context.data() + 1, 144) == NotInCoredumpHandler); + Require(sceCoredumpGetThreadContextInfo(reinterpret_cast(UINTPTR_MAX - 7u), 144) == NotInCoredumpHandler); for (const size_t size : {size_t{0}, size_t{1}, size_t{143}, size_t{145}, SIZE_MAX}) - Require(Throws([&] { sceCoredumpGetThreadContextInfo(context.data(), size); }, "sceCoredumpGetThreadContextInfo")); + Require(sceCoredumpGetThreadContextInfo(context.data(), size) == InvalidArgument); + for (const auto length : {INT32_MIN, -1, 0, 1, INT32_MAX}) { + const int expected = length < 0 ? 0 : NotInCoredumpHandler; + Require(sceCoredumpAttachMemoryRegion(0, nullptr, length) == expected); + Require(sceCoredumpAttachUserMemoryFile(0, nullptr, length) == expected); + Require(sceCoredumpAttachMemoryRegion(0xffffffffu, region.data(), length) == expected); + Require(sceCoredumpAttachUserMemoryFile(0xffffffffu, region.data(), length) == expected); + } Require(sceCoredumpUnregisterCoredumpHandler() == 0); Require(sceCoredumpAttachMemoryRegion(1, region.data(), region.size()) == NotInCoredumpHandler); Require(sceCoredumpAttachUserMemoryFile(1, region.data(), region.size()) == NotInCoredumpHandler); diff --git a/core/libs/tests/GuestCoredumpUserData.cpp b/core/libs/tests/GuestCoredumpUserData.cpp index 30776ebbcc..694d076666 100644 --- a/core/libs/tests/GuestCoredumpUserData.cpp +++ b/core/libs/tests/GuestCoredumpUserData.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "ProcessParametersFixture.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -7,16 +9,9 @@ #include #include -extern "C" { -int APS5_VABI sceCoredumpRegisterCoredumpHandler(uint64_t handler, size_t stackSize, uint64_t context); -int APS5_VABI sceCoredumpUnregisterCoredumpHandler(void); -int64_t APS5_VABI sceCoredumpWriteUserData(const void* data, size_t size); -void APS5_VABI sceCoredumpDebugTextOut(const char* str, int len); -int APS5_VABI sceCoredumpAttachUserFile(uint32_t userValue, const char* path); -int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(uint32_t userValue, const void* mem, size_t size, const char* name); -} - namespace { +alignas(16) std::array process{}; +constexpr int64_t InvalidArgument = static_cast(0x81180000u); constexpr int64_t NotInCoredumpHandler = static_cast(0x81180003u); bool handlerRan = false; int handlerContext = 0; @@ -55,12 +50,26 @@ void RequireAttachesRefused(const std::array& region) { } int main() { + const std::uint64_t processSize = process.size(); + const std::uint32_t processMagic = 0x4942524f; + const std::uint32_t sdkVersion = 0x06500000; + std::memcpy(process.data(), &processSize, sizeof(processSize)); + std::memcpy(process.data() + 8, &processMagic, sizeof(processMagic)); + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + Require(ProcessParametersFixture::Install(process.data(), process.size())); std::array region{}; for (size_t index = 0; index < region.size(); ++index) region[index] = static_cast(index * 5 + 1); const auto original = region; const char text[] = "coredump text\n"; + Require(sceCoredumpUnregisterCoredumpHandler() == static_cast(0x81180001u)); + Require(sceCoredumpRegisterCoredumpHandler(0, 0x4000, 0) == InvalidArgument); + for (const std::size_t stackSize : {std::size_t{0}, std::size_t{0x3fff}, std::size_t{0x20000000}, SIZE_MAX}) { + Require(sceCoredumpRegisterCoredumpHandler(reinterpret_cast(&Handler), stackSize, 0) == InvalidArgument); + } + Require(sceCoredumpAttachUserFile(0x40000000u, "") == NotInCoredumpHandler); Require(NotInCoredumpHandler < 0); + Require(sceCoredumpSetUserDataType(0) == 0); RequireWritesRefused(region); RequireAttachesRefused(region); sceCoredumpDebugTextOut(text, static_cast(sizeof(text) - 1)); @@ -69,26 +78,47 @@ int main() { sceCoredumpDebugTextOut(nullptr, 0); Require(sceCoredumpRegisterCoredumpHandler(reinterpret_cast(&Handler), 16 * 1024, reinterpret_cast(&handlerContext)) == 0); + Require(sceCoredumpRegisterCoredumpHandler(0, 0, 0) == static_cast(0x81180002u)); + Require(sceCoredumpAttachUserFile(0x40000000u, "") == InvalidArgument); + Require(sceCoredumpAttachMemoryRegionAsUserFile(0x40000000u, nullptr, -1, "") == InvalidArgument); RequireWritesRefused(region); RequireAttachesRefused(region); sceCoredumpDebugTextOut(text, static_cast(sizeof(text) - 1)); + Require(!handlerRan); - Require(Throws([&] { sceCoredumpWriteUserData(nullptr, region.size()); }, "sceCoredumpWriteUserData")); - Require(Throws([&] { sceCoredumpWriteUserData(region.data(), 0); }, "sceCoredumpWriteUserData")); - Require(Throws([&] { sceCoredumpWriteUserData(region.data(), SIZE_MAX); }, "sceCoredumpWriteUserData")); + Require(sceCoredumpWriteUserData(nullptr, region.size()) == InvalidArgument); + Require(sceCoredumpWriteUserData(nullptr, 0) == InvalidArgument); + Require(sceCoredumpWriteUserData(region.data(), 0) == NotInCoredumpHandler); + Require(sceCoredumpWriteUserData(region.data(), INT32_MAX) == NotInCoredumpHandler); + Require(sceCoredumpWriteUserData(region.data(), std::size_t{INT32_MAX} + 1) == InvalidArgument); + Require(sceCoredumpWriteUserData(region.data(), SIZE_MAX) == InvalidArgument); Require(Throws([&] { sceCoredumpDebugTextOut(nullptr, 4); }, "sceCoredumpDebugTextOut")); Require(Throws([&] { sceCoredumpDebugTextOut(text, -1); }, "sceCoredumpDebugTextOut")); - Require(Throws([&] { sceCoredumpAttachUserFile(1, nullptr); }, "sceCoredumpAttachUserFile")); - Require(Throws([&] { sceCoredumpAttachMemoryRegionAsUserFile(1, nullptr, region.size(), "a.txt"); }, "sceCoredumpAttachMemoryRegionAsUserFile")); - Require(Throws([&] { sceCoredumpAttachMemoryRegionAsUserFile(1, region.data(), 0, "a.txt"); }, "sceCoredumpAttachMemoryRegionAsUserFile")); - Require(Throws([&] { sceCoredumpAttachMemoryRegionAsUserFile(1, region.data(), SIZE_MAX, "a.txt"); }, "sceCoredumpAttachMemoryRegionAsUserFile")); - Require(Throws([&] { sceCoredumpAttachMemoryRegionAsUserFile(1, region.data(), region.size(), nullptr); }, "sceCoredumpAttachMemoryRegionAsUserFile")); + Require(sceCoredumpAttachUserFile(1, nullptr) == InvalidArgument); + Require(sceCoredumpAttachMemoryRegionAsUserFile(1, nullptr, region.size(), "a.txt") == NotInCoredumpHandler); + Require(sceCoredumpAttachMemoryRegionAsUserFile(1, region.data(), 0, "a.txt") == NotInCoredumpHandler); + Require(sceCoredumpAttachMemoryRegionAsUserFile(1, region.data(), -1, "a.txt") == 0); + Require(sceCoredumpAttachMemoryRegionAsUserFile(1, region.data(), region.size(), nullptr) == InvalidArgument); Require(sceCoredumpUnregisterCoredumpHandler() == 0); RequireWritesRefused(region); RequireAttachesRefused(region); Require(region == original); + Require(sceCoredumpUnregisterCoredumpHandler() == static_cast(0x81180001u)); + Require(sceCoredumpAttachUserFile(0x40000000u, "") == InvalidArgument); + for (const std::uint32_t version : {0x064fffffu, 0x06500000u, 0x12000000u}) { + std::memcpy(process.data() + 16, &version, sizeof(version)); + Require(sceCoredumpRegisterCoredumpHandler(reinterpret_cast(&Handler), 0x4000, 0) == 0); + for (const std::uint32_t flags : {0u, 0xffffu, 0x10000u, 0x40000000u, 0x40000001u, 0x80000000u, 0xc0000000u, UINT32_MAX}) { + const bool invalid = version >= 0x06500000 && (flags & 0xc0000000u) == 0x40000000u; + Require(sceCoredumpAttachUserFile(flags, "") == (invalid ? InvalidArgument : NotInCoredumpHandler)); + Require(sceCoredumpAttachMemoryRegionAsUserFile(flags, nullptr, 0, "") == (invalid ? InvalidArgument : NotInCoredumpHandler)); + Require(sceCoredumpAttachMemoryRegionAsUserFile(flags, nullptr, -1, "") == (invalid ? InvalidArgument : 0)); + Require(sceCoredumpAttachMemoryRegionAsUserFile(flags, nullptr, -1, nullptr) == InvalidArgument); + } + Require(sceCoredumpUnregisterCoredumpHandler() == 0); + } Require(!handlerRan); return 0; } diff --git a/core/libs/tests/GuestCpuTimeClocks.cpp b/core/libs/tests/GuestCpuTimeClocks.cpp index cb7405736e..18ae60bb87 100644 --- a/core/libs/tests/GuestCpuTimeClocks.cpp +++ b/core/libs/tests/GuestCpuTimeClocks.cpp @@ -1,19 +1,17 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" #include "SceTypes.hpp" #include #include #include extern "C" { -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); -int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res); -int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp); -int APS5_VABI sceKernelClockGetres(KernelClockid clockId, KernelTimespec* tp); + #ifdef _WIN32 int APS5_VABI ClockTimeWin32ErrorToGuestErrno_nid_no_patch(std::uint32_t error); #endif -int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); + } #ifdef _WIN32 @@ -23,7 +21,6 @@ static constexpr std::uint32_t WINDOWS_ERROR_INVALID_HANDLE = 6; #endif static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EFAULT = static_cast(0x8002000E); static constexpr int GUEST_CLOCK_MONOTONIC = 4; static constexpr int GUEST_CLOCK_THREAD_CPUTIME_ID = 14; diff --git a/core/libs/tests/GuestCredentials.cpp b/core/libs/tests/GuestCredentials.cpp index 887f69c727..c2bc00310d 100644 --- a/core/libs/tests/GuestCredentials.cpp +++ b/core/libs/tests/GuestCredentials.cpp @@ -1,26 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" { -int APS5_VABI seteuid_nid_postfix(std::uint32_t); -int APS5_VABI setegid_nid_postfix(std::uint32_t); -int APS5_VABI setuid_nid_postfix(std::uint32_t); -int APS5_VABI setgid_nid_postfix(std::uint32_t); -int APS5_VABI setreuid_nid_postfix(std::uint32_t, std::uint32_t); -int APS5_VABI setregid_nid_postfix(std::uint32_t, std::uint32_t); -int APS5_VABI setgroups_nid_postfix(int, const std::uint32_t*); -int APS5_VABI getgroups_nid_postfix(int, std::uint32_t*); -int APS5_VABI getuid_nid_postfix(void); -int APS5_VABI geteuid_nid_postfix(void); -int APS5_VABI getgid_nid_postfix(void); -int APS5_VABI getegid_nid_postfix(void); -int APS5_VABI issetugid_nid_postfix(void); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } using SetId = int (APS5_VABI *)(std::uint32_t); using SetReId = int (APS5_VABI *)(std::uint32_t, std::uint32_t); @@ -43,6 +28,17 @@ static bool ThrowsRe(SetReId function, std::uint32_t r, std::uint32_t e) { return false; } +using SetResId = int (APS5_VABI *)(std::uint32_t, std::uint32_t, std::uint32_t); + +static bool ThrowsRes(SetResId function, std::uint32_t r, std::uint32_t e, std::uint32_t s) { + try { + function(r, e, s); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + constexpr int GuestEfault = 14; constexpr int GuestEinval = 22; constexpr std::uint32_t Unchanged = 0xffffffffu; @@ -68,6 +64,32 @@ int main() { Require(Throws(setgid_nid_postfix, Unchanged)); Require(getuid_nid_postfix() == 0 && geteuid_nid_postfix() == 0); Require(getgid_nid_postfix() == 0 && getegid_nid_postfix() == 0 && issetugid_nid_postfix() == 0); + + std::uint32_t ruid = 99, euid = 99, suid = 99; + *__error_nid_postfix() = 13; + Require(getresuid_nid_postfix(&ruid, &euid, &suid) == 0); + Require(ruid == 0 && euid == 0 && suid == 0); + Require(*__error_nid_postfix() == 13); + ruid = 99; + euid = 99; + suid = 99; + Require(getresuid_nid_postfix(&ruid, nullptr, nullptr) == 0 && ruid == 0); + Require(getresuid_nid_postfix(nullptr, &euid, nullptr) == 0 && euid == 0); + Require(getresuid_nid_postfix(nullptr, nullptr, &suid) == 0 && suid == 0); + Require(getresuid_nid_postfix(nullptr, nullptr, nullptr) == 0); + + std::uint32_t rgid = 99, egid = 99, sgid = 99; + *__error_nid_postfix() = 13; + Require(getresgid_nid_postfix(&rgid, &egid, &sgid) == 0); + Require(rgid == 0 && egid == 0 && sgid == 0); + Require(*__error_nid_postfix() == 13); + rgid = 99; + egid = 99; + sgid = 99; + Require(getresgid_nid_postfix(&rgid, nullptr, nullptr) == 0 && rgid == 0); + Require(getresgid_nid_postfix(nullptr, &egid, nullptr) == 0 && egid == 0); + Require(getresgid_nid_postfix(nullptr, nullptr, &sgid) == 0 && sgid == 0); + Require(getresgid_nid_postfix(nullptr, nullptr, nullptr) == 0); Require(setreuid_nid_postfix(Unchanged, Unchanged) == 0); Require(setreuid_nid_postfix(0, Unchanged) == 0); Require(setreuid_nid_postfix(Unchanged, 0) == 0); @@ -82,10 +104,44 @@ int main() { Require(ThrowsRe(setregid_nid_postfix, 1000, 0)); Require(ThrowsRe(setregid_nid_postfix, 0, 1000)); + Require(setresuid_nid_postfix(Unchanged, Unchanged, Unchanged) == 0); + Require(setresuid_nid_postfix(0, Unchanged, Unchanged) == 0); + Require(setresuid_nid_postfix(Unchanged, 0, Unchanged) == 0); + Require(setresuid_nid_postfix(Unchanged, Unchanged, 0) == 0); + Require(setresuid_nid_postfix(0, 0, 0) == 0); + Require(ThrowsRes(setresuid_nid_postfix, 1000, 0, 0)); + Require(ThrowsRes(setresuid_nid_postfix, 0, 1000, 0)); + Require(ThrowsRes(setresuid_nid_postfix, 0, 0, 1000)); + Require(ThrowsRes(setresuid_nid_postfix, 1000, Unchanged, Unchanged)); + Require(ThrowsRes(setresuid_nid_postfix, Unchanged, 1000, Unchanged)); + Require(ThrowsRes(setresuid_nid_postfix, Unchanged, Unchanged, 1000)); + + Require(setresgid_nid_postfix(Unchanged, Unchanged, Unchanged) == 0); + Require(setresgid_nid_postfix(0, Unchanged, Unchanged) == 0); + Require(setresgid_nid_postfix(Unchanged, 0, Unchanged) == 0); + Require(setresgid_nid_postfix(Unchanged, Unchanged, 0) == 0); + Require(setresgid_nid_postfix(0, 0, 0) == 0); + Require(ThrowsRes(setresgid_nid_postfix, 1000, 0, 0)); + Require(ThrowsRes(setresgid_nid_postfix, 0, 1000, 0)); + Require(ThrowsRes(setresgid_nid_postfix, 0, 0, 1000)); + Require(ThrowsRes(setresgid_nid_postfix, 1000, Unchanged, Unchanged)); + Require(ThrowsRes(setresgid_nid_postfix, Unchanged, 1000, Unchanged)); + Require(ThrowsRes(setresgid_nid_postfix, Unchanged, Unchanged, 1000)); + std::array groups{}; Require(getgroups_nid_postfix(0, nullptr) == 1); Require(getgroups_nid_postfix(static_cast(groups.size()), groups.data()) == 1 && groups[0] == 0); Require(groups[0] == static_cast(getegid_nid_postfix())); + *__error_nid_postfix() = 13; + RequireFailure(setgroups_nid_postfix(1, reinterpret_cast(1)), GuestEfault); + *__error_nid_postfix() = 13; + RequireFailure(getgroups_nid_postfix(1, reinterpret_cast(1)), GuestEfault); + Require(getgroups_nid_postfix(0, nullptr) == 1); + + *__error_nid_postfix() = 13; + RequireFailure(getresuid_nid_postfix(reinterpret_cast(1), nullptr, nullptr), GuestEfault); + *__error_nid_postfix() = 13; + RequireFailure(getresgid_nid_postfix(nullptr, reinterpret_cast(1), nullptr), GuestEfault); const std::array wanted{0, 5, 7}; Require(setgroups_nid_postfix(static_cast(wanted.size()), wanted.data()) == 0); diff --git a/core/libs/tests/GuestCrt.cpp b/core/libs/tests/GuestCrt.cpp index d2fe45d744..1e8f1887f5 100644 --- a/core/libs/tests/GuestCrt.cpp +++ b/core/libs/tests/GuestCrt.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceAmpr/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/SonyCrt.hpp" @@ -14,31 +16,6 @@ #include #include -extern "C" { -SonyLconv* APS5_VABI sceAmprLocaleconv(void); -double APS5_VABI sceAmprSinh(double x, double y); -char* APS5_VABI sceAmprStrtokS(char* str, const char* delim, char** context); -int APS5_VABI sceAmprCtimeS(char* buffer, std::size_t size, const std::int64_t* time); -int APS5_VABI sceAmprWcscpyS(std::uint16_t* dest, std::size_t size, const std::uint16_t* src); -void* APS5_VABI sceAmprSetConstraintHandlerS(void* handler); -int APS5_VABI sceAmprFltrounds(void); -void APS5_VABI sceAmprIgnoreHandlerS(const char* message, void* pointer, int error); -std::size_t APS5_VABI sceAmprMbstowcs(std::uint16_t* dest, const char* src, std::size_t size); -int APS5_VABI sceAmprAsctimeS(char* buffer, std::size_t size, const GuestTm* time); -int APS5_VABI ctime_s_nid_postfix(char* buffer, std::size_t size, const std::int64_t* time); -int APS5_VABI asctime_s_nid_postfix(char* buffer, std::size_t size, const GuestTm* time); -std::size_t APS5_VABI mbstowcs_nid_postfix(std::uint16_t* dest, const char* src, std::size_t size); -char* APS5_VABI strtok_s_nid_postfix(char* str, const char* delim, char** context); -int APS5_VABI __fpclassifyd_nid_postfix(double value); -unsigned __int128 APS5_VABI __udivti3_nid_postfix(unsigned __int128 dividend, unsigned __int128 divisor); -__int128 APS5_VABI __divti3_nid_postfix(__int128 dividend, __int128 divisor); -unsigned __int128 APS5_VABI __umodti3_nid_postfix(unsigned __int128 dividend, unsigned __int128 divisor); -__int128 APS5_VABI __modti3_nid_postfix(__int128 dividend, __int128 divisor); -SonyLconv* APS5_VABI localeconv_nid_postfix(void); -int APS5_VABI sceAmprFpclassifyd(double value); -unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix(); -} - #define Require(condition) do { if (!(condition)) { std::fprintf(stderr, "guest_crt: line %d: %s\n", __LINE__, #condition); std::abort(); } } while (0) static int g_handlerCalls = 0; @@ -154,6 +131,7 @@ int main() { trapped = false; try { __umodti3_nid_postfix(((unsigned __int128)0x1234 << 64) | 0x5678, 0); + } catch (const std::runtime_error& error) { trapped = std::strcmp(error.what(), "__umodti3: integer divide by zero (0x00000000000012340000000000005678 % 0)") == 0; } @@ -195,6 +173,13 @@ int main() { known.tm_year = 9000; text[0] = 'x'; Require(sceAmprAsctimeS(text, sizeof(text), &known) == EINVAL && text[0] == '\0'); + for (const int year : {INT_MAX, INT_MAX - 1899, INT_MIN}) { + known.tm_year = year; + text[0] = 'x'; + Require(sceAmprAsctimeS(text, sizeof(text), &known) == EINVAL && text[0] == '\0'); + text[0] = 'x'; + Require(asctime_s_nid_postfix(text, sizeof(text), &known) == EINVAL && text[0] == '\0'); + } known.tm_year = 70; known.tm_mday = 32; text[0] = 'x'; diff --git a/core/libs/tests/GuestCryptoMemcmp.cpp b/core/libs/tests/GuestCryptoMemcmp.cpp new file mode 100644 index 0000000000..e23991ed1f --- /dev/null +++ b/core/libs/tests/GuestCryptoMemcmp.cpp @@ -0,0 +1,124 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#endif + +namespace { + +void Require(bool value) { + if (!value) { + std::fprintf(stderr, "CRYPTO_memcmp check failed\n"); + std::abort(); + } +} + +class GuardedPage { +public: + explicit GuardedPage(bool different) { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + pageSize = info.dwPageSize; + memory = static_cast(VirtualAlloc(nullptr, pageSize * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(memory != nullptr); + DWORD previous; + Require(VirtualProtect(memory + pageSize, pageSize, PAGE_NOACCESS, &previous) != 0); +#else + const long nativePageSize = sysconf(_SC_PAGESIZE); + Require(nativePageSize > 0); + pageSize = static_cast(nativePageSize); + void* allocation = mmap(nullptr, pageSize * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(allocation != MAP_FAILED); + memory = static_cast(allocation); + Require(mprotect(memory + pageSize, pageSize, PROT_NONE) == 0); +#endif + std::memset(memory, 0xA5, pageSize); + if (different) memory[pageSize - 1] ^= 0xFF; +#ifdef _WIN32 + Require(VirtualProtect(memory, pageSize, PAGE_READONLY, &previous) != 0); +#else + Require(mprotect(memory, pageSize, PROT_READ) == 0); +#endif + } + + GuardedPage(const GuardedPage&) = delete; + GuardedPage& operator=(const GuardedPage&) = delete; + + ~GuardedPage() { +#ifdef _WIN32 + VirtualFree(memory, 0, MEM_RELEASE); +#else + munmap(memory, pageSize * 2); +#endif + } + + const unsigned char* Tail(std::size_t length) const { + Require(length <= pageSize); + return memory + pageSize - length; + } + +private: + unsigned char* memory = nullptr; + std::size_t pageSize = 0; +}; + +} + +int main() { + Require(CRYPTO_memcmp_nid_postfix(nullptr, nullptr, 0) == 0); + unsigned char byte = 0xA5; + Require(CRYPTO_memcmp_nid_postfix(nullptr, &byte, 0) == 0); + Require(CRYPTO_memcmp_nid_postfix(&byte, nullptr, 0) == 0); + for (unsigned int left = 0; left < 256; ++left) { + for (unsigned int right = 0; right < 256; ++right) { + const unsigned char a = static_cast(left); + const unsigned char b = static_cast(right); + Require((CRYPTO_memcmp_nid_postfix(&a, &b, 1) == 0) == (left == right)); + } + } + std::array first; + for (std::size_t index = 0; index < first.size(); ++index) first[index] = static_cast(index * 37); + auto second = first; + const auto original = first; + Require(CRYPTO_memcmp_nid_postfix(first.data(), first.data(), first.size()) == 0); + Require(CRYPTO_memcmp_nid_postfix(first.data() + 1, second.data() + 1, 65) == 0); + for (std::size_t index = 1; index <= 65; ++index) { + second[index] ^= 0x80; + const auto changed = second; + Require(CRYPTO_memcmp_nid_postfix(first.data() + 1, second.data() + 1, 65) != 0); + Require(CRYPTO_memcmp_nid_postfix(second.data() + 1, first.data() + 1, 65) != 0); + Require(first == original && second == changed); + second[index] ^= 0x80; + } + second[32] ^= 0x80; + Require(CRYPTO_memcmp_nid_postfix(first.data(), second.data(), 32) == 0); + Require(CRYPTO_memcmp_nid_postfix(first.data(), second.data(), 33) != 0); + second = first; + second[1] ^= 0x80; + second[65] ^= 0x80; + Require(CRYPTO_memcmp_nid_postfix(first.data(), second.data(), first.size()) != 0); + first.fill(0xCC); + Require(CRYPTO_memcmp_nid_postfix(first.data(), first.data() + 1, first.size() - 1) == 0); + first[32] = 0xCD; + const auto overlap = first; + Require(CRYPTO_memcmp_nid_postfix(first.data(), first.data() + 1, first.size() - 1) != 0); + Require(first == overlap); + const GuardedPage equal(false); + const GuardedPage other(false); + const GuardedPage different(true); + for (const std::size_t length : {0, 1, 2, 7, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129}) { + Require(CRYPTO_memcmp_nid_postfix(equal.Tail(length), other.Tail(length), length) == 0); + Require((CRYPTO_memcmp_nid_postfix(equal.Tail(length), different.Tail(length), length) == 0) == (length == 0)); + } +} diff --git a/core/libs/tests/GuestCurrentCpu.cpp b/core/libs/tests/GuestCurrentCpu.cpp index 389ad302ff..323eead874 100644 --- a/core/libs/tests/GuestCurrentCpu.cpp +++ b/core/libs/tests/GuestCurrentCpu.cpp @@ -1,22 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" #include "SceTypes.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include #include #include -extern "C" { -int APS5_VABI sceKernelGetCurrentCpu(void); -int APS5_VABI sceKernelGetMainSocId(void); -int APS5_VABI sceKernelGetCpumode(void); -std::int64_t APS5_VABI sceKernelGetCpuFrequency(void); -std::uint64_t APS5_VABI sceKernelGetTscFrequency(void); -KernelCpumask APS5_VABI sceKernelGetAvailableCpumask(void); -Pthread APS5_VABI scePthreadSelf(); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadSetaffinity(Pthread thread, KernelCpumask mask); -} - static constexpr int SCE_OK = 0; static constexpr unsigned MAX_HOST_CPUS = 256; static constexpr int CALLS_PER_THREAD = 64; @@ -44,6 +33,7 @@ int main() { const KernelCpumask available = sceKernelGetAvailableCpumask(); Require(sceKernelGetMainSocId() == 0 && sceKernelGetCpumode() == 0); Require(sceKernelGetCpuFrequency() > 0 && static_cast(sceKernelGetCpuFrequency()) == sceKernelGetTscFrequency()); + Require(sceKernelGetTscFrequency() >= 100000000u && sceKernelGetTscFrequency() <= 10000000000u); const std::array affinities{available, 0x1FFB, 0x3, 0x3F, 0x1000, 0x1, 0xFFFF, 0x10000}; for (const KernelCpumask affinity : affinities) { diff --git a/core/libs/tests/GuestDaemon.cpp b/core/libs/tests/GuestDaemon.cpp index 0e87384eb6..bec3a6613a 100644 --- a/core/libs/tests/GuestDaemon.cpp +++ b/core/libs/tests/GuestDaemon.cpp @@ -1,12 +1,14 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" int APS5_VABI daemon_nid_postfix(int, int); + static void Require(bool value) { if (!value) std::abort(); } static bool ReportsNotImplemented(int noChdir, int noClose) { try { daemon_nid_postfix(noChdir, noClose); + } catch (const std::runtime_error& error) { return std::string(error.what()) == "daemon: forking into the background not implemented"; } diff --git a/core/libs/tests/GuestDatagramReceive.cpp b/core/libs/tests/GuestDatagramReceive.cpp index 2c7bc9e0c5..d11fe04de6 100644 --- a/core/libs/tests/GuestDatagramReceive.cpp +++ b/core/libs/tests/GuestDatagramReceive.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,30 +11,6 @@ #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -std::int64_t APS5_VABI sendto_nid_postfix(int, const void*, std::uint64_t, int, const void*, std::uint32_t); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -std::int64_t APS5_VABI recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceNetInit_nid_postfix(); -int APS5_VABI sceNetSocket(const char*, int, int, int); -int APS5_VABI sceNetBind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI sceNetConnect(int, const void*, std::uint32_t); -int APS5_VABI sceNetGetsockname(int, void*, std::uint32_t*); -int APS5_VABI sceNetSetsockopt(int, int, int, const void*, std::uint32_t); -std::int64_t APS5_VABI sceNetSendto(int, const void*, std::size_t, int, const void*, std::uint32_t); -std::int64_t APS5_VABI sceNetRecv(int, void*, std::size_t, int); -std::int64_t APS5_VABI sceNetRecvfrom(int, void*, std::size_t, int, void*, std::uint32_t*); -int APS5_VABI sceNetSocketClose(int); -int* APS5_VABI sceNetErrnoLoc(); -} - static void Require(bool condition, const char* message) { if (!condition) { std::fprintf(stderr, "%s\n", message); @@ -56,6 +35,7 @@ static void Check(bool net, bool from, int family) { const auto bind = [&](int descriptor) { return net ? sceNetBind_nid_postfix(descriptor, destination.data(), addressSize) : bind_nid_postfix(descriptor, destination.data(), addressSize); + }; const int bound = bind(receiver); if (bound < 0 && family == 28 && *error == 49) { @@ -80,6 +60,7 @@ static void Check(bool net, bool from, int family) { sceNetRecv(receiver, buffer, length, flags); return from ? recvfrom_nid_postfix(receiver, buffer, length, flags, address, addressLength) : recv_nid_postfix(receiver, buffer, length, flags); + }; const auto send = [&](const char* data, std::size_t length) { Require((net ? sceNetSendto(sender, data, length, 0, destination.data(), addressSize) : @@ -140,6 +121,21 @@ static void Check(bool net, bool from, int family) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } while (std::chrono::steady_clock::now() < deadline); Require(result == 0 && byte == '!', "empty datagram"); + if (!net) { + send("hello", 5); + const auto ready = std::chrono::steady_clock::now() + std::chrono::seconds(1); + do { + result = receive(&byte, 1, 2, nullptr, nullptr); + if (result >= 0 || *error != 35) break; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } while (std::chrono::steady_clock::now() < ready); + Require(result == 1, "wait for the datagram"); + Require(receive(&byte, 0, 2, nullptr, nullptr) == 0 && receive(&byte, 1, 2, nullptr, nullptr) == 1, "a zero-length peek keeps the datagram"); + Require(receive(&byte, 0, 0, nullptr, nullptr) == 0, "a zero-length receive returns 0"); + Require(receive(&byte, 1, 0, nullptr, nullptr) < 0 && *error == 35, "a zero-length receive discards the datagram"); + Require(fcntl_nid_postfix(receiver, 4, 2) == 0, "make receiver blocking"); + Require(receive(&byte, 0, 0, nullptr, nullptr) == 0, "a zero-length receive on an empty queue returns 0 without waiting"); + } Require(closeSocket(receiver) == 0, "close receiver"); Require(closeSocket(sender) == 0, "close sender"); } diff --git a/core/libs/tests/GuestDeferredModule.cpp b/core/libs/tests/GuestDeferredModule.cpp index 719a68829f..f9e3089049 100644 --- a/core/libs/tests/GuestDeferredModule.cpp +++ b/core/libs/tests/GuestDeferredModule.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #define WIN32_LEAN_AND_MEAN #include "GuestDeferredModule.hpp" #include @@ -6,15 +7,6 @@ #include #include -extern "C" { -void* APS5_VABI dlopen_nid_postfix(const char*, int); -void* APS5_VABI dlsym_nid_postfix(void*, const char*); -int APS5_VABI dlclose_nid_postfix(void*); -char* APS5_VABI dlerror_nid_postfix(); -int APS5_VABI sceKernelLoadStartModule(const char*, std::size_t, const void*, unsigned, const void*, int*); -int APS5_VABI sceKernelStopUnloadModule(int, std::size_t, const void*, unsigned, const void*, int*); -} - static void Require(bool value, const char* what) { if (!value) { std::fprintf(stderr, "%s\n", what); diff --git a/core/libs/tests/GuestDemangle.cpp b/core/libs/tests/GuestDemangle.cpp index ea2f1100cf..b3bc34a66d 100644 --- a/core/libs/tests/GuestDemangle.cpp +++ b/core/libs/tests/GuestDemangle.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -9,12 +10,6 @@ #include #include -extern "C" { -char* APS5_VABI __cxa_demangle_nid_postfix(const char*, char*, std::size_t*, int*); -void* APS5_VABI malloc_nid_postfix(std::size_t); -void APS5_VABI free_nid_postfix(void*); -} - namespace { constexpr char MangledName[] = "_ZN3foo3barEi"; @@ -159,12 +154,13 @@ void RegisterAllocator() { void ReleaseIfReturned(void* result, void* original = nullptr) { ForgetIfMoved(original, result); free_nid_postfix(result != nullptr ? result : original); + } void TestNullOutputBuffer() { std::size_t length = 0; int status = 99; - char* result = __cxa_demangle_nid_postfix(MangledName, nullptr, &length, &status); + char* result = reinterpret_cast(__cxa_demangle_nid_postfix(MangledName, nullptr, &length, &status)); Check(result != nullptr, "null output buffer did not demangle"); Check(status == 0, "null output buffer returned the wrong status"); Check(result != nullptr && std::strcmp(result, DemangledName) == 0, "null output buffer returned the wrong string"); @@ -174,7 +170,7 @@ void TestNullOutputBuffer() { } void TestOptionalStatus() { - char* result = __cxa_demangle_nid_postfix(MangledName, nullptr, nullptr, nullptr); + char* result = reinterpret_cast(__cxa_demangle_nid_postfix(MangledName, nullptr, nullptr, nullptr)); Check(result != nullptr && std::strcmp(result, DemangledName) == 0, "optional status output failed"); Check(result != nullptr && Owns(result), "optional status output was not guest-owned"); ReleaseIfReturned(result); @@ -185,7 +181,7 @@ void TestLargeBufferReuse() { const std::size_t originalLength = 128; std::size_t length = originalLength; int status = 99; - char* result = __cxa_demangle_nid_postfix(MangledName, buffer, &length, &status); + char* result = reinterpret_cast(__cxa_demangle_nid_postfix(MangledName, static_cast(buffer), &length, &status)); Check(result == buffer, "sufficient guest buffer was not reused"); Check(status == 0, "sufficient guest buffer returned the wrong status"); Check(result != nullptr && std::strcmp(result, DemangledName) == 0, "sufficient guest buffer returned the wrong string"); @@ -198,7 +194,7 @@ void TestSmallBufferGrowth() { std::size_t length = 1; const std::size_t reallocationsBefore = reallocationCalls; int status = 99; - char* result = __cxa_demangle_nid_postfix(MangledName, buffer, &length, &status); + char* result = reinterpret_cast(__cxa_demangle_nid_postfix(MangledName, static_cast(buffer), &length, &status)); Check(result != nullptr, "too-small guest buffer did not grow"); Check(status == 0, "grown guest buffer returned the wrong status"); Check(result != nullptr && std::strcmp(result, DemangledName) == 0, "grown guest buffer returned the wrong string"); @@ -210,18 +206,19 @@ void TestSmallBufferGrowth() { void TestInvalidInputs() { int status = 99; - char* result = __cxa_demangle_nid_postfix("not_a_mangled_name", nullptr, nullptr, &status); + char* result = reinterpret_cast(__cxa_demangle_nid_postfix("not_a_mangled_name", nullptr, nullptr, &status)); Check(result == nullptr && status == -2, "invalid mangled name did not return status -2"); status = 99; - result = __cxa_demangle_nid_postfix(nullptr, nullptr, nullptr, &status); + result = reinterpret_cast(__cxa_demangle_nid_postfix(nullptr, nullptr, nullptr, &status)); Check(result == nullptr && status == -3, "null mangled name did not return status -3"); auto* buffer = static_cast(malloc_nid_postfix(32)); std::memcpy(buffer, "unchanged", sizeof("unchanged")); - result = __cxa_demangle_nid_postfix(MangledName, buffer, nullptr, &status); + result = reinterpret_cast(__cxa_demangle_nid_postfix(MangledName, static_cast(buffer), nullptr, &status)); Check(result == nullptr && status == -3, "missing length for a supplied buffer did not return status -3"); Check(std::strcmp(buffer, "unchanged") == 0, "invalid supplied-buffer arguments modified the buffer"); free_nid_postfix(buffer); + } void TestAllocationFailure() { @@ -229,7 +226,7 @@ void TestAllocationFailure() { failNextAllocation = true; std::size_t length = 0; int status = 99; - char* result = __cxa_demangle_nid_postfix(MangledName, nullptr, &length, &status); + char* result = reinterpret_cast(__cxa_demangle_nid_postfix(MangledName, nullptr, &length, &status)); Check(result == nullptr, "guest allocation failure returned a buffer"); Check(status == -1, "guest allocation failure did not return status -1"); Check(allocationFailures == failuresBefore + 1, "demangler bypassed failed guest allocation"); @@ -243,7 +240,7 @@ void TestReallocationFailure() { const std::size_t failuresBefore = reallocationFailures; failNextReallocation = true; int status = 99; - char* result = __cxa_demangle_nid_postfix(MangledName, buffer, &length, &status); + char* result = reinterpret_cast(__cxa_demangle_nid_postfix(MangledName, static_cast(buffer), &length, &status)); Check(result == nullptr, "guest reallocation failure returned a buffer"); Check(status == -1, "guest reallocation failure did not return status -1"); Check(reallocationFailures == failuresBefore + 1, "demangler bypassed failed guest reallocation"); @@ -257,7 +254,7 @@ void TestAllocatorDiagnostics() { bool allocationDiagnostic = false; char* result = nullptr; try { - result = __cxa_demangle_nid_postfix(MangledName, nullptr, nullptr, nullptr); + result = reinterpret_cast(__cxa_demangle_nid_postfix(MangledName, nullptr, nullptr, nullptr)); } catch (const std::runtime_error& error) { allocationDiagnostic = std::strcmp(error.what(), "guest allocator callback failure") == 0; } @@ -271,7 +268,7 @@ void TestAllocatorDiagnostics() { bool reallocationDiagnostic = false; result = nullptr; try { - result = __cxa_demangle_nid_postfix(MangledName, buffer, &length, nullptr); + result = reinterpret_cast(__cxa_demangle_nid_postfix(MangledName, static_cast(buffer), &length, nullptr)); } catch (const std::runtime_error& error) { reallocationDiagnostic = std::strcmp(error.what(), "guest reallocator callback failure") == 0; } diff --git a/core/libs/tests/GuestDinkumwareAtomics.cpp b/core/libs/tests/GuestDinkumwareAtomics.cpp index 8e45d76902..782953cae3 100644 --- a/core/libs/tests/GuestDinkumwareAtomics.cpp +++ b/core/libs/tests/GuestDinkumwareAtomics.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,39 +6,58 @@ #include #include -extern "C" { -void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int); -void APS5_VABI _Atomic_store_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); -std::uint16_t APS5_VABI _Atomic_load_2_nid_postfix(volatile std::uint16_t*, int); -std::uint64_t APS5_VABI _Atomic_load_8_nid_postfix(volatile std::uint64_t*, int); -std::uint32_t APS5_VABI _Atomic_exchange_4_nid_postfix(volatile std::uint32_t*, std::uint32_t, int); -std::uint64_t APS5_VABI _Atomic_exchange_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); -int APS5_VABI _Atomic_compare_exchange_strong_2_nid_postfix(volatile std::uint16_t*, std::uint16_t*, std::uint16_t, int, int); -int APS5_VABI _Atomic_compare_exchange_weak_8_nid_postfix(volatile std::uint64_t*, std::uint64_t*, std::uint64_t, int, int); -std::uint8_t APS5_VABI _Atomic_fetch_add_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int); -std::uint64_t APS5_VABI _Atomic_fetch_add_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); -std::uint16_t APS5_VABI _Atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t*, std::uint16_t, int); -std::uint32_t APS5_VABI _Atomic_fetch_and_4_nid_postfix(volatile std::uint32_t*, std::uint32_t, int); -std::uint8_t APS5_VABI _Atomic_fetch_or_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int); -std::uint64_t APS5_VABI _Atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); -int APS5_VABI _Atomic_is_lock_free_4_nid_postfix(); -int APS5_VABI _Atomic_flag_test_and_set_nid_postfix(volatile unsigned char*, int); -void APS5_VABI _Atomic_flag_clear_nid_postfix(volatile unsigned char*, int); -void APS5_VABI _Atomic_thread_fence_nid_postfix(int); -void APS5_VABI _Atomic_signal_fence_nid_postfix(int); -void APS5_VABI _Atomic_copy_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, const volatile void*, int); -void APS5_VABI _Atomic_exchange_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, volatile void*, int); -int APS5_VABI _Atomic_compare_exchange_strong_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, void*, const void*, int, int); -int APS5_VABI _Atomic_compare_exchange_weak_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, void*, const void*, int, int); -} - static void Require(bool value) { if (!value) std::abort(); } constexpr int Relaxed = 0, Acquire = 2, Release = 3, AcqRel = 4, SeqCst = 5; +static void CheckRemainingTyped() { + std::uint8_t byte = 0; + Require(_Atomic_load_1_nid_postfix(&byte, SeqCst) == 0); + Require(_Atomic_exchange_1_nid_postfix(&byte, 0xa5, SeqCst) == 0 && byte == 0xa5); + std::uint8_t expectedByte = 0; + Require(_Atomic_compare_exchange_strong_1_nid_postfix(&byte, &expectedByte, 1, SeqCst, SeqCst) == 0 && expectedByte == 0xa5 && byte == 0xa5); + Require(_Atomic_compare_exchange_strong_1_nid_postfix(&byte, &expectedByte, 0x3c, SeqCst, SeqCst) == 1 && byte == 0x3c); + expectedByte = 0; + Require(_Atomic_compare_exchange_weak_1_nid_postfix(&byte, &expectedByte, 1, SeqCst, Relaxed) == 0 && expectedByte == 0x3c); + Require(_Atomic_compare_exchange_weak_1_nid_postfix(&byte, &expectedByte, 0x0f, SeqCst, Relaxed) == 1 && byte == 0x0f); + Require(_Atomic_fetch_sub_1_nid_postfix(&byte, 0x10, SeqCst) == 0x0f && byte == 0xff); + Require(_Atomic_fetch_and_1_nid_postfix(&byte, 0x3c, Relaxed) == 0xff && byte == 0x3c); + Require(_Atomic_fetch_xor_1_nid_postfix(&byte, 0xff, AcqRel) == 0x3c && byte == 0xc3); + + std::uint16_t half = 0; + _Atomic_store_2_nid_postfix(&half, 0x1234, Release); + Require(half == 0x1234 && _Atomic_exchange_2_nid_postfix(&half, 0xfff0, SeqCst) == 0x1234 && half == 0xfff0); + Require(_Atomic_fetch_add_2_nid_postfix(&half, 0x20, SeqCst) == 0xfff0 && half == 0x0010); + Require(_Atomic_fetch_and_2_nid_postfix(&half, 0x0018, SeqCst) == 0x0010 && half == 0x0010); + Require(_Atomic_fetch_or_2_nid_postfix(&half, 0x0101, SeqCst) == 0x0010 && half == 0x0111); + Require(_Atomic_fetch_xor_2_nid_postfix(&half, 0x0110, SeqCst) == 0x0111 && half == 0x0001); + std::uint16_t expectedHalf = 2; + Require(_Atomic_compare_exchange_weak_2_nid_postfix(&half, &expectedHalf, 9, SeqCst, Relaxed) == 0 && expectedHalf == 1); + Require(_Atomic_compare_exchange_weak_2_nid_postfix(&half, &expectedHalf, 9, SeqCst, Relaxed) == 1 && half == 9); + + std::uint32_t word = 0; + _Atomic_store_4_nid_postfix(&word, 0x00ff00ffu, SeqCst); + Require(word == 0x00ff00ffu && _Atomic_fetch_or_4_nid_postfix(&word, 0xff000000u, SeqCst) == 0x00ff00ffu && word == 0xffff00ffu); + Require(_Atomic_fetch_xor_4_nid_postfix(&word, 0xffffffffu, SeqCst) == 0xffff00ffu && word == 0x0000ff00u); + std::uint32_t expectedWord = 0; + Require(_Atomic_compare_exchange_strong_4_nid_postfix(&word, &expectedWord, 1, SeqCst, SeqCst) == 0 && expectedWord == 0x0000ff00u); + Require(_Atomic_compare_exchange_strong_4_nid_postfix(&word, &expectedWord, 1, SeqCst, SeqCst) == 1 && word == 1); + + std::uint64_t wide = 0x8000000000000001ull; + Require(_Atomic_fetch_sub_8_nid_postfix(&wide, 2, SeqCst) == 0x8000000000000001ull && wide == 0x7fffffffffffffffull); + Require(_Atomic_fetch_and_8_nid_postfix(&wide, 0xffffffff00000000ull, SeqCst) == 0x7fffffffffffffffull && wide == 0x7fffffff00000000ull); + Require(_Atomic_fetch_or_8_nid_postfix(&wide, 0xffull, SeqCst) == 0x7fffffff00000000ull && wide == 0x7fffffff000000ffull); + std::uint64_t expectedWide = 0; + Require(_Atomic_compare_exchange_strong_8_nid_postfix(&wide, &expectedWide, 5, SeqCst, SeqCst) == 0 && expectedWide == 0x7fffffff000000ffull); + Require(_Atomic_compare_exchange_strong_8_nid_postfix(&wide, &expectedWide, 5, SeqCst, SeqCst) == 1 && wide == 5); + + Require(_Atomic_is_lock_free_1_nid_postfix() == 1 && _Atomic_is_lock_free_2_nid_postfix() == 1 && _Atomic_is_lock_free_8_nid_postfix() == 1); +} + static void CheckTyped() { std::uint8_t byte = 0; _Atomic_store_1_nid_postfix(&byte, 7, SeqCst); + Require(byte == 7 && _Atomic_fetch_add_1_nid_postfix(&byte, 250, Relaxed) == 7 && byte == 1); Require(_Atomic_fetch_or_1_nid_postfix(&byte, 0x80, AcqRel) == 1 && byte == 0x81); std::uint16_t half = 5; @@ -50,6 +70,7 @@ static void CheckTyped() { Require(_Atomic_exchange_4_nid_postfix(&word, 9, 6) == 0xf00f && word == 9); std::uint64_t wide = 0; _Atomic_store_8_nid_postfix(&wide, 0x123456789ull, Release); + Require(_Atomic_load_8_nid_postfix(&wide, Acquire) == 0x123456789ull); Require(_Atomic_load_8_nid_postfix(&wide, Release) == 0 && _Atomic_load_8_nid_postfix(&wide, AcqRel) == 0); Require(_Atomic_fetch_add_8_nid_postfix(&wide, 1, SeqCst) == 0x123456789ull && wide == 0x12345678aull); @@ -66,9 +87,11 @@ static void CheckFlagsAndFences() { Require(_Atomic_flag_test_and_set_nid_postfix(&flag, SeqCst) == 0 && flag == 1); Require(_Atomic_flag_test_and_set_nid_postfix(&flag, Relaxed) == 1); _Atomic_flag_clear_nid_postfix(&flag, Release); + Require(flag == 0); _Atomic_thread_fence_nid_postfix(SeqCst); _Atomic_signal_fence_nid_postfix(SeqCst); + } struct Wide { @@ -79,9 +102,11 @@ static void CheckGeneric() { unsigned char lock = 0; Wide target{{1, 2, 3}}, source{{4, 5, 6}}, expected{{1, 2, 3}}; _Atomic_copy_nid_postfix(&lock, sizeof(Wide), &target, &source, SeqCst); + Require(std::memcmp(&target, &source, sizeof(Wide)) == 0 && lock == 0); Wide other{{7, 8, 9}}; _Atomic_exchange_nid_postfix(&lock, sizeof(Wide), &target, &other, SeqCst); + Require(target.parts[0] == 7 && other.parts[0] == 4 && lock == 0); const Wide desired{{10, 11, 12}}; Require(_Atomic_compare_exchange_strong_nid_postfix(&lock, sizeof(Wide), &target, &expected, &desired, SeqCst, SeqCst) == 0); @@ -98,6 +123,7 @@ static void CheckConcurrentGeneric() { for (int i = 0; i < Iterations; ++i) { Wide expected; _Atomic_copy_nid_postfix(&lock, sizeof(Wide), &expected, &counter, SeqCst); + Wide next; do { next = Wide{{expected.parts[0] + 1, expected.parts[1] + 2, expected.parts[2] + 3}}; @@ -111,8 +137,46 @@ static void CheckConcurrentGeneric() { Require(counter.parts[0] == 2 * Iterations && counter.parts[1] == 4 * Iterations && counter.parts[2] == 6 * Iterations); } +static void CheckSized() { + std::uint8_t byte = 0xa5; + Require(__atomic_load_1_nid_postfix(&byte, Acquire) == 0xa5); + std::uint16_t half = 0; + __atomic_store_2_nid_postfix(&half, 0x1234, Release); + Require(half == 0x1234); + Require(__atomic_fetch_add_2_nid_postfix(&half, 0xffff, Relaxed) == 0x1234 && half == 0x1233); + Require(__atomic_fetch_or_2_nid_postfix(&half, 0x8000, SeqCst) == 0x1233 && half == 0x9233); + std::uint32_t word = 5; + Require(__atomic_exchange_4_nid_postfix(&word, 9, AcqRel) == 5 && word == 9); + Require(__atomic_fetch_sub_4_nid_postfix(&word, 10, SeqCst) == 9 && word == 0xffffffffu); + Require(__atomic_fetch_and_1_nid_postfix(&byte, 0x0f, SeqCst) == 0xa5 && byte == 0x05); + std::uint8_t expectedByte = 4; + Require(__atomic_compare_exchange_1_nid_postfix(&byte, &expectedByte, 7, SeqCst, Relaxed) == 0 && expectedByte == 5 && byte == 5); + Require(__atomic_compare_exchange_1_nid_postfix(&byte, &expectedByte, 7, SeqCst, Relaxed) == 1 && expectedByte == 5 && byte == 7); + std::uint64_t wide = 0; + __atomic_store_8_nid_postfix(&wide, 0x0123456789abcdefull, SeqCst); + Require(__atomic_load_8_nid_postfix(&wide, SeqCst) == 0x0123456789abcdefull); + Require(__atomic_fetch_xor_8_nid_postfix(&wide, 0xffffffff00000000ull, SeqCst) == 0x0123456789abcdefull && wide == 0xfedcba9889abcdefull); + std::uint64_t expectedWide = 0xfedcba9889abcdefull; + Require(__atomic_compare_exchange_8_nid_postfix(&wide, &expectedWide, 1, SeqCst, SeqCst) == 1 && wide == 1); + Require(__atomic_exchange_8_nid_postfix(&wide, 2, 6) == 1 && wide == 2); + Require(__atomic_load_8_nid_postfix(&wide, Release) == 2 && __atomic_load_8_nid_postfix(&wide, AcqRel) == 2 && __atomic_load_8_nid_postfix(&wide, -1) == 2); + Require(__atomic_fetch_add_8_nid_postfix(&wide, 1, 7) == 2 && wide == 3); + + std::uint64_t counter = 0; + std::thread threads[4]; + for (auto& thread : threads) { + thread = std::thread([&counter] { + for (int i = 0; i < 100000; ++i) __atomic_fetch_add_8_nid_postfix(&counter, 1, Relaxed); + }); + } + for (auto& thread : threads) thread.join(); + Require(counter == 400000); +} + int main() { + CheckRemainingTyped(); CheckTyped(); + CheckSized(); CheckFlagsAndFences(); CheckGeneric(); CheckConcurrentGeneric(); diff --git a/core/libs/tests/GuestDinkumwareFetch.cpp b/core/libs/tests/GuestDinkumwareFetch.cpp new file mode 100644 index 0000000000..81a8d10c1b --- /dev/null +++ b/core/libs/tests/GuestDinkumwareFetch.cpp @@ -0,0 +1,133 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::exit(1); +} + +template +using Fetch = TValue (APS5_VABI*)(volatile TValue*, TValue); + +template +static TValue Expected(TValue before, TValue operand, const std::string& operation) { + if (operation == "and") return before & operand; + if (operation == "or") return before | operand; + return before ^ operand; +} + +template +static void Check(Fetch fetch, const std::string& operation) { + constexpr TValue maximum = std::numeric_limits::max(); + std::array guarded{maximum, 0, maximum}; + const auto one = [&](TValue before, TValue operand) { + guarded[1] = before; + Require(fetch(&guarded[1], operand) == before, "fetch must return the original value"); + Require(guarded[1] == Expected(before, operand, operation), "bitwise result"); + Require(guarded[0] == maximum && guarded[2] == maximum, "neighboring storage changed"); + }; + if constexpr (sizeof(TValue) == 1) { + for (unsigned before = 0; before < 256; ++before) + for (unsigned operand = 0; operand < 256; ++operand) + one(static_cast(before), static_cast(operand)); + } else { + constexpr std::array values{0, 1, maximum, static_cast(maximum >> 1), static_cast(TValue{1} << (sizeof(TValue) * 8 - 1)), static_cast(0xA55AA55A)}; + for (TValue before : values) for (TValue operand : values) one(before, operand); + std::uint32_t random = 0x31415926; + for (unsigned i = 0; i < 4096; ++i) { + random = random * 1664525u + 1013904223u; + const TValue before = static_cast(random); + random = random * 1664525u + 1013904223u; + one(before, static_cast(random)); + } + } + guarded[1] = operation == "and" ? maximum : 0; + std::array workers; + std::barrier start(4); + for (unsigned index = 0; index < workers.size(); ++index) { + workers[index] = std::thread([&, index] { + const TValue bit = static_cast(TValue{1} << index); + const TValue operand = operation == "and" ? static_cast(~bit) : bit; + start.arrive_and_wait(); + for (unsigned iteration = 0; iteration < 1025; ++iteration) { + const TValue previous = fetch(&guarded[1], operand); + const bool expected = operation == "and" ? iteration == 0 : operation == "or" ? iteration != 0 : iteration % 2 != 0; + Require(((previous & bit) != 0) == expected, "concurrent original-bit history"); + } + }); + } + for (auto& worker : workers) worker.join(); + Require(guarded[1] == (operation == "and" ? static_cast(maximum & ~TValue{15}) : TValue{15}), "concurrent updates lost"); + Require(guarded[0] == maximum && guarded[2] == maximum, "concurrent neighboring storage changed"); + std::array flags{}; + std::array observed{}; + std::barrier phase(3); + const auto litmus = [&](unsigned index) { + for (unsigned i = 0; i < 512; ++i) { + phase.arrive_and_wait(); + fetch(&flags[index], operation == "and" ? TValue{0} : TValue{1}); + observed[index] = fetch(&flags[1 - index], operation == "and" ? maximum : TValue{0}); + phase.arrive_and_wait(); + } + }; + std::thread first(litmus, 0), second(litmus, 1); + for (unsigned i = 0; i < 512; ++i) { + flags.fill(operation == "and" ? maximum : TValue{0}); + phase.arrive_and_wait(); + phase.arrive_and_wait(); + const TValue initial = operation == "and" ? maximum : TValue{0}; + Require(observed[0] != initial || observed[1] != initial, "forbidden sequentially consistent outcome"); + } + first.join(); + second.join(); +} + +int main(int argc, char** argv) { + Require(argc == 5, "library directory, patched or unpatched, operation, width"); + const auto path = std::filesystem::absolute(argv[1]) / "libc.prx"; + const std::string operation = argv[3]; + const int width = std::atoi(argv[4]); + Require(operation == "and" || operation == "or" || operation == "xor", "operation"); + Require(width == 1 || width == 2 || width == 4, "width"); + const std::string name = "_Fetch_" + operation + "_seq_cst_" + std::to_string(width); + const std::string symbol = std::string(argv[2]) == "patched" ? Nid::ComputeNid(name, "") : name + "_nid_postfix"; +#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(), "loaded libc path"); + Require(std::filesystem::equivalent(path, std::filesystem::path(loaded.data())), "wrong libc loaded"); + if (startup) Require(startup == library, "startup libc differs from selected libc"); + std::printf("Selected libc: %ls (startup module %s)\n", loaded.data(), startup ? "same" : "not imported"); + 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 helper"); + if (width == 1) Check(reinterpret_cast>(function), operation); + if (width == 2) Check(reinterpret_cast>(function), operation); + if (width == 4) Check(reinterpret_cast>(function), operation); + std::printf("%s: value, original result, bounds, concurrency and ordering checks passed\n", name.c_str()); +} diff --git a/core/libs/tests/GuestDinkumwareInternals.cpp b/core/libs/tests/GuestDinkumwareInternals.cpp new file mode 100644 index 0000000000..c1be3d457e --- /dev/null +++ b/core/libs/tests/GuestDinkumwareInternals.cpp @@ -0,0 +1,418 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int RangeError = 34; +constexpr int InvalidArgument = 22; +constexpr short Denormal = -2; +constexpr short Finite = -1; +constexpr short Zero = 0; +constexpr short Infinite = 1; +constexpr short NotANumber = 2; +constexpr int FloatingExceptions = FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "Dinkumware internals: %s\n", message); + std::exit(1); + } +} + +void CheckStod(const char* text, std::int64_t pten, double expected, long consumed, int error, const char* message) { + *__error_nid_postfix() = 0; + char* end = nullptr; + const double value = _Stod_nid_postfix(text, &end, pten); + const bool same = std::isnan(expected) ? std::isnan(value) : value == expected && std::signbit(value) == std::signbit(expected); + Require(same && end - text == consumed && *__error_nid_postfix() == error, message); +} + +void CheckExp(double x, double y, short offset, double expected, short expectedKind, int expectedErrno, int expectedFlags, const char* message) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + double result = x; + const short kind = _Exp_nid_postfix(&result, y, offset); + const bool same = std::isnan(expected) ? std::isnan(result) : result == expected && std::signbit(result) == std::signbit(expected); + Require(kind == expectedKind && same && errno == expectedErrno && (std::fetestexcept(FE_ALL_EXCEPT) & FloatingExceptions) == expectedFlags, message); +} + +void CheckFExp(float x, float y, short offset, float expected, short expectedKind, int expectedErrno, int expectedFlags, const char* message) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + float result = x; + const short kind = _FExp_nid_postfix(&result, y, offset); + const bool same = std::isnan(expected) ? std::isnan(result) : result == expected && std::signbit(result) == std::signbit(expected); + Require(kind == expectedKind && same && errno == expectedErrno && (std::fetestexcept(FE_ALL_EXCEPT) & FloatingExceptions) == expectedFlags, message); +} + +void TestStod() { + CheckStod("1.5", 0, 1.5, 3, 0, "unscaled decimal"); + CheckStod("1.5", 1, 15.0, 3, 0, "decimal scaled up"); + CheckStod("12", -2, 0.12, 2, 0, "decimal scaled down"); + CheckStod(" -2.25e3xyz", 300, -0x1.a3f7f806a8135p+1007, 9, 0, "the scale is applied before rounding"); + CheckStod("1e-400", 300, 1e-100, 6, 0, "an underflowing text scaled back into range"); + CheckStod("1e-400", 0, 0.0, 6, RangeError, "an unscaled underflow"); + CheckStod("1e300", 100, HUGE_VAL, 5, RangeError, "a scale that overflows"); + CheckStod("1e300", 8, 1e308, 5, 0, "a scale up to the largest decades"); + CheckStod("5e-324", -1, 0.0, 6, RangeError, "a scale that underflows to zero"); + CheckStod("1", 2147483647, HUGE_VAL, 1, RangeError, "the largest scale"); + CheckStod("5", std::int64_t{1} << 40, HUGE_VAL, 1, RangeError, "a scale above 32 bits"); + CheckStod("1e400", -100, 1e300, 5, 0, "an overflowing text scaled back into range"); + CheckStod("0.01e41", -2, 1e37, 7, 0, "a negative scale from leading fraction zeros"); + CheckStod("1e-2147483648", -1, 0.0, 13, RangeError, "the smallest exponent and scale"); + CheckStod("-0", 5, -0.0, 2, 0, "negative zero keeps its sign"); + CheckStod("infinity", 3, HUGE_VAL, 8, 0, "infinity is not scaled"); + CheckStod("nan", 3, NAN, 3, 0, "NaN is not scaled"); + CheckStod("1e", 2, 100.0, 1, 0, "an exponent marker without digits"); + CheckStod(".5", 2, 50.0, 2, 0, "a fraction without integer digits"); + CheckStod("0.3", -1, 0.03, 3, 0, "0.3 scaled down rounds as 0.03"); + CheckStod("123456789012345678901234567890", -20, 1234567890.1234567, 30, 0, "a long digit string"); + CheckStod("abc", 4, 0.0, 0, 0, "no number"); + CheckStod("0x1.8p0", 0, 1.5, 7, 0, "unscaled hexadecimal"); +} + +void TestStoll() { + char* end = nullptr; + *__error_nid_postfix() = 0; + Require(_Stoll_nid_postfix("-0x1F", &end, 0) == -31 && std::strcmp(end, "") == 0, "_Stoll base detection"); + Require(_Stoll_nid_postfix("+42abc", &end, 10) == 42 && std::strcmp(end, "abc") == 0, "_Stoll stops at the first non-digit"); + Require(_Stoll_nid_postfix(" z", &end, 36) == 35, "_Stoll base 36"); + Require(*__error_nid_postfix() == 0, "_Stoll set errno without an overflow"); + Require(_Stoll_nid_postfix("9223372036854775808", &end, 10) == 9223372036854775807ll && *__error_nid_postfix() == RangeError, "_Stoll saturates"); +} + +void TestArithmetic() { + Require(_Divide_nid_postfix(3.0, 2.0) == 1.5, "_Divide quotient"); + Require(std::isinf(_Divide_nid_postfix(1.0, 0.0)) && !std::signbit(_Divide_nid_postfix(1.0, 0.0)), "_Divide positive infinity"); + Require(std::isinf(_Divide_nid_postfix(-1.0, 0.0)) && std::signbit(_Divide_nid_postfix(-1.0, 0.0)), "_Divide negative infinity"); + Require(std::isnan(_Divide_nid_postfix(0.0, 0.0)), "_Divide invalid"); + Require(_Divide_nid_postfix(-0.0, 2.0) == 0.0 && std::signbit(_Divide_nid_postfix(-0.0, 2.0)), "_Divide signed zero"); + Require(_Divide_nid_postfix(1.0, INFINITY) == 0.0 && !std::signbit(_Divide_nid_postfix(1.0, INFINITY)), "_Divide finite by infinity"); + Require(std::isnan(_Divide_nid_postfix(INFINITY, INFINITY)), "_Divide infinity by infinity"); + Require(_Recip_nid_postfix(4.0) == 0.25, "_Recip quotient"); + Require(_Recip_nid_postfix(-4.0) == -0.25, "_Recip negative quotient"); + Require(std::isinf(_Recip_nid_postfix(-0.0)) && std::signbit(_Recip_nid_postfix(-0.0)), "_Recip signed infinity"); + Require(_Recip_nid_postfix(INFINITY) == 0.0 && !std::signbit(_Recip_nid_postfix(INFINITY)), "_Recip infinity"); + Require(std::isnan(_Recip_nid_postfix(NAN)), "_Recip NaN"); + Require(_FDivide_nid_postfix(3.0f, 2.0f) == 1.5f, "_FDivide quotient"); + Require(std::isinf(_FDivide_nid_postfix(-1.0f, 0.0f)) && std::signbit(_FDivide_nid_postfix(-1.0f, 0.0f)), "_FDivide negative infinity"); + Require(std::isnan(_FDivide_nid_postfix(0.0f, 0.0f)), "_FDivide invalid"); + Require(_FDivide_nid_postfix(-0.0f, 2.0f) == 0.0f && std::signbit(_FDivide_nid_postfix(-0.0f, 2.0f)), "_FDivide signed zero"); + Require(_FRecip_nid_postfix(4.0f) == 0.25f, "_FRecip quotient"); + Require(_FRecip_nid_postfix(-4.0f) == -0.25f, "_FRecip negative quotient"); + Require(std::isinf(_FRecip_nid_postfix(-0.0f)) && std::signbit(_FRecip_nid_postfix(-0.0f)), "_FRecip signed infinity"); + Require(_FRecip_nid_postfix(INFINITY) == 0.0f && !std::signbit(_FRecip_nid_postfix(INFINITY)), "_FRecip infinity"); + Require(std::isnan(_FRecip_nid_postfix(NAN)), "_FRecip NaN"); +} + +void TestFloatErrors() { + char* end = nullptr; + int error = 99; + *__error_nid_postfix() = 0; + Require(_Stodx_nid_postfix("1.5", &end, 2, &error) == 150.0 && error == 0 && *__error_nid_postfix() == 0, "_Stodx in range"); + error = 99; + Require(std::isinf(_Stodx_nid_postfix("1e400", &end, 0, &error)) && error == 1 && *__error_nid_postfix() == RangeError, "_Stodx overflow"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stodx_nid_postfix("1e-400", &end, 2, &error) == 0.0 && error == 1 && *__error_nid_postfix() == RangeError, "_Stodx underflow"); + error = 99; + *__error_nid_postfix() = 7; + Require(_Stodx_nid_postfix("1e-400", &end, 300, &error) == 1e-100 && error == 0 && *__error_nid_postfix() == 7, "_Stodx rescued by its scale"); + error = 99; + *__error_nid_postfix() = RangeError; + Require(_Stodx_nid_postfix("2.5", &end, 1, &error) == 25.0 && error == 0 && *__error_nid_postfix() == RangeError, "_Stodx kept a preset errno"); + error = 99; + *__error_nid_postfix() = 0; + Require(std::isinf(_Stofx_nid_postfix("1e39", &end, 0, &error)) && error == 1 && *__error_nid_postfix() == RangeError, "_Stofx overflow"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stofx_nid_postfix("2.5e-1", &end, 3, &error) == 250.0f && error == 0 && *__error_nid_postfix() == 0, "_Stofx scaled"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stofx_nid_postfix("1e39", &end, -1, &error) == 1e38f && error == 0 && *__error_nid_postfix() == 0, "_Stofx overflow scaled back into range"); + error = 99; + Require(_Stodx_nid_postfix("1e400", &end, -100, &error) == 1e300 && error == 0 && *__error_nid_postfix() == 0, "_Stodx overflow scaled back into range"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stoldx_nid_postfix("1e400", &end, 0, &error) == 1e400L && error == 0 && *__error_nid_postfix() == 0, "_Stoldx uses the extended range"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stodx_nid_postfix("1e-310", &end, 0, &error) == 1e-310 && error == 0 && *__error_nid_postfix() == 0 && end[0] == '\0', "_Stodx subnormal"); + error = 99; + Require(_Stodx_nid_postfix("1e-300", &end, -10, &error) == 1e-310 && error == 0 && *__error_nid_postfix() == 0, "_Stodx scaled into the subnormal range"); + error = 99; + *__error_nid_postfix() = 7; + Require(_Stodx_nid_postfix("1e-310", &end, 0, &error) == 1e-310 && error == 0 && *__error_nid_postfix() == 7, "_Stodx subnormal kept a preset errno"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stodx_nid_postfix("2e-324", &end, 0, &error) == 0.0 && error == 1 && *__error_nid_postfix() == RangeError, "_Stodx below the smallest subnormal"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stofx_nid_postfix("1e-45", &end, 0, &error) == 0x1p-149f && error == 0 && *__error_nid_postfix() == 0 && end[0] == '\0', "_Stofx inexact subnormal"); + error = 99; + Require(_Stofx_nid_postfix("1e-35", &end, -10, &error) == 0x1p-149f && error == 0 && *__error_nid_postfix() == 0, "_Stofx scaled into the subnormal range"); + error = 99; + *__error_nid_postfix() = 7; + const float subnormal = _Stofx_nid_postfix("1e-40", &end, 0, &error); + Require(subnormal > 0x1p-134f && subnormal < 0x1p-132f && error == 0 && *__error_nid_postfix() == 7, "_Stofx subnormal kept a preset errno"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stofx_nid_postfix("1e-46", &end, 0, &error) == 0.0f && error == 1 && *__error_nid_postfix() == RangeError, "_Stofx underflow"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stofx_nid_postfix("1.17549435e-38", &end, 0, &error) == 0x1p-126f && error == 0 && *__error_nid_postfix() == 0, "_Stofx smallest normal"); + error = 99; + Require(_Stoldx_nid_postfix("1e-4940", &end, 0, &error) == 1e-4940L && error == 0 && *__error_nid_postfix() == 0, "_Stoldx subnormal"); + error = 99; + Require(_Stoldx_nid_postfix("1e-4930", &end, -10, &error) == 1e-4940L && error == 0 && *__error_nid_postfix() == 0, "_Stoldx scaled into the subnormal range"); + error = 99; + Require(_Stoldx_nid_postfix("1e-5000", &end, 0, &error) == 0.0L && error == 1 && *__error_nid_postfix() == RangeError, "_Stoldx underflow"); + Require(_Stof_nid_postfix("1.5", &end, 1) == 15.0f, "_Stof"); + Require(_Stold_nid_postfix("1.5", &end, -1) == 0.15L, "_Stold"); + error = 99; + Require(std::isnan(_Stodx_nid_postfix("nan", &end, 2, &error)) && error == 0 && end[0] == '\0', "_Stodx NaN"); +} + +void TestIntegerErrors() { + char* end = nullptr; + int error = 99; + *__error_nid_postfix() = 5; + Require(_Stollx_nid_postfix("123", &end, 0, &error) == 123 && error == 0 && *__error_nid_postfix() == 5, "_Stollx in range"); + Require(_Stollx_nid_postfix("9223372036854775808", &end, 10, &error) == 9223372036854775807ll && error == RangeError && *__error_nid_postfix() == RangeError, "_Stollx overflow"); + *__error_nid_postfix() = 5; + const char* text = " 12"; + for (int base : {1, -1, 37}) { + error = 99; + Require(_Stollx_nid_postfix(text, &end, base, &error) == 0 && error == InvalidArgument && end == text && *__error_nid_postfix() == 5, "_Stollx invalid base"); + error = 99; + Require(_Stolx_nid_postfix(text, &end, base, &error) == 0 && error == InvalidArgument && end == text && *__error_nid_postfix() == 5, "_Stolx invalid base"); + } + Require(_Stoll_nid_postfix(text, &end, 1) == 0 && end == text && *__error_nid_postfix() == 5, "_Stoll invalid base"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stolx_nid_postfix("9223372036854775808", &end, 0, &error) == 9223372036854775807ll && error == RangeError && *__error_nid_postfix() == 0, "_Stolx overflow inside the unsigned range"); + Require(_Stolx_nid_postfix("-18446744073709551615", &end, 10, &error) == (-9223372036854775807ll - 1) && error == RangeError && *__error_nid_postfix() == 0, "_Stolx negative overflow inside the unsigned range"); + Require(_Stolx_nid_postfix("18446744073709551616", &end, 10, &error) == 9223372036854775807ll && error == RangeError && *__error_nid_postfix() == RangeError, "_Stolx overflow past the unsigned range"); + error = 99; + *__error_nid_postfix() = 0; + Require(_Stoulx_nid_postfix("-1", &end, 0, &error) == 18446744073709551615ull && error == 0, "_Stoulx wraps a negative value"); + Require(_Stoullx_nid_postfix("-18446744073709551615", &end, 10, &error) == 1 && error == 0 && *__error_nid_postfix() == 0, "_Stoullx wraps a negated maximum"); + Require(_Stoullx_nid_postfix("18446744073709551616", &end, 10, &error) == 18446744073709551615ull && error == RangeError && *__error_nid_postfix() == RangeError, "_Stoullx overflow"); +} + +void TestDoubleHelpers() { + const double e = 0x1.5bf0a8b145769p+1; + CheckExp(1.0, 1.0, 0, e, Finite, 0, 0, "_Exp e"); + CheckExp(0.0, 1.0, 0, 1.0, Finite, 0, 0, "_Exp zero exponent"); + CheckExp(0.0, 5.0, 0, 5.0, Finite, 0, 0, "_Exp multiplier at zero exponent"); + CheckExp(1.0, -0.0, 0, -0.0, Zero, 0, 0, "_Exp keeps a negative zero multiplier"); + CheckExp(1.0, 0.0, 0, 0.0, Zero, 0, 0, "_Exp zero multiplier"); + CheckExp(-1.0, -0.0, 0, -0.0, Zero, 0, 0, "_Exp negative zero multiplier"); + CheckExp(INFINITY, -0.0, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_Exp positive infinity with zero multiplier is invalid"); + CheckExp(INFINITY, 0.0, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_Exp positive infinity with positive zero is invalid"); + CheckExp(INFINITY, 1.0, 0, INFINITY, Infinite, 0, 0, "_Exp positive infinity"); + CheckExp(INFINITY, -1.0, 0, -INFINITY, Infinite, 0, 0, "_Exp positive infinity preserves the multiplier sign"); + CheckExp(INFINITY, INFINITY, 0, INFINITY, Infinite, 0, 0, "_Exp positive infinity with infinite multiplier"); + CheckExp(INFINITY, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_Exp positive infinity with negative infinite multiplier"); + CheckExp(-INFINITY, 1.0, 0, 0.0, Zero, 0, 0, "_Exp negative infinity"); + CheckExp(-INFINITY, -1.0, 0, -0.0, Zero, 0, 0, "_Exp negative infinity preserves the multiplier sign"); + CheckExp(-INFINITY, 0.0, 0, 0.0, Zero, 0, 0, "_Exp negative infinity with positive zero"); + CheckExp(-INFINITY, -0.0, 0, -0.0, Zero, 0, 0, "_Exp negative infinity with negative zero"); + CheckExp(-INFINITY, INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_Exp negative infinity with infinite multiplier is invalid"); + CheckExp(-INFINITY, -INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_Exp negative infinity with negative infinite multiplier is invalid"); + CheckExp(0.0, INFINITY, 0, INFINITY, Infinite, 0, 0, "_Exp zero exponent with infinite multiplier"); + CheckExp(0.0, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_Exp zero exponent with negative infinite multiplier"); + CheckExp(1.0, INFINITY, 0, INFINITY, Infinite, 0, 0, "_Exp finite exponent with infinite multiplier"); + CheckExp(1.0, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_Exp finite exponent with negative infinite multiplier"); + CheckExp(1.0, std::numeric_limits::quiet_NaN(), 0, 1.0, NotANumber, 0, 0, "_Exp NaN multiplier leaves the exponent"); + CheckExp(INFINITY, std::numeric_limits::quiet_NaN(), 0, INFINITY, NotANumber, 0, 0, "_Exp NaN multiplier leaves an infinite exponent"); + CheckExp(std::numeric_limits::quiet_NaN(), 0.0, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_Exp NaN exponent with zero multiplier"); + CheckExp(std::numeric_limits::quiet_NaN(), 1.0, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_Exp NaN exponent"); + CheckExp(std::numeric_limits::quiet_NaN(), INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_Exp NaN exponent with infinite multiplier"); + CheckExp(1843.0, 1.0, 0, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp positive cutoff"); + CheckExp(1843.0, -1.0, 0, -INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp positive cutoff preserves the multiplier sign"); + CheckExp(1843.0, 1.0, -2660, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp positive cutoff ignores a compensating offset"); + CheckExp(-1843.0, 1.0, 0, 0.0, Zero, 0, 0, "_Exp negative cutoff"); + CheckExp(-1843.0, -1.0, 0, -0.0, Zero, 0, 0, "_Exp negative cutoff preserves the multiplier sign"); + CheckExp(-1843.0, 1.0, 2660, 0.0, Zero, 0, 0, "_Exp negative cutoff ignores a compensating offset"); + CheckExp(1843.0, 0.0, 0, 0.0, Zero, 0, 0, "_Exp positive cutoff with zero multiplier"); + CheckExp(1843.0, std::numeric_limits::quiet_NaN(), 0, 1843.0, NotANumber, 0, 0, "_Exp positive cutoff with NaN multiplier"); + CheckExp(0.0, 1.0, -1074, std::numeric_limits::denorm_min(), Finite, 0, 0, "_Exp exact minimum subnormal"); + CheckExp(0.0, 1.0, -1075, 0.0, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_Exp scaled underflow"); + CheckExp(0.0, -1.0, -1075, -0.0, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_Exp scaled underflow preserves the multiplier sign"); + CheckExp(0.0, 1.0, 100, std::scalbn(1.0, 100), Finite, 0, 0, "_Exp scaled finite result"); + 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"); + + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + double result = 1.0; + Require(_Exp_nid_postfix(&result, 3.0, 0) == Finite && std::fabs(result - 3.0 * e) <= 3.0 * e * 1e-14, "_Exp multiplier"); + result = 1.0; + Require(_Exp_nid_postfix(&result, -2.0, 0) == Finite && std::fabs(result + 2.0 * e) <= 2.0 * e * 1e-14, "_Exp negative multiplier"); + result = 1.0; + Require(_Exp_nid_postfix(&result, 1.0, 10) == Finite && std::fabs(result - e * 1024.0) <= e * 1024.0 * 1e-14, "_Exp scale"); + for (int i = -200; i <= 200; ++i) { + const double x = static_cast(i) / 16.0; + result = x; + Require(_Exp_nid_postfix(&result, 1.0, 0) == Finite && std::fabs(result - std::exp(x)) <= std::fabs(std::exp(x)) * 1e-13, "_Exp finite sweep"); + } + + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 1.0; + Require(_Exp_nid_postfix(&result, std::numeric_limits::denorm_min(), 0) == Finite && std::fpclassify(result) == FP_SUBNORMAL && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp subnormal result classification"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = -740.0; + Require(_Exp_nid_postfix(&result, 1.0, 0) == Finite && std::fpclassify(result) == FP_SUBNORMAL && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp negative exponent subnormal result"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 1.0; + Require(_Exp_nid_postfix(&result, std::numeric_limits::max(), 0) == Infinite && std::isinf(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_INEXACT)) == (FE_OVERFLOW | FE_INEXACT), "_Exp finite multiplier overflow"); + + const double largeExpected = std::scalbn(std::exp(710.0 - 1024.0 * std::log(2.0)) * 1e-100, 1024); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 710.0; + Require(_Exp_nid_postfix(&result, 1e-100, 0) == Finite && std::isfinite(result) && std::fabs(result - largeExpected) <= largeExpected * 1e-13 && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_Exp large exponent stays finite"); + const double smallExpected = std::scalbn(std::exp(-710.0 + 1024.0 * std::log(2.0)) * 1e300, -1024); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = -710.0; + Require(_Exp_nid_postfix(&result, 1e300, 0) == Finite && std::isfinite(result) && std::fabs(result - smallExpected) <= smallExpected * 1e-13 && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_Exp small exponent stays finite"); + const double multiplierExpected = std::exp(-0.5) * std::numeric_limits::max(); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = -0.5; + Require(_Exp_nid_postfix(&result, std::numeric_limits::max(), 0) == Finite && std::isfinite(result) && std::fabs(result - multiplierExpected) <= multiplierExpected * 1e-13 && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_Exp large multiplier stays finite"); + + const float ef = 0x1.5bf0a8p+1f; + CheckFExp(1.0f, 1.0f, 0, ef, Finite, 0, 0, "_FExp e"); + CheckFExp(0.0f, 1.0f, 0, 1.0f, Finite, 0, 0, "_FExp zero exponent"); + CheckFExp(0.0f, 5.0f, 0, 5.0f, Finite, 0, 0, "_FExp multiplier at zero exponent"); + CheckFExp(1.0f, -0.0f, 0, -0.0f, Zero, 0, 0, "_FExp keeps a negative zero multiplier"); + CheckFExp(1.0f, 0.0f, 0, 0.0f, Zero, 0, 0, "_FExp zero multiplier"); + CheckFExp(-1.0f, -0.0f, 0, -0.0f, Zero, 0, 0, "_FExp negative zero multiplier"); + CheckFExp(INFINITY, -0.0f, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_FExp positive infinity with zero multiplier is invalid"); + CheckFExp(INFINITY, 0.0f, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_FExp positive infinity with positive zero is invalid"); + CheckFExp(INFINITY, 1.0f, 0, INFINITY, Infinite, 0, 0, "_FExp positive infinity"); + CheckFExp(INFINITY, -1.0f, 0, -INFINITY, Infinite, 0, 0, "_FExp positive infinity preserves the multiplier sign"); + CheckFExp(INFINITY, INFINITY, 0, INFINITY, Infinite, 0, 0, "_FExp positive infinity with infinite multiplier"); + CheckFExp(INFINITY, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_FExp positive infinity with negative infinite multiplier"); + CheckFExp(-INFINITY, 1.0f, 0, 0.0f, Zero, 0, 0, "_FExp negative infinity"); + CheckFExp(-INFINITY, -1.0f, 0, -0.0f, Zero, 0, 0, "_FExp negative infinity preserves the multiplier sign"); + CheckFExp(-INFINITY, 0.0f, 0, 0.0f, Zero, 0, 0, "_FExp negative infinity with positive zero"); + CheckFExp(-INFINITY, -0.0f, 0, -0.0f, Zero, 0, 0, "_FExp negative infinity with negative zero"); + CheckFExp(-INFINITY, INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_FExp negative infinity with infinite multiplier is invalid"); + CheckFExp(-INFINITY, -INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_FExp negative infinity with negative infinite multiplier is invalid"); + CheckFExp(0.0f, INFINITY, 0, INFINITY, Infinite, 0, 0, "_FExp zero exponent with infinite multiplier"); + CheckFExp(0.0f, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_FExp zero exponent with negative infinite multiplier"); + CheckFExp(1.0f, INFINITY, 0, INFINITY, Infinite, 0, 0, "_FExp finite exponent with infinite multiplier"); + CheckFExp(1.0f, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_FExp finite exponent with negative infinite multiplier"); + CheckFExp(1.0f, std::numeric_limits::quiet_NaN(), 0, 1.0f, NotANumber, 0, 0, "_FExp NaN multiplier leaves the exponent"); + CheckFExp(INFINITY, std::numeric_limits::quiet_NaN(), 0, INFINITY, NotANumber, 0, 0, "_FExp NaN multiplier leaves an infinite exponent"); + CheckFExp(std::numeric_limits::quiet_NaN(), 0.0f, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_FExp NaN exponent with zero multiplier"); + CheckFExp(std::numeric_limits::quiet_NaN(), 1.0f, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_FExp NaN exponent"); + CheckFExp(std::numeric_limits::quiet_NaN(), INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_FExp NaN exponent with infinite multiplier"); + CheckFExp(230.0f, 1.0f, 0, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp positive cutoff"); + CheckFExp(230.0f, -1.0f, 0, -INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp positive cutoff preserves the multiplier sign"); + CheckFExp(230.0f, 1.0f, -332, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp positive cutoff ignores the offset"); + CheckFExp(-230.0f, 1.0f, 0, 0.0f, Zero, 0, 0, "_FExp negative cutoff"); + CheckFExp(-230.0f, -1.0f, 0, -0.0f, Zero, 0, 0, "_FExp negative cutoff preserves the multiplier sign"); + CheckFExp(-230.0f, 1.0f, 332, 0.0f, Zero, 0, 0, "_FExp negative cutoff ignores the offset"); + CheckFExp(230.0f, 0.0f, 0, 0.0f, Zero, 0, 0, "_FExp positive cutoff with zero multiplier"); + CheckFExp(230.0f, std::numeric_limits::quiet_NaN(), 0, 230.0f, NotANumber, 0, 0, "_FExp positive cutoff with NaN multiplier"); + CheckFExp(0.0f, 1.0f, -149, std::numeric_limits::denorm_min(), Finite, 0, 0, "_FExp exact minimum subnormal"); + CheckFExp(0.0f, 1.0f, -150, 0.0f, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_FExp scaled underflow"); + CheckFExp(0.0f, -1.0f, -150, -0.0f, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_FExp scaled underflow preserves the multiplier sign"); + CheckFExp(0.0f, 1.0f, 100, std::scalbn(1.0f, 100), Finite, 0, 0, "_FExp scaled finite result"); + 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"); + + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + float resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, 3.0f, 0) == Finite && std::fabs(resultf - 3.0f * ef) <= 3.0f * ef * 1e-6f, "_FExp multiplier"); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, -2.0f, 0) == Finite && std::fabs(resultf + 2.0f * ef) <= 2.0f * ef * 1e-6f, "_FExp negative multiplier"); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 10) == Finite && std::fabs(resultf - ef * 1024.0f) <= ef * 1024.0f * 1e-6f, "_FExp scale"); + for (int i = -120; i <= 120; ++i) { + const float x = static_cast(i) / 16.0f; + resultf = x; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == Finite && std::fabs(resultf - std::exp(x)) <= std::fabs(std::exp(x)) * 1e-6f, "_FExp finite sweep"); + } + + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, std::numeric_limits::denorm_min(), 0) == Finite && std::fpclassify(resultf) == FP_SUBNORMAL && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp subnormal result classification"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = -100.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == Finite && std::fpclassify(resultf) == FP_SUBNORMAL && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp negative exponent subnormal result"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, std::numeric_limits::max(), 0) == Infinite && std::isinf(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_INEXACT)) == (FE_OVERFLOW | FE_INEXACT), "_FExp finite multiplier overflow"); + + const float largeExpectedf = static_cast(std::exp(100.0) * 1e-35); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = 100.0f; + Require(_FExp_nid_postfix(&resultf, 1e-35f, 0) == Finite && std::isfinite(resultf) && std::fabs(resultf - largeExpectedf) <= largeExpectedf * 1e-6f && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_FExp large exponent stays finite"); + const float smallExpectedf = static_cast(std::exp(-100.0) * 1e30); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = -100.0f; + Require(_FExp_nid_postfix(&resultf, 1e30f, 0) == Finite && std::isfinite(resultf) && std::fabs(resultf - smallExpectedf) <= smallExpectedf * 1e-6f && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_FExp small exponent stays finite"); + const float multiplierExpectedf = static_cast(std::exp(-0.5) * std::numeric_limits::max()); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = -0.5f; + Require(_FExp_nid_postfix(&resultf, std::numeric_limits::max(), 0) == Finite && std::isfinite(resultf) && std::fabs(resultf - multiplierExpectedf) <= multiplierExpectedf * 1e-6f && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_FExp large multiplier stays finite"); +} + +void TestDispatch() { + Require(_Sin_nid_postfix(0.5, 0) == std::sin(0.5) && _Sin_nid_postfix(0.5, 1) == std::cos(0.5) && _Sin_nid_postfix(0.5, 7) == std::cos(0.5), "_Sin"); + Require(_FSin_nid_postfix(0.5f, 0) == std::sin(0.5f) && _FSin_nid_postfix(0.5f, 2) == std::cos(0.5f), "_FSin"); + Require(_LSin_nid_postfix(0.5L, 0) == std::sin(0.5L) && _LSin_nid_postfix(0.5L, 3) == std::cos(0.5L), "_LSin"); + Require(_Log_nid_postfix(8.0, 0) == std::log(8.0) && _Log_nid_postfix(1000.0, 1) == 3.0 && _Log_nid_postfix(1000.0, 5) == 3.0 && _Log_nid_postfix(8.0, -1) == 3.0, "_Log"); + Require(_FLog_nid_postfix(8.0f, 0) == std::log(8.0f) && _FLog_nid_postfix(100.0f, 1) == 2.0f && _FLog_nid_postfix(8.0f, -3) == 3.0f, "_FLog"); +} + +void TestLongDoubleHelpers() { + Require(_LDivide_nid_postfix(3.0L, 2.0L) == 1.5L, "_LDivide quotient"); + Require(std::isinf(_LDivide_nid_postfix(1.0L, 0.0L)), "_LDivide infinity"); + Require(std::isnan(_LDivide_nid_postfix(0.0L, 0.0L)), "_LDivide invalid"); + Require(_LRecip_nid_postfix(4.0L) == 0.25L, "_LRecip quotient"); + Require(std::isinf(_LRecip_nid_postfix(-0.0L)) && std::signbit(_LRecip_nid_postfix(-0.0L)), "_LRecip signed infinity"); + Require(_LLog_nid_postfix(8.0L, 0) == std::log(8.0L), "_LLog natural"); + Require(std::fabs(_LLog_nid_postfix(1000.0L, 1) - 3.0L) <= 1e-17L && std::fabs(_LLog_nid_postfix(1000.0L, 5) - 3.0L) <= 1e-17L, "_LLog base 10"); + Require(_LLog_nid_postfix(8.0L, -1) == 3.0L && _LLog_nid_postfix(8.0L, -3) == 3.0L, "_LLog base 2"); +} + +} + +int main() { + TestStod(); + TestStoll(); + TestDoubleHelpers(); + TestFloatErrors(); + TestIntegerErrors(); + TestArithmetic(); + TestDispatch(); + TestLongDoubleHelpers(); +} diff --git a/core/libs/tests/GuestDirectory.cpp b/core/libs/tests/GuestDirectory.cpp index 4df4394622..f5646097dc 100644 --- a/core/libs/tests/GuestDirectory.cpp +++ b/core/libs/tests/GuestDirectory.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/GuestDirectory.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" @@ -15,21 +17,12 @@ #ifdef _WIN32 #include #endif -extern "C" { -void* APS5_VABI opendir_nid_postfix(const char*); -GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void*); -int APS5_VABI readdir_r_nid_postfix(void*, GuestDirectoryEntry*, GuestDirectoryEntry**); -int APS5_VABI closedir_nid_postfix(void*); -void APS5_VABI rewinddir_nid_postfix(void*); -std::int64_t APS5_VABI telldir_nid_postfix(void*); -void APS5_VABI seekdir_nid_postfix(void*, std::int64_t); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI lstat_nid_postfix(const char*, FileStat*); -} + static void Require(bool value) { if (!value) std::abort(); } static std::map ReadFileNumbers(void* directory) { std::map numbers; rewinddir_nid_postfix(directory); + while (auto* entry = readdir_nid_postfix(directory)) { Require(entry->fileNumber != 0); numbers[entry->name] = entry->fileNumber; @@ -105,10 +98,12 @@ int main() { Require(entries.at("subdirectory") == 4); Require(entries.at(".") == 4 && entries.at("..") == 4); rewinddir_nid_postfix(directory); + std::size_t count = 0; while (readdir_nid_postfix(directory)) ++count; Require(count == entries.size()); rewinddir_nid_postfix(directory); + std::map copies; GuestDirectoryEntry buffer; GuestDirectoryEntry* result = nullptr; @@ -129,6 +124,7 @@ int main() { result = &buffer; Require(readdir_r_nid_postfix(directory, &buffer, &result) == 0 && result == nullptr); rewinddir_nid_postfix(directory); + *__error_nid_postfix() = 13; auto* first = readdir_nid_postfix(directory); Require(first != nullptr); @@ -141,6 +137,7 @@ int main() { bool nullEntryThrows = false; try { readdir_r_nid_postfix(directory, nullptr, &result); + } catch (const std::invalid_argument&) { nullEntryThrows = true; } @@ -148,11 +145,13 @@ int main() { bool nullResultThrows = false; try { readdir_r_nid_postfix(directory, &buffer, nullptr); + } catch (const std::invalid_argument&) { nullResultThrows = true; } Require(nullResultThrows); rewinddir_nid_postfix(directory); + Require(readdir_nid_postfix(directory) != nullptr); const std::int64_t second = telldir_nid_postfix(directory); Require(second != -1); @@ -161,6 +160,7 @@ int main() { const std::string secondName = entryAfterTell->name; while (readdir_nid_postfix(directory)) {} seekdir_nid_postfix(directory, second); + auto* entryAfterSeek = readdir_nid_postfix(directory); Require(entryAfterSeek != nullptr && secondName == entryAfterSeek->name); Require(entryAfterSeek->fileNumber == fileNumbers.at(secondName)); @@ -168,6 +168,7 @@ int main() { Require(telldir_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == 9); *__error_nid_postfix() = 0; seekdir_nid_postfix(nullptr, second); + Require(*__error_nid_postfix() == 9); Require(closedir_nid_postfix(directory) == 0); Require(opendir_nid_postfix((root / "missing").string().c_str()) == nullptr); diff --git a/core/libs/tests/GuestDirectoryBuffer.cpp b/core/libs/tests/GuestDirectoryBuffer.cpp index e5d0c6c122..02e2ff0246 100644 --- a/core/libs/tests/GuestDirectoryBuffer.cpp +++ b/core/libs/tests/GuestDirectoryBuffer.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include "prx/libkernel/KernelErrors.hpp" @@ -13,18 +15,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -int APS5_VABI sceKernelGetdents(int, char*, int); -int APS5_VABI sceKernelGetdirentries(int, char*, int, std::int64_t*); -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelMprotect(void*, std::size_t, int); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Directory buffer check failed at line %d\n", line); @@ -88,6 +78,7 @@ static void CheckOversizedName() { bool threw = false; try { sceKernelGetdents(directory, buffer.data(), static_cast(buffer.size())); + } catch (const std::runtime_error&) { threw = true; } @@ -121,7 +112,7 @@ static void CheckReadOnlyDestination() { static_cast(mapping)) == SCE_KERNEL_ERROR_EFAULT); Require(sceKernelMprotect(mapping, page, 3) == 0); Require(sceKernelGetdirentries(directory, buffer.data(), static_cast(buffer.size()), &base) > 0); - Require(sceKernelMunmap(mapping, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapping), page) == 0); Require(sceKernelReleaseDirectMemory(physical, page) == 0); Require(sceKernelClose(directory) == 0); std::filesystem::remove_all(root); diff --git a/core/libs/tests/GuestDirectoryClose.cpp b/core/libs/tests/GuestDirectoryClose.cpp index 68d3d60176..5a59797a0e 100644 --- a/core/libs/tests/GuestDirectoryClose.cpp +++ b/core/libs/tests/GuestDirectoryClose.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -10,13 +12,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelGetdents(int, char*, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI _close_nid_postfix(int); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Directory close check failed at line %d\n", line); diff --git a/core/libs/tests/GuestDirectoryEntries.cpp b/core/libs/tests/GuestDirectoryEntries.cpp index 35bf2413c9..91b41aea16 100644 --- a/core/libs/tests/GuestDirectoryEntries.cpp +++ b/core/libs/tests/GuestDirectoryEntries.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include @@ -11,20 +14,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -int APS5_VABI sceKernelLseek(int, std::int64_t, int); -int APS5_VABI sceKernelGetdents(int, char*, int); -int APS5_VABI sceKernelGetdirentries(int, char*, int, std::int64_t*); -int APS5_VABI getdents_nid_postfix(int, char*, int); -int APS5_VABI getdirentries_nid_postfix(int, char*, int, std::int64_t*); -int* APS5_VABI __error_nid_postfix(); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Directory entry check failed at line %d\n", line); diff --git a/core/libs/tests/GuestDirectoryLinkTypes.cpp b/core/libs/tests/GuestDirectoryLinkTypes.cpp new file mode 100644 index 0000000000..fb9b0f0845 --- /dev/null +++ b/core/libs/tests/GuestDirectoryLinkTypes.cpp @@ -0,0 +1,76 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Directory link type check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static constexpr std::uint8_t TypeDirectory = 4; +static constexpr std::uint8_t TypeRegular = 8; +static constexpr std::uint8_t TypeLink = 10; + +static std::map Types(const char* path) { + const int directory = sceKernelOpen(path, SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); + Require(directory >= 0); + std::array buffer{}; + std::map types; + for (;;) { + const int read = sceKernelGetdents(directory, buffer.data(), static_cast(buffer.size())); + Require(read >= 0); + if (read == 0) break; + for (int offset = 0; offset < read;) { + std::uint16_t recordLength = 0; + std::memcpy(&recordLength, buffer.data() + offset + 4, sizeof(recordLength)); + Require(recordLength != 0 && offset + recordLength <= read); + types[std::string(buffer.data() + offset + 8)] = static_cast(buffer[offset + 6]); + offset += recordLength; + } + } + Require(sceKernelClose(directory) == 0); + return types; +} + +int main() { + const auto root = std::filesystem::temp_directory_path() / + ("anyps5-directory-link-types-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directories(root / "app0" / "real")); + Require(std::filesystem::create_directories(root / "dump" / "packed")); + Require(std::filesystem::create_directories(root / "data")); + { std::ofstream stream(root / "dump" / "eboot.bin", std::ios::binary); stream << "x"; } + std::filesystem::create_directory_symlink(root / "dump" / "packed", root / "app0" / "packed"); + std::filesystem::create_symlink(root / "dump" / "eboot.bin", root / "app0" / "eboot.bin"); + std::filesystem::create_symlink(root / "dump" / "missing", root / "app0" / "dangling"); + std::filesystem::create_directory_symlink(root / "dump" / "packed", root / "data" / "saved"); + std::filesystem::current_path(root); + + auto app0 = Types("/app0"); + Require(app0.contains("real") && app0["real"] == TypeDirectory); + Require(app0.contains("packed") && app0["packed"] == TypeDirectory); + Require(app0.contains("eboot.bin") && app0["eboot.bin"] == TypeRegular); + Require(app0.contains("dangling") && app0["dangling"] == TypeLink); + + auto data = Types("/data"); + Require(data.contains("saved") && data["saved"] == TypeLink); + + std::error_code error; + std::filesystem::current_path(std::filesystem::temp_directory_path(), error); + std::filesystem::remove_all(root, error); + std::puts("directory link type tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestDirectoryRewind.cpp b/core/libs/tests/GuestDirectoryRewind.cpp index 00217b646e..424c5bda23 100644 --- a/core/libs/tests/GuestDirectoryRewind.cpp +++ b/core/libs/tests/GuestDirectoryRewind.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/DirectoryDescriptor.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -14,15 +16,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -int APS5_VABI sceKernelGetdents(int, char*, int); -std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); -int APS5_VABI dup_nid_postfix(int); -int APS5_VABI dup2_nid_postfix(int, int); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Directory rewind check failed at line %d\n", line); @@ -31,7 +24,6 @@ static void Check(bool value, int line) { } #define Require(value) Check((value), __LINE__) - static std::set ReadAll(int directory) { std::set names; std::array buffer{}; diff --git a/core/libs/tests/GuestDiscMap.cpp b/core/libs/tests/GuestDiscMap.cpp index 41f711baff..7c3811f9a5 100644 --- a/core/libs/tests/GuestDiscMap.cpp +++ b/core/libs/tests/GuestDiscMap.cpp @@ -1,14 +1,10 @@ +#include "prx/libSceDiscMap/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -std::int32_t APS5_VABI sceDiscMapIsRequestOnHDD(const char* path, std::int64_t offset, std::int64_t size, std::int32_t* result); -std::int32_t APS5_VABI sceDiscMapUnknown_ioKMruft1ek(const char* path, std::int64_t offset, std::int64_t size, void* flags, void* out1, void* out2); -} - namespace { constexpr std::int32_t noBitmapInfo = static_cast(0x81100004); diff --git a/core/libs/tests/GuestDup.cpp b/core/libs/tests/GuestDup.cpp index 4e0451ae85..03db1dcba6 100644 --- a/core/libs/tests/GuestDup.cpp +++ b/core/libs/tests/GuestDup.cpp @@ -1,25 +1,13 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include #include #include -extern "C" { -int APS5_VABI dup_nid_postfix(int); -int APS5_VABI dup2_nid_postfix(int, int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI close_nid_postfix(int); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -int APS5_VABI socketpair_nid_postfix(int, int, int, int*); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } constexpr int GuestEbadf = 9; @@ -104,6 +92,8 @@ static void BadDescriptors() { #ifdef _WIN32 const auto previous = _set_invalid_parameter_handler(RecordInvalidParameter); #endif + Require(dup2_nid_postfix(pipe[0], closed) == closed); + Require(close_nid_postfix(closed) == 0); RequireFailure(close_nid_postfix(closed), GuestEbadf); RequireFailure(close_nid_postfix(-1), GuestEbadf); #ifdef _WIN32 @@ -190,6 +180,7 @@ static void DuplicatesShareStatusFlags() { constexpr int SetDescriptorFlags = 2; constexpr int GetFlags = 3; constexpr int SetFlags = 4; + constexpr int ReadWrite = 2; constexpr int NonBlocking = 4; constexpr int GuestEagain = 35; int pipe[2]; @@ -197,18 +188,20 @@ static void DuplicatesShareStatusFlags() { Require(pipe_nid_postfix(pipe) == 0); Require(pipe_nid_postfix(other) == 0); Require(fcntl_nid_postfix(pipe[0], SetDescriptorFlags, 1) == 0); + *__error_nid_postfix() = 0; + Require(fcntl_nid_postfix(pipe[0], 0, INT_MAX) == -1 && *__error_nid_postfix() == 22); const int duplicate = dup_nid_postfix(pipe[0]); Require(duplicate >= 0); Require(fcntl_nid_postfix(duplicate, GetDescriptorFlags) == 0); Require(fcntl_nid_postfix(duplicate, SetFlags, NonBlocking) == 0); - Require(fcntl_nid_postfix(pipe[0], GetFlags) == NonBlocking); + Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadWrite | NonBlocking)); Require(dup2_nid_postfix(pipe[0], other[0]) == other[0]); Require(fcntl_nid_postfix(other[0], GetDescriptorFlags) == 0); - Require(fcntl_nid_postfix(other[0], GetFlags) == NonBlocking); + Require(fcntl_nid_postfix(other[0], GetFlags) == (ReadWrite | NonBlocking)); char buffer[4] = {}; RequireFailure(static_cast(read_nid_postfix(other[0], buffer, sizeof(buffer))), GuestEagain); Require(fcntl_nid_postfix(pipe[0], SetFlags, 0) == 0); - Require(fcntl_nid_postfix(duplicate, GetFlags) == 0); + Require(fcntl_nid_postfix(duplicate, GetFlags) == ReadWrite); Require(fcntl_nid_postfix(pipe[0], GetDescriptorFlags) == 1); for (const int descriptor : {pipe[0], pipe[1], other[0], other[1], duplicate}) Require(close_nid_postfix(descriptor) == 0); } diff --git a/core/libs/tests/GuestDynamicCast.cpp b/core/libs/tests/GuestDynamicCast.cpp index c7892a5e96..5a4212d578 100644 --- a/core/libs/tests/GuestDynamicCast.cpp +++ b/core/libs/tests/GuestDynamicCast.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include #include @@ -11,8 +12,7 @@ struct Object { const void* vtable; }; extern "C" { extern const unsigned char _ZTVN10__cxxabiv117__class_type_infoE_nid_postfix[]; extern const unsigned char _ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix[]; -void* APS5_VABI __dynamic_cast_nid_postfix(const void*, const void*, const void*, std::ptrdiff_t); -LibcException::Header* APS5_VABI __cxa_init_primary_exception_nid_postfix(void*, std::type_info*, void (*)(void*)); + } namespace { @@ -32,14 +32,15 @@ const Vtable DerivedVtable{0, &DerivedType, nullptr}; int main() { const Object derived{&DerivedVtable.first}; const Object base{&BaseVtable.first}; - Require(__dynamic_cast_nid_postfix(&derived, &BaseType, &DerivedType, 0) == &derived); - Require(__dynamic_cast_nid_postfix(&derived, &BaseType, &OtherType, 0) == nullptr); - Require(__dynamic_cast_nid_postfix(&base, &BaseType, &DerivedType, 0) == nullptr); - Require(__dynamic_cast_nid_postfix(nullptr, &BaseType, &DerivedType, 0) == nullptr); + Require(__dynamic_cast_nid_postfix(&derived, reinterpret_cast(&BaseType), reinterpret_cast(&DerivedType), 0) == &derived); + Require(__dynamic_cast_nid_postfix(&derived, reinterpret_cast(&BaseType), reinterpret_cast(&OtherType), 0) == nullptr); + Require(__dynamic_cast_nid_postfix(&base, reinterpret_cast(&BaseType), reinterpret_cast(&DerivedType), 0) == nullptr); + Require(__dynamic_cast_nid_postfix(nullptr, reinterpret_cast(&BaseType), reinterpret_cast(&DerivedType), 0) == nullptr); void* object = __cxa_allocate_exception_nid_postfix(16); auto* type = reinterpret_cast(const_cast(&DerivedType)); LibcException::Header* header = __cxa_init_primary_exception_nid_postfix(object, type, Destroy); Require(header == LibcException::FromObject(object)); Require(header->type == type && header->destructor == Destroy && header->adjusted == object); __cxa_free_exception_nid_postfix(object); + } diff --git a/core/libs/tests/GuestDynamicLoader.cpp b/core/libs/tests/GuestDynamicLoader.cpp index 8ea9fe7c93..57bea0de5b 100644 --- a/core/libs/tests/GuestDynamicLoader.cpp +++ b/core/libs/tests/GuestDynamicLoader.cpp @@ -1,27 +1,24 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include +#include #include #include -#ifndef _WIN32 +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else #include #endif #include #include #include -extern "C" { -int APS5_VABI sceKernelDlsym(std::int32_t, const char*, void**); -void* APS5_VABI dlopen_nid_postfix(const char*, int); -void* APS5_VABI dlsym_nid_postfix(void*, const char*); -int APS5_VABI dlclose_nid_postfix(void*); -char* APS5_VABI dlerror_nid_postfix(); -int APS5_VABI _sceKernelRtldThreadAtexitIncrement_nid_postfix(const void*); -int APS5_VABI _sceKernelRtldThreadAtexitDecrement_nid_postfix(const void*); -int APS5_VABI sceKernelLoadStartModule(const char*, std::size_t, const void*, unsigned, const void*, int*); -int APS5_VABI sceKernelStopUnloadModule(int, std::size_t, const void*, unsigned, const void*, int*); -} + static void Require(bool value, const std::source_location& location = std::source_location::current()) { if (value) return; std::fprintf(stderr, "guest_dynamic_loader: failed at %s:%u\n", location.file_name(), location.line()); @@ -37,6 +34,28 @@ static bool ThrowsRuntimeError(TFunction function) { } return false; } +static bool FailsAsUnrelinked(const std::filesystem::path& module) { + void* handle = dlopen_nid_postfix(module.string().c_str(), 2); + const char* error = dlerror_nid_postfix(); + return handle == nullptr && error != nullptr && std::strstr(error, module.filename().string().c_str()) != nullptr && + std::strstr(error, "--module-dir") != nullptr; +} +static void CheckUnrelinkedModule(const std::filesystem::path& module) { +#ifdef _WIN32 + const UINT processMode = SetErrorMode(0); + DWORD threadMode = 0; + const bool threadModeSet = SetThreadErrorMode(SEM_NOGPFAULTERRORBOX, &threadMode) != 0; +#endif + const bool first = FailsAsUnrelinked(module); + const bool again = FailsAsUnrelinked(module); +#ifdef _WIN32 + const DWORD threadModeAfter = GetThreadErrorMode(); + SetThreadErrorMode(threadMode, nullptr); + SetErrorMode(processMode); + Require(threadModeSet && threadModeAfter == SEM_NOGPFAULTERRORBOX); +#endif + Require(first && again); +} #ifdef _WIN32 static void CheckGlobalLookupOrder(const char* path) { const std::filesystem::path other = "anyps5-global-order-for-test.guest.prx"; @@ -87,6 +106,7 @@ static bool CheckGlobalVisibility(const char* path, bool globalFirst) { std::fprintf(stderr, "RTLD_GLOBAL lost after closing its handle: global_first=%d scope=%s; surviving handle returns %d\n", globalFirst, scope == nullptr ? "NULL" : "-2", add(20, 22)); dlerror_nid_postfix(); + visible = false; } } @@ -108,7 +128,8 @@ int main(int argc, char** argv) { Require(dlerror_nid_postfix() != nullptr); Require(dlerror_nid_postfix() == nullptr); Require(dlopen_nid_postfix("anyps5-missing-module-for-test.prx", 2) == nullptr); - Require(dlerror_nid_postfix() != nullptr); + const char* missing = dlerror_nid_postfix(); + Require(missing != nullptr && std::strstr(missing, "--module-dir") == nullptr); void* mainSymbol = nullptr; const int mainResult = sceKernelDlsym(0, "GuestDefaultScopeOnly", &mainSymbol); std::fprintf(stderr, "sceKernelDlsym(0, GuestDefaultScopeOnly): result=0x%08x, symbol=%p\n", static_cast(mainResult), mainSymbol); @@ -117,6 +138,7 @@ int main(int argc, char** argv) { Require(reinterpret_cast(mainSymbol)(6, 7) == 42); Require(sceKernelDlsym(0, "anyps5_missing_main_symbol", &mainSymbol) == static_cast(0x80020003u)); dlerror_nid_postfix(); + void* executable = dlopen_nid_postfix(nullptr, 2); Require(executable != nullptr && dlclose_nid_postfix(executable) == 0); #ifdef _WIN32 @@ -155,10 +177,12 @@ int main(int argc, char** argv) { const std::filesystem::path guest = "anyps5-relinked-module-for-test.prx"; auto relinked = guest; relinked += ".guest.prx"; + std::filesystem::remove(relinked); { std::ofstream elf(guest, std::ios::binary); elf.write("\x7f" "ELF", 4); } + CheckUnrelinkedModule(guest); std::filesystem::copy_file(argv[1], relinked, std::filesystem::copy_options::overwrite_existing); void* redirected = dlopen_nid_postfix(guest.string().c_str(), 2); Require(redirected != nullptr); @@ -187,6 +211,54 @@ int main(int argc, char** argv) { std::error_code notEmpty; if (createdLibs) std::filesystem::remove("libs", notEmpty); + const std::filesystem::path nestedRoot = "anyps5-relinked-prx-dir-for-test"; + const std::filesystem::path nestedGuest = nestedRoot / "Engine.Render.prx"; + const std::filesystem::path nestedRelinked = nestedRoot / "prx" / "Engine.Render.prx.guest.prx"; + std::filesystem::create_directories(nestedRelinked.parent_path()); + { + std::ofstream elf(nestedGuest, std::ios::binary); + elf.write("\x7f" "ELF", 4); + } + std::filesystem::copy_file(argv[1], nestedRelinked, std::filesystem::copy_options::overwrite_existing); + void* nested = dlopen_nid_postfix(nestedGuest.string().c_str(), 2); + Require(nested != nullptr); + add = reinterpret_cast(dlsym_nid_postfix(nested, "GuestModuleAdd")); + Require(add && add(20, 22) == 42); + Require(dlclose_nid_postfix(nested) == 0); + std::filesystem::remove_all(nestedRoot); + + const std::filesystem::path layeredRoot = "anyps5-relinked-prx-layers-for-test"; + const std::filesystem::path layeredGuest = layeredRoot / "Engine.Layers.prx"; + std::filesystem::create_directories(layeredRoot / "prx"); + { + std::ofstream elf(layeredGuest, std::ios::binary); + elf.write("" "ELF", 4); + std::ofstream broken(layeredRoot / "prx" / "Engine.Layers.prx.guest.prx", std::ios::binary); + broken << "not a module"; + } + std::filesystem::copy_file(argv[1], layeredRoot / "ENGINE.LAYERS.PRX.GUEST.PRX", std::filesystem::copy_options::overwrite_existing); + void* layered = dlopen_nid_postfix(layeredGuest.string().c_str(), 2); + Require(layered != nullptr); + add = reinterpret_cast(dlsym_nid_postfix(layered, "GuestModuleAdd")); + Require(add && add(19, 23) == 42); + Require(dlclose_nid_postfix(layered) == 0); + std::filesystem::remove_all(layeredRoot); + + const std::filesystem::path foldedRoot = "anyps5-relinked-prx-folded-for-test"; + const std::filesystem::path foldedGuest = foldedRoot / "Engine.Folded.prx"; + std::filesystem::create_directories(foldedRoot / "prx"); + { + std::ofstream elf(foldedGuest, std::ios::binary); + elf.write("" "ELF", 4); + } + std::filesystem::copy_file(argv[1], foldedRoot / "prx" / "ENGINE.FOLDED.PRX.GUEST.PRX", std::filesystem::copy_options::overwrite_existing); + void* folded = dlopen_nid_postfix(foldedGuest.string().c_str(), 2); + Require(folded != nullptr); + add = reinterpret_cast(dlsym_nid_postfix(folded, "GuestModuleAdd")); + Require(add && add(10, 32) == 42); + Require(dlclose_nid_postfix(folded) == 0); + std::filesystem::remove_all(foldedRoot); + void* pinned = dlopen_nid_postfix(argv[1], 2); add = reinterpret_cast(dlsym_nid_postfix(pinned, "GuestModuleAdd")); Require(add && _sceKernelRtldThreadAtexitIncrement_nid_postfix(reinterpret_cast(add)) == 0); @@ -198,9 +270,11 @@ int main(int argc, char** argv) { Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast(add)) == 0); Require(_sceKernelRtldThreadAtexitIncrement_nid_postfix(reinterpret_cast(&ThrowsRuntimeError)) == 0); Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast(&ThrowsRuntimeError)) == 0); - Require(ThrowsRuntimeError([] { _sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast(&Require)); })); int local = 0; - Require(ThrowsRuntimeError([&] { _sceKernelRtldThreadAtexitIncrement_nid_postfix(&local); })); + Require(_sceKernelRtldThreadAtexitIncrement_nid_postfix(&local) == -1); + Require(_sceKernelRtldThreadAtexitIncrement_nid_postfix(nullptr) == -1); + Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(&local) == 0); + Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(nullptr) == 0); #ifndef _WIN32 Require(dlsym_nid_postfix(nullptr, "malloc") == nullptr); Require(dlsym_nid_postfix(nullptr, "dlopen") == nullptr); diff --git a/core/libs/tests/GuestDynamicLoaderVariants.cpp b/core/libs/tests/GuestDynamicLoaderVariants.cpp index 5dd0f764b8..adeb771494 100644 --- a/core/libs/tests/GuestDynamicLoaderVariants.cpp +++ b/core/libs/tests/GuestDynamicLoaderVariants.cpp @@ -1,12 +1,9 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -void* APS5_VABI dlopen_nid_postfix(const char*, int); -void* APS5_VABI dlsym_nid_postfix(void*, const char*); -int APS5_VABI dlclose_nid_postfix(void*); -} + static void Require(bool value, const std::source_location& location = std::source_location::current()) { if (value) return; std::fprintf(stderr, "guest_dynamic_loader_variants: failed at %s:%u\n", location.file_name(), location.line()); diff --git a/core/libs/tests/GuestElfPhdrMatch.cpp b/core/libs/tests/GuestElfPhdrMatch.cpp index 3e8d585a70..4d0797a548 100644 --- a/core/libs/tests/GuestElfPhdrMatch.cpp +++ b/core/libs/tests/GuestElfPhdrMatch.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/specifics/linux/ElfTypes.hpp" #include "SceTypes.hpp" @@ -5,10 +6,6 @@ #include #include -extern "C" { -int APS5_VABI __elf_phdr_match_addr_nid_postfix(dl_phdr_info*, void*); -} - static void Require(bool condition) { if (!condition) std::abort(); } diff --git a/core/libs/tests/GuestEntitlements.cpp b/core/libs/tests/GuestEntitlements.cpp new file mode 100644 index 0000000000..6960f9b349 --- /dev/null +++ b/core/libs/tests/GuestEntitlements.cpp @@ -0,0 +1,74 @@ +#include "prx/libSceNpEntitlementAccess/include/Api.hpp" +#include "SceTypes.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +static void SetEntitlements(const std::filesystem::path& file) { +#ifdef _WIN32 + Require(_wputenv_s(L"ANYPS5_ENTITLEMENTS", file.wstring().c_str()) == 0); +#else + Require(::setenv("ANYPS5_ENTITLEMENTS", file.c_str(), 1) == 0); +#endif +} + +int main(int argc, char** argv) { + const auto root = std::filesystem::temp_directory_path() / ("anyps5_guest_entitlements-" + std::to_string(std::random_device{}())); + const auto directory = root / std::filesystem::path(u8"dlc-a\u00e7\u00e3o-\u65e5\u672c"); + const auto file = directory / "anyps5-entitlements.ini"; + if (argc == 2 && std::string_view(argv[1]) == "missing") { + std::uint32_t skuFlag = 0; + Require(sceNpEntitlementAccessGetSkuFlag(nullptr) == static_cast(0x80558003)); + Require(sceNpEntitlementAccessGetSkuFlag(&skuFlag) == 0 && skuFlag == 3); + std::uint32_t pftFlag = 1; + Require(sceNpEntitlementAccessGetPftFlag(nullptr) == static_cast(0x817D0002)); + Require(sceNpEntitlementAccessGetPftFlag(&pftFlag) == 0 && pftFlag == 0); + std::uint32_t trialFlag = 1; + Require(sceNpEntitlementAccessGetGameTrialsFlag(nullptr) == static_cast(0x817D0002)); + Require(sceNpEntitlementAccessGetGameTrialsFlag(&trialFlag) == 0 && trialFlag == 0); + Require(sceNpEntitlementAccessGenerateTransactionId(nullptr) == static_cast(0x80558003)); + SetEntitlements(file); + std::uint32_t hits = 0; + bool rejected = false; + try { + sceNpEntitlementAccessGetAddcontEntitlementInfoList(0, nullptr, 0, &hits); + + } catch (const std::runtime_error& error) { + rejected = std::string_view(error.what()).starts_with("NpEntitlementAccess: cannot read "); + } + Require(rejected); + std::puts("Guest entitlements rejected a missing configured file"); + return 0; + } + std::filesystem::create_directories(directory); + { + std::ofstream ini(file, std::ios::binary); + ini << "# owned add-ons\nFIRSTADDON0000\n SECONDADDON ; second\n"; + Require(static_cast(ini)); + } + SetEntitlements(file); + NpEntitlementAccessAddcontEntitlementInfo list[3]{}; + std::uint32_t hits = 0; + Require(sceNpEntitlementAccessGetAddcontEntitlementInfoList(0, list, 3, &hits) == 0); + Require(hits == 2); + Require(std::strcmp(list[0].entitlement_label.data, "FIRSTADDON0000") == 0); + Require(std::strcmp(list[1].entitlement_label.data, "SECONDADDON") == 0); + NpUnifiedEntitlementLabel label{}; + std::memcpy(label.data, "SECONDADDON", 11); + NpEntitlementAccessAddcontEntitlementInfo info{}; + Require(sceNpEntitlementAccessGetAddcontEntitlementInfo(0, &label, &info) == 0); + Require(std::strcmp(info.entitlement_label.data, "SECONDADDON") == 0 && info.package_type == 3 && info.download_status == 4); + std::filesystem::remove_all(root); + std::puts("Guest entitlements passed"); + return 0; +} diff --git a/core/libs/tests/GuestEnvironment.cpp b/core/libs/tests/GuestEnvironment.cpp index 46e2b2b11b..aab45e4742 100644 --- a/core/libs/tests/GuestEnvironment.cpp +++ b/core/libs/tests/GuestEnvironment.cpp @@ -1,14 +1,9 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -char* APS5_VABI getenv_nid_postfix(const char*); -int APS5_VABI setenv_nid_postfix(const char*, const char*, int); -int APS5_VABI unsetenv_nid_postfix(const char*); -int APS5_VABI putenv_nid_postfix(char*); -int* APS5_VABI __error_nid_postfix(); -} + static void Require(bool value) { if (!value) std::abort(); } int main() { const char* key = "ANYPS5_GUEST_ENV_TEST_4C27"; diff --git a/core/libs/tests/GuestEqueueEvents.cpp b/core/libs/tests/GuestEqueueEvents.cpp index 8e97329913..1a990f8c23 100644 --- a/core/libs/tests/GuestEqueueEvents.cpp +++ b/core/libs/tests/GuestEqueueEvents.cpp @@ -1,41 +1,15 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Equeue/Equeue.hpp" #include "SceTypes.hpp" #include #include #include +#include #include #include -extern "C" { -int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); -int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo); -int APS5_VABI sceKernelAddUserEvent(KernelEqueue eq, int id); -int APS5_VABI sceKernelAddUserEventEdge(KernelEqueue eq, int id); -int APS5_VABI sceKernelTriggerUserEvent(KernelEqueue eq, int id, void* udata); -int APS5_VABI sceKernelDeleteUserEvent(KernelEqueue eq, int id); -int APS5_VABI sceKernelAddAmprEvent(KernelEqueue eq, int id, void* udata); -int APS5_VABI sceKernelDeleteAmprEvent(KernelEqueue eq, int id); -int APS5_VABI EqueueTriggerEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, int16_t filter, void* triggerData); -int APS5_VABI sceKernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts, void* udata); -int APS5_VABI sceKernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec, void* udata); -int APS5_VABI sceKernelDeleteTimerEvent(KernelEqueue eq, int id); -intptr_t APS5_VABI sceKernelGetEventData(const KernelEvent* ev); -int APS5_VABI sceKernelGetEventError(const KernelEvent* ev); -intptr_t APS5_VABI sceKernelGetEventFflags(const KernelEvent* ev); -int APS5_VABI sceKernelGetEventFilter(const KernelEvent* ev); -uintptr_t APS5_VABI sceKernelGetEventId(const KernelEvent* ev); -void* APS5_VABI sceKernelGetEventUserData(const KernelEvent* ev); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_ENOENT = static_cast(0x80020002); -static constexpr int SCE_KERNEL_ERROR_EBADF = static_cast(0x80020009); -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003c); -static constexpr int EVFILT_TIMER = -7; -static constexpr int EVFILT_USER = -11; -static constexpr int EVFILT_HRTIMER = -15; -static constexpr int EVFILT_AMPR = -25; -static constexpr std::uint16_t EV_ERROR = 0x4000; static void Require(bool value) { if (!value) std::abort(); } @@ -49,6 +23,16 @@ static bool RejectsNull(TResult (APS5_VABI *accessor)(const KernelEvent*)) { return false; } +static bool RejectsWideFflags(const KernelEvent* ev) { + try { + sceKernelGetEventError(ev); + + } catch (const std::runtime_error&) { + return true; + } + return false; +} + static void SleepAtLeast(std::chrono::steady_clock::duration duration) { const auto start = std::chrono::steady_clock::now(); while (std::chrono::steady_clock::now() - start < duration) { @@ -246,12 +230,64 @@ static void VerifyRepeatedAmprCompletionsAreNotLost() { Require(sceKernelDeleteEqueue(eq) == SCE_OK); } +static void RequireReported(KernelEqueue eq, int num, std::initializer_list expected) { + KernelEvent events[4]{}; + int count = 0; + const KernelUseconds poll = 0; + Require(sceKernelWaitEqueue(eq, events, num, &count, &poll) == SCE_OK); + Require(count == static_cast(expected.size())); + int index = 0; + for (const uintptr_t id : expected) { + Require(sceKernelGetEventId(&events[index++]) == id); + } +} + +static void VerifyReportedEventsMoveBehindWaitingOnes() { + KernelEqueue eq = 0; + KernelEvent events[4]{}; + int count = 0; + const KernelUseconds poll = 0; + int payload = 0; + Require(sceKernelCreateEqueue(&eq, "rotate") == SCE_OK); + for (const int id : {1, 2}) { + Require(sceKernelAddUserEvent(eq, id) == SCE_OK); + Require(sceKernelTriggerUserEvent(eq, id, &payload) == SCE_OK); + } + RequireReported(eq, 1, {1}); + RequireReported(eq, 1, {2}); + RequireReported(eq, 1, {1}); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); + + Require(sceKernelCreateEqueue(&eq, "rotate three") == SCE_OK); + for (const int id : {1, 2, 3}) { + Require(sceKernelAddUserEvent(eq, id) == SCE_OK); + Require(sceKernelTriggerUserEvent(eq, id, &payload) == SCE_OK); + } + RequireReported(eq, 2, {1, 2}); + RequireReported(eq, 2, {3, 1}); + RequireReported(eq, 2, {2, 3}); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); + + Require(sceKernelCreateEqueue(&eq, "rotate edge") == SCE_OK); + for (const int id : {7, 8}) { + Require(sceKernelAddUserEventEdge(eq, id) == SCE_OK); + Require(sceKernelTriggerUserEvent(eq, id, &payload) == SCE_OK); + } + RequireReported(eq, 1, {7}); + Require(sceKernelTriggerUserEvent(eq, 7, &payload) == SCE_OK); + RequireReported(eq, 1, {8}); + RequireReported(eq, 1, {7}); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + int main() { VerifyPeriodicTimer(); VerifyUserEventReportedOncePerWait(); VerifyBlockingWaitReportsLevelEventOnce(); VerifyEdgeUserEventReportedOnce(); VerifyRepeatedAmprCompletionsAreNotLost(); + VerifyReportedEventsMoveBehindWaitingOnes(); KernelEqueue eq = 0; Require(sceKernelCreateEqueue(&eq, "events") == SCE_OK); @@ -291,12 +327,30 @@ int main() { Require(sceKernelGetEventFflags(&rawEvent) == static_cast(0x80000001LL)); Require(sceKernelGetEventData(&rawEvent) == -5); Require(sceKernelGetEventUserData(&rawEvent) == nullptr); - Require(sceKernelGetEventError(&rawEvent) == 0); - rawEvent.flags = static_cast(~EV_ERROR); - Require(sceKernelGetEventError(&rawEvent) == 0); + Require(RejectsWideFflags(&rawEvent)); + rawEvent.fflags = 0; rawEvent.flags = EV_ERROR; rawEvent.data = 22; - Require(sceKernelGetEventError(&rawEvent) == 22); + Require(sceKernelGetEventError(&rawEvent) == 0); + rawEvent.data = -1; + Require(sceKernelGetEventError(&rawEvent) == 0); + rawEvent.data = 0x80020016; + Require(sceKernelGetEventError(&rawEvent) == 0); + rawEvent.fflags = 5; + rawEvent.data = 0; + Require(sceKernelGetEventError(&rawEvent) == static_cast(0x80020005)); + rawEvent.data = 22; + Require(sceKernelGetEventError(&rawEvent) == static_cast(0x80020005)); + rawEvent.flags = 0; + rawEvent.fflags = 7; + Require(sceKernelGetEventError(&rawEvent) == static_cast(0x80020007)); + rawEvent.flags = EV_EOF; + rawEvent.fflags = 3; + rawEvent.data = 9; + Require(sceKernelGetEventError(&rawEvent) == static_cast(0x80020003)); + rawEvent.flags = static_cast(~EV_ERROR); + rawEvent.fflags = 0xFFFF; + Require(sceKernelGetEventError(&rawEvent) == static_cast(0x8002FFFF)); Require(RejectsNull(sceKernelGetEventData)); Require(RejectsNull(sceKernelGetEventError)); diff --git a/core/libs/tests/GuestErr.cpp b/core/libs/tests/GuestErr.cpp index cf7f656594..570a1c1d85 100644 --- a/core/libs/tests/GuestErr.cpp +++ b/core/libs/tests/GuestErr.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" @@ -11,27 +13,10 @@ #include #endif -using ErrExit = void (APS5_VABI*)(int); - extern "C" { -void APS5_VABI err_set_file_nid_postfix(FileStream* stream); -void APS5_VABI err_set_exit_nid_postfix(ErrExit function); -void APS5_VABI warn_nid_postfix(const char* format, ...); -void APS5_VABI warnc_nid_postfix(int code, const char* format, ...); -void APS5_VABI warnx_nid_postfix(const char* format, ...); -[[noreturn]] void APS5_VABI err_nid_postfix(int status, const char* format, ...); -[[noreturn]] void APS5_VABI errc_nid_postfix(int status, int code, const char* format, ...); -[[noreturn]] void APS5_VABI errx_nid_postfix(int status, const char* format, ...); -void APS5_VABI vwarn_nid_postfix(const char* format, VaList* args); -void APS5_VABI vwarnc_nid_postfix(int code, const char* format, VaList* args); -void APS5_VABI vwarnx_nid_postfix(const char* format, VaList* args); -[[noreturn]] void APS5_VABI verr_nid_postfix(int status, const char* format, VaList* args); -[[noreturn]] void APS5_VABI verrc_nid_postfix(int status, int code, const char* format, VaList* args); -[[noreturn]] void APS5_VABI verrx_nid_postfix(int status, const char* format, VaList* args); -char* APS5_VABI strerror_nid_postfix(int error); -int* APS5_VABI __error_nid_postfix(); + extern const char* __progname_nid_postfix; -void APS5_VABI psignal_nid_postfix(unsigned int signal, const char* prefix); + extern const char* const sys_signame_nid_postfix[32]; extern const char* const sys_siglist_nid_postfix[32]; } @@ -63,6 +48,7 @@ public: Capture() : stream(std::tmpfile()) { err_set_file_nid_postfix(&stream); } ~Capture() { err_set_file_nid_postfix(nullptr); + stream.Close(); } std::string Take() { @@ -144,15 +130,24 @@ int main() { Capture capture; *__error_nid_postfix() = 2; warn_nid_postfix("open %s", "a.txt"); + Require(capture.Take() == "prog: open a.txt: " + noEntry + "\n", "warn"); *__error_nid_postfix() = 2; warn_nid_postfix(nullptr); + Require(capture.Take() == "prog: " + noEntry + "\n", "warn without format"); + *__error_nid_postfix() = 13; + _warn_nid_postfix("open %s", "b.txt"); + + Require(capture.Take() == "prog: open b.txt: " + denied + "\n", "_warn"); warnc_nid_postfix(13, "%d-%s", 7, "x"); + Require(capture.Take() == "prog: 7-x: " + denied + "\n", "warnc"); warnx_nid_postfix("plain %c", 'q'); + Require(capture.Take() == "prog: plain q\n", "warnx"); warnx_nid_postfix(nullptr); + Require(capture.Take() == "prog: \n", "warnx without format"); err_set_exit_nid_postfix(Hook); @@ -163,6 +158,12 @@ int main() { *__error_nid_postfix() = 2; Require(ExitStatus([] { err_nid_postfix(5, "e"); }) == 5, "err status"); Require(capture.Take() == "prog: e: " + noEntry + "\n", "err text"); + *__error_nid_postfix() = 13; + Require(ExitStatus([] { _err_nid_postfix(7, "%s %d", "f", 8); }) == 7 && g_hookStatus == 7, "_err status"); + Require(capture.Take() == "prog: f 8: " + denied + "\n", "_err text"); + *__error_nid_postfix() = 2; + Require(ExitStatus([] { _warn_nid_postfix(nullptr); }) == -1 && g_hookStatus == 7, "_warn does not exit"); + Require(capture.Take() == "prog: " + noEntry + "\n", "_warn without format"); *__error_nid_postfix() = 2; CallVariant(Variant::Warn, 0, 0, "open %s", "a.txt"); @@ -185,6 +186,7 @@ int main() { Require(ExitStatus([] { CallVariant(Variant::Errx, 0, 0, nullptr); }) == 0, "verrx status 0"); Require(capture.Take() == "prog: \n", "verrx without format"); err_set_exit_nid_postfix(nullptr); + } __progname_nid_postfix = savedName; diff --git a/core/libs/tests/GuestErrorDialog.cpp b/core/libs/tests/GuestErrorDialog.cpp index 106819b6a8..034eb4e5e0 100644 --- a/core/libs/tests/GuestErrorDialog.cpp +++ b/core/libs/tests/GuestErrorDialog.cpp @@ -1,16 +1,8 @@ +#include "prx/libSceErrorDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include -extern "C" { -int APS5_VABI sceErrorDialogInitialize(void); -int APS5_VABI sceErrorDialogOpen(const void* param); -int APS5_VABI sceErrorDialogUpdateStatus(void); -int APS5_VABI sceErrorDialogGetStatus(void); -int APS5_VABI sceErrorDialogClose(void); -int APS5_VABI sceErrorDialogTerminate(void); -} - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -20,6 +12,7 @@ constexpr int kStatusFinished = 3; constexpr int kErrNotInitialized = static_cast(0x80ED0001); constexpr int kErrAlreadyInitialized = static_cast(0x80ED0002); constexpr int kErrParam = static_cast(0x80ED0003); +constexpr int kErrUnexpectedFatal = static_cast(0x80ED0004); constexpr int kErrInvalidState = static_cast(0x80ED0005); struct ErrorDialogParam { @@ -68,4 +61,6 @@ int main() { Require(sceErrorDialogGetStatus() == 0); Require(sceErrorDialogUpdateStatus() == 0); Require(sceErrorDialogClose() == kErrInvalidState); + Require(sceErrorDialogOpenDetail() == kErrUnexpectedFatal); + Require(sceErrorDialogOpenWithReport() == kErrUnexpectedFatal); } diff --git a/core/libs/tests/GuestErrors.cpp b/core/libs/tests/GuestErrors.cpp index b66b25b00d..1946147d26 100644 --- a/core/libs/tests/GuestErrors.cpp +++ b/core/libs/tests/GuestErrors.cpp @@ -1,14 +1,12 @@ +#include "prx/libc/include/WindowsFormatting.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include -extern "C" { -char* APS5_VABI strerror_nid_postfix(int); -int APS5_VABI strerror_r_nid_postfix(int, char*, std::size_t); -int* APS5_VABI __error_nid_postfix(); -} static void Require(bool value) { if (!value) std::abort(); } static const char* const freebsd[97] = { "No error: 0", "Operation not permitted", "No such file or directory", @@ -21,7 +19,7 @@ static const char* const freebsd[97] = { "Is a directory", "Invalid argument", "Too many open files in system", "Too many open files", "Inappropriate ioctl for device", "Text file busy", "File too large", "No space left on device", "Illegal seek", - "Read-only file system", "Too many links", "Broken pipe", + "Read-only filesystem", "Too many links", "Broken pipe", "Numerical argument out of domain", "Result too large", "Resource temporarily unavailable", "Operation now in progress", "Operation already in progress", "Socket operation on non-socket", "Destination address required", "Message too long", "Protocol wrong type for socket", @@ -62,7 +60,11 @@ int main() { for (int error = 0; error <= 96; ++error) { Require(strerror_r_nid_postfix(error, buffer, sizeof(buffer)) == 0); Require(std::strcmp(buffer, freebsd[error]) == 0); - Require(std::strcmp(strerror_nid_postfix(error), freebsd[error]) == 0); + char coded[32]; + std::snprintf(coded, sizeof(coded), "error_code=0x%08x", static_cast(error)); + const char* expected = error == 0 ? "No error" : error < 95 ? freebsd[error] : coded; + Require(std::strcmp(strerror_nid_postfix(error), expected) == 0); + Require(*__error_nid_postfix() == 13); } Require(strerror_r_nid_postfix(-1, buffer, sizeof(buffer)) == 22); Require(std::strcmp(buffer, "Unknown error: -1") == 0); @@ -70,15 +72,18 @@ int main() { struct UnknownError { int error; const char* text; + const char* coded; }; - for (const UnknownError& unknown : {UnknownError{97, "Unknown error: 97"}, UnknownError{INT_MAX, "Unknown error: 2147483647"}, - UnknownError{INT_MIN, "Unknown error: -2147483648"}}) { + for (const UnknownError& unknown : {UnknownError{97, "Unknown error: 97", "error_code=0x00000061"}, + UnknownError{9999, "Unknown error: 9999", "error_code=0x0000270f"}, + UnknownError{INT_MAX, "Unknown error: 2147483647", "error_code=0x7fffffff"}, + UnknownError{-1, "Unknown error: -1", "error_code=0xffffffff"}, + UnknownError{INT_MIN, "Unknown error: -2147483648", "error_code=0x80000000"}}) { Require(strerror_r_nid_postfix(unknown.error, buffer, sizeof(buffer)) == 22); Require(std::strcmp(buffer, unknown.text) == 0); Require(*__error_nid_postfix() == 13); - Require(std::strcmp(strerror_nid_postfix(unknown.error), unknown.text) == 0); - Require(*__error_nid_postfix() == 22); - *__error_nid_postfix() = 13; + Require(std::strcmp(strerror_nid_postfix(unknown.error), unknown.coded) == 0); + Require(*__error_nid_postfix() == 13); } char sentinel[] = "xyz"; Require(strerror_r_nid_postfix(22, sentinel, 1) == 34); diff --git a/core/libs/tests/GuestExceptionHandler.cpp b/core/libs/tests/GuestExceptionHandler.cpp index fbb5dfd8f7..2ad28a7af4 100644 --- a/core/libs/tests/GuestExceptionHandler.cpp +++ b/core/libs/tests/GuestExceptionHandler.cpp @@ -1,15 +1,9 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include -extern "C" { -int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler); -int APS5_VABI sceKernelRemoveExceptionHandler(int signum); -} - -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); -static constexpr int SCE_KERNEL_ERROR_EAGAIN = static_cast(0x80020023); - static void APS5_VABI Handler(int, void*) {} static void APS5_VABI Other(int, void*) {} diff --git a/core/libs/tests/GuestExecuteOnce.cpp b/core/libs/tests/GuestExecuteOnce.cpp index 327fbe8c32..e58792b37d 100644 --- a/core/libs/tests/GuestExecuteOnce.cpp +++ b/core/libs/tests/GuestExecuteOnce.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,7 +6,7 @@ #include using Callback = int (APS5_VABI *)(void*, void*, void**); -extern "C" int APS5_VABI _ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1__nid_postfix(int*, Callback, void*); + static int ExecuteOnce(int* flag, Callback callback, void* arg) { return _ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1__nid_postfix(flag, callback, arg); } diff --git a/core/libs/tests/GuestExecuteOnceConcurrency.cpp b/core/libs/tests/GuestExecuteOnceConcurrency.cpp index e5fc8f802e..1537ea5887 100644 --- a/core/libs/tests/GuestExecuteOnceConcurrency.cpp +++ b/core/libs/tests/GuestExecuteOnceConcurrency.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -10,10 +11,6 @@ using Callback = int (APS5_VABI *)(void*, void*, void**); using ExecuteFunction = int (APS5_VABI *)(int*, Callback, void*); -extern "C" { -int APS5_VABI _ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1__nid_postfix(int*, Callback, void*); -int APS5_VABI std_execute_once_nid_postfix(int*, Callback, void*); -} static void Check(bool value, int line) { if (!value) { diff --git a/core/libs/tests/GuestExecvp.cpp b/core/libs/tests/GuestExecvp.cpp index 24044df2f7..22e67f4d98 100644 --- a/core/libs/tests/GuestExecvp.cpp +++ b/core/libs/tests/GuestExecvp.cpp @@ -1,12 +1,9 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/General.hpp" #include #include #include -extern "C" { -int APS5_VABI execvp_nid_postfix(const char*, char* const*); -int APS5_VABI execv_nid_postfix(const char*, char* const*); -int APS5_VABI execve_nid_postfix(const char*, char* const*, char* const*); -} + template static bool Throws(TCall call, const std::string& message) { try { call(); diff --git a/core/libs/tests/GuestExitOrder.cpp b/core/libs/tests/GuestExitOrder.cpp index 889e6f0f26..af813531ef 100644 --- a/core/libs/tests/GuestExitOrder.cpp +++ b/core/libs/tests/GuestExitOrder.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,9 +6,6 @@ #include #include -extern "C" int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dsoHandle); -extern "C" int APS5_VABI atexit_nid_postfix(void (APS5_VABI *func)()); - namespace { int events[2]; diff --git a/core/libs/tests/GuestFdopendir.cpp b/core/libs/tests/GuestFdopendir.cpp index 551ad063f0..7dfad311ad 100644 --- a/core/libs/tests/GuestFdopendir.cpp +++ b/core/libs/tests/GuestFdopendir.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/GuestDirectory.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/File.hpp" @@ -9,16 +12,7 @@ #include #include #include -extern "C" { -void* APS5_VABI fdopendir_nid_postfix(int); -GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void*); -int APS5_VABI closedir_nid_postfix(void*); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int); -int* APS5_VABI __error_nid_postfix(); -} + static void Require(bool value) { if (!value) std::abort(); } int main() { const auto root = std::filesystem::path("anyps5-fdopendir-test-" + diff --git a/core/libs/tests/GuestFileLock.cpp b/core/libs/tests/GuestFileLock.cpp index 3fb7ac7b71..26dd7afae8 100644 --- a/core/libs/tests/GuestFileLock.cpp +++ b/core/libs/tests/GuestFileLock.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -12,25 +15,6 @@ class FileStream; -extern "C" { -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI sceKernelClose(int); -int APS5_VABI dup_nid_postfix(int); -int APS5_VABI dup2_nid_postfix(int, int); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI flock_nid_postfix(int, int); -std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t); -int* APS5_VABI __error_nid_postfix(); -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -FileStream* APS5_VABI fdopen_nid_postfix(int, const char*); -FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); -int APS5_VABI fclose_nid_postfix(FileStream*); -int APS5_VABI fileno_nid_postfix(FileStream*); -} - static void Require(bool condition) { if (!condition) std::abort(); } @@ -53,7 +37,8 @@ static bool Acquires(int descriptor, int mode) { struct Fixture { static inline unsigned serial = 0; - std::string path = (std::filesystem::temp_directory_path() / ("anyps5-flock-" + std::to_string(::GetCurrentProcessId()) + "-" + std::to_string(serial++) + ".bin")).string(); + std::filesystem::path native = std::filesystem::temp_directory_path() / ("anyps5-flock-" + std::to_string(::GetCurrentProcessId()) + "-" + std::to_string(serial++) + ".bin"); + std::string path = native.string(); std::set descriptors; int source = Open(true); int other = Open(false); @@ -76,7 +61,7 @@ struct Fixture { } ~Fixture() { for (const int descriptor : descriptors) Require(close_nid_postfix(descriptor) == 0); - Require(std::remove(path.c_str()) == 0); + Require(std::filesystem::remove(native)); } }; @@ -262,7 +247,7 @@ static int ChildTry(const Fixture (&fixtures)[6], int mode) { wchar_t executable[32768]; Require(::GetModuleFileNameW(nullptr, executable, 32768) != 0); std::wstring command = L"\"" + std::wstring(executable) + L"\" child " + std::to_wstring(mode | 4); - for (const Fixture& fixture : fixtures) command += L" \"" + std::filesystem::path(fixture.path).wstring() + L"\""; + for (const Fixture& fixture : fixtures) command += L" \"" + fixture.native.filename().wstring() + L"\""; STARTUPINFOW startup{}; startup.cb = sizeof(startup); PROCESS_INFORMATION process{}; @@ -296,11 +281,29 @@ static void Processes() { Require(ChildTry(fixtures, 2) == 0); } +static void Directories() { + const std::filesystem::path directory = std::filesystem::temp_directory_path() / ("anyps5-flock-dir-" + std::to_string(::GetCurrentProcessId())); + std::filesystem::create_directories(directory); + const std::string path = directory.string(); + const int descriptor = open_nid_postfix(path.c_str(), 0x20000, 0); + Require(descriptor >= 0); + Require(flock_nid_postfix(descriptor, 1 | 4) == 0); + Require(flock_nid_postfix(descriptor, 2 | 4) == 0); + Require(flock_nid_postfix(descriptor, 2) == 0); + Require(flock_nid_postfix(descriptor, 8) == 0); + Require(flock_nid_postfix(descriptor, 0) == -1 && *__error_nid_postfix() == 9); + Require(flock_nid_postfix(descriptor, 4) == -1 && *__error_nid_postfix() == 9); + Require(close_nid_postfix(descriptor) == 0); + std::error_code ignored; + std::filesystem::remove_all(directory, ignored); +} + int main(int argc, char** argv) { if (argc >= 4 && std::string(argv[1]) == "child") { int busy = 0; for (int index = 3; index < argc; ++index) { - const int descriptor = open_nid_postfix(argv[index], 2, 0); + const std::string path = (std::filesystem::temp_directory_path() / argv[index]).string(); + const int descriptor = open_nid_postfix(path.c_str(), 2, 0); Require(descriptor >= 0); if (flock_nid_postfix(descriptor, std::atoi(argv[2])) == 0) { Require(flock_nid_postfix(descriptor, 8) == 0); @@ -320,5 +323,6 @@ int main(int argc, char** argv) { else if (mode == "threads") Threads(); else if (mode == "streams") Streams(); else if (mode == "process") Processes(); + else if (mode == "directory") Directories(); else std::abort(); } diff --git a/core/libs/tests/GuestFileTimestamps.cpp b/core/libs/tests/GuestFileTimestamps.cpp index 62750ec5f3..e45df8c8f5 100644 --- a/core/libs/tests/GuestFileTimestamps.cpp +++ b/core/libs/tests/GuestFileTimestamps.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -10,22 +12,6 @@ #include #endif -extern "C" { -int APS5_VABI utimes_nid_postfix(const char*, const KernelTimeval*); -int APS5_VABI lutimes_nid_postfix(const char*, const KernelTimeval*); -int APS5_VABI futimes_nid_postfix(int, const KernelTimeval*); -int APS5_VABI sceKernelUtimes_nid_postfix(const char*, const KernelTimeval*); -int APS5_VABI stat_nid_postfix(const char*, FileStat*); -int APS5_VABI lstat_nid_postfix(const char*, FileStat*); -int APS5_VABI sceKernelFstat(int, FileStat*); -std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*); -std::int64_t APS5_VABI _fstat_nid_postfix(int, FileStat*); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI pipe_nid_postfix(int*); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value, const char* message) { if (!value) { std::fprintf(stderr, "%s (guest errno %d)\n", message, *__error_nid_postfix()); @@ -60,6 +46,70 @@ static void CheckTimes(const std::string& path, int fd, const KernelTimeval* exp #endif } +#ifdef _WIN32 +static std::uint64_t Ticks(const FILETIME& time) { + return (static_cast(time.dwHighDateTime) << 32) | time.dwLowDateTime; +} + +static void CheckBirthtime(const std::string& path, int fd, std::uint64_t expected) { + FILETIME creation{}; + Require(GetFileTime(reinterpret_cast(_get_osfhandle(fd)), &creation, nullptr, nullptr), "native creation time"); + Require(Ticks(creation) == expected, "native birth time follows an earlier mtime and never advances"); + FileStat byPath{}, byFd{}; + Require(stat_nid_postfix(path.c_str(), &byPath) == 0, "stat birth time"); + Require(sceKernelFstat(fd, &byFd) == 0, "fstat birth time"); + for (const auto* status : {&byPath, &byFd}) { + Require(status->st_birthtim.tv_sec == static_cast(expected / 10000000) - 11644473600LL && + status->st_birthtim.tv_nsec == static_cast(expected % 10000000) * 100, "guest birth time matches native metadata"); + } +} + +template +static void CheckBirthtimeUpdates(const std::string& path, int fd, TUpdate update) { + const auto handle = CreateFileW(std::filesystem::path(path).c_str(), FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + Require(handle != INVALID_HANDLE_VALUE, "open birth time fixture"); + constexpr std::uint64_t initial = 116444736000000000ULL + 2000000000ULL * 10000000 + 1234567; + const FILETIME creation{static_cast(initial), static_cast(initial >> 32)}; + Require(SetFileTime(handle, &creation, nullptr, nullptr), "seed native birth time"); + const KernelTimeval later[2]{{1900000000, 654321}, {2100000000, 123456}}; + Require(update(later) == 0, "update later modification time"); + CheckTimes(path, fd, later); + CheckBirthtime(path, fd, initial); + const KernelTimeval earlier[2]{{1800000000, 1}, {2000000000, 123456}}; + Require(update(earlier) == 0, "update modification time less than a microsecond before birth"); + CheckTimes(path, fd, earlier); + constexpr std::uint64_t lowered = initial - 7; + CheckBirthtime(path, fd, lowered); + const KernelTimeval equal[2]{{1700000000, 999999}, earlier[1]}; + Require(update(equal) == 0, "update equal modification time"); + CheckTimes(path, fd, equal); + CheckBirthtime(path, fd, lowered); + const KernelTimeval invalid[2]{{1600000000, 0}, {std::numeric_limits::max(), 0}}; + Require(update(invalid) != 0, "reject invalid modification time before changing birth"); + CheckTimes(path, fd, equal); + CheckBirthtime(path, fd, lowered); + FILETIME future{}; + GetSystemTimePreciseAsFileTime(&future); + const auto futureTicks = Ticks(future) + 600ULL * 10000000; + future = {static_cast(futureTicks), static_cast(futureTicks >> 32)}; + Require(SetFileTime(handle, &future, nullptr, nullptr), "seed future birth time"); + Require(CloseHandle(handle), "close birth time fixture"); + const auto before = std::chrono::system_clock::now(); + Require(update(nullptr) == 0, "current time lowers a future birth time"); + const auto after = std::chrono::system_clock::now(); + FILETIME nativeCreation{}, access{}, modified{}; + Require(GetFileTime(reinterpret_cast(_get_osfhandle(fd)), &nativeCreation, &access, &modified), "read current native times"); + Require(Ticks(nativeCreation) == Ticks(access) && Ticks(access) == Ticks(modified), "current times share one snapshot including lowered birth"); + CheckBirthtime(path, fd, Ticks(modified)); + FileStat status{}; + Require(sceKernelFstat(fd, &status) == 0, "fstat current birth time"); + const auto recorded = std::chrono::seconds(status.st_birthtim.tv_sec) + std::chrono::nanoseconds(status.st_birthtim.tv_nsec); + Require(recorded >= before.time_since_epoch() - std::chrono::seconds(1) && + recorded <= after.time_since_epoch() + std::chrono::seconds(1), "current birth time uses host clock"); +} +#endif + int main() { const auto root = std::filesystem::path("anyps5-file-times-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); Require(std::filesystem::create_directory(root), "create fixture directory"); @@ -68,6 +118,15 @@ int main() { const int fd = open_nid_postfix(path.c_str(), 0, 0); Require(fd >= 0, "open read-only descriptor"); +#ifdef _WIN32 + for (const auto update : {utimes_nid_postfix, lutimes_nid_postfix, sceKernelUtimes_nid_postfix}) { + CheckBirthtimeUpdates(path, fd, [&](const KernelTimeval* times) { return update(path.c_str(), times); }); + } + for (const auto update : {futimes_nid_postfix, sceKernelFutimes}) { + CheckBirthtimeUpdates(path, fd, [&](const KernelTimeval* times) { return update(fd, times); }); + } +#endif + const KernelTimeval first[2]{{1000000000, 123456}, {1000000001, 500000}}; Require(utimes_nid_postfix(path.c_str(), first) == 0, "utimes fractional timestamp"); CheckTimes(path, fd, first); @@ -145,6 +204,14 @@ int main() { Require(std::filesystem::create_directory(directory), "create timestamp directory"); const int directoryFd = open_nid_postfix(directory.c_str(), 0, 0); Require(directoryFd >= 0, "open directory descriptor"); +#ifdef _WIN32 + for (const auto update : {utimes_nid_postfix, lutimes_nid_postfix, sceKernelUtimes_nid_postfix}) { + CheckBirthtimeUpdates(directory, directoryFd, [&](const KernelTimeval* times) { return update(directory.c_str(), times); }); + } + for (const auto update : {futimes_nid_postfix, sceKernelFutimes}) { + CheckBirthtimeUpdates(directory, directoryFd, [&](const KernelTimeval* times) { return update(directoryFd, times); }); + } +#endif Require(utimes_nid_postfix(directory.c_str(), first) == 0, "directory utimes"); FileStat directoryStatus{}; Require(stat_nid_postfix(directory.c_str(), &directoryStatus) == 0 && Same(directoryStatus.st_mtim, first[1]), "directory stat precision"); @@ -159,7 +226,14 @@ int main() { Require(utimes_nid_postfix(path.c_str(), second) == 0, "utimes target to second"); std::error_code symlinkError; const auto linkPath = (root / "link.txt").string(); +#ifdef _WIN32 + if (!CreateSymbolicLinkW(std::filesystem::path(linkPath).c_str(), std::filesystem::path(path).c_str(), + SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) { + symlinkError = std::error_code(GetLastError(), std::system_category()); + } +#else std::filesystem::create_symlink(path, linkPath, symlinkError); +#endif if (!symlinkError) { Require(lutimes_nid_postfix(linkPath.c_str(), first) == 0, "lutimes modifies symlink"); FileStat targetStatus{}; diff --git a/core/libs/tests/GuestFileVector.cpp b/core/libs/tests/GuestFileVector.cpp index e9e7b46880..72e398204c 100644 --- a/core/libs/tests/GuestFileVector.cpp +++ b/core/libs/tests/GuestFileVector.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include @@ -26,37 +29,6 @@ static int MakePipe(int* ends) { return ::pipe(ends); } static int ClosePipe(int end) { return ::close(end); } #endif -struct GuestIovec { - void* base; - std::size_t length; -}; - -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -int APS5_VABI dup_nid_postfix(int); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -int APS5_VABI sceKernelLseek(int, std::int64_t, int); -std::int64_t APS5_VABI sceKernelReadv(int, const GuestIovec*, int); -std::int64_t APS5_VABI readv_nid_postfix(int, const GuestIovec*, int); -std::int64_t APS5_VABI writev_nid_postfix(int, const GuestIovec*, int); -std::int64_t APS5_VABI _readv_nid_postfix(int, const GuestIovec*, int); -std::int64_t APS5_VABI _writev_nid_postfix(int, const GuestIovec*, int); -int* APS5_VABI __error_nid_postfix(); -std::int64_t APS5_VABI sceKernelWritev(int, const GuestIovec*, int); -std::int64_t APS5_VABI sceKernelPreadv(int, const GuestIovec*, int, std::int64_t); -std::int64_t APS5_VABI sceKernelPwritev(int, const GuestIovec*, int, std::int64_t); -std::int64_t APS5_VABI preadv_nid_postfix(int, const GuestIovec*, int, std::int64_t); -std::int64_t APS5_VABI pwritev_nid_postfix(int, const GuestIovec*, int, std::int64_t); -int* APS5_VABI __error_nid_postfix(); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t); -int APS5_VABI sceKernelFtruncate(int, std::int64_t); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "File vector check failed at line %d\n", line); @@ -94,13 +66,14 @@ static void ConcurrentRecords(const std::filesystem::path& path) { for (std::size_t worker = 0; worker < Workers; ++worker) { writers[worker] = std::thread([&, worker] { std::uint16_t label = 0; - std::array pieces; + std::array pieces; pieces.fill({&label, sizeof(label)}); start.arrive_and_wait(); for (std::size_t record = 0; record < Records; ++record) { label = static_cast(worker * Records + record); const auto written = worker % 2 == 0 ? sceKernelWritev(descriptors[worker], pieces.data(), static_cast(pieces.size())) : writev_nid_postfix(descriptors[worker], pieces.data(), static_cast(pieces.size())); + Require(written == RecordBytes); } }); @@ -126,12 +99,13 @@ static void ConcurrentRecords(const std::filesystem::path& path) { for (std::size_t worker = 0; worker < Workers; ++worker) { readers[worker] = std::thread([&, worker] { std::array record{}; - std::array pieces; + std::array pieces; for (std::size_t part = 0; part < Parts; ++part) pieces[part] = {&record[part], sizeof(record[part])}; start.arrive_and_wait(); for (;;) { const auto read = worker % 2 == 0 ? sceKernelReadv(descriptors[worker], pieces.data(), static_cast(pieces.size())) : readv_nid_postfix(descriptors[worker], pieces.data(), static_cast(pieces.size())); + if (read == 0) break; Require(read == RecordBytes); for (const auto label : record) Require(label == record[0]); @@ -152,7 +126,7 @@ static void ConcurrentRecords(const std::filesystem::path& path) { for (std::size_t worker = 0; worker < Workers; ++worker) { positioned[worker] = std::thread([&, worker] { std::array record{}; - std::array pieces; + std::array pieces; for (std::size_t part = 0; part < Parts; ++part) pieces[part] = {&record[part], sizeof(record[part])}; start.arrive_and_wait(); for (std::size_t iteration = 0; iteration < Records; ++iteration) { @@ -161,9 +135,11 @@ static void ConcurrentRecords(const std::filesystem::path& path) { record.fill(static_cast(0x8000 + worker * Records + iteration)); transferred = worker == 0 ? sceKernelPwritev(descriptors[worker], pieces.data(), static_cast(pieces.size()), 0) : pwritev_nid_postfix(descriptors[worker], pieces.data(), static_cast(pieces.size()), 0); + } else { transferred = worker == 1 ? sceKernelPreadv(descriptors[worker], pieces.data(), static_cast(pieces.size()), 0) : preadv_nid_postfix(descriptors[worker], pieces.data(), static_cast(pieces.size()), 0); + for (const auto label : record) Require(label == record[0]); } Require(transferred == RecordBytes); @@ -189,7 +165,7 @@ int main() { char first[3] = {}; char second[4] = {}; - GuestIovec reads[2] = {{first, sizeof(first)}, {second, sizeof(second)}}; + KernelIovec reads[2] = {{first, sizeof(first)}, {second, sizeof(second)}}; Require(sceKernelReadv(file, reads, 2) == 7); Require(std::memcmp(first, "012", 3) == 0 && std::memcmp(second, "3456", 4) == 0); Require(sceKernelLseek(file, 0, 1) == 7); @@ -215,7 +191,7 @@ int main() { char ab[] = "AB"; char cde[] = "CDE"; - GuestIovec writes[2] = {{ab, 2}, {cde, 3}}; + KernelIovec writes[2] = {{ab, 2}, {cde, 3}}; Require(sceKernelPwritev(file, writes, 2, 2) == 5); Require(sceKernelLseek(file, 0, 1) == 10); Require(Contents(path) == "01ABCDE789"); @@ -223,7 +199,7 @@ int main() { Require(sceKernelWritev(file, writes, 2) == 5); Require(sceKernelLseek(file, 0, 1) == 13); Require(Contents(path) == "01ABCDE7ABCDE"); - GuestIovec swapped[2] = {{cde, 3}, {ab, 2}}; + KernelIovec swapped[2] = {{cde, 3}, {ab, 2}}; Require(sceKernelLseek(file, 8, 0) == 8 && writev_nid_postfix(file, swapped, 2) == 5); Require(sceKernelLseek(file, 0, 1) == 13 && Contents(path) == "01ABCDE7CDEAB"); Require(sceKernelLseek(file, 8, 0) == 8 && writev_nid_postfix(file, writes, 2) == 5); @@ -232,7 +208,7 @@ int main() { Require(sceKernelLseek(file, 0, 1) == 13 && Contents(path) == "01ABCDE7CDEAB"); Require(sceKernelLseek(file, 8, 0) == 8 && writev_nid_postfix(file, writes, 2) == 5); - GuestIovec gapped[3] = {{first, 2}, {nullptr, 0}, {second, 3}}; + KernelIovec gapped[3] = {{first, 2}, {nullptr, 0}, {second, 3}}; Require(sceKernelPreadv(file, gapped, 3, 0) == 5); Require(std::memcmp(first, "01", 2) == 0 && std::memcmp(second, "ABC", 3) == 0); Require(sceKernelLseek(file, 2, 0) == 2); @@ -240,7 +216,7 @@ int main() { Require(std::memcmp(first, "AB", 2) == 0 && std::memcmp(second, "CDE", 3) == 0); Require(sceKernelLseek(file, 0, 1) == 7); - GuestIovec empty[1] = {{nullptr, 0}}; + KernelIovec empty[1] = {{nullptr, 0}}; Require(sceKernelReadv(file, empty, 1) == 0); Require(sceKernelReadv(file, reads, 0) == 0); Require(sceKernelWritev(file, nullptr, 0) == 0); @@ -272,12 +248,12 @@ int main() { Require(Contents(path) == "01ABCDE7ABCDE"); char x[] = "X"; char y[] = "Y"; - GuestIovec marks[2] = {{x, 1}, {y, 1}}; + KernelIovec marks[2] = {{x, 1}, {y, 1}}; Require(pwritev_nid_postfix(file, marks, 2, 0) == 2); Require(Contents(path) == "XYABCDE7ABCDE"); char zero[] = "0"; char one[] = "1"; - GuestIovec restore[2] = {{zero, 1}, {one, 1}}; + KernelIovec restore[2] = {{zero, 1}, {one, 1}}; Require(pwritev_nid_postfix(file, restore, 2, 0) == 2); Require(sceKernelLseek(file, 0, 1) == 4); Require(pwritev_nid_postfix(file, writes, 2, -1) == -1 && *__error_nid_postfix() == 22); @@ -326,12 +302,12 @@ int main() { int ends[2] = {}; Require(MakePipe(ends) == 0); char xyz[] = "xyz"; - GuestIovec message[1] = {{xyz, 3}}; + KernelIovec message[1] = {{xyz, 3}}; Require(sceKernelWritev(ends[1], message, 1) == 3); Require(sceKernelReadv(ends[0], reads, 2) == 3); Require(std::memcmp(first, "xyz", 3) == 0); char stuvw[] = "stuvw"; - GuestIovec longer[1] = {{stuvw, 5}}; + KernelIovec longer[1] = {{stuvw, 5}}; Require(sceKernelWritev(ends[1], longer, 1) == 5); Require(sceKernelReadv(ends[0], reads, 2) == 5); Require(std::memcmp(first, "stu", 3) == 0 && std::memcmp(second, "vw", 2) == 0); @@ -351,7 +327,7 @@ int main() { const int random = sceKernelOpen("/dev/urandom", 0, 0); Require(random >= 0); unsigned char noise[48]{}; - GuestIovec pieces[2] = {{noise, 16}, {noise + 16, 16}}; + KernelIovec pieces[2] = {{noise, 16}, {noise + 16, 16}}; Require(sceKernelReadv(random, pieces, 2) == 32); Require(sceKernelPreadv(random, pieces, 1, 1000) == 16); Require(sceKernelPread(random, noise + 32, 16, 1000) == 16); diff --git a/core/libs/tests/GuestFileVectorMemory.cpp b/core/libs/tests/GuestFileVectorMemory.cpp index 58285bbdd7..8bac8775f0 100644 --- a/core/libs/tests/GuestFileVectorMemory.cpp +++ b/core/libs/tests/GuestFileVectorMemory.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -19,28 +22,6 @@ #include #endif -struct GuestIovec { - void* base; - std::size_t length; -}; - -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); -std::int64_t APS5_VABI sceKernelReadv(int, const GuestIovec*, int); -std::int64_t APS5_VABI sceKernelWritev(int, const GuestIovec*, int); -std::int64_t APS5_VABI sceKernelPreadv(int, const GuestIovec*, int, std::int64_t); -std::int64_t APS5_VABI sceKernelPwritev(int, const GuestIovec*, int, std::int64_t); -std::int64_t APS5_VABI readv_nid_postfix(int, const GuestIovec*, int); -std::int64_t APS5_VABI writev_nid_postfix(int, const GuestIovec*, int); -std::int64_t APS5_VABI _readv_nid_postfix(int, const GuestIovec*, int); -std::int64_t APS5_VABI _writev_nid_postfix(int, const GuestIovec*, int); -std::int64_t APS5_VABI preadv_nid_postfix(int, const GuestIovec*, int, std::int64_t); -std::int64_t APS5_VABI pwritev_nid_postfix(int, const GuestIovec*, int, std::int64_t); -int* APS5_VABI __error_nid_postfix(); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "File vector memory check failed at line %d\n", line); @@ -54,7 +35,7 @@ struct Transfer { bool posix; bool positioned; bool write; - std::function call; + std::function call; }; static std::uintptr_t observedTable = 0; @@ -77,14 +58,14 @@ int main(int argc, char** argv) { const std::array transfers{{ {"readv", true, false, false, readv_nid_postfix}, {"writev", true, false, true, writev_nid_postfix}, - {"preadv", true, true, false, [](int fd, const GuestIovec* iov, int count) { return preadv_nid_postfix(fd, iov, count, 1); }}, - {"pwritev", true, true, true, [](int fd, const GuestIovec* iov, int count) { return pwritev_nid_postfix(fd, iov, count, 1); }}, + {"preadv", true, true, false, [](int fd, const KernelIovec* iov, int count) { return preadv_nid_postfix(fd, iov, count, 1); }}, + {"pwritev", true, true, true, [](int fd, const KernelIovec* iov, int count) { return pwritev_nid_postfix(fd, iov, count, 1); }}, {"_readv", true, false, false, _readv_nid_postfix}, {"_writev", true, false, true, _writev_nid_postfix}, {"sceKernelReadv", false, false, false, sceKernelReadv}, {"sceKernelWritev", false, false, true, sceKernelWritev}, - {"sceKernelPreadv", false, true, false, [](int fd, const GuestIovec* iov, int count) { return sceKernelPreadv(fd, iov, count, 1); }}, - {"sceKernelPwritev", false, true, true, [](int fd, const GuestIovec* iov, int count) { return sceKernelPwritev(fd, iov, count, 1); }}, + {"sceKernelPreadv", false, true, false, [](int fd, const KernelIovec* iov, int count) { return sceKernelPreadv(fd, iov, count, 1); }}, + {"sceKernelPwritev", false, true, true, [](int fd, const KernelIovec* iov, int count) { return sceKernelPwritev(fd, iov, count, 1); }}, }}; #ifdef _WIN32 SYSTEM_INFO system{}; @@ -98,9 +79,9 @@ int main(int argc, char** argv) { Require(memory != MAP_FAILED); #endif std::array bytes{'A', 'B'}; - const std::array vectors{{{bytes.data(), 1}, {bytes.data() + 1, 1}}}; + const std::array vectors{{{bytes.data(), 1}, {bytes.data() + 1, 1}}}; std::memcpy(memory, vectors.data(), sizeof(vectors)); - std::memcpy(memory + page - sizeof(GuestIovec), vectors.data(), sizeof(GuestIovec)); + std::memcpy(memory + page - sizeof(KernelIovec), vectors.data(), sizeof(KernelIovec)); Protect(memory, page, true); Protect(memory + page, page, false); const auto path = std::filesystem::temp_directory_path() / @@ -113,13 +94,13 @@ int main(int argc, char** argv) { { std::ofstream file(path, std::ios::binary); file << "ABCD"; Require(static_cast(file)); } const int fd = sceKernelOpen(guestPath.c_str(), SCE_KERNEL_O_RDWR, 0); Require(fd >= 0); - const std::array invalid{{ + const std::array invalid{{ nullptr, - reinterpret_cast(std::uintptr_t{1}), - reinterpret_cast(memory + page), - reinterpret_cast(memory + page - sizeof(GuestIovec)), - reinterpret_cast(memory + page - sizeof(GuestIovec) / 2), - reinterpret_cast(UINTPTR_MAX - sizeof(GuestIovec) + 1), + reinterpret_cast(std::uintptr_t{1}), + reinterpret_cast(memory + page), + reinterpret_cast(memory + page - sizeof(KernelIovec)), + reinterpret_cast(memory + page - sizeof(KernelIovec) / 2), + reinterpret_cast(UINTPTR_MAX - sizeof(KernelIovec) + 1), }}; for (const auto* table : invalid) { *__error_nid_postfix() = 0; @@ -142,7 +123,7 @@ int main(int argc, char** argv) { observedTable = reinterpret_cast(memory); observedBytes = 0; GuestArena::GuestArenaSetHostReadHook_nid_no_patch(ObserveRead); - Require(transfer.call(fd, reinterpret_cast(memory), 2) == 2); + Require(transfer.call(fd, reinterpret_cast(memory), 2) == 2); GuestArena::GuestArenaSetHostReadHook_nid_no_patch(nullptr); Require(observedBytes == sizeof(vectors)); Require(sceKernelLseek(fd, 0, SEEK_CUR) == (transfer.positioned ? 0 : 2)); @@ -156,10 +137,10 @@ int main(int argc, char** argv) { Require((bytes == (transfer.positioned ? std::array{'B', 'C'} : std::array{'A', 'B'}))); } Require(sceKernelLseek(fd, 0, SEEK_SET) == 0); - Require(transfer.call(fd, reinterpret_cast(memory + page - sizeof(GuestIovec)), 1) == 1); + Require(transfer.call(fd, reinterpret_cast(memory + page - sizeof(KernelIovec)), 1) == 1); Require(sceKernelLseek(fd, 0, SEEK_CUR) == (transfer.positioned ? 0 : 1)); Require(sceKernelLseek(fd, 0, SEEK_SET) == 0); - std::array maximum{}; + std::array maximum{}; maximum.back() = {bytes.data(), 1}; Require(transfer.call(fd, maximum.data(), static_cast(maximum.size())) == 1); Require(sceKernelLseek(fd, 0, SEEK_CUR) == (transfer.positioned ? 0 : 1)); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index c1a1a863bb..6e50871754 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -1,11 +1,15 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/RenameDot.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/NativeStat.hpp" #include "prx/libkernel/KernelErrors.hpp" #include +#include #include #include #include @@ -21,85 +25,14 @@ #ifdef _WIN32 #include #include +#include #else #include #endif #ifdef __linux__ #include #endif -extern "C" { -int APS5_VABI remove_nid_postfix(const char*); -int APS5_VABI rename_nid_postfix(const char*, const char*); -int APS5_VABI sceKernelChmod_nid_postfix(const char*, unsigned short); -int APS5_VABI sceKernelFchmod(int, unsigned short); -int APS5_VABI fchmod_nid_postfix(int, int); -int APS5_VABI chmod_nid_postfix(const char*, int); -int APS5_VABI chflags_nid_postfix(const char*, std::uint64_t); -int APS5_VABI fchflags_nid_postfix(int, std::uint64_t); -int APS5_VABI futimes_nid_postfix(int, const KernelTimeval*); -int APS5_VABI sceKernelFutimes(int, const KernelTimeval*); -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI sceKernelFsync(int); -int APS5_VABI fdatasync_nid_postfix(int); -int APS5_VABI fsync_nid_postfix(int); -int APS5_VABI sceKernelFdatasync(int); -int APS5_VABI sceKernelWriteThrottlingStatus(std::uint64_t*); -int APS5_VABI sceKernelFtruncate(int, long long); -int APS5_VABI sceKernelTruncate_nid_postfix(const char*, long long); -int APS5_VABI truncate_nid_postfix(const char*, long long); -int APS5_VABI sceKernelUtimes_nid_postfix(const char*, const void*); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI creat_nid_postfix(const char*, int); -int APS5_VABI _open_nid_postfix(const char*, int, ...); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI flock_nid_postfix(int, int); -int APS5_VABI stat_nid_postfix(const char*, FileStat*); -int APS5_VABI lstat_nid_postfix(const char*, FileStat*); -int APS5_VABI unlink_nid_postfix(const char*); -int APS5_VABI rmdir_nid_postfix(const char*); -int APS5_VABI mkdir_nid_postfix(const char*, unsigned short); -int APS5_VABI sceKernelOpen(const char*, int, unsigned short); -int APS5_VABI sceKernelClose(int); -int APS5_VABI sceKernelGetdents(int, char*, int); -int APS5_VABI getdents_nid_postfix(int, char*, int); -int APS5_VABI getdirentries_nid_postfix(int, char*, int, int64_t*); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -std::int64_t APS5_VABI sceKernelReadv(int, const void*, int); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelPreadv(int, const void*, int, std::int64_t); -std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t); -int APS5_VABI sceKernelStat(const char*, FileStat*); -int APS5_VABI sceKernelCheckReachability(const char*); -int APS5_VABI sceKernelFstat(int, FileStat*); -int APS5_VABI sceKernelUnlink(const char*); -int APS5_VABI sceKernelRename(const char*, const char*); -int APS5_VABI sceKernelRmdir(const char*); -int APS5_VABI sceKernelMkdir(const char*, unsigned short); -int APS5_VABI sceKernelRename(const char*, const char*); -int APS5_VABI sceKernelCheckReachability(const char*); -int APS5_VABI access_nid_postfix(const char*, int); -int APS5_VABI chdir_nid_postfix(const char*); -void* APS5_VABI opendir_nid_postfix(const char*); -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); -std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*); -int APS5_VABI fclose_nid_postfix(FileStream*); -int APS5_VABI fgetc_nid_postfix(FileStream*); -int APS5_VABI feof_nid_postfix(FileStream*); -int APS5_VABI ferror_nid_postfix(FileStream*); -int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t); -int APS5_VABI fflush_nid_postfix(FileStream*); -void APS5_VABI flockfile_nid_postfix(FileStream*); -void APS5_VABI funlockfile_nid_postfix(FileStream*); -int APS5_VABI ftrylockfile_nid_postfix(FileStream*); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI dup2_nid_postfix(int, int); -std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::size_t); -std::int64_t APS5_VABI write_nid_postfix(int, const void*, std::size_t); -int APS5_VABI sceKernelDebugOutText(int, const char*); -} + #ifdef _WIN32 static bool g_invalidParameter = false; static void RecordInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned, std::uintptr_t) { g_invalidParameter = true; } @@ -127,6 +60,7 @@ static void Check(bool value, int line) { static bool ChflagsThrows(const std::filesystem::path& path, std::uint64_t flags, const char* names) { try { chflags_nid_postfix(path.string().c_str(), flags); + } catch (const std::runtime_error& error) { return std::strstr(error.what(), names) != nullptr; } @@ -136,6 +70,7 @@ static bool ChflagsThrows(const std::filesystem::path& path, std::uint64_t flags static bool FchflagsThrows(int descriptor, std::uint64_t flags, const char* names) { try { fchflags_nid_postfix(descriptor, flags); + } catch (const std::runtime_error& error) { return std::strstr(error.what(), names) != nullptr; } @@ -248,19 +183,50 @@ static void TestParentTraversal() { Require(stat_nid_postfix("/parent-test-mount/missing/.", &status) == -1 && *__error_nid_postfix() == 2); Require(stat_nid_postfix("/parent-test-mount/existing/.", &status) == 0 && (status.st_mode & 0170000) == 0040000); Require(access_nid_postfix("/parent-test-mount/existing/./", 0) == 0); -#ifndef _WIN32 + Require(TrimTrailingSeparators_nid_no_patch("/") == "/"); + Require(TrimTrailingSeparators_nid_no_patch("/mount/dir//") == "/mount/dir"); + Require(TrimTrailingSeparators_nid_no_patch("dir/") == "dir"); +#ifdef _WIN32 + Require(TrimTrailingSeparators_nid_no_patch("C:\\") == "C:\\"); + Require(TrimTrailingSeparators_nid_no_patch("C:/") == "C:/"); + Require(TrimTrailingSeparators_nid_no_patch("C:\\dir\\") == "C:\\dir"); + Require(TrimTrailingSeparators_nid_no_patch("C:\\\\") == "C:\\"); + Require(TrimTrailingSeparators_nid_no_patch("C:\\folder\\\\") == "C:\\folder"); +#endif + std::filesystem::create_directory(root / "dot-victim"); + for (const char* name : {"/parent-test-mount/dot-victim/.", "/parent-test-mount/dot-victim/..", "/parent-test-mount/dot-victim/./", "/parent-test-mount/dot-victim/../"}) { + Require(unlink_nid_postfix(name) == -1 && *__error_nid_postfix() == 22); + Require(rmdir_nid_postfix(name) == -1 && *__error_nid_postfix() == 22); + Require(sceKernelUnlink(name) == static_cast(0x80020016u)); + Require(sceKernelRmdir(name) == static_cast(0x80020016u)); + } + Require(unlink_nid_postfix("/parent-test-mount/present.txt/.") == -1 && *__error_nid_postfix() == 20); + Require(rename_nid_postfix("/parent-test-mount/present.txt/.", "/parent-test-mount/dot-renamed") == -1 && *__error_nid_postfix() == 20); + Require(rename_nid_postfix("/parent-test-mount/present.txt/..", "/parent-test-mount/dot-renamed") == -1 && *__error_nid_postfix() == 20); + Require(rmdir_nid_postfix("/parent-test-mount/missing/..") == -1 && *__error_nid_postfix() == 2); + Require(std::filesystem::is_directory(root / "dot-victim") && std::filesystem::is_regular_file(root / "present.txt")); + std::filesystem::remove(root / "dot-victim"); for (const char* name : {"slash-unlink", "slash-remove", "slash-rename", "slash-open", "slash-kernel"}) std::ofstream(root / name) << "kept"; Require(unlink_nid_postfix("/parent-test-mount/slash-unlink/") == -1 && *__error_nid_postfix() == 20); Require(remove_nid_postfix("/parent-test-mount/slash-remove/") == -1 && *__error_nid_postfix() == 20); Require(rename_nid_postfix("/parent-test-mount/slash-rename/", "/parent-test-mount/slash-renamed") == -1 && *__error_nid_postfix() == 20); Require(open_nid_postfix("/parent-test-mount/slash-open/", 0, 0) == -1 && *__error_nid_postfix() == 20); Require(sceKernelUnlink("/parent-test-mount/slash-kernel/") == static_cast(0x80020014u)); + Require(rename_nid_postfix("/parent-test-mount/slash-remove", "/parent-test-mount/slash-open/") == -1 && *__error_nid_postfix() == 20); + Require(stat_nid_postfix("/parent-test-mount/slash-open/", &status) == -1 && *__error_nid_postfix() == 20); + Require(access_nid_postfix("/parent-test-mount/slash-open/", 0) == -1 && *__error_nid_postfix() == 20); + Require(chmod_nid_postfix("/parent-test-mount/slash-open/", 0644) == -1 && *__error_nid_postfix() == 20); + Require(mkdir_nid_postfix("/parent-test-mount/slash-open/", 0755) == -1 && *__error_nid_postfix() == 20); + Require(rmdir_nid_postfix("/parent-test-mount/slash-open/") == -1 && *__error_nid_postfix() == 20); + Require(sceKernelOpen("/parent-test-mount/slash-open/", 0x601, 0600) == static_cast(0x80020014u)); + Require(unlink_nid_postfix((root.generic_string() + "/slash-kernel/").c_str()) == -1 && *__error_nid_postfix() == 20); for (const char* name : {"slash-unlink", "slash-remove", "slash-rename", "slash-open", "slash-kernel"}) Require(std::filesystem::is_regular_file(root / name)); Require(!std::filesystem::exists(root / "slash-renamed")); Require(sceKernelStat("/parent-test-mount/Existing/", &status) == 0 && (status.st_mode & 0170000) == 0040000); + const int listed = sceKernelOpen("/parent-test-mount/Existing/", 0x20000, 0); + Require(listed >= 0 && sceKernelClose(listed) == 0); Require(mkdir_nid_postfix("/parent-test-mount/slash-directory/", 0755) == 0 && std::filesystem::is_directory(root / "slash-directory")); Require(rmdir_nid_postfix("/parent-test-mount/slash-directory/") == 0 && !std::filesystem::exists(root / "slash-directory")); -#endif RemovePathAlias_nid_no_patch("parent-test-mount"); std::error_code linkError; std::filesystem::create_directory_symlink("Existing", root / "directory-link", linkError); @@ -289,6 +255,7 @@ static void TestRelinkedModuleNames() { const auto relinked = original + ".guest.prx"; FileStat status{}; Require(sceKernelStat(original.c_str(), &status) == static_cast(0x80020002u)); + Require(lstat_nid_postfix(original.c_str(), &status) == -1 && *__error_nid_postfix() == 2); Require(sceKernelCheckReachability(original.c_str()) == static_cast(0x80020002u)); Require(sceKernelOpen(original.c_str(), 0, 0) == static_cast(0x80020002u)); const auto missing = (root / "missing-content.pak").string(); @@ -301,6 +268,7 @@ static void TestRelinkedModuleNames() { { std::ofstream file(relinked); file << "relinked"; } Require(sceKernelStat(original.c_str(), &status) == 0 && status.st_size == 8); Require(stat_nid_postfix(original.c_str(), &status) == 0 && status.st_size == 8); + Require(lstat_nid_postfix(original.c_str(), &status) == 0 && status.st_size == 8); Require(sceKernelCheckReachability(original.c_str()) == 0); const int opened = sceKernelOpen(original.c_str(), 0, 0); Require(opened >= 0 && sceKernelClose(opened) == 0); @@ -445,6 +413,7 @@ static void TestMissingPathListsDirectoryOnce() { bool ambiguous = false; try { sceKernelStat((root / "PLUGIN.prx").generic_string().c_str(), &status); + } catch (const std::runtime_error&) { ambiguous = true; } @@ -604,6 +573,49 @@ int main() { for (const char* openMode : {"w", "wb", "r+", "a"}) { Require(fopen_nid_postfix(root.string().c_str(), openMode) == nullptr && *__error_nid_postfix() == 21); } + { + const auto listed = root / "listed.txt"; + std::ofstream(listed, std::ios::binary) << "listed"; + for (const char* openMode : {"r", "rb", "re"}) { + auto* directoryStream = fopen_nid_postfix(root.string().c_str(), openMode); + Require(directoryStream != nullptr); + const int directoryDescriptor = fileno_nid_postfix(directoryStream); + FileStat directoryStatus{}; + Require(directoryDescriptor >= 0 && sceKernelFstat(directoryDescriptor, &directoryStatus) == 0 && (directoryStatus.st_mode & 0170000) == 0040000); + Require(feof_nid_postfix(directoryStream) == 0 && ferror_nid_postfix(directoryStream) == 0 && fflush_nid_postfix(directoryStream) == 0); + char unread[8] = {}; + int scanned = 0; + *__error_nid_postfix() = 0; + Require(fread_nid_postfix(unread, 1, sizeof(unread), directoryStream) == 0 && *__error_nid_postfix() == 21); + Require(ferror_nid_postfix(directoryStream) != 0 && feof_nid_postfix(directoryStream) == 0); + clearerr_nid_postfix(directoryStream); + + Require(ferror_nid_postfix(directoryStream) == 0); + *__error_nid_postfix() = 0; + Require(fgetc_nid_postfix(directoryStream) == -1 && *__error_nid_postfix() == 21 && ferror_nid_postfix(directoryStream) != 0); + clearerr_nid_postfix(directoryStream); + + *__error_nid_postfix() = 0; + Require(fgets_nid_postfix(unread, sizeof(unread), directoryStream) == nullptr && *__error_nid_postfix() == 21 && ferror_nid_postfix(directoryStream) != 0); + clearerr_nid_postfix(directoryStream); + + *__error_nid_postfix() = 0; + Require(fscanf_nid_postfix(directoryStream, "%d", &scanned) == -1 && *__error_nid_postfix() == 21 && ferror_nid_postfix(directoryStream) != 0); + clearerr_nid_postfix(directoryStream); + + Require(fseek_nid_postfix(directoryStream, 0, 0) == 0 && ftell_nid_postfix(directoryStream) == 0); + *__error_nid_postfix() = 0; + Require(fputc_nid_postfix('x', directoryStream) == -1 && *__error_nid_postfix() == 9 && ferror_nid_postfix(directoryStream) != 0); + Require(fclose_nid_postfix(directoryStream) == 0); +#ifdef _WIN32 + const int reused = open_nid_postfix(listed.string().c_str(), 0, 0); + char text[6]{}; + Require(reused == directoryDescriptor && read_nid_postfix(reused, text, sizeof(text)) == 6 && std::memcmp(text, "listed", 6) == 0); + Require(close_nid_postfix(reused) == 0); +#endif + } + Require(std::filesystem::remove(listed)); + } Require(mkdir_nid_postfix((root / "missing" / "child").string().c_str(), 0700) == -1 && *__error_nid_postfix() == 2); Require(mkdir_nid_postfix(nullptr, 0700) == -1 && *__error_nid_postfix() == 14); const auto file = root / "file.txt"; @@ -622,6 +634,27 @@ int main() { Require(chflags_nid_postfix(file.string().c_str(), 0x200000) == -1 && *__error_nid_postfix() == 1); Require(ChflagsThrows(file, 0x2, "UF_IMMUTABLE")); Require(ChflagsThrows(file, 0x1000 | 0x40000, "UF_READONLY|SF_APPEND")); + const auto chflagsDangling = root / "dangling.link"; + const auto chflagsPointing = root / "file.link"; +#ifdef _WIN32 + const bool chflagsLinked = CreateSymbolicLinkW(chflagsDangling.wstring().c_str(), L"missing.bin", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0 && + CreateSymbolicLinkW(chflagsPointing.wstring().c_str(), L"file.txt", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0; +#else + std::error_code chflagsLinkError; + std::filesystem::create_symlink("missing.bin", chflagsDangling, chflagsLinkError); + if (!chflagsLinkError) std::filesystem::create_symlink("file.txt", chflagsPointing, chflagsLinkError); + const bool chflagsLinked = !chflagsLinkError; +#endif + if (chflagsLinked) { + Require(chflags_nid_postfix(chflagsDangling.string().c_str(), 0) == -1 && *__error_nid_postfix() == 2); + Require(chflags_nid_postfix(chflagsPointing.string().c_str(), 0) == 0); + Require(chflags_nid_postfix((chflagsPointing.string() + "/").c_str(), 0) == -1 && *__error_nid_postfix() == 20); + Require(chflags_nid_postfix(chflagsPointing.string().c_str(), 0x20) == -1 && *__error_nid_postfix() == 45); + Require(unlink_nid_postfix(chflagsDangling.string().c_str()) == 0 && unlink_nid_postfix(chflagsPointing.string().c_str()) == 0); + } else { + unlink_nid_postfix(chflagsDangling.string().c_str()); + std::fprintf(stderr, "symbolic links unavailable: the chflags link cases not tested\n"); + } const int flagged = open_nid_postfix(file.string().c_str(), 0, 0); Require(flagged >= 0 && fchflags_nid_postfix(flagged, 0) == 0); Require(fchflags_nid_postfix(flagged, 0x20) == -1 && *__error_nid_postfix() == 45); @@ -634,8 +667,33 @@ int main() { Require(fchflags_nid_postfix(flagged, 0) == -1 && *__error_nid_postfix() == 9); Require(fchflags_nid_postfix(flagged, ~0ULL) == -1 && *__error_nid_postfix() == 9); Require(fchflags_nid_postfix(-1, 0) == -1 && *__error_nid_postfix() == 9); + const int flaggedKqueue = kqueue_nid_postfix(); + Require(flaggedKqueue >= 0); + Require(fchflags_nid_postfix(flaggedKqueue, 0) == -1 && *__error_nid_postfix() == 22); + Require(close_nid_postfix(flaggedKqueue) == 0); const int flaggedRoot = sceKernelOpen(root.string().c_str(), 0x20000, 0); Require(flaggedRoot >= 0 && fchflags_nid_postfix(flaggedRoot, 0) == 0 && sceKernelClose(flaggedRoot) == 0); + const auto exclusiveLink = root / "exclusive.link"; + const auto exclusiveTarget = root / "exclusive-target.txt"; +#ifdef _WIN32 + const bool exclusiveLinked = CreateSymbolicLinkW(exclusiveLink.wstring().c_str(), L"exclusive-target.txt", + SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0; +#else + std::error_code exclusiveLinkError; + std::filesystem::create_symlink("exclusive-target.txt", exclusiveLink, exclusiveLinkError); + const bool exclusiveLinked = !exclusiveLinkError; +#endif + if (exclusiveLinked) { + Require(open_nid_postfix(exclusiveLink.string().c_str(), 0xa01, 0644) == -1 && *__error_nid_postfix() == 17); + Require(sceKernelOpen(exclusiveLink.string().c_str(), 0xa01, 0644) == static_cast(0x80020011u)); + Require(!std::filesystem::exists(exclusiveTarget)); + const int exclusiveCreated = open_nid_postfix(exclusiveLink.string().c_str(), 0x201, 0644); + Require(exclusiveCreated >= 0 && close_nid_postfix(exclusiveCreated) == 0 && std::filesystem::is_regular_file(exclusiveTarget)); + Require(open_nid_postfix(exclusiveLink.string().c_str(), 0xa01, 0644) == -1 && *__error_nid_postfix() == 17); + Require(unlink_nid_postfix(exclusiveLink.string().c_str()) == 0 && unlink_nid_postfix(exclusiveTarget.string().c_str()) == 0); + } else { + std::fprintf(stderr, "symbolic links unavailable: the O_EXCL link cases not tested\n"); + } int flaggedPipe[2]; Require(pipe_nid_postfix(flaggedPipe) == 0); Require(fchflags_nid_postfix(flaggedPipe[0], 0) == -1 && *__error_nid_postfix() == 22); @@ -762,6 +820,7 @@ int main() { Require(held != INVALID_HANDLE_VALUE); const int result = kernel ? sceKernelRename(name(lockedSource).c_str(), name(preservedTarget).c_str()) : rename_nid_postfix(name(lockedSource).c_str(), name(preservedTarget).c_str()); + Require(CloseHandle(held) != 0); Require(result != 0 && std::filesystem::is_regular_file(marker)); if (!std::filesystem::is_directory(preservedTarget)) @@ -832,6 +891,37 @@ int main() { }; Require(sceKernelFchmod(descriptor, 0400) == 0 && !ownerWrite()); Require(fchmod_nid_postfix(descriptor, 0600) == 0 && ownerWrite()); + const auto sizedLink = sized.parent_path() / "sized.link"; + const auto missingLink = sized.parent_path() / "missing.link"; + const auto loopLink = sized.parent_path() / "loop.link"; +#ifdef _WIN32 + const DWORD linkFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + const bool chmodLinked = CreateSymbolicLinkW(sizedLink.wstring().c_str(), sized.filename().wstring().c_str(), linkFlags) && + CreateSymbolicLinkW(missingLink.wstring().c_str(), L"missing.bin", linkFlags) && + CreateSymbolicLinkW(loopLink.wstring().c_str(), L"loop.link", linkFlags); +#else + std::error_code chmodLinkError; + std::filesystem::create_symlink(sized.filename(), sizedLink, chmodLinkError); + if (!chmodLinkError) std::filesystem::create_symlink("missing.bin", missingLink, chmodLinkError); + if (!chmodLinkError) std::filesystem::create_symlink("loop.link", loopLink, chmodLinkError); + const bool chmodLinked = !chmodLinkError; +#endif + if (chmodLinked) { + Require(chmod_nid_postfix(sizedLink.string().c_str(), 0400) == 0 && !ownerWrite()); +#ifdef _WIN32 + Require((GetFileAttributesW(sizedLink.wstring().c_str()) & FILE_ATTRIBUTE_READONLY) == 0); +#endif + Require(chmod_nid_postfix(sizedLink.string().c_str(), 0600) == 0 && ownerWrite()); + Require(chmod_nid_postfix(missingLink.string().c_str(), 0600) == -1 && *__error_nid_postfix() == 2); +#ifdef _WIN32 + Require(chmod_nid_postfix(loopLink.string().c_str(), 0600) == -1 && *__error_nid_postfix() == 62); +#endif + } else { + std::fprintf(stderr, "symbolic links unavailable: the chmod link cases not tested\n"); + } + unlink_nid_postfix(sizedLink.string().c_str()); + unlink_nid_postfix(missingLink.string().c_str()); + unlink_nid_postfix(loopLink.string().c_str()); Require(sceKernelFtruncate(descriptor, 3) == 0); FileStat times{}; const KernelTimeval past[2]{{1000000000, 0}, {1000000000, 500000}}; @@ -869,6 +959,8 @@ int main() { Require(fsync_nid_postfix(end) == -1 && *__error_nid_postfix() == 22); Require(fdatasync_nid_postfix(end) == -1 && *__error_nid_postfix() == 22); Require(sceKernelFsync(end) == static_cast(0x80020016u) && sceKernelFdatasync(end) == static_cast(0x80020016u)); + Require(fchmod_nid_postfix(end, 0600) == -1 && *__error_nid_postfix() == 22); + Require(sceKernelFchmod(end, 0600) == static_cast(0x80020016u)); } Require(close_nid_postfix(syncPipe[0]) == 0 && close_nid_postfix(syncPipe[1]) == 0); Require(close_nid_postfix(socket) == 0); @@ -1012,11 +1104,33 @@ int main() { Require(checkRelativeLink(fileLink, 11)); Require(checkRelativeLink(directoryLink, 16)); Require(checkRelativeLink(danglingLink, 11)); + const auto belowStatLoop = statLoop.string() + "\\inside"; + Require(sceKernelUnlink(belowStatLoop.c_str()) == static_cast(0x8002003eu)); + Require(unlink_nid_postfix(belowStatLoop.c_str()) == -1 && *__error_nid_postfix() == 62); + Require(sceKernelMkdir(belowStatLoop.c_str(), 0700) == static_cast(0x8002003eu)); + Require(mkdir_nid_postfix(belowStatLoop.c_str(), 0700) == -1 && *__error_nid_postfix() == 62); + 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(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{}; Require(lstat_nid_postfix(fileLink.string().c_str(), &linkStatus) == 0); Require(stat_nid_postfix(fileLink.string().c_str(), &followed) == 0 && (followed.st_mode & 0170000) == 0100000); Require(followed.st_ino == status.st_ino && linkStatus.st_ino != status.st_ino && linkStatus.st_dev == status.st_dev); Require(stat_nid_postfix(directoryLink.string().c_str(), &followed) == 0 && (followed.st_mode & 0170000) == 0040000); + const auto renameThroughLoop = statLoop.string() + "\\inside"; + const auto renameTarget = (root / "rename-target").string(); + Require(sceKernelRename(renameThroughLoop.c_str(), renameTarget.c_str()) == static_cast(0x8002003eu)); + Require(rename_nid_postfix(renameThroughLoop.c_str(), renameTarget.c_str()) == -1 && *__error_nid_postfix() == 62); + Require(sceKernelRename(present.string().c_str(), renameThroughLoop.c_str()) == static_cast(0x8002003eu)); + Require(rename_nid_postfix(present.string().c_str(), renameThroughLoop.c_str()) == -1 && *__error_nid_postfix() == 62); + const auto missingSource = (root / "missing-source").string(); + const auto underFile = present.string() + "\\child"; + Require(sceKernelRename(missingSource.c_str(), renameThroughLoop.c_str()) == static_cast(0x80020002u)); + Require(rename_nid_postfix(missingSource.c_str(), renameThroughLoop.c_str()) == -1 && *__error_nid_postfix() == 2); + Require(sceKernelRename(underFile.c_str(), renameThroughLoop.c_str()) == static_cast(0x80020014u)); + Require(rename_nid_postfix(underFile.c_str(), renameThroughLoop.c_str()) == -1 && *__error_nid_postfix() == 20); Require(stat_nid_postfix(fileLink.string().c_str(), &status) == 0 && status.st_size == 5); FileStat absoluteStatus{}; Require(lstat_nid_postfix(absoluteLink.string().c_str(), &absoluteStatus) == 0); @@ -1044,6 +1158,42 @@ int main() { const int followedJunction = sceKernelOpen(junction.string().c_str(), 0, 0); Require(followedJunction >= 0 && sceKernelClose(followedJunction) == 0); Require(std::filesystem::remove(junction) && std::filesystem::remove(junctionTarget)); + const auto tagged = root / "tagged.bin"; + { std::ofstream stream(tagged, std::ios::binary); stream << std::string(1u << 18, 'a'); } + const HANDLE taggedHandle = CreateFileW(tagged.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + Require(taggedHandle != INVALID_HANDLE_VALUE); + std::array reparseData{}; + REPARSE_GUID_DATA_BUFFER reparseHeader{}; + reparseHeader.ReparseTag = 0x00000ABC; + reparseHeader.ReparseDataLength = 4; + reparseHeader.ReparseGuid = {0x6a1e3d0b, 0x1f6c, 0x4c1e, {0x9b, 0x52, 0x3d, 0x8e, 0x41, 0x07, 0x5c, 0x2a}}; + std::memcpy(reparseData.data(), &reparseHeader, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE); + DWORD returned = 0; + const bool taggedSet = DeviceIoControl(taggedHandle, FSCTL_SET_REPARSE_POINT, reparseData.data(), + static_cast(reparseData.size()), nullptr, 0, &returned, nullptr) != 0; + if (!taggedSet) Require(GetLastError() == ERROR_PRIVILEGE_NOT_HELD || GetLastError() == ERROR_ACCESS_DENIED); + Require(CloseHandle(taggedHandle) != 0); + if (!taggedSet) { + Require(std::filesystem::remove(tagged)); + std::fprintf(stderr, "Windows reparse point checks skipped: setting a reparse point on a file needs rights this user lacks\n"); + } else { + Require((GetFileAttributesW(tagged.c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0); + FileStat taggedStatus{}; + FileStat taggedFollowed{}; + const int taggedResult = lstat_nid_postfix(tagged.string().c_str(), &taggedStatus); + const int taggedError = *__error_nid_postfix(); + Require(stat_nid_postfix(tagged.string().c_str(), &taggedFollowed) == taggedResult); + Require(taggedResult == 0 ? (taggedStatus.st_ino == taggedFollowed.st_ino && taggedStatus.st_mode == taggedFollowed.st_mode) + : *__error_nid_postfix() == taggedError); + const HANDLE taggedCleanup = CreateFileW(tagged.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + Require(taggedCleanup != INVALID_HANDLE_VALUE); + reparseHeader.ReparseDataLength = 0; + Require(DeviceIoControl(taggedCleanup, FSCTL_DELETE_REPARSE_POINT, &reparseHeader, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, + nullptr, 0, &returned, nullptr) != 0); + Require(CloseHandle(taggedCleanup) != 0 && std::filesystem::remove(tagged)); + } #endif FileStat identity{}; FileStat byDescriptor{}; @@ -1089,6 +1239,32 @@ int main() { Require(lseek_nid_postfix(-1, 0, 7) == -1 && *__error_nid_postfix() == 9); Require(lseek_nid_postfix(0x7fffffff, 0, -1) == -1 && *__error_nid_postfix() == 9); Require(lseek_nid_postfix(locked, 0, 7) == -1 && *__error_nid_postfix() == 22); + { + char transfer[4]{}; + int transferPipe[2] = {-1, -1}; + Require(pipe_nid_postfix(transferPipe) == 0); + Require(read_nid_postfix(-1, nullptr, 1) == -1 && *__error_nid_postfix() == 9); + Require(write_nid_postfix(-1, nullptr, 1) == -1 && *__error_nid_postfix() == 9); + Require(sceKernelRead(-1, nullptr, 1) == static_cast(0x80020009u)); + Require(sceKernelWrite(-1, nullptr, 1) == static_cast(0x80020009u)); + Require(read_nid_postfix(locked, nullptr, 1) == -1 && *__error_nid_postfix() == 14); + Require(pread_nid_postfix(-1, transfer, 1, -1) == -1 && *__error_nid_postfix() == 9); + Require(pread_nid_postfix(-1, nullptr, 1, 0) == -1 && *__error_nid_postfix() == 9); + Require(sceKernelPread(-1, transfer, 1, -1) == static_cast(0x80020009u)); + Require(pread_nid_postfix(transferPipe[0], transfer, 1, -1) == -1 && *__error_nid_postfix() == 29); + Require(pread_nid_postfix(locked, transfer, 1, -1) == -1 && *__error_nid_postfix() == 22); + Require(pread_nid_postfix(locked, nullptr, 1, -1) == -1 && *__error_nid_postfix() == 22); + Require(pread_nid_postfix(locked, nullptr, 1, 0) == -1 && *__error_nid_postfix() == 14); + Require(close_nid_postfix(transferPipe[0]) == 0 && close_nid_postfix(transferPipe[1]) == 0); + } + Require(lseek_nid_postfix(locked, 4, 0) == 4); + Require(lseek_nid_postfix(locked, INT64_MAX, 1) == -1 && *__error_nid_postfix() == 84); + Require(lseek_nid_postfix(locked, 0, 1) == 4); + Require(lseek_nid_postfix(locked, -1, 0) == -1 && *__error_nid_postfix() == 22); + const std::int64_t lockedSize = lseek_nid_postfix(locked, 0, 2); + Require(lockedSize >= 0); + Require(lseek_nid_postfix(locked, INT64_MAX - lockedSize + 1, 2) == -1 && *__error_nid_postfix() == 84); + Require(lseek_nid_postfix(locked, 0, 1) == lockedSize); Require(flock_nid_postfix(-1, 2 | 4) == -1 && *__error_nid_postfix() == 9); Require(flock_nid_postfix(0x7fffffff, 1) == -1 && *__error_nid_postfix() == 9); int flockPipe[2] = {-1, -1}; @@ -1144,7 +1320,7 @@ int main() { const int directoryDescriptor = open_nid_postfix(rootName.c_str(), 0, 0); Require(directoryDescriptor >= 0); char directoryBytes[8]{}; - struct { void* base; std::size_t length; } directoryVector[2] = {{directoryBytes, 3}, {directoryBytes + 3, 5}}; + KernelIovec directoryVector[2] = {{directoryBytes, 3}, {directoryBytes + 3, 5}}; Require(read_nid_postfix(directoryDescriptor, directoryBytes, sizeof(directoryBytes)) == -1 && *__error_nid_postfix() == 21); Require(sceKernelRead(directoryDescriptor, directoryBytes, sizeof(directoryBytes)) == static_cast(0x80020015u)); Require(sceKernelReadv(directoryDescriptor, directoryVector, 2) == static_cast(0x80020015u)); @@ -1370,14 +1546,18 @@ int main() { Require(std::memcmp(first, second, sizeof(first)) != 0); Require(feof_nid_postfix(stream) == 0 && ferror_nid_postfix(stream) == 0); flockfile_nid_postfix(stream); + Require(fread_nid_postfix(first, 1, 4, stream) == 4); funlockfile_nid_postfix(stream); + Require(ftrylockfile_nid_postfix(stream) == 0); funlockfile_nid_postfix(stream); + Require(fflush_nid_postfix(stream) == 0); bool unsupported = false; try { fgetc_nid_postfix(stream); + } catch (const std::runtime_error&) { unsupported = true; } @@ -1387,6 +1567,7 @@ int main() { bool rejected = false; try { fopen_nid_postfix(device, mode); + } catch (const std::runtime_error&) { rejected = true; } diff --git a/core/libs/tests/GuestFiopen.cpp b/core/libs/tests/GuestFiopen.cpp index 276459ec07..b5d8e930bd 100644 --- a/core/libs/tests/GuestFiopen.cpp +++ b/core/libs/tests/GuestFiopen.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -7,14 +8,6 @@ #include #include -extern "C" { -FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const char*, int, int); -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -int APS5_VABI fopen_s_nid_postfix(FileStream**, const char*, const char*); -int APS5_VABI fclose_nid_postfix(FileStream*); -std::int64_t APS5_VABI ftello_nid_postfix(FileStream*); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Fiopen check failed at line %d\n", line); @@ -38,6 +31,7 @@ static std::string Contents(const char* name) { std::string text; for (int c; (c = std::fgetc(stream->GetHandle())) != EOF;) text += static_cast(c); fclose_nid_postfix(stream); + return text; } @@ -45,6 +39,7 @@ static void Write(FileStream* stream, const char* text) { Require(stream != nullptr); Require(std::fputs(text, stream->GetHandle()) >= 0); fclose_nid_postfix(stream); + } static bool Exists(const char* name) { @@ -87,10 +82,12 @@ int main() { auto* stream = Open(file, In); Require(stream != nullptr && std::fgetc(stream->GetHandle()) == 'a'); fclose_nid_postfix(stream); + stream = Open(file, In); Require(stream != nullptr); std::fputc('x', stream->GetHandle()); fclose_nid_postfix(stream); + Require(Contents(file) == "abc"); Write(Open(file, App), "d"); @@ -101,6 +98,7 @@ int main() { stream = Open(file, In | Ate); Require(stream != nullptr && ftello_nid_postfix(stream) == 5); fclose_nid_postfix(stream); + stream = Open(file, In | Binary); Require(stream != nullptr && ftello_nid_postfix(stream) == 0); fclose_nid_postfix(stream); @@ -112,6 +110,7 @@ int main() { stream = Open(file, In | Noreplace); Require(stream != nullptr); fclose_nid_postfix(stream); + stream = Open(file, In | Binary | Noreplace); Require(stream != nullptr); fclose_nid_postfix(stream); @@ -128,6 +127,7 @@ int main() { stream = Open(file, In | Out); Require(stream != nullptr && std::fgetc(stream->GetHandle()) == 'X'); fclose_nid_postfix(stream); + Require(Contents(file) == "Xbcdef"); Write(Open(file, In | Out | App), "g"); @@ -152,7 +152,7 @@ int main() { Require(stream != nullptr && std::fgetc(stream->GetHandle()) == 'k'); fclose_nid_postfix(stream); -#ifndef _WIN32 +#ifdef __linux__ auto* full = std::fopen("/dev/full", "wb"); Require(full != nullptr); auto* fullStream = new FileStream(full, true); diff --git a/core/libs/tests/GuestFirmwareConstantReturns.cpp b/core/libs/tests/GuestFirmwareConstantReturns.cpp new file mode 100644 index 0000000000..f88525c988 --- /dev/null +++ b/core/libs/tests/GuestFirmwareConstantReturns.cpp @@ -0,0 +1,81 @@ +#include "SceTypes.hpp" +#include "prx/libSceComposite/libSceComposite.hpp" +#include "prx/libSceFsInternalForVsh/libSceFsInternalForVsh.hpp" +#include "prx/libSceHttpCache/libSceHttpCache.hpp" +#include "prx/libScePlayReady/libScePlayReady.hpp" +#include "prx/libSceSdma/libSceSdma.hpp" +#include "prx/libSceSsl/libSceSsl.hpp" +#include "prx/libSceVisionManager/libSceVisionManager.hpp" +#include "prx/libmdbg_syscore/libmdbg_syscore.hpp" +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +void CheckComposite() { + constexpr int Unsupported = static_cast(0x80d40006); + Require(sceCompositorCreateIndirectRenderTarget() == Unsupported); + Require(sceCompositorDeleteIndirectRenderTarget() == Unsupported); + Require(sceCompositorSetIndirectCanvasCommand() == 0); + Require(sceCompositorSetIndirectCompositionCommand() == 0); + Require(sceCompositorSetIndirectRenderTargetConfigCommand() == 0); +} + +void CheckFsInternal() { + constexpr int KernelNotSupported = static_cast(0x8002002d); + Require(sceFsErriCtl() == KernelNotSupported); + Require(sceFsErriGetState() == KernelNotSupported); +} + +void CheckHttpCacheAndPlayReady() { + Require(sceHttpCacheDebugSetUrl() == 0); + Require(scePlayReadyMemGetStats() == static_cast(0x80004001)); +} + +void CheckSdma() { + constexpr int SdmaError = static_cast(0x809900c9); + Require(sceSdmaInitialize() == 0); + Require(sceSdmaFinalize() == 0); + Require(sceSdmaQueryWorkingBufferSize() == 0x8000); + Require(sceSdmaFlushAllWithEvent() == SdmaError); + Require(sceSdmaWaitForEvent() == SdmaError); +} + +void CheckSsl() { + Require(CRYPTO_uninitAsymmetricKey() == 0); + Require(VLONG_freeVlongQueue() == 0); +} + +void CheckVisionManager() { + constexpr int VisionError = static_cast(0x80e70004); + Require(sceVisionManagerOpen() == VisionError); + Require(sceVisionManagerClose() == VisionError); + Require(sceVisionManagerRequestFindUser() == VisionError); + Require(sceVisionManagerRequestFindUserInRegion() == VisionError); + Require(sceVisionManagerRequestFindUserWithoutIdentify() == VisionError); + Require(sceVisionManagerRequestRegisterUserInRegion() == VisionError); + Require(sceVisionManagerGetResultOfFindUser() == VisionError); + Require(sceVisionManagerGetResultOfFindUserInRegion() == VisionError); + Require(sceVisionManagerGetResultOfFindUserWithoutIdentify() == VisionError); + Require(sceVisionManagerGetResultOfRegisterUserInRegion() == VisionError); + Require(sceVisionManagerLoadSettings() == 0); +} + +void CheckDebugSyscore() { + Require(sceDebugGetProcessEventCntlFlag() == 1); + Require(sceDebugSetProcessEventCntlFlag() == 1); +} + +} + +int main() { + CheckComposite(); + CheckFsInternal(); + CheckHttpCacheAndPlayReady(); + CheckSdma(); + CheckSsl(); + CheckVisionManager(); + CheckDebugSyscore(); + return 0; +} diff --git a/core/libs/tests/GuestFlexibleMemoryBudget.cpp b/core/libs/tests/GuestFlexibleMemoryBudget.cpp index cba381e734..2b3b74f051 100644 --- a/core/libs/tests/GuestFlexibleMemoryBudget.cpp +++ b/core/libs/tests/GuestFlexibleMemoryBudget.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" @@ -12,24 +13,9 @@ #include extern "C" { -int APS5_VABI sceKernelConfiguredFlexibleMemorySize(std::size_t*); -int APS5_VABI sceKernelAvailableFlexibleMemorySize(std::size_t*); -int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); -int APS5_VABI sceKernelReleaseFlexibleMemory(void*, std::size_t); -int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; -int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); + std::uint64_t GuestImageSegmentBytes_nid_no_patch(); -void* APS5_VABI dlopen_nid_postfix(const char*, int); -char* APS5_VABI dlerror_nid_postfix(); -int APS5_VABI dlclose_nid_postfix(void*); -std::size_t APS5_VABI sceKernelGetDirectMemorySize(); -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelMapDirectMemory2(void**, std::size_t, int, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelMapNamedDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t, const char*); -int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int); -int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); -int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); + } static void Require(bool condition, std::source_location location = std::source_location::current()) { diff --git a/core/libs/tests/GuestFlexibleMemoryModule.cpp b/core/libs/tests/GuestFlexibleMemoryModule.cpp index 1bf220a810..10f33287fc 100644 --- a/core/libs/tests/GuestFlexibleMemoryModule.cpp +++ b/core/libs/tests/GuestFlexibleMemoryModule.cpp @@ -1,3 +1,37 @@ +#ifdef __APPLE__ +#include "prx/libc/include/specifics/linux/ElfTypes.hpp" +#include +#include +#endif + extern "C" { unsigned char guestFlexibleMemoryModuleData[512u * 1024u * 1024u] = {}; } + +#ifdef __APPLE__ +namespace { + +[[gnu::section("__APS5DATA,__meta"), gnu::used]] alignas(8) std::byte metadata[0x60 + sizeof(Elf64_Phdr)]{}; + +[[maybe_unused]] const bool described = [] { + Dl_info image{}; + if (dladdr(guestFlexibleMemoryModuleData, &image) == 0) return false; + const std::uint64_t loadBias = reinterpret_cast(image.dli_fbase); + Elf64_Phdr header{}; + header.p_type = PT_LOAD; + header.p_flags = PF_R | PF_W; + header.p_offset = reinterpret_cast(guestFlexibleMemoryModuleData) - loadBias; + header.p_vaddr = header.p_offset; + header.p_paddr = header.p_offset; + header.p_memsz = sizeof(guestFlexibleMemoryModuleData); + const std::uint64_t version = 2; + const std::uint64_t count = 1; + std::memcpy(metadata + 0x08, &loadBias, sizeof(loadBias)); + std::memcpy(metadata + 0x58, &count, sizeof(count)); + std::memcpy(metadata + 0x60, &header, sizeof(header)); + std::memcpy(metadata, &version, sizeof(version)); + return true; +}(); + +} +#endif diff --git a/core/libs/tests/GuestFloatBuiltins.cpp b/core/libs/tests/GuestFloatBuiltins.cpp index a64f17eb85..e8051b96c0 100644 --- a/core/libs/tests/GuestFloatBuiltins.cpp +++ b/core/libs/tests/GuestFloatBuiltins.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -6,75 +7,6 @@ #include #include -using Int128 = __int128; -using Uint128 = unsigned __int128; - -extern "C" { -double APS5_VABI __adddf3_nid_postfix(double, double); -float APS5_VABI __addsf3_nid_postfix(float, float); -double APS5_VABI __subdf3_nid_postfix(double, double); -float APS5_VABI __subsf3_nid_postfix(float, float); -double APS5_VABI __muldf3_nid_postfix(double, double); -float APS5_VABI __mulsf3_nid_postfix(float, float); -double APS5_VABI __divdf3_nid_postfix(double, double); -float APS5_VABI __divsf3_nid_postfix(float, float); -double APS5_VABI __negdf2_nid_postfix(double); -float APS5_VABI __negsf2_nid_postfix(float); -int APS5_VABI __eqdf2_nid_postfix(double, double); -int APS5_VABI __nedf2_nid_postfix(double, double); -int APS5_VABI __ltdf2_nid_postfix(double, double); -int APS5_VABI __ledf2_nid_postfix(double, double); -int APS5_VABI __gtdf2_nid_postfix(double, double); -int APS5_VABI __gedf2_nid_postfix(double, double); -int APS5_VABI __unorddf2_nid_postfix(double, double); -int APS5_VABI __eqsf2_nid_postfix(float, float); -int APS5_VABI __ltsf2_nid_postfix(float, float); -int APS5_VABI __gesf2_nid_postfix(float, float); -int APS5_VABI __unordsf2_nid_postfix(float, float); -double APS5_VABI __extendsfdf2_nid_postfix(float); -float APS5_VABI __truncdfsf2_nid_postfix(double); -std::int32_t APS5_VABI __fixsfsi_nid_postfix(float); -std::int64_t APS5_VABI __fixsfdi_nid_postfix(float); -Int128 APS5_VABI __fixsfti_nid_postfix(float); -std::int32_t APS5_VABI __fixdfsi_nid_postfix(double); -std::int64_t APS5_VABI __fixdfdi_nid_postfix(double); -Int128 APS5_VABI __fixdfti_nid_postfix(double); -std::uint32_t APS5_VABI __fixunssfsi_nid_postfix(float); -std::uint64_t APS5_VABI __fixunssfdi_nid_postfix(float); -Uint128 APS5_VABI __fixunssfti_nid_postfix(float); -std::uint32_t APS5_VABI __fixunsdfsi_nid_postfix(double); -std::uint64_t APS5_VABI __fixunsdfdi_nid_postfix(double); -Uint128 APS5_VABI __fixunsdfti_nid_postfix(double); -std::int64_t APS5_VABI __fixxfdi_nid_postfix(long double); -Int128 APS5_VABI __fixxfti_nid_postfix(long double); -std::uint32_t APS5_VABI __fixunsxfsi_nid_postfix(long double); -std::uint64_t APS5_VABI __fixunsxfdi_nid_postfix(long double); -Uint128 APS5_VABI __fixunsxfti_nid_postfix(long double); -double APS5_VABI __floatsidf_nid_postfix(std::int32_t); -double APS5_VABI __floatunsidf_nid_postfix(std::uint32_t); -float APS5_VABI __floatsisf_nid_postfix(std::int32_t); -float APS5_VABI __floatunsisf_nid_postfix(std::uint32_t); -double APS5_VABI __floatdidf_nid_postfix(std::int64_t); -float APS5_VABI __floatdisf_nid_postfix(std::int64_t); -long double APS5_VABI __floatdixf_nid_postfix(std::int64_t); -double APS5_VABI __floatundidf_nid_postfix(std::uint64_t); -float APS5_VABI __floatundisf_nid_postfix(std::uint64_t); -long double APS5_VABI __floatundixf_nid_postfix(std::uint64_t); -double APS5_VABI __floattidf_nid_postfix(Int128); -float APS5_VABI __floattisf_nid_postfix(Int128); -long double APS5_VABI __floattixf_nid_postfix(Int128); -double APS5_VABI __floatuntidf_nid_postfix(Uint128); -float APS5_VABI __floatuntisf_nid_postfix(Uint128); -long double APS5_VABI __floatuntixf_nid_postfix(Uint128); -__complex__ float APS5_VABI __mulsc3_nid_postfix(float, float, float, float); -__complex__ double APS5_VABI __muldc3_nid_postfix(double, double, double, double); -__complex__ long double APS5_VABI __mulxc3_nid_postfix(long double, long double, long double, long double); -__complex__ float APS5_VABI __divsc3_nid_postfix(float, float, float, float); -__complex__ double APS5_VABI __divdc3_nid_postfix(double, double, double, double); -__complex__ long double APS5_VABI __divxc3_nid_postfix(long double, long double, long double, long double); -long double APS5_VABI __powixf2_nid_postfix(long double, int); -} - static void Require(bool value) { if (!value) std::abort(); } static std::uint64_t Bits(double value) { return std::bit_cast(value); } diff --git a/core/libs/tests/GuestFloatingPoint.cpp b/core/libs/tests/GuestFloatingPoint.cpp index 3ea9884bb0..6d9afd5ae6 100644 --- a/core/libs/tests/GuestFloatingPoint.cpp +++ b/core/libs/tests/GuestFloatingPoint.cpp @@ -1,29 +1,22 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include +#include #include #include +#include #include #include #include - -extern "C" { -int* APS5_VABI __error_nid_postfix(); -void APS5_VABI _Feraise_nid_postfix(int); -int APS5_VABI fegetround_nid_postfix(); -int APS5_VABI fesetround_nid_postfix(int); -int APS5_VABI fetestexcept_nid_postfix(int); -int APS5_VABI feclearexcept_nid_postfix(int); -int APS5_VABI fegetexceptflag_nid_postfix(std::uint16_t*, int); -int APS5_VABI fesetexceptflag_nid_postfix(const std::uint16_t*, int); -int APS5_VABI feenableexcept_nid_postfix(int); -int APS5_VABI fedisableexcept_nid_postfix(int); -int APS5_VABI __feenableexcept_nid_postfix(int); -int APS5_VABI __fedisableexcept_nid_postfix(int); -int APS5_VABI fegettrapenable_nid_postfix(); -int APS5_VABI fesettrapenable_nid_postfix(int); -} +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif namespace { @@ -44,6 +37,10 @@ void WriteEnvironment(const Environment& state) { _mm_setcsr(state.sse); } +bool SameControlState(const Environment& left, const Environment& right) { + return left.x87[0] == right.x87[0] && left.x87[1] == right.x87[1] && left.sse == right.sse; +} + void Require(bool value, const char* message) { if (!value) { std::fprintf(stderr, "floating-point environment: %s\n", message); @@ -60,6 +57,28 @@ Environment SeedFlags(unsigned x87, unsigned sse) { return ReadEnvironment(); } +void CheckEnabledExceptions() { + const auto seeded = SeedFlags(0, 0); + for (unsigned x87 = 0; x87 <= 0x3f; ++x87) { + for (unsigned sse = 0; sse <= 0x3f; ++sse) { + auto state = seeded; + state.x87[0] = (state.x87[0] & ~0xc3fu) | 0x800 | x87; + state.x87[1] = (state.x87[1] & ~0x3fu) | (x87 & 0x15u); + state.sse = (state.sse & ~0x7fbfu) | 0x2000 | (sse << 7) | (sse & 0x2au); + WriteEnvironment(state); + *__error_nid_postfix() = 123; + const int enabled = fegetexcept_nid_postfix(); + const int error = *__error_nid_postfix(); + const auto after = ReadEnvironment(); + WriteEnvironment(seeded); + Require(enabled == static_cast(0x3f ^ x87), "enabled exceptions use the x87 masks independently of SSE"); + Require(after.x87[0] == state.x87[0] && after.x87[1] == state.x87[1] && after.sse == state.sse, + "enabled exception query preserves control modes and flags"); + Require(error == 123, "enabled exception query preserves errno"); + } + } +} + void CheckRounding() { const auto seeded = SeedFlags(0x02, 0x10); constexpr int modes[] = {0, 0x400, 0x800, 0xc00}; @@ -103,6 +122,7 @@ void CheckFeraise() { const auto seeded = SeedFlags(0, 0); *__error_nid_postfix() = 0; _Feraise_nid_postfix(static_cast(requested)); + const unsigned raised = (requested & 0x18) != 0 ? requested | 0x20 : requested; const auto state = ReadEnvironment(); Require((state.x87[1] & 0x3f) == raised && (state.sse & 0x3f) == raised, "_Feraise raises the flags on both units"); @@ -142,6 +162,7 @@ void CheckFlags() { traps.x87[0] &= ~0x08u; traps.sse &= ~(0x08u << 7); WriteEnvironment(traps); + traps = ReadEnvironment(); const std::uint16_t none = 0; Require(fesetexceptflag_nid_postfix(&none, 0x01) == 0, "restore with an unmasked exception"); Require(feclearexcept_nid_postfix(0x02) == 0, "clear with an unmasked exception"); @@ -187,35 +208,156 @@ unsigned SseMasks() { return (ReadEnvironment().sse >> 7) & 0x3fu; } +bool HostKeepsSseTraps() { + const unsigned saved = _mm_getcsr(); + _mm_setcsr(saved & ~(0x01u << 7)); + const bool kept = (_mm_getcsr() & (0x01u << 7)) == 0; + _mm_setcsr(saved); + return kept; +} + void CheckTraps() { const auto seeded = SeedFlags(0, 0); + const bool sseKept = HostKeepsSseTraps(); + const auto sse = [&](unsigned masks) { return sseKept ? masks : 0x3fu; }; + const auto both = [&](unsigned traps) { return static_cast(0xffffffc0u | (sseKept ? traps : 0u)); }; Require(feenableexcept_nid_postfix(0x49) == static_cast(0xffffffc0u), "enable reports no previous trap as the console does"); Require(X87Masks() == 0x36 && fegettrapenable_nid_postfix() == 0x09, "enable unmasks the x87 exceptions"); - Require(SseMasks() == 0x36, "enable unmasks the SSE exceptions"); - Require(fedisableexcept_nid_postfix(0x41) == static_cast(0xffffffc9u), "disable returns the traps enabled on both units"); + Require(SseMasks() == sse(0x36), "enable unmasks the SSE exceptions"); + Require(fedisableexcept_nid_postfix(0x41) == both(0x09), "disable returns the traps enabled on both units"); Require(X87Masks() == 0x37 && fegettrapenable_nid_postfix() == 0x08, "disable masks the x87 exception"); - Require(SseMasks() == 0x37, "disable masks the SSE exception"); - Require(__feenableexcept_nid_postfix(0x02) == static_cast(0xffffffc8u) && X87Masks() == 0x35, "the internal enable differs"); - Require(__fedisableexcept_nid_postfix(0x3f) == static_cast(0xffffffcau) && X87Masks() == 0x3f && fegettrapenable_nid_postfix() == 0, "the internal disable differs"); + Require(SseMasks() == sse(0x37), "disable masks the SSE exception"); + Require(__feenableexcept_nid_postfix(0x02) == both(0x08) && X87Masks() == 0x35, "the internal enable differs"); + Require(__fedisableexcept_nid_postfix(0x3f) == both(0x0a) && X87Masks() == 0x3f && fegettrapenable_nid_postfix() == 0, "the internal disable differs"); Require(fesettrapenable_nid_postfix(0x40) == 1 && X87Masks() == 0x3f, "an out-of-range trap set was accepted"); - Require(fesettrapenable_nid_postfix(0x10004) == 0 && fegettrapenable_nid_postfix() == 0x04 && SseMasks() == 0x3b, "a trap set did not use the low 16 bits"); - Require(fesettrapenable_nid_postfix(0x01) == 0 && fegettrapenable_nid_postfix() == 0x05 && SseMasks() == 0x3a, "a trap set masked enabled traps"); - Require(fesettrapenable_nid_postfix(0) == 0 && fegettrapenable_nid_postfix() == 0x05 && SseMasks() == 0x3a, "an empty trap set masked enabled traps"); + Require(fesettrapenable_nid_postfix(0x10004) == 0 && fegettrapenable_nid_postfix() == 0x04 && SseMasks() == sse(0x3b), "a trap set did not use the low 16 bits"); + Require(fesettrapenable_nid_postfix(0x01) == 0 && fegettrapenable_nid_postfix() == 0x05 && SseMasks() == sse(0x3a), "a trap set masked enabled traps"); + Require(fesettrapenable_nid_postfix(0) == 0 && fegettrapenable_nid_postfix() == 0x05 && SseMasks() == sse(0x3a), "an empty trap set masked enabled traps"); auto mixed = ReadEnvironment(); mixed.x87[0] = (mixed.x87[0] & ~0x3fu) | 0x36; mixed.sse = (mixed.sse & ~(0x3fu << 7)) | (0x3au << 7); WriteEnvironment(mixed); - Require(feenableexcept_nid_postfix(0) == static_cast(0xffffffc1u), "enable reports only the traps enabled on both units"); + Require(feenableexcept_nid_postfix(0) == both(0x01), "enable reports only the traps enabled on both units"); Require(fegettrapenable_nid_postfix() == 0x09, "the trap query reads only the x87 control word"); WriteEnvironment(seeded); } -int main() { +using HoldExcept = int (APS5_VABI*)(Environment*); + +void CheckHold(HoldExcept hold, Environment* saved) { + const auto before = ReadEnvironment(); + *__error_nid_postfix() = 123; + Require(hold(saved) == 0, "feholdexcept succeeds"); + const auto after = ReadEnvironment(); + Require(std::memcmp(saved, &before, sizeof(before)) == 0, "feholdexcept saves the full original environment"); + Require((after.x87[0] & 0x3f) == 0x3f && (after.sse & 0x1f80) == 0x1f80, "feholdexcept masks both units"); + Require((after.x87[1] & 0x80ff) == 0 && (after.sse & 0x3f) == 0, "feholdexcept clears pending exceptions"); + Require((after.x87[1] & ~0x80ffu) == (before.x87[1] & ~0x80ffu), "feholdexcept preserves other x87 status bits"); + Require((after.x87[0] & ~0x3fu) == (before.x87[0] & ~0x3fu), "feholdexcept preserves x87 control settings"); + Require((after.sse & ~0x1fbfu) == (before.sse & ~0x1fbfu), "feholdexcept preserves SSE control settings"); + Require(after.x87[2] == before.x87[2] && std::memcmp(after.x87.data() + 3, before.x87.data() + 3, 16) == 0, "feholdexcept preserves x87 tags and instruction/data state"); + Require(*__error_nid_postfix() == 123, "feholdexcept preserves errno"); +} + +void CheckHoldExcept(HoldExcept hold) { + Require(hold != nullptr, "feholdexcept export is missing"); + static_assert(sizeof(Environment) == 32 && alignof(Environment) == 4 && offsetof(Environment, sse) == 28); + const auto original = ReadEnvironment(); + struct { + std::array before; + Environment value; + std::array after; + } output; + for (unsigned rounding = 0; rounding < 4; ++rounding) { + for (unsigned modes = 0; modes < 4; ++modes) { + for (unsigned mask = 0; mask < 64; ++mask) { + auto state = original; + const unsigned sseMask = (mask * 13 + 7) & 0x3f; + state.x87[0] = (state.x87[0] & ~0xc3fu) | (rounding << 10) | mask; + state.x87[1] = (state.x87[1] & ~0x80ffu) | (mask & 0x25); + state.sse = (state.sse & ~0xffffu) | (sseMask << 7) | (sseMask & 0x1a) | (((rounding + 1) & 3) << 13) | ((modes & 1) << 6) | ((modes & 2) << 14); + WriteEnvironment(state); + std::memset(&output, 0xa5, sizeof(output)); + CheckHold(hold, &output.value); + for (const auto byte : output.before) Require(byte == 0xa5, "feholdexcept underwrites output"); + for (const auto byte : output.after) Require(byte == 0xa5, "feholdexcept overwrites output"); + } + } + } + WriteEnvironment(original); + bool threw = false; + try { hold(nullptr); } catch (const std::invalid_argument&) { threw = true; } + Require(threw, "feholdexcept rejects null storage"); + const auto afterNull = ReadEnvironment(); + Require(SameControlState(original, afterNull), "null feholdexcept preserves the environment"); +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + const std::size_t page = info.dwPageSize; + auto* memory = static_cast(VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(memory != nullptr, "allocate feholdexcept guard pages"); + DWORD oldProtection; + Require(VirtualProtect(memory + page, page, PAGE_NOACCESS, &oldProtection) != 0, "protect feholdexcept trailing page"); +#else + const auto page = static_cast(sysconf(_SC_PAGESIZE)); + auto* memory = static_cast(mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(memory != MAP_FAILED, "allocate feholdexcept guard pages"); + Require(mprotect(memory + page, page, PROT_NONE) == 0, "protect feholdexcept trailing page"); +#endif + CheckHold(hold, reinterpret_cast(memory + page - sizeof(Environment))); +#ifdef _WIN32 + Require(VirtualFree(memory, 0, MEM_RELEASE) != 0, "release feholdexcept guard pages"); +#else + Require(munmap(memory, page * 2) == 0, "release feholdexcept guard pages"); +#endif + WriteEnvironment(original); + std::barrier ready(2); + const auto worker = [&](unsigned mode) { + const auto saved = ReadEnvironment(); + auto state = saved; + state.x87[0] = (state.x87[0] & ~0xc00u) | (mode << 10) | 0x3f; + state.x87[1] &= ~0x80ffu; + state.sse |= 0x1f80; + state.sse &= ~0x3fu; + WriteEnvironment(state); + ready.arrive_and_wait(); + Environment output; + CheckHold(hold, &output); + ready.arrive_and_wait(); + Require(fegetround_nid_postfix() == static_cast(mode << 10), "feholdexcept keeps thread-local rounding"); + WriteEnvironment(saved); + }; + std::thread first(worker, 1); + std::thread second(worker, 2); + first.join(); + second.join(); + const auto afterThreads = ReadEnvironment(); + Require(SameControlState(original, afterThreads), "feholdexcept workers preserve the main environment"); +} + +int main(int argc, char** argv) { + Require(argc == 2, "expected libc path"); +#ifdef _WIN32 + const auto library = LoadLibraryA(argv[1]); + Require(library != nullptr, "load libc for feholdexcept"); + const auto hold = reinterpret_cast(GetProcAddress(library, "feholdexcept_nid_postfix")); +#else + const auto library = dlopen(argv[1], RTLD_NOW); + Require(library != nullptr, "load libc for feholdexcept"); + const auto hold = reinterpret_cast(dlsym(library, "feholdexcept_nid_postfix")); +#endif const auto original = ReadEnvironment(); CheckRounding(); + CheckEnabledExceptions(); CheckFlags(); CheckTraps(); CheckThreads(); + CheckHoldExcept(hold); WriteEnvironment(original); +#ifdef _WIN32 + FreeLibrary(library); +#else + dlclose(library); +#endif std::puts("floating-point environment checks passed"); } diff --git a/core/libs/tests/GuestFminf.cpp b/core/libs/tests/GuestFminf.cpp index fcf48171a6..a414e26444 100644 --- a/core/libs/tests/GuestFminf.cpp +++ b/core/libs/tests/GuestFminf.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,15 +6,6 @@ #include #include -extern "C" { -double APS5_VABI fmin_nid_postfix(double, double); -float APS5_VABI fminf_nid_postfix(float, float); -long double APS5_VABI fminl_nid_postfix(long double, long double); -double APS5_VABI fmax_nid_postfix(double, double); -float APS5_VABI fmaxf_nid_postfix(float, float); -long double APS5_VABI fmaxl_nid_postfix(long double, long double); -} - static void Check(bool condition, const char* name, int line) { if (!condition) { std::fprintf(stderr, "%s check failed at line %d\n", name, line); diff --git a/core/libs/tests/GuestFnmatch.cpp b/core/libs/tests/GuestFnmatch.cpp index 32b4e5b16e..8ed07dc566 100644 --- a/core/libs/tests/GuestFnmatch.cpp +++ b/core/libs/tests/GuestFnmatch.cpp @@ -1,15 +1,86 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libScePosixForWebKit/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" int APS5_VABI fnmatch_nid_postfix(const char*, const char*, int); +#include +#include +#include + struct Case { const char* pattern; const char* string; int flags; int result; }; +static void Require(bool value) { + if (!value) { + std::fputs("Guest byte case check failed\n", stderr); + std::abort(); + } +} + +static int FoldCByte(int byte) { + return byte >= 'A' && byte <= 'Z' ? byte + 'a' - 'A' : byte; +} + +static void CheckCaseInsensitiveBytePairs() { + for (int a = 0; a < 256; ++a) { + for (int b = 0; b < 256; ++b) { + char left[] = {static_cast(a), 0}; + char right[] = {static_cast(b), 0}; + const int expected = FoldCByte(a) - FoldCByte(b); + const int sign = (expected > 0) - (expected < 0); + Require(strcasecmp_nid_postfix(left, right) == sign); + Require(strncasecmp_nid_postfix(left, right, 0) == 0); + Require(strncasecmp_nid_postfix(left, right, 1) == sign); + Require(strncasecmp_nid_postfix(left, right, 2) == sign); + const char* match = b == 0 || expected == 0 ? left : nullptr; + Require(strcasestr_nid_postfix(left, right) == match); + const char pattern[] = {'\\', static_cast(a), 0}; + Require(fnmatch_nid_postfix(a == 0 ? pattern + 1 : pattern, right, 0x10) == (expected == 0 ? 0 : 1)); + Require(static_cast(left[0]) == a && left[1] == 0); + Require(static_cast(right[0]) == b && right[1] == 0); + } + } + const char left = 'A', right = 'a'; + Require(strncasecmp_nid_postfix(&left, &right, 0) == 0); + Require(strncasecmp_nid_postfix(&left, &right, 1) == 0); + const char repeated[] = "AaAaBAaAaC"; + Require(strcasestr_nid_postfix(repeated, "aaac") == repeated + 6); + Require(strcasestr_nid_postfix(repeated, "aAa") == repeated); + Require(strcasestr_nid_postfix(repeated, "aaad") == nullptr); + Require(strcasecmp_nid_postfix("AbC", "aBcD") == -1); + Require(strncasecmp_nid_postfix("AbC", "aBcD", 3) == 0); + Require(strncasecmp_nid_postfix("AbC", "aBcD", 4) == -1); +} + +static void CheckCaseInsensitiveHostLocale() { + const char* original = std::setlocale(LC_CTYPE, nullptr); + Require(original != nullptr); + struct RestoreLocale { + std::string name; + ~RestoreLocale() { Require(std::setlocale(LC_CTYPE, name.c_str()) != nullptr); } + } restore{original}; + Require(std::setlocale(LC_CTYPE, "C") != nullptr); + CheckCaseInsensitiveBytePairs(); + bool exercised = false; + for (const char* name : {".1252", "English_United States.1252", "en_US.ISO8859-1", "de_DE.ISO8859-1", + "en_US.iso88591", "tr_TR.ISO8859-9", "Turkish_Turkey.1254", "tr_TR.UTF-8"}) { + if (!std::setlocale(LC_CTYPE, name)) continue; + bool changed = false; + for (int byte = 0; byte < 256; ++byte) changed |= std::tolower(byte) != FoldCByte(byte); + if (!changed) continue; + std::fprintf(stderr, "Checking guest byte comparisons with host LC_CTYPE=%s\n", name); + CheckCaseInsensitiveBytePairs(); + exercised = true; + } + if (!exercised) std::fputs("No installed locale changes host byte case folding; host-locale regression skipped\n", stderr); +} + int main() { + CheckCaseInsensitiveHostLocale(); static const Case cases[] = { {"*.txt", "notes.txt", 0x00, 0}, {"*.txt", "notes.TXT", 0x00, 1}, @@ -71,6 +142,7 @@ int main() { bool threw = false; try { fnmatch_nid_postfix(nullptr, "x", 0); + } catch (const std::invalid_argument&) { threw = true; } diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index 80184223b8..093c88fe45 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -1,11 +1,17 @@ +#include "prx/libSceFontFt/include/Api.hpp" +#include "prx/libSceFont/include/Api.hpp" #include "prx/libSceFont/include/FontDriver.hpp" +#include "prx/libc/include/General.hpp" #include "tests/FontFixture.hpp" +#include #include +#include #include #include #include #include #include +#include #include #include #include @@ -13,71 +19,6 @@ #include #include -extern "C" { -int APS5_VABI sceFontMemoryInit(FontMemory*, void*, std::uint32_t, const FontMemoryInterface*, void*, FontMemoryDestroyFunction, void*); -int APS5_VABI sceFontMemoryTerm(FontMemory*); -int APS5_VABI sceFontCreateLibrary(const FontMemory*, const void*, FontLibrary*); -int APS5_VABI sceFontDestroyLibrary(FontLibrary*); -int APS5_VABI sceFontCreateRenderer(const FontMemory*, const void*, FontRenderer*); -int APS5_VABI sceFontDestroyRenderer(FontRenderer*); -int APS5_VABI sceFontGetPixelResolution(FontLibrary, std::uint32_t*); -int APS5_VABI sceFontAttachDeviceCacheBuffer(FontLibrary, void*, std::uint32_t); -int APS5_VABI sceFontClearDeviceCache(FontLibrary); -int APS5_VABI sceFontDettachDeviceCacheBuffer(FontLibrary, void**, std::uint32_t*); -int APS5_VABI sceFontSupportSystemFonts(FontLibrary); -int APS5_VABI sceFontSupportExternalFonts(FontLibrary, std::uint32_t, std::uint32_t); -int APS5_VABI sceFontOpenFontSet(FontLibrary, std::uint32_t, std::uint32_t, const FontOpenDetail*, FontHandle*); -int APS5_VABI sceFontOpenFontMemory(FontLibrary, const void*, std::uint32_t, const FontOpenDetail*, FontHandle*); -int APS5_VABI sceFontOpenFontFile(FontLibrary, const char*, std::uint32_t, const FontOpenDetail*, FontHandle*); -int APS5_VABI sceFontCloseFont(FontHandle); -int APS5_VABI sceFontSetScriptLanguage(FontHandle, int, int); -int APS5_VABI sceFontGetScriptLanguage(FontHandle, int, int*); -int APS5_VABI sceFontSetTypographicDesign(FontHandle, int, int); -int APS5_VABI sceFontGetTypographicDesign(FontHandle, int, int*); -int APS5_VABI sceFontDefineAttribute(FontHandle, int, int*); -int APS5_VABI sceFontGetAttribute(FontHandle, int, int*); -int APS5_VABI sceFontSetResolutionDpi(FontHandle, std::uint32_t, std::uint32_t); -int APS5_VABI sceFontGetResolutionDpi(FontHandle, std::uint32_t*, std::uint32_t*); -int APS5_VABI sceFontSetScalePixel(FontHandle, float, float); -int APS5_VABI sceFontBindRenderer(FontHandle, FontRenderer); -int APS5_VABI sceFontUnbindRenderer(FontHandle); -int APS5_VABI sceFontSetupRenderScalePixel(FontHandle, float, float); -int APS5_VABI sceFontSetupRenderScalePoint(FontHandle, float, float); -int APS5_VABI sceFontGetKerning(FontHandle, std::uint32_t, std::uint32_t, FontKerning*); -int APS5_VABI sceFontGetFontGlyphsCount(FontHandle, std::uint32_t*); -int APS5_VABI sceFontGetCharGlyphCode(FontHandle, std::uint32_t, std::uint32_t*); -int APS5_VABI sceFontGetFontResolution(FontHandle, std::uint32_t*, float*); -int APS5_VABI sceFontGetRenderScaledKerning(FontHandle, std::uint32_t, std::uint32_t, FontKerning*); -int APS5_VABI sceFontGenerateCharGlyph(FontHandle, std::uint32_t, const FontGenerateGlyphDetail*, FontGlyph*); -int APS5_VABI sceFontGlyphDefineAttribute(FontGlyph, int, int*); -int APS5_VABI sceFontGlyphGetAttribute(FontGlyph, int, int*); -int APS5_VABI sceFontDeleteGlyph(const FontMemory*, FontGlyph*); -int APS5_VABI sceFontTextSourceInit(FontTextSource*, const void*, std::uint32_t, FontTextParseFunction, void*); -int APS5_VABI sceFontCreateString(const FontMemory*, FontTextSource*, const FontCreateStringDetail*, FontString*); -int APS5_VABI sceFontDestroyString(FontString*); -FontTextCharacter* APS5_VABI sceFontStringRefersTextCharacters(FontString, std::uint32_t*); -int APS5_VABI sceFontWritingInit(FontWriting*, FontString, const FontTextCharacter*); -const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting*); -FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting*, const FontWritingLetterStep**); -const void* APS5_VABI sceFontSelectLibraryFt(int); -const void* APS5_VABI sceFontSelectRendererFt(int); -int APS5_VABI sceFontFtSupportBdf(); -int APS5_VABI sceFontFtSupportCid(); -int APS5_VABI sceFontFtSupportOpenType(); -int APS5_VABI sceFontFtSupportOpenTypeOtf(); -int APS5_VABI sceFontFtSupportOpenTypeTtf(); -int APS5_VABI sceFontFtSupportPcf(); -int APS5_VABI sceFontFtSupportPfr(); -int APS5_VABI sceFontFtSupportTrueType(); -int APS5_VABI sceFontFtSupportType1(); -int APS5_VABI sceFontFtSupportType42(); -int APS5_VABI sceFontFtSupportWinFonts(); -int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter*, int*); -FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(const FontTextCharacter*, const FontTextCharacter*, FontTextCodes*); -FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes*); -FontTextCodes* APS5_VABI sceFontTextCodesStepBack(FontTextCodes*); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Font check failed at line %d\n", line); @@ -88,9 +29,9 @@ static void Check(bool value, int line) { static void SetFontDirectory(const std::filesystem::path& directory) { #ifdef _WIN32 - Require(_putenv_s("ANYPS5_SYSTEM_FONTS", directory.string().c_str()) == 0); + Require(_wputenv_s(L"ANYPS5_SYSTEM_FONTS", directory.wstring().c_str()) == 0); #else - Require(::setenv("ANYPS5_SYSTEM_FONTS", directory.string().c_str(), 1) == 0); + Require(::setenv("ANYPS5_SYSTEM_FONTS", directory.c_str(), 1) == 0); #endif } @@ -108,6 +49,12 @@ static std::uint32_t APS5_VABI CoarsePixelResolution() { return 16; } +static int APS5_VABI WideFontResolution(Font::FontObj*, std::uint32_t* resolution, float* scale) { + *resolution = 0x12345678; + *scale = 12.5f; + return SCE_FONT_OK; +} + using FontFixture::BuildFont; using FontFixture::Words; @@ -140,6 +87,25 @@ static std::int32_t APS5_VABI ParseText(FontTextSource* source, void** order, Fo return 1; } +struct ParsedCodes { + const std::uint32_t* codes; + std::size_t count; + std::size_t at; + FontHandle font; +}; + +static std::int32_t APS5_VABI ParseCodes(FontTextSource* source, void** order, FontTextParseResult* result) { + auto* parsed = static_cast(source->textObject); + *order = const_cast(parsed->codes + parsed->at); + if (parsed->at == parsed->count) { + result->Terminate.terminateCode = 0; + return 0; + } + result->FontCode.font = parsed->font; + result->FontCode.code = parsed->codes[parsed->at++]; + return 1; +} + static void CheckTextCodes(const FontMemory* memory, FontHandle font) { ParsedText parsed{"AVA", 0, font}; FontTextSource source{}; @@ -149,6 +115,73 @@ static void CheckTextCodes(const FontMemory* memory, FontHandle font) { std::uint32_t count = 0; FontTextCharacter* characters = sceFontStringRefersTextCharacters(string, &count); Require(characters != nullptr && count == 3); + Require(sceFontStringGetWritingForm(string) == 0x12); + FontString rtlString = nullptr; + Require(sceFontTextSourceSetWritingForm(&source, 0x11) == SCE_FONT_OK && sceFontCreateString(memory, &source, nullptr, &rtlString) == SCE_FONT_OK); + Require(sceFontStringGetWritingForm(rtlString) == 0x11); + Require(sceFontDestroyString(&rtlString) == SCE_FONT_OK); + Require(sceFontTextSourceSetWritingForm(&source, 0x12) == SCE_FONT_OK && sceFontCreateString(memory, &source, nullptr, &rtlString) == SCE_FONT_OK); + Require(sceFontStringGetWritingForm(rtlString) == 0x12); + Require(sceFontDestroyString(&rtlString) == SCE_FONT_OK); + Require(sceFontTextSourceSetWritingForm(&source, 0x10) == SCE_FONT_OK); + + const std::uint32_t latinCodes[]{'A', 0x00E9, 0x4E2D}; + const std::uint32_t greekCodes[]{0x03B1}; + const std::uint32_t digitsCodes[]{'1', '2', ' '}; + const std::uint32_t hebrewCodes[]{0x05E9, 0x05DC}; + const std::uint32_t arabicCodes[]{0x0627, 0x0644}; + const std::uint32_t syriacCodes[]{0x0710}; + const std::uint32_t thaanaCodes[]{0x0780}; + const std::uint32_t nkoCodes[]{0x07CA}; + const std::uint32_t hebrewLetterCodes[]{0xFB1D}; + const std::uint32_t arabicFormCodes[]{0xFE70}; + const std::uint32_t digitHebrewCodes[]{'1', ' ', 0x05D0}; + const std::uint32_t hebrewLatinCodes[]{0x05D0, 'A'}; + const std::uint32_t latinHebrewCodes[]{'A', 0x05D0}; + const std::uint32_t rlmLatinCodes[]{0x200F, 'A'}; + const std::uint32_t lrmHebrewCodes[]{0x200E, 0x05D0}; + const std::uint32_t embeddingCodes[]{0x202B, 'A'}; + const std::uint32_t overrideCodes[]{0x202E, 'A'}; + const std::uint32_t isolatedHebrewCodes[]{0x2067, 0x05D0, 0x2069, 'A'}; + const std::uint32_t isolatedLatinCodes[]{0x2066, 'A', 0x2069, 0x05D0}; + const std::uint32_t openIsolateCodes[]{0x2067, 0x05D0}; + const struct { const std::uint32_t* codes; std::size_t count; std::int32_t sourceForm; int expected; } forms[]{ + {latinCodes, 3, 0x10, 0x12}, + {greekCodes, 1, 0x10, 0x12}, + {digitsCodes, 3, 0x10, 0x12}, + {nullptr, 0, 0x10, 0x12}, + {hebrewCodes, 2, 0x10, 0x11}, + {arabicCodes, 2, 0x10, 0x11}, + {syriacCodes, 1, 0x10, 0x11}, + {thaanaCodes, 1, 0x10, 0x11}, + {nkoCodes, 1, 0x10, 0x11}, + {hebrewLetterCodes, 1, 0x10, 0x11}, + {arabicFormCodes, 1, 0x10, 0x11}, + {digitHebrewCodes, 3, 0x10, 0x11}, + {hebrewLatinCodes, 2, 0x10, 0x11}, + {latinHebrewCodes, 2, 0x10, 0x12}, + {rlmLatinCodes, 2, 0x10, 0x11}, + {lrmHebrewCodes, 2, 0x10, 0x12}, + {embeddingCodes, 2, 0x10, 0x12}, + {overrideCodes, 2, 0x10, 0x12}, + {isolatedHebrewCodes, 4, 0x10, 0x12}, + {isolatedLatinCodes, 4, 0x10, 0x11}, + {openIsolateCodes, 2, 0x10, 0x12}, + {hebrewCodes, 2, 0x11, 0x11}, + {hebrewCodes, 2, 0x12, 0x12}, + {latinCodes, 3, 0x11, 0x11}, + {latinCodes, 3, 0x12, 0x12}, + }; + for (const auto& form : forms) { + ParsedCodes parsedCodes{form.codes, form.count, 0, font}; + FontTextSource codeSource{}; + FontString codeString = nullptr; + Require(sceFontTextSourceInit(&codeSource, parsed.text, 3, ParseCodes, &parsedCodes) == SCE_FONT_OK); + Require(sceFontTextSourceSetWritingForm(&codeSource, form.sourceForm) == SCE_FONT_OK); + Require(sceFontCreateString(memory, &codeSource, nullptr, &codeString) == SCE_FONT_OK && codeString != nullptr); + Require(sceFontStringGetWritingForm(codeString) == form.expected); + Require(sceFontDestroyString(&codeString) == SCE_FONT_OK); + } FontTextCodes codes; std::memset(&codes, 0xa5, sizeof(codes)); @@ -187,6 +220,7 @@ static void CheckTextCodes(const FontMemory* memory, FontHandle font) { bool threw = false; try { sceFontCharactersRefersTextCodes(nullptr, nullptr, &codes); + } catch (const std::runtime_error&) { threw = true; } @@ -198,6 +232,68 @@ 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 CheckGlyphScale(FontHandle font, const FontMemory* memory, float expectedW, float expectedH) { + float width = 0.0f; + float height = 0.0f; + Require(sceFontGetScalePixel(font, &width, &height) == SCE_FONT_OK); + Require(width == expectedW && height == expectedH); + FontGlyph glyph = nullptr; + Require(sceFontGenerateCharGlyph(font, 'A', nullptr, &glyph) == SCE_FONT_OK && glyph != nullptr); + Require(sceFontGlyphGetScalePixel(glyph, &width, &height) == SCE_FONT_OK); + if (width != expectedW || height != expectedH) { + std::fprintf(stderr, "Generated glyph scale %.3f x %.3f differs from font scale %.3f x %.3f\n", width, height, expectedW, expectedH); + } + Require(width == expectedW && height == expectedH); + Require(glyph->em_size == static_cast(std::lround(expectedH))); + Require(sceFontDeleteGlyph(memory, &glyph) == SCE_FONT_OK && glyph == nullptr); +} + +static void CheckPointAndDpiScale(FontHandle font, FontLibrary library, const FontMemory* memory, FontRenderer renderer) { + FontKerning kerning{}; + Require(sceFontSetScalePoint(font, 20.0f, 30.0f) == SCE_FONT_OK); + CheckGlyphScale(font, memory, 40.0f, 60.0f); + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -8.0f)); + Require(sceFontSetResolutionDpi(font, 216, 288) == SCE_FONT_OK); + CheckGlyphScale(font, memory, 60.0f, 120.0f); + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -12.0f)); + Require(sceFontSetScalePoint(font, 50.0f, 40.0f) == SCE_FONT_OK); + CheckGlyphScale(font, memory, 150.0f, 160.0f); + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -30.0f)); + Require(sceFontSetResolutionDpi(font, 0, 0) == SCE_FONT_OK); + CheckGlyphScale(font, memory, 50.0f, 40.0f); + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -10.0f)); + Require(sceFontSetScalePixel(font, 100.0f, 75.0f) == SCE_FONT_OK); + Require(sceFontSetResolutionDpi(font, 144, 216) == SCE_FONT_OK); + CheckGlyphScale(font, memory, 100.0f, 75.0f); + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -20.0f)); + Require(sceFontSetScalePoint(font, 20.0f, 30.0f) == SCE_FONT_OK); + CheckGlyphScale(font, memory, 40.0f, 90.0f); + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -8.0f)); + FontHandle instance = nullptr; + Require(sceFontOpenFontInstance(font, nullptr, &instance) == SCE_FONT_OK && instance != nullptr); + CheckGlyphScale(instance, memory, 40.0f, 90.0f); + Require(sceFontGetKerning(instance, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -8.0f)); + Require(sceFontSetScalePoint(instance, 30.0f, 40.0f) == SCE_FONT_OK); + CheckGlyphScale(instance, memory, 60.0f, 120.0f); + CheckGlyphScale(font, memory, 40.0f, 90.0f); + Require(sceFontCloseFont(instance) == SCE_FONT_OK); + Require(sceFontBindRenderer(font, renderer) == SCE_FONT_OK); + Require(sceFontSetupRenderScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -20.0f)); + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -8.0f)); + Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); + const std::vector data = KerningFont(); + FontHandle fresh = nullptr; + Require(sceFontOpenFontMemory(library, data.data(), static_cast(data.size()), nullptr, &fresh) == SCE_FONT_OK); + float width = 0.0f; + float height = 0.0f; + Require(sceFontGetScalePixel(fresh, &width, &height) == SCE_FONT_OK); + CheckGlyphScale(fresh, memory, width, height); + Require(sceFontCloseFont(fresh) == SCE_FONT_OK); + Require(sceFontSetResolutionDpi(font, 144, 144) == SCE_FONT_OK); + Require(sceFontSetScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK); +} + int main() { constexpr std::uint32_t SystemFontSet = 0x18070043u; const std::filesystem::path fontRoot = std::filesystem::temp_directory_path() / ("anyps5_guest_font-" + std::to_string(std::random_device{}())); @@ -209,16 +305,36 @@ int main() { Require(sceFontMemoryInit(&memory, nullptr, 0, &iface, nullptr, nullptr, nullptr) == SCE_FONT_OK); Require(sceFontSelectLibraryFt(0) != nullptr && sceFontSelectLibraryFt(1) == nullptr); Require(sceFontSelectRendererFt(0) != nullptr && sceFontSelectRendererFt(1) == nullptr); - Require(sceFontFtSupportBdf() == 0 && sceFontFtSupportCid() == 0 && sceFontFtSupportOpenType() == 0 && sceFontFtSupportOpenTypeOtf() == 0 && sceFontFtSupportOpenTypeTtf() == 0 && sceFontFtSupportPcf() == 0 && sceFontFtSupportPfr() == 0 && sceFontFtSupportTrueType() == 0 && sceFontFtSupportType1() == 0 && sceFontFtSupportType42() == 0 && sceFontFtSupportWinFonts() == 0); FontLibrary library = nullptr; Require(sceFontCreateLibrary(&memory, nullptr, &library) == SCE_FONT_ERROR_INVALID_PARAMETER && library == nullptr); Require(sceFontCreateLibrary(&memory, sceFontSelectLibraryFt(0), &library) == SCE_FONT_OK && library != nullptr); + Require(sceFontFtSupportBdf(library) == 0 && sceFontFtSupportCid(library) == 0 && sceFontFtSupportOpenType(library) == 0 && sceFontFtSupportOpenTypeOtf(library) == 0 && sceFontFtSupportOpenTypeTtf(library) == 0 && sceFontFtSupportPcf(library) == 0 && sceFontFtSupportPfr(library) == 0 && sceFontFtSupportTrueType(library) == 0 && sceFontFtSupportTrueTypeGx(library) == 0 && sceFontFtSupportType1(library) == 0 && sceFontFtSupportType42(library) == 0 && sceFontFtSupportWinFonts(library) == 0); + const auto* nativeLibrary = static_cast(library); + const auto savedLock = nativeLibrary->lock_word; + for (auto support : {sceFontFtSupportBdf, sceFontFtSupportCid, sceFontFtSupportOpenType, sceFontFtSupportOpenTypeOtf, sceFontFtSupportOpenTypeTtf, sceFontFtSupportPcf, sceFontFtSupportPfr, sceFontFtSupportTrueType, sceFontFtSupportTrueTypeGx, sceFontFtSupportType1, sceFontFtSupportType42, sceFontFtSupportWinFonts}) { + Font::FontLibNative invalidLibrary{}; + Require(support(nullptr) == SCE_FONT_ERROR_INVALID_LIBRARY); + Require(support(&invalidLibrary) == SCE_FONT_ERROR_INVALID_LIBRARY); + Require(support(library) == SCE_FONT_OK); + Require(nativeLibrary->lock_word == savedLock); + } + + Font::FontLibNative foreignLibrary{}; + foreignLibrary.magic = Font::LIBRARY_MAGIC; + foreignLibrary.lock_word = 5; + bool foreignDriverRejected = false; + try { + static_cast(sceFontFtSupportTrueType(&foreignLibrary)); + } catch (const std::runtime_error&) { + foreignDriverRejected = true; + } + Require(foreignDriverRejected && foreignLibrary.lock_word == 5); std::uint32_t subPixelCount = 1; Require(sceFontGetPixelResolution(library, &subPixelCount) == SCE_FONT_OK && subPixelCount == 64); Require(sceFontGetPixelResolution(library, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); - Require(sceFontGetPixelResolution(nullptr, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGetPixelResolution(nullptr, nullptr) == SCE_FONT_ERROR_INVALID_LIBRARY); subPixelCount = 1; Require(sceFontGetPixelResolution(nullptr, &subPixelCount) == SCE_FONT_ERROR_INVALID_LIBRARY && subPixelCount == 0); FontHandleOpaque notALibrary{}; @@ -230,13 +346,18 @@ int main() { Require(sceFontCreateLibrary(&memory, &coarseDriver, &coarseLibrary) == SCE_FONT_OK && coarseLibrary != nullptr); Require(sceFontGetPixelResolution(coarseLibrary, &subPixelCount) == SCE_FONT_OK && subPixelCount == 16); Require(sceFontGetPixelResolution(library, &subPixelCount) == SCE_FONT_OK && subPixelCount == 64); + Require(static_cast(coarseLibrary)->lock_word == 0); coarseDriver.pixel_resolution = nullptr; Require(sceFontGetPixelResolution(coarseLibrary, &subPixelCount) == SCE_FONT_ERROR_INVALID_LIBRARY && subPixelCount == 0); + Require(sceFontGetPixelResolution(coarseLibrary, nullptr) == SCE_FONT_ERROR_INVALID_LIBRARY); + Require(static_cast(coarseLibrary)->lock_word == 0); Require(sceFontDestroyLibrary(&coarseLibrary) == SCE_FONT_OK && coarseLibrary == nullptr); - void* cacheBuffer = nullptr; + void* cacheBuffer = ¬ALibrary; std::uint32_t cacheSize = 0; + cacheSize = 123; Require(sceFontDettachDeviceCacheBuffer(nullptr, &cacheBuffer, &cacheSize) == SCE_FONT_ERROR_INVALID_LIBRARY); + Require(cacheBuffer == nullptr && cacheSize == 0); Require(sceFontDettachDeviceCacheBuffer(¬ALibrary, &cacheBuffer, &cacheSize) == SCE_FONT_ERROR_INVALID_LIBRARY); Require(sceFontClearDeviceCache(library) == SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER); Require(sceFontDettachDeviceCacheBuffer(library, &cacheBuffer, &cacheSize) == SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER); @@ -247,13 +368,15 @@ int main() { Require(sceFontAttachDeviceCacheBuffer(library, callerCache.data(), static_cast(callerCache.size())) == SCE_FONT_ERROR_ALREADY_ATTACHED); Require(sceFontClearDeviceCache(library) == SCE_FONT_OK); Require(sceFontDettachDeviceCacheBuffer(library, &cacheBuffer, &cacheSize) == SCE_FONT_OK && cacheBuffer == callerCache.data() && cacheSize == callerCache.size()); + for (std::size_t offset = 4; offset < 24; ++offset) Require(callerCache[offset] == 0); Require(sceFontDettachDeviceCacheBuffer(library, &cacheBuffer, &cacheSize) == SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER); Require(sceFontAttachDeviceCacheBuffer(library, nullptr, 0x2000) == SCE_FONT_OK); cacheBuffer = nullptr; cacheSize = 0; - Require(sceFontDettachDeviceCacheBuffer(library, &cacheBuffer, &cacheSize) == SCE_FONT_OK && cacheBuffer != nullptr && cacheSize == 0x2000); - Release(nullptr, cacheBuffer); + const int allocationsBeforeDetach = allocations; + Require(sceFontDettachDeviceCacheBuffer(library, &cacheBuffer, &cacheSize) == SCE_FONT_OK && cacheBuffer == nullptr && cacheSize == 0); + Require(allocations == allocationsBeforeDetach - 1); Require(sceFontDettachDeviceCacheBuffer(library, &cacheBuffer, &cacheSize) == SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER); Require(sceFontAttachDeviceCacheBuffer(library, nullptr, 0x2000) == SCE_FONT_OK); @@ -277,18 +400,104 @@ int main() { Require(sceFontOpenFontMemory(library, notAFont, sizeof(notAFont), nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FORMAT && font == nullptr); const std::vector fontData = EmptyGlyphFont(); + const auto fileFont = fontRoot / std::filesystem::path(u8"external-a\u00e7\u00e3o-\u65e5\u672c") / "font.ttf"; + std::filesystem::create_directories(fileFont.parent_path()); + { + std::ofstream file(fileFont, std::ios::binary); + file.write(reinterpret_cast(fontData.data()), static_cast(fontData.size())); + Require(static_cast(file)); + } + const std::u8string fontRootText = fontRoot.u8string(); + AddPathAlias_nid_no_patch("/font-test", reinterpret_cast(fontRootText.c_str())); + const std::u8string fileFontText = u8"/font-test/external-a\u00e7\u00e3o-\u65e5\u672c/font.ttf"; + FontHandle fileHandle = nullptr; + Require(sceFontOpenFontFile(library, reinterpret_cast(fileFontText.c_str()), 1, nullptr, &fileHandle) == SCE_FONT_OK && fileHandle != nullptr); + Require(sceFontCloseFont(fileHandle) == SCE_FONT_OK); + RemovePathAlias_nid_no_patch("/font-test"); Require(sceFontOpenFontMemory(library, fontData.data(), static_cast(fontData.size()), nullptr, &font) == SCE_FONT_OK && font != nullptr); int setting = -1; - Require(sceFontGetScriptLanguage(font, 3, &setting) == SCE_FONT_OK && setting == 0); - Require(sceFontSetScriptLanguage(font, 3, 7) == SCE_FONT_OK && sceFontSetScriptLanguage(font, 4, 9) == SCE_FONT_OK); - Require(sceFontGetScriptLanguage(font, 3, &setting) == SCE_FONT_OK && setting == 7); - Require(sceFontGetScriptLanguage(font, 4, &setting) == SCE_FONT_OK && setting == 9); - Require(sceFontGetScriptLanguage(font, 3, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGetScriptLanguage(font, 3, &setting) == SCE_FONT_ERROR_INVALID_PARAMETER && setting == 0); + Require(sceFontSetScriptLanguage(font, 3, 7) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontSetScriptLanguage(font, 0x100, 0x106) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontSetScriptLanguage(font, 0x600, 0x603) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontSetScriptLanguage(font, 0x3000, 0x3015) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGetScriptLanguage(font, 0x100, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); Require(sceFontSetScriptLanguage(nullptr, 3, 7) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); - Require(sceFontSetTypographicDesign(font, 1, 2) == SCE_FONT_OK); - Require(sceFontGetTypographicDesign(font, 1, &setting) == SCE_FONT_OK && setting == 2); - Require(sceFontGetTypographicDesign(font, 5, &setting) == SCE_FONT_OK && setting == 0); - Require(sceFontGetTypographicDesign(nullptr, 1, &setting) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontGetScriptLanguage(nullptr, 3, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + for (const auto script : {0x100, 0x600, 0x3000}) { + bool setThrew = false; + bool getThrew = false; + try { + sceFontSetScriptLanguage(font, script, script); + + } catch (const std::logic_error&) { + setThrew = true; + } + try { + sceFontGetScriptLanguage(font, script, &setting); + + } catch (const std::logic_error&) { + getThrew = true; + } + Require(setThrew && getThrew && setting == 0); + } + Require(sceFontSetTypographicDesign(font, 1, 2) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontSetTypographicDesign(font, 17, 1) == SCE_FONT_ERROR_INVALID_PARAMETER); + for (const auto selector : {-1, 0, 2, 5, 16, 18, 65536, 65540}) { + Require(sceFontSetTypographicDesign(font, selector, 0) == SCE_FONT_ERROR_INVALID_PARAMETER); + setting = 123; + Require(sceFontGetTypographicDesign(font, selector, &setting) == SCE_FONT_ERROR_INVALID_PARAMETER && setting == 0); + } + Require(sceFontGetTypographicDesign(nullptr, 1, &setting) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && setting == 0); + Require(sceFontGetTypographicDesign(nullptr, 1, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontSetTypographicDesign(nullptr, 1, 2) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontSetTypographicDesign(font, 65537, 0) == SCE_FONT_ERROR_INVALID_PARAMETER); + for (const auto selector : {1, 17, 65537, 65538, 65539}) { + Require(sceFontGetTypographicDesign(font, selector, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + bool getThrew = false; + try { + sceFontGetTypographicDesign(font, selector, &setting); + + } catch (const std::logic_error&) { + getThrew = true; + } + Require(getThrew && setting == 0); + if (selector == 65537) continue; + for (const auto feature : {0, selector == 1 ? 1 : 32}) { + bool setThrew = false; + try { + sceFontSetTypographicDesign(font, selector, feature); + + } catch (const std::logic_error&) { + setThrew = true; + } + Require(setThrew); + } + } + std::array typographyRecord{}; + auto* nativeFont = reinterpret_cast(font); + nativeFont->open_info.fontset_record = typographyRecord.data(); + for (const auto selector : {65537, 65538, 65539}) { + Require(sceFontSetTypographicDesign(font, selector, 0) == SCE_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY); + setting = 123; + Require(sceFontGetTypographicDesign(font, selector, &setting) == SCE_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY && setting == 0); + Require(sceFontGetTypographicDesign(font, selector, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + } + const std::uint32_t typographyCount = 1; + const std::int32_t typographyFontId = 10; + std::memcpy(typographyRecord.data() + 32, &typographyCount, sizeof(typographyCount)); + std::memcpy(typographyRecord.data() + 36, &typographyFontId, sizeof(typographyFontId)); + Require(sceFontSetTypographicDesign(font, 65537, 0) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGetTypographicDesign(font, 65538, &setting) == SCE_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY); + bool typographyQueryThrew = false; + try { + sceFontGetTypographicDesign(font, 65537, &setting); + + } catch (const std::logic_error&) { + typographyQueryThrew = true; + } + Require(typographyQueryThrew && setting == 0); + nativeFont->open_info.fontset_record = nullptr; int attribute = -1; Require(sceFontGetAttribute(font, 0x11, &attribute) == SCE_FONT_OK && attribute == 0); Require(sceFontDefineAttribute(font, 0x11, &attribute) == SCE_FONT_OK && attribute == 0); @@ -311,6 +520,16 @@ int main() { Require(sceFontGetAttribute(otherFont, 0, &attribute) == SCE_FONT_OK && attribute == 7); Require(sceFontGetAttribute(font, 0, &attribute) == SCE_FONT_OK && attribute == 5); Require(sceFontCloseFont(otherFont) == SCE_FONT_OK); + auto* resolutionLibrary = static_cast(library); + const Font::SysDriver* originalDriver = resolutionLibrary->sys_driver; + Font::SysDriver wideResolutionDriver = *originalDriver; + wideResolutionDriver.scale = WideFontResolution; + resolutionLibrary->sys_driver = &wideResolutionDriver; + std::uint32_t wideResolution = 0; + float wideScale = 0; + Require(sceFontGetFontResolution(font, &wideResolution, &wideScale) == SCE_FONT_OK); + Require(wideResolution == 0x12345678 && wideScale == 12.5f); + resolutionLibrary->sys_driver = originalDriver; std::uint32_t hDpi = 1; std::uint32_t vDpi = 1; Require(sceFontGetResolutionDpi(font, &hDpi, &vDpi) == SCE_FONT_OK && hDpi == 72 && vDpi == 72); @@ -363,8 +582,16 @@ int main() { font = nullptr; Require(sceFontOpenFontMemory(library, kerningData.data(), static_cast(kerningData.size()), nullptr, &font) == SCE_FONT_OK && font != nullptr); Require(sceFontGetFontGlyphsCount(font, &glyphsCount) == SCE_FONT_OK && glyphsCount == 3); - Require(sceFontGetCharGlyphCode(font, 'A', &glyphCode) == SCE_FONT_OK && glyphCode == 1); - Require(sceFontGetCharGlyphCode(font, 'V', &glyphCode) == SCE_FONT_OK && glyphCode == 2); + Require(sceFontGetCharGlyphCode(font, 'A', &glyphCode) == SCE_FONT_OK && glyphCode == 0x80000001u); + Require(sceFontGetCharGlyphCode(font, 'V', &glyphCode) == SCE_FONT_OK && glyphCode == 0x80000002u); + Require(sceFontGetCharGlyphCode(nullptr, 0, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontGetFontGlyphsCount(nullptr, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontGetCharGlyphCode(font, 0, nullptr) == SCE_FONT_ERROR_NO_SUPPORT_CODE); + Require(sceFontGetCharGlyphCode(font, 0x80000000u, &glyphCode) == SCE_FONT_OK && glyphCode == 0x80000000u); + Require(sceFontGetCharGlyphCode(font, 0x80000002u, &glyphCode) == SCE_FONT_OK && glyphCode == 0x80000002u); + Require(sceFontGetCharGlyphCode(font, 0xc0000001u, &glyphCode) == SCE_FONT_OK && glyphCode == 0x80000001u); + Require(sceFontGetCharGlyphCode(font, 0x80000003u, &glyphCode) == SCE_FONT_ERROR_NO_SUPPORT_GLYPH && glyphCode == 0); + Require(sceFontGetCharGlyphCode(font, 0x80010001u, &glyphCode) == SCE_FONT_ERROR_NO_SUPPORT_CODE && glyphCode == 0); glyphCode = 1; Require(sceFontGetCharGlyphCode(font, 'B', &glyphCode) == SCE_FONT_ERROR_NO_SUPPORT_GLYPH && glyphCode == 0); glyphCode = 1; @@ -373,30 +600,31 @@ int main() { Require(sceFontSetScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK); FontGlyph glyph = nullptr; Require(sceFontGenerateCharGlyph(font, 'A', nullptr, &glyph) == SCE_FONT_OK && glyph != nullptr); - int glyphAttribute = -1; - Require(sceFontGlyphDefineAttribute(glyph, 0x11, &glyphAttribute) == SCE_FONT_OK && glyphAttribute == 0); - Require(sceFontGlyphGetAttribute(glyph, 0x11, &glyphAttribute) == SCE_FONT_OK && glyphAttribute == 0x11); - glyphAttribute = -1; - Require(sceFontGlyphDefineAttribute(glyph, 0, &glyphAttribute) == SCE_FONT_OK && glyphAttribute == 0x11); - Require(sceFontGlyphDefineAttribute(glyph, 5, nullptr) == SCE_FONT_OK); - glyphAttribute = -1; - Require(sceFontGlyphGetAttribute(glyph, 0, &glyphAttribute) == SCE_FONT_OK && glyphAttribute == 5); - Require(sceFontGlyphGetAttribute(glyph, 0x11, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); - glyphAttribute = -1; - Require(sceFontGlyphDefineAttribute(nullptr, 0x11, &glyphAttribute) == SCE_FONT_ERROR_INVALID_GLYPH && glyphAttribute == -1); - Require(sceFontGlyphGetAttribute(nullptr, 0x11, &glyphAttribute) == SCE_FONT_ERROR_INVALID_GLYPH && glyphAttribute == -1); + const auto originalFlags = glyph->flags; + std::int32_t previousAttribute = -1; + Require(sceFontGlyphDefineAttribute(glyph, 0x11, &previousAttribute) == SCE_FONT_OK); + Require(previousAttribute == ((originalFlags & 2) ? 0x11 : 0x10)); + Require(glyph->flags == (originalFlags | 2)); + Require(sceFontGlyphDefineAttribute(glyph, 0x10, &previousAttribute) == SCE_FONT_OK && previousAttribute == 0x11); + Require(glyph->flags == (originalFlags & ~2u)); + Require(sceFontGlyphDefineAttribute(glyph, 0x10, &previousAttribute) == SCE_FONT_OK && previousAttribute == 0x10); + Require(sceFontGlyphDefineAttribute(glyph, 0x11, nullptr) == SCE_FONT_OK); + for (std::int32_t attribute = -1; attribute <= 256; ++attribute) { + if (attribute == 0x10 || attribute == 0x11) continue; + previousAttribute = -1; + Require(sceFontGlyphDefineAttribute(glyph, attribute, &previousAttribute) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(previousAttribute == 0 && glyph->flags == (originalFlags | 2)); + } + previousAttribute = -1; + Require(sceFontGlyphDefineAttribute(nullptr, 0x11, &previousAttribute) == SCE_FONT_ERROR_INVALID_GLYPH && previousAttribute == 0); + Require(sceFontGlyphDefineAttribute(nullptr, 0x11, nullptr) == SCE_FONT_ERROR_INVALID_GLYPH); FontGlyphOpaque notAGlyph{}; - Require(sceFontGlyphDefineAttribute(¬AGlyph, 0x11, &glyphAttribute) == SCE_FONT_ERROR_INVALID_GLYPH); - Require(sceFontGlyphGetAttribute(¬AGlyph, 0x11, &glyphAttribute) == SCE_FONT_ERROR_INVALID_GLYPH); - FontGlyph otherGlyph = nullptr; - Require(sceFontGenerateCharGlyph(font, 'V', nullptr, &otherGlyph) == SCE_FONT_OK && otherGlyph != nullptr); - glyphAttribute = -1; - Require(sceFontGlyphGetAttribute(otherGlyph, 0, &glyphAttribute) == SCE_FONT_OK && glyphAttribute == 0); - Require(sceFontGlyphDefineAttribute(otherGlyph, 9, &glyphAttribute) == SCE_FONT_OK && glyphAttribute == 0); - glyphAttribute = -1; - Require(sceFontGlyphGetAttribute(otherGlyph, 0, &glyphAttribute) == SCE_FONT_OK && glyphAttribute == 9); - Require(sceFontGlyphGetAttribute(glyph, 0, &glyphAttribute) == SCE_FONT_OK && glyphAttribute == 5); - Require(sceFontDeleteGlyph(&memory, &otherGlyph) == SCE_FONT_OK && otherGlyph == nullptr); + previousAttribute = -1; + Require(sceFontGlyphDefineAttribute(¬AGlyph, 0x11, &previousAttribute) == SCE_FONT_ERROR_INVALID_GLYPH && previousAttribute == 0); + int glyphAttribute = -1; + Require(sceFontGlyphGetAttribute(glyph, 0x11, &glyphAttribute) == SCE_FONT_OK && glyphAttribute == 0x11); + Require(sceFontGlyphGetAttribute(glyph, 0x11, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGlyphGetAttribute(nullptr, 0x11, &glyphAttribute) == SCE_FONT_ERROR_INVALID_GLYPH); Require(sceFontDeleteGlyph(&memory, &glyph) == SCE_FONT_OK && glyph == nullptr); FontKerning kerning{1.0f, 1.0f, 1.0f, 1.0f}; Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -20.0f)); @@ -421,6 +649,8 @@ int main() { Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_ERROR_NOT_BOUND_RENDERER); + CheckPointAndDpiScale(font, library, &memory, renderer); + ParsedText parsed{"AV", 0, font}; FontTextSource source{}; Require(sceFontTextSourceInit(&source, parsed.text, 2, ParseText, &parsed) == SCE_FONT_OK); @@ -441,11 +671,31 @@ int main() { Require(step != nullptr); Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == characters && out.letter != nullptr && out.guard == guard); Require(out.letter->x == step->x && out.letter->advanceX == step->advanceX && out.letter->Components.textsIndex == 0 && out.letter->Components.characterTextCount == 1); + const FontWritingLetterStep* retainedLetter = out.letter; + const FontWritingLetterStep retainedValue = *retainedLetter; + Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == nullptr && out.letter == nullptr && out.guard == guard); + Require(std::memcmp(retainedLetter, &retainedValue, sizeof(retainedValue)) == 0); + Require(sceFontWritingRefersRenderStepCharacter(&writing, nullptr) == nullptr); step = sceFontWritingRefersRenderStep(&writing); Require(step != nullptr); Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == characters + 1 && out.guard == guard); Require(out.letter->x == step->x && out.letter->Components.textsIndex == 1 && out.letter->Components.glyphsIndex == 1); - Require(sceFontWritingRefersRenderStepCharacter(&writing, nullptr) == characters + 1); + Require(out.letter == retainedLetter); + Require(sceFontWritingRefersRenderStepCharacter(&writing, nullptr) == nullptr); + out.letter = retainedLetter; + Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == nullptr && out.letter == nullptr && out.guard == guard); + Require(sceFontWritingRefersRenderStep(&writing) == nullptr); + Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == nullptr && out.letter == nullptr); + Require(sceFontWritingInit(&writing, string, characters) == SCE_FONT_OK); + Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == nullptr && out.letter == nullptr); + Require(sceFontWritingRefersRenderStep(&writing) != nullptr); + Require(sceFontWritingRefersRenderStepCharacter(&writing, nullptr) == characters); + Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == nullptr && out.letter == nullptr); + Require(sceFontWritingInit(&writing, string, characters) == SCE_FONT_OK); + Require(sceFontWritingRefersRenderStep(&writing) != nullptr); + Require(sceFontWritingRefersRenderStep(&writing) != nullptr); + 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); CheckTextCodes(&memory, font); Require(sceFontCloseFont(font) == SCE_FONT_OK); diff --git a/core/libs/tests/GuestFontGraphics.cpp b/core/libs/tests/GuestFontGraphics.cpp new file mode 100644 index 0000000000..3093955bc9 --- /dev/null +++ b/core/libs/tests/GuestFontGraphics.cpp @@ -0,0 +1,214 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceFont/libSceFont.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int InvalidParameter = static_cast(0x80460002); +constexpr int InvalidFill = static_cast(0x80460086); +constexpr int InvalidRegion = static_cast(0x80460087); +constexpr int InvalidFillPlot = static_cast(0x80460088); +constexpr int InvalidFillRates = static_cast(0x80460089); +constexpr float NaN = std::numeric_limits::quiet_NaN(); + +int g_lastError = 0; +int g_errorCount = 0; + +void APS5_VABI RecordError(int error) { + g_lastError = error; + ++g_errorCount; +} + +void Require(bool value) { if (!value) std::abort(); } + +template +void Fill(T& object, std::uint8_t value) { + std::memset(&object, value, sizeof(object)); +} + +template +bool AllBytes(const T& object, std::size_t from, std::size_t to, std::uint8_t value) { + const auto* bytes = reinterpret_cast(&object); + for (std::size_t i = from; i < to; ++i) if (bytes[i] != value) return false; + return true; +} + +int g_expectedErrors = 0; + +void RequireError(int result, int error) { + Require(result == error); + Require(g_lastError == error); + g_lastError = 0; + ++g_expectedErrors; +} + +const FontGraphicsFill* Sentinel(std::intptr_t value) { + return reinterpret_cast(value); +} + +void Regions() { + FontGraphicsRegion region; + Fill(region, 0xa5); + Require(sceFontGraphicsRegionInit(®ion, 1.0f, 2.0f, 3.0f, 4.0f) == 0); + Require(region.x == 1.0f && region.y == 2.0f && region.width == 3.0f && region.height == 4.0f); + Require(region.flags == 0xf && region.tag == 0xf60 && region.outerRadius == 0.0f && region.innerRadius == 0.0f); + Require(sceFontGraphicsRegionInit(®ion, 0.0f, 0.0f, 0.0f, 0.0f) == 0); + Require(sceFontGraphicsRegionInit(®ion, 0.0f, 0.0f, NaN, NaN) == 0); + Fill(region, 0xa5); + RequireError(sceFontGraphicsRegionInit(®ion, 0.0f, 0.0f, -1.0f, 1.0f), InvalidParameter); + RequireError(sceFontGraphicsRegionInit(®ion, 0.0f, 0.0f, 1.0f, -1.0f), InvalidParameter); + RequireError(sceFontGraphicsRegionInit(nullptr, 0.0f, 0.0f, 1.0f, 1.0f), InvalidParameter); + Require(AllBytes(region, 0, sizeof(region), 0xa5)); + + Require(sceFontGraphicsRegionInitCircular(®ion, 10.0f, 20.0f, 4.0f, 1.0f) == 0); + Require(region.x == 6.0f && region.y == 16.0f && region.width == 8.0f && region.height == 8.0f); + Require(region.flags == 0xff && region.tag == 0xf60 && region.outerRadius == 4.0f && region.innerRadius == 1.0f); + Require(sceFontGraphicsRegionInitCircular(®ion, 0.0f, 0.0f, 2.0f, 2.0f) == 0); + RequireError(sceFontGraphicsRegionInitCircular(®ion, 0.0f, 0.0f, 1.0f, 2.0f), InvalidParameter); + RequireError(sceFontGraphicsRegionInitCircular(®ion, 0.0f, 0.0f, 0.0f, 0.0f), InvalidParameter); + RequireError(sceFontGraphicsRegionInitCircular(®ion, 0.0f, 0.0f, 1.0f, 0.0f), InvalidParameter); + RequireError(sceFontGraphicsRegionInitCircular(nullptr, 0.0f, 0.0f, 2.0f, 1.0f), InvalidParameter); + + Require(sceFontGraphicsRegionInitRoundish(®ion, 1.0f, 2.0f, 10.0f, 8.0f, 4.0f, 2.0f, 0x5) == 0); + Require(region.width == 10.0f && region.height == 8.0f && region.flags == 0x5 && region.tag == 0xf60); + Require(region.outerRadius == 4.0f && region.innerRadius == 2.0f); + Require(sceFontGraphicsRegionInitRoundish(®ion, 0.0f, 0.0f, 10.0f, 8.0f, 0.0f, 9.0f, 0xff) == 0); + RequireError(sceFontGraphicsRegionInitRoundish(®ion, 0.0f, 0.0f, 10.0f, 8.0f, 4.5f, 0.0f, 0), InvalidParameter); + RequireError(sceFontGraphicsRegionInitRoundish(®ion, 0.0f, 0.0f, 10.0f, 8.0f, 2.0f, 3.0f, 0), InvalidParameter); + RequireError(sceFontGraphicsRegionInitRoundish(®ion, 0.0f, 0.0f, 10.0f, 8.0f, 2.0f, 1.0f, 0x100), InvalidParameter); + RequireError(sceFontGraphicsRegionInitRoundish(®ion, 0.0f, 0.0f, 0.0f, 8.0f, 0.0f, 0.0f, 0), InvalidParameter); + RequireError(sceFontGraphicsRegionInitRoundish(®ion, 0.0f, 0.0f, 10.0f, 8.0f, -1.0f, 0.0f, 0), InvalidParameter); +} + +void FillMethods() { + FontGraphicsFillMethod method; + Fill(method, 0xa5); + Require(sceFontGraphicsFillMethodInit(&method, 0x50, 12) == 0); + Require(method.mode == 0x50 && method.type == 12 && method.flags == 0xf && method.tag == 0xf01b); + Require(AllBytes(method, 16, 32, 0)); + for (std::int32_t mode : {0x00, 0x10, 0x20, 0x30, 0x40}) Require(sceFontGraphicsFillMethodInit(&method, mode, 1) == 0); + for (std::int32_t type = 1; type <= 7; ++type) Require(sceFontGraphicsFillMethodInit(&method, 0, type) == 0); + for (std::int32_t mode : {0x01, 0x08, 0x60, -0x10}) RequireError(sceFontGraphicsFillMethodInit(&method, mode, 1), InvalidParameter); + for (std::int32_t type : {0, 8, 11, 13, -1}) RequireError(sceFontGraphicsFillMethodInit(&method, 0, type), InvalidParameter); + RequireError(sceFontGraphicsFillMethodInit(nullptr, 0, 1), InvalidParameter); +} + +void FillPlots() { + FontGraphicsRegion region; + Require(sceFontGraphicsRegionInitRoundish(®ion, 1.0f, 2.0f, 10.0f, 8.0f, 4.0f, 2.0f, 0x5) == 0); + FontGraphicsFillPlot plot; + Fill(plot, 0xa5); + Require(sceFontGraphicsFillPlotInit(&plot, ®ion) == 0); + Require(plot.mapping[0] == 1.0f && plot.mapping[1] == 2.0f && plot.mapping[2] == 11.0f && plot.mapping[3] == 10.0f); + Require(plot.region == ®ion && plot.tag == 0xf01c && plot.reserved26 == 0 && plot.layout == 0xf77 && plot.flags == 1); + Require(AllBytes(plot, 31, 64, 0)); + + Fill(plot, 0xa5); + Require(sceFontGraphicsFillPlotInit(&plot, nullptr) == 0); + Require(plot.region == nullptr && plot.tag == 0xf01c && plot.layout == 0 && plot.flags == 2); + Require(AllBytes(plot, 0, 16, 0)); + const auto* whole = reinterpret_cast(std::intptr_t{-1}); + Require(sceFontGraphicsFillPlotInit(&plot, whole) == 0); + Require(plot.region == whole && plot.layout == 0xf77 && plot.flags == 6); + + FontGraphicsRegion bad = region; + bad.tag = 0; + Fill(plot, 0xa5); + RequireError(sceFontGraphicsFillPlotInit(&plot, &bad), InvalidRegion); + Require(plot.tag == 0 && AllBytes(plot, 0, 24, 0xa5) && AllBytes(plot, 26, 64, 0xa5)); + bad = region; + bad.outerRadius = NaN; + RequireError(sceFontGraphicsFillPlotInit(&plot, &bad), InvalidRegion); + bad = region; + bad.outerRadius = 4.5f; + RequireError(sceFontGraphicsFillPlotInit(&plot, &bad), InvalidRegion); + bad = region; + bad.outerRadius = 0.0f; + bad.innerRadius = 4.0f; + Require(sceFontGraphicsFillPlotInit(&plot, &bad) == 0); + bad.innerRadius = 4.5f; + RequireError(sceFontGraphicsFillPlotInit(&plot, &bad), InvalidRegion); + RequireError(sceFontGraphicsFillPlotInit(nullptr, ®ion), InvalidFillPlot); + + Require(sceFontGraphicsFillPlotInit(&plot, ®ion) == 0); + Require(sceFontGraphicsFillPlotSetLayout(&plot, 0x400, 0x24) == 0); + Require(plot.layout == 0x424 && plot.flags == 9); + Require(sceFontGraphicsFillPlotSetLayout(&plot, 0, 0) == 0); + Require(plot.layout == 0 && plot.flags == 8); + Require(sceFontGraphicsFillPlotSetLayout(&plot, 0x170, 0x1100) == 0); + Require(plot.layout == 0x1170); + RequireError(sceFontGraphicsFillPlotSetLayout(&plot, 0x171, 0), InvalidParameter); + RequireError(sceFontGraphicsFillPlotSetLayout(&plot, 0, 0x3), InvalidParameter); + RequireError(sceFontGraphicsFillPlotSetLayout(&plot, 0, 0x50), InvalidParameter); + Require(sceFontGraphicsFillPlotSetMapping(&plot, 5.0f, 6.0f, 7.0f, NaN) == 0); + Require(plot.mapping[0] == 5.0f && plot.mapping[2] == 7.0f && std::isnan(plot.mapping[3]) && plot.flags == 9); + + FontGraphicsFillPlot untagged{}; + RequireError(sceFontGraphicsFillPlotSetLayout(&untagged, 0, 0), InvalidFillPlot); + RequireError(sceFontGraphicsFillPlotSetMapping(&untagged, 0.0f, 0.0f, 0.0f, 0.0f), InvalidFillPlot); +} + +void FillRates() { + FontGraphicsFillRates rates; + Fill(rates, 0xa5); + std::array fill{0x30}; + const auto* fillPointer = reinterpret_cast(fill.data()); + Require(sceFontGraphicsFillRatesInit(&rates, fillPointer, 9) == 0); + Require(rates.fill == fillPointer && rates.tag == 0xf01a && rates.effect == 9 && rates.layout == 0xf77 && rates.flags == 2); + Require(AllBytes(rates, 0, 16, 0) && AllBytes(rates, 31, 64, 0)); + for (std::intptr_t value : {-1, 0, 1, 2}) Require(sceFontGraphicsFillRatesInit(&rates, Sentinel(value), 0) == 0); + for (std::uint32_t effect : {0u, 1u, 2u, 3u, 4u, 5u, 8u, 9u, 16u}) { + Require(sceFontGraphicsFillRatesInit(&rates, nullptr, effect) == 0); + Require(rates.effect == effect); + } + + Fill(rates, 0xa5); + RequireError(sceFontGraphicsFillRatesInit(&rates, nullptr, 6), InvalidParameter); + Require(AllBytes(rates, 0, sizeof(rates), 0)); + fill[0] = 0x31; + RequireError(sceFontGraphicsFillRatesInit(&rates, fillPointer, 0), InvalidFill); + fill[0] = 0x60; + RequireError(sceFontGraphicsFillRatesInit(&rates, fillPointer, 0), InvalidFill); + RequireError(sceFontGraphicsFillRatesInit(nullptr, nullptr, 0), InvalidParameter); + + fill[0] = 0x10; + Require(sceFontGraphicsFillRatesInit(&rates, nullptr, 0) == 0); + Require(sceFontGraphicsFillRatesSetFillEffect(&rates, fillPointer, 16) == 0); + Require(rates.fill == fillPointer && rates.effect == 16 && rates.flags == 2 && rates.layout == 0xf77); + RequireError(sceFontGraphicsFillRatesSetFillEffect(&rates, nullptr, 17), InvalidParameter); + Require(rates.fill == fillPointer && rates.effect == 16); + + Require(sceFontGraphicsFillRatesSetLayout(&rates, 0xf77, 0x44) == 0); + Require(rates.layout == 0 && rates.flags == 3); + Require(sceFontGraphicsFillRatesSetLayout(&rates, 0, 0) == 0); + Require(rates.layout == 0 && rates.flags == 2); + RequireError(sceFontGraphicsFillRatesSetLayout(&rates, 0x801, 0), InvalidParameter); + Require(sceFontGraphicsFillRatesSetMapping(&rates, 1.0f, 2.0f, 3.0f, 4.0f) == 0); + Require(rates.mapping[0] == 1.0f && rates.mapping[3] == 4.0f && rates.flags == 1); + + FontGraphicsFillRates untagged{}; + RequireError(sceFontGraphicsFillRatesSetFillEffect(&untagged, nullptr, 0), InvalidFillRates); + RequireError(sceFontGraphicsFillRatesSetLayout(&untagged, 0, 0), InvalidFillRates); + RequireError(sceFontGraphicsFillRatesSetMapping(&untagged, 0.0f, 0.0f, 0.0f, 0.0f), InvalidFillRates); +} + +} + +int main() { + sceFontSetGraphicsErrorCallback(RecordError); + Regions(); + FillMethods(); + FillPlots(); + FillRates(); + Require(g_errorCount == g_expectedErrors); + sceFontSetGraphicsErrorCallback(nullptr); + const int before = g_errorCount; + FontGraphicsRegion region; + Require(sceFontGraphicsRegionInit(®ion, 0.0f, 0.0f, -1.0f, 0.0f) == InvalidParameter); + Require(g_errorCount == before); +} diff --git a/core/libs/tests/GuestFontMetrics.cpp b/core/libs/tests/GuestFontMetrics.cpp new file mode 100644 index 0000000000..24d5f2b1ca --- /dev/null +++ b/core/libs/tests/GuestFontMetrics.cpp @@ -0,0 +1,137 @@ +#include "prx/libSceFont/include/FontDriver.hpp" +#include "prx/libSceFont/libSceFont.hpp" +#include "prx/libSceFontFt/libSceFontFt.hpp" +#include "tests/FontFixture.hpp" +#include +#include +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Font metrics check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +struct Expected { + std::uint32_t form; + float ascender; + float descender; + float lineGap; + float maxAdvance; + float verticalDescender; + float verticalAscender; +}; + +static FontMetrics Metrics(FontHandle font, std::uint32_t form) { + FontMetrics metrics{}; + std::memset(&metrics, 0x7F, sizeof(metrics)); + Require(sceFontGetFontMetrics(font, form, &metrics) == SCE_FONT_OK); + return metrics; +} + +static void RequireMetrics(FontHandle font, std::uint32_t form, const Expected& expected) { + const FontMetrics metrics = Metrics(font, form); + Require(metrics.form == expected.form); + Require(metrics.ascender == expected.ascender && metrics.descender == expected.descender && metrics.lineGap == expected.lineGap); + Require(metrics.maxAdvance == expected.maxAdvance); + Require(metrics.verticalDescender == expected.verticalDescender && metrics.verticalAscender == expected.verticalAscender); + Require(metrics.xMin == 0.0f && metrics.yMin == 0.0f && metrics.xMax == 0.0f && metrics.yMax == 0.0f); + Require(metrics.emWidth == 99.984375f && metrics.emHeight == 99.984375f); +} + +static void* APS5_VABI Allocate(void*, std::uint32_t size) { + return std::malloc(size); +} + +static void APS5_VABI Release(void*, void* pointer) { + std::free(pointer); +} + +using FontFixture::BuildFont; +using FontFixture::Words; + +static std::vector Os2Table() { + return Words({0, 500, 400, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x20, 0x7E, 700, -300, 100, 900, 250}); +} + +static FontHandle Open(FontLibrary library, const std::vector& data) { + FontHandle font = nullptr; + Require(sceFontOpenFontMemory(library, data.data(), static_cast(data.size()), nullptr, &font) == SCE_FONT_OK && font != nullptr); + Require(sceFontSetScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK); + return font; +} + +static std::vector VerticalHeader(int major) { + return Words({major, 0, 880, -120, 60, 1000, 40, 0, 950, 0, 0, 0, 0, 0, 0, 0, 0, 1}); +} + +int main() { + 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, 4, 0x52) == SCE_FONT_OK); + + const auto plainData = BuildFont(1, {}); + FontHandle plain = Open(library, plainData); + RequireMetrics(plain, 1, {1, 80.0f, -20.0f, 0.0f, 50.0f, 0.0f, 0.0f}); + RequireMetrics(plain, 2, {1, 80.0f, -20.0f, 0.0f, 50.0f, 0.0f, 0.0f}); + RequireMetrics(plain, 3, {1, 80.0f, -20.0f, 0.0f, 50.0f, 0.0f, 0.0f}); + RequireMetrics(plain, 4, {4, 0.0f, 100.0f, 0.0f, 100.0f, -50.0f, 80.0f}); + FontMetrics untouched{}; + untouched.ascender = 5.0f; + untouched.descender = 6.0f; + untouched.lineGap = 7.0f; + Require(sceFontGetFontMetrics(plain, 7, &untouched) == SCE_FONT_OK); + Require(untouched.form == 7 && untouched.ascender == 5.0f && untouched.descender == 6.0f && untouched.lineGap == 7.0f); + Require(untouched.maxAdvance == 50.0f && untouched.verticalDescender == 0.0f && untouched.verticalAscender == 0.0f); + + const auto typoData = BuildFont(1, {{"OS/2", Os2Table()}, {"vhea", VerticalHeader(0x11)}, {"vmtx", Words({1000, 40})}}); + FontHandle typo = Open(library, typoData); + RequireMetrics(typo, 1, {1, 80.0f, -20.0f, 0.0f, 50.0f, 0.0f, 0.0f}); + RequireMetrics(typo, 2, {2, 70.0f, -30.0f, 10.0f, 50.0f, 0.0f, 0.0f}); + RequireMetrics(typo, 3, {3, 90.0f, -25.0f, 0.0f, 50.0f, 0.0f, 0.0f}); + RequireMetrics(typo, 4, {4, 4.0f, 95.0f, -93.984375f, 100.0f, -12.0f, 70.0f}); + Require(sceFontSetEffectWeight(typo, 1.03125f, 0.984375f, 0) == SCE_FONT_OK); + Require(sceFontSetEffectSlant(typo, 0.25f) == SCE_FONT_OK); + const FontMetrics styled = Metrics(typo, 2); + Require(styled.effectWeightX == 0.03125f && styled.effectWeightY == -0.015625f && styled.slantRatio == 0.25f); + Require(sceFontSetEffectWeight(typo, 1.5f, 0.5f, 0) == SCE_FONT_OK); + const FontMetrics clamped = Metrics(typo, 2); + Require(clamped.effectWeightX == 0.04f && clamped.effectWeightY == -0.04f); + + const auto versionOneData = BuildFont(1, {{"vhea", VerticalHeader(1)}, {"vmtx", Words({1000, 40})}}); + FontHandle versionOne = Open(library, versionOneData); + RequireMetrics(versionOne, 4, {4, 4.0f, 95.0f, 0.0f, 100.0f, -12.0f, 4.0f}); + + const auto os2OnlyData = BuildFont(1, {{"OS/2", Os2Table()}}); + FontHandle os2Only = Open(library, os2OnlyData); + RequireMetrics(os2Only, 4, {4, 70.0f, 70.0f, 0.0f, 100.0f, -50.0f, 70.0f}); + + FontMetrics cleared{}; + std::memset(&cleared, 0x7F, sizeof(cleared)); + Require(sceFontGetFontMetrics(nullptr, 1, &cleared) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(cleared.form == 0 && cleared.ascender == 0.0f && cleared.maxAdvance == 0.0f && cleared.emHeight == 0.0f); + Require(cleared.effectWeightX == 1.0f && cleared.effectWeightY == 1.0f && cleared.slantRatio == 0.0f); + std::uint64_t notAFont[32]{}; + std::memset(&cleared, 0x7F, sizeof(cleared)); + Require(sceFontGetFontMetrics(reinterpret_cast(notAFont), 1, &cleared) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + 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); + + for (FontHandle font : {plain, typo, versionOne, os2Only}) Require(sceFontCloseFont(font) == SCE_FONT_OK); + Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); + std::puts("Font metrics tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestFontOutlineProfile.cpp b/core/libs/tests/GuestFontOutlineProfile.cpp new file mode 100644 index 0000000000..1c57782d45 --- /dev/null +++ b/core/libs/tests/GuestFontOutlineProfile.cpp @@ -0,0 +1,121 @@ +#include "prx/libSceFont/include/FontDriver.hpp" +#include "prx/libSceFont/libSceFont.hpp" +#include "prx/libSceFontFt/libSceFontFt.hpp" +#include "tests/FontFixture.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Font outline profile check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static void* APS5_VABI Allocate(void*, std::uint32_t size) { + return std::malloc(size); +} + +static void APS5_VABI Release(void*, void* pointer) { + std::free(pointer); +} + +static void PatchMaxp(std::vector& font, std::initializer_list words) { + const int tableCount = (font[4] << 8) | font[5]; + for (int i = 0; i < tableCount; ++i) { + const std::size_t entry = 12 + 16 * static_cast(i); + if (std::memcmp(font.data() + entry, "maxp", 4) != 0) continue; + std::size_t offset = (static_cast(font[entry + 8]) << 24) | (static_cast(font[entry + 9]) << 16) | + (static_cast(font[entry + 10]) << 8) | font[entry + 11]; + for (const int word : words) { + if (word >= 0) { + font[offset] = static_cast((word >> 8) & 0xFF); + font[offset + 1] = static_cast(word & 0xFF); + } + offset += 2; + } + return; + } + Require(false); +} + +static FontHandle Open(FontLibrary library, const std::vector& data) { + FontHandle font = nullptr; + Require(sceFontOpenFontMemory(library, data.data(), static_cast(data.size()), nullptr, &font) == SCE_FONT_OK && font != nullptr); + return font; +} + +static void Fill(FontOutlineProfile& profile) { + std::memset(&profile, 0x7F, sizeof(profile)); +} + +static bool Zero(const FontOutlineProfile& profile) { + return profile.maxContours == 0 && profile.maxPoints == 0 && profile.maxCompositeContours == 0 && profile.maxCompositePoints == 0; +} + +int main() { + 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, 4, 0x52) == SCE_FONT_OK); + + auto profiledData = FontFixture::BuildFont(1, {}); + PatchMaxp(profiledData, {-1, -1, -1, 120, 7, 300, 11}); + FontHandle profiled = Open(library, profiledData); + FontOutlineProfile profile{}; + Fill(profile); + Require(sceFontGetFontGlyphsOutlineProfile(profiled, &profile) == SCE_FONT_OK); + Require(profile.maxContours == 7 && profile.maxPoints == 120 && profile.maxCompositeContours == 11 && profile.maxCompositePoints == 300); + + const auto plainData = FontFixture::BuildFont(1, {}); + FontHandle plain = Open(library, plainData); + Fill(profile); + Require(sceFontGetFontGlyphsOutlineProfile(plain, &profile) == SCE_FONT_OK && Zero(profile)); + + auto shortData = FontFixture::BuildFont(1, {}); + PatchMaxp(shortData, {0, 0x5000}); + FontHandle shortMaxp = Open(library, shortData); + Fill(profile); + Require(sceFontGetFontGlyphsOutlineProfile(shortMaxp, &profile) == SCE_FONT_ERROR_NO_SUPPORT_FUNCTION && Zero(profile)); + + std::array record{}; + auto* nativeFont = reinterpret_cast(profiled); + nativeFont->open_info.fontset_record = record.data(); + Require(sceFontGetFontGlyphsOutlineProfile(profiled, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + nativeFont->open_info.fontset_record = nullptr; + Fill(profile); + Require(sceFontGetFontGlyphsOutlineProfile(profiled, &profile) == SCE_FONT_OK && profile.maxPoints == 120); + + Fill(profile); + Require(sceFontGetFontGlyphsOutlineProfile(nullptr, &profile) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && Zero(profile)); + std::uint64_t notAFont[32]{}; + Fill(profile); + Require(sceFontGetFontGlyphsOutlineProfile(reinterpret_cast(notAFont), &profile) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && Zero(profile)); + Require(sceFontGetFontGlyphsOutlineProfile(plain, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGetFontGlyphsOutlineProfile(nullptr, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontGetFontGlyphsOutlineProfile(reinterpret_cast(notAFont), nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + auto* plainFont = reinterpret_cast(plain); + const FontLibrary owner = plainFont->library; + plainFont->library = nullptr; + Fill(profile); + Require(sceFontGetFontGlyphsOutlineProfile(plain, &profile) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && Zero(profile)); + Require(sceFontGetFontGlyphsOutlineProfile(plain, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + plainFont->library = owner; + Fill(profile); + Require(sceFontGetFontGlyphsOutlineProfile(plain, &profile) == SCE_FONT_OK && Zero(profile)); + + for (FontHandle font : {profiled, plain, shortMaxp}) Require(sceFontCloseFont(font) == SCE_FONT_OK); + Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); + std::puts("Font outline profile tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestFontStyleInformation.cpp b/core/libs/tests/GuestFontStyleInformation.cpp new file mode 100644 index 0000000000..21443be90b --- /dev/null +++ b/core/libs/tests/GuestFontStyleInformation.cpp @@ -0,0 +1,158 @@ +#include "prx/libSceFont/include/FontDriver.hpp" +#include "prx/libSceFont/libSceFont.hpp" +#include "prx/libSceFontFt/libSceFontFt.hpp" +#include "tests/FontFixture.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Font style information check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +using FontFixture::BuildFont; +using FontFixture::Words; + +static void* APS5_VABI Allocate(void*, std::uint32_t size) { + return std::malloc(size); +} + +static void APS5_VABI Release(void*, void* pointer) { + std::free(pointer); +} + +static std::vector NameTable(std::string_view family, std::string_view style) { + const int familyBytes = static_cast(family.size() * 2); + const int styleBytes = static_cast(style.size() * 2); + std::vector table = Words({0, 2, 30, 3, 1, 0x409, 1, familyBytes, 0, 3, 1, 0x409, 2, styleBytes, familyBytes}); + for (const char c : family) FontFixture::Put16(table, c); + for (const char c : style) FontFixture::Put16(table, c); + return table; +} + +static std::vector Os2Table(int weight, int width) { + return Words({0, 500, weight, width, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x20, 0x7E, 700, -300, 100, 900, 250}); +} + +static void PatchHead(std::vector& font, int macStyle, int direction) { + const int tableCount = (font[4] << 8) | font[5]; + for (int i = 0; i < tableCount; ++i) { + const std::size_t entry = 12 + 16 * static_cast(i); + if (std::memcmp(font.data() + entry, "head", 4) != 0) continue; + const std::size_t offset = (static_cast(font[entry + 8]) << 24) | (static_cast(font[entry + 9]) << 16) | + (static_cast(font[entry + 10]) << 8) | font[entry + 11]; + font[offset + 44] = static_cast((macStyle >> 8) & 0xFF); + font[offset + 45] = static_cast(macStyle & 0xFF); + font[offset + 48] = static_cast((direction >> 8) & 0xFF); + font[offset + 49] = static_cast(direction & 0xFF); + return; + } + Require(false); +} + +static FontHandle Open(FontLibrary library, const std::vector& data) { + FontHandle font = nullptr; + Require(sceFontOpenFontMemory(library, data.data(), static_cast(data.size()), nullptr, &font) == SCE_FONT_OK && font != nullptr); + return font; +} + +static FontStyleInformation Information(FontHandle font) { + FontStyleInformation information{}; + std::memset(&information, 0x7F, sizeof(information)); + Require(sceFontGetFontStyleInformation(font, &information) == SCE_FONT_OK); + return information; +} + +static bool Cleared(const FontStyleInformation& information) { + return information.familyName && information.familyName[0] == '\0' && information.styleName && information.styleName[0] == '\0' && + information.styleFlags == 0 && information.weight == 0 && information.width == 0 && information.layoutFlags == 0; +} + +int main() { + 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, 4, 0x52) == SCE_FONT_OK); + + auto namedData = BuildFont(1, {{"name", NameTable("Tst", "Bd")}}); + PatchHead(namedData, 3, -2); + FontHandle named = Open(library, namedData); + const FontStyleInformation namedInfo = Information(named); + Require(namedInfo.familyName && std::string(namedInfo.familyName) == "Tst"); + Require(namedInfo.styleName && std::string(namedInfo.styleName) == "Bd"); + Require(namedInfo.styleFlags == 3 && namedInfo.weight == 0 && namedInfo.width == 0 && namedInfo.layoutFlags == 2); + + const auto os2Data = BuildFont(1, {{"name", NameTable("Tst", "Rg")}, {"OS/2", Os2Table(400, 5)}, + {"vhea", Words({0x11, 0, 880, -120, 60, 1000, 40, 0, 950, 0, 0, 0, 0, 0, 0, 0, 0, 1})}, {"vmtx", Words({1000, 40})}}); + FontHandle os2 = Open(library, os2Data); + const FontStyleInformation os2Info = Information(os2); + Require(os2Info.styleFlags == 0 && os2Info.weight == 400 && os2Info.width == 5 && os2Info.layoutFlags == 0x81); + + const auto oddData = BuildFont(1, {{"name", NameTable("Tst", "Rg")}, {"OS/2", Os2Table(450, 12)}}); + FontHandle odd = Open(library, oddData); + const FontStyleInformation oddInfo = Information(odd); + Require(oddInfo.weight == 0 && oddInfo.width == 0 && oddInfo.layoutFlags == 1); + + for (const int weight : {100, 900}) { + const auto data = BuildFont(1, {{"name", NameTable("Tst", "Rg")}, {"OS/2", Os2Table(weight, 9)}}); + FontHandle font = Open(library, data); + const FontStyleInformation info = Information(font); + Require(info.weight == weight && info.width == 9); + Require(sceFontCloseFont(font) == SCE_FONT_OK); + } + for (const int weight : {0, 1000}) { + const auto data = BuildFont(1, {{"name", NameTable("Tst", "Rg")}, {"OS/2", Os2Table(weight, 10)}}); + FontHandle font = Open(library, data); + const FontStyleInformation info = Information(font); + Require(info.weight == 0 && info.width == 0); + Require(sceFontCloseFont(font) == SCE_FONT_OK); + } + + static const char recordFamily[] = "Rf"; + static const char recordStyle[] = "Rs"; + std::array record{}; + record[4] = std::byte{1}; + record[5] = std::byte{7}; + record[6] = std::byte{3}; + record[7] = std::byte{0x81}; + const char* familyPointer = recordFamily; + const char* stylePointer = recordStyle; + std::memcpy(record.data() + 8, &familyPointer, sizeof(familyPointer)); + std::memcpy(record.data() + 16, &stylePointer, sizeof(stylePointer)); + auto* nativeFont = reinterpret_cast(os2); + nativeFont->open_info.fontset_record = record.data(); + const FontStyleInformation recordInfo = Information(os2); + nativeFont->open_info.fontset_record = nullptr; + Require(recordInfo.familyName == recordFamily && recordInfo.styleName == recordStyle); + Require(recordInfo.styleFlags == 1 && recordInfo.weight == 700 && recordInfo.width == 3 && recordInfo.layoutFlags == 0x81); + + FontStyleInformation cleared{}; + std::memset(&cleared, 0x7F, sizeof(cleared)); + Require(sceFontGetFontStyleInformation(nullptr, &cleared) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && Cleared(cleared)); + std::uint64_t notAFont[32]{}; + std::memset(&cleared, 0x7F, sizeof(cleared)); + Require(sceFontGetFontStyleInformation(reinterpret_cast(notAFont), &cleared) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && Cleared(cleared)); + Require(sceFontGetFontStyleInformation(named, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGetFontStyleInformation(nullptr, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + + for (FontHandle font : {named, os2, odd}) Require(sceFontCloseFont(font) == SCE_FONT_OK); + Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); + std::puts("Font style information tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestFontWritingLine.cpp b/core/libs/tests/GuestFontWritingLine.cpp index 7b40b889ca..0f41bb2066 100644 --- a/core/libs/tests/GuestFontWritingLine.cpp +++ b/core/libs/tests/GuestFontWritingLine.cpp @@ -1,18 +1,10 @@ +#include "prx/libSceFont/include/Api.hpp" #include "prx/libSceFont/include/FontTypes.hpp" #include #include #include #include - -extern "C" { -int APS5_VABI sceFontCreateWritingLine(const FontMemory*, std::int32_t, const void*, void**); -int APS5_VABI sceFontDestroyWritingLine(void**); -int APS5_VABI sceFontWritingLineClear(void*); -int APS5_VABI sceFontWritingLineWritesOrder(void*, std::uint64_t, const FontWritingMetrics*, void*); -const FontWritingLineStep* APS5_VABI sceFontWritingLineRefersRenderStep(void*); -int APS5_VABI sceFontWritingLineGetRenderMetrics(void*, FontWritingMetrics*); -int APS5_VABI sceFontWritingLineGetOrderingSpace(void*, float*, float*, float*, float*); -} +#include static void Check(bool value, int line) { if (!value) { @@ -32,12 +24,28 @@ static bool Throws(F f) { return false; } -struct WritingLineDetail { - std::uint16_t detailId; - std::uint16_t reserved[3]; - void* reservedPointers[3]; +struct AllocationState { + std::size_t live = 0; + std::uint32_t lastSize = 0; + bool fail = false; }; +static void* APS5_VABI Allocate(void* object, std::uint32_t size) { + auto& state = *static_cast(object); + state.lastSize = size; + if (state.fail) return nullptr; + void* allocation = std::malloc(size); + if (allocation) ++state.live; + return allocation; +} + +static void APS5_VABI Deallocate(void* object, void* allocation) { + auto& state = *static_cast(object); + Require(allocation && state.live > 0); + --state.live; + std::free(allocation); +} + constexpr std::uint64_t SPACE_BEFORE = std::uint64_t{1} << 30; constexpr std::uint64_t SPACE_AFTER = std::uint64_t{1} << 31; @@ -80,15 +88,27 @@ static bool OrderIs(void* line, const std::uint64_t* levels, const std::size_t* } int main() { + AllocationState allocations; + FontMemoryInterface memoryInterface{}; + memoryInterface.alloc = Allocate; + memoryInterface.dealloc = Deallocate; FontMemory memory{}; void* line = nullptr; - Require(Throws([&] { sceFontCreateWritingLine(&memory, 0x10, nullptr, nullptr); })); - Require(Throws([&] { sceFontCreateWritingLine(&memory, 0x13, nullptr, &line); }) && line == nullptr); - Require(Throws([&] { sceFontCreateWritingLine(&memory, 0, nullptr, &line); }) && line == nullptr); - WritingLineDetail detail{0x0FD4, {}, {}}; - Require(Throws([&] { sceFontCreateWritingLine(&memory, 0x10, &detail, &line); }) && line == nullptr); - detail.detailId = 0x0FD5; - Require(sceFontCreateWritingLine(&memory, 0x10, &detail, &line) == SCE_FONT_OK && line != nullptr); + Require(sceFontCreateWritingLine(nullptr, 0x12, nullptr, &line) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontCreateWritingLine(&memory, 0x12, nullptr, &line) == SCE_FONT_ERROR_INVALID_MEMORY); + memory.mem_kind = 0xF00; + memory.mspace_handle = &allocations; + Require(sceFontCreateWritingLine(&memory, 0x12, nullptr, &line) == SCE_FONT_ERROR_INVALID_MEMORY); + memory.iface = &memoryInterface; + Require(sceFontCreateWritingLine(&memory, 0x12, nullptr, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + for (int form : {0, 0x10, 0x13}) Require(sceFontCreateWritingLine(&memory, form, nullptr, &line) == SCE_FONT_ERROR_INVALID_PARAMETER && !line); + std::uint16_t detail = 0x0FD5; + Require(sceFontCreateWritingLine(&memory, 0x12, &detail, &line) == SCE_FONT_ERROR_INVALID_PARAMETER && !line); + allocations.fail = true; + Require(sceFontCreateWritingLine(&memory, 0x12, nullptr, &line) == SCE_FONT_ERROR_ALLOCATION_FAILED && !line); + allocations.fail = false; + Require(sceFontCreateWritingLine(&memory, 0x12, nullptr, &line) == SCE_FONT_OK && line); + Require(allocations.lastSize == 2208 && allocations.live == 1); void* ltr = nullptr; Require(sceFontCreateWritingLine(&memory, 0x12, nullptr, <r) == SCE_FONT_OK && ltr != nullptr && ltr != line); void* rtl = nullptr; @@ -108,14 +128,19 @@ int main() { const FontWritingMetrics runC{8.0f, 0.0f, {-4.0f, 1.0f, 2.0f, 5.0f}}; const FontWritingMetrics runD{4.0f, 0.0f, {-4.0f, 1.0f, 0.0f, 20.0f}}; - Require(Throws([&] { sceFontWritingLineWritesOrder(nullptr, Attribute(0), &runA, &a); })); - Require(Throws([&] { sceFontWritingLineWritesOrder(line, 1, &runA, &a); })); - Require(Throws([&] { sceFontWritingLineWritesOrder(line, 0x1FB, &runA, &a); })); - Require(Throws([&] { sceFontWritingLineWritesOrder(line, Attribute(0), &runA, nullptr); })); - Require(Throws([&] { sceFontWritingLineWritesOrder(line, Attribute(0x7F), &runA, &a); })); + Require(sceFontWritingLineWritesOrder(nullptr, Attribute(0), &runA, &a) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontWritingLineWritesOrder(line, 1, &runA, &a) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontWritingLineWritesOrder(line, 0x1FB, &runA, &a) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontWritingLineWritesOrder(line, Attribute(0), &runA, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontWritingLineWritesOrder(line, Attribute(0x7F), &runA, &a) == SCE_FONT_ERROR_INVALID_PARAMETER); Require(sceFontWritingLineRefersRenderStep(line) == nullptr); - Require(sceFontWritingLineWritesOrder(line, 0, &runA, &a) == SCE_FONT_OK); + Require(sceFontWritingLineWritesOrder(line, 0, &runA, &a) == SCE_FONT_ERROR_INVALID_PARAMETER); + allocations.fail = true; + Require(sceFontWritingLineWritesOrder(line, Attribute(0), &runA, &a) == SCE_FONT_ERROR_ALLOCATION_FAILED); + allocations.fail = false; + Require(sceFontWritingLineWritesOrder(line, Attribute(0), &runA, &a) == SCE_FONT_OK); + Require(allocations.lastSize == 64); Require(sceFontWritingLineWritesOrder(line, Attribute(0), &runB, &b) == SCE_FONT_OK); Require(SpaceIs(line, 16.0f, 1.0f, 0.0f, 1.0f)); Require(sceFontWritingLineGetRenderMetrics(line, &metrics) == SCE_FONT_OK && Same(metrics, {})); @@ -221,26 +246,37 @@ int main() { float space = 7.0f; Require(sceFontWritingLineGetOrderingSpace(rtl, nullptr, nullptr, &space, nullptr) == SCE_FONT_OK && space == 15.0f); Require(Throws([&] { sceFontWritingLineGetOrderingSpace(rtl, &space, &space, &space, nullptr); })); - Require(Throws([&] { sceFontWritingLineGetOrderingSpace(nullptr, &space, nullptr, nullptr, nullptr); })); - Require(Throws([&] { sceFontWritingLineGetRenderMetrics(nullptr, &metrics); })); - Require(Throws([&] { sceFontWritingLineGetRenderMetrics(rtl, nullptr); })); - Require(Throws([&] { sceFontWritingLineClear(nullptr); })); + Require(sceFontWritingLineGetOrderingSpace(nullptr, &space, nullptr, nullptr, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER && space == 0.0f); + Require(sceFontWritingLineGetRenderMetrics(nullptr, &metrics) == SCE_FONT_ERROR_INVALID_PARAMETER && Same(metrics, {})); + Require(sceFontWritingLineGetRenderMetrics(rtl, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontWritingLineClear(nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); int notALine = 0; Require(sceFontWritingLineRefersRenderStep(nullptr) == nullptr); - Require(Throws([&] { sceFontWritingLineClear(¬ALine); })); - Require(Throws([&] { sceFontWritingLineRefersRenderStep(¬ALine); })); - Require(Throws([&] { sceFontWritingLineGetOrderingSpace(¬ALine, &space, &space, &space, &space); })); + Require(sceFontWritingLineClear(¬ALine) == SCE_FONT_ERROR_INVALID_WRITING); + Require(sceFontWritingLineRefersRenderStep(¬ALine) == nullptr); + space = 7.0f; + Require(sceFontWritingLineGetOrderingSpace(¬ALine, &space, &space, &space, &space) == SCE_FONT_ERROR_INVALID_WRITING && space == 0.0f); + metrics = runA; + Require(sceFontWritingLineGetRenderMetrics(¬ALine, &metrics) == SCE_FONT_ERROR_INVALID_WRITING && Same(metrics, {})); + Require(sceFontWritingLineWritesOrder(¬ALine, 0, nullptr, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontWritingLineWritesOrder(¬ALine, Attribute(0), nullptr, &a) == SCE_FONT_ERROR_INVALID_WRITING); void* bogus = ¬ALine; - Require(Throws([&] { sceFontDestroyWritingLine(&bogus); }) && bogus == ¬ALine); + Require(sceFontDestroyWritingLine(&bogus) == SCE_FONT_ERROR_INVALID_WRITING && bogus == ¬ALine); + memory.mem_kind = 0; + Require(sceFontWritingLineClear(line) == SCE_FONT_ERROR_INVALID_WRITING); + Require(sceFontWritingLineWritesOrder(line, Attribute(0), &runA, &a) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontDestroyWritingLine(&line) == SCE_FONT_ERROR_INVALID_WRITING && line); + memory.mem_kind = 0xF00; void* destroyed = line; Require(sceFontDestroyWritingLine(&line) == SCE_FONT_OK && line == nullptr); - Require(sceFontDestroyWritingLine(&line) == SCE_FONT_OK && line == nullptr); - Require(sceFontDestroyWritingLine(nullptr) == SCE_FONT_OK); - Require(Throws([&] { sceFontWritingLineClear(destroyed); })); - Require(Throws([&] { sceFontWritingLineWritesOrder(destroyed, Attribute(0), &runA, &a); })); + Require(sceFontDestroyWritingLine(&line) == SCE_FONT_ERROR_INVALID_PARAMETER && line == nullptr); + Require(sceFontDestroyWritingLine(nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontWritingLineClear(destroyed) == SCE_FONT_ERROR_INVALID_WRITING); + Require(sceFontWritingLineWritesOrder(destroyed, Attribute(0), &runA, &a) == SCE_FONT_ERROR_INVALID_WRITING); Require(sceFontDestroyWritingLine(<r) == SCE_FONT_OK && ltr == nullptr); Require(sceFontDestroyWritingLine(&rtl) == SCE_FONT_OK && rtl == nullptr); + Require(allocations.live == 0); std::puts("font writing line: ok"); } diff --git a/core/libs/tests/GuestFopenErrors.cpp b/core/libs/tests/GuestFopenErrors.cpp index 5c72df7df0..1643eef3fc 100644 --- a/core/libs/tests/GuestFopenErrors.cpp +++ b/core/libs/tests/GuestFopenErrors.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -8,14 +9,6 @@ #include #include -extern "C" { -void* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode); -int APS5_VABI fclose_nid_postfix(void* stream); -void* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, void* stream); -int APS5_VABI fopen_s_nid_postfix(void** result, const char* filename, const char* mode); -int* APS5_VABI __error_nid_postfix(); -} - namespace { void Require(bool value, int line) { @@ -29,6 +22,7 @@ void Require(bool value, int line) { constexpr int GuestEnoent = 2; constexpr int GuestEisdir = 21; constexpr int GuestEinval = 22; +constexpr int GuestEnotsup = 45; constexpr int GuestEloop = 62; constexpr int GuestEnametoolong = 63; @@ -60,12 +54,12 @@ void Open() { Check(OpenFails((LongName + "/inside").c_str(), "a", GuestEnametoolong)); Check(OpenFails("missing.txt", "r", GuestEnoent)); Check(OpenFails("folder", "w", GuestEisdir)); - void* plain = fopen_nid_postfix("plain.txt", "r"); + FileStream* plain = fopen_nid_postfix("plain.txt", "r"); Check(plain != nullptr && fclose_nid_postfix(plain) == 0); } bool ReopenFails(const char* name, const char* mode, int error) { - void* stream = fopen_nid_postfix("plain.txt", "r"); + FileStream* stream = fopen_nid_postfix("plain.txt", "r"); Check(stream != nullptr); *__error_nid_postfix() = 0; const bool failed = freopen_nid_postfix(name, mode, stream) == nullptr && *__error_nid_postfix() == error; @@ -79,14 +73,14 @@ void Reopen() { Check(ReopenFails(LongName.c_str(), "r", GuestEnametoolong)); Check(ReopenFails(LongName.c_str(), "w", GuestEnametoolong)); Check(ReopenFails("missing.txt", "r", GuestEnoent)); - void* stream = fopen_nid_postfix("plain.txt", "r"); + FileStream* stream = fopen_nid_postfix("plain.txt", "r"); Check(stream != nullptr); - void* reopened = freopen_nid_postfix("plain.txt", "r", stream); + FileStream* reopened = freopen_nid_postfix("plain.txt", "r", stream); Check(reopened != nullptr && fclose_nid_postfix(reopened) == 0); } void CheckedOpen() { - void* stream = reinterpret_cast(1); + FileStream* stream = reinterpret_cast(1); Check(fopen_s_nid_postfix(&stream, "loop", "r") == GuestEloop && stream == nullptr); Check(fopen_s_nid_postfix(&stream, LongName.c_str(), "w") == GuestEnametoolong && stream == nullptr); Check(fopen_s_nid_postfix(&stream, "folder", "w") == GuestEisdir && stream == nullptr); @@ -95,6 +89,31 @@ void CheckedOpen() { Check(fopen_s_nid_postfix(&stream, "plain.txt", "r") == 0 && stream != nullptr && fclose_nid_postfix(stream) == 0); } +void CheckedReopen() { + FileStream* const sentinel = reinterpret_cast(1); + FileStream* result = sentinel; + FileStream* stream = fopen_nid_postfix("plain.txt", "r"); + Check(stream != nullptr); + Check(freopen_s_nid_postfix(nullptr, "plain.txt", "r", stream) == GuestEinval); + Check(freopen_s_nid_postfix(&result, nullptr, "r", stream) == GuestEnotsup && result == nullptr); + result = sentinel; + Check(freopen_s_nid_postfix(&result, "plain.txt", nullptr, stream) == GuestEinval && result == nullptr); + result = sentinel; + Check(freopen_s_nid_postfix(&result, "plain.txt", "r", nullptr) == GuestEinval && result == nullptr); + result = sentinel; + Check(freopen_s_nid_postfix(&result, "plain.txt", "z", stream) == GuestEinval && result == nullptr); + Check(freopen_s_nid_postfix(&result, "reopened.txt", "w", stream) == 0 && result == stream); + Check(fclose_nid_postfix(stream) == 0 && std::filesystem::exists("reopened.txt")); + stream = fopen_nid_postfix("plain.txt", "r"); + Check(stream != nullptr); + result = sentinel; + Check(freopen_s_nid_postfix(&result, "loop", "r", stream) == GuestEloop && result == nullptr); + stream = fopen_nid_postfix("plain.txt", "r"); + Check(stream != nullptr); + result = sentinel; + Check(freopen_s_nid_postfix(&result, "missing.txt", "r", stream) == GuestEnoent && result == nullptr); +} + } int main() { @@ -102,6 +121,7 @@ int main() { Open(); Reopen(); CheckedOpen(); + CheckedReopen(); std::error_code error; std::filesystem::current_path(std::filesystem::temp_directory_path(), error); std::filesystem::remove_all(root, error); diff --git a/core/libs/tests/GuestFopenLongHostPath.cpp b/core/libs/tests/GuestFopenLongHostPath.cpp index 9ebe1738ce..8b79945a60 100644 --- a/core/libs/tests/GuestFopenLongHostPath.cpp +++ b/core/libs/tests/GuestFopenLongHostPath.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -7,17 +8,11 @@ #include #include #include +#ifndef NOMINMAX #define NOMINMAX +#endif #include -extern "C" { -void* APS5_VABI fopen_nid_postfix(const char*, const char*); -int APS5_VABI fclose_nid_postfix(void*); -std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, void*); -std::size_t APS5_VABI fwrite_nid_postfix(const void*, std::size_t, std::size_t, void*); -int* APS5_VABI __error_nid_postfix(); -} - namespace { void Require(bool value, const char* message) { @@ -29,7 +24,7 @@ std::filesystem::path Extended(const std::filesystem::path& path) { return native.starts_with(L"\\\\") ? L"\\\\?\\UNC\\" + native.substr(2) : L"\\\\?\\" + native; } -using Stream = std::unique_ptr; +using Stream = std::unique_ptr; Stream Open(const char* path, const char* mode) { Stream stream(fopen_nid_postfix(path, mode), fclose_nid_postfix); diff --git a/core/libs/tests/GuestForceTlsDestructor.cpp b/core/libs/tests/GuestForceTlsDestructor.cpp index 6f900b9396..aba2bcafaa 100644 --- a/core/libs/tests/GuestForceTlsDestructor.cpp +++ b/core/libs/tests/GuestForceTlsDestructor.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -10,15 +12,6 @@ #include #endif -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, ModuleInfoEx* info); -int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(void (APS5_VABI* destructor)(void*), void* object, void* dsoSymbol); -void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix(); -int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle); -} - namespace { using Destructor = void (APS5_VABI*)(void*); @@ -57,13 +50,14 @@ void* APS5_VABI Worker(void*) { KernelModule ModuleOf(const void* address) { ModuleInfoEx info{}; info.st_size = sizeof(ModuleInfoEx); - Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(address), 2, &info) == 0); + Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(address), 1, &info) == 0); return info.id; } bool ForceThrows(KernelModule handle) { try { _sceLibcInternalForceTlsDestructor_nid_postfix(handle); + } catch (const std::runtime_error&) { return true; } @@ -101,6 +95,7 @@ int main() { Require(_sceLibcInternalForceTlsDestructor_nid_postfix(0) == 0); Require(calls.size() == 2); _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{3, 1, 2}); calls.clear(); @@ -108,6 +103,7 @@ int main() { Require(_sceLibcInternalForceTlsDestructor_nid_postfix(other) == 0); Require(calls.empty()); _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls.empty()); Require(Register(RecordAndRegister, 10) == 0); diff --git a/core/libs/tests/GuestFreeType.cpp b/core/libs/tests/GuestFreeType.cpp index 8e5c7e7d0f..ed1987ef28 100644 --- a/core/libs/tests/GuestFreeType.cpp +++ b/core/libs/tests/GuestFreeType.cpp @@ -1,11 +1,18 @@ +#include "prx/libSceFreeType/include/Api.hpp" #include "FreeTypeFontFixture.hpp" -#include "prx/libSceFreeType/include/GuestFreeType.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" #include "prx/libc/include/General.hpp" #include #include FT_FREETYPE_H +#include FT_GASP_H #include FT_OUTLINE_H +#include FT_TRIGONOMETRY_H +#include FT_CID_H +#include FT_SIZES_H +#include FT_SYSTEM_H +#include #include #include #include @@ -14,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -22,19 +30,6 @@ using namespace FreeType; -extern "C" { -int APS5_VABI FT_Init_FreeType_nid_postfix(void**); -int APS5_VABI FT_Done_FreeType_nid_postfix(void*); -int APS5_VABI FT_New_Face_nid_postfix(void*, const char*, std::int64_t, GuestFaceRec**); -int APS5_VABI FT_New_Memory_Face_nid_postfix(void*, const unsigned char*, std::int64_t, std::int64_t, GuestFaceRec**); -int APS5_VABI FT_Done_Face_nid_postfix(GuestFaceRec*); -int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(GuestFaceRec*, std::uint32_t, std::uint32_t); -std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(GuestFaceRec*, std::uint64_t); -int APS5_VABI FT_Load_Glyph_nid_postfix(GuestFaceRec*, std::uint32_t, std::int32_t); -int APS5_VABI FT_Load_Char_nid_postfix(GuestFaceRec*, std::uint64_t, std::int32_t); -int APS5_VABI FT_Render_Glyph_nid_postfix(GuestGlyphSlotRec*, std::int32_t); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "FreeType check failed at line %d\n", line); @@ -43,6 +38,77 @@ static void Check(bool value, int line) { } #define Require(value) Check((value), __LINE__) +static void CheckGaspTable(const std::vector* table, const std::vector>& ranges, + bool versionZero, bool render) { + const auto font = SquareGlyphFont('A', 700, false, table); + FreeType::GuestLibrary* library = nullptr; + GuestFaceRec* face = nullptr; + FT_Library nativeLibrary = nullptr; + FT_Face native = nullptr; + Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&nativeLibrary) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &face) == 0); + Require(FT_New_Memory_Face(nativeLibrary, font.data(), static_cast(font.size()), 0, &native) == 0); + auto* slot = face->glyph; + const auto glyphIndex = slot->glyph_index; + const auto bitmap = slot->bitmap.buffer; + const auto points = slot->outline.points; + for (std::uint32_t ppem = 0; ppem <= 65536; ++ppem) { + int expected = FT_GASP_NO_TABLE; + for (const auto& [maximum, flags] : ranges) { + if (ppem > maximum) continue; + expected = versionZero ? flags & 3 : flags; + break; + } + Require(FT_Get_Gasp_nid_postfix(face, ppem) == expected); + Require(FT_Get_Gasp(native, ppem) == expected); + } + Require(FT_Get_Gasp_nid_postfix(face, UINT32_MAX) == FT_GASP_NO_TABLE); + Require(face->glyph == slot && slot->glyph_index == glyphIndex && slot->bitmap.buffer == bitmap && slot->outline.points == points); + if (render) { + for (const unsigned ppem : {7u, 8u, 15u, 16u, 31u, 32u, 64u}) { + const int flags = FT_Get_Gasp_nid_postfix(face, ppem); + const int load = flags & FT_GASP_DO_GRIDFIT ? FT_LOAD_DEFAULT : FT_LOAD_NO_HINTING; + const auto mode = flags & FT_GASP_DO_GRAY ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO; + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, ppem) == 0 && FT_Set_Pixel_Sizes(native, 0, ppem) == 0); + Require(FT_Load_Char_nid_postfix(face, 'A', load) == 0 && FT_Load_Char(native, 'A', load) == 0); + Require(FT_Render_Glyph_nid_postfix(face->glyph, mode) == 0 && FT_Render_Glyph(native->glyph, mode) == 0); + const auto& actual = face->glyph->bitmap; + const auto& expected = native->glyph->bitmap; + Require(actual.width > 0 && actual.rows > 0); + Require(actual.width == expected.width && actual.rows == expected.rows && actual.pitch == expected.pitch); + Require(actual.pixel_mode == expected.pixel_mode && actual.num_grays == expected.num_grays); + Require(face->glyph->bitmap_left == native->glyph->bitmap_left && face->glyph->bitmap_top == native->glyph->bitmap_top); + Require(std::memcmp(actual.buffer, expected.buffer, static_cast(actual.rows) * std::abs(actual.pitch)) == 0); + } + } + Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(nativeLibrary) == 0); +} + +static void CheckGasp() { + Require(FT_Get_Gasp_nid_postfix(nullptr, 0) == FT_GASP_NO_TABLE); + Require(FT_Get_Gasp_nid_postfix(nullptr, UINT32_MAX) == FT_GASP_NO_TABLE); + CheckGaspTable(nullptr, {}, false, false); + const std::vector> ranges{{0, 0x8003}, {7, 2}, {15, 1}, {31, 0}, {65535, 0xFFFF}}; + for (const unsigned version : {0u, 1u}) { + std::vector table; + Put16(table, version); + Put16(table, ranges.size()); + for (const auto& [maximum, flags] : ranges) { + Put16(table, maximum); + Put16(table, flags); + } + CheckGaspTable(&table, ranges, version == 0, true); + } + std::vector shortRange; + PutAll16(shortRange, {1, 1, 48, FT_GASP_DO_GRAY}); + CheckGaspTable(&shortRange, {{48, FT_GASP_DO_GRAY}}, false, false); + for (const std::vector& table : {std::vector{}, {0, 1}, {0, 1, 0, 0}, {0, 2, 0, 1, 0, 10, 0, 3}}) { + CheckGaspTable(&table, {}, false, false); + } + std::puts("FreeType gasp: 8 fonts, 524304 queries, null faces and 14 rendered glyphs passed"); +} + template static bool ThrowsWith(TCall call, const std::string& prefix) { try { call(); @@ -52,7 +118,7 @@ template static bool ThrowsWith(TCall call, const std::string& return false; } -static void FaceMatches(const GuestFaceRec* face, FT_Face host, const void* library) { +static void FaceMatches(const GuestFaceRec* face, FT_Face host, const FreeType::GuestLibrary* library) { Require(face->num_faces == host->num_faces && face->face_index == host->face_index && face->face_flags == host->face_flags); Require(face->style_flags == host->style_flags && face->num_glyphs == host->num_glyphs && face->num_glyphs == 2); Require((face->family_name == nullptr) == (host->family_name == nullptr) && (face->style_name == nullptr) == (host->style_name == nullptr)); @@ -67,6 +133,121 @@ static void FaceMatches(const GuestFaceRec* face, FT_Face host, const void* libr Require(face->glyph != nullptr && face->glyph->face == face && face->glyph->library == library && face->size != nullptr && face->size->face == face); } +static unsigned orientationCases = 0; + +static void CheckOrientationCase(std::vector points, std::vector contours, + int expected, bool compareNative = true) { + ++orientationCases; + const auto originalPoints = points; + const auto originalContours = contours; + std::vector tags(points.size(), FT_CURVE_TAG_ON); + GuestOutline outline{static_cast(contours.size()), static_cast(points.size()), + points.data(), tags.data(), contours.data(), FT_OUTLINE_EVEN_ODD_FILL | FT_OUTLINE_REVERSE_FILL}; + const auto original = outline; + Require(FT_Outline_Get_Orientation_nid_postfix(&outline) == expected); + Require(outline.n_points == original.n_points && outline.n_contours == original.n_contours && outline.flags == original.flags); + Require(outline.points == original.points && outline.tags == original.tags && outline.contours == original.contours); + for (std::size_t index = 0; index < points.size(); ++index) { + Require(points[index].x == originalPoints[index].x && points[index].y == originalPoints[index].y); + Require(tags[index] == FT_CURVE_TAG_ON); + } + Require(contours == originalContours); + if (compareNative) { + std::vector nativePoints; + for (const auto& point : points) nativePoints.push_back({static_cast(point.x), static_cast(point.y)}); + FT_Outline native{outline.n_contours, outline.n_points, nativePoints.data(), tags.data(), contours.data(), outline.flags}; + Require(FT_Outline_Get_Orientation(&native) == expected); + } +} + +static void CheckOutlineOrientation() { + Require(FT_Outline_Get_Orientation_nid_postfix(nullptr) == FT_ORIENTATION_TRUETYPE); + CheckOrientationCase({}, {}, FT_ORIENTATION_TRUETYPE); + CheckOrientationCase({{0, 0}}, {0}, FT_ORIENTATION_NONE); + CheckOrientationCase({{0, 0}, {1, 1}}, {1}, FT_ORIENTATION_NONE); + CheckOrientationCase({{0, 0}, {1, 1}, {2, 2}}, {2}, FT_ORIENTATION_NONE); + CheckOrientationCase({{0, 0}, {40, 0}, {40, 40}, {0, 40}}, {3}, FT_ORIENTATION_POSTSCRIPT); + CheckOrientationCase({{0, 0}, {0, 40}, {40, 40}, {40, 0}}, {3}, FT_ORIENTATION_TRUETYPE); + CheckOrientationCase({{0, 0}, {40, 0}, {40, 40}, {0, 40}, {10, 10}, {10, 30}, {30, 30}, {30, 10}}, + {3, 7}, FT_ORIENTATION_POSTSCRIPT); + CheckOrientationCase({{0, 0}, {40, 0}, {40, 40}, {0, 40}, {0, 0}, {0, 40}, {40, 40}, {40, 0}}, + {3, 7}, FT_ORIENTATION_NONE); + CheckOrientationCase({{0, 0}, {40, 0}, {40, 40}, {0, 40}}, {}, FT_ORIENTATION_NONE); + CheckOrientationCase({{1048577, 0}, {1048578, 1}, {1048577, 2}}, {2}, FT_ORIENTATION_NONE); + CheckOrientationCase({{-0x1000000, -0x1000000}, {0x1000000, -0x1000000}, + {0x1000000, 0x1000000}, {-0x1000000, 0x1000000}}, {3}, FT_ORIENTATION_POSTSCRIPT); + CheckOrientationCase({{-0x1000001, 0}, {0, 100}, {100, 0}}, {2}, FT_ORIENTATION_NONE); + CheckOrientationCase({{0, -0x1000001}, {100, 0}, {0, 100}}, {2}, FT_ORIENTATION_NONE); + CheckOrientationCase({{0, 0}, {0x1000001, 100}, {100, 0}}, {2}, FT_ORIENTATION_NONE); + CheckOrientationCase({{0, 0}, {100, 0x1000001}, {0, 100}}, {2}, FT_ORIENTATION_NONE); + CheckOrientationCase({{INT64_MIN, INT64_MIN}, {INT64_MAX, INT64_MAX}, {0, 1}}, {2}, FT_ORIENTATION_NONE, false); + std::vector repeated{{0, 0}, {32767, 0}, {32767, 32767}, {0, 32767}, + {0, 0}, {32767, 0}, {32767, 32767}, {0, 32767}}; + CheckOrientationCase(repeated, {3, 7}, FT_ORIENTATION_POSTSCRIPT, sizeof(FT_Pos) == 8); + if constexpr (sizeof(FT_Pos) == 4) { + FT_Vector nativePoints[]{{0, 0}, {32767, 0}, {32767, 32767}, {0, 32767}, + {0, 0}, {32767, 0}, {32767, 32767}, {0, 32767}}; + unsigned short contours[]{3, 7}; + FT_Outline native{2, 8, nativePoints, nullptr, contours, 0}; + Require(FT_Outline_Get_Orientation(&native) == FT_ORIENTATION_TRUETYPE); + } + std::vector maximum(65535); + const GuestVector square[]{{0, 0}, {32767, 0}, {32767, 32767}, {0, 32767}}; + for (std::size_t index = 0; index < maximum.size(); ++index) maximum[index] = square[index % 4]; + CheckOrientationCase(maximum, {65534}, FT_ORIENTATION_POSTSCRIPT, sizeof(FT_Pos) == 8); + std::vector singletons(65535); + for (std::size_t index = 0; index < singletons.size(); ++index) singletons[index] = static_cast(index); + CheckOrientationCase(maximum, singletons, FT_ORIENTATION_NONE); + std::mt19937 random(0xF7013); + for (unsigned test = 0; test < 512; ++test) { + std::vector points(random() % 10 + 3); + for (auto& point : points) point = {static_cast(random() % 1024) - 512, static_cast(random() % 1024) - 512}; + std::int64_t twiceArea = 0; + auto previous = points.back(); + for (const auto& current : points) { + twiceArea += previous.x * current.y - current.x * previous.y; + previous = current; + } + const int expected = twiceArea > 0 ? FT_ORIENTATION_POSTSCRIPT : twiceArea < 0 ? FT_ORIENTATION_TRUETYPE : FT_ORIENTATION_NONE; + const std::vector contours{static_cast(points.size() - 1)}; + CheckOrientationCase(points, contours, expected); + for (auto& point : points) { point.x += 1000; point.y -= 2000; } + CheckOrientationCase(points, contours, expected); + std::reverse(points.begin(), points.end()); + CheckOrientationCase(points, contours, twiceArea == 0 ? FT_ORIENTATION_NONE : expected == FT_ORIENTATION_POSTSCRIPT ? FT_ORIENTATION_TRUETYPE : FT_ORIENTATION_POSTSCRIPT); + } + const auto font = SquareGlyphFont('A', 700); + FreeType::GuestLibrary* library = nullptr; + GuestFaceRec* face = nullptr; + FT_Library nativeLibrary = nullptr; + FT_Face native = nullptr; + Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&nativeLibrary) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &face) == 0); + Require(FT_New_Memory_Face(nativeLibrary, font.data(), static_cast(font.size()), 0, &native) == 0); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 32) == 0 && FT_Set_Pixel_Sizes(native, 0, 32) == 0); + for (const bool reflect : {false, true}) { + Require(FT_Load_Char_nid_postfix(face, 'A', FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP) == 0); + Require(FT_Load_Char(native, 'A', FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP) == 0); + if (reflect) { + for (unsigned index = 0; index < face->glyph->outline.n_points; ++index) { + face->glyph->outline.points[index].x = -face->glyph->outline.points[index].x; + native->glyph->outline.points[index].x = -native->glyph->outline.points[index].x; + } + } + Require(FT_Outline_Get_Orientation_nid_postfix(&face->glyph->outline) == (reflect ? FT_ORIENTATION_POSTSCRIPT : FT_ORIENTATION_TRUETYPE)); + Require(FT_Outline_Get_Orientation(&native->glyph->outline) == (reflect ? FT_ORIENTATION_POSTSCRIPT : FT_ORIENTATION_TRUETYPE)); + Require(FT_Render_Glyph_nid_postfix(face->glyph, FT_RENDER_MODE_NORMAL) == 0 && FT_Render_Glyph(native->glyph, FT_RENDER_MODE_NORMAL) == 0); + const auto& actual = face->glyph->bitmap; + const auto& expected = native->glyph->bitmap; + Require(actual.width > 0 && actual.rows > 0 && actual.width == expected.width && actual.rows == expected.rows && actual.pitch == expected.pitch); + Require(actual.pixel_mode == expected.pixel_mode && face->glyph->bitmap_left == native->glyph->bitmap_left && face->glyph->bitmap_top == native->glyph->bitmap_top); + Require(std::memcmp(actual.buffer, expected.buffer, static_cast(actual.rows) * std::abs(actual.pitch)) == 0); + } + Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(nativeLibrary) == 0); + std::printf("FreeType outline orientation: %u polygon cases and 2 rendered glyphs passed\n", orientationCases); +} + static void SlotMatches(const GuestGlyphSlotRec* slot, FT_GlyphSlot host) { Require(slot->glyph_index == host->glyph_index && slot->format == static_cast(host->format) && slot->next == nullptr); const auto& metrics = host->metrics; @@ -89,10 +270,600 @@ static void SlotMatches(const GuestGlyphSlotRec* slot, FT_GlyphSlot host) { Require(slot->lsb_delta == host->lsb_delta && slot->rsb_delta == host->rsb_delta && slot->control_len == host->control_len); } +static void SizeMatches(const GuestFaceRec* face, FT_Face host) { + const auto& actual = face->size->metrics; + const auto& expected = host->size->metrics; + Require(actual.x_ppem == expected.x_ppem && actual.y_ppem == expected.y_ppem); + Require(actual.x_scale == expected.x_scale && actual.y_scale == expected.y_scale); + Require(actual.ascender == expected.ascender && actual.descender == expected.descender); + Require(actual.height == expected.height && actual.max_advance == expected.max_advance); +} + +static void Trigonometry() { + constexpr std::int64_t circle = 360ll << 16; + const std::array, 68> poleTangents{{ + {-17694728ll, 33692373208ll}, + {-17694727ll, 45725363639ll}, + {-17694726ll, 45725363639ll}, + {-17694725ll, 64015509094ll}, + {-17694724ll, 64015509094ll}, + {-17694723ll, 128031018189ll}, + {-17694722ll, 128031018189ll}, + {-17694721ll, 640155090944ll}, + {-17694720ll, -640155090944ll}, + {-17694719ll, -128031018189ll}, + {-17694718ll, -128031018189ll}, + {-17694717ll, -64015509094ll}, + {-17694716ll, -64015509094ll}, + {-17694715ll, -45725363639ll}, + {-17694714ll, -45725363639ll}, + {-17694713ll, -33692373208ll}, + {-17694712ll, -33692373208ll}, + {-5898248ll, 33692373208ll}, + {-5898247ll, 45725363639ll}, + {-5898246ll, 45725363639ll}, + {-5898245ll, 64015509094ll}, + {-5898244ll, 64015509094ll}, + {-5898243ll, 128031018189ll}, + {-5898242ll, 128031018189ll}, + {-5898241ll, 640155090944ll}, + {-5898240ll, -640155090944ll}, + {-5898239ll, -128031018189ll}, + {-5898238ll, -128031018189ll}, + {-5898237ll, -64015509094ll}, + {-5898236ll, -64015509094ll}, + {-5898235ll, -45725363639ll}, + {-5898234ll, -45725363639ll}, + {-5898233ll, -33692373208ll}, + {-5898232ll, -33692373208ll}, + {5898232ll, 33692373208ll}, + {5898233ll, 45725363639ll}, + {5898234ll, 45725363639ll}, + {5898235ll, 64015509094ll}, + {5898236ll, 64015509094ll}, + {5898237ll, 128031018189ll}, + {5898238ll, 128031018189ll}, + {5898239ll, 640155090944ll}, + {5898240ll, -640155090944ll}, + {5898241ll, -128031018189ll}, + {5898242ll, -128031018189ll}, + {5898243ll, -64015509094ll}, + {5898244ll, -64015509094ll}, + {5898245ll, -45725363639ll}, + {5898246ll, -45725363639ll}, + {5898247ll, -33692373208ll}, + {5898248ll, -33692373208ll}, + {17694712ll, 33692373208ll}, + {17694713ll, 45725363639ll}, + {17694714ll, 45725363639ll}, + {17694715ll, 64015509094ll}, + {17694716ll, 64015509094ll}, + {17694717ll, 128031018189ll}, + {17694718ll, 128031018189ll}, + {17694719ll, 640155090944ll}, + {17694720ll, -640155090944ll}, + {17694721ll, -128031018189ll}, + {17694722ll, -128031018189ll}, + {17694723ll, -64015509094ll}, + {17694724ll, -64015509094ll}, + {17694725ll, -45725363639ll}, + {17694726ll, -45725363639ll}, + {17694727ll, -33692373208ll}, + {17694728ll, -33692373208ll}}}; + int scalarCases = 0; + int vectorCases = 0; + int differenceCases = 0; + for (const auto& [angle, tangent] : poleTangents) { + Require(FT_Tan_nid_postfix(angle) == tangent); + if (sizeof(FT_Fixed) == 4) { + Require(static_cast(FT_Tan(static_cast(angle))) != tangent); + } else { + Require(static_cast(FT_Tan(static_cast(angle))) == tangent); + } + } + auto compare = [&](std::int64_t angle, FT_Angle nativeAngle, std::int64_t expectedTangent) { + Require(FT_Cos_nid_postfix(angle) == FT_Cos(nativeAngle)); + Require(FT_Sin_nid_postfix(angle) == FT_Sin(nativeAngle)); + Require(FT_Tan_nid_postfix(angle) == expectedTangent); + FT_Vector expected{}; + FT_Vector_Unit(&expected, nativeAngle); + struct GuardedVector { + std::uint64_t before; + GuestVector vector; + std::uint64_t after; + } actual{0xA55A0FF0FF00AA55ull, {INT64_MAX, INT64_MIN}, 0x55AA00FFF00F5AA5ull}; + FT_Vector_Unit_nid_postfix(&actual.vector, angle); + Require(actual.vector.x == expected.x && actual.vector.y == expected.y); + Require(actual.vector.x == FT_Cos_nid_postfix(angle) && actual.vector.y == FT_Sin_nid_postfix(angle)); + Require(actual.before == 0xA55A0FF0FF00AA55ull && actual.after == 0x55AA00FFF00F5AA5ull); + ++scalarCases; + ++vectorCases; + }; + std::mt19937 random(0x17A693Bu); + std::uniform_int_distribution angles(INT32_MIN, INT32_MAX); + for (unsigned sample = 0; sample < 4096; ++sample) { + const std::int32_t angle = angles(random); + Require(FT_Cos_nid_postfix(angle) == FT_Cos(angle)); + Require(FT_Sin_nid_postfix(angle) == FT_Sin(angle)); + Require(static_cast(FT_Tan_nid_postfix(angle)) == FT_Tan(angle)); + FT_Vector expected{}; + GuestVector actual{}; + FT_Vector_Unit(&expected, angle); + FT_Vector_Unit_nid_postfix(&actual, angle); + Require(actual.x == expected.x && actual.y == expected.y); + ++scalarCases; + ++vectorCases; + } + const std::array bases{-circle + 1, -(180ll << 16), -(90ll << 16) - 1, -(45ll << 16), + -1, 0, 1, 25783, 45ll << 16, (90ll << 16) - 1, 90ll << 16, (90ll << 16) + 1, 180ll << 16, circle - 1}; + for (const std::int64_t turns : {0ll, 1ll, -1ll, 97ll, -97ll, 100000000000ll, -100000000000ll, 390000000000ll, -390000000000ll}) { + for (const std::int64_t base : bases) { + const std::int64_t tangent = FT_Tan_nid_postfix(base); + compare(base + circle * turns, static_cast(base), tangent); + for (const std::int64_t other : bases) { + Require(FT_Angle_Diff_nid_postfix(base + circle * turns, other + circle * turns) == + FT_Angle_Diff(static_cast(base), static_cast(other))); + ++differenceCases; + } + } + } + for (const auto boundary : std::array, 4>{ + std::pair{INT64_MIN, -16252928ll}, std::pair{INT64_MAX, 16252927ll}, + std::pair{INT64_MIN + 1, -16252927ll}, std::pair{INT64_MAX - 1, 16252926ll}}) { + Require(FT_Cos_nid_postfix(boundary.first) == FT_Cos(static_cast(boundary.second))); + Require(FT_Sin_nid_postfix(boundary.first) == FT_Sin(static_cast(boundary.second))); + Require(FT_Tan_nid_postfix(boundary.first) == FT_Tan_nid_postfix(boundary.second)); + FT_Vector expected{}; + GuestVector actual{}; + FT_Vector_Unit(&expected, static_cast(boundary.second)); + FT_Vector_Unit_nid_postfix(&actual, boundary.first); + Require(actual.x == expected.x && actual.y == expected.y); + ++scalarCases; + ++vectorCases; + } + FT_Vector_Unit_nid_postfix(nullptr, INT64_MIN); + Require(FT_Angle_Diff_nid_postfix(INT64_MIN, INT64_MIN + 1) == 1); + Require(FT_Angle_Diff_nid_postfix(INT64_MAX, INT64_MAX - 1) == -1); + Require(FT_Angle_Diff_nid_postfix(INT64_MIN, INT64_MIN) == 0); + Require(FT_Angle_Diff_nid_postfix(INT64_MAX, INT64_MAX) == 0); + for (const auto pair : std::array, 4>{ + std::pair{INT64_MIN, INT64_MAX}, std::pair{INT64_MAX, INT64_MIN}, std::pair{INT64_MIN, 0ll}, std::pair{0ll, INT64_MIN}}) { + if (pair.first == 0) { + Require(FT_Angle_Diff_nid_postfix(pair.first, pair.second) == FT_Angle_Diff_nid_postfix(0, -16252928)); + } else { + Require(ThrowsWith([&] { FT_Angle_Diff_nid_postfix(pair.first, pair.second); }, "FT_Angle_Diff: overflowing signed angle subtraction")); + } + } + const auto font = SquareGlyphFont('A', 700); + GuestLibrary* library = nullptr; + FT_Library oracle = nullptr; + GuestFaceRec* face = nullptr; + FT_Face native = nullptr; + Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&oracle) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &face) == 0); + Require(FT_New_Memory_Face(oracle, font.data(), static_cast(font.size()), 0, &native) == 0); + int rendered = 0; + for (const unsigned pixels : {32u, 64u}) { + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, pixels) == FT_Set_Pixel_Sizes(native, 0, pixels)); + for (const FT_Angle angle : {0L, 30L << 16, 45L << 16, 90L << 16, 135L << 16}) { + Require(FT_Load_Char_nid_postfix(face, 'A', FT_LOAD_DEFAULT) == FT_Load_Char(native, 'A', FT_LOAD_DEFAULT)); + GuestVector unit{}; + FT_Vector_Unit_nid_postfix(&unit, angle + circle * 100000000000ll); + for (unsigned point = 0; point < face->glyph->outline.n_points; ++point) { + auto& value = face->glyph->outline.points[point]; + const auto x = static_cast(value.x); + const auto y = static_cast(value.y); + value = {FT_MulFix(x, static_cast(unit.x)) - FT_MulFix(y, static_cast(unit.y)), + FT_MulFix(x, static_cast(unit.y)) + FT_MulFix(y, static_cast(unit.x))}; + } + FT_Matrix matrix{FT_Cos(angle), -FT_Sin(angle), FT_Sin(angle), FT_Cos(angle)}; + FT_Outline_Transform(&native->glyph->outline, &matrix); + Require(FT_Render_Glyph_nid_postfix(face->glyph, FT_RENDER_MODE_NORMAL) == FT_Render_Glyph(native->glyph, FT_RENDER_MODE_NORMAL)); + SlotMatches(face->glyph, native->glyph); + ++rendered; + } + } + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(oracle) == 0); + std::printf("FreeType trigonometry: %d scalar cases, %d vectors, %d angle differences, 68 LP64 tangent goldens, %d rendered glyphs\n", + scalarCases, vectorCases, differenceCases, rendered); +} + +struct DecomposeEvents { + std::vector> calls; + std::size_t stopAt = static_cast(-1); + int result = 0; + + int Add(int kind, GuestVector first, GuestVector second = {}, GuestVector third = {}) { + calls.push_back({kind, first.x, first.y, second.x, second.y, third.x, third.y}); + return calls.size() - 1 == stopAt ? result : 0; + } +}; + +static GuestVector GuestPoint(const FT_Vector& point) { return {point.x, point.y}; } + +static int NativeMove(const FT_Vector* to, void* user) { return static_cast(user)->Add(1, GuestPoint(*to)); } +static int NativeLine(const FT_Vector* to, void* user) { return static_cast(user)->Add(2, GuestPoint(*to)); } +static int NativeConic(const FT_Vector* control, const FT_Vector* to, void* user) { + return static_cast(user)->Add(3, GuestPoint(*control), GuestPoint(*to)); +} +static int NativeCubic(const FT_Vector* first, const FT_Vector* second, const FT_Vector* to, void* user) { + return static_cast(user)->Add(4, GuestPoint(*first), GuestPoint(*second), GuestPoint(*to)); +} + +static int APS5_VABI GuestMove(const GuestVector* to, void* user) { return static_cast(user)->Add(1, *to); } +static int APS5_VABI GuestLine(const GuestVector* to, void* user) { return static_cast(user)->Add(2, *to); } +static int APS5_VABI GuestConic(const GuestVector* control, const GuestVector* to, void* user) { + return static_cast(user)->Add(3, *control, *to); +} +static int APS5_VABI GuestCubic(const GuestVector* first, const GuestVector* second, const GuestVector* to, void* user) { + return static_cast(user)->Add(4, *first, *second, *to); +} + +static int APS5_VABI NullUserMove(const GuestVector*, void* user) { + Require(user == nullptr); + return -17; +} + +static std::size_t CompareDecomposition(std::vector points, std::vector tags, + std::vector contours, int shift, std::int64_t delta, + std::size_t stopAt = static_cast(-1), int stopResult = 0) { + std::vector nativePoints; + for (const auto point : points) nativePoints.push_back({static_cast(point.x), static_cast(point.y)}); + GuestOutline outline{static_cast(contours.size()), static_cast(points.size()), points.data(), tags.data(), contours.data(), 0}; + FT_Outline native{outline.n_contours, outline.n_points, nativePoints.data(), tags.data(), contours.data(), 0}; + DecomposeEvents actual; + DecomposeEvents expected; + actual.stopAt = expected.stopAt = stopAt; + actual.result = expected.result = stopResult; + const GuestOutlineFuncs functions{GuestMove, GuestLine, GuestConic, GuestCubic, shift, delta}; + const FT_Outline_Funcs nativeFunctions{NativeMove, NativeLine, NativeConic, NativeCubic, shift, static_cast(delta)}; + const int result = FT_Outline_Decompose_nid_postfix(&outline, &functions, &actual); + Require(result == FT_Outline_Decompose(&native, &nativeFunctions, &expected)); + Require(actual.calls == expected.calls); + for (std::size_t index = 0; index < points.size(); ++index) { + Require(points[index].x == nativePoints[index].x && points[index].y == nativePoints[index].y); + } + if (stopAt < expected.calls.size()) Require(result == stopResult && actual.calls.size() == stopAt + 1); + return actual.calls.size(); +} + +struct ChangingDecomposition { + DecomposeEvents events; + GuestOutlineFuncs guest{}; + FT_Outline_Funcs native{}; + GuestOutline* outline = nullptr; + FT_Outline* nativeOutline = nullptr; + GuestVector* replacement = nullptr; + FT_Vector* nativeReplacement = nullptr; +}; + +static int NativeChangedLine(const FT_Vector* to, void* user) { + return static_cast(user)->events.Add(5, GuestPoint(*to)); +} +static int APS5_VABI GuestChangedLine(const GuestVector* to, void* user) { + return static_cast(user)->events.Add(5, *to); +} +static int NativeChangingMove(const FT_Vector* to, void* user) { + auto& state = *static_cast(user); + state.native.line_to = NativeChangedLine; + state.native.shift = 4; + state.native.delta = 999; + state.nativeOutline->points = state.nativeReplacement; + return state.events.Add(1, GuestPoint(*to)); +} +static int APS5_VABI GuestChangingMove(const GuestVector* to, void* user) { + auto& state = *static_cast(user); + state.guest.line_to = GuestChangedLine; + state.guest.shift = 4; + state.guest.delta = 999; + state.outline->points = state.replacement; + GuestOutline empty{}; + const GuestOutlineFuncs noCallbacks{}; + Require(FT_Outline_Decompose_nid_postfix(&empty, &noCallbacks, nullptr) == 0); + return state.events.Add(1, *to); +} + +static void OutlineDecomposition() { + GuestOutline empty{}; + const GuestOutlineFuncs callbacks{GuestMove, GuestLine, GuestConic, GuestCubic, 0, 0}; + const FT_Outline_Funcs nativeCallbacks{NativeMove, NativeLine, NativeConic, NativeCubic, 0, 0}; + Require(FT_Outline_Decompose_nid_postfix(nullptr, nullptr, nullptr) == FT_Outline_Decompose(nullptr, nullptr, nullptr)); + Require(FT_Outline_Decompose_nid_postfix(nullptr, &callbacks, nullptr) == FT_Outline_Decompose(nullptr, &nativeCallbacks, nullptr)); + Require(FT_Outline_Decompose_nid_postfix(&empty, nullptr, nullptr) == FT_Err_Invalid_Argument); + const GuestOutlineFuncs unused{nullptr, nullptr, nullptr, nullptr, -1, INT64_MIN}; + Require(FT_Outline_Decompose_nid_postfix(&empty, &unused, nullptr) == 0); + int scenarios = 0; + std::size_t calls = 0; + for (unsigned count = 1; count <= 6; ++count) { + std::vector points; + for (unsigned point = 0; point < count; ++point) points.push_back({static_cast(point) * 13 - 37, static_cast(point) * -17 + 23}); + const unsigned patterns = 1u << (2 * count); + for (unsigned pattern = 0; pattern < patterns; ++pattern) { + std::vector tags; + for (unsigned point = 0; point < count; ++point) tags.push_back(static_cast(((pattern >> (point * 2)) & 3) | 0xA4)); + for (const int shift : {0, 1, 4}) { + for (const int delta : {-17, 0, 19}) { + calls += CompareDecomposition(points, tags, {static_cast(count - 1)}, shift, delta); + ++scenarios; + } + } + } + } + const std::vector curve{{-17, 3}, {7, -5}, {29, 31}, {43, -11}, {59, 37}, {73, -13}, {89, 41}, {101, -17}}; + for (const auto& tags : std::vector>{{1, 0, 0, 1, 2, 2, 1, 0}, {1, 2, 2, 1, 0, 1, 2, 2}, {0, 0, 0, 0, 0, 0, 0, 0}}) { + for (const auto& contours : std::vector>{{7}, {2, 7}, {0, 3, 7}, {2, 1}}) { + const std::size_t events = CompareDecomposition(curve, tags, contours, 2, -3); + ++scenarios; + for (std::size_t stop = 0; stop < events; ++stop) { + for (const int error : {1, -17, 0x12345678, INT32_MIN}) { + calls += CompareDecomposition(curve, tags, contours, 2, -3, stop, error); + ++scenarios; + } + } + } + } + { + GuestVector points[]{{7, -5}, {11, 13}, {17, -19}, {23, 29}}; + FT_Vector nativePoints[]{{7, -5}, {11, 13}, {17, -19}, {23, 29}}; + GuestVector replacement[]{{107, 95}, {111, 113}, {117, 81}, {123, 129}}; + FT_Vector nativeReplacement[]{{107, 95}, {111, 113}, {117, 81}, {123, 129}}; + unsigned char tags[]{1, 1, 1, 1}; + unsigned short contours[]{1, 3}; + GuestOutline outline{2, 4, points, tags, contours, 0}; + FT_Outline native{2, 4, nativePoints, tags, contours, 0}; + ChangingDecomposition actual; + ChangingDecomposition expected; + actual.guest = {GuestChangingMove, nullptr, nullptr, nullptr, 1, -3}; + expected.native = {NativeChangingMove, nullptr, nullptr, nullptr, 1, -3}; + actual.outline = &outline; + actual.replacement = replacement; + expected.nativeOutline = &native; + expected.nativeReplacement = nativeReplacement; + Require(FT_Outline_Decompose_nid_postfix(&outline, &actual.guest, &actual) == FT_Outline_Decompose(&native, &expected.native, &expected)); + Require(actual.events.calls == expected.events.calls && actual.events.calls.size() == 6); + } + { + GuestVector point{7, -5}; + unsigned char tag = 1; + unsigned short contour = 0; + GuestOutline outline{1, 1, &point, &tag, &contour, 0}; + const GuestOutlineFuncs nullUser{NullUserMove, nullptr, nullptr, nullptr, 0, 0}; + Require(FT_Outline_Decompose_nid_postfix(&outline, &nullUser, nullptr) == -17); + } + { + GuestVector points[]{{0x10000000001ll, -0x10000000003ll}, {0x10000000005ll, -0x10000000007ll}, {0x10000000009ll, -0x1000000000Bll}}; + unsigned char tags[]{1, 1, 1}; + unsigned short contours[]{1}; + GuestOutline outline{1, 2, points, tags, contours, 0}; + auto functions = callbacks; + functions.shift = 2; + functions.delta = 0x100000000ll; + DecomposeEvents events; + Require(FT_Outline_Decompose_nid_postfix(&outline, &functions, &events) == 0); + const std::vector> expected{{1, 0x3FF00000004ll, -0x4010000000Cll, 0, 0, 0, 0}, + {2, 0x3FF00000014ll, -0x4010000001Cll, 0, 0, 0, 0}, + {2, 0x3FF00000004ll, -0x4010000000Cll, 0, 0, 0, 0}}; + Require(events.calls == expected); + tags[0] = tags[1] = 0; + events.calls.clear(); + Require(FT_Outline_Decompose_nid_postfix(&outline, &functions, &events) == 0); + const std::vector> conic{{1, 0x3FF0000000Cll, -0x40100000014ll, 0, 0, 0, 0}, + {3, 0x3FF00000004ll, -0x4010000000Cll, 0x3FF0000000Cll, -0x40100000014ll, 0, 0}, + {3, 0x3FF00000014ll, -0x4010000001Cll, 0x3FF0000000Cll, -0x40100000014ll, 0, 0}}; + Require(events.calls == conic); + tags[0] = 1; + tags[1] = tags[2] = 2; + contours[0] = 2; + outline.n_points = 3; + events.calls.clear(); + Require(FT_Outline_Decompose_nid_postfix(&outline, &functions, &events) == 0); + const std::vector> cubic{{1, 0x3FF00000004ll, -0x4010000000Cll, 0, 0, 0, 0}, + {4, 0x3FF00000014ll, -0x4010000001Cll, 0x3FF00000024ll, -0x4010000002Cll, 0x3FF00000004ll, -0x4010000000Cll}}; + Require(events.calls == cubic); + contours[0] = 1; + outline.n_points = 2; + for (const int shift : {-1, 63, INT32_MAX}) { + functions.shift = shift; + Require(ThrowsWith([&] { FT_Outline_Decompose_nid_postfix(&outline, &functions, &events); }, "FT_Outline_Decompose: a shift")); + } + functions.shift = 62; + functions.delta = 0; + Require(ThrowsWith([&] { FT_Outline_Decompose_nid_postfix(&outline, &functions, &events); }, "FT_Outline_Decompose: overflowing coordinate scaling")); + functions.shift = 0; + functions.delta = INT64_MIN; + Require(ThrowsWith([&] { FT_Outline_Decompose_nid_postfix(&outline, &functions, &events); }, "FT_Outline_Decompose: overflowing coordinate translation")); + functions.delta = 0; + points[0] = {INT64_MAX, 0}; + points[1] = {INT64_MAX, 0}; + tags[0] = tags[1] = 0; + Require(ThrowsWith([&] { FT_Outline_Decompose_nid_postfix(&outline, &functions, &events); }, "FT_Outline_Decompose: overflowing midpoint")); + } + const auto font = SquareGlyphFont('A', 700); + GuestLibrary* library = nullptr; + FT_Library oracle = nullptr; + GuestFaceRec* face = nullptr; + FT_Face native = nullptr; + Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&oracle) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &face) == 0); + Require(FT_New_Memory_Face(oracle, font.data(), static_cast(font.size()), 0, &native) == 0); + int rendered = 0; + for (const unsigned pixels : {8u, 16u, 31u, 100u}) { + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, pixels) == FT_Set_Pixel_Sizes(native, 0, pixels)); + for (const unsigned glyph : {0u, 1u}) { + Require(FT_Load_Glyph_nid_postfix(face, glyph, FT_LOAD_DEFAULT) == FT_Load_Glyph(native, glyph, FT_LOAD_DEFAULT)); + DecomposeEvents actual; + DecomposeEvents expected; + Require(FT_Outline_Decompose_nid_postfix(&face->glyph->outline, &callbacks, &actual) == FT_Outline_Decompose(&native->glyph->outline, &nativeCallbacks, &expected)); + Require(actual.calls == expected.calls); + SlotMatches(face->glyph, native->glyph); + Require(FT_Render_Glyph_nid_postfix(face->glyph, FT_RENDER_MODE_NORMAL) == FT_Render_Glyph(native->glyph, FT_RENDER_MODE_NORMAL)); + SlotMatches(face->glyph, native->glyph); + ++rendered; + } + } + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(oracle) == 0); + std::printf("FreeType outline decomposition: %d native comparisons, %zu callbacks, %d rendered glyphs, wide coordinates and callback mutation\n", scenarios, calls, rendered); +} + struct Finalizers { std::vector calls; + bool reenter = false; }; +static std::vector CidMetadataFont(bool keyed, const std::string& registry, const std::string& ordering, + int supplement, unsigned cid) { + const auto index = [](const std::vector>& objects) { + std::vector result; + Put16(result, objects.size()); + if (objects.empty()) return result; + result.insert(result.end(), {1, 1}); + unsigned offset = 1; + for (const auto& object : objects) { + offset += object.size(); + Require(offset < 256); + result.push_back(static_cast(offset)); + } + for (const auto& object : objects) result.insert(result.end(), object.begin(), object.end()); + return result; + }; + const auto integer = [](std::vector& dict, int value) { + dict.push_back(29); + Put32(dict, static_cast(value)); + }; + const auto bytes = [](const std::string& text) { return std::vector(text.begin(), text.end()); }; + std::array offsets{}; + std::vector font; + for (unsigned pass = 0; pass < 2; ++pass) { + std::vector top; + for (const int value : {0, 0, 700, 800}) integer(top, value); + top.push_back(5); + if (keyed) { + for (const int value : {391, 392, supplement}) integer(top, value); + top.insert(top.end(), {12, 30}); + integer(top, cid + 1); + top.insert(top.end(), {12, 34}); + } + integer(top, offsets[0]); top.push_back(15); + integer(top, offsets[1]); top.push_back(17); + if (keyed) { + integer(top, offsets[3]); top.insert(top.end(), {12, 36}); + integer(top, offsets[2]); top.insert(top.end(), {12, 37}); + } + font = {1, 0, 4, 4}; + const auto append = [&](const std::vector>& objects) { + const auto data = index(objects); + font.insert(font.end(), data.begin(), data.end()); + }; + append({bytes(keyed ? "TestCID" : "TestNamed")}); + append({top}); + append({bytes(registry), bytes(ordering)}); + append({}); + offsets[0] = static_cast(font.size()); + font.push_back(0); Put16(font, cid); + offsets[2] = static_cast(font.size()); + font.insert(font.end(), {0, 0, 0}); + offsets[1] = static_cast(font.size()); + append({{248, 236, 14}, {248, 236, 239, 139, 21, 248, 136, 139, 139, 249, 80, 252, 136, 139, 139, 253, 80, 5, 14}}); + offsets[3] = static_cast(font.size()); + std::vector privateDict; + integer(privateDict, 0); integer(privateDict, offsets[4]); privateDict.push_back(18); + append({privateDict}); + offsets[4] = static_cast(font.size()); + } + return font; +} + +static bool SameCidString(const char* a, const char* b) { return a && b ? std::strcmp(a, b) == 0 : a == b; } + +static unsigned cidQueries = 0; + +static void CheckCidQueries(GuestFaceRec* face, FT_Face native, int kind, unsigned cid, int supplement, + const char* registry, const char* ordering) { + for (unsigned mask = 0; mask < 8; ++mask) { + struct Result { + std::uint64_t before = 0x123456789ABCDEF0; + const char* registry = "registry sentinel"; + const char* ordering = "ordering sentinel"; + std::int32_t supplement = 0x12345678; + std::uint32_t after = 0xFEDCBA98; + } actual, expected; + const int error = FT_Get_CID_Registry_Ordering_Supplement_nid_postfix(face, mask & 1 ? &actual.registry : nullptr, + mask & 2 ? &actual.ordering : nullptr, mask & 4 ? &actual.supplement : nullptr); + Require(error == FT_Get_CID_Registry_Ordering_Supplement(native, mask & 1 ? &expected.registry : nullptr, + mask & 2 ? &expected.ordering : nullptr, mask & 4 ? &expected.supplement : nullptr)); + Require(error == (kind > 0 ? 0 : FT_Err_Invalid_Argument)); + Require(SameCidString(actual.registry, expected.registry) && SameCidString(actual.ordering, expected.ordering)); + Require(actual.supplement == expected.supplement && actual.before == 0x123456789ABCDEF0 && actual.after == 0xFEDCBA98); + if (mask & 1) Require(SameCidString(actual.registry, kind > 0 ? registry : nullptr)); + if (mask & 2) Require(SameCidString(actual.ordering, kind > 0 ? ordering : nullptr)); + if (mask & 4) Require(actual.supplement == (kind > 0 ? supplement : 0)); + ++cidQueries; + } + const char* alias = "sentinel"; + const char* nativeAlias = "sentinel"; + Require(FT_Get_CID_Registry_Ordering_Supplement_nid_postfix(face, &alias, &alias, nullptr) == + FT_Get_CID_Registry_Ordering_Supplement(native, &nativeAlias, &nativeAlias, nullptr)); + Require(SameCidString(alias, nativeAlias) && SameCidString(alias, kind > 0 ? ordering : nullptr)); + ++cidQueries; + for (const bool output : {false, true}) { + struct Result { std::uint8_t before = 0x12; std::uint8_t value = 0xA5; std::array after{0x34, 0x56, 0x78}; } actual, expected; + const int error = FT_Get_CID_Is_Internally_CID_Keyed_nid_postfix(face, output ? &actual.value : nullptr); + Require(error == FT_Get_CID_Is_Internally_CID_Keyed(native, output ? &expected.value : nullptr)); + Require(error == (kind >= 0 ? 0 : FT_Err_Invalid_Argument)); + Require(actual.value == expected.value && (!output || actual.value == (kind > 0 ? 1 : 0))); + Require(actual.before == 0x12 && actual.after == expected.after); + ++cidQueries; + for (const unsigned glyph : {0u, 1u, 2u, 41u, 65535u, UINT32_MAX}) { + struct Mapping { std::uint32_t before = 0x12345678; std::uint32_t value = 0xDEADBEEF; std::uint32_t after = 0x87654321; } actualCid, nativeCid; + const int mappingError = FT_Get_CID_From_Glyph_Index_nid_postfix(face, glyph, output ? &actualCid.value : nullptr); + Require(mappingError == FT_Get_CID_From_Glyph_Index(native, glyph, output ? &nativeCid.value : nullptr)); + Require(mappingError == (kind > 0 && glyph < 2 ? 0 : FT_Err_Invalid_Argument)); + Require(actualCid.value == nativeCid.value && (!output || actualCid.value == (mappingError == 0 && glyph == 1 ? cid : 0))); + Require(actualCid.before == 0x12345678 && actualCid.after == 0x87654321); + ++cidQueries; + } + } +} + +static void CheckCidMetadata() { + CheckCidQueries(nullptr, nullptr, -2, 0, 0, nullptr, nullptr); + struct Fixture { std::vector bytes; int kind; unsigned cid; int supplement; const char* registry; const char* ordering; unsigned renderGlyph; }; + const std::array fixtures{{ + {CidMetadataFont(true, "Adobe", "Identity", 7, 41), 1, 41, 7, "Adobe", "Identity", 41}, + {CidMetadataFont(true, "TestRegistry", "TestOrdering", 9, 65535), 1, 65535, 9, "TestRegistry", "TestOrdering", 65535}, + {CidMetadataFont(false, "Adobe", "Identity", 0, 41), 0, 0, 0, nullptr, nullptr, 1}, + {SquareGlyphFont('A', 700), -1, 0, 0, nullptr, nullptr, 1}}}; + for (const auto& fixture : fixtures) { + FreeType::GuestLibrary* library = nullptr; + GuestFaceRec* face = nullptr; + FT_Library nativeLibrary = nullptr; + FT_Face native = nullptr; + Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&nativeLibrary) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, fixture.bytes.data(), fixture.bytes.size(), 0, &face) == 0); + Require(FT_New_Memory_Face(nativeLibrary, fixture.bytes.data(), static_cast(fixture.bytes.size()), 0, &native) == 0); + CheckCidQueries(face, native, fixture.kind, fixture.cid, fixture.supplement, fixture.registry, fixture.ordering); + const char* registry = nullptr; + const char* ordering = nullptr; + if (fixture.kind > 0) Require(FT_Get_CID_Registry_Ordering_Supplement_nid_postfix(face, ®istry, &ordering, nullptr) == 0); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 32) == 0 && FT_Set_Pixel_Sizes(native, 0, 32) == 0); + Require(FT_Load_Glyph_nid_postfix(face, fixture.renderGlyph, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP) == 0); + Require(FT_Load_Glyph(native, fixture.renderGlyph, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP) == 0); + Require(FT_Render_Glyph_nid_postfix(face->glyph, FT_RENDER_MODE_NORMAL) == 0 && FT_Render_Glyph(native->glyph, FT_RENDER_MODE_NORMAL) == 0); + SlotMatches(face->glyph, native->glyph); + Require(face->glyph->bitmap.width > 0 && face->glyph->bitmap.rows > 0); + if (fixture.kind > 0) { + const char* againRegistry = nullptr; + const char* againOrdering = nullptr; + Require(FT_Get_CID_Registry_Ordering_Supplement_nid_postfix(face, &againRegistry, &againOrdering, nullptr) == 0); + Require(registry == againRegistry && ordering == againOrdering); + Require(SameCidString(registry, fixture.registry) && SameCidString(ordering, fixture.ordering)); + } + Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(nativeLibrary) == 0); + } + std::printf("FreeType CID metadata: %u query scenarios, 4 fonts and 4 rendered glyphs passed\n", cidQueries); +} + static void NativeSlotFinalizer(void* object) { auto* slot = static_cast(object); Require(slot->face->glyph == nullptr); @@ -108,7 +879,13 @@ static void NativeSizeFinalizer(void* object) { static void NativeFaceFinalizer(void* object) { auto* face = static_cast(object); Require(face->glyph == nullptr && face->size == nullptr); - static_cast(face->generic.data)->calls.push_back(3); + auto* events = static_cast(face->generic.data); + events->calls.push_back(3); + if (events->reenter) { + Require(FT_Reference_Face(face) == 0 && FT_Reference_Face(face) == 0); + Require(FT_Done_Face(face) == 0); + Require(face->glyph == nullptr && face->size == nullptr); + } } static void APS5_VABI GuestSlotFinalizer(void* object) { @@ -126,7 +903,109 @@ static void APS5_VABI GuestSizeFinalizer(void* object) { static void APS5_VABI GuestFaceFinalizer(void* object) { auto* face = static_cast(object); Require(face->glyph == nullptr && face->size == nullptr); - static_cast(face->generic.data)->calls.push_back(3); + auto* events = static_cast(face->generic.data); + events->calls.push_back(3); + if (events->reenter) { + const auto* library = static_cast(face->internal)->library; + Require(FT_Reference_Face_nid_postfix(face) == 0 && FT_Reference_Face_nid_postfix(face) == 0); + Require(FT_Done_Face_nid_postfix(face) == 0 && library->faces.size() == 1); + Require(face->glyph == nullptr && face->size == nullptr); + } +} + +static std::string BitmapStrikeFont(int width, int height, int descent) { + std::string font = "STARTFONT 2.1\nFONT -anyps5-strike-medium-r-normal--" + std::to_string(height) + "-0-72-72-c-0-iso10646-1\n"; + font += "SIZE " + std::to_string(height) + " 72 72\nFONTBOUNDINGBOX " + std::to_string(width) + " " + std::to_string(height) + " 0 -" + std::to_string(descent); + font += "\nSTARTPROPERTIES 4\nFONT_ASCENT " + std::to_string(height - descent) + "\nFONT_DESCENT " + std::to_string(descent); + font += "\nCHARSET_REGISTRY \"ISO10646\"\nCHARSET_ENCODING \"1\"\nENDPROPERTIES\nCHARS 1\nSTARTCHAR A\nENCODING 65\nSWIDTH 1000 0\nDWIDTH "; + font += std::to_string(width) + " 0\nBBX " + std::to_string(width) + " " + std::to_string(height) + " 0 -" + std::to_string(descent) + "\nBITMAP\n"; + for (int y = 0; y < height; ++y) { + for (int byte = 0; byte < (width + 7) / 8; ++byte) { + unsigned value = 0; + for (int bit = 0; bit < 8; ++bit) { + const int x = byte * 8 + bit; + if (x < width && (x == 0 || y == height - 1 || (x + y) % 3 == 0)) value |= 0x80u >> bit; + } + font += "0123456789ABCDEF"[value >> 4]; + font += "0123456789ABCDEF"[value & 15]; + } + font += '\n'; + } + return font + "ENDCHAR\nENDFONT\n"; +} + +static void CheckStrikeSelection(GuestLibrary* library, FT_Library nativeLibrary, GuestFaceRec* scalable, FT_Face nativeScalable) { + const auto sizeMatches = [](const GuestSizeRec* size, FT_Size host) { + const auto& actual = size->metrics; + const auto& expected = host->metrics; + Require(actual.x_ppem == expected.x_ppem && actual.y_ppem == expected.y_ppem); + Require(actual.x_scale == expected.x_scale && actual.y_scale == expected.y_scale); + Require(actual.ascender == expected.ascender && actual.descender == expected.descender); + Require(actual.height == expected.height && actual.max_advance == expected.max_advance); + }; + constexpr std::array invalidIndices{-1, 1, 2, std::numeric_limits::min(), std::numeric_limits::max()}; + int requests = 0; + int rendered = 0; + for (const auto index : invalidIndices) { + const auto nullError = FT_Select_Size(nullptr, index); + Require(nullError != 0 && FT_Select_Size_nid_postfix(nullptr, index) == nullError); + ++requests; + } + for (const auto index : {0, -1, std::numeric_limits::max()}) { + const auto error = FT_Select_Size(nativeScalable, index); + Require(error == FT_Err_Invalid_Face_Handle && FT_Select_Size_nid_postfix(scalable, index) == error); + sizeMatches(scalable->size, nativeScalable->size); + ++requests; + } + for (const auto& dimensions : {std::array{5, 7, 2}, std::array{11, 13, 3}}) { + const auto [width, height, descent] = dimensions; + const std::string font = BitmapStrikeFont(width, height, descent); + const auto* bytes = reinterpret_cast(font.data()); + GuestFaceRec* face = nullptr; + FT_Face native = nullptr; + Require(FT_New_Memory_Face_nid_postfix(library, bytes, static_cast(font.size()), 0, &face) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes, static_cast(font.size()), 0, &native) == 0); + Require(FT_HAS_FIXED_SIZES(native) && !FT_IS_SCALABLE(native) && face->num_fixed_sizes == 1); + const GuestBitmapSize* strikes = face->available_sizes; + const auto& expectedStrike = native->available_sizes[0]; + Require(strikes[0].width == expectedStrike.width && strikes[0].height == expectedStrike.height && strikes[0].size == expectedStrike.size); + Require(strikes[0].x_ppem == expectedStrike.x_ppem && strikes[0].y_ppem == expectedStrike.y_ppem); + GuestSizeRec* size = face->size; + int marker = 0; + size->generic.data = ▮ + sizeMatches(size, native->size); + Require(size->metrics.y_ppem == 0); + const std::array indices{-1, 1, invalidIndices[3], invalidIndices[4], 0, 2, 0, -1, 0}; + for (const auto index : indices) { + face->glyph->advance.x = 0x123456789ABCll; + face->bbox.xMin = -0x123456789ABCll; + const auto error = FT_Select_Size(native, index); + Require((index == 0) == (error == 0)); + Require(FT_Select_Size_nid_postfix(face, index) == error); + ++requests; + sizeMatches(size, native->size); + Require(face->size == size && size->face == face && size->generic.data == &marker && face->available_sizes == strikes); + Require(face->glyph->advance.x == 0x123456789ABCll && face->bbox.xMin == -0x123456789ABCll); + if (error != 0) continue; + Require(size->metrics.y_ppem == height && size->metrics.ascender == (height - descent) * 64 && size->metrics.descender == -descent * 64); + for (const auto flags : std::array{FT_LOAD_DEFAULT, FT_LOAD_RENDER, FT_LOAD_RENDER | FT_LOAD_MONOCHROME}) { + Require(FT_Load_Char_nid_postfix(face, 'A', flags) == 0 && FT_Load_Char(native, 'A', flags) == 0); + SlotMatches(face->glyph, native->glyph); + const auto& bitmap = face->glyph->bitmap; + Require(face->glyph->format == FT_GLYPH_FORMAT_BITMAP && bitmap.pixel_mode == FT_PIXEL_MODE_MONO); + Require(bitmap.width == width && bitmap.rows == height && bitmap.pitch > 0 && face->glyph->bitmap_top == height - descent); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const bool pixel = (bitmap.buffer[y * bitmap.pitch + x / 8] & (0x80u >> (x % 8))) != 0; + Require(pixel == (x == 0 || y == height - 1 || (x + y) % 3 == 0)); + } + } + ++rendered; + } + } + Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); + } + std::printf("FT_Select_Size: %d native request comparisons, %d bitmap glyph comparisons\n", requests, rendered); } static void SetFinalizers(FT_Face face, Finalizers& events) { @@ -135,22 +1014,585 @@ static void SetFinalizers(FT_Face face, Finalizers& events) { face->size->generic = {&events, NativeSizeFinalizer}; } +static bool Equal(GuestVector a, GuestVector b) { + return a.x == b.x && a.y == b.y; +} + +static bool Equal(const GuestOutline& a, const GuestOutline& b) { + return a.n_points == b.n_points && a.n_contours == b.n_contours && a.points == b.points && + a.tags == b.tags && a.contours == b.contours && a.flags == b.flags; +} + +static void CopyCase(std::uint16_t pointCount, std::uint16_t contourCount, int sourceFlags, int targetFlags, bool wide) { + const GuestVector guard{0x123456789ABCDEF, -0x123456789ABCDEF}; + std::vector points(pointCount + 2, guard), copied(pointCount + 2, guard); + std::vector tags(pointCount + 2, 0xA5), copiedTags(pointCount + 2, 0x5A); + std::vector contours(contourCount + 2, 0xABCD), copiedContours(contourCount + 2, 0xDCBA); + std::vector hostPoints(pointCount), hostCopied(pointCount); + std::vector hostTags(pointCount), hostCopiedTags(pointCount); + std::vector hostContours(contourCount), hostCopiedContours(contourCount); + for (unsigned index = 0; index < pointCount; ++index) { + const auto value = static_cast(index) * 65537 - 100000; + points[index + 1] = wide ? GuestVector{INT64_MIN + index, + INT64_MAX - index} + : GuestVector{value / 4, -value / 4}; + tags[index + 1] = static_cast(index); + hostPoints[index] = {static_cast(points[index + 1].x), static_cast(points[index + 1].y)}; + hostTags[index] = tags[index + 1]; + } + for (unsigned index = 0; index < contourCount; ++index) { + contours[index + 1] = static_cast(index); + hostContours[index] = contours[index + 1]; + } + GuestOutline source{contourCount, pointCount, pointCount ? points.data() + 1 : nullptr, + pointCount ? tags.data() + 1 : nullptr, contourCount ? contours.data() + 1 : nullptr, sourceFlags}; + GuestOutline target{contourCount, pointCount, pointCount ? copied.data() + 1 : nullptr, + pointCount ? copiedTags.data() + 1 : nullptr, contourCount ? copiedContours.data() + 1 : nullptr, targetFlags}; + const auto originalSource = source; + auto expectedTarget = target; + const auto originalPoints = points; + const auto originalTags = tags; + const auto originalContours = contours; + FT_Outline hostSource{contourCount, pointCount, hostPoints.data(), hostTags.data(), hostContours.data(), sourceFlags}; + FT_Outline hostTarget{contourCount, pointCount, hostCopied.data(), hostCopiedTags.data(), hostCopiedContours.data(), targetFlags}; + Require(FT_Outline_Copy_nid_postfix(&source, &target) == FT_Outline_Copy(&hostSource, &hostTarget)); + expectedTarget.flags = hostTarget.flags; + Require(Equal(source, originalSource) && Equal(target, expectedTarget)); + Require(Equal(copied.front(), guard) && Equal(copied.back(), guard)); + Require(copiedTags.front() == 0x5A && copiedTags.back() == 0x5A); + Require(copiedContours.front() == 0xDCBA && copiedContours.back() == 0xDCBA); + for (unsigned index = 0; index < pointCount; ++index) { + Require(Equal(copied[index + 1], originalPoints[index + 1])); + if (!wide) Require(copied[index + 1].x == hostCopied[index].x && copied[index + 1].y == hostCopied[index].y); + Require(copiedTags[index + 1] == hostCopiedTags[index]); + } + for (unsigned index = 0; index < contourCount; ++index) Require(copiedContours[index + 1] == hostCopiedContours[index]); + if (pointCount) { + target.points[0] = {321, -654}; + target.tags[0] ^= 0xFF; + } + if (contourCount) target.contours[0] ^= 0xFFFF; + for (std::size_t index = 0; index < points.size(); ++index) Require(Equal(points[index], originalPoints[index])); + Require(tags == originalTags && contours == originalContours); +} + +static void InvalidAndSelfCopies() { + GuestOutline source{1, 4, nullptr, nullptr, nullptr, 0x1234}; + auto target = source; + Require(FT_Outline_Copy_nid_postfix(nullptr, nullptr) == FT_Outline_Copy(nullptr, nullptr)); + Require(FT_Outline_Copy_nid_postfix(nullptr, &target) == FT_Err_Invalid_Outline); + Require(Equal(target, source)); + Require(FT_Outline_Copy_nid_postfix(&source, nullptr) == FT_Err_Invalid_Outline); + Require(FT_Outline_Copy_nid_postfix(&source, &source) == 0); + for (const auto counts : std::array, 3>{{{1, 3}, {0, 4}, {0, 0}}}) { + target.n_contours = counts[0]; + target.n_points = counts[1]; + const auto before = target; + FT_Outline nativeSource{1, 4, nullptr, nullptr, nullptr, source.flags}; + FT_Outline nativeTarget{target.n_contours, target.n_points, nullptr, nullptr, nullptr, target.flags}; + Require(FT_Outline_Copy_nid_postfix(&source, &target) == FT_Outline_Copy(&nativeSource, &nativeTarget)); + Require(Equal(target, before)); + } +} + +static void RenderCopy() { + const auto font = SquareGlyphFont('A', 700); + GuestLibrary* library = nullptr; + GuestFaceRec* face = nullptr; + FT_Library nativeLibrary = nullptr; + FT_Face nativeFace = nullptr; + Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&nativeLibrary) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &face) == 0); + Require(FT_New_Memory_Face(nativeLibrary, font.data(), static_cast(font.size()), 0, &nativeFace) == 0); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 32) == 0 && FT_Set_Pixel_Sizes(nativeFace, 0, 32) == 0); + for (const int offset : {0, 37, -129}) { + Require(FT_Load_Char_nid_postfix(face, 'A', FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP) == 0); + Require(FT_Load_Char(nativeFace, 'A', FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP) == 0); + auto& target = face->glyph->outline; + std::vector points(target.points, target.points + target.n_points); + std::vector nativePoints(nativeFace->glyph->outline.points, + nativeFace->glyph->outline.points + nativeFace->glyph->outline.n_points); + for (std::size_t index = 0; index < points.size(); ++index) { + points[index].x += offset + points[index].y / 4; + nativePoints[index].x += offset + nativePoints[index].y / 4; + } + auto source = target; + source.points = points.data(); + source.flags ^= FT_OUTLINE_OWNER; + auto nativeSource = nativeFace->glyph->outline; + nativeSource.points = nativePoints.data(); + nativeSource.flags ^= FT_OUTLINE_OWNER; + Require(FT_Outline_Copy_nid_postfix(&source, &target) == 0); + Require(FT_Outline_Copy(&nativeSource, &nativeFace->glyph->outline) == 0); + points[0] = {}; + nativePoints[0] = {}; + Require(FT_Render_Glyph_nid_postfix(face->glyph, FT_RENDER_MODE_NORMAL) == 0); + Require(FT_Render_Glyph(nativeFace->glyph, FT_RENDER_MODE_NORMAL) == 0); + const auto& actual = face->glyph->bitmap; + const auto& expected = nativeFace->glyph->bitmap; + Require(actual.rows > 0 && actual.width > 0); + Require(actual.rows == expected.rows && actual.width == expected.width && actual.pitch == expected.pitch); + Require(actual.pixel_mode == expected.pixel_mode && actual.num_grays == expected.num_grays); + Require(face->glyph->bitmap_left == nativeFace->glyph->bitmap_left && face->glyph->bitmap_top == nativeFace->glyph->bitmap_top); + const auto size = static_cast(actual.rows) * std::abs(actual.pitch); + Require(std::memcmp(actual.buffer, expected.buffer, size) == 0); + } + Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(nativeFace) == 0); + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(nativeLibrary) == 0); +} + +static void CheckOutlineCopies() { + InvalidAndSelfCopies(); + for (const int sourceFlags : {0, FT_OUTLINE_OWNER, FT_OUTLINE_REVERSE_FILL, -1}) { + for (const int targetFlags : {0, FT_OUTLINE_OWNER, FT_OUTLINE_EVEN_ODD_FILL, -1}) { + CopyCase(0, 0, sourceFlags, targetFlags, false); + CopyCase(4, 2, sourceFlags, targetFlags, false); + CopyCase(4, 2, sourceFlags, targetFlags, true); + } + } + CopyCase(65535, 65535, FT_OUTLINE_REVERSE_FILL, FT_OUTLINE_OWNER, false); + CopyCase(65535, 65535, FT_OUTLINE_OWNER, 0, true); + std::mt19937 random(0xF7C0); + for (unsigned index = 0; index < 128; ++index) { + const auto count = static_cast(random() % 256 + 1); + const auto contours = static_cast(random() % (count + 1)); + const auto sourceFlags = static_cast(random() & 0x7FFFFFFFu); + const auto targetFlags = static_cast(random() & 0x7FFFFFFFu); + CopyCase(count, contours, sourceFlags, targetFlags, false); + } + RenderCopy(); + std::puts("FreeType outline copy: 178 copy cases, validation, self-copy and 3 rendered copies passed"); +} + static void SetFinalizers(GuestFaceRec* face, Finalizers& events) { face->generic = {&events, GuestFaceFinalizer}; face->glyph->generic = {&events, GuestSlotFinalizer}; face->size->generic = {&events, GuestSizeFinalizer}; } +static void CheckTransforms(const std::vector& font) { + FT_Library native = nullptr; + FT_Face host = nullptr; + FT_Face nativeOther = nullptr; + FreeType::GuestLibrary* library = nullptr; + GuestFaceRec* face = nullptr; + GuestFaceRec* other = nullptr; + Require(FT_Init_FreeType(&native) == 0 && FT_Init_FreeType_nid_postfix(&library) == 0); + Require(FT_New_Memory_Face(native, font.data(), static_cast(font.size()), 0, &host) == 0); + Require(FT_New_Memory_Face(native, font.data(), static_cast(font.size()), 0, &nativeOther) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &face) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &other) == 0); + Require(FT_Set_Pixel_Sizes(host, 0, 100) == 0 && FT_Set_Pixel_Sizes_nid_postfix(face, 0, 100) == 0); + Require(FT_Set_Pixel_Sizes(nativeOther, 0, 100) == 0 && FT_Set_Pixel_Sizes_nid_postfix(other, 0, 100) == 0); + const std::array matrices{ + GuestMatrix{65536, 0, 0, 65536}, GuestMatrix{0, -65536, 65536, 0}, GuestMatrix{0, 65536, -65536, 0}, + GuestMatrix{-65536, 0, 0, 65536}, GuestMatrix{131072, 0, 0, 32768}, GuestMatrix{65536, 16384, -8192, 65536}, + GuestMatrix{0, 0, 0, 0} + }; + const std::array deltas{GuestVector{0, 0}, GuestVector{64, -128}, GuestVector{31, -63}, GuestVector{-96, 192}}; + std::size_t comparisons = 0; + for (std::size_t m = 0; m <= matrices.size(); ++m) { + for (std::size_t d = 0; d <= deltas.size(); ++d) { + GuestMatrix input = m < matrices.size() ? matrices[m] : matrices[0]; + GuestVector delta = d < deltas.size() ? deltas[d] : deltas[0]; + FT_Matrix matrix{static_cast(input.xx), static_cast(input.xy), static_cast(input.yx), static_cast(input.yy)}; + FT_Vector translation{static_cast(delta.x), static_cast(delta.y)}; + std::array savedSlot{}; + std::array savedSize{}; + std::memcpy(savedSlot.data(), face->glyph, savedSlot.size()); + std::memcpy(savedSize.data(), face->size, savedSize.size()); + FT_Set_Transform_nid_postfix(face, m < matrices.size() ? &input : nullptr, d < deltas.size() ? &delta : nullptr); + + FT_Set_Transform(host, m < matrices.size() ? &matrix : nullptr, d < deltas.size() ? &translation : nullptr); + Require(std::memcmp(savedSlot.data(), face->glyph, savedSlot.size()) == 0); + Require(std::memcmp(savedSize.data(), face->size, savedSize.size()) == 0); + Require(input.xx == matrix.xx && input.xy == matrix.xy && input.yx == matrix.yx && input.yy == matrix.yy); + Require(delta.x == translation.x && delta.y == translation.y); + input = matrices[0]; + delta = deltas[0]; + for (const auto flags : std::array{FT_LOAD_DEFAULT, FT_LOAD_IGNORE_TRANSFORM, FT_LOAD_RENDER}) { + for (const std::uint32_t glyph : {0u, 1u}) { + Require(FT_Load_Glyph_nid_postfix(face, glyph, flags) == FT_Load_Glyph(host, glyph, flags)); + SlotMatches(face->glyph, host->glyph); + if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + Require(FT_Render_Glyph_nid_postfix(face->glyph, FT_RENDER_MODE_NORMAL) == FT_Render_Glyph(host->glyph, FT_RENDER_MODE_NORMAL)); + SlotMatches(face->glyph, host->glyph); + } + ++comparisons; + } + } + Require(FT_Load_Glyph_nid_postfix(other, 1, FT_LOAD_DEFAULT) == FT_Load_Glyph(nativeOther, 1, FT_LOAD_DEFAULT)); + SlotMatches(other->glyph, nativeOther->glyph); + } + } + GuestMatrix rejected{65536, 0, 0, 65536}; + GuestVector rejectedDelta{}; + const auto loadUnchanged = [&] { + Require(FT_Load_Glyph_nid_postfix(face, 1, FT_LOAD_DEFAULT) == FT_Load_Glyph(host, 1, FT_LOAD_DEFAULT)); + SlotMatches(face->glyph, host->glyph); + }; + if constexpr (sizeof(FT_Long) < sizeof(std::int64_t)) { + for (const auto value : std::array{-0x80000001ll, 0x80000000ll, 0x100000000ll, + std::numeric_limits::min(), std::numeric_limits::max()}) { + for (auto member : {&GuestMatrix::xx, &GuestMatrix::xy, &GuestMatrix::yx, &GuestMatrix::yy}) { + rejected = matrices[0]; + rejected.*member = value; + Require(ThrowsWith([&] { FT_Set_Transform_nid_postfix(face, &rejected, nullptr); }, + "FT_Set_Transform: a component outside the host FT_Long range is not implemented")); + loadUnchanged(); + } + for (auto member : {&GuestVector::x, &GuestVector::y}) { + rejectedDelta = {}; + rejectedDelta.*member = value; + Require(ThrowsWith([&] { FT_Set_Transform_nid_postfix(face, &matrices[1], &rejectedDelta); }, + "FT_Set_Transform: a component outside the host FT_Long range is not implemented")); + loadUnchanged(); + } + } + } + rejected.xx = std::numeric_limits::max(); + rejectedDelta.x = std::numeric_limits::min(); + FT_Set_Transform_nid_postfix(nullptr, &rejected, &rejectedDelta); + FT_Set_Transform_nid_postfix(nullptr, nullptr, nullptr); + + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(native) == 0); + std::printf("FT_Set_Transform: %zu native glyph and bitmap comparisons\n", comparisons); +} + +static void SizeMatches(const GuestSizeRec* size, FT_Size native) { + Require(size != nullptr && native != nullptr); + const auto& a = size->metrics; + const auto& b = native->metrics; + Require(a.x_ppem == b.x_ppem && a.y_ppem == b.y_ppem && a.x_scale == b.x_scale && a.y_scale == b.y_scale); + Require(a.ascender == b.ascender && a.descender == b.descender && a.height == b.height && a.max_advance == b.max_advance); +} + +static bool OwnsSize(const GuestFace& face, const GuestSizeRec* size) { + if (size == &face.size) return true; + for (const auto& entry : face.sizes) { + if (size == &entry.rec) return true; + } + return false; +} + +struct SizeEvents { + std::vector sizes; + std::vector> calls; + + int Index(void* size) const { + if (!size) return -1; + for (std::size_t index = 0; index < sizes.size(); ++index) { + if (sizes[index] == size) return static_cast(index); + } + Require(false); + return -2; + } +}; + +static void NativeMultipleSizeFinalizer(void* object) { + auto* size = static_cast(object); + auto& events = *static_cast(size->generic.data); + events.calls.push_back({events.Index(size), events.Index(size->face->size), size->metrics.y_ppem, size->face->glyph == nullptr}); +} + +static void APS5_VABI GuestMultipleSizeFinalizer(void* object) { + auto* size = static_cast(object); + auto* face = static_cast(size->face); + const auto& owner = *static_cast(face->internal); + Require(OwnsSize(owner, size)); + if (face->size) Require(OwnsSize(owner, face->size)); + SizeMatches(size, static_cast(size->internal)); + auto& events = *static_cast(size->generic.data); + events.calls.push_back({events.Index(size), events.Index(face->size), size->metrics.y_ppem, face->glyph == nullptr}); +} + +static void NativeMultipleSizeFaceFinalizer(void* object) { + auto* face = static_cast(object); + Require(face->glyph == nullptr && face->size == nullptr); + static_cast(face->generic.data)->calls.push_back({100, -1, 0, 1}); +} + +static void APS5_VABI GuestMultipleSizeFaceFinalizer(void* object) { + auto* face = static_cast(object); + Require(face->glyph == nullptr && face->size == nullptr); + static_cast(face->generic.data)->calls.push_back({100, -1, 0, 1}); +} + +static FT_Alloc_Func SizeOriginalAlloc = nullptr; +static int SizeAllocationsBeforeFailure = 0; + +static void* SizeFailingAlloc(FT_Memory memory, long bytes) { + if (SizeAllocationsBeforeFailure-- == 0) return nullptr; + return SizeOriginalAlloc(memory, bytes); +} + +static void SizeObjects(const std::vector& font) { + Require(FT_New_Size_nid_postfix(nullptr, nullptr) == FT_New_Size(nullptr, nullptr)); + Require(FT_Done_Size_nid_postfix(nullptr) == FT_Done_Size(nullptr)); + Require(FT_Activate_Size_nid_postfix(nullptr) == FT_Activate_Size(nullptr)); + GuestSizeRec sentinel{}; + GuestSizeRec* untouched = &sentinel; + Require(FT_New_Size_nid_postfix(nullptr, &untouched) == FT_Err_Invalid_Face_Handle && untouched == &sentinel); + FT_FaceRec driverless{}; + GuestFace invalidOwner; + invalidOwner.host = &driverless; + invalidOwner.rec.internal = &invalidOwner; + FT_Size nativeUntouched = reinterpret_cast(&sentinel); + Require(FT_New_Size(&driverless, &nativeUntouched) == FT_Err_Invalid_Driver_Handle); + Require(FT_New_Size_nid_postfix(&invalidOwner.rec, &untouched) == FT_Err_Invalid_Driver_Handle && untouched == &sentinel); + Require(FT_New_Size_nid_postfix(&invalidOwner.rec, nullptr) == FT_New_Size(&driverless, nullptr)); + + FT_SizeRec driverlessSize{}; + GuestSizeRec invalidSize{}; + invalidSize.internal = &driverlessSize; + Require(FT_Done_Size_nid_postfix(&invalidSize) == FT_Done_Size(&driverlessSize)); + Require(FT_Activate_Size_nid_postfix(&invalidSize) == FT_Activate_Size(&driverlessSize)); + driverlessSize.face = &driverless; + Require(FT_Done_Size_nid_postfix(&invalidSize) == FT_Done_Size(&driverlessSize)); + Require(FT_Activate_Size_nid_postfix(&invalidSize) == FT_Activate_Size(&driverlessSize)); + static_assert(sizeof(GuestSizeMetrics) == 56 && sizeof(GuestSizeRec) == 88); + + int scenarios = 0; + int rendered = 0; + int failures = 0; + for (const int faceIndex : {0, -1}) { + for (const int count : {0, 1, 5, 41}) { + for (const int cleanup : {0, 1, 2}) { + GuestLibrary* library = nullptr; + FT_Library oracle = nullptr; + Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&oracle) == 0); + GuestFaceRec* face = nullptr; + FT_Face native = nullptr; + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), faceIndex, &face) == 0); + Require(FT_New_Memory_Face(oracle, font.data(), static_cast(font.size()), faceIndex, &native) == 0); + SizeEvents actual; + SizeEvents expected; + face->generic = {&actual, GuestMultipleSizeFaceFinalizer}; + native->generic = {&expected, NativeMultipleSizeFaceFinalizer}; + std::vector sizes; + std::vector originals; + auto remember = [&](GuestSizeRec* size, FT_Size original) { + Require(size->face == face && original->face == native); + actual.sizes.push_back(size); + expected.sizes.push_back(original); + size->generic = {&actual, GuestMultipleSizeFinalizer}; + original->generic = {&expected, NativeMultipleSizeFinalizer}; + sizes.push_back(size); + originals.push_back(original); + SizeMatches(size, original); + }; + if (native->size) remember(face->size, native->size); + Require(FT_New_Size_nid_postfix(face, nullptr) == FT_New_Size(native, nullptr)); + for (int index = 0; index < count; ++index) { + GuestSizeRec* before = face->size; + FT_Size previous = native->size; + GuestSizeRec* created = nullptr; + FT_Size original = nullptr; + Require(FT_New_Size_nid_postfix(face, &created) == FT_New_Size(native, &original)); + Require(created != nullptr && original != nullptr && face->size == before && native->size == previous); + remember(created, original); + } + if (count == 5) { + Require(FT_New_Size_nid_postfix(face, &face->size) == FT_New_Size(native, &native->size)); + remember(face->size, native->size); + } + for (std::size_t index = 0; index < sizes.size(); ++index) { + Require(FT_Activate_Size_nid_postfix(sizes[index]) == 0 && FT_Activate_Size(originals[index]) == 0); + Require(face->size == sizes[index] && native->size == originals[index]); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 12 + index) == FT_Set_Pixel_Sizes(native, 0, 12 + index)); + SizeMatches(sizes[index], originals[index]); + } + for (std::size_t turn = 0; turn < sizes.size() * 3; ++turn) { + const std::size_t index = (turn * 17 + 3) % sizes.size(); + Require(FT_Activate_Size_nid_postfix(sizes[index]) == FT_Activate_Size(originals[index])); + Require(face->size == sizes[index]); + for (std::size_t other = 0; other < sizes.size(); ++other) SizeMatches(sizes[other], originals[other]); + if (!native->glyph) continue; + Require(FT_Load_Char_nid_postfix(face, 'A', FT_LOAD_NO_HINTING) == FT_Load_Char(native, 'A', FT_LOAD_NO_HINTING)); + for (unsigned point = 0; point < face->glyph->outline.n_points; ++point) face->glyph->outline.points[point].x += 64; + FT_Outline_Translate(&native->glyph->outline, 64, 0); + Require(FT_Activate_Size_nid_postfix(sizes[index]) == FT_Activate_Size(originals[index])); + Require(FT_Render_Glyph_nid_postfix(face->glyph, FT_RENDER_MODE_NORMAL) == FT_Render_Glyph(native->glyph, FT_RENDER_MODE_NORMAL)); + SlotMatches(face->glyph, native->glyph); + ++rendered; + } + if (cleanup == 0) { + while (!sizes.empty()) { + const std::size_t index = sizes.size() % 2 == 0 ? 0 : sizes.size() - 1; + const int removed = actual.Index(sizes[index]); + Require(FT_Done_Size_nid_postfix(sizes[index]) == FT_Done_Size(originals[index])); + actual.sizes[removed] = nullptr; + expected.sizes[removed] = nullptr; + sizes.erase(sizes.begin() + index); + originals.erase(originals.begin() + index); + Require(actual.Index(face->size) == expected.Index(native->size) && actual.calls == expected.calls); + } + Require(face->size == nullptr && native->size == nullptr); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 24) == FT_Set_Pixel_Sizes(native, 0, 24)); + Require(FT_New_Size_nid_postfix(face, &face->size) == FT_New_Size(native, &native->size)); + Require(face->size != nullptr && native->size != nullptr); + remember(face->size, native->size); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 24) == FT_Set_Pixel_Sizes(native, 0, 24)); + for (const bool alias : {false, true}) { + for (const int allocation : {0, 1, 2}) { + auto* owner = static_cast(face->internal); + FT_Memory memory = owner->host->memory; + SizeOriginalAlloc = memory->alloc; + SizeAllocationsBeforeFailure = allocation; + memory->alloc = SizeFailingAlloc; + GuestSizeRec* output = &sentinel; + const int error = FT_New_Size_nid_postfix(face, alias ? &face->size : &output); + memory->alloc = SizeOriginalAlloc; + Require(error == FT_Err_Out_Of_Memory && owner->sizes.size() == 1); + FT_Memory nativeMemory = native->memory; + SizeOriginalAlloc = nativeMemory->alloc; + SizeAllocationsBeforeFailure = allocation; + nativeMemory->alloc = SizeFailingAlloc; + FT_Size nativeOutput = reinterpret_cast(&sentinel); + const int nativeError = FT_New_Size(native, alias ? &native->size : &nativeOutput); + nativeMemory->alloc = SizeOriginalAlloc; + Require(error == nativeError); + Require(alias ? face->size == nullptr && native->size == nullptr : output == nullptr && nativeOutput == nullptr); + Require(actual.Index(face->size) == expected.Index(native->size)); + Require(actual.calls == expected.calls); + ++failures; + } + } + } + if (cleanup == 1) { + Require(FT_Reference_Face_nid_postfix(face) == 0 && FT_Reference_Face(native) == 0); + Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); + Require(actual.calls.empty() && expected.calls.empty()); + } + if (cleanup == 2 && count == 5) { + Require(sizes.size() > 2); + Require(FT_Activate_Size_nid_postfix(sizes[1]) == FT_Activate_Size(originals[1])); + Require(face->size == sizes[1] && native->size == originals[1]); + } + if (cleanup != 2) Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(oracle) == 0); + Require(actual.calls == expected.calls && !actual.calls.empty() && actual.calls.back()[0] == 100); + if (cleanup == 2 && count == 5) { + Require(actual.calls.size() > 3 && actual.calls[0][1] == 1 && actual.calls[1][0] == 1); + for (std::size_t index = 2; index + 1 < actual.calls.size(); ++index) Require(actual.calls[index][1] == 1); + } + ++scenarios; + } + } + } + std::printf("FreeType size objects: %d lifecycles, %d rendered glyphs, %d allocation failures compared with native\n", scenarios, rendered, failures); +} + int main() { + SizeObjects(SquareGlyphFont('A', 700)); + { + const auto referenceFont = SquareGlyphFont('A', 700); + GuestLibrary* referenceLibrary = nullptr; + FT_Library oracle = nullptr; + Require(FT_Init_FreeType_nid_postfix(&referenceLibrary) == 0 && FT_Init_FreeType(&oracle) == 0); + auto* owners = referenceLibrary; + Require(FT_Reference_Face_nid_postfix(nullptr) == FT_Reference_Face(nullptr)); + int references = 0; + int retained = 0; + int finalized = 0; + for (const unsigned depth : {1u, 2u, 3u, 17u}) { + GuestFaceRec* shared = nullptr; + FT_Face native = nullptr; + Require(FT_New_Memory_Face_nid_postfix(referenceLibrary, referenceFont.data(), static_cast(referenceFont.size()), 0, &shared) == 0); + Require(FT_New_Memory_Face(oracle, referenceFont.data(), static_cast(referenceFont.size()), 0, &native) == 0); + Finalizers guestEvents; + Finalizers nativeEvents; + guestEvents.reenter = nativeEvents.reenter = depth == 3; + SetFinalizers(shared, guestEvents); + SetFinalizers(native, nativeEvents); + for (unsigned index = 1; index < depth; ++index) { + Require(FT_Reference_Face_nid_postfix(shared) == FT_Reference_Face(native)); + ++references; + } + for (unsigned index = 1; index < depth; ++index) { + Require(FT_Done_Face(native) == 0 && FT_Done_Face_nid_postfix(shared) == 0); + Require(owners->faces.size() == 1 && guestEvents.calls.empty() && nativeEvents.calls.empty()); + Require(FT_Get_Char_Index_nid_postfix(shared, 'A') == FT_Get_Char_Index(native, 'A')); + Require(FT_Set_Pixel_Sizes_nid_postfix(shared, 0, 16 + index) == 0 && FT_Set_Pixel_Sizes(native, 0, 16 + index) == 0); + Require(FT_Load_Char_nid_postfix(shared, 'A', FT_LOAD_RENDER) == 0 && FT_Load_Char(native, 'A', FT_LOAD_RENDER) == 0); + SlotMatches(shared->glyph, native->glyph); + ++retained; + } + Require(FT_Done_Face(native) == 0 && FT_Done_Face_nid_postfix(shared) == 0); + if (guestEvents.reenter) { + references += 2; + ++retained; + } + Require(owners->faces.empty() && guestEvents.calls == nativeEvents.calls && guestEvents.calls == std::vector({1, 2, 3})); + ++finalized; + } + GuestFaceRec* plain = nullptr; + FT_Face nativePlain = nullptr; + Require(FT_New_Memory_Face_nid_postfix(referenceLibrary, referenceFont.data(), static_cast(referenceFont.size()), 0, &plain) == 0); + Require(FT_New_Memory_Face(oracle, referenceFont.data(), static_cast(referenceFont.size()), 0, &nativePlain) == 0); + Require(FT_Reference_Face_nid_postfix(plain) == FT_Reference_Face(nativePlain)); + ++references; + Require(FT_Done_Face(nativePlain) == 0 && FT_Done_Face_nid_postfix(plain) == 0 && owners->faces.size() == 1); + ++retained; + Require(FT_Done_Face(nativePlain) == 0 && FT_Done_Face_nid_postfix(plain) == 0 && owners->faces.empty()); + ++finalized; + GuestFaceRec* probe = nullptr; + FT_Face nativeProbe = nullptr; + Require(FT_New_Memory_Face_nid_postfix(referenceLibrary, referenceFont.data(), static_cast(referenceFont.size()), -1, &probe) == 0); + Require(FT_New_Memory_Face(oracle, referenceFont.data(), static_cast(referenceFont.size()), -1, &nativeProbe) == 0); + Finalizers probeEvents; + Finalizers nativeProbeEvents; + probe->generic = {&probeEvents, GuestFaceFinalizer}; + nativeProbe->generic = {&nativeProbeEvents, NativeFaceFinalizer}; + Require(probe->glyph == nullptr && probe->size == nullptr); + Require(FT_Reference_Face_nid_postfix(probe) == FT_Reference_Face(nativeProbe)); + ++references; + Require(FT_Done_Face(nativeProbe) == 0 && FT_Done_Face_nid_postfix(probe) == 0 && owners->faces.size() == 1); + ++retained; + Require(probeEvents.calls.empty() && nativeProbeEvents.calls.empty()); + Require(FT_Done_Face(nativeProbe) == 0 && FT_Done_Face_nid_postfix(probe) == 0); + Require(owners->faces.empty() && probeEvents.calls == nativeProbeEvents.calls && probeEvents.calls == std::vector({3})); + ++finalized; + std::array shutdownEvents; + std::array nativeShutdownEvents; + std::array shutdown{}; + std::array nativeShutdown{}; + for (unsigned index = 0; index < shutdown.size(); ++index) { + Require(FT_New_Memory_Face_nid_postfix(referenceLibrary, referenceFont.data(), static_cast(referenceFont.size()), 0, &shutdown[index]) == 0); + Require(FT_New_Memory_Face(oracle, referenceFont.data(), static_cast(referenceFont.size()), 0, &nativeShutdown[index]) == 0); + SetFinalizers(shutdown[index], shutdownEvents[index]); + SetFinalizers(nativeShutdown[index], nativeShutdownEvents[index]); + for (unsigned reference = 0; reference < 3 - index; ++reference) { + Require(FT_Reference_Face_nid_postfix(shutdown[index]) == FT_Reference_Face(nativeShutdown[index])); + ++references; + } + Require(FT_Done_Face(nativeShutdown[index]) == 0 && FT_Done_Face_nid_postfix(shutdown[index]) == 0); + Require(owners->faces.size() == index + 1 && shutdownEvents[index].calls.empty()); + ++retained; + } + Require(FT_Done_FreeType(oracle) == 0 && FT_Done_FreeType_nid_postfix(referenceLibrary) == 0); + for (unsigned index = 0; index < shutdown.size(); ++index) { + Require(shutdownEvents[index].calls == nativeShutdownEvents[index].calls && shutdownEvents[index].calls == std::vector({1, 2, 3})); + ++finalized; + } + Require(references == 28 && retained == 24 && finalized == 8); + std::printf("FreeType face references: %d retains, %d non-final releases, %d finalized faces compared with native\n", references, retained, finalized); + } const auto root = std::filesystem::temp_directory_path() / ("anyps5_guest_freetype-" + std::to_string(std::random_device{}())); std::filesystem::create_directories(root / "app0"); + CheckGasp(); + Trigonometry(); + OutlineDecomposition(); const std::vector font = SquareGlyphFont('A', 700); + CheckOutlineCopies(); { std::ofstream file(root / "app0" / "font.ttf", std::ios::binary); file.write(reinterpret_cast(font.data()), static_cast(font.size())); } const auto previous = std::filesystem::current_path(); + CheckCidMetadata(); std::filesystem::current_path(root); + CheckOutlineOrientation(); FT_Library hostLibrary = nullptr; Require(FT_Init_FreeType(&hostLibrary) == 0); @@ -161,7 +1603,7 @@ int main() { Require(secondFace != 0); Require(FT_Init_FreeType_nid_postfix(nullptr) == FT_Err_Invalid_Argument); - void* library = nullptr; + FreeType::GuestLibrary* library = nullptr; Require(FT_Init_FreeType_nid_postfix(&library) == 0 && library != nullptr); Require(FT_Done_FreeType_nid_postfix(nullptr) == FT_Err_Invalid_Library_Handle); Require(FT_Done_Face_nid_postfix(nullptr) == FT_Err_Invalid_Face_Handle); @@ -177,8 +1619,23 @@ int main() { GuestFaceRec* masked = nullptr; Require(FT_New_Face_nid_postfix(library, "/app0/font.ttf", 0x180000000ll, &masked) == 0 && masked != nullptr && masked->face_index == 0); Require(FT_New_Face_nid_postfix(library, "/app0/font.ttf", 0, &face) == 0 && face != nullptr && face != masked); + { + FT_Face nativeFile = nullptr; + Require(FT_New_Face(hostLibrary, "app0/font.ttf", 0, &nativeFile) == 0); + const auto* bytes = FaceOf(face)->bytes.data(); + Require(FaceOf(face)->bytes.size() == font.size() && std::memcmp(bytes, font.data(), font.size()) == 0); + Require(FT_Reference_Face_nid_postfix(face) == FT_Reference_Face(nativeFile)); + Require(FT_Done_Face(nativeFile) == 0 && FT_Done_Face_nid_postfix(face) == 0); + Require(static_cast(library)->faces.size() == 2); + Require(FaceOf(face)->bytes.data() == bytes && std::memcmp(bytes, font.data(), font.size()) == 0); + Require(FT_Get_Char_Index_nid_postfix(face, 'A') == FT_Get_Char_Index(nativeFile, 'A')); + Require(FT_Done_Face(nativeFile) == 0); + std::puts("File-backed face reference: owned font bytes survive a non-final release"); + } FaceMatches(face, host, library); + CheckStrikeSelection(library, hostLibrary, face, host); + const auto fontSize = static_cast(font.size()); GuestFaceRec* memoryFace = nullptr; Require(FT_New_Memory_Face_nid_postfix(library, nullptr, fontSize, 0, &memoryFace) == FT_Err_Invalid_Argument); @@ -191,6 +1648,8 @@ int main() { Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, -1, &probe) == 0 && probe != nullptr); Require(probe->num_faces == nativeProbe->num_faces && probe->face_index == nativeProbe->face_index); Require((probe->glyph == nullptr) == (nativeProbe->glyph == nullptr) && (probe->size == nullptr) == (nativeProbe->size == nullptr)); + Require(FT_Set_Char_Size_nid_postfix(probe, std::numeric_limits::max(), 0, 0, 0) == + FT_Set_Char_Size(nativeProbe, 64, 64, 72, 72)); Finalizers nativeProbeEvents; Finalizers guestProbeEvents; nativeProbe->generic = {&nativeProbeEvents, NativeFaceFinalizer}; @@ -234,6 +1693,75 @@ int main() { Require(guestMetrics.x_ppem == 100 && guestMetrics.y_ppem == 100 && guestMetrics.x_scale == metrics.x_scale && guestMetrics.y_scale == metrics.y_scale); Require(guestMetrics.ascender == metrics.ascender && guestMetrics.descender == metrics.descender && guestMetrics.height == metrics.height && guestMetrics.max_advance == metrics.max_advance); + GuestVector kerning{123, 456}; + Require(FT_Get_Kerning_nid_postfix(nullptr, 1, 1, FT_KERNING_DEFAULT, &kerning) == FT_Err_Invalid_Face_Handle); + Require(kerning.x == 123 && kerning.y == 456); + Require(FT_Get_Kerning_nid_postfix(nullptr, 1, 1, FT_KERNING_DEFAULT, nullptr) == FT_Err_Invalid_Face_Handle); + Require(FT_Get_Kerning_nid_postfix(face, 1, 1, FT_KERNING_DEFAULT, nullptr) == FT_Err_Invalid_Argument); + Require(FT_Get_Kerning_nid_postfix(face, 1, 1, FT_KERNING_DEFAULT, &kerning) == 0); + Require(kerning.x == 0 && kerning.y == 0); + + auto kernFont = font; + const std::uint32_t kernOffset = static_cast(kernFont.size()) + 16; + kernFont[5] = 8; + kernFont[7] = 128; + kernFont[9] = 3; + kernFont[11] = 0; + for (std::size_t entry = 12; entry < 12 + 7 * 16; entry += 16) { + const std::uint32_t offset = (static_cast(kernFont[entry + 8]) << 24) | + (static_cast(kernFont[entry + 9]) << 16) | + (static_cast(kernFont[entry + 10]) << 8) | kernFont[entry + 11]; + std::vector adjusted; + Put32(adjusted, offset + 16); + std::memcpy(kernFont.data() + entry + 8, adjusted.data(), 4); + } + std::vector kernEntry{'k', 'e', 'r', 'n'}; + Put32(kernEntry, 0); + Put32(kernEntry, kernOffset); + Put32(kernEntry, 24); + kernFont.insert(kernFont.begin() + 12 + 5 * 16, kernEntry.begin(), kernEntry.end()); + PutAll16(kernFont, {0, 1, 0, 20, 1, 1, 6, 0, 0, 1, 1, -73}); + { + std::ofstream file(root / "app0" / "kern.ttf", std::ios::binary); + file.write(reinterpret_cast(kernFont.data()), static_cast(kernFont.size())); + } + FT_Face kernHost = nullptr; + GuestFaceRec* kernFace = nullptr; + Require(FT_New_Memory_Face(hostLibrary, kernFont.data(), static_cast(kernFont.size()), 0, &kernHost) == 0); + Require(FT_New_Face_nid_postfix(library, "/app0/kern.ttf", 0, &kernFace) == 0); + Require(FT_HAS_KERNING(kernHost)); + const unsigned modes[] = {FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, FT_KERNING_UNSCALED, 99}; + for (const unsigned size : {100u, 12u}) { + Require(FT_Set_Pixel_Sizes_nid_postfix(kernFace, 0, size) == 0 && FT_Set_Pixel_Sizes(kernHost, 0, size) == 0); + for (const unsigned mode : modes) { + FT_Vector expected{}; + Require(FT_Get_Kerning(kernHost, 1, 1, mode, &expected) == 0); + kerning = {123, 456}; + Require(FT_Get_Kerning_nid_postfix(kernFace, 1, 1, mode, &kerning) == 0); + Require(kerning.x == expected.x && kerning.y == expected.y && kerning.y == 0); + if (mode == FT_KERNING_UNSCALED) Require(kerning.x == -73); + if (size == 100 && mode == FT_KERNING_DEFAULT) Require(kerning.x == -448); + if (size == 100 && mode == FT_KERNING_UNFITTED) Require(kerning.x == -467); + for (const unsigned leftGlyph : {0x10001u, 0x7FFF0001u}) { + kerning = {123, 456}; + Require(FT_Get_Kerning_nid_postfix(kernFace, leftGlyph, 1, mode, &kerning) == 0); + Require(kerning.x == 0 && kerning.y == 0); + } + kerning = {123, 456}; + Require(FT_Get_Kerning_nid_postfix(kernFace, 1, 0x10001u, mode, &kerning) == 0); + Require(kerning.x == expected.x && kerning.y == expected.y); + } + } + for (const unsigned glyph : {0u, 2u, 0xFFFFFFFFu}) { + kerning = {123, 456}; + Require(FT_Get_Kerning_nid_postfix(kernFace, glyph, 1, FT_KERNING_UNSCALED, &kerning) == 0); + Require(kerning.x == 0 && kerning.y == 0); + kerning = {123, 456}; + Require(FT_Get_Kerning_nid_postfix(kernFace, 1, glyph, FT_KERNING_UNSCALED, &kerning) == 0); + Require(kerning.x == 0 && kerning.y == 0); + } + FT_Done_Face(kernHost); + Require(FT_Get_Char_Index_nid_postfix(face, 'A') == 1 && FT_Get_Char_Index_nid_postfix(face, 'B') == 0); Require(FT_Get_Char_Index_nid_postfix(face, 0x100000041ull) == 1); Require(FT_Get_Char_Index_nid_postfix(nullptr, 'A') == 0); @@ -302,6 +1830,64 @@ int main() { slot->outline.points = original; Require(FT_Render_Glyph_nid_postfix(slot, FT_RENDER_MODE_NORMAL) == 0 && slot->bitmap_left == 10); + { + struct Request { + std::int64_t width; + std::int64_t height; + std::uint32_t horizontal; + std::uint32_t vertical; + }; + const std::array requests{ + Request{0, 0, 0, 0}, Request{0, 12 * 64, 0, 0}, Request{12 * 64, 0, 0, 0}, + Request{12 * 64, 18 * 64, 96, 144}, Request{12 * 64 + 32, 12 * 64 + 32, 72, 72}, + Request{1, 63, 72, 72}, Request{-1, -100, 0, 0}, Request{0, -100, 96, 0}, + Request{12 * 64, 18 * 64, 0, 144}, Request{12 * 64, 18 * 64, 96, 0}, + Request{65535ll * 64, 64, 72, 72}, Request{65536ll * 64, 64, 72, 72}, + Request{64, 65536ll * 64, 72, 72}, + Request{18 * 64, 12 * 64, 72, 72} + }; + std::size_t successes = 0; + std::size_t failures = 0; + for (const auto& request : requests) { + std::array previousSlot{}; + std::memcpy(previousSlot.data(), face->glyph, previousSlot.size()); + const auto expected = FT_Set_Char_Size(host, static_cast(request.width), static_cast(request.height), + request.horizontal, request.vertical); + Require(FT_Set_Char_Size_nid_postfix(face, request.width, request.height, request.horizontal, request.vertical) == expected); + Require(std::memcmp(previousSlot.data(), face->glyph, previousSlot.size()) == 0); + expected == 0 ? ++successes : ++failures; + SizeMatches(face, host); + Require(FT_Load_Char_nid_postfix(face, 'A', FT_LOAD_DEFAULT) == FT_Load_Char(host, 'A', FT_LOAD_DEFAULT)); + SlotMatches(face->glyph, host->glyph); + } + Require(successes != 0 && failures != 0); + for (const auto value : std::array{std::numeric_limits::min(), -0x80000001ll}) { + Require(FT_Set_Char_Size_nid_postfix(face, value, 0, 0, 0) == FT_Set_Char_Size(host, 64, 64, 0, 0)); + SizeMatches(face, host); + Require(FT_Set_Char_Size_nid_postfix(face, 0, value, 0, 0) == 0); + SizeMatches(face, host); + } + for (const auto value : {std::numeric_limits::min(), std::numeric_limits::max()}) { + Require(FT_Set_Char_Size_nid_postfix(nullptr, value, value, 0, 0) == FT_Err_Invalid_Face_Handle); + } + if constexpr (sizeof(FT_F26Dot6) < sizeof(std::int64_t)) { + for (const auto value : std::array{0x7FFFFFFFll, 0x80000000ll, 0x100000040ll, std::numeric_limits::max()}) { + for (const bool width : {false, true}) { + Require(ThrowsWith([&] { FT_Set_Char_Size_nid_postfix(face, width ? value : 64, width ? 64 : value, 72, 72); }, + "FT_Set_Char_Size: a character size or resolution outside the host arithmetic range is not implemented")); + SizeMatches(face, host); + } + } + Require(ThrowsWith([&] { FT_Set_Char_Size_nid_postfix(face, 64, 64, std::numeric_limits::max(), 72); }, + "FT_Set_Char_Size: a character size or resolution outside the host arithmetic range is not implemented")); + SizeMatches(face, host); + } + Require(FT_Set_Char_Size_nid_postfix(memoryFace, 0, 48 * 64, 0, 144) == 0); + Require(memoryFace->size->metrics.y_ppem == 96 && face->size->metrics.y_ppem == 1); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 100) == 0 && FT_Set_Pixel_Sizes(host, 0, 100) == 0); + std::printf("FT_Set_Char_Size: %zu native request comparisons, %zu successes, %zu errors\n", requests.size(), successes, failures); + } + Finalizers nativeDone; Finalizers guestDone; SetFinalizers(host, nativeDone); @@ -325,7 +1911,7 @@ int main() { SetFinalizers(memoryFace, guestSecondEvents); Require(FT_Done_FreeType(hostLibrary) == 0); - void* independent = nullptr; + FreeType::GuestLibrary* independent = nullptr; GuestFaceRec* independentFace = nullptr; Require(FT_Init_FreeType_nid_postfix(&independent) == 0); Require(FT_New_Memory_Face_nid_postfix(independent, font.data(), fontSize, 0, &independentFace) == 0); @@ -337,8 +1923,9 @@ int main() { Require(FT_Get_Char_Index_nid_postfix(independentFace, 'A') == 1); Require(FT_Done_FreeType_nid_postfix(independent) == 0 && independentEvents.calls == nativeDone.calls); - void* empty = nullptr; + FreeType::GuestLibrary* empty = nullptr; Require(FT_Init_FreeType_nid_postfix(&empty) == 0 && FT_Done_FreeType_nid_postfix(empty) == 0); + CheckTransforms(font); std::filesystem::current_path(previous); std::error_code ignored; std::filesystem::remove_all(root, ignored); diff --git a/core/libs/tests/GuestFreeTypeBitmap.cpp b/core/libs/tests/GuestFreeTypeBitmap.cpp new file mode 100644 index 0000000000..6a05b55c58 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeBitmap.cpp @@ -0,0 +1,290 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "FontFixture.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_BITMAP_H +#include FT_MODULE_H + +#include +#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 bitmap check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static GuestBitmap Guest(const FT_Bitmap& bitmap) { + return {bitmap.rows, bitmap.width, bitmap.pitch, bitmap.buffer, bitmap.num_grays, bitmap.pixel_mode, bitmap.palette_mode, bitmap.palette}; +} + +static void Matches(const GuestBitmap& guest, const FT_Bitmap& native, bool comparePadding) { + Require(guest.rows == native.rows && guest.width == native.width && guest.pitch == native.pitch); + Require(guest.num_grays == native.num_grays && guest.pixel_mode == native.pixel_mode && guest.palette_mode == native.palette_mode); + Require(guest.palette == native.palette && (guest.buffer == nullptr) == (native.buffer == nullptr)); + if (!guest.buffer) return; + const auto pitch = static_cast(native.pitch < 0 ? -native.pitch : native.pitch); + const std::size_t width = comparePadding ? pitch : native.width; + for (std::uint32_t row = 0; row < native.rows; ++row) { + Require(std::memcmp(guest.buffer + row * pitch, native.buffer + row * pitch, width) == 0); + } +} + +static void TestFormat(GuestLibrary* library, FT_Library oracle, unsigned char mode, int bits) { + constexpr std::uint32_t width = 10; + constexpr std::uint32_t rows = 3; + const int stride = static_cast((width * bits + 7) / 8) + 2; + std::vector data(rows * static_cast(stride)); + for (std::size_t index = 0; index < data.size(); ++index) data[index] = static_cast(index * 37 + 11); + if (mode == FT_PIXEL_MODE_BGRA) { + for (std::uint32_t row = 0; row < rows; ++row) { + for (std::uint32_t col = 0; col < width; ++col) { + unsigned char* pixel = data.data() + row * stride + col * 4; + pixel[0] = static_cast(col * 7); + pixel[1] = static_cast(row * 23); + pixel[2] = static_cast(col * 11); + pixel[3] = col == 0 ? 0 : static_cast(128 + row * 63); + if (col == 0) pixel[0] = pixel[1] = pixel[2] = 0; + } + } + } + const auto original = data; + int palette = 17; + for (const int sign : {-1, 1}) { + const FT_Bitmap source{rows, width, sign * stride, data.data(), static_cast(bits < 8 ? 1 << bits : 256), mode, 7, &palette}; + GuestBitmap guestSource = Guest(source); + for (const int flow : {-1, 0, 1}) { + FT_Bitmap native{}; + native.pitch = flow; + GuestBitmap target{}; + target.pitch = flow; + Require(FT_Bitmap_Copy_nid_postfix(library, &guestSource, &target) == FT_Bitmap_Copy(oracle, &source, &native)); + Require(target.buffer != source.buffer && native.buffer != source.buffer && target.buffer != native.buffer); + Matches(target, native, true); + const GuestBitmap before = target; + Require(FT_Bitmap_Copy_nid_postfix(library, &target, &target) == 0); + Require(target.buffer == before.buffer && target.pitch == before.pitch); + Matches(target, native, true); + data[0] ^= 0xFF; + Require(target.buffer[flow != 0 && flow != sign ? (rows - 1) * stride : 0] == original[0]); + data[0] ^= 0xFF; + for (const int alignment : {0, 1, 2, 4, -4}) { + Require(FT_Bitmap_Convert_nid_postfix(library, &guestSource, &target, alignment) == FT_Bitmap_Convert(oracle, &source, &native, alignment)); + Matches(target, native, false); + Require(target.pixel_mode == FT_PIXEL_MODE_GRAY && target.rows == rows && target.width == width); + Require(target.pitch == (flow < 0 || (flow == 0 && sign < 0) ? -1 : 1) * (alignment == 4 || alignment == -4 ? 12 : 10)); + Require(data == original); + } + Require(FT_Bitmap_Done_nid_postfix(library, &target) == FT_Bitmap_Done(oracle, &native)); + Matches(target, native, true); + Require(target.rows == 0 && target.pitch == 0 && target.buffer == nullptr && target.palette == nullptr); + Require(FT_Bitmap_Done_nid_postfix(library, &target) == 0); + } + } +} + +struct Allocations { + bool fail = false; + std::unordered_set blocks; + + static void* Allocate(FT_Memory memory, long size) { + auto& state = *static_cast(memory->user); + if (state.fail) return nullptr; + void* block = std::malloc(static_cast(size)); + if (block) Require(state.blocks.insert(block).second); + return block; + } + + static void Free(FT_Memory memory, void* block) { + auto& state = *static_cast(memory->user); + Require(state.blocks.erase(block) == 1); + std::free(block); + } + + static void* Reallocate(FT_Memory, long, long, void*) { + return nullptr; + } +}; + +static void TestAllocationFailure() { + Allocations guestAllocations; + Allocations nativeAllocations; + FT_MemoryRec_ guestMemory{&guestAllocations, Allocations::Allocate, Allocations::Free, Allocations::Reallocate}; + FT_MemoryRec_ nativeMemory{&nativeAllocations, Allocations::Allocate, Allocations::Free, Allocations::Reallocate}; + GuestLibrary library{}; + FT_Library oracle = nullptr; + Require(FT_New_Library(&guestMemory, &library.host) == 0); + Require(FT_New_Library(&nativeMemory, &oracle) == 0); + const auto guestBase = guestAllocations.blocks.size(); + const auto nativeBase = nativeAllocations.blocks.size(); + unsigned char pixels[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + const FT_Bitmap source{2, 5, 5, pixels, 256, FT_PIXEL_MODE_GRAY, 0, nullptr}; + const GuestBitmap guestSource = Guest(source); + GuestBitmap target{}; + FT_Bitmap native{}; + for (const bool convert : {false, true}) { + Require(FT_Bitmap_Copy_nid_postfix(&library, &guestSource, &target) == 0); + Require(FT_Bitmap_Copy(oracle, &source, &native) == 0); + Require(guestAllocations.blocks.size() == guestBase + 1 && nativeAllocations.blocks.size() == nativeBase + 1); + guestAllocations.fail = nativeAllocations.fail = true; + const FT_Error expected = convert ? FT_Bitmap_Convert(oracle, &source, &native, 4) : FT_Bitmap_Copy(oracle, &source, &native); + const int error = convert ? FT_Bitmap_Convert_nid_postfix(&library, &guestSource, &target, 4) : FT_Bitmap_Copy_nid_postfix(&library, &guestSource, &target); + Require(expected == FT_Err_Out_Of_Memory && error == expected); + Matches(target, native, false); + Require(target.buffer == nullptr && guestAllocations.blocks.size() == guestBase && nativeAllocations.blocks.size() == nativeBase); + guestAllocations.fail = nativeAllocations.fail = false; + } + Require(FT_Bitmap_Convert_nid_postfix(&library, &guestSource, &target, 4) == 0); + Require(FT_Bitmap_Convert(oracle, &source, &native, 4) == 0); + Matches(target, native, false); + Require(FT_Bitmap_Done_nid_postfix(&library, &target) == 0 && FT_Bitmap_Done(oracle, &native) == 0); + Require(guestAllocations.blocks.size() == guestBase && nativeAllocations.blocks.size() == nativeBase); + Require(FT_Done_Library(library.host) == 0 && FT_Done_Library(oracle) == 0); + Require(guestAllocations.blocks.empty() && nativeAllocations.blocks.empty()); +} + +struct SlotFinalizer { + GuestFace* face; + GuestLibrary* library; + bool called = false; + + static void Run(void* object) { + auto* slot = static_cast(object); + auto& state = *static_cast(slot->generic.data); + Require(slot->face->glyph == nullptr); + Require(FT_Load_Glyph_nid_postfix(&state.face->rec, 0, FT_LOAD_DEFAULT) == FT_Err_Invalid_Face_Handle); + Require(FT_Bitmap_Done_nid_postfix(state.library, &state.face->slot.bitmap) == 0); + Require(slot->bitmap.buffer == nullptr && slot->bitmap.width == 0); + state.called = true; + } +}; + +static void TestGlyphSlot(GuestLibrary* library, FT_Library oracle) { + const auto bytes = FontFixture::BuildFont(1, {}); + GuestFace face; + face.library = library; + Require(FT_New_Memory_Face(library->host, bytes.data(), static_cast(bytes.size()), 0, &face.host) == 0); + FT_Face nativeFace = nullptr; + Require(FT_New_Memory_Face(oracle, bytes.data(), static_cast(bytes.size()), 0, &nativeFace) == 0); + SyncFace(face); + unsigned char pixels[]{7, 31, 65, 127, 255, 17, 61, 101, 189, 239}; + const FT_Bitmap source{2, 5, 5, pixels, 256, FT_PIXEL_MODE_GRAY, 0, nullptr}; + const GuestBitmap guestSource = Guest(source); + auto& target = face.slot.bitmap; + auto& native = nativeFace->glyph->bitmap; + Require(FT_Bitmap_Copy_nid_postfix(library, &guestSource, &target) == FT_Bitmap_Copy(oracle, &source, &native)); + Matches(target, native, true); + Require(face.host->glyph->bitmap.buffer == target.buffer && face.host->glyph->bitmap.width == 5); + GuestBitmap retained{}; + Require(FT_Bitmap_Copy_nid_postfix(library, &target, &retained) == 0); + Require(retained.buffer != target.buffer && std::memcmp(retained.buffer, target.buffer, 10) == 0); + Require(FT_Bitmap_Done_nid_postfix(library, &target) == FT_Bitmap_Done(oracle, &native)); + Require(face.host->glyph->bitmap.buffer == nullptr && face.host->glyph->bitmap.width == 0); + Require(FT_Bitmap_Convert_nid_postfix(library, &guestSource, &target, 4) == FT_Bitmap_Convert(oracle, &source, &native, 4)); + Matches(target, native, false); + Require(face.host->glyph->bitmap.buffer == target.buffer && face.host->glyph->bitmap.pitch == 8); + Require(FT_Bitmap_Done_nid_postfix(library, &target) == 0 && FT_Bitmap_Done(oracle, &native) == 0); + target.rows = 9; + target.width = 17; + face.host->glyph->bitmap.rows = 9; + face.host->glyph->bitmap.width = 17; + FT_Bitmap_New_nid_postfix(&target); + Require(target.rows == 0 && face.host->glyph->bitmap.rows == 0 && face.host->glyph->bitmap.width == 0); + Require(FT_Bitmap_Done_nid_postfix(library, &retained) == 0); + Require(FT_Bitmap_Copy_nid_postfix(library, &guestSource, &target) == 0); + SlotFinalizer finalizer{&face, library}; + face.host->glyph->generic = {&finalizer, SlotFinalizer::Run}; + Require(FT_Done_Face(face.host) == 0 && FT_Done_Face(nativeFace) == 0); + Require(finalizer.called); +} + +static void TestReusedStorage(GuestLibrary* library) { + const auto bytes = FontFixture::BuildFont(1, {}); + alignas(GuestFace) std::byte storage[sizeof(GuestFace)]{}; + auto* face = new (storage) GuestFace; + face->library = library; + Require(FT_New_Memory_Face(library->host, bytes.data(), static_cast(bytes.size()), 0, &face->host) == 0); + FT_Face native = face->host; + SyncFace(*face); + void* address = &face->slot.bitmap; + face->~GuestFace(); + auto* bitmap = new (address) GuestBitmap{}; + native->glyph->bitmap.width = 73; + FT_Bitmap_New_nid_postfix(bitmap); + Require(bitmap->width == 0 && native->glyph->bitmap.width == 73); + Require(FT_Done_Face(native) == 0); +} + +int main() { + GuestLibrary* library = nullptr; + FT_Library oracle = nullptr; + Require(FT_Init_FreeType_nid_postfix(&library) == 0 && library != nullptr); + Require(FT_Init_FreeType(&oracle) == 0); + + GuestBitmap empty{1, 2, 3, nullptr, 4, 5, 6, nullptr}; + FT_Bitmap_New_nid_postfix(&empty); + FT_Bitmap nativeEmpty{}; + Matches(empty, nativeEmpty, true); + Require(empty.rows == 0 && empty.width == 0 && empty.pitch == 0); + FT_Bitmap_New_nid_postfix(nullptr); + Require(FT_Bitmap_Copy_nid_postfix(nullptr, nullptr, nullptr) == FT_Err_Invalid_Library_Handle); + Require(FT_Bitmap_Convert_nid_postfix(nullptr, nullptr, nullptr, 1) == FT_Err_Invalid_Library_Handle); + Require(FT_Bitmap_Done_nid_postfix(nullptr, nullptr) == FT_Err_Invalid_Library_Handle); + Require(FT_Bitmap_Copy_nid_postfix(library, nullptr, &empty) == FT_Err_Invalid_Argument); + Require(FT_Bitmap_Copy_nid_postfix(library, &empty, nullptr) == FT_Err_Invalid_Argument); + Require(FT_Bitmap_Convert_nid_postfix(library, nullptr, &empty, 1) == FT_Err_Invalid_Argument); + Require(FT_Bitmap_Convert_nid_postfix(library, &empty, nullptr, 1) == FT_Err_Invalid_Argument); + Require(FT_Bitmap_Done_nid_postfix(library, nullptr) == FT_Err_Invalid_Argument); + Require(FT_Bitmap_Copy_nid_postfix(library, &empty, &empty) == 0); + + for (const auto [mode, bits] : {std::pair{FT_PIXEL_MODE_MONO, 1}, {FT_PIXEL_MODE_GRAY2, 2}, {FT_PIXEL_MODE_GRAY4, 4}, + {FT_PIXEL_MODE_GRAY, 8}, {FT_PIXEL_MODE_LCD, 8}, {FT_PIXEL_MODE_LCD_V, 8}, {FT_PIXEL_MODE_BGRA, 32}}) { + TestFormat(library, oracle, static_cast(mode), bits); + } + GuestBitmap target{}; + const GuestBitmap unsupported{0, 0, 0, nullptr, 0, FT_PIXEL_MODE_NONE, 0, nullptr}; + Require(FT_Bitmap_Copy_nid_postfix(library, &unsupported, &target) == 0); + Require(target.buffer == nullptr); + Require(FT_Bitmap_Convert_nid_postfix(library, &unsupported, &target, 1) == FT_Err_Invalid_Argument); + Require(target.buffer == nullptr && target.width == 0); + bool overlapThrew = false; + try { + FT_Bitmap_Convert_nid_postfix(library, &target, &target, 1); + } catch (const std::runtime_error&) { + overlapThrew = true; + } + Require(overlapThrew); + unsigned char sharedPixel = 17; + GuestBitmap shared{1, 1, 1, &sharedPixel, 256, FT_PIXEL_MODE_GRAY, 0, nullptr}; + GuestBitmap alias = shared; + for (const bool convert : {false, true}) { + overlapThrew = false; + try { + if (convert) FT_Bitmap_Convert_nid_postfix(library, &shared, &alias, 1); + else FT_Bitmap_Copy_nid_postfix(library, &shared, &alias); + } catch (const std::runtime_error&) { + overlapThrew = true; + } + Require(overlapThrew && sharedPixel == 17 && alias.buffer == &sharedPixel); + } + Require(FT_Bitmap_Done_nid_postfix(library, &target) == 0); + TestAllocationFailure(); + TestGlyphSlot(library, oracle); + TestReusedStorage(library); + Require(FT_Done_FreeType(library->host) == 0); + delete library; + Require(FT_Done_FreeType(oracle) == 0); +} diff --git a/core/libs/tests/GuestFreeTypeCharmap.cpp b/core/libs/tests/GuestFreeTypeCharmap.cpp index 27372b1526..cfa8f479b9 100644 --- a/core/libs/tests/GuestFreeTypeCharmap.cpp +++ b/core/libs/tests/GuestFreeTypeCharmap.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceFreeType/include/Api.hpp" #include "FreeTypeFontFixture.hpp" #include "prx/libSceFreeType/include/GuestFreeType.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -19,17 +20,6 @@ using namespace FreeType; -extern "C" { -int APS5_VABI FT_Init_FreeType_nid_postfix(void**); -int APS5_VABI FT_New_Face_nid_postfix(void*, const char*, std::int64_t, GuestFaceRec**); -std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(GuestFaceRec*, std::uint64_t); -int APS5_VABI FT_Load_Glyph_nid_postfix(GuestFaceRec*, std::uint32_t, std::int32_t); -int APS5_VABI FT_Select_Charmap_nid_postfix(GuestFaceRec*, std::int32_t); -int APS5_VABI FT_Set_Charmap_nid_postfix(GuestFaceRec*, GuestCharMapRec*); -std::uint64_t APS5_VABI FT_Get_First_Char_nid_postfix(GuestFaceRec*, std::uint32_t*); -std::uint64_t APS5_VABI FT_Get_Next_Char_nid_postfix(GuestFaceRec*, std::uint64_t, std::uint32_t*); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "FreeType charmap check failed at line %d\n", line); @@ -76,7 +66,7 @@ int main() { Require(FT_New_Memory_Face(native, font.data(), static_cast(font.size()), 0, &host) == 0); Require(FT_New_Memory_Face(native, font.data(), static_cast(font.size()), 0, &foreignHost) == 0); Require(FT_New_Memory_Face(native, zeroFont.data(), static_cast(zeroFont.size()), 0, &zeroHost) == 0); - void* library = nullptr; + FreeType::GuestLibrary* library = nullptr; GuestFaceRec* face = nullptr; GuestFaceRec* foreign = nullptr; GuestFaceRec* zero = nullptr; diff --git a/core/libs/tests/GuestFreeTypeCharmapInfo.cpp b/core/libs/tests/GuestFreeTypeCharmapInfo.cpp new file mode 100644 index 0000000000..33c47e7d1c --- /dev/null +++ b/core/libs/tests/GuestFreeTypeCharmapInfo.cpp @@ -0,0 +1,119 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "FontFixture.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_TRUETYPE_TABLES_H + +#include +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType charmap info check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static std::vector Font(std::uint16_t language4, std::uint32_t language12) { + auto cmap = FontFixture::Words({0, 2, 3, 1, 0, 20, 3, 10, 0, 52}); + const auto format4 = FontFixture::Words({4, 32, language4, 4, 4, 1, 0, 'A', 0xFFFF, 0, 'A', 0xFFFF, 1 - 'A', 1, 0, 0}); + const auto format12 = FontFixture::Words({12, 0, 0, 28, static_cast(language12 >> 16), static_cast(language12 & 0xFFFF), + 0, 1, 1, 0xF600, 1, 0xF600, 0, 2}); + cmap.insert(cmap.end(), format4.begin(), format4.end()); + cmap.insert(cmap.end(), format12.begin(), format12.end()); + return FontFixture::BuildFont(3, {{"cmap", cmap}}); +} + +struct FacePair { + std::vector bytes; + GuestFace guest; + FT_Face oracle = nullptr; + + FacePair(GuestLibrary& library, FT_Library nativeLibrary, std::vector font) : bytes(std::move(font)) { + guest.library = &library; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &oracle) == 0); + SyncFace(guest); + } + + ~FacePair() { + Require(FT_Done_Face(guest.host) == 0); + Require(FT_Done_Face(oracle) == 0); + } +}; + +static void Compare(FacePair& face, std::uint16_t language4, std::uint32_t language12) { + Require(face.guest.rec.num_charmaps == 2 && face.oracle->num_charmaps == 2); + const auto selected = face.guest.rec.charmap; + const auto nativeSelected = face.oracle->charmap; + for (int index = 0; index < face.guest.rec.num_charmaps; ++index) { + GuestCharMapRec* guest = face.guest.rec.charmaps[index]; + FT_CharMap native = face.oracle->charmaps[index]; + Require(FT_Get_Charmap_Index_nid_postfix(guest) == index); + Require(FT_Get_Charmap_Index_nid_postfix(guest) == FT_Get_Charmap_Index(native)); + const auto format = FT_Get_CMap_Format_nid_postfix(guest); + Require(format == FT_Get_CMap_Format(native) && (format == 4 || format == 12)); + const auto language = FT_Get_CMap_Language_ID_nid_postfix(guest); + Require(language == FT_Get_CMap_Language_ID(native)); + Require(language == (format == 4 ? language4 : language12)); + } + Require(face.guest.rec.charmap == selected && face.oracle->charmap == nativeSelected); +} + +template static bool Unsupported(TCall call) { + try { + call(); + } catch (const std::runtime_error& error) { + return std::string_view(error.what()).starts_with("FreeType: a charmap outside"); + } + return false; +} + +int main() { + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0); + Require(FT_Init_FreeType(&nativeLibrary) == 0); + { + FacePair first(library, nativeLibrary, Font(0x0411, 0x80000001u)); + FacePair second(library, nativeLibrary, Font(0x0412, 0xFFFFFFFFu)); + Compare(first, 0x0411, 0x80000001u); + Compare(second, 0x0412, 0xFFFFFFFFu); + for (int index = 0; index < first.guest.rec.num_charmaps; ++index) { + Require(FT_Set_Charmap_nid_postfix(&first.guest.rec, first.guest.rec.charmaps[index]) == 0); + Require(FT_Set_Charmap(first.oracle, first.oracle->charmaps[index]) == 0); + Compare(first, 0x0411, 0x80000001u); + Compare(second, 0x0412, 0xFFFFFFFFu); + } + GuestCharMapRec copied = *first.guest.rec.charmaps[0]; + Require(Unsupported([&] { FT_Get_Charmap_Index_nid_postfix(&copied); })); + Require(Unsupported([&] { FT_Get_CMap_Language_ID_nid_postfix(&copied); })); + Require(Unsupported([&] { FT_Get_CMap_Format_nid_postfix(&copied); })); + FacePair bitmap(library, nativeLibrary, FontFixture::BitmapFont()); + Require(!FT_IS_SFNT(bitmap.oracle) && bitmap.guest.rec.num_charmaps == 1); + GuestCharMapRec* bitmapMap = bitmap.guest.rec.charmaps[0]; + Require(FT_Get_Charmap_Index_nid_postfix(bitmapMap) == FT_Get_Charmap_Index(bitmap.oracle->charmaps[0])); + Require(FT_Get_CMap_Language_ID_nid_postfix(bitmapMap) == FT_Get_CMap_Language_ID(bitmap.oracle->charmaps[0])); + Require(FT_Get_CMap_Language_ID_nid_postfix(bitmapMap) == 0); + Require(FT_Get_CMap_Format_nid_postfix(bitmapMap) == FT_Get_CMap_Format(bitmap.oracle->charmaps[0])); + Require(FT_Get_CMap_Format_nid_postfix(bitmapMap) == -1); + GuestCharMapRec empty{}; + FT_CharMapRec nativeEmpty{}; + for (GuestCharMapRec* guest : {static_cast(nullptr), &empty}) { + Require(FT_Get_Charmap_Index_nid_postfix(guest) == FT_Get_Charmap_Index(guest ? &nativeEmpty : nullptr)); + Require(FT_Get_CMap_Language_ID_nid_postfix(guest) == FT_Get_CMap_Language_ID(guest ? &nativeEmpty : nullptr)); + Require(FT_Get_CMap_Format_nid_postfix(guest) == FT_Get_CMap_Format(guest ? &nativeEmpty : nullptr)); + } + } + Require(FT_Done_FreeType(library.host) == 0); + Require(FT_Done_FreeType(nativeLibrary) == 0); +} \ No newline at end of file diff --git a/core/libs/tests/GuestFreeTypeFaceInfo.cpp b/core/libs/tests/GuestFreeTypeFaceInfo.cpp new file mode 100644 index 0000000000..65ca766d9b --- /dev/null +++ b/core/libs/tests/GuestFreeTypeFaceInfo.cpp @@ -0,0 +1,85 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "FontFixture.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_FONT_FORMATS_H + +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType face info check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static std::vector Font(std::uint16_t flags) { + std::vector os2(78, 0); + os2[8] = static_cast(flags >> 8); + os2[9] = static_cast(flags); + return FontFixture::BuildFont(3, {{"OS/2", os2}}); +} + +struct FacePair { + std::vector bytes; + GuestFace guest; + FT_Face oracle = nullptr; + + FacePair(GuestLibrary& library, FT_Library nativeLibrary, std::vector font) : bytes(std::move(font)) { + guest.library = &library; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &oracle) == 0); + SyncFace(guest); + } + + ~FacePair() { + Require(FT_Done_Face(guest.host) == 0); + Require(FT_Done_Face(oracle) == 0); + } +}; + +static const char* Query(FacePair& face, const char* format, std::uint16_t flags) { + const char* actual = FT_Get_X11_Font_Format_nid_postfix(&face.guest.rec); + const char* expected = FT_Get_X11_Font_Format(face.oracle); + Require(actual && expected && std::strcmp(actual, expected) == 0 && std::strcmp(actual, format) == 0); + Require(actual == FT_Get_X11_Font_Format(face.guest.host)); + Require(FT_Get_FSType_Flags_nid_postfix(&face.guest.rec) == FT_Get_FSType_Flags(face.oracle)); + Require(FT_Get_FSType_Flags_nid_postfix(&face.guest.rec) == flags); + return actual; +} + +int main() { + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0 && FT_Init_FreeType(&nativeLibrary) == 0); + std::vector retained; + const char* bitmapFormat = nullptr; + { + FacePair bitmap(library, nativeLibrary, FontFixture::BitmapFont()); + bitmapFormat = Query(bitmap, "BDF", 0); + for (const std::uint16_t flags : {0, 2, 4, 8, 0x0100, 0x0200, 0x0308, 0x8000, 0xFFFF}) { + FacePair face(library, nativeLibrary, Font(flags)); + retained.push_back(Query(face, "TrueType", flags)); + Require(Query(bitmap, "BDF", 0) == bitmapFormat); + } + FacePair missing(library, nativeLibrary, FontFixture::BuildFont(3, {})); + Query(missing, "TrueType", 0); + GuestFaceRec empty{}; + for (GuestFaceRec* face : {static_cast(nullptr), &empty}) { + Require(FT_Get_X11_Font_Format_nid_postfix(face) == FT_Get_X11_Font_Format(nullptr)); + Require(FT_Get_FSType_Flags_nid_postfix(face) == FT_Get_FSType_Flags(nullptr)); + } + } + Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(nativeLibrary) == 0); + for (const char* format : retained) Require(std::strcmp(format, "TrueType") == 0); + Require(std::strcmp(bitmapFormat, "BDF") == 0); +} diff --git a/core/libs/tests/GuestFreeTypeList.cpp b/core/libs/tests/GuestFreeTypeList.cpp new file mode 100644 index 0000000000..f853e00bbb --- /dev/null +++ b/core/libs/tests/GuestFreeTypeList.cpp @@ -0,0 +1,272 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestList.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include FT_LIST_H + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType list check failed at line %d\n", line); + std::abort(); + } +} + +#define Require(value) Check((value), __LINE__) + +constexpr std::size_t Count = 16; +template +using Nodes = std::array; + +template +static int Index(TNode* node, const Nodes& nodes) { + if (!node) return -1; + for (std::size_t i = 0; i < nodes.size(); ++i) if (node == &nodes[i]) return static_cast(i); + Require(false); + return -2; +} + +struct Lists { + GuestListRec guest{}; + FT_ListRec native{}; + Nodes actual{}; + Nodes expected{}; + std::array values{11, 22, 33, 44}; + + Lists() { + for (std::size_t i = 0; i < Count; ++i) actual[i].data = expected[i].data = i % 5 == 0 ? nullptr : &values[i % values.size()]; + } + + void Match() const { + Require(Index(guest.head, actual) == Index(native.head, expected)); + Require(Index(guest.tail, actual) == Index(native.tail, expected)); + for (std::size_t i = 0; i < Count; ++i) { + Require(Index(actual[i].prev, actual) == Index(expected[i].prev, expected)); + Require(Index(actual[i].next, actual) == Index(expected[i].next, expected)); + Require(actual[i].data == expected[i].data); + } + } + + void Fill() { + for (std::size_t i = 0; i < Count; ++i) { + FT_List_Add_nid_postfix(&guest, &actual[i]); + + FT_List_Add(&native, &expected[i]); + } + Match(); + } +}; + +static void Mutations() { + Lists lists; + std::array active{}; + std::array operations{}; + std::mt19937 random(0x7157); + for (unsigned step = 0; step < 2048; ++step) { + const auto i = random() % Count; + const bool choice = (random() & 1u) != 0; + if (active[i]) { + if (choice) { + FT_List_Up_nid_postfix(&lists.guest, &lists.actual[i]); + + FT_List_Up(&lists.native, &lists.expected[i]); + ++operations[3]; + } else { + FT_List_Remove_nid_postfix(&lists.guest, &lists.actual[i]); + + FT_List_Remove(&lists.native, &lists.expected[i]); + active[i] = false; + ++operations[2]; + } + } else { + if (choice) { + FT_List_Insert_nid_postfix(&lists.guest, &lists.actual[i]); + + FT_List_Insert(&lists.native, &lists.expected[i]); + ++operations[1]; + } else { + FT_List_Add_nid_postfix(&lists.guest, &lists.actual[i]); + + FT_List_Add(&lists.native, &lists.expected[i]); + ++operations[0]; + } + active[i] = true; + } + lists.Match(); + for (auto& value : lists.values) { + Require(Index(FT_List_Find_nid_postfix(&lists.guest, &value), lists.actual) == Index(FT_List_Find(&lists.native, &value), lists.expected)); + } + Require(Index(FT_List_Find_nid_postfix(&lists.guest, nullptr), lists.actual) == Index(FT_List_Find(&lists.native, nullptr), lists.expected)); + int absent = 123; + Require(FT_List_Find_nid_postfix(&lists.guest, &absent) == nullptr); + } + for (const auto count : operations) Require(count != 0); + for (auto operation : {FT_List_Add_nid_postfix, FT_List_Insert_nid_postfix, FT_List_Remove_nid_postfix, FT_List_Up_nid_postfix}) { + operation(nullptr, &lists.actual[0]); + operation(&lists.guest, nullptr); + operation(nullptr, nullptr); + lists.Match(); + } + Require(FT_List_Find_nid_postfix(nullptr, nullptr) == nullptr); + std::printf("list mutations: 2048 states compared with native FreeType\n"); +} + +template +struct Walk { + std::conditional_t, GuestListRec, FT_ListRec>* list; + Nodes* nodes; + bool guest; + bool remove; + std::size_t stop; + int error; + std::vector visits; +}; + +template +static int Visit(TNode* node, void* user, bool guest) { + auto& state = *static_cast*>(user); + Require(state.guest == guest); + state.visits.push_back(Index(node, *state.nodes)); + if (state.remove) { + if constexpr (std::is_same_v) FT_List_Remove_nid_postfix(state.list, node); + else FT_List_Remove(state.list, node); + node->prev = nullptr; + node->next = nullptr; + } + return state.visits.size() == state.stop ? state.error : 0; +} + +static int NativeIterator(FT_ListNode node, void* user) { return Visit(node, user, false); } +static int APS5_VABI GuestIterator(GuestListNodeRec* node, void* user) { return Visit(node, user, true); } + +static void IterationCases() { + for (const bool remove : {false, true}) { + for (const std::size_t stop : {0u, 1u, 3u, 16u}) { + Lists lists; + lists.Fill(); + const int error = stop == 3 ? -29 : 73; + Walk guest{&lists.guest, &lists.actual, true, remove, stop, error, {}}; + Walk native{&lists.native, &lists.expected, false, remove, stop, error, {}}; + const int result = FT_List_Iterate_nid_postfix(&lists.guest, GuestIterator, &guest); + Require(result == FT_List_Iterate(&lists.native, NativeIterator, &native)); + Require(result == (stop == 0 ? 0 : error)); + Require(guest.visits == native.visits && guest.visits.size() == (stop == 0 ? Count : stop)); + lists.Match(); + } + } + Lists empty; + Walk guest{&empty.guest, &empty.actual, true, false, 0, 0, {}}; + Walk native{&empty.native, &empty.expected, false, false, 0, 0, {}}; + Require(FT_List_Iterate_nid_postfix(&empty.guest, GuestIterator, &guest) == 0 && guest.visits.empty()); + Require(FT_List_Iterate_nid_postfix(nullptr, GuestIterator, &guest) == FT_List_Iterate(nullptr, NativeIterator, &native)); + Require(FT_List_Iterate_nid_postfix(&empty.guest, nullptr, &guest) == FT_List_Iterate(&empty.native, nullptr, &native)); + Require(FT_List_Iterate_nid_postfix(nullptr, nullptr, nullptr) == FT_List_Iterate(nullptr, nullptr, nullptr)); + std::printf("list iteration: 8 callback/order/error/removal cases compared\n"); +} + +struct Event { + int kind; + int id; + bool cleared; + bool operator==(const Event&) const = default; +}; + +template +struct Disposal { + TList* list; + FT_Memory native; + GuestMemoryRec* guest; + void* user; + std::map live; + std::vector events; +}; + +template +static void Release(Disposal& state, void* block) { + const auto found = state.live.find(block); + Require(found != state.live.end()); + state.events.push_back({2, found->second, state.list->head == nullptr || state.list->tail == nullptr}); + state.live.erase(found); + using Node = std::conditional_t, GuestListNodeRec, FT_ListNodeRec>; + delete static_cast(block); +} + +static void NativeFree(FT_Memory memory, void* block) { + auto& state = *static_cast*>(memory->user); + Require(state.native == memory); + Release(state, block); +} + +static void APS5_VABI GuestFree(GuestMemoryRec* memory, void* block) { + auto& state = *static_cast*>(memory->user); + Require(state.guest == memory); + Release(state, block); +} + +static void NativeDestroy(FT_Memory memory, void* data, void* user) { + auto& state = *static_cast*>(memory->user); + Require(state.native == memory && state.user == user); + state.events.push_back({1, data ? *static_cast(data) : -1, state.list->head == nullptr || state.list->tail == nullptr}); +} + +static void APS5_VABI GuestDestroy(GuestMemoryRec* memory, void* data, void* user) { + auto& state = *static_cast*>(memory->user); + Require(state.guest == memory && state.user == user); + state.events.push_back({1, data ? *static_cast(data) : -1, state.list->head == nullptr || state.list->tail == nullptr}); +} + +static void FinalizationCases() { + for (const bool destroy : {false, true}) { + for (const unsigned count : {0u, 1u, 8u}) { + GuestListRec guestList{}; + FT_ListRec nativeList{}; + std::array ids{0, 1, 2, 3, 4, 5, 6, 7}; + int token = 9; + Disposal guest{&guestList, nullptr, nullptr, &token, {}, {}}; + Disposal native{&nativeList, nullptr, nullptr, &token, {}, {}}; + GuestMemoryRec guestMemory{&guest, nullptr, GuestFree, nullptr}; + FT_MemoryRec_ nativeMemory{&native, nullptr, NativeFree, nullptr}; + guest.guest = &guestMemory; + native.native = &nativeMemory; + for (unsigned i = 0; i < count; ++i) { + auto* a = new GuestListNodeRec{}; + auto* b = new FT_ListNodeRec{}; + a->data = b->data = i == 0 ? nullptr : &ids[i]; + guest.live.emplace(a, i); + native.live.emplace(b, i); + FT_List_Add_nid_postfix(&guestList, a); + + FT_List_Add(&nativeList, b); + } + const auto savedHead = guestList.head; + FT_List_Finalize_nid_postfix(&guestList, GuestDestroy, nullptr, &token); + + Require(guestList.head == savedHead && guest.events.empty()); + FT_List_Finalize_nid_postfix(nullptr, GuestDestroy, &guestMemory, &token); + + Require(guest.events.empty()); + FT_List_Finalize_nid_postfix(&guestList, destroy ? GuestDestroy : nullptr, &guestMemory, &token); + + FT_List_Finalize(&nativeList, destroy ? NativeDestroy : nullptr, &nativeMemory, &token); + Require(guestList.head == nullptr && guestList.tail == nullptr && nativeList.head == nullptr && nativeList.tail == nullptr); + Require(guest.live.empty() && native.live.empty()); + Require(guest.events == native.events && guest.events.size() == count * (destroy ? 2u : 1u)); + for (const auto& event : guest.events) Require(!event.cleared); + } + } + std::printf("list finalization: 6 allocator/destructor/order cases compared\n"); +} + +int main() { + Mutations(); + IterationCases(); + FinalizationCases(); +} diff --git a/core/libs/tests/GuestFreeTypeNames.cpp b/core/libs/tests/GuestFreeTypeNames.cpp new file mode 100644 index 0000000000..7ae21fde1a --- /dev/null +++ b/core/libs/tests/GuestFreeTypeNames.cpp @@ -0,0 +1,123 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "FontFixture.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType names check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static std::vector NamedFont(const std::string& postscript, const std::string& first, const std::string& second) { + std::vector post(32, 0); + post[1] = 2; + for (const int value : {3, 0, 258, 259}) FontFixture::Put16(post, value); + for (const auto* name : {&first, &second}) { + post.push_back(static_cast(name->size())); + post.insert(post.end(), name->begin(), name->end()); + } + auto names = FontFixture::Words({0, 1, 18, 3, 1, 0x0409, 6, static_cast(postscript.size() * 2), 0}); + for (const unsigned char ch : postscript) FontFixture::Put16(names, ch); + return FontFixture::BuildFont(3, {{"post", post}, {"name", names}}); +} + +struct FacePair { + std::vector bytes; + GuestFace guest; + FT_Face oracle = nullptr; + + FacePair(GuestLibrary& library, FT_Library nativeLibrary, std::vector font) : bytes(std::move(font)) { + guest.library = &library; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &oracle) == 0); + SyncFace(guest); + } + + ~FacePair() { + Require(FT_Done_Face(guest.host) == 0); + Require(FT_Done_Face(oracle) == 0); + } +}; + +static void CompareGlyph(GuestFaceRec* guest, FT_Face native, std::uint32_t index, std::uint32_t size) { + std::array expected; + expected.fill('?'); + auto actual = expected; + const FT_Error error = FT_Get_Glyph_Name(native, index, expected.data() + 3, size); + Require(FT_Get_Glyph_Name_nid_postfix(guest, index, actual.data() + 3, size) == error); + Require(actual == expected); +} + +static void CheckFace(FacePair& face, bool names) { + Require((FT_HAS_GLYPH_NAMES(face.oracle) != 0) == names); + for (const std::uint32_t index : {0u, 1u, 2u, 3u, 0x7FFFFFFFu}) { + for (const std::uint32_t size : {0u, 1u, 2u, 5u, 24u}) CompareGlyph(&face.guest.rec, face.oracle, index, size); + Require(FT_Get_Glyph_Name_nid_postfix(&face.guest.rec, index, nullptr, 24) == FT_Get_Glyph_Name(face.oracle, index, nullptr, 24)); + } + for (const std::uint32_t index : {0x80000000u, 0xFFFFFFFFu}) { + for (const std::uint32_t size : {0u, 1u, 24u}) { + std::array expected; + expected.fill('?'); + auto actual = expected; + if (size) expected[3] = '\0'; + Require(FT_Get_Glyph_Name_nid_postfix(&face.guest.rec, index, actual.data() + 3, size) == + (size ? FT_Err_Invalid_Glyph_Index : FT_Err_Invalid_Argument)); + Require(actual == expected); + } + } + for (const char* name : {".notdef", "alpha.custom", "beta", "gamma.custom", "delta", "ALPHA.CUSTOM", "missing", ""}) { + Require(FT_Get_Name_Index_nid_postfix(&face.guest.rec, name) == FT_Get_Name_Index(face.oracle, name)); + } + Require(FT_Get_Name_Index_nid_postfix(&face.guest.rec, nullptr) == 0); + const char* actual = FT_Get_Postscript_Name_nid_postfix(&face.guest.rec); + const char* expected = FT_Get_Postscript_Name(face.oracle); + Require((actual == nullptr) == (expected == nullptr)); + Require(!actual || std::strcmp(actual, expected) == 0); + Require(actual == FT_Get_Postscript_Name(face.guest.host)); +} + +int main() { + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0); + Require(FT_Init_FreeType(&nativeLibrary) == 0); + { + FacePair first(library, nativeLibrary, NamedFont("NameFixture-A", "alpha.custom", "beta")); + FacePair second(library, nativeLibrary, NamedFont("NameFixture-B", "gamma.custom", "delta")); + std::vector post(32, 0); + post[1] = 3; + FacePair unnamed(library, nativeLibrary, FontFixture::BuildFont(3, {{"post", post}})); + CheckFace(first, true); + Require(FT_Get_Name_Index_nid_postfix(&first.guest.rec, "alpha.custom") == 1); + Require(FT_Get_Name_Index_nid_postfix(&first.guest.rec, "beta") == 2); + const char* retained = FT_Get_Postscript_Name_nid_postfix(&first.guest.rec); + Require(retained && std::strcmp(retained, "NameFixture-A") == 0); + CheckFace(second, true); + Require(FT_Get_Name_Index_nid_postfix(&second.guest.rec, "gamma.custom") == 1); + Require(FT_Get_Name_Index_nid_postfix(&second.guest.rec, "delta") == 2); + CheckFace(unnamed, false); + Require(FT_Get_Postscript_Name_nid_postfix(&unnamed.guest.rec) == nullptr); + Require(FT_Get_Postscript_Name_nid_postfix(&first.guest.rec) == retained && std::strcmp(retained, "NameFixture-A") == 0); + for (const std::uint32_t size : {0u, 1u, 24u}) CompareGlyph(nullptr, nullptr, 0, size); + Require(FT_Get_Glyph_Name_nid_postfix(nullptr, 0, nullptr, 0) == FT_Err_Invalid_Face_Handle); + Require(FT_Get_Name_Index_nid_postfix(nullptr, "alpha.custom") == 0); + Require(FT_Get_Name_Index_nid_postfix(nullptr, nullptr) == 0); + Require(FT_Get_Postscript_Name_nid_postfix(nullptr) == nullptr); + } + Require(FT_Done_FreeType(library.host) == 0); + Require(FT_Done_FreeType(nativeLibrary) == 0); +} diff --git a/core/libs/tests/GuestFreeTypeOutline.cpp b/core/libs/tests/GuestFreeTypeOutline.cpp new file mode 100644 index 0000000000..39013980a0 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeOutline.cpp @@ -0,0 +1,106 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestFreeType.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include FT_OUTLINE_H + +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType outline check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static void CompareCheck(std::uint16_t pointCount, std::vector& contours) { + GuestOutline guest{static_cast(contours.size()), pointCount, nullptr, nullptr, contours.data(), 0x71}; + FT_Outline native{guest.n_contours, pointCount, nullptr, nullptr, contours.data(), guest.flags}; + const auto before = contours; + Require(FT_Outline_Check_nid_postfix(&guest) == FT_Outline_Check(&native)); + Require(contours == before && guest.flags == 0x71 && guest.points == nullptr && guest.tags == nullptr); +} + +static void EnumerateContours(std::uint16_t pointCount, std::vector& contours, std::size_t index) { + if (index == contours.size()) { + CompareCheck(pointCount, contours); + return; + } + for (const std::uint16_t end : {0, 1, 2, 3, 4, 5, 6, 65535}) { + contours[index] = end; + EnumerateContours(pointCount, contours, index + 1); + } +} + +static bool Equal(const GuestBBox& actual, const GuestBBox& expected) { + return actual.xMin == expected.xMin && actual.yMin == expected.yMin && actual.xMax == expected.xMax && actual.yMax == expected.yMax; +} + +static void CompareBounds(std::vector points) { + std::vector nativePoints; + for (const auto& point : points) nativePoints.push_back({static_cast(point.x), static_cast(point.y)}); + GuestOutline guest{65535, static_cast(points.size()), points.data(), nullptr, nullptr, 0x71}; + FT_Outline native{guest.n_contours, guest.n_points, nativePoints.data(), nullptr, nullptr, guest.flags}; + struct GuardedBox { + std::uint64_t before; + GuestBBox value; + std::uint64_t after; + } actual{0x1122334455667788, {91, 92, 93, 94}, 0x8877665544332211}; + FT_BBox expected{91, 92, 93, 94}; + FT_Outline_Get_CBox(&native, &expected); + FT_Outline_Get_CBox_nid_postfix(&guest, &actual.value); + + Require(Equal(actual.value, {expected.xMin, expected.yMin, expected.xMax, expected.yMax})); + Require(actual.before == 0x1122334455667788 && actual.after == 0x8877665544332211); + FT_Outline_Get_CBox_nid_postfix(&guest, nullptr); + + Require(guest.n_contours == 65535 && guest.flags == 0x71 && guest.tags == nullptr && guest.contours == nullptr); +} + +int main() { + Require(FT_Outline_Check_nid_postfix(nullptr) == FT_Outline_Check(nullptr)); + for (std::uint16_t pointCount = 0; pointCount <= 6; ++pointCount) { + for (std::size_t contourCount = 0; contourCount <= 3; ++contourCount) { + std::vector contours(contourCount); + EnumerateContours(pointCount, contours, 0); + } + } + std::vector maximum = {65534}; + CompareCheck(65535, maximum); + GuestOutline high{1, 65535, nullptr, nullptr, maximum.data(), 0}; + Require(FT_Outline_Check_nid_postfix(&high) == 0); + std::vector maximumContours(65535); + for (std::size_t index = 0; index < maximumContours.size(); ++index) maximumContours[index] = static_cast(index); + CompareCheck(65535, maximumContours); + CompareBounds({}); + CompareBounds({{17, -29}}); + CompareBounds({{-10, 20}, {5000, -7000}, {30, 40}, {-200, 9000}}); + CompareBounds({{std::numeric_limits::min(), 0}, {0, std::numeric_limits::max()}}); + std::vector maximumPoints(65535, {3, 7}); + maximumPoints.back() = {-12345, 54321}; + CompareBounds(maximumPoints); + std::array wide = {{{std::numeric_limits::min(), 1}, + {std::int64_t{1} << 40, std::numeric_limits::max()}, + {std::numeric_limits::max(), std::numeric_limits::min()}}}; + GuestOutline outline{0, static_cast(wide.size()), wide.data(), nullptr, nullptr, 0}; + GuestBBox box{1, 2, 3, 4}; + FT_Outline_Get_CBox_nid_postfix(&outline, &box); + + Require(Equal(box, {std::numeric_limits::min(), std::numeric_limits::min(), + std::numeric_limits::max(), std::numeric_limits::max()})); + const GuestBBox retained = box; + FT_Outline_Get_CBox_nid_postfix(nullptr, &box); + + Require(Equal(box, retained)); + FT_Outline_Get_CBox_nid_postfix(nullptr, nullptr); + +} diff --git a/core/libs/tests/GuestFreeTypeOutlineBorder.cpp b/core/libs/tests/GuestFreeTypeOutlineBorder.cpp new file mode 100644 index 0000000000..069142dbb8 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeOutlineBorder.cpp @@ -0,0 +1,274 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include +#include FT_FREETYPE_H +#include FT_STROKER_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +using FreeType::GuestOutline; +using FreeType::GuestVector; +using Query = decltype(&FT_Outline_GetInsideBorder_nid_postfix); +static_assert(std::is_same_v); +static_assert(sizeof(GuestOutline) == 40 && alignof(GuestOutline) == 8); +static_assert(offsetof(GuestOutline, points) == 8 && offsetof(GuestOutline, contours) == 24); +static_assert(offsetof(GuestOutline, flags) == 32 && sizeof(GuestVector) == 16); +static_assert(sizeof(FT_StrokerBorder) == 4); + +namespace { + +bool nativeInside; + +void Require(bool result, const char* message) { + if (result) return; + std::fprintf(stderr, "FreeType outline border: %s\n", message); + std::exit(1); +} + +int APS5_VABI Native(const GuestOutline* input) { + if (!input) return nativeInside ? FT_Outline_GetInsideBorder(nullptr) : FT_Outline_GetOutsideBorder(nullptr); + FT_Outline outline{}; + outline.n_points = input->n_points; + outline.n_contours = input->n_contours; + outline.flags = input->flags; + std::vector points; + std::vector contours; + if (outline.n_points > 0) { + for (int i = 0; i < outline.n_points; ++i) { + Require(input->points[i].x >= std::numeric_limits::min() && input->points[i].x <= std::numeric_limits::max(), "reference x range"); + Require(input->points[i].y >= std::numeric_limits::min() && input->points[i].y <= std::numeric_limits::max(), "reference y range"); + points.push_back({static_cast(input->points[i].x), static_cast(input->points[i].y)}); + } + if (outline.n_contours > 0) + for (int i = 0; i < outline.n_contours; ++i) contours.push_back(input->contours[i]); + } + outline.points = points.data(); + outline.contours = contours.data(); + return nativeInside ? FT_Outline_GetInsideBorder(&outline) : FT_Outline_GetOutsideBorder(&outline); +} + +int Expected(bool inside, bool clockwise) { return inside == clockwise ? 1 : 0; } + +class ReadOnlyTail { + void* memory; + std::size_t page; + void* data; +public: + ReadOnlyTail(const void* source, std::size_t bytes) { +#ifdef _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + page = info.dwPageSize; + memory = VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + Require(memory != nullptr, "allocate guard pages"); +#else + page = static_cast(sysconf(_SC_PAGESIZE)); + memory = mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(memory != MAP_FAILED, "allocate guard pages"); +#endif + Require(bytes <= page, "guard fixture size"); + data = static_cast(memory) + page - bytes; + std::memcpy(data, source, bytes); +#ifdef _WIN32 + DWORD previous; + Require(VirtualProtect(memory, page, PAGE_READONLY, &previous) != 0, "read-only input"); + Require(VirtualProtect(static_cast(memory) + page, page, PAGE_NOACCESS, &previous) != 0, "inaccessible neighbor"); +#else + Require(mprotect(memory, page, PROT_READ) == 0, "read-only input"); + Require(mprotect(static_cast(memory) + page, page, PROT_NONE) == 0, "inaccessible neighbor"); +#endif + } + ~ReadOnlyTail() { +#ifdef _WIN32 + VirtualFree(memory, 0, MEM_RELEASE); +#else + munmap(memory, page * 2); +#endif + } + template T* As() const { return static_cast(data); } + void* End() const { return static_cast(memory) + page; } +}; + +void Cases(Query query, bool inside, bool guest) { + Require(query(nullptr) == Expected(inside, true), "null outline"); + GuestOutline outline{}; + outline.points = reinterpret_cast(std::uintptr_t{1}); + outline.contours = reinterpret_cast(std::uintptr_t{1}); + Require(query(&outline) == Expected(inside, true), "empty outline does not read points or contours"); + std::array points{{{0, 0}, {0, 64}, {64, 64}, {64, 0}}}; + std::uint16_t end = 3; + outline = {1, 4, points.data(), nullptr, &end, 0}; + Require(query(&outline) == Expected(inside, true), "clockwise polygon"); + const auto original = points; + for (const int flags : {0, 1, 2, -1}) { + outline.flags = flags; + Require(query(&outline) == Expected(inside, true), "orientation ignores outline flags"); + } + Require(std::memcmp(points.data(), original.data(), sizeof(points)) == 0, "input points unchanged"); + std::reverse(points.begin(), points.end()); + Require(query(&outline) == Expected(inside, false), "counterclockwise polygon"); + for (auto& point : points) point.y = 0; + Require(query(&outline) == Expected(inside, false), "collapsed polygon uses no-orientation fallback"); + points = original; + outline.n_contours = 0; + Require(query(&outline) == Expected(inside, false), "no contours"); + outline.n_contours = 1; + for (const std::int64_t edge : {std::int64_t{0x1000000}, std::int64_t{0x1000001}}) { + points = {{{-edge, -edge}, {-edge, edge}, {edge, edge}, {edge, -edge}}}; + Require(query(&outline) == (edge == 0x1000000 ? Expected(inside, true) : Expected(inside, false)), "coordinate-limit boundary"); + } + std::array mixed{}; + std::copy(original.begin(), original.end(), mixed.begin()); + std::copy(original.rbegin(), original.rend(), mixed.begin() + 4); + std::array mixedEnds{{3, 7}}; + GuestOutline cancelled{2, 8, mixed.data(), nullptr, mixedEnds.data(), 0}; + Require(query(&cancelled) == Expected(inside, false), "non-collapsed opposite contours cancel area"); + std::vector manyPoints(65535); + std::copy(original.rbegin(), original.rend(), manyPoints.begin()); + std::uint16_t manyEnd = 65534; + GuestOutline many{1, 65535, manyPoints.data(), nullptr, &manyEnd, 0}; + Require(query(&many) == Expected(inside, false), "unsigned point count above 32767"); + std::fill(manyPoints.begin(), manyPoints.end(), GuestVector{}); + std::copy(original.begin(), original.end(), manyPoints.end() - 4); + std::vector manyEnds(65532); + for (unsigned i = 0; i < manyEnds.size() - 1; ++i) manyEnds[i] = static_cast(i); + manyEnds.back() = 65534; + many.n_contours = 65532; + many.contours = manyEnds.data(); + Require(query(&many) == Expected(inside, true), "unsigned contour count above 32767"); + if (guest) { + for (const auto edge : {std::numeric_limits::min(), std::numeric_limits::max()}) { + points = original; + points[0].x = edge; + Require(query(&outline) == Expected(inside, false), "64-bit out-of-range coordinate"); + } + points = original; + std::array repeated{}; + std::array ends{}; + const std::array large{{{0, 0}, {0, 16384}, {16384, 16384}, {16384, 0}}}; + for (unsigned i = 0; i < 5; ++i) { + std::copy(large.begin(), large.end(), repeated.begin() + i * 4); + ends[i] = static_cast(i * 4 + 3); + } + GuestOutline wide{5, 20, repeated.data(), nullptr, ends.data(), 0}; + Require(query(&wide) == Expected(inside, true), "LP64 area must not wrap at 32 bits"); + ReadOnlyTail guardedPoints(original.data(), sizeof(original)); + ReadOnlyTail guardedEnds(&end, sizeof(end)); + GuestOutline guarded{1, 4, guardedPoints.As(), static_cast(guardedPoints.End()), guardedEnds.As(), -1}; + ReadOnlyTail guardedOutline(&guarded, sizeof(guarded)); + Require(query(guardedOutline.As()) == Expected(inside, true), "read-only bounded inputs and unused inaccessible tags"); + outline = {1, 4, nullptr, nullptr, &end, 0}; + bool threw = false; + try { query(&outline); } catch (const std::invalid_argument&) { threw = true; } + Require(threw, "null positive-count points are explicitly unsupported"); + outline.points = points.data(); + outline.contours = nullptr; + threw = false; + try { query(&outline); } catch (const std::invalid_argument&) { threw = true; } + Require(threw, "null positive-count contours are explicitly unsupported"); + for (const std::uint16_t badEnd : {std::uint16_t{4}, std::uint16_t{0xffff}}) { + auto invalidEnd = badEnd; + outline.contours = &invalidEnd; + threw = false; + try { query(&outline); } catch (const std::invalid_argument&) { threw = true; } + Require(threw, "invalid contour endpoint is explicitly unsupported"); + } + for (const std::uint16_t nextEnd : {std::uint16_t{2}, std::uint16_t{3}}) { + std::array invalidEnds{{3, nextEnd}}; + outline.n_contours = 2; + outline.contours = invalidEnds.data(); + threw = false; + try { query(&outline); } catch (const std::invalid_argument&) { threw = true; } + Require(threw, "non-increasing contour endpoints are explicitly unsupported"); + } + } + std::uint32_t state = 7; + for (unsigned iteration = 0; iteration < 512; ++iteration) { + for (auto& point : points) { + state = state * 1664525u + 1013904223u; + point.x = static_cast((state >> 16) & 255) - 128; + state = state * 1664525u + 1013904223u; + point.y = static_cast((state >> 16) & 255) - 128; + } + outline = {1, 4, points.data(), nullptr, &end, 0}; + nativeInside = inside; + Require(query(&outline) == Native(&outline), "bounded polygon matches bundled FreeType"); + } + if (guest) { + points = original; + outline = {1, 4, points.data(), nullptr, &end, 0}; + std::array threads; + for (auto& thread : threads) thread = std::thread([&] { + for (unsigned i = 0; i < 100; ++i) Require(query(&outline) == Expected(inside, true), "concurrent independent query"); + }); + for (auto& thread : threads) thread.join(); + } +} + +} + +int main(int argc, char** argv) { +#ifdef _WIN32 + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); +#endif + if (argc == 2 && std::strcmp(argv[1], "reference") == 0) { + for (const bool inside : {false, true}) { + nativeInside = inside; + Cases(Native, inside, false); + } + std::puts("Bundled FreeType reference cases passed"); + return 0; + } + Require(argc == 4, "expected library, border and export mode"); + const bool inside = std::strcmp(argv[2], "inside") == 0; + const bool patched = std::strcmp(argv[3], "patched") == 0; + const char* symbol = patched ? (inside ? "Pz262plj20w" : "sKneJjUXWkQ") : + (inside ? "FT_Outline_GetInsideBorder_nid_postfix" : "FT_Outline_GetOutsideBorder_nid_postfix"); +#ifdef _WIN32 + const auto library = LoadLibraryA(argv[1]); + Require(library != nullptr, "load requested library"); + const auto checkPath = [](HMODULE module, const std::filesystem::path& expected) { + Require(module != nullptr, "load matching runtime module"); + std::array path{}; + const auto length = GetModuleFileNameW(module, path.data(), static_cast(path.size())); + Require(length != 0 && length < path.size(), "read loaded module path"); + Require(std::filesystem::equivalent(expected, std::filesystem::path(path.data())), "loaded module variant path"); + }; + const auto requested = std::filesystem::absolute(argv[1]); + checkPath(library, requested); + checkPath(GetModuleHandleW(L"libc.prx"), requested.parent_path() / "libc.prx"); + auto query = reinterpret_cast(GetProcAddress(library, symbol)); +#else + const auto library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + if (!library) std::fprintf(stderr, "%s\n", dlerror()); + Require(library != nullptr, "load requested library"); + auto query = reinterpret_cast(dlsym(library, symbol)); +#endif + if (!query) std::fprintf(stderr, "Missing %s\n", symbol); + Require(query != nullptr, "missing outline-border export"); + Cases(query, inside, true); + std::puts("Guest outline-border cases passed"); +#ifdef _WIN32 + FreeLibrary(library); +#else + dlclose(library); +#endif +} diff --git a/core/libs/tests/GuestFreeTypeOutlineMutations.cpp b/core/libs/tests/GuestFreeTypeOutlineMutations.cpp new file mode 100644 index 0000000000..b7e437d03e --- /dev/null +++ b/core/libs/tests/GuestFreeTypeOutlineMutations.cpp @@ -0,0 +1,173 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "FreeTypeFontFixture.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_OUTLINE_H + +#include +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType outline mutation check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static bool Equal(const GuestVector& left, const GuestVector& right) { + return left.x == right.x && left.y == right.y; +} + +static void ReverseOracle(std::size_t count, std::vector contours, std::int32_t flags) { + std::vector storage(count + 2); + storage.front() = {991, 992}; + storage.back() = {-993, -994}; + std::vector tags(count + 2, 0xA5); + std::vector nativePoints; + std::vector nativeTags; + for (std::size_t index = 0; index < count; ++index) { + storage[index + 1] = {(std::int64_t{1} << 40) + static_cast(index) * 3, + -(std::int64_t{1} << 41) + static_cast(index) * 7}; + tags[index + 1] = static_cast(index * 73); + nativePoints.push_back({static_cast(index), -static_cast(index)}); + nativeTags.push_back(tags[index + 1]); + } + const auto original = storage; + const auto originalTags = tags; + const auto originalContours = contours; + GuestOutline guest{static_cast(contours.size()), static_cast(count), storage.data() + 1, + tags.data() + 1, contours.data(), flags}; + FT_Outline native{guest.n_contours, guest.n_points, nativePoints.data(), nativeTags.data(), contours.data(), flags}; + FT_Outline_Reverse(&native); + FT_Outline_Reverse_nid_postfix(&guest); + + Require(guest.flags == native.flags && guest.flags == (flags ^ FT_OUTLINE_REVERSE_FILL)); + Require(guest.n_points == count && guest.n_contours == contours.size() && contours == originalContours); + for (std::size_t index = 0; index < count; ++index) { + const auto source = static_cast(nativePoints[index].x); + Require(source < count && Equal(storage[index + 1], original[source + 1])); + Require(tags[index + 1] == nativeTags[index]); + } + Require(Equal(storage.front(), original.front()) && Equal(storage.back(), original.back())); + Require(tags.front() == 0xA5 && tags.back() == 0xA5); + FT_Outline_Reverse_nid_postfix(&guest); + + Require(guest.flags == flags && tags == originalTags); + for (std::size_t index = 0; index < storage.size(); ++index) Require(Equal(storage[index], original[index])); +} + +static void TranslateOracle(std::size_t count, FT_Pos xOffset, FT_Pos yOffset) { + std::vector storage(count + 2, {991, -992}); + std::vector nativePoints; + for (std::size_t index = 0; index < count; ++index) { + const auto value = static_cast(index % 101); + storage[index + 1] = {value - 50, 25 - value}; + nativePoints.push_back({value - 50, 25 - value}); + } + GuestOutline guest{65535, static_cast(count), storage.data() + 1, nullptr, nullptr, 0x71}; + FT_Outline native{guest.n_contours, guest.n_points, nativePoints.data(), nullptr, nullptr, guest.flags}; + FT_Outline_Translate(&native, xOffset, yOffset); + FT_Outline_Translate_nid_postfix(&guest, xOffset, yOffset); + + for (std::size_t index = 0; index < count; ++index) { + Require(storage[index + 1].x == nativePoints[index].x && storage[index + 1].y == nativePoints[index].y); + } + Require(Equal(storage.front(), {991, -992}) && Equal(storage.back(), {991, -992})); + Require(guest.n_contours == 65535 && guest.n_points == count && guest.flags == 0x71); + Require(guest.tags == nullptr && guest.contours == nullptr); +} + +static void WideTranslation() { + const auto low = std::numeric_limits::min(); + const auto high = std::numeric_limits::max(); + const auto wide = std::int64_t{1} << 40; + std::array points = {{{high, low}, {low, high}, {-1, 0}, {1, -1}, {wide, -wide}, {high - 5, low + 5}}}; + const auto original = points; + const std::array expected = {{{low, high}, {low + 1, high - 1}, {0, -1}, {2, -2}, {wide + 1, -wide - 1}, {high - 4, low + 4}}}; + GuestOutline guest{0, static_cast(points.size()), points.data(), nullptr, nullptr, 0}; + FT_Outline_Translate_nid_postfix(&guest, 1, -1); + + for (std::size_t index = 0; index < points.size(); ++index) Require(Equal(points[index], expected[index])); + FT_Outline_Translate_nid_postfix(&guest, -1, 1); + + for (std::size_t index = 0; index < points.size(); ++index) Require(Equal(points[index], original[index])); + GuestVector point{7, -9}; + guest.n_points = 1; + guest.points = &point; + FT_Outline_Translate_nid_postfix(&guest, wide, -wide); + + Require(Equal(point, {wide + 7, -wide - 9})); + point = {low, 1}; + FT_Outline_Translate_nid_postfix(&guest, low, high); + + Require(Equal(point, {0, low})); +} + +static void RenderModifiedOutline() { + const auto bytes = SquareGlyphFont('A', 700); + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0 && FT_Init_FreeType(&nativeLibrary) == 0); + { + GuestFace guest; + guest.library = &library; + FT_Face native = nullptr; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &native) == 0); + Require(FT_Set_Pixel_Sizes(guest.host, 18, 18) == 0 && FT_Set_Pixel_Sizes(native, 18, 18) == 0); + Require(FT_Load_Glyph(guest.host, 1, FT_LOAD_DEFAULT) == 0 && FT_Load_Glyph(native, 1, FT_LOAD_DEFAULT) == 0); + SyncFace(guest); + FT_Outline_Reverse_nid_postfix(&guest.slot.outline); + FT_Outline_Translate_nid_postfix(&guest.slot.outline, 77, -129); + + FT_Outline_Reverse(&native->glyph->outline); + FT_Outline_Translate(&native->glyph->outline, 77, -129); + Require(FT_Render_Glyph_nid_postfix(&guest.slot, FT_RENDER_MODE_NORMAL) == FT_Render_Glyph(native->glyph, FT_RENDER_MODE_NORMAL)); + const auto& actual = guest.slot.bitmap; + const auto& expected = native->glyph->bitmap; + Require(actual.width > 0 && actual.rows > 0 && actual.width == expected.width && actual.rows == expected.rows); + Require(actual.pitch == expected.pitch && actual.pixel_mode == expected.pixel_mode && actual.num_grays == expected.num_grays); + Require(guest.slot.bitmap_left == native->glyph->bitmap_left && guest.slot.bitmap_top == native->glyph->bitmap_top); + const auto pitch = static_cast(expected.pitch < 0 ? -expected.pitch : expected.pitch); + Require(std::memcmp(actual.buffer, expected.buffer, pitch * expected.rows) == 0); + Require(FT_Done_Face(guest.host) == 0 && FT_Done_Face(native) == 0); + } + Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(nativeLibrary) == 0); +} + +int main() { + FT_Outline_Reverse_nid_postfix(nullptr); + FT_Outline_Translate_nid_postfix(nullptr, std::numeric_limits::min(), std::numeric_limits::max()); + + ReverseOracle(0, {}, FT_OUTLINE_REVERSE_FILL); + for (std::size_t count = 1; count <= 8; ++count) { + for (unsigned mask = 0; mask < (1u << (count - 1)); ++mask) { + std::vector contours; + for (std::size_t index = 0; index < count; ++index) { + if (index + 1 == count || (mask & (1u << index))) contours.push_back(static_cast(index)); + } + ReverseOracle(count, contours, static_cast(mask) | 0x31); + } + } + ReverseOracle(65535, {65534}, 0); + std::vector maximumContours(65535); + for (std::size_t index = 0; index < maximumContours.size(); ++index) maximumContours[index] = static_cast(index); + ReverseOracle(65535, maximumContours, FT_OUTLINE_REVERSE_FILL); + for (std::size_t count = 0; count <= 6; ++count) { + for (const FT_Pos xOffset : {-65, 0, 64, 127}) { + for (const FT_Pos yOffset : {-99, 0, 101}) TranslateOracle(count, xOffset, yOffset); + } + } + TranslateOracle(65535, 99, -17); + WideTranslation(); + RenderModifiedOutline(); +} diff --git a/core/libs/tests/GuestFreeTypeRequestSize.cpp b/core/libs/tests/GuestFreeTypeRequestSize.cpp new file mode 100644 index 0000000000..a1e8356b3d --- /dev/null +++ b/core/libs/tests/GuestFreeTypeRequestSize.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "FreeTypeFontFixture.hpp" +#include "prx/libSceFreeType/include/GuestFreeType.hpp" + +#include +#include FT_FREETYPE_H + +#include +#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 size request check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +struct Fixture { + std::vector bytes; + FT_Library library = nullptr; + FT_Face native = nullptr; + FreeType::GuestLibrary* guestLibrary = nullptr; + GuestFaceRec* guest = nullptr; + + explicit Fixture(std::vector font, int faceIndex = 0) : bytes(std::move(font)) { + Require(FT_Init_FreeType(&library) == 0); + Require(FT_New_Memory_Face(library, bytes.data(), static_cast(bytes.size()), faceIndex, &native) == 0); + Require(FT_Init_FreeType_nid_postfix(&guestLibrary) == 0); + Require(FT_New_Memory_Face_nid_postfix(guestLibrary, bytes.data(), static_cast(bytes.size()), faceIndex, &guest) == 0); + } + + ~Fixture() { + Require(FT_Done_FreeType_nid_postfix(guestLibrary) == 0); + Require(FT_Done_FreeType(library) == 0); + } + + void CheckMetrics() const { + const auto& actual = guest->size->metrics; + const auto& expected = native->size->metrics; + Require(actual.x_ppem == expected.x_ppem && actual.y_ppem == expected.y_ppem); + Require(actual.x_scale == expected.x_scale && actual.y_scale == expected.y_scale); + Require(actual.ascender == expected.ascender && actual.descender == expected.descender); + Require(actual.height == expected.height && actual.max_advance == expected.max_advance); + } + + int Request(GuestSizeRequestRec request) { + FT_Size_RequestRec hostRequest{static_cast(request.type), static_cast(request.width), + static_cast(request.height), request.horiResolution, request.vertResolution}; + const auto before = request; + const auto* size = guest->size; + const auto generic = size->generic; + const int error = FT_Request_Size(native, &hostRequest); + Require(FT_Request_Size_nid_postfix(guest, &request) == error); + Require(request.type == before.type && request.width == before.width && request.height == before.height); + Require(request.horiResolution == before.horiResolution && request.vertResolution == before.vertResolution); + Require(guest->size == size && size->face == guest && size->generic.data == generic.data && size->generic.finalizer == generic.finalizer); + CheckMetrics(); + return error; + } + + void Load() const { + Require(FT_Load_Glyph(native, 1, FT_LOAD_DEFAULT) == 0); + Require(FT_Load_Glyph_nid_postfix(guest, 1, FT_LOAD_DEFAULT) == 0); + const auto& actual = guest->glyph->metrics; + const auto& expected = native->glyph->metrics; + Require(actual.width == expected.width && actual.height == expected.height); + Require(actual.horiBearingX == expected.horiBearingX && actual.horiBearingY == expected.horiBearingY); + Require(actual.horiAdvance == expected.horiAdvance && actual.vertAdvance == expected.vertAdvance); + Require(guest->glyph->advance.x == native->glyph->advance.x && guest->glyph->advance.y == native->glyph->advance.y); + } +}; + +static void CheckScalable() { + Fixture fixture(SquareGlyphFont('A', 913)); + fixture.guest->size->generic.data = &fixture; + for (std::uint32_t type = FT_SIZE_REQUEST_TYPE_NOMINAL; type < FT_SIZE_REQUEST_TYPE_MAX; ++type) { + Require(fixture.Request({type, 0, 0, 0, 0}) == FT_Err_Invalid_PPem); + for (const auto& dimensions : std::array, 4>{{{0, 23 * 64 + 17}, + {31 * 64 + 49, 0}, {17 * 64 + 31, 29 * 64 + 33}, {48 * 64, 12 * 64}}}) { + for (const auto& dpi : std::array, 4>{{{0, 0}, {72, 144}, {96, 0}, {0, 120}}}) { + const auto factor = type == FT_SIZE_REQUEST_TYPE_SCALES ? 64 : 1; + Require(fixture.Request({type, dimensions[0] * factor, dimensions[1] * factor, dpi[0], dpi[1]}) == 0); + fixture.Load(); + } + } + } + Require(fixture.Request({FT_SIZE_REQUEST_TYPE_SCALES, 0x18000, 0x28000, 0, 0}) == 0); + const auto scales = fixture.guest->size->metrics; + Require(fixture.Request({FT_SIZE_REQUEST_TYPE_SCALES, 0x18000, 0x28000, + std::numeric_limits::max(), std::numeric_limits::max()}) == 0); + Require(fixture.guest->size->metrics.x_scale == scales.x_scale && fixture.guest->size->metrics.y_scale == scales.y_scale); + Require(fixture.Request({FT_SIZE_REQUEST_TYPE_NOMINAL, 65536LL * 64, 17 * 64, 0, 0}) == FT_Err_Invalid_Pixel_Size); + Require(fixture.Request({FT_SIZE_REQUEST_TYPE_NOMINAL, 19 * 64, 27 * 64, 0, 0}) == 0); + fixture.Load(); + 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]; + const auto savedSlot = *slot; + for (const auto width : {21LL * 64, 65536LL * 64}) { + fixture.Request({FT_SIZE_REQUEST_TYPE_NOMINAL, width, 15 * 64, 0, 0}); + Require(std::memcmp(slot, &savedSlot, sizeof(savedSlot)) == 0); + Require(slot->outline.points[0].x == point.x && slot->outline.points[0].y == point.y); + } +} + +static void CheckInvalid() { + Fixture fixture(SquareGlyphFont('A', 913)); + Require(fixture.Request({FT_SIZE_REQUEST_TYPE_NOMINAL, 17 * 64, 23 * 64, 0, 0}) == 0); + for (const auto& request : std::array{{ + {FT_SIZE_REQUEST_TYPE_NOMINAL, -1, 1024, 0, 0}, + {FT_SIZE_REQUEST_TYPE_NOMINAL, 1024, std::numeric_limits::min(), 0, 0}, + {FT_SIZE_REQUEST_TYPE_MAX, 1024, 1024, 0, 0}, + {std::numeric_limits::max(), 1024, 1024, 0, 0}, + {FT_SIZE_REQUEST_TYPE_MAX, std::numeric_limits::max(), 1024, 0, 0}, + {FT_SIZE_REQUEST_TYPE_NOMINAL, std::numeric_limits::max(), -1, 0, 0}}}) { + Require(FT_Request_Size_nid_postfix(fixture.guest, &request) == FT_Err_Invalid_Argument); + Require(FT_Request_Size_nid_postfix(nullptr, &request) == FT_Err_Invalid_Face_Handle); + fixture.CheckMetrics(); + } + Require(FT_Request_Size_nid_postfix(nullptr, nullptr) == FT_Err_Invalid_Face_Handle); + Require(FT_Request_Size_nid_postfix(fixture.guest, nullptr) == FT_Request_Size(fixture.native, nullptr)); + fixture.CheckMetrics(); + + Fixture probe(SquareGlyphFont('A', 913), -1); + Require(probe.guest->size == nullptr && probe.native->size == nullptr); + GuestSizeRequestRec request{FT_SIZE_REQUEST_TYPE_NOMINAL, -1, -1, 0, 0}; + Require(FT_Request_Size_nid_postfix(probe.guest, &request) == FT_Err_Invalid_Size_Handle); + Require(FT_Request_Size_nid_postfix(probe.guest, nullptr) == FT_Err_Invalid_Size_Handle); + + for (const auto& dimensions : std::array, 2>{{{0x100000000LL + 1024, 1024}, {1024, 0x100000000LL + 1024}}}) { + request = {FT_SIZE_REQUEST_TYPE_NOMINAL, dimensions[0], dimensions[1], 0, 0}; + if constexpr (sizeof(FT_Long) == sizeof(std::int64_t)) { + Require(fixture.Request(request) == FT_Err_Invalid_Pixel_Size); + } else { + bool threw = false; + try { + FT_Request_Size_nid_postfix(fixture.guest, &request); + + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw); + fixture.CheckMetrics(); + } + } +} + +static void CheckBitmap() { + const std::string bdf = "STARTFONT 2.1\nFONT -AnyPS5-Fixed-Medium-R-Normal--8-80-72-72-C-80-ISO10646-1\n" + "SIZE 8 72 72\nFONTBOUNDINGBOX 8 8 0 0\nSTARTPROPERTIES 4\nFONT_ASCENT 8\nFONT_DESCENT 0\n" + "CHARSET_REGISTRY \"ISO10646\"\nCHARSET_ENCODING \"1\"\nENDPROPERTIES\nCHARS 1\nSTARTCHAR A\nENCODING 65\n" + "SWIDTH 1000 0\nDWIDTH 8 0\nBBX 8 8 0 0\nBITMAP\n18\n24\n42\n7E\n42\n42\n42\n00\nENDCHAR\nENDFONT\n"; + Fixture fixture({bdf.begin(), bdf.end()}); + Require(!FT_IS_SCALABLE(fixture.native) && fixture.guest->num_fixed_sizes == 1); + Require(fixture.Request({FT_SIZE_REQUEST_TYPE_NOMINAL, 8 * 64, 8 * 64, 0, 0}) == 0); + fixture.Load(); + Require(fixture.Request({FT_SIZE_REQUEST_TYPE_NOMINAL, 0, 8 * 64, 72, 72}) == 0); + Require(fixture.Request({FT_SIZE_REQUEST_TYPE_REAL_DIM, 8 * 64, 8 * 64, 0, 0}) == 0); + Require(fixture.Request({FT_SIZE_REQUEST_TYPE_NOMINAL, 0, 17 * 64, 0, 0}) == FT_Err_Invalid_Pixel_Size); + for (const auto type : {FT_SIZE_REQUEST_TYPE_BBOX, FT_SIZE_REQUEST_TYPE_CELL, FT_SIZE_REQUEST_TYPE_SCALES}) { + Require(fixture.Request({static_cast(type), 8 * 64, 8 * 64, 0, 0}) == FT_Err_Unimplemented_Feature); + } +} + +int main() { + CheckScalable(); + CheckInvalid(); + CheckBitmap(); +} diff --git a/core/libs/tests/GuestFreeTypeSfntNames.cpp b/core/libs/tests/GuestFreeTypeSfntNames.cpp new file mode 100644 index 0000000000..4cd1e8f14c --- /dev/null +++ b/core/libs/tests/GuestFreeTypeSfntNames.cpp @@ -0,0 +1,140 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "FontFixture.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_SFNT_NAMES_H + +#include +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType SFNT names check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +struct NameRecord { + std::uint16_t platform; + std::uint16_t encoding; + std::uint16_t language; + std::uint16_t name; + std::vector bytes; +}; + +static std::vector NamedFont(const std::vector& records) { + auto table = FontFixture::Words({0, static_cast(records.size()), 6 + 12 * static_cast(records.size())}); + int offset = 0; + for (const auto& record : records) { + for (const int value : {static_cast(record.platform), static_cast(record.encoding), static_cast(record.language), + static_cast(record.name), static_cast(record.bytes.size()), offset}) FontFixture::Put16(table, value); + offset += static_cast(record.bytes.size()); + } + for (const auto& record : records) table.insert(table.end(), record.bytes.begin(), record.bytes.end()); + return FontFixture::BuildFont(3, {{"name", table}}); +} + +struct FacePair { + std::vector bytes; + GuestFace guest; + FT_Face oracle = nullptr; + + FacePair(GuestLibrary& library, FT_Library nativeLibrary, std::vector font) : bytes(std::move(font)) { + guest.library = &library; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &oracle) == 0); + SyncFace(guest); + } + + ~FacePair() { + Require(FT_Done_Face(guest.host) == 0); + Require(FT_Done_Face(oracle) == 0); + } +}; + +static GuestSfntName Query(FacePair& face, std::uint32_t index, const NameRecord& record) { + struct GuardedName { + std::array before; + GuestSfntName value; + std::array after; + } actual; + std::memset(&actual, 0xA5, sizeof(actual)); + FT_SfntName expected{}; + Require(FT_Get_Sfnt_Name(face.oracle, index, &expected) == 0); + Require(FT_Get_Sfnt_Name_nid_postfix(&face.guest.rec, index, &actual.value) == 0); + for (const auto byte : actual.before) Require(byte == 0xA5); + for (const auto byte : actual.after) Require(byte == 0xA5); + const auto& name = actual.value; + Require(name.platform_id == expected.platform_id && name.platform_id == record.platform); + Require(name.encoding_id == expected.encoding_id && name.encoding_id == record.encoding); + Require(name.language_id == expected.language_id && name.language_id == record.language); + Require(name.name_id == expected.name_id && name.name_id == record.name); + Require(name.string_len == expected.string_len && name.string_len == record.bytes.size()); + Require(std::memcmp(name.string, expected.string, name.string_len) == 0); + Require(std::memcmp(name.string, record.bytes.data(), name.string_len) == 0); + FT_SfntName native{}; + Require(FT_Get_Sfnt_Name(face.guest.host, index, &native) == 0 && name.string == native.string); + return name; +} + +static void InvalidQuery(GuestFaceRec* guest, FT_Face native, std::uint32_t index) { + GuestSfntName actual; + std::memset(&actual, 0xA5, sizeof(actual)); + std::array before; + std::memcpy(before.data(), &actual, sizeof(actual)); + FT_SfntName expected{}; + const FT_Error error = FT_Get_Sfnt_Name(native, index, &expected); + Require(error != 0); + Require(FT_Get_Sfnt_Name_nid_postfix(guest, index, &actual) == error); + Require(std::memcmp(&actual, before.data(), sizeof(actual)) == 0); + Require(FT_Get_Sfnt_Name_nid_postfix(guest, index, nullptr) == FT_Get_Sfnt_Name(native, index, nullptr)); +} + +int main() { + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0); + Require(FT_Init_FreeType(&nativeLibrary) == 0); + { + const std::vector records = { + {1, 0, 0, 1, {'F', 'a', 'm', 'i', 'l', 'y'}}, + {3, 1, 0x0409, 6, {0, 'S', 0, 'f', 0, 'n', 0, 't'}}, + {3, 10, 0x0411, 256, {0, 'A', 0, 0, 0, 'B'}} + }; + FacePair first(library, nativeLibrary, NamedFont(records)); + FacePair empty(library, nativeLibrary, FontFixture::BuildFont(3, {})); + Require(FT_Get_Sfnt_Name_Count_nid_postfix(&first.guest.rec) == records.size()); + Require(FT_Get_Sfnt_Name_Count_nid_postfix(&first.guest.rec) == FT_Get_Sfnt_Name_Count(first.oracle)); + std::vector retained; + for (std::uint32_t index = 0; index < records.size(); ++index) retained.push_back(Query(first, index, records[index])); + { + const std::vector other = {{3, 1, 0x0409, 6, {0, 'O', 0, 't', 0, 'h', 0, 'e', 0, 'r'}}}; + FacePair second(library, nativeLibrary, NamedFont(other)); + Require(FT_Get_Sfnt_Name_Count_nid_postfix(&second.guest.rec) == other.size()); + Query(second, 0, other[0]); + } + for (std::uint32_t index = 0; index < records.size(); ++index) { + Require(Query(first, index, records[index]).string == retained[index].string); + Require(std::memcmp(retained[index].string, records[index].bytes.data(), retained[index].string_len) == 0); + Require(FT_Get_Sfnt_Name_nid_postfix(&first.guest.rec, index, nullptr) == FT_Get_Sfnt_Name(first.oracle, index, nullptr)); + } + Require(FT_Get_Sfnt_Name_Count_nid_postfix(&empty.guest.rec) == 0); + Require(FT_Get_Sfnt_Name_Count_nid_postfix(nullptr) == 0); + for (const std::uint32_t index : {0u, 3u, 0x80000000u, 0xFFFFFFFFu}) { + if (index >= records.size()) InvalidQuery(&first.guest.rec, first.oracle, index); + InvalidQuery(&empty.guest.rec, empty.oracle, index); + InvalidQuery(nullptr, nullptr, index); + } + } + Require(FT_Done_FreeType(library.host) == 0); + Require(FT_Done_FreeType(nativeLibrary) == 0); +} diff --git a/core/libs/tests/GuestFreebsdClocks.cpp b/core/libs/tests/GuestFreebsdClocks.cpp index 40972ab464..1ae0f71258 100644 --- a/core/libs/tests/GuestFreebsdClocks.cpp +++ b/core/libs/tests/GuestFreebsdClocks.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -8,20 +11,6 @@ #include #include -extern "C" { -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); -int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res); -int APS5_VABI clock_settime_nid_postfix(int clockId, const KernelTimespec* tp); -int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp); -int APS5_VABI sceKernelClockGetres(KernelClockid clockId, KernelTimespec* tp); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); -int APS5_VABI gettimeofday_nid_postfix(KernelTimeval* tv, KernelTimezone* tz); -std::int64_t APS5_VABI _Xtime_get_ticks_nid_postfix(); -int APS5_VABI sceKernelConvertLocaltimeToUtc(std::int64_t, std::int64_t, std::int64_t*, KernelTimesec*, std::int32_t*); -int APS5_VABI sceKernelConvertUtcToLocaltime(std::int64_t, std::int64_t*, KernelTimesec*, std::int32_t*); -} - static constexpr int SCE_OK = 0; static constexpr int GUEST_CLOCK_REALTIME = 0; @@ -70,6 +59,7 @@ static void UnsupportedResolutionStillThrows() { bool rejected = false; try { clock_getres_nid_postfix(3, &resolution); + } catch (const std::runtime_error&) { rejected = true; } @@ -88,6 +78,7 @@ static void OmittedTimeIsFaultAfterClockValidation() { bool rejected = false; try { clock_gettime_nid_postfix(3, nullptr); + } catch (const std::runtime_error&) { rejected = true; } @@ -244,6 +235,7 @@ static void RequireSettimeError(int clockId, const KernelTimespec* time, int err static bool SettimeThrows(const KernelTimespec& time, const char* requested) { try { clock_settime_nid_postfix(GUEST_CLOCK_REALTIME, &time); + } catch (const std::runtime_error& error) { return std::strstr(error.what(), requested) != nullptr; } diff --git a/core/libs/tests/GuestFreopenHostStandard.cpp b/core/libs/tests/GuestFreopenHostStandard.cpp new file mode 100644 index 0000000000..cf8e85e536 --- /dev/null +++ b/core/libs/tests/GuestFreopenHostStandard.cpp @@ -0,0 +1,77 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#endif + +extern "C" { + +extern FileStream* __stdoutp_nid_postfix; +} + +static void Require(bool value, int line) { + if (!value) { + std::fprintf(stderr, "freopen host standard check failed at line %d, errno %d\n", line, errno); + std::abort(); + } +} +#define Check(value) Require((value), __LINE__) + +#ifdef _WIN32 +static int HostDup(int descriptor) { return ::_dup(descriptor); } +static int HostDup2(int from, int to) { return ::_dup2(from, to); } +static int HostClose(int descriptor) { return ::_close(descriptor); } +static int HostWrite(int descriptor, const char* text, unsigned size) { return ::_write(descriptor, text, size); } +static int HostFileno(std::FILE* stream) { return ::_fileno(stream); } +#else +static int HostDup(int descriptor) { return ::dup(descriptor); } +static int HostDup2(int from, int to) { return ::dup2(from, to) == to ? 0 : -1; } +static int HostClose(int descriptor) { return ::close(descriptor); } +static int HostWrite(int descriptor, const char* text, unsigned size) { return static_cast(::write(descriptor, text, size)); } +static int HostFileno(std::FILE* stream) { return ::fileno(stream); } +#endif + +static std::string Contents(const std::string& name) { + std::ifstream stream(name, std::ios::binary); + return std::string(std::istreambuf_iterator(stream), std::istreambuf_iterator()); +} + +int main() { + const auto sinkName = "anyps5-freopen-host-standard-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".txt"; + std::FILE* sink = std::fopen(sinkName.c_str(), "wb"); + Check(sink != nullptr); + const int original = HostDup(1); + Check(original >= 0 && HostDup2(HostFileno(sink), 1) == 0); + + errno = 0; + FileStream* const reopened = freopen_nid_postfix("", "w", __stdoutp_nid_postfix); + const int error = errno; + + const int written = HostWrite(1, "host", 4); + const bool restored = HostDup2(original, 1) == 0 && HostClose(original) == 0; + Check(std::fclose(sink) == 0); + Check(restored); + Check(reopened == nullptr); + Check(error == ENOENT); + Check(written == 4); + Check(Contents(sinkName) == "host"); + + errno = 0; + Check(fputs_nid_postfix("text", __stdoutp_nid_postfix) == -1 && errno == EBADF); + Check(std::filesystem::remove(sinkName)); + return 0; +} diff --git a/core/libs/tests/GuestFstatFault.cpp b/core/libs/tests/GuestFstatFault.cpp index 4f0aa66bb9..669cb3c903 100644 --- a/core/libs/tests/GuestFstatFault.cpp +++ b/core/libs/tests/GuestFstatFault.cpp @@ -1,23 +1,34 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include +#include #include #include #include #include #include +#include #include -extern "C" { -std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*); -int APS5_VABI sceKernelFstat(int, FileStat*); -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI socketpair_nid_postfix(int, int, int, int*); -int* APS5_VABI __error_nid_postfix(); -} +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif +#ifdef __linux__ +#include +#include +#include +#include +#include +#endif static void Check(bool value, int line) { if (!value) { @@ -37,6 +48,72 @@ static void RequireFailure(int descriptor, int error) { Require(sceKernelFstat(descriptor, nullptr) == static_cast(0x80020000u | static_cast(error))); } +static void RequireBadPointerAt(int descriptor, const char* path, void* address) { + { + auto* bad = static_cast(address); + *__error_nid_postfix() = 0; + Require(fstat_nid_disambig1_nid_postfix(descriptor, bad) == -1 && *__error_nid_postfix() == GuestEfault); + Require(sceKernelFstat(descriptor, bad) == static_cast(0x80020000u | GuestEfault)); + *__error_nid_postfix() = 0; + Require(stat_nid_postfix(path, bad) == -1 && *__error_nid_postfix() == GuestEfault); + *__error_nid_postfix() = 0; + Require(lstat_nid_postfix(path, bad) == -1 && *__error_nid_postfix() == GuestEfault); + Require(sceKernelStat(path, bad) == static_cast(0x80020000u | GuestEfault)); + *__error_nid_postfix() = 0; + Require(_fstatfs_nid_postfix(descriptor, reinterpret_cast(bad)) == -1 && *__error_nid_postfix() == GuestEfault); + } +} + +static void RequireUnwritablePage(int descriptor, const char* path) { +#ifdef _WIN32 + void* page = VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READONLY); + Require(page != nullptr); + void* none = VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_NOACCESS); + Require(none != nullptr); + RequireBadPointerAt(descriptor, path, page); + RequireBadPointerAt(descriptor, path, none); + VirtualFree(page, 0, MEM_RELEASE); + VirtualFree(none, 0, MEM_RELEASE); +#else + void* page = mmap(nullptr, 4096, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(page != MAP_FAILED); + void* none = mmap(nullptr, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(none != MAP_FAILED); + RequireBadPointerAt(descriptor, path, page); + RequireBadPointerAt(descriptor, path, none); + munmap(page, 4096); + munmap(none, 4096); +#endif +} + +#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_writev, 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 { + GuestCopyOut_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() { const auto root = std::filesystem::temp_directory_path() / ("anyps5-fstat-fault-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); @@ -53,6 +130,12 @@ int main() { FileStat status{}; Require(fstat_nid_disambig1_nid_postfix(file, &status) == 0 && status.st_size == 5); RequireFailure(file, GuestEfault); + for (const std::uintptr_t address : {std::uintptr_t{1}, ~std::uintptr_t{0xfff}}) + RequireBadPointerAt(file, "/anyps5-fstat-fault-test/file.txt", reinterpret_cast(address)); + RequireUnwritablePage(file, "/anyps5-fstat-fault-test/file.txt"); +#ifdef __linux__ + RequireNonEfaultFailureThrows(); +#endif int sockets[2]; Require(socketpair_nid_postfix(1, 1, 0, sockets) == 0); @@ -60,10 +143,8 @@ int main() { Require(close_nid_postfix(sockets[0]) == 0); Require(close_nid_postfix(sockets[1]) == 0); Require(close_nid_postfix(file) == 0); -#ifndef _WIN32 RequireFailure(file, GuestEbadf); RequireFailure(-1, GuestEbadf); -#endif std::filesystem::remove_all(root); } diff --git a/core/libs/tests/GuestFstatfs.cpp b/core/libs/tests/GuestFstatfs.cpp index 0c86546ccb..9950e6b9dc 100644 --- a/core/libs/tests/GuestFstatfs.cpp +++ b/core/libs/tests/GuestFstatfs.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -36,15 +39,6 @@ struct GuestStatfs { }; static_assert(sizeof(GuestStatfs) == 472); -extern "C" { -int APS5_VABI _fstatfs_nid_postfix(int, GuestStatfs*); -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI socketpair_nid_postfix(int, int, int, int*); -int* APS5_VABI __error_nid_postfix(); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "fstatfs check failed at line %d\n", line); @@ -63,7 +57,7 @@ static void RequireFailure(int descriptor, int error) { GuestStatfs fs; std::memset(&fs, 0x5a, sizeof(fs)); *__error_nid_postfix() = 0; - Require(_fstatfs_nid_postfix(descriptor, &fs) == -1); + Require(_fstatfs_nid_postfix(descriptor, reinterpret_cast(&fs)) == -1); Require(*__error_nid_postfix() == error); for (std::size_t i = 0; i < sizeof(fs); ++i) Require(reinterpret_cast(&fs)[i] == 0x5a); } @@ -71,7 +65,7 @@ static void RequireFailure(int descriptor, int error) { static void RequireVolume(int descriptor, const std::filesystem::path& path) { GuestStatfs fs; std::memset(&fs, 0x5a, sizeof(fs)); - Require(_fstatfs_nid_postfix(descriptor, &fs) == 0); + Require(_fstatfs_nid_postfix(descriptor, reinterpret_cast(&fs)) == 0); const auto space = std::filesystem::space(path); Require(fs.f_version == StatfsVersion); Require(fs.f_bsize > 0 && fs.f_iosize > 0); @@ -83,6 +77,19 @@ static void RequireVolume(int descriptor, const std::filesystem::path& path) { Require(fs.f_fstypename[0] == '\0' && fs.f_mntfromname[0] == '\0' && fs.f_mntonname[0] == '\0'); } +static void RequireDeviceFilesystem(int descriptor) { + GuestStatfs fs; + std::memset(&fs, 0x5a, sizeof(fs)); + Require(_fstatfs_nid_postfix(descriptor, reinterpret_cast(&fs)) == 0); + GuestStatfs expected{}; + expected.f_version = StatfsVersion; + expected.f_bsize = 512; + expected.f_iosize = 512; + expected.f_blocks = 2; + expected.f_namemax = 255; + Require(std::memcmp(&fs, &expected, sizeof(fs)) == 0); +} + int main() { const auto root = std::filesystem::temp_directory_path() / ("anyps5-fstatfs-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); @@ -122,11 +129,19 @@ int main() { Require(close_nid_postfix(sockets[1]) == 0); RequireFailure(sockets[0], GuestEbadf); + const int random = sceKernelOpen("/dev/urandom", SCE_KERNEL_O_RDONLY, 0); + Require(random >= 0); + RequireDeviceFilesystem(random); + Require(close_nid_postfix(random) == 0); + + const int kqueue = kqueue_nid_postfix(); + Require(kqueue >= 0); + RequireFailure(kqueue, GuestEinval); + Require(close_nid_postfix(kqueue) == 0); + RequireFailure(-1, GuestEbadf); Require(close_nid_postfix(file) == 0); -#ifndef _WIN32 RequireFailure(file, GuestEbadf); -#endif std::filesystem::remove_all(root); } diff --git a/core/libs/tests/GuestFstatvfs.cpp b/core/libs/tests/GuestFstatvfs.cpp index 487e8bacc7..9137793fbb 100644 --- a/core/libs/tests/GuestFstatvfs.cpp +++ b/core/libs/tests/GuestFstatvfs.cpp @@ -1,3 +1,7 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include @@ -11,16 +15,6 @@ #include #include -extern "C" { -int APS5_VABI fstatvfs_nid_postfix(int, void*); -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI socketpair_nid_postfix(int, int, int, int*); -int* APS5_VABI __error_nid_postfix(); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "fstatvfs check failed at line %d\n", line); @@ -34,7 +28,7 @@ static void RequireFailure(int descriptor, int error) { output.fill(0x5A5A5A5A5A5A5A5Aull); const auto saved = output; *__error_nid_postfix() = 0; - Require(fstatvfs_nid_postfix(descriptor, output.data()) == -1); + Require(fstatvfs_nid_postfix(descriptor, reinterpret_cast(output.data())) == -1); Require(*__error_nid_postfix() == error); Require(output == saved); } @@ -43,7 +37,7 @@ static void RequireVolume(int descriptor, const std::filesystem::path& path) { std::array guarded; guarded.fill(0xA5A5A5A5A5A5A5A5ull); *__error_nid_postfix() = 12345; - Require(fstatvfs_nid_postfix(descriptor, guarded.data() + 1) == 0); + Require(fstatvfs_nid_postfix(descriptor, reinterpret_cast(guarded.data() + 1)) == 0); Require(*__error_nid_postfix() == 12345); Require(guarded.front() == 0xA5A5A5A5A5A5A5A5ull && guarded.back() == guarded.front()); const auto* fields = guarded.data() + 1; diff --git a/core/libs/tests/GuestFstatvfsConversion.cpp b/core/libs/tests/GuestFstatvfsConversion.cpp index f93e221e6b..d8533937ad 100644 --- a/core/libs/tests/GuestFstatvfsConversion.cpp +++ b/core/libs/tests/GuestFstatvfsConversion.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/KernelStatfs.hpp" #include @@ -7,8 +8,6 @@ #include #include -extern "C" int APS5_VABI fstatvfs_nid_postfix(int, void*); - namespace { KernelStatfs backend; int backendError; @@ -55,7 +54,7 @@ int main() { }; const int before = calls; guestError = 12345; - Require(fstatvfs_nid_postfix(42, output.data() + 1) == 0); + Require(fstatvfs_nid_postfix(42, reinterpret_cast(output.data() + 1)) == 0); Require(calls == before + 1 && guestError == 12345); Require(output.front() == 0xA5A5A5A5A5A5A5A5ull && output.back() == output.front()); Require(std::memcmp(output.data() + 1, expected.data(), sizeof(expected)) == 0); @@ -63,7 +62,7 @@ int main() { backend.f_bavail = -1; backend.f_ffree = -2; std::array output{}; - Require(fstatvfs_nid_postfix(42, output.data()) == 0); + Require(fstatvfs_nid_postfix(42, reinterpret_cast(output.data())) == 0); Require(output[0] == std::numeric_limits::max()); Require(output[3] == std::numeric_limits::max() - 1 && output[4] == output[3]); Require(fstatvfs_nid_postfix(42, nullptr) == -1 && guestError == 14); @@ -71,7 +70,7 @@ int main() { backendError = failure; output.fill(0x5A5A5A5A5A5A5A5Aull); const auto saved = output; - Require(fstatvfs_nid_postfix(42, output.data()) == -1 && guestError == failure); + Require(fstatvfs_nid_postfix(42, reinterpret_cast(output.data())) == -1 && guestError == failure); Require(output == saved); Require(fstatvfs_nid_postfix(42, nullptr) == -1 && guestError == failure); } diff --git a/core/libs/tests/GuestG2PDialog.cpp b/core/libs/tests/GuestG2PDialog.cpp index b3e0cb2b85..afb2800b64 100644 --- a/core/libs/tests/GuestG2PDialog.cpp +++ b/core/libs/tests/GuestG2PDialog.cpp @@ -1,17 +1,10 @@ +#include "prx/libSceCdlgG2P/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include "prx/libSceUserService/UserService.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include -extern "C" { -int APS5_VABI sceG2PDialogInitialize(void); -int APS5_VABI sceG2PDialogOpen(const void* param); -int APS5_VABI sceG2PDialogUpdateStatus(void); -int APS5_VABI sceG2PDialogGetStatus(void); -int APS5_VABI sceG2PDialogGetResult(void* result); -int APS5_VABI sceG2PDialogClose(void); -int APS5_VABI sceG2PDialogTerminate(void); -} - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -28,13 +21,20 @@ constexpr int kResultUserCanceled = 1; } int main() { - std::int64_t param = 0; - std::int32_t result[2] = {-1, -1}; + Require(sceUserServiceInitialize(nullptr) == 0); + G2PDialogParam 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; + std::int32_t result[4] = {-1, -1, -1, -1}; Require(sceG2PDialogTerminate() == kErrNotInitialized); Require(sceG2PDialogOpen(¶m) == kErrNotInitialized); Require(sceG2PDialogClose() == kErrNotInitialized); - Require(sceG2PDialogGetResult(result) == kErrNotInitialized); + Require(sceG2PDialogGetResult(reinterpret_cast(result)) == kErrNotInitialized); + Require(sceG2PDialogOpen(nullptr) == static_cast(0x80B8000Du)); + Require(sceG2PDialogGetResult(nullptr) == static_cast(0x80B8000Du)); Require(sceG2PDialogGetStatus() == kStatusNone); Require(sceG2PDialogUpdateStatus() == kStatusNone); @@ -44,28 +44,51 @@ int main() { Require(sceG2PDialogUpdateStatus() == kStatusInitialized); Require(sceG2PDialogOpen(nullptr) == kErrArgNull); - Require(sceG2PDialogGetResult(result) == kErrNotFinished); + Require(sceG2PDialogGetResult(reinterpret_cast(result)) == kErrNotFinished); + param.base.size = 0; + Require(sceG2PDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.base.size = sizeof(param.base); + param.base.magic = 0; + Require(sceG2PDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.base.magic = static_cast(reinterpret_cast(¶m)) - 1060003575u; + param.size = 0; + Require(sceG2PDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.size = sizeof(param); + param.reserved[63] = 1; + Require(sceG2PDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.reserved[63] = 0; + param.userId = -1; + Require(sceG2PDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.userId = 42; + Require(sceG2PDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.userId = USER_SERVICE_INITIAL_USER_ID; + Require(sceUserServiceTerminate() == 0); + Require(sceG2PDialogOpen(¶m) == static_cast(0x80B8000Au)); + Require(sceUserServiceInitialize(nullptr) == 0); Require(sceG2PDialogOpen(¶m) == 0); Require(sceG2PDialogGetStatus() == kStatusFinished); Require(sceG2PDialogUpdateStatus() == kStatusFinished); Require(sceG2PDialogGetResult(nullptr) == kErrArgNull); - Require(sceG2PDialogGetResult(result) == 0); - Require(result[0] == kResultUserCanceled); + Require(sceG2PDialogGetResult(reinterpret_cast(result)) == 0); + Require(result[0] == -1 && result[1] == -1 && result[3] == -1); + Require(result[2] == kResultUserCanceled); Require(result[1] == -1); - Require(sceG2PDialogClose() == 0); + Require(sceG2PDialogClose() == static_cast(0x80B8000Bu)); Require(sceG2PDialogTerminate() == 0); Require(sceG2PDialogTerminate() == kErrNotInitialized); Require(sceG2PDialogGetStatus() == kStatusNone); Require(sceG2PDialogInitialize() == 0); Require(sceG2PDialogGetStatus() == kStatusInitialized); - Require(sceG2PDialogClose() == 0); - Require(sceG2PDialogGetStatus() == kStatusFinished); - result[0] = -1; - Require(sceG2PDialogGetResult(result) == 0); - Require(result[0] == kResultUserCanceled); + Require(sceG2PDialogClose() == static_cast(0x80B8000Bu)); + Require(sceG2PDialogGetStatus() == kStatusInitialized); + result[2] = -1; + Require(sceG2PDialogGetResult(reinterpret_cast(result)) == kErrNotFinished); + Require(result[0] == -1 && result[1] == -1 && result[3] == -1); + Require(result[2] == -1); Require(sceG2PDialogTerminate() == 0); + Require(sceUserServiceTerminate() == 0); } diff --git a/core/libs/tests/GuestGameCustomDataDialog.cpp b/core/libs/tests/GuestGameCustomDataDialog.cpp new file mode 100644 index 0000000000..5d5d686a9b --- /dev/null +++ b/core/libs/tests/GuestGameCustomDataDialog.cpp @@ -0,0 +1,63 @@ +#include "prx/libSceGameCustomDataDialog/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80B80003); +constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); +constexpr int kErrNotFinished = static_cast(0x80B80005); +constexpr int kErrArgNull = static_cast(0x80B8000D); +constexpr int kResultUserCanceled = 1; + +} + +int main() { + std::int64_t param = 0; + std::int32_t result[2] = {-1, -1}; + + Require(sceGameCustomDataDialogTerminate() == kErrNotInitialized); + Require(sceGameCustomDataDialogOpenA(¶m) == kErrNotInitialized); + Require(sceGameCustomDataDialogClose() == kErrNotInitialized); + Require(sceGameCustomDataDialogGetResultA(result) == kErrNotInitialized); + Require(sceGameCustomDataDialogGetStatus() == kStatusNone); + Require(sceGameCustomDataDialogUpdateStatus() == kStatusNone); + + Require(sceGameCustomDataDialogInitialize() == 0); + Require(sceGameCustomDataDialogInitialize() == kErrAlreadyInitialized); + Require(sceGameCustomDataDialogGetStatus() == kStatusInitialized); + Require(sceGameCustomDataDialogUpdateStatus() == kStatusInitialized); + + Require(sceGameCustomDataDialogOpenA(nullptr) == kErrArgNull); + Require(sceGameCustomDataDialogGetResultA(result) == kErrNotFinished); + + Require(sceGameCustomDataDialogOpenA(¶m) == 0); + Require(sceGameCustomDataDialogGetStatus() == kStatusFinished); + Require(sceGameCustomDataDialogUpdateStatus() == kStatusFinished); + + Require(sceGameCustomDataDialogGetResultA(nullptr) == kErrArgNull); + Require(sceGameCustomDataDialogGetResultA(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(result[1] == -1); + + Require(sceGameCustomDataDialogClose() == 0); + Require(sceGameCustomDataDialogTerminate() == 0); + Require(sceGameCustomDataDialogTerminate() == kErrNotInitialized); + Require(sceGameCustomDataDialogGetStatus() == kStatusNone); + Require(sceGameCustomDataDialogUpdateStatus() == kStatusNone); + + Require(sceGameCustomDataDialogInitialize() == 0); + Require(sceGameCustomDataDialogUpdateStatus() == kStatusInitialized); + Require(sceGameCustomDataDialogClose() == 0); + Require(sceGameCustomDataDialogUpdateStatus() == kStatusFinished); + result[0] = -1; + Require(sceGameCustomDataDialogGetResultA(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(sceGameCustomDataDialogTerminate() == 0); +} diff --git a/core/libs/tests/GuestGameLiveStreaming.cpp b/core/libs/tests/GuestGameLiveStreaming.cpp index a60c2538cd..7d641c4e23 100644 --- a/core/libs/tests/GuestGameLiveStreaming.cpp +++ b/core/libs/tests/GuestGameLiveStreaming.cpp @@ -1,55 +1,46 @@ +#include "prx/libSceGameLiveStreaming/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include #include #include +#include -struct GameLiveStreamingStatus2 { - std::int32_t userId; - bool isOnAir; - std::uint8_t align[3]; - std::uint32_t spectatorCounts; - std::uint32_t textMessageCounts; - std::uint32_t commandMessageCounts; - std::uint32_t broadcastVideoResolution; - std::uint8_t reserved[48]; -}; static_assert(sizeof(GameLiveStreamingStatus2) == 72); -extern "C" { -int APS5_VABI sceGameLiveStreamingInitialize(std::size_t heap_size); -int APS5_VABI sceGameLiveStreamingTerminate(void); -int APS5_VABI sceGameLiveStreamingGetCurrentStatus2(GameLiveStreamingStatus2* status); -int APS5_VABI sceGameLiveStreamingGetProgramInfo(void); -} - namespace { void Require(bool value) { if (!value) std::abort(); } constexpr std::int32_t kInvalidUserId = -1; +bool EnableThrowsNotInitialized(bool isEnableLiveStreaming) { + try { + sceGameLiveStreamingEnableLiveStreaming(isEnableLiveStreaming); + + } catch (const std::logic_error&) { + return true; + } + return false; +} + } int main() { - bool threwZeroHeap = false; - try { - sceGameLiveStreamingInitialize(0); - } catch (const std::invalid_argument&) { - threwZeroHeap = true; - } - Require(threwZeroHeap); - - Require(sceGameLiveStreamingInitialize(1024 * 1024) == 0); - - bool threwNullStatus = false; - try { - sceGameLiveStreamingGetCurrentStatus2(nullptr); - } catch (const std::invalid_argument&) { - threwNullStatus = true; - } - Require(threwNullStatus); + constexpr int invalidArgument = static_cast(0x80A00002); + constexpr int alreadyInitialized = static_cast(0x80A00003); + constexpr int notInitialized = static_cast(0x80A00004); + Require(sceGameLiveStreamingTerminate() == notInitialized); + Require(sceGameLiveStreamingGetCurrentStatus2(nullptr) == notInitialized); + Require(EnableThrowsNotInitialized(true)); + for (const std::size_t size : {0u, 1u, 0x3fffu, 0x4001u, 0x100000u}) Require(sceGameLiveStreamingInitialize(size) == invalidArgument); + Require(sceGameLiveStreamingInitialize(0x4000) == 0); + Require(sceGameLiveStreamingEnableLiveStreaming(true) == 0); + Require(sceGameLiveStreamingInitialize(0) == invalidArgument); + Require(sceGameLiveStreamingInitialize(0x4000) == alreadyInitialized); + Require(sceGameLiveStreamingGetCurrentStatus2(nullptr) == invalidArgument); GameLiveStreamingStatus2 status{}; std::memset(&status, 0x7f, sizeof(status)); @@ -67,13 +58,22 @@ int main() { Require(byte == 0); } - bool threwProgramInfo = false; - try { - sceGameLiveStreamingGetProgramInfo(); - } catch (const std::runtime_error&) { - threwProgramInfo = true; + Require(sceGameLiveStreamingGetProgramInfo(nullptr) == static_cast(0x80A00002)); + std::array programInfo{}; + programInfo.fill(0x7f); + Require(sceGameLiveStreamingGetProgramInfo(reinterpret_cast(programInfo.data())) == 0); + for (std::size_t i = 0; i < programInfo.size(); ++i) { + Require(programInfo[i] == (i < 0x134C ? 0 : 0x7f)); } - Require(threwProgramInfo); + Require(sceGameLiveStreamingEnableLiveStreaming(false) == 0); Require(sceGameLiveStreamingTerminate() == 0); + Require(EnableThrowsNotInitialized(false)); + std::memset(&status, 0x7f, sizeof(status)); + Require(sceGameLiveStreamingGetCurrentStatus2(&status) == notInitialized); + for (const auto value : std::as_bytes(std::span(&status, 1))) Require(value == std::byte{0x7f}); + programInfo.fill(0x7f); + Require(sceGameLiveStreamingGetProgramInfo(reinterpret_cast(programInfo.data())) == notInitialized); + for (const std::uint8_t byte : programInfo) Require(byte == 0x7f); + Require(sceGameLiveStreamingTerminate() == notInitialized); } diff --git a/core/libs/tests/GuestGameUpdate.cpp b/core/libs/tests/GuestGameUpdate.cpp index 13fcac85c2..34c5c5308b 100644 --- a/core/libs/tests/GuestGameUpdate.cpp +++ b/core/libs/tests/GuestGameUpdate.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceGameUpdate/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -6,18 +7,18 @@ #include #include -extern "C" { -int APS5_VABI sceGameUpdateInitialize(void); -int APS5_VABI sceGameUpdateTerminate(void); -int APS5_VABI sceGameUpdateGetAddcontLatestVersion(std::uint32_t, const void*, GameUpdateAddcontVersionInfo*); -int APS5_VABI sceGameUpdateCreateRequest(void); -int APS5_VABI sceGameUpdateCheck(int, const GameUpdateCheckParam*, GameUpdateCheckResult*); -int APS5_VABI sceGameUpdateAbortRequest(int); -int APS5_VABI sceGameUpdateDeleteRequest(int); -} - static void Require(bool value) { if (!value) std::abort(); } +template +static void RequireThrows(TFunction function) { + try { + function(); + } catch (const std::runtime_error&) { + return; + } + std::abort(); +} + static void TestRequestArguments(int notInitialized, int invalidArgument, int invalidSize, int notFound) { GameUpdateCheckParam param{}; param.size = sizeof(param); @@ -57,13 +58,49 @@ static void TestRequestArguments(int notInitialized, int invalidArgument, int in Require(sceGameUpdateTerminate() == 0); } +static void TestRequestLifecycle(int notFound) { + constexpr int tooManyRequests = static_cast(0x80412806); + constexpr int aborted = static_cast(0x80412807); + GameUpdateCheckParam param{}; + param.size = sizeof(param); + GameUpdateCheckResult result{}; + std::memset(&result, 0xAB, sizeof(result)); + result.size = sizeof(result); + const auto original = result; + + int ids[8]; + for (int& id : ids) { + id = sceGameUpdateCreateRequest(); + Require(id >= 0x20000000 && id <= 0x2FFFFFFF); + } + Require(ids[0] != ids[1]); + Require(sceGameUpdateCreateRequest() == tooManyRequests); + + Require(sceGameUpdateCheck(ids[0], ¶m, &result) == 0); + Require(!result.found && !result.addcont_found); + Require(std::memcmp(&result, &original, 8) == 0); + Require(std::memcmp(reinterpret_cast(&result) + 10, reinterpret_cast(&original) + 10, sizeof(result) - 10) == 0); + + Require(sceGameUpdateAbortRequest(ids[1]) == 0); + Require(sceGameUpdateCheck(ids[1], ¶m, &result) == aborted); + + Require(sceGameUpdateDeleteRequest(ids[3]) == 0); + Require(sceGameUpdateDeleteRequest(ids[3]) == notFound); + Require(sceGameUpdateCheck(ids[3], ¶m, &result) == notFound); + Require(sceGameUpdateCreateRequest() >= 0x20000000); + + Require(sceGameUpdateTerminate() == 0); + Require(sceGameUpdateInitialize() == 0); + Require(sceGameUpdateCheck(ids[0], ¶m, &result) == notFound); +} + int main() { constexpr int notInitialized = static_cast(0x80412801); constexpr int invalidArgument = static_cast(0x80412803); constexpr int invalidSize = static_cast(0x80412804); static_assert(sizeof(GameUpdateAddcontVersionInfo) == 0x30); - const std::uint8_t label[16] = {}; + const NpUnifiedEntitlementLabel label{}; GameUpdateAddcontVersionInfo info{}; info.size = sizeof(info); @@ -73,27 +110,27 @@ int main() { Require(sceGameUpdateTerminate() == notInitialized); TestRequestArguments(notInitialized, invalidArgument, invalidSize, notFound); - Require(sceGameUpdateGetAddcontLatestVersion(0, label, &info) == notInitialized); - Require(sceGameUpdateGetAddcontLatestVersion(0, label, nullptr) == invalidArgument); + Require(sceGameUpdateGetAddcontLatestVersion(0, &label, &info) == notInitialized); + Require(sceGameUpdateGetAddcontLatestVersion(0, &label, nullptr) == invalidArgument); Require(sceGameUpdateGetAddcontLatestVersion(0, nullptr, &info) == invalidArgument); Require(sceGameUpdateInitialize() == 0); Require(sceGameUpdateInitialize() == alreadyInitialized); - Require(sceGameUpdateGetAddcontLatestVersion(0, label, nullptr) == invalidArgument); + Require(sceGameUpdateGetAddcontLatestVersion(0, &label, nullptr) == invalidArgument); Require(sceGameUpdateGetAddcontLatestVersion(0, nullptr, &info) == invalidArgument); info.size = sizeof(info) - 1; - Require(sceGameUpdateGetAddcontLatestVersion(0, label, &info) == invalidSize); + RequireThrows([&] { sceGameUpdateGetAddcontLatestVersion(0, &label, &info); }); std::memset(&info, 0xAB, sizeof(info)); info.size = sizeof(info) + 8; - Require(sceGameUpdateGetAddcontLatestVersion(0, label, &info) == 0); - Require(info.size == sizeof(info) + 8 && !info.found); - const auto* bytes = reinterpret_cast(&info); - for (std::size_t i = sizeof(info.size); i < sizeof(info); ++i) Require(bytes[i] == 0); + const auto original = info; + RequireThrows([&] { sceGameUpdateGetAddcontLatestVersion(0, &label, &info); }); + Require(std::memcmp(&info, &original, sizeof(info)) == 0); + TestRequestLifecycle(notFound); Require(sceGameUpdateTerminate() == 0); Require(sceGameUpdateTerminate() == notInitialized); info.size = sizeof(info); - Require(sceGameUpdateGetAddcontLatestVersion(0, label, &info) == notInitialized); + Require(sceGameUpdateGetAddcontLatestVersion(0, &label, &info) == notInitialized); } diff --git a/core/libs/tests/GuestGenericCategory.cpp b/core/libs/tests/GuestGenericCategory.cpp index b4f3f98687..36026d3f0b 100644 --- a/core/libs/tests/GuestGenericCategory.cpp +++ b/core/libs/tests/GuestGenericCategory.cpp @@ -1,6 +1,8 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/GuestLocale.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include +#include #include #include #include @@ -8,20 +10,13 @@ #include #include -struct Category { void* const* vtable; }; -struct ErrorCode { int value; const Category* category; }; -using Message = GuestLocale::String* (APS5_VABI *)(GuestLocale::String*, const Category*, int); -using Name = const char* (APS5_VABI *)(const Category*); -using Condition = ErrorCode (APS5_VABI *)(const Category*, int); - -extern "C" { -const char* APS5_VABI _ZSt13_Syserror_mapi_nid_postfix(int value); -const Category* APS5_VABI _ZSt16generic_categoryv_nid_postfix(); -GuestLocale::String* APS5_VABI _ZNKSt23_Generic_error_category7messageEi_nid_postfix( - GuestLocale::String* result, const Category* self, int value); -int* APS5_VABI __error_nid_postfix(); -void APS5_VABI _ZdlPv_nid_postfix(void* pointer); -} +using Message = GuestLocale::String* (APS5_VABI *)(GuestLocale::String*, const LibcException::CategoryObject*, int); +using Name = const char* (APS5_VABI *)(const LibcException::CategoryObject*); +using Condition = LibcException::ErrorCode (APS5_VABI *)(const LibcException::CategoryObject*, int); +using EquivalentCondition = bool (APS5_VABI *)(const LibcException::CategoryObject*, int, const LibcException::ErrorCode*); +using EquivalentCode = bool (APS5_VABI *)(const LibcException::CategoryObject*, const LibcException::ErrorCode*, int); +using Delete = void (APS5_VABI *)(const LibcException::CategoryObject*); +struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; }; static void Require(bool value, int line) { if (value) return; @@ -30,7 +25,7 @@ static void Require(bool value, int line) { } #define REQUIRE(value) Require((value), __LINE__) -static void CheckMessage(Message message, const Category* category, int value, const char* expected) { +static void CheckMessage(Message message, const LibcException::CategoryObject* category, int value, const char* expected) { constexpr std::uint64_t marker = 0xA55AA55AA55AA55Aull; struct Guarded { std::uint64_t before; GuestLocale::String result; std::uint64_t after; }; Guarded guarded {marker, {}, marker}; @@ -48,15 +43,48 @@ static void CheckMessage(Message message, const Category* category, int value, c if (length > 15) _ZdlPv_nid_postfix(guarded.result.pointer); } +static void CheckSystemCategory(const LibcException::CategoryObject* generic) { + const LibcException::CategoryObject* system = _ZSt15system_categoryv_nid_postfix(); + REQUIRE(system && system != generic && system == _ZSt15system_categoryv_nid_postfix()); + const auto* type = static_cast(static_cast(system->vtable)[-1]); + REQUIRE(std::strcmp(type->name, "St22_System_error_category") == 0); + REQUIRE(type->base && std::strcmp(type->base->name, "St14error_category") == 0); + REQUIRE(std::strcmp(std::bit_cast(static_cast(system->vtable)[2])(system), "system") == 0); + const auto condition = std::bit_cast(static_cast(system->vtable)[4]); + const LibcException::ErrorCode known = condition(system, 2); + std::uint64_t head = 0; + std::memcpy(&head, &known, sizeof(head)); + REQUIRE(head == std::uint64_t{2} << 32 && known.value == 2 && known.category == generic); + const LibcException::ErrorCode unknown = condition(system, 44); + REQUIRE(unknown.reserved == 0 && unknown.value == 44 && unknown.category == system); + const auto equivalentCondition = std::bit_cast(static_cast(system->vtable)[5]); + REQUIRE(equivalentCondition(system, 2, &known)); + const LibcException::ErrorCode sameCategory {0, 2, system}; + REQUIRE(!equivalentCondition(system, 2, &sameCategory)); + REQUIRE(equivalentCondition(system, 44, &unknown)); + const auto equivalentCode = std::bit_cast(static_cast(system->vtable)[6]); + REQUIRE(equivalentCode(system, &sameCategory, 2) && !equivalentCode(system, &known, 2)); + const auto message = std::bit_cast(static_cast(system->vtable)[3]); + CheckMessage(message, system, 2, "no such file or directory"); + CheckMessage(message, system, 44, "unknown error"); + bool rejected = false; + try { std::bit_cast(static_cast(system->vtable)[1])(system); } + catch (const std::invalid_argument&) { rejected = true; } + REQUIRE(rejected); +} + int main() { const std::array api {}; ApplicationHeapRegister_nid_no_patch(api.data()); - const Category* category = _ZSt16generic_categoryv_nid_postfix(); + const LibcException::CategoryObject* category = _ZSt16generic_categoryv_nid_postfix(); REQUIRE(category && category == _ZSt16generic_categoryv_nid_postfix()); - REQUIRE(std::strcmp(reinterpret_cast(category->vtable[2])(category), "generic") == 0); - const auto message = reinterpret_cast(category->vtable[3]); - const auto condition = reinterpret_cast(category->vtable[4])(category, 35); - REQUIRE(condition.value == 35 && condition.category == category); + REQUIRE(std::strcmp(std::bit_cast(static_cast(category->vtable)[2])(category), "generic") == 0); + const auto message = std::bit_cast(static_cast(category->vtable)[3]); + const auto condition = std::bit_cast(static_cast(category->vtable)[4])(category, 35); + REQUIRE(condition.reserved == 0 && condition.value == 35 && condition.category == category); + const auto equivalent = std::bit_cast(static_cast(category->vtable)[5]); + REQUIRE(equivalent(category, 35, &condition)); + CheckSystemCategory(category); struct Expected { int value; const char* text; }; const Expected expected[] { @@ -89,11 +117,13 @@ int main() { REQUIRE(message(&first, category, 25) == &first && message(&second, category, 11) == &second); REQUIRE(first.pointer != second.pointer); _ZdlPv_nid_postfix(second.pointer); + REQUIRE(std::strcmp(first.pointer, "inappropriate io control operation") == 0); _ZdlPv_nid_postfix(first.pointer); + bool rejected = false; try { message(nullptr, category, 22); } catch (const std::invalid_argument&) { rejected = true; } REQUIRE(rejected); - std::puts("guest generic category, FreeBSD error map and message ownership: PASS"); + std::puts("guest generic and system categories, FreeBSD error map and message ownership: PASS"); } diff --git a/core/libs/tests/GuestGetitimer.cpp b/core/libs/tests/GuestGetitimer.cpp index 1fa5f82516..ae97af4f05 100644 --- a/core/libs/tests/GuestGetitimer.cpp +++ b/core/libs/tests/GuestGetitimer.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -10,19 +12,14 @@ #include #include -struct GuestItimerval { - KernelTimeval interval; - KernelTimeval value; -}; - static_assert(sizeof(GuestItimerval) == 32 && offsetof(GuestItimerval, value) == 16); extern "C" { -int APS5_VABI getitimer_nid_postfix(int, GuestItimerval*); + unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int); -int* APS5_VABI __error_nid_postfix(); + using Handler = void (APS5_VABI *)(int); -Handler APS5_VABI signal_nid_postfix(int, Handler); + } static void Require(bool value, const char* message) { diff --git a/core/libs/tests/GuestGetlogin.cpp b/core/libs/tests/GuestGetlogin.cpp index b006a283fc..385c37ce1a 100644 --- a/core/libs/tests/GuestGetlogin.cpp +++ b/core/libs/tests/GuestGetlogin.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -16,9 +18,6 @@ #include #endif -extern "C" int APS5_VABI getlogin_r_nid_postfix(char*, int); -extern "C" int* APS5_VABI __error_nid_postfix(); - static void Check(bool condition, int line) { if (!condition) { std::fprintf(stderr, "getlogin_r check failed at line %d\n", line); diff --git a/core/libs/tests/GuestGetloginNoSession.cpp b/core/libs/tests/GuestGetloginNoSession.cpp index c9f29f5513..fc31313d74 100644 --- a/core/libs/tests/GuestGetloginNoSession.cpp +++ b/core/libs/tests/GuestGetloginNoSession.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,8 +9,6 @@ #include #include -extern "C" int APS5_VABI getlogin_r_nid_postfix(char*, int); - static int loginError = ENXIO; extern "C" int getlogin_r(char* output, std::size_t size) { diff --git a/core/libs/tests/GuestGetopt.cpp b/core/libs/tests/GuestGetopt.cpp index 26e0ca0803..13db14bc90 100644 --- a/core/libs/tests/GuestGetopt.cpp +++ b/core/libs/tests/GuestGetopt.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -7,19 +9,8 @@ #include #include -struct GuestOption { - const char* name; - int hasArgument; - int* flag; - int value; -}; - extern "C" { -int APS5_VABI getopt_nid_postfix(int, char* const*, const char*); -int APS5_VABI getopt_long_nid_postfix(int, char* const*, const char*, const GuestOption*, int*); -int APS5_VABI getopt_long_only_nid_postfix(int, char* const*, const char*, const GuestOption*, int*); -int APS5_VABI setenv_nid_postfix(const char*, const char*, int); -int APS5_VABI unsetenv_nid_postfix(const char*); + extern int opterr_nid_postfix; extern int optind_nid_postfix; extern int optopt_nid_postfix; diff --git a/core/libs/tests/GuestGetrlimit.cpp b/core/libs/tests/GuestGetrlimit.cpp index 5d6d5d2999..9f2a39f589 100644 --- a/core/libs/tests/GuestGetrlimit.cpp +++ b/core/libs/tests/GuestGetrlimit.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -18,20 +20,6 @@ #include #endif -struct ResourceLimit { - std::int64_t rlim_cur; - std::int64_t rlim_max; -}; - -extern "C" int APS5_VABI getrlimit_nid_postfix(int resource, ResourceLimit* limit); -extern "C" int* APS5_VABI __error_nid_postfix(); -extern "C" int APS5_VABI getdtablesize_nid_postfix(void); -extern "C" int APS5_VABI setrlimit_nid_postfix(int resource, const ResourceLimit* limit); -extern "C" int APS5_VABI pipe_nid_postfix(int* descriptors); -extern "C" int APS5_VABI close_nid_postfix(int descriptor); -extern "C" void* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode); -extern "C" int APS5_VABI fclose_nid_postfix(void* stream); - static constexpr int GUEST_EFAULT = 14; static constexpr int GUEST_EINVAL = 22; static constexpr int GUEST_RLIMIT_CPU = 0; @@ -54,15 +42,15 @@ static constexpr std::int64_t GUEST_RLIM_INFINITY = std::numeric_limits= 0 && limit.rlim_cur <= limit.rlim_max); return limit; } #if defined(_WIN32) || defined(__APPLE__) -static void RequireDescriptorsEndAt(const ResourceLimit& limit) { +static void RequireDescriptorsEndAt(const GuestResourceLimit& limit) { constexpr int GUEST_EMFILE = 24; std::vector descriptors; int pair[2]{-1, -1}; @@ -82,19 +70,19 @@ static void RequireDescriptorsEndAt(const ResourceLimit& limit) { #ifdef _WIN32 static void RequireStreamsOpen() { const auto path = (std::filesystem::temp_directory_path() / ("aps5-open-streams-" + std::to_string(GetCurrentProcessId()))).string(); - std::vector streams; + std::vector streams; for (int index = 0; index < 1024; ++index) { - void* stream = fopen_nid_postfix(path.c_str(), "ab"); + FileStream* stream = fopen_nid_postfix(path.c_str(), "ab"); Require(stream != nullptr); streams.push_back(stream); } - for (void* stream : streams) Require(fclose_nid_postfix(stream) == 0); + for (FileStream* stream : streams) Require(fclose_nid_postfix(stream) == 0); std::filesystem::remove(path); } #endif static void CheckOpenFileLimit() { - const ResourceLimit limit = GuestLimit(GUEST_RLIMIT_NOFILE); + const GuestResourceLimit limit = GuestLimit(GUEST_RLIMIT_NOFILE); Require(limit.rlim_cur == limit.rlim_max); #ifdef _WIN32 Require(limit.rlim_cur == 8192); @@ -114,20 +102,21 @@ static void CheckOpenFileLimit() { RequireDescriptorsEndAt(limit); #endif Require(setrlimit_nid_postfix(GUEST_RLIMIT_NOFILE, &limit) == 0); - const ResourceLimit lowered{limit.rlim_max > 1 ? limit.rlim_max - 1 : 1, limit.rlim_max}; + const GuestResourceLimit lowered{limit.rlim_max > 1 ? limit.rlim_max - 1 : 1, limit.rlim_max}; Require(setrlimit_nid_postfix(GUEST_RLIMIT_NOFILE, &lowered) == 0); - const ResourceLimit updated = GuestLimit(GUEST_RLIMIT_NOFILE); + const GuestResourceLimit updated = GuestLimit(GUEST_RLIMIT_NOFILE); Require(updated.rlim_cur == lowered.rlim_cur); Require(updated.rlim_max == lowered.rlim_max); Require(setrlimit_nid_postfix(GUEST_RLIMIT_NOFILE, &limit) == 0); - const ResourceLimit restored = GuestLimit(GUEST_RLIMIT_NOFILE); + const GuestResourceLimit restored = GuestLimit(GUEST_RLIMIT_NOFILE); Require(restored.rlim_cur == limit.rlim_cur); Require(restored.rlim_max == limit.rlim_max); if (limit.rlim_max != GUEST_RLIM_INFINITY) { - const ResourceLimit raised{limit.rlim_cur, limit.rlim_max + 1}; + const GuestResourceLimit raised{limit.rlim_cur, limit.rlim_max + 1}; bool raisedRejected = false; try { setrlimit_nid_postfix(GUEST_RLIMIT_NOFILE, &raised); + } catch (const std::runtime_error&) { raisedRejected = true; } @@ -135,12 +124,17 @@ static void CheckOpenFileLimit() { } } -static void RequireError(int resource, ResourceLimit* limit, int error) { +static void RequireError(int resource, GuestResourceLimit* limit, int error) { *__error_nid_postfix() = 0; Require(getrlimit_nid_postfix(resource, limit) == -1); Require(*__error_nid_postfix() == error); } +static void RequireInvalidOutputPointer(int resource) { + auto* const invalid = reinterpret_cast(static_cast(1)); + RequireError(resource, invalid, GUEST_EFAULT); +} + #ifndef _WIN32 static std::int64_t Guest(rlim_t value) { return value == RLIM_INFINITY ? GUEST_RLIM_INFINITY : static_cast(value); @@ -149,7 +143,7 @@ static std::int64_t Guest(rlim_t value) { static void RequireHostLimit(int resource, int hostResource) { rlimit host{}; Require(getrlimit(hostResource, &host) == 0); - const ResourceLimit guest = GuestLimit(resource); + const GuestResourceLimit guest = GuestLimit(resource); Require(guest.rlim_cur == Guest(host.rlim_cur)); Require(guest.rlim_max == Guest(host.rlim_max)); } @@ -185,24 +179,26 @@ int main() { GUEST_RLIMIT_KQUEUES, GUEST_RLIMIT_UMTXP, }) { - const ResourceLimit guest = GuestLimit(resource); + const GuestResourceLimit guest = GuestLimit(resource); Require(guest.rlim_cur == GUEST_RLIM_INFINITY); Require(guest.rlim_max == GUEST_RLIM_INFINITY); RequireError(resource, nullptr, GUEST_EFAULT); } - ResourceLimit limit{}; + GuestResourceLimit limit{}; RequireError(GUEST_RLIM_NLIMITS, &limit, GUEST_EINVAL); RequireError(-1, &limit, GUEST_EINVAL); RequireError(GUEST_RLIM_NLIMITS, nullptr, GUEST_EINVAL); RequireError(-1, nullptr, GUEST_EINVAL); RequireError(GUEST_RLIMIT_DATA, nullptr, GUEST_EFAULT); - const ResourceLimit stack = GuestLimit(GUEST_RLIMIT_STACK); + RequireInvalidOutputPointer(GUEST_RLIMIT_DATA); + RequireInvalidOutputPointer(GUEST_RLIMIT_NOFILE); + const GuestResourceLimit stack = GuestLimit(GUEST_RLIMIT_STACK); Require(stack.rlim_cur == 0x200000 && stack.rlim_max == 0x200000); RequireError(GUEST_RLIMIT_STACK, nullptr, GUEST_EFAULT); - const ResourceLimit processes = GuestLimit(GUEST_RLIMIT_NPROC); + const GuestResourceLimit processes = GuestLimit(GUEST_RLIMIT_NPROC); Require(processes.rlim_cur == 1604 && processes.rlim_max == 1604); RequireError(GUEST_RLIMIT_NPROC, nullptr, GUEST_EFAULT); - const ResourceLimit nofile = GuestLimit(GUEST_RLIMIT_NOFILE); + const GuestResourceLimit nofile = GuestLimit(GUEST_RLIMIT_NOFILE); const int tableSize = getdtablesize_nid_postfix(); std::int64_t expectedTableSize = nofile.rlim_cur; #ifndef _WIN32 diff --git a/core/libs/tests/GuestGetrusage.cpp b/core/libs/tests/GuestGetrusage.cpp index 7fefbdca94..5c37bcc0b6 100644 --- a/core/libs/tests/GuestGetrusage.cpp +++ b/core/libs/tests/GuestGetrusage.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,16 +10,10 @@ struct Timeval { std::int64_t tv_sec; std::int64_t tv_usec; }; -struct ResourceUsage { - Timeval ru_utime; - Timeval ru_stime; - std::int64_t rest[14]; -}; -extern "C" int APS5_VABI getrusage_nid_postfix(int who, ResourceUsage* usage); -extern "C" int* APS5_VABI __error_nid_postfix(); + static void Require(bool value) { if (!value) std::abort(); } static std::int64_t CpuMicros(int who) { - ResourceUsage usage{}; + GuestResourceUsage usage{}; Require(getrusage_nid_postfix(who, &usage) == 0); return (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * 1000000 + usage.ru_utime.tv_usec + usage.ru_stime.tv_usec; } @@ -29,15 +25,15 @@ static void SpinUntil(int who, std::int64_t micros) { Require(std::chrono::steady_clock::now() < deadline); } } -static void RequireFailure(int who, ResourceUsage* usage, int error) { +static void RequireFailure(int who, GuestResourceUsage* usage, int error) { *__error_nid_postfix() = 0; Require(getrusage_nid_postfix(who, usage) == -1); Require(*__error_nid_postfix() == error); } int main() { - ResourceUsage untouched; + GuestResourceUsage untouched; std::memset(&untouched, 0x5a, sizeof(untouched)); - const ResourceUsage pattern = untouched; + const GuestResourceUsage pattern = untouched; RequireFailure(2, &untouched, 22); RequireFailure(-2, &untouched, 22); Require(std::memcmp(&untouched, &pattern, sizeof(untouched)) == 0); @@ -45,10 +41,15 @@ int main() { RequireFailure(0, nullptr, 14); RequireFailure(1, nullptr, 14); RequireFailure(-1, nullptr, 14); - ResourceUsage children; + auto* const invalid = reinterpret_cast(static_cast(1)); + RequireFailure(2, invalid, 22); + RequireFailure(0, invalid, 14); + RequireFailure(1, invalid, 14); + RequireFailure(-1, invalid, 14); + GuestResourceUsage children; std::memset(&children, 0xff, sizeof(children)); Require(getrusage_nid_postfix(-1, &children) == 0); - const ResourceUsage empty{}; + const GuestResourceUsage empty{}; Require(std::memcmp(&children, &empty, sizeof(children)) == 0); SpinUntil(0, 400000); const std::int64_t mainThread = CpuMicros(1); diff --git a/core/libs/tests/GuestGets.cpp b/core/libs/tests/GuestGets.cpp new file mode 100644 index 0000000000..4862729c19 --- /dev/null +++ b/core/libs/tests/GuestGets.cpp @@ -0,0 +1,156 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +extern "C" { +extern FileStream* __stdinp_nid_postfix; + +} + +namespace { + +using Gets = char* (APS5_VABI*)(char*); +Gets readLine; + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "gets: %s\n", message); + std::abort(); +} + +class Input { + FileStream* previous = __stdinp_nid_postfix; + FileStream stream; + +public: + explicit Input(std::FILE* handle) : stream(handle) { __stdinp_nid_postfix = &stream; } + Input(const void* bytes, std::size_t size) : stream(std::tmpfile()) { + Require(std::fwrite(bytes, 1, size, stream.GetHandle()) == size, "seed input"); + std::rewind(stream.GetHandle()); + __stdinp_nid_postfix = &stream; + } + ~Input() { + __stdinp_nid_postfix = previous; + if (stream.IsOpen()) stream.Close(); + } + FileStream& Stream() { return stream; } +}; + +void CheckBytes() { + std::array bytes; + std::size_t size = 0; + for (unsigned value = 0; value < 256; ++value) + if (value != '\n') bytes[size++] = static_cast(value); + bytes[size++] = '\n'; + Input input(bytes.data(), size); + std::array output; + output.fill(0xa5); + *__error_nid_postfix() = 123; + Require(readLine(reinterpret_cast(output.data() + 4)) == reinterpret_cast(output.data() + 4), "byte read pointer"); + const int error = *__error_nid_postfix(); + Require(std::memcmp(output.data() + 4, bytes.data(), 255) == 0 && output[259] == 0, "all byte values and terminator"); + Require(error == 123, "successful read preserves errno"); + for (std::size_t index = 0; index < 4; ++index) Require(output[index] == 0xa5, "prefix guard"); + for (std::size_t index = 260; index < output.size(); ++index) Require(output[index] == 0xa5, "suffix guard"); + output.fill(0xa5); + Require(readLine(reinterpret_cast(output.data() + 4)) == nullptr, "EOF return"); + for (const auto value : output) Require(value == 0xa5, "initial EOF leaves buffer unchanged"); + Require((input.Stream().GuestState().flags & 0x20) != 0, "guest EOF flag"); +} + +void CheckLines() { + const char text[] = "\nfirst\r\nlast"; + Input input(text, sizeof(text) - 1); + char output[16]; + Require(readLine(output) == output && output[0] == 0, "empty line"); + Require(readLine(output) == output && std::strcmp(output, "first\r") == 0, "newline removed without dropping carriage return"); + Require(readLine(output) == output && std::strcmp(output, "last") == 0, "partial last line at EOF"); + output[0] = 'x'; + Require(readLine(output) == nullptr && output[0] == 'x', "repeated EOF preserves destination"); +} + +void CheckErrors() { + Input input("", 0); + input.Stream().Close(); + char output[8] = "keep"; + Require(readLine(output) == nullptr && std::strcmp(output, "keep") == 0 && *__error_nid_postfix() == 9, "closed input"); + bool threw = false; + try { readLine(nullptr); } catch (const std::invalid_argument&) { threw = true; } + Require(threw, "null buffer is explicit unsupported input"); +} + +void CheckReadError(const char* path) { + { + Input input(std::fopen(path, "wb")); + std::array output; + output.fill('x'); + Require(readLine(output.data()) == nullptr, "write-only input read error"); + for (const auto value : output) Require(value == 'x', "initial read error leaves destination unchanged"); + Require((input.Stream().GuestState().flags & 0x40) != 0, "guest error flag"); + } + Require(std::remove(path) == 0, "remove error fixture"); +} + +void CheckThreads() { + constexpr unsigned lines = 2048; + std::array text; + for (unsigned index = 0; index < lines; ++index) + std::snprintf(text.data() + index * 8, 9, "%07u\n", index); + Input input(text.data(), lines * 8); + std::array, lines> seen{}; + std::atomic invalid{false}; + std::vector workers; + for (unsigned thread = 0; thread < 8; ++thread) workers.emplace_back([&] { + char output[lines * 8 + 1]; + while (readLine(output)) { + if (std::strlen(output) != 7) { invalid = true; continue; } + unsigned index = 0; + bool digits = true; + for (unsigned digit = 0; digit < 7; ++digit) { + if (output[digit] < '0' || output[digit] > '9') { digits = false; break; } + index = index * 10 + static_cast(output[digit] - '0'); + } + if (!digits || index >= lines) invalid = true; + else ++seen[index]; + } + }); + for (auto& worker : workers) worker.join(); + Require(!invalid, "concurrent line contents"); + for (auto& count : seen) Require(count == 1, "each line read exactly once"); +} + +} + +int main(int argc, char** argv) { + Require(argc == 3, "expected libc path and scratch file"); +#ifdef _WIN32 + const auto library = LoadLibraryA(argv[1]); + Require(library != nullptr, "load libc"); + readLine = reinterpret_cast(GetProcAddress(library, "gets_nid_postfix")); +#else + const auto library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + Require(library != nullptr, "load libc"); + readLine = reinterpret_cast(dlsym(library, "gets_nid_postfix")); +#endif + Require(readLine != nullptr, "missing gets export"); + CheckBytes(); + CheckLines(); + CheckErrors(); + CheckReadError(argv[2]); + CheckThreads(); + std::puts("gets checks passed"); +} diff --git a/core/libs/tests/GuestGnmNeoDefaultState.cpp b/core/libs/tests/GuestGnmNeoDefaultState.cpp new file mode 100644 index 0000000000..c4731f75ea --- /dev/null +++ b/core/libs/tests/GuestGnmNeoDefaultState.cpp @@ -0,0 +1,69 @@ +#include "prx/libSceGnmDriverForNeoMode/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace { + +using StateWriter = int (APS5_VABI *)(std::uint32_t*, std::uint32_t); + +constexpr std::uint32_t Untouched = 0xa5a5a5a5u; +constexpr std::uint32_t BufferWords = 0x110; + +std::uint32_t g_words[BufferWords]; + +void Require(bool value) { if (!value) std::abort(); } + +std::uint32_t* Fresh() { + for (auto& word : g_words) word = Untouched; + return g_words; +} + +void RequireAt(std::uint32_t at, std::initializer_list expected) { + for (const auto word : expected) Require(g_words[at++] == word); +} + +void RequireUntouchedFrom(std::uint32_t at) { + for (; at < BufferWords; ++at) Require(g_words[at] == Untouched); +} + +void CheckHardwareState(StateWriter writer, std::uint32_t stateWords, std::initializer_list tail) { + Require(writer(Fresh(), 0xff) == 0); + RequireUntouchedFrom(0); + + Require(writer(Fresh(), 0x100) == 0x100); + RequireAt(0, {0xc0012800u, 0x80000000u, 0x80000000u, 0xc0001200u, 0, 0xc0055800u, 0x2ec47fc0u, 0xffffffffu, 0, 0, 0, 10}); + RequireAt(12, {0xc0017600u, 0x216, 0xffffffffu, 0xc0017600u, 0x217, 0xffffffffu}); + const std::uint32_t end = 12 + stateWords; + RequireAt(end - static_cast(tail.size()), tail); + const std::uint32_t padding = 0x100 - end - 1; + RequireAt(end, {0xc0001000u | ((padding - 1) << 16), 0}); + for (std::uint32_t at = end + 2; at < 0x100; ++at) Require(g_words[at] == Untouched); + RequireUntouchedFrom(0x100); + + Require(writer(Fresh(), 0x10f) == 0x100); + RequireUntouchedFrom(0x100); +} + +void CheckContextState(StateWriter writer, std::uint32_t packetWords, std::uint32_t stateWords, std::initializer_list tail) { + Require(writer(Fresh(), packetWords - 1) == 0); + RequireUntouchedFrom(0); + Require(writer(Fresh(), packetWords + 1) == 0); + RequireUntouchedFrom(0); + + Require(writer(Fresh(), packetWords) == static_cast(packetWords)); + RequireAt(0, {0xc0012800u, 0x80000000u, 0x80000000u, 0xc0001200u, 0}); + RequireAt(stateWords - static_cast(tail.size()), tail); + RequireUntouchedFrom(stateWords); +} + +} // namespace + +int main() { + CheckHardwareState(sceGnmDrawInitDefaultHardwareState, 115, {0x100, 4, 0xc0017900u, 0x200, 0xe0000000u}); + CheckHardwareState(sceGnmDrawInitDefaultHardwareState200, 131, {0x200, 0xe0000000u, 0xc0017900u, 0x40000258u, 0x6d007fu}); + CheckHardwareState(sceGnmDrawInitDefaultHardwareState350, 134, {0x200, 0xe0000000u, 0xc0017900u, 0x40000258u, 0x6d007fu}); + CheckContextState(sceGnmDrawInitToDefaultContextState, 0x20, 19, {0xff00ff00u, 0xff00, 0xc00d1000u, 0}); + CheckContextState(sceGnmDrawInitToDefaultContextState400, 0x100, 102, {0x40000258u, 0x6d007fu, 0xc09a1000u, 0}); +} diff --git a/core/libs/tests/GuestGnmNeoEmbedded.cpp b/core/libs/tests/GuestGnmNeoEmbedded.cpp new file mode 100644 index 0000000000..fb8f7caa80 --- /dev/null +++ b/core/libs/tests/GuestGnmNeoEmbedded.cpp @@ -0,0 +1,73 @@ +#include "prx/libSceGnmDriverForNeoMode/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace { + +constexpr int Failure = static_cast(0x8EEE00FFu); +constexpr std::uint32_t Untouched = 0xa5a5a5a5u; +constexpr std::uint32_t BufferWords = 48; + +std::uint32_t g_words[BufferWords]; + +void Require(bool value) { if (!value) std::abort(); } + +std::uint32_t* Fresh() { + for (auto& word : g_words) word = Untouched; + return g_words; +} + +void RequireAt(std::uint32_t at, std::initializer_list expected) { + for (const auto word : expected) Require(g_words[at++] == word); +} + +void RequireUntouchedFrom(std::uint32_t at) { + for (; at < BufferWords; ++at) Require(g_words[at] == Untouched); +} + +const std::uint32_t* ShaderCode() { + const auto address = (static_cast(g_words[3]) << 40) | (static_cast(g_words[2]) << 8); + return reinterpret_cast(address); +} + +void CheckVs() { + Require(sceGnmSetEmbeddedVsShader(Fresh(), 0x1d, 0, 0) == 0); + RequireAt(0, {0xc0027600u, 0x48}); + RequireAt(4, {0xc0027600u, 0x4a, 0xc0000, 4, 0xc0016900u, 0x207, 0, 0xc0016900u, 0x1b1, 0, 0xc0016900u, 0x1c3, 4, + 0xc00a1000u, 0}); + RequireUntouchedFrom(19); + const auto* code = ShaderCode(); + Require(code[0] == 0xbeeb0387u && code[10] == 0xf80008cfu && code[11] == 0x03020001u && code[14] == 0xbf810000u); + + Require(sceGnmSetEmbeddedVsShader(Fresh(), 0x1d, 1, 0) == Failure); + RequireUntouchedFrom(0); +} + +void CheckPs() { + Require(sceGnmSetEmbeddedPsShader(Fresh(), 0x28, 0, 0) == 0); + RequireAt(0, {0xc0027600u, 8}); + RequireAt(4, {0xc0027600u, 10, 0xc0000, 4, 0xc0026900u, 0x1c4, 0, 4, 0xc0026900u, 0x1b3, 2, 2, 0xc0016900u, 0x1b6, 0, + 0xc0016900u, 0x1b8, 0, 0xc0016900u, 0x203, 0x10, 0xc0016900u, 0x8f, 0xf, 0xc00a1000u, 0}); + RequireUntouchedFrom(30); + const auto* black = ShaderCode(); + Require(black[0] == 0xbeeb0383u && black[2] == 0x5e000100u && black[4] == 0xf8001c0fu && black[6] == 0xbf810000u); + + Require(sceGnmSetEmbeddedPsShader(Fresh(), 0x28, 1, 0) == 0); + RequireAt(4, {0xc0027600u, 10, 0x2000, 0, 0xc0026900u, 0x1c4, 0, 2, 0xc0026900u, 0x1b3, 2, 2, 0xc0016900u, 0x1b6, 0, + 0xc0016900u, 0x1b8, 0, 0xc0016900u, 0x203, 0x10, 0xc0016900u, 0x8f, 3, 0xc00a1000u, 0}); + RequireUntouchedFrom(30); + const auto* depthOnly = ShaderCode(); + Require(depthOnly != black && depthOnly[1] == 0x7e040280u && depthOnly[2] == 0xf8001803u && depthOnly[4] == 0xbf810000u); + + Require(sceGnmSetEmbeddedPsShader(Fresh(), 0x28, 2, 0) == Failure); + RequireUntouchedFrom(0); +} + +} // namespace + +int main() { + CheckVs(); + CheckPs(); +} diff --git a/core/libs/tests/GuestGnmNeoPackets.cpp b/core/libs/tests/GuestGnmNeoPackets.cpp new file mode 100644 index 0000000000..b31a5d0897 --- /dev/null +++ b/core/libs/tests/GuestGnmNeoPackets.cpp @@ -0,0 +1,136 @@ +#include "prx/libSceGnmDriverForNeoMode/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace { + +constexpr std::uint32_t Untouched = 0xa5a5a5a5u; +constexpr std::uint32_t U = Untouched; + +std::uint32_t g_words[16]; + +void Require(bool value) { if (!value) std::abort(); } + +std::uint32_t* Fresh() { + for (auto& word : g_words) word = Untouched; + return g_words; +} + +void RequireWords(std::initializer_list expected) { + std::size_t at = 0; + for (const auto word : expected) Require(g_words[at++] == word); + for (; at < 16; ++at) Require(g_words[at] == Untouched); +} + +void RequireRefused(int result) { + Require(result == -1); + RequireWords({}); +} + +void CheckDirectDraws() { + Require(sceGnmInsertDingDongMarker(Fresh(), 4) == 0); + RequireWords({0xc0021000u, 0, 0, 0}); + RequireRefused(sceGnmInsertDingDongMarker(Fresh(), 3)); + Require(sceGnmInsertDingDongMarker(nullptr, 4) == -1); + + Require(sceGnmDrawIndexAuto(Fresh(), 7, 36, 0xe0000001u) == 0); + RequireWords({0xc0012d00u, 36, 0xe0000002u, 0xc0021000u, 0, U, U}); + RequireRefused(sceGnmDrawIndexAuto(Fresh(), 7, 36, 2)); + RequireRefused(sceGnmDrawIndexAuto(Fresh(), 6, 36, 0)); + + Require(sceGnmDrawIndex(Fresh(), 10, 99, 0x123456789abcull, 0x20000000u, 0) == 0); + RequireWords({0xc0042700u, 99, 0x56789abcu, 0x1234, 99, 0x20000000u, 0xc0021000u, 0, U, U}); + RequireRefused(sceGnmDrawIndex(Fresh(), 10, 99, 0x1001, 0, 0)); + RequireRefused(sceGnmDrawIndex(Fresh(), 10, 99, 0, 0, 0)); + RequireRefused(sceGnmDrawIndex(Fresh(), 10, 99, 0x1000, 0x10, 0)); + + Require(sceGnmDrawIndexOffset(Fresh(), 9, 4, 12, 0x40000001u) == 0); + RequireWords({0xc0033501u, 12, 4, 12, 0x40000000u, 0xc0021000u, 0, U, U}); + RequireRefused(sceGnmDrawIndexOffset(Fresh(), 10, 4, 12, 0)); +} + +void CheckDispatches() { + Require(sceGnmDispatchDirect(Fresh(), 9, 8, 4, 2, 0x19) == 0); + RequireWords({0xc0031503u, 8, 4, 2, 0x19, 0xc0021000u, 0, U, U}); + RequireRefused(sceGnmDispatchDirect(Fresh(), 9, 0x80000000u, 1, 1, 0)); + RequireRefused(sceGnmDispatchDirect(Fresh(), 8, 1, 1, 1, 0)); + + Require(sceGnmDispatchIndirect(Fresh(), 7, 0x40, 0x8) == 0); + RequireWords({0xc0011602u, 0x40, 9, 0xc0021000u, 0, U, U}); + RequireRefused(sceGnmDispatchIndirect(Fresh(), 9, 0x40, 0)); + + Require(sceGnmDispatchIndirectOnMec(Fresh(), 8, 0x700001000ull, 0x19) == 0); + RequireWords({0xc0021603u, 0x1000, 7, 0x19, 0xc0021000u, 0}); + RequireRefused(sceGnmDispatchIndirectOnMec(Fresh(), 8, 0x700001002ull, 0)); + RequireRefused(sceGnmDispatchIndirectOnMec(Fresh(), 8, 0, 0)); +} + +void CheckIndirectDraws() { + Require(sceGnmDrawIndirect(Fresh(), 9, 0x20, 2, 3, 5, 0x20000001u) == 0); + RequireWords({0xc0032401u, 0x20, 0x4f, 0x51, 0x20000002u, 0xc0021000u, 0, U, U}); + RequireRefused(sceGnmDrawIndirect(Fresh(), 9, 0x20, 7, 0, 0, 0)); + RequireRefused(sceGnmDrawIndirect(Fresh(), 9, 0x20, 2, 0x10, 0, 0)); + + Require(sceGnmDrawIndexIndirect(Fresh(), 9, 0x60, 4, 1, 0, 0x1) == 0); + RequireWords({0xc0032501u, 0x60, 0xcd, 0, 0, 0xc0021000u, 0, U, U}); + RequireRefused(sceGnmDrawIndexIndirect(Fresh(), 9, 0x60, 4, 0, 0x10, 0)); + + Require(sceGnmDrawIndirectMulti(Fresh(), 11, 0x30, 6, 6, 2, 0, 0x80000000u) == 0); + RequireWords({0xc0052c00u, 0x30, 0x14e, 0, 6, 0x10, 0x80000002u, 0xc0021000u, 0, U, U}); + RequireRefused(sceGnmDrawIndirectMulti(Fresh(), 9, 0x30, 6, 6, 2, 0, 0)); + + Require(sceGnmDrawIndexIndirectMulti(Fresh(), 11, 0x30, 6, 2, 0, 1, 0x60000000u) == 0); + RequireWords({0xc0053800u, 0x30, 0, 0x4d, 6, 0x14, 0x60000000u, 0xc0021000u, 0, U, U}); + RequireRefused(sceGnmDrawIndexIndirectMulti(Fresh(), 11, 0x30, 6, 0, 0, 0, 0)); + RequireRefused(sceGnmDrawIndexIndirectMulti(Fresh(), 11, 0x30, 6, 3, 0, 0, 0)); + + Require(sceGnmDrawIndirectCountMulti(Fresh(), 16, 0, 5, 0x200000040ull, 2, 1, 1, 1) == 0); + RequireWords({0xc0011000u, 0, U, 0xc0089c01u, 0, 0x4d, 0x4d, 0x40000000u, 5, 0x40, 2, 0x10, 2, 0xc0011000u, 0, U}); + RequireRefused(sceGnmDrawIndirectCountMulti(Fresh(), 15, 0, 5, 0, 2, 1, 1, 0)); + + Require(sceGnmDrawIndexIndirectCountMulti(Fresh(), 16, 0x14, 3, 0, 6, 0, 0, 0xe0000000u) == 0); + RequireWords({0xc0011000u, 0, U, 0xc0089d00u, 0x14, 0, 0, 0, 3, 0, 0, 0x14, 0xe0000000u, 0xc0011000u, 0, U}); + RequireRefused(sceGnmDrawIndexIndirectCountMulti(Fresh(), 16, 0, 3, 0, 5, 0, 0, 0)); +} + +void CheckComputeWaits() { + Require(sceGnmComputeWaitSemaphore(Fresh(), 10, 0x123456780ull, 1) == 0); + RequireWords({0xc0021000u, 0, U, U, 0xc0013900u, 0x23456780u, 0xe0100001u, 0xc0011000u, 0, U}); + Require(sceGnmComputeWaitSemaphore(Fresh(), 10, 0x123456780ull, 0) == 0); + Require(g_words[6] == 0xe0000001u); + RequireRefused(sceGnmComputeWaitSemaphore(Fresh(), 9, 0, 0)); + + Require(sceGnmComputeWaitOnAddress(Fresh(), 14, 0x500001003ull, 0xff, 0xb, 7) == 0); + RequireWords({0xc0021000u, 0, U, U, 0xc0053c00u, 0x13, 0x1000, 5, 7, 0xff, 10, 0xc0011000u, 0, U}); + Require(sceGnmComputeWaitOnAddress(Fresh(), 14, 0x1234, 1, 3, 0) == 0); + RequireWords({0xc0021000u, 0, U, U, 0xc0053c00u, 3, 0x1234, 0, 0, 1, 10, 0xc0011000u, 0, U}); + RequireRefused(sceGnmComputeWaitOnAddress(Fresh(), 13, 0x1234, 1, 3, 0)); +} + +void CheckVgtControl() { + Require(sceGnmSetVgtControl(Fresh(), 3, 0x7f, 1, 1) == 0); + RequireWords({0xc0017900u, 0x40000258u, 0x75007fu}); + Require(sceGnmSetVgtControl(Fresh(), 3, 0x10, 1, 0) == 0); + RequireWords({0xc0017900u, 0x40000258u, 0x6d0010u}); + RequireRefused(sceGnmSetVgtControl(Fresh(), 3, 0x100, 0, 0)); + RequireRefused(sceGnmSetVgtControl(Fresh(), 3, 0, 2, 0)); + RequireRefused(sceGnmSetVgtControl(Fresh(), 3, 0, 0, 2)); + RequireRefused(sceGnmSetVgtControl(Fresh(), 4, 0, 0, 0)); + + Require(sceGnmResetVgtControl(Fresh(), 3) == 0); + RequireWords({0xc0017900u, 0x40000258u, 0x6d007fu}); + RequireRefused(sceGnmResetVgtControl(Fresh(), 2)); + Require(sceGnmResetVgtControl(nullptr, 3) == -1); +} + +} // namespace + +int main() { + CheckDirectDraws(); + CheckDispatches(); + CheckIndirectDraws(); + CheckComputeWaits(); + CheckVgtControl(); +} diff --git a/core/libs/tests/GuestGnmNeoRetail.cpp b/core/libs/tests/GuestGnmNeoRetail.cpp new file mode 100644 index 0000000000..3101ebb9b0 --- /dev/null +++ b/core/libs/tests/GuestGnmNeoRetail.cpp @@ -0,0 +1,81 @@ +#include "prx/libSceGnmDriverForNeoMode/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +constexpr int Failure = static_cast(0x8EEE00FFu); +constexpr int NoInterface = static_cast(0x80000000u); +constexpr int ValidationNotEnabled = static_cast(0x80D13FFFu); + +using Query = int (APS5_VABI *)(void); + +void RequireAll(int expected, std::initializer_list queries) { + for (const auto query : queries) { + if (query() != expected) std::abort(); + } +} + +} // namespace + +int main() { + RequireAll(Failure, { + sceGnmDebugModuleReset, + sceGnmDebugReset, + sceGnmFindResources, + sceGnmFindResourcesPublic, + sceGnmGetOwnerName, + sceGnmGetResourceBaseAddressAndSizeInBytes, + sceGnmGetResourceName, + sceGnmGetResourceRegistrationBuffers, + sceGnmGetResourceShaderGuid, + sceGnmGetResourceType, + sceGnmGetResourceUserData, + sceGnmQueryResourceRegistrationUserMemoryRequirements, + sceGnmRegisterGdsResource, + sceGnmRegisterOwner, + sceGnmRegisterOwnerForSystem, + sceGnmRegisterResource, + sceGnmSetResourceRegistrationUserMemory, + sceGnmSetResourceUserData, + sceGnmUnregisterAllResourcesForOwner, + sceGnmUnregisterOwnerAndResources, + sceGnmUnregisterResource, + sceGnmSdmaClose, + sceGnmSdmaConstFill, + sceGnmSdmaCopyLinear, + sceGnmSdmaCopyTiled, + sceGnmSdmaCopyWindow, + sceGnmSdmaFlush, + sceGnmSdmaGetMinCmdSize, + sceGnmSdmaOpen, + sceGnmGetPhysicalCounterFromVirtualized, + sceGnmSetSpiEnableSqCounters, + sceGnmSetSpiEnableSqCountersForUnitInstance, + }); + RequireAll(NoInterface, { + sceGnmDriverInternalRetrieveGnmInterface, + sceGnmDriverInternalRetrieveGnmInterfaceForGpuDebugger, + sceGnmDriverInternalRetrieveGnmInterfaceForReplay, + sceGnmDriverInternalRetrieveGnmInterfaceForResourceRegistration, + sceGnmDriverInternalRetrieveGnmInterfaceForValidation, + }); + RequireAll(0, { + sceGnmDriverTraceInProgress, + sceGnmValidateOnSubmitEnabled, + }); + RequireAll(-1, { + sceGnmGetDbgGcHandle, + }); + RequireAll(ValidationNotEnabled, { + sceGnmValidateDisableDiagnostics, + sceGnmValidateDisableDiagnostics2, + sceGnmValidateDispatchCommandBuffers, + sceGnmValidateDrawCommandBuffers, + sceGnmValidateGetDiagnosticInfo, + sceGnmValidateGetDiagnostics, + sceGnmValidateResetState, + sceGnmValidationRegisterMemoryCheckCallback, + }); +} diff --git a/core/libs/tests/GuestGnmNeoShaders.cpp b/core/libs/tests/GuestGnmNeoShaders.cpp new file mode 100644 index 0000000000..75760c5b1e --- /dev/null +++ b/core/libs/tests/GuestGnmNeoShaders.cpp @@ -0,0 +1,128 @@ +#include "prx/libSceGnmDriverForNeoMode/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace { + +constexpr std::uint32_t Untouched = 0xa5a5a5a5u; +constexpr std::size_t BufferWords = 48; +constexpr std::uint32_t Regs[12] = {0x1000, 0, 0x2000, 0x3000, 0x4000, 0x5000, 0x6000, 0x7000, 0x8000, 0x9000, 0xa000, 0xb000}; +constexpr std::uint32_t BadRegs[12] = {0x1000, 1}; +constexpr std::uint32_t MaskedRegs[12] = {0x1000, 0, 0x030003c0u, 0x3000, 0x4000, 0x5000, 0x6000}; + +std::uint32_t g_words[BufferWords]; + +void Require(bool value) { if (!value) std::abort(); } + +std::uint32_t* Fresh() { + for (auto& word : g_words) word = Untouched; + return g_words; +} + +void RequireWords(std::initializer_list expected) { + std::size_t at = 0; + for (const auto word : expected) Require(g_words[at++] == word); + for (; at < BufferWords; ++at) Require(g_words[at] == Untouched); +} + +void RequireRefused(int result) { + Require(result == -1); + RequireWords({}); +} + +void CheckVs() { + Require(sceGnmUpdateVsShader(Fresh(), 0x1d, Regs, 0) == 0); + RequireWords({0xc0027600u, 0x48, 0x1000, 0, 0xc0027600u, 0x4a, 0x2000, 0x3000, 0xc0011000u, 0xc01e0207u, 0x6000, + 0xc0011000u, 0xc01e01b1u, 0x4000, 0xc0011000u, 0xc01e01c3u, 0x5000, 0xc00a1000u, 0}); + Require(sceGnmUpdateVsShader(Fresh(), 0x1d, MaskedRegs, 0x40) == 0); + Require(g_words[6] == 0x40); + Require(sceGnmUpdateVsShader(Fresh(), 0x1d, MaskedRegs, 0) == 0); + Require(g_words[6] == 0x030003c0u); + RequireRefused(sceGnmUpdateVsShader(Fresh(), 0x1d, Regs, 0x80000000u)); + RequireRefused(sceGnmUpdateVsShader(Fresh(), 0x1c, Regs, 0)); + RequireRefused(sceGnmUpdateVsShader(Fresh(), 0x1d, BadRegs, 0)); + RequireRefused(sceGnmUpdateVsShader(Fresh(), 0x1d, nullptr, 0)); +} + +void CheckPs() { + Require(sceGnmUpdatePsShader(Fresh(), 0x28, Regs) == 0); + RequireWords({0xc0027600u, 8, 0x1000, 0, 0xc0027600u, 10, 0x2000, 0x3000, 0xc0021000u, 0xc01e01c4u, 0x4000, 0x5000, + 0xc0021000u, 0xc01e01b3u, 0x6000, 0x7000, 0xc0011000u, 0xc01e01b6u, 0x8000, 0xc0011000u, 0xc01e01b8u, 0x9000, + 0xc0011000u, 0xc01e0203u, 0xa000, 0xc0011000u, 0xc01e008fu, 0xb000, 0xc00a1000u, 0}); + Require(sceGnmUpdatePsShader(Fresh(), 0x28, nullptr) == 0); + RequireWords({0xc0027600u, 8, 0, 0, 0xc0011000u, 0xc01e0203u, 0, 0xc01f1000u, 0}); + RequireRefused(sceGnmUpdatePsShader(Fresh(), 0x27, Regs)); + RequireRefused(sceGnmUpdatePsShader(Fresh(), 0x28, BadRegs)); + + Require(sceGnmUpdatePsShader350(Fresh(), 0x28, Regs) == 0); + Require(g_words[26] == 0xc01e008fu && g_words[28] == 0xc00a1000u); + Require(sceGnmUpdatePsShader350(Fresh(), 0x28, nullptr) == 0); + RequireWords({0xc0027600u, 8, 0, 0, 0xc0011000u, 0xc01e0203u, 0, 0xc0016900u, 0x8f, 0xf, 0xc01c1000u, 0}); + RequireRefused(sceGnmUpdatePsShader350(Fresh(), 0x27, nullptr)); + RequireRefused(sceGnmUpdatePsShader350(Fresh(), 0x28, BadRegs)); + + Require(sceGnmSetPsShader350(Fresh(), 0x28, Regs) == 0); + RequireWords({0xc0027600u, 8, 0x1000, 0, 0xc0027600u, 10, 0x2000, 0x3000, 0xc0026900u, 0x1c4, 0x4000, 0x5000, + 0xc0026900u, 0x1b3, 0x6000, 0x7000, 0xc0016900u, 0x1b6, 0x8000, 0xc0016900u, 0x1b8, 0x9000, + 0xc0016900u, 0x203, 0xa000, 0xc0016900u, 0x8f, 0xb000, 0xc00a1000u, 0}); + Require(sceGnmSetPsShader350(Fresh(), 0x28, nullptr) == 0); + RequireWords({0xc0027600u, 8, 0, 0, 0xc0016900u, 0x203, 0, 0xc0016900u, 0x8f, 0xf, 0xc01c1000u, 0}); + RequireRefused(sceGnmSetPsShader350(Fresh(), 0x27, Regs)); + RequireRefused(sceGnmSetPsShader350(Fresh(), 0x28, BadRegs)); +} + +void CheckGsHsCs() { + Require(sceGnmUpdateGsShader(Fresh(), 0x1d, Regs) == 0); + RequireWords({0xc0027600u, 0x88, 0x1000, 0, 0xc0027600u, 0x8a, 0x2000, 0x3000, 0xc0011000u, 0xc01e02e5u, 0x4000, + 0xc0011000u, 0xc01e029bu, 0x5000, 0xc0011000u, 0xc01e02e4u, 0x6000, 0xc00a1000u, 0}); + RequireRefused(sceGnmUpdateGsShader(Fresh(), 0x1c, Regs)); + RequireRefused(sceGnmUpdateGsShader(Fresh(), 0x1d, nullptr)); + RequireRefused(sceGnmUpdateGsShader(Fresh(), 0x1d, BadRegs)); + + Require(sceGnmUpdateHsShader(Fresh(), 0x1d, Regs, 0x77) == 0); + RequireWords({0xc0027600u, 0x108, 0x1000, 0, 0xc0027600u, 0x10a, 0x2000, 0x3000, 0xc0021000u, 0xc01e0286u, 0x5000, 0x6000, + 0xc0011000u, 0xc01e02dbu, 0x4000, 0xc0011000u, 0xc01e02d6u, 0x77, 0xc00a1000u, 0}); + RequireRefused(sceGnmUpdateHsShader(Fresh(), 0x1c, Regs, 0)); + RequireRefused(sceGnmUpdateHsShader(Fresh(), 0x1d, BadRegs, 0)); + + Require(sceGnmSetCsShaderWithModifier(Fresh(), 0x19, Regs, 0x3c0) == 0); + RequireWords({0xc0027600u, 0x20c, 0x1000, 0, 0xc0027600u, 0x212, 0x23c0, 0x3000, 0xc0037600u, 0x207, 0x4000, 0x5000, 0x6000, + 0xc00a1000u, 0}); + Require(sceGnmSetCsShaderWithModifier(Fresh(), 0x19, MaskedRegs, 0x40) == 0); + Require(g_words[6] == 0x03000040u); + Require(sceGnmSetCsShaderWithModifier(Fresh(), 0x19, MaskedRegs, 0) == 0); + Require(g_words[6] == 0x030003c0u); + RequireRefused(sceGnmSetCsShaderWithModifier(Fresh(), 0x19, Regs, 0x400)); + RequireRefused(sceGnmSetCsShaderWithModifier(Fresh(), 0x18, Regs, 0)); + RequireRefused(sceGnmSetCsShaderWithModifier(Fresh(), 0x19, BadRegs, 0)); +} + +void CheckWorkloads() { + std::uint32_t stream = Untouched; + Require(sceGnmCreateWorkloadStream(0, &stream) == 3 && stream == Untouched); + Require(sceGnmCreateWorkloadStream(5, nullptr) == 3); + Require(sceGnmCreateWorkloadStream(5, &stream) == 0 && stream == 1); + + std::uint64_t workload = Untouched; + Require(sceGnmBeginWorkload(3, &workload) == 0 && workload == 1); + Require(sceGnmBeginWorkload(0x10, &workload) == 1 && workload == 0); + Require(sceGnmBeginWorkload(0, nullptr) == 3); + + Require(sceGnmEndWorkload(0) == 2); + Require(sceGnmEndWorkload(1) == 0); + Require(sceGnmEndWorkload(0x0full << 56) == 0); + Require(sceGnmEndWorkload(0x10ull << 56) == 2); +} + +} // namespace + +int main() { + CheckVs(); + CheckPs(); + CheckGsHsCs(); + CheckWorkloads(); + Require(sceGnmGetGpuCoreClockFrequency() == 911000000); + Require(sceGnmGetOffChipTessellationBufferSize() == 0x800000); +} diff --git a/core/libs/tests/GuestHeapReportForGame.cpp b/core/libs/tests/GuestHeapReportForGame.cpp index 9238d7bdb1..ccc355a05d 100644 --- a/core/libs/tests/GuestHeapReportForGame.cpp +++ b/core/libs/tests/GuestHeapReportForGame.cpp @@ -1,15 +1,27 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif #include #include #include #include -extern "C" { -void APS5_VABI sceLibcInternalBacktraceForGame_nid_postfix(const char* heapName); -void APS5_VABI sceLibcInternalHeapErrorReportForGame_nid_postfix(void* heap, void* block, std::uint32_t error); + +static std::string ReadAll(const std::filesystem::path& path) { + std::ifstream file(path); + std::stringstream text; + text << file.rdbuf(); + return text.str(); } static void Require(bool value, const char* message) { @@ -20,25 +32,68 @@ static void Require(bool value, const char* message) { int main() { const auto path = std::filesystem::temp_directory_path() / ("anyps5-heap-report-" + std::to_string(std::rand()) + ".txt"); - Require(std::freopen(path.string().c_str(), "w", stderr) != nullptr, "stderr redirection failed"); + const auto outputPath = std::filesystem::temp_directory_path() / ("anyps5-heap-report-out-" + std::to_string(std::rand()) + ".txt"); +#ifdef _WIN32 + Require(_wfreopen(path.wstring().c_str(), L"w", stderr) != nullptr, "stderr redirection failed"); + Require(_wfreopen(outputPath.wstring().c_str(), L"w", stdout) != nullptr, "stdout redirection failed"); +#else + Require(std::freopen(path.c_str(), "w", stderr) != nullptr, "stderr redirection failed"); + Require(std::freopen(outputPath.c_str(), "w", stdout) != nullptr, "stdout redirection failed"); +#endif char heapName[] = "SceLibcHeap"; sceLibcInternalBacktraceForGame_nid_postfix(heapName); sceLibcInternalBacktraceForGame_nid_postfix(nullptr); - int heap = 0; + alignas(16) char heap[0x400] = {}; + std::strcpy(heap + 0x3B0, "SceLibcHeap"); + const std::uint32_t number = 3; + std::memcpy(heap + 0x3F4, &number, sizeof(number)); int block = 0; - sceLibcInternalHeapErrorReportForGame_nid_postfix(&heap, &block, 0x1234u); + sceLibcInternalHeapErrorReportForGame_nid_postfix(heap, &block, 1); + std::memset(heap + 0x3F4, 0, sizeof(number)); + sceLibcInternalHeapErrorReportForGame_nid_postfix(heap, &block, 2); + bool threw = false; + try { + sceLibcInternalHeapErrorReportForGame_nid_postfix(nullptr, &block, 1); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "null heap accepted"); + std::fflush(stdout); + const bool closed = _Stdout_nid_postfix.Close() == 0; + bool closedThrew = false; + try { + sceLibcInternalHeapErrorReportForGame_nid_postfix(heap, &block, 1); + } catch (const std::exception&) { + closedThrew = true; + } + if (!closed || closedThrew) { + std::fputs("heap error report on a closed standard output\n", stderr); + std::abort(); + } std::fflush(stderr); std::fclose(stderr); - std::ifstream file(path); - std::stringstream text; - text << file.rdbuf(); - file.close(); + const std::string report = ReadAll(path); + const std::string output = ReadAll(outputPath); std::filesystem::remove(path); - const std::string report = text.str(); + std::filesystem::remove(outputPath); + const std::string first = "[SceLibc] A heap error is detected.\n[SceLibc] -- Mspace Name --\n[SceLibc] SceLibcHeap ( 3)\n" + "[SceLibc] -- Error reason --\n[SceLibc] segment out of range.\n[SceLibc] pid:<0x"; + const std::string second = "[SceLibc] A heap error is detected.\n[SceLibc] -- Mspace Name --\n[SceLibc] SceLibcHeap\n[SceLibc] pid:<0x"; + Require(output.rfind(first, 0) == 0, "segment error report differs"); + Require(output.find(second, first.size()) != std::string::npos, "unnamed reason report differs"); + Require(output.find("reason", output.find(second)) == std::string::npos, "reason printed for error 2"); Require(report.find("[SceLibcHeap] backtrace requested from guest 0x") != std::string::npos, "heap name not reported"); Require(report.find("[] backtrace requested from guest 0x") != std::string::npos, "null heap name not reported"); - Require(report.find("[libc] heap error 0x1234: heap ") != std::string::npos, "heap error not reported"); + Require(report.find("[libc] heap error 0x1: heap ") != std::string::npos, "host diagnostic missing"); +#ifdef _WIN32 + const unsigned long pid = static_cast(_getpid()); +#else + const unsigned long pid = static_cast(getpid()); +#endif + char pidText[32]; + std::snprintf(pidText, sizeof(pidText), "pid:<0x%lx> tid:<0x", pid); + Require(output.find(pidText) != std::string::npos, "pid differs"); Require(std::string(heapName) == "SceLibcHeap", "heap name modified"); } diff --git a/core/libs/tests/GuestHeapRounding.cpp b/core/libs/tests/GuestHeapRounding.cpp index d888f7311f..e71d91abab 100644 --- a/core/libs/tests/GuestHeapRounding.cpp +++ b/core/libs/tests/GuestHeapRounding.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #ifndef NOMINMAX #define NOMINMAX #endif @@ -16,13 +17,6 @@ #include #include -extern "C" { -void* APS5_VABI malloc_nid_postfix(std::size_t); -void APS5_VABI free_nid_postfix(void*); -void* APS5_VABI realloc_nid_postfix(void*, std::size_t); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr std::size_t HeaderBytes = 2 * sizeof(void*); @@ -40,6 +34,7 @@ bool checkThreshold(std::size_t bytes, unsigned char value) { pointer[bytes - 1] = static_cast(value + 1); const bool preserved = pointer[0] == value && pointer[bytes - 1] == static_cast(value + 1); free_nid_postfix(pointer); + return preserved; } @@ -77,14 +72,15 @@ DWORD runChild(const wchar_t* mode) { int runMallocOverflow() { registerDefaultHeap(); - *__error_nid_postfix() = 0; + *__error_nid_postfix() = 77; try { auto* pointer = malloc_nid_postfix(std::numeric_limits::max() - HeaderBytes); if (pointer != nullptr) { free_nid_postfix(pointer); + return 1; } - return *__error_nid_postfix() == 12 ? 0 : 4; + return *__error_nid_postfix() == 77 ? 0 : 4; } catch (const std::exception& error) { std::fprintf(stderr, "malloc failed with an unexpected exception: %s\n", error.what()); return 2; @@ -100,14 +96,15 @@ int runReallocOverflow() { auto* original = static_cast(malloc_nid_postfix(bytes)); std::memset(original, 0x5a, bytes); bool rejectedWithExpectedError = false; - *__error_nid_postfix() = 0; + *__error_nid_postfix() = 77; try { auto* result = realloc_nid_postfix(original, std::numeric_limits::max() - HeaderBytes); if (result != nullptr) { free_nid_postfix(result); + return 4; } - rejectedWithExpectedError = *__error_nid_postfix() == 12; + rejectedWithExpectedError = *__error_nid_postfix() == 77; } catch (const std::exception& error) { std::fprintf(stderr, "realloc failed with an unexpected exception: %s\n", error.what()); } catch (...) { @@ -141,6 +138,7 @@ int runReallocOverflow() { if (result == 0) { free_nid_postfix(replacement); free_nid_postfix(original); + } return static_cast(result); } diff --git a/core/libs/tests/GuestHmd.cpp b/core/libs/tests/GuestHmd.cpp index 203775d1fd..fa06463949 100644 --- a/core/libs/tests/GuestHmd.cpp +++ b/core/libs/tests/GuestHmd.cpp @@ -1,12 +1,23 @@ +#include "ProcessParametersFixture.hpp" #include "prx/libSceHmd/Hmd.hpp" +#include "prx/libSceHmd/include/Api.hpp" +#include +#include +#include #include #include #include #include +#include namespace { +constexpr std::uint32_t LastSdkBeforeStartupState = 0x044fffff; +constexpr std::uint32_t FirstSdkWithStartupState = 0x04500000; + +alignas(16) std::array process{}; + void Require(bool condition, const char* message) { if (!condition) { std::fprintf(stderr, "HMD: %s\n", message); @@ -14,17 +25,35 @@ void Require(bool condition, const char* message) { } } +void InstallProcessParameters() { + const std::uint64_t size = process.size(); + const std::uint32_t magic = 0x4942524f; + std::memcpy(process.data(), &size, sizeof(size)); + std::memcpy(process.data() + 8, &magic, sizeof(magic)); + Require(ProcessParametersFixture::Install(process.data(), process.size()), "process parameters were not installed"); +} + +void SetCompiledSdkVersion(std::uint32_t version) { + std::memcpy(process.data() + 0x10, &version, sizeof(version)); +} + void CheckDisconnected() { Hmd::DeviceStatus status = Hmd::DeviceStatus::Ready; Require(sceHmdInternalGetDeviceStatus(&status) == 0, "status query failed"); Require(status == Hmd::DeviceStatus::NotDetected, "headset reported connected"); Require(sceHmdInternalGetDeviceStatus(nullptr) == Hmd::ParameterNull, "null status"); Require(sceHmdInternalMmapIsConnect() == 0, "connection reported present"); + Require(!sceHmdInternalIsGameVr2d(), "game reported in VR2D without a headset"); + Require(!sceHmdInternalIsMiniAppVr2d(), "mini app reported in VR2D without a headset"); + Require(!sceHmdInternalIsCommonDlgVr2d(), "common dialog reported in VR2D without a headset"); + Require(!sceHmdInternalIsCommonDlgMiniAppVr2d(), "common dialog mini app reported in VR2D without a headset"); } void CheckBufferQueries() { Require(sceHmdDistortionGetWorkMemoryAlign() == 0x400, "distortion work memory alignment"); + Require(sceHmdGetDistortionWorkMemoryAlign() == 0x400, "distortion work memory alignment query"); Require(sceHmdDistortionGetWorkMemorySize() == 0x20000, "distortion work memory size"); + Require(sceHmdGetDistortionWorkMemorySize() == 0x20000, "distortion work memory size query"); Require(sceHmdReprojectionQueryGarlicBuffAlign() == 0x100, "reprojection garlic buffer alignment"); Require(sceHmdReprojectionQueryGarlicBuffSize() == 0x100000, "reprojection garlic buffer size"); Require(sceHmdReprojectionQueryOnionBuffAlign() == 0x100, "reprojection onion buffer alignment"); @@ -42,10 +71,10 @@ void CheckHandleQueries(std::int32_t expected) { std::memset(sensor, 0x5a, sizeof(sensor)); for (const auto handle : {0, -1, 1, 0x0f000000}) { Require(sceHmdGetFieldOfView(handle, nullptr) == Hmd::ParameterNull, "null field of view accepted"); - Require(sceHmdGetFieldOfView(handle, fieldOfView) == expected, "field of view without a headset"); - Require(sceHmdGet2DEyeOffset(handle, left, right) == expected, "eye offset without a headset"); + Require(sceHmdGetFieldOfView(handle, reinterpret_cast(fieldOfView)) == expected, "field of view without a headset"); + Require(sceHmdGet2DEyeOffset(handle, reinterpret_cast(left), reinterpret_cast(right)) == expected, "eye offset without a headset"); Require(sceHmdGet2DEyeOffset(handle, nullptr, nullptr) == expected, "eye offset with null outputs"); - Require(sceHmdGetInertialSensorData(handle, sensor, 1) == expected, "sensor data without a headset"); + Require(sceHmdGetInertialSensorData(handle, reinterpret_cast(sensor), 1) == expected, "sensor data without a headset"); } for (const auto* bytes : {left, right, fieldOfView}) { for (std::size_t index = 0; index < 16; ++index) Require(bytes[index] == 0x5a, "a refused query wrote its output"); @@ -53,11 +82,82 @@ void CheckHandleQueries(std::int32_t expected) { for (const auto byte : sensor) Require(byte == 0x5a, "a refused sensor query wrote its output"); } +template +bool Unimplemented(Call call) { + try { + static_cast(call()); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void CheckCommandQueries(bool handleZeroMeasured) { + unsigned char zeroed[64] = {}; + unsigned char vr2dBytes[64] = {}; + unsigned char commands[16]; + std::memset(commands, 0x5a, sizeof(commands)); + for (const std::size_t offset : {0x00u, 0x08u, 0x20u}) std::memset(vr2dBytes + offset, 0x11, 8); + const auto* distortion = reinterpret_cast(zeroed); + const auto* wideNear = reinterpret_cast(zeroed); + const auto* zeroedVr2d = reinterpret_cast(zeroed); + const auto* vr2d = reinterpret_cast(vr2dBytes); + auto* commandBuffer = reinterpret_cast(commands); + const auto vr2dWith = [&](std::size_t offset, unsigned char value, std::size_t bytes, auto check) { + unsigned char changed[64]; + std::memcpy(changed, vr2dBytes, sizeof(changed)); + std::memset(changed + offset, value, bytes); + check(reinterpret_cast(changed)); + }; + for (const auto handle : {-1, 1, 0x0f000000}) { + Require(sceHmdGetDistortionCorrectionCommand(handle, distortion, commandBuffer) == Hmd::InvalidHandle, "distortion command accepted a handle sceHmdOpen never issued"); + Require(sceHmdDistortionGetCorrectionCommand(handle, distortion, commandBuffer) == Hmd::InvalidHandle, "aliased distortion command accepted a handle sceHmdOpen never issued"); + Require(sceHmdGetWideNearDistortionCorrectionCommand(handle, wideNear, commandBuffer) == Hmd::InvalidHandle, "wide-near distortion command accepted a handle sceHmdOpen never issued"); + Require(sceHmdDistortionGetWideNearCorrectionCommand(handle, wideNear, commandBuffer) == Hmd::InvalidHandle, "aliased wide-near distortion command accepted a handle sceHmdOpen never issued"); + Require(sceHmdGet2dVrCommand(handle, vr2d, commandBuffer) == Hmd::InvalidHandle, "2D VR command accepted a handle sceHmdOpen never issued"); + Require(sceHmdDistortionGet2dVrCommand(handle, vr2d, commandBuffer) == Hmd::InvalidHandle, "aliased 2D VR command accepted a handle sceHmdOpen never issued"); + } + for (const auto handle : {0, -1, 1, 0x0f000000}) { + for (const auto query : {sceHmdGet2dVrCommand, sceHmdDistortionGet2dVrCommand}) { + Require(query(handle, zeroedVr2d, commandBuffer) == Hmd::ParameterNull, "2D VR command took a parameter with null pointers"); + Require(query(handle, nullptr, commandBuffer) == Hmd::ParameterNull, "2D VR command took a null parameter"); + Require(query(handle, vr2d, nullptr) == Hmd::ParameterNull, "2D VR command took a null command buffer"); + for (const std::size_t offset : {0x00u, 0x08u, 0x20u}) { + vr2dWith(offset, 0, 8, [&](const Hmd2dVrCommandParameter* changed) { + Require(query(handle, changed, commandBuffer) == Hmd::ParameterNull, "2D VR command took a null pointer in its parameter"); + }); + } + vr2dWith(0x34, 0x01, 4, [&](const Hmd2dVrCommandParameter* changed) { + Require(query(handle, changed, commandBuffer) == Hmd::ParameterInvalid, "2D VR command took a non-zero field at 0x34"); + }); + vr2dWith(0x38, 0x01, 8, [&](const Hmd2dVrCommandParameter* changed) { + Require(query(handle, changed, commandBuffer) == Hmd::ParameterInvalid, "2D VR command took a non-zero field at 0x38"); + }); + } + } + if (!handleZeroMeasured) { + Require(Unimplemented([&] { return sceHmdGetDistortionCorrectionCommand(0, distortion, commandBuffer); }), "distortion command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdDistortionGetCorrectionCommand(0, distortion, commandBuffer); }), "aliased distortion command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdGetWideNearDistortionCorrectionCommand(0, wideNear, commandBuffer); }), "wide-near distortion command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdDistortionGetWideNearCorrectionCommand(0, wideNear, commandBuffer); }), "aliased wide-near distortion command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdGet2dVrCommand(0, vr2d, commandBuffer); }), "2D VR command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdDistortionGet2dVrCommand(0, vr2d, commandBuffer); }), "aliased 2D VR command answered handle 0 where its answer is not measured"); + } else { + Require(sceHmdGetDistortionCorrectionCommand(0, distortion, commandBuffer) == Hmd::NotInitialized, "distortion command before initialize"); + Require(sceHmdDistortionGetCorrectionCommand(0, distortion, commandBuffer) == Hmd::NotInitialized, "aliased distortion command before initialize"); + Require(sceHmdGetWideNearDistortionCorrectionCommand(0, wideNear, commandBuffer) == Hmd::NotInitialized, "wide-near distortion command before initialize"); + Require(sceHmdDistortionGetWideNearCorrectionCommand(0, wideNear, commandBuffer) == Hmd::NotInitialized, "aliased wide-near distortion command before initialize"); + Require(sceHmdGet2dVrCommand(0, vr2d, commandBuffer) == Hmd::NotInitialized, "2D VR command before initialize"); + Require(sceHmdDistortionGet2dVrCommand(0, vr2d, commandBuffer) == Hmd::NotInitialized, "aliased 2D VR command before initialize"); + } + for (const auto byte : commands) Require(byte == 0x5a, "a refused command query wrote its output"); +} + void CheckAssyError(std::int32_t expected) { unsigned char data[32]; std::memset(data, 0x5a, sizeof(data)); Require(sceHmdGetAssyError(nullptr) == Hmd::ParameterNull, "null assembly error output accepted"); - Require(sceHmdGetAssyError(data) == expected, "assembly error query without a headset"); + Require(sceHmdGetAssyError(reinterpret_cast(data)) == expected, "assembly error query without a headset"); for (const auto byte : data) Require(byte == 0x5a, "a refused assembly error query wrote its output"); } @@ -69,6 +169,7 @@ int main() { std::memset(&info, 0xa5, sizeof(info)); const auto untouched = info; + InstallProcessParameters(); CheckDisconnected(); CheckBufferQueries(); Require(sceHmdTerminate() == Hmd::NotInitialized, "terminate before initialize"); @@ -78,6 +179,10 @@ int main() { Require(sceHmdGetDeviceInformation(&info) == Hmd::NotInitialized, "query before initialize"); Require(std::memcmp(&info, &untouched, sizeof(info)) == 0, "failed query modified output"); CheckHandleQueries(Hmd::NotInitialized); + SetCompiledSdkVersion(LastSdkBeforeStartupState); + CheckCommandQueries(true); + SetCompiledSdkVersion(FirstSdkWithStartupState); + CheckCommandQueries(false); CheckAssyError(Hmd::NotInitialized); Require(sceHmdGetDeviceInformationByHandle(0, &info) == Hmd::NotInitialized, "handle query before initialize"); @@ -116,6 +221,7 @@ int main() { Require(std::memcmp(&info, &expected, sizeof(info)) == 0, "invalid handle modified output"); } CheckHandleQueries(Hmd::InvalidHandle); + CheckCommandQueries(false); CheckAssyError(Hmd::DeviceDisconnected); Require(sceHmdGetDeviceInformationByHandle(0, nullptr) == Hmd::InvalidHandle, "null query accepted invalid handle"); diff --git a/core/libs/tests/GuestHmd2.cpp b/core/libs/tests/GuestHmd2.cpp index 4f05d57f02..6303182c1e 100644 --- a/core/libs/tests/GuestHmd2.cpp +++ b/core/libs/tests/GuestHmd2.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHmd2/include/Api.hpp" #include "ProcessParametersFixture.hpp" #include "prx/libSceHmd/Hmd.hpp" #include "prx/libSceHmd2/Hmd2.hpp" @@ -10,51 +11,8 @@ #include #include -extern "C" { -std::int32_t APS5_VABI Hmd2WriteStateTwo(void* a0); -std::int32_t APS5_VABI Hmd2WriteSize0x2000Align0x100(std::uint64_t* a0, std::uint64_t* a1); -std::int32_t APS5_VABI Hmd2WriteFourConstants(void* a0); -std::int32_t APS5_VABI Hmd2WriteSixHalvesThenInvalidHandle(std::uint64_t a0, void* a1); -std::int32_t APS5_VABI Hmd2WriteTwelveHalvesThenInvalidHandle(std::uint64_t a0, void* a1); -std::int32_t APS5_VABI Hmd2SlotUpTo3CheckSecondThirdThenZero(std::uint64_t a0, const void* a1, const void* a2); -std::int32_t APS5_VABI Hmd2SlotUpTo1ThenError1002(std::uint64_t a0); -std::int32_t APS5_VABI Hmd2SlotUpTo1CheckSecondThenError1002(std::uint64_t a0, const void* a1); -std::int32_t APS5_VABI Hmd2SlotUpTo1ThenMinusOne(std::uint64_t a0); -std::int32_t APS5_VABI Hmd2SlotUpTo2ThenMinusOne(std::uint64_t a0); -std::int32_t APS5_VABI Hmd2SlotUpTo2ThenError1002(std::uint64_t a0); -std::int32_t APS5_VABI Hmd2SlotUpTo2ThenError1003(std::uint64_t a0); -std::int32_t APS5_VABI Hmd2SlotUpTo2CheckSecondThenError002C(std::uint64_t a0, const void* a1); -std::int32_t APS5_VABI Hmd2IndexZeroCheckSecondThenZero(std::uint64_t a0, const void* a1); -std::int32_t APS5_VABI Hmd2CheckFirstThenError0004(const void*); -std::int32_t APS5_VABI Hmd2CheckFirstThenZero(const void*); -std::int32_t APS5_VABI Hmd2CheckFirstThenError1002(const void*); -std::int32_t APS5_VABI Hmd2CheckFirstThenMinusOne(const void*); -std::int32_t APS5_VABI Hmd2CheckFirstTwoThenParameterInvalid(const void*, const void*); -std::int32_t APS5_VABI Hmd2CheckSecondThenError002C(const void*, const void*); -std::int32_t APS5_VABI Hmd2CheckFirstThenParameterInvalid(const void*); -std::int32_t APS5_VABI Hmd2CheckAllSixThenZero(const void*, const void*, const void*, const void*, const void*, const void*); -std::int32_t APS5_VABI Hmd2CheckSecondThenParameterInvalid(const void*, const void*); -std::int32_t APS5_VABI Hmd2CheckFirstThenError800d0203(const void*); -std::int32_t APS5_VABI Hmd2CheckFirstThenError1003(const void*); -std::int32_t APS5_VABI Hmd2CheckFirstThenError1010(const void*); -std::int32_t APS5_VABI Hmd2CheckFirstTwoThenError1002(const void*, const void*); -std::int32_t APS5_VABI Hmd2CheckFirstTwoThenError1003(const void*, const void*); -std::int32_t APS5_VABI Hmd2CheckFirstAndThirdThenZero(const void*, const void*, const void*); -std::int32_t APS5_VABI Hmd2CheckFirstAndThirdThenError1003(const void*, const void*, const void*); -std::int32_t APS5_VABI Hmd2CheckSecondThenInvalidHandle(const void*, const void*); -std::int32_t APS5_VABI Hmd2CheckSecondThenError1002(const void*, const void*); -std::int32_t APS5_VABI Hmd2BeforeInitializeZero(); -std::int32_t APS5_VABI Hmd2BeforeInitializeNotInitialized(); -std::int32_t APS5_VABI Hmd2BeforeInitializeError0004(); -std::int32_t APS5_VABI Hmd2BeforeInitializeParameterNull(); -std::int32_t APS5_VABI Hmd2BeforeInitializeParameterInvalid(); -std::int32_t APS5_VABI Hmd2BeforeInitializeReprojectionNotInitialized(); -std::int32_t APS5_VABI Hmd2BeforeInitializeError002C(); -std::int32_t APS5_VABI Hmd2BeforeInitializeError1002(); -std::int32_t APS5_VABI Hmd2BeforeInitializeError1003(); -std::int32_t APS5_VABI Hmd2BeforeInitializeMinusOne(); -std::int32_t APS5_VABI Hmd2BeforeInitializeValue0400(); -std::int32_t APS5_VABI Hmd2BeforeInitializeValue20000(); +namespace Hmd2 { + } namespace { @@ -299,6 +257,16 @@ int main() { InstallProcessParameters(); TestBeforeInitialize(); TestWithoutHeadset(); + bool threw = false; + try { + static_cast(sceHmd2ReprojectionQueryBufferSizeAlign()); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "buffer size query did not throw"); + Require(sceHmd2GazeActivateGazeTracking(0) == 0, "gaze activation before initialization"); + Require(sceHmd2ReprojectionGetCurrentOutput(nullptr) == Hmd2::ReprojectionNotInitialized, "reprojection before initialization"); + Require(sceHmd2SetFieldOfView(nullptr) == Hmd2::ParameterNull, "null field of view"); std::puts("HMD2 tests passed"); return 0; } diff --git a/core/libs/tests/GuestHmd2Gaze.cpp b/core/libs/tests/GuestHmd2Gaze.cpp new file mode 100644 index 0000000000..7a9a674c36 --- /dev/null +++ b/core/libs/tests/GuestHmd2Gaze.cpp @@ -0,0 +1,96 @@ +#include "prx/libSceHmd2/include/Api.hpp" +#include "prx/libSceHmd2/Hmd2.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::int32_t InvalidHandle = static_cast(0x8a720003); +constexpr std::int32_t ParameterNull = static_cast(0x8a720008); +constexpr std::int32_t ParameterInvalid = static_cast(0x8a720009); + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "FAIL: %s\n", what); + std::abort(); +} + +template +T Read(const unsigned char* bytes, std::size_t offset) { + T value; + std::memcpy(&value, bytes + offset, sizeof(value)); + return value; +} + +bool NegativeZero(float value) { + return value == 0.0f && std::signbit(value); +} + +void CheckGazeResult() { + Hmd2::GazeResultParameter param {sizeof(Hmd2::GazeResultParameter), 0x0e010000, 0}; + unsigned char result[0x38]; + std::memset(result, 0x5a, sizeof(result)); + const std::uint32_t size = 0x38; + std::memcpy(result, &size, sizeof(size)); + + Require(sceHmd2GazeGetResult(nullptr, reinterpret_cast(result)) == ParameterNull, "null param"); + Require(sceHmd2GazeGetResult(¶m, nullptr) == ParameterNull, "null result"); + Hmd2::GazeResultParameter wrongParam = param; + wrongParam.size = 0x18; + Require(sceHmd2GazeGetResult(&wrongParam, reinterpret_cast(result)) == ParameterInvalid, "param size"); + const std::uint32_t wrongSize = 0x18; + std::memcpy(result, &wrongSize, sizeof(wrongSize)); + Require(sceHmd2GazeGetResult(¶m, reinterpret_cast(result)) == ParameterInvalid, "result size"); + Require(Read(result, 4) == 0x5a5a5a5a, "result untouched after a size error"); + + std::memcpy(result, &size, sizeof(size)); + Require(sceHmd2GazeGetResult(¶m, reinterpret_cast(result)) == InvalidHandle, "unopened handle"); + Require(Read(result, 0) == 0x38, "size kept"); + Require(Read(result, 4) == -1, "+4 is -1"); + for (const std::size_t offset : {0x8u, 0xcu, 0x10u, 0x20u, 0x24u, 0x28u, 0x2cu, 0x30u, 0x34u}) + Require(Read(result, offset) == 0, "cleared word"); + Require(NegativeZero(Read(result, 0x14)), "+0x14 is -0.0"); + Require(Read(result, 0x18) == 0, "+0x18 is 0.0"); + Require(Read(result, 0x1c) == -1.0f, "+0x1c is -1.0"); + + Hmd2::GazeResultParameter zero {sizeof(Hmd2::GazeResultParameter), 0, 0}; + Require(sceHmd2GazeGetResult(&zero, reinterpret_cast(result)) == InvalidHandle, "handle 0"); +} + +void CheckFoveatedResult() { + Hmd2::GazeResultParameter param {sizeof(Hmd2::GazeResultParameter), 0x0e010000, 0}; + unsigned char result[0x18]; + std::memset(result, 0x5a, sizeof(result)); + const std::uint32_t size = 0x18; + std::memcpy(result, &size, sizeof(size)); + + Require(sceHmd2GazeGetResultForFoveatedRendering(nullptr, reinterpret_cast(result)) == ParameterNull, "foveated null param"); + Require(sceHmd2GazeGetResultForFoveatedRendering(¶m, nullptr) == ParameterNull, "foveated null result"); + unsigned char large[0x38] = {}; + const std::uint32_t largeSize = 0x38; + std::memcpy(large, &largeSize, sizeof(largeSize)); + Require(sceHmd2GazeGetResultForFoveatedRendering(¶m, reinterpret_cast(large)) == ParameterInvalid, "foveated result size"); + + Require(sceHmd2GazeGetResultForFoveatedRendering(¶m, reinterpret_cast(result)) == InvalidHandle, "foveated unopened handle"); + Require(Read(result, 0) == 0x18, "foveated size kept"); + Require(Read(result, 4) == -1, "foveated +4 is -1"); + Require(NegativeZero(Read(result, 8)), "foveated +8 is -0.0"); + Require(Read(result, 0xc) == 0, "foveated +0xc is 0.0"); + Require(Read(result, 0x10) == -1.0f, "foveated +0x10 is -1.0"); + Require(Read(result, 0x14) == 0x5a5a5a5a, "foveated +0x14 untouched"); +} + +} + +int main() { + Require(sceHmd2Initialize(nullptr) != 0, "initialize reports no headset"); + CheckGazeResult(); + CheckFoveatedResult(); + std::puts("guest Hmd2 gaze results without a headset: PASS"); + return 0; +} diff --git a/core/libs/tests/GuestHmdSetupDialog.cpp b/core/libs/tests/GuestHmdSetupDialog.cpp new file mode 100644 index 0000000000..1b4f731d93 --- /dev/null +++ b/core/libs/tests/GuestHmdSetupDialog.cpp @@ -0,0 +1,63 @@ +#include "prx/libSceHmdSetupDialog/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80B80003); +constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); +constexpr int kErrNotFinished = static_cast(0x80B80005); +constexpr int kErrArgNull = static_cast(0x80B8000D); +constexpr int kResultUserCanceled = 1; + +} + +int main() { + std::int64_t param = 0; + std::int32_t result[2] = {-1, -1}; + + Require(sceHmdSetupDialogTerminate() == kErrNotInitialized); + Require(sceHmdSetupDialogOpen(¶m) == kErrNotInitialized); + Require(sceHmdSetupDialogClose() == kErrNotInitialized); + Require(sceHmdSetupDialogGetResult(result) == kErrNotInitialized); + Require(sceHmdSetupDialogGetStatus() == kStatusNone); + Require(sceHmdSetupDialogUpdateStatus() == kStatusNone); + + Require(sceHmdSetupDialogInitialize() == 0); + Require(sceHmdSetupDialogInitialize() == kErrAlreadyInitialized); + Require(sceHmdSetupDialogGetStatus() == kStatusInitialized); + Require(sceHmdSetupDialogUpdateStatus() == kStatusInitialized); + + Require(sceHmdSetupDialogOpen(nullptr) == kErrArgNull); + Require(sceHmdSetupDialogGetResult(result) == kErrNotFinished); + + Require(sceHmdSetupDialogOpen(¶m) == 0); + Require(sceHmdSetupDialogGetStatus() == kStatusFinished); + Require(sceHmdSetupDialogUpdateStatus() == kStatusFinished); + + Require(sceHmdSetupDialogGetResult(nullptr) == kErrArgNull); + Require(sceHmdSetupDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(result[1] == -1); + + Require(sceHmdSetupDialogClose() == 0); + Require(sceHmdSetupDialogTerminate() == 0); + Require(sceHmdSetupDialogTerminate() == kErrNotInitialized); + Require(sceHmdSetupDialogGetStatus() == kStatusNone); + Require(sceHmdSetupDialogUpdateStatus() == kStatusNone); + + Require(sceHmdSetupDialogInitialize() == 0); + Require(sceHmdSetupDialogUpdateStatus() == kStatusInitialized); + Require(sceHmdSetupDialogClose() == 0); + Require(sceHmdSetupDialogUpdateStatus() == kStatusFinished); + result[0] = -1; + Require(sceHmdSetupDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(sceHmdSetupDialogTerminate() == 0); +} diff --git a/core/libs/tests/GuestHostStandardError.cpp b/core/libs/tests/GuestHostStandardError.cpp index 4c3fdc2309..131df792f6 100644 --- a/core/libs/tests/GuestHostStandardError.cpp +++ b/core/libs/tests/GuestHostStandardError.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -16,18 +18,7 @@ #endif extern "C" { -FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); -int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t); -std::size_t APS5_VABI fwrite_nid_postfix(const void*, std::size_t, std::size_t, FileStream*); -int APS5_VABI fflush_nid_postfix(FileStream*); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI dup2_nid_postfix(int, int); -std::int64_t APS5_VABI write_nid_postfix(int, const void*, std::size_t); -int APS5_VABI printf_nid_postfix(const char*, ...); -int APS5_VABI puts_nid_postfix(const char*); -int APS5_VABI fprintf_nid_postfix(FileStream*, const char*, ...); -void APS5_VABI perror_nid_postfix(const char*); -int* APS5_VABI __error_nid_postfix(); + extern FileStream* __stderrp_nid_postfix; extern FileStream* __stdoutp_nid_postfix; } diff --git a/core/libs/tests/GuestHostStandardErrorWindows.cpp b/core/libs/tests/GuestHostStandardErrorWindows.cpp index f91c92d09e..0248298110 100644 --- a/core/libs/tests/GuestHostStandardErrorWindows.cpp +++ b/core/libs/tests/GuestHostStandardErrorWindows.cpp @@ -1,9 +1,12 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/HostStandardStreams.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include +#include #include #ifdef _WIN32 #include @@ -12,15 +15,7 @@ #endif extern "C" { -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI dup2_nid_postfix(int, int); -std::int64_t APS5_VABI write_nid_postfix(int, const void*, std::size_t); -int APS5_VABI printf_nid_postfix(const char*, ...); -int APS5_VABI puts_nid_postfix(const char*); -int APS5_VABI fprintf_nid_postfix(FileStream*, const char*, ...); -int APS5_VABI fflush_nid_postfix(FileStream*); -void APS5_VABI perror_nid_postfix(const char*); -int* APS5_VABI __error_nid_postfix(); + extern FileStream _Stdout_nid_postfix; extern FileStream _Stderr_nid_postfix; } @@ -84,6 +79,7 @@ static void GuestText(int standard, const std::string& text) { Require(fprintf_nid_postfix(&_Stderr_nid_postfix, "guest-fprintf\n") >= 0); *__error_nid_postfix() = 2; perror_nid_postfix("guest-perror"); + Require(fflush_nid_postfix(&_Stderr_nid_postfix) == 0); } std::string got(text.size(), '\0'); @@ -96,7 +92,55 @@ static void GuestText(int standard, const std::string& text) { ::_close(guest[1]); } -int main() { +static void Buffering(int standard) { + int capture[2]{-1, -1}; + Require(::_pipe(capture, 65536, _O_BINARY) == 0); + const int original = ::_dup(standard); + Require(original >= 0 && ::_dup2(capture[1], standard) == 0); + auto* guestStream = standard == 1 ? &_Stdout_nid_postfix : &_Stderr_nid_postfix; + char buffer[4096]{}; + flockfile_nid_postfix(guestStream); + + if (standard == 1) setbuf_nid_postfix(guestStream, buffer); + else Require(setvbuf_nid_postfix(guestStream, buffer, 0, sizeof(buffer)) == 0); + funlockfile_nid_postfix(guestStream); + + Require(fprintf_nid_postfix(guestStream, "guest-buffered") == 14); + Require(std::fputs("host-immediate", standard == 1 ? stdout : stderr) >= 0); + char bytes[64]{}; + DWORD read = 0; + Require(Available(capture[0], bytes, sizeof(bytes), read) == 14); + Require(read == 14 && std::string(bytes, read) == "host-immediate"); + Require(::_read(capture[0], bytes, 14) == 14); + Require(Available(capture[0], bytes, sizeof(bytes), read) == 0); + Require(fflush_nid_postfix(guestStream) == 0); + Require(Available(capture[0], bytes, sizeof(bytes), read) == 14); + Require(read == 14 && std::string(bytes, read) == "guest-buffered"); + Require(::_read(capture[0], bytes, 14) == 14); + + int guest[2]{-1, -1}; + Require(pipe_nid_postfix(guest) == 0 && dup2_nid_postfix(guest[1], standard) == standard); + Require(fprintf_nid_postfix(guestStream, "guest-redirected") == 16 && fflush_nid_postfix(guestStream) == 0); + Require(std::fputs("host-original", standard == 1 ? stdout : stderr) >= 0); + Require(Available(capture[0], bytes, sizeof(bytes), read) == 13); + Require(read == 13 && std::string(bytes, read) == "host-original"); + Require(Available(guest[0], bytes, sizeof(bytes), read) == 16); + Require(read == 16 && std::string(bytes, read) == "guest-redirected"); + Require(setvbuf_nid_postfix(guestStream, nullptr, 2, 0) == 0); + Require(::_dup2(original, standard) == 0); + ::_close(original); + ::_close(capture[0]); + ::_close(capture[1]); + ::_close(guest[0]); + ::_close(guest[1]); +} + +int main(int argc, char** argv) { + if (argc == 2 && std::strcmp(argv[1], "--buffering") == 0) { + Buffering(2); + Buffering(1); + return 0; + } Split(2); Split(1); GuestText(1, "guest-printf\nguest-puts\n"); diff --git a/core/libs/tests/GuestHostStreams.cpp b/core/libs/tests/GuestHostStreams.cpp index e31c1dad51..f05f9f4b1b 100644 --- a/core/libs/tests/GuestHostStreams.cpp +++ b/core/libs/tests/GuestHostStreams.cpp @@ -1,17 +1,14 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include -extern "C" { -int APS5_VABI dup_nid_postfix(int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI sceKernelClose(int); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } +constexpr int GuestEperm = 1; constexpr int GuestEbadf = 9; +constexpr int SceKernelErrorEperm = static_cast(0x80020001); constexpr int SceKernelErrorEbadf = static_cast(0x80020009); static bool Open(int fd) { @@ -26,13 +23,17 @@ int main() { for (int fd = 0; fd < 3; ++fd) open[fd] = Open(fd); for (int fd = 0; fd < 3; ++fd) { if (!open[fd]) continue; - Require(close_nid_postfix(fd) == -1 && *__error_nid_postfix() == GuestEbadf); - Require(sceKernelClose(fd) == SceKernelErrorEbadf); + Require(close_nid_postfix(fd) == -1 && *__error_nid_postfix() == GuestEperm); + Require(sceKernelClose(fd) == SceKernelErrorEperm); Require(Open(fd)); } int pipe[2]; Require(pipe_nid_postfix(pipe) == 0); for (const int fd : pipe) Require(fd >= 3 || !open[fd]); Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0); + Require(close_nid_postfix(pipe[0]) == -1 && *__error_nid_postfix() == GuestEbadf); + Require(sceKernelClose(pipe[1]) == SceKernelErrorEbadf); + Require(close_nid_postfix(-1) == -1 && *__error_nid_postfix() == GuestEbadf); + Require(sceKernelClose(-1) == SceKernelErrorEbadf); return 0; } diff --git a/core/libs/tests/GuestHostname.cpp b/core/libs/tests/GuestHostname.cpp index 36e18845dc..871c2b68d1 100644 --- a/core/libs/tests/GuestHostname.cpp +++ b/core/libs/tests/GuestHostname.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,11 +9,6 @@ #include #include -extern "C" { -int APS5_VABI gethostname_nid_postfix(char*, std::size_t); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool condition, int line) { if (!condition) { std::fprintf(stderr, "Hostname check failed at line %d\n", line); @@ -22,7 +18,7 @@ static void Require(bool condition, int line) { #define Check(value) Require((value), __LINE__) int main() { - constexpr std::string_view expected = "AnyPS5"; + constexpr std::string_view expected = ""; for (std::size_t length = 0; length <= expected.size() + 2; ++length) { std::vector buffer(length + 2, '#'); *__error_nid_postfix() = 71; diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index 93eb0119d3..fbbdd525bf 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -1,4 +1,7 @@ +#include "prx/libSceHttp/include/Http.hpp" #include "SceTypes.hpp" +#include "ProcessParametersFixture.hpp" +#include #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -7,49 +10,47 @@ #include #include #include +#include #include #include -extern "C" { -int APS5_VABI sceHttpUriParse(SceHttpUriElement*, const char*, void*, std::size_t*, std::size_t); -int APS5_VABI sceHttpUriCopy(SceHttpUriElement*, const SceHttpUriElement*, void*, std::size_t, std::size_t*); -int APS5_VABI sceHttpUriMerge(char*, const char*, const char*, std::size_t*, std::size_t, std::uint32_t); -int APS5_VABI sceHttpSetInflateGZIPEnabled(int, int); -int APS5_VABI sceHttpSetRequestStatusCallback(int, void (APS5_VABI *)(int, int, void*), void*); -int APS5_VABI sceHttpSendRequest(int, const void*, std::size_t); -int APS5_VABI sceHttpUriBuild(char*, std::size_t*, std::size_t, const SceHttpUriElement*, std::uint32_t); -int APS5_VABI sceHttpUriEscape(char*, std::size_t*, std::size_t, const char*); -int APS5_VABI sceHttpUriUnescape(char*, std::size_t*, std::size_t, const char*); -int APS5_VABI sceHttpUriSweepPath(char*, const char*, std::size_t); -int APS5_VABI sceHttpCreateEpoll(int, HttpEpollHandle*); -int APS5_VABI sceHttpDestroyEpoll(int, HttpEpollHandle); -int APS5_VABI sceHttpWaitRequest(HttpEpollHandle, HttpNBEvent*, int, int); -int APS5_VABI sceHttpAbortWaitRequest(HttpEpollHandle); -int APS5_VABI sceHttpInit_nid_postfix(int, int, std::uint64_t); -int APS5_VABI sceHttpTerm_nid_postfix(int); -int APS5_VABI sceHttpAuthCacheFlush(int); -int APS5_VABI sceHttpCookieFlush(int); -int APS5_VABI sceHttpReadData(int, void*, std::size_t); -int APS5_VABI sceHttpCreateRequest2(int, const char*, const char*, std::uint64_t); -int APS5_VABI sceHttpsEnableOption(int, std::uint32_t); -int APS5_VABI sceHttpsLoadCert(int, int, void*, void*, void*); -int APS5_VABI sceHttpGetLastErrno(int, int*); -int APS5_VABI sceHttpSetResponseHeaderMaxSize(int, std::uint64_t); -int APS5_VABI sceHttpSetRecvBlockSize(int, std::uint32_t); -int APS5_VABI sceHttpRedirectCacheFlush(int); -int APS5_VABI sceHttpsUnloadCert(int); -int APS5_VABI sceHttpsSetSslVersion(int, int); -int APS5_VABI sceHttpsGetSslError(int, int*, std::uint32_t*); -int APS5_VABI sceHttpSetRedirectCallback(int, HttpRedirectCallback, void*); -int APS5_VABI sceHttpSetCookieRecvCallback(int, HttpCookieRecvCallback, void*); -int APS5_VABI sceHttpSetAuthInfoCallback(int, HttpAuthInfoCallback, void*); -int APS5_VABI sceHttpParseStatusLine(const char*, std::size_t, std::int32_t*, std::int32_t*, std::int32_t*, const char**, std::size_t*); -} +alignas(16) static std::array processParameters{}; static void Require(bool value) { if (!value) std::abort(); } static bool Equal(const char* left, const char* right) { return std::strcmp(left, right) == 0; } + +static_assert(sizeof(HttpMemoryPoolStats) == 24); + +template +static bool Throws(TCall call) { + try { + call(); + } catch (const std::exception&) { + return true; + } + return false; +} + +static void CheckPoolStatsRejected(int id, int expected) { + alignas(HttpMemoryPoolStats) unsigned char buffer[sizeof(HttpMemoryPoolStats) + 16]; + std::memset(buffer, 0x5a, sizeof(buffer)); + Require(sceHttpGetMemoryPoolStats(id, reinterpret_cast(buffer)) == expected); + for (unsigned char byte : buffer) Require(byte == 0x5a); +} + +static void CheckPoolStats(int id, std::size_t poolSize) { + alignas(HttpMemoryPoolStats) unsigned char buffer[sizeof(HttpMemoryPoolStats) + 16]; + std::memset(buffer, 0x5a, sizeof(buffer)); + Require(sceHttpGetMemoryPoolStats(id, reinterpret_cast(buffer)) == 0); + HttpMemoryPoolStats stats; + std::memcpy(&stats, buffer, sizeof(stats)); + Require(stats.poolSize == poolSize); + Require(stats.peakUsedSize == 0 && stats.usedSize == 0); + for (std::size_t i = sizeof(stats); i < sizeof(buffer); ++i) Require(buffer[i] == 0x5a); +} + static void CheckUriParseCharacters() { char pool[256]; SceHttpUriElement element{}; @@ -59,15 +60,174 @@ static void CheckUriParseCharacters() { Require(Equal(element.scheme, "aZ09+.-") && element.port == 0); for (unsigned value = 128; value <= 255; ++value) { const std::string byte(1, static_cast(value)); - const std::string urls[] = { - "http://" + byte + "/", "http://[" + byte + "]/", byte + "://host/", - "a" + byte + "://host/", "http://host:" + byte + "/" - }; - for (const auto& url : urls) { + for (const auto& url : {"http://[" + byte + "]/", "http://host:" + byte + "/"}) { std::size_t required = 0; Require(sceHttpUriParse(nullptr, url.c_str(), nullptr, &required, 0) == static_cast(0x80433060)); Require(sceHttpUriParse(&element, url.c_str(), pool, nullptr, sizeof(pool)) == static_cast(0x80433060)); } + Require(sceHttpUriParse(&element, ("http://" + byte + "/").c_str(), pool, nullptr, sizeof(pool)) == 0); + Require(Equal(element.hostname, "") && element.path == byte + "/"); + Require(sceHttpUriParse(&element, (byte + "://host/").c_str(), pool, nullptr, sizeof(pool)) == 0); + Require(element.opaque && Equal(element.scheme, "") && Equal(element.hostname, "") && element.path == byte + "://host/"); + Require(sceHttpUriParse(&element, ("a" + byte + "://host/").c_str(), pool, nullptr, sizeof(pool)) == 0); + Require(Equal(element.scheme, "") && Equal(element.hostname, "a") && element.path == byte + "://host/"); + } +} + +struct UriParseCase { + const char* url; + int opaque; + std::uint16_t port; + const char* scheme; + const char* username; + const char* password; + const char* hostname; + const char* path; + const char* query; + const char* fragment; + std::size_t pathSpace = 0; +}; + +static void CheckUriParseAsMeasured() { + constexpr UriParseCase cases[] = { + {"http://bad host/", 0, 80, "http", "", "", "bad", " host/", "", ""}, + {"http://ex%41mple.com/", 0, 80, "http", "", "", "ex", "%41mple.com/", "", ""}, + {"http://h\t/", 0, 80, "http", "", "", "h", "\t/", "", ""}, + {"not a url", 1, 0, "", "", "", "not", " a url", "", ""}, + {"example.com/a", 1, 0, "", "", "", "example.com", "/a", "", ""}, + {"mailto:x", 1, 0, "mailto", "", "", "x", "", "", ""}, + {"abc:def", 1, 0, "abc", "", "", "def", "", "", ""}, + {"http:", 1, 80, "http", "", "", "", "", "", ""}, + {"https:h", 1, 443, "https", "", "", "h", "", "", ""}, + {"//host/x", 0, 0, "", "", "", "host", "/x", "", ""}, + {"", 1, 0, "", "", "", "", "", "", ""}, + {"?q", 1, 0, "", "", "", "", "", "?q", ""}, + {"http://host:65536/", 0, 0, "http", "", "", "host", "/", "", ""}, + {"http://h:65535", 0, 65535, "http", "", "", "h", "", "", ""}, + {"http://h:0080/", 0, 80, "http", "", "", "h", "/", "", ""}, + {"http://h:00000/", 0, 0, "http", "", "", "h", "/", "", ""}, + {"http://host:/", 0, 80, "http", "", "", "host", "/", "", ""}, + {"http://[::1]x/", 0, 80, "http", "", "", "::1", "x/", "", ""}, + {"http://[::1]]/", 0, 80, "http", "", "", "::1", "]/", "", ""}, + {"http://[::1]:/", 0, 80, "http", "", "", "::1", "/", "", ""}, + {"http://[]/", 0, 80, "http", "", "", "", "/", "", ""}, + {"http://[.]/", 0, 80, "http", "", "", ".", "/", "", ""}, + {"http://u@h@i/", 0, 80, "http", "u", "", "h", "@i/", "", ""}, + {"http://u s@h/", 0, 80, "http", "", "", "u", " s@h/", "", ""}, + {"http://u:p:q@h/", 0, 80, "http", "u", "p:q", "h", "/", "", ""}, + {"http://u%41@h/", 0, 80, "http", "u%41", "", "h", "/", "", ""}, + {"http://h@/", 0, 80, "http", "h", "", "", "/", "", ""}, + {"http://[::1]@h/", 0, 80, "http", "", "", "::1", "@h/", "", ""}, + {"http://u@[::1]:8/", 0, 8, "http", "u", "", "::1", "/", "", ""}, + {".@h/", 1, 0, "", ".", "", "h", "/", "", ""}, + {"http://.a/", 0, 80, "http", "", "", "", ".a/", "", ""}, + {"http://-a/", 0, 80, "http", "", "", "-a", "/", "", ""}, + {"http://a..b/", 0, 80, "http", "", "", "a..b", "/", "", ""}, + {"a.", 1, 0, "", "", "", "a.", "", "", ""}, + {"http://h/a/..", 0, 80, "http", "", "", "h", "/a/..", "", ""}, + {"http://h/a/.", 0, 80, "http", "", "", "h", "/a/.", "", ""}, + {"http://h/a/b/../../..", 0, 80, "http", "", "", "h", "/..", "", "", 13}, + {"http://h/../../a", 0, 80, "http", "", "", "h", "/a", "", "", 8}, + {"http://h/a//b/../c", 0, 80, "http", "", "", "h", "/a//c", "", "", 10}, + {"http://h/a/.../../b", 0, 80, "http", "", "", "h", "/a/b", "", "", 11}, + {"http://h/./a/../b/", 0, 80, "http", "", "", "h", "/b/", "", "", 10}, + {"./a", 1, 0, "", "", "", "", "./a", "", ""}, + {"a/../b", 1, 0, "", "", "", "a", "/b", "", "", 5}, + {"1x://h/", 1, 0, "", "", "", "1x", "//h/", "", ""}, + {"-x://h/", 0, 0, "-x", "", "", "h", "/", "", ""}, + {"s_x://h/p", 1, 0, "", "", "", "s_x", "//h/p", "", ""}, + {"s:x://h/p", 1, 0, "s", "", "", "x", "//h/p", "", ""}, + {"s@x://h/p", 1, 0, "", "s", "", "x", "//h/p", "", ""}, + {"httpx://h/", 0, 80, "httpx", "", "", "h", "/", "", ""}, + {"httpsx://h/", 0, 443, "httpsx", "", "", "h", "/", "", ""}, + {"HTTPx://h/", 0, 80, "HTTPx", "", "", "h", "/", "", ""}, + {"htt://h/", 0, 0, "htt", "", "", "h", "/", "", ""}, + {"ws://h/", 0, 0, "ws", "", "", "h", "/", "", ""}, + {"https://h:80/", 0, 80, "https", "", "", "h", "/", "", ""}, + {"http://h/p?q#f#g", 0, 80, "http", "", "", "h", "/p", "?q", "#f#g"}, + {"http://h/p#f?q", 0, 80, "http", "", "", "h", "/p", "", "#f?q"}, + {"http://h?", 0, 80, "http", "", "", "h", "", "?", ""}, + }; + char pool[256]; + for (const auto& row : cases) { + SceHttpUriElement element{}; + std::size_t required = 0; + Require(sceHttpUriParse(nullptr, row.url, nullptr, &required, 0) == 0); + const std::size_t size = std::strlen(row.scheme) + std::strlen(row.username) + std::strlen(row.password) + std::strlen(row.hostname) + + (row.pathSpace != 0 ? row.pathSpace : std::strlen(row.path)) + std::strlen(row.query) + std::strlen(row.fragment) + 7; + Require(required == size); + required = 77; + Require(sceHttpUriParse(&element, row.url, pool, &required, size - 1) == static_cast(0x80431022) && required == 77); + Require(sceHttpUriParse(&element, row.url, pool, &required, sizeof(pool)) == 0 && required == size); + Require(element.opaque == row.opaque && element.port == row.port); + Require(Equal(element.scheme, row.scheme) && Equal(element.username, row.username) && Equal(element.password, row.password)); + Require(Equal(element.hostname, row.hostname) && Equal(element.path, row.path)); + Require(Equal(element.query, row.query) && Equal(element.fragment, row.fragment)); + } + for (const char* url : {"http://[::1/", "http://[a b]/", "http://[a%b]/", "http://[a@b]/", "http://host:80a/", "http://h:65537/", "http://h:99999/", + "http://h:000080/", "http://h:80?q", "http://h:80#f", "http://h:80 /", "http://h: 80/", "http://h:+80/", "http://h:-1/", + "http://h:80:81/", "http://[::1]:8a/", "http://a:b/p"}) { + std::size_t required = 77; + Require(sceHttpUriParse(nullptr, url, nullptr, &required, 0) == static_cast(0x80433060) && required == 77); + } +} + +struct UriMergeCase { + const char* base; + const char* relative; + std::size_t sizeOnly; + const char* merged; +}; + +static void CheckUriMergeAsMeasured() { + constexpr UriMergeCase cases[] = { + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "?x", 0x84, "http://user:pw@example.com:8080/a/b/?x"}, + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "#y", 0x84, "http://user:pw@example.com:8080/a/b/#y"}, + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "../../../../d", 0x9a, "http://user:pw@example.com:8080/a/b/../../../../d"}, + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "http:d", 0x8c, "http://user:pw@example.com:8080/a/b/http:d"}, + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "//other.com/x", 0x9a, "//other.com/x"}, + {"http://example.com", "../d", 0x44, "http://example.com/../d"}, + {"http://example.com/a/./b/../c", "d", 0x5f, "http://example.com/a/d"}, + {"not a url", "d", 0x26, "not a url/d"}, + {"example.com/a", "b", 0x32, "example.com/b"}, + {"http://[::1]:80/a/b", "c", 0x3c, "http://::1/a/c"}, + {"http://ex ample.com/a", "c", 0x47, "http://ex ample.com/c"}, + {"http://example.com/a", "//bad host/x", 0x5a, "//bad host/x"}, + {"//host/a/b", "c", 0x27, "//host/a/c"}, + {"/a/b", "c", 0x17, "/a/c"}, + {"a/b", "c", 0x14, "a/c"}, + {"", "c", 0xb, "/c"}, + {"", "", 0x9, "/"}, + {"mailto:x@y", "c", 0x27, "mailto:x@y/c"}, + {"http://h", "?q", 0x22, "http://h/?q"}, + {"https://h:443/a", "c", 0x31, "https://h/c"}, + {"http://h:0/a", "c", 0x2a, "http://h/c"}, + {"http://h:65536/a", "c", 0x32, "http://h/c"}, + {"http://:p@h/a", "c", 0x2d, "http://:p@h/c"}, + {"http://bad host/", "./x", 0x3c, "http://bad host/./x"}, + {"http://h/a", "http://bad host/", 0x44, "http://bad host/"}, + {"http://h/a", "HTTPS://Other.com/", 0x48, "HTTPS://Other.com/"}, + {"http://h/a", "//o", 0x2a, "//o"}, + {"http://h/a", "x:y", 0x2a, "http://h/x:y"}, + {"http://h/a", "d e", 0x2a, "http://h/d e"}, + {"http://h/a", "..", 0x28, "http://h/.."}, + {"http://h/a/./b/../c", "../d", 0x47, "http://h/a/../d"}, + {"http://h/a?q#f", "", 0x30, "http://h/"}, + {"not a url", "//x/y", 0x2e, "//x/y"}, + }; + char merged[512]; + for (const auto& row : cases) { + std::size_t required = 0; + Require(sceHttpUriMerge(nullptr, row.base, row.relative, &required, 0, 0) == 0 && required == row.sizeOnly); + std::memset(merged, 'Z', sizeof(merged)); + required = 77; + Require(sceHttpUriMerge(merged, row.base, row.relative, &required, row.sizeOnly - 1, 0) == static_cast(0x80431022)); + Require(merged[0] == 'Z'); + Require(sceHttpUriMerge(merged, row.base, row.relative, &required, sizeof(merged), 0) == 0 && Equal(merged, row.merged)); + } + for (const char* base : {"http://[::1/a", "http://h:80a/a"}) { + std::size_t required = 77; + Require(sceHttpUriMerge(merged, base, "c", &required, sizeof(merged), 0) == static_cast(0x80433060) && required == 77); } } @@ -92,12 +252,191 @@ static void CheckUriEscapeBytes() { } } -static int AuthInfo(int, int, const char*, char*, char*, int, std::uint8_t**, std::uint64_t*, int*, void*) { std::abort(); } +struct StatusLineCase { + bool tildeFill; + const char* line; + std::size_t length; + int result; + std::int32_t major; + std::int32_t minor; + std::int32_t code; + long reasonOffset; + std::size_t phraseLength; +}; + +static void CheckStatusLineAsMeasured() { + constexpr int parseInvalidResponse = static_cast(0x80432060); + constexpr StatusLineCase cases[] = { + {false, "HTTP/1.1 200 OK\r\n", 17, 17, 1, 1, 200, 12, 0x3}, + {false, "HTTP/1.0 404 Not Found\n", 23, 23, 1, 0, 404, 12, 0xa}, + {false, "HTTP/1.1 200 OK", 15, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {false, "HTTP/1.1 200\r\n", 14, 14, 1, 1, 200, 12, 0x0}, + {false, "HTTP/1.1 200 \r\n", 15, 15, 1, 1, 200, 12, 0x1}, + {false, "HTTP/2 200 OK\r\n", 15, parseInvalidResponse, 2, 0, -7, -1, 0xabcd}, + {false, "HTTP/10.20 999 X\r\n", 18, 18, 10, 20, 999, 14, 0x2}, + {false, "HTTP/1.1 20 OK\r\n", 16, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "HTTP/1.1 2000 OK\r\n", 18, 18, 1, 1, 200, 12, 0x4}, + {false, "HTTP/1.1 abc OK\r\n", 17, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "http/1.1 200 OK\r\n", 17, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\r\n", 18, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "HTTP/1.1\t200 OK\r\n", 17, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200OK\r\n", 16, 16, 1, 1, 200, 12, 0x2}, + {false, "ICY 200 OK\r\n", 12, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\r", 16, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\r\nX: y", 21, 17, 1, 1, 200, 12, 0x3}, + {false, "HTTP/1.1 200 OK\r\r\n", 18, 18, 1, 1, 200, 12, 0x4}, + {false, "HTTP/.1 200 OK\r\n", 16, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1. 200 OK\r\n", 16, parseInvalidResponse, 1, 0, -7, -1, 0xabcd}, + {false, "HTTP/4294967297.1 200 OK\n", 25, 25, 1, 1, 200, 21, 0x3}, + {false, "HTTP/1.1 -12 OK\n", 16, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, " HTTP/1.1 200 OK\n", 17, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 A\nB\n", 17, 15, 1, 1, 200, 12, 0x2}, + {false, "HTTP/1.1 200\n", 13, 13, 1, 1, 200, 12, 0x0}, + {false, "HTTP/1.1 200 OK\n", 15, 16, 1, 1, 200, 12, 0x3}, + {false, "HTTP/1.1 200 OK\n", 12, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\n", 11, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\n", 8, 16, 1, 1, 200, 12, 0x3}, + {false, "HTTP/1.1 200 OK\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 8, 16, 1, 1, 200, 12, 0x3}, + {true, "HTTP/1.1 200 OK\n", 9, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 10, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 11, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 12, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 13, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 14, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 15, 16, 1, 1, 200, 12, 0x3}, + {true, "HTTP/1.1 200 OK\n", 16, 16, 1, 1, 200, 12, 0x3}, + {true, "HTTP/10.20 999 X\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 8, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/10.20 999 X\n", 9, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/10.20 999 X\n", 10, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/10.20 999 X\n", 11, parseInvalidResponse, 10, 20, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 12, parseInvalidResponse, 10, 20, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 13, parseInvalidResponse, 10, 20, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 14, parseInvalidResponse, 10, 20, 999, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 15, parseInvalidResponse, 10, 20, 999, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 16, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/10.20 999 X\n", 17, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/1.12 200 OK\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 8, 17, 1, 12, 200, 13, 0x3}, + {true, "HTTP/1.12 200 OK\n", 9, 17, 1, 12, 200, 13, 0x3}, + {true, "HTTP/1.12 200 OK\n", 10, parseInvalidResponse, 1, 12, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 11, parseInvalidResponse, 1, 12, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 12, parseInvalidResponse, 1, 12, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 13, parseInvalidResponse, 1, 12, 200, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 14, parseInvalidResponse, 1, 12, 200, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 15, parseInvalidResponse, 1, 12, 200, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 16, 17, 1, 12, 200, 13, 0x3}, + {true, "HTTP/1.12 200 OK\n", 17, 17, 1, 12, 200, 13, 0x3}, + {true, "HTTP/12.1 200 OK\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 8, 17, 12, 1, 200, 13, 0x3}, + {true, "HTTP/12.1 200 OK\n", 9, 17, 12, 1, 200, 13, 0x3}, + {true, "HTTP/12.1 200 OK\n", 10, parseInvalidResponse, 12, 1, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 11, parseInvalidResponse, 12, 1, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 12, parseInvalidResponse, 12, 1, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 13, parseInvalidResponse, 12, 1, 200, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 14, parseInvalidResponse, 12, 1, 200, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 15, parseInvalidResponse, 12, 1, 200, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 16, 17, 12, 1, 200, 13, 0x3}, + {true, "HTTP/12.1 200 OK\n", 17, 17, 12, 1, 200, 13, 0x3}, + {true, "HTTP/1.1 200\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 8, 13, 1, 1, 200, 12, 0x0}, + {true, "HTTP/1.1 200\n", 9, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 10, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 11, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 12, 13, 1, 1, 200, 12, 0x0}, + {true, "HTTP/1.1 200\n", 13, 13, 1, 1, 200, 12, 0x0}, + {true, "HTTP/1.1 200 OK", 15, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK", 8, 255, 1, 1, 200, 12, 0xf2}, + {true, "HTTP/1.1 200 OK", 9, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK", 12, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK", 13, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 2", 8, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 20", 8, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1", 8, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + }; + static char buffer[256]; + for (const auto& row : cases) { + std::memset(buffer, row.tildeFill ? '~' : '\0', sizeof(buffer)); + std::memcpy(buffer, row.line, std::strlen(row.line)); + if (row.tildeFill) buffer[sizeof(buffer) - 2] = '\n'; + buffer[sizeof(buffer) - 1] = '\0'; + std::int32_t major = -7; + std::int32_t minor = -7; + std::int32_t code = -7; + const char* reason = nullptr; + std::size_t phraseLength = 0xabcd; + Require(sceHttpParseStatusLine(buffer, row.length, &major, &minor, &code, &reason, &phraseLength) == row.result); + Require(major == row.major && minor == row.minor && code == row.code && phraseLength == row.phraseLength); + Require(row.reasonOffset < 0 ? reason == nullptr : reason == buffer + row.reasonOffset); + } +} + +static int APS5_VABI AuthInfo(int, int, const char*, char*, char*, int, std::uint8_t**, std::uint64_t*, int*, void*) { std::abort(); } static void APS5_VABI RequestStatusCallback(int, int, void* argument) { ++*static_cast(argument); } int main() { + const std::uint64_t processSize = processParameters.size(); + const std::uint32_t processMagic = 0x4942524f; + std::uint32_t sdkVersion = 0x1000000; + std::memcpy(processParameters.data(), &processSize, sizeof(processSize)); + std::memcpy(processParameters.data() + 8, &processMagic, sizeof(processMagic)); + std::memcpy(processParameters.data() + 16, &sdkVersion, sizeof(sdkVersion)); + Require(ProcessParametersFixture::Install(processParameters.data(), processParameters.size())); + for (const std::uint32_t version : {0u, 0xffffffu, 0x1000000u}) { + std::memcpy(processParameters.data() + 16, &version, sizeof(version)); + for (unsigned value = 1; value < 256; ++value) { + if (value == '%') continue; + const char input[]{static_cast(value), 0}; + char output[]{'x', 'x'}; + std::size_t required = 123; + const bool allowed = version >= 0x1000000 || (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z') || (value >= '0' && value <= '9') || value == '*' || value == '-' || value == '.' || value == '_' || value == '+'; + Require(sceHttpUriUnescape(output, &required, sizeof(output), input) == (allowed ? 0 : static_cast(0x80433060))); + Require(allowed ? output[0] == input[0] && output[1] == 0 && required == 2 : output[0] == 'x' && output[1] == 'x' && required == 123); + } + char output[2]{}; + Require(sceHttpUriUnescape(output, nullptr, sizeof(output), "%7E") == 0 && output[0] == '~'); + } + std::memcpy(processParameters.data() + 16, &sdkVersion, sizeof(sdkVersion)); int callbackCount = 0; const auto request = sceHttpCreateRequest2(1, "GET", "/", 0); Require(sceHttpSetRequestStatusCallback(request, RequestStatusCallback, &callbackCount) == 0); @@ -114,7 +453,7 @@ int main() { const char* url = "https://user:secret@example.com:8443/a/./b/../c?x=1&y=2#top"; std::size_t required = 0; Require(sceHttpUriParse(nullptr, url, nullptr, &required, 0) == 0); - Require(required == 6 + 5 + 7 + 12 + 5 + 9 + 5); + Require(required == 6 + 5 + 7 + 12 + 12 + 9 + 5); char pool[256]; SceHttpUriElement element; @@ -124,6 +463,7 @@ int main() { Require(Equal(element.scheme, "https") && Equal(element.username, "user") && Equal(element.password, "secret")); Require(Equal(element.hostname, "example.com") && element.port == 8443); Require(Equal(element.path, "/a/c") && Equal(element.query, "?x=1&y=2") && Equal(element.fragment, "#top")); + Require(element.path == pool + 30 && element.query == pool + 42 && element.fragment == pool + 51); char built[256]; Require(sceHttpUriBuild(nullptr, &required, 0, &element, buildAll) == 0); @@ -153,7 +493,7 @@ int main() { Require(sceHttpUriParse(&element, "http://[::1]:8080/index.html", pool, nullptr, sizeof(pool)) == 0); Require(Equal(element.hostname, "::1") && element.port == 8080 && Equal(element.path, "/index.html")); Require(sceHttpUriBuild(built, nullptr, sizeof(built), &element, buildAll) == 0); - Require(Equal(built, "http://[::1]:8080/index.html")); + Require(Equal(built, "http://::1:8080/index.html")); Require(sceHttpUriParse(&element, "mailto:someone@example.com", pool, nullptr, sizeof(pool)) == 0); Require(element.opaque != 0 && element.port == 0); @@ -170,13 +510,38 @@ int main() { Require(sceHttpUriParse(&element, nullptr, pool, nullptr, sizeof(pool)) == invalidUrl); Require(sceHttpUriParse(nullptr, url, nullptr, nullptr, 0) == invalidValue); - Require(sceHttpUriParse(nullptr, "http://host:65536/", nullptr, &required, 0) == invalidUrl); + Require(sceHttpUriParse(nullptr, "http://host:65537/", nullptr, &required, 0) == invalidUrl); Require(sceHttpUriParse(nullptr, "http://host:80a/", nullptr, &required, 0) == invalidUrl); Require(sceHttpUriParse(nullptr, "http://[::1/", nullptr, &required, 0) == invalidUrl); - Require(sceHttpUriParse(nullptr, "http://bad host/", nullptr, &required, 0) == invalidUrl); + Require(sceHttpUriParse(nullptr, "http://host:80?q", nullptr, &required, 0) == invalidUrl); + CheckUriParseAsMeasured(); Require(sceHttpUriBuild(built, nullptr, sizeof(built), nullptr, buildAll) == invalidUrl); Require(sceHttpUriBuild(nullptr, nullptr, 0, &element, buildAll) == invalidValue); + { + SceHttpUriElement bounded{}; + bounded.opaque = 2; + char scheme[33]; + char hostname[256]; + std::memset(scheme, 's', sizeof(scheme)); + std::memset(hostname, 'h', sizeof(hostname)); + bounded.scheme = scheme; + bounded.hostname = hostname; + char output[512]; + Require(sceHttpUriBuild(output, &required, sizeof(output), &bounded, 3) == 0); + Require(required == 32 + 1 + 2 + 255 + 1); + Require(output[32] == ':' && output[33] == '/' && output[34] == '/'); + Require(output[required - 1] == '\0'); + scheme[0] = '\0'; + Require(sceHttpUriBuild(output, &required, sizeof(output), &bounded, 1) == 0); + Require(required == 4 && Equal(output, "//")); + char prefix[] = "HTTPS-extra"; + bounded.scheme = prefix; + bounded.port = 443; + Require(sceHttpUriBuild(output, &required, sizeof(output), &bounded, 4) == 0); + Require(required == 3 && Equal(output, "//")); + } + char escaped[64]; Require(sceHttpUriEscape(nullptr, &required, 0, "a b/~\xC3\xA9") == 0); Require(required == std::strlen("a%20b%2F~%C3%A9") + 1); @@ -209,9 +574,9 @@ int main() { const std::size_t size = std::strlen(row[1]) + 1; Require(sceHttpUriUnescape(nullptr, &required, 0, row[0]) == 0 && required == size); std::memset(escaped, 'Z', sizeof(escaped)); - required = 77; - Require(sceHttpUriUnescape(escaped, &required, size - 1, row[0]) == outOfMemory && required == 77); - Require(std::string_view(escaped, size - 1) == row[1] && escaped[size - 1] == 'Z'); + required = 123; + Require(sceHttpUriUnescape(escaped, &required, size - 1, row[0]) == outOfMemory && required == 123); + Require(std::memcmp(escaped, row[1], size - 1) == 0 && escaped[size - 1] == 'Z'); Require(sceHttpUriUnescape(escaped, nullptr, size, row[0]) == 0); Require(Equal(escaped, row[1]) && escaped[size] == 'Z'); } @@ -231,6 +596,17 @@ int main() { Require(sceHttpUriUnescape(escaped, &required, sizeof(escaped), nullptr) == invalidValue); Require(required == 123 && Equal(escaped, "A/%20")); + for (const char* malformed : {"%", "%1", "%1g", "%gg", "%+1", "%-1", "prefix%4", "prefix%xy"}) { + std::memset(escaped, 'Z', sizeof(escaped)); + required = 123; + Require(sceHttpUriUnescape(escaped, &required, sizeof(escaped), malformed) == static_cast(0x80433060)); + Require(required == 123); + for (char value : escaped) Require(value == 'Z'); + } + std::memset(escaped, 'Z', sizeof(escaped)); + Require(sceHttpUriUnescape(escaped, &required, 2, "a%20b") == outOfMemory); + Require(required == 123 && escaped[0] == 'a' && escaped[1] == ' ' && escaped[2] == 'Z'); + char bytes[256], encodedBytes[766], decodedBytes[256]; for (std::size_t i = 1; i < 256; ++i) bytes[i - 1] = static_cast(i); bytes[255] = '\0'; @@ -285,9 +661,10 @@ int main() { Require(sceHttpUriMerge(merged, base, nullptr, &required, sizeof(merged), 0) == invalidValue); Require(sceHttpUriMerge(merged, base, "./x", &required, sizeof(merged), 1) == invalidValue); Require(sceHttpUriMerge(nullptr, nullptr, nullptr, &required, 0, 1) == invalidValue); - Require(sceHttpUriMerge(merged, "http://bad host/", "./x", &required, sizeof(merged), 0) == invalidUrl); - Require(sceHttpUriMerge(merged, base, "http://bad host/", &required, sizeof(merged), 0) == invalidUrl); + Require(sceHttpUriMerge(merged, "http://[::1/", "./x", &required, sizeof(merged), 0) == invalidUrl); + Require(sceHttpUriMerge(merged, "http://h:80a/a", "c", &required, sizeof(merged), 0) == invalidUrl); Require(required == 123); + CheckUriMergeAsMeasured(); constexpr int beforeInit = static_cast(0x80431001); constexpr int aborted = static_cast(0x80431080); @@ -299,6 +676,10 @@ int main() { flushCaches(0, beforeInit); flushCaches(-1, beforeInit); flushCaches(request, beforeInit); + CheckPoolStatsRejected(0, beforeInit); + CheckPoolStatsRejected(-1, beforeInit); + CheckPoolStatsRejected(request, beforeInit); + Require(Throws([] { sceHttpGetMemoryPoolStats(0, nullptr); })); HttpEpollHandle epoll = nullptr; HttpNBEvent events[2]{}; Require(sceHttpCreateEpoll(1, &epoll) == beforeInit); @@ -312,6 +693,14 @@ int main() { flushCaches(-1, invalidId); flushCaches(INT32_MAX, invalidId); flushCaches(request, invalidId); + CheckPoolStats(context, 0x10000); + CheckPoolStats(context, 0x10000); + CheckPoolStatsRejected(0, invalidId); + CheckPoolStatsRejected(-1, invalidId); + CheckPoolStatsRejected(INT32_MAX, invalidId); + CheckPoolStatsRejected(request, invalidId); + Require(Throws([context] { sceHttpGetMemoryPoolStats(context, nullptr); })); + Require(Throws([] { sceHttpGetMemoryPoolStats(INT32_MAX, nullptr); })); Require(sceHttpCreateEpoll(-1, &epoll) == invalidId); Require(sceHttpCreateEpoll(context, nullptr) == invalidValue); Require(sceHttpCreateEpoll(context, &epoll) == 0 && epoll != nullptr); @@ -353,11 +742,15 @@ int main() { Require(waitWokenBy([&] { Require(sceHttpDestroyEpoll(context, epoll) == 0); }) == 0); Require(sceHttpCreateEpoll(context, &epoll) == 0); - const int otherContext = sceHttpInit_nid_postfix(0, 0, 0x10000); + const int otherContext = sceHttpInit_nid_postfix(0, 0, 0x23000); Require(otherContext > 0 && otherContext != context); flushCaches(otherContext, 0); + CheckPoolStats(otherContext, 0x23000); + CheckPoolStats(context, 0x10000); Require(sceHttpTerm_nid_postfix(otherContext) == 0); flushCaches(otherContext, invalidId); + CheckPoolStatsRejected(otherContext, invalidId); + CheckPoolStats(context, 0x10000); flushCaches(context, 0); Require(sceHttpTerm_nid_postfix(otherContext) == invalidId); Require(sceHttpWaitRequest(epoll, events, 2, 0) == 0); @@ -365,13 +758,23 @@ int main() { Require(sceHttpWaitRequest(epoll, events, 2, 0) == beforeInit); Require(sceHttpTerm_nid_postfix(context) == beforeInit); flushCaches(context, beforeInit); - const int newContext = sceHttpInit_nid_postfix(0, 0, 0x10000); + CheckPoolStatsRejected(context, beforeInit); + const int newContext = sceHttpInit_nid_postfix(0, 0, 0x123456000ull); Require(newContext > 0 && newContext != context && newContext != otherContext); flushCaches(context, invalidId); flushCaches(otherContext, invalidId); flushCaches(newContext, 0); + CheckPoolStatsRejected(context, invalidId); + CheckPoolStatsRejected(otherContext, invalidId); + CheckPoolStats(newContext, 0x123456000ull); Require(sceHttpTerm_nid_postfix(newContext) == 0); flushCaches(newContext, beforeInit); + CheckPoolStatsRejected(newContext, beforeInit); + const int emptyPoolContext = sceHttpInit_nid_postfix(0, 0, 0); + Require(emptyPoolContext > 0); + CheckPoolStats(emptyPoolContext, 0); + CheckPoolStatsRejected(newContext, invalidId); + Require(sceHttpTerm_nid_postfix(emptyPoolContext) == 0); char data[16]; Require(sceHttpReadData(1, data, sizeof(data)) == network); @@ -448,8 +851,9 @@ int main() { Require(parse("HTTP/1.1 2x0 OK\n", 16) == parseInvalidResponse); Require(parse("HTTP/1.1 20", 11) == parseInvalidResponse); Require(parse(response, 15) == parseInvalidResponse); - Require(parse(response, 16) == parseInvalidResponse); Require(phrase == nullptr && phraseLength == 0); + Require(parse(response, 16) == 17 && phrase == response + 12 && phraseLength == 3); + CheckStatusLineAsMeasured(); auto sweep = [](const char* src, const char* expected) { char swept[128]; std::memset(swept, 'x', sizeof(swept)); @@ -501,5 +905,89 @@ int main() { Require(sceHttpUriCopy(nullptr, &source, nullptr, 0, nullptr) == static_cast(0x804311FE)); SceHttpUriElement empty{}; Require(sceHttpUriCopy(©, &empty, pool, sizeof(pool), &needed) == 0 && needed == 5 && copy.scheme != nullptr && copy.scheme[0] == 0); + Require(copy.scheme == pool && copy.hostname == pool + 1 && copy.path == pool + 2 && copy.username == pool + 3 && copy.password == pool + 4); + + auto allBytes = [](const void* data, std::size_t size, unsigned char value) { + const auto* bytes = static_cast(data); + for (std::size_t i = 0; i < size; ++i) { + if (bytes[i] != value) return false; + } + return true; + }; + std::memset(pool, '~', sizeof(pool)); + std::memset(©, 0x5a, sizeof(copy)); + Require(sceHttpUriCopy(©, &source, pool, 64, &needed) == 0 && needed == 30); + Require(copy.scheme == pool && copy.hostname == pool + 5 && copy.path == pool + 17 && copy.username == pool + 22 && copy.password == pool + 27); + Require(allBytes(pool + 30, 34, 0) && pool[64] == '~' && copy.query == nullptr && copy.fragment == nullptr); + Require(allBytes(reinterpret_cast(©) + 4, 4, 0) && allBytes(copy.reserved, sizeof(copy.reserved), 0)); + + std::memset(pool, '~', sizeof(pool)); + std::memset(©, 0x5a, sizeof(copy)); + needed = 0xabcd; + Require(sceHttpUriCopy(©, &source, pool, 29, &needed) == static_cast(0x80431022) && needed == 0xabcd); + Require(allBytes(©, sizeof(copy), 0) && allBytes(pool, 29, 0) && pool[29] == '~'); + std::memset(pool, '~', sizeof(pool)); + Require(sceHttpUriCopy(©, &empty, pool, 0, &needed) == static_cast(0x80431022) && pool[0] == '~' && needed == 0xabcd); + Require(sceHttpUriCopy(©, &source, pool, 4, nullptr) == static_cast(0x80431022)); + + std::memset(pool, '~', sizeof(pool)); + Require(sceHttpUriCopy(nullptr, &source, pool, sizeof(pool), &needed) == 0 && needed == 30 && pool[0] == '~'); + Require(sceHttpUriCopy(nullptr, &source, pool, sizeof(pool), nullptr) == static_cast(0x804311FE)); + std::memset(©, 0x5a, sizeof(copy)); + Require(sceHttpUriCopy(©, &source, nullptr, 64, &needed) == 0 && needed == 30 && allBytes(©, sizeof(copy), 0x5a)); + Require(sceHttpUriCopy(©, &source, nullptr, 64, nullptr) == static_cast(0x804311FE) && allBytes(©, sizeof(copy), 0x5a)); + needed = 0xabcd; + Require(sceHttpUriCopy(©, nullptr, pool, sizeof(pool), &needed) == static_cast(0x80433060) && needed == 0xabcd && allBytes(©, sizeof(copy), 0x5a)); + Require(sceHttpUriCopy(nullptr, nullptr, nullptr, 0, nullptr) == static_cast(0x80433060)); + + char scheme[] = "SCH"; + char user[] = "usr"; + char pass[] = "pwd"; + char host[] = "hostname"; + char path[] = "/p/q"; + char query[] = "?query"; + char fragment[] = "#frag"; + char blank[] = ""; + SceHttpUriElement manual{}; + manual.opaque = 7; + manual.scheme = scheme; + manual.username = user; + manual.password = pass; + manual.hostname = host; + manual.path = path; + manual.query = query; + manual.fragment = fragment; + manual.port = 1234; + std::memset(manual.reserved, 0x11, sizeof(manual.reserved)); + Require(sceHttpUriCopy(©, &manual, pool, sizeof(pool), &needed) == 0 && needed == 26 && copy.opaque == 0 && copy.port == 1234); + Require(Equal(copy.scheme, "SCH") && copy.hostname == pool + 4 && Equal(copy.hostname, "hostname") && copy.path == pool + 13 && Equal(copy.path, "/p/q")); + Require(copy.username == pool + 18 && Equal(copy.username, "usr") && copy.password == pool + 22 && Equal(copy.password, "pwd")); + Require(copy.query == nullptr && copy.fragment == nullptr && allBytes(copy.reserved, sizeof(copy.reserved), 0)); + + SceHttpUriElement mixed{}; + mixed.hostname = host; + mixed.username = blank; + mixed.query = query; + mixed.port = 80; + Require(sceHttpUriCopy(©, &mixed, pool, sizeof(pool), &needed) == 0 && needed == 13 && copy.port == 80); + Require(copy.scheme == pool && copy.hostname == pool + 1 && copy.path == pool + 10 && copy.username == pool + 11 && copy.password == pool + 12); + + SceHttpUriElement inPlace = manual; + Require(sceHttpUriCopy(&inPlace, &inPlace, pool, sizeof(pool), &needed) == 0 && needed == 5 && inPlace.port == 0); + Require(inPlace.scheme == pool && inPlace.hostname == pool + 1 && inPlace.password == pool + 4 && inPlace.password[0] == 0); + inPlace = manual; + Require(sceHttpUriCopy(&inPlace, &inPlace, nullptr, 0, &needed) == 0 && needed == 26 && inPlace.scheme == scheme); + + alignas(SceHttpUriElement) char hostPool[256]; + auto* inside = reinterpret_cast(hostPool + 160); + std::memcpy(static_cast(inside), &manual, sizeof(manual)); + Require(sceHttpUriCopy(©, inside, hostPool, sizeof(hostPool), &needed) == 0 && needed == 5 && copy.port == 0 && copy.hostname[0] == 0); + + std::memcpy(hostPool + 200, "HOSTINPOOL", 11); + SceHttpUriElement pooled = manual; + pooled.hostname = hostPool + 200; + Require(sceHttpUriCopy(nullptr, &pooled, hostPool, sizeof(hostPool), &needed) == 0 && needed == 28); + Require(sceHttpUriCopy(©, &pooled, hostPool, sizeof(hostPool), &needed) == 0 && needed == 18 && copy.hostname == hostPool + 4 && copy.hostname[0] == 0); + Require(Equal(copy.path, "/p/q") && copy.path == hostPool + 5 && copy.port == 1234); } } diff --git a/core/libs/tests/GuestHttp2.cpp b/core/libs/tests/GuestHttp2.cpp index d29c0021e4..286b03b042 100644 --- a/core/libs/tests/GuestHttp2.cpp +++ b/core/libs/tests/GuestHttp2.cpp @@ -1,48 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceHttp2/include/Http2.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI sceHttp2Init(int, int, std::size_t, int); -int APS5_VABI sceHttp2CreateTemplate(int, const char*, int, int); -int APS5_VABI sceHttp2CreateRequestWithURL(int, const char*, const char*, std::uint64_t); -int APS5_VABI sceHttp2CreateCookieBox(int); -int APS5_VABI sceHttp2DeleteCookieBox(int); -int APS5_VABI sceHttp2SetCookieBox(int, int); -int APS5_VABI sceHttp2CookieFlush(int); -int APS5_VABI sceHttp2SetRequestNoContentLength(int); -int APS5_VABI sceHttp2SendRequest(int, const void*, std::size_t); -int APS5_VABI sceHttp2SendRequestAsync(int, const void*, std::size_t, Http2AsyncOption*, void*); -int APS5_VABI sceHttp2WaitAsync(int, Http2AsyncResult*, std::uint32_t*, void*); -int APS5_VABI sceHttp2DeleteRequest(int); -int APS5_VABI sceHttp2DeleteTemplate(int); -int APS5_VABI sceHttp2SetMinSslVersion(int, std::uint32_t); -int APS5_VABI sceHttp2SslEnableOption(int, std::uint32_t); -int APS5_VABI sceHttp2SslDisableOption(int, std::uint32_t); -int APS5_VABI sceHttp2Term(int); -int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); -int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo); -int APS5_VABI sceKernelAddUserEventEdge(KernelEqueue eq, int id); -int APS5_VABI sceKernelDeleteUserEvent(KernelEqueue eq, int id); -uintptr_t APS5_VABI sceKernelGetEventId(const KernelEvent* ev); -void* APS5_VABI sceKernelGetEventUserData(const KernelEvent* ev); -} - -struct Http2MemoryPoolStats { - std::size_t pool_size; - std::size_t max_inuse_size; - std::size_t current_inuse_size; - std::int32_t reserved; -}; - -extern "C" { -int APS5_VABI sceHttp2GetMemoryPoolStats(int, Http2MemoryPoolStats*); -int APS5_VABI sceHttp2SetResolveRetry(int, std::int32_t); -int APS5_VABI sceHttp2WebSocketSetPingInterval(); -} - static void Require(bool value) { if (!value) std::abort(); } static constexpr int InvalidId = static_cast(0x817B1100); @@ -64,15 +26,20 @@ static bool Throws(TCall call) { } int main() { + Require(sceHttp2CreateCookieBox(0) == static_cast(0x817b121du)); + Require(sceHttp2CreateCookieBox(0xffff) == static_cast(0x817b121du)); + const int independentBox = sceHttp2CreateCookieBox(0x10000); + Require(independentBox > 0); + Require(sceHttp2DeleteCookieBox(independentBox) == 0); const int context = sceHttp2Init(1, 1, 0x10000, 4); Require(context > 0); Http2MemoryPoolStats stats{1, 1, 1, 1}; Require(sceHttp2GetMemoryPoolStats(context, &stats) == 0); Require(stats.pool_size == 0x10000 && stats.max_inuse_size == 0 && stats.current_inuse_size == 0 && stats.reserved == 0); - const int box = sceHttp2CreateCookieBox(context); - const int other = sceHttp2CreateCookieBox(context); + const int box = sceHttp2CreateCookieBox(0x10000); + const int other = sceHttp2CreateCookieBox(0x10000); Require(box > 0 && other > 0 && box != other && box != context); - const int discarded = sceHttp2CreateCookieBox(context); + const int discarded = sceHttp2CreateCookieBox(0x10000); Require(discarded > 0 && discarded != box && discarded != other); Require(sceHttp2DeleteCookieBox(discarded) == 0); Require(sceHttp2DeleteCookieBox(discarded) == InvalidId); @@ -86,7 +53,7 @@ int main() { Require(request > 0); Require(sceHttp2SetCookieBox(request, other) == 0); Require(sceHttp2SetRequestNoContentLength(request) == 0); - Require(Throws([] { sceHttp2WebSocketSetPingInterval(); })); + Require(Throws([] { sceHttp2WebSocketSetPingInterval(0, 1000000); })); Require(sceHttp2CookieFlush(context) == 0); Require(sceHttp2SendRequest(request, nullptr, 0) == static_cast(0x80436063)); @@ -116,7 +83,7 @@ int main() { const int doomedContext = sceHttp2Init(1, 1, 0x2000, 4); Require(doomedContext > 0); - const int doomedContextBox = sceHttp2CreateCookieBox(doomedContext); + const int doomedContextBox = sceHttp2CreateCookieBox(0x10001); const int doomedContextTemplate = sceHttp2CreateTemplate(doomedContext, "agent", 2, 0); Require(doomedContextBox > 0 && doomedContextTemplate > 0); const int doomedContextRequest = sceHttp2CreateRequestWithURL(doomedContextTemplate, "GET", "https://example.com/descendant", 0); @@ -124,7 +91,8 @@ int main() { RequireSslSetters(doomedContext, 0); Require(sceHttp2Term(doomedContext) == 0); RequireSslSetters(doomedContext, InvalidId); - RequireSslSetters(doomedContextBox, InvalidId); + RequireSslSetters(doomedContextBox, 0); + Require(sceHttp2DeleteCookieBox(doomedContextBox) == 0); Require(sceHttp2DeleteCookieBox(doomedContextBox) == InvalidId); RequireSslSetters(doomedContextTemplate, InvalidId); RequireSslSetters(doomedContextRequest, InvalidId); diff --git a/core/libs/tests/GuestHttpOptions.cpp b/core/libs/tests/GuestHttpOptions.cpp index bfa0a25748..e396f51ccd 100644 --- a/core/libs/tests/GuestHttpOptions.cpp +++ b/core/libs/tests/GuestHttpOptions.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHttp/include/Http.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,35 +6,6 @@ #include #include -extern "C" { -int APS5_VABI sceHttpInit_nid_postfix(int, int, std::uint64_t); -int APS5_VABI sceHttpTerm_nid_postfix(int); -int APS5_VABI sceHttpCreateTemplate(int, const char*, int, int); -int APS5_VABI sceHttpDeleteTemplate(int); -int APS5_VABI sceHttpCreateConnectionWithURL(int, const char*, int); -int APS5_VABI sceHttpDeleteConnection(int); -int APS5_VABI sceHttpCreateRequestWithURL(int, int, const char*, std::uint64_t); -int APS5_VABI sceHttpDeleteRequest(int); -int APS5_VABI sceHttpSetAutoRedirect(int, int); -int APS5_VABI sceHttpGetAutoRedirect(int, int*); -int APS5_VABI sceHttpSetNonblock(int, int); -int APS5_VABI sceHttpGetNonblock(int, int*); -int APS5_VABI sceHttpTrySetNonblock(int, int); -int APS5_VABI sceHttpTryGetNonblock(int, int*); -int APS5_VABI sceHttpSetAcceptEncodingGZIPEnabled(int, int); -int APS5_VABI sceHttpGetAcceptEncodingGZIPEnabled(int, int*); -int APS5_VABI sceHttpSetDefaultAcceptEncodingGZIPEnabled(int, int); -int APS5_VABI sceHttpCreateEpoll(int, HttpEpollHandle*); -int APS5_VABI sceHttpDestroyEpoll(int, HttpEpollHandle); -int APS5_VABI sceHttpSetEpoll(int, HttpEpollHandle, void*); -int APS5_VABI sceHttpUnsetEpoll(int); -int APS5_VABI sceHttpGetEpoll(int, HttpEpollHandle*, void**); -int APS5_VABI sceHttpCacheRedirectedConnectionEnabled(int, int); -int APS5_VABI sceHttpsEnableOptionPrivate(int, std::uint32_t); -int APS5_VABI sceHttpsDisableOptionPrivate(int, std::uint32_t); -int APS5_VABI sceHttpAbortRequestForce(int); -} - namespace { constexpr int BeforeInit = static_cast(0x80431001u); @@ -72,6 +44,40 @@ void CheckAutoRedirect(int context) { Require(sceHttpDeleteConnection(connection) == 0 && sceHttpDeleteTemplate(pattern) == 0, "delete failed"); } +void CheckAuthEnabled(int context) { + const int pattern = sceHttpCreateTemplate(context, "anyps5-test", 2, 0); + Require(Get(sceHttpGetAuthEnabled, pattern) == 0, "authentication is enabled by default"); + Require(sceHttpSetAuthEnabled(pattern, 1) == 0 && Get(sceHttpGetAuthEnabled, pattern) == 1, "sceHttpGetAuthEnabled did not return the set value"); + const int connection = sceHttpCreateConnectionWithURL(pattern, "http://example.com/", 1); + const int request = sceHttpCreateRequestWithURL(connection, 0, "http://example.com/a", 0); + Require(Get(sceHttpGetAuthEnabled, connection) == 1 && Get(sceHttpGetAuthEnabled, request) == 1, "a child did not inherit its parent's authentication option"); + Require(sceHttpSetAuthEnabled(request, 0) == 0 && Get(sceHttpGetAuthEnabled, request) == 0, "authentication could not be disabled on a request"); + Require(Get(sceHttpGetAuthEnabled, connection) == 1 && Get(sceHttpGetAuthEnabled, pattern) == 1, "setting a request changed its parent options"); + int value = -1; + Require(sceHttpGetAuthEnabled(request, nullptr) == InvalidValue, "a null authentication output was not refused"); + Require(sceHttpGetAuthEnabled(UnknownId, nullptr) == InvalidValue, "the authentication output pointer is not checked before the id"); + Require(sceHttpGetAuthEnabled(UnknownId, &value) == InvalidId && sceHttpSetAuthEnabled(UnknownId, 1) == InvalidId, "an unknown id was not refused for authentication options"); + Require(sceHttpDeleteRequest(request) == 0 && sceHttpGetAuthEnabled(request, &value) == InvalidId, "a deleted request still has authentication options"); + Require(sceHttpDeleteConnection(connection) == 0 && sceHttpDeleteTemplate(pattern) == 0, "delete failed"); +} + +void CheckCookieEnabled(int context) { + const int pattern = sceHttpCreateTemplate(context, "anyps5-test", 2, 0); + Require(Get(sceHttpGetCookieEnabled, pattern) == 0, "a template enables cookies by default"); + Require(sceHttpSetCookieEnabled(pattern, 1) == 0 && Get(sceHttpGetCookieEnabled, pattern) == 1, "the cookie-enabled getter does not return what was set"); + const int connection = sceHttpCreateConnectionWithURL(pattern, "http://example.com/", 1); + const int request = sceHttpCreateRequestWithURL(connection, 0, "http://example.com/a", 0); + Require(Get(sceHttpGetCookieEnabled, connection) == 1 && Get(sceHttpGetCookieEnabled, request) == 1, "a child did not inherit its parent's cookie option"); + Require(sceHttpSetCookieEnabled(request, 0) == 0 && Get(sceHttpGetCookieEnabled, request) == 0, "the cookie option could not be disabled on a child"); + Require(Get(sceHttpGetCookieEnabled, connection) == 1 && Get(sceHttpGetCookieEnabled, pattern) == 1, "changing a child changed its parent or sibling"); + int value = -1; + Require(sceHttpGetCookieEnabled(request, nullptr) == InvalidValue, "a null cookie output was not refused"); + Require(sceHttpSetCookieEnabled(pattern, -1) == InvalidValue && sceHttpSetCookieEnabled(pattern, 2) == InvalidValue, "an invalid cookie-enabled value was accepted"); + Require(sceHttpGetCookieEnabled(UnknownId, &value) == InvalidId && sceHttpSetCookieEnabled(UnknownId, 1) == InvalidId, "an unknown id was not refused for cookie options"); + Require(sceHttpDeleteRequest(request) == 0 && sceHttpGetCookieEnabled(request, &value) == InvalidId, "a deleted request retained its cookie option"); + Require(sceHttpDeleteConnection(connection) == 0 && sceHttpDeleteTemplate(pattern) == 0, "delete failed"); +} + void CheckNonblock(int context) { const int pattern = sceHttpCreateTemplate(context, "anyps5-test", 2, 0); Require(Get(sceHttpGetNonblock, pattern) == 0 && Get(sceHttpTryGetNonblock, pattern) == 0, "a template is nonblocking by default"); @@ -144,11 +150,14 @@ void CheckTermEndsOptions() { int value = -1; HttpEpollHandle bound = nullptr; Require(sceHttpGetAutoRedirect(pattern, &value) == BeforeInit && sceHttpSetAutoRedirect(pattern, 0) == BeforeInit, "the redirect option outlived the last sceHttpTerm"); + Require(sceHttpGetAuthEnabled(pattern, &value) == BeforeInit && sceHttpSetAuthEnabled(pattern, 0) == BeforeInit, "the authentication option outlived the last sceHttpTerm"); + Require(sceHttpGetCookieEnabled(pattern, &value) == BeforeInit && sceHttpSetCookieEnabled(pattern, 0) == BeforeInit, "the cookie option outlived the last sceHttpTerm"); Require(sceHttpGetNonblock(connection, &value) == BeforeInit && sceHttpTryGetNonblock(connection, &value) == BeforeInit, "the nonblocking getters work after the last sceHttpTerm"); Require(sceHttpSetNonblock(connection, 1) == BeforeInit && sceHttpTrySetNonblock(connection, 1) == BeforeInit, "the nonblocking setters work after the last sceHttpTerm"); Require(sceHttpGetAcceptEncodingGZIPEnabled(pattern, &value) == BeforeInit && sceHttpSetAcceptEncodingGZIPEnabled(pattern, 1) == BeforeInit, "the gzip option outlived the last sceHttpTerm"); Require(sceHttpGetEpoll(connection, &bound, nullptr) == BeforeInit && sceHttpUnsetEpoll(connection) == BeforeInit, "the epoll binding outlived the last sceHttpTerm"); Require(sceHttpCacheRedirectedConnectionEnabled(connection, 1) == BeforeInit, "sceHttpCacheRedirectedConnectionEnabled works after the last sceHttpTerm"); + Require(sceHttpSetProxy(connection, 1, 0, "proxy.example", 3128) == BeforeInit, "sceHttpSetProxy works after the last sceHttpTerm"); Require(sceHttpsEnableOptionPrivate(connection, 0x4000u) == BeforeInit && sceHttpsDisableOptionPrivate(connection, 0x4000u) == BeforeInit, "the private SSL options work after the last sceHttpTerm"); Require(sceHttpGetNonblock(connection, nullptr) == BeforeInit, "a null output is checked before initialisation"); Require(value == -1 && bound == nullptr, "a refused getter wrote its output"); @@ -161,6 +170,8 @@ void CheckUnobservedOptions(int context) { const int pattern = sceHttpCreateTemplate(context, "anyps5-test", 2, 0); const int connection = sceHttpCreateConnectionWithURL(pattern, "http://example.com/", 1); const int request = sceHttpCreateRequestWithURL(connection, 0, "http://example.com/a", 0); + Require(sceHttpAddRequestHeader(request, "X-Test", "value", 0) == 0 && sceHttpRemoveRequestHeader(request, "X-Test") == 0, "offline request header calls failed"); + Require(sceHttpRemoveRequestHeader(UnknownId, nullptr) == 0, "offline header removal started validating arguments"); Require(sceHttpCacheRedirectedConnectionEnabled(pattern, 0) == 0 && sceHttpCacheRedirectedConnectionEnabled(request, 1) == 0, "sceHttpCacheRedirectedConnectionEnabled refused a live object"); Require(sceHttpCacheRedirectedConnectionEnabled(UnknownId, 1) == InvalidId, "sceHttpCacheRedirectedConnectionEnabled took an unknown id"); for (const auto option : {sceHttpsEnableOptionPrivate, sceHttpsDisableOptionPrivate}) { @@ -170,6 +181,12 @@ void CheckUnobservedOptions(int context) { Require(option(UnknownId, 0x0200u) == InvalidValue, "the SSL flags are not checked before the id"); Require(option(UnknownId, 0x01u) == InvalidId, "a private SSL option took an unknown id"); } + for (const int id : {pattern, connection, request}) { + Require(sceHttpSetProxy(id, 1, 0, "proxy.example", 3128) == 0, "sceHttpSetProxy refused a live object"); + } + Require(sceHttpSetProxy(connection, 0, 0, nullptr, 0) == InvalidValue, "sceHttpSetProxy took a null host"); + Require(sceHttpSetProxy(UnknownId, 0, 0, nullptr, 0) == InvalidValue, "the proxy host is not checked before the id"); + Require(sceHttpSetProxy(UnknownId, 1, 1, "proxy.example", 8080) == InvalidId, "sceHttpSetProxy took an unknown id"); Require(sceHttpAbortRequestForce(request) == 0, "sceHttpAbortRequestForce failed on a live request"); Require(sceHttpDeleteRequest(request) == 0 && sceHttpDeleteConnection(connection) == 0 && sceHttpDeleteTemplate(pattern) == 0, "delete failed"); } @@ -180,6 +197,8 @@ int main() { const int context = sceHttpInit_nid_postfix(1, 1, 0x10000); Require(context > 0, "sceHttpInit failed"); CheckAutoRedirect(context); + CheckAuthEnabled(context); + CheckCookieEnabled(context); CheckNonblock(context); CheckAcceptEncodingGzip(context); CheckEpoll(context); diff --git a/core/libs/tests/GuestHttpResponseHeader.cpp b/core/libs/tests/GuestHttpResponseHeader.cpp index 76bbc60b5d..46244f8153 100644 --- a/core/libs/tests/GuestHttpResponseHeader.cpp +++ b/core/libs/tests/GuestHttpResponseHeader.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceHttp/include/Http.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -13,8 +14,6 @@ #include #endif -extern "C" int APS5_VABI sceHttpParseResponseHeader(const char*, std::size_t, const char*, const char**, std::size_t*); - static void Require(bool value) { if (!value) { std::fputs("HTTP response header check failed\n", stderr); diff --git a/core/libs/tests/GuestImeDialog.cpp b/core/libs/tests/GuestImeDialog.cpp index 574398c192..cd5fce7fcb 100644 --- a/core/libs/tests/GuestImeDialog.cpp +++ b/core/libs/tests/GuestImeDialog.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceImeDialog/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceIme/include/ImeInput.hpp" @@ -7,15 +8,6 @@ #include #include -extern "C" { -int APS5_VABI sceImeDialogInit(const Param*, const ExtendedParam*); -int APS5_VABI sceImeDialogGetStatus(void); -int APS5_VABI sceImeDialogGetResult(Result*); -int APS5_VABI sceImeDialogAbort(void); -int APS5_VABI sceImeDialogTerm(void); -int APS5_VABI sceImeDialogForceClose(void); -} - namespace { constexpr int StatusNone = 0; @@ -25,6 +17,7 @@ constexpr std::uint32_t EndStatusOk = 0; constexpr std::uint32_t EndStatusUserCanceled = 1; constexpr std::uint32_t EndStatusAborted = 2; constexpr int ErrorBusy = static_cast(0x80bc0001u); +constexpr int ErrorSuspending = static_cast(0x80bc0009u); constexpr int ErrorNotOpened = static_cast(0x80bc0002u); constexpr int ErrorNotInUse = static_cast(0x80bc0107u); constexpr int ErrorInvalidMaxTextLength = static_cast(0x80bc0016u); @@ -177,6 +170,13 @@ int main() { Open(param); Close(); + Require(sceImeDialogGetPanelPositionAndForm(nullptr) == ErrorNotOpened); + std::uint32_t panelWidth = 0; + std::uint32_t panelHeight = 0; + Require(sceImeDialogGetPanelSize(nullptr, &panelWidth, &panelHeight) == 0 && panelWidth == 1920 && panelHeight == 1080); + panelWidth = 0; + panelHeight = 0; + Require(sceImeDialogGetPanelSizeExtended(nullptr, nullptr, &panelWidth, &panelHeight) == 0 && panelWidth == 1920 && panelHeight == 1080); Require(sceImeDialogInit(nullptr, nullptr) == ErrorInvalidAddress); const Param empty = DialogParam(buffer, 0); Require(sceImeDialogInit(&empty, nullptr) == ErrorInvalidMaxTextLength); @@ -186,4 +186,21 @@ int main() { Require(sceImeDialogInit(&noBuffer, nullptr) == ErrorInvalidInputTextBuffer); Require(sceImeDialogGetStatus() == StatusNone); Require(!ImeCapturesKeyboard_nid_postfix()); + + std::uint32_t star = 0xa5a5a5a5u; + const auto starAddress = static_cast(reinterpret_cast(&star)); + Require(sceImeDialogGetCurrentStarState(starAddress) == ErrorNotInUse && star == 0xa5a5a5a5u); + Require(sceImeDialogSetPanelPosition(640, 360) == ErrorNotInUse); + Require(sceImeDialogInitInternal(nullptr, nullptr) == ErrorInvalidAddress); + Require(sceImeDialogInitInternal(&empty, nullptr) == ErrorInvalidMaxTextLength); + Require(sceImeDialogGetStatus() == StatusNone); + Require(sceImeDialogInitInternal(¶m, nullptr) == 0); + Require(sceImeDialogGetStatus() == StatusRunning && ImeCapturesKeyboard_nid_postfix()); + Require(sceImeDialogInitInternal(¶m, nullptr) == ErrorBusy); + Require(sceImeDialogGetCurrentStarState(0) == ErrorInvalidAddress); + Require(sceImeDialogGetCurrentStarState(starAddress) == 0 && star == 0); + Require(sceImeDialogSetPanelPosition(640, 360) == ErrorSuspending); + Require(sceImeDialogAbort() == 0); + Require(sceImeDialogSetPanelPosition(0, 0) == ErrorSuspending); + Close(); } diff --git a/core/libs/tests/GuestImeFilterText.cpp b/core/libs/tests/GuestImeFilterText.cpp index bac9e2daa2..7c15604512 100644 --- a/core/libs/tests/GuestImeFilterText.cpp +++ b/core/libs/tests/GuestImeFilterText.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceIme/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libSceIme/include/ImeFilterText.hpp" #include @@ -7,11 +8,6 @@ #include #include -extern "C" { -int APS5_VABI sceImeFilterText(char16_t* dst, int32_t* out_length, uint32_t capacity, - const ime_filter::FilterParam* param, const char16_t* src, uint32_t src_length); -} - namespace { constexpr int kInvalidParam = static_cast(0x80bc0030u); @@ -65,6 +61,7 @@ void CheckArguments() { Require(Run(0, 5, 16, {u'a'}).code == kInvalidParam, "type 5"); Require(Run(0, 0xff, 16, {u'a'}).code == kInvalidParam, "type 0xff"); Require(Run(0, 0x104, 16, {u'a'}).code == kInvalidParam, "type 0x104"); + Require(Run(0, 0x200, 16, {u'a'}).code == kInvalidParam, "type 0x200"); } void CheckSimpleTypes() { diff --git a/core/libs/tests/GuestImeKeyboard.cpp b/core/libs/tests/GuestImeKeyboard.cpp index 180a47106f..449089bc13 100644 --- a/core/libs/tests/GuestImeKeyboard.cpp +++ b/core/libs/tests/GuestImeKeyboard.cpp @@ -1,15 +1,9 @@ +#include "prx/libSceIme/include/Api.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI sceImeKeyboardOpen(int32_t user_id, const KeyboardParam* param); -int APS5_VABI sceImeKeyboardClose(int32_t user_id); -int APS5_VABI sceImeKeyboardGetInfo(uint32_t resource_id, KeyboardInfo* info); -int APS5_VABI sceImeKeyboardSetMode(int32_t user_id, uint32_t mode); -} - static_assert(sizeof(KeyboardInfo) == 36); constexpr int AlreadyOpened = static_cast(0x80bc0001u); diff --git a/core/libs/tests/GuestImeOpen.cpp b/core/libs/tests/GuestImeOpen.cpp index ce516d5530..04cefbca47 100644 --- a/core/libs/tests/GuestImeOpen.cpp +++ b/core/libs/tests/GuestImeOpen.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceIme/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libSceIme/include/ImeInput.hpp" #include "prx/libSceVideoOut/include/ImeTextInput.hpp" @@ -10,16 +11,6 @@ #include #include -extern "C" { -int APS5_VABI sceImeOpen_nid_postfix(const Param* param, const ExtendedParam* extended); -int APS5_VABI sceImeClose_nid_postfix(void); -int APS5_VABI sceImeUpdate(EventHandler handler); -int APS5_VABI sceImeSetText(const char16_t* text, uint32_t length); -int APS5_VABI sceImeSetCaret(const Caret* caret); -int APS5_VABI sceImeGetPanelPositionAndForm(PositionAndForm* form); -void APS5_VABI sceImeParamInit(Param* param); -} - namespace { constexpr int Busy = static_cast(0x80bc0001u); @@ -69,6 +60,10 @@ void APS5_VABI Record(void* arg, const ImeEvent* event) { if (g_closeOnPressClose && event->id == EventPressClose) g_reentrantClose = sceImeClose_nid_postfix(); } +std::int32_t APS5_VABI TextFilter(char16_t*, std::uint32_t*, const char16_t*, std::uint32_t) { + std::abort(); +} + void APS5_VABI Other(void*, const ImeEvent*) { std::abort(); } @@ -177,6 +172,7 @@ struct Buffers { Param MakeParam(Buffers& buffers, uint32_t maxLength) { Param param; sceImeParamInit(¶m); + param.user_id = 1; param.max_text_length = maxLength; param.input_text_buffer = buffers.text; diff --git a/core/libs/tests/GuestImeParameters.cpp b/core/libs/tests/GuestImeParameters.cpp index 23f590a389..82ec0959f8 100644 --- a/core/libs/tests/GuestImeParameters.cpp +++ b/core/libs/tests/GuestImeParameters.cpp @@ -1,22 +1,10 @@ +#include "prx/libSceIme/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -void APS5_VABI sceImeParamInit(Param* param); -int APS5_VABI sceImeGetPanelSize(const Param* param, uint32_t* width, uint32_t* height); -int APS5_VABI sceImeClose_nid_postfix(void); -int APS5_VABI sceImeGetPanelPositionAndForm(PositionAndForm* form); -int APS5_VABI sceImeSetCaret(const Caret* caret); -int APS5_VABI sceImeSetText(const char16_t* text, uint32_t length); -int APS5_VABI sceImeSetTextGeometry(TextAreaMode mode, const TextGeometry* geometry); -int APS5_VABI sceImeKeyboardOpen(int32_t user_id, const KeyboardParam* param); -int APS5_VABI sceImeKeyboardClose(int32_t user_id); -int APS5_VABI sceImeKeyboardGetResourceId(int32_t user_id, KeyboardResourceIdArray* resource_ids); -} - static_assert(sizeof(Param) == 96); static_assert(offsetof(Param, option) == 32); static_assert(offsetof(Param, reserved) == 88); @@ -36,6 +24,7 @@ static void CheckInitialization() { std::memset(&guarded, 0xa5, sizeof(guarded)); sceImeParamInit(nullptr); sceImeParamInit(&guarded.param); + Require(guarded.param.user_id == -1, "initial user must be invalid"); const auto* bytes = reinterpret_cast(&guarded.param); for (size_t i = sizeof(guarded.param.user_id); i < sizeof(Param); ++i) { @@ -47,6 +36,7 @@ static void CheckInitialization() { guarded.param.option = 0x4000; guarded.param.max_text_length = 32; sceImeParamInit(&guarded.param); + Require(guarded.param.user_id == -1 && guarded.param.type == 0 && guarded.param.option == 0 && guarded.param.max_text_length == 0, "reinitialization must reset previous values"); diff --git a/core/libs/tests/GuestIndexedDraw.cpp b/core/libs/tests/GuestIndexedDraw.cpp index 3c6ed23b0b..14333e04f6 100644 --- a/core/libs/tests/GuestIndexedDraw.cpp +++ b/core/libs/tests/GuestIndexedDraw.cpp @@ -1,15 +1,11 @@ +#include "prx/libSceAgc/DcbDraw/include/IndexBuffer.hpp" +#include "prx/libSceAgc/DcbDraw/include/DrawIndexed.hpp" #include "SceTypes.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include #include #include -extern "C" { -std::uint32_t* APS5_VABI sceAgcDcbSetIndexCount(CommandBuffer*, std::uint32_t); -std::uint32_t APS5_VABI sceAgcDcbSetIndexCountGetSize(); -std::uint32_t* APS5_VABI sceAgcDcbDrawIndex(CommandBuffer*, std::uint32_t, const volatile void*, std::uint64_t); -std::uint32_t APS5_VABI sceAgcDcbDrawIndexGetSize(); -} static void Require(bool condition) { if (!condition) std::abort(); } template static void Reject(TAction action) { bool rejected = false; diff --git a/core/libs/tests/GuestInetAddress.cpp b/core/libs/tests/GuestInetAddress.cpp index 80447cad5d..444bfb5b21 100644 --- a/core/libs/tests/GuestInetAddress.cpp +++ b/core/libs/tests/GuestInetAddress.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,13 +7,6 @@ #include #include -extern "C" { -int APS5_VABI inet_aton_nid_postfix(const char* text, std::uint32_t* address); -std::uint32_t APS5_VABI inet_addr_nid_postfix(const char* text); -char* APS5_VABI inet_ntoa_nid_postfix(std::uint32_t address); -char* APS5_VABI inet_ntoa_r_nid_postfix(std::uint32_t address, char* buffer, std::uint32_t size); -} - namespace { void Require(bool value, const char* what) { @@ -64,5 +59,14 @@ int main() { Require(inet_ntoa_r_nid_postfix(Network(1, 2, 3, 4), buffer, sizeof(buffer)) == buffer && std::string(buffer) == "1.2.3.4", "inet_ntoa_r"); char tiny[4] = {'k', 'e', 'e', 'p'}; Require(inet_ntoa_r_nid_postfix(Network(1, 2, 3, 4), tiny, sizeof(tiny)) == tiny, "inet_ntoa_r small buffer returns it"); + + char exact[14]; + std::memset(exact, 'z', sizeof(exact)); + Require(__inet_ntoa_r_nid_postfix(Network(192, 168, 0, 255), exact, sizeof(exact)) == exact && std::string(exact) == "192.168.0.255", "__inet_ntoa_r"); + char oneShort[13]; + std::memset(oneShort, 'z', sizeof(oneShort)); + *__error_nid_postfix() = 0; + Require(__inet_ntoa_r_nid_postfix(Network(192, 168, 0, 255), oneShort, sizeof(oneShort)) == oneShort && *__error_nid_postfix() == 28, "__inet_ntoa_r small buffer"); + Require(std::string(oneShort, sizeof(oneShort)) == std::string(sizeof(oneShort), 'z') && std::string(text) == "255.255.255.255", "__inet_ntoa_r leaves the buffers"); return 0; } diff --git a/core/libs/tests/GuestInitOnce.cpp b/core/libs/tests/GuestInitOnce.cpp new file mode 100644 index 0000000000..c7140ac745 --- /dev/null +++ b/core/libs/tests/GuestInitOnce.cpp @@ -0,0 +1,49 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +namespace { + +constexpr unsigned long InitFailed = 4; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "init once: %s\n", message); + std::abort(); +} + +int Begin(void** once) { + int pending = -1; + Require(__std_init_once_begin_initialize_clr_nid_postfix(once, 0, &pending, nullptr) == 1, "begin returns 1"); + return pending; +} + +} + +int main() { + alignas(8) void* once = nullptr; + Require(Begin(&once) == 1, "the first caller runs the initialization"); + Require(reinterpret_cast(once) == 1, "a running initialization marks the object"); + Require(__std_init_once_complete_clr_nid_postfix(&once, InitFailed, nullptr) == 1, "complete a failed initialization"); + Require(once == nullptr, "a failed initialization resets the object"); + Require(Begin(&once) == 1, "a caller after a failure runs the initialization again"); + + std::atomic waiterDone{false}; + std::atomic waiterPending{-1}; + std::thread waiter([&] { + waiterPending = Begin(&once); + waiterDone = true; + }); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + Require(!waiterDone, "a second caller waits while the initialization runs"); + Require(__std_init_once_complete_clr_nid_postfix(&once, 0, nullptr) == 1, "complete the initialization"); + waiter.join(); + Require(waiterPending == 0, "the waiting caller sees the initialization done"); + Require(reinterpret_cast(once) == 2, "a completed initialization marks the object"); + Require(Begin(&once) == 0, "a later caller does not run the initialization"); + return 0; +} diff --git a/core/libs/tests/GuestIntegerBuiltins.cpp b/core/libs/tests/GuestIntegerBuiltins.cpp index 64f5cbb0e1..e0b4eaac19 100644 --- a/core/libs/tests/GuestIntegerBuiltins.cpp +++ b/core/libs/tests/GuestIntegerBuiltins.cpp @@ -1,73 +1,9 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -using Int128 = __int128; -using Uint128 = unsigned __int128; - -extern "C" { -std::int64_t APS5_VABI __ashldi3_nid_postfix(std::int64_t, int); -std::int64_t APS5_VABI __ashrdi3_nid_postfix(std::int64_t, int); -std::int64_t APS5_VABI __lshrdi3_nid_postfix(std::int64_t, int); -Int128 APS5_VABI __ashlti3_nid_postfix(Int128, int); -Int128 APS5_VABI __ashrti3_nid_postfix(Int128, int); -Int128 APS5_VABI __lshrti3_nid_postfix(Int128, int); -int APS5_VABI __clzsi2_nid_postfix(std::uint32_t); -int APS5_VABI __clzdi2_nid_postfix(std::uint64_t); -int APS5_VABI __clzti2_nid_postfix(Uint128); -int APS5_VABI __ctzsi2_nid_postfix(std::uint32_t); -int APS5_VABI __ctzdi2_nid_postfix(std::uint64_t); -int APS5_VABI __ctzti2_nid_postfix(Uint128); -int APS5_VABI __ffsdi2_nid_postfix(std::uint64_t); -int APS5_VABI __ffsti2_nid_postfix(Uint128); -int APS5_VABI __paritysi2_nid_postfix(std::uint32_t); -int APS5_VABI __paritydi2_nid_postfix(std::uint64_t); -int APS5_VABI __parityti2_nid_postfix(Uint128); -int APS5_VABI __popcountsi2_nid_postfix(std::uint32_t); -int APS5_VABI __popcountdi2_nid_postfix(std::uint64_t); -int APS5_VABI __popcountti2_nid_postfix(Uint128); -int APS5_VABI __cmpdi2_nid_postfix(std::int64_t, std::int64_t); -int APS5_VABI __cmpti2_nid_postfix(Int128, Int128); -int APS5_VABI __ucmpdi2_nid_postfix(std::uint64_t, std::uint64_t); -int APS5_VABI __ucmpti2_nid_postfix(Uint128, Uint128); -std::int64_t APS5_VABI __negdi2_nid_postfix(std::int64_t); -Int128 APS5_VABI __negti2_nid_postfix(Int128); -std::int64_t APS5_VABI __muldi3_nid_postfix(std::int64_t, std::int64_t); -Int128 APS5_VABI __multi3_nid_postfix(Int128, Int128); -std::int32_t APS5_VABI __divsi3_nid_postfix(std::int32_t, std::int32_t); -std::int64_t APS5_VABI __divdi3_nid_postfix(std::int64_t, std::int64_t); -std::int32_t APS5_VABI __modsi3_nid_postfix(std::int32_t, std::int32_t); -std::int64_t APS5_VABI __moddi3_nid_postfix(std::int64_t, std::int64_t); -std::uint32_t APS5_VABI __udivsi3_nid_postfix(std::uint32_t, std::uint32_t); -std::uint64_t APS5_VABI __udivdi3_nid_postfix(std::uint64_t, std::uint64_t); -std::uint32_t APS5_VABI __umodsi3_nid_postfix(std::uint32_t, std::uint32_t); -std::uint64_t APS5_VABI __umoddi3_nid_postfix(std::uint64_t, std::uint64_t); -std::int32_t APS5_VABI __divmodsi4_nid_postfix(std::int32_t, std::int32_t, std::int32_t*); -std::int64_t APS5_VABI __divmoddi4_nid_postfix(std::int64_t, std::int64_t, std::int64_t*); -std::uint32_t APS5_VABI __udivmodsi4_nid_postfix(std::uint32_t, std::uint32_t, std::uint32_t*); -std::uint64_t APS5_VABI __udivmoddi4_nid_postfix(std::uint64_t, std::uint64_t, std::uint64_t*); -Uint128 APS5_VABI __udivmodti4_nid_postfix(Uint128, Uint128, Uint128*); -std::int32_t APS5_VABI __mulosi4_nid_postfix(std::int32_t, std::int32_t, int*); -std::int64_t APS5_VABI __mulodi4_nid_postfix(std::int64_t, std::int64_t, int*); -Int128 APS5_VABI __muloti4_nid_postfix(Int128, Int128, int*); -std::int32_t APS5_VABI __mulvsi3_nid_postfix(std::int32_t, std::int32_t); -std::int64_t APS5_VABI __mulvdi3_nid_postfix(std::int64_t, std::int64_t); -Int128 APS5_VABI __mulvti3_nid_postfix(Int128, Int128); -std::int32_t APS5_VABI __addvsi3_nid_postfix(std::int32_t, std::int32_t); -std::int64_t APS5_VABI __addvdi3_nid_postfix(std::int64_t, std::int64_t); -Int128 APS5_VABI __addvti3_nid_postfix(Int128, Int128); -std::int32_t APS5_VABI __subvsi3_nid_postfix(std::int32_t, std::int32_t); -std::int64_t APS5_VABI __subvdi3_nid_postfix(std::int64_t, std::int64_t); -Int128 APS5_VABI __subvti3_nid_postfix(Int128, Int128); -std::int32_t APS5_VABI __negvsi2_nid_postfix(std::int32_t); -std::int64_t APS5_VABI __negvdi2_nid_postfix(std::int64_t); -Int128 APS5_VABI __negvti2_nid_postfix(Int128); -std::int32_t APS5_VABI __absvsi2_nid_postfix(std::int32_t); -std::int64_t APS5_VABI __absvdi2_nid_postfix(std::int64_t); -Int128 APS5_VABI __absvti2_nid_postfix(Int128); -} - static void Require(bool value) { if (!value) std::abort(); } template diff --git a/core/libs/tests/GuestInterfaces.cpp b/core/libs/tests/GuestInterfaces.cpp index 5b69f12016..6306850521 100644 --- a/core/libs/tests/GuestInterfaces.cpp +++ b/core/libs/tests/GuestInterfaces.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -7,9 +8,11 @@ #include #include #include -#include #include +#ifdef __linux__ +#include #include +#endif #include #include #include @@ -25,15 +28,6 @@ #include #include -struct GuestInterfaceAddress { - GuestInterfaceAddress* next; - char* name; - std::uint32_t flags; - void* address; - void* netmask; - void* destination; - void* data; -}; static_assert(sizeof(GuestInterfaceAddress) == 56); static_assert(offsetof(GuestInterfaceAddress, flags) == 16); static_assert(offsetof(GuestInterfaceAddress, address) == 24); @@ -41,13 +35,6 @@ static_assert(offsetof(GuestInterfaceAddress, netmask) == 32); static_assert(offsetof(GuestInterfaceAddress, destination) == 40); static_assert(offsetof(GuestInterfaceAddress, data) == 48); -extern "C" { -int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list); -void APS5_VABI freeifaddrs_nid_postfix(GuestInterfaceAddress* list); -void APS5_VABI free_nid_postfix(void* pointer); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } struct HostInterfaceAddress { @@ -76,7 +63,7 @@ static HostInterfaceAddress HostAddress(const sockaddr* address) { return entry; } -#ifndef _WIN32 +#ifdef __linux__ static std::uint64_t SysfsCounter(const char* name, const char* file) { std::ifstream input(std::string("/sys/class/net/") + name + "/statistics/" + file); unsigned long long value = 0; @@ -151,6 +138,7 @@ static std::vector HostSnapshot() { try { for (auto* item = host; item; item = item->ifa_next) { if (!item->ifa_addr) continue; +#ifdef __linux__ if (item->ifa_addr->sa_family == AF_PACKET) { const auto& link = *reinterpret_cast(item->ifa_addr); HostInterfaceAddress entry{}; @@ -165,6 +153,7 @@ static std::vector HostSnapshot() { members.insert(members.begin(), entry); continue; } +#endif if (item->ifa_addr->sa_family != AF_INET && item->ifa_addr->sa_family != AF_INET6) continue; Require(item->ifa_netmask != nullptr); HostInterfaceAddress entry = HostAddress(item->ifa_addr); @@ -192,7 +181,7 @@ int main() { const std::uint8_t loopbackAddress[] = {127, 0, 0, 1}; const std::uint8_t loopbackMask[] = {255, 0, 0, 0}; bool matched = false; -#ifndef _WIN32 +#ifdef __linux__ bool linkChecked = false; SendLoopbackDatagram(); #endif @@ -211,7 +200,7 @@ int main() { Require((item->flags & ~knownFlags) == 0); const auto* address = static_cast(item->address); Require(address != nullptr && reinterpret_cast(address) % 8 == 0); -#ifndef _WIN32 +#ifdef __linux__ if (address[1] == 18) { Require(item->netmask == nullptr && item->destination == nullptr); const auto* data = static_cast(item->data); @@ -293,15 +282,17 @@ int main() { } matched = equal && entries == host.size() && loopback; freeifaddrs_nid_postfix(list); + } Require(matched); -#ifndef _WIN32 +#ifdef __linux__ Require(linkChecked); #endif GuestInterfaceAddress* released = nullptr; Require(getifaddrs_nid_postfix(&released) == 0 && released != nullptr); free_nid_postfix(released); freeifaddrs_nid_postfix(nullptr); + *__error_nid_postfix() = 0; Require(getifaddrs_nid_postfix(nullptr) == -1); Require(*__error_nid_postfix() == 14); diff --git a/core/libs/tests/GuestItimer.cpp b/core/libs/tests/GuestItimer.cpp index 4e1e9d5845..7a688d67d0 100644 --- a/core/libs/tests/GuestItimer.cpp +++ b/core/libs/tests/GuestItimer.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -12,33 +14,14 @@ #include #include -struct GuestItimerval { - KernelTimeval interval; - KernelTimeval value; -}; static_assert(sizeof(GuestItimerval) == 32 && offsetof(GuestItimerval, value) == 16); -struct GuestSignalSet { - std::uint32_t bits[4]; -}; -struct GuestTimespec { - std::int64_t seconds; - std::int64_t nanoseconds; -}; using Handler = void (APS5_VABI *)(int); using TerminateHandler = void (*)(); extern "C" { -int APS5_VABI setitimer_nid_postfix(int, const GuestItimerval*, GuestItimerval*); -int APS5_VABI getitimer_nid_postfix(int, GuestItimerval*); + unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int); -Handler APS5_VABI signal_nid_postfix(int, Handler); -int APS5_VABI _sigprocmask_nid_postfix(int, const GuestSignalSet*, GuestSignalSet*); -int APS5_VABI sigtimedwait_nid_postfix(const GuestSignalSet*, void*, const GuestTimespec*); -int APS5_VABI sigpending_nid_postfix(GuestSignalSet*); -int* APS5_VABI __error_nid_postfix(); -void* APS5_VABI __cxa_current_primary_exception_nid_postfix(); -std::type_info* APS5_VABI __cxa_current_exception_type_nid_postfix(); -TerminateHandler APS5_VABI _ZSt13set_terminatePFvvE_nid_postfix(TerminateHandler); + } static void Require(bool value, const char* message) { @@ -140,6 +123,7 @@ int main(int argc, char** argv) { bool threw = false; try { setitimer_nid_postfix(cpu, &valid, &previous); + } catch (const std::runtime_error&) { threw = true; } @@ -150,6 +134,7 @@ int main(int argc, char** argv) { const GuestItimerval tiny{{0, 999}, {1, 0}}; try { setitimer_nid_postfix(0, &tiny, nullptr); + } catch (const std::runtime_error&) { threw = true; } @@ -189,8 +174,8 @@ int main(int argc, char** argv) { Require(GuestAlarm_nid_no_patch(0) == 0, "setitimer did not cancel the alarm"); const GuestSignalSet alarmSet{{1u << 13, 0, 0, 0}}; - const GuestTimespec poll{0, 0}; - const GuestTimespec patience{2, 0}; + const KernelTimespec poll{0, 0}; + const KernelTimespec patience{2, 0}; GuestSignalSet unblocked{}; Require(_sigprocmask_nid_postfix(1, &alarmSet, &unblocked) == 0, "blocking SIGALRM failed"); ExpireOnce(); @@ -220,6 +205,7 @@ int main(int argc, char** argv) { Require(signal_nid_postfix(14, OnAlarm) == ignore, "SIGALRM handler was not installed"); _ZSt13set_terminatePFvvE_nid_postfix(OnTerminate); + const GuestItimerval handled{{0, 250000}, {0, 50000}}; Require(setitimer_nid_postfix(0, &handled, nullptr) == 0, "arming the handled timer failed"); std::this_thread::sleep_for(std::chrono::seconds(2)); diff --git a/core/libs/tests/GuestJitSharedMemory.cpp b/core/libs/tests/GuestJitSharedMemory.cpp index 66ed8a4838..02a9cf253f 100644 --- a/core/libs/tests/GuestJitSharedMemory.cpp +++ b/core/libs/tests/GuestJitSharedMemory.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include @@ -7,19 +8,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelJitCreateSharedMemory(const char*, std::size_t, int, int*); -int APS5_VABI sceKernelJitCreateAliasOfSharedMemory(int, int, int*); -int APS5_VABI sceKernelJitMapSharedMemory(int, int, void**); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI dup_nid_postfix(int); -int APS5_VABI dup2_nid_postfix(int, int); -int APS5_VABI shm_open_nid_postfix(const char*, int, int); -void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; -int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; -} - namespace { constexpr std::size_t Size = 0x10000; @@ -79,10 +67,10 @@ void WriteThroughAliasRunsThroughExecutableView() { Require(close_nid_postfix(fd) == 0); auto* again = static_cast(Map(alias, Read)); Require(again[Size - 1] == 0x5a); - Require(sceKernelMunmap(again, Size) == 0); - Require(sceKernelMunmap(writable, Size) == 0); + Require(sceKernelMunmap(reinterpret_cast(again), Size) == 0); + Require(sceKernelMunmap(reinterpret_cast(writable), Size) == 0); Require(Call(executable) == 0x12345678); - Require(sceKernelMunmap(executable, Size) == 0); + Require(sceKernelMunmap(reinterpret_cast(executable), Size) == 0); Require(close_nid_postfix(alias) == 0); } @@ -98,8 +86,8 @@ void OneViewWritesAndRuns() { Require(close_nid_postfix(fd) == 0); auto* view = static_cast(Map(duplicate, Read | Execute)); Require(Call(view) == 42); - Require(sceKernelMunmap(view, Size) == 0); - Require(sceKernelMunmap(code, Size) == 0); + Require(sceKernelMunmap(reinterpret_cast(view), Size) == 0); + Require(sceKernelMunmap(reinterpret_cast(code), Size) == 0); Require(close_nid_postfix(duplicate) == 0); } @@ -114,7 +102,7 @@ void MmapOfTheDescriptorSharesTheObject() { view[0x300] = 0x6b; Require(jit[0x300] == 0x6b); Require(munmap_nid_postfix(mapped, Size) == 0); - Require(sceKernelMunmap(const_cast(jit), Size) == 0); + Require(sceKernelMunmap(reinterpret_cast(const_cast(jit)), Size) == 0); Require(close_nid_postfix(fd) == 0); } @@ -138,9 +126,9 @@ void Dup2KeepsTheDescriptorTableInStep() { address = nullptr; Require(sceKernelJitMapSharedMemory(target, Read, &address) == SCE_KERNEL_ERROR_EBADF); Require(address == nullptr); - Require(sceKernelMunmap(same, Size) == 0); - Require(sceKernelMunmap(readable, Size) == 0); - Require(sceKernelMunmap(writable, Size) == 0); + Require(sceKernelMunmap(reinterpret_cast(same), Size) == 0); + Require(sceKernelMunmap(reinterpret_cast(readable), Size) == 0); + Require(sceKernelMunmap(reinterpret_cast(writable), Size) == 0); Require(close_nid_postfix(plain) == 0); Require(close_nid_postfix(target) == 0); Require(close_nid_postfix(fd) == 0); @@ -187,6 +175,8 @@ int main() { WriteThroughAliasRunsThroughExecutableView(); OneViewWritesAndRuns(); Dup2KeepsTheDescriptorTableInStep(); +#ifndef __APPLE__ MmapOfTheDescriptorSharesTheObject(); +#endif InvalidArgumentsAreRejected(); } diff --git a/core/libs/tests/GuestJobManager.cpp b/core/libs/tests/GuestJobManager.cpp index 7258ae6694..af4995720a 100644 --- a/core/libs/tests/GuestJobManager.cpp +++ b/core/libs/tests/GuestJobManager.cpp @@ -1,4 +1,8 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libSceJobManager/libSceJobManager.hpp" #include "SceTypes.hpp" +#include #include #include #include @@ -9,61 +13,9 @@ #include #include #include - -struct MemorySizeQueryParams { - std::uint32_t version; - std::uint32_t workers; - std::uint32_t sequences; - std::uint32_t invocations; - std::uint32_t dispatches; - std::uint32_t syncPoints; - std::uint32_t waits; - std::uint32_t computeSequences; - std::uint64_t computeQueueMask; -}; - -struct ConfigParams { - std::uint32_t version; - std::uint32_t threadFlags; - std::uint32_t stackSize; - std::uint32_t priority; - const char* threadName; - std::uint32_t spinMicroseconds; - std::uint32_t computeAffinity; - std::uint32_t computePriority; - std::uint32_t reserved; -}; - -using JobEntry = void (APS5_VABI*)(void*, std::uint32_t); - -struct Sequence { - APS5_VABI virtual int Dispatch(JobEntry, void*, std::uint32_t, const char*) = 0; - APS5_VABI virtual int SyncPoint() = 0; - APS5_VABI virtual int Wait() = 0; - APS5_VABI virtual void CompleteDestructor() = 0; - APS5_VABI virtual void DeletingDestructor() = 0; -}; - -struct SequenceFactory { - APS5_VABI virtual int Create(Sequence**, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, const char*) = 0; - APS5_VABI virtual int Destroy(Sequence*) = 0; - APS5_VABI virtual void CompleteDestructor() = 0; - APS5_VABI virtual void DeletingDestructor() = 0; - APS5_VABI virtual int CreateCompute(Sequence**, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, const char*) = 0; - APS5_VABI virtual int DestroyCompute(Sequence*) = 0; -}; - -extern "C" { -void APS5_VABI _ZN3sce3Job10JobManagerC1Ev(void*); -void APS5_VABI _ZN3sce3Job10JobManagerD1Ev(void*); -std::uint64_t APS5_VABI _ZN3sce3Job10JobManager27calculateRequiredMemorySizeEPKNS1_21MemorySizeQueryParamsE(const MemorySizeQueryParams*); -int APS5_VABI _ZN3sce3Job10JobManager10initializeEPvmjjjmPKNS1_12ConfigParamsE(void*, void*, std::uint64_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint64_t, const ConfigParams*); -int APS5_VABI _ZN3sce3Job10JobManager10initializeEPvmjjPKNS1_12ConfigParamsE(void*, void*, std::uint64_t, std::uint32_t, std::uint32_t, const ConfigParams*); -int APS5_VABI _ZN3sce3Job10JobManager8shutdownEv(void*); -SequenceFactory* APS5_VABI _ZN3sce3Job10JobManager27getSequenceFactoryInterfaceENS1_8PriorityE(void*, std::uint32_t); -SequenceFactory* APS5_VABI _ZN3sce3Job10JobManager34getUnifiedSequenceFactoryInterfaceENS1_8PriorityE(void*, std::uint32_t); -int APS5_VABI _ZN3sce3Job10JobManager24setPersistentThreadCountEj(void*, std::uint32_t); -} +#ifdef _WIN32 +#include +#endif #define Construct _ZN3sce3Job10JobManagerC1Ev #define Destruct _ZN3sce3Job10JobManagerD1Ev @@ -137,6 +89,46 @@ static void MemoryCase() { static void APS5_VABI Nothing(void*, std::uint32_t) { } +static void OwnedStateCase() { + for (const auto [workers, count] : {std::pair{1u, 1u}, std::pair{3u, 32u}, std::pair{13u, 2u}}) { + const auto size = SizeFor(workers, count, count * 4, count * 2, count * 2); + Block block(size + 256); + std::memset(block.memory, 0xa5, block.bytes); + ManagerObject manager; + const auto begin = reinterpret_cast(block.memory); + const auto inside = [begin, size](const void* pointer) { + const auto address = reinterpret_cast(pointer); + return address >= begin && address < begin + size; + }; + for (int round = 0; round < 3; ++round) { + Require(Initialize(manager.storage, block.memory, size, workers, count, nullptr) == 0); + void* implementation = nullptr; + std::memcpy(&implementation, manager.storage, sizeof(implementation)); + Require(inside(implementation)); + for (std::uint32_t priority = 0; priority < 2; ++priority) { + Require(inside(Factories(manager.storage, priority))); + Require(inside(UnifiedFactories(manager.storage, priority))); + } + auto* factory = Factories(manager.storage, round % 2); + std::array sequences{}; + for (std::uint32_t index = 0; index < count; ++index) { + auto*& sequence = sequences[index]; + Require(factory->Create(&sequence, 4, 4, 2, 2, nullptr) == 0 && inside(sequence)); + Require(sequence->Dispatch(Nothing, nullptr, 1, nullptr) == 0); + Require(sequence->SyncPoint() == 0); + Require(sequence->Dispatch(Nothing, nullptr, 1, nullptr) == 0); + } + for (std::uint32_t index = count; index != 0; --index) { + Require(sequences[index - 1]->Wait() == 0); + Require(factory->Destroy(sequences[index - 1]) == 0); + } + Require(Shutdown(manager.storage) == 0); + const auto* bytes = static_cast(block.memory); + for (auto offset = size; offset < block.bytes; ++offset) Require(bytes[offset] == 0xa5); + } + } +} + static void ValidationCase() { Block block(SizeFor(2, 2, 16, 4, 4)); ManagerObject manager; @@ -176,10 +168,10 @@ static void ValidationCase() { Require(Initialize(manager.storage, block.memory, block.bytes, 2, 2, nullptr) == AlreadyInitialized); Require(SetPersistentThreadCount(manager.storage, 3) == InvalidArgument); Require(SetPersistentThreadCount(manager.storage, 2) == 0); - SequenceFactory* factory = Factories(manager.storage, 0); + ISequenceFactory* factory = Factories(manager.storage, 0); Require(factory != nullptr && Factories(manager.storage, 1) != nullptr && factory != Factories(manager.storage, 1)); Require(UnifiedFactories(manager.storage, 0) != nullptr); - Sequence* sequence = nullptr; + ISequence* sequence = nullptr; Require(factory->Create(nullptr, 1, 1, 1, 1, nullptr) == NullArgument); Require(factory->Create(&sequence, 0, 0, 0, 0, nullptr) == OutOfRange); Require(factory->Create(&sequence, 0x8000, 1, 1, 1, nullptr) == OutOfRange); @@ -187,13 +179,13 @@ static void ValidationCase() { Require(factory->Create(&sequence, 1, 1, 0x100, 1, nullptr) == OutOfRange); Require(factory->Create(&sequence, 1, 1, 1, 0x40, nullptr) == OutOfRange); Require(factory->CreateCompute(&sequence, 1, 1, 1, 1, nullptr) == NoneFree && sequence == nullptr); - Sequence* first = nullptr; - Sequence* second = nullptr; + ISequence* first = nullptr; + ISequence* second = nullptr; Require(factory->Create(&first, 8, 8, 2, 2, "first") == 0 && first != nullptr); Require(factory->Create(&second, 8, 8, 2, 2, "second") == 0 && second != nullptr && second != first); Require(factory->Create(&sequence, 8, 8, 2, 2, "third") == NoneFree); Require(factory->DestroyCompute(first) == Invalid); - Require(factory->Destroy(reinterpret_cast(block.memory)) == Invalid); + Require(factory->Destroy(reinterpret_cast(block.memory)) == Invalid); Require(factory->Destroy(first) == 0 && factory->Destroy(first) == Invalid); Require(factory->Create(&sequence, 8, 8, 2, 2, "again") == 0 && sequence == first); Require(sequence->Dispatch(nullptr, nullptr, 1, nullptr) == NullArgument); @@ -219,8 +211,8 @@ static void DispatchCase() { Block block(SizeFor(4, 1, 8, 2, 2)); ManagerObject manager; Require(Initialize(manager.storage, block.memory, block.bytes, 4, 1, nullptr) == 0); - SequenceFactory* factory = Factories(manager.storage, 0); - Sequence* sequence = nullptr; + ISequenceFactory* factory = Factories(manager.storage, 0); + ISequence* sequence = nullptr; Require(factory->Create(&sequence, 0x7fff, 8, 2, 2, "dispatch") == 0); Tally tally(1000); Tally single(1); @@ -251,8 +243,8 @@ static void OrderingCase() { Block block(SizeFor(4, 1, 8, 4, 2)); ManagerObject manager; Require(Initialize(manager.storage, block.memory, block.bytes, 4, 1, nullptr) == 0); - SequenceFactory* factory = Factories(manager.storage, 1); - Sequence* sequence = nullptr; + ISequenceFactory* factory = Factories(manager.storage, 1); + ISequence* sequence = nullptr; Require(factory->Create(&sequence, 256, 8, 4, 2, "ordering") == 0); std::atomic finished{0}; std::atomic early{0}; @@ -273,7 +265,7 @@ static void OrderingCase() { } struct Nested { - Sequence* sequence; + ISequence* sequence; std::atomic barrierAppended{false}; std::atomic followerAppended{false}; std::atomic leaderFinished{false}; @@ -295,11 +287,66 @@ static void APS5_VABI LeaderJob(void* argument, std::uint32_t) { nested.leaderFinished = true; } +static void RequireParked(int clock); + +struct WorkerWait { + int spareClock = 0; + ISequence* child; + std::atomic publisherEntered{false}; + std::atomic spareEntered{false}; + std::atomic publish{false}; + std::atomic finished{false}; + std::atomic childCalls{0}; +}; + +static void APS5_VABI WorkerWaitChild(void* argument, std::uint32_t) { + ++static_cast(argument)->childCalls; +} + +static void APS5_VABI WorkerWaitPublisher(void* argument, std::uint32_t index) { + auto& state = *static_cast(argument); + if (index != 0) { + Require(pthread_getcpuclockid_nid_postfix(scePthreadSelf(), &state.spareClock) == 0); + state.spareEntered = true; + return; + } + state.publisherEntered = true; + while (!state.publish) std::this_thread::yield(); + Require(state.child->Dispatch(WorkerWaitChild, &state, 1, "child") == 0); + Require(state.child->Wait() == 0); + state.finished = true; +} + +static void WorkerWaitCase() { + Block block(SizeFor(2, 2, 4, 0, 4)); + ManagerObject manager; + const ConfigParams config{0, 0x21fff, 0x10000, 0x2bc, "WorkerWait", 0, 0, 0, 0}; + Require(Initialize(manager.storage, block.memory, block.bytes, 2, 2, &config) == 0); + Require(SetPersistentThreadCount(manager.storage, 0) == 0); + ISequenceFactory* factory = Factories(manager.storage, 0); + ISequence* publisher = nullptr; + WorkerWait state{}; + Require(factory->Create(&publisher, 2, 1, 0, 1, "publisher") == 0); + Require(factory->Create(&state.child, 1, 1, 0, 1, "child") == 0); + Require(publisher->Dispatch(WorkerWaitPublisher, &state, 2, "park spare") == 0); + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + while ((!state.publisherEntered || !state.spareEntered) && std::chrono::steady_clock::now() < deadline) std::this_thread::yield(); + Require(state.publisherEntered && state.spareEntered); + RequireParked(state.spareClock); + state.publish = true; + deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + while (!state.finished && std::chrono::steady_clock::now() < deadline) std::this_thread::yield(); + Require(state.finished && state.childCalls == 1); + Require(publisher->Wait() == 0); + Require(factory->Destroy(publisher) == 0 && factory->Destroy(state.child) == 0); + Require(Shutdown(manager.storage) == 0); +} + static void NestedCase() { Block block(SizeFor(2, 1, 4, 2, 2)); ManagerObject manager; Require(Initialize(manager.storage, block.memory, block.bytes, 2, 1, nullptr) == 0); - SequenceFactory* factory = Factories(manager.storage, 0); + ISequenceFactory* factory = Factories(manager.storage, 0); Nested nested{}; Require(factory->Create(&nested.sequence, 16, 4, 2, 2, "nested") == 0); Require(nested.sequence->Dispatch(LeaderJob, &nested, 1, "leader") == 0); @@ -323,8 +370,8 @@ static void LimitsCase() { Block block(SizeFor(2, 2, 2, 1, 1)); ManagerObject manager; Require(Initialize(manager.storage, block.memory, block.bytes, 2, 2, nullptr) == 0); - SequenceFactory* factory = Factories(manager.storage, 0); - Sequence* sequence = nullptr; + ISequenceFactory* factory = Factories(manager.storage, 0); + ISequence* sequence = nullptr; Require(factory->Create(&sequence, 4, 2, 1, 1, "limits") == 0); for (int round = 0; round < 3; ++round) { Require(sequence->Dispatch(Nothing, nullptr, 2, nullptr) == 0); @@ -348,6 +395,15 @@ static void LimitsCase() { Require(factory->Create(&sequence, 4, 2, 1, 1, "waits") == 0); Require(sequence->Wait() == 0 && sequence->Wait() == 0); Require(factory->Destroy(sequence) == 0); + Require(Shutdown(manager.storage) == 0); +} + +static void NodePoolCase() { + Block block(SizeFor(2, 2, 2, 1, 1)); + ManagerObject manager; + Require(Initialize(manager.storage, block.memory, block.bytes, 2, 2, nullptr) == 0); + ISequenceFactory* factory = Factories(manager.storage, 0); + ISequence* sequence = nullptr; Require(factory->Create(&sequence, 0x7fff, 0x3ff, 0xff, 1, "nodes") == 0); int result = 0; int accepted = 0; @@ -413,9 +469,9 @@ static void PriorityCase() { Block block(SizeFor(1, 3, 8, 2, 4)); ManagerObject manager; Require(Initialize(manager.storage, block.memory, block.bytes, 1, 3, nullptr) == 0); - Sequence* gate = nullptr; - Sequence* low = nullptr; - Sequence* high = nullptr; + ISequence* gate = nullptr; + ISequence* low = nullptr; + ISequence* high = nullptr; Require(Factories(manager.storage, 0)->Create(&gate, 4, 4, 1, 1, "gate") == 0); Require(Factories(manager.storage, 0)->Create(&low, 4, 4, 1, 1, "low") == 0); Require(Factories(manager.storage, 1)->Create(&high, 4, 4, 1, 1, "high") == 0); @@ -437,8 +493,8 @@ static void ShutdownCase() { ManagerObject manager; for (int round = 0; round < 3; ++round) { Require(Initialize(manager.storage, block.memory, block.bytes, 3, 2, nullptr) == 0); - SequenceFactory* factory = UnifiedFactories(manager.storage, 0); - Sequence* sequence = nullptr; + ISequenceFactory* factory = UnifiedFactories(manager.storage, 0); + ISequence* sequence = nullptr; Tally tally(64); Require(factory->Create(&sequence, 64, 8, 2, 2, "shutdown") == 0); Require(sequence->Dispatch(CountJob, &tally, 64, nullptr) == 0 && sequence->Wait() == 0 && tally.calls == 64); @@ -453,16 +509,416 @@ static void ShutdownCase() { } } -int main(int argc, char** argv) { - const std::string name = argc > 1 ? argv[1] : ""; +static void APS5_VABI CaptureWorkerClock(void* argument, std::uint32_t) { + Require(pthread_getcpuclockid_nid_postfix(scePthreadSelf(), static_cast(argument)) == 0); +} + +static std::int64_t CpuNanos(int clock) { + KernelTimespec time{}; + Require(sceKernelClockGettime(clock, &time) == 0); + return time.tv_sec * 1000000000LL + time.tv_nsec; +} + +static void RequireParked(int clock) { + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + const auto start = CpuNanos(clock); + std::this_thread::sleep_for(std::chrono::milliseconds(80)); + Require(CpuNanos(clock) - start < 20000000); +} + +static void WorkerPolicyCase() { + for (const std::uint32_t mode : {0x10000u, 0x20000u}) { + for (const std::uint32_t spin : {0u, 50u}) { + Block block(SizeFor(1, 1, 8, 2, 2)); + ManagerObject manager; + const ConfigParams config{0, mode | 0x1fff, 0x10000, 0x2bc, "WorkerPolicy", spin, 0, 0, 0}; + Require(Initialize(manager.storage, block.memory, block.bytes, 1, 1, &config) == 0); + auto* factory = Factories(manager.storage, 0); + ISequence* sequence = nullptr; + Require(factory->Create(&sequence, 8, 8, 2, 2, "worker policy") == 0); + int clock = 0; + Require(sequence->Dispatch(CaptureWorkerClock, &clock, 1, nullptr) == 0 && sequence->Wait() == 0); + RequireParked(clock); + for (int round = 0; round < 2; ++round) { + Require(SetPersistentThreadCount(manager.storage, 1) == 0); + RequireParked(clock); + Require(sequence->Dispatch(Nothing, nullptr, 1, nullptr) == 0 && sequence->Wait() == 0); + const auto start = CpuNanos(clock); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (CpuNanos(clock) - start < 30000000 && std::chrono::steady_clock::now() < deadline) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + Require(CpuNanos(clock) - start >= 30000000); + Require(SetPersistentThreadCount(manager.storage, 0) == 0); + RequireParked(clock); + } + Require(SetPersistentThreadCount(manager.storage, 1) == 0); + Require(sequence->Dispatch(Nothing, nullptr, 1, nullptr) == 0 && sequence->Wait() == 0); + Require(Shutdown(manager.storage) == 0); + } + } +} + +static void WorkerWakeCase() { + Block block(SizeFor(4, 1, 8, 2, 2)); + ManagerObject manager; + for (const std::uint32_t mode : {0x10000u, 0x20000u}) { + const ConfigParams config{0, mode | 0x1fff, 0x10000, 0x2bc, "WorkerWake", 50, 0, 0, 0}; + Require(Initialize(manager.storage, block.memory, block.bytes, 4, 1, &config) == 0); + ISequence* sequence = nullptr; + auto* factory = Factories(manager.storage, 0); + Require(factory->Create(&sequence, 1024, 8, 2, 2, "worker wake") == 0); + for (std::uint32_t round = 0; round < 60; ++round) { + Require(SetPersistentThreadCount(manager.storage, round % 5) == 0); + Tally tally(257); + Require(sequence->Dispatch(CountJob, &tally, 257, nullptr) == 0); + Require(sequence->SyncPoint() == 0); + Require(sequence->Dispatch(CountJob, &tally, 257, nullptr) == 0); + Require(sequence->Wait() == 0 && tally.calls == 514); + for (const auto& count : tally.seen) Require(count == 2); + } + Require(factory->Destroy(sequence) == 0); + Require(Shutdown(manager.storage) == 0); + } +} + +struct HeldWorker { + std::atomic started{false}; + std::atomic release{false}; +}; + +struct SpinClock { + int guest = 0; + std::int64_t reference = 0; +#ifdef _WIN32 + HANDLE thread = nullptr; + ~SpinClock() { if (thread != nullptr) CloseHandle(thread); } +#endif +}; + +static std::int64_t SpinCpuUnits(const SpinClock& clock); + +static void APS5_VABI CaptureSpinClock(void* argument, std::uint32_t) { + auto& clock = *static_cast(argument); + CaptureWorkerClock(&clock.guest, 0); +#ifdef _WIN32 + clock.thread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, GetCurrentThreadId()); + Require(clock.thread != nullptr); +#endif + const auto before = SpinCpuUnits(clock); + const auto until = std::chrono::steady_clock::now() + std::chrono::milliseconds(5); + while (std::chrono::steady_clock::now() < until) {} + clock.reference = SpinCpuUnits(clock) - before; + Require(clock.reference > 0); +} + +static std::int64_t SpinCpuUnits(const SpinClock& clock) { +#ifdef _WIN32 + ULONG64 cycles = 0; + Require(QueryThreadCycleTime(clock.thread, &cycles)); + return static_cast(cycles); +#else + return CpuNanos(clock.guest); +#endif +} + +static void APS5_VABI HoldWorker(void* argument, std::uint32_t) { + auto& worker = *static_cast(argument); + worker.started = true; + worker.started.notify_one(); + worker.release.wait(false); +} + +struct SpinSample { + std::int64_t consumed; + std::int64_t reference; +}; + +static SpinSample MeasureWorkerSpin(std::uint32_t spin, std::uint32_t rounds, bool pending) { + Block block(SizeFor(2, 2, 4, 2, 2)); + ManagerObject manager; + const ConfigParams config{0, 0x10003, 0x10000, 0x2bc, "WorkerSpin", spin, 0, 0, 0}; + Require(Initialize(manager.storage, block.memory, block.bytes, 2, 2, &config) == 0); + auto* factory = Factories(manager.storage, 0); + ISequence* held = nullptr; + ISequence* pulse = nullptr; + Require(factory->Create(&held, 4, 4, 2, 2, "held worker") == 0); + Require(factory->Create(&pulse, 4, 4, 2, 2, "idle worker") == 0); + HeldWorker worker; + Require(held->Dispatch(HoldWorker, &worker, 1, nullptr) == 0); + worker.started.wait(false); + if (pending) { + Require(held->SyncPoint() == 0); + Require(held->Dispatch(Nothing, nullptr, 1, nullptr) == 0); + } + SpinClock clock; + Require(pulse->Dispatch(CaptureSpinClock, &clock, 1, nullptr) == 0 && pulse->Wait() == 0); + RequireParked(clock.guest); + const auto before = SpinCpuUnits(clock); + for (std::uint32_t round = 0; round < rounds; ++round) { + Require(pulse->Dispatch(Nothing, nullptr, 1, nullptr) == 0 && pulse->Wait() == 0); + Require(sceKernelUsleep_nid_postfix(1000) == 0); + } + const auto consumed = SpinCpuUnits(clock) - before; + RequireParked(clock.guest); + Require(!worker.release); + worker.release = true; + worker.release.notify_one(); + Require(held->Wait() == 0); + Require(factory->Destroy(held) == 0 && factory->Destroy(pulse) == 0); + Require(Shutdown(manager.storage) == 0); + return {consumed, clock.reference}; +} + +static SpinSample QuietestWorkerSpin(std::uint32_t spin, std::uint32_t rounds, bool pending) { + auto best = MeasureWorkerSpin(spin, rounds, pending); + for (int attempt = 1; attempt < 3; ++attempt) { + const auto sample = MeasureWorkerSpin(spin, rounds, pending); + if (sample.consumed < best.consumed) { + best = sample; + } + } + return best; +} + +static void WorkerSpinCase() { + constexpr std::uint32_t rounds = 512; + const auto parked = MeasureWorkerSpin(0, rounds, true); + const auto spinning = MeasureWorkerSpin(50, rounds, true); + const auto noPending = QuietestWorkerSpin(50, rounds, false); +#ifdef _WIN32 + constexpr const char* units = "cycles"; +#else + constexpr const char* units = "ns"; +#endif + std::fprintf(stderr, "idle worker CPU over %u pulses (%s): spin=0 %lld, spin=50 %lld, no pending work %lld, 5ms active reference %lld\n", rounds, units, + static_cast(parked.consumed), static_cast(spinning.consumed), static_cast(noPending.consumed), static_cast(spinning.reference)); + const auto interval = spinning.reference * rounds / 100; + Require(spinning.consumed > parked.consumed + interval / 3); + Require(spinning.consumed < parked.consumed + interval * 5); + Require(noPending.consumed < parked.consumed + interval / 3); +} + +struct WorkerClocks { + std::array clocks{}; + std::atomic arrived{0}; + std::atomic release{false}; +}; + +static void APS5_VABI CaptureWorkerClocks(void* argument, std::uint32_t index) { + auto& workers = *static_cast(argument); + CaptureWorkerClock(&workers.clocks[index], 0); + ++workers.arrived; + while (!workers.release) std::this_thread::yield(); +} + +static void WorkerCountCase() { + Block block(SizeFor(4, 1, 8, 2, 2)); + ManagerObject manager; + const ConfigParams config{0, 0x1000f, 0x10000, 0x2bc, "WorkerCount", 0, 0, 0, 0}; + Require(Initialize(manager.storage, block.memory, block.bytes, 4, 1, &config) == 0); + ISequence* sequence = nullptr; + Require(Factories(manager.storage, 0)->Create(&sequence, 8, 8, 2, 2, "worker count") == 0); + WorkerClocks workers; + Require(sequence->Dispatch(CaptureWorkerClocks, &workers, 4, nullptr) == 0); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (workers.arrived != 4 && std::chrono::steady_clock::now() < deadline) std::this_thread::yield(); + Require(workers.arrived == 4); + workers.release = true; + Require(sequence->Wait() == 0); + for (const std::uint32_t persistent : {2u, 4u}) { + Require(SetPersistentThreadCount(manager.storage, 0) == 0); + for (const int clock : workers.clocks) RequireParked(clock); + Require(SetPersistentThreadCount(manager.storage, persistent) == 0); + for (std::uint32_t job = 0; job < persistent; ++job) { + Require(sequence->Dispatch(Nothing, nullptr, 1, nullptr) == 0 && sequence->Wait() == 0); + } + std::array starts{}; + for (std::size_t index = 0; index < starts.size(); ++index) starts[index] = CpuNanos(workers.clocks[index]); + const auto until = std::chrono::steady_clock::now() + std::chrono::seconds(2); + unsigned int active = 0; + do { + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + active = 0; + for (std::size_t index = 0; index < starts.size(); ++index) { + if (CpuNanos(workers.clocks[index]) - starts[index] >= 30000000) ++active; + } + } while (active < persistent && std::chrono::steady_clock::now() < until); + Require(active == persistent); + } + Require(Shutdown(manager.storage) == 0); +} + +static void APS5_VABI ShutdownGateJob(void* argument, std::uint32_t index) { + GateJob(argument, index); + ++static_cast(argument)->next; +} + +static void ShutdownActiveCase(bool wait, bool queue, bool destroy, std::uint32_t count = 1) { + for (int round = 0; round < 3; ++round) { + Block block(SizeFor(1, 1, 4, 2, 2)); + ManagerObject manager; + Require(Initialize(manager.storage, block.memory, block.bytes, 1, 1, nullptr) == 0); + ISequenceFactory* factory = Factories(manager.storage, 0); + ISequence* sequence = nullptr; + Require(factory->Create(&sequence, 4, 4, 2, 2, "shutdown active") == 0); + Order order; + Require(sequence->Dispatch(ShutdownGateJob, &order, count, nullptr) == 0); + while (!order.started) std::this_thread::yield(); + if (queue) { + Require(sequence->SyncPoint() == 0); + Require(sequence->Dispatch(LowJob, &order, 1, nullptr) == 0); + } + std::atomic entered{false}; + std::atomic returned{false}; + std::thread waiter; + if (wait) { + waiter = std::thread([&] { + entered = true; + Require(destroy ? factory->Destroy(sequence) == 0 : sequence->Wait() == 0); + returned = true; + }); + while (!entered) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + Require(!returned); + } + std::atomic stopping{false}; + std::atomic stopped{false}; + std::thread shutdown([&] { + stopping = true; + Require(Shutdown(manager.storage) == 0); + stopped = true; + }); + while (!stopping) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + Require(!stopped); + order.release = true; + shutdown.join(); + if (waiter.joinable()) waiter.join(); + Require(stopped && (!wait || returned)); + Require(order.next == 1); + Require(Factories(manager.storage, 0) == nullptr); + Require(Initialize(manager.storage, block.memory, block.bytes, 1, 1, nullptr) == 0); + Require(Shutdown(manager.storage) == 0); + } +} + +struct ShutdownCrossWait { + ISequence* sequence; + std::atomic entered{false}; + std::atomic returned{false}; +}; + +static void APS5_VABI ShutdownWaitOther(void* argument, std::uint32_t) { + auto& state = *static_cast(argument); + state.entered = true; + Require(state.sequence->Wait() == 0); + state.returned = true; +} + +static void ConcurrentWaitCase() { + for (const bool stop : {false, true}) { + for (int round = 0; round < 3; ++round) { + Block block(SizeFor(2, 1, 4, 2, 8)); + ManagerObject manager; + Require(Initialize(manager.storage, block.memory, block.bytes, 2, 1, nullptr) == 0); + auto* factory = Factories(manager.storage, 0); + ISequence* sequence = nullptr; + Require(factory->Create(&sequence, 2, 2, 1, 8, nullptr) == 0); + Order order; + Require(sequence->Dispatch(ShutdownGateJob, &order, 1, nullptr) == 0); + while (!order.started) std::this_thread::yield(); + std::atomic entered{0}; + std::atomic returned{0}; + std::array waiters; + for (auto& waiter : waiters) waiter = std::thread([&] { + ++entered; + Require(sequence->Wait() == 0); + ++returned; + }); + while (entered != waiters.size()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(returned == 0); + std::thread shutdown; + if (stop) { + shutdown = std::thread([&] { Require(Shutdown(manager.storage) == 0); }); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + order.release = true; + for (auto& waiter : waiters) waiter.join(); + Require(returned == waiters.size() && order.next == 1); + if (stop) shutdown.join(); + else { + Require(sequence->Dispatch(Nothing, nullptr, 1, nullptr) == 0); + Require(factory->Destroy(sequence) == 0); + Require(Shutdown(manager.storage) == 0); + } + } + } +} + +static void ShutdownCrossWaitCase() { + Block block(SizeFor(2, 2, 4, 2, 2)); + ManagerObject manager; + Require(Initialize(manager.storage, block.memory, block.bytes, 2, 2, nullptr) == 0); + auto* factory = Factories(manager.storage, 0); + ISequence* producer = nullptr; + ISequence* consumer = nullptr; + Require(factory->Create(&producer, 1, 1, 1, 1, nullptr) == 0); + Require(factory->Create(&consumer, 1, 1, 1, 1, nullptr) == 0); + Order order; + Require(producer->Dispatch(ShutdownGateJob, &order, 1, nullptr) == 0); + while (!order.started) std::this_thread::yield(); + ShutdownCrossWait state{producer}; + Require(consumer->Dispatch(ShutdownWaitOther, &state, 1, nullptr) == 0); + while (!state.entered) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + Require(!state.returned); + std::atomic stopping{false}; + std::atomic stopped{false}; + std::thread shutdown([&] { + stopping = true; + Require(Shutdown(manager.storage) == 0); + stopped = true; + }); + while (!stopping) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + Require(!stopped); + order.release = true; + shutdown.join(); + Require(state.returned && stopped && order.next == 1); +} + +int RunJobManagerCase(const char* caseName) { + const std::string name = caseName; if (name == "memory") MemoryCase(); + else if (name == "owned_state") OwnedStateCase(); else if (name == "validation") ValidationCase(); else if (name == "dispatch") DispatchCase(); else if (name == "ordering") OrderingCase(); else if (name == "limits") LimitsCase(); + else if (name == "node_pool") NodePoolCase(); else if (name == "nested") NestedCase(); else if (name == "priority") PriorityCase(); else if (name == "shutdown") ShutdownCase(); + else if (name == "worker_policy") WorkerPolicyCase(); + else if (name == "worker_wait") WorkerWaitCase(); + else if (name == "worker_wake") WorkerWakeCase(); + else if (name == "worker_spin") WorkerSpinCase(); + else if (name == "worker_count") WorkerCountCase(); + else if (name == "shutdown_running") ShutdownActiveCase(false, false, false); + else if (name == "shutdown_waiter") ShutdownActiveCase(true, false, false); + else if (name == "shutdown_queued") ShutdownActiveCase(true, true, false); + else if (name == "shutdown_destroy") ShutdownActiveCase(true, true, true); + else if (name == "shutdown_partial") ShutdownActiveCase(true, false, false, 4); + else if (name == "shutdown_cross_wait") ShutdownCrossWaitCase(); + else if (name == "concurrent_wait") ConcurrentWaitCase(); else return 2; return 0; } + +#ifndef ANYPS5_JOB_MANAGER_REFERENCE +int main(int argc, char** argv) { + return RunJobManagerCase(argc > 1 ? argv[1] : ""); +} +#endif diff --git a/core/libs/tests/GuestJpegDec.cpp b/core/libs/tests/GuestJpegDec.cpp index 018dba662d..6c6183b175 100644 --- a/core/libs/tests/GuestJpegDec.cpp +++ b/core/libs/tests/GuestJpegDec.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceJpegDec/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "Decoder/Jpeg.hpp" @@ -25,14 +26,6 @@ static_assert(offsetof(JpegDecImageInfo, component_id) == 0xC && offsetof(JpegDe static_assert(offsetof(JpegDecImageInfo, coefficient_mem_size) == 0x14 && offsetof(JpegDecImageInfo, suitable_csc_attribute) == 0x18); static_assert(offsetof(JpegDecImageInfo, output_image_width) == 0x1C && offsetof(JpegDecImageInfo, output_image_height) == 0x20); -extern "C" { -std::int32_t APS5_VABI sceJpegDecQueryMemorySize(const JpegDecCreateParam*); -std::int32_t APS5_VABI sceJpegDecCreate(const JpegDecCreateParam*, void*, std::uint32_t, void**); -std::int32_t APS5_VABI sceJpegDecDelete(void*); -std::int32_t APS5_VABI sceJpegDecParseHeader(const JpegDecParseParam*, JpegDecImageInfo*); -std::int32_t APS5_VABI sceJpegDecDecode(void*, const JpegDecDecodeParam*, JpegDecImageInfo*); -} - static void Require(bool value) { if (!value) std::abort(); } template @@ -68,23 +61,22 @@ static JpegDecDecodeParam DecodeParam(const std::vector& jpeg, std } int main() { - constexpr std::int32_t invalidAddr = static_cast(0x80690001); - constexpr std::int32_t invalidSize = static_cast(0x80690002); - constexpr std::int32_t invalidParam = static_cast(0x80690003); - constexpr std::int32_t invalidHandle = static_cast(0x80690004); - constexpr std::int32_t invalidWorkMemory = static_cast(0x80690005); - constexpr std::int32_t invalidData = static_cast(0x80690010); + constexpr std::int32_t invalidAddr = static_cast(0x80650001); + constexpr std::int32_t invalidSize = static_cast(0x80650002); + constexpr std::int32_t invalidParam = static_cast(0x80650003); + constexpr std::int32_t invalidHandle = static_cast(0x80650004); + constexpr std::int32_t invalidData = static_cast(0x80650010); const std::vector jpeg = TestJpeg(7, 5, 3); JpegDecImageInfo info{}; const JpegDecParseParam parseParam{jpeg.data(), static_cast(jpeg.size()), 0, 1}; - Require(sceJpegDecParseHeader(nullptr, &info) == invalidParam); + Require(sceJpegDecParseHeader(nullptr, &info) == invalidAddr); Require(sceJpegDecParseHeader(&parseParam, nullptr) == invalidAddr); const JpegDecParseParam emptyParam{nullptr, 0, 0, 1}; Require(sceJpegDecParseHeader(&emptyParam, &info) == invalidAddr); for (const std::uint16_t downScale : {std::uint16_t{0}, std::uint16_t{3}, std::uint16_t{16}}) { const JpegDecParseParam unknownScaleParse{jpeg.data(), static_cast(jpeg.size()), 0, downScale}; - Require(ThrowsRuntimeError([&] { sceJpegDecParseHeader(&unknownScaleParse, &info); })); + Require(sceJpegDecParseHeader(&unknownScaleParse, &info) == invalidParam); } const JpegDecParseParam otherModeParse{jpeg.data(), static_cast(jpeg.size()), 1, 1}; Require(ThrowsRuntimeError([&] { sceJpegDecParseHeader(&otherModeParse, &info); })); @@ -99,17 +91,45 @@ int main() { const JpegDecParseParam grayParse{grayJpeg.data(), static_cast(grayJpeg.size()), 0, 1}; Require(sceJpegDecParseHeader(&grayParse, &info) == 0); Require(info.image_width == 3 && info.image_height == 2 && info.component_number == 1); + Require(info.color_space == 2 && info.component_id[0] == 1 && info.sampling_factor[0] == 0x11); + Require(info.component_id[1] == 0 && info.sampling_factor[1] == 0); Require(sceJpegDecParseHeader(&parseParam, &info) == 0); Require(info.image_width == 7 && info.image_height == 5 && info.component_number == 3); Require(info.output_image_width == 7 && info.output_image_height == 5); Require(info.coefficient_mem_size == 0 && info.suitable_csc_attribute == 1); + Require(info.color_space == 1); + for (std::uint32_t i = 0; i < 3; ++i) Require(info.component_id[i] == i + 1 && info.sampling_factor[i] == 0x11); + std::vector progressiveHeader = jpeg; + bool changedFrame = false; + for (std::size_t offset = 2; offset + 3 < progressiveHeader.size();) { + const std::uint8_t marker = progressiveHeader[offset + 1]; + const std::size_t length = (static_cast(progressiveHeader[offset + 2]) << 8) | progressiveHeader[offset + 3]; + if (marker == 0xC0) { + progressiveHeader[offset + 1] = 0xC2; + changedFrame = true; + break; + } + offset += 2 + length; + } + Require(changedFrame); + const JpegDecParseParam progressiveParse{progressiveHeader.data(), static_cast(progressiveHeader.size()), 0, 1}; + Require(sceJpegDecParseHeader(&progressiveParse, &info) == 0); + Require(info.coefficient_mem_size == 448); + Require(sceJpegDecParseHeader(&parseParam, &info) == 0); const JpegDecCreateParam param{sizeof(JpegDecCreateParam), info.suitable_csc_attribute, info.image_width}; const std::int32_t memorySize = sceJpegDecQueryMemorySize(¶m); - Require(memorySize > 0); + Require(memorySize == 32128); const JpegDecCreateParam fullParam{sizeof(JpegDecCreateParam), 2, info.image_width}; - Require(sceJpegDecQueryMemorySize(&fullParam) == memorySize); - Require(sceJpegDecQueryMemorySize(nullptr) == invalidParam); + const std::int32_t fullMemorySize = sceJpegDecQueryMemorySize(&fullParam); + Require(fullMemorySize == 37504); + for (const std::uint32_t width : {0u, 65536u, UINT32_MAX}) { + const JpegDecCreateParam badWidth{sizeof(JpegDecCreateParam), 1, width}; + Require(sceJpegDecQueryMemorySize(&badWidth) == invalidParam); + } + const JpegDecCreateParam largestWidth{sizeof(JpegDecCreateParam), 1, 65535}; + Require(sceJpegDecQueryMemorySize(&largestWidth) == 3569344); + Require(sceJpegDecQueryMemorySize(nullptr) == invalidAddr); const JpegDecCreateParam badSize{sizeof(JpegDecCreateParam) - 4, 1, 7}; Require(sceJpegDecQueryMemorySize(&badSize) == invalidSize); for (const std::uint32_t attribute : {0u, 3u}) { @@ -117,12 +137,13 @@ int main() { Require(sceJpegDecQueryMemorySize(&badAttribute) == invalidParam); } - std::vector memory(static_cast(memorySize) + 8); + std::vector memory(static_cast(fullMemorySize) + 32); void* handle = nullptr; - Require(sceJpegDecCreate(¶m, memory.data(), 0, &handle) == invalidWorkMemory); + Require(sceJpegDecCreate(¶m, memory.data(), 0, &handle) == invalidSize); Require(sceJpegDecCreate(¶m, memory.data(), static_cast(memorySize), nullptr) == invalidAddr); - Require(sceJpegDecCreate(&fullParam, memory.data(), static_cast(memorySize), &handle) == 0); - Require(handle != nullptr); + Require(sceJpegDecCreate(&fullParam, memory.data(), static_cast(fullMemorySize), &handle) == 0); + Require(handle != nullptr && reinterpret_cast(handle) % 32 == 0); + Require(sceJpegDecCreate(¶m, memory.data(), 0, nullptr) == invalidSize); Require(sceJpegDecDelete(nullptr) == invalidHandle); Require(sceJpegDecDelete(handle) == 0); @@ -133,14 +154,42 @@ int main() { std::vector image(7 * 5 * 4, 0xCC); JpegDecDecodeParam decodeParam = DecodeParam(jpeg, image, 7 * 4); + std::vector tallSampling = TestJpeg(31, 5, 3); + for (std::size_t offset = 2; offset + 3 < tallSampling.size();) { + const std::uint8_t marker = tallSampling[offset + 1]; + const std::size_t length = (static_cast(tallSampling[offset + 2]) << 8) | tallSampling[offset + 3]; + if (marker == 0xC0) { + tallSampling[offset + 11] = 0x14; + break; + } + offset += 2 + length; + } + std::vector tallSamplingOutput(31 * 5 * 4); + JpegDecDecodeParam tallSamplingDecode = DecodeParam(tallSampling, tallSamplingOutput, 0); + Require(sceJpegDecDecode(handle, &tallSamplingDecode, &info) == static_cast(0x80650006)); + JpegDecDecodeParam progressiveDecode = DecodeParam(progressiveHeader, image, 7 * 4); + Require(sceJpegDecDecode(handle, &progressiveDecode, &info) == static_cast(0x80650005)); + std::array insufficientCoefficients{}; + progressiveDecode.coefficient_mem_addr = insufficientCoefficients.data(); + progressiveDecode.coefficient_mem_size = static_cast(insufficientCoefficients.size()); + Require(sceJpegDecDecode(handle, &progressiveDecode, &info) == static_cast(0x80650005)); Require(sceJpegDecDecode(nullptr, &decodeParam, &info) == invalidHandle); - Require(sceJpegDecDecode(handle, nullptr, &info) == invalidParam); + Require(sceJpegDecDecode(handle, nullptr, &info) == invalidAddr); JpegDecDecodeParam badFormat = decodeParam; badFormat.pixel_format = 2; Require(sceJpegDecDecode(handle, &badFormat, &info) == invalidParam); JpegDecDecodeParam unknownScale = decodeParam; unknownScale.down_scale = 3; - Require(ThrowsRuntimeError([&] { sceJpegDecDecode(handle, &unknownScale, &info); })); + Require(sceJpegDecDecode(handle, &unknownScale, &info) == invalidParam); + JpegDecDecodeParam invalidOptions = decodeParam; + invalidOptions.alpha_value = 256; + Require(sceJpegDecDecode(handle, &invalidOptions, &info) == invalidParam); + invalidOptions = decodeParam; + invalidOptions.decode_mode = 2; + Require(sceJpegDecDecode(handle, &invalidOptions, &info) == invalidParam); + invalidOptions = decodeParam; + invalidOptions.image_mem_addr = image.data() + 1; + Require(sceJpegDecDecode(handle, &invalidOptions, &info) == invalidAddr); std::vector invalidAlphaImage(7 * 5 * 4, 0xCC); JpegDecDecodeParam invalidAlpha = DecodeParam(jpeg, invalidAlphaImage, 7 * 4); invalidAlpha.alpha_value = 0x100; @@ -168,7 +217,7 @@ int main() { } constexpr std::uint32_t pitch = 7 * 4 + 8; - std::vector bgra(4 * pitch + 7 * 4, 0xEE); + std::vector bgra(5 * pitch, 0xEE); JpegDecDecodeParam bgraParam = DecodeParam(jpeg, bgra, pitch); bgraParam.pixel_format = 1; bgraParam.alpha_value = 0x77; @@ -185,6 +234,9 @@ int main() { } const std::vector large = TestJpeg(37, 21, 3); + std::vector wideOutput(37 * 21 * 4); + JpegDecDecodeParam wideParam = DecodeParam(large, wideOutput, 0); + Require(sceJpegDecDecode(handle, &wideParam, nullptr) == static_cast(0x80650006)); const std::vector largeGray = TestJpeg(19, 11, 1); for (const auto* source : {&large, &largeGray}) { const std::optional sourceHeader = Decoder::Jpeg::ParseHeader(*source); @@ -208,7 +260,7 @@ int main() { } const std::uint32_t paddedPitch = width * 4 + 4; - std::vector padded((height - 1) * paddedPitch + width * 4, 0xEE); + std::vector padded(height * paddedPitch, 0xEE); JpegDecDecodeParam paddedParam = DecodeParam(*source, padded, paddedPitch); paddedParam.down_scale = downScale; paddedParam.pixel_format = 1; diff --git a/core/libs/tests/GuestJpegEnc.cpp b/core/libs/tests/GuestJpegEnc.cpp index 52437df7a5..c97ce0d6a1 100644 --- a/core/libs/tests/GuestJpegEnc.cpp +++ b/core/libs/tests/GuestJpegEnc.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceJpegEnc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "Decoder/Jpeg.hpp" @@ -8,13 +9,6 @@ #include #include -extern "C" { -std::int32_t APS5_VABI sceJpegEncQueryMemorySize(const JpegEncCreateParam*); -std::int32_t APS5_VABI sceJpegEncCreate(const JpegEncCreateParam*, void*, std::uint32_t, void**); -std::int32_t APS5_VABI sceJpegEncDelete(void*); -std::int32_t APS5_VABI sceJpegEncEncode(void*, const JpegEncEncodeParam*, JpegEncOutputInfo*); -} - static void Require(bool value) { if (!value) std::abort(); } alignas(4) static unsigned char image[16 * 16 * 4]; @@ -209,7 +203,7 @@ int main() { rgba.image_pitch = pitch; rgba.compression_ratio = 0; info = {}; - Require(sceJpegEncEncode(handle, &rgba, &info) == 0); + Require(sceJpegEncEncode(handle, &rgba, &info) == static_cast(info.size) && info.size > 0); Require(info.height == 16); Require(ReadFrame(jpeg, info.size) == std::pair{3, std::uint8_t{0x22}}); Require(AverageError(expected, DecodeOutput(info)) < 6); @@ -225,7 +219,7 @@ int main() { JpegEncEncodeParam bgra = rgba; bgra.pixel_format = 1; info = {}; - Require(sceJpegEncEncode(handle, &bgra, &info) == 0); + Require(sceJpegEncEncode(handle, &bgra, &info) == static_cast(info.size) && info.size > 0); Require(ReadFrame(jpeg, info.size) == std::pair{3, std::uint8_t{0x22}}); Require(AverageError(expected, DecodeOutput(info)) < 6); @@ -242,7 +236,7 @@ int main() { yuv.image_pitch = 16 * 2; yuv.pixel_format = 10; info = {}; - Require(sceJpegEncEncode(handle, &yuv, &info) == 0); + Require(sceJpegEncEncode(handle, &yuv, &info) == static_cast(info.size) && info.size > 0); Require(AverageError(std::vector(16 * 16 * 3, 100), DecodeOutput(info)) < 3); static unsigned char gray[16 * 16]; @@ -257,7 +251,7 @@ int main() { y8.color_space = 2; y8.sampling_type = 0; info = {}; - Require(sceJpegEncEncode(handle, &y8, &info) == 0); + Require(sceJpegEncEncode(handle, &y8, &info) == static_cast(info.size) && info.size > 0); Require(ReadFrame(jpeg, info.size) == std::pair{1, std::uint8_t{0x11}}); const auto decodedGray = DecodeOutput(info, 1); Require(AverageError(std::vector(gray, gray + sizeof(gray)), decodedGray) < 6); @@ -265,11 +259,11 @@ int main() { rgba.sampling_type = 1; rgba.compression_ratio = 0; info = {}; - Require(sceJpegEncEncode(handle, &rgba, &info) == 0); + Require(sceJpegEncEncode(handle, &rgba, &info) == static_cast(info.size) && info.size > 0); Require(ReadFrame(jpeg, info.size) == std::pair{3, std::uint8_t{0x21}}); std::memset(jpeg, 0, sizeof(jpeg)); - Require(sceJpegEncEncode(handle, &rgba, nullptr) == 0); + Require(sceJpegEncEncode(handle, &rgba, nullptr) == static_cast(info.size)); Require(jpeg[0] == 0xFF && jpeg[1] == 0xD8); JpegEncEncodeParam tooSmall = rgba; @@ -278,8 +272,9 @@ int main() { Require(encodeWith([](JpegEncEncodeParam& p) { p.image_width = 0; }) == invalidParam); Require(ThrowsRuntimeError([&] { encodeWith([](JpegEncEncodeParam& p) { p.encode_mode = 1; }); })); - Require(ThrowsRuntimeError([&] { encodeWith([](JpegEncEncodeParam& p) { p.restart_interval = -2; }); })); - Require(ThrowsRuntimeError([&] { encodeWith([](JpegEncEncodeParam& p) { p.restart_interval = INT32_MIN; }); })); + Require(encodeWith([](JpegEncEncodeParam& p) { p.restart_interval = -2; }) == invalidParam); + Require(encodeWith([](JpegEncEncodeParam& p) { p.restart_interval = INT32_MIN; }) == invalidParam); + Require(encodeWith([](JpegEncEncodeParam& p) { p.encode_mode = 1; p.restart_interval = -2; }) == invalidParam); constexpr std::uint32_t wideWidth = 48; constexpr std::uint32_t wideHeight = 32; @@ -302,7 +297,7 @@ int main() { p.jpeg_size = sizeof(restarted); p.restart_interval = restart; JpegEncOutputInfo out{}; - Require(sceJpegEncEncode(handle, &p, &out) == 0); + Require(sceJpegEncEncode(handle, &p, &out) == static_cast(out.size)); Require(IsJpeg(restarted, out.size) && out.height == wideHeight); const auto decoded = Decoder::Jpeg::Decode({restarted, out.size}); Require(decoded.has_value() && decoded->width == wideWidth && decoded->height == wideHeight); diff --git a/core/libs/tests/GuestJson.cpp b/core/libs/tests/GuestJson.cpp index 16de775009..1cc2993f93 100644 --- a/core/libs/tests/GuestJson.cpp +++ b/core/libs/tests/GuestJson.cpp @@ -1,5 +1,8 @@ +#include "prx/libSceJson2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include +#include #include #include #include @@ -7,145 +10,17 @@ #include #include #include +#include #include -struct Value { void* node; }; -struct String { void* text; }; -struct Array { void* items; }; -struct Object { void* items; }; -struct Iterator { void* position; }; -struct Pair { - String key; - std::uint64_t reserved; - Value value; -}; -using NullAccessCallback = const Value& (APS5_VABI*)(std::int32_t, const Value*, void*); +using namespace SceJson2; + using SetNullAccessCallback = int (APS5_VABI*)(void*, NullAccessCallback, void*); -using TypeMismatchHandler = const Value& (APS5_VABI*)(std::int32_t, const Value*, const Value*, void*); -using SpecialFloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); -using ElementAccessFailureHandler = void (APS5_VABI*)(std::int32_t, const String*, const std::size_t*, const Value*, const Value*, void*); extern "C" { -int APS5_VABI _ZN3sce4Json11InitializerC1Ev(void*); -int APS5_VABI _ZN3sce4Json11InitializerD1Ev(void*); -int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(void*, const void*); -int APS5_VABI _ZN3sce4Json11Initializer9terminateEv(void*); -int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void*, NullAccessCallback, void*); -int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void*, NullAccessCallback, void*); -void APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(NullAccessCallback, void*); -int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(Value*, TypeMismatchHandler, void*); -int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(Value*, SpecialFloatHandler, void*); -int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(Value*, ElementAccessFailureHandler, void*); -int APS5_VABI _ZN3sce4Json5Value3setEd(Value*, double); -int APS5_VABI _ZN3sce4Json5Value3setERKS1_(Value*, const Value*); -Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(Value*, const Value*); -void APS5_VABI _ZN3sce4Json5ValueC1ERKS1_(Value*, const Value*); -void APS5_VABI _ZN3sce4Json6StringC1Ev(String*); -void APS5_VABI _ZN3sce4Json6StringC1EPKc(String*, const char*); -void APS5_VABI _ZN3sce4Json6StringD1Ev(String*); -const char* APS5_VABI _ZNK3sce4Json6String5c_strEv(const String*); -std::size_t APS5_VABI _ZNK3sce4Json6String6lengthEv(const String*); -bool APS5_VABI _ZNK3sce4Json6String5emptyEv(const String*); -void APS5_VABI _ZN3sce4Json5ArrayC1Ev(Array*); -void APS5_VABI _ZN3sce4Json5ArrayC1ERKS1_(Array*, const Array*); -void APS5_VABI _ZN3sce4Json5Array5clearEv(Array*); -const Value* APS5_VABI _ZNK3sce4Json5Array5frontEv(const Array*); -Value* APS5_VABI _ZNK3sce4Json5Array8iteratorptEv(const Iterator*); -void APS5_VABI _ZN3sce4Json5ArrayD1Ev(Array*); -void APS5_VABI _ZN3sce4Json5Array9push_backERKNS0_5ValueE(Array*, const Value*); -Iterator* APS5_VABI _ZNK3sce4Json5Array5beginEv(Iterator*, const Array*); -Iterator* APS5_VABI _ZNK3sce4Json5Array3endEv(Iterator*, const Array*); -std::size_t APS5_VABI _ZNK3sce4Json5Array4sizeEv(const Array*); -const Value* APS5_VABI _ZNK3sce4Json5Array4backEv(const Array*); -void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(Iterator*); -Iterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(Iterator*); -Value* APS5_VABI _ZNK3sce4Json5Array8iteratordeEv(const Iterator*); -bool APS5_VABI _ZNK3sce4Json5Array8iteratorneERKS2_(const Iterator*, const Iterator*); -void APS5_VABI _ZN3sce4Json6ObjectC1Ev(Object*); -void APS5_VABI _ZN3sce4Json6ObjectD1Ev(Object*); -Value* APS5_VABI _ZN3sce4Json6ObjectixERKNS0_6StringE(Object*, const String*); -Iterator* APS5_VABI _ZNK3sce4Json6Object5beginEv(Iterator*, const Object*); -Iterator* APS5_VABI _ZNK3sce4Json6Object3endEv(Iterator*, const Object*); -void APS5_VABI _ZN3sce4Json6Object8iteratorD1Ev(Iterator*); -Iterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEv(Iterator*); -Pair* APS5_VABI _ZNK3sce4Json6Object8iteratordeEv(const Iterator*); -Pair* APS5_VABI _ZNK3sce4Json6Object8iteratorptEv(const Iterator*); -Iterator* APS5_VABI _ZNK3sce4Json6Object4findERKNS0_6StringE(Iterator*, const Object*, const String*); -int APS5_VABI _ZNK3sce4Json6String7compareEPKc(const String*, const char*); -int APS5_VABI _ZNK3sce4Json6String7compareERKS1_(const String*, const String*); -std::size_t APS5_VABI _ZNK3sce4Json6String4sizeEv(const String*); -std::size_t APS5_VABI _ZNK3sce4Json6String8capacityEv(const String*); -bool APS5_VABI _ZNK3sce4Json6StringeqEPKc(const String*, const char*); -bool APS5_VABI _ZNK3sce4Json6StringeqERKS1_(const String*, const String*); + extern std::size_t _ZN3sce4Json6String4nposE; -void APS5_VABI _ZN3sce4Json6String5clearEv(String*); -void APS5_VABI _ZN3sce4Json6String6resizeEm(String*, std::size_t); -void APS5_VABI _ZN3sce4Json6String7reserveEm(String*, std::size_t); -String* APS5_VABI _ZN3sce4Json6String6appendERKS1_(String*, const String*); -String* APS5_VABI _ZN3sce4Json6String6appendEPKc(String*, const char*); -String* APS5_VABI _ZN3sce4Json6String6appendEPKcm(String*, const char*, std::size_t); -String* APS5_VABI _ZN3sce4Json6StringpLEPKc(String*, const char*); -String* APS5_VABI _ZN3sce4Json6StringpLEh(String*, unsigned char); -String* APS5_VABI _ZNK3sce4Json6String6substrEmm(String*, const String*, std::size_t, std::size_t); -std::size_t APS5_VABI _ZNK3sce4Json6String4findEcm(const String*, char, std::size_t); -std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEcm(const String*, char, std::size_t); -bool APS5_VABI _ZNK3sce4Json6Object8iteratorneERKS2_(const Iterator*, const Iterator*); -bool APS5_VABI _ZNK3sce4Json6Object8iteratoreqERKS2_(const Iterator*, const Iterator*); -void APS5_VABI _ZN3sce4Json5ValueC1Ev(Value*); -void APS5_VABI _ZN3sce4Json5ValueC1Eb(Value*, bool); -void APS5_VABI _ZN3sce4Json5ValueC1Ed(Value*, double); -void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(Value*, const Object*); -void APS5_VABI _ZN3sce4Json5ValueD1Ev(Value*); -void APS5_VABI _ZN3sce4Json5Value5clearEv(Value*); -int APS5_VABI _ZN3sce4Json5Value3setEl(Value*, std::int64_t); -int APS5_VABI _ZN3sce4Json5Value3setEPKc(Value*, const char*); -std::int32_t APS5_VABI _ZNK3sce4Json5Value7getTypeEv(const Value*); -const bool* APS5_VABI _ZNK3sce4Json5Value10getBooleanEv(const Value*); -const std::int64_t* APS5_VABI _ZNK3sce4Json5Value10getIntegerEv(const Value*); -const std::uint64_t* APS5_VABI _ZNK3sce4Json5Value11getUIntegerEv(const Value*); -const double* APS5_VABI _ZNK3sce4Json5Value7getRealEv(const Value*); -const String* APS5_VABI _ZNK3sce4Json5Value9getStringEv(const Value*); -const Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const Value*); -std::size_t APS5_VABI _ZNK3sce4Json5Value5countEv(const Value*); -const Value* APS5_VABI _ZNK3sce4Json5ValueixEPKc(const Value*, const char*); -Value* APS5_VABI _ZN3sce4Json5Value10referValueERKNS0_6StringE(Value*, const String*); -const Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const Value*, std::size_t); -int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringE(Value*, String*); -int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value*, const char*, std::size_t); -int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(Value*, int (APS5_VABI*)(char&, void*), void*); -bool APS5_VABI _ZNK3sce4Json6Object5emptyEv(const Object*); -std::size_t APS5_VABI _ZNK3sce4Json6Object4sizeEv(const Object*); -const Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const Value*); -void APS5_VABI _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(Value*, std::int32_t); -const Value* APS5_VABI _ZNK3sce4Json5Value8getValueEm(const Value*, std::size_t); -const Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const Value*, const String*); -Value* APS5_VABI _ZN3sce4Json5Value10referValueEm(Value*, std::size_t); -void APS5_VABI _ZN3sce4Json14InitParameter2C1Ev(void*); -void APS5_VABI _ZN3sce4Json14InitParameter2C2Ev(void*); -void APS5_VABI _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(void*, void*, void*); -void APS5_VABI _ZN3sce4Json14InitParameter217setFileBufferSizeEm(void*, std::size_t); -int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(void*, const void*); -void APS5_VABI _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(void*, void*, void*); -void APS5_VABI _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(void*, std::int32_t); -void APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(void*, void*, void*, std::size_t); -void APS5_VABI _ZN3sce4Json6Object4PairC1Ev(Pair*); -void APS5_VABI _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(Pair*, const String*, const Value*); -void APS5_VABI _ZN3sce4Json6Object4PairD1Ev(Pair*); -Iterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(Iterator*, Object*, const Pair*); -void APS5_VABI _ZN3sce4Json6Object8iteratorC1Ev(Iterator*); -Iterator* APS5_VABI _ZN3sce4Json6Object8iteratoraSERKS2_(Iterator*, const Iterator*); -Iterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEi(Iterator*, Iterator*, int); -void APS5_VABI _ZN3sce4Json5Array8iteratorC1Ev(Iterator*); -Iterator* APS5_VABI _ZN3sce4Json5Array8iteratoraSERKS2_(Iterator*, const Iterator*); -Iterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEi(Iterator*, Iterator*, int); -void APS5_VABI _ZN3sce4Json12MemAllocator11notifyErrorEimPv(void*, int, std::size_t, void*); -bool APS5_VABI _ZNK3sce4Json5ValuecvbEv(const Value*); -void APS5_VABI _ZN3sce4Json5Value4swapERS1_(Value*, Value*); -void APS5_VABI _ZN3sce4Json5Array8iteratorC1ERKS2_(Iterator*, const Iterator*); -Iterator* APS5_VABI _ZN3sce4Json5Array8iterator7advanceEm(Iterator*, std::size_t); -void APS5_VABI _ZN3sce4Json6Object8iteratorC1ERKS2_(Iterator*, const Iterator*); -Iterator* APS5_VABI _ZN3sce4Json6Object8iterator7advanceEm(Iterator*, std::size_t); -std::size_t APS5_VABI _ZN3sce4Json6Object5eraseERKNS0_6StringE(Object*, const String*); + } static void Check(bool value, int line) { @@ -156,8 +31,7 @@ static void Check(bool value, int line) { } #define Require(value) Check((value), __LINE__) -enum : std::int32_t { TypeNull, TypeBoolean, TypeInteger, TypeUInteger, TypeReal, TypeString, TypeArray, TypeObject }; -constexpr int JsonErrorParse = static_cast(0x80920101); +constexpr int JsonErrorParse = static_cast(0x80848101); static Value fallback{}; static int callbackCalls = 0; @@ -165,7 +39,7 @@ static std::int32_t lastRequested = -1; static const Value* lastParent = nullptr; static void* lastContext = nullptr; -static const Value& APS5_VABI OnNullAccess(std::int32_t requested, const Value* parent, void* context) { +static const Value& APS5_VABI OnNullAccess(ValueType requested, const Value* parent, void* context) { ++callbackCalls; lastRequested = requested; lastParent = parent; @@ -176,9 +50,11 @@ static const Value& APS5_VABI OnNullAccess(std::int32_t requested, const Value* static std::string Serialize(Value& value) { String out{}; _ZN3sce4Json6StringC1Ev(&out); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &out) == 0); std::string text(_ZNK3sce4Json6String5c_strEv(&out), _ZNK3sce4Json6String6lengthEv(&out)); _ZN3sce4Json6StringD1Ev(&out); + return text; } @@ -192,11 +68,12 @@ static void ParseAndRoundTrip() { " \"pi\": 1.5, \"ok\": true, \"none\": null, \"list\": [1, \"two\", [3]] }"; Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text.c_str(), text.size()) == 0); Require(_ZNK3sce4Json5Value7getTypeEv(&root) == TypeObject); Require(_ZNK3sce4Json5Value5countEv(&root) == 8); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&Member(root, "name"))), "x\xc3\xa9\n") == 0); - Require(_ZNK3sce4Json5Value7getTypeEv(&Member(root, "count")) == TypeInteger); + Require(_ZNK3sce4Json5Value7getTypeEv(&Member(root, "count")) == TypeUInteger); Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "count")) == 3); Require(*_ZNK3sce4Json5Value11getUIntegerEv(&Member(root, "count")) == 3); Require(*_ZNK3sce4Json5Value7getRealEv(&Member(root, "count")) == 3.0); @@ -210,6 +87,7 @@ static void ParseAndRoundTrip() { String absent{}; _ZN3sce4Json6StringC1EPKc(&present, "ok"); _ZN3sce4Json6StringC1EPKc(&absent, "applyToAll"); + Require(_ZN3sce4Json5Value10referValueERKNS0_6StringE(&root, &present) == &Member(root, "ok")); Require(_ZN3sce4Json5Value10referValueERKNS0_6StringE(&root, &absent) == nullptr); Require(_ZNK3sce4Json5Value5countEv(&root) == 8); @@ -220,37 +98,48 @@ static void ParseAndRoundTrip() { const Array* array = _ZNK3sce4Json5Value8getArrayEv(&list); Require(_ZNK3sce4Json5Array4sizeEv(array) == 3); std::vector types; - Iterator it{}, end{}; + ArrayIterator it{}, end{}; _ZNK3sce4Json5Array5beginEv(&it, array); _ZNK3sce4Json5Array3endEv(&end, array); + for (; _ZNK3sce4Json5Array8iteratorneERKS2_(&it, &end); _ZN3sce4Json5Array8iteratorppEv(&it)) types.push_back(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5Array8iteratordeEv(&it))); _ZN3sce4Json5Array8iteratorD1Ev(&it); _ZN3sce4Json5Array8iteratorD1Ev(&end); - Require((types == std::vector{TypeInteger, TypeString, TypeArray})); + + Require((types == std::vector{TypeUInteger, TypeString, TypeArray})); Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5ValueixEm(_ZNK3sce4Json5ValueixEm(&list, 2), 0)) == 3); const std::string serialized = Serialize(root); - Require(serialized == "{\"name\":\"x\xc3\xa9\\n\",\"count\":3,\"big\":18446744073709551615,\"neg\":-5," - "\"pi\":1.5,\"ok\":true,\"none\":null,\"list\":[1,\"two\",[3]]}"); + Require(serialized == "{\"big\":18446744073709551615,\"count\":3,\"list\":[1,\"two\",[3]]," + "\"name\":\"x\xc3\xa9\\n\",\"neg\":-5,\"none\":null,\"ok\":true,\"pi\":1.5}"); Value reparsed{}; _ZN3sce4Json5ValueC1Ev(&reparsed); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&reparsed, serialized.c_str(), serialized.size()) == 0); Require(Serialize(reparsed) == serialized); _ZN3sce4Json5ValueD1Ev(&reparsed); - for (const char* invalid : {"{\"a\":}", "[1,]", "\"open", "tru", "{} x", "01"}) { - Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, invalid, std::strlen(invalid)) < 0); + for (const char* invalid : {"{\"a\":}", "[1,]", "\"open", "tru"}) { + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, invalid, std::strlen(invalid)) == JsonErrorParse); Require(_ZNK3sce4Json5Value7getTypeEv(&root) == TypeObject && _ZNK3sce4Json5Value5countEv(&root) == 8); } Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, nullptr, 0) == JsonErrorParse); - Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, nullptr, 10) == JsonErrorParse); + bool nullTextRejected = false; + try { + _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, nullptr, 10); + + } catch (const std::invalid_argument&) { + nullTextRejected = true; + } + Require(nullTextRejected); Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, "", 0) == JsonErrorParse); Require(_ZNK3sce4Json5Value7getTypeEv(&root) == TypeObject && _ZNK3sce4Json5Value5countEv(&root) == 8); for (const char* overflow : {"1e309", "[-1e309]", "{\"a\":[1,1e400]}"}) { bool unverified = false; try { _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, overflow, std::strlen(overflow)); + } catch (const std::runtime_error& error) { unverified = std::string(error.what()).find("unverified") != std::string::npos; } @@ -258,6 +147,7 @@ static void ParseAndRoundTrip() { Require(_ZNK3sce4Json5Value7getTypeEv(&root) == TypeObject && _ZNK3sce4Json5Value5countEv(&root) == 8); } _ZN3sce4Json5ValueD1Ev(&root); + } static std::string Nested(std::size_t depth, bool objects) { @@ -271,6 +161,7 @@ static std::string Nested(std::size_t depth, bool objects) { static void NestingDepth() { Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + for (const bool objects : {false, true}) { const std::string accepted = Nested(512, objects); Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, accepted.c_str(), accepted.size()) == 0); @@ -280,14 +171,47 @@ static void NestingDepth() { } } _ZN3sce4Json5ValueD1Ev(&root); + +} + +static void ArrayAssignment() { + Array source{}, target{}; + _ZN3sce4Json5ArrayC1Ev(&source); + _ZN3sce4Json5ArrayC1Ev(&target); + + Value flag{}, real{}; + _ZN3sce4Json5ValueC1Eb(&flag, true); + _ZN3sce4Json5ValueC1Ed(&real, 1.5); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&source, &flag); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&source, &real); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&source, &flag); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&target, &real); + + Require(_ZN3sce4Json5ArrayaSERKS1_(&target, &source) == &target); + Require(_ZNK3sce4Json5Array4sizeEv(&target) == 3); + Require(_ZNK3sce4Json5Array4sizeEv(&source) == 3); + _ZN3sce4Json5Array5clearEv(&source); + + Require(_ZNK3sce4Json5Array4sizeEv(&target) == 3); + Require(*_ZNK3sce4Json5Value10getBooleanEv(_ZNK3sce4Json5Array5frontEv(&target))); + Require(*_ZNK3sce4Json5Value10getBooleanEv(_ZNK3sce4Json5Array4backEv(&target))); + Require(_ZN3sce4Json5ArrayaSERKS1_(&target, &target) == &target); + Require(_ZNK3sce4Json5Array4sizeEv(&target) == 3); + _ZN3sce4Json5ValueD1Ev(&flag); + _ZN3sce4Json5ValueD1Ev(&real); + _ZN3sce4Json5ArrayD1Ev(&source); + _ZN3sce4Json5ArrayD1Ev(&target); + } static void ObjectsAndArrays() { Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + String alpha{}, beta{}; _ZN3sce4Json6StringC1EPKc(&alpha, "alpha"); _ZN3sce4Json6StringC1EPKc(&beta, "beta"); + Require(_ZNK3sce4Json6Object5emptyEv(&object)); Value* first = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &alpha); Require(!_ZNK3sce4Json6Object5emptyEv(&object)); @@ -297,9 +221,10 @@ static void ObjectsAndArrays() { Require(_ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &alpha) == first); std::vector keys; - Iterator it{}, end{}; + ObjectIterator it{}, end{}; _ZNK3sce4Json6Object5beginEv(&it, &object); _ZNK3sce4Json6Object3endEv(&end, &object); + for (; _ZNK3sce4Json6Object8iteratorneERKS2_(&it, &end); _ZN3sce4Json6Object8iteratorppEv(&it)) { const Pair* pair = _ZNK3sce4Json6Object8iteratordeEv(&it); keys.emplace_back(_ZNK3sce4Json6String5c_strEv(&pair->key)); @@ -307,10 +232,12 @@ static void ObjectsAndArrays() { } _ZN3sce4Json6Object8iteratorD1Ev(&it); _ZN3sce4Json6Object8iteratorD1Ev(&end); + Require((keys == std::vector{"alpha", "beta"})); Value wrapped{}; _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&wrapped, &object); + Require(Serialize(wrapped) == "{\"alpha\":1,\"beta\":\"b\"}"); _ZN3sce4Json5ValueD1Ev(&wrapped); _ZN3sce4Json6StringD1Ev(&alpha); @@ -319,6 +246,7 @@ static void ObjectsAndArrays() { Array array{}; _ZN3sce4Json5ArrayC1Ev(&array); + Value flag{}, real{}; _ZN3sce4Json5ValueC1Eb(&flag, true); _ZN3sce4Json5ValueC1Ed(&real, 2.5); @@ -326,23 +254,29 @@ static void ObjectsAndArrays() { _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &real); _ZN3sce4Json5ValueD1Ev(&flag); _ZN3sce4Json5ValueD1Ev(&real); + Require(_ZNK3sce4Json5Array4sizeEv(&array) == 2); Require(*_ZNK3sce4Json5Value7getRealEv(_ZNK3sce4Json5Array4backEv(&array)) == 2.5); + ArrayIterator arrayIt{}, arrayEnd{}; int visited = 0; - _ZNK3sce4Json5Array5beginEv(&it, &array); - _ZNK3sce4Json5Array3endEv(&end, &array); - for (; _ZNK3sce4Json5Array8iteratorneERKS2_(&it, &end); _ZN3sce4Json5Array8iteratorppEv(&it)) ++visited; + _ZNK3sce4Json5Array5beginEv(&arrayIt, &array); + _ZNK3sce4Json5Array3endEv(&arrayEnd, &array); + + for (; _ZNK3sce4Json5Array8iteratorneERKS2_(&arrayIt, &arrayEnd); _ZN3sce4Json5Array8iteratorppEv(&arrayIt)) ++visited; Require(visited == 2); Require(*_ZNK3sce4Json5Value10getBooleanEv(_ZNK3sce4Json5Array5frontEv(&array))); - _ZNK3sce4Json5Array5beginEv(&it, &array); - Require(_ZNK3sce4Json5Array8iteratorptEv(&it) == _ZNK3sce4Json5Array8iteratordeEv(&it)); + _ZNK3sce4Json5Array5beginEv(&arrayIt, &array); + + Require(_ZNK3sce4Json5Array8iteratorptEv(&arrayIt) == _ZNK3sce4Json5Array8iteratordeEv(&arrayIt)); Array copy{}; _ZN3sce4Json5ArrayC1ERKS1_(©, &array); _ZN3sce4Json5Array5clearEv(&array); + Require(_ZNK3sce4Json5Array4sizeEv(&array) == 0); bool threw = false; try { _ZNK3sce4Json5Array5frontEv(&array); + } catch (const std::out_of_range&) { threw = true; } @@ -350,20 +284,39 @@ static void ObjectsAndArrays() { Require(_ZNK3sce4Json5Array4sizeEv(©) == 2); Require(*_ZNK3sce4Json5Value10getBooleanEv(_ZNK3sce4Json5Array5frontEv(©))); Require(*_ZNK3sce4Json5Value7getRealEv(_ZNK3sce4Json5Array4backEv(©)) == 2.5); + _ZN3sce4Json5Array9pop_frontEv(©); + + Require(_ZNK3sce4Json5Array4sizeEv(©) == 1); + Require(*_ZNK3sce4Json5Value7getRealEv(_ZNK3sce4Json5Array5frontEv(©)) == 2.5); + _ZN3sce4Json5Array9pop_frontEv(©); + + Require(_ZNK3sce4Json5Array5emptyEv(©)); + threw = false; + try { + _ZN3sce4Json5Array9pop_frontEv(©); + + } catch (const std::out_of_range&) { + threw = true; + } + Require(threw && _ZNK3sce4Json5Array5emptyEv(©)); _ZN3sce4Json5ArrayD1Ev(©); _ZN3sce4Json5ArrayD1Ev(&array); + } static void ObjectPair() { String key{}; _ZN3sce4Json6StringC1EPKc(&key, "answer"); + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + Require(_ZN3sce4Json5Value3setEPKc(&value, "forty-two") == 0); Pair pair{}; std::memset(&pair, 0x5a, sizeof(pair)); _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &value); + Require(pair.reserved == 0); Require(pair.key.text != key.text && pair.value.node != value.node); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair.key), "answer") == 0); @@ -373,37 +326,46 @@ static void ObjectPair() { Require(_ZN3sce4Json5Value3setEl(&value, 7) == 0); _ZN3sce4Json6StringD1Ev(&key); _ZN3sce4Json5ValueD1Ev(&value); + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair.key), "answer") == 0); Require(_ZNK3sce4Json5Value7getTypeEv(&pair.value) == TypeString); _ZN3sce4Json6Object4PairD1Ev(&pair); + Require(pair.key.text == nullptr && pair.value.node == nullptr); } static void ArrayIteratorCopies() { Array array{}; _ZN3sce4Json5ArrayC1Ev(&array); + for (std::int64_t i = 1; i <= 3; ++i) { Value item{}; _ZN3sce4Json5ValueC1Ev(&item); + Require(_ZN3sce4Json5Value3setEl(&item, i) == 0); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &item); _ZN3sce4Json5ValueD1Ev(&item); + } - Iterator it{}; + ArrayIterator it{}; std::memset(&it, 0x5a, sizeof(it)); _ZN3sce4Json5Array8iteratorC1Ev(&it); - Require(it.position == nullptr); - Iterator begin{}, end{}; + Require(it.state != nullptr); + + ArrayIterator begin{}, end{}; _ZNK3sce4Json5Array5beginEv(&begin, &array); _ZNK3sce4Json5Array3endEv(&end, &array); + Require(_ZN3sce4Json5Array8iteratoraSERKS2_(&it, &begin) == &it); Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(&it, &begin)); std::vector seen; - Iterator previous{}; + ArrayIterator previous{}; while (_ZNK3sce4Json5Array8iteratorneERKS2_(&it, &end)) { + _ZN3sce4Json5Array8iteratorD1Ev(&previous); + Require(_ZN3sce4Json5Array8iteratorppEi(&previous, &it, 0) == &previous); seen.push_back(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(&previous))); Require(_ZNK3sce4Json5Array8iteratorneERKS2_(&previous, &it)); @@ -411,6 +373,11 @@ static void ArrayIteratorCopies() { Require((seen == std::vector{1, 2, 3})); Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(&it, &end)); Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(&previous)) == 3); + Require(_ZN3sce4Json5Array8iteratoraSERKS2_(&it, &end) == &it); + Require(it.state == nullptr); + Require(_ZN3sce4Json5Array8iteratoraSERKS2_(&it, &begin) == &it); + Require(it.state == nullptr); + Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(&it, &begin)); _ZN3sce4Json5Array8iteratorD1Ev(&previous); _ZN3sce4Json5Array8iteratorD1Ev(&it); @@ -423,19 +390,23 @@ static void AllocatorNotifyError() { int allocator = 0x1234; int userData = 0x5678; _ZN3sce4Json12MemAllocator11notifyErrorEimPv(&allocator, -1, 64, &userData); + Require(allocator == 0x1234 && userData == 0x5678); } static void ObjectFind() { Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + String alpha{}, beta{}, gamma{}; _ZN3sce4Json6StringC1EPKc(&alpha, "alpha"); _ZN3sce4Json6StringC1EPKc(&beta, "beta"); _ZN3sce4Json6StringC1EPKc(&gamma, "gamma"); - Iterator found{}, end{}; + + ObjectIterator found{}, end{}; _ZNK3sce4Json6Object3endEv(&end, &object); _ZNK3sce4Json6Object4findERKNS0_6StringE(&found, &object, &alpha); + Require(!_ZNK3sce4Json6Object8iteratorneERKS2_(&found, &end)); _ZN3sce4Json6Object8iteratorD1Ev(&found); _ZN3sce4Json6Object8iteratorD1Ev(&end); @@ -444,7 +415,9 @@ static void ObjectFind() { Value* second = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &beta); Require(_ZN3sce4Json5Value3setEPKc(second, "b") == 0); _ZNK3sce4Json6Object3endEv(&end, &object); - Iterator* returned = _ZNK3sce4Json6Object4findERKNS0_6StringE(&found, &object, &beta); + _ZN3sce4Json6Object8iteratorD1Ev(&found); + + ObjectIterator* returned = _ZNK3sce4Json6Object4findERKNS0_6StringE(&found, &object, &beta); Require(returned == &found); Require(_ZNK3sce4Json6Object8iteratorneERKS2_(&found, &end)); const Pair* pair = _ZNK3sce4Json6Object8iteratorptEv(&found); @@ -452,8 +425,11 @@ static void ObjectFind() { Require(&pair->value == second); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair->key), "beta") == 0); _ZN3sce4Json6Object8iteratorppEv(&found); + Require(!_ZNK3sce4Json6Object8iteratorneERKS2_(&found, &end)); + _ZN3sce4Json6Object8iteratorD1Ev(&found); _ZNK3sce4Json6Object4findERKNS0_6StringE(&found, &object, &gamma); + Require(!_ZNK3sce4Json6Object8iteratorneERKS2_(&found, &end)); Require(_ZNK3sce4Json6Object4sizeEv(&object) == 2); _ZN3sce4Json6Object8iteratorD1Ev(&found); @@ -474,47 +450,56 @@ static void ObjectFind() { _ZN3sce4Json6StringD1Ev(&beta); _ZN3sce4Json6StringD1Ev(&gamma); _ZN3sce4Json6ObjectD1Ev(&object); + } static void ObjectIteratorCopies() { Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + String key1{}, key2{}; _ZN3sce4Json6StringC1EPKc(&key1, "one"); _ZN3sce4Json6StringC1EPKc(&key2, "two"); + Value val1{}, val2{}; _ZN3sce4Json5ValueC1Ev(&val1); _ZN3sce4Json5ValueC1Ev(&val2); + Require(_ZN3sce4Json5Value3setEl(&val1, 10) == 0); Require(_ZN3sce4Json5Value3setEl(&val2, 20) == 0); Pair p1{}, p2{}; _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&p1, &key1, &val1); _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&p2, &key2, &val2); - Iterator ins1{}, ins2{}; + + ObjectIterator ins1{}, ins2{}; Require(_ZN3sce4Json6Object6insertERKNS1_4PairE(&ins1, &object, &p1) == &ins1); Require(_ZN3sce4Json6Object6insertERKNS1_4PairE(&ins2, &object, &p2) == &ins2); _ZN3sce4Json6Object8iteratorD1Ev(&ins1); _ZN3sce4Json6Object8iteratorD1Ev(&ins2); - Iterator it{}; + ObjectIterator it{}; std::memset(&it, 0x5a, sizeof(it)); _ZN3sce4Json6Object8iteratorC1Ev(&it); - Require(it.position == nullptr); - Iterator begin{}, end{}; + Require(it.state != nullptr); + _ZN3sce4Json6Object8iteratorD1Ev(&it); + + ObjectIterator begin{}, end{}; _ZNK3sce4Json6Object5beginEv(&begin, &object); _ZNK3sce4Json6Object3endEv(&end, &object); Require(!_ZNK3sce4Json6Object8iteratoreqERKS2_(&begin, &end)); Require(_ZNK3sce4Json6Object8iteratorneERKS2_(&begin, &end)); - Iterator post{}; + ObjectIterator post{}; Require(_ZN3sce4Json6Object8iteratorppEi(&post, &begin, 0) == &post); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(&post)->key), "one") == 0); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(&begin)->key), "two") == 0); _ZN3sce4Json6Object8iteratorD1Ev(&post); - Iterator assigned{}; + ObjectIterator assigned{}; + _ZN3sce4Json6Object8iteratorC1Ev(&assigned); + Require(_ZN3sce4Json6Object8iteratoraSERKS2_(&assigned, &begin) == &assigned); Require(_ZNK3sce4Json6Object8iteratoreqERKS2_(&assigned, &begin)); Require(!_ZNK3sce4Json6Object8iteratorneERKS2_(&assigned, &begin)); @@ -535,16 +520,20 @@ static void ObjectIteratorCopies() { static void ObjectInsert() { Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "score"); + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + Require(_ZN3sce4Json5Value3setEl(&value, 100) == 0); Pair pair{}; _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &value); - Iterator inserted{}, end{}; + ObjectIterator inserted{}, end{}; _ZNK3sce4Json6Object3endEv(&end, &object); + Require(_ZN3sce4Json6Object6insertERKNS1_4PairE(&inserted, &object, &pair) == &inserted); Require(_ZNK3sce4Json6Object8iteratorneERKS2_(&inserted, &end)); Require(_ZNK3sce4Json6Object4sizeEv(&object) == 1); @@ -552,16 +541,33 @@ static void ObjectInsert() { Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&p->key), "score") == 0); Require(*_ZNK3sce4Json5Value10getIntegerEv(&p->value) == 100); + Require(_ZN3sce4Json5Value3setEl(&pair.value, 200) == 0); + ObjectIterator duplicate{}; + Require(_ZN3sce4Json6Object6insertERKNS1_4PairE(&duplicate, &object, &pair) == &duplicate); + Require(_ZNK3sce4Json6Object8iteratoreqERKS2_(&duplicate, &inserted)); + Require(_ZNK3sce4Json6Object4sizeEv(&object) == 1); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&_ZNK3sce4Json6Object8iteratordeEv(&duplicate)->value) == 100); + _ZN3sce4Json6Object8iteratorD1Ev(&duplicate); + String otherKey{}; _ZN3sce4Json6StringC1EPKc(&otherKey, "other"); + Value otherValue{}; _ZN3sce4Json5ValueC1Ev(&otherValue); + Require(_ZN3sce4Json5Value3setEPKc(&otherValue, "str") == 0); Pair otherPair{}; _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&otherPair, &otherKey, &otherValue); - Iterator otherInserted{}; + + ObjectIterator otherInserted{}; Require(_ZN3sce4Json6Object6insertERKNS1_4PairE(&otherInserted, &object, &otherPair) == &otherInserted); Require(_ZNK3sce4Json6Object4sizeEv(&object) == 2); + ObjectIterator first{}; + _ZNK3sce4Json6Object5beginEv(&first, &object); + + Require(_ZNK3sce4Json6Object8iteratoreqERKS2_(&first, &otherInserted)); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&_ZNK3sce4Json6Object8iteratordeEv(&inserted)->value) == 100); + _ZN3sce4Json6Object8iteratorD1Ev(&first); _ZN3sce4Json6Object8iteratorD1Ev(&otherInserted); _ZN3sce4Json6Object4PairD1Ev(&otherPair); @@ -575,6 +581,79 @@ static void ObjectInsert() { _ZN3sce4Json6ObjectD1Ev(&object); } +static void ObjectKeyOrder() { + Object object{}; + _ZN3sce4Json6ObjectC1Ev(&object); + + for (const char* name : {"z", "\xc3\xa9", "a", "A", ""}) { + String key{}; + _ZN3sce4Json6StringC1EPKc(&key, name); + _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &key); + _ZN3sce4Json6StringD1Ev(&key); + + } + ObjectIterator member{}; + _ZNK3sce4Json6Object5beginEv(&member, &object); + + for (const char* expected : {"", "A", "a", "z", "\xc3\xa9"}) { + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(&member)->key), expected) == 0); + _ZN3sce4Json6Object8iteratorppEv(&member); + + } + _ZN3sce4Json6Object8iteratorD1Ev(&member); + + String key{}; + _ZN3sce4Json6StringC1EPKc(&key, "a"); + + Value* existing = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &key); + _ZN3sce4Json6String6appendEPKcm(&key, "\0suffix", 7); + + Require(_ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &key) == existing); + _ZNK3sce4Json6Object4findERKNS0_6StringE(&member, &object, &key); + + Require(&_ZNK3sce4Json6Object8iteratordeEv(&member)->value == existing); + _ZN3sce4Json6Object8iteratorD1Ev(&member); + + Require(_ZNK3sce4Json6Object4sizeEv(&object) == 5); + _ZN3sce4Json6Object5eraseERKNS0_6StringE(&object, &key); + + Require(_ZNK3sce4Json6Object4sizeEv(&object) == 4); + _ZN3sce4Json6StringD1Ev(&key); + _ZN3sce4Json6ObjectD1Ev(&object); + +} + +static void ParseDuplicateKeys() { + Value root{}; + _ZN3sce4Json5ValueC1Ev(&root); + + for (const char* text : {R"({"a":1,"a":2})", R"({"a":[1,2],"a":2})", R"({"a":{"old":true},"a":2})", R"({"a":1,"a\u0000suffix":2})"}) { + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, std::strlen(text)) == 0); + Require(_ZNK3sce4Json5Value5countEv(&root) == 1); + Require(Serialize(root) == R"({"a":2})"); + } + _ZN3sce4Json5ValueD1Ev(&root); + +} + +static void StringComparison() { + String left{}, right{}; + _ZN3sce4Json6StringC1EPKc(&left, "a"); + _ZN3sce4Json6StringC1EPKc(&right, "a"); + _ZN3sce4Json6String6appendEPKcm(&left, "\0suffix", 7); + + Require(_ZNK3sce4Json6StringeqERKS1_(&left, &right)); + Require(_ZNK3sce4Json6StringeqEPKc(&left, "a")); + Require(_ZNK3sce4Json6String7compareERKS1_(&left, &right) == 0); + Require(_ZNK3sce4Json6String7compareEPKc(&left, "a") == 0); + Require(_ZNK3sce4Json6String7compareEPKc(&left, "z") == -25); + Require(_ZNK3sce4Json6String7compareEPKc(&left, "") == 97); + Require(_ZNK3sce4Json6String7compareEPKc(&left, "\xff") == -158); + _ZN3sce4Json6StringD1Ev(&right); + _ZN3sce4Json6StringD1Ev(&left); + +} + static void StringMethods() { Require(_ZN3sce4Json6String4nposE == static_cast(-1)); const auto text = [](const String& string) { return std::string(_ZNK3sce4Json6String5c_strEv(&string), _ZNK3sce4Json6String6lengthEv(&string)); }; @@ -582,21 +661,65 @@ static void StringMethods() { String suffix{}; _ZN3sce4Json6StringC1EPKc(&base, "a/b/c"); _ZN3sce4Json6StringC1EPKc(&suffix, "-tail"); + Require(_ZNK3sce4Json6String4findEcm(&base, '/', 0) == 1); Require(_ZNK3sce4Json6String4findEcm(&base, '/', 2) == 3); Require(_ZNK3sce4Json6String4findEcm(&base, 'z', 0) == _ZN3sce4Json6String4nposE); Require(_ZNK3sce4Json6String5rfindEcm(&base, '/', _ZN3sce4Json6String4nposE) == 3); Require(_ZNK3sce4Json6String5rfindEcm(&base, '/', 2) == 1); + const std::size_t npos = _ZN3sce4Json6String4nposE; + Require(_ZNK3sce4Json6String2atEm(&base, 0) == 'a'); + Require(_ZNK3sce4Json6String2atEm(&base, 4) == 'c'); + bool atRefused = false; + try { + _ZNK3sce4Json6String2atEm(&base, 5); + + } catch (const std::out_of_range&) { + atRefused = true; + } + Require(atRefused); + Require(_ZNK3sce4Json6String4findEPKcm(&base, "/b", 0) == 1); + Require(_ZNK3sce4Json6String4findEPKcm(&base, "/", 2) == 3); + Require(_ZNK3sce4Json6String4findEPKcm(&base, "", 5) == 5); + Require(_ZNK3sce4Json6String4findEPKcm(&base, "", 6) == npos); + Require(_ZNK3sce4Json6String4findEPKcm(&base, "b/d", 0) == npos); + Require(_ZNK3sce4Json6String4findEPKcmm(&base, "b/d", 0, 2) == 2); + Require(_ZNK3sce4Json6String4findEPKcmm(&base, nullptr, 3, 0) == 3); + Require(_ZNK3sce4Json6String5rfindEPKcm(&base, "/", npos) == 3); + Require(_ZNK3sce4Json6String5rfindEPKcm(&base, "/", 2) == 1); + Require(_ZNK3sce4Json6String5rfindEPKcm(&base, "", npos) == 5); + Require(_ZNK3sce4Json6String5rfindEPKcmm(&base, "a/x", npos, 2) == 0); + Require(_ZNK3sce4Json6String5rfindEPKcmm(&base, "zz", npos, 2) == npos); + String needle{}; + _ZN3sce4Json6StringC1EPKc(&needle, "/c"); + + Require(_ZNK3sce4Json6String4findERKS1_m(&base, &needle, 0) == 3); + Require(_ZNK3sce4Json6String4findERKS1_m(&base, &needle, 4) == npos); + Require(_ZNK3sce4Json6String5rfindERKS1_m(&base, &needle, npos) == 3); + Require(_ZNK3sce4Json6String5rfindERKS1_m(&base, &needle, 2) == npos); + _ZN3sce4Json6StringD1Ev(&needle); + + bool findRefused = false; + try { + _ZNK3sce4Json6String4findEPKcm(&base, nullptr, 0); + + } catch (const std::invalid_argument&) { + findRefused = true; + } + Require(findRefused); String part{}; Require(_ZNK3sce4Json6String6substrEmm(&part, &base, 2, 3) == &part); Require(text(part) == "b/c"); _ZN3sce4Json6StringD1Ev(&part); + Require(_ZNK3sce4Json6String6substrEmm(&part, &base, 5, _ZN3sce4Json6String4nposE) == &part); Require(text(part).empty()); _ZN3sce4Json6StringD1Ev(&part); + bool refused = false; try { _ZNK3sce4Json6String6substrEmm(&part, &base, 6, 1); + } catch (const std::out_of_range&) { refused = true; } @@ -605,10 +728,12 @@ static void StringMethods() { Require(_ZNK3sce4Json6String6lengthEv(&base) == 5); String match{}; _ZN3sce4Json6StringC1EPKc(&match, "a/b/c"); + Require(_ZNK3sce4Json6StringeqERKS1_(&base, &match)); Require(_ZNK3sce4Json6String7compareERKS1_(&base, &match) == 0); _ZN3sce4Json6StringD1Ev(&match); _ZN3sce4Json6String7reserveEm(&base, 64); + Require(_ZNK3sce4Json6String8capacityEv(&base) >= 64); Require(_ZN3sce4Json6String6appendERKS1_(&base, &suffix) == &base); Require(_ZN3sce4Json6String6appendEPKc(&base, "!") == &base); @@ -617,57 +742,91 @@ static void StringMethods() { Require(_ZN3sce4Json6StringpLEh(&base, '?') == &base); Require(text(base) == "a/b/c-tail!12+?"); _ZN3sce4Json6String6resizeEm(&base, 5); + Require(_ZNK3sce4Json6String4sizeEv(&base) == 5); Require(text(base) == "a/b/c"); _ZN3sce4Json6String6appendERKS1_(&base, &base); + Require(text(base) == "a/b/ca/b/c"); _ZN3sce4Json6String5clearEv(&base); + Require(text(base).empty()); _ZN3sce4Json6StringD1Ev(&suffix); _ZN3sce4Json6StringD1Ev(&base); + } static void ValueSwap() { Value v1{}, v2{}; _ZN3sce4Json5ValueC1Ev(&v1); _ZN3sce4Json5ValueC1Ev(&v2); + Require(_ZN3sce4Json5Value3setEl(&v1, 123) == 0); Require(_ZN3sce4Json5Value3setEPKc(&v2, "hello") == 0); _ZN3sce4Json5Value4swapERS1_(&v1, &v2); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&v2) == 123); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&v1)), "hello") == 0); + Require(_ZN3sce4Json5Value3setEl(&v1, -456) == 0); + const auto* firstStorage = _ZNK3sce4Json5Value10getIntegerEv(&v1); + const auto* secondStorage = _ZNK3sce4Json5Value10getIntegerEv(&v2); + _ZN3sce4Json5Value4swapERS1_(&v1, &v2); + + Require(_ZNK3sce4Json5Value10getIntegerEv(&v1) == firstStorage && *firstStorage == 123); + Require(_ZNK3sce4Json5Value10getIntegerEv(&v2) == secondStorage && *secondStorage == -456); + _ZN3sce4Json5Value4swapERS1_(&v1, &v1); + + Require(*_ZNK3sce4Json5Value10getIntegerEv(&v1) == 123); _ZN3sce4Json5ValueD1Ev(&v1); _ZN3sce4Json5ValueD1Ev(&v2); + } static void ArrayIteratorAdvance() { Array array{}; _ZN3sce4Json5ArrayC1Ev(&array); + for (int i = 0; i < 5; ++i) { Value val{}; _ZN3sce4Json5ValueC1Ev(&val); _ZN3sce4Json5Value3setEl(&val, i * 10); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &val); _ZN3sce4Json5ValueD1Ev(&val); + } - Iterator begin{}; + ArrayIterator begin{}; _ZNK3sce4Json5Array5beginEv(&begin, &array); - Iterator copy{}; + + ArrayIterator copy{}; _ZN3sce4Json5Array8iteratorC1ERKS2_(©, &begin); + Require(_ZN3sce4Json5Array8iterator7advanceEm(©, 3) == ©); Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(©)) == 30); + ArrayIterator end{}; + _ZNK3sce4Json5Array3endEv(&end, &array); + _ZN3sce4Json5Array8iterator7advanceEm(©, std::numeric_limits::max()); + + Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(©, &end)); + _ZN3sce4Json5Array8iteratorppEv(©); + + Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(©, &end)); + Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(&begin)) == 0); + _ZN3sce4Json5Array8iteratorD1Ev(&end); _ZN3sce4Json5Array8iteratorD1Ev(&begin); _ZN3sce4Json5Array8iteratorD1Ev(©); _ZN3sce4Json5ArrayD1Ev(&array); + } static void ObjectOperations() { Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + String k1{}, k2{}, k3{}; _ZN3sce4Json6StringC1EPKc(&k1, "alpha"); _ZN3sce4Json6StringC1EPKc(&k2, "beta"); _ZN3sce4Json6StringC1EPKc(&k3, "gamma"); + Value v1{}, v2{}, v3{}; _ZN3sce4Json5ValueC1Ev(&v1); _ZN3sce4Json5ValueC1Ev(&v2); @@ -675,35 +834,53 @@ static void ObjectOperations() { _ZN3sce4Json5Value3setEl(&v1, 1); _ZN3sce4Json5Value3setEl(&v2, 2); _ZN3sce4Json5Value3setEl(&v3, 3); + Pair p1{}, p2{}, p3{}; _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&p1, &k1, &v1); _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&p2, &k2, &v2); _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&p3, &k3, &v3); - Iterator ins{}; - _ZN3sce4Json6Object6insertERKNS1_4PairE(&ins, &object, &p1); + + ObjectIterator ins{}; + _ZN3sce4Json6Object6insertERKNS1_4PairE(&ins, &object, &p3); _ZN3sce4Json6Object8iteratorD1Ev(&ins); _ZN3sce4Json6Object6insertERKNS1_4PairE(&ins, &object, &p2); _ZN3sce4Json6Object8iteratorD1Ev(&ins); - _ZN3sce4Json6Object6insertERKNS1_4PairE(&ins, &object, &p3); + _ZN3sce4Json6Object6insertERKNS1_4PairE(&ins, &object, &p1); _ZN3sce4Json6Object8iteratorD1Ev(&ins); - Iterator begin{}; + ObjectIterator begin{}; _ZNK3sce4Json6Object5beginEv(&begin, &object); - Iterator copy{}; + + ObjectIterator copy{}; _ZN3sce4Json6Object8iteratorC1ERKS2_(©, &begin); + Require(_ZN3sce4Json6Object8iterator7advanceEm(©, 2) == ©); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(©)->key), "gamma") == 0); + _ZN3sce4Json6Object8iterator7advanceEm(©, 1); + + bool endRejected = false; + try { + _ZN3sce4Json6Object8iterator7advanceEm(©, 1); + + } catch (const std::out_of_range&) { + endRejected = true; + } + Require(endRejected); _ZN3sce4Json6Object8iteratorD1Ev(&begin); _ZN3sce4Json6Object8iteratorD1Ev(©); Require(_ZNK3sce4Json6Object4sizeEv(&object) == 3); - Require(_ZN3sce4Json6Object5eraseERKNS0_6StringE(&object, &k2) == 1); - Require(_ZNK3sce4Json6Object4sizeEv(&object) == 2); - Require(_ZN3sce4Json6Object5eraseERKNS0_6StringE(&object, &k2) == 0); + _ZN3sce4Json6Object5eraseERKNS0_6StringE(&object, &k2); - Iterator findResult{}, end{}; + Require(_ZNK3sce4Json6Object4sizeEv(&object) == 2); + _ZN3sce4Json6Object5eraseERKNS0_6StringE(&object, &k2); + + Require(_ZNK3sce4Json6Object4sizeEv(&object) == 2); + + ObjectIterator findResult{}, end{}; _ZNK3sce4Json6Object4findERKNS0_6StringE(&findResult, &object, &k2); _ZNK3sce4Json6Object3endEv(&end, &object); + Require(_ZNK3sce4Json6Object8iteratoreqERKS2_(&findResult, &end)); _ZN3sce4Json6Object8iteratorD1Ev(&findResult); _ZN3sce4Json6Object8iteratorD1Ev(&end); @@ -732,10 +909,12 @@ static void NullAccess(SetNullAccessCallback setCallback) { int context = 0; Require(setCallback(initializer, OnNullAccess, &context) == 0); _ZN3sce4Json5ValueC1Ev(&fallback); + Require(_ZN3sce4Json5Value3setEl(&fallback, 99) == 0); Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + const char text[] = "{\"present\":\"text\"}"; Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); Require(&Member(root, "missing") == &fallback); @@ -752,26 +931,40 @@ static void NullAccess(SetNullAccessCallback setCallback) { Require(callbackCalls == 4); _ZN3sce4Json5ValueD1Ev(&root); _ZN3sce4Json5ValueD1Ev(&fallback); + Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); } static void ValueAccess() { for (std::int32_t type = TypeNull; type <= TypeObject; ++type) { Value typed{}; - _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&typed, type); + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&typed, static_cast(type)); + Require(_ZNK3sce4Json5Value7getTypeEv(&typed) == type); Require(_ZNK3sce4Json5Value5countEv(&typed) == 0); _ZN3sce4Json5ValueD1Ev(&typed); + } Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + const char text[] = "{\"list\":[10,20,30],\"name\":\"value\",\"flag\":true}"; Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); Require(_ZNK3sce4Json6Object4sizeEv(_ZNK3sce4Json5Value9getObjectEv(&root)) == 3); + ObjectIterator member{}; + _ZNK3sce4Json6Object5beginEv(&member, _ZNK3sce4Json5Value9getObjectEv(&root)); + + for (const char* expected : {"flag", "list", "name"}) { + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(&member)->key), expected) == 0); + _ZN3sce4Json6Object8iteratorppEv(&member); + + } + _ZN3sce4Json6Object8iteratorD1Ev(&member); String key{}; _ZN3sce4Json6StringC1EPKc(&key, "list"); + const Value* list = _ZNK3sce4Json5Value8getValueERKNS0_6StringE(&root, &key); Require(_ZNK3sce4Json5Value7getTypeEv(list) == TypeArray); Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Value8getValueEm(list, 1)) == 20); @@ -789,23 +982,26 @@ static void ValueAccess() { String missing{}; _ZN3sce4Json6StringC1EPKc(&missing, "missing"); + Require(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5Value8getValueERKNS0_6StringE(&root, &missing)) == TypeNull); _ZN3sce4Json6StringD1Ev(&missing); _ZN3sce4Json5ValueD1Ev(&root); alignas(16) std::uint8_t parameter[40]; std::memset(parameter, 0xff, sizeof(parameter)); - _ZN3sce4Json14InitParameter2C1Ev(parameter); + _ZN3sce4Json14InitParameter2C1Ev(reinterpret_cast(parameter)); + int allocator = 0; int userData = 0; - _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(parameter, &allocator, &userData); - _ZN3sce4Json14InitParameter217setFileBufferSizeEm(parameter, 4096); + _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(reinterpret_cast(parameter), &allocator, &userData); + _ZN3sce4Json14InitParameter217setFileBufferSizeEm(reinterpret_cast(parameter), 4096); + void* stored[3]{}; std::memcpy(stored, parameter, sizeof(stored)); Require(stored[0] == &allocator && stored[1] == &userData && reinterpret_cast(stored[2]) == 4096); alignas(16) std::uint8_t initializer[16]{}; Require(_ZN3sce4Json11InitializerC1Ev(initializer) == 0); - Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, parameter) == 0); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, reinterpret_cast(parameter)) == 0); Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0); Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); } @@ -813,11 +1009,13 @@ static void ValueAccess() { static void InitParameter2BaseConstructor() { alignas(16) std::uint8_t parameter[40]; std::memset(parameter, 0xff, sizeof(parameter)); - _ZN3sce4Json14InitParameter2C1Ev(parameter); + _ZN3sce4Json14InitParameter2C1Ev(reinterpret_cast(parameter)); + alignas(16) std::uint8_t baseStorage[72]; std::memset(baseStorage, 0xff, sizeof(baseStorage)); auto* baseParameter = baseStorage + 16; - _ZN3sce4Json14InitParameter2C2Ev(baseParameter); + _ZN3sce4Json14InitParameter2C2Ev(reinterpret_cast(baseParameter)); + Require(std::memcmp(parameter, baseParameter, sizeof(parameter)) == 0); void* defaultAllocator; void* defaultUserData; @@ -828,8 +1026,9 @@ static void InitParameter2BaseConstructor() { Require(defaultAllocator == nullptr && defaultUserData == nullptr && defaultFileBufferSize == 0); int allocator = 0; int userData = 0; - _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(baseParameter, &allocator, &userData); - _ZN3sce4Json14InitParameter217setFileBufferSizeEm(baseParameter, 4096); + _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(reinterpret_cast(baseParameter), &allocator, &userData); + _ZN3sce4Json14InitParameter217setFileBufferSizeEm(reinterpret_cast(baseParameter), 4096); + void* storedAllocator; void* storedUserData; std::size_t storedFileBufferSize; @@ -842,22 +1041,15 @@ static void InitParameter2BaseConstructor() { Require(baseStorage[i] == 0xff); alignas(16) std::uint8_t initializer[16]{}; Require(_ZN3sce4Json11InitializerC1Ev(initializer) == 0); - Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, baseParameter) == 0); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, reinterpret_cast(baseParameter)) == 0); Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0); Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); } -struct StoredInitParameter2 { - void* allocator; - void* userData; - std::size_t fileBufferSize; - std::int32_t specialFloatFormatType; - std::uint32_t reserved[3]; -}; -static_assert(sizeof(StoredInitParameter2) == 40); +static_assert(sizeof(InitParameter2) == 40); -static StoredInitParameter2 Stored(const std::uint8_t* parameter) { - StoredInitParameter2 stored{}; +static InitParameter2 Stored(const std::uint8_t* parameter) { + InitParameter2 stored{}; std::memcpy(&stored, parameter, sizeof(stored)); return stored; } @@ -869,32 +1061,33 @@ static bool Untouched(const std::uint8_t* parameter, std::size_t from) { } static void InitParameters() { - using Constructor = void (APS5_VABI*)(void*); + using Constructor = decltype(&_ZN3sce4Json14InitParameter2C1Ev); for (Constructor construct : {_ZN3sce4Json14InitParameter2C1Ev, _ZN3sce4Json14InitParameter2C2Ev}) { alignas(16) std::uint8_t parameter[40]; std::memset(parameter, 0xff, sizeof(parameter)); - construct(parameter); - StoredInitParameter2 stored = Stored(parameter); + construct(reinterpret_cast(parameter)); + InitParameter2 stored = Stored(parameter); Require(stored.allocator == nullptr && stored.userData == nullptr && stored.fileBufferSize == 0); - Require(stored.specialFloatFormatType == 0); + Require(stored.specialFloatFormatType == 2); Require(stored.reserved[0] == 0 && stored.reserved[1] == 0 && stored.reserved[2] == 0); int allocParam = 0; int userData = 0; - _ZN3sce4Json14InitParameter217setFileBufferSizeEm(parameter, 8192); - _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(parameter, 2); - _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(parameter, &allocParam, &userData); + _ZN3sce4Json14InitParameter217setFileBufferSizeEm(reinterpret_cast(parameter), 8192); + _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(reinterpret_cast(parameter), 2); + _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(reinterpret_cast(parameter), &allocParam, &userData); + stored = Stored(parameter); Require(stored.allocator == &allocParam && stored.userData == &userData && stored.fileBufferSize == 8192); Require(stored.specialFloatFormatType == 2); Require(stored.reserved[0] == 0 && stored.reserved[1] == 0 && stored.reserved[2] == 0); - _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(parameter, 0); + _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(reinterpret_cast(parameter), 0); Require(Stored(parameter).specialFloatFormatType == 0 && Stored(parameter).fileBufferSize == 8192); alignas(16) std::uint8_t initializer[16]{}; Require(_ZN3sce4Json11InitializerC1Ev(initializer) == 0); - Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, parameter) == 0); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, reinterpret_cast(parameter)) == 0); Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0); Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); } @@ -903,7 +1096,8 @@ static void InitParameters() { std::memset(rttiParameter, 0xff, sizeof(rttiParameter)); int allocParam = 0; int userData = 0; - _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(rttiParameter, &allocParam, &userData, 4096); + _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(reinterpret_cast(rttiParameter), &allocParam, &userData, 4096); + void* rttiStored[3]{}; std::memcpy(rttiStored, rttiParameter, sizeof(rttiStored)); Require(rttiStored[0] == &allocParam && rttiStored[1] == &userData && reinterpret_cast(rttiStored[2]) == 4096); @@ -913,30 +1107,38 @@ static void InitParameters() { static void ValueClear() { Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeNull); _ZN3sce4Json5Value5clearEv(&value); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeNull); Require(_ZN3sce4Json5Value3setEl(&value, 12345) == 0); Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeInteger); _ZN3sce4Json5Value5clearEv(&value); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeNull); Require(_ZN3sce4Json5Value3setEPKc(&value, "test string") == 0); Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeString); _ZN3sce4Json5Value5clearEv(&value); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeNull); Object obj{}; _ZN3sce4Json6ObjectC1Ev(&obj); + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "prop"); + Value* item = _ZN3sce4Json6ObjectixERKNS0_6StringE(&obj, &key); _ZN3sce4Json5Value3setEl(item, 999); _ZN3sce4Json5ValueD1Ev(&value); _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&value, &obj); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeObject); _ZN3sce4Json5Value5clearEv(&value); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeNull); _ZN3sce4Json6StringD1Ev(&key); _ZN3sce4Json6ObjectD1Ev(&obj); @@ -952,6 +1154,8 @@ struct ReadState { const char* text; std::size_t position; int calls; + int successStatus = 0; + int failureStatus = -1; }; static int APS5_VABI ReadNext(char& ch, void* context) { @@ -959,24 +1163,65 @@ static int APS5_VABI ReadNext(char& ch, void* context) { ++state->calls; if (state->text[state->position] == '\0') { ch = '\0'; - return -1; + return state->failureStatus; } ch = state->text[state->position++]; - return 0; + return state->successStatus; +} + +static void IntegerRepresentation() { + Value value{}; + _ZN3sce4Json5ValueC1Ev(&value); + + for (const char* text : {"0", "1", "9223372036854775807", "9223372036854775808", "18446744073709551615"}) { + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, text, std::strlen(text)) == 0); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeUInteger); + Require(Serialize(value) == text); + } + Require(*_ZNK3sce4Json5Value10getIntegerEv(&value) == -1); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeUInteger); + Require(_ZN3sce4Json5Value3setEl(&value, -1) == 0); + Require(*_ZNK3sce4Json5Value11getUIntegerEv(&value) == std::numeric_limits::max()); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeInteger); + for (const char* text : {"-0", "-1", "-9223372036854775808"}) { + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, text, std::strlen(text)) == 0); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeInteger); + } + Require(*_ZNK3sce4Json5Value11getUIntegerEv(&value) == (std::uint64_t{1} << 63)); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeInteger); + _ZN3sce4Json5ValueD1Ev(&value); + } static void StreamParse() { Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + ReadState valid{"{\"list\": [1, 2.5, true]}", 0, 0}; Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&root, ReadNext, &valid) == 0); - Require(valid.calls == static_cast(std::strlen(valid.text)) + 1); + Require(valid.calls == static_cast(std::strlen(valid.text))); Require(_ZNK3sce4Json5Value7getTypeEv(&root) == TypeObject); ReadState invalid{"{\"list\": [1, 2.5", 0, 0}; Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&root, ReadNext, &invalid) < 0); ReadState trailing{"[1] 2", 0, 0}; - Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&root, ReadNext, &trailing) < 0); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&root, ReadNext, &trailing) == 0); + Require(trailing.position == 3); + Require(Serialize(root) == "[1]"); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&root, ReadNext, &trailing) == 0); + Require(Serialize(root) == "2"); + ReadState positive{"true false", 0, 0, 7}; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&root, ReadNext, &positive) == 0); + Require(positive.position == 4 && positive.calls == 4); + Require(Serialize(root) == "true"); + ReadState failed{"tr", 0, 0, 0, -123456}; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&root, ReadNext, &failed) == -123456); + Require(failed.calls == 3); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, "{} trailing", 11) == 0); + Require(Serialize(root) == "{}"); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, "01", 2) == 0); + Require(Serialize(root) == "1"); _ZN3sce4Json5ValueD1Ev(&root); + } static void StaticNullAccess() { @@ -987,10 +1232,12 @@ static void StaticNullAccess() { int context = 0; _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(OnNullAccess, &context); _ZN3sce4Json5ValueC1Ev(&fallback); + Require(_ZN3sce4Json5Value3setEl(&fallback, 99) == 0); Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + const char text[] = "{\"present\":\"text\"}"; Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); Require(&Member(root, "missing") == &fallback); @@ -1004,6 +1251,7 @@ static void StaticNullAccess() { Require(callbackCalls == 2); _ZN3sce4Json5ValueD1Ev(&root); _ZN3sce4Json5ValueD1Ev(&fallback); + } static Value replacement{}; @@ -1013,7 +1261,7 @@ static const Value* mismatchValue = nullptr; static const Value* mismatchSecond = nullptr; static void* mismatchContext = nullptr; -static const Value& APS5_VABI OnTypeMismatch(std::int32_t requested, const Value* value, const Value* second, void* context) { +static const Value& APS5_VABI OnTypeMismatch(ValueType requested, const Value* value, const Value* second, void* context) { ++mismatchCalls; mismatchRequested = requested; mismatchValue = value; @@ -1022,17 +1270,19 @@ static const Value& APS5_VABI OnTypeMismatch(std::int32_t requested, const Value return replacement; } -static void RequireMismatch(int calls, std::int32_t requested, const Value* value, void* context) { +static void RequireMismatch(int calls, ValueType requested, const Value* value, void* context) { Require(mismatchCalls == calls && mismatchRequested == requested && mismatchValue == value); Require(mismatchSecond == nullptr && mismatchContext == context); } static void ValueTypeMismatchHandler() { _ZN3sce4Json5ValueC1Ev(&replacement); + Require(_ZN3sce4Json5Value3setEl(&replacement, 42) == 0); int context = 0; Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, OnTypeMismatch, &context) == 0); const char text[] = "{\"name\":\"text\",\"none\":null,\"list\":[true]}"; Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); @@ -1054,6 +1304,7 @@ static void ValueTypeMismatchHandler() { Value later{}; _ZN3sce4Json5ValueC1Ev(&later); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&later, text, sizeof(text) - 1) == 0); Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(later, "name")) == 0); Require(mismatchCalls == 4); @@ -1064,6 +1315,7 @@ static void ValueTypeMismatchHandler() { Value other{}; _ZN3sce4Json5ValueC1Ev(&other); + Require(_ZN3sce4Json5Value3setEPKc(&other, "other") == 0); Require(_ZN3sce4Json5ValueaSERKS1_(&root, &other) == &root); Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); @@ -1075,11 +1327,13 @@ static void ValueTypeMismatchHandler() { Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); RequireMismatch(7, TypeInteger, &root, &context); _ZN3sce4Json5Value5clearEv(&root); + Require(_ZN3sce4Json5Value3setEPKc(&root, "cleared") == 0); Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); RequireMismatch(8, TypeInteger, &root, &context); Value copy{}; _ZN3sce4Json5ValueC1ERKS1_(©, &root); + Require(*_ZNK3sce4Json5Value10getIntegerEv(©) == 0); Require(mismatchCalls == 8); @@ -1091,6 +1345,7 @@ static void ValueTypeMismatchHandler() { _ZN3sce4Json5ValueD1Ev(&later); _ZN3sce4Json5ValueD1Ev(&root); _ZN3sce4Json5ValueD1Ev(&replacement); + } static int failureCalls = 0; @@ -1112,6 +1367,7 @@ static bool ThrowsUnverified(const TAccess& access) { static void ValueElementAccessFailureHandler() { Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + const char text[] = "{\"list\":[1],\"key\":2}"; Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); int context = 0; @@ -1122,6 +1378,7 @@ static void ValueElementAccessFailureHandler() { Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Value8getValueEm(&list, 0)) == 1); String missing{}; _ZN3sce4Json6StringC1EPKc(&missing, "missing"); + Require(ThrowsUnverified([&] { _ZNK3sce4Json5ValueixEPKc(&root, "missing"); })); Require(ThrowsUnverified([&] { _ZNK3sce4Json5Value8getValueERKNS0_6StringE(&root, &missing); })); Require(ThrowsUnverified([&] { _ZNK3sce4Json5ValueixEm(&root, 0); })); @@ -1133,6 +1390,7 @@ static void ValueElementAccessFailureHandler() { Require(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5Value8getValueEm(&list, 1)) == TypeNull); _ZN3sce4Json6StringD1Ev(&missing); _ZN3sce4Json5ValueD1Ev(&root); + } static int specialFloatCalls = 0; @@ -1140,66 +1398,191 @@ static int specialFloatCalls = 0; static Value* APS5_VABI OnSpecialFloat(Value* result, std::int32_t, double, const Value*, void*, bool*) { ++specialFloatCalls; _ZN3sce4Json5ValueC1Ev(result); + return result; } static void ValueSpecialFloatHandler() { Value real{}; _ZN3sce4Json5ValueC1Ed(&real, std::numeric_limits::quiet_NaN()); + int context = 0; Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&real, OnSpecialFloat, &context) == 0); String out{}; _ZN3sce4Json6StringC1Ev(&out); + Require(ThrowsUnverified([&] { _ZN3sce4Json5Value9serializeERNS0_6StringE(&real, &out); })); Require(_ZN3sce4Json5Value3setEd(&real, std::numeric_limits::infinity()) == 0); Require(ThrowsUnverified([&] { _ZN3sce4Json5Value9serializeERNS0_6StringE(&real, &out); })); _ZN3sce4Json6StringD1Ev(&out); + Require(_ZN3sce4Json5Value3setEd(&real, 2.5) == 0); Require(Serialize(real) == "2.5"); Require(specialFloatCalls == 0); _ZN3sce4Json5ValueD1Ev(&real); + } static void ValueOperatorBool() { Value nullVal{}; _ZN3sce4Json5ValueC1Ev(&nullVal); + Require(!_ZNK3sce4Json5ValuecvbEv(&nullVal)); _ZN3sce4Json5ValueD1Ev(&nullVal); Value typedNull{}; _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&typedNull, TypeNull); + Require(!_ZNK3sce4Json5ValuecvbEv(&typedNull)); _ZN3sce4Json5ValueD1Ev(&typedNull); Value boolFalse{}; _ZN3sce4Json5ValueC1Eb(&boolFalse, false); + Require(!_ZNK3sce4Json5ValuecvbEv(&boolFalse)); _ZN3sce4Json5ValueD1Ev(&boolFalse); Value boolTrue{}; _ZN3sce4Json5ValueC1Eb(&boolTrue, true); + Require(_ZNK3sce4Json5ValuecvbEv(&boolTrue)); _ZN3sce4Json5ValueD1Ev(&boolTrue); + Value scalar{}; + _ZN3sce4Json5ValueC1Ev(&scalar); + + for (std::int64_t value : {std::int64_t{0}, std::int64_t{1}, std::int64_t{-1}}) { + Require(_ZN3sce4Json5Value3setEl(&scalar, value) == 0); + Require(_ZNK3sce4Json5ValuecvbEv(&scalar) == (value != 0)); + } + for (std::uint64_t value : {std::uint64_t{0}, std::uint64_t{1}, std::uint64_t{0xffffffffffffffff}}) { + Require(_ZN3sce4Json5Value3setEm(&scalar, value) == 0); + Require(_ZNK3sce4Json5ValuecvbEv(&scalar) == (value != 0)); + } + for (double value : {0.0, -0.0, 1.0, -1.0, std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}) { + Require(_ZN3sce4Json5Value3setEd(&scalar, value) == 0); + Require(_ZNK3sce4Json5ValuecvbEv(&scalar) == (value != 0.0)); + } + for (const char* text : {"", "0", "false", " "}) { + Require(_ZN3sce4Json5Value3setEPKc(&scalar, text) == 0); + Require(_ZNK3sce4Json5ValuecvbEv(&scalar) == (*text != 0)); + } + _ZN3sce4Json5ValueD1Ev(&scalar); + + for (std::int32_t type : {TypeArray, TypeObject}) { + Value container{}; + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&container, static_cast(type)); + + Require(_ZNK3sce4Json5ValuecvbEv(&container)); + _ZN3sce4Json5ValueD1Ev(&container); + + } + Value cleared{}; _ZN3sce4Json5ValueC1Eb(&cleared, true); + Require(_ZNK3sce4Json5ValuecvbEv(&cleared)); _ZN3sce4Json5Value5clearEv(&cleared); + Require(!_ZNK3sce4Json5ValuecvbEv(&cleared)); _ZN3sce4Json5ValueD1Ev(&cleared); + } -int main() { +static int NumericLocale() { + const std::string previous = std::setlocale(LC_NUMERIC, nullptr); + const char* selected = nullptr; + for (const char* name : {"xx_COMMA", "de_DE.UTF-8", "de_DE.utf8", "de-DE", "German_Germany.1252", "fr_FR.UTF-8"}) { + if (std::setlocale(LC_NUMERIC, name) && std::strcmp(std::localeconv()->decimal_point, ",") == 0) { + selected = name; + break; + } + } + if (!selected) { + std::setlocale(LC_NUMERIC, previous.c_str()); + std::fprintf(stderr, "No decimal-comma numeric locale is installed\n"); + return 77; + } + std::printf("JSON numeric locale: %s\n", selected); + int failures = 0; + const auto check = [&](bool success, const char* operation, const char* actual) { + if (!success) { + std::fprintf(stderr, "%s failed: %s\n", operation, actual); + ++failures; + } + }; + for (const auto number : {1.5, -0.125, 1.25e30, std::numeric_limits::max(), + std::numeric_limits::min(), std::numeric_limits::denorm_min()}) { + Value value{}; + _ZN3sce4Json5ValueC1Ed(&value, number); + + const auto text = Serialize(value); + check(text.find(',') == std::string::npos, "Decimal-point serialization", text.c_str()); + Value parsed{}; + _ZN3sce4Json5ValueC1Ev(&parsed); + + const int result = _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&parsed, text.c_str(), text.size()); + check(result == 0 && *_ZNK3sce4Json5Value7getRealEv(&parsed) == number, "Numeric round trip", text.c_str()); + _ZN3sce4Json5ValueD1Ev(&parsed); + _ZN3sce4Json5ValueD1Ev(&value); + + } + for (const auto [text, number] : {std::pair{"1.5", 1.5}, {"-1.25e2", -125.0}, + {"4.9406564584124654e-324", std::numeric_limits::denorm_min()}, + {"2.4703282292062327e-324", 0.0}, + {"2.4703282292062328e-324", std::numeric_limits::denorm_min()}, + {"18446744073709551616", 18446744073709551616.0}, + {"1e-999", 0.0}, {"-1e-999", -0.0}}) { + Value value{}; + _ZN3sce4Json5ValueC1Ev(&value); + + const int result = _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, text, std::strlen(text)); + const auto parsed = *_ZNK3sce4Json5Value7getRealEv(&value); + check(result == 0 && parsed == number && std::signbit(parsed) == std::signbit(number), "Decimal-point parsing", text); + _ZN3sce4Json5ValueD1Ev(&value); + + } + Value root{}; + _ZN3sce4Json5ValueC1Ev(&root); + + constexpr char document[] = "{\"list\":[2.5,-0.125],\"value\":1.5}"; + check(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, document, sizeof(document) - 1) == 0, + "Nested numeric parsing", document); + const auto serialized = Serialize(root); + check(serialized == document, "Nested numeric serialization", serialized.c_str()); + check(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, "1,5", 3) == 0 && _ZNK3sce4Json5Value7getTypeEv(&root) == TypeUInteger && + *_ZNK3sce4Json5Value11getUIntegerEv(&root) == 1, "Stop at a decimal comma", "1,5"); + bool overflow = false; + try { + _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, "1e309", 5); + + } catch (const std::runtime_error&) { + overflow = true; + } + check(overflow, "Retain overflow rejection", "1e309"); + _ZN3sce4Json5ValueD1Ev(&root); + + check(std::strcmp(std::localeconv()->decimal_point, ",") == 0, "Preserve host locale", selected); + std::setlocale(LC_NUMERIC, previous.c_str()); + return failures ? 1 : 0; +} + +int main(int argc, char** argv) { + if (argc == 2 && std::strcmp(argv[1], "--numeric-locale") == 0) return NumericLocale(); ParseAndRoundTrip(); NestingDepth(); ObjectsAndArrays(); + ArrayAssignment(); ObjectFind(); ObjectPair(); ObjectInsert(); + ObjectKeyOrder(); ArrayIteratorCopies(); ObjectIteratorCopies(); AllocatorNotifyError(); StringMethods(); + StringComparison(); + ParseDuplicateKeys(); ValueSwap(); ArrayIteratorAdvance(); ObjectOperations(); @@ -1214,5 +1597,6 @@ int main() { InitParameters(); ValueClear(); ValueOperatorBool(); + IntegerRepresentation(); StreamParse(); } diff --git a/core/libs/tests/GuestJxr.cpp b/core/libs/tests/GuestJxr.cpp new file mode 100644 index 0000000000..6c295e4822 --- /dev/null +++ b/core/libs/tests/GuestJxr.cpp @@ -0,0 +1,75 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceJxr/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +void* lastFreed = nullptr; +int frees = 0; + +void* APS5_VABI Allocate(std::size_t size) { return std::malloc(size); } +void APS5_VABI Free(void* pointer) { + if (pointer != nullptr) { + lastFreed = pointer; + ++frees; + } + std::free(pointer); +} +void* APS5_VABI Reallocate(void* pointer, std::size_t size) { return std::realloc(pointer, size); } +void* APS5_VABI Calloc(std::size_t count, std::size_t size) { return std::calloc(count, size); } +void* APS5_VABI Align(std::size_t, std::size_t size) { return std::malloc(size); } +void* APS5_VABI Realign(void* pointer, std::size_t size, std::size_t) { return std::realloc(pointer, size); } +int APS5_VABI PosixAlign(void** pointer, std::size_t, std::size_t size) { + *pointer = std::malloc(size); + return *pointer ? 0 : 12; +} + +void InitializeHeap() { + alignas(16) static std::array process{}; + alignas(16) static std::array libc{}; + alignas(16) static std::array replacement{}; + const auto write = [](auto& data, std::size_t offset, auto value) { + std::memcpy(data.data() + offset, &value, sizeof(value)); + }; + write(process, 0, std::uint64_t{0x40}); + write(process, 8, std::uint32_t{0x4942524f}); + write(process, 0x38, libc.data()); + write(libc, 0, std::uint64_t{0x38}); + write(libc, 0x30, replacement.data()); + write(replacement, 0, std::uint64_t{0x78}); + write(replacement, 8, std::uint64_t{2}); + write(replacement, 0x20, reinterpret_cast(&Allocate)); + write(replacement, 0x28, reinterpret_cast(&Free)); + write(replacement, 0x30, reinterpret_cast(&Calloc)); + write(replacement, 0x38, reinterpret_cast(&Reallocate)); + write(replacement, 0x40, reinterpret_cast(&Align)); + write(replacement, 0x48, reinterpret_cast(&Realign)); + write(replacement, 0x50, reinterpret_cast(&PosixAlign)); + ApplicationHeapInitialize_nid_no_patch(process.data()); +} + +} + +int main() { + InitializeHeap(); + void* memory = malloc_nid_postfix(64); + Require(memory != nullptr); + void* const allocated = memory; + Require(PKFree_nid_postfix(&memory) == 0); + Require(memory == nullptr); + Require(frees == 1 && lastFreed == allocated); + Require(PKFree_nid_postfix(&memory) == 0); + Require(memory == nullptr); + Require(frees == 1); + Require(PKFree_nid_postfix(nullptr) == 0); + Require(frees == 1); + return 0; +} diff --git a/core/libs/tests/GuestKernelAppArchive.cpp b/core/libs/tests/GuestKernelAppArchive.cpp index 2eb8aae01f..f09c256ba8 100644 --- a/core/libs/tests/GuestKernelAppArchive.cpp +++ b/core/libs/tests/GuestKernelAppArchive.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "AppArchiveFixture.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -16,48 +19,10 @@ #include #include -struct KernelIovec { - void* base; - std::size_t length; -}; - extern "C" { -int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode); -int APS5_VABI sceKernelClose(int d); -std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes); -std::int64_t APS5_VABI sceKernelWrite(int d, const void* buf, std::size_t nbytes); -std::int64_t APS5_VABI sceKernelPread(int d, void* buf, std::size_t nbytes, std::int64_t offset); -std::int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* iov, int iovcnt); -std::int64_t APS5_VABI sceKernelPreadv(int d, const KernelIovec* iov, int iovcnt, std::int64_t offset); -std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence); -int APS5_VABI sceKernelStat(const char* path, FileStat* sb); -int APS5_VABI sceKernelFstat(int d, FileStat* sb); -int APS5_VABI sceKernelCheckReachability(const char* path); -int APS5_VABI dup_nid_postfix(int d); -int APS5_VABI dup2_nid_postfix(int from, int to); -int APS5_VABI close_nid_postfix(int d); -std::int64_t APS5_VABI pread_nid_postfix(int d, void* buf, std::size_t nbytes, std::int64_t offset); -std::int64_t APS5_VABI lseek_nid_postfix(int d, std::int64_t offset, int whence); -int APS5_VABI fcntl_nid_postfix(int d, int command, ...); -int APS5_VABI ioctl_nid_postfix(int d, std::uint64_t request, void* argument); -void* APS5_VABI opendir_nid_postfix(const char* path); -void* APS5_VABI fdopen_nid_postfix(int d, const char* mode); -int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, std::int32_t* id); -int APS5_VABI sceKernelAioWaitRequest(std::int32_t id, std::int32_t* state, std::uint32_t* usec); -int APS5_VABI sceKernelAioDeleteRequest(std::int32_t id, std::int32_t* ret); -int APS5_VABI flock_nid_postfix(int d, int operation); -int APS5_VABI _fstatfs_nid_postfix(int d, void* buf); -int APS5_VABI ftruncate_nid_postfix(int d, std::int64_t length); -int APS5_VABI sceKernelFsync(int d); -int APS5_VABI sceKernelFdatasync(int d); -int APS5_VABI sceKernelGetdents(int d, char* buf, int nbytes); -int APS5_VABI sceKernelFchmod(int d, std::uint16_t mode); -int APS5_VABI sceKernelFutimes(int d, const void* times); -void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protection, int flags, int descriptor, std::int64_t offset) noexcept; -void* APS5_VABI __cxa_current_primary_exception_nid_postfix(); -std::type_info* APS5_VABI __cxa_current_exception_type_nid_postfix(); + using TerminateHandler = void (*)(); -TerminateHandler APS5_VABI _ZSt13set_terminatePFvvE_nid_postfix(TerminateHandler); + } namespace { @@ -146,7 +111,7 @@ void Reads() { KernelAioRwRequest request{65530, 20, buffer.data(), &result, fd}; std::int32_t id = 0; std::int32_t state = 0; - Check(sceKernelAioSubmitReadCommands(&request, 1, 0, &id) == 0); + Check(sceKernelAioSubmitReadCommands(&request, 1, 1, &id) == 0); Check(sceKernelAioWaitRequest(id, &state, nullptr) == 0); Check(result.return_value == 20 && std::equal(buffer.begin(), buffer.begin() + 20, expected.begin() + 65530)); std::int32_t removed = 0; @@ -198,7 +163,7 @@ void Refusals() { Check(fd >= 0); Check(NotImplemented([fd] { (void)fdopen_nid_postfix(fd, "r"); })); Check(NotImplemented([fd] { (void)flock_nid_postfix(fd, 1); })); - Check(NotImplemented([fd] { alignas(8) char statfs[512]; (void)_fstatfs_nid_postfix(fd, statfs); })); + Check(NotImplemented([fd] { alignas(8) char statfs[512]; (void)_fstatfs_nid_postfix(fd, reinterpret_cast(statfs)); })); Check(NotImplemented([fd] { (void)ftruncate_nid_postfix(fd, 0); })); Check(NotImplemented([fd] { (void)sceKernelFsync(fd); })); Check(NotImplemented([fd] { (void)sceKernelFdatasync(fd); })); @@ -243,6 +208,7 @@ int main(int argc, char** argv) { const int fd = sceKernelOpen("/app0/Data/Small.txt", SCE_KERNEL_O_RDONLY, 0); Check(fd >= 0); _ZSt13set_terminatePFvvE_nid_postfix(OnMmapRefused); + (void)mmap_nid_postfix(nullptr, 16384, 1, 2, fd, 0); return 1; } else if (mode == "plain") { diff --git a/core/libs/tests/GuestKernelErrors.cpp b/core/libs/tests/GuestKernelErrors.cpp index 1932625356..0cc54fbfe9 100644 --- a/core/libs/tests/GuestKernelErrors.cpp +++ b/core/libs/tests/GuestKernelErrors.cpp @@ -1,3 +1,9 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#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 @@ -8,60 +14,20 @@ #include extern "C" { -int APS5_VABI sceKernelCreateSema(KernelSema*, const char*, std::uint32_t, int, int, void*); -int APS5_VABI sceKernelDeleteSema(KernelSema); -int APS5_VABI sceKernelSignalSema(KernelSema, int); -int APS5_VABI sceKernelPollSema(KernelSema, int); -int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); -int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo); -int APS5_VABI sceKernelDeleteUserEvent(KernelEqueue eq, int id); -int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); -int APS5_VABI pthread_mutexattr_init_nid_postfix(PthreadMutexattr* attr); -int APS5_VABI pthread_mutexattr_settype_nid_postfix(PthreadMutexattr* attr, int type); -int APS5_VABI pthread_mutexattr_destroy_nid_postfix(PthreadMutexattr* attr); -int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex* mutex, const PthreadMutexattr* attr); -int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type); -int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex); -int APS5_VABI sceKernelCreateEventFlag(KernelEventFlag* ef, const char* name, std::uint32_t attr, std::uint64_t init_pattern, const void* param); -int APS5_VABI sceKernelDeleteEventFlag(KernelEventFlag ef); -int APS5_VABI sceKernelOpenEventFlag(KernelEventFlag* ef, const char* name); -int APS5_VABI sceKernelSetEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern); -int APS5_VABI sceKernelClearEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern); -int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, std::uint64_t set_pattern, int* num_wait_threads); -int APS5_VABI sceKernelPollEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern, std::uint32_t wait_mode, std::uint64_t* result_pat); -int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern, std::uint32_t wait_mode, std::uint64_t* result_pat, KernelUseconds* timeout); + int APS5_Test_GetEventFlagWaiterCount(KernelEventFlag handle); } static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EPERM = static_cast(0x80020001); -static constexpr int SCE_KERNEL_ERROR_ENOENT = static_cast(0x80020002); -static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); -static constexpr int SCE_KERNEL_ERROR_EBADF = static_cast(0x80020009); -static constexpr int SCE_KERNEL_ERROR_EDEADLK = static_cast(0x8002000B); -static constexpr int SCE_KERNEL_ERROR_EFAULT = static_cast(0x8002000E); -static constexpr int SCE_KERNEL_ERROR_EACCES = static_cast(0x8002000D); -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); -static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast(0x80020010); -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); -static constexpr int SCE_KERNEL_ERROR_ECANCELED = static_cast(0x80020055); static constexpr std::uint32_t EVF_ATTR_SINGLE = 0x10; static constexpr std::uint32_t EVF_ATTR_MULTI = 0x20; +static constexpr std::uint32_t EVF_ATTR_SHARED = 0x100; static constexpr std::uint32_t EVF_WAITMODE_AND = 0x01; static constexpr std::uint32_t EVF_WAITMODE_OR = 0x02; static constexpr std::uint32_t EVF_WAITMODE_CLEAR_ALL = 0x10; static constexpr std::uint32_t EVF_WAITMODE_CLEAR_PAT = 0x20; static constexpr int MUTEX_TYPE_ERRORCHECK = 1; +static constexpr int MUTEX_TYPE_NORMAL = 3; static constexpr int PRIO_NONE = 0; static constexpr int PRIO_INHERIT = 1; static constexpr int PRIO_PROTECT = 2; @@ -78,6 +44,13 @@ static void WaitForEventFlagWaiters(KernelEventFlag handle, int expected) { } } +template +static void RequireTimesOut(TLock lock) { + const auto start = std::chrono::steady_clock::now(); + Require(lock(20000) == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); +} + int main() { constexpr int maximum = std::numeric_limits::max(); KernelSema semaphore = nullptr; @@ -92,9 +65,9 @@ int main() { Require(sceKernelCreateSema(&semaphore, "limit", 1, 1, 3, nullptr) == SCE_OK); Require(sceKernelSignalSema(semaphore, 3) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelSignalSema(semaphore, 2) == SCE_OK); - Require(sceKernelSignalSema(semaphore, 0) == SCE_OK); + Require(sceKernelSignalSema(semaphore, 0) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelPollSema(semaphore, 3) == SCE_OK); - Require(sceKernelSignalSema(semaphore, 0) == SCE_OK); + Require(sceKernelSignalSema(semaphore, 0) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelPollSema(semaphore, 1) == SCE_KERNEL_ERROR_EBUSY); Require(sceKernelDeleteSema(semaphore) == SCE_OK); @@ -126,6 +99,7 @@ int main() { Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); Require(scePthreadMutexLock(&mutex) == SCE_OK); Require(scePthreadMutexLock(&mutex) == SCE_KERNEL_ERROR_EDEADLK); + RequireTimesOut([&](KernelUseconds usec) { return scePthreadMutexTimedlock(&mutex, usec); }); Require(scePthreadMutexDestroy(&mutex) == SCE_KERNEL_ERROR_EBUSY); Require(scePthreadMutexUnlock(&mutex) == SCE_OK); Require(scePthreadMutexDestroy(&mutex) == SCE_OK); @@ -137,9 +111,41 @@ int main() { Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); Require(scePthreadMutexLock(&adaptive) == SCE_OK); Require(scePthreadMutexLock(&adaptive) == SCE_KERNEL_ERROR_EDEADLK); + RequireTimesOut([&](KernelUseconds usec) { return scePthreadMutexTimedlock(&adaptive, usec); }); Require(scePthreadMutexUnlock(&adaptive) == SCE_OK); Require(scePthreadMutexDestroy(&adaptive) == SCE_OK); + PthreadMutex timedDefault = nullptr; + Require(scePthreadMutexInit(&timedDefault, nullptr, nullptr) == SCE_OK); + Require(scePthreadMutexLock(&timedDefault) == SCE_OK); + RequireTimesOut([&](KernelUseconds usec) { return scePthreadMutexTimedlock(&timedDefault, usec); }); + Require(scePthreadMutexUnlock(&timedDefault) == SCE_OK); + Require(scePthreadMutexTimedlock(&timedDefault, 20000) == SCE_OK); + Require(scePthreadMutexUnlock(&timedDefault) == SCE_OK); + Require(scePthreadMutexDestroy(&timedDefault) == SCE_OK); + + Require(scePthreadMutexattrInit(&attr) == SCE_OK); + Require(scePthreadMutexattrSettype(&attr, MUTEX_TYPE_NORMAL) == SCE_OK); + PthreadMutex normal = nullptr; + Require(scePthreadMutexInit(&normal, &attr, nullptr) == SCE_OK); + Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); + Require(scePthreadMutexLock(&normal) == SCE_OK); + RequireTimesOut([&](KernelUseconds usec) { return scePthreadMutexTimedlock(&normal, usec); }); + Require(scePthreadMutexUnlock(&normal) == SCE_OK); + Require(scePthreadMutexDestroy(&normal) == SCE_OK); + + PthreadRwlock rwlock = nullptr; + Require(scePthreadRwlockInit(&rwlock, nullptr, nullptr) == SCE_OK); + Require(scePthreadRwlockWrlock(&rwlock) == SCE_OK); + Require(scePthreadRwlockTrywrlock(&rwlock) == SCE_KERNEL_ERROR_EBUSY); + Require(scePthreadRwlockWrlock(&rwlock) == SCE_KERNEL_ERROR_EDEADLK); + RequireTimesOut([&](KernelUseconds usec) { return scePthreadRwlockTimedrdlock(&rwlock, usec); }); + RequireTimesOut([&](KernelUseconds usec) { return scePthreadRwlockTimedwrlock(&rwlock, usec); }); + Require(scePthreadRwlockUnlock(&rwlock) == SCE_OK); + Require(scePthreadRwlockTimedwrlock(&rwlock, 20000) == SCE_OK); + Require(scePthreadRwlockUnlock(&rwlock) == SCE_OK); + Require(scePthreadRwlockDestroy(&rwlock) == SCE_OK); + PthreadMutex adaptiveStatic = reinterpret_cast(std::uintptr_t{1}); Require(scePthreadMutexLock(&adaptiveStatic) == SCE_OK); Require(adaptiveStatic != reinterpret_cast(std::uintptr_t{1})); @@ -198,6 +204,13 @@ int main() { Require(sceKernelDeleteEventFlag(ef) == SCE_OK); Require(sceKernelCreateEventFlag(&ef, "test", EVF_ATTR_SINGLE | EVF_ATTR_MULTI, 0, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelCreateEventFlag(&ef, "test", EVF_ATTR_MULTI | 0x1000u, 0, nullptr) == SCE_KERNEL_ERROR_EINVAL); + for (const std::uint32_t attr : {0x03u, 0x13u, 0x23u, 0x103u, 0x130u, 0x04u, 0x08u, 0x40u, 0x80u, 0x200u, 0x80000000u, 0xffffffffu}) { + Require(sceKernelCreateEventFlag(&ef, "test_bad_attr", attr, 0, nullptr) == SCE_KERNEL_ERROR_EINVAL); + } + for (const std::uint32_t attr : {0x02u, 0x11u, 0x12u, 0x21u, 0x22u}) { + Require(sceKernelCreateEventFlag(&ef, "test_attr", attr, 0, nullptr) == SCE_OK); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + } Require(sceKernelDeleteEventFlag(nullptr) == SCE_KERNEL_ERROR_ESRCH); Require(sceKernelSetEventFlag(nullptr, 1) == SCE_KERNEL_ERROR_ESRCH); Require(sceKernelClearEventFlag(nullptr, 1) == SCE_KERNEL_ERROR_ESRCH); @@ -205,18 +218,89 @@ int main() { Require(sceKernelWaitEventFlag(nullptr, 1, EVF_WAITMODE_OR, nullptr, nullptr) == SCE_KERNEL_ERROR_ESRCH); Require(sceKernelCancelEventFlag(nullptr, 0, nullptr) == SCE_KERNEL_ERROR_ESRCH); - KernelEventFlag opened = nullptr; - Require(sceKernelOpenEventFlag(nullptr, "test") == SCE_KERNEL_ERROR_EINVAL); - Require(sceKernelOpenEventFlag(&opened, nullptr) == SCE_KERNEL_ERROR_EINVAL); - Require(sceKernelOpenEventFlag(&opened, "SceShellCoreUtilAppFocus") == SCE_KERNEL_ERROR_ENOENT && opened == nullptr); - Require(sceKernelCreateEventFlag(&ef, "test_open", EVF_ATTR_MULTI, 0x05, nullptr) == SCE_OK); - Require(sceKernelOpenEventFlag(&opened, "test_open") == SCE_OK && opened == ef); - Require(sceKernelSetEventFlag(opened, 0x08) == SCE_OK); - std::uint64_t openedPattern = 0; - Require(sceKernelPollEventFlag(ef, 0x08, EVF_WAITMODE_OR, &openedPattern) == SCE_OK && openedPattern == 0x0d); + Require(sceKernelCreateEventFlag(&ef, "", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); Require(sceKernelDeleteEventFlag(ef) == SCE_OK); - opened = nullptr; - Require(sceKernelOpenEventFlag(&opened, "test_open") == SCE_KERNEL_ERROR_ENOENT && opened == nullptr); + Require(sceKernelCreateEventFlag(&ef, "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn", EVF_ATTR_SHARED | EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + for (const std::uint32_t attr : {EVF_ATTR_MULTI, EVF_ATTR_SHARED | EVF_ATTR_MULTI}) { + Require(sceKernelCreateEventFlag(&ef, "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn", attr, 0, nullptr) == SCE_KERNEL_ERROR_ENAMETOOLONG); + } + + KernelEventFlag opened = nullptr; + Require(sceKernelOpenEventFlag(nullptr, "test") == SCE_KERNEL_ERROR_ESRCH); + Require(sceKernelOpenEventFlag(&opened, nullptr) == SCE_KERNEL_ERROR_EFAULT && opened == nullptr); + Require(sceKernelOpenEventFlag(&opened, "") == SCE_KERNEL_ERROR_ESRCH && opened == nullptr); + Require(sceKernelOpenEventFlag(&opened, "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn") == SCE_KERNEL_ERROR_ENAMETOOLONG && opened == nullptr); + Require(sceKernelOpenEventFlag(&opened, "SceShellCoreUtilAppFocus") == SCE_KERNEL_ERROR_ESRCH && opened == nullptr); + Require(sceKernelCloseEventFlag(nullptr) == SCE_KERNEL_ERROR_ESRCH); + + KernelEventFlag plain = nullptr; + Require(sceKernelCreateEventFlag(&plain, "test_open", EVF_ATTR_MULTI, 0x01, nullptr) == SCE_OK); + Require(sceKernelOpenEventFlag(&opened, "test_open") == SCE_KERNEL_ERROR_ESRCH && opened == nullptr); + Require(sceKernelCloseEventFlag(plain) == SCE_KERNEL_ERROR_EPERM); + Require(sceKernelSetEventFlag(plain, 0x02) == SCE_OK); + KernelEventFlag plainTwin = nullptr; + Require(sceKernelCreateEventFlag(&plainTwin, "test_open", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + Require(sceKernelDeleteEventFlag(plainTwin) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "test_open", EVF_ATTR_SHARED | EVF_ATTR_MULTI, 0x05, nullptr) == SCE_OK); + KernelEventFlag sharedTwin = nullptr; + Require(sceKernelCreateEventFlag(&sharedTwin, "test_open", EVF_ATTR_SHARED | EVF_ATTR_MULTI, 0, nullptr) == SCE_KERNEL_ERROR_EPERM && sharedTwin == nullptr); + Require(sceKernelOpenEventFlag(&opened, "test_open") == SCE_OK && opened != nullptr && opened != ef && opened != plain); + Require(sceKernelOpenEventFlag(nullptr, "test_open") == SCE_KERNEL_ERROR_EFAULT); + KernelEventFlag openedTwice = nullptr; + Require(sceKernelOpenEventFlag(&openedTwice, "test_open") == SCE_OK && openedTwice != opened && openedTwice != ef); + Require(sceKernelSetEventFlag(opened, 0x08) == SCE_OK); + Require(sceKernelClearEventFlag(openedTwice, ~std::uint64_t{0x04}) == SCE_OK); + std::uint64_t openedPattern = 0; + Require(sceKernelPollEventFlag(ef, 0x08, EVF_WAITMODE_OR, &openedPattern) == SCE_OK && openedPattern == 0x09); + Require(sceKernelDeleteEventFlag(opened) == SCE_KERNEL_ERROR_EPERM); + Require(sceKernelCloseEventFlag(ef) == SCE_KERNEL_ERROR_EPERM); + Require(sceKernelCloseEventFlag(openedTwice) == SCE_OK); + Require(sceKernelCloseEventFlag(openedTwice) == SCE_KERNEL_ERROR_ESRCH); + Require(sceKernelSetEventFlag(openedTwice, 0x01) == SCE_KERNEL_ERROR_ESRCH); + + std::atomic orphanWaitRes{-1}; + std::uint64_t orphanWaitPat = 0xdeadbeef; + std::thread orphanWaiter([&] { + orphanWaitRes = sceKernelWaitEventFlag(opened, 0x80, EVF_WAITMODE_OR, &orphanWaitPat, nullptr); + }); + WaitForEventFlagWaiters(opened, 1); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + orphanWaiter.join(); + Require(orphanWaitRes == SCE_KERNEL_ERROR_EACCES && orphanWaitPat == 0); + KernelEventFlag reopened = nullptr; + Require(sceKernelOpenEventFlag(&reopened, "test_open") == SCE_KERNEL_ERROR_ESRCH && reopened == nullptr); + std::uint64_t orphanPattern = 0xdeadbeef; + Require(sceKernelPollEventFlag(opened, 0, EVF_WAITMODE_OR, &orphanPattern) == SCE_KERNEL_ERROR_EINVAL && orphanPattern == 0xdeadbeef); + Require(sceKernelPollEventFlag(opened, 0x01, EVF_WAITMODE_OR, &orphanPattern) == SCE_KERNEL_ERROR_EACCES && orphanPattern == 0); + orphanPattern = 0xdeadbeef; + KernelUseconds orphanTimeout = 1000; + Require(sceKernelWaitEventFlag(opened, 0x01, EVF_WAITMODE_OR, &orphanPattern, &orphanTimeout) == SCE_KERNEL_ERROR_EACCES && orphanPattern == 0 && orphanTimeout == 1000); + Require(sceKernelSetEventFlag(opened, 0x02) == SCE_OK); + Require(sceKernelClearEventFlag(opened, 0) == SCE_OK); + int orphanCount = -1; + Require(sceKernelCancelEventFlag(opened, 0x04, &orphanCount) == SCE_OK && orphanCount == 0); + Require(sceKernelDeleteEventFlag(opened) == SCE_KERNEL_ERROR_EPERM); + + Require(sceKernelCreateEventFlag(&ef, "test_open", EVF_ATTR_SHARED | EVF_ATTR_SINGLE, 0x01, nullptr) == SCE_OK); + Require(sceKernelOpenEventFlag(&reopened, "test_open") == SCE_OK && reopened != opened); + Require(sceKernelCloseEventFlag(opened) == SCE_OK); + Require(sceKernelCloseEventFlag(opened) == SCE_KERNEL_ERROR_ESRCH); + std::atomic sharedSingleRes{-1}; + std::uint64_t sharedSinglePat = 0; + std::thread sharedSingleWaiter([&] { + sharedSingleRes = sceKernelWaitEventFlag(ef, 0x80, EVF_WAITMODE_OR, &sharedSinglePat, nullptr); + }); + WaitForEventFlagWaiters(ef, 1); + openedPattern = 0xdeadbeef; + Require(sceKernelPollEventFlag(reopened, 0x01, EVF_WAITMODE_OR, &openedPattern) == SCE_KERNEL_ERROR_EPERM && openedPattern == 0); + Require(sceKernelSetEventFlag(reopened, 0x80) == SCE_OK); + sharedSingleWaiter.join(); + Require(sharedSingleRes == SCE_OK && sharedSinglePat == 0x81); + Require(sceKernelCloseEventFlag(reopened) == SCE_OK); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + Require(sceKernelDeleteEventFlag(plain) == SCE_OK); Require(sceKernelCreateEventFlag(&ef, "flag", EVF_ATTR_MULTI, 0x15, nullptr) == SCE_OK); std::uint64_t pattern = 0; @@ -224,6 +308,16 @@ int main() { Require(sceKernelPollEventFlag(ef, 1, 0, &pattern) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelPollEventFlag(ef, 1, EVF_WAITMODE_AND | EVF_WAITMODE_OR, &pattern) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelPollEventFlag(ef, 1, EVF_WAITMODE_AND | EVF_WAITMODE_CLEAR_ALL | EVF_WAITMODE_CLEAR_PAT, &pattern) == SCE_KERNEL_ERROR_EINVAL); + for (const std::uint32_t mode : {0x04u, 0x40u, 0x100u, 0x80000000u, 0x103u}) { + pattern = 0xdeadbeef; + Require(sceKernelPollEventFlag(ef, 1, mode, &pattern) == SCE_KERNEL_ERROR_EINVAL && pattern == 0xdeadbeef); + } + for (const std::uint32_t mode : {EVF_WAITMODE_OR | 0x04u, EVF_WAITMODE_OR | 0x08u, EVF_WAITMODE_AND | 0x40u, EVF_WAITMODE_AND | 0x100u, EVF_WAITMODE_OR | 0x80000000u, + EVF_WAITMODE_OR | 0xffffff00u}) { + Require(sceKernelPollEventFlag(ef, 0x10, mode, &pattern) == SCE_OK && pattern == 0x15); + } + Require(sceKernelPollEventFlag(ef, 0x10, EVF_WAITMODE_OR | EVF_WAITMODE_CLEAR_PAT | 0x100u, &pattern) == SCE_OK && pattern == 0x15); + Require(sceKernelSetEventFlag(ef, 0x10) == SCE_OK); Require(sceKernelPollEventFlag(ef, 0x20, EVF_WAITMODE_OR, &pattern) == SCE_KERNEL_ERROR_EBUSY); Require(pattern == 0x15); @@ -261,7 +355,43 @@ int main() { Require(sceKernelDeleteEventFlag(ef) == SCE_OK); - Require(sceKernelCreateEventFlag(&ef, "single", EVF_ATTR_SINGLE | 0x02u, 0, nullptr) == SCE_OK); + Require(sceKernelCreateEventFlag(&ef, "single", EVF_ATTR_SINGLE | 0x02u, 0x04, nullptr) == SCE_OK); + std::atomic singleRes{-1}; + std::uint64_t singlePat = 0xdeadbeef; + std::thread singleWaiter([&] { + singleRes = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND, &singlePat, nullptr); + }); + WaitForEventFlagWaiters(ef, 1); + pattern = 0xdeadbeef; + Require(sceKernelPollEventFlag(ef, 0x04, EVF_WAITMODE_AND, &pattern) == SCE_KERNEL_ERROR_EPERM && pattern == 0); + pattern = 0xdeadbeef; + Require(sceKernelWaitEventFlag(ef, 0x04, EVF_WAITMODE_AND, &pattern, nullptr) == SCE_KERNEL_ERROR_EPERM && pattern == 0); + waitThreads = -1; + Require(sceKernelCancelEventFlag(ef, 0x08, &waitThreads) == SCE_OK && waitThreads == 1); + singleWaiter.join(); + Require(singleRes == SCE_KERNEL_ERROR_ECANCELED && singlePat == 0); + Require(sceKernelPollEventFlag(ef, 0x08, EVF_WAITMODE_AND, &pattern) == SCE_OK && pattern == 0x08); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "multi_cancel", EVF_ATTR_MULTI, 0x01, nullptr) == SCE_OK); + std::atomic cancelRes1{-1}; + std::atomic cancelRes2{-1}; + std::uint64_t cancelPat1 = 0xdeadbeef; + std::uint64_t cancelPat2 = 0xdeadbeef; + std::thread cancelT1([&] { + cancelRes1 = sceKernelWaitEventFlag(ef, 0x02, EVF_WAITMODE_AND, &cancelPat1, nullptr); + }); + std::thread cancelT2([&] { + cancelRes2 = sceKernelWaitEventFlag(ef, 0x04, EVF_WAITMODE_OR, &cancelPat2, nullptr); + }); + WaitForEventFlagWaiters(ef, 2); + waitThreads = -1; + Require(sceKernelCancelEventFlag(ef, 0x05, &waitThreads) == SCE_OK && waitThreads == 2); + cancelT1.join(); + cancelT2.join(); + Require(cancelRes1 == SCE_KERNEL_ERROR_ECANCELED && cancelPat1 == 0); + Require(cancelRes2 == SCE_KERNEL_ERROR_ECANCELED && cancelPat2 == 0); + Require(sceKernelPollEventFlag(ef, 0x05, EVF_WAITMODE_AND, &pattern) == SCE_OK && pattern == 0x05); Require(sceKernelDeleteEventFlag(ef) == SCE_OK); Require(sceKernelCreateEventFlag(&ef, "imm_sat", EVF_ATTR_MULTI, 0x05, nullptr) == SCE_OK); @@ -323,8 +453,9 @@ int main() { Require(sceKernelCreateEventFlag(&ef, "del_waiters", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); std::atomic delRes1{-1}; std::atomic delRes2{-1}; + std::uint64_t delPat = 0xdeadbeef; std::thread delT1([&] { - delRes1 = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND, nullptr, nullptr); + delRes1 = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND, &delPat, nullptr); }); std::thread delT2([&] { delRes2 = sceKernelWaitEventFlag(ef, 0x02, EVF_WAITMODE_AND, nullptr, nullptr); @@ -333,7 +464,7 @@ int main() { Require(sceKernelDeleteEventFlag(ef) == SCE_OK); delT1.join(); delT2.join(); - Require(delRes1 == SCE_KERNEL_ERROR_EACCES); + Require(delRes1 == SCE_KERNEL_ERROR_EACCES && delPat == 0); Require(delRes2 == SCE_KERNEL_ERROR_EACCES); for (int iter = 0; iter < 50; ++iter) { diff --git a/core/libs/tests/GuestKernelFcntl.cpp b/core/libs/tests/GuestKernelFcntl.cpp index b75d3c22a9..492633063c 100644 --- a/core/libs/tests/GuestKernelFcntl.cpp +++ b/core/libs/tests/GuestKernelFcntl.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -14,23 +17,7 @@ #include #include #endif -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI __sys_socketex_nid_postfix(const char*, int, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI _fcntl_nid_postfix(int, int, ...); -int APS5_VABI ioctl_nid_postfix(int, std::uint64_t, void*); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceKernelFcntl(int, int, ...); -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); -} + static void Require(bool value) { if (!value) std::abort(); } static constexpr int DuplicateCommand = 0; static constexpr int GetDescriptorFlags = 1; @@ -199,6 +186,7 @@ static void SocketsKeepTheirPath() { } static void FileFlagsRoundTrip() { + std::remove("kernel_fcntl_probe.bin"); const int file = sceKernelOpen("kernel_fcntl_probe.bin", CreateReadWrite, 0644); Require(file >= 0); Require(sceKernelFcntl(file, GetFlags) == ReadWrite); @@ -275,13 +263,27 @@ static void FileFlagsRoundTrip() { Require(fcntl_nid_postfix(appending, GetFlags) == (WriteOnly | Append)); Require(fcntl_nid_postfix(appending, SetFlags, Append | NonBlocking) == 0); Require(fcntl_nid_postfix(appending, GetFlags) == (WriteOnly | Append | NonBlocking)); -#ifdef _WIN32 - Require(ThrowsWith([appending] { fcntl_nid_postfix(appending, SetFlags, 0); }, "fcntl: changing O_APPEND on a file is not implemented")); -#else + Require(write_nid_postfix(appending, "ab", 2) == 2); Require(fcntl_nid_postfix(appending, SetFlags, 0) == 0); Require(fcntl_nid_postfix(appending, GetFlags) == WriteOnly); -#endif - Require(ThrowsWith([appending] { fcntl_nid_postfix(appending, SetFlags, Append | 0x80); }, "fcntl: changing O_FSYNC on a file is not implemented")); + Require(sceKernelLseek(appending, 0, FromStart) == 0); + Require(write_nid_postfix(appending, "X", 1) == 1); + const int sharing = fcntl_nid_postfix(appending, DuplicateCommand, 0); + Require(sharing >= 0); + Require(fcntl_nid_postfix(sharing, SetFlags, Append) == 0); + Require(fcntl_nid_postfix(appending, GetFlags) == (WriteOnly | Append)); + Require(sceKernelLseek(appending, 0, FromStart) == 0); + Require(write_nid_postfix(appending, "c", 1) == 1); + Require(fcntl_nid_postfix(sharing, SetFlags, 0) == 0); + Require(sceKernelLseek(appending, 0, FromStart) == 0); + Require(write_nid_postfix(appending, "Y", 1) == 1); + Require(close_nid_postfix(sharing) == 0); + const int reading = sceKernelOpen("kernel_fcntl_probe.bin", ReadOnly, 0); + Require(reading >= 0); + char written[16]{}; + Require(sceKernelRead(reading, written, sizeof(written)) == 8 && std::memcmp(written, "Ybcdeabc", 8) == 0); + Require(sceKernelClose(reading) == 0); + Require(fcntl_nid_postfix(appending, SetFlags, Append | 0x80) == 0 && fcntl_nid_postfix(appending, GetFlags) == (WriteOnly | Append | 0x80)); Require(sceKernelClose(appending) == 0); std::remove("kernel_fcntl_probe.bin"); } @@ -425,8 +427,10 @@ static void RecordLocksBelongToTheProcess() { static void PipeFlagsRoundTrip() { int pipe[2]; Require(pipe_nid_postfix(pipe) == 0); - Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadOnly); - Require(fcntl_nid_postfix(pipe[1], GetFlags) == WriteOnly); + Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadWrite); + Require(fcntl_nid_postfix(pipe[1], GetFlags) == ReadWrite); + const int duplicate = fcntl_nid_postfix(pipe[1], DuplicateCommand, 20); + Require(duplicate >= 20 && fcntl_nid_postfix(duplicate, GetFlags) == ReadWrite && close_nid_postfix(duplicate) == 0); Require(fcntl_nid_postfix(pipe[1], SetDescriptorFlags, 1) == 0); Require(fcntl_nid_postfix(pipe[1], GetDescriptorFlags) == 1); Require(fcntl_nid_postfix(pipe[0], GetDescriptorFlags) == 0); @@ -436,7 +440,7 @@ static void PipeFlagsRoundTrip() { Require(fcntl_nid_postfix(pipe[1], GetDescriptorFlags) == 0); Require(fcntl_nid_postfix(pipe[0], SetFlags, fcntl_nid_postfix(pipe[0], GetFlags) | NonBlocking) == 0); - Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadOnly | NonBlocking)); + Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadWrite | NonBlocking)); char buffer[8] = {}; RequireFailure(static_cast(read_nid_postfix(pipe[0], buffer, sizeof(buffer))), GuestEagain); Require(write_nid_postfix(pipe[1], "abc", 3) == 3); @@ -448,10 +452,10 @@ static void PipeFlagsRoundTrip() { int off = 0; Require(ioctl_nid_postfix(pipe[0], NonblockingRequest, &off) == 0); - Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadOnly); + Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadWrite); int on = 1; Require(ioctl_nid_postfix(pipe[0], NonblockingRequest, &on) == 0); - Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadOnly | NonBlocking)); + Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadWrite | NonBlocking)); Require(fcntl_nid_postfix(pipe[1], SetFlags, NonBlocking) == 0); char block[1024] = {}; diff --git a/core/libs/tests/GuestKernelFcntlSync.cpp b/core/libs/tests/GuestKernelFcntlSync.cpp new file mode 100644 index 0000000000..e5f6665f44 --- /dev/null +++ b/core/libs/tests/GuestKernelFcntlSync.cpp @@ -0,0 +1,96 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" std::uint64_t FileSynchronousWritesForTest_nid_no_patch(); + +namespace { + +constexpr int GetFlags = 3; +constexpr int SetFlags = 4; +constexpr int Synchronous = 0x80; +constexpr int ReadWrite = 2; +constexpr int Create = 0x200; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "check failed at line %u\n", static_cast(location.line())); + std::exit(1); +} + +std::uint64_t Synced() { + return FileSynchronousWritesForTest_nid_no_patch(); +} + +void Toggle(const std::string& path) { + const int file = open_nid_postfix(path.c_str(), ReadWrite | Create, 0600); + Require(file >= 0); + Require((fcntl_nid_postfix(file, GetFlags) & Synchronous) == 0); + auto before = Synced(); + Require(write_nid_postfix(file, "a", 1) == 1 && Synced() == before); + + Require(fcntl_nid_postfix(file, SetFlags, Synchronous) == 0); + Require((fcntl_nid_postfix(file, GetFlags) & Synchronous) != 0); + before = Synced(); + Require(write_nid_postfix(file, "b", 1) == 1 && Synced() == before + 1); + char c[] = "c", d[] = "d", y[] = "y", z[] = "z"; + const KernelIovec vectors[] = {{c, 1}, {d, 1}}; + Require(writev_nid_postfix(file, vectors, 2) == 2 && Synced() == before + 2); + Require(pwrite_nid_disambig1_nid_postfix(file, "x", 1, 10) == 1 && Synced() == before + 3); + const KernelIovec tail[] = {{y, 1}, {z, 1}}; + Require(pwritev_nid_postfix(file, tail, 2, 11) == 2 && Synced() == before + 4); + + const int duplicate = dup_nid_postfix(file); + Require(duplicate >= 0 && (fcntl_nid_postfix(duplicate, GetFlags) & Synchronous) != 0); + Require(write_nid_postfix(duplicate, "e", 1) == 1 && Synced() == before + 5); + + Require(fcntl_nid_postfix(duplicate, SetFlags, 0) == 0); + Require((fcntl_nid_postfix(file, GetFlags) & Synchronous) == 0); + before = Synced(); + Require(write_nid_postfix(file, "f", 1) == 1 && Synced() == before); + + char contents[13] = {}; + Require(pread_nid_postfix(file, contents, sizeof(contents), 0) == 13); + Require(std::memcmp(contents, "abcdef", 6) == 0 && std::memcmp(contents + 10, "xyz", 3) == 0); + Require(close_nid_postfix(duplicate) == 0 && close_nid_postfix(file) == 0); +} + +void OpenedSynchronous(const std::string& path) { + const int file = open_nid_postfix(path.c_str(), ReadWrite | Create | Synchronous, 0600); + Require(file >= 0 && (fcntl_nid_postfix(file, GetFlags) & Synchronous) != 0); + const auto before = Synced(); + Require(write_nid_postfix(file, "h", 1) == 1 && Synced() == before + 1); + Require(close_nid_postfix(file) == 0); +} + +void Pipes() { + int ends[2] = {-1, -1}; + Require(pipe_nid_postfix(ends) == 0); + Require(fcntl_nid_postfix(ends[1], SetFlags, Synchronous) == 0); + Require((fcntl_nid_postfix(ends[1], GetFlags) & Synchronous) != 0); + const auto before = Synced(); + Require(write_nid_postfix(ends[1], "i", 1) == 1 && Synced() == before); + Require(close_nid_postfix(ends[0]) == 0 && close_nid_postfix(ends[1]) == 0); +} + +} + +int main() { + const auto stamp = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + const std::string first = "anyps5-fsync-" + stamp + ".bin"; + const std::string second = "anyps5-fsync-open-" + stamp + ".bin"; + Toggle(first); + OpenedSynchronous(second); + Pipes(); + std::filesystem::remove(first); + std::filesystem::remove(second); + std::puts("guest_kernel_fcntl_sync passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelIsStack.cpp b/core/libs/tests/GuestKernelIsStack.cpp index 968cdc981f..e9fdbae01d 100644 --- a/core/libs/tests/GuestKernelIsStack.cpp +++ b/core/libs/tests/GuestKernelIsStack.cpp @@ -1,24 +1,16 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end); -int APS5_VABI sceKernelMapFlexibleMemory(void** addr, std::size_t len, int prot, int flags); -int APS5_VABI sceKernelMunmap(std::uint64_t addr, std::size_t len); -int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, std::uint64_t infoSize); -} - static constexpr int SCE_OK = 0; #ifdef ANYPS5_ENABLE_ASAN static constexpr std::uintptr_t APPLICATION_AREA_END = 0x000010FC00000000ull; #else static constexpr std::uintptr_t APPLICATION_AREA_END = 0x000000FC00000000ull; #endif -static constexpr int SCE_KERNEL_ERROR_EACCES = static_cast(0x8002000d); static int global = 0; static void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestKernelRename.cpp b/core/libs/tests/GuestKernelRename.cpp index 6b74a12f95..22cbbdbf65 100644 --- a/core/libs/tests/GuestKernelRename.cpp +++ b/core/libs/tests/GuestKernelRename.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -11,9 +12,7 @@ #else #include #endif -extern "C" { -int* APS5_VABI __error_nid_postfix(); -} + using RenameFunction = int (APS5_VABI*)(const char*, const char*); static RenameFunction LoadKernelRename() { #ifdef _WIN32 diff --git a/core/libs/tests/GuestKernelStackProtect.cpp b/core/libs/tests/GuestKernelStackProtect.cpp index a980ea9d09..2574e5e198 100644 --- a/core/libs/tests/GuestKernelStackProtect.cpp +++ b/core/libs/tests/GuestKernelStackProtect.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -18,18 +20,6 @@ #include #endif -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); -int APS5_VABI scePthreadKeyDelete(PthreadKey key); -int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); -int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end); -void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protection, int flags, int descriptor, std::int64_t offset) noexcept; -int APS5_VABI mprotect_nid_postfix(void* address, std::size_t length, int protection) noexcept; -int* APS5_VABI __error_nid_postfix(); -} - static constexpr int SCE_OK = 0; static constexpr std::uintptr_t Page = 0x4000; static constexpr std::size_t GuardBytes = 0x10000; diff --git a/core/libs/tests/GuestKernelSync.cpp b/core/libs/tests/GuestKernelSync.cpp index fbe8391437..6386538124 100644 --- a/core/libs/tests/GuestKernelSync.cpp +++ b/core/libs/tests/GuestKernelSync.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include #include @@ -12,19 +15,7 @@ #define WIN32_LEAN_AND_MEAN #include #endif -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, unsigned short); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t); -int APS5_VABI sceKernelMkdir(const char*, unsigned short); -int APS5_VABI sceKernelRename(const char*, const char*); -int APS5_VABI sceKernelUnlink(const char*); -int APS5_VABI sceKernelChmod_nid_postfix(const char*, std::uint16_t); -void APS5_VABI sceKernelSync(); -void APS5_VABI sync_nid_postfix(); -void* APS5_VABI fopen_nid_postfix(const char*, const char*); -int APS5_VABI fclose_nid_postfix(void*); -} + static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Kernel sync check failed at line %d\n", line); @@ -50,7 +41,7 @@ int main() { Require(sceKernelMkdir("kernel_sync_probe/existing.bin/child", 0777) == static_cast(0x80020014u)); const int reader = sceKernelOpen("kernel_sync_probe/existing.bin", 0x0, 0); Require(reader >= 0 && sceKernelClose(reader) == 0); - void* stream = fopen_nid_postfix("kernel_sync_probe/existing.bin", "r"); + FileStream* stream = fopen_nid_postfix("kernel_sync_probe/existing.bin", "r"); Require(stream != nullptr && fclose_nid_postfix(stream) == 0); Require(!Recorded("kernel_sync_probe/existing.bin")); stream = fopen_nid_postfix("kernel_sync_probe/existing.bin", "a"); @@ -63,27 +54,33 @@ int main() { Require(Recorded("kernel_sync_probe/moved.bin")); Require(sceKernelUnlink("kernel_sync_probe/moved.bin") == 0); sceKernelSync(); + Require(!Recorded("kernel_sync_probe/data.bin") && !Recorded("kernel_sync_probe/moved.bin")); sceKernelSync(); + const int locked = sceKernelOpen("kernel_sync_probe/read_only.bin", 0x1 | 0x200, 0644); Require(locked >= 0 && sceKernelWrite(locked, "data", 4) == 4 && sceKernelClose(locked) == 0); Require(sceKernelChmod_nid_postfix("kernel_sync_probe/read_only.bin", 0444) == 0); Require(Recorded("kernel_sync_probe/read_only.bin")); sceKernelSync(); + Require(sceKernelChmod_nid_postfix("kernel_sync_probe/read_only.bin", 0644) == 0); #ifdef _WIN32 const auto exclusive = CreateFileW(ResolvePath_nid_no_patch("kernel_sync_probe/read_only.bin").c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); Require(exclusive != INVALID_HANDLE_VALUE); sceKernelSync(); + CloseHandle(exclusive); #endif sceKernelSync(); + const int synced = sceKernelOpen("kernel_sync_probe/synced.bin", 0x1 | 0x200 | 0x400, 0644); Require(synced >= 0 && sceKernelWrite(synced, "data", 4) == 4 && sceKernelClose(synced) == 0); Require(Recorded("kernel_sync_probe/synced.bin")); Require(sceKernelUnlink("kernel_sync_probe/synced.bin") == 0); sync_nid_postfix(); + Require(!Recorded("kernel_sync_probe/synced.bin")); std::filesystem::remove_all("kernel_sync_probe"); } diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index a60abca020..99ffc5a124 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -1,7 +1,15 @@ +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/General.hpp" #include "prx/libc/include/RemoveTree.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include "SceTypes.hpp" #include #include @@ -17,51 +25,17 @@ #include #ifdef _WIN32 #include +#include #ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE #define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2 #endif #endif #ifndef _WIN32 #include +#include #include #endif -extern "C" { -int APS5_VABI fstatfs_nid_postfix(int, void*); -int APS5_VABI _fstatfs_nid_postfix(int, void*); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -std::int64_t APS5_VABI readlink_nid_postfix(const char*, char*, std::size_t); -std::int64_t APS5_VABI pathconf_nid_postfix(const char*, int); -int APS5_VABI fchdir_nid_postfix(int); -int APS5_VABI chdir_nid_postfix(const char*); -int APS5_VABI chroot_nid_postfix(const char*); -char* APS5_VABI getcwd_nid_postfix(char*, std::size_t); -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -int APS5_VABI sceKernelRename(const char*, const char*); -int APS5_VABI sceKernelFstat(int, FileStat*); -int APS5_VABI sceKernelGetdents(int, char*, int); -std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -int APS5_VABI dup_nid_postfix(int); -int APS5_VABI dup2_nid_postfix(int, int); -int APS5_VABI setsid_nid_postfix(void); -int APS5_VABI link_nid_postfix(const char*, const char*); -int APS5_VABI symlink_nid_postfix(const char*, const char*); -int APS5_VABI openat_nid_postfix(int, const char*, int, ...); -int APS5_VABI unlinkat_nid_postfix(int, const char*, int); -std::int64_t APS5_VABI readlinkat_nid_postfix(int, const char*, char*, std::size_t); -int APS5_VABI mkdirat_nid_postfix(int, const char*, std::uint16_t); -int APS5_VABI renameat_nid_postfix(int, const char*, int, const char*); -int APS5_VABI fchmodat_nid_postfix(int, const char*, int, int); -int APS5_VABI fchown_nid_postfix(int, std::uint32_t, std::uint32_t); -int APS5_VABI fstatat_nid_postfix(int, const char*, FileStat*, int); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t); -int* APS5_VABI __error_nid_postfix(); -} - void Require(bool condition, std::source_location location = std::source_location::current()) { if (!condition) { std::fprintf(stderr, "Kernel sys check failed at line %u (guest errno %d)\n", location.line(), *__error_nid_postfix()); @@ -76,6 +50,7 @@ int Error() { bool PathconfThrows(const char* path, int name) { try { pathconf_nid_postfix(path, name); + } catch (const std::runtime_error&) { return true; } @@ -85,15 +60,111 @@ bool PathconfThrows(const char* path, int name) { bool ChrootThrows(const std::string& path) { try { chroot_nid_postfix(path.c_str()); + } catch (const std::runtime_error& error) { return std::strstr(error.what(), path.c_str()) != nullptr; } return false; } +template +bool ThrowsNaming(Call call, const char* text) { + try { + call(); + } catch (const std::runtime_error& error) { + return std::strstr(error.what(), text) != nullptr; + } + return false; +} + +void OwnershipAndLinkTests(const std::filesystem::path& root) { + constexpr std::uint32_t Unchanged = 0xFFFFFFFFu; + const auto file = (root / "owned.txt").string(); + std::ofstream(file).put('x'); + const auto missing = (root / "missing.txt").string(); + const auto throughFile = file + "/inner"; + const auto linked = (root / "linked.txt").string(); + const auto other = (root / "other.txt").string(); + Require(chown_nid_postfix(file.c_str(), 0, 0) == 0); + Require(chown_nid_postfix(file.c_str(), Unchanged, Unchanged) == 0); + Require(lchown_nid_postfix(file.c_str(), 0, Unchanged) == 0); + Require(fchownat_nid_postfix(-100, file.c_str(), Unchanged, 0, 0) == 0); + Require(fchownat_nid_postfix(-100, file.c_str(), 0, 0, 0x200) == 0); + for (const auto call : {chown_nid_postfix, lchown_nid_postfix}) { + Require(call(missing.c_str(), 0, 0) == -1 && Error() == 2); + Require(call(throughFile.c_str(), 0, 0) == -1 && Error() == 20); + Require(call("", 0, 0) == -1 && Error() == 2); + Require(call(nullptr, 0, 0) == -1 && Error() == 14); + } + Require(fchownat_nid_postfix(-100, missing.c_str(), 0, 0, 0) == -1 && Error() == 2); + Require(fchownat_nid_postfix(-100, file.c_str(), 0, 0, 0x400) == -1 && Error() == 22); + Require(chown_nid_postfix(missing.c_str(), 1000, 0) == -1 && Error() == 2); + Require(chown_nid_postfix(nullptr, 1000, 0) == -1 && Error() == 14); + Require(lchown_nid_postfix(throughFile.c_str(), 0, 1000) == -1 && Error() == 20); + 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(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); + Require(linkat_nid_postfix(-100, missing.c_str(), -100, other.c_str(), 0x400) == -1 && Error() == 2); + Require(linkat_nid_postfix(-100, file.c_str(), -100, other.c_str(), 0x200) == -1 && Error() == 22); + Require(linkat_nid_postfix(-100, nullptr, -100, other.c_str(), 0) == -1 && Error() == 14); + Require(linkat_nid_postfix(-100, "", -100, other.c_str(), 0) == -1 && Error() == 2); + Require(linkat_nid_postfix(directory, "owned.txt", -100, other.c_str(), 0) == 0); + Require(std::filesystem::hard_link_count(file) == 3 && std::filesystem::remove(other)); + Require(linkat_nid_postfix(-100, file.c_str(), directory, "other.txt", 0) == 0); + Require(std::filesystem::hard_link_count(file) == 3); + Require(linkat_nid_postfix(directory, "owned.txt", directory, "other.txt", 0) == -1 && Error() == 17); + Require(linkat_nid_postfix(directory, "missing.txt", directory, "absent.txt", 0) == -1 && Error() == 2); + Require(linkat_nid_postfix(4321, "owned.txt", directory, "absent.txt", 0) == -1 && Error() == 9); + Require(linkat_nid_postfix(directory, "owned.txt", 4321, "absent.txt", 0) == -1 && Error() == 9); + Require(linkat_nid_postfix(directory, "missing.txt", 4321, "absent.txt", 0) == -1 && Error() == 2); + Require(linkat_nid_postfix(directory, "missing.txt", -100, nullptr, 0) == -1 && Error() == 2); + Require(linkat_nid_postfix(-100, root.string().c_str(), 4321, "absent.txt", 0) == -1 && Error() == 1); + Require(linkat_nid_postfix(-100, root.string().c_str(), -100, "", 0) == -1 && Error() == 1); + Require(linkat_nid_postfix(directory, "owned.txt", -100, nullptr, 0) == -1 && Error() == 14); + Require(linkat_nid_postfix(directory, "owned.txt", -100, "", 0) == -1 && Error() == 2); + const int plain = sceKernelOpen(file.c_str(), SCE_KERNEL_O_RDONLY, 0); + Require(plain >= 0); + Require(linkat_nid_postfix(plain, "owned.txt", directory, "absent.txt", 0) == -1 && Error() == 20); + Require(linkat_nid_postfix(directory, "owned.txt", plain, "absent.txt", 0) == -1 && Error() == 20); + Require(sceKernelClose(plain) == 0 && std::filesystem::remove(other)); + Require(!std::filesystem::exists(root / "absent.txt")); + const auto symbolic = (root / "owned.link").string(); + const auto dangling = (root / "dangling.link").string(); + if (symlink_nid_postfix("owned.txt", symbolic.c_str()) == 0) { + Require(symlink_nid_postfix("missing.txt", dangling.c_str()) == 0); + Require(chown_nid_postfix(symbolic.c_str(), 0, 0) == 0 && lchown_nid_postfix(symbolic.c_str(), 0, 0) == 0); + Require(chown_nid_postfix(dangling.c_str(), 0, 0) == -1 && Error() == 2); + 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")); + 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); +#else + 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(sceKernelClose(directory) == 0); + Require(std::filesystem::remove(linked) && std::filesystem::remove(file)); +} + bool SymlinkTargetThrows(const char* target, const std::string& created) { try { symlink_nid_postfix(target, created.c_str()); + } catch (const std::runtime_error&) { return true; } @@ -113,6 +184,119 @@ bool RemoveSymlink(const std::string& link) { #endif } +class GuardedPaths { +public: + GuardedPaths() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + memory = static_cast(VirtualAlloc(nullptr, 2 * page, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(memory != nullptr); + DWORD previous = 0; + Require(VirtualProtect(memory + page, page, PAGE_NOACCESS, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + memory = static_cast(mmap(nullptr, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(memory != MAP_FAILED); + Require(mprotect(memory + page, page, PROT_NONE) == 0); +#endif + } + + ~GuardedPaths() { +#ifdef _WIN32 + Require(VirtualFree(memory, 0, MEM_RELEASE) != 0); +#else + Require(munmap(memory, 2 * page) == 0); +#endif + } + + char* Unreadable() const { return memory + page; } + char* Crossing() const { return memory + page - 3; } + char* Boundary() const { return memory + page - 2; } + +private: + char* memory = nullptr; + std::size_t page = 0; +}; + +void CheckUnreadablePaths(const std::string& renameSource, int directory) { + constexpr int Fault = 14; + constexpr int KernelFault = static_cast(0x8002000Eu); + GuardedPaths paths; + std::memcpy(paths.Crossing(), "abc", 3); + std::string copy; + Require(CopyGuestPath_nid_no_patch(paths.Unreadable(), copy) == Fault); + Require(CopyGuestPath_nid_no_patch(paths.Crossing(), copy) == Fault); + paths.Boundary()[0] = '.'; + paths.Boundary()[1] = '\0'; + Require(CopyGuestPath_nid_no_patch(paths.Boundary(), copy) == 0 && copy == "."); + const std::string maximum(1023, 'p'); + std::array unterminated{}; + unterminated.fill('p'); + Require(CopyGuestPath_nid_no_patch(maximum.c_str(), copy) == 0 && copy == maximum); + Require(CopyGuestPath_nid_no_patch(unterminated.data(), copy) == 63); + const auto posixFault = [&](auto call) { + *__error_nid_postfix() = 0; + Require(call() == -1 && Error() == Fault); + }; + FileStat status{}; + char linkBuffer[16]{}; + posixFault([&] { return open_nid_postfix(paths.Unreadable(), SCE_KERNEL_O_RDONLY, 0); }); + posixFault([&] { return chmod_nid_postfix(paths.Unreadable(), 0); }); + posixFault([&] { return mkdir_nid_postfix(paths.Unreadable(), 0755); }); + posixFault([&] { return rmdir_nid_postfix(paths.Unreadable()); }); + posixFault([&] { return unlink_nid_postfix(paths.Unreadable()); }); + posixFault([&] { return stat_nid_postfix(paths.Unreadable(), &status); }); + posixFault([&] { return lstat_nid_postfix(paths.Unreadable(), &status); }); + posixFault([&] { return truncate_nid_postfix(paths.Unreadable(), 0); }); + posixFault([&] { return utimes_nid_postfix(paths.Unreadable(), nullptr); }); + posixFault([&] { return lutimes_nid_postfix(paths.Unreadable(), nullptr); }); + posixFault([&] { return readlink_nid_postfix(paths.Unreadable(), linkBuffer, sizeof(linkBuffer)); }); + posixFault([&] { return pathconf_nid_postfix(paths.Unreadable(), 4); }); + posixFault([&] { return link_nid_postfix(paths.Unreadable(), "unused-link"); }); + posixFault([&] { return link_nid_postfix(renameSource.c_str(), paths.Unreadable()); }); + posixFault([&] { return symlink_nid_postfix(paths.Unreadable(), "unused-symlink"); }); + posixFault([&] { return symlink_nid_postfix("target", paths.Unreadable()); }); + posixFault([&] { return openat_nid_postfix(-100, paths.Unreadable(), SCE_KERNEL_O_RDONLY); }); + posixFault([&] { return mkdirat_nid_postfix(-100, paths.Unreadable(), 0755); }); + posixFault([&] { return fchmodat_nid_postfix(-100, paths.Unreadable(), 0, 0); }); + posixFault([&] { return lchmod_nid_postfix(paths.Unreadable(), 0); }); + posixFault([&] { return fstatat_nid_postfix(-100, paths.Unreadable(), &status, 0); }); + posixFault([&] { return unlinkat_nid_postfix(-100, paths.Unreadable(), 0); }); + posixFault([&] { return readlinkat_nid_postfix(-100, paths.Unreadable(), linkBuffer, sizeof(linkBuffer)); }); + posixFault([&] { return renameat_nid_postfix(-100, paths.Unreadable(), -100, "unused-rename"); }); + posixFault([&] { return renameat_nid_postfix(-100, renameSource.c_str(), -100, paths.Unreadable()); }); + posixFault([&] { return openat_nid_postfix(directory, paths.Unreadable(), SCE_KERNEL_O_RDONLY); }); + posixFault([&] { return mkdirat_nid_postfix(directory, paths.Unreadable(), 0755); }); + posixFault([&] { return fchmodat_nid_postfix(directory, paths.Unreadable(), 0, 0); }); + posixFault([&] { return fstatat_nid_postfix(directory, paths.Unreadable(), &status, 0); }); + posixFault([&] { return unlinkat_nid_postfix(directory, paths.Unreadable(), 0); }); + posixFault([&] { return readlinkat_nid_postfix(directory, paths.Unreadable(), linkBuffer, sizeof(linkBuffer)); }); + posixFault([&] { return renameat_nid_postfix(directory, paths.Unreadable(), directory, "unused-rename"); }); + posixFault([&] { return renameat_nid_postfix(directory, "file.txt", directory, paths.Unreadable()); }); + *__error_nid_postfix() = 0; + Require(renameat_nid_postfix(directory, "missing", directory, paths.Unreadable()) == -1 && Error() == 2); + *__error_nid_postfix() = 0; + Require(link_nid_postfix("missing", paths.Unreadable()) == -1 && Error() == 2); + posixFault([&] { return shm_open_nid_postfix(paths.Unreadable(), SCE_KERNEL_O_RDWR, 0600); }); + posixFault([&] { return shm_unlink_nid_postfix(paths.Unreadable()); }); + Require(sceKernelOpen(paths.Unreadable(), SCE_KERNEL_O_RDONLY, 0) == KernelFault); + Require(sceKernelStat(paths.Unreadable(), &status) == KernelFault); + Require(sceKernelMkdir(paths.Unreadable(), 0755) == KernelFault); + Require(sceKernelRmdir(paths.Unreadable()) == KernelFault); + Require(sceKernelUnlink(paths.Unreadable()) == KernelFault); + Require(sceKernelChmod_nid_postfix(paths.Unreadable(), 0) == KernelFault); + Require(sceKernelTruncate_nid_postfix(paths.Unreadable(), 0) == KernelFault); + Require(sceKernelUtimes_nid_postfix(paths.Unreadable(), nullptr) == KernelFault); + Require(sceKernelCheckReachability(paths.Unreadable()) == KernelFault); + Require(sceKernelRename(paths.Unreadable(), ".") == KernelFault); + Require(sceKernelRename(renameSource.c_str(), paths.Unreadable()) == KernelFault); + Require(!std::filesystem::exists("unused-link") && !std::filesystem::exists("unused-symlink") && !std::filesystem::exists("unused-rename")); +} + void CheckSymlinkTargetRestrictions(const std::filesystem::path& root) { const auto created = (root / "rejected").string(); for (const auto& target : {std::string(".."), std::string("../x"), std::string("a/../b"), std::string("a/../../x"), std::string("/abs")}) @@ -142,6 +326,26 @@ void CheckSymlinkTargetRestrictions(const std::filesystem::path& root) { } } +void CheckCondattrClock() { + constexpr KernelClockid Realtime = 0; + constexpr KernelClockid Monotonic = 4; + PthreadCondattr attr = nullptr; + KernelClockid clock = -1; + Require(scePthreadCondattrGetclock(nullptr, &clock) == SCE_KERNEL_ERROR_EINVAL && clock == -1); + Require(scePthreadCondattrGetclock(nullptr, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadCondattrGetclock(&attr, &clock) == SCE_KERNEL_ERROR_EINVAL && clock == -1); + Require(scePthreadCondattrInit(&attr) == 0 && attr != nullptr); + Require(scePthreadCondattrGetclock(&attr, &clock) == 0 && clock == Realtime); + Require(scePthreadCondattrSetclock(&attr, Monotonic) == 0); + Require(scePthreadCondattrGetclock(&attr, &clock) == 0 && clock == Monotonic); + bool getclockThrew = false; + try { scePthreadCondattrGetclock(&attr, nullptr); } catch (const std::runtime_error&) { getclockThrew = true; } + Require(getclockThrew); + Require(scePthreadCondattrDestroy(&attr) == 0 && attr == nullptr); + clock = -1; + Require(scePthreadCondattrGetclock(&attr, &clock) == SCE_KERNEL_ERROR_EINVAL && clock == -1); +} + void CheckPathconf(const std::filesystem::path& root, const std::string& file) { const auto directory = root.generic_string(); for (const auto& path : {file, directory}) { @@ -175,6 +379,31 @@ void CheckPathconf(const std::filesystem::path& root, const std::string& file) { Require(pathconf_nid_postfix(longPath.c_str(), 4) == -1 && Error() == 63); } +void CheckSettimeofday() { + KernelTimezone zone{-1, -1}; + Require(settimeofday_nid_postfix(nullptr, nullptr) == 0); + Require(gettimeofday_nid_postfix(nullptr, &zone) == 0 && zone.tz_minuteswest == 0 && zone.tz_dsttime == 0); + const KernelTimezone west{60, 1}; + for (const KernelTimeval invalid : {KernelTimeval{0, 1000000}, KernelTimeval{0, -1}, KernelTimeval{-1, 0}}) + Require(settimeofday_nid_postfix(&invalid, &west) == -1 && Error() == 22); + Require(gettimeofday_nid_postfix(nullptr, &zone) == 0 && zone.tz_minuteswest == 0 && zone.tz_dsttime == 0); + const KernelTimeval valid{1000000000, 0}; + bool rejected = false; + try { + settimeofday_nid_postfix(&valid, &west); + } catch (const std::runtime_error& error) { + rejected = std::strstr(error.what(), "settimeofday: setting the time to 1000000000.000000") != nullptr; + } + Require(rejected); + Require(gettimeofday_nid_postfix(nullptr, &zone) == 0 && zone.tz_minuteswest == 0 && zone.tz_dsttime == 0); + Require(settimeofday_nid_postfix(nullptr, &west) == 0); + KernelTimeval now{}; + Require(gettimeofday_nid_postfix(&now, &zone) == 0 && zone.tz_minuteswest == 60 && zone.tz_dsttime == 1 && now.tv_sec > 0); + const KernelTimezone zero{0, 0}; + Require(settimeofday_nid_postfix(nullptr, &zero) == 0); + Require(gettimeofday_nid_postfix(nullptr, &zone) == 0 && zone.tz_minuteswest == 0 && zone.tz_dsttime == 0); +} + void DirectoryRenameTests(const std::filesystem::path& parent) { const auto fixture = parent / "directory-identity"; const auto original = fixture / "original"; @@ -256,33 +485,119 @@ void DirectoryRenameTests(const std::filesystem::path& parent) { Require(std::filesystem::remove(original) && std::filesystem::remove(moved) && std::filesystem::remove(fixture)); } +static void CheckProcessQueries() { + KernelTitleWorkaround workaround{1, 0, {(1ull << 3) | (1ull << 0x39), ~0ull}}; + std::int32_t enabled = -1; + Require(sceKernelTitleWorkaroundIsEnabled(nullptr, 3, &enabled) == static_cast(0x8002000E)); + Require(sceKernelTitleWorkaroundIsEnabled(&workaround, 3, nullptr) == static_cast(0x8002000E)); + Require(sceKernelTitleWorkaroundIsEnabled(&workaround, 0x3A, &enabled) == static_cast(0x80020016) && enabled == -1); + Require(sceKernelTitleWorkaroundIsEnabled(&workaround, -1, &enabled) == static_cast(0x80020016) && enabled == -1); + Require(sceKernelTitleWorkaroundIsEnabled(&workaround, 3, &enabled) == 0 && enabled == 1); + Require(sceKernelTitleWorkaroundIsEnabled(&workaround, 4, &enabled) == 0 && enabled == 0); + Require(sceKernelTitleWorkaroundIsEnabled(&workaround, 63, &enabled) == static_cast(0x80020016)); + Require(sceKernelTitleWorkaroundIsEnabled(&workaround, 0x39, &enabled) == 0 && enabled == 1); + Require(sceKernelTitleWorkaroundIsEnabled(&workaround, 0x38, &enabled) == 0 && enabled == 0); + Require(sceKernelGetProcessType(getpid_nid_postfix()) == 0); + Require(sceKernelGetProcessType(-1) == 0 && sceKernelGetProcessType(0) == 0); + Require(sceKernelGetProcessType(getpid_nid_postfix() + 1) == static_cast(0x80020003)); +} + +template +bool DescriptorPathconfThrows(F fn, int descriptor, int name) { + try { + fn(descriptor, name); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +template +void CheckDescriptorPathconf(F fn, int descriptor) { + Require(fn(descriptor, 1) == 32767); + Require(fn(descriptor, 4) == 255); + Require(fn(descriptor, 5) == 1024); + Require(fn(descriptor, 6) == 512); + Require(fn(descriptor, 7) == 1); + Require(fn(descriptor, 8) == 1); + Require(fn(descriptor, 12) == 64); + *__error_nid_postfix() = 0; + Require(fn(descriptor, 15) == -1 && Error() == 0); + Require(fn(descriptor, 18) == 1024); + Require(fn(descriptor, 54) == 0); + Require(fn(descriptor, 55) == 0); + for (const int name : {0, 11, 61, 62, 65, -1}) + Require(fn(descriptor, name) == -1 && Error() == 22); + Require(fn(descriptor, 53) == 200112); + for (const int name : {2, 3, 9, 10, 14, 16, 17, 21, 59, 60, 63, 64}) + Require(DescriptorPathconfThrows(fn, descriptor, name)); + Require(fn(-1, 4) == -1 && Error() == 9); + Require(fn(descriptor + 1000, 4) == -1 && Error() == 9); +} + +template +void CheckPipePathconf(F fn) { + int ends[2]{-1, -1}; + Require(pipe_nid_postfix(ends) == 0); + for (const int end : ends) { + Require(fn(end, 6) == 512); + Require(fn(end, 53) == 200112); + for (const int name : {0, 1, 4, 5, 7, 8, 12, 18, 54, 55, 61, -1}) + Require(fn(end, name) == -1 && Error() == 22); + } + Require(close_nid_postfix(ends[0]) == 0 && close_nid_postfix(ends[1]) == 0); +} + int main() { #ifdef _WIN32 auto hostDirectorySpelling = std::filesystem::current_path().native(); CharUpperBuffW(hostDirectorySpelling.data(), static_cast(hostDirectorySpelling.size())); Require(SetCurrentDirectoryW(hostDirectorySpelling.c_str()) != 0); #endif + CheckProcessQueries(); Require(setsid_nid_postfix() == -1 && Error() == 1); Require(umask_nid_postfix(0) == 022); Require(umask_nid_postfix(07777) == 0); Require(umask_nid_postfix(022) == 07777); + CheckCondattrClock(); 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)); DirectoryRenameTests(root); + OwnershipAndLinkTests(root); const auto file = (root / "file.txt").string(); std::ofstream(file).put('x'); CheckPathconf(root, file); const int descriptor = open_nid_postfix(file.c_str(), 0, 0); Require(descriptor >= 0); - unsigned char viaPublic[472] = {}; - unsigned char viaSyscall[472] = {}; - Require(fstatfs_nid_postfix(descriptor, viaPublic) == 0 && _fstatfs_nid_postfix(descriptor, viaSyscall) == 0); + KernelStatfs publicStats{}; + auto* viaPublic = reinterpret_cast(&publicStats); + KernelStatfs syscallStats{}; + auto* viaSyscall = reinterpret_cast(&syscallStats); + Require(fstatfs_nid_postfix(descriptor, reinterpret_cast(viaPublic)) == 0 && _fstatfs_nid_postfix(descriptor, reinterpret_cast(viaSyscall)) == 0); Require(std::memcmp(viaPublic, viaSyscall, 4) == 0 && std::memcmp(viaPublic + 16, viaSyscall + 16, 8) == 0); Require(std::memcmp(viaPublic + 280, viaSyscall + 280, 16) == 0); Require(fstatfs_nid_postfix(descriptor, nullptr) == -1 && Error() == 14); + FileStat viaFstat{}; + FileStat viaUnderscore{}; + Require(fstat_nid_postfix(descriptor, &viaFstat) == 0 && _fstat_nid_postfix(descriptor, &viaUnderscore) == 0); + Require(std::memcmp(&viaFstat, &viaUnderscore, sizeof(FileStat)) == 0 && viaFstat.st_size == 1); + Require(fstat_nid_postfix(descriptor, nullptr) == -1 && Error() == 14); + CheckDescriptorPathconf(fpathconf_nid_postfix, descriptor); + CheckDescriptorPathconf(_fpathconf_nid_postfix, descriptor); + CheckPipePathconf(fpathconf_nid_postfix); + CheckPipePathconf(_fpathconf_nid_postfix); Require(close_nid_postfix(descriptor) == 0); - Require(fstatfs_nid_postfix(-1, viaPublic) == -1 && Error() == 9); + Require(fstatfs_nid_postfix(-1, reinterpret_cast(viaPublic)) == -1 && Error() == 9); + Require(fstat_nid_postfix(descriptor, &viaFstat) == -1 && Error() == 9); + Require(fstat_nid_postfix(-1, &viaFstat) == -1 && Error() == 9); + Require(fpathconf_nid_postfix(descriptor, 4) == -1 && Error() == 9); + Require(_fpathconf_nid_postfix(descriptor, 4) == -1 && Error() == 9); + const int dirDescriptor = open_nid_postfix(root.generic_string().c_str(), 0, 0); + Require(dirDescriptor >= 0); + CheckDescriptorPathconf(fpathconf_nid_postfix, dirDescriptor); + CheckDescriptorPathconf(_fpathconf_nid_postfix, dirDescriptor); + Require(close_nid_postfix(dirDescriptor) == 0); char buffer[64]; for (const auto& component : {std::string("missing"), std::string("file.txt")}) { const auto prefix = root.generic_string() + "/" + component + "/../"; @@ -295,6 +610,19 @@ int main() { Require(symlink_nid_postfix("file.txt", created.c_str()) == -1 && Error() == expected); Require(std::filesystem::hard_link_count(file) == 1 && !std::filesystem::exists(root / "new.txt")); } + for (const std::size_t length : {std::size_t{1024}, std::size_t{1100}}) { + const auto tooLong = std::string(length, 'a'); + const auto created = (root / "target-too-long").string(); + Require(symlink_nid_postfix(tooLong.c_str(), created.c_str()) == -1 && Error() == 63); + Require(!std::filesystem::exists(std::filesystem::symlink_status(root / "target-too-long"))); + } + { + const auto longest = std::string(1023, 'a'); + const auto created = (root / "target-longest").string(); + const int made = symlink_nid_postfix(longest.c_str(), created.c_str()); + Require(made == 0 || Error() != 63); + if (made == 0) Require(RemoveSymlink(created)); + } Require(readlink_nid_postfix(file.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22); Require(readlink_nid_postfix(root.string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22); Require(readlink_nid_postfix((root / "missing").string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 2); @@ -310,10 +638,67 @@ int main() { Require(readlinkat_nid_postfix(-100, nullptr, buffer, sizeof(buffer)) == -1 && Error() == 14); #ifndef _WIN32 Require(readlink_nid_postfix((root / std::string(300, 'n')).string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 63); +#else + const auto setReparseTag = [](const std::filesystem::path& path, DWORD tag) { + const HANDLE handle = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + Require(handle != INVALID_HANDLE_VALUE); + std::array reparseData{}; + REPARSE_GUID_DATA_BUFFER reparseHeader{}; + reparseHeader.ReparseTag = tag; + reparseHeader.ReparseDataLength = 4; + reparseHeader.ReparseGuid = {0x6a1e3d0b, 0x1f6c, 0x4c1e, {0x9b, 0x52, 0x3d, 0x8e, 0x41, 0x07, 0x5c, 0x2a}}; + std::memcpy(reparseData.data(), &reparseHeader, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE); + DWORD returned = 0; + const bool set = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, reparseData.data(), static_cast(reparseData.size()), + nullptr, 0, &returned, nullptr) != 0; + if (!set) Require(GetLastError() == ERROR_PRIVILEGE_NOT_HELD || GetLastError() == ERROR_ACCESS_DENIED); + Require(CloseHandle(handle) != 0); + if (!set) { + Require(std::filesystem::remove(path)); + std::fprintf(stderr, "Windows reparse point checks skipped: setting a reparse point on a file needs rights this user lacks\n"); + return false; + } + Require((GetFileAttributesW(path.c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0); + return true; + }; + const auto clearReparseTag = [](const std::filesystem::path& path, DWORD tag) { + const HANDLE handle = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + Require(handle != INVALID_HANDLE_VALUE); + REPARSE_GUID_DATA_BUFFER reparseHeader{}; + reparseHeader.ReparseTag = tag; + reparseHeader.ReparseGuid = {0x6a1e3d0b, 0x1f6c, 0x4c1e, {0x9b, 0x52, 0x3d, 0x8e, 0x41, 0x07, 0x5c, 0x2a}}; + DWORD returned = 0; + Require(DeviceIoControl(handle, FSCTL_DELETE_REPARSE_POINT, &reparseHeader, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, + nullptr, 0, &returned, nullptr) != 0); + Require(CloseHandle(handle) != 0 && std::filesystem::remove(path)); + }; + const auto tagged = root / "tagged.bin"; + std::ofstream(tagged, std::ios::binary) << "abc"; + if (setReparseTag(tagged, 0x00000ABC)) { + Require(readlink_nid_postfix(tagged.string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22); + clearReparseTag(tagged, 0x00000ABC); + } + const auto surrogate = root / "surrogate.bin"; + std::ofstream(surrogate, std::ios::binary) << "abc"; + if (setReparseTag(surrogate, 0x20000ABC)) { + Require(readlink_nid_postfix(surrogate.string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22); + FileStat surrogateStatus{}; + Require(lstat_nid_postfix(surrogate.string().c_str(), &surrogateStatus) == 0); + Require(surrogateStatus.st_mode == 0100666 && surrogateStatus.st_nlink == 1); + clearReparseTag(surrogate, 0x20000ABC); + } #endif std::error_code error; const auto link = (root / "link").string(); +#ifdef _WIN32 + if (!CreateSymbolicLinkW(std::filesystem::path(link).c_str(), L"sub\\target.txt", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) { + error = std::error_code(GetLastError(), std::system_category()); + } +#else std::filesystem::create_symlink("sub/target.txt", link, error); +#endif if (!error) { std::memset(buffer, '#', sizeof(buffer)); Require(readlink_nid_postfix(link.c_str(), buffer, sizeof(buffer)) == 14); @@ -358,12 +743,26 @@ int main() { Require(link_nid_postfix(nullptr, linked.c_str()) == -1 && Error() == 14); Require(link_nid_postfix(original.c_str(), "") == -1 && Error() == 2); Require(std::filesystem::remove(linked) && std::filesystem::hard_link_count(original) == 1); + CheckSettimeofday(); const int directory = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); Require(directory >= 0); const int fileDescriptor = sceKernelOpen(file.c_str(), SCE_KERNEL_O_RDONLY, 0); Require(fileDescriptor >= 0); Require(fchdir_nid_postfix(-1) == -1 && Error() == 9); Require(fchdir_nid_postfix(fileDescriptor) == -1 && Error() == 20); + int pipeEnds[2] = {-1, -1}; + Require(pipe_nid_postfix(pipeEnds) == 0); + Require(fchdir_nid_postfix(pipeEnds[0]) == -1 && Error() == 22); + Require(fchdir_nid_postfix(pipeEnds[1]) == -1 && Error() == 22); + Require(close_nid_postfix(pipeEnds[0]) == 0 && close_nid_postfix(pipeEnds[1]) == 0); + const int characterDevice = open_nid_postfix("/dev/random", 0, 0); + Require(characterDevice >= 0); + Require(fchdir_nid_postfix(characterDevice) == -1 && Error() == 20); + Require(close_nid_postfix(characterDevice) == 0); + const int socketDescriptor = socket_nid_postfix(2, 2, 0); + Require(socketDescriptor >= 0); + Require(fchdir_nid_postfix(socketDescriptor) == -1 && Error() == 22); + Require(close_nid_postfix(socketDescriptor) == 0); Require(fchdir_nid_postfix(directory) == 0); char directoryPath[1024]; Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath); @@ -395,6 +794,12 @@ int main() { Require(chroot_nid_postfix((root / "file.txt" / "..").string().c_str()) == -1 && Error() == 20); Require(chroot_nid_postfix((root / std::string(256, 'n')).string().c_str()) == -1 && Error() == 63); Require(chroot_nid_postfix(("/" + std::string(1024, 'p')).c_str()) == -1 && Error() == 63); + 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); + Require(symlink_nid_postfix("target", throughFile.c_str()) == -1 && Error() == 20); + Require(link_nid_postfix(file.c_str(), throughFile.c_str()) == -1 && Error() == 20); + Require(link_nid_postfix(throughFile.c_str(), (root / "linked").string().c_str()) == -1 && Error() == 20); Require(ChrootThrows(root.string())); Require(ChrootThrows("/" + root.filename().string() + "/.")); const auto guestRoot = "/" + root.filename().string(); @@ -480,6 +885,7 @@ int main() { CheckSymlinkTargetRestrictions(root); const int base = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); Require(base >= 0); + CheckUnreadablePaths(file, base); const int relative = openat_nid_postfix(base, "file.txt", SCE_KERNEL_O_RDONLY); char byte = 0; Require(relative >= 0 && sceKernelRead(relative, &byte, 1) == 1 && byte == 'x'); @@ -503,6 +909,22 @@ int main() { const int plain = sceKernelOpen(file.c_str(), SCE_KERNEL_O_RDONLY, 0); Require(plain >= 0); Require(openat_nid_postfix(plain, "file.txt", SCE_KERNEL_O_RDONLY) == -1 && Error() == 20); + const int aliased = _openat_nid_postfix(base, "file.txt", SCE_KERNEL_O_RDONLY); + byte = 0; + Require(aliased >= 0 && sceKernelRead(aliased, &byte, 1) == 1 && byte == 'x'); + Require(sceKernelClose(aliased) == 0); + const int aliasMade = _openat_nid_postfix(base, "alias.bin", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL, 0600); + Require(aliasMade >= 0 && sceKernelClose(aliasMade) == 0 && std::filesystem::is_regular_file(root / "alias.bin")); +#ifndef _WIN32 + Require((std::filesystem::status(root / "alias.bin").permissions() & std::filesystem::perms::all) == + (std::filesystem::perms::owner_read | std::filesystem::perms::owner_write)); +#endif + const int aliasWritten = _openat_nid_postfix(base, "alias.bin", SCE_KERNEL_O_WRONLY); + Require(aliasWritten >= 0 && sceKernelClose(aliasWritten) == 0); + Require(_openat_nid_postfix(base, "alias.bin", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL, 0600) == -1 && Error() == 17); + Require(_openat_nid_postfix(base, "file.txt", SCE_KERNEL_O_ACCMODE) == -1 && Error() == 22); + Require(_openat_nid_postfix(-1, "file.txt", SCE_KERNEL_O_RDONLY) == -1 && Error() == 9); + Require(_openat_nid_postfix(plain, "file.txt", SCE_KERNEL_O_RDONLY) == -1 && Error() == 20); Require(readlinkat_nid_postfix(base, "file.txt", buffer, sizeof(buffer)) == -1 && Error() == 22); Require(readlinkat_nid_postfix(base, "missing.bin", buffer, sizeof(buffer)) == -1 && Error() == 2); Require(readlinkat_nid_postfix(base, "file.txt/child", buffer, sizeof(buffer)) == -1 && Error() == 20); @@ -529,6 +951,19 @@ int main() { const std::string longName(256, 'a'); Require(mkdirat_nid_postfix(base, longName.c_str(), 0700) == -1 && Error() == 63); Require(mkdirat_nid_postfix(base, "dir/../dotdot_dir", 0700) == 0 && std::filesystem::is_directory(root / "dotdot_dir")); +#ifdef _WIN32 + for (const DWORD linkFlags : {DWORD(0), DWORD(SYMBOLIC_LINK_FLAG_DIRECTORY)}) { + const auto loopLink = root / "mkdirat_loop"; + if (CreateSymbolicLinkW(loopLink.c_str(), L"mkdirat_loop", linkFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) == 0) { + Require(GetLastError() == ERROR_PRIVILEGE_NOT_HELD); + std::fprintf(stderr, "symbolic links unavailable: the mkdirat loop case not tested\n"); + break; + } + Require(mkdirat_nid_postfix(base, "mkdirat_loop/child", 0700) == -1 && Error() == 62); + Require(mkdirat_nid_postfix(base, "mkdirat_loop", 0700) == -1 && Error() == 17); + Require((linkFlags != 0 ? RemoveDirectoryW(loopLink.c_str()) : DeleteFileW(loopLink.c_str())) != 0); + } +#endif Require(unlinkat_nid_postfix(plain, "file.txt", 0) == -1 && Error() == 20); Require(unlinkat_nid_postfix(-1, "file.txt", 0) == -1 && Error() == 9); Require(sceKernelClose(plain) == 0); @@ -546,7 +981,8 @@ int main() { Require(unlinkat_nid_postfix(base, "file.txt", 0x800) == -1 && Error() == 20); Require(unlinkat_nid_postfix(base, ".", 0x800) == -1 && Error() == 22); Require(unlinkat_nid_postfix(base, "..", 0x800) == -1 && Error() == 22); - Require(unlinkat_nid_postfix(base, ".", 0) == -1 && Error() == 1); + Require(unlinkat_nid_postfix(base, ".", 0) == -1 && Error() == 22); + Require(unlinkat_nid_postfix(base, "..", 0) == -1 && Error() == 22); std::filesystem::create_directory(root / "usub"); Require(unlinkat_nid_postfix(base, "usub", 0) == -1 && Error() == 1); std::filesystem::create_directory(root / "nonempty"); @@ -636,10 +1072,47 @@ int main() { Require(symlink_nid_postfix("loop", (root / "loop").string().c_str()) == 0); Require(fchmodat_nid_postfix(base, "loop/x", 0600, 0) == -1 && Error() == 62); Require(fchmodat_nid_postfix(base, longName.c_str(), 0600, 0) == -1 && Error() == 63); +#endif + const auto modeFile = (root / "file.txt").string(); +#ifndef _WIN32 + const auto ownerOnly = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write; + const auto readable = ownerOnly | std::filesystem::perms::group_read | std::filesystem::perms::others_read; +#endif + Require(lchmod_nid_postfix(modeFile.c_str(), 0600) == 0); +#ifndef _WIN32 + Require((std::filesystem::status(modeFile).permissions() & std::filesystem::perms::all) == ownerOnly); +#endif + Require(lchmod_nid_postfix(guestFile.c_str(), 0100644) == 0); +#ifndef _WIN32 + Require((std::filesystem::status(modeFile).permissions() & std::filesystem::perms::all) == readable); +#endif + Require(lchmod_nid_postfix((root / "missing.bin").string().c_str(), 0600) == -1 && Error() == 2); + Require(lchmod_nid_postfix(nullptr, 0600) == -1 && Error() == 14); + Require(lchmod_nid_postfix("", 0600) == -1 && Error() == 2); +#ifndef _WIN32 + Require(lchmod_nid_postfix((root / "loop" / "x").string().c_str(), 0600) == -1 && Error() == 62); + Require(lchmod_nid_postfix((root / longName).string().c_str(), 0600) == -1 && Error() == 63); + const auto modeLink = (root / "mode-link.txt").string(); + Require(symlink_nid_postfix("file.txt", modeLink.c_str()) == 0); +#ifdef __APPLE__ + Require(lchmod_nid_postfix(modeLink.c_str(), 0600) == 0); + Require((std::filesystem::symlink_status(modeLink).permissions() & std::filesystem::perms::all) == ownerOnly); +#else + bool lchmodThrew = false; + try { lchmod_nid_postfix(modeLink.c_str(), 0600); } catch (const std::exception&) { lchmodThrew = true; } + Require(lchmodThrew); +#endif + Require((std::filesystem::status(modeFile).permissions() & std::filesystem::perms::all) == readable); + Require(RemoveSymlink(modeLink)); #endif Require(fchown_nid_postfix(base, 0, 0) == 0); Require(fchown_nid_postfix(base, 0xFFFFFFFFu, 0xFFFFFFFFu) == 0); + Require(fchown_nid_postfix(base, 0, 0xFFFFFFFFu) == 0); + Require(fchown_nid_postfix(base, 0xFFFFFFFFu, 0) == 0); Require(fchown_nid_postfix(-1, 0, 0) == -1 && Error() == 9); + Require(fchown_nid_postfix(-1, 1, 0) == -1 && Error() == 9); + Require(fchown_nid_postfix(-1, 0, 1) == -1 && Error() == 9); + Require(fchown_nid_postfix(-1, 1, 1) == -1 && Error() == 9); bool fchownThrew = false; try { fchown_nid_postfix(base, 1, 0); } catch (const std::exception&) { fchownThrew = true; } Require(fchownThrew); @@ -685,20 +1158,27 @@ int main() { Require(junctionText.find('?') == std::string::npos && std::filesystem::equivalent(junctionText, junctionTarget, junctionError) && !junctionError); Require(std::filesystem::remove(junction) && std::filesystem::remove(junctionTarget)); #endif -#ifndef _WIN32 if (created == 0) { const auto throughLink = (root / "through-link.bin").string(); Require(link_nid_postfix(symbolic.c_str(), throughLink.c_str()) == 0); +#ifdef _WIN32 + Require((GetFileAttributesW(std::filesystem::path(throughLink).c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) == 0); +#else Require(!std::filesystem::is_symlink(std::filesystem::symlink_status(throughLink))); +#endif Require(std::filesystem::equivalent(throughLink, original) && std::filesystem::hard_link_count(original) == 2); Require(std::filesystem::remove(throughLink) && std::filesystem::hard_link_count(original) == 1); Require(link_nid_postfix((root / "dangling").string().c_str(), throughLink.c_str()) == -1 && Error() == 2); - const auto directoryLink = (root / "directory-link").string(); - Require(symlink_nid_postfix(".", directoryLink.c_str()) == 0); - Require(link_nid_postfix(directoryLink.c_str(), throughLink.c_str()) == -1 && Error() == 1); + const auto dotLink = (root / "dot-link").string(); + Require(symlink_nid_postfix(".", dotLink.c_str()) == 0); + Require(link_nid_postfix(dotLink.c_str(), throughLink.c_str()) == -1 && Error() == 1); +#ifdef _WIN32 + Require(symlink_nid_postfix("loop-b", (root / "loop-a").string().c_str()) == 0); + Require(symlink_nid_postfix("loop-a", (root / "loop-b").string().c_str()) == 0); + Require(link_nid_postfix((root / "loop-a").string().c_str(), throughLink.c_str()) == -1 && Error() == 62); +#endif Require(!std::filesystem::exists(std::filesystem::symlink_status(throughLink))); } -#endif RemoveTree(root); return 0; } diff --git a/core/libs/tests/GuestKernelSysAio.cpp b/core/libs/tests/GuestKernelSysAio.cpp new file mode 100644 index 0000000000..200d3290b8 --- /dev/null +++ b/core/libs/tests/GuestKernelSysAio.cpp @@ -0,0 +1,202 @@ +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +} + +extern "C" { + +void AioHoldWorkerForTest_nid_no_patch(bool hold); +} + +namespace { + +constexpr int Ebadf = 9; +constexpr int Efault = 14; +constexpr int Einval = 22; +constexpr int Eagain = 35; +constexpr int Einprogress = 36; +constexpr int Eopnotsupp = 45; +constexpr int Ecanceled = 85; +constexpr int ORdonly = 0; +constexpr int ORdwr = 2; +constexpr int OCreat = 0x200; +constexpr int OSync = 0x80; +constexpr int ODsync = 0x1000000; +constexpr int AioCanceled = 1; +constexpr int AioAllDone = 3; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "check failed at line %u\n", static_cast(location.line())); + std::exit(1); +} + +int Error() { + return *__error_nid_postfix(); +} + +GuestAiocb Request(int descriptor, void* buffer, std::uint64_t bytes, std::int64_t offset = 0) { + GuestAiocb request{}; + request.fildes = descriptor; + request.buf = buffer; + request.nbytes = bytes; + request.offset = offset; + return request; +} + +void Wait(GuestAiocb& request) { + const GuestAiocb* list[] = {&request}; + Require(aio_suspend_nid_postfix(list, 1, nullptr) == 0); +} + +void BeforeUse() { + GuestAiocb request{}; + const GuestAiocb* list[] = {&request}; + Require(aio_error_nid_postfix(&request) == Einval); + Require(aio_return_nid_postfix(&request) == -1 && Error() == Einval); + Require(aio_suspend_nid_postfix(list, 1, nullptr) == -1 && Error() == Eagain); +} + +void Validation(int file, int readOnly) { + char buffer[8] = {}; + Require(aio_read_nid_postfix(nullptr) == -1 && Error() == Efault); + + auto bad = Request(-1, buffer, sizeof(buffer)); + Require(aio_read_nid_postfix(&bad) == -1 && Error() == Ebadf); + Require(bad.status == -1 && bad.error == Ebadf && aio_error_nid_postfix(&bad) == Ebadf); + + auto readOnlyWrite = Request(readOnly, buffer, sizeof(buffer)); + Require(aio_write_nid_postfix(&readOnlyWrite) == -1 && Error() == Ebadf); + + auto negative = Request(file, buffer, sizeof(buffer), -1); + Require(aio_read_nid_postfix(&negative) == -1 && Error() == Einval && negative.error == Einval); + + auto huge = Request(file, buffer, std::uint64_t{std::numeric_limits::max()} + 1); + Require(aio_read_nid_postfix(&huge) == -1 && Error() == Einval && huge.error == 0); + + auto notify = Request(file, buffer, sizeof(buffer)); + notify.sigevent.notify = 2; + Require(aio_read_nid_postfix(¬ify) == -1 && Error() == Einval && notify.error == Einval); + notify.sigevent = {1, 0, 0, {}}; + Require(aio_read_nid_postfix(¬ify) == -1 && Error() == Einval); + + int pipe[2] = {-1, -1}; + Require(pipe_nid_postfix(pipe) == 0); + auto piped = Request(pipe[0], buffer, sizeof(buffer)); + Require(aio_read_nid_postfix(&piped) == -1 && Error() == Eopnotsupp && piped.error == Eopnotsupp); + auto pipeSync = Request(pipe[1], nullptr, 0); + Require(aio_fsync_nid_postfix(OSync, &pipeSync) == -1 && Error() == Einval); + Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0); + + auto sync = Request(file, nullptr, 0); + Require(aio_fsync_nid_postfix(0, &sync) == -1 && Error() == Einval); + Require(aio_fsync_nid_postfix(ODsync, &sync) == -1 && Error() == Einval); +} + +void ReadAndWrite(int file) { + char text[] = "asynchronous"; + auto write = Request(file, text, 12, 4); + Require(aio_write_nid_postfix(&write) == 0); + Wait(write); + Require(aio_error_nid_postfix(&write) == 0); + Require(aio_return_nid_postfix(&write) == 12 && write.status == 12 && write.error == 0); + Require(aio_return_nid_postfix(&write) == -1 && Error() == Einval); + Require(aio_error_nid_postfix(&write) == Einval); + + char read[16] = {}; + auto request = Request(file, read, sizeof(read), 6); + Require(aio_read_nid_postfix(&request) == 0); + Wait(request); + Require(aio_return_nid_postfix(&request) == 10 && std::memcmp(read, "ynchronous", 10) == 0); + + auto empty = Request(file, read, 0, 0); + Require(aio_read_nid_postfix(&empty) == 0); + Wait(empty); + Require(aio_return_nid_postfix(&empty) == 0); + + const GuestAiocb* none[] = {&request}; + Require(aio_suspend_nid_postfix(none, 1, nullptr) == 0); + Require(aio_suspend_nid_postfix(none, 0, nullptr) == 0); + Require(aio_suspend_nid_postfix(none, -1, nullptr) == -1 && Error() == Einval); + Require(aio_suspend_nid_postfix(none, 257, nullptr) == -1 && Error() == Einval); + const KernelTimespec badTime{0, 1000000000}; + Require(aio_suspend_nid_postfix(none, 1, &badTime) == -1 && Error() == Einval); +} + +void Cancel(int file) { + char buffer[4] = {}; + AioHoldWorkerForTest_nid_no_patch(true); + auto first = Request(file, buffer, sizeof(buffer)); + auto second = Request(file, buffer, sizeof(buffer)); + Require(aio_read_nid_postfix(&first) == 0 && aio_read_nid_postfix(&second) == 0); + Require(first.error == Einprogress && aio_error_nid_postfix(&first) == Einprogress); + const GuestAiocb* list[] = {&first}; + const KernelTimespec brief{0, 5000000}; + Require(aio_suspend_nid_postfix(list, 1, &brief) == -1 && Error() == Eagain); + + Require(aio_cancel_nid_postfix(file, &first) == AioCanceled); + Require(aio_error_nid_postfix(&first) == Ecanceled && aio_error_nid_postfix(&second) == Einprogress); + Require(aio_suspend_nid_postfix(list, 1, &brief) == 0); + Require(aio_return_nid_postfix(&first) == -1 && Error() == Ecanceled); + Require(first.status == -1 && first.error == Ecanceled); + Require(aio_return_nid_postfix(&first) == -1 && Error() == Einval); + Require(aio_cancel_nid_postfix(file, &first) == AioAllDone); + + Require(aio_cancel_nid_postfix(file, nullptr) == AioCanceled); + Require(aio_return_nid_postfix(&second) == -1 && Error() == Ecanceled); + Require(aio_cancel_nid_postfix(file, nullptr) == AioAllDone); + Require(aio_cancel_nid_postfix(-1, nullptr) == -1 && Error() == Ebadf); + AioHoldWorkerForTest_nid_no_patch(false); +} + +void SyncOrder(int file) { + char text[] = "ordered"; + AioHoldWorkerForTest_nid_no_patch(true); + auto write = Request(file, text, 7, 0); + auto sync = Request(file, nullptr, 0); + Require(aio_write_nid_postfix(&write) == 0 && aio_fsync_nid_postfix(OSync, &sync) == 0); + Require(aio_error_nid_postfix(&sync) == Einprogress); + AioHoldWorkerForTest_nid_no_patch(false); + + GuestAiocb* done = nullptr; + Require(aio_waitcomplete_nid_postfix(&done, nullptr) == 7 && done == &write); + Require(aio_waitcomplete_nid_postfix(&done, nullptr) == 0 && done == &sync && sync.status == 0); + const KernelTimespec zero{0, 0}; + Require(aio_waitcomplete_nid_postfix(&done, &zero) == -1 && Error() == Eagain && done == nullptr); + const KernelTimespec brief{0, 5000000}; + Require(aio_waitcomplete_nid_postfix(&done, &brief) == -1 && Error() == Eagain); + const KernelTimespec badTime{0, -1}; + Require(aio_waitcomplete_nid_postfix(&done, &badTime) == -1 && Error() == Einval); +} + +} + +int main() { + const auto path = "anyps5-kernel-sys-aio-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".bin"; + BeforeUse(); + const int file = open_nid_postfix(path.c_str(), ORdwr | OCreat, 0600); + const int readOnly = open_nid_postfix(path.c_str(), ORdonly, 0); + Require(file >= 0 && readOnly >= 0); + Validation(file, readOnly); + ReadAndWrite(file); + Cancel(file); + SyncOrder(file); + Require(close_nid_postfix(readOnly) == 0 && close_nid_postfix(file) == 0); + Require(std::filesystem::remove(path)); + std::puts("guest_kernel_sys_aio passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysBarrier.cpp b/core/libs/tests/GuestKernelSysBarrier.cpp new file mode 100644 index 0000000000..084228c772 --- /dev/null +++ b/core/libs/tests/GuestKernelSysBarrier.cpp @@ -0,0 +1,83 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +static constexpr int PROCESS_PRIVATE = 0; +static constexpr int PROCESS_SHARED = 1; +static constexpr int BARRIER_SERIAL_THREAD = -1; +static constexpr int MS_ASYNC = 0x1; +static constexpr int MS_INVALIDATE = 0x2; +static constexpr unsigned WAITERS = 4; + +static void Require(bool value) { if (!value) std::abort(); } + +struct Context { + PthreadBarrierPrivate* barrier = nullptr; + std::atomic serial{0}; + std::atomic released{0}; +}; + +static void* APS5_VABI Waiter(void* arg) { + auto& context = *static_cast(arg); + const int result = scePthreadBarrierWait(&context.barrier); + if (result == BARRIER_SERIAL_THREAD) ++context.serial; + else if (result == 0) ++context.released; + else std::abort(); + return nullptr; +} + +int main() { + PthreadBarrierattrPrivate* attr = nullptr; + Require(scePthreadBarrierattrInit(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierattrInit(&attr) == 0 && attr != nullptr); + int pshared = -1; + Require(scePthreadBarrierattrGetpshared(&attr, &pshared) == 0 && pshared == PROCESS_PRIVATE); + Require(scePthreadBarrierattrSetpshared(&attr, PROCESS_SHARED) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierattrSetpshared(&attr, PROCESS_PRIVATE) == 0); + + Context context; + Require(scePthreadBarrierInit(&context.barrier, &attr, 0, "zero") == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierInit(&context.barrier, &attr, 0x80000000u, "over") == SCE_KERNEL_ERROR_EINVAL && context.barrier == nullptr); + Require(scePthreadBarrierInit(nullptr, &attr, WAITERS, "null") == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierInit(&context.barrier, &attr, WAITERS, "barrier") == 0 && context.barrier != nullptr); + Require(scePthreadBarrierattrDestroy(&attr) == 0 && attr == nullptr); + Require(scePthreadBarrierattrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL); + + for (int round = 0; round < 2; ++round) { + context.serial = 0; + context.released = 0; + std::array threads{}; + for (auto& thread : threads) Require(scePthreadCreate(&thread, nullptr, Waiter, &context, nullptr) == 0); + for (auto& thread : threads) Require(scePthreadJoin(thread, nullptr) == 0); + Require(context.serial == 1 && context.released == static_cast(WAITERS) - 1); + } + Require(scePthreadBarrierDestroy(&context.barrier) == 0 && context.barrier == nullptr); + Require(scePthreadBarrierDestroy(&context.barrier) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierWait(nullptr) == SCE_KERNEL_ERROR_EINVAL); + + PthreadBarrierPrivate* single = nullptr; + Require(scePthreadBarrierInit(&single, nullptr, 1, nullptr) == 0); + Require(scePthreadBarrierWait(&single) == BARRIER_SERIAL_THREAD); + Require(scePthreadBarrierDestroy(&single) == 0); + + int clock = 0; + int posixClock = 0; + Require(scePthreadGetcpuclockid(scePthreadSelf(), &clock) == 0); + Require(pthread_getcpuclockid_nid_postfix(scePthreadSelf(), &posixClock) == 0 && clock == posixClock); + Require(scePthreadGetcpuclockid(scePthreadSelf(), nullptr) == SCE_KERNEL_ERROR_EFAULT); + Require(scePthreadGetcpuclockid(nullptr, &clock) == SCE_KERNEL_ERROR_EINVAL); + + Require(pthread_setconcurrency_nid_postfix(3) == 0 && scePthreadGetconcurrency() == 3); + Require(pthread_setconcurrency_nid_postfix(0) == 0 && scePthreadGetconcurrency() == 0); + + auto* unmapped = reinterpret_cast(static_cast(0x4000)); + Require(sceKernelMsync(unmapped, 0x4000, 0) == SCE_KERNEL_ERROR_ENOMEM); + Require(sceKernelMsync(unmapped, 0x4000, MS_ASYNC | MS_INVALIDATE) == SCE_KERNEL_ERROR_EINVAL); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysConsoleIdentity.cpp b/core/libs/tests/GuestKernelSysConsoleIdentity.cpp new file mode 100644 index 0000000000..f01ac5f625 --- /dev/null +++ b/core/libs/tests/GuestKernelSysConsoleIdentity.cpp @@ -0,0 +1,71 @@ +#include "prx/libkernel_sys/libkernel_sys.hpp" +#include "prx/libc/libc.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int GuestEperm = 1; +constexpr int GuestEsrch = 3; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "KERNEL SYS CONSOLE IDENTITY: %s\n", message); + std::abort(); + } +} + +void CheckConsoleKind() { + Require(sceKernelIsCEX() == 1, "the console must be reported as a retail unit"); + Require(sceKernelIsDevKit() == 0, "the console must not be reported as a development kit"); +} + +void CheckAuthInfo() { + struct { + KernelAuthInfo value; + std::uint64_t guard; + } info; + *__error_nid_postfix() = 0; + Require(get_authinfo_nid_postfix(0, nullptr) == -1 && *__error_nid_postfix() == GuestEperm, "a null result must fail with EPERM"); + std::memset(&info, 0x5a, sizeof(info)); + *__error_nid_postfix() = 0; + Require(get_authinfo_nid_postfix(getpid_nid_postfix() + 1, &info.value) == -1 && *__error_nid_postfix() == GuestEsrch, + "another process must fail with ESRCH"); + for (const auto byte : std::bit_cast>(info)) + Require(byte == 0x5a, "a refused call must not write the result"); + for (const int pid : {0, getpid_nid_postfix()}) { + std::memset(&info, 0x5a, sizeof(info)); + Require(get_authinfo_nid_postfix(pid, &info.value) == 0, "the guest process must be described"); + Require(info.value.programAuthorityId == 0 && info.value.capabilities[0] == 0x2000000000000000, "the authority ID or first capability word is wrong"); + const auto bytes = std::bit_cast>(info.value); + for (std::size_t i = 16; i < bytes.size(); ++i) Require(bytes[i] == 0, "the rest of the result must be cleared"); + Require(info.guard == 0x5a5a5a5a5a5a5a5a, "get_authinfo wrote past its 0x88-byte result"); + } +} + +void CheckOpenPsId() { + std::array plain{}; + std::array system; + system.fill(0xa5); + *__error_nid_postfix() = GuestEsrch; + Require(sceKernelGetOpenPsIdForSystem(nullptr) == 22, "a null system OpenPSID must return positive EINVAL"); + Require(*__error_nid_postfix() == GuestEsrch, "a null system OpenPSID must leave errno unchanged"); + for (auto byte : system) Require(byte == 0xa5, "a null system OpenPSID must not write output"); + Require(sceKernelGetOpenPsId(plain.data()) == 0, "the OpenPSID must be readable"); + Require(sceKernelGetOpenPsIdForSystem(system.data()) == 0, "the system OpenPSID must be readable"); + Require(std::memcmp(plain.data(), system.data(), plain.size()) == 0, "both OpenPSID queries must agree"); + for (std::size_t i = plain.size(); i < system.size(); ++i) Require(system[i] == 0xa5, "the system OpenPSID wrote past 16 bytes"); +} + +} + +int main() { + CheckConsoleKind(); + CheckAuthInfo(); + CheckOpenPsId(); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysConstants.cpp b/core/libs/tests/GuestKernelSysConstants.cpp new file mode 100644 index 0000000000..8565415fc8 --- /dev/null +++ b/core/libs/tests/GuestKernelSysConstants.cpp @@ -0,0 +1,45 @@ +#include "SceTypes.hpp" +#include "prx/libkernel_sys/libkernel_sys.hpp" +#include + +namespace { + +constexpr int GUEST_ENOTSUP = 45; +constexpr int SCE_KERNEL_ERROR_EPERM = static_cast(0x80020001); +constexpr int SCE_KERNEL_ERROR_ENOENT = static_cast(0x80020002); +constexpr int SCE_KERNEL_ERROR_ENOTSUP = static_cast(0x8002002d); +constexpr int SCE_KERNEL_ERROR_ENOSYS = static_cast(0x8002004e); + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + Require(pthread_switch_add_np_nid_postfix() == GUEST_ENOTSUP); + Require(pthread_switch_delete_np_nid_postfix() == GUEST_ENOTSUP); + + Require(sceKernelAddResource() == 0); + Require(sceKernelEventLogInit() == 0); + Require(sceKernelIsGenuineKratosCex() == 0); + Require(sceKernelReleaseTraceDirectMemory() == 0); + + Require(sceKernelHasNeoMode() == 1); + Require(sceKernelIsAuthenticNeo() == 1); + Require(sceKernelIsGenuineN() == 1); + + Require(sceKernelCreateEport() == SCE_KERNEL_ERROR_EPERM); + Require(sceKernelDeleteEport() == SCE_KERNEL_ERROR_EPERM); + Require(sceKernelOpenEport() == SCE_KERNEL_ERROR_EPERM); + Require(sceKernelCloseEport() == SCE_KERNEL_ERROR_EPERM); + Require(sceKernelTriggerEport() == SCE_KERNEL_ERROR_EPERM); + + Require(sceKernelQueryTraceMemory() == SCE_KERNEL_ERROR_ENOENT); + + Require(sceKernelIccGetCPMode() == SCE_KERNEL_ERROR_ENOTSUP); + Require(sceKernelIccSetCPMode() == SCE_KERNEL_ERROR_ENOTSUP); + + Require(sceKernelClearGameDirectMemory() == SCE_KERNEL_ERROR_ENOSYS); + Require(sceKernelMapTraceMemory() == SCE_KERNEL_ERROR_ENOSYS); + Require(sceKernelTraceMemoryTypeProtect() == SCE_KERNEL_ERROR_ENOSYS); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysKtimer.cpp b/core/libs/tests/GuestKernelSysKtimer.cpp new file mode 100644 index 0000000000..bb189576b4 --- /dev/null +++ b/core/libs/tests/GuestKernelSysKtimer.cpp @@ -0,0 +1,180 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +} + +namespace { + +constexpr int Efault = 14; +constexpr int Einval = 22; +constexpr int Eagain = 35; +constexpr int ClockRealtime = 0; +constexpr int ClockMonotonic = 4; +constexpr int ClockUptime = 5; +constexpr int TimerAbstime = 1; +constexpr std::int64_t Millisecond = 1000000; +constexpr std::int64_t Second = 1000000000; + +const GuestSigevent None{0, 0, 0, {}}; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "check failed at line %u\n", static_cast(location.line())); + std::exit(1); +} + +int Error() { + return *__error_nid_postfix(); +} + +std::int64_t Nanoseconds(const KernelTimespec& time) { + return time.tv_sec * Second + time.tv_nsec; +} + +int Create(int clock = ClockMonotonic) { + int id = -1; + Require(ktimer_create_nid_postfix(clock, &None, &id) == 0); + return id; +} + +GuestItimerspec Get(int id) { + GuestItimerspec value{{-1, -1}, {-1, -1}}; + Require(ktimer_gettime_nid_postfix(id, &value) == 0); + return value; +} + +void Sleep(std::int64_t nanoseconds) { + std::this_thread::sleep_for(std::chrono::nanoseconds(nanoseconds)); +} + +void Creation() { + int id = -1; + const GuestSigevent badNotify{2, 0, 0, {}}; + const GuestSigevent noSignal{1, 0, 0, {}}; + const GuestSigevent badSignal{4, 129, 0, {}}; + Require(ktimer_create_nid_postfix(ClockUptime, &None, &id) == -1 && Error() == Einval && id == -1); + Require(ktimer_create_nid_postfix(-1, &None, &id) == -1 && Error() == Einval); + Require(ktimer_create_nid_postfix(ClockMonotonic, &badNotify, &id) == -1 && Error() == Einval); + Require(ktimer_create_nid_postfix(ClockMonotonic, &noSignal, &id) == -1 && Error() == Einval); + Require(ktimer_create_nid_postfix(ClockMonotonic, &badSignal, &id) == -1 && Error() == Einval); + Require(ktimer_create_nid_postfix(ClockMonotonic, &None, nullptr) == -1 && Error() == Efault); + + std::vector ids; + for (int expected = 3; expected < 32; ++expected) { + ids.push_back(Create(expected % 2 == 0 ? ClockRealtime : ClockMonotonic)); + Require(ids.back() == expected); + } + Require(ktimer_create_nid_postfix(ClockMonotonic, &None, &id) == -1 && Error() == Eagain); + Require(ktimer_delete_nid_postfix(7) == 0); + Require(Create() == 7); + for (const int each : ids) Require(ktimer_delete_nid_postfix(each) == 0); +} + +void InvalidIds() { + GuestItimerspec value{}; + const int id = Create(); + Require(ktimer_delete_nid_postfix(id) == 0); + for (const int bad : {-1, 0, 2, id, 32}) { + Require(ktimer_delete_nid_postfix(bad) == -1 && Error() == Einval); + Require(ktimer_settime_nid_postfix(bad, 0, &value, nullptr) == -1 && Error() == Einval); + Require(ktimer_gettime_nid_postfix(bad, &value) == -1 && Error() == Einval); + Require(ktimer_getoverrun_nid_postfix(bad) == -1 && Error() == Einval); + } + Require(ktimer_settime_nid_postfix(id, 0, nullptr, nullptr) == -1 && Error() == Efault); +} + +void Values() { + const int id = Create(); + GuestItimerspec disarmed = Get(id); + Require(Nanoseconds(disarmed.it_value) == 0 && Nanoseconds(disarmed.it_interval) == 0); + for (const GuestItimerspec bad : {GuestItimerspec{{0, 0}, {-1, 0}}, GuestItimerspec{{0, 0}, {0, -1}}, GuestItimerspec{{0, 0}, {0, Second}}, + GuestItimerspec{{-1, 0}, {1, 0}}, GuestItimerspec{{0, Second}, {1, 0}}}) + Require(ktimer_settime_nid_postfix(id, 0, &bad, nullptr) == -1 && Error() == Einval); + + const GuestItimerspec clear{{-1, 0}, {0, 0}}; + Require(ktimer_settime_nid_postfix(id, 0, &clear, nullptr) == 0); + disarmed = Get(id); + Require(Nanoseconds(disarmed.it_value) == 0 && Nanoseconds(disarmed.it_interval) == 0); + + const GuestItimerspec small{{0, 1}, {10, 0}}; + Require(ktimer_settime_nid_postfix(id, 0, &small, nullptr) == 0); + GuestItimerspec old{}; + const GuestItimerspec later{{0, 0}, {20, 0}}; + Require(ktimer_settime_nid_postfix(id, 0, &later, &old) == 0); + Require(Nanoseconds(old.it_interval) == Millisecond); + Require(Nanoseconds(old.it_value) > 9 * Second && Nanoseconds(old.it_value) <= 10 * Second); + const GuestItimerspec tiny{{0, 0}, {0, 1}}; + Require(ktimer_settime_nid_postfix(id, 0, &tiny, &old) == 0); + Require(Nanoseconds(old.it_value) > 19 * Second && Nanoseconds(old.it_value) <= 20 * Second); + const GuestItimerspec off{}; + Require(ktimer_settime_nid_postfix(id, 0, &off, &old) == 0); + Require(Nanoseconds(old.it_interval) == 0 && Nanoseconds(old.it_value) <= Millisecond); + Require(ktimer_delete_nid_postfix(id) == 0); +} + +void Expiry() { + const int id = Create(); + const GuestItimerspec once{{0, 0}, {0, 30 * Millisecond}}; + Require(ktimer_settime_nid_postfix(id, 0, &once, nullptr) == 0); + const auto running = Get(id); + Require(Nanoseconds(running.it_value) > 0 && Nanoseconds(running.it_value) <= 30 * Millisecond); + Sleep(60 * Millisecond); + const auto expired = Get(id); + Require(Nanoseconds(expired.it_value) == 0 && Nanoseconds(expired.it_interval) == 0); + Require(ktimer_getoverrun_nid_postfix(id) == 0); + + const GuestItimerspec periodic{{0, 10 * Millisecond}, {0, 10 * Millisecond}}; + Require(ktimer_settime_nid_postfix(id, 0, &periodic, nullptr) == 0); + Sleep(55 * Millisecond); + const auto period = Get(id); + Require(Nanoseconds(period.it_interval) == 10 * Millisecond); + Require(Nanoseconds(period.it_value) > 0 && Nanoseconds(period.it_value) <= 10 * Millisecond); + Require(ktimer_getoverrun_nid_postfix(id) == 0); + Require(ktimer_delete_nid_postfix(id) == 0); +} + +void Absolute() { + for (const int clock : {ClockRealtime, ClockMonotonic}) { + const int id = Create(clock); + KernelTimespec now{}; + Require(clock_gettime_nid_postfix(clock, &now) == 0); + const GuestItimerspec future{{0, 0}, {now.tv_sec + 5, now.tv_nsec}}; + Require(ktimer_settime_nid_postfix(id, TimerAbstime, &future, nullptr) == 0); + const auto remaining = Get(id); + Require(Nanoseconds(remaining.it_value) > 4 * Second && Nanoseconds(remaining.it_value) <= 5 * Second); + + const GuestItimerspec past{{0, 0}, {now.tv_sec - 1, now.tv_nsec}}; + Require(ktimer_settime_nid_postfix(id, TimerAbstime, &past, nullptr) == 0); + Require(Nanoseconds(Get(id).it_value) == 0); + const GuestItimerspec pastPeriodic{{1, 0}, {now.tv_sec - 10, now.tv_nsec}}; + Require(ktimer_settime_nid_postfix(id, TimerAbstime, &pastPeriodic, nullptr) == 0); + const auto moved = Get(id); + Require(Nanoseconds(moved.it_value) > 0 && Nanoseconds(moved.it_value) <= Second); + Require(Nanoseconds(moved.it_interval) == Second); + Require(ktimer_delete_nid_postfix(id) == 0); + } +} + +} + +int main() { + Creation(); + InvalidIds(); + Values(); + Expiry(); + Absolute(); + std::puts("guest_kernel_sys_ktimer passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysLogin.cpp b/core/libs/tests/GuestKernelSysLogin.cpp new file mode 100644 index 0000000000..44c5f217ed --- /dev/null +++ b/core/libs/tests/GuestKernelSysLogin.cpp @@ -0,0 +1,76 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" { +void LoginNameForTest_nid_no_patch(const char* name); +} + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "check failed at line %u\n", static_cast(location.line())); + std::exit(1); +} + +int Error() { + return *__error_nid_postfix(); +} + +void Getlogin() { + char expected[33]{}; + const int error = getlogin_r_nid_postfix(expected, static_cast(sizeof(expected))); + char* name = getlogin_nid_postfix(); + if (error != 0) { + Require(name == nullptr && Error() == error); + return; + } + if (expected[0] == '\0') { + Require(name == nullptr); + return; + } + Require(name != nullptr && std::strcmp(name, expected) == 0); + Require(getlogin_nid_postfix() == name); +} + +void GetloginPaths() { + LoginNameForTest_nid_no_patch("player"); + char* name = getlogin_nid_postfix(); + Require(name != nullptr && std::strcmp(name, "player") == 0); + char buffer[7]; + std::memset(buffer, 'x', sizeof(buffer)); + Require(getlogin_r_nid_postfix(buffer, 6) == 34 && buffer[0] == 'x'); + Require(getlogin_r_nid_postfix(buffer, 7) == 0 && std::strcmp(buffer, "player") == 0); + + const std::string longName(40, 'n'); + LoginNameForTest_nid_no_patch(longName.c_str()); + *__error_nid_postfix() = 0; + Require(getlogin_nid_postfix() == nullptr && Error() == 34); + + LoginNameForTest_nid_no_patch(""); + bool threw = false; + try { + getlogin_nid_postfix(); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw); + LoginNameForTest_nid_no_patch(nullptr); +} + +} + +int main() { + Getlogin(); + GetloginPaths(); + std::puts("getlogin passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysModuleInternal.cpp b/core/libs/tests/GuestKernelSysModuleInternal.cpp new file mode 100644 index 0000000000..a51226a5e9 --- /dev/null +++ b/core/libs/tests/GuestKernelSysModuleInternal.cpp @@ -0,0 +1,95 @@ +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +static std::vector List(int (APS5_VABI* list)(KernelModule*, std::size_t, std::size_t*)) { + std::vector handles(256, -1); + std::size_t count = 0; + Require(list(handles.data(), handles.size(), &count) == 0); + Require(count > 0 && count < handles.size()); + handles.resize(count); + return handles; +} + +static void CheckList() { + const auto internal = List(sceKernelGetModuleListInternal); + Require(internal == List(sceKernelGetModuleList)); + std::vector shorter(internal.size(), -1); + std::size_t untouched = 99; + Require(sceKernelGetModuleListInternal(shorter.data(), internal.size() - 1, &untouched) == SCE_KERNEL_ERROR_ENOMEM); + Require(untouched == 99 && shorter.back() == -1); + Require(sceKernelGetModuleListInternal(nullptr, 0, &untouched) == SCE_KERNEL_ERROR_EFAULT); + Require(sceKernelGetModuleListInternal(shorter.data(), shorter.size(), nullptr) == SCE_KERNEL_ERROR_EFAULT); +} + +static void CheckEachModule() { + for (const auto handle : List(sceKernelGetModuleListInternal)) { + ModuleInfoEx extended{}; + extended.st_size = sizeof(ModuleInfoEx); + Require(sceKernelGetModuleInfoInternal(handle, &extended) == 0); + ModuleInfo basic{}; + basic.st_size = sizeof(ModuleInfo); + Require(sceKernelGetModuleInfo(handle, &basic) == 0); + Require(extended.st_size == sizeof(ModuleInfoEx) && extended.id == handle); + Require(std::string(extended.name) == basic.name && extended.segment_count == basic.segment_count); + for (std::uint32_t i = 0; i < basic.segment_count; ++i) + Require(extended.segments[i].address == basic.segments[i].address && extended.segments[i].size == basic.segments[i].size && + extended.segments[i].prot == basic.segments[i].prot); + } +} + +static void CheckMatchesAddressLookup() { + ModuleInfoEx byAddress{}; + byAddress.st_size = sizeof(ModuleInfoEx); + Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(&sceKernelGetModuleInfo), 1, &byAddress) == 0); + ModuleInfoEx byHandle{}; + byHandle.st_size = sizeof(ModuleInfoEx); + Require(sceKernelGetModuleInfoInternal(byAddress.id, &byHandle) == 0); + Require(byAddress.st_size == 0 && byHandle.st_size == sizeof(ModuleInfoEx)); + Require(std::memcmp(byHandle.name, byAddress.name, sizeof(ModuleInfoEx) - offsetof(ModuleInfoEx, name)) == 0); +} + +static void CheckRefusals() { + const auto handle = List(sceKernelGetModuleListInternal).front(); + for (const std::uint64_t size : {0x160, 0x1a4, 0x1b0, 0}) { + ModuleInfoEx sized{}; + sized.st_size = size; + sized.name[0] = 'x'; + Require(sceKernelGetModuleInfoInternal(handle, &sized) == SCE_KERNEL_ERROR_EINVAL); + Require(sized.st_size == size && sized.name[0] == 'x'); + } + Require(sceKernelGetModuleInfoInternal(handle, nullptr) == SCE_KERNEL_ERROR_EFAULT); + ModuleInfoEx unknown{}; + unknown.st_size = sizeof(ModuleInfoEx); + Require(sceKernelGetModuleInfoInternal(-1, &unknown) == SCE_KERNEL_ERROR_ESRCH && unknown.name[0] == '\0'); +} + +static void CheckLoadStartSysmodule() { + int res = -1; + Require(sceKernelLoadStartModuleForSysmodule(nullptr, 0, nullptr, 0, nullptr, &res) == static_cast(SCE_KERNEL_ERROR_EFAULT)); + Require(res == 0); + Require(sceKernelLoadStartModuleForSysmodule(nullptr, 0, nullptr, 0, nullptr, nullptr) == static_cast(SCE_KERNEL_ERROR_EFAULT)); + res = -1; + Require(sceKernelLoadStartModuleForSysmodule("/system/common/lib/nonexistent_sysmodule.sprx", 0, nullptr, 0, nullptr, &res) == + static_cast(SCE_KERNEL_ERROR_ENOENT)); + Require(res == 0); +} + +int main() { + CheckList(); + CheckEachModule(); + CheckMatchesAddressLookup(); + CheckRefusals(); + CheckLoadStartSysmodule(); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysPselect.cpp b/core/libs/tests/GuestKernelSysPselect.cpp new file mode 100644 index 0000000000..c4cb2f25a5 --- /dev/null +++ b/core/libs/tests/GuestKernelSysPselect.cpp @@ -0,0 +1,167 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int Eintr = 4; +constexpr int Einval = 22; +constexpr int SigBlock = 1; +constexpr int SigSetmask = 3; +constexpr int SigUsr1 = 30; + +struct Timespec { + std::int64_t seconds; + std::int64_t nanoseconds; +}; + +struct SignalSet { + std::uint32_t bits[4]{}; +}; + +struct DescriptorSet { + std::array words{}; + void Add(int descriptor) { words[descriptor / 64] |= std::uint64_t{1} << (descriptor % 64); } + bool Has(int descriptor) const { return ((words[descriptor / 64] >> (descriptor % 64)) & 1u) != 0; } +}; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "check failed at line %u\n", static_cast(location.line())); + std::exit(1); +} + +int Error() { + return *__error_nid_postfix(); +} + +SignalSet CurrentMask() { + SignalSet current{}; + Require(sigprocmask_nid_postfix(SigBlock, nullptr, ¤t) == 0); + return current; +} + +bool Same(const SignalSet& left, const SignalSet& right) { + return std::memcmp(left.bits, right.bits, sizeof(left.bits)) == 0; +} + +int caught = 0; + +void APS5_VABI Count(int) { + ++caught; +} + +void RaiseBlocked(const SignalSet& blocked) { + Require(sigprocmask_nid_postfix(SigBlock, &blocked, nullptr) == 0); + Require(raise_nid_postfix(SigUsr1) == 0 && caught == 0); +} + +} + +int main() { + int descriptors[2]{-1, -1}; + Require(pipe_nid_postfix(descriptors) == 0); + const int reader = descriptors[0]; + const int writer = descriptors[1]; + SignalSet blocked{}; + blocked.bits[(SigUsr1 - 1) / 32] |= 1u << ((SigUsr1 - 1) % 32); + const SignalSet before = CurrentMask(); + { + DescriptorSet read{}; + read.Add(reader); + const Timespec limit{0, 200000000}; + const auto start = std::chrono::steady_clock::now(); + Require(pselect_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit, &blocked) == 0); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(150)); + Require(!read.Has(reader)); + Require(Same(CurrentMask(), before)); + } + Require(write_nid_postfix(writer, "x", 1) == 1); + { + DescriptorSet read{}; + read.Add(reader); + const Timespec limit{0, 999999999}; + Require(pselect_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit, nullptr) == 1); + Require(read.Has(reader)); + } + { + DescriptorSet read{}; + read.Add(reader); + Require(pselect_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, nullptr, &blocked) == 1); + Require(read.Has(reader)); + Require(Same(CurrentMask(), before)); + } + { + const Timespec invalid{0, 1000000000}; + Require(pselect_nid_postfix(0, nullptr, nullptr, nullptr, &invalid, &blocked) == -1 && Error() == Einval); + Require(Same(CurrentMask(), before)); + const Timespec zero{0, 0}; + Require(pselect_nid_postfix(-1, nullptr, nullptr, nullptr, &zero, nullptr) == -1 && Error() == Einval); + } + Require(signal_nid_postfix(SigUsr1, Count) != reinterpret_cast(static_cast(-1))); + const SignalSet empty{}; + { + RaiseBlocked(blocked); + const SignalSet withUsr1 = CurrentMask(); + DescriptorSet read{}; + read.Add(writer); + const DescriptorSet original = read; + const Timespec limit{0, 300000000}; + const auto start = std::chrono::steady_clock::now(); + Require(pselect_nid_postfix(writer + 1, read.words.data(), nullptr, nullptr, &limit, &empty) == -1 && Error() == Eintr); + Require(std::chrono::steady_clock::now() - start < std::chrono::milliseconds(150)); + Require(caught == 1); + Require(read.words == original.words); + Require(Same(CurrentMask(), withUsr1)); + caught = 0; + } + { + RaiseBlocked(blocked); + Require(pselect_nid_postfix(0, nullptr, nullptr, nullptr, nullptr, &empty) == -1 && Error() == Eintr && caught == 1); + caught = 0; + } + { + RaiseBlocked(blocked); + DescriptorSet read{}; + read.Add(reader); + const Timespec limit{0, 300000000}; + Require(pselect_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit, &empty) == 1 && caught == 1); + Require(read.Has(reader)); + caught = 0; + } + { + RaiseBlocked(blocked); + const Timespec zero{0, 0}; + DescriptorSet read{}; + read.Add(writer); + Require(pselect_nid_postfix(writer + 1, read.words.data(), nullptr, nullptr, &zero, &empty) == 0 && caught == 1); + caught = 0; + } + { + RaiseBlocked(blocked); + const Timespec invalid{0, 1000000000}; + Require(pselect_nid_postfix(0, nullptr, nullptr, nullptr, &invalid, &empty) == -1 && Error() == Einval && caught == 1); + caught = 0; + } + { + RaiseBlocked(blocked); + const Timespec limit{0, 200000000}; + const auto start = std::chrono::steady_clock::now(); + Require(pselect_nid_postfix(0, nullptr, nullptr, nullptr, &limit, &blocked) == 0 && caught == 0); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(150)); + Require(sigprocmask_nid_postfix(SigSetmask, &empty, nullptr) == 0 && caught == 1); + caught = 0; + } + Require(sigprocmask_nid_postfix(SigSetmask, &before, nullptr) == 0); + Require(close_nid_postfix(reader) == 0 && close_nid_postfix(writer) == 0); + std::puts("pselect passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysPthread.cpp b/core/libs/tests/GuestKernelSysPthread.cpp new file mode 100644 index 0000000000..1fbcc5054b --- /dev/null +++ b/core/libs/tests/GuestKernelSysPthread.cpp @@ -0,0 +1,58 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libkernel/Pthread/include/Atfork.hpp" +#include + +static constexpr int SCOPE_PROCESS = 0; +static constexpr int SCOPE_SYSTEM = 2; + +static void Require(bool value) { if (!value) std::abort(); } + +static void APS5_VABI Prepare() {} +static void APS5_VABI Parent() {} +static void APS5_VABI Child() {} + +static void TestScope() { + PthreadAttr attr{}; + Require(scePthreadAttrInit(&attr) == 0); + int scope = -1; + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_SYSTEM); + Require(scePthreadAttrSetscope(&attr, SCOPE_PROCESS) == 0); + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_PROCESS); + for (const int invalid : {1, 3, -1}) { + Require(scePthreadAttrSetscope(&attr, invalid) == SCE_KERNEL_ERROR_EINVAL); + scope = -1; + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_PROCESS); + } + Require(scePthreadAttrSetscope(&attr, SCOPE_SYSTEM) == 0); + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_SYSTEM); + Require(scePthreadAttrGetscope(&attr, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrGetscope(nullptr, &scope) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetscope(nullptr, SCOPE_PROCESS) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrDestroy(&attr) == 0); +} + +static void TestConcurrency() { + Require(scePthreadSetconcurrency(3) == 0 && scePthreadGetconcurrency() == 3); + Require(scePthreadSetconcurrency(-1) == SCE_KERNEL_ERROR_EINVAL && scePthreadGetconcurrency() == 3); + Require(scePthreadSetconcurrency(0) == 0 && scePthreadGetconcurrency() == 0); +} + +static void TestAtfork() { + const auto before = PthreadAtforkHandlers_nid_no_patch().size(); + Require(scePthreadAtfork(Prepare, Parent, Child) == 0); + Require(scePthreadAtfork(nullptr, nullptr, nullptr) == 0); + const auto handlers = PthreadAtforkHandlers_nid_no_patch(); + Require(handlers.size() == before + 2); + Require(handlers[before].prepare == Prepare && handlers[before].parent == Parent && handlers[before].child == Child); + Require(handlers[before + 1].prepare == nullptr && handlers[before + 1].parent == nullptr && handlers[before + 1].child == nullptr); +} + +int main() { + TestScope(); + TestConcurrency(); + TestAtfork(); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysSdkVersion.cpp b/core/libs/tests/GuestKernelSysSdkVersion.cpp new file mode 100644 index 0000000000..dc20a51ac8 --- /dev/null +++ b/core/libs/tests/GuestKernelSysSdkVersion.cpp @@ -0,0 +1,44 @@ +#include "prx/libkernel_sys/include/Api.hpp" +#include "ProcessParametersFixture.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +alignas(16) std::array process{}; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "KERNEL SYS SDK VERSION: %s\n", message); + std::abort(); + } +} + +} + +int main() { + const std::uint64_t size = process.size(); + const std::uint32_t magic = 0x4942524f; + const std::uint32_t ps4SdkVersion = 0x07008001; + const std::uint32_t ps5SdkVersion = 0x0a008001; + std::memcpy(process.data(), &size, sizeof(size)); + std::memcpy(process.data() + 8, &magic, sizeof(magic)); + std::memcpy(process.data() + 0x10, &ps4SdkVersion, sizeof(ps4SdkVersion)); + std::memcpy(process.data() + 0x14, &ps5SdkVersion, sizeof(ps5SdkVersion)); + Require(ProcessParametersFixture::Install(process.data(), process.size()), "process parameters were not installed"); + + Require(sceKernelGetCompiledSdkVersion(nullptr) == static_cast(0x80020016), "null output is not EINVAL"); + std::array output{-1, 0x5a5a5a5a}; + Require(sceKernelGetCompiledSdkVersion(output.data()) == 0, "query failed"); + Require(output[0] == 0x07008001, "version is not the 32-bit field at offset 0x10 of the process parameters"); + Require(output[1] == 0x5a5a5a5a, "the query wrote past the version"); + + std::puts("KERNEL SYS SDK VERSION tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysSwapcontext.cpp b/core/libs/tests/GuestKernelSysSwapcontext.cpp new file mode 100644 index 0000000000..1aa364e87e --- /dev/null +++ b/core/libs/tests/GuestKernelSysSwapcontext.cpp @@ -0,0 +1,172 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libc/include/general/AsmMacros.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/System/include/UserContext.hpp" +#include +#include +#include +#include +#include + +using ContextEntry = void (APS5_VABI*)(); + +extern "C" { +int APS5_VABI SwapKeepingRegisters(UserUcontext* saved, const UserUcontext* next); +int APS5_VABI SwapClobberingRegisters(UserUcontext* saved, const UserUcontext* next); +} + +asm(".text\n" + APS5_ASM_FUNCTION("SwapKeepingRegisters") + " push %rbx\n" + " push %rbp\n" + " push %r12\n" + " push %r13\n" + " push %r14\n" + " push %r15\n" + " sub $8, %rsp\n" + " movabsq $0x1111111111111111, %rbx\n" + " movabsq $0x2222222222222222, %rbp\n" + " movabsq $0x3333333333333333, %r12\n" + " movabsq $0x4444444444444444, %r13\n" + " movabsq $0x5555555555555555, %r14\n" + " movabsq $0x6666666666666666, %r15\n" + " call " APS5_ASM_SYMBOL("swapcontext_nid_postfix") "\n" + " movabsq $0x1111111111111111, %rcx\n" + " cmp %rcx, %rbx\n" + " jne 1f\n" + " movabsq $0x2222222222222222, %rcx\n" + " cmp %rcx, %rbp\n" + " jne 1f\n" + " movabsq $0x3333333333333333, %rcx\n" + " cmp %rcx, %r12\n" + " jne 1f\n" + " movabsq $0x4444444444444444, %rcx\n" + " cmp %rcx, %r13\n" + " jne 1f\n" + " movabsq $0x5555555555555555, %rcx\n" + " cmp %rcx, %r14\n" + " jne 1f\n" + " movabsq $0x6666666666666666, %rcx\n" + " cmp %rcx, %r15\n" + " je 2f\n" + "1:\n" + " mov $-100, %eax\n" + "2:\n" + " add $8, %rsp\n" + " pop %r15\n" + " pop %r14\n" + " pop %r13\n" + " pop %r12\n" + " pop %rbp\n" + " pop %rbx\n" + " ret\n" + APS5_ASM_FUNCTION("SwapClobberingRegisters") + " push %rbx\n" + " push %rbp\n" + " push %r12\n" + " push %r13\n" + " push %r14\n" + " push %r15\n" + " sub $8, %rsp\n" + " movabsq $0x0badc0de0badc0de, %rbx\n" + " mov %rbx, %rbp\n" + " mov %rbx, %r12\n" + " mov %rbx, %r13\n" + " mov %rbx, %r14\n" + " mov %rbx, %r15\n" + " call " APS5_ASM_SYMBOL("swapcontext_nid_postfix") "\n" + " add $8, %rsp\n" + " pop %r15\n" + " pop %r14\n" + " pop %r13\n" + " pop %r12\n" + " pop %rbp\n" + " pop %rbx\n" + " ret\n"); + +namespace { + +constexpr int Einval = 22; +constexpr int SigBlock = 1; +constexpr int SigSetmask = 3; +constexpr int SigUsr1 = 30; +constexpr std::size_t StackBytes = 256 * 1024; + +struct alignas(16) Stack { + unsigned char bytes[StackBytes]; +}; + +Stack g_stack; +UserUcontext g_main = {}; +UserUcontext g_coroutine = {}; +int g_steps = 0; +bool g_coroutineSawBlocked = true; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "check failed at line %u\n", static_cast(location.line())); + std::exit(1); +} + +int Error() { + return *__error_nid_postfix(); +} + +bool Usr1Blocked() { + std::uint32_t mask[4] = {}; + Require(sigprocmask_nid_postfix(SigBlock, nullptr, mask) == 0); + return (mask[0] & (1u << (SigUsr1 - 1))) != 0; +} + +void APS5_VABI Coroutine() { + g_coroutineSawBlocked = Usr1Blocked(); + for (int step = 0; step < 3; ++step) { + ++g_steps; + Require(SwapClobberingRegisters(&g_coroutine, &g_main) == 0); + } +} + +void Arguments() { + UserUcontext context = {}; + Require(getcontext_nid_postfix(&context) == 0); + Require(swapcontext_nid_postfix(nullptr, &context) == -1 && Error() == Einval); + Require(swapcontext_nid_postfix(&context, nullptr) == -1 && Error() == Einval); + + UserUcontext saved = {}; + UserUcontext invalid = context; + invalid.mcontext.len = 0; + Require(swapcontext_nid_postfix(&saved, &invalid) == -1 && Error() == Einval); + Require(saved.mcontext.len == sizeof(UserMcontext) && saved.mcontext.rip != 0); +} + +void PingPong() { + Require(getcontext_nid_postfix(&g_coroutine) == 0); + g_coroutine.stackPointer = g_stack.bytes; + g_coroutine.stackSize = sizeof(g_stack.bytes); + g_coroutine.link = &g_main; + makecontext_nid_postfix(&g_coroutine, &Coroutine, 0); + Require(g_coroutine.mcontext.len == sizeof(UserMcontext)); + + std::uint32_t block[4] = {1u << (SigUsr1 - 1), 0, 0, 0}; + std::uint32_t previous[4] = {}; + Require(sigprocmask_nid_postfix(SigBlock, block, previous) == 0); + for (int expected = 1; expected <= 3; ++expected) { + Require(SwapKeepingRegisters(&g_main, &g_coroutine) == 0); + Require(g_steps == expected && Usr1Blocked()); + } + Require(!g_coroutineSawBlocked); + Require(SwapKeepingRegisters(&g_main, &g_coroutine) == 0); + Require(g_steps == 3 && Usr1Blocked()); + Require(sigprocmask_nid_postfix(SigSetmask, previous, nullptr) == 0); +} + +} + +int main() { + Arguments(); + PingPong(); + std::puts("guest_kernel_sys_swapcontext passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysThreadDefaults.cpp b/core/libs/tests/GuestKernelSysThreadDefaults.cpp new file mode 100644 index 0000000000..f38f272d56 --- /dev/null +++ b/core/libs/tests/GuestKernelSysThreadDefaults.cpp @@ -0,0 +1,74 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include + +static constexpr int POSIX_EINVAL = 22; +static constexpr std::size_t INITIAL_DEFAULT = 1u << 20; + +static void Require(bool value) { if (!value) std::abort(); } + +static std::size_t NewAttrStackSize() { + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == 0); + std::size_t size = 0; + Require(scePthreadAttrGetstacksize(&attr, &size) == 0); + Require(scePthreadAttrDestroy(&attr) == 0); + return size; +} + +static void* APS5_VABI ReportStackSize(void* out) { + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == 0); + Require(scePthreadAttrGet(scePthreadSelf(), &attr) == 0); + Require(scePthreadAttrGetstacksize(&attr, static_cast(out)) == 0); + Require(scePthreadAttrDestroy(&attr) == 0); + return nullptr; +} + +static std::size_t DefaultThreadStackSize() { + std::size_t size = 0; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, ReportStackSize, &size, "stack") == 0); + Require(scePthreadJoin(thread, nullptr) == 0); + return size; +} + +static void CheckDefaultStackSize() { + Require(NewAttrStackSize() == INITIAL_DEFAULT); + Require(pthread_set_defaultstacksize_np_nid_postfix(0) == POSIX_EINVAL); + Require(pthread_set_defaultstacksize_np_nid_postfix(16384) == POSIX_EINVAL); + Require(scePthreadSetDefaultstacksize(16384) == SCE_KERNEL_ERROR_EINVAL); + Require(NewAttrStackSize() == INITIAL_DEFAULT); + + Require(pthread_set_defaultstacksize_np_nid_postfix(16385) == 0); + Require(NewAttrStackSize() == 16385); + + Require(scePthreadSetDefaultstacksize(0x40000) == 0); + Require(NewAttrStackSize() == 0x40000); + Require(DefaultThreadStackSize() == 0x40000); + + Require(scePthreadSetDefaultstacksize(INITIAL_DEFAULT) == 0); + Require(DefaultThreadStackSize() == INITIAL_DEFAULT); +} + +static void CheckSetName() { + char name[32] = {}; + scePthreadSetName(scePthreadSelf(), "renamed main"); + + Require(scePthreadGetname(scePthreadSelf(), name) == 0); + Require(std::strcmp(name, "renamed main") == 0); + scePthreadSetName(scePthreadSelf(), nullptr); + + Require(scePthreadGetname(scePthreadSelf(), name) == 0); + Require(name[0] == '\0'); +} + +int main() { + CheckDefaultStackSize(); + CheckSetName(); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysThreads.cpp b/core/libs/tests/GuestKernelSysThreads.cpp new file mode 100644 index 0000000000..4134cfd6f2 --- /dev/null +++ b/core/libs/tests/GuestKernelSysThreads.cpp @@ -0,0 +1,104 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#else +#include +#endif + +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "Kernel sys API check failed at line %u\n", location.line()); + std::abort(); + } +} + +static void* LoadLibraryFile(const std::filesystem::path& path) { +#ifdef _WIN32 + auto* module = reinterpret_cast(LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH)); +#else + auto* module = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!module) std::fprintf(stderr, "%s\n", dlerror()); +#endif + Require(module != nullptr); +#ifdef _WIN32 + std::wstring loaded(32768, L'\0'); + const auto length = GetModuleFileNameW(static_cast(module), loaded.data(), static_cast(loaded.size())); + Require(length != 0 && length < loaded.size()); + loaded.resize(length); + Require(std::filesystem::equivalent(path, std::filesystem::path(loaded))); +#endif + return module; +} + +template +static Function LoadFunction(void* module, const char* name, bool patched, const char* unpatched = nullptr) { + const auto symbol = patched ? Nid::ComputeNid(name, "") : unpatched ? std::string(unpatched) : std::string(name) + "_nid_postfix"; +#ifdef _WIN32 + const auto function = reinterpret_cast(reinterpret_cast(GetProcAddress(static_cast(module), symbol.c_str()))); +#else + const auto function = reinterpret_cast(dlsym(module, symbol.c_str())); +#endif + if (!function) std::fprintf(stderr, "Missing symbol %s\n", symbol.c_str()); + Require(function != nullptr); + return function; +} + + +int main(int argc, char** argv) { + Require(argc == 4); + const auto directory = std::filesystem::absolute(argv[1]); + const bool patched = std::strcmp(argv[2], "patched") == 0; + const std::string test = argv[3]; +#ifdef __linux__ + if (test == "main_late_load") { + int (APS5_VABI* isMain)() = nullptr; + int (APS5_VABI* sceIsMain)() = nullptr; + int other = -1; + int sceOther = -1; + std::thread loader([&] { + auto* module = LoadLibraryFile(directory / "libkernel_sys.prx"); + isMain = LoadFunction(module, "pthread_main_np", patched); + sceIsMain = LoadFunction(module, "scePthreadMain", patched, "scePthreadMain"); + other = isMain(); + sceOther = sceIsMain(); + }); + loader.join(); + Require(other == 0 && isMain() == 1 && sceOther == 0 && sceIsMain() == 1); + return 0; + } +#endif + Require(test == "main"); + auto* sys = LoadLibraryFile(directory / "libkernel_sys.prx"); + LoadLibraryFile(directory / "libkernel.prx"); + auto* libc = LoadLibraryFile(directory / "libc.prx"); + const auto error = LoadFunction(libc, "__error", patched); + const auto isMain = LoadFunction(sys, "pthread_main_np", patched); + const auto sceIsMain = LoadFunction(sys, "scePthreadMain", patched, "scePthreadMain"); + *error() = 71; + int other = -1; + int sceOther = -1; + std::thread worker([&] { + other = isMain(); + sceOther = sceIsMain(); + }); + worker.join(); + Require(other == 0 && isMain() == 1 && sceOther == 0 && sceIsMain() == 1 && *error() == 71); + return 0; +} diff --git a/core/libs/tests/GuestKill.cpp b/core/libs/tests/GuestKill.cpp index 7b75694ea5..162e1b47ed 100644 --- a/core/libs/tests/GuestKill.cpp +++ b/core/libs/tests/GuestKill.cpp @@ -1,16 +1,12 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include using Handler = void (APS5_VABI *)(int); -extern "C" { -Handler APS5_VABI signal_nid_postfix(int, Handler); -int APS5_VABI kill_nid_postfix(int, int); -int APS5_VABI killpg_nid_postfix(int, int); -int APS5_VABI getpid_nid_postfix(void); -int* APS5_VABI __error_nid_postfix(); -} + volatile std::sig_atomic_t received = 0; void APS5_VABI Callback(int value) { received = value; } static void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestKqueue.cpp b/core/libs/tests/GuestKqueue.cpp index 62085d4470..f3a6638b89 100644 --- a/core/libs/tests/GuestKqueue.cpp +++ b/core/libs/tests/GuestKqueue.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -15,33 +17,14 @@ #include #include #endif +#include +#ifdef _WIN32 +#include +#else +#include +#endif -extern "C" { -int APS5_VABI kqueue_nid_postfix(); -int APS5_VABI kevent_nid_postfix(int, const KernelEvent*, int, KernelEvent*, int, const KernelTimespec*); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI dup_nid_postfix(int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI socketpair_nid_postfix(int, int, int, int*); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI mkdir_nid_postfix(const char*, unsigned short); -int APS5_VABI rmdir_nid_postfix(const char*); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -int APS5_VABI unlink_nid_postfix(const char*); -int APS5_VABI sceKernelRename(const char*, const char*); -int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); -int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo); -int APS5_VABI sceKernelAddFileEvent(KernelEqueue eq, int fd, int watch, void* udata); -int APS5_VABI sceKernelDeleteFileEvent(KernelEqueue eq, int fd); -int* APS5_VABI __error_nid_postfix(); -} - +static constexpr int GuestEperm = 1; static constexpr int GuestEnoent = 2; static constexpr int GuestEbadf = 9; static constexpr int GuestEinval = 22; @@ -56,9 +39,13 @@ static constexpr int FirstSocket = 0x10000000; static constexpr std::int16_t EvfiltRead = -1; static constexpr std::int16_t EvfiltWrite = -2; static constexpr std::int16_t EvfiltVnode = -4; +static constexpr std::int16_t EvfiltAio = -3; static constexpr std::int16_t EvfiltProc = -5; static constexpr std::int16_t EvfiltTimer = -7; +static constexpr std::int16_t EvfiltProcdesc = -8; +static constexpr std::int16_t EvfiltLio = -10; static constexpr std::int16_t EvfiltUser = -11; +static constexpr std::int16_t EvfiltSendfile = -12; static constexpr std::uint16_t EvAdd = 0x0001; static constexpr std::uint16_t EvDelete = 0x0002; static constexpr std::uint16_t EvEnable = 0x0004; @@ -186,7 +173,94 @@ static void Timers() { RequireFailure(Apply(kq, Change(3, EvfiltTimer, EvAdd, 0x10, 5)), GuestEinval); RequireFailure(Apply(kq, Change(3, EvfiltTimer, EvAdd, NoteUseconds | 0x1, 5)), GuestEinval); Require(Apply(kq, Change(4, EvfiltTimer, EvAdd, 0, 100000)) == 0); - Require(Throws([kq] { Apply(kq, Change(4, EvfiltTimer, EvAdd, 0, 5)); })); + Require(Apply(kq, Change(4, EvfiltTimer, EvAdd, 0, 5)) == 0); + const KernelTimespec fiftyMillis{0, 50000000}; + Require(kevent_nid_postfix(kq, nullptr, 0, events, 4, &fiftyMillis) == 0); + Require(close_nid_postfix(kq) == 0); +} + +static void TimerReadd() { + const int kq = kqueue_nid_postfix(); + int first = 0; + int second = 0; + KernelEvent events[4]{}; + Require(Apply(kq, Change(6, EvfiltTimer, EvAdd, 0, 10, &first)) == 0); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + Require(Apply(kq, Change(6, EvfiltTimer, EvAdd, 0, 1000, &second)) == 0); + Require(Poll(kq, events, 4) == 1 && events[0].ident == 6 && events[0].data >= 6 && events[0].udata == &second); + Require(Apply(kq, Change(6, EvfiltTimer, EvDelete)) == 0); + + const auto armed = std::chrono::steady_clock::now(); + Require(Apply(kq, Change(7, EvfiltTimer, EvAdd, 0, 50)) == 0); + Require(Apply(kq, Change(7, EvfiltTimer, EvAdd, 0, 10)) == 0); + Require(Wait(kq, events, 4) == 1 && events[0].ident == 7); + Require(std::chrono::steady_clock::now() - armed >= std::chrono::milliseconds(49)); + std::this_thread::sleep_for(std::chrono::milliseconds(120)); + Require(Poll(kq, events, 4) == 1 && events[0].ident == 7 && events[0].data >= 6); + Require(Apply(kq, Change(7, EvfiltTimer, EvDelete)) == 0); + + Require(Apply(kq, Change(8, EvfiltTimer, EvAdd, 0, 10)) == 0); + Require(Apply(kq, Change(8, EvfiltTimer, EvAdd, 0, 100)) == 0); + Require(Wait(kq, events, 4) == 1 && events[0].ident == 8); + std::this_thread::sleep_for(std::chrono::milliseconds(120)); + Require(Poll(kq, events, 4) == 1 && events[0].ident == 8 && events[0].data >= 1 && events[0].data <= 4); + Require(Apply(kq, Change(8, EvfiltTimer, EvDelete)) == 0); + + Require(Apply(kq, Change(9, EvfiltTimer, EvAdd, 0, 20)) == 0); + Require(Apply(kq, Change(9, EvfiltTimer, EvAdd | EvOneshot, 0, 20)) == 0); + Require(Wait(kq, events, 4) == 1 && events[0].ident == 9); + Require(Wait(kq, events, 4) == 1 && events[0].ident == 9); + Require(Apply(kq, Change(9, EvfiltTimer, EvDelete)) == 0); + + 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(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(close_nid_postfix(kq) == 0); +} + +static void UnsupportedFilters() { + const int kq = kqueue_nid_postfix(); + for (const std::int16_t filter : {EvfiltAio, EvfiltLio, EvfiltSendfile}) { + RequireFailure(Apply(kq, Change(0, filter, EvAdd)), GuestEperm); + RequireFailure(Apply(kq, Change(5, filter, EvAdd)), GuestEperm); + RequireFailure(Apply(kq, Change(5, filter, EvAdd | EvClear)), GuestEperm); + RequireFailure(Apply(kq, Change(5, filter, EvDelete)), GuestEnoent); + RequireFailure(Apply(kq, Change(5, filter, EvEnable)), GuestEnoent); + const KernelEvent add = Change(5, filter, EvAdd, 7, 9); + KernelEvent failed[1]{}; + Require(kevent_nid_postfix(kq, &add, 1, failed, 1, nullptr) == 1); + Require(failed[0].flags == EvError && failed[0].data == GuestEperm && failed[0].fflags == 0); + } + int pipe[2]; + Require(pipe_nid_postfix(pipe) == 0); + int pair[2]; + Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0); + RequireFailure(Apply(kq, Change(static_cast(pipe[0]), EvfiltProcdesc, EvAdd)), GuestEinval); + RequireFailure(Apply(kq, Change(static_cast(pipe[1]), EvfiltProcdesc, EvAdd)), GuestEinval); + RequireFailure(Apply(kq, Change(static_cast(pair[0]), EvfiltProcdesc, EvAdd)), GuestEinval); + RequireFailure(Apply(kq, Change(static_cast(pair[0]), EvfiltProcdesc, EvDelete)), GuestEnoent); + RequireFailure(Apply(kq, Change(static_cast(kq), EvfiltProcdesc, EvAdd)), GuestEinval); + RequireFailure(Apply(kq, Change(std::uintptr_t{1} << 40, EvfiltProcdesc, EvAdd)), GuestEbadf); + const KernelEvent attach = Change(static_cast(pipe[0]), EvfiltProcdesc, EvAdd, 7, 9); + KernelEvent failed[1]{}; + Require(kevent_nid_postfix(kq, &attach, 1, failed, 1, nullptr) == 1); + Require(failed[0].flags == EvError && failed[0].data == GuestEinval && failed[0].fflags == 0); + const int random = open_nid_postfix("/dev/urandom", 0, 0); + Require(random >= 0); + Require(Throws([kq, random] { Apply(kq, Change(static_cast(random), EvfiltProcdesc, EvAdd)); })); + Require(Throws([kq] { Apply(kq, Change(2, EvfiltProcdesc, EvAdd)); })); + RequireFailure(Apply(kq, Change(static_cast(random), EvfiltProcdesc, EvDelete)), GuestEnoent); + Require(close_nid_postfix(random) == 0); + Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0); + Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0); + RequireFailure(Apply(kq, Change(static_cast(pipe[0]), EvfiltProcdesc, EvAdd)), GuestEbadf); Require(close_nid_postfix(kq) == 0); } @@ -785,6 +859,59 @@ static void DisableAndDispatch() { Require(close_nid_postfix(kq) == 0); } +static void RequireNext(int kq, std::uintptr_t ident) { + KernelEvent event{}; + Require(Poll(kq, &event, 1) == 1 && event.ident == ident); +} + +static void ReportedKnotesMoveToTail() { + const int kq = kqueue_nid_postfix(); + int first[2]; + int second[2]; + int pair[2]; + Require(pipe_nid_postfix(first) == 0 && pipe_nid_postfix(second) == 0); + Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0); + const std::uintptr_t level[3] = {static_cast(first[0]), static_cast(second[0]), static_cast(pair[0])}; + for (const auto ident : level) { + Require(Apply(kq, Change(ident, EvfiltRead, EvAdd)) == 0); + } + Require(write_nid_postfix(first[1], "a", 1) == 1 && write_nid_postfix(second[1], "b", 1) == 1); + Require(send_nid_postfix(pair[1], "c", 1, 0) == 1); + for (int round = 0; round < 2; ++round) { + for (const auto ident : level) { + RequireNext(kq, ident); + } + } + for (const auto ident : level) { + Require(Apply(kq, Change(ident, EvfiltRead, EvDelete)) == 0); + } + + KernelEvent event{}; + for (const std::uintptr_t ident : {11, 12}) { + Require(Apply(kq, Change(ident, EvfiltUser, EvAdd | EvClear)) == 0); + Require(Apply(kq, Change(ident, EvfiltUser, 0, NoteTrigger)) == 0); + } + RequireNext(kq, 11); + Require(Apply(kq, Change(11, EvfiltUser, 0, NoteTrigger)) == 0); + RequireNext(kq, 12); + RequireNext(kq, 11); + Require(Poll(kq, &event, 1) == 0); + + for (const std::uintptr_t ident : {21, 22}) { + Require(Apply(kq, Change(ident, EvfiltUser, EvAdd | EvDispatch)) == 0); + Require(Apply(kq, Change(ident, EvfiltUser, 0, NoteTrigger)) == 0); + } + RequireNext(kq, 21); + Require(Apply(kq, Change(21, EvfiltUser, EvEnable, NoteTrigger)) == 0); + RequireNext(kq, 22); + RequireNext(kq, 21); + Require(Poll(kq, &event, 1) == 0); + + for (const int descriptor : {first[0], first[1], second[0], second[1], pair[0], pair[1], kq}) { + Require(close_nid_postfix(descriptor) == 0); + } +} + static void Errors() { const int kq = kqueue_nid_postfix(); KernelEvent events[4]{}; @@ -828,7 +955,28 @@ static void Errors() { } static std::string TemporaryName(const char* stem) { - return std::string("anyps5-kqueue-") + stem + "-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + return std::string("anyps5-kqueue-") + stem + "-" + std::to_string(getpid_nid_postfix()) + "-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); +} + +static void ProcdescOnFiles() { + const int kq = kqueue_nid_postfix(); + const int other = kqueue_nid_postfix(); + RequireFailure(Apply(kq, Change(static_cast(other), EvfiltProcdesc, EvAdd)), GuestEinval); + const auto path = TemporaryName("procdesc-file"); + const int file = open_nid_postfix(path.c_str(), ReadWrite | Create, 0644); + Require(file >= 0); + RequireFailure(Apply(kq, Change(static_cast(file), EvfiltProcdesc, EvAdd)), GuestEinval); + Require(close_nid_postfix(file) == 0); + Require(std::filesystem::remove(path)); + const auto folder = TemporaryName("procdesc-directory"); + Require(std::filesystem::create_directory(folder)); + const int directory = open_nid_postfix(folder.c_str(), Directory, 0); + Require(directory >= 0); + RequireFailure(Apply(kq, Change(static_cast(directory), EvfiltProcdesc, EvAdd)), GuestEinval); + Require(close_nid_postfix(directory) == 0); + Require(std::filesystem::remove(folder)); + Require(close_nid_postfix(other) == 0); + Require(close_nid_postfix(kq) == 0); } static void FileWatch() { @@ -1101,6 +1249,102 @@ static void DeniedParentWatch() { Require(std::filesystem::remove_all(path) == 2); } #endif + +#ifdef __linux__ +static std::size_t InotifyWatchCount() { + std::size_t count = 0; + std::error_code error; + for (const auto& entry : std::filesystem::directory_iterator("/proc/self/fd", error)) { + std::error_code linkError; + const auto target = std::filesystem::read_symlink(entry.path(), linkError); + if (!linkError && target.string().find("inotify") != std::string::npos) ++count; + } + return count; +} +#endif + +static void VnodeOneshotWatchStops() { + const auto path = TemporaryName("oneshot"); + const int file = open_nid_postfix(path.c_str(), ReadWrite | Create, 0644); + Require(file >= 0); + const int kq = kqueue_nid_postfix(); + int marker = 0; + KernelEvent events[4]{}; + const auto ident = static_cast(file); +#ifdef __linux__ + auto watchesBefore = InotifyWatchCount(); + for (int attempt = 0; attempt < 100; ++attempt) { + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + const auto settled = InotifyWatchCount(); + if (settled == watchesBefore) break; + watchesBefore = settled; + } +#endif + Require(Apply(kq, Change(ident, EvfiltVnode, EvAdd | EvOneshot, NoteDelete | NoteWrite, 0, &marker)) == 0); + Require(write_nid_postfix(file, "abc", 3) == 3); + Require(Wait(kq, events, 4) == 1); + Require(events[0].ident == ident && events[0].filter == EvfiltVnode && (events[0].flags & EvOneshot) != 0 && events[0].udata == &marker); + Require(write_nid_postfix(file, "def", 3) == 3); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + Require(Poll(kq, events, 4) == 0); + RequireFailure(Apply(kq, Change(ident, EvfiltVnode, EvDelete)), GuestEnoent); +#ifdef __linux__ + for (int attempt = 0; attempt < 100 && InotifyWatchCount() > watchesBefore; ++attempt) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + Require(InotifyWatchCount() <= watchesBefore); +#endif + Require(close_nid_postfix(file) == 0); + Require(close_nid_postfix(kq) == 0); + Require(std::filesystem::remove(path)); +} + +static void TimerDisableEnableRearm() { + const int kq = kqueue_nid_postfix(); + int marker = 0; + KernelEvent events[4]{}; + Require(Apply(kq, Change(1, EvfiltTimer, EvAdd, NoteUseconds, 50000, &marker)) == 0); + Require(Apply(kq, Change(1, EvfiltTimer, EvDisable, 0, 0, &marker)) == 0); + std::this_thread::sleep_for(std::chrono::milliseconds(150)); + Require(Apply(kq, Change(1, EvfiltTimer, EvEnable, 0, 0, &marker)) == 0); + Require(Wait(kq, events, 4) == 1); + Require(events[0].ident == 1 && events[0].filter == EvfiltTimer && events[0].udata == &marker); + Require(events[0].data >= 2 && events[0].data <= 6); + Require(Wait(kq, events, 4) == 1); + Require(events[0].data >= 1 && events[0].data <= 2); + Require(Throws([kq] { Apply(kq, Change(1, EvfiltTimer, EvAdd | EvEnable)); })); + Require(Apply(kq, Change(1, EvfiltTimer, EvDelete)) == 0); + Require(close_nid_postfix(kq) == 0); +} + +static void PeriodicTimerFiresTwice() { + const int kq = kqueue_nid_postfix(); + KernelEvent events[4]{}; + Require(Apply(kq, Change(1, EvfiltTimer, EvAdd, NoteUseconds, 50000)) == 0); + Require(Wait(kq, events, 4) == 1); + Require(Wait(kq, events, 4) == 1); + Require(Apply(kq, Change(1, EvfiltTimer, EvDelete)) == 0); + Require(close_nid_postfix(kq) == 0); +} + +#ifdef _WIN32 +static void RecycleBinDeleteWatchedFile() { + const auto directory = TemporaryName("deleted"); + std::filesystem::create_directories(directory + "\\$Extend\\$Deleted"); + const auto path = directory + "\\watched"; + const int file = open_nid_postfix(path.c_str(), ReadWrite | Create, 0644); + Require(file >= 0); + const int kq = kqueue_nid_postfix(); + KernelEvent events[4]{}; + const auto ident = static_cast(file); + Require(Apply(kq, Change(ident, EvfiltVnode, EvAdd | EvClear, NoteDelete | NoteRename)) == 0); + Require(sceKernelRename(path.c_str(), (directory + "\\$Extend\\$Deleted\\moved").c_str()) == SceOk); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + Require(Poll(kq, events, 4) == 1 && events[0].ident == ident && (events[0].fflags & NoteDelete) != 0); + Require(close_nid_postfix(file) == 0); + Require(close_nid_postfix(kq) == 0); + std::filesystem::remove_all(directory); +} +#endif + static void Timeout() { const int kq = kqueue_nid_postfix(); KernelEvent event{}; @@ -1112,7 +1356,47 @@ static void Timeout() { Require(close_nid_postfix(kq) == 0); } +static void ReplaceStandardInputWithNull() { +#ifdef _WIN32 + const int null = _open("NUL", _O_RDONLY); + Require(null >= 0); + if (null != 0) { + Require(_dup2(null, 0) == 0); + Require(_close(null) == 0); + } +#else + const int null = ::open("/dev/null", O_RDONLY); + Require(null >= 0); + if (null != 0) { + Require(::dup2(null, 0) == 0); + Require(::close(null) == 0); + } +#endif +} + +static bool StandardInputAtEnd() { + char byte = 0; +#ifdef _WIN32 + return _read(0, &byte, 1) == 0; +#else + return ::read(0, &byte, 1) == 0; +#endif +} + +static void StandardInputReplacement() { + ReplaceStandardInputWithNull(); + Require(StandardInputAtEnd()); +#ifdef _WIN32 + Require(_close(0) == 0); +#else + Require(::close(0) == 0); +#endif + ReplaceStandardInputWithNull(); + Require(StandardInputAtEnd()); +} + static void StandardInput() { + ReplaceStandardInputWithNull(); const int copy = dup_nid_postfix(0); if (copy < 0) { Require(*__error_nid_postfix() == GuestEbadf); @@ -1134,9 +1418,13 @@ static void StandardInput() { } int main() { + StandardInputReplacement(); StandardInput(); CreateAndClose(); Timers(); + TimerReadd(); + UnsupportedFilters(); + ProcdescOnFiles(); PipeRead(); #ifndef __APPLE__ PipeWrite(); @@ -1152,11 +1440,18 @@ int main() { EdgeTriggeredDispatch(); #endif DisableAndDispatch(); + ReportedKnotesMoveToTail(); Errors(); LowWaterMarks(); FileWatch(); #ifndef _WIN32 RenameAndDeleteWatchedFile(); + VnodeOneshotWatchStops(); + PeriodicTimerFiresTwice(); + TimerDisableEnableRearm(); +#endif +#ifdef _WIN32 + RecycleBinDeleteWatchedFile(); #endif #if !defined(_WIN32) && !defined(__APPLE__) MoveDirectoryEntries(); diff --git a/core/libs/tests/GuestLibcCndInitDefaultName.cpp b/core/libs/tests/GuestLibcCndInitDefaultName.cpp index 6d5d046398..582215e686 100644 --- a/core/libs/tests/GuestLibcCndInitDefaultName.cpp +++ b/core/libs/tests/GuestLibcCndInitDefaultName.cpp @@ -1,13 +1,8 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include -extern "C" { -int APS5_VABI _Cnd_init_with_default_name_override_nid_postfix(void** handle, const char* name); -void APS5_VABI _Cnd_destroy_nid_postfix(void** handle); -int APS5_VABI _Cnd_signal_nid_postfix(void** handle); -} - static void Require(bool value) { if (!value) std::abort(); } template @@ -28,6 +23,7 @@ int main() { Require(condition != nullptr); Require(_Cnd_signal_nid_postfix(&condition) == success); _Cnd_destroy_nid_postfix(&condition); + Require(condition == nullptr); void* unnamed = nullptr; @@ -37,6 +33,7 @@ int main() { RequireThrows([] { _Cnd_init_with_default_name_override_nid_postfix(nullptr, "NullHandle"); + }); return 0; } diff --git a/core/libs/tests/GuestLibcCndTimedWait.cpp b/core/libs/tests/GuestLibcCndTimedWait.cpp index e85144f158..da073cf4f1 100644 --- a/core/libs/tests/GuestLibcCndTimedWait.cpp +++ b/core/libs/tests/GuestLibcCndTimedWait.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -14,23 +15,6 @@ #include #endif -struct Xtime { - std::int64_t sec; - std::int64_t nsec; -}; - -extern "C" { -int APS5_VABI _Mtx_init_nid_postfix(void** handle, int type); -void APS5_VABI _Mtx_destroy_nid_postfix(void** handle); -int APS5_VABI _Mtx_lock_nid_postfix(void** handle); -int APS5_VABI _Mtx_unlock_nid_postfix(void** handle); -int APS5_VABI _Cnd_init_nid_postfix(void** handle); -void APS5_VABI _Cnd_destroy_nid_postfix(void** handle); -int APS5_VABI _Cnd_wait_nid_postfix(void** condition, void** mutex); -int APS5_VABI _Cnd_broadcast_nid_postfix(void** handle); -int APS5_VABI _Cnd_timedwait_nid_postfix(void** condition, void** mutex, const Xtime* target); -} - static void Require(bool value) { if (!value) std::abort(); } #ifdef _WIN32 @@ -181,4 +165,5 @@ int main() { RequireThrows([&] { _Cnd_timedwait_nid_postfix(&condition, &mutex, &overflowSecTime); }); _Cnd_destroy_nid_postfix(&condition); _Mtx_destroy_nid_postfix(&mutex); + } diff --git a/core/libs/tests/GuestLibcFilesystem.cpp b/core/libs/tests/GuestLibcFilesystem.cpp new file mode 100644 index 0000000000..fe53b7ff63 --- /dev/null +++ b/core/libs/tests/GuestLibcFilesystem.cpp @@ -0,0 +1,122 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int NoSuchFile = 2; +constexpr int TypeNotFound = -1; +constexpr int TypeRegular = 1; +constexpr int TypeDirectory = 2; +constexpr int TypeUnknown = 8; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "filesystem: %s\n", message); + std::abort(); +} + +std::string root; + +std::string At(const char* name) { + return root + "/" + name; +} + +void Write(const std::string& path, std::size_t bytes) { + std::ofstream file(path, std::ios::binary | std::ios::trunc); + file << std::string(bytes, 'x'); +} + +void TestStat() { + Write(At("file"), 7); + int perms = -1; + int error = -1; + Require(_ZNSt10filesystem5_StatEPKcPNS_5permsEPi_nid_postfix(At("file").c_str(), &perms, &error) == TypeRegular && error == 0, "regular file"); + FileStat status{}; + Require(stat_nid_postfix(At("file").c_str(), &status) == 0 && perms == (status.st_mode & 07777), "permission bits"); + Require(_ZNSt10filesystem5_StatEPKcPNS_5permsEPi_nid_postfix(root.c_str(), nullptr, &error) == TypeDirectory && error == 0, "directory"); + perms = 1234; + Require(_ZNSt10filesystem5_StatEPKcPNS_5permsEPi_nid_postfix(At("missing").c_str(), &perms, &error) == TypeNotFound && error == NoSuchFile && perms == 1234, "missing path"); + Require(_ZNSt10filesystem5_StatEPKcPNS_5permsEPi_nid_postfix(At("file/below").c_str(), nullptr, &error) == TypeNotFound, "path below a file"); + Require(_ZNSt10filesystem6_LstatEPKcPNS_5permsEPi_nid_postfix(At("file").c_str(), nullptr, &error) == TypeRegular && error == 0, "lstat of a regular file"); + std::error_code link; + std::filesystem::create_symlink("file", At("link"), link); + if (!link) Require(_ZNSt10filesystem6_LstatEPKcPNS_5permsEPi_nid_postfix(At("link").c_str(), nullptr, &error) == TypeRegular, "lstat follows a link as the console does"); +} + +void TestSizeAndTime() { + int error = 0; + Require(_ZNSt10filesystem10_File_sizeEPKcPi_nid_postfix(At("file").c_str(), &error) == 7 && error == 0, "file size"); + error = 0; + Require(_ZNSt10filesystem10_File_sizeEPKcPi_nid_postfix(At("missing").c_str(), &error) == UINT64_MAX && error == NoSuchFile, "size of a missing file"); + std::int64_t seconds = 0; + std::int64_t nanoseconds = -1; + FileStat status{}; + Require(stat_nid_postfix(At("file").c_str(), &status) == 0, "stat for time"); + Require(_ZNSt10filesystem16_Last_write_timeEPKcPlS2__nid_postfix(At("file").c_str(), &seconds, &nanoseconds) == 0, "last write time"); + Require(seconds == status.st_mtim.tv_sec && nanoseconds == status.st_mtim.tv_nsec, "last write time matches stat"); + seconds = 5; + Require(_ZNSt10filesystem16_Last_write_timeEPKcPlS2__nid_postfix(At("missing").c_str(), &seconds, &nanoseconds) == NoSuchFile && seconds == 5, "time of a missing file"); +} + +void TestDirectories() { + Require(_ZNSt10filesystem9_Make_dirEPKcS1__nid_postfix(At("made").c_str(), nullptr) == 1, "make a directory"); + Require(_ZNSt10filesystem9_Make_dirEPKcS1__nid_postfix(At("made").c_str(), nullptr) == 0, "make an existing directory"); + Require(_ZNSt10filesystem9_Make_dirEPKcS1__nid_postfix(At("absent/made").c_str(), nullptr) == -NoSuchFile, "make below a missing directory"); + Require(_ZNSt10filesystem9_Make_dirEPKcS1__nid_postfix(At("copy").c_str(), At("missing").c_str()) == -NoSuchFile, "copy the mode of a missing directory"); + Require(_ZNSt10filesystem9_Make_dirEPKcS1__nid_postfix(At("copy").c_str(), At("made").c_str()) == 1, "copy the mode of a directory"); + Require(_ZNSt10filesystem11_Remove_dirEPKc_nid_postfix(At("copy").c_str()), "remove a directory"); + Require(!_ZNSt10filesystem11_Remove_dirEPKc_nid_postfix(At("copy").c_str()), "remove a missing directory"); + char name[260]; + int error = -1; + int type = -1; + Require(_ZNSt10filesystem9_Open_dirERA260_cPKcRiRNS_9file_typeE_nid_postfix(name, At("made").c_str(), &error, &type) == nullptr && error == 0 && name[0] == '\0', "an empty directory gives no handle"); + Require(_ZNSt10filesystem9_Open_dirERA260_cPKcRiRNS_9file_typeE_nid_postfix(name, At("missing").c_str(), &error, &type) == nullptr && error == NoSuchFile, "a missing directory"); + Write(At("made/one"), 1); + Write(At("made/two"), 2); + void* handle = _ZNSt10filesystem9_Open_dirERA260_cPKcRiRNS_9file_typeE_nid_postfix(name, At("made").c_str(), &error, &type); + Require(handle != nullptr && error == 0 && type == TypeUnknown, "open a directory"); + std::set names{name}; + type = 0; + Require(_ZNSt10filesystem9_Read_dirERA260_cPvRNS_9file_typeE_nid_postfix(name, handle, &type) == name && type == TypeUnknown, "read a directory"); + names.insert(name); + Require(names == std::set{"one", "two"}, "entries without dot entries"); + Require(_ZNSt10filesystem9_Read_dirERA260_cPvRNS_9file_typeE_nid_postfix(name, handle, &type)[0] == '\0' && type == TypeUnknown, "end of the directory"); + Require(_ZNSt10filesystem10_Close_dirEPv_nid_postfix(handle) == 0, "close a directory"); +} + +void TestFiles() { + Require(_ZNSt10filesystem6_ChmodEPKcNS_5permsE_nid_postfix(At("file").c_str(), 0600) == 0, "chmod"); + Require(_ZNSt10filesystem6_ChmodEPKcNS_5permsE_nid_postfix(At("missing").c_str(), 0600) == -1, "chmod of a missing file"); + Require(_ZNSt10filesystem7_UnlinkEPKc_nid_postfix(At("made/one").c_str()) == 0, "unlink"); + Require(_ZNSt10filesystem7_UnlinkEPKc_nid_postfix(At("made/one").c_str()) == NoSuchFile, "unlink a missing file"); + char current[260]; + char expected[256]; + Require(getcwd_nid_postfix(expected, sizeof(expected)) != nullptr, "getcwd"); + Require(_ZNSt10filesystem12_Current_getERA260_c_nid_postfix(current) && std::strcmp(current, expected) == 0, "current directory"); +} + +} + +int main() { + root = "anyps5-libc-filesystem-" + std::to_string(std::random_device{}()); + std::filesystem::create_directory(root); + TestStat(); + TestSizeAndTime(); + TestDirectories(); + TestFiles(); + std::error_code ignored; + std::filesystem::remove_all(root, ignored); + return 0; +} diff --git a/core/libs/tests/GuestLibcMutexInitWithName.cpp b/core/libs/tests/GuestLibcMutexInitWithName.cpp index 2501aef36b..a3faa64d90 100644 --- a/core/libs/tests/GuestLibcMutexInitWithName.cpp +++ b/core/libs/tests/GuestLibcMutexInitWithName.cpp @@ -1,14 +1,8 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include -extern "C" { -int APS5_VABI _Mtx_init_with_name_nid_postfix(void** handle, int type, const char* name); -void APS5_VABI _Mtx_destroy_nid_postfix(void** handle); -int APS5_VABI _Mtx_lock_nid_postfix(void** handle); -int APS5_VABI _Mtx_unlock_nid_postfix(void** handle); -} - static void Require(bool value) { if (!value) std::abort(); } template @@ -33,9 +27,11 @@ int main() { Require(_Mtx_lock_nid_postfix(&mutex) == busy); Require(_Mtx_unlock_nid_postfix(&mutex) == success); _Mtx_destroy_nid_postfix(&mutex); + Require(mutex == nullptr); RequireThrows([] { _Mtx_init_with_name_nid_postfix(nullptr, plain, "NullHandle"); + }); } diff --git a/core/libs/tests/GuestLibcRandomSeeding.cpp b/core/libs/tests/GuestLibcRandomSeeding.cpp new file mode 100644 index 0000000000..33e46f6781 --- /dev/null +++ b/core/libs/tests/GuestLibcRandomSeeding.cpp @@ -0,0 +1,112 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +static int failures = 0; + +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "%s\n", message); + ++failures; + } +} + +static std::vector Draw(std::size_t count) { + std::vector values; + values.reserve(count); + for (std::size_t index = 0; index < count; ++index) values.push_back(random_nid_postfix()); + return values; +} + +static void RequireSequence(const std::vector& actual, const std::vector& expected, const char* message) { + Require(actual == expected, message); +} + +static std::string FirstStateWord(const char* table) { + static const char* digits = "0123456789abcdef"; + std::string text; + const unsigned char* bytes = reinterpret_cast(table); + for (std::size_t index = 0; index < 8; ++index) { + text.push_back(digits[bytes[index] >> 4]); + text.push_back(digits[bytes[index] & 0x0f]); + } + return text; +} + +struct StateCase { + std::int64_t size; + std::vector values; + const char* firstStateWord; +}; + +int main() { + RequireSequence(Draw(5), {1804289383, 846930886, 1681692777, 1714636915, 1957747793}, + "a fresh process must draw the measured first five values"); + + srandom_nid_postfix(1); + RequireSequence(Draw(6), {1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335}, + "srandom(1) must match the measured sequence"); + + srandom_nid_postfix(0); + RequireSequence(Draw(6), {577655601, 1248161417, 192959563, 143633695, 541917479, 1903093295}, + "srandom(0) must match the measured sequence"); + + srandom_nid_postfix(2); + RequireSequence(Draw(6), {1505335290, 1738766719, 190686788, 260874575, 747983061, 906156498}, + "srandom(2) must match the measured sequence"); + + srandom_nid_postfix(12345); + RequireSequence(Draw(6), {383100999, 858300821, 357768173, 455528251, 133005921, 116285904}, + "srandom(12345) must match the measured sequence"); + + srandom_nid_postfix(0xffffffffull); + RequireSequence(Draw(6), {254925627, 1205188300, 366127624, 1401405153, 76053476, 1604170158}, + "srandom(0xffffffff) must match the measured sequence"); + + srandom_nid_postfix(0x80000000ull); + RequireSequence(Draw(6), {1336741213, 1210407648, 1447044896, 337392383, 82502902, 538660432}, + "srandom(0x80000000) must match the measured sequence"); + + std::vector tiny(16); + Require(initstate_nid_postfix(1, tiny.data(), 0) == nullptr, "initstate of 0 bytes must return null"); + Require(initstate_nid_postfix(1, tiny.data(), 7) == nullptr, "initstate of 7 bytes must return null"); + + const std::array states {{ + {8, {1646035001, 1025921153, 510616708, 590357944, 771515668}, "000000001465fc2d"}, + {15, {1646035001, 1025921153, 510616708, 590357944, 771515668}, "000000001465fc2d"}, + {31, {1646035001, 1025921153, 510616708, 590357944, 771515668}, "000000001465fc2d"}, + {32, {964237963, 406111040, 156505215, 1274863108, 1882652865}, "0100000082016ee0"}, + {63, {964237963, 406111040, 156505215, 1274863108, 1882652865}, "0100000082016ee0"}, + {64, {1894937090, 1645272306, 2143216519, 1889283008, 669383071}, "02000000fea8f5e2"}, + {127, {1894937090, 1645272306, 2143216519, 1889283008, 669383071}, "02000000fea8f5e2"}, + {128, {1804289383, 846930886, 1681692777, 1714636915, 1957747793}, "03000000b1391599"}, + {129, {1804289383, 846930886, 1681692777, 1714636915, 1957747793}, "03000000b1391599"}, + {255, {1804289383, 846930886, 1681692777, 1714636915, 1957747793}, "03000000b1391599"}, + {256, {510644794, 625058908, 1816371419, 326864818, 1257431873}, "0400000032ba371f"}, + {257, {510644794, 625058908, 1816371419, 326864818, 1257431873}, "0400000032ba371f"}, + {1000, {510644794, 625058908, 1816371419, 326864818, 1257431873}, "0400000032ba371f"}, + }}; + + std::vector> buffers; + for (const auto& state : states) { + buffers.emplace_back(static_cast(state.size) + 16, static_cast(0xa5)); + char* const buffer = buffers.back().data(); + char* const returned = initstate_nid_postfix(1, buffer, state.size); + Require(returned != nullptr, "initstate must not return null from 8 bytes up"); + RequireSequence(Draw(5), state.values, "initstate must draw the measured values"); + Require(FirstStateWord(buffer) == state.firstStateWord, "initstate must save the measured type word and first state word"); + } + + std::vector last(128); + char* const replaced = initstate_nid_postfix(5, last.data(), 128); + Require(replaced != nullptr, "initstate must not return null from 8 bytes up"); + Require(setstate_nid_postfix(replaced) != nullptr, "setstate of the state initstate returned must not return null"); + + return failures == 0 ? 0 : 1; +} diff --git a/core/libs/tests/GuestLibcStaticImports.cpp b/core/libs/tests/GuestLibcStaticImports.cpp index a8068575dd..ff0cc2a7fc 100644 --- a/core/libs/tests/GuestLibcStaticImports.cpp +++ b/core/libs/tests/GuestLibcStaticImports.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,21 +9,6 @@ #include #include -struct Xtime { - std::int64_t sec; - std::int64_t nsec; -}; - -extern "C" { -float APS5_VABI nearbyintf_nid_postfix(float x); -int APS5_VABI _Mtx_init_nid_postfix(void** handle, int type); -void APS5_VABI _Mtx_destroy_nid_postfix(void** handle); -int APS5_VABI _Mtx_lock_nid_postfix(void** handle); -int APS5_VABI _Mtx_unlock_nid_postfix(void** handle); -int APS5_VABI _Mtx_trylock_nid_postfix(void** handle); -int APS5_VABI _Thrd_sleep_nid_postfix(const Xtime* target, Xtime* remaining); -} - namespace { void Require(bool value) { @@ -156,6 +142,7 @@ void TestThrdSleep() { RequireThrows([] { _Thrd_sleep_nid_postfix(nullptr, nullptr); + }); } diff --git a/core/libs/tests/GuestLibcThreads.cpp b/core/libs/tests/GuestLibcThreads.cpp index 001558be39..adb6ec98ee 100644 --- a/core/libs/tests/GuestLibcThreads.cpp +++ b/core/libs/tests/GuestLibcThreads.cpp @@ -1,61 +1,19 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include +#include #include +#include #include #include -extern "C" { -int APS5_VABI _Mtx_init_nid_postfix(void** handle, int type); -void APS5_VABI _Mtx_destroy_nid_postfix(void** handle); -int APS5_VABI _Mtx_lock_nid_postfix(void** handle); -int APS5_VABI _Mtx_unlock_nid_postfix(void** handle); -int APS5_VABI _Cnd_init_nid_postfix(void** handle); -void APS5_VABI _Cnd_destroy_nid_postfix(void** handle); -int APS5_VABI _Cnd_wait_nid_postfix(void** condition, void** mutex); -int APS5_VABI _Cnd_broadcast_nid_postfix(void** handle); -int APS5_VABI _Cnd_signal_nid_postfix(void** handle); - -struct GuestPad { - unsigned (APS5_VABI* const* vtable)(GuestPad*); - void* condition; - void* mutex; - bool started; -}; - -void APS5_VABI _ZNSt4_PadC2Ev_nid_postfix(GuestPad* pad); -void APS5_VABI _ZNSt4_PadD2Ev_nid_postfix(GuestPad* pad); -void APS5_VABI _ZNSt4_Pad8_ReleaseEv_nid_postfix(GuestPad* pad); -void APS5_VABI _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(GuestPad* pad, void** thread); -int APS5_VABI _Thrd_join_nid_postfix(void* thread, int* code); -int APS5_VABI _Thrd_detach_nid_postfix(void* thread); -void RegisterPthreadCreate_nid_no_patch(int (APS5_VABI* fn)(void**, const void*, void* (APS5_VABI*)(void*), void*)); -void RegisterPthreadJoin_nid_no_patch(int (APS5_VABI* fn)(void*, void**)); -void RegisterPthreadDetach_nid_no_patch(int (APS5_VABI* fn)(void*)); - -struct ThreadForwards { - int (APS5_VABI* once)(std::int32_t*, void (APS5_VABI*)(void)); - int (APS5_VABI* keyCreate)(int*, void (APS5_VABI*)(void*)); - int (APS5_VABI* keyDelete)(int); - void* (APS5_VABI* getSpecific)(int); - int (APS5_VABI* setSpecific)(int, void*); - int (APS5_VABI* equal)(void*, void*); - void* (APS5_VABI* self)(); - void (APS5_VABI* exit)(void*); -}; -void RegisterThreadForwards_nid_no_patch(const ThreadForwards* forwards); -void APS5_VABI _Call_once_nid_postfix(std::int32_t* flag, void (APS5_VABI* function)(void)); -int APS5_VABI _Tss_create_nid_postfix(int* key, void (APS5_VABI* destructor)(void*)); -int APS5_VABI _Tss_delete_nid_postfix(int key); -void* APS5_VABI _Tss_get_nid_postfix(int key); -int APS5_VABI _Tss_set_nid_postfix(int key, void* value); -int APS5_VABI _Thrd_equal_nid_postfix(void* first, void* second); -void* APS5_VABI _Thrd_current_nid_postfix(void); -int APS5_VABI _Mtx_current_owns_nid_postfix(void** handle); -void* APS5_VABI scePthreadSelf(); -} static void Require(bool value) { if (!value) std::abort(); } @@ -70,6 +28,7 @@ static void RequireThrows(TCall call) { } constexpr int success = 0; +constexpr int timedout = 2; constexpr int busy = 3; constexpr int plain = 0x01; constexpr int tryable = 0x02; @@ -82,6 +41,14 @@ static void* CreateMutex(int type) { return mutex; } +static Xtime RelativeTime(int milliseconds) { + const auto point = std::chrono::system_clock::now() + std::chrono::milliseconds(milliseconds); + const auto since = point.time_since_epoch(); + const auto seconds = std::chrono::duration_cast(since); + const auto nanos = std::chrono::duration_cast(since - seconds); + return {seconds.count(), nanos.count()}; +} + static void MutexBasics() { void* mutex = CreateMutex(tryable); Require(_Mtx_lock_nid_postfix(&mutex) == success); @@ -98,10 +65,12 @@ static void MutexBasics() { RequireThrows([&] { _Mtx_lock_nid_postfix(&foreignMutex); }); _Cnd_destroy_nid_postfix(&foreignCondition); _Mtx_destroy_nid_postfix(&mutex); + Require(mutex == nullptr); RequireThrows([&] { _Mtx_lock_nid_postfix(&mutex); }); void* emptyMutex = nullptr; _Mtx_destroy_nid_postfix(&emptyMutex); + RequireThrows([] { _Mtx_destroy_nid_postfix(nullptr); }); void* plainMutex = CreateMutex(plain); @@ -119,11 +88,13 @@ static void MutexBasics() { Require(_Mtx_lock_nid_postfix(&defaultMutex) == success && _Mtx_lock_nid_postfix(&defaultMutex) == busy); Require(_Mtx_unlock_nid_postfix(&defaultMutex) == success); _Mtx_destroy_nid_postfix(&defaultMutex); + void* unknownBitsMutex = nullptr; Require(_Mtx_init_nid_postfix(&unknownBitsMutex, tryable | 0x8) == success && unknownBitsMutex != nullptr); Require(_Mtx_lock_nid_postfix(&unknownBitsMutex) == success && _Mtx_lock_nid_postfix(&unknownBitsMutex) == busy); Require(_Mtx_unlock_nid_postfix(&unknownBitsMutex) == success); _Mtx_destroy_nid_postfix(&unknownBitsMutex); + RequireThrows([] { _Mtx_init_nid_postfix(nullptr, tryable); }); RequireThrows([] { _Mtx_lock_nid_postfix(nullptr); }); RequireThrows([] { _Mtx_unlock_nid_postfix(nullptr); }); @@ -145,6 +116,38 @@ static void RecursiveMutex() { other.join(); Require(acquired); _Mtx_destroy_nid_postfix(&mutex); + +} + +static void TimedMutex() { + void* mutex = CreateMutex(tryable); + Xtime future = RelativeTime(60000); + Require(_Mtx_timedlock_nid_postfix(&mutex, &future) == success); + Require(_Mtx_timedlock_nid_postfix(&mutex, &future) == busy); + Require(_Mtx_unlock_nid_postfix(&mutex) == success); + + Require(_Mtx_lock_nid_postfix(&mutex) == success); + int result = -1; + std::thread worker([&] { + Xtime deadline = RelativeTime(30); + result = _Mtx_timedlock_nid_postfix(&mutex, &deadline); + }); + worker.join(); + Require(result == timedout); + Require(_Mtx_unlock_nid_postfix(&mutex) == success); + + void* empty = nullptr; + RequireThrows([&] { _Mtx_timedlock_nid_postfix(&empty, &future); }); + RequireThrows([&] { _Mtx_timedlock_nid_postfix(nullptr, &future); }); + RequireThrows([&] { _Mtx_timedlock_nid_postfix(&mutex, nullptr); }); + _Mtx_destroy_nid_postfix(&mutex); + + void* recursiveMutex = CreateMutex(recursive); + Require(_Mtx_timedlock_nid_postfix(&recursiveMutex, &future) == success); + Require(_Mtx_timedlock_nid_postfix(&recursiveMutex, &future) == success); + Require(_Mtx_unlock_nid_postfix(&recursiveMutex) == success); + Require(_Mtx_unlock_nid_postfix(&recursiveMutex) == success); + _Mtx_destroy_nid_postfix(&recursiveMutex); } static void MutualExclusion() { @@ -161,6 +164,7 @@ static void MutualExclusion() { for (auto& worker : workers) worker.join(); Require(counter == 80000); _Mtx_destroy_nid_postfix(&mutex); + } static void ConditionBroadcast() { @@ -206,12 +210,15 @@ static void ConditionBroadcast() { RequireThrows([] { _Cnd_broadcast_nid_postfix(nullptr); }); RequireThrows([] { _Cnd_init_nid_postfix(nullptr); }); _Cnd_destroy_nid_postfix(&condition); + Require(condition == nullptr); RequireThrows([&] { _Cnd_broadcast_nid_postfix(&condition); }); void* emptyCondition = nullptr; _Cnd_destroy_nid_postfix(&emptyCondition); + RequireThrows([] { _Cnd_destroy_nid_postfix(nullptr); }); _Mtx_destroy_nid_postfix(&mutex); + } static void ConditionSignal() { @@ -219,6 +226,7 @@ static void ConditionSignal() { Require(_Cnd_init_nid_postfix(&condition) == success); Require(_Cnd_signal_nid_postfix(&condition) == success); _Cnd_destroy_nid_postfix(&condition); + } struct TestPad { @@ -234,7 +242,10 @@ struct HostPadThread { void* result = nullptr; }; -static int APS5_VABI HostPthreadCreate(void** thread, const void*, void* (APS5_VABI* entry)(void*), void* arg) { +static const void* lastCreateAttrSlot = nullptr; + +static int APS5_VABI HostPthreadCreate(void** thread, const void* attrSlot, void* (APS5_VABI* entry)(void*), void* arg) { + lastCreateAttrSlot = attrSlot; auto* handle = new HostPadThread{}; handle->worker = std::thread([handle, entry, arg] { handle->result = entry(arg); }); *thread = handle; @@ -260,23 +271,101 @@ static unsigned APS5_VABI TestPadGo(GuestPad* base) { auto* pad = reinterpret_cast(base); pad->ran.store(true); _ZNSt4_Pad8_ReleaseEv_nid_postfix(base); + return 42; } +static void RequireExit(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "CRT exit lifecycle: %s\n", message); + std::abort(); + } +} + +struct CrtExitState { + int code = 0; + int key = -1; + std::barrier<>* ready = nullptr; + void* parent = nullptr; + void* self = nullptr; + std::atomic destructors{0}; + std::atomic returned{false}; +}; + +static void APS5_VABI CrtExitDestructor(void* value) { + auto& state = *static_cast(value); + RequireExit(_Thrd_current_nid_postfix() == state.self, "identity during destructor"); + RequireExit(_Tss_get_nid_postfix(state.key) == nullptr, "value cleared before destructor"); + if (state.destructors.fetch_add(1) == 0) + RequireExit(_Tss_set_nid_postfix(state.key, &state) == 0, "reinstall value for another cleanup pass"); +} + +static auto volatile crtExit = &_Thrd_exit_nid_postfix; + +static void* APS5_VABI CrtExitEntry(void* value) { + auto& state = *static_cast(value); + state.self = _Thrd_current_nid_postfix(); + RequireExit(state.self && _Thrd_equal_nid_postfix(state.self, state.self) == 1, "current identity"); + RequireExit(_Thrd_equal_nid_postfix(state.self, state.parent) == 0, "different parent identity"); + RequireExit(_Tss_get_nid_postfix(state.key) == nullptr, "child starts with empty value"); + RequireExit(_Tss_set_nid_postfix(state.key, &state) == 0, "set child value"); + state.ready->arrive_and_wait(); + RequireExit(_Tss_get_nid_postfix(state.key) == &state, "child value isolation"); + crtExit(state.code); + state.returned = true; + return nullptr; +} + +static void ThreadExitLifecycle() { + constexpr std::array codes{0, 42, -1, std::numeric_limits::min(), std::numeric_limits::max()}; + constexpr std::array expected{0, 42, 0xffffffffu, 0x80000000u, 0x7fffffffu}; + std::barrier ready(6); + std::array states; + std::array threads{}; + int key = -1, parentValue = 31; + void* parent = _Thrd_current_nid_postfix(); + RequireExit(_Tss_create_nid_postfix(&key, CrtExitDestructor) == 0, "create destructor key"); + RequireExit(_Tss_set_nid_postfix(key, &parentValue) == 0, "set parent value"); + for (unsigned i = 0; i < states.size(); ++i) { + states[i].code = codes[i]; + states[i].key = key; + states[i].ready = &ready; + states[i].parent = parent; + RequireExit(scePthreadCreate(&threads[i], nullptr, CrtExitEntry, &states[i], "CRT exit audit") == 0, "create child"); + } + RequireExit(_Tss_get_nid_postfix(key) == &parentValue, "parent isolated before release"); + ready.arrive_and_wait(); + for (unsigned i = 0; i < states.size(); ++i) { + void* result = nullptr; + RequireExit(scePthreadJoin(threads[i], &result) == 0, "join exiting child"); + RequireExit(reinterpret_cast(result) == expected[i], "full zero-extended exit result"); + RequireExit(!states[i].returned, "exit must not return"); + RequireExit(states[i].destructors == 2, "reinstalled destructor runs twice before join returns"); + RequireExit(states[i].self == threads[i], "created and current identity match"); + } + RequireExit(_Tss_get_nid_postfix(key) == &parentValue, "parent isolated after children exit"); + RequireExit(_Tss_delete_nid_postfix(key) == 0, "delete key"); + RequireExit(_Tss_get_nid_postfix(key) == nullptr, "deleted key value unavailable"); + std::puts("PASS CRT lifecycle: five concurrent exit codes, full return width, identity, parent/child isolation, two destructor passes, key deletion"); +} + static void PadLaunchAndJoin() { unsigned (APS5_VABI* vtable[1])(GuestPad*) = {TestPadGo}; TestPad unregPad{}; unregPad.vtable = vtable; _ZNSt4_PadC2Ev_nid_postfix(reinterpret_cast(&unregPad)); + RegisterPthreadCreate_nid_no_patch(nullptr); RegisterPthreadJoin_nid_no_patch(nullptr); void* unregThread = nullptr; RequireThrows([&] { _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(reinterpret_cast(&unregPad), &unregThread); + }); RequireThrows([&] { int code = 0; _Thrd_join_nid_postfix(&unregPad, &code); + }); _ZNSt4_PadD2Ev_nid_postfix(reinterpret_cast(&unregPad)); @@ -286,10 +375,14 @@ static void PadLaunchAndJoin() { TestPad pad{}; pad.vtable = vtable; _ZNSt4_PadC2Ev_nid_postfix(reinterpret_cast(&pad)); + void* thread = nullptr; _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(reinterpret_cast(&pad), &thread); + Require(thread != nullptr && pad.started && pad.ran.load()); + Require(lastCreateAttrSlot == nullptr); _ZNSt4_PadD2Ev_nid_postfix(reinterpret_cast(&pad)); + int code = 0; Require(_Thrd_join_nid_postfix(thread, &code) == success && code == 42); Require(_Thrd_join_nid_postfix(nullptr, &code) == 4); @@ -298,6 +391,32 @@ static void PadLaunchAndJoin() { RegisterPthreadJoin_nid_no_patch(nullptr); } +static void PadLaunchWithAttributes() { + unsigned (APS5_VABI* vtable[1])(GuestPad*) = {TestPadGo}; + RegisterPthreadCreate_nid_no_patch(HostPthreadCreate); + RegisterPthreadJoin_nid_no_patch(HostPthreadJoin); + + TestPad pad{}; + pad.vtable = vtable; + _ZNSt4_PadC2Ev_nid_postfix(reinterpret_cast(&pad)); + + int attribute = 0; + void* attrSlot = &attribute; + void* thread = nullptr; + lastCreateAttrSlot = nullptr; + _ZNSt4_Pad7_LaunchEPP12pthread_attrPP7pthread_nid_postfix(reinterpret_cast(&pad), &attrSlot, &thread); + Require(thread != nullptr && pad.started && pad.ran.load()); + Require(attrSlot == &attribute); + Require(lastCreateAttrSlot == &attrSlot); + + _ZNSt4_PadD2Ev_nid_postfix(reinterpret_cast(&pad)); + int code = 0; + Require(_Thrd_join_nid_postfix(thread, &code) == success && code == 42); + + RegisterPthreadCreate_nid_no_patch(nullptr); + RegisterPthreadJoin_nid_no_patch(nullptr); +} + static void PadDetach() { unsigned (APS5_VABI* vtable[1])(GuestPad*) = {TestPadGo}; RegisterPthreadDetach_nid_no_patch(nullptr); @@ -309,10 +428,13 @@ static void PadDetach() { TestPad pad{}; pad.vtable = vtable; _ZNSt4_PadC2Ev_nid_postfix(reinterpret_cast(&pad)); + void* thread = nullptr; _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(reinterpret_cast(&pad), &thread); + Require(thread != nullptr && pad.ran.load()); _ZNSt4_PadD2Ev_nid_postfix(reinterpret_cast(&pad)); + Require(_Thrd_detach_nid_postfix(thread) == success); Require(_Thrd_detach_nid_postfix(nullptr) == 4); @@ -322,6 +444,7 @@ static void PadDetach() { static void KernelKeys() { Require(_Thrd_current_nid_postfix() == scePthreadSelf()); + Require(_Thrd_id_nid_postfix() == scePthreadSelf()); int key = -1; Require(_Tss_create_nid_postfix(&key, nullptr) == success && key >= 0); int value = 0; @@ -355,10 +478,11 @@ static void APS5_VABI FakeExit(void*) { std::abort(); } static void APS5_VABI OnceTarget() {} static void ForwardedCalls() { - static const ThreadForwards fakes{FakeOnce, FakeKeyCreate, FakeKeyDelete, FakeGetSpecific, FakeSetSpecific, FakeEqual, FakeSelf, FakeExit}; + static const ThreadForwards_nid_no_patch fakes{FakeOnce, FakeKeyCreate, FakeKeyDelete, FakeGetSpecific, FakeSetSpecific, FakeEqual, FakeSelf, FakeExit}; RegisterThreadForwards_nid_no_patch(&fakes); std::int32_t flag = 0; _Call_once_nid_postfix(&flag, OnceTarget); + Require(forwardLog.flag == &flag && forwardLog.function == OnceTarget); int key = 0; Require(_Tss_create_nid_postfix(&key, nullptr) == static_cast(0x80020016) && key == 41); @@ -367,21 +491,109 @@ static void ForwardedCalls() { Require(_Tss_set_nid_postfix(8, &flag) == success && forwardLog.key == 8 && forwardLog.value == &flag); Require(_Thrd_equal_nid_postfix(&flag, &key) == 1 && forwardLog.first == &flag && forwardLog.second == &key); Require(_Thrd_current_nid_postfix() == &forwardLog.key); + Require(_Thrd_id_nid_postfix() == &forwardLog.key); Require(_Mtx_current_owns_nid_postfix(nullptr) == 1); RegisterThreadForwards_nid_no_patch(nullptr); RequireThrows([] { _Thrd_current_nid_postfix(); }); + RequireThrows([] { _Thrd_id_nid_postfix(); }); RequireThrows([] { _Tss_get_nid_postfix(0); }); } +struct VersionedShared { + PthreadMutex mutex = nullptr; + PthreadCond condition = nullptr; + std::atomic stage{0}; + int tryResult = -1; +}; + +static void* APS5_VABI VersionedWorker(void* argument) { + auto* shared = static_cast(argument); + shared->tryResult = _ZNSt8__sce_v212_Mtx_trylockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&shared->mutex)); + shared->stage = 1; + Require(_ZNSt8__sce_v29_Mtx_lockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&shared->mutex)) == success); + shared->stage = 2; + Require(_ZNSt8__sce_v211_Cnd_signalEPP12pthread_cond_nid_postfix(reinterpret_cast(&shared->condition)) == success); + Require(_ZNSt8__sce_v211_Mtx_unlockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&shared->mutex)) == success); + return reinterpret_cast(std::intptr_t{42}); +} + +static void* APS5_VABI DetachedWorker(void*) { return nullptr; } + +static void VersionedSync() { + PthreadMutex recursiveMutex = reinterpret_cast(std::uintptr_t{1}); + Require(_ZNSt8__sce_v29_Mtx_initEPP13pthread_mutexi_nid_postfix(reinterpret_cast(&recursiveMutex), plain | recursive) == success && recursiveMutex != nullptr); + Require(_ZNSt8__sce_v29_Mtx_lockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&recursiveMutex)) == success); + Require(_ZNSt8__sce_v29_Mtx_lockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&recursiveMutex)) == success); + Require(_ZNSt8__sce_v212_Mtx_trylockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&recursiveMutex)) == success); + for (int unlock = 0; unlock < 3; ++unlock) Require(_ZNSt8__sce_v211_Mtx_unlockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&recursiveMutex)) == success); + Require(_ZNSt8__sce_v212_Mtx_destroyEPP13pthread_mutex_nid_postfix(reinterpret_cast(&recursiveMutex)) == success); + + PthreadMutex plainMutex = nullptr; + Require(_ZNSt8__sce_v219_Mtx_init_with_nameEPP13pthread_mutexiPKc_nid_postfix(reinterpret_cast(&plainMutex), plain, "") == success); + Require(_ZNSt8__sce_v29_Mtx_lockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&plainMutex)) == success); + Require(_ZNSt8__sce_v29_Mtx_lockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&plainMutex)) == busy); + Require(_ZNSt8__sce_v211_Mtx_unlockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&plainMutex)) == success); + PthreadCond unownedCondition = nullptr; + Require(_ZNSt8__sce_v29_Cnd_initEPP12pthread_cond_nid_postfix(reinterpret_cast(&unownedCondition)) == success); + Require(scePthreadMutexUnlock(&plainMutex) != 0); + Require(scePthreadCondWait(&unownedCondition, &plainMutex) != 0); + Require(_ZNSt8__sce_v211_Mtx_unlockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&plainMutex)) == success); + Require(_ZNSt8__sce_v29_Cnd_waitEPP12pthread_condPP13pthread_mutex_nid_postfix(reinterpret_cast(&unownedCondition), reinterpret_cast(&plainMutex)) == success); + Require(_ZNSt8__sce_v212_Cnd_destroyEPP12pthread_cond_nid_postfix(reinterpret_cast(&unownedCondition)) == success); + Require(_ZNSt8__sce_v212_Mtx_destroyEPP13pthread_mutex_nid_postfix(reinterpret_cast(&plainMutex)) == success); + + VersionedShared shared; + Require(_ZNSt8__sce_v29_Mtx_initEPP13pthread_mutexi_nid_postfix(reinterpret_cast(&shared.mutex), plain) == success); + Require(_ZNSt8__sce_v219_Cnd_init_with_nameEPP12pthread_condPKc_nid_postfix(reinterpret_cast(&shared.condition), "versioned") == success); + Require(_ZNSt8__sce_v29_Mtx_lockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&shared.mutex)) == success); + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, VersionedWorker, &shared, "versioned") == 0); + while (shared.stage.load() == 0) std::this_thread::yield(); + while (shared.stage.load() == 1) Require(_ZNSt8__sce_v29_Cnd_waitEPP12pthread_condPP13pthread_mutex_nid_postfix(reinterpret_cast(&shared.condition), reinterpret_cast(&shared.mutex)) == success); + Require(shared.tryResult == busy); + Require(_ZNSt8__sce_v211_Mtx_unlockEPP13pthread_mutex_nid_postfix(reinterpret_cast(&shared.mutex)) == success); + int code = 0; + Require(_ZNSt8__sce_v210_Thrd_joinEP7pthreadPi_nid_postfix(thread, &code) == success && code == 42); + Require(_ZNSt8__sce_v210_Thrd_joinEP7pthreadPi_nid_postfix(thread, &code) == 4); + Require(_ZNSt8__sce_v214_Cnd_broadcastEPP12pthread_cond_nid_postfix(reinterpret_cast(&shared.condition)) == success); + Require(_ZNSt8__sce_v212_Cnd_destroyEPP12pthread_cond_nid_postfix(reinterpret_cast(&shared.condition)) == success); + Require(_ZNSt8__sce_v212_Mtx_destroyEPP13pthread_mutex_nid_postfix(reinterpret_cast(&shared.mutex)) == success); + + PthreadCond condition = nullptr; + Require(_ZNSt8__sce_v29_Cnd_initEPP12pthread_cond_nid_postfix(reinterpret_cast(&condition)) == success); + Require(_ZNSt8__sce_v212_Cnd_destroyEPP12pthread_cond_nid_postfix(reinterpret_cast(&condition)) == success); + + Pthread detached = nullptr; + Require(scePthreadCreate(&detached, nullptr, DetachedWorker, nullptr, "detached") == 0); + Require(_ZNSt8__sce_v212_Thrd_detachEP7pthread_nid_postfix(detached) == success); + Require(_ZNSt8__sce_v212_Thrd_detachEP7pthread_nid_postfix(nullptr) == 4); + _ZNSt8__sce_v211_Thrd_yieldEv_nid_postfix(); + + Require(_ZNSt8__sce_v28_Thrd_idEv_nid_postfix() != nullptr); +} + +static void ThreadHelpers() { + Require(_Thrd_yield_nid_postfix() == success); + std::thread other([&] { + Require(_Thrd_yield_nid_postfix() == success); + }); + other.join(); +} + int main() { + ThreadExitLifecycle(); KernelKeys(); + VersionedSync(); ForwardedCalls(); MutexBasics(); RecursiveMutex(); + TimedMutex(); MutualExclusion(); ConditionBroadcast(); ConditionSignal(); PadLaunchAndJoin(); + PadLaunchWithAttributes(); PadDetach(); + ThreadHelpers(); } diff --git a/core/libs/tests/GuestLibreSsl3Digest.cpp b/core/libs/tests/GuestLibreSsl3Digest.cpp new file mode 100644 index 0000000000..c2b6bf80a8 --- /dev/null +++ b/core/libs/tests/GuestLibreSsl3Digest.cpp @@ -0,0 +1,50 @@ +#include "prx/libSceLibreSsl3/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, "LibreSSL3 digest check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireInvalidArgument(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::invalid_argument&) { return; } catch (const std::exception&) {} + Require(false, location); +} + +static std::string Hex(const unsigned char* data, std::size_t size) { + static constexpr char digits[] = "0123456789abcdef"; + std::string text; + for (std::size_t i = 0; i < size; ++i) { + text += digits[data[i] >> 4]; + text += digits[data[i] & 15]; + } + return text; +} + +int main() { + const auto* abc = reinterpret_cast("abc"); + const std::string million(1000000, 'a'); + const auto* many = reinterpret_cast(million.data()); + unsigned char digest[24]; + std::memset(digest, 0xee, sizeof(digest)); + Require(MD5_nid_postfix(abc, 3, digest) == digest && Hex(digest, 16) == "900150983cd24fb0d6963f7d28e17f72" && digest[16] == 0xee); + Require(MD5_nid_postfix(nullptr, 0, digest) == digest && Hex(digest, 16) == "d41d8cd98f00b204e9800998ecf8427e"); + Require(MD5_nid_postfix(many, million.size(), digest) == digest && Hex(digest, 16) == "7707d6ae4e027c70eea2a935c2296f21"); + std::memset(digest, 0xee, sizeof(digest)); + Require(SHA1_nid_postfix(abc, 3, digest) == digest && Hex(digest, 20) == "a9993e364706816aba3e25717850c26c9cd0d89d" && digest[20] == 0xee); + Require(SHA1_nid_postfix(nullptr, 0, digest) == digest && Hex(digest, 20) == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + Require(SHA1_nid_postfix(many, million.size(), digest) == digest && Hex(digest, 20) == "34aa973cd4c4daa4f61eeb2bdbad27316534016f"); + RequireInvalidArgument([&] { MD5_nid_postfix(abc, 3, nullptr); }); + RequireInvalidArgument([&] { SHA1_nid_postfix(abc, 3, nullptr); }); + RequireInvalidArgument([&] { SHA1_nid_postfix(nullptr, 1, digest); }); + + std::puts("libSceLibreSsl3 digest tests passed"); +} diff --git a/core/libs/tests/GuestLibreSslAes.cpp b/core/libs/tests/GuestLibreSslAes.cpp new file mode 100644 index 0000000000..e36afbc5d8 --- /dev/null +++ b/core/libs/tests/GuestLibreSslAes.cpp @@ -0,0 +1,219 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", what); + std::abort(); +} + +template static void RequireThrows(TAction action, const char* what) { + try { action(); } catch (const std::exception&) { return; } + Require(false, what); +} + +static std::vector Bytes(const char* hex) { + std::vector bytes; + for (; hex[0] && hex[1]; hex += 2) bytes.push_back(static_cast(std::stoi(std::string(hex, 2), nullptr, 16))); + return bytes; +} + +static std::string Hex(const unsigned char* data, std::size_t size) { + static constexpr char digits[] = "0123456789abcdef"; + std::string text; + for (std::size_t i = 0; i < size; ++i) { + text += digits[data[i] >> 4]; + text += digits[data[i] & 15]; + } + return text; +} + +static std::uint32_t Word(const std::vector& bytes, std::size_t index) { + return std::uint32_t(bytes[4 * index]) << 24 | std::uint32_t(bytes[4 * index + 1]) << 16 | std::uint32_t(bytes[4 * index + 2]) << 8 | bytes[4 * index + 3]; +} + +static AesKey Sentinel() { + AesKey key; + for (std::uint32_t i = 0; i < 60; ++i) key.roundKeys[i] = 0xa5a50000u | i; + key.rounds = 0x5a5a; + return key; +} + +template static void RequireSchedule(TSetup setup, const char* keyHex, const char* scheduleHex, const char* what) { + const auto userKey = Bytes(keyHex); + const auto schedule = Bytes(scheduleHex); + const std::size_t used = schedule.size() / 4; + const AesKey before = Sentinel(); + AesKey key = before; + Require(setup(userKey.data(), static_cast(userKey.size() * 8), &key) == 0 && key.rounds == static_cast(used / 4 - 1), what); + for (std::size_t i = 0; i < 60; ++i) Require(key.roundKeys[i] == (i < used ? Word(schedule, i) : before.roundKeys[i]), what); +} + +template static void RequireRefusalsKeepKey(TSetup setup, const char* what) { + const unsigned char userKey[32] {}; + const AesKey before = Sentinel(); + for (const int bits : {0, 64, 127, 129, 160, 224, 255, 384, 512, -128}) { + AesKey key = before; + Require(setup(userKey, bits, &key) == -2 && !std::memcmp(&key, &before, sizeof(key)), what); + } + for (const int bits : {128, 64}) { + AesKey key = before; + Require(setup(nullptr, bits, &key) == -1 && !std::memcmp(&key, &before, sizeof(key)), what); + } + Require(setup(userKey, 128, nullptr) == -1, what); +} + +int main() { + const auto plain = Bytes("00112233445566778899aabbccddeeff"); + const char* keys[3] { + "000102030405060708090a0b0c0d0e0f", + "000102030405060708090a0b0c0d0e0f1011121314151617", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + }; + const char* ciphers[3] {"69c4e0d86a7b0430d8cdb78070b4c55a", "dda97ca4864cdfe06eaf70a0ec0d7191", "8ea2b7ca516745bfeafc49904b496089"}; + for (int i = 0; i < 3; ++i) { + const auto key = Bytes(keys[i]); + const int bits = static_cast(key.size() * 8); + AesKey encrypt {}, decrypt {}; + unsigned char out[16] {}, back[16] {}; + Require(AES_set_encrypt_key_nid_postfix(key.data(), bits, &encrypt) == 0 && encrypt.rounds == 10 + 2 * i, "encrypt key"); + Require(AES_set_decrypt_key_nid_postfix(key.data(), bits, &decrypt) == 0 && decrypt.rounds == encrypt.rounds, "decrypt key"); + const int last = 4 * encrypt.rounds; + Require(!std::memcmp(decrypt.roundKeys, encrypt.roundKeys + last, 16) && !std::memcmp(decrypt.roundKeys + last, encrypt.roundKeys, 16), "decrypt key reverses the round keys"); + AES_encrypt_nid_postfix(plain.data(), out, &encrypt); + + Require(Hex(out, 16) == ciphers[i], "FIPS-197 appendix C cipher"); + AES_decrypt_nid_postfix(out, back, &decrypt); + + Require(!std::memcmp(back, plain.data(), 16), "FIPS-197 appendix C inverse cipher"); + AES_ecb_encrypt_nid_postfix(plain.data(), out, &encrypt, 1); + + Require(Hex(out, 16) == ciphers[i], "ECB encrypt"); + AES_ecb_encrypt_nid_postfix(out, out, &decrypt, 0); + + Require(!std::memcmp(out, plain.data(), 16), "ECB decrypt in place"); + } + + AesKey fipsKey {}; + Require(AES_set_encrypt_key_nid_postfix(Bytes("2b7e151628aed2a6abf7158809cf4f3c").data(), 128, &fipsKey) == 0, "FIPS-197 appendix A key"); + Require(fipsKey.roundKeys[4] == 0xa0fafe17 && fipsKey.roundKeys[43] == 0xb6630ca6, "FIPS-197 appendix A.1 expansion"); + AesKey bad {}; + bad.rounds = 7; + bad.roundKeys[0] = 1; + const unsigned char zero[32] {}; + Require(AES_set_encrypt_key_nid_postfix(zero, 64, &bad) == -2 && bad.rounds == 7 && bad.roundKeys[0] == 1, "unsupported key length keeps the key"); + Require(AES_set_decrypt_key_nid_postfix(zero, 129, &bad) == -2 && bad.rounds == 7 && bad.roundKeys[0] == 1, "unsupported decrypt key length keeps the key"); + Require(AES_set_encrypt_key_nid_postfix(nullptr, 128, &bad) == -1 && AES_set_encrypt_key_nid_postfix(zero, 128, nullptr) == -1, "null key arguments"); + unsigned char block[16] {}; + RequireThrows([&] { AES_encrypt_nid_postfix(zero, block, &bad); }, "key schedule with an invalid round count"); + RequireRefusalsKeepKey(&AES_set_encrypt_key_nid_postfix, "refused encrypt key setup leaves the key unchanged"); + RequireRefusalsKeepKey(&AES_set_decrypt_key_nid_postfix, "refused decrypt key setup leaves the key unchanged"); + RequireSchedule(&AES_set_encrypt_key_nid_postfix, "2b7e151628aed2a6abf7158809cf4f3c", + "2b7e151628aed2a6abf7158809cf4f3ca0fafe1788542cb123a339392a6c7605f2c295f27a96b9435935807a7359f67f3d80477d4716fe3e1e237e446d7a883b" + "ef44a541a8525b7fb671253bdb0bad00d4d1c6f87c839d87caf2b8bc11f915bc6d88a37a110b3efddbf98641ca0093fd4e54f70e5f5fc9f384a64fb24ea6dc4f" + "ead27321b58dbad2312bf5607f8d292fac7766f319fadc2128d12941575c006ed014f9a8c9ee2589e13f0cc8b6630ca6", + "FIPS-197 appendix A.1 AES-128 encrypt schedule"); + RequireSchedule(&AES_set_encrypt_key_nid_postfix, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7bfe0c91f72402f5a5ec12068e6c827f6b0e7a95b95c56fec24db7b4bd69b5411885a74796e92538fd" + "e75fad44bb095386485af05721efb14fa448f6d94d6dce24aa326360113b30e6a25e7ed583b1cf9a27f939436a94f767c0a69407d19da4e1ec1786eb6fa64971" + "485f703222cb8755e26d135233f0b7b340beeb282f18a2596747d26b458c553ea7e1466c9411f1df821f750aad07d753ca4005388fcc5006282d166abc3ce7b5" + "e98ba06f448c773c8ecc720401002202", + "FIPS-197 appendix A.2 AES-192 encrypt schedule"); + RequireSchedule(&AES_set_encrypt_key_nid_postfix, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff49ba354118e6925afa51a8b5f2067fcdea8b09c1a93d194cdbe49846eb75d5b9a" + "d59aecb85bf3c917fee94248de8ebe96b5a9328a2678a647983122292f6c79b3812c81addadf48ba24360af2fab8b46498c5bfc9bebd198e268c3ba709e04214" + "68007bacb2df331696e939e46c518d80c814e20476a9fb8a5025c02d59c58239de1369676ccc5a71fa2563959674ee155886ca5d2e2f31d77e0af1fa27cf73c3" + "749c47ab18501ddae2757e4f7401905acafaaae3e4d59b349adf6acebd10190dfe4890d1e6188d0b046df344706c631e", + "FIPS-197 appendix A.3 AES-256 encrypt schedule"); + RequireSchedule(&AES_set_decrypt_key_nid_postfix, "2b7e151628aed2a6abf7158809cf4f3c", + "d014f9a8c9ee2589e13f0cc8b6630ca60c7b5a631319eafeb0398890664cfbb4df7d925a1f62b09da320626ed675732412c07647c01f22c7bc42d2f37555114a" + "6efcd876d2df54807c5df034c917c3b96ea30afcbc238cf6ae82a4b4b54a338d90884413d280860a12a128421bc897397c1f13f74208c219c021ae480969bf7b" + "cc7505eb3e17d1ee82296c51c94811332b3708a7f262d405bc3ebdbf4b617d622b7e151628aed2a6abf7158809cf4f3c", + "AES-128 decrypt schedule"); + RequireSchedule(&AES_set_decrypt_key_nid_postfix, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "e98ba06f448c773c8ecc720401002202ac491644e55710b746c08a75c89b2cada3979ac28e5ba6d8e12cc9e654b272baf3b42258b59ebb5cf8fb64fe491e06f3" + "4d65dfa2b1e5620dea899c312dcc3c1a5b6cfe3cc745a02bf8b9a572462a9904c5ddb7f8be933c760b4f46a6fc80bdafb5dc7ad0f7cffb09a7ec43939c295e17" + "5023b89a3bc51d84d04b19377b4e8b8e41b34544ab0592b9ce92f15e421381d9659763e78c817087123039436be6a51e9eb149c479d69c5dfeb4a27ceab6d7fd" + "8e73b0f7da0e6452c810f32b809079e5", + "AES-192 decrypt schedule"); + RequireSchedule(&AES_set_decrypt_key_nid_postfix, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "fe4890d1e6188d0b046df344706c631eada23f4963e23b2455427c8a5c70910457c96cf6074f07c0706abb07137f9241b668b621ce40046d36a047ae0932ed8e" + "34ad1e4450866b367725bcc76315294632526c367828b24cf8e043c33f92aa20c440b289642b757227a3d7f114309581d669a7334a7ade7a80c8f18fc772e9e3" + "25ba3c22a06bc7fb4388a2833393427054fb808b9c137949cab22ff547ba186c6c3d632985d1fbd9e3e36578701be0f34a7459f9c8e8f9c256a156bc8d083799" + "42107758e9ec98f066329ea193f8858b8ec6bff6829ca03b9e49af7edba96125603deb1015ca71be2b73aef0857d7781", + "AES-256 decrypt schedule"); + + const auto key = Bytes("2b7e151628aed2a6abf7158809cf4f3c"); + const auto message = Bytes("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"); + AesKey encrypt {}, decrypt {}; + AES_set_encrypt_key_nid_postfix(key.data(), 128, &encrypt); + AES_set_decrypt_key_nid_postfix(key.data(), 128, &decrypt); + + std::vector out(message.size() + 16), back(message.size() + 16); + + auto iv = Bytes("000102030405060708090a0b0c0d0e0f"); + AES_cbc_encrypt_nid_postfix(message.data(), out.data(), 64, &encrypt, iv.data(), 1); + + Require(Hex(out.data(), 64) == "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7", "SP 800-38A F.2.1 CBC encrypt"); + Require(Hex(iv.data(), 16) == "3ff1caa1681fac09120eca307586e1a7", "CBC encrypt returns the last cipher block as iv"); + iv = Bytes("000102030405060708090a0b0c0d0e0f"); + std::memcpy(back.data(), out.data(), 64); + AES_cbc_encrypt_nid_postfix(back.data(), back.data(), 32, &decrypt, iv.data(), 0); + AES_cbc_encrypt_nid_postfix(back.data() + 32, back.data() + 32, 32, &decrypt, iv.data(), 0); + + Require(!std::memcmp(back.data(), message.data(), 64) && Hex(iv.data(), 16) == "3ff1caa1681fac09120eca307586e1a7", "SP 800-38A F.2.2 CBC decrypt in place across calls"); + iv = Bytes("000102030405060708090a0b0c0d0e0f"); + std::vector partial(32, 0xee); + AES_cbc_encrypt_nid_postfix(message.data(), partial.data(), 20, &encrypt, iv.data(), 1); + + Require(Hex(partial.data(), 16) == "7649abac8119b246cee98e9b12e9197d" && partial[31] != 0xee && Hex(iv.data(), 16) == Hex(partial.data() + 16, 16), "CBC encrypt pads a partial block with the iv and writes the whole block"); + iv = Bytes("000102030405060708090a0b0c0d0e0f"); + std::vector plainPartial(32, 0xee); + AES_cbc_encrypt_nid_postfix(partial.data(), plainPartial.data(), 20, &decrypt, iv.data(), 0); + + Require(!std::memcmp(plainPartial.data(), message.data(), 20) && plainPartial[20] == 0xee && Hex(iv.data(), 16) == Hex(partial.data() + 16, 16), "CBC decrypt writes only the requested length"); + + iv = Bytes("000102030405060708090a0b0c0d0e0f"); + int num = 0; + AES_cfb128_encrypt_nid_postfix(message.data(), out.data(), 5, &encrypt, iv.data(), &num, 1); + AES_cfb128_encrypt_nid_postfix(message.data() + 5, out.data() + 5, 32, &encrypt, iv.data(), &num, 1); + + Require(num == 5, "CFB128 keeps the block position"); + AES_cfb128_encrypt_nid_postfix(message.data() + 37, out.data() + 37, 27, &encrypt, iv.data(), &num, 1); + + Require(Hex(out.data(), 64) == "3b3fd92eb72dad20333449f8e83cfb4ac8a64537a0b3a93fcde3cdad9f1ce58b26751f67a3cbb140b1808cf187a4f4dfc04b05357c5d1c0eeac4c66f9ff7f2e6" && num == 0, "SP 800-38A F.3.13 CFB128 encrypt in pieces"); + iv = Bytes("000102030405060708090a0b0c0d0e0f"); + num = 0; + AES_cfb128_encrypt_nid_postfix(out.data(), back.data(), 64, &encrypt, iv.data(), &num, 0); + + Require(!std::memcmp(back.data(), message.data(), 64), "SP 800-38A F.3.14 CFB128 decrypt"); + num = 16; + RequireThrows([&] { AES_cfb128_encrypt_nid_postfix(message.data(), out.data(), 1, &encrypt, iv.data(), &num, 1); }, "CFB128 position out of range"); + + auto counter = Bytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); + unsigned char keystream[16] {}; + unsigned position = 0; + AES_ctr128_encrypt_nid_postfix(message.data(), out.data(), 7, &encrypt, counter.data(), keystream, &position); + AES_ctr128_encrypt_nid_postfix(message.data() + 7, out.data() + 7, 57, &encrypt, counter.data(), keystream, &position); + + Require(Hex(out.data(), 64) == "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", "SP 800-38A F.5.1 CTR encrypt in pieces"); + Require(position == 0 && Hex(counter.data(), 16) == "f0f1f2f3f4f5f6f7f8f9fafbfcfdff03", "CTR advances the counter per block"); + counter = Bytes("ffffffffffffffffffffffffffffffff"); + position = 0; + AES_ctr128_encrypt_nid_postfix(message.data(), out.data(), 37, &encrypt, counter.data(), keystream, &position); + + Require(Hex(out.data(), 37) == "e13338e36cb71962e00d020b4cedbd86d3dae15b04bb352fa0f59febfcb4da3e67da610697", "CTR counter wraps across all 128 bits"); + Require(position == 5 && Hex(counter.data(), 16) == "00000000000000000000000000000002", "CTR position and counter after a partial block"); + position = 16; + RequireThrows([&] { AES_ctr128_encrypt_nid_postfix(message.data(), out.data(), 1, &encrypt, counter.data(), keystream, &position); }, "CTR position out of range"); + + std::puts("libSceLibreSsl AES tests passed"); +} diff --git a/core/libs/tests/GuestLibreSslCipher.cpp b/core/libs/tests/GuestLibreSslCipher.cpp new file mode 100644 index 0000000000..366264cc99 --- /dev/null +++ b/core/libs/tests/GuestLibreSslCipher.cpp @@ -0,0 +1,97 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct alignas(8) RawCipher { + std::array prefix; + std::int32_t strength; + std::int32_t algorithm; +}; + +static_assert(sizeof(RawCipher) == 88); +static_assert(offsetof(RawCipher, strength) == 80); +static_assert(offsetof(RawCipher, algorithm) == 84); + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", message); + std::abort(); +} + +static void RequireString(const char* actual, const char* expected) { + Require(actual && std::strcmp(actual, expected) == 0, expected); +} + +int main() { + struct { std::int32_t before, bits, after; } output {0x12345678, -777, 0x23456789}; + Require(SSL_CIPHER_get_bits_nid_postfix(nullptr, &output.bits) == 0, "null cipher has zero strength"); + Require(output.bits == -777, "null cipher leaves the optional output untouched"); + Require(SSL_CIPHER_get_bits_nid_postfix(nullptr, nullptr) == 0, "both pointers may be null"); + RequireString(SSL_CIPHER_get_name_nid_postfix(nullptr), "(NONE)"); + RequireString(SSL_CIPHER_get_version_nid_postfix(nullptr), "(NONE)"); + + struct { std::uint64_t before; RawCipher cipher; std::uint64_t after; } guarded; + std::memset(&guarded, 0xa5, sizeof(guarded)); + RawCipher& cipher = guarded.cipher; + const char customName[] = "arbitrary caller-provided cipher"; + const char* name = customName; + std::memcpy(cipher.prefix.data() + 8, &name, sizeof(name)); + Require(SSL_CIPHER_get_name_nid_postfix(&cipher) == name, "name pointer is returned without copying or a registry lookup"); + name = nullptr; + std::memcpy(cipher.prefix.data() + 8, &name, sizeof(name)); + Require(SSL_CIPHER_get_name_nid_postfix(&cipher) == nullptr, "null name field is preserved"); + name = customName; + std::memcpy(cipher.prefix.data() + 8, &name, sizeof(name)); + + constexpr std::int32_t bitPairs[][2] { + {0, 0}, {40, 128}, {56, 64}, {112, 168}, {128, 128}, {192, 192}, {256, 256}, + {-1, -2}, {std::numeric_limits::min(), std::numeric_limits::max()}, + {std::numeric_limits::max(), std::numeric_limits::min()}, + }; + for (const auto& pair : bitPairs) { + cipher.strength = pair[0]; + cipher.algorithm = pair[1]; + const auto snapshot = guarded; + output.bits = -777; + Require(SSL_CIPHER_get_bits_nid_postfix(&cipher, &output.bits) == pair[0], "effective strength is distinct from algorithm bits"); + Require(output.bits == pair[1], "algorithm bits use the 32-bit field at guest offset 84"); + Require(output.before == 0x12345678 && output.after == 0x23456789, "output writes exactly four bytes"); + Require(SSL_CIPHER_get_bits_nid_postfix(&cipher, nullptr) == pair[0], "optional output may be omitted"); + Require(std::memcmp(&guarded, &snapshot, sizeof(guarded)) == 0, "queries preserve the cipher and its guards"); + } + + constexpr std::uint64_t lowIds[] {0, 1, 0x35, 0xc02f, 0xffffff}; + for (std::uint64_t version = 0; version < 256; ++version) { + for (const auto low : lowIds) { + const std::uint64_t id = (version << 24) | low; + std::memcpy(cipher.prefix.data() + 16, &id, sizeof(id)); + RequireString(SSL_CIPHER_get_version_nid_postfix(&cipher), version == 3 ? "TLSv1/SSLv3" : "unknown"); + } + } + constexpr std::uint64_t wideIds[] {0x103000035ULL, 0x800000000300c02fULL, 0xffffffff03000000ULL, + std::numeric_limits::max()}; + for (const auto id : wideIds) { + std::memcpy(cipher.prefix.data() + 16, &id, sizeof(id)); + RequireString(SSL_CIPHER_get_version_nid_postfix(&cipher), "unknown"); + } + + cipher.strength = 40; + cipher.algorithm = 128; + Require(SSL_CIPHER_get_bits_nid_postfix(&cipher, &cipher.algorithm) == 40, "output may alias the algorithm field"); + Require(cipher.algorithm == 128, "self-alias preserves algorithm bits"); + Require(SSL_CIPHER_get_bits_nid_postfix(&cipher, &cipher.strength) == 128, "algorithm output is written before strength is read"); + Require(cipher.strength == 128, "aliased output receives algorithm bits"); + + const auto snapshot = guarded; + const std::int32_t valid = 0; + std::memcpy(cipher.prefix.data(), &valid, sizeof(valid)); + Require(SSL_CIPHER_get_name_nid_postfix(&cipher) == customName, "valid flag does not gate name access"); + Require(SSL_CIPHER_get_bits_nid_postfix(&cipher, nullptr) == 128, "valid flag does not gate strength access"); + Require(guarded.before == snapshot.before && guarded.after == snapshot.after, "input guards remain intact"); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslDes.cpp b/core/libs/tests/GuestLibreSslDes.cpp new file mode 100644 index 0000000000..6191facec5 --- /dev/null +++ b/core/libs/tests/GuestLibreSslDes.cpp @@ -0,0 +1,75 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using DesBlock = std::array; +using DesSchedule = std::array; + +static void Require(bool value) { if (!value) std::abort(); } + +template +static void RequireThrows(TAction action) { + try { action(); } catch (const std::invalid_argument&) { return; } + std::abort(); +} + +static DesBlock Block(std::uint64_t value) { + DesBlock block{}; + for (int index = 0; index < 8; ++index) block[index] = static_cast(value >> (56 - 8 * index)); + return block; +} + +static void CheckVector(std::uint64_t key, std::uint64_t plain, std::uint64_t cipher) { + const auto keyBlock = Block(key); + DesSchedule schedule{}; + schedule.fill(0xaa); + Require(DES_set_key_nid_postfix(keyBlock.data(), schedule.data()) == 0); + const auto plainBlock = Block(plain); + DesBlock output{}; + DES_ecb_encrypt_nid_postfix(plainBlock.data(), output.data(), schedule.data(), 1); + + Require(output == Block(cipher)); + DES_ecb_encrypt_nid_postfix(output.data(), output.data(), schedule.data(), 0); + + Require(output == plainBlock); + DesBlock again{}; + DES_ecb_encrypt_nid_postfix(plainBlock.data(), again.data(), schedule.data(), 7); + + Require(again == Block(cipher)); +} + +static void CheckParity() { + DesBlock key{0x00, 0x01, 0x02, 0x03, 0x12, 0x13, 0xfe, 0xff}; + DES_set_odd_parity_nid_postfix(key.data()); + + Require(key == (DesBlock{0x01, 0x01, 0x02, 0x02, 0x13, 0x13, 0xfe, 0xfe})); + for (int value = 0; value < 256; ++value) { + DesBlock single{}; + single.fill(static_cast(value)); + DES_set_odd_parity_nid_postfix(single.data()); + + Require((single[0] & 0xfe) == (value & 0xfe) && std::popcount(single[0]) % 2 == 1); + } +} + +int main() { + CheckVector(0x133457799bbcdff1, 0x0123456789abcdef, 0x85e813540f0ab405); + CheckVector(0x0e329232ea6d0d73, 0x8787878787878787, 0x0000000000000000); + CheckVector(0x0101010101010101, 0x8000000000000000, 0x95f8a5e5dd31d900); + CheckParity(); + DesSchedule schedule{}; + const auto weak = Block(0x0101010101010101); + Require(DES_set_key_nid_postfix(weak.data(), schedule.data()) == 0); + const auto badParity = Block(0x0000000000000000); + Require(DES_set_key_nid_postfix(badParity.data(), schedule.data()) == 0); + RequireThrows([&] { DES_set_key_nid_postfix(nullptr, schedule.data()); }); + RequireThrows([&] { DES_ecb_encrypt_nid_postfix(weak.data(), schedule.data(), nullptr, 1); }); + RequireThrows([] { DES_set_odd_parity_nid_postfix(nullptr); }); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslDigest.cpp b/core/libs/tests/GuestLibreSslDigest.cpp new file mode 100644 index 0000000000..e207d3ec6c --- /dev/null +++ b/core/libs/tests/GuestLibreSslDigest.cpp @@ -0,0 +1,187 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using InitFunction = int (APS5_VABI*)(void*); +using UpdateFunction = int (APS5_VABI*)(void*, const void*, std::size_t); +using FinalFunction = int (APS5_VABI*)(unsigned char*, void*); + +template +int APS5_VABI Initialize(void* context) { return TFunction(static_cast(context)); } + +template +int APS5_VABI Update(void* context, const void* data, std::size_t size) { return TFunction(static_cast(context), data, size); } + +template +int APS5_VABI Finalize(unsigned char* digest, void* context) { return TFunction(digest, static_cast(context)); } + +struct Algorithm { + const char* name; + InitFunction init; + UpdateFunction update; + FinalFunction final; + std::size_t contextSize; + std::size_t blockSize; + std::size_t digestSize; + const char* expected[6]; +}; + +static void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", what); + std::abort(); +} + +template static void RequireThrows(TAction action, const char* what) { + try { action(); } catch (const std::exception&) { return; } + Require(false, what); +} + +static std::string Hex(const unsigned char* data, std::size_t size) { + static constexpr char digits[] = "0123456789abcdef"; + std::string text; + for (std::size_t i = 0; i < size; ++i) { + text += digits[data[i] >> 4]; + text += digits[data[i] & 15]; + } + return text; +} + +static std::string Digest(const Algorithm& algorithm, const std::string& message, std::size_t chunk) { + alignas(16) unsigned char context[256] {}; + unsigned char digest[64] {}; + Require(algorithm.init(context) == 1, "init returns 1"); + for (std::size_t offset = 0; offset < message.size(); offset += chunk) { + const auto length = std::min(chunk, message.size() - offset); + Require(algorithm.update(context, message.data() + offset, length) == 1, "update returns 1"); + } + Require(algorithm.final(digest, context) == 1, "final returns 1"); + return Hex(digest, algorithm.digestSize); +} + +int main() { + const std::string messages[6] { + "", + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + std::string(1000000, 'a'), + }; + const Algorithm algorithms[] { + {"MD4", Initialize, Update, Finalize, 92, 64, 16, { + "31d6cfe0d16ae931b73c59d7e0c089c0", "a448017aaf21d8525fc10ae87aa6729d", "4691a9ec81b1a6bd1ab8557240b245c5", + nullptr, "e33b4ddc9c38f2199c3e7b164fcc0536", nullptr}}, + {"MD5", Initialize, Update, Finalize, 92, 64, 16, { + "d41d8cd98f00b204e9800998ecf8427e", "900150983cd24fb0d6963f7d28e17f72", "8215ef0796a20bcaaae116d3876c664a", + "03dd8807a93175fb062dfb55dc7d359c", "57edf4a22be3c955ac49da2e2107b67a", "7707d6ae4e027c70eea2a935c2296f21"}}, + {"SHA1", Initialize, Update, Finalize, 96, 64, 20, { + "da39a3ee5e6b4b0d3255bfef95601890afd80709", "a9993e364706816aba3e25717850c26c9cd0d89d", "84983e441c3bd26ebaae4aa1f95129e5e54670f1", + "a49b2446a02c645bf419f995b67091253a04a259", "50abf5706a150990a08b2c5ea40fa0e585554732", "34aa973cd4c4daa4f61eeb2bdbad27316534016f"}}, + {"SHA224", Initialize, Update, Finalize, 112, 64, 28, { + "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", + "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525", "c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3", + "b50aecbe4e9bb0b57bc5f3ae760a8e01db24f203fb3cdcd13148046e", "20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67"}}, + {"SHA256", Initialize, Update, Finalize, 112, 64, 32, { + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1", + "f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e", "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"}}, + {"SHA384", Initialize, Update, Finalize, 216, 128, 48, { + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7", + "3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b", + "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039", + "b12932b0627d1c060942f5447764155655bd4da0c9afa6dd9b9ef53129af1b8fb0195996d2de9ca0df9d821ffee67026", + "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985"}}, + {"SHA512", Initialize, Update, Finalize, 216, 128, 64, { + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", + "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", + "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445", + "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909", + "72ec1ef1124a45b047e8b7c75a932195135bb61de24ec0d1914042246e0aec3a2354e093d76f3048b456764346900cb130d2a4fd5dd16abb5e30bcb850dee843", + "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b"}}, + }; + + for (const auto& algorithm : algorithms) { + for (int i = 0; i < 6; ++i) { + if (!algorithm.expected[i]) continue; + const auto& message = messages[i]; + const std::size_t chunks[] {message.size() ? message.size() : 1, 1, algorithm.blockSize - 1, algorithm.blockSize, algorithm.blockSize + 1, 1000}; + for (auto chunk : chunks) { + if (i == 5 && chunk < 64) continue; + Require(Digest(algorithm, message, chunk) == algorithm.expected[i], algorithm.name); + } + } + + alignas(16) unsigned char context[256] {}; + alignas(16) unsigned char copy[256] {}; + unsigned char digest[64] {}; + unsigned char copyDigest[64] {}; + const bool wide = algorithm.blockSize == 128; + const auto num = [&](const unsigned char* c) { + std::uint32_t value; + std::memcpy(&value, c + (wide ? 208 : (algorithm.contextSize == 112 ? 104 : algorithm.contextSize - 4)), 4); + return value; + }; + const auto bitsLow = [&](const unsigned char* c) { + const std::size_t offset = wide ? 64 : (algorithm.contextSize == 96 ? 20 : algorithm.contextSize == 112 ? 32 : 16); + std::uint64_t value = 0; + std::memcpy(&value, c + offset, wide ? 8 : 4); + return value; + }; + Require(algorithm.init(context) == 1, "init"); + Require(algorithm.update(context, nullptr, 0) == 1, "empty update with null data"); + Require(algorithm.update(context, "ab", 2) == 1, "partial update"); + Require(num(context) == 2 && bitsLow(context) == 16, "context records the buffered bytes and the bit count"); + std::memcpy(copy, context, sizeof(copy)); + Require(algorithm.update(context, "c", 1) == 1 && algorithm.update(copy, "c", 1) == 1, "update of a copied context"); + Require(algorithm.final(digest, context) == 1 && algorithm.final(copyDigest, copy) == 1, "final of a copied context"); + Require(Hex(digest, algorithm.digestSize) == algorithm.expected[1] && !std::memcmp(digest, copyDigest, sizeof(digest)), "copied context digest"); + if (!wide) Require(num(context) == 0, "final clears the buffered count"); + RequireThrows([&] { algorithm.init(nullptr); }, "null context"); + RequireThrows([&] { algorithm.update(context, nullptr, 1); }, "null data"); + if (!wide) RequireThrows([&] { algorithm.final(nullptr, context); }, "null digest"); + } + + alignas(16) unsigned char context[256] {}; + unsigned char digest[64] {}; + Require(SHA512_Init_nid_postfix(reinterpret_cast(context)) == 1 && SHA512_Final_nid_postfix(nullptr, reinterpret_cast(context)) == 0, "SHA512_Final without a digest buffer"); + SHA256_Init_nid_postfix(reinterpret_cast(context)); + + std::uint32_t length = 33; + std::memcpy(context + 108, &length, 4); + Require(SHA256_Final_nid_postfix(digest, reinterpret_cast(context)) == 0, "SHA256_Final with an oversized digest length"); + SHA256_Init_nid_postfix(reinterpret_cast(context)); + + length = 16; + std::memcpy(context + 108, &length, 4); + std::memset(digest, 0xee, sizeof(digest)); + Require(SHA256_Final_nid_postfix(digest, reinterpret_cast(context)) == 1 && Hex(digest, 16) == "e3b0c44298fc1c149afbf4c8996fb924" && digest[16] == 0xee, "SHA256_Final truncates to the digest length"); + for (std::uint32_t truncated : {28u, 32u}) { + SHA512_Init_nid_postfix(reinterpret_cast(context)); + std::memcpy(context + 212, &truncated, 4); + std::memset(digest, 0xee, sizeof(digest)); + Require(SHA512_Final_nid_postfix(digest, reinterpret_cast(context)) == 0 && digest[0] == 0xee && digest[63] == 0xee, "SHA512_Final with a SHA-512/t digest length"); + } + SHA256_Init_nid_postfix(reinterpret_cast(context)); + + length = 64; + std::memcpy(context + 104, &length, 4); + RequireThrows([&] { SHA256_Update_nid_postfix(reinterpret_cast(context), "a", 1); }, "corrupt buffered count"); + + const auto* abc = reinterpret_cast("abc"); + Require(SHA256_nid_postfix(abc, 3, digest) == digest && Hex(digest, 32) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "SHA256 one-shot"); + Require(SHA256_nid_postfix(nullptr, 0, digest) == digest && Hex(digest, 32) == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "SHA256 one-shot of nothing"); + unsigned char* shared = SHA256_nid_postfix(abc, 3, nullptr); + Require(shared && shared != digest && Hex(shared, 32) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "SHA256 one-shot into the static buffer"); + Require(SHA256_nid_postfix(nullptr, 0, nullptr) == shared && Hex(shared, 32) == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "SHA256 one-shot reuses the static buffer"); + std::puts("libSceLibreSsl digest tests passed"); +} diff --git a/core/libs/tests/GuestLibreSslError.cpp b/core/libs/tests/GuestLibreSslError.cpp new file mode 100644 index 0000000000..f81ec64d6b --- /dev/null +++ b/core/libs/tests/GuestLibreSslError.cpp @@ -0,0 +1,69 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libSceLibreSsl/include/Error.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +static void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", what); + std::abort(); +} + +static std::uint64_t Pack(std::uint64_t library, std::uint64_t function, std::uint64_t reason) { + return library << 24 | function << 12 | reason; +} + +int main() { + Require(ERR_get_error_nid_postfix() == 0, "empty queue get"); + Require(ERR_peek_error_nid_postfix() == 0, "empty queue peek"); + + LibreSslPutError_nid_no_patch(3, 100, 65); + LibreSslPutError_nid_no_patch(4, 101, 66); + LibreSslPutError_nid_no_patch(20, 102, 67); + Require(ERR_peek_error_nid_postfix() == Pack(3, 100, 65), "peek returns the earliest error"); + Require(ERR_peek_error_nid_postfix() == Pack(3, 100, 65), "peek keeps the earliest error"); + Require(ERR_get_error_nid_postfix() == Pack(3, 100, 65), "get returns the earliest error"); + Require(ERR_get_error_nid_postfix() == Pack(4, 101, 66), "get returns the second error"); + Require(ERR_peek_error_nid_postfix() == Pack(20, 102, 67), "peek follows get"); + Require(ERR_get_error_nid_postfix() == Pack(20, 102, 67), "get returns the third error"); + Require(ERR_get_error_nid_postfix() == 0, "drained queue get"); + Require(ERR_peek_error_nid_postfix() == 0, "drained queue peek"); + + for (int i = 1; i <= 16; ++i) LibreSslPutError_nid_no_patch(i, 0, i); + for (int i = 2; i <= 16; ++i) Require(ERR_get_error_nid_postfix() == Pack(i, 0, i), "full queue drops the oldest error"); + Require(ERR_get_error_nid_postfix() == 0, "full queue holds 15 errors"); + + LibreSslPutError_nid_no_patch(0x1ff, 0x1fff, 0x1fff); + Require(ERR_get_error_nid_postfix() == 0xFFFFFFFFu, "fields are masked and the code keeps 32 bits"); + + LibreSslPutError_nid_no_patch(5, 1, 2); + LibreSslPutError_nid_no_patch(6, 1, 2); + ERR_clear_error_nid_postfix(); + Require(ERR_peek_error_nid_postfix() == 0, "clear empties the queue"); + LibreSslPutError_nid_no_patch(7, 1, 2); + Require(ERR_get_error_nid_postfix() == Pack(7, 1, 2), "queue works after clear"); + + LibreSslPutError_nid_no_patch(8, 1, 2); + bool otherEmpty = false; + bool otherOwn = false; + std::thread other([&] { + otherEmpty = ERR_peek_error_nid_postfix() == 0; + LibreSslPutError_nid_no_patch(9, 1, 2); + otherOwn = ERR_get_error_nid_postfix() == Pack(9, 1, 2); + }); + other.join(); + Require(otherEmpty, "another thread starts with an empty queue"); + Require(otherOwn, "another thread reads its own error"); + Require(ERR_get_error_nid_postfix() == Pack(8, 1, 2), "this thread keeps its error"); + Require(ERR_get_error_nid_postfix() == 0, "the other thread's error stays on that thread"); + + LibreSslPutError_nid_no_patch(10, 1, 2); + ERR_remove_thread_state_nid_postfix(nullptr); + Require(ERR_peek_error_nid_postfix() == 0, "removing the thread state empties the queue"); + + std::puts("guest_libressl_error: ok"); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslOcsp.cpp b/core/libs/tests/GuestLibreSslOcsp.cpp new file mode 100644 index 0000000000..f0c3f82ad8 --- /dev/null +++ b/core/libs/tests/GuestLibreSslOcsp.cpp @@ -0,0 +1,46 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +using StringFunction = const char* (APS5_VABI*)(std::int64_t); + +void Check(StringFunction function, const char* name, std::int64_t value, const char* expected) { + const char* actual = function(value); + if (actual == nullptr || std::strcmp(actual, expected) != 0) { + std::fprintf(stderr, "%s(%lld): expected %s, got %s\n", name, static_cast(value), expected, actual ? actual : "(null)"); + std::abort(); + } +} + +int main() { + constexpr auto Unknown = "(UNKNOWN)"; + constexpr std::int64_t Outside[] {-1, 4, 7, 9, 0x100, 0x100000000, std::numeric_limits::min(), std::numeric_limits::max()}; + + const char* responses[] {"successful", "malformedrequest", "internalerror", "trylater", nullptr, "sigrequired", "unauthorized"}; + for (std::int64_t code = 0; code < 7; ++code) { + Check(OCSP_response_status_str_nid_postfix, "OCSP_response_status_str", code, responses[code] ? responses[code] : Unknown); + } + + const char* certificates[] {"good", "revoked", "unknown"}; + for (std::int64_t code = 0; code < 3; ++code) { + Check(OCSP_cert_status_str_nid_postfix, "OCSP_cert_status_str", code, certificates[code]); + } + + const char* reasons[] {"unspecified", "keyCompromise", "cACompromise", "affiliationChanged", "superseded", "cessationOfOperation", "certificateHold", nullptr, "removeFromCRL"}; + for (std::int64_t code = 0; code < 9; ++code) { + Check(OCSP_crl_reason_str_nid_postfix, "OCSP_crl_reason_str", code, reasons[code] ? reasons[code] : Unknown); + } + + for (const auto value : Outside) { + Check(OCSP_response_status_str_nid_postfix, "OCSP_response_status_str", value, Unknown); + Check(OCSP_cert_status_str_nid_postfix, "OCSP_cert_status_str", value, Unknown); + if (value != 4) Check(OCSP_crl_reason_str_nid_postfix, "OCSP_crl_reason_str", value, Unknown); + } + Check(OCSP_response_status_str_nid_postfix, "OCSP_response_status_str", 0x100000000 | 1, Unknown); + Check(OCSP_crl_reason_str_nid_postfix, "OCSP_crl_reason_str", -0x100000000 | 8, Unknown); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslRuntime.cpp b/core/libs/tests/GuestLibreSslRuntime.cpp new file mode 100644 index 0000000000..7a67fb61ea --- /dev/null +++ b/core/libs/tests/GuestLibreSslRuntime.cpp @@ -0,0 +1,86 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#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 runtime 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); +} + +static void InitializeHeap() { + void* const api[10] {}; + ApplicationHeapRegister_nid_no_patch(api); +} + +int main() { + InitializeHeap(); + std::array first {}; + std::array second {}; + first.fill(0xa5); + Require(RAND_bytes_nid_postfix(first.data(), 0) == 1 && RAND_bytes_nid_postfix(first.data(), -5) == 1); + Require(RAND_bytes_nid_postfix(nullptr, 0) == 1); + Require(std::all_of(first.begin(), first.end(), [](unsigned char value) { return value == 0xa5; })); + Require(RAND_bytes_nid_postfix(first.data(), 3) == 1); + Require(std::all_of(first.begin() + 3, first.end(), [](unsigned char value) { return value == 0xa5; })); + Require(RAND_bytes_nid_postfix(first.data(), static_cast(first.size())) == 1); + Require(RAND_bytes_nid_postfix(second.data(), static_cast(second.size())) == 1); + Require(first != second); + std::array counts {}; + for (const auto value : first) ++counts[value]; + Require(*std::max_element(counts.begin(), counts.end()) < 64); + RequireThrows([] { RAND_bytes_nid_postfix(nullptr, 1); }); + + RAND_add_nid_postfix(first.data(), 16, 8.0); + RAND_add_nid_postfix(nullptr, 0, 0.0); + Require(RAND_status_nid_postfix() == 1); + Require(RAND_load_file_nid_postfix("/nonexistent", -1) == 123456); + Require(RAND_load_file_nid_postfix(nullptr, 1024) == 1024 && RAND_load_file_nid_postfix(nullptr, 0) == 0); + Require(RAND_load_file_nid_postfix(nullptr, 0x100000005) == 5); + + char name[32]; + std::memset(name, 'x', sizeof(name)); + Require(RAND_file_name_nid_postfix(name, sizeof(name)) == name && !std::strcmp(name, "/dev/urandom")); + std::memset(name, 'x', sizeof(name)); + Require(RAND_file_name_nid_postfix(name, 13) == name && !std::strcmp(name, "/dev/urandom")); + std::memset(name, 'x', sizeof(name)); + Require(RAND_file_name_nid_postfix(name, 12) == nullptr && !std::strcmp(name, "/dev/urando")); + std::memset(name, 'x', sizeof(name)); + Require(RAND_file_name_nid_postfix(name, 0) == nullptr && name[0] == 'x'); + Require(RAND_file_name_nid_postfix(nullptr, 0) == nullptr); + RequireThrows([] { RAND_file_name_nid_postfix(nullptr, 1); }); + + auto* block = static_cast(CRYPTO_malloc_nid_postfix(64, "file.c", 10)); + Require(block != nullptr); + std::memset(block, 0x5a, 64); + CRYPTO_free_nid_postfix(block); + + block = static_cast(CRYPTO_malloc_nid_postfix(32, nullptr, 0)); + Require(block != nullptr); + free_nid_postfix(block); + + block = static_cast(malloc_nid_postfix(16)); + CRYPTO_free_nid_postfix(block); + CRYPTO_free_nid_postfix(nullptr); + + Require(CRYPTO_malloc_nid_postfix(0, nullptr, 0) == nullptr); + Require(CRYPTO_malloc_nid_postfix(-1, nullptr, 0) == nullptr); + Require(CRYPTO_set_mem_functions_nid_postfix(reinterpret_cast(&std::malloc), reinterpret_cast(&std::realloc), reinterpret_cast(&std::free)) == 0); + Require(CRYPTO_set_mem_functions_nid_postfix(nullptr, nullptr, nullptr) == 0); + std::puts("libSceLibreSsl runtime tests passed"); +} diff --git a/core/libs/tests/GuestLibreSslX509Errors.cpp b/core/libs/tests/GuestLibreSslX509Errors.cpp new file mode 100644 index 0000000000..4b68ca300b --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509Errors.cpp @@ -0,0 +1,83 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +static constexpr std::uint64_t ExpectedHashes[] { + 0x8b05d07b5566befULL, 0, 0x708834e4234778b5ULL, 0xefb3526cbf8b4d8fULL, + 0xf9c246e135cdbad1ULL, 0xd42f63e9d35f4aa1ULL, 0xcccbec8974d6af08ULL, 0x8d443856acab19a4ULL, + 0x8d2b572b67e1760ULL, 0xa3b575a1199d9fd1ULL, 0xbed7043d1daf8f09ULL, 0x2a0a7832fb23e29dULL, + 0x265d14ebfd73f005ULL, 0x675282b2d8705cd0ULL, 0xade734fba45db83ULL, 0x2688e741036ec90fULL, + 0xf75897f162ad286eULL, 0x74e217b21ed87663ULL, 0xa33689775feaef0eULL, 0xa3359b0eaed11517ULL, + 0x6925fd54e311eb7cULL, 0xe6f75276f756e2e2ULL, 0xc9122d9145ee8c0fULL, 0xe88507a36fdaa6e6ULL, + 0xe4225a4c8853b86fULL, 0x5ef1c83b11d335a6ULL, 0x9b6dfad0758a0221ULL, 0xfe7f0ff3aadd086ULL, + 0x85a68d2a11031f82ULL, 0x72c1d177130ec956ULL, 0x44d8d6b5ed2f06edULL, 0x738e7dc7a61e12cdULL, + 0xc1fe96db71c85021ULL, 0x26559c0cfe2f2f78ULL, 0xc3bf2a493ebacd94ULL, 0xa4206ad9f25787ddULL, + 0x3f7344fff3aa908bULL, 0x23335a5daca94f1aULL, 0x1d9d2747a36c85e2ULL, 0xf07fa5bffdd43063ULL, + 0x1bad38ca7c7bc1e8ULL, 0x7715922cd76c9dceULL, 0x82fcbf1e3440e64eULL, 0x6d1f21d68462a5d4ULL, + 0x2e572ca0e246a7c9ULL, 0x5e23e31a525a543bULL, 0xa0bd09ebfc8cc89ULL, 0x85288e1dd50bdaceULL, + 0xf659605c19b0997eULL, 0xddad96c31c47ffdeULL, 0x2ccb58faf48aae6aULL, 0x9973a358685fe222ULL, + 0xe154c3e01bf7ba9bULL, 0x1e042e731311c0e8ULL, 0xa9af33b6e407c014ULL, +}; + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", message); + std::abort(); +} + +static std::uint64_t Hash(const char* text) { + Require(text != nullptr, "error string is non-null"); + std::uint64_t value = 14695981039346656037ULL; + for (; *text; ++text) value = (value ^ static_cast(*text)) * 1099511628211ULL; + return value; +} + +int main() { + constexpr std::int64_t offsets[] {0, INT64_C(0x100000000), -INT64_C(0x100000000), + std::numeric_limits::min(), INT64_C(0x7fffffff00000000)}; + for (std::int64_t code = 0; code < 55; ++code) { + if (code == 1) continue; + const char* stable = X509_verify_cert_error_string_nid_postfix(code); + for (const auto offset : offsets) { + const char* actual = X509_verify_cert_error_string_nid_postfix(code + offset); + Require(Hash(actual) == ExpectedHashes[code], "complete LibreSSL 2.5.4 message mapping"); + Require(actual == stable, "known messages retain static storage across 64-bit aliases"); + } + } + + for (std::int64_t code = -300; code <= 300; ++code) { + if (code == 0 || (code >= 2 && code <= 54)) continue; + const std::string expected = "error number " + std::to_string(code); + const char* actual = X509_verify_cert_error_string_nid_postfix(code); + Require(actual && expected == actual, "unknown and newer codes retain the numeric fallback"); + } + + struct WideCase { std::int64_t error; const char* expected; }; + constexpr WideCase wideCases[] { + {std::numeric_limits::max(), "error number 9223372036854775807"}, + {std::numeric_limits::min() + 1, "error number -9223372036854775807"}, + {INT64_C(4294967297), "error number 4294967297"}, + {-INT64_C(4294967295), "error number -4294967295"}, + {INT64_C(2147483648), "error number 2147483648"}, + {-INT64_C(2147483648), "error number -2147483648"}, + {INT64_C(4294967358), "error number 4294967358"}, + }; + for (const auto& item : wideCases) { + const char* actual = X509_verify_cert_error_string_nid_postfix(item.error); + Require(actual && std::strcmp(actual, item.expected) == 0, "fallback formats all 64 argument bits"); + } + + const char* shared = X509_verify_cert_error_string_nid_postfix(-123); + const char* known = X509_verify_cert_error_string_nid_postfix(10); + Require(std::strcmp(shared, "error number -123") == 0, "known lookup preserves unknown buffer"); + const char* next = X509_verify_cert_error_string_nid_postfix(62); + Require(shared == next && std::strcmp(shared, "error number 62") == 0, "unknown results share overwritten storage"); + Require(std::strcmp(known, "certificate has expired") == 0, "unknown formatting preserves known message"); + Require(std::strcmp(X509_verify_cert_error_string_nid_postfix(0), "ok") == 0, "success message"); + return 0; +} diff --git a/core/libs/tests/GuestLifecycle.cpp b/core/libs/tests/GuestLifecycle.cpp index 11cb61e1ec..ccdf3cc1df 100644 --- a/core/libs/tests/GuestLifecycle.cpp +++ b/core/libs/tests/GuestLifecycle.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -10,19 +11,6 @@ #include #endif -struct InitEnvParams { - int argc; - std::uint32_t pad; - const char* argv[3]; -}; - -extern "C" { -int APS5_VABI cxa_atexit_nid_postfix(void (APS5_VABI *)(void*), void*, void*); -void APS5_VABI cxa_finalize_nid_postfix(void*); -int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (APS5_VABI *)(void*), void*, void*); -void APS5_VABI init_env_nid_postfix(const InitEnvParams*); -} - namespace { void Require(bool value) { @@ -54,10 +42,13 @@ int main() { Require(cxa_atexit_nid_postfix(Second, nullptr, first) == 0); Require(cxa_atexit_nid_postfix(Other, nullptr, second) == 0); cxa_finalize_nid_postfix(first); + Require(secondAt == 0 && firstAt == 1 && otherAt == -1); cxa_finalize_nid_postfix(reinterpret_cast(3)); + Require(otherAt == -1); cxa_finalize_nid_postfix(second); + Require(otherAt == 2); std::thread worker([] { @@ -79,6 +70,7 @@ int main() { try { init_env_nid_postfix(nullptr); + } catch (const std::runtime_error& error) { Require(std::strstr(error.what(), "not implemented") == nullptr); return 0; diff --git a/core/libs/tests/GuestLoadExecHost.cpp b/core/libs/tests/GuestLoadExecHost.cpp index bba40568d0..618d8f5ebf 100644 --- a/core/libs/tests/GuestLoadExecHost.cpp +++ b/core/libs/tests/GuestLoadExecHost.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceSystemService/include/SystemService.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/General.hpp" @@ -17,11 +20,6 @@ #include #endif -extern "C" int APS5_VABI sceSystemServiceLoadExec(const char*, const char* const*); -extern "C" int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI*)(void*), void*, void*); -extern "C" int APS5_VABI getargc_nid_postfix(); -extern "C" const char** APS5_VABI getargv_nid_postfix(); - namespace { bool cleaned = false; diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 71b71a1669..047d28a1d3 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -1,6 +1,9 @@ +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/GuestLocale.hpp" #include #include +#include #include #include #include @@ -11,29 +14,15 @@ extern "C" { extern GuestLocale::Implementation* _ZSt21_sceLibcClassicLocale_nid_postfix; -GuestLocale::Implementation* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_postfix(); -GuestLocale::Implementation* APS5_VABI _ZNSt6locale5_InitEv_nid_postfix(); -struct GuestLocaleFacade { GuestLocale::Implementation* implementation; }; -GuestLocaleFacade* APS5_VABI _ZNSt6locale7classicEv_nid_postfix(); -void APS5_VABI _ZNSt6locale5facet9_RegisterEv_nid_postfix(GuestLocale::Facet* self); -void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(GuestLocale::LocinfoStorage* self, const char* localeName); -void APS5_VABI _ZNSt8_LocinfoD1Ev_nid_postfix(GuestLocale::LocinfoStorage* self); -void APS5_VABI _ZNSt8ios_baseD2Ev_nid_postfix(GuestLocale::IosBase* self); -struct StreamManipulator { - GuestLocale::IosBase* (APS5_VABI* function)(GuestLocale::IosBase*, std::int32_t); - std::int32_t argument; -}; -StreamManipulator APS5_VABI _ZSt4setwi_nid_postfix(std::int32_t width); -void APS5_VABI _ZNSt8ios_base7_AddstdEPS__nid_postfix(GuestLocale::IosBase* self); -const short* APS5_VABI _Getpctype_nid_postfix(); -const short* APS5_VABI _Getptolower_nid_postfix(); -const short* APS5_VABI _Getptoupper_nid_postfix(); -int APS5_VABI _Mbtowcx_nid_postfix(std::uint16_t* dst, const char* src, std::size_t count, std::mbstate_t* st); -int APS5_VABI _Wctombx_nid_postfix(char* dst, std::uint16_t src, std::mbstate_t* st); -void APS5_VABI _Locksyslock_nid_postfix(); -void APS5_VABI _Unlocksyslock_nid_postfix(); -std::uint32_t APS5_VABI _Towctrans_nid_postfix(std::uint32_t wc, std::uint64_t desc); -int* APS5_VABI __error_nid_postfix(); + +extern std::uint64_t _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix; +extern std::uint64_t _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix; +extern std::uint64_t _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix; +extern std::uintptr_t _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[17]; +extern std::uintptr_t _ZTVSt7codecvtIwc9_MbstatetE_nid_postfix[13]; +extern std::uintptr_t _ZTVSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[14]; +extern std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9]; +extern std::uintptr_t _ZTVSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[13]; } @@ -41,6 +30,17 @@ static void Require(bool condition) { if (!condition) throw std::runtime_error("Guest locale check failed"); } +void* APS5_VABI TestHeapAllocate(std::size_t bytes) { return std::malloc(bytes); } +void APS5_VABI TestHeapFree(void* pointer) { std::free(pointer); } +void* APS5_VABI TestHeapCalloc(std::size_t count, std::size_t bytes) { return std::calloc(count, bytes); } +void* APS5_VABI TestHeapReallocate(void* pointer, std::size_t bytes) { return std::realloc(pointer, bytes); } +void* APS5_VABI TestHeapAlign(std::size_t, std::size_t bytes) { return std::malloc(bytes); } +void* APS5_VABI TestHeapRealign(void* pointer, std::size_t bytes, std::size_t) { return std::realloc(pointer, bytes); } +int APS5_VABI TestHeapPosixAlign(void** pointer, std::size_t, std::size_t bytes) { + *pointer = std::malloc(bytes); + return *pointer == nullptr ? 12 : 0; +} + template static void RequireException(TAction action) { bool caught = false; @@ -52,6 +52,34 @@ static void RequireException(TAction action) { Require(caught); } +static void CheckInputFacetObjects() { + Require(_ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 0 && _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 0); + _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 7; + _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 9; + Require(_ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 7 && _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 9); + _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; + _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; + for (const auto slot : _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) Require(slot == 0); +} + +static void CheckCodecvtWideVtableObject() { + for (std::uintptr_t& slot : _ZTVSt7codecvtIwc9_MbstatetE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + +static void CheckCollateVtableObject() { + for (std::uintptr_t& slot : _ZTVSt7collateIcE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + static void CheckGuestCalls() { auto* locale = _ZSt21_sceLibcClassicLocale_nid_postfix; Require(locale != nullptr && locale == _ZNSt6locale16_GetgloballocaleEv_nid_postfix()); @@ -95,8 +123,10 @@ static void CheckLocinfoAlignment() { bytes.fill(std::byte{0x5a}); auto* storage = reinterpret_cast(bytes.data() + 8); _ZNSt8_LocinfoC1EPKc_nid_postfix(storage, "C"); + for (std::size_t index = 0; index < bytes.size(); ++index) Require(bytes[index] == (index >= 8 && index < 72 ? std::byte{} : std::byte{0x5a})); _ZNSt8_LocinfoD1Ev_nid_postfix(storage); + RequireException([&] { _ZNSt8_LocinfoC1EPKc_nid_postfix(storage, "unknown-locale"); }); RequireException([] { _ZNSt8_LocinfoC1EPKc_nid_postfix(nullptr, "C"); }); } @@ -108,7 +138,7 @@ static void CheckCharacterTables() { Require(classification[-1] == 0 && lower[-1] == -1 && upper[-1] == -1); Require(classification['A'] == 0x03 && classification['a'] == 0x11); Require(classification['G'] == 0x02 && classification['0'] == 0x21); - Require(classification[' '] == 0x04 && classification['\t'] == 0xc0); + Require(classification[' '] == 0x04 && classification['\t'] == 0x4c0); Require(classification['\n'] == 0xc0 && classification['!'] == 0x08); for (int value = 0; value < 256; ++value) { Require(lower[value] == (value >= 'A' && value <= 'Z' ? value + 32 : value)); @@ -151,6 +181,7 @@ static void CheckStreamDestruction() { } object; object.stream.locale = &_ZSt21_sceLibcClassicLocale_nid_postfix; _ZNSt8ios_baseD2Ev_nid_postfix(&object.stream); + Require(object.stream.locale == nullptr && object.before == 0x12345678 && object.after == 0x87654321); object.stream.locale = &_ZSt21_sceLibcClassicLocale_nid_postfix; object.stream.callbacks = &object; @@ -188,31 +219,42 @@ static void CheckStandardStreams() { RequireException([&] { _ZNSt8ios_base7_AddstdEPS__nid_postfix(nullptr); }); _ZNSt8ios_base7_AddstdEPS__nid_postfix(&streams[0].stream); _ZNSt8ios_base7_AddstdEPS__nid_postfix(&streams[1].stream); + Require(streams[0].stream.standardStream == 1 && streams[1].stream.standardStream == 2); _ZNSt8ios_base7_AddstdEPS__nid_postfix(&streams[0].stream); + Require(streams[0].stream.standardStream == 1); _ZNSt8ios_baseD2Ev_nid_postfix(&streams[0].stream); + Require(streams[0].stream.locale == classic && streams[0].stream.standardStream == 1); _ZNSt8ios_baseD2Ev_nid_postfix(&streams[0].stream); + Require(streams[0].stream.locale == nullptr && streams[0].stream.standardStream == 1); streams[0].stream.locale = classic; _ZNSt8ios_base7_AddstdEPS__nid_postfix(&streams[0].stream); + Require(streams[0].stream.standardStream == 1); _ZNSt8ios_baseD2Ev_nid_postfix(&streams[1].stream); + Require(streams[1].stream.locale == nullptr && streams[1].stream.standardStream == 2); for (int index = 2; index < 7; ++index) { _ZNSt8ios_base7_AddstdEPS__nid_postfix(&streams[index].stream); + Require(streams[index].stream.standardStream == index + 1); } _ZNSt8ios_base7_AddstdEPS__nid_postfix(&streams[7].stream); _ZNSt8ios_base7_AddstdEPS__nid_postfix(&streams[8].stream); + Require(streams[7].stream.standardStream == 8 && streams[8].stream.standardStream == 8); _ZNSt8ios_baseD2Ev_nid_postfix(&streams[7].stream); + Require(streams[7].stream.locale == classic); _ZNSt8ios_baseD2Ev_nid_postfix(&streams[8].stream); + Require(streams[8].stream.locale == nullptr); for (int index = 2; index < 7; ++index) { _ZNSt8ios_baseD2Ev_nid_postfix(&streams[index].stream); + Require(streams[index].stream.locale == nullptr && streams[index].stream.standardStream == index + 1); } for (auto& object : streams) Require(object.before == 0x12345678 && object.after == 0x87654321); @@ -240,7 +282,78 @@ static void CheckWideCaseTranslation() { } } +static void CheckWideNumPutVtableObject() { + for (std::uintptr_t& slot : _ZTVSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + +static void CheckFacetGetcat() { + const GuestLocale::Implementation* locale = _ZSt21_sceLibcClassicLocale_nid_postfix; + GuestLocale::Facet* codecvt = nullptr; + Require(_ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPKS2__nid_postfix(&codecvt, &locale) == 2 && codecvt != nullptr); + auto* codecvtVtable = reinterpret_cast(codecvt->vtable); + Require(codecvtVtable->alwaysNoconv(codecvt) && codecvtVtable->encoding(codecvt) == 1 && codecvtVtable->maxLength(codecvt) == 1); + const char* nextFrom = nullptr; + char target[4]{}; + char* nextTo = nullptr; + Require(codecvtVtable->out(codecvt, nullptr, "ab", "ab" + 2, &nextFrom, target, target + 4, &nextTo) == 3); + Require(nextFrom == "ab" && nextTo == target); + Require(codecvtVtable->in(codecvt, nullptr, "ab", "ab" + 2, &nextFrom, target, target + 4, &nextTo) == 3); + nextTo = reinterpret_cast(0x10); + Require(codecvtVtable->unshift(codecvt, nullptr, nullptr, nullptr, &nextTo) == 0 && nextTo == nullptr); + Require(codecvtVtable->length(codecvt, nullptr, "ab", "ab" + 2, 5) == 2); + Require(codecvtVtable->length(codecvt, nullptr, "ab", "ab" + 2, 1) == 1); + GuestLocale::Facet* punct = nullptr; + Require(_ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(&punct, &locale) == 4 && punct != nullptr); + auto* punctVtable = reinterpret_cast(punct->vtable); + auto* punctFacet = reinterpret_cast(punct); + Require(punctVtable->decimalPoint(punctFacet) == '.' && punctVtable->thousandsSep(punctFacet) == ','); + GuestLocale::String name{}; + Require(punctVtable->grouping(&name, punctFacet) == &name && name.size == 0); + const char* contents = name.size <= 15 ? name.buffer : name.pointer; + Require(punctVtable->falsename(&name, punctFacet)->size == 5 && std::memcmp(contents, "false", 5) == 0); + contents = name.size <= 15 ? name.buffer : name.pointer; + Require(punctVtable->truename(&name, punctFacet)->size == 4 && std::memcmp(contents, "true", 4) == 0); + GuestLocale::Facet* numPut = nullptr; + Require(_ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(&numPut, &locale) == 4 && numPut != nullptr); + GuestLocale::Facet* timePut = nullptr; + Require(_ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(&timePut, &locale) == 5 && timePut != nullptr); + _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 11; + Require(_ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 11); + _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; + for (GuestLocale::Facet* facet : {codecvt, punct, numPut, timePut}) { + facet->vtable->retain(facet); + facet->vtable->retain(facet); + Require(facet->references == 2); + facet->vtable->deleteObject(facet); + } + Require(_ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPKS2__nid_postfix(nullptr, &locale) == 2); + Require(_ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(&punct, &locale) == 4); +} + +static void CheckTimeGetVtableObject() { + for (std::uintptr_t& slot : _ZTVSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + int main() { + std::array heapApi{}; + heapApi[0] = reinterpret_cast(&TestHeapAllocate); + heapApi[1] = reinterpret_cast(&TestHeapFree); + heapApi[2] = reinterpret_cast(&TestHeapCalloc); + heapApi[3] = reinterpret_cast(&TestHeapReallocate); + heapApi[4] = reinterpret_cast(&TestHeapAlign); + heapApi[5] = reinterpret_cast(&TestHeapRealign); + heapApi[6] = reinterpret_cast(&TestHeapPosixAlign); + ApplicationHeapRegister_nid_no_patch(heapApi.data()); CheckGuestCalls(); CheckLocinfoAlignment(); CheckCharacterTables(); @@ -249,9 +362,16 @@ int main() { CheckStreamWidth(); CheckStandardStreams(); CheckWideCaseTranslation(); + CheckInputFacetObjects(); + CheckCodecvtWideVtableObject(); + CheckWideNumPutVtableObject(); + CheckFacetGetcat(); + CheckCollateVtableObject(); + CheckTimeGetVtableObject(); _Locksyslock_nid_postfix(); _Locksyslock_nid_postfix(); _Unlocksyslock_nid_postfix(); _Unlocksyslock_nid_postfix(); + std::cout << "Guest locale checks passed\n"; } diff --git a/core/libs/tests/GuestLoginDialog.cpp b/core/libs/tests/GuestLoginDialog.cpp index 0289d86fda..9b567297b0 100644 --- a/core/libs/tests/GuestLoginDialog.cpp +++ b/core/libs/tests/GuestLoginDialog.cpp @@ -1,19 +1,8 @@ +#include "prx/libSceLoginDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" -#include #include #include -#include -#include - -extern "C" { -int APS5_VABI sceLoginDialogInitialize(void); -int APS5_VABI sceLoginDialogOpen(const void* param); -int APS5_VABI sceLoginDialogUpdateStatus(void); -int APS5_VABI sceLoginDialogGetStatus(void); -int APS5_VABI sceLoginDialogGetResult(void* result); -int APS5_VABI sceLoginDialogClose(void); -int APS5_VABI sceLoginDialogTerminate(void); -} +#include "prx/libSceCommonDialog/CommonDialog.hpp" namespace { @@ -24,35 +13,24 @@ constexpr int kStatusInitialized = 1; constexpr int kStatusFinished = 3; constexpr int kErrNotInitialized = static_cast(0x81340001); constexpr int kErrAlreadyInitialized = static_cast(0x81340002); -constexpr int kErrParamInvalid = static_cast(0x81340003); -constexpr int kErrBusy = static_cast(0x81340005); -constexpr int kErrNoService = static_cast(0x81340007); +constexpr int kErrNotFinished = static_cast(0x81340005); +constexpr int kErrArgNull = static_cast(0x81340003); constexpr int kResultUserCanceled = 1; -void RequireRejected(const std::uint32_t (&valid)[16], std::size_t word, std::uint32_t value) { - std::uint32_t param[16]; - std::memcpy(param, valid, sizeof(param)); - param[word] = value; - Require(sceLoginDialogOpen(param) == kErrParamInvalid); -} - -void RequireResult(const std::int32_t (&result)[4]) { - Require(result[0] == kResultUserCanceled); - Require(result[1] == kErrNoService); - Require(result[2] == -1 && result[3] == -1); -} - } int main() { - const std::uint32_t param[16] = {0x40}; - std::int32_t result[4] = {-1, -1, -1, -1}; + LoginDialogParam param{}; + param.size = sizeof(param); + std::fill(std::begin(param.userIds), std::end(param.userIds), -1); + LoginDialogResult result{-1, -1, {11, 22, 33, 44, 55, 66, 77, 88}}; Require(sceLoginDialogTerminate() == kErrNotInitialized); - Require(sceLoginDialogOpen(param) == kErrNotInitialized); + Require(sceLoginDialogOpen(nullptr) == kErrArgNull); + Require(sceLoginDialogOpen(¶m) == kErrNotInitialized); Require(sceLoginDialogClose() == kErrNotInitialized); - Require(sceLoginDialogGetResult(result) == kErrNotInitialized); - Require(result[0] == -1 && result[1] == -1); + Require(sceLoginDialogGetResult(nullptr) == kErrArgNull); + Require(sceLoginDialogGetResult(&result) == kErrNotInitialized); Require(sceLoginDialogGetStatus() == kStatusNone); Require(sceLoginDialogUpdateStatus() == kStatusNone); @@ -61,22 +39,38 @@ int main() { Require(sceLoginDialogGetStatus() == kStatusInitialized); Require(sceLoginDialogUpdateStatus() == kStatusInitialized); - Require(sceLoginDialogOpen(nullptr) == kErrParamInvalid); - for (std::uint32_t size : {0u, 0x3Cu, 0x3Fu, 0x41u}) RequireRejected(param, 0, size); - for (std::uint32_t selector : {2u, 0xFFFFFFFFu}) RequireRejected(param, 1, selector); - for (std::size_t word = 0x2C / 4; word < 16; ++word) RequireRejected(param, word, 1); - Require(sceLoginDialogGetStatus() == kStatusInitialized); - Require(sceLoginDialogGetResult(result) == kErrBusy); - RequireResult(result); - std::memset(result, 0xFF, sizeof(result)); + Require(sceLoginDialogOpen(nullptr) == kErrArgNull); + Require(sceLoginDialogGetResult(nullptr) == kErrArgNull); + Require(sceLoginDialogGetResult(&result) == kErrNotFinished); + Require(result.result == 1 && result.errorCode == static_cast(0x81340007u)); - Require(sceLoginDialogOpen(param) == 0); + param.size = 63; + Require(sceLoginDialogOpen(¶m) == kErrArgNull); + param.size = sizeof(param); + param.mode = 2; + Require(sceLoginDialogOpen(¶m) == kErrArgNull); + param.mode = 1; + param.reserved[4] = 1; + Require(sceLoginDialogOpen(¶m) == kErrArgNull); + param.reserved[4] = 0; + for (auto& userId : param.userIds) { + for (const auto invalid : {0, 0x0fffffff, 0x40000000, -2}) { + userId = invalid; + Require(sceLoginDialogOpen(¶m) == static_cast(0x81340007u)); + } + userId = -1; + } + param.userIds[0] = 0x10000000; + param.userIds[8] = 0x3fffffff; + Require(sceLoginDialogOpen(¶m) == 0); Require(sceLoginDialogGetStatus() == kStatusFinished); Require(sceLoginDialogUpdateStatus() == kStatusFinished); - Require(sceLoginDialogGetResult(nullptr) == kErrParamInvalid); - Require(sceLoginDialogGetResult(result) == 0); - RequireResult(result); + Require(sceLoginDialogGetResult(nullptr) == kErrArgNull); + Require(sceLoginDialogGetResult(&result) == 0); + Require(result.result == kResultUserCanceled); + Require(result.errorCode == static_cast(0x81340007u)); + Require(result.data[0] == 11 && result.data[7] == 88); Require(sceLoginDialogClose() == 0); Require(sceLoginDialogTerminate() == 0); @@ -87,8 +81,8 @@ int main() { Require(sceLoginDialogGetStatus() == kStatusInitialized); Require(sceLoginDialogClose() == 0); Require(sceLoginDialogGetStatus() == kStatusFinished); - result[0] = -1; - Require(sceLoginDialogGetResult(result) == 0); - Require(result[0] == kResultUserCanceled); + result.result = -1; + Require(sceLoginDialogGetResult(&result) == 0); + Require(result.result == kResultUserCanceled); Require(sceLoginDialogTerminate() == 0); } diff --git a/core/libs/tests/GuestLoginService.cpp b/core/libs/tests/GuestLoginService.cpp index a1fa20d143..dfee6d6f6c 100644 --- a/core/libs/tests/GuestLoginService.cpp +++ b/core/libs/tests/GuestLoginService.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceLoginService/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,12 +6,6 @@ #include #include -extern "C" { -int APS5_VABI sceLoginServiceInitialize(void); -int APS5_VABI sceLoginServiceTerminate(void); -int APS5_VABI sceLoginServiceRequestDevices(void* param); -} - static void Require(bool value) { if (!value) std::abort(); } @@ -20,7 +15,8 @@ int main() { Require(sceLoginServiceTerminate() == 0); unsigned char buffer[0x80]{}; try { - sceLoginServiceRequestDevices(buffer); + sceLoginServiceRequestDevices(reinterpret_cast(buffer)); + } catch (const std::runtime_error& error) { if (std::string(error.what()) == "sceLoginServiceRequestDevices not implemented") return 0; std::fprintf(stderr, "sceLoginServiceRequestDevices threw: %s\n", error.what()); diff --git a/core/libs/tests/GuestLowAddressMemory.cpp b/core/libs/tests/GuestLowAddressMemory.cpp new file mode 100644 index 0000000000..3af824dcac --- /dev/null +++ b/core/libs/tests/GuestLowAddressMemory.cpp @@ -0,0 +1,60 @@ +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include +#include +#include +#include +#include + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "Low guest memory check failed at line %u\n", static_cast(location.line())); + std::abort(); + } +} + +int main() { + constexpr std::size_t page = 0x4000; + constexpr std::size_t bufferBytes = 64u << 20; + constexpr std::size_t stackBytes = 128u << 20; + constexpr std::uintptr_t lowLimit = 0x80000000; + const auto failed = reinterpret_cast(~std::uintptr_t{0}); + auto* buffer = static_cast(mmap_nid_postfix(reinterpret_cast(0x20000000), bufferBytes, 3, 0x1002, -1, 0)); + auto* stack = static_cast(mmap_nid_postfix(reinterpret_cast(0x40000000), stackBytes, 3, 0x1002, -1, 0)); + std::fprintf(stderr, "Low guest memory: buffer=%p stack=%p\n", static_cast(buffer), static_cast(stack)); + Require(buffer != failed && stack != failed); + const auto bufferAddress = reinterpret_cast(buffer); + const auto stackAddress = reinterpret_cast(stack); + Require(bufferAddress >= 0x20000000 && bufferAddress <= lowLimit - bufferBytes); + Require(stackAddress >= 0x40000000 && stackAddress <= lowLimit - stackBytes); + Require(GuestArena::GuestArenaContains_nid_postfix(buffer, bufferBytes)); + Require(GuestArena::GuestArenaContains_nid_postfix(stack, stackBytes)); + buffer[0] = 0x31; + buffer[bufferBytes - 1] = 0x32; + stack[0] = 0x41; + stack[stackBytes - 1] = 0x42; + + void* collision = mmap_nid_postfix(buffer, page, 3, 0x1002, -1, 0); + Require(collision != failed && collision != buffer); + Require(buffer[0] == 0x31 && buffer[bufferBytes - 1] == 0x32); + Require(munmap_nid_postfix(collision, page) == 0); + Require(mmap_nid_postfix(buffer, page, 3, 0x5012, -1, 0) == failed && buffer[0] == 0x31); + Require(mmap_nid_postfix(buffer, page, 3, 0x1012, -1, 0) == buffer); + Require(buffer[0] == 0 && buffer[bufferBytes - 1] == 0x32); + + Require(mprotect_nid_postfix(buffer, page, 1) == 0); + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(buffer, 0, &info, sizeof(info)) == 0 && info.protection == 1); + Require(mprotect_nid_postfix(buffer, page, 3) == 0); + buffer[0] = 0x51; + + void* ordinary = mmap_nid_postfix(nullptr, page, 3, 0x1002, -1, 0); + Require(ordinary != failed && reinterpret_cast(ordinary) >= 0x200000000ull); + Require(munmap_nid_postfix(ordinary, page) == 0); + Require(munmap_nid_postfix(buffer, bufferBytes) == 0); + Require(munmap_nid_postfix(stack, stackBytes) == 0); + Require(mmap_nid_postfix(buffer, page, 3, 0x1002, -1, 0) == buffer); + Require(buffer[0] == 0); + Require(munmap_nid_postfix(buffer, page) == 0); +} diff --git a/core/libs/tests/GuestM4aacEnc.cpp b/core/libs/tests/GuestM4aacEnc.cpp index d9f6f00888..0db4db35fa 100644 --- a/core/libs/tests/GuestM4aacEnc.cpp +++ b/core/libs/tests/GuestM4aacEnc.cpp @@ -1,28 +1,9 @@ +#include "prx/libSceM4aacEnc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -int32_t APS5_VABI sceM4aacEncClearContext(int32_t m4aacEncHandle); -int APS5_VABI sceM4aacEncClearContextEx(); -int32_t APS5_VABI sceM4aacEncCreateEncoder(int32_t channels, int32_t samplingRate, int32_t bitRate, int32_t inputFormat, int32_t outputFormat, int32_t* pInternalError); -int APS5_VABI sceM4aacEncCreateEncoderEx(); -int32_t APS5_VABI sceM4aacEncDeleteEncoder(int32_t m4aacEncHandle, int32_t* pInternalError); -int APS5_VABI sceM4aacEncDeleteEncoderEx(); -int32_t APS5_VABI sceM4aacEncEncode(int32_t m4aacEncHandle, void* pInput, uint32_t inputByte, void* pOutput, uint32_t* pOutputByte, int32_t* pInternalError); -int APS5_VABI sceM4aacEncEncodeEx(); -int APS5_VABI sceM4aacEncEncodeWithPriority(); -int APS5_VABI sceM4aacEncEncodeWithPriorityEx(); -int32_t APS5_VABI sceM4aacEncFlush(int32_t m4aacEncHandle, void* pOutput, uint32_t* pOutputByte, int32_t* pInternalError); -int APS5_VABI sceM4aacEncFlushEx(); -int APS5_VABI sceM4aacEncFlushWithPriority(); -int APS5_VABI sceM4aacEncFlushWithPriorityEx(); -int APS5_VABI sceM4aacEncGetContext(); -int APS5_VABI sceM4aacEncInitialize(); -int APS5_VABI sceM4aacEncTerminate(); -} - namespace { void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index d7a52ec814..c372dae29d 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -13,50 +14,10 @@ #include #include #include -struct GuestFloatingPointEnvironment { - std::uint32_t control; - std::uint32_t status; - std::uint32_t tag; - unsigned char other[16]; - std::uint32_t mxcsr; -}; + static_assert(sizeof(GuestFloatingPointEnvironment) == 32); extern "C" { -int APS5_VABI fegetenv_nid_postfix(GuestFloatingPointEnvironment*); -double APS5_VABI atof_nid_postfix(const char*); -double APS5_VABI strtod_nid_postfix(const char*, char**); -double APS5_VABI wcstod_nid_postfix(const char16_t*, char16_t**); -float APS5_VABI wcstof_nid_postfix(const char16_t*, char16_t**); -long double APS5_VABI wcstold_nid_postfix(const char16_t*, char16_t**); -float APS5_VABI strtof_nid_postfix(const char*, char**); -long double APS5_VABI strtold_nid_postfix(const char*, char**); -std::int64_t APS5_VABI strtol_nid_postfix(const char*, char**, int); -std::uint64_t APS5_VABI strtoul_nid_postfix(const char*, char**, int); -std::intmax_t APS5_VABI strtoimax_nid_postfix(const char*, char**, int); -long long APS5_VABI strtoll_nid_postfix(const char*, char**, int); -unsigned long long APS5_VABI strtoull_nid_postfix(const char*, char**, int); -std::uintmax_t APS5_VABI strtoumax_nid_postfix(const char*, char**, int); -unsigned long long APS5_VABI _Stoull_nid_postfix(const char*, char**, int); -std::uint64_t APS5_VABI _Stoul_nid_postfix(const char*, char**, int); -int APS5_VABI atoi_nid_postfix(const char*); -std::int64_t APS5_VABI atol_nid_postfix(const char*); -long long APS5_VABI atoll_nid_postfix(const char*); -int* APS5_VABI __error_nid_postfix(); -double APS5_VABI sqrt_nid_postfix(double); -float APS5_VABI sqrtf_nid_postfix(float); -double APS5_VABI fabs_nid_postfix(double); -int APS5_VABI abs_nid_postfix(int); -double APS5_VABI trunc_nid_postfix(double); -double APS5_VABI ceil_nid_postfix(double); -double APS5_VABI floor_nid_postfix(double); -float APS5_VABI truncf_nid_postfix(float); -float APS5_VABI ceilf_nid_postfix(float); -float APS5_VABI floorf_nid_postfix(float); -double APS5_VABI nearbyint_nid_postfix(double); -double APS5_VABI fma_nid_postfix(double, double, double); -long double APS5_VABI acosl_nid_postfix(long double); -long double APS5_VABI asinl_nid_postfix(long double); -long double APS5_VABI frexpl_nid_postfix(long double, int*); + struct LibcFloatConstant { std::uint32_t bits[4]; }; extern LibcFloatConstant _FInf_nid_postfix; extern LibcFloatConstant _FNan_nid_postfix; @@ -72,136 +33,9 @@ extern SonyDconst _LDenorm_nid_postfix; extern SonyDconst _LInf_nid_postfix; extern SonyDconst _LNan_nid_postfix; extern SonyDconst _LSnan_nid_postfix; -short APS5_VABI _FDtest_nid_postfix(const float*); -short APS5_VABI _Dtest_nid_postfix(const double*); -int APS5_VABI __fpclassifyf_nid_postfix(float); -int APS5_VABI isnanf_nid_postfix(float); -int APS5_VABI __signbitl_nid_postfix(long double); -float APS5_VABI fmodf_nid_postfix(float, float); -double APS5_VABI fmod_nid_postfix(double, double); -float APS5_VABI asinf_nid_postfix(float); -float APS5_VABI acosf_nid_postfix(float); -double APS5_VABI asin_nid_postfix(double); -float APS5_VABI atan2f_nid_postfix(float, float); -float APS5_VABI hypotf_nid_postfix(float, float); -double APS5_VABI hypot_nid_postfix(double, double); -double APS5_VABI fdim_nid_postfix(double, double); -float APS5_VABI fdimf_nid_postfix(float, float); -float APS5_VABI fabsf_nid_postfix(float); -long double APS5_VABI fabsl_nid_postfix(long double); -double APS5_VABI copysign_nid_postfix(double, double); -float APS5_VABI copysignf_nid_postfix(float, float); -double APS5_VABI remquo_nid_postfix(double, double, int*); -float APS5_VABI remquof_nid_postfix(float, float, int*); -float APS5_VABI fmaf_nid_postfix(float, float, float); -long double APS5_VABI ceill_nid_postfix(long double); -long double APS5_VABI floorl_nid_postfix(long double); -float APS5_VABI tanf_nid_postfix(float); -int APS5_VABI rand_nid_postfix(); -void APS5_VABI srand_nid_postfix(unsigned int); -int APS5_VABI rand_r_nid_postfix(unsigned int*); -float APS5_VABI log10f_nid_postfix(float); -float APS5_VABI powf_nid_postfix(float, float); -double APS5_VABI log1p_nid_postfix(double); -float APS5_VABI log1pf_nid_postfix(float); -double APS5_VABI expm1_nid_postfix(double); -float APS5_VABI expm1f_nid_postfix(float); -float APS5_VABI erff_nid_postfix(float); -double APS5_VABI erf_nid_postfix(double); -double APS5_VABI erfc_nid_postfix(double); -float APS5_VABI erfcf_nid_postfix(float); -double APS5_VABI lgamma_nid_postfix(double); + extern int signgam_nid_postfix; -float APS5_VABI logbf_nid_postfix(float); -int APS5_VABI ilogb_nid_postfix(double); -int APS5_VABI ilogbf_nid_postfix(float); -double APS5_VABI exp2_nid_postfix(double); -float APS5_VABI __powisf2_nid_postfix(float, int); -double APS5_VABI __powidf2_nid_postfix(double, int); -double APS5_VABI ldexp_nid_postfix(double, int); -double APS5_VABI scalbn_nid_postfix(double, int); -float APS5_VABI scalbnf_nid_postfix(float, int); -float APS5_VABI nextafterf_nid_postfix(float, float); -double APS5_VABI nextafter_nid_postfix(double, double); -double APS5_VABI frexp_nid_postfix(double, int*); -float APS5_VABI frexpf_nid_postfix(float, int*); -std::int64_t APS5_VABI lround_nid_postfix(double); -std::div_t APS5_VABI div_nid_postfix(int, int); -double APS5_VABI sinh_nid_postfix(double); -double APS5_VABI cosh_nid_postfix(double); -float APS5_VABI coshf_nid_postfix(float); -float APS5_VABI sinhf_nid_postfix(float); -float APS5_VABI _FSinh_nid_postfix(float, float); -float APS5_VABI _FCosh_nid_postfix(float, float); -double APS5_VABI asinh_nid_postfix(double); -float APS5_VABI asinhf_nid_postfix(float); -double APS5_VABI acosh_nid_postfix(double); -float APS5_VABI acoshf_nid_postfix(float); -double APS5_VABI atanh_nid_postfix(double); -float APS5_VABI atanhf_nid_postfix(float); -std::int64_t APS5_VABI lroundf_nid_postfix(float); -std::int64_t APS5_VABI llround_nid_postfix(double); -std::int64_t APS5_VABI llroundf_nid_postfix(float); -std::int64_t APS5_VABI llroundl_nid_postfix(long double); -int APS5_VABI __isfinitef_nid_postfix(float); -int APS5_VABI __isfinitel_nid_postfix(long double); -int APS5_VABI __isnormal_nid_postfix(double); -int APS5_VABI __isnormalf_nid_postfix(float); -int APS5_VABI __isinff_nid_postfix(float); -int APS5_VABI __isinf_nid_postfix(double); -int APS5_VABI isnan_nid_postfix(double); -int APS5_VABI isinf_nid_postfix(double); -std::lldiv_t APS5_VABI lldiv_nid_postfix(long long, long long); -std::lldiv_t APS5_VABI imaxdiv_nid_postfix(std::int64_t, std::int64_t); -std::int64_t APS5_VABI labs_nid_postfix(std::int64_t); -std::int64_t APS5_VABI llabs_nid_postfix(std::int64_t); -std::int64_t APS5_VABI imaxabs_nid_postfix(std::int64_t); -long double APS5_VABI scalbnl_nid_postfix(long double, int); -float APS5_VABI significandf_nid_postfix(float); -double APS5_VABI significand_nid_postfix(double); -long double APS5_VABI acoshl_nid_postfix(long double); -long double APS5_VABI asinhl_nid_postfix(long double); -long double APS5_VABI atanhl_nid_postfix(long double); -long double APS5_VABI atan2l_nid_postfix(long double, long double); -long double APS5_VABI cbrtl_nid_postfix(long double); -long double APS5_VABI erfl_nid_postfix(long double); -long double APS5_VABI erfcl_nid_postfix(long double); -long double APS5_VABI expm1l_nid_postfix(long double); -long double APS5_VABI log1pl_nid_postfix(long double); -long double APS5_VABI log2l_nid_postfix(long double); -long double APS5_VABI lgammal_nid_postfix(long double); -long double APS5_VABI tgammal_nid_postfix(long double); -float APS5_VABI tgammaf_nid_postfix(float); -long double APS5_VABI hypotl_nid_postfix(long double, long double); -float APS5_VABI hypot3f_nid_postfix(float, float, float); -double APS5_VABI hypot3_nid_postfix(double, double, double); -long double APS5_VABI hypot3l_nid_postfix(long double, long double, long double); -float APS5_VABI j0f_nid_postfix(float); -float APS5_VABI j1f_nid_postfix(float); -float APS5_VABI jnf_nid_postfix(int, float); -float APS5_VABI y0f_nid_postfix(float); -float APS5_VABI y1f_nid_postfix(float); -float APS5_VABI ynf_nid_postfix(int, float); -std::lldiv_t APS5_VABI ldiv_nid_postfix(std::int64_t, std::int64_t); -float APS5_VABI nanf_nid_postfix(const char*); -double APS5_VABI nan_nid_postfix(const char*); -long double APS5_VABI atanl_nid_postfix(long double); -long double APS5_VABI sinl_nid_postfix(long double); -long double APS5_VABI cosl_nid_postfix(long double); -long double APS5_VABI tanl_nid_postfix(long double); -long double APS5_VABI sinhl_nid_postfix(long double); -long double APS5_VABI coshl_nid_postfix(long double); -long double APS5_VABI tanhl_nid_postfix(long double); -long double APS5_VABI expl_nid_postfix(long double); -long double APS5_VABI exp2l_nid_postfix(long double); -long double APS5_VABI logl_nid_postfix(long double); -long double APS5_VABI log10l_nid_postfix(long double); -long double APS5_VABI powl_nid_postfix(long double, long double); -long double APS5_VABI ldexpl_nid_postfix(long double, int); -long double APS5_VABI truncl_nid_postfix(long double); -long double APS5_VABI nearbyintl_nid_postfix(long double); -double APS5_VABI logb_nid_postfix(double); -double APS5_VABI remainder_nid_postfix(double, double); + struct DinkumwareFloatCharacteristics { int digits; int mantissaDigits; @@ -436,8 +270,9 @@ 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 != 0x7fc00000u || end != test.text + test.sequenceUsed) { + if (single != expectedSingle || end != test.text + test.sequenceUsed) { std::fprintf(stderr, "strtof(\"%s\") = 0x%08x\n", test.text, static_cast(single)); correct = false; } @@ -460,7 +295,7 @@ static void CheckNanConversions() { } if (test.wideOtherMeasured) { const auto wideSingle = std::bit_cast(wcstof_nid_postfix(wide, &wideEnd)); - const bool singleOk = wideSingle == 0x7fc00000u && wideEnd == wide + test.sequenceUsed; + 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); @@ -486,7 +321,6 @@ static void CheckFloatUnderflowErrno() { errno = 7; Require(strtod_nid_postfix("1e-310", &end) == 1e-310 && errno == 7); Require(strtod_nid_postfix("-4.9406564584124654e-324", &end) == -0x1p-1074 && errno == 7); - Require(strtof_nid_postfix("1e-40", &end) == 1e-40f && errno == 7); Require(strtold_nid_postfix("1e-4940", &end) == 1e-4940L && errno == 7); Require(strtod_nid_postfix("1e-330", &end) == 0.0 && errno == ERANGE); errno = 0; @@ -495,6 +329,49 @@ static void CheckFloatUnderflowErrno() { Require(strtod_nid_postfix("1e400", &end) == HUGE_VAL && errno == ERANGE); errno = 0; Require(strtod_nid_postfix("2.2250738585072014e-308", &end) == 0x1p-1022 && errno == 0); + Require(strtod_nid_postfix("0x1.8p-1075", &end) == 0x1p-1074 && errno == ERANGE); + errno = 0; + Require(strtod_nid_postfix("-0x1.000001p-1075", &end) == -0x1p-1074 && errno == ERANGE); + errno = 0; + Require(strtod_nid_postfix("0x1p-1074", &end) == 0x1p-1074 && strtod_nid_postfix("0x1.8p-1074", &end) == 0x1p-1073 && errno == 0); + Require(strtod_nid_postfix("0x1p-1076", &end) == 0.0 && errno == ERANGE); + errno = 0; + Require(strtod_nid_postfix("0x1p+1100", &end) == HUGE_VAL && errno == ERANGE); + errno = 7; + Require(strtold_nid_postfix("0x1p-16446", &end) == 0.0L && strtold_nid_postfix("0x1p+20000", &end) == HUGE_VALL && errno == 7); + Require(strtof_nid_postfix("0x1p-149", &end) == 0x1p-149f && strtof_nid_postfix("0x1.8p-149", &end) == 0x1p-148f && strtof_nid_postfix("0x1.000001p-149", &end) == 0x1p-149f && errno == 7); + Require(strtof_nid_postfix("1.40129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125e-45", &end) == 0x1p-149f && strtof_nid_postfix("1.17549435e-38", &end) == 0x1p-126f && errno == 7); + Require(strtof_nid_postfix("0x1p-150", &end) == 0.0f && errno == ERANGE); + errno = 7; + Require(strtof_nid_postfix("0x1.8p-150", &end) == 0x1p-149f && errno == ERANGE); + errno = 7; + Require(strtof_nid_postfix("-0x0.0000018p-126", &end) == -0x1p-149f && errno == ERANGE); + errno = 7; + Require(strtof_nid_postfix("0x1.4p-149", &end) == 0x1p-149f && strtof_nid_postfix("0x1.cp-149", &end) == 0x1p-148f && errno == 7); + errno = 7; + Require(strtof_nid_postfix("0x1p+200", &end) == HUGE_VALF && errno == ERANGE); + errno = 7; + Require(strtof_nid_postfix("1e-40", &end) == 1e-40f && errno == ERANGE); + errno = 7; + Require(strtof_nid_postfix("-1e-45", &end) == -0x1p-149f && errno == ERANGE); + errno = 7; + Require(strtof_nid_postfix("3e-45", &end) == 0x1p-148f && errno == ERANGE); + Require(strtof_nid_postfix("1e39", &end) == HUGE_VALF && errno == ERANGE); + errno = 0; +} + +static void CheckAtanfConsoleBits() { + constexpr std::uint32_t cases[][2] = { + {0x3e80000bu, 0x3e7adbc5u}, {0x3e0007c9u, 0x3dfebd2au}, {0xbdccce4cu, 0xbdcc2090u}, + {0x3ef00334u, 0x3ee07046u}, {0x3f40077eu, 0x3f24c148u}, {0x3fa009a5u, 0x3f656b6au}, + {0x4080055cu, 0x3fa9b5a8u}, {0xbf12345eu, 0xbf04d718u}, {0x3f800000u, 0x3f490fdbu}, + {0x4c800000u, 0x3fc90fdbu}, {0xcc800000u, 0xbfc90fdbu}, {0x7f800000u, 0x3fc90fdbu}, + {0xff800000u, 0xbfc90fdbu}, {0x7fc00001u, 0x7fc00001u}, {0x80000000u, 0x80000000u}, + {0x00000001u, 0x00000001u}, {0x39000000u, 0x39000000u}, + }; + for (const auto& [input, expected] : cases) { + Require(std::bit_cast(atanf_nid_postfix(std::bit_cast(input))) == expected); + } } static void CheckHypotSpecialValue() { @@ -523,6 +400,13 @@ static void CheckPowfSpecialValue() { Require(std::bit_cast(powf_nid_postfix(1.0f, std::bit_cast(0x7f800001u))) == 0x3f800000u); } +struct RestoreFloatingPointState { + std::fenv_t environment; + int error = errno; + RestoreFloatingPointState() { Require(std::fegetenv(&environment) == 0); } + ~RestoreFloatingPointState() { Require(std::fesetenv(&environment) == 0); errno = error; } +}; + static void CheckAcosfErrno() { errno = 0; Require(std::bit_cast(acosf_nid_postfix(2.0f)) == 0xffc00000u); @@ -533,6 +417,47 @@ static void CheckAcosfErrno() { Require(errno == 0); } +static void CheckFmodfErrno() { + const RestoreFloatingPointState saved; + const struct { std::uint32_t dividend; std::uint32_t divisor; std::uint32_t expected; bool invalid; bool nan; } cases[] = { + {0x3f800000u, 0x00000000u, 0u, true, true}, {0x3f800000u, 0x80000000u, 0u, true, true}, + {0x7f800000u, 0x40000000u, 0u, true, true}, {0x7fc00000u, 0x40000000u, 0u, false, true}, + {0x3f800000u, 0x7f800000u, 0x3f800000u, false, false}, {0xbf800000u, 0x7f800000u, 0xbf800000u, false, false}, + {0x41280000u, 0x40000000u, 0x3f000000u, false, false}, {0xc1280000u, 0x40000000u, 0xbf000000u, false, false}, + {0x41280000u, 0xc0000000u, 0x3f000000u, false, false}, {0xc1280000u, 0xc0000000u, 0xbf000000u, false, false}, + {0x00000000u, 0x40000000u, 0x00000000u, false, false}, {0x80000000u, 0x40000000u, 0x80000000u, false, false}, + {0x00000000u, 0x80000000u, 0u, true, true}, + {0x3f800000u, 0x7fc00001u, 0u, false, true}, + {0x00000000u, 0x7fc00001u, 0u, false, true}, + {0x7f800001u, 0x40000000u, 0u, true, true}, + {0x3f800000u, 0x7f800001u, 0u, true, true}, + {0x00000003u, 0x00000002u, 0x00000001u, false, false}, + {0x00800001u, 0x00800000u, 0x00000001u, false, false}, + {0x80000001u, 0x3f800000u, 0x80000001u, false, false}, + }; + for (const auto& test : cases) { + std::feclearexcept(FE_ALL_EXCEPT); + *__error_nid_postfix() = 73; + const float result = fmodf_nid_postfix(std::bit_cast(test.dividend), std::bit_cast(test.divisor)); + const std::uint32_t bits = std::bit_cast(result); + const int invalid = std::fetestexcept(FE_INVALID) != 0; + const bool nan = std::isnan(result); + if (nan != test.nan || (!nan && bits != test.expected) || invalid != test.invalid || *__error_nid_postfix() != 73) { + std::fprintf(stderr, "Guest fmodf failed for %08x and %08x: got %08x, invalid %d, errno %d\n", + test.dividend, test.divisor, bits, invalid, *__error_nid_postfix()); + std::abort(); + } + } + std::feclearexcept(FE_ALL_EXCEPT); + std::feraiseexcept(FE_DIVBYZERO); + *__error_nid_postfix() = 73; + (void)fmodf_nid_postfix(1.0f, 0.0f); + if (*__error_nid_postfix() != 73 || std::fetestexcept(FE_DIVBYZERO) == 0) { + std::fprintf(stderr, "Guest fmodf dropped the floating-point environment\n"); + std::abort(); + } +} + static void CheckLongDouble() { Require(ExtendedBits(acosl_nid_postfix(-1.0L), 0xc90fdaa22168c235ull, 0x4000)); Require(ExtendedBits(acosl_nid_postfix(0.0L), 0xc90fdaa22168c235ull, 0x3fff)); @@ -588,6 +513,164 @@ static long double MakeExtended(std::uint64_t mantissa, std::uint16_t signExpone return value; } +static void CheckExpm1ConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0}, {0x8000000000000000ull, 0x8000000000000000ull, 0}, + {0x3c8fffffffffffffull, 0x3c8fffffffffffffull, 0}, {0x3c90000000000000ull, 0x3c90000000000000ull, 0}, + {0x3fd62e42fefa39efull, 0x3fda827999fcef32ull, 0}, {0x3fd62e4300000000ull, 0x3fda82799b6f2366ull, 0}, + {0x3ff0a2b1ffffffffull, 0x3ffd413c1a24d411ull, 0}, {0x3ff0a2b200000000ull, 0x3ffd413c1a24d414ull, 0}, + {0xbfd0000000000000ull, 0xbfcc5041854df7d4ull, 0}, {0xbfe0000000000000ull, 0xbfd92e9a0720d3ecull, 0}, + {0x3ff0000000000000ull, 0x3ffb7e151628aed2ull, 0}, {0xbff0000000000000ull, 0xbfe43a54e4e98864ull, 0}, + {0x4043687a00000000ull, 0x436fffd8395c0c41ull, 0}, {0xc043687a00000000ull, 0xbff0000000000000ull, 0}, + {0x4049000000000000ull, 0x44719103e4080b45ull, 0}, {0x40862e42fefa39efull, 0x7fefffffffffff2aull, 0}, + {0x40862e42fefa39f0ull, 0x7ff0000000000000ull, ERANGE}, {0xc08f400000000000ull, 0xbff0000000000000ull, 0}, + {0x7fefffffffffffffull, 0x7ff0000000000000ull, ERANGE}, {0xffefffffffffffffull, 0xbff0000000000000ull, 0}, + {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, {0xfff0000000000000ull, 0xbff0000000000000ull, 0}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0xfff8000000000002ull, 0xfff8000000000002ull, 0}, + {0x3fc78b9e474afed8ull, 0x3fc9d99e60806b35ull, 0}, {0xbf887a99bea50fb1ull, 0xbf88554c5b9d0cbbull, 0}, + {0x3ff790fb0ae85390ull, 0x400ae5103402894bull, 0}, {0xbf840fe48e07e54dull, 0xbf83f6d1bcda4943ull, 0}, + {0xbfbb1276066a31c7ull, 0xbfb9b098289dab68ull, 0}, {0x3fc07bc20a0c887eull, 0x3fc197828317c86cull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(expm1_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + +static void CheckNextafterConsoleBits() { + constexpr struct { + std::uint64_t x; + std::uint64_t y; + std::uint64_t expected; + int expectedErrno; + } cases[] = { + {0x0000000000000000ull, 0x3ff0000000000000ull, 0x0000000000000001ull, 0}, + {0x8000000000000000ull, 0xbff0000000000000ull, 0x8000000000000001ull, 0}, + {0x0000000000000001ull, 0x0000000000000000ull, 0x0000000000000000ull, ERANGE}, + {0x8000000000000001ull, 0x3ff0000000000000ull, 0x8000000000000000ull, ERANGE}, + {0x0010000000000000ull, 0x0000000000000000ull, 0x000fffffffffffffull, ERANGE}, + {0x000fffffffffffffull, 0x3ff0000000000000ull, 0x0010000000000000ull, 0}, + {0x7fefffffffffffffull, 0x7ff0000000000000ull, 0x7ff0000000000000ull, ERANGE}, + {0xffefffffffffffffull, 0xfff0000000000000ull, 0xfff0000000000000ull, ERANGE}, + {0x7ff0000000000000ull, 0x0000000000000000ull, 0x7fefffffffffffffull, 0}, + {0x3ff0000000000000ull, 0x4000000000000000ull, 0x3ff0000000000001ull, 0}, + {0x3ff0000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull, 0}, + {0x8000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0}, + {0x7ff0000000000001ull, 0x3ff0000000000000ull, 0x7ff8000000000001ull, 0}, + {0x3ff0000000000000ull, 0xfff8000000000123ull, 0xfff8000000000123ull, 0}, + {0x7ff8000000000123ull, 0xfff8000000000456ull, 0x7ff8000000000123ull, 0}, + {0xfff8000000000456ull, 0x7ff0000000000001ull, 0xfff8000000000456ull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(nextafter_nid_postfix(std::bit_cast(item.x), std::bit_cast(item.y))) == item.expected); + Require(errno == (item.expectedErrno != 0 ? item.expectedErrno : 1234)); + } + constexpr struct { + std::uint32_t x; + std::uint32_t y; + std::uint32_t expected; + int expectedErrno; + } floatCases[] = { + {0x00000000u, 0x3f800000u, 0x00000001u, 0}, + {0x80000000u, 0xbf800000u, 0x80000001u, 0}, + {0x00000001u, 0x00000000u, 0x00000000u, ERANGE}, + {0x80000001u, 0x3f800000u, 0x80000000u, ERANGE}, + {0x00800000u, 0x00000000u, 0x007fffffu, ERANGE}, + {0x007fffffu, 0x3f800000u, 0x00800000u, 0}, + {0x7f7fffffu, 0x7f800000u, 0x7f800000u, ERANGE}, + {0xff7fffffu, 0xff800000u, 0xff800000u, ERANGE}, + {0x7f800000u, 0x00000000u, 0x7f7fffffu, 0}, + {0x3f800000u, 0x40000000u, 0x3f800001u, 0}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0}, + {0x80000000u, 0x00000000u, 0x00000000u, 0}, + {0x7f800001u, 0x3f800000u, 0x7fc00001u, 0}, + {0x3f800000u, 0xffc00123u, 0xffc00123u, 0}, + {0x7fc00123u, 0xffc00456u, 0x7fc00123u, 0}, + {0xffc00456u, 0x7f800001u, 0xffc00456u, 0}, + }; + for (const auto& item : floatCases) { + errno = 1234; + Require(std::bit_cast(nextafterf_nid_postfix(std::bit_cast(item.x), std::bit_cast(item.y))) == item.expected); + Require(errno == (item.expectedErrno != 0 ? item.expectedErrno : 1234)); + } +} + +static void CheckLog1pConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0}, {0x8000000000000000ull, 0x8000000000000000ull, 0}, + {0x3c8fffffffffffffull, 0x3c8fffffffffffffull, 0}, {0x3c90000000000000ull, 0x3c90000000000000ull, 0}, + {0x3e1fffffffffffffull, 0x3e1fffffff7fffffull, 0}, {0x3e20000000000000ull, 0x3e1fffffff800000ull, 0}, + {0xbe20000000000000ull, 0xbe20000000400000ull, 0}, {0x3fda8279ffffffffull, 0x3fd62e43471c5d9dull, 0}, + {0x3fda827a00000000ull, 0x3fd62e43471c5d9eull, 0}, {0xbfd2bec400000000ull, 0xbfd62e4420e1ff10ull, 0}, + {0xbfd2bec3ffffffffull, 0xbfd62e4420e1ff0full, 0}, {0x3ff0000000000000ull, 0x3fe62e42fefa39efull, 0}, + {0xbfe0000000000000ull, 0xbfe62e42fefa39efull, 0}, {0xbfefffffffffffffull, 0xc0425e4f7b2737faull, 0}, + {0xbff0000000000000ull, 0xfff0000000000000ull, 0}, {0xbff0000000000001ull, 0xfff8000000000000ull, EDOM}, + {0xc000000000000000ull, 0xfff8000000000000ull, EDOM}, {0x433fffffffffffffull, 0x40425e4f7b2737faull, 0}, + {0x4340000000000000ull, 0x40425e4f7b2737faull, 0}, {0x7fefffffffffffffull, 0x40862e42fefa39efull, 0}, + {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, + {0x3f0f5ab6d3865ccaull, 0x3f0f5a7962a0216cull, 0}, {0x3fe8683f0ace287cull, 0x3fe223b9ede79242ull, 0}, + {0x3f5f8cd247124706ull, 0x3f5f850e039f6187ull, 0}, {0x40298faede87074bull, 0x4004fc71ddb00cc0ull, 0}, + {0x404b621796f08648ull, 0x401015adbb851c70ull, 0}, {0x3f21ecf9339d7420ull, 0x3f21eca8e0601790ull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(log1p_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + +static void CheckTanhConsoleBits() { + constexpr std::uint64_t cases[][2] = { + {0x0000000000000000ull, 0x0000000000000000ull}, {0x8000000000000000ull, 0x8000000000000000ull}, + {0x3e2fffffffffffffull, 0x3e2fffffffffffffull}, {0x3e30000000000000ull, 0x3e30000000000000ull}, + {0xbe30000000000000ull, 0xbe30000000000000ull}, {0x3fefffffffffffffull, 0x3fe85efab514f394ull}, + {0x3ff0000000000000ull, 0x3fe85efab514f394ull}, {0xbff0000000000000ull, 0xbfe85efab514f394ull}, + {0x3fe0000000000000ull, 0x3fdd9353d7568af3ull}, {0x4035ffffffffffffull, 0x3ff0000000000000ull}, + {0x4036000000000000ull, 0x3ff0000000000000ull}, {0xc036000000000000ull, 0xbff0000000000000ull}, + {0x4049000000000000ull, 0x3ff0000000000000ull}, {0x7fefffffffffffffull, 0x3ff0000000000000ull}, + {0x7ff0000000000000ull, 0x3ff0000000000000ull}, {0xfff0000000000000ull, 0xbff0000000000000ull}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull}, {0xfff8000000000002ull, 0xfff8000000000002ull}, + {0x3f927ed0db8370b1ull, 0x3f927e4d1092a580ull}, {0x3f95d74e4cee78bfull, 0x3f95d67547d523a0ull}, + {0x3f18f1b062cb523cull, 0x3f18f1b06187faaeull}, {0x3f937129f25ae835ull, 0x3f937090dd8c9cb3ull}, + {0x3facb0d89f8aa91cull, 0x3faca92afe31a32bull}, {0x3f07f462f020acdeull, 0x3f07f462efd91530ull}, + }; + for (const auto& [input, expected] : cases) { + errno = 1234; + Require(std::bit_cast(tanh_nid_postfix(std::bit_cast(input))) == expected && errno == 1234); + } +} + +static void CheckRoundingNaNConsoleBits() { + constexpr std::uint64_t cases[][2] = { + {0x7ff0000000000001ull, 0x7ff8000000000001ull}, {0xfff4000000000123ull, 0xfffc000000000123ull}, {0x7ff8000000000456ull, 0x7ff8000000000456ull}, + {0x7ff0000000000000ull, 0x7ff0000000000000ull}, {0xfff0000000000000ull, 0xfff0000000000000ull}, + }; + constexpr std::uint32_t floatCases[][2] = { + {0x7f800001u, 0x7fc00001u}, {0xffa00123u, 0xffe00123u}, {0x7fc00456u, 0x7fc00456u}, {0x7f800000u, 0x7f800000u}, {0xff800000u, 0xff800000u}, + }; + for (const auto function : {trunc_nid_postfix, ceil_nid_postfix, floor_nid_postfix, nearbyint_nid_postfix}) { + for (const auto& [input, expected] : cases) { + Require(std::bit_cast(function(std::bit_cast(input))) == expected); + } + } + for (const auto function : {truncf_nid_postfix, ceilf_nid_postfix, floorf_nid_postfix, nearbyintf_nid_postfix}) { + for (const auto& [input, expected] : floatCases) { + Require(std::bit_cast(function(std::bit_cast(input))) == expected); + } + } +} + 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}, @@ -622,6 +705,21 @@ static void CheckAsinl() { *__error_nid_postfix() = 0; } +static void CheckCbrtfConsoleBits() { + constexpr std::uint32_t cases[][2] = { + {0x00000123u, 0x28059605u}, {0x00400327u, 0x2a4b334bu}, {0x3f000555u, 0x3f4b32c7u}, {0x3fc00778u, 0x3f9287e5u}, + {0x42000999u, 0x404b3509u}, {0x7e000bbcu, 0x544b362au}, {0xbf400dddu, 0xbf689d01u}, {0x80201002u, 0xaa215ff8u}, + {0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u}, {0x00000001u, 0x26a14518u}, {0x007fffffu, 0x2a7fffffu}, + {0x00800000u, 0x2a800000u}, {0x3f800000u, 0x3f800000u}, {0x41000000u, 0x40000000u}, {0xc1000000u, 0xc0000000u}, + {0x7f7fffffu, 0x54cb2ff5u}, {0x7f800000u, 0x7f800000u}, {0xff800000u, 0xff800000u}, {0x7fc00001u, 0x7fc00001u}, + {0xffc00002u, 0xffc00002u}, + }; + for (const auto& [input, expected] : cases) { + errno = 1234; + Require(std::bit_cast(cbrtf_nid_postfix(std::bit_cast(input))) == expected && errno == 1234); + } +} + static void CheckIntegerConversions() { for (const long long numerator : {4294967301LL, -4294967301LL}) { for (const long long denominator : {3LL, -3LL}) { @@ -796,13 +894,52 @@ static void CheckAsin() { std::feclearexcept(FE_ALL_EXCEPT); errno = 0; asin_nid_postfix(2.0); + Require(std::fetestexcept(FE_INVALID)); Require(errno == 0); std::feclearexcept(FE_ALL_EXCEPT); asin_nid_postfix(nan); + Require(!std::fetestexcept(FE_INVALID)); } +static void CheckTrigfConsoleBits() { + constexpr struct { + std::uint32_t input; + std::uint32_t sine; + std::uint32_t cosine; + std::uint32_t tangent; + int error; + } cases[] = { + {0x3e801d5au, 0x3e7d9057u, 0x3f780703u, 0x3e82db72u, 0}, {0x3fc00321u, 0x3f7f5c46u, 0x3d90acbbu, 0x4161edb0u, 0}, + {0x40400556u, 0x3e102d3eu, 0xbf7d7328u, 0xbe11a09du, 0}, {0x40c00781u, 0xbe8e9c41u, 0x3f75de78u, 0xbe947c92u, 0}, + {0x412009acu, 0xbf0bc6b7u, 0xbf56790du, 0x3f26d71eu, 0}, {0x47000bbbu, 0x3eb0df2fu, 0x3f703d5au, 0x3ebc799eu, 0}, + {0x4e000de0u, 0xbf664722u, 0x3edfade4u, 0xc003c6abu, 0}, {0x5f001006u, 0xbe0ecf11u, 0x3f7d7f8cu, 0xbe1037deu, 0}, + {0x7e001112u, 0x3eabd0cbu, 0x3f7127beu, 0x3eb6645du, 0}, {0xc0400556u, 0xbe102d3eu, 0xbf7d7328u, 0x3e11a09du, 0}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0}, {0x80000000u, 0x80000000u, 0x3f800000u, 0x80000000u, 0}, + {0x39000000u, 0x39000000u, 0x3f800000u, 0x39000000u, 0}, {0x3f490fdbu, 0x3f3504f3u, 0x3f3504f3u, 0x3f800000u, 0}, + {0x40490fdbu, 0xb3bbbd2eu, 0xbf800000u, 0x33bbbd2fu, 0}, {0x4dc90fdbu, 0xbf3d5f98u, 0x3f2c42b6u, 0xbf8cb742u, 0}, + {0x7f7fffffu, 0xbf0599b3u, 0x3f5a5f96u, 0xbf1c9ecau, 0}, {0xff7fffffu, 0x3f0599b3u, 0x3f5a5f96u, 0x3f1c9ecau, 0}, + {0x7f800000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, EDOM}, {0xff800000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, EDOM}, + {0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0}, + }; + for (const auto& item : cases) { + const float x = std::bit_cast(item.input); + const int expected = item.error != 0 ? item.error : 1234; + errno = 1234; + Require(std::bit_cast(sinf_nid_postfix(x)) == item.sine && errno == expected); + errno = 1234; + Require(std::bit_cast(cosf_nid_postfix(x)) == item.cosine && errno == expected); + errno = 1234; + Require(std::bit_cast(tanf_nid_postfix(x)) == item.tangent && errno == expected); + float sine = 0.0f; + float cosine = 0.0f; + sincosf_nid_postfix(x, &sine, &cosine); + + Require(std::bit_cast(sine) == item.sine && std::bit_cast(cosine) == item.cosine); + } +} + static void CheckExtendedMath() { Require(ExtendedBits(atanl_nid_postfix(INFINITY), 0xc90fdaa22168c235ull, 0x3fff)); Require(std::fabs(atanl_nid_postfix(1.0L) - 0.785398163397448309616L) < 1e-18L); @@ -837,8 +974,36 @@ static void CheckExtendedMath() { Require(std::isnan(remainder_nid_postfix(1.0, 0.0)) && std::isnan(remainder_nid_postfix(INFINITY, 1.0))); } +static void CheckExp2fConsoleBits() { + constexpr struct { + std::uint32_t input; + std::uint32_t expected; + int error; + } cases[] = { + {0x3d00010bu, 0x3f82cd8du, 0}, {0x3f000303u, 0x3fb5066du, 0}, {0x40a0051bu, 0x42000e29u, 0}, {0x42f0070cu, 0x7b813a1bu, 0}, + {0xbf400909u, 0x3f183437u, 0}, {0xc2f00b14u, 0x037c3073u, 0}, {0x00000000u, 0x3f800000u, 0}, {0x80000000u, 0x3f800000u, 0}, + {0x33000000u, 0x3f800000u, 0}, {0x3f800000u, 0x40000000u, 0}, {0x42fe0000u, 0x7f000000u, 0}, {0x42ffffffu, 0x7f7fffa7u, 0}, + {0x43000000u, 0x7f800000u, ERANGE}, {0x7f7fffffu, 0x7f800000u, ERANGE}, {0x7f800000u, 0x7f800000u, 0}, {0xff800000u, 0x00000000u, 0}, + {0xc2fc0000u, 0x00800000u, 0}, {0xc3150000u, 0x00000001u, 0}, {0xc315ffffu, 0x00000001u, 0}, {0xc3160000u, 0x00000000u, ERANGE}, + {0xc3160001u, 0x00000000u, ERANGE}, {0xff7fffffu, 0x00000000u, ERANGE}, {0x7fc00001u, 0x7fc00001u, 0}, {0xffc00002u, 0xffc00002u, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(exp2f_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + static void CheckHyperbolic() { const double infinity = std::numeric_limits::infinity(); + Require(_Cosh_nid_postfix(0.0, 2.0) == 2.0); + Require(Near(_Cosh_nid_postfix(1.0, 2.0), 3.0861612696304874, 1e-15)); + Require(Near(_Cosh_nid_postfix(-1.0, -2.0), -3.0861612696304874, 1e-15)); + Require(std::isinf(_Cosh_nid_postfix(infinity, -1.0)) && std::signbit(_Cosh_nid_postfix(infinity, -1.0))); + Require(std::isnan(_Cosh_nid_postfix(infinity, 0.0))); + Require(Near(_Cosh_nid_postfix(711.0, 1e-300), 0x1.2190c08e2f1a4p+28, 1e-15)); + Require(_Cosh_nid_postfix(720.0, 0.0) == 0.0 && !std::signbit(_Cosh_nid_postfix(720.0, 0.0))); + Require(_Cosh_nid_postfix(720.0, -0.0) == 0.0 && std::signbit(_Cosh_nid_postfix(720.0, -0.0))); Require(sinh_nid_postfix(0.) == 0. && std::signbit(sinh_nid_postfix(-0.))); Require(cosh_nid_postfix(0.) == 1. && coshf_nid_postfix(0.f) == 1.f); Require(asinh_nid_postfix(0.) == 0. && std::signbit(asinh_nid_postfix(-0.)) && std::signbit(asinhf_nid_postfix(-0.f))); @@ -859,6 +1024,150 @@ static void CheckHyperbolic() { Require(Near(acosh_nid_postfix(1e300), 691.4686750787736, 1e-15)); } +static void CheckAcoshfDomainEnvironment() { + const RestoreFloatingPointState saved; + const unsigned savedCsr = _mm_getcsr(); + const struct { + std::uint32_t input; + std::uint32_t result; + unsigned raised; + unsigned x87Raised; + int error; + } cases[] = { + {0x00000000u, 0xffc00000u, 1u, 1u, EDOM}, + {0x80000000u, 0xffc00000u, 1u, 1u, EDOM}, + {0x3f000000u, 0xffc00000u, 1u, 1u, EDOM}, + {0x3f7fffffu, 0xffc00000u, 1u, 1u, EDOM}, + {0xbf800000u, 0xffc00000u, 1u, 1u, EDOM}, + {0xff800000u, 0xffc00000u, 1u, 1u, EDOM}, + {0xffc00001u, 0xffc00001u, 1u, 1u, EDOM}, + {0xff800001u, 0xffc00001u, 1u, 1u, EDOM}, + {0x3f800000u, 0x00000000u, 0u, 0u, 1234}, + {0x7f800000u, 0x7f800000u, 0u, 0u, 1234}, + {0x7fc00001u, 0x7fc00001u, 0u, 0u, 1234}, + {0x7f800001u, 0x7fc00001u, 1u, 0u, 1234}, + }; + const int rounding[] = {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}; + float (APS5_VABI * volatile call)(float) = acoshf_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 || after.mxcsr != (control | sticky | item.raised) || (after.status & 0x3fu) != (sticky | item.x87Raised) || after.control != before.control || error != item.error) { + std::fprintf(stderr, "acoshf domain: input=%08llx mode=%u denorm=%u sticky=%u bits=%08llx 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 CheckAcoshfMsunBoundaries() { + Require(acoshf_nid_postfix(1.0f) == 0.0f && !std::signbit(acoshf_nid_postfix(1.0f))); + errno = EINTR; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(acoshf_nid_postfix(0.5f)) && errno == EDOM && (std::fetestexcept(FE_INVALID) & FE_INVALID) != 0); + for (const std::uint32_t bits : {0x3f800001u, 0x3fffffffu, 0x40000000u, 0x40000001u, + 0x4d7fffffu, 0x4d800000u, 0x4d800001u, 0x7f7fffffu}) { + const float x = std::bit_cast(bits); + Require(Near(acoshf_nid_postfix(x), std::acosh(static_cast(x)), 1e-6)); + } + Require(std::isinf(acoshf_nid_postfix(INFINITY)) && acoshf_nid_postfix(INFINITY) > 0.0f); + Require(std::isnan(acoshf_nid_postfix(-INFINITY))); + Require(std::isnan(acoshf_nid_postfix(std::numeric_limits::quiet_NaN()))); + const RestoreFloatingPointState saved; + const unsigned savedCsr = _mm_getcsr(); + const struct { + std::uint32_t input; + std::uint32_t result[4]; + unsigned raised; + } cases[] = { + {0x3f800000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, 0u}, + {0x3f800001u, {0x39ffffffu, 0x39ffffffu, 0x3a000001u, 0x39ffffffu}, 32u}, + {0x3fffffffu, {0x3fa89213u, 0x3fa89212u, 0x3fa89214u, 0x3fa89212u}, 32u}, + {0x40000000u, {0x3fa89214u, 0x3fa89213u, 0x3fa89214u, 0x3fa89213u}, 32u}, + {0x40000001u, {0x3fa89215u, 0x3fa89214u, 0x3fa89215u, 0x3fa89214u}, 32u}, + {0x40000002u, {0x3fa89216u, 0x3fa89215u, 0x3fa89217u, 0x3fa89215u}, 32u}, + {0x40000003u, {0x3fa89217u, 0x3fa89217u, 0x3fa89218u, 0x3fa89217u}, 32u}, + {0x4d7fffffu, {0x41a0cf66u, 0x41a0cf65u, 0x41a0cf66u, 0x41a0cf65u}, 32u}, + {0x4d800000u, {0x41a0cf66u, 0x41a0cf64u, 0x41a0cf66u, 0x41a0cf64u}, 32u}, + {0x4d800001u, {0x41a0cf66u, 0x41a0cf65u, 0x41a0cf67u, 0x41a0cf65u}, 32u}, + {0x4d8050f2u, {0x41a0d473u, 0x41a0d472u, 0x41a0d474u, 0x41a0d472u}, 32u}, + {0x7f7fffffu, {0x42b2d4fcu, 0x42b2d4fbu, 0x42b2d4fdu, 0x42b2d4fbu}, 32u}, + }; + const int rounding[] = {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}; + float (APS5_VABI * volatile call)(float) = acoshf_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 | item.raised) || (after.status & 0x3fu) != sticky || after.control != before.control || error != 1234) { + std::fprintf(stderr, "acoshf positive: input=%08llx mode=%u denorm=%u sticky=%u bits=%08llx expected=%08llx csr=%x x87=%x errno=%d\n", static_cast(item.input), mode, denorm, sticky, static_cast(result), static_cast(item.result[mode]), after.mxcsr, after.status, error); + Require(false); + } + } + } + } + } + _mm_setcsr(savedCsr); + +} + +static void CheckCoshfMsunBoundaries() { + static constexpr struct { + std::uint32_t input; + std::uint32_t output; + } cases[] = { + {0x00000000u, 0x3f800000u}, + {0x80000000u, 0x3f800000u}, + {0x00000001u, 0x3f800000u}, + {0x397fffffu, 0x3f800000u}, + {0x39800000u, 0x3f800000u}, + {0x3eb17217u, 0x3f87c3b6u}, + {0x3eb17218u, 0x3f87c3b6u}, + {0x3f800000u, 0x3fc583aau}, + {0xbf800000u, 0x3fc583aau}, + {0x410fffffu, 0x457d389cu}, + {0x41100000u, 0x457d38acu}, + {0x42b17216u, 0x7effff04u}, + {0x42b17217u, 0x7effff84u}, + {0x42b17218u, 0x7f000002u}, + {0x42b2d4fbu, 0x7f7fff6cu}, + {0x42b2d4fcu, 0x7f7fffecu}, + {0xc2b2d4fcu, 0x7f7fffecu}, + }; + for (const auto& test : cases) + Require(std::bit_cast(coshf_nid_postfix(std::bit_cast(test.input))) == test.output); + Require(std::bit_cast(coshf_nid_postfix(std::bit_cast(0x42b2d4fdu))) == 0x7f800000u); + Require(std::bit_cast(coshf_nid_postfix(INFINITY)) == 0x7f800000u); + Require(std::bit_cast(coshf_nid_postfix(-INFINITY)) == 0x7f800000u); + Require(std::bit_cast(coshf_nid_postfix(std::bit_cast(0x7fc12345u))) == 0x7fc12345u); +} + static void CheckScaledHyperbolic() { const int rounding = std::fegetround(); Require(std::fesetround(FE_TONEAREST) == 0); @@ -1049,6 +1358,26 @@ static void RequireBytes(const void* constant, std::initializer_list(logf_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + static void CheckDinkumwareConstants() { RequireBytes(&_Inf_nid_postfix, {0, 0, 0, 0, 0, 0, 0xf0, 0x7f}); RequireBytes(&_Denorm_nid_postfix, {1}); @@ -1137,6 +1466,15 @@ static bool Near(long double actual, long double expected) { return std::fabs(actual - expected) <= std::fabs(expected) * 1e-17L; } +static void CheckAcosfMsun() { + Require(std::bit_cast(acosf_nid_postfix(std::bit_cast(0x3d58d031u))) == 0x3fc2488au); + Require(std::bit_cast(acosf_nid_postfix(std::bit_cast(0x3f00b99au))) == 0x3f859f4fu); + Require(std::bit_cast(acosf_nid_postfix(std::bit_cast(0xbf00b99au))) == 0x40064033u); + Require(std::bit_cast(acosf_nid_postfix(1.0f)) == 0u); + Require(std::bit_cast(acosf_nid_postfix(-1.0f)) == 0x40490fdbu); + Require(std::bit_cast(acosf_nid_postfix(std::bit_cast(0x32800000u))) == 0x3fc90fdbu); +} + static void CheckLongDoubleFunctions() { const long double infinity = std::numeric_limits::infinity(); const long double nan = std::numeric_limits::quiet_NaN(); @@ -1227,17 +1565,31 @@ static void CheckLongDoubleRounding(long double value, std::int64_t expected, bo } } -static void CheckNoConversionIsPositiveZero() { - for (const char* text : {"", " ", "-", "+", "-.", "+.", "-x", "-.e1", "-e5", "- 1", " -."}) { +static void CheckNoConversionZero() { + for (const char* text : {"", " ", "-", " -", "+", "-.", "+.", "-x", "-.e1", "-e5", "- 1", " -."}) { char* end = nullptr; const double parsed = strtod_nid_postfix(text, &end); Require(parsed == 0. && !std::signbit(parsed) && end == text); + const bool negative = std::strchr(text, '-') != nullptr; + const bool loneSign = negative && text[std::strlen(text) - 1] == '-'; const float parsedFloat = strtof_nid_postfix(text, &end); - Require(parsedFloat == 0.f && !std::signbit(parsedFloat) && end == text); + Require(parsedFloat == 0.f && std::signbit(parsedFloat) == (negative && !loneSign) && end == text); const long double parsedLong = strtold_nid_postfix(text, &end); - Require(parsedLong == 0.L && !std::signbit(parsedLong) && end == text); + Require(parsedLong == 0.L && std::signbit(parsedLong) == negative && end == text); Require(!std::signbit(atof_nid_postfix(text))); } + for (const char* text : {"-i", "-in", "-N", "-nax"}) { + char* end = nullptr; + const float parsedFloat = strtof_nid_postfix(text, &end); + 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)"}) { + 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(strtof_nid_postfix("nan", nullptr)) == 0x7fc00000u); char* end = nullptr; const char negativeZero[] = "-0.0x"; const double parsed = strtod_nid_postfix(negativeZero, &end); @@ -1262,9 +1614,9 @@ static void CheckNoConversionIsPositiveZero() { const double parsedWide = wcstod_nid_postfix(wide, &wideEnd); Require(parsedWide == 0. && !std::signbit(parsedWide) && wideEnd == wide); const float parsedWideFloat = wcstof_nid_postfix(wide, &wideEnd); - Require(parsedWideFloat == 0.f && !std::signbit(parsedWideFloat) && wideEnd == wide); + Require(parsedWideFloat == 0.f && std::signbit(parsedWideFloat) && wideEnd == wide); const long double parsedWideLong = wcstold_nid_postfix(wide, &wideEnd); - Require(parsedWideLong == 0.L && !std::signbit(parsedWideLong) && wideEnd == wide); + Require(parsedWideLong == 0.L && std::signbit(parsedWideLong) && wideEnd == wide); } template @@ -1296,11 +1648,13 @@ static void CheckDinkumwareFloatCharacteristics() { int main() { CheckFloatClassification(); CheckDinkumwareConstants(); - CheckNoConversionIsPositiveZero(); + CheckNoConversionZero(); CheckMissingClassificationExports(); CheckFloatMathExports(); CheckIntegerConversions(); + CheckCbrtfConsoleBits(); CheckExtendedMath(); + CheckTrigfConsoleBits(); Require(atof_nid_postfix(" -12.5tail") == -12.5); char* end = nullptr; const char input[] = "0x1.8p+2 remainder"; @@ -1340,6 +1694,7 @@ int main() { 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()); } @@ -1406,6 +1761,7 @@ int main() { const auto gammaSign = [](double x) { signgam_nid_postfix = 7; lgamma_nid_postfix(x); + return signgam_nid_postfix; }; Require(gammaSign(0.5) == 1 && gammaSign(10.) == 1 && gammaSign(0.) == 1 && gammaSign(-0.) == -1); @@ -1455,6 +1811,11 @@ int main() { CheckFegetenv(); CheckLongDouble(); CheckNearbyintl(); + CheckExpm1ConsoleBits(); + CheckLog1pConsoleBits(); + CheckNextafterConsoleBits(); + CheckTanhConsoleBits(); + CheckRoundingNaNConsoleBits(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); @@ -1542,6 +1903,7 @@ int main() { CheckPowfSpecialValue(); Require(abs_nid_postfix(0) == 0); CheckAcosfErrno(); + CheckAcosfMsun(); Require(abs_nid_postfix(42) == 42); Require(abs_nid_postfix(-42) == 42); Require(abs_nid_postfix(std::numeric_limits::max()) == std::numeric_limits::max()); @@ -1577,12 +1939,19 @@ int main() { Require(std::isinf(__powidf2_nid_postfix(-0.0, -3)) && std::signbit(__powidf2_nid_postfix(-0.0, -3))); Require(__powidf2_nid_postfix(2.0, -127) == 0x1p-127); CheckHyperbolic(); + CheckAcoshfDomainEnvironment(); + CheckAcoshfMsunBoundaries(); + CheckCoshfMsunBoundaries(); + CheckExp2fConsoleBits(); CheckScaledHyperbolic(); CheckSinhf(); CheckAtanhf(); + CheckLogfConsoleBits(); CheckHypotSpecialValue(); + CheckAtanfConsoleBits(); CheckFloatUnderflowErrno(); CheckFmodErrno(); + CheckFmodfErrno(); CheckDoubleClassification(); CheckIntegerAbsAndDivision(); CheckScaling(); diff --git a/core/libs/tests/GuestMathExact.cpp b/core/libs/tests/GuestMathExact.cpp index e601e88973..5db64f610d 100644 --- a/core/libs/tests/GuestMathExact.cpp +++ b/core/libs/tests/GuestMathExact.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -7,38 +8,9 @@ #include #include -extern "C" { -long double APS5_VABI fdiml_nid_postfix(long double, long double); -long double APS5_VABI fmodl_nid_postfix(long double, long double); -long double APS5_VABI remquol_nid_postfix(long double, long double, int*); -int APS5_VABI ilogbl_nid_postfix(long double); -long double APS5_VABI roundl_nid_postfix(long double); -long double APS5_VABI modfl_nid_postfix(long double, long double*); -long double APS5_VABI nanl_nid_postfix(const char*); -long double APS5_VABI scalblnl_nid_postfix(long double, std::int64_t); -long double APS5_VABI fmal_nid_postfix(long double, long double, long double); -double APS5_VABI scalb_nid_postfix(double, double); -double APS5_VABI scalbln_nid_postfix(double, std::int64_t); -long double APS5_VABI remainderl_nid_postfix(long double, long double); -double APS5_VABI drem_nid_postfix(double, double); -float APS5_VABI dremf_nid_postfix(float, float); -std::int64_t APS5_VABI lrint_nid_postfix(double); -double APS5_VABI rint_nid_postfix(double); -float APS5_VABI rintf_nid_postfix(float); -long double APS5_VABI rintl_nid_postfix(long double); -long long APS5_VABI llrint_nid_postfix(double); -int APS5_VABI finite_nid_postfix(double); -int APS5_VABI __fpclassifyl_nid_postfix(long double); -int APS5_VABI __isnanl_nid_postfix(long double); -int APS5_VABI __isnormall_nid_postfix(long double); -short APS5_VABI _LDtest_nid_postfix(const long double*); -int APS5_VABI _Dclass_nid_postfix(double); -short APS5_VABI _LDsign_nid_postfix(long double); -} - static void Require(bool value) { if (!value) std::abort(); } -static long double Extended(std::uint16_t signExponent, std::uint64_t mantissa) { +[[gnu::noinline]] static long double Extended(std::uint16_t signExponent, std::uint64_t mantissa) { long double value = 0; unsigned char bytes[sizeof(long double)] = {}; std::memcpy(bytes, &mantissa, sizeof(mantissa)); @@ -49,6 +21,40 @@ static long double Extended(std::uint16_t signExponent, std::uint64_t mantissa) static bool SameBits(long double left, long double right) { return std::memcmp(&left, &right, 10) == 0; } +static void CheckRoundingAndScaling() { + Require(copysignl_nid_postfix(3.0L, -1.0L) == -3.0L && copysignl_nid_postfix(-3.0L, 0.0L) == 3.0L); + Require(std::signbit(copysignl_nid_postfix(0.0L, -0.0L)) && !std::signbit(copysignl_nid_postfix(-0.0L, 2.0L))); + Require(sqrtl_nid_postfix(6.25L) == 2.5L && std::isnan(sqrtl_nid_postfix(-1.0L))); + Require(sqrtl_nid_postfix(-0.0L) == 0.0L && std::signbit(sqrtl_nid_postfix(-0.0L))); + + const int saved = std::fegetround(); + const struct { + int mode; + long long half; + long long negativeHalf; + } modes[] = {{FE_TONEAREST, 2, -2}, {FE_UPWARD, 3, -2}, {FE_DOWNWARD, 2, -3}, {FE_TOWARDZERO, 2, -2}}; + for (const auto& mode : modes) { + Require(std::fesetround(mode.mode) == 0); + errno = 0; + Require(lrintl_nid_postfix(2.5L) == mode.half && lrintl_nid_postfix(-2.5L) == mode.negativeHalf); + Require(llrintl_nid_postfix(2.5L) == mode.half && llrintl_nid_postfix(-2.5L) == mode.negativeHalf); + Require(lrintf_nid_postfix(2.5f) == mode.half && lrintf_nid_postfix(-2.5f) == mode.negativeHalf); + Require(llrintf_nid_postfix(2.5f) == mode.half && llrintf_nid_postfix(-2.5f) == mode.negativeHalf); + Require(errno == 0); + } + std::fesetround(saved); + Require(llrintl_nid_postfix(3.5L) == 4 && lrintf_nid_postfix(16777216.0f) == 16777216); + + Require(scalbf_nid_postfix(1.5f, 3.0f) == 12.0f && scalbf_nid_postfix(12.0f, -2.0f) == 3.0f); + Require(std::isnan(scalbf_nid_postfix(1.0f, 0.5f)) && std::isnan(scalbf_nid_postfix(NAN, 1.0f))); + Require(std::isinf(scalbf_nid_postfix(1.0f, INFINITY)) && scalbf_nid_postfix(8.0f, -INFINITY) == 0.0f); + Require(scalblnf_nid_postfix(1.0f, 10) == 1024.0f && scalblnf_nid_postfix(1024.0f, -10) == 1.0f); + Require(std::isinf(scalblnf_nid_postfix(1.0f, INT64_MAX)) && scalblnf_nid_postfix(1.0f, INT64_MIN) == 0.0f); + + Require(__isinfl_nid_postfix(static_cast(INFINITY)) == 1 && __isinfl_nid_postfix(-static_cast(INFINITY)) == 1); + Require(__isinfl_nid_postfix(1.0L) == 0 && __isinfl_nid_postfix(static_cast(NAN)) == 0 && __isinfl_nid_postfix(0.0L) == 0); +} + static void CheckSignalingNanPassThrough() { const long double signaling = Extended(0x7fff, 0x8000000000001234ull); Require(SameBits(fdiml_nid_postfix(signaling, 1.0L), signaling)); @@ -70,6 +76,7 @@ static void CheckErrno() { Require(std::isnan(remquol_nid_postfix(1.0L, 0.0L, "ient)) && errno == EDOM); errno = 0; remquol_nid_postfix(NAN, 0.0L, "ient); + Require(errno == 0); Require(remquol_nid_postfix(-7.0L, 2.0L, "ient) == 1.0L && quotient == -4); errno = 0; @@ -133,6 +140,7 @@ static void CheckClassification() { } int main() { + CheckRoundingAndScaling(); CheckSignalingNanPassThrough(); CheckErrno(); CheckConsoleQuirks(); diff --git a/core/libs/tests/GuestMbus.cpp b/core/libs/tests/GuestMbus.cpp index c4f97b1141..29ef46dc7e 100644 --- a/core/libs/tests/GuestMbus.cpp +++ b/core/libs/tests/GuestMbus.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMbus/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -6,14 +7,6 @@ #include #include -extern "C" { -std::int32_t APS5_VABI sceDeviceServiceInitialize(std::int32_t mode, std::int32_t flags); -std::int32_t APS5_VABI sceDeviceServiceTerminate(); -std::int32_t APS5_VABI sceDeviceServiceGetEventState(std::int32_t deviceType); -std::int32_t APS5_VABI sceDeviceServiceGetGeneration(); -std::int32_t APS5_VABI sceDeviceServiceQueryDeviceInfo_(std::int32_t deviceClass, std::int32_t arg1, std::int32_t arg2, void* infos, std::int32_t maxInfos, std::int32_t* count, std::int32_t* reserved, std::uint64_t infoSize); -} - namespace { void Require(bool condition, const char* message) { @@ -26,6 +19,19 @@ void Require(bool condition, const char* message) { } int main() { + DeviceServiceInitParam param{}; + constexpr auto invalidArgument = static_cast(0x809B0080u); + Require(sceDeviceServiceInitialize(3, ¶m) == invalidArgument, "invalid parameter size accepted"); + param.size = 16; + param.reserved = 1; + Require(sceDeviceServiceInitialize(3, ¶m) == invalidArgument, "reserved field accepted"); + param.reserved = 0; + DeviceServiceThreadParam threadParam{}; + threadParam.reserved = 1; + param.threadParam = &threadParam; + Require(sceDeviceServiceInitialize(3, ¶m) == invalidArgument, "threadParam reserved field accepted"); + param.threadParam = nullptr; + Require(sceDeviceServiceInitialize(3, ¶m) == 0, "default parameter initialization failed"); Require(sceDeviceServiceInitialize(3, 0) == 0, "initialization failed"); Require(sceDeviceServiceGetGeneration() == 0, "the device set started at another generation"); Require(sceDeviceServiceGetEventState(1) == 0, "a device event was reported"); @@ -33,13 +39,17 @@ int main() { std::int32_t count = 7; std::int32_t reserved = 0; Require(sceDeviceServiceQueryDeviceInfo_(0x7001, 0, 0, info.data(), 1, &count, &reserved, info.size()) == 0 && count == 0, "a device was listed"); - bool threw = false; - try { - sceDeviceServiceQueryDeviceInfo_(0x7001, 0, 0, info.data(), 1, nullptr, &reserved, info.size()); - } catch (const std::invalid_argument&) { - threw = true; - } - Require(threw, "a null count was accepted"); + Require(sceDeviceServiceQueryDeviceInfo_(0x7001, nullptr, 0, info.data(), 1, nullptr, &reserved, info.size()) == 0, "optional count output rejected"); + Require(sceDeviceServiceQueryDeviceInfo_(0x7001, nullptr, 0, nullptr, 0, nullptr, nullptr, info.size()) == 0, "empty query rejected"); + Require(sceDeviceServiceQueryDeviceInfo_(0x7001, nullptr, 0, nullptr, 1, &count, nullptr, info.size()) == invalidArgument, "null record buffer accepted"); + Require(sceDeviceServiceQueryDeviceInfo_(0x7001, nullptr, 0, info.data(), -1, &count, nullptr, info.size()) == invalidArgument, "negative capacity accepted"); + Require(sceDeviceServiceQueryDeviceInfo_(0x7001, nullptr, 0, info.data(), 1, &count, nullptr, info.size() - 1) == invalidArgument, "incorrect record size accepted"); + DeviceServiceUsageFilter filter{1, 2, 3}; + Require(sceDeviceServiceQueryDeviceInfo_(0x8001, &filter, 0, info.data(), 1, &count, nullptr, info.size()) == 0, "valid HID filter rejected"); + Require(sceDeviceServiceQueryDeviceInfo_(0x8001, nullptr, 0, info.data(), 1, &count, nullptr, info.size()) == invalidArgument, "missing HID filter accepted"); + Require(sceDeviceServiceQueryDeviceInfo_(0x7001, &filter, 0, info.data(), 1, &count, nullptr, info.size()) == invalidArgument, "unexpected HID filter accepted"); + filter.minimumUsage = 4; + Require(sceDeviceServiceQueryDeviceInfo_(0x8001, &filter, 0, info.data(), 1, &count, nullptr, info.size()) == invalidArgument, "reversed HID usage range accepted"); Require(sceDeviceServiceGetGeneration() == 0, "the generation advanced without a device change"); Require(sceDeviceServiceTerminate() == 0, "termination failed"); std::puts("DeviceService tests passed"); diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index a52c5a5e2d..f31f21accc 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestHeap.hpp" @@ -42,55 +45,6 @@ #include #endif -extern "C" { -void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; -int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; -int APS5_VABI sceKernelMmap(void*, std::size_t, int, int, int, std::int64_t, void**); -int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int) noexcept; -int APS5_VABI madvise_nid_postfix(void*, std::size_t, int); -int APS5_VABI mincore_nid_postfix(const void*, std::size_t, char*); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceKernelMapNamedFlexibleMemory(void**, std::size_t, int, int, const char*); -int APS5_VABI sceKernelMapNamedFlexibleMemoryInternal(void**, std::size_t, int, int, const char*); -int APS5_VABI sceKernelAvailableFlexibleMemorySize(std::size_t*); -int APS5_VABI sceKernelInternalMemoryGetAvailableSize(std::size_t*); -int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int APS5_VABI sceKernelReleaseFlexibleMemory(void*, std::size_t); -int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); -int APS5_VABI sceKernelMtypeprotect(const void*, std::size_t, int, int); -int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int); -int APS5_VABI sceKernelMemoryPoolBatch(const KernelMemoryPoolBatchEntry*, int, int*, int); -int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); -int APS5_VABI sceKernelSetVirtualRangeName(const void*, std::uint64_t, const char*); -int APS5_VABI sceKernelClearVirtualRangeName(const void*, std::uint64_t); -int APS5_VABI sceKernelGetPageTableStats(int*, int*, int*, int*); -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelEnableDmemAliasing(); -int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); -int APS5_VABI sceKernelAvailableDirectMemorySize(std::int64_t, std::int64_t, std::size_t, std::int64_t*, std::size_t*); -int APS5_VABI sceKernelCheckedReleaseDirectMemory(std::int64_t, std::size_t); -int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t); -int APS5_VABI sceKernelMemoryPoolReserve(void*, std::size_t, std::size_t, int, void**); -int APS5_VABI sceKernelMemoryPoolCommit(void*, std::size_t, int, int, int); -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t); -int APS5_VABI sceKernelAioInitializeImpl(void*, std::int32_t); -int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest*, std::int32_t, std::int32_t, std::int32_t*); -int APS5_VABI sceKernelAioWaitRequest(std::int32_t, std::int32_t*, std::uint32_t*); -int APS5_VABI sceKernelAioDeleteRequest(std::int32_t, std::int32_t*); -int APS5_VABI sceKernelMlock_nid_postfix(void*, std::uint64_t); -int APS5_VABI sceKernelGetDirectMemoryType(std::int64_t, int*, std::int64_t*, std::int64_t*); -int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int); -void* APS5_VABI dlopen_nid_postfix(const char*, int); -void* APS5_VABI dlsym_nid_postfix(void*, const char*); -int APS5_VABI dlclose_nid_postfix(void*); -} - static void Require(bool condition, std::source_location location = std::source_location::current()) { if (!condition) { std::fprintf(stderr, "Guest memory check failed at %s:%u\n", location.file_name(), static_cast(location.line())); @@ -113,12 +67,33 @@ static void CheckReleaseFlexibleMemory() { Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0 && mapped != nullptr); static_cast(mapped)[length - 1] = 1; Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length); + auto* bytes = static_cast(mapped); + Require(sceKernelMprotect(mapped, 0x4000, 1) == 0); Require(sceKernelReleaseFlexibleMemory(mapped, length) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0 && info.protection == 1); + Require(sceKernelVirtualQuery(static_cast(mapped) + length - 1, 0, &info, sizeof(info)) == 0 && info.protection == 3); + Require(bytes[length - 1] == 1); + bytes[length - 2] = 2; + Require(sceKernelReleaseFlexibleMemory(mapped, length) == 0); + Require(sceKernelReleaseFlexibleMemory(mapped, 0) == 0); + Require(sceKernelReleaseFlexibleMemory(reinterpret_cast(std::uintptr_t{0x7ff000000000}), length) == 0); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); + Require(sceKernelReleaseFlexibleMemory(nullptr, 0) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelReleaseFlexibleMemory(static_cast(mapped) + 1, 0) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelReleaseFlexibleMemory(mapped, 1) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelReleaseFlexibleMemory(static_cast(mapped) + 0x1000, 0x4000) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelReleaseFlexibleMemory(mapped, 0x6000) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelReleaseFlexibleMemory(reinterpret_cast(~std::uintptr_t{0x3fff}), 0x8000) == SCE_KERNEL_ERROR_EINVAL); void* again = mapped; - Require(sceKernelMapFlexibleMemory(&again, length, 3, 0x90) == 0 && again == mapped); + Require(sceKernelMapFlexibleMemory(&again, length, 3, 0x90) == SCE_KERNEL_ERROR_ENOMEM); + Require(bytes[length - 1] == 1 && bytes[length - 2] == 2); + again = mapped; + Require(sceKernelMapFlexibleMemory(&again, length, 3, 0x10) == 0 && again == mapped); Require(static_cast(again)[length - 1] == 0); - Require(sceKernelMunmap(again, length) == 0); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length); + Require(sceKernelMunmap(reinterpret_cast(again), length) == 0); } static void CheckFlexibleMappingIgnoresFlag8000() { @@ -130,11 +105,11 @@ static void CheckFlexibleMappingIgnoresFlag8000() { Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0x8000) == 0 && mapped != nullptr); static_cast(mapped)[length - 1] = 1; Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length); - Require(sceKernelMunmap(mapped, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), length) == 0); void* again = mapped; Require(sceKernelMapFlexibleMemory(&again, length, 3, 0x8090) == 0 && again == mapped); Require(static_cast(again)[length - 1] == 0); - Require(sceKernelMunmap(again, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(again), length) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); bool rejected = false; void* unknown = nullptr; @@ -157,8 +132,15 @@ static void CheckPageTableStats() { static void CheckInternalMemoryAvailableSize() { std::size_t available = 1; - Require(sceKernelInternalMemoryGetAvailableSize(&available) == 0 && available == 0); - Require(sceKernelInternalMemoryGetAvailableSize(nullptr) == static_cast(0x80020016u)); + bool threw = false; + try { + sceKernelInternalMemoryGetAvailableSize(&available); + + } catch (const std::logic_error&) { + threw = true; + } + Require(threw && available == 1); + Require(sceKernelInternalMemoryGetAvailableSize(nullptr) == -1); } static void CheckNamedAndHintedMappings() { @@ -180,13 +162,13 @@ static void CheckNamedAndHintedMappings() { void* overwrite = first; Require(sceKernelMapFlexibleMemory(&overwrite, 0x4000, 3, 0x90) == static_cast(0x8002000cu)); Require(overwrite == first); - Require(sceKernelMunmap(hinted, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(hinted), length) == 0); void* exclusive = hinted; Require(sceKernelMapFlexibleMemory(&exclusive, length, 3, 0x90) == 0); Require(exclusive == hinted); static_cast(exclusive)[0] = 1; - Require(sceKernelMunmap(exclusive, length) == 0); - Require(sceKernelMunmap(first, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(exclusive), length) == 0); + Require(sceKernelMunmap(reinterpret_cast(first), length) == 0); } static void CheckAudioCoprocessorProtection() { @@ -199,7 +181,7 @@ static void CheckAudioCoprocessorProtection() { Require(static_cast(writable)[length - 1] == 7); Require(sceKernelMprotect(writable, length, 0x3f2) == 0); static_cast(writable)[0] = 9; - Require(sceKernelMunmap(writable, length) == 0); + 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; } @@ -235,7 +217,7 @@ static void CheckMprotectIgnoresUndefinedBits() { for (const auto undefined : {0u, 8u, 0x400u, 0x80000000u, 0xfffffc08u}) check(mapping, static_cast(static_cast(protection) | undefined)); } - Require(sceKernelMunmap(mapping, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapping), length) == 0); std::int64_t physical = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, length, 0, 0, &physical) == 0); mapping = nullptr; @@ -245,7 +227,7 @@ static void CheckMprotectIgnoresUndefinedBits() { check(mapping, 0x100); check(mapping, 0x200); check(mapping, 0x3f7); - Require(sceKernelMunmap(mapping, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapping), length) == 0); Require(sceKernelReleaseDirectMemory(physical, length) == 0); } @@ -283,10 +265,21 @@ static void CheckStrictMemoryProtectionBits() { Require(sceKernelGetDirectMemoryType(physical, &type, &start, &end) == 0 && type == 0); Require(bytes[0] == 5 && bytes[length - 1] == 7); } - Require(sceKernelMunmap(mapping, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapping), length) == 0); Require(sceKernelReleaseDirectMemory(physical, length) == 0); } +static void CheckPosixMprotectIgnoresUndefinedBits() { + constexpr std::size_t length = 0x4000; + auto* mapping = static_cast(mmap_nid_postfix(nullptr, length, 3, 0x1002, -1, 0)); + Require(mapping != reinterpret_cast(static_cast(-1))); + Require(mprotect_nid_postfix(const_cast(mapping), length, static_cast(0xfffffff2u)) == 0); + mapping[length - 1] = 5; + Require(mapping[length - 1] == 5); + Require(mprotect_nid_postfix(const_cast(mapping), length, 0x8 | 0x3) == 0); + Require(munmap_nid_postfix(const_cast(mapping), length) == 0); +} + static void CheckInternalNamedFlexibleMapping() { constexpr std::size_t length = 0x10000; std::size_t before = 0; @@ -296,14 +289,14 @@ static void CheckInternalNamedFlexibleMapping() { Require(sceKernelMapNamedFlexibleMemoryInternal(&mapped, length, 3, 0, "internal mapping") == 0 && mapped != nullptr); Require(std::strcmp(NameAt(mapped), "internal mapping") == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length); - Require(sceKernelMunmap(mapped, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), length) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); void* flagged = nullptr; Require(sceKernelMapNamedFlexibleMemoryInternal(&flagged, length, 3, 0x8000, "internal flag") == 0 && flagged != nullptr); Require(std::strcmp(NameAt(flagged), "internal flag") == 0); static_cast(flagged)[length - 1] = 1; Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length); - Require(sceKernelMunmap(flagged, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(flagged), length) == 0); bool rejected = false; void* unknown = nullptr; try { sceKernelMapNamedFlexibleMemoryInternal(&unknown, length, 3, 0x20000, "internal mapping"); } catch (const std::exception&) { rejected = true; } @@ -326,7 +319,7 @@ static void CheckFlexibleFixedReplacement() { Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x10) == 0 && replacement == middle); Require(bytes[0] == 17 && bytes[page] == 0 && bytes[page * 2] == 19 && bytes[page * 3] == 20); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 4); - Require(sceKernelMunmap(base, page * 4) == 0); + Require(sceKernelMunmap(reinterpret_cast(base), page * 4) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); } @@ -344,23 +337,23 @@ static void CheckMunmapScopedFlexibleRelease() { Require(sceKernelMapDirectMemory(&direct, page, 3, 0x10, phys, 0) == 0 && direct == static_cast(base) + page); std::size_t mid = 0; Require(sceKernelAvailableFlexibleMemorySize(&mid) == 0); - Require(sceKernelMunmap(direct, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(direct), page) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == mid); void* reserved = nullptr; Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0, 0) == 0 && reserved != nullptr); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == mid); - Require(sceKernelMunmap(reserved, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page * 2) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == mid); - Require(sceKernelMunmap(base, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(base), page) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == mid + page); Require(sceKernelReleaseDirectMemory(phys, page) == 0); void* fresh = nullptr; Require(sceKernelMapFlexibleMemory(&fresh, page * 4, 3, 0) == 0 && fresh != nullptr); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 4); - Require(sceKernelMunmap(static_cast(fresh) + page, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(static_cast(fresh) + page), page * 2) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 2); - Require(sceKernelMunmap(fresh, page) == 0); - Require(sceKernelMunmap(static_cast(fresh) + page * 3, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(fresh), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(static_cast(fresh) + page * 3), page) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); } @@ -392,7 +385,7 @@ static void CheckBatchMapStopsAtInvalidEntry() { Require(sceKernelBatchMap2(entries, 3, &processed, 0) == SCE_KERNEL_ERROR_EINVAL); Require(processed == 1); Require(entries[0].start != nullptr && entries[1].start == nullptr && entries[2].start == nullptr); - Require(sceKernelMunmap(entries[0].start, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(entries[0].start), page) == 0); } KernelBatchMapEntry entries[3] = {flexible(), flexible(), flexible()}; entries[1].operation = protect; @@ -408,7 +401,7 @@ static void CheckBatchMapStopsAtInvalidEntry() { processed = -1; Require(sceKernelBatchMap2(unmaps, 2, &processed, 0) == SCE_KERNEL_ERROR_EINVAL && processed == 1); Require(sceKernelBatchMap2(entries, 1, &processed, 0) == 0 && processed == 1); - Require(sceKernelMunmap(entries[0].start, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(entries[0].start), page) == 0); } static void CheckVirtualQueryFindsNeighbours() { @@ -426,14 +419,14 @@ static void CheckVirtualQueryFindsNeighbours() { Require(info.start == reinterpret_cast(low) + page && info.end == reinterpret_cast(low) + page * 2 && info.protection == 1); Require(sceKernelVirtualQuery(lowBytes + page * 2, 0, &info, sizeof(info)) == 0); Require(info.start == reinterpret_cast(low) + page * 2 && info.end == reinterpret_cast(low) + page * 4 && info.protection == 3); - Require(sceKernelMunmap(lowBytes + page * 2, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(lowBytes + page * 2), page) == 0); Require(sceKernelVirtualQuery(lowBytes + page * 2, 0, &info, sizeof(info)) != 0); Require(sceKernelVirtualQuery(lowBytes + page * 2, 1, &info, sizeof(info)) == 0); Require(info.start == reinterpret_cast(low) + page * 3 && info.end == reinterpret_cast(low) + page * 4); Require(sceKernelVirtualQuery(lowBytes + page * 3, 0, &info, sizeof(info)) == 0 && info.start == reinterpret_cast(low) + page * 3); - Require(sceKernelMunmap(low, page * 2) == 0); - Require(sceKernelMunmap(lowBytes + page * 3, page) == 0); - Require(sceKernelMunmap(high, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(low), page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(lowBytes + page * 3), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(high), page) == 0); } static void CheckReleaseFindsEveryDirectMapping() { @@ -462,6 +455,23 @@ static void CheckReleaseFindsEveryDirectMapping() { for (std::size_t index = 0; index < pieces; ++index) Require(sceKernelVirtualQuery(mapped[index], 0, &info, sizeof(info)) != 0); } +static void CheckReleaseSkipsFreePages() { + constexpr std::size_t page = 0x4000; + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 4, 0, 0, &phys) == 0); + void* mapped = nullptr; + Require(sceKernelMapDirectMemory(&mapped, page * 4, 3, 0, phys, 0) == 0); + Require(sceKernelReleaseDirectMemory(phys + static_cast(page), page) == 0); + Require(sceKernelReleaseDirectMemory(phys + static_cast(page), page) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) != 0); + Require(sceKernelVirtualQuery(static_cast(mapped) + page * 3, 0, &info, sizeof(info)) == 0); + Require(sceKernelCheckedReleaseDirectMemory(phys, page) == SCE_KERNEL_ERROR_ENOENT); + Require(sceKernelCheckedReleaseDirectMemory(phys + static_cast(page * 3), page) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 4) == 0); +} + static void CheckCheckedReleaseDirectMemory() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; @@ -472,7 +482,7 @@ static void CheckCheckedReleaseDirectMemory() { Require(sceKernelCheckedReleaseDirectMemory(phys, page * 3) == SCE_KERNEL_ERROR_ENOENT); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0); - Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 2) == 0); Require(sceKernelCheckedReleaseDirectMemory(phys + page, page) == 0); Require(sceKernelCheckedReleaseDirectMemory(phys, page * 2) == SCE_KERNEL_ERROR_ENOENT); Require(sceKernelCheckedReleaseDirectMemory(phys, page) == 0); @@ -501,9 +511,9 @@ static void CheckDirectMemoryFollowsPhysicalPages() { Require(sceKernelMprotect(alias, page, 3) == 0); static_cast(alias)[5] = 22; Require(static_cast(first)[page + 5] == 22); - Require(sceKernelMunmap(alias, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(alias), page) == 0); static_cast(first)[page + 5] = 22; - Require(sceKernelMunmap(first, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(first), page * 2) == 0); void* filler = nullptr; Require(sceKernelMapFlexibleMemory(&filler, page * 2, 3, 0) == 0); void* second = nullptr; @@ -514,16 +524,16 @@ static void CheckDirectMemoryFollowsPhysicalPages() { void* fixed = reserved; Require(sceKernelMapDirectMemory(&fixed, page, 1, 0x10, phys, 0) == 0); Require(fixed == reserved && static_cast(fixed)[0] == 11); - Require(sceKernelMunmap(fixed, page) == 0); - Require(sceKernelMunmap(second, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(fixed), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(second), page) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); std::int64_t again = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &again) == 0 && again == phys); void* fresh = nullptr; Require(sceKernelMapDirectMemory(&fresh, page * 2, 3, 0, again, 0) == 0); Require(static_cast(fresh)[0] == 0 && static_cast(fresh)[page + 5] == 0); - Require(sceKernelMunmap(fresh, page * 2) == 0); - Require(sceKernelMunmap(filler, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(fresh), page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(filler), page * 2) == 0); Require(sceKernelReleaseDirectMemory(again, page * 2) == 0); } @@ -547,11 +557,11 @@ static void CheckSparseDirectMemoryBacking() { Require(aliasBytes[page - 1] == 29 && aliasBytes[0] == 0); aliasBytes[page - 1] = 41; Require(lastBytes[page - 1] == 41 && firstBytes[0] == 17); - Require(sceKernelMunmap(last, page) == 0 && sceKernelMunmap(alias, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(last), page) == 0 && sceKernelMunmap(reinterpret_cast(alias), page) == 0); last = nullptr; Require(sceKernelMapDirectMemory(&last, page, 3, 0, physical + size - page, 0) == 0); Require(static_cast(last)[page - 1] == 41); - Require(sceKernelMunmap(first, page) == 0 && sceKernelMunmap(last, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(first), page) == 0 && sceKernelMunmap(reinterpret_cast(last), page) == 0); Require(sceKernelReleaseDirectMemory(physical, size) == 0); } @@ -564,7 +574,7 @@ static void CheckSavedDirectMemorySurvivesCachePressure() { void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, bytes, 3, 0, retained[index], bytes) == 0); std::memset(mapped, static_cast(index + 1), bytes); - Require(sceKernelMunmap(mapped, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), bytes) == 0); } const auto temporary = [] { constexpr std::size_t small = 0x10000; @@ -577,12 +587,12 @@ static void CheckSavedDirectMemorySurvivesCachePressure() { Require(std::all_of(data, data + small, [](unsigned char value) { return value == 0; })); data[7] = round; data[small - 1] = static_cast(round + 0x40); - Require(sceKernelMunmap(mapped, small) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), small) == 0); mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, small, 3, 0, physical, 0) == 0); data = static_cast(mapped); for (std::size_t at = 0; at < small; ++at) Require(data[at] == (at == 7 ? round : at == small - 1 ? static_cast(round + 0x40) : 0)); - Require(sceKernelMunmap(mapped, small) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), small) == 0); Require(sceKernelReleaseDirectMemory(physical, small) == 0); } }; @@ -592,7 +602,7 @@ static void CheckSavedDirectMemorySurvivesCachePressure() { Require(sceKernelMapDirectMemory(&mapped, bytes, 3, 0, retained[index], bytes) == 0); const auto* data = static_cast(mapped); Require(std::all_of(data, data + bytes, [&](unsigned char value) { return value == index + 1; })); - Require(sceKernelMunmap(mapped, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), bytes) == 0); Require(sceKernelReleaseDirectMemory(retained[index], bytes) == 0); } temporary(); @@ -623,7 +633,7 @@ static void CheckReusedDirectMemorySections() { auto physical = allocate(); auto* data = map(physical); std::memset(data, 0x5a, bytes); - Require(sceKernelMunmap(data, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(data), bytes) == 0); auto before = handles(); Require(sceKernelReleaseDirectMemory(physical, bytes) == 0); const auto released = handles(); @@ -642,12 +652,12 @@ static void CheckReusedDirectMemorySections() { Require(data[page + 3] == 41); data[page * 2] = 53; Require(alias[page * 2] == 53); - Require(sceKernelMunmap(alias, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(alias), bytes) == 0); Require(data[5] == 17 && data[page + 3] == 41 && data[page * 2] == 53); - Require(sceKernelMunmap(data, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(data), bytes) == 0); data = map(physical); Require(data[5] == 17 && data[page + 3] == 41 && data[page * 2] == 53 && data[bytes - 1] == 29); - Require(sceKernelMunmap(data, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(data), bytes) == 0); before = handles(); Require(sceKernelReleaseDirectMemory(physical, bytes) == 0); Require(handles() == before - 1); @@ -660,9 +670,9 @@ static void CheckReusedDirectMemorySections() { second = nullptr; Require(sceKernelMapDirectMemory(&second, page, 3, 0, physical + static_cast(page), 0) == 0); Require(static_cast(second)[page - 1] == round); - Require(sceKernelMunmap(second, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(second), page) == 0); } - Require(sceKernelMunmap(data, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(data), bytes) == 0); Require(sceKernelReleaseDirectMemory(physical, bytes) == 0); } #endif @@ -714,7 +724,7 @@ static void CheckFixedMappingReplacesPartialOverlap() { Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); void* reserved = nullptr; Require(sceKernelMapDirectMemory(&reserved, page * 3, 3, 0, phys, 0) == 0); - Require(sceKernelMunmap(reserved, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page * 3) == 0); void* head = reserved; Require(sceKernelMapDirectMemory(&head, page, 3, 0x10, phys, 0) == 0 && head == reserved); void* fixed = reserved; @@ -725,7 +735,7 @@ static void CheckFixedMappingReplacesPartialOverlap() { Require(sceKernelVirtualQuery(fixed, 0, &info, sizeof(info)) == 0); Require(info.is_direct && info.offset == static_cast(phys)); Require(static_cast(fixed)[page * 2 + 7] == 0x5c); - Require(sceKernelMunmap(fixed, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(fixed), page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } @@ -738,14 +748,14 @@ static void CheckFixedMappingOverHoleInsideAllocation() { auto* bytes = static_cast(mapped); bytes[7] = 0x11; bytes[page * 3 + 7] = 0x44; - Require(sceKernelMunmap(bytes + page, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(bytes + page), page) == 0); void* fixed = bytes + page; Require(sceKernelMapDirectMemory(&fixed, page * 2, 3, 0x10, phys + static_cast(page), 0) == 0); Require(fixed == bytes + page); bytes[page * 2 + 7] = 0x33; Require(bytes[7] == 0x11 && bytes[page * 2 + 7] == 0x33 && bytes[page * 3 + 7] == 0x44); - Require(sceKernelMunmap(bytes + page * 2, page * 2) == 0); - Require(sceKernelMunmap(mapped, page * 4) == 0); + Require(sceKernelMunmap(reinterpret_cast(bytes + page * 2), page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 4) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 4) == 0); } @@ -846,7 +856,7 @@ static void CheckMtypeprotect() { std::int64_t start = -1; std::int64_t end = -1; Require(sceKernelGetDirectMemoryType(phys + static_cast(page * 2), &type, &start, &end) == 0 && type == 2); - Require(sceKernelMunmap(mapped, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } @@ -858,7 +868,7 @@ static void CheckDirectMemoryGpuProtBits() { Require(sceKernelMapDirectMemory(&mapping, page, 0x3f2, 0, phys, 0) == 0); static_cast(mapping)[0] = 11; Require(static_cast(mapping)[0] == 11); - Require(sceKernelMunmap(mapping, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapping), page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } @@ -892,16 +902,16 @@ static void CheckGpuAccessFollowsProtection() { const auto image = std::find_if(lease.begin(), lease.end(), [&](const auto& range) { return probe >= range->address && probe - range->address < range->bytes; }); Require(image != lease.end() && !(*image)->releasable && !(*image)->gpu); } - Require(sceKernelMunmap(cpu, page) == 0); - Require(sceKernelMunmap(gpu, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(cpu), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(gpu), page) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); void* flexible = nullptr; Require(sceKernelMapNamedFlexibleMemory(&flexible, page, 3, 0, "cpu") == 0); Require(!GpuMapped(flexible)); - Require(sceKernelMunmap(flexible, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(flexible), page) == 0); Require(sceKernelMapNamedFlexibleMemory(&flexible, page, 0x33, 0, "gpu") == 0); Require(GpuMapped(flexible)); - Require(sceKernelMunmap(flexible, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(flexible), page) == 0); } static std::vector> gpuMapped; @@ -924,7 +934,7 @@ static void CheckClearingTheObserverWaitsForItsCalls() { GuestAllocations::GuestAllocationsSetGpuMapObserver_nid_postfix(nullptr); Require(observerDone); mapper.join(); - Require(sceKernelMunmap(gpu, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(gpu), page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } @@ -964,10 +974,10 @@ static void CheckGpuMapsAreObserved() { Require(sceKernelMapDirectMemory(&gpuRemap, page, 0x32, 0x10, remapPhys + static_cast(page), 0) == 0 && gpuRemap == gpuTarget); Require(gpuMapped.size() == 2 && gpuMapped.back().first == reinterpret_cast(gpuTarget) && gpuMapped.back().second == page); GuestAllocations::GuestAllocationsSetGpuMapObserver_nid_postfix(nullptr); - Require(sceKernelMunmap(reserved, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page * 2) == 0); Require(sceKernelReleaseDirectMemory(remapPhys, page * 2) == 0); - Require(sceKernelMunmap(cpu, page) == 0); - Require(sceKernelMunmap(gpu, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(cpu), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(gpu), page) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); } @@ -975,7 +985,7 @@ static void CheckHintedVirtualReservation() { constexpr std::size_t page = 0x4000; void* probe = nullptr; Require(sceKernelReserveVirtualRange(&probe, page * 8, 0, 0) == 0); - Require(sceKernelMunmap(probe, page * 8) == 0); + Require(sceKernelMunmap(reinterpret_cast(probe), page * 8) == 0); void* const hint = static_cast(probe) + page * 2; void* placed = hint; Require(sceKernelReserveVirtualRange(&placed, page * 2, 0, 0) == 0); @@ -983,8 +993,8 @@ static void CheckHintedVirtualReservation() { void* above = hint; Require(sceKernelReserveVirtualRange(&above, page * 2, 0, 0) == 0); Require(above > hint); - Require(sceKernelMunmap(above, page * 2) == 0); - Require(sceKernelMunmap(placed, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(above), page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(placed), page * 2) == 0); } static void CheckReservationHintWithoutRoomAbove() { @@ -1000,15 +1010,15 @@ static void CheckReservationHintWithoutRoomAbove() { void* belowTheHint = lastPage; Require(sceKernelReserveVirtualRange(&belowTheHint, page * 2, 0, 0) == 0); Require(belowTheHint < lastPage && GuestArena::GuestArenaContains_nid_postfix(belowTheHint, page * 2)); - Require(sceKernelMunmap(belowTheHint, page * 2) == 0); - Require(sceKernelMunmap(aboveTheArena, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(belowTheHint), page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(aboveTheArena), page * 2) == 0); } static void CheckFixedVirtualReservation() { constexpr std::size_t page = 0x4000; void* probe = nullptr; Require(sceKernelReserveVirtualRange(&probe, page * 4, 0, 0) == 0); - Require(sceKernelMunmap(probe, page * 4) == 0); + Require(sceKernelMunmap(reinterpret_cast(probe), page * 4) == 0); void* const requested = static_cast(probe) + page; void* fixed = requested; Require(sceKernelReserveVirtualRange(&fixed, page * 2, 0x400010, 0) == 0); @@ -1040,16 +1050,17 @@ static void CheckFixedVirtualReservation() { bool refused = false; try { sceKernelReserveVirtualRange(&reserved, page * 2, 0x90, 0); + } catch (const std::exception&) { refused = true; } Require(refused); - Require(sceKernelMunmap(reserved, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); void* pooled = nullptr; Require(sceKernelMemoryPoolReserve(requested, page * 2, 0, 0x10, &pooled) == 0); Require(pooled == requested); - Require(sceKernelMunmap(pooled, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(pooled), page * 2) == 0); } static void CheckHintInsideReservation() { @@ -1078,8 +1089,8 @@ static void CheckHintInsideReservation() { Require(sceKernelMapDirectMemory(&overlapping, page * 2, 3, 0, phys + page, 0) == 0); Require(overlapping != base + page); Require(static_cast(direct)[0] == 21 && static_cast(flexible)[0] == 22); - Require(sceKernelMunmap(overlapping, page * 2) == 0); - Require(sceKernelMunmap(reserved, page * 4) == 0); + Require(sceKernelMunmap(reinterpret_cast(overlapping), page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page * 4) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } @@ -1104,7 +1115,7 @@ static void CheckReservedRangeIsNotCommitted() { Require(sceKernelVirtualQuery(static_cast(reserved) + page, 0, &info, sizeof(info)) == 0); Require(!info.is_committed && info.protection == 0); Require(info.start == start + page && info.end == start + page * 2); - Require(sceKernelMunmap(reserved, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } @@ -1125,7 +1136,7 @@ static void CheckNoOverwriteRefusesLiveMapping() { VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0); Require(info.is_direct && info.offset == static_cast(phys)); - Require(sceKernelMunmap(mapped, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); } @@ -1139,7 +1150,7 @@ static void CheckNoOverwriteRejectsHostOccupiedMapping() { constexpr std::size_t page = 0x4000; void* reservation = nullptr; Require(sceKernelReserveVirtualRange(&reservation, page * 4, 0, 0) == 0); - Require(sceKernelMunmap(reservation, page * 4) == 0); + Require(sceKernelMunmap(reinterpret_cast(reservation), page * 4) == 0); void* target = static_cast(reservation) + page; GuestArena::GuestArenaCommit_nid_postfix(target, page, ReadWriteProtection, page); std::int64_t phys = 0; @@ -1200,6 +1211,7 @@ static void CheckGuestModuleImageProtection() { bool failed = false; try { dlclose_nid_postfix(module); + } catch (const std::runtime_error&) { failed = true; } @@ -1240,6 +1252,7 @@ static void CheckGuestModuleImageProtection() { bool rejected = false; try { sceKernelMprotect(GetModuleHandleA("kernel32.dll"), page, 1); + } catch (const std::invalid_argument&) { rejected = true; } @@ -1250,6 +1263,7 @@ static void CheckGuestModuleImageProtection() { const auto libkernel = GetModuleHandleA("libkernel.prx"); Require(libkernel != nullptr); sceKernelMprotect(libkernel, page, 1); + } catch (const std::invalid_argument&) { rejected = true; } @@ -1277,6 +1291,7 @@ static void CheckGuestModuleImageProtection() { rejected = false; try { sceKernelMprotect(plainTarget, page, 1); + } catch (const std::invalid_argument&) { rejected = true; } @@ -1327,6 +1342,7 @@ static void CheckGuestModuleImageProtection() { bool rejected = false; try { sceKernelMprotect(reinterpret_cast(&::getpid), page, 1); + } catch (const std::invalid_argument&) { rejected = true; } @@ -1354,8 +1370,8 @@ static void CheckUnhintedKernelMappingsLandInTheWindow() { const auto flexibleAddress = reinterpret_cast(flexible); Require(flexibleAddress >= windowStart); Require(flexibleAddress + page <= windowEnd); - Require(sceKernelMunmap(direct, page) == 0); - Require(sceKernelMunmap(flexible, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(direct), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(flexible), page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } @@ -1365,13 +1381,13 @@ static void CheckUnmappedArenaRangeStaysReserved() { void* address = nullptr; Require(sceKernelMapNamedFlexibleMemory(&address, page, 3, 0, "reserved-after-unmap") == 0); Require(address != nullptr); - Require(sceKernelMunmap(address, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(address), page) == 0); void* hostile = mmap(address, page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); Require(hostile == MAP_FAILED && errno == EEXIST); void* again = nullptr; Require(sceKernelMapNamedFlexibleMemory(&again, page, 3, 0, "reserved-after-unmap-2") == 0); Require(again != nullptr); - Require(sceKernelMunmap(again, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(again), page) == 0); } #endif @@ -1424,7 +1440,13 @@ static void CheckFixedMappingsReachTheApplicationAreaEnd() { 0; #endif constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull + expectedOffset; - Require(base == 0x200000000ull + expectedOffset && base + size == applicationAreaEnd); + constexpr std::uintptr_t expectedBase = +#ifdef __APPLE__ + 0x200000000ull + expectedOffset; +#else + expectedOffset == 0 ? 0x10000ull : 0x200000000ull + expectedOffset; +#endif + Require(base == expectedBase && base + size == applicationAreaEnd); std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* const requested = reinterpret_cast(applicationAreaEnd - page * 2); @@ -1445,8 +1467,8 @@ static void CheckFixedMappingsReachTheApplicationAreaEnd() { refused = true; } Require(refused && beyond == reinterpret_cast(applicationAreaEnd)); - Require(sceKernelMunmap(alias, page) == 0); - Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(alias), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); } #ifdef _WIN32 @@ -1472,7 +1494,7 @@ static void CheckAliasOfReorderedSharedPages() { Require(bytes[6] == 0x33 && bytes[page + 6] == 0x44); GuestArena::GuestArenaUnmapAlias_nid_postfix(const_cast(alias)); Require(bytes[5] == 0x22 && bytes[page + 5] == 0x11); - Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); } @@ -1503,7 +1525,7 @@ static void CheckAliasAcrossDirectAllocations() { Require(VirtualQuery(alias + offset, &released, sizeof(released)) == sizeof(released) && released.State == MEM_FREE); } Require(bytes[5] == 0x11 && bytes[page + 5] == 0x22 && bytes[page * 2 + 5] == 0x33); - Require(sceKernelMunmap(mapped, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 3) == 0); Require(sceKernelReleaseDirectMemory(first, page * 3) == 0); } @@ -1525,7 +1547,7 @@ static void CheckAliasLeavesResidentRunsUnderAHostWrite() { Require(alias != nullptr && alias[5] == 0x11 && alias[page + 5] == 0x22); GuestArena::GuestArenaUnmapAlias_nid_postfix(const_cast(alias)); Require(bytes[5] == 0x11 && bytes[page + 5] == 0x22); - Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); } @@ -1542,7 +1564,7 @@ static void CheckAliasRefusesPagesThatAreNotSharedViews() { bool refused = false; try { GuestArena::GuestArenaMapAlias_nid_postfix(reinterpret_cast(reserved), page * 2); } catch (const std::runtime_error&) { refused = true; } Require(refused); - Require(sceKernelMunmap(reserved, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } #endif @@ -1594,8 +1616,8 @@ static void CheckMlock() { #ifndef ANYPS5_ENABLE_ASAN Require(sceKernelMlock_nid_postfix(reserved, page) == outOfMemory); #endif - Require(sceKernelMunmap(reserved, page) == 0); - Require(sceKernelMunmap(mapped, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), length) == 0); #ifndef ANYPS5_ENABLE_ASAN Require(sceKernelMlock_nid_postfix(mapped, page) == outOfMemory); #endif @@ -1623,8 +1645,8 @@ static void CheckSharedDirectMemoryLifecycle() { left[page] = 48; Require(sceKernelMprotect(inaccessible, page, 1) == 0); Require(static_cast(inaccessible)[0] == 48); - Require(sceKernelMunmap(inaccessible, page) == 0); - Require(sceKernelMunmap(left + page, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(inaccessible), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(left + page), page) == 0); right[page] = 79; Require(left[0] == 31 && left[page * 2] == 63); void* middle = left + page; @@ -1636,9 +1658,9 @@ static void CheckSharedDirectMemoryLifecycle() { left[page] = 95; Require(sceKernelMprotect(second, page * 3, 1) == 0); Require(right[page] == 95); - Require(sceKernelMunmap(first, page) == 0); - Require(sceKernelMunmap(left + page * 2, page) == 0); - Require(sceKernelMunmap(middle, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(first), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(left + page * 2), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(middle), page) == 0); Require(sceKernelMprotect(second, page * 3, 3) == 0); right[page * 2] = 111; void* reserved = nullptr; @@ -1654,8 +1676,8 @@ static void CheckSharedDirectMemoryLifecycle() { Require(!info.is_direct && info.is_flexible && info.offset == 0); static_cast(fixed)[0] = 143; Require(right[page * 2] == 127); - Require(sceKernelMunmap(reserved, page * 3) == 0); - Require(sceKernelMunmap(second, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(second), page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys + page, page) == 0); std::int64_t replacement = 0; Require(sceKernelAllocateDirectMemory(phys + page, phys + page * 2, page, 0, 0, &replacement) == 0); @@ -1664,7 +1686,7 @@ static void CheckSharedDirectMemoryLifecycle() { Require(sceKernelMapDirectMemory(&mixed, page * 3, 3, 0, phys, 0) == 0); const auto* data = static_cast(mixed); Require(data[0] == 31 && data[page] == 0 && data[page * 2] == 127); - Require(sceKernelMunmap(mixed, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(mixed), page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } @@ -1731,7 +1753,7 @@ static void CheckSingleDirectMappingTracksWritesWithoutFaults() { Require(g_accessViolations == 0); Require(collect(mapped, page * 4) == 2); Require(collect(mapped, page * 4) == 0); - Require(sceKernelMunmap(static_cast(mapped) + page * 2, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(static_cast(mapped) + page * 2), page) == 0); Require(bytes[page + 0x1000] == 7 && bytes[page * 3] == 9); bytes[0] = 5; Require(collect(mapped, page * 2) != 0); @@ -1742,19 +1764,19 @@ static void CheckSingleDirectMappingTracksWritesWithoutFaults() { static_cast(second)[0x1001] = 11; Require(bytes[page + 0x1001] == 11); Require(collect(mapped, page * 2) != 0); - Require(sceKernelMunmap(second, page) == 0); - Require(sceKernelMunmap(mapped, page * 2) == 0); - Require(sceKernelMunmap(static_cast(mapped) + page * 3, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(second), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(static_cast(mapped) + page * 3), page) == 0); void* again = nullptr; Require(sceKernelMapDirectMemory(&again, page * 4, 3, 0, phys, 0) == 0); const auto* data = static_cast(again); Require(data[0] == 5 && data[page + 0x1000] == 7 && data[page + 0x1001] == 11 && data[page * 2] == 0 && data[page * 3] == 9); - Require(sceKernelMunmap(again, page * 4) == 0); + Require(sceKernelMunmap(reinterpret_cast(again), page * 4) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 4) == 0); #endif } -static void CheckLargeResidentWritesStopFaulting() { +static void CheckLargeResidentWritesDoNotFault() { #ifdef _WIN32 constexpr std::size_t bytes = 0x200000; std::int64_t physical = 0; @@ -1770,7 +1792,7 @@ static void CheckLargeResidentWritesStopFaulting() { Require(collect() == bytes / 4096); Require(collect() == 0 && collect() == 0); MEMORY_BASIC_INFORMATION memory{}; - Require(VirtualQuery(mapped, &memory, sizeof(memory)) == sizeof(memory) && memory.Protect == PAGE_READONLY); + Require(VirtualQuery(mapped, &memory, sizeof(memory)) == sizeof(memory) && memory.Protect == PAGE_READWRITE); auto* data = static_cast(mapped); const auto path = std::filesystem::path("anyps5-resident-read-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".bin"); @@ -1798,7 +1820,7 @@ static void CheckLargeResidentWritesStopFaulting() { Require(collect() == 1 && collect() == 0); } for (unsigned int round = 0; round < 16; ++round) Require(collect() == 0); - Require(VirtualQuery(mapped, &memory, sizeof(memory)) == sizeof(memory) && memory.Protect == PAGE_READONLY); + Require(VirtualQuery(mapped, &memory, sizeof(memory)) == sizeof(memory) && memory.Protect == PAGE_READWRITE); const auto before = g_accessViolations; const auto handler = AddVectoredExceptionHandler(1, CountAccessViolations); Require(handler != nullptr); @@ -1807,14 +1829,14 @@ static void CheckLargeResidentWritesStopFaulting() { Require(collect() == bytes / 4096 && collect() == 0); } Require(RemoveVectoredExceptionHandler(handler)); - Require(g_accessViolations == before + 1); + Require(g_accessViolations == before); Require(VirtualQuery(mapped, &memory, sizeof(memory)) == sizeof(memory) && memory.Protect == PAGE_READWRITE); Require(mprotect_nid_postfix(mapped, bytes, 1) == 0); Require(collect() == 0 && collect() == 0); Require(mprotect_nid_postfix(mapped, bytes, 3) == 0); data[0x10000] = 97; Require(collect() == 1 && collect() == 0); - Require(sceKernelMunmap(mapped, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), bytes) == 0); Require(sceKernelReleaseDirectMemory(physical, bytes) == 0); #endif } @@ -1850,8 +1872,8 @@ static void CheckImportAccessDoesNotInventSharedWrites() { } Require(static_cast(second)[4097] == 73); Require(collect(first) == 0 && collect(second) == 0); - Require(sceKernelMunmap(first, bytes) == 0); - Require(sceKernelMunmap(second, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(first), bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(second), bytes) == 0); Require(sceKernelReleaseDirectMemory(physical, bytes) == 0); #endif } @@ -1891,7 +1913,7 @@ static void CheckResidentCollectPreservesPartialWrites() { Require(collect(small, bytes - small, 1, true, false) == std::vector{small}); Require(collect(small * 2, bytes - small * 2, bytes / small, true, true) == std::vector{0x200000}); Require(collect(0, bytes, bytes / small, true, false).empty()); - Require(sceKernelMunmap(mapped, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), bytes) == 0); Require(sceKernelReleaseDirectMemory(physical, bytes) == 0); #endif } @@ -1953,8 +1975,8 @@ static void CheckResidentCollectionDuringHostWrites() { Require(data[small + 11] == 4); const auto pages = collect(); Require(std::find(pages.begin(), pages.end(), data + small) != pages.end()); - Require(sceKernelMunmap(alias, 0x4000) == 0); - Require(sceKernelMunmap(mapped, bytes) == 0); + Require(sceKernelMunmap(reinterpret_cast(alias), 0x4000) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), bytes) == 0); Require(sceKernelReleaseDirectMemory(physical, bytes) == 0); #endif } @@ -2034,8 +2056,8 @@ static void CheckSecondDirectMappingWaitsForLeases() { static_cast(second)[8] = 3; Require(static_cast(mapped)[page + 8] == 3); static_cast(mapped)[page * 2 + 8] = 4; - Require(sceKernelMunmap(second, page) == 0); - Require(sceKernelMunmap(mapped, page * 4) == 0); + Require(sceKernelMunmap(reinterpret_cast(second), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 4) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 4) == 0); #endif } @@ -2088,7 +2110,7 @@ static void CheckBreakingAResidentRunKeepsConcurrentAccesses() { std::array second{}; go = true; for (std::size_t index = 0; index < chunks; ++index) { - if (unmap) Require(sceKernelMunmap(static_cast(mapped) + index * chunk + page, page) == 0); + if (unmap) Require(sceKernelMunmap(reinterpret_cast(static_cast(mapped) + index * chunk + page), page) == 0); else Require(sceKernelMapDirectMemory(&second[index], page, 3, 0, phys + static_cast(index * chunk + page), 0) == 0); } writer.join(); @@ -2101,16 +2123,16 @@ static void CheckBreakingAResidentRunKeepsConcurrentAccesses() { } if (unmap) continue; for (std::size_t offset = 0; offset < page; ++offset) Require(static_cast(second[index])[offset] == value(index * chunk + page + offset)); - Require(sceKernelMunmap(second[index], page) == 0); + Require(sceKernelMunmap(reinterpret_cast(second[index]), page) == 0); } for (std::size_t index = 0; index < chunks; ++index) { auto* base = static_cast(mapped) + index * chunk; if (!unmap) { - Require(sceKernelMunmap(base, chunk) == 0); + Require(sceKernelMunmap(reinterpret_cast(base), chunk) == 0); continue; } - Require(sceKernelMunmap(base, page) == 0); - Require(sceKernelMunmap(base + page * 2, chunk - page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(base), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(base + page * 2), chunk - page * 2) == 0); } Require(sceKernelReleaseDirectMemory(phys, chunk * chunks) == 0); } @@ -2121,30 +2143,30 @@ static void CheckRemappedDirectMemoryIsChargedOnce() { #ifdef _WIN32 constexpr std::size_t bytes = 0x10000000; const auto committed = [] { - PERFORMANCE_INFORMATION info{}; - info.cb = sizeof(info); - Require(K32GetPerformanceInfo(&info, sizeof(info)) != 0); - return static_cast(info.CommitTotal) * static_cast(info.PageSize); + struct { + PROCESS_MEMORY_COUNTERS_EX counters; + SIZE_T privateWorkingSetSize; + ULONG64 sharedCommitUsage; + } info{}; + info.counters.cb = sizeof(info); + Require(K32GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&info), sizeof(info)) != 0); + return static_cast(info.counters.PrivateUsage) + static_cast(info.sharedCommitUsage); }; - bool once = false; - for (int attempt = 0; attempt < 3 && !once; ++attempt) { - const auto before = committed(); - std::int64_t phys = 0; - Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, 0, 0, &phys) == 0); - void* mapped = nullptr; - Require(sceKernelMapDirectMemory(&mapped, bytes, 3, 0, phys, 0) == 0); - RequireResidentMapping(mapped); - std::memset(mapped, 0xa5, bytes); - Require(sceKernelMunmap(mapped, bytes) == 0); - void* again = nullptr; - Require(sceKernelMapDirectMemory(&again, bytes, 3, 0, phys, 0) == 0); - const auto* data = static_cast(again); - Require(data[0] == 0xa5 && data[bytes / 2] == 0xa5 && data[bytes - 1] == 0xa5); - once = committed() - before < static_cast(bytes + bytes / 2); - Require(sceKernelMunmap(again, bytes) == 0); - Require(sceKernelReleaseDirectMemory(phys, bytes) == 0); - } - Require(once); + const auto before = committed(); + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, 0, 0, &phys) == 0); + void* mapped = nullptr; + Require(sceKernelMapDirectMemory(&mapped, bytes, 3, 0, phys, 0) == 0); + RequireResidentMapping(mapped); + std::memset(mapped, 0xa5, bytes); + Require(sceKernelMunmap(reinterpret_cast(mapped), bytes) == 0); + void* again = nullptr; + Require(sceKernelMapDirectMemory(&again, bytes, 3, 0, phys, 0) == 0); + const auto* data = static_cast(again); + Require(data[0] == 0xa5 && data[bytes / 2] == 0xa5 && data[bytes - 1] == 0xa5); + Require(committed() - before < static_cast(bytes + bytes / 2)); + Require(sceKernelMunmap(reinterpret_cast(again), bytes) == 0); + Require(sceKernelReleaseDirectMemory(phys, bytes) == 0); #endif } @@ -2195,9 +2217,9 @@ static void CheckSharedWriteTracking() { collect(second, page * 3); static_cast(third)[0] = 105; Require(collect(first, page * 3) == 1 && collect(second, page * 3) == 1); - Require(sceKernelMunmap(third, page) == 0); - Require(sceKernelMunmap(first, page * 3) == 0); - Require(sceKernelMunmap(second, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(third), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(first), page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(second), page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); #endif } @@ -2240,7 +2262,7 @@ static void CheckSharedWritePages() { Require(bytes[page + small] == 5 && bytes[page + small * 2 - 1] == 5); Require(collect(page, small) == 0); Require(collect(0, page * 2) == 1); - Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); std::filesystem::remove(path); #endif @@ -2283,8 +2305,8 @@ static void CheckSharedWritesDuringHostWrite() { Require(collect(first).empty() && collect(second).empty()); left[small + 16] = 3; Require(right[small + 16] == 3 && collect(second) == std::vector{small}); - Require(sceKernelMunmap(first, page) == 0); - Require(sceKernelMunmap(second, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(first), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(second), page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); #endif } @@ -2317,7 +2339,7 @@ static void CheckFailedCollectKeepsWrites() { Require(sceKernelMprotect(const_cast(shared + page), page, 0) == 0); Require(!collect(mapped, page * 2, count)); Require(collect(mapped, page, count) && count == 1); - Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); #endif } @@ -2399,8 +2421,8 @@ static void CheckPinnedSharedPages() { Require(protection(page) == PAGE_READONLY); bytes[page + 8] = 11; Require(collect() == 1); - Require(sceKernelMunmap(alias, page * 3) == 0); - Require(sceKernelMunmap(mapped, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(alias), page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); #endif } @@ -2444,7 +2466,7 @@ static void CheckReadsIntoSharedWriteTracking() { KernelAioRwRequest request{static_cast(page), page, bytes, &result, fd}; std::int32_t id = 0; std::int32_t state = 0; - Require(sceKernelAioSubmitReadCommands(&request, 1, 0, &id) == 0); + Require(sceKernelAioSubmitReadCommands(&request, 1, 1, &id) == 0); Require(sceKernelAioWaitRequest(id, &state, nullptr) == 0); Require(result.state == 3 && result.return_value == static_cast(page)); Require(std::memcmp(bytes, seed.data() + page, page) == 0); @@ -2455,12 +2477,12 @@ static void CheckReadsIntoSharedWriteTracking() { Require(sceKernelPread(fd, mapped, page, 0) == static_cast(SCE_KERNEL_ERROR_EFAULT)); KernelAioResult refusedResult{-1, 0}; KernelAioRwRequest refusedRequest{0, page, bytes, &refusedResult, fd}; - Require(sceKernelAioSubmitReadCommands(&refusedRequest, 1, 0, &id) == 0); + Require(sceKernelAioSubmitReadCommands(&refusedRequest, 1, 1, &id) == 0); Require(sceKernelAioWaitRequest(id, &state, nullptr) == 0); Require(refusedResult.return_value == static_cast(SCE_KERNEL_ERROR_EFAULT)); Require(sceKernelAioDeleteRequest(id, &deleted) == 0); Require(sceKernelClose(fd) == 0); - Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); std::filesystem::remove(path); #endif @@ -2515,7 +2537,7 @@ static void CheckWriteWatchArmedCollect() { Require(CollectArmedRuns(mapping, 0, length, runs) && runs == PageRuns{{5, 6}}); Require(Written(mapping, length, {{length / small / 2, length / small}})); Require(Written(mapping, length, {})); - Require(sceKernelMunmap(mapping, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapping), length) == 0); } static bool CollectFreshRuns(const void* base, std::size_t offset, std::size_t bytes, PageRuns& runs) { @@ -2543,7 +2565,7 @@ static void CheckWriteWatchFreshCollect() { Require(CollectFreshRuns(mapping, 0, length, runs) && runs.empty()); bytes[200 * small] = 1; Require(Written(mapping, length, {{5, 6}, {200, 201}})); - Require(sceKernelMunmap(mapping, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapping), length) == 0); } static void CheckWriteWatch() { @@ -2593,7 +2615,7 @@ static void CheckWriteWatch() { Require(Written(mapping, length, {{50, 51}})); constexpr std::size_t guestPage = 0x4000; auto* middle = const_cast(bytes + 4 * guestPage); - Require(sceKernelMunmap(middle, guestPage) == 0); + Require(sceKernelMunmap(reinterpret_cast(middle), guestPage) == 0); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, length)); Require(!CollectRuns(mapping, 0, length, runs) && runs.empty()); void* fixed = middle; @@ -2603,7 +2625,7 @@ static void CheckWriteWatch() { Require(Written(mapping, length, {})); middle[1] = 1; Require(Written(mapping, length, {{16, 17}})); - Require(sceKernelMunmap(mapping, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapping), length) == 0); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, small)); constexpr std::size_t tableSpan = 0x200000; constexpr std::size_t spanned = 2 * tableSpan; @@ -2643,7 +2665,7 @@ static void CheckDirectMemoryBackingNeedsNoFilesystem() { const auto backing = BackingOf(mapped); if (backing.rfind("/memfd:", 0) != 0) std::fprintf(stderr, "direct memory backing: %s\n", backing.c_str()); Require(backing.rfind("/memfd:", 0) == 0); - Require(sceKernelMunmap(mapped, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } @@ -2686,10 +2708,10 @@ static void CheckDirectMemorySharedBacking() { Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page * 2, &file, &offset)); Require(GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress + page, page, &file, &offset) && offset == base + page * 2); close(file); - Require(sceKernelMunmap(high, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(high), page) == 0); Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress + page, page, &file, &offset)); - Require(sceKernelMunmap(flexible, page) == 0); - Require(sceKernelMunmap(first, page * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(flexible), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(first), page * 2) == 0); Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, page, &file, &offset)); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); @@ -2713,15 +2735,15 @@ static void CheckDirectMemorySharedBacking() { close(leftFile); close(rightFile); std::memset(leftView, 0x77, page); - Require(sceKernelMunmap(leftView, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(leftView), page) == 0); Require(sceKernelReleaseDirectMemory(left, page) == 0); std::int64_t again = 0; Require(sceKernelAllocateDirectMemory(left, left + static_cast(page), page, 0, 0, &again) == 0 && again == left); void* againView = nullptr; Require(sceKernelMapDirectMemory(&againView, page, 3, 0, again, 0) == 0); for (std::size_t i = 0; i < page; ++i) Require(static_cast(againView)[i] == 0); - Require(sceKernelMunmap(againView, page) == 0); - Require(sceKernelMunmap(rightView, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(againView), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(rightView), page) == 0); Require(sceKernelReleaseDirectMemory(again, page) == 0); Require(sceKernelReleaseDirectMemory(right, page) == 0); } @@ -2757,8 +2779,8 @@ static void CheckDirectMemoryWriteWatch() { Require(bytes[page + 7] == 3); Require(CollectRuns(first, 0, page, runs) && runs == PageRuns{{0, 1}}); Require(CollectRuns(first, page * 2, page, runs) && runs.empty()); - Require(sceKernelMunmap(alias, page) == 0); - Require(sceKernelMunmap(first, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(alias), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(first), page * 3) == 0); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, page)); void* flexible = nullptr; Require(sceKernelMapFlexibleMemory(&flexible, page * 3, 3, 0) == 0); @@ -2772,7 +2794,7 @@ static void CheckDirectMemoryWriteWatch() { Require(Written(flexible, page * 3, {})); static_cast(fixed)[9] = 4; Require(Written(flexible, page * 3, {{page / small, page / small + 1}})); - Require(sceKernelMunmap(flexible, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(flexible), page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } @@ -2792,12 +2814,12 @@ static void CheckDirectMemoryWriteWatchFollowsRemovedMappings() { Require(sceKernelMapDirectMemory(&pair, page * 2, 3, 0, phys + page, 0) == 0); Require(watched(whole, 0) && !watched(whole, page) && !watched(whole, page * 2) && watched(whole, page * 3)); Require(!watched(pair, 0) && !watched(pair, page)); - Require(sceKernelMunmap(pair, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(pair), page) == 0); void* single = nullptr; Require(sceKernelMapDirectMemory(&single, page, 3, 0, phys + page, 0) == 0); Require(!watched(single, 0) && watched(whole, 0) && watched(whole, page * 3)); - Require(sceKernelMunmap(single, page) == 0); - Require(sceKernelMunmap(static_cast(pair) + page, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(single), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(static_cast(pair) + page), page) == 0); void* replaced = whole; Require(sceKernelMapDirectMemory(&replaced, page, 3, 0x10, phys + page * 3, 0) == 0 && replaced == whole); Require(!watched(whole, 0) && !watched(whole, page * 3)); @@ -2812,9 +2834,9 @@ static void CheckDirectMemoryWriteWatchFollowsRemovedMappings() { void* again = nullptr; Require(sceKernelMapDirectMemory(&again, page, 3, 0, phys, 0) == 0); Require(!watched(alone, 0) && !watched(again, 0)); - Require(sceKernelMunmap(again, page) == 0); - Require(sceKernelMunmap(alone, page) == 0); - Require(sceKernelMunmap(whole, page * 4) == 0); + Require(sceKernelMunmap(reinterpret_cast(again), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(alone), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(whole), page * 4) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 4) == 0); } @@ -2837,7 +2859,7 @@ static double MapAcrossBackingsSeconds(std::int64_t phys, std::size_t backings) std::uint64_t offset = 0; int type = 0; Require(QueryDirectMapping(last, &start, &end, &offset, &type) && start == last && end == last + page); - Require(sceKernelMunmap(mapped, page * backings) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page * backings) == 0); } return best; } @@ -2978,7 +3000,7 @@ static void CheckVirtualQuerySplitFlexibleRanges() { Require(info.protection == (index == 1 ? 1 : 3)); Require(info.is_committed && info.is_flexible && !info.is_direct); } - Require(sceKernelMunmap(bytes + page, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(bytes + page), page) == 0); for (std::size_t index : {std::size_t{0}, std::size_t{2}}) { VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(bytes + page * index, 0, &info, sizeof(info)) == 0); @@ -2990,8 +3012,8 @@ static void CheckVirtualQuerySplitFlexibleRanges() { Require(next.start == reinterpret_cast(bytes + page * 2)); Require(next.end == next.start + page && next.protection == 3); Require(bytes[0] == 17 && bytes[page * 2] == 29); - Require(sceKernelMunmap(bytes, page) == 0); - Require(sceKernelMunmap(bytes + page * 2, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(bytes), page) == 0); + Require(sceKernelMunmap(reinterpret_cast(bytes + page * 2), page) == 0); } static void CheckVirtualQueryWireLayout() { @@ -3021,7 +3043,7 @@ static void CheckVirtualQueryWireLayout() { Require(std::all_of(result.before.begin(), result.before.end(), [](auto byte) { return byte == 0xa5; })); Require(std::all_of(result.after.begin(), result.after.end(), [](auto byte) { return byte == 0x5a; })); Require(sceKernelClearVirtualRangeName(mapped, page) == 0); - Require(sceKernelMunmap(mapped, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page) == 0); } static void CheckVirtualQueryForNonReadableGuestRange() { @@ -3034,7 +3056,7 @@ static void CheckVirtualQueryForNonReadableGuestRange() { Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0); Require(info.start == reinterpret_cast(mapped)); Require(info.end == info.start + page && info.protection == 0); - Require(sceKernelMunmap(mapped, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page) == 0); } static void CheckPosixFixedAnonymousMapping() { @@ -3070,7 +3092,7 @@ static void CheckPosixFixedAnonymousMapping() { memory[page] = 0x51; memory[page * 2 - 1] = 0x52; memory[page * 2] = 0x61; - Require(sceKernelMunmap(memory + page * 3, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(memory + page * 3), page) == 0); Require(mmap_nid_postfix(memory + page * 3, page, 3, exclusive, -1, 0) == memory + page * 3); Require(std::all_of(memory + page * 3, memory + page * 4, [](unsigned char value) { return value == 0; })); memory[page * 4 - 1] = 0x62; @@ -3123,7 +3145,7 @@ static void CheckPosixFixedAnonymousReleasesFlexibleAccounting() { Require(mmap_nid_postfix(bytes + page, page, 3, fixed, -1, 0) == bytes + page); Require(bytes[0] == 0x31 && bytes[page] == 0 && bytes[page * 2] == 0x51); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 2); - Require(sceKernelMunmap(base, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(base), page * 3) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); } @@ -3152,7 +3174,7 @@ static void CheckPosixFixedFileReleasesFlexibleAccounting() { Require(mmap_nid_postfix(bytes + page, page, 1, fixedShared, fd, 0) == bytes + page); Require(bytes[0] == 0x31 && bytes[page] == 0x6d && bytes[page * 2] == 0x51); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 2); - Require(sceKernelMunmap(base, page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(base), page * 3) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); Require(sceKernelClose(fd) == 0); std::filesystem::remove(path); @@ -3165,7 +3187,7 @@ static void CheckSceKernelMmap() { Require(sceKernelMmap(nullptr, page, 3, 0x1002, -1, 0, &mapped) == 0 && mapped != nullptr); static_cast(mapped)[page - 1] = 7; Require(static_cast(mapped)[page - 1] == 7); - Require(sceKernelMunmap(mapped, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page) == 0); void* untouched = &mapped; Require(sceKernelMmap(nullptr, 0, 3, 0x1002, -1, 0, &untouched) == static_cast(0x80020016u)); Require(untouched == &mapped); @@ -3200,7 +3222,6 @@ static void CheckVirtualQueryPartialMunmap() { Require(munmap_nid_postfix(memory + page * 2, page) == 0); } - static void CheckAvailableDirectMemoryIsLargestFreeBlock() { constexpr std::int64_t window = 0x4000000; const std::int64_t top = static_cast(DIRECT_MEMORY_SIZE) - window; @@ -3220,12 +3241,43 @@ static void CheckAvailableDirectMemoryIsLargestFreeBlock() { Require(start == top + 0x200000 && size == 0x400000); Require(sceKernelAvailableDirectMemorySize(top, top + window, 0x1000000, &start, &size) == 0); Require(start == top + 0x1000000 && size == static_cast(window - 0x1000000)); - Require(sceKernelAvailableDirectMemorySize(top, top + 0x200000, 0, &start, &size) == SCE_KERNEL_ERROR_ENOMEM); - Require(start == 0 && size == 0); - Require(sceKernelAvailableDirectMemorySize(top + window, top, 0, &start, &size) == SCE_KERNEL_ERROR_EAGAIN); - Require(sceKernelAvailableDirectMemorySize(-1, top + window, 0, &start, &size) == SCE_KERNEL_ERROR_EAGAIN); - Require(sceKernelAvailableDirectMemorySize(top, top + window, std::numeric_limits::max(), &start, &size) == SCE_KERNEL_ERROR_EAGAIN); - Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, nullptr, &size) == SCE_KERNEL_ERROR_EINVAL); + const std::int64_t run = top + 0x800000; + const auto untouched = [&](std::int64_t searchStart, std::int64_t searchEnd, std::size_t alignment, int expected) { + start = -7; + size = 7; + return sceKernelAvailableDirectMemorySize(searchStart, searchEnd, alignment, &start, &size) == expected && start == -7 && size == 7; + }; + Require(untouched(top, top + 0x200000, 0, SCE_KERNEL_ERROR_ENOMEM)); + Require(untouched(top + window, top, 0, SCE_KERNEL_ERROR_ENOMEM)); + Require(untouched(run, run, 0, SCE_KERNEL_ERROR_ENOMEM)); + Require(untouched(0, -1, 0, SCE_KERNEL_ERROR_ENOMEM)); + Require(untouched(run + 0x1000, run + 0x3000, 0, SCE_KERNEL_ERROR_ENOMEM)); + Require(untouched(static_cast(DIRECT_MEMORY_SIZE), std::numeric_limits::max(), 0, SCE_KERNEL_ERROR_ENOMEM)); + Require(untouched(std::numeric_limits::max() - 0x3ffe, std::numeric_limits::max(), 0, SCE_KERNEL_ERROR_ENOMEM)); + Require(untouched(std::numeric_limits::max(), run + 0x100000, 0, SCE_KERNEL_ERROR_ENOMEM)); + Require(untouched(run, std::numeric_limits::max(), std::size_t{1} << 62, SCE_KERNEL_ERROR_ENOMEM)); + for (const std::size_t alignment : {std::size_t{3}, std::size_t{0x6000}, std::size_t{0xc000}, std::size_t{0x14000}, ~std::size_t{0x3fff}, std::numeric_limits::max()}) { + Require(untouched(top, top + window, alignment, SCE_KERNEL_ERROR_EINVAL)); + Require(untouched(top + window, top, alignment, SCE_KERNEL_ERROR_EINVAL)); + } + Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, nullptr, &size) == 0 && size == static_cast(window - 0x800000)); + Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, &start, nullptr) == 0 && start == run); + Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, nullptr, nullptr) == 0); + Require(sceKernelAvailableDirectMemorySize(top, top + window, 3, nullptr, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelAvailableDirectMemorySize(-1, top + window, 0, &start, &size) == 0 && start >= 0 && static_cast(size) <= top + window - start); + Require(sceKernelAvailableDirectMemorySize(std::numeric_limits::min(), top + window, 0, &start, &size) == 0 && start >= 0); + Require(sceKernelAvailableDirectMemorySize(run, run + 0xfffff, 0, &start, &size) == 0 && start == run && size == 0xfffff); + Require(sceKernelAvailableDirectMemorySize(run, run + 0x3fff, 0, &start, &size) == 0 && start == run && size == 0x3fff); + Require(sceKernelAvailableDirectMemorySize(run + 1, run + 0x100000, 0, &start, &size) == 0 && start == run + 0x4000 && size == 0xfc000); + Require(sceKernelAvailableDirectMemorySize(run + 0x2000, run + 0x6000, 0, &start, &size) == 0 && start == run + 0x4000 && size == 0x2000); + Require(sceKernelAvailableDirectMemorySize(run + 0x2000, run + 0x10e000, 1, &start, &size) == 0 && start == run + 0x4000 && size == 0x10a000); + Require(sceKernelAvailableDirectMemorySize(run + 0x4000, run + 0x100000, 0x8000, &start, &size) == 0 && start == run + 0x8000 && size == 0xf8000); + Require(sceKernelAvailableDirectMemorySize(top, top + window, std::size_t{1} << 63, &start, &size) == 0); + Require(start == std::numeric_limits::min() && size == static_cast(top + window) - (std::size_t{1} << 63)); + std::int64_t divider = -1; + Require(sceKernelAllocateDirectMemory(run + 0x40000, run + 0x44000, 0x4000, 0, 0, ÷r) == 0 && divider == run + 0x40000); + Require(sceKernelAvailableDirectMemorySize(run, run + 0x84000, 0, &start, &size) == 0 && start == run && size == 0x40000); + Require(sceKernelReleaseDirectMemory(divider, 0x4000) == 0); Require(sceKernelReleaseDirectMemory(first, 0x200000) == 0); Require(sceKernelReleaseDirectMemory(last, 0x200000) == 0); @@ -3251,8 +3303,8 @@ static void CheckMunmapOverUnmappedPages() { Require(sceKernelVirtualQuery(memory, 0, &info, sizeof(info)) == SCE_KERNEL_ERROR_EACCES); Require(sceKernelVirtualQuery(memory + page * 2, 0, &info, sizeof(info)) == SCE_KERNEL_ERROR_EACCES); Require(munmap_nid_postfix(memory, page * 3) == 0); - Require(sceKernelMunmap(memory, page * 3) == 0); - Require(sceKernelMunmap(memory, 0) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelMunmap(reinterpret_cast(memory), page * 3) == 0); + Require(sceKernelMunmap(reinterpret_cast(memory), 0) == SCE_KERNEL_ERROR_EINVAL); } int main(int argc, char** argv) { @@ -3286,9 +3338,11 @@ int main(int argc, char** argv) { CheckGuestModuleUsesFlexibleMemory(); CheckBatchMapStopsAtInvalidEntry(); CheckCheckedReleaseDirectMemory(); + CheckReleaseSkipsFreePages(); CheckAudioCoprocessorProtection(); CheckMprotectIgnoresUndefinedBits(); CheckStrictMemoryProtectionBits(); + CheckPosixMprotectIgnoresUndefinedBits(); CheckDirectMemoryFollowsPhysicalPages(); CheckSparseDirectMemoryBacking(); CheckSavedDirectMemorySurvivesCachePressure(); @@ -3339,7 +3393,7 @@ int main(int argc, char** argv) { CheckPinnedSharedPages(); CheckFailedCollectKeepsWrites(); CheckSingleDirectMappingTracksWritesWithoutFaults(); - CheckLargeResidentWritesStopFaulting(); + CheckLargeResidentWritesDoNotFault(); CheckImportAccessDoesNotInventSharedWrites(); CheckResidentCollectPreservesPartialWrites(); CheckResidentCollectionDuringHostWrites(); @@ -3381,6 +3435,14 @@ int main(int argc, char** argv) { reject(page, 3, 0x1012, -1, 0, 22); reject(page, 3, 0x2, 0, 0, 45); // file-backed reject(page, 3, 0x22, -1, 0, 45); // Linux MAP_ANON is not guest MAP_ANON + reject(page, 3, 0x1003, -1, 0, 22); + reject(page, 3, 0x3, 0, 0, 22); + auto* anonymousOnly = static_cast(mmap_nid_postfix(nullptr, page, 3, 0x1000, -1, 0)); + Require(anonymousOnly != failed && (reinterpret_cast(anonymousOnly) & (page - 1)) == 0); + anonymousOnly[page - 1] = 7; + Require(anonymousOnly[page - 1] == 7 && anonymousOnly[0] == 0); + Require(munmap_nid_postfix(const_cast(anonymousOnly), page) == 0); + reject(page, 3, 0x1000, 0, 0, 22); auto* memory = static_cast(mmap_nid_postfix(nullptr, page * 3 - 1, 3, 0x1002, -1, 0)); Require(memory != failed && (reinterpret_cast(memory) & (page - 1)) == 0); @@ -3441,7 +3503,7 @@ int main(int argc, char** argv) { Require(sceKernelReserveVirtualRange(&mincoreReserved, page, 0, 0) == 0); mincoreVec[0] = 0x55; Require(mincore_nid_postfix(mincoreReserved, page, mincoreVec) == 0 && mincoreVec[0] == 0); - Require(sceKernelMunmap(mincoreReserved, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(mincoreReserved), page) == 0); Require(munmap_nid_postfix(memory + 1, page) == -1 && *__error_nid_postfix() == 22); Require(munmap_nid_postfix(memory, 0) == -1 && *__error_nid_postfix() == 22); Require(memory[0] == 42); diff --git a/core/libs/tests/GuestMemoryPoolStats.cpp b/core/libs/tests/GuestMemoryPoolStats.cpp index 7aec461e18..14d926ca31 100644 --- a/core/libs/tests/GuestMemoryPoolStats.cpp +++ b/core/libs/tests/GuestMemoryPoolStats.cpp @@ -1,3 +1,6 @@ +#include "prx/libSceSsl/include/Ssl.hpp" +#include "prx/libSceNet/include/Net.hpp" +#include "prx/libSceHttp2/include/Http2.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -6,41 +9,19 @@ #include #include -struct PoolStats { - std::size_t pool_size; - std::size_t max_inuse_size; - std::size_t current_inuse_size; - std::int32_t reserved; -}; - -struct NetPoolStats { - std::size_t pool_size; - std::size_t max_inuse_size; - std::size_t current_inuse_size; -}; - -extern "C" { -int APS5_VABI sceSslInit_nid_postfix(std::size_t); -int APS5_VABI sceSslTerm_nid_postfix(int); -int APS5_VABI sceSslGetMemoryPoolStats(int, void*); -int APS5_VABI sceHttp2Init(int, int, std::size_t, int); -int APS5_VABI sceHttp2Term(int); -int APS5_VABI sceHttp2GetMemoryPoolStats(int, void*); -int APS5_VABI sceNetPoolCreate(const char*, int, int); -int APS5_VABI sceNetPoolDestroy(int); -int APS5_VABI sceNetGetMemoryPoolStats(int, void*); -} - static void Require(bool value) { if (!value) std::abort(); } -using StatsCall = int(APS5_VABI*)(int, void*); - -static void CheckRejected(StatsCall call, int id, int expected) { - std::array buffer; - buffer.fill(0x5a); - Require(call(id, buffer.data()) == expected); - for (std::size_t i = 0; i < sizeof(PoolStats); ++i) Require(buffer[i] == 0); - for (std::size_t i = sizeof(PoolStats); i < buffer.size(); ++i) Require(buffer[i] == 0x5a); +template +static void CheckRejected(int (APS5_VABI *call)(int, TStats*), int id, int expected) { + struct { + TStats stats; + std::array guard; + } buffer; + std::memset(&buffer, 0x5a, sizeof(buffer)); + Require(call(id, &buffer.stats) == expected); + const auto* bytes = reinterpret_cast(&buffer.stats); + for (std::size_t i = 0; i < sizeof(TStats); ++i) Require(bytes[i] == 0); + for (const auto byte : buffer.guard) Require(byte == 0x5a); } int main() { @@ -50,12 +31,12 @@ int main() { constexpr int http2NullPointer = static_cast(0x817B1225); constexpr int netBadDescriptor = static_cast(0x80410109); constexpr int netInvalid = static_cast(0x80410116); - static_assert(sizeof(PoolStats) == 0x20 && sizeof(NetPoolStats) == 0x18); + static_assert(sizeof(SceSsl::MemoryPoolStats) == 0x20 && sizeof(Http2MemoryPoolStats) == 0x20 && sizeof(NetMemoryPoolStats) == 0x18); const int ssl = sceSslInit_nid_postfix(0x40000); Require(ssl > 0); - PoolStats stats{}; - Require(sceSslGetMemoryPoolStats(ssl, &stats) == 0 && stats.pool_size == 0x40000); + SceSsl::MemoryPoolStats sslStats{}; + Require(sceSslGetMemoryPoolStats(ssl, &sslStats) == 0 && sslStats.poolSize == 0x40000); Require(sceSslGetMemoryPoolStats(ssl, nullptr) == sslInvalidValue); Require(sceSslGetMemoryPoolStats(12345, nullptr) == sslInvalidValue); CheckRejected(sceSslGetMemoryPoolStats, 12345, sslInvalidId); @@ -63,7 +44,8 @@ int main() { const int http2 = sceHttp2Init(1, ssl, 0x40000, 1); Require(http2 > 0); - Require(sceHttp2GetMemoryPoolStats(http2, &stats) == 0 && stats.pool_size == 0x40000); + Http2MemoryPoolStats http2Stats{}; + Require(sceHttp2GetMemoryPoolStats(http2, &http2Stats) == 0 && http2Stats.pool_size == 0x40000); Require(sceHttp2GetMemoryPoolStats(http2, nullptr) == http2NullPointer); Require(sceHttp2GetMemoryPoolStats(12345, nullptr) == http2NullPointer); CheckRejected(sceHttp2GetMemoryPoolStats, 12345, http2InvalidId); @@ -76,14 +58,17 @@ int main() { const int pool = sceNetPoolCreate("stats", 0x20000, 0); Require(pool > 0); - NetPoolStats netStats{}; + NetMemoryPoolStats netStats{}; Require(sceNetGetMemoryPoolStats(pool, &netStats) == 0 && netStats.pool_size == 0x20000); Require(sceNetGetMemoryPoolStats(pool, nullptr) == netInvalid); Require(sceNetGetMemoryPoolStats(12345, nullptr) == netBadDescriptor); - std::array untouched; - untouched.fill(0x5a); + struct { + NetMemoryPoolStats stats; + std::array guard; + } untouched; + std::memset(&untouched, 0x5a, sizeof(untouched)); auto buffer = untouched; - Require(sceNetGetMemoryPoolStats(12345, buffer.data()) == netBadDescriptor && buffer == untouched); + Require(sceNetGetMemoryPoolStats(12345, &buffer.stats) == netBadDescriptor && std::memcmp(&buffer, &untouched, sizeof(buffer)) == 0); Require(sceNetPoolDestroy(pool) == 0); Require(sceNetGetMemoryPoolStats(pool, nullptr) == netBadDescriptor); return 0; diff --git a/core/libs/tests/GuestMemrchr.cpp b/core/libs/tests/GuestMemrchr.cpp index 8715e30bf2..6f09c1f987 100644 --- a/core/libs/tests/GuestMemrchr.cpp +++ b/core/libs/tests/GuestMemrchr.cpp @@ -1,10 +1,7 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include -extern "C" { -void* APS5_VABI memrchr_nid_postfix(const void*, int, std::size_t); -} - int main() { const unsigned char bytes[] = {0xff, 0x01, 0xff, 0x00}; const auto* found = static_cast(memrchr_nid_postfix(bytes, 0xff, sizeof(bytes))); diff --git a/core/libs/tests/GuestMessageDestructorAbi.S b/core/libs/tests/GuestMessageDestructorAbi.S index 57ac11e827..7330b86885 100644 --- a/core/libs/tests/GuestMessageDestructorAbi.S +++ b/core/libs/tests/GuestMessageDestructorAbi.S @@ -141,7 +141,33 @@ ProbeDestroy: .cfi_def_cfa %rsp, 8 ret .cfi_endproc +.globl GuestCleanup +GuestCleanup: + .cfi_startproc + .cfi_personality 0x00, __gcc_personality_v0_nid_postfix + .cfi_lsda 0x00, .Lcleanup_lsda + PROLOGUE +.Lcleanup_call: + call FixtureHostThrow +.Lcleanup_call_end: + ud2 +.Lcleanup_landing: + incl FixtureGuardDestroyed(%rip) + movq %rax, %rdi + call _Unwind_Resume_nid_postfix + ud2 + .cfi_endproc + .section .rdata,"dr" +.Lcleanup_lsda: + .byte 0xff, 0xff, 0x01 + .uleb128 .Lcleanup_calls_end-.Lcleanup_calls +.Lcleanup_calls: + .uleb128 .Lcleanup_call-GuestCleanup + .uleb128 .Lcleanup_call_end-.Lcleanup_call + .uleb128 .Lcleanup_landing-GuestCleanup + .uleb128 0 +.Lcleanup_calls_end: .Linner_lsda: .byte 0xff, 0x00 .uleb128 .Linner_types_end-.Linner_types_offset diff --git a/core/libs/tests/GuestMessageDestructorAbi.cpp b/core/libs/tests/GuestMessageDestructorAbi.cpp index 68e398ef4d..90d524adf0 100644 --- a/core/libs/tests/GuestMessageDestructorAbi.cpp +++ b/core/libs/tests/GuestMessageDestructorAbi.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" #include #include @@ -10,10 +11,10 @@ using GuestDestroy = void (APS5_VABI *)(void*); using HostDestroy = void (*)(void*); using GuestThrowFunction = void (APS5_VABI *)(std::uintptr_t); struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; }; -struct Object { const void* vtable; const char* message; }; + struct Table { std::ptrdiff_t offset; const TypeRecord* type; GuestDestroy destroy; GuestDestroy deleteObject; GuestWhat what; }; struct Message { std::size_t length; std::size_t capacity; std::atomic references; }; -static_assert(sizeof(Object) == 16); +static_assert(sizeof(LibcException::ExceptionObject) == 16); extern "C" { extern const unsigned char _ZTVN10__cxxabiv117__class_type_infoE_nid_postfix[]; @@ -22,21 +23,12 @@ extern const unsigned char _ZTISt12out_of_range_nid_postfix[]; extern const unsigned char _ZTISt11regex_error_nid_postfix[]; extern const unsigned char _ZTISt13runtime_error_nid_postfix[]; extern const unsigned char _ZTISt11logic_error_nid_postfix[]; -void APS5_VABI _ZNSt12out_of_rangeC1EPKc_nid_postfix(Object*, const char*); -void APS5_VABI _ZNSt12out_of_rangeC1ERKS__nid_postfix(Object*, const Object*); -void APS5_VABI _ZNSt12out_of_rangeD1Ev_nid_postfix(Object*); -void APS5_VABI _ZNSt11logic_errorC1EPKc_nid_postfix(Object*, const char*); -void APS5_VABI _ZNSt11logic_errorD1Ev_nid_postfix(Object*); -[[noreturn]] void APS5_VABI _ZSt14_Xout_of_rangePKc_nid_postfix(const char*); -[[noreturn]] void APS5_VABI _ZSt15_Xruntime_errorPKc_nid_postfix(const char*); -[[noreturn]] void APS5_VABI _ZNKSt11logic_error8_DoraiseEv_nid_postfix(const Object*); -[[noreturn]] void APS5_VABI _ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix(std::regex_constants::error_type); + int APS5_VABI GuestOuter(); +void APS5_VABI GuestCleanup(); void APS5_VABI GuestStdCapture(); void APS5_VABI ProbeDestroy(GuestDestroy, void*, void*); const char* APS5_VABI ProbeWhat(GuestWhat, const void*, const void*); -void APS5_VABI _ZNSt8bad_castC1Ev_nid_postfix(Object*); -void APS5_VABI _ZNSt9bad_allocC1Ev_nid_postfix(Object*); TypeRecord FixtureBaseType{_ZTVN10__cxxabiv117__class_type_infoE_nid_postfix + 16, "11FixtureBase", nullptr}; TypeRecord FixtureErrorType{_ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix + 16, "12FixtureError", &FixtureBaseType}; @@ -55,7 +47,7 @@ unsigned FixtureWhatChecked = 0; std::fprintf(stderr, "guest exception failure %u\n", code); ExitProcess(code); } -const char* APS5_VABI FixtureWhat(const void* object) { return static_cast(object)->message; } +const char* APS5_VABI FixtureWhat(const void* object) { return static_cast(object)->message; } void APS5_VABI FixtureDestroy(void* object) { if (object != FixtureLastObject) FixtureFail(11); ++FixtureDestroyed; @@ -67,7 +59,7 @@ void APS5_VABI FixtureCheckWhat(const char* message) { } } -static Object decoy{}, retained{}; +static LibcException::ExceptionObject decoy{}, retained{}; static GuestDestroy registeredDestructor; static void* expectedObject; static void* poison; @@ -128,18 +120,20 @@ static int TestVtable(const char* mode) { const bool plain = std::strstr(mode, "plain") != nullptr; const bool deleting = std::strstr(mode, "delete") != nullptr; const bool destroying = deleting || std::strstr(mode, "destroy") != nullptr; - auto* actual = static_cast(std::malloc(sizeof(Object))); - auto* other = static_cast(std::malloc(sizeof(Object))); + auto* actual = static_cast(std::malloc(sizeof(LibcException::ExceptionObject))); + auto* other = static_cast(std::malloc(sizeof(LibcException::ExceptionObject))); if (!actual || !other) FixtureFail(57); actual->message = other->message = nullptr; if (plain) { _ZNSt9bad_allocC1Ev_nid_postfix(actual); _ZNSt8bad_castC1Ev_nid_postfix(other); + } else { _ZNSt12out_of_rangeC1EPKc_nid_postfix(actual, "actual virtual message"); _ZNSt12out_of_rangeC1EPKc_nid_postfix(other, "unrelated virtual message"); + } - const Object originalOther = *other; + const LibcException::ExceptionObject originalOther = *other; watchedObject = actual; watchedMessage = plain ? nullptr : reinterpret_cast(const_cast(actual->message)) - 1; foreignObject = other; @@ -167,20 +161,21 @@ static int TestVtable(const char* mode) { if (!plain) { if (!deleting) _ZNSt12out_of_rangeD1Ev_nid_postfix(actual); _ZNSt12out_of_rangeD1Ev_nid_postfix(other); + } if (!deleting) std::free(actual); std::free(other); return 0; } -static Message* MessageHeader(const Object& object) { +static Message* MessageHeader(const LibcException::ExceptionObject& object) { return reinterpret_cast(const_cast(object.message)) - 1; } extern "C" void APS5_VABI ObserveDestroy(void* pointer) { if (pointer != expectedObject || ++callbacks != 1) FixtureFail(40); ProbeDestroy(registeredDestructor, pointer, poison); - if (static_cast(pointer)->message != nullptr || + if (static_cast(pointer)->message != nullptr || !decoy.message || std::strcmp(decoy.message, "unrelated message") || (keepMessage && MessageHeader(retained)->references.load() != 0)) FixtureFail(41); } @@ -190,21 +185,59 @@ extern "C" void APS5_VABI FixtureInspect(void* pointer) { if (header->_pad != 0 || !header->destructor) FixtureFail(42); expectedObject = pointer; GuestWhat what; - std::memcpy(&what, static_cast(static_cast(pointer)->vtable) + + std::memcpy(&what, static_cast(static_cast(pointer)->vtable) + 2 * sizeof(void*), sizeof(what)); const char* message = ProbeWhat(what, pointer, poison); if (!message || std::strcmp(message, expectedMessage)) FixtureFail(62); if (keepMessage) { - _ZNSt12out_of_rangeC1ERKS__nid_postfix(&retained, static_cast(pointer)); + _ZNSt12out_of_rangeC1ERKS__nid_postfix(&retained, static_cast(pointer)); + if (MessageHeader(retained)->references.load() != 1) FixtureFail(43); } registeredDestructor = reinterpret_cast(header->destructor); if (!regularRelease) header->destructor = reinterpret_cast(ObserveDestroy); } +static unsigned hybridDestroyed; +struct HybridGuard { ~HybridGuard() { ++hybridDestroyed; } }; + +extern "C" [[gnu::noinline, noreturn]] void APS5_VABI FixtureHostThrow() { + HybridGuard guard; + __asm__ volatile("pxor %%xmm12, %%xmm12" ::: "xmm12"); + throw 73; +} + +[[gnu::noinline]] static void HybridMiddle() { + HybridGuard guard; + GuestCleanup(); +} + +static int TestHybridUnwind() { + const std::uint64_t expected[2] {0x123456789abcdef0, 0xfedcba9876543210}; + std::uint64_t restored[2] {}; + __asm__ volatile("movdqu %0, %%xmm12" :: "m"(expected) : "xmm12"); + unsigned caught = 0; + try { + HybridGuard guard; + try { HybridMiddle(); } + catch (int value) { + if (value != 73 || hybridDestroyed != 2 || FixtureGuardDestroyed != 1) return 63; + ++caught; + throw; + } + } catch (int value) { + if (value != 73) return 64; + ++caught; + } + __asm__ volatile("movdqu %%xmm12, %0" : "=m"(restored)); + return caught == 2 && hybridDestroyed == 3 && FixtureGuardDestroyed == 1 && + std::memcmp(restored, expected, sizeof(expected)) == 0 ? 0 : 65; +} + int main(int argc, char** argv) { SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); if (argc != 2) return 2; + if (std::strcmp(argv[1], "hybrid") == 0) return TestHybridUnwind(); if (std::strncmp(argv[1], "vtable-", 7) == 0) return TestVtable(argv[1]); if (std::strcmp(argv[1], "control") == 0) { if (GuestOuter() != 0 || FixtureDestroyed != 1 || FixtureGuardDestroyed != 1 || @@ -217,7 +250,7 @@ int main(int argc, char** argv) { regularRelease = std::strcmp(argv[1], "release") == 0; keepMessage = std::strstr(argv[1], "unshared") == nullptr; FixtureArgument = reinterpret_cast(FixtureMessage); - Object raised{}; + LibcException::ExceptionObject raised{}; if (regex) { expectedMessage = "regular expression error"; FixtureStdOutType = _ZTISt11regex_error_nid_postfix; @@ -229,12 +262,14 @@ int main(int argc, char** argv) { FixtureThrow = reinterpret_cast(_ZSt15_Xruntime_errorPKc_nid_postfix); } else if (doraise) { _ZNSt11logic_errorC1EPKc_nid_postfix(&raised, FixtureMessage); + expectedMessage = FixtureMessage; FixtureStdOutType = _ZTISt11logic_error_nid_postfix; FixtureThrow = reinterpret_cast(_ZNKSt11logic_error8_DoraiseEv_nid_postfix); FixtureArgument = reinterpret_cast(&raised); } _ZNSt12out_of_rangeC1EPKc_nid_postfix(&decoy, "unrelated message"); + poison = std::strstr(argv[1], "zero") ? nullptr : &decoy; GuestStdCapture(); const char* expected = regex ? "regular expression error" : FixtureMessage; @@ -244,6 +279,7 @@ int main(int argc, char** argv) { !decoy.message || std::strcmp(decoy.message, "unrelated message")) return 44; _ZNSt12out_of_rangeD1Ev_nid_postfix(&retained); _ZNSt12out_of_rangeD1Ev_nid_postfix(&decoy); + if (doraise) _ZNSt11logic_errorD1Ev_nid_postfix(&raised); if (retained.message || decoy.message || (doraise && raised.message)) return 45; std::puts("guest message callback, retained ownership and release: PASS"); diff --git a/core/libs/tests/GuestMktime.cpp b/core/libs/tests/GuestMktime.cpp index 97d72aeaa9..f3e74a4a6b 100644 --- a/core/libs/tests/GuestMktime.cpp +++ b/core/libs/tests/GuestMktime.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/SonyCrt.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/GuestTime.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -8,14 +10,6 @@ #include #include -extern "C" { -std::int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm*); -std::int64_t APS5_VABI mktime_nid_postfix(GuestTm*); -GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t*, GuestTm*); -void APS5_VABI tzset_nid_postfix(); -int APS5_VABI setenv_nid_postfix(const char*, const char*, int); -} - using Converter = std::int64_t (APS5_VABI *)(GuestTm*); static void Require(bool value, const char* message) { @@ -67,6 +61,7 @@ static void CheckFailure(Converter convert) { static void CheckZone(const char* zone) { Require(setenv_nid_postfix("TZ", zone, 1) == 0, "Could not set the time zone"); tzset_nid_postfix(); + constexpr std::array converters{libc_mktime_nid_postfix, mktime_nid_postfix}; constexpr std::array timers{-86401, -2, -1, 0, 1, 59, 60, 86400}; for (auto convert : converters) { diff --git a/core/libs/tests/GuestMlock.cpp b/core/libs/tests/GuestMlock.cpp index 79b571e9b2..71883b2b4c 100644 --- a/core/libs/tests/GuestMlock.cpp +++ b/core/libs/tests/GuestMlock.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -15,17 +17,6 @@ #include #endif -extern "C" { -int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); -int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int APS5_VABI mlock_nid_postfix(const void*, std::size_t); -int APS5_VABI munlock_nid_postfix(const void*, std::size_t); -int APS5_VABI mlockall_nid_postfix(int); -int APS5_VABI munlockall_nid_postfix(); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } static void RequireLockAllInvalid(int flags) { @@ -37,6 +28,7 @@ static void RequireLockAllUnsupported(int flags) { bool rejected = false; try { mlockall_nid_postfix(flags); + } catch (const std::runtime_error&) { rejected = true; } @@ -140,9 +132,9 @@ int main() { *__error_nid_postfix() = untouched; Require(munlock_nid_postfix(reserved, page) == 0); Require(*__error_nid_postfix() == untouched); - Require(sceKernelMunmap(reserved, page) == 0); + Require(sceKernelMunmap(reinterpret_cast(reserved), page) == 0); - Require(sceKernelMunmap(mapped, length) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), length) == 0); #ifndef ANYPS5_ENABLE_ASAN *__error_nid_postfix() = 0; Require(mlock_nid_postfix(mapped, page) == -1 && *__error_nid_postfix() == 12); diff --git a/core/libs/tests/GuestModuleInfo.cpp b/core/libs/tests/GuestModuleInfo.cpp index 6c70763d2a..85dd43c3fb 100644 --- a/core/libs/tests/GuestModuleInfo.cpp +++ b/core/libs/tests/GuestModuleInfo.cpp @@ -1,27 +1,24 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libkernel/KernelErrors.hpp" #include #include #include +#include #include #include #include #include +#include #include #include -extern "C" { -void* APS5_VABI dlopen_nid_postfix(const char*, int); -void* APS5_VABI dlsym_nid_postfix(void*, const char*); -int APS5_VABI dlclose_nid_postfix(void*); -int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t, int, ModuleInfoEx*); -int APS5_VABI sceKernelGetModuleList(KernelModule*, std::size_t, std::size_t*); -int APS5_VABI sceKernelGetModuleInfo(KernelModule, ModuleInfo*); -int APS5_VABI sceKernelGetModuleList2(KernelModule*, std::size_t, std::size_t*); -int APS5_VABI sceKernelGetModuleInfo2(KernelModule, ModuleInfo*); -std::int32_t APS5_VABI sceKernelGetLibkernelTextLocation_nid_postfix(void**, std::size_t*); -std::int32_t APS5_VABI sceKernelInternalMemoryGetModuleSegmentInfo_nid_postfix(InternalSegmentInfo*); + +static void Require(bool value, const std::source_location& location = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "module info check failed at line %u\n", location.line()); + std::abort(); + } } -static void Require(bool value) { if (!value) std::abort(); } template static bool ThrowsInvalidArgument(TFunction function) { try { @@ -34,7 +31,7 @@ static bool ThrowsInvalidArgument(TFunction function) { static ModuleInfoEx Query(const void* address, int expected) { ModuleInfoEx info{}; info.st_size = sizeof(ModuleInfoEx); - Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(address), 2, &info) == expected); + Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(address), 1, &info) == expected); return info; } int main(int argc, char** argv) { @@ -44,7 +41,7 @@ int main(int argc, char** argv) { const void* add = dlsym_nid_postfix(module, "GuestModuleAdd"); Require(add != nullptr); const auto info = Query(add, 0); - Require(info.st_size == sizeof(ModuleInfoEx)); + Require(info.st_size == 0); Require(info.id == static_cast(reinterpret_cast(module))); Require(std::string(info.name) == std::string(argv[1]).substr(std::string(argv[1]).find_last_of("/\\") + 1)); Require(info.segment_count > 0 && info.segment_count <= 4 && info.ref_count == 1); @@ -55,7 +52,11 @@ int main(int argc, char** argv) { executable = (segment.prot & 5) == 5; } Require(executable); +#if defined(_WIN32) && defined(__SEH__) + Require(info.eh_frame_addr == 0 && info.eh_frame_size == 0); +#else Require(info.eh_frame_addr != 0 && info.eh_frame_size != 0); +#endif #ifndef _WIN32 Require(info.eh_frame_hdr_addr != 0 && info.eh_frame_hdr_size != 0); #endif @@ -158,10 +159,34 @@ int main(int argc, char** argv) { Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(add), 2, nullptr) == SCE_KERNEL_ERROR_EFAULT); ModuleInfoEx invalid{}; invalid.st_size = sizeof(ModuleInfoEx); - Require(ThrowsInvalidArgument([&] { sceKernelGetModuleInfoFromAddr(reinterpret_cast(add), 1, &invalid); })); + for (const int flags : {-1, 0, 3, 4, 8}) { + std::memset(&invalid, 0xbb, sizeof(invalid)); + Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(add), flags, &invalid) == SCE_KERNEL_ERROR_EINVAL); + Require(std::all_of(reinterpret_cast(&invalid), reinterpret_cast(&invalid + 1), [](unsigned char value) { return value == 0; })); + } + Require(ThrowsInvalidArgument([&] { sceKernelGetModuleInfoFromAddr(reinterpret_cast(add), 0, nullptr); })); + std::vector everyModule(64); + std::vector titleOnlyModules(64); + std::size_t everyCount = 0; + std::size_t titleOnlyCount = 0; + Require(sceKernelGetModuleList(everyModule.data(), everyModule.size(), &everyCount) == 0); + Require(sceKernelGetModuleList2(titleOnlyModules.data(), titleOnlyModules.size(), &titleOnlyCount) == 0); + const auto systemModule = std::find_if(everyModule.begin(), everyModule.begin() + static_cast(everyCount), + [&](KernelModule handle) { return std::find(titleOnlyModules.begin(), titleOnlyModules.begin() + static_cast(titleOnlyCount), handle) == titleOnlyModules.begin() + static_cast(titleOnlyCount); }); + Require(systemModule != everyModule.begin() + static_cast(everyCount)); + ModuleInfo systemInfo{}; + systemInfo.st_size = sizeof(ModuleInfo); + Require(sceKernelGetModuleInfo(*systemModule, &systemInfo) == 0 && systemInfo.segment_count > 0); + const auto systemAddress = static_cast(systemInfo.segments[0].address); + ModuleInfoEx anyModule{}; + Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(&Query), 2, &anyModule) == 0 && anyModule.segment_count > 0 && anyModule.st_size == 0); + Require(sceKernelGetModuleInfoFromAddr(systemAddress, 1, &anyModule) == 0 && anyModule.id == *systemModule); + std::memset(&anyModule, 0xbb, sizeof(anyModule)); + Require(sceKernelGetModuleInfoFromAddr(systemAddress, 2, &anyModule) == SCE_KERNEL_ERROR_ESRCH && anyModule.segment_count == 0 && anyModule.name[0] == 0); + Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(add), 1, &anyModule) == 0 && anyModule.id == info.id); ModuleInfoEx unset{}; unset.st_size = 0xf0000; - Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(add), 2, &unset) == 0 && unset.st_size == sizeof(ModuleInfoEx)); + Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(add), 1, &unset) == 0 && unset.st_size == 0); const auto again = Query(add, 0); Require(again.eh_frame_hdr_addr == info.eh_frame_hdr_addr && again.eh_frame_hdr_size == info.eh_frame_hdr_size); Require(again.eh_frame_addr == info.eh_frame_addr && again.eh_frame_size == info.eh_frame_size); @@ -172,6 +197,8 @@ int main(int argc, char** argv) { Require(reloadedAdd != nullptr); const auto fresh = Query(reloadedAdd, 0); Require(fresh.eh_frame_size == info.eh_frame_size && fresh.eh_frame_hdr_size == info.eh_frame_hdr_size); - Require(fresh.eh_frame_addr - fresh.segments[0].address == info.eh_frame_addr - info.segments[0].address); + if (info.eh_frame_addr) + Require(fresh.eh_frame_addr - fresh.segments[0].address == info.eh_frame_addr - info.segments[0].address); + else Require(fresh.eh_frame_addr == 0); Require(dlclose_nid_postfix(reloaded) == 0); } diff --git a/core/libs/tests/GuestMove.cpp b/core/libs/tests/GuestMove.cpp index b114030e35..98d1e59b72 100644 --- a/core/libs/tests/GuestMove.cpp +++ b/core/libs/tests/GuestMove.cpp @@ -1,23 +1,10 @@ +#include "prx/libSceMove/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" { -int APS5_VABI sceMoveInit(void); -int APS5_VABI sceMoveTerm(void); -int APS5_VABI sceMoveOpen(std::int32_t userId, std::int32_t type, std::int32_t index); -int APS5_VABI sceMoveClose(std::int32_t handle); -int APS5_VABI sceMoveGetDeviceInfo(std::int32_t handle, void* info); -int APS5_VABI sceMoveGetExtensionPortInfo(std::int32_t handle, void* data); -int APS5_VABI sceMoveReadStateLatest(std::int32_t handle, void* data); -int APS5_VABI sceMoveReadStateRecent(std::int32_t handle, std::int64_t timestamp, void* data, std::int32_t* outCount); -int APS5_VABI sceMoveSetVibration(std::int32_t handle, std::uint8_t motor); -int APS5_VABI sceMoveSetLightSphere(std::int32_t handle, std::uint8_t red, std::uint8_t green, std::uint8_t blue); -int APS5_VABI sceMoveResetLightSphere(std::int32_t handle); -} - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -85,8 +72,11 @@ void RequireUnknownHandle(std::int32_t handle) { Require(sceMoveSetVibration(handle, 0xFF) == kInvalidHandle); Require(sceMoveSetLightSphere(handle, 0xFF, 0, 0) == kInvalidHandle); Require(sceMoveResetLightSphere(handle) == kInvalidHandle); - Require(sceMoveGetDeviceInfo(handle, nullptr) == kInvalidHandle); - Require(sceMoveReadStateRecent(handle, -1, nullptr, nullptr) == kInvalidHandle); + Require(sceMoveGetDeviceInfo(handle, nullptr) == kInvalidArg); + Require(sceMoveGetExtensionPortInfo(handle, nullptr) == kInvalidArg); + Require(sceMoveReadStateLatest(handle, nullptr) == kInvalidArg); + Require(sceMoveReadStateRecent(handle, -1, buffer.bytes, &count) == kInvalidArg); + Require(sceMoveReadStateRecent(handle, 0, buffer.bytes, nullptr) == kInvalidArg); Require(buffer.Untouched()); Require(count == -7); } @@ -99,6 +89,8 @@ int main() { Require(sceMoveInit() == kOk); Require(sceMoveInit() == kAlreadyInit); + RequireUnknownHandle(0); + RequireUnknownHandle(-1); RequireUnknownHandle(kFirstHandle); const std::int32_t first = sceMoveOpen(kUserId, 0, 0); diff --git a/core/libs/tests/GuestMprotectUnmapped.cpp b/core/libs/tests/GuestMprotectUnmapped.cpp index 8a4ed05199..326051d6ab 100644 --- a/core/libs/tests/GuestMprotectUnmapped.cpp +++ b/core/libs/tests/GuestMprotectUnmapped.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "SceTypes.hpp" @@ -6,16 +8,6 @@ #include #include -extern "C" { -void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; -int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; -int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int) noexcept; -int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); -int APS5_VABI sceKernelMtypeprotect(const void*, std::size_t, int, int); -int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr std::size_t Page = 0x4000; diff --git a/core/libs/tests/GuestMsgDialogNative.cpp b/core/libs/tests/GuestMsgDialogNative.cpp index 8842c84299..21383b3c87 100644 --- a/core/libs/tests/GuestMsgDialogNative.cpp +++ b/core/libs/tests/GuestMsgDialogNative.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMsgDialog.native/libSceMsgDialog.native.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,16 +6,7 @@ extern "C" { extern int SceMsgDialogNativeModuleLoaded_nid_no_patch; -int APS5_VABI sceMsgDialogInitialize(void); -int APS5_VABI sceMsgDialogOpen(const void* param); -int APS5_VABI sceMsgDialogGetStatus(void); -int APS5_VABI sceMsgDialogUpdateStatus(void); -int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result); -int APS5_VABI sceMsgDialogClose(void); -int APS5_VABI sceMsgDialogTerminate(void); -int APS5_VABI sceMsgDialogProgressBarInc(int target, std::uint32_t delta); -int APS5_VABI sceMsgDialogProgressBarSetMsg(int target, const char* msg); -int APS5_VABI sceMsgDialogProgressBarSetValue(int target, std::uint32_t rate); + } namespace { diff --git a/core/libs/tests/GuestMsync.cpp b/core/libs/tests/GuestMsync.cpp index 451dd44583..e5dc64d5ce 100644 --- a/core/libs/tests/GuestMsync.cpp +++ b/core/libs/tests/GuestMsync.cpp @@ -1,18 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include -#include - -extern "C" { -void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; -int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; -int APS5_VABI msync_nid_postfix(void*, std::size_t, int); -int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t); -int* APS5_VABI __error_nid_postfix(); -} namespace { constexpr std::size_t Page = 0x4000; @@ -23,6 +16,7 @@ constexpr int Async = 0x1; constexpr int Invalidate = 0x2; constexpr int Invalid = 22; constexpr int NoMemory = 12; +constexpr int Busy = 16; void Require(bool condition, std::source_location location = std::source_location::current()) { if (condition) return; @@ -40,15 +34,6 @@ bool Fails(void* address, std::size_t length, int flags, int error) { return msync_nid_postfix(address, length, flags) == -1 && *__error_nid_postfix() == error; } -bool Throws(void* address, std::size_t length, int flags) { - try { - msync_nid_postfix(address, length, flags); - } catch (const std::runtime_error&) { - return true; - } - return false; -} - char* Map(std::size_t pages) { void* mapped = mmap_nid_postfix(nullptr, pages * Page, ReadWrite, PrivateAnonymous, -1, 0); Require(mapped != reinterpret_cast(static_cast(-1))); @@ -69,7 +54,11 @@ int main() { Require(Fails(mapped, Page, Async | Invalidate, Invalid)); const auto top = std::numeric_limits::max() & ~(Page - 1); Require(Fails(reinterpret_cast(top), 2 * Page, Sync, Invalid)); - Require(Throws(mapped, Page, Invalidate)); + Require(Fails(mapped, Page, Invalidate, Busy)); + Require(Fails(mapped, 3 * Page, Sync | Invalidate, Busy)); + Require(Fails(mapped + 1, 16, Invalidate, Busy)); + Require(Fails(mapped, 0, Invalidate, Busy)); + Require(Succeeds(mapped, Page, 0x8)); Require(munmap_nid_postfix(mapped, 3 * Page) == 0); Require(Fails(mapped, Page, Sync, NoMemory)); diff --git a/core/libs/tests/GuestMultibyteLocale.cpp b/core/libs/tests/GuestMultibyteLocale.cpp new file mode 100644 index 0000000000..682521486d --- /dev/null +++ b/core/libs/tests/GuestMultibyteLocale.cpp @@ -0,0 +1,123 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +constexpr auto Invalid = static_cast(-1); +constexpr auto Incomplete = static_cast(-2); + +static void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", what); + std::abort(); +} + +static std::size_t Decode(const char* text, std::size_t count, std::uint16_t& value) { + unsigned char state[16]{}; + value = 0xcccc; + *__error_nid_postfix() = 0; + return mbrtowc_nid_postfix(&value, text, count, state); +} + +static void CheckDecoding() { + std::uint16_t value = 0; + Require(_Getmbcurmax_nid_postfix() == 3, "three bytes per character at most"); + Require(Decode("A", 1, value) == 1 && value == 'A', "ASCII"); + Require(Decode("\xc3\xa9", 2, value) == 2 && value == 0xe9, "two bytes"); + Require(Decode("\xe2\x82\xac", 3, value) == 3 && value == 0x20ac, "three bytes"); + Require(Decode("\xed\xa0\x80", 3, value) == 3 && value == 0xd800, "an encoded surrogate decodes"); + Require(Decode("\xef\xbf\xbf", 3, value) == 3 && value == 0xffff, "U+FFFF decodes"); + Require(Decode("\xc1\xbf", 2, value) == 2 && value == 0x7f, "an overlong form decodes"); + Require(Decode("\xc0\x80", 2, value) == 0 && value == 0, "an overlong NUL ends the string"); + Require(Decode("\xf0\x9f\x98\x80", 4, value) == Invalid && *__error_nid_postfix() == 86, "four-byte forms are rejected"); + Require(Decode("\x80", 1, value) == Invalid && *__error_nid_postfix() == 86, "a lone continuation byte"); + Require(Decode("\xe9", 1, value) == Incomplete, "a Latin-1 byte starts a three-byte form"); + Require(Decode("\xe9" "A", 2, value) == Invalid, "the Latin-1 byte is not followed by a continuation"); + Require(Decode("\xc3", 0, value) == Incomplete, "no bytes"); +} + +static void CheckState() { + unsigned char state[16]{}; + std::uint16_t value = 0; + Require(mbrtowc_nid_postfix(&value, "\xe2", 1, state) == Incomplete && !mbsinit_nid_postfix(state), "a partial character is pending"); + Require(mbrtowc_nid_postfix(&value, "\x82", 1, state) == Incomplete, "still pending"); + Require(mbrtowc_nid_postfix(&value, "\xac", 1, state) == 1 && value == 0x20ac && mbsinit_nid_postfix(state), "the last byte completes it"); + Require(mbrtowc_nid_postfix(&value, "\xc3", 1, state) == Incomplete, "pending again"); + Require(mbrtowc_nid_postfix(&value, "A", 1, state) == Invalid && !mbsinit_nid_postfix(state), "an error leaves the state pending"); + unsigned char pending[16]; + std::memcpy(pending, state, sizeof(pending)); + value = 0x7777; + Require(mbrtowc_nid_postfix(&value, nullptr, 0, state) == Incomplete && std::memcmp(state, pending, sizeof(pending)) == 0, + "a null string with no bytes to read is incomplete and leaves a pending state"); + *__error_nid_postfix() = 0; + Require(mbrtowc_nid_postfix(&value, nullptr, 1, state) == Invalid && *__error_nid_postfix() == 86 && value == 0x7777 + && std::memcmp(state, pending, sizeof(pending)) == 0, + "a null string in a pending state is an error and keeps the state"); + Require(mbrlen_nid_postfix(nullptr, 0, state) == 0 && mbsinit_nid_postfix(state), "mbrlen with a null string resets the state"); + unsigned char initial[16]{}; + Require(mbrtowc_nid_postfix(&value, nullptr, 0, state) == Incomplete && std::memcmp(state, initial, sizeof(initial)) == 0, + "a null string with no bytes to read is incomplete in the initial state"); + Require(mbrtowc_nid_postfix(&value, nullptr, 1, state) == 0 && value == 0x7777 && std::memcmp(state, initial, sizeof(initial)) == 0, + "a null string in the initial state converts nothing"); + Require(mbsinit_nid_postfix(nullptr) == 1, "a null state is initial"); + + mbtowc_nid_postfix(nullptr, nullptr, 0); + Require(mbtowc_nid_postfix(&value, "\xc3", 1) == -1, "mbtowc reports a partial character as -1"); + Require(mbtowc_nid_postfix(&value, "\xa9", 1) == 1 && value == 0xe9, "mbtowc keeps the partial character"); + Require(mbtowc_nid_postfix(&value, "\xe2\x82\xac", 8) == 3 && value == 0x20ac, "mbtowc limits the count to three bytes"); + mbtowc_nid_postfix(&value, "\xc3", 1); + + Require(mbtowc_nid_postfix(nullptr, nullptr, 0) == 0 && mbtowc_nid_postfix(&value, "A", 1) == 1, "a null string resets mbtowc"); + Require(mblen_nid_postfix("\xc3", 1) == -1 && mblen_nid_postfix("\xa9", 1) == -1, "mblen keeps no state"); + Require(mblen_nid_postfix("\xe2\x82\xac", 3) == 3 && mblen_nid_postfix(nullptr, 0) == 0, "mblen"); +} + +static void CheckEncoding() { + char bytes[4] = {}; + Require(wcrtomb_nid_postfix(bytes, 0x7f, nullptr) == 1 && bytes[0] == 0x7f, "one byte"); + Require(wcrtomb_nid_postfix(bytes, 0xe9, nullptr) == 2 && std::memcmp(bytes, "\xc3\xa9", 2) == 0, "two bytes"); + Require(wcrtomb_nid_postfix(bytes, 0xdc00, nullptr) == 3 && std::memcmp(bytes, "\xed\xb0\x80", 3) == 0, "a surrogate is encoded alone"); + Require(wcrtomb_nid_postfix(nullptr, 0x20ac, nullptr) == 1, "a null buffer"); + Require(wctomb_nid_postfix(bytes, 0xffff) == 3 && std::memcmp(bytes, "\xef\xbf\xbf", 3) == 0 && wctomb_nid_postfix(nullptr, 1) == 0, "wctomb"); +} + +static void CheckStrings() { + const char text[] = "a\xc3\xa9\xe2\x82\xac" "b"; + std::uint16_t wide[8]; + std::memset(wide, 0xcc, sizeof(wide)); + Require(mbstowcs_nid_postfix(wide, text, 8) == 4 && wide[1] == 0xe9 && wide[2] == 0x20ac && wide[4] == 0, "mbstowcs"); + Require(mbstowcs_nid_postfix(nullptr, text, 0) == 4, "mbstowcs counts characters"); + std::memset(wide, 0xcc, sizeof(wide)); + Require(mbstowcs_nid_postfix(wide, "a\xff", 8) == Invalid && wide[0] == 'a' && wide[1] == 0xcccc, "mbstowcs stops at an invalid byte"); + + unsigned char state[16]{}; + const char* cursor = text; + std::memset(wide, 0xcc, sizeof(wide)); + Require(mbsrtowcs_nid_postfix(wide, &cursor, 2, state) == 2 && cursor == text + 3, "mbsrtowcs advances by whole characters"); + cursor = text; + std::memset(wide, 0xcc, sizeof(wide)); + Require(mbsrtowcs_nid_postfix(wide, &cursor, 0, state) == 0 && wide[0] == 0 && cursor == text, "mbsrtowcs clears the first element"); + const char invalid[] = "ab\xff"; + cursor = invalid; + *__error_nid_postfix() = 0; + Require(mbsrtowcs_nid_postfix(wide, &cursor, 8, state) == Invalid && cursor == invalid + 2 && *__error_nid_postfix() == 86, "mbsrtowcs points at the invalid byte"); + + const std::uint16_t source[] = {'a', 0xe9, 0x20ac, 0}; + char narrow[8]; + std::memset(narrow, 'x', sizeof(narrow)); + Require(wcstombs_nid_postfix(narrow, source, 8) == 6 && std::memcmp(narrow, "a\xc3\xa9\xe2\x82\xac\0", 7) == 0, "wcstombs"); + std::memset(narrow, 'x', sizeof(narrow)); + Require(wcstombs_nid_postfix(narrow, source, 4) == 4 && std::memcmp(narrow, "a\xc3\xa9\xe2x", 5) == 0, "wcstombs fills the capacity mid-character"); + Require(wcstombs_nid_postfix(nullptr, source, 0) == 6, "wcstombs counts bytes"); +} + +int main() { + CheckDecoding(); + CheckState(); + CheckEncoding(); + CheckStrings(); + return 0; +} diff --git a/core/libs/tests/GuestMutexContention.cpp b/core/libs/tests/GuestMutexContention.cpp index dbb0da5e2f..648e403aa7 100644 --- a/core/libs/tests/GuestMutexContention.cpp +++ b/core/libs/tests/GuestMutexContention.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" #include "SceTypes.hpp" #include #include @@ -11,19 +13,6 @@ #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread*, const PthreadAttr*, PthreadEntry, void*, const char*); -int APS5_VABI scePthreadJoin(Pthread, void**); -int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr*); -int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr*, int); -int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr*); -int APS5_VABI scePthreadMutexInit(PthreadMutex*, const PthreadMutexattr*, const char*); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex*); -int APS5_VABI scePthreadMutexLock(PthreadMutex*); -int APS5_VABI scePthreadMutexTimedlock(PthreadMutex*, KernelUseconds); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex*); -} - namespace { using Clock = std::chrono::steady_clock; diff --git a/core/libs/tests/GuestMutexRaiseException.cpp b/core/libs/tests/GuestMutexRaiseException.cpp index ecdd471b4e..6bfa05b6ac 100644 --- a/core/libs/tests/GuestMutexRaiseException.cpp +++ b/core/libs/tests/GuestMutexRaiseException.cpp @@ -1,52 +1,42 @@ -#include "SceTypes.hpp" -#include -#include -#include -#include -#include - -extern "C" { -int APS5_VABI scePthreadCreate(Pthread*, const PthreadAttr*, PthreadEntry, void*, const char*); -int APS5_VABI scePthreadJoin(Pthread, void**); -int APS5_VABI scePthreadMutexInit(PthreadMutex*, const PthreadMutexattr*, const char*); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex*); -int APS5_VABI scePthreadMutexLock(PthreadMutex*); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex*); -int APS5_VABI sceKernelInstallExceptionHandler(int, void*); -int APS5_VABI sceKernelRemoveExceptionHandler(int); -int APS5_VABI sceKernelRaiseException(Pthread, int); -} - -static constexpr int SigUsr1 = 30; -static void Require(bool value) { if (!value) std::abort(); } -static std::atomic deliveries{0}; -static void APS5_VABI Handler(int, void*) { deliveries.fetch_add(1); } - -struct Context { PthreadMutex mutex = nullptr; std::atomic started{false}; std::atomic acquired{false}; }; -static void* APS5_VABI Worker(void* opaque) { - auto& context = *static_cast(opaque); - context.started.store(true); - Require(scePthreadMutexLock(&context.mutex) == 0); - context.acquired.store(true); - Require(scePthreadMutexUnlock(&context.mutex) == 0); - return nullptr; -} - -int main() { - Require(sceKernelInstallExceptionHandler(SigUsr1, reinterpret_cast(&Handler)) == 0); - Context context; - Require(scePthreadMutexInit(&context.mutex, nullptr, nullptr) == 0); - Require(scePthreadMutexLock(&context.mutex) == 0); - Pthread worker = nullptr; - Require(scePthreadCreate(&worker, nullptr, Worker, &context, "mutex wait") == 0); - while (!context.started.load()) std::this_thread::yield(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - Require(sceKernelRaiseException(worker, SigUsr1) == 0); - for (int i = 0; i < 5000 && deliveries.load() == 0; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); - Require(deliveries.load() == 1); - Require(scePthreadMutexUnlock(&context.mutex) == 0); - Require(scePthreadJoin(worker, nullptr) == 0); - Require(context.acquired.load()); - Require(scePthreadMutexDestroy(&context.mutex) == 0); - Require(sceKernelRemoveExceptionHandler(SigUsr1) == 0); -} \ No newline at end of file +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +static constexpr int SigUsr1 = 30; +static void Require(bool value) { if (!value) std::abort(); } +static std::atomic deliveries{0}; +static void APS5_VABI Handler(int, void*) { deliveries.fetch_add(1); } + +struct Context { PthreadMutex mutex = nullptr; std::atomic started{false}; std::atomic acquired{false}; }; +static void* APS5_VABI Worker(void* opaque) { + auto& context = *static_cast(opaque); + context.started.store(true); + Require(scePthreadMutexLock(&context.mutex) == 0); + context.acquired.store(true); + Require(scePthreadMutexUnlock(&context.mutex) == 0); + return nullptr; +} + +int main() { + Require(sceKernelInstallExceptionHandler(SigUsr1, reinterpret_cast(&Handler)) == 0); + Context context; + Require(scePthreadMutexInit(&context.mutex, nullptr, nullptr) == 0); + Require(scePthreadMutexLock(&context.mutex) == 0); + Pthread worker = nullptr; + Require(scePthreadCreate(&worker, nullptr, Worker, &context, "mutex wait") == 0); + while (!context.started.load()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + Require(sceKernelRaiseException(worker, SigUsr1) == 0); + for (int i = 0; i < 5000 && deliveries.load() == 0; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(deliveries.load() == 1); + Require(scePthreadMutexUnlock(&context.mutex) == 0); + Require(scePthreadJoin(worker, nullptr) == 0); + Require(context.acquired.load()); + Require(scePthreadMutexDestroy(&context.mutex) == 0); + Require(sceKernelRemoveExceptionHandler(SigUsr1) == 0); +} diff --git a/core/libs/tests/GuestNamedShmConcurrency.cpp b/core/libs/tests/GuestNamedShmConcurrency.cpp index 60d91794a2..da44c93dba 100644 --- a/core/libs/tests/GuestNamedShmConcurrency.cpp +++ b/core/libs/tests/GuestNamedShmConcurrency.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "nid/Sha1.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -14,16 +16,6 @@ #include #include -extern "C" { -int APS5_VABI shm_open_nid_postfix(const char*, int, int); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*); -int APS5_VABI fchmod_nid_postfix(int, int); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} - namespace { using Unlink = int (*)(const char*); constexpr std::int64_t Page = 0x4000; @@ -143,9 +135,13 @@ void Run() { if (fchmod_nid_postfix(descriptor, 0400) != 0) throw std::runtime_error("changing shared-object permissions to read-only failed"); - *__error_nid_postfix() = 0; - if (unlinkObject(object.name.c_str()) != -1 || *__error_nid_postfix() != 13) - throw std::runtime_error("unlink of a read-only shared object did not return EACCES"); + if (::geteuid() != 0) { + *__error_nid_postfix() = 0; + if (unlinkObject(object.name.c_str()) != -1 || *__error_nid_postfix() != 13) + throw std::runtime_error("unlink of a read-only shared object did not return EACCES"); + } else { + std::fprintf(stderr, "running as root, which ignores file permissions: the read-only unlink case not tested\n"); + } if (fchmod_nid_postfix(descriptor, 0600) != 0) throw std::runtime_error("restoring shared-object permissions failed"); if (unlinkObject(object.name.c_str()) != 0) diff --git a/core/libs/tests/GuestNamedShmDescriptorReuse.cpp b/core/libs/tests/GuestNamedShmDescriptorReuse.cpp index e2b7263e23..6af68772cb 100644 --- a/core/libs/tests/GuestNamedShmDescriptorReuse.cpp +++ b/core/libs/tests/GuestNamedShmDescriptorReuse.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "nid/Sha1.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -15,17 +17,6 @@ #include #include -extern "C" { -int APS5_VABI shm_open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t); -void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; -int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int ReadWrite = 2; constexpr int Create = 0x200; diff --git a/core/libs/tests/GuestNamedShmIoDescriptorReuse.cpp b/core/libs/tests/GuestNamedShmIoDescriptorReuse.cpp index 368fb3cc60..088119df94 100644 --- a/core/libs/tests/GuestNamedShmIoDescriptorReuse.cpp +++ b/core/libs/tests/GuestNamedShmIoDescriptorReuse.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "nid/Sha1.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -14,19 +16,6 @@ #include #include -struct GuestIovec { void* base; std::size_t length; }; -extern "C" { -int APS5_VABI shm_open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI dup2_nid_postfix(int, int); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int); -std::int64_t APS5_VABI writev_nid_postfix(int, const GuestIovec*, int); -std::int64_t APS5_VABI pwritev_nid_postfix(int, const GuestIovec*, int, std::int64_t); -int* APS5_VABI __error_nid_postfix(); -} namespace { int original = -1; int replacement = -1; @@ -76,7 +65,7 @@ void Run(int operation, int boundary) { swapped = false; armed = true; char payload[] = "bad!"; - GuestIovec vector{payload, 4}; + KernelIovec vector{payload, 4}; std::int64_t result = 0; switch (operation) { case 0: result = write_nid_postfix(original, payload, 4); break; @@ -115,6 +104,9 @@ extern "C" ssize_t readlink(const char* path, char* buffer, std::size_t length) } return result; } +extern "C" ssize_t __readlink_chk(const char* path, char* buffer, std::size_t length, std::size_t) { + return readlink(path, buffer, length); +} extern "C" int flock(int descriptor, int operation) { const int result = static_cast(::syscall(SYS_flock, descriptor, operation)); if (result == 0 && operation == LOCK_EX && armed && !replaceAtProbe && !replaceBeforeProbe && !swapped) { diff --git a/core/libs/tests/GuestNamedShmLifecycle.cpp b/core/libs/tests/GuestNamedShmLifecycle.cpp index a5c26b8bee..4c14b9cc3c 100644 --- a/core/libs/tests/GuestNamedShmLifecycle.cpp +++ b/core/libs/tests/GuestNamedShmLifecycle.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "nid/Sha1.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" @@ -15,31 +17,6 @@ #include #include -extern "C" { -int APS5_VABI shm_open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*); -std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int); -int APS5_VABI dup2_nid_postfix(int, int); -void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; -int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; -int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int); -int APS5_VABI open_nid_postfix(const char*, int, int); -std::int64_t APS5_VABI writev_nid_postfix(int, const struct GuestIovec*, int); -std::int64_t APS5_VABI pwritev_nid_postfix(int, const struct GuestIovec*, int, std::int64_t); -int* APS5_VABI __error_nid_postfix(); -} - -struct GuestIovec { - void* base; - std::size_t length; -}; - namespace { constexpr int ReadOnly = 0; constexpr int ReadWrite = 2; @@ -115,7 +92,7 @@ void Run() { Require(write_nid_postfix(descriptor, "xy", 2) == 1, "cross-EOF write did not return its short length"); Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast(Page), "cross-EOF write changed object size"); char vectorBytes[] = "uv"; - GuestIovec vectorWrite{vectorBytes, 2}; + KernelIovec vectorWrite{vectorBytes, 2}; Require(pwritev_nid_postfix(descriptor, nullptr, 0, Page) == 0, "zero-vector positioned write on a writable descriptor failed"); Require(pwritev_nid_postfix(descriptor, &vectorWrite, 1, Page) == 0, "positioned vector write at EOF grew the object"); Require(pwritev_nid_postfix(descriptor, &vectorWrite, 1, Page - 1) == 1, "positioned vector write past EOF was not short"); diff --git a/core/libs/tests/GuestNamedShmOpen.cpp b/core/libs/tests/GuestNamedShmOpen.cpp index dc6cebf447..82abbb8a07 100644 --- a/core/libs/tests/GuestNamedShmOpen.cpp +++ b/core/libs/tests/GuestNamedShmOpen.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "nid/Sha1.hpp" #include @@ -10,9 +12,6 @@ #include #include -extern "C" int APS5_VABI shm_open_nid_postfix(const char*, int, int); -extern "C" int* APS5_VABI __error_nid_postfix(); - static void Require(bool value, const char* message) { if (!value) { throw std::runtime_error(std::string(message) + " (guest errno=" + std::to_string(*__error_nid_postfix()) + ")"); diff --git a/core/libs/tests/GuestNamedShmPermissions.cpp b/core/libs/tests/GuestNamedShmPermissions.cpp index c6f8d4c1bf..332d7f6441 100644 --- a/core/libs/tests/GuestNamedShmPermissions.cpp +++ b/core/libs/tests/GuestNamedShmPermissions.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "nid/Sha1.hpp" #include @@ -9,9 +11,6 @@ #include #include -extern "C" int APS5_VABI shm_open_nid_postfix(const char*, int, int); -extern "C" std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t); - static void Require(bool condition, const char* message) { if (!condition) throw std::runtime_error(message); } diff --git a/core/libs/tests/GuestNamedShmWindows.cpp b/core/libs/tests/GuestNamedShmWindows.cpp new file mode 100644 index 0000000000..401ac8df2e --- /dev/null +++ b/core/libs/tests/GuestNamedShmWindows.cpp @@ -0,0 +1,614 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/SharedMemory.hpp" +#include "nid/Sha1.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace { +constexpr int ReadOnly = 0; +constexpr int ReadWrite = 2; +constexpr int Create = 0x200; +constexpr int Exclusive = 0x800; +constexpr std::int64_t RaceSize = 4096; + +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(std::string(message) + " (guest errno=" + std::to_string(*__error_nid_postfix()) + ")"); +} + +void RequireError(int descriptor, int expected, const char* message) { + Require(descriptor == -1 && *__error_nid_postfix() == expected, message); +} + +void RequireNoAdministratorsAccess(HANDLE handle) { + PACL dacl = nullptr; + PSECURITY_DESCRIPTOR descriptor = nullptr; + const DWORD result = ::GetSecurityInfo(handle, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + nullptr, nullptr, &dacl, nullptr, &descriptor); + if (result != ERROR_SUCCESS || dacl == nullptr) { + if (descriptor != nullptr) ::LocalFree(descriptor); + Require(false, "reading named shared-memory object permissions failed"); + } + SID_IDENTIFIER_AUTHORITY authority = SECURITY_NT_AUTHORITY; + PSID administrators = nullptr; + if (::AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &administrators) == FALSE) { + ::LocalFree(descriptor); + Require(false, "building the Administrators SID failed"); + } + ACL_SIZE_INFORMATION information{}; + const BOOL gotInformation = ::GetAclInformation(dacl, &information, sizeof(information), AclSizeInformation); + bool grantsAdministrators = false; + if (gotInformation) { + for (DWORD index = 0; index < information.AceCount; ++index) { + void* rawAce = nullptr; + if (!::GetAce(dacl, index, &rawAce)) continue; + const auto* header = static_cast(rawAce); + if (header->AceType != ACCESS_ALLOWED_ACE_TYPE) continue; + const auto* ace = static_cast(rawAce); + if (::EqualSid(const_cast(&ace->SidStart), administrators) != FALSE) { + grantsAdministrators = true; + break; + } + } + } + ::FreeSid(administrators); + ::LocalFree(descriptor); + Require(gotInformation && !grantsAdministrators, + "named shared-memory object grants access to the Administrators group"); +} + +void RequireNoUnlinkedFiles(const std::string& name) { + const auto digest = Nid::Sha1(std::vector(name.begin(), name.end())); + std::string prefix; + for (const auto byte : digest) { + prefix += "0123456789abcdef"[byte >> 4]; + prefix += "0123456789abcdef"[byte & 15]; + } + const auto directory = std::filesystem::temp_directory_path() / L"AnyPS5" / L"shm"; + if (!std::filesystem::exists(directory)) return; + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + const auto filename = entry.path().filename().string(); + Require(!filename.starts_with(prefix + "-unlinked-"), + "closing the last descriptor did not reap its unlinked host object"); + } +} + +void VerifyUntrackedDescriptor(int descriptor) { + Require(File::IsSharedMemoryDescriptor_nid_no_patch(descriptor), "named descriptor was not tracked"); + std::vector path(32768); + const DWORD length = ::GetFinalPathNameByHandleW(reinterpret_cast(::_get_osfhandle(descriptor)), + path.data(), static_cast(path.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + Require(length > 0 && length < path.size(), "locating the tracked file failed"); + const HANDLE handle = ::CreateFileW(path.data(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + Require(handle != INVALID_HANDLE_VALUE, "opening an untracked handle to the named file failed"); + const int untracked = ::_open_osfhandle(reinterpret_cast(handle), _O_RDONLY | _O_BINARY); + if (untracked < 0) ::CloseHandle(handle); + Require(untracked >= 0, "creating an untracked descriptor failed"); + const bool classified = File::IsSharedMemoryDescriptor_nid_no_patch(untracked); + Require(::_close(untracked) == 0, "closing the untracked descriptor failed"); + Require(!classified, "an untracked descriptor entered the named shared-memory path"); + Require(!File::IsSharedMemoryDescriptor_nid_no_patch(untracked), "a closed untracked descriptor was classified as shared memory"); +} + +void VerifyConcurrentDescriptorReuse(const std::string& name) { + std::barrier start(3); + std::vector okay(2, 1); + std::vector workers; + for (int index = 0; index < 2; ++index) { + workers.emplace_back([&, index] { + start.arrive_and_wait(); + for (int iteration = 0; iteration < 64; ++iteration) { + const int descriptor = shm_open_nid_postfix(name.c_str(), ReadWrite, 0); + if (descriptor < 0) { okay[index] = 0; break; } + if (!File::IsSharedMemoryDescriptor_nid_no_patch(descriptor) || + pwrite_nid_disambig1_nid_postfix(descriptor, "x", 1, 64) != 0) okay[index] = 0; + if (close_nid_postfix(descriptor) != 0) okay[index] = 0; + } + }); + } + start.arrive_and_wait(); + for (auto& worker : workers) worker.join(); + Require(okay[0] && okay[1], "concurrent close/open lost named-descriptor tracking or bounded writes"); +} + +bool RunResizeWriteRace(const std::string& name) { + const int descriptor = shm_open_nid_postfix(name.c_str(), ReadWrite, 0); + if (descriptor < 0) return false; + bool okay = true; + for (int iteration = 0; iteration < 300; ++iteration) { + const auto size = (iteration & 1) ? 0 : RaceSize; + if (ftruncate_nid_postfix(descriptor, size) != 0) { okay = false; break; } + if (pwrite_nid_disambig1_nid_postfix(descriptor, "x", 1, RaceSize / 2) < 0) { okay = false; break; } + struct _stat64 status{}; + if (::_fstat64(descriptor, &status) != 0 || (status.st_size != 0 && status.st_size != RaceSize)) { + okay = false; + break; + } + } + close_nid_postfix(descriptor); + return okay; +} + +void VerifyCrossProcessResizeWrite(const std::string& name, int descriptor) { + wchar_t executable[32768]{}; + const DWORD length = ::GetModuleFileNameW(nullptr, executable, static_cast(std::size(executable))); + Require(length > 0 && length < std::size(executable), "locating the Windows shared-memory test executable failed"); + std::vector workers; + for (int index = 0; index < 2; ++index) { + std::wstring command = L"\"" + std::wstring(executable, length) + L"\" --race " + std::wstring(name.begin(), name.end()); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + PROCESS_INFORMATION process{}; + Require(::CreateProcessW(nullptr, command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, + nullptr, nullptr, &startup, &process) != FALSE, + "starting a cross-process shared-memory race worker failed"); + workers.push_back(process); + } + const bool localSucceeded = RunResizeWriteRace(name); + bool childrenSucceeded = true; + for (const auto& process : workers) { + childrenSucceeded = childrenSucceeded && ::WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0; + DWORD exitCode = 1; + childrenSucceeded = childrenSucceeded && ::GetExitCodeProcess(process.hProcess, &exitCode) && exitCode == 0; + ::CloseHandle(process.hThread); + ::CloseHandle(process.hProcess); + } + Require(localSucceeded && childrenSucceeded, + "cross-process truncate/write race produced an invalid shared-object size"); + struct _stat64 status{}; + Require(::_fstat64(descriptor, &status) == 0 && (status.st_size == 0 || status.st_size == RaceSize), + "cross-process truncate/write race left an invalid final size"); + Require(shm_unlink_nid_postfix(name.c_str()) == 0, "cross-process race cleanup failed"); +} + +void VerifyResizeReadPosition(const std::string& name) { + constexpr std::size_t payloadSize = 2u << 20; + const int descriptor = shm_open_nid_postfix(name.c_str(), ReadWrite | Create | Exclusive, 0600); + Require(descriptor >= 0 && ftruncate_nid_postfix(descriptor, payloadSize) == 0, + "creating the resize/read object failed"); + const int resizer = dup_nid_postfix(descriptor); + Require(resizer >= 0, "duplicating the resize/read descriptor failed"); + std::vector payload(payloadSize, 'r'); + Require(pwrite_nid_disambig1_nid_postfix(descriptor, payload.data(), payload.size(), 0) == payloadSize && + lseek_nid_postfix(descriptor, 137, 0) == 137, "preparing the resize/read object failed"); + std::barrier start(2); + bool resized = true; + std::thread resizing([&] { + start.arrive_and_wait(); + for (int iteration = 0; iteration < 256; ++iteration) + if (ftruncate_nid_postfix(resizer, payloadSize + ((iteration & 1) ? 4096 : 0)) != 0) resized = false; + }); + start.arrive_and_wait(); + std::vector output(payloadSize); + bool read = true; + for (int iteration = 0; iteration < 32; ++iteration) + if (pread_nid_postfix(descriptor, output.data(), output.size(), 0) != payloadSize || output != payload) read = false; + resizing.join(); + Require(resized && read && lseek_nid_postfix(descriptor, 0, 1) == 137, + "concurrent resize/read changed the shared file position or contents"); + Require(shm_unlink_nid_postfix(name.c_str()) == 0 && close_nid_postfix(descriptor) == 0 && close_nid_postfix(resizer) == 0, + "resize/read cleanup failed"); + RequireNoUnlinkedFiles(name); +} + +void Run() { + const std::string base = "/anyps5-windows-named-" + std::to_string(::GetCurrentProcessId()) + "-" + std::to_string(::GetTickCount64()); + const std::string name = base + "-lifecycle"; + Require(shm_unlink_nid_postfix(name.c_str()) == -1 && *__error_nid_postfix() == 2, + "new test name unexpectedly existed"); + + *__error_nid_postfix() = 73; + const int original = shm_open_nid_postfix(name.c_str(), ReadWrite | Create | Exclusive, 0600); + Require(original >= 0 && *__error_nid_postfix() == 73, "exclusive creation failed or changed guest errno"); + VerifyUntrackedDescriptor(original); + Require(ftruncate_nid_postfix(original, 64) == 0, "sizing the named object failed"); + VerifyConcurrentDescriptorReuse(name); + const char originalBytes[] = "original"; + Require(pwrite_nid_disambig1_nid_postfix(original, originalBytes, sizeof(originalBytes), 0) == sizeof(originalBytes), + "write to named object failed"); + + *__error_nid_postfix() = 0; + RequireError(shm_open_nid_postfix(name.c_str(), ReadWrite | Create | Exclusive, 0600), 17, + "exclusive creation replaced an existing named object"); + const int reader = shm_open_nid_postfix(name.c_str(), ReadOnly, 0); + char contents[sizeof(originalBytes)]{}; + Require(reader >= 0 && pread_nid_postfix(reader, contents, sizeof(contents), 0) == sizeof(contents) && + std::memcmp(contents, originalBytes, sizeof(contents)) == 0, "reopen did not share the original contents"); + *__error_nid_postfix() = 0; + Require(pwrite_nid_disambig1_nid_postfix(reader, "x", 1, 0) == -1 && *__error_nid_postfix() == 9, + "read-only descriptor accepted a write"); + *__error_nid_postfix() = 0; + Require(pwrite_nid_disambig1_nid_postfix(reader, "x", 1, 64) == -1 && *__error_nid_postfix() == 9, + "read-only descriptor accepted a write at end of file"); + *__error_nid_postfix() = 0; + Require(pwrite_nid_disambig1_nid_postfix(reader, "", 0, 64) == -1 && *__error_nid_postfix() == 9, + "read-only descriptor accepted a zero-length write"); + + Require(shm_unlink_nid_postfix(name.c_str()) == 0, "unlink of named object failed"); + *__error_nid_postfix() = 0; + RequireError(shm_open_nid_postfix(name.c_str(), ReadOnly, 0), 2, "unlinked name remained openable"); + std::memset(contents, 0, sizeof(contents)); + Require(pread_nid_postfix(original, contents, sizeof(contents), 0) == sizeof(contents) && + std::memcmp(contents, originalBytes, sizeof(contents)) == 0, "open descriptor stopped working after unlink"); + const char updatedByte = 'X'; + Require(pwrite_nid_disambig1_nid_postfix(original, &updatedByte, 1, 0) == 1, + "open descriptor stopped accepting writes after unlink"); + + const int replacement = shm_open_nid_postfix(name.c_str(), ReadWrite | Create | Exclusive, 0600); + Require(replacement >= 0, "name could not be recreated while old descriptors remained open"); + Require(ftruncate_nid_postfix(replacement, 64) == 0, "sizing the replacement object failed"); + Require(fchmod_nid_postfix(original, 0400) == 0, + "fchmod failed on the old open object after unlink and name recreation"); + Require(fchmod_nid_postfix(original, 0600) == 0, + "restoring mode on the old open object after name recreation failed"); + std::memset(contents, 0x7f, sizeof(contents)); + Require(pread_nid_postfix(replacement, contents, sizeof(contents), 0) == sizeof(contents) && + std::memcmp(contents, "\0\0\0\0\0\0\0\0", sizeof(contents)) == 0, + "recreated name still referred to the unlinked object"); + Require(pwrite_nid_disambig1_nid_postfix(replacement, "y", 1, 0) == 1, + "fchmod on the old descriptor changed the replacement object's permissions"); + const char replacementBytes[] = "replacement"; + Require(pwrite_nid_disambig1_nid_postfix(replacement, replacementBytes, sizeof(replacementBytes), 0) == sizeof(replacementBytes), + "write to replacement object failed"); + std::memset(contents, 0, sizeof(contents)); + Require(pread_nid_postfix(original, contents, sizeof(contents), 0) == sizeof(contents) && + contents[0] == updatedByte && std::memcmp(contents + 1, originalBytes + 1, sizeof(originalBytes) - 2) == 0, + "replacement write changed the old open object"); + Require(shm_unlink_nid_postfix(name.c_str()) == 0, "replacement unlink failed"); + Require(close_nid_postfix(reader) == 0 && close_nid_postfix(original) == 0 && close_nid_postfix(replacement) == 0, + "closing lifecycle descriptors failed"); + RequireNoUnlinkedFiles(name); + + const std::string duplicateName = base + "-duplicate"; + const int duplicateOriginal = shm_open_nid_postfix(duplicateName.c_str(), ReadWrite | Create | Exclusive, 0600); + Require(duplicateOriginal >= 0 && ftruncate_nid_postfix(duplicateOriginal, 64) == 0, + "creating the duplicate test object failed"); + const int duplicate = dup_nid_postfix(duplicateOriginal); + Require(duplicate >= 0 && pwrite_nid_disambig1_nid_postfix(duplicate, "d", 1, 0) == 1, + "dup lost shared-memory write access"); + const int duplicateReader = shm_open_nid_postfix(duplicateName.c_str(), ReadOnly, 0); + Require(duplicateReader >= 0 && shm_unlink_nid_postfix(duplicateName.c_str()) == 0, + "unlinking the duplicate test object failed"); + Require(close_nid_postfix(duplicateOriginal) == 0, "closing the duplicated original failed"); + Require(dup2_nid_postfix(-1, duplicate) == -1, + "dup2 accepted an invalid source"); + Require(pwrite_nid_disambig1_nid_postfix(duplicate, "e", 1, 0) == 1, + "failed dup2 destroyed its shared-memory target"); + Require(dup2_nid_postfix(duplicateReader, duplicate) == duplicate, + "dup2 could not replace a shared-memory target"); + Require(pwrite_nid_disambig1_nid_postfix(duplicate, "f", 1, 0) == -1 && *__error_nid_postfix() == 9, + "dup2 lost shared-memory read-only access"); + Require(close_nid_postfix(duplicateReader) == 0, "closing the duplicate reader failed"); + const std::string replacementName = base + "-duplicate-replacement"; + const int duplicateReplacement = shm_open_nid_postfix(replacementName.c_str(), ReadWrite | Create | Exclusive, 0600); + Require(duplicateReplacement >= 0 && dup2_nid_postfix(duplicateReplacement, duplicate) == duplicate, + "dup2 could not replace the last unlinked descriptor"); + RequireNoUnlinkedFiles(duplicateName); + Require(shm_unlink_nid_postfix(replacementName.c_str()) == 0 && + close_nid_postfix(duplicateReplacement) == 0 && close_nid_postfix(duplicate) == 0, + "cleaning up dup2 replacement descriptors failed"); + RequireNoUnlinkedFiles(replacementName); + VerifyResizeReadPosition(base + "-resize-read"); + + const std::string concurrentName = base + "-exclusive-race"; + constexpr int contenderCount = 12; + std::vector descriptors(contenderCount, -2); + std::vector errors(contenderCount, 0); + std::vector contenders; + contenders.reserve(contenderCount); + for (int index = 0; index < contenderCount; ++index) { + contenders.emplace_back([&, index] { + descriptors[index] = shm_open_nid_postfix(concurrentName.c_str(), ReadWrite | Create | Exclusive, 0600); + errors[index] = *__error_nid_postfix(); + }); + } + for (auto& contender : contenders) contender.join(); + int created = 0; + for (int index = 0; index < contenderCount; ++index) { + if (descriptors[index] >= 0) { + ++created; + Require(close_nid_postfix(descriptors[index]) == 0, "closing exclusive-race descriptor failed"); + } else { + Require(errors[index] == 17, "exclusive-race loser did not receive EEXIST"); + } + } + Require(created == 1, "exclusive create race produced more or fewer than one winner"); + Require(shm_unlink_nid_postfix(concurrentName.c_str()) == 0, "exclusive-race cleanup failed"); + + const std::string raceName = base + "-cross-process-race"; + const int raceDescriptor = shm_open_nid_postfix(raceName.c_str(), ReadWrite | Create | Exclusive, 0600); + Require(raceDescriptor >= 0 && ftruncate_nid_postfix(raceDescriptor, RaceSize) == 0, + "cross-process race object creation failed"); + VerifyCrossProcessResizeWrite(raceName, raceDescriptor); + Require(close_nid_postfix(raceDescriptor) == 0, "closing cross-process race descriptor failed"); + + const std::string permissionsName = base + "-permissions"; + const int restricted = shm_open_nid_postfix(permissionsName.c_str(), ReadWrite | Create | Exclusive, 0400); + Require(restricted >= 0, "read-only mode creation failed"); + RequireNoAdministratorsAccess(reinterpret_cast(::_get_osfhandle(restricted))); + Require(close_nid_postfix(restricted) == 0, "closing read-only mode object failed"); + *__error_nid_postfix() = 0; + RequireError(shm_open_nid_postfix(permissionsName.c_str(), ReadWrite, 0), 13, + "mode 0400 allowed a later read-write open"); + const int restrictedReader = shm_open_nid_postfix(permissionsName.c_str(), ReadOnly, 0); + Require(restrictedReader >= 0, "mode 0400 rejected a read-only open"); + Require(fchmod_nid_postfix(restrictedReader, 0600) == 0, + "fchmod could not restore write permission on a named object"); + const int restoredWriter = shm_open_nid_postfix(permissionsName.c_str(), ReadWrite, 0); + Require(restoredWriter >= 0, "mode 0600 after fchmod rejected read-write open"); + Require(fchmod_nid_postfix(restoredWriter, 0400) == 0, + "fchmod could not remove write permission on a named object"); + *__error_nid_postfix() = 0; + Require(shm_unlink_nid_postfix(permissionsName.c_str()) == -1 && *__error_nid_postfix() == 13, + "unlink of a read-only named object did not return EACCES"); + Require(fchmod_nid_postfix(restoredWriter, 0600) == 0, + "fchmod could not restore permission before unlink"); + Require(shm_unlink_nid_postfix(permissionsName.c_str()) == 0, + "owner could not unlink a writable named object"); + Require(close_nid_postfix(restoredWriter) == 0, "closing restored writer failed"); + Require(close_nid_postfix(restrictedReader) == 0, "closing restricted reader failed"); + + const std::string noPermissionsName = base + "-mode-zero"; + const int noPermissions = shm_open_nid_postfix(noPermissionsName.c_str(), ReadWrite | Create | Exclusive, 0000); + Require(noPermissions >= 0 && fchmod_nid_postfix(noPermissions, 0600) == 0, + "fchmod could not restore access to a mode 0000 named object"); + Require(shm_unlink_nid_postfix(noPermissionsName.c_str()) == 0 && close_nid_postfix(noPermissions) == 0, + "mode 0000 object cleanup failed"); + + *__error_nid_postfix() = 0; + RequireError(shm_open_nid_postfix(nullptr, ReadWrite, 0), 14, "null name did not return EFAULT"); + *__error_nid_postfix() = 0; + RequireError(shm_open_nid_postfix("relative", ReadWrite, 0), 22, "relative name did not return EINVAL"); + std::puts("Windows named shared-memory lifecycle checks passed"); +} + +bool EnvironmentEntryHasKey(const std::wstring& entry, const wchar_t* key) { + const auto separator = entry.find(L'='); + return separator != std::wstring::npos && separator == std::wcslen(key) && + ::CompareStringOrdinal(entry.data(), static_cast(separator), key, static_cast(separator), TRUE) == CSTR_EQUAL; +} + +std::vector EnvironmentWithTempPath(const std::wstring& path) { + std::vector entries; + LPWCH environment = ::GetEnvironmentStringsW(); + if (environment == nullptr) throw std::runtime_error("reading the Windows environment failed"); + try { + for (const wchar_t* current = environment; *current != L'\0'; current += std::wcslen(current) + 1) { + std::wstring entry(current); + if (!EnvironmentEntryHasKey(entry, L"TMP") && !EnvironmentEntryHasKey(entry, L"TEMP")) + entries.push_back(std::move(entry)); + } + } catch (...) { + ::FreeEnvironmentStringsW(environment); + throw; + } + ::FreeEnvironmentStringsW(environment); + entries.push_back(L"TMP=" + path); + entries.push_back(L"TEMP=" + path); + std::sort(entries.begin(), entries.end(), [](const auto& left, const auto& right) { + return ::CompareStringOrdinal(left.c_str(), -1, right.c_str(), -1, TRUE) == CSTR_LESS_THAN; + }); + std::vector block; + for (const auto& entry : entries) { + block.insert(block.end(), entry.begin(), entry.end()); + block.push_back(L'\0'); + } + block.push_back(L'\0'); + return block; +} + +bool SameDirectory(HANDLE left, HANDLE right) { + BY_HANDLE_FILE_INFORMATION leftInfo{}, rightInfo{}; + return ::GetFileInformationByHandle(left, &leftInfo) && ::GetFileInformationByHandle(right, &rightInfo) && + leftInfo.dwVolumeSerialNumber == rightInfo.dwVolumeSerialNumber && + leftInfo.nFileIndexHigh == rightInfo.nFileIndexHigh && leftInfo.nFileIndexLow == rightInfo.nFileIndexLow; +} + +std::filesystem::path ExtendedPath(const std::filesystem::path& path) { + wchar_t fullPath[32768]{}; + const DWORD length = ::GetFullPathNameW(path.c_str(), static_cast(std::size(fullPath)), fullPath, nullptr); + if (length == 0 || length >= std::size(fullPath)) + throw std::runtime_error("getting the full private temporary path failed"); + std::wstring full(fullPath, length); + if (full.starts_with(L"\\\\?\\")) return full; + if (full.starts_with(L"\\\\")) return L"\\\\?\\UNC\\" + full.substr(2); + return L"\\\\?\\" + full; +} + +std::filesystem::path DosPath(const std::filesystem::path& path) { + const auto& native = path.native(); + if (native.starts_with(L"\\\\?\\UNC\\")) return L"\\\\" + native.substr(8); + if (native.starts_with(L"\\\\?\\")) return native.substr(4); + return path; +} + +void RemovePrivateDirectory(const std::filesystem::path& directory) { + WIN32_FIND_DATAW entry{}; + const HANDLE search = ::FindFirstFileW((directory / L"*").c_str(), &entry); + if (search == INVALID_HANDLE_VALUE) { + if (::GetLastError() != ERROR_FILE_NOT_FOUND) + throw std::runtime_error("enumerating the private temporary directory for cleanup failed"); + } else { + try { + do { + if (std::wcscmp(entry.cFileName, L".") == 0 || std::wcscmp(entry.cFileName, L"..") == 0) continue; + if (entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + throw std::runtime_error("the private temporary directory contains a reparse point"); + const auto child = directory / entry.cFileName; + if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) RemovePrivateDirectory(child); + else if (!::DeleteFileW(child.c_str())) + throw std::runtime_error("removing a private temporary file failed"); + } while (::FindNextFileW(search, &entry)); + if (::GetLastError() != ERROR_NO_MORE_FILES) + throw std::runtime_error("enumerating the private temporary directory for cleanup failed"); + } catch (...) { + ::FindClose(search); + throw; + } + ::FindClose(search); + } + if (!::RemoveDirectoryW(directory.c_str())) + throw std::runtime_error("removing the private temporary directory failed"); +} + +int RunWithShortTempPath(bool longPath) { + wchar_t tempBuffer[32768]{}; + const DWORD tempLength = ::GetTempPathW(static_cast(std::size(tempBuffer)), tempBuffer); + if (tempLength == 0 || tempLength >= std::size(tempBuffer)) + throw std::runtime_error("locating the Windows temporary directory failed"); + const std::filesystem::path parent(std::wstring(tempBuffer, tempLength)); + std::filesystem::path baseRoot; + bool created = false; + for (unsigned attempt = 0; attempt < 16; ++attempt) { + baseRoot = parent / (L"AnyPS5-Temp-Regression-" + std::to_wstring(::GetCurrentProcessId()) + L"-" + + std::to_wstring(::GetTickCount64()) + L"-" + std::to_wstring(attempt)); + if (::CreateDirectoryW(baseRoot.c_str(), nullptr)) { + created = true; + break; + } + if (::GetLastError() != ERROR_ALREADY_EXISTS) throw std::runtime_error("creating a private temporary directory failed"); + } + if (!created) throw std::runtime_error("creating a unique private temporary directory failed"); + const auto cleanupRoot = ExtendedPath(baseRoot); + std::filesystem::path longRoot = cleanupRoot; + const auto cleanup = [&] { + RemovePrivateDirectory(cleanupRoot); + }; + + try { + std::vector shortBuffer(32768); + DWORD shortLength = ::GetShortPathNameW(longRoot.c_str(), shortBuffer.data(), static_cast(shortBuffer.size())); + if (shortLength == 0) + throw std::runtime_error("GetShortPathNameW failed (Win32 error " + std::to_string(::GetLastError()) + ")"); + if (shortLength >= shortBuffer.size()) + throw std::runtime_error("GetShortPathNameW required a buffer of " + std::to_string(shortLength) + " characters"); + std::filesystem::path shortRoot(std::wstring(shortBuffer.data(), shortLength)); + if (longPath) { + bool aliasAvailable = true; + bool longAliasReady = false; + for (unsigned level = 0; level < 80; ++level) { + longRoot /= L"LongSharedMemoryTempDirectoryRegressionSegment"; + if (!::CreateDirectoryW(longRoot.c_str(), nullptr)) + throw std::runtime_error("creating the nested long temporary directory failed (Win32 error " + std::to_string(::GetLastError()) + ")"); + shortLength = ::GetShortPathNameW(longRoot.c_str(), shortBuffer.data(), static_cast(shortBuffer.size())); + if (shortLength == 0) + throw std::runtime_error("GetShortPathNameW failed for nested path (Win32 error " + std::to_string(::GetLastError()) + ")"); + if (shortLength >= shortBuffer.size()) + throw std::runtime_error("GetShortPathNameW required a buffer of " + std::to_string(shortLength) + " characters"); + shortRoot = std::filesystem::path(std::wstring(shortBuffer.data(), shortLength)); + if (::CompareStringOrdinal(longRoot.filename().c_str(), -1, shortRoot.filename().c_str(), -1, TRUE) == CSTR_EQUAL) { + aliasAvailable = false; + break; + } + if (DosPath(longRoot).native().size() > MAX_PATH + 64 && DosPath(shortRoot).native().size() < MAX_PATH - 64) { + longAliasReady = true; + break; + } + } + if (!aliasAvailable) { + std::puts("long-short-temp regression skipped: this volume did not create an 8.3 alias for the nested directory"); + cleanup(); + return 77; + } + if (!longAliasReady) + throw std::runtime_error("nested private path did not exceed MAX_PATH with a usable short alias"); + } + if (::CompareStringOrdinal(longRoot.filename().c_str(), -1, shortRoot.filename().c_str(), -1, TRUE) == CSTR_EQUAL) { + std::puts("short-temp regression skipped: this volume did not create an 8.3 alias for the private directory"); + cleanup(); + return 77; + } + const HANDLE longHandle = ::CreateFileW(longRoot.c_str(), FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (longHandle == INVALID_HANDLE_VALUE) throw std::runtime_error("opening the long temporary path failed"); + const HANDLE shortHandle = ::CreateFileW(shortRoot.c_str(), FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (shortHandle == INVALID_HANDLE_VALUE) { + ::CloseHandle(longHandle); + throw std::runtime_error("opening the 8.3 temporary alias failed"); + } + const bool isSameDirectory = SameDirectory(longHandle, shortHandle); + ::CloseHandle(shortHandle); + ::CloseHandle(longHandle); + if (!isSameDirectory) throw std::runtime_error("the short temporary path did not identify the created directory"); + std::wprintf(L"%ls regression verified 8.3 directory alias (%llu/%llu characters):\n%ls\n%ls\n", + longPath ? L"long-short-temp" : L"short-temp", + static_cast(longRoot.native().size()), + static_cast(shortRoot.native().size()), longRoot.c_str(), shortRoot.c_str()); + std::fflush(stdout); + + wchar_t executable[32768]{}; + const DWORD executableLength = ::GetModuleFileNameW(nullptr, executable, static_cast(std::size(executable))); + if (executableLength == 0 || executableLength >= std::size(executable)) + throw std::runtime_error("locating the Windows shared-memory test executable failed"); + std::wstring command = L"\"" + std::wstring(executable, executableLength) + L"\" --short-temp-child"; + auto environment = EnvironmentWithTempPath(DosPath(shortRoot).native()); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + PROCESS_INFORMATION process{}; + if (!::CreateProcessW(nullptr, command.data(), nullptr, nullptr, FALSE, + CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, environment.data(), nullptr, &startup, &process)) + throw std::runtime_error("starting the short temporary path child failed"); + const DWORD wait = ::WaitForSingleObject(process.hProcess, 16000); + DWORD exitCode = 1; + const bool childSucceeded = wait == WAIT_OBJECT_0 && ::GetExitCodeProcess(process.hProcess, &exitCode) && exitCode == 0; + if (wait != WAIT_OBJECT_0) { + ::TerminateProcess(process.hProcess, 1); + ::WaitForSingleObject(process.hProcess, 1000); + } + if (!childSucceeded) + std::fprintf(stderr, "%s child failed: wait=%lu exit=%lu\n", longPath ? "long-short-temp" : "short-temp", + static_cast(wait), static_cast(exitCode)); + ::CloseHandle(process.hThread); + ::CloseHandle(process.hProcess); + cleanup(); + return childSucceeded ? 0 : 1; + } catch (...) { + cleanup(); + throw; + } +} +} + +int main(int argumentCount, char** arguments) { + try { + if (argumentCount == 3 && std::string_view(arguments[1]) == "--race") + return RunResizeWriteRace(arguments[2]) ? 0 : 1; + if (argumentCount == 2 && std::string_view(arguments[1]) == "--short-temp") + return RunWithShortTempPath(false); + if (argumentCount == 2 && std::string_view(arguments[1]) == "--long-short-temp") + return RunWithShortTempPath(true); + if (argumentCount == 2 && std::string_view(arguments[1]) == "--short-temp-child") { + Run(); + return 0; + } + Run(); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/GuestNanosleep.cpp b/core/libs/tests/GuestNanosleep.cpp index c97b6c1f13..6ae6cbbd17 100644 --- a/core/libs/tests/GuestNanosleep.cpp +++ b/core/libs/tests/GuestNanosleep.cpp @@ -1,20 +1,14 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include -extern "C" { -int APS5_VABI nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp); -int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp); -int APS5_VABI sceKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmtp); -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); -int* APS5_VABI __error_nid_postfix(); -} - using Nanosleep = int (APS5_VABI *)(const KernelTimespec*, KernelTimespec*); static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EFAULT = static_cast(0x8002000E); -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static constexpr int GUEST_EACCES = 13; static constexpr int GUEST_EFAULT = 14; diff --git a/core/libs/tests/GuestNanosleepLongRequest.cpp b/core/libs/tests/GuestNanosleepLongRequest.cpp new file mode 100644 index 0000000000..c68722401f --- /dev/null +++ b/core/libs/tests/GuestNanosleepLongRequest.cpp @@ -0,0 +1,54 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "SceTypes.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#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::abort(); +} + +static constexpr auto ObservationTime = std::chrono::milliseconds(1500); +static std::atomic started{false}; +static std::atomic returned{false}; +static std::int64_t requestedSeconds = 0; +static bool posix = true; + +static void* APS5_VABI Sleeper(void*) { + const KernelTimespec request{requestedSeconds, 0}; + started.store(true); + if (posix) nanosleep_nid_postfix(&request, nullptr); + else sceKernelNanosleep(&request, nullptr); + returned.store(true); + return nullptr; +} + +static void SleepsPastTheObservation(std::int64_t seconds, bool usePosix) { + started.store(false); + returned.store(false); + requestedSeconds = seconds; + posix = usePosix; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, Sleeper, nullptr, nullptr) == 0); + while (!started.load()) std::this_thread::yield(); + std::this_thread::sleep_for(ObservationTime); + Require(!returned.load()); +} + +int main() { + for (const bool usePosix : {true, false}) { + SleepsPastTheObservation(INT64_MAX, usePosix); + SleepsPastTheObservation(18446744074LL, usePosix); + SleepsPastTheObservation(std::int64_t{1} << 40, usePosix); + } + std::fflush(nullptr); + std::_Exit(0); +} diff --git a/core/libs/tests/GuestNetBufferValidation.cpp b/core/libs/tests/GuestNetBufferValidation.cpp new file mode 100644 index 0000000000..cf734de324 --- /dev/null +++ b/core/libs/tests/GuestNetBufferValidation.cpp @@ -0,0 +1,73 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/Socket/include/SocketPoll.hpp" + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "check failed at line %u\n", location.line()); + std::abort(); + } +} + +int main() { + Require(sceNetInit_nid_postfix() == 0); + for (const int family : {2, 28}) { + for (const int type : {1, 2}) { + const int socket = sceNetSocket("buffer-validation", family, type, type == 1 ? 6 : 17); + Require(socket >= 0); + std::shared_ptr owner; + std::uintptr_t native = 0; + Require(KernelFindSocketNative_nid_no_patch(socket, &owner, &native)); + for (const int option : {0x1001, 0x1002}) { + const auto nativeBuffer = [&] { + int value = 0; +#ifdef _WIN32 + int size = sizeof(value); +#else + socklen_t size = sizeof(value); +#endif + Require(::getsockopt(native, SOL_SOCKET, option == 0x1001 ? SO_SNDBUF : SO_RCVBUF, + reinterpret_cast(&value), &size) == 0); + return value; + }; + const int initial = 32768; + Require(sceNetSetsockopt(socket, 0xffff, option, &initial, sizeof(initial)) == 0); + const int originalNative = nativeBuffer(); + for (const int invalid : {0, -1, -4096, INT_MIN}) { + Require(sceNetSetsockopt(socket, 0xffff, option, &invalid, sizeof(invalid)) == static_cast(0x80410116u)); + Require(*sceNetErrnoLoc() == 22); + Require(setsockopt_nid_postfix(socket, 0xffff, option, &invalid, sizeof(invalid)) == -1); + Require(*__error_nid_postfix() == 22); + int value = 0; + std::uint32_t size = sizeof(value); + Require(sceNetGetsockopt(socket, 0xffff, option, &value, &size) == 0 && value == initial); + Require(nativeBuffer() == originalNative); + } + Require(sceNetSetsockopt(socket, 0xffff, option, nullptr, sizeof(initial)) == static_cast(0x80410116u)); + Require(sceNetSetsockopt(socket, 0xffff, option, &initial, 3) == static_cast(0x80410116u)); + const int valid = 8192; + Require(sceNetSetsockopt(socket, 0xffff, option, &valid, sizeof(valid)) == 0); + int value = 0; + std::uint32_t size = sizeof(value); + Require(sceNetGetsockopt(socket, 0xffff, option, &value, &size) == 0 && value == valid); + } + Require(sceNetSocketClose(socket) == 0); + } + } + const int zero = 0; + Require(sceNetSetsockopt(-1, 0xffff, 0x1001, &zero, sizeof(zero)) == static_cast(0x80410109u)); + Require(*sceNetErrnoLoc() == 9); +} diff --git a/core/libs/tests/GuestNetCtl.cpp b/core/libs/tests/GuestNetCtl.cpp index ea68f8eb02..9a0e595f2b 100644 --- a/core/libs/tests/GuestNetCtl.cpp +++ b/core/libs/tests/GuestNetCtl.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNetCtl/include/NetCtl.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -31,23 +32,13 @@ struct NetCtlDefaultRoute { }; #endif + extern "C" { -int APS5_VABI sceNetCtlCheckCallback(void); -int APS5_VABI sceNetCtlGetInfo(int, NetCtlInfo*); -int APS5_VABI sceNetCtlGetNatInfo(NetCtlNatInfo*); -int APS5_VABI sceNetCtlGetState(int*); -int APS5_VABI sceNetCtlGetStateV6(int*); -int APS5_VABI sceNetCtlGetInfoV6(int, void*); -int APS5_VABI sceNetCtlGetResult(int, int*); -int APS5_VABI sceNetCtlGetResultV6(int, int*); + #ifdef __APPLE__ std::size_t NetCtlDefaultRoutes_nid_no_patch(const char*, std::size_t, NetCtlDefaultRoute*, std::size_t); #endif -int APS5_VABI sceNetCtlRegisterCallback(NetCtlCallback, void*, int*); -int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback, void*, int*); -void APS5_VABI sceNetCtlTerm(void); -int APS5_VABI sceNetCtlUnregisterCallback(int); -int APS5_VABI sceNetCtlUnregisterCallbackV6(int); + } namespace { @@ -141,13 +132,19 @@ void CheckCallbacks() { Require(sceNetCtlRegisterCallback(RecordSecond, &secondProbe, &second) == 0 && second == 1); sceNetCtlTerm(); Require(sceNetCtlCheckCallback() == 0); - Require(g_first.calls == 1 && g_second.calls == 1); + Require(g_first.calls == 1 && g_second.calls == 2); + Require(sceNetCtlInit() == 0); + Require(sceNetCtlUnregisterCallback(first) == 0); + Require(sceNetCtlUnregisterCallback(second) == 0); + Require(sceNetCtlUnregisterCallbackV6(v6) == 0); + Require(sceNetCtlUnregisterCallback(first) == static_cast(0x80412104)); + Require(sceNetCtlUnregisterCallbackV6(v6) == static_cast(0x80412104)); Require(sceNetCtlRegisterCallbackV6(RecordV6, &v6Probe, &v6) == 0 && v6 == 0); Require(sceNetCtlRegisterCallback(RecordFirst, &secondProbe, &first) == 0 && first == 0); Require(sceNetCtlCheckCallback() == 0); Require(g_first.calls == 2 && g_first.event == event && g_first.arg == &secondProbe); - Require(g_second.calls == 1 && g_self.calls == 1 && g_v6.calls == 0); + Require(g_second.calls == 2 && g_self.calls == 1 && g_v6.calls == 0); Require(sceNetCtlUnregisterCallback(first) == 0); Require(sceNetCtlUnregisterCallbackV6(v6) == 0); } @@ -263,6 +260,20 @@ int main() { #ifdef __APPLE__ CheckDefaultRoutes(); #endif + constexpr int invalidId = static_cast(0x80412105); + Require(sceNetCtlCheckCallback() == 0); + Require(sceNetCtlGetState(nullptr) == kErrInvalidAddr); + Require(sceNetCtlGetStateV6(nullptr) == kErrInvalidAddr); + Require(sceNetCtlGetInfo(0, nullptr) == kErrInvalidAddr); + Require(sceNetCtlGetResult(0, nullptr) == kErrInvalidAddr); + Require(sceNetCtlGetNatInfo(nullptr) == kErrInvalidAddr); + Require(sceNetCtlRegisterCallback(nullptr, nullptr, nullptr) == kErrInvalidAddr); + Require(sceNetCtlRegisterCallbackV6(nullptr, nullptr, nullptr) == kErrInvalidAddr); + Require(sceNetCtlUnregisterCallback(-1) == invalidId); + Require(sceNetCtlUnregisterCallbackV6(-1) == invalidId); + Require(sceNetCtlRegisterCallbackForNpToolkit(nullptr, nullptr, nullptr) == kErrInvalidAddr); + Require(sceNetCtlUnregisterCallbackForNpToolkit(-1) == invalidId); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0); CheckCallbacks(); Require(sceNetCtlGetStateV6(nullptr) == kErrInvalidAddr); @@ -270,29 +281,26 @@ int main() { Require(sceNetCtlGetStateV6(&stateV6) == 0); Require(stateV6 == 0 || stateV6 == kStateIpObtained); Require(sceNetCtlGetResult(0, nullptr) == kErrInvalidAddr); - int errorCode = -1; - Require(sceNetCtlGetResult(0, &errorCode) == 0 && errorCode == 0); + Require(sceNetCtlGetResult(kEventDisconnected, nullptr) == kErrInvalidAddr); + for (const int eventType : {kEventDisconnected, 2, kEventIpObtained}) { + int errorCode = -1; + Require(sceNetCtlGetResult(eventType, &errorCode) == 0 && errorCode == 0); + } Require(sceNetCtlGetResultV6(kEventIpObtained, nullptr) == kErrInvalidAddr); for (const int eventType : {kEventDisconnected, 2, kEventIpObtained}) { int errorCodeV6 = -1; Require(sceNetCtlGetResultV6(eventType, &errorCodeV6) == 0 && errorCodeV6 == 0); } - for (const int eventType : {0, 4, -1}) { - int errorCodeV6 = -1; - bool threw = false; - try { sceNetCtlGetResultV6(eventType, &errorCodeV6); } catch (const std::runtime_error&) { threw = true; } - Require(threw && errorCodeV6 == -1); - } NetCtlInfo info{}; Require(sceNetCtlGetInfo(0, &info) == kErrInvalidCode); Require(sceNetCtlGetInfo(22, &info) == kErrInvalidCode); Require(sceNetCtlGetInfo(14, nullptr) == kErrInvalidAddr); std::uint8_t infoV6[256] = {}; - Require(sceNetCtlGetInfoV6(0, infoV6) == kErrInvalidCode); - Require(sceNetCtlGetInfoV6(6, infoV6) == kErrInvalidCode); + Require(sceNetCtlGetInfoV6(0, reinterpret_cast(infoV6)) == kErrInvalidCode); + Require(sceNetCtlGetInfoV6(6, reinterpret_cast(infoV6)) == kErrInvalidCode); Require(sceNetCtlGetInfoV6(1, nullptr) == kErrInvalidAddr); - if (stateV6 != kStateIpObtained) Require(sceNetCtlGetInfoV6(1, infoV6) == kErrNotConnected); + if (stateV6 != kStateIpObtained) Require(sceNetCtlGetInfoV6(1, reinterpret_cast(infoV6)) == kErrNotConnected); const int result = sceNetCtlGetInfo(14, &info); if (result == kErrNotConnected) { @@ -335,6 +343,19 @@ int main() { } Require(sceNetCtlGetNatInfo(nullptr) == kErrInvalidAddr); - NetCtlNatInfo nat_info{}; - Require(sceNetCtlGetNatInfo(&nat_info) == kErrNotConnected); + NetCtlNatInfo natInfo{}; + for (const auto size : {0u, 15u, 17u, 128u, 0xffffffffu}) { + natInfo.size = size; + Require(sceNetCtlGetNatInfo(&natInfo) == static_cast(0x80412111)); + } + natInfo.size = sizeof(natInfo); + std::memset(natInfo.opaque, kFill, sizeof(natInfo.opaque)); + bool natUnsupported = false; + try { + sceNetCtlGetNatInfo(&natInfo); + } catch (const std::logic_error&) { + natUnsupported = true; + } + Require(natUnsupported && natInfo.size == sizeof(natInfo)); + for (const auto value : natInfo.opaque) Require(value == kFill); } diff --git a/core/libs/tests/GuestNetCtlAp.cpp b/core/libs/tests/GuestNetCtlAp.cpp index fbb8b0f01d..d9db3b4796 100644 --- a/core/libs/tests/GuestNetCtlAp.cpp +++ b/core/libs/tests/GuestNetCtlAp.cpp @@ -1,23 +1,11 @@ +#include "prx/libSceNetCtlAp/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include +#include #include -extern "C" { -int APS5_VABI sceNetCtlApInit(void); -void APS5_VABI sceNetCtlApTerm(void); -int APS5_VABI sceNetCtlApCheckCallback(void); -int APS5_VABI sceNetCtlApClearEvent(int eventType); -int APS5_VABI sceNetCtlApRegisterCallback(void (APS5_VABI *func)(int, void*), void* arg, int* cid); -int APS5_VABI sceNetCtlApUnregisterCallback(int cid); -int APS5_VABI sceNetCtlApGetResult(int eventType, int* errorCode); -int APS5_VABI sceNetCtlApGetInfo(void* info); -int APS5_VABI sceNetCtlApGetState(int* state); -int APS5_VABI sceNetCtlApStop(void); -int APS5_VABI sceNetCtlApGetConnectInfo(std::uint8_t* optStr, void* connectInfo); -} - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -74,9 +62,13 @@ int main() { std::memset(connect, 0x5a, sizeof(connect)); const std::uint64_t connectSize = sizeof(connect); std::memcpy(connect, &connectSize, sizeof(connectSize)); - std::uint8_t option[] = "opt"; + const char option[] = "opt"; Require(sceNetCtlApGetConnectInfo(option, connect) == kErrNotStarted); Require(sceNetCtlApGetConnectInfo(nullptr, connect) == kErrNotStarted); + Require(sceNetCtlApGetConnectInfo("", connect) == kErrNotStarted); + Require(sceNetCtlApGetConnectInfo("12345678", connect) == kErrNotStarted); + Require(sceNetCtlApGetConnectInfo("123456789", nullptr) == static_cast(0x80412901u)); + Require(sceNetCtlApGetConnectInfo("123456789", connect) == static_cast(0x80412901u)); for (std::size_t i = sizeof(connectSize); i < sizeof(connect); ++i) Require(connect[i] == 0x5a); Require(sceNetCtlApGetConnectInfo(option, nullptr) == kErrInvalidAddr); const std::uint64_t wrongConnectSize = sizeof(info); @@ -95,6 +87,7 @@ int main() { int code = 0x5a5a; Require(sceNetCtlApClearEvent(type) == kErrInvalidType); Require(sceNetCtlApGetResult(type, &code) == kErrInvalidType && code == 0x5a5a); + Require(sceNetCtlApGetResult(type, nullptr) == kErrInvalidAddr); } int cids[8]; @@ -102,7 +95,7 @@ int main() { cids[i] = -1; Require(sceNetCtlApRegisterCallback(Callback, &cids[i], &cids[i]) == 0 && cids[i] == i); } - int extra = -1; + int extra = 0x5a5a; Require(sceNetCtlApRegisterCallback(Callback, nullptr, &extra) == kErrCallbackMax && extra == -1); Require(sceNetCtlApRegisterCallback(nullptr, nullptr, &extra) == kErrInvalidAddr); Require(sceNetCtlApRegisterCallback(Callback, nullptr, nullptr) == kErrInvalidAddr); @@ -117,6 +110,7 @@ int main() { bool threw = false; try { sceNetCtlApInit(); + } catch (const std::runtime_error&) { threw = true; } @@ -130,5 +124,6 @@ int main() { Require(sceNetCtlApGetState(&state) == 0 && state == 0); sceNetCtlApTerm(); sceNetCtlApTerm(); + RequireUninitialized(); } diff --git a/core/libs/tests/GuestNetCtlApDialog.cpp b/core/libs/tests/GuestNetCtlApDialog.cpp index bdce6ca102..82b0318b87 100644 --- a/core/libs/tests/GuestNetCtlApDialog.cpp +++ b/core/libs/tests/GuestNetCtlApDialog.cpp @@ -1,17 +1,9 @@ +#include "prx/libSceNetCtlApDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include - -extern "C" { -int APS5_VABI sceNetCtlApDialogInitialize(void); -int APS5_VABI sceNetCtlApDialogTerminate(void); -int APS5_VABI sceNetCtlApDialogOpen(const void* param); -int APS5_VABI sceNetCtlApDialogClose(void); -int APS5_VABI sceNetCtlApDialogUpdateStatus(void); -int APS5_VABI sceNetCtlApDialogGetStatus(void); -int APS5_VABI sceNetCtlApDialogGetResult(void* result); -} +#include "prx/libSceCommonDialog/CommonDialog.hpp" namespace { @@ -37,14 +29,17 @@ void RequireCanceled(const std::uint8_t (&result)[44]) { } int main() { - std::uint8_t param[104] = {}; - std::uint8_t result[44]; + NetCtlApDialogParam param{}; + param.base.size = sizeof(param.base); + param.base.magic = static_cast(reinterpret_cast(¶m)) - 1060003575u; + param.size = sizeof(param); + alignas(NetCtlApDialogResult) std::uint8_t result[44]; std::memset(result, 0x5a, sizeof(result)); Require(sceNetCtlApDialogTerminate() == kErrNotInitialized); - Require(sceNetCtlApDialogOpen(param) == kErrNotInitialized); + Require(sceNetCtlApDialogOpen(¶m) == kErrNotInitialized); Require(sceNetCtlApDialogClose() == kErrNotInitialized); - Require(sceNetCtlApDialogGetResult(result) == kErrNotInitialized); + Require(sceNetCtlApDialogGetResult(reinterpret_cast(result)) == kErrNotInitialized); Require(sceNetCtlApDialogGetStatus() == kStatusNone); Require(sceNetCtlApDialogUpdateStatus() == kStatusNone); @@ -55,27 +50,39 @@ int main() { Require(sceNetCtlApDialogOpen(nullptr) == kErrArgNull); Require(sceNetCtlApDialogGetStatus() == kStatusInitialized); - Require(sceNetCtlApDialogGetResult(result) == kErrNotFinished); + Require(sceNetCtlApDialogGetResult(reinterpret_cast(result)) == static_cast(0x80B8000Au)); + std::memset(result + 4, 0, 36); + Require(sceNetCtlApDialogGetResult(reinterpret_cast(result)) == kErrNotFinished); - Require(sceNetCtlApDialogOpen(param) == 0); + param.size = 103; + Require(sceNetCtlApDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.size = sizeof(param); + param.reserved60 = 1; + Require(sceNetCtlApDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.reserved60 = 0; + std::memset(param.option, 'a', sizeof(param.option)); + Require(sceNetCtlApDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.option[8] = 0; + param.reserved[30] = 1; + Require(sceNetCtlApDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.reserved[30] = 0; + Require(sceNetCtlApDialogOpen(¶m) == 0); Require(sceNetCtlApDialogGetStatus() == kStatusFinished); Require(sceNetCtlApDialogUpdateStatus() == kStatusFinished); Require(sceNetCtlApDialogGetResult(nullptr) == kErrArgNull); - Require(sceNetCtlApDialogGetResult(result) == 0); + Require(sceNetCtlApDialogGetResult(reinterpret_cast(result)) == kResultUserCanceled); RequireCanceled(result); - Require(sceNetCtlApDialogOpen(param) == 0); - Require(sceNetCtlApDialogClose() == 0); + Require(sceNetCtlApDialogOpen(¶m) == 0); + Require(sceNetCtlApDialogClose() == static_cast(0x80B8000Bu)); Require(sceNetCtlApDialogTerminate() == 0); Require(sceNetCtlApDialogTerminate() == kErrNotInitialized); Require(sceNetCtlApDialogGetStatus() == kStatusNone); Require(sceNetCtlApDialogInitialize() == 0); - Require(sceNetCtlApDialogClose() == 0); - Require(sceNetCtlApDialogGetStatus() == kStatusFinished); - std::memset(result, 0x5a, sizeof(result)); - Require(sceNetCtlApDialogGetResult(result) == 0); - RequireCanceled(result); + Require(sceNetCtlApDialogClose() == static_cast(0x80B8000Bu)); + Require(sceNetCtlApDialogGetStatus() == kStatusInitialized); + Require(sceNetCtlApDialogGetResult(reinterpret_cast(result)) == kErrNotFinished); Require(sceNetCtlApDialogTerminate() == 0); } diff --git a/core/libs/tests/GuestNetDnsInfo.cpp b/core/libs/tests/GuestNetDnsInfo.cpp new file mode 100644 index 0000000000..2dc49bca3e --- /dev/null +++ b/core/libs/tests/GuestNetDnsInfo.cpp @@ -0,0 +1,139 @@ +#include "prx/libSceNet/include/Net.hpp" +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kErrorBase = static_cast(0x80410100); +constexpr int kEfault = 14; +constexpr int kEinval = 22; +constexpr int kEnotinit = 200; + +bool Failed(int result, int error) { return result == (kErrorBase | error) && *sceNetErrnoLoc() == error; } + +void CheckUninitialized() { + NetDnsInfo info{0x08080808u, 0x01010101u}; + const NetDnsInfo before = info; + NetDns6Info info6{}; + std::memset(&info6, 0x5a, sizeof(info6)); + const NetDns6Info before6 = info6; + for (const std::uint32_t flags : {0u, 1u, 0xffffffffu}) { + Require(Failed(sceNetGetDnsInfo(&info, flags), kEnotinit)); + Require(Failed(sceNetSetDnsInfo(&info, flags), kEnotinit)); + Require(Failed(sceNetGetDns6Info(&info6, flags), kEnotinit)); + Require(Failed(sceNetSetDns6Info(&info6, flags), kEnotinit)); + Require(Failed(sceNetGetDnsInfo(nullptr, flags), kEnotinit)); + Require(Failed(sceNetSetDnsInfo(nullptr, flags), kEnotinit)); + Require(Failed(sceNetGetDns6Info(nullptr, flags), kEnotinit)); + Require(Failed(sceNetSetDns6Info(nullptr, flags), kEnotinit)); + Require(std::memcmp(&info, &before, sizeof(info)) == 0); + Require(std::memcmp(&info6, &before6, sizeof(info6)) == 0); + } +} + +void CheckDns() { + NetDnsInfo info{0x08080808u, 0}; + Require(Failed(sceNetSetDnsInfo(&info, 1), kEinval)); + NetDnsInfo empty{0, 0x01010101u}; + Require(Failed(sceNetSetDnsInfo(&empty, 0), kEinval)); + + Require(sceNetSetDnsInfo(&info, 0) == 0); + NetDnsInfo read{1, 1}; + Require(sceNetGetDnsInfo(&read, 0) == 1 && read.primaryAddress == 0x08080808u && read.secondaryAddress == 0); + info.secondaryAddress = 0x04040808u; + Require(sceNetSetDnsInfo(&info, 0) == 0); + Require(sceNetGetDnsInfo(&read, 0) == 2 && read.primaryAddress == 0x08080808u && read.secondaryAddress == 0x04040808u); + Require(sceNetSetDnsInfo(nullptr, 0) == 0); + Require(sceNetGetDnsInfo(&read, 0) >= 0); + + NetDns6Info info6{}; + Require(Failed(sceNetGetDnsInfo(nullptr, 0), kEinval)); + Require(Failed(sceNetGetDns6Info(nullptr, 0), kEinval)); + for (const std::uint32_t flags : {1u, 2u, 0xffffffffu}) { + NetDnsInfo unchanged{0x5a5a5a5au, 0x5a5a5a5au}; + const NetDnsInfo before = unchanged; + std::memset(&info6, 0x5a, sizeof(info6)); + const NetDns6Info before6 = info6; + Require(Failed(sceNetGetDnsInfo(&unchanged, flags), kEinval)); + Require(Failed(sceNetGetDns6Info(&info6, flags), kEinval)); + Require(Failed(sceNetGetDnsInfo(nullptr, flags), kEinval)); + Require(Failed(sceNetGetDns6Info(nullptr, flags), kEinval)); + Require(Failed(sceNetSetDnsInfo(nullptr, flags), kEinval)); + Require(Failed(sceNetSetDns6Info(nullptr, flags), kEinval)); + Require(std::memcmp(&unchanged, &before, sizeof(before)) == 0); + Require(std::memcmp(&info6, &before6, sizeof(before6)) == 0); + } + std::memset(&info6, 0xff, sizeof(info6)); + Require(sceNetGetDns6Info(&info6, 0) == 0); + for (const auto byte : info6.primaryAddress.bytes) Require(byte == 0); + for (const auto byte : info6.secondaryAddress.bytes) Require(byte == 0); + + NetDns6Info set6{}; + Require(Failed(sceNetSetDns6Info(&set6, 0), kEinval)); + set6.primaryAddress.bytes[0] = 0x20; + set6.primaryAddress.bytes[15] = 0x01; + Require(Failed(sceNetSetDns6Info(&set6, 2), kEinval)); + Require(sceNetSetDns6Info(&set6, 0) == 0); + Require(sceNetGetDns6Info(&info6, 0) == 1 && std::memcmp(&info6, &set6, sizeof(set6)) == 0); + set6.secondaryAddress.bytes[0] = 0x26; + Require(sceNetSetDns6Info(&set6, 0) == 0); + Require(sceNetGetDns6Info(&info6, 0) == 2 && std::memcmp(&info6, &set6, sizeof(set6)) == 0); + Require(sceNetSetDns6Info(nullptr, 0) == 0); + Require(sceNetGetDns6Info(&info6, 0) == 0); +} + +} + +void* UnreadablePage() { +#ifdef _WIN32 + return VirtualAlloc(nullptr, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS); +#else + void* page = mmap(nullptr, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + return page == MAP_FAILED ? nullptr : page; +#endif +} + +void CheckUnreadableInfo() { + void* page = UnreadablePage(); + Require(page != nullptr); + NetDnsInfo info{0x08080808u, 0}; + Require(sceNetSetDnsInfo(&info, 0) == 0); + Require(Failed(sceNetSetDnsInfo(static_cast(page), 1), kEinval)); + Require(Failed(sceNetSetDnsInfo(static_cast(page), 0), kEfault)); + NetDnsInfo read{}; + Require(sceNetGetDnsInfo(&read, 0) == 1 && read.primaryAddress == 0x08080808u); + Require(sceNetSetDnsInfo(nullptr, 0) == 0); + + NetDns6Info info6{}; + info6.primaryAddress.bytes[0] = 0x20; + Require(sceNetSetDns6Info(&info6, 0) == 0); + Require(Failed(sceNetSetDns6Info(static_cast(page), 1), kEinval)); + Require(Failed(sceNetSetDns6Info(static_cast(page), 0), kEfault)); + NetDns6Info read6{}; + Require(sceNetGetDns6Info(&read6, 0) == 1 && std::memcmp(&read6, &info6, sizeof(info6)) == 0); + Require(sceNetSetDns6Info(nullptr, 0) == 0); +#ifdef _WIN32 + Require(VirtualFree(page, 0, MEM_RELEASE) != 0); +#else + Require(munmap(page, 4096) == 0); +#endif +} + +int main() { + CheckUninitialized(); + Require(sceNetInit_nid_postfix() == 0); + CheckDns(); + CheckUnreadableInfo(); + Require(sceNetTerm() == 0); + CheckUninitialized(); + return 0; +} diff --git a/core/libs/tests/GuestNetKeepalive.cpp b/core/libs/tests/GuestNetKeepalive.cpp new file mode 100644 index 0000000000..d8d8624a88 --- /dev/null +++ b/core/libs/tests/GuestNetKeepalive.cpp @@ -0,0 +1,115 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include "prx/libSceNet/libSceNet.hpp" +#include "prx/libkernel/libkernel.hpp" +#include "prx/libkernel/Socket/include/SocketPoll.hpp" + + +static void Require(bool condition) { + if (!condition) std::abort(); +} + +static bool Failed(std::int64_t result, int error) { + return result == static_cast(0x80410100u | static_cast(error)) && *sceNetErrnoLoc() == error; +} + +static void CheckKeepalive() { + for (const int family : {2, 28}) { + for (const int type : {1, 2}) { + const int socket = sceNetSocket("keepalive", family, type, type == 1 ? 6 : 17); + Require(socket >= 0); + int value = -1; + std::uint32_t size = sizeof(value); + Require(sceNetGetsockopt(socket, 0xffff, 8, &value, &size) == 0 && value == 0 && size == sizeof(value)); + for (const int setting : {1, -1, 0, 8, 0}) { + Require(sceNetSetsockopt(socket, 0xffff, 8, &setting, sizeof(setting)) == 0); + size = sizeof(value); + Require(sceNetGetsockopt(socket, 0xffff, 8, &value, &size) == 0 && value == (setting ? 8 : 0)); + if (type == 1) { + std::shared_ptr owner; + std::uintptr_t native = 0; + Require(KernelFindSocketNative_nid_no_patch(socket, &owner, &native)); + int enabled = 0; +#ifdef _WIN32 + int length = sizeof(enabled); +#else + socklen_t length = sizeof(enabled); +#endif + Require(::getsockopt(native, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&enabled), &length) == 0); + Require((enabled != 0) == (setting != 0)); + } + } + const int enabled = 1; + Require(Failed(sceNetSetsockopt(socket, 0xffff, 8, &enabled, sizeof(enabled) - 1), 22)); + Require(Failed(sceNetSetsockopt(socket, 0xffff, 8, nullptr, sizeof(enabled)), 22)); + Require(setsockopt_nid_postfix(socket, 0xffff, 8, &enabled, sizeof(enabled)) == 0); + const int other = sceNetSocket("keepalive-other", family, type, type == 1 ? 6 : 17); + Require(other >= 0); + size = sizeof(value); + Require(sceNetGetsockopt(other, 0xffff, 8, &value, &size) == 0 && value == 0); + Require(sceNetSocketClose(other) == 0); + Require(Failed(sceNetGetsockopt(socket, 0xffff, 8, nullptr, &size), 22)); + Require(Failed(sceNetGetsockopt(socket, 0xffff, 8, &value, nullptr), 22)); + for (const std::uint32_t length : {0u, 1u, 3u, 4u, 8u}) { + std::array bytes; + bytes.fill(0xa5); + size = length; + const int expected = 8; + Require(sceNetGetsockopt(socket, 0xffff, 8, bytes.data(), &size) == 0 && size == std::min(length, 4u)); + Require(std::memcmp(bytes.data(), &expected, size) == 0); + Require(std::all_of(bytes.begin() + size, bytes.end(), [](auto byte) { return byte == 0xa5; })); + } + Require(sceNetSocketClose(socket) == 0); + size = sizeof(value); + Require(Failed(sceNetGetsockopt(socket, 0xffff, 8, &value, &size), 9)); + Require(Failed(sceNetSetsockopt(socket, 0xffff, 8, &enabled, sizeof(enabled)), 9)); + } + } +} + +int main() { + Require(sceNetInit_nid_postfix() == 0); + CheckKeepalive(); + const auto readFlag = [](int socket) { + int value = -1; + std::uint32_t size = sizeof(value); + Require(sceNetGetsockopt(socket, 0xffff, 8, &value, &size) == 0 && size == sizeof(value)); + return value; + }; + for (const int family : {2, 28}) { + const int listener = sceNetSocket("keepalive-listener", family, 1, 6); + const int client = sceNetSocket("keepalive-client", family, 1, 6); + Require(listener >= 0 && client >= 0); + const int on = 1; + const int off = 0; + Require(sceNetSetsockopt(listener, 0xffff, 8, &on, sizeof(on)) == 0); + std::array address{}; + std::uint32_t size = family == 2 ? 16 : 28; + address[0] = size; + address[1] = family; + if (family == 2) { + address[4] = 127; + address[7] = 1; + } else { + address[23] = 1; + } + Require(sceNetBind_nid_postfix(listener, address.data(), size) == 0); + Require(sceNetListen(listener, 1) == 0); + Require(sceNetGetsockname(listener, address.data(), &size) == 0); + Require(sceNetConnect(client, address.data(), size) == 0); + const int accepted = sceNetAccept(listener, nullptr, nullptr); + Require(accepted >= 0 && readFlag(accepted) == 8 && readFlag(client) == 0); + Require(sceNetSetsockopt(listener, 0xffff, 8, &off, sizeof(off)) == 0); + Require(readFlag(listener) == 0 && readFlag(accepted) == 8); + for (const int socket : {accepted, client, listener}) Require(sceNetSocketClose(socket) == 0); + } +} diff --git a/core/libs/tests/GuestNetNodelayReadback.cpp b/core/libs/tests/GuestNetNodelayReadback.cpp new file mode 100644 index 0000000000..ad296cdba6 --- /dev/null +++ b/core/libs/tests/GuestNetNodelayReadback.cpp @@ -0,0 +1,77 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "prx/libSceNet/libSceNet.hpp" +#include "prx/libkernel/Socket/include/SocketPoll.hpp" + + +static void Require(bool condition, std::source_location where = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "check failed at line %u\n", where.line()); + std::abort(); + } +} + +static void CheckValue(int socket, int expected) { + for (const std::uint32_t capacity : {0u, 1u, 2u, 3u, 4u, 8u}) { + std::array bytes; + bytes.fill(0xa5); + auto size = capacity; + Require(sceNetGetsockopt(socket, 6, 1, bytes.data(), &size) == 0); + Require(size == std::min(capacity, 4u)); + Require(std::memcmp(bytes.data(), &expected, size) == 0); + Require(std::all_of(bytes.begin() + size, bytes.end(), [](auto value) { return value == 0xa5; })); + } +} + +int main() { + Require(sceNetInit_nid_postfix() == 0); + for (const int family : {2, 28}) { + const int socket = sceNetSocket("nodelay", family, 1, 6); + const int other = sceNetSocket("nodelay-other", family, 1, 0); + const int datagram = sceNetSocket("nodelay-datagram", family, 2, 17); + Require(socket >= 0 && other >= 0 && datagram >= 0); + CheckValue(socket, 0); + for (const int requested : {1, 0, -1, 256, 0}) { + Require(sceNetSetsockopt(socket, 6, 1, &requested, sizeof(requested)) == 0); + CheckValue(socket, requested != 0 ? 4 : 0); + CheckValue(other, 0); + } + std::shared_ptr owner; + std::uintptr_t native = 0; + Require(KernelFindSocketNative_nid_no_patch(socket, &owner, &native)); + const int enabled = 1; + Require(::setsockopt(native, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&enabled), sizeof(enabled)) == 0); + CheckValue(socket, 4); + std::uint32_t size = 0; + int value = 99; + Require(sceNetGetsockopt(socket, 6, 1, nullptr, &size) < 0 && *sceNetErrnoLoc() == 22); + Require(sceNetGetsockopt(socket, 6, 1, &value, nullptr) < 0 && *sceNetErrnoLoc() == 22); + for (const std::uint32_t capacity : {0u, 3u, 4u}) { + size = capacity; + Require(sceNetGetsockopt(datagram, 6, 1, &value, &size) < 0 && *sceNetErrnoLoc() == 22); + Require(size == capacity && value == 99); + Require(sceNetGetsockopt(socket, 6, 0x7fffffff, &value, &size) < 0); + Require(*sceNetErrnoLoc() == (capacity < 4 ? 22 : 42)); + Require(size == capacity && value == 99); + } + owner.reset(); + for (const int descriptor : {socket, other, datagram}) Require(sceNetSocketClose(descriptor) == 0); + size = 0; + Require(sceNetGetsockopt(socket, 6, 1, &value, &size) < 0 && *sceNetErrnoLoc() == 9); + Require(size == 0 && value == 99); + } +} diff --git a/core/libs/tests/GuestNetProtocol.cpp b/core/libs/tests/GuestNetProtocol.cpp new file mode 100644 index 0000000000..038c59f77d --- /dev/null +++ b/core/libs/tests/GuestNetProtocol.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include "prx/libSceNet/libSceNet.hpp" + + +static void Require(bool condition, std::source_location where = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "check failed at line %u\n", where.line()); + std::abort(); + } +} + +static void CheckProtocol(int socket, int expected) { + for (const std::uint32_t capacity : {0u, 1u, 3u, 4u, 8u}) { + std::array bytes; + bytes.fill(0xa5); + auto size = capacity; + Require(sceNetGetsockopt(socket, 0xffff, 0x1016, bytes.data(), &size) == 0); + Require(size == std::min(capacity, 4u)); + Require(std::memcmp(bytes.data(), &expected, size) == 0); + Require(std::all_of(bytes.begin() + size, bytes.end(), [](auto value) { return value == 0xa5; })); + } +} + +int main() { + Require(sceNetInit_nid_postfix() == 0); + for (const int family : {2, 28}) { + for (const int type : {1, 2}) { + const int expected = type == 1 ? 6 : 17; + for (const int protocol : {0, expected}) { + const int socket = sceNetSocket("protocol", family, type, protocol); + Require(socket >= 0); + CheckProtocol(socket, expected); + std::uint32_t size = 4; + int value = 99; + Require(sceNetGetsockopt(socket, 0xffff, 0x1016, nullptr, &size) < 0 && *sceNetErrnoLoc() == 22); + Require(sceNetGetsockopt(socket, 0xffff, 0x1016, &value, nullptr) < 0 && *sceNetErrnoLoc() == 22); + Require(value == 99 && size == 4); + Require(sceNetSetsockopt(socket, 0xffff, 0x1016, &value, sizeof(value)) < 0); + CheckProtocol(socket, expected); + Require(sceNetSocketClose(socket) == 0); + Require(sceNetGetsockopt(socket, 0xffff, 0x1016, &value, &size) < 0 && *sceNetErrnoLoc() == 9); + Require(value == 99 && size == 4); + } + } + const int listener = sceNetSocket("protocol-listener", family, 1, 0); + const int client = sceNetSocket("protocol-client", family, 1, 6); + Require(listener >= 0 && client >= 0); + std::array address{}; + std::uint32_t size = family == 2 ? 16 : 28; + address[0] = size; + address[1] = family; + if (family == 2) { address[4] = 127; address[7] = 1; } + else address[23] = 1; + Require(sceNetBind_nid_postfix(listener, address.data(), size) == 0); + Require(sceNetListen(listener, 1) == 0); + CheckProtocol(listener, 6); + Require(sceNetGetsockname(listener, address.data(), &size) == 0); + Require(sceNetConnect(client, address.data(), size) == 0); + const int accepted = sceNetAccept(listener, nullptr, nullptr); + Require(accepted >= 0); + CheckProtocol(accepted, 6); + CheckProtocol(client, 6); + for (const int socket : {accepted, client, listener}) Require(sceNetSocketClose(socket) == 0); + } +} diff --git a/core/libs/tests/GuestNetThread.cpp b/core/libs/tests/GuestNetThread.cpp new file mode 100644 index 0000000000..abdc2c22d8 --- /dev/null +++ b/core/libs/tests/GuestNetThread.cpp @@ -0,0 +1,73 @@ +#include "prx/libSceNet/include/Net.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int Einval = 22; +constexpr int Eopnotsupp = 45; +constexpr int NetError = static_cast(0x80410100u); + + + +struct Run { + std::atomic started{0}; + std::atomic pastExit{false}; +}; + +Run run; + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "%s\n", what); + std::abort(); +} + +void APS5_VABI Count(void* arg) { + ++static_cast(arg)->started; +} + +void APS5_VABI ExitEarly(void* arg) { + auto* run = static_cast(arg); + ++run->started; + sceNetThreadExit(); + run->pastExit = true; +} + +} + +int main() { + NetThreadParameter counting{Count, &run, 0, 0}; + Pthread thread = nullptr; + Require(sceNetThreadCreate(&thread, &counting, "anyps5-net-thread") == 0 && thread != nullptr, "sceNetThreadCreate failed"); + Require(sceNetThreadJoin(thread) == 0, "sceNetThreadJoin failed"); + Require(run.started == 1, "the thread did not run its entry with its argument"); + + Pthread untouched = reinterpret_cast(&run); + *sceNetErrnoLoc() = 0; + Require(sceNetThreadCreate(&untouched, nullptr, "anyps5-net-thread") == (NetError | Einval), "a null parameter was not refused with EINVAL"); + Require(*sceNetErrnoLoc() == Einval && untouched == reinterpret_cast(&run), "a refused create set no errno or wrote the thread"); + + NetThreadParameter exiting{ExitEarly, &run, 0, 1}; + Require(sceNetThreadCreate(&thread, &exiting, nullptr) == 0, "sceNetThreadCreate failed without a name"); + Require(sceNetThreadJoin(thread) == 0, "joining an exited thread failed"); + Require(run.started == 2 && !run.pastExit, "sceNetThreadExit did not end the thread"); + + static NetThreadParameter detached{Count, &run, 0, 0}; + Require(sceNetThreadCreate(nullptr, &detached, "anyps5-net-thread") == 0, "a null thread output was refused"); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (run.started != 3 && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(run.started == 3, "the thread created without an output did not run"); + + *sceNetErrnoLoc() = 0; + Require(sceNetEmulationGet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationGet did not return EOPNOTSUPP"); + *sceNetErrnoLoc() = 0; + Require(sceNetEmulationSet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationSet did not return EOPNOTSUPP"); + std::puts("net thread tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestNewHandler.cpp b/core/libs/tests/GuestNewHandler.cpp index 04a5d9a8a1..29b5acfc3c 100644 --- a/core/libs/tests/GuestNewHandler.cpp +++ b/core/libs/tests/GuestNewHandler.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -11,15 +12,7 @@ extern "C" { using Handler = void (APS5_VABI*)(); -Handler APS5_VABI _ZSt15set_new_handlerPFvvE_nid_postfix(Handler); -Handler APS5_VABI _ZSt15get_new_handlerv_nid_postfix(); -void* APS5_VABI _Znwm_nid_postfix(std::size_t); -void* APS5_VABI _Znam_nid_postfix(std::size_t); -void* APS5_VABI _ZnwmSt11align_val_t_nid_postfix(std::size_t, std::size_t); -void* APS5_VABI _ZnwmRKSt9nothrow_t_nid_postfix(std::size_t, const void*) noexcept; -void* APS5_VABI _ZnamRKSt9nothrow_t_nid_postfix(std::size_t, const void*) noexcept; -void APS5_VABI _ZdlPv_nid_postfix(void*); -void APS5_VABI _ZdlPvSt11align_val_t_nid_postfix(void*, std::size_t); + } namespace { @@ -27,6 +20,7 @@ namespace { alignas(16) std::array storage{}; unsigned failuresLeft = 0; unsigned allocations = 0; +unsigned releases = 0; unsigned handlerCalls = 0; std::size_t lastSize = 0; std::size_t lastAlignment = 0; @@ -50,7 +44,7 @@ void* APS5_VABI allocate(std::size_t bytes) { return storage.data(); } -void APS5_VABI release(void*) {} +void APS5_VABI release(void*) { ++releases; } void* APS5_VABI allocateZeroed(std::size_t count, std::size_t bytes) { return allocate(count * bytes); } void* APS5_VABI reallocate(void*, std::size_t bytes) { return allocate(bytes); } void* APS5_VABI align(std::size_t alignment, std::size_t bytes) { @@ -80,6 +74,7 @@ void APS5_VABI allocatingHandler() { void Reset(unsigned failures) { failuresLeft = failures; allocations = 0; + releases = 0; handlerCalls = 0; } @@ -138,6 +133,17 @@ int main(int argc, char** argv) { REQUIRE(_ZSt15get_new_handlerv_nid_postfix() == nullptr); + Reset(0); + void* nothrowPointer = _Znwm_nid_postfix(8); + const int nothrowTag = 0; + REQUIRE(nothrowPointer == storage.data() && releases == 0); + _ZdlPvRKSt9nothrow_t_nid_postfix(nothrowPointer, ¬hrowTag); + + REQUIRE(releases == 1); + _ZdlPvRKSt9nothrow_t_nid_postfix(nullptr, ¬hrowTag); + + REQUIRE(releases == 1); + Reset(1); REQUIRE(Throws([] { _Znwm_nid_postfix(8); }) && handlerCalls == 0 && allocations == 1); Reset(1); @@ -186,6 +192,7 @@ int main(int argc, char** argv) { void* aligned = _ZnwmSt11align_val_t_nid_postfix(24, 16); REQUIRE(aligned == storage.data() && lastSize == 24 && lastAlignment == 16 && allocations == 1); _ZdlPvSt11align_val_t_nid_postfix(aligned, 16); + Reset(1); REQUIRE(Throws([] { _ZnwmSt11align_val_t_nid_postfix(8, 32); }) && handlerCalls == 0 && allocations == 1); @@ -193,6 +200,7 @@ int main(int argc, char** argv) { REQUIRE(_ZnwmSt11align_val_t_nid_postfix(0, 16) == storage.data() && lastSize == 1 && lastAlignment == 16); Reset(2); _ZSt15set_new_handlerPFvvE_nid_postfix(&countingHandler); + REQUIRE(_ZnwmSt11align_val_t_nid_postfix(8, 16) == storage.data() && handlerCalls == 2 && allocations == 3); _ZSt15set_new_handlerPFvvE_nid_postfix(nullptr); diff --git a/core/libs/tests/GuestNpAuth.cpp b/core/libs/tests/GuestNpAuth.cpp index 2ab80ba71c..a1920ceb5c 100644 --- a/core/libs/tests/GuestNpAuth.cpp +++ b/core/libs/tests/GuestNpAuth.cpp @@ -1,24 +1,9 @@ +#include "prx/libSceNpAuth/include/Api.hpp" #include "SceTypes.hpp" #include #include - -extern "C" { -int APS5_VABI sceNpAuthAbortRequest(int req_id); -int APS5_VABI sceNpAuthCreateAsyncRequest(const void* param); -int APS5_VABI sceNpAuthCreateRequest(void); -int APS5_VABI sceNpAuthDeleteRequest(int req_id); -int APS5_VABI sceNpAuthGetAuthorizationCode(int req_id, const void* param, void* auth_code); -int APS5_VABI sceNpAuthGetAuthorizationCodeA(int req_id, const void* param, void* auth_code, int* issuer_id); -int APS5_VABI sceNpAuthGetAuthorizationCodeV3(int req_id, const void* param, void* auth_code, int* issuer_id); -int APS5_VABI sceNpAuthGetAuthorizedAppCode(); -int APS5_VABI sceNpAuthGetIdToken(int req_id, const void* param, void* id_token); -int APS5_VABI sceNpAuthGetIdTokenA(int req_id, const void* param, void* id_token); -int APS5_VABI sceNpAuthGetIdTokenV3(int req_id, const void* param, void* id_token); -int APS5_VABI sceNpAuthPollAsync(int req_id, int* result); -int APS5_VABI sceNpAuthSetTimeout(int req_id, int resolve_retry, uint32_t resolve_timeout, uint32_t connect_timeout, uint32_t send_timeout, uint32_t receive_timeout); -int APS5_VABI sceNpAuthWaitAsync(int req_id, int* result); -} +#include static void Require(bool condition) { if (!condition) { @@ -26,7 +11,43 @@ static void Require(bool condition) { } } +static void RequireUnstarted(int request) { + constexpr int invalidId = static_cast(0x80550307); + constexpr int sentinel = 0x13579bdf; + for (int attempt = 0; attempt != 3; ++attempt) { + int result = sentinel; + Require(sceNpAuthPollAsync(request, &result) == invalidId); + Require(result == sentinel); + Require(sceNpAuthWaitAsync(request, &result) == invalidId); + Require(result == sentinel); + } +} + +static void CheckUnstartedAborts() { + constexpr int aborted = static_cast(0x80550304); + alignas(8) char parameter[16]{}; + alignas(8) char output[16]{}; + for (const bool async : {false, true}) { + const int request = async ? sceNpAuthCreateAsyncRequest(nullptr) : sceNpAuthCreateRequest(); + Require(request > 0); + RequireUnstarted(request); + Require(sceNpAuthAbortRequest(request) == 0); + Require(sceNpAuthAbortRequest(request) == 0); + RequireUnstarted(request); + for (auto getter : {sceNpAuthGetIdToken, sceNpAuthGetIdTokenV3, sceNpAuthGetAuthorizationCode}) { + Require(getter(request, parameter, output) == aborted); + RequireUnstarted(request); + } + int issuer = 0x2468ace; + Require(sceNpAuthGetAuthorizationCodeV3(request, parameter, output, &issuer) == aborted); + Require(issuer == 0x2468ace); + RequireUnstarted(request); + Require(sceNpAuthDeleteRequest(request) == 0); + } +} + int main() { + CheckUnstartedAborts(); constexpr int invalidArgument = static_cast(0x80550301); constexpr int signedOut = static_cast(0x80550006); constexpr int aborted = static_cast(0x80550304); @@ -50,14 +71,24 @@ int main() { int issuerId = 0; Require(sceNpAuthSetTimeout(syncReq, 2, 5000000, 10000000, 10000000, 10000000) == 0); - Require(sceNpAuthSetTimeout(asyncReq, 0, 0, 0, 0, 0) == 0); + Require(sceNpAuthSetTimeout(asyncReq, 0, 0, 0, 0, 0) == invalidArgument); + + Require(sceNpAuthSetTimeout(syncReq, -1, 1000000, 10000000, 10000000, 10000000) == invalidArgument); + Require(sceNpAuthSetTimeout(syncReq, 1, 999999, 10000000, 10000000, 10000000) == invalidArgument); + Require(sceNpAuthSetTimeout(syncReq, 1, 1000000, 9999999, 10000000, 10000000) == invalidArgument); + Require(sceNpAuthSetTimeout(syncReq, 1, 1000000, 10000000, 9999999, 10000000) == invalidArgument); + Require(sceNpAuthSetTimeout(syncReq, 1, 1000000, 10000000, 10000000, 9999999) == invalidArgument); + Require(sceNpAuthSetTimeout(syncReq, 1, 1000000, 10000000, 10000000, 10000000) == 0); + Require(sceNpAuthSetTimeout(syncReq, 1, 0, 0, 0, 0) == 0); + Require(sceNpAuthSetTimeout(syncReq, 0, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu) == 0); + Require(sceNpAuthSetTimeout(-1, 0, 0, 0, 0, 0) == invalidArgument); int pollUnprocessed = 0; Require(sceNpAuthPollAsync(asyncReq, &pollUnprocessed) == invalidId); int waitUnprocessed = 0; Require(sceNpAuthWaitAsync(asyncReq, &waitUnprocessed) == invalidId); - Require(sceNpAuthGetAuthorizedAppCode() == signedOut); + Require(sceNpAuthGetAuthorizedAppCode(syncReq, nullptr, nullptr, nullptr, nullptr) == invalidArgument); Require(sceNpAuthGetIdToken(syncReq, nullptr, token) == invalidArgument); Require(sceNpAuthGetIdToken(syncReq, param, nullptr) == invalidArgument); @@ -120,8 +151,8 @@ int main() { Require(sceNpAuthGetIdTokenV3(abortedReq, param, token) == aborted); Require(sceNpAuthGetAuthorizationCodeV3(abortedReq, param, authCode, &issuerId) == aborted); int abortedWait = 0; - Require(sceNpAuthWaitAsync(abortedReq, &abortedWait) == 0); - Require(abortedWait == aborted); + Require(sceNpAuthWaitAsync(abortedReq, &abortedWait) == invalidId); + Require(abortedWait == 0); const int syncReqA = sceNpAuthCreateRequest(); Require(sceNpAuthGetIdTokenA(syncReqA, nullptr, token) == invalidArgument); diff --git a/core/libs/tests/GuestNpAuthAuthorizedAppCode.cpp b/core/libs/tests/GuestNpAuthAuthorizedAppCode.cpp index f6dfce0de8..b644c62ed3 100644 --- a/core/libs/tests/GuestNpAuthAuthorizedAppCode.cpp +++ b/core/libs/tests/GuestNpAuthAuthorizedAppCode.cpp @@ -1,13 +1,45 @@ -#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNpAuth/include/Api.hpp" +#include "SceTypes.hpp" +#include #include +#include -extern "C" { -int APS5_VABI sceNpAuthGetAuthorizedAppCode(); +static void Require(bool condition) { + if (!condition) std::abort(); } int main() { - constexpr int kSignedOut = static_cast(0x80550006); - if (sceNpAuthGetAuthorizedAppCode() != kSignedOut) std::abort(); - if (sceNpAuthGetAuthorizedAppCode() != kSignedOut) std::abort(); - return 0; + constexpr auto invalidArgument = static_cast(0x80550301u); + constexpr auto invalidSize = static_cast(0x80550302u); + constexpr auto requestNotFound = static_cast(0x80550306u); + std::array client{}; + std::array code{}; + code.fill('x'); + const auto original = code; + bool authorizationRequired = true; + NpAuthAuthorizedAppCodeParameter parameter{40, 1, reinterpret_cast(client.data()), "", 0}; + Require(sceNpAuthGetAuthorizedAppCode(0, nullptr, code.data(), nullptr, &authorizationRequired) == invalidArgument); + parameter.size = 39; + Require(sceNpAuthGetAuthorizedAppCode(0, ¶meter, code.data(), nullptr, &authorizationRequired) == invalidSize); + parameter.size = 40; + Require(sceNpAuthGetAuthorizedAppCode(0, ¶meter, nullptr, nullptr, &authorizationRequired) == invalidArgument); + Require(sceNpAuthGetAuthorizedAppCode(0, ¶meter, code.data(), nullptr, nullptr) == invalidArgument); + parameter.option = 2; + Require(sceNpAuthGetAuthorizedAppCode(0, ¶meter, code.data(), nullptr, &authorizationRequired) == invalidArgument); + parameter.option = 0; + parameter.userId = -1; + Require(sceNpAuthGetAuthorizedAppCode(0, ¶meter, code.data(), nullptr, &authorizationRequired) == invalidArgument); + parameter.userId = 1; + Require(sceNpAuthGetAuthorizedAppCode(0, ¶meter, code.data(), nullptr, &authorizationRequired) == requestNotFound); + const auto requestId = sceNpAuthCreateRequest(); + Require(requestId > 0); + bool threw = false; + try { + sceNpAuthGetAuthorizedAppCode(requestId, ¶meter, code.data(), nullptr, &authorizationRequired); + + } catch (const std::logic_error&) { + threw = true; + } + Require(threw && code == original && authorizationRequired); + Require(sceNpAuthDeleteRequest(requestId) == 0); } diff --git a/core/libs/tests/GuestNpAuthAuthorizedAppDialog.cpp b/core/libs/tests/GuestNpAuthAuthorizedAppDialog.cpp index 7eecd54c43..a8fecd529b 100644 --- a/core/libs/tests/GuestNpAuthAuthorizedAppDialog.cpp +++ b/core/libs/tests/GuestNpAuthAuthorizedAppDialog.cpp @@ -1,16 +1,10 @@ +#include "prx/libSceNpAuthAuthorizedAppDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include - -extern "C" { -int APS5_VABI sceNpAuthAuthorizedAppDialogInitialize(void); -int APS5_VABI sceNpAuthAuthorizedAppDialogOpen(const void* param); -int APS5_VABI sceNpAuthAuthorizedAppDialogUpdateStatus(void); -int APS5_VABI sceNpAuthAuthorizedAppDialogGetStatus(void); -int APS5_VABI sceNpAuthAuthorizedAppDialogGetResult(void* result); -int APS5_VABI sceNpAuthAuthorizedAppDialogClose(void); -int APS5_VABI sceNpAuthAuthorizedAppDialogTerminate(void); -} +#include +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "prx/libSceUserService/UserService.hpp" namespace { @@ -28,7 +22,16 @@ constexpr int kResultUserCanceled = 1; } int main() { - std::int64_t param = 0; + Require(sceUserServiceInitialize(nullptr) == 0); + NpAuthAuthorizedAppDialogParam 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; + char application[136] = "test"; + std::string text(1024, 'a'); + param.application = application; + param.text = text.c_str(); std::int32_t result[2] = {-1, -1}; Require(sceNpAuthAuthorizedAppDialogGetStatus() == kStatusNone); @@ -45,27 +48,51 @@ int main() { Require(sceNpAuthAuthorizedAppDialogOpen(nullptr) == kErrArgNull); Require(sceNpAuthAuthorizedAppDialogGetResult(result) == kErrNotFinished); + constexpr int invalidArgument = static_cast(0x80B8000Au); + param.size = 119; + Require(sceNpAuthAuthorizedAppDialogOpen(¶m) == invalidArgument); + param.size = sizeof(param); + param.userId = 42; + Require(sceNpAuthAuthorizedAppDialogOpen(¶m) == invalidArgument); + param.userId = USER_SERVICE_INITIAL_USER_ID; + param.application = nullptr; + Require(sceNpAuthAuthorizedAppDialogOpen(¶m) == invalidArgument); + param.application = application; + param.text = ""; + Require(sceNpAuthAuthorizedAppDialogOpen(¶m) == invalidArgument); + text.push_back('a'); + param.text = text.c_str(); + Require(sceNpAuthAuthorizedAppDialogOpen(¶m) == invalidArgument); + text.pop_back(); + param.text = text.c_str(); + param.mode = 2; + Require(sceNpAuthAuthorizedAppDialogOpen(¶m) == invalidArgument); + param.mode = 1; + param.reserved[31] = 1; + Require(sceNpAuthAuthorizedAppDialogOpen(¶m) == invalidArgument); + param.reserved[31] = 0; Require(sceNpAuthAuthorizedAppDialogOpen(¶m) == 0); Require(sceNpAuthAuthorizedAppDialogUpdateStatus() == kStatusFinished); Require(sceNpAuthAuthorizedAppDialogGetStatus() == kStatusFinished); Require(sceNpAuthAuthorizedAppDialogGetResult(nullptr) == kErrArgNull); - Require(sceNpAuthAuthorizedAppDialogGetResult(result) == 0); + Require(sceNpAuthAuthorizedAppDialogGetResult(result) == kResultUserCanceled); Require(result[0] == kResultUserCanceled); Require(result[1] == -1); - Require(sceNpAuthAuthorizedAppDialogClose() == 0); + Require(sceNpAuthAuthorizedAppDialogClose() == static_cast(0x80B8000Bu)); Require(sceNpAuthAuthorizedAppDialogTerminate() == 0); Require(sceNpAuthAuthorizedAppDialogTerminate() == kErrNotInitialized); Require(sceNpAuthAuthorizedAppDialogGetStatus() == kStatusNone); Require(sceNpAuthAuthorizedAppDialogInitialize() == 0); Require(sceNpAuthAuthorizedAppDialogGetStatus() == kStatusInitialized); - Require(sceNpAuthAuthorizedAppDialogClose() == 0); - Require(sceNpAuthAuthorizedAppDialogGetStatus() == kStatusFinished); - result[0] = -1; - Require(sceNpAuthAuthorizedAppDialogGetResult(result) == 0); - Require(result[0] == kResultUserCanceled); + Require(sceNpAuthAuthorizedAppDialogClose() == static_cast(0x80B8000Bu)); + Require(sceNpAuthAuthorizedAppDialogGetStatus() == kStatusInitialized); + Require(sceNpAuthAuthorizedAppDialogGetResult(result) == kErrNotFinished); Require(sceNpAuthAuthorizedAppDialogTerminate() == 0); + Require(sceNpAuthAuthorizedAppDialogOpen(nullptr) == kErrArgNull); + Require(sceNpAuthAuthorizedAppDialogGetResult(nullptr) == kErrArgNull); + Require(sceUserServiceTerminate() == 0); return 0; } diff --git a/core/libs/tests/GuestNpCommerceDialog.cpp b/core/libs/tests/GuestNpCommerceDialog.cpp index 7b7239959f..58383cb878 100644 --- a/core/libs/tests/GuestNpCommerceDialog.cpp +++ b/core/libs/tests/GuestNpCommerceDialog.cpp @@ -1,64 +1,201 @@ +#include "prx/libSceNpCommerce/include/Api.hpp" +#include "ProcessParametersFixture.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include +#include #include "SceTypes.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include #include #include -extern "C" { -int APS5_VABI sceNpCommerceDialogInitialize(); -int APS5_VABI sceNpCommerceDialogOpen(const void* param); -int APS5_VABI sceNpCommerceDialogOpen2(void); -int APS5_VABI sceNpCommerceDialogUpdateStatus(void); -int APS5_VABI sceNpCommerceDialogGetStatus(void); -int APS5_VABI sceNpCommerceDialogGetResult(void* result); -int APS5_VABI sceNpCommerceDialogClose(void); -int APS5_VABI sceNpCommerceDialogTerminate(); -} - namespace { void Require(bool value) { if (!value) std::abort(); } +alignas(16) std::array process{}; + constexpr int statusNone = 0; constexpr int statusInitialized = 1; constexpr int statusFinished = 3; +constexpr int errorAlreadyInitialized = static_cast(0x80B80004u); constexpr std::int32_t resultUserCanceled = 1; -void RequireCanceledResult() { - std::uint8_t result[0x20]; - std::memset(result, 0x33, sizeof(result)); - Require(sceNpCommerceDialogGetResult(result) == 0); - std::int32_t value = 0; - std::memcpy(&value, result, sizeof(value)); - Require(value == resultUserCanceled); - Require(result[4] == 0); - Require(result[5] == 0x33); +void RequireCanceledResult(std::uint64_t userData, bool clearsReserved) { + NpCommerceDialogResult result; + std::memset(&result, 0x33, sizeof(result)); + Require(sceNpCommerceDialogGetResult(&result) == resultUserCanceled); + Require(result.result == resultUserCanceled); + Require(result.authorized == 0); + Require(result.userData == userData); + for (const auto value : result.padding) Require(value == 0x33); + for (const auto value : result.reserved) Require(value == (clearsReserved ? 0 : 0x33)); } } int main() { + Require(ProcessParametersFixture::Install(process.data(), process.size())); + std::uint32_t sdkVersion = 0x03500000; + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + Require(sceUserServiceInitialize(nullptr) == 0); + NpCommerceDialogParam2 param2{}; + param2.base.size = sizeof(param2.base); + param2.base.magic = static_cast(reinterpret_cast(¶m2)) - 1060003575u; + param2.size = sizeof(param2); + param2.userId = USER_SERVICE_INITIAL_USER_ID; + param2.userData = 0x123456789abcdef0; + Require(sceNpCommerceDialogGetStatus() == statusNone); + Require(sceNpCommerceDialogOpen(nullptr) == static_cast(0x80B8000Du)); + Require(sceNpCommerceDialogOpen2(nullptr) == static_cast(0x80B8000Du)); + Require(sceNpCommerceDialogOpen2(¶m2) == static_cast(0x80B80003u)); + param2.base.magic = 0; + Require(sceNpCommerceDialogOpen2(¶m2) == static_cast(0x80B8000Au)); + param2.base.magic = static_cast(reinterpret_cast(¶m2)) - 1060003575u; + param2.base.reserved[35] = 1; + Require(sceNpCommerceDialogOpen2(¶m2) == static_cast(0x80B8000Au)); + param2.base.reserved[35] = 0; + Require(sceNpCommerceDialogTerminate() == static_cast(0x80B80003u)); + Require(sceNpCommerceDialogClose() == static_cast(0x80B80003u)); + Require(sceNpCommerceDialogGetResult(nullptr) == static_cast(0x80B8000Du)); + NpCommerceDialogResult result{}; + Require(sceNpCommerceDialogGetResult(&result) == static_cast(0x80B80003u)); + + Require(sceNpCommerceDialogInitializeInternal() == 0); + Require(sceNpCommerceDialogGetStatus() == statusInitialized); + Require(sceNpCommerceDialogInitializeInternal() == errorAlreadyInitialized); + Require(sceNpCommerceDialogInitialize() == errorAlreadyInitialized); + Require(sceNpCommerceDialogGetStatus() == statusInitialized); + Require(sceNpCommerceDialogTerminate() == 0); Require(sceNpCommerceDialogGetStatus() == statusNone); Require(sceNpCommerceDialogInitialize() == 0); + Require(sceNpCommerceDialogInitializeInternal() == errorAlreadyInitialized); + Require(sceNpCommerceDialogGetStatus() == statusInitialized); + Require(sceNpCommerceDialogTerminate() == 0); + Require(sceNpCommerceDialogGetStatus() == statusNone); + Require(sceNpCommerceDialogInitialize() == 0); + Require(sceNpCommerceDialogClose() == static_cast(0x80B8000Bu)); + Require(sceNpCommerceDialogGetResult(&result) == static_cast(0x80B80005u)); + Require(sceNpCommerceDialogGetResult(nullptr) == static_cast(0x80B8000Du)); Require(sceNpCommerceDialogGetStatus() == statusInitialized); - Require(sceNpCommerceDialogOpen2() == 0); + Require(sceNpCommerceDialogOpen2(¶m2) == 0); Require(sceNpCommerceDialogUpdateStatus() == statusFinished); Require(sceNpCommerceDialogGetStatus() == statusFinished); - RequireCanceledResult(); + RequireCanceledResult(param2.userData, true); - Require(sceNpCommerceDialogClose() == 0); + Require(sceNpCommerceDialogClose() == static_cast(0x80B8000Bu)); Require(sceNpCommerceDialogGetStatus() == statusFinished); Require(sceNpCommerceDialogTerminate() == 0); Require(sceNpCommerceDialogGetStatus() == statusNone); - alignas(8) std::uint8_t param[0x40] = {}; + NpCommerceDialogParam 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; Require(sceNpCommerceDialogInitialize() == 0); - Require(sceNpCommerceDialogOpen(param) == 0); - Require(sceNpCommerceDialogOpen2() == 0); + param.base.size = 0; + Require(sceNpCommerceDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.base.size = sizeof(param.base); + param.size = 0; + Require(sceNpCommerceDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.size = sizeof(param); + param.option = 8; + Require(sceNpCommerceDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.option = 0; + param.reserved[31] = 1; + Require(sceNpCommerceDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.reserved[31] = 0; + param.userId = -1; + Require(sceNpCommerceDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.userId = USER_SERVICE_INITIAL_USER_ID; + const char* products[11]{}; + for (std::uint32_t mode = 0; mode <= 6; ++mode) { + param.mode = mode; + for (std::uint32_t count = 0; count <= 11; ++count) { + param.productCount = count; + for (bool hasProducts : {false, true}) { + param.products = hasProducts ? products : nullptr; + bool valid = false; + if (mode == 0 || mode == 2) valid = count <= 1 && hasProducts == (count != 0); + if (mode == 1) valid = count == 1 && hasProducts; + if (mode == 3) valid = count >= 1 && count <= 10 && hasProducts; + if (mode == 4) valid = count <= 10 && hasProducts == (count != 0); + Require(sceNpCommerceDialogOpen(¶m) == (valid ? 0 : static_cast(0x80B8000Au))); + param2.mode = mode; + param2.productCount = count; + param2.products = param.products; + Require(sceNpCommerceDialogOpen2(¶m2) == (valid ? 0 : static_cast(0x80B8000Au))); + } + } + } + param2.mode = 1; + param2.contentMode = 1; + param2.productCount = 1; + param2.products = products; + param2.option = 99; + Require(sceNpCommerceDialogOpen2(¶m2) == 0); + param2.option = 100; + Require(sceNpCommerceDialogOpen2(¶m2) == static_cast(0x80B8000Au)); + param2.option = 0; + param2.mode = 2; + bool threw = false; + try { + sceNpCommerceDialogOpen2(¶m2); + + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw); + param2.contentMode = 0; + param2.mode = 0; + param2.productCount = 0; + param2.products = nullptr; + param2.reserved[31] = 1; + Require(sceNpCommerceDialogOpen2(¶m2) == static_cast(0x80B8000Au)); + param2.reserved[31] = 0; + param2.size = 0; + Require(sceNpCommerceDialogOpen2(¶m2) == static_cast(0x80B8000Au)); + param2.size = sizeof(param2); + param2.userId = -1; + Require(sceNpCommerceDialogOpen2(¶m2) == static_cast(0x80B8000Au)); + param2.userId = USER_SERVICE_INITIAL_USER_ID; + param2.userData = 0x123456789abcdef0; + for (std::uint32_t mode : {1000u, 1001u}) { + param.mode = mode; + param.productCount = 0; + param.argument = 1; + Require(sceNpCommerceDialogOpen(¶m) == static_cast(0x80B8000Au)); + } + param.mode = 5; + param.productCount = 0; + param.products = nullptr; + for (std::uint64_t argument = 0; argument <= 4; ++argument) { + param.argument = argument; + Require(sceNpCommerceDialogOpen(¶m) == (argument == 1 ? 0 : static_cast(0x80B8000Au))); + } + sdkVersion = 0x01700000; + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + param.argument = 3; + param.userData = 0xfedcba9876543210; + Require(sceNpCommerceDialogOpen(¶m) == 0); + RequireCanceledResult(param.userData, true); + sdkVersion = 0x01600000; + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + param.size = 0; + param.option = 100; + param.userId = -1; + param.reserved[31] = 1; + param.argument = 7; + Require(sceNpCommerceDialogOpen(¶m) == 0); + RequireCanceledResult(param.userData, false); + Require(sceNpCommerceDialogOpen2(¶m2) == 0); Require(sceNpCommerceDialogUpdateStatus() == statusFinished); - RequireCanceledResult(); + RequireCanceledResult(param2.userData, false); Require(sceNpCommerceDialogTerminate() == 0); Require(sceNpCommerceDialogGetStatus() == statusNone); + Require(sceUserServiceTerminate() == 0); return 0; } diff --git a/core/libs/tests/GuestNpCommon.cpp b/core/libs/tests/GuestNpCommon.cpp new file mode 100644 index 0000000000..fc8817dbf1 --- /dev/null +++ b/core/libs/tests/GuestNpCommon.cpp @@ -0,0 +1,148 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceNpCommon/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include + +static constexpr int INVALID_ARGUMENT = static_cast(0x80550003); +static constexpr int INVALID_PLATFORM_TYPE = static_cast(0x80550004); +static constexpr int NOT_MATCH = static_cast(0x80550609); +static constexpr int COND_TIMEDOUT = static_cast(0x8055800B); +static constexpr int MUTEX_BUSY = static_cast(0x8055800F); + +static bool expect(bool condition, const char* message) { + if (!condition) std::fprintf(stderr, "%s\n", message); + return condition; +} + +static NpOnlineId onlineId(const char* text) { + NpOnlineId id{}; + std::strncpy(id.data, text, sizeof(id.data)); + return id; +} + +static NpIdFull npIdWithTag(const char* handle, std::uint32_t tag) { + NpIdFull id{}; + id.handle = onlineId(handle); + std::memcpy(id.opt + 4, &tag, sizeof(tag)); + return id; +} + +static int testIds() { + const NpOnlineId kestrel = onlineId("kestrel_77"); + const NpOnlineId kestrelCopy = onlineId("kestrel_77"); + const NpOnlineId heron = onlineId("heron-4"); + bool ok = expect(sceNpCmpOnlineId(nullptr, &kestrel) == INVALID_ARGUMENT, "a null online ID was compared"); + ok &= expect(sceNpCmpOnlineId(&kestrel, &kestrelCopy) == 0, "equal online IDs did not match"); + ok &= expect(sceNpCmpOnlineId(&kestrel, &heron) == NOT_MATCH, "different online IDs matched"); + + NpIdFull low = npIdWithTag("kestrel_77", 0x00347370); + NpIdFull high = low; + high.reserved[7] = 1; + std::int32_t order = 5; + ok &= expect(sceNpCmpNpIdInOrder(&low, &high, nullptr) == INVALID_ARGUMENT, "a null order was accepted"); + ok &= expect(sceNpCmpNpIdInOrder(&low, &high, &order) == 0 && order == -1, "reserved bytes did not order the NpIds"); + ok &= expect(sceNpCmpNpIdInOrder(&high, &low, &order) == 0 && order == 1, "the reverse order is not 1"); + ok &= expect(sceNpCmpNpIdInOrder(&low, &low, &order) == 0 && order == 0, "an NpId is not equal to itself"); + high = low; + high.opt[0] = 9; + high.reserved[0] = 0; + low.reserved[0] = 200; + ok &= expect(sceNpCmpNpIdInOrder(&low, &high, &order) == 0 && order == -1, "opt did not take precedence over reserved"); + const NpIdFull heronId = npIdWithTag("heron-4", 0); + ok &= expect(sceNpCmpNpIdInOrder(&heronId, &low, &order) == 0 && order == -1, "the handle did not take precedence"); + + const NpIdFull none = npIdWithTag("heron-4", 0); + const NpIdFull ps3 = npIdWithTag("heron-4", 0x00337370); + const NpIdFull vita = npIdWithTag("heron-4", 0x32707370); + const NpIdFull ps4 = npIdWithTag("heron-4", 0x00347370); + const NpIdFull other = npIdWithTag("heron-4", 0x00357370); + ok &= expect(sceNpGetPlatformType(nullptr) == INVALID_ARGUMENT, "a null NpId has a platform"); + ok &= expect(sceNpGetPlatformType(&none) == 0, "an untagged NpId is not platform 0"); + ok &= expect(sceNpGetPlatformType(&ps3) == 1, "the ps3 tag is not platform 1"); + ok &= expect(sceNpGetPlatformType(&vita) == 2, "the psp2 tag is not platform 2"); + ok &= expect(sceNpGetPlatformType(&ps4) == 3, "the ps4 tag is not platform 3"); + ok &= expect(sceNpGetPlatformType(&other) == INVALID_PLATFORM_TYPE, "an unknown tag was accepted"); + + NpOnlineId unterminated = onlineId("abcdefghijklmnop"); + unterminated.data[16] = 'q'; + ok &= expect(sceNpIntIsValidOnlineId(nullptr) == 0, "a null online ID is valid"); + ok &= expect(sceNpIntIsValidOnlineId(&kestrel) == 1, "kestrel_77 is not valid"); + ok &= expect(sceNpIntIsValidOnlineId(&heron) == 1, "heron-4 is not valid"); + const NpOnlineId sixteen = onlineId("abcdefghijklmnop"); + const NpOnlineId shortId = onlineId("ab"); + const NpOnlineId leadingDash = onlineId("-heron"); + const NpOnlineId dotted = onlineId("her.on"); + ok &= expect(sceNpIntIsValidOnlineId(&sixteen) == 1, "a 16 character ID is not valid"); + ok &= expect(sceNpIntIsValidOnlineId(&unterminated) == 0, "an unterminated ID is valid"); + ok &= expect(sceNpIntIsValidOnlineId(&shortId) == 0, "a 2 character ID is valid"); + ok &= expect(sceNpIntIsValidOnlineId(&leadingDash) == 0, "an ID starting with '-' is valid"); + ok &= expect(sceNpIntIsValidOnlineId(&dotted) == 0, "an ID with '.' is valid"); + return ok ? 0 : 1; +} + +static void* APS5_VABI returnTicket(void* arg) { + return reinterpret_cast(reinterpret_cast(arg) + 1); +} + +static int testSync() { + PthreadMutex plain = nullptr; + PthreadMutex recursive = nullptr; + bool ok = expect(sceNpMutexInit(&plain, "np-plain", 0) == 0 && plain, "a plain mutex was not created"); + ok &= expect(sceNpLwMutexInit(&recursive, "np-recursive", 1) == 0 && recursive, "a recursive mutex was not created"); + if (!ok) return 1; + ok &= expect(sceNpMutexLock(&plain) == 0, "the plain mutex did not lock"); + ok &= expect(sceNpMutexTryLock(&plain) == MUTEX_BUSY, "a held plain mutex did not report BUSY"); + ok &= expect(sceNpMutexUnlock(&plain) == 0, "the plain mutex did not unlock"); + ok &= expect(sceNpLwMutexTryLock(&recursive) == 0 && sceNpLwMutexTryLock(&recursive) == 0, "flag 1 did not make the mutex recursive"); + ok &= expect(sceNpLwMutexUnlock(&recursive) == 0 && sceNpLwMutexUnlock(&recursive) == 0, "the recursive mutex did not unlock twice"); + ok &= expect(sceNpLwMutexLock(&recursive) == 0, "the recursive mutex did not lock via sceNpLwMutexLock"); + ok &= expect(sceNpLwMutexUnlock(&recursive) == 0, "the recursive mutex did not unlock"); + ok &= expect(sceNpLwMutexDestroy(&recursive) == 0, "the recursive mutex was not destroyed"); + + PthreadCond cond = nullptr; + ok &= expect(sceNpCondInit(&cond, "np-cond", 0) == 0 && cond, "a condition was not created"); + ok &= expect(sceNpMutexLock(&plain) == 0, "the plain mutex did not lock for the wait"); + ok &= expect(sceNpCondTimedwait(&cond, &plain, 2000) == COND_TIMEDOUT, "a timed wait did not report TIMEDOUT"); + ok &= expect(sceNpMutexUnlock(&plain) == 0, "the mutex was not held after the wait"); + ok &= expect(sceNpCondSignal(&cond) == 0, "signalling without waiters failed"); + ok &= expect(sceNpCondDestroy(&cond) == 0, "the condition was not destroyed"); + ok &= expect(sceNpMutexDestroy(&plain) == 0, "the plain mutex was not destroyed"); + + Pthread thread = nullptr; + void* retval = nullptr; + ok &= expect(scePthreadCreate(&thread, nullptr, returnTicket, reinterpret_cast(0x4140), "np-join") == 0, "the thread was not created"); + ok &= expect(sceNpJoinThread(thread, &retval) == 0 && retval == reinterpret_cast(0x4141), "the join did not return the thread result"); + return ok ? 0 : 1; +} + +static int testClock() { + KernelTimespec before{}; + KernelTimespec after{}; + std::int64_t usec = -1; + sceKernelClockGettime(4, &before); + + bool ok = expect(sceNpGetSystemClockUsec(&usec) == 0, "the clock was not read"); + sceKernelClockGettime(4, &after); + + ok &= expect(usec >= before.tv_sec * 1000000 + before.tv_nsec / 1000 && usec <= after.tv_sec * 1000000 + after.tv_nsec / 1000, "the clock is not the monotonic kernel clock in microseconds"); + char version[12]; + std::memset(version, 'x', sizeof(version)); + sceNpGetSdkVersion(version); + + ok &= expect(std::strcmp(version, "1.00") == 0, "the version is not 1.00"); + ok &= expect(version[8] == 'x', "the version was written past 8 bytes"); + return ok ? 0 : 1; +} + +int main(int argc, char** argv) { + if (argc != 2) return 2; + int status = 2; + if (std::strcmp(argv[1], "ids") == 0) status = testIds(); + else if (std::strcmp(argv[1], "sync") == 0) status = testSync(); + else if (std::strcmp(argv[1], "clock") == 0) status = testClock(); + if (status == 0) std::printf("NpCommon %s tests passed\n", argv[1]); + return status; +} diff --git a/core/libs/tests/GuestNpCommonRuntime.cpp b/core/libs/tests/GuestNpCommonRuntime.cpp new file mode 100644 index 0000000000..1747776629 --- /dev/null +++ b/core/libs/tests/GuestNpCommonRuntime.cpp @@ -0,0 +1,432 @@ +#include "prx/libSceNpCommon/include/Api.hpp" +#include "SceTypes.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int NpIpcAlreadyInitialized = static_cast(0x80559D00); +constexpr int NpIpcNotInitialized = static_cast(0x80559D01); +constexpr int NpOutOfMemory = static_cast(0x80550005); +constexpr int NpNullObject = static_cast(0x80558005); +constexpr int NpNotInitialized = static_cast(0x80558001); +constexpr int NpAlreadyInitialized = static_cast(0x80558002); +constexpr int NpAlreadyQueued = static_cast(0x80558006); +constexpr int NpUtilInvalid = static_cast(0x80550601); +constexpr int NpUtilBufferTooSmall = static_cast(0x80550602); +constexpr int NpUtilOutOfRange = static_cast(0x8055060A); +constexpr int NpUtilInvalidCharacter = static_cast(0x80550608); +constexpr int NpInvalidFormat = static_cast(0x80558003); +constexpr int NpInvalidCharacter = static_cast(0x80558004); +constexpr int NpBufferTooSmall = static_cast(0x80558008); +constexpr int NpOutOfRange = static_cast(0x8055800E); +constexpr int NpInvalidArgument = static_cast(0x80558009); +constexpr int NpEventFlagBusy = static_cast(0x8055800C); +constexpr int NpSemaBusy = static_cast(0x8055800D); +constexpr int KernelNotPermitted = static_cast(0x80020001); +constexpr int KernelNoSuchObject = static_cast(0x80020003); +constexpr std::uint32_t EventFlagWaitAnd = 1; +constexpr std::uint32_t EventFlagShared = 0x100; + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +void* APS5_VABI ReturnArgument(void* arg) { + Pthread self {}; + Require(sceNpThreadGetId(&self) == 0 && self != nullptr, "a created thread can read its id"); + return arg; +} + +void TestAtomics() { + std::int32_t value = 5; + Require(sceNpAtomicInc32(&value) == 5 && value == 6, "increment returns the previous value"); + Require(sceNpAtomicDec32(&value) == 6 && value == 5, "decrement returns the previous value"); + Require(sceNpAtomicCas32(&value, 4, 9) == 5 && value == 5, "a failed compare-and-swap returns the current value"); + Require(sceNpAtomicCas32(&value, 5, 9) == 5 && value == 9, "a successful compare-and-swap returns the previous value"); +} + +void TestSemaphoresAndFlags() { + KernelSema sema {}; + Require(sceNpCreateSema(&sema, 0, 0, 2, "NpSema") == 0, "a semaphore is created"); + Require(sceNpPollSema(sema) == NpSemaBusy, "an empty semaphore reports the busy error"); + Require(sceNpSignalSema(sema) == 0 && sceNpPollSema(sema) == 0, "a signalled semaphore can be taken once"); + Require(sceNpDeleteSema(sema) == 0, "the semaphore is deleted"); + + KernelEventFlag flag {}; + Require(sceNpCreateEventFlag(&flag, "NpFlag", 0, 0) == 0, "an event flag is created"); + std::uint64_t result = 0; + Require(sceNpPollEventFlag(flag, 1, EventFlagWaitAnd, &result) == NpEventFlagBusy, "a clear event flag reports the busy error"); + Require(sceNpSetEventFlag(flag, 1) == 0 && sceNpPollEventFlag(flag, 1, EventFlagWaitAnd, &result) == 0 && result == 1, "a set bit is polled"); + Require(sceNpDeleteEventFlag(flag) == 0, "the event flag is deleted"); +} + +void TestSharedEventFlags() { + KernelEventFlag creator {}; + Require(sceNpCreateEventFlag(&creator, "NpSharedFlag", EventFlagShared, 0) == 0, "a shared event flag is created"); + KernelEventFlag opened {}; + Require(sceNpOpenEventFlag(&opened, "NpSharedFlag") == 0 && opened != creator, "the shared event flag is opened by name"); + std::uint64_t result = 0; + Require(sceNpSetEventFlag(opened, 2) == 0 && sceNpPollEventFlag(creator, 2, EventFlagWaitAnd, &result) == 0 && result == 2, + "a bit set through the opened handle is polled through the creator"); + Require(sceNpCloseEventFlag(creator) == KernelNotPermitted, "the creator handle is not closed"); + Require(sceNpCloseEventFlag(opened) == 0, "the opened handle is closed"); + Require(sceNpCloseEventFlag(opened) == KernelNoSuchObject, "a closed handle is gone"); + Require(sceNpDeleteEventFlag(creator) == 0, "the creator deletes the shared event flag"); +} + +void TestThreads() { + Require(sceNpThreadGetId(nullptr) == NpInvalidArgument, "a null thread id output is refused"); + Pthread thread {}; + int marker = 0; + Require(sceNpCreateThread(&thread, ReturnArgument, &marker, 0, 0x10000, 0, "NpThread") == 0, "a thread is created"); + void* value = nullptr; + Require(sceNpJoinThread(thread, &value) == 0 && value == &marker, "the thread is joined with its return value"); +} + +void UseAllocator(const NpAllocator& allocator) { + auto* block = static_cast(allocator.allocate(64, allocator.context)); + Require(block != nullptr, "the allocator hands out memory"); + block[63] = 0x5A; + block = static_cast(allocator.reallocate(block, 256, allocator.context)); + Require(block != nullptr && block[63] == 0x5A, "reallocation keeps the contents"); + allocator.release(block, allocator.context); + allocator.release(nullptr, allocator.context); +} + +void TestHeaps() { + NpMemoryHeap memoryHeap {}; + Require(sceNpMemoryHeapInit(nullptr, 0x1000, "np-test") == NpNullObject, "a null memory heap is refused"); + Require(sceNpMemoryHeapInit(&memoryHeap, 0x1001, "np-test") == 0, "a memory heap is created"); + Require(memoryHeap.memory.size == 0x4000 && memoryHeap.memory.address != nullptr && memoryHeap.heap.mspace != nullptr, + "the memory heap size is rounded up to 16 KiB"); + NpAllocatorEx allocatorEx {}; + sceNpMemoryHeapGetAllocatorEx(&memoryHeap, 0x1234, &allocatorEx); + + Require(allocatorEx.allocator.context == &memoryHeap && allocatorEx.outOfMemoryError == 0x1234, + "the extended allocator carries the caller's error code"); + UseAllocator(allocatorEx.allocator); + sceNpMemoryHeapDestroy(&memoryHeap); + + Require(memoryHeap.memory.address == nullptr && memoryHeap.heap.mspace == nullptr, "destroying clears the memory heap"); + + NpKernelMemory memory {}; + void* address = nullptr; + Require(sceNpAllocateKernelMemoryWithAlignment(0x10, "np-test", &address, nullptr) == NpNullObject, + "kernel memory needs an object"); + Require(sceNpAllocateKernelMemoryWithAlignment(0x10, "np-test", &address, &memory) == 0 + && address == memory.address && memory.size == 0x4000, + "kernel memory is mapped in 16 KiB units"); + NpHeap heap {}; + Require(sceNpHeapInit(&heap, memory.address, memory.size, "np-test") == 0 && heap.mspace != nullptr, "a heap is created"); + NpAllocator allocator {}; + sceNpHeapGetAllocator(&heap, &allocator); + + Require(allocator.context == &heap, "the heap allocator uses the heap as its context"); + UseAllocator(allocator); + NpHeapStat stat {}; + Require(sceNpHeapGetStat(&heap, &stat) == 0 && stat.maxInuseSize >= stat.currentInuseSize, "heap statistics are reported"); + sceNpHeapDestroy(&heap); + + Require(heap.mspace == nullptr, "destroying clears the heap"); + Require(sceNpFreeKernelMemory(&memory) == 0 && memory.address == nullptr, "kernel memory is released"); + Require(sceNpFreeKernelMemory(nullptr) == NpNullObject, "freeing needs an object"); + + Require(sceNpArchInit() == 0 && sceNpArchInit() == 0, "the library initialises more than once"); + NpAllocatorEx* global = sceNpGlobalHeapGetAllocatorExPtr(); + Require(global->outOfMemoryError == NpOutOfMemory, "the global allocator reports the library's out-of-memory error"); + UseAllocator(global->allocator); + sceNpArchTerm(); + + UseAllocator(sceNpGlobalHeapGetAllocatorExPtr()->allocator); + sceNpArchTerm(); + + Require(sceNpIpc2ClientTerm() == NpIpcNotInitialized, "the ipc client must be started before it stops"); + Require(sceNpIpc2ClientInit() == 0 && sceNpIpc2ClientInit() == NpIpcAlreadyInitialized, "the ipc client starts once"); + Require(sceNpIpc2ClientTerm() == 0 && sceNpIpc2ClientInit() == 0 && sceNpIpc2ClientTerm() == 0, "the ipc client restarts"); +} + +struct CalloutRecord { + std::atomic order {0}; + std::atomic first {0}; + std::atomic second {0}; +}; + +CalloutRecord g_callouts; + +void APS5_VABI RecordFirst(void*) { + g_callouts.first = ++g_callouts.order; +} + +void APS5_VABI RecordSecond(void*) { + g_callouts.second = ++g_callouts.order; +} + +void APS5_VABI RecordUnexpected(void*) { + Require(false, "a stopped callout does not run"); +} + +void TestCallouts() { + NpCalloutContext context {}; + NpCallout callout {}; + Require(sceNpCalloutStartOnCtx(&context, &callout, 0, RecordFirst, nullptr) == NpNotInitialized, "a context must be initialised first"); + Require(sceNpCalloutInitCtx(&context, "np-callout", 0x4000, 0, 0) == 0, "a callout context starts"); + Require(sceNpCalloutInitCtx(&context, "np-callout", 0x4000, 0, 0) == NpAlreadyInitialized, "a context starts once"); + + NpCallout stopped {}; + int wasPending = 0; + Require(sceNpCalloutStartOnCtx(&context, &stopped, 60000000, RecordUnexpected, nullptr) == 0, "a distant callout is queued"); + Require(sceNpCalloutStartOnCtx(&context, &stopped, 60000000, RecordUnexpected, nullptr) == NpAlreadyQueued, + "a queued callout cannot be queued again"); + Require(sceNpCalloutStopOnCtx(&context, &stopped, &wasPending) == 0 && wasPending == 1, "a queued callout is removed"); + Require(sceNpCalloutStopOnCtx(&context, &stopped, &wasPending) == 0 && wasPending == 0, "removing twice reports nothing pending"); + + NpCallout queuedFirst {}; + NpCallout queuedSecond {}; + Require(sceNpCalloutStartOnCtx(&context, &queuedFirst, 30000000, RecordUnexpected, nullptr) == 0, "an earlier distant callout is queued"); + Require(sceNpCalloutStartOnCtx(&context, &queuedSecond, 60000000, RecordUnexpected, nullptr) == 0, "a later distant callout is queued"); + const std::uint64_t queuedDeadline = queuedFirst.deadline; + Require(sceNpCalloutStartOnCtx(&context, &queuedFirst, 0, RecordFirst, &context) == NpAlreadyQueued, + "the earlier callout cannot be queued again"); + Require(queuedFirst.next == &queuedSecond && queuedFirst.deadline == queuedDeadline && queuedFirst.handler == RecordUnexpected && + queuedFirst.arg == nullptr, "a rejected start leaves the queued callout unchanged"); + Require(sceNpCalloutStopOnCtx(&context, &queuedSecond, &wasPending) == 0 && wasPending == 1, + "the later callout is still pending after the rejected start"); + Require(sceNpCalloutStopOnCtx(&context, &queuedFirst, &wasPending) == 0 && wasPending == 1, "the earlier callout is still pending"); + + NpCallout later {}; + Require(sceNpCalloutStartOnCtx(&context, &later, 20000, RecordSecond, nullptr) == 0, "a later callout is queued"); + Require(sceNpCalloutStartOnCtx(&context, &callout, 1000, RecordFirst, nullptr) == 0, "an earlier callout is queued"); + for (int i = 0; i < 2000 && g_callouts.second == 0; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(g_callouts.first == 1 && g_callouts.second == 2, "callouts run in deadline order"); + sceNpCalloutTermCtx(&context); + + Require(context.initialized == 0, "terminating clears the context"); + sceNpCalloutTermCtx(&context); + +} + +bool Equals(const char* actual, const char* expected) { + return std::strcmp(actual, expected) == 0; +} + +void TestIds() { + Require(sceNpUtilCmpAccountId(7, 7) == 0 && sceNpUtilCmpAccountId(7, 8) == 1, "account ids compare for equality"); + + constexpr int NullArgument = static_cast(0x80550003); + constexpr int InvalidNpId = static_cast(0x80550605); + constexpr int NotMatch = static_cast(0x80550609); + auto makeNpId = [](const char* name, std::uint32_t opt, std::uint32_t platform) { + std::array id{}; + std::memcpy(id.data(), name, std::strlen(name)); + std::memcpy(id.data() + 0x14, &opt, sizeof(opt)); + std::memcpy(id.data() + 0x18, &platform, sizeof(platform)); + id[0x1C] = 1; + return id; + }; + const auto alice = makeNpId("Alice_01", 5, 0x11223344); + auto other = alice; + Require(sceNpCmpNpId(nullptr, alice.data()) == NullArgument && sceNpCmpNpId(alice.data(), nullptr) == NullArgument, "null NpIds are refused"); + Require(sceNpCmpNpId(alice.data(), other.data()) == 0, "identical NpIds match"); + other[0x1C] = 0; + Require(sceNpCmpNpId(alice.data(), other.data()) == InvalidNpId && sceNpCmpNpId(other.data(), alice.data()) == InvalidNpId, "the flag byte at 0x1C must be 1"); + Require(sceNpCmpNpId(makeNpId("Bob-2", 5, 0x11223344).data(), alice.data()) == NotMatch, "different online ids do not match"); + Require(sceNpCmpNpId(makeNpId("Alice_01", 6, 0x11223344).data(), alice.data()) == NotMatch, "different words at 0x14 do not match"); + Require(sceNpCmpNpId(makeNpId("Alice_01", 5, 0).data(), alice.data()) == 0, "an empty first byte at 0x18 on the first id matches"); + Require(sceNpCmpNpId(alice.data(), makeNpId("Alice_01", 5, 0).data()) == 0, "an empty first byte at 0x18 on the second id matches"); + Require(sceNpCmpNpId(alice.data(), makeNpId("Alice_01", 5, 0x11223355).data()) == NotMatch, "different words at 0x18 do not match"); + + Require(sceNpIntIsOnlineIdString("Alice_01") && sceNpIntIsOnlineIdString("Bob-2"), "letters, digits, '_' and '-' are allowed"); + Require(!sceNpIntIsOnlineIdString("ab") && !sceNpIntIsOnlineIdString("abcdefghijklmnopq"), "online ids are 3 to 16 characters"); + Require(!sceNpIntIsOnlineIdString("1abc") && !sceNpIntIsOnlineIdString("ab.c") && !sceNpIntIsOnlineIdString(nullptr), + "online ids start with a letter and use no other symbols"); +} + +void TestJsonText() { + char escaped[64] = {}; + Require(sceNpUtilJsonEscape(escaped, sizeof(escaped), "a\"b/\n\x01", 7) == 0 && Equals(escaped, "a\\\"b\\/\\n\\u0001"), + "json escaping covers quotes, solidus, newlines and controls"); + std::strcpy(escaped, "x"); + 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"); + + char text[32] = {}; + std::size_t length = 0; + Require(sceNpUtilJsonUnescape(text, sizeof(text), "a\\nb\\u0041", 10, &length) == 0 && Equals(text, "a\nbA") && length == 5, + "json unescaping decodes escapes and counts the terminator"); + Require(sceNpUtilJsonUnescape(text, sizeof(text), "\\uD83D\\uDE00", 12, &length) == 0 && Equals(text, "\xF0\x9F\x98\x80") + && length == 5, + "surrogate pairs become one UTF-8 character"); + Require(sceNpUtilJsonUnescape(text, sizeof(text), "\\u00G1", 6, &length) == NpUtilBufferTooSmall && Equals(text, "\\u00G1"), + "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"); + + char one[4] = {}; + std::size_t consumed = 0; + Require(sceNpUtilJsonGetOneChar(one, sizeof(one), "\\u00e9x", 7, &length, &consumed) == 0 && length == 2 && consumed == 6 + && std::memcmp(one, "\xC3\xA9", 2) == 0, + "one escaped character is decoded"); + Require(sceNpUtilJsonGetOneChar(one, sizeof(one), "\xE6\x97\xA5", 3, &length, &consumed) == 0 && length == 3 && consumed == 3, + "one UTF-8 character is copied"); + Require(sceNpUtilJsonGetOneChar(one, sizeof(one), "\xE6\x97", 2, &length, &consumed) == NpUtilBufferTooSmall && length == 0 + && consumed == 3, + "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"); + + const char* character = nullptr; + const char* japanese = "\xE6\x97\xA5" "a"; + Require(sceNpUtilGetNthChar(1, japanese, &character) == 0 && character == japanese + 3, "characters are counted, not bytes"); + Require(sceNpUtilGetNthChar(10, "abc", &character) == 0 && *character == '\0', "counting stops at the end"); + Require(sceNpUtilGetNthChar(2, "a\x01b", &character) == NpUtilInvalidCharacter, "control characters are refused"); + + char url[32] = {}; + Require(sceNpUtilHttpUrlEncode(url, sizeof(url), "a b=c*", 6, &length) == 0 && Equals(url, "a+b%3Dc*") && length == 9, + "form encoding keeps the safe set and escapes the rest"); + Require(sceNpUtilHttpUrlEncode(nullptr, 0, "@", 1, &length) == 0 && length == 4, "a null buffer reports the encoded size"); + Require(sceNpUtilHttpUrlEncode(url, 3, "@", 1, &length) == NpUtilBufferTooSmall, "encoding needs room for the terminator"); +} + +void TestTitleIds() { + NpTitleIdParts parts {{'A', 'B', 'C', 'D', '1', '2', '3', '4', '5'}, 0, 7}; + char titleId[13] = {}; + Require(sceNpUtilBuildTitleId(&parts, titleId, sizeof(titleId)) == 0 && Equals(titleId, "ABCD12345_07"), "the index is two digits"); + Require(sceNpUtilBuildTitleId(&parts, titleId, 12) == NpUtilBufferTooSmall, "a title id needs 13 bytes"); + parts.index = 100; + Require(sceNpUtilBuildTitleId(&parts, titleId, sizeof(titleId)) == NpUtilOutOfRange, "the index is at most 99"); +} + +void TestLanguagesAndDebugIds() { + char code[6] = {}; + char tag[36] = {}; + Require(sceNpUtilGetNpLanguageCode(1, code) == 0 && Equals(code, "en"), "language 1 is English"); + Require(sceNpUtilGetNpLanguageCode2(20, tag) == 0 && Equals(tag, "es-419"), "language 20 is Latin American Spanish"); + Require(Equals(sceNpUtilGetNpLanguageCodeStr(11), "zh-CN") && Equals(sceNpUtilGetNpLanguageCode2Str(30), "uk-UA"), + "language strings are returned directly"); + Require(sceNpUtilGetNpLanguageCode(31, code) == NpUtilInvalid && sceNpUtilGetNpLanguageCodeStr(31) == nullptr, + "unknown languages are refused"); + Require(sceNpUtilGetNpLanguageCode2(0, nullptr) == NpUtilInvalid, "a language code needs a buffer"); + const std::uint32_t first = sceNpDbgAssignDebugId(); + Require(first != 0 && sceNpDbgAssignDebugId() == first + 1, "debug ids count up"); +} + +void TestBase64() { + const std::uint8_t payload[] = {1, 2, 3, 4, 5}; + char text[16] = {}; + Require(sceNpBase64Encoder(payload, text, 5) == 8 && Equals(text, "AQIDBAU="), "base64 pads the last group"); + Require(sceNpBase64Encoder(payload, text, 0) == 0, "an empty payload encodes to nothing"); + Require(sceNpBase64GetDecodeSize("AQIDBAU=", 8) == 5 && sceNpBase64GetDecodeSize("AQ==", 4) == 1, + "the decoded size follows the padding"); + Require(sceNpBase64GetDecodeSize("", 0) == NpInvalidFormat && sceNpBase64GetDecodeSize("AQI", 3) == NpInvalidFormat, + "base64 needs whole groups"); + Require(sceNpBase64GetDecodeSize("AQ=A", 4) == NpInvalidCharacter && sceNpBase64GetDecodeSize("A=AAAQID", 8) == NpInvalidCharacter, + "padding is only allowed at the end"); + std::uint8_t bytes[8] = {}; + Require(sceNpBase64Decoder("AQIDBAU=", 8, bytes, sizeof(bytes)) == 5 && std::memcmp(bytes, payload, 5) == 0, + "base64 decodes back to the payload"); + Require(sceNpBase64Decoder("AQID", 4, bytes, 2) == NpInvalidFormat, "decoding refuses a short buffer"); + Require(sceNpBase64Decoder("AQ?D", 4, bytes, 3) == NpInvalidCharacter, "decoding refuses characters outside the alphabet"); + + const std::uint8_t url[] = {0xFB, 0x00}; + std::memset(text, 0, sizeof(text)); + Require(sceNpBase64UrlEncoder(url, text, 1) == 2 && Equals(text, "-w"), "base64url leaves out the padding"); + Require(sceNpBase64UrlEncoder(url, text, 2) == 3 && Equals(text, "-wA"), "base64url writes three characters for two bytes"); + Require(sceNpBase64UrlGetDecodeSize("-wA", 3) == 2 && sceNpBase64UrlGetDecodeSize("-w==", 4) == 1, + "base64url accepts padded and unpadded tails"); + Require(sceNpBase64UrlGetDecodeSize("", 0) == NpInvalidCharacter && sceNpBase64UrlGetDecodeSize("AAAAA", 5) == NpInvalidCharacter, + "a single trailing character cannot be decoded"); + Require(sceNpBase64UrlGetDecodeSize("+w==", 4) == NpInvalidCharacter, "base64url refuses the standard alphabet"); + Require(sceNpBase64UrlDecoder("-wA", 3, bytes, 2) == 2 && bytes[0] == 0xFB && bytes[1] == 0, "base64url decodes a short tail"); + Require(sceNpBase64UrlDecoder("-w", 2, bytes, 0) == NpInvalidFormat, "base64url decoding refuses a short buffer"); +} + +void TestNumbers() { + Require(sceNpHexToInt('a') == 10 && sceNpHexToInt('F') == 15 && sceNpHexToInt('g') == -1, "hex digits are read"); + Require(sceNpIntToHex(10, false) == 'a' && sceNpIntToHex(15, true) == 'F' && sceNpIntToHex(9, true) == '9', "hex digits are written"); + + char text[32] = {}; + std::size_t length = 0; + Require(sceNpUInt32ToStr(text, sizeof(text), 255, 16, 3, &length) == 0 && Equals(text, "0XFF") && length == 4, + "the hex prefix follows the case flag"); + Require(sceNpUInt32ToStr(text, sizeof(text), 8, 8, 2, &length) == 0 && Equals(text, "010") && length == 3, "octal gets a zero prefix"); + Require(sceNpUInt32ToStr(text, sizeof(text), 0, 10, 4, &length) == 0 && Equals(text, "0") && length == 1, "zero has no sign"); + Require(sceNpUInt32ToStr(text, 4, 1234, 10, 0, &length) == NpBufferTooSmall, "the terminator must fit"); + Require(sceNpUInt32ToStr(text, sizeof(text), 1, 37, 0, &length) == NpInvalidArgument, "the radix is limited to 36"); + Require(sceNpUInt32ToStr(nullptr, 0, 42, 10, 4, &length) == 0 && length == 3, "a null buffer reports the length"); + Require(sceNpInt32ToStr(text, sizeof(text), -2147483647 - 1, 10, 0, &length) == 0 && Equals(text, "-2147483648"), + "a negative 32-bit value always has a sign"); + Require(sceNpInt32ToStr(text, 3, -42, 10, 0, &length) == NpBufferTooSmall, "the sign takes buffer space"); + Require(sceNpInt64ToStr(text, sizeof(text), -42, 10, 0, &length) == 0 && Equals(text, "42") && length == 2, + "a 64-bit value only gets a sign with the sign flag"); + Require(sceNpInt64ToStr(text, sizeof(text), -42, 10, 4, &length) == 0 && Equals(text, "-42"), "the sign flag writes the minus"); + Require(sceNpUInt64ToStr(text, sizeof(text), UINT64_MAX, 16, 3, &length) == 0 && Equals(text, "0XFFFFFFFFFFFFFFFF") && length == 18, + "the full 64-bit range is formatted"); + + std::int32_t value32 = 0; + std::size_t parsed = 0; + Require(sceNpStrToInt32("-567abc", &value32, 10, &parsed) == 0 && value32 == -567 && parsed == 4, "parsing stops at the first non-digit"); + Require(sceNpStrToInt32("0x1A", &value32, 0, &parsed) == 0 && value32 == 26, "base zero follows the prefix"); + Require(sceNpStrToInt32("invalid", &value32, 10, &parsed) == NpOutOfRange, "no digits is an error"); + parsed = 0; + Require(sceNpStrToInt32("3000000000", &value32, 10, &parsed) == NpOutOfRange && parsed == 10, + "the length is reported before the 32-bit range check"); + std::int64_t value64 = 0; + Require(sceNpStrToInt64("-9223372036854775808", &value64, 10, nullptr) == 0 && value64 == INT64_MIN, "the 64-bit minimum parses"); + Require(sceNpStrToInt64("99999999999999999999", &value64, 10, nullptr) == NpOutOfRange, "64-bit overflow is an error"); + std::uint32_t unsigned32 = 0; + Require(sceNpStrToUInt32("4294967295", &unsigned32, 10, nullptr) == 0 && unsigned32 == UINT32_MAX, "the 32-bit maximum parses"); + Require(sceNpStrToUInt32("4294967296", &unsigned32, 10, nullptr) == NpOutOfRange, "32-bit unsigned overflow is an error"); + std::uint64_t unsigned64 = 0; + Require(sceNpStrToUInt64("0xFFFFFFFFFFFFFFFF", &unsigned64, 0, nullptr) == 0 && unsigned64 == UINT64_MAX, "the 64-bit maximum parses"); + Require(sceNpStrToUInt64("18446744073709551616", &unsigned64, 10, nullptr) == NpOutOfRange, "64-bit unsigned overflow is an error"); +} + +void TestHexAndCopies() { + const std::uint8_t data[] = {0x0A, 0xFE}; + char text[8] = {}; + Require(sceNpStrBuildHex(text, 5, data, 2, false) == 0 && Equals(text, "0afe"), "bytes are written as lowercase hex"); + Require(sceNpStrBuildHex(text, 5, data, 2, true) == 0 && Equals(text, "0AFE"), "bytes are written as uppercase hex"); + Require(sceNpStrBuildHex(text, 4, data, 2, false) == NpBufferTooSmall, "hex output needs room for the terminator"); + std::uint8_t bytes[4] = {}; + Require(sceNpStrParseHex("0aFE", bytes, 2) == 0 && bytes[0] == 0x0A && bytes[1] == 0xFE, "hex is read in either case"); + Require(sceNpStrParseHex("0AF", bytes, 2) == NpInvalidArgument, "odd hex is refused"); + Require(sceNpStrParseHex("0AFE", bytes, 1) == NpBufferTooSmall, "hex parsing checks the output size"); + Require(sceNpStrParseHex("0AG1", bytes, 2) == NpInvalidArgument, "non-hex characters are refused"); + Require(sceNpStrnParseHex("DEADBEEF1234", 8, bytes, 4) == 0 && bytes[0] == 0xDE && bytes[3] == 0xEF, "a hex prefix is read"); + + char copy[8] = {}; + Require(sceNpStrcpyToBuf(copy, 6, "Hello") == 0 && Equals(copy, "Hello"), "a string that fits is copied"); + Require(sceNpStrcpyToBuf(copy, 5, "Hello") == NpBufferTooSmall, "a string without room for its terminator is refused"); + Require(sceNpStrncpyToBuf(copy, 6, "Hello World", 5) == 0 && Equals(copy, "Hello"), "the length limit sizes the copy"); + Require(sceNpStrncpyToBuf(copy, 5, "Hello World", 5) == NpBufferTooSmall, "the limited string still needs a terminator"); +} + +} + +int main() { + TestAtomics(); + TestSemaphoresAndFlags(); + TestSharedEventFlags(); + TestThreads(); + TestHeaps(); + TestBase64(); + TestNumbers(); + TestHexAndCopies(); + TestCallouts(); + TestIds(); + TestLanguagesAndDebugIds(); + TestJsonText(); + TestTitleIds(); + std::puts("guest np common runtime: PASS"); +} diff --git a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp index 99f3f83152..13cc9a8491 100644 --- a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp +++ b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -7,57 +8,11 @@ using Deleter = void (APS5_VABI*)(void*); -struct IntrusivePtr { - void* object; - Deleter deleter; - void* libContext; -}; - using Construct = void (APS5_VABI*)(IntrusivePtr*); using Copy = void (APS5_VABI*)(IntrusivePtr*, const IntrusivePtr*); using Destroy = void (APS5_VABI*)(IntrusivePtr*); using Access = void* (APS5_VABI*)(const IntrusivePtr*); -extern "C" { -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr*); - -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); - -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr*); - -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr*); -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr*); -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr*); -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr*); -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr*); -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr*); -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr*); -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr*); -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr*); -void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr*); - -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEED1Ev(IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr*, void*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(IntrusivePtr*); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(IntrusivePtr*, const IntrusivePtr*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(IntrusivePtr*, void*, Deleter, void*); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(IntrusivePtr*); -} - static void Require(bool value) { if (!value) std::abort(); } static constexpr std::size_t ObjectSize = 0x40; @@ -193,11 +148,13 @@ static void TestSharedOwnership() { IntrusivePtr owner; _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(&owner); + owner.object = object; owner.deleter = RecordDelete; IntrusivePtr argument; _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(&argument, &owner); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(&argument) == object); Require(ReadCount(object, 0) == 2); @@ -222,6 +179,7 @@ static void TestVectorPushBackReleasesElements() { IntrusivePtr vector; _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(&vector, &context); + Require(vector.object == nullptr); Require(vector.deleter == nullptr); Require(vector.libContext == &context); @@ -257,6 +215,7 @@ static void TestAdoptedVectorBodyDestroyedThroughDeleter() { IntrusivePtr vector; _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(&vector, body, RecordDelete, &context); + Require(vector.object == body); Require(vector.deleter == RecordDelete); Require(vector.libContext == &context); @@ -279,6 +238,11 @@ int main() { _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev, _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev, _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEC1Ev, }; for (const auto construct : constructors) TestConstruct(construct); @@ -293,12 +257,39 @@ int main() { _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv, _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv, _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEE3getEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEdeEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEdeEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V15OwnerEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEdeEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_17TitleCloudStorage2V110DataStatusEEEEEEdeEv, }; for (const auto access : accessors) TestAccess(access); const Copy copies[] = { _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_, _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1ERS7_, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEEC1ERS7_, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEC1ERS7_, }; for (const auto copy : copies) TestCopy(copy); @@ -311,6 +302,18 @@ int main() { {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev, VectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEED1Ev, VectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage2V15OwnerEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEED1Ev, VectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEED1Ev, VectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEED1Ev, VectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_17TitleCloudStorage2V110DataStatusEEEEEED1Ev, VectorReferenceCountOffset}, }; for (const auto& testCase : destructors) TestDestroy(testCase); diff --git a/core/libs/tests/GuestNpCppWebApiOffline.cpp b/core/libs/tests/GuestNpCppWebApiOffline.cpp new file mode 100644 index 0000000000..ed22df2b30 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiOffline.cpp @@ -0,0 +1,30 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +using SelfCall = int (APS5_VABI*)(void*); + +static void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "%s\n", what); + std::abort(); +} + +static void TestIteratorDestructors() { + unsigned char iterator[0x20]; + std::memset(iterator, 0x5a, sizeof(iterator)); + const SelfCall destructors[] = { + _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEED2Ev, + _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEED2Ev, + }; + for (const auto call : destructors) Require(call(iterator) == 0, "an iterator destructor failed"); + for (const auto byte : iterator) Require(byte == 0x5a, "an iterator destructor wrote to the iterator"); +} + +int main() { + TestIteratorDestructors(); + std::puts("NpCppWebApi offline tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestNpCppWebApiSignedOut.cpp b/core/libs/tests/GuestNpCppWebApiSignedOut.cpp new file mode 100644 index 0000000000..826ceb8fa0 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiSignedOut.cpp @@ -0,0 +1,126 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +static constexpr int NotSignedIn = static_cast(0x80552907); + +using Object = int (APS5_VABI*)(void*); +using Call = int (APS5_VABI*)(); + +struct RequestFlow { + Object transactionConstruct; + Object transactionDestroy; + Call setOnFinishedCallback; + Call setResponseInformationOption; + Call start; + Call finish; + Call getResponseHeaders; + Object parameterConstruct; + Object parameterDestroy; + Call initialize; + Call terminate; + Call call; +}; + +static const RequestFlow flows[] = { + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsC1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsD1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends10initializeEPNS1_6Common10LibContextEPKc, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends9terminateEv, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi10getFriendsEiRKNS4_21ParameterToGetFriendsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_18GetFriendsResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersC1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersD1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers10initializeEPNS1_6Common10LibContextE, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers9terminateEv, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi16getBlockingUsersEiRKNS4_27ParameterToGetBlockingUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetBlockingUsersResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesC1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesD1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences10initializeEPNS1_6Common10LibContextEPKc, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences9terminateEv, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi17getBasicPresencesEiRKNS4_28ParameterToGetBasicPresencesERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_25GetBasicPresencesResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityC1Ev, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityD1Ev, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity10initializeEPNS1_6Common10LibContextEPKcSA_NS6_12IntrusivePtrINS3_19WebApiFilterRequestEEE, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity9terminateEv, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi15filterProfanityEiRKNS4_26ParameterToFilterProfanityERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23FilterProfanityResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusC1Ev, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusD1Ev, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus10initializeEPNS1_6Common10LibContextEm, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus9terminateEv, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi33getCommunicationRestrictionStatusEiRKNS4_44ParameterToGetCommunicationRestrictionStatusERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_38CommunicationRestrictionStatusResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, +}; + +static void TestRequestFlow(const RequestFlow& flow) { + alignas(16) unsigned char transaction[0x100]; + alignas(16) unsigned char parameter[0x100]; + std::memset(transaction, 0x5a, sizeof(transaction)); + std::memset(parameter, 0x5a, sizeof(parameter)); + + Require(flow.transactionConstruct(transaction) == 0); + Require(flow.setOnFinishedCallback() == 0); + Require(flow.setResponseInformationOption() == 0); + Require(flow.parameterConstruct(parameter) == 0); + Require(flow.initialize() == 0); + Require(flow.call() == NotSignedIn); + Require(flow.start() == NotSignedIn); + Require(flow.getResponseHeaders() == NotSignedIn); + Require(flow.finish() == 0); + Require(flow.terminate() == 0); + Require(flow.parameterDestroy(parameter) == 0); + Require(flow.transactionDestroy(transaction) == 0); +} + +int main() { + for (const auto& flow : flows) TestRequestFlow(flow); + Require(_ZN3sce2Np9CppWebApi15ProfanityFilter2V226WebApiFilterRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_19WebApiFilterRequestEEE() == NotSignedIn); + return 0; +} diff --git a/core/libs/tests/GuestNpCppWebApiString.cpp b/core/libs/tests/GuestNpCppWebApiString.cpp index 18a4dd25a6..6d50a21eb3 100644 --- a/core/libs/tests/GuestNpCppWebApiString.cpp +++ b/core/libs/tests/GuestNpCppWebApiString.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,16 +6,6 @@ #include #include -extern "C" { -void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(void* self, void* libContext); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendEPKc(void* self, const char* text); -const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const void* self); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(void* self, void* libContext); -void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(void* self, const void* element); -} - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -81,18 +72,17 @@ void TestConstructedStringIsEmpty() { Storage storage; std::memset(storage.bytes, kFill, sizeof(storage.bytes)); - _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); - + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(storage.bytes), &libContext); Require(Load(storage, kReferenceCountOffset) == 0); Require(Load(storage, kBufferOffset) == nullptr); Require(Load(storage, kBufferSizeOffset) == 0); Require(Load(storage, kLibContextOffset) == &libContext); Require(GuardIntact(storage)); - const char* text = _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes); + const char* text = _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)); Require(text != nullptr && text[0] == '\0'); - _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(storage.bytes)); Require(Load(storage, kBufferOffset) == nullptr); Require(Load(storage, kBufferSizeOffset) == 0); Require(GuardIntact(storage)); @@ -102,29 +92,28 @@ void TestNullLibContextIsStored() { Storage storage; std::memset(storage.bytes, kFill, sizeof(storage.bytes)); - _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, nullptr); - + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(storage.bytes), nullptr); Require(Load(storage, kLibContextOffset) == nullptr); Require(Load(storage, kBufferOffset) == nullptr); - Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes)[0] == '\0'); - _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes))[0] == '\0'); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(storage.bytes)); + } void TestAppendBuildsAndClearsBuffer() { std::uint64_t libContext = 1; Storage storage; std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(storage.bytes), &libContext); - _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); - - Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(storage.bytes, "rank") == 0); - Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes), "rank") == 0); - Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(storage.bytes, "ing") == 0); - Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes), "ranking") == 0); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(storage.bytes), "rank") == 0); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)), "rank") == 0); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(storage.bytes), "ing") == 0); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)), "ranking") == 0); Require(Load(storage, kBufferOffset) != nullptr); Require(GuardIntact(storage)); - _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(storage.bytes)); Require(Load(storage, kBufferOffset) == nullptr); Require(Load(storage, kBufferSizeOffset) == 0); Require(GuardIntact(storage)); @@ -135,11 +124,12 @@ void TestAppendNullKeepsEmpty() { Storage storage; std::memset(storage.bytes, kFill, sizeof(storage.bytes)); - _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); - Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(storage.bytes, nullptr) == 0); + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(storage.bytes), &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(storage.bytes), nullptr) == 0); Require(Load(storage, kBufferOffset) == nullptr); - Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes)[0] == '\0'); - _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes))[0] == '\0'); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(storage.bytes)); + } void TestVectorPushBackCopiesStringContent() { @@ -149,15 +139,15 @@ void TestVectorPushBackCopiesStringContent() { std::memset(vector.bytes, kFill, sizeof(vector.bytes)); std::memset(element.bytes, kFill, sizeof(element.bytes)); - _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(vector.bytes, &libContext); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(reinterpret_cast(vector.bytes), &libContext); Require(VectorGuardIntact(vector)); - _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(element.bytes, &libContext); - Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(element.bytes, "privilege") == 0); + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(element.bytes), &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(element.bytes), "privilege") == 0); char* elementBuffer = Load(element, kBufferOffset); Require(elementBuffer != nullptr); - Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(vector.bytes, element.bytes) == 0); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(reinterpret_cast(vector.bytes), element.bytes) == 0); void* body; std::memcpy(&body, vector.bytes + kObjectOffset, sizeof(body)); @@ -173,13 +163,76 @@ void TestVectorPushBackCopiesStringContent() { Require(storedBuffer != elementBuffer); Require(std::strcmp(storedBuffer, "privilege") == 0); - _ZN3sce2Np9CppWebApi6Common6StringD1Ev(element.bytes); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(element.bytes)); Require(std::strcmp(storedBuffer, "privilege") == 0); - _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(vector.bytes); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(reinterpret_cast(vector.bytes)); Require(VectorGuardIntact(vector)); } +void TestStringEmptyAndEquals() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(storage.bytes), &libContext); + Require(_ZNK3sce2Np9CppWebApi6Common6String5emptyEv(reinterpret_cast(storage.bytes)) == 1); + Require(_ZN3sce2Np9CppWebApi6Common6StringeqEPKc(reinterpret_cast(storage.bytes), "rank") == 0); + + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(storage.bytes), "rank") == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6String5emptyEv(reinterpret_cast(storage.bytes)) == 0); + Require(_ZN3sce2Np9CppWebApi6Common6StringeqEPKc(reinterpret_cast(storage.bytes), "rank") == 1); + Require(_ZN3sce2Np9CppWebApi6Common6StringeqEPKc(reinterpret_cast(storage.bytes), "rank2") == 0); + Require(_ZN3sce2Np9CppWebApi6Common6StringeqEPKc(reinterpret_cast(storage.bytes), nullptr) == 0); + Require(GuardIntact(storage)); + + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(storage.bytes)); +} + +int g_elementDeleterCalls = 0; +void APS5_VABI ElementDeleter(void*) { ++g_elementDeleterCalls; } + +void TestIntrusivePtrVectorPushBack() { + std::uint64_t libContext = 1; + VectorStorage vector; + std::memset(vector.bytes, kFill, sizeof(vector.bytes)); + + alignas(8) unsigned char element[0x18]; + alignas(8) unsigned char elementObject[0x10]; + std::memset(element, 0, sizeof(element)); + std::memset(elementObject, 0, sizeof(elementObject)); + g_elementDeleterCalls = 0; + + void* object = elementObject; + auto* deleter = &ElementDeleter; + std::memcpy(element + 0x00, &object, sizeof(void*)); + std::memcpy(element + 0x08, &deleter, sizeof(void*)); + std::memcpy(element + 0x10, &libContext, sizeof(void*)); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEEC1EPNS2_10LibContextE(reinterpret_cast(vector.bytes), &libContext); + Require(VectorGuardIntact(vector)); + + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEE8pushBackERKS8_(reinterpret_cast(vector.bytes), element) == 0); + + void* body; + std::memcpy(&body, vector.bytes + kObjectOffset, sizeof(body)); + Require(body != nullptr); + Require(LoadBody(body, kBodyReferenceCountOffset) == 1); + Require(LoadBody(body, kBodyElementKindOffset) == 1); + Require(LoadBody(body, kBodySizeOffset) == 1); + Require(*reinterpret_cast(elementObject) == 1); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEED1Ev(reinterpret_cast(vector.bytes)); + Require(g_elementDeleterCalls == 1); + Require(VectorGuardIntact(vector)); +} + +void TestOfflineResponses() { + Require(_ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() == 0); + Require(_ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi17getPlayerSessionsEiRKNS4_28ParameterToGetPlayerSessionsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_29GetPlayerSessionsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() == static_cast(0x80552907)); + Require(_ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi26setPlayerSessionPropertiesEiRKNS4_37ParameterToSetPlayerSessionPropertiesERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE() == static_cast(0x80552907)); +} + } int main() { @@ -188,4 +241,7 @@ int main() { TestAppendBuildsAndClearsBuffer(); TestAppendNullKeepsEmpty(); TestVectorPushBackCopiesStringContent(); + TestStringEmptyAndEquals(); + TestIntrusivePtrVectorPushBack(); + TestOfflineResponses(); } diff --git a/core/libs/tests/GuestNpFriendListDialog.cpp b/core/libs/tests/GuestNpFriendListDialog.cpp new file mode 100644 index 0000000000..f06898db7b --- /dev/null +++ b/core/libs/tests/GuestNpFriendListDialog.cpp @@ -0,0 +1,63 @@ +#include "prx/libSceNpFriendListDialog/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80B80003); +constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); +constexpr int kErrNotFinished = static_cast(0x80B80005); +constexpr int kErrArgNull = static_cast(0x80B8000D); +constexpr int kResultUserCanceled = 1; + +} + +int main() { + std::int64_t param = 0; + std::int32_t result[2] = {-1, -1}; + + Require(sceNpFriendListDialogTerminate() == kErrNotInitialized); + Require(sceNpFriendListDialogOpenA(¶m) == kErrNotInitialized); + Require(sceNpFriendListDialogClose() == kErrNotInitialized); + Require(sceNpFriendListDialogGetResultA(result) == kErrNotInitialized); + Require(sceNpFriendListDialogGetStatus() == kStatusNone); + Require(sceNpFriendListDialogUpdateStatus() == kStatusNone); + + Require(sceNpFriendListDialogInitialize() == 0); + Require(sceNpFriendListDialogInitialize() == kErrAlreadyInitialized); + Require(sceNpFriendListDialogGetStatus() == kStatusInitialized); + Require(sceNpFriendListDialogUpdateStatus() == kStatusInitialized); + + Require(sceNpFriendListDialogOpenA(nullptr) == kErrArgNull); + Require(sceNpFriendListDialogGetResultA(result) == kErrNotFinished); + + Require(sceNpFriendListDialogOpenA(¶m) == 0); + Require(sceNpFriendListDialogGetStatus() == kStatusFinished); + Require(sceNpFriendListDialogUpdateStatus() == kStatusFinished); + + Require(sceNpFriendListDialogGetResultA(nullptr) == kErrArgNull); + Require(sceNpFriendListDialogGetResultA(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(result[1] == -1); + + Require(sceNpFriendListDialogClose() == 0); + Require(sceNpFriendListDialogTerminate() == 0); + Require(sceNpFriendListDialogTerminate() == kErrNotInitialized); + Require(sceNpFriendListDialogGetStatus() == kStatusNone); + Require(sceNpFriendListDialogUpdateStatus() == kStatusNone); + + Require(sceNpFriendListDialogInitialize() == 0); + Require(sceNpFriendListDialogUpdateStatus() == kStatusInitialized); + Require(sceNpFriendListDialogClose() == 0); + Require(sceNpFriendListDialogUpdateStatus() == kStatusFinished); + result[0] = -1; + Require(sceNpFriendListDialogGetResultA(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(sceNpFriendListDialogTerminate() == 0); +} diff --git a/core/libs/tests/GuestNpGameIntent.cpp b/core/libs/tests/GuestNpGameIntent.cpp index dbf030f79e..8e74607008 100644 --- a/core/libs/tests/GuestNpGameIntent.cpp +++ b/core/libs/tests/GuestNpGameIntent.cpp @@ -1,24 +1,12 @@ +#include "prx/libSceNpGameIntent/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include -extern "C" { -int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param); -int APS5_VABI sceNpGameIntentInfoInit(NpGameIntentInfo* info); -int APS5_VABI sceNpGameIntentInitialize(const void* initParam); -int APS5_VABI sceNpGameIntentReceiveIntent(NpGameIntentInfo* intentInfo); -int APS5_VABI sceNpGameIntentGetPropertyValueString(const NpGameIntentData* intentData, const char* key, char* valueBuf, size_t bufSize); -int APS5_VABI sceNpGameIntentVshInitialize(void); -int APS5_VABI sceNpGameIntentVshTerminate(void); -int APS5_VABI sceNpGameIntentTerminate(void); -int APS5_VABI sceNpAppLauncherInitialize(const void* initParam); -int APS5_VABI sceNpAppLauncherLaunchApp(void); -int APS5_VABI sceNpAppLauncherTerminate(void); -} - static void Require(bool value, const char* what) { if (value) return; std::printf("NpGameIntent: %s\n", what); @@ -36,6 +24,8 @@ static bool Throws(TCall call) { } int main() { + constexpr int kNotInitialized = -2141898750; + constexpr int kInvalidArgument = -2141898748; constexpr int kIntentNotFound = -2141898746; constexpr int kValueNotFound = -2141898745; @@ -54,22 +44,75 @@ int main() { Require(info.size == sizeof(NpGameIntentInfo), "info size mismatch"); Require(info.user_id == -1, "info user_id mismatch"); + char preBuf[64]{}; + Require(sceNpGameIntentGetPropertyValueString(nullptr, "key", preBuf, sizeof(preBuf)) == kNotInitialized, + "uninitialized get property string with null data must report not-initialized"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", preBuf, sizeof(preBuf)) == kNotInitialized, + "uninitialized get property string with valid data must report not-initialized"); + Require(sceNpGameIntentReceiveIntent(nullptr) == kNotInitialized, + "uninitialized receive intent with null pointer must report not-initialized"); + Require(sceNpGameIntentReceiveIntent(&info) == kNotInitialized, + "uninitialized receive intent with valid pointer must report not-initialized"); + Require(sceNpGameIntentInitialize(&initParam) == 0, "initialization failed"); - Require(sceNpGameIntentReceiveIntent(&info) == kIntentNotFound, "receive intent mismatch"); + Require(sceNpGameIntentReceiveIntent(nullptr) == kInvalidArgument, "null receive intent did not fail"); + Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "mismatched struct size must report invalid argument"); + info.size = 0; + Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "zero size must report invalid argument"); + info.size = 0x413f; + Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "0x413f size must report invalid argument"); + info.size = 0x4141; + Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "0x4141 size must report invalid argument"); + info.size = sizeof(NpGameIntentInfo); char buf[64]{}; - Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, sizeof(buf)) == kValueNotFound, + Require(sceNpGameIntentGetPropertyValueString(nullptr, "key", buf, sizeof(buf)) == kInvalidArgument, + "null intent data did not fail"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, nullptr, buf, sizeof(buf)) == kInvalidArgument, + "null key did not fail"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "", buf, sizeof(buf)) == kInvalidArgument, + "empty key did not fail"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", nullptr, sizeof(buf)) == kInvalidArgument, + "null value buf did not fail"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, 0) == kInvalidArgument, + "zero buf size did not fail"); + + char canaryBuf[64]{}; + std::memset(canaryBuf, 'X', sizeof(canaryBuf)); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", canaryBuf, 0) == kInvalidArgument, + "zero buf size canary call did not fail"); + Require(canaryBuf[0] == 'X', "buffer was modified on zero size error"); + Require(sceNpGameIntentGetPropertyValueString(nullptr, "key", canaryBuf, sizeof(canaryBuf)) == kInvalidArgument, + "null data canary call did not fail"); + Require(canaryBuf[0] == 'X', "buffer was modified on null data error"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, nullptr, canaryBuf, sizeof(canaryBuf)) == kInvalidArgument, + "null key canary call did not fail"); + Require(canaryBuf[0] == 'X', "buffer was modified on null key error"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "", canaryBuf, sizeof(canaryBuf)) == kInvalidArgument, + "empty key canary call did not fail"); + Require(canaryBuf[0] == 'X', "buffer was modified on empty key error"); + + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", canaryBuf, sizeof(canaryBuf)) == kValueNotFound, "get property value mismatch"); + Require(canaryBuf[0] == '\0', "valid call did not write terminator"); + + canaryBuf[0] = 'Y'; + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", canaryBuf, 1) == kValueNotFound, + "bufSize 1 did not succeed"); + Require(canaryBuf[0] == '\0', "bufSize 1 did not write terminator"); Require(sceNpGameIntentVshInitialize() == 0, "vsh init failed"); Require(sceNpGameIntentVshTerminate() == 0, "vsh terminate failed"); Require(sceNpGameIntentTerminate() == 0, "terminate failed"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, sizeof(buf)) == kNotInitialized, + "post-terminate get property value must report not-initialized"); + Require(sceNpGameIntentReceiveIntent(&info) == kNotInitialized, + "post-terminate receive intent must report not-initialized"); + unsigned char appLauncherParam[0x28]{}; *reinterpret_cast(appLauncherParam) = 0x28; Require(sceNpAppLauncherInitialize(appLauncherParam) == 0, "app launcher init failed"); - Require(Throws([] { sceNpAppLauncherLaunchApp(); }), - "app launcher launch must throw until implemented"); Require(sceNpAppLauncherTerminate() == 0, "app launcher terminate failed"); *reinterpret_cast(appLauncherParam) = 0x10; Require(sceNpAppLauncherInitialize(appLauncherParam) == 0, diff --git a/core/libs/tests/GuestNpManager.cpp b/core/libs/tests/GuestNpManager.cpp index 83a5566664..01c3629c98 100644 --- a/core/libs/tests/GuestNpManager.cpp +++ b/core/libs/tests/GuestNpManager.cpp @@ -1,59 +1,24 @@ +#include "prx/libSceNpManager/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include #include #include #include +#include #include #include -extern "C" { -int APS5_VABI sceNpGetAccountCountryA(int user_id, void* country_code); -int APS5_VABI sceNpGetNpId(int user_id, NpId* np_id); -int APS5_VABI sceNpGetOnlineId(int user_id, NpOnlineId* online_id); -int APS5_VABI sceNpGetUserIdByAccountId(std::uint64_t account_id, int* user_id); -int APS5_VABI sceNpUnknown_8weiQyIdI3A(int userId, void* buffer); -int APS5_VABI sceNpUnknown_WxLcN76q19o(void); -int APS5_VABI sceNpGetAccountCountry(const NpOnlineId* online_id, void* country_code); -int APS5_VABI sceNpGetAccountDateOfBirth(const NpOnlineId* online_id, void* date_of_birth); -int APS5_VABI sceNpGetAccountLanguage(int req_id, const NpOnlineId* online_id, void* language); -int APS5_VABI sceNpGetParentalControlInfo(int req_id, const NpOnlineId* online_id, std::int8_t* age, void* info); -int APS5_VABI sceNpGetUserIdByOnlineId(const NpOnlineId* online_id, int* user_id); -int APS5_VABI sceNpCheckPlus(int req_id, const void* param, void* result); -int APS5_VABI sceNpNotifyPlusFeature(const void* param); -int APS5_VABI sceNpGetAccountId(const NpOnlineId* online_id, std::uint64_t* account_id); -int APS5_VABI sceNpSetGamePresenceOnline(int req_id, const NpOnlineId* online_id, std::uint32_t presence); -int APS5_VABI sceNpRegisterPlusEventCallback(void* callback, void* userdata); -int APS5_VABI sceNpUnregisterPlusEventCallback(void); -} - -extern "C" { -int APS5_VABI sceNpCreateRequest(void); -int APS5_VABI sceNpCreateAsyncRequest(const NpCreateAsyncRequestParameter* param); -int APS5_VABI sceNpDeleteRequest(int reqId); -int APS5_VABI sceNpSetTimeout(int reqId, std::int32_t resolveRetry, std::uint32_t resolveTimeout, - std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout); -} - -extern "C" { -int APS5_VABI sceNpCheckCallback(void); -int APS5_VABI sceNpRegisterStateCallback(void* callback, void* userdata); -int APS5_VABI sceNpUnregisterStateCallback(void); -int APS5_VABI sceNpRegisterStateCallbackA(void* callback, void* userdata); -int APS5_VABI sceNpUnregisterStateCallbackA(int callbackId); -int APS5_VABI sceNpRegisterNpReachabilityStateCallback(void* callback, void* userdata); -void APS5_VABI sceNpRegisterGamePresenceCallback(void* callback, void* userdata); -int APS5_VABI sceNpUnregisterNpReachabilityStateCallback(void); -} - namespace { constexpr int InvalidArgument = static_cast(0x80550003u); constexpr int SignedOut = static_cast(0x80550006u); +constexpr int InvalidSize = static_cast(0x80550011u); constexpr int UserNotFound = static_cast(0x80550007u); constexpr int AlreadyRegistered = static_cast(0x80550008u); constexpr int NotRegistered = static_cast(0x80550009u); constexpr int RequestNotFound = static_cast(0x80550014u); +constexpr int InvalidId = static_cast(0x80550015u); void Require(bool condition, const char* message) { if (!condition) { @@ -122,56 +87,64 @@ void CheckStateCallbacksA() { int userdata[9]{}; Require(sceNpRegisterStateCallbackA(nullptr, &userdata[0]) == InvalidArgument, "null state callback"); int ids[8]{}; - ids[0] = sceNpRegisterStateCallbackA(callbacks[0], &userdata[0]); + ids[0] = sceNpRegisterStateCallbackA(reinterpret_cast(callbacks[0]), &userdata[0]); Require(ids[0] >= 1 && ids[0] <= 8, "state callback id outside 1..8"); - Require(sceNpRegisterStateCallbackA(callbacks[0], &userdata[1]) == AlreadyRegistered, "duplicate state callback"); + Require(sceNpRegisterStateCallbackA(reinterpret_cast(callbacks[0]), &userdata[1]) == AlreadyRegistered, "duplicate state callback"); Require(stateCallsA[0].calls == 0, "registering calls the state callback"); Require(sceNpRegisterNpReachabilityStateCallback(reinterpret_cast(&AbortingReachabilityCallback), nullptr) == 0, "registering the reachability callback"); sceNpRegisterGamePresenceCallback(nullptr, nullptr); sceNpRegisterGamePresenceCallback(reinterpret_cast(&AbortingGamePresenceCallback), &userdata[0]); + Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed"); Require(SignedOutOnce(stateCallsA[0], &userdata[0]), "sceNpCheckCallback must deliver SIGNED_OUT once"); Require(sceNpUnregisterNpReachabilityStateCallback() == 0, "unregistering the reachability callback"); Require(sceNpCheckCallback() == 0, "second sceNpCheckCallback failed"); Require(stateCallsA[0].calls == 1, "the second sceNpCheckCallback delivered the state again"); for (int index = 1; index < 8; ++index) { - ids[index] = sceNpRegisterStateCallbackA(callbacks[index], &userdata[index]); + ids[index] = sceNpRegisterStateCallbackA(reinterpret_cast(callbacks[index]), &userdata[index]); Require(ids[index] >= 1 && ids[index] <= 8, "state callback id outside 1..8"); for (int other = 0; other < index; ++other) { Require(ids[index] != ids[other], "two state callbacks got the same id"); } } - Require(sceNpRegisterStateCallbackA(callbacks[8], &userdata[8]) == CallbackMax, "ninth state callback"); + Require(sceNpRegisterStateCallbackA(reinterpret_cast(callbacks[8]), &userdata[8]) == CallbackMax, "ninth state callback"); Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed with eight callbacks"); for (int index = 0; index < 8; ++index) { Require(SignedOutOnce(stateCallsA[index], &userdata[index]), "each callback gets SIGNED_OUT once"); } Require(stateCallsA[8].calls == 0, "a rejected callback was called"); - Require(sceNpUnregisterStateCallbackA(0) == InvalidArgument, "callback id 0"); - Require(sceNpUnregisterStateCallbackA(9) == InvalidArgument, "callback id 9"); + Require(sceNpUnregisterStateCallbackA(0) == InvalidId, "callback id 0"); + Require(sceNpUnregisterStateCallbackA(9) == InvalidId, "callback id 9"); + Require(sceNpUnregisterStateCallbackA(-1) == InvalidId, "callback id -1"); + Require(sceNpUnregisterStateCallbackA(std::numeric_limits::min()) == InvalidId, "callback id INT_MIN"); + Require(sceNpUnregisterStateCallbackA(std::numeric_limits::max()) == InvalidId, "callback id INT_MAX"); Require(sceNpUnregisterStateCallbackA(ids[3]) == 0, "unregistering one state callback"); for (int index = 0; index < 8; ++index) { if (index == 3) continue; - Require(sceNpRegisterStateCallbackA(callbacks[index], &userdata[index]) == AlreadyRegistered, + Require(sceNpRegisterStateCallbackA(reinterpret_cast(callbacks[index]), &userdata[index]) == AlreadyRegistered, "unregistering one id removed another callback"); } - Require(sceNpRegisterStateCallbackA(callbacks[3], &userdata[3]) == ids[3], "registering again takes the freed slot"); - Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed after registering again"); + Require(sceNpRegisterStateCallbackA(reinterpret_cast(callbacks[3]), &userdata[3]) == ids[3], "registering again takes the freed slot"); + Require(sceNpCheckCallbackForLib() == 0, "sceNpCheckCallbackForLib failed after registering again"); for (int index = 0; index < 8; ++index) { Require(stateCallsA[index].calls == (index == 3 ? 2 : 1), "only the registration made again gets SIGNED_OUT"); } + Require(sceNpCheckCallbackForLib() == 0 && sceNpCheckCallback() == 0, "checking again failed"); + for (int index = 0; index < 8; ++index) { + Require(stateCallsA[index].calls == (index == 3 ? 2 : 1), "sceNpCheckCallbackForLib left the state pending"); + } for (int index = 0; index < 8; ++index) { Require(sceNpUnregisterStateCallbackA(ids[index]) == 0, "unregistering a state callback"); } Require(sceNpUnregisterStateCallbackA(ids[0]) == NotRegistered, "unregistering a state callback twice"); - const int removed = sceNpRegisterStateCallbackA(callbacks[8], &userdata[8]); + const int removed = sceNpRegisterStateCallbackA(reinterpret_cast(callbacks[8]), &userdata[8]); Require(removed >= 1 && removed <= 8, "a freed slot is reused"); Require(sceNpUnregisterStateCallbackA(removed) == 0, "unregistering before sceNpCheckCallback"); Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed with no callbacks"); Require(stateCallsA[8].calls == 0, "an unregistered callback was called"); SelfUnregister self{}; - self.id = sceNpRegisterStateCallbackA(reinterpret_cast(&SelfUnregisteringCallback), &self); + self.id = sceNpRegisterStateCallbackA(reinterpret_cast(reinterpret_cast(&SelfUnregisteringCallback)), &self); Require(self.id >= 1 && self.id <= 8, "state callback id outside 1..8"); Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed with a callback that unregisters itself"); Require(self.calls == 1 && self.result == 0, "a state callback must be able to unregister itself"); @@ -184,8 +157,8 @@ void CheckLegacyStateCallback() { auto* callback = reinterpret_cast(&LegacyStateCallback); Require(sceNpUnregisterStateCallback() == NotRegistered, "unregistering with no legacy callback"); Require(sceNpRegisterStateCallback(nullptr, &userdata) == InvalidArgument, "null legacy callback"); - Require(sceNpRegisterStateCallback(callback, &userdata) == 0, "registering the legacy callback"); - Require(sceNpRegisterStateCallback(callback, nullptr) == AlreadyRegistered, "duplicate legacy callback"); + Require(sceNpRegisterStateCallback(reinterpret_cast(callback), &userdata) == 0, "registering the legacy callback"); + Require(sceNpRegisterStateCallback(reinterpret_cast(callback), nullptr) == AlreadyRegistered, "duplicate legacy callback"); legacyStateCall.npId = &sentinel; Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed with the legacy callback"); Require(SignedOutOnce(legacyStateCall, &userdata), "the legacy callback must get SIGNED_OUT with no NpId"); @@ -221,6 +194,33 @@ void CheckRequestTimeouts() { Require(sceNpSetTimeout(asyncId, 2, 0, 0, 0, 0) == RequestNotFound, "deleted async request"); } +void CheckAsyncRequestLifecycle() { + NpCreateAsyncRequestParameter param{}; + const int unusedAsyncResult = sceNpCreateAsyncRequest(¶m); + Require(unusedAsyncResult > 0, "sceNpCreateAsyncRequest must return a positive request id"); + const int deletedId = sceNpCreateAsyncRequest(¶m); + Require(sceNpDeleteRequest(deletedId) == 0, "initial delete"); + + constexpr int resultSentinel = 0x5a5a5a5a; + const int unknownIds[] = {0, -1, 0x7fffffff, deletedId}; + for (const int id : unknownIds) { + int result = resultSentinel; + Require(sceNpPollAsync(id, &result) == RequestNotFound, "poll must reject an absent or deleted id"); + Require(result == resultSentinel, "poll must not touch the result for an absent or deleted id"); + Require(sceNpPollAsync(id, nullptr) == InvalidArgument, + "poll must reject a null result before the id"); + Require(sceNpAbortRequest(id) == RequestNotFound, "abort must reject an absent or deleted id"); + Require(sceNpDeleteRequest(id) == RequestNotFound, "delete must reject an absent or deleted id"); + } + + int result = resultSentinel; + Require(sceNpPollAsync(unusedAsyncResult, &result) == 0, "poll on a live async request"); + Require(result == SignedOut, "poll must report the signed-out result on a live async request"); + Require(sceNpAbortRequest(unusedAsyncResult) == 0, "abort on a live request"); + Require(sceNpDeleteRequest(unusedAsyncResult) == 0, "delete after abort"); + Require(sceNpDeleteRequest(unusedAsyncResult) == RequestNotFound, "second delete"); +} + void CheckOnlineIdQueries() { NpOnlineId onlineId{}; std::memcpy(onlineId.data, "player", 6); @@ -267,19 +267,119 @@ void CheckOnlineIdQueries() { Require(std::memcmp(output, untouched, sizeof(output)) == 0 && age == 0x5a && userId == 0x5a5a && accountId == 0x5a5a, "a failed query wrote its output"); try { sceNpNotifyPlusFeature(output); + Require(false, "sceNpNotifyPlusFeature must report that it is not implemented"); } catch (const std::runtime_error& error) { Require(std::string(error.what()) == "sceNpNotifyPlusFeature not implemented", "sceNpNotifyPlusFeature threw"); } } +void CheckUserIdQueries() { + constexpr int InvalidUserId = -1; + constexpr int OtherUserId = 0x10000; + constexpr int NeverCreated = 0x7fffffff; + unsigned char output[64]; + std::memset(output, 0x5a, sizeof(output)); + unsigned char untouched[64]; + std::memset(untouched, 0x5a, sizeof(untouched)); + std::int8_t age = 0x5a; + const int request = sceNpCreateRequest(); + Require(request > 0, "sceNpCreateRequest must return a positive request id"); + for (const int userId : {InitialUserId, OtherUserId}) { + Require(sceNpCheckNpAvailabilityA(request, userId) == SignedOut, "sceNpCheckNpAvailabilityA must report the user as signed out"); + Require(sceNpGetAccountLanguageA(request, userId, output) == SignedOut, "sceNpGetAccountLanguageA must report the user as signed out"); + Require(sceNpGetParentalControlInfoA(request, userId, &age, output) == SignedOut, "sceNpGetParentalControlInfoA must report the user as signed out"); + Require(sceNpGetAccountDateOfBirthA(userId, output) == SignedOut, "sceNpGetAccountDateOfBirthA must report the user as signed out"); + } + Require(sceNpCheckNpAvailabilityA(request, InvalidUserId) == InvalidArgument, "sceNpCheckNpAvailabilityA must reject the invalid user id"); + Require(sceNpCheckNpAvailabilityA(0, InitialUserId) == InvalidArgument, "sceNpCheckNpAvailabilityA must reject request id 0"); + Require(sceNpCheckNpAvailabilityA(NeverCreated, InitialUserId) == RequestNotFound, "sceNpCheckNpAvailabilityA must reject a request that was never created"); + Require(sceNpCheckNpAvailabilityA(NeverCreated, InvalidUserId) == InvalidArgument, "sceNpCheckNpAvailabilityA must check the user before the request"); + Require(sceNpGetAccountLanguageA(request, InitialUserId, nullptr) == InvalidArgument, "sceNpGetAccountLanguageA must reject a null output"); + Require(sceNpGetAccountLanguageA(request, InvalidUserId, output) == InvalidArgument, "sceNpGetAccountLanguageA must reject the invalid user id"); + Require(sceNpGetAccountLanguageA(0, InitialUserId, output) == InvalidArgument, "sceNpGetAccountLanguageA must reject request id 0"); + Require(sceNpGetAccountLanguageA(NeverCreated, InitialUserId, output) == RequestNotFound, "sceNpGetAccountLanguageA must reject a request that was never created"); + Require(sceNpGetAccountLanguageA(NeverCreated, InitialUserId, nullptr) == InvalidArgument, "sceNpGetAccountLanguageA must check the output before the request"); + Require(sceNpGetParentalControlInfoA(request, InitialUserId, nullptr, output) == InvalidArgument, "sceNpGetParentalControlInfoA must reject a null age"); + Require(sceNpGetParentalControlInfoA(request, InitialUserId, &age, nullptr) == InvalidArgument, "sceNpGetParentalControlInfoA must reject a null info"); + Require(sceNpGetParentalControlInfoA(request, InvalidUserId, &age, output) == InvalidArgument, "sceNpGetParentalControlInfoA must reject the invalid user id"); + Require(sceNpGetParentalControlInfoA(0, InitialUserId, &age, output) == InvalidArgument, "sceNpGetParentalControlInfoA must reject request id 0"); + Require(sceNpGetParentalControlInfoA(NeverCreated, InitialUserId, &age, output) == RequestNotFound, "sceNpGetParentalControlInfoA must reject a request that was never created"); + Require(sceNpGetParentalControlInfoA(NeverCreated, InvalidUserId, &age, output) == InvalidArgument, "sceNpGetParentalControlInfoA must check the user before the request"); + Require(sceNpGetAccountDateOfBirthA(InitialUserId, nullptr) == InvalidArgument, "sceNpGetAccountDateOfBirthA must reject a null output"); + Require(sceNpGetAccountDateOfBirthA(InvalidUserId, output) == InvalidArgument, "sceNpGetAccountDateOfBirthA must reject the invalid user id"); + Require(sceNpDeleteRequest(request) == 0, "sceNpDeleteRequest failed"); + Require(sceNpCheckNpAvailabilityA(request, InitialUserId) == RequestNotFound, "sceNpCheckNpAvailabilityA must reject a deleted request"); + Require(sceNpGetAccountLanguageA(request, InitialUserId, output) == RequestNotFound, "sceNpGetAccountLanguageA must reject a deleted request"); + Require(sceNpGetParentalControlInfoA(request, InitialUserId, &age, output) == RequestNotFound, "sceNpGetParentalControlInfoA must reject a deleted request"); + Require(std::memcmp(output, untouched, sizeof(output)) == 0 && age == 0x5a, "a signed-out account query wrote its output"); + constexpr std::uint32_t Sentinel = 0x5a5a5a5au; + constexpr std::uint32_t Offline = 0; + NpOnlineId onlineId{}; + std::memcpy(onlineId.data, "player", 6); + for (const int userId : {InitialUserId, OtherUserId}) { + std::uint32_t status = Sentinel; + Require(sceNpGetGamePresenceStatusA(userId, &status) == 0 && status == Offline, "sceNpGetGamePresenceStatusA must report the user as offline"); + } + std::uint32_t status = Sentinel; + Require(sceNpGetGamePresenceStatusA(InvalidUserId, &status) == InvalidArgument, "sceNpGetGamePresenceStatusA must reject the invalid user id"); + Require(sceNpGetGamePresenceStatusA(InitialUserId, nullptr) == InvalidArgument, "sceNpGetGamePresenceStatusA must reject a null status"); + Require(sceNpGetGamePresenceStatus(nullptr, &status) == InvalidArgument, "sceNpGetGamePresenceStatus must reject a null online id"); + Require(sceNpGetGamePresenceStatus(&onlineId, nullptr) == InvalidArgument, "sceNpGetGamePresenceStatus must reject a null status"); + Require(status == Sentinel, "a rejected presence query wrote its status"); + Require(sceNpGetGamePresenceStatus(&onlineId, &status) == 0 && status == Offline, "sceNpGetGamePresenceStatus must report the online id as offline"); +} + +} + +struct ContentRestrictionFields { + std::uint64_t size; + std::int8_t defaultAgeRestriction; + std::uint8_t padding[3]; + std::int32_t ageRestrictionCount; + const void* ageRestrictions; +}; + +int SetContentRestriction(const ContentRestrictionFields& fields) { + return sceNpSetContentRestriction(reinterpret_cast(&fields)); +} + +void CheckPlusAndContentRestriction() { + bool isMember = true; + Require(sceNpIsPlusMember(0x10000, &isMember) == 0 && !isMember, "sceNpIsPlusMember must report no Plus membership"); + Require(sceNpIsPlusMember(0x10000, nullptr) == InvalidArgument, "sceNpIsPlusMember must reject a null output"); + + const std::uint8_t ages[8]{}; + ContentRestrictionFields fields{sizeof(ContentRestrictionFields), 12, {}, 2, ages}; + Require(SetContentRestriction(fields) == 0, "sceNpSetContentRestriction must accept a valid restriction"); + fields.ageRestrictionCount = 0; + fields.ageRestrictions = nullptr; + Require(SetContentRestriction(fields) == 0, "sceNpSetContentRestriction must accept no age restrictions"); + Require(sceNpSetContentRestriction(nullptr) == InvalidArgument, "sceNpSetContentRestriction must reject null"); + auto bad = fields; + bad.size = sizeof(ContentRestrictionFields) + 8; + Require(SetContentRestriction(bad) == InvalidSize, "sceNpSetContentRestriction must check the size"); + bad = fields; + bad.defaultAgeRestriction = -1; + Require(SetContentRestriction(bad) == InvalidArgument, "sceNpSetContentRestriction must reject a negative default age"); + bad = fields; + bad.ageRestrictionCount = 0x101; + bad.ageRestrictions = ages; + Require(SetContentRestriction(bad) == InvalidArgument, "sceNpSetContentRestriction must reject more than 256 restrictions"); + bad.ageRestrictionCount = 0x100; + Require(SetContentRestriction(bad) == 0, "sceNpSetContentRestriction must accept 256 restrictions"); + bad.ageRestrictions = nullptr; + Require(SetContentRestriction(bad) == InvalidArgument, "sceNpSetContentRestriction must reject restrictions without an array"); } int main() { CheckOnlineIdQueries(); + CheckUserIdQueries(); CheckStateCallbacksA(); CheckLegacyStateCallback(); CheckRequestTimeouts(); + CheckAsyncRequestLifecycle(); + CheckPlusAndContentRestriction(); NpId npId{}; std::memset(&npId, 0x5a, sizeof(npId)); NpId untouched{}; @@ -306,9 +406,10 @@ int main() { Require(sceNpGetUserIdByAccountId(0x1234, nullptr) == InvalidArgument, "sceNpGetUserIdByAccountId must reject a null output"); Require(sceNpGetUserIdByAccountId(0x1234, &userId) == SignedOut, "sceNpGetUserIdByAccountId must report NP as signed out"); Require(userId == 0x5a5a, "sceNpGetUserIdByAccountId must leave the user id untouched"); - unsigned char buffer[0x11]{}; + NpOnlineId buffer{}; try { - sceNpUnknown_8weiQyIdI3A(0x10000, buffer); + sceNpUnknown_8weiQyIdI3A(0x10000, &buffer); + Require(false, "sceNpUnknown_8weiQyIdI3A must report that it is not implemented"); } catch (const std::runtime_error& error) { Require(std::string(error.what()) == "sceNpUnknown_8weiQyIdI3A not implemented", @@ -316,6 +417,7 @@ int main() { } try { sceNpUnknown_WxLcN76q19o(); + Require(false, "sceNpUnknown_WxLcN76q19o must report that it is not implemented"); } catch (const std::runtime_error& error) { Require(std::string(error.what()) == "sceNpUnknown_WxLcN76q19o not implemented", diff --git a/core/libs/tests/GuestNpMatching2Offline.cpp b/core/libs/tests/GuestNpMatching2Offline.cpp new file mode 100644 index 0000000000..796c217bdf --- /dev/null +++ b/core/libs/tests/GuestNpMatching2Offline.cpp @@ -0,0 +1,92 @@ +#include "prx/libSceNpMatching2/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include + + + + + +namespace { + +constexpr int ResolverTimedOut = static_cast(0x804101E2u); +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); + +int failures = 0; + +void Require(bool value, const char* name, const char* what) { + if (value) return; + std::fprintf(stderr, "%s: %s\n", name, what); + ++failures; +} + +} + +int main() { + NpMatching2InitializeParameter init{}; + NpMatching2CreateContextParameter create{}; + create.size = 0x28; + unsigned char requestOpt[24] = {}; + std::uint16_t context = 77; + + Require(sceNpMatching2CreateContext(nullptr, nullptr) == NotInitialized, "sceNpMatching2CreateContext", "a call before initialization did not return NOT_INITIALIZED first"); + 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(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"); + + init.size = 0x30; + Require(sceNpMatching2Initialize(&init) == 0, "sceNpMatching2Initialize", "initialization failed"); + + Require(sceNpMatching2CreateContext(nullptr, &context) == InvalidArgument, "sceNpMatching2CreateContext", "a null parameter was taken"); + create.size = 0x30; + Require(sceNpMatching2CreateContext(&create, &context) == InvalidArgument, "sceNpMatching2CreateContext", "parameter size 0x30 was taken"); + create.size = 0x28; + Require(sceNpMatching2CreateContext(&create, nullptr) == InvalidArgument, "sceNpMatching2CreateContext", "a null output was taken"); + Require(context == 77, "sceNpMatching2CreateContext", "a refused call wrote the context id"); + Require(sceNpMatching2CreateContext(&create, &context) == 0 && context == 1, "sceNpMatching2CreateContext", "the first context is not id 1"); + Require(sceNpMatching2DestroyContext(1) == 0, "sceNpMatching2DestroyContext", "destroying context 1 failed"); + std::uint16_t created = 0; + for (std::uint16_t expected = 2; expected <= 10; ++expected) { + Require(sceNpMatching2CreateContext(&create, &created) == 0 && created == expected, "sceNpMatching2CreateContext", "a context id was reused before the ids up to 10 were handed out"); + } + Require(sceNpMatching2CreateContext(&create, &created) == 0 && created == 1, "sceNpMatching2CreateContext", "after id 10 the allocation did not wrap to the free id 1"); + created = 77; + Require(sceNpMatching2CreateContext(&create, &created) == ContextMax && created == 77, "sceNpMatching2CreateContext", "an eleventh context did not return CONTEXT_MAX"); + Require(sceNpMatching2DestroyContext(11) == InvalidContextId, "sceNpMatching2DestroyContext", "an unknown context was destroyed"); + + Require(sceNpMatching2ContextStart(1, 0) == ResolverTimedOut, "sceNpMatching2ContextStart", "starting a context did not return the offline resolver timeout"); + Require(sceNpMatching2ContextStart(99, 0) == ResolverTimedOut, "sceNpMatching2ContextStart", "the offline answer did not come before the context check"); + 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(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"); + Require(sceNpMatching2RegisterRoomMessageCallback(99, nullptr, nullptr) == InvalidContextId, "sceNpMatching2RegisterRoomMessageCallback", "an unknown context was taken"); + Require(sceNpMatching2RegisterRoomMessageCallback(1, nullptr, nullptr) == 0, "sceNpMatching2RegisterRoomMessageCallback", "registering failed"); + Require(sceNpMatching2RegisterSignalingCallback(99, nullptr, nullptr) == InvalidContextId, "sceNpMatching2RegisterSignalingCallback", "an unknown context was taken"); + Require(sceNpMatching2RegisterSignalingCallback(1, nullptr, nullptr) == 0, "sceNpMatching2RegisterSignalingCallback", "registering failed"); + Require(sceNpMatching2SetDefaultRequestOptParam(99, nullptr) == InvalidArgument, "sceNpMatching2SetDefaultRequestOptParam", "a null option was not refused before the context"); + Require(sceNpMatching2SetDefaultRequestOptParam(99, requestOpt) == InvalidContextId, "sceNpMatching2SetDefaultRequestOptParam", "an unknown context was taken"); + Require(sceNpMatching2SetDefaultRequestOptParam(1, requestOpt) == 0, "sceNpMatching2SetDefaultRequestOptParam", "setting the default option failed"); + + Require(sceNpMatching2Terminate() == 0, "sceNpMatching2Terminate", "termination failed"); + Require(sceNpMatching2DestroyContext(1) == NotInitialized, "sceNpMatching2DestroyContext", "a context was destroyed after termination"); + 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"); + + if (failures != 0) return 1; + std::puts("NpMatching2 offline tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestNpPartner.cpp b/core/libs/tests/GuestNpPartner.cpp index 700570470a..e57e29c5c5 100644 --- a/core/libs/tests/GuestNpPartner.cpp +++ b/core/libs/tests/GuestNpPartner.cpp @@ -1,13 +1,8 @@ +#include "prx/libSceNpPartner001/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include -extern "C" { -int APS5_VABI sceNpEAAccessInitialize(void); -int APS5_VABI sceNpEAAccessTerminate(void); -int APS5_VABI sceNpHasEAAccessSubscription(int, bool*); -} - namespace { void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestNpParty.cpp b/core/libs/tests/GuestNpParty.cpp index d47398d76a..2ee253c2b5 100644 --- a/core/libs/tests/GuestNpParty.cpp +++ b/core/libs/tests/GuestNpParty.cpp @@ -1,26 +1,81 @@ +#include "prx/libSceNpParty/libSceNpParty.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" int APS5_VABI sceNpPartyGetState(std::uint16_t* state); +namespace { -int main() { +constexpr int kAlreadyInitialized = static_cast(0x80552502u); +constexpr int kNotInitialized = static_cast(0x80552503u); +constexpr int kInvalidArgument = static_cast(0x80552504u); +constexpr int kNotInParty = static_cast(0x80552506u); + +bool check(bool value, const char* message) { + if (!value) std::fprintf(stderr, "%s\n", message); + return value; +} + +bool stateTests() { std::uint16_t states[3]; std::memset(states, 0xa5, sizeof(states)); - if (sceNpPartyGetState(nullptr) != static_cast(0x80552504u)) { - std::fprintf(stderr, "a null state was not refused with INVALID_ARGUMENT\n"); - return 1; - } - if (sceNpPartyGetState(&states[1]) != 0 || states[1] != 2) { - std::fprintf(stderr, "the state is not NOT_IN_PARTY\n"); - return 1; - } - if (states[0] != 0xa5a5 || states[2] != 0xa5a5) { - std::fprintf(stderr, "sceNpPartyGetState wrote past its 16-bit state\n"); - return 1; - } - std::puts("NpParty state tests passed"); + return check(sceNpPartyGetState(nullptr) == kInvalidArgument, "a null state was not refused with INVALID_ARGUMENT") + && check(sceNpPartyGetState(&states[1]) == 0 && states[1] == 2, "the state is not NOT_IN_PARTY") + && check(states[0] == 0xa5a5 && states[2] == 0xa5a5, "sceNpPartyGetState wrote past its 16-bit state") + && check(sceNpPartyGetStateAsUserA(1, nullptr) == kInvalidArgument, "a null user state was not refused with INVALID_ARGUMENT") + && check(sceNpPartyGetStateAsUserA(1, &states[0]) == 0 && states[0] == 2, "the user state is not NOT_IN_PARTY") + && check(states[2] == 0xa5a5, "sceNpPartyGetStateAsUserA wrote past its 16-bit state"); +} + +bool lifecycleTests() { + std::uint64_t param[4] = {}; + std::uint64_t handlers[8] = {}; + return check(sceNpPartyTerminate() == kNotInitialized, "terminate before initialize did not fail") + && check(sceNpPartyCheckCallback() == kNotInitialized, "check callback before initialize did not fail") + && check(sceNpPartyRegisterHandlerA(handlers, nullptr) == kNotInitialized, "register handler before initialize did not fail") + && check(sceNpPartyRegisterHandler(handlers, nullptr) == kNotInitialized, "the plain register handler before initialize did not fail") + && check(sceNpPartyInitialize(nullptr) == kInvalidArgument, "a null initialize param was not refused") + && check(sceNpPartyInitialize(param) == 0, "initialize failed") + && check(sceNpPartyInitialize(param) == kAlreadyInitialized, "a second initialize did not fail") + && check(sceNpPartyRegisterHandlerA(nullptr, nullptr) == kInvalidArgument, "null handlers were not refused") + && check(sceNpPartyRegisterHandlerA(handlers, param) == 0, "register handler failed") + && check(sceNpPartyRegisterHandler(nullptr, nullptr) == kInvalidArgument, "null plain handlers were not refused") + && check(sceNpPartyRegisterHandler(handlers, param) == 0, "the plain register handler failed") + && check(sceNpPartyCheckCallback() == 0, "check callback failed") + && check(sceNpPartyTerminate() == 0, "terminate failed") + && check(sceNpPartyTerminate() == kNotInitialized, "a second terminate did not fail") + && check(sceNpPartyInitialize(param) == 0, "initialize after terminate failed") + && check(sceNpPartyTerminate() == 0, "terminate after reinitialize failed"); +} + +bool notInPartyTests() { + std::uint8_t out[64]; + std::memset(out, 0xa5, sizeof(out)); + bool ok = check(sceNpPartyGetId(nullptr) == kInvalidArgument, "a null party id was not refused") + && check(sceNpPartyGetId(out) == kNotInParty, "get id did not report NOT_IN_PARTY") + && check(sceNpPartyGetMembersA(nullptr) == kInvalidArgument, "a null member list was not refused") + && check(sceNpPartyGetMembersA(out) == kNotInParty, "get members did not report NOT_IN_PARTY") + && check(sceNpPartyGetMemberInfoA(1, nullptr) == kInvalidArgument, "a null member info was not refused") + && check(sceNpPartyGetMemberInfoA(1, out) == kNotInParty, "get member info did not report NOT_IN_PARTY") + && check(sceNpPartyGetMembers(nullptr) == kInvalidArgument, "a null plain member list was not refused") + && check(sceNpPartyGetMembers(out) == kNotInParty, "the plain get members did not report NOT_IN_PARTY") + && check(sceNpPartyGetMemberInfo(1, nullptr) == kInvalidArgument, "a null plain member info was not refused") + && check(sceNpPartyGetMemberInfo(1, out) == kNotInParty, "the plain get member info did not report NOT_IN_PARTY") + && check(sceNpPartyGetMemberSessionInfo(1, nullptr) == kInvalidArgument, "a null session info was not refused") + && check(sceNpPartyGetMemberSessionInfo(1, out) == kNotInParty, "get session info did not report NOT_IN_PARTY") + && check(sceNpPartyGetMemberVoiceInfo(1, 2, nullptr) == kInvalidArgument, "a null voice state was not refused") + && check(sceNpPartyGetMemberVoiceInfo(1, 2, out) == kNotInParty, "get voice info did not report NOT_IN_PARTY") + && check(sceNpPartySendBinaryMessage(nullptr) == kInvalidArgument, "null send params were not refused") + && check(sceNpPartySendBinaryMessage(out) == kNotInParty, "send did not report NOT_IN_PARTY"); + for (std::uint8_t byte : out) ok = ok && check(byte == 0xa5, "a NOT_IN_PARTY call wrote its output"); + return ok; +} + +} + +int main() { + if (!stateTests() || !lifecycleTests() || !notInPartyTests()) return 1; + std::puts("NpParty tests passed"); return 0; } diff --git a/core/libs/tests/GuestNpProfileDialog.cpp b/core/libs/tests/GuestNpProfileDialog.cpp new file mode 100644 index 0000000000..cb7fa68f8d --- /dev/null +++ b/core/libs/tests/GuestNpProfileDialog.cpp @@ -0,0 +1,159 @@ +#include "prx/libSceNpProfileDialog/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80B80003u); +constexpr int kErrAlreadyInitialized = static_cast(0x80B80004u); +constexpr int kErrNotFinished = static_cast(0x80B80005u); +constexpr int kErrArgNull = static_cast(0x80B8000Du); +constexpr std::int32_t kResultUserCanceled = 1; + +constexpr std::size_t kParamSize = 128; +constexpr std::size_t kParamASize = 120; +constexpr std::size_t kParamUserData = 88; +constexpr std::size_t kParamAUserData = 80; +constexpr std::size_t kResultSize = 48; +constexpr std::uint8_t kSentinel = 0x5a; + +template +void Store(std::uint8_t* bytes, std::size_t offset, TValue value) { + std::memcpy(bytes + offset, &value, sizeof(value)); +} + +template +TValue Load(const std::uint8_t* bytes, std::size_t offset) { + TValue value; + std::memcpy(&value, bytes + offset, sizeof(value)); + return value; +} + +void MakeParam(std::uint8_t* bytes, std::size_t capacity, std::uint64_t size, std::uint32_t mode, std::size_t userDataOffset, void* userData) { + std::memset(bytes, 0, capacity); + Store(bytes, 0, 48); + Store(bytes, 48, size); + Store(bytes, 56, mode); + Store(bytes, 60, 1); + Store(bytes, userDataOffset, userData); +} + +template +void RequireThrows(TCall call) { + bool threw = false; + try { + call(); + } catch (const std::runtime_error& error) { + threw = std::string(error.what()).find("not implemented") == std::string::npos; + } + Require(threw); +} + +void RequireCanceled(const std::uint8_t* result, void* userData) { + Require(Load(result, 0) == 0); + Require(Load(result, 4) == kResultUserCanceled); + Require(Load(result, 8) == userData); + for (std::size_t i = 16; i < kResultSize; ++i) Require(result[i] == 0); + for (std::size_t i = kResultSize; i < kResultSize + 16; ++i) Require(result[i] == kSentinel); +} + +} + +int main() { + alignas(8) std::uint8_t param[kParamSize + 16]; + alignas(8) std::uint8_t paramA[kParamASize + 16]; + alignas(8) std::uint8_t result[kResultSize + 16]; + int first = 0; + int second = 0; + std::memset(result, kSentinel, sizeof(result)); + MakeParam(param, sizeof(param), kParamSize, 1, kParamUserData, &first); + MakeParam(paramA, sizeof(paramA), kParamASize, 1, kParamAUserData, &second); + + Require(sceNpProfileDialogTerminate() == kErrNotInitialized); + Require(sceNpProfileDialogClose() == kErrNotInitialized); + Require(sceNpProfileDialogGetResult(reinterpret_cast(result)) == kErrNotInitialized); + Require(sceNpProfileDialogGetResult(nullptr) == kErrNotInitialized); + Require(sceNpProfileDialogGetStatus() == kStatusNone); + Require(sceNpProfileDialogUpdateStatus() == kStatusNone); + RequireThrows([&] { sceNpProfileDialogOpen(reinterpret_cast(param)); }); + RequireThrows([&] { sceNpProfileDialogOpenA(reinterpret_cast(paramA)); }); + Require(sceNpProfileDialogGetStatus() == kStatusNone); + Require(result[0] == kSentinel); + + Require(sceNpProfileDialogInitialize() == 0); + Require(sceNpProfileDialogInitialize() == kErrAlreadyInitialized); + Require(sceNpProfileDialogGetStatus() == kStatusInitialized); + Require(sceNpProfileDialogUpdateStatus() == kStatusInitialized); + + Require(sceNpProfileDialogGetResult(reinterpret_cast(result)) == kErrNotFinished); + Require(result[0] == kSentinel); + RequireThrows([] { sceNpProfileDialogGetResult(nullptr); }); + RequireThrows([] { sceNpProfileDialogClose(); }); + Require(sceNpProfileDialogGetStatus() == kStatusInitialized); + + Require(sceNpProfileDialogOpen(nullptr) == kErrArgNull); + Require(sceNpProfileDialogOpenA(nullptr) == kErrArgNull); + Require(sceNpProfileDialogGetStatus() == kStatusInitialized); + + alignas(8) std::uint8_t wrong[kParamSize + 16]; + MakeParam(wrong, sizeof(wrong), kParamASize, 1, kParamUserData, &first); + RequireThrows([&] { sceNpProfileDialogOpen(reinterpret_cast(wrong)); }); + MakeParam(wrong, sizeof(wrong), kParamSize, 1, kParamAUserData, &second); + RequireThrows([&] { sceNpProfileDialogOpenA(reinterpret_cast(wrong)); }); + MakeParam(wrong, sizeof(wrong), kParamSize, 0, kParamUserData, &first); + RequireThrows([&] { sceNpProfileDialogOpen(reinterpret_cast(wrong)); }); + MakeParam(wrong, sizeof(wrong), kParamASize, 5, kParamAUserData, &second); + RequireThrows([&] { sceNpProfileDialogOpenA(reinterpret_cast(wrong)); }); + Require(sceNpProfileDialogGetStatus() == kStatusInitialized); + Require(sceNpProfileDialogGetResult(reinterpret_cast(result)) == kErrNotFinished); + + std::uint8_t paramCopy[sizeof(param)]; + std::memcpy(paramCopy, param, sizeof(param)); + Require(sceNpProfileDialogOpen(reinterpret_cast(param)) == 0); + Require(std::memcmp(param, paramCopy, sizeof(param)) == 0); + Require(sceNpProfileDialogGetStatus() == kStatusFinished); + Require(sceNpProfileDialogUpdateStatus() == kStatusFinished); + Require(sceNpProfileDialogGetResult(nullptr) == kErrArgNull); + Require(sceNpProfileDialogGetResult(reinterpret_cast(result)) == 0); + RequireCanceled(result, &first); + RequireThrows([] { sceNpProfileDialogClose(); }); + Require(sceNpProfileDialogGetStatus() == kStatusFinished); + + for (std::uint32_t mode = 1; mode <= 4; ++mode) { + Store(paramA, 56, mode); + std::uint8_t paramACopy[sizeof(paramA)]; + std::memcpy(paramACopy, paramA, sizeof(paramA)); + std::memset(result, kSentinel, sizeof(result)); + Require(sceNpProfileDialogOpenA(reinterpret_cast(paramA)) == 0); + Require(std::memcmp(paramA, paramACopy, sizeof(paramA)) == 0); + Require(sceNpProfileDialogUpdateStatus() == kStatusFinished); + Require(sceNpProfileDialogGetResult(reinterpret_cast(result)) == 0); + RequireCanceled(result, &second); + } + + Require(sceNpProfileDialogTerminate() == 0); + Require(sceNpProfileDialogTerminate() == kErrNotInitialized); + Require(sceNpProfileDialogGetStatus() == kStatusNone); + Require(sceNpProfileDialogGetResult(reinterpret_cast(result)) == kErrNotInitialized); + + Require(sceNpProfileDialogInitialize() == 0); + std::memset(result, kSentinel, sizeof(result)); + Require(sceNpProfileDialogGetResult(reinterpret_cast(result)) == kErrNotFinished); + Require(result[0] == kSentinel); + Store(param, 56, 4); + Require(sceNpProfileDialogOpen(reinterpret_cast(param)) == 0); + Require(sceNpProfileDialogGetResult(reinterpret_cast(result)) == 0); + RequireCanceled(result, &first); + Require(sceNpProfileDialogTerminate() == 0); + return 0; +} diff --git a/core/libs/tests/GuestNpPs4Imports.cpp b/core/libs/tests/GuestNpPs4Imports.cpp new file mode 100644 index 0000000000..4ffc79c2fe --- /dev/null +++ b/core/libs/tests/GuestNpPs4Imports.cpp @@ -0,0 +1,127 @@ +#include "prx/libSceNpTrophy/include/Api.hpp" +#include "prx/libSceNpSignaling/include/Api.hpp" +#include "prx/libSceNpMatching2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int Matching2AlreadyInitialized = static_cast(0x80550C02u); +constexpr int Matching2NotInitialized = static_cast(0x80550C03u); +constexpr int Matching2InvalidArgument = static_cast(0x80550C0Au); +constexpr int TrophyInvalidArgument = static_cast(0x80551604u); +constexpr int TrophyInvalidTrophyId = static_cast(0x8055160au); +constexpr int TrophyInvalidGroupId = static_cast(0x8055160bu); +constexpr int TrophyNotRegistered = static_cast(0x8055160fu); + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "NpPs4Imports: %s\n", message); + std::abort(); + } +} + +void WriteBigEndian(std::vector& bytes, std::size_t at, std::uint64_t value, std::size_t width) { + for (std::size_t index = 0; index < width; ++index) bytes[at + index] = static_cast(value >> (8u * (width - 1u - index))); +} + +void WriteTrophyPackage(const std::filesystem::path& path) { + const char* const names[] = {"TROPCONF.ESFM", "TROP000.PNG", "TROP001.PNG", "TROP002.PNG", "ICON0.PNG"}; + constexpr std::size_t count = sizeof(names) / sizeof(names[0]); + std::vector bytes(0x60 + count * 0x40 + count * 16, 0u); + WriteBigEndian(bytes, 0x00, 0xdca24d00u, 4); + WriteBigEndian(bytes, 0x08, bytes.size(), 8); + WriteBigEndian(bytes, 0x10, count, 4); + WriteBigEndian(bytes, 0x14, 0x40u, 4); + for (std::size_t index = 0; index < count; ++index) { + const auto entry = 0x60 + index * 0x40; + std::memcpy(bytes.data() + entry, names[index], std::strlen(names[index])); + WriteBigEndian(bytes, entry + 32, 0x60 + count * 0x40 + index * 16, 8); + WriteBigEndian(bytes, entry + 40, 16u, 8); + } + std::filesystem::create_directories(path.parent_path()); + std::ofstream(path, std::ios::binary).write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); +} + +void CheckTrophyInfo() { + constexpr int user = 0x10000000; + constexpr std::uint32_t label = 7u; + const std::filesystem::path package = "app0/sce_sys/trophy/trophy07.trp"; + const std::filesystem::path state = "_trophy/trophy07.268435456"; + std::filesystem::remove(state); + WriteTrophyPackage(package); + int context = -1; + int handle = -1; + Require(sceNpTrophyCreateContext(&context, user, label, 0u) == 0 && sceNpTrophyCreateHandle(&handle) == 0, "trophy context or handle not created"); + GameDetails game{}; + GameData gameData{}; + game.size = sizeof(game); + gameData.size = sizeof(gameData); + Require(sceNpTrophyGetGameInfo(context, handle, &game, &gameData) == TrophyNotRegistered, "game info answered before registration"); + Require(sceNpTrophyRegisterContext(context, handle, 0u) == 0, "trophy context not registered"); + Require(sceNpTrophyGetGameInfo(context, handle, nullptr, nullptr) == TrophyInvalidArgument, "game info without outputs accepted"); + game.size = sizeof(game) - 8u; + Require(sceNpTrophyGetGameInfo(context, handle, &game, nullptr) == TrophyInvalidArgument, "game details of a wrong size accepted"); + game.size = sizeof(game); + std::memset(game.title, 'x', sizeof(game.title)); + Require(sceNpTrophyGetGameInfo(context, handle, &game, &gameData) == 0, "game info refused"); + Require(game.numTrophies == 3u && game.numGroups == 0u && game.numPlatinum + game.numGold + game.numSilver + game.numBronze == 0u && game.title[0] == '\0', "game details are not the package's three trophies with unknown grades"); + Require(gameData.unlockedTrophies == 0u && gameData.progressPercentage == 0u, "game data reports progress before an unlock"); + int platinum = 0; + Require(sceNpTrophyUnlockTrophy(context, handle, 1, &platinum) == 0, "trophy 1 not unlocked"); + Require(sceNpTrophyGetGameInfo(context, handle, nullptr, &gameData) == 0 && gameData.unlockedTrophies == 1u && gameData.progressPercentage == 33u, "game data does not count the unlock"); + TrophyDetails trophy{}; + TrophyData trophyData{}; + trophy.size = sizeof(trophy); + trophyData.size = sizeof(trophyData); + Require(sceNpTrophyGetTrophyInfo(context, handle, 1, &trophy, &trophyData) == 0, "trophy info refused"); + Require(trophy.trophyId == 1 && trophy.trophyGrade == 0 && trophy.groupId == -1 && !trophy.hidden && trophy.name[0] == '\0' && trophyData.trophyId == 1 && trophyData.unlocked, "trophy 1 info is wrong"); + Require(sceNpTrophyGetTrophyInfo(context, handle, 0, nullptr, &trophyData) == 0 && trophyData.trophyId == 0 && !trophyData.unlocked, "trophy 0 reported unlocked"); + Require(sceNpTrophyGetTrophyInfo(context, handle, 3, &trophy, &trophyData) == TrophyInvalidTrophyId, "a trophy past the package accepted"); + GroupDetails group{}; + GroupData groupData{}; + group.size = sizeof(group); + groupData.size = sizeof(groupData); + Require(sceNpTrophyGetGroupInfo(context, handle, -1, &group, &groupData) == 0, "base game group refused"); + Require(group.groupId == -1 && group.numTrophies == 3u && groupData.groupId == -1 && groupData.unlockedTrophies == 1u && groupData.progressPercentage == 33u, "base game group info is wrong"); + Require(sceNpTrophyGetGroupInfo(context, handle, 0, &group, &groupData) == TrophyInvalidGroupId, "a group the package does not have accepted"); + std::filesystem::remove(state); + std::filesystem::remove(package); + std::error_code ignored; + for (auto directory = package.parent_path(); !directory.empty(); directory = directory.parent_path()) std::filesystem::remove(directory, ignored); + std::filesystem::remove(state.parent_path(), ignored); +} + +void CheckMatching2() { + unsigned char param[0x30]{}; + const auto setSize = [&](std::uint64_t size) { std::memcpy(param + 0x20, &size, sizeof(size)); }; + Require(sceNpMatching2Terminate() == Matching2NotInitialized, "NpMatching2 terminated before initialization"); + Require(sceNpMatching2Initialize(nullptr) == Matching2InvalidArgument, "a null NpMatching2 parameter was accepted"); + setSize(0x20u); + Require(sceNpMatching2Initialize(reinterpret_cast(param)) == Matching2InvalidArgument, "an NpMatching2 parameter of size 0x20 was accepted"); + setSize(0x28u); + Require(sceNpMatching2Initialize(reinterpret_cast(param)) == 0, "an NpMatching2 parameter of size 0x28 was refused"); + Require(sceNpMatching2Initialize(reinterpret_cast(param)) == Matching2AlreadyInitialized, "NpMatching2 initialized twice"); + Require(sceNpMatching2Terminate() == 0, "NpMatching2 did not terminate"); + setSize(0x30u); + Require(sceNpMatching2Initialize(reinterpret_cast(param)) == 0, "an NpMatching2 parameter with an SSL pool was refused"); + Require(sceNpMatching2Terminate() == 0, "NpMatching2 did not terminate after the SSL form"); +} + +} + +int main() { + CheckTrophyInfo(); + CheckMatching2(); + return 0; +} diff --git a/core/libs/tests/GuestNpReachability.cpp b/core/libs/tests/GuestNpReachability.cpp index 4e0c1f2262..1c16e32fe7 100644 --- a/core/libs/tests/GuestNpReachability.cpp +++ b/core/libs/tests/GuestNpReachability.cpp @@ -1,14 +1,7 @@ +#include "prx/libSceNpManager/include/Api.hpp" #include "SceTypes.hpp" #include -extern "C" { -int APS5_VABI sceNpRegisterNpReachabilityStateCallback(void*, void*); -int APS5_VABI sceNpUnregisterNpReachabilityStateCallback(); -int APS5_VABI sceNpGetNpReachabilityState(int, uint32_t*); -int APS5_VABI sceNpCheckCallback(); -int APS5_VABI sceNpCheckNpAvailability(int, const NpOnlineId*); -} - static constexpr int ErrorArgument = static_cast(0x80550003); static constexpr int ErrorAlreadyRegistered = static_cast(0x80550008); static constexpr int ErrorNotRegistered = static_cast(0x80550009); @@ -33,6 +26,14 @@ int main() { Require(sceNpUnregisterNpReachabilityStateCallback() == 0); NpOnlineId onlineId{}; onlineId.data[0] = 'p'; - Require(sceNpCheckNpAvailability(1, &onlineId) == ErrorUserNotFound); - Require(sceNpCheckNpAvailability(1, nullptr) == ErrorArgument); + Require(sceNpCheckNpAvailability(1, &onlineId, nullptr) == ErrorUserNotFound); + Require(sceNpCheckNpAvailability(1, nullptr, nullptr) == ErrorArgument); + std::uint8_t available[3]{0x55, 0x55, 0x55}; + Require(sceNpCheckNpAvailability(0, &onlineId, &available[1]) == ErrorArgument); + Require(available[1] == 0x55); + Require(sceNpCheckNpAvailability(1, nullptr, &available[1]) == ErrorArgument); + Require(available[1] == 0x55); + Require(sceNpCheckNpAvailability(1, &onlineId, &available[1]) == ErrorUserNotFound); + Require(available[0] == 0x55 && available[1] == 0 && available[2] == 0x55); + } diff --git a/core/libs/tests/GuestNpScoreOffline.cpp b/core/libs/tests/GuestNpScoreOffline.cpp new file mode 100644 index 0000000000..15cc132c95 --- /dev/null +++ b/core/libs/tests/GuestNpScoreOffline.cpp @@ -0,0 +1,200 @@ +#include "prx/libSceNpScoreRanking/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include + +namespace { + +constexpr int SignedOut = static_cast(0x80550006u); +constexpr int InvalidArgument = static_cast(0x80550704u); +constexpr int TooManyObjects = static_cast(0x80550706u); +constexpr int Aborted = static_cast(0x80550707u); +constexpr int InsufficientArgument = static_cast(0x8055070Cu); +constexpr int InvalidId = static_cast(0x8055070Eu); +constexpr int InvalidAlignment = static_cast(0x80550714u); +constexpr int TooManyNpId = static_cast(0x80550719u); +constexpr int PollFinished = 0; +constexpr int PollRunning = 1; + +int failures = 0; +int context = 0; +unsigned char selfNpId[36]; +unsigned char npIds[2 * 36]; +unsigned char ranks[2 * 0x88]; +unsigned char comments[2 * 64]; +unsigned char infos[2 * 200]; +unsigned char board[24]; +unsigned char manyNpIds[101 * 36]; +unsigned char manyRanks[101 * 0x88]; + +void Require(bool value, const char* name, const char* what) { + if (value) return; + std::fprintf(stderr, "%s: %s\n", name, what); + ++failures; +} + +int NewRequest() { + return sceNpScoreCreateRequest(context); +} + +bool Untouched() { + for (const unsigned char value : ranks) if (value != 0xA5) return false; + for (const unsigned char value : comments) if (value != 0xA5) return false; + for (const unsigned char value : infos) if (value != 0xA5) return false; + for (const unsigned char value : board) if (value != 0xA5) return false; + return true; +} + +void CheckOffline(const char* name, int status, int req) { + Require(status == SignedOut, name, "valid arguments did not return SIGNED_OUT"); + int result = 0; + Require(sceNpScorePollAsync(req, &result) == PollFinished && result == SignedOut, name, "the request did not finish with SIGNED_OUT"); +} + +void CheckContexts() { + Require(sceNpScoreCreateNpTitleCtx(0xFFFFFFFFu, nullptr) == InvalidArgument, "sceNpScoreCreateNpTitleCtx", "the invalid service label was not refused before the null NpId"); + Require(sceNpScoreCreateNpTitleCtx(0, nullptr) == InsufficientArgument, "sceNpScoreCreateNpTitleCtx", "a null NpId was taken"); + context = sceNpScoreCreateNpTitleCtx(0, selfNpId); + Require(context == 1, "sceNpScoreCreateNpTitleCtx", "the first context is not id 1"); + Require(sceNpScoreCreateRequest(context + 100) == InvalidId, "sceNpScoreCreateRequest", "an unknown context was taken"); + int extra[31]; + for (int& entry : extra) entry = sceNpScoreCreateNpTitleCtx(1, selfNpId); + Require(extra[30] == 32, "sceNpScoreCreateNpTitleCtx", "32 contexts could not be created"); + Require(sceNpScoreCreateNpTitleCtx(1, selfNpId) == TooManyObjects, "sceNpScoreCreateNpTitleCtx", "a 33rd context did not return TOO_MANY_OBJECTS"); + const int kept = sceNpScoreCreateRequest(extra[0]); + Require(kept == 1, "sceNpScoreCreateRequest", "the first request is not id 1"); + for (const int entry : extra) Require(sceNpScoreDeleteNpTitleCtx(entry) == 0, "sceNpScoreDeleteNpTitleCtx", "deleting a context failed"); + Require(sceNpScoreDeleteRequest(kept) == InvalidId, "sceNpScoreDeleteNpTitleCtx", "a request of a deleted context was kept"); + Require(sceNpScoreDeleteNpTitleCtx(extra[0]) == InvalidId, "sceNpScoreDeleteNpTitleCtx", "a deleted context was found"); + int requests[32]; + for (int& entry : requests) entry = NewRequest(); + Require(requests[31] > 0, "sceNpScoreCreateRequest", "32 requests could not be created"); + Require(NewRequest() == TooManyObjects, "sceNpScoreCreateRequest", "a 33rd request did not return TOO_MANY_OBJECTS"); + for (const int entry : requests) sceNpScoreDeleteRequest(entry); + Require(sceNpScoreDeleteRequest(requests[0]) == InvalidId, "sceNpScoreDeleteRequest", "a deleted request was found"); +} + +void CheckRequests() { + int req = NewRequest(); + int result = 7; + Require(sceNpScorePollAsync(req + 100, &result) == InvalidId, "sceNpScorePollAsync", "an unknown request was taken"); + Require(sceNpScorePollAsync(req, &result) == PollRunning && result == 7, "sceNpScorePollAsync", "an unstarted request was not running"); + Require(sceNpScoreAbortRequest(req + 100) == InvalidId, "sceNpScoreAbortRequest", "an unknown request was taken"); + Require(sceNpScoreAbortRequest(req) == 0, "sceNpScoreAbortRequest", "aborting failed"); + Require(sceNpScorePollAsync(req, nullptr) == PollFinished, "sceNpScorePollAsync", "a null result was not accepted"); + Require(sceNpScorePollAsync(req, &result) == PollFinished && result == Aborted, "sceNpScoreAbortRequest", "an aborted request did not finish with ABORTED"); + Require(sceNpScoreRecordScore(req, 1, 5, nullptr, nullptr, nullptr, nullptr, nullptr) == Aborted, "sceNpScoreRecordScore", "an aborted request did not return ABORTED"); + sceNpScoreDeleteRequest(req); + + req = NewRequest(); + Require(sceNpScoreRecordScore(req, 1, 5, nullptr, nullptr, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreRecordScore", "the first call did not return SIGNED_OUT"); + Require(sceNpScoreAbortRequest(req) == 0, "sceNpScoreAbortRequest", "aborting a finished request failed"); + Require(sceNpScorePollAsync(req, &result) == PollFinished && result == SignedOut, "sceNpScoreAbortRequest", "aborting a finished request changed its result"); + Require(sceNpScoreRecordScore(req, 1, 5, nullptr, nullptr, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreRecordScore", "a finished request was not reused"); + sceNpScoreDeleteRequest(req); + +} + +void CheckRecord() { + NpScoreGameInfo info{}; + std::uint32_t tmpRank = 77; + const int req = NewRequest(); + info.infoSize = 190; + Require(sceNpScoreRecordScore(req, 1, 5, nullptr, &info, &tmpRank, nullptr, &info) == InvalidArgument, "sceNpScoreRecordScore", "an option was taken"); + Require(sceNpScoreRecordScore(req, 1, 5, nullptr, &info, &tmpRank, nullptr, nullptr) == InvalidArgument, "sceNpScoreRecordScore", "190 bytes of game info were taken"); + Require(sceNpScoreRecordScoreAsync(req, 1, 5, nullptr, &info, &tmpRank, nullptr, nullptr) == InvalidArgument, "sceNpScoreRecordScoreAsync", "190 bytes of game info were taken"); + info.infoSize = 189; + Require(sceNpScoreRecordScore(req + 100, 1, 5, nullptr, &info, &tmpRank, nullptr, nullptr) == InvalidId, "sceNpScoreRecordScore", "an unknown request was taken"); + Require(sceNpScorePollAsync(req, nullptr) == PollRunning, "sceNpScoreRecordScore", "a refused call finished the request"); + CheckOffline("sceNpScoreRecordScoreAsync", sceNpScoreRecordScoreAsync(req, 1, 5, nullptr, &info, &tmpRank, nullptr, nullptr), req); + Require(tmpRank == 77, "sceNpScoreRecordScoreAsync", "a rank was written"); + sceNpScoreDeleteRequest(req); + +} + +void CheckRange() { + const int req = NewRequest(); + Require(sceNpScoreGetRankingByRange(req, 1, 1, ranks, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, board) == InvalidArgument, "sceNpScoreGetRankingByRange", "an option was taken"); + Require(sceNpScoreGetRankingByRange(req, 1, 1, ranks, 101 * 0x80, nullptr, 0, nullptr, 0, 101, nullptr, nullptr, nullptr) == InvalidArgument, "sceNpScoreGetRankingByRange", "101 ranks were taken"); + Require(sceNpScoreGetRankingByRange(req, 1, 0, nullptr, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidArgument, "sceNpScoreGetRankingByRange", "rank 0 was not refused before the null array"); + Require(sceNpScoreGetRankingByRange(req, 1, 1, nullptr, 3, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InsufficientArgument, "sceNpScoreGetRankingByRange", "a null rank array was not refused before its size"); + Require(sceNpScoreGetRankingByRange(req, 1, 1, ranks, 0, nullptr, 0, nullptr, 0, 0, nullptr, nullptr, nullptr) == InsufficientArgument, "sceNpScoreGetRankingByRange", "zero ranks were taken"); + Require(sceNpScoreGetRankingByRange(req, 1, 1, ranks, 0x88, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByRange", "a rank array size other than 0x80 per rank was taken"); + Require(sceNpScoreGetRankingByRange(req, 1, 1, ranks, 0x100, comments, 64, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByRange", "a comment array size other than 64 per rank was taken"); + Require(sceNpScoreGetRankingByRange(req, 1, 1, ranks, 0x100, comments, 128, infos, 200, 2, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByRange", "an info array size other than 200 per rank was taken"); + Require(sceNpScoreGetRankingByRange(req + 100, 1, 1, ranks, 0x100, comments, 128, infos, 400, 2, nullptr, nullptr, nullptr) == InvalidId, "sceNpScoreGetRankingByRange", "an unknown request was taken"); + CheckOffline("sceNpScoreGetRankingByRange", sceNpScoreGetRankingByRange(req, 1, 1, ranks, 0x100, comments, 128, infos, 400, 2, nullptr, nullptr, nullptr), req); + Require(sceNpScoreGetRankingByRangeAsync(req, 1, 1, ranks, 0x100, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByRangeAsync", "valid arguments did not return SIGNED_OUT"); + Require(sceNpScoreGetRankingByRangeAsync(req, 1, 0, ranks, 0x100, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidArgument, "sceNpScoreGetRankingByRangeAsync", "rank 0 was taken"); + Require(sceNpScoreGetRankingByRangeAsync(req, 1, 1, manyRanks, 100 * 0x80, nullptr, 0, nullptr, 0, 100, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByRangeAsync", "100 ranks did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); + +} + +void CheckNpId() { + const int req = NewRequest(); + Require(sceNpScoreGetRankingByNpId(req, 1, npIds, 36, ranks, 0x88, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, board) == InvalidArgument, "sceNpScoreGetRankingByNpId", "an option was taken"); + Require(sceNpScoreGetRankingByNpId(req, 1, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0, 102, nullptr, nullptr, nullptr) == TooManyNpId, "sceNpScoreGetRankingByNpId", "102 NpIds were not refused before the null arrays"); + Require(sceNpScoreGetRankingByNpId(req, 1, nullptr, 36, ranks, 0x88, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InsufficientArgument, "sceNpScoreGetRankingByNpId", "a null NpId array was taken"); + Require(sceNpScoreGetRankingByNpId(req, 1, npIds, 36, nullptr, 0x88, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InsufficientArgument, "sceNpScoreGetRankingByNpId", "a null rank array was taken"); + Require(sceNpScoreGetRankingByNpId(req, 1, npIds, 24, ranks, 0x88, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByNpId", "an NpId array size other than 36 per NpId was taken"); + Require(sceNpScoreGetRankingByNpId(req, 1, npIds, 36, ranks, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByNpId", "a rank array size other than 0x88 per NpId was taken"); + Require(sceNpScoreGetRankingByNpId(req, 1, npIds, 36, ranks, 0x88, comments, 128, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByNpId", "a comment array size other than 64 per NpId was taken"); + CheckOffline("sceNpScoreGetRankingByNpId", sceNpScoreGetRankingByNpId(req, 1, npIds, 72, ranks, 0x110, comments, 128, infos, 400, 2, nullptr, nullptr, nullptr), req); + Require(sceNpScoreGetRankingByNpIdAsync(req, 1, npIds, 0, ranks, 0, nullptr, 0, nullptr, 0, 0, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByNpIdAsync", "zero NpIds did not return SIGNED_OUT"); + Require(sceNpScoreGetRankingByNpIdAsync(req, 1, manyNpIds, sizeof(manyNpIds), manyRanks, sizeof(manyRanks), nullptr, 0, nullptr, 0, 101, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByNpIdAsync", "101 NpIds did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); + +} + +void CheckFriends() { + const int req = NewRequest(); + const std::uint32_t zero = 0; + const std::uint32_t five = 5; + const NpScoreFriendRankingOption shortOption{16, nullptr, nullptr}; + const NpScoreFriendRankingOption rankZero{24, &zero, nullptr}; + const NpScoreFriendRankingOption rankFive{24, &five, nullptr}; + Require(sceNpScoreGetFriendsRanking(req, 1, 0, nullptr, 0x80, nullptr, 0, nullptr, 0, 0, nullptr, nullptr, &shortOption) == InvalidArgument, "sceNpScoreGetFriendsRanking", "a null rank array was taken"); + Require(sceNpScoreGetFriendsRanking(req, 1, 0, ranks, 0x88, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, &shortOption) == InvalidAlignment, "sceNpScoreGetFriendsRanking", "the rank array size was not checked before the option"); + Require(sceNpScoreGetFriendsRanking(req, 1, 0, ranks, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, &rankFive) == InvalidArgument, "sceNpScoreGetFriendsRanking", "an option without includeSelf was taken"); + Require(sceNpScoreGetFriendsRanking(req, 1, 1, ranks, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, &shortOption) == InvalidArgument, "sceNpScoreGetFriendsRanking", "an option size other than 24 was taken"); + Require(sceNpScoreGetFriendsRanking(req, 1, 1, ranks, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, &rankZero) == InvalidArgument, "sceNpScoreGetFriendsRanking", "start rank 0 was taken"); + Require(sceNpScoreGetFriendsRanking(req, 1, 1, ranks, 101 * 0x80, nullptr, 0, nullptr, 0, 101, nullptr, nullptr, nullptr) == InvalidArgument, "sceNpScoreGetFriendsRanking", "101 friends were taken"); + Require(sceNpScoreGetFriendsRanking(req, 1, 1, ranks, 0, nullptr, 0, nullptr, 0, 0, nullptr, nullptr, nullptr) == InsufficientArgument, "sceNpScoreGetFriendsRanking", "zero friends were taken"); + Require(sceNpScoreGetFriendsRanking(req, 1, 1, ranks, 0x80, nullptr, 0, infos, 400, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetFriendsRanking", "an info array size other than 200 per friend was taken"); + CheckOffline("sceNpScoreGetFriendsRanking", sceNpScoreGetFriendsRanking(req, 1, 1, ranks, 0x80, comments, 64, infos, 200, 1, nullptr, nullptr, &rankFive), req); + Require(sceNpScoreGetFriendsRankingAsync(req, 1, 0, ranks, 0x100, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetFriendsRankingAsync", "valid arguments did not return SIGNED_OUT"); + Require(sceNpScoreGetFriendsRankingAsync(req, 1, 0, manyRanks, 100 * 0x80, nullptr, 0, nullptr, 0, 100, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetFriendsRankingAsync", "100 friends did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); + +} + +void CheckBoard() { + const int req = NewRequest(); + Require(sceNpScoreGetBoardInfo(req, 1, board, board) == InvalidArgument, "sceNpScoreGetBoardInfo", "an option was taken"); + Require(sceNpScoreGetBoardInfo(req + 100, 1, board, nullptr) == InvalidId, "sceNpScoreGetBoardInfo", "an unknown request was taken"); + CheckOffline("sceNpScoreGetBoardInfo", sceNpScoreGetBoardInfo(req, 1, board, nullptr), req); + sceNpScoreDeleteRequest(req); + +} + +} + +int main() { + std::memset(ranks, 0xA5, sizeof(ranks)); + std::memset(comments, 0xA5, sizeof(comments)); + std::memset(infos, 0xA5, sizeof(infos)); + std::memset(board, 0xA5, sizeof(board)); + CheckContexts(); + CheckRequests(); + CheckRecord(); + CheckRange(); + CheckNpId(); + CheckFriends(); + CheckBoard(); + Require(Untouched(), "libSceNpScoreRanking", "an output array was written"); + if (failures != 0) return 1; + std::puts("NpScoreRanking offline tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp index 27bc077144..1818d3df1b 100644 --- a/core/libs/tests/GuestNpSessionSignaling.cpp +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -1,23 +1,21 @@ +#include "prx/libSceNpSessionSignaling/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include - -extern "C" { -int APS5_VABI sceNpSessionSignalingInitialize(void* param); -int APS5_VABI sceNpSessionSignalingCreateContext2(const void* param, std::uint32_t* contextId); -std::int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(std::int32_t contextId, void* info); -int APS5_VABI sceNpSessionSignalingRequestPrepare(std::uint32_t contextId, std::uint32_t* requestId); -std::int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(std::int32_t contextId, std::int32_t connectionId, std::int32_t* status, void* peerAddress, std::uint16_t* peerPort); -int APS5_VABI sceNpSessionSignalingTerminate(void); -} +#include namespace { constexpr int InvalidArgument = static_cast(0x80553303u); constexpr int Unavailable = static_cast(0x80552D06u); constexpr int ConnectionNotFound = static_cast(0x8055330Cu); +constexpr int NotInitialized = static_cast(0x80553301u); +constexpr int AlreadyInitialized = static_cast(0x80553302u); +constexpr int ContextLimit = static_cast(0x80553307u); +constexpr int ContextNotFound = static_cast(0x80553308u); +constexpr int GroupNotFound = static_cast(0x8055330Au); void Require(bool condition, const char* message) { if (!condition) { @@ -29,13 +27,45 @@ void Require(bool condition, const char* message) { } int main() { - unsigned char initializeParam[32] = {}; - Require(sceNpSessionSignalingInitialize(initializeParam) == 0, "initialize failed"); - unsigned char contextParam[64] = {}; + alignas(8) unsigned char initializeParam[40] = {}; + Require(sceNpSessionSignalingTerminate() == NotInitialized, "terminate before initialize"); + Require(sceNpSessionSignalingCreateContext2(nullptr, nullptr) == NotInitialized, "initialization check precedes context arguments"); + Require(sceNpSessionSignalingGetConnectionStatus(0, 0, nullptr, nullptr, nullptr) == NotInitialized, "status before initialize"); + Require(sceNpSessionSignalingInitialize(nullptr) == InvalidArgument, "null initialize parameter"); + Require(sceNpSessionSignalingInitialize(reinterpret_cast(initializeParam)) == InvalidArgument, "zero pool size"); + const std::uint64_t poolSize = 4 * 1024 * 1024; + std::memcpy(initializeParam + 8, &poolSize, sizeof(poolSize)); + Require(sceNpSessionSignalingInitialize(reinterpret_cast(initializeParam)) == 0, "initialize failed"); + Require(sceNpSessionSignalingInitialize(nullptr) == AlreadyInitialized, "initialization check precedes initialize arguments"); + alignas(8) unsigned char contextParam[56] = {}; std::uint32_t contextId = 0; - Require(sceNpSessionSignalingCreateContext2(contextParam, &contextId) == 0, "context creation failed"); + Require(sceNpSessionSignalingCreateContext(nullptr, &contextId) == InvalidArgument, "null context parameter"); + Require(contextId == 0, "invalid context parameter wrote an id"); + Require(sceNpSessionSignalingCreateContext2(nullptr, &contextId) == InvalidArgument, "null context2 parameter"); + Require(contextId == 0, "invalid context2 parameter wrote an id"); + Require(sceNpSessionSignalingCreateContext(reinterpret_cast(contextParam), nullptr) == InvalidArgument, "null context id"); + Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), nullptr) == InvalidArgument, "null context2 id"); + Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == 0, "context creation failed"); + const auto firstId = contextId; + Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == ContextLimit, "duplicate user context"); + Require(contextId == firstId, "duplicate context modified output"); + std::array otherContexts{}; + for (std::int32_t userId = 1; userId <= 9; ++userId) { + std::memcpy(contextParam, &userId, sizeof(userId)); + Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &otherContexts[userId - 1]) == 0, "create remaining contexts"); + } + const std::int32_t overflowUser = 10; + std::memcpy(contextParam, &overflowUser, sizeof(overflowUser)); + Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == ContextLimit, "context limit"); + Require(contextId == firstId, "context limit modified output"); + Require(sceNpSessionSignalingDestroyContext(firstId) == 0, "destroy first context"); + Require(sceNpSessionSignalingDestroyContext(firstId) == ContextNotFound, "destroy stale context"); + Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == 0, "reuse context slot"); + Require(contextId != firstId, "reused context kept stale id"); std::uint32_t requestId = 0xa5a5a5a5u; + Require(sceNpSessionSignalingRequestPrepare(firstId, &requestId) == ContextNotFound, "prepare stale context"); + Require(sceNpSessionSignalingRequestPrepare(firstId, nullptr) == InvalidArgument, "prepare argument precedence"); Require(sceNpSessionSignalingRequestPrepare(contextId, nullptr) == InvalidArgument, "null request id"); Require(sceNpSessionSignalingRequestPrepare(contextId, &requestId) == Unavailable, "prepare without network"); Require(requestId == 0xa5a5a5a5u, "failed prepare wrote a request id"); @@ -46,16 +76,56 @@ int main() { unsigned char untouched[sizeof(info)]; std::memcpy(untouched, info, sizeof(info)); Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, nullptr) == InvalidArgument, "null info"); - Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, info) == Unavailable, "local net info without network"); + Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, reinterpret_cast(info)) == Unavailable, "local net info without network"); Require(std::memcmp(info, untouched, sizeof(info)) == 0, "failed query modified the info"); std::int32_t status = 0x5a5a5a5a; std::uint32_t peerAddress = 0xa5a5a5a5u; std::uint16_t peerPort = 0xa5a5; + Require(sceNpSessionSignalingGetConnectionStatus(firstId, 1, &status, &peerAddress, &peerPort) == ContextNotFound, "status stale context"); Require(sceNpSessionSignalingGetConnectionStatus(netContextId, 1, nullptr, &peerAddress, &peerPort) == InvalidArgument, "null connection status"); Require(sceNpSessionSignalingGetConnectionStatus(netContextId, 1, &status, &peerAddress, &peerPort) == ConnectionNotFound, "connection without network"); Require(sceNpSessionSignalingGetConnectionStatus(netContextId, 1, &status, nullptr, nullptr) == ConnectionNotFound, "connection without peer outputs"); Require(status == 0x5a5a5a5a && peerAddress == 0xa5a5a5a5u && peerPort == 0xa5a5, "failed status query wrote an output"); + Require(sceNpSessionSignalingDeactivate(firstId, 1) == ContextNotFound, "deactivate stale context"); + Require(sceNpSessionSignalingDeactivate(contextId, 1) == GroupNotFound, "deactivate nonexistent group"); + + Require(sceNpSessionSignalingGetGroupInfo(netContextId, 1, nullptr) == InvalidArgument, "null group info"); + Require(sceNpSessionSignalingGetGroupInfo(netContextId, 1, reinterpret_cast(info)) == GroupNotFound, "group info without a session"); + Require(std::memcmp(info, untouched, sizeof(info)) == 0, "failed group query modified the info"); + + const unsigned char sessionId[48] = {}; + const unsigned char peer[40] = {}; + std::int32_t found = 0x5a5a5a5a; + Require(sceNpSessionSignalingGetGroupFromSessionId(netContextId, nullptr, reinterpret_cast(&found)) == InvalidArgument, "null session id"); + Require(sceNpSessionSignalingGetGroupFromSessionId(netContextId, reinterpret_cast(sessionId), nullptr) == InvalidArgument, "null group id"); + Require(sceNpSessionSignalingGetGroupFromSessionId(netContextId, reinterpret_cast(sessionId), reinterpret_cast(&found)) == GroupNotFound, "group from session id without a session"); + Require(sceNpSessionSignalingGetConnectionFromNetAddress(netContextId, 0x0100007fu, 3658, nullptr) == InvalidArgument, "null connection id from net address"); + Require(sceNpSessionSignalingGetConnectionFromNetAddress(netContextId, 0x0100007fu, 3658, reinterpret_cast(&found)) == ConnectionNotFound, "connection from net address without a session"); + Require(sceNpSessionSignalingGetConnectionFromNetAddress2(netContextId, 1, 0x0100007fu, 3658, nullptr) == InvalidArgument, "null connection id from group net address"); + Require(sceNpSessionSignalingGetConnectionFromNetAddress2(netContextId, 1, 0x0100007fu, 3658, reinterpret_cast(&found)) == GroupNotFound, "connection from group net address without a session"); + Require(sceNpSessionSignalingGetConnectionFromPeerAddress(netContextId, nullptr, reinterpret_cast(&found)) == InvalidArgument, "null peer address"); + Require(sceNpSessionSignalingGetConnectionFromPeerAddress(netContextId, reinterpret_cast(peer), nullptr) == InvalidArgument, "null connection id from peer address"); + Require(sceNpSessionSignalingGetConnectionFromPeerAddress(netContextId, reinterpret_cast(peer), reinterpret_cast(&found)) == ConnectionNotFound, "connection from peer address without a session"); + Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, nullptr, reinterpret_cast(&found)) == InvalidArgument, "null group peer address"); + Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, reinterpret_cast(peer), nullptr) == InvalidArgument, "null connection id from group peer address"); + Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, reinterpret_cast(peer), reinterpret_cast(&found)) == GroupNotFound, "connection from group peer address without a session"); + Require(sceNpSessionSignalingGetGroupInfo(firstId, 1, reinterpret_cast(info)) == ContextNotFound, "group info stale context"); + Require(sceNpSessionSignalingGetGroupFromSessionId(firstId, reinterpret_cast(sessionId), reinterpret_cast(&found)) == ContextNotFound, "group from session id stale context"); + Require(sceNpSessionSignalingGetConnectionFromNetAddress(firstId, 0x0100007fu, 3658, reinterpret_cast(&found)) == ContextNotFound, "connection from net address stale context"); + Require(sceNpSessionSignalingGetConnectionFromNetAddress2(firstId, 1, 0x0100007fu, 3658, reinterpret_cast(&found)) == ContextNotFound, "connection from group net address stale context"); + Require(sceNpSessionSignalingGetConnectionFromPeerAddress(firstId, reinterpret_cast(peer), reinterpret_cast(&found)) == ContextNotFound, "connection from peer address stale context"); + Require(found == 0x5a5a5a5a, "a failed lookup wrote an id"); Require(sceNpSessionSignalingTerminate() == 0, "terminate failed"); + Require(sceNpSessionSignalingDestroyContext(contextId) == NotInitialized, "destroy 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(sceNpSessionSignalingTerminate() == NotInitialized, "repeated terminate"); + Require(sceNpSessionSignalingInitialize(reinterpret_cast(initializeParam)) == 0, "reinitialize"); + Require(sceNpSessionSignalingRequestPrepare(contextId, &requestId) == ContextNotFound, "reinitialize retained a context"); + Require(sceNpSessionSignalingTerminate() == 0, "final terminate"); } diff --git a/core/libs/tests/GuestNpSignalingOffline.cpp b/core/libs/tests/GuestNpSignalingOffline.cpp new file mode 100644 index 0000000000..f457b0aeae --- /dev/null +++ b/core/libs/tests/GuestNpSignalingOffline.cpp @@ -0,0 +1,129 @@ +#include "prx/libSceNpSignaling/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include + +namespace { + +constexpr int SignedOut = static_cast(0x80550006u); +constexpr int NotInitialized = static_cast(0x80552701u); +constexpr int AlreadyInitialized = static_cast(0x80552702u); +constexpr int CtxIdNotAvailable = static_cast(0x80552704u); +constexpr int CtxNotFound = static_cast(0x80552705u); +constexpr int ConnNotFound = static_cast(0x8055270Eu); +constexpr int InvalidArgument = static_cast(0x80552715u); +constexpr int OwnNpId = static_cast(0x80552716u); + +struct SignalingNpId { + char handle[16]; + char term; + char dummy[3]; + std::uint8_t opt[8]; + std::uint8_t reserved[8]; +}; +static_assert(sizeof(SignalingNpId) == 36); + +int failures = 0; + +void Require(bool value, const char* name, const char* what) { + if (value) return; + std::fprintf(stderr, "%s: %s\n", name, what); + ++failures; +} + +SignalingNpId MakeNpId(const char* handle) { + SignalingNpId id{}; + std::memcpy(id.handle, handle, std::strlen(handle)); + return id; +} + +} + +int main() { + const SignalingNpId owner = MakeNpId("player_1"); + const SignalingNpId peer = MakeNpId("peer-2"); + const SignalingNpId maximum = MakeNpId("abcdefghijklmnop"); + const SignalingNpId tooShort = MakeNpId("ab"); + const SignalingNpId leadingUnderscore = MakeNpId("_player"); + const SignalingNpId withDot = MakeNpId("pl.ayer"); + SignalingNpId latin = MakeNpId("pl\xE9yer"); + SignalingNpId upper = MakeNpId("Player9"); + SignalingNpId unterminated = MakeNpId("player_1"); + unterminated.term = 'x'; + unsigned char info[64]; + std::memset(info, 0xA5, sizeof(info)); + int context = -7; + int connection = -7; + + Require(sceNpSignalingCreateContext(nullptr, nullptr, nullptr, nullptr) == NotInitialized, "sceNpSignalingCreateContext", "a call before initialization did not return NOT_INITIALIZED first"); + Require(sceNpSignalingDeleteContext(1) == NotInitialized, "sceNpSignalingDeleteContext", "a call before initialization did not return NOT_INITIALIZED"); + Require(sceNpSignalingSetContextOption(1, 1, 1) == NotInitialized, "sceNpSignalingSetContextOption", "a call before initialization did not return NOT_INITIALIZED"); + Require(sceNpSignalingActivateConnection(1, nullptr, nullptr) == NotInitialized, "sceNpSignalingActivateConnection", "a call before initialization did not return NOT_INITIALIZED first"); + Require(sceNpSignalingTerminateConnection(1, 1) == NotInitialized, "sceNpSignalingTerminateConnection", "a call before initialization did not return NOT_INITIALIZED"); + Require(sceNpSignalingDeactivateConnection(1, 1) == NotInitialized, "sceNpSignalingDeactivateConnection", "a call before initialization did not return NOT_INITIALIZED"); + Require(sceNpSignalingGetConnectionInfo(1, 1, 1, nullptr) == NotInitialized, "sceNpSignalingGetConnectionInfo", "a call before initialization did not return NOT_INITIALIZED first"); + Require(sceNpSignalingTerminate() == NotInitialized, "sceNpSignalingTerminate", "terminating before initialization did not return NOT_INITIALIZED"); + + Require(sceNpSignalingInitialize(0, 0, 0, 0) == 0, "sceNpSignalingInitialize", "initialization failed"); + Require(sceNpSignalingInitialize(0, 0, 0, 0) == AlreadyInitialized, "sceNpSignalingInitialize", "a second initialization did not return ALREADY_INITIALIZED"); + + Require(sceNpSignalingCreateContext(nullptr, nullptr, nullptr, &context) == InvalidArgument, "sceNpSignalingCreateContext", "a null SignalingNpId was taken"); + Require(sceNpSignalingCreateContext(&owner, nullptr, nullptr, nullptr) == InvalidArgument, "sceNpSignalingCreateContext", "a null output was taken"); + Require(sceNpSignalingCreateContext(&tooShort, nullptr, nullptr, &context) == InvalidArgument, "sceNpSignalingCreateContext", "a two-character online ID was taken"); + Require(sceNpSignalingCreateContext(&leadingUnderscore, nullptr, nullptr, &context) == InvalidArgument, "sceNpSignalingCreateContext", "an online ID starting with '_' was taken"); + Require(sceNpSignalingCreateContext(&withDot, nullptr, nullptr, &context) == InvalidArgument, "sceNpSignalingCreateContext", "an online ID with '.' was taken"); + Require(sceNpSignalingCreateContext(&unterminated, nullptr, nullptr, &context) == InvalidArgument, "sceNpSignalingCreateContext", "an unterminated online ID was taken"); + Require(sceNpSignalingCreateContext(&latin, nullptr, nullptr, &context) == InvalidArgument, "sceNpSignalingCreateContext", "an online ID with a byte outside ASCII was taken"); + Require(context == -7, "sceNpSignalingCreateContext", "a refused call wrote the context id"); + + Require(sceNpSignalingCreateContext(&owner, nullptr, nullptr, &context) == 0 && context == 1, "sceNpSignalingCreateContext", "the first context is not id 1"); + Require(sceNpSignalingActivateConnection(context, &upper, &connection) == SignedOut, "sceNpSignalingActivateConnection", "an online ID with upper-case letters and digits was refused"); + int created = 0; + Require(sceNpSignalingCreateContext(&maximum, nullptr, nullptr, &created) == 0 && created == 2, "sceNpSignalingCreateContext", "the second context is not id 2"); + Require(sceNpSignalingDeleteContext(2) == 0, "sceNpSignalingDeleteContext", "deleting context 2 failed"); + for (int expected = 3; expected <= 8; ++expected) { + Require(sceNpSignalingCreateContext(&maximum, nullptr, nullptr, &created) == 0 && created == expected, "sceNpSignalingCreateContext", "a context id was reused before the ids up to 8 were handed out"); + } + Require(sceNpSignalingCreateContext(&maximum, nullptr, nullptr, &created) == 0 && created == 2, "sceNpSignalingCreateContext", "after id 8 the allocation did not wrap to the free id 2"); + created = -7; + Require(sceNpSignalingCreateContext(&owner, nullptr, nullptr, &created) == CtxIdNotAvailable && created == -7, "sceNpSignalingCreateContext", "a ninth context did not return CTXID_NOT_AVAILABLE"); + + Require(sceNpSignalingSetContextOption(99, 2, 5) == CtxNotFound, "sceNpSignalingSetContextOption", "an unknown context was not refused before the option"); + Require(sceNpSignalingSetContextOption(context, 2, 0) == InvalidArgument, "sceNpSignalingSetContextOption", "option id 2 was taken"); + Require(sceNpSignalingSetContextOption(context, 1, 2) == InvalidArgument, "sceNpSignalingSetContextOption", "flag value 2 was taken"); + Require(sceNpSignalingSetContextOption(context, 1, 0) == 0, "sceNpSignalingSetContextOption", "clearing the flag failed"); + Require(sceNpSignalingSetContextOption(context, 1, 1) == 0, "sceNpSignalingSetContextOption", "setting the flag failed"); + + Require(sceNpSignalingActivateConnection(context, nullptr, &connection) == InvalidArgument, "sceNpSignalingActivateConnection", "a null peer was taken"); + Require(sceNpSignalingActivateConnection(context, &peer, nullptr) == InvalidArgument, "sceNpSignalingActivateConnection", "a null output was taken"); + Require(sceNpSignalingActivateConnection(99, &tooShort, &connection) == InvalidArgument, "sceNpSignalingActivateConnection", "an invalid peer was not refused before the context"); + Require(sceNpSignalingActivateConnection(99, &peer, &connection) == CtxNotFound, "sceNpSignalingActivateConnection", "an unknown context was taken"); + Require(sceNpSignalingActivateConnection(context, &owner, &connection) == OwnNpId, "sceNpSignalingActivateConnection", "the context's own SignalingNpId did not return OWN_NP_ID"); + Require(sceNpSignalingActivateConnection(context, &peer, &connection) == SignedOut, "sceNpSignalingActivateConnection", "a valid peer did not return SIGNED_OUT"); + Require(connection == -7, "sceNpSignalingActivateConnection", "a connection id was written"); + + Require(sceNpSignalingTerminateConnection(99, 1) == CtxNotFound, "sceNpSignalingTerminateConnection", "an unknown context was taken"); + Require(sceNpSignalingTerminateConnection(context, 1) == ConnNotFound, "sceNpSignalingTerminateConnection", "a connection that was never created was found"); + Require(sceNpSignalingDeactivateConnection(99, 1) == CtxNotFound, "sceNpSignalingDeactivateConnection", "an unknown context was taken"); + Require(sceNpSignalingDeactivateConnection(context, 1) == ConnNotFound, "sceNpSignalingDeactivateConnection", "a connection that was never created was found"); + Require(sceNpSignalingGetConnectionInfo(99, 1, 1, nullptr) == InvalidArgument, "sceNpSignalingGetConnectionInfo", "a null output was not refused before the context"); + Require(sceNpSignalingGetConnectionInfo(99, 1, 1, info) == CtxNotFound, "sceNpSignalingGetConnectionInfo", "an unknown context was taken"); + Require(sceNpSignalingGetConnectionInfo(context, 1, 1, info) == ConnNotFound, "sceNpSignalingGetConnectionInfo", "a connection that was never created was found"); + Require(info[0] == 0xA5 && info[63] == 0xA5, "sceNpSignalingGetConnectionInfo", "connection information was written"); + + Require(sceNpSignalingDeleteContext(99) == 0, "sceNpSignalingDeleteContext", "deleting an unknown context failed"); + Require(sceNpSignalingDeleteContext(context) == 0, "sceNpSignalingDeleteContext", "deleting a context failed"); + Require(sceNpSignalingSetContextOption(context, 1, 1) == CtxNotFound, "sceNpSignalingSetContextOption", "a deleted context was found"); + + Require(sceNpSignalingTerminate() == 0, "sceNpSignalingTerminate", "termination failed"); + Require(sceNpSignalingCreateContext(&owner, nullptr, nullptr, &created) == NotInitialized, "sceNpSignalingCreateContext", "a context was created after termination"); + Require(sceNpSignalingInitialize(0, 0, 0, 0) == 0, "sceNpSignalingInitialize", "initializing again failed"); + Require(sceNpSignalingSetContextOption(2, 1, 1) == CtxNotFound, "sceNpSignalingTerminate", "a context created before termination is still found"); + Require(sceNpSignalingCreateContext(&owner, nullptr, nullptr, &created) == 0 && created == 1, "sceNpSignalingCreateContext", "termination did not release the contexts and restart the ids at 1"); + Require(sceNpSignalingTerminate() == 0, "sceNpSignalingTerminate", "the second termination failed"); + + if (failures != 0) return 1; + std::puts("NpSignaling offline tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestNpSnsFacebookDialog.cpp b/core/libs/tests/GuestNpSnsFacebookDialog.cpp new file mode 100644 index 0000000000..347a2dcfe3 --- /dev/null +++ b/core/libs/tests/GuestNpSnsFacebookDialog.cpp @@ -0,0 +1,63 @@ +#include "prx/libSceNpSnsFacebookDialog/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80B80003); +constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); +constexpr int kErrNotFinished = static_cast(0x80B80005); +constexpr int kErrArgNull = static_cast(0x80B8000D); +constexpr int kResultUserCanceled = 1; + +} + +int main() { + std::int64_t param = 0; + std::int32_t result[2] = {-1, -1}; + + Require(sceNpSnsFacebookDialogTerminate() == kErrNotInitialized); + Require(sceNpSnsFacebookDialogOpen(¶m) == kErrNotInitialized); + Require(sceNpSnsFacebookDialogClose() == kErrNotInitialized); + Require(sceNpSnsFacebookDialogGetResult(result) == kErrNotInitialized); + Require(sceNpSnsFacebookDialogGetStatus() == kStatusNone); + Require(sceNpSnsFacebookDialogUpdateStatus() == kStatusNone); + + Require(sceNpSnsFacebookDialogInitialize() == 0); + Require(sceNpSnsFacebookDialogInitialize() == kErrAlreadyInitialized); + Require(sceNpSnsFacebookDialogGetStatus() == kStatusInitialized); + Require(sceNpSnsFacebookDialogUpdateStatus() == kStatusInitialized); + + Require(sceNpSnsFacebookDialogOpen(nullptr) == kErrArgNull); + Require(sceNpSnsFacebookDialogGetResult(result) == kErrNotFinished); + + Require(sceNpSnsFacebookDialogOpen(¶m) == 0); + Require(sceNpSnsFacebookDialogGetStatus() == kStatusFinished); + Require(sceNpSnsFacebookDialogUpdateStatus() == kStatusFinished); + + Require(sceNpSnsFacebookDialogGetResult(nullptr) == kErrArgNull); + Require(sceNpSnsFacebookDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(result[1] == -1); + + Require(sceNpSnsFacebookDialogClose() == 0); + Require(sceNpSnsFacebookDialogTerminate() == 0); + Require(sceNpSnsFacebookDialogTerminate() == kErrNotInitialized); + Require(sceNpSnsFacebookDialogGetStatus() == kStatusNone); + Require(sceNpSnsFacebookDialogUpdateStatus() == kStatusNone); + + Require(sceNpSnsFacebookDialogInitialize() == 0); + Require(sceNpSnsFacebookDialogUpdateStatus() == kStatusInitialized); + Require(sceNpSnsFacebookDialogClose() == 0); + Require(sceNpSnsFacebookDialogUpdateStatus() == kStatusFinished); + result[0] = -1; + Require(sceNpSnsFacebookDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(sceNpSnsFacebookDialogTerminate() == 0); +} diff --git a/core/libs/tests/GuestNpTrophy.cpp b/core/libs/tests/GuestNpTrophy.cpp new file mode 100644 index 0000000000..b6023bad77 --- /dev/null +++ b/core/libs/tests/GuestNpTrophy.cpp @@ -0,0 +1,115 @@ +#include "prx/libSceNpTrophy/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int InvalidArgument = static_cast(0x80551604u); +constexpr int InsufficientBuffer = static_cast(0x80551605u); +constexpr int InvalidHandle = static_cast(0x80551608u); +constexpr int InvalidContext = static_cast(0x80551609u); +constexpr int InvalidTrophyId = static_cast(0x8055160au); +constexpr int AlreadyUnlocked = static_cast(0x8055160cu); +constexpr int NotRegistered = static_cast(0x8055160fu); +constexpr int AlreadyRegistered = static_cast(0x80551610u); +constexpr int ContextAlreadyExists = static_cast(0x80551613u); +constexpr int TitleConfNotInstalled = static_cast(0x80551626u); +constexpr int TrophyNotUnlocked = static_cast(0x8055161au); + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "NpTrophy: %s\n", message); + std::abort(); + } +} + +void PutBigEndian(std::vector& bytes, std::size_t at, std::uint64_t value, int width) { + for (int i = 0; i < width; ++i) bytes[at + i] = static_cast(value >> (8 * (width - 1 - i))); +} + +void WritePackage(const std::filesystem::path& path, const std::vector>& files) { + const std::size_t header = 0x60, entry = 0x40; + std::vector bytes(header + entry * files.size()); + PutBigEndian(bytes, 0, 0xdca24d00u, 4); + PutBigEndian(bytes, 0x10, files.size(), 4); + PutBigEndian(bytes, 0x14, entry, 4); + for (std::size_t i = 0; i < files.size(); ++i) { + const auto at = header + entry * i; + std::memcpy(bytes.data() + at, files[i].first.data(), files[i].first.size()); + PutBigEndian(bytes, at + 32, bytes.size(), 8); + PutBigEndian(bytes, at + 40, files[i].second.size(), 8); + bytes.insert(bytes.end(), files[i].second.begin(), files[i].second.end()); + } + std::filesystem::create_directories(path.parent_path()); + std::ofstream(path, std::ios::binary).write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); +} + +} + +int main() { + const auto root = std::filesystem::temp_directory_path() / ("anyps5-np-trophy-" + std::to_string(std::random_device{}())); + std::filesystem::remove_all(root); + WritePackage(root / "app0/sce_sys/trophy/trophy00.trp", {{"TROPCONF.ESFM", "conf"}, {"ICON0.PNG", "game-icon"}, {"TROP000.PNG", "first"}, {"TROP001.PNG", "second"}}); + std::filesystem::current_path(root); + + int context = 0, handle = 0, missing = 0, platinum = 0; + Require(sceNpTrophyCreateContext(nullptr, 1, 0, 0) == InvalidArgument, "null context"); + Require(sceNpTrophyCreateContext(&context, 1, 0, 1) == InvalidArgument, "context options"); + Require(sceNpTrophyCreateContext(&context, 1, 0, 0) == 0, "create the context"); + Require(sceNpTrophyCreateContext(&missing, 1, 0, 0) == ContextAlreadyExists, "second context for the same user and label"); + Require(sceNpTrophyCreateHandle(&handle) == 0, "create the handle"); + + std::uint32_t flags[4] = {}, count = 0; + Require(sceNpTrophyGetTrophyUnlockState(context, handle, reinterpret_cast(flags), &count) == NotRegistered, "state before registration"); + Require(sceNpTrophyRegisterContext(context, handle, 0) == 0, "register the context"); + Require(sceNpTrophyRegisterContext(context, handle, 0) == AlreadyRegistered, "register twice"); + Require(sceNpTrophyGetTrophyUnlockState(context, handle, reinterpret_cast(flags), &count) == 0 && count == 2 && flags[0] == 0, "two trophies, none unlocked"); + + std::uint64_t size = 0; + Require(sceNpTrophyGetGameIcon(context, handle, nullptr, &size) == 0 && size == 9, "game icon size"); + char icon[16] = {}; + size = 4; + Require(sceNpTrophyGetGameIcon(context, handle, icon, &size) == InsufficientBuffer, "game icon into a short buffer"); + size = sizeof(icon); + Require(sceNpTrophyGetGameIcon(context, handle, icon, &size) == 0 && size == 9 && std::memcmp(icon, "game-icon", 9) == 0, "game icon bytes"); + + size = sizeof(icon); + Require(sceNpTrophyGetTrophyIcon(context, handle, 1, icon, &size) == TrophyNotUnlocked, "icon of a locked trophy"); + Require(sceNpTrophyUnlockTrophy(context, handle, 2, &platinum) == InvalidTrophyId, "unlock a trophy past the count"); + Require(sceNpTrophyUnlockTrophy(context, handle, 1, &platinum) == 0 && platinum == -1, "unlock trophy 1"); + Require(sceNpTrophyUnlockTrophy(context, handle, 1, &platinum) == AlreadyUnlocked, "unlock trophy 1 twice"); + Require(sceNpTrophyGetTrophyUnlockState(context, handle, reinterpret_cast(flags), &count) == 0 && flags[0] == 2u, "trophy 1 unlocked"); + size = sizeof(icon); + Require(sceNpTrophyGetTrophyIcon(context, handle, 1, icon, &size) == 0 && size == 6 && std::memcmp(icon, "second", 6) == 0, "icon of the unlocked trophy"); + int saved = -1; + { + std::ifstream state(root / "_trophy/trophy00.1"); + state >> saved; + } + Require(saved == 1, "unlock state saved for the user"); + + 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"); + Require(sceNpTrophyCreateContext(nullptr, 1, 0, 0) == InvalidArgument, "null context creation"); + Require(sceNpTrophyGetGameIcon(context, handle, icon, nullptr) == InvalidArgument, "null game icon size pointer"); + Require(sceNpTrophyGetGroupIcon(context, handle, 0, icon, nullptr) == InvalidArgument, "null group icon size pointer"); + Require(sceNpTrophyGetTrophyIcon(context, handle, 0, icon, nullptr) == InvalidArgument, "null trophy icon size pointer"); + Require(sceNpTrophyDestroyHandle(-1) == InvalidHandle, "invalid handle destruction"); + Require(sceNpTrophyDestroyContext(missing) == 0, "destroy the second context"); + Require(sceNpTrophyDestroyHandle(handle) == 0 && sceNpTrophyDestroyContext(context) == 0, "destroy the handle and context"); + Require(sceNpTrophyDestroyContext(context) == InvalidContext, "destroy the context twice"); + + std::filesystem::current_path(root.parent_path()); + std::error_code error; + std::filesystem::remove_all(root, error); + return 0; +} diff --git a/core/libs/tests/GuestNpTrophy2.cpp b/core/libs/tests/GuestNpTrophy2.cpp index 165d4347f1..2dcaa5f409 100644 --- a/core/libs/tests/GuestNpTrophy2.cpp +++ b/core/libs/tests/GuestNpTrophy2.cpp @@ -1,17 +1,11 @@ +#include "prx/libSceNpTrophy2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp" #include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp" +#include #include #include -extern "C" { -int APS5_VABI sceNpTrophy2RegisterUnlockCallback(void*, void*); -int APS5_VABI sceNpTrophy2UnregisterUnlockCallback(); -int APS5_VABI sceNpTrophy2GetGameInfo(int, int, NpTrophy2GameDetails*, NpTrophy2GameData*); -int APS5_VABI sceNpTrophy2GetGroupInfo(int, int, int, NpTrophy2GroupDetails*, NpTrophy2GroupData*); -int APS5_VABI sceNpTrophy2GetTrophyInfo(int, int, int, NpTrophy2Details*, NpTrophy2Data*); -int APS5_VABI sceNpTrophy2ShowTrophyList(int); -} - static void Require(bool value) { if (!value) std::abort(); } static void APS5_VABI OnUnlock(int, int, void*) {} @@ -57,4 +51,8 @@ int main() { Require(sceNpTrophy2GetTrophyInfo(1, 1, 3, nullptr, &trophyData) == 0); Require(trophyData.trophy_id == 3); Require(ThrowsInvalidArgument([] { sceNpTrophy2GetTrophyInfo(1, 1, 3, nullptr, nullptr); })); + std::size_t iconSize = 1234; + Require(sceNpTrophy2GetRewardIcon(1, 1, 1, nullptr, &iconSize) == SCE_NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND); + Require(iconSize == 0); + Require(sceNpTrophy2GetRewardIcon(1, 1, 1, nullptr, nullptr) == SCE_NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND); } diff --git a/core/libs/tests/GuestNpTrophyData.cpp b/core/libs/tests/GuestNpTrophyData.cpp index 5c4eaf025a..6810f8a3da 100644 --- a/core/libs/tests/GuestNpTrophyData.cpp +++ b/core/libs/tests/GuestNpTrophyData.cpp @@ -1,7 +1,10 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" +#include "prx/libSceNpTrophy2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp" #include "prx/libSceNpTrophy2/include/TrophyStore.hpp" +#include #include #include #include @@ -17,25 +20,8 @@ struct NpUniversalDataSystemEvent; struct NpUniversalDataSystemEventPropertyObject; -extern "C" { -int APS5_VABI sceNpUniversalDataSystemCreateContext(int*, int, uint32_t, uint64_t); -int APS5_VABI sceNpUniversalDataSystemRegisterContext(int, int, uint64_t); -int APS5_VABI sceNpUniversalDataSystemCreateHandle(int*); -int APS5_VABI sceNpUniversalDataSystemCreateEvent(const char*, const NpUniversalDataSystemEventPropertyObject*, NpUniversalDataSystemEvent**, NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemDestroyEvent(NpUniversalDataSystemEvent*); -int APS5_VABI sceNpUniversalDataSystemPostEvent(int, int, const void*, uint64_t); -int APS5_VABI sceNpUniversalDataSystemCreateEventPropertyObject(NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemDestroyEventPropertyObject(NpUniversalDataSystemEventPropertyObject*); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetInt32(NpUniversalDataSystemEventPropertyObject*, const char*, int32_t); -int APS5_VABI sceNpTrophy2CreateContext(int*, int, uint32_t, uint64_t); -int APS5_VABI sceNpTrophy2GetGameInfo(int, int, NpTrophy2GameDetails*, NpTrophy2GameData*); -int APS5_VABI sceNpTrophy2GetGroupInfo(int, int, int, NpTrophy2GroupDetails*, NpTrophy2GroupData*); -int APS5_VABI sceNpTrophy2GetGroupInfoArray(int, int, uint32_t, uint32_t, NpTrophy2GroupDetails*, NpTrophy2GroupData*, uint32_t*); -int APS5_VABI sceNpTrophy2GetTrophyInfo(int, int, int, NpTrophy2Details*, NpTrophy2Data*); -int APS5_VABI sceNpTrophy2GetTrophyInfoArray(int, int, uint32_t, uint32_t, NpTrophy2Details*, NpTrophy2Data*, uint32_t*); -} - static constexpr int InitialUser = 0x10000000; +static constexpr int InvalidTrophyId = static_cast(0x8055390Au); static const char TropConf[] = R"({"defaultLanguage":"en-US","trophies":[ {"id":"0","grade":"P"}, @@ -168,8 +154,18 @@ static void ReportTrophyData(int context) { Require(details.trophy_grade == 1 && std::strcmp(details.name, "All of them") == 0, "the platinum is not reported"); const auto steps = Trophy(context, 5, &details); Require(details.target.type == 1 && details.target.value == 5 && !steps.unlocked && steps.progress.type == 1 && steps.progress.value == 0, "the progressive trophy does not report its target"); - NpTrophy2Data unknown{}; - Require(ThrowsInvalidArgument([context, &unknown] { sceNpTrophy2GetTrophyInfo(context, 1, 99, nullptr, &unknown); }), "a trophy that is not in the package was reported"); + const auto untouched = [](const void* bytes, std::size_t size) { + const auto* first = static_cast(bytes); + return std::all_of(first, first + size, [](unsigned char byte) { return byte == 0x5a; }); + }; + for (const int unknownId : {8, 99, -1}) { + NpTrophy2Details unknownDetails{}; + NpTrophy2Data unknown{}; + std::memset(&unknownDetails, 0x5a, sizeof(unknownDetails)); + std::memset(&unknown, 0x5a, sizeof(unknown)); + Require(sceNpTrophy2GetTrophyInfo(context, 1, unknownId, &unknownDetails, &unknown) == InvalidTrophyId, "a trophy that is not in the package did not return the invalid trophy id error"); + Require(untouched(&unknownDetails, sizeof(unknownDetails)) && untouched(&unknown, sizeof(unknown)), "a trophy that is not in the package wrote its details or data"); + } std::vector list(10); std::vector listed(10); diff --git a/core/libs/tests/GuestNpTrophyStats.cpp b/core/libs/tests/GuestNpTrophyStats.cpp index a8e94f7516..35511ae5b6 100644 --- a/core/libs/tests/GuestNpTrophyStats.cpp +++ b/core/libs/tests/GuestNpTrophyStats.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" +#include "prx/libSceNpTrophy2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp" #include "prx/libSceNpTrophy2/include/TrophyStore.hpp" @@ -21,26 +23,6 @@ struct NpUniversalDataSystemEvent; struct NpUniversalDataSystemEventPropertyObject; -extern "C" { -int APS5_VABI sceNpUniversalDataSystemCreateContext(int*, int, uint32_t, uint64_t); -int APS5_VABI sceNpUniversalDataSystemRegisterContext(int, int, uint64_t); -int APS5_VABI sceNpUniversalDataSystemCreateHandle(int*); -int APS5_VABI sceNpUniversalDataSystemCreateEvent(const char*, const NpUniversalDataSystemEventPropertyObject*, NpUniversalDataSystemEvent**, NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemDestroyEvent(NpUniversalDataSystemEvent*); -int APS5_VABI sceNpUniversalDataSystemPostEvent(int, int, const void*, uint64_t); -int APS5_VABI sceNpUniversalDataSystemCreateEventPropertyObject(NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemDestroyEventPropertyObject(NpUniversalDataSystemEventPropertyObject*); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetInt32(NpUniversalDataSystemEventPropertyObject*, const char*, int32_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetUInt32(NpUniversalDataSystemEventPropertyObject*, const char*, uint32_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetInt64(NpUniversalDataSystemEventPropertyObject*, const char*, int64_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetUInt64(NpUniversalDataSystemEventPropertyObject*, const char*, uint64_t); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetFloat32(NpUniversalDataSystemEventPropertyObject*, const char*, float); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetObject(NpUniversalDataSystemEventPropertyObject*, const char*, const NpUniversalDataSystemEventPropertyObject*, NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpTrophy2CreateContext(int*, int, uint32_t, uint64_t); -int APS5_VABI sceNpTrophy2GetGameInfo(int, int, NpTrophy2GameDetails*, NpTrophy2GameData*); -int APS5_VABI sceNpTrophy2GetTrophyInfo(int, int, int, NpTrophy2Details*, NpTrophy2Data*); -} - static constexpr int InitialUser = 0x10000000; static const char TropConf[] = R"({"defaultLanguage":"en-US","trophies":[ @@ -63,6 +45,20 @@ static const char StatsDefinition[] = R"({"statDefinitionArray":[ {"statId":10,"dataType":"uint64","aggregation":"latest"},{"statId":20,"dataType":"uint64","aggregation":"latest"}, {"statId":30,"dataType":"uint32","aggregation":"latest"},{"statId":40,"dataType":"uint32","aggregation":"latest"}]})"; +static const char ProgressConf[] = R"({"defaultLanguage":"en-US","trophies":[ + {"id":"0","grade":"P"}, + {"id":"1","grade":"B","platinumTrophyId":"0","unlockCondition":{"udsStatId":"112","comparator":"ge","targetValue":"10","progressive":true}}, + {"id":"2","grade":"B","platinumTrophyId":"0","unlockCondition":{"udsStatId":"113","comparator":"ge","targetValue":"3","progressive":true}} +]})"; + +static const char ProgressDefinition[] = R"({"statDefinitionArray":[ + {"statId":112,"dataType":"int32","aggregation":"max","minValue":"0","initialValue":"0"}, + {"statId":113,"dataType":"int32","aggregation":"max","minValue":"0","initialValue":"0"}]})"; + +static const char ProgressExtraction[] = R"({"statsExtractionRuleArray":[ + {"condition":{"eventName":"_UpdateTrophyProgress","property":{"path":"$._trophy_id","comparator":"==","value":"1"}},"action":{"input":"$._trophy_progress","output":{"statId":112}}}, + {"condition":{"eventName":"_UpdateTrophyProgress","property":{"path":"$._trophy_id","comparator":"==","value":"2"}},"action":{"input":"$._trophy_progress","output":{"statId":113}}}]})"; + static const char SumDefinition[] = R"({"statDefinitionArray":[{"statId":10,"dataType":"uint64","aggregation":"sum"}]})"; static const char StatsExtraction[] = R"({"statsExtractionRuleArray":[ @@ -165,13 +161,13 @@ private: int saved = -1; }; -struct Uds { +struct UdsSession { int context = 0; int handle = 0; }; -static Uds OpenUds(std::uint32_t serviceLabel) { - Uds uds; +static UdsSession OpenUds(std::uint32_t serviceLabel) { + UdsSession uds; Require(sceNpUniversalDataSystemCreateContext(&uds.context, InitialUser, serviceLabel, 0) == 0, "UDS context was not created"); Require(sceNpUniversalDataSystemCreateHandle(&uds.handle) == 0, "UDS handle was not created"); Require(sceNpUniversalDataSystemRegisterContext(uds.context, uds.handle, 0) == 0, "UDS context was not registered"); @@ -179,7 +175,7 @@ static Uds OpenUds(std::uint32_t serviceLabel) { } template -static void Post(const Uds& uds, const char* name, TSet set) { +static void Post(const UdsSession& uds, const char* name, TSet set) { NpUniversalDataSystemEventPropertyObject* properties = nullptr; Require(sceNpUniversalDataSystemCreateEventPropertyObject(&properties) == 0, "property object was not created"); set(properties); @@ -326,11 +322,36 @@ static void IgnoreOtherAggregations(Unlocks& unlocks) { Require(sceNpUniversalDataSystemEventPropertyObjectSetUInt32(properties, "counter", 10) == 0, "counter was not set"); }); const auto log = capture.Finish(); - Require(log.find("[trophy] /app0/sce_sys/uds/uds05.ucp: stat 10 has aggregation \"sum\"; only \"latest\" is supported") != std::string::npos, + Require(log.find("[trophy] /app0/sce_sys/uds/uds05.ucp: stat 10 has aggregation \"sum\"; only \"latest\" and \"max\" are supported") != std::string::npos, "a stat with a sum aggregation was not reported"); Require(!Trophy(context, 1).unlocked && !Exists("_trophy/268435456_5.stats.txt") && unlocks.trophies.size() == 6, "a stat with a sum aggregation was stored or unlocked a trophy"); } +static void RecordTrophyProgress(Unlocks& unlocks) { + const auto context = TrophyContext(6); + const auto uds = OpenUds(6); + const auto progress = [&uds](std::int32_t trophy, std::int32_t value) { + Post(uds, "_UpdateTrophyProgress", [trophy, value](NpUniversalDataSystemEventPropertyObject* properties) { + Require(sceNpUniversalDataSystemEventPropertyObjectSetInt32(properties, "_trophy_id", trophy) == 0, "_trophy_id was not set"); + Require(sceNpUniversalDataSystemEventPropertyObjectSetInt32(properties, "_trophy_progress", value) == 0, "_trophy_progress was not set"); + }); + }; + progress(1, 4); + const auto partway = Trophy(context, 1); + Require(!partway.unlocked && partway.progress.type == 1 && partway.progress.value == 4 && Trophy(context, 2).progress.value == 0, "trophy progress was not recorded for its own trophy"); + progress(1, 2); + Require(Trophy(context, 1).progress.value == 4, "lower trophy progress replaced higher progress"); + progress(2, 3); + Require(Trophy(context, 2).unlocked && !Trophy(context, 1).unlocked && !Trophy(context, 0).unlocked, "trophy progress at its target did not unlock only that trophy"); + progress(1, 12); + const auto done = Trophy(context, 1); + Require(done.unlocked && done.progress.value == 10 && Trophy(context, 0).unlocked, "trophy progress past its target did not unlock the trophy and the platinum"); + Require(Read("_trophy/268435456_6.stats.txt") == "112 12\n113 3\n", "trophy progress was not stored"); + const std::vector> expected{{6, 2}, {6, 1}, {6, 0}}; + Require(unlocks.trophies.size() == 9 && std::vector>(unlocks.trophies.end() - 3, unlocks.trophies.end()) == expected, + "the listener did not see the progress unlocks and the platinum once, in order"); +} + int main() { const auto previous = std::filesystem::current_path(); const auto root = std::filesystem::temp_directory_path() / ("anyps5-trophy-stats-" + std::to_string(std::random_device{}())); @@ -347,6 +368,8 @@ int main() { Write("app0/sce_sys/uds/uds03.ucp", rules.substr(0, rules.size() - 1)); Write("app0/sce_sys/trophy2/trophy05.ucp", trophies); Write("app0/sce_sys/uds/uds05.ucp", Ucp({{"stats_definition.json", SumDefinition}, {"stats_extraction.json", StatsExtraction}})); + Write("app0/sce_sys/trophy2/trophy06.ucp", Ucp({{"tropconf.json", ProgressConf}, {"tropmeta_en-US.json", TropMeta}})); + Write("app0/sce_sys/uds/uds06.ucp", Ucp({{"stats_definition.json", ProgressDefinition}, {"stats_extraction.json", ProgressExtraction}})); Write("_trophy/268435456_0.stats.txt", "40 2\n"); Unlocks unlocks; @@ -356,6 +379,7 @@ int main() { UnlockThroughStats(context, unlocks); IgnoreMissingAndMalformedFiles(unlocks); IgnoreOtherAggregations(unlocks); + RecordTrophyProgress(unlocks); NpTrophy2RemoveUnlockListener_nid_no_patch(listener); std::filesystem::current_path(previous); diff --git a/core/libs/tests/GuestNpTrophyUnlocks.cpp b/core/libs/tests/GuestNpTrophyUnlocks.cpp index be3d6e2859..ba632255da 100644 --- a/core/libs/tests/GuestNpTrophyUnlocks.cpp +++ b/core/libs/tests/GuestNpTrophyUnlocks.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" +#include "prx/libSceNpTrophy2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp" @@ -12,21 +14,6 @@ struct NpUniversalDataSystemEvent; struct NpUniversalDataSystemEventPropertyObject; -extern "C" { -int APS5_VABI sceNpUniversalDataSystemCreateContext(int*, int, uint32_t, uint64_t); -int APS5_VABI sceNpUniversalDataSystemRegisterContext(int, int, uint64_t); -int APS5_VABI sceNpUniversalDataSystemCreateHandle(int*); -int APS5_VABI sceNpUniversalDataSystemCreateEvent(const char*, const NpUniversalDataSystemEventPropertyObject*, NpUniversalDataSystemEvent**, NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemDestroyEvent(NpUniversalDataSystemEvent*); -int APS5_VABI sceNpUniversalDataSystemPostEvent(int, int, const void*, uint64_t); -int APS5_VABI sceNpUniversalDataSystemCreateEventPropertyObject(NpUniversalDataSystemEventPropertyObject**); -int APS5_VABI sceNpUniversalDataSystemDestroyEventPropertyObject(NpUniversalDataSystemEventPropertyObject*); -int APS5_VABI sceNpUniversalDataSystemEventPropertyObjectSetInt32(NpUniversalDataSystemEventPropertyObject*, const char*, int32_t); -int APS5_VABI sceNpTrophy2CreateContext(int*, int, uint32_t, uint64_t); -int APS5_VABI sceNpTrophy2GetGameInfo(int, int, NpTrophy2GameDetails*, NpTrophy2GameData*); -int APS5_VABI sceNpTrophy2GetTrophyInfo(int, int, int, NpTrophy2Details*, NpTrophy2Data*); -} - static constexpr int InitialUser = 0x10000000; static void Require(bool value, const char* message) { diff --git a/core/libs/tests/GuestNpTus.cpp b/core/libs/tests/GuestNpTus.cpp index 11d9cf5cf5..b80d87fa8b 100644 --- a/core/libs/tests/GuestNpTus.cpp +++ b/core/libs/tests/GuestNpTus.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpTus/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include @@ -5,37 +6,6 @@ #include #include -extern "C" { -int APS5_VABI sceNpTusCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId); -int APS5_VABI sceNpTusDeleteNpTitleCtx(int titleCtxId); -int APS5_VABI sceNpTusCreateRequest(int titleCtxId); -int APS5_VABI sceNpTusDeleteRequest(int reqId); -int APS5_VABI sceNpTusAbortRequest(int reqId); -int APS5_VABI sceNpTusPollAsync(int reqId, int* result); -int APS5_VABI sceNpTusGetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusSetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); -int APS5_VABI sceNpTusDeleteMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option); -int APS5_VABI sceNpTusAddAndGetVariableAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); -int APS5_VABI sceNpTusWaitAsync(int reqId, int* result); -int APS5_VABI sceNpTusGetMultiSlotVariableA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusGetMultiSlotVariableAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusGetMultiSlotVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusGetMultiSlotVariableAVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusGetMultiSlotVariableAVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSaveAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSave(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); -int APS5_VABI sceNpTusSetMultiSlotVariableA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); -int APS5_VABI sceNpTusSetMultiSlotVariableAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); -int APS5_VABI sceNpTusSetMultiSlotVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); -int APS5_VABI sceNpTusDeleteMultiSlotVariableA(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option); -int APS5_VABI sceNpTusDeleteMultiSlotVariableAsync(int reqId, const void* targetNpId, const std::int32_t* slotIds, int arrayLen, void* option); -int APS5_VABI sceNpTusDeleteMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, int arrayLen, void* option); -int APS5_VABI sceNpTusDeleteMultiSlotVariableVUserAsync(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, int arrayLen, void* option); -int APS5_VABI sceNpTusDeleteMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, int arrayLen, void* option); -} - static constexpr int SIGNED_OUT = static_cast(0x80550006); static constexpr int INVALID_ARGUMENT = static_cast(0x80550704); static constexpr int TOO_MANY_OBJECTS = static_cast(0x80550706); @@ -51,6 +21,34 @@ static void RequireFinished(int reqId, int expected) { Require(sceNpTusPollAsync(reqId, &result) == 0 && result == expected); } +static void CheckTssTitleContexts() { + const int shared = sceNpTssCreateNpTitleCtxA(0, 1); + Require(shared > 0); + const int request = sceNpTusCreateRequest(shared); + Require(request > 0); + Require(sceNpTusDeleteNpTitleCtx(shared) == 0); + Require(sceNpTusDeleteNpTitleCtx(shared) == INVALID_ID); + Require(sceNpTusDeleteRequest(request) == INVALID_ID); + + const NpId npId{}; + Require(sceNpTssCreateNpTitleCtx(0, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTssCreateNpTitleCtx(0xFFFFFFFFu, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTssCreateNpTitleCtx(0xFFFFFFFFu, &npId) == INVALID_ARGUMENT); + + std::array contexts{}; + for (std::size_t index = 0; index < contexts.size(); ++index) { + const std::size_t form = index % 3; + contexts[index] = form == 0 ? sceNpTusCreateNpTitleCtxA(0, 1) : form == 1 ? sceNpTssCreateNpTitleCtxA(0, 1) : sceNpTssCreateNpTitleCtx(0, &npId); + Require(contexts[index] > 0); + } + Require(sceNpTssCreateNpTitleCtxA(0, 1) == TOO_MANY_OBJECTS); + Require(sceNpTssCreateNpTitleCtx(0, &npId) == TOO_MANY_OBJECTS); + Require(sceNpTusCreateNpTitleCtxA(0, 1) == TOO_MANY_OBJECTS); + Require(sceNpTssCreateNpTitleCtx(0, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTssCreateNpTitleCtx(0xFFFFFFFFu, &npId) == INVALID_ARGUMENT); + for (const int context : contexts) Require(sceNpTusDeleteNpTitleCtx(context) == 0); +} + static void CheckMultiSlotVariables() { const int context = sceNpTusCreateNpTitleCtxA(0, 1); Require(context > 0); @@ -271,4 +269,5 @@ int main() { Require(sceNpTusCreateRequest(contexts[1]) > 0); for (const int child : children) Require(sceNpTusPollAsync(child, &result) == INVALID_ID); for (std::size_t index = 1; index < contexts.size(); ++index) Require(sceNpTusDeleteNpTitleCtx(contexts[index]) == 0); + CheckTssTitleContexts(); } diff --git a/core/libs/tests/GuestNpTusOffline.cpp b/core/libs/tests/GuestNpTusOffline.cpp new file mode 100644 index 0000000000..d4157cb607 --- /dev/null +++ b/core/libs/tests/GuestNpTusOffline.cpp @@ -0,0 +1,200 @@ +#include "prx/libSceNpTus/libSceNpTus.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +namespace { + +constexpr int SignedOut = static_cast(0x80550006u); +constexpr int InvalidArgument = static_cast(0x80550704u); +constexpr int InvalidId = static_cast(0x8055070Eu); +constexpr int InvalidType = static_cast(0x80550711u); +constexpr int TooManySlotId = static_cast(0x80550718u); +constexpr int TooManyNpId = static_cast(0x80550719u); + +int context = 0; +alignas(8) unsigned char buffer[1024]; +unsigned char npId[64]; +NpOnlineId virtualUser{}; +NpOnlineId virtualUsers[2]{}; +NpOnlineId emptyUser{}; +unsigned char npIds[2][64]; +std::uint64_t accounts[2] = {1, 2}; +std::int32_t slots[2] = {1, 2}; +std::int64_t variables[2] = {5, 6}; +std::int32_t manySlots[65] = {}; +std::uint64_t length = 0; +int failures = 0; + +void Require(bool value, const char* name, const char* what) { + if (value) return; + std::fprintf(stderr, "%s: %s\n", name, what); + ++failures; +} + +int NewRequest() { + const int req = sceNpTusCreateRequest(context); + if (req <= 0) { + std::fprintf(stderr, "sceNpTusCreateRequest failed\n"); + std::abort(); + } + return req; +} + +void CheckAsync(const char* name, const std::function& valid, const std::function& unknownRequest, const std::function& withOption) { + int req = NewRequest(); + Require(valid(req) == 0, name, "valid arguments did not start the request"); + int result = 0; + Require(sceNpTusWaitAsync(req, &result) == 0 && result == SignedOut, name, "the request did not finish signed out"); + sceNpTusDeleteRequest(req); + + Require(unknownRequest() == InvalidId, name, "an unknown request was not refused with INVALID_ID"); + if (withOption) { + req = NewRequest(); + Require(withOption(req) == InvalidArgument, name, "a non-null option was not refused with INVALID_ARGUMENT"); + sceNpTusDeleteRequest(req); + + } +} + +void CheckSync(const char* name, const std::function& call) { + const int req = NewRequest(); + Require(call(req) == SignedOut, name, "the synchronous call did not return SIGNED_OUT"); + sceNpTusDeleteRequest(req); + +} + +} + +int main() { + std::memset(npId, 'n', sizeof(npId)); + std::memset(npIds, 'n', sizeof(npIds)); + std::strcpy(virtualUser.data, "virtual"); + std::strcpy(virtualUsers[0].data, "first"); + std::strcpy(virtualUsers[1].data, "second"); + context = sceNpTusCreateNpTitleCtxA(1, 1); + if (context <= 0) return 1; + CheckAsync("sceNpTssGetSmallStorageAsync", [&](int req) { return sceNpTssGetSmallStorageAsync(req, buffer, 16, &length, nullptr); }, [&]() { int req = 9999; return sceNpTssGetSmallStorageAsync(9999, buffer, 16, &length, nullptr); }, [&](int req) { return sceNpTssGetSmallStorageAsync(req, buffer, 16, &length, &buffer); }); + CheckAsync("sceNpTusAddAndGetVariableAVUserAsync", [&](int req) { return sceNpTusAddAndGetVariableAVUserAsync(req, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusAddAndGetVariableAVUserAsync(9999, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusAddAndGetVariableAVUserAsync(req, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusAddAndGetVariableAsync", [&](int req) { return sceNpTusAddAndGetVariableAsync(req, &npId, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusAddAndGetVariableAsync(9999, &npId, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusAddAndGetVariableAsync(req, &npId, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusAddAndGetVariableForCrossSaveAsync", [&](int req) { return sceNpTusAddAndGetVariableForCrossSaveAsync(req, 1, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusAddAndGetVariableForCrossSaveAsync(9999, 1, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusAddAndGetVariableForCrossSaveAsync(req, 1, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusAddAndGetVariableForCrossSaveVUserAsync", [&](int req) { return sceNpTusAddAndGetVariableForCrossSaveVUserAsync(req, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusAddAndGetVariableForCrossSaveVUserAsync(9999, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusAddAndGetVariableForCrossSaveVUserAsync(req, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusAddAndGetVariableVUserAsync", [&](int req) { return sceNpTusAddAndGetVariableVUserAsync(req, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusAddAndGetVariableVUserAsync(9999, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusAddAndGetVariableVUserAsync(req, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusDeleteMultiSlotDataAAsync", [&](int req) { return sceNpTusDeleteMultiSlotDataAAsync(req, 1, slots, 2, nullptr); }, [&]() { int req = 9999; return sceNpTusDeleteMultiSlotDataAAsync(9999, 1, slots, 2, nullptr); }, [&](int req) { return sceNpTusDeleteMultiSlotDataAAsync(req, 1, slots, 2, &buffer); }); + CheckAsync("sceNpTusDeleteMultiSlotDataAsync", [&](int req) { return sceNpTusDeleteMultiSlotDataAsync(req, &npId, slots, 2, nullptr); }, [&]() { int req = 9999; return sceNpTusDeleteMultiSlotDataAsync(9999, &npId, slots, 2, nullptr); }, [&](int req) { return sceNpTusDeleteMultiSlotDataAsync(req, &npId, slots, 2, &buffer); }); + CheckAsync("sceNpTusGetDataAAsync", [&](int req) { return sceNpTusGetDataAAsync(req, 1, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&]() { int req = 9999; return sceNpTusGetDataAAsync(9999, 1, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&](int req) { return sceNpTusGetDataAAsync(req, 1, 1, buffer, sizeof(buffer), buffer, 16, &buffer); }); + CheckAsync("sceNpTusGetDataAVUserAsync", [&](int req) { return sceNpTusGetDataAVUserAsync(req, &virtualUser, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&]() { int req = 9999; return sceNpTusGetDataAVUserAsync(9999, &virtualUser, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&](int req) { return sceNpTusGetDataAVUserAsync(req, &virtualUser, 1, buffer, sizeof(buffer), buffer, 16, &buffer); }); + CheckAsync("sceNpTusGetDataAsync", [&](int req) { return sceNpTusGetDataAsync(req, &npId, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&]() { int req = 9999; return sceNpTusGetDataAsync(9999, &npId, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&](int req) { return sceNpTusGetDataAsync(req, &npId, 1, buffer, sizeof(buffer), buffer, 16, &buffer); }); + CheckAsync("sceNpTusGetDataForCrossSaveAsync", [&](int req) { return sceNpTusGetDataForCrossSaveAsync(req, 1, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&]() { int req = 9999; return sceNpTusGetDataForCrossSaveAsync(9999, 1, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&](int req) { return sceNpTusGetDataForCrossSaveAsync(req, 1, 1, buffer, sizeof(buffer), buffer, 16, &buffer); }); + CheckAsync("sceNpTusGetDataVUserAsync", [&](int req) { return sceNpTusGetDataVUserAsync(req, &virtualUser, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&]() { int req = 9999; return sceNpTusGetDataVUserAsync(9999, &virtualUser, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }, [&](int req) { return sceNpTusGetDataVUserAsync(req, &virtualUser, 1, buffer, sizeof(buffer), buffer, 16, &buffer); }); + CheckAsync("sceNpTusGetFriendsDataStatusAsync", [&](int req) { return sceNpTusGetFriendsDataStatusAsync(req, 1, 0, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetFriendsDataStatusAsync(9999, 1, 0, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetFriendsDataStatusAsync(req, 1, 0, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetFriendsVariableAsync", [&](int req) { return sceNpTusGetFriendsVariableAsync(req, 1, 0, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetFriendsVariableAsync(9999, 1, 0, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetFriendsVariableAsync(req, 1, 0, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiSlotDataStatusAAsync", [&](int req) { return sceNpTusGetMultiSlotDataStatusAAsync(req, 1, slots, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiSlotDataStatusAAsync(9999, 1, slots, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiSlotDataStatusAAsync(req, 1, slots, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiSlotDataStatusAVUserAsync", [&](int req) { return sceNpTusGetMultiSlotDataStatusAVUserAsync(req, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiSlotDataStatusAVUserAsync(9999, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiSlotDataStatusAVUserAsync(req, &virtualUser, slots, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiSlotDataStatusAsync", [&](int req) { return sceNpTusGetMultiSlotDataStatusAsync(req, &npId, slots, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiSlotDataStatusAsync(9999, &npId, slots, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiSlotDataStatusAsync(req, &npId, slots, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiSlotDataStatusForCrossSaveAsync", [&](int req) { return sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(req, 1, slots, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(9999, 1, slots, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(req, 1, slots, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync", [&](int req) { return sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(req, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(9999, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(req, &virtualUser, slots, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiSlotDataStatusVUserAsync", [&](int req) { return sceNpTusGetMultiSlotDataStatusVUserAsync(req, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiSlotDataStatusVUserAsync(9999, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiSlotDataStatusVUserAsync(req, &virtualUser, slots, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserDataStatusAAsync", [&](int req) { return sceNpTusGetMultiUserDataStatusAAsync(req, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserDataStatusAAsync(9999, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserDataStatusAAsync(req, accounts, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserDataStatusAVUserAsync", [&](int req) { return sceNpTusGetMultiUserDataStatusAVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserDataStatusAVUserAsync(9999, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserDataStatusAVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserDataStatusAsync", [&](int req) { return sceNpTusGetMultiUserDataStatusAsync(req, npIds, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserDataStatusAsync(9999, npIds, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserDataStatusAsync(req, npIds, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserDataStatusForCrossSaveAsync", [&](int req) { return sceNpTusGetMultiUserDataStatusForCrossSaveAsync(req, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserDataStatusForCrossSaveAsync(9999, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserDataStatusForCrossSaveAsync(req, accounts, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync", [&](int req) { return sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(9999, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserDataStatusVUserAsync", [&](int req) { return sceNpTusGetMultiUserDataStatusVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserDataStatusVUserAsync(9999, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserDataStatusVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserVariableAAsync", [&](int req) { return sceNpTusGetMultiUserVariableAAsync(req, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserVariableAAsync(9999, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserVariableAAsync(req, accounts, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserVariableAVUserAsync", [&](int req) { return sceNpTusGetMultiUserVariableAVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserVariableAVUserAsync(9999, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserVariableAVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserVariableAsync", [&](int req) { return sceNpTusGetMultiUserVariableAsync(req, npIds, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserVariableAsync(9999, npIds, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserVariableAsync(req, npIds, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserVariableForCrossSaveAsync", [&](int req) { return sceNpTusGetMultiUserVariableForCrossSaveAsync(req, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserVariableForCrossSaveAsync(9999, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserVariableForCrossSaveAsync(req, accounts, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserVariableForCrossSaveVUserAsync", [&](int req) { return sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(9999, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusGetMultiUserVariableVUserAsync", [&](int req) { return sceNpTusGetMultiUserVariableVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&]() { int req = 9999; return sceNpTusGetMultiUserVariableVUserAsync(9999, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }, [&](int req) { return sceNpTusGetMultiUserVariableVUserAsync(req, virtualUsers, 1, buffer, sizeof(buffer), 2, &buffer); }); + CheckAsync("sceNpTusSetDataAAsync", [&](int req) { return sceNpTusSetDataAAsync(req, 1, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }, [&]() { int req = 9999; return sceNpTusSetDataAAsync(9999, 1, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }, [&](int req) { return sceNpTusSetDataAAsync(req, 1, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, &buffer); }); + CheckAsync("sceNpTusSetDataAVUserAsync", [&](int req) { return sceNpTusSetDataAVUserAsync(req, &virtualUser, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }, [&]() { int req = 9999; return sceNpTusSetDataAVUserAsync(9999, &virtualUser, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }, [&](int req) { return sceNpTusSetDataAVUserAsync(req, &virtualUser, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, &buffer); }); + CheckAsync("sceNpTusSetDataAsync", [&](int req) { return sceNpTusSetDataAsync(req, &npId, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }, [&]() { int req = 9999; return sceNpTusSetDataAsync(9999, &npId, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }, [&](int req) { return sceNpTusSetDataAsync(req, &npId, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, &buffer); }); + CheckAsync("sceNpTusSetDataVUserAsync", [&](int req) { return sceNpTusSetDataVUserAsync(req, &virtualUser, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }, [&]() { int req = 9999; return sceNpTusSetDataVUserAsync(9999, &virtualUser, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }, [&](int req) { return sceNpTusSetDataVUserAsync(req, &virtualUser, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, &buffer); }); + CheckAsync("sceNpTusTryAndSetVariableAAsync", [&](int req) { return sceNpTusTryAndSetVariableAAsync(req, 1, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusTryAndSetVariableAAsync(9999, 1, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusTryAndSetVariableAAsync(req, 1, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusTryAndSetVariableAVUserAsync", [&](int req) { return sceNpTusTryAndSetVariableAVUserAsync(req, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusTryAndSetVariableAVUserAsync(9999, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusTryAndSetVariableAVUserAsync(req, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusTryAndSetVariableAsync", [&](int req) { return sceNpTusTryAndSetVariableAsync(req, &npId, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusTryAndSetVariableAsync(9999, &npId, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusTryAndSetVariableAsync(req, &npId, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusTryAndSetVariableForCrossSaveAsync", [&](int req) { return sceNpTusTryAndSetVariableForCrossSaveAsync(req, 1, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusTryAndSetVariableForCrossSaveAsync(9999, 1, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusTryAndSetVariableForCrossSaveAsync(req, 1, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusTryAndSetVariableForCrossSaveVUserAsync", [&](int req) { return sceNpTusTryAndSetVariableForCrossSaveVUserAsync(req, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusTryAndSetVariableForCrossSaveVUserAsync(9999, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusTryAndSetVariableForCrossSaveVUserAsync(req, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckAsync("sceNpTusTryAndSetVariableVUserAsync", [&](int req) { return sceNpTusTryAndSetVariableVUserAsync(req, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&]() { int req = 9999; return sceNpTusTryAndSetVariableVUserAsync(9999, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }, [&](int req) { return sceNpTusTryAndSetVariableVUserAsync(req, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), &buffer); }); + CheckSync("sceNpTssGetSmallStorage", [&](int req) { return sceNpTssGetSmallStorage(req, buffer, 16, &length, nullptr); }); + CheckSync("sceNpTusAddAndGetVariable", [&](int req) { return sceNpTusAddAndGetVariable(req, &npId, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusAddAndGetVariableA", [&](int req) { return sceNpTusAddAndGetVariableA(req, 1, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusAddAndGetVariableAVUser", [&](int req) { return sceNpTusAddAndGetVariableAVUser(req, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusAddAndGetVariableForCrossSave", [&](int req) { return sceNpTusAddAndGetVariableForCrossSave(req, 1, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusAddAndGetVariableForCrossSaveVUser", [&](int req) { return sceNpTusAddAndGetVariableForCrossSaveVUser(req, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusAddAndGetVariableVUser", [&](int req) { return sceNpTusAddAndGetVariableVUser(req, &virtualUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusDeleteMultiSlotData", [&](int req) { return sceNpTusDeleteMultiSlotData(req, &npId, slots, 2, nullptr); }); + CheckSync("sceNpTusDeleteMultiSlotDataA", [&](int req) { return sceNpTusDeleteMultiSlotDataA(req, 1, slots, 2, nullptr); }); + CheckSync("sceNpTusGetData", [&](int req) { return sceNpTusGetData(req, &npId, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }); + CheckSync("sceNpTusGetDataA", [&](int req) { return sceNpTusGetDataA(req, 1, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }); + CheckSync("sceNpTusGetDataAVUser", [&](int req) { return sceNpTusGetDataAVUser(req, &virtualUser, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }); + CheckSync("sceNpTusGetDataForCrossSave", [&](int req) { return sceNpTusGetDataForCrossSave(req, 1, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }); + CheckSync("sceNpTusGetDataVUser", [&](int req) { return sceNpTusGetDataVUser(req, &virtualUser, 1, buffer, sizeof(buffer), buffer, 16, nullptr); }); + CheckSync("sceNpTusGetFriendsDataStatus", [&](int req) { return sceNpTusGetFriendsDataStatus(req, 1, 0, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetFriendsVariable", [&](int req) { return sceNpTusGetFriendsVariable(req, 1, 0, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiSlotDataStatus", [&](int req) { return sceNpTusGetMultiSlotDataStatus(req, &npId, slots, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiSlotDataStatusA", [&](int req) { return sceNpTusGetMultiSlotDataStatusA(req, 1, slots, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiSlotDataStatusAVUser", [&](int req) { return sceNpTusGetMultiSlotDataStatusAVUser(req, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiSlotDataStatusForCrossSave", [&](int req) { return sceNpTusGetMultiSlotDataStatusForCrossSave(req, 1, slots, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiSlotDataStatusForCrossSaveVUser", [&](int req) { return sceNpTusGetMultiSlotDataStatusForCrossSaveVUser(req, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiSlotDataStatusVUser", [&](int req) { return sceNpTusGetMultiSlotDataStatusVUser(req, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiSlotVariable", [&](int req) { return sceNpTusGetMultiSlotVariable(req, &npId, slots, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiSlotVariableVUser", [&](int req) { return sceNpTusGetMultiSlotVariableVUser(req, &virtualUser, slots, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserDataStatus", [&](int req) { return sceNpTusGetMultiUserDataStatus(req, npIds, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserDataStatusA", [&](int req) { return sceNpTusGetMultiUserDataStatusA(req, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserDataStatusAVUser", [&](int req) { return sceNpTusGetMultiUserDataStatusAVUser(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserDataStatusForCrossSave", [&](int req) { return sceNpTusGetMultiUserDataStatusForCrossSave(req, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserDataStatusForCrossSaveVUser", [&](int req) { return sceNpTusGetMultiUserDataStatusForCrossSaveVUser(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserDataStatusVUser", [&](int req) { return sceNpTusGetMultiUserDataStatusVUser(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserVariable", [&](int req) { return sceNpTusGetMultiUserVariable(req, npIds, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserVariableA", [&](int req) { return sceNpTusGetMultiUserVariableA(req, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserVariableAVUser", [&](int req) { return sceNpTusGetMultiUserVariableAVUser(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserVariableForCrossSave", [&](int req) { return sceNpTusGetMultiUserVariableForCrossSave(req, accounts, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserVariableForCrossSaveVUser", [&](int req) { return sceNpTusGetMultiUserVariableForCrossSaveVUser(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusGetMultiUserVariableVUser", [&](int req) { return sceNpTusGetMultiUserVariableVUser(req, virtualUsers, 1, buffer, sizeof(buffer), 2, nullptr); }); + CheckSync("sceNpTusSetData", [&](int req) { return sceNpTusSetData(req, &npId, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }); + CheckSync("sceNpTusSetDataA", [&](int req) { return sceNpTusSetDataA(req, 1, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }); + CheckSync("sceNpTusSetDataAVUser", [&](int req) { return sceNpTusSetDataAVUser(req, &virtualUser, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }); + CheckSync("sceNpTusSetDataVUser", [&](int req) { return sceNpTusSetDataVUser(req, &virtualUser, 1, 16, 8, buffer, buffer, sizeof(buffer), nullptr, nullptr, nullptr); }); + CheckSync("sceNpTusSetMultiSlotVariable", [&](int req) { return sceNpTusSetMultiSlotVariable(req, &npId, slots, variables, 2, nullptr); }); + CheckSync("sceNpTusSetMultiSlotVariableVUser", [&](int req) { return sceNpTusSetMultiSlotVariableVUser(req, &virtualUser, slots, variables, 2, nullptr); }); + CheckSync("sceNpTusTryAndSetVariable", [&](int req) { return sceNpTusTryAndSetVariable(req, &npId, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusTryAndSetVariableA", [&](int req) { return sceNpTusTryAndSetVariableA(req, 1, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusTryAndSetVariableAVUser", [&](int req) { return sceNpTusTryAndSetVariableAVUser(req, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusTryAndSetVariableForCrossSave", [&](int req) { return sceNpTusTryAndSetVariableForCrossSave(req, 1, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusTryAndSetVariableForCrossSaveVUser", [&](int req) { return sceNpTusTryAndSetVariableForCrossSaveVUser(req, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + CheckSync("sceNpTusTryAndSetVariableVUser", [&](int req) { return sceNpTusTryAndSetVariableVUser(req, &virtualUser, 1, 1, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr); }); + { + const int req = NewRequest(); + Require(sceNpTusGetData(req, &npId, 1, buffer, sizeof(buffer), buffer, 16, &buffer) == InvalidArgument, "sceNpTusGetData", "an option was not refused before waiting"); + Require(sceNpTusSetMultiSlotVariable(req, &npId, manySlots, reinterpret_cast(buffer), 65, nullptr) == TooManySlotId, "sceNpTusSetMultiSlotVariable", "65 slots were not refused before waiting"); + sceNpTusDeleteRequest(req); + } + { + int req = NewRequest(); + Require(sceNpTusAddAndGetVariableVUserAsync(req, &emptyUser, 1, 5, nullptr, nullptr, buffer, sizeof(buffer), nullptr) == InvalidArgument, "sceNpTusAddAndGetVariableVUserAsync", "an empty virtual user was taken"); + NpOnlineId partlyEmpty[2]{}; + std::strcpy(partlyEmpty[0].data, "first"); + Require(sceNpTusGetMultiUserVariableVUserAsync(req, partlyEmpty, 1, buffer, sizeof(buffer), 2, nullptr) == InvalidArgument, "sceNpTusGetMultiUserVariableVUserAsync", "an empty virtual user in the array was taken"); + Require(sceNpTusTryAndSetVariableAAsync(req, 1, 1, 0, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr) == InvalidType, "sceNpTusTryAndSetVariableAAsync", "operation type 0 was taken"); + Require(sceNpTusTryAndSetVariableAAsync(req, 1, 1, 7, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr) == InvalidType, "sceNpTusTryAndSetVariableAAsync", "operation type 7 was taken"); + Require(sceNpTusTryAndSetVariableAsync(req, npId, 1, 7, 5, nullptr, nullptr, nullptr, buffer, sizeof(buffer), nullptr) == InvalidArgument, "sceNpTusTryAndSetVariableAsync", "operation type 7 was not refused with INVALID_ARGUMENT"); + Require(sceNpTssGetSmallStorageAsync(req, buffer, 64 * 1024 + 1, &length, nullptr) == InvalidArgument, "sceNpTssGetSmallStorageAsync", "a size above 64 KiB was taken"); + Require(sceNpTusDeleteMultiSlotDataAAsync(req, 1, manySlots, 65, nullptr) == TooManySlotId, "sceNpTusDeleteMultiSlotDataAAsync", "65 slots were taken"); + Require(sceNpTusGetMultiSlotDataStatusAsync(req, npId, manySlots, buffer, sizeof(buffer), 65, nullptr) == TooManyNpId, "sceNpTusGetMultiSlotDataStatusAsync", "65 slots did not return TOO_MANY_NPID"); + const std::int32_t negative[2] = {1, -1}; + Require(sceNpTusDeleteMultiSlotDataAAsync(req, 1, negative, 2, nullptr) == InvalidArgument, "sceNpTusDeleteMultiSlotDataAAsync", "a negative slot id was taken"); + Require(sceNpTusGetMultiUserVariableAAsync(req, accounts, 1, buffer, sizeof(buffer), 102, nullptr) == TooManyNpId, "sceNpTusGetMultiUserVariableAAsync", "102 users were taken"); + Require(sceNpTusGetFriendsVariableAsync(req, 1, 0, 5, buffer, sizeof(buffer), 2, nullptr) == InvalidArgument, "sceNpTusGetFriendsVariableAsync", "sort type 5 was taken"); + Require(sceNpTusGetFriendsVariableAsync(req, 1, 0, 1, buffer, sizeof(buffer), 101, nullptr) == TooManyNpId, "sceNpTusGetFriendsVariableAsync", "101 friends were taken"); + sceNpTusDeleteRequest(req); + + } + if (failures != 0) return 1; + std::puts("NpTus offline request tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestNpUtilityOffline.cpp b/core/libs/tests/GuestNpUtilityOffline.cpp new file mode 100644 index 0000000000..b8879b9256 --- /dev/null +++ b/core/libs/tests/GuestNpUtilityOffline.cpp @@ -0,0 +1,230 @@ +#include "prx/libSceNpUtility/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int SignedOut = static_cast(0x80550006u); +constexpr int InvalidArgument = static_cast(0x80550704u); +constexpr int TooManyObjects = static_cast(0x80550706u); +constexpr int Aborted = static_cast(0x80550707u); +constexpr int InsufficientArgument = static_cast(0x8055070Cu); +constexpr int InvalidId = static_cast(0x8055070Eu); +constexpr int InvalidType = static_cast(0x80550711u); +constexpr int LookupSynchronousRequest = static_cast(0x80550307u); +constexpr int PollFinished = 0; +constexpr int PollRunning = 1; +constexpr int WordFilterTitleCtxBase = 0x01000000; +constexpr int WordFilterRequestBase = 0x02000000; + +int failures = 0; +unsigned char selfNpId[36]; +unsigned char npId[36]; +char sanitized[0x402]; +char longComment[0x402]; +const std::uint64_t asyncParam[3] = {0x18, 0, 0}; +const std::uint64_t wrongAsyncParam[3] = {0x10, 0, 0}; + +void Require(bool value, const char* name, const char* what) { + if (value) return; + std::fprintf(stderr, "%s: %s\n", name, what); + ++failures; +} + +void CheckLookup() { + NpOnlineId onlineId{}; + std::strcpy(onlineId.data, "player_1"); + Require(sceNpLookupCreateTitleCtx(nullptr) == InsufficientArgument, "sceNpLookupCreateTitleCtx", "a null NpId was taken"); + const int context = sceNpLookupCreateTitleCtx(selfNpId); + Require(context == 1, "sceNpLookupCreateTitleCtx", "the first lookup context is not id 1"); + Require(sceNpLookupCreateRequest(context + 100) == InvalidId, "sceNpLookupCreateRequest", "an unknown context was taken"); + const int req = sceNpLookupCreateRequest(context); + Require(req == 1, "sceNpLookupCreateRequest", "the first lookup request is not id 1"); + std::memset(npId, 0xA5, sizeof(npId)); + Require(sceNpLookupNpId(req, nullptr, nullptr, &onlineId) == InvalidArgument, "sceNpLookupNpId", "an option was not refused before the null arguments"); + Require(sceNpLookupNpId(req, nullptr, npId, nullptr) == InsufficientArgument, "sceNpLookupNpId", "a null online ID was taken"); + Require(sceNpLookupNpId(req, &onlineId, nullptr, nullptr) == InsufficientArgument, "sceNpLookupNpId", "a null NpId output was taken"); + Require(sceNpLookupNpId(req + 100, &onlineId, npId, nullptr) == InvalidId, "sceNpLookupNpId", "an unknown request was taken"); + Require(sceNpLookupNpId(req, &onlineId, npId, nullptr) == SignedOut, "sceNpLookupNpId", "the lookup did not return SIGNED_OUT"); + Require(sceNpLookupNpId(req, &onlineId, npId, nullptr) == InvalidType, "sceNpLookupNpId", "a used request did not return INVALID_TYPE"); + Require(npId[0] == 0xA5 && npId[35] == 0xA5, "sceNpLookupNpId", "an NpId was written"); + Require(sceNpLookupDeleteRequest(req) == 0, "sceNpLookupDeleteRequest", "deleting the request failed"); + Require(sceNpLookupDeleteRequest(req) == InvalidId, "sceNpLookupDeleteRequest", "a deleted request was found"); + int requests[32]; + for (int& request : requests) request = sceNpLookupCreateRequest(context); + Require(requests[31] > 0, "sceNpLookupCreateRequest", "32 requests could not be created"); + Require(sceNpLookupCreateRequest(context) == TooManyObjects, "sceNpLookupCreateRequest", "a 33rd request did not return TOO_MANY_OBJECTS"); + Require(sceNpLookupDeleteTitleCtx(context) == 0, "sceNpLookupDeleteTitleCtx", "deleting the context failed"); + Require(sceNpLookupDeleteRequest(requests[0]) == InvalidId, "sceNpLookupDeleteTitleCtx", "a request of the deleted context was kept"); + Require(sceNpLookupDeleteTitleCtx(context) == InvalidId, "sceNpLookupDeleteTitleCtx", "a deleted context was found"); + int contexts[32]; + for (int& entry : contexts) entry = sceNpLookupCreateTitleCtx(selfNpId); + Require(contexts[0] == 2 && contexts[31] == 33, "sceNpLookupCreateTitleCtx", "lookup context ids do not keep counting from 2"); + Require(sceNpLookupCreateTitleCtx(selfNpId) == TooManyObjects, "sceNpLookupCreateTitleCtx", "a 33rd context did not return TOO_MANY_OBJECTS"); + for (const int entry : contexts) sceNpLookupDeleteTitleCtx(entry); +} + +void CheckWordFilter() { + Require(sceNpWordFilterCreateTitleCtx(nullptr) == InsufficientArgument, "sceNpWordFilterCreateTitleCtx", "a null NpId was taken"); + const int context = sceNpWordFilterCreateTitleCtx(selfNpId); + Require(context == WordFilterTitleCtxBase + 1, "sceNpWordFilterCreateTitleCtx", "the first word filter context is not 0x01000001"); + Require(sceNpWordFilterCreateAsyncRequest(context, nullptr) == InvalidArgument, "sceNpWordFilterCreateAsyncRequest", "a null parameter was taken"); + Require(sceNpWordFilterCreateAsyncRequest(context, wrongAsyncParam) == InvalidArgument, "sceNpWordFilterCreateAsyncRequest", "a parameter size other than 0x18 was taken"); + Require(sceNpWordFilterCreateAsyncRequest(context + 100, asyncParam) == InvalidId, "sceNpWordFilterCreateAsyncRequest", "an unknown context was taken"); + const int sync = sceNpWordFilterCreateRequest(context); + Require(sync == WordFilterRequestBase + 1, "sceNpWordFilterCreateRequest", "the first word filter request is not 0x02000001"); + std::memset(longComment, 'a', 0x400); + longComment[0x400] = 0; + Require(sceNpWordFilterCensorComment(sync, nullptr, &sanitized) == InsufficientArgument, "sceNpWordFilterCensorComment", "a null comment was not refused before the option"); + Require(sceNpWordFilterCensorComment(sync, "word", &sanitized) == InvalidArgument, "sceNpWordFilterCensorComment", "an option was taken"); + longComment[0x400] = 'a'; + Require(sceNpWordFilterCensorComment(sync, longComment, nullptr) == InvalidArgument, "sceNpWordFilterCensorComment", "a 0x401-byte comment was taken"); + Require(sceNpWordFilterCensorComment(sync + 100, "word", nullptr) == InvalidId, "sceNpWordFilterCensorComment", "an unknown request was taken"); + longComment[0x400] = 0; + Require(sceNpWordFilterCensorComment(sync, longComment, nullptr) == SignedOut, "sceNpWordFilterCensorComment", "a 0x400-byte comment did not return SIGNED_OUT"); + Require(sceNpWordFilterCensorComment(sync, "word", nullptr) == InvalidType, "sceNpWordFilterCensorComment", "a used request did not return INVALID_TYPE"); + int result = 7; + Require(sceNpWordFilterPollAsync(sync, &result) == InvalidId && result == 7, "sceNpWordFilterPollAsync", "a synchronous request was polled"); + Require(sceNpWordFilterDeleteRequest(sync) == 0, "sceNpWordFilterDeleteRequest", "deleting the request failed"); + + const int async = sceNpWordFilterCreateAsyncRequest(context, asyncParam); + Require(async == WordFilterRequestBase + 2, "sceNpWordFilterCreateAsyncRequest", "the asynchronous request did not take the next id"); + Require(sceNpWordFilterPollAsync(async, nullptr) == InvalidArgument, "sceNpWordFilterPollAsync", "a null result was taken"); + Require(sceNpWordFilterPollAsync(async + 100, &result) == InvalidId, "sceNpWordFilterPollAsync", "an unknown request was taken"); + Require(sceNpWordFilterPollAsync(async, &result) == PollRunning && result == 7, "sceNpWordFilterPollAsync", "an unstarted request was not running"); + std::memset(sanitized, 'z', sizeof(sanitized)); + Require(sceNpWordFilterSanitizeComment(async, "word", nullptr, nullptr) == InsufficientArgument, "sceNpWordFilterSanitizeComment", "a null output was taken"); + Require(sceNpWordFilterSanitizeComment(async, nullptr, sanitized, &result) == InsufficientArgument, "sceNpWordFilterSanitizeComment", "a null comment was not refused before the option"); + Require(sceNpWordFilterSanitizeComment(async, "word", sanitized, &result) == InvalidArgument, "sceNpWordFilterSanitizeComment", "an option was taken"); + Require(sceNpWordFilterSanitizeComment(async, "word", sanitized, nullptr) == 0, "sceNpWordFilterSanitizeComment", "the asynchronous request did not start"); + Require(sceNpWordFilterPollAsync(async, &result) == PollFinished && result == SignedOut, "sceNpWordFilterPollAsync", "the request did not finish with SIGNED_OUT"); + Require(sanitized[0] == 'z' && sanitized[4] == 'z', "sceNpWordFilterSanitizeComment", "a sanitized comment was written"); + Require(sceNpWordFilterAbortRequest(async) == 0, "sceNpWordFilterAbortRequest", "aborting a finished request failed"); + Require(sceNpWordFilterPollAsync(async, &result) == PollFinished && result == SignedOut, "sceNpWordFilterAbortRequest", "aborting a finished request changed its result"); + + const int aborted = sceNpWordFilterCreateAsyncRequest(context, asyncParam); + Require(sceNpWordFilterAbortRequest(aborted + 100) == InvalidId, "sceNpWordFilterAbortRequest", "an unknown request was taken"); + Require(sceNpWordFilterAbortRequest(aborted) == 0, "sceNpWordFilterAbortRequest", "aborting failed"); + Require(sceNpWordFilterPollAsync(aborted, &result) == PollFinished && result == Aborted, "sceNpWordFilterAbortRequest", "an aborted request did not finish with ABORTED"); + Require(sceNpWordFilterCensorComment(aborted, "word", nullptr) == Aborted, "sceNpWordFilterCensorComment", "an aborted request did not return ABORTED"); + + const int kept = sceNpWordFilterCreateRequest(context); + Require(sceNpWordFilterDeleteTitleCtx(context) == 0, "sceNpWordFilterDeleteTitleCtx", "deleting the context failed"); + Require(sceNpWordFilterDeleteRequest(kept) == InvalidId, "sceNpWordFilterDeleteTitleCtx", "a request of the deleted context was kept"); + Require(sceNpWordFilterDeleteTitleCtx(context) == InvalidId, "sceNpWordFilterDeleteTitleCtx", "a deleted context was found"); + Require(sceNpLookupDeleteTitleCtx(context) == InvalidId, "sceNpLookupDeleteTitleCtx", "a word filter context was found among the lookup contexts"); +} + +} + +int WaitInThread(const char* name, int (APS5_VABI *wait)(int, int*), int reqId, int* result, void (*finish)(int)) { + int status = 7; + std::atomic entered{false}; + std::atomic returned{false}; + std::thread waiter([&] { + entered = true; + status = wait(reqId, result); + returned = true; + }); + while (!entered) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + Require(!returned, name, "the wait returned before the request finished"); + finish(reqId); + waiter.join(); + return status; +} + +void StartLookup(int reqId) { + NpOnlineId onlineId{}; + std::strcpy(onlineId.data, "player_2"); + sceNpLookupNpId(reqId, &onlineId, npId, nullptr); +} + +void AbortLookup(int reqId) { + sceNpLookupAbortRequest(reqId); +} + +void DeleteLookup(int reqId) { + sceNpLookupDeleteRequest(reqId); +} + +void CheckLookupAsync() { + const int context = sceNpLookupCreateTitleCtx(selfNpId); + Require(sceNpLookupCreateAsyncRequest(context, nullptr) == InvalidArgument, "sceNpLookupCreateAsyncRequest", "a null parameter was taken"); + Require(sceNpLookupCreateAsyncRequest(context, wrongAsyncParam) == InvalidArgument, "sceNpLookupCreateAsyncRequest", "a parameter size other than 0x18 was taken"); + Require(sceNpLookupCreateAsyncRequest(context + 100, asyncParam) == InvalidId, "sceNpLookupCreateAsyncRequest", "an unknown context was taken"); + + Require(sceNpLookupSetTimeout(context, -1, 0, 0, 0, 0) == InvalidArgument, "sceNpLookupSetTimeout", "a negative retry count was taken"); + Require(sceNpLookupSetTimeout(context, 0, 0, 0, 0, 0) == InvalidArgument, "sceNpLookupSetTimeout", "settings without any effect were taken"); + Require(sceNpLookupSetTimeout(context, 0, 999999, 0, 0, 0) == InvalidArgument, "sceNpLookupSetTimeout", "a resolve timeout under 1 s was taken"); + Require(sceNpLookupSetTimeout(context, 0, 0, 9999999, 0, 0) == InvalidArgument, "sceNpLookupSetTimeout", "a connect timeout under 10 s was taken"); + Require(sceNpLookupSetTimeout(context, 0, 0, 0, 9999999, 0) == InvalidArgument, "sceNpLookupSetTimeout", "a send timeout under 10 s was taken"); + Require(sceNpLookupSetTimeout(context, 0, 0, 0, 0, 9999999) == InvalidArgument, "sceNpLookupSetTimeout", "a receive timeout under 10 s was taken"); + Require(sceNpLookupSetTimeout(context + 100, 1, 0, 0, 0, 0) == InvalidId, "sceNpLookupSetTimeout", "an unknown id was taken"); + Require(sceNpLookupSetTimeout(context, 1, 0, 0, 0, 0) == 0, "sceNpLookupSetTimeout", "a context timeout was refused"); + + const int async = sceNpLookupCreateAsyncRequest(context, asyncParam); + Require(async > 0, "sceNpLookupCreateAsyncRequest", "creating an asynchronous request failed"); + Require(sceNpLookupSetTimeout(async, 0, 1000000, 10000000, 10000000, 10000000) == 0, "sceNpLookupSetTimeout", "a request timeout was refused"); + int result = 7; + Require(sceNpLookupPollAsync(async + 100, &result) == InvalidId, "sceNpLookupPollAsync", "an unknown request was taken"); + Require(sceNpLookupPollAsync(async, nullptr) == InvalidArgument, "sceNpLookupPollAsync", "a null result was taken"); + Require(sceNpLookupPollAsync(async + 100, nullptr) == InvalidArgument, "sceNpLookupPollAsync", "an unknown request was checked before a null result"); + Require(sceNpLookupWaitAsync(async, nullptr) == InvalidArgument, "sceNpLookupWaitAsync", "a null result was taken"); + Require(sceNpLookupPollAsync(async, &result) == PollRunning && result == 7, "sceNpLookupPollAsync", "an unstarted request was not running"); + Require(WaitInThread("sceNpLookupWaitAsync", sceNpLookupWaitAsync, async, &result, StartLookup) == 0 && result == SignedOut, "sceNpLookupWaitAsync", "waiting did not end with SIGNED_OUT when the lookup started"); + Require(sceNpLookupPollAsync(async, nullptr) == InvalidArgument, "sceNpLookupPollAsync", "a null result was taken on a finished request"); + result = 7; + Require(sceNpLookupPollAsync(async, &result) == PollFinished && result == SignedOut, "sceNpLookupPollAsync", "the request did not finish with SIGNED_OUT"); + + const int sync = sceNpLookupCreateRequest(context); + StartLookup(sync); + result = 7; + Require(sceNpLookupWaitAsync(sync, &result) == LookupSynchronousRequest && result == 7, "sceNpLookupWaitAsync", "a synchronous request was not refused with 0x80550307"); + Require(sceNpLookupPollAsync(sync, &result) == LookupSynchronousRequest && result == 7, "sceNpLookupPollAsync", "a synchronous request was not refused with 0x80550307"); + + const int aborted = sceNpLookupCreateAsyncRequest(context, asyncParam); + Require(sceNpLookupAbortRequest(aborted + 100) == InvalidId, "sceNpLookupAbortRequest", "an unknown request was taken"); + Require(WaitInThread("sceNpLookupWaitAsync", sceNpLookupWaitAsync, aborted, &result, AbortLookup) == 0 && result == Aborted, "sceNpLookupAbortRequest", "waiting did not end with ABORTED"); + Require(sceNpLookupNpId(aborted, nullptr, npId, nullptr) == InsufficientArgument, "sceNpLookupNpId", "arguments were not checked before the abort"); + + const int deleted = sceNpLookupCreateAsyncRequest(context, asyncParam); + Require(WaitInThread("sceNpLookupWaitAsync", sceNpLookupWaitAsync, deleted, &result, DeleteLookup) == InvalidId, "sceNpLookupWaitAsync", "waiting on a deleted request did not return INVALID_ID"); + Require(sceNpLookupWaitAsync(deleted, &result) == InvalidId, "sceNpLookupWaitAsync", "an unknown request was taken"); + sceNpLookupDeleteTitleCtx(context); +} + +void CheckWordFilterAsync() { + const int context = sceNpWordFilterCreateTitleCtx(selfNpId); + Require(sceNpWordFilterSetTimeout(context, 0, 0, 0, 0, 0) == InvalidArgument, "sceNpWordFilterSetTimeout", "settings without any effect were taken"); + Require(sceNpWordFilterSetTimeout(context, 0, 0, 0, 0, 9999999) == InvalidArgument, "sceNpWordFilterSetTimeout", "a receive timeout under 10 s was taken"); + Require(sceNpWordFilterSetTimeout(context, 1, 0, 0, 0, 0) == 0, "sceNpWordFilterSetTimeout", "a context timeout was refused"); + Require(sceNpWordFilterSetTimeout(1, 1, 0, 0, 0, 0) == InvalidId, "sceNpWordFilterSetTimeout", "a lookup id was taken"); + const int sync = sceNpWordFilterCreateRequest(context); + const int async = sceNpWordFilterCreateAsyncRequest(context, asyncParam); + Require(sceNpWordFilterSetTimeout(async, 2, 1000000, 0, 0, 0) == 0, "sceNpWordFilterSetTimeout", "a request timeout was refused"); + int result = 7; + Require(sceNpWordFilterWaitAsync(async, nullptr) == InvalidArgument, "sceNpWordFilterWaitAsync", "a null result was taken"); + Require(sceNpWordFilterWaitAsync(sync, &result) == InvalidId, "sceNpWordFilterWaitAsync", "a synchronous request was taken"); + Require(sceNpWordFilterWaitAsync(async + 100, &result) == InvalidId, "sceNpWordFilterWaitAsync", "an unknown request was taken"); + Require(sceNpWordFilterCensorComment(async, "word", nullptr) == 0, "sceNpWordFilterCensorComment", "the asynchronous request did not start"); + Require(sceNpWordFilterWaitAsync(async, &result) == 0 && result == SignedOut, "sceNpWordFilterWaitAsync", "the request did not finish with SIGNED_OUT"); + sceNpWordFilterDeleteTitleCtx(context); +} + +int main() { + std::memset(selfNpId, 0, sizeof(selfNpId)); + std::strcpy(reinterpret_cast(selfNpId), "player_1"); + CheckLookup(); + CheckWordFilter(); + CheckLookupAsync(); + CheckWordFilterAsync(); + if (failures != 0) return 1; + std::puts("NpUtility offline tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestNpWebApi.cpp b/core/libs/tests/GuestNpWebApi.cpp new file mode 100644 index 0000000000..465ee0900c --- /dev/null +++ b/core/libs/tests/GuestNpWebApi.cpp @@ -0,0 +1,252 @@ +#include "ProcessParametersFixture.hpp" +#include "prx/libSceNpWebApi/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int InvalidArgument = static_cast(0x80552902u); +constexpr int InvalidLibContextId = static_cast(0x80552903u); +constexpr int LibContextNotFound = static_cast(0x80552904u); +constexpr int UserContextNotFound = static_cast(0x80552905u); +constexpr int RequestNotFound = static_cast(0x80552906u); +constexpr int NotSignedIn = static_cast(0x80552907u); +constexpr int InvalidContentParameter = static_cast(0x80552908u); +constexpr int Aborted = static_cast(0x80552909u); +constexpr int UserContextAlreadyExist = static_cast(0x8055290Au); +constexpr int PushEventFilterNotFound = static_cast(0x8055290Bu); +constexpr int PushEventCallbackNotFound = static_cast(0x8055290Cu); +constexpr int HandleNotFound = static_cast(0x8055290Du); +constexpr int ServicePushEventFilterNotFound = static_cast(0x8055290Eu); +constexpr int ServicePushEventCallbackNotFound = static_cast(0x8055290Fu); +constexpr int SignedInUserNotFound = static_cast(0x80552910u); + +constexpr int MethodGet = 0; +constexpr int MethodPost = 1; +constexpr int MethodPatch = 4; + +alignas(16) std::array process{}; +const char onlineId[20] = "player"; +const char filterParam[16] = {}; +const char body[] = "{}"; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "NpWebApi: %s\n", message); + std::abort(); + } +} + +void APS5_VABI PushEvent() { +} + +void* PushEventCallback() { + return reinterpret_cast(&PushEvent); +} + +void SetSdkVersion(std::uint32_t version) { + std::memcpy(process.data() + 16, &version, sizeof(version)); +} + +void InstallProcessParameters() { + const std::uint64_t size = process.size(); + const std::uint32_t magic = 0x4942524f; + std::memcpy(process.data(), &size, sizeof(size)); + std::memcpy(process.data() + 8, &magic, sizeof(magic)); + Require(ProcessParametersFixture::Install(process.data(), process.size()), "process parameters were not installed"); +} + +void TestLibraryContexts() { + const int context = sceNpWebApiInitialize(1, 0x10000); + Require(context > 0 && context <= 0x7fff, "library context id out of range"); + const int other = sceNpWebApiInitialize(1, 0x10000); + Require(other > 0 && other != context, "a second library context must get its own id"); + Require(sceNpWebApiCreateHandle(0x7fff + 1) == LibContextNotFound, "handle of an unknown context"); + + const int handle = sceNpWebApiCreateHandle(context); + Require(handle > 0 && static_cast(handle) < 0xf0000000u, "handle id out of range"); + Require(sceNpWebApiCreatePushEventFilter(context, nullptr, 1) == InvalidArgument, "push event filter without its parameter"); + Require(sceNpWebApiCreatePushEventFilter(context, filterParam, 0) == InvalidArgument, "push event filter with no parameters"); + const int filter = sceNpWebApiCreatePushEventFilter(context, filterParam, 1); + Require(filter > 0 && filter != handle, "push event filter id"); + + Require(sceNpWebApiCreateServicePushEventFilter(context, handle, nullptr, 0, filterParam, 1) == InvalidArgument, "service filter without a service name"); + Require(sceNpWebApiCreateServicePushEventFilter(0x7fff + 1, handle, "service", 0, filterParam, 1) == LibContextNotFound, "service filter of an unknown context"); + Require(sceNpWebApiCreateServicePushEventFilter(other, handle, "service", 0, filterParam, 1) == HandleNotFound, "service filter with another context's handle"); + Require(sceNpWebApiCreateServicePushEventFilter(context, handle, "service", 0xffffffffu, filterParam, 1) == InvalidArgument, "service filter with the invalid service label from SDK 2.00"); + Require(sceNpWebApiCreateServicePushEventFilter(0x7fff + 1, handle, "service", 0xffffffffu, filterParam, 1) == InvalidArgument, "the service label is checked before the library context"); + Require(sceNpWebApiCreateServicePushEventFilter(context, handle, "service", 0, filterParam, 1) == SignedInUserNotFound, "service filter without a signed-in user"); + Require(sceNpWebApiDeleteServicePushEventFilter(context, 1) == ServicePushEventFilterNotFound, "no service filter exists"); + + Require(sceNpWebApiDeletePushEventFilter(other, filter) == PushEventFilterNotFound, "filter deleted through another context"); + Require(sceNpWebApiDeletePushEventFilter(context, filter) == 0, "delete the push event filter"); + Require(sceNpWebApiDeletePushEventFilter(context, filter) == PushEventFilterNotFound, "push event filter deleted twice"); + Require(sceNpWebApiDeleteHandle(context, handle) == 0, "delete the handle"); + Require(sceNpWebApiDeleteHandle(context, handle) == HandleNotFound, "handle deleted twice"); + + Require(sceNpWebApiTerminate(context) == 0, "terminate the library context"); + Require(sceNpWebApiTerminate(context) == LibContextNotFound, "library context terminated twice"); + Require(sceNpWebApiCreateHandle(context) == LibContextNotFound, "handle of a terminated context"); + Require(sceNpWebApiTerminate(other) == 0, "terminate the second library context"); +} + +void TestUserContexts() { + const int context = sceNpWebApiInitialize(1, 0x10000); + const int other = sceNpWebApiInitialize(1, 0x10000); + Require(context > 0 && other > 0, "library contexts for user contexts"); + Require(sceNpWebApiTerminate(other) == 0, "terminate the unused library context"); + + Require(sceNpWebApiCreateContext(0x8000, onlineId) == InvalidLibContextId, "user context of a library context id above the range"); + Require(sceNpWebApiCreateContext(0x8000, nullptr) == InvalidLibContextId, "the library context id is checked before the online id"); + Require(sceNpWebApiCreateContext(context, nullptr) == InvalidArgument, "user context without an online id"); + Require(sceNpWebApiCreateContext(0, onlineId) == LibContextNotFound, "user context of library context 0"); + Require(sceNpWebApiCreateContext(other, onlineId) == LibContextNotFound, "user context of a terminated library context"); + + const int user = sceNpWebApiCreateContext(context, onlineId); + Require(user > 0 && (user >> 16) == context && (user & 0xffff) != 0, "user context id holds its library context"); + Require(sceNpWebApiCreateContext(context, onlineId) == UserContextAlreadyExist, "a second user context for the same user"); + + Require(sceNpWebApiDeleteContext(user + 1) == UserContextNotFound, "delete an unknown user context"); + Require(sceNpWebApiDeleteContext(-1) == LibContextNotFound, "delete user context -1"); + Require(sceNpWebApiDeleteContext((other << 16) | 1) == LibContextNotFound, "delete a user context of a terminated library context"); + Require(sceNpWebApiDeleteContext(user) == 0, "delete the user context"); + Require(sceNpWebApiDeleteContext(user) == UserContextNotFound, "user context deleted twice"); + + const int next = sceNpWebApiCreateContext(context, onlineId); + Require(next > 0 && next != user && (next >> 16) == context, "a new user context after the first was deleted"); + std::int64_t request = 0; + Require(sceNpWebApiCreateRequest(next, "group", "/path", MethodGet, nullptr, &request) == 0, "request before termination"); + Require(sceNpWebApiTerminate(context) == 0, "terminate a library context with a user context"); + Require(sceNpWebApiDeleteContext(next) == LibContextNotFound, "user context of a terminated library context"); + Require(sceNpWebApiSendRequest(request, nullptr, 0) == LibContextNotFound, "request of a terminated library context"); +} + +void TestRequests() { + const int context = sceNpWebApiInitialize(1, 0x10000); + const int user = sceNpWebApiCreateContext(context, onlineId); + Require(user > 0, "user context for requests"); + + std::int64_t request = 0; + Require(sceNpWebApiCreateRequest(user, nullptr, "/path", MethodGet, nullptr, &request) == InvalidArgument, "request without an API group"); + Require(sceNpWebApiCreateRequest(user, "group", nullptr, MethodGet, nullptr, &request) == InvalidArgument, "request without a path"); + const NpWebApiContentParameter untyped{sizeof(body), nullptr, {}}; + Require(sceNpWebApiCreateRequest(user, "group", "/path", MethodPost, &untyped, &request) == InvalidContentParameter, "content without a type"); + Require(sceNpWebApiCreateRequest(0, "group", "/path", MethodPost, &untyped, &request) == InvalidContentParameter, "content is checked before the user context"); + Require(sceNpWebApiCreateRequest(user, "group", "/path", MethodPatch, nullptr, &request) == InvalidArgument, "PATCH with SDK 2.50 or later"); + Require(sceNpWebApiCreateRequest(user + 1, "group", "/path", MethodGet, nullptr, &request) == UserContextNotFound, "request of an unknown user context"); + Require(sceNpWebApiCreateRequest(0, "group", "/path", MethodGet, nullptr, &request) == LibContextNotFound, "request of user context 0"); + Require(sceNpWebApiCreateRequest(-1, "group", "/path", MethodGet, nullptr, &request) == LibContextNotFound, "request of user context -1"); + Require(request == 0, "a failed request leaves the id alone"); + + const NpWebApiContentParameter empty{0, nullptr, {}}; + Require(sceNpWebApiCreateRequest(user, "group", "/path", MethodGet, &empty, nullptr) == 0, "empty content without a type and no id output"); + const NpWebApiContentParameter typed{sizeof(body), "application/json", {}}; + Require(sceNpWebApiCreateRequest(user, "group", "/path", MethodPost, &typed, &request) == 0, "create a request"); + Require(static_cast(request >> 32) == user && (request & 0xffffffff) != 0, "request id holds its user context"); + std::int64_t second = 0; + Require(sceNpWebApiCreateRequest(user, "group", "/path", MethodGet, nullptr, &second) == 0 && second != request && static_cast(second >> 32) == user, "a second request"); + + Require(sceNpWebApiSendRequest(request, body, sizeof(body)) == NotSignedIn, "a request cannot be sent without a signed-in user"); + Require(sceNpWebApiSendRequest(request, body, sizeof(body)) == NotSignedIn, "a failed send can be retried"); + Require(sceNpWebApiSendRequest(request + 0x10000, nullptr, 0) == RequestNotFound, "send an unknown request"); + Require(sceNpWebApiSendRequest(static_cast(user + 1) << 32 | 1, nullptr, 0) == UserContextNotFound, "send a request of an unknown user context"); + Require(sceNpWebApiSendRequest(1, nullptr, 0) == LibContextNotFound, "send a request of library context 0"); + + Require(sceNpWebApiAbortRequest(request + 0x10000) == RequestNotFound, "abort an unknown request"); + Require(sceNpWebApiAbortRequest(-1) == LibContextNotFound, "abort request -1"); + Require(sceNpWebApiDeleteRequest(-1) == LibContextNotFound, "delete request -1"); + Require(sceNpWebApiAbortRequest(request) == 0, "abort the request"); + Require(sceNpWebApiAbortRequest(request) == 0, "abort the request twice"); + Require(sceNpWebApiSendRequest(request, body, sizeof(body)) == Aborted, "send an aborted request"); + Require(sceNpWebApiSendRequest(second, nullptr, 0) == NotSignedIn, "aborting one request leaves the other"); + + Require(sceNpWebApiDeleteRequest(request) == 0, "delete the request"); + Require(sceNpWebApiDeleteRequest(request) == RequestNotFound, "request deleted twice"); + Require(sceNpWebApiSendRequest(request, nullptr, 0) == RequestNotFound, "send a deleted request"); + Require(sceNpWebApiDeleteContext(user) == 0, "delete the user context with a request"); + Require(sceNpWebApiSendRequest(second, nullptr, 0) == UserContextNotFound, "request of a deleted user context"); + Require(sceNpWebApiDeleteRequest(second) == UserContextNotFound, "delete a request of a deleted user context"); + Require(sceNpWebApiTerminate(context) == 0, "terminate the request library context"); +} + +void TestPushEventCallbacks() { + const int context = sceNpWebApiInitialize(1, 0x10000); + const int other = sceNpWebApiInitialize(1, 0x10000); + const int user = sceNpWebApiCreateContext(context, onlineId); + Require(user > 0, "user context for callbacks"); + const int filter = sceNpWebApiCreatePushEventFilter(context, filterParam, 1); + const int otherFilter = sceNpWebApiCreatePushEventFilter(other, filterParam, 1); + Require(filter > 0 && otherFilter > 0, "push event filters for callbacks"); + int argument = 0; + + Require(sceNpWebApiRegisterPushEventCallback(user, filter, nullptr, &argument) == InvalidArgument, "push event callback without a function"); + Require(sceNpWebApiRegisterPushEventCallback(user + 1, filter, nullptr, &argument) == InvalidArgument, "the function is checked before the user context"); + Require(sceNpWebApiRegisterPushEventCallback(user + 1, filter, PushEventCallback(), &argument) == UserContextNotFound, "push event callback of an unknown user context"); + Require(sceNpWebApiRegisterPushEventCallback(user, otherFilter, PushEventCallback(), &argument) == PushEventFilterNotFound, "push event callback with another context's filter"); + const int callback = sceNpWebApiRegisterPushEventCallback(user, filter, PushEventCallback(), &argument); + Require(callback > 0 && static_cast(callback) < 0xf0000000u, "push event callback id"); + Require(sceNpWebApiUnregisterPushEventCallback(user + 1, callback) == UserContextNotFound, "unregister through an unknown user context"); + Require(sceNpWebApiUnregisterServicePushEventCallback(user, callback) == ServicePushEventCallbackNotFound, "a push event callback is not a service callback"); + Require(sceNpWebApiUnregisterPushEventCallback(user, callback) == 0, "unregister the push event callback"); + Require(sceNpWebApiUnregisterPushEventCallback(user, callback) == PushEventCallbackNotFound, "push event callback unregistered twice"); + + Require(sceNpWebApiRegisterServicePushEventCallback(user, 1, nullptr, &argument) == InvalidArgument, "service callback without a function"); + Require(sceNpWebApiRegisterServicePushEventCallback(user + 1, 1, PushEventCallback(), &argument) == UserContextNotFound, "service callback of an unknown user context"); + Require(sceNpWebApiRegisterServicePushEventCallback(user, filter, PushEventCallback(), &argument) == ServicePushEventFilterNotFound, "a push event filter is not a service filter"); + Require(sceNpWebApiUnregisterServicePushEventCallback(user, 1) == ServicePushEventCallbackNotFound, "no service callback exists"); + + Require(sceNpWebApiTerminate(context) == 0 && sceNpWebApiTerminate(other) == 0, "terminate the callback library contexts"); +} + +void TestOlderSdks() { + const int context = sceNpWebApiInitialize(1, 0x10000); + const int user = sceNpWebApiCreateContext(context, onlineId); + Require(user > 0, "user context for older SDKs"); + int argument = 0; + + SetSdkVersion(0x02000000); + std::int64_t request = 0; + Require(sceNpWebApiCreateRequest(user, "group", "/path", MethodPatch, nullptr, &request) == 0, "PATCH before SDK 2.50"); + Require(sceNpWebApiSendRequest(request, nullptr, 0) == NotSignedIn, "send a PATCH request"); + Require(sceNpWebApiRegisterPushEventCallback(user, 1, nullptr, &argument) == InvalidArgument, "push event callback without a function from SDK 1.00"); + const int callback = sceNpWebApiRegisterPushEventCallback(user, 1, PushEventCallback(), &argument); + Require(callback > 0, "push event callback without a filter before SDK 2.50"); + Require(sceNpWebApiUnregisterPushEventCallback(user, callback) == 0, "unregister the unfiltered push event callback"); + const int service = sceNpWebApiRegisterServicePushEventCallback(user, 1, PushEventCallback(), &argument); + Require(service > 0 && service != callback, "service callback without a filter before SDK 2.50"); + Require(sceNpWebApiUnregisterPushEventCallback(user, service) == PushEventCallbackNotFound, "a service callback is not a push event callback"); + Require(sceNpWebApiUnregisterServicePushEventCallback(user, service) == 0, "unregister the service callback"); + Require(sceNpWebApiUnregisterServicePushEventCallback(user, service) == ServicePushEventCallbackNotFound, "service callback unregistered twice"); + + SetSdkVersion(0x01000000); + const int handle = sceNpWebApiCreateHandle(context); + Require(sceNpWebApiCreateServicePushEventFilter(context, handle, "service", 0xffffffffu, filterParam, 1) == SignedInUserNotFound, "the invalid service label is accepted before SDK 2.00"); + + SetSdkVersion(0x00900000); + const int unchecked = sceNpWebApiRegisterPushEventCallback(user, 1, nullptr, &argument); + Require(unchecked > 0, "push event callback without a function before SDK 1.00"); + Require(sceNpWebApiUnregisterPushEventCallback(user, unchecked) == 0, "unregister the push event callback without a function"); + Require(sceNpWebApiRegisterServicePushEventCallback(user + 1, 1, nullptr, &argument) == UserContextNotFound, "the user context is checked before the service callback function before SDK 1.00"); + Require(sceNpWebApiRegisterServicePushEventCallback(user, 1, nullptr, &argument) == InvalidArgument, "service callback without a function before SDK 1.00"); + + SetSdkVersion(0x04508001); + Require(sceNpWebApiTerminate(context) == 0, "terminate the older SDK library context"); +} + +} + +int main() { + InstallProcessParameters(); + SetSdkVersion(0x04508001); + TestLibraryContexts(); + TestUserContexts(); + TestRequests(); + TestPushEventCallbacks(); + TestOlderSdks(); + return 0; +} diff --git a/core/libs/tests/GuestNpWebApi2.cpp b/core/libs/tests/GuestNpWebApi2.cpp index 8ae914ceda..291dfdadd8 100644 --- a/core/libs/tests/GuestNpWebApi2.cpp +++ b/core/libs/tests/GuestNpWebApi2.cpp @@ -1,59 +1,47 @@ +#include "prx/libSceNpWebApi2/include/Api.hpp" #include "SceTypes.hpp" #include #include #include +#ifdef __APPLE__ +#include +#endif +#include #include -struct NpWebApi2MemoryPoolStats { - std::size_t pool_size; - std::size_t max_inuse_size; - std::size_t current_inuse_size; - std::int32_t reserved; -}; - -extern "C" { -int APS5_VABI sceNpWebApi2Initialize(int, std::size_t); -int APS5_VABI sceNpWebApi2InitializeForPresence(int, std::uint64_t); -int APS5_VABI sceNpWebApi2IntInitialize(const NpWebApi2IntInitializeArgs*); -int APS5_VABI sceNpWebApi2IntInitialize2(const NpWebApi2IntInitialize2Args*); -int APS5_VABI sceNpWebApi2Terminate(int); -int APS5_VABI sceNpWebApi2GetMemoryPoolStats(int, NpWebApi2MemoryPoolStats*); -int APS5_VABI sceNpWebApi2PushEventCreateHandle(int); -int APS5_VABI sceNpWebApi2PushEventAbortHandle(int, int); -int APS5_VABI sceNpWebApi2PushEventSetHandleTimeout(int, int, std::uint32_t); -int APS5_VABI sceNpWebApi2CreateUserContext(int, int); -int APS5_VABI sceNpWebApi2CreateRequest(int, const char*, const char*, const char*, const void*, std::int64_t*); -int APS5_VABI sceNpWebApi2SetRequestTimeout(std::int64_t, std::uint32_t); -int APS5_VABI sceNpWebApi2AddWebTraceTag(std::int64_t, const char*); -int APS5_VABI sceNpWebApi2DeleteRequest(std::int64_t); -} - static void Require(bool value) { if (!value) std::abort(); } -template -static bool Throws(TCall call) { - try { - call(); - } catch (const std::exception&) { - return true; - } - return false; -} - int main() { + constexpr int invalidContext = static_cast(0x80553404); + constexpr int invalidHandle = static_cast(0x8055340D); const int context = sceNpWebApi2Initialize(1, 0x20000); const int other = sceNpWebApi2Initialize(1, 0x8000); Require(context > 0 && other > 0 && context != other); - NpWebApi2MemoryPoolStats stats{1, 1, 1, 1}; + NpWebApi2MemoryPoolStats stats; + std::memset(&stats, 0xa5, sizeof(stats)); Require(sceNpWebApi2GetMemoryPoolStats(context, &stats) == 0); + const auto* statsBytes = reinterpret_cast(&stats); + for (std::size_t index = sizeof(std::size_t); index < sizeof(stats); ++index) Require(statsBytes[index] == 0); Require(stats.pool_size == 0x20000 && stats.max_inuse_size == 0 && stats.current_inuse_size == 0 && stats.reserved == 0); Require(sceNpWebApi2GetMemoryPoolStats(other, &stats) == 0 && stats.pool_size == 0x8000); - Require(Throws([&] { sceNpWebApi2GetMemoryPoolStats(context, nullptr); })); + Require(sceNpWebApi2GetMemoryPoolStats(context, nullptr) == 0); + Require(sceNpWebApi2GetMemoryPoolStats(-1, nullptr) == invalidContext); + Require(sceNpWebApi2PushEventCreateHandle(-1) == invalidContext); const int handle = sceNpWebApi2PushEventCreateHandle(context); Require(handle > 0); Require(sceNpWebApi2PushEventAbortHandle(context, handle) == 0); - Require(sceNpWebApi2PushEventAbortHandle(context, handle + 1000) == 0); + Require(sceNpWebApi2PushEventAbortHandle(context, handle + 1000) == invalidHandle); Require(sceNpWebApi2PushEventSetHandleTimeout(context, handle, 30000) == 0); + Require(sceNpWebApi2PushEventAbortHandle(other, handle) == invalidHandle); + Require(sceNpWebApi2PushEventDeleteHandle(other, handle) == invalidHandle); + Require(sceNpWebApi2PushEventSetHandleTimeout(other, handle, 0) == invalidHandle); + Require(sceNpWebApi2PushEventSetHandleTimeout(context, handle, 0) == 0); + Require(sceNpWebApi2PushEventSetHandleTimeout(context, handle, UINT32_MAX) == 0); + Require(sceNpWebApi2PushEventDeleteHandle(context, handle) == 0); + Require(sceNpWebApi2PushEventDeleteHandle(context, handle) == invalidHandle); + Require(sceNpWebApi2PushEventAbortHandle(context, handle) == invalidHandle); + Require(sceNpWebApi2PushEventSetHandleTimeout(context, handle, 1) == invalidHandle); + const int survivingHandle = sceNpWebApi2PushEventCreateHandle(other); const int user = sceNpWebApi2CreateUserContext(context, 1); std::int64_t request = 0; Require(user > 0 && sceNpWebApi2CreateRequest(user, "sessionManager", "/v1/sessions", "GET", nullptr, &request) == 0); @@ -62,9 +50,10 @@ int main() { Require(sceNpWebApi2AddWebTraceTag(request, nullptr) == static_cast(0x80553402)); Require(sceNpWebApi2DeleteRequest(request) == 0); Require(sceNpWebApi2Terminate(context) == 0); - Require(Throws([&] { sceNpWebApi2GetMemoryPoolStats(context, &stats); })); - Require(Throws([&] { sceNpWebApi2PushEventAbortHandle(context, handle); })); - Require(Throws([&] { sceNpWebApi2PushEventSetHandleTimeout(context, handle, 30000); })); + Require(sceNpWebApi2GetMemoryPoolStats(context, &stats) == invalidContext); + Require(sceNpWebApi2PushEventAbortHandle(context, handle) == invalidContext); + Require(sceNpWebApi2PushEventSetHandleTimeout(context, handle, 30000) == invalidContext); + Require(sceNpWebApi2PushEventAbortHandle(other, survivingHandle) == 0); Require(sceNpWebApi2Terminate(other) == 0); constexpr int invalidArgument = static_cast(0x80553402); @@ -82,7 +71,7 @@ int main() { Require(presence > 0 && sceNpWebApi2GetMemoryPoolStats(presence, &stats) == 0 && stats.pool_size == 0x2000); for (const int created : {internal, internal2, presence}) { Require(sceNpWebApi2Terminate(created) == 0); - Require(Throws([&] { sceNpWebApi2GetMemoryPoolStats(created, &stats); })); + Require(sceNpWebApi2GetMemoryPoolStats(created, &stats) == invalidContext); } return 0; } diff --git a/core/libs/tests/GuestOfflineImports.cpp b/core/libs/tests/GuestOfflineImports.cpp index 32dcdd9acf..3dd2c7a0b7 100644 --- a/core/libs/tests/GuestOfflineImports.cpp +++ b/core/libs/tests/GuestOfflineImports.cpp @@ -1,3 +1,7 @@ +#include "prx/libSceVideoRecordingP/include/Api.hpp" +#include "prx/libSceRudp/include/Api.hpp" +#include "prx/libSceNpEntitlementAccess/include/Api.hpp" +#include "prx/libSceHttp/include/Http.hpp" #include "SceTypes.hpp" #include @@ -6,46 +10,54 @@ #include #include #include -#include - -extern "C" { -int APS5_VABI sceHttpSetCookieEnabled(int, int); -int APS5_VABI sceHttpSendRequest(int, const void*, std::size_t); -int APS5_VABI sceNpEntitlementAccessGetEntitlementKey( - std::uint32_t, const NpUnifiedEntitlementLabel*, NpEntitlementAccessEntitlementKey*); -int APS5_VABI sceNpEntitlementAccessGetGameTrialsFlag(std::uint32_t*); -int APS5_VABI sceNpEntitlementAccessGetPftFlag(std::uint32_t*); -int APS5_VABI sceNpEntitlementAccessRequestUnifiedEntitlementInfoList(); -int APS5_VABI sceNpEntitlementAccessPollUnifiedEntitlementInfoList(); -int APS5_VABI sceNpEntitlementAccessRequestServiceEntitlementInfoList(); -int APS5_VABI sceNpEntitlementAccessPollServiceEntitlementInfoList(); -int APS5_VABI sceNpEntitlementAccessRequestUnifiedEntitlementInfo(); -int APS5_VABI sceNpEntitlementAccessPollUnifiedEntitlementInfo(); -int APS5_VABI sceNpEntitlementAccessRequestServiceEntitlementInfo(); -int APS5_VABI sceNpEntitlementAccessPollServiceEntitlementInfo(); -int APS5_VABI sceRudpInit_nid_postfix(void*, int); -int APS5_VABI sceRudpActivate(); -int APS5_VABI sceRudpGetStatus(void*, std::size_t); -int APS5_VABI sceRudpEnd(); -} static void Require(bool value) { if (!value) std::abort(); } int main() { - constexpr int invalidValue = static_cast(0x804311FE); + constexpr int recordingInvalid = static_cast(0x80a80003); + constexpr int recordingNotOpen = static_cast(0x80a80006); + constexpr int recordingUnavailable = static_cast(0x80a80002); + std::uint32_t recordingOutput = 0x12345678; + for (std::int32_t selector = 0; selector <= 0x10000; ++selector) { + const bool query = selector == 0xa001 || selector == 0xa002 || selector == 0xa003 || selector == 0xa005 || selector == 0xa00a || selector == 0xa00b || selector == 0xa00c; + const bool control = selector == 0xa004 || selector == 0xa006; + Require(sceVideoRecordingGetInfo(selector, &recordingOutput, sizeof(recordingOutput)) == (query ? recordingNotOpen : recordingInvalid)); + Require(recordingOutput == 0x12345678); + Require(sceVideoRecordingGetInfo(selector, nullptr, 0) == (query ? recordingNotOpen : recordingInvalid)); + Require(sceVideoRecordingSetInfo(selector, nullptr, 0) == (control ? recordingNotOpen : recordingInvalid)); + } + for (std::int32_t selector : {-1, INT32_MIN, INT32_MAX}) { + Require(sceVideoRecordingGetInfo(selector, nullptr, 0) == recordingInvalid); + Require(sceVideoRecordingSetInfo(selector, nullptr, 0) == recordingInvalid); + } + const float recordingVolume = 0.5f; + for (std::int32_t selector : {0x8002, 0x8003}) { + for (std::size_t size : {0u, 1u, 3u, 4u, 8u}) { + Require(sceVideoRecordingSetInfo(selector, nullptr, size) == recordingInvalid); + Require(sceVideoRecordingSetInfo(selector, &recordingVolume, size) == (size < sizeof(float) ? recordingInvalid : recordingUnavailable)); + } + } + + for (std::int64_t requestId : {std::int64_t{0}, std::int64_t{1}, INT64_MAX}) { + bool abortFailed = false; + bool deleteFailed = false; + try { + sceNpEntitlementAccessAbortRequest(requestId); + + } catch (const std::logic_error&) { + abortFailed = true; + } + try { + sceNpEntitlementAccessDeleteRequest(requestId); + + } catch (const std::logic_error&) { + deleteFailed = true; + } + Require(abortFailed && deleteFailed); + } + constexpr int network = static_cast(0x80431063); - Require(sceHttpSetCookieEnabled(1, 0) == 0); Require(sceHttpSendRequest(1, nullptr, 0) == network); - bool cookieUnsupported = false; - try { - sceHttpSetCookieEnabled(1, 1); - } catch (const std::runtime_error& error) { - cookieUnsupported = std::string_view(error.what()) == "sceHttpSetCookieEnabled not implemented"; - } - Require(cookieUnsupported); - for (int enabled : {-1, 2, 0x100}) { - Require(sceHttpSetCookieEnabled(1, enabled) == invalidValue); - } constexpr int parameter = static_cast(0x817D0002); constexpr int noEntitlement = static_cast(0x817D0007); @@ -79,16 +91,16 @@ int main() { Require(sceNpEntitlementAccessGetPftFlag(nullptr) == parameter); constexpr int signedOut = static_cast(0x80550006); - Require(sceNpEntitlementAccessRequestUnifiedEntitlementInfoList() == signedOut); - Require(sceNpEntitlementAccessPollUnifiedEntitlementInfoList() == signedOut); - Require(sceNpEntitlementAccessRequestServiceEntitlementInfoList() == signedOut); - Require(sceNpEntitlementAccessPollServiceEntitlementInfoList() == signedOut); - Require(sceNpEntitlementAccessRequestUnifiedEntitlementInfo() == signedOut); - Require(sceNpEntitlementAccessPollUnifiedEntitlementInfo() == signedOut); - Require(sceNpEntitlementAccessRequestServiceEntitlementInfo() == signedOut); - Require(sceNpEntitlementAccessPollServiceEntitlementInfo() == signedOut); + Require(sceNpEntitlementAccessRequestUnifiedEntitlementInfoList(0, 0, nullptr, 0, nullptr, nullptr) == signedOut); + Require(sceNpEntitlementAccessPollUnifiedEntitlementInfoList(0, nullptr, nullptr, 0, nullptr, nullptr, nullptr) == signedOut); + Require(sceNpEntitlementAccessRequestServiceEntitlementInfoList(0, 0, nullptr, 0, nullptr, nullptr) == signedOut); + Require(sceNpEntitlementAccessPollServiceEntitlementInfoList(0, nullptr, nullptr, 0, nullptr, nullptr, nullptr) == signedOut); + Require(sceNpEntitlementAccessRequestUnifiedEntitlementInfo(0, 0, nullptr, nullptr) == signedOut); + Require(sceNpEntitlementAccessPollUnifiedEntitlementInfo(0, nullptr, nullptr) == signedOut); + Require(sceNpEntitlementAccessRequestServiceEntitlementInfo(0, 0, nullptr, nullptr) == signedOut); + Require(sceNpEntitlementAccessPollServiceEntitlementInfo(0, nullptr, nullptr) == signedOut); - Require(sceRudpActivate() == 0); + Require(sceRudpActivate(-1, nullptr, 0) == static_cast(0x80770001)); std::array status; status.fill(0x5a); const auto originalStatus = status; @@ -97,7 +109,7 @@ int main() { Require(status == originalStatus); alignas(8) static unsigned char rudpPool[0xC58]; Require(sceRudpInit_nid_postfix(rudpPool, sizeof(rudpPool)) == 0); - Require(sceRudpActivate() == 0); + Require(sceRudpActivate(-1, nullptr, 0) == static_cast(0x80770004)); Require(sceRudpGetStatus(status.data(), status.size()) == 0); for (unsigned char byte : status) Require(byte == 0); Require(sceRudpGetStatus(nullptr, 0) == static_cast(0x80770004)); diff --git a/core/libs/tests/GuestOnce.cpp b/core/libs/tests/GuestOnce.cpp index 3e60f58ec3..be0de52b29 100644 --- a/core/libs/tests/GuestOnce.cpp +++ b/core/libs/tests/GuestOnce.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" #include "SceTypes.hpp" #include #include @@ -6,20 +9,9 @@ #include #include -struct GuestOnce { std::int32_t state; void* mutex; }; static_assert(sizeof(GuestOnce) == 16 && offsetof(GuestOnce, mutex) == 8); using Initializer = void (APS5_VABI *)(); -extern "C" { -int APS5_VABI pthread_once_nid_postfix(GuestOnce*, Initializer); -int APS5_VABI scePthreadOnce(std::int32_t* once, Initializer init); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -void APS5_VABI scePthreadExit(void* retval); -int APS5_VABI scePthreadCancel(Pthread thread); -void APS5_VABI scePthreadTestcancel(); -Pthread APS5_VABI scePthreadSelf(); -void APS5_VABI __pthread_cleanup_push_imp_nid_postfix(void (APS5_VABI* routine)(void*), void* argument, void* info); -} + static void Require(bool value) { if (!value) std::abort(); } static GuestOnce control{}, nested{}, retry{}; static std::atomic calls{0}, arrived{0}; @@ -41,12 +33,14 @@ static void APS5_VABI ExitInsideInitializer() { scePthreadExit(nullptr); } static void APS5_VABI CancelInsideInitializer() { Require(scePthreadCancel(scePthreadSelf()) == 0); scePthreadTestcancel(); + } static void APS5_VABI CountRetry() { ++retried; } static void APS5_VABI AbortCleanup(void*) { std::abort(); } static void APS5_VABI PushAndThrow() { std::uintptr_t info[8]; __pthread_cleanup_push_imp_nid_postfix(AbortCleanup, nullptr, info); + throw 7; } static GuestOnce pushed{}; diff --git a/core/libs/tests/GuestOperationMode.cpp b/core/libs/tests/GuestOperationMode.cpp index b3fe29f518..e0f08ff13c 100644 --- a/core/libs/tests/GuestOperationMode.cpp +++ b/core/libs/tests/GuestOperationMode.cpp @@ -1,10 +1,8 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include -extern "C" { -void APS5_VABI sceKernelGetOperationMode(int* mode, int* submode); -int APS5_VABI sceKernelIsTrinityMode(void); -} + static void Require(bool value) { if (!value) std::abort(); } static bool Throws(int* mode, int* submode) { try { sceKernelGetOperationMode(mode, submode); } catch (const std::invalid_argument&) { return true; } @@ -13,6 +11,7 @@ static bool Throws(int* mode, int* submode) { int main() { int out[4] = {-1, -1, -1, -1}; sceKernelGetOperationMode(&out[0], &out[2]); + Require(out[0] == 2 && out[1] == -1); Require(out[2] == 0 && out[3] == -1); int value = -1; diff --git a/core/libs/tests/GuestOpusCeltDec.cpp b/core/libs/tests/GuestOpusCeltDec.cpp index aa3572b7e1..653acfe878 100644 --- a/core/libs/tests/GuestOpusCeltDec.cpp +++ b/core/libs/tests/GuestOpusCeltDec.cpp @@ -1,18 +1,12 @@ +#include "prx/libSceOpusCeltDec/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include +#include #include - -extern "C" { -int APS5_VABI sceOpusCeltDecInitialize(std::uint32_t*); -int APS5_VABI sceOpusCeltDecTerminate(std::uint32_t*); -int APS5_VABI sceOpusCeltDecGetSize(int); -int APS5_VABI sceOpusCeltDecCreateEx(std::uint32_t*, void*, int, int); -int APS5_VABI sceOpusCeltDecDecode(void*, const std::uint8_t*, int, std::int16_t*, int); -int APS5_VABI sceOpusCeltDecDestroy(void*); -} +#include namespace { @@ -75,11 +69,28 @@ void RequireTone(const std::int16_t* pcm, int samples, int channels, int rate) { } // namespace int main() { + constexpr std::uint32_t errorBase = 0x81400000u; + Require(sceOpusCeltDecInitialize(nullptr) == static_cast(errorBase + 13)); + Require(sceOpusCeltDecDestroy(nullptr) == static_cast(errorBase)); std::uint32_t context = 0; Require(sceOpusCeltDecInitialize(&context) == 0); + std::array invalidState{}; + std::int16_t output = 12345; + const std::uint8_t packet = 0; + Require(sceOpusCeltDecCreateEx(nullptr, invalidState.data(), 0, 0) == static_cast(errorBase)); + Require(sceOpusCeltDecCreateEx(&context, invalidState.data(), 0, 0) == static_cast(errorBase + 3)); + Require(sceOpusCeltDecCreateEx(&context, invalidState.data(), 48000, 0) == static_cast(errorBase + 3)); + Require(sceOpusCeltDecDecode(nullptr, nullptr, -1, nullptr, -1) == static_cast(errorBase)); + Require(sceOpusCeltDecDecode(invalidState.data(), nullptr, -1, nullptr, -1) == static_cast(errorBase + 6)); + Require(sceOpusCeltDecDecode(invalidState.data(), &packet, -1, nullptr, -1) == static_cast(errorBase + 7)); + Require(sceOpusCeltDecDecode(invalidState.data(), &packet, 1, nullptr, -1) == static_cast(errorBase + 8)); + Require(sceOpusCeltDecDecode(invalidState.data(), &packet, 1, &output, -1) == static_cast(errorBase + 9)); + Require(sceOpusCeltDecDecode(invalidState.data(), &packet, 1, &output, 2) == static_cast(errorBase + 1)); + Require(output == 12345); + RequireThrows([&] { sceOpusCeltDecInitialize(&context); }); Require(sceOpusCeltDecGetSize(1) == 640 && sceOpusCeltDecGetSize(2) == 640); - RequireThrows([&] { sceOpusCeltDecGetSize(3); }); + Require(sceOpusCeltDecGetSize(3) == static_cast(errorBase + 3)); std::array state{}, monoState{}; Require(sceOpusCeltDecCreateEx(&context, state.data(), 48000, 2) == 0); Require(sceOpusCeltDecCreateEx(&context, monoState.data(), 48000, 1) == 0); @@ -109,25 +120,37 @@ int main() { std::array unsupported{}; for (int rate : {0, 22050, 44100, 96000}) - RequireThrows([&] { sceOpusCeltDecCreateEx(&context, unsupported.data(), rate, 2); }); + Require(sceOpusCeltDecCreateEx(&context, unsupported.data(), rate, 2) == static_cast(errorBase + 2)); for (int rate : {8000, 12000, 16000, 24000, 48000}) { - std::array toneState{}; - Require(sceOpusCeltDecCreateEx(&context, toneState.data(), rate, 2) == 0); - const int samples = 960 * rate / 48000; - std::array tone{}; - for (int frame = 0; frame < 4; ++frame) { - tone.fill(12345); - RequireThrows([&] { sceOpusCeltDecDecode(toneState.data(), CELT_TONE[frame], 60, tone.data() + 1, samples * 4 - 1); }); - Require(sceOpusCeltDecDecode(toneState.data(), CELT_TONE[frame], 60, tone.data() + 1, samples * 4) == samples * 4); - Require(tone[0] == 12345 && tone[samples * 2 + 1] == 12345); - if (frame == 1 || frame == 2) RequireTone(tone.data() + 1, samples, 2, rate); + for (int channels : {1, 2}) { + std::array toneState{}; + Require(sceOpusCeltDecCreateEx(&context, toneState.data(), rate, channels) == 0); + int error = OPUS_OK; + auto* reference = opus_decoder_create(rate, channels, &error); + Require(reference && error == OPUS_OK); + const int samples = 960 * rate / 48000; + std::array tone{}; + for (int frame = 0; frame < 4; ++frame) { + tone.fill(12345); + RequireThrows([&] { sceOpusCeltDecDecode(toneState.data(), CELT_TONE[frame], 60, tone.data() + 1, samples * channels * 2 - 1); }); + Require(sceOpusCeltDecDecode(toneState.data(), CELT_TONE[frame], 60, tone.data() + 1, samples * channels * 2) == samples * channels * 2); + Require(tone[0] == 12345 && tone[samples * channels + 1] == 12345); + std::array expected{}; + Require(opus_decode(reference, CELT_TONE[frame], 60, expected.data(), samples, 0) == samples); + if (!std::equal(expected.begin(), expected.begin() + samples * channels, tone.begin() + 1)) { + std::fprintf(stderr, "CELT PCM differs from libopus at %d Hz, %d channels, frame %d\n", rate, channels, frame); + std::abort(); + } + if (frame == 1 || frame == 2) RequireTone(tone.data() + 1, samples, channels, rate); + } + opus_decoder_destroy(reference); + Require(sceOpusCeltDecDestroy(toneState.data()) == 0); } - Require(sceOpusCeltDecDestroy(toneState.data()) == 0); } Require(sceOpusCeltDecDestroy(state.data()) == 0); - RequireThrows([&] { sceOpusCeltDecDestroy(state.data()); }); - RequireThrows([&] { sceOpusCeltDecDecode(state.data(), CELT_STEREO + 2, CELT_STEREO[0], pcm.data(), 3840); }); + Require(sceOpusCeltDecDestroy(state.data()) == static_cast(errorBase + 1)); + Require(sceOpusCeltDecDecode(state.data(), CELT_STEREO + 2, CELT_STEREO[0], pcm.data(), 3840) == static_cast(errorBase + 1)); Require(sceOpusCeltDecDestroy(monoState.data()) == 0); Require(sceOpusCeltDecTerminate(&context) == 0); RequireThrows([&] { sceOpusCeltDecTerminate(&context); }); diff --git a/core/libs/tests/GuestOpusDec.cpp b/core/libs/tests/GuestOpusDec.cpp index a5824ee855..24eddac922 100644 --- a/core/libs/tests/GuestOpusDec.cpp +++ b/core/libs/tests/GuestOpusDec.cpp @@ -1,19 +1,14 @@ +#include "prx/libSceOpusDec/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include +#include +#include #include - -extern "C" { -int APS5_VABI sceOpusDecInitialize(std::uint32_t*); -int APS5_VABI sceOpusDecTerminate(std::uint32_t*); -int APS5_VABI sceOpusDecGetSize(int); -int APS5_VABI sceOpusDecCreateEx(std::uint32_t*, void*, int, int); -int APS5_VABI sceOpusDecDecode(void*, const std::uint8_t*, int, std::int16_t*, int); -int APS5_VABI sceOpusDecDestroy(void*); -} +#include namespace { @@ -46,11 +41,72 @@ const std::uint8_t OPUS_NARROWBAND[] = { 10, 0, 0x08, 0x85, 0x89, 0x9D, 0x73, 0x0F, 0xC2, 0xB1, 0xE2, 0x48, }; +void CheckReference(int rate, int channels) { + std::uint32_t context = 0; + std::array state{}; + Require(sceOpusDecInitialize(&context) == 0); + Require(sceOpusDecCreateEx(&context, state.data(), rate, channels) == 0); + int error = OPUS_OK; + auto* reference = opus_decoder_create(rate, channels, &error); + Require(reference && error == OPUS_OK); + std::array longPacket{}; + auto* repacketizer = opus_repacketizer_create(); + Require(repacketizer != nullptr); + for (int i = 0; i < 6; ++i) Require(opus_repacketizer_cat(repacketizer, OPUS_STEREO + 2, OPUS_STEREO[0]) == OPUS_OK); + const int longSize = opus_repacketizer_out(repacketizer, longPacket.data() + 2, static_cast(longPacket.size() - 2)); + Require(longSize > 0); + opus_repacketizer_destroy(repacketizer); + longPacket[0] = static_cast(longSize); + longPacket[1] = static_cast(longSize >> 8); + for (auto packets : {std::span(OPUS_NARROWBAND), std::span(OPUS_STEREO), + std::span(OPUS_NARROWBAND), std::span(longPacket.data(), longSize + 2)}) { + for (std::size_t offset = 0; offset < packets.size();) { + const int bytes = packets[offset] | (packets[offset + 1] << 8); + const auto* packet = packets.data() + offset + 2; + std::array expected{}; + const int samples = opus_decode(reference, packet, bytes, expected.data(), 5760, 0); + Require(samples > 0); + const int count = samples * channels; + std::array actual{}; + actual.fill(12345); + Require(sceOpusDecDecode(state.data(), packet, bytes, actual.data() + 1, count * 2 + 1) == count * 2); + if (!std::equal(expected.begin(), expected.begin() + count, actual.begin() + 1)) { + std::fprintf(stderr, "Opus PCM differs from libopus at %d Hz, %d channels, packet offset %zu\n", rate, channels, offset); + std::abort(); + } + Require(actual.front() == 12345 && std::all_of(actual.begin() + count + 1, actual.end(), [](auto value) { return value == 12345; })); + offset += bytes + 2; + } + } + opus_decoder_destroy(reference); + Require(sceOpusDecDestroy(state.data()) == 0); + Require(sceOpusDecTerminate(&context) == 0); +} + } // namespace int main() { + constexpr std::uint32_t errorBase = 0x81740000u; + Require(sceOpusDecInitialize(nullptr) == static_cast(errorBase + 13)); + Require(sceOpusDecDestroy(nullptr) == static_cast(errorBase)); + for (int rate : {8000, 12000, 16000, 24000, 48000}) + for (int channels : {1, 2}) CheckReference(rate, channels); std::uint32_t context = 0, otherContext = 0; Require(sceOpusDecInitialize(&context) == 0); + std::array invalidState{}; + std::int16_t output = 12345; + const std::uint8_t packet = 0; + Require(sceOpusDecCreateEx(nullptr, invalidState.data(), 0, 0) == static_cast(errorBase)); + Require(sceOpusDecCreateEx(&context, invalidState.data(), 0, 0) == static_cast(errorBase + 2)); + Require(sceOpusDecCreateEx(&context, invalidState.data(), 48000, 0) == static_cast(errorBase + 3)); + Require(sceOpusDecDecode(nullptr, nullptr, -1, nullptr, -1) == static_cast(errorBase)); + Require(sceOpusDecDecode(invalidState.data(), nullptr, -1, nullptr, -1) == static_cast(errorBase + 6)); + Require(sceOpusDecDecode(invalidState.data(), &packet, -1, nullptr, -1) == static_cast(errorBase + 7)); + Require(sceOpusDecDecode(invalidState.data(), &packet, 1, nullptr, -1) == static_cast(errorBase + 8)); + Require(sceOpusDecDecode(invalidState.data(), &packet, 1, &output, -1) == static_cast(errorBase + 9)); + Require(sceOpusDecDecode(invalidState.data(), &packet, 1, &output, 2) == static_cast(errorBase + 1)); + Require(output == 12345); + Require(sceOpusDecInitialize(&otherContext) == 0 && context != otherContext); RequireThrows([&] { sceOpusDecInitialize(&context); }); Require(sceOpusDecGetSize(1) == 640 && sceOpusDecGetSize(2) == 640); @@ -60,10 +116,10 @@ int main() { Require(sceOpusDecCreateEx(&context, monoState.data(), 48000, 1) == 0); RequireThrows([&] { sceOpusDecTerminate(&context); }); RequireThrows([&] { sceOpusDecCreateEx(&context, state.data(), 48000, 2); }); - RequireThrows([&] { sceOpusDecGetSize(3); }); + Require(sceOpusDecGetSize(3) == static_cast(errorBase + 3)); std::array unsupported{}; for (int rate : {0, 22050, 44100, 96000}) - RequireThrows([&] { sceOpusDecCreateEx(&context, unsupported.data(), rate, 2); }); + Require(sceOpusDecCreateEx(&context, unsupported.data(), rate, 2) == static_cast(errorBase + 2)); std::array pcm{}, reference{}; pcm.fill(12345); RequireThrows([&] { sceOpusDecDecode(state.data(), OPUS_STEREO + 2, 70, pcm.data() + 1, 3839); }); @@ -119,8 +175,8 @@ int main() { } Require(sceOpusDecDestroy(state.data()) == 0); - RequireThrows([&] { sceOpusDecDestroy(state.data()); }); - RequireThrows([&] { sceOpusDecDecode(state.data(), OPUS_STEREO + 2, 70, pcm.data(), 3840); }); + Require(sceOpusDecDestroy(state.data()) == static_cast(errorBase + 1)); + Require(sceOpusDecDecode(state.data(), OPUS_STEREO + 2, 70, pcm.data(), 3840) == static_cast(errorBase + 1)); Require(sceOpusDecCreateEx(&context, state.data(), 48000, 2) == 0); Require(sceOpusDecDecode(state.data(), OPUS_STEREO + 2, 70, pcm.data(), 3840) == 3840); std::array rejected; diff --git a/core/libs/tests/GuestOverflowError.cpp b/core/libs/tests/GuestOverflowError.cpp index 9bfb21a082..7b7e633934 100644 --- a/core/libs/tests/GuestOverflowError.cpp +++ b/core/libs/tests/GuestOverflowError.cpp @@ -1,10 +1,11 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include struct TypeRecord { const void* const* vtable; const char* name; const TypeRecord* base; }; -struct Object { const void* vtable; const char* message; }; + struct Vtable { std::ptrdiff_t offset; const TypeRecord* type; const void* destroy; const void* deleteObject; const void* what; }; extern "C" { @@ -12,8 +13,7 @@ extern const TypeRecord _ZTISt13runtime_error_nid_postfix; extern const TypeRecord _ZTISt14overflow_error_nid_postfix; extern const Vtable _ZTVSt13runtime_error_nid_postfix; extern const Vtable _ZTVSt14overflow_error_nid_postfix; -void APS5_VABI _ZNSt13runtime_errorC1EPKc_nid_postfix(Object*, const char*); -void APS5_VABI _ZNSt14overflow_errorD1Ev_nid_postfix(Object*); + } static void Require(bool value) { if (!value) std::abort(); } @@ -27,12 +27,15 @@ int main() { const auto& runtimeTable = _ZTVSt13runtime_error_nid_postfix; Require(table.offset == 0 && table.type == &type); Require(table.destroy == runtimeTable.destroy && table.deleteObject == runtimeTable.deleteObject && table.what == runtimeTable.what); - Object object{}; + LibcException::ExceptionObject object{}; _ZNSt13runtime_errorC1EPKc_nid_postfix(&object, "value too large"); + Require(object.message != nullptr && std::strcmp(object.message, "value too large") == 0); _ZNSt14overflow_errorD1Ev_nid_postfix(&object); + Require(object.message == nullptr); _ZNSt14overflow_errorD1Ev_nid_postfix(&object); + Require(object.message == nullptr); return 0; } diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp index 335c38cea7..beab5d7f6a 100644 --- a/core/libs/tests/GuestPad.cpp +++ b/core/libs/tests/GuestPad.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -13,30 +14,6 @@ #include #include -extern "C" { -int APS5_VABI scePadOpen_nid_postfix(int, int, int, const void*); -int APS5_VABI scePadOpenExt(int, int, int, const void*); -int APS5_VABI scePadClose_nid_postfix(int); -int APS5_VABI scePadGetHandle(int, int, int); -int APS5_VABI scePadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool); -int APS5_VABI scePadSetVibrationMode(int, int); -int APS5_VABI scePadInit_nid_postfix(void); -int APS5_VABI scePadUnknown_n3kSX62fgNo(); -int APS5_VABI scePadRead_nid_postfix(int, PadData*, int); -int APS5_VABI scePadReadState(int, PadData*); -int APS5_VABI scePadSetTiltCorrectionState(int, bool); -int APS5_VABI scePadResetOrientation(int); -int APS5_VABI scePadSetAngularVelocityDeadbandState(int, bool); -int APS5_VABI scePadSetAngularVelocityBiasCorrectionState(int, bool); -int APS5_VABI scePadIsRemoteController(int, bool*); -int APS5_VABI scePadGetAccessibilityControllerState(int, std::uint32_t*); -int APS5_VABI scePadGetControllerInformation(int, PadControllerInformation*); -int APS5_VABI scePadGetExtControllerInformation(int, void*); -int APS5_VABI scePadResetLightBar(int); -int APS5_VABI scePadSetLightBar(int, const PadLightBarParam*); -int APS5_VABI scePadSetMotionSensorState(int, bool); -} - static void Require(bool value) { if (!value) std::abort(); } static void CheckInputCommands() { @@ -149,6 +126,17 @@ static void CheckAngularVelocityDeadband(int handle) { PadPublishInput_nid_postfix(PadInputState{}); } +static void CheckStickDeadZones(int handle) { + PadControllerInformation info{}; + Require(scePadGetControllerInformation(handle, &info) == PAD_OK); + Require(info.stickInfo.deadZoneLeft == 13 && info.stickInfo.deadZoneRight == 13); + std::array extended{}; + Require(scePadGetExtControllerInformation(handle, extended.data()) == PAD_OK); + PadControllerInformation base{}; + std::memcpy(&base, extended.data(), sizeof(base)); + Require(base.stickInfo.deadZoneLeft == 13 && base.stickInfo.deadZoneRight == 13); +} + static void CheckTouchContact() { PadInputState touch; touch.buttons = static_cast(Pad::PadButton::TouchPad); @@ -296,6 +284,99 @@ static void CheckReadStateHandle(int handle) { Require(scePadReadState(handle, nullptr) == PAD_ERROR_INVALID_ARG); } +static void CheckSpecialPort(int user, int handle) { + constexpr int noHandle = static_cast(0x80920008); + constexpr int alreadyOpened = static_cast(0x80920004); + const int special = scePadOpen_nid_postfix(user, PAD_PORT_TYPE_SPECIAL, 0, nullptr); + Require(special > 0 && special != handle); + Require(scePadOpen_nid_postfix(user, PAD_PORT_TYPE_SPECIAL, 0, nullptr) == alreadyOpened); + Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == special); + Require(scePadGetHandle(user, 0, 0) == handle); + PadData data[2]; + std::memset(data, 0xff, sizeof(data)); + Require(scePadRead_nid_postfix(special, nullptr, 1) == PAD_ERROR_INVALID_ARG); + Require(scePadRead_nid_postfix(special, data, 0) == PAD_ERROR_INVALID_ARG); + Require(scePadRead_nid_postfix(special, data, 2) == 1); + Require(!data[0].connected && data[0].connected_count == 0 && data[0].buttons == 0 && data[0].timestamp == 0); + Require(data[0].left_stick_x == 0x80 && data[0].left_stick_y == 0x80 && data[0].right_stick_x == 0x80 && data[0].right_stick_y == 0x80); + Require(data[0].analog_buttons_l2 == 0 && data[0].orientation_x == 0.0f && data[0].orientation_w == 1.0f); + Require(data[0].acceleration_x == 0.0f && data[0].acceleration_y == 1.0f && data[0].acceleration_z == 0.0f && data[0].touch_data_touch_num == 0); + Require(data[1].buttons == 0xffffffff); + std::memset(data, 0xff, sizeof(data[0])); + Require(scePadReadState(special, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadReadState(special, data) == PAD_OK); + Require(!data[0].connected && data[0].connected_count == 0 && data[0].buttons == 0); + Require(data[0].left_stick_x == 0x80 && data[0].orientation_w == 1.0f && data[0].acceleration_y == 1.0f); + PadControllerInformation info; + std::memset(&info, 0xff, sizeof(info)); + Require(scePadGetControllerInformation(special, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetControllerInformation(special, &info) == PAD_OK); + Require(!info.connected && info.connectedCount == 0 && info.deviceClass == 0); + PadDeviceClassData classData; + std::memset(&classData, 0xff, sizeof(classData)); + Require(scePadDeviceClassParseData(special, data, &classData) == PAD_OK); + Require(!classData.dataValid && classData.deviceClass == 0); + PadDeviceClassExtendedInformation extended; + std::memset(&extended, 0xff, sizeof(extended)); + Require(scePadDeviceClassGetExtendedInformation(special, &extended) == PAD_OK); + Require(extended.deviceClass == -1 && extended.reserved[0] == 0); + Require(scePadDeviceClassGetExtendedInformation(handle, &extended) == PAD_OK); + Require(extended.deviceClass == 0); + std::int32_t specialType = -1; + Require(scePadGetControllerType(special, &specialType) == PAD_OK); + Require(specialType == PAD_CONTROLLER_TYPE_UNKNOWN); + const PadVibrationParam vibration{0, 0}; + const PadLightBarParam lightBar{0, 0, 255}; + Require(scePadSetVibration(special, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadSetVibration(special, &vibration) == PAD_OK); + Require(scePadSetLightBar(special, &lightBar) == PAD_OK); + Require(scePadResetLightBar(special) == PAD_OK); + Require(scePadSetMotionSensorState(special, true) == PAD_OK); + Require(scePadResetOrientation(special) == PAD_OK); + Require(scePadReadState(handle, data) == PAD_OK); + Require(data[0].connected); + Require(scePadGetControllerInformation(handle, &info) == PAD_OK); + Require(info.connected && info.connectedCount == 1); + Require(scePadClose_nid_postfix(special) == PAD_OK); + Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == noHandle); + Require(scePadClose_nid_postfix(special) == PAD_ERROR_INVALID_HANDLE); + Require(scePadRead_nid_postfix(special, data, 1) == PAD_ERROR_INVALID_HANDLE); + Require(scePadReadState(special, data) == PAD_ERROR_INVALID_HANDLE); + Require(scePadGetControllerInformation(special, &info) == PAD_ERROR_INVALID_HANDLE); + Require(scePadGetHandle(user, 0, 0) == handle); + Require(scePadReadState(handle, data) == PAD_OK); + const int other = user + 1; + const int first = scePadOpen_nid_postfix(user, PAD_PORT_TYPE_SPECIAL, 0, nullptr); + const int second = scePadOpen_nid_postfix(other, PAD_PORT_TYPE_SPECIAL, 0, nullptr); + Require(first > 0 && second > 0 && first != second && second != handle); + Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == first); + Require(scePadGetHandle(other, PAD_PORT_TYPE_SPECIAL, 0) == second); + Require(scePadOpen_nid_postfix(other, PAD_PORT_TYPE_SPECIAL, 0, nullptr) == alreadyOpened); + Require(scePadClose_nid_postfix(first) == PAD_OK); + Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == noHandle); + Require(scePadGetHandle(other, PAD_PORT_TYPE_SPECIAL, 0) == second); + Require(scePadRead_nid_postfix(first, data, 1) == PAD_ERROR_INVALID_HANDLE); + Require(scePadRead_nid_postfix(second, data, 1) == 1); + Require(!data[0].connected); + const int again = scePadOpen_nid_postfix(user, PAD_PORT_TYPE_SPECIAL, 0, nullptr); + Require(again > 0 && again != second); + Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == again); + Require(scePadClose_nid_postfix(again) == PAD_OK); + Require(scePadClose_nid_postfix(second) == PAD_OK); + Require(scePadGetHandle(other, PAD_PORT_TYPE_SPECIAL, 0) == noHandle); + std::array handles{}; + for (int i = 0; i < 16; ++i) { + handles[i] = scePadOpen_nid_postfix(user + i, PAD_PORT_TYPE_SPECIAL, 0, nullptr); + Require(handles[i] > 0 && handles[i] != handle && (i == 0 || handles[i] != handles[i - 1])); + } + Require(scePadOpen_nid_postfix(user + 16, PAD_PORT_TYPE_SPECIAL, 0, nullptr) == static_cast(0x809b0081)); + Require(scePadGetHandle(user + 16, PAD_PORT_TYPE_SPECIAL, 0) == noHandle); + Require(scePadGetHandle(user + 15, PAD_PORT_TYPE_SPECIAL, 0) == handles[15]); + for (int i = 0; i < 16; ++i) Require(scePadClose_nid_postfix(handles[i]) == PAD_OK); + Require(scePadGetHandle(user + 15, PAD_PORT_TYPE_SPECIAL, 0) == noHandle); + Require(scePadGetHandle(user, 0, 0) == handle); +} + static void CheckRemoteController(int handle) { bool remote = true; Require(scePadIsRemoteController(handle + 1, &remote) == PAD_ERROR_INVALID_HANDLE); @@ -305,6 +386,48 @@ static void CheckRemoteController(int handle) { Require(!remote); } +static void CheckTrackerDevices(int handle) { + for (const int candidate : {handle, handle + 1}) { + Require(!scePadIsDS4Connected(candidate)); + Require(!scePadIsBlasterConnected(candidate)); + Require(!scePadIsMoveConnected(candidate)); + } +} + +static void CheckSystemRemote(int handle) { + constexpr int noHandle = static_cast(0x80920008); + Require(scePadGetHandle(PAD_USER_ID_SYSTEM, PAD_PORT_TYPE_REMOTE, 0) == noHandle); + const int remote = scePadOpen_nid_postfix(PAD_USER_ID_SYSTEM, PAD_PORT_TYPE_REMOTE, 0, nullptr); + Require(remote > 0 && remote != handle); + Require(scePadGetHandle(PAD_USER_ID_SYSTEM, PAD_PORT_TYPE_REMOTE, 0) == remote); + Require(scePadGetHandle(PAD_USER_ID_SYSTEM, PAD_PORT_TYPE_REMOTE, 1) == noHandle); + PadData drained[64]{}; + scePadRead_nid_postfix(handle, drained, 64); + PadInputState pressed; + pressed.buttons = static_cast(Pad::PadButton::Cross); + PadPublishInput_nid_postfix(pressed); + PadData data{}; + data.buttons = ~0u; + data.connected = true; + Require(scePadReadState(remote, &data) == PAD_OK); + Require(data.buttons == 0 && !data.connected); + Require(scePadReadState(remote, nullptr) == PAD_ERROR_INVALID_ARG); + PadData queued[4]{}; + queued[0].connected = true; + Require(scePadRead_nid_postfix(remote, queued, 4) == 1); + Require(queued[0].buttons == 0 && !queued[0].connected); + PadLightBarParam lightBar{1, 2, 3}; + Require(scePadSetLightBar(remote, &lightBar) == PAD_ERROR_INVALID_HANDLE); + Require(scePadReadState(handle, &data) == PAD_OK); + Require((data.buttons & pressed.buttons) != 0 && data.connected); + PadPublishInput_nid_postfix(PadInputState{}); + scePadRead_nid_postfix(handle, drained, 64); + Require(scePadClose_nid_postfix(remote) == PAD_OK); + Require(scePadGetHandle(PAD_USER_ID_SYSTEM, PAD_PORT_TYPE_REMOTE, 0) == noHandle); + Require(scePadReadState(remote, &data) == PAD_ERROR_INVALID_HANDLE); + Require(scePadReadState(handle, &data) == PAD_OK); +} + static void CheckAccessibilityControllerState(int handle) { std::uint32_t state = 7; Require(scePadGetAccessibilityControllerState(handle + 1, nullptr) == PAD_ERROR_INVALID_ARG); @@ -314,6 +437,15 @@ static void CheckAccessibilityControllerState(int handle) { Require(state == 0); } +static void CheckControllerType(int handle) { + std::int32_t type = -1; + Require(scePadGetControllerType(handle + 1, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetControllerType(handle + 1, &type) == PAD_ERROR_INVALID_HANDLE); + Require(type == -1); + Require(scePadGetControllerType(handle, &type) == PAD_OK); + Require(type == PAD_CONTROLLER_TYPE_DUAL_SENSE); +} + static void CheckInjectedState(const std::filesystem::path& path) { PadInputState physical; physical.buttons = 0x2000; @@ -372,12 +504,53 @@ static void CheckInjectedState(const std::filesystem::path& path) { PadPublishInput_nid_postfix(PadInputState{}); } +static void CheckInjectedQueueTimes(const std::filesystem::path& path) { + PadData drained[64]{}; + Pad::Read(drained, 64); + PadInputState physical; + physical.buttons = 0x2000; + PadPublishInput_nid_postfix(physical); + const auto before = Pad::ReadState(); + std::ofstream(path) << "5 16384 300\n"; + PadData pressed{}; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + do { + pressed = Pad::ReadState(); + if ((pressed.buttons & 0x4000) != 0) break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } while (std::chrono::steady_clock::now() < deadline); + Require(pressed.buttons == 0x6000 && pressed.timestamp > before.timestamp); + PadData queued{}; + Require(Pad::Read(&queued, 1) == 1); + Require(queued.buttons == pressed.buttons); + if (queued.timestamp < pressed.timestamp) { + std::fputs("queued input predates its injected button press\n", stderr); + std::abort(); + } + physical.sticks[0] = 130; + PadPublishInput_nid_postfix(physical); + std::this_thread::sleep_for(std::chrono::milliseconds(310)); + const auto released = Pad::ReadState(); + Require(released.buttons == 0x2000 && released.timestamp > pressed.timestamp); + Require(Pad::Read(&queued, 1) == 1); + Require(queued.buttons == released.buttons && queued.left_stick_x == 130); + if (queued.timestamp < released.timestamp) { + std::fputs("queued input predates its injected button release\n", stderr); + std::abort(); + } + PadPublishInput_nid_postfix(PadInputState{}); +} + int main() { CheckInputCommands(); std::uint32_t accessibilityState = 7; Require(scePadGetAccessibilityControllerState(PAD_HANDLE, nullptr) == PAD_ERROR_NOT_INITIALIZED); Require(scePadGetAccessibilityControllerState(PAD_HANDLE, &accessibilityState) == PAD_ERROR_NOT_INITIALIZED); Require(accessibilityState == 7); + std::int32_t controllerType = -1; + Require(scePadGetControllerType(PAD_HANDLE, nullptr) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetControllerType(PAD_HANDLE, &controllerType) == PAD_ERROR_NOT_INITIALIZED); + Require(controllerType == -1); Require(scePadUnknown_n3kSX62fgNo() == static_cast(0x80920005)); const auto path = std::filesystem::temp_directory_path() / ("anyps5-pad-state-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); struct Cleanup { @@ -419,37 +592,54 @@ int main() { Require(scePadGetHandle(user, 0, 0) == noHandle); Require(scePadOpen_nid_postfix(user, 1, 0, nullptr) == PAD_ERROR_INVALID_ARG); - Require(scePadOpen_nid_postfix(user, 0, 1, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadOpen_nid_postfix(user, 0, 1, nullptr) == static_cast(0x809b0003)); + for (const int type : {0, 2, 3, 4}) Require(scePadOpen_nid_postfix(-1, type, 0, nullptr) == static_cast(0x809b0001)); + Require(scePadOpen_nid_postfix(-1, 0, 1, nullptr) == static_cast(0x809b0001)); + Require(scePadOpen_nid_postfix(-1, 1, 0, nullptr) == PAD_ERROR_INVALID_ARG); + for (const int type : {5, 6, 16, 0xff, -1}) Require(scePadOpen_nid_postfix(user, type, 0, nullptr) == PAD_ERROR_INVALID_ARG); Require(scePadGetHandle(user, 0, 0) == noHandle); const unsigned char wheel[16]{0xb7, 0x0e, 0x04, 0x0e, 0x08, 0x6e, 0x01}; Require(scePadOpenExt(user, PAD_PORT_TYPE_SPECIAL, 0, wheel) == static_cast(0x80920007)); Require(scePadOpenExt(user, PAD_PORT_TYPE_SPECIAL, 1, wheel) == PAD_ERROR_INVALID_ARG); Require(scePadOpenExt(user, PAD_PORT_TYPE_SPECIAL, 0, nullptr) == PAD_ERROR_INVALID_ARG); Require(scePadGetHandle(user, 0, 0) == noHandle); - Require(scePadOpen_nid_postfix(user, PAD_PORT_TYPE_SPECIAL, 0, nullptr) == static_cast(0x80920007)); + const int earlySpecial = scePadOpen_nid_postfix(user, PAD_PORT_TYPE_SPECIAL, 0, nullptr); + Require(earlySpecial > 0); Require(scePadGetHandle(user, 0, 0) == noHandle); + Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == earlySpecial); + Require(scePadClose_nid_postfix(earlySpecial) == PAD_OK); + Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == noHandle); + PadSetPortConnected_nid_postfix(1, true); Require(scePadInit_nid_postfix() == 0); + PadInfo initInfo{}; + Require(scePadGetInfo_nid_postfix(&initInfo) == PAD_OK); + Require(initInfo.connectedCount[0] == 2); + Require(initInfo.padTypeInfo[0].connectPort == 0); + Require(initInfo.padTypeInfo[1].connectPort == 1); + PadSetPortConnected_nid_postfix(1, false); Require(scePadUnknown_n3kSX62fgNo() == PAD_OK); const int handle = scePadOpen_nid_postfix(user, 0, 0, nullptr); Require(handle > 0); Require(scePadUnknown_n3kSX62fgNo() == PAD_OK); Require(scePadGetHandle(user, 0, 0) == handle); - Require(scePadOpen_nid_postfix(user, PAD_PORT_TYPE_SPECIAL, 0, nullptr) == static_cast(0x80920007)); - Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == noHandle); - Require(scePadGetHandle(user, 0, 0) == handle); + CheckSpecialPort(user, handle); CheckTiltCorrection(handle); CheckAngularVelocityDeadband(handle); CheckTouchContact(); CheckVirtualTouchContact(); CheckTouchPadInformation(handle); + CheckStickDeadZones(handle); CheckPartialTrigger(); CheckReadQueue(handle); CheckTapBetweenReads(); CheckLatchedPressLifetime(handle); CheckReadStateHandle(handle); CheckRemoteController(handle); + CheckTrackerDevices(handle); CheckAccessibilityControllerState(handle); + CheckControllerType(handle); CheckInjectedState(commandPath); + CheckInjectedQueueTimes(commandPath); PadLightBarParam lightBar{255, 128, 64}; Require(scePadSetLightBar(handle, &lightBar) == PAD_OK); Require(scePadSetLightBar(handle, nullptr) == PAD_ERROR_INVALID_ARG); @@ -466,7 +656,7 @@ int main() { Require(scePadSetVibrationMode(handle, 5) == 0); Require(scePadSetVibrationMode(handle, 6) == PAD_ERROR_INVALID_ARG); Require(scePadSetVibrationMode(handle, -1) == PAD_ERROR_INVALID_ARG); - Require(scePadGetHandle(0xff, 16, 0) == handle); + CheckSystemRemote(handle); Require(scePadGetHandle(user, 16, 0) == noHandle); Require(scePadGetHandle(user, 0, 1) == noHandle); Require(scePadClose_nid_postfix(handle) == 0); @@ -478,4 +668,45 @@ int main() { Require(scePadSetAngularVelocityDeadbandState(handle + 1, true) == PAD_ERROR_INVALID_HANDLE); Require(scePadSetAngularVelocityBiasCorrectionState(handle, false) == PAD_OK); Require(scePadSetAngularVelocityBiasCorrectionState(handle + 1, false) == PAD_ERROR_INVALID_HANDLE); + PadInfo info{}; + Require(scePadGetInfo_nid_postfix(&info) == PAD_OK); + Require(info.maxConnectCount == PAD_MAX_CONTROLLERS); + Require(info.connectedCount[0] == 1); + Require(info.padTypeInfo[0].connectPort == 0); + + const int user2 = 0x10000001; + Require(scePadGetHandle(user2, 0, 0) == noHandle); + const int handle2 = scePadOpen_nid_postfix(user2, 0, 0, nullptr); + Require(handle2 == PAD_HANDLE_BASE + 1); + Require(scePadGetHandle(user2, 0, 0) == handle2); + + PadInputState p2Input{}; + p2Input.buttons = static_cast(Pad::PadButton::Triangle); + PadPublishInputForPort_nid_postfix(1, p2Input); + PadData p2Data{}; + Require(scePadReadState(handle2, &p2Data) == 0); + Require((p2Data.buttons & static_cast(Pad::PadButton::Triangle)) != 0); + + Require(scePadSetVibrationMode(handle2, 1) == 0); + std::uint32_t p2AccessState = 7; + Require(scePadGetAccessibilityControllerState(handle2, &p2AccessState) == PAD_OK); + Require(p2AccessState == 0); + std::int32_t p2Type = -1; + Require(scePadGetControllerType(handle2, &p2Type) == PAD_OK); + Require(p2Type == PAD_CONTROLLER_TYPE_DUAL_SENSE); + + PadSetPortConnected_nid_postfix(1, true); + Require(scePadGetInfo_nid_postfix(&info) == PAD_OK); + Require(info.connectedCount[0] == 2); + Require(info.padTypeInfo[0].connectPort == 0); + Require(info.padTypeInfo[1].connectPort == 1); + PadSetPortConnected_nid_postfix(1, false); + Require(scePadGetInfo_nid_postfix(&info) == PAD_OK); + Require(info.connectedCount[0] == 1); + Require(info.padTypeInfo[0].connectPort == 0); + + Require(scePadClose_nid_postfix(handle2) == 0); + Require(scePadGetHandle(user2, 0, 0) == noHandle); + Require(scePadSetAngularVelocityDeadbandState(handle2, false) == PAD_OK); + Require(scePadSetAngularVelocityDeadbandState(handle2 + 1, false) == PAD_ERROR_INVALID_HANDLE); } diff --git a/core/libs/tests/GuestPadVibrationMode.cpp b/core/libs/tests/GuestPadVibrationMode.cpp index 1d826fedb1..4b54861ff0 100644 --- a/core/libs/tests/GuestPadVibrationMode.cpp +++ b/core/libs/tests/GuestPadVibrationMode.cpp @@ -3,13 +3,6 @@ #include #include -extern "C" { -int APS5_VABI scePadInit_nid_postfix(void); -int APS5_VABI scePadOpen_nid_postfix(int userId, int type, int index, const void* param); -int APS5_VABI scePadClose_nid_postfix(int handle); -int APS5_VABI scePadSetVibrationMode(int handle, int mode); -} - static void Require(bool value) { if (!value) std::abort(); } int main() { @@ -29,5 +22,10 @@ int main() { Require(handle > 0); Require(scePadSetVibrationMode(handle, 0) == PAD_ERROR_INVALID_ARG); Require(scePadSetVibrationMode(handle, 1) == PAD_OK); + Require(scePadSetVibrationMode(handle, 2) == PAD_OK); + Require(scePadSetVibrationMode(handle, 3) == PAD_OK); + Require(scePadSetVibrationMode(handle, 4) == PAD_ERROR_INVALID_ARG); + Require(scePadSetVibrationMode(handle, 5) == PAD_OK); + Require(scePadSetVibrationMode(handle, 6) == PAD_ERROR_INVALID_ARG); Require(scePadClose_nid_postfix(handle) == PAD_OK); } diff --git a/core/libs/tests/GuestPageTableStats.cpp b/core/libs/tests/GuestPageTableStats.cpp index eba38dd12c..18b33b9f39 100644 --- a/core/libs/tests/GuestPageTableStats.cpp +++ b/core/libs/tests/GuestPageTableStats.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" @@ -7,17 +8,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelGetPageTableStats(int*, int*, int*, int*); -int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); -int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t); -int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -} - static void Require(bool condition, std::source_location location = std::source_location::current()) { if (!condition) { std::fprintf(stderr, "Page table stats check failed at %s:%u\n", location.file_name(), static_cast(location.line())); @@ -76,7 +66,7 @@ static void CheckCpuGranules() { RequireTaken(before, 1, 0); Map(base + PageTableGranule, 0x4000, 0); RequireTaken(before, 2, 0); - Require(sceKernelMunmap(base, GpuDirectorySpan * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(base), GpuDirectorySpan * 2) == 0); RequireTaken(before, 0, 0); } @@ -89,7 +79,7 @@ static void CheckGpuEntries() { RequireTaken(before, 3, 4); Map(base + GpuDirectorySpan, 0x4000, 0x33); RequireTaken(before, 4, 6); - Require(sceKernelMunmap(base, GpuDirectorySpan * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(base), GpuDirectorySpan * 2) == 0); RequireTaken(before, 0, 0); } @@ -106,13 +96,26 @@ static void CheckGpuDirectMapping() { Require(sceKernelMapDirectMemory(&small, 0x10000, 0x33, 0x10, phys + static_cast(2 * PageTableGranule), 0x10000) == 0); Require(small == base + 2 * PageTableGranule + 0x10000); RequireTaken(before, 3, 2); - Require(sceKernelMunmap(base, GpuDirectorySpan * 2) == 0); + Require(sceKernelMunmap(reinterpret_cast(base), GpuDirectorySpan * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, 3 * PageTableGranule) == 0); RequireTaken(before, 0, 0); } +static void CheckTraceMemoryStats() { + std::uint64_t first[2] = {0xaaaaaaaaaaaaaaaaull, 0xaaaaaaaaaaaaaaaaull}; + std::uint64_t second[2] = {0xaaaaaaaaaaaaaaaaull, 0xaaaaaaaaaaaaaaaaull}; + Require(sceKernelGetTraceMemoryStats(first, second) == 0); + Require(first[0] == 0 && first[1] == 0xaaaaaaaaaaaaaaaaull && second[0] == 0 && second[1] == 0xaaaaaaaaaaaaaaaaull); + first[0] = 1; + Require(sceKernelGetTraceMemoryStats(first, nullptr) == 0 && first[0] == 0); + second[0] = 1; + Require(sceKernelGetTraceMemoryStats(nullptr, second) == 0 && second[0] == 0); + Require(sceKernelGetTraceMemoryStats(nullptr, nullptr) == 0); +} + int main() { CheckRejectedArguments(); + CheckTraceMemoryStats(); CheckCpuGranules(); CheckGpuEntries(); CheckGpuDirectMapping(); diff --git a/core/libs/tests/GuestParentProcess.cpp b/core/libs/tests/GuestParentProcess.cpp index 203cfe3a00..6098a0766a 100644 --- a/core/libs/tests/GuestParentProcess.cpp +++ b/core/libs/tests/GuestParentProcess.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #ifdef _WIN32 @@ -5,12 +7,7 @@ #else #include #endif -extern "C" { -int APS5_VABI getpid_nid_postfix(void); -int APS5_VABI getppid_nid_postfix(void); -int APS5_VABI getsid_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} + static void Require(bool value) { if (!value) std::abort(); } int main() { const int self = getpid_nid_postfix(); @@ -21,6 +18,72 @@ int main() { Require(*__error_nid_postfix() == 3); Require(getsid_nid_postfix(-1) == -1); Require(*__error_nid_postfix() == 3); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(0, 0) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(self, 0) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(0, self) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(self, self) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(0, 123456) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(self, 123456) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(-1, -1) == -1); + Require(*__error_nid_postfix() == 22); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(0, -1) == -1); + Require(*__error_nid_postfix() == 22); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(self, -1) == -1); + Require(*__error_nid_postfix() == 22); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(-1, 0) == -1); + Require(*__error_nid_postfix() == 3); + *__error_nid_postfix() = 0; + Require(setpgid_nid_postfix(123456, 0) == -1); + Require(*__error_nid_postfix() == 3); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(0, 0) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(self, 0) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(0, self) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(self, self) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(0, 123456) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(self, 123456) == -1); + Require(*__error_nid_postfix() == 1); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(-1, -1) == -1); + Require(*__error_nid_postfix() == 22); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(0, -1) == -1); + Require(*__error_nid_postfix() == 22); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(self, -1) == -1); + Require(*__error_nid_postfix() == 22); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(-1, 0) == -1); + Require(*__error_nid_postfix() == 3); + *__error_nid_postfix() = 0; + Require(setpgrp_nid_postfix(123456, 0) == -1); + Require(*__error_nid_postfix() == 3); const int parent = getppid_nid_postfix(); Require(parent > 0 && parent != getpid_nid_postfix()); Require(getppid_nid_postfix() == parent); diff --git a/core/libs/tests/GuestPathCase.cpp b/core/libs/tests/GuestPathCase.cpp index 18b87e9c6e..675bd28809 100644 --- a/core/libs/tests/GuestPathCase.cpp +++ b/core/libs/tests/GuestPathCase.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -8,21 +10,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t); -int APS5_VABI sceKernelStat(const char*, FileStat*); -int APS5_VABI chdir_nid_postfix(const char*); -int APS5_VABI access_nid_postfix(const char*, int); -int APS5_VABI rename_nid_postfix(const char*, const char*); -int APS5_VABI remove_nid_postfix(const char*); -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*); -int APS5_VABI fclose_nid_postfix(FileStream*); -} - static void Require(bool value) { if (!value) std::abort(); } static void CheckRead(const std::string& path, char expected) { diff --git a/core/libs/tests/GuestPathErrors.cpp b/core/libs/tests/GuestPathErrors.cpp index 3da76d9739..9eb21a8c7a 100644 --- a/core/libs/tests/GuestPathErrors.cpp +++ b/core/libs/tests/GuestPathErrors.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -12,26 +15,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode); -int APS5_VABI sceKernelUnlink(const char* path); -int APS5_VABI sceKernelClose(int d); -int APS5_VABI open_nid_postfix(const char* path, int flags, int mode); -int APS5_VABI unlink_nid_postfix(const char* path); -int APS5_VABI sceKernelStat(const char* path, FileStat* sb); -int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb); -int APS5_VABI lstat_nid_postfix(const char* path, FileStat* sb); -int APS5_VABI sceKernelMkdir(const char* path, std::uint16_t mode); -int APS5_VABI sceKernelRmdir(const char* path); -int APS5_VABI mkdir_nid_postfix(const char* path, std::uint16_t mode); -int APS5_VABI rmdir_nid_postfix(const char* path); -int APS5_VABI sceKernelRename(const char* from, const char* to); -int APS5_VABI rename_nid_postfix(const char* from, const char* to); -int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t length); -int APS5_VABI truncate_nid_postfix(const char* path, std::int64_t length); -int* APS5_VABI __error_nid_postfix(); -} - namespace { void Require(bool value, int line) { diff --git a/core/libs/tests/GuestPathLifetime.cpp b/core/libs/tests/GuestPathLifetime.cpp index 44e8f59072..041de1ffb8 100644 --- a/core/libs/tests/GuestPathLifetime.cpp +++ b/core/libs/tests/GuestPathLifetime.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/Shutdown.hpp" #include @@ -9,8 +10,6 @@ #include #include -extern "C" char* APS5_VABI getcwd_nid_postfix(char*, std::size_t); - namespace { int events[2]; diff --git a/core/libs/tests/GuestPathNameLength.cpp b/core/libs/tests/GuestPathNameLength.cpp index e07722661b..c6197d2e8f 100644 --- a/core/libs/tests/GuestPathNameLength.cpp +++ b/core/libs/tests/GuestPathNameLength.cpp @@ -1,22 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" { -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI mkdir_nid_postfix(const char*, int); -int APS5_VABI rmdir_nid_postfix(const char*); -int APS5_VABI unlink_nid_postfix(const char*); -int APS5_VABI rename_nid_postfix(const char*, const char*); -int APS5_VABI stat_nid_postfix(const char*, void*); -int APS5_VABI access_nid_postfix(const char*, int); -int APS5_VABI chflags_nid_postfix(const char*, std::uint64_t); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int ErrorNoEntry = 2; @@ -32,7 +21,7 @@ bool Fails(long result, int error) { return result == -1 && *__error_nid_postfix bool TooLong(const std::string& path) { unsigned char status[256]; - return Fails(stat_nid_postfix(path.c_str(), status), ErrorNameTooLong); + return Fails(stat_nid_postfix(path.c_str(), reinterpret_cast(status)), ErrorNameTooLong); } } @@ -42,11 +31,12 @@ int main() { const std::string longName(256, 'n'); const std::string longPath(1100, 'p'); const std::string maxName(255, 'm'); - Require(Fails(stat_nid_postfix(longName.c_str(), status), ErrorNameTooLong), "stat of a name above NAME_MAX (255) must fail with ENAMETOOLONG (63)"); + Require(Fails(stat_nid_postfix(longName.c_str(), reinterpret_cast(status)), ErrorNameTooLong), "stat of a name above NAME_MAX (255) must fail with ENAMETOOLONG (63)"); rmdir_nid_postfix("anyps5_name_length"); + Require(mkdir_nid_postfix("anyps5_name_length", 0755) == 0, "mkdir of the test directory failed"); - Require(Fails(stat_nid_postfix(("anyps5_name_length/" + longName).c_str(), status), ErrorNameTooLong), "stat of a long final component below an existing directory must fail with ENAMETOOLONG (63)"); - Require(Fails(stat_nid_postfix(("anyps5_name_length/missing/" + longName).c_str(), status), ErrorNoEntry), "stat of a long component below a missing directory must fail with ENOENT (2), the lookup stops there first"); + Require(Fails(stat_nid_postfix(("anyps5_name_length/" + longName).c_str(), reinterpret_cast(status)), ErrorNameTooLong), "stat of a long final component below an existing directory must fail with ENAMETOOLONG (63)"); + Require(Fails(stat_nid_postfix(("anyps5_name_length/missing/" + longName).c_str(), reinterpret_cast(status)), ErrorNoEntry), "stat of a long component below a missing directory must fail with ENOENT (2), the lookup stops there first"); const std::string below = "anyps5_name_length/"; const std::string name255(255, 'n'); Require(!TooLong(below + name255), "a final component of exactly 255 bytes must not be too long"); @@ -55,11 +45,13 @@ int main() { Require(TooLong(below + longName + "/"), "a long component followed by a trailing slash must fail with ENAMETOOLONG (63)"); Require(!TooLong(below + std::string(200, 'n') + "\\" + std::string(100, 'n')), "a backslash separates components here, so 200 + 100 bytes around it are accepted (FreeBSD would refuse the 301-byte name)"); const std::string block = name255 + "/"; +#ifndef __APPLE__ Require(!TooLong(block + block + block + name255), "a path of exactly 1023 bytes must not be too long"); +#endif Require(TooLong(block + block + block + block), "a path of exactly 1024 bytes must fail with ENAMETOOLONG (63)"); Require(rmdir_nid_postfix("anyps5_name_length") == 0, "rmdir of the test directory failed"); - Require(Fails(stat_nid_postfix((longName + "/x").c_str(), status), ErrorNameTooLong), "stat of a long first component must fail with ENAMETOOLONG (63)"); - Require(Fails(stat_nid_postfix(longPath.c_str(), status), ErrorNameTooLong), "stat of a path of 1100 bytes must fail with ENAMETOOLONG (63)"); + Require(Fails(stat_nid_postfix((longName + "/x").c_str(), reinterpret_cast(status)), ErrorNameTooLong), "stat of a long first component must fail with ENAMETOOLONG (63)"); + Require(Fails(stat_nid_postfix(longPath.c_str(), reinterpret_cast(status)), ErrorNameTooLong), "stat of a path of 1100 bytes must fail with ENAMETOOLONG (63)"); Require(Fails(access_nid_postfix(longName.c_str(), 0), ErrorNameTooLong), "access of a long name must fail with ENAMETOOLONG (63)"); Require(Fails(chflags_nid_postfix(longName.c_str(), 0), ErrorNameTooLong), "chflags of a long name must fail with ENAMETOOLONG (63)"); Require(Fails(unlink_nid_postfix(longName.c_str()), ErrorNameTooLong), "unlink of a long name must fail with ENAMETOOLONG (63)"); @@ -69,6 +61,6 @@ int main() { Require(Fails(rename_nid_postfix(longName.c_str(), "anyps5_name_length_target"), ErrorNameTooLong), "rename from a long name must fail with ENAMETOOLONG (63)"); Require(Fails(rename_nid_postfix("anyps5_name_length_source", longName.c_str()), ErrorNameTooLong), "rename to a long name must fail with ENAMETOOLONG (63)"); - Require(Fails(stat_nid_postfix(maxName.c_str(), status), ErrorNoEntry), "a name of exactly 255 bytes is not too long"); + Require(Fails(stat_nid_postfix(maxName.c_str(), reinterpret_cast(status)), ErrorNoEntry), "a name of exactly 255 bytes is not too long"); return 0; } diff --git a/core/libs/tests/GuestPlayGo.cpp b/core/libs/tests/GuestPlayGo.cpp index 918e6ad031..529e3fd923 100644 --- a/core/libs/tests/GuestPlayGo.cpp +++ b/core/libs/tests/GuestPlayGo.cpp @@ -1,7 +1,10 @@ +#include "prx/libScePlayGo/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include +#include #include #include #include @@ -12,22 +15,12 @@ #include #include -extern "C" { -int APS5_VABI scePlayGoInitialize(const PlayGoInitParams*); -int APS5_VABI scePlayGoOpen(int*, const void*); -int APS5_VABI scePlayGoClose(int); -int APS5_VABI scePlayGoGetLanguageMask(int, std::uint64_t*); -int APS5_VABI scePlayGoSetLanguageMask(int, std::uint64_t); -int APS5_VABI scePlayGoGetSupportedOptionalChunk(int, std::int32_t, PlayGoOptionalChunk*); -int APS5_VABI scePlayGoGetOptionalChunk(int, std::int32_t, PlayGoOptionalChunk*); -int APS5_VABI scePlayGoSetToDoList(int, const PlayGoToDo*, std::uint32_t); -int APS5_VABI scePlayGoGetToDoList(int, PlayGoToDo*, std::uint32_t, std::uint32_t*); -int APS5_VABI scePlayGoGetLocus(int, const std::uint16_t*, std::uint32_t, std::int8_t*); -int APS5_VABI scePlayGoGetChunkId(int, std::uint16_t*, std::uint32_t, std::uint32_t*); +static void Require(bool value, const std::source_location location = std::source_location::current()) { + if (value) return; + std::fprintf(stderr, "requirement failed at line %u\n", location.line()); + std::abort(); } -static void Require(bool value) { if (!value) std::abort(); } - template static void RequireMissingChunkMetadata(Operation&& operation) { bool threw = false; @@ -67,16 +60,21 @@ static int CheckScenarioWithoutChunkTable(const char* root) { int handle = 0; Require(scePlayGoInitialize(&init) == 0); Require(scePlayGoOpen(&handle, nullptr) == 0); - const std::uint16_t anyIds[3] = {1, 10, 0xFFFF}; + const std::uint16_t listedIds[3] = {1, 10, 999}; std::int8_t loci[3] = {0, 0, 0}; - Require(scePlayGoGetLocus(handle, anyIds, 3, loci) == 0 && loci[0] == 3 && loci[1] == 3 && loci[2] == 3); - const PlayGoToDo todo[2] = {{0, 3, 0}, {0xFFFF, 3, 0}}; + Require(scePlayGoGetLocus(handle, listedIds, 3, loci) == 0 && loci[0] == 3 && loci[1] == 3 && loci[2] == 3); + const PlayGoToDo todo[2] = {{0, 3, 0}, {999, 3, 0}}; Require(scePlayGoSetToDoList(handle, todo, 2) == 0); + for (const std::uint16_t unlisted : {std::uint16_t{1000}, std::uint16_t{0xFFFF}}) { + std::int8_t locus = 0; + RequireMissingChunkMetadata([&] { scePlayGoGetLocus(handle, &unlisted, 1, &locus); }); + } std::uint32_t chunkCount = 0; Require(scePlayGoGetChunkId(handle, nullptr, 0, &chunkCount) == 0 && chunkCount == 1000); std::vector ids(chunkCount + 1, 0xFFFF); - Require(scePlayGoGetChunkId(handle, ids.data(), static_cast(ids.size()), &chunkCount) == 0 && chunkCount == 1000); + Require(scePlayGoGetChunkId(handle, ids.data(), chunkCount, &chunkCount) == 0 && chunkCount == 1000); for (std::uint32_t index = 0; index < chunkCount; ++index) Require(ids[index] == index); + Require(ids.back() == 0xFFFF); std::vector listedLoci(chunkCount, 0); Require(scePlayGoGetLocus(handle, ids.data(), chunkCount, listedLoci.data()) == 0); for (const auto locus : listedLoci) Require(locus == 3); @@ -116,6 +114,9 @@ static int CheckNoPlayGoFile(const char* root) { Require(getOptionalChunk(handle + 1, 0, &option) == badHandle); Require(getOptionalChunk(handle, 0, nullptr) == badPointer); Require(getOptionalChunk(handle, 0, &option) == 0 && option.bitmask == ~0ull); + Require(getOptionalChunk(handle, 3, &option) == static_cast(0x80B20024)); + Require(getOptionalChunk(handle, 0xFFFFFFFF, &option) == static_cast(0x80B20024)); + Require(getOptionalChunk(handle, 3, nullptr) == badPointer); } PlayGoToDo todo[2] = {{0, 3, 0}, {0, 0, 0}}; @@ -135,6 +136,12 @@ static int CheckNoPlayGoFile(const char* root) { const std::uint16_t chunk = 0; std::int8_t locus = 0; Require(scePlayGoGetLocus(handle, &chunk, 0, &locus) == badSize); + Require(scePlayGoGetLocus(handle, &chunk, 1001, &locus) == badSize); + std::uint32_t invalidListCount = 42; + std::uint16_t invalidListOutput = 0; + Require(scePlayGoGetChunkId(handle, &invalidListOutput, 0, &invalidListCount) == badSize); + Require(scePlayGoGetChunkId(handle, &invalidListOutput, 1001, &invalidListCount) == badSize); + Require(invalidListCount == 42); Require(scePlayGoGetLocus(handle, &chunk, 1, &locus) == 0 && locus == 3); const std::uint16_t undeclared[2] = {1, 10}; std::int8_t loci[2] = {0, 0}; @@ -159,6 +166,7 @@ static int CheckRejectedLayout(const char* root, const char* fileName) { bool threw = false; try { scePlayGoGetLocus(handle, &chunk, 1, &locus); + } catch (const std::runtime_error& error) { threw = std::string_view(error.what()).find(fileName) != std::string_view::npos; } @@ -193,6 +201,27 @@ static int CheckNoPlayGoFileAnswered(const char* root, const char* answer) { return 0; } +static int CheckScenarioWithoutChunkTableAnswered(const char* root, const char* answer) { + constexpr int badChunkId = static_cast(0x80B2000C); + SetUnlistedChunks(answer); + std::filesystem::current_path(root); + PlayGoInitParams init{}; + int handle = 0; + Require(scePlayGoInitialize(&init) == 0); + Require(scePlayGoOpen(&handle, nullptr) == 0); + const std::uint16_t unlisted[2] = {1000, 0xFFFF}; + std::int8_t loci[2] = {0, 0}; + const int result = scePlayGoGetLocus(handle, unlisted, 2, loci); + if (std::string_view(answer) == "installed") Require(result == 0 && loci[0] == 3 && loci[1] == 3); + else Require(result == badChunkId); + const std::uint16_t listed[2] = {0, 999}; + Require(scePlayGoGetLocus(handle, listed, 2, loci) == 0 && loci[0] == 3 && loci[1] == 3); + std::uint32_t chunkCount = 0; + Require(scePlayGoGetChunkId(handle, nullptr, 0, &chunkCount) == 0 && chunkCount == 1000); + Require(scePlayGoClose(handle) == 0); + return 0; +} + static int CheckUnknownUnlistedChunkAnswer(const char* root) { SetUnlistedChunks("maybe"); std::filesystem::current_path(root); @@ -205,6 +234,7 @@ static int CheckUnknownUnlistedChunkAnswer(const char* root) { bool threw = false; try { scePlayGoGetLocus(handle, &unlisted, 1, &locus); + } catch (const std::runtime_error& error) { threw = std::string_view(error.what()).find("APS5_PLAYGO_UNLISTED_CHUNKS must be installed or absent") != std::string_view::npos; } @@ -244,6 +274,8 @@ int main(int argc, char** argv) { if (mode == "--no-file-unknown-answer") return CheckUnknownUnlistedChunkAnswer(argv[2]); if (mode == "--package-chunk-table" || mode == "--scenario-with-chunk-table") return CheckPackageChunkTable(argv[2]); if (mode == "--scenario-only") return CheckScenarioWithoutChunkTable(argv[2]); + if (mode == "--scenario-only-installed") return CheckScenarioWithoutChunkTableAnswered(argv[2], "installed"); + if (mode == "--scenario-only-absent") return CheckScenarioWithoutChunkTableAnswered(argv[2], "absent"); if (mode == "--ficm-only") return CheckRejectedLayout(argv[2], "playgo-ficm.dat"); if (mode == "--hash-table-only") return CheckRejectedLayout(argv[2], "playgo-hash-table.dat"); if (mode == "--invalid-chunk-table") return CheckRejectedLayout(argv[2], "playgo-chunk.dat"); @@ -255,7 +287,25 @@ int main(int argc, char** argv) { RunLayout(argv[0], "--package-chunk-table", {{"playgo-chunk.dat", ChunkTableFile()}}); RunLayout(argv[0], "--package-chunk-table", {{"playgo-chunk.dat", ChunkTableFile("plgo")}}); RunLayout(argv[0], "--scenario-with-chunk-table", {{"playgo-chunk.dat", ChunkTableFile()}, {"playgo-scenario.json", "{}"}}); + std::int64_t eta = -1; + std::int32_t speed = -1; + PlayGoInitParams fixtureInit{}; + int validHandle = -1; + Require(scePlayGoInitialize(&fixtureInit) == 0); + Require(scePlayGoOpen(&validHandle, nullptr) == 0); + constexpr int badHandleError = static_cast(0x80B20009); + constexpr int badPointerError = static_cast(0x80B2000A); + Require(scePlayGoGetEta(-1, nullptr, 0, &eta) == badHandleError); + Require(scePlayGoGetEta(validHandle, nullptr, 0, nullptr) == badPointerError); + Require(scePlayGoGetInstallSpeed(-1, &speed) == badHandleError); + Require(scePlayGoGetInstallSpeed(0x506C476F, &speed) == badHandleError && speed == -1); + Require(scePlayGoGetInstallSpeed(validHandle, nullptr) == badPointerError); + Require(scePlayGoGetInstallSpeed(validHandle, &speed) == 0 && speed == 2); + Require(scePlayGoPrefetch(-1, nullptr, 0, 0) == badHandleError); + Require(scePlayGoClose(validHandle) == 0); RunLayout(argv[0], "--scenario-only", {{"playgo-scenario.json", "{}"}}); + RunLayout(argv[0], "--scenario-only-installed", {{"playgo-scenario.json", "{}"}}); + RunLayout(argv[0], "--scenario-only-absent", {{"playgo-scenario.json", "{}"}}); RunLayout(argv[0], "--ficm-only", {{"playgo-ficm.dat", "x"}}); RunLayout(argv[0], "--hash-table-only", {{"playgo-hash-table.dat", "x"}}); RunLayout(argv[0], "--invalid-chunk-table", {{"playgo-chunk.dat", "not a chunk table"}}); diff --git a/core/libs/tests/GuestPlayGoDialog.cpp b/core/libs/tests/GuestPlayGoDialog.cpp index 470cfee79d..e8a89d8e49 100644 --- a/core/libs/tests/GuestPlayGoDialog.cpp +++ b/core/libs/tests/GuestPlayGoDialog.cpp @@ -1,37 +1,31 @@ +#include "prx/libScePlayGoDialog/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -int APS5_VABI scePlayGoDialogInitialize(void); -int APS5_VABI scePlayGoDialogTerminate(void); -int APS5_VABI scePlayGoDialogOpen(const void* param); -int APS5_VABI scePlayGoDialogClose(void); -int APS5_VABI scePlayGoDialogUpdateStatus(void); -int APS5_VABI scePlayGoDialogGetStatus(void); -int APS5_VABI scePlayGoDialogGetResult(void* result); -} - static void Require(bool value) { if (!value) std::abort(); } int main() { - constexpr std::int32_t notInitialized = static_cast(0x80ED0001); - constexpr std::int32_t alreadyInitialized = static_cast(0x80ED0002); - constexpr std::int32_t paramInvalid = static_cast(0x80ED0003); - constexpr std::int32_t invalidState = static_cast(0x80ED0005); + constexpr std::int32_t notInitialized = static_cast(0x80B80003); + constexpr std::int32_t alreadyInitialized = static_cast(0x80B80004); + constexpr std::int32_t paramInvalid = static_cast(0x80B8000D); constexpr std::int32_t notFinished = static_cast(0x80B80005u); Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); Require(scePlayGoDialogClose() == notInitialized); Require(scePlayGoDialogOpen(nullptr) == paramInvalid); - Require(scePlayGoDialogGetResult(nullptr) == notInitialized); + Require(scePlayGoDialogGetResult(nullptr) == paramInvalid); const int openParam = 1; - Require(scePlayGoDialogOpen(&openParam) == invalidState); + Require(scePlayGoDialogOpen(&openParam) == notInitialized); + PlayGoDialogResult early{}; + Require(scePlayGoDialogGetResult(&early) == notInitialized); + early.reserved[31] = 1; + Require(scePlayGoDialogGetResult(&early) == static_cast(0x80B8000Au)); Require(scePlayGoDialogInitialize() == 0); Require(scePlayGoDialogGetStatus() == 1); Require(scePlayGoDialogInitialize() == alreadyInitialized); @@ -47,8 +41,13 @@ int main() { PlayGoDialogResult result{}; Require(scePlayGoDialogGetResult(&result) == 0); - Require(result.result == 0); - Require(scePlayGoDialogClose() == 0); + Require(result.result == 0 && result.errorCode == 0); + result.errorCode = 7; + result.result = 9; + result.reserved[0] = 1; + Require(scePlayGoDialogGetResult(&result) == static_cast(0x80B8000Au)); + Require(result.errorCode == 7 && result.result == 9 && result.reserved[0] == 1); + Require(scePlayGoDialogClose() == static_cast(0x80B8000Bu)); Require(scePlayGoDialogTerminate() == 0); Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); diff --git a/core/libs/tests/GuestPlayerReviewDialog.cpp b/core/libs/tests/GuestPlayerReviewDialog.cpp index c7917268a4..c14e478429 100644 --- a/core/libs/tests/GuestPlayerReviewDialog.cpp +++ b/core/libs/tests/GuestPlayerReviewDialog.cpp @@ -1,24 +1,16 @@ +#include "prx/libSceCdlgPlayerReview/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include "prx/libSceUserService/UserService.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include -extern "C" { -int APS5_VABI scePlayerReviewDialogInitialize(void); -int APS5_VABI scePlayerReviewDialogTerminate(void); -int APS5_VABI scePlayerReviewDialogOpen(const void* param); -int APS5_VABI scePlayerReviewDialogClose(void); -int APS5_VABI scePlayerReviewDialogGetStatus(void); -int APS5_VABI scePlayerReviewDialogUpdateStatus(void); -int APS5_VABI scePlayerReviewDialogGetResult(void* result); -} - namespace { constexpr int COMMON_DIALOG_STATUS_NONE = 0; constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); @@ -28,19 +20,26 @@ void Require(bool value) { if (!value) std::abort(); } } int main() { - std::uint8_t param[64] = {}; - std::int32_t result[8] = {}; + Require(sceUserServiceInitialize(nullptr) == 0); + PlayerReviewDialogParam 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; + std::int32_t result[4] = {-1, -1, -1, -1}; Require(scePlayerReviewDialogTerminate() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(scePlayerReviewDialogOpen(nullptr) == static_cast(0x80B8000Du)); + Require(scePlayerReviewDialogGetResult(nullptr) == static_cast(0x80B8000Du)); Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_NONE); - Require(scePlayerReviewDialogOpen(param) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(scePlayerReviewDialogOpen(¶m) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); Require(scePlayerReviewDialogClose() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); - Require(scePlayerReviewDialogGetResult(result) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(scePlayerReviewDialogGetResult(reinterpret_cast(result)) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); Require(scePlayerReviewDialogInitialize() == 0); Require(scePlayerReviewDialogInitialize() == COMMON_DIALOG_ERROR_ALREADY_INITIALIZED); Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_INITIALIZED); Require(scePlayerReviewDialogUpdateStatus() == COMMON_DIALOG_STATUS_INITIALIZED); - Require(scePlayerReviewDialogGetResult(result) == COMMON_DIALOG_ERROR_NOT_FINISHED); + Require(scePlayerReviewDialogGetResult(reinterpret_cast(result)) == COMMON_DIALOG_ERROR_NOT_FINISHED); Require(scePlayerReviewDialogTerminate() == 0); Require(scePlayerReviewDialogTerminate() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_NONE); @@ -48,25 +47,52 @@ int main() { Require(scePlayerReviewDialogInitialize() == 0); Require(scePlayerReviewDialogUpdateStatus() == COMMON_DIALOG_STATUS_INITIALIZED); Require(scePlayerReviewDialogOpen(nullptr) == COMMON_DIALOG_ERROR_ARG_NULL); - Require(scePlayerReviewDialogOpen(param) == 0); + param.base.size = 0; + Require(scePlayerReviewDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.base.size = sizeof(param.base); + param.base.magic = 0; + Require(scePlayerReviewDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.base.magic = static_cast(reinterpret_cast(¶m)) - 1060003575u; + param.size = 0; + Require(scePlayerReviewDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.size = sizeof(param); + param.reserved[63] = 1; + Require(scePlayerReviewDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.reserved[63] = 0; + param.userId = -1; + Require(scePlayerReviewDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.userId = USER_SERVICE_INITIAL_USER_ID; + param.mode = 2; + Require(scePlayerReviewDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.mode = 0; + param.userId = 42; + Require(scePlayerReviewDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.userId = USER_SERVICE_INITIAL_USER_ID; + Require(sceUserServiceTerminate() == 0); + Require(scePlayerReviewDialogOpen(¶m) == static_cast(0x80B8000Au)); + Require(sceUserServiceInitialize(nullptr) == 0); + Require(scePlayerReviewDialogOpen(¶m) == 0); Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_FINISHED); Require(scePlayerReviewDialogUpdateStatus() == COMMON_DIALOG_STATUS_FINISHED); Require(scePlayerReviewDialogGetResult(nullptr) == COMMON_DIALOG_ERROR_ARG_NULL); - Require(scePlayerReviewDialogGetResult(result) == 0); - Require(result[0] == COMMON_DIALOG_RESULT_USER_CANCELED); - Require(scePlayerReviewDialogClose() == 0); + Require(scePlayerReviewDialogGetResult(reinterpret_cast(result)) == COMMON_DIALOG_RESULT_USER_CANCELED); + Require(result[0] == -1 && result[1] == -1 && result[3] == -1); + Require(result[2] == COMMON_DIALOG_RESULT_USER_CANCELED); + Require(scePlayerReviewDialogClose() == static_cast(0x80B8000Bu)); Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_FINISHED); Require(scePlayerReviewDialogUpdateStatus() == COMMON_DIALOG_STATUS_FINISHED); Require(scePlayerReviewDialogTerminate() == 0); Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_NONE); Require(scePlayerReviewDialogInitialize() == 0); Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_INITIALIZED); - Require(scePlayerReviewDialogClose() == 0); - Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_FINISHED); - Require(scePlayerReviewDialogUpdateStatus() == COMMON_DIALOG_STATUS_FINISHED); - result[0] = -1; - Require(scePlayerReviewDialogGetResult(result) == 0); - Require(result[0] == COMMON_DIALOG_RESULT_USER_CANCELED); + Require(scePlayerReviewDialogClose() == static_cast(0x80B8000Bu)); + Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_INITIALIZED); + Require(scePlayerReviewDialogUpdateStatus() == COMMON_DIALOG_STATUS_INITIALIZED); + result[2] = -1; + Require(scePlayerReviewDialogGetResult(reinterpret_cast(result)) == COMMON_DIALOG_ERROR_NOT_FINISHED); + Require(result[0] == -1 && result[1] == -1 && result[3] == -1); + Require(result[2] == -1); Require(scePlayerReviewDialogTerminate() == 0); Require(scePlayerReviewDialogUpdateStatus() == COMMON_DIALOG_STATUS_NONE); + Require(sceUserServiceTerminate() == 0); } diff --git a/core/libs/tests/GuestPlayerSelectionDialog.cpp b/core/libs/tests/GuestPlayerSelectionDialog.cpp index 8814165e4f..4b1c516bd1 100644 --- a/core/libs/tests/GuestPlayerSelectionDialog.cpp +++ b/core/libs/tests/GuestPlayerSelectionDialog.cpp @@ -1,18 +1,10 @@ +#include "prx/libScePlayerSelectionDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include - -extern "C" { -int APS5_VABI scePlayerSelectionDialogInitialize(void); -int APS5_VABI scePlayerSelectionDialogOpen(const void* param); -int APS5_VABI scePlayerSelectionDialogUpdateStatus(void); -int APS5_VABI scePlayerSelectionDialogGetStatus(void); -int APS5_VABI scePlayerSelectionDialogGetResult(void* result); -int APS5_VABI scePlayerSelectionDialogParamInitialize(void* param); -int APS5_VABI scePlayerSelectionDialogClose(void); -int APS5_VABI scePlayerSelectionDialogTerminate(void); -} +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "prx/libSceUserService/UserService.hpp" namespace { @@ -28,8 +20,13 @@ constexpr int kResultUserCanceled = 1; } int main() { - std::int64_t param = 0; - std::int32_t result[2] = {-1, -1}; + 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; + PlayerSelectionDialogResult result{-1, 99, nullptr, 77, 55}; Require(scePlayerSelectionDialogTerminate() == kErrNotInitialized); Require(scePlayerSelectionDialogOpen(¶m) == kErrNotInitialized); @@ -38,12 +35,13 @@ int main() { bool threw = false; try { scePlayerSelectionDialogParamInitialize(¶m); + } catch (const std::runtime_error&) { threw = true; } Require(threw); - Require(param == 0); - Require(scePlayerSelectionDialogGetResult(result) == kErrNotInitialized); + Require(param.size == sizeof(param)); + Require(scePlayerSelectionDialogGetResult(&result) == kErrNotInitialized); Require(scePlayerSelectionDialogGetStatus() == 0); Require(scePlayerSelectionDialogUpdateStatus() == 0); @@ -53,20 +51,51 @@ int main() { Require(scePlayerSelectionDialogUpdateStatus() == 1); Require(scePlayerSelectionDialogOpen(nullptr) == kErrArgNull); - Require(scePlayerSelectionDialogGetResult(result) == kErrNotFinished); + Require(scePlayerSelectionDialogGetResult(&result) == kErrNotFinished); + param.size = 0; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.size = sizeof(param); + param.maxPlayers = 257; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.maxPlayers = 1; + param.mode = 4; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.mode = 0; + param.playerCount = 1; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + PlayerSelectionDialogPlayer player{}; + param.players = &player; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + player.accountId = 1; + player.option0 = 1; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.maxPlayers = 2; + player.option1 = 2; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + player.option1 = 1; + player.mode = 3; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + player.mode = 2; + player.reserved[63] = 1; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + player.reserved[63] = 0; + param.reserved[63] = 1; + Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.reserved[63] = 0; Require(scePlayerSelectionDialogOpen(¶m) == 0); Require(scePlayerSelectionDialogGetStatus() == kStatusFinished); Require(scePlayerSelectionDialogUpdateStatus() == kStatusFinished); Require(scePlayerSelectionDialogGetResult(nullptr) == kErrArgNull); - Require(scePlayerSelectionDialogGetResult(result) == 0); - Require(result[0] == kResultUserCanceled); - Require(result[1] == -1); + Require(scePlayerSelectionDialogGetResult(&result) == kResultUserCanceled); + Require(result.result == kResultUserCanceled); + Require(result.option == 99 && result.accountIds == nullptr && result.count == 77 && result.padding == 55); - Require(scePlayerSelectionDialogClose() == 0); + Require(scePlayerSelectionDialogClose() == static_cast(0x80B8000Bu)); Require(scePlayerSelectionDialogTerminate() == 0); Require(scePlayerSelectionDialogTerminate() == kErrNotInitialized); Require(scePlayerSelectionDialogGetStatus() == 0); Require(scePlayerSelectionDialogClose() == kErrNotInitialized); + Require(sceUserServiceTerminate() == 0); } diff --git a/core/libs/tests/GuestPngDec.cpp b/core/libs/tests/GuestPngDec.cpp index 83281c1c1c..120be43fdc 100644 --- a/core/libs/tests/GuestPngDec.cpp +++ b/core/libs/tests/GuestPngDec.cpp @@ -1,31 +1,21 @@ +#include "prx/libScePngDec/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "Decoder/Png.hpp" #include #include +#include #include #include -#include +#include +#include +#include #include -extern "C" { -std::int32_t APS5_VABI scePngDecQueryMemorySize(const PngDecCreateParam*); -std::int32_t APS5_VABI scePngDecCreate(const PngDecCreateParam*, void*, std::uint32_t, void**); -std::int32_t APS5_VABI scePngDecDelete(void*); -std::int32_t APS5_VABI scePngDecParseHeader(const PngDecParseParam*, PngDecImageInfo*); -std::int32_t APS5_VABI scePngDecDecode(void*, const PngDecDecodeParam*, PngDecImageInfo*); -} - -static void Require(bool value) { if (!value) std::abort(); } - -template -static bool ThrowsRuntimeError(TFunction function) { - try { - function(); - } catch (const std::runtime_error&) { - return true; - } - return false; +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (value) return; + std::fprintf(stderr, "%s:%u: PNG decoder assertion failed\n", location.file_name(), location.line()); + std::abort(); } static const std::uint8_t PALETTE_TRNS[] = { @@ -46,6 +36,44 @@ static const std::uint8_t GRAY16[] = { 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, }; +static const std::uint8_t GRAY16_TRNS[] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x07, 0x4D, 0x8E, + 0xBB, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4E, 0x53, 0x12, 0x34, 0x2F, 0xD3, 0x49, 0x5E, 0x00, + 0x00, 0x00, 0x12, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x10, 0x32, 0x11, 0x5A, 0xCD, 0xB0, + 0xFA, 0x2C, 0x03, 0x03, 0x00, 0x0B, 0xDF, 0x02, 0x7C, 0x4E, 0x3C, 0x25, 0x55, 0x00, 0x00, 0x00, + 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, +}; + +static const std::uint8_t RGB16_TRNS[] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x10, 0x02, 0x00, 0x00, 0x00, 0xAD, 0x44, 0x46, + 0x30, 0x00, 0x00, 0x00, 0x06, 0x74, 0x52, 0x4E, 0x53, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0x89, + 0xE4, 0x4E, 0xE6, 0x00, 0x00, 0x00, 0x23, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x10, 0x32, + 0x09, 0xAB, 0x98, 0xB5, 0x47, 0xE8, 0x7F, 0xD8, 0xFF, 0x59, 0xFF, 0x19, 0xFE, 0x33, 0x30, 0x30, + 0x36, 0x30, 0x32, 0x30, 0x34, 0x30, 0xFC, 0xFF, 0x0F, 0x00, 0x88, 0xFB, 0x0A, 0x69, 0xC3, 0x3F, + 0x47, 0x19, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, +}; + +static const std::uint8_t GRAY_ALPHA16[] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x10, 0x04, 0x00, 0x00, 0x00, 0x88, 0x2F, 0x19, + 0xEC, 0x00, 0x00, 0x00, 0x19, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x10, 0x32, 0xB9, 0xF7, + 0x61, 0xF5, 0x59, 0x06, 0x20, 0xF8, 0xF7, 0xFF, 0xFF, 0x7F, 0xC6, 0x86, 0x46, 0x00, 0x48, 0x58, + 0x08, 0x8A, 0x8F, 0xC8, 0x5C, 0x92, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, + 0x60, 0x82, +}; + +static const std::uint8_t RGBA16[] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x10, 0x06, 0x00, 0x00, 0x00, 0x22, 0x26, 0xD1, + 0x67, 0x00, 0x00, 0x00, 0x2D, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x01, 0x22, 0x00, 0xDD, 0xFF, + 0x00, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0x0D, 0xEF, 0xAB, 0xCD, 0x20, 0x01, 0xEE, 0xFE, 0xFF, + 0xFF, 0x00, 0x00, 0x01, 0x80, 0x40, 0xFF, 0xAB, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x02, 0x7F, 0xFE, + 0x80, 0xFF, 0x07, 0xD0, 0x10, 0x52, 0x90, 0xA2, 0xAC, 0x25, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, + 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, +}; + static std::vector RgbPng() { std::vector rgb(3 * 2 * 3); for (std::size_t i = 0; i < rgb.size(); ++i) rgb[i] = static_cast(10 + i * 13); @@ -70,6 +98,121 @@ static PngDecDecodeParam DecodeParam(const std::vector& png, std:: return param; } +static void Check16BitImage(void* limitedHandle, void* enabledHandle, std::span png, + std::initializer_list expected, std::uint16_t colorSpace, + std::uint32_t flags, std::uint32_t height = 2) { + constexpr std::int32_t invalidSize = static_cast(0x80690002); + constexpr std::int32_t invalidParam = static_cast(0x80690003); + constexpr std::int32_t invalidWorkMemory = static_cast(0x80690005); + const std::vector input(png.begin(), png.end()); + const std::vector expectedPixels(expected); + Require(expectedPixels.size() == 2 * height * 4); + for (const std::uint16_t format : {0, 1}) { + for (const std::uint32_t padding : {0, 8}) { + const std::uint32_t pitch = 8 + padding; + const std::uint32_t size = pitch * height; + std::vector output(size + 16, 0xCC); + PngDecDecodeParam decode = DecodeParam(input, output); + decode.pixel_format = format; + decode.image_pitch = padding == 0 ? 0 : pitch; + decode.image_mem_size = size; + PngDecImageInfo info{}; + Require(scePngDecDecode(limitedHandle, &decode, &info) == invalidWorkMemory); + Require(info.image_width == 2 && info.image_height == height && info.bit_depth == 16); + Require(info.color_space == colorSpace && info.image_flag == flags); + Require(std::all_of(output.begin(), output.end(), [](std::uint8_t byte) { return byte == 0xCC; })); + Require(scePngDecDecode(enabledHandle, &decode, format == 0 ? &info : nullptr) == static_cast(2 << 16 | height)); + Require(info.image_width == 2 && info.image_height == height && info.bit_depth == 16); + Require(info.color_space == colorSpace && info.image_flag == flags); + for (std::uint32_t y = 0; y < height; ++y) { + for (std::uint32_t x = 0; x < 2; ++x) { + for (std::uint32_t channel = 0; channel < 4; ++channel) { + const std::uint32_t inputChannel = format == 1 && channel < 3 ? 2 - channel : channel; + Require(output[y * pitch + x * 4 + channel] == expectedPixels[y * 8 + x * 4 + inputChannel]); + } + } + for (std::uint32_t offset = 8; offset < pitch; ++offset) Require(output[y * pitch + offset] == 0xCC); + } + Require(std::all_of(output.begin() + size, output.end(), [](std::uint8_t byte) { return byte == 0xCC; })); + std::fill(output.begin(), output.end(), 0xCC); + decode.image_mem_size = size - 1; + for (void* handle : {limitedHandle, enabledHandle}) { + Require(scePngDecDecode(handle, &decode, &info) == (padding == 0 ? invalidSize : invalidParam)); + Require(std::all_of(output.begin(), output.end(), [](std::uint8_t byte) { return byte == 0xCC; })); + const auto validPitch = decode.image_pitch; + decode.image_pitch = 7; + Require(scePngDecDecode(handle, &decode, &info) == invalidParam); + Require(std::all_of(output.begin(), output.end(), [](std::uint8_t byte) { return byte == 0xCC; })); + decode.image_pitch = validPitch; + } + } + } +} + +static void CheckDimensionReporting(std::uint32_t width, std::uint32_t height) { + constexpr std::int32_t invalidAddr = static_cast(0x80690001); + constexpr std::int32_t invalidSize = static_cast(0x80690002); + constexpr std::int32_t invalidParam = static_cast(0x80690003); + constexpr std::int32_t invalidData = static_cast(0x80690010); + std::vector rgb(static_cast(width) * height * 3); + for (std::size_t i = 0; i < rgb.size(); ++i) rgb[i] = static_cast(i * 13 + 7); + const auto png = Decoder::Png::Encode(rgb, width, height, 3); + const PngDecCreateParam create{sizeof(PngDecCreateParam), 0, width}; + const auto memorySize = scePngDecQueryMemorySize(&create); + Require(memorySize > 0); + std::vector memory(memorySize); + void* handle = nullptr; + Require(scePngDecCreate(&create, memory.data(), memorySize, &handle) == 0); + const bool packed = width <= 32767 && height <= 32767; + const auto result = packed ? static_cast(width << 16 | height) : 0; + for (const std::uint16_t format : {0, 1}) { + for (const std::uint32_t padding : {0, 8}) { + const std::uint32_t pitch = width * 4 + padding; + const std::uint32_t size = pitch * height; + std::vector image(size + 16, 0xCC); + auto decode = DecodeParam(png, image); + decode.pixel_format = format; + decode.image_pitch = padding == 0 ? 0 : pitch; + decode.image_mem_size = size; + Require(scePngDecDecode(handle, &decode, nullptr) == (packed ? result : invalidAddr)); + if (!packed) Require(std::all_of(image.begin(), image.end(), [](auto byte) { return byte == 0xCC; })); + PngDecImageInfo info{}; + Require(scePngDecDecode(handle, &decode, &info) == result); + Require(info.image_width == width && info.image_height == height && info.color_space == 3 && info.bit_depth == 8 && info.image_flag == 0); + 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 < 3; ++channel) { + const auto inputChannel = format == 0 ? channel : 2 - channel; + Require(image[y * pitch + x * 4 + channel] == rgb[(static_cast(y) * width + x) * 3 + inputChannel]); + } + Require(image[y * pitch + x * 4 + 3] == 0x77); + } + for (std::uint32_t offset = width * 4; offset < pitch; ++offset) Require(image[y * pitch + offset] == 0xCC); + } + Require(std::all_of(image.begin() + size, image.end(), [](auto byte) { return byte == 0xCC; })); + if (packed) continue; + std::fill(image.begin(), image.end(), 0xCC); + decode.image_mem_size = 1; + Require(scePngDecDecode(handle, &decode, nullptr) == invalidAddr); + Require(scePngDecDecode(handle, &decode, &info) == (padding == 0 ? invalidSize : invalidParam)); + decode.image_mem_size = size; + decode.image_pitch = 1; + Require(scePngDecDecode(handle, &decode, nullptr) == invalidAddr); + Require(scePngDecDecode(handle, &decode, &info) == invalidParam); + decode.image_mem_size = 0; + Require(scePngDecDecode(handle, &decode, nullptr) == invalidSize); + decode.image_mem_size = size; + decode.pixel_format = 2; + Require(scePngDecDecode(handle, &decode, nullptr) == invalidParam); + decode.pixel_format = format; + decode.png_mem_size = 8; + Require(scePngDecDecode(handle, &decode, nullptr) == invalidData); + Require(std::all_of(image.begin(), image.end(), [](auto byte) { return byte == 0xCC; })); + } + } + Require(scePngDecDelete(handle) == 0); +} + int main() { constexpr std::int32_t invalidAddr = static_cast(0x80690001); constexpr std::int32_t invalidSize = static_cast(0x80690002); @@ -81,8 +224,8 @@ int main() { const PngDecCreateParam param{sizeof(PngDecCreateParam), 0, 1920}; const std::int32_t memorySize = scePngDecQueryMemorySize(¶m); - Require(memorySize > 0); - Require(scePngDecQueryMemorySize(nullptr) == invalidParam); + Require(memorySize == 69264); + Require(scePngDecQueryMemorySize(nullptr) == invalidAddr); const PngDecCreateParam badSize{sizeof(PngDecCreateParam) - 1, 0, 1920}; Require(scePngDecQueryMemorySize(&badSize) == invalidSize); const PngDecCreateParam badAttribute{sizeof(PngDecCreateParam), 2, 1920}; @@ -94,21 +237,21 @@ int main() { const PngDecCreateParam overWidth{sizeof(PngDecCreateParam), 1, 1000001}; Require(scePngDecQueryMemorySize(&overWidth) == invalidParam); const PngDecCreateParam maxWidth{sizeof(PngDecCreateParam), 1, 1000000}; - Require(scePngDecQueryMemorySize(&maxWidth) == memorySize); + Require(scePngDecQueryMemorySize(&maxWidth) == 16054416); std::vector memory(static_cast(memorySize) + 1); unsigned char* unaligned = memory.data() + 1; void* handle = nullptr; - Require(scePngDecCreate(nullptr, unaligned, static_cast(memorySize), &handle) == invalidParam); + Require(scePngDecCreate(nullptr, unaligned, static_cast(memorySize), &handle) == invalidAddr); Require(scePngDecCreate(&badSize, unaligned, static_cast(memorySize), &handle) == invalidSize); Require(scePngDecCreate(&overWidth, unaligned, static_cast(memorySize), &handle) == invalidParam); Require(scePngDecCreate(¶m, nullptr, static_cast(memorySize), &handle) == invalidAddr); Require(scePngDecCreate(¶m, unaligned, static_cast(memorySize), nullptr) == invalidAddr); - Require(scePngDecCreate(¶m, unaligned, static_cast(memorySize) - 1, &handle) == invalidWorkMemory); + Require(scePngDecCreate(¶m, unaligned, static_cast(memorySize) - 1, &handle) == invalidSize); Require(handle == nullptr); Require(scePngDecCreate(¶m, unaligned, static_cast(memorySize), &handle) == 0); - Require(reinterpret_cast(handle) % 8 == 0); - Require(static_cast(handle) >= unaligned && static_cast(handle) < unaligned + 8); + Require(reinterpret_cast(handle) % 16 == 0); + Require(static_cast(handle) >= unaligned && static_cast(handle) < unaligned + 16); const std::vector rgbPng = RgbPng(); PngDecImageInfo info{}; @@ -125,7 +268,7 @@ int main() { } const PngDecParseParam completeTransparency{PALETTE_TRNS, 73, 0}; Require(scePngDecParseHeader(&completeTransparency, &info) == 0 && info.image_flag == 2); - Require(scePngDecParseHeader(nullptr, &info) == invalidParam); + Require(scePngDecParseHeader(nullptr, &info) == invalidAddr); Require(scePngDecParseHeader(&parse, nullptr) == invalidAddr); const PngDecParseParam parseNull{nullptr, 16, 0}; Require(scePngDecParseHeader(&parseNull, &info) == invalidAddr); @@ -205,15 +348,15 @@ int main() { Require(info.image_flag == 2); const std::vector gray16Png(GRAY16, GRAY16 + sizeof(GRAY16)); - std::vector gray16Image(2 * 4); + std::vector gray16Image(2 * 4, 0xCC); decode = DecodeParam(gray16Png, gray16Image); - Require(scePngDecDecode(handle, &decode, &info) == (2 << 16 | 1)); - Require(gray16Image == std::vector({0x12, 0x12, 0x12, 0x77, 0xAB, 0xAB, 0xAB, 0x77})); + Require(scePngDecDecode(handle, &decode, &info) == invalidWorkMemory); + Require(std::all_of(gray16Image.begin(), gray16Image.end(), [](std::uint8_t byte) { return byte == 0xCC; })); Require(info.bit_depth == 16); decode = DecodeParam(rgbPng, image); Require(scePngDecDecode(nullptr, &decode, &info) == invalidHandle); - Require(scePngDecDecode(handle, nullptr, &info) == invalidParam); + Require(scePngDecDecode(handle, nullptr, &info) == invalidAddr); decode.png_mem_addr = nullptr; Require(scePngDecDecode(handle, &decode, &info) == invalidAddr); decode = DecodeParam(rgbPng, image); @@ -253,11 +396,16 @@ int main() { Require(scePngDecDecode(handle, &decode, &info) == decodeError); void* deepHandle = nullptr; - std::vector deepMemory(static_cast(memorySize)); const PngDecCreateParam deepParam{sizeof(PngDecCreateParam), 1, 1920}; - Require(scePngDecCreate(&deepParam, deepMemory.data(), static_cast(memorySize), &deepHandle) == 0); - decode = DecodeParam(gray16Png, gray16Image); - Require(ThrowsRuntimeError([&] { scePngDecDecode(deepHandle, &decode, &info); })); + const auto deepSize = scePngDecQueryMemorySize(&deepParam); + Require(deepSize == 85136); + std::vector deepMemory(static_cast(deepSize)); + Require(scePngDecCreate(&deepParam, deepMemory.data(), static_cast(deepSize), &deepHandle) == 0); + Check16BitImage(handle, deepHandle, GRAY16, {0x12, 0x12, 0x12, 0x77, 0xAB, 0xAB, 0xAB, 0x77}, 2, 0, 1); + Check16BitImage(handle, deepHandle, GRAY16_TRNS, {0x12, 0x12, 0x12, 0, 0x12, 0x12, 0x12, 0xFF, 0xAB, 0xAB, 0xAB, 0xFF, 0, 0, 0, 0xFF}, 2, 2); + Check16BitImage(handle, deepHandle, RGB16_TRNS, {0x12, 0x56, 0x9A, 0, 0x12, 0x56, 0x9A, 0xFF, 0xFF, 0, 0x80, 0xFF, 0, 0x80, 0xFF, 0xFF}, 3, 2); + Check16BitImage(handle, deepHandle, GRAY_ALPHA16, {0x12, 0x12, 0x12, 0xDE, 0xAB, 0xAB, 0xAB, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0x80}, 18, 0); + Check16BitImage(handle, deepHandle, RGBA16, {0x12, 0x56, 0x9A, 0x0D, 0xAB, 0x20, 0xEE, 0xFF, 0, 0x80, 0xFF, 0, 0xFF, 1, 0x7F, 0x80}, 19, 0); decode = DecodeParam(rgbPng, image); Require(scePngDecDecode(deepHandle, &decode, &info) == (3 << 16 | 2)); Require(scePngDecDelete(deepHandle) == 0); @@ -268,5 +416,9 @@ int main() { Require(scePngDecDelete(handle) == invalidHandle); Require(scePngDecDecode(handle, &decode, &info) == invalidHandle); + for (const std::uint32_t dimension : {32767, 32768, 65536}) { + CheckDimensionReporting(dimension, 1); + CheckDimensionReporting(1, dimension); + } return 0; } diff --git a/core/libs/tests/GuestPngEnc.cpp b/core/libs/tests/GuestPngEnc.cpp index 7984c2fff5..53cbcd808f 100644 --- a/core/libs/tests/GuestPngEnc.cpp +++ b/core/libs/tests/GuestPngEnc.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePngEnc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "Decoder/Png.hpp" @@ -7,13 +8,6 @@ #include #include -extern "C" { -int APS5_VABI scePngEncQueryMemorySize(const PngEncCreateParam*); -int APS5_VABI scePngEncCreate(const PngEncCreateParam*, void*, std::uint32_t, void**); -int APS5_VABI scePngEncDelete(void*); -int APS5_VABI scePngEncEncode(void*, const PngEncEncodeParam*, PngEncOutputInfo*); -} - static void Require(bool value) { if (!value) std::abort(); } int main() { @@ -26,28 +20,32 @@ int main() { constexpr std::uint32_t height = 3; constexpr std::uint32_t pitch = 24; - PngEncCreateParam create{sizeof(PngEncCreateParam), 0, 16, 5}; + PngEncCreateParam create{sizeof(PngEncCreateParam), 0, 16, 4}; const int memorySize = scePngEncQueryMemorySize(&create); - Require(memorySize > 0); + Require(memorySize == 286192); Require(scePngEncQueryMemorySize(nullptr) == invalidAddr); PngEncCreateParam bad = create; bad.attribute = 1; Require(scePngEncQueryMemorySize(&bad) == invalidParam); bad = create; - bad.max_filter_number = 6; + bad.max_filter_number = 5; Require(scePngEncQueryMemorySize(&bad) == invalidParam); bad = create; bad.max_image_width = 0; - Require(scePngEncQueryMemorySize(&bad) == invalidSize); + Require(scePngEncQueryMemorySize(&bad) == invalidParam); bad.max_image_width = 1000001; - Require(scePngEncQueryMemorySize(&bad) == invalidSize); + Require(scePngEncQueryMemorySize(&bad) == invalidParam); + bad = create; + bad.this_size = 0; + Require(scePngEncQueryMemorySize(&bad) == invalidSize); std::vector memory((memorySize + 7) / 8); void* handle = nullptr; Require(scePngEncCreate(&create, nullptr, memorySize, &handle) == invalidAddr); Require(scePngEncCreate(&create, memory.data(), memorySize - 1, &handle) == invalidSize); - Require(scePngEncCreate(&create, memory.data(), memorySize, &handle) == 0 && handle == memory.data()); + Require(scePngEncCreate(&create, memory.data(), memorySize, &handle) == 0 && reinterpret_cast(handle) % 16 == 0); + Require(scePngEncCreate(&create, memory.data(), 0, nullptr) == invalidSize); std::vector image(pitch * height, 0xEE); for (std::uint32_t y = 0; y < height; ++y) { for (std::uint32_t x = 0; x < width; ++x) { @@ -126,8 +124,9 @@ int main() { {0, FILTER_NONE}, {1, FILTER_SUB}, {2, FILTER_UP}, {4, FILTER_AVERAGE}, {8, FILTER_PAETH}, {3, FILTER_SUB | FILTER_UP}, {6, FILTER_UP | FILTER_AVERAGE}, {9, FILTER_SUB | FILTER_PAETH}, {14, FILTER_UP | FILTER_AVERAGE | FILTER_PAETH}, {15, FILTER_ALL}}; - for (const auto& [filterType, filters] : filterSets) Require(encodeWith(filterType) == Encode(rows, 16, 6, 4, {6, filters})); - Require(encodeWith(3) != encodeWith(1) && encodeWith(3) != encodeWith(2) && encodeWith(15) != encodeWith(14)); + for (const auto& [filterType, filters] : filterSets) Require(encodeWith(filterType) == Encode(rows, 16, 6, 4, {6, static_cast(filters | FILTER_NONE)})); + const std::vector unfiltered = Encode(rows, 16, 6, 4, {6, FILTER_NONE}); + Require(encodeWith(0) == unfiltered); encode.png_mem_size = 20; PngEncOutputInfo info{1, 1}; @@ -135,7 +134,7 @@ int main() { encode.png_mem_size = static_cast(png.size()); PngEncEncodeParam invalid = encode; - Require(scePngEncEncode(handle, nullptr, nullptr) == invalidParam); + Require(scePngEncEncode(handle, nullptr, nullptr) == invalidAddr); invalid.image_mem_addr = nullptr; Require(scePngEncEncode(handle, &invalid, nullptr) == invalidAddr); for (auto change : {+[](PngEncEncodeParam& p) { p.pixel_format = 2; }, +[](PngEncEncodeParam& p) { p.color_space = 4; }, @@ -146,15 +145,31 @@ int main() { Require(scePngEncEncode(handle, &invalid, nullptr) == invalidParam); } for (auto change : {+[](PngEncEncodeParam& p) { p.image_width = 0; }, +[](PngEncEncodeParam& p) { p.image_width = 17; }, - +[](PngEncEncodeParam& p) { p.image_pitch = 19; }, +[](PngEncEncodeParam& p) { p.image_mem_size = 67; }, - +[](PngEncEncodeParam& p) { p.png_mem_size = 0; }}) { + +[](PngEncEncodeParam& p) { p.image_pitch = 19; }, +[](PngEncEncodeParam& p) { p.image_mem_size = 67; }}) { invalid = encode; change(invalid); - Require(scePngEncEncode(handle, &invalid, nullptr) == invalidSize); + Require(scePngEncEncode(handle, &invalid, nullptr) == invalidParam); } invalid = encode; invalid.image_mem_size = pitch * (height - 1) + width * 4; - Require(scePngEncEncode(handle, &invalid, nullptr) > 0); + Require(scePngEncEncode(handle, &invalid, nullptr) == invalidParam); + invalid = encode; + invalid.png_mem_size = 0; + Require(scePngEncEncode(handle, &invalid, nullptr) == invalidSize); + invalid = encode; + invalid.image_mem_addr = image.data() + 1; + Require(scePngEncEncode(handle, &invalid, nullptr) == invalidAddr); + PngEncCreateParam oneFilter = create; + oneFilter.max_filter_number = 1; + void* oneFilterHandle = nullptr; + std::vector oneFilterMemory((scePngEncQueryMemorySize(&oneFilter) + 7) / 8); + Require(scePngEncCreate(&oneFilter, oneFilterMemory.data(), static_cast(oneFilterMemory.size() * 8), &oneFilterHandle) == 0); + invalid = encode; + invalid.filter_type = 3; + Require(scePngEncEncode(oneFilterHandle, &invalid, nullptr) == invalidParam); + invalid.filter_type = 1; + Require(scePngEncEncode(oneFilterHandle, &invalid, nullptr) > 0); + Require(scePngEncDelete(oneFilterHandle) == 0); Require(scePngEncDelete(handle) == 0); Require(scePngEncDelete(handle) == invalidHandle); diff --git a/core/libs/tests/GuestPoll.cpp b/core/libs/tests/GuestPoll.cpp index 131e75eb9d..c5241498ea 100644 --- a/core/libs/tests/GuestPoll.cpp +++ b/core/libs/tests/GuestPoll.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -7,42 +9,18 @@ #include #endif #include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" #include #include #include #include +#include #include #include #include #include #include -struct PollDescriptor { - int descriptor; - short events; - short revents; -}; -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI mkdir_nid_postfix(const char*, unsigned short); -int APS5_VABI rmdir_nid_postfix(const char*); -int APS5_VABI unlink_nid_postfix(const char*); -int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI kqueue_nid_postfix(); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int* APS5_VABI __error_nid_postfix(); -} + #ifdef _WIN32 using NativeSocket = SOCKET; constexpr NativeSocket NativeInvalid = INVALID_SOCKET; @@ -61,6 +39,46 @@ constexpr short Hup = 0x10; constexpr short Invalid = 0x20; constexpr short ReadNormal = 0x40; constexpr short WriteBand = 0x100; +constexpr std::int16_t EvfiltUser = -11; +constexpr std::uint16_t EvAdd = 0x0001; +constexpr std::uint16_t EvClear = 0x0020; +constexpr std::uint32_t NoteTrigger = 0x01000000; +static KernelEvent UserEvent(std::uint16_t flags, std::uint32_t fflags) { + KernelEvent change{}; + change.ident = 7; + change.filter = EvfiltUser; + change.flags = flags; + change.fflags = fflags; + return change; +} +static void PollsKqueues(int socket) { + const int queue = kqueue_nid_postfix(); + Require(queue >= 0); + GuestPollDescriptor kq{queue, static_cast(In | ReadNormal | Out | Priority | ReadBand | WriteBand), -1}; + Require(poll_nid_postfix(&kq, 1, 0) == 0 && kq.revents == 0); + const auto added = UserEvent(EvAdd | EvClear, 0); + Require(kevent_nid_postfix(queue, &added, 1, nullptr, 0, nullptr) == 0); + Require(poll_nid_postfix(&kq, 1, 0) == 0 && kq.revents == 0); + const auto start = std::chrono::steady_clock::now(); + Require(poll_nid_postfix(&kq, 1, 30) == 0 && kq.revents == 0); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(25)); + std::thread trigger([queue] { + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + const auto triggered = UserEvent(0, NoteTrigger); + Require(kevent_nid_postfix(queue, &triggered, 1, nullptr, 0, nullptr) == 0); + }); + GuestPollDescriptor mixed[2]{{socket, In, -1}, {queue, static_cast(In | Out), -1}}; + Require(poll_nid_postfix(mixed, 2, -1) == 1); + trigger.join(); + Require(mixed[0].revents == 0 && mixed[1].revents == In); + Require(poll_nid_postfix(&kq, 1, 0) == 1 && kq.revents == (In | ReadNormal)); + KernelEvent events[1]{}; + const KernelTimespec zero{0, 0}; + Require(kevent_nid_postfix(queue, nullptr, 0, events, 1, &zero) == 1); + Require(poll_nid_postfix(&kq, 1, 0) == 0 && kq.revents == 0); + Require(close_nid_postfix(queue) == 0); + Require(poll_nid_postfix(&kq, 1, 0) == 1 && kq.revents == Invalid); +} static void PollsFilesAndDirectories(int socket) { constexpr int ReadWrite = 0x0002, Create = 0x0200, DirectoryFlag = 0x00020000; const auto stamp = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); @@ -71,28 +89,48 @@ static void PollsFilesAndDirectories(int socket) { Require(mkdir_nid_postfix(directoryPath.c_str(), 0755) == 0); const int directory = open_nid_postfix(directoryPath.c_str(), DirectoryFlag, 0); Require(directory >= 0); - PollDescriptor vnode{file, static_cast(In | Out | ReadNormal | Priority | ReadBand | WriteBand), -1}; + GuestPollDescriptor vnode{file, static_cast(In | Out | ReadNormal | Priority | ReadBand | WriteBand), -1}; Require(poll_nid_postfix(&vnode, 1, 0) == 1 && vnode.revents == (In | Out | ReadNormal)); vnode.events = static_cast(Priority | ReadBand | WriteBand); Require(poll_nid_postfix(&vnode, 1, 0) == 0 && vnode.revents == 0); const auto start = std::chrono::steady_clock::now(); Require(poll_nid_postfix(&vnode, 1, 30) == 0); Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(25)); - PollDescriptor folder{directory, static_cast(In | Out), -1}; + GuestPollDescriptor folder{directory, static_cast(In | Out), -1}; Require(poll_nid_postfix(&folder, 1, -1) == 1 && folder.revents == (In | Out)); - PollDescriptor mixed[3]{{socket, In, -1}, {file, Out, -1}, {directory, ReadNormal, -1}}; + GuestPollDescriptor mixed[3]{{socket, In, -1}, {file, Out, -1}, {directory, ReadNormal, -1}}; Require(poll_nid_postfix(mixed, 3, -1) == 2); Require(mixed[0].revents == 0 && mixed[1].revents == Out && mixed[2].revents == ReadNormal); Require(close_nid_postfix(directory) == 0 && close_nid_postfix(file) == 0); Require(rmdir_nid_postfix(directoryPath.c_str()) == 0 && unlink_nid_postfix(filePath.c_str()) == 0); } +static void StandardDescriptorFile(int descriptor) { + const int saved = dup_nid_postfix(descriptor); + const std::string path = "anyps5-poll-input-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + const int file = open_nid_postfix(path.c_str(), 0x0002 | 0x0200, 0644); + Require(file >= 0); + Require(dup2_nid_postfix(file, descriptor) == descriptor); + if (file != descriptor) Require(close_nid_postfix(file) == 0); + GuestPollDescriptor input{descriptor, static_cast(In | Out | ReadNormal | Priority), -1}; + const int ready = poll_nid_postfix(&input, 1, 0); + if (ready != 1) std::fprintf(stderr, "poll on a regular file at descriptor %d returned %d instead of 1\n", descriptor, ready); + Require(ready == 1 && input.revents == (In | Out | ReadNormal)); + input.events = Priority; + Require(poll_nid_postfix(&input, 1, 0) == 0 && input.revents == 0); + if (saved >= 0) { + Require(dup2_nid_postfix(saved, descriptor) == descriptor); + Require(close_nid_postfix(saved) == 0); + } else Require(close_nid_postfix(descriptor) == 0); + Require(unlink_nid_postfix(path.c_str()) == 0); +} + static void TestPipes(int socket) { int ends[2]; Require(pipe_nid_postfix(ends) == 0); - PollDescriptor empty[2]{{ends[0], In, -1}, {ends[1], Out, -1}}; + GuestPollDescriptor empty[2]{{ends[0], In, -1}, {ends[1], Out, -1}}; Require(poll_nid_postfix(empty, 2, 0) == 1 && empty[0].revents == 0 && empty[1].revents == Out); - PollDescriptor reader{ends[0], In | ReadNormal, -1}; + GuestPollDescriptor reader{ends[0], In | ReadNormal, -1}; const auto start = std::chrono::steady_clock::now(); Require(poll_nid_postfix(&reader, 1, 50) == 0 && reader.revents == 0); Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40)); @@ -102,13 +140,13 @@ static void TestPipes(int socket) { Require(shortWait >= std::chrono::milliseconds(40) && shortWait < std::chrono::milliseconds(200)); Require(write_nid_postfix(ends[1], "wake", 4) == 4); Require(poll_nid_postfix(&reader, 1, 1000) == 1 && reader.revents == (In | ReadNormal)); - PollDescriptor writer{ends[1], In, -1}; + GuestPollDescriptor writer{ends[1], In, -1}; Require(poll_nid_postfix(&writer, 1, 0) == 0 && writer.revents == 0); - PollDescriptor readerOut{ends[0], Out, -1}; + GuestPollDescriptor readerOut{ends[0], Out, -1}; bool threw = false; try { poll_nid_postfix(&readerOut, 1, 0); } catch (const std::runtime_error&) { threw = true; } Require(threw); - PollDescriptor mixed[2]{{socket, In, -1}, {ends[0], In, -1}}; + GuestPollDescriptor mixed[2]{{socket, In, -1}, {ends[0], In, -1}}; Require(poll_nid_postfix(mixed, 2, 1000) == 1 && mixed[0].revents == 0 && mixed[1].revents == In); char buffer[8]{}; Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 4); @@ -135,14 +173,14 @@ static void TestPipes(int socket) { Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 1); Require(poll_nid_postfix(&reader, 1, 0) == 1 && reader.revents == (In | ReadNormal | Hup)); Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 0); - PollDescriptor closedPipe{ends[1], In, -1}; + GuestPollDescriptor closedPipe{ends[1], In, -1}; Require(poll_nid_postfix(&closedPipe, 1, 0) == 1 && closedPipe.revents == Invalid); Require(close_nid_postfix(ends[0]) == 0); int full[2]; Require(pipe_nid_postfix(full) == 0); static char block[4096]; std::int64_t queued = 0; - PollDescriptor fill{full[1], Out, -1}; + GuestPollDescriptor fill{full[1], Out, -1}; while (poll_nid_postfix(&fill, 1, 0) == 1) { Require(fill.revents == Out && queued < (1 << 20)); Require(write_nid_postfix(full[1], block, 256) == 256); @@ -160,7 +198,7 @@ static void TestPipes(int socket) { int other[2]; Require(pipe_nid_postfix(other) == 0); Require(close_nid_postfix(other[0]) == 0); - PollDescriptor orphan{other[1], Out, -1}; + GuestPollDescriptor orphan{other[1], Out, -1}; Require(poll_nid_postfix(&orphan, 1, 0) == 1 && orphan.revents == Hup); orphan.events = In | Out; Require(poll_nid_postfix(&orphan, 1, 0) == 1 && orphan.revents == (In | Hup)); @@ -177,12 +215,12 @@ static void PollsUrgentData() { const int client = socket_nid_postfix(2, 1, 0); Require(client >= 0 && fcntl_nid_postfix(client, 4, 4) == 0); if (connect_nid_postfix(client, address.data(), address.size()) != 0) - Require(*__error_nid_postfix() == 35 || *__error_nid_postfix() == 36); - PollDescriptor connecting{client, Priority | Out, -1}; + Require(*__error_nid_postfix() == 36); + GuestPollDescriptor connecting{client, Priority | Out, -1}; Require(poll_nid_postfix(&connecting, 1, 1000) == 1 && connecting.revents == Out); const int server = accept_nid_postfix(listener, nullptr, nullptr); Require(server >= 0); - PollDescriptor pair[2]{{server, Priority | Out, -1}, {client, In | Priority | Out | ReadBand, -1}}; + GuestPollDescriptor pair[2]{{server, Priority | Out, -1}, {client, In | Priority | Out | ReadBand, -1}}; Require(poll_nid_postfix(pair, 2, 0) == 2 && pair[0].revents == Out && pair[1].revents == Out); char byte = 'u'; Require(send_nid_postfix(client, &byte, 1, 1) == -1 && *__error_nid_postfix() == 45); @@ -196,14 +234,14 @@ static void PollsUrgentData() { Require(::connect(peer, reinterpret_cast(&native), sizeof(native)) == 0); const int urgent = accept_nid_postfix(listener, nullptr, nullptr); Require(urgent >= 0); - PollDescriptor waiting{urgent, Priority, -1}; + GuestPollDescriptor waiting{urgent, Priority, -1}; Require(poll_nid_postfix(&waiting, 1, 0) == 0 && waiting.revents == 0); Require(::send(peer, "u", 1, MSG_OOB) == 1); Require(poll_nid_postfix(&waiting, 1, 1000) == 1 && waiting.revents == Priority); - PollDescriptor bands{urgent, In | Priority | Out, -1}; + GuestPollDescriptor bands{urgent, In | Priority | Out, -1}; Require(poll_nid_postfix(&bands, 1, 0) == 1 && bands.revents == (Priority | Out)); #ifdef _WIN32 - PollDescriptor band{urgent, ReadBand, -1}; + GuestPollDescriptor band{urgent, ReadBand, -1}; Require(poll_nid_postfix(&band, 1, 0) == 1 && band.revents == ReadBand); #endif CloseNative(peer); @@ -212,8 +250,69 @@ static void PollsUrgentData() { Require(close_nid_postfix(client) == 0); Require(close_nid_postfix(listener) == 0); } +static void PollsAStreamWhosePeerClosed() { + const int listener = socket_nid_postfix(2, 1, 0); + Require(listener >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(listener, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(listener, address.data(), &size) == 0 && listen_nid_postfix(listener, 4) == 0); + const int client = socket_nid_postfix(2, 1, 0); + Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0); + const int server = accept_nid_postfix(listener, nullptr, nullptr); + Require(server >= 0); + + Require(shutdown_nid_postfix(client, 1) == 0); + GuestPollDescriptor wait{server, In, 0}; + Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == In); + wait = {server, static_cast(In | Out), 0}; + Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == (In | Out)); + wait = {server, ReadNormal, 0}; + Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == ReadNormal); + wait = {server, Out, 0}; + Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == Out); + char byte; + Require(recv_nid_postfix(server, &byte, 1, 0) == 0); + + Require(shutdown_nid_postfix(server, 1) == 0); + wait = {server, In, 0}; + Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == (In | Hup)); + wait = {client, In, 0}; + Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == (In | Hup)); + for (const int descriptor : {client, server, listener}) Require(close_nid_postfix(descriptor) == 0); +} + +#ifndef _WIN32 +static void PollsAHungUpSocketWithoutPollout() { + const int listener = socket_nid_postfix(2, 1, 0); + Require(listener >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(listener, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(listener, address.data(), &size) == 0 && listen_nid_postfix(listener, 1) == 0); + const int client = socket_nid_postfix(2, 1, 0); + Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0); + const int server = accept_nid_postfix(listener, nullptr, nullptr); + Require(server >= 0); + Require(close_nid_postfix(client) == 0); + char byte = 0; + GuestPollDescriptor ended{server, In, 0}; + Require(poll_nid_postfix(&ended, 1, 1000) == 1); + Require(recv_nid_postfix(server, &byte, 1, 0) == 0); + Require(shutdown_nid_postfix(server, 1) == 0); + GuestPollDescriptor hungUp{server, static_cast(In | Out), 0}; + Require(poll_nid_postfix(&hungUp, 1, 1000) == 1 && hungUp.revents == (In | Hup)); + Require(close_nid_postfix(server) == 0 && close_nid_postfix(listener) == 0); +} +#endif + int main() { + for (const int descriptor : {0, 1, 2}) StandardDescriptorFile(descriptor); PollsUrgentData(); + PollsAStreamWhosePeerClosed(); +#ifndef _WIN32 + PollsAHungUpSocketWithoutPollout(); +#endif const int listener = socket_nid_postfix(2, 1, 0); Require(listener >= 0 && listener < 1024); std::array address{16, 2, 0, 0, 127, 0, 0, 1}; @@ -221,7 +320,7 @@ int main() { std::uint32_t size = address.size(); Require(getsockname_nid_postfix(listener, address.data(), &size) == 0); Require(listen_nid_postfix(listener, 4) == 0); - PollDescriptor single{listener, In, -1}; + GuestPollDescriptor single{listener, In, -1}; Require(poll_nid_postfix(&single, 1, 0) == 0 && single.revents == 0); const auto start = std::chrono::steady_clock::now(); Require(poll_nid_postfix(&single, 1, 50) == 0); @@ -231,7 +330,7 @@ int main() { Require(poll_nid_postfix(&single, 1, 1000) == 1 && single.revents == In); const int server = accept_nid_postfix(listener, nullptr, nullptr); Require(server >= 0); - PollDescriptor pair[3]{{server, In, 0}, {-1, In, 7}, {client, Out, 0}}; + GuestPollDescriptor pair[3]{{server, In, 0}, {-1, In, 7}, {client, Out, 0}}; Require(poll_nid_postfix(pair, 3, 1000) == 1); Require(pair[0].revents == 0 && pair[1].revents == 0 && pair[2].revents == Out); const char message[] = "poll"; @@ -243,7 +342,7 @@ int main() { Require(close_nid_postfix(client) == 0); Require(poll_nid_postfix(pair, 1, 1000) == 1 && (pair[0].revents & (In | Hup)) != 0); Require(recv_nid_postfix(server, buffer, sizeof(buffer), 0) == 0); - PollDescriptor closed[2]{{client, In, 0}, {listener, In, 0}}; + GuestPollDescriptor closed[2]{{client, In, 0}, {listener, In, 0}}; Require(poll_nid_postfix(closed, 2, -1) == 1 && closed[0].revents == Invalid && closed[1].revents == 0); Require(poll_nid_postfix(nullptr, 0, 10) == 0); const auto shortWaitStart = std::chrono::steady_clock::now(); @@ -258,33 +357,23 @@ int main() { for (const char* path : {"/dev/urandom", "/dev/random"}) { const int random = open_nid_postfix(path, 0, 0); Require(random >= 0); - PollDescriptor device{random, static_cast(In | Out | Priority), -1}; + GuestPollDescriptor device{random, static_cast(In | Out | Priority), -1}; Require(poll_nid_postfix(&device, 1, 0) == 1 && device.revents == In); device.events = static_cast(ReadNormal | Out); Require(poll_nid_postfix(&device, 1, 0) == 1 && device.revents == ReadNormal); device.events = static_cast(Out | Priority | ReadBand | WriteBand); Require(poll_nid_postfix(&device, 1, -1) == 1 && device.revents == (Out | Priority | ReadBand | WriteBand)); - PollDescriptor mixed[2]{{listener, In, 0}, {random, Out, 0}}; + GuestPollDescriptor mixed[2]{{listener, In, 0}, {random, Out, 0}}; Require(poll_nid_postfix(mixed, 2, -1) == 1 && mixed[0].revents == 0 && mixed[1].revents == Out); Require(close_nid_postfix(random) == 0); } - PollDescriptor console[3]{{0, static_cast(In | ReadNormal | Out), -1}, {1, static_cast(In | Out | WriteBand), -1}, {2, Out, -1}}; + GuestPollDescriptor console[3]{{0, static_cast(In | ReadNormal | Out), -1}, {1, static_cast(In | Out | WriteBand), -1}, {2, Out, -1}}; Require(poll_nid_postfix(console, 3, 0) == 2); Require(console[0].revents == 0 && console[1].revents == Out && console[2].revents == Out); - PollDescriptor input{0, In, -1}; + GuestPollDescriptor input{0, In, -1}; const auto inputStart = std::chrono::steady_clock::now(); Require(poll_nid_postfix(&input, 1, 30) == 0 && input.revents == 0); Require(std::chrono::steady_clock::now() - inputStart >= std::chrono::milliseconds(25)); - const int queue = kqueue_nid_postfix(); - Require(queue >= 0); - PollDescriptor unknown[2]{{listener, In, 0}, {queue, In, 0}}; - bool unsupported = false; - try { - poll_nid_postfix(unknown, 2, 0); - } catch (const std::runtime_error&) { - unsupported = true; - } - Require(unsupported); - Require(close_nid_postfix(queue) == 0); + PollsKqueues(listener); Require(close_nid_postfix(listener) == 0); } diff --git a/core/libs/tests/GuestPositionedClose.cpp b/core/libs/tests/GuestPositionedClose.cpp index 3d44da1f08..1d89c27276 100644 --- a/core/libs/tests/GuestPositionedClose.cpp +++ b/core/libs/tests/GuestPositionedClose.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" #include "prx/libc/include/PreciseWait.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -13,12 +15,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Positioned close check failed at line %d\n", line); diff --git a/core/libs/tests/GuestPositionedRace.cpp b/core/libs/tests/GuestPositionedRace.cpp index 6c936c56b4..90c3257cf6 100644 --- a/core/libs/tests/GuestPositionedRace.cpp +++ b/core/libs/tests/GuestPositionedRace.cpp @@ -1,3 +1,5 @@ +#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" @@ -12,13 +14,7 @@ #include #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode); -int APS5_VABI sceKernelClose(int d); -std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes); -std::int64_t APS5_VABI sceKernelPread(int d, void* buf, std::size_t nbytes, std::int64_t offset); -std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence); -} + static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Positioned race check failed at line %d\n", line); diff --git a/core/libs/tests/GuestPosixFilesystem.cpp b/core/libs/tests/GuestPosixFilesystem.cpp index 0e3326d3d0..c18d690982 100644 --- a/core/libs/tests/GuestPosixFilesystem.cpp +++ b/core/libs/tests/GuestPosixFilesystem.cpp @@ -1,4 +1,9 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libScePosixForWebKit/include/Api.hpp" #include "prx/libc/include/General.hpp" +#include "SceTypes.hpp" #include #include #include @@ -17,18 +22,6 @@ #include #endif -extern "C" { -int APS5_VABI mkstemp_nid_postfix(char*); -int APS5_VABI isatty_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI socket_nid_postfix(int, int, int); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::size_t); -std::int64_t APS5_VABI write_nid_postfix(int, const void*, std::size_t); -std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int); -} - static void Require(bool condition, std::source_location location = std::source_location::current()) { if (!condition) { std::fprintf(stderr, "POSIX filesystem check failed at line %u (guest errno %d)\n", location.line(), *__error_nid_postfix()); @@ -134,6 +127,52 @@ int main() { Require(isatty_nid_postfix(slave) == 1 && *__error_nid_postfix() == 123); Require(::close(slave) == 0 && ::close(terminal) == 0); #endif + Require(setenv_nid_postfix("WEBKIT_WRAP_SET", "set", 1) == 0); + char* const variable = getenv_nid_postfix("WEBKIT_WRAP_SET"); + Require(variable != nullptr && std::strcmp(variable, "set") == 0 && __wrap_getenv("WEBKIT_WRAP_SET") == variable); + Require(getenv_nid_postfix("WEBKIT_WRAP_UNSET") == nullptr && __wrap_getenv("WEBKIT_WRAP_UNSET") == nullptr); + Require(access_nid_postfix(pattern.c_str(), 0) == 0 && __wrap_access(pattern.c_str(), 0) == 0); + Require(access_nid_postfix("/anyps5-posix-test/absent", 0) == -1); + const int accessError = *__error_nid_postfix(); + *__error_nid_postfix() = 0; + Require(__wrap_access("/anyps5-posix-test/absent", 0) == -1 && *__error_nid_postfix() == accessError && accessError == 2); + std::array expectedDirectory{}; + std::array wrappedDirectory{}; + Require(getcwd_nid_postfix(expectedDirectory.data(), expectedDirectory.size()) == expectedDirectory.data()); + Require(__wrap_getcwd(wrappedDirectory.data(), wrappedDirectory.size()) == wrappedDirectory.data()); + Require(std::strcmp(expectedDirectory.data(), wrappedDirectory.data()) == 0); + Require(getcwd_nid_postfix(wrappedDirectory.data(), 1) == nullptr); + const int directoryError = *__error_nid_postfix(); + *__error_nid_postfix() = 0; + Require(__wrap_getcwd(wrappedDirectory.data(), 1) == nullptr && *__error_nid_postfix() == directoryError && directoryError == 34); + FileStat expectedStatus{}; + FileStat wrappedStatus{}; + Require(lstat_nid_postfix(pattern.c_str(), &expectedStatus) == 0 && __wrap_lstat(pattern.c_str(), &wrappedStatus) == 0); + Require((wrappedStatus.st_mode & 0170000) == 0100000 && wrappedStatus.st_size == static_cast(sizeof(payload))); + Require(std::memcmp(&expectedStatus, &wrappedStatus, sizeof(FileStat)) == 0); + const auto notAfter = [](const KernelTimespec& earlier, const KernelTimespec& later) { + return earlier.tv_sec < later.tv_sec || (earlier.tv_sec == later.tv_sec && earlier.tv_nsec <= later.tv_nsec); + }; + KernelTimespec before{}; + KernelTimespec during{}; + KernelTimespec after{}; + Require(clock_gettime_nid_postfix(4, &before) == 0 && __wrap_clock_gettime(4, &during) == 0 && clock_gettime_nid_postfix(4, &after) == 0); + Require(notAfter(before, during) && notAfter(during, after)); + Require(clock_gettime_nid_postfix(4, nullptr) == -1 && *__error_nid_postfix() == 14); + *__error_nid_postfix() = 0; + Require(__wrap_clock_gettime(4, nullptr) == -1 && *__error_nid_postfix() == 14); + constexpr std::size_t page = 0x4000; + void* const failed = reinterpret_cast(static_cast(-1)); + void* const mapped = __wrap_mmap(nullptr, page, 3, 0x1002, -1, 0); + Require(mapped != failed); + auto* const bytes = static_cast(mapped); + bytes[0] = 0x5a; + bytes[page - 1] = 0xa5; + Require(bytes[0] == 0x5a && bytes[page - 1] == 0xa5); + Require(munmap_nid_postfix(mapped, page) == 0); + Require(mmap_nid_postfix(nullptr, page, 8, 0x1002, -1, 0) == failed && *__error_nid_postfix() == 22); + *__error_nid_postfix() = 0; + Require(__wrap_mmap(nullptr, page, 8, 0x1002, -1, 0) == failed && *__error_nid_postfix() == 22); RemovePathAlias_nid_no_patch("/anyps5-posix-test"); Require(std::filesystem::remove_all(root) > 0); } diff --git a/core/libs/tests/GuestPosixNamedSem.cpp b/core/libs/tests/GuestPosixNamedSem.cpp new file mode 100644 index 0000000000..99ab8cb8a5 --- /dev/null +++ b/core/libs/tests/GuestPosixNamedSem.cpp @@ -0,0 +1,125 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +static constexpr int GUEST_ENOENT = 2; +static constexpr int GUEST_EEXIST = 17; +static constexpr int GUEST_EINVAL = 22; +static constexpr int GUEST_EAGAIN = 35; +static constexpr int GUEST_ENAMETOOLONG = 63; +static constexpr int O_CREAT_FLAG = 0x200; +static constexpr int O_EXCL_FLAG = 0x800; + +static void Require(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "line %d failed, errno=%d\n", line, *__error_nid_postfix()); + std::abort(); + } +} +#define REQUIRE(condition) Require(condition, __LINE__) + +template +static bool Throws(Call call) { + try { + call(); + } catch (const std::exception&) { + return true; + } + return false; +} + +static bool OpenFails(const char* name, int flags, int error) { + *__error_nid_postfix() = 0; + return sem_open_nid_postfix(name, flags, 0600, 1) == nullptr && *__error_nid_postfix() == error; +} + +static bool UnlinkFails(const char* name, int error) { + *__error_nid_postfix() = 0; + return sem_unlink_nid_postfix(name) == -1 && *__error_nid_postfix() == error; +} + +static int Value(void* sem) { + int value = -1; + REQUIRE(sem_getvalue_nid_postfix(sem, &value) == 0); + return value; +} + +static void CheckNames() { + REQUIRE(OpenFails("relative", O_CREAT_FLAG, GUEST_EINVAL)); + REQUIRE(OpenFails("/anyps5-named-flags", 0x1, GUEST_EINVAL)); + REQUIRE(OpenFails("/anyps5-named-flags", O_CREAT_FLAG | 0x2, GUEST_EINVAL)); + REQUIRE(OpenFails("/anyps5-named-missing", 0, GUEST_ENOENT)); + REQUIRE(OpenFails("/anyps5-named-missing", O_EXCL_FLAG, GUEST_ENOENT)); + const std::string longPath = "/" + std::string(1015, 'p'); + REQUIRE(OpenFails(longPath.c_str(), O_CREAT_FLAG, GUEST_ENAMETOOLONG)); + REQUIRE(UnlinkFails(longPath.c_str(), GUEST_ENAMETOOLONG)); + const std::string longFile = "/" + std::string(252, 'f'); + REQUIRE(OpenFails(longFile.c_str(), O_CREAT_FLAG, GUEST_ENAMETOOLONG)); + REQUIRE(OpenFails(longFile.c_str(), 0x1, GUEST_EINVAL)); + REQUIRE(UnlinkFails(longFile.c_str(), GUEST_ENAMETOOLONG)); + const std::string longestFile = "/" + std::string(251, 'f'); + void* longest = sem_open_nid_postfix(longestFile.c_str(), O_CREAT_FLAG, 0600, 0); + REQUIRE(longest != nullptr && sem_close_nid_postfix(longest) == 0 && sem_unlink_nid_postfix(longestFile.c_str()) == 0); + REQUIRE(UnlinkFails("relative", GUEST_ENOENT)); + REQUIRE(UnlinkFails("/anyps5-named-missing", GUEST_ENOENT)); + REQUIRE(Throws([] { sem_open_nid_postfix(nullptr, 0); })); + REQUIRE(Throws([] { sem_unlink_nid_postfix(nullptr); })); +} + +static void CheckLifetime() { + const char* name = "/anyps5-named-lifetime"; + void* sem = sem_open_nid_postfix(name, O_CREAT_FLAG | O_EXCL_FLAG, 0600, 2); + REQUIRE(sem != nullptr && Value(sem) == 2); + REQUIRE(sem_open_nid_postfix(name, 0) == sem); + REQUIRE(sem_open_nid_postfix(name, O_CREAT_FLAG, 0600, 9) == sem && Value(sem) == 2); + REQUIRE(OpenFails(name, O_CREAT_FLAG | O_EXCL_FLAG, GUEST_EEXIST)); + REQUIRE(sem_trywait_nid_postfix(sem) == 0 && sem_trywait_nid_postfix(sem) == 0); + *__error_nid_postfix() = 0; + REQUIRE(sem_trywait_nid_postfix(sem) == -1 && *__error_nid_postfix() == GUEST_EAGAIN); + std::thread poster([name] { + void* other = sem_open_nid_postfix(name, 0); + REQUIRE(other != nullptr && sem_post_nid_postfix(other) == 0 && sem_close_nid_postfix(other) == 0); + }); + REQUIRE(sem_wait_nid_postfix(sem) == 0); + poster.join(); + REQUIRE(sem_post_nid_postfix(sem) == 0 && sem_post_nid_postfix(sem) == 0 && Value(sem) == 2); + *__error_nid_postfix() = 0; + REQUIRE(sem_destroy_nid_postfix(sem) == -1 && *__error_nid_postfix() == GUEST_EINVAL && Value(sem) == 2); + for (int open = 0; open < 3; ++open) REQUIRE(sem_close_nid_postfix(sem) == 0); + *__error_nid_postfix() = 0; + REQUIRE(sem_close_nid_postfix(sem) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + + void* reopened = sem_open_nid_postfix(name, 0); + REQUIRE(reopened != nullptr && Value(reopened) == 2); + REQUIRE(sem_unlink_nid_postfix(name) == 0); + REQUIRE(UnlinkFails(name, GUEST_ENOENT)); + REQUIRE(OpenFails(name, 0, GUEST_ENOENT)); + REQUIRE(sem_post_nid_postfix(reopened) == 0 && Value(reopened) == 3); + void* recreated = sem_open_nid_postfix(name, O_CREAT_FLAG | O_EXCL_FLAG, 0600, 5); + REQUIRE(recreated != nullptr && recreated != reopened && Value(recreated) == 5 && Value(reopened) == 3); + REQUIRE(sem_close_nid_postfix(reopened) == 0); + REQUIRE(sem_close_nid_postfix(recreated) == 0 && sem_unlink_nid_postfix(name) == 0); +} + +static void CheckUnnamed() { + std::uintptr_t storage[2] = {}; + REQUIRE(sem_init_nid_postfix(storage, 0, 1) == 0); + *__error_nid_postfix() = 0; + REQUIRE(sem_close_nid_postfix(storage) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + REQUIRE(Value(storage) == 1 && sem_destroy_nid_postfix(storage) == 0); + REQUIRE(Throws([] { sem_close_nid_postfix(nullptr); })); +} + +int main() { + CheckNames(); + CheckLifetime(); + CheckUnnamed(); + return 0; +} diff --git a/core/libs/tests/GuestPosixRandom.cpp b/core/libs/tests/GuestPosixRandom.cpp index 396fcc3aa1..e4f7307064 100644 --- a/core/libs/tests/GuestPosixRandom.cpp +++ b/core/libs/tests/GuestPosixRandom.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePosixForWebKit/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -7,9 +8,6 @@ #include #include -extern "C" void APS5_VABI arc4random_buf_nid_postfix(void*, std::size_t); -extern "C" std::uint32_t APS5_VABI arc4random_nid_postfix(); - void Require(bool condition, std::source_location location = std::source_location::current()) { if (!condition) { std::fprintf(stderr, "Posix random check failed at line %u\n", location.line()); @@ -22,15 +20,18 @@ int main() { std::array second{}; first.fill(0xa5); arc4random_buf_nid_postfix(first.data(), 0); + Require(std::all_of(first.begin(), first.end(), [](unsigned char value) { return value == 0xa5; })); for (const std::size_t size : {1u, 3u, 5u, 4098u}) { first.fill(0xa5); arc4random_buf_nid_postfix(first.data(), size); + Require(std::all_of(first.begin() + static_cast(size), first.end(), [](unsigned char value) { return value == 0xa5; })); } arc4random_buf_nid_postfix(first.data(), first.size()); arc4random_buf_nid_postfix(second.data(), second.size()); + Require(first != second); std::array counts{}; for (const auto value : first) ++counts[value]; diff --git a/core/libs/tests/GuestPosixScalarIo.cpp b/core/libs/tests/GuestPosixScalarIo.cpp index ec96da483a..ea79971ed5 100644 --- a/core/libs/tests/GuestPosixScalarIo.cpp +++ b/core/libs/tests/GuestPosixScalarIo.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -20,30 +23,6 @@ #include #endif -extern "C" { -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI pipe_nid_postfix(int*); -int* APS5_VABI __error_nid_postfix(); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -std::int64_t APS5_VABI _read_nid_postfix(int, void*, std::size_t); -std::int64_t APS5_VABI _write_nid_postfix(int, const void*, std::size_t); -std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int); -std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t); -std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); -int APS5_VABI sceKernelStat(const char*, FileStat*); -int APS5_VABI sceKernelUnlink(const char*); -int APS5_VABI sceKernelTruncate_nid_postfix(const char*, std::int64_t); -int APS5_VABI sceKernelUtimes_nid_postfix(const char*, const void*); -int APS5_VABI sceKernelMkdir(const char*, std::uint16_t); -int APS5_VABI sceKernelRmdir(const char*); -int APS5_VABI sceKernelRename(const char*, const char*); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "POSIX scalar I/O check failed at line %d (guest errno %d)\n", line, *__error_nid_postfix()); @@ -145,6 +124,7 @@ int main() { bool limitDiagnosticPreserved = false; try { write_nid_postfix(descriptor, payload, static_cast(std::numeric_limits::max()) + 1); + } catch (const std::runtime_error& error) { limitDiagnosticPreserved = std::string(error.what()).find("nbytes exceeds platform limit") != std::string::npos; } diff --git a/core/libs/tests/GuestPosixSem.cpp b/core/libs/tests/GuestPosixSem.cpp index f6a2963120..bb2dc5f570 100644 --- a/core/libs/tests/GuestPosixSem.cpp +++ b/core/libs/tests/GuestPosixSem.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -9,16 +11,6 @@ #include #include -extern "C" { -int APS5_VABI sem_init_nid_postfix(void*, int, unsigned int); -int APS5_VABI sem_destroy_nid_postfix(void*); -int APS5_VABI sem_post_nid_postfix(void*); -int APS5_VABI sem_trywait_nid_postfix(void*); -int APS5_VABI sem_timedwait_nid_postfix(void*, const KernelTimespec*); -int APS5_VABI sem_getvalue_nid_postfix(void*, int*); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool condition, const char* message) { if (!condition) { std::fprintf(stderr, "%s\n", message); @@ -26,6 +18,29 @@ static void Require(bool condition, const char* message) { } } +static void CheckRetire() { + constexpr int rounds = 20000; + std::atomic published{nullptr}; + std::thread poster([&] { + for (int round = 0; round < rounds; ++round) { + published.wait(nullptr, std::memory_order_acquire); + std::uintptr_t* target = published.exchange(nullptr, std::memory_order_acq_rel); + Require(sem_post_nid_postfix(target) == 0, "post to a semaphore its waiter destroys must succeed"); + } + }); + for (int round = 0; round < rounds; ++round) { + std::uintptr_t slot = 0; + Require(sem_init_nid_postfix(&slot, 0, 0) == 0, "retired semaphore must initialize"); + published.store(&slot, std::memory_order_release); + published.notify_one(); + for (int spins = 0; sem_trywait_nid_postfix(&slot) != 0; ++spins) { + if (spins >= 4096) std::this_thread::yield(); + } + Require(sem_destroy_nid_postfix(&slot) == 0, "semaphore must be destroyable once its count is taken"); + } + poster.join(); +} + int main(int argc, char** argv) { constexpr unsigned int maximum = 0x7fffffffu; std::uintptr_t sem = 0; @@ -99,4 +114,5 @@ int main(int argc, char** argv) { Require(sem_timedwait_nid_postfix(&sem, &expiredTimeout) == -1 && *__error_nid_postfix() == 60, "blocked expired-timeout wait must report ETIMEDOUT"); Require(sem_getvalue_nid_postfix(&sem, &value) == 0 && value == 0, "failed timed waits must preserve the count"); Require(sem_destroy_nid_postfix(&sem) == 0, "blocked semaphore destroy must succeed"); + CheckRetire(); } diff --git a/core/libs/tests/GuestPosixSpawn.cpp b/core/libs/tests/GuestPosixSpawn.cpp index 5b714f2fb0..d71e652950 100644 --- a/core/libs/tests/GuestPosixSpawn.cpp +++ b/core/libs/tests/GuestPosixSpawn.cpp @@ -1,6 +1,10 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libSceLibcInternal/include/Spawn.hpp" +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include @@ -9,33 +13,17 @@ #include #include #include +#include +#include +#ifdef _WIN32 +#include +#else +extern "C" int kill(int, int); +#endif -extern "C" { -int APS5_VABI posix_spawnattr_init_nid_postfix(PosixSpawnAttributes**); -int APS5_VABI posix_spawnattr_destroy_nid_postfix(PosixSpawnAttributes**); -int APS5_VABI posix_spawnattr_setflags_nid_postfix(PosixSpawnAttributes**, std::int16_t); -int APS5_VABI posix_spawnattr_setsigdefault_nid_postfix(PosixSpawnAttributes**, const GuestSignalSet*); -int APS5_VABI posix_spawnattr_getflags_nid_postfix(PosixSpawnAttributes* const*, std::int16_t*); -int APS5_VABI posix_spawnattr_getpgroup_nid_postfix(PosixSpawnAttributes* const*, std::int32_t*); -int APS5_VABI posix_spawnattr_getschedparam_nid_postfix(PosixSpawnAttributes* const*, std::int32_t*); -int APS5_VABI posix_spawnattr_getschedpolicy_nid_postfix(PosixSpawnAttributes* const*, std::int32_t*); -int APS5_VABI posix_spawnattr_getsigdefault_nid_postfix(PosixSpawnAttributes* const*, GuestSignalSet*); -int APS5_VABI posix_spawnattr_getsigmask_nid_postfix(PosixSpawnAttributes* const*, GuestSignalSet*); -int APS5_VABI posix_spawnattr_setpgroup_nid_postfix(PosixSpawnAttributes**, std::int32_t); -int APS5_VABI posix_spawnattr_setschedparam_nid_postfix(PosixSpawnAttributes**, const std::int32_t*); -int APS5_VABI posix_spawnattr_setschedpolicy_nid_postfix(PosixSpawnAttributes**, std::int32_t); -int APS5_VABI posix_spawnattr_setsigmask_nid_postfix(PosixSpawnAttributes**, const GuestSignalSet*); -int APS5_VABI posix_spawn_file_actions_init_nid_postfix(PosixSpawnFileActions**); -int APS5_VABI posix_spawn_file_actions_destroy_nid_postfix(PosixSpawnFileActions**); -int APS5_VABI posix_spawn_file_actions_addopen_nid_postfix(PosixSpawnFileActions**, int, const char*, int, std::uint16_t); -int APS5_VABI posix_spawn_file_actions_adddup2_nid_postfix(PosixSpawnFileActions**, int, int); -int APS5_VABI posix_spawn_file_actions_addclose_nid_postfix(PosixSpawnFileActions**, int); -int APS5_VABI posix_spawn_nid_postfix(std::int32_t*, const char*, PosixSpawnFileActions* const*, PosixSpawnAttributes* const*, char* const*, char* const*); -int APS5_VABI posix_spawnp_nid_postfix(std::int32_t*, const char*, PosixSpawnFileActions* const*, PosixSpawnAttributes* const*, char* const*, char* const*); -int APS5_VABI setenv_nid_postfix(const char*, const char*, int); -int APS5_VABI unsetenv_nid_postfix(const char*); -int APS5_VABI chdir_nid_postfix(const char*); -} +#if defined(__linux__) +extern char** environ; +#endif static void Require(bool condition, int line) { if (!condition) { @@ -175,29 +163,29 @@ static void CheckSpawn() { std::int32_t processId = 77; Check(ReportsNotImplemented([&] { posix_spawn_nid_postfix(&processId, existing.c_str(), &actions, &attributes, arguments, environment); }, - "posix_spawn: process creation not implemented")); + "posix_spawn: executables outside /app0 (" + existing + ") not implemented")); Check(posix_spawn_nid_postfix(&processId, missing.c_str(), &actions, &attributes, arguments, environment) == NoSuchFile); Check(posix_spawn_nid_postfix(&processId, "", nullptr, nullptr, arguments, nullptr) == NoSuchFile); Check(posix_spawn_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr) == NoSuchFile); Check(setenv_nid_postfix("PATH", bothDirectories.c_str(), 1) == 0); Check(ReportsNotImplemented([&] { posix_spawnp_nid_postfix(&processId, "tool.elf", &actions, &attributes, arguments, nullptr); }, - "posix_spawnp: process creation not implemented")); + "posix_spawnp: executables outside /app0 (" + existing + ") not implemented")); Check(posix_spawnp_nid_postfix(&processId, "missing.elf", &actions, &attributes, arguments, nullptr) == NoSuchFile); Check(posix_spawnp_nid_postfix(&processId, "", nullptr, nullptr, arguments, nullptr) == NoSuchFile); Check(setenv_nid_postfix("PATH", emptyOnly.c_str(), 1) == 0); Check(posix_spawnp_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr) == NoSuchFile); Check(ReportsNotImplemented([&] { posix_spawnp_nid_postfix(&processId, existing.c_str(), nullptr, nullptr, arguments, nullptr); }, - "posix_spawnp: process creation not implemented")); + "posix_spawnp: executables outside /app0 (" + existing + ") not implemented")); Check(posix_spawnp_nid_postfix(&processId, missing.c_str(), nullptr, nullptr, arguments, nullptr) == NoSuchFile); Check(setenv_nid_postfix("PATH", (emptyOnly + ":").c_str(), 1) == 0); Check(posix_spawnp_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr) == NoSuchFile); Check(chdir_nid_postfix(guest.c_str()) == 0); Check(ReportsNotImplemented([&] { posix_spawnp_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr); }, - "posix_spawnp: process creation not implemented")); + "posix_spawnp: executables outside /app0 (./tool.elf) not implemented")); Check(ReportsNotImplemented([&] { posix_spawn_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr); }, - "posix_spawn: process creation not implemented")); + "posix_spawn: executables outside /app0 (tool.elf) not implemented")); Check(chdir_nid_postfix("/") == 0); Check(unsetenv_nid_postfix("PATH") == 0); @@ -215,8 +203,170 @@ static void CheckSpawn() { std::filesystem::remove_all(directory); } +static std::filesystem::path OwnExecutablePath() { +#ifdef _WIN32 + std::wstring own(32768, L'\0'); + const DWORD length = GetModuleFileNameW(nullptr, own.data(), static_cast(own.size())); + own.resize(length); + return std::filesystem::path(own); +#else + std::error_code error; + return std::filesystem::read_symlink("/proc/self/exe", error); +#endif +} + +static std::int32_t SpawnChild(const char* role) { + const auto own = OwnExecutablePath(); + const auto target = own.parent_path() / std::filesystem::path(reinterpret_cast(("child.bin" + own.extension().string()).c_str())); + std::error_code error; + std::filesystem::copy_file(own, target, std::filesystem::copy_options::overwrite_existing, error); + Check(!error); + const auto app0 = std::filesystem::current_path() / "app0"; + Check(std::filesystem::create_directories(app0) || std::filesystem::exists(app0)); + { std::ofstream file(app0 / "child.bin"); file << "child"; } + + char program[] = "/app0/child.bin"; + char* arguments[] = {program, nullptr}; + std::vector entries; +#ifdef _WIN32 + char** const parentEnvironment = _environ; +#else + char** const parentEnvironment = environ; +#endif + for (char** entry = parentEnvironment; entry != nullptr && *entry != nullptr; ++entry) + entries.push_back(*entry); + entries.push_back(std::string("ANYPS5_POSIX_SPAWN_ROLE=") + role); + std::vector environment; + for (auto& entry : entries) + environment.push_back(entry.data()); + environment.push_back(nullptr); + std::int32_t processId = 0; + Check(posix_spawn_nid_postfix(&processId, program, nullptr, nullptr, arguments, environment.data()) == 0); + Check(processId > 0); + return processId; +} + +static void CleanupChild() { + std::error_code error; + std::filesystem::remove(OwnExecutablePath().parent_path() / std::filesystem::path(reinterpret_cast(("child.bin" + OwnExecutablePath().extension().string()).c_str())), error); + std::filesystem::remove_all(std::filesystem::current_path() / "app0", error); +} + +static void CheckSpawnedChild(const char* role, int expectedCode) { + const std::int32_t processId = SpawnChild(role); + int status = -1; + if (std::strcmp(role, "child-sleep") == 0) + Check(waitpid_nid_postfix(processId, &status, 1) == 0); + Check(waitpid_nid_postfix(processId, &status, 0) == processId); + Check((status & 0x7f) == 0 && ((status >> 8) & 0xff) == expectedCode); + status = -1; + Check(waitpid_nid_postfix(processId, &status, 0) == -1 && *__error_nid_postfix() == 10); + CleanupChild(); +} + +static void CheckConcurrentWaiters() { + const std::int32_t processId = SpawnChild("child-sleep"); + int statusA = -1; + int statusB = -1; + int errorA = 0; + int errorB = 0; + int resultA = 0; + int resultB = 0; + const auto waiter = [processId](int* status, int* error) { + const int result = waitpid_nid_postfix(processId, status, 0); + *error = *__error_nid_postfix(); + return result; + }; + std::thread threadA([&] { resultA = waiter(&statusA, &errorA); }); + std::thread threadB([&] { resultB = waiter(&statusB, &errorB); }); + threadA.join(); + threadB.join(); + const bool reapedA = resultA == processId; + const bool reapedB = resultB == processId; + Check(reapedA != reapedB); + const int reapedStatus = reapedA ? statusA : statusB; + Check((reapedStatus & 0x7f) == 0 && ((reapedStatus >> 8) & 0xff) == 43); + Check((reapedA ? errorB : errorA) == 10); + CleanupChild(); +} + +#ifndef _WIN32 +static void CheckStoppedChild() { + const std::int32_t processId = SpawnChild("child-stop"); + int status = -1; + Check(waitpid_nid_postfix(processId, &status, 2) == processId); + Check((status & 0x7f) == 0x7f && (status >> 8) == 17); + Check(kill(processId, SIGCONT) == 0); + Check(waitpid_nid_postfix(processId, &status, 4) == processId && status == 0x13); + Check(waitpid_nid_postfix(processId, &status, 0) == processId); + Check((status & 0x7f) == 0 && ((status >> 8) & 0xff) == 44); + Check(waitpid_nid_postfix(processId, &status, 0) == -1 && *__error_nid_postfix() == 10); + CleanupChild(); +} + +static void CheckKilledChild(const char* role, int guestSignal) { + const std::int32_t processId = SpawnChild(role); + int status = -1; + Check(waitpid_nid_postfix(processId, &status, 0) == processId); + Check((status & 0x7f) == guestSignal); + Check(waitpid_nid_postfix(processId, &status, 0) == -1 && *__error_nid_postfix() == 10); + CleanupChild(); +} +#else +static void CheckTerminatedChild(const char* role, unsigned code, int guestSignal) { + const std::int32_t processId = SpawnChild(role); + const HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, static_cast(processId)); + Check(process != nullptr); + Check(TerminateProcess(process, code) != 0); + CloseHandle(process); + int status = -1; + Check(waitpid_nid_postfix(processId, &status, 0) == processId); + Check(code == 0xFFFFFFFFu ? ((status & 0x7f) == 0 && ((status >> 8) & 0xff) == 255) + : ((status & 0x7f) == guestSignal && (status & 0x80) == 0)); + Check(waitpid_nid_postfix(processId, &status, 0) == -1 && *__error_nid_postfix() == 10); + CleanupChild(); +} +#endif + int main() { + if (const char* role = std::getenv("ANYPS5_POSIX_SPAWN_ROLE")) { + if (std::strcmp(role, "child-exit-42") == 0) std::_Exit(42); + if (std::strcmp(role, "child-exit-neg") == 0) std::exit(-1); +#ifndef _WIN32 + if (std::strcmp(role, "child-stop") == 0) { + std::raise(SIGSTOP); + std::_Exit(44); + } + if (std::strcmp(role, "child-kill-bus") == 0) std::raise(SIGBUS); + if (std::strcmp(role, "child-kill-sys") == 0) std::raise(SIGSYS); +#endif + if (std::strcmp(role, "child-sleep") == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + std::_Exit(43); + } + if (std::strcmp(role, "child-laze") == 0) { + std::this_thread::sleep_for(std::chrono::seconds(30)); + std::_Exit(47); + } + std::_Exit(99); + } CheckAttributes(); CheckFileActions(); CheckSpawn(); + CheckSpawnedChild("child-exit-42", 42); + CheckSpawnedChild("child-sleep", 43); + CheckSpawnedChild("child-exit-neg", 255); + CheckConcurrentWaiters(); +#ifndef _WIN32 + CheckStoppedChild(); + CheckKilledChild("child-kill-bus", 10); + CheckKilledChild("child-kill-sys", 12); +#else + CheckTerminatedChild("child-laze", 0xC0000005u, 11); + CheckTerminatedChild("child-laze", 0xC0000096u, 4); + CheckTerminatedChild("child-laze", 0x80000003u, 5); + CheckTerminatedChild("child-laze", 0xC0000094u, 8); + CheckTerminatedChild("child-laze", 0xC0000409u, 6); + CheckTerminatedChild("child-laze", 0xFFFFFFFFu, 0); +#endif } diff --git a/core/libs/tests/GuestPosixThreadName.cpp b/core/libs/tests/GuestPosixThreadName.cpp new file mode 100644 index 0000000000..05f284f0db --- /dev/null +++ b/core/libs/tests/GuestPosixThreadName.cpp @@ -0,0 +1,46 @@ +#include "SceTypes.hpp" +#include "prx/libScePosixForWebKit/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include +#include +#include +#include + +static constexpr int GUEST_EINVAL = 22; +static constexpr int GUEST_ENAMETOOLONG = 63; + +static void Require(bool value) { if (!value) std::abort(); } + +static std::string ReadName(Pthread thread) { + std::array buffer{}; + buffer.fill(0x55); + Require(pthread_getname_np_nid_postfix(thread, buffer.data()) == 0); + return std::string(buffer.data()); +} + +static void RequireStored(Pthread thread, const std::string& name) { + Require(ReadName(thread) == name); +} + +int main() { + const Pthread self = pthread_self_nid_postfix(); + RequireStored(self, ""); + + Require(pthread_setname_np_nid_postfix(self, "webkit") == 0); + RequireStored(self, "webkit"); + + Require(pthread_setname_np_nid_postfix(self, "") == 0); + RequireStored(self, ""); + + Require(pthread_setname_np_nid_postfix(self, nullptr) == GUEST_EINVAL); + Require(pthread_setname_np_nid_postfix(nullptr, "name") == GUEST_EINVAL); + + const std::string longest(31, 'b'); + Require(pthread_setname_np_nid_postfix(self, longest.c_str()) == 0); + RequireStored(self, longest); + + const std::string tooLong(32, 'c'); + Require(pthread_setname_np_nid_postfix(self, tooLong.c_str()) == GUEST_ENAMETOOLONG); + RequireStored(self, longest); + return 0; +} diff --git a/core/libs/tests/GuestProcessArguments.cpp b/core/libs/tests/GuestProcessArguments.cpp index fa0f5584b8..d32233f67b 100644 --- a/core/libs/tests/GuestProcessArguments.cpp +++ b/core/libs/tests/GuestProcessArguments.cpp @@ -1,11 +1,9 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" int APS5_VABI getargc_nid_postfix(void); -extern "C" const char** APS5_VABI getargv_nid_postfix(void); - static void Require(bool condition, const char* message) { if (!condition) { std::fprintf(stderr, "%s\n", message); diff --git a/core/libs/tests/GuestProgramName.cpp b/core/libs/tests/GuestProgramName.cpp index 48be372cc8..7436649aa5 100644 --- a/core/libs/tests/GuestProgramName.cpp +++ b/core/libs/tests/GuestProgramName.cpp @@ -1,11 +1,13 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "SceTypes.hpp" #include #include extern "C" { -const char* APS5_VABI getprogname_nid_postfix(); + extern const char* __progname_nid_postfix; -int* APS5_VABI __error_nid_postfix(); + } static void Require(bool value) { if (!value) std::abort(); } @@ -14,12 +16,14 @@ int main() { const char* const original = __progname_nid_postfix; *__error_nid_postfix() = 45; Require(getprogname_nid_postfix() == original); + Require(_getprogname_nid_postfix() == original); Require(std::strcmp(getprogname_nid_postfix(), "eboot.bin") == 0); Require(*__error_nid_postfix() == 45); char renamed[] = "quake2"; __progname_nid_postfix = renamed; Require(getprogname_nid_postfix() == renamed); + Require(_getprogname_nid_postfix() == renamed); renamed[0] = 'Q'; Require(std::strcmp(getprogname_nid_postfix(), "Quake2") == 0); diff --git a/core/libs/tests/GuestProprietaryVoiceChatHelper.cpp b/core/libs/tests/GuestProprietaryVoiceChatHelper.cpp index 27f0e6dff8..f3f512f138 100644 --- a/core/libs/tests/GuestProprietaryVoiceChatHelper.cpp +++ b/core/libs/tests/GuestProprietaryVoiceChatHelper.cpp @@ -1,17 +1,10 @@ +#include "prx/libSceProprietaryVoiceChatHelperPrx/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -int APS5_VABI sceProprietaryVoiceChatHelperInitialize(); -int APS5_VABI sceProprietaryVoiceChatHelperTerminate(); -int APS5_VABI sceProprietaryVoiceChatHelperSetVoiceChatState(); -int APS5_VABI sceProprietaryVoiceChatHelperGetVoiceChatUsageState(); -int APS5_VABI sceProprietaryVoiceChatHelperGetVoiceChat(); -} - static void Require(bool value, const char* what) { if (value) return; std::printf("ProprietaryVoiceChatHelper: %s\n", what); diff --git a/core/libs/tests/GuestProtectionReport.cpp b/core/libs/tests/GuestProtectionReport.cpp index 87a3d29b52..00bf0c9ab7 100644 --- a/core/libs/tests/GuestProtectionReport.cpp +++ b/core/libs/tests/GuestProtectionReport.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include @@ -5,17 +6,6 @@ #include #include -extern "C" { -int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int) noexcept; -int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); -int APS5_VABI sceKernelMunmap(std::uint64_t, std::size_t); -int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); -int APS5_VABI sceKernelQueryMemoryProtection(void*, void**, void**, int*); -} - namespace { constexpr std::size_t Page = 0x4000; diff --git a/core/libs/tests/GuestPsml.cpp b/core/libs/tests/GuestPsml.cpp index f82ec1744a..d2cc7925b7 100644 --- a/core/libs/tests/GuestPsml.cpp +++ b/core/libs/tests/GuestPsml.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePsml_debug/libScePsml_debug.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,28 +6,7 @@ #include #include #include - -extern "C" { -std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param); -std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param); -std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param); -std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement800M3_2(void* requirement, const void* param); -std::int32_t APS5_VABI scePsmlMfsrCreateContext800M3_2(void** context, const void* param); -std::int32_t APS5_VABI scePsmlMfsrCreateContext1300(void** context, const void* param); -std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket900(void* context, void* commandBuffer, const void* param); -std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1000(void* context, void* commandBuffer, const void* param); -std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1300(void* context, void* commandBuffer, const void* param); -std::int32_t APS5_VABI scePsmlMfsrInit(); -std::int32_t APS5_VABI scePsmlMfsrGetSharedResourcesInitRequirement(void* requirement, const void* param); -std::int32_t APS5_VABI scePsmlMfsrCreateSharedResources(void** sharedResources, const void* param); -std::int32_t APS5_VABI scePsmlMfsrReleaseSharedResources(void* sharedResources); -std::int32_t APS5_VABI scePsmlMfsrReleaseContext(void* context); -std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacketSizeInDwords(void* context, std::uint32_t* packetSizeInDwords); -std::int32_t APS5_VABI scePsmlMfsrSelectConfig(void* config); -std::int32_t APS5_VABI scePsmlMfsrGetMipmapBias(void* context, float* bias, std::uint32_t dimension); -std::int32_t APS5_VABI scePsmlMfsrRequestCapture(void* context); -std::int32_t APS5_VABI scePsmlMfsrIsCaptureInProgress(void* context); -} +#include namespace { @@ -36,20 +16,15 @@ constexpr std::int32_t kErrNotInitialized = static_cast(0x8A810001 constexpr std::int32_t kErrInvalidArgument = static_cast(0x8A810005); constexpr std::int32_t kErrNullOutput = static_cast(0x8A810009); -struct CommandBuffer { - std::uint32_t* cursor; - std::uint32_t sizeInDwords; -}; - -using CreateContext = std::int32_t (APS5_VABI*)(void**, const void*); using GetDispatchPacket = std::int32_t (APS5_VABI*)(void*, void*, const void*); -void CreateContextUntouched(CreateContext create) { +template +void CreateContextUntouched(std::int32_t (APS5_VABI* create)(void**, const TParam*)) { std::array param{}; std::uint64_t contextStorage = 0; void* const sentinel = &contextStorage; void* context = sentinel; - Require(create(&context, param.data()) == kErrNotInitialized); + Require(create(&context, reinterpret_cast(param.data())) == kErrNotInitialized); Require(context == sentinel); Require(create(nullptr, nullptr) == kErrNotInitialized); } @@ -60,7 +35,7 @@ void DispatchPacketUntouched(GetDispatchPacket dispatch) { std::array dwords; dwords.fill(0xa5a5a5a5u); const auto untouchedDwords = dwords; - CommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; + MfsrCommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; Require(dispatch(&contextStorage, &commandBuffer, param.data()) == kErrNotInitialized); Require(dispatch(nullptr, &commandBuffer, nullptr) == kErrNotInitialized); Require(commandBuffer.cursor == dwords.data()); @@ -78,29 +53,10 @@ bool Throws(TCall call, const char* text) { return false; } -struct Requirement { - std::uint64_t blockBytes; - std::uint64_t alignment; - std::uint64_t blockCount; -}; - -struct Block { - std::uint64_t address; - std::uint64_t bytes; -}; - -struct SharedParam { - std::uint32_t type; - std::uint32_t reserved; - const Block* blocks; - std::uint64_t blockCount; - std::uint64_t reserved2; -}; - struct ContextParam { std::array bytes{}; - void Set(const Block* blocks, std::uint64_t count, void* shared) { + void Set(const MfsrBlock* blocks, std::uint64_t count, void* shared) { std::memcpy(bytes.data() + 0x08, &blocks, sizeof(blocks)); std::memcpy(bytes.data() + 0x10, &count, sizeof(count)); std::memcpy(bytes.data() + 0x38, &shared, sizeof(shared)); @@ -116,8 +72,8 @@ struct ContextParam { } }; -std::array Blocks(const Requirement& requirement, std::uint64_t first) { - std::array blocks{}; +std::vector Blocks(const MfsrRequirement& requirement, std::uint64_t first) { + std::vector blocks(requirement.blockCount); for (std::size_t i = 0; i < blocks.size(); ++i) blocks[i] = {first + i * requirement.blockBytes, requirement.blockBytes}; return blocks; } @@ -126,8 +82,8 @@ void SetupBeforeInit() { std::uint64_t storage = 0; void* const sentinel = &storage; void* handle = sentinel; - Requirement requirement{1, 2, 3}; - SharedParam sharedParam{}; + MfsrRequirement requirement{1, 2, 3}; + MfsrSharedResourcesParam sharedParam{}; Require(scePsmlMfsrGetSharedResourcesInitRequirement(&requirement, &sharedParam) == kErrNotInitialized); Require(requirement.blockBytes == 1 && requirement.alignment == 2 && requirement.blockCount == 3); Require(scePsmlMfsrCreateSharedResources(&handle, &sharedParam) == kErrNotInitialized); @@ -150,10 +106,10 @@ void SetupAfterInit() { Require(scePsmlMfsrInit() == 0); Require(scePsmlMfsrInit() == 0); - SharedParam sharedParam{}; - Requirement shared{}; + MfsrSharedResourcesParam sharedParam{}; + MfsrRequirement shared{}; Require(scePsmlMfsrGetSharedResourcesInitRequirement(&shared, &sharedParam) == 0); - Require(shared.blockBytes == 0x200000 && shared.alignment == 0x200000 && shared.blockCount == 4); + Require(shared.blockBytes == 0x200000 && shared.alignment == 0x200000 && shared.blockCount == 52); const auto sharedBlocks = Blocks(shared, 0x10000000); sharedParam.blocks = sharedBlocks.data(); @@ -164,14 +120,14 @@ void SetupAfterInit() { ContextParam contextParam; contextParam.Set(nullptr, 0, sharedResources); - Requirement context{}; - Require(scePsmlMfsrGetContextBufferRequirement800M3_2(&context, contextParam.bytes.data()) == 0); + MfsrRequirement context{}; + Require(scePsmlMfsrGetContextBufferRequirement800M3_2(&context, reinterpret_cast(contextParam.bytes.data())) == 0); Require(context.blockBytes == 0x200000 && context.alignment == 0x200000 && context.blockCount == 4); const auto contextBlocks = Blocks(context, 0x20000000); contextParam.Set(contextBlocks.data(), contextBlocks.size(), sharedResources); void* mfsrContext = nullptr; - Require(scePsmlMfsrCreateContext800M3_2(&mfsrContext, contextParam.bytes.data()) == 0); + Require(scePsmlMfsrCreateContext800M3_2(&mfsrContext, reinterpret_cast(contextParam.bytes.data())) == 0); Require(mfsrContext != nullptr && mfsrContext != sharedResources); std::uint32_t packetDwords = 0; @@ -201,7 +157,7 @@ void SetupAfterInit() { dimensionsParam.Set(contextBlocks.data(), contextBlocks.size(), sharedResources); dimensionsParam.SetInputDimensions(1920, 1080); void* dimensionContext = nullptr; - Require(scePsmlMfsrCreateContext800M3_2(&dimensionContext, dimensionsParam.bytes.data()) == 0); + Require(scePsmlMfsrCreateContext800M3_2(&dimensionContext, reinterpret_cast(dimensionsParam.bytes.data())) == 0); Require(scePsmlMfsrGetMipmapBias(dimensionContext, &bias, 3840) == 0); Require(bias == 0.0f); Require(scePsmlMfsrGetMipmapBias(dimensionContext, &bias, 1920) == 0); @@ -217,7 +173,7 @@ void SetupAfterInit() { descriptorParam.Set(contextBlocks.data(), contextBlocks.size(), sharedResources); descriptorParam.SetDescriptor0(descriptor.data()); void* descriptorContext = nullptr; - Require(scePsmlMfsrCreateContext800M3_2(&descriptorContext, descriptorParam.bytes.data()) == 0); + Require(scePsmlMfsrCreateContext800M3_2(&descriptorContext, reinterpret_cast(descriptorParam.bytes.data())) == 0); Require(scePsmlMfsrGetMipmapBias(descriptorContext, &bias, 2560) == 0); Require(bias == 0.0f); Require(scePsmlMfsrReleaseContext(descriptorContext) == 0); @@ -229,9 +185,47 @@ void SetupAfterInit() { Require(scePsmlMfsrReleaseSharedResources(sharedResources) == 0); } +void Mfsr2OnBaseConsole() { + constexpr std::int32_t notInitialized = static_cast(0x8A8A0001); + constexpr std::int32_t unsupportedConsole = static_cast(0x8A8A0012); + Require(scePsmlMfsr2Init() == unsupportedConsole); + Require(scePsmlMfsr2Init() == unsupportedConsole); + + std::array param{}; + std::array requirement; + requirement.fill(0xa5); + const auto untouchedRequirement = requirement; + Require(scePsmlMfsr2GetSharedResourcesInitRequirement(reinterpret_cast(requirement.data()), reinterpret_cast(param.data())) == notInitialized); + Require(scePsmlMfsr2GetContextInitRequirement(reinterpret_cast(requirement.data()), reinterpret_cast(param.data())) == notInitialized); + Require(requirement == untouchedRequirement); + + std::uint64_t storage = 0; + void* const sentinel = &storage; + void* handle = sentinel; + Require(scePsmlMfsr2CreateSharedResources(&handle, reinterpret_cast(param.data())) == notInitialized); + Require(scePsmlMfsr2CreateContext(&handle, reinterpret_cast(param.data())) == notInitialized); + Require(handle == sentinel); + Require(scePsmlMfsr2ReleaseContext(sentinel) == notInitialized); + Require(scePsmlMfsr2ReleaseSharedResources(sentinel) == notInitialized); + + std::uint32_t packetDwords = 0xa5a5a5a5; + Require(scePsmlMfsr2GetDispatchPacketsSizeInDwords(sentinel, &packetDwords) == notInitialized); + Require(packetDwords == 0xa5a5a5a5); + + std::array dispatchParam{}; + std::array dwords; + dwords.fill(0xa5a5a5a5u); + const auto untouchedDwords = dwords; + MfsrCommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; + Require(scePsmlMfsr2GetDispatchPackets(sentinel, &commandBuffer, reinterpret_cast(dispatchParam.data())) == notInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(commandBuffer.sizeInDwords == dwords.size()); + Require(dwords == untouchedDwords); +} + void RefusedAfterInit() { - SharedParam sharedParam{}; - Requirement requirement{}; + MfsrSharedResourcesParam sharedParam{}; + MfsrRequirement requirement{}; sharedParam.type = 1; Require(Throws([&] { scePsmlMfsrGetSharedResourcesInitRequirement(&requirement, &sharedParam); }, "type 1 is not supported")); @@ -239,7 +233,7 @@ void RefusedAfterInit() { Require(scePsmlMfsrGetSharedResourcesInitRequirement(&requirement, &sharedParam) == 0); const auto blocks = Blocks(requirement, 0x10000000); sharedParam.blocks = blocks.data(); - sharedParam.blockCount = 3; + sharedParam.blockCount = 51; void* sharedResources = nullptr; Require(Throws([&] { scePsmlMfsrCreateSharedResources(&sharedResources, &sharedParam); }, "fewer memory blocks")); auto small = blocks; @@ -253,12 +247,12 @@ void RefusedAfterInit() { ContextParam contextParam; contextParam.Set(blocks.data(), blocks.size(), &foreign); void* context = nullptr; - Require(Throws([&] { scePsmlMfsrCreateContext800M3_2(&context, contextParam.bytes.data()); }, "shared resources handle was not created")); + Require(Throws([&] { scePsmlMfsrCreateContext800M3_2(&context, reinterpret_cast(contextParam.bytes.data())); }, "shared resources handle was not created")); Require(context == nullptr); std::array param{}; std::array dwords{}; - CommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; + MfsrCommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; Require(Throws([&] { scePsmlMfsrGetContextBufferRequirement1100(&requirement, param.data()); }, "not implemented")); Require(Throws([&] { scePsmlMfsrCreateContext1300(&context, param.data()); }, "not implemented")); Require(Throws([&] { scePsmlMfsrGetDispatchMfsrPacket900(&foreign, &commandBuffer, param.data()); }, "not implemented")); @@ -288,7 +282,7 @@ int main() { std::array dwords; dwords.fill(0xa5a5a5a5u); const auto untouchedDwords = dwords; - CommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; + MfsrCommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; Require(scePsmlMfsrGetDispatchMfsrPacket1100(&contextStorage, &commandBuffer, param.data()) == kErrNotInitialized); Require(commandBuffer.cursor == dwords.data()); Require(commandBuffer.sizeInDwords == dwords.size()); @@ -300,7 +294,7 @@ int main() { std::array olderRequirement; olderRequirement.fill(0xa5); const auto untouchedOlderRequirement = olderRequirement; - Require(scePsmlMfsrGetContextBufferRequirement800M3_2(olderRequirement.data(), param.data()) == kErrNotInitialized); + Require(scePsmlMfsrGetContextBufferRequirement800M3_2(reinterpret_cast(olderRequirement.data()), reinterpret_cast(param.data())) == kErrNotInitialized); Require(olderRequirement == untouchedOlderRequirement); Require(scePsmlMfsrGetContextBufferRequirement800M3_2(nullptr, nullptr) == kErrNotInitialized); @@ -310,6 +304,7 @@ int main() { DispatchPacketUntouched(scePsmlMfsrGetDispatchMfsrPacket1000); DispatchPacketUntouched(scePsmlMfsrGetDispatchMfsrPacket1300); SetupBeforeInit(); + Mfsr2OnBaseConsole(); SetupAfterInit(); RefusedAfterInit(); } diff --git a/core/libs/tests/GuestPthreadAttr.cpp b/core/libs/tests/GuestPthreadAttr.cpp index 5afa252aa8..7e85c80984 100644 --- a/core/libs/tests/GuestPthreadAttr.cpp +++ b/core/libs/tests/GuestPthreadAttr.cpp @@ -1,46 +1,15 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include +#include #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadSetprio(Pthread thread, int prio); -int APS5_VABI scePthreadSetaffinity(Pthread thread, KernelCpumask mask); -int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr); -int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched); -int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param); -int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask); -int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize); -int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param); -int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask* mask); -int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, std::size_t* stackSize); -int APS5_VABI scePthreadAttrGetdetachstate(const PthreadAttr* attr, int* state); -int APS5_VABI scePthreadAttrGetsolosched(const PthreadAttr* attr, int* solosched); -int APS5_VABI pthread_attr_setstacksize_nid_postfix(PthreadAttr* attr, std::size_t stackSize); -int APS5_VABI pthread_attr_setsolosched_np_nid_postfix(PthreadAttr* attr, int solosched); -int APS5_VABI pthread_attr_getinheritsched_nid_postfix(const PthreadAttr* attr, int* inherit_sched); -int APS5_VABI pthread_attr_setinheritsched_nid_postfix(PthreadAttr* attr, int inherit_sched); -int APS5_VABI pthread_attr_getscope_nid_postfix(const PthreadAttr* attr, int* scope); -int APS5_VABI pthread_attr_setscope_nid_postfix(PthreadAttr* attr, int scope); -int APS5_VABI pthread_attr_get_np_nid_postfix(Pthread thread, PthreadAttr* attr); -int APS5_VABI pthread_attr_setschedparam_nid_postfix(PthreadAttr* attr, const KernelSchedParam* param); -int APS5_VABI pthread_attr_setschedpolicy_nid_postfix(PthreadAttr* attr, int policy); -int APS5_VABI pthread_attr_getschedpolicy_nid_postfix(const PthreadAttr* attr, int* policy); -int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int state); -int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); -int APS5_VABI scePthreadKeyDelete(PthreadKey key); -int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); -Pthread APS5_VABI scePthreadSelf(); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static constexpr int EXPLICIT_SCHED = 0; static constexpr int INHERIT_SCHED = 4; static constexpr int SCOPE_PROCESS = 0; @@ -52,9 +21,9 @@ static constexpr KernelCpumask CREATION_AFFINITY = 0x3; static constexpr KernelCpumask UPDATED_AFFINITY = 0x1000; static constexpr std::size_t STACK_SIZE = 2u << 20; static constexpr std::size_t MIN_STACK_SIZE = 16384; +static constexpr int GUEST_ESRCH = 3; static constexpr int GUEST_EINVAL = 22; static constexpr int GUEST_ENOTSUP = 45; -static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); static constexpr int DETACH_DETACHED = 1; static constexpr int DETACHED_THREADS = 32; @@ -67,12 +36,6 @@ struct ReportedAttributes { int detachState = -1; }; -extern "C" { -int APS5_VABI pthread_attr_setstack_nid_postfix(PthreadAttr* attr, void* addr, std::size_t size); -int APS5_VABI pthread_attr_getstack_nid_postfix(const PthreadAttr* attr, void** addr, std::size_t* size); -int APS5_VABI pthread_attr_getstackaddr_nid_postfix(const PthreadAttr* attr, void** addr); -} - static ReportedAttributes Query(Pthread thread) { PthreadAttr attr = nullptr; Require(scePthreadAttrInit(&attr) == SCE_OK); @@ -101,8 +64,6 @@ static void CheckStackSizeLimit() { Require(scePthreadAttrDestroy(&attr) == SCE_OK); } -extern "C" int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched); - static void CheckSoloSched() { PthreadAttr attr = nullptr; Require(pthread_attr_setsolosched_np_nid_postfix(nullptr, 1) == GUEST_EINVAL); @@ -207,6 +168,7 @@ static void CheckSchedulingAttributes() { bool threw = false; try { pthread_attr_getinheritsched_nid_postfix(&attr, nullptr); + } catch (const std::exception&) { threw = true; } @@ -266,11 +228,13 @@ static void CheckThreadScope() { Require(scePthreadJoin(inheritedThread, nullptr) == SCE_OK); } -static int QueryResult(Pthread thread) { +static int QueryResult(Pthread thread, bool posix = false) { PthreadAttr attr = nullptr; Require(scePthreadAttrInit(&attr) == SCE_OK); Require(scePthreadAttrSetstacksize(&attr, STACK_SIZE) == SCE_OK); - const int result = scePthreadAttrGet(thread, &attr); + *__error_nid_postfix() = 12345; + const int result = posix ? pthread_attr_get_np_nid_postfix(thread, &attr) : scePthreadAttrGet(thread, &attr); + if (posix) Require(*__error_nid_postfix() == 12345); std::size_t stackSize = 0; Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK); Require(result == SCE_OK || stackSize == STACK_SIZE); @@ -278,6 +242,13 @@ static int QueryResult(Pthread thread) { return result; } +static void CheckPosixQuery(Pthread thread, int expected) { + const int result = QueryResult(thread, true); + if (result == expected) return; + std::fprintf(stderr, "pthread_attr_get_np returned %d, expected %d\n", result, expected); + std::abort(); +} + static bool ReportedEnded(Pthread thread) { const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); while (QueryResult(thread) == SCE_OK) { @@ -290,11 +261,13 @@ static bool ReportedEnded(Pthread thread) { struct ExitQuery { PthreadKey key = -1; int result = -1; + int posixResult = -1; }; static void APS5_VABI QuerySelfAtExit(void* value) { auto* query = static_cast(value); query->result = QueryResult(scePthreadSelf()); + query->posixResult = QueryResult(scePthreadSelf(), true); } static void* APS5_VABI SetExitQuery(void* arg) { @@ -309,22 +282,28 @@ static void* APS5_VABI Finish(void*) { static void CheckEndedThreads() { Require(QueryResult(scePthreadSelf()) == SCE_OK); + CheckPosixQuery(scePthreadSelf(), 0); + CheckPosixQuery(nullptr, GUEST_EINVAL); Pthread joined = nullptr; Require(scePthreadCreate(&joined, nullptr, Finish, nullptr, nullptr) == SCE_OK); Require(scePthreadJoin(joined, nullptr) == SCE_OK); Require(QueryResult(joined) == SCE_KERNEL_ERROR_ESRCH); + CheckPosixQuery(joined, GUEST_ESRCH); PthreadAttr unset = nullptr; Require(scePthreadAttrGet(joined, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrGet(joined, &unset) == SCE_KERNEL_ERROR_EINVAL); + Require(pthread_attr_get_np_nid_postfix(joined, nullptr) == GUEST_EINVAL); + Require(pthread_attr_get_np_nid_postfix(joined, &unset) == GUEST_EINVAL); ExitQuery exitQuery; Require(scePthreadKeyCreate(&exitQuery.key, reinterpret_cast(QuerySelfAtExit)) == SCE_OK); Pthread finished = nullptr; Require(scePthreadCreate(&finished, nullptr, SetExitQuery, &exitQuery, nullptr) == SCE_OK); Require(ReportedEnded(finished)); + CheckPosixQuery(finished, GUEST_ESRCH); Require(scePthreadJoin(finished, nullptr) == SCE_OK); - Require(exitQuery.result == SCE_OK); + Require(exitQuery.result == SCE_OK && exitQuery.posixResult == 0); Require(QueryResult(finished) == SCE_KERNEL_ERROR_ESRCH); Require(scePthreadKeyDelete(exitQuery.key) == SCE_OK); @@ -335,6 +314,7 @@ static void CheckEndedThreads() { Pthread detached = nullptr; Require(scePthreadCreate(&detached, &detachedAttr, Finish, nullptr, nullptr) == SCE_OK); Require(ReportedEnded(detached)); + CheckPosixQuery(detached, GUEST_ESRCH); } Require(scePthreadAttrDestroy(&detachedAttr) == SCE_OK); } diff --git a/core/libs/tests/GuestPthreadAttrErrors.cpp b/core/libs/tests/GuestPthreadAttrErrors.cpp index 06c2e15832..97636eb722 100644 --- a/core/libs/tests/GuestPthreadAttrErrors.cpp +++ b/core/libs/tests/GuestPthreadAttrErrors.cpp @@ -1,29 +1,12 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize); -int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, std::size_t* stackSize); -int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int state); -int APS5_VABI scePthreadAttrGetdetachstate(const PthreadAttr* attr, int* state); -int APS5_VABI scePthreadAttrSetschedpolicy(PthreadAttr* attr, int policy); -int APS5_VABI scePthreadAttrGetschedpolicy(const PthreadAttr* attr, int* policy); -int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched); -int APS5_VABI scePthreadAttrGetinheritsched(const PthreadAttr* attr, int* inheritSched); -int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param); -int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param); -int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask); -int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask* mask); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); -static constexpr int SCE_KERNEL_ERROR_EOPNOTSUPP = static_cast(0x8002002Du); static void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestPthreadAttrStack.cpp b/core/libs/tests/GuestPthreadAttrStack.cpp index faf907527a..4193f08fe6 100644 --- a/core/libs/tests/GuestPthreadAttrStack.cpp +++ b/core/libs/tests/GuestPthreadAttrStack.cpp @@ -1,19 +1,9 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackAddress, std::size_t* stackSize); -int APS5_VABI scePthreadAttrGetstackaddr(const PthreadAttr* attr, void** stackAddress); -int APS5_VABI scePthreadAttrGetguardsize(const PthreadAttr* attr, std::size_t* guardSize); -int APS5_VABI scePthreadAttrSetguardsize(PthreadAttr* attr, std::size_t guardSize); -int APS5_VABI scePthreadAttrSetstack(PthreadAttr* attr, void* stackAddress, std::size_t stackSize); -int APS5_VABI scePthreadAttrSetstackaddr(PthreadAttr* attr, void* stackAddress); -} - static constexpr int SCE_OK = 0; static constexpr int SceEinval = static_cast(0x80020016u); static constexpr std::size_t DefaultStackSize = 1u << 20; diff --git a/core/libs/tests/GuestPthreadBarrier.cpp b/core/libs/tests/GuestPthreadBarrier.cpp index 6fbdcb4bad..3b314e9430 100644 --- a/core/libs/tests/GuestPthreadBarrier.cpp +++ b/core/libs/tests/GuestPthreadBarrier.cpp @@ -1,24 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -struct GuestBarrier; -struct GuestBarrierattr; - -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI pthread_barrierattr_init_nid_postfix(GuestBarrierattr** attr); -int APS5_VABI pthread_barrierattr_destroy_nid_postfix(GuestBarrierattr** attr); -int APS5_VABI pthread_barrierattr_getpshared_nid_postfix(GuestBarrierattr* const* attr, int* pshared); -int APS5_VABI pthread_barrierattr_setpshared_nid_postfix(GuestBarrierattr** attr, int pshared); -int APS5_VABI pthread_barrier_init_nid_postfix(GuestBarrier** barrier, GuestBarrierattr* const* attr, unsigned count); -int APS5_VABI pthread_barrier_wait_nid_postfix(GuestBarrier** barrier); -int APS5_VABI pthread_barrier_destroy_nid_postfix(GuestBarrier** barrier); -} - static constexpr int GUEST_EINVAL = 22; static constexpr int PROCESS_PRIVATE = 0; static constexpr int PROCESS_SHARED = 1; @@ -28,7 +14,7 @@ static constexpr unsigned WAITERS = 4; static void Require(bool value) { if (!value) std::abort(); } struct Context { - GuestBarrier* barrier = nullptr; + PthreadBarrierPrivate* barrier = nullptr; std::atomic serial{0}; std::atomic released{0}; }; @@ -57,7 +43,7 @@ static void RunWaiters(Context& context) { } int main() { - GuestBarrierattr* missing = nullptr; + PthreadBarrierattrPrivate* missing = nullptr; int pshared = -1; Require(pthread_barrierattr_init_nid_postfix(nullptr) == GUEST_EINVAL); Require(pthread_barrierattr_destroy_nid_postfix(nullptr) == GUEST_EINVAL); @@ -68,7 +54,7 @@ int main() { Require(pthread_barrierattr_setpshared_nid_postfix(&missing, PROCESS_PRIVATE) == GUEST_EINVAL); Require(pshared == -1); - GuestBarrierattr* attr = nullptr; + PthreadBarrierattrPrivate* attr = nullptr; Require(pthread_barrierattr_init_nid_postfix(&attr) == 0 && attr != nullptr); Require(pthread_barrierattr_getpshared_nid_postfix(&attr, &pshared) == 0 && pshared == PROCESS_PRIVATE); Require(pthread_barrierattr_setpshared_nid_postfix(&attr, 2) == GUEST_EINVAL); @@ -88,9 +74,11 @@ int main() { catch (const std::invalid_argument&) { rejected = true; } Require(rejected); - GuestBarrier* invalid = nullptr; + PthreadBarrierPrivate* invalid = nullptr; Require(pthread_barrier_init_nid_postfix(nullptr, &attr, 1) == GUEST_EINVAL); Require(pthread_barrier_init_nid_postfix(&invalid, &attr, 0) == GUEST_EINVAL && invalid == nullptr); + Require(pthread_barrier_init_nid_postfix(&invalid, &attr, 0x80000000u) == GUEST_EINVAL && invalid == nullptr); + Require(pthread_barrier_init_nid_postfix(&invalid, &attr, 0xffffffffu) == GUEST_EINVAL && invalid == nullptr); Context withAttr; Require(pthread_barrier_init_nid_postfix(&withAttr.barrier, &attr, WAITERS) == 0 && withAttr.barrier != nullptr); @@ -118,7 +106,7 @@ int main() { Require(destroyed.serial == 1); } - GuestBarrier* single = nullptr; + PthreadBarrierPrivate* single = nullptr; Require(pthread_barrierattr_init_nid_postfix(&attr) == 0); Require(pthread_barrier_init_nid_postfix(&single, &attr, 1) == 0); Require(pthread_barrier_wait_nid_postfix(&single) == BARRIER_SERIAL_THREAD); diff --git a/core/libs/tests/GuestPthreadCancel.cpp b/core/libs/tests/GuestPthreadCancel.cpp index 81cc47ebde..9309d74dae 100644 --- a/core/libs/tests/GuestPthreadCancel.cpp +++ b/core/libs/tests/GuestPthreadCancel.cpp @@ -1,3 +1,10 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" #include "SceTypes.hpp" #include #include @@ -5,42 +12,7 @@ #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -Pthread APS5_VABI scePthreadSelf(); -void APS5_VABI scePthreadExit(void* retval); -int APS5_VABI scePthreadCancel(Pthread thread); -int APS5_VABI scePthreadSetcancelstate(int state, int* old_state); -int APS5_VABI scePthreadSetcanceltype(int type, int* old_type); -void APS5_VABI scePthreadTestcancel(); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); -int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); -int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex); -int APS5_VABI scePthreadSemInit(PthreadSem* sem, int flag, unsigned int value, const char* name); -int APS5_VABI scePthreadSemDestroy(PthreadSem* sem); -int APS5_VABI scePthreadSemWait(PthreadSem* sem); -int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); -int APS5_VABI pthread_cancel_nid_postfix(Pthread thread); -int APS5_VABI pthread_setcanceltype_nid_postfix(int type, int* old_type); -int APS5_VABI sem_init_nid_postfix(void* sem, int pshared, unsigned int value); -int APS5_VABI sem_destroy_nid_postfix(void* sem); -int APS5_VABI sem_wait_nid_postfix(void* sem); -int APS5_VABI sem_timedwait_nid_postfix(void* sem, const KernelTimespec* abstime); -int APS5_VABI sem_getvalue_nid_postfix(void* sem, int* value); -void APS5_VABI __pthread_cleanup_push_imp_nid_postfix(void (APS5_VABI* routine)(void*), void* argument, void* info); -void APS5_VABI __pthread_cleanup_pop_imp_nid_postfix(int execute); -void APS5_VABI pthread_cleanup_push_nid_postfix(void (APS5_VABI* routine)(void*), void* argument); -void APS5_VABI pthread_cleanup_pop_nid_postfix(int execute); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static constexpr int GUEST_ESRCH = 3; static constexpr int GUEST_EINVAL = 22; static constexpr int CANCEL_ENABLE = 0; @@ -80,6 +52,7 @@ static void* APS5_VABI CondWaiter(void*) { std::uintptr_t info[8]; Require(scePthreadMutexLock(&shared.mutex) == SCE_OK); __pthread_cleanup_push_imp_nid_postfix(UnlockMutex, nullptr, info); + shared.ready.store(true); for (;;) Require(scePthreadCondWait(&shared.cond, &shared.mutex) == SCE_OK); } @@ -87,6 +60,7 @@ static void* APS5_VABI CondWaiter(void*) { static void* APS5_VABI SemWaiter(void*) { shared.ready.store(true); scePthreadSemWait(&shared.sem); + shared.returned.store(true); return nullptr; } @@ -94,6 +68,7 @@ static void* APS5_VABI SemWaiter(void*) { static void* APS5_VABI PosixSemWaiter(void*) { shared.ready.store(true); sem_wait_nid_postfix(&shared.posixSem); + shared.returned.store(true); return nullptr; } @@ -144,6 +119,7 @@ static void* APS5_VABI Idler(void*) { static void* APS5_VABI Joiner(void*) { shared.ready.store(true); scePthreadJoin(shared.target, nullptr); + shared.returned.store(true); return nullptr; } @@ -160,10 +136,12 @@ static void* APS5_VABI Disabled(void*) { while (!shared.release.load()) { sceKernelUsleep_nid_postfix(1000); scePthreadTestcancel(); + } shared.survived.store(true); Require(scePthreadSetcancelstate(CANCEL_ENABLE, &old) == SCE_OK && old == CANCEL_DISABLE); scePthreadTestcancel(); + shared.returned.store(true); return nullptr; } @@ -175,6 +153,7 @@ static void* APS5_VABI Asynchronous(void*) { shared.ready.store(true); while (!shared.release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); scePthreadSetcancelstate(CANCEL_ENABLE, nullptr); + shared.returned.store(true); return nullptr; } @@ -185,8 +164,10 @@ static void* APS5_VABI Handlers(void*) { __pthread_cleanup_push_imp_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{1}), first); __pthread_cleanup_push_imp_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{2}), second); __pthread_cleanup_pop_imp_nid_postfix(0); + __pthread_cleanup_push_imp_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{3}), second); __pthread_cleanup_pop_imp_nid_postfix(1); + scePthreadExit(reinterpret_cast(std::intptr_t{7})); return nullptr; } @@ -196,8 +177,10 @@ static void* APS5_VABI HeapHandlers(void*) { __pthread_cleanup_push_imp_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{1}), first); pthread_cleanup_push_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{2})); pthread_cleanup_pop_nid_postfix(0); + pthread_cleanup_push_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{3})); __pthread_cleanup_pop_imp_nid_postfix(1); + pthread_cleanup_push_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{4})); scePthreadExit(reinterpret_cast(std::intptr_t{8})); return nullptr; diff --git a/core/libs/tests/GuestPthreadCancelRecovery.cpp b/core/libs/tests/GuestPthreadCancelRecovery.cpp index 297f761a92..e7f647ad7c 100644 --- a/core/libs/tests/GuestPthreadCancelRecovery.cpp +++ b/core/libs/tests/GuestPthreadCancelRecovery.cpp @@ -1,3 +1,8 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" #include "SceTypes.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include @@ -17,46 +22,6 @@ #include #endif -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadCancel(Pthread thread); -int APS5_VABI scePthreadSetcancelstate(int state, int* oldState); -int APS5_VABI scePthreadSetcanceltype(int type, int* oldType); -void APS5_VABI scePthreadTestcancel(); -int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); -int APS5_VABI scePthreadKeyDelete(PthreadKey key); -int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); -int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); -int APS5_VABI scePthreadCondSignal(PthreadCond* cond); -int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec); -int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex); -int APS5_VABI pthread_cond_wait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex); -int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime); -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* time); -int APS5_VABI scePthreadSemInit(PthreadSem* sem, int flag, unsigned int value, const char* name); -int APS5_VABI scePthreadSemDestroy(PthreadSem* sem); -int APS5_VABI scePthreadSemWait(PthreadSem* sem); -int APS5_VABI scePthreadSemTimedwait(PthreadSem* sem, KernelUseconds usec); -int APS5_VABI scePthreadSemGetvalue(PthreadSem* sem, int* value); -int APS5_VABI sem_init_nid_postfix(void* sem, int pshared, unsigned int value); -int APS5_VABI sem_destroy_nid_postfix(void* sem); -int APS5_VABI sem_wait_nid_postfix(void* sem); -int APS5_VABI sem_reltimedwait_np_nid_postfix(void* sem, std::uint32_t usec); -int APS5_VABI sem_getvalue_nid_postfix(void* sem, int* value); -int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); -void APS5_VABI __pthread_cleanup_push_imp_nid_postfix(void (APS5_VABI* routine)(void*), void* argument, void* info); -void APS5_VABI __pthread_cleanup_pop_imp_nid_postfix(int execute); -} - static constexpr int CANCEL_ENABLE = 0; static constexpr int CANCEL_DISABLE = 1; static constexpr int CANCEL_DEFERRED = 0; @@ -121,6 +86,7 @@ static void APS5_VABI Record(void* value) { static void APS5_VABI CleanupWithPoints(void*) { Record(reinterpret_cast(std::uintptr_t{2})); scePthreadTestcancel(); + Require(sceKernelUsleep_nid_postfix(1) == 0); Record(reinterpret_cast(std::uintptr_t{3})); } @@ -150,6 +116,7 @@ static void* APS5_VABI PendingToken(void*) { Require(scePthreadSetspecific(key, &normalResult) == 0); __pthread_cleanup_push_imp_nid_postfix(Record, reinterpret_cast(std::uintptr_t{1}), first); __pthread_cleanup_push_imp_nid_postfix(CleanupWithPoints, nullptr, second); + Gate(); WaitForToken(); returned = true; @@ -161,6 +128,7 @@ static void CheckDefaults() { Require(scePthreadSetcancelstate(CANCEL_ENABLE, &old) == 0 && old == CANCEL_ENABLE); Require(scePthreadSetcanceltype(CANCEL_DEFERRED, &old) == 0 && old == CANCEL_DEFERRED); scePthreadTestcancel(); + } static void* APS5_VABI ConsumeToken(void*) { @@ -198,17 +166,21 @@ static void* APS5_VABI ConditionWaiter(void*) { Require(scePthreadMutexLock(&mutex) == 0); std::uintptr_t info[8]; __pthread_cleanup_push_imp_nid_postfix(Unlock, nullptr, info); + Signal(ready); while (!conditionReleased) Require(scePthreadCondTimedwait(&cond, &mutex, WAIT_MICROSECONDS) == 0); Require(scePthreadMutexUnlock(&mutex) == 0); __pthread_cleanup_pop_imp_nid_postfix(0); + if (disabled) { stage = 1; scePthreadTestcancel(); + int old = -1; Require(scePthreadSetcancelstate(CANCEL_ENABLE, &old) == 0 && old == CANCEL_DISABLE); stage = 2; scePthreadTestcancel(); + } returned = true; return &normalResult; @@ -279,6 +251,7 @@ static void* APS5_VABI SignaledConditionWaiter(void* value) { for (int level = 0; level < context.lockCount; ++level) Require(scePthreadMutexLock(&context.mutex) == 0); std::uintptr_t cleanup[8]; __pthread_cleanup_push_imp_nid_postfix(UnlockSignaledCondition, &worker, cleanup); + context.ready.fetch_add(1); int result = 0; do { @@ -303,6 +276,7 @@ static void* APS5_VABI SignaledConditionWaiter(void* value) { } while (result == 0 && !context.released); context.results[worker.index] = result; __pthread_cleanup_pop_imp_nid_postfix(1); + return nullptr; } diff --git a/core/libs/tests/GuestPthreadCondattr.cpp b/core/libs/tests/GuestPthreadCondattr.cpp index 237ac4374e..465bd7c678 100644 --- a/core/libs/tests/GuestPthreadCondattr.cpp +++ b/core/libs/tests/GuestPthreadCondattr.cpp @@ -1,17 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr); -int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr); -int APS5_VABI scePthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clockId); -int APS5_VABI pthread_condattr_setclock_nid_postfix(PthreadCondattr* attr, KernelClockid clockId); -int APS5_VABI pthread_condattr_setpshared_nid_postfix(PthreadCondattr* attr, int pshared); -int APS5_VABI pthread_condattr_getpshared_nid_postfix(const PthreadCondattr* attr, int* pshared); -} - static constexpr int SCE_OK = 0; static constexpr int SceEinval = static_cast(0x80020016u); static constexpr int GuestEinval = 22; @@ -31,14 +24,23 @@ int main() { Require(pthread_condattr_setpshared_nid_postfix(&unset, 0) == GuestEinval); Require(pthread_condattr_getpshared_nid_postfix(nullptr, &sharing) == GuestEinval && sharing == -1); Require(pthread_condattr_getpshared_nid_postfix(&unset, &sharing) == GuestEinval && sharing == -1); + Require(scePthreadCondattrSetpshared(&unset, 0) == SceEinval); + Require(scePthreadCondattrGetpshared(nullptr, &sharing) == SceEinval && sharing == -1); PthreadCondattr attr = nullptr; Require(scePthreadCondattrInit(&attr) == SCE_OK); - for (const KernelClockid clock : {0, 1, 2, Monotonic, 5, 12}) { + for (const KernelClockid clock : {0, 1, 2, Monotonic}) { Require(scePthreadCondattrSetclock(&attr, clock) == SCE_OK); Require(pthread_condattr_setclock_nid_postfix(&attr, clock) == 0); } + Require(scePthreadCondattrSetpshared(&attr, 1) == SceEinval); + Require(scePthreadCondattrSetpshared(&attr, 0) == SCE_OK); + Require(scePthreadCondattrGetpshared(&attr, &sharing) == SCE_OK && sharing == 0); Require(pthread_condattr_getpshared_nid_postfix(&attr, &sharing) == 0 && sharing == 0); + for (const KernelClockid clock : {-1, 3, 5, 12, 13, 14, 15}) { + Require(scePthreadCondattrSetclock(&attr, clock) == SceEinval); + Require(pthread_condattr_setclock_nid_postfix(&attr, clock) == GuestEinval); + } for (const int invalid : {1, 2, -1}) { Require(pthread_condattr_setpshared_nid_postfix(&attr, invalid) == GuestEinval); sharing = -1; @@ -50,6 +52,7 @@ int main() { bool threw = false; try { pthread_condattr_getpshared_nid_postfix(&attr, nullptr); + } catch (const std::runtime_error&) { threw = true; } diff --git a/core/libs/tests/GuestPthreadCreateSuspended.cpp b/core/libs/tests/GuestPthreadCreateSuspended.cpp new file mode 100644 index 0000000000..cadde5c8e2 --- /dev/null +++ b/core/libs/tests/GuestPthreadCreateSuspended.cpp @@ -0,0 +1,276 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel_sys/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; +static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); +static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003u); +static constexpr int GUEST_ESRCH = 3; +static constexpr int GUEST_EINVAL = 22; +static constexpr int GUEST_SIGUSR1 = 30; +static constexpr int GUEST_CREATE_DETACHED = 1; +static constexpr int DETACHED_ROUNDS = 200; +static constexpr auto SUSPENDED_WINDOW = std::chrono::milliseconds(50); +static void* const CANCELED = reinterpret_cast(std::uintptr_t{1}); +static void* const FINISHED = reinterpret_cast(std::uintptr_t{7}); + +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +struct Started { + std::atomic ran{false}; + std::atomic returned{false}; +}; + +static std::atomic release{false}; +static std::atomic stopResuming{false}; +static std::atomic raised{0}; +static std::atomic raisedOn{nullptr}; + +static void* APS5_VABI Record(void* opaque) { + static_cast(opaque)->ran.store(true); + return FINISHED; +} + +static void* APS5_VABI CancelAfterResume(void* opaque) { + auto* started = static_cast(opaque); + started->ran.store(true); + scePthreadTestcancel(); + started->returned.store(true); + return FINISHED; +} + +static void* APS5_VABI CancelInResumeAll(void* opaque) { + auto* started = static_cast(opaque); + started->ran.store(true); + Require(scePthreadCancel(scePthreadSelf()) == SCE_OK, "scePthreadCancel of the calling thread failed"); + pthread_resume_all_np_nid_postfix(); + started->returned.store(true); + return FINISHED; +} + +static void* APS5_VABI ResumeAllUntilStopped(void*) { + while (!stopResuming.load()) pthread_resume_all_np_nid_postfix(); + return FINISHED; +} + +static void* APS5_VABI WaitForRelease(void*) { + while (!release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return FINISHED; +} + +static void APS5_VABI Handler(int, void*) { + raisedOn.store(scePthreadSelf()); + raised.fetch_add(1); +} + +static void CreateSuspendedPosix(Pthread* thread, PthreadEntry entry, void* arg) { + PthreadAttr attr = nullptr; + Require(pthread_attr_init_nid_postfix(&attr) == 0, "pthread_attr_init failed"); + Require(pthread_attr_setcreatesuspend_np_nid_postfix(&attr) == 0, "pthread_attr_setcreatesuspend_np failed"); + Require(pthread_create_nid_postfix(thread, &attr, entry, arg) == 0, "pthread_create of a suspended thread failed"); + Require(pthread_attr_destroy_nid_postfix(&attr) == 0, "pthread_attr_destroy failed"); +} + +static void CreateSuspendedSce(Pthread* thread, PthreadEntry entry, void* arg) { + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == SCE_OK, "scePthreadAttrInit failed"); + Require(scePthreadAttrSetcreatesuspend(&attr) == SCE_OK, "scePthreadAttrSetcreatesuspend failed"); + Require(scePthreadCreate(thread, &attr, entry, arg, "suspended") == SCE_OK, "scePthreadCreate of a suspended thread failed"); + Require(scePthreadAttrDestroy(&attr) == SCE_OK, "scePthreadAttrDestroy failed"); +} + +static void ReportsErrors() { + Require(pthread_attr_setcreatesuspend_np_nid_postfix(nullptr) == GUEST_EINVAL, "pthread_attr_setcreatesuspend_np accepted a null attribute"); + Require(scePthreadAttrSetcreatesuspend(nullptr) == SCE_KERNEL_ERROR_EINVAL, "scePthreadAttrSetcreatesuspend accepted a null attribute"); + PthreadAttr destroyed = nullptr; + Require(scePthreadAttrInit(&destroyed) == SCE_OK, "scePthreadAttrInit failed"); + Require(scePthreadAttrDestroy(&destroyed) == SCE_OK, "scePthreadAttrDestroy failed"); + Require(pthread_attr_setcreatesuspend_np_nid_postfix(&destroyed) == GUEST_EINVAL, "pthread_attr_setcreatesuspend_np accepted a destroyed attribute"); + Require(scePthreadAttrSetcreatesuspend(&destroyed) == SCE_KERNEL_ERROR_EINVAL, "scePthreadAttrSetcreatesuspend accepted a destroyed attribute"); + Require(pthread_resume_np_nid_postfix(nullptr) == GUEST_EINVAL, "pthread_resume_np accepted a null thread"); + Require(scePthreadResume(nullptr) == SCE_KERNEL_ERROR_EINVAL, "scePthreadResume accepted a null thread"); + Pthread joined = nullptr; + Started started; + Require(scePthreadCreate(&joined, nullptr, Record, &started, "joined") == SCE_OK, "scePthreadCreate failed"); + Require(scePthreadJoin(joined, nullptr) == SCE_OK, "scePthreadJoin failed"); + Require(pthread_resume_np_nid_postfix(joined) == GUEST_ESRCH, "pthread_resume_np found a joined thread"); + Require(scePthreadResume(joined) == SCE_KERNEL_ERROR_ESRCH, "scePthreadResume found a joined thread"); +} + +static void ResumesOneThread(bool sce) { + Started started; + Pthread thread = nullptr; + if (sce) CreateSuspendedSce(&thread, Record, &started); + else CreateSuspendedPosix(&thread, Record, &started); + std::this_thread::sleep_for(SUSPENDED_WINDOW); + Require(!started.ran.load(), "a thread created suspended ran before it was resumed"); + Require((sce ? scePthreadResume(thread) : pthread_resume_np_nid_postfix(thread)) == 0, "resuming a suspended thread failed"); + void* result = nullptr; + Require((sce ? scePthreadJoin(thread, &result) : pthread_join_nid_postfix(thread, &result)) == 0, "joining a resumed thread failed"); + Require(result == FINISHED && started.ran.load(), "a resumed thread did not run its start routine"); +} + +static void ResumesRunningAndCallingThreads() { + Require(pthread_resume_np_nid_postfix(scePthreadSelf()) == 0, "pthread_resume_np of the calling thread failed"); + release.store(false); + Pthread running = nullptr; + Require(scePthreadCreate(&running, nullptr, WaitForRelease, nullptr, "running") == SCE_OK, "scePthreadCreate failed"); + Require(pthread_resume_np_nid_postfix(running) == 0, "pthread_resume_np of a running thread failed"); + release.store(true); + void* result = nullptr; + Require(scePthreadJoin(running, &result) == SCE_OK && result == FINISHED, "joining a running thread failed"); +} + +static void ResumesAllThreads(void (*resumeAll)()) { + Started first; + Started second; + Pthread firstThread = nullptr; + Pthread secondThread = nullptr; + CreateSuspendedPosix(&firstThread, Record, &first); + CreateSuspendedSce(&secondThread, Record, &second); + std::this_thread::sleep_for(SUSPENDED_WINDOW); + Require(!first.ran.load() && !second.ran.load(), "a thread created suspended ran before it was resumed"); + resumeAll(); + void* result = nullptr; + Require(scePthreadJoin(firstThread, &result) == SCE_OK && result == FINISHED && first.ran.load(), "resuming all threads left the first one suspended"); + Require(scePthreadJoin(secondThread, &result) == SCE_OK && result == FINISHED && second.ran.load(), "resuming all threads left the second one suspended"); +} + +static void CancelsBeforeResume() { + Started started; + Pthread thread = nullptr; + CreateSuspendedSce(&thread, CancelAfterResume, &started); + Require(scePthreadCancel(thread) == SCE_OK, "scePthreadCancel of a suspended thread failed"); + Require(pthread_resume_np_nid_postfix(thread) == 0, "pthread_resume_np of a canceled thread failed"); + void* result = nullptr; + Require(scePthreadJoin(thread, &result) == SCE_OK, "joining a canceled thread failed"); + Require(result == CANCELED && started.ran.load() && !started.returned.load(), "a cancel requested while suspended did not take effect at the first cancellation point"); +} + +static void CancelsInResumeAll() { + Started started; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, CancelInResumeAll, &started, "resume_all") == SCE_OK, "scePthreadCreate failed"); + void* result = nullptr; + Require(scePthreadJoin(thread, &result) == SCE_OK, "joining a canceled thread failed"); + Require(result == CANCELED && started.ran.load() && !started.returned.load(), "pthread_resume_all_np did not act on a pending cancel"); +} + +static void ReportsCreateSuspendedInAttrGet(int (APS5_VABI* get)(Pthread, PthreadAttr*)) { + Started first; + Started second; + Pthread suspended = nullptr; + CreateSuspendedPosix(&suspended, Record, &first); + PthreadAttr attr = nullptr; + Require(pthread_attr_init_nid_postfix(&attr) == 0, "pthread_attr_init failed"); + Require(get(suspended, &attr) == 0, "getting the attributes of a suspended thread failed"); + Pthread copy = nullptr; + Require(pthread_create_nid_postfix(©, &attr, Record, &second) == 0, "pthread_create with the attributes of a suspended thread failed"); + std::this_thread::sleep_for(SUSPENDED_WINDOW); + Require(!second.ran.load(), "a thread created with the attributes of a suspended thread ran before it was resumed"); + Require(pthread_resume_np_nid_postfix(copy) == 0 && pthread_resume_np_nid_postfix(suspended) == 0, "resuming a suspended thread failed"); + void* result = nullptr; + Require(pthread_join_nid_postfix(copy, &result) == 0 && result == FINISHED && second.ran.load(), "joining a resumed thread failed"); + Require(pthread_join_nid_postfix(suspended, &result) == 0 && result == FINISHED && first.ran.load(), "joining a resumed thread failed"); + release.store(false); + Pthread running = nullptr; + Require(scePthreadCreate(&running, nullptr, WaitForRelease, nullptr, "running") == SCE_OK, "scePthreadCreate failed"); + Require(pthread_attr_setcreatesuspend_np_nid_postfix(&attr) == 0, "pthread_attr_setcreatesuspend_np failed"); + Require(get(running, &attr) == 0, "getting the attributes of a running thread failed"); + Started third; + Pthread plain = nullptr; + Require(pthread_create_nid_postfix(&plain, &attr, Record, &third) == 0, "pthread_create with the attributes of a running thread failed"); + Require(pthread_join_nid_postfix(plain, &result) == 0 && result == FINISHED && third.ran.load(), "a thread created with the attributes of a running thread did not run"); + release.store(true); + Require(scePthreadJoin(running, nullptr) == SCE_OK, "joining a running thread failed"); + Require(pthread_attr_destroy_nid_postfix(&attr) == 0, "pthread_attr_destroy failed"); +} + +static void ResumesDetachedThreadsWhileTheyExit() { + stopResuming.store(false); + Pthread resumer = nullptr; + Require(scePthreadCreate(&resumer, nullptr, ResumeAllUntilStopped, nullptr, "resumer") == SCE_OK, "scePthreadCreate failed"); + for (int round = 0; round < DETACHED_ROUNDS; ++round) { + Started started; + PthreadAttr attr = nullptr; + Require(pthread_attr_init_nid_postfix(&attr) == 0, "pthread_attr_init failed"); + Require(pthread_attr_setcreatesuspend_np_nid_postfix(&attr) == 0, "pthread_attr_setcreatesuspend_np failed"); + Require(pthread_attr_setdetachstate_nid_postfix(&attr, GUEST_CREATE_DETACHED) == 0, "pthread_attr_setdetachstate failed"); + Pthread thread = nullptr; + Require(pthread_create_nid_postfix(&thread, &attr, Record, &started) == 0, "pthread_create of a detached suspended thread failed"); + Require(pthread_attr_destroy_nid_postfix(&attr) == 0, "pthread_attr_destroy failed"); + int result = 0; + while ((result = pthread_resume_np_nid_postfix(thread)) == 0) std::this_thread::yield(); + Require(result == GUEST_ESRCH && started.ran.load(), "a detached thread resumed while it exited did not run its start routine"); + } + stopResuming.store(true); + Require(scePthreadJoin(resumer, nullptr) == SCE_OK, "joining the resuming thread failed"); +} + +#ifdef _WIN32 +static void RefusesRaiseWhileSuspended() { + Require(sceKernelInstallExceptionHandler(GUEST_SIGUSR1, reinterpret_cast(&Handler)) == SCE_OK, "sceKernelInstallExceptionHandler failed"); + Started started; + Pthread thread = nullptr; + CreateSuspendedSce(&thread, Record, &started); + bool refused = false; + try { + sceKernelRaiseException(thread, GUEST_SIGUSR1); + } catch (const std::runtime_error& error) { + refused = std::string(error.what()).find("suspended") != std::string::npos; + } + Require(refused, "sceKernelRaiseException did not refuse a thread still suspended at creation"); + Require(pthread_resume_np_nid_postfix(thread) == 0, "pthread_resume_np failed"); + Require(scePthreadJoin(thread, nullptr) == SCE_OK && started.ran.load(), "joining a resumed thread failed"); + Require(raised.load() == 0, "the handler ran for a refused raise"); + Require(sceKernelRemoveExceptionHandler(GUEST_SIGUSR1) == SCE_OK, "sceKernelRemoveExceptionHandler failed"); +} +#else +static void DefersRaiseUntilResume() { + Require(sceKernelInstallExceptionHandler(GUEST_SIGUSR1, reinterpret_cast(&Handler)) == SCE_OK, "sceKernelInstallExceptionHandler failed"); + Started started; + Pthread thread = nullptr; + CreateSuspendedSce(&thread, Record, &started); + Require(sceKernelRaiseException(thread, GUEST_SIGUSR1) == SCE_OK, "sceKernelRaiseException on a suspended thread failed"); + std::this_thread::sleep_for(SUSPENDED_WINDOW); + Require(raised.load() == 0, "the handler ran on a thread still suspended at creation"); + Require(pthread_resume_np_nid_postfix(thread) == 0, "pthread_resume_np failed"); + Require(scePthreadJoin(thread, nullptr) == SCE_OK && started.ran.load(), "joining a resumed thread failed"); + Require(raised.load() == 1 && raisedOn.load() == thread, "the handler did not run on the thread once it was resumed"); + Require(sceKernelRemoveExceptionHandler(GUEST_SIGUSR1) == SCE_OK, "sceKernelRemoveExceptionHandler failed"); +} +#endif + +int main() { + ReportsErrors(); + ResumesOneThread(false); + ResumesOneThread(true); + ResumesRunningAndCallingThreads(); + ResumesAllThreads([] { Require(pthread_multi_np_nid_postfix() == 0, "pthread_multi_np failed"); }); + ResumesAllThreads([] { pthread_resume_all_np_nid_postfix(); }); + ResumesAllThreads([] { Require(scePthreadMulti() == SCE_OK, "scePthreadMulti failed"); }); + ResumesAllThreads([] { scePthreadResumeAll(); }); + CancelsBeforeResume(); + CancelsInResumeAll(); + ReportsCreateSuspendedInAttrGet(pthread_attr_get_np_nid_postfix); + ReportsCreateSuspendedInAttrGet(scePthreadAttrGet); + ResumesDetachedThreadsWhileTheyExit(); +#ifdef _WIN32 + RefusesRaiseWhileSuspended(); +#else + DefersRaiseUntilResume(); +#endif +} diff --git a/core/libs/tests/GuestPthreadDestroyedObjects.cpp b/core/libs/tests/GuestPthreadDestroyedObjects.cpp index a9a041cfc0..2687a84b66 100644 --- a/core/libs/tests/GuestPthreadDestroyedObjects.cpp +++ b/core/libs/tests/GuestPthreadDestroyedObjects.cpp @@ -1,25 +1,12 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" #include "SceTypes.hpp" #include #include -extern "C" { -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); -int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); -int APS5_VABI scePthreadCondSignal(PthreadCond* cond); -int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond); -int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex); -int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_cond_signal_nid_postfix(PthreadCond* cond); -int APS5_VABI pthread_cond_destroy_nid_postfix(PthreadCond* cond); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); static constexpr int POSIX_EINVAL = 22; static void Require(bool value) { if (!value) std::abort(); } @@ -34,13 +21,37 @@ int main() { Require(pthread_mutex_lock_nid_postfix(&mutex) == POSIX_EINVAL); Require(mutex == reinterpret_cast(std::uintptr_t{2})); + PthreadMutex uninitialized = nullptr; + Require(scePthreadMutexUnlock(&uninitialized) == SCE_KERNEL_ERROR_EPERM); + Require(uninitialized == nullptr); + Require(scePthreadMutexLock(&uninitialized) == SCE_OK); + Require(scePthreadMutexUnlock(&uninitialized) == SCE_OK); + Require(scePthreadMutexUnlock(&uninitialized) == SCE_KERNEL_ERROR_EPERM); + Require(scePthreadMutexDestroy(&uninitialized) == SCE_OK); + + PthreadMutex adaptive = reinterpret_cast(std::uintptr_t{1}); + Require(scePthreadMutexUnlock(&adaptive) == SCE_KERNEL_ERROR_EPERM); + Require(adaptive == reinterpret_cast(std::uintptr_t{1})); + Require(scePthreadMutexLock(&adaptive) == SCE_OK); + Require(scePthreadMutexUnlock(&adaptive) == SCE_OK); + Require(scePthreadMutexDestroy(&adaptive) == SCE_OK); + PthreadCond cond = nullptr; Require(scePthreadCondInit(&cond, nullptr, "destroyed") == SCE_OK); Require(scePthreadCondDestroy(&cond) == SCE_OK); + Require(reinterpret_cast(cond) == 1); Require(scePthreadCondSignal(&cond) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadCondBroadcast(&cond) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadCondWait(&cond, &mutex) == SCE_KERNEL_ERROR_EINVAL); Require(pthread_cond_signal_nid_postfix(&cond) == POSIX_EINVAL); Require(scePthreadCondDestroy(&cond) == SCE_KERNEL_ERROR_EINVAL); Require(pthread_cond_destroy_nid_postfix(&cond) == POSIX_EINVAL); + + PthreadCond untouched = nullptr; + Require(scePthreadCondDestroy(&untouched) == SCE_OK && untouched == nullptr); + Require(scePthreadCondSignal(&untouched) == SCE_OK && untouched != nullptr); + Require(scePthreadCondDestroy(&untouched) == SCE_OK && reinterpret_cast(untouched) == 1); + untouched = nullptr; + Require(scePthreadCondBroadcast(&untouched) == SCE_OK && untouched != nullptr); + Require(scePthreadCondDestroy(&untouched) == SCE_OK); } diff --git a/core/libs/tests/GuestPthreadDestructorLifetime.cpp b/core/libs/tests/GuestPthreadDestructorLifetime.cpp index 4bddcb951c..2001d54acb 100644 --- a/core/libs/tests/GuestPthreadDestructorLifetime.cpp +++ b/core/libs/tests/GuestPthreadDestructorLifetime.cpp @@ -1,23 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadCancel(Pthread thread); -void APS5_VABI scePthreadExit(void* retval); -Pthread APS5_VABI scePthreadSelf(); -int APS5_VABI scePthreadGetthreadid(); -int APS5_VABI scePthreadGetname(Pthread thread, char* name); -int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); -int APS5_VABI scePthreadKeyDelete(PthreadKey key); -void* APS5_VABI scePthreadGetspecific(PthreadKey key); -int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); -int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); -} - struct Context { PthreadKey key = -1; Pthread expected = nullptr; diff --git a/core/libs/tests/GuestPthreadFrameChain.cpp b/core/libs/tests/GuestPthreadFrameChain.cpp index d9c491090f..b0d3fc5822 100644 --- a/core/libs/tests/GuestPthreadFrameChain.cpp +++ b/core/libs/tests/GuestPthreadFrameChain.cpp @@ -1,13 +1,9 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -} - static constexpr int SCE_OK = 0; static constexpr std::uintptr_t ChainReach = std::uintptr_t{64} << 10; diff --git a/core/libs/tests/GuestPthreadGetname.cpp b/core/libs/tests/GuestPthreadGetname.cpp index f8545f6078..fe3554d545 100644 --- a/core/libs/tests/GuestPthreadGetname.cpp +++ b/core/libs/tests/GuestPthreadGetname.cpp @@ -1,18 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -Pthread APS5_VABI pthread_self_nid_postfix(void); -int APS5_VABI pthread_rename_np_nid_postfix(Pthread thread, const char* name); -int APS5_VABI pthread_getname_np_nid_postfix(Pthread thread, char* name); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadGetname(Pthread thread, char* name); -} - static constexpr int GUEST_ESRCH = 3; static constexpr int GUEST_EFAULT = 14; diff --git a/core/libs/tests/GuestPthreadJoinDetach.cpp b/core/libs/tests/GuestPthreadJoinDetach.cpp index 48a982989e..20c8014765 100644 --- a/core/libs/tests/GuestPthreadJoinDetach.cpp +++ b/core/libs/tests/GuestPthreadJoinDetach.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libkernel/KernelErrors.hpp" #include @@ -7,10 +8,6 @@ #include #include -extern "C" int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -extern "C" int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -extern "C" int APS5_VABI scePthreadDetach(Pthread thread); - static constexpr int SCE_OK = 0; static void Require(bool condition, const char* message) { diff --git a/core/libs/tests/GuestPthreadJoinSelf.cpp b/core/libs/tests/GuestPthreadJoinSelf.cpp index 2fec26bef7..6277b58467 100644 --- a/core/libs/tests/GuestPthreadJoinSelf.cpp +++ b/core/libs/tests/GuestPthreadJoinSelf.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -5,16 +7,7 @@ #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -Pthread APS5_VABI scePthreadSelf(void); -int APS5_VABI pthread_join_nid_postfix(Pthread thread, void** retval); -int APS5_VABI scePthreadDetach(Pthread thread); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EDEADLK = static_cast(0x8002000Bu); static constexpr int GUEST_EDEADLK = 11; static void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestPthreadKey.cpp b/core/libs/tests/GuestPthreadKey.cpp index ae87651a61..bbe608bc54 100644 --- a/core/libs/tests/GuestPthreadKey.cpp +++ b/core/libs/tests/GuestPthreadKey.cpp @@ -1,21 +1,11 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); -int APS5_VABI scePthreadKeyDelete(PthreadKey key); -void* APS5_VABI scePthreadGetspecific(PthreadKey key); -int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); -int APS5_VABI pthread_key_create_nid_postfix(PthreadKey* key, pthread_key_destructor_func_t destructor); -int APS5_VABI pthread_key_delete_nid_postfix(PthreadKey key); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestPthreadMutexErrors.cpp b/core/libs/tests/GuestPthreadMutexErrors.cpp index da1083b121..7d7947f824 100644 --- a/core/libs/tests/GuestPthreadMutexErrors.cpp +++ b/core/libs/tests/GuestPthreadMutexErrors.cpp @@ -1,38 +1,13 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type); -int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexGetspinloops(PthreadMutex* mutex, int* count); -int APS5_VABI scePthreadMutexSetspinloops(PthreadMutex* mutex, int count); -int APS5_VABI scePthreadMutexGetyieldloops(PthreadMutex* mutex, int* count); -int APS5_VABI scePthreadMutexSetyieldloops(PthreadMutex* mutex, int count); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_reltimedlock_np_nid_postfix(PthreadMutex* mutex, KernelUseconds usec); -int APS5_VABI pthread_mutex_isowned_np_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_getspinloops_np_nid_postfix(PthreadMutex* mutex, int* count); -int APS5_VABI pthread_mutex_setspinloops_np_nid_postfix(PthreadMutex* mutex, int count); -int APS5_VABI pthread_mutex_getyieldloops_np_nid_postfix(PthreadMutex* mutex, int* count); -int APS5_VABI pthread_mutex_setyieldloops_np_nid_postfix(PthreadMutex* mutex, int count); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); -static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast(0x80020010u); static constexpr int POSIX_EDEADLK = 11; static constexpr int POSIX_EINVAL = 22; @@ -72,7 +47,7 @@ static void CheckOwnershipAndRelativeLock() { Require(pthread_mutex_isowned_np_nid_postfix(&adaptive) == 0 && adaptive == AdaptiveInitializer); Require(pthread_mutex_reltimedlock_np_nid_postfix(&mutex, 1000) == 0 && mutex != nullptr); Require(pthread_mutex_isowned_np_nid_postfix(&mutex) == 1); - Require(pthread_mutex_reltimedlock_np_nid_postfix(&mutex, 1000) == POSIX_EDEADLK); + Require(pthread_mutex_reltimedlock_np_nid_postfix(&mutex, 1000) == POSIX_ETIMEDOUT); OtherThread other{&mutex}; Pthread thread = nullptr; Require(scePthreadCreate(&thread, nullptr, CheckFromOtherThread, &other, nullptr) == SCE_OK); diff --git a/core/libs/tests/GuestPthreadMutexattr.cpp b/core/libs/tests/GuestPthreadMutexattr.cpp index 48dd291a5b..58774211dd 100644 --- a/core/libs/tests/GuestPthreadMutexattr.cpp +++ b/core/libs/tests/GuestPthreadMutexattr.cpp @@ -1,35 +1,19 @@ +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI pthread_mutexattr_init_nid_postfix(PthreadMutexattr* attr); -int APS5_VABI pthread_mutexattr_destroy_nid_postfix(PthreadMutexattr* attr); -int APS5_VABI pthread_mutexattr_getpshared_nid_postfix(const PthreadMutexattr* attr, int* pshared); -int APS5_VABI pthread_mutexattr_setpshared_nid_postfix(PthreadMutexattr* attr, int pshared); -int APS5_VABI pthread_mutexattr_setprotocol_nid_postfix(PthreadMutexattr* attr, int protocol); -int APS5_VABI pthread_mutexattr_getprotocol_nid_postfix(const PthreadMutexattr* attr, int* protocol); -int APS5_VABI pthread_mutexattr_setprioceiling_nid_postfix(PthreadMutexattr* attr, int prioceiling); -int APS5_VABI pthread_mutexattr_getprioceiling_nid_postfix(const PthreadMutexattr* attr, int* prioceiling); -int APS5_VABI pthread_mutexattr_settype_nid_postfix(PthreadMutexattr* attr, int type); -int APS5_VABI pthread_mutexattr_gettype_nid_postfix(const PthreadMutexattr* attr, int* type); -int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex* mutex, const PthreadMutexattr* attr); -int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const KernelTimespec* abstime); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI sched_get_priority_max_nid_postfix(int policy); -} - static constexpr int GUEST_EINVAL = 22; +static constexpr int SCE_OK = 0; +static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); static constexpr int PRIO_NONE = 0; static constexpr int PRIO_INHERIT = 1; static constexpr int PRIO_PROTECT = 2; -static constexpr int SCHED_RR = 3; +static constexpr int GuestSched_Rr = 3; static void Require(bool value) { if (!value) std::abort(); } @@ -80,6 +64,78 @@ static void CheckType() { Require(pthread_mutexattr_gettype_nid_postfix(nullptr, &type) == GUEST_EINVAL && type == -1); } +static void CheckKind() { + PthreadMutexattr attr = nullptr; + *__error_nid_postfix() = 0; + Require(pthread_mutexattr_getkind_np_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + *__error_nid_postfix() = 0; + Require(pthread_mutexattr_setkind_np_nid_postfix(nullptr, 2) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + *__error_nid_postfix() = 0; + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, 2) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + Require(pthread_mutexattr_init_nid_postfix(&attr) == 0); + Require(pthread_mutexattr_getkind_np_nid_postfix(attr) == 1); + int type = -1; + for (const int kind : {2, 3, 4, 1}) { + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, kind) == 0); + Require(pthread_mutexattr_getkind_np_nid_postfix(attr) == kind); + Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == 0 && type == kind); + } + 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_mutex_init_nid_postfix(&mutex, &attr) == 0); + Require(pthread_mutex_lock_nid_postfix(&mutex) == 0); + Require(pthread_mutex_lock_nid_postfix(&mutex) == 0); + Require(pthread_mutex_unlock_nid_postfix(&mutex) == 0); + Require(pthread_mutex_unlock_nid_postfix(&mutex) == 0); + Require(pthread_mutex_destroy_nid_postfix(&mutex) == 0); + Require(pthread_mutexattr_destroy_nid_postfix(&attr) == 0); + *__error_nid_postfix() = 0; + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, 2) == -1 && *__error_nid_postfix() == GUEST_EINVAL); +} + +static void CheckSceAccessors() { + PthreadMutexattr attr = nullptr; + int value = -1; + Require(scePthreadMutexattrGettype(nullptr, &value) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrGettype(&attr, &value) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetprotocol(&attr, PRIO_NONE) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrGetprotocol(&attr, &value) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetprioceiling(&attr, 300) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrGetprioceiling(&attr, &value) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetpshared(nullptr, 0) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrGetpshared(&attr, &value) == SCE_KERNEL_ERROR_EINVAL); + Require(value == -1); + Require(scePthreadMutexattrInit(&attr) == SCE_OK); + Require(scePthreadMutexattrGettype(&attr, &value) == SCE_OK && value == 1); + Require(scePthreadMutexattrSettype(&attr, 3) == SCE_OK); + Require(scePthreadMutexattrGettype(&attr, &value) == SCE_OK && value == 3); + Require(Throws([&] { scePthreadMutexattrGettype(&attr, nullptr); })); + Require(scePthreadMutexattrGetprotocol(&attr, &value) == SCE_OK && value == PRIO_NONE); + for (const int invalid : {-1, 3}) + Require(scePthreadMutexattrSetprotocol(&attr, invalid) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrGetprotocol(&attr, &value) == SCE_OK && value == PRIO_NONE); + Require(scePthreadMutexattrSetprotocol(&attr, PRIO_INHERIT) == SCE_OK); + Require(scePthreadMutexattrGetprotocol(&attr, &value) == SCE_OK && value == PRIO_INHERIT); + Require(scePthreadMutexattrSetprioceiling(&attr, 300) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrGetprioceiling(&attr, &value) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetprotocol(&attr, PRIO_PROTECT) == SCE_OK); + Require(scePthreadMutexattrGetprioceiling(&attr, &value) == SCE_OK && value == sched_get_priority_max_nid_postfix(GuestSched_Rr)); + Require(scePthreadMutexattrSetprioceiling(&attr, 300) == SCE_OK); + Require(scePthreadMutexattrGetprioceiling(&attr, &value) == SCE_OK && value == 300); + int protocol = -1; + Require(pthread_mutexattr_getprotocol_nid_postfix(&attr, &protocol) == 0 && protocol == PRIO_PROTECT); + Require(scePthreadMutexattrGetpshared(&attr, &value) == SCE_OK && value == 0); + Require(scePthreadMutexattrSetpshared(&attr, 1) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetpshared(&attr, 0) == SCE_OK); + Require(scePthreadMutexattrGetpshared(&attr, &value) == SCE_OK && value == 0); + Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); + Require(scePthreadMutexattrGettype(&attr, &value) == SCE_KERNEL_ERROR_EINVAL); +} + static void CheckProcessSharing() { PthreadMutexattr attr = nullptr; int sharing = -1; @@ -127,6 +183,8 @@ static void CheckInvalidPriorityAttribute(PthreadMutexattr* attr) { int main() { CheckType(); + CheckKind(); + CheckSceAccessors(); CheckProcessSharing(); CheckTimedlockInvalidDeadline(); PthreadMutexattr attr = nullptr; @@ -143,7 +201,7 @@ int main() { Require(pthread_mutexattr_setprioceiling_nid_postfix(&attr, 300) == GUEST_EINVAL); Require(pthread_mutexattr_setprotocol_nid_postfix(&attr, PRIO_PROTECT) == 0); Require(pthread_mutexattr_getprotocol_nid_postfix(&attr, &protocol) == 0 && protocol == PRIO_PROTECT); - Require(pthread_mutexattr_getprioceiling_nid_postfix(&attr, &ceiling) == 0 && ceiling == sched_get_priority_max_nid_postfix(SCHED_RR)); + Require(pthread_mutexattr_getprioceiling_nid_postfix(&attr, &ceiling) == 0 && ceiling == sched_get_priority_max_nid_postfix(GuestSched_Rr)); Require(pthread_mutexattr_setprioceiling_nid_postfix(&attr, 300) == 0); Require(pthread_mutexattr_getprioceiling_nid_postfix(&attr, &ceiling) == 0 && ceiling == 300); PthreadMutex mutex = nullptr; @@ -153,7 +211,7 @@ int main() { Require(pthread_mutex_destroy_nid_postfix(&mutex) == 0); Require(pthread_mutexattr_setprotocol_nid_postfix(&attr, PRIO_PROTECT) == 0); Require(pthread_mutexattr_getprotocol_nid_postfix(&attr, &protocol) == 0 && protocol == PRIO_PROTECT); - Require(pthread_mutexattr_getprioceiling_nid_postfix(&attr, &ceiling) == 0 && ceiling == sched_get_priority_max_nid_postfix(SCHED_RR)); + Require(pthread_mutexattr_getprioceiling_nid_postfix(&attr, &ceiling) == 0 && ceiling == sched_get_priority_max_nid_postfix(GuestSched_Rr)); Require(pthread_mutexattr_setprotocol_nid_postfix(&attr, PRIO_NONE) == 0); Require(pthread_mutexattr_getprotocol_nid_postfix(&attr, &protocol) == 0 && protocol == PRIO_NONE); Require(pthread_mutexattr_getprioceiling_nid_postfix(&attr, &ceiling) == GUEST_EINVAL); diff --git a/core/libs/tests/GuestPthreadRename.cpp b/core/libs/tests/GuestPthreadRename.cpp index e3fdbd18b1..ccfa5ced39 100644 --- a/core/libs/tests/GuestPthreadRename.cpp +++ b/core/libs/tests/GuestPthreadRename.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -5,15 +7,6 @@ #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -Pthread APS5_VABI scePthreadSelf(); -int APS5_VABI scePthreadGetname(Pthread thread, char* name); -int APS5_VABI pthread_rename_np_nid_postfix(Pthread thread, const char* name); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } static void CheckStoredName(Pthread thread, const std::string& expected) { diff --git a/core/libs/tests/GuestPthreadRwlock.cpp b/core/libs/tests/GuestPthreadRwlock.cpp index b7f54df37e..1f6783b2d5 100644 --- a/core/libs/tests/GuestPthreadRwlock.cpp +++ b/core/libs/tests/GuestPthreadRwlock.cpp @@ -1,3 +1,7 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/Pthread/include/Rwlock.hpp" #include "SceTypes.hpp" #include #include @@ -6,35 +10,9 @@ #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); -int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock* rwlock); -int APS5_VABI pthread_rwlock_init_nid_postfix(PthreadRwlock* rwlock, const PthreadRwlockattr* attr); -int APS5_VABI pthread_rwlockattr_init_nid_postfix(PthreadRwlockattr* attr); -int APS5_VABI pthread_rwlockattr_destroy_nid_postfix(PthreadRwlockattr* attr); -int APS5_VABI pthread_rwlockattr_getpshared_nid_postfix(const PthreadRwlockattr* attr, int* pshared); -int APS5_VABI pthread_rwlockattr_setpshared_nid_postfix(PthreadRwlockattr* attr, int pshared); -int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock* rwlock); -int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock); -int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock); -int APS5_VABI pthread_rwlock_tryrdlock_nid_postfix(PthreadRwlock* rwlock); -int APS5_VABI pthread_rwlock_trywrlock_nid_postfix(PthreadRwlock* rwlock); -int APS5_VABI pthread_rwlock_timedrdlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime); -int APS5_VABI pthread_rwlock_timedwrlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime); -int APS5_VABI scePthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr, const char* name); -int APS5_VABI scePthreadRwlockDestroy(PthreadRwlock* rwlock); -int APS5_VABI scePthreadRwlockRdlock(PthreadRwlock* rwlock); -int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock); -int APS5_VABI scePthreadRwlockTryrdlock(PthreadRwlock* rwlock); -int APS5_VABI scePthreadRwlockUnlock(PthreadRwlock* rwlock); -} - using TimedLock = int (APS5_VABI *)(PthreadRwlock*, const KernelTimespec*); static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; static constexpr int GUEST_EPERM = 1; static constexpr int GUEST_EDEADLK = 11; static constexpr int GUEST_EBUSY = 16; @@ -204,7 +182,7 @@ static void CheckFailedLockAttempts() { PthreadRwlock rwlock = nullptr; Require(pthread_rwlock_wrlock_nid_postfix(&rwlock) == 0); const KernelTimespec timeout = After(10); - Require(pthread_rwlock_timedrdlock_nid_postfix(&rwlock, &timeout) == GUEST_EDEADLK); + Require(pthread_rwlock_timedrdlock_nid_postfix(&rwlock, &timeout) == GUEST_ETIMEDOUT); Require(pthread_rwlock_tryrdlock_nid_postfix(&rwlock) == GUEST_EBUSY); Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); Require(pthread_rwlock_destroy_nid_postfix(&rwlock) == 0); @@ -312,6 +290,52 @@ static void CheckWaitingWriterHoldsBackNewReaders() { Require(pthread_rwlock_destroy_nid_postfix(&recursive) == 0); } +using RelativeLock = int (APS5_VABI *)(PthreadRwlock*, KernelUseconds); + +static void ExpectRelativeTimeout(RelativeLock lock, PthreadRwlock* rwlock) { + const auto start = std::chrono::steady_clock::now(); + Require(lock(rwlock, 20000) == GUEST_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); + Require(lock(rwlock, 0) == GUEST_ETIMEDOUT); +} + +static void CheckRelativeTimeouts() { + PthreadRwlock fresh[2] = {}; + Require(pthread_rwlock_reltimedrdlock_np_nid_postfix(&fresh[0], 0) == 0); + Require(pthread_rwlock_reltimedwrlock_np_nid_postfix(&fresh[1], 0) == 0); + for (auto& lock : fresh) { + Require(lock != nullptr && lock != DestroyedRwlock); + Require(pthread_rwlock_unlock_nid_postfix(&lock) == 0); + Require(pthread_rwlock_destroy_nid_postfix(&lock) == 0); + Require(pthread_rwlock_reltimedrdlock_np_nid_postfix(&lock, 0) == GUEST_EINVAL); + Require(pthread_rwlock_reltimedwrlock_np_nid_postfix(&lock, 0) == GUEST_EINVAL); + } + + PthreadRwlock rwlock = nullptr; + Require(pthread_rwlock_reltimedwrlock_np_nid_postfix(&rwlock, 5000000) == 0); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + + Holder reader{&rwlock, false}; + Start(reader); + Require(pthread_rwlock_reltimedrdlock_np_nid_postfix(&rwlock, 0) == 0); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + ExpectRelativeTimeout(pthread_rwlock_reltimedwrlock_np_nid_postfix, &rwlock); + reader.release.store(true); + Require(pthread_rwlock_reltimedwrlock_np_nid_postfix(&rwlock, 5000000) == 0); + Require(scePthreadJoin(reader.thread, nullptr) == SCE_OK); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + + Holder writer{&rwlock, true}; + Start(writer); + ExpectRelativeTimeout(pthread_rwlock_reltimedrdlock_np_nid_postfix, &rwlock); + ExpectRelativeTimeout(pthread_rwlock_reltimedwrlock_np_nid_postfix, &rwlock); + writer.release.store(true); + Require(pthread_rwlock_reltimedrdlock_np_nid_postfix(&rwlock, 5000000) == 0); + Require(scePthreadJoin(writer.thread, nullptr) == SCE_OK); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + Require(pthread_rwlock_destroy_nid_postfix(&rwlock) == 0); +} + template static bool Throws(Call call) { try { @@ -349,11 +373,53 @@ static void CheckAttrProcessSharing() { Require(pthread_rwlockattr_destroy_nid_postfix(&attr) == 0); } +static void CheckAttrSettype() { + PthreadRwlockattr attr = nullptr; + Require(pthread_rwlockattr_init_nid_postfix(&attr) == 0); + for (const int type : {1, 2}) { + Require(scePthreadRwlockattrSettype(&attr, type) == SCE_OK); + PthreadRwlock rwlock = nullptr; + Require(pthread_rwlock_init_nid_postfix(&rwlock, &attr) == 0); + Require(pthread_rwlock_wrlock_nid_postfix(&rwlock) == 0 && pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + Require(pthread_rwlock_destroy_nid_postfix(&rwlock) == 0); + } + for (const int type : {0, 3, -1, static_cast(0x80000000u)}) Require(scePthreadRwlockattrSettype(&attr, type) == SCE_KERNEL_ERROR_EINVAL); + Require(pthread_rwlockattr_destroy_nid_postfix(&attr) == 0); + Require(scePthreadRwlockattrSettype(&attr, 1) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadRwlockattrSettype(nullptr, 1) == SCE_KERNEL_ERROR_EINVAL); +} + +static void CheckAttrType() { + PthreadRwlockattr attr = nullptr; + Require(pthread_rwlockattr_init_nid_postfix(&attr) == 0); + int type = -1; + Require(pthread_rwlockattr_gettype_np_nid_postfix(&attr, &type) == 0 && type == 0); + Require(scePthreadRwlockattrGettype(&attr, &type) == SCE_OK && type == 0); + for (const int set : {1, 2}) { + Require(scePthreadRwlockattrSettype(&attr, set) == SCE_OK); + Require(pthread_rwlockattr_gettype_np_nid_postfix(&attr, &type) == 0 && type == set); + Require(scePthreadRwlockattrGettype(&attr, &type) == SCE_OK && type == set); + } + Require(Throws([&] { pthread_rwlockattr_gettype_np_nid_postfix(&attr, nullptr); })); + int pshared = -1; + Require(scePthreadRwlockattrGetpshared(&attr, &pshared) == SCE_OK && pshared == PROCESS_PRIVATE); + Require(scePthreadRwlockattrSetpshared(&attr, 1) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadRwlockattrSetpshared(&attr, PROCESS_PRIVATE) == SCE_OK); + Require(pthread_rwlockattr_destroy_nid_postfix(&attr) == 0); + PthreadRwlockattr unset = nullptr; + Require(Throws([&] { pthread_rwlockattr_gettype_np_nid_postfix(nullptr, &type); })); + Require(Throws([&] { pthread_rwlockattr_gettype_np_nid_postfix(&unset, &type); })); + Require(Throws([&] { scePthreadRwlockattrGettype(&unset, &type); })); +} + int main() { + CheckAttrSettype(); CheckAttrProcessSharing(); + CheckAttrType(); CheckWaitingWriterHoldsBackNewReaders(); CheckConcurrentFirstWrlockExcludes(); CheckDeadlinesBeyondTheClockRange(); + CheckRelativeTimeouts(); const KernelTimespec invalid{0, NANOS_PER_SECOND}; PthreadRwlock fresh[4] = {}; Require(pthread_rwlock_tryrdlock_nid_postfix(&fresh[0]) == 0); @@ -381,9 +447,10 @@ int main() { Require(pthread_rwlock_trywrlock_nid_postfix(&rwlock) == 0); Require(pthread_rwlock_tryrdlock_nid_postfix(&rwlock) == GUEST_EBUSY); Require(pthread_rwlock_trywrlock_nid_postfix(&rwlock) == GUEST_EBUSY); - KernelTimespec deadline = After(5000); - Require(pthread_rwlock_timedrdlock_nid_postfix(&rwlock, &deadline) == GUEST_EDEADLK); - Require(pthread_rwlock_timedwrlock_nid_postfix(&rwlock, &deadline) == GUEST_EDEADLK); + KernelTimespec deadline = After(20); + Require(pthread_rwlock_timedrdlock_nid_postfix(&rwlock, &deadline) == GUEST_ETIMEDOUT); + deadline = After(20); + Require(pthread_rwlock_timedwrlock_nid_postfix(&rwlock, &deadline) == GUEST_ETIMEDOUT); Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == GUEST_EPERM); Require(pthread_rwlock_tryrdlock_nid_postfix(&rwlock) == 0); diff --git a/core/libs/tests/GuestPthreadSchedparam.cpp b/core/libs/tests/GuestPthreadSchedparam.cpp index 171d3fc5be..71d9ab1a58 100644 --- a/core/libs/tests/GuestPthreadSchedparam.cpp +++ b/core/libs/tests/GuestPthreadSchedparam.cpp @@ -1,33 +1,13 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -Pthread APS5_VABI scePthreadSelf(void); -int APS5_VABI scePthreadGetschedparam(Pthread thread, int* policy, KernelSchedParam* param); -int APS5_VABI scePthreadSetschedparam(Pthread thread, int policy, const KernelSchedParam* param); -int APS5_VABI pthread_getschedparam_nid_postfix(Pthread thread, int* policy, KernelSchedParam* param); -int APS5_VABI pthread_getprio_nid_postfix(Pthread thread); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI pthread_setschedparam_nid_postfix(Pthread thread, int policy, const KernelSchedParam* param); -int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr); -int APS5_VABI pthread_attr_get_np_nid_postfix(Pthread thread, PthreadAttr* attr); -int APS5_VABI scePthreadAttrGetschedpolicy(const PthreadAttr* attr, int* policy); -int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param); -int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched); -int APS5_VABI scePthreadAttrSetschedpolicy(PthreadAttr* attr, int policy); -int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); -static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); static constexpr int GUEST_EINVAL = 22; static constexpr int GUEST_ESRCH = 3; static constexpr int GUEST_SCHED_FIFO = 1; diff --git a/core/libs/tests/GuestPthreadSecondJoin.cpp b/core/libs/tests/GuestPthreadSecondJoin.cpp index 4db4d57034..3bca2b8745 100644 --- a/core/libs/tests/GuestPthreadSecondJoin.cpp +++ b/core/libs/tests/GuestPthreadSecondJoin.cpp @@ -1,20 +1,14 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include +#include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadDetach(Pthread thread); -int APS5_VABI pthread_join_nid_postfix(Pthread thread, void** retval); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003u); -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); static constexpr int GUEST_ESRCH = 3; static constexpr std::size_t REUSED_SLOT_ROUNDS = 64; @@ -40,7 +34,7 @@ int main() { result = reinterpret_cast(std::uintptr_t{0x55}); Require(scePthreadJoin(thread, &result) == SCE_KERNEL_ERROR_ESRCH); Require(result == reinterpret_cast(std::uintptr_t{0x55})); - Require(scePthreadDetach(thread) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadDetach(thread) == SCE_KERNEL_ERROR_ESRCH); Require(pthread_join_nid_postfix(thread, &result) == GUEST_ESRCH); Require(result == reinterpret_cast(std::uintptr_t{0x55})); @@ -62,4 +56,18 @@ int main() { Require(result == reinterpret_cast(std::uintptr_t{0x55})); Require(scePthreadDetach(detached) == SCE_KERNEL_ERROR_EINVAL); release = true; + + Pthread named = nullptr; + char name[64] = {}; + Require(scePthreadCreate(&named, nullptr, Finish, nullptr, "0123456789abcdefghijklmnopqrstu") == SCE_OK); + Require(scePthreadGetname(named, name) == SCE_OK && std::strcmp(name, "0123456789abcdefghijklmnopqrstu") == 0); + Require(scePthreadJoin(named, nullptr) == SCE_OK); + Pthread untouched = reinterpret_cast(std::uintptr_t{0x77}); + Require(scePthreadCreate(&untouched, nullptr, Finish, nullptr, "0123456789abcdefghijklmnopqrstuv") == SCE_KERNEL_ERROR_ENAMETOOLONG); + Require(untouched == reinterpret_cast(std::uintptr_t{0x77})); + Pthread unnamed = nullptr; + std::memset(name, 'x', sizeof(name)); + Require(scePthreadCreate(&unnamed, nullptr, Finish, nullptr, nullptr) == SCE_OK); + Require(scePthreadGetname(unnamed, name) == SCE_OK && name[0] == 0); + Require(scePthreadJoin(unnamed, nullptr) == SCE_OK); } diff --git a/core/libs/tests/GuestPthreadSem.cpp b/core/libs/tests/GuestPthreadSem.cpp index 574228ddf4..c2f4373538 100644 --- a/core/libs/tests/GuestPthreadSem.cpp +++ b/core/libs/tests/GuestPthreadSem.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -7,23 +9,7 @@ #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadSemInit(PthreadSem* sem, int flag, unsigned int value, const char* name); -int APS5_VABI scePthreadSemDestroy(PthreadSem* sem); -int APS5_VABI scePthreadSemPost(PthreadSem* sem); -int APS5_VABI scePthreadSemWait(PthreadSem* sem); -int APS5_VABI scePthreadSemTrywait(PthreadSem* sem); -int APS5_VABI scePthreadSemTimedwait(PthreadSem* sem, unsigned int usec); -int APS5_VABI scePthreadSemGetvalue(PthreadSem* sem, int* value); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast(0x80020010); -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); -static constexpr int SCE_KERNEL_ERROR_EOVERFLOW = static_cast(0x80020054u); -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); static void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestPthreadSpin.cpp b/core/libs/tests/GuestPthreadSpin.cpp index ca7e2b6926..2308710c88 100644 --- a/core/libs/tests/GuestPthreadSpin.cpp +++ b/core/libs/tests/GuestPthreadSpin.cpp @@ -1,25 +1,15 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include -struct PthreadSpinlockPrivate; using PthreadSpinlock = PthreadSpinlockPrivate*; -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI pthread_spin_init_nid_postfix(PthreadSpinlock* lock, int pshared); -int APS5_VABI pthread_spin_destroy_nid_postfix(PthreadSpinlock* lock); -int APS5_VABI pthread_spin_lock_nid_postfix(PthreadSpinlock* lock); -int APS5_VABI pthread_spin_trylock_nid_postfix(PthreadSpinlock* lock); -int APS5_VABI pthread_spin_unlock_nid_postfix(PthreadSpinlock* lock); -} - static constexpr int GUEST_EPERM = 1; static constexpr int GUEST_EBUSY = 16; static constexpr int GUEST_EINVAL = 22; -static constexpr int PTHREAD_PROCESS_PRIVATE = 0; -static constexpr int PTHREAD_PROCESS_SHARED = 1; +static constexpr int GuestPthread_Process_Private = 0; +static constexpr int GuestPthread_Process_Shared = 1; static constexpr int Increments = 200000; static void Require(bool value) { if (!value) std::abort(); } @@ -49,9 +39,9 @@ static void* APS5_VABI Incrementer(void* arg) { } static void ArgumentErrors() { - Require(pthread_spin_init_nid_postfix(nullptr, PTHREAD_PROCESS_PRIVATE) == GUEST_EINVAL); + Require(pthread_spin_init_nid_postfix(nullptr, GuestPthread_Process_Private) == GUEST_EINVAL); PthreadSpinlock lock = nullptr; - Require(pthread_spin_init_nid_postfix(&lock, PTHREAD_PROCESS_SHARED) == GUEST_EINVAL); + Require(pthread_spin_init_nid_postfix(&lock, GuestPthread_Process_Shared) == GUEST_EINVAL); Require(pthread_spin_init_nid_postfix(&lock, 2) == GUEST_EINVAL); Require(pthread_spin_init_nid_postfix(&lock, -1) == GUEST_EINVAL); Require(lock == nullptr); @@ -75,6 +65,7 @@ static void SingleThread(int pshared) { bool threw = false; try { pthread_spin_lock_nid_postfix(&lock); + } catch (const std::runtime_error&) { threw = true; } @@ -90,7 +81,7 @@ static void SingleThread(int pshared) { static void HeldByAnotherThread() { Shared shared; - Require(pthread_spin_init_nid_postfix(&shared.lock, PTHREAD_PROCESS_PRIVATE) == 0); + Require(pthread_spin_init_nid_postfix(&shared.lock, GuestPthread_Process_Private) == 0); Require(pthread_spin_lock_nid_postfix(&shared.lock) == 0); Pthread thread = nullptr; Require(scePthreadCreate(&thread, nullptr, Contender, &shared, nullptr) == 0); @@ -103,7 +94,7 @@ static void HeldByAnotherThread() { static void MutualExclusion() { Shared shared; - Require(pthread_spin_init_nid_postfix(&shared.lock, PTHREAD_PROCESS_PRIVATE) == 0); + Require(pthread_spin_init_nid_postfix(&shared.lock, GuestPthread_Process_Private) == 0); Pthread first = nullptr; Pthread second = nullptr; Require(scePthreadCreate(&first, nullptr, Incrementer, &shared, nullptr) == 0); @@ -116,7 +107,7 @@ static void MutualExclusion() { int main() { ArgumentErrors(); - SingleThread(PTHREAD_PROCESS_PRIVATE); + SingleThread(GuestPthread_Process_Private); HeldByAnotherThread(); MutualExclusion(); } diff --git a/core/libs/tests/GuestPthreadStack.cpp b/core/libs/tests/GuestPthreadStack.cpp index 5fcbe7ac52..d0e2472a70 100644 --- a/core/libs/tests/GuestPthreadStack.cpp +++ b/core/libs/tests/GuestPthreadStack.cpp @@ -1,11 +1,15 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #ifdef __linux__ #include "prx/libc/include/GuestAllocations.hpp" +#include #include #include #include #include #include +#include +#include #include #include #endif @@ -14,16 +18,9 @@ #include extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr); -int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize); -int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackAddress, std::size_t* stackSize); -Pthread APS5_VABI scePthreadSelf(); + #ifdef __linux__ -int APS5_VABI scePthreadDetach(Pthread thread); + #endif } @@ -32,6 +29,20 @@ static constexpr std::size_t FRAME_SIZE = 4096; static constexpr std::size_t FRAME_MARGIN = 2 * FRAME_SIZE; static constexpr std::size_t HOST_CALL_STACK = 256 * 1024; static constexpr std::size_t STACK_SIZES[] = {16384, 65536, 1u << 20, 16u << 20}; +#ifdef __linux__ +static void* lastGuestStack = nullptr; +static std::size_t lastGuestStackSize = 0; +#endif + +#ifdef __linux__ +extern bool GuestThreadStackHostRangeOverlaps(std::uintptr_t start, std::uintptr_t end); +static std::atomic observedUnmaps{0}; + +static void ObserveUnmap(std::uintptr_t address, std::size_t bytes) { + if (GuestThreadStackHostRangeOverlaps(address, address + bytes)) std::abort(); + observedUnmaps.fetch_add(1, std::memory_order_relaxed); +} +#endif thread_local unsigned char threadLocalBlock[512 * 1024]; @@ -61,6 +72,10 @@ static void* APS5_VABI Worker(void* arg) { void* address = nullptr; std::size_t size = 0; Require(scePthreadAttrGetstack(&attr, &address, &size) == SCE_OK); +#ifdef __linux__ + lastGuestStack = address; + lastGuestStackSize = size; +#endif Require(scePthreadAttrDestroy(&attr) == SCE_OK); Require(size == requested); @@ -78,33 +93,56 @@ static void* APS5_VABI Worker(void* arg) { } #ifdef __linux__ -bool GuestThreadStackHostRangeOverlaps(std::uintptr_t start, std::uintptr_t end); -static std::atomic observedUnmaps{0}; -static std::atomic retiredStackWord{0}; -static std::atomic retiredUnmapped{false}; -static std::atomic detachedDone{false}; -static int detachedTid = 0; +struct StackProbe { + std::size_t requested = 0; + std::uintptr_t stack = 0; + std::uintptr_t markerAddress = 0; + std::uintptr_t marker = 0; + std::atomic done{false}; + int hostTid = 0; +}; -static void* APS5_VABI DetachedWorker(void*) { - int local = 0; - detachedTid = static_cast(syscall(SYS_gettid)); - retiredStackWord.store(reinterpret_cast(&local), std::memory_order_relaxed); - detachedDone.store(true, std::memory_order_release); +static void* APS5_VABI RetainedStackWorker(void* opaque) { + auto& probe = *static_cast(opaque); + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == SCE_OK); + Require(scePthreadAttrGet(scePthreadSelf(), &attr) == SCE_OK); + void* address = nullptr; + std::size_t size = 0; + Require(scePthreadAttrGetstack(&attr, &address, &size) == SCE_OK); + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + Require(size == probe.requested); + probe.stack = reinterpret_cast(address); + probe.markerAddress = probe.stack + size - 4096; + *reinterpret_cast(probe.markerAddress) = probe.marker; + Require(*reinterpret_cast(probe.markerAddress) == probe.marker); + probe.hostTid = static_cast(syscall(SYS_gettid)); + probe.done.store(true, std::memory_order_release); return nullptr; } -static void* APS5_VABI ReaperWorker(void*) { - return nullptr; -} - -static void ObserveUnmap(std::uintptr_t address, std::size_t bytes) { - if (GuestThreadStackHostRangeOverlaps(address, address + bytes)) { - std::fprintf(stderr, "Retired stack still owns host-only pages after unmap\n"); - std::abort(); +static void WaitForProbe(StackProbe& probe) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (!probe.done.load(std::memory_order_acquire)) { + Require(std::chrono::steady_clock::now() < deadline); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - observedUnmaps.fetch_add(1, std::memory_order_relaxed); - const auto retired = retiredStackWord.load(std::memory_order_relaxed); - if (retired >= address && retired - address < bytes) retiredUnmapped.store(true, std::memory_order_relaxed); + const auto task = "/proc/self/task/" + std::to_string(probe.hostTid); + while (access(task.c_str(), F_OK) == 0) { + Require(std::chrono::steady_clock::now() < deadline); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +static void RequireCachedStackDiscarded(const StackProbe& probe) { + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(probe.markerAddress, false); + Require(range != nullptr && range->readable && range->writable); + Require(*reinterpret_cast(probe.markerAddress) == 0); +} + +static bool ReusesOneOf(const StackProbe& probe, const StackProbe* previous, std::size_t count) { + for (std::size_t i = 0; i < count; ++i) if (probe.stack == previous[i].stack) return true; + return false; } #endif @@ -122,26 +160,91 @@ int main() { void* result = nullptr; Require(scePthreadJoin(thread, &result) == SCE_OK); Require(result == &stackSize); +#ifdef __linux__ + if (stackSize == STACK_SIZES[sizeof(STACK_SIZES) / sizeof(STACK_SIZES[0]) - 1]) { + const auto pageSize = static_cast(sysconf(_SC_PAGESIZE)); + const auto stackAddress = reinterpret_cast(lastGuestStack); + const auto firstPageAddress = stackAddress - stackAddress % pageSize; + const auto leadingBytes = stackAddress - firstPageAddress; + const auto pageCount = (leadingBytes + lastGuestStackSize + pageSize - 1) / pageSize; + std::vector residency(pageCount); + if (mincore(reinterpret_cast(firstPageAddress), pageCount * pageSize, residency.data()) == 0) { + for (std::size_t page = 0; page < pageCount; ++page) + Require((residency[page] & 1u) == 0); + } else { + Require(errno == ENOMEM); + } + } +#endif } #ifdef __linux__ - Pthread detached = nullptr; - Require(scePthreadCreate(&detached, nullptr, DetachedWorker, nullptr, nullptr) == SCE_OK); - Require(scePthreadDetach(detached) == SCE_OK); - const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); - while (!detachedDone.load(std::memory_order_acquire)) { - Require(std::chrono::steady_clock::now() < deadline); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + constexpr std::size_t JoinedStackSize = 81920; + StackProbe joined[6]; + for (std::size_t i = 0; i < 6; ++i) { + joined[i].requested = JoinedStackSize; + joined[i].marker = 0x77000000u + i; + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == SCE_OK); + Require(scePthreadAttrSetstacksize(&attr, JoinedStackSize) == SCE_OK); + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, &attr, RetainedStackWorker, &joined[i], nullptr) == SCE_OK); + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + Require(scePthreadJoin(thread, nullptr) == SCE_OK); + RequireCachedStackDiscarded(joined[i]); } - const auto task = "/proc/self/task/" + std::to_string(detachedTid); - while (access(task.c_str(), F_OK) == 0) { - Require(std::chrono::steady_clock::now() < deadline); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + StackProbe joinedReuse; + joinedReuse.requested = JoinedStackSize; + joinedReuse.marker = 0x7700ffffu; + PthreadAttr joinedAttr = nullptr; + Require(scePthreadAttrInit(&joinedAttr) == SCE_OK); + Require(scePthreadAttrSetstacksize(&joinedAttr, JoinedStackSize) == SCE_OK); + Pthread joinedThread = nullptr; + Require(scePthreadCreate(&joinedThread, &joinedAttr, RetainedStackWorker, &joinedReuse, nullptr) == SCE_OK); + Require(scePthreadAttrDestroy(&joinedAttr) == SCE_OK); + Require(scePthreadJoin(joinedThread, nullptr) == SCE_OK); + Require(ReusesOneOf(joinedReuse, joined, 6)); + RequireCachedStackDiscarded(joinedReuse); + + constexpr std::size_t DetachedStackSize = 114688; + StackProbe detached[5]; + for (std::size_t i = 0; i < 5; ++i) { + detached[i].requested = DetachedStackSize; + detached[i].marker = 0x77300000u + i; + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == SCE_OK); + Require(scePthreadAttrSetstacksize(&attr, DetachedStackSize) == SCE_OK); + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, &attr, RetainedStackWorker, &detached[i], nullptr) == SCE_OK); + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + Require(scePthreadDetach(thread) == SCE_OK); + WaitForProbe(detached[i]); + } + StackProbe detachedReuse; + detachedReuse.requested = DetachedStackSize; + detachedReuse.marker = 0x7730ffffu; + PthreadAttr detachedAttr = nullptr; + Require(scePthreadAttrInit(&detachedAttr) == SCE_OK); + Require(scePthreadAttrSetstacksize(&detachedAttr, DetachedStackSize) == SCE_OK); + Pthread detachedThread = nullptr; + Require(scePthreadCreate(&detachedThread, &detachedAttr, RetainedStackWorker, &detachedReuse, nullptr) == SCE_OK); + Require(scePthreadAttrDestroy(&detachedAttr) == SCE_OK); + Require(scePthreadJoin(detachedThread, nullptr) == SCE_OK); + for (const auto& probe : detached) RequireCachedStackDiscarded(probe); + Require(ReusesOneOf(detachedReuse, detached, 5)); + RequireCachedStackDiscarded(detachedReuse); + + for (std::size_t stackSize = 4u << 20; stackSize <= 20u << 20; stackSize += 1u << 20) { + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == SCE_OK); + Require(scePthreadAttrSetstacksize(&attr, stackSize) == SCE_OK); + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, &attr, Worker, &stackSize, nullptr) == SCE_OK); + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + void* result = nullptr; + Require(scePthreadJoin(thread, &result) == SCE_OK); + Require(result == &stackSize); } - Pthread reaper = nullptr; - Require(scePthreadCreate(&reaper, nullptr, ReaperWorker, nullptr, nullptr) == SCE_OK); - Require(scePthreadJoin(reaper, nullptr) == SCE_OK); - Require(retiredUnmapped.load(std::memory_order_relaxed)); - GuestAllocations::GuestAllocationsSetUnmapObserver_nid_postfix(nullptr); Require(observedUnmaps.load(std::memory_order_relaxed) != 0); + GuestAllocations::GuestAllocationsSetUnmapObserver_nid_postfix(nullptr); #endif } diff --git a/core/libs/tests/GuestPthreadTimedLocks.cpp b/core/libs/tests/GuestPthreadTimedLocks.cpp index ae2a8187fa..601619c9cd 100644 --- a/core/libs/tests/GuestPthreadTimedLocks.cpp +++ b/core/libs/tests/GuestPthreadTimedLocks.cpp @@ -1,24 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock); -int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock); -int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock* rwlock); -int APS5_VABI pthread_rwlock_timedrdlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime); -int APS5_VABI pthread_rwlock_timedwrlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime); -int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex* mutex, const PthreadMutexattr* attr); -int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex); -int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const KernelTimespec* abstime); -} - static constexpr int SCE_OK = 0; static constexpr int GUEST_EDEADLK = 11; static constexpr auto HoldTime = std::chrono::milliseconds(50); diff --git a/core/libs/tests/GuestQsort.cpp b/core/libs/tests/GuestQsort.cpp index f27a089c63..00e7f72ef2 100644 --- a/core/libs/tests/GuestQsort.cpp +++ b/core/libs/tests/GuestQsort.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -8,14 +9,6 @@ #include #include -extern "C" { -int APS5_VABI mergesort_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*)); -int* APS5_VABI __error_nid_postfix(); -void APS5_VABI qsort_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*)); -int APS5_VABI qsort_s_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*, void*), void*); -void* APS5_VABI set_constraint_handler_s_nid_postfix(void*); -} - namespace { bool failAllocation = false; @@ -156,6 +149,7 @@ bool ExpectOrder(const char* name, const std::vector& keys, const std::vect std::vector elements; for (std::size_t index = 0; index < keys.size(); ++index) elements.push_back({keys[index], static_cast(index)}); qsort_nid_postfix(elements.data(), elements.size(), sizeof(Element), CompareKeys); + for (std::size_t index = 0; index < tags.size(); ++index) { if (elements[index].tag != tags[index]) { std::fprintf(stderr, "%s: element %zu has tag %d, expected %d\n", name, index, elements[index].tag, tags[index]); @@ -175,6 +169,7 @@ bool ExpectSorted(std::size_t size, std::size_t count) { std::vector counts(256); for (std::size_t index = 0; index < count; ++index) ++counts[bytes[index * size]]; qsort_nid_postfix(bytes.data(), count, size, CompareFirstByte); + for (std::size_t index = 0; index < count; ++index) { if (index > 0 && bytes[(index - 1) * size] > bytes[index * size]) { std::fprintf(stderr, "qsort of %zu %zu-byte elements is not sorted at %zu\n", count, size, index); @@ -191,7 +186,6 @@ bool ExpectSorted(std::size_t size, std::size_t count) { return true; } - bool CheckMergesort() { InitializeHeap(); bool correct = true; diff --git a/core/libs/tests/GuestRaiseException.cpp b/core/libs/tests/GuestRaiseException.cpp index fd74e36771..205e6e4c81 100644 --- a/core/libs/tests/GuestRaiseException.cpp +++ b/core/libs/tests/GuestRaiseException.cpp @@ -1,3 +1,8 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Semaphore/include/Semaphore.hpp" +#include "prx/libkernel/Pthread/include/Rwlock.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" #include "SceTypes.hpp" #include #include @@ -13,33 +18,6 @@ #include #endif -extern "C" { -int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler); -int APS5_VABI sceKernelRemoveExceptionHandler(int signum); -int APS5_VABI sceKernelRaiseException(Pthread thread, int signum); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -Pthread APS5_VABI scePthreadSelf(); -int APS5_VABI sceKernelCreateSema(KernelSema* sem, const char* name, uint32_t attr, int init, int max, void* opt); -int APS5_VABI sceKernelDeleteSema(KernelSema sem); -int APS5_VABI sceKernelSignalSema(KernelSema sem, int count); -int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr, const char* name); -int APS5_VABI scePthreadRwlockDestroy(PthreadRwlock* rwlock); -int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock); -int APS5_VABI scePthreadRwlockUnlock(PthreadRwlock* rwlock); -int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); -int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count); -} - -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); -static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003c); static constexpr int SIGUSR1 = 30; static constexpr int Repeats = 50; static constexpr std::size_t R12Offset = 0xa0; @@ -404,7 +382,6 @@ static void NestedWaitKeepsOuterWake() { Require(sceKernelDeleteSema(sem) == 0); } -#ifdef _WIN32 static constexpr int AddressRounds = 50; static std::uint32_t addressWord = 0; static std::uint32_t otherAddressWord = 0; @@ -483,7 +460,36 @@ static void HandlerLeavesAddressLock(PthreadEntry entry, const char* name) { worker.stop.store(true); Require(scePthreadJoin(thread, nullptr) == 0); } -#endif + +static std::uint32_t idleWord = 0; + +static void* APS5_VABI AddressIdle(void* arg) { + auto& worker = *static_cast(arg); + worker.id = std::this_thread::get_id(); + worker.started.store(true); + worker.waitResult = sceKernelSyncOnAddressWait(&idleWord, 0, nullptr, "address idle"); + return nullptr; +} + +static void HandlerRunsInUnboundedAddressWait() { + Worker worker; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, AddressIdle, &worker, "address idle") == 0); + while (!worker.started.load()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + holdingEntered.store(false); + holdingLeft.store(false); + holdingRelease.store(false); + Require(sceKernelRaiseException(thread, SIGUSR1) == 0); + RequireInRound(AwaitWithin(holdingEntered, std::chrono::seconds(5)), "address idle", 0, "the handler did not run while the thread waited without a timeout"); + RequireInRound(holdingThread.load() == worker.id, "address idle", 0, "the handler ran on another thread"); + holdingRelease.store(true); + RequireInRound(AwaitWithin(holdingLeft, std::chrono::seconds(5)), "address idle", 0, "the handler did not return within 5 s of its release"); + std::atomic_ref(idleWord).store(1); + Require(sceKernelSyncOnAddressWake(&idleWord, 1) >= 0); + Require(scePthreadJoin(thread, nullptr) == 0); + Require(worker.waitResult == 0); +} static void ExpectDelivery(int before, std::thread::id thread) { for (int attempt = 0; attempt < 5000 && calls.load() == before; ++attempt) std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -551,6 +557,7 @@ int main() { bool rejected = false; try { sceKernelRaiseException(scePthreadSelf(), SIGUSR1); + } catch (const std::runtime_error&) { rejected = true; } @@ -659,6 +666,7 @@ int main() { bool refused = false; try { sceKernelRaiseException(loopingThread, SIGUSR1); + } catch (const std::runtime_error&) { refused = true; } @@ -745,10 +753,9 @@ int main() { NestedWaitKeepsOuterWake(); Require(sceKernelRemoveExceptionHandler(SIGUSR1) == 0); -#ifdef _WIN32 Require(sceKernelInstallExceptionHandler(SIGUSR1, reinterpret_cast(&HoldingHandler)) == 0); HandlerLeavesAddressLock(AddressWaking, "address waking"); HandlerLeavesAddressLock(AddressWaiting, "address waiting"); + HandlerRunsInUnboundedAddressWait(); Require(sceKernelRemoveExceptionHandler(SIGUSR1) == 0); -#endif } diff --git a/core/libs/tests/GuestRaiseInHostCode.cpp b/core/libs/tests/GuestRaiseInHostCode.cpp index 955d4605b6..e89332fc4c 100644 --- a/core/libs/tests/GuestRaiseInHostCode.cpp +++ b/core/libs/tests/GuestRaiseInHostCode.cpp @@ -1,4 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" +#include #include #include #include @@ -7,14 +9,6 @@ #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread*, const PthreadAttr*, PthreadEntry, void*, const char*); -int APS5_VABI scePthreadJoin(Pthread, void**); -int APS5_VABI sceKernelInstallExceptionHandler(int, void*); -int APS5_VABI sceKernelRemoveExceptionHandler(int); -int APS5_VABI sceKernelRaiseException(Pthread, int); -} - static constexpr int SigUsr1 = 30; static constexpr int Raises = 100; static void Require(bool value) { if (!value) std::abort(); } @@ -54,8 +48,11 @@ int main() { Pthread writer = nullptr; Require(scePthreadCreate(&writer, nullptr, Writer, nullptr, "host writer") == 0); while (!started.load()) std::this_thread::yield(); + auto longest = std::chrono::steady_clock::duration::zero(); for (int raise = 1; raise <= Raises; ++raise) { + const auto raisedAt = std::chrono::steady_clock::now(); Require(sceKernelRaiseException(writer, SigUsr1) == 0); + longest = std::max(longest, std::chrono::steady_clock::now() - raisedAt); while (deliveries.load() < raise) std::this_thread::yield(); Require(std::fputc('y', shared) == 'y'); released.store(raise); @@ -64,6 +61,7 @@ int main() { Require(scePthreadJoin(writer, nullptr) == 0); Require(sceKernelRemoveExceptionHandler(SigUsr1) == 0); std::fclose(shared); + std::printf("longest raise: %.2f ms\n", std::chrono::duration(longest).count()); done.store(true); watchdog.join(); } diff --git a/core/libs/tests/GuestRaiseInSystemCall.cpp b/core/libs/tests/GuestRaiseInSystemCall.cpp index 834594d260..6f0d08c8be 100644 --- a/core/libs/tests/GuestRaiseInSystemCall.cpp +++ b/core/libs/tests/GuestRaiseInSystemCall.cpp @@ -1,86 +1,122 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include #include +#include #include #include #include +#include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread*, const PthreadAttr*, PthreadEntry, void*, const char*); -int APS5_VABI scePthreadJoin(Pthread, void**); -int APS5_VABI sceKernelInstallExceptionHandler(int, void*); -int APS5_VABI sceKernelRemoveExceptionHandler(int); -int APS5_VABI sceKernelRaiseException(Pthread, int); -} - static constexpr int SigUsr1 = 30; static constexpr int Raises = 10; static constexpr DWORD PipeBytes = 4096; +static constexpr std::uint64_t BlockBytes = 4u * PipeBytes; +static constexpr std::size_t RipOffset = 0xe0; +static constexpr ULONG ThreadSuspendCount = 35; static void Require(bool value) { if (!value) std::abort(); } static std::FILE* shared = nullptr; static std::atomic deliveries{0}; static std::atomic released{0}; -static std::atomic stop{false}; -static std::atomic blocks{0}; +static std::atomic written{0}; +static std::atomic blocked{0}; +static std::atomic writerThread{nullptr}; +static bool delayReturn = false; -static void APS5_VABI Handler(int, void*) { +static bool InTestImage(std::uint64_t rip) { + const auto base = reinterpret_cast(GetModuleHandleW(nullptr)); + const auto* dos = reinterpret_cast(base); + const auto* nt = reinterpret_cast(base + dos->e_lfanew); + return rip >= base && rip - base < nt->OptionalHeader.SizeOfImage; +} + +static bool Suspended(HANDLE thread) { + using Query = LONG(NTAPI*)(HANDLE, ULONG, void*, ULONG, ULONG*); + static const auto query = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryInformationThread")); + ULONG count = 0; + Require(query != nullptr && query(thread, ThreadSuspendCount, &count, sizeof(count), nullptr) >= 0); + return count > 0; +} + +static void APS5_VABI Handler(int, void* context) { const auto delivery = deliveries.fetch_add(1) + 1; + std::uint64_t rip = 0; + std::memcpy(&rip, static_cast(context) + RipOffset, sizeof(rip)); + Require(InTestImage(rip)); while (released.load() < delivery) std::this_thread::yield(); } static void* APS5_VABI Writer(void*) { - const std::string block(16u * PipeBytes, 'x'); - while (!stop.load()) { + HANDLE self = nullptr; + Require(DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &self, THREAD_QUERY_INFORMATION, FALSE, 0) != 0); + writerThread.store(self); + const std::string block(BlockBytes, 'x'); + for (int raise = 1; raise <= Raises; ++raise) { Require(std::fwrite(block.data(), 1, block.size(), shared) == block.size()); - blocks.fetch_add(1); - volatile std::uint64_t spin = 0; - for (std::uint64_t i = 0; i < 200000; ++i) spin = spin + i; + if (delayReturn) { + volatile std::uint64_t spin = 0; + for (std::uint64_t i = 0; i < 100000000; ++i) spin = spin + i; + } + written.store(raise); + while (deliveries.load() < raise) { + } } return nullptr; } -int main() { +int main(int argc, char** argv) { + const std::string_view mode = argc > 1 ? argv[1] : ""; + Require(mode.empty() || mode == "--delay-raise" || mode == "--delay-return"); + delayReturn = mode == "--delay-return"; std::atomic done{false}; std::thread watchdog([&done] { - for (int i = 0; i < 25000 && !done.load(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(25); + while (!done.load() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (!done.load()) std::abort(); }); HANDLE read = nullptr; HANDLE write = nullptr; Require(CreatePipe(&read, &write, nullptr, PipeBytes) != 0); shared = _fdopen(_open_osfhandle(reinterpret_cast(write), _O_WRONLY | _O_BINARY), "wb"); - Require(shared != nullptr); - std::atomic draining{true}; + Require(shared != nullptr && std::setvbuf(shared, nullptr, _IONBF, 0) == 0); std::thread reader([&] { char chunk[PipeBytes]; DWORD got = 0; + std::uint64_t total = 0; + int block = 0; while (ReadFile(read, chunk, sizeof(chunk), &got, nullptr) != 0 && got != 0) { - if (draining.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10)); + total += got; + if (block == Raises || total <= static_cast(block) * (BlockBytes + 1)) continue; + blocked.store(++block); + while (!Suspended(writerThread.load())) { + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + Require(written.load() < block); } }); Require(sceKernelInstallExceptionHandler(SigUsr1, reinterpret_cast(&Handler)) == 0); Pthread writer = nullptr; Require(scePthreadCreate(&writer, nullptr, Writer, nullptr, "pipe writer") == 0); - while (blocks.load() == 0) std::this_thread::yield(); for (int raise = 1; raise <= Raises; ++raise) { + while (blocked.load() < raise) std::this_thread::yield(); + if (mode == "--delay-raise") std::this_thread::sleep_for(std::chrono::milliseconds(200)); Require(sceKernelRaiseException(writer, SigUsr1) == 0); while (deliveries.load() < raise) std::this_thread::yield(); Require(std::fputc('y', shared) == 'y'); released.store(raise); } - stop.store(true); - draining.store(false); Require(scePthreadJoin(writer, nullptr) == 0); Require(sceKernelRemoveExceptionHandler(SigUsr1) == 0); std::fclose(shared); reader.join(); CloseHandle(read); + CloseHandle(writerThread.load()); done.store(true); watchdog.join(); } diff --git a/core/libs/tests/GuestRaiseNestedSleep.cpp b/core/libs/tests/GuestRaiseNestedSleep.cpp index 839f831ecf..b5e27b4f5a 100644 --- a/core/libs/tests/GuestRaiseNestedSleep.cpp +++ b/core/libs/tests/GuestRaiseNestedSleep.cpp @@ -1,18 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler); -int APS5_VABI sceKernelRemoveExceptionHandler(int signum); -int APS5_VABI sceKernelRaiseException(Pthread thread, int signum); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); -} - static constexpr int SIGUSR1 = 30; static constexpr int SCE_OK = 0; static constexpr KernelUseconds OuterSleep = 1000000; diff --git a/core/libs/tests/GuestRandom.cpp b/core/libs/tests/GuestRandom.cpp index df1d5f83c9..9ac352658a 100644 --- a/core/libs/tests/GuestRandom.cpp +++ b/core/libs/tests/GuestRandom.cpp @@ -1,11 +1,9 @@ +#include "prx/libSceRandom/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -int APS5_VABI sceRandomGetRandomNumber(void*, std::size_t); -} static void Require(bool value) { if (!value) std::abort(); } int main() { constexpr int invalid = static_cast(0x817C0016); diff --git a/core/libs/tests/GuestRandomDeviceSeed.cpp b/core/libs/tests/GuestRandomDeviceSeed.cpp new file mode 100644 index 0000000000..0f2ffde1ad --- /dev/null +++ b/core/libs/tests/GuestRandomDeviceSeed.cpp @@ -0,0 +1,91 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/HostEntropy.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +static void* entropyBuffer; +static std::size_t entropySize; +static bool failEntropy; + +void HostEntropy_nid_no_patch(void* buffer, std::size_t size) { + entropyBuffer = buffer; + entropySize = size; + if (failEntropy) throw std::runtime_error("entropy unavailable"); + auto* bytes = static_cast(buffer); + for (std::size_t i = 0; i < size; ++i) bytes[i] = static_cast(i * 37 + 11); +} + +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static std::uint32_t Word(const char* buffer, std::size_t index) { + std::uint32_t value; + std::memcpy(&value, buffer + sizeof(value) * index, sizeof(value)); + return value; +} + +int main() { + constexpr std::array sizes {8, 32, 64, 128, 256}; + constexpr std::array degrees {1, 7, 15, 31, 63}; + constexpr std::array separations {0, 3, 1, 3, 1}; + std::array state{}; + for (std::size_t type = 0; type < sizes.size(); ++type) { + state.fill(static_cast(0xa5)); + char* table = state.data() + 1; + char* previous = initstate_nid_postfix(42, table, static_cast(sizes[type])); + for (int i = 0; i < 5; ++i) random_nid_postfix(); + const std::uint32_t header = Word(table, 0); + srandomdev_nid_postfix(); + Require(entropyBuffer == table + 4, "srandomdev fills the active state after its header"); + Require(entropySize == degrees[type] * 4, "srandomdev fills every generator word"); + Require(Word(table, 0) == header, "srandomdev leaves the saved header untouched"); + for (std::size_t i = 0; i < entropySize; ++i) + Require(static_cast(table[4 + i]) == static_cast(i * 37 + 11), + "srandomdev neither derives words from one seed nor advances the generator"); + Require(state[0] == static_cast(0xa5) && + std::all_of(state.begin() + 1 + sizes[type], state.end(), [](char c) { return c == static_cast(0xa5); }), + "srandomdev preserves the buffer guards"); + const auto seeded = state; + if (type != 0) { + const std::uint32_t sum = Word(table, 1) + Word(table, 1 + separations[type]); + Require(random_nid_postfix() == sum / 2, "srandomdev resets the front and rear positions"); + Require(Word(table, 1 + separations[type]) == sum, "random advances the newly seeded table"); + } else { + std::int64_t x = static_cast(Word(table, 1)); + if (x == 0) x = 123459876; + x = 16807 * (x % 127773) - 2836 * (x / 127773); + if (x < 0) x += 0x7fffffff; + const auto expected = x; + Require(random_nid_postfix() == static_cast(expected), "the linear generator uses the entropy word"); + } + setstate_nid_postfix(previous); + Require(Word(table, 0) == type + (type == 0 ? 0 : 5), "the reseeded state saves its new position"); + state = seeded; + const std::uint32_t resetHeader = static_cast(type); + std::memcpy(table, &resetHeader, sizeof(resetHeader)); + setstate_nid_postfix(table); + const auto beforeFailure = state; + failEntropy = true; + bool threw = false; + try { + srandomdev_nid_postfix(); + } catch (const std::runtime_error&) { + threw = true; + } + failEntropy = false; + Require(threw, "srandomdev propagates entropy failures"); + Require(state == beforeFailure, "an entropy failure before writing keeps the state"); + setstate_nid_postfix(previous); + } +} diff --git a/core/libs/tests/GuestRandomState.cpp b/core/libs/tests/GuestRandomState.cpp index 71511241be..6e126f762d 100644 --- a/core/libs/tests/GuestRandomState.cpp +++ b/core/libs/tests/GuestRandomState.cpp @@ -1,6 +1,8 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include #include @@ -10,23 +12,6 @@ #include #include -extern "C" { -std::int64_t APS5_VABI random_nid_postfix(); -void APS5_VABI srandom_nid_postfix(std::uint64_t); -char* APS5_VABI initstate_nid_postfix(std::uint64_t, char*, std::int64_t); -char* APS5_VABI setstate_nid_postfix(char*); -int* APS5_VABI __error_nid_postfix(); -double APS5_VABI erand48_nid_postfix(std::uint16_t*); -std::int64_t APS5_VABI nrand48_nid_postfix(std::uint16_t*); -std::int64_t APS5_VABI jrand48_nid_postfix(std::uint16_t*); -double APS5_VABI drand48_nid_postfix(); -std::int64_t APS5_VABI lrand48_nid_postfix(); -std::int64_t APS5_VABI mrand48_nid_postfix(); -void APS5_VABI srand48_nid_postfix(std::int64_t); -std::uint16_t* APS5_VABI seed48_nid_postfix(const std::uint16_t*); -void APS5_VABI lcong48_nid_postfix(const std::uint16_t*); -} - static int failures = 0; static void Require(bool condition, const char* message) { @@ -56,10 +41,10 @@ static bool SeedIs(const std::uint16_t* seed, std::uint16_t low, std::uint16_t m static void CheckRand48() { std::uint16_t seed[3] = {0x330e, 0xabcd, 0x1234}; - Require(erand48_nid_postfix(seed) == 0x657eb7255101p-48 && SeedIs(seed, 0x5101, 0xb725, 0x657e), "erand48 first value"); + Require(erand48_nid_postfix(seed) == 0x657eb725p-32 && SeedIs(seed, 0x5101, 0xb725, 0x657e), "erand48 first value"); Require(nrand48_nid_postfix(seed) == 1804928587 && SeedIs(seed, 0x6378, 0x0c96, 0xd72a), "nrand48 second value"); Require(jrand48_nid_postfix(seed) == 1517566982 && SeedIs(seed, 0x2a23, 0x3c06, 0x5a74), "jrand48 third value"); - Require(erand48_nid_postfix(seed) == 0x72534abf62f2p-48 && SeedIs(seed, 0x62f2, 0x4abf, 0x7253), "erand48 fourth value"); + Require(erand48_nid_postfix(seed) == 0x72534abfp-32 && SeedIs(seed, 0x62f2, 0x4abf, 0x7253), "erand48 fourth value"); Require(nrand48_nid_postfix(seed) == 684387517 && SeedIs(seed, 0x8d15, 0xd97a, 0x5195), "nrand48 fifth value"); Require(jrand48_nid_postfix(seed) == 3807181130 && SeedIs(seed, 0xeffc, 0xf94a, 0xe2ec), "jrand48 keeps bit 31 without sign extension"); @@ -78,6 +63,7 @@ static void CheckRand48() { const std::uint16_t parameters[7] = {1, 0, 0, 3, 0, 0, 5}; lcong48_nid_postfix(parameters); + Require(lrand48_nid_postfix() == 0 && mrand48_nid_postfix() == 0, "lcong48 sets seed, multiplier and addend"); std::uint16_t high[3] = {0, 0, 1}; Require(jrand48_nid_postfix(high) == 196608 && SeedIs(high, 5, 0, 3), "lcong48 multiplier scales the seed"); @@ -90,6 +76,7 @@ static void CheckRand48() { Require(drand48_nid_postfix() == erand48_nid_postfix(again), "seed48 restores the default multiplier and addend"); lcong48_nid_postfix(parameters); srand48_nid_postfix(0x1234abcd); + std::uint16_t reset[3] = {0x330e, 0xabcd, 0x1234}; Require(lrand48_nid_postfix() == nrand48_nid_postfix(reset), "srand48 restores the default multiplier and addend"); @@ -101,20 +88,25 @@ static void CheckRand48() { Require(jrand48_nid_postfix(ones) == 4294582547, "jrand48 matches the console for an all-ones seed"); constexpr std::array consoleMrand48 {178800969, 1952030186, 3585512650, 1443049011, 2428758494, 7588830}; srand48_nid_postfix(1); + for (const std::int64_t expected : consoleMrand48) Require(mrand48_nid_postfix() == expected, "mrand48 matches the console after srand48(1)"); + constexpr std::array consoleDrand48 {0x4435afd7p-32, 0xae99dc60p-32, 0x43f73108p-32}; + srand48_nid_postfix(7); + for (const double expected : consoleDrand48) Require(drand48_nid_postfix() == expected, "drand48 matches the titles' libc.prx after srand48(7)"); #ifndef _WIN32 std::uint16_t guest[3] = {0x1234, 0x5678, 0x9abc}; unsigned short host[3] = {0x1234, 0x5678, 0x9abc}; for (int round = 0; round < 64; ++round) { - Require(erand48_nid_postfix(guest) == ::erand48(host), "erand48 matches the host"); + Require(erand48_nid_postfix(guest) == std::ldexp(std::floor(::erand48(host) * 0x1p32), -32), "erand48 keeps the host's upper 32 bits"); Require(nrand48_nid_postfix(guest) == ::nrand48(host), "nrand48 matches the host"); Require(jrand48_nid_postfix(guest) == static_cast(::jrand48(host)), "jrand48 matches the host's 32 bits"); } ::srand48(1234567); srand48_nid_postfix(1234567); + for (int round = 0; round < 32; ++round) { - Require(drand48_nid_postfix() == ::drand48(), "drand48 matches the host"); + Require(drand48_nid_postfix() == std::ldexp(std::floor(::drand48() * 0x1p32), -32), "drand48 keeps the host's upper 32 bits"); Require(lrand48_nid_postfix() == ::lrand48(), "lrand48 matches the host"); Require(mrand48_nid_postfix() == static_cast(::mrand48()), "mrand48 matches the host's 32 bits"); } @@ -123,23 +115,26 @@ static void CheckRand48() { int main() { CheckRand48(); - constexpr Sequence seedOne {1408992891, 1638182910, 83848093, 145962118, 627851256}; - constexpr Sequence seed12345 {1052984060, 1639322404, 1971525204, 2097956667, 2012147915}; + constexpr Sequence seedOne {1804289383, 846930886, 1681692777, 1714636915, 1957747793}; + constexpr Sequence seed12345 {383100999, 858300821, 357768173, 455528251, 133005921}; constexpr std::array seed42ByType {{ - {2060028338, 1164936638, 481681973, 1763071474, 962919018}, - {660273387, 1356916325, 287587791, 1132382070, 1497594867}, - {1015209508, 12952757, 1418769128, 2005808989, 1212998434}, - {745791173, 418148062, 949332769, 1480536337, 177711995}, - {996638292, 1209745284, 1148231710, 400392963, 1149215164}, + {413993338, 139015486, 2118548913, 1172713531, 191403351}, + {769798547, 2024571666, 1204852799, 931293870, 1762463907}, + {2051258974, 339992574, 1379825892, 1298392284, 825292997}, + {71876166, 708592740, 1483128881, 907283241, 442951012}, + {472624893, 994493761, 100792968, 176611971, 1804504504}, }}; - constexpr std::array seed7Type1 {1271472800, 27686551, 1252069488, 1609001632, 28873941, 508017694}; + constexpr std::array seed7Type1 {1380991591, 1769076016, 21842418, 334161865, 1367472909, 1249858043}; Require(Draw() == seedOne, "unseeded random must match srandom(1)"); srandom_nid_postfix(1); + Require(Draw() == seedOne, "srandom(1) sequence"); srandom_nid_postfix(0x100000001ull); + Require(Draw() == seedOne, "srandom must use the low 32 bits of the seed"); srandom_nid_postfix(12345); + Require(Draw() == seed12345, "srandom(12345) sequence"); struct SizeCase { @@ -167,14 +162,16 @@ int main() { } Require(defaultTable != nullptr && Word(defaultTable, 0) == 5 * 5 + 3, "default table must hold its rear position"); Require(setstate_nid_postfix(defaultTable) == active, "setstate must return the previous state"); - Require(random_nid_postfix() == 1533042628 && random_nid_postfix() == 1225903521, + Require(random_nid_postfix() == 116285904 && random_nid_postfix() == 591987137, "setstate on the default table must continue the srandom(12345) sequence"); alignas(4) std::array small{}; alignas(4) std::array large{}; initstate_nid_postfix(7, small.data(), static_cast(small.size())); + for (std::size_t i = 0; i < 3; ++i) Require(random_nid_postfix() == seed7Type1[i], "initstate(7) on 32 bytes"); initstate_nid_postfix(7, large.data(), static_cast(large.size())); + Require(setstate_nid_postfix(small.data()) == large.data(), "setstate must return the 64-byte state"); Require(setstate_nid_postfix(large.data()) == small.data(), "setstate must return the 32-byte state"); Require(Word(small.data(), 0) == 5 * 3 + 1, "setstate must save the rear position of the old state"); @@ -203,6 +200,7 @@ int main() { bool threw = false; try { initstate_nid_postfix(1, nullptr, 128); + } catch (const std::invalid_argument&) { threw = true; } @@ -210,6 +208,7 @@ int main() { threw = false; try { setstate_nid_postfix(nullptr); + } catch (const std::invalid_argument&) { threw = true; } @@ -218,9 +217,11 @@ int main() { constexpr std::size_t threadCount = 4; constexpr std::size_t callsPerThread = 2000; srandom_nid_postfix(99); + std::vector serial(threadCount * callsPerThread); for (auto& value : serial) value = random_nid_postfix(); srandom_nid_postfix(99); + std::vector concurrent(threadCount * callsPerThread); std::vector threads; for (std::size_t t = 0; t < threadCount; ++t) @@ -232,5 +233,25 @@ int main() { std::sort(concurrent.begin(), concurrent.end()); Require(serial == concurrent, "concurrent calls must draw the serial sequence"); + for (const std::int64_t size : {8, 32, 64, 128, 256}) { + std::array entropyState{}; + char* previous = initstate_nid_postfix(42, entropyState.data(), size); + srandomdev_nid_postfix(); + const std::size_t separation = size == 32 || size == 128 ? 3 : 1; + std::int64_t expected; + if (size == 8) { + std::int64_t x = static_cast(Word(entropyState.data(), 1)); + if (x == 0) x = 123459876; + x = 16807 * (x % 127773) - 2836 * (x / 127773); + if (x < 0) x += 0x7fffffff; + expected = x; + } else { + const std::uint32_t sum = Word(entropyState.data(), 1) + Word(entropyState.data(), 1 + separation); + expected = sum >> 1; + } + Require(random_nid_postfix() == expected, "srandomdev seeds the shared library's active state from host entropy"); + setstate_nid_postfix(previous); + } + return failures == 0 ? 0 : 1; } diff --git a/core/libs/tests/GuestRazorCpu.cpp b/core/libs/tests/GuestRazorCpu.cpp index 13572d5feb..f938b7a80b 100644 --- a/core/libs/tests/GuestRazorCpu.cpp +++ b/core/libs/tests/GuestRazorCpu.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceRazorCpu/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,31 +6,6 @@ #include #include -extern "C" { -std::uint32_t APS5_VABI sceRazorCpuIsCapturing(void); -int APS5_VABI sceRazorCpuJobManagerDispatch(const void* args); -int APS5_VABI sceRazorCpuJobManagerJob(const void* args); -int APS5_VABI sceRazorCpuJobManagerSequence(const void* args); -int APS5_VABI sceRazorCpuPushMarker(const char* label, std::uint32_t color, std::uint32_t flags); -int APS5_VABI sceRazorCpuNamedSync(const char* label); -int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, std::uint32_t color, std::uint32_t flags); -int APS5_VABI sceRazorCpuPopMarker(void); -int APS5_VABI sceRazorCpuFlushOccurred(std::uint64_t* timeSpentInFlush); -int APS5_VABI sceRazorCpuPlotValue(const char* series, float value); -int APS5_VABI sceRazorCpuWriteBookmark(const char* label, const char* description); -int APS5_VABI sceRazorCpuSync(void); -void APS5_VABI sceRazorCpuDisableFiberUserMarkers(void); -int APS5_VABI sceRazorCpuInitDataTags(void* buffer, std::size_t size); -int APS5_VABI sceRazorCpuShutdownDataTags(void); -std::size_t APS5_VABI sceRazorCpuGetDataTagStorageSize(std::uint32_t tagCount); -int APS5_VABI sceRazorCpuTagArray(const char* label, std::uint32_t category, void* array, std::size_t bufferSize, std::size_t elementSize); -int APS5_VABI sceRazorCpuTagBuffer(const char* label, std::uint32_t category, void* buffer, std::size_t bufferSize); -int APS5_VABI sceRazorCpuResizeTaggedBuffer(std::uint32_t category, void* buffer, std::size_t newBufferSize); -int APS5_VABI sceRazorCpuUnTagBuffer(std::uint32_t category, void* buffer); -int APS5_VABI sceRazorCpuBeginLogicalFileAccess(const char* filename, std::uint64_t tag, std::uint64_t size, std::uint8_t operation); -int APS5_VABI sceRazorCpuEndLogicalFileAccess(void); -} - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -38,9 +14,9 @@ void Require(bool value) { if (!value) std::abort(); } int main() { Require(sceRazorCpuIsCapturing() == 0); - Require(sceRazorCpuJobManagerDispatch(nullptr) == 0); - Require(sceRazorCpuJobManagerJob(nullptr) == 0); - Require(sceRazorCpuJobManagerSequence(nullptr) == 0); + Require(sceRazorCpuJobManagerDispatch(nullptr, 1, 2, 3, 4, 5, 6) == 0); + Require(sceRazorCpuJobManagerJob(1, 2, 3, 16) == 0); + Require(sceRazorCpuJobManagerSequence(nullptr, 1, 2, 3) == 0); Require(sceRazorCpuNamedSync("frame") == 0); Require(sceRazorCpuPushMarkerStatic("outer", 0x80ffffffu, 2) == 0); Require(sceRazorCpuPushMarkerStatic("inner", 0x80ffffffu, 2) == 0); @@ -52,7 +28,7 @@ int main() { Require(sceRazorCpuIsCapturing() == 0); std::uint64_t timeSpentInFlush = 0x123456789abcdef0ull; Require(sceRazorCpuFlushOccurred(&timeSpentInFlush) == 0); - Require(timeSpentInFlush == 0); + Require(timeSpentInFlush == 0x123456789abcdef0ull); Require(sceRazorCpuFlushOccurred(nullptr) == 0); Require(sceRazorCpuPlotValue("read time (ms)", 1.5f) == 0); Require(sceRazorCpuWriteBookmark("read timeout", "lba=0x10") == 0); @@ -91,6 +67,7 @@ int main() { bool threw = false; try { sceRazorCpuGetDataTagStorageSize(16); + } catch (const std::runtime_error&) { threw = true; } diff --git a/core/libs/tests/GuestReallocalign.cpp b/core/libs/tests/GuestReallocalign.cpp index fded34f311..ff924916d5 100644 --- a/core/libs/tests/GuestReallocalign.cpp +++ b/core/libs/tests/GuestReallocalign.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,11 +6,6 @@ #include #include -extern "C" { -void* APS5_VABI reallocalign_nid_postfix(void*, std::size_t, std::size_t); -int* APS5_VABI __error_nid_postfix(); -} - namespace { void Require(bool value) { @@ -30,12 +26,28 @@ int main() { auto* shrunk = static_cast(reallocalign_nid_postfix(grown, 16, 16)); Require(shrunk != nullptr && (reinterpret_cast(shrunk) % 16) == 0); for (int index = 0; index < 16; ++index) Require(shrunk[index] == 0x5a); - *__error_nid_postfix() = 0; - Require(reallocalign_nid_postfix(shrunk, 16, 0) == nullptr && *__error_nid_postfix() == 22); - *__error_nid_postfix() = 0; - Require(reallocalign_nid_postfix(shrunk, 16, 3) == nullptr && *__error_nid_postfix() == 22); - *__error_nid_postfix() = 0; - Require(reallocalign_nid_postfix(shrunk, std::numeric_limits::max(), 16) == nullptr && *__error_nid_postfix() == 12); + shrunk = static_cast(reallocalign_nid_postfix(shrunk, 16, 0)); + Require(shrunk != nullptr && (reinterpret_cast(shrunk) % 32) == 0); for (int index = 0; index < 16; ++index) Require(shrunk[index] == 0x5a); - Require(reallocalign_nid_postfix(shrunk, 0, 16) == nullptr); + shrunk = static_cast(reallocalign_nid_postfix(shrunk, 40, 100)); + Require(shrunk != nullptr && (reinterpret_cast(shrunk) % 128) == 0); + for (int index = 0; index < 16; ++index) Require(shrunk[index] == 0x5a); + constexpr std::size_t max = std::numeric_limits::max(); + *__error_nid_postfix() = 77; + Require(reallocalign_nid_postfix(shrunk, max, 16) == nullptr && *__error_nid_postfix() == 77); + Require(reallocalign_nid_postfix(shrunk, max - 0x1008f, 16) == nullptr && *__error_nid_postfix() == 22); + *__error_nid_postfix() = 77; + Require(reallocalign_nid_postfix(shrunk, max - 0x110ae, 4096) == nullptr && *__error_nid_postfix() == 77); + Require(reallocalign_nid_postfix(shrunk, max - 0x110af, 4096) == nullptr && *__error_nid_postfix() == 22); + *__error_nid_postfix() = 77; + Require(reallocalign_nid_postfix(shrunk, 16, std::size_t{1} << 63) == nullptr && *__error_nid_postfix() == 77); + for (int index = 0; index < 16; ++index) Require(shrunk[index] == 0x5a); + Require(reallocalign_nid_postfix(shrunk, 0, 16) == shrunk && malloc_usable_size_nid_postfix(shrunk) == 0); + free_nid_postfix(shrunk); + + void* empty = reallocalign_nid_postfix(nullptr, 0, 0); + Require(empty != nullptr); + free_nid_postfix(empty); + + return 0; } diff --git a/core/libs/tests/GuestRealpath.cpp b/core/libs/tests/GuestRealpath.cpp index faae73050f..52240b0a7f 100644 --- a/core/libs/tests/GuestRealpath.cpp +++ b/core/libs/tests/GuestRealpath.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -11,13 +12,6 @@ #include #include -extern "C" { -char* APS5_VABI realpath_nid_postfix(const char*, char*); -int APS5_VABI chdir_nid_postfix(const char*); -void APS5_VABI free_nid_postfix(void*); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } static bool Resolves(const std::string& path, const std::string& expected) { diff --git a/core/libs/tests/GuestRecordLockOwners.cpp b/core/libs/tests/GuestRecordLockOwners.cpp index cbffd82062..416bb02141 100644 --- a/core/libs/tests/GuestRecordLockOwners.cpp +++ b/core/libs/tests/GuestRecordLockOwners.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -12,13 +15,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode); -int APS5_VABI sceKernelClose(int d); -int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...); -int* APS5_VABI __error_nid_postfix(); -} - namespace { void Require(bool value, int line) { diff --git a/core/libs/tests/GuestRefusedConnect.cpp b/core/libs/tests/GuestRefusedConnect.cpp index 409f8ad97c..b78fccbe41 100644 --- a/core/libs/tests/GuestRefusedConnect.cpp +++ b/core/libs/tests/GuestRefusedConnect.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,22 +8,6 @@ #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceNetInit_nid_postfix(); -int APS5_VABI sceNetSocket(const char*, int, int, int); -int APS5_VABI sceNetConnect(int, const void*, std::uint32_t); -int APS5_VABI sceNetSocketClose(int); -int* APS5_VABI sceNetErrnoLoc(); -} - namespace { constexpr int GuestInet = 2; diff --git a/core/libs/tests/GuestRegMgr.cpp b/core/libs/tests/GuestRegMgr.cpp index dfdf074535..ec355a78f7 100644 --- a/core/libs/tests/GuestRegMgr.cpp +++ b/core/libs/tests/GuestRegMgr.cpp @@ -1,13 +1,11 @@ +#include "prx/libSceRegMgr/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include -extern "C" { -std::int32_t APS5_VABI sceRegMgrNonSysGetInt(std::int64_t key, std::int32_t unk, std::int32_t* data); -} - static void Require(bool value) { if (!value) std::abort(); } template @@ -24,4 +22,26 @@ int main() { std::int32_t value = 0; Require(ReportsNotImplemented("sceRegMgrNonSysGetInt", [&] { sceRegMgrNonSysGetInt(0, 0, &value); })); Require(ReportsNotImplemented("sceRegMgrNonSysGetInt", [&] { sceRegMgrNonSysGetInt(-1, 1, nullptr); })); + + int32_t intVal = 0; + char strBuf[64] = {}; + uint8_t binBuf[32] = {}; + + Require(sceRegMgrGetInt(0x1000, nullptr) == static_cast(0x80820001)); + Require(sceRegMgrGetStr(0x1001, nullptr, 10) == static_cast(0x80820001)); + Require(sceRegMgrGetStr(0x1001, strBuf, -1) == static_cast(0x80820001)); + Require(sceRegMgrGetBin(0x1002, nullptr, 10) == static_cast(0x80820001)); + Require(sceRegMgrGetBin(0x1002, binBuf, -1) == static_cast(0x80820001)); + + intVal = 0x55AA; + Require(sceRegMgrGetInt(888888, &intVal) == static_cast(0x80820002)); + Require(intVal == 0x55AA); + std::strcpy(strBuf, "CANARY"); + Require(sceRegMgrGetStr(888888, strBuf, sizeof(strBuf)) == static_cast(0x80820002)); + Require(std::strcmp(strBuf, "CANARY") == 0); + binBuf[0] = 0x77; + Require(sceRegMgrGetBin(888888, binBuf, sizeof(binBuf)) == static_cast(0x80820002)); + Require(binBuf[0] == 0x77); + + return 0; } diff --git a/core/libs/tests/GuestRenameDot.cpp b/core/libs/tests/GuestRenameDot.cpp index ed44b81e35..83260cf108 100644 --- a/core/libs/tests/GuestRenameDot.cpp +++ b/core/libs/tests/GuestRenameDot.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -7,15 +9,6 @@ #include #endif -extern "C" { -int APS5_VABI mkdir_nid_postfix(const char*, int); -int APS5_VABI rmdir_nid_postfix(const char*); -int APS5_VABI rename_nid_postfix(const char*, const char*); -int APS5_VABI sceKernelRename(const char*, const char*); -int APS5_VABI stat_nid_postfix(const char*, void*); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int ErrorNoEntry = 2; @@ -32,6 +25,7 @@ void Cleanup() { rmdir_nid_postfix(Source); rmdir_nid_postfix(Target); rmdir_nid_postfix(Moved); + std::remove(PlainFile); std::remove(Loop); } @@ -47,7 +41,7 @@ bool Fails(long result, int error) { return result == -1 && *__error_nid_postfix bool Exists(const char* path) { unsigned char status[256]; - return stat_nid_postfix(path, status) == 0; + return stat_nid_postfix(path, reinterpret_cast(status)) == 0; } } diff --git a/core/libs/tests/GuestRenderingWait.cpp b/core/libs/tests/GuestRenderingWait.cpp index a17c039713..352b8b1d1d 100644 --- a/core/libs/tests/GuestRenderingWait.cpp +++ b/core/libs/tests/GuestRenderingWait.cpp @@ -8,14 +8,7 @@ #include #include #include -extern "C" std::uint32_t APS5_VABI sceAgcDriverGetWaitRenderingPacketSizeInDwords(); -extern "C" std::uint32_t APS5_VABI sceAgcDriverGetSetFlipPacketSizeInDwords(); -extern "C" std::uint32_t APS5_VABI sceAgcDriverGetSetWorkloadsActivePacketSize(); -extern "C" std::uint32_t APS5_VABI sceAgcDriverGetSetWorkloadCompletePacketSize(); -extern "C" std::uint32_t APS5_VABI sceAgcDriverGetSetWorkloadStreamInactivePacketSize(); -extern "C" std::uint32_t APS5_VABI sceAgcDriverNopDataGetPacketSize(std::uint32_t); -extern "C" std::uint32_t APS5_VABI sceAgcDriverUserDataGetPacketSize(std::uint32_t); -extern "C" std::uint32_t APS5_VABI sceAgcDriverWaitUntilSafeForRendering(std::uint32_t**, std::uint32_t, std::uint32_t, std::uint32_t, int); + static void Require(bool value) { if (!value) std::abort(); } class Output final : public AgcDriver::IVideoOutput, public AgcDriver::IRenderingWait, public AgcDriver::IFlipRequest, public std::enable_shared_from_this { diff --git a/core/libs/tests/GuestResolver.cpp b/core/libs/tests/GuestResolver.cpp index 20f02e1d16..3773c62df8 100644 --- a/core/libs/tests/GuestResolver.cpp +++ b/core/libs/tests/GuestResolver.cpp @@ -1,3 +1,5 @@ +#include "prx/libScePosixForWebKit/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -16,14 +18,6 @@ #include #include -extern "C" { -GuestResolver::HostEntry* APS5_VABI gethostbyname_nid_postfix(const char*); -int* APS5_VABI sceNetErrnoLoc(); -int APS5_VABI getaddrinfo_nid_postfix(const char*, const char*, const GuestResolver::AddressInfo*, GuestResolver::AddressInfo**); -void APS5_VABI freeaddrinfo_nid_postfix(GuestResolver::AddressInfo*); -int APS5_VABI getnameinfo_nid_postfix(const void*, std::uint32_t, char*, std::uint32_t, char*, std::uint32_t, int); -const char* APS5_VABI gai_strerror_nid_postfix(int); -} void Require(bool condition, std::source_location location = std::source_location::current()) { if (!condition) { std::fprintf(stderr, "Resolver check failed at line %u\n", location.line()); @@ -135,6 +129,7 @@ int main() { Require(std::strcmp(host, numeric) == 0 && std::strcmp(service, "27910") == 0); } freeaddrinfo_nid_postfix(result); + } GuestResolver::AddressInfo* result = reinterpret_cast(1); hints.family = 1234; @@ -145,4 +140,5 @@ int main() { hints.flags = 12; Require(getaddrinfo_nid_postfix("invalid-numeric-address", "1", &hints, &result) == 8 && !result); freeaddrinfo_nid_postfix(nullptr); + } diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index a5585f843d..8d9b302b49 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -1,73 +1,21 @@ +#include "prx/libSceRtc/include/Rtc.hpp" #include "SceTypes.hpp" +#include "ProcessParametersFixture.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include +#include #include #include #include #include #include +#include +#include +#include +#include #include #include - -extern "C" { -int APS5_VABI sceRtcCheckValid(const RtcDateTime*); -int APS5_VABI sceRtcIsLeapYear(int); -int APS5_VABI sceRtcGetDaysInMonth(int, int); -int APS5_VABI sceRtcGetDayOfWeek(int, int, int); -int APS5_VABI sceRtcGetTickResolution(void); -int APS5_VABI sceRtcGetTick(const RtcDateTime*, RtcTick*); -int APS5_VABI sceRtcSetTick(RtcDateTime*, const RtcTick*); -int APS5_VABI sceRtcGetCurrentTick(RtcTick*); -int APS5_VABI sceRtcGetCurrentNetworkTick(RtcTick*); -int APS5_VABI sceRtcGetCurrentClock(RtcDateTime*, int); -int APS5_VABI sceRtcGetCurrentClockLocalTime(RtcDateTime*); -int APS5_VABI sceRtcConvertUtcToLocalTime(const RtcTick*, RtcTick*); -int APS5_VABI sceRtcConvertLocalTimeToUtc(const RtcTick*, RtcTick*); -int APS5_VABI sceRtcGetTime_t(const RtcDateTime*, std::int64_t*); -int APS5_VABI sceRtcSetTime_t(RtcDateTime*, std::int64_t); -int APS5_VABI sceRtcGetWin32FileTime(const RtcDateTime*, std::uint64_t*); -int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime*, std::uint64_t); -int APS5_VABI sceRtcGetDosTime(const RtcDateTime*, std::uint32_t*); -int APS5_VABI sceRtcSetDosTime(RtcDateTime*, std::uint32_t); -int APS5_VABI sceRtcGetYear(const RtcDateTime*); -int APS5_VABI sceRtcSetYear(RtcDateTime*, int); -int APS5_VABI sceRtcGetMonth(const RtcDateTime*); -int APS5_VABI sceRtcSetMonth(RtcDateTime*, int); -int APS5_VABI sceRtcGetDay(const RtcDateTime*); -int APS5_VABI sceRtcSetDay(RtcDateTime*, int); -int APS5_VABI sceRtcGetHour(const RtcDateTime*); -int APS5_VABI sceRtcSetHour(RtcDateTime*, int); -int APS5_VABI sceRtcGetMinute(const RtcDateTime*); -int APS5_VABI sceRtcSetMinute(RtcDateTime*, int); -int APS5_VABI sceRtcGetSecond(const RtcDateTime*); -int APS5_VABI sceRtcSetSecond(RtcDateTime*, int); -int APS5_VABI sceRtcGetMicrosecond(const RtcDateTime*); -int APS5_VABI sceRtcSetMicrosecond(RtcDateTime*, int); -int APS5_VABI sceRtcFormatRFC3339(char*, const RtcTick*, int); -int APS5_VABI sceRtcParseRFC3339(RtcTick*, const char*); -int APS5_VABI sceRtcParseDateTime(RtcTick*, const char*); -int APS5_VABI sceRtcTickAddTicks(RtcTick*, const RtcTick*, std::int64_t); -int APS5_VABI sceRtcTickAddMicroseconds(RtcTick*, const RtcTick*, std::int64_t); -int APS5_VABI sceRtcTickAddSeconds(RtcTick*, const RtcTick*, std::int64_t); -int APS5_VABI sceRtcTickAddMinutes(RtcTick*, const RtcTick*, std::int64_t); -int APS5_VABI sceRtcTickAddHours(RtcTick*, const RtcTick*, std::int32_t); -int APS5_VABI sceRtcTickAddDays(RtcTick*, const RtcTick*, std::int32_t); -int APS5_VABI sceRtcTickAddWeeks(RtcTick*, const RtcTick*, std::int32_t); -int APS5_VABI sceRtcTickAddMonths(RtcTick*, const RtcTick*, std::int32_t); -int APS5_VABI sceRtcTickAddYears(RtcTick*, const RtcTick*, std::int16_t); -int APS5_VABI sceRtcFormatRFC3339LocalTime(char*, const RtcTick*); -int APS5_VABI sceRtcFormatRFC2822(char*, const RtcTick*, int); -int APS5_VABI sceRtcFormatRFC2822LocalTime(char*, const RtcTick*); -int APS5_VABI sceRtcFormatRFC3339Precise(char*, const RtcTick*, int, int); -int APS5_VABI sceRtcFormatRFC3339PreciseLocalTime(char*, const RtcTick*, int); -int APS5_VABI sceRtcCompareTick(const RtcTick*, const RtcTick*); -int APS5_VABI sceRtcGetCurrentAdNetworkTick(RtcTick*); -int APS5_VABI sceRtcGetCurrentRawNetworkTick(RtcTick*); -int APS5_VABI sceRtcGetCurrentDebugNetworkTick(RtcTick*); -int APS5_VABI sceRtcSetCurrentTick(const RtcTick*); -int APS5_VABI sceRtcSetCurrentNetworkTick(const RtcTick*); -int APS5_VABI sceRtcSetCurrentAdNetworkTick(const RtcTick*); -int APS5_VABI sceRtcSetCurrentDebugNetworkTick(const RtcTick*); -} +#include static void Require(bool value) { if (!value) std::abort(); } @@ -111,7 +59,40 @@ static int CallAdd(Add function, RtcTick* destination, const RtcTick* source, st std::abort(); } -int main() { +static void SetCompiledSdkVersion(std::byte* process, std::uint32_t version) { + std::memcpy(process + 16, &version, sizeof(version)); +} + +static void EnterTitleDirectory(const char* titleId) { + const auto directory = std::filesystem::temp_directory_path() / ("rtc-test-" + std::to_string(std::random_device{}())); + std::filesystem::create_directories(directory / "app0" / "sce_sys"); + std::filesystem::current_path(directory); + if (titleId == nullptr) return; + struct Entry { std::uint16_t key, format; std::uint32_t length, maximum, offset; }; + const std::uint32_t header[5] = {0x46535000, 0x0101, 52, 72, 2}; + const Entry entries[2] = {{0, 0x0204, 8, 8, 0}, {6, 0x0204, 10, 12, 8}}; + std::ofstream file("app0/sce_sys/param.sfo", std::ios::binary); + file.write(reinterpret_cast(header), sizeof(header)); + file.write(reinterpret_cast(entries), sizeof(entries)); + file << std::string("TITLE\0TITLE_ID\0\0\0\0\0\0", 20) << std::string("Example\0", 8) << titleId << std::string("\0\0\0", 3); +} + +int main(int argc, char** argv) { + static std::array process{}; + Require(ProcessParametersFixture::Install(process.data(), process.size())); + SetCompiledSdkVersion(process.data(), 0x01000051); + if (argc == 2 && std::string_view(argv[1]) == "ps5-title") { + EnterTitleDirectory(nullptr); + RtcDateTime untouched{0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaaaaaau}; + RtcDateTime converted = untouched; + Require(sceRtcGetDayOfWeek(10000, 1, 1) == static_cast(0x80B50008) && sceRtcGetDayOfWeek(0, 13, 1) == static_cast(0x80B50009)); + Require(sceRtcSetTime_t(&converted, -1) == static_cast(0x80B50003) && Equal(converted, untouched)); + char text[64]; + RtcTick tick{std::numeric_limits::max()}; + Require(sceRtcFormatRFC3339(text, &tick, 1) == static_cast(0x80B50003)); + return 0; + } + EnterTitleDirectory("CUSA00000"); constexpr int invalidPointer = static_cast(0x80B50002); constexpr int invalidValue = static_cast(0x80B50003); constexpr int badParse = static_cast(0x80B50007); @@ -134,6 +115,7 @@ int main() { Require(sceRtcGetDaysInMonth(10000, 1) == 31); Require(sceRtcGetDaysInMonth(9999, 1) == 31); Require(sceRtcGetDayOfWeek(1, 1, 1) == 1 && sceRtcGetDayOfWeek(2026, 9, 26) == 6); + Require(sceRtcGetDayOfWeek(10000, 1, 1) == 6); Require(sceRtcGetDayOfWeek(2023, 2, 29) == invalidDay); RtcDateTime leapDay{2024, 2, 29, 12, 34, 56, 789000}; @@ -156,7 +138,7 @@ int main() { tick.tick = maxTick; Require(sceRtcSetTick(&converted, &tick) == 0 && Equal(converted, RtcDateTime{9999, 12, 31, 23, 59, 59, 999999})); tick.tick = maxTick + 1; - Require(sceRtcSetTick(&converted, &tick) == invalidValue); + Require(sceRtcSetTick(&converted, &tick) == 0 && Equal(converted, RtcDateTime{10000, 1, 1, 0, 0, 0, 0})); Require(sceRtcGetCurrentClock(nullptr, 0) == invalidPointer); Require(sceRtcGetCurrentClock(&converted, 0) == 0 && sceRtcCheckValid(&converted) == 0); @@ -171,10 +153,24 @@ int main() { RtcDateTime millennium{2000, 1, 1, 0, 0, 0, 0}; Require(sceRtcGetTime_t(&millennium, &seconds) == 0 && seconds == 946684800); Require(sceRtcSetTime_t(&converted, 946684800) == 0 && Equal(converted, millennium)); - Require(sceRtcSetTime_t(&converted, -1) == invalidValue); + Require(sceRtcSetTime_t(&converted, -1) == 0 && Equal(converted, RtcDateTime{2106, 2, 7, 6, 28, 15, 0})); + Require(sceRtcSetTime_t(&converted, 0x100000001ll) == 0 && Equal(converted, RtcDateTime{1970, 1, 1, 0, 0, 1, 0})); + Require(sceRtcSetTime_t(&converted, -0x80000000ll) == 0 && Equal(converted, RtcDateTime{2038, 1, 19, 3, 14, 8, 0})); + const RtcDateTime past2106{2106, 2, 7, 6, 28, 16, 0}; + Require(sceRtcGetTime_t(&past2106, &seconds) == 0 && seconds == 0x100000000ll); + const RtcDateTime beforeEpoch{1969, 12, 31, 23, 59, 59, 0}; + seconds = -1; + Require(sceRtcGetTime_t(&beforeEpoch, &seconds) == invalidYear && seconds == 0); + const RtcDateTime badMonth{1969, 13, 1, 0, 0, 0, 0}; + seconds = -1; + Require(sceRtcGetTime_t(&badMonth, &seconds) == invalidMonth && seconds == -1); std::uint64_t fileTime = 0; Require(sceRtcGetWin32FileTime(&epoch, &fileTime) == 0 && fileTime == 116444736000000000ull); Require(sceRtcSetWin32FileTime(&converted, 116444736000000000ull) == 0 && Equal(converted, epoch)); + const RtcDateTime before1601{1600, 12, 31, 23, 59, 59, 0}; + fileTime = 1; + Require(sceRtcGetWin32FileTime(&before1601, &fileTime) == invalidYear && fileTime == 0); + Require(sceRtcSetWin32FileTime(&converted, std::numeric_limits::max()) == 0 && Equal(converted, RtcDateTime{60056, 5, 28, 5, 36, 10, 955161})); std::uint32_t dosTime = 0xffffffffu; const RtcDateTime dosDate{2024, 2, 29, 12, 34, 57, 789000}; Require(sceRtcGetDosTime(&dosDate, &dosTime) == 0 && dosTime == 0x585d645cu); @@ -270,6 +266,22 @@ int main() { Require(sceRtcFormatRFC3339(text, &tick, offset) == invalidValue); Require(std::memcmp(text, original, sizeof(text)) == 0); } + RtcTick edge{maxTick + 1}; + Require(sceRtcFormatRFC3339(text, &edge, 0) == invalidYear); + edge.tick = std::numeric_limits::max(); + Require(sceRtcFormatRFC3339(text, &edge, 0) == invalidYear); + edge.tick = maxTick; + Require(sceRtcFormatRFC3339(text, &edge, 1) == invalidYear); + edge.tick = 59999999; + Require(sceRtcFormatRFC3339(text, &edge, -1) == invalidYear); + edge.tick = 60000000; + Require(sceRtcFormatRFC3339(text, &edge, -1) == 0 && std::strcmp(text, "0001-01-01T00:00:00.00-00:01") == 0); + edge.tick = std::numeric_limits::max(); + Require(sceRtcFormatRFC3339(text, &edge, 1) == 0 && std::strcmp(text, "0001-01-01T00:00:59.99+00:01") == 0); + edge.tick = 1ull << 63; + Require(sceRtcFormatRFC3339(text, &edge, -1) == invalidYear); + edge.tick = maxTick + 1; + Require(sceRtcFormatRFC3339(text, &edge, -1) == 0 && std::strcmp(text, "9999-12-31T23:59:00.00-00:01") == 0); Require(sceRtcFormatRFC3339(nullptr, &tick, 1440) == invalidPointer); Require(sceRtcFormatRFC3339(text, nullptr, 1440) == invalidValue); { @@ -438,6 +450,8 @@ int main() { const char* name; std::int64_t minutes; } additionalZones[] = { + {"LIGT", 600}, {"AWSST", 540}, {"CETDST", 120}, + {"DNT", 60}, {"NOR", 60}, {"SET", 60}, {"NFT", -150}, {"SWT", 60}, {"MEZ", 60}, {"WETDST", 60}, {"METDST", 120}, {"EETDST", 180}, {"AESST", 660}, {"ACSST", 630}, {"CAST", 570}, {"CADT", 630}, {"SAST", 570}, {"SADT", 630}, {"WST", 480}, {"WDT", 540}, }; @@ -463,6 +477,20 @@ int main() { Require(tick.tick == at(2000, 3, 1, 8, 9, 10) - static_cast(zone.minutes * 60000000)); } + const struct { + const char* name; + std::int64_t minutes; + } secondLetterZones[] = { + {"MT", 0}, {"mt", 510}, {"IT", 0}, {"it", 210}, + {"BT", 0}, {"bt", 180}, {"NT", 0}, {"nt", -660}, + }; + for (const auto& zone : secondLetterZones) { + char text[80]; + std::snprintf(text, sizeof(text), "Thu, 29 Feb 2024 12:34:56 %s", zone.name); + Require(sceRtcParseDateTime(&tick, text) == 0); + Require(tick.tick == leapDayTick - 789000ull - static_cast(zone.minutes * 60000000)); + } + RtcTick source{leapDayTick}; RtcTick result{}; Require(sceRtcTickAddSeconds(&result, &source, 3600) == 0 && result.tick == leapDayTick + 3600000000ull); @@ -734,10 +762,11 @@ int main() { RtcTick now{}; Require(sceRtcGetCurrentTick(&now) == 0 && now.tick > leapDayTick); + const RtcTick fixed{leapDayTick}; RtcTick local{}; RtcTick back{}; - Require(sceRtcConvertUtcToLocalTime(&now, &local) == 0); - Require(sceRtcConvertLocalTimeToUtc(&local, &back) == 0 && back.tick == now.tick); + Require(sceRtcConvertUtcToLocalTime(&fixed, &local) == 0); + Require(sceRtcConvertLocalTimeToUtc(&local, &back) == 0 && back.tick == fixed.tick); char rfc2822[32]; tick.tick = leapDayTick; @@ -759,7 +788,19 @@ int main() { Require(sceRtcFormatRFC2822(rfc2822, &tick, -1) == invalidValue); tick.tick = maxTick; Require(sceRtcFormatRFC2822(rfc2822, &tick, 0) == 0 && std::strcmp(rfc2822, "Fri, 31 Dec 9999 23:59:59 +0000") == 0); + Require(sceRtcFormatRFC2822(rfc2822, &tick, 1) == invalidYear); + tick.tick = maxTick + 1; + Require(sceRtcFormatRFC2822(rfc2822, &tick, 0) == invalidYear); + tick.tick = std::numeric_limits::max(); + Require(sceRtcFormatRFC2822(rfc2822, &tick, 0) == invalidValue); Require(sceRtcFormatRFC2822(rfc2822, &tick, 1) == invalidValue); + tick.tick = 0ull - 60000000ull; + Require(sceRtcFormatRFC2822(rfc2822, &tick, 1) == invalidValue); + tick.tick = 1ull << 63; + Require(sceRtcFormatRFC2822(rfc2822, &tick, 0) == invalidValue); + Require(sceRtcFormatRFC2822(rfc2822, &tick, -1) == invalidYear); + tick.tick = maxTick + 1; + Require(sceRtcFormatRFC2822(rfc2822, &tick, -1) == 0 && std::strcmp(rfc2822, "Fri, 31 Dec 9999 23:59:00 -0001") == 0); tick.tick = leapDayTick; for (int offset : {1440, -1440, std::numeric_limits::min(), std::numeric_limits::max()}) { std::memset(rfc2822, 'x', sizeof(rfc2822)); @@ -891,4 +932,40 @@ int main() { Require(sceRtcGetCurrentDebugNetworkTick(¤t) == 0 && current.tick >= debugClock.tick && current.tick < debugClock.tick + 1000000); Require(sceRtcGetCurrentRawNetworkTick(¤t) == 0 && current.tick > unixEpochTick); } + SetCompiledSdkVersion(process.data(), 0x02ffffff); + Require(sceRtcGetDayOfWeek(0, 13, 1) == invalidYear && sceRtcGetDayOfWeek(10000, 1, 1) == 6); + SetCompiledSdkVersion(process.data(), 0x03000000); + Require(sceRtcGetDayOfWeek(0, 13, 1) == invalidMonth && sceRtcGetDayOfWeek(10000, 1, 1) == invalidYear); + SetCompiledSdkVersion(process.data(), 0x05050001); + Require(sceRtcGetDayOfWeek(0, 13, 1) == invalidMonth && sceRtcGetDayOfWeek(10000, 13, 1) == invalidMonth && sceRtcGetDayOfWeek(2024, 0, 1) == invalidMonth); + Require(sceRtcGetDayOfWeek(0, 1, 0) == invalidYear && sceRtcGetDayOfWeek(10000, 2, 30) == invalidYear && sceRtcGetDayOfWeek(65536, 1, 1) == invalidYear); + Require(sceRtcGetDayOfWeek(2024, 2, 30) == invalidDay && sceRtcGetDayOfWeek(9999, 12, 31) == 5 && sceRtcGetDayOfWeek(1, 1, 1) == 1); + Require(sceRtcGetDaysInMonth(0, 13) == invalidYear && sceRtcGetDaysInMonth(10000, 1) == 31 && sceRtcIsLeapYear(10000) == 1); + RtcDateTime sentinel{0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaaaaaau}; + RtcDateTime ranged = sentinel; + Require(sceRtcSetTime_t(&ranged, -1) == invalidValue && Equal(ranged, sentinel)); + Require(sceRtcSetTime_t(&ranged, std::numeric_limits::min()) == invalidValue && Equal(ranged, sentinel)); + Require(sceRtcSetTime_t(&ranged, 4294967296ll) == 0 && Equal(ranged, RtcDateTime{2106, 2, 7, 6, 28, 16, 0})); + Require(sceRtcSetTime_t(&ranged, 253402300800ll) == 0 && Equal(ranged, RtcDateTime{10000, 1, 1, 0, 0, 0, 0})); + Require(sceRtcSetTime_t(&ranged, 18446744073709ll) == 0 && Equal(ranged, RtcDateTime{1969, 12, 31, 23, 59, 59, 448384})); + Require(sceRtcSetTime_t(&ranged, std::numeric_limits::max()) == 0 && Equal(ranged, RtcDateTime{1969, 12, 31, 23, 59, 59, 0})); + char ranges[64]; + RtcTick shifted{0}; + Require(sceRtcFormatRFC3339(ranges, &shifted, -1) == invalidValue); + shifted.tick = 59999999; + Require(sceRtcFormatRFC3339(ranges, &shifted, -1) == invalidValue); + shifted.tick = 60000000; + Require(sceRtcFormatRFC3339(ranges, &shifted, -1) == 0 && std::strcmp(ranges, "0001-01-01T00:00:00.00-00:01") == 0); + shifted.tick = std::numeric_limits::max(); + Require(sceRtcFormatRFC3339(ranges, &shifted, 1) == invalidValue && sceRtcFormatRFC3339(ranges, &shifted, 1439) == invalidValue); + shifted.tick = std::numeric_limits::max() - 60000000 + 1; + Require(sceRtcFormatRFC3339(ranges, &shifted, 1) == invalidValue); + shifted.tick = std::numeric_limits::max() - 60000000; + Require(sceRtcFormatRFC3339(ranges, &shifted, 1) == invalidYear); + shifted.tick = 1ull << 63; + Require(sceRtcFormatRFC3339(ranges, &shifted, 0) == invalidYear && sceRtcFormatRFC3339(ranges, &shifted, -1) == invalidYear); + shifted.tick = maxTick + 1; + Require(sceRtcFormatRFC3339(ranges, &shifted, -1) == 0 && std::strcmp(ranges, "9999-12-31T23:59:00.00-00:01") == 0); + Require(sceRtcInit() == 0); + Require(sceRtcEnd() == 0); } diff --git a/core/libs/tests/GuestRtprioThread.cpp b/core/libs/tests/GuestRtprioThread.cpp new file mode 100644 index 0000000000..0d44857120 --- /dev/null +++ b/core/libs/tests/GuestRtprioThread.cpp @@ -0,0 +1,100 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; +static constexpr int GUEST_SCHED_FIFO = 1; +static constexpr int GUEST_SCHED_RR = 3; +static constexpr int EXPLICIT_SCHED = 0; +static constexpr int RTP_LOOKUP = 0; +static constexpr int RTP_SET = 1; +static constexpr std::uint16_t RTP_PRIO_REALTIME = 2; +static constexpr std::uint16_t RTP_PRIO_FIFO = 10; +static constexpr int GUEST_ESRCH = 3; +static constexpr int GUEST_EFAULT = 14; + +static void Require(bool value) { if (!value) std::abort(); } + +struct Report { + int policy; + int priority; + std::atomic tid{0}; + GuestRtPrio lookup{7, 7}; + int lookupResult = -1; + std::atomic release{false}; +}; + +static void* APS5_VABI Entry(void* arg) { + auto* report = static_cast(arg); + report->lookupResult = rtprio_thread_nid_postfix(RTP_LOOKUP, 0, &report->lookup); + report->tid.store(scePthreadGetthreadid(), std::memory_order_release); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (!report->release.load(std::memory_order_acquire)) { + Require(std::chrono::steady_clock::now() < deadline); + std::this_thread::yield(); + } + return nullptr; +} + +static int LookupError(int function, int lwpid, GuestRtPrio* priority) { + *__error_nid_postfix() = 0; + Require(rtprio_thread_nid_postfix(function, lwpid, priority) == -1); + return *__error_nid_postfix(); +} + +static void CheckThread(int policy, int priority, std::uint16_t expectedType) { + Report report{policy, priority}; + PthreadAttr attr = nullptr; + KernelSchedParam param{priority}; + Require(scePthreadAttrInit(&attr) == SCE_OK); + Require(scePthreadAttrSetinheritsched(&attr, EXPLICIT_SCHED) == SCE_OK); + Require(scePthreadAttrSetschedpolicy(&attr, policy) == SCE_OK); + Require(scePthreadAttrSetschedparam(&attr, ¶m) == SCE_OK); + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, &attr, Entry, &report, "rtprio") == SCE_OK); + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (report.tid.load(std::memory_order_acquire) == 0) { + Require(std::chrono::steady_clock::now() < deadline); + std::this_thread::yield(); + } + Require(report.lookupResult == 0 && report.lookup.type == expectedType && report.lookup.prio == priority); + GuestRtPrio fromCreator{7, 7}; + Require(rtprio_thread_nid_postfix(RTP_LOOKUP, report.tid.load(), &fromCreator) == 0); + Require(fromCreator.type == expectedType && fromCreator.prio == priority); + bool threw = false; + try { rtprio_thread_nid_postfix(RTP_SET, report.tid.load(), &fromCreator); } catch (const std::exception&) { threw = true; } + Require(threw); + report.release.store(true, std::memory_order_release); + Require(scePthreadJoin(thread, nullptr) == SCE_OK); + Require(LookupError(RTP_LOOKUP, report.tid.load(), &fromCreator) == GUEST_ESRCH); +} + +int main() { + int policy = -1; + KernelSchedParam param{}; + Require(scePthreadGetschedparam(scePthreadSelf(), &policy, ¶m) == SCE_OK); + Require(policy == GUEST_SCHED_FIFO || policy == GUEST_SCHED_RR); + GuestRtPrio self{7, 7}; + Require(rtprio_thread_nid_postfix(RTP_LOOKUP, 0, &self) == 0); + Require(self.type == (policy == GUEST_SCHED_FIFO ? RTP_PRIO_FIFO : RTP_PRIO_REALTIME) && self.prio == param.sched_priority); + GuestRtPrio byId{7, 7}; + Require(rtprio_thread_nid_postfix(RTP_LOOKUP, scePthreadGetthreadid(), &byId) == 0); + Require(byId.type == self.type && byId.prio == self.prio); + CheckThread(GUEST_SCHED_FIFO, 512, RTP_PRIO_FIFO); + CheckThread(GUEST_SCHED_RR, 600, RTP_PRIO_REALTIME); + CheckThread(GUEST_SCHED_FIFO, 256, RTP_PRIO_FIFO); + Require(LookupError(2, 0, &self) == GUEST_ESRCH); + Require(LookupError(RTP_LOOKUP, 0, nullptr) == GUEST_EFAULT); + Require(LookupError(RTP_LOOKUP, 1, &self) == GUEST_ESRCH); + Require(LookupError(RTP_LOOKUP, 999999, &self) == GUEST_ESRCH); + bool threw = false; + try { rtprio_thread_nid_postfix(RTP_SET, 0, &self); } catch (const std::exception&) { threw = true; } + Require(threw); +} diff --git a/core/libs/tests/GuestRudpContextLifecycle.cpp b/core/libs/tests/GuestRudpContextLifecycle.cpp index c3045181fb..766e84b8dd 100644 --- a/core/libs/tests/GuestRudpContextLifecycle.cpp +++ b/core/libs/tests/GuestRudpContextLifecycle.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceRudp/include/Api.hpp" #include "SceTypes.hpp" #include @@ -10,21 +11,7 @@ #include extern "C" { -using GuestRudpEventHandler = void(APS5_VABI*)(int, int, int, void*); -int APS5_VABI sceRudpInit_nid_postfix(void*, int); -int APS5_VABI sceRudpEnd(); -int APS5_VABI sceRudpActivate(); -int APS5_VABI sceRudpEnableInternalIOThread(std::uint32_t, std::uint32_t); -int APS5_VABI sceRudpSetEventHandler(GuestRudpEventHandler, void*); -int APS5_VABI sceRudpCreateContext(GuestRudpEventHandler, void*, int*); -int APS5_VABI sceRudpBind(int, std::uint16_t, int, std::uint16_t); -int APS5_VABI sceRudpSetOption(int, int, const void*, std::uint32_t); -int APS5_VABI sceRudpGetOption(int, int, void*, std::uint32_t); -int APS5_VABI sceRudpInitiate(int, const void*, std::uint32_t, std::uint16_t); -int APS5_VABI sceRudpGetContextStatus(int, void*, std::uint32_t); -int APS5_VABI sceRudpWrite(int, const void*, std::uint32_t, int); -int APS5_VABI sceRudpRead(int, void*, std::uint32_t, int, void*); -int APS5_VABI sceRudpTerminate(int); + } static void Require(bool value) { if (!value) std::abort(); } @@ -78,6 +65,7 @@ static void CheckOptions(int context) { bool threw = false; try { sceRudpGetOption(context, option, &value, sizeof(value)); + } catch (const std::runtime_error&) { threw = true; } @@ -91,6 +79,7 @@ static void CheckOptions(int context) { threw = false; try { sceRudpGetOption(current, option, &value, sizeof(value)); + } catch (const std::runtime_error&) { threw = true; } @@ -113,7 +102,7 @@ int main() { constexpr int connReset = static_cast(0x80770009u); alignas(16) static unsigned char pool[0x10000]; - Require(sceRudpActivate() == 0); + Require(sceRudpActivate(-1, nullptr, 0) == static_cast(0x80770001)); Require(sceRudpEnableInternalIOThread(0, 0) == notInitialized); @@ -143,7 +132,7 @@ int main() { Require(sceRudpInit_nid_postfix(pool, sizeof(pool)) == 0); - Require(sceRudpActivate() == 0); + Require(sceRudpActivate(-1, nullptr, 0) == static_cast(0x80770004)); Require(sceRudpEnableInternalIOThread(0, 0) == 0); Require(sceRudpEnableInternalIOThread(1024, 1) == 0); @@ -170,6 +159,18 @@ int main() { Require(sceRudpCreateContext(Handler, nullptr, &ctx) == 0); Require(ctx >= 0); + Require(sceRudpActivate(ctx, nullptr, 0) == static_cast(0x80770004)); + unsigned char address[34]{}; + Require(sceRudpActivate(-1, address, sizeof(address)) == static_cast(0x80770004)); + bool activationUnsupported = false; + try { + sceRudpActivate(ctx, address, sizeof(address)); + + } catch (const std::runtime_error&) { + activationUnsupported = true; + } + Require(activationUnsupported); + Require(sceRudpBind(ctx, 0, 0, 0) == 0); Require(sceRudpSetOption(ctx, 0, nullptr, 0) == invalidOption); diff --git a/core/libs/tests/GuestRudpContextQueries.cpp b/core/libs/tests/GuestRudpContextQueries.cpp index 76374d4b86..a45bc97d8e 100644 --- a/core/libs/tests/GuestRudpContextQueries.cpp +++ b/core/libs/tests/GuestRudpContextQueries.cpp @@ -1,17 +1,11 @@ +#include "prx/libSceRudp/include/Api.hpp" #include "SceTypes.hpp" #include #include extern "C" { -using GuestRudpEventHandler = void(APS5_VABI*)(int, int, int, void*); -int APS5_VABI sceRudpInit_nid_postfix(void*, int); -int APS5_VABI sceRudpCreateContext(GuestRudpEventHandler, void*, int*); -int APS5_VABI sceRudpTerminate(int); -int APS5_VABI sceRudpEnd(); -int APS5_VABI sceRudpGetNumberOfPacketsToRead(int); -int APS5_VABI sceRudpGetSizeWritable(int); -int APS5_VABI sceRudpGetSizeReadable(int); + } static void Require(bool value) { diff --git a/core/libs/tests/GuestRudpPoll.cpp b/core/libs/tests/GuestRudpPoll.cpp index d797fad444..21274887b3 100644 --- a/core/libs/tests/GuestRudpPoll.cpp +++ b/core/libs/tests/GuestRudpPoll.cpp @@ -1,27 +1,18 @@ +#include "prx/libSceRudp/include/Api.hpp" #include "SceTypes.hpp" +#include #include #include +#include #include +#include #include #include #include extern "C" { -using GuestRudpEventHandler = void(APS5_VABI*)(int, int, int, void*); -struct GuestRudpPollEvent { - int ctxId; - std::uint16_t reqEvents; - std::uint16_t rtnEvents; -}; -int APS5_VABI sceRudpInit_nid_postfix(void*, int); -int APS5_VABI sceRudpCreateContext(GuestRudpEventHandler, void*, int*); -int APS5_VABI sceRudpTerminate(int); -int APS5_VABI sceRudpEnd(); -int APS5_VABI sceRudpPollCreate(std::uint32_t); -int APS5_VABI sceRudpPollControl(int, int, int, std::uint16_t); -int APS5_VABI sceRudpPollWait(int, GuestRudpPollEvent*, std::uint32_t, std::uint64_t); -int APS5_VABI sceRudpPollDestroy(int); + } static void Require(bool value) { @@ -43,7 +34,7 @@ int main() { constexpr std::uint16_t write = 0x0002; constexpr std::uint64_t forever = std::numeric_limits::max(); alignas(16) static std::array pool{}; - std::array events{}; + std::array events{}; const auto eventCount = static_cast(events.size()); Require(sceRudpPollCreate(1) == notInitialized); @@ -91,6 +82,23 @@ int main() { Require(sceRudpPollWait(poll, events.data(), eventCount, 20000) == 0); Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(20)); +#ifdef _WIN32 + for (const auto timeout : {100ULL, 1000ULL}) { + std::array samples{}; + for (auto& sample : samples) { + const auto before = std::chrono::steady_clock::now(); + Require(sceRudpPollWait(poll, events.data(), eventCount, timeout) == 0); + sample = std::chrono::steady_clock::now() - before; + Require(sample >= std::chrono::microseconds(timeout)); + } + std::sort(samples.begin(), samples.end()); + const auto median = samples[samples.size() / 2]; + std::fprintf(stderr, "sceRudpPollWait(%llu us) median: %lld ns\n", timeout, + static_cast(median.count())); + Require(median < std::chrono::milliseconds(12)); + } +#endif + Require(sceRudpTerminate(second) == 0); Require(sceRudpPollControl(poll, add, first, read) == 0); Require(sceRudpTerminate(first) == 0); @@ -100,6 +108,19 @@ int main() { Require(sceRudpPollControl(poll, add, third, read) == 0); Require(sceRudpPollControl(other, add, third, read) == 0); + const int finitePoll = sceRudpPollCreate(1); + Require(finitePoll > 0); + int finiteResult = 0; + std::thread finiteWaiter([&] { + finiteResult = sceRudpPollWait(finitePoll, events.data(), eventCount, 1000000); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + const auto destroyedAt = std::chrono::steady_clock::now(); + Require(sceRudpPollDestroy(finitePoll) == 0); + finiteWaiter.join(); + Require(finiteResult == invalidPoll); + Require(std::chrono::steady_clock::now() - destroyedAt < std::chrono::milliseconds(500)); + int destroyedResult = 0; std::thread destroyedWaiter([&] { destroyedResult = sceRudpPollWait(poll, events.data(), eventCount, forever); }); std::this_thread::sleep_for(std::chrono::milliseconds(20)); diff --git a/core/libs/tests/GuestRudpPump.cpp b/core/libs/tests/GuestRudpPump.cpp index d9e350ae23..26482bfeea 100644 --- a/core/libs/tests/GuestRudpPump.cpp +++ b/core/libs/tests/GuestRudpPump.cpp @@ -1,20 +1,12 @@ +#include "prx/libSceRudp/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include -extern "C" { -int APS5_VABI sceRudpInit_nid_postfix(void*, int); -int APS5_VABI sceRudpEnd(); -int APS5_VABI sceRudpProcessEvents(); -int APS5_VABI sceRudpNetFlush(); -int APS5_VABI sceRudpGetMaxSegmentSize(std::uint16_t*); -int APS5_VABI sceRudpNetReceived(); -int APS5_VABI sceRudpEnableInternalIOThread2(std::uint32_t, std::uint32_t, std::uint64_t); -} - static void Require(bool value, int line) { if (!value) { std::fprintf(stderr, "rudp check failed at line %d\n", line); @@ -27,9 +19,9 @@ int main() { constexpr int notInitialized = static_cast(0x80770001); constexpr int invalidArgument = static_cast(0x80770004); std::uint16_t segmentSize = 0x5a5a; - CHECK(sceRudpProcessEvents() == notInitialized); + CHECK(sceRudpProcessEvents(0) == notInitialized); CHECK(sceRudpNetFlush() == notInitialized); - CHECK(sceRudpNetReceived() == notInitialized); + CHECK(sceRudpNetReceived(0, nullptr, 0, nullptr, 0) == notInitialized); CHECK(sceRudpEnableInternalIOThread2(0, 0, 0) == notInitialized); CHECK(sceRudpGetMaxSegmentSize(&segmentSize) == notInitialized); CHECK(segmentSize == 0x5a5a); @@ -37,9 +29,9 @@ int main() { alignas(8) static unsigned char pool[0xC58]; CHECK(sceRudpInit_nid_postfix(pool, sizeof(pool)) == 0); - CHECK(sceRudpProcessEvents() == 0); + CHECK(sceRudpProcessEvents(0) == 0); CHECK(sceRudpNetFlush() == 0); - CHECK(sceRudpNetReceived() == 0); + CHECK(sceRudpNetReceived(0, nullptr, 0, nullptr, 0) == 0); CHECK(sceRudpEnableInternalIOThread2(0x4000, 0, 0x3) == 0); CHECK(sceRudpGetMaxSegmentSize(nullptr) == invalidArgument); CHECK(sceRudpGetMaxSegmentSize(&segmentSize) == 0); @@ -53,6 +45,6 @@ int main() { CHECK(sceRudpGetMaxSegmentSize(&segmentSize) == notInitialized); CHECK(segmentSize == 0x5a5a); - CHECK(sceRudpNetReceived() == notInitialized); + CHECK(sceRudpNetReceived(0, nullptr, 0, nullptr, 0) == notInitialized); return 0; } diff --git a/core/libs/tests/GuestRudpStatus.cpp b/core/libs/tests/GuestRudpStatus.cpp index ab845fffac..8aed90988d 100644 --- a/core/libs/tests/GuestRudpStatus.cpp +++ b/core/libs/tests/GuestRudpStatus.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceRudp/include/Api.hpp" #include "SceTypes.hpp" #include @@ -7,12 +8,7 @@ #include extern "C" { -using GuestRudpEventHandler = void(APS5_VABI*)(int, int, int, void*); -int APS5_VABI sceRudpInit_nid_postfix(void*, int); -int APS5_VABI sceRudpGetStatus(void*, std::size_t); -int APS5_VABI sceRudpCreateContext(GuestRudpEventHandler, void*, int*); -int APS5_VABI sceRudpTerminate(int); -int APS5_VABI sceRudpEnd(); + } static void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestRuneLocale.cpp b/core/libs/tests/GuestRuneLocale.cpp index e677153ff7..31a506c9f8 100644 --- a/core/libs/tests/GuestRuneLocale.cpp +++ b/core/libs/tests/GuestRuneLocale.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -11,43 +12,6 @@ extern "C" { extern const unsigned char _DefaultRuneLocale_nid_postfix[4224]; extern const unsigned char* _CurrentRuneLocale_nid_postfix; extern int __mb_sb_limit_nid_postfix; -int APS5_VABI isalpha_nid_postfix(int); -int APS5_VABI iscntrl_nid_postfix(int); -int APS5_VABI isdigit_nid_postfix(int); -int APS5_VABI isgraph_nid_postfix(int); -int APS5_VABI islower_nid_postfix(int); -int APS5_VABI isprint_nid_postfix(int); -int APS5_VABI ispunct_nid_postfix(int); -int APS5_VABI isspace_nid_postfix(int); -int APS5_VABI isupper_nid_postfix(int); -int APS5_VABI isxdigit_nid_postfix(int); -int APS5_VABI isblank_nid_postfix(int); -int APS5_VABI tolower_nid_postfix(int); -int APS5_VABI toupper_nid_postfix(int); -int APS5_VABI iswalnum_nid_postfix(std::uint32_t); -int APS5_VABI iswalpha_nid_postfix(std::uint32_t); -int APS5_VABI iswblank_nid_postfix(std::uint32_t); -int APS5_VABI iswcntrl_nid_postfix(std::uint32_t); -int APS5_VABI iswdigit_nid_postfix(std::uint32_t); -int APS5_VABI iswgraph_nid_postfix(std::uint32_t); -int APS5_VABI iswlower_nid_postfix(std::uint32_t); -int APS5_VABI iswprint_nid_postfix(std::uint32_t); -int APS5_VABI iswpunct_nid_postfix(std::uint32_t); -int APS5_VABI iswspace_nid_postfix(std::uint32_t); -int APS5_VABI iswupper_nid_postfix(std::uint32_t); -int APS5_VABI iswxdigit_nid_postfix(std::uint32_t); -int APS5_VABI iswctype_nid_postfix(std::uint32_t, std::uint64_t); -std::uint64_t APS5_VABI wctype_nid_postfix(const char*); -std::uint32_t APS5_VABI towlower_nid_postfix(std::uint32_t); -std::uint32_t APS5_VABI towupper_nid_postfix(std::uint32_t); -std::uint64_t APS5_VABI wctrans_nid_postfix(const char*); -std::uint32_t APS5_VABI towctrans_nid_postfix(std::uint32_t, std::uint64_t); -std::uint32_t APS5_VABI _Towlower_nid_postfix(std::uint32_t, const void*); -std::uint32_t APS5_VABI _Towupper_nid_postfix(std::uint32_t, const void*); -int APS5_VABI _Tolower_nid_postfix(int, const void*); -int APS5_VABI _Toupper_nid_postfix(int, const void*); -std::uint32_t APS5_VABI btowc_nid_postfix(int); -int APS5_VABI wctob_nid_postfix(std::uint32_t); } void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -120,6 +84,10 @@ int main() { } Require(entry.wide(0xffffffff) == 0 && iswctype_nid_postfix(0xffffffff, entry.mask) == 0); } + for (std::uint32_t c = 0; c < 300; ++c) { + Require((iswascii_nid_postfix(c) != 0) == (c <= 0x7f)); + } + Require(iswascii_nid_postfix(0xffffffff) == 0); Require(wctype_nid_postfix("ideogram") == 0x80000 && wctype_nid_postfix("special") == 0x100000); Require(wctype_nid_postfix("phonogram") == 0x200000 && wctype_nid_postfix("number") == 0x400000); Require(wctype_nid_postfix("rune") == 0xffffff00 && wctype_nid_postfix("") == 0 && wctype_nid_postfix("Alpha") == 0); @@ -130,9 +98,11 @@ int main() { } Require(towlower_nid_postfix(0xffffffff) == 0xffffffff && towupper_nid_postfix(0xffffffff) == 0xffffffff); Require(btowc_nid_postfix(-1) == 0xffffffff && btowc_nid_postfix(0) == 0); - Require(btowc_nid_postfix('A') == 'A' && btowc_nid_postfix(0xe9) == 0xe9 && btowc_nid_postfix(-23) == 0xe9); - Require(wctob_nid_postfix('A') == 'A' && wctob_nid_postfix(0xe9) == 0xe9 && wctob_nid_postfix(0) == 0); - Require(wctob_nid_postfix(0x100) == -1 && wctob_nid_postfix(0x20ac) == -1 && wctob_nid_postfix(0xffffffff) == -1); + Require(btowc_nid_postfix('A') == 'A' && btowc_nid_postfix(0x7f) == 0x7f && btowc_nid_postfix(0x80) == 0xffffffff); + Require(btowc_nid_postfix(0xe9) == 0xffffffff && btowc_nid_postfix(-23) == 0xffffffff && btowc_nid_postfix(0x141) == 'A'); + Require(wctob_nid_postfix('A') == 'A' && wctob_nid_postfix(0x7f) == 0x7f && wctob_nid_postfix(0x80) == -1 && wctob_nid_postfix(0) == 0); + Require(wctob_nid_postfix(0xe9) == -1 && wctob_nid_postfix(0x20ac) == -1 && wctob_nid_postfix(0xffffffff) == -1); + Require(wctob_nid_postfix(0x10041) == 'A' && wctob_nid_postfix(0x80000000) == 0); Require(wctrans_nid_postfix("tolower") == 1 && wctrans_nid_postfix("toupper") == 2); Require(wctrans_nid_postfix("") == 0 && wctrans_nid_postfix("TOLOWER") == 0 && wctrans_nid_postfix("tolowe") == 0); for (std::int64_t c = -300; c < 70000; ++c) { @@ -147,5 +117,15 @@ int main() { } errno = 0; Require(towctrans_nid_postfix('A', 0) == 'A' && towctrans_nid_postfix('a', 3) == 'a' && errno == 0); + for (int c = -300; c < 0x1200; ++c) { + const auto byte = static_cast(c); + errno = 0; + const std::uint32_t wide = btowc_nid_postfix(c); + const bool invalidLead = c != -1 && ((byte >= 0x80 && byte < 0xc0) || byte >= 0xf0); + Require(wide == (c != -1 && byte < 0x80 ? byte : 0xffffffffu) && errno == (invalidLead ? 86 : 0)); + } + errno = 7; + Require(btowc_nid_postfix(0xc3) == 0xffffffff && btowc_nid_postfix(0xef) == 0xffffffff && btowc_nid_postfix(-1) == 0xffffffff && errno == 7); + Require(btowc_nid_postfix(0x41) == 'A' && errno == 7); return 0; } diff --git a/core/libs/tests/GuestRuntimeLifecycle.cpp b/core/libs/tests/GuestRuntimeLifecycle.cpp new file mode 100644 index 0000000000..2a4dd8a22d --- /dev/null +++ b/core/libs/tests/GuestRuntimeLifecycle.cpp @@ -0,0 +1,135 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +struct Event { std::size_t args; const void* argp; }; +std::array events{}; +std::atomic count{0}; +std::atomic entered{false}, releaseEntry{false}; +bool concurrent = false, reentrant = false; +thread_local bool isLifecycleWaiter = false; +std::atomic waiterObserved{false}; +std::atomic waiterReturns{0}; +const char* modulePath = nullptr; +std::int32_t reentrantHandle = -1; +void Require(bool ok, const std::source_location& where = std::source_location::current()) { + if (ok) return; + std::fprintf(stderr, "guest_runtime_lifecycle: failed at %u\n", where.line()); + std::abort(); +} +} + +extern "C" int pthread_cond_wait(pthread_cond_t* condition, pthread_mutex_t* mutex) { + using Wait = int (*)(pthread_cond_t*, pthread_mutex_t*); + static const auto actual = reinterpret_cast(::dlsym(RTLD_NEXT, "pthread_cond_wait")); + Require(actual != nullptr); + if (isLifecycleWaiter) waiterObserved.store(true, std::memory_order_release); + const int result = actual(condition, mutex); + if (isLifecycleWaiter) waiterReturns.fetch_add(1, std::memory_order_relaxed); + return result; +} + +extern "C" void APS5_VABI RecordArgs(std::size_t args, const void* argp) { + const auto index = count.fetch_add(1); + Require(index < events.size()); + events[index] = {args, argp}; + if (index == 0 && reentrant) { + reentrantHandle = sceKernelLoadStartModule(modulePath, 0, nullptr, 0, nullptr, nullptr); + Require(reentrantHandle > 0 && count.load() == 1); + } + if (index == 0 && concurrent) { + entered.store(true, std::memory_order_release); + const auto limit = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!releaseEntry.load(std::memory_order_acquire)) { + Require(std::chrono::steady_clock::now() < limit); + std::this_thread::yield(); + } + } +} + +int main(int argc, char** argv) { + Require(argc == 3); + concurrent = std::strcmp(argv[2], "concurrent") == 0; + reentrant = std::strcmp(argv[2], "reentrant") == 0; + modulePath = argv[1]; + void* preload = ::dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + if (!preload) std::fprintf(stderr, "%s\n", ::dlerror()); + Require(preload && count.load() == 0); + if (std::strcmp(argv[2], "never-start") == 0) { + Require(::dlclose(preload) == 0 && count.load() == 0); + return 0; + } + const std::uint64_t payload = 0xfeed0123; + const bool zeroStart = std::strcmp(argv[2], "zero-start") == 0; + const std::size_t requestedSize = zeroStart ? 0 : sizeof(payload); + const void* requestedPointer = zeroStart ? nullptr : &payload; + int result = -1; + std::int32_t first = -1, second = -1; + if (concurrent) { + std::thread one([&] { first = sceKernelLoadStartModule(argv[1], requestedSize, requestedPointer, 0, nullptr, &result); }); + const auto limit = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!entered.load(std::memory_order_acquire)) { + Require(std::chrono::steady_clock::now() < limit); + std::this_thread::yield(); + } + std::thread two([&] { + isLifecycleWaiter = true; + second = sceKernelLoadStartModule(argv[1], 0, nullptr, 0, nullptr, nullptr); + isLifecycleWaiter = false; + }); + while (!waiterObserved.load(std::memory_order_acquire)) { + Require(std::chrono::steady_clock::now() < limit); + std::this_thread::yield(); + } + Require(count.load() == 1); + releaseEntry.store(true, std::memory_order_release); + one.join(); + two.join(); + Require(waiterReturns.load() > 0); + std::fprintf(stderr, "Observed lifecycle waiter before initializer release; actual condition-wait returns=%u\n", waiterReturns.load()); + } else { + first = sceKernelLoadStartModule(argv[1], requestedSize, requestedPointer, 0, nullptr, &result); + second = sceKernelLoadStartModule(argv[1], 0, nullptr, 0, nullptr, nullptr); + } + std::fprintf(stderr, "first=%d second=%d result=%x count=%u\n", first, second, result, count.load()); + if (first < 0) std::fprintf(stderr, "loader error: %s\n", dlerror_nid_postfix()); + Require(first > 0 && second > 0 && first != second && result == 0x5a17); + Require(count.load() == 2 && events[0].args == requestedSize && events[0].argp == requestedPointer); + Require(events[1].args == 0xa1 && events[1].argp == nullptr); + Require(sceKernelStopUnloadModule(first, 0, nullptr, 0, nullptr, nullptr) == 0 && count.load() == 2); + Require(sceKernelStopUnloadModule(second, 0, nullptr, 0, nullptr, nullptr) == 0 && count.load() == 2); + if (reentrant) { + Require(reentrantHandle != first && reentrantHandle != second); + Require(::dlclose(preload) == 0 && count.load() == 2); + Require(sceKernelStopUnloadModule(reentrantHandle, 0, nullptr, 0, nullptr, nullptr) == 0 && count.load() == 4); + } else { + Require(::dlclose(preload) == 0 && count.load() == 4); + } + Require(events[2].args == 0xf1 && events[2].argp == nullptr); + Require(events[3].args == 0xfe && events[3].argp == nullptr); + if (std::strcmp(argv[2], "reload") == 0) { + void* resident = ::dlopen(argv[1], RTLD_NOW | RTLD_NOLOAD); + Require(resident == nullptr); + result = -1; + const auto reload = sceKernelLoadStartModule(argv[1], sizeof(payload), &payload, 0, nullptr, &result); + Require(reload > 0 && reload != first && reload != second && result == 0x5a17); + Require(count.load() == 6 && events[4].args == sizeof(payload) && events[4].argp == &payload); + Require(events[5].args == 0xa1 && events[5].argp == nullptr); + Require(sceKernelStopUnloadModule(reload, 0, nullptr, 0, nullptr, nullptr) == 0 && count.load() == 8); + Require(events[6].args == 0xf1 && events[6].argp == nullptr); + Require(events[7].args == 0xfe && events[7].argp == nullptr); + Require(::dlopen(argv[1], RTLD_NOW | RTLD_NOLOAD) == nullptr); + } +} diff --git a/core/libs/tests/GuestSafeErrors.cpp b/core/libs/tests/GuestSafeErrors.cpp index 436ce48e09..b253f14d6c 100644 --- a/core/libs/tests/GuestSafeErrors.cpp +++ b/core/libs/tests/GuestSafeErrors.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/WindowsFormatting.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,15 +10,6 @@ #include #include -extern "C" { -int APS5_VABI strerror_s_nid_postfix(char*, std::size_t, int); -std::size_t APS5_VABI strerrorlen_s_nid_postfix(int); -int APS5_VABI strerror_r_nid_postfix(int, char*, std::size_t); -int* APS5_VABI __error_nid_postfix(); -void* APS5_VABI set_constraint_handler_s_nid_postfix(void*); -void APS5_VABI ignore_handler_s_nid_postfix(const char*, void*, int); -} - namespace { void Require(bool condition, const char* message) { @@ -96,8 +89,10 @@ void CheckConstraints() { Require(violations == before + 1 && violationBuffer == nullptr, "one callback for null destination"); } set_constraint_handler_s_nid_postfix(reinterpret_cast(ignore_handler_s_nid_postfix)); + Require(strerror_s_nid_postfix(nullptr, 1, 2) == 22, "installed ignore handler returns to caller"); set_constraint_handler_s_nid_postfix(reinterpret_cast(Constraint)); + } void CheckThreads() { @@ -122,5 +117,6 @@ int main() { CheckConstraints(); CheckThreads(); set_constraint_handler_s_nid_postfix(previous); + std::puts("safe error message checks passed"); } diff --git a/core/libs/tests/GuestSandboxCertificates.cpp b/core/libs/tests/GuestSandboxCertificates.cpp index 2c76c900ba..5fb1e400b3 100644 --- a/core/libs/tests/GuestSandboxCertificates.cpp +++ b/core/libs/tests/GuestSandboxCertificates.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "SceTypes.hpp" @@ -7,16 +10,7 @@ #include #include #include -extern "C" { -const char* APS5_VABI sceKernelGetFsSandboxRandomWord(); -int APS5_VABI sceKernelOpen(const char*, int, unsigned short); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -int APS5_VABI sceKernelStat(const char*, FileStat*); -void* APS5_VABI fopen_nid_postfix(const char*, const char*); -std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, void*); -int APS5_VABI fclose_nid_postfix(void*); -} + static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Sandbox certificates check failed at line %d\n", line); @@ -37,7 +31,7 @@ static std::string ReadKernel(const std::string& path) { return text; } static std::string ReadStream(const std::string& path) { - void* stream = fopen_nid_postfix(path.c_str(), "rb"); + FileStream* stream = fopen_nid_postfix(path.c_str(), "rb"); Require(stream != nullptr); std::string text; char buffer[4096]; diff --git a/core/libs/tests/GuestSandboxRandomWord.cpp b/core/libs/tests/GuestSandboxRandomWord.cpp index 15eb6eb241..b9ec713584 100644 --- a/core/libs/tests/GuestSandboxRandomWord.cpp +++ b/core/libs/tests/GuestSandboxRandomWord.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,12 +7,6 @@ #include #include -extern "C" { -const char* APS5_VABI sceKernelGetFsSandboxRandomWord(); -int APS5_VABI open_nid_postfix(const char*, int, int); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } int main() { diff --git a/core/libs/tests/GuestSaveDataCancel.cpp b/core/libs/tests/GuestSaveDataCancel.cpp index 9b8087c031..5f9612d5ce 100644 --- a/core/libs/tests/GuestSaveDataCancel.cpp +++ b/core/libs/tests/GuestSaveDataCancel.cpp @@ -1,13 +1,14 @@ +#include "prx/libSceSaveData.native/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include -extern "C" int APS5_VABI sceSaveDataCancel(); - int main() { try { - sceSaveDataCancel(); + sceSaveDataCancel(0); + } 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()); diff --git a/core/libs/tests/GuestSaveDataConvert.cpp b/core/libs/tests/GuestSaveDataConvert.cpp index 580d22e892..13085f00db 100644 --- a/core/libs/tests/GuestSaveDataConvert.cpp +++ b/core/libs/tests/GuestSaveDataConvert.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceSaveData/include/Api.hpp" +#include "prx/libSceSaveData.native/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include @@ -6,17 +8,11 @@ #include #include #include +#include #include #include #include -extern "C" { -int APS5_VABI sceSaveDataConvert(const SaveDataConvert*); -int APS5_VABI sceSaveDataGetConvertProgress(float*); -int APS5_VABI sceSaveDataInitialize3(const void*); -int APS5_VABI sceSaveDataTerminate(); -} - namespace { constexpr int SaveDataErrorParameter = -2137063424; @@ -31,6 +27,15 @@ 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); @@ -58,6 +63,9 @@ 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"); @@ -67,6 +75,11 @@ int main() { Check(progress < 1.0f, "convert progress is below 1.0f after failed convert"); Check(progress == 0.0f, "convert progress reports 0.0f after failed convert"); Check(Convert("kept", sizeof("kept")) == 0, "an existing save converts"); + SaveDataEvent event{}; + Check(sceSaveDataGetEventResult(nullptr, &event) == 0, "successful conversion queues completion"); + Check(event.type == 5 && event.error_code == 0, "conversion event reports successful conversion"); + Check(event.user_id == 0x10000000 && std::strcmp(event.dir_name.data, "kept") == 0, "conversion event identifies the user and save"); + Check(sceSaveDataGetEventResult(nullptr, &event) == SaveDataErrorNotFound, "conversion event is delivered once"); Check(Read(kept) == std::vector{'d', 'a', 't', 'a'}, "the converted save keeps its files"); Check(sceSaveDataGetConvertProgress(&progress) == 0, "get convert progress succeeds after successful convert"); Check(progress == 1.0f, "convert progress reports 1.0f after successful convert"); diff --git a/core/libs/tests/GuestSaveDataDelete.cpp b/core/libs/tests/GuestSaveDataDelete.cpp index 0ebbce1462..c8f243059d 100644 --- a/core/libs/tests/GuestSaveDataDelete.cpp +++ b/core/libs/tests/GuestSaveDataDelete.cpp @@ -1,3 +1,6 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceSaveData/include/Api.hpp" +#include "prx/libSceSaveData.native/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/FileStream.hpp" #include "SceTypes.hpp" @@ -13,17 +16,6 @@ #include #include -extern "C" { -int APS5_VABI sceSaveDataDelete(const SaveDataDelete*); -int APS5_VABI sceSaveDataInitialize3(const void*); -int APS5_VABI sceSaveDataMount3(const SaveDataMount3*, SaveDataMountResult*); -int APS5_VABI sceSaveDataUmount2(std::uint32_t, const SaveDataMountPoint*); -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*); -std::size_t APS5_VABI fwrite_nid_postfix(const void*, std::size_t, std::size_t, FileStream*); -int APS5_VABI fclose_nid_postfix(FileStream*); -} - namespace { constexpr int SaveDataErrorParameter = -2137063424; @@ -89,6 +81,9 @@ int main() { char unterminated[sizeof(SceSaveDataDirName::data)]; std::memset(unterminated, 'a', sizeof(unterminated)); Check(Delete(unterminated, sizeof(unterminated)) == SaveDataErrorParameter, "an unterminated name is rejected"); + Check(sceSaveDataDelete(nullptr) == SaveDataErrorParameter, "null del returns PARAMETER"); + SaveDataDelete nullDirDel{}; + Check(sceSaveDataDelete(&nullDirDel) == SaveDataErrorParameter, "null dir_name returns PARAMETER"); constexpr char mountedPayload[] = "live mounted save"; constexpr char updatedPayload[] = "still mounted"; diff --git a/core/libs/tests/GuestSaveDataDialog.cpp b/core/libs/tests/GuestSaveDataDialog.cpp index 1c3ffc702d..e3f2ef95bf 100644 --- a/core/libs/tests/GuestSaveDataDialog.cpp +++ b/core/libs/tests/GuestSaveDataDialog.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceSaveDataDialog/include/Api.hpp" +#include "prx/libSceSaveDataDialog.native/include/Api.hpp" #include "prx/libSceSaveDataDialog.native/SaveDataDialog.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -5,19 +7,6 @@ #include #include -extern "C" { -int APS5_VABI sceSaveDataDialogInitialize(); -int APS5_VABI sceSaveDataDialogOpen(const void* param); -int APS5_VABI sceSaveDataDialogGetStatus(); -int APS5_VABI sceSaveDataDialogUpdateStatus(); -int APS5_VABI sceSaveDataDialogIsReadyToDisplay(); -int APS5_VABI sceSaveDataDialogProgressBarInc(int target, std::uint32_t delta); -int APS5_VABI sceSaveDataDialogProgressBarSetValue(int target, std::uint32_t rate); -int APS5_VABI sceSaveDataDialogClose(const void* param); -int APS5_VABI sceSaveDataDialogGetResult(void* result); -int APS5_VABI sceSaveDataDialogTerminate(); -} - static void Require(bool value, const char* message) { if (!value) { std::fprintf(stderr, "%s\n", message); @@ -106,6 +95,38 @@ int main() { Require(sceSaveDataDialogTerminate() == 0, "terminate"); Require(sceSaveDataDialogTerminate() == SAVE_DATA_DIALOG_ERROR_NOT_INITIALIZED, "terminate when already terminated"); Require(sceSaveDataDialogGetStatus() == SAVE_DATA_DIALOG_STATUS_NONE, "terminated state"); + + Require(sceSaveDataDialogInitialize() == 0, "re-initialize for empty selection"); + SaveDataDialogParam emptySelectionParam{}; + emptySelectionParam.size = sizeof(emptySelectionParam); + emptySelectionParam.mode = 1; + Require(sceSaveDataDialogOpen(&emptySelectionParam) == 0, "open selection dialog without items"); + Require(sceSaveDataDialogGetStatus() == SAVE_DATA_DIALOG_STATUS_FINISHED, "selection finishes immediately"); + SaveDataDirName emptySelectionDir{}; + std::memset(emptySelectionDir.data, 'X', sizeof(emptySelectionDir.data)); + SaveDataDialogResult emptySelectionResult{}; + emptySelectionResult.dir_name = &emptySelectionDir; + Require(sceSaveDataDialogGetResult(&emptySelectionResult) == 0, "read empty selection result"); + Require(emptySelectionDir.data[0] == '\0', "empty selection directory name must be null-terminated"); + Require(emptySelectionResult.mode == emptySelectionParam.mode && + emptySelectionResult.result == SAVE_DATA_DIALOG_RESULT_OK, "selection result status"); + Require(sceSaveDataDialogTerminate() == 0, "terminate selection"); + + Require(sceSaveDataDialogInitialize() == 0, "re-initialize for empty result"); + param = {}; + param.size = sizeof(param); + param.mode = SAVE_DATA_DIALOG_MODE_PROGRESS_BAR; + Require(sceSaveDataDialogOpen(¶m) == 0, "open progress without items"); + Require(sceSaveDataDialogClose(nullptr) == 0, "close empty progress"); + Require(sceSaveDataDialogUpdateStatus() == SAVE_DATA_DIALOG_STATUS_FINISHED, "finish empty progress"); + SaveDataDirName emptySelected{}; + std::memset(emptySelected.data, 'X', sizeof(emptySelected.data)); + SaveDataDialogResult emptyResult{}; + emptyResult.dir_name = &emptySelected; + Require(sceSaveDataDialogGetResult(&emptyResult) == 0, "read empty result"); + Require(emptySelected.data[0] == '\0', "empty directory name must be null-terminated"); + Require(sceSaveDataDialogTerminate() == 0, "terminate empty progress"); + Require(sceSaveDataDialogProgressBarInc(0, 10) == SAVE_DATA_DIALOG_ERROR_NOT_RUNNING, "progress inc after terminate"); Require(sceSaveDataDialogProgressBarSetValue(0, 50) == SAVE_DATA_DIALOG_ERROR_NOT_RUNNING, "progress set value after terminate"); } diff --git a/core/libs/tests/GuestSaveDataMountStatus.cpp b/core/libs/tests/GuestSaveDataMountStatus.cpp index 88557e574d..596fc94026 100644 --- a/core/libs/tests/GuestSaveDataMountStatus.cpp +++ b/core/libs/tests/GuestSaveDataMountStatus.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceSaveData/include/Api.hpp" +#include "prx/libSceSaveData.native/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include @@ -7,18 +9,9 @@ #include #include #include - -extern "C" { -int APS5_VABI sceSaveDataInitialize3(const void*); -int APS5_VABI sceSaveDataMount3(const SaveDataMount3*, SaveDataMountResult*); -int APS5_VABI sceSaveDataUmount2(std::uint32_t, const SaveDataMountPoint*); -int APS5_VABI sceSaveDataGetMountInfo(const SaveDataMountPoint*, SaveDataMountInfo*); -int APS5_VABI sceSaveDataGetParam(const SaveDataMountPoint*, std::uint32_t, void*, std::size_t, std::size_t*); -int APS5_VABI sceSaveDataSetParam(const SaveDataMountPoint*, std::uint32_t, const void*, std::size_t); -int APS5_VABI sceSaveDataLoadIcon(const SaveDataMountPoint*, SaveDataIcon*); -int APS5_VABI sceSaveDataSaveIcon(const SaveDataMountPoint*, const SaveDataIcon*); -int APS5_VABI sceSaveDataSaveIconByPath(const SaveDataMountPoint*, const char*); -} +#ifdef _WIN32 +#include +#endif namespace { @@ -45,6 +38,11 @@ void CheckUnmountedCalls() { SaveDataIcon icon{}; char buf[128]{}; std::size_t gotSize = 0; + SaveDataMount3 nullMount{}; + SaveDataMountResult mountResult{}; + Check(sceSaveDataMount3(nullptr, &mountResult) == SaveDataErrorParameter, "mount3 null mount is PARAMETER"); + Check(sceSaveDataMount3(&nullMount, nullptr) == SaveDataErrorParameter, "mount3 null result is PARAMETER"); + Check(sceSaveDataMount3(&nullMount, &mountResult) == SaveDataErrorParameter, "mount3 null dir_name is PARAMETER"); Check(sceSaveDataUmount2(0, nullptr) == SaveDataErrorParameter, "umount2 null point is PARAMETER"); Check(sceSaveDataUmount2(0, &point) == SaveDataErrorNotMounted, "umount2 unmounted point is NOT_MOUNTED"); Check(sceSaveDataLoadIcon(nullptr, &icon) == SaveDataErrorParameter, "loadIcon null point is PARAMETER"); @@ -61,6 +59,10 @@ void CheckUnmountedCalls() { Check(sceSaveDataSetParam(nullptr, 1, buf, sizeof(buf)) == SaveDataErrorParameter, "setParam null point is PARAMETER"); Check(sceSaveDataSetParam(&point, 1, nullptr, sizeof(buf)) == SaveDataErrorParameter, "setParam null buf is PARAMETER"); Check(sceSaveDataSetParam(&point, 1, buf, sizeof(buf)) == SaveDataErrorNotMounted, "setParam unmounted point is NOT_MOUNTED"); + Check(sceSaveDataBackup(nullptr) == SaveDataErrorParameter, "backup null backup is PARAMETER"); + SaveDataBackup nullDirBackup{}; + Check(sceSaveDataBackup(&nullDirBackup) == SaveDataErrorParameter, "backup null dir_name is PARAMETER"); + Check(sceSaveDataGetEventResult(nullptr, nullptr) == SaveDataErrorParameter, "getEventResult null event is PARAMETER"); } void CheckMountInfoAndParam(const SaveDataMountPoint* point) { @@ -157,7 +159,15 @@ void CheckEquivalentMounts() { const auto casePath = std::filesystem::path("_sd") / "IDENTITY"; const bool caseAlias = std::filesystem::exists(casePath) && std::filesystem::equivalent(originalPath, casePath, error); Check(!error || error == std::errc::no_such_file_or_directory, "case-alias identity check succeeds"); +#ifdef _WIN32 + error.clear(); + if (!CreateSymbolicLinkW((std::filesystem::path("_sd") / "linked").c_str(), L"identity", + SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) { + error = std::error_code(GetLastError(), std::system_category()); + } +#else std::filesystem::create_directory_symlink("identity", std::filesystem::path("_sd") / "linked", error); +#endif const bool symlinkAlias = !error; Check(caseAlias || symlinkAlias, "a host alias is available to check mount identity"); @@ -179,7 +189,12 @@ void CheckEquivalentMounts() { if (caseAlias) MountAndCheck("IDENTITY", MountReadWrite, 0u, "case alias after unmount"); if (symlinkAlias) { MountAndCheck("linked", MountReadWrite, 0u, "symlink alias after unmount"); +#ifdef _WIN32 + error.clear(); + if (!RemoveDirectoryW((std::filesystem::path("_sd") / "linked").c_str())) error = std::error_code(GetLastError(), std::system_category()); +#else std::filesystem::remove(std::filesystem::path("_sd") / "linked", error); +#endif Check(!error, "symlink alias is removed"); } } @@ -226,6 +241,7 @@ int main() { const auto previous = std::filesystem::current_path(); std::filesystem::current_path(root); CheckUnmountedCalls(); + Check(sceSaveDataSyncSaveDataMemory(nullptr) == static_cast(0x809F0000), "null param rejected in memory sync"); Check(sceSaveDataInitialize3(nullptr) == 0, "SaveData initializes"); MountAndCheck("fresh", MountReadWrite | MountCreate2, 1u, "CREATE2 of a missing save"); MountAndCheck("fresh", MountReadWrite | MountCreate2, 0u, "CREATE2 of an existing save"); diff --git a/core/libs/tests/GuestSaveDataPs4.cpp b/core/libs/tests/GuestSaveDataPs4.cpp index 39e7b14d1e..0c4a45540f 100644 --- a/core/libs/tests/GuestSaveDataPs4.cpp +++ b/core/libs/tests/GuestSaveDataPs4.cpp @@ -1,19 +1,22 @@ +#include "prx/libSceSaveData/include/Api.hpp" +#include "prx/libSceSaveData.native/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include #include #include +#include #include #include #include -extern "C" int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount*, SaveDataMountResult*); -extern "C" int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); -extern "C" int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); - namespace { constexpr int SaveDataErrorNotFound = -2137063416; +constexpr int SaveDataErrorNotInitialized = -2137063423; +constexpr int SaveDataErrorParameter = -2137063424; +constexpr int SaveDataErrorNotMounted = -2137063420; +constexpr std::uint32_t MountReadWrite = 2; int failures = 0; @@ -46,6 +49,11 @@ void TestTransferringMountPs4() { std::memset(&result, 0xAA, sizeof(result)); Check(sceSaveDataTransferringMountPs4(&mount, &result) == SaveDataErrorNotFound, "TransferringMountPs4 returns NOT_FOUND"); Check(result.mount_point.data[0] == '\0', "TransferringMountPs4 reports no mount point"); + std::memset(&result, 0xAA, sizeof(result)); + Check(sceSaveDataTransferringMount(&mount, &result) == SaveDataErrorNotFound, "TransferringMount returns NOT_FOUND"); + Check(result.mount_point.data[0] == '\0', "TransferringMount reports no mount point"); + Check(sceSaveDataTransferringMount(nullptr, nullptr) == SaveDataErrorNotFound, "TransferringMount handles null arguments"); + Check(sceSaveDataTransferringMountPs4(nullptr, nullptr) == SaveDataErrorNotFound, "TransferringMountPs4 handles null arguments"); } void TestDirNameSearchPs4() { @@ -73,6 +81,39 @@ void TestDirNameSearchPs4() { Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(&cond, nullptr); }), "null result throws"); } +void TestSaveDataMemoryV1() { + Check(sceSaveDataSetupSaveDataMemory(7, 0x1000, nullptr) == SaveDataErrorNotInitialized, "v1 SetupSaveDataMemory before v1 Initialize"); + Check(sceSaveDataInitialize(nullptr) == 0, "v1 Initialize"); + Check(sceSaveDataSetupSaveDataMemory(7, 0x1000, nullptr) == 0, "v1 SetupSaveDataMemory"); + char written[16] = "settings-v1"; + Check(sceSaveDataSetSaveDataMemory(7, written, sizeof(written), 0x100) == 0, "v1 SetSaveDataMemory at an offset"); + char read[16] = {}; + Check(sceSaveDataGetSaveDataMemory(7, read, sizeof(read), 0x100) == 0, "v1 GetSaveDataMemory"); + Check(std::memcmp(read, written, sizeof(read)) == 0, "v1 GetSaveDataMemory returns the bytes v1 set"); + std::ifstream slot("_sd_mem/u7/slot0.bin", std::ios::binary); + slot.seekg(0x100); + char stored[16] = {}; + Check(static_cast(slot.read(stored, sizeof(stored))) && std::memcmp(stored, written, sizeof(stored)) == 0, "v1 SetSaveDataMemory stores into slot 0 of the user"); + Check(sceSaveDataTerminate() == 0, "Terminate after v1 Initialize"); +} + +void TestMountPs4() { + SaveDataMountResult result{}; + Check(sceSaveDataMount(nullptr, &result) == SaveDataErrorParameter, "null mount is PARAMETER"); + SaveDataMountPs4 mount{}; + Check(sceSaveDataMount(&mount, &result) == SaveDataErrorParameter, "null dir_name is PARAMETER"); + Check(sceSaveDataUmount(nullptr) == SaveDataErrorParameter, "umount null point is PARAMETER"); + SceSaveDataDirName dir{}; + std::strcpy(dir.data, "kept"); + mount.dir_name = &dir; + mount.mount_mode = MountReadWrite; + Check(sceSaveDataMount(&mount, nullptr) == SaveDataErrorParameter, "null mount_result is PARAMETER"); + Check(sceSaveDataMount(&mount, &result) == 0, "mount existing save directory succeeds"); + Check(result.mount_point.data[0] != '\0', "mount writes mount point"); + Check(sceSaveDataUmount(&result.mount_point) == 0, "umount mounted point succeeds"); + Check(sceSaveDataUmount(&result.mount_point) == SaveDataErrorNotMounted, "umount unmounted point is NOT_MOUNTED"); +} + } int main() { @@ -83,6 +124,8 @@ int main() { TestTransferringMountPs4(); TestDirNameSearchPs4(); + TestSaveDataMemoryV1(); + TestMountPs4(); std::filesystem::current_path(previous); std::error_code error; diff --git a/core/libs/tests/GuestSaveDataSearch.cpp b/core/libs/tests/GuestSaveDataSearch.cpp index 47613690d3..c476d6fd0c 100644 --- a/core/libs/tests/GuestSaveDataSearch.cpp +++ b/core/libs/tests/GuestSaveDataSearch.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSaveData.native/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -12,30 +13,12 @@ #include #include -extern "C" { -int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); -int APS5_VABI sceSaveDataDirNameSearch2(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); -int APS5_VABI sceSaveDataMount3(const SaveDataMount3*, SaveDataMountResult*); -int APS5_VABI sceSaveDataUmount2(std::uint32_t, const SaveDataMountPoint*); -int APS5_VABI sceSaveDataSetParam(const SaveDataMountPoint*, std::uint32_t, const void*, std::size_t); -int APS5_VABI sceSaveDataGetMountInfo(const SaveDataMountPoint*, SaveDataMountInfo*); -} - static constexpr int SaveDataErrorParameter = -2137063424; static void Require(bool value, const char* message) { if (!value) throw std::runtime_error(message); } -template -static bool ThrowsRuntimeError(TFunction function) { - try { - function(); - } catch (const std::runtime_error&) { - return true; - } - return false; -} static std::vector Search(std::uint32_t key, std::uint32_t order, std::uint32_t capacity, std::uint32_t* hits) { SceSaveDataDirName pattern{}; @@ -291,10 +274,10 @@ static void CheckNullArguments() { SaveDataDirNameSearchCond cond{}; cond.user_id = 1; SaveDataDirNameSearchResult result{}; - Require(ThrowsRuntimeError([&] { sceSaveDataDirNameSearch(nullptr, &result); }), "null cond throws in sceSaveDataDirNameSearch"); - Require(ThrowsRuntimeError([&] { sceSaveDataDirNameSearch(&cond, nullptr); }), "null result throws in sceSaveDataDirNameSearch"); - Require(ThrowsRuntimeError([&] { sceSaveDataDirNameSearch2(nullptr, &result); }), "null cond throws in sceSaveDataDirNameSearch2"); - Require(ThrowsRuntimeError([&] { sceSaveDataDirNameSearch2(&cond, nullptr); }), "null result throws in sceSaveDataDirNameSearch2"); + Require(sceSaveDataDirNameSearch(nullptr, &result) == SaveDataErrorParameter, "null cond returns PARAMETER in sceSaveDataDirNameSearch"); + Require(sceSaveDataDirNameSearch(&cond, nullptr) == SaveDataErrorParameter, "null result returns PARAMETER in sceSaveDataDirNameSearch"); + Require(sceSaveDataDirNameSearch2(nullptr, &result) == SaveDataErrorParameter, "null cond returns PARAMETER in sceSaveDataDirNameSearch2"); + Require(sceSaveDataDirNameSearch2(&cond, nullptr) == SaveDataErrorParameter, "null result returns PARAMETER in sceSaveDataDirNameSearch2"); cond.order = 2; Require(sceSaveDataDirNameSearch2(&cond, &result) == SaveDataErrorParameter, "invalid order must be rejected in sceSaveDataDirNameSearch2"); } diff --git a/core/libs/tests/GuestScanCount.cpp b/core/libs/tests/GuestScanCount.cpp index 1ddfe19674..9c2df500c0 100644 --- a/core/libs/tests/GuestScanCount.cpp +++ b/core/libs/tests/GuestScanCount.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include @@ -8,9 +9,6 @@ #include #include -extern "C" int APS5_VABI sscanf_s_nid_postfix(const char*, const char*, ...); -extern "C" int APS5_VABI vsscanf_s_nid_postfix(const char*, const char*, VaList*); - static int APS5_VABI ScanList(const char* input, const char* format, ...) { #ifdef _WIN32 __builtin_sysv_va_list args; diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 3f00cfe6d4..77e3e7ad0d 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -1,3 +1,7 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceNetCtl/include/NetCtl.hpp" +#include "prx/libSceNet/include/Net.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -13,6 +17,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/Socket/include/SocketSelectDeadline.hpp" #include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libSceNet/src/ResolverTestHooks.hpp" #include "SceTypes.hpp" #include #include @@ -31,124 +36,84 @@ extern "C" { extern const std::uint8_t in6addr_any_nid_postfix[16]; extern const std::uint8_t in6addr_loopback_nid_postfix[16]; -int APS5_VABI sceNetInit_nid_postfix(void); -int APS5_VABI sceNetShowNetstatWithMemory(int); -int APS5_VABI sceNetShowNetstat(void); -int APS5_VABI sceNetShowIfconfig(void); -int APS5_VABI sceNetShowRoute(void); -int APS5_VABI sceNetShowRoute6(void); -int APS5_VABI sceNetSocket(const char*, int, int, int); -int APS5_VABI sceNetBind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI sceNetListen(int, int); -int APS5_VABI sceNetGetsockname(int, void*, std::uint32_t*); -int APS5_VABI sceNetGetpeername(int, void*, std::uint32_t*); -int APS5_VABI sceNetGetSockInfo(int, void*, int, int); -int APS5_VABI sceNetConnect(int, const void*, std::uint32_t); -int APS5_VABI sceNetAccept(int, void*, std::uint32_t*); -std::int64_t APS5_VABI sceNetSend(int, const void*, std::size_t, int); -std::int64_t APS5_VABI sceNetRecv(int, void*, std::size_t, int); -std::int64_t APS5_VABI sceNetSendto(int, const void*, std::size_t, int, const void*, std::uint32_t); -std::int64_t APS5_VABI sceNetRecvfrom(int, void*, std::size_t, int, void*, std::uint32_t*); -int APS5_VABI sceNetSocketClose(int); -int APS5_VABI sceNetSocketAbort(int, int); -int APS5_VABI sceNetShutdown(int, int); -int APS5_VABI sceNetSetsockopt(int, int, int, const void*, std::uint32_t); -int APS5_VABI sceNetGetsockopt(int, int, int, void*, std::uint32_t*); -int* APS5_VABI sceNetErrnoLoc(void); -int APS5_VABI sceNetEpollCreate(const char*, int); -int APS5_VABI sceNetEpollControl(int, int, int, const NetEpollEvent*); -int APS5_VABI sceNetEpollWait(int, NetEpollEvent*, int, int); -int APS5_VABI sceNetEpollDestroy(int); -int APS5_VABI sceNetEpollAbort(int, int); -int APS5_VABI sceNetGetsockopt(int, int, int, void*, std::uint32_t*); + extern const std::uint32_t sce_net_in6addr_any[4]; extern const std::uint8_t sce_net_in6addr_linklocal_allnodes[16]; -int APS5_VABI sceNetResolverCreate(const char*, int, int); -int APS5_VABI sceNetResolverStartNtoa(int, const char*, void*, int, int, int); -int APS5_VABI sceNetResolverStartNtoa6(int, const char*, void*, int, int, int); -int APS5_VABI sceNetResolverStartNtoaMultipleRecordsEx(int, const char*, void*, int, int, int); -int APS5_VABI sceNetResolverStartNtoaMultipleRecords(int, const char*, void*, int, int, int); -int APS5_VABI sceNetResolverStartAton(int, const void*, char*, int, int, int, int); -int APS5_VABI sceNetResolverStartAton6(int, const void*, char*, int, int, int, int); -int APS5_VABI sceNetResolverAbort(int, int); -int APS5_VABI sceNetResolverDestroy(int); -int APS5_VABI sceNetResolverGetError(int, int*); -int APS5_VABI sceNetClearDnsCache(int); -int APS5_VABI sceNetGetDnsInfo(std::uint32_t*, int); -int APS5_VABI sceNetCtlGetState(int*); -int APS5_VABI sceNetCtlGetInfo(int, NetCtlInfo*); -int APS5_VABI sceNetCtlGetIfStat(NetCtlIfStat*); -int APS5_VABI sceNetCtlRegisterCallback(NetCtlCallback, void*, int*); -int APS5_VABI sceNetCtlUnregisterCallback(int); -int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback, void*, int*); -int APS5_VABI sceNetCtlUnregisterCallbackV6(int); + std::uint64_t NetCtlReadByteCounter_nid_no_patch(const char*); -int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI close_nid_postfix(int); + bool GuestSocketIsOpen_nid_no_patch(int); -int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -struct GuestPollEntry { - int descriptor; - short events; - short revents; -}; -int APS5_VABI poll_nid_postfix(GuestPollEntry*, std::uint32_t, int); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceNetInetPton(int, const char*, void*); -int APS5_VABI sceNetInetPtonEx(int, const char*, void*, int); -int APS5_VABI sceNetUsleep(int); -void APS5_VABI sceNetGetSystemTime(std::uint64_t*); -const char* APS5_VABI sceNetInetNtop(int, const void*, char*, std::uint32_t); -int* APS5_VABI sceNetErrnoLoc(void); -} -struct NetIovec { - void* base; - std::uint64_t length; -}; - -struct NetMsghdr { - void* name; - std::uint32_t name_length; - NetIovec* iov; - int iov_length; - void* control; - std::uint32_t control_length; - int flags; -}; - -struct NetMemoryPoolStats { - std::size_t pool_size; - std::size_t max_inuse_size; - std::size_t current_inuse_size; -}; - -extern "C" { -std::int64_t APS5_VABI sceNetSendmsg(int, const NetMsghdr*, int); -std::int64_t APS5_VABI sceNetRecvmsg(int, NetMsghdr*, int); -int APS5_VABI sceNetPoolCreate(const char*, int, int); -int APS5_VABI sceNetPoolDestroy(int); -int APS5_VABI sceNetGetMemoryPoolStats(int, NetMemoryPoolStats*); -const char* APS5_VABI sceNetGetIfName(std::uint32_t); -int APS5_VABI sceNetGetRandom(std::uint32_t*); -void* APS5_VABI sceNetMemoryAllocate(std::int64_t, int); -void APS5_VABI sceNetMemoryFree(void*); } static void Require(bool condition) { if (!condition) std::abort(); } +static bool FailResolverLookup(const char* hostname, int family, std::vector>&) { + Require(hostname != nullptr && (std::strcmp(hostname, "guest-sce-net.invalid.") == 0 || + std::strcmp(hostname, "guest-sce-net.invalid") == 0)); + Require(family == AF_INET || family == AF_INET6); + return false; +} + +class ScopedResolverLookupOverride { +public: + explicit ScopedResolverLookupOverride(GuestSceNetTest::ResolverLookup lookup) { + GuestSceNetTest::SetResolverLookupForTests_nid_no_patch(lookup); + } + ~ScopedResolverLookupOverride() { + GuestSceNetTest::SetResolverLookupForTests_nid_no_patch(nullptr); + } +}; + static void APS5_VABI IgnoreNetCtlEvent(int, void*) {} +struct NetCtlEventRecord { + int calls = 0; + int event = 0; + void* arg = nullptr; +}; + +static void APS5_VABI RecordNetCtlEvent(int event, void* arg) { + auto* record = static_cast(arg); + ++record->calls; + record->event = event; + record->arg = arg; +} + static bool Failed(std::int64_t result, int error) { return result == static_cast(0x80410100u | static_cast(error)) && *sceNetErrnoLoc() == error; } +template +static std::string CaptureStdout(TBody body) { + std::fflush(stdout); + std::FILE* capture = std::tmpfile(); + Require(capture != nullptr); +#ifdef _WIN32 + const int saved = _dup(_fileno(stdout)); + Require(saved >= 0 && _dup2(_fileno(capture), _fileno(stdout)) == 0); +#else + const int saved = dup(fileno(stdout)); + Require(saved >= 0 && dup2(fileno(capture), fileno(stdout)) >= 0); +#endif + body(); + std::fflush(stdout); +#ifdef _WIN32 + Require(_dup2(saved, _fileno(stdout)) == 0); + _close(saved); +#else + Require(dup2(saved, fileno(stdout)) >= 0); + close(saved); +#endif + std::string text; + std::rewind(capture); + for (int character = std::fgetc(capture); character != EOF; character = std::fgetc(capture)) text.push_back(static_cast(character)); + std::fclose(capture); + return text; +} + static void CheckSocketHostErrors() { Require(Failed(sceNetSocket(nullptr, 2, 1, 17), 43)); } @@ -239,13 +204,24 @@ static std::string CaptureNetstat(int pool) { } static void CheckNetstat() { - Require(sceNetShowNetstat() == 0); - Require(sceNetShowIfconfig() == 0); + for (const auto diagnostic : {sceNetShowNetstat, sceNetShowIfconfig}) { + bool unsupported = false; + try { + diagnostic(); + } catch (const std::runtime_error&) { + unsupported = true; + } + Require(unsupported); + } + Require(sceNetShowPolicy() == 0); + Require(sceNetShowNetstatEx(0) == 0); Require(sceNetShowRoute() == 0); Require(sceNetShowRoute6() == 0); Require(Failed(sceNetShowNetstatWithMemory(-1), 9)); + Require(Failed(sceNetShowRouteWithMemory(-1), 9)); const int pool = sceNetPoolCreate("netstat", 4096, 0); Require(pool > 0); + Require(sceNetShowRouteWithMemory(pool) == 0); const auto empty = CaptureNetstat(pool); Require(empty.find("bytes=4096 sockets=0") != std::string::npos); const int socket = sceNetSocket("netstat", 2, 1, 0); @@ -266,6 +242,7 @@ static void CheckNetstat() { Require(CaptureNetstat(pool).find("sockets=0") != std::string::npos); Require(sceNetPoolDestroy(pool) == 0); Require(Failed(sceNetShowNetstatWithMemory(pool), 9)); + Require(Failed(sceNetShowRouteWithMemory(pool), 9)); } static void CheckPoolStats() { @@ -297,12 +274,20 @@ static void CheckRandom() { Require(Failed(sceNetGetRandom(nullptr), 22)); std::array values{}; for (auto& value : values) Require(sceNetGetRandom(&value) == 0); + std::array guarded{0x5a5a5a5au, 0x5a5a5a5au}; + Require(sceNetGetRandom(guarded.data()) == 0 && guarded[1] == 0x5a5a5a5au && *sceNetErrnoLoc() == 22); Require(std::any_of(values.begin() + 1, values.end(), [&](std::uint32_t value) { return value != values[0]; })); } static void CheckNetMemory() { - Require(sceNetMemoryAllocate(0, 0) == nullptr); - Require(sceNetMemoryAllocate(-1, 2) == nullptr); + *sceNetErrnoLoc() = 33; + for (const std::int32_t size : {0, -1, INT32_MIN}) { + Require(sceNetMemoryAllocate(size, 0) == nullptr); + Require(sceNetMemoryAllocate(size, 2) == nullptr); + Require(sceNetMemoryAllocate(size, 4) == nullptr); + Require(sceNetMemoryAllocate(size, -1) == nullptr); + } + Require(*sceNetErrnoLoc() == 33); auto* zeroed = static_cast(sceNetMemoryAllocate(4096, 2)); Require(zeroed != nullptr); Require(std::all_of(zeroed, zeroed + 4096, [](std::uint8_t byte) { return byte == 0; })); @@ -313,14 +298,20 @@ static void CheckNetMemory() { Require(zeroed[4095] == 0xa5); sceNetMemoryFree(plain); sceNetMemoryFree(zeroed); - sceNetMemoryFree(nullptr); - bool threw = false; - try { - sceNetMemoryAllocate(16, 1); - } catch (const std::runtime_error&) { - threw = true; + for (const int flags : {1, 3, 4, 6, 8, 0x10, 0x100, 0x40000001, -5, INT32_MIN}) { + auto* block = static_cast(sceNetMemoryAllocate(16, flags)); + Require(block != nullptr && reinterpret_cast(block) % 64 == 0); + if ((flags & 2) != 0) Require(std::all_of(block, block + 16, [](std::uint8_t byte) { return byte == 0; })); + std::memset(block, 0x5a, 16); + sceNetMemoryFree(block); } - Require(threw); + for (const std::int32_t size : {1, 7, 256, 65536}) { + void* block = sceNetMemoryAllocate(size, 0); + Require(block != nullptr && reinterpret_cast(block) % 64 == 0); + sceNetMemoryFree(block); + } + sceNetMemoryFree(nullptr); + Require(*sceNetErrnoLoc() == 33); } static void CheckUnboundSocketName() { @@ -514,6 +505,40 @@ static void CheckEmptyEpoll() { } } +static void CheckRejectedArguments() { + Require(Failed(sceNetEpollCreate(nullptr, 0), 22)); + for (const int flags : {1, 0x10000, -1}) Require(Failed(sceNetEpollCreate("rejected-epoll", flags), 22)); + + std::array untouched{}; + untouched.fill(0xA5); + NetEtherAddr mac{}; + std::memcpy(mac.data, untouched.data(), untouched.size()); + for (const int flags : {1, 0x10000, -1}) { + Require(Failed(sceNetGetMacAddress(&mac, flags), 22)); + Require(std::memcmp(mac.data, untouched.data(), untouched.size()) == 0); + } + Require(Failed(sceNetGetMacAddress(nullptr, 1), 22)); + + for (const int flags : {1, 0x10000, -1}) Require(Failed(sceNetResolverCreate("rejected-resolver", 0, flags), 22)); + + std::array ipv4{}; + const int resolver = sceNetResolverCreate("rejected-ntoa", 0, 0); + Require(resolver >= 0); + Require(sceNetResolverAbort(resolver, 1) == 0); + Require(Failed(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), -1, 1, 0), 22)); + Require(Failed(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, -1, 0), 22)); + for (const int flags : {2, 0x20000, 0x10003, static_cast(0x80000000u), -1}) + Require(Failed(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, flags), 22)); + Require(Failed(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0), 4)); + Require(sceNetResolverDestroy(resolver) == 0); + + Require(Failed(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), -1, 0, 0), 22)); + Require(Failed(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 0, -1, 0), 22)); + Require(Failed(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 0, 0, 2), 22)); + for (const int flags : {0, 1, 0x10000, 0x10001}) + Require(Failed(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 0, 0, flags), 9)); +} + static void CheckDontWait() { constexpr int DontWait = 0x80; constexpr int Peek = 0x2; @@ -580,6 +605,36 @@ static void CheckReceiveTimeout(int receiver, int sender, const std::array= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(sceNetBind_nid_postfix(listener, address.data(), address.size()) == 0 && sceNetListen(listener, 1) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(listener, address.data(), &size) == 0); + const int client = sceNetSocket("stream-timeout-client", 2, 1, 6); + Require(client >= 0 && sceNetConnect(client, address.data(), address.size()) == 0); + const int accepted = sceNetAccept(listener, nullptr, nullptr); + Require(accepted >= 0); + const KernelTimeval timeout{0, 50000}; + Require(sceNetSetsockopt(client, 0xffff, 0x1006, &timeout, sizeof(timeout)) == 0); + char buffer[8]{}; + NetIovec in[] = {{buffer, sizeof(buffer)}}; + NetMsghdr receive{nullptr, 0, in, 1, nullptr, 0, 0}; + const auto start = std::chrono::steady_clock::now(); + Require(Failed(sceNetRecv(client, buffer, sizeof(buffer), 0), 35)); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40)); + Require(Failed(sceNetRecvfrom(client, buffer, sizeof(buffer), 0, nullptr, nullptr), 35)); + Require(Failed(sceNetRecvmsg(client, &receive, 0), 35)); + const char payload[] = "late"; + Require(sceNetSend(accepted, payload, sizeof(payload), 0) == sizeof(payload)); + Require(sceNetRecv(client, buffer, sizeof(buffer), 0) == sizeof(payload) && std::strcmp(buffer, payload) == 0); + Require(sceNetSend(client, payload, sizeof(payload), 0) == sizeof(payload)); + std::memset(buffer, 0, sizeof(buffer)); + Require(sceNetRecv(accepted, buffer, sizeof(buffer), 0) == sizeof(payload) && std::strcmp(buffer, payload) == 0); + for (const int descriptor : {client, accepted, listener}) Require(sceNetSocketClose(descriptor) == 0); +} + static void CheckMicrosecondTimeouts(int receiver) { const int receiveTimeout = 50000; const int sendTimeout = 70000; @@ -743,6 +798,57 @@ static void CheckKernelTimeouts() { Require(sceNetSocketClose(descriptor) == 0); } +static void CheckIoctl() { + const int socket = sceNetSocket("ioctl", 2, 2, 17); + Require(socket >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(sceNetBind_nid_postfix(socket, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(socket, address.data(), &size) == 0 && size == address.size()); + for (const int enabled : {1, 0, 7}) { + int value = enabled; + Require(sceNetIoctl(socket, 0x8004667e, &value) == 0); + int nonblocking = -1; + std::uint32_t length = sizeof(nonblocking); + Require(sceNetGetsockopt(socket, 0xffff, 0x1200, &nonblocking, &length) == 0 && nonblocking == (enabled != 0 ? 1 : 0)); + Require(fcntl_nid_postfix(socket, 3) == (enabled != 0 ? 2 | 4 : 2)); + } + int disabled = 0; + int enabled = 1; + Require(sceNetIoctl(socket, 0x8004667e, &enabled) == 0); + char buffer[8]{}; + Require(Failed(sceNetRecv(socket, buffer, sizeof(buffer), 0), 35)); + int readable = -1; + Require(sceNetIoctl(socket, 0x4004667f, &readable) == 0 && readable == 0); + const char payload[5] = {'i', 'o', 'c', 't', 'l'}; + Require(sceNetSendto(socket, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload)); + GuestPollDescriptor entry{socket, 1, 0}; + Require(poll_nid_postfix(&entry, 1, 1000) == 1 && entry.revents == 1); + Require(sceNetIoctl(socket, 0x4004667f, &readable) == 0 && readable == sizeof(payload)); + Require(sceNetRecv(socket, buffer, sizeof(buffer), 0) == sizeof(payload)); + Require(sceNetIoctl(socket, 0x4004667f, &readable) == 0 && readable == 0); + Require(Failed(sceNetIoctl(socket, 0x8004667e, nullptr), 14)); + Require(Failed(sceNetIoctl(socket, 0x4004667f, nullptr), 14)); + Require(Failed(sceNetIoctl(socket, 123, &readable), 25)); + Require(Failed(sceNetIoctl(socket, 123, nullptr), 25)); + Require(sceNetSocketClose(socket) == 0); + Require(Failed(sceNetIoctl(socket, 0x8004667e, &enabled), 9)); + Require(Failed(sceNetIoctl(-1, 0x4004667f, &readable), 9)); + const int random = open_nid_postfix("/dev/urandom", 0, 0); + Require(random >= 0); + Require(Failed(sceNetIoctl(random, 0x4004667f, &readable), 9)); + Require(close_nid_postfix(random) == 0); + + const int kernel = socket_nid_postfix(2, 2, 0); + Require(kernel >= 0); + std::array kernelAddress{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(kernel, kernelAddress.data(), kernelAddress.size()) == 0); + Require(sceNetIoctl(kernel, 0x8004667e, &enabled) == 0 && fcntl_nid_postfix(kernel, 3) == (2 | 4)); + Require(sceNetIoctl(kernel, 0x8004667e, &disabled) == 0 && fcntl_nid_postfix(kernel, 3) == 2); + Require(sceNetIoctl(kernel, 0x4004667f, &readable) == 0 && readable == 0); + Require(close_nid_postfix(kernel) == 0); +} + static void CheckKernelStatusFlags() { const int descriptor = sceNetSocket("kernel-status", 2, 1, 6); Require(descriptor >= 0); @@ -802,9 +908,24 @@ static void CheckInetPtonWhitespace() { for (const char* text : {" 1.2.3.4", "1.2.3.4x", "1.2.3.4.", "1.2.3.4\x01", "1.2.3.4\xa0", "1.2. 3.4", "1.2.3", "1.2.3 ", "01.2.3.4", "256.0.0.1"}) { CheckInetPtonRejects(2, text); } - for (const char* text : {"::1 ", "::1\t", " ::1", "::1x"}) { + for (const char* text : {"::1 ", "::1\t", " ::1", "::1x", "::ffff:1.2.3.4x", "::ffff:1.2.3.4\x01", "::ffff:01.2.3.4", "::ffff:1.2.3", + "::ffff:0x1.2.3.4", "::ffff ", "::ffff:1.2 .3.4"}) { CheckInetPtonRejects(28, text); } + for (const char* text : {"::ffff:1.2.3.4 x", "::ffff:1.2.3.4\t", "::ffff:1.2.3.4\nzz", "::ffff:1.2.3.4 "}) { + std::array address{}; + *sceNetErrnoLoc() = 123; + Require(sceNetInetPton(28, text, address.data()) == 1 && *sceNetErrnoLoc() == 123); + Require(address[10] == 0xff && address[11] == 0xff && address[12] == 1 && address[15] == 4); + } + std::array full{}; + Require(sceNetInetPton(28, "1:2:3:4:5:6:1.2.3.4 z", full.data()) == 1 && full[1] == 1 && full[11] == 6 && full[12] == 1 && full[15] == 4); + *sceNetErrnoLoc() = 123; + Require(sceNetInetPton(2, "1.2.3.4", nullptr) == 1 && *sceNetErrnoLoc() == 123); + Require(sceNetInetPton(2, "abc", nullptr) == 0 && *sceNetErrnoLoc() == 123); + Require(sceNetInetPton(28, "::1", nullptr) < 0 && *sceNetErrnoLoc() == 22); + *sceNetErrnoLoc() = 123; + Require(sceNetInetPton(28, nullptr, full.data()) < 0 && *sceNetErrnoLoc() == 22); } struct AtonCase { @@ -860,7 +981,9 @@ static void CheckInetPtonEx() { Require(sceNetInetPtonEx(2, "not an address", address.data(), 0) == 0); Require(Failed(sceNetInetPtonEx(99, "127.0.0.1", address.data(), 0), 47)); Require(Failed(sceNetInetPtonEx(2, nullptr, address.data(), 0), 22)); - Require(Failed(sceNetInetPtonEx(2, "127.0.0.1", nullptr, 0), 22)); + *sceNetErrnoLoc() = 123; + Require(sceNetInetPtonEx(2, "127.0.0.1", nullptr, 0) == 1 && *sceNetErrnoLoc() == 123); + Require(sceNetInetPtonEx(2, "abc", nullptr, 0) == 0 && *sceNetErrnoLoc() == 123); for (const int flags : {1, 0x100, -1}) { address.fill(0); Require(sceNetInetPtonEx(2, "1.2.3", address.data(), flags) == 1); @@ -868,6 +991,96 @@ static void CheckInetPtonEx() { } } +static void CheckEtherAddress() { + struct GuardedAddress { + std::uint8_t before; + NetEtherAddr address; + std::uint8_t after; + }; + static_assert(sizeof(GuardedAddress) == 8); + const GuardedAddress original{0x12, {{0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5}}, 0x34}; + for (const char* text : {"00:01:02:0A:0b:0F", "00-01-02-0A-0b-0F", "00.01 02+0A/0b_0F", "00:01:02:0A:0b:0F trailing"}) { + auto output = original; + const std::array expected{0, 1, 2, 10, 11, 15}; + *sceNetErrnoLoc() = 33; + Require(sceNetEtherStrton(text, &output.address) == 0); + Require(std::memcmp(output.address.data, expected.data(), expected.size()) == 0); + Require(output.before == original.before && output.after == original.after && *sceNetErrnoLoc() == 33); + } + for (unsigned value = 0; value < 256; ++value) { + NetEtherAddr input{}; + for (unsigned index = 0; index < 6; ++index) input.data[index] = static_cast(value + index * 43); + char text[18]{}; + Require(sceNetEtherNtostr(&input, text, sizeof(text)) == 0); + auto output = original; + Require(sceNetEtherStrton(text, &output.address) == 0); + Require(std::memcmp(input.data, output.address.data, sizeof(input.data)) == 0); + Require(output.before == original.before && output.after == original.after); + } + const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:-. +"; + for (std::size_t index = 0; index + 1 < sizeof(digits); ++index) { + const unsigned digit = index < 36 ? index : index < 62 ? index - 26 : 0x7f; + for (std::size_t position = 0; position < 2; ++position) { + std::string text = "01:23:45:67:89:ab"; + text[position] = digits[index]; + const std::array expected{static_cast(position == 0 ? digit * 16 + 1 : digit), 0x23, 0x45, 0x67, 0x89, 0xab}; + auto output = original; + *sceNetErrnoLoc() = 33; + Require(sceNetEtherStrton(text.c_str(), &output.address) == 0); + Require(std::memcmp(output.address.data, expected.data(), expected.size()) == 0); + Require(output.before == original.before && output.after == original.after && *sceNetErrnoLoc() == 33); + } + } + for (const char* text : {"", "0", "0:1:2:a:B:f", "0-1-2-a-B-f", "0:1-2:a-B:f", "0:1:2:3:4", "0:1:2:3:4:", + "0:1:2:3:4:5:6", "000:1:2:3:4:5", "0:1:2:3:4:555", ":0:1:2:3:4:5", "0::1:2:3:4:5", "0.1.2.3.4.5", + "0x0:1:2:3:4:5", "0:1:2:3:4:g", "+0:1:2:3:4:5", " 0:1:2:3:4:5", "0:1:2:3:4:5 ", "0:1:2:3:4:5\n", "001a2bcdeff0"}) { + auto output = original; + Require(Failed(sceNetEtherStrton(text, &output.address), 22)); + Require(std::memcmp(&output, &original, sizeof(output)) == 0); + } + const std::string valid = "00:11:22:33:44:55"; + std::vector fixed(valid.begin(), valid.end()); + auto fixedOutput = original; + Require(sceNetEtherStrton(fixed.data(), &fixedOutput.address) == 0); + const std::array fixedExpected{0, 0x11, 0x22, 0x33, 0x44, 0x55}; + Require(std::memcmp(fixedOutput.address.data, fixedExpected.data(), fixedExpected.size()) == 0); + Require(fixedOutput.before == original.before && fixedOutput.after == original.after); + for (std::size_t length = 0; length < valid.size(); ++length) { + std::vector text(length + 1, '\0'); + std::memcpy(text.data(), valid.data(), length); + auto output = original; + Require(Failed(sceNetEtherStrton(text.data(), &output.address), 22)); + Require(std::memcmp(&output, &original, sizeof(output)) == 0); + } + for (std::size_t position = 0; position < valid.size(); ++position) { + auto text = valid; + text[position] = '\0'; + auto output = original; + Require(Failed(sceNetEtherStrton(text.c_str(), &output.address), 22)); + Require(std::memcmp(&output, &original, sizeof(output)) == 0); + } + auto output = original; + Require(Failed(sceNetEtherStrton(nullptr, &output.address), 22)); + Require(std::memcmp(&output, &original, sizeof(output)) == 0); + Require(Failed(sceNetEtherStrton(valid.c_str(), nullptr), 22)); + Require(Failed(sceNetEtherStrton(nullptr, nullptr), 22)); + for (std::size_t position = 0; position < valid.size(); ++position) { + auto text = valid; + text[position] = static_cast(0xff); + output = original; + bool threw = false; + try { + Require(sceNetEtherStrton(text.c_str(), &output.address) == 0); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw == (position % 3 != 2)); + if (threw) Require(std::memcmp(&output, &original, sizeof(output)) == 0); + else Require(std::memcmp(output.address.data, fixedExpected.data(), fixedExpected.size()) == 0); + Require(output.before == original.before && output.after == original.after); + } +} + static void CheckSystemTime() { *sceNetErrnoLoc() = 22; sceNetGetSystemTime(nullptr); @@ -890,12 +1103,17 @@ static void CheckSystemTime() { } static void CheckUsleep() { - Require(sceNetUsleep(0) == 0); + Require(CaptureStdout([] { Require(sceNetUsleep(0) == 0); }).empty()); const auto start = std::chrono::steady_clock::now(); Require(sceNetUsleep(2000) == 0); Require(std::chrono::steady_clock::now() - start >= std::chrono::microseconds(2000)); - *sceNetErrnoLoc() = 0; - Require(Failed(sceNetUsleep(-1), 22)); + const std::string printed = CaptureStdout([] { + *sceNetErrnoLoc() = 0; + Require(Failed(sceNetUsleep(-1), 22)); + *sceNetErrnoLoc() = 0; + Require(Failed(sceNetUsleep(INT32_MIN), 22)); + }); + Require(printed == "[LIBNET] usleep -1\n[LIBNET] usleep -2147483648\n"); } static void CheckEpollIdent() { @@ -924,6 +1142,39 @@ static void CheckEpollIdent() { Require(sceNetSocketClose(sender) == 0 && sceNetSocketClose(receiver) == 0); } +static void CheckEpollClosedStream() { + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + const int listener = sceNetSocket("closed-stream-listener", 2, 1, 6); + Require(listener >= 0); + Require(sceNetBind_nid_postfix(listener, address.data(), address.size()) == 0 && sceNetListen(listener, 1) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(listener, address.data(), &size) == 0); + const int client = sceNetSocket("closed-stream-client", 2, 1, 6); + Require(client >= 0 && sceNetConnect(client, address.data(), address.size()) == 0); + const int accepted = sceNetAccept(listener, nullptr, nullptr); + Require(accepted >= 0); + const int epoll = sceNetEpollCreate("epoll-closed-stream", 0); + Require(epoll >= 0); + NetEpollEvent registration{}; + registration.events = 1; + registration.ident = static_cast(accepted); + Require(sceNetEpollControl(epoll, 1, accepted, ®istration) == 0); + Require(sceNetSocketClose(client) == 0); + NetEpollEvent ready{}; + Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1 && ready.events == 1); + char byte = 0; + Require(sceNetRecv(accepted, &byte, 1, 0) == 0); + registration.events = 3; + Require(sceNetEpollControl(epoll, 2, accepted, ®istration) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1 && ready.events == 3); + Require(sceNetShutdown(accepted, 1) == 0); + registration.events = 1; + Require(sceNetEpollControl(epoll, 2, accepted, ®istration) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1 && ready.events == (1 | 16)); + Require(sceNetEpollDestroy(epoll) == 0); + Require(sceNetSocketClose(accepted) == 0 && sceNetSocketClose(listener) == 0); +} + static void CheckTcpNoDelay() { const int stream = sceNetSocket("tcp-nodelay", 2, 1, 6); const int datagram = sceNetSocket("tcp-nodelay-udp", 2, 2, 17); @@ -976,6 +1227,13 @@ static bool HostIpv6LoopbackUnavailable() { #endif } +static void CheckRejectedAddressText(const char* text) { + std::array address; + address.fill(0xa5); + Require(sceNetInetPton(28, text, address.data()) == 0); + Require(address[0] == 0xa5 && address[15] == 0xa5); +} + static void CheckLinkLocalAllNodesIpv6() { Require(reinterpret_cast(sce_net_in6addr_linklocal_allnodes) % alignof(std::uint32_t) == 0); std::array address{}; @@ -1320,7 +1578,7 @@ static std::vector RequireSockInfoList(int flags) { int socket = 0; std::memcpy(&socket, &listed[0x24], sizeof(socket)); Require(sockets.empty() || socket > sockets.back()); - std::array single; + alignas(NetSockInfo6) std::array single; Require(sceNetGetSockInfo(socket, single.data(), 1, 0) == 1 && std::equal(single.begin(), single.end(), listed)); sockets.push_back(socket); } @@ -1367,7 +1625,7 @@ static void CheckSockInfo() { Require(sceNetSetsockopt(datagram, 0xffff, 0x1001, &buffers[1], sizeof(buffers[1])) == 0); const auto configured = RequireSockInfo(datagram, 2, 0x200001, datagramAddress, listening); Require(std::memcmp(configured.data() + 0x64, buffers.data(), sizeof(buffers)) == 0); - std::array record; + alignas(NetSockInfo6) std::array record; record.fill(0xa5); Require(Failed(sceNetGetSockInfo(datagram, nullptr, 1, 0), 22)); Require(Failed(sceNetGetSockInfo(datagram, record.data(), 0, 0), 22)); @@ -1390,6 +1648,185 @@ static void CheckSockInfo() { Require(RequireSockInfoList(0) == earlier); } +static std::array RequireSockInfo6(int socket, int type, bool ipv6, bool nonblock, const std::array& local, + const std::array& remote, int requestFlags) { + std::array records; + records.fill(0xa5); + Require(sceNetGetSockInfo6(socket, reinterpret_cast(records.data()), 2, requestFlags) == 1); + std::array expected{}; + std::memcpy(expected.data() + 0x24, &socket, sizeof(socket)); + expected[0x28] = static_cast(type); + if (ipv6) { + std::memcpy(expected.data() + 0x6c, local.data() + 8, 16); + std::memcpy(expected.data() + 0x7c, remote.data() + 8, 16); + } else { + std::memcpy(expected.data() + 0x34, local.data() + 4, 4); + std::memcpy(expected.data() + 0x38, remote.data() + 4, 4); + } + std::memcpy(expected.data() + 0x3c, local.data() + 2, 2); + std::memcpy(expected.data() + 0x3e, remote.data() + 2, 2); + const int flags = 1 | (nonblock ? 0x200000 : 0) | (ipv6 ? 0x400000 : 0); + std::memcpy(expected.data() + 0x48, &flags, sizeof(flags)); + for (const auto [offset, option] : {std::array{0x64, 0x1002}, std::array{0x68, 0x1001}}) { + int size = 0; + std::uint32_t length = sizeof(size); + Require(sceNetGetsockopt(socket, 0xffff, option, &size, &length) == 0 && size > 0); + std::memcpy(expected.data() + offset, &size, sizeof(size)); + } + Require(std::equal(expected.begin(), expected.end(), records.begin())); + Require(std::all_of(records.begin() + 0xa0, records.end(), [](auto byte) { return byte == 0xa5; })); + return expected; +} + +static std::vector RequireSockInfo6List(int flags) { + const int count = sceNetGetSockInfo6(-1, nullptr, 0, flags); + Require(count >= 0); + std::vector records(static_cast(count + 1) * 0xa0, 0xa5); + Require(sceNetGetSockInfo6(-1, reinterpret_cast(records.data()), count + 1, flags) == count); + Require(std::all_of(records.end() - 0xa0, records.end(), [](auto byte) { return byte == 0xa5; })); + std::vector unbounded(records.size(), 0xa5); + Require(sceNetGetSockInfo6(-1, reinterpret_cast(unbounded.data()), INT32_MAX, flags) == count && unbounded == records); + std::vector sockets; + for (int index = 0; index < count; ++index) { + const auto listed = records.begin() + index * 0xa0; + int socket = 0; + int status = 0; + std::memcpy(&socket, &listed[0x24], sizeof(socket)); + std::memcpy(&status, &listed[0x48], sizeof(status)); + Require(sockets.empty() || socket > sockets.back()); + Require((flags & 0x1000) != 0 || (status & 0x400000) != 0); + alignas(NetSockInfo6) std::array single; + Require(sceNetGetSockInfo6(socket, reinterpret_cast(single.data()), 1, flags) == 1 && std::equal(single.begin(), single.end(), listed)); + sockets.push_back(socket); + } + for (const int limit : {1, 2}) { + const int written = std::min(count, limit); + std::vector limited(records.size(), 0xa5); + Require(sceNetGetSockInfo6(-1, reinterpret_cast(limited.data()), limit, flags) == written); + Require(std::equal(limited.begin(), limited.begin() + written * 0xa0, records.begin())); + Require(std::all_of(limited.begin() + written * 0xa0, limited.end(), [](auto byte) { return byte == 0xa5; })); + } + return sockets; +} + +static std::vector WithSockets(std::vector sockets, std::initializer_list added) { + sockets.insert(sockets.end(), added); + std::sort(sockets.begin(), sockets.end()); + return sockets; +} + +static void CheckSockInfo6() { + const auto earlier = RequireSockInfo6List(0); + const auto earlierAll = RequireSockInfo6List(0x1000); + Require(RequireSockInfo6List(0x40) == earlier && RequireSockInfo6List(0x1040) == earlierAll); + Require(std::includes(earlierAll.begin(), earlierAll.end(), earlier.begin(), earlier.end())); + + const std::array unnamed{}; + const int datagram = sceNetSocket("sockinfo6-inet", 2, 2, 17); + Require(datagram >= 0); + std::array inetAddress{16, 2, 0, 0, 127, 0, 0, 1}; + Require(sceNetBind_nid_postfix(datagram, inetAddress.data(), 16) == 0); + std::uint32_t size = 16; + Require(sceNetGetsockname(datagram, inetAddress.data(), &size) == 0 && size == 16); + const int nonblocking = 1; + Require(sceNetSetsockopt(datagram, 0xffff, 0x1200, &nonblocking, sizeof(nonblocking)) == 0); + RequireSockInfo6(datagram, 2, false, true, inetAddress, unnamed, 0x1000); + RequireSockInfo6(datagram, 2, false, true, inetAddress, unnamed, 0x1040); + + alignas(NetSockInfo6) std::array record; + record.fill(0xa5); + const auto untouched = [&record] { return std::all_of(record.begin(), record.end(), [](auto byte) { return byte == 0xa5; }); }; + Require(Failed(sceNetGetSockInfo6(datagram, nullptr, 1, 0x1000), 22)); + Require(Failed(sceNetGetSockInfo6(datagram, reinterpret_cast(record.data()), 0, 0x1000), 22)); + Require(Failed(sceNetGetSockInfo6(datagram, reinterpret_cast(record.data()), -1, 0x1000), 22)); + Require(Failed(sceNetGetSockInfo6(-1, reinterpret_cast(record.data()), 0, 0), 22)); + Require(Failed(sceNetGetSockInfo6(-1, reinterpret_cast(record.data()), -5, 0x1000), 22)); + for (const int flags : {1, 0x41, 0x80, 0x1001, 0x2000, 0x400000, 0x1048}) { + Require(Failed(sceNetGetSockInfo6(datagram, reinterpret_cast(record.data()), 1, flags), 22)); + Require(Failed(sceNetGetSockInfo6(-1, nullptr, 0, flags), 22)); + Require(Failed(sceNetGetSockInfo6(-1, reinterpret_cast(record.data()), 1, flags), 22)); + } + Require(Failed(sceNetGetSockInfo6(datagram, reinterpret_cast(record.data()), 1, 0), 47)); + Require(Failed(sceNetGetSockInfo6(datagram, reinterpret_cast(record.data()), 1, 0x40), 47)); + Require(untouched()); + const int closed = sceNetSocket("sockinfo6-closed", 2, 2, 17); + Require(closed >= 0 && sceNetSocketClose(closed) == 0); + for (const int flags : {0, 0x40, 0x1000, 0x1040}) { + Require(Failed(sceNetGetSockInfo6(closed, reinterpret_cast(record.data()), 1, flags), 9)); + Require(Failed(sceNetGetSockInfo6(INT32_MAX, reinterpret_cast(record.data()), 1, flags), 9)); + } + Require(untouched()); + Require(RequireSockInfo6List(0) == earlier); + Require(RequireSockInfo6List(0x1000) == WithSockets(earlierAll, {datagram})); + + if (ipv6Unavailable) { + Require(sceNetSocketClose(datagram) == 0); + Require(Failed(sceNetGetSockInfo6(datagram, reinterpret_cast(record.data()), 1, 0x1000), 9)); + Require(RequireSockInfo6List(0x1000) == earlierAll); + return; + } + + const int udp = sceNetSocket("sockinfo6-datagram", 28, 2, 17); + Require(udp >= 0); + RequireSockInfo6(udp, 2, true, false, unnamed, unnamed, 0); + std::array udpAddress{28, 28}; + udpAddress[23] = 1; + Require(sceNetBind_nid_postfix(udp, udpAddress.data(), udpAddress.size()) == 0); + size = udpAddress.size(); + Require(sceNetGetsockname(udp, udpAddress.data(), &size) == 0 && size == udpAddress.size()); + Require(udpAddress[2] != 0 || udpAddress[3] != 0); + RequireSockInfo6(udp, 2, true, false, udpAddress, unnamed, 0); + RequireSockInfo6(udp, 2, true, false, udpAddress, unnamed, 0x40); + RequireSockInfo6(udp, 2, true, false, udpAddress, unnamed, 0x1000); + RequireSockInfo6(udp, 2, true, false, udpAddress, unnamed, 0x1040); + + const int listener = sceNetSocket("sockinfo6-listener", 28, 1, 6); + std::array listening{28, 28}; + listening[23] = 1; + Require(listener >= 0 && sceNetBind_nid_postfix(listener, listening.data(), listening.size()) == 0 && sceNetListen(listener, 1) == 0); + size = listening.size(); + Require(sceNetGetsockname(listener, listening.data(), &size) == 0 && size == listening.size()); + RequireSockInfo6(listener, 1, true, false, listening, unnamed, 0); + const int client = sceNetSocket("sockinfo6-client", 28, 1, 6); + Require(client >= 0 && sceNetConnect(client, listening.data(), listening.size()) == 0); + const int accepted = sceNetAccept(listener, nullptr, nullptr); + Require(accepted >= 0); + std::array clientAddress{}; + size = clientAddress.size(); + Require(sceNetGetsockname(client, clientAddress.data(), &size) == 0 && size == clientAddress.size()); + Require(clientAddress[2] != 0 || clientAddress[3] != 0); + RequireSockInfo6(client, 1, true, false, clientAddress, listening, 0); + RequireSockInfo6(accepted, 1, true, false, listening, clientAddress, 0); + + const int peer = sceNetSocket("sockinfo6-peer", 28, 2, 17); + std::array peerAddress{28, 28}; + peerAddress[23] = 1; + Require(peer >= 0 && sceNetBind_nid_postfix(peer, peerAddress.data(), peerAddress.size()) == 0); + size = peerAddress.size(); + Require(sceNetGetsockname(peer, peerAddress.data(), &size) == 0 && size == peerAddress.size()); + Require(sceNetConnect(peer, udpAddress.data(), udpAddress.size()) == 0); + RequireSockInfo6(peer, 2, true, false, peerAddress, udpAddress, 0); + const std::array buffers{65536, 32768}; + Require(sceNetSetsockopt(peer, 0xffff, 0x1200, &nonblocking, sizeof(nonblocking)) == 0); + Require(sceNetSetsockopt(peer, 0xffff, 0x1002, &buffers[0], sizeof(buffers[0])) == 0); + Require(sceNetSetsockopt(peer, 0xffff, 0x1001, &buffers[1], sizeof(buffers[1])) == 0); + const auto configured = RequireSockInfo6(peer, 2, true, true, peerAddress, udpAddress, 0); + Require(std::memcmp(configured.data() + 0x64, buffers.data(), sizeof(buffers)) == 0); + Require(std::all_of(configured.begin() + 0x34, configured.begin() + 0x3c, [](auto byte) { return byte == 0; })); + Require(sceNetGetSockInfo6(peer, reinterpret_cast(record.data()), 1, 0x1040) == 1 && std::equal(configured.begin(), configured.end(), record.begin())); + + Require(RequireSockInfo6List(0) == WithSockets(earlier, {udp, listener, client, accepted, peer})); + Require(RequireSockInfo6List(0x40) == WithSockets(earlier, {udp, listener, client, accepted, peer})); + Require(RequireSockInfo6List(0x1000) == WithSockets(earlierAll, {datagram, udp, listener, client, accepted, peer})); + Require(Failed(sceNetGetSockInfo6(datagram, reinterpret_cast(record.data()), 1, 0), 47)); + + for (const int descriptor : {peer, accepted, client, listener, udp}) Require(sceNetSocketClose(descriptor) == 0); + Require(Failed(sceNetGetSockInfo6(udp, reinterpret_cast(record.data()), 1, 0), 9)); + Require(RequireSockInfo6List(0) == earlier); + Require(sceNetSocketClose(datagram) == 0); + Require(RequireSockInfo6List(0x1000) == earlierAll); +} + static void CheckDatagramDisconnect() { const int peer = sceNetSocket("disconnect-peer", 2, 2, 17); const int other = sceNetSocket("disconnect-other", 2, 2, 17); @@ -1428,6 +1865,7 @@ static void CheckDatagramDisconnect() { } int main() { + CheckEtherAddress(); const auto epoch = std::chrono::steady_clock::time_point{}; Require(KernelSocketSelect::DeadlineAfter(epoch, 2, 250000) == epoch + std::chrono::milliseconds(2250)); Require(KernelSocketSelect::DeadlineAfter(epoch, INT64_MAX, 0) == std::chrono::steady_clock::time_point::max()); @@ -1439,6 +1877,18 @@ int main() { Require(in6addr_any_nid_postfix[i] == 0); Require(in6addr_loopback_nid_postfix[i] == (i == 15 ? 1 : 0)); } + for (const int flags : {0, 1, -1}) { + Require(sceNetClearDnsCache(flags) == static_cast(0x804101C8)); + Require(*sceNetErrnoLoc() == 200); + } + std::uint32_t randomOutput = 0x12345678; + Require(Failed(sceNetGetRandom(&randomOutput), 200)); + Require(randomOutput == 0x12345678); + Require(Failed(sceNetGetRandom(nullptr), 200)); + Require(Failed(sceNetUsleep(0), 200)); + Require(Failed(sceNetUsleep(-1), 200)); + Require(Failed(sceNetShowNetstat(), 200)); + Require(Failed(sceNetShowIfconfig(), 200)); Require(sceNetInit_nid_postfix() == 0); CheckEpollWritable(); CheckSocketHostErrors(); @@ -1448,10 +1898,16 @@ int main() { CheckNetMemory(); CheckNetstat(); CheckEmptyEpoll(); + CheckRejectedArguments(); CheckDontWait(); CheckAddressText(2, "127.0.0.1"); CheckAddressText(2, "255.255.255.255"); CheckAddressText(28, "::1"); + CheckAddressText(28, "::ffff:0.0.0.1"); + CheckAddressText(28, "::ffff:0:102:304"); + CheckAddressText(28, "fe80::5efe:102:304"); + CheckRejectedAddressText("::01.2.3.4"); + CheckRejectedAddressText("::ffff:01.2.3.4"); CheckAddressText(28, "1234:5678:9abc:def0:1234:5678:9abc:def0"); CheckInetPtonWhitespace(); CheckInetPtonExParts(); @@ -1459,6 +1915,7 @@ int main() { CheckUsleep(); CheckSystemTime(); CheckEpollIdent(); + CheckEpollClosedStream(); CheckTcpNoDelay(); CheckLinkLocalAllNodesIpv6(); CheckUnboundSocketName(); @@ -1476,6 +1933,7 @@ int main() { CheckDatagramDisconnect(); CheckCloseKernelSocket(); CheckSockInfo(); + CheckSockInfo6(); const int closed = sceNetSocket("closed-by-kernel", 2, 2, 17); Require(closed >= 0); @@ -1488,6 +1946,7 @@ int main() { CheckKernelTimeouts(); CheckSocketError(); CheckKernelStatusFlags(); + CheckIoctl(); const int listener = sceNetSocket(nullptr, 2, 1, 6); Require(listener >= 0); @@ -1633,12 +2092,12 @@ int main() { Require(select_nid_postfix(std::max(kernel_socket, udp_sender) + 1, nullptr, mixed_writable, nullptr, wait_second) == 2); Require(((mixed_writable[kernel_socket / 64] >> (kernel_socket % 64)) & 1u) != 0); Require(((mixed_writable[udp_sender / 64] >> (udp_sender % 64)) & 1u) != 0); - GuestPollEntry net_read{udp_receiver, 1, 0}; + GuestPollDescriptor net_read{udp_receiver, 1, 0}; Require(poll_nid_postfix(&net_read, 1, 0) == 0 && net_read.revents == 0); Require(sceNetSendto(udp_sender, datagram, sizeof(datagram), 0, address.data(), address.size()) == sizeof(datagram)); Require(poll_nid_postfix(&net_read, 1, 1000) == 1 && net_read.revents == 1); Require(sceNetRecvfrom(udp_receiver, datagram_result, sizeof(datagram_result), 0, nullptr, nullptr) == sizeof(datagram_result)); - GuestPollEntry mixed_poll[2]{{kernel_socket, 4, 0}, {udp_sender, 4, 0}}; + GuestPollDescriptor mixed_poll[2]{{kernel_socket, 4, 0}, {udp_sender, 4, 0}}; Require(poll_nid_postfix(mixed_poll, 2, 1000) == 2 && mixed_poll[0].revents == 4 && mixed_poll[1].revents == 4); Require(close_nid_postfix(kernel_socket) == 0); std::uint64_t stdio_readable[16]{1}; @@ -1648,6 +2107,7 @@ int main() { stdio_readable[0] = 1; Require(select_nid_postfix(1, stdio_readable, nullptr, nullptr, poll_now) == 0 && stdio_readable[0] == 0); CheckReceiveTimeout(udp_receiver, udp_sender, address); + CheckStreamReceiveTimeout(); CheckMicrosecondTimeouts(udp_receiver); CheckMessages(udp_receiver, udp_sender, address); Require(sceNetSocketClose(udp_sender) == 0); @@ -1658,8 +2118,13 @@ int main() { int resolver_error = -1; Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0); std::array ipv4{}; - Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.invalid.", ipv4.data(), 5000000, 1, 0) == - static_cast(0x804101E1)); + ipv4.fill(0xA5); + { + ScopedResolverLookupOverride lookupOverride(FailResolverLookup); + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.invalid.", ipv4.data(), 5000000, 1, 0) == + static_cast(0x804101E1)); + } + Require(std::all_of(ipv4.begin(), ipv4.end(), [](std::uint8_t byte) { return byte == 0xA5; })); Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast(0x804101E1)); Require(sceNetResolverStartNtoa(resolver, nullptr, ipv4.data(), 5000000, 1, 0) == static_cast(0x80410116)); @@ -1673,13 +2138,15 @@ int main() { Require(std::all_of(resolved6.begin(), resolved6.end() - 1, [](std::uint8_t byte) { return byte == 0; }) && resolved6[15] == 1); Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0); Require(sceNetResolverStartNtoa6(resolver, nullptr, resolved6.data(), 5000000, 1, 0) == static_cast(0x80410116)); + const auto resolved6BeforeFailure = resolved6; Require(sceNetResolverStartNtoa6(resolver, "127.0.0.1", resolved6.data(), 5000000, 1, 0) == static_cast(0x804101E1)); + Require(resolved6 == resolved6BeforeFailure); Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast(0x804101E1)); Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0); Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0); - std::array records{}; + alignas(NetResolverInfo) std::array records{}; records.fill(0xA5); - Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 5000000, 1, 0) == 0); + Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", reinterpret_cast(records.data()), 5000000, 1, 0) == 0); std::int32_t record_family = 0; std::int32_t record_count = 0; std::int32_t record_count4 = 0; @@ -1690,34 +2157,44 @@ int main() { Require(std::all_of(records.begin() + 32 * record_count, records.begin() + 320, [](std::uint8_t byte) { return byte == 0; })); Require(std::all_of(records.begin() + 328, records.begin() + 384, [](std::uint8_t byte) { return byte == 0; })); Require(std::all_of(records.begin() + 384, records.end(), [](std::uint8_t byte) { return byte == 0xA5; })); + const auto resolvedRecords = records; for (int first = 0; first < record_count; ++first) { for (int second = first + 1; second < record_count; ++second) { Require(std::memcmp(records.data() + 32 * first, records.data() + 32 * second, 4) != 0); } } const auto resolved = records; - Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, nullptr, records.data(), 5000000, 1, 0) == + Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, nullptr, reinterpret_cast(records.data()), 5000000, 1, 0) == static_cast(0x80410116) && *sceNetErrnoLoc() == 22 && records == resolved); Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", nullptr, 5000000, 1, 0) == static_cast(0x80410116) && *sceNetErrnoLoc() == 22); - Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "guest-sce-net.invalid.", records.data(), 5000000, 1, 0) == - static_cast(0x804101E1) && records == resolved); + { + ScopedResolverLookupOverride lookupOverride(FailResolverLookup); + Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "guest-sce-net.invalid.", reinterpret_cast(records.data()), 5000000, 1, 0) == + static_cast(0x804101E1)); + } + Require(records == resolvedRecords); - std::array plain{}; + alignas(NetResolverInfo) std::array plain{}; plain.fill(0xA5); - Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) == 0); + Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", reinterpret_cast(plain.data()), 5000000, 1, 0) == 0); Require(plain == resolved); Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0); - Require(sceNetResolverStartNtoaMultipleRecords(resolver, nullptr, plain.data(), 5000000, 1, 0) == + Require(sceNetResolverStartNtoaMultipleRecords(resolver, nullptr, reinterpret_cast(plain.data()), 5000000, 1, 0) == static_cast(0x80410116) && *sceNetErrnoLoc() == 22 && plain == resolved); Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", nullptr, 5000000, 1, 0) == static_cast(0x80410116) && *sceNetErrnoLoc() == 22); - Require(sceNetResolverStartNtoaMultipleRecords(resolver, "guest-sce-net.invalid", plain.data(), 5000000, 1, 0) == - static_cast(0x804101E1) && plain == resolved); + const auto resolvedPlain = plain; + { + ScopedResolverLookupOverride lookupOverride(FailResolverLookup); + Require(sceNetResolverStartNtoaMultipleRecords(resolver, "guest-sce-net.invalid", reinterpret_cast(plain.data()), 5000000, 1, 0) == + static_cast(0x804101E1)); + } + Require(plain == resolvedPlain); Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast(0x804101E1)); for (const int flags : {0x1, 0x10000}) { bool threw = false; - try { sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, flags); } + try { sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", reinterpret_cast(plain.data()), 5000000, 1, flags); } catch (const std::runtime_error&) { threw = true; } Require(threw && plain == resolved); } @@ -1741,11 +2218,11 @@ int main() { Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0 && ipv4[0] == 127); Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0); Require(sceNetResolverAbort(resolver, 1) == 0); - Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) == + Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", reinterpret_cast(plain.data()), 5000000, 1, 0) == static_cast(0x80410104) && *sceNetErrnoLoc() == 4); - Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) == 0 && plain == resolved); + Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", reinterpret_cast(plain.data()), 5000000, 1, 0) == 0 && plain == resolved); Require(sceNetResolverAbort(resolver, 2) == 0); - Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 5000000, 1, 0) == 0 && records == resolved); + Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", reinterpret_cast(records.data()), 5000000, 1, 0) == 0 && records == resolved); std::memset(reverse, 0x5A, sizeof(reverse)); Require(sceNetResolverStartAton(resolver, loopback.data(), reverse, sizeof(reverse), 5000000, 1, 0) == static_cast(0x80410104) && *sceNetErrnoLoc() == 4 && reverse[0] == 0x5A); @@ -1754,7 +2231,7 @@ int main() { Require(sceNetResolverAbort(resolver, 3) == 0); Require(sceNetResolverStartAton(resolver, loopback.data(), reverse, sizeof(reverse), 5000000, 1, 0) == static_cast(0x80410104) && *sceNetErrnoLoc() == 4); - Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 5000000, 1, 0) == + Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", reinterpret_cast(records.data()), 5000000, 1, 0) == static_cast(0x80410104) && *sceNetErrnoLoc() == 4 && records == resolved); Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0); for (const int flags : {0x4, -1}) { @@ -1814,7 +2291,7 @@ int main() { const int other = sceNetResolverCreate("guest-sce-net-other", 0, 0); Require(other >= 0 && other != resolver); Require(sceNetResolverAbort(other, 3) == 0); - Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) == 0); + Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", reinterpret_cast(plain.data()), 5000000, 1, 0) == 0); Require(sceNetResolverDestroy(other) == 0); Require(sceNetResolverAbort(other, 0) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9); Require(sceNetResolverAbort(-1, 1) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9); @@ -1823,18 +2300,17 @@ int main() { ipv4.fill(0xA5); Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0 && ipv4[0] == 127); Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0); - for (const int flags : {0x1, -1}) { - bool threw = false; - try { sceNetClearDnsCache(flags); } catch (const std::runtime_error&) { threw = true; } - Require(threw); + for (const int flags : {1, 2, 0x10000, -1}) { + Require(sceNetClearDnsCache(flags) == static_cast(0x80410116)); + Require(*sceNetErrnoLoc() == 22); } Require(sceNetResolverDestroy(resolver) == 0); resolver_error = -1; Require(sceNetResolverGetError(resolver, &resolver_error) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9 && resolver_error == -1); - Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 5000000, 1, 0) == + Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", reinterpret_cast(records.data()), 5000000, 1, 0) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9 && records == resolved); - Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) == + Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", reinterpret_cast(plain.data()), 5000000, 1, 0) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9 && plain == resolved); Require(sceNetResolverAbort(resolver, 1) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9); @@ -1848,6 +2324,7 @@ int main() { if (!ipv6Unavailable) CheckStreamSender(28); int state = -1; + Require(sceNetCtlInit() == 0); Require(sceNetCtlGetState(&state) == 0); Require(state == 0 || state == 3); Require(sceNetCtlGetIfStat(nullptr) == static_cast(0x80412107)); @@ -1870,7 +2347,7 @@ int main() { { std::array dns{}; dns.fill(0xA5A5A5A5u); - const int count = sceNetGetDnsInfo(dns.data(), 0); + const int count = sceNetGetDnsInfo(reinterpret_cast(dns.data()), 0); Require(count >= 0 && count <= 2 && dns[2] == 0xA5A5A5A5u); Require(count == 0 || state == 3); for (int index = count; index < 2; ++index) Require(dns[index] == 0); @@ -1887,13 +2364,11 @@ int main() { Require(std::strcmp(text, reinterpret_cast(info.opaque)) == 0); } for (const int flags : {0x1, -1}) { - bool threw = false; - try { sceNetGetDnsInfo(dns.data(), flags); } catch (const std::runtime_error&) { threw = true; } - Require(threw); + const auto before = dns; + Require(Failed(sceNetGetDnsInfo(reinterpret_cast(dns.data()), flags), 22)); + Require(dns == before); } - bool threw = false; - try { sceNetGetDnsInfo(nullptr, 0); } catch (const std::runtime_error&) { threw = true; } - Require(threw); + Require(Failed(sceNetGetDnsInfo(nullptr, 0), 22)); } NetCtlIfStat first{}; first.device = 7; @@ -1946,6 +2421,46 @@ int main() { } Require(sceNetCtlUnregisterCallbackV6(0) == 0); } + { + int cid = -1; + Require(sceNetCtlRegisterCallbackForNpToolkit(nullptr, nullptr, &cid) == static_cast(0x80412107)); + Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, nullptr) == static_cast(0x80412107)); + int cids[8] = {}; + for (int i = 0; i < 8; ++i) { + Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, &cids[i]) == 0); + Require(cids[i] == i); + } + Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, &cid) == static_cast(0x80412103)); + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, &cid) == 0 && cid == 0); + Require(sceNetCtlUnregisterCallback(0) == 0); + Require(sceNetCtlUnregisterCallbackForNpToolkit(-1) == static_cast(0x80412105)); + Require(sceNetCtlUnregisterCallbackForNpToolkit(8) == static_cast(0x80412105)); + Require(sceNetCtlUnregisterCallbackForNpToolkit(3) == 0); + Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, &cid) == 0); + Require(cid == 3); + for (int i = 0; i < 8; ++i) { + Require(sceNetCtlUnregisterCallbackForNpToolkit(i) == 0); + } + + NetCtlEventRecord toolkit; + NetCtlEventRecord plain; + Require(sceNetCtlRegisterCallbackForNpToolkit(RecordNetCtlEvent, &toolkit, &cid) == 0 && cid == 0); + Require(sceNetCtlCheckCallback() == 0 && toolkit.calls == 0); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0); + Require(toolkit.calls == 1 && toolkit.arg == &toolkit && (toolkit.event == 1 || toolkit.event == 3)); + int current = -1; + Require(sceNetCtlGetState(¤t) == 0 && toolkit.event == (current == 3 ? 3 : 1)); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && toolkit.calls == 1); + int plainCid = -1; + Require(sceNetCtlRegisterCallback(RecordNetCtlEvent, &plain, &plainCid) == 0); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && plain.calls == 0); + Require(sceNetCtlCheckCallback() == 0 && plain.calls == 1 && toolkit.calls == 1); + Require(sceNetCtlUnregisterCallback(plainCid) == 0); + Require(sceNetCtlUnregisterCallbackForNpToolkit(cid) == 0); + Require(sceNetCtlRegisterCallbackForNpToolkit(RecordNetCtlEvent, &toolkit, &cid) == 0); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && toolkit.calls == 2); + Require(sceNetCtlUnregisterCallbackForNpToolkit(cid) == 0); + } if (ipv6Unavailable) { std::puts("skipped the IPv6 socket checks, the host has no usable IPv6 loopback"); return 77; diff --git a/core/libs/tests/GuestSceV2Exceptions.S b/core/libs/tests/GuestSceV2Exceptions.S new file mode 100644 index 0000000000..8b076dc726 --- /dev/null +++ b/core/libs/tests/GuestSceV2Exceptions.S @@ -0,0 +1,72 @@ +.cfi_sections .eh_frame +.text +.globl SceV2Catch +SceV2Catch: + .cfi_startproc +#ifdef _WIN32 + .cfi_personality 0x00, __gxx_personality_v0_nid_postfix + .cfi_lsda 0x00, .Lcatch_lsda +#else + .cfi_personality 0x9b, .Lcatch_personality + .cfi_lsda 0x1b, .Lcatch_lsda +#endif + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + movq %rsp, %rbp + .cfi_def_cfa_register %rbp + subq $16, %rsp + movq %rdi, %rax + movq %rsi, %rdi +.Lcatch_call: + call *%rax +.Lcatch_call_end: + ud2 +.Lcatch_landing: + movl %edx, SceV2Selector(%rip) + movq %rax, %rdi + call __cxa_begin_catch_nid_postfix + movq %rax, %rdi + call SceV2Inspect + call __cxa_end_catch_nid_postfix + leave + .cfi_def_cfa %rsp, 8 + ret + .cfi_endproc +#ifdef _WIN32 +.section .rdata,"dr" +#else +.section .data.rel.ro,"aw",@progbits +.p2align 3 +.Lcatch_personality: + .quad __gxx_personality_v0_nid_postfix +.section .rodata +#endif +.Lcatch_lsda: +#ifdef _WIN32 + .byte 0xff, 0x80 +#else + .byte 0xff, 0x9b +#endif + .uleb128 .Lcatch_types_end-.Lcatch_types_offset +.Lcatch_types_offset: + .byte 0x01 + .uleb128 .Lcatch_calls_end-.Lcatch_calls +.Lcatch_calls: + .uleb128 .Lcatch_call-SceV2Catch + .uleb128 .Lcatch_call_end-.Lcatch_call + .uleb128 .Lcatch_landing-SceV2Catch + .uleb128 1 +.Lcatch_calls_end: + .byte 1, 1, 2, 0 +#ifdef _WIN32 + .quad SceV2CatchType + .quad SceV2OtherType +#else + .long SceV2CatchType-. + .long SceV2OtherType-. +#endif +.Lcatch_types_end: +#ifndef _WIN32 +.section .note.GNU-stack,"",@progbits +#endif diff --git a/core/libs/tests/GuestSceV2Exceptions.cpp b/core/libs/tests/GuestSceV2Exceptions.cpp new file mode 100644 index 0000000000..6683c8d67b --- /dev/null +++ b/core/libs/tests/GuestSceV2Exceptions.cpp @@ -0,0 +1,192 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; }; +static_assert(sizeof(LibcException::ExceptionObject) == 16 && sizeof(LibcException::SystemErrorObject) == 32); + +using GuestWhat = const char* (APS5_VABI *)(const LibcException::ExceptionObject*); +using Thrower = void (APS5_VABI *)(const void*); + +extern "C" { +extern const TypeRecord _ZTISt9exception_nid_postfix; +extern const TypeRecord _ZTISt9bad_alloc_nid_postfix; +extern const TypeRecord _ZTISt12length_error_nid_postfix; +extern const TypeRecord _ZTISt12system_error_nid_postfix; +extern const TypeRecord _ZTINSt13__sce_cxxvers16_Error_exceptionE_nid_postfix; + +void APS5_VABI SceV2Catch(Thrower thrower, const void* argument); + +const void* SceV2CatchType = nullptr; +const void* SceV2OtherType = nullptr; +int SceV2Selector = 0; +const void* SceV2Caught = nullptr; +} + +static unsigned failures = 0; + +static void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "FAIL: %s\n", what); + ++failures; +} + +static const TypeRecord* TypeOf(const LibcException::ExceptionObject* object) { + return static_cast(static_cast(object->vtable)[-1]); +} + +static const char* What(const LibcException::ExceptionObject* object) { + return reinterpret_cast(const_cast(static_cast(object->vtable)[2]))(object); +} + +static TypeRecord Named(const char* name) { + return {_ZTISt9exception_nid_postfix.vtable, name, nullptr}; +} + +static char inspected[256]; +static std::size_t inspectBytes = sizeof(LibcException::ExceptionObject); +static LibcException::SystemErrorObject inspectedSystem {}; + +extern "C" void APS5_VABI SceV2Inspect(void* object) { + const auto* exception = static_cast(object); + std::snprintf(inspected, sizeof(inspected), "%s|%s", TypeOf(exception)->name, What(exception)); + std::memcpy(&inspectedSystem, object, inspectBytes); +} + +struct Case { const char* label; Thrower thrower; const void* argument; const void* catchType; const void* otherType; int selector; const char* expected; }; + +static void Run(const Case& test) { + SceV2CatchType = test.catchType; + SceV2OtherType = test.otherType; + SceV2Selector = 0; + inspected[0] = 0; + SceV2Catch(test.thrower, test.argument); + Require(SceV2Selector == test.selector, test.label); + if (std::strcmp(inspected, test.expected) != 0) { + std::fprintf(stderr, " %s: got '%s', want '%s'\n", test.label, inspected, test.expected); + ++failures; + } +} + +template +static Thrower AsThrower(TFunction function) { + return reinterpret_cast(function); +} + +static void CheckThrowers() { + const TypeRecord logicError = Named("NSt8__sce_v211logic_errorE"); + const TypeRecord runtimeError = Named("NSt8__sce_v213runtime_errorE"); + const TypeRecord lengthError = Named("NSt8__sce_v212length_errorE"); + const TypeRecord systemError = Named("NSt8__sce_v212system_errorE"); + const TypeRecord unrelated = Named("NSt8__sce_v211range_errorE"); + const void* exception = &_ZTISt9exception_nid_postfix; + const void* errorException = &_ZTINSt13__sce_cxxvers16_Error_exceptionE_nid_postfix; + const Case cases[] { + {"_Xlength_error", AsThrower(_ZNSt8__sce_v214_Xlength_errorEPKc_nid_postfix), "length", &logicError, &unrelated, 2, + "NSt8__sce_v212length_errorE|length"}, + {"_Xlength_error is not std::length_error", AsThrower(_ZNSt8__sce_v214_Xlength_errorEPKc_nid_postfix), "length", &lengthError, &_ZTISt12length_error_nid_postfix, 2, + "NSt8__sce_v212length_errorE|length"}, + {"_Xout_of_range", AsThrower(_ZNSt8__sce_v214_Xout_of_rangeEPKc_nid_postfix), "range", errorException, &runtimeError, 2, + "NSt8__sce_v212out_of_rangeE|range"}, + {"_Xinvalid_argument", AsThrower(_ZNSt8__sce_v218_Xinvalid_argumentEPKc_nid_postfix), "argument", &logicError, &runtimeError, 2, + "NSt8__sce_v216invalid_argumentE|argument"}, + {"_Xruntime_error", AsThrower(_ZNSt8__sce_v215_Xruntime_errorEPKc_nid_postfix), "runtime", &runtimeError, &logicError, 2, + "NSt8__sce_v213runtime_errorE|runtime"}, + {"_Xoverflow_error", AsThrower(_ZNSt8__sce_v216_Xoverflow_errorEPKc_nid_postfix), nullptr, exception, &logicError, 2, + "NSt8__sce_v214overflow_errorE|"}, + {"_Xbad_alloc", AsThrower(_ZNSt8__sce_v211_Xbad_allocEv_nid_postfix), nullptr, &_ZTISt9bad_alloc_nid_postfix, errorException, 2, + "St9bad_alloc|std::bad_alloc"}, + {"_Xbad_function_call", AsThrower(_ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix), nullptr, exception, errorException, 2, + "NSt8__sce_v217bad_function_callE|bad function call"}, + {"_Throw(bad_optional_access)", AsThrower(_ZNSt8__sce_v26_ThrowERKNS_19bad_optional_accessE_nid_postfix), nullptr, exception, errorException, 2, + "NSt8__sce_v219bad_optional_accessE|Bad optional access"}, + {"_Throw(bad_weak_ptr)", AsThrower(_ZNSt8__sce_v26_ThrowERKNS_12bad_weak_ptrE_nid_postfix), nullptr, exception, errorException, 2, + "NSt8__sce_v212bad_weak_ptrE|bad_weak_ptr"}, + {"_Throw(bad_variant_access)", AsThrower(_ZNSt8__sce_v26_ThrowERKNS_18bad_variant_accessE_nid_postfix), nullptr, exception, errorException, 2, + "NSt8__sce_v218bad_variant_accessE|bad variant access"}, + {"_Throw_Cpp_error", AsThrower(_ZNSt8__sce_v216_Throw_Cpp_errorEi_nid_postfix), reinterpret_cast(1), &systemError, &_ZTISt12system_error_nid_postfix, 2, + "NSt8__sce_v212system_errorE|invalid argument: invalid argument"}, + }; + inspectBytes = sizeof(void*); + for (const auto& test : cases) Run(test); + inspectBytes = sizeof(LibcException::ExceptionObject); +} + +static void CheckCopies() { + const TypeRecord runtimeError = Named("NSt8__sce_v213runtime_errorE"); + const TypeRecord systemError = Named("NSt8__sce_v212system_errorE"); + const TypeRecord logicError = Named("NSt8__sce_v211logic_errorE"); + + LibcException::ExceptionObject range {}; + _ZNSt13__sce_cxxvers16_Error_exceptionC2EPKc_nid_postfix(&range, "range copy"); + Run({"_Throw(range_error)", AsThrower(_ZNSt8__sce_v26_ThrowERKNS_11range_errorE_nid_postfix), &range, &runtimeError, &logicError, 2, + "NSt8__sce_v211range_errorE|range copy"}); + Require(inspectedSystem.base.message == range.message, "_Throw(range_error) shares the message"); + _ZNSt13__sce_cxxvers16_Error_exceptionD2Ev_nid_postfix(&range); + + LibcException::SystemErrorObject error {}; + _ZNSt13__sce_cxxvers16_Error_exceptionC2EPKc_nid_postfix(&error.base, "system copy"); + error.code = {0, 5, _ZSt16generic_categoryv_nid_postfix()}; + inspectBytes = sizeof(LibcException::SystemErrorObject); + SceV2CatchType = &runtimeError; + SceV2OtherType = &logicError; + SceV2Catch(AsThrower(_ZNSt8__sce_v26_ThrowERKNS_12system_errorE_nid_postfix), &error); + Require(std::strcmp(inspected, "NSt8__sce_v212system_errorE|system copy") == 0, "_Throw(system_error) type and message"); + Require(inspectedSystem.code.value == 5, "_Throw(system_error) copies the code value"); + Require(inspectedSystem.code.category == error.code.category, "_Throw(system_error) copies the category"); + + SceV2CatchType = &systemError; + SceV2Catch(AsThrower(_ZNSt8__sce_v28ios_base6_ThrowERKNS0_7failureE_nid_postfix), &error); + Require(std::strcmp(inspected, "NSt8__sce_v28ios_base7failureE|system copy") == 0, "ios_base::_Throw(failure) type and message"); + Require(inspectedSystem.code.value == 5, "ios_base::_Throw(failure) copies the code"); + inspectBytes = sizeof(LibcException::ExceptionObject); + _ZNSt13__sce_cxxvers16_Error_exceptionD2Ev_nid_postfix(&error.base); +} + +static void CheckErrorException() { + LibcException::ExceptionObject empty {}; + _ZNSt13__sce_cxxvers16_Error_exceptionC1EPKc_nid_postfix(&empty, nullptr); + Require(std::strcmp(_ZNKSt13__sce_cxxvers16_Error_exception4whatEv_nid_postfix(&empty), "") == 0, "_Error_exception(nullptr) has an empty message"); + Require(std::strcmp(TypeOf(&empty)->name, "NSt13__sce_cxxvers16_Error_exceptionE") == 0, "_Error_exception type name"); + Require(TypeOf(&empty)->base == &_ZTISt9exception_nid_postfix, "_Error_exception derives from std::exception"); + + LibcException::ExceptionObject text {}; + _ZNSt13__sce_cxxvers16_Error_exceptionC2EPKc_nid_postfix(&text, "text"); + LibcException::ExceptionObject copy {}; + _ZNSt13__sce_cxxvers16_Error_exceptionC1ERKS0__nid_postfix(©, &text); + Require(std::strcmp(_ZNKSt13__sce_cxxvers16_Error_exception4whatEv_nid_postfix(©), "text") == 0, "_Error_exception copy keeps the message"); + _ZNSt13__sce_cxxvers16_Error_exceptionD2Ev_nid_postfix(&text); + Require(std::strcmp(_ZNKSt13__sce_cxxvers16_Error_exception4whatEv_nid_postfix(©), "text") == 0, "_Error_exception copy outlives the source"); + _ZNSt13__sce_cxxvers16_Error_exceptionD1Ev_nid_postfix(©); + _ZNSt13__sce_cxxvers16_Error_exceptionD1Ev_nid_postfix(&empty); +} + +static void CheckSyserrorMap() { + Require(std::strcmp(_ZNSt8__sce_v213_Syserror_mapEi_nid_postfix(0), "success") == 0, "_Syserror_map(0)"); + Require(_ZSt13_Syserror_mapi_nid_postfix(0) == nullptr, "unversioned _Syserror_map(0) stays null"); + Require(std::strcmp(_ZNSt8__sce_v213_Syserror_mapEi_nid_postfix(2), _ZSt13_Syserror_mapi_nid_postfix(2)) == 0, "_Syserror_map(2) matches the unversioned text"); + Require(std::strcmp(_ZNSt8__sce_v213_Syserror_mapEi_nid_postfix(99999), "unknown error") == 0, "_Syserror_map(unknown)"); + Require(_ZSt13_Syserror_mapi_nid_postfix(99999) == nullptr, "unversioned _Syserror_map(unknown) stays null"); +} + +static void CheckTicks() { + const auto before = _Xtime_get_ticks_nid_postfix(); + const auto ticks = _ZNSt8__sce_v216_Xtime_get_ticksEv_nid_postfix(); + const auto after = _Xtime_get_ticks_nid_postfix(); + Require(before <= ticks && ticks <= after, "_Xtime_get_ticks is the unversioned clock"); +} + +int main() { + CheckErrorException(); + CheckThrowers(); + CheckCopies(); + CheckSyserrorMap(); + CheckTicks(); + if (failures) return 1; + std::puts("guest std::__sce_v2 exceptions: PASS"); + return 0; +} diff --git a/core/libs/tests/GuestSchedPriority.cpp b/core/libs/tests/GuestSchedPriority.cpp index 5c175d9f37..34e03f1f99 100644 --- a/core/libs/tests/GuestSchedPriority.cpp +++ b/core/libs/tests/GuestSchedPriority.cpp @@ -1,23 +1,15 @@ +#include "prx/libkernel_sys/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" int* APS5_VABI __error_nid_postfix(); +#include + extern "C" { -int APS5_VABI sched_get_priority_max_nid_postfix(int policy); -int APS5_VABI sched_get_priority_min_nid_postfix(int policy); -int APS5_VABI getpriority_nid_postfix(int which, int who); -int APS5_VABI setpriority_nid_postfix(int which, int who, int niceval); -struct GuestRtPrio { - std::uint16_t type; - std::uint16_t prio; -}; -int APS5_VABI rtprio_thread_nid_postfix(int function, int lwpid, GuestRtPrio* priority); -int APS5_VABI pthread_setconcurrency_nid_postfix(int newLevel); -int APS5_VABI pthread_getconcurrency_nid_postfix(void); -int APS5_VABI getpid_nid_postfix(void); -int* APS5_VABI __error_nid_postfix(); + } static constexpr int PrioProcess = 0; static constexpr int PrioGroup = 1; @@ -47,12 +39,26 @@ static void RequireSetError(int which, int who, int nice, int error) { } int main() { Require(Rejects(-1) && Rejects(2) && Rejects(5)); + const int self = getpid_nid_postfix(); + KernelTimespec interval{}; + *__error_nid_postfix() = 13; + Require(sched_rr_get_interval_nid_postfix(0, &interval) == 0 && interval.tv_sec == 0 && interval.tv_nsec == 94000000 && *__error_nid_postfix() == 13); + interval.tv_sec = -1; + interval.tv_nsec = -1; + Require(sched_rr_get_interval_nid_postfix(0, &interval) == 0 && interval.tv_sec == 0 && interval.tv_nsec == 94000000); + interval = {}; + Require(sched_rr_get_interval_nid_postfix(self, &interval) == 0 && interval.tv_sec == 0 && interval.tv_nsec == 94000000); + *__error_nid_postfix() = 0; + Require(sched_rr_get_interval_nid_postfix(-1, &interval) == -1 && *__error_nid_postfix() == 3); + *__error_nid_postfix() = 0; + Require(sched_rr_get_interval_nid_postfix(self + 1, &interval) == -1 && *__error_nid_postfix() == 3); + *__error_nid_postfix() = 0; + Require(sched_rr_get_interval_nid_postfix(0, nullptr) == -1 && *__error_nid_postfix() == 14); for (int policy : {1, 3}) { Require(sched_get_priority_max_nid_postfix(policy) == 767); Require(sched_get_priority_min_nid_postfix(policy) == 256); } Require(Rejects(0) && Rejects(4)); - const int self = getpid_nid_postfix(); RequireNice(PrioProcess, 0); RequireNice(PrioProcess, self); RequireNice(PrioGroup, 0); @@ -80,14 +86,12 @@ int main() { Require(setpriority_nid_postfix(PrioProcess, 0, -99) == 0 && getpriority_nid_postfix(PrioProcess, 0) == -20); Require(setpriority_nid_postfix(PrioProcess, 0, 0) == 0 && getpriority_nid_postfix(PrioProcess, 0) == 0); GuestRtPrio rtprio{7, 7}; + Require(rtprio_thread_nid_postfix(0, 0, &rtprio) == 0 && rtprio.type == 10 && rtprio.prio == 700); bool rtprioThrew = false; - try { rtprio_thread_nid_postfix(0, 0, &rtprio); } catch (const std::exception&) { rtprioThrew = true; } - Require(rtprioThrew); - rtprioThrew = false; try { rtprio_thread_nid_postfix(1, 0, &rtprio); } catch (const std::exception&) { rtprioThrew = true; } Require(rtprioThrew); *__error_nid_postfix() = 0; - Require(rtprio_thread_nid_postfix(2, 0, &rtprio) == -1 && *__error_nid_postfix() == 22); + Require(rtprio_thread_nid_postfix(2, 0, &rtprio) == -1 && *__error_nid_postfix() == 3); *__error_nid_postfix() = 0; Require(rtprio_thread_nid_postfix(0, 0, nullptr) == -1 && *__error_nid_postfix() == 14); *__error_nid_postfix() = 0; diff --git a/core/libs/tests/GuestScreenShot.cpp b/core/libs/tests/GuestScreenShot.cpp new file mode 100644 index 0000000000..fe9a121fed --- /dev/null +++ b/core/libs/tests/GuestScreenShot.cpp @@ -0,0 +1,114 @@ +#include "prx/libSceScreenShot/include/Api.hpp" +#include "ProcessParametersFixture.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int ErrorArgument = static_cast(0x80BE0001); +constexpr int ErrorOverlayFile = static_cast(0x80BE0003); +constexpr int ErrorOverlaySize = static_cast(0x80BE000E); + +alignas(16) std::array process{}; +std::string pathPrefix; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "ScreenShot: %s\n", message); + std::abort(); +} + +void InstallProcessParameters(std::uint32_t sdkVersion) { + const std::uint64_t size = process.size(); + const std::uint32_t magic = 0x4942524f; + std::memcpy(process.data(), &size, sizeof(size)); + std::memcpy(process.data() + 8, &magic, sizeof(magic)); + std::memcpy(process.data() + 0x10, &sdkVersion, sizeof(sdkVersion)); + Require(ProcessParametersFixture::Install(process.data(), process.size()), "process parameters were not installed"); +} + +class TempPath { +public: + explicit TempPath(const char* tag) : name(pathPrefix + tag) {} + ~TempPath() { + std::error_code error; + std::filesystem::remove_all(name, error); + } + const char* c_str() const { return name.c_str(); } + void Fill(std::size_t bytes) const { + std::ofstream file(name, std::ios::binary | std::ios::trunc); + const std::string chunk(4096, 'x'); + for (std::size_t written = 0; written < bytes; written += chunk.size()) { + file.write(chunk.data(), static_cast(std::min(chunk.size(), bytes - written))); + } + } + +private: + std::string name; +}; + +void TestDisableState() { + int value = -1; + Require(sceScreenShotIsDisabled(0, &value) == 0 && value == 0, "capture starts enabled"); + Require(sceScreenShotDisable() == 0, "disable"); + Require(sceScreenShotIsDisabled(0, &value) == 0 && value == 1, "disable is reported"); + Require(sceScreenShotEnable() == 0, "enable"); + Require(sceScreenShotIsDisabled(0, &value) == 0 && value == 0, "enable is reported"); + value = 7; + 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"); +} + +void TestOverlay(bool large) { + const int maxX = large ? 3839 : 1919; + const int maxY = large ? 2159 : 1079; + const std::size_t maxSize = large ? 8u << 20 : 2u << 20; + TempPath image("image"); + image.Fill(4096); + Require(sceScreenShotSetOverlayImage(image.c_str(), 0, 0) == 0, "overlay at the origin"); + Require(sceScreenShotSetOverlayImage(image.c_str(), maxX, maxY) == 0, "overlay at the last offsets"); + Require(sceScreenShotSetOverlayImage(image.c_str(), maxX + 1, 0) == ErrorArgument, "x past the screen"); + Require(sceScreenShotSetOverlayImage(image.c_str(), 0, maxY + 1) == ErrorArgument, "y past the screen"); + Require(sceScreenShotSetOverlayImage(image.c_str(), -1, 0) == ErrorArgument, "negative x"); + Require(sceScreenShotSetOverlayImage(nullptr, 0, 0) == ErrorArgument, "null path"); + const std::string longPath(1024, 'a'); + Require(sceScreenShotSetOverlayImage(longPath.c_str(), 0, 0) == ErrorArgument, "path of 1024 characters"); + TempPath missing("missing"); + Require(sceScreenShotSetOverlayImage(missing.c_str(), 0, 0) == ErrorOverlayFile, "missing file"); + TempPath directory("directory"); + std::filesystem::create_directory(directory.c_str()); + Require(sceScreenShotSetOverlayImage(directory.c_str(), 0, 0) == ErrorOverlayFile, "directory"); + TempPath empty("empty"); + empty.Fill(0); + Require(sceScreenShotSetOverlayImage(empty.c_str(), 0, 0) == ErrorOverlaySize, "empty file"); + TempPath full("full"); + full.Fill(maxSize); + Require(sceScreenShotSetOverlayImage(full.c_str(), 0, 0) == 0, "file at the size limit"); + full.Fill(maxSize + 1); + Require(sceScreenShotSetOverlayImage(full.c_str(), 0, 0) == ErrorOverlaySize, "file past the size limit"); + for (int origin = 1; origin <= 9; ++origin) Require(sceScreenShotSetOverlayImageWithOrigin(image.c_str(), 0, 0, origin) == 0, "origins 1 to 9"); + Require(sceScreenShotSetOverlayImageWithOrigin(image.c_str(), 0, 0, 0) == ErrorArgument, "origin 0"); + Require(sceScreenShotSetOverlayImageWithOrigin(image.c_str(), 0, 0, 10) == ErrorArgument, "origin 10"); + Require(sceScreenShotSetOverlayImageWithOrigin(nullptr, 0, 0, 0) == ErrorArgument, "origin checked first"); +} + +} + +int main(int argc, char** argv) { + const bool large = argc > 1 && std::strcmp(argv[1], "large") == 0; + pathPrefix = std::string("anyps5-screenshot-") + (large ? "large-" : "small-") + std::to_string(std::random_device{}()) + "-"; + InstallProcessParameters(large ? 0x3500000u : 0x34fffffu); + TestDisableState(); + TestOverlay(large); + return 0; +} diff --git a/core/libs/tests/GuestSearch.cpp b/core/libs/tests/GuestSearch.cpp index f1572c3640..55d2862aba 100644 --- a/core/libs/tests/GuestSearch.cpp +++ b/core/libs/tests/GuestSearch.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -7,7 +8,7 @@ #include #include using Comparator = int (APS5_VABI *)(const void*, const void*); -extern "C" void* APS5_VABI bsearch_nid_postfix(const void*, const void*, std::size_t, std::size_t, Comparator); + static void Require(bool value, std::source_location location = std::source_location::current()) { if (!value) { std::fprintf(stderr, "Guest search failed at line %u\n", location.line()); std::abort(); } } diff --git a/core/libs/tests/GuestSelect.cpp b/core/libs/tests/GuestSelect.cpp index 979415e15d..1c1f48d9dd 100644 --- a/core/libs/tests/GuestSelect.cpp +++ b/core/libs/tests/GuestSelect.cpp @@ -1,8 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include #include #include +#include #include #include #include @@ -24,25 +27,6 @@ struct GuestDescriptorSet { } }; -extern "C" { -int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI unlink_nid_postfix(const char*); -int APS5_VABI kqueue_nid_postfix(); -int APS5_VABI kevent_nid_postfix(int, const KernelEvent*, int, KernelEvent*, int, const KernelTimespec*); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } constexpr int GuestEbadf = 9; @@ -72,6 +56,30 @@ static KernelEvent UserEvent(std::uint16_t flags, std::uint32_t fflags) { return change; } +static void StandardDescriptorFile(int descriptor) { + const int saved = dup_nid_postfix(descriptor); + const std::string path = "anyps5-select-input-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + const int file = open_nid_postfix(path.c_str(), 0x0002 | 0x0200, 0644); + Require(file >= 0); + Require(dup2_nid_postfix(file, descriptor) == descriptor); + if (file != descriptor) Require(close_nid_postfix(file) == 0); + GuestDescriptorSet read{}; + GuestDescriptorSet write{}; + GuestDescriptorSet except{}; + const GuestTimeval zero{0, 0}; + const int ready = SelectAll(descriptor, read, write, except, zero); + if (ready != 2) std::fprintf(stderr, "select on a regular file at descriptor %d returned %d instead of 2\n", descriptor, ready); + Require(ready == 2); + Require(read.Has(descriptor) && write.Has(descriptor) && !except.Has(descriptor)); + char byte = 0; + Require(read_nid_postfix(descriptor, &byte, 1) == 0); + if (saved >= 0) { + Require(dup2_nid_postfix(saved, descriptor) == descriptor); + Require(close_nid_postfix(saved) == 0); + } else Require(close_nid_postfix(descriptor) == 0); + Require(unlink_nid_postfix(path.c_str()) == 0); +} + static void Kqueues() { const int kq = kqueue_nid_postfix(); Require(kq >= 0 && kq < GuestDescriptorLimit); @@ -102,6 +110,7 @@ static void Kqueues() { } int main() { + for (const int descriptor : {0, 1, 2}) StandardDescriptorFile(descriptor); Kqueues(); int descriptors[2]{-1, -1}; @@ -202,14 +211,43 @@ int main() { Require(read.Has(reader) && write.Has(writer)); } + Require(reader < 63); + + { + GuestDescriptorSet read{}; + GuestDescriptorSet except{}; + read.Add(reader); + read.Add(63); + read.Add(64); + except.Add(63); + GuestTimeval limit{0, 100000}; + Require(select_nid_postfix(reader, read.words.data(), nullptr, except.words.data(), &limit) == 0); + Require(!read.Has(reader) && !read.Has(63) && read.Has(64) && !except.Has(63)); + } + { GuestDescriptorSet read{}; read.Add(reader); - GuestTimeval limit{0, 100000}; - Require(select_nid_postfix(reader, read.words.data(), nullptr, nullptr, &limit) == 0); + const GuestTimeval zero{0, 0}; + Require(select_nid_postfix(0, read.words.data(), nullptr, nullptr, &zero) == 0); Require(read.Has(reader)); } + { + const char byte = 'n'; + Require(write_nid_postfix(writer, &byte, 1) == 1); + GuestDescriptorSet read{}; + read.Add(reader); + read.Add(63); + read.Add(64); + GuestTimeval limit{0, 200000}; + Require(select_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit) == 1); + Require(read.Has(reader) && !read.Has(63) && read.Has(64)); + char buffer[8]{}; + const auto count = read_nid_postfix(reader, buffer, sizeof(buffer)); + Require(count >= 1 && buffer[count - 1] == byte); + } + { GuestDescriptorSet read{}; read.Add(reader); diff --git a/core/libs/tests/GuestSelectWait.cpp b/core/libs/tests/GuestSelectWait.cpp index a21b7b320a..e5dd072cd4 100644 --- a/core/libs/tests/GuestSelectWait.cpp +++ b/core/libs/tests/GuestSelectWait.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -10,11 +12,6 @@ struct GuestTimeval { std::int64_t microseconds; }; -extern "C" { -int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value, const char* message) { if (!value) { std::fprintf(stderr, "%s\n", message); @@ -56,14 +53,17 @@ int main() { } std::array read{}; read[0] = 1 | (std::uint64_t{1} << 63); + read[1] = 1; const GuestTimeval limit{0, 500}; const auto start = Clock::now(); Require(select_nid_postfix(1, read.data(), nullptr, nullptr, &limit) == 0, "select with no ready device did not time out"); Require(Clock::now() - start >= std::chrono::microseconds(limit.microseconds), "select with no ready device returned before its deadline"); - Require(read[0] == (std::uint64_t{1} << 63), - "select did not preserve bits outside the descriptor limit"); + Require(read[0] == 0, + "select did not clear the bits up to the end of the last copied word"); + Require(read[1] == 1, + "select did not preserve the words past the descriptor limit"); std::array write{}; write[0] = 2; const GuestTimeval longLimit{1, 0}; diff --git a/core/libs/tests/GuestSemCanceledWake.cpp b/core/libs/tests/GuestSemCanceledWake.cpp index 526888bcb0..8bf56c413a 100644 --- a/core/libs/tests/GuestSemCanceledWake.cpp +++ b/core/libs/tests/GuestSemCanceledWake.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libkernel/Pthread/include/Cancel.hpp" #include @@ -6,19 +7,6 @@ #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread*, const PthreadAttr*, PthreadEntry, void*, const char*); -int APS5_VABI scePthreadJoin(Pthread, void**); -int APS5_VABI scePthreadCancel(Pthread); -int APS5_VABI scePthreadSetcancelstate(int, int*); -int APS5_VABI scePthreadSemInit(PthreadSem*, int, unsigned, const char*); -int APS5_VABI scePthreadSemDestroy(PthreadSem*); -int APS5_VABI scePthreadSemPost(PthreadSem*); -int APS5_VABI scePthreadSemWait(PthreadSem*); -int APS5_VABI scePthreadSemTimedwait(PthreadSem*, KernelUseconds); -int APS5_VABI scePthreadSemGetvalue(PthreadSem*, int*); -} - namespace { void Require(bool value, const char* message) { diff --git a/core/libs/tests/GuestSemaphore.cpp b/core/libs/tests/GuestSemaphore.cpp index 5392fa1603..bc843e1a5a 100644 --- a/core/libs/tests/GuestSemaphore.cpp +++ b/core/libs/tests/GuestSemaphore.cpp @@ -1,24 +1,12 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Semaphore/include/Semaphore.hpp" #include "SceTypes.hpp" #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI sceKernelCreateSema(KernelSema* sem, const char* name, uint32_t attr, int init, int max, void* opt); -int APS5_VABI sceKernelDeleteSema(KernelSema sem); -int APS5_VABI sceKernelPollSema(KernelSema sem, int need); -int APS5_VABI sceKernelSignalSema(KernelSema sem, int count); -int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time); -int APS5_VABI sceKernelCancelSema(KernelSema sem, int count, int* threads); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast(0x80020010); -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); -static constexpr int SCE_KERNEL_ERROR_ECANCELED = static_cast(0x80020055); -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); static void Require(bool value) { if (!value) std::abort(); } @@ -35,7 +23,37 @@ static void* APS5_VABI Wait(void* arg) { return nullptr; } +static void RequireInvalidArguments() { + KernelSema rejected = nullptr; + Require(sceKernelCreateSema(&rejected, "rejected", 0, 5, 3, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelCreateSema(&rejected, "rejected", 0, 0, 0, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelCreateSema(&rejected, "rejected", 0, -1, 3, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelCreateSema(&rejected, nullptr, 0, 0, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(rejected == nullptr); + + KernelSema sem = nullptr; + Require(sceKernelCreateSema(&sem, "limits", 0, 2, 3, nullptr) == SCE_OK); + Require(sceKernelPollSema(sem, 0) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelPollSema(sem, -1) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelPollSema(sem, 4) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelPollSema(sem, 100) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelPollSema(sem, 3) == SCE_KERNEL_ERROR_EBUSY); + Require(sceKernelSignalSema(sem, 0) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelPollSema(sem, 2) == SCE_OK); + Require(sceKernelSignalSema(sem, 3) == SCE_OK); + Require(sceKernelPollSema(sem, 3) == SCE_OK); + Require(sceKernelDeleteSema(sem) == SCE_OK); +} + int main() { + KernelUseconds nullTimeout = 1000; + int nullWaiters = -1; + Require(sceKernelWaitSema(nullptr, 1, nullptr) == SCE_KERNEL_ERROR_ESRCH); + Require(sceKernelWaitSema(nullptr, 1, &nullTimeout) == SCE_KERNEL_ERROR_ESRCH && nullTimeout == 1000); + Require(sceKernelPollSema(nullptr, 1) == SCE_KERNEL_ERROR_ESRCH); + Require(sceKernelSignalSema(nullptr, 1) == SCE_KERNEL_ERROR_ESRCH); + Require(sceKernelCancelSema(nullptr, 0, &nullWaiters) == SCE_KERNEL_ERROR_ESRCH && nullWaiters == -1); + Require(sceKernelDeleteSema(nullptr) == SCE_KERNEL_ERROR_ESRCH); KernelSema sem = nullptr; Require(sceKernelCreateSema(&sem, "cancel", 0, 1, 3, nullptr) == SCE_OK); @@ -85,4 +103,6 @@ int main() { Require(sceKernelSignalSema(sem, 1) == SCE_OK); Require(sceKernelWaitSema(sem, 1, nullptr) == SCE_OK); Require(sceKernelDeleteSema(sem) == SCE_OK); + + RequireInvalidArguments(); } diff --git a/core/libs/tests/GuestSendfile.cpp b/core/libs/tests/GuestSendfile.cpp index d8aa5f363e..f2fac5d3cc 100644 --- a/core/libs/tests/GuestSendfile.cpp +++ b/core/libs/tests/GuestSendfile.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -6,27 +8,7 @@ #include #include #include -struct Iovec { - const void* base; - std::uint64_t length; -}; -struct Hdtr { - const Iovec* headers; - int headerCount; - const Iovec* trailers; - int trailerCount; -}; -extern "C" { -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int); -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI socketpair_nid_postfix(int, int, int, int*); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -int APS5_VABI ioctl_nid_postfix(int, std::uint64_t, void*); -int APS5_VABI sendfile_nid_postfix(int, int, std::int64_t, std::uint64_t, const Hdtr*, std::int64_t*, int); -int* APS5_VABI __error_nid_postfix(); -} + static void Require(bool value) { if (!value) std::abort(); } static bool Fails(int result, int error) { return result == -1 && *__error_nid_postfix() == error; } static std::string Receive(int descriptor, std::size_t length) { @@ -59,9 +41,9 @@ int main() { const int file = OpenWritten("sendfile_probe.bin", content); int pair[2]; Require(socketpair_nid_postfix(1, 1, 0, pair) == 0); - const Iovec header[] = {{"GET", 3}, {" ", 1}}; - const Iovec trailer[] = {{"END", 3}}; - const Hdtr parts{header, 2, trailer, 1}; + KernelIovec header[] = {{const_cast("GET"), 3}, {const_cast(" "), 1}}; + KernelIovec trailer[] = {{const_cast("END"), 3}}; + const GuestSendfileHeaders parts{header, 2, trailer, 1}; std::int64_t sent = -1; Require(sendfile_nid_postfix(file, pair[0], 5, 10, &parts, &sent, 0) == 0 && sent == 17); Require(Receive(pair[1], 17) == "GET " + content.substr(5, 10) + "END"); @@ -75,21 +57,21 @@ int main() { Require(Receive(pair[1], 4) == content.substr(content.size() - 4)); Require(sendfile_nid_postfix(file, pair[0], size + 10, 0, &parts, &sent, 0) == 0 && sent == 7); Require(Receive(pair[1], 7) == "GET END"); - const Hdtr nullHeaders{nullptr, 5, trailer, 1}; + const GuestSendfileHeaders nullHeaders{nullptr, 5, trailer, 1}; Require(sendfile_nid_postfix(file, pair[0], 0, 3, &nullHeaders, nullptr, 0) == 0); Require(Receive(pair[1], 6) == content.substr(0, 3) + "END"); Require(lseek_nid_postfix(file, 0, 1) == 0); sent = 77; Require(Fails(sendfile_nid_postfix(file, pair[0], -1, 0, nullptr, &sent, 0), 22) && sent == 77); - const std::vector many(1025, Iovec{"x", 1}); - const Hdtr tooMany{many.data(), 1025, nullptr, 0}; + std::vector many(1025, KernelIovec{const_cast("x"), 1}); + const GuestSendfileHeaders tooMany{many.data(), 1025, nullptr, 0}; Require(Fails(sendfile_nid_postfix(file, pair[0], 0, 0, &tooMany, &sent, 0), 22) && sent == 77); - const Hdtr negative{nullptr, 0, trailer, -1}; + const GuestSendfileHeaders negative{nullptr, 0, trailer, -1}; Require(Fails(sendfile_nid_postfix(file, pair[0], 0, 0, &negative, &sent, 0), 22) && sent == 77); Require(Fails(sendfile_nid_postfix(pair[1], pair[0], 0, 0, nullptr, &sent, 0), 22) && sent == 0); sent = 77; - Require(Fails(sendfile_nid_postfix(file, file, 0, 0, nullptr, &sent, 0), 9) && sent == 0); + Require(Fails(sendfile_nid_postfix(file, file, 0, 0, nullptr, &sent, 0), 38) && sent == 0); const int datagram = socket_nid_postfix(2, 2, 0); Require(datagram >= 0); sent = 77; @@ -98,6 +80,17 @@ int main() { Require(unconnected >= 0); sent = 77; Require(Fails(sendfile_nid_postfix(file, unconnected, size + 10, 0, nullptr, &sent, 0), 57) && sent == 0); + const int random = open_nid_postfix("/dev/urandom", 0, 0); + Require(random >= 0); + sent = 77; + Require(Fails(sendfile_nid_postfix(random, pair[0], 0, 16, &parts, &sent, 0), 22) && sent == 0); + sent = 77; + Require(Fails(sendfile_nid_postfix(random, pair[0], 0, 0, nullptr, &sent, 0), 22) && sent == 0); + const int queue = kqueue_nid_postfix(); + Require(queue >= 0); + sent = 77; + Require(Fails(sendfile_nid_postfix(queue, pair[0], 0, 0, nullptr, &sent, 0), 22) && sent == 0); + Require(close_nid_postfix(queue) == 0 && close_nid_postfix(random) == 0); const std::string large = Content(32 << 20); const int largeFile = OpenWritten("sendfile_large.bin", large); @@ -108,10 +101,13 @@ int main() { Require(sent > 0 && sent < static_cast(large.size())); Require(Receive(pair[1], static_cast(sent)) == large.substr(0, static_cast(sent))); + Require(close_nid_postfix(unconnected) == 0); + sent = 77; + Require(Fails(sendfile_nid_postfix(file, unconnected, 0, 0, nullptr, &sent, 0), 9) && sent == 0); Require(close_nid_postfix(largeFile) == 0 && close_nid_postfix(file) == 0); sent = 77; Require(Fails(sendfile_nid_postfix(file, pair[0], 0, 0, nullptr, &sent, 0), 9) && sent == 77); - Require(close_nid_postfix(datagram) == 0 && close_nid_postfix(unconnected) == 0); + Require(close_nid_postfix(datagram) == 0); Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0); std::filesystem::remove("sendfile_probe.bin"); std::filesystem::remove("sendfile_large.bin"); diff --git a/core/libs/tests/GuestServiceDatabase.cpp b/core/libs/tests/GuestServiceDatabase.cpp index 30caec9095..e89a3d5985 100644 --- a/core/libs/tests/GuestServiceDatabase.cpp +++ b/core/libs/tests/GuestServiceDatabase.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePosixForWebKit/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libScePosixForWebKit/GuestResolver.hpp" #include @@ -8,8 +9,6 @@ #include #include -extern "C" GuestResolver::ServiceEntry* APS5_VABI getservbyname_nid_postfix(const char*, const char*); - void Require(bool condition, std::source_location location = std::source_location::current()) { if (!condition) { std::fprintf(stderr, "Service database check failed at line %u\n", location.line()); diff --git a/core/libs/tests/GuestSetrlimit.cpp b/core/libs/tests/GuestSetrlimit.cpp index a869eec59c..f86c83edfb 100644 --- a/core/libs/tests/GuestSetrlimit.cpp +++ b/core/libs/tests/GuestSetrlimit.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,17 +7,6 @@ #include #include -struct ResourceLimit { - std::int64_t rlim_cur; - std::int64_t rlim_max; -}; - -extern "C" { -int APS5_VABI getrlimit_nid_postfix(int, ResourceLimit*); -int APS5_VABI setrlimit_nid_postfix(int, const ResourceLimit*); -int* APS5_VABI __error_nid_postfix(); -} - static constexpr int GUEST_EFAULT = 14; static constexpr int GUEST_EINVAL = 22; static constexpr int GUEST_RLIMIT_DATA = 2; @@ -29,23 +20,24 @@ static constexpr std::int64_t GUEST_RLIM_INFINITY = std::numeric_limits 0) { - const ResourceLimit changed{current.rlim_cur - 1, current.rlim_max}; + const GuestResourceLimit changed{current.rlim_cur - 1, current.rlim_max}; Require(Throws(GUEST_RLIMIT_DATA, changed)); } - const ResourceLimit softAboveHard{GUEST_RLIM_INFINITY, current.rlim_max}; + const GuestResourceLimit softAboveHard{GUEST_RLIM_INFINITY, current.rlim_max}; if (current.rlim_cur == current.rlim_max) Require(setrlimit_nid_postfix(GUEST_RLIMIT_DATA, &softAboveHard) == 0); else Require(Throws(GUEST_RLIMIT_DATA, softAboveHard)); - const ResourceLimit negative{-1, -2}; + const GuestResourceLimit negative{-1, -2}; if (current.rlim_cur == GUEST_RLIM_INFINITY && current.rlim_max == GUEST_RLIM_INFINITY) Require(setrlimit_nid_postfix(GUEST_RLIMIT_DATA, &negative) == 0); else diff --git a/core/libs/tests/GuestShaderLink.cpp b/core/libs/tests/GuestShaderLink.cpp index f5b510652b..5c39b6fffe 100644 --- a/core/libs/tests/GuestShaderLink.cpp +++ b/core/libs/tests/GuestShaderLink.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceAgc/Shader/include/LinkShaders.hpp" +#include "prx/libSceAgc/Misc/include/RegisterDefaults.hpp" #include "SceShaders.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" @@ -6,9 +8,7 @@ #include #include #include -extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister*, ShaderRegister*, const void*, const Shader*, const Shader*, std::uint32_t); -extern "C" void* APS5_VABI sceAgcGetRegisterDefaults(); -extern "C" void* APS5_VABI sceAgcGetRegisterDefaults2(std::uint32_t); + static void Require(bool value) { if (!value) std::abort(); } static unsigned preparations = 0; static unsigned mappings = 0; diff --git a/core/libs/tests/GuestShare.cpp b/core/libs/tests/GuestShare.cpp index 698b357e1e..a3e197e8a4 100644 --- a/core/libs/tests/GuestShare.cpp +++ b/core/libs/tests/GuestShare.cpp @@ -1,19 +1,10 @@ +#include "prx/libSceShare/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include - -extern "C" { -int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, std::int32_t* req_id); -int APS5_VABI sceShareCaptureScreenshot(const void* param, std::int32_t* req_id); -int APS5_VABI sceShareCaptureVideoClip(const void* param, std::int32_t* req_id); -int APS5_VABI sceShareCaptureVideoClipExtended(const void* extended_param, std::int32_t* req_id); -int APS5_VABI sceShareGetCurrentStatus(std::uint32_t feature_flag, void* status); -int APS5_VABI sceShareOpenMenuForContent(const void* content_id); -int APS5_VABI sceShareGetRunningStatus(std::uint32_t* status); -int APS5_VABI sceShareSetContentParamForApplicationTitle(const char* application_title); -} +#include namespace { @@ -27,18 +18,27 @@ int main() { using Capture = int (APS5_VABI*)(const void*, std::int32_t*); std::int32_t reqId = 7; - for (Capture capture : {sceShareCaptureScreenshotExtended, sceShareCaptureVideoClipExtended}) { + constexpr std::int32_t invalidParam = static_cast(0x81960002); + const auto checkExtended = [&](int (APS5_VABI* capture)(const TParam*, std::int32_t*), std::uint64_t size) { + std::uint64_t fields[5]{}; reqId = 7; - Require(capture(param, &reqId) == notSupported); - Require(reqId == -1); - - reqId = 7; - Require(capture(nullptr, &reqId) == notSupported); - Require(reqId == -1); - - Require(capture(param, nullptr) == notSupported); - Require(capture(nullptr, nullptr) == notSupported); - } + Require(capture(reinterpret_cast(fields), &reqId) == invalidParam && reqId == 7); + Require(capture(nullptr, &reqId) == invalidParam && reqId == 7); + fields[0] = size; + Require(capture(reinterpret_cast(fields), &reqId) == notSupported && reqId == -1); + Require(capture(reinterpret_cast(fields), nullptr) == notSupported); + if (size == 40) { + fields[2] = 1; + reqId = 7; + Require(capture(reinterpret_cast(fields), &reqId) == invalidParam && reqId == -1); + fields[3] = 1; + Require(capture(reinterpret_cast(fields), nullptr) == notSupported); + fields[3] |= std::uint64_t{1} << 32; + Require(capture(reinterpret_cast(fields), nullptr) == invalidParam); + } + }; + checkExtended(sceShareCaptureScreenshotExtended, 24); + checkExtended(sceShareCaptureVideoClipExtended, 40); for (Capture capture : {sceShareCaptureScreenshot, sceShareCaptureVideoClip}) { reqId = 7; @@ -53,16 +53,15 @@ int main() { Require(sceShareOpenMenuForContent(param) == notSupported); Require(sceShareOpenMenuForContent(nullptr) == notSupported); - constexpr std::int32_t invalidParam = static_cast(0x81960002); std::uint8_t status[18]; std::memset(status, 0x5a, sizeof(status)); - Require(sceShareGetCurrentStatus(1, status) == 0); + Require(sceShareGetCurrentStatus(1, reinterpret_cast(status)) == 0); for (std::size_t index = 0; index < sizeof(status); ++index) Require(status[index] == (index < 16 ? 0 : 0x5a)); std::memset(status, 0x5a, sizeof(status)); - Require(sceShareGetCurrentStatus(0xffffffffu, status) == 0); + Require(sceShareGetCurrentStatus(0xffffffffu, reinterpret_cast(status)) == 0); Require(status[0] == 0 && status[15] == 0 && status[16] == 0x5a); std::memset(status, 0x5a, sizeof(status)); - Require(sceShareGetCurrentStatus(0, status) == invalidParam); + Require(sceShareGetCurrentStatus(0, reinterpret_cast(status)) == invalidParam); Require(status[0] == 0x5a); Require(sceShareGetCurrentStatus(1, nullptr) == invalidParam); std::uint32_t running[2]{0xffffffffu, 0x5a5a5a5au}; @@ -71,5 +70,10 @@ int main() { Require(sceShareGetRunningStatus(nullptr) == invalidParam); Require(sceShareSetContentParamForApplicationTitle("title") == 0); Require(sceShareSetContentParamForApplicationTitle(nullptr) == invalidParam); + Require(sceShareSetContentParamForApplicationTitle("") == invalidParam); + std::string title(5119, 'a'); + Require(sceShareSetContentParamForApplicationTitle(title.c_str()) == 0); + title.push_back('a'); + Require(sceShareSetContentParamForApplicationTitle(title.c_str()) == invalidParam); return 0; } diff --git a/core/libs/tests/GuestSharePlay.cpp b/core/libs/tests/GuestSharePlay.cpp index fe2819c276..c1545a1dad 100644 --- a/core/libs/tests/GuestSharePlay.cpp +++ b/core/libs/tests/GuestSharePlay.cpp @@ -1,16 +1,11 @@ +#include "prx/libSceSharePlay/libSceSharePlay.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include - -extern "C" { -int APS5_VABI sceSharePlayInitialize(void*, std::size_t); -int APS5_VABI sceSharePlayTerminate(void); -int APS5_VABI sceSharePlayGetCurrentConnectionInfoA(SharePlayConnectionInfoA*); -int APS5_VABI sceSharePlayProhibitController(int); -} +#include static void Require(bool value) { if (!value) std::abort(); } @@ -26,12 +21,20 @@ static bool Throws(TCall call) { int main() { alignas(16) static unsigned char heap[0x1800]; + constexpr int notInitialized = static_cast(0x810E0004); + constexpr int alreadyInitialized = static_cast(0x810E0003); + constexpr int invalidArgument = static_cast(0x810E0001); + Require(sceSharePlayTerminate() == notInitialized); + Require(sceSharePlayGetCurrentConnectionInfoA(nullptr) == notInitialized); + Require(sceSharePlayGetCurrentConnectionInfo(nullptr) == notInitialized); + Require(sceSharePlayProhibitController(2) == notInitialized); Require(sceSharePlayInitialize(nullptr, sizeof(heap)) == 0); + Require(sceSharePlayInitialize(heap, 0) == alreadyInitialized); Require(sceSharePlayTerminate() == 0); Require(sceSharePlayInitialize(heap, sizeof(heap)) == 0); Require(sceSharePlayTerminate() == 0); - Require(Throws([&] { sceSharePlayInitialize(heap, 0); })); - Require(Throws([&] { sceSharePlayInitialize(nullptr, 0); })); + for (const std::size_t size : {std::size_t{0}, std::size_t{1}, sizeof(heap) - 1}) Require(sceSharePlayInitialize(heap, size) == invalidArgument); + Require(sceSharePlayInitialize(nullptr, 0) == 0); SharePlayConnectionInfoA info; std::memset(&info, 0x5a, sizeof(info)); Require(sceSharePlayGetCurrentConnectionInfoA(&info) == 0); @@ -39,9 +42,22 @@ int main() { Require(info.hostOnlineId.data[0] == 0 && info.visitorOnlineId.data[0] == 0 && info.hostOnlineId.pad[2] == 0); Require(info.hostUserId == -1 && info.visitorUserId == -1); Require(sceSharePlayGetCurrentConnectionInfoA(nullptr) == static_cast(0x810E0001)); + unsigned char plain[sizeof(SharePlayConnectionInfo) + 8]; + std::memset(plain, 0x5a, sizeof(plain)); + auto* plainInfo = reinterpret_cast(plain); + Require(sceSharePlayGetCurrentConnectionInfo(plainInfo) == 0); + Require(plainInfo->status == 0 && plainInfo->mode == 0 && plainInfo->hostOnlineId.data[0] == 0 && plainInfo->visitorOnlineId.pad[2] == 0); + Require(plainInfo->hostUserId == -1 && plainInfo->visitorUserId == -1); + for (std::size_t i = sizeof(SharePlayConnectionInfo); i < sizeof(plain); ++i) Require(plain[i] == 0x5a); + Require(sceSharePlayGetCurrentConnectionInfo(nullptr) == invalidArgument); Require(sceSharePlayProhibitController(1) == 0); Require(sceSharePlayProhibitController(1) == 0); Require(sceSharePlayProhibitController(0) == 0); - Require(Throws([] { sceSharePlayProhibitController(2); })); - Require(Throws([] { sceSharePlayProhibitController(-1); })); + Require(sceSharePlayProhibitController(2) == invalidArgument); + Require(sceSharePlayProhibitController(255) == invalidArgument); + Require(sceSharePlayTerminate() == 0); + std::memset(&info, 0x5a, sizeof(info)); + Require(sceSharePlayGetCurrentConnectionInfoA(&info) == notInitialized); + for (const auto value : std::as_bytes(std::span(&info, 1))) Require(value == std::byte{0x5a}); + Require(sceSharePlayTerminate() == notInitialized); } diff --git a/core/libs/tests/GuestShmOpen.cpp b/core/libs/tests/GuestShmOpen.cpp index ff4f8fb16c..6d6461d725 100644 --- a/core/libs/tests/GuestShmOpen.cpp +++ b/core/libs/tests/GuestShmOpen.cpp @@ -1,19 +1,12 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include -extern "C" { -int APS5_VABI shm_open_nid_postfix(const char*, int, int); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t); -int APS5_VABI close_nid_postfix(int); -void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; -int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; -int* APS5_VABI __error_nid_postfix(); -} + static void Require(bool value) { if (!value) std::abort(); } static const char* const anonymous = reinterpret_cast(1); static void RequireInvalid(const char* path, int flags) { @@ -68,13 +61,7 @@ int main() { RequireInvalid("/anyps5-shm", 0x1); RequireInvalid("/anyps5-shm", 0x202 | 0x8); #ifdef _WIN32 - bool threw = false; - try { - shm_open_nid_postfix("/anyps5-shm", 0x202, 0600); - } catch (const std::runtime_error&) { - threw = true; - } - Require(threw); + RequireInvalid("", 0x202); #else RequireInvalid("", 0x202); #endif diff --git a/core/libs/tests/GuestSigaltstack.cpp b/core/libs/tests/GuestSigaltstack.cpp index 6260846fd8..afd3787ccc 100644 --- a/core/libs/tests/GuestSigaltstack.cpp +++ b/core/libs/tests/GuestSigaltstack.cpp @@ -1,17 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -struct GuestStack { - void* sp; - std::size_t size; - int flags; -}; -extern "C" { -int APS5_VABI sigaltstack_nid_postfix(const GuestStack*, GuestStack*); -int* APS5_VABI __error_nid_postfix(); -} + static void Require(bool value) { if (!value) std::abort(); } static GuestStack Query() { GuestStack current; diff --git a/core/libs/tests/GuestSignalHandlerMask.cpp b/core/libs/tests/GuestSignalHandlerMask.cpp new file mode 100644 index 0000000000..36029bfd31 --- /dev/null +++ b/core/libs/tests/GuestSignalHandlerMask.cpp @@ -0,0 +1,229 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +static constexpr int SigKill = 9; +static constexpr int SigTerm = 15; +static constexpr int SigUsr1 = 30; +static constexpr int SigUsr2 = 31; +static constexpr int SaRestart = 0x2; +static constexpr int SaResethand = 0x4; +static constexpr int SaNodefer = 0x10; +static constexpr int SigBlock = 1; +static constexpr int SigUnblock = 2; +static constexpr int SigSetmask = 3; + +static std::uint32_t Bit(int signal) { return 1u << (signal - 1); } + +static void Require(bool value) { if (!value) std::abort(); } + +static int calls = 0; +static int callsAfterNestedRaise = 0; +static std::uint32_t maskInHandler = 0; + +static std::uint32_t CurrentMask() { + GuestSignalSet set{}; + Require(sigprocmask_nid_postfix(SigBlock, nullptr, &set) == 0); + return set.bits[0]; +} + +static void APS5_VABI RaiseAgain(int signal) { + if (++calls == 1) { + maskInHandler = CurrentMask(); + Require(raise_nid_postfix(signal) == 0); + callsAfterNestedRaise = calls; + } +} + +static void APS5_VABI Count(int) { + ++calls; +} + +static std::uint64_t handlerExit[12]; + +static void APS5_VABI LeaveWithLongjmp(int) { + ++calls; + longjmp_nid_postfix(handlerExit, 1); +} + +static void APS5_VABI BlockFromOtherThread(int) { + ++calls; + std::thread([] { + const GuestSignalSet set{{Bit(SigTerm), 0, 0, 0}}; + Require(sigprocmask_nid_postfix(SigBlock, &set, nullptr) == 0); + }).join(); +} + +static void APS5_VABI SetMaskFromOtherThread(int) { + ++calls; + std::thread([] { + const GuestSignalSet set{{Bit(SigTerm), 0, 0, 0}}; + Require(sigprocmask_nid_postfix(SigSetmask, &set, nullptr) == 0); + }).join(); +} + +static void APS5_VABI SaveAndRestoreMask(int) { + ++calls; + GuestSignalSet old{}; + Require(sigprocmask_nid_postfix(SigBlock, nullptr, &old) == 0); + Require(sigprocmask_nid_postfix(SigSetmask, &old, nullptr) == 0); +} + +static void APS5_VABI ReplaceAndRestoreMask(int) { + ++calls; + const GuestSignalSet none{}; + GuestSignalSet old{}; + Require(sigprocmask_nid_postfix(SigSetmask, &none, &old) == 0); + Require(sigprocmask_nid_postfix(SigSetmask, &old, nullptr) == 0); +} + +static GuestSignalSet setInHandler{}; + +static void APS5_VABI RecordMask(int) { + ++calls; + Require(sigprocmask_nid_postfix(SigBlock, nullptr, &setInHandler) == 0); +} + +static void APS5_VABI UnblockAndRaise(int signal) { + if (++calls == 1) { + const GuestSignalSet own{{Bit(signal), 0, 0, 0}}; + Require(sigprocmask_nid_postfix(SigUnblock, &own, nullptr) == 0); + Require(raise_nid_postfix(signal) == 0); + callsAfterNestedRaise = calls; + } +} + +static void Install(int signal, void (APS5_VABI* handler)(int), int flags, std::uint32_t mask) { + GuestSigaction action{reinterpret_cast(handler), flags, {{mask, 0, 0, 0}}}; + Require(sigaction_nid_postfix(signal, &action, nullptr) == 0); +} + +static void CheckDeferred(int signal) { + calls = 0; + callsAfterNestedRaise = 0; + Install(signal, RaiseAgain, SaRestart, Bit(SigUsr1)); + Require(raise_nid_postfix(signal) == 0); + Require(callsAfterNestedRaise == 1); + Require(calls == 2); + Require(maskInHandler == (Bit(signal) | Bit(SigUsr1))); + Require(CurrentMask() == 0); +} + +static void CheckNodefer() { + calls = 0; + callsAfterNestedRaise = 0; + Install(SigUsr2, RaiseAgain, SaRestart | SaNodefer, 0); + Require(raise_nid_postfix(SigUsr2) == 0); + Require(callsAfterNestedRaise == 2); + Require(maskInHandler == 0); + Require(CurrentMask() == 0); +} + +static void CheckResethand() { + calls = 0; + Install(SigUsr1, Count, SaResethand, 0); + Require(raise_nid_postfix(SigUsr1) == 0); + Require(calls == 1); + GuestSigaction current{}; + Require(sigaction_nid_postfix(SigUsr1, nullptr, ¤t) == 0); + Require(current.handler == 0); + Require((current.flags & SaResethand) != 0); +} + +static void CheckOtherThreadKept() { + calls = 0; + Install(SigUsr1, BlockFromOtherThread, SaRestart, 0); + Require(raise_nid_postfix(SigUsr1) == 0); + Require(calls == 1); + Require(CurrentMask() == Bit(SigTerm)); + const GuestSignalSet set{{Bit(SigTerm), 0, 0, 0}}; + Require(sigprocmask_nid_postfix(SigUnblock, &set, nullptr) == 0); + Require(CurrentMask() == 0); +} + +static void CheckOtherThreadKeptSameSignal() { + calls = 0; + Install(SigUsr1, BlockFromOtherThread, SaRestart, Bit(SigTerm)); + Require(raise_nid_postfix(SigUsr1) == 0); + Require(calls == 1); + Require(CurrentMask() == Bit(SigTerm)); + const GuestSignalSet set{{Bit(SigTerm), 0, 0, 0}}; + Require(sigprocmask_nid_postfix(SigUnblock, &set, nullptr) == 0); + Require(CurrentMask() == 0); +} + +static void CheckOtherThreadSetMaskKept() { + calls = 0; + Install(SigUsr1, SetMaskFromOtherThread, SaRestart, 0); + Require(raise_nid_postfix(SigUsr1) == 0); + Require(calls == 1); + Require(CurrentMask() == Bit(SigTerm)); + const GuestSignalSet none{}; + Require(sigprocmask_nid_postfix(SigSetmask, &none, nullptr) == 0); + Require(CurrentMask() == 0); +} + +static void CheckMaskRestoredInsideHandler(int signal, void (APS5_VABI* handler)(int)) { + calls = 0; + Install(signal, handler, SaRestart, 0); + Require(raise_nid_postfix(signal) == 0); + Require(calls == 1); + Require(CurrentMask() == 0); + Require(raise_nid_postfix(signal) == 0); + Require(calls == 2); + Require(CurrentMask() == 0); +} + +static void CheckUnblockInsideHandler() { + calls = 0; + callsAfterNestedRaise = 0; + Install(SigUsr2, UnblockAndRaise, SaRestart, 0); + Require(raise_nid_postfix(SigUsr2) == 0); + Require(callsAfterNestedRaise == 2); + Require(CurrentMask() == 0); +} + +static void CheckUpperMaskWords() { + calls = 0; + const GuestSigaction action{reinterpret_cast(RecordMask), SaRestart, {{Bit(SigKill), 1u << 7, 0, 1u << 31}}}; + Require(sigaction_nid_postfix(SigUsr1, &action, nullptr) == 0); + Require(raise_nid_postfix(SigUsr1) == 0); + Require(calls == 1); + Require(setInHandler.bits[0] == Bit(SigUsr1) && setInHandler.bits[1] == 1u << 7 && setInHandler.bits[2] == 0 && setInHandler.bits[3] == 1u << 31); + GuestSignalSet after{}; + Require(sigprocmask_nid_postfix(SigBlock, nullptr, &after) == 0); + Require(after.bits[0] == 0 && after.bits[1] == 0 && after.bits[2] == 0 && after.bits[3] == 0); +} + +static void CheckLeftWithLongjmp() { + calls = 0; + Install(SigUsr1, LeaveWithLongjmp, SaRestart, Bit(SigUsr2)); + for (int round = 1; round <= 2; ++round) { + if (setjmp_nid_postfix(handlerExit) == 0) { + raise_nid_postfix(SigUsr1); + std::abort(); + } + Require(calls == round); + Require(CurrentMask() == 0); + } +} + +int main() { + CheckDeferred(SigUsr2); + CheckDeferred(SigTerm); + CheckNodefer(); + CheckResethand(); + CheckOtherThreadKept(); + CheckOtherThreadKeptSameSignal(); + CheckOtherThreadSetMaskKept(); + CheckMaskRestoredInsideHandler(SigUsr2, SaveAndRestoreMask); + CheckMaskRestoredInsideHandler(SigTerm, SaveAndRestoreMask); + CheckMaskRestoredInsideHandler(SigUsr2, ReplaceAndRestoreMask); + CheckUnblockInsideHandler(); + CheckUpperMaskWords(); + CheckLeftWithLongjmp(); + return 0; +} diff --git a/core/libs/tests/GuestSignalInterrupt.cpp b/core/libs/tests/GuestSignalInterrupt.cpp new file mode 100644 index 0000000000..d520e1954a --- /dev/null +++ b/core/libs/tests/GuestSignalInterrupt.cpp @@ -0,0 +1,98 @@ +#include "SceTypes.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +using Handler = void (APS5_VABI *)(int); + +static_assert(sizeof(GuestSignalSet) == 16 && alignof(GuestSignalSet) == 4); +static_assert(sizeof(GuestSigaction) == 32 && offsetof(GuestSigaction, flags) == 8 && offsetof(GuestSigaction, mask) == 12); + +extern "C" { +extern GuestSignalSet _sigintr_nid_postfix; +} + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +bool Same(const GuestSigaction& first, const GuestSigaction& second) { + return first.handler == second.handler && first.flags == second.flags && + std::memcmp(&first.mask, &second.mask, sizeof(first.mask)) == 0; +} + +void APS5_VABI HandlerFunction(int) {} + +} + +int main() { + const GuestSignalSet initial = _sigintr_nid_postfix; + Require(initial.bits[0] == 0 && initial.bits[1] == 0 && initial.bits[2] == 0 && initial.bits[3] == 0, "initial interrupt set"); + for (int guest : {1, 32, 33, 64, 65, 96, 97, 128}) { + GuestSigaction original{}; + Require(sigaction_nid_postfix(guest, nullptr, &original) == 0, "read original disposition"); + GuestSigaction custom{reinterpret_cast(HandlerFunction), 0x42, + {{0x11111111, 0x22222222, 0x33333333, 0x44444444}}}; + Require(sigaction_nid_postfix(guest, &custom, nullptr) == 0, "install custom disposition"); + Require(sigaction_nid_postfix(guest, nullptr, &custom) == 0, "read installed disposition"); + const auto word = static_cast(guest - 1) / 32; + const auto bit = std::uint32_t{1} << (static_cast(guest - 1) % 32); + GuestSignalSet expected{{0x55555555, 0xaaaaaaaa, 0x55555555, 0xaaaaaaaa}}; + expected.bits[word] &= ~bit; + _sigintr_nid_postfix = expected; + for (int flag : {1, -1, std::numeric_limits::min(), std::numeric_limits::max()}) { + *__error_nid_postfix() = 12345; + Require(siginterrupt_nid_postfix(guest, flag) == 0, "nonzero flag"); + Require(*__error_nid_postfix() == 12345, "successful errno preservation"); + expected.bits[word] |= bit; + Require(std::memcmp(&_sigintr_nid_postfix, &expected, sizeof(expected)) == 0, "set bit without changing neighboring bits"); + GuestSigaction actual{}; + Require(sigaction_nid_postfix(guest, nullptr, &actual) == 0, "read interrupted disposition"); + auto interrupted = custom; + interrupted.flags &= ~0x2; + Require(Same(actual, interrupted), "preserve handler, mask and other flags"); + } + for (int repeat = 0; repeat < 2; ++repeat) { + *__error_nid_postfix() = 23456; + Require(siginterrupt_nid_postfix(guest, 0) == 0, "clear interrupt bit"); + Require(*__error_nid_postfix() == 23456, "zero flag errno preservation"); + expected.bits[word] &= ~bit; + Require(std::memcmp(&_sigintr_nid_postfix, &expected, sizeof(expected)) == 0, "clear bit without changing neighboring bits"); + GuestSigaction actual{}; + Require(sigaction_nid_postfix(guest, nullptr, &actual) == 0 && Same(actual, custom), "restore restart without changing disposition"); + } + Require(siginterrupt_nid_postfix(guest, -1) == 0, "make later signal interruptible"); + Require(signal_nid_postfix(guest, HandlerFunction) == HandlerFunction, "subsequent signal preserves previous handler"); + GuestSigaction actual{}; + Require(sigaction_nid_postfix(guest, nullptr, &actual) == 0 && actual.flags == 0, "signal consults shared interrupt set"); + Require(siginterrupt_nid_postfix(guest, 0) == 0, "make later signal restartable"); + Require(signal_nid_postfix(guest, HandlerFunction) == HandlerFunction, "repeat signal registration"); + Require(sigaction_nid_postfix(guest, nullptr, &actual) == 0 && actual.flags == 0x2, "signal restores restart"); + Require(sigaction_nid_postfix(guest, &original, nullptr) == 0, "restore original disposition"); + _sigintr_nid_postfix = initial; + } + const GuestSignalSet sentinels{{0x12345678, 0xabcdef01, 0x23456789, 0xbcdef012}}; + _sigintr_nid_postfix = sentinels; + GuestSigaction before{}; + Require(sigaction_nid_postfix(30, nullptr, &before) == 0, "read invalid-call control"); + for (int guest : {0, -1, 129, std::numeric_limits::min(), std::numeric_limits::max()}) { + for (int flag : {0, 1, -1}) { + *__error_nid_postfix() = 34567; + Require(siginterrupt_nid_postfix(guest, flag) == -1 && *__error_nid_postfix() == 22, "invalid signal reports EINVAL"); + Require(std::memcmp(&_sigintr_nid_postfix, &sentinels, sizeof(sentinels)) == 0, "invalid signal leaves all words unchanged"); + } + } + GuestSigaction after{}; + Require(sigaction_nid_postfix(30, nullptr, &after) == 0 && Same(before, after), "invalid calls preserve dispositions"); + _sigintr_nid_postfix = initial; +} diff --git a/core/libs/tests/GuestSignalSets.cpp b/core/libs/tests/GuestSignalSets.cpp index 786ddae94b..5440dc7032 100644 --- a/core/libs/tests/GuestSignalSets.cpp +++ b/core/libs/tests/GuestSignalSets.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/AsmMacros.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -7,21 +9,8 @@ #include #include -struct GuestSignalSet { - std::uint32_t bits[4]; -}; - extern "C" { -int APS5_VABI sigemptyset_nid_postfix(GuestSignalSet*); -int APS5_VABI sigfillset_nid_postfix(GuestSignalSet*); -int APS5_VABI sigaddset_nid_postfix(GuestSignalSet*, int); -int APS5_VABI sigdelset_nid_postfix(GuestSignalSet*, int); -int APS5_VABI sigismember_nid_postfix(const GuestSignalSet*, int); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI _sigprocmask_nid_postfix(int, const GuestSignalSet*, GuestSignalSet*); -int APS5_VABI sigprocmask_nid_postfix(int, const void*, void*); -[[gnu::returns_twice]] int APS5_VABI sigsetjmp_nid_postfix(std::uint64_t* buffer, int saveMask); -[[noreturn]] void APS5_VABI siglongjmp_nid_postfix(std::uint64_t* buffer, int value); + int APS5_VABI JumpKeepingCalleeSaved(std::uint64_t* buffer); } @@ -165,6 +154,30 @@ static void CheckSigsetjmp() { SetBlockedMask(0); } +static int LongjumpBack(int value, std::uint32_t maskAtSet, std::uint32_t maskAtJump) { + std::uint64_t buffer[12]{}; + volatile int jumped = 0; + SetBlockedMask(maskAtSet); + const int result = setjmp_nid_postfix(buffer); + if (jumped == 0) { + Require(result == 0); + Require(BufferWord(buffer, 72) == maskAtSet); + Require(BufferWord(buffer, 88) == 1); + jumped = 1; + SetBlockedMask(maskAtJump); + longjmp_nid_postfix(buffer, value); + } + return result; +} + +static void CheckLongjmp() { + Require(LongjumpBack(0, 0x5u, 0x30u) == 1); + Require(BlockedMask() == 0x5u); + Require(LongjumpBack(7, 0x80000009u, 0u) == 7); + Require(BlockedMask() == 0x80000009u); + SetBlockedMask(0); +} + static void CheckInvalidHow() { SetBlockedMask(0x5u); const GuestSignalSet blocked{{0x30u, 0, 0, 0}}; @@ -186,6 +199,7 @@ static void CheckInvalidHow() { int main() { CheckSigsetjmp(); CheckSiglongjmp(); + CheckLongjmp(); CheckInvalidHow(); static constexpr int Untouched = 5; GuestSignalSet set{{0x12345678u, 0x9abcdef0u, 0xffffffffu, 1u}}; diff --git a/core/libs/tests/GuestSignals.cpp b/core/libs/tests/GuestSignals.cpp index 5183ec41b0..8afec0ead8 100644 --- a/core/libs/tests/GuestSignals.cpp +++ b/core/libs/tests/GuestSignals.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -9,33 +11,15 @@ #include #include using Handler = void (APS5_VABI *)(int); -struct GuestSignalSet { - std::uint32_t bits[4]; -}; + extern "C" { -Handler APS5_VABI signal_nid_postfix(int, Handler); -int APS5_VABI raise_nid_postfix(int); -int APS5_VABI kill_nid_postfix(int, int); -int APS5_VABI getpid_nid_postfix(); -int APS5_VABI pthread_kill_nid_postfix(void*, int); -void* APS5_VABI scePthreadSelf(); -int APS5_VABI scePthreadCreate(void** thread, const void* attr, void* (APS5_VABI *entry)(void*), void* arg, const char* name); -int APS5_VABI scePthreadJoin(void* thread, void** retval); -int APS5_VABI sigaction_nid_postfix(int, const void*, void*); -int APS5_VABI sigprocmask_nid_postfix(int, const void*, void*); -int APS5_VABI pthread_sigmask_nid_postfix(int, const void*, void*); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sigsuspend_nid_postfix(const GuestSignalSet*); -int APS5_VABI _is_signal_return_nid_postfix(std::uint64_t); + unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int); -int APS5_VABI sigpending_nid_postfix(GuestSignalSet*); + } -struct GuestSigaction { - std::uintptr_t handler; - int flags; - GuestSignalSet mask; -}; + static_assert(sizeof(GuestSigaction) == 32); +extern GuestSignalSet _sigintr_nid_postfix; static bool Same(const GuestSigaction& left, const GuestSigaction& right) { return left.handler == right.handler && left.flags == right.flags && std::memcmp(&left.mask, &right.mask, sizeof(left.mask)) == 0; } @@ -54,7 +38,7 @@ void* APS5_VABI SuspendEntry(void* arg) { return nullptr; } struct SuspendSender { - void* target = nullptr; + Pthread target = nullptr; int result = -1; }; struct EndingThread { @@ -75,6 +59,12 @@ void APS5_VABI Callback(int value) { received = value; handlerReturn = reinterpret_cast(__builtin_return_address(0)); } +GuestSignalSet maskInHandler{}; +void APS5_VABI MaskCallback(int value) { + received = value; + sigprocmask_nid_postfix(1, nullptr, &maskInHandler); + +} static void Require(bool value) { if (!value) std::abort(); } int main() { const auto invalid = reinterpret_cast(static_cast(-1)); @@ -97,11 +87,11 @@ int main() { Require(pthread_kill_nid_postfix(nullptr, 15) == 3); Require(pthread_kill_nid_postfix(nullptr, 128) == 3); Require(pthread_kill_nid_postfix(nullptr, 129) == 22); - Require(pthread_kill_nid_postfix(reinterpret_cast(std::uintptr_t{0x10}), 15) == 3); - Require(pthread_kill_nid_postfix(reinterpret_cast(std::uintptr_t{0x10}), 0) == 3); + Require(pthread_kill_nid_postfix(reinterpret_cast(std::uintptr_t{0x10}), 15) == 3); + Require(pthread_kill_nid_postfix(reinterpret_cast(std::uintptr_t{0x10}), 0) == 3); { EndingThread ending; - void* thread = nullptr; + Pthread thread = nullptr; Require(scePthreadCreate(&thread, nullptr, EndingEntry, &ending, nullptr) == 0); Require(pthread_kill_nid_postfix(thread, 0) == 0); ending.release.store(true); @@ -143,6 +133,17 @@ int main() { Require(sigprocmask_nid_postfix(1, nullptr, &previous) == 0 && previous.bits[0] == 0x20); Require(pthread_sigmask_nid_postfix(2, &blocked, &previous) == 0 && previous.bits[0] == 0x20); Require(pthread_sigmask_nid_postfix(1, nullptr, &previous) == 0 && previous.bits[0] == 0); + const GuestSignalSet withKillAndStop{{(1u << 8) | (1u << 16) | 0x20, 0, 0, 0}}; + const GuestSignalSet noSignals{{0, 0, 0, 0}}; + Require(sigprocmask_nid_postfix(1, &withKillAndStop, nullptr) == 0); + Require(sigprocmask_nid_postfix(1, nullptr, &previous) == 0 && previous.bits[0] == 0x20); + Require(sigprocmask_nid_postfix(3, &noSignals, nullptr) == 0); + Require(sigprocmask_nid_postfix(3, &withKillAndStop, nullptr) == 0); + Require(sigprocmask_nid_postfix(1, nullptr, &previous) == 0 && previous.bits[0] == 0x20); + Require(sigprocmask_nid_postfix(3, &noSignals, nullptr) == 0); + Require(pthread_sigmask_nid_postfix(1, &withKillAndStop, nullptr) == 0); + Require(pthread_sigmask_nid_postfix(1, nullptr, &previous) == 0 && previous.bits[0] == 0x20); + Require(pthread_sigmask_nid_postfix(3, &noSignals, nullptr) == 0); const auto callback = reinterpret_cast(Callback); const auto infoCallback = reinterpret_cast(InfoCallback); @@ -161,6 +162,15 @@ int main() { Require(signal_nid_postfix(15, Callback) == nullptr); Require(sigaction_nid_postfix(15, nullptr, ¤t) == 0 && current.handler == callback && current.flags == 0x2); Require(signal_nid_postfix(15, nullptr) == Callback); + for (const int flags : {0x80, 0x100, 0x8000, 0x40 | 0x80}) { + const GuestSigaction undefinedFlags{callback, flags, {}}; + *__error_nid_postfix() = 0; + Require(sigaction_nid_postfix(15, &undefinedFlags, nullptr) == -1 && *__error_nid_postfix() == 22); + Require(sigaction_nid_postfix(15, nullptr, ¤t) == 0 && current.handler == 0 && current.flags == 0x2); + } + const GuestSigaction ignoredWithFlags{1, 0x8000, {}}; + Require(sigaction_nid_postfix(15, &ignoredWithFlags, nullptr) == 0); + Require(sigaction_nid_postfix(15, &defaults, nullptr) == 0); const GuestSigaction crash{infoCallback, 0x40 | 0x10, {}}; for (int recorded : {10, 1, 64, 128}) { @@ -173,6 +183,7 @@ int main() { bool rejected = false; try { raise_nid_postfix(6); + } catch (const std::runtime_error&) { rejected = true; } @@ -215,12 +226,20 @@ int main() { *__error_nid_postfix() = 0; Require(sigsuspend_nid_postfix(&empty) == -1 && *__error_nid_postfix() == 4 && received == 30); Require(sigprocmask_nid_postfix(1, nullptr, &previous) == 0 && previous.bits[0] == user.bits[0]); + Require(signal_nid_postfix(30, MaskCallback) != invalid); + const GuestSignalSet suspendWithKillAndStop{{(1u << 8) | (1u << 16) | (1u << 28), 0, 0, 0}}; + received = 0; + Require(raise_nid_postfix(30) == 0 && received == 0); + Require(sigsuspend_nid_postfix(&suspendWithKillAndStop) == -1 && received == 30); + Require(maskInHandler.bits[0] == ((1u << 28) | (1u << 29))); + Require(sigprocmask_nid_postfix(1, nullptr, &previous) == 0 && previous.bits[0] == user.bits[0]); + Require(signal_nid_postfix(30, Callback) != invalid); received = 0; SuspendWait wait{&empty, 0, 0}; - void* suspender = nullptr; + Pthread suspender = nullptr; Require(scePthreadCreate(&suspender, nullptr, SuspendEntry, &wait, nullptr) == 0); SuspendSender sender{suspender, -1}; - void* senderThread = nullptr; + Pthread senderThread = nullptr; Require(scePthreadCreate(&senderThread, nullptr, SuspendSend, &sender, nullptr) == 0); Require(scePthreadJoin(senderThread, nullptr) == 0 && sender.result == 0); Require(scePthreadJoin(suspender, nullptr) == 0); @@ -235,10 +254,10 @@ int main() { Require(signal_nid_postfix(73, Callback) != invalid); received = 0; SuspendWait highWait{&suspendMask, 0, 0}; - void* highSuspender = nullptr; + Pthread highSuspender = nullptr; Require(scePthreadCreate(&highSuspender, nullptr, SuspendEntry, &highWait, nullptr) == 0); SuspendSender highSender{highSuspender, -1}; - void* highSenderThread = nullptr; + Pthread highSenderThread = nullptr; Require(scePthreadCreate(&highSenderThread, nullptr, SuspendSend, &highSender, nullptr) == 0); Require(scePthreadJoin(highSenderThread, nullptr) == 0 && highSender.result == 0); Require(scePthreadJoin(highSuspender, nullptr) == 0); @@ -365,4 +384,93 @@ int main() { Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == 0); Require(sigprocmask_nid_postfix(3, &noneBlocked, nullptr) == 0); Require(sigaction_nid_postfix(30, &restored, nullptr) == 0); + + const GuestSigaction caught{callback, 0, {}}; + received = 0; + Require(sigaction_nid_postfix(30, &caught, nullptr) == 0); + Require(sigprocmask_nid_postfix(3, &userSignal, nullptr) == 0); + Require(raise_nid_postfix(30) == 0 && received == 0); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == userSignal.bits[0]); + Require(sigaction_nid_postfix(30, &restored, nullptr) == 0); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == userSignal.bits[0]); + Require(sigaction_nid_postfix(30, &ignored, nullptr) == 0); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == 0); + Require(sigaction_nid_postfix(30, &caught, nullptr) == 0); + Require(sigprocmask_nid_postfix(3, &noneBlocked, nullptr) == 0 && received == 0); + Require(sigprocmask_nid_postfix(3, &userSignal, nullptr) == 0); + Require(raise_nid_postfix(30) == 0 && received == 0); + Require(signal_nid_postfix(30, ignore) == Callback); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == 0); + Require(signal_nid_postfix(30, Callback) == ignore); + Require(sigprocmask_nid_postfix(3, &noneBlocked, nullptr) == 0 && received == 0); + Require(signal_nid_postfix(30, nullptr) == Callback); + + const GuestSignalSet windowSignal{{1u << 27, 0, 0, 0}}; + Require(sigaction_nid_postfix(28, &caught, nullptr) == 0); + Require(sigprocmask_nid_postfix(3, &windowSignal, nullptr) == 0); + Require(raise_nid_postfix(28) == 0 && received == 0); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == windowSignal.bits[0]); + Require(sigaction_nid_postfix(28, &restored, nullptr) == 0); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == 0); + Require(sigaction_nid_postfix(28, &caught, nullptr) == 0); + Require(sigprocmask_nid_postfix(3, &noneBlocked, nullptr) == 0 && received == 0); + Require(sigaction_nid_postfix(28, &restored, nullptr) == 0); + + const GuestSignalSet continueSignal{{1u << 18, 0, 0, 0}}; + Require(sigprocmask_nid_postfix(3, &continueSignal, nullptr) == 0); + Require(raise_nid_postfix(19) == 0); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == continueSignal.bits[0]); + Require(signal_nid_postfix(19, nullptr) == nullptr); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == 0); + Require(sigprocmask_nid_postfix(3, &noneBlocked, nullptr) == 0 && received == 0); + + const GuestSignalSet highSignal{{0, 1u << 7, 0, 0}}; + Require(sigprocmask_nid_postfix(3, &highSignal, nullptr) == 0); + Require(raise_nid_postfix(40) == 0); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[1] == highSignal.bits[1]); + Require(sigaction_nid_postfix(40, &ignored, nullptr) == 0); + Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == 0 && pendingIgnored.bits[1] == 0); + Require(sigaction_nid_postfix(40, &restored, nullptr) == 0); + Require(sigprocmask_nid_postfix(3, &noneBlocked, nullptr) == 0); + Require(_sigintr_nid_postfix.bits[0] == 0 && _sigintr_nid_postfix.bits[1] == 0 && _sigintr_nid_postfix.bits[2] == 0 && + _sigintr_nid_postfix.bits[3] == 0); + GuestSigaction interruptible{}; + Require(signal_nid_postfix(30, Callback) == nullptr); + Require(sigaction_nid_postfix(30, nullptr, &interruptible) == 0 && interruptible.flags == 0x2); + _sigintr_nid_postfix.bits[0] = 1u << 29; + Require(signal_nid_postfix(30, Callback) == Callback); + Require(sigaction_nid_postfix(30, nullptr, &interruptible) == 0 && + interruptible.handler == reinterpret_cast(Callback) && interruptible.flags == 0); + signal_nid_postfix(31, Callback); + + Require(sigaction_nid_postfix(31, nullptr, &interruptible) == 0 && interruptible.flags == 0x2); + _sigintr_nid_postfix.bits[3] = 1u << 31; + Require(signal_nid_postfix(128, Callback) == nullptr); + Require(sigaction_nid_postfix(128, nullptr, &interruptible) == 0 && interruptible.flags == 0); + _sigintr_nid_postfix.bits[0] = 0; + _sigintr_nid_postfix.bits[3] = 0; + Require(signal_nid_postfix(30, Callback) == Callback); + Require(sigaction_nid_postfix(30, nullptr, &interruptible) == 0 && interruptible.flags == 0x2); + Require(signal_nid_postfix(30, nullptr) == Callback && signal_nid_postfix(31, nullptr) == Callback && + signal_nid_postfix(128, nullptr) == Callback); + Require(signal_nid_postfix(30, Callback) == nullptr); + Require(siginterrupt_nid_postfix(30, 1) == 0 && _sigintr_nid_postfix.bits[0] == (1u << 29)); + Require(sigaction_nid_postfix(30, nullptr, &interruptible) == 0 && + interruptible.handler == reinterpret_cast(Callback) && interruptible.flags == 0); + Require(signal_nid_postfix(30, Callback) == Callback); + Require(sigaction_nid_postfix(30, nullptr, &interruptible) == 0 && interruptible.flags == 0); + Require(siginterrupt_nid_postfix(30, 0) == 0 && _sigintr_nid_postfix.bits[0] == 0); + Require(sigaction_nid_postfix(30, nullptr, &interruptible) == 0 && + interruptible.handler == reinterpret_cast(Callback) && interruptible.flags == 0x2); + Require(siginterrupt_nid_postfix(128, 1) == 0 && _sigintr_nid_postfix.bits[3] == (1u << 31)); + Require(sigaction_nid_postfix(128, nullptr, &interruptible) == 0 && interruptible.handler == 0 && interruptible.flags == 0); + Require(siginterrupt_nid_postfix(128, 0) == 0 && _sigintr_nid_postfix.bits[3] == 0); + Require(sigaction_nid_postfix(128, nullptr, &interruptible) == 0 && interruptible.flags == 0x2); + for (const int badSignal : {0, -1, 129}) { + *__error_nid_postfix() = 0; + Require(siginterrupt_nid_postfix(badSignal, 1) == -1 && *__error_nid_postfix() == 22); + } + Require(_sigintr_nid_postfix.bits[0] == 0 && _sigintr_nid_postfix.bits[3] == 0); + Require(signal_nid_postfix(30, nullptr) == Callback); + Require(sigaction_nid_postfix(128, &restored, nullptr) == 0); } diff --git a/core/libs/tests/GuestSigninDialog.cpp b/core/libs/tests/GuestSigninDialog.cpp index d8410fcbd5..7baf28dae6 100644 --- a/core/libs/tests/GuestSigninDialog.cpp +++ b/core/libs/tests/GuestSigninDialog.cpp @@ -1,18 +1,9 @@ +#include "prx/libSceSigninDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -int APS5_VABI sceSigninDialogInitialize(void); -int APS5_VABI sceSigninDialogOpen(const void* param); -int APS5_VABI sceSigninDialogUpdateStatus(void); -int APS5_VABI sceSigninDialogGetStatus(void); -int APS5_VABI sceSigninDialogGetResult(void* result); -int APS5_VABI sceSigninDialogClose(void); -int APS5_VABI sceSigninDialogTerminate(void); -} - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -34,8 +25,10 @@ int main() { std::memset(result, 0x5a, sizeof(result)); Require(sceSigninDialogTerminate() == kErrNotInitialized); + Require(sceSigninDialogOpen(nullptr) == kErrNotInitialized); Require(sceSigninDialogOpen(¶m) == kErrNotInitialized); Require(sceSigninDialogClose() == kErrNotInitialized); + Require(sceSigninDialogGetResult(nullptr) == kErrNotInitialized); Require(sceSigninDialogGetResult(result) == kErrNotInitialized); Require(sceSigninDialogGetStatus() == kStatusNone); Require(sceSigninDialogUpdateStatus() == kStatusNone); @@ -45,6 +38,7 @@ int main() { Require(sceSigninDialogGetStatus() == kStatusInitialized); Require(sceSigninDialogOpen(nullptr) == kErrArgNull); + Require(sceSigninDialogGetResult(nullptr) == kErrNotFinished); Require(sceSigninDialogGetResult(result) == kErrNotFinished); Require(sceSigninDialogOpen(¶m) == 0); diff --git a/core/libs/tests/GuestSigtimedwait.cpp b/core/libs/tests/GuestSigtimedwait.cpp index 0663a3ce2f..4c0101000c 100644 --- a/core/libs/tests/GuestSigtimedwait.cpp +++ b/core/libs/tests/GuestSigtimedwait.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -6,13 +8,7 @@ #include #include #include -struct GuestSignalSet { - std::uint32_t bits[4]; -}; -struct GuestTimespec { - std::int64_t seconds; - std::int64_t nanoseconds; -}; + struct GuestSiginfo { std::int32_t signo; std::int32_t error; @@ -28,22 +24,7 @@ struct GuestSiginfo { std::int32_t tail; }; using Handler = void (APS5_VABI *)(int); -extern "C" { -int APS5_VABI sigtimedwait_nid_postfix(const GuestSignalSet*, void*, const GuestTimespec*); -int APS5_VABI sigwaitinfo_nid_postfix(const GuestSignalSet*, void*); -int APS5_VABI sigwait_nid_postfix(const GuestSignalSet*, int*); -int APS5_VABI pause_nid_postfix(void); -Handler APS5_VABI signal_nid_postfix(int, Handler); -int APS5_VABI raise_nid_postfix(int); -int APS5_VABI kill_nid_postfix(int, int); -int APS5_VABI _sigprocmask_nid_postfix(int, const GuestSignalSet*, GuestSignalSet*); -int APS5_VABI getpid_nid_postfix(void); -Pthread APS5_VABI scePthreadSelf(void); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI pthread_kill_nid_postfix(Pthread thread, int guest); -int* APS5_VABI __error_nid_postfix(); -} + volatile int received = 0; void APS5_VABI Callback(int value) { received = value; } static void Require(bool value) { if (!value) std::abort(); } @@ -97,8 +78,8 @@ static void CheckTimeoutWithUnrelatedSignals() { const GuestSignalSet blocked{{(1u << 1) | (1u << 12), 0, 0, 0}}; const GuestSignalSet pipe{{1u << 12, 0, 0, 0}}; const GuestSignalSet interrupt{{1u << 1, 0, 0, 0}}; - const GuestTimespec timeout{0, 200000000}; - const GuestTimespec poll{0, 0}; + const KernelTimespec timeout{0, 200000000}; + const KernelTimespec poll{0, 0}; GuestSignalSet previous{}; Require(_sigprocmask_nid_postfix(1, &blocked, &previous) == 0); TimeoutSender sender{scePthreadSelf()}; @@ -122,8 +103,8 @@ int main() { const GuestSignalSet empty{{0, 0, 0, 0}}; const GuestSignalSet pipe{{1u << 12, 0, 0, 0}}; const GuestSignalSet interrupt{{1u << 1, 0, 0, 0}}; - const GuestTimespec poll{0, 0}; - const GuestTimespec expired{0, 1000000000}; + const KernelTimespec poll{0, 0}; + const KernelTimespec expired{0, 1000000000}; GuestSiginfo info{}; int taken = -1; *__error_nid_postfix() = 0; @@ -189,7 +170,7 @@ int main() { Require(sigtimedwait_nid_postfix(&pipe, nullptr, nullptr) == 13); JoinSender(senderThread, sender); senderThread = StartSender(sender); - const GuestTimespec patience{1, 0}; + const KernelTimespec patience{1, 0}; Require(sigtimedwait_nid_postfix(&pipe, &info, &patience) == 13 && info.signo == 13); JoinSender(senderThread, sender); Require(signal_nid_postfix(15, Callback) != reinterpret_cast(static_cast(-1))); diff --git a/core/libs/tests/GuestSleepCancel.cpp b/core/libs/tests/GuestSleepCancel.cpp index 463a4a0cbe..a77fbe126f 100644 --- a/core/libs/tests/GuestSleepCancel.cpp +++ b/core/libs/tests/GuestSleepCancel.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include @@ -9,15 +10,6 @@ #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** result); -int APS5_VABI scePthreadCancel(Pthread thread); -int APS5_VABI scePthreadSetcancelstate(int state, int* oldState); -void APS5_VABI scePthreadTestcancel(); -unsigned int APS5_VABI sleep_nid_postfix(unsigned int seconds); -} - 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()); @@ -46,6 +38,7 @@ static void* APS5_VABI Sleeper(void*) { if (disabled) { Require(scePthreadSetcancelstate(0, nullptr) == 0); scePthreadTestcancel(); + Require(false); } return nullptr; diff --git a/core/libs/tests/GuestSleepWake.cpp b/core/libs/tests/GuestSleepWake.cpp index 177ef4b990..d4477301c6 100644 --- a/core/libs/tests/GuestSleepWake.cpp +++ b/core/libs/tests/GuestSleepWake.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/Time/include/Time.hpp" #include "SceTypes.hpp" #include #include @@ -6,7 +7,7 @@ #include extern "C" { -int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); + void KernelWakeSleepers_nid_no_patch(); void KernelNoteGpuSubmit_nid_no_patch(); } diff --git a/core/libs/tests/GuestSocialScreenDialog.cpp b/core/libs/tests/GuestSocialScreenDialog.cpp new file mode 100644 index 0000000000..dcbb9c085e --- /dev/null +++ b/core/libs/tests/GuestSocialScreenDialog.cpp @@ -0,0 +1,63 @@ +#include "prx/libSceSocialScreenDialog/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80B80003); +constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); +constexpr int kErrNotFinished = static_cast(0x80B80005); +constexpr int kErrArgNull = static_cast(0x80B8000D); +constexpr int kResultUserCanceled = 1; + +} + +int main() { + std::int64_t param = 0; + std::int32_t result[2] = {-1, -1}; + + Require(sceSocialScreenDialogTerminate() == kErrNotInitialized); + Require(sceSocialScreenDialogOpen(¶m) == kErrNotInitialized); + Require(sceSocialScreenDialogClose() == kErrNotInitialized); + Require(sceSocialScreenDialogGetResult(result) == kErrNotInitialized); + Require(sceSocialScreenDialogGetStatus() == kStatusNone); + Require(sceSocialScreenDialogUpdateStatus() == kStatusNone); + + Require(sceSocialScreenDialogInitialize() == 0); + Require(sceSocialScreenDialogInitialize() == kErrAlreadyInitialized); + Require(sceSocialScreenDialogGetStatus() == kStatusInitialized); + Require(sceSocialScreenDialogUpdateStatus() == kStatusInitialized); + + Require(sceSocialScreenDialogOpen(nullptr) == kErrArgNull); + Require(sceSocialScreenDialogGetResult(result) == kErrNotFinished); + + Require(sceSocialScreenDialogOpen(¶m) == 0); + Require(sceSocialScreenDialogGetStatus() == kStatusFinished); + Require(sceSocialScreenDialogUpdateStatus() == kStatusFinished); + + Require(sceSocialScreenDialogGetResult(nullptr) == kErrArgNull); + Require(sceSocialScreenDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(result[1] == -1); + + Require(sceSocialScreenDialogClose() == 0); + Require(sceSocialScreenDialogTerminate() == 0); + Require(sceSocialScreenDialogTerminate() == kErrNotInitialized); + Require(sceSocialScreenDialogGetStatus() == kStatusNone); + Require(sceSocialScreenDialogUpdateStatus() == kStatusNone); + + Require(sceSocialScreenDialogInitialize() == 0); + Require(sceSocialScreenDialogUpdateStatus() == kStatusInitialized); + Require(sceSocialScreenDialogClose() == 0); + Require(sceSocialScreenDialogUpdateStatus() == kStatusFinished); + result[0] = -1; + Require(sceSocialScreenDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(sceSocialScreenDialogTerminate() == 0); +} diff --git a/core/libs/tests/GuestSocketAddressLength.cpp b/core/libs/tests/GuestSocketAddressLength.cpp index 930a8626d6..e2b7017eec 100644 --- a/core/libs/tests/GuestSocketAddressLength.cpp +++ b/core/libs/tests/GuestSocketAddressLength.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,21 +7,13 @@ #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -std::int64_t APS5_VABI sendto_nid_postfix(int, const void*, std::uint64_t, int, const void*, std::uint32_t); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int Stream = 1; constexpr int Datagram = 2; constexpr int ErrorFault = 14; constexpr int ErrorInvalid = 22; +constexpr int ErrorAddressFamilyNotSupported = 47; constexpr int ErrorNameTooLong = 63; void Require(bool condition, const char* what) { @@ -51,11 +45,89 @@ void ShortAddressesAreInvalid() { const int stream6 = socket_nid_postfix(28, Stream, 0); Require(stream6 >= 0, "socket failed"); Require(Fails(connect_nid_postfix(stream6, v6.data(), 20), ErrorInvalid), "connect with an AF_INET6 address shorter than sockaddr_in6 must fail with EINVAL (22)"); + Require(Fails(bind_nid_postfix(stream6, v6.data(), 20), ErrorInvalid), "bind with an AF_INET6 address shorter than sockaddr_in6 must fail with EINVAL (22)"); Require(Fails(bind_nid_postfix(stream, address.data(), 256), ErrorNameTooLong), "an address longer than SOCK_MAXADDRLEN (255) must fail with ENAMETOOLONG (63)"); Require(Fails(bind_nid_postfix(stream, nullptr, address.size()), ErrorFault), "a null address must fail with EFAULT (14)"); Require(close_nid_postfix(stream) == 0 && close_nid_postfix(datagram) == 0 && close_nid_postfix(stream6) == 0, "close failed"); } +void ArgumentsAreCheckedBeforeTheDescriptor() { + const auto address = Loopback(); + constexpr int ErrorBadDescriptor = 9; + Require(Fails(bind_nid_postfix(-1, address.data(), 1), ErrorInvalid), "bind(-1) with a 1-byte address must fail with EINVAL (22) before the descriptor is looked up"); + Require(Fails(bind_nid_postfix(-1, address.data(), 256), ErrorNameTooLong), "bind(-1) with a 256-byte address must fail with ENAMETOOLONG (63)"); + Require(Fails(bind_nid_postfix(-1, nullptr, address.size()), ErrorFault), "bind(-1) with a null address must fail with EFAULT (14)"); + Require(Fails(connect_nid_postfix(-1, address.data(), 1), ErrorInvalid), "connect(-1) with a 1-byte address must fail with EINVAL (22)"); + Require(Fails(sendto_nid_postfix(-1, "x", 1, 0, address.data(), 1), ErrorInvalid), "sendto(-1) with a 1-byte address must fail with EINVAL (22)"); + Require(Fails(bind_nid_postfix(-1, address.data(), address.size()), ErrorBadDescriptor), "bind(-1) with a valid address must fail with EBADF (9)"); + Require(Fails(connect_nid_postfix(-1, address.data(), address.size()), ErrorBadDescriptor), "connect(-1) with a valid address must fail with EBADF (9)"); + Require(Fails(sendto_nid_postfix(-1, "x", 1, 0, address.data(), address.size()), ErrorBadDescriptor), "sendto(-1) with a valid address must fail with EBADF (9)"); +} + +void LongAddressesAndOtherFamiliesFail() { + const int datagram = socket_nid_postfix(2, Datagram, 0); + const int stream6 = socket_nid_postfix(28, Stream, 0); + const int datagram6 = socket_nid_postfix(28, Datagram, 0); + Require(datagram >= 0 && stream6 >= 0 && datagram6 >= 0, "socket failed"); + std::array address{16, 2}; + address[4] = 127; + address[7] = 1; + for (const std::uint32_t length : {17u, 28u, 255u}) { + Require(Fails(bind_nid_postfix(datagram, address.data(), length), ErrorInvalid), "bind with an AF_INET address longer than sockaddr_in must fail with EINVAL (22)"); + Require(Fails(connect_nid_postfix(datagram, address.data(), length), ErrorInvalid), "connect with an AF_INET address longer than sockaddr_in must fail with EINVAL (22)"); + Require(Fails(sendto_nid_postfix(datagram, "x", 1, 0, address.data(), length), ErrorInvalid), "sendto with an AF_INET address longer than sockaddr_in must fail with EINVAL (22)"); + } + std::array v6{28, 28}; + v6[23] = 1; + Require(Fails(bind_nid_postfix(stream6, v6.data(), 29), ErrorInvalid), "bind with an AF_INET6 address longer than sockaddr_in6 must fail with EINVAL (22)"); + Require(Fails(connect_nid_postfix(stream6, v6.data(), 64), ErrorInvalid), "connect with an AF_INET6 address longer than sockaddr_in6 must fail with EINVAL (22)"); + for (const std::uint8_t family : {std::uint8_t{28}, std::uint8_t{1}, std::uint8_t{0}}) { + address[1] = family; + Require(Fails(bind_nid_postfix(datagram, address.data(), 16), ErrorAddressFamilyNotSupported), "bind of another family on an AF_INET socket must fail with EAFNOSUPPORT (47)"); + } + address[1] = 28; + Require(Fails(sendto_nid_postfix(datagram, "x", 1, 0, address.data(), 28), ErrorInvalid), "sendto checks the length before the family"); + Require(Fails(sendto_nid_postfix(datagram, "x", 1, 0, address.data(), 16), ErrorAddressFamilyNotSupported), "sendto of another family fails with EAFNOSUPPORT (47)"); + address[1] = 2; + Require(Fails(bind_nid_postfix(datagram6, address.data(), 16), ErrorInvalid), "an AF_INET6 socket checks the length before the family"); + Require(Fails(connect_nid_postfix(datagram6, address.data(), 28), ErrorAddressFamilyNotSupported), "an AF_INET address of the AF_INET6 length fails with EAFNOSUPPORT (47)"); + Require(close_nid_postfix(datagram) == 0 && close_nid_postfix(stream6) == 0 && close_nid_postfix(datagram6) == 0, "close failed"); +} + +void ConnectedStreamSendtoIgnoresTheAddress() { + const int listener = socket_nid_postfix(2, Stream, 0); + const int client = socket_nid_postfix(2, Stream, 0); + Require(listener >= 0 && client >= 0, "socket failed"); + auto address = Loopback(); + Require(bind_nid_postfix(listener, address.data(), address.size()) == 0 && listen_nid_postfix(listener, 1) == 0, "listen failed"); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(listener, address.data(), &size) == 0, "getsockname failed"); + Require(connect_nid_postfix(client, address.data(), address.size()) == 0, "connect failed"); + std::array padded{}; + std::memcpy(padded.data(), address.data(), address.size()); + Require(sendto_nid_postfix(client, "x", 1, 0, padded.data(), padded.size()) == 1, "tcp_usr_send ignores the address of a connected stream socket, so sendto must send"); + Require(Fails(sendto_nid_postfix(client, "x", 1, 0, padded.data(), 1), ErrorInvalid), "getsockaddr still checks the length of a connected stream socket's address"); + Require(close_nid_postfix(client) == 0 && close_nid_postfix(listener) == 0, "close failed"); +} + +void MismatchedAndUnknownFamiliesFail() { + const int stream = socket_nid_postfix(2, Stream, 0); + const int datagram = socket_nid_postfix(2, Datagram, 0); + const int stream6 = socket_nid_postfix(28, Stream, 0); + const int datagram6 = socket_nid_postfix(28, Datagram, 0); + Require(stream >= 0 && datagram >= 0 && stream6 >= 0 && datagram6 >= 0, "socket failed"); + const auto address = Loopback(); + Require(Fails(bind_nid_postfix(stream6, address.data(), address.size()), ErrorInvalid), "bind with an AF_INET address on an AF_INET6 socket must fail with EINVAL (22)"); + std::array unknownFamily{16, 99}; + Require(Fails(connect_nid_postfix(stream, unknownFamily.data(), unknownFamily.size()), ErrorAddressFamilyNotSupported), "connect with an unknown family must fail with EAFNOSUPPORT (47)"); + Require(Fails(sendto_nid_postfix(datagram, "x", 1, 0, unknownFamily.data(), unknownFamily.size()), ErrorAddressFamilyNotSupported), "sendto with an unknown family must fail with EAFNOSUPPORT (47)"); + std::array unknownFamily6{28, 99}; + Require(Fails(bind_nid_postfix(stream6, unknownFamily6.data(), unknownFamily6.size()), ErrorAddressFamilyNotSupported), "bind with an unknown family must fail with EAFNOSUPPORT (47)"); + Require(Fails(connect_nid_postfix(stream6, unknownFamily6.data(), unknownFamily6.size()), ErrorAddressFamilyNotSupported), "connect with an unknown family must fail with EAFNOSUPPORT (47)"); + Require(Fails(sendto_nid_postfix(datagram6, "x", 1, 0, unknownFamily6.data(), unknownFamily6.size()), ErrorAddressFamilyNotSupported), "sendto with an unknown family must fail with EAFNOSUPPORT (47)"); + Require(close_nid_postfix(stream) == 0 && close_nid_postfix(datagram) == 0 && close_nid_postfix(stream6) == 0 && close_nid_postfix(datagram6) == 0, "close failed"); +} + void FullAddressesStillWork() { const int stream = socket_nid_postfix(2, Stream, 0); Require(stream >= 0, "socket failed"); @@ -64,10 +136,77 @@ void FullAddressesStillWork() { Require(close_nid_postfix(stream) == 0, "close failed"); } +std::array PaddedLoopback(int family) { + std::array address{}; + if (family == 2) { + const auto v4 = Loopback(); + std::memcpy(address.data(), v4.data(), v4.size()); + } else { + address[0] = 28; + address[1] = 28; + address[23] = 1; + } + return address; +} + +void LongAddressesAreInvalid(int family, int type) { + const std::uint32_t size = family == 2 ? 16 : 28; + auto address = PaddedLoopback(family); + const int descriptor = socket_nid_postfix(family, type, 0); + const int listener = type == Stream ? socket_nid_postfix(family, Stream, 0) : descriptor; + Require(descriptor >= 0 && listener >= 0, "socket failed"); + for (std::uint32_t length = size + 1; length <= address.size(); ++length) { + Require(Fails(bind_nid_postfix(descriptor, address.data(), length), ErrorInvalid), "bind with an address longer than sockaddr_in or sockaddr_in6 must fail with EINVAL (22)"); + } + Require(bind_nid_postfix(listener, address.data(), size) == 0, "bind with an address of exactly the size of sockaddr_in or sockaddr_in6 failed"); + if (type == Stream) Require(listen_nid_postfix(listener, 1) == 0, "listen failed"); + std::uint32_t named = size; + Require(getsockname_nid_postfix(listener, address.data(), &named) == 0 && named == size, "getsockname failed"); + for (std::uint32_t length = size + 1; length <= address.size(); ++length) { + Require(Fails(connect_nid_postfix(descriptor, address.data(), length), ErrorInvalid), "connect with an address longer than sockaddr_in or sockaddr_in6 must fail with EINVAL (22)"); + } + if (family == 28 && type == Datagram) { + for (std::uint32_t length = size + 1; length <= address.size(); ++length) { + Require(Fails(sendto_nid_postfix(descriptor, "x", 1, 0, address.data(), length), ErrorInvalid), "sendto on an AF_INET6 datagram socket with an address longer than sockaddr_in6 must fail with EINVAL (22)"); + } + Require(sendto_nid_postfix(descriptor, "x", 1, 0, address.data(), size) == 1, "sendto with an address of exactly the size of sockaddr_in6 failed"); + } + Require(connect_nid_postfix(descriptor, address.data(), size) == 0, "connect with an address of exactly the size of sockaddr_in or sockaddr_in6 failed"); + Require(close_nid_postfix(descriptor) == 0, "close failed"); + if (listener != descriptor) Require(close_nid_postfix(listener) == 0, "close failed"); +} + +void LongMappedAddressesBindOnlyDualStackDatagramSockets() { + const int datagram = socket_nid_postfix(28, Datagram, 0); + Require(datagram >= 0, "socket failed"); + std::array mapped{29, 28}; + mapped[18] = 0xff; + mapped[19] = 0xff; + mapped[20] = 127; + mapped[23] = 1; + Require(Fails(bind_nid_postfix(datagram, mapped.data(), mapped.size()), ErrorInvalid), "bind of a long IPv4-mapped address with IPV6_V6ONLY set must fail with EINVAL (22)"); + const int off = 0; + Require(setsockopt_nid_postfix(datagram, 41, 27, &off, sizeof(off)) == 0, "clearing IPV6_V6ONLY failed"); + Require(bind_nid_postfix(datagram, mapped.data(), mapped.size()) == 0, "udp6_bind passes a long IPv4-mapped address to in_pcbbind without IPV6_V6ONLY, so bind must succeed"); + Require(Fails(sendto_nid_postfix(datagram, "x", 1, 0, mapped.data(), mapped.size()), ErrorInvalid), "udp6_send checks the length before the IPv4-mapped case, so sendto must fail with EINVAL (22)"); + mapped[3] = 9; + Require(connect_nid_postfix(datagram, mapped.data(), mapped.size()) == 0, "udp6_connect passes a long IPv4-mapped address to in_pcbconnect without IPV6_V6ONLY, so connect must succeed"); + Require(close_nid_postfix(datagram) == 0, "close failed"); +} + } int main() { ShortAddressesAreInvalid(); + ArgumentsAreCheckedBeforeTheDescriptor(); + LongAddressesAndOtherFamiliesFail(); + ConnectedStreamSendtoIgnoresTheAddress(); + MismatchedAndUnknownFamiliesFail(); FullAddressesStillWork(); + LongAddressesAreInvalid(2, Stream); + LongAddressesAreInvalid(2, Datagram); + LongAddressesAreInvalid(28, Stream); + LongAddressesAreInvalid(28, Datagram); + LongMappedAddressesBindOnlyDualStackDatagramSockets(); return 0; } diff --git a/core/libs/tests/GuestSocketConnectListening.cpp b/core/libs/tests/GuestSocketConnectListening.cpp index 0f42c12bdb..fb5e900c3d 100644 --- a/core/libs/tests/GuestSocketConnectListening.cpp +++ b/core/libs/tests/GuestSocketConnectListening.cpp @@ -1,20 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int Inet = 2; diff --git a/core/libs/tests/GuestSocketDescriptorFlags.cpp b/core/libs/tests/GuestSocketDescriptorFlags.cpp index ef8772f738..6d14f3997a 100644 --- a/core/libs/tests/GuestSocketDescriptorFlags.cpp +++ b/core/libs/tests/GuestSocketDescriptorFlags.cpp @@ -1,18 +1,10 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI dup_nid_postfix(int); -int APS5_VABI dup2_nid_postfix(int, int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int DuplicateDescriptor = 0; diff --git a/core/libs/tests/GuestSocketDescriptors.cpp b/core/libs/tests/GuestSocketDescriptors.cpp index 9a0ac4f5e6..3a66b41464 100644 --- a/core/libs/tests/GuestSocketDescriptors.cpp +++ b/core/libs/tests/GuestSocketDescriptors.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -25,32 +28,6 @@ struct GuestDescriptorSet { } }; -struct PollDescriptor { - int descriptor; - short events; - short revents; -}; - -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*); -int APS5_VABI dup_nid_postfix(int); -int APS5_VABI dup2_nid_postfix(int, int); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI open_nid_postfix(const char*, int, int); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); -int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int); -int* APS5_VABI __error_nid_postfix(); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "socket descriptor check failed at line %d, errno %d\n", line, *__error_nid_postfix()); @@ -159,7 +136,7 @@ static void CloseReleasesNumber() { Require(files.back() == socket); unsigned char bytes[4]{}; Require(read_nid_postfix(socket, bytes, sizeof(bytes)) == sizeof(bytes)); - PollDescriptor entry{socket, In, 0}; + GuestPollDescriptor entry{socket, In, 0}; Require(poll_nid_postfix(&entry, 1, 0) == 1 && entry.revents == In); for (const int file : files) Require(close_nid_postfix(file) == 0); const int kernelClosed = Socket(); diff --git a/core/libs/tests/GuestSocketEvents.cpp b/core/libs/tests/GuestSocketEvents.cpp index 1ef4eea859..59bd2bb4ad 100644 --- a/core/libs/tests/GuestSocketEvents.cpp +++ b/core/libs/tests/GuestSocketEvents.cpp @@ -1,37 +1,17 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include +#include #include #include #include +#include #include +#include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI pipe_nid_postfix(int*); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); -int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo); -int APS5_VABI sceKernelAddReadEvent(KernelEqueue eq, int fd, std::size_t size, void* udata); -int APS5_VABI sceKernelDeleteReadEvent(KernelEqueue eq, int fd); -int APS5_VABI sceKernelAddWriteEvent(KernelEqueue eq, int fd, std::size_t size, void* udata); -int APS5_VABI sceKernelDeleteWriteEvent(KernelEqueue eq, int fd); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_ENOENT = static_cast(0x80020002); -static constexpr int SCE_KERNEL_ERROR_EBADF = static_cast(0x80020009); -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003c); static constexpr int EVFILT_READ = -1; static constexpr int EVFILT_WRITE = -2; static constexpr std::uint16_t EV_EOF = 0x8000; @@ -96,8 +76,83 @@ static void PipeReadEvents() { Require(sceKernelDeleteEqueue(eq) == SCE_OK); } +static void RegularFileEvents() { + const std::string path = "anyps5-equeue-file-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + const int file = open_nid_postfix(path.c_str(), 0x0002 | 0x0200, 0644); + Require(file >= 0); + Require(write_nid_postfix(file, "0123456789", 10) == 10); + Require(lseek_nid_postfix(file, 2, 0) == 2); + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "file") == SCE_OK); + const KernelUseconds shortTimeout = 20000; + const KernelUseconds timeout = 1000000; + int marker = 0; + int count = 0; + std::array events{}; + Require(sceKernelAddReadEvent(eq, file, 1, &marker) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &timeout) == SCE_OK); + Require(count == 1 && events[0].ident == static_cast(file) && events[0].filter == EVFILT_READ); + Require(events[0].data == 8 && events[0].udata == &marker); + Require(lseek_nid_postfix(file, 0, 2) == 10); + Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &shortTimeout) == SCE_KERNEL_ERROR_ETIMEDOUT); + const int appender = open_nid_postfix(path.c_str(), 0x0002, 0); + Require(appender >= 0); + std::thread writer([appender] { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(lseek_nid_postfix(appender, 0, 2) == 10); + Require(write_nid_postfix(appender, "abc", 3) == 3); + }); + Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, nullptr) == SCE_OK); + writer.join(); + Require(count == 1 && events[0].data == 3); + Require(sceKernelDeleteReadEvent(eq, file) == SCE_OK); + Require(sceKernelAddWriteEvent(eq, file, 0, nullptr) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &timeout) == SCE_OK); + Require(count == 1 && events[0].filter == EVFILT_WRITE && events[0].data == 0); + Require(sceKernelDeleteWriteEvent(eq, file) == SCE_OK); + Require(Rejects(sceKernelAddReadEvent, eq, file, 64)); + Require(Rejects(sceKernelAddWriteEvent, eq, file, 64)); + Require(sceKernelAddReadEvent(eq, file, 0, nullptr) == SCE_OK); + Require(close_nid_postfix(file) == 0); + Require(sceKernelDeleteReadEvent(eq, file) == SCE_KERNEL_ERROR_ENOENT); + Require(sceKernelAddReadEvent(eq, file, 0, nullptr) == SCE_KERNEL_ERROR_EBADF); + Require(close_nid_postfix(appender) == 0); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); + Require(std::filesystem::remove(path)); +} + +static void CloseWhileWaitingOnFile() { + const std::string path = "anyps5-equeue-close-" + 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, "close") == 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 file = open_nid_postfix(path.c_str(), 0x0002, 0); + Require(file >= 0); + Require(sceKernelAddReadEvent(eq, file, 0, nullptr) == SCE_OK); + Require(close_nid_postfix(file) == 0); + } + stop.store(true); + waiter.join(); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); + Require(std::filesystem::remove(path)); +} + int main() { PipeReadEvents(); + RegularFileEvents(); + CloseWhileWaitingOnFile(); 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/GuestSocketHostWrite.cpp b/core/libs/tests/GuestSocketHostWrite.cpp index df022d1f8a..a49e49d08d 100644 --- a/core/libs/tests/GuestSocketHostWrite.cpp +++ b/core/libs/tests/GuestSocketHostWrite.cpp @@ -1,3 +1,7 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" #define WIN32_LEAN_AND_MEAN #include #include "prx/libc/include/GuestArena.hpp" @@ -12,57 +16,8 @@ #include #include -struct NetIovec { - void* base; - std::uint64_t length; -}; - -struct NetMsghdr { - void* name; - std::uint32_t nameLength; - NetIovec* iov; - int iovLength; - void* control; - std::uint32_t controlLength; - int flags; -}; static_assert(sizeof(NetMsghdr) == 48 && offsetof(NetMsghdr, iov) == 16 && offsetof(NetMsghdr, flags) == 44); -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -std::int64_t APS5_VABI sendto_nid_postfix(int, const void*, std::uint64_t, int, const void*, std::uint32_t); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -std::int64_t APS5_VABI recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceNetSocket(const char*, int, int, int); -int APS5_VABI sceNetBind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI sceNetGetsockname(int, void*, std::uint32_t*); -int APS5_VABI sceNetListen(int, int); -int APS5_VABI sceNetConnect(int, const void*, std::uint32_t); -int APS5_VABI sceNetAccept(int, void*, std::uint32_t*); -int APS5_VABI sceNetSetsockopt(int, int, int, const void*, std::uint32_t); -std::int64_t APS5_VABI sceNetSendto(int, const void*, std::size_t, int, const void*, std::uint32_t); -std::int64_t APS5_VABI sceNetRecv(int, void*, std::size_t, int); -std::int64_t APS5_VABI sceNetRecvfrom(int, void*, std::size_t, int, void*, std::uint32_t*); -std::int64_t APS5_VABI sceNetRecvmsg(int, NetMsghdr*, int); -int APS5_VABI sceNetSocketClose(int); -int* APS5_VABI sceNetErrnoLoc(); -int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); -int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); -int APS5_VABI sceKernelMprotect(void*, std::size_t, int); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); -} - static void Require(bool value, const char* message) { if (!value) { std::fprintf(stderr, "%s\n", message); @@ -94,6 +49,7 @@ int main(int argc, char** argv) { const auto create = [&] { return net ? sceNetSocket(nullptr, 2, stream ? 1 : 2, stream ? 6 : 17) : socket_nid_postfix(2, stream ? 1 : 2, stream ? 6 : 17); + }; int receiver = create(); const int sender = create(); @@ -156,6 +112,7 @@ int main(int argc, char** argv) { do { result = net ? sceNetRecv(receiver, buffer.data(), buffer.size(), 2) : recv_nid_postfix(receiver, buffer.data(), buffer.size(), 2); + if (result == sizeof(payload) || (result < 0 && *error != 35)) break; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } while (std::chrono::steady_clock::now() < deadline); @@ -312,8 +269,8 @@ int main(int argc, char** argv) { Require(sceKernelMprotect(static_cast(outputMapping) + page, page, 3) == 0, "restore bytes beyond the sender address"); } - Require(sceKernelMunmap(outputTwin, page * 2) == 0, "unmap the second sender output mapping"); - Require(sceKernelMunmap(outputMapping, page * 2) == 0, "unmap sender outputs"); + Require(sceKernelMunmap(reinterpret_cast(outputTwin), page * 2) == 0, "unmap the second sender output mapping"); + Require(sceKernelMunmap(reinterpret_cast(outputMapping), page * 2) == 0, "unmap sender outputs"); Require(sceKernelReleaseDirectMemory(outputPhysical, page * 2) == 0, "release sender output memory"); } reset(); @@ -355,7 +312,7 @@ int main(int argc, char** argv) { waitForPacket(); Require(sceNetRecvmsg(receiver, message, 0) == static_cast(0x8041010eu) && *error == 14, "read-only message outputs fail with EFAULT"); - Require(message->flags == -1 && message->controlLength == 123, "rejected message outputs unchanged"); + Require(message->flags == -1 && message->control_length == 123, "rejected message outputs unchanged"); Require(receive(recovery.data(), recovery.size(), 0, nullptr, nullptr) == sizeof(payload), "a rejected message preserves queued data"); Require(sceKernelMprotect(twin, page, 3) == 0, "restore receive message write permission"); @@ -411,7 +368,7 @@ int main(int argc, char** argv) { Require(Protection(buffer) == PAGE_READONLY, "completed receive can be rearmed"); Require((net ? sceNetSocketClose(sender) : close_nid_postfix(sender)) == 0, "close sender"); Require((net ? sceNetSocketClose(receiver) : close_nid_postfix(receiver)) == 0, "close receiver"); - Require(sceKernelMunmap(twin, page * 2) == 0, "unmap the second direct memory mapping"); - Require(sceKernelMunmap(mapping, page * 2) == 0, "unmap direct memory"); + Require(sceKernelMunmap(reinterpret_cast(twin), page * 2) == 0, "unmap the second direct memory mapping"); + Require(sceKernelMunmap(reinterpret_cast(mapping), page * 2) == 0, "unmap direct memory"); Require(sceKernelReleaseDirectMemory(physical, page * 2) == 0, "release direct memory"); } diff --git a/core/libs/tests/GuestSocketOnNonSocket.cpp b/core/libs/tests/GuestSocketOnNonSocket.cpp index 953bd473d0..49c9d54ea0 100644 --- a/core/libs/tests/GuestSocketOnNonSocket.cpp +++ b/core/libs/tests/GuestSocketOnNonSocket.cpp @@ -1,25 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" { -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI shutdown_nid_postfix(int, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); -int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int ErrorBadDescriptor = 9; diff --git a/core/libs/tests/GuestSocketOptions.cpp b/core/libs/tests/GuestSocketOptions.cpp index 7ab20ba2f4..7c73a6f364 100644 --- a/core/libs/tests/GuestSocketOptions.cpp +++ b/core/libs/tests/GuestSocketOptions.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,21 +7,6 @@ #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); -int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI poll_nid_postfix(void*, std::uint32_t, int); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int Stream = 1; @@ -35,6 +22,9 @@ constexpr int SoReusePort = 0x200; constexpr int SoTimestamp = 0x400; constexpr int SoNoSigpipe = 0x800; constexpr int SoSendBuffer = 0x1001; +constexpr int SoReceiveBuffer = 0x1002; +constexpr int SoSendLowWater = 0x1003; +constexpr int SoReceiveLowWater = 0x1004; constexpr int SoError = 0x1007; constexpr int SoType = 0x1008; constexpr int ErrorInvalid = 22; @@ -48,12 +38,6 @@ struct Linger { std::int32_t seconds; }; -struct PollDescriptor { - std::int32_t descriptor; - std::int16_t events; - std::int16_t revents; -}; - void Require(bool condition, const char* what) { if (condition) return; std::fprintf(stderr, "%s (errno %d)\n", what, *__error_nid_postfix()); @@ -95,7 +79,7 @@ std::array Listen(int* listener) { } void WaitUntilSettled(int descriptor) { - PollDescriptor entry{descriptor, 0x1 | 0x4, 0}; + GuestPollDescriptor entry{descriptor, 0x1 | 0x4, 0}; Require(poll_nid_postfix(&entry, 1, 6000) == 1, "the nonblocking connect did not settle within 6 s"); } @@ -154,16 +138,41 @@ void BooleanOptionsReportTheirBit() { Require(GetInt(datagram, SoBroadcast) == SoBroadcast, "SO_BROADCAST reads back as its bit"); SetInt(stream, SoNoSigpipe, 1); Require(GetInt(stream, SoNoSigpipe) == SoNoSigpipe, "SO_NOSIGPIPE reads back as its bit"); - for (const int option : {SoReusePort, SoDebug, SoUseLoopback, SoTimestamp}) { - const int one = 1; - Require(SetFails(stream, SolSocket, option, &one, sizeof(one), ErrorNoProtocolOption), "an option no title needs must fail with ENOPROTOOPT (42)"); + for (const int option : {SoReusePort, SoDebug, SoUseLoopback, SoTimestamp, 0x2000, 0x10000}) { + Require(GetInt(stream, option) == 0, "a kept flag starts off"); + SetInt(stream, option, 5); + Require(GetInt(stream, option) == option, "a kept flag reads back as its bit"); + SetInt(stream, option, 0); + Require(GetInt(stream, option) == 0, "a kept flag turns off"); + } + for (const int option : {0x4000, 0x8000}) { + SetInt(stream, option, 1); int value = 0; std::uint32_t size = sizeof(value); - Require(getsockopt_nid_postfix(stream, SolSocket, option, &value, &size) == -1 && *__error_nid_postfix() == ErrorNoProtocolOption, "getsockopt of an option no title needs must fail with ENOPROTOOPT (42)"); + Require(getsockopt_nid_postfix(stream, SolSocket, option, &value, &size) == -1 && *__error_nid_postfix() == ErrorNoProtocolOption, "SO_NO_OFFLOAD and SO_NO_DDP are accepted but cannot be read"); } + const unsigned char narrowFlag = 1; + Require(SetFails(stream, SolSocket, SoDebug, &narrowFlag, 1, ErrorInvalid), "a flag shorter than an int must fail with EINVAL (22)"); Require(close_nid_postfix(stream) == 0 && close_nid_postfix(datagram) == 0, "close failed"); } +void ReusePortSharesAPort() { + const int first = socket_nid_postfix(2, Datagram, 0); + const int second = socket_nid_postfix(2, Datagram, 0); + const int third = socket_nid_postfix(2, Datagram, 0); + Require(first >= 0 && second >= 0 && third >= 0, "socket failed"); + SetInt(first, SoReusePort, 1); + SetInt(second, SoReusePort, 1); + Require(GetInt(first, SoReusePort) == SoReusePort && GetInt(first, 0x4) == 0, "SO_REUSEPORT does not read back as SO_REUSEADDR"); + auto address = Loopback(0); + Require(bind_nid_postfix(first, address.data(), address.size()) == 0, "bind failed"); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(first, address.data(), &size) == 0, "getsockname failed"); + Require(bind_nid_postfix(second, address.data(), address.size()) == 0, "a second SO_REUSEPORT socket must bind the same port"); + Require(bind_nid_postfix(third, address.data(), address.size()) == -1 && *__error_nid_postfix() == 48, "a socket without SO_REUSEPORT must fail with EADDRINUSE (48)"); + Require(close_nid_postfix(first) == 0 && close_nid_postfix(second) == 0 && close_nid_postfix(third) == 0, "close failed"); +} + void LingerFollowsFreeBsd() { const int stream = socket_nid_postfix(2, Stream, 0); Require(stream >= 0, "socket failed"); @@ -204,6 +213,176 @@ void LengthsFollowFreeBsd() { Require(close_nid_postfix(stream) == 0, "close failed"); } +void BuffersFollowThePs5() { + const int stream = socket_nid_postfix(2, Stream, 0); + const int datagram = socket_nid_postfix(28, Datagram, 0); + Require(stream >= 0 && datagram >= 0, "socket failed"); + Require(GetInt(stream, SoSendBuffer) == 32768 && GetInt(stream, SoReceiveBuffer) == 65536, "a stream socket starts with 32768 and 65536 bytes"); + Require(GetInt(stream, SoSendLowWater) == 2048 && GetInt(stream, SoReceiveLowWater) == 1, "the low-water marks start at 2048 and 1"); + Require(GetInt(datagram, SoSendBuffer) == 9216 && GetInt(datagram, SoReceiveBuffer) == 42080, "a datagram socket starts with 9216 and 42080 bytes"); + SetInt(stream, SoSendBuffer, 1000); + Require(GetInt(stream, SoSendBuffer) == 1000 && GetInt(stream, SoSendLowWater) == 1000, "a smaller buffer is kept and lowers its low-water mark"); + SetInt(stream, SoReceiveBuffer, 524289); + Require(GetInt(stream, SoReceiveBuffer) == 524288, "a buffer above 512 KiB is limited to 524288 bytes"); + SetInt(stream, SoSendBuffer, 5592405); + Require(GetInt(stream, SoSendBuffer) == 524288, "the largest reservation is accepted and limited"); + const int tooLarge = 5592406; + Require(SetFails(stream, SolSocket, SoSendBuffer, &tooLarge, sizeof(tooLarge), 55), "a reservation above sb_max must fail with ENOBUFS (55)"); + Require(GetInt(stream, SoSendBuffer) == 524288, "a failed reservation keeps the buffer"); + SetInt(stream, SoReceiveLowWater, 600000); + Require(GetInt(stream, SoReceiveLowWater) == 524288, "a low-water mark above the buffer is limited to it"); + SetInt(stream, SoReceiveBuffer, 50); + Require(GetInt(stream, SoReceiveLowWater) == 50, "a smaller buffer lowers the low-water mark"); + const int zero = 0; + Require(SetFails(stream, SolSocket, SoReceiveLowWater, &zero, sizeof(zero), ErrorInvalid), "a low-water mark below 1 must fail with EINVAL (22)"); + int pair[2] = {-1, -1}; + Require(socketpair_nid_postfix(1, Stream, 0, pair) == 0, "socketpair failed"); + Require(GetInt(pair[0], SoSendBuffer) == 8192 && GetInt(pair[0], SoReceiveBuffer) == 8192, "an AF_UNIX stream socket starts with 8192 bytes each way"); + Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0, "close failed"); + Require(socketpair_nid_postfix(1, Datagram, 0, pair) == 0, "socketpair failed"); + Require(GetInt(pair[0], SoSendBuffer) == 2048 && GetInt(pair[0], SoReceiveBuffer) == 4096, "an AF_UNIX datagram socket starts with 2048 and 4096 bytes"); + Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0, "close failed"); + Require(close_nid_postfix(stream) == 0 && close_nid_postfix(datagram) == 0, "close failed"); + const int listener = socket_nid_postfix(2, Stream, 0); + Require(listener >= 0, "socket failed"); + auto address = Loopback(0); + Require(bind_nid_postfix(listener, address.data(), address.size()) == 0 && listen_nid_postfix(listener, 1) == 0, "listen failed"); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(listener, address.data(), &size) == 0, "getsockname failed"); + SetInt(listener, SoReceiveBuffer, 3000); + SetInt(listener, SoSendLowWater, 700); + const int client = socket_nid_postfix(2, Stream, 0); + Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0, "connect failed"); + const int accepted = accept_nid_postfix(listener, nullptr, nullptr); + Require(accepted >= 0, "accept failed"); + Require(GetInt(accepted, SoReceiveBuffer) == 3000 && GetInt(accepted, SoSendLowWater) == 700 && GetInt(accepted, SoSendBuffer) == 32768, "an accepted socket takes the listener's buffers"); + Require(close_nid_postfix(accepted) == 0 && close_nid_postfix(client) == 0 && close_nid_postfix(listener) == 0, "close failed"); +} + +void NullValues() { + const int stream = socket_nid_postfix(2, Stream, 0); + Require(stream >= 0, "socket failed"); + Require(SetFails(stream, SolSocket, SoKeepAlive, nullptr, sizeof(int), 14), "a null value with a length must fail with EFAULT (14)"); + Require(SetFails(-1, SolSocket, SoKeepAlive, nullptr, sizeof(int), 14), "the null value is checked before the descriptor"); + Require(SetFails(stream, SolSocket, 0x7fff, nullptr, sizeof(int), 14), "the null value is checked before the option"); + Require(SetFails(stream, SolSocket, SoKeepAlive, nullptr, 0, ErrorInvalid), "a null value without a length must fail with EINVAL (22)"); + std::uint32_t size = sizeof(int); + Require(getsockopt_nid_postfix(stream, SolSocket, SoSendBuffer, nullptr, &size) == 0 && size == 0, "getsockopt without a value succeeds with length 0"); + Require(close_nid_postfix(stream) == 0, "close failed"); +} + +int GetIp(int descriptor, int option) { + int value = -12345; + std::uint32_t size = sizeof(value); + Require(getsockopt_nid_postfix(descriptor, 0, option, &value, &size) == 0 && size == sizeof(value), "getsockopt of an IPPROTO_IP option failed"); + return value; +} + +bool SetIp(int descriptor, int option, int value) { + return setsockopt_nid_postfix(descriptor, 0, option, &value, sizeof(value)) == 0; +} + +void IpLevelFollowsThePs5() { + constexpr int IpTypeOfService = 3; + constexpr int IpTimeToLive = 4; + constexpr int IpMulticastTimeToLive = 10; + constexpr int IpMulticastLoop = 11; + const int datagram = socket_nid_postfix(2, Datagram, 0); + const int datagram6 = socket_nid_postfix(28, Datagram, 0); + Require(datagram >= 0 && datagram6 >= 0, "socket failed"); + Require(GetIp(datagram, IpTimeToLive) == 64 && GetIp(datagram, IpTypeOfService) == 0, "IP_TTL starts at 64 and IP_TOS at 0"); + Require(GetIp(datagram, IpMulticastTimeToLive) == 1 && GetIp(datagram, IpMulticastLoop) == 1, "IP_MULTICAST_TTL and IP_MULTICAST_LOOP start at 1"); + Require(SetIp(datagram, IpTimeToLive, 32) && GetIp(datagram, IpTimeToLive) == 32, "IP_TTL reads back"); + Require(SetIp(datagram, IpTimeToLive, 300) && GetIp(datagram, IpTimeToLive) == 44, "IP_TTL keeps the low byte"); + Require(SetIp(datagram, IpTimeToLive, -1) && GetIp(datagram, IpTimeToLive) == 255, "IP_TTL of -1 reads back as 255"); + Require(SetIp(datagram, IpTimeToLive, 0) && GetIp(datagram, IpTimeToLive) == 0, "IP_TTL of 0 is kept"); + const unsigned char byte = 9; + Require(SetFails(datagram, 0, IpTimeToLive, &byte, 1, ErrorInvalid), "a one-byte IP_TTL must fail with EINVAL (22)"); + Require(SetIp(datagram, IpTypeOfService, 0x1ff) && GetIp(datagram, IpTypeOfService) == 0xff, "IP_TOS keeps the low byte"); + const unsigned char seven = 7; + Require(setsockopt_nid_postfix(datagram, 0, IpMulticastTimeToLive, &seven, 1) == 0 && GetIp(datagram, IpMulticastTimeToLive) == 7, "a one-byte IP_MULTICAST_TTL is accepted"); + const int tooLarge = 300; + Require(SetFails(datagram, 0, IpMulticastTimeToLive, &tooLarge, sizeof(tooLarge), ErrorInvalid) && GetIp(datagram, IpMulticastTimeToLive) == 7, "IP_MULTICAST_TTL above 255 must fail with EINVAL (22)"); + unsigned char narrow = 0xaa; + std::uint32_t size = 1; + Require(getsockopt_nid_postfix(datagram, 0, IpMulticastTimeToLive, &narrow, &size) == 0 && size == 1 && narrow == 7, "a one-byte read gets the IP_MULTICAST_TTL byte"); + Require(SetIp(datagram, IpMulticastLoop, 5) && GetIp(datagram, IpMulticastLoop) == 1, "IP_MULTICAST_LOOP reads back as 1"); + Require(SetIp(datagram, IpMulticastLoop, 0) && GetIp(datagram, IpMulticastLoop) == 0, "IP_MULTICAST_LOOP can be turned off"); + Require(SetIp(datagram, IpMulticastLoop, 1) && SetIp(datagram, IpMulticastLoop, 0x100) && GetIp(datagram, IpMulticastLoop) == 0, "an int IP_MULTICAST_LOOP keeps only its low byte"); + int value = 0; + size = sizeof(value); + Require(getsockopt_nid_postfix(datagram6, 0, IpTimeToLive, &value, &size) == -1 && *__error_nid_postfix() == ErrorInvalid, "IPPROTO_IP on an AF_INET6 socket must fail with EINVAL (22)"); + size = sizeof(value); + Require(getsockopt_nid_postfix(datagram, 0, 0x7777, &value, &size) == -1 && *__error_nid_postfix() == ErrorNoProtocolOption, "an unknown IPPROTO_IP option must fail with ENOPROTOOPT (42)"); + size = sizeof(value); + Require(getsockopt_nid_postfix(datagram, 99, 1, &value, &size) == -1 && *__error_nid_postfix() == ErrorInvalid, "an unknown level must fail with EINVAL (22)"); + Require(SetFails(datagram, 99, 1, &value, sizeof(value), ErrorInvalid), "setsockopt at an unknown level must fail with EINVAL (22)"); + for (const int option : {7, 20, 65}) { + Require(GetIp(datagram, option) == 0, "a receive option starts off"); + Require(SetIp(datagram, option, 5) && GetIp(datagram, option) == 1, "a receive option reads back as 1"); + Require(SetIp(datagram, option, 0) && GetIp(datagram, option) == 0, "a receive option turns off"); + } + Require(close_nid_postfix(datagram) == 0 && close_nid_postfix(datagram6) == 0, "close failed"); +} + +void MulticastMembership() { + constexpr int IpMulticastInterface = 9; + constexpr int IpAddMembership = 12; + constexpr int IpDropMembership = 13; + constexpr int ErrorNotSupported = 45; + constexpr int ErrorAddressNotAvailable = 49; + const int datagram = socket_nid_postfix(2, Datagram, 0); + const int stream = socket_nid_postfix(2, Stream, 0); + Require(datagram >= 0 && stream >= 0, "socket failed"); + const std::array group{224, 0, 0, 251, 0, 0, 0, 0}; + const std::array unicast{10, 0, 0, 1, 0, 0, 0, 0}; + const std::array other{224, 0, 0, 253, 0, 0, 0, 0}; + Require(SetFails(stream, 0, IpAddMembership, group.data(), group.size(), ErrorNotSupported), "a stream socket must fail a membership with EOPNOTSUPP (45)"); + Require(SetFails(datagram, 0, IpAddMembership, group.data(), 7, ErrorInvalid), "a short membership must fail with EINVAL (22)"); + Require(SetFails(datagram, 0, IpAddMembership, unicast.data(), unicast.size(), ErrorInvalid), "a group that is not multicast must fail with EINVAL (22)"); + Require(SetFails(datagram, 0, IpDropMembership, other.data(), other.size(), ErrorAddressNotAvailable), "dropping a group never joined must fail with EADDRNOTAVAIL (49)"); + std::array address{0xaa, 0xaa, 0xaa, 0xaa}; + std::uint32_t size = address.size(); + Require(getsockopt_nid_postfix(datagram, 0, IpMulticastInterface, address.data(), &size) == 0 && size == 4 && address == std::array{}, "IP_MULTICAST_IF starts at INADDR_ANY"); + const std::array loopback{127, 0, 0, 1}; + const std::array anyIndex{}; + Require(setsockopt_nid_postfix(datagram, 0, IpMulticastInterface, anyIndex.data(), anyIndex.size()) == 0, "an ip_mreqn without an index selects the default interface"); + size = address.size(); + Require(getsockopt_nid_postfix(datagram, 0, IpMulticastInterface, address.data(), &size) == 0 && address == std::array{}, "the default interface reads back as INADDR_ANY"); + Require(SetFails(datagram, 0, IpMulticastInterface, loopback.data(), 2, ErrorInvalid), "a short IP_MULTICAST_IF must fail with EINVAL (22)"); + Require(close_nid_postfix(datagram) == 0 && close_nid_postfix(stream) == 0, "close failed"); +} + +void MulticastOnTheHost() { + constexpr int IpMulticastInterface = 9; + constexpr int IpAddMembership = 12; + constexpr int IpDropMembership = 13; + constexpr int ErrorAddressInUse = 48; + constexpr int ErrorAddressNotAvailable = 49; + const int datagram = socket_nid_postfix(2, Datagram, 0); + Require(datagram >= 0, "socket failed"); + const std::array group{224, 0, 0, 251, 0, 0, 0, 0}; + Require(setsockopt_nid_postfix(datagram, 0, IpAddMembership, group.data(), group.size()) == 0, "joining 224.0.0.251 through INADDR_ANY failed"); + Require(SetFails(datagram, 0, IpAddMembership, group.data(), group.size(), ErrorAddressInUse), "joining twice must fail with EADDRINUSE (48)"); + Require(setsockopt_nid_postfix(datagram, 0, IpDropMembership, group.data(), group.size()) == 0, "drop failed"); + Require(SetFails(datagram, 0, IpDropMembership, group.data(), group.size(), ErrorAddressNotAvailable), "dropping twice must fail with EADDRNOTAVAIL (49)"); + const std::array throughLoopback{224, 0, 0, 252, 127, 0, 0, 1}; + const std::array anyInterface{224, 0, 0, 252, 0, 0, 0, 0}; + Require(setsockopt_nid_postfix(datagram, 0, IpAddMembership, throughLoopback.data(), throughLoopback.size()) == 0, "joining 224.0.0.252 through 127.0.0.1 failed"); + Require(setsockopt_nid_postfix(datagram, 0, IpDropMembership, anyInterface.data(), anyInterface.size()) == 0, "a drop through INADDR_ANY leaves the group on any interface"); + Require(SetFails(datagram, 0, IpDropMembership, anyInterface.data(), anyInterface.size(), ErrorAddressNotAvailable), "dropping it again must fail with EADDRNOTAVAIL (49)"); + const std::array loopback{127, 0, 0, 1}; + Require(setsockopt_nid_postfix(datagram, 0, IpMulticastInterface, loopback.data(), loopback.size()) == 0, "selecting the loopback interface for IP_MULTICAST_IF failed"); + std::array address{0xaa, 0xaa, 0xaa, 0xaa}; + std::uint32_t size = address.size(); + Require(getsockopt_nid_postfix(datagram, 0, IpMulticastInterface, address.data(), &size) == 0 && size == 4 && address == loopback, "IP_MULTICAST_IF reads back"); + const std::array anyIndex{}; + Require(setsockopt_nid_postfix(datagram, 0, IpMulticastInterface, anyIndex.data(), anyIndex.size()) == 0, "an ip_mreqn without an index selects the default interface"); + size = address.size(); + Require(getsockopt_nid_postfix(datagram, 0, IpMulticastInterface, address.data(), &size) == 0 && address == std::array{}, "the default interface reads back as INADDR_ANY"); + Require(close_nid_postfix(datagram) == 0, "close failed"); +} + void UnknownRequestsFail() { const int stream = socket_nid_postfix(2, Stream, 0); Require(stream >= 0, "socket failed"); @@ -218,12 +397,21 @@ void UnknownRequestsFail() { } -int main() { +int main(int argc, char** argv) { + if (argc > 1 && std::strcmp(argv[1], "multicast") == 0) { + MulticastOnTheHost(); + return 0; + } ReportsTypeAndListening(); ReportsAndClearsTheConnectError(); BooleanOptionsReportTheirBit(); + ReusePortSharesAPort(); LingerFollowsFreeBsd(); LengthsFollowFreeBsd(); + BuffersFollowThePs5(); + NullValues(); + IpLevelFollowsThePs5(); + MulticastMembership(); UnknownRequestsFail(); return 0; } diff --git a/core/libs/tests/GuestSocketPair.cpp b/core/libs/tests/GuestSocketPair.cpp index ec71ffc24c..367ebff08d 100644 --- a/core/libs/tests/GuestSocketPair.cpp +++ b/core/libs/tests/GuestSocketPair.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -7,47 +9,6 @@ #include #include -struct PollDescriptor { - int descriptor; - short events; - short revents; -}; - -struct Iovec { - void* base; - std::uint64_t length; -}; - -struct Msghdr { - void* name; - std::uint32_t nameLength; - Iovec* iov; - int iovLength; - void* control; - std::uint32_t controlLength; - int flags; -}; - -extern "C" { -int APS5_VABI socketpair_nid_postfix(int, int, int, int*); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -std::int64_t APS5_VABI recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*); -std::int64_t APS5_VABI recvmsg_nid_postfix(int, Msghdr*, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI getpeername_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI shutdown_nid_postfix(int, int); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int); -int APS5_VABI close_nid_postfix(int); -std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*); -int APS5_VABI sceKernelFstat(int, FileStat*); -int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); -int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); -int APS5_VABI sceKernelMunmap(void*, std::size_t); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } template @@ -81,7 +42,7 @@ static bool Fails(int domain, int type, int protocol, int error) { } static bool Readable(int descriptor, short accepted = In) { - PollDescriptor waited{descriptor, In, 0}; + GuestPollDescriptor waited{descriptor, In, 0}; return poll_nid_postfix(&waited, 1, 2000) == 1 && (waited.revents & accepted) != 0; } @@ -104,7 +65,15 @@ static bool Unnamed(int descriptor, int (APS5_VABI *query)(int, void*, std::uint return query(descriptor, name, &length) == 0 && length == 4 && name[0] == 16 && name[1] == 1 && name[4] == 0xaa; } -static bool ReceiveFaults(int descriptor, Msghdr* message) { +static bool NotConnected(int descriptor) { + std::uint8_t name[16]; + std::memset(name, 0xaa, sizeof(name)); + std::uint32_t length = sizeof(name); + *__error_nid_postfix() = 0; + return getpeername_nid_postfix(descriptor, name, &length) == -1 && *__error_nid_postfix() == 57 && length == sizeof(name) && name[0] == 0xaa; +} + +static bool ReceiveFaults(int descriptor, GuestMsghdr* message) { *__error_nid_postfix() = 0; return recvmsg_nid_postfix(descriptor, message, 0) == -1 && *__error_nid_postfix() == 14; } @@ -122,26 +91,26 @@ static void CheckReceiveMessageFaults() { auto* readOnly = writable + page; auto* inaccessible = writable + 2 * page; char data[16]{}; - Iovec good{data, sizeof(data)}; - auto* readOnlyMessage = reinterpret_cast(readOnly); + KernelIovec good{data, sizeof(data)}; + auto* readOnlyMessage = reinterpret_cast(readOnly); *readOnlyMessage = {nullptr, 0, &good, 1, nullptr, 0, -1}; - auto* readOnlyVector = reinterpret_cast(readOnly + 64); + auto* readOnlyVector = reinterpret_cast(readOnly + 64); *readOnlyVector = {data, sizeof(data)}; Require(sceKernelMprotect(readOnly, page, 1) == 0 && sceKernelMprotect(inaccessible, page, 0) == 0); int pair[2] = {-1, -1}; Require(socketpair_nid_postfix(Unix, Datagram, 0, pair) == 0); Require(Queue(pair[0], pair[1], "x")); - Require(ReceiveFaults(pair[1], reinterpret_cast(0x1000))); - Require(ReceiveFaults(pair[1], reinterpret_cast(inaccessible))); - Msghdr message{nullptr, 0, reinterpret_cast(0x1000), 1, nullptr, 0, -1}; + Require(ReceiveFaults(pair[1], reinterpret_cast(0x1000))); + Require(ReceiveFaults(pair[1], reinterpret_cast(inaccessible))); + GuestMsghdr message{nullptr, 0, reinterpret_cast(0x1000), 1, nullptr, 0, -1}; Require(ReceiveFaults(pair[1], &message)); - message.iov = reinterpret_cast(inaccessible); + message.iov = reinterpret_cast(inaccessible); Require(ReceiveFaults(pair[1], &message)); char peeked[4]{}; Require(recv_nid_postfix(pair[1], peeked, sizeof(peeked), Peek) == 1 && peeked[0] == 'x'); - Iovec vector{reinterpret_cast(0x1000), sizeof(data)}; + KernelIovec vector{reinterpret_cast(0x1000), sizeof(data)}; message = {nullptr, 0, &vector, 1, nullptr, 0, -1}; Require(ReceiveFaults(pair[1], &message)); vector.base = inaccessible; @@ -164,7 +133,7 @@ static void CheckReceiveMessageFaults() { Require(Queue(pair[0], pair[1], "abcd") && recvmsg_nid_postfix(pair[1], &message, 0) == 4); Require(std::memcmp(writable, "abcd", 4) == 0 && message.flags == 0 && message.controlLength == 0); Require(message.nameLength == 16 && name[0] == 16 && name[1] == Unix && name[16] == 0xa5); - Iovec vectors[2]{{writable, 2}, {inaccessible, 16}}; + KernelIovec vectors[2]{{writable, 2}, {inaccessible, 16}}; message = {nullptr, 0, vectors, 2, nullptr, 0, -1}; Require(Queue(pair[0], pair[1], "ef") && recvmsg_nid_postfix(pair[1], &message, 0) == 2 && std::memcmp(writable, "ef", 2) == 0); message = {readOnly + 128, 0, readOnlyVector, 1, nullptr, 0, -1}; @@ -172,7 +141,7 @@ static void CheckReceiveMessageFaults() { Require(std::memcmp(data, "gh", 2) == 0 && message.nameLength == 0); Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0); - Require(sceKernelMprotect(memory, page * 3, 3) == 0 && sceKernelMunmap(memory, page * 3) == 0); + Require(sceKernelMprotect(memory, page * 3, 3) == 0 && sceKernelMunmap(reinterpret_cast(memory), page * 3) == 0); } int main(int argc, char** argv) { @@ -188,7 +157,7 @@ int main(int argc, char** argv) { Require(Exchange(pair[1], pair[0], "other way")); Require(Unnamed(pair[0], getsockname_nid_postfix) && Unnamed(pair[1], getpeername_nid_postfix)); - PollDescriptor polled[2] = {{pair[1], In, 0}, {pair[0], Out, 0}}; + GuestPollDescriptor polled[2] = {{pair[1], In, 0}, {pair[0], Out, 0}}; Require(poll_nid_postfix(polled, 2, 0) == 1 && polled[0].revents == 0 && (polled[1].revents & Out) != 0); const char ping[] = "ping"; Require(send_nid_postfix(pair[0], ping, sizeof(ping), 0) == sizeof(ping)); @@ -211,11 +180,13 @@ int main(int argc, char** argv) { Require(shutdown_nid_postfix(pair[0], 1) == 0); Require(Readable(pair[1], In | Hup) && recv_nid_postfix(pair[1], received, sizeof(received), 0) == 0); Require(Exchange(pair[1], pair[0], "still open")); + Require(Unnamed(pair[0], getpeername_nid_postfix) && Unnamed(pair[1], getpeername_nid_postfix)); Require(close_nid_postfix(pair[0]) == 0); + Require(NotConnected(pair[1]) && Unnamed(pair[1], getsockname_nid_postfix)); Require(Readable(pair[1], In | Hup) && recv_nid_postfix(pair[1], received, sizeof(received), 0) == 0); -#ifndef _WIN32 - Require(send_nid_postfix(pair[1], ping, sizeof(ping), NoSignal) == -1 && *__error_nid_postfix() == 32); -#endif + std::int64_t sent = 0; + for (int i = 0; i < 100 && sent >= 0; ++i) sent = send_nid_postfix(pair[1], ping, sizeof(ping), NoSignal); + Require(sent == -1 && *__error_nid_postfix() == 32); Require(close_nid_postfix(pair[1]) == 0); Require(close_nid_postfix(pair[1]) == -1 && *__error_nid_postfix() == 9); @@ -260,7 +231,11 @@ int main(int argc, char** argv) { Require(senderLength == 16 && sender[0] == 16 && sender[1] == 1); Require(recv_nid_postfix(pair[1], datagram, sizeof(datagram), 0) == 2 && datagram[0] == 'b' && datagram[1] == 'b'); Require(Exchange(pair[1], pair[0], "back")); - Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0); + const int copy = dup_nid_postfix(pair[0]); + Require(copy >= 0 && close_nid_postfix(pair[0]) == 0); + Require(Unnamed(pair[1], getpeername_nid_postfix) && Exchange(pair[1], copy, "copy")); + Require(close_nid_postfix(copy) == 0 && NotConnected(pair[1])); + Require(close_nid_postfix(pair[1]) == 0); #ifdef __APPLE__ Require(ThrowsRuntimeError([&] { socketpair_nid_postfix(Unix, SequencedPacket, 0, pair); })); @@ -273,8 +248,8 @@ int main(int argc, char** argv) { Require(send_nid_postfix(pair[0], "record", 6, 0) == 6); Require(send_nid_postfix(pair[0], "next", 4, 0) == 4); char part[2]{}; - Iovec piece{part, sizeof(part)}; - Msghdr message{nullptr, 0, &piece, 1, nullptr, 0, -1}; + KernelIovec piece{part, sizeof(part)}; + GuestMsghdr message{nullptr, 0, &piece, 1, nullptr, 0, -1}; const auto truncated = recvmsg_nid_postfix(pair[1], &message, 0); if (truncated != 2 || message.flags != 0x10) { std::fprintf(stderr, "recvmsg of a 6-byte SOCK_SEQPACKET record into 2 bytes returned %lld with msg_flags 0x%x, expected 2 with MSG_TRUNC (0x10)\n", diff --git a/core/libs/tests/GuestSocketPortZero.cpp b/core/libs/tests/GuestSocketPortZero.cpp index 070b9c8982..f450f75277 100644 --- a/core/libs/tests/GuestSocketPortZero.cpp +++ b/core/libs/tests/GuestSocketPortZero.cpp @@ -1,20 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI getpeername_nid_postfix(int, void*, std::uint32_t*); -std::int64_t APS5_VABI sendto_nid_postfix(int, const void*, std::uint64_t, int, const void*, std::uint32_t); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} static void Require(bool value, const char* message) { if (!value) { @@ -45,7 +36,28 @@ static void CheckDatagram(int family) { Require(connect_nid_postfix(probe, address.data(), size) == -1 && *__error_nid_postfix() == 49, "connect to port 0 on an unconnected datagram socket must fail with EADDRNOTAVAIL (49)"); Require(getpeername_nid_postfix(probe, name.data(), &length) == -1 && *__error_nid_postfix() == 57, "still unconnected"); - Require(close_nid_postfix(probe) == 0, "close"); + const int peer = socket_nid_postfix(family, 2, 0); + Require(peer >= 0, "peer"); + const int bound = bind_nid_postfix(peer, address.data(), size); + if (bound < 0 && family == 28 && *__error_nid_postfix() == 49) { + Require(close_nid_postfix(peer) == 0 && close_nid_postfix(probe) == 0, "close"); + return; + } + Require(bound == 0, "bind peer"); + Address target{}; + length = target.size(); + Require(getsockname_nid_postfix(peer, target.data(), &length) == 0, "peer name"); + Require(connect_nid_postfix(probe, target.data(), size) == 0, "connect to the peer"); + length = name.size(); + Require(getpeername_nid_postfix(probe, name.data(), &length) == 0, "connected"); + Require(connect_nid_postfix(probe, address.data(), size) == -1 && *__error_nid_postfix() == 49, + "connect to port 0 on a connected datagram socket must fail with EADDRNOTAVAIL (49)"); + length = name.size(); + Require(getpeername_nid_postfix(probe, name.data(), &length) == -1 && *__error_nid_postfix() == 57, + "connect to port 0 must disconnect a connected datagram socket"); + Require(send_nid_postfix(probe, "x", 1, 0) == -1 && *__error_nid_postfix() == 39, + "send without an address after the disconnect must fail with EDESTADDRREQ (39)"); + Require(close_nid_postfix(peer) == 0 && close_nid_postfix(probe) == 0, "close"); } static void CheckStream(int family) { diff --git a/core/libs/tests/GuestSocketReadWrite.cpp b/core/libs/tests/GuestSocketReadWrite.cpp index 5a8d48781b..f320cf6569 100644 --- a/core/libs/tests/GuestSocketReadWrite.cpp +++ b/core/libs/tests/GuestSocketReadWrite.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -9,47 +12,6 @@ #include #endif -struct PollDescriptor { - int descriptor; - short events; - short revents; -}; - -struct Iovec { - void* base; - std::size_t length; -}; - -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI sceKernelClose(int); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -std::int64_t APS5_VABI _read_nid_postfix(int, void*, std::size_t); -std::int64_t APS5_VABI _write_nid_postfix(int, const void*, std::size_t); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t); -std::int64_t APS5_VABI sceKernelReadv(int, const Iovec*, int); -std::int64_t APS5_VABI sceKernelWritev(int, const Iovec*, int); -std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI preadv_nid_postfix(int, const Iovec*, int, std::int64_t); -std::int64_t APS5_VABI pwritev_nid_postfix(int, const Iovec*, int, std::int64_t); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelPreadv(int, const Iovec*, int, std::int64_t); -std::int64_t APS5_VABI sceKernelPwritev(int, const Iovec*, int, std::int64_t); -int* APS5_VABI __error_nid_postfix(); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "socket read/write check failed at line %d, errno %d\n", line, *__error_nid_postfix()); @@ -65,7 +27,7 @@ constexpr int KernelBadDescriptor = static_cast(0x80020009u); constexpr int KernelIllegalSeek = static_cast(0x8002001du); static bool Readable(int descriptor, short accepted = In) { - PollDescriptor waited{descriptor, In, 0}; + GuestPollDescriptor waited{descriptor, In, 0}; return poll_nid_postfix(&waited, 1, 2000) == 1 && (waited.revents & accepted) != 0; } @@ -105,28 +67,28 @@ int main() { const char vectorFirst[] = "vector "; const char vectorSecond[] = "write"; - Iovec vectorWrite[] = {{const_cast(vectorFirst), sizeof(vectorFirst) - 1}, + KernelIovec vectorWrite[] = {{const_cast(vectorFirst), sizeof(vectorFirst) - 1}, {const_cast(vectorSecond), sizeof(vectorSecond)}}; Require(sceKernelWritev(client, vectorWrite, 2) == sizeof(vectorFirst) + sizeof(vectorSecond) - 1); char vectorReadFirst[sizeof(vectorFirst) - 1]{}; char vectorReadSecond[sizeof(vectorSecond)]{}; - Iovec vectorRead[] = {{vectorReadFirst, sizeof(vectorReadFirst)}, {vectorReadSecond, sizeof(vectorReadSecond)}}; + KernelIovec vectorRead[] = {{vectorReadFirst, sizeof(vectorReadFirst)}, {vectorReadSecond, sizeof(vectorReadSecond)}}; Require(Readable(server) && sceKernelReadv(server, vectorRead, 2) == sizeof(vectorFirst) + sizeof(vectorSecond) - 1); Require(std::memcmp(vectorReadFirst, "vector ", sizeof(vectorReadFirst)) == 0 && std::strcmp(vectorReadSecond, "write") == 0); const char sceVectorFirst[] = "sceKernel"; const char sceVectorSecond[] = "Writev"; - Iovec sceVectorWrite[] = {{const_cast(sceVectorFirst), sizeof(sceVectorFirst) - 1}, + KernelIovec sceVectorWrite[] = {{const_cast(sceVectorFirst), sizeof(sceVectorFirst) - 1}, {const_cast(sceVectorSecond), sizeof(sceVectorSecond)}}; Require(sceKernelWritev(client, sceVectorWrite, 2) == sizeof(sceVectorFirst) + sizeof(sceVectorSecond) - 1); char sceVectorReadFirst[sizeof(sceVectorFirst) - 1]{}; char sceVectorReadSecond[sizeof(sceVectorSecond)]{}; - Iovec sceVectorRead[] = {{sceVectorReadFirst, sizeof(sceVectorReadFirst)}, {sceVectorReadSecond, sizeof(sceVectorReadSecond)}}; + KernelIovec sceVectorRead[] = {{sceVectorReadFirst, sizeof(sceVectorReadFirst)}, {sceVectorReadSecond, sizeof(sceVectorReadSecond)}}; Require(Readable(server) && sceKernelReadv(server, sceVectorRead, 2) == sizeof(sceVectorFirst) + sizeof(sceVectorSecond) - 1); Require(std::memcmp(sceVectorReadFirst, "sceKernel", sizeof(sceVectorReadFirst)) == 0 && std::strcmp(sceVectorReadSecond, "Writev") == 0); const char positionedWrite[] = "positioned"; - Iovec positionedIov[] = {{const_cast(positionedWrite), sizeof(positionedWrite)}}; + KernelIovec positionedIov[] = {{const_cast(positionedWrite), sizeof(positionedWrite)}}; *__error_nid_postfix() = 0; Require(pread_nid_postfix(server, received, 1, 0) == -1 && *__error_nid_postfix() == 29); Require(pwrite_nid_disambig1_nid_postfix(client, positionedWrite, 1, 0) == -1 && *__error_nid_postfix() == 29); @@ -141,18 +103,16 @@ int main() { *__error_nid_postfix() = 0; Require(read_nid_postfix(server, received, sizeof(received)) == -1 && *__error_nid_postfix() == 35); Require(sceKernelRead(server, received, sizeof(received)) == KernelAgain); - Iovec emptyRead[] = {{received, sizeof(received)}}; + KernelIovec emptyRead[] = {{received, sizeof(received)}}; Require(sceKernelReadv(server, emptyRead, 1) == KernelAgain); Require(read_nid_postfix(server, received, 0) == 0 && sceKernelRead(server, received, 0) == 0); Require(close_nid_postfix(client) == 0); Require(Readable(server, In | Hup) && read_nid_postfix(server, received, sizeof(received)) == 0); Require(sceKernelRead(server, received, sizeof(received)) == 0); -#ifndef _WIN32 std::int64_t written = 0; for (int i = 0; i < 100 && written >= 0; ++i) written = write_nid_postfix(server, request, sizeof(request)); Require(written == -1 && *__error_nid_postfix() == 32); -#endif Require(close_nid_postfix(server) == 0); *__error_nid_postfix() = 0; Require(read_nid_postfix(server, received, sizeof(received)) == -1 && *__error_nid_postfix() == 9); diff --git a/core/libs/tests/GuestSocketStreamOptions.cpp b/core/libs/tests/GuestSocketStreamOptions.cpp index f85a693f17..647c93cdda 100644 --- a/core/libs/tests/GuestSocketStreamOptions.cpp +++ b/core/libs/tests/GuestSocketStreamOptions.cpp @@ -1,29 +1,13 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI socketpair_nid_postfix(int, int, int, int*); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI shutdown_nid_postfix(int, int); -int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); -int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI close_nid_postfix(int); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); -int* APS5_VABI __error_nid_postfix(); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "socket stream option check failed at line %d, errno %d\n", line, *__error_nid_postfix()); @@ -91,12 +75,14 @@ int main() { Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0); const int server = accept_nid_postfix(listener, nullptr, nullptr); Require(server >= 0); - Require(Set(listener, SocketLevel, NoSigpipe, 1) && Set(listener, TcpLevel, TcpNoDelay, 1)); + Require(Set(listener, SocketLevel, NoSigpipe, 1) && Set(listener, TcpLevel, TcpNoDelay, 1) && Set(listener, TcpLevel, 0x100, 60) && Set(listener, TcpLevel, 4, 1) && Set(listener, TcpLevel, 8, 1)); const int secondClient = socket_nid_postfix(2, 1, 0); Require(secondClient >= 0 && connect_nid_postfix(secondClient, address.data(), address.size()) == 0); const int inheriting = accept_nid_postfix(listener, nullptr, nullptr); Require(inheriting >= 0); Require(Get(inheriting, SocketLevel, NoSigpipe) == NoSigpipe && Get(inheriting, TcpLevel, TcpNoDelay) == 4); + Require(Get(inheriting, TcpLevel, 0x100) == 60 && Get(server, TcpLevel, 0x100) == 7200); + Require(Get(inheriting, TcpLevel, 4) == 0x1000 && Get(inheriting, TcpLevel, 8) == 0); Require(Get(server, SocketLevel, NoSigpipe) == 0 && Get(server, TcpLevel, TcpNoDelay) == 0); Require(close_nid_postfix(inheriting) == 0 && close_nid_postfix(secondClient) == 0); const int datagram = socket_nid_postfix(2, 2, 0); @@ -114,7 +100,38 @@ int main() { int value = 0; Require(Set(client, TcpLevel, TcpNoDelay, 0) && Get(client, TcpLevel, TcpNoDelay) == 0); Require(Fails(setsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &one, sizeof(one) - 1), Invalid)); - Require(Fails(setsockopt_nid_postfix(client, TcpLevel, TcpMaximumSegment, &one, sizeof(one)), NoProtocolOption)); + Require(Fails(setsockopt_nid_postfix(client, TcpLevel, TcpMaximumSegment, &one, sizeof(one)), Invalid)); + const int fresh = socket_nid_postfix(2, 1, 0); + Require(fresh >= 0 && Get(fresh, TcpLevel, TcpMaximumSegment) == 536); + for (const int segment : {1000, 175, 0, -1}) { + Require(Fails(setsockopt_nid_postfix(fresh, TcpLevel, TcpMaximumSegment, &segment, sizeof(segment)), Invalid)); + } + for (const int segment : {176, 300, 536}) + Require(Fails(setsockopt_nid_postfix(fresh, TcpLevel, TcpMaximumSegment, &segment, sizeof(segment)), Invalid)); + Require(Get(fresh, TcpLevel, TcpMaximumSegment) == 536); + const int fresh6 = socket_nid_postfix(28, 1, 0); + if (fresh6 >= 0) { + Require(Get(fresh6, TcpLevel, TcpMaximumSegment) == 1220); + Require(close_nid_postfix(fresh6) == 0); + } + Require(Set(client, TcpLevel, TcpMaximumSegment, 176) && Get(client, TcpLevel, TcpMaximumSegment) == 216); + Require(Set(client, TcpLevel, TcpMaximumSegment, 200) && Get(client, TcpLevel, TcpMaximumSegment) == 216); + const int larger = 400; + Require(Fails(setsockopt_nid_postfix(client, TcpLevel, TcpMaximumSegment, &larger, sizeof(larger)), Invalid)); + Require(Get(fresh, TcpLevel, 0x100) == 7200 && Get(fresh, TcpLevel, 0x200) == 75 && Get(fresh, TcpLevel, 0x400) == 8); + Require(Set(fresh, TcpLevel, 0x100, 60) && Get(fresh, TcpLevel, 0x100) == 60); + Require(Set(fresh, TcpLevel, 0x100, 0) && Get(fresh, TcpLevel, 0x100) == 7200); + Require(Set(fresh, TcpLevel, 0x200, 10) && Get(fresh, TcpLevel, 0x200) == 10); + Require(Set(fresh, TcpLevel, 0x400, 3) && Get(fresh, TcpLevel, 0x400) == 3); + Require(Set(fresh, TcpLevel, 0x400, -1) && Get(fresh, TcpLevel, 0x400) == -1); + Require(Fails(setsockopt_nid_postfix(fresh, TcpLevel, 0x7777, &one, sizeof(one)), NoProtocolOption)); + for (const auto& [option, bit] : {std::pair{4, 0x1000}, std::pair{8, 0x8}, std::pair{0x80, 0x10000}}) { + Require(Get(fresh, TcpLevel, option) == 0); + Require(Set(fresh, TcpLevel, option, 5) && Get(fresh, TcpLevel, option) == bit); + Require(Set(fresh, TcpLevel, option, -1) && Get(fresh, TcpLevel, option) == bit); + Require(Set(fresh, TcpLevel, option, 0) && Get(fresh, TcpLevel, option) == 0); + } + Require(close_nid_postfix(fresh) == 0); length = sizeof(value); Require(Fails(setsockopt_nid_postfix(datagram, TcpLevel, TcpNoDelay, &one, sizeof(one)), Invalid)); Require(Fails(getsockopt_nid_postfix(datagram, TcpLevel, TcpNoDelay, &value, &length), Invalid)); @@ -124,13 +141,13 @@ int main() { char received = 0; Require(Get(server, SocketLevel, NonBlockingOption) == 0); Require((fcntl_nid_postfix(server, GetStatusFlags) & NonBlockingFlag) == 0); - Require(Set(server, SocketLevel, NonBlockingOption, 5) && Get(server, SocketLevel, NonBlockingOption) == 1); + Require(Set(server, SocketLevel, NonBlockingOption, 5) && Get(server, SocketLevel, NonBlockingOption) == 0x100); Require((fcntl_nid_postfix(server, GetStatusFlags) & NonBlockingFlag) != 0); Require(Fails(recv_nid_postfix(server, &received, sizeof(received), 0), Again)); Require(Set(server, SocketLevel, NonBlockingOption, 0) && Get(server, SocketLevel, NonBlockingOption) == 0); Require((fcntl_nid_postfix(server, GetStatusFlags) & NonBlockingFlag) == 0); Require(Fails(setsockopt_nid_postfix(server, SocketLevel, NonBlockingOption, &one, sizeof(one) - 1), Invalid)); - Require(Set(datagram, SocketLevel, NonBlockingOption, 1) && Get(datagram, SocketLevel, NonBlockingOption) == 1); + Require(Set(datagram, SocketLevel, NonBlockingOption, 1) && Get(datagram, SocketLevel, NonBlockingOption) == 0x100); Require(Fails(recv_nid_postfix(datagram, &received, sizeof(received), 0), Again)); #ifndef _WIN32 diff --git a/core/libs/tests/GuestSocketTimeouts.cpp b/core/libs/tests/GuestSocketTimeouts.cpp index 2ff28a2173..104d70406e 100644 --- a/core/libs/tests/GuestSocketTimeouts.cpp +++ b/core/libs/tests/GuestSocketTimeouts.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -11,40 +13,6 @@ struct Timeval { std::int64_t microseconds; }; -struct Iovec { - void* base; - std::uint64_t length; -}; - -struct Msghdr { - void* name; - std::uint32_t nameLength; - Iovec* iov; - int iovLength; - void* control; - std::uint32_t controlLength; - int flags; -}; - -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); -int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*); -int APS5_VABI close_nid_postfix(int); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI sendto_nid_postfix(int, const void*, std::uint64_t, int, const void*, std::uint32_t); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -std::int64_t APS5_VABI recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*); -std::int64_t APS5_VABI recvmsg_nid_postfix(int, Msghdr*, int); -std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); -int* APS5_VABI __error_nid_postfix(); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "socket timeout check failed at line %d, errno %d\n", line, *__error_nid_postfix()); @@ -115,8 +83,8 @@ int main() { Require(Fails(recv_nid_postfix(datagram, &byte, sizeof(byte), 0), WouldBlock)); Require(Clock::now() - started >= std::chrono::milliseconds(59)); Require(Fails(recvfrom_nid_postfix(datagram, &byte, sizeof(byte), 0, nullptr, nullptr), WouldBlock)); - Iovec part{&byte, sizeof(byte)}; - Msghdr message{nullptr, 0, &part, 1, nullptr, 0, 0}; + KernelIovec part{&byte, sizeof(byte)}; + GuestMsghdr message{nullptr, 0, &part, 1, nullptr, 0, 0}; Require(Fails(recvmsg_nid_postfix(datagram, &message, 0), WouldBlock)); Require(Fails(read_nid_postfix(datagram, &byte, sizeof(byte)), WouldBlock)); Require(Set(datagram, ReceiveTimeout, 60, 0) == 0); diff --git a/core/libs/tests/GuestSoftwareVersion.cpp b/core/libs/tests/GuestSoftwareVersion.cpp index d572e1722b..1343316e32 100644 --- a/core/libs/tests/GuestSoftwareVersion.cpp +++ b/core/libs/tests/GuestSoftwareVersion.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include @@ -5,22 +7,10 @@ #include #include -struct SceKernelProsperoSystemSwVersion { - std::uint64_t size; - char versionString[0x1C]; - std::uint32_t version; -}; - static_assert(offsetof(SceKernelProsperoSystemSwVersion, versionString) == 0x08); static_assert(offsetof(SceKernelProsperoSystemSwVersion, version) == 0x24); static_assert(sizeof(SceKernelProsperoSystemSwVersion) == 0x28); -extern "C" { -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sceKernelGetProsperoSystemSwVersion_nid_postfix(SceKernelProsperoSystemSwVersion*); -int APS5_VABI sceKernelGetAllowedSdkVersionOnSystem_nid_postfix(std::uint32_t*); -} - static void Require(bool value) { if (!value) std::abort(); } static void CheckProsperoSystemSwVersion() { diff --git a/core/libs/tests/GuestSpSysCallWrapper.cpp b/core/libs/tests/GuestSpSysCallWrapper.cpp new file mode 100644 index 0000000000..1d22c59592 --- /dev/null +++ b/core/libs/tests/GuestSpSysCallWrapper.cpp @@ -0,0 +1,153 @@ +#include "SceTypes.hpp" +#include "prx/libSceSpSysCallWrapper/libSceSpSysCallWrapper.hpp" +#include "prx/libSceNet/libSceNet.hpp" +#include "prx/libkernel/libkernel.hpp" +#include +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +struct Shared { + PthreadMutex mutex = nullptr; + PthreadCond cond = nullptr; + bool ready = false; +}; + +void* APS5_VABI ExitWithArgument(void* arg) { + sceSpPthreadExit(arg); + return nullptr; +} + +void* APS5_VABI SignalReady(void* arg) { + auto* shared = static_cast(arg); + Require(sceSpPthreadMutexLock(&shared->mutex) == 0); + shared->ready = true; + Require(sceSpPthreadCondSignal(&shared->cond) == 0); + Require(sceSpPthreadMutexUnlock(&shared->mutex) == 0); + return nullptr; +} + +void Threads() { + PthreadAttr attr = nullptr; + Require(sceSpPthreadAttrInit(&attr) == 0); + Require(sceSpPthreadAttrSetstacksize(&attr, 0x10000) == 0); + Require(sceSpPthreadAttrSetinheritsched(&attr, 0) == scePthreadAttrSetinheritsched(&attr, 0)); + const KernelSchedParam param{700}; + Require(sceSpPthreadAttrSetschedparam(&attr, ¶m) == scePthreadAttrSetschedparam(&attr, ¶m)); + + Pthread thread = nullptr; + int marker = 0; + Require(sceSpPthreadCreate(&thread, &attr, ExitWithArgument, &marker, "sp_exit") == 0); + void* result = nullptr; + Require(sceSpPthreadJoin(thread, &result) == 0); + Require(result == &marker); + + Pthread detached = nullptr; + Require(sceSpPthreadCreate(&detached, &attr, ExitWithArgument, nullptr, "sp_detach") == 0); + Require(sceSpPthreadDetach(detached) == 0); + Require(sceSpPthreadAttrDestroy(&attr) == 0); + + PthreadMutexattr mutexAttr = nullptr; + Require(sceSpPthreadMutexattrInit(&mutexAttr) == 0); + Require(sceSpPthreadMutexattrSettype(&mutexAttr, 2) == 0); + Require(sceSpPthreadMutexattrSetprotocol(&mutexAttr, 0) == scePthreadMutexattrSetprotocol(&mutexAttr, 0)); + Require(sceSpPthreadSetprio(scePthreadSelf(), 700) == scePthreadSetprio(scePthreadSelf(), 700)); + PthreadCondattr condAttr = nullptr; + Require(sceSpPthreadCondattrInit(&condAttr) == 0); + + Shared shared; + Require(sceSpPthreadMutexInit(&shared.mutex, &mutexAttr, "sp_mutex") == 0); + Require(sceSpPthreadCondInit(&shared.cond, &condAttr, "sp_cond") == 0); + Require(sceSpPthreadMutexattrDestroy(&mutexAttr) == 0); + Require(sceSpPthreadCondattrDestroy(&condAttr) == 0); + + Require(sceSpPthreadMutexLock(&shared.mutex) == 0); + Pthread signaller = nullptr; + Require(sceSpPthreadCreate(&signaller, nullptr, SignalReady, &shared, "sp_signal") == 0); + while (!shared.ready) Require(sceSpPthreadCondWait(&shared.cond, &shared.mutex) == 0); + Require(sceSpPthreadMutexUnlock(&shared.mutex) == 0); + Require(sceSpPthreadJoin(signaller, nullptr) == 0); + Require(sceSpPthreadCondDestroy(&shared.cond) == 0); + Require(sceSpPthreadMutexDestroy(&shared.mutex) == 0); +} + +void Time() { + KernelTimeval now{}; + Require(sceSpKernelGettimeofday(&now) == 0); + Require(now.tv_sec > 0 && now.tv_usec >= 0 && now.tv_usec < 1000000); + const KernelTimespec delay{0, 1000000}; + Require(sceSpKernelNanosleep(&delay, nullptr) == 0); +} + +template +void SameError(TWrapped wrapped, TDirect direct) { + const auto wrappedResult = wrapped(); + const int wrappedErrno = *sceNetErrnoLoc(); + const auto directResult = direct(); + Require(wrappedResult == directResult); + Require(wrappedErrno == *sceNetErrnoLoc()); +} + +void Net() { + Require(sceSpNetErrnoLoc() == sceNetErrnoLoc()); + + const int pool = sceSpNetPoolCreate("sp_pool", 0x4000, 0); + Require(pool >= 0); + const int resolver = sceSpNetResolverCreate("sp_resolver", pool, 0); + Require(resolver >= 0); + int status = 0; + Require(sceSpNetResolverGetError(resolver, &status) == sceNetResolverGetError(resolver, &status)); + Require(sceSpNetResolverDestroy(resolver) == 0); + Require(sceSpNetPoolDestroy(pool) == 0); + + const int epoll = sceSpNetEpollCreate("sp_epoll", 0); + Require(epoll >= 0); + Require(sceSpNetEpollDestroy(epoll) == 0); + + const int socket = sceSpNetSocket("sp_socket", 2, 2, 0); + Require(socket >= 0); + const int enable = 1; + Require(sceSpNetSetsockopt(socket, 0xffff, 0x4, &enable, sizeof(enable)) == sceNetSetsockopt(socket, 0xffff, 0x4, &enable, sizeof(enable))); + Require(sceSpNetSocketClose(socket) == 0); + + char byte = 0; + const std::uint8_t address[16]{16, 2}; + SameError([&] { return sceSpNetSend(-1, &byte, 1, 0); }, [&] { return sceNetSend(-1, &byte, 1, 0); }); + SameError([&] { return sceSpNetRecv(-1, &byte, 1, 0); }, [&] { return sceNetRecv(-1, &byte, 1, 0); }); + SameError([&] { return sceSpNetConnect(-1, address, sizeof(address)); }, [&] { return sceNetConnect(-1, address, sizeof(address)); }); + SameError([&] { return sceSpNetEpollControl(-1, 1, 0, nullptr); }, [&] { return sceNetEpollControl(-1, 1, 0, nullptr); }); + SameError([&] { return sceSpNetEpollWait(-1, nullptr, 0, 0); }, [&] { return sceNetEpollWait(-1, nullptr, 0, 0); }); + SameError([&] { return sceSpNetEpollAbort(-1, 0); }, [&] { return sceNetEpollAbort(-1, 0); }); + SameError([&] { return sceSpNetResolverAbort(-1, 0); }, [&] { return sceNetResolverAbort(-1, 0); }); + std::uint32_t resolved = 0; + SameError([&] { return sceSpNetResolverStartNtoa(-1, "localhost", &resolved, 0, 0, 0); }, [&] { return sceNetResolverStartNtoa(-1, "localhost", &resolved, 0, 0, 0); }); +} + +void Posix() { + Require(sceSpGetPid() > 0); + Require(sceSpGetPid() == getpid_nid_postfix()); + + const std::int64_t zero[2]{0, 0}; + Require(sceSpSelect(0, nullptr, nullptr, nullptr, zero) == 0); + const std::int64_t invalid[2]{0, 1000000}; + *__error_nid_postfix() = 0; + Require(sceSpSelect(0, nullptr, nullptr, nullptr, invalid) == -1); + const int wrappedErrno = *__error_nid_postfix(); + *__error_nid_postfix() = 0; + Require(select_nid_postfix(0, nullptr, nullptr, nullptr, invalid) == -1); + Require(wrappedErrno == 22 && *__error_nid_postfix() == 22); + *__error_nid_postfix() = 0; + Require(sceSpSelect(-1, nullptr, nullptr, nullptr, nullptr) == -1 && *__error_nid_postfix() == 22); +} + +} + +int main() { + Threads(); + Time(); + Net(); + Posix(); +} diff --git a/core/libs/tests/GuestSsl.cpp b/core/libs/tests/GuestSsl.cpp index 4becc68d8c..f4d57bc7a2 100644 --- a/core/libs/tests/GuestSsl.cpp +++ b/core/libs/tests/GuestSsl.cpp @@ -1,39 +1,13 @@ +#include "prx/libSceSsl/include/Ssl.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "ProcessParametersFixture.hpp" +#include +#include #include #include #include #include - -extern "C" { -int APS5_VABI sceSslInit_nid_postfix(std::size_t); -int APS5_VABI sceSslTerm_nid_postfix(int); -int APS5_VABI sceSslGetCaCerts(int, void*); -int APS5_VABI sceSslFreeCaCerts(int, void*); -int APS5_VABI sceSslGetCaList(int, void*); -int APS5_VABI sceSslFreeCaList(int, void*); -int APS5_VABI sceSslUnloadCert(int); -int APS5_VABI sceSslCreateConnection(int, int, const char*); -int APS5_VABI sceSslDeleteConnection(int); -int APS5_VABI sceSslSetVerifyCallback(int, int (APS5_VABI *)(int, unsigned int, void**, int, void*), void*); -int APS5_VABI sceSslConnect(int); -int APS5_VABI sceSslRead(int, void*, std::size_t, int); -int APS5_VABI sceSslWrite(int, void*, std::size_t, int); -int APS5_VABI sceSslClose(int); -int APS5_VABI sceSslEnableVerifyOption(int, std::uint32_t); -int APS5_VABI sceSslDisableVerifyOption(int, std::uint32_t); -int APS5_VABI sceSslSetMinSslVersion(int, int); -int APS5_VABI sceSslSetAlpn(int, const char*); -const char* APS5_VABI sceSslGetAlpnSelected(int); -} - -struct SslMemoryPoolStats { - std::size_t pool_size; - std::size_t max_inuse_size; - std::size_t current_inuse_size; - std::int32_t reserved; -}; - -extern "C" int APS5_VABI sceSslGetMemoryPoolStats(int, SslMemoryPoolStats*); +#include static void Require(bool value) { if (!value) std::abort(); } @@ -49,48 +23,45 @@ static bool Throws(TCall call) { static int APS5_VABI VerifyCallback(int, unsigned int, void**, int, void*) { return 0; } -struct SslCaCerts { - void* certs; - std::size_t num; - void* pool; -}; - -struct SslCaList { - void** certs; - int num; -}; - int main() { + static std::array parameters{}; + const std::uint64_t processSize = parameters.size(); + const std::uint32_t processMagic = 0x4942524f; + std::memcpy(parameters.data(), &processSize, sizeof(processSize)); + std::memcpy(parameters.data() + 8, &processMagic, sizeof(processMagic)); + Require(ProcessParametersFixture::Install(parameters.data(), parameters.size())); + constexpr int invalidValue = static_cast(0x8095F007); constexpr int notFound = static_cast(0x8095F004); constexpr int invalidArg = static_cast(0x8095177A); int marker = 0; const int context = sceSslInit_nid_postfix(0x10000); Require(context > 0); - SslMemoryPoolStats stats{1, 1, 1, 1}; + SceSsl::MemoryPoolStats stats{1, 1, 1, 1}; Require(sceSslGetMemoryPoolStats(context, &stats) == 0); - Require(stats.pool_size == 0x10000 && stats.max_inuse_size == 0 && stats.current_inuse_size == 0 && stats.reserved == 0); - Require(sceSslGetCaCerts(context, nullptr) == invalidArg); + Require(stats.poolSize == 0x10000 && stats.maxInuseSize == 0 && stats.currentInuseSize == 0 && stats.reserved == 0); + Require(sceSslGetCaCerts(context, nullptr) == invalidValue); Require(sceSslFreeCaCerts(context, nullptr) == invalidArg); - SslCaCerts certs{&marker, 3, &marker}; + SceSsl::Data certificateMarker{}; + SceSsl::CaCerts certs{&certificateMarker, 3, &marker}; Require(sceSslGetCaCerts(context, &certs) == notFound); Require(certs.certs == nullptr && certs.num == 0 && certs.pool == nullptr); - certs = {&marker, 3, &marker}; + certs = {&certificateMarker, 3, &marker}; Require(sceSslFreeCaCerts(context, &certs) == 0); Require(certs.certs == nullptr && certs.num == 0 && certs.pool == nullptr); - Require(sceSslGetCaList(context, nullptr) == invalidArg); + Require(sceSslGetCaList(context, nullptr) == invalidValue); Require(sceSslFreeCaList(context, nullptr) == invalidArg); void* handle = ▮ - SslCaList list{&handle, 3}; + SceSsl::CaList list{&handle, 3}; Require(sceSslGetCaList(context, &list) == notFound); Require(list.certs == nullptr && list.num == 0); Require(sceSslFreeCaList(context, &list) == 0); Require(list.certs == nullptr && list.num == 0); - SslCaList foreign{&handle, 1}; + SceSsl::CaList foreign{&handle, 1}; Require(Throws([&] { sceSslFreeCaList(context, &foreign); })); foreign = {nullptr, 1}; Require(Throws([&] { sceSslFreeCaList(context, &foreign); })); @@ -102,7 +73,6 @@ int main() { Require(list.certs == nullptr && list.num == 0); constexpr int invalidId = static_cast(0x8095F006); - constexpr int invalidValue = static_cast(0x8095F007); constexpr int badLength = static_cast(0x80951773); constexpr int badDescriptor = static_cast(0x80410109); Require(sceSslInit_nid_postfix(0) == static_cast(0x80020016)); @@ -129,7 +99,14 @@ int main() { Require(sceSslGetAlpnSelected(connection) == nullptr); Require(sceSslSetAlpn(connection, "http/1.1") == 0); Require(sceSslSetAlpn(connection, "") == badLength); - Require(Throws([&] { sceSslSetAlpn(connection, nullptr); })); + Require(sceSslSetAlpn(connection, ",h2") == badLength); + Require(sceSslSetAlpn(connection, "h2,") == badLength); + Require(sceSslSetAlpn(connection, "h2,,http/1.1") == badLength); + Require(sceSslSetAlpn(connection, std::string(255, 'a').c_str()) == 0); + Require(sceSslSetAlpn(connection, std::string(256, 'a').c_str()) == badLength); + Require(sceSslSetAlpn(connection, std::string(65535, 'a').c_str()) == badLength); + Require(sceSslSetAlpn(0, nullptr) == invalidId); + Require(sceSslSetAlpn(connection, nullptr) == 0); Require(sceSslGetAlpnSelected(connection) == nullptr); char buffer[4]{}; Require(sceSslConnect(connection) == badDescriptor); @@ -144,7 +121,7 @@ int main() { Require(sceSslDisableVerifyOption(unknown, serverVerify) == invalidId); Require(sceSslSetMinSslVersion(unknown, 5) == invalidId); Require(sceSslSetAlpn(unknown, "http/1.1") == invalidId); - Require(Throws([&] { sceSslGetAlpnSelected(unknown); })); + Require(sceSslGetAlpnSelected(unknown) == nullptr); Require(sceSslConnect(unknown) == invalidId); Require(sceSslRead(unknown, buffer, sizeof(buffer), 0) == invalidId); Require(sceSslWrite(unknown, buffer, sizeof(buffer), 0) == invalidId); @@ -158,16 +135,25 @@ int main() { Require(sceSslDisableVerifyOption(connection, serverVerify) == invalidId); Require(sceSslSetMinSslVersion(connection, 5) == invalidId); Require(sceSslSetAlpn(connection, "http/1.1") == invalidId); - Require(Throws([&] { sceSslGetAlpnSelected(connection); })); + Require(sceSslGetAlpnSelected(connection) == nullptr); Require(sceSslConnect(connection) == invalidId); Require(sceSslClose(connection) == invalidId); Require(sceSslCreateConnection(context, 0, "example.com") == invalidId); Require(sceSslDeleteConnection(connection) == invalidId); - Require(sceSslGetCaList(context, nullptr) == invalidArg); + Require(sceSslGetCaList(context, nullptr) == invalidValue); Require(sceSslFreeCaList(context, nullptr) == invalidArg); list = {&handle, 3}; - Require(Throws([&] { sceSslGetCaList(context, &list); })); + Require(sceSslGetCaList(context, &list) == invalidId); + Require(list.certs == &handle && list.num == 3); + Require(sceSslGetCaCerts(context, nullptr) == invalidId); + certs = {&certificateMarker, 3, &marker}; + Require(sceSslGetCaCerts(context, &certs) == invalidId); + Require(certs.certs == &certificateMarker && certs.num == 3 && certs.pool == &marker); Require(Throws([&] { sceSslFreeCaList(context, &list); })); - Require(Throws([&] { sceSslUnloadCert(context); })); + for (const std::uint32_t sdkVersion : {0u, 0x074fffffu, 0x07500000u, 0x09000000u}) { + std::memcpy(parameters.data() + 16, &sdkVersion, sizeof(sdkVersion)); + Require(sceSslUnloadCert(context) == (sdkVersion < 0x07500000 ? 0 : invalidId)); + Require(sceSslUnloadCert(0) == (sdkVersion < 0x07500000 ? 0 : invalidId)); + } } diff --git a/core/libs/tests/GuestSslLoad.cpp b/core/libs/tests/GuestSslLoad.cpp index 28b894ddb4..92507f0183 100644 --- a/core/libs/tests/GuestSslLoad.cpp +++ b/core/libs/tests/GuestSslLoad.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSsl/include/Ssl.hpp" #include "prx/libSceSsl/SslTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SslCertificateFixture.hpp" @@ -10,32 +11,6 @@ #include #include -extern "C" { -int APS5_VABI sceSslInit_nid_postfix(std::uint64_t); -int APS5_VABI sceSslTerm_nid_postfix(int); -int APS5_VABI sceSslGetMemoryPoolStats(int, SceSsl::MemoryPoolStats*); -int APS5_VABI sceSslLoadCert(int, int, SceSsl::Data**, SceSsl::Data*, SceSsl::Data*); -int APS5_VABI sceSslUnloadCert(int); -int APS5_VABI sceSslGetCaList(int, SceSsl::CaList*); -int APS5_VABI sceSslFreeCaList(int, SceSsl::CaList*); -int APS5_VABI sceSslGetCaCerts(int, SceSsl::CaCerts*); -int APS5_VABI sceSslFreeCaCerts(int, SceSsl::CaCerts*); -int APS5_VABI sceSslCreateConnection(int, int, const char*); -int APS5_VABI sceSslDeleteConnection(int); -int APS5_VABI sceSslSetVerifyCallback(int, int (APS5_VABI *)(int, unsigned int, void**, int, void*), void*); -int APS5_VABI sceSslGetSerialNumber(int, void*, unsigned char*, std::size_t*); -void* APS5_VABI sceSslGetSubjectName(int, void*); -void* APS5_VABI sceSslGetIssuerName(int, void*); -int APS5_VABI sceSslGetNameEntryCount(int, void*); -int APS5_VABI sceSslGetNameEntryInfo(int, void*, int, char*, std::size_t, char*, std::size_t, std::size_t*); -int APS5_VABI sceSslFreeSslCertName(int, void*); -int APS5_VABI sceSslClose(int); -int APS5_VABI sceSslGetNotBefore(int, void*, SceSsl::RtcTick*); -int APS5_VABI sceSslGetNotAfter(int, void*, SceSsl::RtcTick*); -int APS5_VABI sceSslGetFingerprint(int, void*, int, unsigned char*); -int APS5_VABI sceSslGetPem(int, void*, char*, std::size_t*); -} - namespace { constexpr int NotFound = static_cast(0x8095F004); @@ -141,14 +116,14 @@ void AtomicFailures() { Check(sceSslGetCaCerts(id, &bytes) == 0, "CA vanished after failure"); CheckRoot(bytes); Check(sceSslFreeCaCerts(id, &bytes) == 0, "snapshot free failed"); - Throws([&] { sceSslLoadCert(id, -1, nullptr, nullptr, nullptr); }); - Throws([&] { sceSslLoadCert(id, 1, nullptr, nullptr, nullptr); }); + Check(sceSslLoadCert(id, -1, nullptr, nullptr, nullptr) == 0, "negative CA count was not ignored"); + Check(sceSslLoadCert(id, 1, nullptr, nullptr, nullptr) == 0, "null CA list was not ignored"); SceSsl::Data* absent = nullptr; Throws([&] { sceSslLoadCert(id, 1, &absent, nullptr, nullptr); }); - Throws([&] { sceSslLoadCert(id, 0, nullptr, &der, nullptr); }); + Check(Stats(id).currentInuseSize == before, "argument failure changed retained CA"); Check(sceSslTerm_nid_postfix(id) == 0, "term failed"); - Throws([&] { sceSslLoadCert(id, 1, &pointer, nullptr, nullptr); }); + Check(sceSslLoadCert(id, 1, &pointer, nullptr, nullptr) == InvalidId, "invalid load context error mismatch"); const int tiny = sceSslInit_nid_postfix(32); Throws([&] { sceSslLoadCert(tiny, 1, &pointer, nullptr, nullptr); }); Check(Stats(tiny).currentInuseSize == 0, "failed small-pool load leaked"); @@ -163,8 +138,20 @@ void ClientIdentity() { Check(sceSslLoadCert(id, 0, nullptr, &client, &key) == 0, "client identity load failed"); const auto before = Stats(id).currentInuseSize; Check(before > 0, "identity not retained"); - Throws([&] { sceSslLoadCert(id, 0, nullptr, &client, &wrong); }); - Check(Stats(id).currentInuseSize == before, "mismatched key changed identity"); + Check(sceSslLoadCert(id, 0, nullptr, &client, &wrong) == 0, "key pair was checked during loading"); + Check(sceSslLoadCert(id, 0, nullptr, &client, nullptr) == 0, "certificate-only load failed"); + const auto certificateBytes = Stats(id).currentInuseSize; + Check(certificateBytes > 0 && certificateBytes < before, "certificate replacement retained the old key"); + Check(sceSslLoadCert(id, 0, nullptr, nullptr, &key) == 0, "independent key load failed"); + Check(Stats(id).currentInuseSize == before, "separate identity load retained different data"); + auto arbitraryKey = Data("invalid key encoding"); + Check(sceSslLoadCert(id, 0, nullptr, nullptr, &arbitraryKey) == 0, "key was parsed during loading"); + Check(Stats(id).currentInuseSize == certificateBytes + arbitraryKey.size, "raw key bytes were not retained"); + SceSsl::Data missing{}; + Check(sceSslLoadCert(id, 0, nullptr, &missing, nullptr) == static_cast(0x80951771), "null certificate payload error mismatch"); + Check(sceSslLoadCert(id, 0, nullptr, nullptr, &missing) == static_cast(0x80951771), "null key payload error mismatch"); + Check(Stats(id).currentInuseSize == certificateBytes + arbitraryKey.size, "null payload cleared the identity"); + Check(sceSslLoadCert(-1, -1, nullptr, &missing, &missing) == InvalidId, "load context validation precedence mismatch"); Check(sceSslUnloadCert(id) == 0 && Stats(id).currentInuseSize == 0, "identity release failed"); Check(sceSslTerm_nid_postfix(id) == 0, "identity term failed"); } @@ -182,37 +169,39 @@ void CertificateReaders() { Check(sceSslGetSerialNumber(id, cert, nullptr, &querySize) == 0 && querySize == 3, "query serial size failed"); querySize = 0; unsigned char shortBuf[2]{}; - Check(sceSslGetSerialNumber(id, cert, shortBuf, &querySize) == 0 && querySize == 3, "zero-len serial query failed"); + Check(sceSslGetSerialNumber(id, cert, shortBuf, &querySize) == 0 && querySize == 0, "zero-len serial copy changed length"); querySize = sizeof(shortBuf); - Check(sceSslGetSerialNumber(id, cert, shortBuf, &querySize) == static_cast(0x8095177a) && querySize == 3, - "short serial buffer accepted"); + Check(sceSslGetSerialNumber(id, cert, shortBuf, &querySize) == 0 && querySize == 2 && shortBuf[0] == 0 && shortBuf[1] == 0x80, + "short serial copy mismatch"); unsigned char serialData[8]{}; std::size_t serialLen = sizeof(serialData); Check(sceSslGetSerialNumber(id, cert, serialData, &serialLen) == 0, "get serial failed"); Check(serialLen == sizeof(serialData) && serialData[3] == 0, "a large enough serial buffer changed the caller's length"); Check(serialData[0] == 0x00 && serialData[1] == 0x80 && serialData[2] == 0xa1, "serial bytes mismatch"); - Check(sceSslGetSerialNumber(id, cert, serialData, nullptr) == static_cast(0x8095177a), "null serial length accepted"); - Throws([&] { sceSslGetSerialNumber(id, nullptr, serialData, &serialLen); }); + Throws([&] { sceSslGetSerialNumber(id, cert, serialData, nullptr); }); + Check(sceSslGetSerialNumber(id, cert, nullptr, nullptr) == 0, "empty serial query failed"); + Check(sceSslGetSerialNumber(id, nullptr, serialData, &serialLen) == static_cast(0x8095f007), "null certificate error mismatch"); void* fake = &serialLen; Throws([&] { sceSslGetSerialNumber(id, fake, serialData, &serialLen); }); - Throws([&] { sceSslGetSerialNumber(9999, cert, serialData, &serialLen); }); + Check(sceSslGetSerialNumber(9999, cert, serialData, &serialLen) == InvalidId, "unknown context error mismatch"); void* subject = sceSslGetSubjectName(id, cert); Check(subject != nullptr, "get subject failed"); + Check(sceSslGetSubjectName(id, cert) == subject, "subject handle identity changed"); void* issuer = sceSslGetIssuerName(id, cert); Check(issuer != nullptr, "get issuer failed"); - Throws([&] { sceSslGetSubjectName(id, nullptr); }); - Throws([&] { sceSslGetIssuerName(id, nullptr); }); + Check(sceSslGetSubjectName(id, nullptr) == nullptr, "invalid name query returned a handle"); + Check(sceSslGetIssuerName(id, nullptr) == nullptr, "invalid name query returned a handle"); Throws([&] { sceSslGetSubjectName(id, fake); }); Throws([&] { sceSslGetIssuerName(id, fake); }); - Throws([&] { sceSslGetSubjectName(9999, cert); }); - Throws([&] { sceSslGetIssuerName(9999, cert); }); + Check(sceSslGetSubjectName(9999, cert) == nullptr, "invalid name query returned a handle"); + Check(sceSslGetIssuerName(9999, cert) == nullptr, "invalid name query returned a handle"); Check(sceSslGetNameEntryCount(id, subject) == 3, "subject entry count mismatch"); Check(sceSslGetNameEntryCount(id, issuer) == 3, "issuer entry count mismatch"); - Check(sceSslGetNameEntryCount(id, nullptr) == static_cast(0x8095177a), "null cert name count accepted"); + Check(sceSslGetNameEntryCount(id, nullptr) == static_cast(0x8095f007), "null cert name count accepted"); Throws([&] { sceSslGetNameEntryCount(id, fake); }); - Throws([&] { sceSslGetNameEntryCount(9999, subject); }); + Check(sceSslGetNameEntryCount(9999, subject) == InvalidId, "unknown name context error mismatch"); char oid[32]{}; char val[64]{}; @@ -226,37 +215,50 @@ void CertificateReaders() { char smallVal[4]{}; std::size_t fullLen = 0; - Check(sceSslGetNameEntryInfo(id, subject, 1, nullptr, 0, smallVal, sizeof(smallVal), &fullLen) == 0, "truncated query failed"); - Check(std::strcmp(smallVal, "Any") == 0 && fullLen == 12, "buffer truncation mismatch"); + Check(sceSslGetNameEntryInfo(id, subject, 1, oid, 0, smallVal, sizeof(smallVal), &fullLen) == 0, "truncated query failed"); + Check(std::memcmp(smallVal, "AnyP", 4) == 0 && fullLen == 4, "buffer truncation mismatch"); + char shortOid[5] = {'x', 'x', 'x', 'x', 'x'}; + char shortValue[5] = {'y', 'y', 'y', 'y', 'y'}; + Check(sceSslGetNameEntryInfo(id, subject, 1, shortOid, 4, shortValue, 4, &fullLen) == 0, "bounded entry query failed"); + Check(std::memcmp(shortOid, "orga", 4) == 0 && shortOid[4] == 'x', "oid copy or guard mismatch"); + Check(std::memcmp(shortValue, "AnyP", 4) == 0 && shortValue[4] == 'y' && fullLen == 4, "value copy or guard mismatch"); + Check(sceSslGetNameEntryInfo(id, subject, 1, shortOid, 0, shortValue, 0, &fullLen) == 0 && fullLen == 0, "zero capacity query failed"); + Check(sceSslGetNameEntryInfo(id, subject, 1, nullptr, 0, shortValue, 0, &fullLen) == static_cast(0x8095f007), "null oid accepted"); + Check(sceSslGetNameEntryInfo(id, subject, 1, shortOid, 0, nullptr, 0, &fullLen) == static_cast(0x8095f007), "null value accepted"); + Check(sceSslGetNameEntryInfo(id, subject, 1, shortOid, 0, shortValue, 0, nullptr) == static_cast(0x8095f007), "null value length accepted"); - Check(sceSslGetNameEntryInfo(id, subject, -1, oid, sizeof(oid), val, sizeof(val), &vlen) == static_cast(0x8095177a), "negative index accepted"); - Check(sceSslGetNameEntryInfo(id, subject, 3, oid, sizeof(oid), val, sizeof(val), &vlen) == static_cast(0x8095177a), "out of bounds index accepted"); - Check(sceSslGetNameEntryInfo(id, nullptr, 0, oid, sizeof(oid), val, sizeof(val), &vlen) == static_cast(0x8095177a), "null certName accepted"); + Check(sceSslGetNameEntryInfo(id, subject, -1, oid, sizeof(oid), val, sizeof(val), &vlen) == static_cast(0x8095f007), "negative index accepted"); + Check(sceSslGetNameEntryInfo(id, subject, 3, oid, sizeof(oid), val, sizeof(val), &vlen) == static_cast(0x8095f007), "out of bounds index accepted"); + Check(sceSslGetNameEntryInfo(id, nullptr, 0, oid, sizeof(oid), val, sizeof(val), &vlen) == static_cast(0x8095f007), "null certName accepted"); Throws([&] { sceSslGetNameEntryInfo(id, fake, 0, oid, sizeof(oid), val, sizeof(val), &vlen); }); - Throws([&] { sceSslGetNameEntryInfo(9999, subject, 0, oid, sizeof(oid), val, sizeof(val), &vlen); }); + Check(sceSslGetNameEntryInfo(9999, subject, 0, oid, sizeof(oid), val, sizeof(val), &vlen) == InvalidId, "unknown entry context error mismatch"); Check(sceSslFreeSslCertName(id, subject) == 0, "free subject name failed"); Check(sceSslFreeSslCertName(id, issuer) == 0, "free issuer name failed"); - Check(sceSslFreeSslCertName(id, nullptr) == static_cast(0x8095177a), "null cert name free accepted"); - Throws([&] { sceSslFreeSslCertName(id, subject); }); - Throws([&] { sceSslFreeSslCertName(id, fake); }); - Throws([&] { sceSslFreeSslCertName(9999, subject); }); + Check(sceSslGetNameEntryCount(id, subject) == 3, "name release invalidated a live certificate name"); + Check(sceSslFreeSslCertName(id, nullptr) == static_cast(0x8095f007), "null cert name free accepted"); + Check(sceSslFreeSslCertName(id, subject) == 0, "non-null name release failed"); + Check(sceSslFreeSslCertName(id, fake) == 0, "non-null name release failed"); + Check(sceSslFreeSslCertName(9999, subject) == InvalidId, "invalid name release context error mismatch"); SceSsl::RtcTick begin{}; + Check(sceSslGetNotBefore(0, nullptr, nullptr) == InvalidId, "date validation precedence mismatch"); + Check(sceSslGetNotAfter(0, nullptr, nullptr) == InvalidId, "date validation precedence mismatch"); + Check(sceSslGetFingerprint(0, nullptr, -1, nullptr) == InvalidId, "fingerprint validation precedence mismatch"); Check(sceSslGetNotBefore(id, cert, &begin) == 0, "get not before failed"); Check(begin.tick == 63927094442000000ULL, "not before tick mismatch"); - Check(sceSslGetNotBefore(id, cert, nullptr) == static_cast(0x8095177a), "null begin accepted"); - Throws([&] { sceSslGetNotBefore(id, nullptr, &begin); }); + Check(sceSslGetNotBefore(id, cert, nullptr) == static_cast(0x8095f007), "null begin accepted"); + Check(sceSslGetNotBefore(id, nullptr, &begin) == static_cast(0x8095f007), "null certificate date error mismatch"); Throws([&] { sceSslGetNotBefore(id, fake, &begin); }); - Throws([&] { sceSslGetNotBefore(9999, cert, &begin); }); + Check(sceSslGetNotBefore(9999, cert, &begin) == InvalidId, "invalid date context error mismatch"); SceSsl::RtcTick limit{}; Check(sceSslGetNotAfter(id, cert, &limit) == 0, "get not after failed"); Check(limit.tick == 64242454442000000ULL, "not after tick mismatch"); - Check(sceSslGetNotAfter(id, cert, nullptr) == static_cast(0x8095177a), "null limit accepted"); - Throws([&] { sceSslGetNotAfter(id, nullptr, &limit); }); + Check(sceSslGetNotAfter(id, cert, nullptr) == static_cast(0x8095f007), "null limit accepted"); + Check(sceSslGetNotAfter(id, nullptr, &limit) == static_cast(0x8095f007), "null certificate date error mismatch"); Throws([&] { sceSslGetNotAfter(id, fake, &limit); }); - Throws([&] { sceSslGetNotAfter(9999, cert, &limit); }); + Check(sceSslGetNotAfter(9999, cert, &limit) == InvalidId, "invalid date context error mismatch"); unsigned char md5[16]{}; Check(sceSslGetFingerprint(id, cert, 0, md5) == 0, "get md5 fingerprint failed"); @@ -273,12 +275,12 @@ void CertificateReaders() { constexpr unsigned char expectedSha256[32] = {0x3d, 0x7d, 0xac, 0xd3, 0x04, 0x90, 0x42, 0xe1, 0x57, 0x19, 0x87, 0x27, 0x54, 0x45, 0xae, 0xed, 0xc4, 0xea, 0xd6, 0xd4, 0x20, 0xa0, 0x7f, 0xc4, 0xf5, 0x26, 0xc1, 0xa0, 0x85, 0x87, 0x5a, 0x4c}; Check(std::memcmp(sha256, expectedSha256, sizeof(sha256)) == 0, "sha256 digest mismatch"); - Check(sceSslGetFingerprint(id, cert, 3, nullptr) == static_cast(0x8095177a), "null fingerprint buffer accepted"); + Check(sceSslGetFingerprint(id, cert, 3, nullptr) == static_cast(0x8095f007), "null fingerprint buffer accepted"); Check(sceSslGetFingerprint(id, cert, -1, sha256) == static_cast(0x8095177a), "negative hash type accepted"); Check(sceSslGetFingerprint(id, cert, 99, sha256) == static_cast(0x8095177a), "invalid hash type accepted"); - Throws([&] { sceSslGetFingerprint(id, nullptr, 3, sha256); }); + Check(sceSslGetFingerprint(id, nullptr, 3, sha256) == static_cast(0x8095f007), "null fingerprint certificate error mismatch"); Throws([&] { sceSslGetFingerprint(id, fake, 3, sha256); }); - Throws([&] { sceSslGetFingerprint(9999, cert, 3, sha256); }); + Check(sceSslGetFingerprint(9999, cert, 3, sha256) == InvalidId, "invalid fingerprint context error mismatch"); std::size_t pemLen = 0; Check(sceSslGetPem(id, cert, nullptr, &pemLen) == 0, "query pem size failed"); @@ -287,27 +289,27 @@ void CertificateReaders() { std::size_t writtenLen = pemBuf.size(); Check(sceSslGetPem(id, cert, pemBuf.data(), &writtenLen) == 0, "write pem failed"); Check(writtenLen == pemLen, "written pem length mismatch"); - Check(std::strstr(pemBuf.data(), "-----BEGIN CERTIFICATE-----\n") != nullptr, "pem header missing"); - Check(std::strstr(pemBuf.data(), "-----END CERTIFICATE-----\n") != nullptr, "pem footer missing"); + Check(std::strstr(pemBuf.data(), "-----BEGIN CERTIFICATE-----\r\n") != nullptr, "pem header missing"); + Check(std::strstr(pemBuf.data(), "-----END CERTIFICATE-----\r\n") != nullptr, "pem footer missing"); char smallPem[8]{}; std::size_t smallPemLen = sizeof(smallPem); - Throws([&] { sceSslGetPem(id, cert, smallPem, &smallPemLen); }); - Check(sceSslGetPem(id, cert, pemBuf.data(), nullptr) == static_cast(0x8095177a), "null pem len accepted"); - Throws([&] { sceSslGetPem(id, nullptr, pemBuf.data(), &writtenLen); }); + Check(sceSslGetPem(id, cert, smallPem, &smallPemLen) == static_cast(0x8095f008) && smallPemLen == pemLen, "short PEM size error mismatch"); + Check(sceSslGetPem(id, cert, pemBuf.data(), nullptr) == static_cast(0x8095f007), "null pem len accepted"); + Check(sceSslGetPem(id, nullptr, pemBuf.data(), &writtenLen) == static_cast(0x8095f007), "null PEM certificate error mismatch"); Throws([&] { sceSslGetPem(id, fake, pemBuf.data(), &writtenLen); }); Check(sceSslClose(1) == InvalidId, "closing an unknown connection did not report an invalid id"); Check(sceSslFreeCaList(id, &handles) == 0, "free ca list failed"); Check(sceSslTerm_nid_postfix(id) == 0, "term failed"); - Throws([&] { sceSslGetSerialNumber(id, cert, serialData, &serialLen); }); - Throws([&] { sceSslGetSubjectName(id, cert); }); - Throws([&] { sceSslGetIssuerName(id, cert); }); - Throws([&] { sceSslGetNotBefore(id, cert, &begin); }); - Throws([&] { sceSslGetNotAfter(id, cert, &limit); }); - Throws([&] { sceSslGetFingerprint(id, cert, 3, sha256); }); - Throws([&] { sceSslGetPem(id, cert, pemBuf.data(), &writtenLen); }); + Check(sceSslGetSerialNumber(id, cert, serialData, &serialLen) == InvalidId, "terminated context error mismatch"); + Check(sceSslGetSubjectName(id, cert) == nullptr, "invalid name query returned a handle"); + Check(sceSslGetIssuerName(id, cert) == nullptr, "invalid name query returned a handle"); + Check(sceSslGetNotBefore(id, cert, &begin) == InvalidId, "invalid date context error mismatch"); + Check(sceSslGetNotAfter(id, cert, &limit) == InvalidId, "invalid date context error mismatch"); + Check(sceSslGetFingerprint(id, cert, 3, sha256) == InvalidId, "invalid fingerprint context error mismatch"); + Check(sceSslGetPem(id, cert, pemBuf.data(), &writtenLen) == InvalidId, "terminated PEM context error mismatch"); const int id2 = sceSslInit_nid_postfix(1024 * 1024); Check(sceSslLoadCert(id2, 1, &pointer, nullptr, nullptr) == 0, "CA load failed"); @@ -326,8 +328,8 @@ void CertificateReaders() { Throws([&] { sceSslGetFingerprint(id2, cert2, 3, sha256); }); Throws([&] { sceSslGetPem(id2, cert2, pemBuf.data(), &writtenLen); }); Check(sceSslTerm_nid_postfix(id2) == 0, "term failed"); - Throws([&] { sceSslGetNameEntryCount(id2, subject2); }); - Throws([&] { sceSslFreeSslCertName(id2, subject2); }); + Check(sceSslGetNameEntryCount(id2, subject2) == InvalidId, "terminated name context error mismatch"); + Check(sceSslFreeSslCertName(id2, subject2) == InvalidId, "invalid name release context error mismatch"); } } diff --git a/core/libs/tests/GuestSslPs5Readers.cpp b/core/libs/tests/GuestSslPs5Readers.cpp index caffe1cf3e..60aeebcbde 100644 --- a/core/libs/tests/GuestSslPs5Readers.cpp +++ b/core/libs/tests/GuestSslPs5Readers.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSsl/include/Ssl.hpp" #include "prx/libSceSsl/SslTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SslCertificateFixture.hpp" @@ -9,22 +10,7 @@ #include #include #include - -extern "C" { -int APS5_VABI sceSslInit_nid_postfix(std::uint64_t); -int APS5_VABI sceSslTerm_nid_postfix(int); -int APS5_VABI sceSslLoadCert(int, int, SceSsl::Data**, SceSsl::Data*, SceSsl::Data*); -int APS5_VABI sceSslUnloadCert(int); -int APS5_VABI sceSslGetCaList(int, SceSsl::CaList*); -int APS5_VABI sceSslFreeCaList(int, SceSsl::CaList*); -int APS5_VABI sceSslGetSerialNumber(int, void*, unsigned char*, std::size_t*); -void* APS5_VABI sceSslGetSubjectName(int, void*); -void* APS5_VABI sceSslGetIssuerName(int, void*); -int APS5_VABI sceSslGetNameEntryCount(int, void*); -int APS5_VABI sceSslGetNameEntryInfo(int, void*, int, char*, std::size_t, char*, std::size_t, std::size_t*); -int APS5_VABI sceSslFreeSslCertName(int, void*); -int APS5_VABI sceSslGetPem(int, void*, char*, std::size_t*); -} +#include namespace { @@ -55,8 +41,8 @@ void Readers() { Check(serial == std::array{0x00, 0x80, 0xa1, 0xa5, 0xa5}, "serial copy changed its bytes or guards"); serial.fill(0xa5); length = 2; - Check(sceSslGetSerialNumber(context, certificate, serial.data(), &length) == static_cast(0x8095177a) && length == 3, "short serial buffer did not report its required size"); - Check(std::all_of(serial.begin(), serial.end(), [](auto byte) { return byte == 0xa5; }), "short serial buffer was modified"); + Check(sceSslGetSerialNumber(context, certificate, serial.data(), &length) == 0 && length == 2, "short serial copy changed length"); + Check(serial == std::array{0x00, 0x80, 0xa5, 0xa5, 0xa5}, "short serial copy changed guard bytes"); Throws([&] { sceSslGetSerialNumber(other, certificate, nullptr, &length); }); auto* subject = sceSslGetSubjectName(context, certificate); @@ -65,31 +51,36 @@ void Readers() { Check(sceSslGetNameEntryCount(context, subject) == 3 && sceSslGetNameEntryCount(context, issuer) == 3, "PS5 name entry count failed"); Throws([&] { sceSslGetSubjectName(other, certificate); }); Throws([&] { sceSslGetNameEntryCount(other, subject); }); - Throws([&] { sceSslFreeSslCertName(other, subject); }); + Check(sceSslFreeSslCertName(other, subject) == 0, "non-null name release checked ownership"); std::array oid{}, value{}; length = SIZE_MAX; Check(sceSslGetNameEntryInfo(context, subject, 1, oid.data(), oid.size(), value.data(), value.size(), &length) == 0, "PS5 eight-argument name query failed"); Check(std::strcmp(oid.data(), "organizationName") == 0 && std::strcmp(value.data(), "AnyPS5 Tests") == 0 && length == 12, "name output or full-width length changed"); + std::string expectedPem; + for (const char byte : std::string(RootPem)) { + if (byte == '\n') expectedPem += '\r'; + expectedPem += byte; + } length = SIZE_MAX; - Check(sceSslGetPem(context, certificate, nullptr, &length) == 0 && length == std::strlen(RootPem) + 1, "PS5 PEM size query failed"); + Check(sceSslGetPem(context, certificate, nullptr, &length) == 0 && length == expectedPem.size() + 1, "PS5 PEM size query failed"); std::vector pem(length + 2, static_cast(0x5a)); - Check(sceSslGetPem(context, certificate, pem.data(), &length) == 0 && length == std::strlen(RootPem) + 1, "PS5 PEM copy failed"); - Check(std::strcmp(pem.data(), RootPem) == 0 && pem[length] == 0x5a && pem[length + 1] == 0x5a, "PEM copy changed bytes or guards"); + Check(sceSslGetPem(context, certificate, pem.data(), &length) == 0 && length == expectedPem.size() + 1, "PS5 PEM copy failed"); + Check(std::strcmp(pem.data(), expectedPem.c_str()) == 0 && pem[length] == 0x5a && pem[length + 1] == 0x5a, "PEM copy changed bytes or guards"); std::array small{'x', 'x', 'x', 'x'}; length = 2; - Throws([&] { sceSslGetPem(context, certificate, small.data(), &length); }); - Check(std::all_of(small.begin(), small.end(), [](char byte) { return byte == 'x'; }), "short PEM buffer was modified"); + Check(sceSslGetPem(context, certificate, small.data(), &length) == static_cast(0x8095f008) && length == expectedPem.size() + 1, "short PEM error or size mismatch"); + Check(small == std::array{0, 0, 'x', 'x'}, "short PEM clearing or guards mismatch"); Throws([&] { sceSslGetPem(other, certificate, nullptr, &length); }); Check(sceSslUnloadCert(context) == 0, "unload failed"); Check(sceSslGetSerialNumber(context, certificate, nullptr, &length) == 0 && length == 3, "CA snapshot lost its certificate on unload"); Check(sceSslFreeCaList(context, &list) == 0, "CA list free failed"); Throws([&] { sceSslGetSerialNumber(context, certificate, nullptr, &length); }); - Check(sceSslGetNameEntryCount(context, subject) == 3, "owned name depended on freed certificate storage"); + Throws([&] { sceSslGetNameEntryCount(context, subject); }); Check(sceSslFreeSslCertName(context, subject) == 0, "name free failed"); Throws([&] { sceSslGetNameEntryCount(context, subject); }); Check(sceSslTerm_nid_postfix(context) == 0, "context term failed"); - Throws([&] { sceSslGetNameEntryCount(context, issuer); }); + Check(sceSslGetNameEntryCount(context, issuer) == static_cast(0x8095f006), "terminated context count error mismatch"); Check(sceSslTerm_nid_postfix(other) == 0, "other context term failed"); } diff --git a/core/libs/tests/GuestSslTransport.cpp b/core/libs/tests/GuestSslTransport.cpp index 93fcb1b901..94aafaf622 100644 --- a/core/libs/tests/GuestSslTransport.cpp +++ b/core/libs/tests/GuestSslTransport.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceSsl/include/Ssl.hpp" +#include "prx/libSceNet/include/Net.hpp" #ifdef _WIN32 #include #else @@ -28,38 +31,6 @@ #include #include -extern "C" { -int APS5_VABI sceSslInit_nid_postfix(std::uint64_t); -int APS5_VABI sceSslTerm_nid_postfix(int); -int APS5_VABI sceSslGetMemoryPoolStats(int, SceSsl::MemoryPoolStats*); -int APS5_VABI sceSslLoadCert(int, int, SceSsl::Data**, SceSsl::Data*, SceSsl::Data*); -int APS5_VABI sceSslCreateConnection(int, int, const char*); -int APS5_VABI sceSslDeleteConnection(int); -int APS5_VABI sceSslSetVerifyCallback(int, int (APS5_VABI *)(int, unsigned int, void**, int, void*), void*); -int APS5_VABI sceSslConnect(int); -int APS5_VABI sceSslRead(int, void*, std::size_t, int); -int APS5_VABI sceSslWrite(int, void*, std::size_t, int); -int APS5_VABI sceSslClose(int); -int APS5_VABI sceSslEnableVerifyOption(int, std::uint32_t); -int APS5_VABI sceSslDisableVerifyOption(int, std::uint32_t); -int APS5_VABI sceSslSetMinSslVersion(int, int); -int APS5_VABI sceSslSetAlpn(int, const char*); -const char* APS5_VABI sceSslGetAlpnSelected(int); -void* APS5_VABI sceSslGetSubjectName(int, void*); -int APS5_VABI sceSslGetNameEntryCount(int, void*); -int APS5_VABI sceSslGetNameEntryInfo(int, void*, int, char*, std::size_t, char*, std::size_t, std::size_t*); -int APS5_VABI sceSslFreeSslCertName(int, void*); -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); -int APS5_VABI close_nid_postfix(int); -int APS5_VABI sceNetInit_nid_postfix(); -int APS5_VABI sceNetSocket(const char*, int, int, int); -int APS5_VABI sceNetConnect(int, const void*, std::uint32_t); -int APS5_VABI sceNetSetsockopt(int, int, int, const void*, std::uint32_t); -int APS5_VABI sceNetSocketClose(int); -} - namespace { constexpr int ParallelConnections = 4; @@ -312,6 +283,7 @@ struct Sockets { int Set(int descriptor, int option, const void* value, std::uint32_t length) const { return net ? sceNetSetsockopt(descriptor, GuestSocketLevel, option, value, length) : setsockopt_nid_postfix(descriptor, GuestSocketLevel, option, value, length); + } int Close(int descriptor) const { return net ? sceNetSocketClose(descriptor) : close_nid_postfix(descriptor); } @@ -458,9 +430,10 @@ int main(int argc, char** argv) { Require(selected && std::strcmp(selected, "http/1.1") == 0, "the negotiated ALPN protocol is not reported"); Require(sceSslWrite(connection, buffer.data(), 1, 1) == ErrorInvalidValue, "unknown write flags were accepted"); char request[] = "ping"; - Require(sceSslWrite(connection, request, 4, 0) == 4, "write the request"); + Require(sceSslSend(connection, request, 4) == 4, "send the request"); Require(sceSslRead(connection, buffer.data(), 2, 1) == 2 && std::memcmp(buffer.data(), "po", 2) == 0, "a partial read did not return the available bytes"); - Require(sceSslRead(connection, buffer.data(), buffer.size(), 0) == 2 && std::memcmp(buffer.data(), "ng", 2) == 0, "a full read did not stop at the end of the stream"); + Require(sceSslRecv(connection, buffer.data(), 2) == 2 && std::memcmp(buffer.data(), "ng", 2) == 0, "receive did not fill the remaining reply"); + Require(sceSslRecv(connection, buffer.data(), buffer.size()) == 0, "receive did not stop at the end of the stream"); Require(sceSslRead(connection, buffer.data(), buffer.size(), 1) == 0, "close notify did not end the stream"); Require(sceSslClose(connection) == 0, "close the connection"); Require(sceSslRead(connection, buffer.data(), buffer.size(), 0) == 0, "a read after the close did not return 0"); diff --git a/core/libs/tests/GuestStandardStreams.cpp b/core/libs/tests/GuestStandardStreams.cpp index c37c580e6b..f2d65cf2a4 100644 --- a/core/libs/tests/GuestStandardStreams.cpp +++ b/core/libs/tests/GuestStandardStreams.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -17,68 +18,17 @@ #include #ifdef _WIN32 #include +#include #else #include #endif extern "C" { -FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); -void APS5_VABI clearerr_unlocked_nid_postfix(FileStream*); -int APS5_VABI fileno_unlocked_nid_postfix(FileStream*); -char* APS5_VABI fgetln_nid_postfix(FileStream*, std::size_t*); -std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*); -std::size_t APS5_VABI fwrite_nid_postfix(const void*, std::size_t, std::size_t, FileStream*); -FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); -int APS5_VABI fseeko_nid_postfix(FileStream*, std::int64_t, int); -std::int64_t APS5_VABI ftello_nid_postfix(FileStream*); -int APS5_VABI fseek_nid_postfix(FileStream*, std::int64_t, int); -std::int64_t APS5_VABI ftell_nid_postfix(FileStream*); -int* APS5_VABI __error_nid_postfix(); + extern FileStream* __stdinp_nid_postfix; extern FileStream* __stdoutp_nid_postfix; extern FileStream* __stderrp_nid_postfix; extern int __isthreaded_nid_postfix; -int APS5_VABI fprintf_nid_postfix(FileStream*, const char*, ...); -int APS5_VABI fputs_nid_postfix(const char*, FileStream*); -int APS5_VABI printf_nid_postfix(const char*, ...); -int APS5_VABI vprintf_nid_postfix(const char*, void*); -int APS5_VABI puts_nid_postfix(const char*); -int APS5_VABI vfprintf_nid_postfix(FileStream*, const char*, void*); -int APS5_VABI vsprintf_nid_postfix(char*, const char*, void*); -int APS5_VABI fgetc_nid_postfix(FileStream*); -int APS5_VABI fputc_nid_postfix(int, FileStream*); -int APS5_VABI getc_unlocked_nid_postfix(FileStream*); -int APS5_VABI putc_unlocked_nid_postfix(int, FileStream*); -int APS5_VABI fputwc_nid_postfix(char16_t, FileStream*); -int APS5_VABI fputws_nid_postfix(const char16_t*, FileStream*); -std::int32_t APS5_VABI getwc_nid_postfix(FileStream*); -std::int32_t APS5_VABI getwchar_nid_postfix(); -int APS5_VABI putwc_nid_postfix(char16_t, FileStream*); -int APS5_VABI putwchar_nid_postfix(char16_t); -char16_t* APS5_VABI fgetws_nid_postfix(char16_t*, int, FileStream*); -int APS5_VABI getchar_unlocked_nid_postfix(); -int APS5_VABI putchar_unlocked_nid_postfix(int); -int APS5_VABI fscanf_nid_postfix(FileStream*, const char*, ...); -int APS5_VABI __srget_nid_postfix(FileStream*); -int APS5_VABI __swbuf_nid_postfix(int, FileStream*); -int APS5_VABI ungetc_nid_postfix(int, FileStream*); -char* APS5_VABI fgets_nid_postfix(char*, int, FileStream*); -int APS5_VABI feof_nid_postfix(FileStream*); -int APS5_VABI ferror_nid_postfix(FileStream*); -int APS5_VABI feof_unlocked_nid_postfix(FileStream*); -int APS5_VABI ferror_unlocked_nid_postfix(FileStream*); -int APS5_VABI fileno_nid_postfix(FileStream*); -void APS5_VABI clearerr_nid_postfix(FileStream*); -void APS5_VABI rewind_nid_postfix(FileStream*); -int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t); -void APS5_VABI setbuf_nid_postfix(FileStream*, char*); -FileStream* APS5_VABI fdopen_nid_postfix(int, const char*); -int APS5_VABI fclose_nid_postfix(FileStream*); -int APS5_VABI fflush_nid_postfix(FileStream*); -int APS5_VABI _Getmbcurmax_nid_postfix(); -int APS5_VABI ___mb_cur_max_nid_postfix(); -void APS5_VABI flockfile_nid_postfix(FileStream*); -void APS5_VABI funlockfile_nid_postfix(FileStream*); -int APS5_VABI ftrylockfile_nid_postfix(FileStream*); + } static void Require(bool value) { if (!value) std::abort(); } static int APS5_VABI WriteFormatted(FileStream* stream, const char* format, ...) { @@ -89,7 +39,7 @@ static int APS5_VABI WriteFormatted(FileStream* stream, const char* format, ...) std::va_list args; va_start(args, format); #endif - const int result = vfprintf_nid_postfix(stream, format, args); + const int result = vfprintf_nid_postfix(stream, format, reinterpret_cast(args)); #ifdef _WIN32 __builtin_sysv_va_end(args); #else @@ -105,7 +55,7 @@ static int APS5_VABI PrintStandard(const char* format, ...) { std::va_list args; va_start(args, format); #endif - const int result = vprintf_nid_postfix(format, args); + const int result = vprintf_nid_postfix(format, reinterpret_cast(args)); #ifdef _WIN32 __builtin_sysv_va_end(args); #else @@ -121,7 +71,7 @@ static int APS5_VABI FormatString(char* buffer, const char* format, ...) { std::va_list args; va_start(args, format); #endif - const int result = vsprintf_nid_postfix(buffer, format, args); + const int result = vsprintf_nid_postfix(buffer, format, reinterpret_cast(args)); #ifdef _WIN32 __builtin_sysv_va_end(args); #else @@ -162,6 +112,7 @@ static void CheckUnlockedStreams() { stream.SetError(); Require(putc_unlocked_nid_postfix('R', &stream) == 'R' && (guest.flags & 0x40)); clearerr_nid_postfix(&stream); + Require(!(guest.flags & 0x40)); stream.Close(); @@ -171,6 +122,7 @@ static void CheckUnlockedStreams() { Require(putc_unlocked_nid_postfix('X', &readOnly) == EOF && (readOnly.GuestState().flags & 0x40)); Require(ferror_unlocked_nid_postfix(&readOnly) == 1 && feof_unlocked_nid_postfix(&readOnly) == 0); clearerr_unlocked_nid_postfix(&readOnly); + Require(ferror_unlocked_nid_postfix(&readOnly) == 0); readOnly.Close(); Require(CheckFileBytes(filename, "A", "rb", false)); @@ -186,6 +138,7 @@ static void CheckUnlockedQueries() { stream.SetError(); Require(feof_unlocked_nid_postfix(&stream) == 1 && ferror_unlocked_nid_postfix(&stream) == 1); clearerr_unlocked_nid_postfix(&stream); + Require(feof_unlocked_nid_postfix(&stream) == 0 && ferror_unlocked_nid_postfix(&stream) == 0); Require(std::fgetc(stream.GetHandle()) == EOF && std::ungetc('Q', stream.GetHandle()) == 'Q'); Require(feof_unlocked_nid_postfix(&stream) == 0 && ferror_unlocked_nid_postfix(&stream) == 0); @@ -195,6 +148,7 @@ static void CheckUnlockedQueries() { Require(fgetc_nid_postfix(&stream) == 0x7f && feof_nid_postfix(&stream) == 0); Require(fgetc_nid_postfix(&stream) == -1 && feof_nid_postfix(&stream) != 0); clearerr_unlocked_nid_postfix(&stream); + Require(feof_nid_postfix(&stream) == 0 && ferror_nid_postfix(&stream) == 0); Require(fileno_unlocked_nid_postfix(&stream) == fileno_nid_postfix(&stream) && fileno_unlocked_nid_postfix(&stream) >= 0); Require(fileno_unlocked_nid_postfix(nullptr) == -1); @@ -219,6 +173,7 @@ static void CheckTryLock() { Require(ftrylockfile_nid_postfix(&stream) == 0); Require(tryFromOtherThread() == -1); funlockfile_nid_postfix(&stream); + Require(tryFromOtherThread() == -1); std::atomic written = false; std::thread writer([&] { @@ -228,14 +183,17 @@ static void CheckTryLock() { std::this_thread::sleep_for(std::chrono::milliseconds(50)); Require(!written && fputc_nid_postfix('A', &stream) == 'A'); funlockfile_nid_postfix(&stream); + writer.join(); Require(written && tryFromOtherThread() == 0); std::atomic held = false, release = false; std::thread owner([&] { flockfile_nid_postfix(&stream); + held = true; while (!release) std::this_thread::yield(); funlockfile_nid_postfix(&stream); + }); while (!held) std::this_thread::yield(); const int contended = ftrylockfile_nid_postfix(&stream); @@ -243,6 +201,7 @@ static void CheckTryLock() { owner.join(); Require(contended == -1 && ftrylockfile_nid_postfix(&stream) == 0); funlockfile_nid_postfix(&stream); + Require(tryFromOtherThread() == 0); Require(fseeko_nid_postfix(&stream, 0, SEEK_SET) == 0); Require(fgetc_nid_postfix(&stream) == 'A' && fgetc_nid_postfix(&stream) == 'B'); @@ -263,9 +222,11 @@ static void CheckUnlockedStreamLock(bool write, int (APS5_VABI *query)(FileStrea int result = EOF; std::thread owner([&] { flockfile_nid_postfix(&stream); + held = true; while (!release) std::this_thread::yield(); funlockfile_nid_postfix(&stream); + }); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(1); while (!held && std::chrono::steady_clock::now() < deadline) std::this_thread::yield(); @@ -301,6 +262,7 @@ static void CheckFgetsSize() { FileStream stream(std::tmpfile()); Require(fwrite_nid_postfix("line\n", 1, 5, &stream) == 5); rewind_nid_postfix(&stream); + for (const int size : {0, -1, std::numeric_limits::min()}) { char buffer[8]; std::memset(buffer, '!', sizeof(buffer)); @@ -310,6 +272,7 @@ static void CheckFgetsSize() { Require(buffer[0] == '!' && ftello_nid_postfix(&stream) == 0); Require(ferror_nid_postfix(&stream) != 0); clearerr_nid_postfix(&stream); + Require(ferror_nid_postfix(&stream) == 0); } char line[8]{}; @@ -323,14 +286,17 @@ static void CheckRewind() { Require(fwrite_nid_postfix("ab", 1, 2, &stream) == 2); *__error_nid_postfix() = 7; rewind_nid_postfix(&stream); + Require(*__error_nid_postfix() == 7 && ftello_nid_postfix(&stream) == 0); Require(fgetc_nid_postfix(&stream) == 'a' && ungetc_nid_postfix('z', &stream) == 'z'); rewind_nid_postfix(&stream); + Require(fgetc_nid_postfix(&stream) == 'a' && fgetc_nid_postfix(&stream) == 'b' && fgetc_nid_postfix(&stream) == EOF); Require(feof_nid_postfix(&stream) != 0); stream.SetError(); Require(ferror_nid_postfix(&stream) != 0); rewind_nid_postfix(&stream); + Require(feof_nid_postfix(&stream) == 0 && ferror_nid_postfix(&stream) == 0 && fgetc_nid_postfix(&stream) == 'a'); stream.Close(); int pipeEnds[2]; @@ -353,6 +319,7 @@ static void CheckRewind() { Require(fgetc_nid_postfix(&pipe) == 'q' && fgetc_nid_postfix(&pipe) == EOF && feof_nid_postfix(&pipe) != 0); *__error_nid_postfix() = 0; rewind_nid_postfix(&pipe); + Require(*__error_nid_postfix() == 29 && feof_nid_postfix(&pipe) == 0 && ferror_nid_postfix(&pipe) == 0); pipe.Close(); } @@ -572,6 +539,16 @@ static void CheckClosedStreamWrites() { Require(fseeko_nid_postfix(&stream, 0, SEEK_SET) == -1 && *__error_nid_postfix() == 9); Require(ftello_nid_postfix(&stream) == -1 && *__error_nid_postfix() == 9); Require(fileno_nid_postfix(&stream) == -1 && *__error_nid_postfix() == 9); + *__error_nid_postfix() = 0; + Require(fputwc_nid_postfix(u't', &stream) == -1 && *__error_nid_postfix() == 9); + *__error_nid_postfix() = 0; + Require(putwc_nid_postfix(u't', &stream) == -1 && *__error_nid_postfix() == 9); + *__error_nid_postfix() = 0; + Require(fputws_nid_postfix(u"text", &stream) == -1 && *__error_nid_postfix() == 9); + *__error_nid_postfix() = 0; + Require(ungetwc_nid_postfix('t', &stream) == -1 && *__error_nid_postfix() == 9); + *__error_nid_postfix() = 0; + Require(ungetwc_nid_postfix(0x100, &stream) == -1 && *__error_nid_postfix() == 9); Require(ferror_nid_postfix(&stream) == 0 && feof_nid_postfix(&stream) == 0); Require(fclose_nid_postfix(&stream) == -1 && *__error_nid_postfix() == 9); } @@ -582,6 +559,10 @@ static int CheckClosedStandardOutput() { Require(puts_nid_postfix("text") == -1 && *__error_nid_postfix() == 9); Require(PrintStandard("%d", 1) == -1 && *__error_nid_postfix() == 9); Require(fputs_nid_postfix("text", __stdoutp_nid_postfix) == -1 && *__error_nid_postfix() == 9); + *__error_nid_postfix() = 0; + Require(wprintf_nid_postfix(u"%d", 1) == -1 && *__error_nid_postfix() == 9); + *__error_nid_postfix() = 0; + Require(putwchar_nid_postfix(u't') == -1 && *__error_nid_postfix() == 9); Require(std::fputs("host output\n", stdout) >= 0 && std::fflush(stdout) == 0 && !std::ferror(stdout)); Require(freopen_nid_postfix("missing-closed-stream-dir/err.log", "a", __stderrp_nid_postfix) == nullptr); Require(std::fputs("host error\n", stderr) >= 0 && std::fflush(stderr) == 0 && !std::ferror(stderr)); @@ -623,7 +604,7 @@ int main(int argc, char** argv) { if (argc > 1 && std::string_view(argv[1]) == "--exclusive-stdout") return CheckExclusiveStandardOutput(); CheckClosedStreamWrites(); CheckDescriptorAccessModes(); - Require(_Getmbcurmax_nid_postfix() == 1 && _Getmbcurmax_nid_postfix() == ___mb_cur_max_nid_postfix()); + Require(_Getmbcurmax_nid_postfix() == 3 && ___mb_cur_max_nid_postfix() == 1); CheckFlushWhileReading(); Require(fdopen_nid_postfix(-1, "rb") == nullptr && *__error_nid_postfix() == 9); Require(fdopen_nid_postfix(0, nullptr) == nullptr && *__error_nid_postfix() == 22); @@ -641,6 +622,7 @@ int main(int argc, char** argv) { auto* wrapped = fdopen_nid_postfix(descriptor, "r+b"); Require(wrapped != nullptr && fileno_nid_postfix(wrapped) == descriptor); setbuf_nid_postfix(wrapped, nullptr); + Require(fseek_nid_postfix(wrapped, 0, SEEK_SET) == 0); char contents[32]{}; Require(fgets_nid_postfix(contents, sizeof(contents), wrapped) == contents && std::strcmp(contents, "retained") == 0); @@ -713,6 +695,7 @@ int main(int argc, char** argv) { Require(fgetc_nid_postfix(&stream) == EOF); Require(feof_nid_postfix(&stream) && (guest.flags & 0x20)); clearerr_nid_postfix(&stream); + Require(!feof_nid_postfix(&stream) && !(guest.flags & 0x20)); stream.Close(); Require(guest.flags == 0 && guest.descriptor == -1); @@ -744,17 +727,21 @@ int main(int argc, char** argv) { Require(wideBuf[0] == u'E' && wideBuf[1] == u'n' && wideBuf[2] == u'd' && wideBuf[3] == 0); Require(fgetws_nid_postfix(wideBuf, 16, &wideIo) == nullptr); Require(feof_nid_postfix(&wideIo)); - Require(fgetws_nid_postfix(wideBuf, 0, &wideIo) == nullptr && *__error_nid_postfix() == 22); - Require(ferror_nid_postfix(&wideIo)); clearerr_nid_postfix(&wideIo); + + *__error_nid_postfix() = 0; + Require(fgetws_nid_postfix(wideBuf, 0, &wideIo) == nullptr && *__error_nid_postfix() == 0); + Require(fgetws_nid_postfix(wideBuf, -1, &wideIo) == nullptr && *__error_nid_postfix() == 0); + Require(!ferror_nid_postfix(&wideIo)); Require(fgetws_nid_postfix(nullptr, 16, &wideIo) == nullptr && *__error_nid_postfix() == 22); Require(ferror_nid_postfix(&wideIo)); clearerr_nid_postfix(&wideIo); + Require(fgetws_nid_postfix(wideBuf, 16, nullptr) == nullptr && *__error_nid_postfix() == 22); wideBuf[0] = u'X'; wideBuf[1] = u'Y'; std::rewind(wideIo.GetHandle()); - Require(fgetws_nid_postfix(wideBuf, 1, &wideIo) == wideBuf && wideBuf[0] == 0 && wideBuf[1] == u'Y'); + Require(fgetws_nid_postfix(wideBuf, 1, &wideIo) == nullptr && wideBuf[0] == u'X' && wideBuf[1] == u'Y'); Require(fgetc_nid_postfix(&wideIo) == 'H'); wideIo.Close(); @@ -778,7 +765,7 @@ int main(int argc, char** argv) { Require(fputc_nid_postfix(0xff, &midErr) == 0xff); std::rewind(midErr.GetHandle()); char16_t midBuf[16]{}; - Require(fgetws_nid_postfix(midBuf, 16, &midErr) == nullptr); + Require(fgetws_nid_postfix(midBuf, 16, &midErr) == midBuf && midBuf[0] == u'A' && midBuf[1] == 0); Require(ferror_nid_postfix(&midErr)); midErr.Close(); @@ -891,6 +878,7 @@ int main(int argc, char** argv) { FileStream locked(std::tmpfile()); flockfile_nid_postfix(&locked); flockfile_nid_postfix(&locked); + std::atomic lockedWrite = false; std::thread writer([&] { Require(fputc_nid_postfix('B', &locked) == 'B'); @@ -899,9 +887,11 @@ int main(int argc, char** argv) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); Require(!lockedWrite && fputc_nid_postfix('A', &locked) == 'A'); funlockfile_nid_postfix(&locked); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); Require(!lockedWrite); funlockfile_nid_postfix(&locked); + writer.join(); Require(lockedWrite && fseeko_nid_postfix(&locked, 0, SEEK_SET) == 0); Require(fgetc_nid_postfix(&locked) == 'A' && fgetc_nid_postfix(&locked) == 'B'); @@ -920,6 +910,14 @@ int main(int argc, char** argv) { CheckStandardStreamDeleteSharing(); const std::string standardFilename = "anyps5-standard-reopen-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); Require(freopen_nid_postfix(standardFilename.c_str(), "w", __stdoutp_nid_postfix) == __stdoutp_nid_postfix); + const HANDLE hostOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); + Require(hostOutput != nullptr && hostOutput != INVALID_HANDLE_VALUE); + Require(hostOutput != reinterpret_cast(::_get_osfhandle(::_fileno(stdout)))); + DWORD handleFlags = 0; + Require(::GetHandleInformation(hostOutput, &handleFlags) != 0); + constexpr char hostMessage[] = "host stdout preserved\n"; + DWORD hostWritten = 0; + Require(::WriteFile(hostOutput, hostMessage, sizeof(hostMessage) - 1, &hostWritten, nullptr) != 0 && hostWritten == sizeof(hostMessage) - 1); Require(printf_nid_postfix("standard output") == 15 && fflush_nid_postfix(__stdoutp_nid_postfix) == 0); { std::ifstream input(standardFilename, std::ios::binary); std::string contents; std::getline(input, contents); Require(contents == "standard output"); } diff --git a/core/libs/tests/GuestStatvfs.cpp b/core/libs/tests/GuestStatvfs.cpp index 2019801dc0..ba87a280b0 100644 --- a/core/libs/tests/GuestStatvfs.cpp +++ b/core/libs/tests/GuestStatvfs.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/General.hpp" #include #include @@ -18,9 +20,6 @@ #include #endif -extern "C" int APS5_VABI statvfs_nid_postfix(const char*, void*); -extern "C" int* APS5_VABI __error_nid_postfix(); - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "statvfs check failed at line %d\n", line); @@ -33,7 +32,7 @@ static void RequireVolume(const char* path, const std::filesystem::path& native) std::array output; output.fill(0xA5A5A5A5A5A5A5A5ull); *__error_nid_postfix() = 12345; - Require(statvfs_nid_postfix(path, output.data() + 1) == 0); + Require(statvfs_nid_postfix(path, reinterpret_cast(output.data() + 1)) == 0); Require(*__error_nid_postfix() == 12345); Require(output.front() == 0xA5A5A5A5A5A5A5A5ull && output.back() == output.front()); const auto* fields = output.data() + 1; @@ -49,7 +48,7 @@ static void RequireError(const char* path, int error) { output.fill(0x5A5A5A5A5A5A5A5Aull); const auto saved = output; *__error_nid_postfix() = 0; - const int result = statvfs_nid_postfix(path, output.data()); + const int result = statvfs_nid_postfix(path, reinterpret_cast(output.data())); if (result != -1) std::fprintf(stderr, "path %s: expected failure, got %d\n", path ? path : "(null)", result); Require(result == -1); diff --git a/core/libs/tests/GuestStdExecuteOnce.cpp b/core/libs/tests/GuestStdExecuteOnce.cpp index d189ddcd71..9bead6d3e1 100644 --- a/core/libs/tests/GuestStdExecuteOnce.cpp +++ b/core/libs/tests/GuestStdExecuteOnce.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,7 +6,7 @@ #include using Callback = int (APS5_VABI *)(void*, void*, void**); -extern "C" int APS5_VABI std_execute_once_nid_postfix(int*, Callback, void*); + static void Require(bool value) { if (!value) std::abort(); } static std::atomic calls{0}, arrived{0}; @@ -53,6 +54,7 @@ int main() { try { std_execute_once_nid_postfix(&throwing, Throw, nullptr); + std::abort(); } catch (int value) { Require(value == 7 && throwing == 0); diff --git a/core/libs/tests/GuestStdThreadHardwareConcurrency.cpp b/core/libs/tests/GuestStdThreadHardwareConcurrency.cpp new file mode 100644 index 0000000000..a0e900ce98 --- /dev/null +++ b/core/libs/tests/GuestStdThreadHardwareConcurrency.cpp @@ -0,0 +1,14 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +int main() { + const unsigned result = _ZNSt6thread20hardware_concurrencyEv_nid_postfix(); + if (result != std::thread::hardware_concurrency()) { + std::fprintf(stderr, "guest processor hint %u differs from std::thread::hardware_concurrency %u\n", + result, std::thread::hardware_concurrency()); + return 1; + } + return 0; +} diff --git a/core/libs/tests/GuestStdioVariadic.cpp b/core/libs/tests/GuestStdioVariadic.cpp index 62ead8be7f..8a0f65449d 100644 --- a/core/libs/tests/GuestStdioVariadic.cpp +++ b/core/libs/tests/GuestStdioVariadic.cpp @@ -1,5 +1,7 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -9,13 +11,7 @@ extern "C" { extern FileStream* __stdinp_nid_postfix; -int APS5_VABI vfscanf_nid_postfix(FileStream*, const char*, void*); -int APS5_VABI vscanf_nid_postfix(const char*, void*); -int APS5_VABI scanf_nid_postfix(const char*, ...); -int APS5_VABI vfwprintf_nid_postfix(FileStream*, const char16_t*, void*); -int APS5_VABI fwprintf_nid_postfix(FileStream*, const char16_t*, ...); -int APS5_VABI vswscanf_nid_postfix(const char16_t*, const char16_t*, void*); -int APS5_VABI vswscanf_s_nid_postfix(const char16_t*, const char16_t*, void*); + } #ifdef _WIN32 @@ -34,39 +30,124 @@ static void Require(bool value, const char* what) { static int APS5_VABI ScanStream(FileStream* stream, const char* format, ...) { APS5_VA_BEGIN(format); - const int result = vfscanf_nid_postfix(stream, format, args); + const int result = vfscanf_nid_postfix(stream, format, reinterpret_cast(args)); APS5_VA_END(); return result; } static int APS5_VABI ScanInput(const char* format, ...) { APS5_VA_BEGIN(format); - const int result = vscanf_nid_postfix(format, args); + const int result = vscanf_nid_postfix(format, reinterpret_cast(args)); APS5_VA_END(); return result; } +static int APS5_VABI PrintSecure(FileStream* stream, const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vfprintf_s_nid_postfix(stream, format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + +static int APS5_VABI PrintSecureOutput(const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vprintf_s_nid_postfix(format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + +static int APS5_VABI ScanSecureStream(FileStream* stream, const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vfscanf_s_nid_postfix(stream, format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + +static int APS5_VABI ScanSecureInput(const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vscanf_s_nid_postfix(format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + +static int APS5_VABI PrintWideSecure(FileStream* stream, const char16_t* format, ...) { + APS5_VA_BEGIN(format); + const int result = vfwprintf_s_nid_postfix(stream, format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + +static int APS5_VABI PrintWideSecureOutput(const char16_t* format, ...) { + APS5_VA_BEGIN(format); + const int result = vwprintf_s_nid_postfix(format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + +static FileStream* SeededStream(const char* text) { + auto* stream = new FileStream(std::tmpfile(), true); + Require(std::fputs(text, stream->GetHandle()) >= 0, "seed a stream"); + std::rewind(stream->GetHandle()); + return stream; +} + +static int NextByte(FileStream* stream) { + return std::fgetc(stream->GetHandle()); +} + static int APS5_VABI PrintWide(FileStream* stream, const char16_t* format, ...) { APS5_VA_BEGIN(format); - const int result = vfwprintf_nid_postfix(stream, format, args); + const int result = vfwprintf_nid_postfix(stream, format, reinterpret_cast(args)); APS5_VA_END(); return result; } static int APS5_VABI ScanWide(const char16_t* input, const char16_t* format, ...) { APS5_VA_BEGIN(format); - const int result = vswscanf_nid_postfix(input, format, args); + const int result = vswscanf_nid_postfix(input, format, reinterpret_cast(args)); APS5_VA_END(); return result; } static int APS5_VABI ScanWideSized(const char16_t* input, const char16_t* format, ...) { APS5_VA_BEGIN(format); - const int result = vswscanf_s_nid_postfix(input, format, args); + const int result = vswscanf_s_nid_postfix(input, format, reinterpret_cast(args)); APS5_VA_END(); return result; } +static long APS5_VABI SumThroughCopy(int count, ...) { + APS5_VA_BEGIN(count); + unsigned char before[24]; + std::memcpy(before, &args, sizeof(before)); +#ifdef _WIN32 + __builtin_sysv_va_list copy; +#else + std::va_list copy; +#endif + _Vacopy_nid_postfix(reinterpret_cast(©), reinterpret_cast(&args)); + + Require(std::memcmp(©, &args, sizeof(before)) == 0, "_Vacopy copies all 24 bytes"); + long sum = 0; + for (int index = 0; index < count; ++index) sum += __builtin_va_arg(copy, long) * (index + 1); + Require(std::memcmp(&args, before, sizeof(before)) == 0, "_Vacopy leaves the source untouched"); + for (int index = 0; index < count; ++index) sum -= __builtin_va_arg(args, long); + APS5_VA_END(); + return sum; +} + +static void CheckVacopy() { + long expected = 0; + for (long index = 1; index <= 9; ++index) expected += index * 100 * index - index * 100; + Require(SumThroughCopy(9, 100L, 200L, 300L, 400L, 500L, 600L, 700L, 800L, 900L) == expected, "_Vacopy copy reads every argument"); + unsigned char overlap[32]; + for (int index = 0; index < 32; ++index) overlap[index] = static_cast(index); + _Vacopy_nid_postfix(overlap + 8, overlap); + + const unsigned char shifted[32] = {0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + Require(std::memcmp(overlap, shifted, sizeof(overlap)) == 0, "_Vacopy copies the overflow area before the offsets"); +} + static std::string Contents(FileStream& stream) { std::fflush(stream.GetHandle()); std::rewind(stream.GetHandle()); @@ -104,11 +185,111 @@ int main() { Require(Contents(written) == "7:\xc3\xa9t\xc3\xa9| 2.2", "the stream holds the formatted text"); written.Close(); + FileStream securePlain(std::tmpfile()); + FileStream secureChecked(std::tmpfile()); + FileStream secureList(std::tmpfile()); + Require(fprintf_nid_postfix(&securePlain, "%d|%s|%5.2f|%c|%ls", 42, "ab", 3.14159, 'z', u"é") == 16, "fprintf reference"); + Require(fprintf_s_nid_postfix(&secureChecked, "%d|%s|%5.2f|%c|%ls", 42, "ab", 3.14159, 'z', u"é") == 16, "fprintf_s count"); + Require(PrintSecure(&secureList, "%d|%s|%5.2f|%c|%ls", 42, "ab", 3.14159, 'z', u"é") == 16, "vfprintf_s count"); + const std::string expectedText = "42|ab| 3.14|z|\xc3\xa9"; + Require(Contents(securePlain) == expectedText, "fprintf reference bytes"); + Require(Contents(secureChecked) == expectedText, "fprintf_s writes the bytes fprintf writes"); + Require(Contents(secureList) == expectedText, "vfprintf_s writes the bytes fprintf writes"); + securePlain.Close(); + secureChecked.Close(); + secureList.Close(); + + FileStream violations(std::tmpfile()); + signed char marker = -7; + char sibling[16]; + Require(snprintf_s_nid_postfix(sibling, sizeof(sibling), "ab%hhn", &marker) == -1, "snprintf_s rejects %n"); + Require(fprintf_s_nid_postfix(&violations, "ab%hhn", &marker) == -1 && marker == -7, "fprintf_s rejects %n like snprintf_s"); + Require(PrintSecure(&violations, "ab%n", &number) == -1, "vfprintf_s rejects %n"); + Require(snprintf_s_nid_postfix(sibling, sizeof(sibling), "%s", static_cast(nullptr)) == -1, "snprintf_s rejects a null %s"); + Require(fprintf_s_nid_postfix(&violations, "x%s", static_cast(nullptr)) == -1, "fprintf_s rejects a null %s"); + Require(fprintf_s_nid_postfix(&violations, static_cast(nullptr)) == -1, "fprintf_s rejects a null format"); + Require(PrintSecure(&violations, static_cast(nullptr)) == -1, "vfprintf_s rejects a null format"); + Require(fprintf_s_nid_postfix(nullptr, "x") == -1, "fprintf_s rejects a null stream"); + Require(PrintSecure(nullptr, "x") == -1, "vfprintf_s rejects a null stream"); + Require(Contents(violations).empty(), "a rejected call writes nothing"); + Require(fprintf_s_nid_postfix(&violations, "%s", "") == 0, "fprintf_s of an empty string"); + violations.Close(); + errno = 0; + Require(fprintf_s_nid_postfix(&violations, "x") == -1 && errno == 9, "fprintf_s on a closed stream sets EBADF"); + Require(PrintSecureOutput("vprintf_s: %d\n", 42) == 14, "vprintf_s counts the bytes written to standard output"); + Require(PrintSecureOutput("%n", &number) == -1 && PrintSecureOutput(static_cast(nullptr)) == -1, "vprintf_s violations"); + + FileStream wideChecked(std::tmpfile()); + FileStream wideList(std::tmpfile()); + int widePosition = -5; + Require(fwprintf_s_nid_postfix(&wideChecked, u"%05d:%ls|", 42, u"été") == 10, "fwprintf_s counts wide characters"); + Require(PrintWideSecure(&wideList, u"%05d:%ls|", 42, u"été") == 10, "vfwprintf_s counts wide characters"); + Require(Contents(wideChecked) == "00042:\xc3\xa9t\xc3\xa9|" && Contents(wideList) == Contents(wideChecked), "wide secure output bytes"); + Require(fwprintf_s_nid_postfix(&wideChecked, u"a%n", &widePosition) == -1 && widePosition == -5, "fwprintf_s rejects %n"); + Require(PrintWideSecure(&wideChecked, u"a%ls", static_cast(nullptr)) == -1, "vfwprintf_s rejects a null %ls"); + Require(fwprintf_s_nid_postfix(&wideChecked, static_cast(nullptr)) == -1, "fwprintf_s rejects a null format"); + Require(fwprintf_s_nid_postfix(nullptr, u"x") == -1 && PrintWideSecure(nullptr, u"x") == -1, "wide secure forms reject a null stream"); + wideChecked.Close(); + wideList.Close(); + errno = 0; + Require(fwprintf_s_nid_postfix(&wideList, u"x") == -1 && errno == 9, "fwprintf_s on a closed stream sets EBADF"); + Require(wprintf_s_nid_postfix(u"wprintf_s %d\n", 5) == 12, "wprintf_s counts wide characters"); + Require(PrintWideSecureOutput(u"vwprintf_s %d\n", 6) == 13, "vwprintf_s counts wide characters"); + Require(wprintf_s_nid_postfix(u"%n", &widePosition) == -1 && PrintWideSecureOutput(static_cast(nullptr)) == -1, "wide standard output violations"); + + FileStream* sizedInput = SeededStream("17 hello Q 9 12abc key=val abcdef 4.5"); + char sizedWord[6]{}; + char sizedChar = 0; + Require(fscanf_s_nid_postfix(sizedInput, "%d %s %c", &number, sizedWord, 6u, &sizedChar, 1u) == 3, "fscanf_s assigns the sized conversions"); + Require(number == 17 && std::strcmp(sizedWord, "hello") == 0 && sizedChar == 'Q', "fscanf_s values"); + Require(ScanSecureStream(sizedInput, "%d", &number) == 1 && number == 9, "vfscanf_s continues from the stream position"); + Require(fscanf_s_nid_postfix(sizedInput, "%d", &number) == 1 && number == 12 && NextByte(sizedInput) == 'a', "fscanf_s leaves the character that ended the number"); + char key[4]{}; + char value[8]{}; + Require(fscanf_s_nid_postfix(sizedInput, "bc %3[a-z]=%7s", key, 4u, value, 8u) == 2, "fscanf_s with a set and a string"); + Require(std::strcmp(key, "key") == 0 && std::strcmp(value, "val") == 0 && NextByte(sizedInput) == ' ', "fscanf_s set and string values"); + char tooSmall[4] = {'x', 'x', 'x', 'x'}; + char siblingSmall[4] = {'x', 'x', 'x', 'x'}; + Require(sscanf_s_nid_postfix("abcdef", "%s", siblingSmall, 4u) == 0 && siblingSmall[0] == '\0', "sscanf_s reference for an undersized buffer"); + Require(fscanf_s_nid_postfix(sizedInput, "%s", tooSmall, 4u) == 0 && tooSmall[0] == '\0', "fscanf_s rejects an undersized buffer like sscanf_s"); + sizedInput->Close(); + errno = 0; + Require(fscanf_s_nid_postfix(sizedInput, "%d", &number) == EOF && errno == 9, "fscanf_s on a closed stream sets EBADF"); + delete sizedInput; + + FileStream* tailInput = SeededStream("x 3.5 7"); + int count = 0; + Require(ScanSecureStream(tailInput, "%d", &number) == 0, "vfscanf_s matching failure"); + Require(fscanf_s_nid_postfix(tailInput, " x %*f%n %d", &count, &number) == 1 && count == 5 && number == 7, "fscanf_s suppression and %n"); + Require(fscanf_s_nid_postfix(tailInput, "%d", &number) == EOF, "fscanf_s at end of file"); + tailInput->Close(); + delete tailInput; + + FileStream* nullInput = SeededStream("5 6"); + Require(fscanf_s_nid_postfix(nullInput, "%d", static_cast(nullptr)) == EOF, "fscanf_s null destination"); + Require(fscanf_s_nid_postfix(nullInput, static_cast(nullptr)) == EOF, "fscanf_s null format"); + Require(fscanf_s_nid_postfix(nullptr, "%d", &number) == EOF && ScanSecureStream(nullptr, "%d", &number) == EOF, "fscanf_s null stream"); + nullInput->Close(); + delete nullInput; + + FileStream* const savedInput = __stdinp_nid_postfix; + FileStream* redirected = SeededStream("21 word 3 more"); + __stdinp_nid_postfix = redirected; + char redirectedWord[8]{}; + int redirectedNumber = 0; + Require(scanf_s_nid_postfix("%d %7s", &number, redirectedWord, 8u) == 2 && number == 21 && std::strcmp(redirectedWord, "word") == 0, "scanf_s reads the guest standard input"); + Require(ScanSecureInput("%d %4s", &redirectedNumber, redirectedWord, 5u) == 2 && redirectedNumber == 3 && std::strcmp(redirectedWord, "more") == 0, "vscanf_s reads the guest standard input"); + Require(scanf_s_nid_postfix("%d", &number) == EOF, "scanf_s at end of input"); + __stdinp_nid_postfix = savedInput; + redirected->Close(); + delete redirected; + std::int32_t a = 0, b = 0; Require(ScanWide(u" 31 -4", u"%d%d", &a, &b) == 2 && a == 31 && b == -4, "vswscanf"); Require(ScanWide(u"", u"%d", &a) == EOF, "vswscanf on empty input"); char16_t letters[4]{}; Require(ScanWideSized(u"abcdef", u"%3ls", letters, 4u) == 1 && std::u16string(letters) == u"abc", "vswscanf_s %ls with a size"); Require(ScanWideSized(u"abcdef", u"%ls", letters, 4u) == 0 && letters[0] == 0, "vswscanf_s rejects a too small buffer"); + CheckVacopy(); return 0; } diff --git a/core/libs/tests/GuestStrftime.cpp b/core/libs/tests/GuestStrftime.cpp index eef399eda0..cf18e7c687 100644 --- a/core/libs/tests/GuestStrftime.cpp +++ b/core/libs/tests/GuestStrftime.cpp @@ -1,95 +1,253 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestTime.hpp" #include #include #include - -extern "C" { -std::size_t APS5_VABI strftime_nid_postfix(char*, std::size_t, const char*, const GuestTm*); -char* APS5_VABI asctime_nid_postfix(const GuestTm*); -} +#include namespace { -GuestTm MakeTime(int year, int hour, int wday, int yday, int isdst) { +bool correct = true; + +GuestTm MakeTime() { GuestTm time{}; - time.tm_sec = 5; - time.tm_min = 4; - time.tm_hour = hour; + time.tm_year = 126; + time.tm_mon = 9; time.tm_mday = 2; - time.tm_mon = 0; - time.tm_year = year; - time.tm_wday = wday; - time.tm_yday = yday; - time.tm_isdst = isdst; + time.tm_hour = 13; + time.tm_min = 5; + time.tm_sec = 9; + time.tm_wday = 5; + time.tm_yday = 274; time.tm_gmtoff = 19800; - time.tm_zone = "IST"; + time.tm_zone = "XYZ"; return time; } +void Check(const char* name, const GuestTm& time, const char* format, const char* expected) { + char text[256]; + const std::size_t length = strftime_nid_postfix(text, sizeof(text), format, &time); + if (length != std::strlen(expected) || std::strcmp(text, expected) != 0) { + std::fprintf(stderr, "strftime %s (\"%s\"): expected \"%s\", received \"%s\" (%zu)\n", name, format, expected, length == 0 ? "" : text, length); + correct = false; + } +} + +void CheckAsctime(const char* name, const GuestTm& time, const char* expected) { + const char* result = asctime_nid_postfix(&time); + if (std::strcmp(result, expected) != 0) { + std::fprintf(stderr, "asctime %s: expected \"%s\", received \"%s\"\n", name, expected, result); + correct = false; + } +} + } int main() { - struct Case { - const char* name; - int year; - int hour; - int wday; - int yday; - int isdst; - const char* format; + if (setenv_nid_postfix("TZ", "UTC+3", 1) != 0) return 1; + tzset_nid_postfix(); + const GuestTm base = MakeTime(); + Check("names", base, "%a|%A|%b|%B|%h", "Fri|Friday|Oct|October|Oct"); + Check("composites", base, "%c|%D|%x|%X|%T|%r|%R|%F", "Fri Oct 2 13:05:09 2026|10/02/26|10/02/26|13:05:09|13:05:09|01:05:09 PM|13:05|2026-10-02"); + Check("numbers", base, "%C|%d|%e|%g|%G|%H|%I|%j|%m|%M|%p|%S|%u|%U|%V|%w|%W|%y|%Y", "20|02| 2|26|2026|13|01|275|10|05|PM|09|5|39|40|5|39|26|2026"); + Check("whitespace and percent", base, "%n%t%%", "\n\t%"); + Check("alternative forms", base, "%Ec|%EC|%Ex|%EX|%Ey|%EY|%Od|%Oe|%OH|%OI|%Om|%OM|%OS|%Ou|%OU|%OV|%Ow|%OW|%Oy|%Ez", "Fri Oct 2 13:05:09 2026|20|10/02/26|13:05:09|26|2026|02| 2|13|01|10|05|09|5|39|40|5|39|26|-0300"); + Check("other conversions", base, "%k|%l|%P|%s|%+|%v|%Q|%q", "%k|%l|%P|%s|%+|%v|%Q|%q"); + Check("flags and widths", base, "%-d|%_d|%0e|%^a|%#b|%10Y", "%-d|%_d|%0e|%^a|%#b|%10Y"); + Check("modifier before an unknown conversion", base, "%Ea|%Oa|%EE|%OO|%Ek|%EY", "Fri|Fri|%E|%O|%k|2026"); + Check("trailing percent", base, "ab%", "ab%"); + Check("zone", base, "%z|%Z", "-0300|UTC"); + + GuestTm zone = base; + zone.tm_isdst = 1; + zone.tm_gmtoff = -1; + Check("zone in daylight time", zone, "%z", "-0200"); + zone.tm_isdst = -1; + Check("zone with unknown daylight time", zone, "%z", "-0300"); + + for (int value = -3; value <= 14; ++value) { + GuestTm time = base; + time.tm_wday = value; + time.tm_mon = value; + static const char* const expected[] = { + "Sun|Sun|Jan|Jan|00|0|0", "Sun|Sun|Jan|Jan|00|0|0", "Sun|Sun|Jan|Jan|00|0|0", "Sun|Sunday|Jan|January|01|7|0", + "Mon|Monday|Feb|February|02|1|1", "Tue|Tuesday|Mar|March|03|2|2", "Wed|Wednesday|Apr|April|04|3|3", "Thu|Thursday|May|May|05|4|4", + "Fri|Friday|Jun|June|06|5|5", "Sat|Saturday|Jul|July|07|6|6", "||Aug|August|08|7|7", "||Sep|September|09|8|8", + "||Oct|October|10|9|9", "||Nov|November|11|0|0", "||Dec|December|12|1|1", "||||13|2|2", "||||14|3|3", "||||15|4|4", + }; + char name[24]; + std::snprintf(name, sizeof(name), "index %d", value); + Check(name, time, "%a|%A|%b|%B|%m|%u|%w", expected[value + 3]); + } + + struct Field { + int value; const char* expected; }; - constexpr Case cases[] = { - {"alternative", 126, 3, 5, 1, 0, "%Ea %OX %OD %EF %EEY", "Fri 03:04:05 01/02/26 2026-01-02 EY"}, - {"padding", 126, 3, 5, 1, 0, "%-d|%_d|%0e|%-k|%_+|%--d", "2| 2|02|3|Fri Jan 2 03:04:05 IST 2026|-d"}, - {"zone", 126, 3, 5, 1, 0, "%z %Z %0z", "+0530 IST +0530"}, - {"year 899", -1001, 3, 5, 1, 0, "%Y %C %y %G %g %F", "0899 08 99 0899 99 0899-01-02"}, - {"year -1", -1901, 3, 5, 1, 0, "%Y %C %y %c", "-001 -0 01 Fri Jan 2 03:04:05 -001"}, - {"year 12345", 10445, 3, 5, 1, 0, "%Y %C %y %D %v", "12345 123 45 01/02/45 2-Jan-12345"}, - {"out of range", 126, 100, 8, 400, 0, "%I %l %u %W %U %j %a %p", "04 4 8 57 57 401 ? PM"}, - {"unknown dst", 126, 3, 5, 1, -1, "[%z][%Z]", "[][IST]"}, - {"literals", 126, 3, 5, 1, 0, "%q %% %", "q % %"}, + constexpr Field fields[] = { + {-25, "00|00|AM|00|00|00| 0|000|00|00"}, {-13, "00|00|AM|00|00|00| 0|000|00|00"}, {-12, "00|00|AM|00|00|00| 0|000|00|00"}, + {-1, "00|11|AM|00|00|00| 0|000|00|00"}, {0, "00|12|AM|00|00|00| 0|001|00|00"}, {1, "01|01|AM|01|01|01| 1|002|00|00"}, + {11, "11|11|AM|11|11|11|11|012|01|02"}, {12, "12|12|PM|12|12|12|12|013|02|02"}, {13, "13|01|PM|13|13|13|13|014|02|02"}, + {25, "25|01|PM|25|25|25|25|026|03|04"}, {99, "99|03|PM|99|99|99|99|100|14|14"}, {100, "00|04|PM|00|00|00| 0|101|14|14"}, }; - bool correct = true; - for (const auto& test : cases) { - const GuestTm time = MakeTime(test.year, test.hour, test.wday, test.yday, test.isdst); - char text[128]; - const std::size_t length = strftime_nid_postfix(text, sizeof(text), test.format, &time); - if (length != std::strlen(test.expected) || std::strcmp(text, test.expected) != 0) { - std::fprintf(stderr, "strftime %s (\"%s\"): expected \"%s\", received \"%s\" (%zu)\n", - test.name, test.format, test.expected, length == 0 ? "" : text, length); - correct = false; - } + for (const auto& field : fields) { + GuestTm time = base; + time.tm_hour = field.value; + time.tm_min = field.value; + time.tm_sec = field.value; + time.tm_mday = field.value; + time.tm_yday = field.value; + char name[24]; + std::snprintf(name, sizeof(name), "field %d", field.value); + Check(name, time, "%H|%I|%p|%M|%S|%d|%e|%j|%U|%W", field.expected); } - const GuestTm now = MakeTime(126, 3, 5, 1, 0); - char text[32]; - if (strftime_nid_postfix(text, 10, "%Y-%m-%d", &now) != 0 || strftime_nid_postfix(text, 11, "%Y-%m-%d", &now) != 10) { - std::fprintf(stderr, "strftime does not return 0 when the result and its terminator do not fit\n"); - correct = false; + + constexpr Field numbers[] = { + {-10, "00| 0|000|00|00|00"}, {9, "09| 9|010|09|09|09"}, {101, "01| 1|102|01|01|01"}, {999, "99|99|000|99|99|99"}, + {1000, "00| 0|001|00|00|00"}, {123456, "56|56|457|56|56|56"}, + }; + for (const auto& field : numbers) { + GuestTm time = base; + time.tm_hour = field.value; + time.tm_min = field.value; + time.tm_sec = field.value; + time.tm_mday = field.value; + time.tm_yday = field.value; + char name[24]; + std::snprintf(name, sizeof(name), "number %d", field.value); + Check(name, time, "%d|%e|%j|%H|%M|%S", field.expected); } - if (strftime_nid_postfix(text, sizeof(text), nullptr, &now) == 0 || std::strcmp(text, "Fri Jan 2 03:04:05 2026") != 0) { - std::fprintf(stderr, "strftime with a null format does not format %%c\n"); - correct = false; - } - struct AsctimeCase { - int year; + GuestTm leap = base; + leap.tm_sec = 61; + Check("leap second", leap, "%S|%T", "61|13:05:61"); + + struct Year { + int tmYear; const char* expected; }; - constexpr AsctimeCase asctimeCases[] = { - {126, "Fri Jan 2 03:04:05 2026\n"}, - {-1001, "Fri Jan 2 03:04:05 0899\n"}, - {-1901, "Fri Jan 2 03:04:05 -001\n"}, - {10445, "Fri Jan 2 03:04:05 12345\n"}, - {-2147483647 - 1, "Fri Jan 2 03:04:05 -2147481748\n"}, + constexpr Year years[] = { + {-14245, "0000|00|55|0000|55"}, {-11900, "0000|00|00|0000|00"}, {-2001, "0000|00|99|0000|99"}, {-1999, "0000|00|01|0000|01"}, + {-1901, "0000|00|99|0000|99"}, {-1900, "0000|00|00|0000|00"}, {-1899, "0001|01|01|0001|01"}, {-1800, "0100|01|00|0100|00"}, + {-1, "1899|19|99|1899|99"}, {0, "1900|19|00|1900|00"}, {8099, "9999|99|99|9999|99"}, {8100, "0000|00|00|0000|00"}, + {8101, "0001|00|01|0001|01"}, {10099, "1999|19|99|1999|99"}, {10445, "2345|23|45|2345|45"}, }; - for (const auto& test : asctimeCases) { - const GuestTm time = MakeTime(test.year, 3, 5, 1, 0); - const char* result = asctime_nid_postfix(&time); - if (std::strcmp(result, test.expected) != 0) { - std::fprintf(stderr, "asctime of tm_year %d: expected \"%s\", received \"%s\"\n", test.year, test.expected, result); - correct = false; - } + for (const auto& year : years) { + GuestTm time = base; + time.tm_year = year.tmYear; + char name[24]; + std::snprintf(name, sizeof(name), "year %d", year.tmYear); + Check(name, time, "%Y|%C|%y|%G|%g", year.expected); } + + struct Iso { + int year, mon, mday, wday, yday; + const char* expected; + }; + constexpr Iso weeks[] = { + {126, 0, 1, 4, 0, "2026|26|01|00|00"}, {126, 0, 4, 0, 3, "2026|26|01|01|00"}, {126, 0, 5, 1, 4, "2026|26|02|01|01"}, + {120, 11, 31, 4, 365, "2020|20|53|52|52"}, {121, 0, 3, 0, 2, "2020|20|53|01|00"}, {121, 0, 4, 1, 3, "2021|21|01|01|01"}, + {124, 11, 30, 1, 364, "2025|25|01|52|53"}, {104, 11, 31, 5, 365, "2004|04|53|52|52"}, {105, 0, 2, 0, 1, "2004|04|53|01|00"}, + {126, 0, 1, 9, 10, "2026|26|02|02|02"}, {126, 0, 1, -2, 10, "2026|26|02|01|01"}, {-1901, 0, 1, 4, 0, "0000|99|01|00|00"}, + }; + for (const auto& week : weeks) { + GuestTm time = base; + time.tm_year = week.year; + time.tm_mon = week.mon; + time.tm_mday = week.mday; + time.tm_wday = week.wday; + time.tm_yday = week.yday; + char name[48]; + std::snprintf(name, sizeof(name), "week %d/%d wday %d yday %d", week.year, week.mon, week.wday, week.yday); + Check(name, time, "%G|%g|%V|%U|%W", week.expected); + } + + constexpr Iso outOfRangeWeeks[] = { + {126, 0, 11, -2, 10, "01|01"}, {126, 1, 5, 4, 400, "57|57"}, {126, 3, 11, -19, 100, "16|16"}, {126, 9, 5, -16, 277, "40|41"}, + {126, 0, 19, -12, 18, "04|04"}, {126, 0, 1, 6, 1365, "95|95"}, {97, 0, 1, 6, 415, "59|59"}, {113, 11, 31, 1, 365, "53|53"}, + {7, 11, 31, 1, 365, "53|53"}, {126, 9, 24, 6, 296, "42|42"}, + }; + for (const auto& week : outOfRangeWeeks) { + GuestTm time = base; + time.tm_year = week.year; + time.tm_mon = week.mon; + time.tm_mday = week.mday; + time.tm_wday = week.wday; + time.tm_yday = week.yday; + char name[48]; + std::snprintf(name, sizeof(name), "week number wday %d yday %d", week.wday, week.yday); + Check(name, time, "%U|%W", week.expected); + } + + char text[8]; + std::memset(text, '#', sizeof(text)); + if (strftime_nid_postfix(text, 5, "%Y", &base) != 4 || std::strcmp(text, "2026") != 0) { + std::fprintf(stderr, "strftime does not fill a buffer that holds the result and its terminator\n"); + correct = false; + } + std::memset(text, '#', sizeof(text)); + if (strftime_nid_postfix(text, 4, "%Y", &base) != 0 || std::memcmp(text, "2026#", 5) != 0) { + std::fprintf(stderr, "strftime with no room for the terminator does not return 0 after writing the conversion\n"); + correct = false; + } + std::memset(text, '#', sizeof(text)); + if (strftime_nid_postfix(text, 5, "ab%Ycd", &base) != 0 || std::memcmp(text, "ab####", 6) != 0) { + std::fprintf(stderr, "strftime writes part of a conversion that does not fit\n"); + correct = false; + } + std::memset(text, '#', sizeof(text)); + if (strftime_nid_postfix(text, 8, "%t%D", &base) != 0 || std::memcmp(text, "\t10/02/#", 8) != 0) { + std::fprintf(stderr, "strftime does not write the parts of a composite conversion that fit\n"); + correct = false; + } + std::memset(text, '#', sizeof(text)); + if (strftime_nid_postfix(text, 2, "%X%#H", &base) != 0 || std::memcmp(text, "13#", 3) != 0) { + std::fprintf(stderr, "strftime does not stop a composite conversion at the first part that does not fit\n"); + correct = false; + } + std::memset(text, '#', sizeof(text)); + if (strftime_nid_postfix(text, 3, "abc", &base) != 0 || std::memcmp(text, "abc#", 4) != 0) { + std::fprintf(stderr, "strftime does not write the literals that fit\n"); + correct = false; + } + std::memset(text, '#', sizeof(text)); + if (strftime_nid_postfix(text, 0, "%Y", &base) != 0 || text[0] != '#') { + std::fprintf(stderr, "strftime writes to a zero-size buffer\n"); + correct = false; + } + bool threw = false; + try { + strftime_nid_postfix(text, sizeof(text), "%E", &base); + } catch (const std::invalid_argument&) { + threw = true; + } + if (!threw) { + std::fprintf(stderr, "strftime does not throw for a format ending after a modifier\n"); + correct = false; + } + + CheckAsctime("base", base, "Fri Oct 2 13:05:09 2026\n"); + GuestTm negative = base; + negative.tm_wday = -1; + negative.tm_mon = -1; + CheckAsctime("negative names", negative, "Sun Jan 2 13:05:09 2026\n"); + GuestTm past = base; + past.tm_wday = 7; + past.tm_mon = 12; + CheckAsctime("names past the tables", past, " 2 13:05:09 2026\n"); + GuestTm big = base; + big.tm_mday = 40; + big.tm_hour = 25; + big.tm_min = 61; + big.tm_sec = 61; + CheckAsctime("large fields", big, "Fri Oct 40 25:61:61 2026\n"); + big.tm_mday = 123456; + big.tm_hour = -10; + CheckAsctime("wrapped and negative fields", big, "Fri Oct 56 00:61:61 2026\n"); + GuestTm year = base; + year.tm_year = 8100; + CheckAsctime("year 10000", year, "Fri Oct 2 13:05:09 0000\n"); + year.tm_year = -1901; + CheckAsctime("year -1", year, "Fri Oct 2 13:05:09 0000\n"); return correct ? 0 : 1; } diff --git a/core/libs/tests/GuestStringCompare.cpp b/core/libs/tests/GuestStringCompare.cpp new file mode 100644 index 0000000000..9fc7691c39 --- /dev/null +++ b/core/libs/tests/GuestStringCompare.cpp @@ -0,0 +1,44 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +void Require(bool value) { + if (!value) std::abort(); +} + +} + +int main() { + Require(strcmp_nid_postfix("abc", "abc") == 0); + Require(strcmp_nid_postfix("abc", "abd") == -1); + Require(strcmp_nid_postfix("abd", "abc") == 1); + Require(strcmp_nid_postfix("a", "z") == -25); + Require(strcmp_nid_postfix("abc", "abcd") == -100); + Require(strcmp_nid_postfix("abcd", "abc") == 100); + Require(strcmp_nid_postfix("\x80", "a") == 31); + Require(strcmp_nid_postfix("", "") == 0); + + Require(strncmp_nid_postfix("abcdef", "abcxyz", 3) == 0); + Require(strncmp_nid_postfix("abcdef", "abcxyz", 4) == -20); + Require(strncmp_nid_postfix("abc", "xyz", 0) == 0); + Require(strncmp_nid_postfix("abc\0x", "abc\0y", 6) == 0); + Require(strncmp_nid_postfix("ab", "abc", 8) == -99); + + const unsigned char base[] = {0x01, 0x02, 0x03}; + const unsigned char higher[] = {0x01, 0x02, 0x04}; + const unsigned char top[] = {0x01, 0x02, 0xff}; + const unsigned char low[] = {0x01, 0x02, 0x01}; + Require(memcmp_nid_postfix(base, base, 3) == 0); + Require(memcmp_nid_postfix(base, higher, 3) == -1); + Require(memcmp_nid_postfix(top, low, 3) == 254); + Require(memcmp_nid_postfix(top, low, 2) == 0); + const unsigned char sign[] = {0x80}; + const unsigned char one[] = {0x01}; + Require(memcmp_nid_postfix(sign, one, 1) == 127); + const unsigned char withNul[] = {0x00, 0x05}; + const unsigned char withNulLow[] = {0x00, 0x02}; + Require(memcmp_nid_postfix(withNul, withNulLow, 2) == 3); +} diff --git a/core/libs/tests/GuestStrings.cpp b/core/libs/tests/GuestStrings.cpp index ef2f2f0a69..c7ee8ee32e 100644 --- a/core/libs/tests/GuestStrings.cpp +++ b/core/libs/tests/GuestStrings.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libScePosixForWebKit/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,46 +10,6 @@ #include #include -extern "C" { -void APS5_VABI bcopy_nid_postfix(const void*, void*, std::size_t); -char* APS5_VABI basename_nid_postfix(const char*); -char* APS5_VABI basename_r_nid_postfix(const char*, char*); -char* APS5_VABI __inet_ntoa_nid_postfix(unsigned int); -char* APS5_VABI dirname_nid_postfix(const char*); -int* APS5_VABI __error_nid_postfix(); -long long APS5_VABI strtonum_nid_postfix(const char*, long long, long long, const char**); -int APS5_VABI strcasecmp_nid_postfix(const char*, const char*); -int APS5_VABI strncasecmp_nid_postfix(const char*, const char*, std::size_t); -std::size_t APS5_VABI strnlen_nid_postfix(const char*, std::size_t); -std::size_t APS5_VABI strnlen_s_nid_postfix(const char*, std::size_t); -char* APS5_VABI strncat_nid_postfix(char*, const char*, std::size_t); -char* APS5_VABI strpbrk_nid_postfix(const char*, const char*); -const char* APS5_VABI strchr_nid_postfix(const char*, int); -char* APS5_VABI strrchr_nid_postfix(const char*, int); -std::size_t APS5_VABI strcspn_nid_postfix(const char*, const char*); -std::size_t APS5_VABI strlcat_nid_postfix(char*, const char*, std::size_t); -char* APS5_VABI stpcpy_nid_postfix(char*, const char*); -char* APS5_VABI strtok_r_nid_postfix(char*, const char*, char**); -char* APS5_VABI strtok_nid_postfix(char*, const char*); -char* APS5_VABI strsep_nid_postfix(char**, const char*); -char* APS5_VABI strcasestr_nid_postfix(const char*, const char*); -int APS5_VABI strcpy_s_nid_postfix(char*, std::size_t, const char*); -int APS5_VABI strncpy_s_nid_postfix(char*, std::size_t, const char*, std::size_t); -int APS5_VABI strcat_s_nid_postfix(char*, std::size_t, const char*); -int APS5_VABI strncat_s_nid_postfix(char*, std::size_t, const char*, std::size_t); -int APS5_VABI memcpy_s_nid_postfix(void*, std::size_t, const void*, std::size_t); -int APS5_VABI memmove_s_nid_postfix(void*, std::size_t, const void*, std::size_t); -int APS5_VABI memset_s_nid_postfix(void*, std::size_t, int, std::size_t); -void* APS5_VABI memccpy_nid_postfix(void*, const void*, int, std::size_t); -void* APS5_VABI memmem_nid_postfix(const void*, std::size_t, const void*, std::size_t); -char* APS5_VABI strnstr_nid_postfix(const char*, const char*, std::size_t); -std::size_t APS5_VABI strxfrm_nid_postfix(char*, const char*, std::size_t); -int APS5_VABI snprintf_s_nid_postfix(char*, std::size_t, const char*, ...); -int APS5_VABI sscanf_s_nid_postfix(const char*, const char*, ...); -int APS5_VABI __inet_aton_nid_postfix(const char*, void*); -std::uint32_t APS5_VABI __inet_addr_nid_postfix(const char*); -} - static void Require(bool condition) { if (!condition) { std::fputs("Guest string check failed\n", stderr); @@ -82,6 +44,40 @@ static bool CheckMemcpyOverlap() { return correct; } +static int Sign(int value) { + return value < 0 ? -1 : value > 0 ? 1 : 0; +} + +static void CheckStandardStrings() { + Require(strcmp_nid_postfix("abc", "abc") == 0 && Sign(strcmp_nid_postfix("abc", "abd")) == -1 && Sign(strcmp_nid_postfix("abc", "ab")) == 1); + Require(Sign(strcmp_nid_postfix("\x80", "\x7f")) == 1); + Require(strncmp_nid_postfix("abcX", "abcY", 3) == 0 && Sign(strncmp_nid_postfix("abcX", "abcY", 4)) == -1 && strncmp_nid_postfix("a", "b", 0) == 0); + Require(strcoll_nid_postfix("same", "same") == 0 && Sign(strcoll_nid_postfix("B", "a")) == -1); + Require(strlen_nid_postfix("") == 0 && strlen_nid_postfix("seven 7") == 7); + + char buffer[16]; + std::memset(buffer, 'x', sizeof(buffer)); + Require(strcpy_nid_postfix(buffer, "ab") == buffer && std::memcmp(buffer, "ab\0x", 4) == 0); + Require(strcat_nid_postfix(buffer, "cd") == buffer && std::strcmp(buffer, "abcd") == 0); + + std::memset(buffer, 'x', sizeof(buffer)); + Require(strncpy_nid_postfix(buffer, "ab", 5) == buffer && std::memcmp(buffer, "ab\0\0\0x", 6) == 0); + std::memset(buffer, 'x', sizeof(buffer)); + Require(strncpy_nid_postfix(buffer, "abcdef", 3) == buffer && std::memcmp(buffer, "abcx", 4) == 0); + + const char* text = "needle in a haystack"; + Require(strstr_nid_postfix(text, "hay") == text + 12 && strstr_nid_postfix(text, "") == text && strstr_nid_postfix(text, "hey") == nullptr); + Require(strspn_nid_postfix("aabbc", "ab") == 4 && strspn_nid_postfix("xyz", "ab") == 0 && strspn_nid_postfix("ab", "") == 0); + + unsigned char bytes[6] = {1, 2, 3, 4, 5, 6}; + const unsigned char same[6] = {1, 2, 3, 4, 5, 6}; + Require(bcmp_nid_postfix(bytes, same, 6) == 0 && bcmp_nid_postfix(bytes, "zz", 0) == 0); + bytes[5] = 9; + Require(bcmp_nid_postfix(bytes, same, 6) != 0 && bcmp_nid_postfix(bytes, same, 5) == 0); + bzero_nid_postfix(bytes + 1, 4); + Require(bytes[0] == 1 && bytes[1] == 0 && bytes[4] == 0 && bytes[5] == 9); +} + static void CheckSecureCopySizeLimits() { using Copy = int (APS5_VABI*)(void*, std::size_t, const void*, std::size_t); constexpr std::size_t RsizeMax = SIZE_MAX >> 1; @@ -131,6 +127,13 @@ static void CheckBoundsCheckedFunctions() { Require(memcpy_s_nid_postfix(bytes, 2, source, 4) == 34 && bytes[0] == 0 && bytes[1] == 0 && bytes[2] == 3); char overlap[6] = "abcde"; Require(memmove_s_nid_postfix(overlap + 1, 5, overlap, 3) == 0 && std::strcmp(overlap, "aabce") == 0); + char strOverlap[16] = "abcdef"; + Require(strncpy_s_nid_postfix(strOverlap + 1, 10, strOverlap, 3) == 22 && strOverlap[1] == '\0'); + char catOverlap[16] = "abcdef"; + Require(strncat_s_nid_postfix(catOverlap + 3, 10, catOverlap, 3) == 22 && catOverlap[3] == '\0'); + char nonOverlap[16] = {}; + std::strcpy(nonOverlap + 8, "cd"); + Require(strcpy_s_nid_postfix(nonOverlap, SIZE_MAX >> 1, nonOverlap + 8) == 0 && std::strcmp(nonOverlap, "cd") == 0); Require(memset_s_nid_postfix(bytes, sizeof(bytes), 9, 8) == 22 && bytes[0] == 9 && bytes[3] == 9); char sentinel[4] = {1, 2, 3, 4}; Require(memset_s_nid_postfix(sentinel, sizeof(sentinel), 7, 2) == 0 && sentinel[1] == 7 && sentinel[2] == 3); @@ -173,19 +176,20 @@ static void CheckSecureStringSizeLimits() { } static void CheckCaseInsensitiveComparison() { + Require(strcoll_nid_postfix("a", "z") == -25 && strcoll_nid_postfix("z", "a") == 25 && strcoll_nid_postfix("ab", "abc") == -99); + Require(strcoll_nid_postfix("A", "a") == -32 && strcoll_nid_postfix("same", "same") == 0 && strcoll_nid_postfix("\xff", "\x01") == 254); Require(strcasecmp_nid_postfix("", "") == 0 && strcasecmp_nid_postfix("AbC", "aBc") == 0); - Require(strcasecmp_nid_postfix("A", "b") == 'a' - 'b' && strcasecmp_nid_postfix("b", "A") == 'b' - 'a'); - Require(strcasecmp_nid_postfix("ab", "A") == 'b' && strcasecmp_nid_postfix("AB", "a") == 'b'); - Require(strcasecmp_nid_postfix("A", "aB") == -'b' && strcasecmp_nid_postfix("a", "AB") == -'b'); - Require(strcasecmp_nid_postfix("", "Z") == -'z' && strcasecmp_nid_postfix("Z", "") == 'z'); - Require(strncasecmp_nid_postfix("abc", "ABD", 2) == 0 && strncasecmp_nid_postfix("abc", "ABD", 3) == 'c' - 'd'); - Require(strncasecmp_nid_postfix("Ab", "a", 2) == 'b' && strncasecmp_nid_postfix("a", "AB", 5) == -'b'); - Require(strncasecmp_nid_postfix("Q", "", 1) == 'q' && strncasecmp_nid_postfix("", "Q", 1) == -'q'); + Require(strcasecmp_nid_postfix("A", "b") == -1 && strcasecmp_nid_postfix("b", "A") == 1); + Require(strcasecmp_nid_postfix("ab", "A") == 1 && strcasecmp_nid_postfix("AB", "a") == 1); + Require(strcasecmp_nid_postfix("A", "aB") == -1 && strcasecmp_nid_postfix("a", "AB") == -1); + Require(strcasecmp_nid_postfix("", "Z") == -1 && strcasecmp_nid_postfix("Z", "") == 1); + Require(strncasecmp_nid_postfix("abc", "ABD", 2) == 0 && strncasecmp_nid_postfix("abc", "ABD", 3) == -1); + Require(strncasecmp_nid_postfix("Ab", "a", 2) == 1 && strncasecmp_nid_postfix("a", "AB", 5) == -1); + Require(strncasecmp_nid_postfix("Q", "", 1) == 1 && strncasecmp_nid_postfix("", "Q", 1) == -1); Require(strncasecmp_nid_postfix("x", "y", 0) == 0 && strncasecmp_nid_postfix("same", "SAME", 99) == 0); } static void CheckSscanfS() { -#ifndef _WIN32 int number = 0; char word[4] = "zz"; char letter = 0; @@ -202,7 +206,6 @@ static void CheckSscanfS() { Require(sscanf_s_nid_postfix("7 %", "%d %%%n", &number, &position) == 1 && position == 3); Require(sscanf_s_nid_postfix(" ", "%d", &number) == EOF); Require(sscanf_s_nid_postfix("x", "%d", &number) == 0); -#endif } static bool CheckDirnameLengths() { @@ -258,6 +261,7 @@ static void CheckBcopy() { auto expected = original; for (std::size_t i = 0; i < count; ++i) expected[destination + i] = original[source + i]; bcopy_nid_postfix(actual.data() + source, actual.data() + destination, count); + Require(actual == expected); } } @@ -265,8 +269,10 @@ static void CheckBcopy() { auto destination = original; destination.fill(0xff); bcopy_nid_postfix(original.data(), destination.data(), original.size()); + Require(destination == original); bcopy_nid_postfix(nullptr, nullptr, 0); + } static void CheckStrtonum() { @@ -327,6 +333,7 @@ static void CheckCharacterConversionInSearches() { } int main() { + CheckStandardStrings(); CheckStrtonum(); CheckBcopy(); if (!CheckDirnameLengths()) return 1; diff --git a/core/libs/tests/GuestSulphaConfig.cpp b/core/libs/tests/GuestSulphaConfig.cpp new file mode 100644 index 0000000000..007930e68a --- /dev/null +++ b/core/libs/tests/GuestSulphaConfig.cpp @@ -0,0 +1,46 @@ +#include "prx/libSceSulphaDrv/libSceSulphaDrv.hpp" +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +int main() { + errno = 7; + Require(sceSulphaGetDefaultConfig(nullptr) == static_cast(0x804d0003u) && errno == 7, + "A null configuration must return the native argument error without changing errno"); + struct Config { + std::uint64_t size; + std::uint64_t capacity; + std::uint32_t setting; + 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"); + } + } + } +} diff --git a/core/libs/tests/GuestSyncOnAddress.cpp b/core/libs/tests/GuestSyncOnAddress.cpp index c43e7a66fe..e320373917 100644 --- a/core/libs/tests/GuestSyncOnAddress.cpp +++ b/core/libs/tests/GuestSyncOnAddress.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -8,17 +10,7 @@ #include #include -extern "C" { -int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); -int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); -int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name); -int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, const KernelUseconds* timeout, const char* name); -int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_t expected, const KernelUseconds* timeout, const char* name); -int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count); -} - static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); static constexpr KernelUseconds FAILSAFE_TIMEOUT = 10000000; static constexpr auto SETTLE = std::chrono::milliseconds(100); diff --git a/core/libs/tests/GuestSysarch.cpp b/core/libs/tests/GuestSysarch.cpp index f2c4551138..b6533c2e65 100644 --- a/core/libs/tests/GuestSysarch.cpp +++ b/core/libs/tests/GuestSysarch.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include @@ -6,13 +8,6 @@ #include #include -extern "C" { -int APS5_VABI sysarch(int number, void* args); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -} - static void Require(bool value) { if (!value) std::abort(); } template static std::string Thrown(TCall call) { diff --git a/core/libs/tests/GuestSyslog.cpp b/core/libs/tests/GuestSyslog.cpp index 0d55e1a440..c0a954c07a 100644 --- a/core/libs/tests/GuestSyslog.cpp +++ b/core/libs/tests/GuestSyslog.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -11,13 +12,6 @@ #include #endif -extern "C" { -void APS5_VABI syslog_nid_postfix(int, const char*, ...); -void APS5_VABI openlog_nid_postfix(const char*, int, int); -void APS5_VABI closelog_nid_postfix(); -int APS5_VABI setlogmask_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} static void Require(bool value) { if (!value) std::abort(); } int main() { auto* captured = std::tmpfile(); @@ -33,33 +27,42 @@ int main() { *__error_nid_postfix() = 2; Require(setlogmask_nid_postfix(0) == 0xff); syslog_nid_postfix(6, "station %s at %d kbps", "radio", 128); + const int afterFirst = *__error_nid_postfix(); syslog_nid_postfix(16 | 3, "open: %m, %%m kept, %d%%\n", 50); syslog_nid_postfix(0x10000 | 4, "masked"); syslog_nid_postfix(7, ""); + Require(setlogmask_nid_postfix(1 << 3) == 0xff); Require(setlogmask_nid_postfix(0) == (1 << 3)); int count = -1; syslog_nid_postfix(7, "suppressed %m%n", &count); + Require(count == -1 && *__error_nid_postfix() == 2); syslog_nid_postfix(16 | 3, "allowed: %m"); + Require(*__error_nid_postfix() == 2); syslog_nid_postfix(0x10000 | 7, "suppressed %n", &count); + Require(count == -1 && *__error_nid_postfix() == 2); Require(setlogmask_nid_postfix(1 << 4) == (1 << 3)); syslog_nid_postfix(0x10000 | 4, "warning without diagnostic"); syslog_nid_postfix(0x10000 | 7, "both suppressed %n", &count); + Require(count == -1 && *__error_nid_postfix() == 2); Require(setlogmask_nid_postfix(0x100) == (1 << 4)); Require(setlogmask_nid_postfix(0) == 0x100); syslog_nid_postfix(0x10000 | 3, "no priorities enabled %n", &count); + Require(count == -1 && *__error_nid_postfix() == 2); Require(setlogmask_nid_postfix(-1) == 0x100); Require(setlogmask_nid_postfix(0) == -1); syslog_nid_postfix(7, "negative mask"); + Require(setlogmask_nid_postfix(0x55) == -1); for (int priority = 0; priority < 8; ++priority) syslog_nid_postfix(16 | priority, "level %d", priority); + std::thread([] { Require(setlogmask_nid_postfix(0xff) == 0x55); }).join(); Require(setlogmask_nid_postfix(0) == 0xff); syslog_nid_postfix(7, "mask shared across threads"); @@ -73,6 +76,7 @@ int main() { openlog_nid_postfix("closing", 1, 0); closelog_nid_postfix(); syslog_nid_postfix(6, "identity cleared, options and facility retained"); + std::fflush(stderr); #ifdef _WIN32 Require(::_dup2(saved, ::_fileno(stderr)) == 0 && ::_close(saved) == 0); diff --git a/core/libs/tests/GuestSysmodule.cpp b/core/libs/tests/GuestSysmodule.cpp index f98548f2ce..b416232025 100644 --- a/core/libs/tests/GuestSysmodule.cpp +++ b/core/libs/tests/GuestSysmodule.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceSysmodule/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceSysmodule/ModuleTable.hpp" @@ -18,20 +20,6 @@ #include #endif -extern "C" { -int APS5_VABI sceSysmoduleIsLoaded(std::uint16_t id); -int APS5_VABI sceSysmoduleLoadModule(std::uint16_t id); -int APS5_VABI sceSysmoduleUnloadModule(std::uint16_t id); -int APS5_VABI sceSysmoduleLoadModuleInternal(std::uint32_t id); -int APS5_VABI sceSysmoduleUnloadModuleInternal(std::uint32_t id); -int APS5_VABI sceSysmoduleIsLoadedInternal(std::uint32_t id); -int APS5_VABI sceSysmoduleLoadModuleInternalWithArg(std::uint32_t id, int argc, void* argv, std::uint64_t unk, int* ret); -int APS5_VABI sceSysmoduleUnloadModuleInternalWithArg(std::uint32_t id, int argc, void* argv, std::uint64_t unk, int* ret); -int APS5_VABI sceSysmoduleGetModuleInfoForUnwind(std::uint64_t addr, int flags, ModuleInfoForUnwind* info); -int APS5_VABI sceSysmoduleGetModuleHandleInternal(std::uint32_t id, std::int32_t* handle); -int APS5_VABI sceKernelDlsym(std::int32_t handle, const char* symbol, void** addr); -} - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -110,6 +98,7 @@ void CheckUnwindInfo() { bool threw = false; try { sceSysmoduleGetModuleInfoForUnwind(reinterpret_cast(gap), 0, &info); + } catch (const std::runtime_error&) { threw = true; } diff --git a/core/libs/tests/GuestSystemCommand.cpp b/core/libs/tests/GuestSystemCommand.cpp index 23290b0822..9eb03d0240 100644 --- a/core/libs/tests/GuestSystemCommand.cpp +++ b/core/libs/tests/GuestSystemCommand.cpp @@ -1,9 +1,8 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include -extern "C" { -int APS5_VABI system_nid_postfix(const char*); -int* APS5_VABI __error_nid_postfix(); -} + static void Require(bool value) { if (!value) std::abort(); } int main() { *__error_nid_postfix() = 13; diff --git a/core/libs/tests/GuestSystemConfiguration.cpp b/core/libs/tests/GuestSystemConfiguration.cpp index c6726534a9..ec022beb6f 100644 --- a/core/libs/tests/GuestSystemConfiguration.cpp +++ b/core/libs/tests/GuestSystemConfiguration.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include @@ -5,34 +8,16 @@ #include #include #include +#include #include #include -struct SceKernelSystemSwVersion { - std::uint64_t size; - char versionString[0x1C]; - std::uint32_t version; -}; + static_assert(offsetof(SceKernelSystemSwVersion, size) == 0x00); static_assert(offsetof(SceKernelSystemSwVersion, versionString) == 0x08); static_assert(offsetof(SceKernelSystemSwVersion, version) == 0x24); static_assert(sizeof(SceKernelSystemSwVersion) == 0x28); extern "C" { -std::int64_t APS5_VABI sysconf_nid_postfix(int); -int APS5_VABI getpagesize_nid_postfix(); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI sysctl_nid_postfix(const int*, std::uint32_t, void*, std::size_t*, const void*, std::size_t); -int APS5_VABI sysctlbyname_nid_postfix(const char*, void*, std::size_t*, const void*, std::size_t); -int APS5_VABI sysctlnametomib_nid_postfix(const char*, int*, std::size_t*); -int APS5_VABI sceKernelGetSystemSwVersion_nid_postfix(SceKernelSystemSwVersion*); -int APS5_VABI getpid_nid_postfix(); -void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t); -int APS5_VABI munmap_nid_postfix(void*, std::size_t); -std::uint64_t APS5_VABI sceKernelGetTscFrequency(void); -struct GuestRlimitTest { - std::int64_t rlim_cur; - std::int64_t rlim_max; -}; -int APS5_VABI getrlimit_nid_postfix(int resource, GuestRlimitTest* limit); + extern char** environ_nid_postfix; } static void Require(bool value) { if (!value) std::abort(); } @@ -64,6 +49,7 @@ static void CheckProcessorCountSysctl() { const int unknown[] = {6, 99}; try { sysctl_nid_postfix(unknown, 2, &value, &length, nullptr, 0); + } catch (const std::runtime_error&) { threw = true; } @@ -249,6 +235,29 @@ static void CheckUnansweredSysctl() { Require(size == 4 && untouched[0] == 0x5a5a5a5a && untouched[1] == 0x5a5a5a5a && untouched[2] == 0x5a5a5a5a); } } +static void CheckUnknownSysctlName() { + static constexpr const char* name = "kern.anyps5_no_such_name"; + unsigned char buffer[16]; + std::memset(buffer, 0xaa, sizeof(buffer)); + std::size_t length = sizeof(buffer); + *__error_nid_postfix() = 13; + Require(sysctlbyname_nid_postfix(name, buffer, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 2); + Require(length == sizeof(buffer) && Untouched(buffer, sizeof(buffer))); + + length = 77; + *__error_nid_postfix() = 13; + Require(sysctlbyname_nid_postfix(name, nullptr, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 2 && length == 77); + + int mib[4] = {0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a}; + std::size_t mibLength = 4; + *__error_nid_postfix() = 13; + Require(sysctlnametomib_nid_postfix(name, mib, &mibLength) == -1 && *__error_nid_postfix() == 2); + Require(mibLength == 4 && mib[0] == 0x5a5a5a5a && mib[1] == 0x5a5a5a5a && mib[2] == 0x5a5a5a5a && mib[3] == 0x5a5a5a5a); + + mibLength = 0; + *__error_nid_postfix() = 13; + Require(sysctlnametomib_nid_postfix(name, nullptr, &mibLength) == -1 && *__error_nid_postfix() == 2 && mibLength == 0); +} static void CheckPathnameSysctl() { *__error_nid_postfix() = 13; char buffer[32] = {}; @@ -383,8 +392,60 @@ static void CheckOsReleaseDateSysctl() { length = sizeof(value); Require(sysctl_nid_postfix(deeper, 3, &value, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 20); } +struct BootTimeval { + std::int64_t tv_sec; + std::int64_t tv_usec; +}; +static_assert(sizeof(BootTimeval) == 16); + +static void CheckBootTimeSysctl() { + const int mib[] = {1, 21}; + *__error_nid_postfix() = 13; + std::size_t length = 0; + Require(sysctl_nid_postfix(mib, 2, nullptr, &length, nullptr, 0) == 0 && length == sizeof(BootTimeval)); + length = 0; + Require(sysctlbyname_nid_postfix("kern.boottime", nullptr, &length, nullptr, 0) == 0 && length == sizeof(BootTimeval)); + BootTimeval first{}; + length = sizeof(first); + Require(sysctl_nid_postfix(mib, 2, &first, &length, nullptr, 0) == 0 && length == sizeof(first)); + Require(first.tv_sec > 0 && first.tv_sec <= static_cast(std::time(nullptr))); + Require(first.tv_usec >= 0 && first.tv_usec < 1000000); + KernelTimespec now{}; + KernelTimespec uptime{}; + Require(clock_gettime_nid_postfix(5, &uptime) == 0 && clock_gettime_nid_postfix(0, &now) == 0); + const std::int64_t drift = (first.tv_sec + uptime.tv_sec - now.tv_sec) * 1000000 + first.tv_usec + uptime.tv_nsec / 1000 - now.tv_nsec / 1000; + Require(drift > -1000000 && drift < 1000000); + BootTimeval second{}; + length = sizeof(second); + Require(sysctl_nid_postfix(mib, 2, &second, &length, nullptr, 0) == 0 && length == sizeof(second)); + Require(second.tv_sec == first.tv_sec && second.tv_usec == first.tv_usec); + BootTimeval named{}; + length = 32; + Require(sysctlbyname_nid_postfix("kern.boottime", &named, &length, nullptr, 0) == 0 && length == sizeof(named)); + Require(named.tv_sec == first.tv_sec && named.tv_usec == first.tv_usec); + Require(*__error_nid_postfix() == 13); + + int namedMib[3] = {0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a}; + std::size_t mibLength = 3; + Require(sysctlnametomib_nid_postfix("kern.boottime", namedMib, &mibLength) == 0 && mibLength == 2); + Require(namedMib[0] == 1 && namedMib[1] == 21 && namedMib[2] == 0x5a5a5a5a); + + unsigned char truncated[16]; + std::memset(truncated, 0xaa, sizeof(truncated)); + length = 8; + Require(sysctl_nid_postfix(mib, 2, truncated, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 12); + Require(length == 8 && std::memcmp(truncated, &first.tv_sec, 8) == 0 && truncated[8] == 0xaa); + std::memset(truncated, 0xaa, sizeof(truncated)); + length = 8; + Require(sysctlbyname_nid_postfix("kern.boottime", truncated, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 12); + Require(length == 8 && truncated[8] == 0xaa); + + length = sizeof(first); + Require(sysctl_nid_postfix(mib, 2, &first, &length, &first, sizeof(first)) == -1 && *__error_nid_postfix() == 1); + Require(sysctlbyname_nid_postfix("kern.boottime", &first, &length, &first, sizeof(first)) == -1 && *__error_nid_postfix() == 1); +} static void CheckLeafWithTrailingComponent() { - const int leaves[][2] = {{6, 3}, {6, 2}, {6, 5}, {6, 7}, {1, 24}, {1, 37}, {1, 50}}; + const int leaves[][2] = {{6, 3}, {6, 2}, {6, 5}, {6, 7}, {1, 21}, {1, 24}, {1, 37}, {1, 50}}; for (const auto& leaf : leaves) { const int mib[] = {leaf[0], leaf[1], 0}; char buffer[8] = {}; @@ -432,12 +493,38 @@ static void CheckMeasuredAbsentSysctlMibs() { bool threw = false; try { sysctl_nid_postfix(entry.mib, entry.length, buffer, &length, buffer, sizeof(buffer)); + } catch (const std::runtime_error&) { threw = true; } Require(threw); } } +static void CheckVersionLimits() { + unsigned char first[4]; + unsigned char second[4]; + std::memset(first, 0xaa, sizeof(first)); + std::memset(second, 0xaa, sizeof(second)); + *__error_nid_postfix() = 13; + Require(sceKernelGetSystemExVersion(reinterpret_cast(first + 1), reinterpret_cast(second + 1)) == 0 && *__error_nid_postfix() == 13); + Require(first[0] == 0xaa && first[1] == 0 && first[2] == 0xaa && second[0] == 0xaa && second[1] == 0 && second[2] == 0xaa); + std::memset(first, 0xaa, sizeof(first)); + Require(sceKernelGetSystemExVersion(reinterpret_cast(first), nullptr) == 22 && first[0] == 0xaa); + Require(sceKernelGetSystemExVersion(nullptr, reinterpret_cast(first)) == 22 && first[0] == 0xaa); + Require(sceKernelGetSystemExVersion(nullptr, nullptr) == 22 && *__error_nid_postfix() == 13); + + std::memset(first, 0xaa, sizeof(first)); + Require(sysKernelGetLowerLimitSysexVersion_nid_postfix(reinterpret_cast(first + 1)) == 0); + Require(first[0] == 0xaa && first[1] == 0 && first[2] == 0xaa); + Require(sysKernelGetLowerLimitSysexVersion_nid_postfix(nullptr) == SCE_KERNEL_ERROR_EINVAL); + + struct { + std::uint32_t version; + std::uint32_t after; + } update{0xaaaaaaaau, 0xaaaaaaaau}; + Require(sysKernelGetLowerLimitUpdVersion_nid_postfix(&update.version) == 0 && update.version == 0 && update.after == 0xaaaaaaaau); + Require(sysKernelGetLowerLimitUpdVersion_nid_postfix(nullptr) == SCE_KERNEL_ERROR_EINVAL && *__error_nid_postfix() == 13); +} static void CheckSystemSwVersion() { *__error_nid_postfix() = 13; Require(sceKernelGetSystemSwVersion_nid_postfix(nullptr) == SCE_KERNEL_ERROR_EFAULT && *__error_nid_postfix() == 13); @@ -523,6 +610,7 @@ static void CheckOvercommitSysctl() { length = sizeof(value); try { sysctlbyname_nid_postfix("vm.overcommit", &value, &length, &value, sizeof(value)); + } catch (const std::runtime_error&) { threw = true; } @@ -636,6 +724,27 @@ static void CheckVmmapSysctl() { const int tooLong[] = {1, 14, 32, self, 0}; Require(sysctl_nid_postfix(tooLong, 5, nullptr, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 22); } +static void CheckEnvSysctl() { + const int self = getpid_nid_postfix(); + const int mib[] = {1, 14, 35, self}; + *__error_nid_postfix() = 13; + std::size_t length = 7; + Require(sysctl_nid_postfix(mib, 4, nullptr, &length, nullptr, 0) == 0 && length == 0); + unsigned char buffer[16]; + std::memset(buffer, 0xaa, sizeof(buffer)); + length = sizeof(buffer); + Require(sysctl_nid_postfix(mib, 4, buffer, &length, nullptr, 0) == 0 && length == 0 && buffer[0] == 0xaa); + Require(*__error_nid_postfix() == 13); + length = sizeof(buffer); + Require(sysctl_nid_postfix(mib, 4, buffer, &length, buffer, sizeof(buffer)) == -1 && *__error_nid_postfix() == 1); + const int foreign[] = {1, 14, 35, self + 1000}; + Require(sysctl_nid_postfix(foreign, 4, nullptr, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 3); + const int current[] = {1, 14, 35, -1}; + Require(sysctl_nid_postfix(current, 4, nullptr, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 3); + Require(sysctl_nid_postfix(mib, 3, nullptr, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 22); + const int tooLong[] = {1, 14, 35, self, 0}; + Require(sysctl_nid_postfix(tooLong, 5, nullptr, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 22); +} static void CheckPseudoRandomMibSysctl() { const int mib[] = {1, 50}; *__error_nid_postfix() = 13; @@ -670,6 +779,7 @@ static void CheckPseudoRandomMibSysctl() { bool threw = false; try { sysctl_nid_postfix(mib, 2, &value, &length, &value, sizeof(value)); + } catch (const std::runtime_error&) { threw = true; } @@ -688,6 +798,7 @@ static void CheckNameToMib() { {"hw.physmem", {6, 5}, 2}, {"hw.realmem", {6, 12}, 2}, {"kern.arandom", {1, 37}, 2}, + {"kern.boottime", {1, 21}, 2}, {"kern.rng_pseudo", {1, 50}, 2}, {"kern.proc.pathname", {1, 14, 12}, 3}, }; @@ -749,18 +860,18 @@ static void CheckNameToMib() { char tooLong[1025]; std::memset(tooLong, 'a', sizeof(tooLong) - 1); tooLong[sizeof(tooLong) - 1] = 0; - Require(sysctlnametomib_nid_postfix(tooLong, mib, &size) == -1 && *__error_nid_postfix() == 63); + int tooLongMib[4] = {0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a}; + std::size_t tooLongMibSize = 4; + Require(sysctlnametomib_nid_postfix(tooLong, tooLongMib, &tooLongMibSize) == -1 && *__error_nid_postfix() == 63); + Require(tooLongMibSize == 4 && tooLongMib[0] == 0x5a5a5a5a && tooLongMib[1] == 0x5a5a5a5a && tooLongMib[2] == 0x5a5a5a5a && tooLongMib[3] == 0x5a5a5a5a); tooLong[1023] = 0; + *__error_nid_postfix() = 13; + Require(sysctlnametomib_nid_postfix(tooLong, tooLongMib, &tooLongMibSize) == -1 && *__error_nid_postfix() == 2 && tooLongMibSize == 4); + Require(tooLongMib[0] == 0x5a5a5a5a && tooLongMib[1] == 0x5a5a5a5a && tooLongMib[2] == 0x5a5a5a5a && tooLongMib[3] == 0x5a5a5a5a); bool threw = false; - try { - sysctlnametomib_nid_postfix(tooLong, mib, &size); - } catch (const std::runtime_error&) { - threw = true; - } - Require(threw); - threw = false; try { sysctlnametomib_nid_postfix(nullptr, mib, &size); + } catch (const std::invalid_argument&) { threw = true; } @@ -768,17 +879,13 @@ static void CheckNameToMib() { threw = false; try { sysctlnametomib_nid_postfix("hw.ncpu", mib, nullptr); + } catch (const std::invalid_argument&) { threw = true; } Require(threw); - threw = false; - try { - sysctlnametomib_nid_postfix("kern.cp_times", mib, &size); - } catch (const std::runtime_error&) { - threw = true; - } - Require(threw); + *__error_nid_postfix() = 13; + Require(sysctlnametomib_nid_postfix("kern.anyps5_no_such_name", mib, &size) == -1 && *__error_nid_postfix() == 2); } int main() { CheckProcessorCountSysctl(); @@ -790,24 +897,28 @@ int main() { #endif CheckTscFrequencySysctl(); CheckUnansweredSysctl(); + CheckUnknownSysctlName(); CheckPathnameSysctl(); CheckSystemStringSysctl(); CheckSystemStringNames(); CheckOsReleaseDateSysctl(); + CheckBootTimeSysctl(); CheckLeafWithTrailingComponent(); CheckMeasuredAbsentSysctlMibs(); CheckSystemSwVersion(); + CheckVersionLimits(); CheckPseudoRandomSysctl(); CheckSdkVersionSysctl(); CheckOvercommitSysctl(); CheckArandomSysctl(); CheckVmmapSysctl(); + CheckEnvSysctl(); CheckPseudoRandomMibSysctl(); CheckNameToMib(); *__error_nid_postfix() = 13; Require(sysconf_nid_postfix(1) == 262144); Require(sysconf_nid_postfix(3) == 128); - GuestRlimitTest nofileLimit{}; + GuestResourceLimit nofileLimit{}; Require(getrlimit_nid_postfix(8, &nofileLimit) == 0); Require(sysconf_nid_postfix(5) == nofileLimit.rlim_cur); Require(sysconf_nid_postfix(47) == 0x4000); diff --git a/core/libs/tests/GuestSystemExit.cpp b/core/libs/tests/GuestSystemExit.cpp index 4cf3ad3931..a760b7a5b9 100644 --- a/core/libs/tests/GuestSystemExit.cpp +++ b/core/libs/tests/GuestSystemExit.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceSystemService/include/SystemService.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libSceSystemService/SystemService.hpp" @@ -22,14 +24,9 @@ #include #include #endif -extern "C" int APS5_VABI sceSystemServiceLoadExec(const char*, const char* const*); -extern "C" int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dsoHandle); -extern "C" void APS5_VABI _Exit_nid_postfix(int); -extern "C" void APS5_VABI exit_nid_postfix(int); -extern "C" void APS5_VABI catchReturnFromMain_nid_postfix(int); + using GuestQuickExitCallback = void (APS5_VABI*)(); -extern "C" int APS5_VABI at_quick_exit_nid_postfix(GuestQuickExitCallback); -extern "C" void APS5_VABI quick_exit_nid_postfix(int); + namespace { std::atomic awaiting{false}; void AwaitExit() { @@ -70,14 +67,24 @@ void WINAPI UnexpectedDetach(PVOID) { TerminateProcess(GetCurrentProcess(), 4); #endif constexpr char RelaunchMarker[] = "ANYPS5_TEST_RELAUNCH_MARKER"; constexpr char MainExecutable[] = "ANYPS5_TEST_MAIN_EXECUTABLE"; -void SetVariable(const char* name, const char* value) { +void SetVariable(const char* name, const std::filesystem::path& value) { #ifdef _WIN32 - Require(SetEnvironmentVariableA(name, value) != 0); - Require(_putenv_s(name, value) == 0); + const std::wstring wideName = std::filesystem::path(name).wstring(); + Require(SetEnvironmentVariableW(wideName.c_str(), value.c_str()) != 0); + Require(_wputenv_s(wideName.c_str(), value.c_str()) == 0); #else - Require(setenv(name, value, 1) == 0); + Require(setenv(name, value.c_str(), 1) == 0); #endif } +std::filesystem::path PathVariable(const char* name) { +#ifdef _WIN32 + const wchar_t* value = _wgetenv(std::filesystem::path(name).wstring().c_str()); +#else + const char* value = std::getenv(name); +#endif + Require(value != nullptr); + return value; +} std::filesystem::path OwnExecutable() { #ifdef _WIN32 std::wstring executable(32768, L'\0'); @@ -94,9 +101,7 @@ std::filesystem::path OwnExecutable() { #endif } void AppendMarker(const std::string& line) { - const char* marker = std::getenv(RelaunchMarker); - Require(marker != nullptr); - std::ofstream(marker, std::ios::app) << line << '\n'; + std::ofstream(PathVariable(RelaunchMarker), std::ios::app) << line << '\n'; } void MainShutdown() { AppendMarker("shutdown"); @@ -149,7 +154,7 @@ int RunChild(const std::filesystem::path& self, const char* mode) { int DriveNestedExit() { const auto self = OwnExecutable(); const auto marker = std::filesystem::temp_directory_path() / ("anyps5-nested-exit-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".txt"); - SetVariable(RelaunchMarker, marker.string().c_str()); + SetVariable(RelaunchMarker, marker); const int code = RunChild(self, "--nested-exit"); std::string text; { @@ -166,7 +171,7 @@ int DriveNestedExit() { int DriveExitOrder() { const auto self = OwnExecutable(); const auto marker = std::filesystem::temp_directory_path() / ("anyps5-exit-order-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".txt"); - SetVariable(RelaunchMarker, marker.string().c_str()); + SetVariable(RelaunchMarker, marker); const int code = RunChild(self, "--exit-order"); std::string text; { @@ -185,8 +190,8 @@ int DriveRelaunch() { const auto secondary = self.parent_path() / ("picker.picker.bin" + self.extension().string()); std::filesystem::copy_file(self, secondary, std::filesystem::copy_options::overwrite_existing); const auto marker = std::filesystem::temp_directory_path() / ("anyps5-relaunch-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".txt"); - SetVariable(RelaunchMarker, marker.string().c_str()); - SetVariable(MainExecutable, self.string().c_str()); + SetVariable(RelaunchMarker, marker); + SetVariable(MainExecutable, self); Require(RunChild(self, "--relaunch") == 0); std::string text; for (int attempt = 0; attempt < 200 && text.find("relaunched") == std::string::npos; ++attempt) { @@ -235,6 +240,7 @@ int main(int argc, char** argv) { LibcSetEndFlush_nid_no_patch(ExitFlushAfterDrain); } _Exit_nid_postfix(7); + } if (argc > firstArgument && std::strcmp(argv[firstArgument], "--requested-exit-driver") == 0) { Require(RunChild(OwnExecutable(), "--requested-exit") == 0); @@ -248,16 +254,19 @@ int main(int argc, char** argv) { Require(slot != FLS_OUT_OF_INDEXES && FlsSetValue(slot, &slot)); #endif _Exit_nid_postfix(0); + return 2; } if (argc > firstArgument && std::strcmp(argv[firstArgument], "--return-from-main") == 0) { catchReturnFromMain_nid_postfix(0); + return 2; } if (argc > firstArgument && std::strcmp(argv[firstArgument], "--quick-exit") == 0) { Require(at_quick_exit_nid_postfix(QuickFirst) == 0); Require(at_quick_exit_nid_postfix(QuickSecond) == 0); quick_exit_nid_postfix(0); + return 2; } if (argc > firstArgument && std::strcmp(argv[firstArgument], "--nested-exit-driver") == 0) { @@ -269,6 +278,7 @@ int main(int argc, char** argv) { Require(__cxa_atexit_nid_postfix(CheckShutdownStillRunning, nullptr, nullptr) == 0); Require(__cxa_atexit_nid_postfix(ExitingHandler, nullptr, nullptr) == 0); exit_nid_postfix(0); + return 2; } if (argc > firstArgument && std::strcmp(argv[firstArgument], "--exit-order-driver") == 0) { @@ -278,6 +288,7 @@ int main(int argc, char** argv) { LibcRegisterShutdown_nid_postfix(GuestExitShutdown); Require(__cxa_atexit_nid_postfix(CheckShutdownStillRunning, nullptr, nullptr) == 0); exit_nid_postfix(0); + return 2; } if (argc > firstArgument && std::strcmp(argv[firstArgument], "--relaunch-driver") == 0) { @@ -300,8 +311,7 @@ int main(int argc, char** argv) { return 2; } if (argc > firstArgument && std::strcmp(argv[firstArgument], "--relaunched") == 0) { - const char* main = std::getenv(MainExecutable); - Require(main != nullptr && std::filesystem::equivalent(OwnExecutable(), main)); + Require(std::filesystem::equivalent(OwnExecutable(), PathVariable(MainExecutable))); AppendMarker("relaunched"); return 0; } diff --git a/core/libs/tests/GuestSystemFont.cpp b/core/libs/tests/GuestSystemFont.cpp index f7f07156cf..643b4761e0 100644 --- a/core/libs/tests/GuestSystemFont.cpp +++ b/core/libs/tests/GuestSystemFont.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceFontFt/include/Api.hpp" +#include "prx/libSceFont/include/Api.hpp" #include "prx/libSceFont/include/FontTypes.hpp" #include #include @@ -17,31 +19,6 @@ #include #endif -extern "C" { -int APS5_VABI sceFontMemoryInit(FontMemory*, void*, std::uint32_t, const FontMemoryInterface*, void*, FontMemoryDestroyFunction, void*); -int APS5_VABI sceFontCreateLibrary(const FontMemory*, const void*, FontLibrary*); -int APS5_VABI sceFontDestroyLibrary(FontLibrary*); -int APS5_VABI sceFontSupportSystemFonts(FontLibrary); -int APS5_VABI sceFontSupportExternalFonts(FontLibrary, std::uint32_t, std::uint32_t); -int APS5_VABI sceFontOpenFontSet(FontLibrary, std::uint32_t, std::uint32_t, const FontOpenDetail*, FontHandle*); -int APS5_VABI sceFontOpenFontMemory(FontLibrary, const void*, std::uint32_t, const FontOpenDetail*, FontHandle*); -int APS5_VABI sceFontOpenFontInstance(FontHandle, FontHandle, FontHandle*); -int APS5_VABI sceFontCloseFont(FontHandle); -int APS5_VABI sceFontSetScalePixel(FontHandle, float, float); -int APS5_VABI sceFontGetCharGlyphMetrics(FontHandle, std::uint32_t, FontGlyphMetrics*); -int APS5_VABI sceFontGetHorizontalLayout(FontHandle, FontHorizontalLayout*); -int APS5_VABI sceFontGetKerning(FontHandle, std::uint32_t, std::uint32_t, FontKerning*); -int APS5_VABI sceFontCreateRenderer(const FontMemory*, const void*, FontRenderer*); -int APS5_VABI sceFontDestroyRenderer(FontRenderer*); -int APS5_VABI sceFontBindRenderer(FontHandle, FontRenderer); -int APS5_VABI sceFontUnbindRenderer(FontHandle); -int APS5_VABI sceFontSetupRenderScalePixel(FontHandle, float, float); -void APS5_VABI sceFontRenderSurfaceInit(FontRenderSurface*, void*, int, int, int, int); -int APS5_VABI sceFontRenderCharGlyphImageHorizontal(FontHandle, std::uint32_t, FontRenderSurface*, float, float, FontGlyphMetrics*, FontRenderOutput*); -const void* APS5_VABI sceFontSelectLibraryFt(int); -const void* APS5_VABI sceFontSelectRendererFt(int); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "System font check failed at line %d\n", line); @@ -127,9 +104,9 @@ static void WriteFile(const std::filesystem::path& path, const std::vector pixels(128 * 128); FontRenderSurface surface{}; sceFontRenderSurfaceInit(&surface, pixels.data(), 128, 1, 128, 128); + FontGlyphMetrics metrics{}; FontRenderOutput result{}; const int rc = sceFontRenderCharGlyphImageHorizontal(font, code, &surface, 10.0f, 100.0f, &metrics, &result); @@ -208,7 +186,7 @@ int main() { const std::filesystem::path root = std::filesystem::temp_directory_path() / ("anyps5_guest_system_font-" + std::to_string(std::random_device{}())); std::filesystem::remove_all(root); const std::filesystem::path empty = root / "empty"; - const std::filesystem::path fonts = root / "fonts"; + const std::filesystem::path fonts = root / std::filesystem::path(u8"fonts-a\u00e7\u00e3o-\u65e5\u672c"); std::filesystem::create_directories(empty); std::filesystem::create_directories(fonts); WriteFile(fonts / "SST-Bold.otf", SquareGlyphFont('A', 700)); @@ -239,6 +217,7 @@ int main() { bool threw = false; try { sceFontOpenFontSet(library, EuropeanBold, 1, nullptr, &font); + } catch (const std::runtime_error&) { threw = true; } @@ -338,9 +317,46 @@ int main() { Require(RenderedPixels(mixed, 0x4E00, advance) > 0 && advance == 63.0f); Require(RenderedPixels(mixed, 0x0627, advance) > 0 && advance == 66.0f); Require(RenderedPixels(mixed, 0xAC00, advance) == SCE_FONT_ERROR_NO_SUPPORT_GLYPH); + std::vector untouched(128 * 128); + FontRenderSurface measuring{}; + sceFontRenderSurfaceInit(&measuring, untouched.data(), 0, 1, 0, 31); + + FontGlyphMetrics measured{}; + FontRenderOutput measuredResult{}; + Require(sceFontRenderCharGlyphImageHorizontal(mixed, 'A', &measuring, 10.0f, 100.0f, &measured, &measuredResult) == SCE_FONT_OK); + Require(measured.Horizontal.advance == 60.0f && measured.width > 0.0f && measuredResult.ImageMetrics.advance > 0.0f); + Require(measuredResult.UpdateRect.w == 0 && measuredResult.UpdateRect.h == 0); + for (const std::uint8_t pixel : untouched) Require(pixel == 0); + sceFontRenderSurfaceInit(&measuring, untouched.data(), 128, 1, 128, 0); + + Require(sceFontRenderCharGlyphImageHorizontal(mixed, 'A', &measuring, 10.0f, 100.0f, &measured, &measuredResult) == SCE_FONT_OK && measured.Horizontal.advance == 60.0f); + sceFontRenderSurfaceInit(&measuring, untouched.data(), 128, 1, 128, 128); + + measuring.width = -1; + Require(sceFontRenderCharGlyphImageHorizontal(mixed, 'A', &measuring, 10.0f, 100.0f, &measured, &measuredResult) == SCE_FONT_ERROR_NO_SUPPORT_SURFACE); + sceFontRenderSurfaceInit(&measuring, untouched.data(), 0, 1, 128, 128); + + Require(sceFontRenderCharGlyphImageHorizontal(mixed, 'A', &measuring, 10.0f, 100.0f, &measured, &measuredResult) == SCE_FONT_ERROR_NO_SUPPORT_SURFACE); + sceFontRenderSurfaceInit(&measuring, nullptr, 0, 1, 0, 31); + + Require(sceFontRenderCharGlyphImageHorizontal(mixed, 'A', &measuring, 10.0f, 100.0f, &measured, &measuredResult) == SCE_FONT_ERROR_NO_SUPPORT_SURFACE); Require(sceFontUnbindRenderer(mixed) == SCE_FONT_OK); Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_OK); + const auto hangulFonts = root / "hangul-fonts"; + std::filesystem::create_directories(hangulFonts); + for (const auto* name : {"SST-Light.otf", "SST-Roman.otf", "SST-Bold.otf", "SSTJpPro-Regular.otf"}) { + WriteFile(hangulFonts / name, SquareGlyphFont('A', 600)); + } + WriteFile(hangulFonts / "SCEPS4Yoongd-Light.otf", SquareGlyphFont(0xAC00, 400)); + WriteFile(hangulFonts / "SCEPS4Yoongd-Medium.otf", SquareGlyphFont(0xAC00, 650)); + WriteFile(hangulFonts / "SCEPS4Yoongd-Bold.otf", SquareGlyphFont(0xAC00, 800)); + SetFontDirectory(hangulFonts); + Require(SetAdvanceOf(library, 0x1A072543u, 0xAC00) == 40.0f); + Require(SetAdvanceOf(library, 0x1A082543u, 0xAC00) == 40.0f); + Require(SetAdvanceOf(library, 0x18072444u, 0xAC00) == 65.0f); + Require(SetAdvanceOf(library, 0x18072447u, 0xAC00) == 80.0f); + for (FontHandle handle : {font, instance, substitute, memoryFont, mixed, mixedInstance}) Require(sceFontCloseFont(handle) == SCE_FONT_OK); Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); Require(allocations == 0); diff --git a/core/libs/tests/GuestSystemGesture.cpp b/core/libs/tests/GuestSystemGesture.cpp index 01d5123939..23970e8b82 100644 --- a/core/libs/tests/GuestSystemGesture.cpp +++ b/core/libs/tests/GuestSystemGesture.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSystemGesture/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -9,29 +10,6 @@ #include #include -extern "C" { -std::int32_t APS5_VABI sceSystemGestureOpen(std::int32_t, const void*); -int APS5_VABI sceSystemGestureGetPrimitiveTouchEvents(std::int32_t, void*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceSystemGestureGetPrimitiveTouchEventsCount(std::int32_t); -int APS5_VABI sceSystemGestureGetTouchEvents(std::int32_t, const void*, void*, std::uint32_t, std::uint32_t*); -int APS5_VABI sceSystemGestureGetPrimitiveTouchEventByIndex(std::int32_t, std::uint32_t, void*); -int APS5_VABI sceSystemGestureGetTouchEventByIndex(std::int32_t, const void*, std::uint32_t, void*); -int APS5_VABI sceSystemGestureGetTouchEventByEventID(std::int32_t, const void*, std::uint32_t, void*); -int APS5_VABI sceSystemGestureAppendTouchRecognizer(std::int32_t, void*); -int APS5_VABI sceSystemGestureCreateTouchRecognizer(std::int32_t, void*, std::int32_t, const void*, const void*); -int APS5_VABI sceSystemGestureGetTouchRecognizerInformation(std::int32_t, const void*, void*); -int APS5_VABI sceSystemGestureRemoveTouchRecognizer(std::int32_t, void*); -int APS5_VABI sceSystemGestureResetTouchRecognizer(std::int32_t, void*); -int APS5_VABI sceSystemGestureUpdateTouchRecognizer(std::int32_t, void*); -int APS5_VABI sceSystemGestureUpdateTouchRecognizerRectangle(std::int32_t, void*, const void*); -int APS5_VABI sceSystemGestureResetPrimitiveTouchRecognizer(std::int32_t); -int APS5_VABI sceSystemGestureUpdateAllTouchRecognizer(std::int32_t); -int APS5_VABI sceSystemGestureUpdatePrimitiveTouchRecognizer(std::int32_t, const void*); -int APS5_VABI sceSystemGestureInitializePrimitiveTouchRecognizer(const void*); -int APS5_VABI sceSystemGestureFinalizePrimitiveTouchRecognizer(void); -int APS5_VABI sceSystemGestureGetPrimitiveTouchEventByPrimitiveID(std::int32_t, std::uint16_t, void*); -} - static void Require(bool value) { if (!value) std::abort(); } static bool SameRectangle(const SystemGestureRectangle& left, const SystemGestureRectangle& right) { @@ -118,7 +96,7 @@ static SystemGesturePrimitiveTouchEvent SingleEvent(std::int32_t handle) { } static void TestPrimitiveTouchEvents(std::int32_t handle, int invalidArgument, int invalidHandle, int indexOutOfArray) { - Require(sceSystemGestureInitializePrimitiveTouchRecognizer(nullptr) == 0); + Require(sceSystemGestureInitializePrimitiveTouchRecognizer() == 0); const PadData pressed = TouchReport(1000, {{7, 480, 471}}); Require(UpdateWithReports(handle, &pressed, 1) == 0); @@ -174,12 +152,14 @@ static void TestPrimitiveTouchEvents(std::int32_t handle, int invalidArgument, i Require(byId.current_position.x == 300.0f && byId.current_position.y == 400.0f); Require(sceSystemGestureGetPrimitiveTouchEventByPrimitiveID(handle, 4, nullptr) == invalidArgument); Require(sceSystemGestureGetPrimitiveTouchEventByPrimitiveID(handle + 1, 4, &byId) == invalidHandle); - Require(Throws([&] { sceSystemGestureGetPrimitiveTouchEventByPrimitiveID(handle, 9, &byId); })); + Require(sceSystemGestureGetPrimitiveTouchEventByPrimitiveID(handle, 9, &byId) == static_cast(0x80890005)); + Require(sceSystemGestureGetPrimitiveTouchEventByIndex(handle + 1, 0, nullptr) == invalidArgument); std::array events{}; std::uint32_t reported = 7; Require(sceSystemGestureGetPrimitiveTouchEvents(handle, events.data(), 1, &reported) == 0); Require(reported == 1); + Require(sceSystemGestureGetPrimitiveTouchEvents(handle, events.data(), 0, &reported) == invalidArgument); Require(events[0].primitive_id == 3); reported = 7; Require(sceSystemGestureGetPrimitiveTouchEvents(handle, events.data(), 2, &reported) == 0); @@ -188,11 +168,14 @@ static void TestPrimitiveTouchEvents(std::int32_t handle, int invalidArgument, i Require(sceSystemGestureGetPrimitiveTouchEventByIndex(handle, 2, &event) == indexOutOfArray); Require(UpdateWithReports(handle + 1, &pair, 1) == invalidHandle); - Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(handle, nullptr) == 0); + Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(handle, nullptr) == invalidArgument); Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == 2); const std::array sequence{TouchReport(7000, {{3, 110, 200}}), TouchReport(8000, {{3, 130, 210}})}; - Require(UpdateWithReports(handle, sequence.data(), 2) == 0); + Require(UpdateWithReports(handle, sequence.data(), 2) == invalidArgument); + Require(UpdateWithReports(handle, sequence.data(), -1) == invalidArgument); + Require(UpdateWithReports(handle, sequence.data(), 1) == 0); + Require(UpdateWithReports(handle, sequence.data() + 1, 1) == 0); Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == 1); event = SingleEvent(handle); Require(event.primitive_id == 3); @@ -205,45 +188,50 @@ static void TestPrimitiveTouchEvents(std::int32_t handle, int invalidArgument, i Require(UpdateWithReports(handle, &pressed, 1) == 0); Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == 1); Require(sceSystemGestureFinalizePrimitiveTouchRecognizer() == 0); - Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == 0); + Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == static_cast(0x80890003)); + Require(sceSystemGestureInitializePrimitiveTouchRecognizer() == 0); + Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == 1); } int main() { - constexpr int invalidArgument = static_cast(0x80D10002); - constexpr int invalidHandle = static_cast(0x80D10003); + constexpr int invalidArgument = static_cast(0x80890001); + constexpr int invalidHandle = static_cast(0x80890006); + Require(sceSystemGestureOpen(0, nullptr) == static_cast(0x80890003)); + Require(sceSystemGestureInitializePrimitiveTouchRecognizer() == 0); + Require(sceSystemGestureOpen(1, nullptr) == invalidArgument); const std::int32_t handle = sceSystemGestureOpen(0, nullptr); std::array buffer; buffer.fill(0x5a); const auto original = buffer; std::uint32_t count = 7; - Require(sceSystemGestureGetPrimitiveTouchEvents(handle, buffer.data(), 4, &count) == 0); + Require(sceSystemGestureGetPrimitiveTouchEvents(handle, reinterpret_cast(buffer.data()), 4, &count) == 0); Require(count == 0); Require(buffer == original); count = 7; - Require(sceSystemGestureGetPrimitiveTouchEvents(handle, buffer.data(), 4, nullptr) == invalidArgument); - Require(sceSystemGestureGetPrimitiveTouchEvents(handle + 1, buffer.data(), 4, &count) == invalidHandle); + Require(sceSystemGestureGetPrimitiveTouchEvents(handle, reinterpret_cast(buffer.data()), 4, nullptr) == invalidArgument); + Require(sceSystemGestureGetPrimitiveTouchEvents(handle + 1, reinterpret_cast(buffer.data()), 4, &count) == invalidHandle); Require(count == 7); Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == 0); Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle + 1) == invalidHandle); - constexpr int indexOutOfArray = static_cast(0x80D10005); + constexpr int indexOutOfArray = static_cast(0x80890004); SystemGestureTouchRecognizer recognizer{}; SystemGestureRectangle rectangle{}; count = 7; - Require(sceSystemGestureGetTouchEvents(handle, &recognizer, buffer.data(), 4, &count) == 0 && count == 0); - Require(sceSystemGestureGetTouchEvents(handle, &recognizer, buffer.data(), 4, nullptr) == invalidArgument); - Require(sceSystemGestureGetTouchEvents(handle + 1, &recognizer, buffer.data(), 4, &count) == invalidHandle); - Require(sceSystemGestureGetPrimitiveTouchEventByIndex(handle, 0, buffer.data()) == indexOutOfArray); - Require(sceSystemGestureGetPrimitiveTouchEventByIndex(handle + 1, 0, buffer.data()) == invalidHandle); - Require(sceSystemGestureGetTouchEventByIndex(handle, &recognizer, 0, buffer.data()) == indexOutOfArray); - Require(sceSystemGestureGetTouchEventByIndex(handle + 1, &recognizer, 0, buffer.data()) == invalidHandle); - Require(sceSystemGestureGetTouchEventByIndex(handle, nullptr, 0, buffer.data()) == invalidArgument); - Require(sceSystemGestureGetTouchEventByEventID(handle, &recognizer, 0, buffer.data()) == indexOutOfArray); - Require(sceSystemGestureGetTouchEventByEventID(handle + 1, &recognizer, 0, buffer.data()) == invalidHandle); - Require(sceSystemGestureGetTouchEventByEventID(handle, nullptr, 0, buffer.data()) == invalidArgument); + Require(sceSystemGestureGetTouchEvents(handle, &recognizer, reinterpret_cast(buffer.data()), 4, &count) == 0 && count == 0); + Require(sceSystemGestureGetTouchEvents(handle, &recognizer, reinterpret_cast(buffer.data()), 4, nullptr) == invalidArgument); + Require(sceSystemGestureGetTouchEvents(handle + 1, &recognizer, reinterpret_cast(buffer.data()), 4, &count) == invalidHandle); + Require(sceSystemGestureGetPrimitiveTouchEventByIndex(handle, 0, reinterpret_cast(buffer.data())) == indexOutOfArray); + Require(sceSystemGestureGetPrimitiveTouchEventByIndex(handle + 1, 0, reinterpret_cast(buffer.data())) == invalidHandle); + Require(sceSystemGestureGetTouchEventByIndex(handle, &recognizer, 0, reinterpret_cast(buffer.data())) == indexOutOfArray); + Require(sceSystemGestureGetTouchEventByIndex(handle + 1, &recognizer, 0, reinterpret_cast(buffer.data())) == invalidHandle); + Require(sceSystemGestureGetTouchEventByIndex(handle, nullptr, 0, reinterpret_cast(buffer.data())) == invalidArgument); + Require(sceSystemGestureGetTouchEventByEventID(handle, &recognizer, 0, reinterpret_cast(buffer.data())) == indexOutOfArray); + Require(sceSystemGestureGetTouchEventByEventID(handle + 1, &recognizer, 0, reinterpret_cast(buffer.data())) == invalidHandle); + Require(sceSystemGestureGetTouchEventByEventID(handle, nullptr, 0, reinterpret_cast(buffer.data())) == invalidArgument); Require(buffer == original); Require(sceSystemGestureAppendTouchRecognizer(handle, &recognizer) == 0); @@ -253,7 +241,7 @@ int main() { Require(sceSystemGestureUpdateTouchRecognizerRectangle(handle, &recognizer, &rectangle) == 0); Require(sceSystemGestureResetPrimitiveTouchRecognizer(handle) == 0); Require(sceSystemGestureUpdateAllTouchRecognizer(handle) == 0); - Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(handle, nullptr) == 0); + Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(handle, nullptr) == invalidArgument); Require(sceSystemGestureAppendTouchRecognizer(handle, nullptr) == invalidArgument); Require(sceSystemGestureUpdateTouchRecognizerRectangle(handle, &recognizer, nullptr) == invalidArgument); Require(sceSystemGestureAppendTouchRecognizer(handle + 1, &recognizer) == invalidHandle); @@ -267,9 +255,27 @@ int main() { Require(sceSystemGestureUpdateTouchRecognizerRectangle(handle + 1, &recognizer, &rectangle) == invalidHandle); Require(sceSystemGestureResetPrimitiveTouchRecognizer(handle + 1) == invalidHandle); Require(sceSystemGestureUpdateAllTouchRecognizer(handle + 1) == invalidHandle); - Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(handle + 1, nullptr) == invalidHandle); + Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(handle + 1, nullptr) == invalidArgument); + Require(sceSystemGestureDebugGetVersion() == 0x6a00); TestTouchRecognizerInformation(handle, invalidArgument, invalidHandle); TestPrimitiveTouchEvents(handle, invalidArgument, invalidHandle, indexOutOfArray); + std::array otherHandles{}; + for (auto& otherHandle : otherHandles) { + otherHandle = sceSystemGestureOpen(0, nullptr); + Require(otherHandle > 0 && otherHandle != handle); + Require(sceSystemGestureGetPrimitiveTouchEventsCount(otherHandle) == 0); + } + Require(sceSystemGestureOpen(0, nullptr) == static_cast(0x8089000A)); + Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == 1); + Require(sceSystemGestureClose(otherHandles[0]) == 0); + Require(sceSystemGestureClose(otherHandles[0]) == invalidHandle); + const auto replacement = sceSystemGestureOpen(0, nullptr); + Require(replacement > 0 && replacement != otherHandles[0]); + Require(sceSystemGestureGetPrimitiveTouchEventsCount(otherHandles[0]) == invalidHandle); + Require(sceSystemGestureClose(replacement) == 0); + for (std::size_t index = 1; index < otherHandles.size(); ++index) Require(sceSystemGestureClose(otherHandles[index]) == 0); + Require(sceSystemGestureClose(handle) == 0); + Require(sceSystemGestureFinalizePrimitiveTouchRecognizer() == 0); return 0; } diff --git a/core/libs/tests/GuestSystemKillApp.cpp b/core/libs/tests/GuestSystemKillApp.cpp index 3ebfe28013..b42f9d8de6 100644 --- a/core/libs/tests/GuestSystemKillApp.cpp +++ b/core/libs/tests/GuestSystemKillApp.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/Shutdown.hpp" #include @@ -5,10 +6,7 @@ #include #include #include -extern "C" int APS5_VABI sceSystemServiceGetAppIdOfRunningBigApp(void); -extern "C" int APS5_VABI sceSystemServiceKillApp(int, int, int, int); -extern "C" int APS5_VABI sceLncUtilKillApp(unsigned); -extern "C" int APS5_VABI sceLncUtilKillAppWithReason(unsigned, int); + namespace { bool cleaned = false; bool finished = false; diff --git a/core/libs/tests/GuestSystemService.cpp b/core/libs/tests/GuestSystemService.cpp index 113cb6aa2d..8a197d97b5 100644 --- a/core/libs/tests/GuestSystemService.cpp +++ b/core/libs/tests/GuestSystemService.cpp @@ -1,16 +1,13 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" #include "SceTypes.hpp" #include "prx/libSceSystemService/SystemService.hpp" #include #include #include +#include +#include #include -extern "C" int APS5_VABI sceSystemServiceGetHdrToneMapLuminance(SystemServiceHdrToneMapLuminance* luminance); -extern "C" int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, std::size_t bufSize); -extern "C" int APS5_VABI sceSystemServiceLaunchApp(const char* titleId, const char* const* arguments, const void* param); -extern "C" int APS5_VABI sceSystemServiceAddLocalProcessForPsmKit(const char* path, const char* const* arguments, const void* param); -extern "C" int APS5_VABI sceSystemServiceKillLocalProcessForPsmKit(int handle); - namespace { constexpr int browserUnavailable = static_cast(0x8002002Du); @@ -38,25 +35,6 @@ bool LaunchAppThrows(const char* titleId, const char* const* arguments, const vo } -extern "C" int APS5_VABI sceSystemServicePowerTick(void); -extern "C" int APS5_VABI sceSystemServiceReportAbnormalTermination(const void* info); -extern "C" int APS5_VABI sceSystemServiceDisableMusicPlayer(void); -extern "C" int APS5_VABI sceSystemServiceReenableMusicPlayer(void); -extern "C" int APS5_VABI sceSystemServiceDisableMediaPlay(void); -extern "C" int APS5_VABI sceSystemServiceReenableMediaPlay(void); -extern "C" int APS5_VABI sceSystemServiceLaunchWebBrowser(const char* uri, void* param); -extern "C" int APS5_VABI sceSystemServiceShowControllerSettings(void); -extern "C" int APS5_VABI sceSystemServiceShowDisplaySafeAreaSettings(void); -extern "C" int APS5_VABI sceSystemServiceOpenChallengeActivity(void); -extern "C" int APS5_VABI sceSystemServiceGetStatus(SystemServiceStatus* status); -extern "C" int APS5_VABI sceSystemServiceGetDisplaySafeAreaInfo(SystemServiceDisplaySafeAreaInfo* info); -extern "C" int APS5_VABI sceSystemServiceGetNoticeScreenSkipFlag(bool* value); -extern "C" int APS5_VABI sceSystemServiceSetNoticeScreenSkipFlag(void); -extern "C" int APS5_VABI sceSystemServiceDisableNoticeScreenSkipFlagAutoSet(void); -extern "C" int APS5_VABI sceSystemServiceHideSplashScreen(void); -extern "C" int APS5_VABI sceSystemServiceReceiveEvent(SystemServiceEvent* event); -extern "C" void APS5_VABI sceSystemServiceInitializePlayerDialogParam(SystemServicePlayerDialogParam* param); - namespace { void TestPlayerDialogParam() { @@ -87,6 +65,7 @@ int main() { Require(sceSystemServiceDisableMusicPlayer() == SYSTEM_SERVICE_OK); Require(sceSystemServiceDisableMusicPlayer() == SYSTEM_SERVICE_OK); Require(sceSystemServiceReenableMusicPlayer() == SYSTEM_SERVICE_OK); + Require(sceSystemServiceIsBgmPlaying() == 0 && sceShellCoreUtilIsBgmPlaying() == 0); Require(sceSystemServiceReenableMusicPlayer() == SYSTEM_SERVICE_OK); Require(sceSystemServiceDisableMediaPlay() == SYSTEM_SERVICE_OK); Require(sceSystemServiceDisableMediaPlay() == SYSTEM_SERVICE_OK); @@ -95,13 +74,16 @@ int main() { Require(sceSystemServiceShowControllerSettings() == SYSTEM_SERVICE_OK); Require(sceSystemServiceShowDisplaySafeAreaSettings() == SYSTEM_SERVICE_OK); Require(sceSystemServiceOpenChallengeActivity() == SYSTEM_SERVICE_OK); - Require(sceSystemServiceLaunchWebBrowser("http://127.0.0.1:8780/video?v=0", nullptr) == browserUnavailable); - unsigned char browserParam[64]; - std::memset(browserParam, 0x5a, sizeof(browserParam)); - Require(sceSystemServiceLaunchWebBrowser("https://example.com/", browserParam) == browserUnavailable); - for (unsigned char byte : browserParam) Require(byte == 0x5a); - Require(sceSystemServiceLaunchWebBrowser("", nullptr) == browserUnavailable); - Require(sceSystemServiceLaunchWebBrowser(nullptr, nullptr) == browserUnavailable); + Require(sceSystemServiceLaunchWebBrowser("http:") == browserUnavailable); + Require(sceSystemServiceLaunchWebBrowser("https:") == browserUnavailable); + Require(sceSystemServiceLaunchWebBrowser("psno:") == browserUnavailable); + Require(sceSystemServiceLaunchWebBrowser("") == static_cast(0x80a10008)); + Require(sceSystemServiceLaunchWebBrowser("HTTP:") == static_cast(0x80a10008)); + Require(sceSystemServiceLaunchWebBrowser(nullptr) == static_cast(0x80a10003)); + std::string browserUri = "http:" + std::string(6010, 'a'); + Require(sceSystemServiceLaunchWebBrowser(browserUri.c_str()) == browserUnavailable); + browserUri += 'a'; + Require(sceSystemServiceLaunchWebBrowser(browserUri.c_str()) == static_cast(0x80a10007)); Require(LaunchAppThrows("PPSA99109", nullptr, nullptr)); Require(LaunchAppThrows(nullptr, nullptr, nullptr)); const char* launchArguments[] = {"--backup", nullptr}; diff --git a/core/libs/tests/GuestSystemServiceAppTitleId.cpp b/core/libs/tests/GuestSystemServiceAppTitleId.cpp new file mode 100644 index 0000000000..7fee52ec8e --- /dev/null +++ b/core/libs/tests/GuestSystemServiceAppTitleId.cpp @@ -0,0 +1,132 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr char Sentinel = 0x5A; + +struct GuardedBuffer { + char bytes[10 + 6]; + GuardedBuffer() { std::memset(bytes, Sentinel, sizeof(bytes)); } + bool Untouched() const { + for (const char byte : bytes) { + if (byte != Sentinel) return false; + } + return true; + } + bool GuardIntact() const { + for (std::size_t index = 10; index < sizeof(bytes); ++index) { + if (bytes[index] != Sentinel) return false; + } + return true; + } +}; + +void Require(bool value) { if (!value) std::abort(); } + +template +bool Throws(TCall call) { + try { + call(); + } catch (const std::exception&) { + return true; + } + return false; +} + +bool GetAppIdThrows(const char* titleId) { + return Throws([&] { static_cast(sceSystemServiceGetAppId(titleId)); }); +} + +bool GetAppTitleIdThrows(int appId, GuardedBuffer& buffer) { + return Throws([&] { static_cast(sceSystemServiceGetAppTitleId(appId, buffer.bytes)); }) && buffer.Untouched(); +} + +bool LncGetAppTitleIdThrows(std::uint32_t appId, GuardedBuffer& buffer) { + return Throws([&] { static_cast(sceLncUtilGetAppTitleId(static_cast(appId), buffer.bytes)); }) && buffer.Untouched(); +} + +void CheckOtherApplications(int runningAppId) { + const int otherAppIds[] = {runningAppId + 1, runningAppId - 1, 0, -1, std::numeric_limits::min(), std::numeric_limits::max()}; + for (const int appId : otherAppIds) { + GuardedBuffer buffer; + Require(GetAppTitleIdThrows(appId, buffer)); + Require(LncGetAppTitleIdThrows(static_cast(appId), buffer)); + } + GuardedBuffer buffer; + Require(LncGetAppTitleIdThrows(static_cast(runningAppId) | 0x80000000u, buffer)); + Require(LncGetAppTitleIdThrows(std::numeric_limits::max(), buffer)); +} + +void CheckNullBuffers(int runningAppId) { + Require(Throws([&] { static_cast(sceSystemServiceGetAppTitleId(runningAppId, nullptr)); })); + Require(Throws([&] { static_cast(sceLncUtilGetAppTitleId(runningAppId, nullptr)); })); + Require(Throws([&] { static_cast(sceSystemServiceGetAppTitleId(runningAppId + 1, nullptr)); })); + Require(GetAppIdThrows(nullptr)); +} + +void CheckKnownTitle(int runningAppId) { + constexpr std::string_view titleId = "PPSA00000"; + Require(runningAppId >= 0); + Require(sceSystemServiceGetAppId("PPSA00000") == runningAppId); + Require(sceSystemServiceGetAppId("PPSA00000") == runningAppId); + const char* const otherTitles[] = {"PPSA00001", "PPSA00010", "ppsa00000", "PPSA0000", "PPSA000000", "PPSA00000 ", " PPSA00000", "CUSA00000", ""}; + for (const char* other : otherTitles) { + Require(GetAppIdThrows(other)); + } + + GuardedBuffer buffer; + Require(sceSystemServiceGetAppTitleId(runningAppId, buffer.bytes) == 0); + Require(std::memcmp(buffer.bytes, "PPSA00000", titleId.size() + 1) == 0); + Require(buffer.GuardIntact()); + Require(sceSystemServiceGetAppId(buffer.bytes) == runningAppId); + + GuardedBuffer lncBuffer; + Require(sceLncUtilGetAppTitleId(runningAppId, lncBuffer.bytes) == 0); + Require(std::memcmp(lncBuffer.bytes, "PPSA00000", titleId.size() + 1) == 0); + Require(lncBuffer.GuardIntact()); + + GuardedBuffer dirty; + std::memset(dirty.bytes, 'x', 10); + Require(sceSystemServiceGetAppTitleId(runningAppId, dirty.bytes) == 0); + Require(std::strcmp(dirty.bytes, "PPSA00000") == 0 && dirty.GuardIntact()); + + CheckOtherApplications(runningAppId); + CheckNullBuffers(runningAppId); +} + +void CheckLongTitle(int runningAppId) { + Require(sceSystemServiceGetAppId("PPSA000000") == runningAppId); + Require(GetAppIdThrows("PPSA00000")); + Require(GetAppIdThrows("PPSA0000000")); + GuardedBuffer buffer; + Require(GetAppTitleIdThrows(runningAppId, buffer)); + Require(LncGetAppTitleIdThrows(static_cast(runningAppId), buffer)); +} + +void CheckMissingMetadata(int runningAppId) { + Require(GetAppIdThrows("PPSA00000")); + Require(GetAppIdThrows(nullptr)); + GuardedBuffer buffer; + Require(GetAppTitleIdThrows(runningAppId, buffer)); + Require(LncGetAppTitleIdThrows(static_cast(runningAppId), buffer)); +} + +} + +int main(int argc, char** argv) { + Require(argc == 2); + const std::string_view mode = argv[1]; + const int runningAppId = sceSystemServiceGetAppIdOfRunningBigApp(); + if (mode == "known") CheckKnownTitle(runningAppId); + else if (mode == "long") CheckLongTitle(runningAppId); + else if (mode == "missing") CheckMissingMetadata(runningAppId); + else Require(false); +} diff --git a/core/libs/tests/GuestSystemServiceLanguage.cpp b/core/libs/tests/GuestSystemServiceLanguage.cpp index efd37f499b..8e1aefdf3a 100644 --- a/core/libs/tests/GuestSystemServiceLanguage.cpp +++ b/core/libs/tests/GuestSystemServiceLanguage.cpp @@ -1,10 +1,11 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" #include "SceTypes.hpp" #include "prx/libSceSystemService/SystemService.hpp" #include +#include #include #include - -extern "C" int APS5_VABI sceSystemServiceParamGetInt(int paramId, int* value); +#include namespace { @@ -37,6 +38,11 @@ bool ParameterRejected(int paramId) { return false; } +bool Answers(int paramId) { + return (paramId >= 1 && paramId <= 5) || (paramId >= SYSTEM_SERVICE_PARAM_ID_100 && paramId <= 111) || (paramId >= 200 && paramId <= 213) || + paramId == 300 || paramId == SYSTEM_SERVICE_PARAM_ID_400; +} + bool Rejected(const char* value) { SetLanguage(value); return ParameterRejected(SYSTEM_SERVICE_PARAM_ID_LANG) && ParameterRejected(SYSTEM_SERVICE_PARAM_ID_400); @@ -45,6 +51,18 @@ bool Rejected(const char* value) { } int main() { + for (int paramId = -1; paramId <= 2048; ++paramId) { + Require(sceSystemServiceParamGetInt(paramId, nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER); + if (Answers(paramId)) continue; + int value = -12345; + Require(sceSystemServiceParamGetInt(paramId, &value) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(value == -12345); + } + for (int paramId : {std::numeric_limits::min(), std::numeric_limits::max()}) { + int value = -12345; + Require(sceSystemServiceParamGetInt(paramId, &value) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(value == -12345); + } SetLanguage(nullptr); Require(Language() == SYSTEM_SERVICE_PARAM_LANG_ENGLISH_US); SetLanguage(""); @@ -62,7 +80,23 @@ int main() { SetLanguage("arabic"); Require(sceSystemServiceParamGetInt(SYSTEM_SERVICE_PARAM_ID_LANG, nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER); Require(sceSystemServiceParamGetInt(SYSTEM_SERVICE_PARAM_ID_400, nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER); - int enterButton = -1; - Require(sceSystemServiceParamGetInt(SYSTEM_SERVICE_PARAM_ID_ENTER_BUTTON_ASSIGN, &enterButton) == SYSTEM_SERVICE_OK); - Require(enterButton == SYSTEM_SERVICE_PARAM_ENTER_BUTTON_CROSS); + for (int paramId : {0, SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, SYSTEM_SERVICE_PARAM_ID_GAME_PARENTAL_LEVEL, 8, 20, 401, 999, + SYSTEM_SERVICE_PARAM_ID_ENTER_BUTTON_ASSIGN, 1001, 1008}) { + int unknown = -1; + Require(sceSystemServiceParamGetInt(paramId, &unknown) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(unknown == -1); + Require(sceSystemServiceParamGetInt(paramId, nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER); + } + constexpr std::pair measured[] = { + {100, 0}, {101, 1}, {102, 2}, {103, 100}, {104, 0x20202}, {105, 0}, {106, 0}, {107, 100}, {108, 0}, {109, 100}, {110, 0x20202}, + {111, 0}, {200, 0}, {201, 0}, {202, 0}, {203, 0}, {204, 0}, {205, 0}, {206, 0}, {207, 0}, {208, 0}, {209, 1}, {210, 71}, + {211, 0}, {212, 0}, {213, 0}, {300, 0}, + }; + for (const auto& [paramId, expected] : measured) { + Require(Answers(paramId)); + int value = -1; + Require(sceSystemServiceParamGetInt(paramId, &value) == SYSTEM_SERVICE_OK); + Require(value == expected); + Require(sceSystemServiceParamGetInt(paramId, nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER); + } } diff --git a/core/libs/tests/GuestSystemServiceLoadExecStandardError.cpp b/core/libs/tests/GuestSystemServiceLoadExecStandardError.cpp index ec83ac1c10..f054b5793b 100644 --- a/core/libs/tests/GuestSystemServiceLoadExecStandardError.cpp +++ b/core/libs/tests/GuestSystemServiceLoadExecStandardError.cpp @@ -1,16 +1,14 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceSystemService/include/SystemService.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include +#include #include #include -extern "C" { -int APS5_VABI sceSystemServiceLoadExec(const char*, const char* const*); -int APS5_VABI dup2_nid_postfix(int, int); -} - namespace { constexpr const char* Marker = "APS5_LOAD_EXEC_STANDARD_ERROR"; @@ -28,6 +26,17 @@ std::string Drain(int descriptor) { return text; } +bool HostOutputIs(const std::string& text, const std::string& line) { + constexpr std::string_view WriteWatchUnavailable = "[memory] write watch unavailable: "; + std::size_t begin = 0; + if (text.compare(0, WriteWatchUnavailable.size(), WriteWatchUnavailable) == 0) { + const std::size_t end = text.find('\n'); + if (end == std::string::npos) return false; + begin = end + 1; + } + return text.compare(begin, std::string::npos, line) == 0; +} + void Run(const char* mode, const char* const* arguments) { int host[2] = {-1, -1}; int guest[2] = {-1, -1}; @@ -53,7 +62,7 @@ void Run(const char* mode, const char* const* arguments) { int status = 0; Require(::waitpid(child, &status, 0) == child); Require(WIFEXITED(status) && WEXITSTATUS(status) == 0); - Require(hostText == std::string("started ") + mode + "\n"); + Require(HostOutputIs(hostText, std::string("started ") + mode + "\n")); Require(guestText.empty()); } diff --git a/core/libs/tests/GuestSystemServicePathBoundary.cpp b/core/libs/tests/GuestSystemServicePathBoundary.cpp index da8e6927f0..8d80cb489a 100644 --- a/core/libs/tests/GuestSystemServicePathBoundary.cpp +++ b/core/libs/tests/GuestSystemServicePathBoundary.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -8,8 +9,6 @@ #include #include -extern "C" int APS5_VABI sceSystemServiceLoadExec(const char*, const char* const*); - namespace { void Require(bool value) { diff --git a/core/libs/tests/GuestSystemServiceTimeZone.cpp b/core/libs/tests/GuestSystemServiceTimeZone.cpp index 7f61bcf1fe..cb0e4c65c8 100644 --- a/core/libs/tests/GuestSystemServiceTimeZone.cpp +++ b/core/libs/tests/GuestSystemServiceTimeZone.cpp @@ -1,10 +1,9 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" #include "SceTypes.hpp" #include "prx/libSceSystemService/SystemService.hpp" #include #include -extern "C" int APS5_VABI sceSystemServiceParamGetInt(int paramId, int* value); - namespace { void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestTcp.cpp b/core/libs/tests/GuestTcp.cpp index 2b211538ed..71878e7021 100644 --- a/core/libs/tests/GuestTcp.cpp +++ b/core/libs/tests/GuestTcp.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,23 +10,6 @@ #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI listen_nid_postfix(int, int); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -int APS5_VABI getpeername_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI shutdown_nid_postfix(int, int); -std::int64_t APS5_VABI recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool condition) { if (!condition) std::abort(); } @@ -137,10 +122,87 @@ static void UnboundSocketNameIsZero() { } } +static void WaitAllReceivesTheWholeRequest() { + constexpr int WaitAll = 0x40; + const int listener = socket_nid_postfix(2, 1, 0); + Require(listener >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(listener, address.data(), address.size()) == 0 && listen_nid_postfix(listener, 4) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(listener, address.data(), &size) == 0); + const int client = socket_nid_postfix(2, 1, 0); + Require(connect_nid_postfix(client, address.data(), address.size()) == 0); + const int accepted = accept_nid_postfix(listener, nullptr, nullptr); + Require(accepted >= 0); + + Require(send_nid_postfix(client, "abcd", 4, 0) == 4); + std::thread sender([client] { + std::this_thread::sleep_for(std::chrono::milliseconds(150)); + Require(send_nid_postfix(client, "efgh", 4, 0) == 4); + }); + char received[8]{}; + Require(recv_nid_postfix(accepted, received, sizeof(received), WaitAll) == 8); + sender.join(); + Require(std::memcmp(received, "abcdefgh", 8) == 0); + + Require(send_nid_postfix(client, "xy", 2, 0) == 2 && shutdown_nid_postfix(client, 1) == 0); + char tail[8]{}; + Require(recvfrom_nid_postfix(accepted, tail, sizeof(tail), WaitAll, nullptr, nullptr) == 2); + Require(std::memcmp(tail, "xy", 2) == 0); + + const int datagram = socket_nid_postfix(2, 2, 0); + Require(datagram >= 0); + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(datagram, local.data(), local.size()) == 0); + size = local.size(); + Require(getsockname_nid_postfix(datagram, local.data(), &size) == 0); + Require(sendto_nid_postfix(datagram, "dgram", 5, 0, local.data(), local.size()) == 5); + char record[16]{}; + Require(recv_nid_postfix(datagram, record, sizeof(record), WaitAll) == 5 && std::memcmp(record, "dgram", 5) == 0); + for (const int descriptor : {datagram, client, accepted, listener}) Require(close_nid_postfix(descriptor) == 0); +} + +static void ReceiveAnsweredWithoutTheHost() { + constexpr int DontWait = 0x80; + const int listener = socket_nid_postfix(2, 1, 0); + Require(listener >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(listener, address.data(), address.size()) == 0 && listen_nid_postfix(listener, 4) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(listener, address.data(), &size) == 0); + const int unconnected = socket_nid_postfix(2, 1, 0); + char byte; + Require(recv_nid_postfix(unconnected, &byte, 0, DontWait) == -1 && *__error_nid_postfix() == 57); + const int client = socket_nid_postfix(2, 1, 0); + Require(connect_nid_postfix(client, address.data(), address.size()) == 0); + const int accepted = accept_nid_postfix(listener, nullptr, nullptr); + Require(accepted >= 0); + + Require(recv_nid_postfix(accepted, &byte, 0, DontWait) == 0); + Require(recv_nid_postfix(accepted, &byte, 0, 0) == 0); + std::array sender; + sender.fill(0xaa); + std::uint32_t senderSize = sender.size(); + Require(recvfrom_nid_postfix(accepted, &byte, 0, 0, sender.data(), &senderSize) == 0 && senderSize == 0); + +#ifdef _WIN32 + Require(shutdown_nid_postfix(accepted, 0) == 0); + Require(send_nid_postfix(client, "x", 1, 0) == 1); + char received[4]; + Require(recv_nid_postfix(accepted, received, sizeof(received), 0) == 0); + Require(recv_nid_postfix(accepted, received, sizeof(received), DontWait) == 0); + senderSize = sender.size(); + Require(recvfrom_nid_postfix(accepted, received, sizeof(received), 0, sender.data(), &senderSize) == 0 && senderSize == 0); +#endif + for (const int descriptor : {unconnected, client, accepted, listener}) Require(close_nid_postfix(descriptor) == 0); +} + int main() { StreamReceiveReportsNoSender(); ListenersShareAPortAcrossFamilies(); AcceptedSocketsFollowListenerBlocking(); + WaitAllReceivesTheWholeRequest(); + ReceiveAnsweredWithoutTheHost(); UnboundSocketNameIsZero(); const int listener = socket_nid_postfix(2, 1, 6); Require(listener >= 0); @@ -199,11 +261,9 @@ int main() { Require(send_nid_postfix(client, request, sizeof(request), 0x20000) == -1 && *__error_nid_postfix() == 32); Require(close_nid_postfix(accepted) == 0); -#ifndef _WIN32 std::int64_t sent = 0; for (int i = 0; i < 100 && sent >= 0; ++i) sent = send_nid_postfix(client, request, sizeof(request), 0x20000); Require(sent == -1 && *__error_nid_postfix() == 32); -#endif Require(close_nid_postfix(client) == 0); Require(close_nid_postfix(listener) == 0); Require(close_nid_postfix(listener) == -1 && *__error_nid_postfix() == 9); diff --git a/core/libs/tests/GuestTerminal.cpp b/core/libs/tests/GuestTerminal.cpp index ad27b96a30..8fabd1454f 100644 --- a/core/libs/tests/GuestTerminal.cpp +++ b/core/libs/tests/GuestTerminal.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,24 +10,6 @@ #include #include -extern "C" { -int APS5_VABI tcgetattr_nid_postfix(int, void*); -int APS5_VABI tcsetattr_nid_postfix(int, int, const void*); -int APS5_VABI tcsendbreak_nid_postfix(int, int); -int APS5_VABI tcdrain_nid_postfix(int); -int APS5_VABI tcflush_nid_postfix(int, int); -int APS5_VABI tcflow_nid_postfix(int, int); -int APS5_VABI tcgetpgrp_nid_postfix(int); -int APS5_VABI tcsetpgrp_nid_postfix(int, int); -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI pipe_nid_postfix(int*); -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI socketpair_nid_postfix(int, int, int, int*); -int APS5_VABI mkdir_nid_postfix(const char*, unsigned short); -int APS5_VABI close_nid_postfix(int); -int* APS5_VABI __error_nid_postfix(); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "terminal check failed at line %d, errno %d\n", line, *__error_nid_postfix()); diff --git a/core/libs/tests/GuestTermiosSpeed.cpp b/core/libs/tests/GuestTermiosSpeed.cpp index cc0a4c76b4..1ad51b5903 100644 --- a/core/libs/tests/GuestTermiosSpeed.cpp +++ b/core/libs/tests/GuestTermiosSpeed.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -14,8 +15,6 @@ #include #endif -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { using SetSpeed = int (APS5_VABI*)(void*, std::uint32_t); diff --git a/core/libs/tests/GuestTextToSpeech2.cpp b/core/libs/tests/GuestTextToSpeech2.cpp index 9bac0096e0..13cab7e16c 100644 --- a/core/libs/tests/GuestTextToSpeech2.cpp +++ b/core/libs/tests/GuestTextToSpeech2.cpp @@ -1,31 +1,21 @@ +#include "prx/libSceTextToSpeech2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" { -std::int32_t APS5_VABI sceTextToSpeech2Initialize(const void* param); -int APS5_VABI sceTextToSpeech2Open(); -int APS5_VABI sceTextToSpeech2Cancel(); -int APS5_VABI sceTextToSpeech2Close(); -int APS5_VABI sceTextToSpeech2GetSpeechStatus(); -int APS5_VABI sceTextToSpeech2Speak(); -int APS5_VABI sceTextToSpeech2Terminate(); -int APS5_VABI sceTextToSpeech2GetSystemStatus(); -} - int main() { constexpr std::int32_t unsupported = static_cast(0x8002002D); - const std::uint32_t param[12]{0x2000000, 0x26c}; - if (sceTextToSpeech2Initialize(param) != unsupported) { + const std::uint32_t param[16]{0x2000000, 0x26c}; + if (sceTextToSpeech2Initialize(reinterpret_cast(param)) != unsupported) { std::puts("TextToSpeech2: initialization did not report the unsupported operation"); std::abort(); } - if (sceTextToSpeech2Open() != unsupported || sceTextToSpeech2Cancel() != unsupported || - sceTextToSpeech2Close() != unsupported || sceTextToSpeech2GetSpeechStatus() != unsupported || - sceTextToSpeech2Speak() != unsupported || sceTextToSpeech2Terminate() != unsupported || - sceTextToSpeech2GetSystemStatus() != unsupported) { + if (sceTextToSpeech2Open(nullptr) != unsupported || sceTextToSpeech2Cancel() != unsupported || + sceTextToSpeech2Close() != unsupported || sceTextToSpeech2GetSpeechStatus(nullptr) != unsupported || + sceTextToSpeech2Speak(nullptr) != unsupported || sceTextToSpeech2Terminate() != unsupported || + sceTextToSpeech2GetSystemStatus(nullptr) != unsupported) { std::puts("TextToSpeech2: later calls did not report the unsupported operation"); std::abort(); } diff --git a/core/libs/tests/GuestThermalSensors.cpp b/core/libs/tests/GuestThermalSensors.cpp index 3c051911a6..2fcdb8a5a7 100644 --- a/core/libs/tests/GuestThermalSensors.cpp +++ b/core/libs/tests/GuestThermalSensors.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include @@ -11,9 +12,7 @@ extern "C" { void ReadThermalSensors_nid_no_patch(const char* root, int celsius[3]); -int APS5_VABI sceKernelGetCpuTemperature(int* outCelsius); -int APS5_VABI sceKernelGetSocSensorTemperature(int sensorId, int* outCelsius); -int APS5_VABI sceKernelGetSocPowerConsumption(void* consumption); + } static void Require(bool value) { diff --git a/core/libs/tests/GuestThreadAtexit.cpp b/core/libs/tests/GuestThreadAtexit.cpp index f37fd5d526..ebcfc1eeeb 100644 --- a/core/libs/tests/GuestThreadAtexit.cpp +++ b/core/libs/tests/GuestThreadAtexit.cpp @@ -1,17 +1,11 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -Pthread APS5_VABI scePthreadSelf(); -int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(void (APS5_VABI* destructor)(void*), void* object, void* dsoSymbol); -void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix(); -} - namespace { using Destructor = void (APS5_VABI*)(void*); @@ -47,6 +41,7 @@ void* APS5_VABI Worker(void*) { bool DestructorsThrow() { try { _sceLibcInternalThreadDtors_nid_postfix(); + } catch (const std::runtime_error&) { return true; } @@ -61,9 +56,11 @@ int main() { Require(Register(RecordAndRegister, 2) == 0); Require(Register(Record, 3) == 0); _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{3, 2, 9, 1}); Require(callers == std::vector(4, mainThread)); _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls.size() == 4); calls.clear(); @@ -83,5 +80,6 @@ int main() { Require(DestructorsThrow()); Require(calls.empty()); _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{6}); } diff --git a/core/libs/tests/GuestThreadCallbackAbi.cpp b/core/libs/tests/GuestThreadCallbackAbi.cpp index 4fe7aa04ca..a5e6abb9f6 100644 --- a/core/libs/tests/GuestThreadCallbackAbi.cpp +++ b/core/libs/tests/GuestThreadCallbackAbi.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -12,12 +13,6 @@ using GuestDestructor = void(APS5_VABI *)(void*); -extern "C" { -int APS5_VABI __cxa_thread_atexit_impl_nid_postfix(GuestDestructor, void*, void*); -int APS5_VABI __cxa_thread_atexit_nid_postfix(GuestDestructor, void*, void*); -int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(GuestDestructor, void*, void*); -} - namespace { struct Call { diff --git a/core/libs/tests/GuestThreadCpuClock.cpp b/core/libs/tests/GuestThreadCpuClock.cpp index 15946c4aec..952c0652ea 100644 --- a/core/libs/tests/GuestThreadCpuClock.cpp +++ b/core/libs/tests/GuestThreadCpuClock.cpp @@ -1,26 +1,21 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include #include #include +#include #include -extern "C" { -int APS5_VABI pthread_getcpuclockid_nid_postfix(Pthread thread, int* clockId); -Pthread APS5_VABI pthread_self_nid_postfix(void); -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); -int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res); -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp); -int* APS5_VABI __error_nid_postfix(); -} - static constexpr int GUEST_EFAULT = 14; static constexpr int GUEST_EINVAL = 22; -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static constexpr int GUEST_CLOCK_MONOTONIC = 4; static constexpr int GUEST_CLOCK_THREAD_CPUTIME_ID = 14; +static constexpr int GUEST_CLOCK_PROCESS_CPUTIME_ID = 15; +static constexpr std::uint32_t PROCESS_CPU_CLOCK = 0xC0000000u; static constexpr std::int64_t NANOS_PER_SECOND = 1000000000LL; static constexpr std::int64_t NANOS_PER_MILLISECOND = 1000000LL; static constexpr std::int64_t BURN_NANOS = 200 * NANOS_PER_MILLISECOND; @@ -107,10 +102,36 @@ static void OtherThreadClockReadsThatThread() { Require(*__error_nid_postfix() == GUEST_EINVAL); } +static void ProcessClocksNameOnlyTheGuest() { + const int self = getpid_nid_postfix(); + const int own = static_cast(PROCESS_CPU_CLOCK | static_cast(self)); + const int other = static_cast(PROCESS_CPU_CLOCK | static_cast(self + 1)); + const std::int64_t start = Nanos(own); + BurnOwnCpu(BURN_NANOS); + const std::int64_t before = Nanos(own); + const std::int64_t reference = Nanos(GUEST_CLOCK_PROCESS_CPUTIME_ID); + const std::int64_t after = Nanos(own); + Require(before - start >= BURN_NANOS - TICK_MARGIN_NANOS); + Require(before <= reference + TICK_MARGIN_NANOS && reference <= after + TICK_MARGIN_NANOS); + KernelTimespec time{-1, -1}; + *__error_nid_postfix() = 0; + Require(clock_gettime_nid_postfix(other, &time) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + Require(time.tv_sec == -1 && time.tv_nsec == -1); + Require(sceKernelClockGettime(other, &time) == SCE_KERNEL_ERROR_EINVAL); + KernelTimespec threadResolution{-1, -1}; + Require(clock_getres_nid_postfix(GUEST_CLOCK_THREAD_CPUTIME_ID, &threadResolution) == 0); + for (const int clockId : {own, other}) { + KernelTimespec resolution{-1, -1}; + Require(clock_getres_nid_postfix(clockId, &resolution) == 0); + Require(resolution.tv_sec == threadResolution.tv_sec && resolution.tv_nsec == threadResolution.tv_nsec); + } +} + int main() { int clockId = 0; Require(pthread_getcpuclockid_nid_postfix(nullptr, &clockId) == GUEST_EINVAL); Require(pthread_getcpuclockid_nid_postfix(pthread_self_nid_postfix(), nullptr) == GUEST_EFAULT); OwnClockFollowsThreadCpuTime(); OtherThreadClockReadsThatThread(); + ProcessClocksNameOnlyTheGuest(); } diff --git a/core/libs/tests/GuestThreadSanitizer.cpp b/core/libs/tests/GuestThreadSanitizer.cpp index 4403a31564..7c69247cd9 100644 --- a/core/libs/tests/GuestThreadSanitizer.cpp +++ b/core/libs/tests/GuestThreadSanitizer.cpp @@ -1,8 +1,7 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include -extern "C" int APS5_VABI sceKernelIsThreadSanitizerEnabled(void); - int main() { if (sceKernelIsThreadSanitizerEnabled() != 0) std::abort(); return 0; diff --git a/core/libs/tests/GuestThreadSelf.cpp b/core/libs/tests/GuestThreadSelf.cpp index 87e38f6c0a..ae09ff38d4 100644 --- a/core/libs/tests/GuestThreadSelf.cpp +++ b/core/libs/tests/GuestThreadSelf.cpp @@ -1,3 +1,7 @@ +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" #include "SceTypes.hpp" #include #include @@ -9,38 +13,6 @@ #include #endif -extern "C" { -int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -int APS5_VABI scePthreadDetach(Pthread thread); -int APS5_VABI pthread_join_nid_postfix(Pthread thread, void** value); -int APS5_VABI pthread_detach_nid_postfix(Pthread thread); -void APS5_VABI scePthreadExit(void* retval); -Pthread APS5_VABI scePthreadSelf(); -int APS5_VABI scePthreadGetthreadid(void); -int APS5_VABI pthread_getthreadid_np_nid_postfix(void); -void APS5_VABI scePthreadTestcancel(); -void APS5_VABI pthread_testcancel_nid_postfix(void); -int APS5_VABI scePthreadSetcancelstate(int state, int* old_state); -int APS5_VABI scePthreadSetcanceltype(int type, int* old_type); -void APS5_VABI pthread_set_name_np_nid_postfix(Pthread thread, const char* name); -int APS5_VABI scePthreadGetname(Pthread thread, char* name); -int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr); -int APS5_VABI scePthreadAttrGetstackaddr(const PthreadAttr* attr, void** stack_addr); -int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, size_t* stack_size); -int APS5_VABI mprotect_nid_postfix(void* address, std::size_t length, int protection) noexcept; -int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end); -} - static bool StackContains(Pthread thread, const void* address) { PthreadAttr attr = nullptr; if (scePthreadAttrInit(&attr) != 0 || scePthreadAttrGet(thread, &attr) != 0) return false; @@ -48,20 +20,18 @@ static bool StackContains(Pthread thread, const void* address) { size_t size = 0; const bool queried = scePthreadAttrGetstackaddr(&attr, &stack) == 0 && scePthreadAttrGetstacksize(&attr, &size) == 0; scePthreadAttrDestroy(&attr); + const auto begin = reinterpret_cast(stack); const auto value = reinterpret_cast(address); return queried && stack != nullptr && size != 0 && value >= begin && value - begin < size; } static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; -static constexpr int SCE_KERNEL_ERROR_EPERM = 0x80020001; -static constexpr int SCE_KERNEL_ERROR_EDEADLK = 0x8002000b; static constexpr int GUEST_EDEADLK = 11; static constexpr int GUEST_EINVAL = 22; static constexpr int MUTEX_TYPE_RECURSIVE = 2; -static constexpr int PTHREAD_CANCEL_ENABLE = 0; -static constexpr int PTHREAD_CANCEL_ASYNCHRONOUS = 2; +static constexpr int GuestPthread_Cancel_Enable = 0; +static constexpr int GuestPthread_Cancel_Asynchronous = 2; static constexpr std::intptr_t WorkerRetval = 0x1234; static void Require(bool value) { if (!value) std::abort(); } @@ -78,6 +48,7 @@ static void ReportedStack(Pthread thread, std::uintptr_t* begin, std::uintptr_t* size_t size = 0; Require(scePthreadAttrGetstackaddr(&attr, &stack) == SCE_OK && scePthreadAttrGetstacksize(&attr, &size) == SCE_OK); scePthreadAttrDestroy(&attr); + *begin = reinterpret_cast(stack); *end = *begin + size; } @@ -165,12 +136,14 @@ static void* APS5_VABI Worker(void* arg) { context.posixJoinSelf = pthread_join_nid_postfix(context.selfFromWorker, nullptr); int oldState = -1; int oldType = -1; - context.testcancelReturned = scePthreadSetcancelstate(PTHREAD_CANCEL_ENABLE, &oldState) == SCE_OK && - scePthreadSetcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldType) == SCE_OK; + context.testcancelReturned = scePthreadSetcancelstate(GuestPthread_Cancel_Enable, &oldState) == SCE_OK && + scePthreadSetcanceltype(GuestPthread_Cancel_Asynchronous, &oldType) == SCE_OK; scePthreadTestcancel(); pthread_testcancel_nid_postfix(); - context.testcancelReturned = context.testcancelReturned && oldState == PTHREAD_CANCEL_ENABLE; + + context.testcancelReturned = context.testcancelReturned && oldState == GuestPthread_Cancel_Enable; scePthreadExit(reinterpret_cast(WorkerRetval)); + return nullptr; } @@ -229,14 +202,15 @@ int main() { scePthreadTestcancel(); pthread_testcancel_nid_postfix(); - Require(scePthreadMutexUnlock(&mutex) == SCE_OK); Require(scePthreadMutexDestroy(&mutex) == SCE_OK); char name[32] = {'x'}; pthread_set_name_np_nid_postfix(mainSelf, "q2-main"); + Require(scePthreadGetname(mainSelf, name) == SCE_OK && std::strcmp(name, "q2-main") == 0); pthread_set_name_np_nid_postfix(mainSelf, nullptr); + Require(scePthreadGetname(mainSelf, name) == SCE_OK && name[0] == '\0'); pthread_set_name_np_nid_postfix(nullptr, "ignored"); diff --git a/core/libs/tests/GuestTimeConversion.cpp b/core/libs/tests/GuestTimeConversion.cpp index 7b11ebe2ef..abc96e55f2 100644 --- a/core/libs/tests/GuestTimeConversion.cpp +++ b/core/libs/tests/GuestTimeConversion.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/SonyCrt.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestTime.hpp" #include @@ -15,24 +17,11 @@ #include extern "C" { -GuestTm* APS5_VABI libc_gmtime_nid_postfix(const std::int64_t*); -GuestTm* APS5_VABI gmtime_nid_postfix(const std::int64_t*); -GuestTm* APS5_VABI libc_localtime_nid_postfix(const std::int64_t*); -GuestTm* APS5_VABI localtime_nid_postfix(const std::int64_t*); -GuestTm* APS5_VABI gmtime_s_nid_postfix(const std::int64_t*, GuestTm*); -GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t*, GuestTm*); -char* APS5_VABI asctime_nid_postfix(const GuestTm*); -std::int64_t APS5_VABI mktime_nid_postfix(GuestTm*); -std::size_t APS5_VABI strftime_nid_postfix(char*, std::size_t, const char*, const GuestTm*); -void APS5_VABI tzset_nid_postfix(void); + extern char* tzname_nid_postfix[2]; extern std::int64_t timezone_nid_postfix; extern int daylight_nid_postfix; -char* APS5_VABI ctime_nid_postfix(const std::int64_t*); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI setenv_nid_postfix(const char*, const char*, int); -double APS5_VABI libc_difftime_nid_postfix(std::int64_t, std::int64_t); -double APS5_VABI difftime_nid_postfix(std::int64_t, std::int64_t); + } using Converter = GuestTm* (APS5_VABI *)(const std::int64_t*); @@ -140,75 +129,41 @@ static void CheckBufferSize(const GuestTm& guest, const char* format, std::size_ Require(strftime_nid_postfix(buffer, 1, format, &guest) == 0, "strftime accepted a one byte buffer"); } -static const char* HostZoneName(int isdst) { -#ifdef _WIN32 - return _tzname[isdst != 0 ? 1 : 0]; -#else - return tzname[isdst != 0 ? 1 : 0]; -#endif -} -static void CheckFreeBsdConversions() { +static void CheckConsoleConversions() { const std::int64_t timer = 1700000000; GuestTm guest{}; Require(gmtime_s_nid_postfix(&timer, &guest) == &guest, "UTC conversion failed"); - CheckFormat(guest, "%k|%l", "22|10"); - CheckFormat(guest, "%v", "14-Nov-2023"); - CheckFormat(guest, "%+", "Tue Nov 14 22:13:20 UTC 2023"); - CheckFormat(guest, "%s", "1699992800"); + CheckFormat(guest, "%k|%l|%v|%+|%s", "%k|%l|%v|%+|%s"); CheckFormat(guest, "%%k %%l %%s %%v %%+", "%k %l %s %v %+"); - CheckFormat(guest, "%%%k", "%22"); - CheckFormat(guest, "[%k][%l]", "[22][10]"); - CheckFormat(guest, "%Ek|%Ol|%Os|%Ev|%O+", "22|10|1699992800|14-Nov-2023|Tue Nov 14 22:13:20 UTC 2023"); - CheckFormat(guest, "%E%k", "%k"); - CheckFormat(guest, "%O%l", "%l"); - CheckFormat(guest, "%E%s", "%s"); - CheckFormat(guest, "%E%v", "%v"); - CheckFormat(guest, "%O%+", "%+"); - CheckFormat(guest, "%k|%E%l|%l", "22|%l|10"); - CheckFormat(guest, "%E%", "%"); - CheckFormat(guest, "%O%%k", "%22"); - CheckFormat(guest, "%E%k%k", "%k22"); + CheckFormat(guest, "%Ek|%Ol|%Os|%Ev|%O+", "%k|%l|%s|%v|%+"); + CheckFormat(guest, "%Ec|%Ox|%EY|%Om", "Tue Nov 14 22:13:20 2023|11/14/23|2023|11"); + CheckFormat(guest, "%EE|%OO|%EO", "%E|%O|%O"); CheckBufferSize(guest, "%k", 2); - CheckBufferSize(guest, "%k:%l", 5); - CheckBufferSize(guest, "%+", 28); + CheckBufferSize(guest, "%c", 24); CheckBufferSize(guest, "%Ek", 2); + bool threw = false; + try { + char buffer[16]; + strftime_nid_postfix(buffer, sizeof(buffer), "ab%O", &guest); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "strftime accepted a format ending after a modifier"); guest.tm_zone = "XYZ"; - CheckFormat(guest, "%+", "Tue Nov 14 22:13:20 XYZ 2023"); - CheckFormat(guest, "%O+", "Tue Nov 14 22:13:20 XYZ 2023"); - guest.tm_zone = "A%B"; - CheckFormat(guest, "%+", "Tue Nov 14 22:13:20 A%B 2023"); - guest.tm_zone = nullptr; guest.tm_isdst = 0; - char expected[96]; - std::snprintf(expected, sizeof(expected), "Tue Nov 14 22:13:20 %s 2023", HostZoneName(0)); - CheckFormat(guest, "%+", expected); + CheckFormat(guest, "%Z", tzname_nid_postfix[0]); guest.tm_isdst = 1; - std::snprintf(expected, sizeof(expected), "Tue Nov 14 22:13:20 %s 2023", HostZoneName(1)); - CheckFormat(guest, "%+", expected); + CheckFormat(guest, "%Z", tzname_nid_postfix[1]); guest.tm_isdst = 0; - GuestTm local{}; - Require(localtime_s_nid_postfix(&timer, &local) == &local, "Local conversion failed"); - GuestTm normalized = local; - const std::int64_t mktimeResult = mktime_nid_postfix(&normalized); - Require(mktimeResult == timer, "mktime did not invert localtime"); - std::snprintf(expected, sizeof(expected), "%lld", static_cast(mktimeResult)); - CheckFormat(local, "%s", expected); - CheckFormat(local, "%Os", expected); - guest.tm_hour = 0; - CheckFormat(guest, "%k|%l", " 0|12"); + CheckFormat(guest, "%I|%p", "12|AM"); guest.tm_hour = 12; - CheckFormat(guest, "%k|%l", "12|12"); - guest.tm_hour = 5; - guest.tm_mday = 5; - CheckFormat(guest, "%k|%l|%v", " 5| 5| 5-Nov-2023"); + CheckFormat(guest, "%I|%p", "12|PM"); guest.tm_hour = 13; - CheckFormat(guest, "%k|%l", "13| 1"); - char tooSmall[4]{}; - Require(strftime_nid_postfix(tooSmall, sizeof(tooSmall), "%k:%l", &guest) == 0, "strftime accepted an undersized buffer"); + CheckFormat(guest, "%I|%p", "01|PM"); } static void CheckLiteralConversions() { @@ -220,31 +175,18 @@ static void CheckLiteralConversions() { const char* expected; }; constexpr FormatCase cases[]{ - {"%Q", "Q"}, - {"%P|%f|%N|%1|%#Y", "P|f|N|1|#Y"}, - {"[%Q] %Y-%m-%d", "[Q] 2023-11-14"}, - {"%EQ|%OQ|%_Q|%-Q|%0Q", "Q|Q|Q|Q|Q"}, - {"%E_Q|%_EQ|%O0Q|%-OQ", "Q|Q|Q|Q"}, - {"%EEY|%EOY|%OEY|%OOY", "EY|OY|EY|OY"}, - {"%EE%Y|%OO%k", "E2023|O22"}, - {"%--Y|%__Y|%00Y|%_0Y|%0-Y", "-Y|_Y|0Y|0Y|-Y"}, - {"%E_EY|%_EOY|%O0OY", "EY|OY|OY"}, + {"%Q", "%Q"}, + {"%P|%f|%N|%1|%#Y", "%P|%f|%N|%1|%#Y"}, + {"[%Q] %Y-%m-%d", "[%Q] 2023-11-14"}, + {"%EQ|%OQ|%_Q|%-Q|%0Q", "%Q|%Q|%_Q|%-Q|%0Q"}, + {"%--Y|%__Y|%00Y", "%--Y|%__Y|%00Y"}, {"%", "%"}, {"text%", "text%"}, - {"%E", "E"}, - {"%O", "O"}, - {"%_", "_"}, - {"%-", "-"}, - {"%0", "0"}, - {"%E_", "_"}, - {"%_E", "E"}, - {"%Q%", "Q%"}, - {"%k%", "22%"}, - {"%%Q|%%%Q|%%%%Q", "%Q|%Q|%%Q"}, - {"%E%Q|%O%Q|%_%Q|%0%Q|%-%Q", "%Q|%Q|%Q|%Q|%Q"}, - {"%E%%Q|%O%%%Q", "%Q|%%Q"}, - {"%Q|%Ek|%Ol|%Ev|%Y", "Q|22|10|14-Nov-2023|2023"}, - {"%\xc3\xa5", "\xc3\xa5"}, + {"%_", "%_"}, + {"%Q%", "%Q%"}, + {"%Y%", "2023%"}, + {"%%Q|%%%Q|%%%%Q", "%Q|%%Q|%%Q"}, + {"%\xc3\xa5", "%\xc3\xa5"}, {"%%", "%"}, {"literal text", "literal text"}, }; @@ -302,13 +244,70 @@ static void CheckLocalRange() { Require(localtime_s_nid_postfix(&overflow, &scratch) == nullptr, "Unrepresentable negative local time was accepted"); } -#ifndef _WIN32 +#ifdef _WIN32 +extern "C" long* __cdecl __dstbias(void); + +struct DaylightBiasFixture { + long saved = *__dstbias(); + ~DaylightBiasFixture() { *__dstbias() = saved; } + void Apply() const { *__dstbias() = -3600; } +}; + +struct ZoneOffset { + const char* zone; + std::int64_t timer; + long offset; + int isdst; +}; + +static void CheckPosixFormZoneOffsets() { + DaylightBiasFixture bias; + constexpr std::array expectations{{ + {"PST8PDT", 979560000, -28800, 0}, + {"PST8PDT", 995198400, -25200, 1}, + {"EST5EDT", 979560000, -18000, 0}, + {"EST5EDT", 995198400, -14400, 1}, + }}; + for (const ZoneOffset& expected : expectations) { + Require(setenv_nid_postfix("TZ", expected.zone, 1) == 0, "Could not set the POSIX-form time zone"); + tzset_nid_postfix(); + bias.Apply(); + GuestTm local{}; + Require(localtime_s_nid_postfix(&expected.timer, &local) == &local, "Local conversion in a POSIX-form zone failed"); + if (local.tm_gmtoff != expected.offset || local.tm_isdst != expected.isdst) { + long hostTimezone = -1; + long hostDstBias = -1; + _get_timezone(&hostTimezone); + _get_dstbias(&hostDstBias); + std::tm hostLocal{}; + const __time64_t hostTimer = expected.timer; + _localtime64_s(&hostLocal, &hostTimer); + std::fprintf(stderr, "%s at %lld: offset %ld daylight %d, expected %ld daylight %d (host timezone %ld dstbias %ld daylight %d)\n", expected.zone, static_cast(expected.timer), static_cast(local.tm_gmtoff), local.tm_isdst, expected.offset, expected.isdst, hostTimezone, hostDstBias, hostLocal.tm_isdst); + std::abort(); + } + } + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); + tzset_nid_postfix(); +} +#endif + static void CheckRegionZones() { +#ifdef _WIN32 + constexpr std::array zones{"PST8PDT", "EST5EDT"}; + constexpr std::array timers{-62135553600, -12609691200, -5016340800, -5000011200, -2732356800, -2209032000, -2208988800, -2208945600, -631108800, 979560000, 995198400}; +#else constexpr std::array zones{"America/Los_Angeles", "Europe/Berlin", "Asia/Kolkata", "America/Argentina/Buenos_Aires"}; constexpr std::array timers{-62135553600, -12609691200, -5016340800, -5000011200, -2732356800, -2209032000, -2208988800, -2208945600, -631108800}; +#endif +#ifdef _WIN32 + DaylightBiasFixture bias; +#endif for (const char* zone : zones) { Require(setenv_nid_postfix("TZ", zone, 1) == 0, "Could not set the region time zone"); tzset_nid_postfix(); +#ifdef _WIN32 + bias.Apply(); +#endif for (const std::int64_t timer : timers) { GuestTm local{}; Require(localtime_s_nid_postfix(&timer, &local) == &local, "Local conversion in a region zone failed"); @@ -326,8 +325,8 @@ static void CheckRegionZones() { } Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); tzset_nid_postfix(); + } -#endif static void CheckGuestZone() { const std::int64_t moment = 172800; @@ -343,35 +342,40 @@ static void CheckGuestZone() { Require(mktime_nid_postfix(&calendar) == moment, "mktime ignored the guest TZ"); Require(setenv_nid_postfix("TZ", "UTC+3", 1) == 0, "Could not change the guest time zone"); tzset_nid_postfix(); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 21 && result.tm_mday == 2 && result.tm_gmtoff == -10800, "tzset ignored the guest TZ"); Require(timezone_nid_postfix == 10800 && daylight_nid_postfix == 1, "tzset did not set timezone and daylight for UTC+3"); Require(setenv_nid_postfix("TZ", "", 1) == 0, "Could not empty the guest time zone"); tzset_nid_postfix(); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 0 && result.tm_mday == 3 && result.tm_gmtoff == 0, "An empty guest TZ was not treated as UTC"); Require(timezone_nid_postfix == 0 && daylight_nid_postfix == 1, "tzset did not set timezone and daylight for UTC"); Require(setenv_nid_postfix("TZ", "EST5EDT", 1) == 0, "Could not set a daylight saving guest time zone"); tzset_nid_postfix(); + Require(timezone_nid_postfix == 18000 && daylight_nid_postfix == 1, "timezone is not the standard offset of a daylight saving zone"); Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); tzset_nid_postfix(); + Require(setenv_nid_postfix("TZ", "EST5", 1) == 0, "Could not set the guest time zone without tzset"); Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 19 && result.tm_gmtoff == -18000, "localtime ignored a guest TZ change made without tzset"); - Require(std::strcmp(tzname_nid_postfix[0], "EST") == 0, "Implicit guest TZ update did not refresh tzname"); + Require(std::strcmp(tzname_nid_postfix[0], "EST") == 0 && std::strcmp(tzname_nid_postfix[1], " ") == 0, + "Implicit guest TZ update did not refresh tzname"); Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); tzset_nid_postfix(); + } static bool ZoneFieldsConsistent(const GuestTm& time, std::int64_t instant, int hour) { const std::int64_t localSeconds = ((instant + time.tm_gmtoff) % 86400 + 86400) % 86400; if (time.tm_hour * 3600 + time.tm_min * 60 + time.tm_sec != localSeconds || time.tm_hour != hour) return false; - char expected[16]; - const std::int64_t offset = time.tm_gmtoff < 0 ? -time.tm_gmtoff : time.tm_gmtoff; - std::snprintf(expected, sizeof(expected), "%02d %c%04d", hour, time.tm_gmtoff < 0 ? '-' : '+', static_cast(offset / 3600 * 100)); - char formatted[16]{}; - return strftime_nid_postfix(formatted, sizeof(formatted), "%H %z", &time) == 8 && std::strcmp(formatted, expected) == 0; + char expected[4]; + std::snprintf(expected, sizeof(expected), "%02d", hour); + char formatted[4]{}; + return strftime_nid_postfix(formatted, sizeof(formatted), "%H", &time) == 2 && std::strcmp(formatted, expected) == 0; } static void CheckZoneRace() { @@ -379,7 +383,11 @@ static void CheckZoneRace() { std::atomic stop{false}; std::atomic torn{0}; std::thread flipper([&] { - for (bool west = false; !stop.load(); west = !west) setenv_nid_postfix("TZ", west ? "UTC-5" : "UTC+3", 1); + for (bool west = false; !stop.load(); west = !west) { + setenv_nid_postfix("TZ", west ? "UTC-5" : "UTC+3", 1); + + std::this_thread::yield(); + } }); std::array readers; for (auto& reader : readers) { @@ -403,6 +411,7 @@ static void CheckZoneRace() { Require(torn == 0, "A time conversion mixed the fields of two time zones while TZ changed"); Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); tzset_nid_postfix(); + } #ifdef _WIN32 @@ -414,12 +423,14 @@ static void CheckRejectedZone() { bool thrown = false; try { localtime_s_nid_postfix(&moment, &result); + } catch (const std::runtime_error&) { thrown = true; } Require(thrown, "localtime accepted a TZ the host cannot hold"); Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); tzset_nid_postfix(); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_gmtoff == 7200, "localtime did not recover after a rejected TZ"); } #endif @@ -427,6 +438,7 @@ static void CheckRejectedZone() { int main() { Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not set the guest time zone"); tzset_nid_postfix(); + constexpr std::array utcCases{{ {0, 70, 0, 1, 0, 0, 0, 4, 0}, {-1, 69, 11, 31, 23, 59, 59, 3, 364}, @@ -506,10 +518,11 @@ int main() { Require(strftime_nid_postfix(formatted, sizeof(formatted), "%Y-%m-%d %H:%M:%S", &utc) == 19 && std::strcmp(formatted, "1970-01-01 00:00:00") == 0, "strftime did not read the guest tm"); - CheckFreeBsdConversions(); + CheckConsoleConversions(); CheckLocalRange(); -#ifndef _WIN32 CheckRegionZones(); +#ifdef _WIN32 + CheckPosixFormZoneOffsets(); #endif CheckLiteralConversions(); @@ -524,13 +537,16 @@ int main() { #endif Require(setenv_nid_postfix("TZ", "UTC-5", 1) == 0, "Could not set the guest time zone"); tzset_nid_postfix(); + GuestTm shifted{}; Require(localtime_s_nid_postfix(&epoch, &shifted) == &shifted && shifted.tm_hour == 5 && shifted.tm_gmtoff == 18000, "tzset did not pick up the new TZ"); - Require(tzname_nid_postfix[0] != nullptr && std::strcmp(tzname_nid_postfix[0], "UTC") == 0 && tzname_nid_postfix[1] != nullptr, - "tzname does not name the zone tzset set"); + Require(tzname_nid_postfix[0] != nullptr && std::strcmp(tzname_nid_postfix[0], "UTC") == 0 && tzname_nid_postfix[1] != nullptr && + std::strcmp(tzname_nid_postfix[1], " ") == 0, + "tzname does not name the zone tzset set and leave the daylight name blank"); Require(setenv_nid_postfix("TZ", "EST5EDT", 1) == 0, "Could not set the guest time zone"); tzset_nid_postfix(); + Require(std::strcmp(tzname_nid_postfix[0], "EST") == 0 && std::strcmp(tzname_nid_postfix[1], "EDT") == 0, "tzname does not name the standard and daylight zones"); } diff --git a/core/libs/tests/GuestTimeExtras.cpp b/core/libs/tests/GuestTimeExtras.cpp index 6fc9d4cde0..f03c36ce64 100644 --- a/core/libs/tests/GuestTimeExtras.cpp +++ b/core/libs/tests/GuestTimeExtras.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/SonyCrt.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestTime.hpp" #include @@ -7,24 +9,6 @@ #include #include -struct Xtime { - std::int64_t sec; - std::int64_t nsec; -}; - -extern "C" { -GuestTm* APS5_VABI gmtime_s_nid_postfix(const std::int64_t*, GuestTm*); -GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t*, GuestTm*); -GuestTm* APS5_VABI gmtime_r_nid_postfix(const std::int64_t*, GuestTm*); -GuestTm* APS5_VABI localtime_r_nid_postfix(const std::int64_t*, GuestTm*); -std::int64_t APS5_VABI timegm_nid_postfix(GuestTm*); -void APS5_VABI tzset_nid_postfix(); -int APS5_VABI setenv_nid_postfix(const char*, const char*, int); -int APS5_VABI xtime_get_nid_postfix(Xtime*, int); -Xtime APS5_VABI _Xtime_to_ts_nid_postfix(const Xtime*); -Xtime APS5_VABI _Xtime_diff_to_ts_nid_postfix(const Xtime*); -} - static void Require(bool value, const char* message) { if (!value) { std::fprintf(stderr, "%s\n", message); @@ -156,6 +140,7 @@ static void CheckXtime() { int main() { Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not set the time zone"); tzset_nid_postfix(); + CheckReentrantConversions(); CheckTimegmCases(); CheckXtime(); diff --git a/core/libs/tests/GuestTimedJoin.cpp b/core/libs/tests/GuestTimedJoin.cpp new file mode 100644 index 0000000000..928c3bb73b --- /dev/null +++ b/core/libs/tests/GuestTimedJoin.cpp @@ -0,0 +1,177 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +static_assert(sizeof(KernelTimespec)==16 && alignof(KernelTimespec)==8 && offsetof(KernelTimespec,tv_nsec)==8); + +using TimedJoin = decltype(&pthread_timedjoin_np_nid_postfix); +static TimedJoin timedJoin; + +void Require(bool value,const char* message) { + if (!value) { std::fprintf(stderr,"timed join: %s\n",message); std::abort(); } +} + +KernelTimespec After(int milliseconds) { + KernelTimespec result{}; + Require(clock_gettime_nid_postfix(0,&result)==0,"read realtime"); + result.tv_nsec+=static_cast(milliseconds)*1000000; + result.tv_sec+=result.tv_nsec/1000000000; + result.tv_nsec%=1000000000; + return result; +} + +struct Gate { std::atomic release{false}, entered{false}, leaving{false}; }; +void* APS5_VABI Worker(void* argument) { + auto& gate=*static_cast(argument); + gate.entered.store(true,std::memory_order_release); + while (!gate.release.load(std::memory_order_acquire)) std::this_thread::yield(); + gate.leaving.store(true,std::memory_order_release); + return reinterpret_cast(std::uintptr_t{0x12345678}); +} + +void Wait(const std::atomic& flag) { + const auto deadline=std::chrono::steady_clock::now()+std::chrono::seconds(3); + while (!flag.load(std::memory_order_acquire)) { + Require(std::chrono::steady_clock::now() entered{false}; int result=-1; void* value=reinterpret_cast(std::uintptr_t{0x55}); }; +void* APS5_VABI Joiner(void* argument) { + auto& task=*static_cast(argument); + const auto deadline=After(3000); + task.entered.store(true,std::memory_order_release); + task.result=timedJoin(task.target,&task.value,&deadline); + return nullptr; +} + +void AwaitClaim(Pthread target) { + const KernelTimespec past{0,0}; + const auto until=std::chrono::steady_clock::now()+std::chrono::seconds(2); + for (;;) { + const int result=timedJoin(target,nullptr,&past); + if (result==45) return; + Require(result==60,"claim probe returns timeout or ENOTSUP"); + Require(std::chrono::steady_clock::now()(std::uintptr_t{0x55}); std::uint64_t after=0x5a5a5a5a; } result; + const KernelTimespec past{0,0}; + *__error_nid_postfix()=123; + Require(timedJoin(thread,&result.value,&past)==60,"past deadline expires"); + Require(result.value==reinterpret_cast(std::uintptr_t{0x55}),"timeout preserves return slot"); + Require(*__error_nid_postfix()==123,"POSIX error preserves errno"); + const auto deadline=After(50); + const auto originalDeadline=deadline; + const auto started=std::chrono::steady_clock::now(); + Require(timedJoin(thread,&result.value,&deadline)==60,"future deadline expires"); + Require(std::chrono::steady_clock::now()-started>=std::chrono::milliseconds(25),"wait did not expire immediately"); + Require(deadline.tv_sec==originalDeadline.tv_sec && deadline.tv_nsec==originalDeadline.tv_nsec,"deadline input is unchanged"); + gate.release.store(true,std::memory_order_release); + const KernelTimespec distant{std::numeric_limits::max(),999999999}; + Require(timedJoin(thread,&result.value,&distant)==0,"timeout leaves target joinable; distant deadline does not overflow"); + Require(result.value==reinterpret_cast(std::uintptr_t{0x12345678}),"joined return value"); + Require(result.before==0xa5a5a5a5 && result.after==0x5a5a5a5a,"return-slot bounds"); + Require(timedJoin(thread,nullptr,&valid)==3,"already joined target"); + Require(timedJoin(thread,nullptr,nullptr)==22,"deadline validation precedes joined state"); + Gate done; + Pthread completed=Start(done); + done.release.store(true,std::memory_order_release); + Wait(done.leaving); + const auto until=std::chrono::steady_clock::now()+std::chrono::seconds(2); + for (;;) { + const int joined=timedJoin(completed,nullptr,&past); + if (joined==0) break; + Require(joined==60 && std::chrono::steady_clock::now()(std::uintptr_t{1}),"joiner is canceled"); + Require(task.result==-1 && task.value==reinterpret_cast(std::uintptr_t{0x55}),"canceled join did not return or write result"); + const KernelTimespec past{0,0}; + Require(timedJoin(target,nullptr,&past)==60,"cancellation releases join claim"); + gate.release.store(true,std::memory_order_release); + const auto deadline=After(1000); + Require(timedJoin(target,nullptr,&deadline)==0,"cancellation preserves target joinability"); + } else { + gate.release.store(true,std::memory_order_release); + Require(pthread_join_nid_postfix(joiner,nullptr)==0,"join successful joiner"); + Require(task.result==0 && task.value==reinterpret_cast(std::uintptr_t{0x12345678}),"competing join succeeds once"); + } +} + +int main(int argc,char** argv) { + Require(argc==2,"expected kernel path"); +#ifdef _WIN32 + const auto library=LoadLibraryA(argv[1]); + Require(library!=nullptr,"load kernel"); + timedJoin=reinterpret_cast(GetProcAddress(library,"pthread_timedjoin_np_nid_postfix")); +#else + const auto library=dlopen(argv[1],RTLD_NOW); + Require(library!=nullptr,"load kernel"); + timedJoin=reinterpret_cast(dlsym(library,"pthread_timedjoin_np_nid_postfix")); +#endif + Require(timedJoin!=nullptr,"pthread_timedjoin_np export is missing"); + Basic(); + Contention(false); + Contention(true); +#ifdef _WIN32 + Require(FreeLibrary(library)!=0,"release kernel lookup handle"); +#else + Require(dlclose(library)==0,"release kernel lookup handle"); +#endif + std::puts("timed join checks passed"); +} diff --git a/core/libs/tests/GuestTlsGetAddr.cpp b/core/libs/tests/GuestTlsGetAddr.cpp new file mode 100644 index 0000000000..e13ef11bde --- /dev/null +++ b/core/libs/tests/GuestTlsGetAddr.cpp @@ -0,0 +1,86 @@ +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#if defined(_WIN32) +#include +#else +#include +#endif + +#if defined(_WIN32) +extern "C" ULONG _tls_index; + +namespace { + +using Cell = char; +constexpr Cell Initial = 0; + +KernelTlsIndex CellIndex() { return {reinterpret_cast(&_tls_index), 0}; } + +Cell* ThreadCell() { + auto* blocks = *reinterpret_cast(reinterpret_cast(NtCurrentTeb()) + 0x58); + return blocks[_tls_index]; +} + +} +#else +namespace { + +using Cell = std::uint32_t; +constexpr Cell Initial = 0x5ca1ab1e; +thread_local Cell Value = Initial; + +struct Module { + std::size_t id = 0; + void* data = nullptr; +}; + +Module MainModule() { + Module module; + dl_iterate_phdr([](dl_phdr_info* info, std::size_t, void* out) { + auto& found = *static_cast(out); + found = {info->dlpi_tls_modid, info->dlpi_tls_data}; + return 1; + }, &module); + return module; +} + +KernelTlsIndex CellIndex() { + const auto module = MainModule(); + return {module.id, static_cast(reinterpret_cast(&Value) - static_cast(module.data))}; +} + +Cell* ThreadCell() { return &Value; } + +} +#endif + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + const auto index = CellIndex(); + auto* own = static_cast(__tls_get_addr_nid_postfix(&index)); + Require(own == ThreadCell() && *own == Initial); + *own = 7; + Cell* other = nullptr; + Cell seen = 1; + std::thread([&] { + other = static_cast(__tls_get_addr_nid_postfix(&index)); + Require(other == ThreadCell()); + seen = *other; + *other = 9; + }).join(); + Require(other != own && seen == Initial && *own == 7); + *own = Initial; + bool threw = false; + try { __tls_get_addr_nid_postfix(nullptr); } catch (const std::invalid_argument&) { threw = true; } + Require(threw); +} diff --git a/core/libs/tests/GuestTruncate.cpp b/core/libs/tests/GuestTruncate.cpp index 1e037fd9d8..732a8394be 100644 --- a/core/libs/tests/GuestTruncate.cpp +++ b/core/libs/tests/GuestTruncate.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -18,16 +21,6 @@ extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handle #include #endif -extern "C" { -int APS5_VABI truncate_nid_postfix(const char*, std::int64_t); -int APS5_VABI sceKernelTruncate_nid_postfix(const char*, std::int64_t); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -int APS5_VABI sceKernelFtruncate(int, std::int64_t); -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool value) { if (!value) std::abort(); } constexpr int GuestEnoent = 2; diff --git a/core/libs/tests/GuestTruncateExtend.cpp b/core/libs/tests/GuestTruncateExtend.cpp index 66dcffd80e..789105ba61 100644 --- a/core/libs/tests/GuestTruncateExtend.cpp +++ b/core/libs/tests/GuestTruncateExtend.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,18 +10,6 @@ #include #endif -extern "C" { -int APS5_VABI open_nid_postfix(const char*, int, int); -int APS5_VABI close_nid_postfix(int); -std::int64_t APS5_VABI write_nid_postfix(int, const void*, std::uint64_t); -std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::uint64_t, std::int64_t); -std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -int APS5_VABI truncate_nid_postfix(const char*, std::int64_t); -int APS5_VABI unlink_nid_postfix(const char*); -int* APS5_VABI __error_nid_postfix(); -} - namespace { constexpr int ReadOnly = 0; @@ -30,6 +20,7 @@ constexpr char FileName[] = "anyps5_truncate_extend_file"; void Cleanup() { unlink_nid_postfix(FileName); + } void Require(bool condition, const char* what) { diff --git a/core/libs/tests/GuestTruncateRace.cpp b/core/libs/tests/GuestTruncateRace.cpp index 29f2f3a3e1..e4602f6415 100644 --- a/core/libs/tests/GuestTruncateRace.cpp +++ b/core/libs/tests/GuestTruncateRace.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include @@ -11,18 +13,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); -int APS5_VABI sceKernelClose(int); -int APS5_VABI sceKernelFtruncate(int, std::int64_t); -int APS5_VABI ftruncate_nid_postfix(int, std::int64_t); -int APS5_VABI dup_nid_postfix(int); -std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); -std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t); -std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Truncate race check failed at line %d\n", line); diff --git a/core/libs/tests/GuestUdp.cpp b/core/libs/tests/GuestUdp.cpp index ae2ba5fe9a..346f8c1a69 100644 --- a/core/libs/tests/GuestUdp.cpp +++ b/core/libs/tests/GuestUdp.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -7,47 +9,37 @@ #include #include #include -extern "C" { -int APS5_VABI socket_nid_postfix(int, int, int); -int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); -int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*); -int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI getpeername_nid_postfix(int, void*, std::uint32_t*); -int APS5_VABI ioctl_nid_postfix(int, std::uint64_t, void*); -int APS5_VABI _ioctl_nid_postfix(int, std::uint64_t, void*); -std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); -std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); -std::int64_t APS5_VABI sendto_nid_postfix(int, const void*, std::uint64_t, int, const void*, std::uint32_t); -std::int64_t APS5_VABI recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*); -int APS5_VABI close_nid_postfix(int); -const char* APS5_VABI inet_ntop_nid_postfix(int, const void*, char*, std::uint32_t); -int APS5_VABI inet_pton_nid_postfix(int, const char*, void*); -int* APS5_VABI __error_nid_postfix(); -} -struct Iovec { - void* base; - std::uint64_t length; -}; -struct Msghdr { - void* name; - std::uint32_t nameLength; - Iovec* iov; - int iovLength; - void* control; - std::uint32_t controlLength; - int flags; -}; -extern "C" { -std::int64_t APS5_VABI sendmsg_nid_postfix(int, const Msghdr*, int); -std::int64_t APS5_VABI recvmsg_nid_postfix(int, Msghdr*, int); -std::int64_t APS5_VABI sceKernelReadv(int, const Iovec*, int); -std::int64_t APS5_VABI sceKernelWritev(int, const Iovec*, int); -} + static void Require(bool value) { if (!value) std::abort(); } +static void CheckUnreachablePortIgnored() { + const int closing = socket_nid_postfix(2, 2, 0); + Require(closing >= 0); + std::array unreachable{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(closing, unreachable.data(), unreachable.size()) == 0); + std::uint32_t size = unreachable.size(); + Require(getsockname_nid_postfix(closing, unreachable.data(), &size) == 0 && size == unreachable.size()); + const int receiver = socket_nid_postfix(2, 2 | 0x20000000, 0); + const int sender = socket_nid_postfix(2, 2, 0); + Require(receiver >= 0 && sender >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0); + size = address.size(); + Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0); + Require(close_nid_postfix(closing) == 0); + Require(sendto_nid_postfix(receiver, "x", 1, 0, unreachable.data(), unreachable.size()) == 1); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + char received[4]{}; + for (int attempt = 0; attempt < 3; ++attempt) { + Require(recvfrom_nid_postfix(receiver, received, sizeof(received), 0, nullptr, nullptr) == -1 && *__error_nid_postfix() == 35); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + Require(sendto_nid_postfix(sender, "ok", 2, 0, address.data(), address.size()) == 2); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(recvfrom_nid_postfix(receiver, received, sizeof(received), 0, nullptr, nullptr) == 2 && received[0] == 'o'); + Require(close_nid_postfix(sender) == 0 && close_nid_postfix(receiver) == 0); +} + static void CheckAddressLengths(int family) { const std::uint32_t addressSize = family == 2 ? 16 : 28; const int receiver = socket_nid_postfix(family, 2, 0); @@ -89,8 +81,8 @@ static void CheckAddressLengths(int family) { Require(size == copied && std::memcmp(address.data(), expected.data(), copied) == 0); Require(std::all_of(address.begin() + copied, address.end(), [](auto byte) { return byte == 0xa5; })); address.fill(0xa5); - Iovec vector{received, sizeof(received)}; - Msghdr message{address.data(), capacity, &vector, 1, nullptr, 0, -1}; + KernelIovec vector{received, sizeof(received)}; + GuestMsghdr message{address.data(), capacity, &vector, 1, nullptr, 0, -1}; Require(recvmsg_nid_postfix(receiver, &message, 0) == sizeof(payload)); Require(message.nameLength == copied && std::memcmp(received, payload, sizeof(payload)) == 0); Require(std::memcmp(address.data(), expected.data(), copied) == 0); @@ -99,6 +91,31 @@ static void CheckAddressLengths(int family) { Require(close_nid_postfix(sender) == 0 && close_nid_postfix(receiver) == 0); } +static void CheckSendToPortZero() { + const int receiver = socket_nid_postfix(2, 2, 0); + const int sender = socket_nid_postfix(2, 2, 0); + Require(receiver >= 0 && sender >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0); + std::array portZero{16, 2, 0, 0, 127, 0, 0, 1}; + Require(sendto_nid_postfix(sender, "x", 1, 0, portZero.data(), portZero.size()) == -1 && *__error_nid_postfix() == 49); + char buffer[4]; + Require(recv_nid_postfix(receiver, buffer, sizeof(buffer), 0x80) == -1 && *__error_nid_postfix() == 35); + Require(sendto_nid_postfix(sender, "x", 1, 0, address.data(), address.size()) == 1); + Require(recv_nid_postfix(receiver, buffer, sizeof(buffer), 0) == 1); + const int bound = socket_nid_postfix(2, 2, 0); + Require(bound >= 0); + std::array bindAddress{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(bound, bindAddress.data(), bindAddress.size()) == 0); + Require(sendto_nid_postfix(bound, "x", 1, 0, portZero.data(), portZero.size()) == 1); + Require(recv_nid_postfix(receiver, buffer, sizeof(buffer), 0x80) == -1 && *__error_nid_postfix() == 35); + std::array anyDestination{16, 2, 0, 0, 0, 0, 0, 0}; + Require(sendto_nid_postfix(bound, "x", 1, 0, anyDestination.data(), anyDestination.size()) == -1 && *__error_nid_postfix() == 49); + Require(close_nid_postfix(bound) == 0 && close_nid_postfix(receiver) == 0 && close_nid_postfix(sender) == 0); +} + static void CheckDontWait() { const int receiver = socket_nid_postfix(2, 2, 0); const int sender = socket_nid_postfix(2, 2, 0); @@ -108,8 +125,8 @@ static void CheckDontWait() { std::uint32_t size = address.size(); Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0); char buffer[16]{}; - Iovec vector{buffer, sizeof(buffer)}; - Msghdr message{nullptr, 0, &vector, 1, nullptr, 0, 0}; + KernelIovec vector{buffer, sizeof(buffer)}; + GuestMsghdr message{nullptr, 0, &vector, 1, nullptr, 0, 0}; const auto start = std::chrono::steady_clock::now(); Require(recv_nid_postfix(receiver, buffer, sizeof(buffer), 0x80) == -1 && *__error_nid_postfix() == 35); Require(recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0x80, nullptr, nullptr) == -1 && *__error_nid_postfix() == 35); @@ -136,6 +153,7 @@ int main() { CheckAddressLengths(2); CheckAddressLengths(28); CheckDontWait(); + CheckSendToPortZero(); const int receiver = socket_nid_postfix(2, 2, 0); const int sender = socket_nid_postfix(2, 2, 17); Require(receiver >= 0 && sender >= 0 && receiver != sender); @@ -147,11 +165,11 @@ int main() { Require(connect_nid_postfix(sender, destination.data(), destination.size()) == 0); char vectorHead[] = "atomic "; char vectorTail[] = "datagram"; - Iovec vectorOutput[]{{vectorHead, sizeof(vectorHead) - 1}, {vectorTail, sizeof(vectorTail)}}; + KernelIovec vectorOutput[]{{vectorHead, sizeof(vectorHead) - 1}, {vectorTail, sizeof(vectorTail)}}; Require(sceKernelWritev(sender, vectorOutput, 2) == sizeof(vectorHead) + sizeof(vectorTail) - 1); char vectorFirst[sizeof(vectorHead) - 1]{}; char vectorSecond[sizeof(vectorTail)]{}; - Iovec vectorInput[]{{vectorFirst, sizeof(vectorFirst)}, {vectorSecond, sizeof(vectorSecond)}}; + KernelIovec vectorInput[]{{vectorFirst, sizeof(vectorFirst)}, {vectorSecond, sizeof(vectorSecond)}}; Require(sceKernelReadv(receiver, vectorInput, 2) == sizeof(vectorHead) + sizeof(vectorTail) - 1); Require(std::memcmp(vectorFirst, "atomic ", sizeof(vectorFirst)) == 0 && std::strcmp(vectorSecond, "datagram") == 0); int enabled = 1; @@ -211,8 +229,8 @@ int main() { Require(_ioctl_nid_postfix(receiver, 123, &queued) == -1 && *__error_nid_postfix() == 25); char head[] = "scatter "; char tail[] = "gather"; - Iovec parts[2]{{head, 8}, {tail, 6}}; - Msghdr outgoing{nullptr, 0, parts, 2, nullptr, 0, 0}; + KernelIovec parts[2]{{head, 8}, {tail, 6}}; + GuestMsghdr outgoing{nullptr, 0, parts, 2, nullptr, 0, 0}; Require(sendmsg_nid_postfix(sender, &outgoing, 0) == 14); const auto wait = [&] { queued = 0; @@ -224,22 +242,31 @@ int main() { }; wait(); char first[4]{}, second[16]{}; - Iovec pieces[2]{{first, sizeof(first)}, {second, sizeof(second)}}; + KernelIovec pieces[2]{{first, sizeof(first)}, {second, sizeof(second)}}; source.fill(0); unsigned char control[8]{}; - Msghdr incoming{source.data(), static_cast(source.size()), pieces, 2, control, sizeof(control), -1}; + GuestMsghdr incoming{source.data(), static_cast(source.size()), pieces, 2, control, sizeof(control), -1}; + Require(recvmsg_nid_postfix(receiver, &incoming, 2) == 14 && incoming.flags == 2); + incoming.nameLength = static_cast(source.size()); + incoming.controlLength = sizeof(control); Require(recvmsg_nid_postfix(receiver, &incoming, 0) == 14); Require(std::memcmp(first, "scat", 4) == 0 && std::memcmp(second, "ter gather", 10) == 0); Require(incoming.flags == 0 && incoming.controlLength == 0 && incoming.nameLength == 16 && source[1] == 2 && source[4] == 127); Require(sendmsg_nid_postfix(sender, &outgoing, 0) == 14); wait(); char shortBuffer[5]{}; - Iovec single{shortBuffer, sizeof(shortBuffer)}; - Msghdr truncated{nullptr, 0, &single, 1, nullptr, 0, 0}; - Require(recvmsg_nid_postfix(receiver, &truncated, 0) == 5); - Require(std::memcmp(shortBuffer, "scatt", 5) == 0 && truncated.flags == 0x10); + KernelIovec single{shortBuffer, sizeof(shortBuffer)}; + GuestMsghdr truncated{nullptr, 0, &single, 1, nullptr, 0, 0}; + Require(recvmsg_nid_postfix(receiver, &truncated, 2) == 5 && truncated.flags == 0x12); + Require(recvmsg_nid_postfix(receiver, &truncated, 0x80) == 5); + Require(std::memcmp(shortBuffer, "scatt", 5) == 0 && truncated.flags == 0x90); Require(recvmsg_nid_postfix(receiver, &truncated, 0) == -1 && *__error_nid_postfix() == 35); - Msghdr oversized{nullptr, 0, parts, 1025, nullptr, 0, 0}; + KernelIovec none{shortBuffer, 0}; + GuestMsghdr empty{nullptr, 0, &none, 1, nullptr, 0, -1}; + Require(fcntl_nid_postfix(receiver, 4, 2) == 0); + Require(recvmsg_nid_postfix(receiver, &empty, 0) == 0 && empty.flags == 0); + Require(fcntl_nid_postfix(receiver, 4, 6) == 0); + GuestMsghdr oversized{nullptr, 0, parts, 1025, nullptr, 0, 0}; Require(sendmsg_nid_postfix(sender, &oversized, 0) == -1 && *__error_nid_postfix() == 40); Require(recvmsg_nid_postfix(receiver, nullptr, 0) == -1 && *__error_nid_postfix() == 14); Require(close_nid_postfix(receiver) == 0); @@ -311,4 +338,5 @@ int main() { Require(closeOnExec >= 0 && fcntl_nid_postfix(closeOnExec, 3) == 2); Require(close_nid_postfix(closeOnExec) == 0); Require(socket_nid_postfix(2, 0x20000000, 0) == -1 && *__error_nid_postfix() == 43); + CheckUnreachablePortIgnored(); } diff --git a/core/libs/tests/GuestUlobjmgr.cpp b/core/libs/tests/GuestUlobjmgr.cpp index 0fd79500f7..3f6795a979 100644 --- a/core/libs/tests/GuestUlobjmgr.cpp +++ b/core/libs/tests/GuestUlobjmgr.cpp @@ -1,12 +1,8 @@ +#include "prx/ulobjmgr/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include -extern "C" { -int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id); -int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id); -} - namespace { constexpr int einval = 22; diff --git a/core/libs/tests/GuestUltCondition.cpp b/core/libs/tests/GuestUltCondition.cpp index 51741e4aa2..2f5c980fe4 100644 --- a/core/libs/tests/GuestUltCondition.cpp +++ b/core/libs/tests/GuestUltCondition.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceUlt/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -9,20 +10,9 @@ #include #include -extern "C" { -int APS5_VABI sceUltFinalize(); -int APS5_VABI sceUltMutexCreate(void*, const char*, void*, const UltMutexOptParam*, std::uint32_t); -int APS5_VABI sceUltMutexLock(void*); -int APS5_VABI sceUltMutexUnlock(void*); -int APS5_VABI sceUltMutexDestroy(void*); -int APS5_VABI _sceUltConditionVariableCreate(void*, const char*, void*, const void*, std::uint32_t); -int APS5_VABI sceUltConditionVariableWait(void*); -int APS5_VABI sceUltConditionVariableSignal(void*); -int APS5_VABI sceUltConditionVariableDestroy(void*); -} - constexpr int Null = -2139029503; constexpr int Invalid = -2139029500; +constexpr int Permission = -2139029499; constexpr int State = -2139029498; constexpr int Busy = -2139029497; @@ -41,8 +31,10 @@ struct Fixture { Object mutex; Object condition; - Fixture() { - CHECK(sceUltMutexCreate(&mutex, "mutex", nullptr, nullptr, 0x12000000) == 0); + explicit Fixture(bool recursive = false) { + UltMutexOptParam options{}; + options.attribute = recursive ? 1 : 0; + CHECK(sceUltMutexCreate(&mutex, "mutex", nullptr, &options, 0x12000000) == 0); CHECK(_sceUltConditionVariableCreate(&condition, "condition", &mutex, nullptr, 0x12000000) == 0); } @@ -58,10 +50,13 @@ struct Fixture { }; static void Validation() { - Fixture f; + Fixture f(true); Object unused; CHECK(sceUltMutexDestroy(nullptr) == Null); CHECK(sceUltMutexDestroy(&unused) == State); + CHECK(sceUltConditionVariableCreate(nullptr, nullptr, nullptr, nullptr, 0) == Null); + CHECK(sceUltConditionVariableCreate(&unused, nullptr, &unused, nullptr, 0) == Invalid); + CHECK(sceUltConditionVariableCreate(&f.condition, nullptr, &f.mutex, nullptr, 0) == State); CHECK(_sceUltConditionVariableCreate(nullptr, nullptr, nullptr, nullptr, 0) == Null); CHECK(_sceUltConditionVariableCreate(&unused, nullptr, &unused, nullptr, 0) == Invalid); CHECK(_sceUltConditionVariableCreate(&f.condition, nullptr, &f.mutex, nullptr, 0) == State); @@ -73,16 +68,16 @@ static void Validation() { CHECK(sceUltConditionVariableWait(&unused) == State); CHECK(_sceUltConditionVariableCreate(&unused, nullptr, nullptr, nullptr, 0) == Null); CHECK(sceUltConditionVariableWait(&f.condition) == State); - CHECK(sceUltMutexUnlock(&f.mutex) == State); + CHECK(sceUltMutexUnlock(&f.mutex) == Permission); bool unsupported = false; try { _sceUltConditionVariableCreate(&unused, nullptr, &f.mutex, &unused, 0); } catch (const std::runtime_error&) { unsupported = true; } CHECK(unsupported); f.Lock(); - CHECK(sceUltMutexDestroy(&f.mutex) == Busy); + CHECK(sceUltMutexDestroy(&f.mutex) == State); CHECK(sceUltMutexCreate(&f.mutex, nullptr, nullptr, nullptr, 0) == State); auto other = std::async(std::launch::async, [&] { - CHECK(sceUltMutexUnlock(&f.mutex) == State); + CHECK(sceUltMutexUnlock(&f.mutex) == Permission); CHECK(sceUltConditionVariableWait(&f.condition) == State); }); other.get(); @@ -91,9 +86,9 @@ static void Validation() { try { f.Wait(); } catch (const std::runtime_error&) { unsupported = true; } CHECK(unsupported); f.Unlock(); - CHECK(sceUltMutexDestroy(&f.mutex) == Busy); + CHECK(sceUltMutexDestroy(&f.mutex) == State); f.Unlock(); - CHECK(sceUltMutexDestroy(&f.mutex) == Busy); + CHECK(sceUltMutexDestroy(&f.mutex) == State); CHECK(sceUltConditionVariableDestroy(&f.condition) == 0); CHECK(sceUltMutexDestroy(&f.mutex) == 0); CHECK(sceUltMutexLock(&f.mutex) == State); @@ -121,13 +116,13 @@ static void Handoff() { entered = 1; f.Wait(); CHECK(payload == 42); - CHECK(sceUltMutexDestroy(&f.mutex) == Busy); + CHECK(sceUltMutexDestroy(&f.mutex) == State); f.Unlock(); }); Await(entered, 1); f.Lock(); CHECK(sceUltConditionVariableDestroy(&f.condition) == Busy); - CHECK(sceUltMutexDestroy(&f.mutex) == Busy); + CHECK(sceUltMutexDestroy(&f.mutex) == State); CHECK(waiter.wait_for(std::chrono::milliseconds(30)) == std::future_status::timeout); f.Unlock(); f.Lock(); diff --git a/core/libs/tests/GuestUltQueue.cpp b/core/libs/tests/GuestUltQueue.cpp index 2a93f78c0c..7a2a1ef562 100644 --- a/core/libs/tests/GuestUltQueue.cpp +++ b/core/libs/tests/GuestUltQueue.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceUlt/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -10,20 +11,6 @@ #include #include -extern "C" { -int APS5_VABI sceUltInitialize(); -int APS5_VABI sceUltFinalize(); -std::uint64_t APS5_VABI sceUltQueueDataResourcePoolGetWorkAreaSize(std::uint32_t, std::uint64_t, std::uint32_t); -int APS5_VABI sceUltQueueDataResourcePoolCreate(void*, const char*, std::uint32_t, std::uint64_t, std::uint32_t, void*, void*, const void*, std::uint32_t); -int APS5_VABI sceUltQueueDataResourcePoolDestroy(void*); -int APS5_VABI sceUltQueueCreate(void*, const char*, std::uint64_t, void*, void*, const void*, std::uint32_t); -int APS5_VABI sceUltQueueDestroy(void*); -int APS5_VABI sceUltQueuePush(void*, const void*); -int APS5_VABI sceUltQueueTryPush(void*, const void*); -int APS5_VABI sceUltQueuePop(void*, void*); -int APS5_VABI sceUltQueueTryPop(void*, void*); -} - static constexpr int Null = -2139029503; static constexpr int Alignment = -2139029502; static constexpr int Range = -2139029501; @@ -81,7 +68,7 @@ static void Validation() { CHECK(sceUltQueueTryPop(&fixture.first, &saved) == 0 && saved == value); CHECK(sceUltQueueCreate(&extra, nullptr, 9, nullptr, &fixture.pool, nullptr, 0) == Invalid); CHECK(sceUltQueueCreate(&extra, nullptr, 8, &extra, &fixture.pool, nullptr, 0) == Invalid); - CHECK(sceUltQueueCreate(&extra, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Again); + CHECK(sceUltQueueCreate(&extra, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Busy); CHECK(sceUltQueueCreate(nullptr, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Null); CHECK(sceUltQueueCreate(extra.bytes.data() + 1, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Alignment); CHECK(sceUltQueueCreate(&extra, nullptr, 0, nullptr, &fixture.pool, nullptr, 0) == Range); @@ -93,7 +80,11 @@ static void Validation() { CHECK(sceUltQueueDataResourcePoolDestroy(&fixture.pool) == Busy); CHECK(sceUltQueueDataResourcePoolDestroy(nullptr) == Null); CHECK(sceUltQueueDestroy(nullptr) == Null); - CHECK(sceUltQueueDataResourcePoolGetWorkAreaSize(3, 9, 2) == 1072); + CHECK(sceUltQueueDataResourcePoolGetWorkAreaSize(3, 9, 2) == 379); + CHECK(sceUltQueueDataResourcePoolGetWorkAreaSize(0, 0, 0) == 96); + CHECK(sceUltWaitingQueueResourcePoolGetWorkAreaSize(0, 0) == 64); + CHECK(sceUltWaitingQueueResourcePoolGetWorkAreaSize(3, 2) == 320); + CHECK(sceUltWaitingQueueResourcePoolGetWorkAreaSize(0, 0x08000000u) == 0x100000040ull); bool overflow = false; try { sceUltQueueDataResourcePoolGetWorkAreaSize(1, UINT64_MAX, 1); } catch (const std::out_of_range&) { overflow = true; } @@ -151,13 +142,13 @@ static void Blocking() { CHECK(sceUltQueuePush(&fixture.first, &first) == 0); auto producer = std::async(std::launch::async, [&] { return sceUltQueuePush(&fixture.first, &second); }); WaitUntilBlocked(producer); - CHECK(sceUltQueueDestroy(&fixture.first) == Busy); + CHECK(sceUltQueueDestroy(&fixture.first) == State); CHECK(sceUltQueuePop(&fixture.first, &output) == 0 && output == first); CHECK(producer.get() == 0); CHECK(sceUltQueuePop(&fixture.first, &output) == 0 && output == second); auto consumer = std::async(std::launch::async, [&] { return sceUltQueuePop(&fixture.first, &output); }); WaitUntilBlocked(consumer); - CHECK(sceUltQueueDestroy(&fixture.first) == Busy); + CHECK(sceUltQueueDestroy(&fixture.first) == State); CHECK(sceUltQueuePush(&fixture.first, &first) == 0); CHECK(consumer.get() == 0 && output == first); fixture.Destroy(); @@ -175,7 +166,7 @@ static void SharedPool() { CHECK(sceUltQueueTryPop(&fixture.second, &output) == Again); auto producer = std::async(std::launch::async, [&] { return sceUltQueuePush(&fixture.second, &second); }); WaitUntilBlocked(producer); - CHECK(sceUltQueueDestroy(&fixture.second) == Busy); + CHECK(sceUltQueueDestroy(&fixture.second) == State); CHECK(sceUltQueueDestroy(&fixture.first) == 0); CHECK(producer.get() == 0); CHECK(consumer.get() == 0 && output == second); @@ -196,8 +187,8 @@ static void Finalize() { auto consumer = std::async(std::launch::async, [&] { return sceUltQueuePop(&fixture.second, &output); }); WaitUntilBlocked(producer); WaitUntilBlocked(consumer); - CHECK(sceUltQueueDestroy(&fixture.first) == Busy); - CHECK(sceUltQueueDestroy(&fixture.second) == Busy); + CHECK(sceUltQueueDestroy(&fixture.first) == State); + CHECK(sceUltQueueDestroy(&fixture.second) == State); CHECK(sceUltFinalize() == 0); CHECK(producer.get() == State); CHECK(consumer.get() == State && output == 123); diff --git a/core/libs/tests/GuestUltSync.cpp b/core/libs/tests/GuestUltSync.cpp index 282643f205..27785c0575 100644 --- a/core/libs/tests/GuestUltSync.cpp +++ b/core/libs/tests/GuestUltSync.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceUlt/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -9,53 +10,13 @@ #include #include -extern "C" { -int APS5_VABI sceUltInitialize(); -int APS5_VABI sceUltFinalize(); -int APS5_VABI _sceUltMutexCreate(void*, const char*, void*, const void*, std::uint32_t); -int APS5_VABI sceUltMutexLock(void*); -int APS5_VABI sceUltMutexTryLock(void*); -int APS5_VABI sceUltMutexUnlock(void*); -int APS5_VABI sceUltMutexDestroy(void*); -int APS5_VABI sceUltSemaphoreOptParamInitialize(void*, std::uint32_t); -int APS5_VABI sceUltQueueOptParamInitialize(void*, std::uint32_t); -int APS5_VABI sceUltQueueDataResourcePoolOptParamInitialize(void*, std::uint32_t); -int APS5_VABI sceUltWaitingQueueResourcePoolOptParamInitialize(void*, std::uint32_t); -int APS5_VABI sceUltUlthreadOptParamInitialize(void*, std::uint32_t); -int APS5_VABI _sceUltSemaphoreOptParamInitialize(void*, std::uint32_t); -int APS5_VABI _sceUltQueueOptParamInitialize(void*, std::uint32_t); -int APS5_VABI _sceUltQueueDataResourcePoolOptParamInitialize(void*, std::uint32_t); -int APS5_VABI _sceUltWaitingQueueResourcePoolOptParamInitialize(void*, std::uint32_t); -int APS5_VABI _sceUltUlthreadOptParamInitialize(void*, std::uint32_t); -int APS5_VABI _sceUltWaitingQueueResourcePoolCreate(void*, const char*, std::uint32_t, std::uint32_t, void*, const void*, std::uint32_t); -int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void*); -int APS5_VABI sceUltReaderWriterLockOptParamInitialize(void*, std::uint32_t); -int APS5_VABI _sceUltReaderWriterLockOptParamInitialize(void*, std::uint32_t); -int APS5_VABI sceUltReaderWriterLockCreate(void*, const char*, void*, const void*, std::uint32_t); -int APS5_VABI _sceUltReaderWriterLockCreate(void*, const char*, void*, const void*, std::uint32_t); -int APS5_VABI sceUltReaderWriterLockDestroy(void*); -int APS5_VABI sceUltReaderWriterLockLockRead(void*); -int APS5_VABI sceUltReaderWriterLockTryLockRead(void*); -int APS5_VABI sceUltReaderWriterLockUnlockRead(void*); -int APS5_VABI sceUltReaderWriterLockLockWrite(void*); -int APS5_VABI sceUltReaderWriterLockTryLockWrite(void*); -int APS5_VABI sceUltReaderWriterLockUnlockWrite(void*); -std::uint64_t APS5_VABI sceUltUlthreadRuntimeGetWorkAreaSize(std::uint32_t, std::uint32_t); -int APS5_VABI sceUltUlthreadRuntimeCreate(void*, const char*, std::uint32_t, std::uint32_t, void*, const void*, std::uint32_t); -int APS5_VABI sceUltUlthreadRuntimeDestroy(void*); -int APS5_VABI sceUltUlthreadCreate(void*, const char*, UltUlthreadEntry, std::uint64_t, void*, std::uint64_t, void*, const void*, std::uint32_t); -int APS5_VABI sceUltUlthreadJoin(void*, std::int32_t*); -int APS5_VABI sceUltUlthreadTryJoin(void*, std::int32_t*); -int APS5_VABI sceUltUlthreadGetSelf(void**); -int APS5_VABI sceUltUlthreadYield(); -int APS5_VABI sceUltUlthreadExit(std::int32_t); -} - namespace { constexpr int Ok = 0; constexpr int Null = -2139029503; constexpr int Invalid = -2139029500; +constexpr int Permission = -2139029499; +constexpr int Again = -2139029496; constexpr int State = -2139029498; constexpr int Busy = -2139029497; @@ -75,7 +36,9 @@ void MutexTryLock() { Object mutex{}; CHECK(sceUltMutexTryLock(nullptr) == Null); CHECK(sceUltMutexTryLock(&mutex) == State); - CHECK(_sceUltMutexCreate(&mutex, "trylock", nullptr, nullptr, 0) == Ok); + UltMutexOptParam options{}; + options.attribute = 1; + CHECK(sceUltMutexCreate(&mutex, "trylock", nullptr, &options, 0) == Ok); CHECK(sceUltMutexTryLock(&mutex) == Ok); CHECK(sceUltMutexTryLock(&mutex) == Ok); std::atomic other{0}; @@ -85,35 +48,35 @@ void MutexTryLock() { std::thread([&] { other = sceUltMutexTryLock(&mutex); }).join(); CHECK(other == Busy); CHECK(sceUltMutexUnlock(&mutex) == Ok); - CHECK(sceUltMutexUnlock(&mutex) == State); + CHECK(sceUltMutexUnlock(&mutex) == Permission); std::thread([&] { other = sceUltMutexTryLock(&mutex); if (other == Ok) other = sceUltMutexUnlock(&mutex); }).join(); CHECK(other == Ok); CHECK(sceUltMutexLock(&mutex) == Ok); - CHECK(sceUltMutexDestroy(&mutex) == Busy); + CHECK(sceUltMutexDestroy(&mutex) == State); CHECK(sceUltMutexUnlock(&mutex) == Ok); CHECK(sceUltMutexDestroy(&mutex) == Ok); CHECK(sceUltMutexTryLock(&mutex) == State); } void OptParams() { - using Initializer = int (APS5_VABI *)(void*, std::uint32_t); + using Initializer = decltype(&sceUltSemaphoreOptParamInitialize); for (const Initializer initialize : {sceUltSemaphoreOptParamInitialize, sceUltQueueOptParamInitialize, sceUltQueueDataResourcePoolOptParamInitialize, sceUltWaitingQueueResourcePoolOptParamInitialize, sceUltUlthreadOptParamInitialize, sceUltReaderWriterLockOptParamInitialize, _sceUltSemaphoreOptParamInitialize, _sceUltQueueOptParamInitialize, _sceUltQueueDataResourcePoolOptParamInitialize, - _sceUltWaitingQueueResourcePoolOptParamInitialize, _sceUltUlthreadOptParamInitialize, _sceUltReaderWriterLockOptParamInitialize}) { - std::uint8_t bytes[136]; + _sceUltWaitingQueueResourcePoolOptParamInitialize, _sceUltUlthreadOptParamInitialize, _sceUltReaderWriterLockOptParamInitialize, + sceUltConditionVariableOptParamInitialize, _sceUltConditionVariableOptParamInitialize}) { + alignas(UltOptParam) std::uint8_t bytes[136]; std::memset(bytes, 0xa5, sizeof(bytes)); CHECK(initialize(nullptr, 0) == Null); - CHECK(initialize(bytes, 0) == Ok); + CHECK(initialize(reinterpret_cast(bytes), 0) == Ok); for (std::size_t index = 0; index < 128; ++index) CHECK(bytes[index] == 0); for (std::size_t index = 128; index < sizeof(bytes); ++index) CHECK(bytes[index] == 0xa5); } } - void WaitUntil(const std::atomic& flag) { const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); while (!flag.load() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -219,7 +182,7 @@ void ReaderWriterPool() { Object pool{}; Object rwlock{}; std::uint8_t workArea[2048]; - CHECK(_sceUltWaitingQueueResourcePoolCreate(&pool, "pool", 2, 2, workArea, nullptr, 0) == Ok); + CHECK(_sceUltWaitingQueueResourcePoolCreate(&pool, "pool", 2, 2, workArea, nullptr) == Ok); CHECK(sceUltReaderWriterLockCreate(&rwlock, "pooled", &pool, nullptr, 0) == Ok); CHECK(sceUltReaderWriterLockLockWrite(&rwlock) == Ok); std::atomic started{false}; @@ -268,7 +231,7 @@ void ReaderWriterUnderscored() { CHECK(_sceUltReaderWriterLockCreate(nullptr, "underscored", nullptr, optParam, 0) == Null); CHECK(_sceUltReaderWriterLockCreate(&rwlock, "underscored", &pool, optParam, 0) == Invalid); for (const auto byte : rwlock.bytes) CHECK(byte == 0x5a); - CHECK(_sceUltWaitingQueueResourcePoolCreate(&pool, "pool", 2, 2, workArea, nullptr, 0) == Ok); + CHECK(_sceUltWaitingQueueResourcePoolCreate(&pool, "pool", 2, 2, workArea, nullptr) == Ok); CHECK(_sceUltReaderWriterLockCreate(&rwlock, "underscored", &pool, optParam, 0) == Ok); for (const auto byte : rwlock.bytes) CHECK(byte == 0); CHECK(_sceUltReaderWriterLockCreate(&rwlock, "underscored", &pool, optParam, 0) == State); @@ -299,6 +262,7 @@ std::int32_t APS5_VABI ExitingEntry(std::uint64_t status) { if (sceUltUlthreadGetSelf(&self) == Ok) gSelf = self; gYield = sceUltUlthreadYield(); sceUltUlthreadExit(static_cast(status)); + gPastExit = true; return -1; } @@ -322,10 +286,10 @@ void Ulthreads() { gSelf = nullptr; CHECK(sceUltUlthreadCreate(&second, "tryjoin", ExitingEntry, 7, context, sizeof(context), &runtime, nullptr, 0) == Ok); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); - int joined = Busy; - while (joined == Busy && std::chrono::steady_clock::now() < deadline) { + int joined = Again; + while (joined == Again && std::chrono::steady_clock::now() < deadline) { joined = sceUltUlthreadTryJoin(&second, &status); - if (joined == Busy) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + if (joined == Again) std::this_thread::sleep_for(std::chrono::milliseconds(1)); } CHECK(joined == Ok && status == 7 && gSelf.load() == &second); CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == Ok); diff --git a/core/libs/tests/GuestUltThread.cpp b/core/libs/tests/GuestUltThread.cpp index 0d074c1d20..50fef67d4c 100644 --- a/core/libs/tests/GuestUltThread.cpp +++ b/core/libs/tests/GuestUltThread.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceUlt/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -8,43 +9,17 @@ #include #include -extern "C" { -int APS5_VABI sceUltInitialize(); -int APS5_VABI sceUltFinalize(); -std::uint64_t APS5_VABI sceUltUlthreadRuntimeGetWorkAreaSize(std::uint32_t, std::uint32_t); -int APS5_VABI sceUltUlthreadRuntimeCreate(void*, const char*, std::uint32_t, std::uint32_t, void*, const void*, std::uint32_t); -int APS5_VABI sceUltUlthreadCreate(void*, const char*, UltUlthreadEntry, std::uint64_t, void*, std::uint64_t, void*, const void*, std::uint32_t); -int APS5_VABI sceUltUlthreadJoin(void*, std::int32_t*); -int APS5_VABI sceUltUlthreadTryJoin(void*, std::int32_t*); -int APS5_VABI sceUltUlthreadRuntimeDestroy(void*); -int APS5_VABI _sceUltUlthreadRuntimeOptParamInitialize(void*, std::uint32_t); -int APS5_VABI _sceUltUlthreadRuntimeCreate(void*, const char*, std::uint32_t, std::uint32_t, void*, const void*, std::uint32_t); -int APS5_VABI _sceUltUlthreadCreate(void*, const char*, UltUlthreadEntry, std::uint64_t, void*, std::uint64_t, void*, const void*, std::uint32_t); -int APS5_VABI _sceUltWaitingQueueResourcePoolCreate(void*, const char*, std::uint32_t, std::uint32_t, void*, const void*, std::uint32_t); -int APS5_VABI _sceUltSemaphoreCreate(void*, const char*, std::int32_t, void*, const void*, std::uint32_t); -int APS5_VABI sceUltSemaphoreAcquire(void*, std::int32_t); -int APS5_VABI sceUltSemaphoreRelease(void*, std::int32_t); -int APS5_VABI _sceUltMutexOptParamInitialize(void*, std::uint32_t); -int APS5_VABI _sceUltMutexCreate(void*, const char*, void*, const void*, std::uint32_t); -int APS5_VABI sceUltMutexLock(void*); -int APS5_VABI sceUltMutexUnlock(void*); -int APS5_VABI _sceUltQueueDataResourcePoolCreate(void*, const char*, std::uint32_t, std::uint64_t, std::uint32_t, void*, void*, const void*, std::uint32_t); -int APS5_VABI _sceUltQueueCreate(void*, const char*, std::uint64_t, void*, void*, const void*, std::uint32_t); -int APS5_VABI sceUltQueuePush(void*, const void*); -int APS5_VABI sceUltQueuePop(void*, void*); -int APS5_VABI sceUltMutexDestroy(void*); -int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void*); -int APS5_VABI sceUltSemaphoreDestroy(void*); -int APS5_VABI sceUltQueueDestroy(void*); -int APS5_VABI sceUltQueueDataResourcePoolDestroy(void*); -int APS5_VABI sceUltConditionVariableDestroy(void*); -int APS5_VABI sceUltGetConditionVariableInfo(); -} +struct UltConditionVariableInfo; +struct UltWaitingThreadInfo; static constexpr int Ok = 0; static constexpr int Null = -2139029503; +static constexpr int Alignment = -2139029502; +static constexpr int Invalid = -2139029500; +static constexpr int Permission = -2139029499; static constexpr int State = -2139029498; static constexpr int Busy = -2139029497; +static constexpr int Again = -2139029496; static void Require(bool value, int line) { if (!value) { @@ -108,11 +83,13 @@ static void Validation() { std::int32_t status = 0; CHECK(sceUltUlthreadJoin(&thread, &status) == Ok && status == 0x27); CHECK(sceUltFinalize() == Ok); - CHECK(sceUltUlthreadRuntimeGetWorkAreaSize(1, 1) == 256u + 16u * 1024u); + CHECK(sceUltUlthreadRuntimeGetWorkAreaSize(1, 1) == 1184); CHECK(sceUltConditionVariableDestroy(nullptr) == -2139029503); + CHECK(sceUltReaderWriterLockDestroy(nullptr) == Null); bool threwInfo = false; try { - sceUltGetConditionVariableInfo(); + sceUltGetConditionVariableInfo(nullptr, nullptr, nullptr, 0); + } catch (const std::runtime_error&) { threwInfo = true; } @@ -120,23 +97,27 @@ static void Validation() { } static void TryJoin() { + CHECK(sceUltUlthreadRuntimeGetWorkAreaSize(0, 0) == 384); + CHECK(sceUltUlthreadRuntimeGetWorkAreaSize(1, 1) == 1184); + CHECK(sceUltUlthreadRuntimeGetWorkAreaSize(3, 2) == 2128); + CHECK(sceUltUlthreadRuntimeGetWorkAreaSize(0x80000000u, 0) == 0xA00000180ull); Runtime runtime; Thread thread; std::uint8_t optParam[128]; std::memset(optParam, 0xff, sizeof(optParam)); CHECK(_sceUltUlthreadRuntimeOptParamInitialize(nullptr, 0) == Null); - CHECK(_sceUltUlthreadRuntimeOptParamInitialize(optParam, 0) == Ok && optParam[0] == 0); - CHECK(_sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == Ok); - CHECK(_sceUltUlthreadCreate(&thread, "parked", Parked, 0x3C, nullptr, 0, &runtime, nullptr, 0) == Ok); + CHECK(_sceUltUlthreadRuntimeOptParamInitialize(reinterpret_cast(optParam), 0) == Ok && optParam[0] == 0); + CHECK(_sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr) == Ok); + CHECK(_sceUltUlthreadCreate(&thread, "parked", Parked, 0x3C, nullptr, 0, &runtime, nullptr) == Ok); while (!gStarted.load(std::memory_order_acquire)) std::this_thread::yield(); std::int32_t status = 0; const int running = sceUltUlthreadTryJoin(&thread, &status); gRelease.store(true, std::memory_order_release); int joined; - while ((joined = sceUltUlthreadTryJoin(&thread, &status)) == Busy) std::this_thread::yield(); + while ((joined = sceUltUlthreadTryJoin(&thread, &status)) == Again) std::this_thread::yield(); - CHECK(running == Busy); + CHECK(running == Again); CHECK(joined == Ok && status == 0x3C); CHECK(sceUltUlthreadTryJoin(&thread, &status) == State); CHECK(sceUltUlthreadTryJoin(nullptr, &status) == Null); @@ -149,16 +130,16 @@ static void Destroy() { Thread thread; CHECK(sceUltUlthreadRuntimeDestroy(nullptr) == Null); CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == State); - CHECK(_sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == Ok); - CHECK(_sceUltUlthreadRuntimeCreate(&other, "other", 1, 1, nullptr, nullptr, 0) == Ok); - CHECK(_sceUltUlthreadCreate(&thread, "thread", Immediate, 0x11, nullptr, 0, &runtime, nullptr, 0) == Ok); + CHECK(_sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr) == Ok); + CHECK(_sceUltUlthreadRuntimeCreate(&other, "other", 1, 1, nullptr, nullptr) == Ok); + CHECK(_sceUltUlthreadCreate(&thread, "thread", Immediate, 0x11, nullptr, 0, &runtime, nullptr) == Ok); CHECK(sceUltUlthreadRuntimeDestroy(&other) == Ok); - CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == Busy); + CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == State); std::int32_t status = 0; CHECK(sceUltUlthreadJoin(&thread, &status) == Ok && status == 0x11); CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == Ok); CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == State); - CHECK(_sceUltUlthreadCreate(&thread, "thread", Immediate, 0, nullptr, 0, &runtime, nullptr, 0) == State); + CHECK(_sceUltUlthreadCreate(&thread, "thread", Immediate, 0, nullptr, 0, &runtime, nullptr) == State); CHECK(sceUltFinalize() == Ok); } @@ -166,31 +147,94 @@ static void UnderscoredCreate() { alignas(8) static std::uint8_t pool[256]; alignas(8) static std::uint8_t semaphore[256]; alignas(8) static std::uint8_t unknownPool[256]; - CHECK(_sceUltWaitingQueueResourcePoolCreate(nullptr, "pool", 1, 1, nullptr, nullptr, 0) == Null); - CHECK(_sceUltWaitingQueueResourcePoolCreate(pool, "pool", 1, 1, nullptr, nullptr, 0) == Ok); - CHECK(_sceUltSemaphoreCreate(nullptr, "semaphore", 1, pool, nullptr, 0) == Null); - CHECK(_sceUltSemaphoreCreate(semaphore, "semaphore", -1, pool, nullptr, 0) != Ok); - CHECK(_sceUltSemaphoreCreate(semaphore, "semaphore", 2, unknownPool, nullptr, 0) != Ok); - CHECK(_sceUltSemaphoreCreate(semaphore, "semaphore", 2, pool, nullptr, 0) == Ok); - CHECK(_sceUltSemaphoreCreate(semaphore, "semaphore", 2, pool, nullptr, 0) == State); + CHECK(_sceUltWaitingQueueResourcePoolCreate(nullptr, "pool", 1, 1, nullptr, nullptr) == Null); + alignas(8) std::uint8_t workArea[1024]{}; + alignas(8) std::uint32_t commonOptions[32]{}; + commonOptions[0] = 0x6C696275; + commonOptions[1] = 0x6C746D61; + commonOptions[2] = 0x67696300; + CHECK(_sceUltWaitingQueueResourcePoolCreate(pool, "pool", 1, 1, nullptr, nullptr) == Null); + CHECK(_sceUltWaitingQueueResourcePoolCreate(pool, "pool", 1, 1, workArea + 1, nullptr) == Alignment); + commonOptions[31] = 1; + CHECK(_sceUltWaitingQueueResourcePoolCreate(pool, "pool", 1, 1, workArea, commonOptions) == Invalid); + commonOptions[31] = 0; + CHECK(_sceUltWaitingQueueResourcePoolCreate(pool, "pool", 1, 1, workArea, commonOptions) == Ok); + CHECK(_sceUltSemaphoreCreate(nullptr, "semaphore", 1, pool, nullptr) == Null); + CHECK(_sceUltSemaphoreCreate(semaphore, "semaphore", UINT32_MAX, pool, nullptr) == Ok); + CHECK(sceUltSemaphoreTryAcquire(semaphore, 1) == Again); + CHECK(sceUltSemaphoreRelease(semaphore, 1) == Ok); + CHECK(sceUltSemaphoreTryAcquire(semaphore, 1) == Again); + CHECK(sceUltSemaphoreDestroy(semaphore) == Ok); + CHECK(sceUltSemaphoreAcquire(nullptr, 0) == Null); + CHECK(sceUltSemaphoreTryAcquire(nullptr, 0) == Null); + CHECK(sceUltSemaphoreRelease(nullptr, 0) == Null); + CHECK(sceUltSemaphoreAcquire(semaphore + 1, 0) == Alignment); + CHECK(sceUltSemaphoreTryAcquire(semaphore + 1, 0) == Alignment); + CHECK(sceUltSemaphoreRelease(semaphore + 1, 0) == Alignment); + CHECK(sceUltSemaphoreDestroy(semaphore + 1) == Alignment); + CHECK(_sceUltSemaphoreCreate(semaphore, "semaphore", 2, unknownPool, nullptr) != Ok); + commonOptions[0] = 0; + CHECK(_sceUltSemaphoreCreate(semaphore, "semaphore", 2, pool, commonOptions) == Invalid); + commonOptions[0] = 0x6C696275; + CHECK(_sceUltSemaphoreCreate(semaphore, "semaphore", 2, pool, commonOptions) == Ok); + CHECK(_sceUltSemaphoreCreate(semaphore, "semaphore", 2, pool, nullptr) == State); CHECK(sceUltSemaphoreAcquire(semaphore, 2) == Ok); CHECK(sceUltSemaphoreRelease(semaphore, 2) == Ok); - alignas(8) static std::uint8_t mutexOptParam[128]; + alignas(8) static std::uint8_t mutexOptParam[136]; alignas(8) static std::uint8_t mutex[256]; std::memset(mutexOptParam, 0xFF, sizeof(mutexOptParam)); CHECK(_sceUltMutexOptParamInitialize(nullptr, 0) == Null); - CHECK(_sceUltMutexOptParamInitialize(mutexOptParam, 0) == Ok); - CHECK(mutexOptParam[8] == 0 && mutexOptParam[15] == 0); - CHECK(_sceUltMutexCreate(nullptr, "mutex", pool, mutexOptParam, 0) == Null); - CHECK(_sceUltMutexCreate(mutex, "mutex", pool, mutexOptParam, 0) == Ok); + CHECK(_sceUltMutexOptParamInitialize(reinterpret_cast(mutexOptParam + 1), 0) == Alignment); + CHECK(mutexOptParam[0] == 0xFF && mutexOptParam[127] == 0xFF); + CHECK(_sceUltMutexOptParamInitialize(reinterpret_cast(mutexOptParam), 0x12345678) == Ok); + UltMutexOptParam options{}; + std::memcpy(&options, mutexOptParam, sizeof(options)); + CHECK(options.header[0] == 0x6C696275 && options.header[1] == 0x6C746D61 && options.header[2] == 0x67696300); + CHECK(options.buildVersion == 0x12345678 && options.attribute == 0); + for (std::size_t index = 16; index < 128; ++index) CHECK(mutexOptParam[index] == 0); + for (std::size_t index = 128; index < sizeof(mutexOptParam); ++index) CHECK(mutexOptParam[index] == 0xFF); + CHECK(_sceUltMutexCreate(mutex, nullptr, pool, reinterpret_cast(mutexOptParam)) == Null); + CHECK(_sceUltMutexCreate(mutex, "mutex", nullptr, reinterpret_cast(mutexOptParam)) == Null); + CHECK(_sceUltMutexCreate(mutex, "mutex", pool, reinterpret_cast(mutexOptParam + 1)) == Alignment); + mutexOptParam[127] = 1; + CHECK(_sceUltMutexCreate(mutex, "mutex", pool, reinterpret_cast(mutexOptParam)) == Invalid); + mutexOptParam[127] = 0; + mutexOptParam[16] = 2; + CHECK(_sceUltMutexCreate(mutex, "mutex", pool, reinterpret_cast(mutexOptParam)) == Invalid); + mutexOptParam[16] = 0; + mutexOptParam[0] = 0; + CHECK(_sceUltMutexCreate(mutex, "mutex", pool, reinterpret_cast(mutexOptParam)) == Invalid); + CHECK(_sceUltMutexOptParamInitialize(reinterpret_cast(mutexOptParam), 0) == Ok); + CHECK(_sceUltMutexCreate(nullptr, "mutex", pool, reinterpret_cast(mutexOptParam)) == Null); + CHECK(_sceUltMutexCreate(mutex, "mutex", pool, reinterpret_cast(mutexOptParam)) == Ok); CHECK(sceUltMutexLock(mutex) == Ok); + CHECK(sceUltMutexLock(mutex) == State); + std::thread foreignUnlock([&] { CHECK(sceUltMutexUnlock(mutex) == Permission); }); + foreignUnlock.join(); + CHECK(sceUltMutexUnlock(mutex) == Ok); + CHECK(sceUltMutexUnlock(mutex) == Permission); + CHECK(sceUltMutexDestroy(mutex) == Ok); + mutexOptParam[16] = 1; + CHECK(_sceUltMutexCreate(mutex, "mutex", pool, reinterpret_cast(mutexOptParam)) == Ok); + CHECK(sceUltMutexLock(mutex) == Ok); + CHECK(sceUltMutexLock(mutex) == Ok); + CHECK(sceUltMutexUnlock(mutex) == Ok); + CHECK(sceUltMutexDestroy(mutex) == State); CHECK(sceUltMutexUnlock(mutex) == Ok); alignas(8) static std::uint8_t dataPool[512]; alignas(8) static std::uint8_t queue[512]; - CHECK(_sceUltQueueDataResourcePoolCreate(nullptr, "data", 1, 8, 1, pool, nullptr, nullptr, 0) == Null); - CHECK(_sceUltQueueDataResourcePoolCreate(dataPool, "data", 1, 8, 1, pool, nullptr, nullptr, 0) == Ok); - CHECK(_sceUltQueueCreate(nullptr, "queue", 8, pool, dataPool, nullptr, 0) == Null); - CHECK(_sceUltQueueCreate(queue, "queue", 8, pool, dataPool, nullptr, 0) == Ok); + CHECK(_sceUltQueueDataResourcePoolCreate(nullptr, "data", 1, 8, 1, pool, nullptr, nullptr) == Null); + CHECK(_sceUltQueueDataResourcePoolCreate(dataPool, "data", 1, 8, 1, pool, nullptr, nullptr) == Null); + commonOptions[4] = 1; + CHECK(_sceUltQueueDataResourcePoolCreate(dataPool, "data", 1, 8, 1, pool, workArea, commonOptions) == Invalid); + commonOptions[4] = 0; + CHECK(_sceUltQueueDataResourcePoolCreate(dataPool, "data", 1, 8, 1, pool, workArea, commonOptions) == Ok); + CHECK(_sceUltQueueCreate(nullptr, "queue", 8, pool, dataPool, nullptr) == Null); + commonOptions[31] = 1; + CHECK(_sceUltQueueCreate(queue, "queue", 8, pool, dataPool, commonOptions) == Invalid); + commonOptions[31] = 0; + CHECK(_sceUltQueueCreate(queue, "queue", 8, pool, dataPool, reinterpret_cast(commonOptions) + 1) == Alignment); + CHECK(_sceUltQueueCreate(queue, "queue", 8, pool, dataPool, commonOptions) == Ok); std::uint64_t value = 0x1122334455667788ull, output = 0; CHECK(sceUltQueuePush(queue, &value) == Ok); CHECK(sceUltQueuePop(queue, &output) == Ok && output == value); @@ -198,7 +242,7 @@ static void UnderscoredCreate() { CHECK(sceUltWaitingQueueResourcePoolDestroy(unknownPool) == State); CHECK(sceUltMutexDestroy(nullptr) == Null); CHECK(sceUltMutexLock(mutex) == Ok); - CHECK(sceUltMutexDestroy(mutex) == Busy); + CHECK(sceUltMutexDestroy(mutex) == State); std::atomic waiterStarted{false}; std::atomic waiterAcquired{false}; std::thread waiter([&] { @@ -210,8 +254,8 @@ static void UnderscoredCreate() { while (!waiterStarted.load(std::memory_order_acquire)) std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(20)); CHECK(!waiterAcquired.load(std::memory_order_acquire)); - CHECK(sceUltMutexDestroy(mutex) == Busy); - CHECK(sceUltWaitingQueueResourcePoolDestroy(pool) == Busy); + CHECK(sceUltMutexDestroy(mutex) == State); + CHECK(sceUltWaitingQueueResourcePoolDestroy(pool) == State); CHECK(sceUltMutexUnlock(mutex) == Ok); waiter.join(); CHECK(waiterAcquired.load(std::memory_order_acquire)); diff --git a/core/libs/tests/GuestUmtxOp.cpp b/core/libs/tests/GuestUmtxOp.cpp index 97eef1d837..ee4befc68f 100644 --- a/core/libs/tests/GuestUmtxOp.cpp +++ b/core/libs/tests/GuestUmtxOp.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -8,12 +11,6 @@ #include #include -extern "C" { -int APS5_VABI _umtx_op_nid_postfix(void* object, int operation, std::uint64_t value, void* sizeArgument, void* timeArgument); -int* APS5_VABI __error_nid_postfix(); -int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); -} - static constexpr int UMTX_OP_WAIT = 2; static constexpr int UMTX_OP_WAKE = 3; static constexpr int UMTX_OP_MUTEX_LOCK = 5; diff --git a/core/libs/tests/GuestUnderflowError.cpp b/core/libs/tests/GuestUnderflowError.cpp new file mode 100644 index 0000000000..b1bfda125c --- /dev/null +++ b/core/libs/tests/GuestUnderflowError.cpp @@ -0,0 +1,36 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +struct TypeRecord { const void* const* vtable; const char* name; const TypeRecord* base; }; +struct Vtable { std::ptrdiff_t offset; const TypeRecord* type; const void* destroy; const void* deleteObject; const void* what; }; + +extern "C" { +extern const TypeRecord _ZTISt13runtime_error_nid_postfix; +extern const TypeRecord _ZTISt15underflow_error_nid_postfix; +extern const Vtable _ZTVSt13runtime_error_nid_postfix; +extern const Vtable _ZTVSt15underflow_error_nid_postfix; +} + +static void Require(bool value) { if (!value) std::abort(); } + +int main() { + const auto& type = _ZTISt15underflow_error_nid_postfix; + const auto& runtimeType = _ZTISt13runtime_error_nid_postfix; + Require(std::strcmp(type.name, "St15underflow_error") == 0); + Require(type.base == &runtimeType && type.vtable == runtimeType.vtable); + const auto& table = _ZTVSt15underflow_error_nid_postfix; + const auto& runtimeTable = _ZTVSt13runtime_error_nid_postfix; + Require(table.offset == 0 && table.type == &type); + Require(table.destroy == runtimeTable.destroy && table.deleteObject == runtimeTable.deleteObject && table.what == runtimeTable.what); + LibcException::ExceptionObject object{}; + _ZNSt13runtime_errorC1EPKc_nid_postfix(&object, "value too small"); + Require(object.message != nullptr && std::strcmp(object.message, "value too small") == 0); + _ZNSt15underflow_errorD1Ev_nid_postfix(&object); + Require(object.message == nullptr); + _ZNSt15underflow_errorD1Ev_nid_postfix(&object); + Require(object.message == nullptr); + return 0; +} diff --git a/core/libs/tests/GuestUnderscoreEntries.cpp b/core/libs/tests/GuestUnderscoreEntries.cpp new file mode 100644 index 0000000000..6d62580b8f --- /dev/null +++ b/core/libs/tests/GuestUnderscoreEntries.cpp @@ -0,0 +1,286 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static_assert(sizeof(GuestSigaction) == 32); + +static_assert(sizeof(GuestMsghdr) == 48); + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "underscore entry check failed at line %d, errno %d\n", line, *__error_nid_postfix()); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +constexpr int Inet = 2; +constexpr int Stream = 1; +constexpr int Datagram = 2; +constexpr int TcpLevel = 6; +constexpr int TcpNoDelay = 1; +constexpr int UnknownTcpOption = 0x7777; +constexpr int OutOfBand = 0x1; +constexpr int Truncated = 0x10; +constexpr int Interrupted = 4; +constexpr int BadDescriptor = 9; +constexpr int BadAddress = 14; +constexpr int Invalid = 22; +constexpr int NoProtocolOption = 42; +constexpr int NotSupported = 45; + +using Address = std::array; + +static bool Fails(std::int64_t result, int error) { + return result == -1 && *__error_nid_postfix() == error; +} + +static bool Filled(const Address& address, std::uint8_t value) { + return std::all_of(address.begin(), address.end(), [value](std::uint8_t byte) { return byte == value; }); +} + +static void StreamSockets() { + const int listener = socket_nid_postfix(Inet, Stream, 0); + Require(listener >= 0); + Address address{16, Inet, 0, 0, 127, 0, 0, 1}; + Require(_bind_nid_postfix(listener, address.data(), address.size()) == 0); + Require(Fails(_bind_nid_postfix(listener, address.data(), address.size()), Invalid)); + Require(_listen_nid_postfix(listener, 4) == 0); + std::uint32_t size = address.size(); + Require(_getsockname_nid_postfix(listener, address.data(), &size) == 0 && size == 16); + Require(address[0] == 16 && address[1] == Inet && (address[2] != 0 || address[3] != 0) && address[4] == 127 && address[7] == 1); + + const int client = socket_nid_postfix(Inet, Stream, 0); + Require(client >= 0); + Require(Fails(_accept_nid_postfix(client, nullptr, nullptr), Invalid)); + Require(_connect_nid_postfix(client, address.data(), address.size()) == 0); + Address peer; + peer.fill(0xa5); + size = peer.size(); + const int accepted = _accept_nid_postfix(listener, peer.data(), &size); + Require(accepted >= 0 && size == 16); + Address local{}; + size = local.size(); + Require(_getsockname_nid_postfix(client, local.data(), &size) == 0 && size == 16 && local == peer); + Address remote{}; + size = remote.size(); + Require(_getpeername_nid_postfix(client, remote.data(), &size) == 0 && size == 16 && remote == address); + remote.fill(0); + size = remote.size(); + Require(_getpeername_nid_postfix(accepted, remote.data(), &size) == 0 && size == 16 && remote == local); + + int value = -1; + std::uint32_t valueSize = sizeof(value); + Require(_getsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &value, &valueSize) == 0 && value == 0 && valueSize == sizeof(value)); + const int enabled = 1; + Require(_setsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &enabled, sizeof(enabled)) == 0); + valueSize = sizeof(value); + Require(_getsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &value, &valueSize) == 0 && value == 4 && valueSize == sizeof(value)); + Require(Fails(_setsockopt_nid_postfix(client, TcpLevel, UnknownTcpOption, &enabled, sizeof(enabled)), NoProtocolOption)); + + const char text[] = "underscore stream"; + Require(_sendto_nid_postfix(client, text, sizeof(text), 0, nullptr, 0) == sizeof(text)); + char received[sizeof(text)]{}; + Address sender; + sender.fill(0xa5); + std::uint32_t senderSize = sender.size(); + Require(_recvfrom_nid_postfix(accepted, received, sizeof(received), 0, sender.data(), &senderSize) == sizeof(text)); + Require(std::memcmp(received, text, sizeof(text)) == 0 && senderSize == 0 && Filled(sender, 0xa5)); + + for (const int descriptor : {accepted, client, listener}) Require(close_nid_postfix(descriptor) == 0); + Address untouched; + untouched.fill(0xa5); + std::uint32_t untouchedSize = 7; + value = -1; + Require(Fails(_getsockname_nid_postfix(listener, untouched.data(), &untouchedSize), BadDescriptor)); + Require(Fails(_getpeername_nid_postfix(client, untouched.data(), &untouchedSize), BadDescriptor)); + Require(Fails(_accept_nid_postfix(listener, untouched.data(), &untouchedSize), BadDescriptor)); + Require(Fails(_getsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &value, &untouchedSize), BadDescriptor)); + Require(Fails(_listen_nid_postfix(listener, 4), BadDescriptor)); + Require(Filled(untouched, 0xa5) && untouchedSize == 7 && value == -1); +} + +static void DatagramSockets() { + const int receiver = socket_nid_postfix(Inet, Datagram, 0); + const int sender = socket_nid_postfix(Inet, Datagram, 0); + Require(receiver >= 0 && sender >= 0); + Address destination{16, Inet, 0, 0, 127, 0, 0, 1}; + Require(_bind_nid_postfix(receiver, destination.data(), destination.size()) == 0); + std::uint32_t size = destination.size(); + Require(_getsockname_nid_postfix(receiver, destination.data(), &size) == 0 && size == 16 && (destination[2] != 0 || destination[3] != 0)); + Require(Fails(_listen_nid_postfix(receiver, 1), NotSupported)); + + const char datagram[] = "underscore datagram"; + Require(Fails(_sendto_nid_postfix(sender, datagram, sizeof(datagram), OutOfBand, destination.data(), destination.size()), NotSupported)); + Require(_sendto_nid_postfix(sender, datagram, sizeof(datagram), 0, destination.data(), destination.size()) == sizeof(datagram)); + Address senderName{}; + size = senderName.size(); + Require(_getsockname_nid_postfix(sender, senderName.data(), &size) == 0 && size == 16); + char buffer[32]{}; + Address source{}; + size = source.size(); + Require(_recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, source.data(), &size) == sizeof(datagram)); + Require(std::strcmp(buffer, datagram) == 0 && size == 16 && source[0] == 16 && source[1] == Inet); + Require(source[2] == senderName[2] && source[3] == senderName[3] && source[4] == 127 && source[7] == 1); + + char head[] = "scatter "; + char tail[] = "gather"; + KernelIovec parts[2]{{head, 8}, {tail, 6}}; + GuestMsghdr outgoing{destination.data(), static_cast(destination.size()), parts, 2, nullptr, 0, 0}; + Require(_sendmsg_nid_postfix(sender, &outgoing, 0) == 14); + char first[4]{}; + char second[16]{}; + KernelIovec pieces[2]{{first, sizeof(first)}, {second, sizeof(second)}}; + source.fill(0); + GuestMsghdr incoming{source.data(), static_cast(source.size()), pieces, 2, nullptr, 0, -1}; + Require(_recvmsg_nid_postfix(receiver, &incoming, 0) == 14); + Require(std::memcmp(first, "scat", 4) == 0 && std::memcmp(second, "ter gather", 10) == 0); + Require(incoming.flags == 0 && incoming.nameLength == 16 && source[2] == senderName[2] && source[3] == senderName[3] && source[4] == 127); + Require(_sendmsg_nid_postfix(sender, &outgoing, 0) == 14); + char shortBuffer[5]{}; + KernelIovec single{shortBuffer, sizeof(shortBuffer)}; + GuestMsghdr truncated{nullptr, 0, &single, 1, nullptr, 0, 0}; + Require(_recvmsg_nid_postfix(receiver, &truncated, 0) == 5 && std::memcmp(shortBuffer, "scatt", 5) == 0 && truncated.flags == Truncated); + Require(Fails(_sendmsg_nid_postfix(sender, nullptr, 0), BadAddress)); + Require(Fails(_recvmsg_nid_postfix(receiver, nullptr, 0), BadAddress)); + + Require(Fails(_connect_nid_postfix(sender, destination.data(), 15), Invalid)); + Require(_connect_nid_postfix(sender, destination.data(), destination.size()) == 0); + Address peer{}; + size = peer.size(); + Require(_getpeername_nid_postfix(sender, peer.data(), &size) == 0 && size == 16 && peer == destination); + Require(_sendto_nid_postfix(sender, datagram, sizeof(datagram), 0, nullptr, 0) == sizeof(datagram)); + std::memset(buffer, 0, sizeof(buffer)); + Require(_recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, nullptr, nullptr) == sizeof(datagram) && std::strcmp(buffer, datagram) == 0); + + Require(close_nid_postfix(receiver) == 0 && close_nid_postfix(sender) == 0); + std::memset(buffer, 0x5a, sizeof(buffer)); + source.fill(0xa5); + size = 9; + const int enabled = 1; + Require(Fails(_recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, source.data(), &size), BadDescriptor)); + Require(Fails(_recvmsg_nid_postfix(receiver, &incoming, 0), BadDescriptor)); + Require(Fails(_sendto_nid_postfix(sender, datagram, sizeof(datagram), 0, nullptr, 0), BadDescriptor)); + Require(Fails(_sendmsg_nid_postfix(sender, &outgoing, 0), BadDescriptor)); + Require(Fails(_bind_nid_postfix(receiver, destination.data(), destination.size()), BadDescriptor)); + Require(Fails(_connect_nid_postfix(sender, destination.data(), destination.size()), BadDescriptor)); + Require(Fails(_setsockopt_nid_postfix(sender, TcpLevel, TcpNoDelay, &enabled, sizeof(enabled)), BadDescriptor)); + Require(std::all_of(std::begin(buffer), std::end(buffer), [](char byte) { return byte == 0x5a; })); + Require(Filled(source, 0xa5) && size == 9); +} + +static std::set Names(const char* records, int bytes) { + std::set names; + for (int offset = 0; offset < bytes;) { + std::uint16_t recordLength = 0; + std::memcpy(&recordLength, records + offset + 4, sizeof(recordLength)); + Require(recordLength >= 12 && offset + recordLength <= bytes); + names.emplace(records + offset + 8, static_cast(records[offset + 7])); + offset += recordLength; + } + return names; +} + +static void DirectoryEntries() { + const auto root = std::filesystem::path("anyps5-underscore-entries-test-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(root)); + { std::ofstream stream(root / "entry"); } + const int directory = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); + Require(directory >= 0); + std::array records{}; + std::int64_t base = -1; + const int read = _getdirentries_nid_postfix(directory, records.data(), static_cast(records.size()), &base); + Require(read > 0 && base == 0); + const std::set listed{".", "..", "entry"}; + Require(Names(records.data(), read) == listed); + Require(sceKernelLseek(directory, 0, 0) == 0); + std::array expected{}; + std::int64_t expectedBase = -1; + Require(getdirentries_nid_postfix(directory, expected.data(), static_cast(expected.size()), &expectedBase) == read); + Require(expectedBase == base && std::memcmp(expected.data(), records.data(), static_cast(read)) == 0); + std::int64_t end = -1; + Require(_getdirentries_nid_postfix(directory, records.data(), static_cast(records.size()), &end) == 0 && end > 0); + + records.fill(0x5a); + std::int64_t untouched = 1234; + Require(Fails(_getdirentries_nid_postfix(directory, records.data(), 0, &untouched), Invalid)); + Require(Fails(_getdirentries_nid_postfix(directory, nullptr, 64, &untouched), BadAddress)); + Require(sceKernelClose(directory) == 0); + Require(Fails(_getdirentries_nid_postfix(directory, records.data(), static_cast(records.size()), &untouched), BadDescriptor)); + Require(untouched == 1234 && std::all_of(records.begin(), records.end(), [](char byte) { return byte == 0x5a; })); + std::filesystem::remove_all(root); +} + +volatile std::sig_atomic_t received = 0; + +void APS5_VABI Handler(int value) { + received = value; +} + +static bool Same(const GuestSigaction& left, const GuestSigaction& right) { + return left.handler == right.handler && left.flags == right.flags && std::memcmp(&left.mask, &right.mask, sizeof(left.mask)) == 0; +} + +static void Signals() { + const auto handler = reinterpret_cast(Handler); + const GuestSigaction term{handler, 0, {{0x4000, 0, 0, 0x80000000u}}}; + GuestSigaction initial{}; + Require(_sigaction_nid_postfix(15, nullptr, &initial) == 0); + GuestSigaction previous; + std::memset(&previous, 0xa5, sizeof(previous)); + Require(_sigaction_nid_postfix(15, &term, &previous) == 0 && Same(previous, initial)); + received = 0; + Require(raise_nid_postfix(15) == 0 && received == 15); + GuestSigaction current; + std::memset(¤t, 0xa5, sizeof(current)); + Require(_sigaction_nid_postfix(15, nullptr, ¤t) == 0 && Same(current, term)); + + GuestSigaction untouched; + std::memset(&untouched, 0xa5, sizeof(untouched)); + for (const int invalid : {0, 129}) { + std::memset(¤t, 0xa5, sizeof(current)); + Require(Fails(_sigaction_nid_postfix(invalid, &term, ¤t), Invalid) && std::memcmp(¤t, &untouched, sizeof(current)) == 0); + } + for (const int fixed : {9, 17}) { + std::memset(¤t, 0xa5, sizeof(current)); + Require(Fails(_sigaction_nid_postfix(fixed, &term, ¤t), Invalid) && std::memcmp(¤t, &untouched, sizeof(current)) == 0); + } + Require(_sigaction_nid_postfix(15, &initial, ¤t) == 0 && Same(current, term)); + + const GuestSigaction user{handler, 0, {}}; + GuestSigaction initialUser{}; + Require(_sigaction_nid_postfix(30, &user, &initialUser) == 0); + const GuestSignalSet blocked{{1u << 29, 0, 0, 0}}; + const GuestSignalSet empty{{0, 0, 0, 0}}; + GuestSignalSet saved{}; + Require(sigprocmask_nid_postfix(3, &blocked, &saved) == 0); + received = 0; + Require(raise_nid_postfix(30) == 0 && received == 0); + Require(Fails(_sigsuspend_nid_postfix(&empty), Interrupted) && received == 30); + GuestSignalSet restored{}; + Require(sigprocmask_nid_postfix(1, nullptr, &restored) == 0 && std::memcmp(&restored, &blocked, sizeof(restored)) == 0); + Require(sigprocmask_nid_postfix(3, &saved, nullptr) == 0); + Require(_sigaction_nid_postfix(30, &initialUser, nullptr) == 0); +} + +int main() { + StreamSockets(); + DatagramSockets(); + DirectoryEntries(); + Signals(); +} diff --git a/core/libs/tests/GuestUnwindModuleInfo.cpp b/core/libs/tests/GuestUnwindModuleInfo.cpp index d0519e368b..d863dcfea1 100644 --- a/core/libs/tests/GuestUnwindModuleInfo.cpp +++ b/core/libs/tests/GuestUnwindModuleInfo.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include "tests/GuestUnwindModuleInfoFixture.hpp" #include @@ -9,17 +10,13 @@ #include #include -extern "C" int APS5_VABI sceKernelGetModuleInfoForUnwind(uint64_t addr, int flags, ModuleInfoForUnwind* info); -extern "C" void* APS5_VABI dlopen_nid_postfix(const char*, int); -extern "C" void* APS5_VABI dlsym_nid_postfix(void*, const char*); -extern "C" int APS5_VABI dlclose_nid_postfix(void*); - static void Require(bool value) { if (!value) std::abort(); } static bool Throws(std::uint64_t address) { ModuleInfoForUnwind info{}; try { sceKernelGetModuleInfoForUnwind(address, 0, &info); + } catch (const std::runtime_error&) { return true; } diff --git a/core/libs/tests/GuestUnwindModuleInfoFailureLinux.cpp b/core/libs/tests/GuestUnwindModuleInfoFailureLinux.cpp index 222fa17660..da327fc32d 100644 --- a/core/libs/tests/GuestUnwindModuleInfoFailureLinux.cpp +++ b/core/libs/tests/GuestUnwindModuleInfoFailureLinux.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libkernel/KernelErrors.hpp" #include @@ -5,8 +6,6 @@ #include #include -extern "C" int APS5_VABI sceKernelGetModuleInfoForUnwind(std::uint64_t address, int flags, ModuleInfoForUnwind* info); - static std::uint64_t expectedAddress = 0; static int calls = 0; @@ -16,7 +15,7 @@ static void Require(bool value, const char* message) { extern "C" int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, ModuleInfoEx* info) { Require(address == expectedAddress, "module lookup received the wrong address"); - Require(flags == 2, "module lookup received the wrong flags"); + Require(flags == 1, "module lookup received the wrong flags"); Require(info != nullptr && info->st_size == sizeof(ModuleInfoEx), "module lookup received the wrong structure size"); ++calls; return SCE_KERNEL_ERROR_ESRCH; @@ -29,6 +28,7 @@ int main() { bool threw = false; try { sceKernelGetModuleInfoForUnwind(expectedAddress, 1, &info); + } catch (const std::runtime_error& error) { threw = std::string_view(error.what()) == "sceKernelGetModuleInfoForUnwind: failed to query guest module information"; } diff --git a/core/libs/tests/GuestUnwindModuleInfoLinux.cpp b/core/libs/tests/GuestUnwindModuleInfoLinux.cpp index c7c126b26d..22aae73eb4 100644 --- a/core/libs/tests/GuestUnwindModuleInfoLinux.cpp +++ b/core/libs/tests/GuestUnwindModuleInfoLinux.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -5,13 +6,6 @@ #include #include -extern "C" { -int APS5_VABI sceKernelGetModuleInfoForUnwind(uint64_t addr, int flags, ModuleInfoForUnwind* info); -void* APS5_VABI dlopen_nid_postfix(const char* path, int flags); -void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name); -int APS5_VABI dlclose_nid_postfix(void* handle); -} - static void Require(bool value, const char* message) { if (!value) throw std::runtime_error(message); } diff --git a/core/libs/tests/GuestUpdateEnv.cpp b/core/libs/tests/GuestUpdateEnv.cpp new file mode 100644 index 0000000000..5de6f53321 --- /dev/null +++ b/core/libs/tests/GuestUpdateEnv.cpp @@ -0,0 +1,275 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +struct Environment { + std::array x87; + std::uint32_t mxcsr; +}; +static_assert(sizeof(Environment)==32 && offsetof(Environment,mxcsr)==28); +using UpdateEnvironment=int (APS5_VABI*)(const Environment*); + +UpdateEnvironment updateEnvironment; + +void Require(bool condition,const char* message) { + if(!condition) { std::fprintf(stderr,"feupdateenv: %s\n",message); std::exit(1); } +} +Environment Read() { + Environment result; + asm volatile("fnstenv %0\n\tfldcw %0" : "=m"(result.x87)); + result.mxcsr=_mm_getcsr(); + return result; +} +void Write(const Environment& value) { + asm volatile("fldenv %0" : : "m"(value.x87) : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)"); + _mm_setcsr(value.mxcsr); +} +bool Same(const Environment& a,const Environment& b){return a.x87==b.x87 && a.mxcsr==b.mxcsr;} +void Matrix() { + const auto original=Read(); + auto clean=original; + clean.x87[0]|=0x3f; + clean.x87[1]&=~0xffu; + clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7); + unsigned cases=0; + for(unsigned x87=0;x87<64;++x87) + for(unsigned sse=0;sse<64;++sse) + for(unsigned pattern=0;pattern<4;++pattern) + for(unsigned rounding=0;rounding<16;++rounding) { + auto saved=clean; + constexpr unsigned precisions[]{0u,0x200u,0x300u,0u}; + saved.x87[0]=(saved.x87[0]&~0x300u)|precisions[pattern]; + saved.x87[0]=(saved.x87[0]&~0xc00u)|((rounding&3)<<10); + saved.mxcsr=(saved.mxcsr&~0x6000u)|((rounding>>2)<<13); + const unsigned savedX87=pattern==0?0:pattern==1?0x15:pattern==2?0x2a:0x3f; + const unsigned savedSse=pattern==0?0x3f:pattern==1?0x2a:pattern==2?0x15:0; + saved.x87[1]|=savedX87; + saved.mxcsr|=savedSse; + Write(saved); + saved=Read(); + auto pending=clean; + pending.x87[1]|=x87; + pending.mxcsr|=sse; + Write(pending); + const auto input=saved; + *__error_nid_postfix()=123; + const int result=updateEnvironment(&saved); + const auto after=Read(); + const int error=*__error_nid_postfix(); + Write(clean); + auto expected=input; + expected.x87[1]|=x87|sse; + expected.mxcsr|=x87|sse; + Require(result==0 && error==123,"return/errno"); + Require(after.x87==expected.x87 && after.mxcsr==expected.mxcsr,"restore saved controls and raise old union in both units"); + Require(std::memcmp(&input,&saved,sizeof(input))==0,"input unchanged"); + ++cases; + } + Write(original); + std::printf("feupdateenv: %u flag/rounding combinations passed\n",cases); +} + +void TagsAndPointers() { + const auto entry=Read(); + auto clean=entry; + clean.x87[0]|=0x3f; clean.x87[1]&=~0xffu; + clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7); + Write(clean); + const auto original=Read(); + asm volatile("fninit\n\tfld1\n\tfldpi" : : : "st","st(1)"); + const auto desired=Read(); + asm volatile("fninit" : : : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)"); + const int result=updateEnvironment(&desired); + const auto after=Read(); + Write(original); + Require(result==0 && Same(after,desired),"nonempty x87 tag/TOP and instruction pointer restored"); + GuestFloatingPointEnvironment guestSaved; + Require(fegetenv_nid_postfix(&guestSaved)==0,"save using existing guest fegetenv"); + asm volatile("fninit" : : : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)"); + Environment saved; + std::memcpy(&saved, &guestSaved, sizeof(saved)); + Require(updateEnvironment(&saved)==0,"restore guest fegetenv output"); + const auto restored=Read(); + Write(original); + Require(Same(saved,restored),"guest fegetenv round trip"); + Write(entry); +} + +void BoundsAndErrors() { + const auto entry=Read(); + auto clean=entry; + clean.x87[0]|=0x3f; clean.x87[1]&=~0xffu; + clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7); + Write(clean); + const auto original=Read(); +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + const auto page=static_cast(info.dwPageSize); + auto* memory=static_cast(VirtualAlloc(nullptr,2*page,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE)); + Require(memory!=nullptr,"allocate guard pages"); + DWORD previous; + Require(VirtualProtect(memory+page,page,PAGE_NOACCESS,&previous)!=0,"protect trailing page"); +#else + const auto page=static_cast(sysconf(_SC_PAGESIZE)); + auto* memory=static_cast(mmap(nullptr,2*page,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0)); + Require(memory!=MAP_FAILED,"allocate guard pages"); + Require(mprotect(memory+page,page,PROT_NONE)==0,"protect trailing page"); +#endif + auto* end=reinterpret_cast(memory+page-sizeof(Environment)); + std::memcpy(end,&original,sizeof(original)); +#ifdef _WIN32 + Require(VirtualProtect(memory,page,PAGE_READONLY,&previous)!=0,"input read-only"); +#else + Require(mprotect(memory,page,PROT_READ)==0,"input read-only"); +#endif + const int result=updateEnvironment(end); + const auto after=Read(); + Require(result==0 && Same(after,original),"exact32-byte read at guard-page boundary"); +#ifdef _WIN32 + Require(VirtualFree(memory,0,MEM_RELEASE)!=0,"release guard pages"); +#else + Require(munmap(memory,2*page)==0,"release guard pages"); +#endif + bool rejected=false; + *__error_nid_postfix()=123; + try { updateEnvironment(nullptr); } catch(const std::invalid_argument&) { rejected=true; } + Require(rejected && Same(Read(),original) && *__error_nid_postfix()==123,"null rejected without state or errno change"); + alignas(16) std::array extended; + asm volatile("fxsave %0" : "=m"(extended)); + const auto supported=extended[7] ? extended[7] : 0xffbfu; + for(unsigned bit=0;bit<32;++bit) { + const auto reserved=1u< state; + asm volatile("fxsave %0" : "=m"(state)); + const auto supported=state[7]?state[7]:0xffbfu; + for(unsigned mode:{0u,0x40u,0x8000u,0x8040u}) { + if(mode&~supported) continue; + auto desired=clean; desired.mxcsr=(desired.mxcsr&~0x8040u)|mode; + Require(updateEnvironment(&desired)==0,"supported DAZ/FTZ return"); + const auto actual=Read(); Write(clean); + Require(Same(actual,desired),"supported DAZ/FTZ restored"); + } + std::barrier ready(2); + auto worker=[&](unsigned rounding,unsigned flags) { + const auto entry=Read(); + auto desired=clean; + desired.x87[0]=(desired.x87[0]&~0xc00u)|(rounding<<10); + desired.mxcsr=(desired.mxcsr&~0x6000u)|(rounding<<13); + auto pending=clean; pending.mxcsr|=flags; + ready.arrive_and_wait(); + Write(pending); + *__error_nid_postfix()=static_cast(flags); + const int result=updateEnvironment(&desired); + const int error=*__error_nid_postfix(); + const auto after=Read(); + ready.arrive_and_wait(); + Write(entry); + desired.x87[1]|=flags; desired.mxcsr|=flags; + Require(result==0 && error==static_cast(flags),"thread return/errno"); + Require(Same(after,desired),"thread environment and flag union"); + }; + std::thread a(worker,1,4),b(worker,2,8);a.join();b.join(); + Write(original); +} +volatile std::sig_atomic_t entered=0; +#ifdef _WIN32 +unsigned expectedFlag; +DWORD expectedException; +LONG CALLBACK Trap(EXCEPTION_POINTERS* exception) { + const auto& state=exception->ContextRecord->FltSave; + const auto active=state.StatusWord&~state.ControlWord&0x3f; + const auto code=exception->ExceptionRecord->ExceptionCode; + const bool denormal=expectedFlag==2 && code==EXCEPTION_FLT_INVALID_OPERATION; + if(entered && active==expectedFlag && (code==expectedException || denormal)) std::_Exit(0); + return EXCEPTION_CONTINUE_SEARCH; +} +#else +void Trap(int){std::_Exit(entered?0:1);} +#endif +void Pending(unsigned bit,unsigned source) { + Require(bit<6 && source<3,"trap arguments"); +#ifdef _WIN32 + constexpr DWORD codes[]{EXCEPTION_FLT_INVALID_OPERATION,EXCEPTION_FLT_DENORMAL_OPERAND,EXCEPTION_FLT_DIVIDE_BY_ZERO,EXCEPTION_FLT_OVERFLOW,EXCEPTION_FLT_UNDERFLOW,EXCEPTION_FLT_INEXACT_RESULT}; + expectedFlag=1u<(GetProcAddress(library,"feupdateenv_nid_postfix")); +#else + const auto library=dlopen(argv[1],RTLD_NOW); + Require(library!=nullptr,"load libc"); + updateEnvironment=reinterpret_cast(dlsym(library,"feupdateenv_nid_postfix")); +#endif + Require(updateEnvironment!=nullptr,"export is missing"); + if(argc==4) Pending(static_cast(std::atoi(argv[2])),static_cast(std::atoi(argv[3]))); + Matrix(); TagsAndPointers(); BoundsAndErrors(); MasksAndThreads(); +#ifdef _WIN32 + Require(FreeLibrary(library)!=0,"release library lookup handle"); +#else + Require(dlclose(library)==0,"release library lookup handle"); +#endif +} diff --git a/core/libs/tests/GuestUsbStorage.cpp b/core/libs/tests/GuestUsbStorage.cpp new file mode 100644 index 0000000000..43a173d247 --- /dev/null +++ b/core/libs/tests/GuestUsbStorage.cpp @@ -0,0 +1,242 @@ +#include "prx/libSceUsbStorage/UsbStorage.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +int g_attr = 0; + +void APS5_VABI CallbackStub(const UsbStorageEvent*, void*) {} + +UsbStorageCallback Callback() { + return &CallbackStub; +} + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "USBSTORAGE: %s\n", message); + std::abort(); + } +} + +void TestInitializeAndTerminate() { + Require(sceUsbStorageInit(nullptr) == 0, "initialize with a null attribute"); + Require(sceUsbStorageInit(&g_attr) == 0, "initialize with an attribute"); + Require(sceUsbStorageTerm() == 0, "terminate"); + Require(sceUsbStorageInit(nullptr) == 0, "initialize after terminate"); + Require(sceUsbStorageTerm() == 0, "terminate again"); +} + +void TestRegisterCallbacks() { + bool threw = false; + try { + sceUsbStorageRegisterCallback(1, Callback(), nullptr); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageRegisterCallback not implemented"; + } + Require(threw, "register callback of event type 1 did not throw its marker"); + threw = false; + try { + sceUsbStorageRegisterCallback(2, Callback(), nullptr); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageRegisterCallback not implemented"; + } + Require(threw, "register callback of event type 2 did not throw its marker"); + Require(sceUsbStorageRegisterCallback(1, nullptr, nullptr) == UsbStorage::InvalidArgument, "register a null callback"); + Require(sceUsbStorageRegisterCallback(0, Callback(), nullptr) == UsbStorage::InvalidArgument, "register callback of event type 0"); + Require(sceUsbStorageRegisterCallback(3, Callback(), nullptr) == UsbStorage::InvalidArgument, "register callback of event type 3"); + Require(sceUsbStorageRegisterCallback(-1, Callback(), nullptr) == UsbStorage::InvalidArgument, "register callback of a negative event type"); + threw = false; + try { + sceUsbStorageUnregisterCallback(1, Callback()); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageUnregisterCallback not implemented"; + } + Require(threw, "unregister callback of event type 1 did not throw its marker"); + threw = false; + try { + sceUsbStorageUnregisterCallback(2, Callback()); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageUnregisterCallback not implemented"; + } + Require(threw, "unregister callback of event type 2 did not throw its marker"); + Require(sceUsbStorageUnregisterCallback(1, nullptr) == UsbStorage::InvalidArgument, "unregister a null callback"); + Require(sceUsbStorageUnregisterCallback(0, Callback()) == UsbStorage::InvalidArgument, "unregister callback of event type 0"); +} + +void TestMapCallbacks() { + bool threw = false; + try { + sceUsbStorageRegisterCallbackForMapAvailable(0, "/data", Callback(), nullptr); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageRegisterCallbackForMapAvailable not implemented"; + } + Require(threw, "register map-available callback did not throw its marker"); + Require(sceUsbStorageRegisterCallbackForMapAvailable(0, nullptr, Callback(), nullptr) == UsbStorage::InvalidArgument, "register map-available callback with a null directory"); + Require(sceUsbStorageRegisterCallbackForMapAvailable(0, "/data", nullptr, nullptr) == UsbStorage::InvalidArgument, "register a null map-available callback"); + threw = false; + try { + sceUsbStorageUnregisterCallbackForMapAvailable(0, "/data", Callback()); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageUnregisterCallbackForMapAvailable not implemented"; + } + Require(threw, "unregister map-available callback did not throw its marker"); + Require(sceUsbStorageUnregisterCallbackForMapAvailable(0, nullptr, Callback()) == UsbStorage::InvalidArgument, "unregister map-available callback with a null directory"); + Require(sceUsbStorageUnregisterCallbackForMapAvailable(0, "/data", nullptr) == UsbStorage::InvalidArgument, "unregister a null map-available callback"); + threw = false; + try { + sceUsbStorageRegisterCallbackForMapUnavailable(0, "/data", Callback(), nullptr); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageRegisterCallbackForMapUnavailable not implemented"; + } + Require(threw, "register map-unavailable callback did not throw its marker"); + Require(sceUsbStorageRegisterCallbackForMapUnavailable(0, nullptr, Callback(), nullptr) == UsbStorage::InvalidArgument, "register map-unavailable callback with a null directory"); + Require(sceUsbStorageRegisterCallbackForMapUnavailable(0, "/data", nullptr, nullptr) == UsbStorage::InvalidArgument, "register a null map-unavailable callback"); + threw = false; + try { + sceUsbStorageUnregisterCallbackForMapUnavailable(0, "/data", Callback()); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageUnregisterCallbackForMapUnavailable not implemented"; + } + Require(threw, "unregister map-unavailable callback did not throw its marker"); + Require(sceUsbStorageUnregisterCallbackForMapUnavailable(0, nullptr, Callback()) == UsbStorage::InvalidArgument, "unregister map-unavailable callback with a null directory"); + Require(sceUsbStorageUnregisterCallbackForMapUnavailable(0, "/data", nullptr) == UsbStorage::InvalidArgument, "unregister a null map-unavailable callback"); +} + +void TestNoDevice() { + std::uint32_t devices[4]{0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a}; + std::int32_t count = -1; + Require(sceUsbStorageGetDeviceList(devices, &count) == 0, "get the device list"); + Require(count == 0, "no usb storage device is attached"); + 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"); + + std::uint8_t info[16]; + std::memset(info, 0x5a, sizeof(info)); + auto* const infoRecord = reinterpret_cast(info); + Require(sceUsbStorageGetDeviceInfo(0, infoRecord) == UsbStorage::DeviceNotFound, "get device info of device 0"); + Require(sceUsbStorageGetDeviceInfo(1, infoRecord) == UsbStorage::DeviceNotFound, "get device info of device 1"); + Require(info[0] == 0x5a && info[15] == 0x5a, "device info was written"); + Require(sceUsbStorageGetDeviceInfo(0, nullptr) == UsbStorage::InvalidArgument, "get device info without an info buffer"); + + std::uint8_t exists = 1; + Require(sceUsbStorageIsExist(0, "/mnt/usb0", &exists) == true && exists == 0, "device 0 does not exist"); + exists = 1; + Require(sceUsbStorageIsExist(1, nullptr, &exists) == true && exists == 0, "device 1 does not exist"); + exists = 1; + Require(sceUsbStorageIsExist(0xffffffffu, "/mnt/usb0", &exists) == true && exists == 0, "no device id exists"); +} + +void TestMapRequests() { + char mountPoint[16]; + std::uint64_t mountId = 0x5a5a5a5a5a5a5a5aull; + std::int32_t status = 0x5a5a5a5a; + std::memset(mountPoint, 0x5a, sizeof(mountPoint)); + Require(sceUsbStorageRequestMap(0, "/data", 0, 0, mountPoint, &mountId) == UsbStorage::DeviceNotFound, "request a map of device 0"); + Require(sceUsbStorageRequestMap(1, nullptr, 0, 0, nullptr, nullptr) == UsbStorage::DeviceNotFound, "request a map of device 1"); + Require(mountId == 0x5a5a5a5a5a5a5a5aull && mountPoint[0] == static_cast(0x5a), "the map request wrote its outputs"); + std::memset(mountPoint, 0x5a, sizeof(mountPoint)); + Require(sceUsbStorageRequestMapWSB(0, "/data", 0, 0, mountPoint, &status) == UsbStorage::DeviceNotFound, "request a WSB map of device 0"); + Require(sceUsbStorageRequestMapWSB(0, nullptr, 0, 0, nullptr, nullptr) == UsbStorage::DeviceNotFound, "request a WSB map without a subpath"); + Require(mountPoint[0] == static_cast(0x5a) && status == 0x5a5a5a5a, "the WSB map request wrote its outputs"); + Require(sceUsbStorageRequestUnmap(0, nullptr) == UsbStorage::InvalidArgument, "unmap a null directory"); + bool threw = false; + try { + sceUsbStorageRequestUnmap(0, "/mnt/usb0"); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageRequestUnmap not implemented"; + } + Require(threw, "unmap a directory did not throw its marker"); + threw = false; + try { + sceUsbStorageRequestUnmap(7, "/mnt/usb7"); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageRequestUnmap not implemented"; + } + Require(threw, "unmap a directory of another device did not throw its marker"); +} + +void TestGetdents() { + std::int32_t fd = -1; + std::uint64_t bufferSize = 0x5a5a5a5a5a5a5a5aull; + Require(sceUsbStorageGetdentsOpen(0, &fd, &bufferSize) == UsbStorage::DeviceNotFound, "open a getdents scan of device 0"); + Require(sceUsbStorageGetdentsOpen(3, &fd, &bufferSize) == UsbStorage::DeviceNotFound, "open a getdents scan of device 3"); + 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"); + + std::uint8_t buffer[64]; + std::uint64_t bytesRead = 0x5a5a5a5a5a5a5a5aull; + std::memset(buffer, 0x5a, sizeof(buffer)); + Require(sceUsbStorageGetdentsRead(1, buffer, sizeof(buffer), &bytesRead) == UsbStorage::NotSupported, "read a getdents scan that was never opened"); + Require(bytesRead == 0, "the getdents read did not answer zero bytes"); + 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"); + + bool threw = false; + try { + sceUsbStorageGetdentsClose(1); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageGetdentsClose not implemented"; + } + Require(threw, "close a getdents scan that was never opened did not throw its marker"); + threw = false; + try { + sceUsbStorageGetdentsClose(0); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageGetdentsClose not implemented"; + } + Require(threw, "close descriptor 0 did not throw its marker"); +} + +void TestThrowingExports() { + char mountPoint[8]{}; + bool threw = false; + try { + sceUsbStorageGetMountPointOfShellCore(0, mountPoint); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageGetMountPointOfShellCore not implemented"; + } + Require(threw, "get the mount point of shell core did not throw its marker"); + threw = false; + try { + sceUsbStorageClearFakeWriteProtection(); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageClearFakeWriteProtection not implemented"; + } + Require(threw, "clear fake write protection did not throw its marker"); + threw = false; + try { + sceUsbStorageSetFakeMapLockForDebug(); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageSetFakeMapLockForDebug not implemented"; + } + Require(threw, "set fake map lock for debug did not throw its marker"); + threw = false; + try { + sceUsbStorageSetFakeWriteProtection(); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()) == "sceUsbStorageSetFakeWriteProtection not implemented"; + } + Require(threw, "set fake write protection did not throw its marker"); +} + +} + +int main() { + TestInitializeAndTerminate(); + TestRegisterCallbacks(); + TestMapCallbacks(); + TestNoDevice(); + TestMapRequests(); + TestGetdents(); + TestThrowingExports(); + return 0; +} diff --git a/core/libs/tests/GuestUsbStorageDialog.cpp b/core/libs/tests/GuestUsbStorageDialog.cpp index 5217f66be0..56615792bb 100644 --- a/core/libs/tests/GuestUsbStorageDialog.cpp +++ b/core/libs/tests/GuestUsbStorageDialog.cpp @@ -1,17 +1,13 @@ +#include "prx/libSceUsbStorageDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include - -extern "C" { -int APS5_VABI sceUsbStorageDialogInitialize(void); -int APS5_VABI sceUsbStorageDialogOpen(const void* param); -int APS5_VABI sceUsbStorageDialogUpdateStatus(void); -int APS5_VABI sceUsbStorageDialogGetStatus(void); -int APS5_VABI sceUsbStorageDialogGetResult(void* result); -int APS5_VABI sceUsbStorageDialogClose(void); -int APS5_VABI sceUsbStorageDialogTerminate(void); -} +#include +#include +#include "ProcessParametersFixture.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" +#include "prx/libSceUserService/UserService.hpp" namespace { @@ -32,8 +28,21 @@ constexpr int kResultUserCanceled = 1; } int main() { - std::int64_t param = 0; - std::int32_t result = 0x5a5a5a5a; + static std::array process{}; + Require(ProcessParametersFixture::Install(process.data(), process.size())); + std::uint32_t sdkVersion = 0x04500000; + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + Require(sceUserServiceInitialize(nullptr) == 0); + UsbStorageDialogParam 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; + std::strcpy(param.path, "test"); + UsbStorageDialogResult result; + std::memset(&result, 0x5a, sizeof(result)); + result.size = sizeof(result); + result.result = kErrBusy; Require(sceUsbStorageDialogTerminate() == kErrNotInitialized); Require(sceUsbStorageDialogOpen(¶m) == kErrNotInitialized); @@ -49,6 +58,17 @@ int main() { Require(sceUsbStorageDialogOpen(nullptr) == kErrArgNull); Require(sceUsbStorageDialogGetResult(&result) == kErrNotFinished); + param.path[1023] = 'a'; + Require(sceUsbStorageDialogOpen(¶m) == static_cast(0x80B8000Au)); + param.path[1023] = 0; + std::strcpy(param.path, "a..b"); + Require(sceUsbStorageDialogOpen(¶m) == static_cast(0x812D0003u)); + for (const char invalid : std::string(",*?\"<>|")) { + param.path[0] = invalid; + param.path[1] = 0; + Require(sceUsbStorageDialogOpen(¶m) == static_cast(0x812D0003u)); + } + std::strcpy(param.path, "test"); Require(sceUsbStorageDialogOpen(¶m) == 0); Require(sceUsbStorageDialogOpen(¶m) == kErrBusy); Require(sceUsbStorageDialogGetStatus() == kStatusRunning); @@ -58,8 +78,16 @@ int main() { Require(sceUsbStorageDialogGetStatus() == kStatusFinished); Require(sceUsbStorageDialogGetResult(nullptr) == kErrArgNull); - Require(sceUsbStorageDialogGetResult(&result) == 0); - Require(result == kResultUserCanceled); + result.size = 79; + Require(sceUsbStorageDialogGetResult(&result) == static_cast(0x80B8000Au)); + result.size = sizeof(result); + result.result = 0; + Require(sceUsbStorageDialogGetResult(&result) == static_cast(0x80B8000Au)); + result.result = kErrBusy; + Require(sceUsbStorageDialogGetResult(&result) == kResultUserCanceled); + Require(result.result == kResultUserCanceled); + Require(result.size == sizeof(result) && result.status == 0x5a5a5a5a); + for (const auto value : result.reserved) Require(value == 0x5a); Require(sceUsbStorageDialogClose() == kErrNotRunning); Require(sceUsbStorageDialogTerminate() == 0); @@ -71,9 +99,23 @@ int main() { Require(sceUsbStorageDialogGetStatus() == kStatusRunning); Require(sceUsbStorageDialogClose() == 0); Require(sceUsbStorageDialogGetStatus() == kStatusFinished); - result = 0; + result.result = 0; Require(sceUsbStorageDialogGetResult(&result) == 0); - Require(result == kResultUserCanceled); + Require(result.result == kErrNotFinished && result.status == 0); Require(sceUsbStorageDialogTerminate() == 0); + sdkVersion = 0x04400000; + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); + Require(sceUsbStorageDialogInitialize() == 0); + std::strcpy(param.path, "a..b"); + Require(sceUsbStorageDialogOpen(¶m) == 0); + Require(sceUsbStorageDialogUpdateStatus() == kStatusFinished); + result.size = 0; + result.result = 0; + Require(sceUsbStorageDialogGetResult(&result) == kResultUserCanceled); + Require(result.size == 0 && result.result == kResultUserCanceled); + Require(sceUsbStorageDialogTerminate() == 0); + Require(sceUsbStorageDialogOpen(nullptr) == kErrArgNull); + Require(sceUsbStorageDialogGetResult(nullptr) == kErrArgNull); + Require(sceUserServiceTerminate() == 0); return 0; } diff --git a/core/libs/tests/GuestUsbd.cpp b/core/libs/tests/GuestUsbd.cpp index 59e59542b0..eb6cc9af44 100644 --- a/core/libs/tests/GuestUsbd.cpp +++ b/core/libs/tests/GuestUsbd.cpp @@ -1,5 +1,7 @@ +#include "prx/libSceUsbd/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -12,49 +14,15 @@ namespace { -struct UsbdTimeval { - std::int64_t seconds; - std::int64_t microseconds; -}; - -} - -extern "C" { -std::int32_t APS5_VABI sceUsbdInit(); -void APS5_VABI sceUsbdExit(); -std::int64_t APS5_VABI sceUsbdGetDeviceList(void*** list); -void APS5_VABI sceUsbdFreeDeviceList(void** list, std::int32_t unrefDevices); -std::int32_t APS5_VABI sceUsbdHandleEventsTimeout(const UsbdTimeval* timeout); -std::int32_t APS5_VABI sceUsbdOpen(void* device, void** deviceHandle); -void APS5_VABI sceUsbdClose(void* deviceHandle); -void* APS5_VABI sceUsbdRefDevice(void* device); -void APS5_VABI sceUsbdUnrefDevice(void* device); -std::uint8_t APS5_VABI sceUsbdGetBusNumber(void* device); -std::uint8_t APS5_VABI sceUsbdGetDeviceAddress(void* device); -std::int32_t APS5_VABI sceUsbdGetDeviceDescriptor(void* device, void* descriptor); -std::int32_t APS5_VABI sceUsbdGetConfigDescriptor(void* device, std::uint8_t configIndex, void** config); -std::int32_t APS5_VABI sceUsbdGetActiveConfigDescriptor(void* device, void** config); -std::int32_t APS5_VABI sceUsbdClaimInterface(void* deviceHandle, std::int32_t interfaceNumber); -std::int32_t APS5_VABI sceUsbdReleaseInterface(void* deviceHandle, std::int32_t interfaceNumber); -std::int32_t APS5_VABI sceUsbdSetConfiguration(void* deviceHandle, std::int32_t configuration); -std::int32_t APS5_VABI sceUsbdResetDevice(void* deviceHandle); -std::int32_t APS5_VABI sceUsbdCheckConnected(void* deviceHandle); -std::int32_t APS5_VABI sceUsbdKernelDriverActive(void* deviceHandle, std::int32_t interfaceNumber); -std::int32_t APS5_VABI sceUsbdAttachKernelDriver(void* deviceHandle, std::int32_t interfaceNumber); -std::int32_t APS5_VABI sceUsbdControlTransfer(void* deviceHandle, std::uint8_t requestType, std::uint8_t request, std::uint16_t value, std::uint16_t index, std::uint8_t* data, std::uint16_t length, std::uint32_t timeout); -std::int32_t APS5_VABI sceUsbdGetStringDescriptor(void* deviceHandle, std::uint8_t descriptorIndex, std::uint16_t languageId, std::uint8_t* data, std::int32_t length); -std::int32_t APS5_VABI sceUsbdSubmitTransfer(void* transfer); -void* APS5_VABI sceUsbdAllocTransfer(std::int32_t isoPackets); -void APS5_VABI sceUsbdFreeTransfer(void* transfer); -void APS5_VABI sceUsbdFillInterruptTransfer(void* transfer, void* deviceHandle, std::uint8_t endpoint, std::uint8_t* buffer, std::int32_t length, void* callback, void* userData, std::uint32_t timeout); -std::int32_t APS5_VABI sceUsbdCancelTransfer(void* transfer); -std::int32_t APS5_VABI sceUsbdEventHandlingOk(); -void APS5_VABI sceUsbdFreeConfigDescriptor(void* config); } namespace { constexpr std::int32_t invalidArgument = static_cast(0x80240002); +constexpr std::int32_t noDevice = static_cast(0x80240004); +constexpr std::int32_t notFound = static_cast(0x80240005); +constexpr std::int32_t otherError = static_cast(0x802400FF); +constexpr std::uint8_t absentEndpoint = 0x70; void Require(bool condition, const char* message) { if (!condition) { @@ -73,26 +41,11 @@ bool Throws(TCall call) { return false; } -struct HostTransfer { - void* deviceHandle; - std::uint8_t flags; - std::uint8_t endpoint; - std::uint8_t type; - std::uint32_t timeout; - std::int32_t status; - std::int32_t length; - std::int32_t actualLength; - void (APS5_VABI* callback)(HostTransfer*); - void* userData; - std::uint8_t* buffer; - std::int32_t numIsoPackets; -}; - -void APS5_VABI MarkDone(HostTransfer* transfer) { +void APS5_VABI MarkDone(UsbdTransfer* transfer) { *static_cast(transfer->userData) += 1; } -void APS5_VABI MarkDoneThenSignal(HostTransfer* transfer) { +void APS5_VABI MarkDoneThenSignal(UsbdTransfer* transfer) { auto* notified = static_cast*>(transfer->userData); notified->store(true, std::memory_order_release); } @@ -118,6 +71,7 @@ void Device() { if ((descriptor[8] | descriptor[9] << 8) == vendor && (descriptor[10] | descriptor[11] << 8) == product) device = sceUsbdRefDevice(list[index]); } sceUsbdFreeDeviceList(list, 1); + Require(device != nullptr, "the APS5_USBD_DEVICE device was not listed"); std::printf("device bus %u address %u\n", sceUsbdGetBusNumber(device), sceUsbdGetDeviceAddress(device)); void* config = nullptr; @@ -125,13 +79,25 @@ void Device() { const auto* raw = static_cast(config); Require(raw[1] == 2 && raw[4] > 0, "active config descriptor is not a configuration with interfaces"); const std::uint8_t interfaces = raw[4]; + const std::uint8_t activeValue = raw[5]; sceUsbdFreeConfigDescriptor(config); + Require(Throws([&] { sceUsbdFreeConfigDescriptor(config); }), "a config descriptor was freed twice"); Require(sceUsbdGetConfigDescriptor(device, 0, &config) == 0, "config descriptor 0 failed"); sceUsbdFreeConfigDescriptor(config); + Require(sceUsbdGetConfigDescriptorByValue(device, activeValue, &config) == 0 && config != nullptr, "config descriptor by value failed"); + Require(static_cast(config)[5] == activeValue, "config descriptor by value has another configuration value"); + sceUsbdFreeConfigDescriptor(config); + Require(sceUsbdGetConfigDescriptorByValue(device, 0, &config) == static_cast(0x80240005) && config == nullptr, "configuration value 0 was found"); void* handle = nullptr; Require(sceUsbdOpen(device, &handle) == 0 && handle != nullptr, "opening the device failed"); Require(sceUsbdCheckConnected(handle) == 0, "an open device was reported disconnected"); + Require(sceUsbdGetDevice(handle) == device, "the device of an open handle is not the device it was opened from"); + const int speed = sceUsbdGetDeviceSpeed(device); + Require(speed >= 1 && speed <= 4, "the device speed is not low, full, high or super speed"); + std::printf("device speed %d\n", speed); + std::int32_t configuration = -1; + Require(sceUsbdGetConfiguration(handle, &configuration) == 0 && configuration == activeValue, "configuration differs from the active config descriptor"); std::uint8_t text[256] = {}; if (descriptor[15] != 0) { const std::int32_t length = sceUsbdGetStringDescriptor(handle, descriptor[15], 0x409, text, sizeof(text)); @@ -139,10 +105,31 @@ void Device() { std::string product_name; for (std::int32_t index = 2; index + 1 < length; index += 2) product_name += static_cast(text[index]); std::printf("product \"%s\"\n", product_name.c_str()); + char ascii[256] = {}; + const std::int32_t asciiLength = sceUsbdGetStringDescriptorAscii(handle, descriptor[15], reinterpret_cast(ascii), sizeof(ascii)); + Require(asciiLength >= 0 && std::strlen(ascii) == static_cast(asciiLength), "ASCII product string length differs from its terminated length"); + std::printf("ASCII product \"%s\"\n", ascii); + std::uint8_t languages[4] = {}; + Require(sceUsbdGetStringDescriptor(handle, 0, 0, languages, sizeof(languages)) == 4, "language ID string descriptor failed"); + std::uint8_t first[256] = {}; + const std::int32_t firstLength = sceUsbdGetStringDescriptor(handle, descriptor[15], static_cast(languages[2] | languages[3] << 8), first, sizeof(first)); + Require(firstLength >= 2 && first[1] == 3, "product string descriptor in the first language failed"); + const std::int32_t end = first[0] < firstLength ? first[0] : firstLength; + std::string printable; + for (std::int32_t index = 2; index + 1 < end; index += 2) { + if (first[index + 1] == 0 && first[index] >= 0x20 && first[index] <= 0x7E && first[index] != '<' && first[index] != '>') printable += static_cast(first[index]); + } + Require(printable == ascii, "ASCII product string differs from the printable characters of the product string"); + char shortAscii[4] = {'x', 'x', 'x', 'x'}; + const std::int32_t shortLength = sceUsbdGetStringDescriptorAscii(handle, descriptor[15], reinterpret_cast(shortAscii), sizeof(shortAscii)); + Require(shortLength >= 0 && shortLength <= 3 && shortAscii[shortLength] == 0 && std::strncmp(shortAscii, ascii, static_cast(shortLength)) == 0, "a short ASCII buffer was not filled with a terminated prefix"); } std::uint8_t again[18] = {}; Require(sceUsbdControlTransfer(handle, 0x80, 6, 0x0100, 0, again, sizeof(again), 1000) == 18, "synchronous device descriptor request failed"); Require(std::memcmp(again, descriptor, sizeof(again)) == 0, "device descriptor read over the wire differs from the cached one"); + std::uint8_t wire[18] = {}; + Require(sceUsbdGetDescriptor(handle, 1, 0, wire, sizeof(wire)) == 18, "device descriptor request through sceUsbdGetDescriptor failed"); + Require(std::memcmp(wire, descriptor, sizeof(wire)) == 0, "device descriptor read through sceUsbdGetDescriptor differs from the cached one"); for (std::int32_t interfaceNumber = 0; interfaceNumber < interfaces; ++interfaceNumber) { Require(sceUsbdClaimInterface(handle, interfaceNumber) == 0, "claiming an interface failed"); Require(sceUsbdReleaseInterface(handle, interfaceNumber) == 0, "releasing an interface failed"); @@ -153,6 +140,7 @@ void Device() { const std::uint8_t* interfaceArray = *reinterpret_cast(config0 + 16); std::uint8_t interruptAddress = 0; std::uint16_t interruptMax = 0; + std::uint8_t interruptInterface = 0; if (interfaceArray == nullptr) { Require(false, "the active config descriptor has no interface array"); } @@ -166,13 +154,31 @@ void Device() { if ((item[2] & 0x80) != 0 && (item[3] & 3) == 3) { interruptAddress = item[2]; interruptMax = static_cast(item[4] | item[5] << 8); + interruptInterface = alternate[2]; } } } Require(interruptAddress != 0 && interruptMax != 0, "the device has no interrupt IN endpoint"); sceUsbdFreeConfigDescriptor(config); + Require(sceUsbdGetMaxPacketSize(device, interruptAddress) == interruptMax, "the max packet size differs from the endpoint descriptor"); + const int expectedIso = speed <= 2 ? interruptMax : (interruptMax & 0x7FF) * std::min(1 + ((interruptMax >> 11) & 3), 3); + Require(sceUsbdGetMaxIsoPacketSize(device, interruptAddress) == expectedIso, "the max iso packet size differs from the endpoint descriptor"); + Require(sceUsbdGetMaxPacketSize(device, absentEndpoint) == notFound && sceUsbdGetMaxIsoPacketSize(device, absentEndpoint) == notFound, "an absent endpoint has a max packet size"); + Require(sceUsbdSetInterfaceAltSetting(handle, interruptInterface, 0) == notFound, "an unclaimed interface took an alternate setting"); + Require(sceUsbdSetInterfaceAltSetting(handle, 32, 0) == invalidArgument && sceUsbdSetInterfaceAltSetting(handle, -1, 0) == invalidArgument, "an interface number outside 0 to 31 was accepted"); + Require(sceUsbdClaimInterface(handle, interruptInterface) == 0, "claiming the interrupt endpoint's interface failed"); + const std::int32_t alternate = sceUsbdSetInterfaceAltSetting(handle, interruptInterface, 0); + Require(alternate == 0 || alternate == otherError, "alternate setting 0 returned neither success nor the other error"); + std::printf("alternate setting 0 of interface %u: %d\n", interruptInterface, alternate); + Require(sceUsbdClearHalt(handle, interruptAddress) == 0, "clearing the interrupt endpoint's halt failed"); + Require(sceUsbdClearHalt(handle, absentEndpoint) == otherError, "clearing the halt of an absent endpoint did not return the other error"); + const std::int32_t detached = sceUsbdDetachKernelDriver(handle, interruptInterface); + Require(detached == 0 || detached == otherError, "detaching the kernel driver returned neither success nor the other error"); + std::printf("kernel driver detach: %d\n", detached); + Require(sceUsbdReleaseInterface(handle, interruptInterface) == 0, "releasing the interrupt endpoint's interface failed"); + Require(sceUsbdSetInterfaceAltSetting(handle, interruptInterface, 0) == notFound, "a released interface took an alternate setting"); - auto* transfer = static_cast(sceUsbdAllocTransfer(0)); + auto* transfer = reinterpret_cast(sceUsbdAllocTransfer(0)); std::uint8_t setup[8 + 18] = {0x80, 6, 0x00, 0x01, 0x00, 0x00, 18, 0}; int done = 0; transfer->deviceHandle = handle; @@ -182,8 +188,8 @@ void Device() { transfer->length = sizeof(setup); transfer->callback = MarkDone; transfer->userData = &done; - Require(sceUsbdSubmitTransfer(transfer) == 0, "submitting an asynchronous control transfer failed"); - Require(Throws([&] { sceUsbdFreeTransfer(transfer); }), "an in-flight transfer was freed"); + Require(sceUsbdSubmitTransfer(reinterpret_cast(transfer)) == 0, "submitting an asynchronous control transfer failed"); + Require(Throws([&] { sceUsbdFreeTransfer(reinterpret_cast(transfer)); }), "an in-flight transfer was freed"); Pump(done); Require(done == 1 && transfer->status == 0 && transfer->actualLength == 18, "asynchronous control transfer did not complete"); Require(std::memcmp(setup + 8, descriptor, 18) == 0, "asynchronous device descriptor differs from the cached one"); @@ -193,14 +199,15 @@ void Device() { for (int round = 0; round < 300 && !broken; ++round) { std::atomic notified{false}; std::uint8_t blink[64] = {}; - sceUsbdFillInterruptTransfer(transfer, handle, interruptAddress, blink, interruptMax, reinterpret_cast(&MarkDoneThenSignal), ¬ified, 0); - const int submitResult = sceUsbdSubmitTransfer(transfer); + sceUsbdFillInterruptTransfer(reinterpret_cast(transfer), handle, interruptAddress, blink, interruptMax, &MarkDoneThenSignal, ¬ified, 0); + + const int submitResult = sceUsbdSubmitTransfer(reinterpret_cast(transfer)); if (submitResult != 0) { broken = true; break; } std::thread canceller([&] { - const int outcome = sceUsbdCancelTransfer(transfer); + const int outcome = sceUsbdCancelTransfer(reinterpret_cast(transfer)); if (outcome != 0 && outcome != static_cast(0x80240005)) broken = true; }); while (!notified.load(std::memory_order_acquire)) { @@ -212,11 +219,23 @@ void Device() { } Require(!broken, "the cancel race failed"); - Require(sceUsbdCancelTransfer(transfer) == static_cast(0x80240005), "cancelling a completed transfer did not report not found"); - sceUsbdFreeTransfer(transfer); + Require(sceUsbdCancelTransfer(reinterpret_cast(transfer)) == static_cast(0x80240005), "cancelling a completed transfer did not report not found"); + sceUsbdFreeTransfer(reinterpret_cast(transfer)); sceUsbdClose(handle); + Require(Throws([&] { sceUsbdClose(handle); }), "a handle was closed twice"); + Require(Throws([&] { sceUsbdGetDevice(handle); }), "the device of a closed handle was returned"); sceUsbdUnrefDevice(device); + void* byId = sceUsbdOpenDeviceWithVidPid(static_cast(vendor), static_cast(product)); + Require(byId != nullptr, "opening the device by vendor and product failed"); + void* byIdDevice = sceUsbdGetDevice(byId); + std::uint8_t byIdDescriptor[18] = {}; + Require(sceUsbdGetDeviceDescriptor(byIdDevice, byIdDescriptor) == 0, "the device of a handle opened by vendor and product has no descriptor"); + Require((byIdDescriptor[8] | byIdDescriptor[9] << 8) == vendor && (byIdDescriptor[10] | byIdDescriptor[11] << 8) == product, "the device opened by vendor and product has another vendor or product"); + Require(sceUsbdGetMaxPacketSize(byIdDevice, interruptAddress) == interruptMax, "the max packet size through a handle's device differs"); + sceUsbdClose(byId); + Require(Throws([&] { sceUsbdGetDeviceDescriptor(byIdDevice, byIdDescriptor); }), "the device of a closed handle stayed known"); + std::puts("USBD device tests passed"); } @@ -232,8 +251,10 @@ int main(int argc, char** argv) { Require(sceUsbdGetDeviceDescriptor(list[index], deviceDescriptor) == 0 && deviceDescriptor[0] == 18 && deviceDescriptor[1] == 1, "listed device has no device descriptor"); Require(sceUsbdRefDevice(list[index]) == list[index], "listed device was not referenced"); sceUsbdUnrefDevice(list[index]); + } sceUsbdFreeDeviceList(list, 1); + Require(Throws([&] { sceUsbdFreeDeviceList(list, 1); }), "a device list was freed twice"); Require(sceUsbdGetDeviceList(nullptr) == invalidArgument, "null list accepted"); Require(sceUsbdHandleEventsTimeout(nullptr) == invalidArgument, "null timeout accepted"); @@ -249,9 +270,11 @@ int main(int argc, char** argv) { Require(Throws([&] { sceUsbdOpen(nullptr, &handle); }), "opening a null device did not throw"); Require(Throws([&] { sceUsbdOpen(&foreign, &handle); }), "opening a device that was never listed did not throw"); sceUsbdClose(nullptr); + Require(Throws([&] { sceUsbdClose(&foreign); }), "closing a handle that was never opened did not throw"); Require(sceUsbdRefDevice(nullptr) == nullptr, "null device was referenced"); sceUsbdUnrefDevice(nullptr); + Require(Throws([&] { sceUsbdRefDevice(&foreign); }), "referencing a device that was never listed did not throw"); Require(Throws([&] { sceUsbdUnrefDevice(&foreign); }), "unreferencing a device that was never listed did not throw"); Require(sceUsbdGetBusNumber(nullptr) == 0 && sceUsbdGetDeviceAddress(nullptr) == 0, "null device reported a bus or address"); @@ -273,28 +296,71 @@ int main(int argc, char** argv) { Require(Throws([&] { sceUsbdControlTransfer(&foreign, 0x80, 6, 0x100, 0, descriptor, 18, 1000); }), "control transfer on a handle that was never opened did not throw"); Require(sceUsbdGetStringDescriptor(nullptr, 1, 0x409, descriptor, 18) == invalidArgument, "null handle string descriptor accepted"); Require(sceUsbdGetStringDescriptor(&foreign, 1, 0x409, descriptor, 0) == invalidArgument, "empty string descriptor buffer accepted"); + descriptor[0] = 0x55; + Require(sceUsbdGetStringDescriptorAscii(nullptr, 1, descriptor, 18) == invalidArgument, "null handle ASCII string descriptor accepted"); + Require(sceUsbdGetStringDescriptorAscii(&foreign, 1, nullptr, 18) == invalidArgument && sceUsbdGetStringDescriptorAscii(&foreign, 1, descriptor, 0) == invalidArgument, "missing ASCII string buffer accepted"); + Require(descriptor[0] == 0x55, "a rejected ASCII string request wrote the buffer"); + Require(Throws([&] { sceUsbdGetStringDescriptorAscii(&foreign, 1, descriptor, 18); }), "ASCII string request on a handle that was never opened did not throw"); + Require(sceUsbdGetDescriptor(nullptr, 1, 0, descriptor, 18) == invalidArgument, "null handle descriptor request accepted"); + Require(sceUsbdGetDescriptor(&foreign, 1, 0, nullptr, 18) == invalidArgument && sceUsbdGetDescriptor(&foreign, 1, 0, descriptor, 0) == invalidArgument, "missing descriptor buffer accepted"); + Require(Throws([&] { sceUsbdGetDescriptor(&foreign, 1, 0, descriptor, 18); }), "descriptor request on a handle that was never opened did not throw"); + std::int32_t configuration = -1; + Require(sceUsbdGetConfiguration(nullptr, &configuration) == invalidArgument && sceUsbdGetConfiguration(&foreign, nullptr) == invalidArgument, "null configuration query accepted"); + Require(configuration == -1, "a rejected configuration query wrote the output"); + Require(Throws([&] { sceUsbdGetConfiguration(&foreign, &configuration); }), "configuration of a handle that was never opened did not throw"); + Require(sceUsbdGetConfigDescriptorByValue(nullptr, 1, &config) == invalidArgument && sceUsbdGetConfigDescriptorByValue(&foreign, 1, nullptr) == invalidArgument, "null config by value argument accepted"); + Require(Throws([&] { sceUsbdGetConfigDescriptorByValue(&foreign, 1, &config); }), "config by value of a device that was never listed did not throw"); Require(Throws([&] { sceUsbdClaimInterface(&foreign, 0); }), "claiming on a handle that was never opened did not throw"); - auto* transfer = static_cast(sceUsbdAllocTransfer(2)); + auto* transfer = reinterpret_cast(sceUsbdAllocTransfer(2)); Require(transfer != nullptr, "transfer allocation failed"); Require(*reinterpret_cast(transfer + 56) == 2, "iso packet count not stored"); Require(sceUsbdAllocTransfer(-1) == nullptr, "negative iso packet count accepted"); std::uint8_t buffer[8] = {}; int user = 0; - sceUsbdFillInterruptTransfer(transfer, nullptr, 0x81, buffer, 8, reinterpret_cast(&Require), &user, 250); + sceUsbdFillInterruptTransfer(reinterpret_cast(transfer), nullptr, 0x81, buffer, 8, reinterpret_cast(&Require), &user, 250); + Require(transfer[9] == 0x81 && transfer[10] == 3, "endpoint or interrupt type not filled"); Require(*reinterpret_cast(transfer + 12) == 250 && *reinterpret_cast(transfer + 20) == 8, "timeout or length not filled"); Require(*reinterpret_cast(transfer + 40) == &user && *reinterpret_cast(transfer + 48) == buffer, "user data or buffer not filled"); - Require(sceUsbdCancelTransfer(transfer) == static_cast(0x80240005), "cancelling an unsubmitted transfer did not report not found"); + Require(sceUsbdCancelTransfer(reinterpret_cast(transfer)) == static_cast(0x80240005), "cancelling an unsubmitted transfer did not report not found"); Require(sceUsbdSubmitTransfer(nullptr) == invalidArgument, "null transfer submitted"); - Require(sceUsbdSubmitTransfer(transfer) == invalidArgument, "transfer without a handle submitted"); - sceUsbdFillInterruptTransfer(transfer, &foreign, 0x81, buffer, 8, reinterpret_cast(&Require), &user, 250); - Require(Throws([&] { sceUsbdSubmitTransfer(transfer); }), "submitting on a handle that was never opened did not throw"); - sceUsbdFreeTransfer(transfer); + Require(sceUsbdSubmitTransfer(reinterpret_cast(transfer)) == invalidArgument, "transfer without a handle submitted"); + sceUsbdFillInterruptTransfer(reinterpret_cast(transfer), &foreign, 0x81, buffer, 8, reinterpret_cast(&Require), &user, 250); + + Require(Throws([&] { sceUsbdSubmitTransfer(reinterpret_cast(transfer)); }), "submitting on a handle that was never opened did not throw"); + sceUsbdFreeTransfer(reinterpret_cast(transfer)); + Require(Throws([&] { sceUsbdClearHalt(nullptr, 0x81); }), "clearing a halt on a null handle did not throw"); + Require(Throws([&] { sceUsbdClearHalt(&foreign, 0x81); }), "clearing a halt on a handle that was never opened did not throw"); + Require(sceUsbdDetachKernelDriver(nullptr, 0) == invalidArgument, "null handle kernel driver detach accepted"); + Require(Throws([&] { sceUsbdDetachKernelDriver(&foreign, 0); }), "kernel driver detach on a handle that was never opened did not throw"); + Require(sceUsbdGetDevice(nullptr) == nullptr, "a null handle has a device"); + Require(Throws([&] { sceUsbdGetDevice(&foreign); }), "the device of a handle that was never opened did not throw"); + Require(sceUsbdGetDeviceSpeed(nullptr) == 0, "a null device has a speed"); + Require(Throws([&] { sceUsbdGetDeviceSpeed(&foreign); }), "the speed of a device that was never listed did not throw"); + Require(sceUsbdGetMaxPacketSize(nullptr, 0x81) == noDevice, "a null device has a max packet size"); + Require(Throws([&] { sceUsbdGetMaxPacketSize(&foreign, 0x81); }), "the max packet size of a device that was never listed did not throw"); + Require(Throws([&] { sceUsbdGetMaxIsoPacketSize(nullptr, 0x81); }), "the max iso packet size of a null device did not throw"); + Require(Throws([&] { sceUsbdGetMaxIsoPacketSize(&foreign, 0x81); }), "the max iso packet size of a device that was never listed did not throw"); + Require(sceUsbdSetInterfaceAltSetting(nullptr, 0, 0) == invalidArgument, "null handle alternate setting accepted"); + Require(Throws([&] { sceUsbdSetInterfaceAltSetting(&foreign, 0, 0); }), "an alternate setting on a handle that was never opened did not throw"); + Require(sceUsbdOpenDeviceWithVidPid(0, 0) == nullptr, "a device with vendor and product 0 was opened"); sceUsbdFreeTransfer(nullptr); + Require(sceUsbdEventHandlingOk() == 1, "event handling not allowed"); sceUsbdFreeConfigDescriptor(nullptr); + + Require(Throws([] { sceUsbdFillControlSetup(nullptr, 0x80, 6, 0x0100, 0, 18); }), "null control setup buffer accepted"); + std::uint8_t setupBuffer[8] = {}; + sceUsbdFillControlSetup(setupBuffer, 0x80, 6, 0x0100, 0x0409, 18); + + Require(setupBuffer[0] == 0x80 && setupBuffer[1] == 6, "request type or request not filled"); + Require(setupBuffer[2] == 0x00 && setupBuffer[3] == 0x01, "value not filled little-endian"); + Require(setupBuffer[4] == 0x09 && setupBuffer[5] == 0x04, "index not filled little-endian"); + Require(setupBuffer[6] == 18 && setupBuffer[7] == 0, "length not filled little-endian"); if (argc == 2 && std::strcmp(argv[1], "device") == 0) Device(); sceUsbdExit(); + Require(sceUsbdOpenDeviceWithVidPid(0, 0) == nullptr, "a device was opened after the library exited"); + std::puts("USBD tests passed"); return 0; } diff --git a/core/libs/tests/GuestUserContext.cpp b/core/libs/tests/GuestUserContext.cpp index c616e2260d..46fb68c1d5 100644 --- a/core/libs/tests/GuestUserContext.cpp +++ b/core/libs/tests/GuestUserContext.cpp @@ -1,3 +1,6 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/general/AsmMacros.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/System/include/UserContext.hpp" @@ -16,15 +19,6 @@ using ContextEntry = void (APS5_VABI*)(); extern "C" { -[[gnu::returns_twice]] int APS5_VABI getcontext_nid_postfix(UserUcontext* context); -int APS5_VABI setcontext_nid_postfix(const UserUcontext* context); -void APS5_VABI makecontext_nid_postfix(UserUcontext* context, ContextEntry entry, int count, ...); -[[gnu::returns_twice]] int APS5_VABI setjmp_nid_postfix(std::uint64_t* buffer); -[[noreturn]] void APS5_VABI longjmp_nid_postfix(std::uint64_t* buffer, int value); -[[gnu::returns_twice]] int APS5_VABI sigsetjmp_nid_postfix(std::uint64_t* buffer, int saveMask); -[[noreturn]] void APS5_VABI siglongjmp_nid_postfix(std::uint64_t* buffer, int value); -int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet); -int* APS5_VABI __error_nid_postfix(); int APS5_VABI CaptureKnownRegisters(UserUcontext* context); void CaptureReturnAddress(); @@ -238,24 +232,29 @@ void CheckErrors() { invalid.stackPointer = g_contextStack.bytes; invalid.stackSize = StackBytes; makecontext_nid_postfix(&invalid, &EntryThunk, 7); + Require(invalid.mcontext.len == 0, "makecontext invalidates a context with more than six arguments"); invalid = context; invalid.stackPointer = g_contextStack.bytes; invalid.stackSize = 2047; makecontext_nid_postfix(&invalid, &EntryThunk, 0); + Require(invalid.mcontext.len == 0, "makecontext invalidates a context with a stack below MINSIGSTKSZ"); invalid = context; invalid.stackPointer = nullptr; invalid.stackSize = StackBytes; makecontext_nid_postfix(&invalid, &EntryThunk, 0); + Require(invalid.mcontext.len == 0, "makecontext invalidates a context without a stack"); invalid = context; invalid.stackPointer = g_contextStack.bytes; invalid.stackSize = StackBytes; invalid.mcontext.len = 0; makecontext_nid_postfix(&invalid, &EntryThunk, 0); + Require(invalid.mcontext.len == 0 && invalid.mcontext.rip == context.mcontext.rip, "makecontext leaves a context without a valid mc_len untouched"); makecontext_nid_postfix(nullptr, &EntryThunk, 0); + } void CheckCapture() { @@ -331,6 +330,7 @@ void CheckRestore() { std::memcpy(image + FxsaveXmm + index * 8, &value, sizeof(value)); } setcontext_nid_postfix(&target); + Require(false, "setcontext returned from a valid context"); } const auto& machine = target.mcontext; @@ -374,6 +374,7 @@ namespace { if (sigsetjmp_nid_postfix(from->jump, 1) == 0) { if (to->started) siglongjmp_nid_postfix(to->jump, 1); setcontext_nid_postfix(&to->context); + Require(false, "setcontext into the job returned"); } return; @@ -381,6 +382,7 @@ namespace { if (setjmp_nid_postfix(from->jump) == 0) { if (to->started) longjmp_nid_postfix(to->jump, 1); setcontext_nid_postfix(&to->context); + Require(false, "setcontext into the job returned"); } } @@ -415,8 +417,10 @@ void CheckMakecontext() { context.stackSize = StackBytes; context.link = &caller; makecontext_nid_postfix(&context, &EntryThunk, 6, 0x8000000000000001ull, 0x8000000000000002ull, 0x8000000000000003ull, 0x8000000000000004ull, 0x8000000000000005ull, 0x8000000000000006ull); + Require(context.mcontext.len == sizeof(UserMcontext), "makecontext keeps the context valid"); setcontext_nid_postfix(&context); + Require(false, "setcontext into the made context returned"); } Require(g_entries == 1, "the made context runs once and resumes uc_link"); @@ -448,6 +452,7 @@ void CheckJumpBuffers(bool signalJumps) { g_job.context.stackSize = StackBytes; g_job.context.link = nullptr; makecontext_nid_postfix(&g_job.context, &JobEntry, 0); + for (int round = 1; round <= 4; ++round) { Switch(&g_dispatcher, &g_job); Require(g_jobRuns == round, "the job runs once per switch"); @@ -473,6 +478,7 @@ void CheckSignalMask() { phase = 1; Require(sigprocmask_nid_postfix(GuestSigSetmask, second, nullptr) == 0, "set the second mask"); setcontext_nid_postfix(&context); + Require(false, "setcontext returned from a valid context"); } std::uint32_t current[4] = {}; @@ -488,6 +494,7 @@ int ExitFromContext() { context.link = nullptr; makecontext_nid_postfix(&context, &ReturnWithoutLink, 0); setcontext_nid_postfix(&context); + return 1; } diff --git a/core/libs/tests/GuestUserService.cpp b/core/libs/tests/GuestUserService.cpp index 4e6e1809a4..9b46dffc13 100644 --- a/core/libs/tests/GuestUserService.cpp +++ b/core/libs/tests/GuestUserService.cpp @@ -2,32 +2,38 @@ #include "prx/libSceUserService/UserService.hpp" #include #include +#include #include - -extern "C" { -int APS5_VABI sceUserServiceGetForegroundUser(int* user_id); -int APS5_VABI sceUserServiceGetRegisteredUserIdList(UserServiceRegisteredUserIdList* user_id_list); -int APS5_VABI sceUserServiceGetUserColor(int user_id, int* color); -int APS5_VABI sceUserServiceGetNpAccountId(int user_id, std::uint64_t* account_id); -int APS5_VABI sceUserServiceGetAgeLevel(int user_id, std::uint32_t* age_level); -int APS5_VABI sceUserServiceGetAccessibilityChatTranscription(int user_id, std::int32_t* value); -int APS5_VABI sceUserServiceGetAccessibilityKeyremapEnable(); -int APS5_VABI sceUserServiceGetAccessibilityPressAndHoldDelay(int user_id, std::int32_t* value); -int APS5_VABI sceUserServiceGetAccessibilityTriggerEffect(int user_id, std::int32_t* value); -int APS5_VABI sceUserServiceGetAccessibilityVibration(int user_id, std::int32_t* value); -int APS5_VABI sceUserServiceGetAccessibilityZoom(); -int APS5_VABI sceUserServiceGetAccessibilityZoomEnabled(int user_id, std::int32_t* value); -int APS5_VABI sceUserServiceGetAccessibilityZoomFollowFocus(int user_id, std::int32_t* value); -} +#include +#include namespace { void Require(bool value) { if (!value) std::abort(); } +void SetLanguage(const char* value) { +#ifdef _WIN32 + Require(_putenv_s("ANYPS5_LANGUAGE", value != nullptr ? value : "") == 0); +#else + Require((value != nullptr ? ::setenv("ANYPS5_LANGUAGE", value, 1) : ::unsetenv("ANYPS5_LANGUAGE")) == 0); +#endif +} + } int main() { Require(sceUserServiceGetForegroundUser(nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + int32_t userNumber = -1; + Require(sceUserServiceGetUserNumber(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserNumber(123, &userNumber) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserNumber(USER_SERVICE_INITIAL_USER_ID, &userNumber) == USER_SERVICE_OK); + Require(userNumber == 1); + + int32_t privacy = -1; + 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); + int foreground = -1; Require(sceUserServiceGetForegroundUser(&foreground) == USER_SERVICE_OK); Require(foreground == USER_SERVICE_INITIAL_USER_ID); @@ -44,6 +50,48 @@ int main() { Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetUserColor(123, &color) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID, &color) == USER_SERVICE_OK); + + SetLanguage(nullptr); + using KeyboardSetting = decltype(&sceUserServiceGetKeyboardType); + const std::pair keyboardSettings[] = { + {sceUserServiceGetKeyboardType, 20}, {sceUserServiceGetJapaneseInputType, 0}, {sceUserServiceGetTraditionalChineseInputType, 0}}; + for (const auto& [setting, expected] : keyboardSettings) { + std::int32_t value = 0x5A5A; + Require(setting(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(setting(123, &value) == USER_SERVICE_ERROR_UNKNOWN_USER && value == 0x5A5A); + Require(setting(USER_SERVICE_USER_ID_INVALID, &value) == USER_SERVICE_ERROR_UNKNOWN_USER && value == 0x5A5A); + Require(setting(USER_SERVICE_INITIAL_USER_ID, &value) == USER_SERVICE_OK && value == expected); + } + constexpr std::int32_t keyboardTypeByLanguage[] = {37, 20, 26, 21, 17, 28, 29, 33, 34, 38, 39, 40, 23, 35, 16, 30, 31, 32, 19, 36, 22, 41, 25, 44, 48, 43, 47, 42, 46, 45}; + for (int language = 0; language < 30; ++language) { + SetLanguage(std::to_string(language).c_str()); + std::int32_t value = 0x5A5A; + Require(sceUserServiceGetKeyboardType(USER_SERVICE_INITIAL_USER_ID, &value) == USER_SERVICE_OK && value == keyboardTypeByLanguage[language]); + Require(sceUserServiceGetKeyboardType(123, &value) == USER_SERVICE_ERROR_UNKNOWN_USER && value == keyboardTypeByLanguage[language]); + for (const auto variant : {sceUserServiceGetJapaneseInputType, sceUserServiceGetTraditionalChineseInputType}) { + value = 0x5A5A; + Require(variant(USER_SERVICE_INITIAL_USER_ID, &value) == USER_SERVICE_OK && value == 0); + } + } + SetLanguage("30"); + std::int32_t fallback = 0x5A5A; + Require(sceUserServiceGetKeyboardType(USER_SERVICE_INITIAL_USER_ID, &fallback) == USER_SERVICE_OK && fallback == 20); + for (const char* malformed : {"arabic", "-1", "31", "1x", ""}) { + SetLanguage(malformed); + std::int32_t untouched = 0x5A5A; + bool rejected = false; + try { + sceUserServiceGetKeyboardType(USER_SERVICE_INITIAL_USER_ID, &untouched); + } catch (const std::runtime_error& error) { + rejected = std::string(error.what()).find("ANYPS5_LANGUAGE") != std::string::npos; + } + const bool configured = malformed[0] != '\0'; + Require(rejected == configured && untouched == (configured ? 0x5A5A : 20)); + untouched = 0x5A5A; + Require(sceUserServiceGetKeyboardType(123, &untouched) == USER_SERVICE_ERROR_UNKNOWN_USER && untouched == 0x5A5A); + Require(sceUserServiceGetJapaneseInputType(USER_SERVICE_INITIAL_USER_ID, &untouched) == USER_SERVICE_OK && untouched == 0); + } + SetLanguage(nullptr); Require(color == 0); std::uint64_t account_id = 0xdeadbeefu; @@ -86,9 +134,18 @@ int main() { Require(setting.get(USER_SERVICE_INITIAL_USER_ID, &value) == USER_SERVICE_OK); Require(value == setting.value); } + int initialUser = -1; + Require(sceUserServiceGetInitialUser(nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetInitialUser(&initialUser) == USER_SERVICE_OK && initialUser == USER_SERVICE_INITIAL_USER_ID); + Require(sceUserServiceGetGamePresets(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + UserServiceGamePresets presets{}; + presets.difficulty = 0x5a; + Require(sceUserServiceGetGamePresets(0, &presets) == USER_SERVICE_ERROR_INVALID_ARGUMENT && presets.difficulty == 0x5a); + Require(sceUserServiceGetGamePresets(USER_SERVICE_INITIAL_USER_ID, &presets) == USER_SERVICE_OK && presets.difficulty == 0); + std::int32_t accessibilityValue = 0; bool threwKeyremap = false; try { - sceUserServiceGetAccessibilityKeyremapEnable(); + sceUserServiceGetAccessibilityKeyremapEnable(USER_SERVICE_INITIAL_USER_ID, &accessibilityValue); } catch (const std::runtime_error&) { threwKeyremap = true; } @@ -96,9 +153,68 @@ int main() { bool threwZoom = false; try { - sceUserServiceGetAccessibilityZoom(); + sceUserServiceGetAccessibilityZoom(USER_SERVICE_INITIAL_USER_ID, &accessibilityValue); } catch (const std::runtime_error&) { threwZoom = true; } Require(threwZoom); + + char name[32]{}; + Require(sceUserServiceGetUserName(USER_SERVICE_INITIAL_USER_ID, nullptr, sizeof(name)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserName(123, name, sizeof(name)) == USER_SERVICE_ERROR_UNKNOWN_USER); + Require(sceUserServiceGetUserName(USER_SERVICE_INITIAL_USER_ID, name, 5) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(sceUserServiceGetUserName(USER_SERVICE_INITIAL_USER_ID, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "Player") == 0); + Require(sceUserServiceGetUserName(USER_SERVICE_INITIAL_USER_ID + 1, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "Player2") == 0); + + std::int32_t number = -1; + Require(sceUserServiceGetUserNumber(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserNumber(123, &number) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserNumber(USER_SERVICE_INITIAL_USER_ID, &number) == USER_SERVICE_OK); + Require(number == 1); + Require(sceUserServiceGetUserNumber(USER_SERVICE_INITIAL_USER_ID + 1, &number) == USER_SERVICE_OK); + Require(number == 2); + + Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID + 1, &color) == USER_SERVICE_OK); + Require(color == 1); + + 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); + 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); + Require(value == 0x5a5a5a5a); + Require(setting.get(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + } + + UserServiceSetUserConnected_nid_postfix(1, true); + Require(sceUserServiceGetRegisteredUserIdList(®istered) == USER_SERVICE_OK); + Require(registered.user_id[0] == USER_SERVICE_INITIAL_USER_ID); + Require(registered.user_id[1] == USER_SERVICE_INITIAL_USER_ID + 1); + for (int i = 2; i < 16; ++i) { + Require(registered.user_id[i] == USER_SERVICE_USER_ID_INVALID); + } + Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, &age_level) == USER_SERVICE_OK); + Require(age_level == 0xffffffffu); + for (const Setting& setting : settings) { + std::int32_t value = 0x5a5a5a5a; + Require(setting.get(USER_SERVICE_INITIAL_USER_ID + 1, &value) == USER_SERVICE_OK); + Require(value == setting.value); + } + + UserServiceSetUserConnected_nid_postfix(1, false); + Require(sceUserServiceGetRegisteredUserIdList(®istered) == USER_SERVICE_OK); + Require(registered.user_id[0] == USER_SERVICE_INITIAL_USER_ID); + for (int i = 1; i < 16; ++i) { + Require(registered.user_id[i] == USER_SERVICE_USER_ID_INVALID); + } + 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); + 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); + Require(value == 0x5a5a5a5a); + Require(setting.get(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + } } diff --git a/core/libs/tests/GuestUserServiceLifecycle.cpp b/core/libs/tests/GuestUserServiceLifecycle.cpp index c169e42d26..99a61cc97b 100644 --- a/core/libs/tests/GuestUserServiceLifecycle.cpp +++ b/core/libs/tests/GuestUserServiceLifecycle.cpp @@ -4,14 +4,6 @@ #include #include -extern "C" { -int APS5_VABI sceUserServiceInitialize(const void* params); -int APS5_VABI sceUserServiceInitialize2(int priority); -int APS5_VABI sceUserServiceTerminate(void); -int APS5_VABI sceUserServiceGetLoginUserIdList(UserServiceLoginUserIdList* user_id_list); -int APS5_VABI sceUserServiceGetEvent(SceUserServiceEvent* event); -} - namespace { void Require(bool value) { if (!value) std::abort(); } @@ -34,25 +26,26 @@ void RequireLoginOnce() { } int InitializeWithPriority(int priority) { - return sceUserServiceInitialize(&priority); + const UserServiceInitParam param{priority}; + return sceUserServiceInitialize(¶m); } void TestThreadPriority() { for (int priority : {0, 0xFF, 0x300, -1}) { Require(InitializeWithPriority(priority) == USER_SERVICE_ERROR_INVALID_ARGUMENT); - Require(sceUserServiceInitialize2(priority) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceInitialize2(priority, 1) == USER_SERVICE_ERROR_INVALID_ARGUMENT); } RequireNotInitialized(); - Require(sceUserServiceInitialize2(0x100) == USER_SERVICE_OK); + Require(sceUserServiceInitialize2(0x100, 1) == USER_SERVICE_OK); RequireLoginOnce(); for (int priority : {0x100, 700, 0x2FF}) { Require(InitializeWithPriority(priority) == USER_SERVICE_ERROR_ALREADY_INITIALIZED); - Require(sceUserServiceInitialize2(priority) == USER_SERVICE_ERROR_ALREADY_INITIALIZED); + Require(sceUserServiceInitialize2(priority, 1) == USER_SERVICE_ERROR_ALREADY_INITIALIZED); } for (int priority : {0, 0xFF, 0x300, -1}) { Require(InitializeWithPriority(priority) == USER_SERVICE_ERROR_INVALID_ARGUMENT); - Require(sceUserServiceInitialize2(priority) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceInitialize2(priority, 1) == USER_SERVICE_ERROR_INVALID_ARGUMENT); } Require(sceUserServiceInitialize(nullptr) == USER_SERVICE_ERROR_ALREADY_INITIALIZED); Require(sceUserServiceTerminate() == USER_SERVICE_OK); @@ -81,6 +74,42 @@ int main() { Require(sceUserServiceInitialize(nullptr) == USER_SERVICE_OK); RequireLoginOnce(); + + UserServiceSetUserConnected_nid_postfix(1, true); + SceUserServiceEvent event{}; + Require(sceUserServiceGetEvent(&event) == USER_SERVICE_OK); + Require(event.event_type == 0 && event.user_id == USER_SERVICE_INITIAL_USER_ID + 1); + Require(sceUserServiceGetLoginUserIdList(&list) == USER_SERVICE_OK); + Require(list.user_id[0] == USER_SERVICE_INITIAL_USER_ID); + Require(list.user_id[1] == USER_SERVICE_INITIAL_USER_ID + 1); + + UserServiceSetUserConnected_nid_postfix(1, false); + Require(sceUserServiceGetEvent(&event) == USER_SERVICE_OK); + Require(event.event_type == 1 && event.user_id == USER_SERVICE_INITIAL_USER_ID + 1); + Require(sceUserServiceGetLoginUserIdList(&list) == USER_SERVICE_OK); + Require(list.user_id[0] == USER_SERVICE_INITIAL_USER_ID); + Require(list.user_id[1] == USER_SERVICE_USER_ID_INVALID); + + for (int i = 0; i < 50; ++i) { + UserServiceSetUserConnected_nid_postfix(1, true); + UserServiceSetUserConnected_nid_postfix(1, false); + } + Require(sceUserServiceGetEvent(&event) == USER_SERVICE_ERROR_NO_EVENT); + + UserServiceSetUserConnected_nid_postfix(2, true); + UserServiceSetUserConnected_nid_postfix(2, false); + Require(sceUserServiceGetEvent(&event) == USER_SERVICE_ERROR_NO_EVENT); + + UserServiceSetUserConnected_nid_postfix(2, true); + Require(sceUserServiceGetEvent(&event) == USER_SERVICE_OK); + Require(event.event_type == 0 && event.user_id == USER_SERVICE_INITIAL_USER_ID + 2); + UserServiceSetUserConnected_nid_postfix(2, false); + UserServiceSetUserConnected_nid_postfix(2, true); + Require(sceUserServiceGetEvent(&event) == USER_SERVICE_ERROR_NO_EVENT); + UserServiceSetUserConnected_nid_postfix(2, false); + Require(sceUserServiceGetEvent(&event) == USER_SERVICE_OK); + Require(event.event_type == 1 && event.user_id == USER_SERVICE_INITIAL_USER_ID + 2); + Require(sceUserServiceTerminate() == USER_SERVICE_OK); TestThreadPriority(); diff --git a/core/libs/tests/GuestUserServiceUserName.cpp b/core/libs/tests/GuestUserServiceUserName.cpp index f755162970..15ad3469e3 100644 --- a/core/libs/tests/GuestUserServiceUserName.cpp +++ b/core/libs/tests/GuestUserServiceUserName.cpp @@ -5,10 +5,6 @@ #include #include -extern "C" { -int APS5_VABI sceUserServiceGetUserName(int user_id, char* name, std::size_t size); -} - namespace { void Require(bool value) { if (!value) std::abort(); } diff --git a/core/libs/tests/GuestUtime.cpp b/core/libs/tests/GuestUtime.cpp index 884cff4e47..48417ce88f 100644 --- a/core/libs/tests/GuestUtime.cpp +++ b/core/libs/tests/GuestUtime.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -12,17 +14,8 @@ #include #endif -struct LibcUtimbuf { - std::int64_t actime; - std::int64_t modtime; -}; static_assert(sizeof(LibcUtimbuf) == 16); -extern "C" { -int APS5_VABI utime_nid_postfix(const char*, const LibcUtimbuf*); -int* APS5_VABI __error_nid_postfix(); -} - static void Require(bool condition, int line) { if (!condition) { std::fprintf(stderr, "Utime check failed at line %d\n", line); diff --git a/core/libs/tests/GuestUtimeForwarding.cpp b/core/libs/tests/GuestUtimeForwarding.cpp index 870d83d404..146ca8f7bd 100644 --- a/core/libs/tests/GuestUtimeForwarding.cpp +++ b/core/libs/tests/GuestUtimeForwarding.cpp @@ -1,15 +1,9 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" #include "SceTypes.hpp" #include #include #include -struct LibcUtimbuf { - std::int64_t actime; - std::int64_t modtime; -}; - -extern "C" int APS5_VABI utime_nid_postfix(const char*, const LibcUtimbuf*); - namespace { int guestError; int backendResult; diff --git a/core/libs/tests/GuestVdecsw.cpp b/core/libs/tests/GuestVdecsw.cpp index 14fdca44a9..0990141df2 100644 --- a/core/libs/tests/GuestVdecsw.cpp +++ b/core/libs/tests/GuestVdecsw.cpp @@ -1,168 +1,40 @@ +#include "prx/libSceVdecsw/libSceVdecsw.hpp" +#include #include #include #include #include #include +#include #include #include #include #include "prx/libc/include/General.hpp" #include "Vp9Fixture.hpp" #include "H264Fixture.hpp" +#include "HevcFixture.hpp" -struct ComputeMemoryInfo { - std::uint64_t thisSize; - std::uint64_t cpuGpuMemorySize; - void* cpuGpuMemory; -}; +using namespace SceVdecsw; -struct ComputeConfigInfo { - std::uint64_t thisSize; - std::uint16_t computePipeId; - std::uint16_t computeQueueId; - bool checkMemoryType; -}; +static_assert(sizeof(SceVideodec2::Vp9PictureInfo) == 0x58 && offsetof(SceVideodec2::Vp9PictureInfo, ptsData) == 0x10 && offsetof(SceVideodec2::Vp9PictureInfo, profile) == 0x28 && offsetof(SceVideodec2::Vp9PictureInfo, bitDepth) == 0x38 && offsetof(SceVideodec2::Vp9PictureInfo, colorSpace) == 0x3c && offsetof(SceVideodec2::Vp9PictureInfo, renderHeight) == 0x50); -struct DecoderConfigInfo { - std::uint64_t thisSize; - std::uint32_t resourceType; - std::uint32_t codecType; - std::uint32_t profile; - std::uint32_t maxLevel; - std::int32_t maxFrameWidth; - std::int32_t maxFrameHeight; - std::int32_t maxDpbFrameCount; - std::uint32_t decodePipelineDepth; - std::uint64_t computeQueue; - std::uint64_t cpuAffinityMask; - std::int32_t cpuThreadPriority; - bool optimizeProgressiveVideo; - std::uint8_t reserved[19]; -}; - -struct DecoderMemoryInfo { - std::uint64_t thisSize; - std::uint64_t cpuMemorySize; - void* cpuMemory; - std::uint64_t gpuMemorySize; - void* gpuMemory; - std::uint64_t cpuGpuMemorySize; - void* cpuGpuMemory; - std::uint64_t maxFrameBufferSize; - std::uint32_t frameBufferAlignment; -}; - -struct InputData { - std::uint64_t thisSize; - const std::uint8_t* auData; - std::uint64_t auSize; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; -}; - -struct InputResult { - std::uint64_t thisSize; - const void* decodedAu; - std::uint32_t outputFrameCount; - std::uint32_t reserved; -}; - -struct FrameBuffer { - std::uint64_t thisSize; - void* frameBuffer; - std::uint64_t frameBufferSize; -}; - -struct OutputInfo { - std::uint64_t thisSize; - bool isValid; - bool isLastFrame; - bool isErrorFrame; - std::uint8_t pictureCount; - std::uint32_t codecType; - std::uint32_t frameWidth; - std::uint32_t framePitch; - std::uint32_t frameHeight; - bool isDiscardedFrame; - void* frameBuffer; - std::uint64_t frameBufferSize; - std::uint32_t frameFormat; - std::uint32_t framePitchInBytes; -}; - -struct Vp9PictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint32_t profile; - std::uint32_t level; - std::uint32_t keyFrameFlag; - std::uint32_t intraOnly; - std::uint8_t bitDepth; - std::uint8_t colorRange; - std::uint32_t colorSpace; - std::uint32_t numFrames; - std::uint32_t frameWidth; - std::uint32_t frameHeight; - std::uint32_t renderWidth; - std::uint32_t renderHeight; -}; - -static_assert(sizeof(Vp9PictureInfo) == 0x58 && offsetof(Vp9PictureInfo, ptsData) == 0x10 && offsetof(Vp9PictureInfo, profile) == 0x28 && offsetof(Vp9PictureInfo, bitDepth) == 0x38 && offsetof(Vp9PictureInfo, colorSpace) == 0x3c && offsetof(Vp9PictureInfo, renderHeight) == 0x50); - -struct AvcPictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint8_t idrPictureFlag; - std::uint8_t profileIdc; - std::uint8_t levelIdc; - std::uint32_t picWidthInMbsMinus1; - std::uint32_t picHeightInMapUnitsMinus1; - std::uint8_t frameMbsOnlyFlag; - std::uint8_t frameCroppingFlag; - std::uint32_t frameCropLeftOffset; - std::uint32_t frameCropRightOffset; - std::uint32_t frameCropTopOffset; - std::uint32_t frameCropBottomOffset; - std::uint8_t vui[44]; -}; - -static_assert(sizeof(DecoderConfigInfo) == 0x50 && sizeof(OutputInfo) == 0x38 && offsetof(OutputInfo, isLastFrame) == 9 && offsetof(OutputInfo, pictureCount) == 0xb && sizeof(AvcPictureInfo) == 0x78); - -extern "C" { -int APS5_VABI sceVdecswQueryComputeMemoryInfo(ComputeMemoryInfo*); -int APS5_VABI sceVdecswAllocateComputeQueue(const ComputeConfigInfo*, const ComputeMemoryInfo*, std::uint64_t*); -int APS5_VABI sceVdecswReleaseComputeQueue(std::uint64_t); -int APS5_VABI sceVdecswQueryDecoderMemoryInfo(const DecoderConfigInfo*, DecoderMemoryInfo*); -int APS5_VABI sceVdecswCreateDecoder(const DecoderConfigInfo*, const DecoderMemoryInfo*, std::uint64_t*); -int APS5_VABI sceVdecswDeleteDecoder(std::uint64_t); -int APS5_VABI sceVdecswSetDecodeInput(std::uint64_t, const InputData*); -int APS5_VABI sceVdecswTrySyncDecodeInput(std::uint64_t, InputResult*); -int APS5_VABI sceVdecswSyncDecodeInput(std::uint64_t, InputResult*); -int APS5_VABI sceVdecswSetDecodeOutput(std::uint64_t, const FrameBuffer*); -int APS5_VABI sceVdecswTrySyncDecodeOutput(std::uint64_t, OutputInfo*); -int APS5_VABI sceVdecswSyncDecodeOutput(std::uint64_t, OutputInfo*); -int APS5_VABI sceVdecswGetVp9PictureInfo(const OutputInfo*, Vp9PictureInfo*); -int APS5_VABI sceVdecswGetAvcPictureInfo(const OutputInfo*, AvcPictureInfo*, AvcPictureInfo*); -int APS5_VABI sceVdecswGetHevcPictureInfo(const OutputInfo*, void*); -int APS5_VABI sceVdecswFinalizeDecodeSequence(std::uint64_t); -int APS5_VABI sceVdecswResetDecoder(std::uint64_t); -} +static_assert(sizeof(HevcPictureInfo) == 0xb8 && offsetof(HevcPictureInfo, idrPictureFlag) == 0x93 && offsetof(HevcPictureInfo, frameCroppingFlag) == 0xa5); +static_assert(sizeof(SceVdecsw::DecoderConfigInfo) == 0x50 && sizeof(OutputInfo) == 0x38 && offsetof(OutputInfo, isLastFrame) == 9 && offsetof(OutputInfo, pictureCount) == 0xb && sizeof(AvcPictureInfo) == 0x78); namespace { +using namespace SceVdecsw; + using namespace H264Fixture; +using namespace HevcFixture; using namespace Vp9Fixture; constexpr int OutputPending = static_cast(0x81510115u); constexpr int InputQueueEmpty = static_cast(0x81510116u); +constexpr std::uint32_t CodecAvc = 1; +constexpr std::uint32_t CodecHevc = 974921; constexpr std::uint32_t CodecVp9 = 2382845; +constexpr std::array HevcDisplayOrderUnits{0, 3, 2, 1, 5, 4}; void check(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); @@ -171,14 +43,14 @@ void check(bool condition, const std::string& message) { struct Session { std::uint64_t queue = 0; std::uint64_t decoder = 0; - DecoderMemoryInfo memory{sizeof(DecoderMemoryInfo)}; + SceVideodec2::DecoderMemoryInfo memory{sizeof(SceVideodec2::DecoderMemoryInfo)}; - Session() { - ComputeMemoryInfo compute{sizeof(ComputeMemoryInfo)}; + explicit Session(std::uint32_t codec = CodecAvc, std::uint32_t profile = 100, std::uint32_t level = 42) { + SceVideodec2::ComputeMemoryInfo compute{sizeof(SceVideodec2::ComputeMemoryInfo)}; check(sceVdecswQueryComputeMemoryInfo(&compute) == 0 && compute.cpuGpuMemorySize != 0, "compute memory query failed"); - const ComputeConfigInfo computeConfig{sizeof(ComputeConfigInfo), 3, 3, true}; + const SceVideodec2::ComputeConfigInfo computeConfig{sizeof(SceVideodec2::ComputeConfigInfo), 3, 3, true}; check(sceVdecswAllocateComputeQueue(&computeConfig, &compute, &queue) == 0, "compute queue allocation failed"); - DecoderConfigInfo config{sizeof(DecoderConfigInfo), 1, 1, 100, 42, static_cast(Width), static_cast(Height), 4, 3, queue, 0x3f, 0, true, {}}; + SceVdecsw::DecoderConfigInfo config{{sizeof(SceVdecsw::DecoderConfigInfo), 1, codec, profile, level, static_cast(Width), static_cast(Height), 4, 3}, queue, 0x3f, 0, true, {}}; check(sceVdecswQueryDecoderMemoryInfo(&config, &memory) == 0 && memory.maxFrameBufferSize != 0, "decoder memory query failed"); check(sceVdecswCreateDecoder(&config, &memory, &decoder) == 0, "decoder creation failed"); } @@ -186,9 +58,106 @@ struct Session { ~Session() { sceVdecswDeleteDecoder(decoder); sceVdecswReleaseComputeQueue(queue); + } }; +template +bool throws(TAction action) { + try { + action(); + } catch (const std::exception&) { + return true; + } + return false; +} + +template +void checkGenericPictureInfo(const OutputInfo& output, const TInfo& typed) { + const std::array sizes{8, 9, 0x28, sizeof(TInfo) - 1, sizeof(TInfo), sizeof(TInfo) + 16, std::numeric_limits::max()}; + for (const auto size : sizes) { + std::array storage; + storage.fill(0xcc); + std::memcpy(storage.data() + 1, &size, sizeof(size)); + auto expected = storage; + const auto copied = std::min(size, sizeof(TInfo)) - sizeof(size); + std::memcpy(expected.data() + 1 + sizeof(size), reinterpret_cast(&typed) + sizeof(size), copied); + auto second = storage; + const auto secondBefore = second; + check(sceVdecswGetPictureInfo(&output, storage.data() + 1, second.data()) == 0, "generic picture info query failed"); + check(storage == expected && second == secondBefore, "generic picture info bytes, size or guards differ"); + } + for (std::uint64_t size = 0; size < sizeof(size); ++size) { + auto tooSmall = typed; + tooSmall.thisSize = size; + const auto before = tooSmall; + check(throws([&] { sceVdecswGetPictureInfo(&output, &tooSmall, nullptr); }), "generic picture info accepted an undersized size field"); + check(std::memcmp(&tooSmall, &before, sizeof(tooSmall)) == 0, "an invalid generic query changed its output"); + } + auto second = typed; + const auto secondBefore = second; + check(sceVdecswGetPictureInfo(&output, nullptr, &second) == 0, "generic picture info rejected a null first output"); + check(std::memcmp(&second, &secondBefore, sizeof(second)) == 0, "generic picture info wrote a nonexistent second picture"); + auto shorter = output; + shorter.thisSize = sizeof(OutputInfo) - 8; + auto actual = typed; + check(sceVdecswGetPictureInfo(&shorter, &actual, nullptr) == 0 && std::memcmp(&actual, &typed, sizeof(actual)) == 0, "generic picture info rejected the shorter output descriptor"); +} + +void testGenericPictureInfoValidation() { + OutputInfo output{sizeof(OutputInfo)}; + std::array first; + first.fill(0xccccccccccccccccull); + const auto before = first; + auto second = first; + check(throws([&] { sceVdecswGetPictureInfo(nullptr, first.data(), second.data()); }), "generic picture info accepted a null output descriptor"); + check(first == before && second == before, "a null descriptor changed picture info"); + check(sceVdecswGetPictureInfo(&output, first.data(), second.data()) == 0 && first == before && second == before, "an empty output changed picture info"); + check(sceVdecswGetPictureInfo(&output, nullptr, nullptr) == 0, "an empty output required picture-info pointers"); + for (const auto size : {0ull, 0x28ull, 0x31ull, 0x40ull, 0x100000038ull}) { + output.thisSize = size; + check(throws([&] { sceVdecswGetPictureInfo(&output, first.data(), second.data()); }) && first == before && second == before, "generic picture info accepted an invalid descriptor size"); + } + output.thisSize = sizeof(OutputInfo); + first[0] = sizeof(first); + const auto firstBefore = first; + output.pictureCount = 1; + output.frameBuffer = first.data(); + check(throws([&] { sceVdecswGetPictureInfo(&output, first.data(), nullptr); }) && first == firstBefore, "generic picture info accepted a foreign frame buffer"); + output.frameBuffer = nullptr; + check(throws([&] { sceVdecswGetPictureInfo(&output, first.data(), nullptr); }) && first == firstBefore, "generic picture info accepted a null frame buffer"); +} + +void testGenericPictureInfoLifetime() { + OutputInfo output{sizeof(OutputInfo)}; + std::vector buffer; + { + Session session; + buffer.resize(session.memory.maxFrameBufferSize); + const auto units = accessUnits(false); + for (std::uint64_t iteration = 0; iteration < 2; ++iteration) { + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[0].data(), units[0].size(), 1000 + iteration, iteration, 0xa0 + iteration}; + check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "generic lifetime input failed"); + InputResult consumed{sizeof(InputResult)}; + check(sceVdecswSyncDecodeInput(session.decoder, &consumed) == 0, "generic lifetime input was not consumed"); + const FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size()}; + check(sceVdecswSetDecodeOutput(session.decoder, &frame) == 0, "generic lifetime buffer failed"); + check(sceVdecswFinalizeDecodeSequence(session.decoder) == 0, "generic lifetime finalize failed"); + check(sceVdecswSyncDecodeOutput(session.decoder, &output) == 0 && output.isValid, "generic lifetime picture missing"); + AvcPictureInfo info{sizeof(AvcPictureInfo)}; + check(sceVdecswGetPictureInfo(&output, &info, nullptr) == 0 && info.ptsData == input.ptsData && info.dtsData == iteration && info.attachedData == input.attachedData, "generic picture info retained stale timestamps"); + if (iteration == 0) { + check(sceVdecswResetDecoder(session.decoder) == 0, "generic lifetime reset failed"); + const auto before = info; + check(throws([&] { sceVdecswGetPictureInfo(&output, &info, nullptr); }) && std::memcmp(&info, &before, sizeof(info)) == 0, "generic AVC picture info survived a decoder reset"); + } + } + } + AvcPictureInfo info{sizeof(AvcPictureInfo)}; + const auto before = info; + check(throws([&] { sceVdecswGetPictureInfo(&output, &info, nullptr); }) && std::memcmp(&info, &before, sizeof(info)) == 0, "generic picture info survived decoder destruction"); +} + void testDecode() { Session session; const auto units = accessUnits(false); @@ -221,6 +190,7 @@ void testDecode() { check(!output.isErrorFrame && !output.isDiscardedFrame && output.pictureCount == 1 && output.frameWidth == Width && output.frameHeight == CodedHeight && output.frameBuffer == buffers[pictures % 2].data(), "unexpected picture geometry"); AvcPictureInfo info{sizeof(AvcPictureInfo)}; check(sceVdecswGetAvcPictureInfo(&output, &info, nullptr) == 0 && info.isValid, "picture info missing"); + checkGenericPictureInfo(output, info); const auto unit = DisplayOrderUnits[pictures]; check(info.ptsData == 1000 + unit && info.dtsData == unit && info.attachedData == 0xa0 + unit && info.idrPictureFlag == (pictures == 0 ? 1 : 0) && info.profileIdc == 100, "pictures out of display order"); check(info.picWidthInMbsMinus1 == Width / 16 - 1 && info.picHeightInMapUnitsMinus1 == CodedHeight / 16 - 1 && info.frameMbsOnlyFlag == 1 && info.frameCroppingFlag == 1 && info.frameCropLeftOffset == 0 && info.frameCropRightOffset == 0 && info.frameCropTopOffset == 0 && info.frameCropBottomOffset == (CodedHeight - Height) / 2, "picture geometry in the picture info is wrong"); @@ -230,7 +200,7 @@ void testDecode() { }; std::uint32_t produced = 0; for (std::size_t unit = 0; unit < units.size(); ++unit) { - const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "decode input was rejected"); InputResult consumed{sizeof(InputResult)}; check(sceVdecswTrySyncDecodeInput(session.decoder, &consumed) == 0 && consumed.decodedAu == units[unit].data(), "an access unit was not consumed"); @@ -245,10 +215,83 @@ void testDecode() { check(sceVdecswTrySyncDecodeInput(session.decoder, &result) == InputQueueEmpty, "a finalized decoder did not report an empty input queue"); } +void testHevc() { + Session session(CodecHevc, 1, 30); + const auto units = hevcUnits(HevcStream.data(), HevcStream.size()); + check(units.size() == HevcHashes.size(), "unexpected HEVC access unit count"); + std::vector buffer(session.memory.maxFrameBufferSize); + std::size_t pictures = 0; + bool ended = false; + const auto drain = [&] { + for (;;) { + const FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size()}; + check(sceVdecswSetDecodeOutput(session.decoder, &frame) == 0, "HEVC output buffer was rejected"); + OutputInfo output{sizeof(OutputInfo)}; + const auto status = sceVdecswTrySyncDecodeOutput(session.decoder, &output); + if (status == OutputPending) return; + check(status == 0, "HEVC output sync failed"); + if (!output.isValid) { + ended = true; + return; + } + check(pictures < HevcHashes.size() && output.codecType == CodecHevc && output.pictureCount == 1 && output.frameWidth == Width && output.frameHeight == Height, "unexpected HEVC picture geometry"); + HevcPictureInfo info{sizeof(HevcPictureInfo)}; + check(sceVdecswGetHevcPictureInfo(&output, &info) == 0 && info.isValid, "HEVC picture info missing"); + checkGenericPictureInfo(output, info); + const auto unit = HevcDisplayOrderUnits[pictures]; + check(info.ptsData == 1000 + unit && info.dtsData == unit && info.attachedData == 0xa0 + unit, "HEVC pictures out of display order"); + check(info.idrPictureFlag == (pictures == 0 ? 1 : 0) && info.irapPictureFlag == (pictures == 0 ? 1 : 0), "HEVC random access flags differ"); + check(info.generalProfileIdc == 1 && info.generalLevelIdc == 30 && info.bitDepthLumaMinus8 == 0 && info.bitDepthChromaMinus8 == 0, "HEVC profile, level or bit depth differs"); + check(info.picWidthInLumaSamples == Width && info.picHeightInLumaSamples == Height && info.conformanceWindowFlag == 0 && info.frameCroppingFlag == 0, "HEVC picture geometry in the picture info is wrong"); + AvcPictureInfo avc{sizeof(AvcPictureInfo)}; + bool avcThrows = false; + try { + sceVdecswGetAvcPictureInfo(&output, &avc, nullptr); + + } catch (const std::exception&) { + avcThrows = true; + } + check(avcThrows, "AVC picture info was returned for an HEVC picture"); + check(hashNv12(buffer.data(), output.framePitch, output.frameHeight) == HevcHashes[pictures], "HEVC picture " + std::to_string(pictures) + " differs from the reference"); + ++pictures; + } + }; + for (std::size_t unit = 0; unit < units.size(); ++unit) { + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; + check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "HEVC decode input was rejected"); + InputResult consumed{sizeof(InputResult)}; + check(sceVdecswTrySyncDecodeInput(session.decoder, &consumed) == 0 && consumed.decodedAu == units[unit].data(), "an HEVC access unit was not consumed"); + drain(); + } + check(sceVdecswFinalizeDecodeSequence(session.decoder) == 0, "HEVC finalize failed"); + while (!ended) drain(); + check(pictures == HevcHashes.size(), "missing HEVC pictures after finalizing the sequence"); +} + +void testHevcCrop() { + Session session(CodecHevc, 1, 30); + std::vector buffer(session.memory.maxFrameBufferSize); + const FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size()}; + check(sceVdecswSetDecodeOutput(session.decoder, &frame) == 0, "cropped HEVC output buffer was rejected"); + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), HevcCroppedStream.data(), HevcCroppedStream.size(), 1000, 0, 0xa0}; + check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "cropped HEVC decode input was rejected"); + InputResult consumed{sizeof(InputResult)}; + check(sceVdecswSyncDecodeInput(session.decoder, &consumed) == 0, "the cropped HEVC access unit was not consumed"); + check(sceVdecswFinalizeDecodeSequence(session.decoder) == 0, "cropped HEVC finalize failed"); + OutputInfo output{sizeof(OutputInfo)}; + check(sceVdecswSyncDecodeOutput(session.decoder, &output) == 0 && output.isValid && output.frameWidth == 64 && output.frameHeight == 40, "unexpected cropped HEVC picture geometry"); + HevcPictureInfo info{sizeof(HevcPictureInfo)}; + check(sceVdecswGetHevcPictureInfo(&output, &info) == 0 && info.isValid, "cropped HEVC picture info missing"); + checkGenericPictureInfo(output, info); + check(info.picWidthInLumaSamples == 64 && info.picHeightInLumaSamples == 40, "cropped HEVC coded size differs"); + check(info.conformanceWindowFlag == 1 && info.confWinLeftOffset == 0 && info.confWinRightOffset == 0 && info.confWinTopOffset == 0 && info.confWinBottomOffset == 2, "HEVC conformance window differs"); + check(info.frameCroppingFlag == 1 && info.frameCropLeftOffset == 0 && info.frameCropRightOffset == 0 && info.frameCropTopOffset == 0 && info.frameCropBottomOffset == 2, "HEVC frame cropping differs"); +} + void testWithoutOutput() { Session session; const auto units = accessUnits(false); - const InputData input{sizeof(InputData), units[0].data(), units[0].size(), 1000, 0, 0xa0}; + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[0].data(), units[0].size(), 1000, 0, 0xa0}; check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "decode input was rejected"); InputResult result{sizeof(InputResult)}; check(sceVdecswTrySyncDecodeInput(session.decoder, &result) == 0 && result.decodedAu == units[0].data(), "an input was not decoded before an output buffer was set"); @@ -268,7 +311,7 @@ void testSyncWrappers() { InputResult idle{sizeof(InputResult)}; check(sceVdecswSyncDecodeInput(session.decoder, &idle) == InputQueueEmpty, "sync input on an idle decoder did not report an empty queue"); const auto units = accessUnits(false); - const InputData input{sizeof(InputData), units[0].data(), units[0].size(), 1000, 0, 0xa0}; + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[0].data(), units[0].size(), 1000, 0, 0xa0}; check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "decode input was rejected"); InputResult result{sizeof(InputResult)}; check(sceVdecswSyncDecodeInput(session.decoder, &result) == 0 && result.decodedAu == units[0].data(), "sync input did not consume the access unit"); @@ -278,26 +321,28 @@ void testSyncWrappers() { check(sceVdecswFinalizeDecodeSequence(session.decoder) == 0, "finalize failed"); OutputInfo output{sizeof(OutputInfo)}; check(sceVdecswSyncDecodeOutput(session.decoder, &output) == 0 && output.isValid && output.isLastFrame, "sync output did not return the finalized picture"); + HevcPictureInfo hevc{sizeof(HevcPictureInfo)}; bool hevcThrows = false; try { - sceVdecswGetHevcPictureInfo(&output, &result); + sceVdecswGetHevcPictureInfo(&output, &hevc); + } catch (const std::exception&) { hevcThrows = true; } - check(hevcThrows, "GetHevcPictureInfo must still throw"); + check(hevcThrows, "HEVC picture info was returned for an H.264 picture"); } struct Vp9Session { std::uint64_t queue = 0; std::uint64_t decoder = 0; - DecoderMemoryInfo memory{sizeof(DecoderMemoryInfo)}; + SceVideodec2::DecoderMemoryInfo memory{sizeof(SceVideodec2::DecoderMemoryInfo)}; Vp9Session() { - ComputeMemoryInfo compute{sizeof(ComputeMemoryInfo)}; + SceVideodec2::ComputeMemoryInfo compute{sizeof(SceVideodec2::ComputeMemoryInfo)}; check(sceVdecswQueryComputeMemoryInfo(&compute) == 0 && compute.cpuGpuMemorySize != 0, "compute memory query failed"); - const ComputeConfigInfo computeConfig{sizeof(ComputeConfigInfo), 3, 3, true}; + const SceVideodec2::ComputeConfigInfo computeConfig{sizeof(SceVideodec2::ComputeConfigInfo), 3, 3, true}; check(sceVdecswAllocateComputeQueue(&computeConfig, &compute, &queue) == 0, "compute queue allocation failed"); - DecoderConfigInfo config{sizeof(DecoderConfigInfo), 1, CodecVp9, 0, 0, static_cast(Width), static_cast(Height), 4, 3, queue, 0x3f, 0, true, {}}; + SceVdecsw::DecoderConfigInfo config{{sizeof(SceVdecsw::DecoderConfigInfo), 1, CodecVp9, 0, 0, static_cast(Width), static_cast(Height), 4, 3}, queue, 0x3f, 0, true, {}}; check(sceVdecswQueryDecoderMemoryInfo(&config, &memory) == 0 && memory.maxFrameBufferSize != 0, "VP9 decoder memory query failed"); check(sceVdecswCreateDecoder(&config, &memory, &decoder) == 0, "VP9 decoder creation failed"); } @@ -305,33 +350,24 @@ struct Vp9Session { ~Vp9Session() { sceVdecswDeleteDecoder(decoder); sceVdecswReleaseComputeQueue(queue); + } }; -template -bool throws(TAction action) { - try { - action(); - } catch (const std::exception&) { - return true; - } - return false; -} - -Vp9PictureInfo sentinelVp9Info(std::uint64_t size = sizeof(Vp9PictureInfo)) { - Vp9PictureInfo info; +SceVideodec2::Vp9PictureInfo sentinelVp9Info(std::uint64_t size = sizeof(SceVideodec2::Vp9PictureInfo)) { + SceVideodec2::Vp9PictureInfo info; std::memset(&info, 0xcc, sizeof(info)); info.thisSize = size; return info; } -bool sameBytes(const Vp9PictureInfo& left, const Vp9PictureInfo& right) { - return std::memcmp(&left, &right, sizeof(Vp9PictureInfo)) == 0; +bool sameBytes(const SceVideodec2::Vp9PictureInfo& left, const SceVideodec2::Vp9PictureInfo& right) { + return std::memcmp(&left, &right, sizeof(SceVideodec2::Vp9PictureInfo)) == 0; } -void checkVp9Info(const Vp9PictureInfo& info, std::size_t picture) { +void checkVp9Info(const SceVideodec2::Vp9PictureInfo& info, std::size_t picture) { const std::string name = "VP9 picture " + std::to_string(picture); - check(info.thisSize == sizeof(Vp9PictureInfo) && info.isValid, name + " has no picture info"); + check(info.thisSize == sizeof(SceVideodec2::Vp9PictureInfo) && info.isValid, name + " has no picture info"); check(info.ptsData == 1000 + picture && info.dtsData == picture && info.attachedData == 0xa0 + picture, name + " timestamps differ"); check(info.profile == 0 && info.bitDepth == 8 && info.colorRange == 0 && info.colorSpace == 0 && info.numFrames == 1 && info.intraOnly == 0, name + " coded header fields differ"); check(info.keyFrameFlag == (picture == 0 ? 1u : 0u), name + " key frame flag differs"); @@ -369,6 +405,7 @@ void testVp9PictureInfo() { check(output.codecType == CodecVp9 && output.pictureCount == 1 && output.frameWidth == Width && output.frameHeight == Height && output.frameBuffer == target.data(), "unexpected VP9 picture geometry"); auto info = sentinelVp9Info(); check(sceVdecswGetVp9PictureInfo(&output, &info) == 0, "VP9 picture info query failed"); + checkGenericPictureInfo(output, info); checkVp9Info(info, pictures); check(hashNv12(target.data(), output.framePitch, output.frameHeight) == Vp9Hashes[pictures], "VP9 picture " + std::to_string(pictures) + " differs from the reference"); outputs[pictures] = output; @@ -376,7 +413,7 @@ void testVp9PictureInfo() { } }; for (std::size_t unit = 0; unit < units.size(); ++unit) { - const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "VP9 decode input was rejected"); InputResult consumed{sizeof(InputResult)}; check(sceVdecswTrySyncDecodeInput(session.decoder, &consumed) == 0 && consumed.decodedAu == units[unit].data(), "a VP9 access unit was not consumed"); @@ -390,6 +427,7 @@ void testVp9PictureInfo() { auto info = sentinelVp9Info(); check(sceVdecswGetVp9PictureInfo(&outputs[picture], &info) == 0, "VP9 picture info of an earlier picture was lost"); checkVp9Info(info, picture); + checkGenericPictureInfo(outputs[picture], info); } const auto unchanged = sentinelVp9Info(); @@ -412,21 +450,21 @@ void testVp9PictureInfo() { unknown.frameBuffer = nullptr; check(throws([&] { sceVdecswGetVp9PictureInfo(&unknown, &info); }) && sameBytes(info, unchanged), "a null frame buffer was accepted"); - const auto head = offsetof(Vp9PictureInfo, profile); + const auto head = offsetof(SceVideodec2::Vp9PictureInfo, profile); auto partial = sentinelVp9Info(head); check(sceVdecswGetVp9PictureInfo(&outputs[2], &partial) == 0, "a VP9 picture info with a short size field was rejected"); check(partial.thisSize == head && partial.isValid && partial.ptsData == 1002 && partial.dtsData == 2 && partial.attachedData == 0xa2, "the leading VP9 picture info fields were not written"); check(partial.profile == unchanged.profile && partial.frameWidth == unchanged.frameWidth && partial.renderHeight == unchanged.renderHeight, "a VP9 picture info with a short size field was written beyond it"); struct Oversized { - Vp9PictureInfo info; + SceVideodec2::Vp9PictureInfo info; std::uint8_t tail[64]; } oversized; std::memset(&oversized, 0xcc, sizeof(oversized)); oversized.info.thisSize = sizeof(Oversized); check(sceVdecswGetVp9PictureInfo(&outputs[3], &oversized.info) == 0, "a VP9 picture info with a long size field was rejected"); check(oversized.info.thisSize == sizeof(Oversized), "a long VP9 picture info size field was changed"); - oversized.info.thisSize = sizeof(Vp9PictureInfo); + oversized.info.thisSize = sizeof(SceVideodec2::Vp9PictureInfo); checkVp9Info(oversized.info, 3); for (const auto byte : oversized.tail) check(byte == 0xcc, "a VP9 picture info with a long size field was written beyond the structure"); @@ -435,12 +473,13 @@ void testVp9PictureInfo() { check(sceVdecswResetDecoder(session.decoder) == 0, "VP9 reset failed"); check(throws([&] { sceVdecswGetVp9PictureInfo(&outputs[0], &info); }) && sameBytes(info, unchanged), "VP9 picture info survived a decoder reset"); + check(throws([&] { sceVdecswGetPictureInfo(&outputs[0], &info, nullptr); }) && sameBytes(info, unchanged), "generic picture info survived a decoder reset"); } void testVp9PictureInfoOfAvcPicture() { Session session; const auto units = accessUnits(false); - const InputData input{sizeof(InputData), units[0].data(), units[0].size(), 1000, 0, 0xa0}; + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[0].data(), units[0].size(), 1000, 0, 0xa0}; check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "decode input was rejected"); InputResult result{sizeof(InputResult)}; check(sceVdecswSyncDecodeInput(session.decoder, &result) == 0, "the access unit was not consumed"); @@ -457,9 +496,32 @@ void testVp9PictureInfoOfAvcPicture() { } +void testHevcMain10() { + Session session(CodecHevc, 2, 30); + std::vector buffer(session.memory.maxFrameBufferSize); + const FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size()}; + check(sceVdecswSetDecodeOutput(session.decoder, &frame) == 0, "Main 10 HEVC output buffer was rejected"); + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), HevcMain10Stream.data(), HevcMain10Stream.size(), 1000, 0, 0xa0}; + check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "Main 10 HEVC decode input was rejected"); + InputResult consumed{sizeof(InputResult)}; + check(sceVdecswSyncDecodeInput(session.decoder, &consumed) == 0, "the Main 10 HEVC access unit was not consumed"); + check(sceVdecswFinalizeDecodeSequence(session.decoder) == 0, "Main 10 HEVC finalize failed"); + OutputInfo output{sizeof(OutputInfo)}; + check(sceVdecswSyncDecodeOutput(session.decoder, &output) == 0 && output.isValid, "the Main 10 HEVC picture was not output"); + HevcPictureInfo info{sizeof(HevcPictureInfo)}; + check(sceVdecswGetHevcPictureInfo(&output, &info) == 0 && info.isValid, "Main 10 HEVC picture info missing"); + checkGenericPictureInfo(output, info); + check(info.generalProfileIdc == 2 && info.bitDepthLumaMinus8 == 2 && info.bitDepthChromaMinus8 == 2, "Main 10 HEVC profile or bit depth differs"); +} + int main() { try { + testGenericPictureInfoValidation(); + testGenericPictureInfoLifetime(); testDecode(); + testHevc(); + testHevcCrop(); + testHevcMain10(); testWithoutOutput(); testSyncWrappers(); testVp9PictureInfo(); diff --git a/core/libs/tests/GuestVectorAlgorithms.cpp b/core/libs/tests/GuestVectorAlgorithms.cpp index c3b858bac6..99eaebf937 100644 --- a/core/libs/tests/GuestVectorAlgorithms.cpp +++ b/core/libs/tests/GuestVectorAlgorithms.cpp @@ -1,30 +1,47 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -struct MinMaxPointers { - const void* minimum; - const void* maximum; -}; - -extern "C" { -const void* APS5_VABI __std_find_trivial_2_nid_postfix(const void*, const void*, std::uint16_t); -const void* APS5_VABI __std_find_trivial_unsized_1_nid_postfix(const void*, std::uint8_t); -const void* APS5_VABI __std_find_trivial_unsized_4_nid_postfix(const void*, std::uint32_t); -std::size_t APS5_VABI __std_count_trivial_8_nid_postfix(const void*, const void*, std::uint64_t); -const void* APS5_VABI __std_min_element_1_nid_postfix(const void*, const void*, bool); -const void* APS5_VABI __std_max_element_4_nid_postfix(const void*, const void*, bool); -MinMaxPointers APS5_VABI __std_minmax_element_2_nid_postfix(const void*, const void*, bool); -MinMaxPointers APS5_VABI __std_minmax_element_8_nid_postfix(const void*, const void*, bool); -void APS5_VABI __std_reverse_trivially_swappable_2_nid_postfix(void*, void*); -void APS5_VABI __std_reverse_copy_trivially_copyable_8_nid_postfix(const void*, const void*, void*); -void APS5_VABI __std_swap_ranges_trivially_swappable_noalias_nid_postfix(void*, void*, void*); -} static void Require(bool value) { if (!value) std::abort(); } +static void CheckSizedHelpers() { + const std::uint8_t bytes[] = {3, 0xff, 3, 9, 0xff}; + Require(__std_find_trivial_1_nid_postfix(bytes, bytes + 5, 0xff) == bytes + 1); + Require(__std_find_trivial_1_nid_postfix(bytes, bytes + 5, 4) == bytes + 5); + Require(__std_find_trivial_1_nid_postfix(bytes, bytes, 3) == bytes); + Require(__std_count_trivial_1_nid_postfix(bytes, bytes + 5, 3) == 2 && __std_count_trivial_1_nid_postfix(bytes, bytes + 5, 7) == 0); + Require(__std_max_element_1_nid_postfix(bytes, bytes + 5, false) == bytes + 1); + Require(__std_max_element_1_nid_postfix(bytes, bytes + 5, true) == bytes + 3); + + const std::uint16_t halves[] = {0x8000, 5, 0x7fff, 5, 0x8000}; + Require(__std_count_trivial_2_nid_postfix(halves, halves + 5, 5) == 2 && __std_count_trivial_2_nid_postfix(halves, halves, 5) == 0); + Require(__std_min_element_2_nid_postfix(halves, halves + 5, false) == halves + 1); + Require(__std_min_element_2_nid_postfix(halves, halves + 5, true) == halves); + Require(__std_max_element_2_nid_postfix(halves, halves + 5, false) == halves); + Require(__std_max_element_2_nid_postfix(halves, halves + 5, true) == halves + 2); + + const std::uint32_t words[] = {7, 0xfffffff0u, 1, 1, 0xfffffff0u}; + Require(__std_find_trivial_4_nid_postfix(words, words + 5, 1) == words + 2); + Require(__std_find_trivial_4_nid_postfix(words, words + 5, 2) == words + 5); + Require(__std_count_trivial_4_nid_postfix(words, words + 5, 0xfffffff0u) == 2); + Require(__std_min_element_4_nid_postfix(words, words + 5, false) == words + 2); + Require(__std_min_element_4_nid_postfix(words, words + 5, true) == words + 1); + Require(__std_min_element_4_nid_postfix(words, words, true) == words); + + const std::uint64_t wides[] = {0x8000000000000000ull, 2, 0x7fffffffffffffffull, 0x8000000000000000ull}; + Require(__std_find_trivial_8_nid_postfix(wides, wides + 4, 0x8000000000000000ull) == wides); + Require(__std_find_trivial_8_nid_postfix(wides + 1, wides + 4, 0x8000000000000000ull) == wides + 3); + Require(__std_min_element_8_nid_postfix(wides, wides + 4, true) == wides); + Require(__std_min_element_8_nid_postfix(wides, wides + 4, false) == wides + 1); + Require(__std_max_element_8_nid_postfix(wides, wides + 4, false) == wides); + Require(__std_max_element_8_nid_postfix(wides, wides + 4, true) == wides + 2); + Require(__std_max_element_8_nid_postfix(wides, wides, false) == wides); +} + 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); @@ -48,6 +65,36 @@ static void CheckUnsizedRescan() { bytes[3] = 0; bytes[20] = 0; Require(__std_find_trivial_unsized_1_nid_postfix(bytes + 8, 'x') == bytes + 70); + + alignas(32) std::uint16_t halves[32] = {}; + halves[0] = 17; + halves[4] = 17; + halves[20] = 17; + Require(__std_find_trivial_unsized_2_nid_postfix(halves + 1, 17) == halves + 4); + halves[4] = 0; + Require(__std_find_trivial_unsized_2_nid_postfix(halves + 1, 17) == halves); + halves[0] = 0; + Require(__std_find_trivial_unsized_2_nid_postfix(halves + 1, 17) == halves + 20); + + alignas(32) std::uint32_t quads[16] = {}; + quads[0] = 42; + quads[5] = 42; + quads[12] = 42; + Require(__std_find_trivial_unsized_4_nid_postfix(quads + 1, 42) == quads + 5); + quads[5] = 0; + Require(__std_find_trivial_unsized_4_nid_postfix(quads + 1, 42) == quads); + quads[0] = 0; + Require(__std_find_trivial_unsized_4_nid_postfix(quads + 1, 42) == quads + 12); + + alignas(32) std::uint64_t wides[8] = {}; + wides[0] = 99; + wides[2] = 99; + wides[6] = 99; + Require(__std_find_trivial_unsized_8_nid_postfix(wides + 1, 99) == wides + 2); + wides[2] = 0; + Require(__std_find_trivial_unsized_8_nid_postfix(wides + 1, 99) == wides); + wides[0] = 0; + Require(__std_find_trivial_unsized_8_nid_postfix(wides + 1, 99) == wides + 6); } static void CheckMinMax() { @@ -73,10 +120,12 @@ static void CheckMinMax() { static void CheckReverseAndSwap() { std::uint16_t halves[] = {1, 2, 3, 4, 5}; __std_reverse_trivially_swappable_2_nid_postfix(halves, halves + 5); + Require(halves[0] == 5 && halves[1] == 4 && halves[2] == 3 && halves[3] == 2 && halves[4] == 1); const std::uint64_t wides[] = {1, 2, 3}; std::uint64_t reversed[4] = {9, 9, 9, 9}; __std_reverse_copy_trivially_copyable_8_nid_postfix(wides, wides + 3, reversed); + Require(reversed[0] == 3 && reversed[1] == 2 && reversed[2] == 1 && reversed[3] == 9); unsigned char left[37], right[37]; for (int i = 0; i < 37; ++i) { @@ -84,10 +133,12 @@ static void CheckReverseAndSwap() { right[i] = static_cast(100 + i); } __std_swap_ranges_trivially_swappable_noalias_nid_postfix(left, left + 37, right); + for (int i = 0; i < 37; ++i) Require(left[i] == 100 + i && right[i] == i); } int main() { + CheckSizedHelpers(); CheckFindAndCount(); CheckUnsizedRescan(); CheckMinMax(); diff --git a/core/libs/tests/GuestVideoOutColorSpace.cpp b/core/libs/tests/GuestVideoOutColorSpace.cpp index 1c081f65f9..d794feff72 100644 --- a/core/libs/tests/GuestVideoOutColorSpace.cpp +++ b/core/libs/tests/GuestVideoOutColorSpace.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/Output.hpp" #include "SceTypes.hpp" #include "prx/libc/include/Shutdown.hpp" #include "tests/VideoOutTestEnvironment.hpp" @@ -8,18 +9,6 @@ #include #include -extern "C" { -int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param); -int APS5_VABI sceVideoOutClose(int handle); -int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion(VideoOutColorSpaceConversionSettings* settings); -int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option); -int APS5_VABI sceVideoOutAdjustColorSpaceConversion(int handle, const VideoOutColorSpaceConversionSettings* settings); -int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size); -int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option, uint32_t settings_size); -int APS5_VABI sceVideoOutAdjustColorSpaceConversion_(int handle, const VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size); -int APS5_VABI sceVideoOutSetGlobalBlendSpace(int handle, uint32_t space, uint32_t option); -} - static constexpr int SYSTEM_USER = 255; static constexpr int MAIN_BUS = 0; diff --git a/core/libs/tests/GuestVideoOutCursor.cpp b/core/libs/tests/GuestVideoOutCursor.cpp index 214293bdd2..283d91bccf 100644 --- a/core/libs/tests/GuestVideoOutCursor.cpp +++ b/core/libs/tests/GuestVideoOutCursor.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/VideoOut.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -6,13 +7,6 @@ #include #include -extern "C" { -int APS5_VABI sceVideoOutCursorEnable(std::int32_t handle, std::int32_t index, const void* address); -int APS5_VABI sceVideoOutCursorDisable(std::int32_t handle, std::int32_t index); -int APS5_VABI sceVideoOutCursorSetImageAddress(std::int32_t handle, std::int32_t index, const void* address); -int APS5_VABI sceVideoOutCursorSetPosition(std::int32_t handle, std::int32_t index, std::uint32_t x, std::uint32_t y); -} - static bool ReportsNotImplemented(const std::string& name, const std::function& call) { try { call(); diff --git a/core/libs/tests/GuestVideoOutHdr.cpp b/core/libs/tests/GuestVideoOutHdr.cpp index a6e78ed34d..08fc189878 100644 --- a/core/libs/tests/GuestVideoOutHdr.cpp +++ b/core/libs/tests/GuestVideoOutHdr.cpp @@ -1,3 +1,7 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceVideoOut/include/Output.hpp" +#include "prx/libSceVideoOut/include/Event.hpp" +#include "prx/libSceSystemService/include/SystemService.hpp" #include "SceTypes.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" @@ -14,19 +18,6 @@ #include #include -extern "C" { -int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param); -int APS5_VABI sceVideoOutClose(int handle); -int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status); -int APS5_VABI sceVideoOutGetOutputStatusWithVrr(int handle, void* status); -int APS5_VABI sceVideoOutAddOutputModeEvent(KernelEqueue eq, int handle, void* udata); -int APS5_VABI sceVideoOutGetEventId(const KernelEvent* ev); -int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); -int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); -int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo); -int APS5_VABI sceSystemServiceGetHdrToneMapLuminance(SystemServiceHdrToneMapLuminance* luminance); -} - namespace { constexpr int SystemUser = 255; diff --git a/core/libs/tests/GuestVideoOutHighFrameRate.cpp b/core/libs/tests/GuestVideoOutHighFrameRate.cpp index 3e4f4c4437..541436b236 100644 --- a/core/libs/tests/GuestVideoOutHighFrameRate.cpp +++ b/core/libs/tests/GuestVideoOutHighFrameRate.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/Output.hpp" #include "SceTypes.hpp" #include "prx/libc/include/Shutdown.hpp" #include "tests/VideoOutTestEnvironment.hpp" @@ -7,16 +8,6 @@ #include #include -extern "C" { -int APS5_VABI sceVideoOutOpen(int, int, int, const void*); -int APS5_VABI sceVideoOutClose(int); -int APS5_VABI sceVideoOutIsOutputSupported(int, std::uint64_t, const VideoOutOutputOptions*, void*, std::uint64_t); -int APS5_VABI sceVideoOutConfigureOutput(int, std::uint64_t, const VideoOutOutputOptions*, void*, std::uint64_t); -int APS5_VABI sceVideoOutWaitVblank(int); -int APS5_VABI sceVideoOutGetOutputStatus(int, VideoOutOutputStatus*); -int APS5_VABI sceVideoOutGetResolutionStatus(int, VideoOutResolutionStatus*); -} - static constexpr int Unsupported = static_cast(0x80290016u); static void Require(bool value, const char* message) { diff --git a/core/libs/tests/GuestVideoOutOutputMode.cpp b/core/libs/tests/GuestVideoOutOutputMode.cpp index 82ce4bc2fa..7c86474ea9 100644 --- a/core/libs/tests/GuestVideoOutOutputMode.cpp +++ b/core/libs/tests/GuestVideoOutOutputMode.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/Output.hpp" #include "SceTypes.hpp" #include "prx/libc/include/Shutdown.hpp" #include "tests/VideoOutTestEnvironment.hpp" @@ -8,14 +9,6 @@ #include #include -extern "C" { -int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param); -int APS5_VABI sceVideoOutClose(int handle); -int APS5_VABI sceVideoOutInitializeOutputOptions(VideoOutOutputOptions* options); -int APS5_VABI sceVideoOutIsOutputSupported(int handle, std::uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, std::uint64_t reserved); -int APS5_VABI sceVideoOutConfigureOutput(int handle, std::uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, std::uint64_t reserved); -} - static constexpr int SYSTEM_USER = 255; static constexpr int MAIN_BUS = 0; static constexpr int HMD2_MAIN_BUS = 0x20; diff --git a/core/libs/tests/GuestVideoOutResolutionStatus.cpp b/core/libs/tests/GuestVideoOutResolutionStatus.cpp index e401ad3023..97f0a119a0 100644 --- a/core/libs/tests/GuestVideoOutResolutionStatus.cpp +++ b/core/libs/tests/GuestVideoOutResolutionStatus.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/Output.hpp" #include "SceTypes.hpp" #include "prx/libc/include/Shutdown.hpp" #include "tests/VideoOutTestEnvironment.hpp" @@ -8,14 +9,6 @@ #include #include -extern "C" { -int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param); -int APS5_VABI sceVideoOutClose(int handle); -int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status); -int APS5_VABI sceVideoOutGetOutputStatusWithVrr(int handle, void* status); -int APS5_VABI sceVideoOutGetResolutionStatus(int handle, VideoOutResolutionStatus* status); -} - static constexpr int SYSTEM_USER = 255; static constexpr int MAIN_BUS = 0; static constexpr int NEVER_OPENED_HANDLE = 2; @@ -68,6 +61,7 @@ int main() { for (const auto byte : vrr) Require(byte == std::byte{0xff}); Require(Rejects(handle, nullptr)); + Require(Rejects(-1, nullptr)); Require(Rejects(0, &status)); Require(Rejects(-1, &status)); Require(Rejects(NEVER_OPENED_HANDLE, &status)); diff --git a/core/libs/tests/GuestVideoOutWqhdDetection.cpp b/core/libs/tests/GuestVideoOutWqhdDetection.cpp index c0e68b0c49..0e543eaade 100644 --- a/core/libs/tests/GuestVideoOutWqhdDetection.cpp +++ b/core/libs/tests/GuestVideoOutWqhdDetection.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/Output.hpp" #include "SceTypes.hpp" #include "prx/libc/include/Shutdown.hpp" #include "tests/VideoOutTestEnvironment.hpp" @@ -6,13 +7,6 @@ #include #include -extern "C" { -int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param); -int APS5_VABI sceVideoOutClose(int handle); -int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status); -int APS5_VABI sceVideoOutAllowOutputResolutionWqhdDetection(int handle); -} - static constexpr int SYSTEM_USER = 255; static constexpr int MAIN_BUS = 0; static constexpr int NEVER_OPENED_HANDLE = 2; diff --git a/core/libs/tests/GuestVideoRecording.cpp b/core/libs/tests/GuestVideoRecording.cpp index 3338094cac..ff7b12233e 100644 --- a/core/libs/tests/GuestVideoRecording.cpp +++ b/core/libs/tests/GuestVideoRecording.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoRecordingP/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -6,16 +7,6 @@ #include #include -extern "C" { -void APS5_VABI _sceVideoRecordingQueryParam_nid_postfix(void* param); -int APS5_VABI sceVideoRecordingGetStatus(void); -int APS5_VABI sceVideoRecordingQueryMemSize(const void* param); -int APS5_VABI sceVideoRecordingOpen(const char* path, const void* param, void* heap, int heapSize); -int APS5_VABI sceVideoRecordingStart(void); -int APS5_VABI sceVideoRecordingStop(void); -int APS5_VABI sceVideoRecordingClose(int discard); -} - namespace { constexpr int StatusNone = 0; @@ -49,10 +40,10 @@ int main() { std::array heap{}; heap.fill(Sentinel); - Require(Throws([&] { _sceVideoRecordingQueryParam_nid_postfix(param.data()); }), "the default parameters were produced"); - Require(Throws([&] { sceVideoRecordingQueryMemSize(param.data()); }), "a heap size was reported"); + Require(Throws([&] { _sceVideoRecordingQueryParam_nid_postfix(reinterpret_cast(param.data())); }), "the default parameters were produced"); + Require(Throws([&] { sceVideoRecordingQueryMemSize(reinterpret_cast(param.data())); }), "a heap size was reported"); Require(Throws([&] { sceVideoRecordingQueryMemSize(nullptr); }), "a heap size was reported for no parameters"); - Require(Throws([&] { sceVideoRecordingOpen("clip.mp4", param.data(), heap.data(), static_cast(heap.size())); }), "the recorder opened"); + Require(Throws([&] { sceVideoRecordingOpen("clip.mp4", reinterpret_cast(param.data()), heap.data(), static_cast(heap.size())); }), "the recorder opened"); Require(Throws([&] { sceVideoRecordingStart(); }), "the encoder started"); Require(Throws([&] { sceVideoRecordingStop(); }), "the encoder stopped"); Require(Throws([&] { sceVideoRecordingClose(0); }), "the recorder closed"); @@ -62,6 +53,9 @@ int main() { for (const std::uint8_t value : heap) Require(value == Sentinel, "the heap was written"); Require(sceVideoRecordingGetStatus() == StatusNone, "a failed open changed the status"); + Require(sceVideoRecordingGetInfo(0xa001, nullptr, 0) == static_cast(0x80a80006), "info 0xa001 validation"); + Require(sceVideoRecordingGetInfo(0xa005, nullptr, 0) == static_cast(0x80a80006), "info 0xa005 validation"); + Require(sceVideoRecordingGetInfo(0, nullptr, 0) == static_cast(0x80a80003), "unknown info validation"); std::puts("VideoRecording tests passed"); return 0; } diff --git a/core/libs/tests/GuestVideodec.cpp b/core/libs/tests/GuestVideodec.cpp index f4ad8e987e..236336d978 100644 --- a/core/libs/tests/GuestVideodec.cpp +++ b/core/libs/tests/GuestVideodec.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceVideodec2/include/Api.hpp" +#include "prx/libSceVideodec/include/Api.hpp" #include #include #include @@ -6,96 +8,16 @@ #include "prx/libc/include/General.hpp" #include "H264Fixture.hpp" -struct ConfigInfo { - std::uint64_t thisSize; - std::uint32_t codecType; - std::uint32_t profile; - std::uint32_t maxLevel; - std::int32_t maxFrameWidth; - std::int32_t maxFrameHeight; - std::int32_t maxDpbFrameCount; - std::uint64_t videodecFlags; -}; - -struct ResourceInfo { - std::uint64_t thisSize; - std::uint64_t cpuMemorySize; - void* cpuMemory; - std::uint64_t cpuGpuMemorySize; - void* cpuGpuMemory; - std::uint64_t maxFrameBufferSize; - std::uint32_t frameBufferAlignment; -}; - -struct Ctrl { - std::uint64_t thisSize; - void* handle; - std::uint64_t version; -}; - -struct FrameBuffer { - std::uint64_t thisSize; - void* frameBuffer; - std::uint64_t frameBufferSize; -}; - -struct AvcInfo { - std::uint32_t numUnitsInTick; - std::uint32_t timeScale; - std::uint8_t fixedFrameRateFlag; - std::uint8_t aspectRatioIdc; - std::uint16_t sarWidth; - std::uint16_t sarHeight; - std::uint8_t colourPrimaries; - std::uint8_t transferCharacteristics; - std::uint8_t matrixCoefficients; - std::uint8_t videoFullRangeFlag; - std::uint32_t frameCropLeftOffset; - std::uint32_t frameCropRightOffset; - std::uint32_t frameCropTopOffset; - std::uint32_t frameCropBottomOffset; -}; - -union CodecInfo { - std::uint8_t reserved[64]; - AvcInfo avc; -}; - -struct PictureInfo { - std::uint64_t thisSize; - std::uint32_t isValid; - std::uint32_t codecType; - std::uint32_t frameWidth; - std::uint32_t framePitch; - std::uint32_t frameHeight; - std::uint32_t isErrorPic; - std::uint64_t ptsData; - std::uint64_t attachedData; - CodecInfo codec; -}; - -struct InputData { - std::uint64_t thisSize; - const std::uint8_t* auData; - std::uint64_t auSize; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; -}; +using namespace SceVideodec; static_assert(sizeof(PictureInfo) == 112 && sizeof(AvcInfo) == 36); static_assert(sizeof(ConfigInfo) == 40 && sizeof(ResourceInfo) == 56 && sizeof(Ctrl) == 24); - -extern "C" { -int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo*, const ResourceInfo*, Ctrl*); -int APS5_VABI sceVideodecDecode(Ctrl*, const InputData*, FrameBuffer*, PictureInfo*); -int APS5_VABI sceVideodecDeleteDecoder(Ctrl*); -int APS5_VABI sceVideodecFlush(Ctrl*, FrameBuffer*, PictureInfo*); -int APS5_VABI sceVideodecReset(Ctrl*); -} +static_assert(sizeof(SceVideodec2::DecoderConfigInfo) == 40 && sizeof(SceVideodec2::DecoderMemoryInfo) == 72); namespace { +using namespace SceVideodec; + using namespace H264Fixture; constexpr int ErrorApiFail = static_cast(0x80C10000); @@ -105,6 +27,7 @@ constexpr int ErrorAuSize = static_cast(0x80C10009); constexpr int ErrorAuPointer = static_cast(0x80C1000A); constexpr int ErrorFrameBufferSize = static_cast(0x80C1000B); constexpr int ErrorFrameBufferPointer = static_cast(0x80C1000C); +constexpr int ErrorConfigInfo = static_cast(0x80C1000E); constexpr int ErrorArgumentPointer = static_cast(0x80C1000F); constexpr std::uint32_t Pitch = 64; @@ -148,7 +71,7 @@ void testDecode(const std::vector>& units, std::uint32 timestamps.push_back(picture.ptsData); }; for (std::size_t unit = 0; unit < units.size(); ++unit) { - const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; auto target = session.MakeTarget(); PictureInfo picture{sizeof(PictureInfo)}; const auto status = sceVideodecDecode(&session.ctrl, &input, &target, &picture); @@ -193,22 +116,22 @@ void testErrors() { foreign.handle = reinterpret_cast(static_cast(0x1234)); check(sceVideodecDeleteDecoder(&foreign) == ErrorHandle, "an unknown decoder was deleted"); check(sceVideodecReset(&foreign) == ErrorHandle, "an unknown decoder was reset"); - const InputData empty{sizeof(InputData), nullptr, 0, 0, 0, 0}; + const SceVideodec2::InputData empty{sizeof(SceVideodec2::InputData), nullptr, 0, 0, 0, 0}; FrameBuffer frame{sizeof(FrameBuffer), nullptr, 0}; PictureInfo picture{sizeof(PictureInfo)}; check(sceVideodecDecode(nullptr, &empty, &frame, &picture) == ErrorArgumentPointer, "a null control block was accepted"); check(sceVideodecDecode(&ctrl, &empty, &frame, &picture) == ErrorAuPointer, "a null access unit was accepted"); - const InputData sized{sizeof(InputData), reinterpret_cast(&config), 0, 0, 0, 0}; + const SceVideodec2::InputData sized{sizeof(SceVideodec2::InputData), reinterpret_cast(&config), 0, 0, 0, 0}; check(sceVideodecDecode(&ctrl, &sized, &frame, &picture) == ErrorAuSize, "an empty access unit was accepted"); FrameBuffer missing{sizeof(FrameBuffer), nullptr, FrameBytes}; const auto units = accessUnits(false); - const InputData input{sizeof(InputData), units[0].data(), units[0].size(), 0, 0, 0}; + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[0].data(), units[0].size(), 0, 0, 0}; check(sceVideodecDecode(&ctrl, &input, &missing, &picture) == ErrorFrameBufferPointer, "a null frame buffer was accepted"); std::vector storage(static_cast(FrameBytes)); FrameBuffer shortBuffer{sizeof(FrameBuffer), storage.data(), FrameBytes - 1}; int refusals = 0; for (std::size_t unit = 0; unit < units.size(); ++unit) { - const InputData unit0{sizeof(InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; + const SceVideodec2::InputData unit0{sizeof(SceVideodec2::InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; const auto status = sceVideodecDecode(&ctrl, &unit0, &shortBuffer, &picture); check(status == 0 || status == ErrorFrameBufferSize, "a decode failed"); if (status == ErrorFrameBufferSize) ++refusals; @@ -221,7 +144,7 @@ void testErrors() { void testReset() { Session session; const auto units = accessUnits(false); - const InputData input{sizeof(InputData), units[0].data(), units[0].size(), 1000, 0, 0xa0}; + const SceVideodec2::InputData input{sizeof(SceVideodec2::InputData), units[0].data(), units[0].size(), 1000, 0, 0xa0}; auto target = session.MakeTarget(); PictureInfo picture{sizeof(PictureInfo)}; check(sceVideodecDecode(&session.ctrl, &input, &target, &picture) == 0, "decode failed"); @@ -230,6 +153,50 @@ void testReset() { check(!picture.isValid, "a reset decoder produced a picture"); } +void testQueryResourceInfo(std::int32_t width, std::int32_t height, std::uint32_t profile, std::uint32_t level) { + const ConfigInfo config{sizeof(ConfigInfo), 1, profile, level, width, height, 4, 0}; + ResourceInfo resource{sizeof(ResourceInfo)}; + check(sceVideodecQueryResourceInfo(&config, &resource) == 0, "the resource query failed"); + const SceVideodec2::DecoderConfigInfo mapped{sizeof(SceVideodec2::DecoderConfigInfo), 1, 1, profile, level, width, height, 4, 1}; + SceVideodec2::DecoderMemoryInfo memory{sizeof(SceVideodec2::DecoderMemoryInfo)}; + check(sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&mapped, &memory) == 0, "the Videodec2 memory query failed"); + check(resource.thisSize == sizeof(ResourceInfo), "the resource information size was changed"); + check(resource.cpuMemorySize == memory.cpuMemorySize, "the CPU memory size differs from libSceVideodec2"); + check(resource.cpuGpuMemorySize == memory.cpuGpuMemorySize, "the CPU-GPU memory size differs from libSceVideodec2"); + check(resource.maxFrameBufferSize == memory.maxFrameBufferSize, "the frame buffer size differs from libSceVideodec2"); + check(resource.frameBufferAlignment == memory.frameBufferAlignment, "the frame buffer alignment differs from libSceVideodec2"); +} + +void testQueryResourceInfo() { + testQueryResourceInfo(static_cast(Width), static_cast(CodedHeight), 100, 42); + testQueryResourceInfo(1920, 1080, 100, 42); + testQueryResourceInfo(3840, 2160, 100, 51); + const ConfigInfo config{sizeof(ConfigInfo), 1, 100, 42, static_cast(Width), + static_cast(CodedHeight), 4, 0}; + ResourceInfo resource{sizeof(ResourceInfo)}; + check(sceVideodecQueryResourceInfo(&config, &resource) == 0, "the resource query failed"); + check(resource.maxFrameBufferSize >= FrameBytes, "the frame buffer size does not hold a decoded picture"); + Ctrl ctrl{sizeof(Ctrl)}; + check(sceVideodecCreateDecoder(&config, &resource, &ctrl) == 0, "the queried resources were refused"); + check(sceVideodecDeleteDecoder(&ctrl) == 0, "the decoder was not deleted"); + check(sceVideodecQueryResourceInfo(nullptr, &resource) == ErrorArgumentPointer, "a null configuration was accepted"); + check(sceVideodecQueryResourceInfo(&config, nullptr) == ErrorArgumentPointer, "null resource information was accepted"); + const ResourceInfo sentinel{sizeof(ResourceInfo), 7, nullptr, 7, nullptr, 7, 7}; + auto refused = sentinel; + auto shortConfig = config; + shortConfig.thisSize = sizeof(ConfigInfo) - 8; + check(sceVideodecQueryResourceInfo(&shortConfig, &refused) == ErrorStructSize, "a short configuration was accepted"); + auto shortResource = sentinel; + shortResource.thisSize = sizeof(ResourceInfo) - 8; + check(sceVideodecQueryResourceInfo(&config, &shortResource) == ErrorStructSize, "short resource information was accepted"); + auto unsized = config; + unsized.maxFrameWidth = 0; + check(sceVideodecQueryResourceInfo(&unsized, &refused) == ErrorConfigInfo, "a configuration without a frame size was accepted"); + check(refused.cpuMemorySize == 7 && refused.cpuGpuMemorySize == 7 && refused.maxFrameBufferSize == 7 && refused.frameBufferAlignment == 7, + "a refused query wrote the resource information"); + check(shortResource.cpuMemorySize == 7 && shortResource.maxFrameBufferSize == 7, "a refused query wrote the resource information"); +} + } int main() { @@ -238,6 +205,7 @@ int main() { testDecodeCropped(); testErrors(); testReset(); + testQueryResourceInfo(); std::puts("Videodec tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/tests/GuestVideodec2.cpp b/core/libs/tests/GuestVideodec2.cpp index 2cbaf79f8b..f6caa14b4b 100644 --- a/core/libs/tests/GuestVideodec2.cpp +++ b/core/libs/tests/GuestVideodec2.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideodec2/include/Api.hpp" #include #include #include @@ -11,209 +12,72 @@ #include #include "prx/libc/include/General.hpp" #include "H264Fixture.hpp" +#include "HevcFixture.hpp" #include "Vp9Fixture.hpp" -struct DecoderConfigInfo { - std::uint64_t thisSize; - std::uint32_t resourceType; - std::uint32_t codecType; - std::uint32_t profile; - std::uint32_t maxLevel; - std::int32_t maxFrameWidth; - std::int32_t maxFrameHeight; - std::int32_t maxDpbFrameCount; - std::uint32_t decodePipelineDepth; -}; - -struct DecoderMemoryInfo { - std::uint64_t thisSize; - std::uint64_t cpuMemorySize; - void* cpuMemory; - std::uint64_t gpuMemorySize; - void* gpuMemory; - std::uint64_t cpuGpuMemorySize; - void* cpuGpuMemory; - std::uint64_t maxFrameBufferSize; - std::uint32_t frameBufferAlignment; -}; - -struct InputData { - std::uint64_t thisSize; - const std::uint8_t* auData; - std::uint64_t auSize; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; -}; - -struct FrameBuffer { - std::uint64_t thisSize; - void* frameBuffer; - std::uint64_t frameBufferSize; - bool isAccepted; -}; - -struct OutputInfo { - std::uint64_t thisSize; - bool isValid; - bool isErrorFrame; - std::uint8_t pictureCount; - std::uint32_t codecType; - std::uint32_t frameWidth; - std::uint32_t framePitch; - std::uint32_t frameHeight; - void* frameBuffer; - std::uint64_t frameBufferSize; - std::uint32_t frameFormat; - std::uint32_t framePitchInBytes; -}; - -struct AvcPictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint8_t idrPictureFlag; - std::uint8_t profileIdc; - std::uint8_t levelIdc; - std::uint32_t picWidthInLumaSamples; - std::uint32_t picHeightInLumaSamples; -}; - -struct Vp9PictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint32_t profile; - std::uint32_t level; - std::uint32_t keyFrameFlag; - std::uint32_t intraOnly; - std::uint8_t bitDepth; - std::uint8_t colorRange; - std::uint32_t colorSpace; - std::uint32_t numFrames; - std::uint32_t frameWidth; - std::uint32_t frameHeight; - std::uint32_t renderWidth; - std::uint32_t renderHeight; -}; +using namespace SceVideodec2; static_assert(sizeof(Vp9PictureInfo) == 88); static_assert(offsetof(Vp9PictureInfo, colorSpace) == 60); -extern "C" { -int APS5_VABI sceVideodec2QueryDecoderMemoryInfo_nid_postfix(const DecoderConfigInfo*, DecoderMemoryInfo*); -int APS5_VABI sceVideodec2CreateDecoder_nid_postfix(const DecoderConfigInfo*, const DecoderMemoryInfo*, std::uint64_t*); -int APS5_VABI sceVideodec2DeleteDecoder_nid_postfix(std::uint64_t); -int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t, const InputData*, FrameBuffer*, OutputInfo*); -int APS5_VABI sceVideodec2Flush_nid_postfix(std::uint64_t, FrameBuffer*, OutputInfo*); -int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t); -int APS5_VABI sceVideodec2GetPictureInfo_nid_postfix(const OutputInfo*, AvcPictureInfo*, void*); -int APS5_VABI sceVideodec2GetVp9PictureInfo_nid_postfix(const OutputInfo*, Vp9PictureInfo*); -int APS5_VABI sceVideodec2GetAvcPictureInfo_nid_postfix(const OutputInfo*, AvcPictureInfo*, AvcPictureInfo*); -} - namespace { -using namespace H264Fixture; +using namespace SceVideodec2; +using namespace H264Fixture; +using namespace HevcFixture; constexpr std::uint32_t CodecHevc = 974921; constexpr std::uint32_t CodecVp9 = 2382845; -constexpr std::array HevcStream{ - 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, - 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, - 0x95, 0x90, 0x09, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, - 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x96, 0x59, 0x59, 0x2e, - 0x4c, 0xaf, 0x01, 0x68, 0x08, 0x00, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x40, - 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, - 0x08, 0xe0, 0xbb, 0xcb, 0xc1, 0x71, 0x26, 0x0f, 0xbf, 0x6c, 0xcb, 0x4d, 0x2b, 0x2c, 0xf4, 0x08, - 0xe0, 0x17, 0xeb, 0x16, 0xcc, 0x07, 0xd8, 0xdd, 0x11, 0xf8, 0x5e, 0x73, 0x79, 0x57, 0xcb, 0x69, - 0xa3, 0x1c, 0x9c, 0x43, 0x14, 0xdb, 0x61, 0x0e, 0xc5, 0xc6, 0x9c, 0x9c, 0xf8, 0xf2, 0x07, 0x35, - 0xf1, 0x98, 0x6c, 0x1a, 0x86, 0x53, 0x5a, 0xcd, 0x4d, 0xda, 0xff, 0xfb, 0x01, 0xda, 0xc3, 0x61, - 0xb7, 0x8c, 0xae, 0xc9, 0x76, 0xe4, 0x05, 0xd6, 0xfd, 0xe4, 0xcd, 0x93, 0x2c, 0xb1, 0x41, 0x29, - 0xce, 0x22, 0xf7, 0x77, 0x64, 0x0f, 0x1f, 0xd2, 0xc3, 0x68, 0x32, 0x2f, 0x83, 0xd7, 0x53, 0xbe, - 0x23, 0xff, 0x8c, 0x46, 0x18, 0x33, 0x90, 0x28, 0xa3, 0xef, 0xd7, 0xb1, 0x46, 0x84, 0xa0, 0xc2, - 0xd4, 0xd9, 0xce, 0xdf, 0x0a, 0x05, 0xec, 0xad, 0x63, 0xa4, 0x78, 0x27, 0x80, 0x4e, 0x3b, 0x4c, - 0xa6, 0x42, 0xbc, 0xe3, 0x39, 0x4d, 0xc4, 0xd3, 0x44, 0xf1, 0x22, 0xd0, 0x64, 0x9f, 0xd0, 0x88, - 0x7f, 0x39, 0x6d, 0x06, 0xc7, 0x79, 0x75, 0x2a, 0x44, 0x3d, 0x21, 0x41, 0x53, 0x60, 0x8d, 0x08, - 0x9f, 0xd0, 0x34, 0xa6, 0x72, 0x70, 0x3b, 0xa9, 0xfa, 0x62, 0xb1, 0x19, 0x9b, 0x0b, 0xa2, 0xfc, - 0xb4, 0xb3, 0x2c, 0x58, 0xad, 0xc6, 0xf3, 0xea, 0xc4, 0xd0, 0xf9, 0xea, 0x9e, 0xa7, 0xa4, 0x87, - 0xb8, 0x19, 0x68, 0xf1, 0xc1, 0x6b, 0x87, 0xa5, 0x99, 0xa0, 0x69, 0x24, 0x28, 0x80, 0xba, 0xd2, - 0x57, 0xc6, 0xc0, 0x1e, 0x69, 0x1b, 0xce, 0x96, 0xcd, 0x85, 0x9d, 0x59, 0x32, 0x92, 0x71, 0xe7, - 0x15, 0xf2, 0x9b, 0x71, 0x4b, 0xd4, 0xb6, 0x99, 0xb0, 0x71, 0x9b, 0x9b, 0xdb, 0x33, 0x28, 0x86, - 0x5e, 0x44, 0xf3, 0x5d, 0x5d, 0x7b, 0x87, 0x6c, 0x99, 0x8a, 0x6d, 0xaf, 0xf8, 0xdd, 0x39, 0x14, - 0xc0, 0x10, 0x85, 0xaf, 0x3a, 0x77, 0x60, 0x8b, 0xb7, 0x5f, 0x67, 0x45, 0xb4, 0xa7, 0xe7, 0x44, - 0x77, 0x0a, 0xb9, 0xe1, 0xb2, 0xbe, 0xde, 0x1b, 0x5a, 0xb8, 0xc8, 0x9b, 0x1f, 0x15, 0x37, 0xdc, - 0xeb, 0x55, 0x12, 0x37, 0xa1, 0xab, 0x9c, 0xcd, 0xdd, 0x42, 0x5d, 0x90, 0x89, 0xc5, 0x99, 0x54, - 0x6e, 0x9a, 0xd2, 0x71, 0x07, 0xe3, 0x81, 0x20, 0xad, 0xd4, 0x7c, 0xad, 0x69, 0xaf, 0xc7, 0x2d, - 0x7c, 0x84, 0xf1, 0x5d, 0x43, 0x4a, 0x3c, 0xf6, 0xf8, 0x7e, 0x6a, 0xb2, 0xdd, 0x00, 0xda, 0x30, - 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x30, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, 0x19, 0x5f, 0x84, - 0x31, 0x84, 0x50, 0x62, 0xcd, 0x44, 0xa6, 0x4e, 0xbb, 0x2e, 0x87, 0xda, 0x21, 0x25, 0x1c, 0x13, - 0xfe, 0xc2, 0x97, 0xa3, 0xd5, 0xfd, 0xe2, 0x62, 0xd6, 0xe5, 0x50, 0x7a, 0xda, 0x98, 0x8d, 0xcf, - 0xbe, 0x40, 0x5d, 0x69, 0xb6, 0x5f, 0xdc, 0x32, 0x68, 0x05, 0x67, 0x70, 0xfd, 0xa0, 0xd3, 0xcd, - 0x88, 0xeb, 0x12, 0x2e, 0xac, 0xf5, 0x57, 0xa2, 0xc2, 0x81, 0x22, 0x6b, 0x8d, 0x69, 0x6d, 0xac, - 0x41, 0x05, 0x5b, 0x0b, 0x79, 0xe0, 0x86, 0x05, 0x86, 0x8b, 0x02, 0x51, 0x7d, 0x6e, 0x13, 0x63, - 0x36, 0xe6, 0x89, 0x22, 0x75, 0xd9, 0xb0, 0x39, 0x27, 0xcd, 0xac, 0x78, 0xe0, 0x3a, 0x6a, 0x17, - 0x15, 0x05, 0x83, 0x3a, 0xe9, 0x38, 0x7c, 0x75, 0x38, 0xf3, 0xd8, 0x42, 0x57, 0xa1, 0xef, 0x68, - 0xb8, 0xb5, 0x44, 0xfe, 0x4d, 0x66, 0xa1, 0x09, 0xc5, 0x62, 0xff, 0x87, 0x7e, 0xdb, 0x41, 0x6f, - 0x4d, 0x5f, 0x16, 0x49, 0xef, 0xca, 0xda, 0x67, 0x01, 0x29, 0x0b, 0xd6, 0xd8, 0xfc, 0x0b, 0x22, - 0xc8, 0x28, 0xff, 0xb4, 0xb3, 0xa9, 0xf8, 0x9b, 0x78, 0x7d, 0x33, 0xbf, 0xa6, 0x9b, 0x39, 0x64, - 0x57, 0x41, 0x1f, 0x27, 0x05, 0xfc, 0xcd, 0x44, 0xcd, 0xb2, 0xea, 0x79, 0xe0, 0x00, 0x00, 0x00, - 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, 0x02, 0x01, 0xe0, 0x44, 0x97, 0xe1, 0x8f, 0x40, 0x3d, - 0x73, 0xde, 0xdf, 0xf5, 0x1c, 0xff, 0xf6, 0xe2, 0x12, 0x16, 0x3d, 0xb6, 0x75, 0xad, 0x9e, 0xb5, - 0x2b, 0xe7, 0x4a, 0x40, 0xe9, 0x91, 0xd6, 0x86, 0x86, 0x2d, 0xcb, 0xe4, 0xb6, 0x58, 0x4a, 0x2d, - 0x7b, 0x1b, 0xdb, 0xc5, 0x48, 0x2d, 0x09, 0x4d, 0x32, 0x30, 0xde, 0xaa, 0x61, 0xc2, 0x27, 0x64, - 0x54, 0x71, 0x89, 0x32, 0x05, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, - 0x00, 0x01, 0xe0, 0x24, 0xff, 0xfa, 0x2c, 0x6e, 0xdc, 0x78, 0x3d, 0x08, 0x6d, 0x3b, 0xcf, 0xcc, - 0xb6, 0x16, 0xeb, 0x9d, 0x5f, 0x46, 0x0b, 0x24, 0x0e, 0x97, 0x99, 0x06, 0x00, 0x00, 0x00, 0x01, - 0x46, 0x01, 0x30, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, 0x28, 0x95, 0xd7, 0xdc, 0x41, 0xc0, 0x61, - 0x14, 0xd6, 0xee, 0xd4, 0xe8, 0x24, 0x3e, 0x7f, 0x82, 0x22, 0xb0, 0x20, 0xb9, 0xf1, 0x69, 0x9b, - 0xfe, 0xcc, 0xb7, 0x2e, 0xbd, 0xdb, 0x20, 0xfa, 0xe5, 0x97, 0xd9, 0x95, 0x1d, 0x7e, 0x49, 0x35, - 0x69, 0xdb, 0x40, 0xa3, 0x5c, 0x0f, 0x74, 0x6f, 0x00, 0x3d, 0x36, 0x05, 0xc0, 0xc4, 0x37, 0x15, - 0xf9, 0x01, 0x2d, 0xdf, 0xcb, 0x70, 0x3d, 0xfa, 0x47, 0xec, 0x80, 0x07, 0x48, 0x08, 0xc8, 0x54, - 0xd3, 0xe3, 0x00, 0xa3, 0xe4, 0xce, 0x40, 0x05, 0x0b, 0x51, 0x73, 0x56, 0x33, 0x82, 0xa8, 0x0a, - 0xc0, 0xd3, 0xc5, 0xf1, 0xeb, 0x32, 0x8e, 0x4e, 0xa7, 0x6f, 0xb2, 0x64, 0x43, 0x54, 0x71, 0xd4, - 0xaf, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, 0x00, 0x01, 0xe0, 0x82, - 0x2f, 0x5f, 0xdc, 0x63, 0x70, 0x3f, 0xaf, 0x66, 0x75, 0x52, 0xe7, 0x7f, 0x62, 0xfb, 0x84, 0x9c, - 0xef, 0x60, 0x8f, 0xfa, 0x7e, 0x5f, 0x8b, 0x40, 0x15, 0x34, 0x69, 0xe2, 0xc4, 0xfc, 0xff, 0x53, - 0xb8, 0x1e, 0xc7, 0x20, 0x55, 0xdb, 0x5e, 0x05, 0xbd, 0x7a, 0x85, 0xb4, 0xb7, 0xc1, 0x8d, 0xdb, - 0x0f, 0xa6, 0xab, 0xb9, 0x00, 0x2a, 0xdb, 0x83, 0xa0, 0xb0, 0xc5, 0x76, 0x6e, 0xe1, 0xb8, 0x72, - 0xfd, 0x09, 0x61, 0x19, 0xe0, -}; - -constexpr std::array HevcHashes{ - 0x7291c31ee57c5065ull, 0x37b15adc47810098ull, 0xc84737b65a65b247ull, - 0xe39bc891f8286267ull, 0x734caf1ba961b3c2ull, 0x7deb44bd1ed30531ull, -}; - -constexpr std::array HevcMain10Stream{ - 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, - 0xff, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, - 0x95, 0x98, 0x09, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, - 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x93, 0x65, 0x95, 0x9a, - 0xe4, 0xca, 0xf0, 0x16, 0x80, 0x80, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x0f, 0x04, 0x00, - 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, 0x08, - 0x60, 0xf2, 0xfb, 0x04, 0xef, 0x67, 0x67, 0xdc, 0xc7, 0x8e, 0xc7, 0x42, 0x63, 0xeb, 0x7d, 0x28, - 0x73, 0x95, 0x14, 0x0c, 0x71, 0x10, 0x8b, 0x65, 0x04, 0xbd, 0x03, 0xe6, 0x04, 0x35, 0xb9, 0x9b, - 0xec, 0x60, 0x2e, 0xea, 0xcf, 0xd0, 0xdd, 0x2d, 0x3a, 0x62, 0x33, 0x9d, 0x7a, 0x74, 0xb0, 0xeb, - 0x0c, 0x06, 0xe6, 0xa5, 0x30, 0xa7, 0xea, 0xed, 0xc2, 0xa5, 0x8e, 0x7c, 0xda, 0x1b, 0x9d, 0xe4, - 0xac, 0x2b, 0x38, 0xdb, 0x9c, 0xe8, 0x51, 0x34, 0x46, 0xd2, 0x04, 0x1a, 0x77, 0x87, 0x7a, 0x19, - 0x70, 0xf9, 0x4f, 0xcb, 0x03, 0x3c, 0x74, 0x25, 0xb9, 0x92, 0x3b, 0x95, 0xe9, 0xd8, 0xaa, 0x9b, - 0x6b, 0x9b, 0x1f, 0x70, 0x25, 0x2a, 0xe7, 0x6c, 0xc3, 0x7e, 0x55, 0x39, 0xf5, 0x06, 0x5a, 0x23, - 0xb1, 0x2e, 0xf6, 0x01, 0xac, 0x71, 0xb9, 0x20, 0xb4, 0xef, 0x1d, 0x42, 0x7e, 0x0e, 0x01, 0x1d, - 0xaf, 0x0a, 0x13, 0x04, 0x8e, 0x03, 0x4d, 0x1e, 0xc4, 0xe0, -}; +constexpr std::array AvcIpppStream{ + 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x0a, 0xda, 0x11, + 0xec, 0x04, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x0f, 0x03, 0xc4, 0x89, 0xa8, 0x00, + 0x00, 0x00, 0x01, 0x68, 0xce, 0x0f, 0x2c, 0x80, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x09, 0x7f, + 0xe4, 0x5e, 0xd2, 0xf1, 0x40, 0x00, 0x40, 0x07, 0xc5, 0x00, 0x02, 0x50, 0x06, 0x46, 0x28, 0xcd, + 0x0f, 0x80, 0x38, 0x07, 0xff, 0xc0, 0x07, 0xd8, 0x76, 0x33, 0x23, 0x81, 0xce, 0x7d, 0x73, 0x08, + 0x2f, 0xc0, 0xff, 0x1f, 0xc1, 0x61, 0x01, 0x2f, 0xc1, 0x00, 0x01, 0xf0, 0x19, 0x8d, 0x80, 0x38, + 0xd1, 0x0d, 0xcc, 0x7a, 0x99, 0xdf, 0xff, 0xda, 0x00, 0x3d, 0x82, 0x8e, 0xf2, 0x8f, 0x22, 0x18, + 0x94, 0x07, 0x24, 0x94, 0x45, 0x22, 0x44, 0x5c, 0xb8, 0x7f, 0xe1, 0xa2, 0x99, 0xa3, 0xef, 0xe2, + 0x80, 0x01, 0x9f, 0x03, 0x81, 0x0e, 0x01, 0x80, 0x70, 0x00, 0x10, 0x05, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x6e, 0xbf, 0x6f, 0x66, 0xdb, 0x0c, 0x3f, 0x7c, 0x5b, 0xcc, 0xf6, 0x6c, 0xcc, 0x30, 0xe1, + 0x8c, 0x1c, 0x00, 0x04, 0x04, 0x80, 0x00, 0x42, 0x73, 0xfd, 0x1f, 0xfe, 0x20, 0x00, 0x13, 0x00, + 0x01, 0x08, 0xf8, 0x80, 0x00, 0x20, 0x2c, 0x1f, 0x04, 0xbb, 0xf3, 0xc0, 0x23, 0xdd, 0x63, 0x6f, + 0xf9, 0xe1, 0x04, 0x82, 0x53, 0x90, 0x01, 0x89, 0xe5, 0x89, 0x44, 0x5a, 0xf8, 0x39, 0xe0, 0x03, + 0x20, 0x10, 0x50, 0x00, 0x25, 0xf9, 0x8d, 0xe0, 0x04, 0xf3, 0x62, 0xfd, 0x35, 0x7d, 0x77, 0x7e, + 0x0b, 0xe1, 0x4d, 0xfe, 0x85, 0x2b, 0xef, 0xe8, 0xf0, 0x80, 0xa2, 0x80, 0x03, 0x04, 0x00, 0x06, + 0xc0, 0x72, 0x80, 0x02, 0x8c, 0x14, 0xe0, 0x9d, 0xcd, 0xe8, 0x3f, 0xe9, 0xb5, 0x9b, 0x06, 0x4d, + 0x5e, 0x80, 0xa2, 0x1f, 0x06, 0xda, 0xdf, 0x36, 0xfd, 0xa1, 0x0f, 0xfe, 0x08, 0xb0, 0x26, 0x48, + 0xaf, 0x17, 0xe9, 0xc5, 0x9f, 0xbf, 0xba, 0x2e, 0x69, 0xa9, 0xe6, 0x7c, 0xbf, 0x4f, 0xfe, 0xc1, + 0x64, 0x0a, 0xaa, 0x69, 0xda, 0x43, 0xff, 0xf9, 0xfd, 0xfc, 0xff, 0x77, 0xe9, 0x1e, 0x20, 0x18, + 0x30, 0x1f, 0x80, 0x12, 0xe4, 0x51, 0x4d, 0x9b, 0xff, 0x9b, 0x83, 0xa0, 0x01, 0x72, 0xd1, 0x7f, + 0x83, 0xa2, 0xe9, 0x82, 0x73, 0x13, 0xd3, 0x8d, 0x59, 0x95, 0x1f, 0xa6, 0x70, 0x4a, 0xeb, 0xb5, + 0xe0, 0x42, 0x10, 0xc0, 0x02, 0x00, 0x1c, 0x20, 0x00, 0x28, 0x14, 0x68, 0xe0, 0x1a, 0x82, 0xd4, + 0x87, 0x10, 0x00, 0x46, 0x54, 0x41, 0xc1, 0x81, 0x9a, 0x0c, 0x5d, 0xd0, 0x03, 0xd8, 0x0f, 0x52, + 0x07, 0x2c, 0x00, 0x7a, 0x92, 0x12, 0x02, 0xdc, 0x01, 0x67, 0x3a, 0xc1, 0xa4, 0x5a, 0x83, 0x95, + 0xc0, 0x30, 0x02, 0xf8, 0x49, 0xe2, 0x1e, 0x2b, 0xd5, 0x4a, 0x29, 0xa9, 0x6d, 0xf2, 0xc0, 0x7b, + 0x27, 0x2c, 0xb3, 0xde, 0x18, 0x00, 0x11, 0x71, 0xe0, 0x70, 0x02, 0x0c, 0x34, 0x07, 0x06, 0x01, + 0xa2, 0x90, 0x71, 0x40, 0x05, 0x12, 0x98, 0x01, 0x01, 0x63, 0xe2, 0xa8, 0xc0, 0xb8, 0xe0, 0xe0, + 0xf0, 0x06, 0x8a, 0x41, 0xd0, 0x01, 0x59, 0x12, 0x08, 0x30, 0x7d, 0x39, 0x02, 0x1d, 0x5e, 0x01, + 0x80, 0x26, 0x39, 0x0f, 0xc1, 0xef, 0x83, 0xc3, 0xf8, 0x3c, 0x7c, 0x4f, 0x86, 0xdd, 0x50, 0x6d, + 0x69, 0xd0, 0xef, 0xe9, 0x70, 0x35, 0xc4, 0xb3, 0x84, 0x40, 0x00, 0x40, 0x48, 0x00, 0x54, 0x84, + 0x00, 0x0d, 0x51, 0x81, 0xc3, 0x80, 0x3d, 0xd2, 0x1c, 0x20, 0x1c, 0xca, 0xa0, 0x10, 0x1a, 0x46, + 0x38, 0x04, 0x06, 0xd0, 0x75, 0x00, 0x2f, 0x48, 0x1e, 0x80, 0x3f, 0x49, 0x14, 0xc0, 0x3f, 0xc4, + 0x52, 0x01, 0x7e, 0x78, 0x36, 0x19, 0x08, 0x43, 0xf6, 0xc5, 0xc0, 0x01, 0xf8, 0x9f, 0x6e, 0x65, + 0xa4, 0xff, 0xff, 0x86, 0x0e, 0xf8, 0x00, 0x8b, 0xfb, 0xe4, 0xf4, 0x00, 0x57, 0x5c, 0x07, 0x4b, + 0x80, 0x66, 0xe3, 0xd2, 0xcd, 0xa7, 0x03, 0xa0, 0x41, 0x81, 0x20, 0x00, 0x20, 0x24, 0x80, 0x73, + 0x80, 0x0e, 0x52, 0x0e, 0xe5, 0xc0, 0x27, 0xd9, 0x08, 0x9c, 0x6c, 0xa6, 0x58, 0x86, 0xc5, 0xe9, + 0x0e, 0x40, 0x0c, 0xc8, 0x17, 0x5c, 0x50, 0x1a, 0xc1, 0x74, 0x47, 0x9f, 0xf0, 0x81, 0x0d, 0xb0, + 0x00, 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41, 0x9a, 0x20, 0x25, 0xde, 0x49, 0xba, + 0xbd, 0x70, 0x47, 0xbd, 0x5a, 0xa0, 0x00, 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41, + 0x9a, 0x40, 0x2d, 0xc4, 0xf0, 0x47, 0x55, 0xf7, 0xfa, 0xdf, 0xe0, 0x97, 0xbb, 0xee, 0xad, 0xf0, + 0x43, 0xd5, 0x7b, 0x00, 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41, 0x9a, 0x60, 0x35, + 0xc6, 0xc0}; void check(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } - template void checkThrows(TAction action) { bool threw = false; @@ -257,23 +121,14 @@ void testFailures() { input.auData = unit.data(); input.auSize = unit.size(); sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output); + } sceVideodec2Flush_nid_postfix(handle, &frame, &output); + }); check(sceVideodec2DeleteDecoder_nid_postfix(handle) == 0, "delete failed"); } -std::vector> hevcUnits(const std::uint8_t* stream, std::size_t size) { - std::vector starts; - for (std::size_t index = 0; index + 4 < size; ++index) { - if (stream[index] == 0 && stream[index + 1] == 0 && stream[index + 2] == 0 && stream[index + 3] == 1 && ((stream[index + 4] >> 1u) & 0x3fu) == 35) starts.push_back(index); - } - starts.push_back(size); - std::vector> units; - for (std::size_t unit = 0; unit + 1 < starts.size(); ++unit) units.emplace_back(stream + starts[unit], stream + starts[unit + 1]); - return units; -} - using Vp9Fixture::Vp9Hashes; using Vp9Fixture::ivfFrames; @@ -678,6 +533,93 @@ std::uint64_t frameBufferBytes() { return memory.maxFrameBufferSize; } +void testEmptyAccessUnit() { + constexpr int AccessUnitSizeError = static_cast(0x811D010Du); + constexpr int AccessUnitPointerError = static_cast(0x811D010Eu); + std::vector buffer(frameBufferBytes()); + const auto handle = createDecoder(CodecHevc, 1); + const auto units = hevcUnits(HevcStream.data(), HevcStream.size()); + std::size_t pictures = 0; + const auto take = [&](const OutputInfo& output, const FrameBuffer& frame) { + check(output.isValid == frame.isAccepted, "frame buffer acceptance disagrees with the output"); + if (!output.isValid) return false; + check(pictures < HevcHashes.size(), "more pictures than access units"); + check(hashNv12(buffer.data(), output.framePitch, output.frameHeight) == HevcHashes[pictures], "picture " + std::to_string(pictures) + " differs from the reference after a refused access unit"); + ++pictures; + return true; + }; + const auto refuse = [&](const std::uint8_t* data, std::uint64_t size, int expected, const char* message) { + const InputData input{sizeof(InputData), data, size, 2000, 0, 0}; + FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), true}; + OutputInfo output{sizeof(OutputInfo)}; + output.isValid = true; + output.pictureCount = 1; + check(sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output) == expected, message); + check(!output.isValid && output.pictureCount == 0 && !frame.isAccepted, "a refused access unit handed out a picture"); + }; + for (std::size_t unit = 0; unit < units.size(); ++unit) { + if (unit == 2) { + refuse(units[unit].data(), 0, AccessUnitSizeError, "an empty access unit did not return ACCESS_UNIT_SIZE"); + refuse(nullptr, units[unit].size(), AccessUnitPointerError, "a null access unit did not return ACCESS_UNIT_POINTER"); + } + const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0}; + FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false}; + OutputInfo output{sizeof(OutputInfo)}; + check(sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output) == 0, "decode failed"); + take(output, frame); + } + for (;;) { + FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false}; + OutputInfo output{sizeof(OutputInfo)}; + check(sceVideodec2Flush_nid_postfix(handle, &frame, &output) == 0, "flush failed"); + if (!take(output, frame)) break; + } + check(pictures == HevcHashes.size(), "refused access units cost pictures"); + check(sceVideodec2DeleteDecoder_nid_postfix(handle) == 0, "delete failed"); +} + +void testFlushAfterEveryUnit() { + std::vector> units; + for (std::size_t index = 0; index + 4 < AvcIpppStream.size(); ++index) { + if (AvcIpppStream[index] == 0 && AvcIpppStream[index + 1] == 0 && AvcIpppStream[index + 2] == 0 && AvcIpppStream[index + 3] == 1 && (AvcIpppStream[index + 4] & 0x1fu) == 9) units.emplace_back(); + check(!units.empty(), "the IPPP stream does not start with an access unit delimiter"); + units.back().push_back(AvcIpppStream[index]); + } + for (std::size_t index = AvcIpppStream.size() - 4; index < AvcIpppStream.size(); ++index) units.back().push_back(AvcIpppStream[index]); + check(units.size() == 4, "unexpected IPPP access unit count"); + std::vector buffer(frameBufferBytes()); + const auto decode = [&](bool flushEachUnit) { + const auto handle = createDecoder(1, 66); + std::vector hashes; + const auto take = [&](const OutputInfo& output, const FrameBuffer& frame) { + if (!output.isValid) return false; + check(frame.isAccepted && output.frameWidth == Width && output.frameHeight == CodedHeight, "unexpected IPPP picture"); + hashes.push_back(hashNv12(buffer.data(), output.framePitch, output.frameHeight)); + return true; + }; + const auto flush = [&] { + FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false}; + OutputInfo output{sizeof(OutputInfo)}; + check(sceVideodec2Flush_nid_postfix(handle, &frame, &output) == 0, "flush failed"); + return take(output, frame); + }; + for (std::size_t unit = 0; unit < units.size(); ++unit) { + const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), 3000 + unit, unit, 0}; + FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false}; + OutputInfo output{sizeof(OutputInfo)}; + check(sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output) == 0, "IPPP decode failed"); + take(output, frame); + if (flushEachUnit) flush(); + } + while (flush()) {} + check(sceVideodec2DeleteDecoder_nid_postfix(handle) == 0, "delete failed"); + return hashes; + }; + const auto reference = decode(false); + check(reference.size() == units.size(), "an IPPP picture is missing without flushes"); + check(decode(true) == reference, "a flush after each access unit lost or changed the pictures that follow it"); +} + void testDecodeAfterFlush() { std::vector buffer(frameBufferBytes()); const auto handle = createDecoder(1, 100); @@ -845,6 +787,8 @@ int main() { testVp9PictureMetadata(); testTenBit(); testDecodeAfterFlush(); + testEmptyAccessUnit(); + testFlushAfterEveryUnit(); testPictureOwnership(); testConcurrentOwnership(); std::puts("Videodec2 tests passed"); diff --git a/core/libs/tests/GuestVideodec2Unimplemented.cpp b/core/libs/tests/GuestVideodec2Unimplemented.cpp index 6f2fc0ef0a..25eb1d8a77 100644 --- a/core/libs/tests/GuestVideodec2Unimplemented.cpp +++ b/core/libs/tests/GuestVideodec2Unimplemented.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideodec2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,22 +6,6 @@ #include #include -extern "C" { -int APS5_VABI sceVideodec2TermLibrary(); -int APS5_VABI sceVideodec2QueryLibraryInfo(); -int APS5_VABI sceVideodec2QueryDecoderInfo(); -int APS5_VABI sceVideodec2MapMemory(); -int APS5_VABI sceVideodec2MapDirectMemory(void* decoder, const void* memory); -int APS5_VABI sceVideodec2PresetDecoderConfigInfo(); -int APS5_VABI sceVideodec2PresetDecoderConfigInfoAvc(); -int APS5_VABI sceVideodec2PresetDecoderConfigInfoHevc(); -int APS5_VABI sceVideodec2PresetDecoderConfigInfoVp9(); -int APS5_VABI sceVideodec2CreateDecoderBid(); -int APS5_VABI sceVideodec2CreateHevcDecoder(); -int APS5_VABI sceVideodec2QueryHevcDecoderMemoryInfo(); -int APS5_VABI sceVideodec2GetHevcPictureInfo(); -} - static bool ReportsNotImplemented(const std::string& name, const std::function& call) { try { call(); diff --git a/core/libs/tests/GuestVoice.cpp b/core/libs/tests/GuestVoice.cpp index a8671553de..112adf8c4d 100644 --- a/core/libs/tests/GuestVoice.cpp +++ b/core/libs/tests/GuestVoice.cpp @@ -1,32 +1,14 @@ +#include "prx/libSceVoice/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include +#include +#include +#include #include - -extern "C" { -int APS5_VABI sceVoiceInit(VoiceInitParam*, std::int32_t); -int APS5_VABI sceVoiceStart(const VoiceStartParam*); -int APS5_VABI sceVoiceEnd_nid_postfix(); -int APS5_VABI sceVoiceCreatePort(std::uint32_t*, const VoicePortParam*); -int APS5_VABI sceVoiceDeletePort(std::uint32_t); -int APS5_VABI sceVoiceConnectIPortToOPort(std::uint32_t, std::uint32_t); -int APS5_VABI sceVoiceDisconnectIPortFromOPort(std::uint32_t, std::uint32_t); -int APS5_VABI sceVoiceGetVolume(std::uint32_t, float*); -int APS5_VABI sceVoiceSetMuteFlag(std::uint32_t, bool); -int APS5_VABI sceVoiceGetMuteFlag(std::uint32_t, bool*); -int APS5_VABI sceVoiceGetPortInfo(std::uint32_t, VoicePortInfo*); -int APS5_VABI sceVoiceReadFromOPort(std::uint32_t, void*, std::uint32_t*); -int APS5_VABI sceVoiceResetPort(std::uint32_t); -int APS5_VABI sceVoicePausePort(std::uint32_t); -int APS5_VABI sceVoiceResumePort(std::uint32_t); -int APS5_VABI sceVoicePausePortAll(void); -int APS5_VABI sceVoiceResumePortAll(void); -int APS5_VABI sceVoiceGetResourceInfo(VoiceResourceInfo*); -int APS5_VABI sceVoiceEnableChat(void); -int APS5_VABI sceVoiceDisableChat(void); -} +#include namespace { @@ -62,6 +44,9 @@ void TestNotInitialized() { Require(std::memcmp(&info, &sentinel, sizeof(info)) == 0); Require(sceVoicePausePortAll() == NotInitialized); Require(sceVoiceResumePortAll() == NotInitialized); + Require(sceVoiceSetMuteFlagAll(true) == NotInitialized); + Require(sceVoiceSetBitRate(0, 16000) == NotInitialized); + Require(sceVoiceUpdatePort(0, nullptr) == NotInitialized); } std::uint32_t CreatePort(std::int32_t type, float volume) { @@ -69,6 +54,11 @@ std::uint32_t CreatePort(std::int32_t type, float volume) { param.port_type = type; param.volume = volume; if (type == 2 || type == 4) param.voice.bitrate = 16000; + if (type == 1 || type == 3) { + param.pcmaudio.buffer_size = 1024; + param.pcmaudio.sample_rate = 16000; + } + if (type == 5) param.device.type = 2; std::uint32_t id = 0; Require(sceVoiceCreatePort(&id, ¶m) == 0); return id; @@ -78,44 +68,131 @@ std::uint32_t CreatePort(std::int32_t type, float volume) { int main() { TestNotInitialized(); - RequireThrows([] { sceVoiceResetPort(0); }); - RequireThrows([] { sceVoicePausePort(0); }); - RequireThrows([] { sceVoiceResumePort(0); }); - RequireThrows([] { VoiceResourceInfo info{}; sceVoiceGetResourceInfo(&info); }); + constexpr auto notInitialized = static_cast(0x804e0801u); + constexpr auto invalidPort = static_cast(0x804e0804u); + constexpr auto invalidArgument = static_cast(0x804e0805u); + constexpr auto portNotFound = static_cast(0x804e0807u); + Require(sceVoiceGetVolume(128, nullptr) == notInitialized); + Require(sceVoiceGetBitRate(128, nullptr) == notInitialized); + Require(sceVoiceGetMuteFlag(128, nullptr) == notInitialized); + Require(sceVoiceSetVolume(128, 1.0f) == notInitialized); + Require(sceVoiceSetMuteFlag(128, 1) == notInitialized); + Require(sceVoiceDeletePort(128) == notInitialized); + Require(sceVoiceResetPort(0) == notInitialized); + Require(sceVoicePausePort(0) == notInitialized); + Require(sceVoiceResumePort(0) == notInitialized); + Require(sceVoiceGetResourceInfo(nullptr) == notInitialized); + Require(sceVoiceEnableChat() == notInitialized); + Require(sceVoiceDisableChat() == notInitialized); + Require(sceVoiceSetThreadsParams(nullptr) == notInitialized); + VoiceThreadsParams threads{}; + Require(sceVoiceSetThreadsParams(&threads) == notInitialized); + VoiceInitParam init{}; + Require(sceVoiceInit(nullptr, 100) == invalidArgument); + for (const auto mode : {-1, 0, 1, 3, 5, 0x07ffffff, 0x08000001, 0x10000001, 0x20000001, INT32_MAX}) { + init.app_type = mode; + Require(sceVoiceInit(&init, 100) == invalidArgument); + Require(sceVoiceGetResourceInfo(nullptr) == notInitialized); + } + init.app_type = 0x10000000; + for (const auto version : {-1, 0, 99, 101, INT32_MAX}) { + Require(sceVoiceInit(&init, version) == invalidArgument); + Require(sceVoiceGetResourceInfo(nullptr) == notInitialized); + } + VoiceStartParam start{}; + Require(sceVoiceStart(nullptr) == notInitialized); + Require(sceVoiceStart(&start) == notInitialized); + Require(sceVoiceStop() == notInitialized); + Require(sceVoiceEnd_nid_postfix() == notInitialized); + std::vector voiceMemory(0xc0000); + Require(sceVoiceInit(&init, 100) == 0); + Require(sceVoiceInit(nullptr, 0) == static_cast(0x804e0802u)); + Require(sceVoiceInit(&init, 100) == static_cast(0x804e0802u)); + Require(sceVoiceSetThreadsParams(nullptr) == invalidArgument); + Require(sceVoiceSetThreadsParams(&threads) == 0); + threads.mode = 2; + Require(sceVoiceSetThreadsParams(&threads) == invalidArgument); + threads.mode = UINT32_MAX; + Require(sceVoiceSetThreadsParams(&threads) == invalidArgument); + threads.mode = 1; + RequireThrows([&] { sceVoiceSetThreadsParams(&threads); }); + VoiceThreadsParams scheduled{1, 700, 0x7f, 700, 0, 0x7f, 700, 0, 0x7f}; + Require(sceVoiceSetThreadsParams(&scheduled) == 0); + scheduled = {1, 256, 0x1fff, 767, 0, 1, 512, 0, 0x1000}; + Require(sceVoiceSetThreadsParams(&scheduled) == 0); + for (const auto priority : {255, 768, 0, -1}) { + for (int slot = 0; slot < 3; ++slot) { + VoiceThreadsParams outside{1, 700, 0x7f, 700, 0, 0x7f, 700, 0, 0x7f}; + (slot == 0 ? outside.firstPriority : slot == 1 ? outside.secondPriority : outside.thirdPriority) = priority; + RequireThrows([&] { sceVoiceSetThreadsParams(&outside); }); + } + } + for (const std::uint64_t affinity : {std::uint64_t{0}, std::uint64_t{0x2000}, std::uint64_t{0x3fff}, UINT64_MAX}) { + for (int slot = 0; slot < 3; ++slot) { + VoiceThreadsParams outside{1, 700, 0x7f, 700, 0, 0x7f, 700, 0, 0x7f}; + (slot == 0 ? outside.firstAffinity : slot == 1 ? outside.secondAffinity : outside.thirdAffinity) = affinity; + RequireThrows([&] { sceVoiceSetThreadsParams(&outside); }); + } + } + threads.mode = 0; + Require(sceVoiceStart(nullptr) == invalidArgument); + Require(sceVoiceStart(&start) == invalidArgument); + start.container = voiceMemory.data(); + Require(sceVoiceStart(&start) == static_cast(0x804e0806u)); + start.mem_size = 0x40000; + Require(sceVoiceStart(&start) == 0); + start.mem_size = 0; + Require(sceVoiceStart(&start) == 0); + Require(sceVoiceStart(nullptr) == invalidArgument); + start.container = nullptr; + Require(sceVoiceStart(&start) == invalidArgument); + start.container = voiceMemory.data(); + start.mem_size = 0x40000; + Require(sceVoiceSetThreadsParams(nullptr) == invalidArgument); + Require(sceVoiceSetThreadsParams(&threads) == static_cast(0x804e080bu)); + scheduled = {1, 700, 0x7f, 700, 0, 0x7f, 700, 0, 0x7f}; + Require(sceVoiceSetThreadsParams(&scheduled) == static_cast(0x804e080bu)); + Require(sceVoiceGetResourceInfo(nullptr) == invalidArgument); + struct ResourceOutput { + VoiceResourceInfo info; + std::uint32_t guard; + } resourceOutput{{}, 0xa5a5a5a5u}; + Require(sceVoiceGetResourceInfo(&resourceOutput.info) == 0); + Require(resourceOutput.info.maxVoiceInputPorts == 15); + Require(resourceOutput.info.maxVoiceOutputPorts == 4); + Require(resourceOutput.info.maxDeviceInputPorts == 4); + Require(resourceOutput.info.maxDeviceOutputPorts == 4); + Require(resourceOutput.guard == 0xa5a5a5a5u); RequireThrows([] { sceVoiceEnableChat(); }); RequireThrows([] { sceVoiceDisableChat(); }); - VoiceInitParam init{}; - VoiceStartParam start{}; - Require(sceVoiceInit(&init, 0) == 0); - Require(sceVoiceInit(&init, 0) == AlreadyInitialized); - Require(sceVoiceStart(&start) == 0); - RequireThrows([] { sceVoiceGetResourceInfo(nullptr); }); - VoiceResourceInfo resourceInfo{}; - Require(sceVoiceGetResourceInfo(&resourceInfo) == 0); - Require(resourceInfo.max_voice_input_ports == 7); - Require(resourceInfo.max_voice_output_ports == 7); - Require(resourceInfo.max_device_input_ports == 1); - Require(resourceInfo.max_device_output_ports == 1); - Require(resourceInfo.max_ports == 64); - Require(sceVoiceEnableChat() == 0); - Require(sceVoiceDisableChat() == 0); const auto input = CreatePort(2, 0.5f); const auto output = CreatePort(3, 0.25f); Require(sceVoiceConnectIPortToOPort(input, output) == 0); Require(sceVoiceSetMuteFlag(output, true) == 0); + std::uint32_t portIdForInfo = 0; VoicePortInfo before{}; + before.edge = &portIdForInfo; + before.edge_count = 0x1234; + before.reserved = 0x5678; Require(sceVoiceGetPortInfo(input, &before) == 0); + Require(before.frame_size == 20 && before.byte_count == 0); + Require(before.edge == &portIdForInfo && before.edge_count == 0x1234 && before.reserved == 0x5678); + VoicePortInfo pcmInfo{}; + Require(sceVoiceGetPortInfo(output, &pcmInfo) == 0 && pcmInfo.frame_size == 2); Require(sceVoiceResetPort(input) == 0); Require(sceVoiceResetPort(output) == 0); - RequireThrows([&] { sceVoiceResetPort(output + 1); }); + Require(sceVoiceResetPort(output + 1) == portNotFound); + Require(sceVoiceResetPort(128) == invalidPort); + Require(sceVoicePausePort(input) == 0); Require(sceVoicePausePort(input) == 0); Require(sceVoicePausePort(output) == 0); VoicePortInfo paused{}; Require(sceVoiceGetPortInfo(input, &paused) == 0); Require(paused.port_type == before.port_type && paused.state == before.state && paused.frame_size == before.frame_size); Require(sceVoiceResumePort(input) == 0); + Require(sceVoiceResumePort(input) == 0); Require(sceVoiceResumePort(output) == 0); Require(sceVoicePausePortAll() == 0); Require(sceVoicePausePortAll() == 0); @@ -123,8 +200,10 @@ int main() { Require(paused.port_type == before.port_type && paused.state == before.state && paused.frame_size == before.frame_size); Require(sceVoiceResumePortAll() == 0); Require(sceVoiceResumePortAll() == 0); - RequireThrows([&] { sceVoicePausePort(output + 1); }); - RequireThrows([&] { sceVoiceResumePort(output + 1); }); + Require(sceVoicePausePort(output + 1) == portNotFound); + Require(sceVoicePausePort(128) == invalidPort); + Require(sceVoiceResumePort(output + 1) == portNotFound); + Require(sceVoiceResumePort(128) == invalidPort); VoicePortInfo after{}; Require(sceVoiceGetPortInfo(input, &after) == 0); @@ -138,15 +217,253 @@ int main() { Require(sceVoiceReadFromOPort(output, data, &size) == 0 && size == 0); Require(sceVoiceDisconnectIPortFromOPort(input, output) == 0); + Require(sceVoiceGetMuteFlag(input, &muted) == 0 && !muted); + Require(sceVoiceSetMuteFlagAll(true) == 0); + Require(sceVoiceGetMuteFlag(input, &muted) == 0 && muted); + Require(sceVoiceSetMuteFlagAll(false) == 0); + Require(sceVoiceGetMuteFlag(input, &muted) == 0 && !muted); + Require(sceVoiceGetMuteFlag(output, &muted) == 0 && muted); + + std::uint32_t portBitrate = 0; + Require(sceVoiceSetBitRate(input, 7300) == 0); + Require(sceVoiceGetBitRate(input, &portBitrate) == 0 && portBitrate == 7300); + VoicePortInfo rateInfo{}; + Require(sceVoiceGetPortInfo(input, &rateInfo) == 0 && rateInfo.frame_size == 19); + Require(sceVoiceSetBitRate(output + 1, 7300) == portNotFound); + Require(sceVoiceSetBitRate(128, 7300) == invalidPort); + RequireThrows([&] { sceVoiceSetBitRate(output, 7300); }); + RequireThrows([&] { sceVoiceSetBitRate(input, 7301); }); + RequireThrows([&] { sceVoiceSetBitRate(input, 0x80000000u); }); + Require(sceVoiceGetBitRate(input, &portBitrate) == 0 && portBitrate == 7300); + + VoicePortParam update{}; + update.port_type = 2; + update.mute = 1; + update.volume = 0.75f; + update.voice.bitrate = 14400; + Require(sceVoiceUpdatePort(input, &update) == 0); + Require(sceVoiceGetMuteFlag(input, &muted) == 0 && muted); + Require(sceVoiceGetVolume(input, &volume) == 0 && volume == 0.75f); + Require(sceVoiceGetBitRate(input, &portBitrate) == 0 && portBitrate == 14400); + Require(sceVoiceGetPortInfo(input, &rateInfo) == 0 && rateInfo.frame_size == 27); + update.voice.bitrate = 1; + RequireThrows([&] { sceVoiceUpdatePort(input, &update); }); + Require(sceVoiceGetBitRate(input, &portBitrate) == 0 && portBitrate == 14400); + update.voice.bitrate = 14400; + update.port_type = 3; + RequireThrows([&] { sceVoiceUpdatePort(input, &update); }); + RequireThrows([&] { sceVoiceUpdatePort(input, nullptr); }); + Require(sceVoiceUpdatePort(output + 1, &update) == portNotFound); + Require(sceVoiceUpdatePort(128, &update) == invalidPort); + VoicePortParam pcm{}; + pcm.port_type = 3; + pcm.volume = std::numeric_limits::quiet_NaN(); + Require(sceVoiceUpdatePort(output, &pcm) == 0); + Require(sceVoiceGetMuteFlag(output, &muted) == 0 && !muted); + Require(sceVoiceGetVolume(output, &volume) == 0 && volume == 0.0f); + pcm.volume = 0.25f; + pcm.mute = 1; + Require(sceVoiceUpdatePort(output, &pcm) == 0); + + Require(sceVoiceSetMuteFlagAll(true) == 0); + Require(sceVoiceDeletePort(input) == 0); - RequireThrows([&] { sceVoiceResetPort(input); }); - RequireThrows([&] { sceVoicePausePort(input); }); + Require(sceVoiceResetPort(input) == portNotFound); + Require(sceVoicePausePort(input) == portNotFound); Require(sceVoiceResetPort(output) == 0); Require(sceVoiceEnd_nid_postfix() == 0); TestNotInitialized(); - RequireThrows([&] { sceVoiceResetPort(output); }); - RequireThrows([&] { sceVoiceResumePort(output); }); - RequireThrows([] { VoiceResourceInfo info{}; sceVoiceGetResourceInfo(&info); }); - RequireThrows([] { sceVoiceEnableChat(); }); - RequireThrows([] { sceVoiceDisableChat(); }); + Require(sceVoiceResetPort(output) == notInitialized); + Require(sceVoiceResumePort(output) == notInitialized); + Require(sceVoiceGetResourceInfo(nullptr) == notInitialized); + Require(sceVoiceEnableChat() == notInitialized); + Require(sceVoiceDisableChat() == notInitialized); + for (const auto mode : {4, 0x08000000, 0x10000000, 0x20000000}) { + init.app_type = mode; + Require(sceVoiceInit(&init, 100) == 0); + const std::uint32_t memorySize = mode == 4 || mode == 0x08000000 ? 0xc0000 : 0x40000; + start.mem_size = memorySize - 1; + Require(sceVoiceStart(&start) == static_cast(0x804e0806u)); + start.mem_size = memorySize + 1; + Require(sceVoiceStart(&start) == static_cast(0x804e0806u)); + start.mem_size = memorySize; + Require(sceVoiceStart(&start) == 0); + Require(sceVoiceStop() == 0); + Require(sceVoiceStop() == 0); + Require(sceVoiceSetThreadsParams(&threads) == 0); + VoicePortParam portParam{}; + portParam.port_type = 0; + std::uint32_t port = UINT32_MAX; + Require(sceVoiceCreatePort(&port, &portParam) == 0 && port == 0); + Require(sceVoiceGetResourceInfo(&resourceOutput.info) == 0); + Require(resourceOutput.info.maxVoiceInputPorts == (mode == 4 ? 0 : mode == 0x20000000 ? 7 : 15)); + Require(resourceOutput.info.maxVoiceOutputPorts == (mode == 4 ? 0 : 4)); + Require(resourceOutput.info.maxDeviceInputPorts == 4); + Require(resourceOutput.info.maxDeviceOutputPorts == (mode == 4 ? 5 : 4)); + Require(resourceOutput.guard == 0xa5a5a5a5u); + Require(sceVoiceEnd_nid_postfix() == 0); + } + + constexpr auto resourceFull = static_cast(0x804e0808u); + std::uint32_t portId = 123; + VoicePortParam param{}; + Require(sceVoiceCreatePort(nullptr, nullptr) == notInitialized); + Require(sceVoiceCreatePort(&portId, ¶m) == notInitialized && portId == 123); + for (const auto mode : {4, 0x08000000, 0x10000000, 0x20000000}) { + init.app_type = mode; + Require(sceVoiceInit(&init, 100) == 0); + Require(sceVoiceCreatePort(&portId, nullptr) == invalidArgument && portId == 123); + Require(sceVoiceCreatePort(nullptr, ¶m) == invalidArgument); + for (const auto type : {-1, 6, INT32_MAX}) { + param = {}; + param.port_type = type; + Require(sceVoiceCreatePort(&portId, ¶m) == invalidArgument && portId == 255); + } + for (const auto type : {0, 2, 4, 5}) { + param = {}; + param.port_type = type; + if (type == 2 || type == 4) param.voice.bitrate = 16000; + if (type == 5) param.device.type = 14; + const auto limit = type == 0 ? 4 : type == 5 ? (mode == 4 ? 5 : 4) : mode == 4 ? 0 : type == 4 ? 4 : mode == 0x20000000 ? 7 : 15; + std::vector ports; + for (int i = 0; i < limit; ++i) { + Require(sceVoiceCreatePort(&portId, ¶m) == 0 && portId < 128); + ports.push_back(portId); + } + Require(sceVoiceCreatePort(&portId, ¶m) == resourceFull && portId == 255); + if (type == 0 || type == 5) { + param.device.type = 1; + Require(sceVoiceCreatePort(&portId, ¶m) == resourceFull); + param.device.type = type == 0 ? 0 : 2; + } + for (auto id : ports) Require(sceVoiceDeletePort(id) == 0); + if (limit != 0) { + Require(sceVoiceCreatePort(&portId, ¶m) == 0); + Require(sceVoiceDeletePort(portId) == 0); + } + } + param = {}; + param.port_type = 3; + param.pcmaudio.sample_rate = 16000; + param.pcmaudio.buffer_size = 1024; + const auto limit = mode == 4 ? 128 : mode == 0x08000000 ? 30 : mode == 0x10000000 ? 10 : 14; + std::vector ports; + for (int i = 0; i < limit; ++i) { + Require(sceVoiceCreatePort(&portId, ¶m) == 0 && portId < 128); + for (auto id : ports) Require(id != portId); + ports.push_back(portId); + } + Require(sceVoiceCreatePort(&portId, ¶m) == resourceFull && portId == 255); + const auto released = ports.front(); + Require(sceVoiceDeletePort(released) == 0); + Require(sceVoiceCreatePort(&portId, ¶m) == 0); + if (mode == 4) Require(portId == released); + Require(sceVoiceEnd_nid_postfix() == 0); + portId = 123; + } + init.app_type = 0x08000000; + Require(sceVoiceInit(&init, 100) == 0); + for (const auto type : {0, 5}) { + param = {}; + param.port_type = type; + for (const auto deviceType : {-1, 0, 1, 2, 3, 13, 14, 15, INT32_MAX}) { + param.device.type = deviceType; + const bool valid = type == 0 ? deviceType == 0 : deviceType == 2 || deviceType == 14; + Require(sceVoiceCreatePort(&portId, ¶m) == (valid ? 0 : invalidArgument)); + if (valid) Require(sceVoiceDeletePort(portId) == 0); + else Require(portId == 255); + } + } + for (const auto type : {1, 3}) { + param = {}; + param.port_type = type; + param.pcmaudio.buffer_size = 1024; + Require(sceVoiceCreatePort(&portId, ¶m) == invalidArgument); + param.pcmaudio.sample_rate = 16000; + param.pcmaudio.data_type = 1; + Require(sceVoiceCreatePort(&portId, ¶m) == invalidArgument); + param.pcmaudio.data_type = 0; + for (const std::uint32_t bytes : {0u, 511u, 512u, 1023u, 1024u, 1535u, 64512u, 65023u, 65024u, 65535u, 65536u, 66560u, UINT32_MAX}) { + param.pcmaudio.buffer_size = bytes; + const auto rounded = static_cast(bytes - bytes % 512); + const bool valid = rounded >= 1024 && rounded <= 64512; + Require(sceVoiceCreatePort(&portId, ¶m) == (valid ? 0 : invalidArgument)); + if (valid) Require(sceVoiceDeletePort(portId) == 0); + } + param.pcmaudio.buffer_size = 1024; + param.threshold = 33; + Require(sceVoiceCreatePort(&portId, ¶m) == (type == 1 ? resourceFull : 0)); + if (type == 3) Require(sceVoiceDeletePort(portId) == 0); + param.threshold = 32; + Require(sceVoiceCreatePort(&portId, ¶m) == 0); + Require(sceVoiceDeletePort(portId) == 0); + } + for (const auto type : {2, 4}) { + param = {}; + param.port_type = type; + for (const auto bitrate : {-1, 0, 3849, 3850, 4650, 5700, 7300, 11800, 12200, 14400, 16000, 16001, 22533, 22534}) { + param.voice.bitrate = bitrate; + const bool valid = bitrate == 3850 || bitrate == 4650 || bitrate == 5700 || bitrate == 7300 || bitrate == 11800 || bitrate == 12200 || bitrate == 14400 || bitrate == 16000 || bitrate == 22533; + Require(sceVoiceCreatePort(&portId, ¶m) == (valid ? 0 : invalidArgument)); + if (valid) Require(sceVoiceDeletePort(portId) == 0); + } + param.voice.bitrate = 16000; + param.threshold = 1021; + Require(sceVoiceCreatePort(&portId, ¶m) == (type == 2 ? resourceFull : 0)); + if (type == 4) Require(sceVoiceDeletePort(portId) == 0); + param.threshold = 1020; + param.mute = 1; + Require(sceVoiceCreatePort(&portId, ¶m) == 0); + Require(sceVoiceGetMuteFlag(portId, &muted) == 0 && muted); + Require(sceVoiceDeletePort(portId) == 0); + } + Require(sceVoiceGetVolume(128, nullptr) == invalidPort); + Require(sceVoiceGetBitRate(128, nullptr) == invalidPort); + Require(sceVoiceGetMuteFlag(128, nullptr) == invalidPort); + Require(sceVoiceSetVolume(128, 1.0f) == invalidPort); + Require(sceVoiceSetMuteFlag(128, 1) == invalidPort); + Require(sceVoiceDeletePort(128) == invalidPort); + Require(sceVoiceGetVolume(127, nullptr) == invalidArgument); + Require(sceVoiceGetBitRate(127, nullptr) == invalidArgument); + Require(sceVoiceGetMuteFlag(127, nullptr) == invalidArgument); + volume = 123.0f; + std::uint32_t bitrate = 123; + muted = true; + Require(sceVoiceGetVolume(127, &volume) == portNotFound && volume == 123.0f); + Require(sceVoiceGetBitRate(127, &bitrate) == portNotFound && bitrate == 123); + Require(sceVoiceGetMuteFlag(127, &muted) == portNotFound && muted); + Require(sceVoiceSetVolume(127, 1.0f) == portNotFound); + Require(sceVoiceSetMuteFlag(127, 1) == portNotFound); + Require(sceVoiceDeletePort(127) == portNotFound); + for (const auto type : {0, 1, 2, 3, 4, 5}) { + const auto id = CreatePort(type, -2.0f); + Require(sceVoiceGetBitRate(id, &bitrate) == (type == 2 || type == 4 ? 0 : portNotFound)); + if (type == 2 || type == 4) Require(bitrate == 16000); + for (const float value : {-2.0f, -0.0f, 0.25f, 2.0f, std::numeric_limits::max(), std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}) { + Require(sceVoiceSetVolume(id, value) == 0); + Require(sceVoiceGetVolume(id, &volume) == 0); + Require(volume == (std::isfinite(value) ? value : 0.0f)); + if (value == 0.0f) Require(std::signbit(volume) == std::signbit(value)); + } + for (const std::uint8_t value : {0, 1, 2, 128, 255}) { + Require(sceVoiceSetMuteFlag(id, value) == 0); + Require(sceVoiceGetMuteFlag(id, &muted) == 0 && muted == (value != 0)); + } + Require(sceVoiceDeletePort(id) == 0); + } + Require(sceVoiceEnd_nid_postfix() == 0); + + Require(sceVoiceVADSetVersion(0) == 0 && sceVoiceVADSetVersion(1) == 0); + Require(sceVoiceVADSetVersion(2) == invalidArgument && sceVoiceVADSetVersion(-1) == invalidArgument); + init.app_type = 0x10000000; + start.mem_size = 0x40000; + Require(sceVoiceInitHQ(nullptr, 100) == invalidArgument && sceVoiceInitHQ(&init, 0) == invalidArgument); + Require(sceVoiceInitHQ(&init, 100) == 0); + Require(sceVoiceInitHQ(&init, 100) == AlreadyInitialized); + Require(sceVoiceInit(&init, 100) == AlreadyInitialized); + Require(sceVoiceStart(&start) == 0); + Require(sceVoiceEnd_nid_postfix() == 0); + Require(sceVoiceInit(&init, 100) == 0); + Require(sceVoiceInitHQ(&init, 100) == AlreadyInitialized); + Require(sceVoiceEnd_nid_postfix() == 0); } diff --git a/core/libs/tests/GuestVoiceChat.cpp b/core/libs/tests/GuestVoiceChat.cpp index 0a94bcefa8..0b80ea67e8 100644 --- a/core/libs/tests/GuestVoiceChat.cpp +++ b/core/libs/tests/GuestVoiceChat.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVoiceChat/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -5,22 +6,9 @@ #include extern "C" { -int APS5_VABI sceVoiceChatInitialize(void* param); -int APS5_VABI sceVoiceChatTerminate(void); -int APS5_VABI sceVoiceChatRegisterHandlers(void* a); -int APS5_VABI sceVoiceChatRegisterMicEventHandler(void* a, void* b); -int APS5_VABI sceVoiceChatRegisterChannelEventHandler(void* a, void* b); -int APS5_VABI sceVoiceChatUnregisterMicEventHandler(void* a); -int APS5_VABI sceVoiceChatProcessEvent(void); -int APS5_VABI sceVoiceChatStartChannelEvent(void* a); -int APS5_VABI sceVoiceChatStopChannelEvent(void* a); -int APS5_VABI sceVoiceChatGetMicState(int32_t* state); -int APS5_VABI sceVoiceChatGetUserVoiceVolumeGain(void* a, int32_t* gain); -int APS5_VABI sceVoiceChatSetUserVoiceVolumeGain(void* a, int32_t gain); -int APS5_VABI sceVoiceChatGetActiveChannel(void* channel); -int APS5_VABI sceVoiceChatCreateRequest(void* a, void* b); + int APS5_VABI sceVoiceChatRequestAcsTts(int requestId, void* a, void* b); -int APS5_VABI sceVoiceChatAbortRequest(int requestId); + } static void Require(bool value, const char* what) { diff --git a/core/libs/tests/GuestVoicePortAttr.cpp b/core/libs/tests/GuestVoicePortAttr.cpp index 3264ec11c4..3bdcc5e6de 100644 --- a/core/libs/tests/GuestVoicePortAttr.cpp +++ b/core/libs/tests/GuestVoicePortAttr.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVoice/include/Api.hpp" #include "SceTypes.hpp" #include @@ -8,18 +9,10 @@ #include #include -extern "C" { -int APS5_VABI sceVoiceInit(VoiceInitParam*, int32_t); -int APS5_VABI sceVoiceEnd_nid_postfix(void); -int APS5_VABI sceVoiceCreatePort(uint32_t*, const VoicePortParam*); -int APS5_VABI sceVoiceDeletePort(uint32_t); -int APS5_VABI sceVoiceGetPortAttr(uint32_t, int32_t, void*, int32_t); -} - static void Require(bool value) { if (!value) std::abort(); } -template -static bool Throws(Call call, std::string_view message) { +template +static bool Throws(TCall call, std::string_view message) { try { call(); } catch (const std::logic_error& error) { @@ -29,31 +22,44 @@ static bool Throws(Call call, std::string_view message) { } int main() { - constexpr std::int32_t voiceActivity = 1001; - - Require(Throws([] { sceVoiceGetPortAttr(0, voiceActivity, nullptr, 1); }, "library not initialized")); - + constexpr auto notInitialized = static_cast(0x804e0801u); + constexpr auto invalidPort = static_cast(0x804e0804u); + constexpr auto invalidArgument = static_cast(0x804e0805u); + Require(sceVoiceGetPortAttr(128, 1001, nullptr, 1) == notInitialized); VoiceInitParam init{}; - Require(sceVoiceInit(&init, 0) == 0); - + init.app_type = 0x10000000; + Require(sceVoiceInit(&init, 100) == 0); VoicePortParam param{}; - param.port_type = 2; param.volume = 1.0f; - param.voice.bitrate = 16000; - std::uint32_t port = 0; - Require(sceVoiceCreatePort(&port, ¶m) == 0); - - std::array buffer{0xa5, 0xa5, 0xa5}; - Require(sceVoiceGetPortAttr(port, voiceActivity, &buffer[1], 1) == 0); - Require(buffer[0] == 0xa5 && buffer[1] == 0 && buffer[2] == 0xa5); - - Require(Throws([port, &buffer] { sceVoiceGetPortAttr(port, 1, &buffer[1], 1); }, "attribute 1 not implemented")); - - Require(Throws([&buffer] { sceVoiceGetPortAttr(0x1234, voiceActivity, &buffer[1], 1); }, "unknown port")); - - Require(sceVoiceDeletePort(port) == 0); - Require(Throws([port, &buffer] { sceVoiceGetPortAttr(port, voiceActivity, &buffer[1], 1); }, "unknown port")); - + std::array buffer{0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5}; + const auto original = buffer; + Require(sceVoiceGetPortAttr(128, 1001, nullptr, 1) == invalidPort); + Require(sceVoiceGetPortAttr(0, 1001, nullptr, 1) == invalidArgument); + for (std::int32_t type = 0; type <= 5; ++type) { + param = {}; + param.port_type = type; + if (type == 1 || type == 3) { + param.pcmaudio.buffer_size = 1024; + param.pcmaudio.sample_rate = 16000; + } + if (type == 2 || type == 4) param.voice.bitrate = 16000; + if (type == 5) param.device.type = 2; + std::uint32_t port = 0; + Require(sceVoiceCreatePort(&port, ¶m) == 0); + for (const auto attr : {1000, 1001, 1002, 1003, 2000, 2001, 2002}) { + const auto size = attr == 1003 ? 2 : attr == 1002 || attr == 2001 ? 4 : 1; + Require(sceVoiceGetPortAttr(port, attr, buffer.data() + 1, size + 1) == invalidArgument); + if (type == (attr == 1003 ? 5 : 0)) { + Require(Throws([&] { sceVoiceGetPortAttr(port, attr, buffer.data() + 1, size); }, "device attribute backend")); + } else { + Require(sceVoiceGetPortAttr(port, attr, buffer.data() + 1, size) == invalidArgument); + } + Require(buffer == original); + } + Require(sceVoiceGetPortAttr(port, 1, buffer.data() + 1, 1) == invalidArgument); + Require(sceVoiceDeletePort(port) == 0); + Require(sceVoiceGetPortAttr(port, 1001, buffer.data() + 1, 1) == invalidPort); + } Require(sceVoiceEnd_nid_postfix() == 0); return 0; } diff --git a/core/libs/tests/GuestVoiceQoS.cpp b/core/libs/tests/GuestVoiceQoS.cpp index 49d2671371..ebd15d8af0 100644 --- a/core/libs/tests/GuestVoiceQoS.cpp +++ b/core/libs/tests/GuestVoiceQoS.cpp @@ -1,36 +1,10 @@ +#include "prx/libSceVoiceQoS/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include -extern "C" { -int APS5_VABI sceVoiceQoSInit(void*, std::uint32_t, std::int32_t); -int APS5_VABI sceVoiceQoSEnd(); -int APS5_VABI sceVoiceQoSCreateLocalEndpoint(std::int32_t*, std::int32_t, std::int32_t, std::int32_t); -int APS5_VABI sceVoiceQoSDeleteLocalEndpoint(std::int32_t); -int APS5_VABI sceVoiceQoSCreateRemoteEndpoint(std::int32_t*); -int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint(std::int32_t); -int APS5_VABI sceVoiceQoSConnect(std::int32_t*, std::int32_t, std::int32_t); -int APS5_VABI sceVoiceQoSDisconnect(std::int32_t); -int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute(std::int32_t, std::int32_t, const void*, std::int32_t); -int APS5_VABI sceVoiceQoSSetRemoteEndpointAttribute(std::int32_t, std::int32_t, const void*, std::int32_t); -int APS5_VABI sceVoiceQoSGetLocalEndpointAttribute(std::int32_t, std::int32_t, void*, std::int32_t); -int APS5_VABI sceVoiceQoSGetRemoteEndpointAttribute(std::int32_t, std::int32_t, const void*, std::int32_t); -int APS5_VABI sceVoiceQoSGetConnectionAttribute(std::int32_t, std::int32_t, void*, std::int32_t); -int APS5_VABI sceVoiceQoSGetLocalEndpoint(std::int32_t, std::int32_t*); -int APS5_VABI sceVoiceQoSGetRemoteEndpoint(std::int32_t, std::int32_t*); -int APS5_VABI sceVoiceQoSEnableChat(); -int APS5_VABI sceVoiceQoSDisableChat(); -int APS5_VABI sceVoiceQoSGetStatus(std::int32_t, std::int32_t, void*, std::int32_t); -int APS5_VABI sceVoiceQoSDebugGetStatus(); -int APS5_VABI sceVoiceQoSInitHQ(); -int APS5_VABI sceVoiceQoSSetMode(); -int APS5_VABI sceVoiceQoSTerminate(); -int APS5_VABI sceVoiceQoSReadPacket(std::int32_t, void*, std::uint32_t*); -int APS5_VABI sceVoiceQoSWritePacket(std::int32_t, const void*, std::uint32_t*); -} - static void Require(bool value) { if (!value) std::abort(); } template @@ -70,10 +44,10 @@ int main(int argc, char** argv) { std::int32_t remote = -1; std::int32_t connection = -1; Require(sceVoiceQoSCreateLocalEndpoint(nullptr, 1, 0, 0) == argumentInvalid); - Require(sceVoiceQoSCreateLocalEndpoint(&local, 1, 0, 0) == 0 && local > 0); - Require(sceVoiceQoSCreateRemoteEndpoint(&remote) == 0 && remote > 0 && remote != local); - Require(sceVoiceQoSConnect(&connection, remote, local) == argumentInvalid); - Require(sceVoiceQoSConnect(&connection, local, remote) == 0 && connection > 0); + Require(sceVoiceQoSCreateLocalEndpoint(&local, 1, 0, 0) == 0 && local == 0); + Require(sceVoiceQoSCreateRemoteEndpoint(&remote) == 0 && remote == 0); + Require(sceVoiceQoSConnect(&connection, 1, remote) == argumentInvalid); + Require(sceVoiceQoSConnect(&connection, local, remote) == 0 && connection == 0); std::int32_t queriedLocal = -1; std::int32_t queriedRemote = -1; Require(sceVoiceQoSGetLocalEndpoint(connection, nullptr) == argumentInvalid); @@ -86,32 +60,22 @@ int main(int argc, char** argv) { Require(sceVoiceQoSConnect(&duplicate, local, remote) == 0 && duplicate > 0 && duplicate != connection); Require(sceVoiceQoSDisconnect(duplicate) == 0); - std::int32_t otherLocal = -1; - std::int32_t otherConnection = -1; - Require(sceVoiceQoSCreateLocalEndpoint(&otherLocal, 1, 0, 0) == 0); - Require(sceVoiceQoSConnect(&otherConnection, otherLocal, remote) == 0); - Require(sceVoiceQoSDeleteLocalEndpoint(otherLocal) == 0); - Require(sceVoiceQoSDeleteLocalEndpoint(otherLocal) == argumentInvalid); - Require(sceVoiceQoSDisconnect(otherConnection) == 0); + constexpr int limitReached = static_cast(0x804E0907); + std::int32_t otherLocal = 12; + Require(sceVoiceQoSCreateLocalEndpoint(&otherLocal, 1, 0, 0) == limitReached && otherLocal == -1); + Require(sceVoiceQoSCreateLocalEndpoint(&otherLocal, 2, 0, 0) == limitReached && otherLocal == -1); const std::uint32_t level = 0x1234; - std::uint32_t readLevel = 0; - Require(sceVoiceQoSGetLocalEndpointAttribute(local, 3, &readLevel, sizeof(readLevel)) == 0 && readLevel == 0); - Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, sizeof(level)) == 0); - Require(sceVoiceQoSGetLocalEndpointAttribute(local, 3, &readLevel, sizeof(readLevel)) == 0 && readLevel == level); - Require(sceVoiceQoSGetLocalEndpointAttribute(local, 3, nullptr, sizeof(readLevel)) == argumentInvalid); - Require(sceVoiceQoSGetLocalEndpointAttribute(local, 3, &readLevel, 0) == argumentInvalid); - Require(sceVoiceQoSGetLocalEndpointAttribute(local, 3, &readLevel, -1) == argumentInvalid); - Require(sceVoiceQoSGetLocalEndpointAttribute(local + 100, 3, &readLevel, sizeof(readLevel)) == argumentInvalid); + Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, sizeof(level)) == argumentInvalid); Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, nullptr, sizeof(level)) == argumentInvalid); - Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, 0) == 0); - Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, -1) == 0); + Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, 0) == argumentInvalid); + Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, -1) == argumentInvalid); Require(sceVoiceQoSSetLocalEndpointAttribute(local + 100, 3, &level, sizeof(level)) == argumentInvalid); - Require(sceVoiceQoSSetRemoteEndpointAttribute(remote, 3, &level, sizeof(level)) == 0); + Require(sceVoiceQoSSetRemoteEndpointAttribute(remote, 3, &level, sizeof(level)) == argumentInvalid); Require(sceVoiceQoSSetRemoteEndpointAttribute(remote, 3, nullptr, sizeof(level)) == argumentInvalid); - Require(sceVoiceQoSSetRemoteEndpointAttribute(remote, 3, &level, 0) == 0); - Require(sceVoiceQoSSetRemoteEndpointAttribute(remote, 3, &level, -1) == 0); + Require(sceVoiceQoSSetRemoteEndpointAttribute(remote, 3, &level, 0) == argumentInvalid); + Require(sceVoiceQoSSetRemoteEndpointAttribute(remote, 3, &level, -1) == argumentInvalid); Require(sceVoiceQoSSetRemoteEndpointAttribute(remote + 100, 3, &level, sizeof(level)) == argumentInvalid); std::vector packet(1024, 0); @@ -127,15 +91,21 @@ int main(int argc, char** argv) { Require(sceVoiceQoSWritePacket(connection, packet.data(), nullptr) == argumentInvalid); Require(sceVoiceQoSWritePacket(connection + 100, packet.data(), &packetSize) == argumentInvalid); + Require(NotImplemented([&] { sceVoiceQoSSetLocalEndpointAttribute(local, 0, &level, 4); })); + Require(NotImplemented([&] { sceVoiceQoSSetRemoteEndpointAttribute(remote, 7, &level, 1); })); Require(sceVoiceQoSDisconnect(connection) == 0); - Require(sceVoiceQoSDisconnect(connection) == argumentInvalid); - Require(sceVoiceQoSGetLocalEndpoint(connection, &queriedLocal) == argumentInvalid); - Require(sceVoiceQoSGetRemoteEndpoint(connection, &queriedRemote) == argumentInvalid); + Require(sceVoiceQoSDisconnect(connection) == 0); + Require(sceVoiceQoSGetLocalEndpoint(connection, &queriedLocal) == 0 && queriedLocal == -1); + Require(sceVoiceQoSGetRemoteEndpoint(connection, &queriedRemote) == 0 && queriedRemote == -1); Require(sceVoiceQoSDeleteLocalEndpoint(local) == 0); Require(sceVoiceQoSDeleteLocalEndpoint(local) == argumentInvalid); Require(sceVoiceQoSDeleteRemoteEndpoint(remote) == 0); - Require(sceVoiceQoSCreateRemoteEndpoint(&remote) == 0); + const int endpointLimit = appType == 0x10000000 ? 15 : 7; + for (int index = 0; index < endpointLimit; ++index) Require(sceVoiceQoSCreateRemoteEndpoint(&remote) == 0 && remote == index); + Require(sceVoiceQoSCreateRemoteEndpoint(&remote) == limitReached && remote == -1); + Require(sceVoiceQoSDeleteRemoteEndpoint(2) == 0); + Require(sceVoiceQoSCreateRemoteEndpoint(&remote) == 0 && remote == 2); Require(sceVoiceQoSEnd() == 0); Require(sceVoiceQoSEnd() == notInitialized); Require(sceVoiceQoSGetLocalEndpoint(connection, &queriedLocal) == notInitialized); @@ -143,7 +113,6 @@ int main(int argc, char** argv) { Require(sceVoiceQoSDeleteRemoteEndpoint(remote) == notInitialized); Require(sceVoiceQoSCreateLocalEndpoint(&local, 1, 0, 0) == notInitialized); Require(sceVoiceQoSSetRemoteEndpointAttribute(remote, 3, &level, sizeof(level)) == notInitialized); - Require(sceVoiceQoSGetLocalEndpointAttribute(local, 3, &readLevel, sizeof(readLevel)) == notInitialized); Require(sceVoiceQoSReadPacket(connection, packet.data(), &packetSize) == notInitialized); Require(sceVoiceQoSWritePacket(connection, packet.data(), &packetSize) == notInitialized); Require(sceVoiceQoSInit(memory.data(), 0x40000, appType) == 0); @@ -152,13 +121,14 @@ int main(int argc, char** argv) { Require(sceVoiceQoSEnd() == notInitialized); Require(sceVoiceQoSInit(nullptr, 0x40000, appType) == argumentInvalid); + Require(NotImplemented([] { sceVoiceQoSGetLocalEndpointAttribute(0, 0, nullptr, 0); })); Require(NotImplemented([] { sceVoiceQoSGetRemoteEndpointAttribute(1, 1, nullptr, 0); })); Require(NotImplemented([] { sceVoiceQoSGetConnectionAttribute(1, 1, nullptr, 0); })); Require(NotImplemented([] { sceVoiceQoSEnableChat(); })); Require(NotImplemented([] { sceVoiceQoSDisableChat(); })); Require(NotImplemented([] { sceVoiceQoSGetStatus(1, 1, nullptr, 0); })); - Require(NotImplemented([] { sceVoiceQoSDebugGetStatus(); })); - Require(NotImplemented([] { sceVoiceQoSInitHQ(); })); + Require(NotImplemented([] { sceVoiceQoSDebugGetStatus(0, 200, nullptr, 4); })); + Require(NotImplemented([] { sceVoiceQoSInitHQ(nullptr, 0xc0000, 0x80000); })); Require(NotImplemented([] { sceVoiceQoSSetMode(); })); Require(NotImplemented([] { sceVoiceQoSTerminate(); })); } diff --git a/core/libs/tests/GuestVorbisDec.cpp b/core/libs/tests/GuestVorbisDec.cpp index 80bce8c123..9b44ce165b 100644 --- a/core/libs/tests/GuestVorbisDec.cpp +++ b/core/libs/tests/GuestVorbisDec.cpp @@ -1,99 +1,613 @@ -#include "SceTypes.hpp" +#include "prx/libSceVorbisDec/include/Api.hpp" +#include "prx/libSceVorbisDec/include/GuestVorbisFile.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +#include +#include +#include +#include #include #include #include +#include #include +#include -extern "C" { -int APS5_VABI sceVorbisDecOpenCallbacks(void); -int APS5_VABI sceVorbisDecClear(void); -int APS5_VABI sceVorbisDecBitrate(void); -int APS5_VABI sceVorbisDecBitrateInstant(void); -int APS5_VABI sceVorbisDecStreams(void); -int APS5_VABI sceVorbisDecSeekable(void); -int APS5_VABI sceVorbisDecSerialNumber(void); -int APS5_VABI sceVorbisDecRawTotal(void); -int APS5_VABI sceVorbisDecPcmTotal(void); -int APS5_VABI sceVorbisDecTimeTotal(void); -int APS5_VABI sceVorbisDecRawSeek(void); -int APS5_VABI sceVorbisDecPcmSeek(void); -int APS5_VABI sceVorbisDecPcmSeekPage(void); -int APS5_VABI sceVorbisDecTimeSeek(void); -int APS5_VABI sceVorbisDecTimeSeekPage(void); -int APS5_VABI sceVorbisDecRawSeekLap(void); -int APS5_VABI sceVorbisDecPcmSeekLap(void); -int APS5_VABI sceVorbisDecPcmSeekPageLap(void); -int APS5_VABI sceVorbisDecTimeSeekLap(void); -int APS5_VABI sceVorbisDecTimeSeekPageLap(void); -int APS5_VABI sceVorbisDecRawTell(void); -int APS5_VABI sceVorbisDecPcmTell(void); -int APS5_VABI sceVorbisDecTimeTell(void); -int APS5_VABI sceVorbisDecInfo(void); -int APS5_VABI sceVorbisDecComment(void); -int APS5_VABI sceVorbisDecRead(void); -int APS5_VABI sceVorbisDecReadFloat(void); -int APS5_VABI sceVorbisDecReadFilter(void); -int APS5_VABI sceVorbisDecCrossLap(void); -int APS5_VABI sceVorbisDecHalfrate(void); -int APS5_VABI sceVorbisDecHalfrateP(void); -int APS5_VABI sceVorbisDecGetDecodePriority(void); -int APS5_VABI sceVorbisDecSetDecodePriority(void); -int APS5_VABI sceVorbisDecGetLastDecodeError(void); +using namespace VorbisDec; + +namespace { + +constexpr int FirstSerial = 0x1234abcd; +constexpr int SecondSerial = -0x61c88647; +constexpr int FirstChannels = 3; +constexpr int SecondChannels = 2; +constexpr long FirstRate = 44100; +constexpr long SecondRate = 22050; +constexpr long FirstSamples = 44100; +constexpr long SecondSamples = 11025; +constexpr std::int64_t DecodedBytes = (FirstSamples * FirstChannels + SecondSamples * SecondChannels) * 2; +constexpr unsigned char Pattern = 0xA5; + +void Check(bool value, int line) { + if (value) return; + std::fprintf(stderr, "VorbisDec check failed at line %d\n", line); + std::abort(); } +#define Require(value) Check((value), __LINE__) -template -static bool Throws(TCall call) { +template +bool Throws(TCall call) { try { call(); - } catch (const std::runtime_error&) { + } catch (const TException&) { return true; } return false; } -static void Require(bool value, const char* what) { - if (value) return; - std::printf("VorbisDec test failed: %s\n", what); - std::abort(); +struct Source { + const std::vector* bytes = nullptr; + std::size_t position = 0; + int closes = 0; + void* probe = nullptr; + std::int64_t probed = -1; + bool throwOnRead = false; + bool throwOnClose = false; +}; + +std::size_t ReadBytes(Source& source, void* buffer, std::size_t size, std::size_t count) { + if (source.throwOnRead) throw std::runtime_error("read callback"); + if (source.probe) source.probed = sceVorbisDecStreams(source.probe); + if (size == 0) return 0; + const std::size_t items = std::min(count, (source.bytes->size() - source.position) / size); + if (items != 0) std::memcpy(buffer, source.bytes->data() + source.position, items * size); + source.position += items * size; + return items; +} + +int SeekBytes(Source& source, std::int64_t offset, int whence) { + const auto size = static_cast(source.bytes->size()); + const std::int64_t base = whence == SEEK_SET ? 0 : whence == SEEK_CUR ? static_cast(source.position) : size; + if (offset < -base || offset > size - base) return -1; + source.position = static_cast(base + offset); + return 0; +} + +std::size_t APS5_VABI GuestRead(void* buffer, std::size_t size, std::size_t count, void* source) { + return ReadBytes(*static_cast(source), buffer, size, count); +} + +int APS5_VABI GuestSeek(void* source, std::int64_t offset, int whence) { + return SeekBytes(*static_cast(source), offset, whence); +} + +int APS5_VABI GuestClose(void* source) { + auto& closing = *static_cast(source); + ++closing.closes; + if (closing.throwOnClose) throw std::runtime_error("close callback"); + return 0; +} + +std::int64_t APS5_VABI GuestTell(void* source) { + return static_cast(static_cast(source)->position); +} + +std::size_t HostRead(void* buffer, std::size_t size, std::size_t count, void* source) { + return ReadBytes(*static_cast(source), buffer, size, count); +} + +int HostSeek(void* source, ogg_int64_t offset, int whence) { + return SeekBytes(*static_cast(source), offset, whence); +} + +int HostClose(void* source) { + ++static_cast(source)->closes; + return 0; +} + +long HostTell(void* source) { + return static_cast(static_cast(source)->position); +} + +constexpr GuestCallbacks SeekableCallbacks{GuestRead, GuestSeek, GuestClose, GuestTell}; +constexpr GuestCallbacks StreamingCallbacks{GuestRead, nullptr, GuestClose, nullptr}; +const ov_callbacks HostSeekableCallbacks{HostRead, HostSeek, HostClose, HostTell}; +const ov_callbacks HostStreamingCallbacks{HostRead, nullptr, HostClose, nullptr}; + +struct GuestFile { + alignas(16) std::array bytes; + + GuestFile() { bytes.fill(Pattern); } + + void* Get() { return bytes.data(); } + + bool Untouched() const { + return std::all_of(bytes.begin(), bytes.end(), [](unsigned char value) { return value == Pattern; }); + } +}; + +struct FilterLog { + int calls = 0; + std::int64_t firstChannels = 0; + std::int64_t lastChannels = 0; + std::int64_t samples = 0; +}; + +void Halve(float** pcm, std::int64_t channels, std::int64_t samples) { + for (std::int64_t channel = 0; channel < channels; ++channel) + for (std::int64_t index = 0; index < samples; ++index) pcm[channel][index] *= 0.5f; +} + +void APS5_VABI GuestHalve(float** pcm, std::int64_t channels, std::int64_t samples, void* param) { + auto* log = static_cast(param); + if (log->calls++ == 0) log->firstChannels = channels; + log->lastChannels = channels; + log->samples += samples; + Halve(pcm, channels, samples); +} + +void HostHalve(float** pcm, long channels, long samples, void*) { + Halve(pcm, channels, samples); +} + +void AppendPage(std::vector& out, const ogg_page& page) { + out.insert(out.end(), page.header, page.header + page.header_len); + out.insert(out.end(), page.body, page.body + page.body_len); +} + +void EncodeLink(std::vector& out, int serial, int channels, long rate, long samples, const char* title) { + vorbis_info info; + vorbis_info_init(&info); + Require(vorbis_encode_init_vbr(&info, channels, rate, 0.1f) == 0); + vorbis_comment comment; + vorbis_comment_init(&comment); + vorbis_comment_add_tag(&comment, "TITLE", title); + vorbis_comment_add_tag(&comment, "ARTIST", "AnyPS5"); + vorbis_dsp_state dsp; + Require(vorbis_analysis_init(&dsp, &info) == 0); + vorbis_block block; + vorbis_block_init(&dsp, &block); + ogg_stream_state stream; + ogg_stream_init(&stream, serial); + ogg_packet identification; + ogg_packet comments; + ogg_packet codebooks; + vorbis_analysis_headerout(&dsp, &comment, &identification, &comments, &codebooks); + ogg_stream_packetin(&stream, &identification); + ogg_stream_packetin(&stream, &comments); + ogg_stream_packetin(&stream, &codebooks); + ogg_page page; + while (ogg_stream_flush(&stream, &page) != 0) AppendPage(out, page); + for (long done = 0;;) { + const long count = std::min(1024L, samples - done); + if (count > 0) { + float** buffer = vorbis_analysis_buffer(&dsp, static_cast(count)); + for (int channel = 0; channel < channels; ++channel) { + const double frequency = 220.0 * (channel + 1) + 37.0 * serial / 1e9; + for (long index = 0; index < count; ++index) { + buffer[channel][index] = static_cast(0.3 * std::sin(6.283185307179586 * frequency * static_cast(done + index) / static_cast(rate))); + } + } + } + vorbis_analysis_wrote(&dsp, static_cast(count)); + done += count; + while (vorbis_analysis_blockout(&dsp, &block) == 1) { + vorbis_analysis(&block, nullptr); + vorbis_bitrate_addblock(&block); + ogg_packet packet; + while (vorbis_bitrate_flushpacket(&dsp, &packet) == 1) { + ogg_stream_packetin(&stream, &packet); + while (ogg_stream_pageout_fill(&stream, &page, 1024) != 0) AppendPage(out, page); + } + } + if (count == 0) break; + } + while (ogg_stream_flush(&stream, &page) != 0) AppendPage(out, page); + ogg_stream_clear(&stream); + vorbis_block_clear(&block); + vorbis_dsp_clear(&dsp); + vorbis_comment_clear(&comment); + vorbis_info_clear(&info); +} + +std::int64_t RequireSameRead(void* vf, OggVorbis_File& reference, int bigEndian = 0, int word = 2, int isSigned = 1) { + std::array guest{}; + std::array host{}; + int guestStream = -1; + int hostStream = -1; + const std::int64_t got = sceVorbisDecRead(vf, guest.data(), static_cast(guest.size()), bigEndian, word, isSigned, &guestStream); + const long expected = ov_read(&reference, host.data(), static_cast(host.size()), bigEndian, word, isSigned, &hostStream); + Require(got == expected); + Require(got <= 0 || (guestStream == hostStream && std::memcmp(guest.data(), host.data(), static_cast(got)) == 0)); + return got; +} + +std::int64_t DecodeTogether(void* vf, OggVorbis_File& reference) { + std::int64_t total = 0; + for (;;) { + const std::int64_t got = RequireSameRead(vf, reference); + Require(got >= 0 || got == OV_HOLE); + if (got == 0) return total; + if (got > 0) total += got; + } +} + +void RequireSameTells(void* vf, OggVorbis_File& reference) { + Require(sceVorbisDecPcmTell(vf) == ov_pcm_tell(&reference)); + Require(sceVorbisDecRawTell(vf) == ov_raw_tell(&reference)); + Require(sceVorbisDecTimeTell(vf) == ov_time_tell(&reference)); +} + +void TestSeekable(const std::vector& stream) { + GuestFile guest; + GuestFile probe; + void* vf = guest.Get(); + Source source{&stream}; + source.probe = probe.Get(); + Require(sceVorbisDecOpenCallbacks(&source, vf, nullptr, 0, SeekableCallbacks) == 0); + Require(source.probed == 0 && source.closes == 0); + Source referenceSource{&stream}; + OggVorbis_File reference{}; + Require(ov_open_callbacks(&referenceSource, &reference, nullptr, 0, HostSeekableCallbacks) == 0); + + Require(sceVorbisDecStreams(vf) == 2 && sceVorbisDecSeekable(vf) == 1); + Require(sceVorbisDecSerialNumber(vf, 0) == FirstSerial && sceVorbisDecSerialNumber(vf, 1) == SecondSerial); + Require(sceVorbisDecSerialNumber(vf, -1) == FirstSerial && sceVorbisDecSerialNumber(vf, 7) == SecondSerial); + Require(sceVorbisDecPcmTotal(vf, 0) == FirstSamples && sceVorbisDecPcmTotal(vf, 1) == SecondSamples); + Require(sceVorbisDecPcmTotal(vf, -1) == FirstSamples + SecondSamples && sceVorbisDecPcmTotal(vf, 2) == OV_EINVAL); + Require(sceVorbisDecRawTotal(vf, -1) == ov_raw_total(&reference, -1) && sceVorbisDecRawTotal(vf, -1) > 0); + Require(sceVorbisDecRawTotal(vf, 0) == ov_raw_total(&reference, 0) && sceVorbisDecRawTotal(vf, 1) == ov_raw_total(&reference, 1)); + Require(sceVorbisDecTimeTotal(vf, 0) == 1.0 && sceVorbisDecTimeTotal(vf, 1) == 0.5 && sceVorbisDecTimeTotal(vf, -1) == 1.5); + Require(sceVorbisDecBitrate(vf, -1) == ov_bitrate(&reference, -1) && sceVorbisDecBitrate(vf, 1) == ov_bitrate(&reference, 1)); + Require(sceVorbisDecBitrate(vf, -1) > 0 && sceVorbisDecBitrate(vf, 2) == OV_EINVAL); + Require(sceVorbisDecBitrateInstant(vf) == OV_FALSE); + + GuestInfo* first = sceVorbisDecInfo(vf, 0); + GuestInfo* second = sceVorbisDecInfo(vf, 1); + Require(first && second && first != second && sceVorbisDecInfo(vf, 0) == first); + Require(sceVorbisDecInfo(vf, -1) == first && sceVorbisDecInfo(vf, 2) == nullptr); + Require(first->version == 0 && first->channels == FirstChannels && first->rate == FirstRate); + Require(second->version == 0 && second->channels == SecondChannels && second->rate == SecondRate); + for (int link = 0; link < 2; ++link) { + const GuestInfo* info = sceVorbisDecInfo(vf, link); + const vorbis_info* host = ov_info(&reference, link); + Require(info->bitrate_upper == host->bitrate_upper && info->bitrate_nominal == host->bitrate_nominal); + Require(info->bitrate_lower == host->bitrate_lower && info->bitrate_window == host->bitrate_window && info->codec_setup != nullptr); + } + const GuestComment* comment = sceVorbisDecComment(vf, 1); + Require(comment && comment->comments == 2 && comment->comment_lengths[0] == 12 && comment->comment_lengths[1] == 13); + Require(std::strcmp(comment->user_comments[0], "TITLE=second") == 0 && std::strcmp(comment->user_comments[1], "ARTIST=AnyPS5") == 0); + Require(std::strcmp(comment->vendor, ov_comment(&reference, 1)->vendor) == 0); + Require(sceVorbisDecComment(vf, -1) == sceVorbisDecComment(vf, 0) && sceVorbisDecComment(vf, 2) == nullptr); + Require(std::strcmp(sceVorbisDecComment(vf, 0)->user_comments[0], "TITLE=first") == 0); + + const std::int64_t firstRead = RequireSameRead(vf, reference); + Require(firstRead > 0 && sceVorbisDecBitrateInstant(vf) == ov_bitrate_instant(&reference)); + Require(sceVorbisDecInfo(vf, -1) == first); + Require(firstRead + DecodeTogether(vf, reference) == DecodedBytes); + Require(sceVorbisDecPcmTell(vf) == FirstSamples + SecondSamples && sceVorbisDecTimeTell(vf) == 1.5); + RequireSameTells(vf, reference); + Require(sceVorbisDecInfo(vf, -1) == second && sceVorbisDecSerialNumber(vf, -1) == SecondSerial); + + Require(sceVorbisDecPcmSeek(vf, 0) == 0 && ov_pcm_seek(&reference, 0) == 0); + Require(DecodeTogether(vf, reference) == DecodedBytes); + + Require(sceVorbisDecPcmSeek(vf, 1000) == 0 && ov_pcm_seek(&reference, 1000) == 0); + Require(sceVorbisDecPcmTell(vf) == 1000); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && RequireSameRead(vf, reference, 1, 2, 0) > 0 && RequireSameRead(vf, reference, 0, 1, 0) > 0); + Require(sceVorbisDecPcmSeekPage(vf, FirstSamples + 3000) == ov_pcm_seek_page(&reference, FirstSamples + 3000)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && sceVorbisDecInfo(vf, -1) == second); + Require(sceVorbisDecTimeSeek(vf, 0.25) == 0 && ov_time_seek(&reference, 0.25) == 0); + Require(sceVorbisDecPcmTell(vf) == FirstRate / 4); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && sceVorbisDecInfo(vf, -1) == first); + Require(sceVorbisDecTimeSeekPage(vf, 1.2) == ov_time_seek_page(&reference, 1.2)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0); + const auto middle = static_cast(stream.size() / 2); + Require(sceVorbisDecRawSeek(vf, middle) == 0 && ov_raw_seek(&reference, middle) == 0); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0); + + Require(sceVorbisDecPcmSeekLap(vf, 5000) == 0 && ov_pcm_seek_lap(&reference, 5000) == 0); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && RequireSameRead(vf, reference) > 0); + Require(sceVorbisDecPcmSeekPageLap(vf, 30000) == ov_pcm_seek_page_lap(&reference, 30000)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0); + Require(sceVorbisDecRawSeekLap(vf, middle / 2) == ov_raw_seek_lap(&reference, middle / 2)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0); + Require(sceVorbisDecTimeSeekLap(vf, 1.1) == 0 && ov_time_seek_lap(&reference, 1.1) == 0); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && sceVorbisDecInfo(vf, -1) == second); + Require(sceVorbisDecTimeSeekPageLap(vf, 0.75) == ov_time_seek_page_lap(&reference, 0.75)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && sceVorbisDecInfo(vf, -1) == first); + + Require(sceVorbisDecPcmSeek(vf, -1) == OV_EINVAL && sceVorbisDecPcmSeek(vf, FirstSamples + SecondSamples + 1) == OV_EINVAL); + Require(sceVorbisDecTimeSeek(vf, -0.5) == OV_EINVAL && sceVorbisDecTimeSeek(vf, 2.0) == OV_EINVAL); + Require(sceVorbisDecRawSeek(vf, -1) == OV_EINVAL && sceVorbisDecRawSeek(vf, middle * 3) == OV_EINVAL); + + Require(source.closes == 0 && sceVorbisDecClear(vf) == 0 && source.closes == 1); + Require(sceVorbisDecStreams(vf) == 0 && sceVorbisDecInfo(vf, 0) == nullptr && sceVorbisDecPcmTell(vf) == OV_EINVAL); + Require(sceVorbisDecClear(vf) == 0 && source.closes == 1); + Require(ov_clear(&reference) == 0 && referenceSource.closes == 1); + Require(source.probed == 0 && guest.Untouched() && probe.Untouched()); +} + +void TestStreaming(const std::vector& stream) { + GuestFile guest; + void* vf = guest.Get(); + const auto* initial = reinterpret_cast(stream.data()); + Source source{&stream, 64}; + Require(sceVorbisDecOpenCallbacks(&source, vf, initial, 64, StreamingCallbacks) == 0); + Source referenceSource{&stream, 64}; + OggVorbis_File reference{}; + Require(ov_open_callbacks(&referenceSource, &reference, initial, 64, HostStreamingCallbacks) == 0); + + Require(sceVorbisDecSeekable(vf) == 0 && sceVorbisDecStreams(vf) == 1); + Require(sceVorbisDecPcmTotal(vf, -1) == OV_EINVAL && sceVorbisDecRawTotal(vf, 0) == OV_EINVAL && sceVorbisDecTimeTotal(vf, -1) == OV_EINVAL); + Require(sceVorbisDecRawSeek(vf, 0) == OV_ENOSEEK && sceVorbisDecPcmSeek(vf, 0) == OV_ENOSEEK && sceVorbisDecTimeSeekPage(vf, 0.0) == OV_ENOSEEK); + Require(sceVorbisDecBitrate(vf, 0) == ov_bitrate(&reference, 0) && sceVorbisDecBitrate(vf, 1) == ov_bitrate(&reference, 1)); + Require(sceVorbisDecSerialNumber(vf, 3) == FirstSerial); + GuestInfo* info = sceVorbisDecInfo(vf, 0); + Require(info && info == sceVorbisDecInfo(vf, 5) && info == sceVorbisDecInfo(vf, -1)); + Require(info->channels == FirstChannels && info->rate == FirstRate); + const GuestComment* comment = sceVorbisDecComment(vf, 4); + Require(comment && std::strcmp(comment->user_comments[0], "TITLE=first") == 0); + + Require(DecodeTogether(vf, reference) == DecodedBytes); + Require(info->channels == SecondChannels && info->rate == SecondRate && sceVorbisDecInfo(vf, 0) == info); + Require(std::strcmp(comment->user_comments[0], "TITLE=second") == 0); + Require(sceVorbisDecSerialNumber(vf, 0) == SecondSerial); + RequireSameTells(vf, reference); + Require(sceVorbisDecClear(vf) == 0 && source.closes == 1); + Require(ov_clear(&reference) == 0 && referenceSource.closes == 1); + Require(guest.Untouched()); +} + +void TestFloatAndFilter(const std::vector& stream) { + GuestFile guest; + void* vf = guest.Get(); + Source source{&stream}; + Require(sceVorbisDecOpenCallbacks(&source, vf, nullptr, 0, SeekableCallbacks) == 0); + Source referenceSource{&stream}; + OggVorbis_File reference{}; + Require(ov_open_callbacks(&referenceSource, &reference, nullptr, 0, HostSeekableCallbacks) == 0); + + for (int round = 0; round < 6; ++round) { + float** pcm = nullptr; + float** hostPcm = nullptr; + int guestStream = -1; + int hostStream = -1; + const std::int64_t got = sceVorbisDecReadFloat(vf, &pcm, 300, &guestStream); + const long expected = ov_read_float(&reference, &hostPcm, 300, &hostStream); + Require(got == expected && got > 0 && got <= 300 && guestStream == hostStream && guestStream == 0); + for (int channel = 0; channel < FirstChannels; ++channel) { + Require(std::memcmp(pcm[channel], hostPcm[channel], static_cast(got) * sizeof(float)) == 0); + } + } + Require(sceVorbisDecReadFloat(vf, nullptr, 50, nullptr) == ov_read_float(&reference, nullptr, 50, nullptr)); + RequireSameTells(vf, reference); + + FilterLog log; + std::int64_t total = 0; + for (;;) { + std::array guestBytes{}; + std::array hostBytes{}; + int guestStream = -1; + int hostStream = -1; + const std::int64_t got = sceVorbisDecReadFilter(vf, guestBytes.data(), static_cast(guestBytes.size()), 0, 2, 1, &guestStream, GuestHalve, &log); + const long expected = ov_read_filter(&reference, hostBytes.data(), static_cast(hostBytes.size()), 0, 2, 1, &hostStream, HostHalve, nullptr); + Require(got == expected && got >= 0); + if (got == 0) break; + Require(guestStream == hostStream && std::memcmp(guestBytes.data(), hostBytes.data(), static_cast(got)) == 0); + total += got; + } + Require(total > 0 && log.calls > 0 && log.samples > 0 && log.firstChannels == FirstChannels && log.lastChannels == SecondChannels); + + Require(sceVorbisDecPcmSeek(vf, 0) == 0 && ov_pcm_seek(&reference, 0) == 0); + std::array filtered{}; + std::array plain{}; + int bitstream = -1; + const int calls = log.calls; + const std::int64_t got = sceVorbisDecReadFilter(vf, filtered.data(), static_cast(filtered.size()), 0, 2, 1, &bitstream, nullptr, &log); + Require(got > 0 && got == ov_read(&reference, plain.data(), static_cast(plain.size()), 0, 2, 1, &bitstream)); + Require(std::memcmp(filtered.data(), plain.data(), static_cast(got)) == 0 && log.calls == calls); + Require(sceVorbisDecClear(vf) == 0 && source.closes == 1 && ov_clear(&reference) == 0); + Require(guest.Untouched()); +} + +void TestHalfrate(const std::vector& stream) { + GuestFile guest; + void* vf = guest.Get(); + Source source{&stream}; + Require(sceVorbisDecOpenCallbacks(&source, vf, nullptr, 0, SeekableCallbacks) == 0); + Source referenceSource{&stream}; + OggVorbis_File reference{}; + Require(ov_open_callbacks(&referenceSource, &reference, nullptr, 0, HostSeekableCallbacks) == 0); + + Require(sceVorbisDecHalfrateP(vf) == 0); + Require(RequireSameRead(vf, reference) > 0 && RequireSameRead(vf, reference) > 0); + Require(sceVorbisDecHalfrate(vf, 1) == 0 && ov_halfrate(&reference, 1) == 0); + Require(sceVorbisDecHalfrateP(vf) == 1 && ov_halfrate_p(&reference) == 1); + RequireSameTells(vf, reference); + const std::int64_t half = DecodeTogether(vf, reference); + Require(half > 0 && half < DecodedBytes / 2); + RequireSameTells(vf, reference); + Require(sceVorbisDecPcmTell(vf) > FirstSamples); + Require(sceVorbisDecHalfrate(vf, 0) == 0 && sceVorbisDecHalfrateP(vf) == 0); + Require(sceVorbisDecClear(vf) == 0 && source.closes == 1 && ov_clear(&reference) == 0); + Require(guest.Untouched()); +} + +void TestCrossLap(const std::vector& stream) { + GuestFile first; + GuestFile second; + GuestFile closed; + GuestFile otherClosed; + Source firstSource{&stream}; + Source secondSource{&stream}; + Require(sceVorbisDecOpenCallbacks(&secondSource, second.Get(), nullptr, 0, SeekableCallbacks) == 0); + firstSource.probe = second.Get(); + Require(sceVorbisDecOpenCallbacks(&firstSource, first.Get(), nullptr, 0, SeekableCallbacks) == 0); + Require(firstSource.probed == 2); + Source firstReferenceSource{&stream}; + Source secondReferenceSource{&stream}; + OggVorbis_File firstReference{}; + OggVorbis_File secondReference{}; + Require(ov_open_callbacks(&firstReferenceSource, &firstReference, nullptr, 0, HostSeekableCallbacks) == 0); + Require(ov_open_callbacks(&secondReferenceSource, &secondReference, nullptr, 0, HostSeekableCallbacks) == 0); + + Require(RequireSameRead(first.Get(), firstReference) > 0 && RequireSameRead(first.Get(), firstReference) > 0); + Require(sceVorbisDecPcmSeek(second.Get(), FirstSamples + 2000) == 0 && ov_pcm_seek(&secondReference, FirstSamples + 2000) == 0); + Require(sceVorbisDecCrossLap(first.Get(), second.Get()) == 0 && ov_crosslap(&firstReference, &secondReference) == 0); + for (int round = 0; round < 3; ++round) { + Require(RequireSameRead(second.Get(), secondReference) > 0); + Require(RequireSameRead(first.Get(), firstReference) > 0); + } + RequireSameTells(first.Get(), firstReference); + RequireSameTells(second.Get(), secondReference); + Require(sceVorbisDecCrossLap(first.Get(), first.Get()) == 0); + Require(sceVorbisDecCrossLap(first.Get(), closed.Get()) == OV_EINVAL && sceVorbisDecCrossLap(closed.Get(), second.Get()) == OV_EINVAL); + Require(sceVorbisDecCrossLap(closed.Get(), otherClosed.Get()) == OV_EINVAL && sceVorbisDecCrossLap(closed.Get(), closed.Get()) == 0); + Require(sceVorbisDecCrossLap(nullptr, nullptr) == 0); + OggVorbis_File zeroed{}; + Require(sceVorbisDecCrossLap(closed.Get(), nullptr) == OV_EINVAL && ov_crosslap(&zeroed, nullptr) == OV_EINVAL); + Require(Throws([&] { sceVorbisDecCrossLap(first.Get(), nullptr); })); + Require(Throws([&] { sceVorbisDecCrossLap(nullptr, closed.Get()); })); + Require(sceVorbisDecClear(first.Get()) == 0 && sceVorbisDecClear(second.Get()) == 0); + Require(firstSource.closes == 1 && secondSource.closes == 1); + Require(ov_clear(&firstReference) == 0 && ov_clear(&secondReference) == 0); + Require(first.Untouched() && second.Untouched() && closed.Untouched() && otherClosed.Untouched()); +} + +void TestNeverOpened() { + GuestFile guest; + void* vf = guest.Get(); + Require(sceVorbisDecStreams(vf) == 0 && sceVorbisDecSeekable(vf) == 0); + Require(sceVorbisDecSerialNumber(vf, -1) == 0 && sceVorbisDecSerialNumber(vf, 0) == 0 && sceVorbisDecSerialNumber(vf, 3) == 0); + Require(sceVorbisDecBitrate(vf, -1) == OV_EINVAL && sceVorbisDecBitrate(vf, 0) == OV_EINVAL && sceVorbisDecBitrateInstant(vf) == OV_EINVAL); + Require(sceVorbisDecRawTotal(vf, -1) == OV_EINVAL && sceVorbisDecPcmTotal(vf, 0) == OV_EINVAL && sceVorbisDecTimeTotal(vf, -1) == OV_EINVAL); + Require(sceVorbisDecRawSeek(vf, 0) == OV_EINVAL && sceVorbisDecPcmSeek(vf, 0) == OV_EINVAL && sceVorbisDecPcmSeekPage(vf, 0) == OV_EINVAL); + Require(sceVorbisDecTimeSeek(vf, 0.0) == OV_EINVAL && sceVorbisDecTimeSeekPage(vf, 0.0) == OV_EINVAL); + Require(sceVorbisDecRawSeekLap(vf, 0) == OV_EINVAL && sceVorbisDecPcmSeekLap(vf, 0) == OV_EINVAL && sceVorbisDecPcmSeekPageLap(vf, 0) == OV_EINVAL); + Require(sceVorbisDecTimeSeekLap(vf, 0.0) == OV_EINVAL && sceVorbisDecTimeSeekPageLap(vf, 0.0) == OV_EINVAL); + Require(sceVorbisDecRawTell(vf) == OV_EINVAL && sceVorbisDecPcmTell(vf) == OV_EINVAL && sceVorbisDecTimeTell(vf) == OV_EINVAL); + Require(sceVorbisDecInfo(vf, -1) == nullptr && sceVorbisDecInfo(vf, 0) == nullptr); + Require(sceVorbisDecComment(vf, -1) == nullptr && sceVorbisDecComment(vf, 0) == nullptr); + std::array buffer{}; + int bitstream = 7; + Require(sceVorbisDecRead(vf, buffer.data(), static_cast(buffer.size()), 0, 2, 1, &bitstream) == OV_EINVAL && bitstream == 7); + float** pcm = nullptr; + Require(sceVorbisDecReadFloat(vf, &pcm, 64, &bitstream) == OV_EINVAL && pcm == nullptr && bitstream == 7); + FilterLog log; + Require(sceVorbisDecReadFilter(vf, buffer.data(), static_cast(buffer.size()), 0, 2, 1, &bitstream, GuestHalve, &log) == OV_EINVAL); + Require(log.calls == 0 && bitstream == 7); + Require(sceVorbisDecHalfrate(vf, 1) == OV_EINVAL && sceVorbisDecHalfrateP(vf) == OV_EINVAL); + Require(sceVorbisDecClear(vf) == 0 && sceVorbisDecClear(nullptr) == 0); + Require(guest.Untouched()); + + Source source; + Require(Throws([&] { sceVorbisDecOpenCallbacks(&source, nullptr, nullptr, 0, SeekableCallbacks); })); + Require(Throws([] { sceVorbisDecStreams(nullptr); })); + Require(Throws([] { sceVorbisDecInfo(nullptr, 0); })); + Require(Throws([] { sceVorbisDecPcmSeek(nullptr, 0); })); +} + +void TestFailedOpens(const std::vector& stream) { + GuestFile guest; + void* vf = guest.Get(); + std::vector noise(8192); + std::uint32_t state = 0x2545f491; + for (auto& value : noise) { + state = state * 1664525u + 1013904223u; + value = static_cast(state >> 24); + } + Source noiseSource{&noise}; + Require(sceVorbisDecOpenCallbacks(&noiseSource, vf, nullptr, 0, SeekableCallbacks) == OV_ENOTVORBIS); + Require(noiseSource.closes == 0 && sceVorbisDecStreams(vf) == 0 && sceVorbisDecInfo(vf, 0) == nullptr); + Require(sceVorbisDecClear(vf) == 0 && noiseSource.closes == 0); + + Source untold{&stream}; + constexpr GuestCallbacks NoTell{GuestRead, GuestSeek, GuestClose, nullptr}; + Require(sceVorbisDecOpenCallbacks(&untold, vf, nullptr, 0, NoTell) == OV_EINVAL); + Require(untold.closes == 0 && sceVorbisDecStreams(vf) == 0 && sceVorbisDecClear(vf) == 0 && untold.closes == 0); + + constexpr GuestCallbacks NoRead{nullptr, GuestSeek, GuestClose, GuestTell}; + Source unread{&stream}; + OggVorbis_File reference{}; + const ov_callbacks hostNoRead{nullptr, HostSeek, HostClose, HostTell}; + Require(sceVorbisDecOpenCallbacks(&unread, vf, nullptr, 0, NoRead) == ov_open_callbacks(&unread, &reference, nullptr, 0, hostNoRead)); + Require(unread.closes == 0 && sceVorbisDecStreams(vf) == 0); + + Require(sceVorbisDecOpenCallbacks(nullptr, vf, nullptr, 0, SeekableCallbacks) == OV_ENOTVORBIS); + + Source previous{&stream}; + Source next{&stream}; + Require(sceVorbisDecOpenCallbacks(&previous, vf, nullptr, 0, SeekableCallbacks) == 0); + Require(sceVorbisDecOpenCallbacks(&next, vf, nullptr, 0, StreamingCallbacks) == 0); + Require(previous.closes == 0 && sceVorbisDecSeekable(vf) == 0 && sceVorbisDecStreams(vf) == 1); + Require(sceVorbisDecClear(vf) == 0 && previous.closes == 0 && next.closes == 1); + + Source unsized{&stream}; + Require(sceVorbisDecOpenCallbacks(&unsized, vf, nullptr, std::int64_t{1} << 32, SeekableCallbacks) == 0); + Require(sceVorbisDecStreams(vf) == 2 && sceVorbisDecClear(vf) == 0 && unsized.closes == 1); + + const auto* initial = reinterpret_cast(stream.data()); + const auto size = static_cast(stream.size()); + Require(sceVorbisDecOpenCallbacks(nullptr, vf, initial, size, SeekableCallbacks) == 0); + Require(ov_open_callbacks(nullptr, &reference, initial, static_cast(size), HostSeekableCallbacks) == 0); + Require(sceVorbisDecSeekable(vf) == 0 && DecodeTogether(vf, reference) == DecodedBytes); + Require(sceVorbisDecClear(vf) == 0 && ov_clear(&reference) == 0); + Require(guest.Untouched()); +} + +void TestThrowingCallbacks(const std::vector& stream) { + GuestFile guest; + Source source{&stream}; + Require(sceVorbisDecOpenCallbacks(&source, guest.Get(), nullptr, 0, SeekableCallbacks) == 0); + std::array buffer{}; + int bitstream = -1; + source.throwOnRead = true; + bool threw = false; + for (int call = 0; call < 1024 && !threw; ++call) { + threw = Throws([&] { sceVorbisDecRead(guest.Get(), buffer.data(), static_cast(buffer.size()), 0, 2, 1, &bitstream); }); + } + Require(threw); + source.throwOnRead = false; + source.throwOnClose = true; + Require(Throws([&] { sceVorbisDecClear(guest.Get()); })); + Require(source.closes == 1 && sceVorbisDecStreams(guest.Get()) == 0); + Require(sceVorbisDecClear(guest.Get()) == 0 && source.closes == 1); + Require(guest.Untouched()); +} + } int main() { - Require(Throws([] { sceVorbisDecOpenCallbacks(); }), "sceVorbisDecOpenCallbacks must throw"); - Require(Throws([] { sceVorbisDecClear(); }), "sceVorbisDecClear must throw"); - Require(Throws([] { sceVorbisDecBitrate(); }), "sceVorbisDecBitrate must throw"); - Require(Throws([] { sceVorbisDecBitrateInstant(); }), "sceVorbisDecBitrateInstant must throw"); - Require(Throws([] { sceVorbisDecStreams(); }), "sceVorbisDecStreams must throw"); - Require(Throws([] { sceVorbisDecSeekable(); }), "sceVorbisDecSeekable must throw"); - Require(Throws([] { sceVorbisDecSerialNumber(); }), "sceVorbisDecSerialNumber must throw"); - Require(Throws([] { sceVorbisDecRawTotal(); }), "sceVorbisDecRawTotal must throw"); - Require(Throws([] { sceVorbisDecPcmTotal(); }), "sceVorbisDecPcmTotal must throw"); - Require(Throws([] { sceVorbisDecTimeTotal(); }), "sceVorbisDecTimeTotal must throw"); - Require(Throws([] { sceVorbisDecRawSeek(); }), "sceVorbisDecRawSeek must throw"); - Require(Throws([] { sceVorbisDecPcmSeek(); }), "sceVorbisDecPcmSeek must throw"); - Require(Throws([] { sceVorbisDecPcmSeekPage(); }), "sceVorbisDecPcmSeekPage must throw"); - Require(Throws([] { sceVorbisDecTimeSeek(); }), "sceVorbisDecTimeSeek must throw"); - Require(Throws([] { sceVorbisDecTimeSeekPage(); }), "sceVorbisDecTimeSeekPage must throw"); - Require(Throws([] { sceVorbisDecRawSeekLap(); }), "sceVorbisDecRawSeekLap must throw"); - Require(Throws([] { sceVorbisDecPcmSeekLap(); }), "sceVorbisDecPcmSeekLap must throw"); - Require(Throws([] { sceVorbisDecPcmSeekPageLap(); }), "sceVorbisDecPcmSeekPageLap must throw"); - Require(Throws([] { sceVorbisDecTimeSeekLap(); }), "sceVorbisDecTimeSeekLap must throw"); - Require(Throws([] { sceVorbisDecTimeSeekPageLap(); }), "sceVorbisDecTimeSeekPageLap must throw"); - Require(Throws([] { sceVorbisDecRawTell(); }), "sceVorbisDecRawTell must throw"); - Require(Throws([] { sceVorbisDecPcmTell(); }), "sceVorbisDecPcmTell must throw"); - Require(Throws([] { sceVorbisDecTimeTell(); }), "sceVorbisDecTimeTell must throw"); - Require(Throws([] { sceVorbisDecInfo(); }), "sceVorbisDecInfo must throw"); - Require(Throws([] { sceVorbisDecComment(); }), "sceVorbisDecComment must throw"); - Require(Throws([] { sceVorbisDecRead(); }), "sceVorbisDecRead must throw"); - Require(Throws([] { sceVorbisDecReadFloat(); }), "sceVorbisDecReadFloat must throw"); - Require(Throws([] { sceVorbisDecReadFilter(); }), "sceVorbisDecReadFilter must throw"); - Require(Throws([] { sceVorbisDecCrossLap(); }), "sceVorbisDecCrossLap must throw"); - Require(Throws([] { sceVorbisDecHalfrate(); }), "sceVorbisDecHalfrate must throw"); - Require(Throws([] { sceVorbisDecHalfrateP(); }), "sceVorbisDecHalfrateP must throw"); - Require(Throws([] { sceVorbisDecGetDecodePriority(); }), "sceVorbisDecGetDecodePriority must throw"); - Require(Throws([] { sceVorbisDecSetDecodePriority(); }), "sceVorbisDecSetDecodePriority must throw"); - Require(Throws([] { sceVorbisDecGetLastDecodeError(); }), "sceVorbisDecGetLastDecodeError must throw"); + std::vector stream; + EncodeLink(stream, FirstSerial, FirstChannels, FirstRate, FirstSamples, "first"); + EncodeLink(stream, SecondSerial, SecondChannels, SecondRate, SecondSamples, "second"); + + TestSeekable(stream); + TestStreaming(stream); + TestFloatAndFilter(stream); + TestHalfrate(stream); + TestCrossLap(stream); + TestNeverOpened(); + TestFailedOpens(stream); + TestThrowingCallbacks(stream); + + Require(Throws([] { sceVorbisDecGetDecodePriority(); })); + Require(Throws([] { sceVorbisDecSetDecodePriority(); })); + Require(Throws([] { sceVorbisDecGetLastDecodeError(); })); std::puts("All VorbisDec guest tests passed"); return 0; diff --git a/core/libs/tests/GuestVrServiceDialog.cpp b/core/libs/tests/GuestVrServiceDialog.cpp new file mode 100644 index 0000000000..95f074d84c --- /dev/null +++ b/core/libs/tests/GuestVrServiceDialog.cpp @@ -0,0 +1,63 @@ +#include "prx/libSceVrServiceDialog/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotInitialized = static_cast(0x80B80003); +constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); +constexpr int kErrNotFinished = static_cast(0x80B80005); +constexpr int kErrArgNull = static_cast(0x80B8000D); +constexpr int kResultUserCanceled = 1; + +} + +int main() { + std::int64_t param = 0; + std::int32_t result[2] = {-1, -1}; + + Require(sceVrServiceDialogTerminate() == kErrNotInitialized); + Require(sceVrServiceDialogOpen(¶m) == kErrNotInitialized); + Require(sceVrServiceDialogClose() == kErrNotInitialized); + Require(sceVrServiceDialogGetResult(result) == kErrNotInitialized); + Require(sceVrServiceDialogGetStatus() == kStatusNone); + Require(sceVrServiceDialogUpdateStatus() == kStatusNone); + + Require(sceVrServiceDialogInitialize() == 0); + Require(sceVrServiceDialogInitialize() == kErrAlreadyInitialized); + Require(sceVrServiceDialogGetStatus() == kStatusInitialized); + Require(sceVrServiceDialogUpdateStatus() == kStatusInitialized); + + Require(sceVrServiceDialogOpen(nullptr) == kErrArgNull); + Require(sceVrServiceDialogGetResult(result) == kErrNotFinished); + + Require(sceVrServiceDialogOpen(¶m) == 0); + Require(sceVrServiceDialogGetStatus() == kStatusFinished); + Require(sceVrServiceDialogUpdateStatus() == kStatusFinished); + + Require(sceVrServiceDialogGetResult(nullptr) == kErrArgNull); + Require(sceVrServiceDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(result[1] == -1); + + Require(sceVrServiceDialogClose() == 0); + Require(sceVrServiceDialogTerminate() == 0); + Require(sceVrServiceDialogTerminate() == kErrNotInitialized); + Require(sceVrServiceDialogGetStatus() == kStatusNone); + Require(sceVrServiceDialogUpdateStatus() == kStatusNone); + + Require(sceVrServiceDialogInitialize() == 0); + Require(sceVrServiceDialogUpdateStatus() == kStatusInitialized); + Require(sceVrServiceDialogClose() == 0); + Require(sceVrServiceDialogUpdateStatus() == kStatusFinished); + result[0] = -1; + Require(sceVrServiceDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(sceVrServiceDialogTerminate() == 0); +} diff --git a/core/libs/tests/GuestVrSetupDialog.cpp b/core/libs/tests/GuestVrSetupDialog.cpp index 56a48ccdd0..3297365257 100644 --- a/core/libs/tests/GuestVrSetupDialog.cpp +++ b/core/libs/tests/GuestVrSetupDialog.cpp @@ -1,16 +1,8 @@ +#include "prx/libSceVrSetupDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include - -extern "C" { -int APS5_VABI sceVrSetupDialogInitialize(void); -int APS5_VABI sceVrSetupDialogOpen(const void* param); -int APS5_VABI sceVrSetupDialogUpdateStatus(void); -int APS5_VABI sceVrSetupDialogGetStatus(void); -int APS5_VABI sceVrSetupDialogGetResult(void* result); -int APS5_VABI sceVrSetupDialogClose(void); -int APS5_VABI sceVrSetupDialogTerminate(void); -} +#include "prx/libSceCommonDialog/CommonDialog.hpp" namespace { @@ -19,22 +11,25 @@ void Require(bool value) { if (!value) std::abort(); } constexpr int kStatusNone = 0; constexpr int kStatusInitialized = 1; constexpr int kStatusFinished = 3; -constexpr int kErrNotInitialized = static_cast(0x80B80003); -constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); -constexpr int kErrNotFinished = static_cast(0x80B80005); -constexpr int kErrArgNull = static_cast(0x80B8000D); +constexpr int kErrNotInitialized = static_cast(0x8A7C0001); +constexpr int kErrAlreadyInitialized = static_cast(0x8A7C0002); +constexpr int kErrNotFinished = static_cast(0x8A7C0005); +constexpr int kErrArgNull = static_cast(0x8A7C0003); constexpr int kResultUserCanceled = 1; } int main() { - std::int64_t param = 0; + VrSetupDialogParam param{}; + param.size = sizeof(param); std::int32_t result[2] = {-1, -1}; Require(sceVrSetupDialogTerminate() == kErrNotInitialized); Require(sceVrSetupDialogOpen(¶m) == kErrNotInitialized); Require(sceVrSetupDialogClose() == kErrNotInitialized); + Require(sceVrSetupDialogGetResult(nullptr) == kErrArgNull); Require(sceVrSetupDialogGetResult(result) == kErrNotInitialized); + Require(result[0] == -1 && result[1] == -1); Require(sceVrSetupDialogGetStatus() == kStatusNone); Require(sceVrSetupDialogUpdateStatus() == kStatusNone); @@ -43,9 +38,20 @@ int main() { Require(sceVrSetupDialogGetStatus() == kStatusInitialized); Require(sceVrSetupDialogUpdateStatus() == kStatusInitialized); - Require(sceVrSetupDialogOpen(nullptr) == kErrArgNull); + Require(sceVrSetupDialogOpen(nullptr) == static_cast(0x8A7C0003u)); Require(sceVrSetupDialogGetResult(result) == kErrNotFinished); + Require(result[0] == kResultUserCanceled && result[1] == -1); + param.size = 55; + Require(sceVrSetupDialogOpen(¶m) == static_cast(0x8A7C0003u)); + param.size = sizeof(param); + param.reserved8 = 1; + Require(sceVrSetupDialogOpen(¶m) == static_cast(0x8A7C0003u)); + param.reserved8 = 0; + param.reserved9 = 1; + Require(sceVrSetupDialogOpen(¶m) == static_cast(0x8A7C0003u)); + param.reserved9 = 0; + param.data[45] = 255; Require(sceVrSetupDialogOpen(¶m) == 0); Require(sceVrSetupDialogGetStatus() == kStatusFinished); Require(sceVrSetupDialogUpdateStatus() == kStatusFinished); @@ -64,9 +70,8 @@ int main() { Require(sceVrSetupDialogInitialize() == 0); Require(sceVrSetupDialogUpdateStatus() == kStatusInitialized); Require(sceVrSetupDialogClose() == 0); - Require(sceVrSetupDialogUpdateStatus() == kStatusFinished); - result[0] = -1; - Require(sceVrSetupDialogGetResult(result) == 0); - Require(result[0] == kResultUserCanceled); + Require(sceVrSetupDialogUpdateStatus() == kStatusInitialized); + Require(sceVrSetupDialogGetResult(result) == kErrNotFinished); + Require(result[0] == kResultUserCanceled && result[1] == -1); Require(sceVrSetupDialogTerminate() == 0); } diff --git a/core/libs/tests/GuestVrTracker2.cpp b/core/libs/tests/GuestVrTracker2.cpp index f8d5899a66..a1e4b0693a 100644 --- a/core/libs/tests/GuestVrTracker2.cpp +++ b/core/libs/tests/GuestVrTracker2.cpp @@ -1,29 +1,30 @@ -#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceVrTracker2/include/Api.hpp" +#include "prx/libkernel/libkernel.hpp" +#include "ProcessParametersFixture.hpp" +#include #include #include #include #include #include +#include +#include +#include #include +#include +#include -extern "C" { -std::int32_t APS5_VABI VrTracker2NotInitialized(); -std::int32_t APS5_VABI sceVrTracker2Finalize(); -std::int32_t APS5_VABI VrTracker2ClearWideResult(void* result); -std::int32_t APS5_VABI VrTracker2ClearNarrowResult(void* result); -std::int32_t APS5_VABI VrTracker2CheckSecondThenReportNotInitialized(const void* first, const void* second); -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 sceVrTracker2QueryMemory(const void* parameter, void* requirement); -std::int32_t APS5_VABI sceVrTracker2Initialize(); -} +static_assert(sizeof(VrTracker2InitializeParameter) == 72); namespace { +alignas(16) std::array process{}; + constexpr std::int32_t NotInitialized = static_cast(0x8A740001u); constexpr std::int32_t InvalidArgument = static_cast(0x8A740006u); +constexpr std::uint32_t TrackerMemory = 32u << 20; +constexpr std::size_t WorkMemoryTail = 64u << 10; void Require(bool condition, const char* message) { if (!condition) { @@ -54,6 +55,118 @@ bool Throws(TCall call) { return false; } +VrTracker2InitializeParameter TrackerParameter(std::vector& memory) { + VrTracker2InitializeParameter parameter{}; + parameter.size = sizeof(parameter); + parameter.threadPriorities[0] = 256; + parameter.threadPriorities[1] = 767; + parameter.threadAffinityMasks[0] = 0x1fff; + parameter.workMemory = memory.data(); + parameter.workMemorySize = static_cast(memory.size()); + parameter.workMemoryAlignment = 0x10000; + return parameter; +} + +bool WorkMemoryCleared(const std::vector& memory) { + return AllEqual(memory.data(), memory.data() + TrackerMemory, 0) && AllEqual(memory.data() + TrackerMemory, memory.data() + memory.size(), 0xAA); +} + +bool WorkMemoryUntouched(const std::vector& memory) { + return AllEqual(memory.data(), memory.data() + memory.size(), 0xAA); +} + +std::int32_t InitializeFresh(std::vector& memory, const VrTracker2InitializeParameter& parameter) { + std::fill(memory.begin(), memory.end(), 0xAA); + return sceVrTracker2Initialize(¶meter); +} + +void CheckInitializeValidation(std::vector& memory) { + const auto valid = TrackerParameter(memory); + Require(sceVrTracker2Initialize(nullptr) == InvalidArgument, "initialize null"); + for (const std::uint32_t size : {0u, 71u, 73u}) { + auto parameter = valid; + parameter.size = size; + parameter.mode = 1000; + Require(InitializeFresh(memory, parameter) == InvalidArgument, "initialize size before mode"); + } + for (const std::uint32_t mode : {1u, 999u, 1002u, 0xffffffffu}) { + auto parameter = valid; + parameter.mode = mode; + Require(InitializeFresh(memory, parameter) == InvalidArgument, "initialize mode"); + } + auto parameter = valid; + parameter.workMemory = nullptr; + Require(InitializeFresh(memory, parameter) == InvalidArgument, "initialize work memory null"); + parameter = valid; + parameter.workMemorySize = TrackerMemory - 1; + Require(InitializeFresh(memory, parameter) == InvalidArgument, "initialize work memory size"); + parameter = valid; + parameter.workMemoryAlignment = 0xffff; + Require(InitializeFresh(memory, parameter) == InvalidArgument, "initialize alignment"); + for (std::size_t index = 0; index < sizeof(parameter.reserved); ++index) { + parameter = valid; + parameter.reserved[index] = 1; + Require(InitializeFresh(memory, parameter) == InvalidArgument, "initialize reserved byte"); + } + for (std::size_t index = 0; index < 2; ++index) { + for (const std::int32_t priority : {255, 768, -1}) { + parameter = valid; + parameter.threadPriorities[index] = priority; + Require(InitializeFresh(memory, parameter) == InvalidArgument, "initialize thread priority"); + } + for (const std::uint64_t mask : {std::uint64_t{0x2000}, std::uint64_t{0x3fff}, std::uint64_t{1} << 63}) { + parameter = valid; + parameter.threadAffinityMasks[index] = mask; + Require(InitializeFresh(memory, parameter) == InvalidArgument, "initialize affinity mask"); + } + } + Require(WorkMemoryUntouched(memory), "initialize validation leaves work memory"); +} + +void CheckInitializeTracking(std::vector& memory, std::uint32_t sdkVersion) { + const auto valid = TrackerParameter(memory); + Require(InitializeFresh(memory, valid) == -1, "initialize tracking start result"); + Require(WorkMemoryCleared(memory), "initialize clears tracker memory"); + Require(sceVrTracker2Finalize() == NotInitialized, "initialize failure leaves module uninitialized"); + Require(VrTracker2NotInitialized() == NotInitialized, "initialize failure keeps uninitialized answers"); + Require(InitializeFresh(memory, valid) == -1, "initialize retry after failure"); + auto parameter = valid; + parameter.workMemoryAlignment = 0x10001; + parameter.option = 0xffffffffu; + parameter.coordinateSystem = 2; + parameter.disableOption = 1; + parameter.threadAffinityMasks[1] = 0x1fff; + Require(InitializeFresh(memory, parameter) == -1, "initialize late checks follow tracking"); + for (const std::int8_t optionMode : {std::int8_t{-1}, std::int8_t{0}}) { + parameter = valid; + parameter.optionMode = optionMode; + Require(InitializeFresh(memory, parameter) == -1, "initialize accepted option mode"); + } + for (const std::int8_t optionMode : {std::int8_t{2}, std::int8_t{-2}, std::int8_t{127}}) { + parameter = valid; + parameter.optionMode = optionMode; + Require(InitializeFresh(memory, parameter) == InvalidArgument, "initialize option mode"); + Require(WorkMemoryCleared(memory), "initialize option mode follows clearing"); + } + parameter = valid; + parameter.optionMode = 1; + Require(InitializeFresh(memory, parameter) == (sdkVersion >= 0x7000000 ? -1 : InvalidArgument), "initialize SDK option mode"); + Require(sceVrTracker2Finalize() == NotInitialized, "initialize leaves module uninitialized"); +} + +std::filesystem::path EnterApplicationRoot() { + std::random_device device; + std::filesystem::path root; + do { + root = std::filesystem::temp_directory_path() / + ("guest_vr_tracker2_" + std::to_string(getpid_nid_postfix()) + "_" + std::to_string(device())); + } while (!std::filesystem::create_directory(root)); + std::filesystem::create_directories(root / "app0" / "sce_sys"); + std::ofstream(root / "app0" / "sce_sys" / "param.json") << "{\n \"contentId\": \"IV0000-ABCD00000_00-VRTRACKER2000000\",\n \"localizedParameters\": {\"defaultLanguage\": \"en-US\", \"en-US\": {\"titleName\": \"VrTracker2\"}},\n \"titleId\": \"ABCD00000\"\n}\n"; + std::filesystem::current_path(root); + return root; +} + } int main() { @@ -94,22 +207,55 @@ int main() { parameter.back() = 0xAA; std::array requirement{}; requirement.fill(0xAA); - Require(sceVrTracker2QueryMemory(parameter.data(), requirement.data()) == 0, "query return"); + Require(sceVrTracker2QueryMemory(reinterpret_cast(parameter.data()), reinterpret_cast(requirement.data())) == 0, "query return"); const std::array fields{0x20, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x01, 0}; Require(std::memcmp(requirement.data(), fields.data(), fields.size()) == 0, "query fields"); Require(AllEqual(requirement.data() + 12, requirement.data() + 32, 0) && AllEqual(requirement.data() + 32, requirement.data() + 40, 0xAA), "query writes 32 bytes"); - Require(sceVrTracker2QueryMemory(nullptr, requirement.data()) == InvalidArgument, "query null parameter"); - Require(sceVrTracker2QueryMemory(parameter.data(), nullptr) == InvalidArgument, "query null requirement"); + Require(sceVrTracker2QueryMemory(nullptr, reinterpret_cast(requirement.data())) == InvalidArgument, "query null parameter"); + Require(sceVrTracker2QueryMemory(reinterpret_cast(parameter.data()), nullptr) == InvalidArgument, "query null requirement"); for (std::size_t offset = 4; offset < 32; ++offset) { auto rejected = parameter; rejected[offset] = 1; requirement.fill(0xAA); - Require(sceVrTracker2QueryMemory(rejected.data(), requirement.data()) == InvalidArgument, "query reserved byte"); + Require(sceVrTracker2QueryMemory(reinterpret_cast(rejected.data()), reinterpret_cast(requirement.data())) == InvalidArgument, "query reserved byte"); Require(AllEqual(requirement.data(), requirement.data() + 40, 0xAA), "query rejection leaves output"); } + const std::uint64_t processSize = process.size(); + const std::uint32_t magic = 0x4942524f; + std::memcpy(process.data(), &processSize, sizeof(processSize)); + std::memcpy(process.data() + 8, &magic, sizeof(magic)); + Require(ProcessParametersFixture::Install(process.data(), process.size()), "process parameters"); + for (const std::uint32_t version : {0u, 0xffffffu, 0x1000000u, 0x7000000u}) { + std::memcpy(process.data() + 0x10, &version, sizeof(version)); + for (const std::uint32_t mode : {1000u, 1001u}) { + std::memcpy(parameter.data() + 4, &mode, sizeof(mode)); + requirement.fill(0xAA); + const auto result = sceVrTracker2QueryMemory(reinterpret_cast(parameter.data()), reinterpret_cast(requirement.data())); + if (version < 0x1000000) { + Require(result == InvalidArgument, "query SDK restriction"); + Require(AllEqual(requirement.data(), requirement.data() + requirement.size(), 0xAA), "SDK rejection preserves output"); + } else { + Require(result == 0, "query alternate mode"); + std::uint32_t memorySize; + std::memcpy(&memorySize, requirement.data() + 4, sizeof(memorySize)); + Require(memorySize == (mode == 1000 ? 7u << 20 : 3u << 20), "query alternate memory size"); + Require(AllEqual(requirement.data() + 12, requirement.data() + 32, 0), "query alternate reserved fields"); + Require(AllEqual(requirement.data() + 32, requirement.data() + 40, 0xAA), "query alternate output boundary"); + } + } + } parameter[0] = 0x21; - Require(sceVrTracker2QueryMemory(parameter.data(), requirement.data()) == InvalidArgument, "query size"); + Require(sceVrTracker2QueryMemory(reinterpret_cast(parameter.data()), reinterpret_cast(requirement.data())) == InvalidArgument, "query size"); - Require(Throws([] { sceVrTracker2Initialize(); }), "unknown export"); + std::vector memory(TrackerMemory + WorkMemoryTail); + CheckInitializeValidation(memory); + const auto previous = std::filesystem::current_path(); + const auto root = EnterApplicationRoot(); + for (const std::uint32_t version : {0x6ffffffu, 0x7000000u}) { + std::memcpy(process.data() + 0x10, &version, sizeof(version)); + CheckInitializeTracking(memory, version); + } + std::filesystem::current_path(previous); + std::filesystem::remove_all(root); return 0; } diff --git a/core/libs/tests/GuestVrTrackerDevices.cpp b/core/libs/tests/GuestVrTrackerDevices.cpp new file mode 100644 index 0000000000..d34384fa7a --- /dev/null +++ b/core/libs/tests/GuestVrTrackerDevices.cpp @@ -0,0 +1,183 @@ +#include "prx/libSceVrTracker/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace { + +constexpr int NotInit = static_cast(0x81260801u); +constexpr int AlreadyInitialized = static_cast(0x81260802u); +constexpr int DeviceNotRegistered = static_cast(0x81260803u); +constexpr int DeviceAlreadyRegistered = static_cast(0x81260804u); +constexpr int ArgumentInvalid = static_cast(0x81260806u); +constexpr int NotExecuteGpuSubmit = static_cast(0x81260815u); +constexpr std::uint32_t Untouched = 0xa5a5a5a5u; +constexpr int Hmd = 0; +constexpr int Pad = 1; +constexpr int Move = 2; +constexpr int Gun = 3; + +struct QueryResult { + std::uint32_t words[16]; +}; + +std::uint8_t g_onion[16]; +std::uint8_t g_garlic[16]; +std::uint8_t g_work[16]; + +void Require(bool value) { if (!value) std::abort(); } + +template +void RequireThrows(TFunction function) { + try { function(); } catch (const std::runtime_error&) { return; } + std::abort(); +} + +QueryResult FreshResult() { + QueryResult result; + for (auto& word : result.words) word = Untouched; + return result; +} + +VrTrackerLegacy::InitParam ValidInit() { + VrTrackerLegacy::InitParam init{}; + init.size = sizeof(init); + init.direct_memory_onion_size = 0x400000; + init.direct_memory_onion_alignment = 0x10000; + init.direct_memory_garlic_size = 0x3000000; + init.direct_memory_garlic_alignment = 0x10000; + init.work_memory_size = 0x1000000; + init.work_memory_alignment = 0x10000; + init.direct_memory_onion = g_onion; + init.direct_memory_garlic = g_garlic; + init.work_memory = g_work; + return init; +} + +int Recalibrate(std::uint32_t deviceType, std::uint32_t size = sizeof(VrTrackerLegacy::RecalibrateParam)) { + VrTrackerLegacy::RecalibrateParam param{}; + param.size = size; + param.device_type = deviceType; + return sceVrTrackerRecalibrate(¶m); +} + +void CheckQueryMemory() { + VrTrackerLegacy::QueryMemoryParam query{sizeof(VrTrackerLegacy::QueryMemoryParam)}; + auto result = FreshResult(); + Require(sceVrTrackerQueryMemory(&query, &result) == 0); + Require(result.words[0] == Untouched); + Require(result.words[1] == 0x400000 && result.words[2] == 0x10000); + Require(result.words[3] == 0x3000000 && result.words[4] == 0x10000); + Require(result.words[5] == 0x1000000 && result.words[6] == 0x10000); + Require(result.words[7] == Untouched); + + query.calibration_settings.move_position = 1; + query.profile = 100; + Require(sceVrTrackerQueryMemory(&query, &result) == 0 && result.words[1] == 0x800000); + + auto rejected = FreshResult(); + VrTrackerLegacy::QueryMemoryParam bad = query; + bad.profile = 1; + Require(sceVrTrackerQueryMemory(&bad, &rejected) == ArgumentInvalid); + bad = query; + bad.calibration_settings.hmd_position = 1; + Require(sceVrTrackerQueryMemory(&bad, &rejected) == ArgumentInvalid); + bad = query; + bad.calibration_settings.pad_position = 2; + Require(sceVrTrackerQueryMemory(&bad, &rejected) == ArgumentInvalid); + bad = query; + bad.size = sizeof(VrTrackerLegacy::QueryMemoryParam) - 1; + Require(sceVrTrackerQueryMemory(&bad, &rejected) == ArgumentInvalid); + Require(sceVrTrackerQueryMemory(nullptr, &rejected) == ArgumentInvalid); + Require(sceVrTrackerQueryMemory(&query, nullptr) == ArgumentInvalid); + Require(rejected.words[1] == Untouched); +} + +void CheckBeforeInit() { + Require(sceVrTrackerTerm() == NotInit); + Require(sceVrTrackerRegisterDevice(Pad, 5) == NotInit); + Require(sceVrTrackerUnregisterDevice(5) == NotInit); + Require(Recalibrate(Pad) == NotInit); + Require(sceVrTrackerSetDurationUntilStatusNotTracking(Pad, 10) == NotInit); + Require(sceVrTrackerGpuWaitAndCpuProcess() == NotInit); +} + +void CheckInit() { + auto bad = ValidInit(); + bad.size = sizeof(VrTrackerLegacy::InitParam) - 8; + Require(sceVrTrackerInit(reinterpret_cast(&bad)) == ArgumentInvalid); + bad.size = 144; + RequireThrows([&] { sceVrTrackerInit(reinterpret_cast(&bad)); }); + bad = ValidInit(); + bad.calibration_settings.move_position = 1; + Require(sceVrTrackerInit(reinterpret_cast(&bad)) == ArgumentInvalid); + bad.direct_memory_onion_size = 0x800000; + bad.gpu_pipe_id = 7; + Require(sceVrTrackerInit(reinterpret_cast(&bad)) == ArgumentInvalid); + bad.gpu_pipe_id = 0; + bad.gpu_queue_id = 8; + Require(sceVrTrackerInit(reinterpret_cast(&bad)) == ArgumentInvalid); + bad.gpu_queue_id = 0; + bad.work_memory = nullptr; + Require(sceVrTrackerInit(reinterpret_cast(&bad)) == ArgumentInvalid); + bad.work_memory = g_work; + bad.direct_memory_garlic_alignment = 0x1000; + Require(sceVrTrackerInit(reinterpret_cast(&bad)) == ArgumentInvalid); + Require(sceVrTrackerInit(nullptr) == ArgumentInvalid); + Require(sceVrTrackerTerm() == NotInit); + + const auto init = ValidInit(); + Require(sceVrTrackerInit(reinterpret_cast(&init)) == 0); + Require(sceVrTrackerInit(reinterpret_cast(&init)) == AlreadyInitialized); + Require(sceVrTrackerInit(nullptr) == AlreadyInitialized); + Require(sceVrTrackerGpuWaitAndCpuProcess() == NotExecuteGpuSubmit); +} + +void CheckDevices() { + Require(sceVrTrackerRegisterDevice(Pad, 5) == 0); + Require(sceVrTrackerRegisterDevice(Pad, 6) == DeviceAlreadyRegistered); + Require(sceVrTrackerRegisterDevice2(Move, 7) == 0); + Require(sceVrTrackerRegisterDeviceInternal(Gun, 8, 5, 0) == ArgumentInvalid); + Require(sceVrTrackerRegisterDevice(4, 8) == ArgumentInvalid); + Require(sceVrTrackerRegisterDevice(-1, 8) == ArgumentInvalid); + Require(sceVrTrackerRegisterDevice(Hmd, 9) == 0); + + Require(Recalibrate(Pad) == 0); + Require(Recalibrate(Move) == 0); + Require(Recalibrate(Hmd) == DeviceNotRegistered); + Require(Recalibrate(Gun) == DeviceNotRegistered); + Require(Recalibrate(4) == ArgumentInvalid); + Require(Recalibrate(Pad, sizeof(VrTrackerLegacy::RecalibrateParam) + 4) == ArgumentInvalid); + Require(sceVrTrackerRecalibrate(nullptr) == ArgumentInvalid); + + Require(sceVrTrackerSetDurationUntilStatusNotTracking(Gun, 10) == 0); + Require(sceVrTrackerSetDurationUntilStatusNotTracking(4, 10) == ArgumentInvalid); + + Require(sceVrTrackerUnregisterDevice(5) == 0); + Require(sceVrTrackerUnregisterDevice(5) == ArgumentInvalid); + Require(sceVrTrackerUnregisterDevice(-1) == ArgumentInvalid); + Require(Recalibrate(Pad) == DeviceNotRegistered); + Require(sceVrTrackerRegisterDevice(Pad, 6) == 0); + Require(sceVrTrackerUnregisterDevice(9) == 0); +} + +void CheckTerm() { + Require(sceVrTrackerTerm() == 0); + Require(sceVrTrackerTerm() == NotInit); + Require(sceVrTrackerRegisterDevice(Gun, 8) == NotInit); + const auto init = ValidInit(); + Require(sceVrTrackerInit(reinterpret_cast(&init)) == 0); + Require(sceVrTrackerRegisterDevice(Move, 11) == DeviceAlreadyRegistered); + Require(sceVrTrackerTerm() == 0); +} + +} // namespace + +int main() { + CheckQueryMemory(); + CheckBeforeInit(); + CheckInit(); + CheckDevices(); + CheckTerm(); +} diff --git a/core/libs/tests/GuestWaitpid.cpp b/core/libs/tests/GuestWaitpid.cpp index 8ef90e7b19..9b958641cb 100644 --- a/core/libs/tests/GuestWaitpid.cpp +++ b/core/libs/tests/GuestWaitpid.cpp @@ -1,12 +1,9 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include -extern "C" { -int APS5_VABI wait_nid_postfix(int*); -int APS5_VABI waitpid_nid_postfix(int, int*, int); -int APS5_VABI getpid_nid_postfix(void); -int* APS5_VABI __error_nid_postfix(); -} + constexpr int noChild = 10; constexpr int invalidArgument = 22; constexpr int untouched = 0x5a5a5a5a; diff --git a/core/libs/tests/GuestWcsrtombs.cpp b/core/libs/tests/GuestWcsrtombs.cpp index 745e809903..b9139ceaa8 100644 --- a/core/libs/tests/GuestWcsrtombs.cpp +++ b/core/libs/tests/GuestWcsrtombs.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,9 +6,6 @@ #include #include -extern "C" std::size_t APS5_VABI wcsrtombs_nid_postfix(char*, const std::uint16_t**, std::size_t, void*); -extern "C" int* APS5_VABI __error_nid_postfix(); - static void Require(bool value, const char* message) { if (!value) { std::fprintf(stderr, "%s\n", message); @@ -18,9 +16,8 @@ static void Require(bool value, const char* message) { int main() { const std::uint16_t text[] = {'a', 'b', 'c', 0}; const std::uint16_t empty[] = {0}; - const std::uint16_t invalid[] = {'a', 0x100, 'b', 0}; + const std::uint16_t twoBytes[] = {'a', 0x100, 'b', 0}; const std::uint16_t bytes[] = {0x7f, 0x80, 0xe9, 0xff, 0}; - constexpr auto Failed = static_cast(-1); std::uint64_t state[2]{}; char out[8]; const auto* source = text; @@ -46,8 +43,8 @@ int main() { Require(wcsrtombs_nid_postfix(out, &source, 1, state) == 0, "empty string length"); Require(source == nullptr && out[0] == 0 && out[1] == 'x', "empty string terminator"); source = bytes; - Require(wcsrtombs_nid_postfix(out, &source, sizeof(out), state) == 4, "guest single-byte conversion length"); - Require(source == nullptr && std::memcmp(out, "\x7f\x80\xe9\xff\0", 5) == 0, "guest high bytes"); + Require(wcsrtombs_nid_postfix(out, &source, sizeof(out), state) == 7, "UTF-8 conversion length"); + Require(source == nullptr && std::memcmp(out, "\x7f\xc2\x80\xc3\xa9\xc3\xbf\0", 8) == 0, "high characters become two UTF-8 bytes"); source = text; Require(wcsrtombs_nid_postfix(nullptr, &source, 0, state) == 3 && source == text, "length query ignores capacity and preserves source"); @@ -55,21 +52,16 @@ int main() { source = empty; Require(wcsrtombs_nid_postfix(nullptr, &source, 0, state) == 0 && source == empty, "empty length query"); - source = invalid; + source = twoBytes; std::memset(out, 'x', sizeof(out)); - *__error_nid_postfix() = 0; - Require(wcsrtombs_nid_postfix(out, &source, sizeof(out), state) == Failed, "invalid character fails conversion"); - Require(*__error_nid_postfix() == 86, "invalid character uses guest EILSEQ"); - Require(source == invalid + 1 && std::memcmp(out, "axxxxxxx", 8) == 0, "error preserves prefix and identifies invalid character"); *__error_nid_postfix() = 7; - Require(wcsrtombs_nid_postfix(out, &source, 0, state) == 0 && source == invalid + 1, "zero capacity does not convert invalid character"); - Require(*__error_nid_postfix() == 7, "zero capacity preserves errno"); - - source = invalid; - Require(wcsrtombs_nid_postfix(nullptr, &source, 0, state) == Failed, "length query rejects invalid character"); - Require(*__error_nid_postfix() == 86 && source == invalid, "failed length query preserves source and reports guest EILSEQ"); - source = invalid; - *__error_nid_postfix() = 7; - Require(wcsrtombs_nid_postfix(out, &source, 1, state) == 1 && source == invalid + 1, "capacity stops before invalid character"); - Require(*__error_nid_postfix() == 7, "unconverted invalid character does not set errno"); + Require(wcsrtombs_nid_postfix(out, &source, sizeof(out), state) == 4, "a character above 0xff converts"); + Require(source == nullptr && std::memcmp(out, "a\xc4\x80" "b\0", 5) == 0, "U+0100 becomes C4 80"); + Require(*__error_nid_postfix() == 7, "conversion preserves errno"); + source = twoBytes; + Require(wcsrtombs_nid_postfix(nullptr, &source, 0, state) == 4 && source == twoBytes, "length query counts UTF-8 bytes"); + source = twoBytes; + std::memset(out, 'x', sizeof(out)); + Require(wcsrtombs_nid_postfix(out, &source, 2, state) == 1 && source == twoBytes + 1, "a character that does not fit stops the conversion"); + Require(std::memcmp(out, "axxxxxxx", 8) == 0, "no partial character is written"); } diff --git a/core/libs/tests/GuestWcsrtombsS.cpp b/core/libs/tests/GuestWcsrtombsS.cpp index 44a4206540..2169489807 100644 --- a/core/libs/tests/GuestWcsrtombsS.cpp +++ b/core/libs/tests/GuestWcsrtombsS.cpp @@ -1,11 +1,9 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include -extern "C" int APS5_VABI wcsrtombs_s_nid_postfix( - std::size_t*, char*, std::size_t, const std::uint16_t**, std::size_t, void*); -extern "C" int APS5_VABI wcstombs_s_nid_postfix(std::size_t*, char*, std::size_t, const std::uint16_t*, std::size_t); static void Require(bool value) { if (!value) std::abort(); } constexpr auto Failed = static_cast(-1); @@ -33,7 +31,7 @@ int main() { auto run = Convert(text, 0, 0, false); Require(run.status == 0 && run.result == 3 && run.source == text); run = Convert(wide, 0, Huge, false); - Require(run.status == 86 && run.result == Failed && run.source == wide); + Require(run.status == 0 && run.result == 4 && run.source == wide); run = Convert(text, 5, 1, false); Require(run.status == 22 && run.result == Failed && run.source == text); @@ -54,7 +52,7 @@ int main() { Require(run.status == 34 && run.result == Failed && run.source == text + 1 && std::memcmp(run.out, "\0x", 2) == 0); run = Convert(wide, 8, 8); - Require(run.status == 86 && run.result == Failed && run.source == wide && std::memcmp(run.out, "a\0xx", 4) == 0); + Require(run.status == 0 && run.result == 4 && run.source == nullptr && std::memcmp(run.out, "a\xc4\x80" "b\0", 5) == 0); run = Convert(text, 0, 8); Require(run.status == 22 && run.result == Failed && run.source == text && run.out[0] == 'x'); @@ -92,6 +90,9 @@ int main() { Require(wcstombs_s_nid_postfix(&result, narrow, sizeof(narrow), nullptr, sizeof(narrow)) == 22); Require(result == Failed && narrow[0] == '\0'); std::memset(narrow, 'x', sizeof(narrow)); - Require(wcstombs_s_nid_postfix(&result, narrow, sizeof(narrow), wide, sizeof(narrow)) == 86 && result == Failed); - Require(std::memcmp(narrow, "a\0xx", 4) == 0); + Require(wcstombs_s_nid_postfix(&result, narrow, sizeof(narrow), wide, sizeof(narrow)) == 0 && result == 4); + Require(std::memcmp(narrow, "a\xc4\x80" "b\0x", 6) == 0); + std::memset(narrow, 'x', sizeof(narrow)); + Require(wcstombs_s_nid_postfix(&result, narrow, sizeof(narrow), wide, 2) == 0 && result == 1); + Require(std::memcmp(narrow, "a\0x", 3) == 0); } diff --git a/core/libs/tests/GuestWebBrowserDialog.cpp b/core/libs/tests/GuestWebBrowserDialog.cpp index ea5d58c2cc..01fdca210e 100644 --- a/core/libs/tests/GuestWebBrowserDialog.cpp +++ b/core/libs/tests/GuestWebBrowserDialog.cpp @@ -1,18 +1,9 @@ +#include "prx/libSceWebBrowserDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceCommonDialog/CommonDialog.hpp" #include #include -extern "C" { -int APS5_VABI sceWebBrowserDialogInitialize(void); -int APS5_VABI sceWebBrowserDialogTerminate(void); -int APS5_VABI sceWebBrowserDialogOpen(const void* param); -int APS5_VABI sceWebBrowserDialogOpenForPredeterminedContent(const void* param); -int APS5_VABI sceWebBrowserDialogClose(void); -int APS5_VABI sceWebBrowserDialogGetStatus(void); -int APS5_VABI sceWebBrowserDialogUpdateStatus(void); -int APS5_VABI sceWebBrowserDialogGetResult(void* result); -} - namespace { constexpr int COMMON_DIALOG_STATUS_NONE = 0; @@ -20,7 +11,6 @@ constexpr int COMMON_DIALOG_STATUS_INITIALIZED = 1; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); -constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); @@ -30,21 +20,27 @@ void Require(bool value) { if (!value) std::abort(); } int main() { std::uint8_t param[64] = {}; - std::int32_t result = 0x5a5a5a5a; + WebBrowserDialogAllowList allowList{}; + allowList.size = sizeof(allowList); + allowList.addresses[0] = "test.invalid"; + WebBrowserDialogResult result{}; + result.result = 0x5a5a5a5a; Require(sceWebBrowserDialogTerminate() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(sceWebBrowserDialogSetCookie(nullptr) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(sceWebBrowserDialogResetCookie(nullptr) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); Require(sceWebBrowserDialogGetStatus() == COMMON_DIALOG_STATUS_NONE); Require(sceWebBrowserDialogClose() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); Require(sceWebBrowserDialogOpen(param) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); Require(sceWebBrowserDialogGetResult(&result) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); - Require(result == 0x5a5a5a5a); + Require(result.result == 0x5a5a5a5a); Require(sceWebBrowserDialogInitialize() == 0); Require(sceWebBrowserDialogInitialize() == COMMON_DIALOG_ERROR_ALREADY_INITIALIZED); Require(sceWebBrowserDialogGetStatus() == COMMON_DIALOG_STATUS_INITIALIZED); Require(sceWebBrowserDialogUpdateStatus() == COMMON_DIALOG_STATUS_INITIALIZED); Require(sceWebBrowserDialogGetResult(&result) == COMMON_DIALOG_ERROR_NOT_FINISHED); - Require(result == 0x5a5a5a5a); + Require(result.result == 0x5a5a5a5a); Require(sceWebBrowserDialogTerminate() == 0); Require(sceWebBrowserDialogTerminate() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); @@ -58,37 +54,72 @@ int main() { Require(sceWebBrowserDialogGetResult(nullptr) == COMMON_DIALOG_ERROR_ARG_NULL); Require(sceWebBrowserDialogGetResult(&result) == 0); - Require(result == COMMON_DIALOG_RESULT_USER_CANCELED); + Require(result.result == COMMON_DIALOG_RESULT_USER_CANCELED); - Require(sceWebBrowserDialogClose() == 0); + Require(sceWebBrowserDialogClose() == static_cast(0x80B8000Bu)); Require(sceWebBrowserDialogTerminate() == 0); Require(sceWebBrowserDialogTerminate() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); Require(sceWebBrowserDialogGetStatus() == COMMON_DIALOG_STATUS_NONE); - Require(sceWebBrowserDialogOpenForPredeterminedContent(param) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(sceWebBrowserDialogOpenForPredeterminedContent(param, &allowList) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); Require(sceWebBrowserDialogInitialize() == 0); - Require(sceWebBrowserDialogOpenForPredeterminedContent(nullptr) == COMMON_DIALOG_ERROR_ARG_NULL); - Require(sceWebBrowserDialogOpenForPredeterminedContent(param) == 0); + Require(sceWebBrowserDialogOpenForPredeterminedContent(nullptr, &allowList) == COMMON_DIALOG_ERROR_ARG_NULL); + Require(sceWebBrowserDialogOpenForPredeterminedContent(param, nullptr) == COMMON_DIALOG_ERROR_ARG_NULL); + allowList.addresses[0] = nullptr; + Require(sceWebBrowserDialogOpenForPredeterminedContent(param, &allowList) == static_cast(0x80B8000Au)); + allowList.addresses[19] = "test.invalid"; + allowList.reserved[255] = 1; + Require(sceWebBrowserDialogOpenForPredeterminedContent(param, &allowList) == static_cast(0x80B8000Au)); + allowList.reserved[255] = 0; + allowList.addresses[19] = ""; + Require(sceWebBrowserDialogOpenForPredeterminedContent(param, &allowList) == static_cast(0x80B8000Au)); + char longAddress[257]; + std::fill(std::begin(longAddress), std::end(longAddress), 'a'); + longAddress[256] = '\0'; + allowList.addresses[19] = longAddress; + Require(sceWebBrowserDialogOpenForPredeterminedContent(param, &allowList) == static_cast(0x80B8000Au)); + longAddress[255] = '\0'; + Require(sceWebBrowserDialogOpenForPredeterminedContent(param, &allowList) == 0); Require(sceWebBrowserDialogGetStatus() == COMMON_DIALOG_STATUS_FINISHED); Require(sceWebBrowserDialogUpdateStatus() == COMMON_DIALOG_STATUS_FINISHED); - result = 0x5a5a5a5a; + result.result = 0x5a5a5a5a; Require(sceWebBrowserDialogGetResult(&result) == 0); - Require(result == COMMON_DIALOG_RESULT_USER_CANCELED); + Require(result.result == COMMON_DIALOG_RESULT_USER_CANCELED); - Require(sceWebBrowserDialogClose() == 0); + Require(sceWebBrowserDialogClose() == static_cast(0x80B8000Bu)); Require(sceWebBrowserDialogTerminate() == 0); Require(sceWebBrowserDialogGetStatus() == COMMON_DIALOG_STATUS_NONE); Require(sceWebBrowserDialogInitialize() == 0); + Require(sceWebBrowserDialogClose() == static_cast(0x80B8000Bu)); Require(sceWebBrowserDialogGetStatus() == COMMON_DIALOG_STATUS_INITIALIZED); - Require(sceWebBrowserDialogClose() == 0); - Require(sceWebBrowserDialogGetStatus() == COMMON_DIALOG_STATUS_FINISHED); - result = 0x5a5a5a5a; + Require(sceWebBrowserDialogGetResult(&result) == COMMON_DIALOG_ERROR_NOT_FINISHED); + Require(sceWebBrowserDialogTerminate() == 0); + Require(sceWebBrowserDialogGetResult(nullptr) == COMMON_DIALOG_ERROR_ARG_NULL); + result.reserved[239] = 1; + Require(sceWebBrowserDialogGetResult(&result) == static_cast(0x80B8000Au)); + result.reserved[239] = 0; + WebBrowserDialogExtendedResult extended{}; + result.extended = &extended; + Require(sceWebBrowserDialogGetResult(&result) == static_cast(0x80B8000Au)); + extended.size = sizeof(extended); + Require(sceWebBrowserDialogGetResult(&result) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(sceWebBrowserDialogInitialize() == 0); + Require(sceWebBrowserDialogOpen(param) == 0); + extended.reserved0[3] = 1; + Require(sceWebBrowserDialogGetResult(&result) == static_cast(0x80B8000Au)); + extended.reserved0[3] = 0; + extended.reserved1[19] = 1; + Require(sceWebBrowserDialogGetResult(&result) == static_cast(0x80B8000Au)); + extended.reserved1[19] = 0; + extended.type = 0x5a5a5a5a; + extended.content[2047] = 'x'; + result.padding = 0x5a5a5a5a; Require(sceWebBrowserDialogGetResult(&result) == 0); - Require(result == COMMON_DIALOG_RESULT_USER_CANCELED); + Require(result.result == COMMON_DIALOG_RESULT_USER_CANCELED); + Require(result.padding == 0x5a5a5a5a && result.extended == &extended); + Require(extended.type == 0x5a5a5a5a && extended.content[2047] == 'x'); Require(sceWebBrowserDialogTerminate() == 0); - Require(sceWebBrowserDialogGetStatus() == COMMON_DIALOG_STATUS_NONE); - return 0; } diff --git a/core/libs/tests/GuestWebBrowserDialogCookies.cpp b/core/libs/tests/GuestWebBrowserDialogCookies.cpp new file mode 100644 index 0000000000..32deb3db57 --- /dev/null +++ b/core/libs/tests/GuestWebBrowserDialogCookies.cpp @@ -0,0 +1,125 @@ +#include "prx/libSceWebBrowserDialog/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +namespace { + +constexpr int NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int INVALID_STATE = static_cast(0x80B80006u); +constexpr int NOT_RUNNING = static_cast(0x80B8000Bu); +constexpr int PARAM_INVALID = static_cast(0x80B8000Au); +constexpr int ARG_NULL = static_cast(0x80B8000Du); + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "FAIL: %s\n", what); + std::abort(); +} + +WebBrowserDialogCookieParam Cookie(const char* url, const char* cookie) { + WebBrowserDialogCookieParam param {}; + param.size = sizeof(param); + param.address = url; + param.cookie = cookie; + return param; +} + +WebBrowserDialogResetCookieParam Reset() { + WebBrowserDialogResetCookieParam param {}; + param.size = sizeof(param); + return param; +} + +void CheckSetCookieParameters() { + auto valid = Cookie("https://example.com", "a=b"); + Require(sceWebBrowserDialogSetCookie(&valid) == 0, "valid SetCookie"); + Require(sceWebBrowserDialogSetCookie(nullptr) == ARG_NULL, "SetCookie null"); + + auto wrongSize = valid; + wrongSize.size = sizeof(WebBrowserDialogResetCookieParam); + Require(sceWebBrowserDialogSetCookie(&wrongSize) == PARAM_INVALID, "SetCookie size"); + auto noUrl = Cookie(nullptr, "a=b"); + Require(sceWebBrowserDialogSetCookie(&noUrl) == PARAM_INVALID, "SetCookie null url"); + auto emptyUrl = Cookie("", "a=b"); + Require(sceWebBrowserDialogSetCookie(&emptyUrl) == PARAM_INVALID, "SetCookie empty url"); + auto noCookie = Cookie("https://example.com", nullptr); + Require(sceWebBrowserDialogSetCookie(&noCookie) == PARAM_INVALID, "SetCookie null cookie"); + auto emptyCookie = Cookie("https://example.com", ""); + Require(sceWebBrowserDialogSetCookie(&emptyCookie) == PARAM_INVALID, "SetCookie empty cookie"); + + const std::string longestUrl(0x7ff, 'u'); + const std::string longUrl(0x800, 'u'); + auto longest = Cookie(longestUrl.c_str(), "a=b"); + Require(sceWebBrowserDialogSetCookie(&longest) == 0, "SetCookie 2047-byte url"); + auto tooLong = Cookie(longUrl.c_str(), "a=b"); + Require(sceWebBrowserDialogSetCookie(&tooLong) == PARAM_INVALID, "SetCookie 2048-byte url"); + + const std::string longestCookie(0xfff, 'c'); + const std::string longCookie(0x1000, 'c'); + auto longestValue = Cookie("https://example.com", longestCookie.c_str()); + Require(sceWebBrowserDialogSetCookie(&longestValue) == 0, "SetCookie 4095-byte cookie"); + auto tooLongValue = Cookie("https://example.com", longCookie.c_str()); + Require(sceWebBrowserDialogSetCookie(&tooLongValue) == PARAM_INVALID, "SetCookie 4096-byte cookie"); + + auto reserved = valid; + reserved.reserved[255] = 1; + Require(sceWebBrowserDialogSetCookie(&reserved) == PARAM_INVALID, "SetCookie reserved"); +} + +void CheckResetCookieParameters() { + auto valid = Reset(); + Require(sceWebBrowserDialogResetCookie(&valid) == 0, "valid ResetCookie"); + Require(sceWebBrowserDialogResetCookie(nullptr) == ARG_NULL, "ResetCookie null"); + auto wrongSize = valid; + wrongSize.size = sizeof(WebBrowserDialogCookieParam); + Require(sceWebBrowserDialogResetCookie(&wrongSize) == PARAM_INVALID, "ResetCookie size"); + auto reserved = valid; + reserved.reserved[0] = 1; + Require(sceWebBrowserDialogResetCookie(&reserved) == PARAM_INVALID, "ResetCookie reserved"); +} + +void CheckOpenDialog() { + std::uint8_t open[64] = {}; + auto cookie = Cookie("https://example.com", "a=b"); + auto reset = Reset(); + Require(sceWebBrowserDialogOpen(open) == 0, "Open"); + Require(sceWebBrowserDialogSetCookie(&cookie) == INVALID_STATE, "SetCookie while open"); + Require(sceWebBrowserDialogSetCookie(nullptr) == INVALID_STATE, "SetCookie checks the open dialog before null"); + Require(sceWebBrowserDialogResetCookie(nullptr) == ARG_NULL, "ResetCookie checks null before the open dialog"); + Require(sceWebBrowserDialogResetCookie(&reset) == INVALID_STATE, "ResetCookie while open"); + Require(sceWebBrowserDialogUpdateStatus() == 3, "UpdateStatus finished"); + Require(sceWebBrowserDialogSetCookie(&cookie) == 0, "SetCookie after UpdateStatus"); + Require(sceWebBrowserDialogResetCookie(&reset) == 0, "ResetCookie after UpdateStatus"); + + Require(sceWebBrowserDialogOpen(open) == 0, "second Open"); + Require(sceWebBrowserDialogClose() == NOT_RUNNING, "Close of a finished dialog"); + Require(sceWebBrowserDialogSetCookie(&cookie) == 0, "SetCookie after Close"); + + Require(sceWebBrowserDialogOpen(open) == 0, "third Open"); + Require(sceWebBrowserDialogTerminate() == 0, "Terminate"); + Require(sceWebBrowserDialogInitialize() == 0, "Initialize again"); + Require(sceWebBrowserDialogResetCookie(&reset) == 0, "ResetCookie after Terminate"); +} + +} + +int main() { + auto cookie = Cookie("https://example.com", "a=b"); + auto reset = Reset(); + Require(sceWebBrowserDialogSetCookie(&cookie) == NOT_INITIALIZED, "SetCookie before Initialize"); + Require(sceWebBrowserDialogSetCookie(nullptr) == NOT_INITIALIZED, "SetCookie null before Initialize"); + Require(sceWebBrowserDialogResetCookie(&reset) == NOT_INITIALIZED, "ResetCookie before Initialize"); + Require(sceWebBrowserDialogResetCookie(nullptr) == NOT_INITIALIZED, "ResetCookie null before Initialize"); + + Require(sceWebBrowserDialogInitialize() == 0, "Initialize"); + CheckSetCookieParameters(); + CheckResetCookieParameters(); + CheckOpenDialog(); + Require(sceWebBrowserDialogTerminate() == 0, "final Terminate"); + std::puts("guest web browser dialog cookies: PASS"); + return 0; +} diff --git a/core/libs/tests/GuestWideConversion.cpp b/core/libs/tests/GuestWideConversion.cpp index 07b28e68ca..5590c6124a 100644 --- a/core/libs/tests/GuestWideConversion.cpp +++ b/core/libs/tests/GuestWideConversion.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,25 +9,6 @@ #include #include -extern "C" { -std::uint64_t APS5_VABI _WStoul_nid_postfix(const char16_t*, char16_t**, int); -int APS5_VABI _Iswctype_nid_postfix(std::uint32_t, std::uint64_t); -int APS5_VABI swscanf_nid_postfix(const char16_t*, const char16_t*, ...); -int APS5_VABI swscanf_s_nid_postfix(const char16_t*, const char16_t*, ...); -int APS5_VABI iswalnum_nid_postfix(std::uint32_t); -int APS5_VABI iswalpha_nid_postfix(std::uint32_t); -int APS5_VABI iswcntrl_nid_postfix(std::uint32_t); -int APS5_VABI iswdigit_nid_postfix(std::uint32_t); -int APS5_VABI iswgraph_nid_postfix(std::uint32_t); -int APS5_VABI iswlower_nid_postfix(std::uint32_t); -int APS5_VABI iswprint_nid_postfix(std::uint32_t); -int APS5_VABI iswpunct_nid_postfix(std::uint32_t); -int APS5_VABI iswspace_nid_postfix(std::uint32_t); -int APS5_VABI iswupper_nid_postfix(std::uint32_t); -int APS5_VABI iswxdigit_nid_postfix(std::uint32_t); -int APS5_VABI iswblank_nid_postfix(std::uint32_t); -} - static int failures = 0; static void Require(bool condition, const char* message) { @@ -134,18 +116,25 @@ static void CheckScan() { Require(swscanf_s_nid_postfix(u"x", u"%2lc", pair, std::size_t{2}) == 0, "%2lc short input"); Require(swscanf_s_nid_postfix(u"cab12", u"%l[abc]%d", text, std::size_t{6}, &first) == 2 && std::u16string(text) == u"cab" && first == 12, "%l["); Require(swscanf_s_nid_postfix(u"été!", u"%l[^!]", text, std::size_t{6}) == 1 && std::u16string(text) == u"été", "%l[^"); + char16_t untouched[4] = {u'?', u'?', u'?', u'?'}; + Require(swscanf_s_nid_postfix(u"Āb cd", u"%S", untouched, std::size_t{4}) == 0 && untouched[0] == u'?', "%S is an invalid conversion"); + Require(swscanf_s_nid_postfix(u"7 Āb", u"%d%lS", &first, untouched, std::size_t{4}) == 1 && first == 7 && untouched[0] == u'?', "%lS stops after the earlier assignment"); + Require(swscanf_s_nid_postfix(u"Āyz", u"%2C", untouched, std::size_t{4}) == 0 && untouched[0] == u'?', "%C is an invalid conversion"); + Require(swscanf_s_nid_postfix(u"5 6", u"%hC%d", &first) == 0 && first == 7, "%hC stops before later conversions"); std::int64_t count = -1; Require(swscanf_s_nid_postfix(u" 12 ab", u"%d %ln", &first, &count) == 1 && count == 4, "%ln"); double real = 0; Require(swscanf_s_nid_postfix(u"1.5e3", u"%lf", &real) == 1 && real == 1500.0, "%lf"); - Require(swscanf_s_nid_postfix(u"1e+x", u"%lf", &real) == 0, "%lf incomplete exponent is a matching failure"); + Require(swscanf_s_nid_postfix(u"1e+", u"%lf", &real) == 1 && real == 1.0, "%lf converts the mantissa of an incomplete exponent"); long double extended = 0; Require(swscanf_s_nid_postfix(u"2.25", u"%Lf", &extended) == 1 && extended == 2.25L, "%Lf"); float single = 0; Require(swscanf_s_nid_postfix(u"-inf", u"%f", &single) == 1 && single == -std::numeric_limits::infinity(), "%f infinity"); Require(swscanf_s_nid_postfix(u"a", u"b%d", &first) == 0, "literal mismatch is a matching failure"); + Require(swscanf_s_nid_postfix(u"", u"x") == 0, "literal at end of input returns zero"); + Require(swscanf_s_nid_postfix(u"", u"%%") == -1, "percent literal at end of input is an input failure"); Require(swscanf_s_nid_postfix(u"", u"%d", &first) == -1, "empty input is an input failure"); Require(swscanf_s_nid_postfix(u" ", u"%ls", text, std::size_t{6}) == -1, "white space only is an input failure"); Require(swscanf_s_nid_postfix(u"5", u"%d%d", &first, &second) == 1, "input failure after a conversion"); @@ -155,7 +144,7 @@ static void CheckScan() { Require(swscanf_s_nid_postfix(u"5", nullptr) == -1, "null format"); Require(Throws([&] { swscanf_s_nid_postfix(u"abc", u"%l[a-c]", text, std::size_t{6}); }), "scanset range throws"); Require(Throws([&] { swscanf_s_nid_postfix(u"5", u"%Ld", &wide); }), "%Ld throws"); - Require(Throws([&] { swscanf_s_nid_postfix(u"Ā", u"%s", narrow, std::size_t{4}); }), "unconvertible narrow %s throws"); + Require(swscanf_s_nid_postfix(u"Ā", u"%s", narrow, std::size_t{4}) == 1 && std::strcmp(narrow, "\xc4\x80") == 0, "narrow %s stores UTF-8"); } static void CheckUnsizedScan() { @@ -193,6 +182,11 @@ static void CheckUnsizedScan() { Require(swscanf_nid_postfix(u" q7", u"%c%d", &letter, &first) == 1 && letter == ' ', "swscanf %c does not skip white space"); Require(swscanf_nid_postfix(u"cab12", u"%l[abc]%d", text, &first) == 2 && std::u16string(text) == u"cab" && first == 12, "swscanf %l["); Require(swscanf_nid_postfix(u"été!", u"%l[^!]", text) == 1 && std::u16string(text) == u"été", "swscanf %l[^"); + char16_t untouched[4] = {u'?', u'?', u'?', u'?'}; + Require(swscanf_nid_postfix(u"Āb cd", u"%S", untouched) == 0 && untouched[0] == u'?', "swscanf %S is an invalid conversion"); + Require(swscanf_nid_postfix(u"7 Āb 9", u"%d%3S%d", &first, untouched, &second) == 1 && first == 7 && untouched[0] == u'?', "swscanf stops at %S"); + Require(swscanf_nid_postfix(u"Āyz", u"%lC", untouched) == 0 && untouched[0] == u'?', "swscanf %lC is an invalid conversion"); + Require(swscanf_nid_postfix(u"abc", u"%*S") == 0, "swscanf suppressed %S is an invalid conversion"); Require(swscanf_nid_postfix(u"key=val", u"%[^=]=%s", narrow, narrow + 4) == 2 && std::strcmp(narrow, "key") == 0 && std::strcmp(narrow + 4, "val") == 0, "swscanf %[^"); std::int64_t count = -1; Require(swscanf_nid_postfix(u" 12 ab", u"%d %ln", &first, &count) == 1 && count == 4, "swscanf %ln"); @@ -203,13 +197,15 @@ static void CheckUnsizedScan() { double real = 0; Require(swscanf_nid_postfix(u"1.5e3", u"%lf", &real) == 1 && real == 1500.0, "swscanf %lf"); - Require(swscanf_nid_postfix(u"1e+x", u"%lf", &real) == 0, "swscanf %lf incomplete exponent is a matching failure"); + Require(swscanf_nid_postfix(u"1e+", u"%lf", &real) == 1 && real == 1.0, "swscanf %lf converts the mantissa of an incomplete exponent"); long double extended = 0; Require(swscanf_nid_postfix(u"2.25", u"%Lf", &extended) == 1 && extended == 2.25L, "swscanf %Lf"); float single = 0; Require(swscanf_nid_postfix(u"-inf", u"%f", &single) == 1 && single == -std::numeric_limits::infinity(), "swscanf %f infinity"); Require(swscanf_nid_postfix(u"a", u"b%d", &first) == 0, "swscanf literal mismatch is a matching failure"); + Require(swscanf_nid_postfix(u"", u"x") == 0, "swscanf literal at end of input returns zero"); + Require(swscanf_nid_postfix(u"", u"%%") == -1, "swscanf percent literal at end of input is an input failure"); Require(swscanf_nid_postfix(u"3 x", u"%d%d", &first, &second) == 1 && first == 3, "swscanf partial match"); Require(swscanf_nid_postfix(u"", u"%d", &first) == -1, "swscanf empty input is an input failure"); Require(swscanf_nid_postfix(u"", u"%ls", text) == -1, "swscanf empty input before %ls"); @@ -220,7 +216,7 @@ static void CheckUnsizedScan() { Require(Throws([&] { swscanf_nid_postfix(u"abc", u"%l[a-c]", text); }), "swscanf scanset range throws"); Require(Throws([&] { swscanf_nid_postfix(u"5", u"%Ld", &wide); }), "swscanf %Ld throws"); Require(Throws([&] { swscanf_nid_postfix(u"1", u"%lp", &pointer); }), "swscanf %lp throws"); - Require(Throws([&] { swscanf_nid_postfix(u"Ā", u"%s", narrow); }), "swscanf unconvertible narrow %s throws"); + Require(swscanf_nid_postfix(u"Ā", u"%s", narrow) == 1 && std::strcmp(narrow, "\xc4\x80") == 0, "swscanf narrow %s stores UTF-8"); } int main() { diff --git a/core/libs/tests/GuestWideFormatting.cpp b/core/libs/tests/GuestWideFormatting.cpp index 8ebfa2753e..56b278d8ae 100644 --- a/core/libs/tests/GuestWideFormatting.cpp +++ b/core/libs/tests/GuestWideFormatting.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestTime.hpp" @@ -8,16 +9,6 @@ #include #include -extern "C" int APS5_VABI vswprintf_nid_postfix(char16_t*, std::size_t, const char16_t*, VaList*); -extern "C" int APS5_VABI snwprintf_s_nid_postfix(char16_t*, std::size_t, const char16_t*, ...); -extern "C" int APS5_VABI vswprintf_s_nid_postfix(char16_t*, std::size_t, const char16_t*, VaList*); -extern "C" int APS5_VABI swprintf_s_nid_postfix(char16_t*, std::size_t, const char16_t*, ...); -extern "C" int APS5_VABI vsnwprintf_s_nid_postfix(char16_t*, std::size_t, const char16_t*, VaList*); -extern "C" int APS5_VABI swprintf_nid_postfix(char16_t*, std::size_t, const char16_t*, ...); -extern "C" std::size_t APS5_VABI strftime_nid_postfix(char*, std::size_t, const char*, const GuestTm*); -extern "C" std::size_t APS5_VABI wcsftime_nid_postfix(char16_t*, std::size_t, const char16_t*, const GuestTm*); -extern "C" int* APS5_VABI __error_nid_postfix(); - static int APS5_VABI Format(char16_t* buffer, std::size_t size, const char16_t* format, ...) { #ifdef _WIN32 __builtin_sysv_va_list args; @@ -212,7 +203,8 @@ static void CheckSwprintf() { static bool WcsftimeThrows(char16_t* buffer, const char16_t* format, const GuestTm* time) { try { - wcsftime_nid_postfix(buffer, 16, format, time); + wcsftime_nid_postfix(reinterpret_cast(buffer), 16, reinterpret_cast(format), time); + } catch (const std::invalid_argument&) { return true; } @@ -246,31 +238,33 @@ static void CheckWcsftime() { char16_t wide[128]; const std::size_t length = strftime_nid_postfix(narrow, sizeof(narrow), item.narrow, &time); Require(length != 0, "wcsftime reference strftime"); - Require(wcsftime_nid_postfix(wide, 128, item.wide, &time) == length, "wcsftime length matches strftime"); + Require(wcsftime_nid_postfix(reinterpret_cast(wide), 128, reinterpret_cast(item.wide), &time) == length, "wcsftime length matches strftime"); for (std::size_t index = 0; index <= length; ++index) Require(wide[index] == static_cast(narrow[index]), "wcsftime output matches strftime"); } char16_t buffer[32]; - Require(wcsftime_nid_postfix(buffer, 32, u"%Y-%m-%d %H:%M:%S", &time) == 19 + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 32, reinterpret_cast(u"%Y-%m-%d %H:%M:%S"), &time) == 19 && buffer == std::u16string(u"2024-02-29 13:05:09"), "wcsftime date and time"); - Require(wcsftime_nid_postfix(buffer, 32, u"%a %b %j %p", &time) == 14 + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 32, reinterpret_cast(u"%a %b %j %p"), &time) == 14 && buffer == std::u16string(u"Thu Feb 060 PM"), "wcsftime names"); - Require(wcsftime_nid_postfix(buffer, 32, u"%%", &time) == 1 && buffer == std::u16string(u"%"), "wcsftime percent"); - Require(wcsftime_nid_postfix(buffer, 20, u"%Y-%m-%d %H:%M:%S", &time) == 19, "wcsftime exact fit"); + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 32, reinterpret_cast(u"%%"), &time) == 1 && buffer == std::u16string(u"%"), "wcsftime percent"); + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 20, reinterpret_cast(u"%Y-%m-%d %H:%M:%S"), &time) == 19, "wcsftime exact fit"); int& error = *__error_nid_postfix(); for (auto& unit : buffer) unit = u'x'; error = 5; - Require(wcsftime_nid_postfix(buffer, 19, u"%Y-%m-%d %H:%M:%S", &time) == 0 && error == 5 && buffer[0] == u'x', + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 19, reinterpret_cast(u"%Y-%m-%d %H:%M:%S"), &time) == 0 && error == 5 && buffer[0] == u'x', "wcsftime overflow"); - Require(wcsftime_nid_postfix(buffer, 1, u"%Y", &time) == 0 && error == 5 && buffer[0] == u'x', "wcsftime one unit"); - Require(wcsftime_nid_postfix(buffer, 0, u"%Y", &time) == 0 && error == 5 && buffer[0] == u'x', "wcsftime zero size"); - Require(wcsftime_nid_postfix(buffer, 32, u"", &time) == 0 && error == 5 && buffer[0] == u'x', "wcsftime empty format"); - Require(wcsftime_nid_postfix(nullptr, 32, u"%Y", &time) == 0 && error == 5, "wcsftime null buffer"); - Require(wcsftime_nid_postfix(buffer, 32, u"%Y \u20ac", &time) == 0 && error == 86 && buffer[0] == u'x', - "wcsftime unconvertible format"); + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 1, reinterpret_cast(u"%Y"), &time) == 0 && error == 5 && buffer[0] == u'x', "wcsftime one unit"); + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 0, reinterpret_cast(u"%Y"), &time) == 0 && error == 5 && buffer[0] == u'x', "wcsftime zero size"); + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 32, reinterpret_cast(u""), &time) == 0 && error == 5 && buffer[0] == u'x', "wcsftime empty format"); + Require(wcsftime_nid_postfix(nullptr, 32, reinterpret_cast(u"%Y"), &time) == 0 && error == 5, "wcsftime null buffer"); + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 32, reinterpret_cast(u"%Y \u20ac"), &time) == 6 && buffer == std::u16string(u"2024 \u20ac"), + "wcsftime passes a character above 0xff through UTF-8"); + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 7, reinterpret_cast(u"%Y \u20ac"), &time) == 6, "wcsftime sizes the narrow buffer for three-byte characters"); + for (auto& unit : buffer) unit = u'x'; error = 5; - Require(wcsftime_nid_postfix(buffer, SIZE_MAX, u"%Y", &time) == 0 && error == 22 && buffer[0] == u'x', + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), SIZE_MAX, reinterpret_cast(u"%Y"), &time) == 0 && error == 22 && buffer[0] == u'x', "wcsftime oversized buffer"); Require(WcsftimeThrows(buffer, nullptr, &time), "wcsftime null format"); Require(WcsftimeThrows(buffer, u"%Y", nullptr), "wcsftime null time"); diff --git a/core/libs/tests/GuestWideStreamScan.cpp b/core/libs/tests/GuestWideStreamScan.cpp index 0da21861b0..857a72001f 100644 --- a/core/libs/tests/GuestWideStreamScan.cpp +++ b/core/libs/tests/GuestWideStreamScan.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -8,15 +9,7 @@ extern "C" { extern FileStream* __stdinp_nid_postfix; -int APS5_VABI vfwscanf_nid_postfix(FileStream*, const char16_t*, void*); -int APS5_VABI vfwscanf_s_nid_postfix(FileStream*, const char16_t*, void*); -int APS5_VABI vwscanf_nid_postfix(const char16_t*, void*); -int APS5_VABI vwscanf_s_nid_postfix(const char16_t*, void*); -int APS5_VABI fwscanf_nid_postfix(FileStream*, const char16_t*, ...); -int APS5_VABI fwscanf_s_nid_postfix(FileStream*, const char16_t*, ...); -int APS5_VABI wscanf_nid_postfix(const char16_t*, ...); -int APS5_VABI wscanf_s_nid_postfix(const char16_t*, ...); -std::int32_t APS5_VABI fgetwc_nid_postfix(FileStream*); + } #ifdef _WIN32 @@ -35,28 +28,28 @@ static void Require(bool value, const char* what) { static int APS5_VABI ScanStream(FileStream* stream, const char16_t* format, ...) { APS5_VA_BEGIN(format); - const int result = vfwscanf_nid_postfix(stream, format, args); + const int result = vfwscanf_nid_postfix(stream, format, reinterpret_cast(args)); APS5_VA_END(); return result; } static int APS5_VABI ScanStreamSized(FileStream* stream, const char16_t* format, ...) { APS5_VA_BEGIN(format); - const int result = vfwscanf_s_nid_postfix(stream, format, args); + const int result = vfwscanf_s_nid_postfix(stream, format, reinterpret_cast(args)); APS5_VA_END(); return result; } static int APS5_VABI ScanInput(const char16_t* format, ...) { APS5_VA_BEGIN(format); - const int result = vwscanf_nid_postfix(format, args); + const int result = vwscanf_nid_postfix(format, reinterpret_cast(args)); APS5_VA_END(); return result; } static int APS5_VABI ScanInputSized(const char16_t* format, ...) { APS5_VA_BEGIN(format); - const int result = vwscanf_s_nid_postfix(format, args); + const int result = vwscanf_s_nid_postfix(format, reinterpret_cast(args)); APS5_VA_END(); return result; } @@ -101,6 +94,14 @@ static void CheckConsumption() { Require(fgetwc_nid_postfix(stream) == u'b', "the mismatching character is pushed back"); Close(stream); + stream = Open(""); + Require(ScanStream(stream, u"x") == 0, "a literal at end of input returns zero"); + Close(stream); + + stream = Open(""); + Require(ScanStream(stream, u"%%") == -1, "a percent literal at end of input is an input failure"); + Close(stream); + stream = Open("x"); Require(ScanStream(stream, u"%d", &value) == 0, "a letter is not an integer"); Require(fgetwc_nid_postfix(stream) == u'x', "the rejected letter is pushed back"); @@ -120,6 +121,10 @@ static void CheckConsumption() { Require(ScanStream(stream, u"%lf", &real) == 1 && real == 2.5, "a float"); Require(fgetwc_nid_postfix(stream) == u'x', "the character after the float"); Close(stream); + + stream = Open("1e+"); + Require(ScanStream(stream, u"%lf", &real) == 1 && real == 1.0, "an incomplete exponent converts its mantissa"); + Close(stream); } static void CheckWideText() { diff --git a/core/libs/tests/GuestWideStrings.cpp b/core/libs/tests/GuestWideStrings.cpp index 30f48c4efb..db3553fee9 100644 --- a/core/libs/tests/GuestWideStrings.cpp +++ b/core/libs/tests/GuestWideStrings.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -10,55 +11,6 @@ #include #include -extern "C" { -std::size_t APS5_VABI c32rtomb_nid_postfix(char* destination, std::uint32_t value, void* state); -std::size_t APS5_VABI c16rtomb_nid_postfix(char* destination, std::uint16_t unit, void* state); -std::size_t APS5_VABI mbrtoc32_nid_postfix(std::uint32_t* destination, const char* source, std::size_t count, void* state); -std::size_t APS5_VABI mbrtoc16_nid_postfix(std::uint16_t* destination, const char* source, std::size_t count, void* state); -int APS5_VABI mblen_nid_postfix(const char* source, std::size_t count); -int APS5_VABI wctomb_nid_postfix(char* destination, std::uint16_t value); -int* APS5_VABI __error_nid_postfix(); -const char16_t* APS5_VABI wmemchr_nid_postfix(const char16_t* s, char16_t c, std::size_t n); -int APS5_VABI wmemcmp_nid_postfix(const char16_t* s1, const char16_t* s2, std::size_t n); -char16_t* APS5_VABI wmemcpy_nid_postfix(char16_t* dest, const char16_t* src, std::size_t n); -char16_t* APS5_VABI wmemmove_nid_postfix(char16_t* dest, const char16_t* src, std::size_t n); -std::size_t APS5_VABI wcslen_nid_postfix(const char16_t* s); -std::size_t APS5_VABI wcsnlen_s_nid_postfix(const char16_t* s, std::size_t maxlen); -int APS5_VABI wcscmp_nid_postfix(const char16_t* s1, const char16_t* s2); -int APS5_VABI wcsncmp_nid_postfix(const char16_t* s1, const char16_t* s2, std::size_t n); -char16_t* APS5_VABI wcscpy_nid_postfix(char16_t* dest, const char16_t* src); -char16_t* APS5_VABI wcscat_nid_postfix(char16_t* dest, const char16_t* src); -char16_t* APS5_VABI wcsncpy_nid_postfix(char16_t* dest, const char16_t* src, std::size_t n); -int APS5_VABI wmemcpy_s_nid_postfix(char16_t* dest, std::size_t destsz, const char16_t* src, std::size_t count); -int APS5_VABI wmemmove_s_nid_postfix(char16_t* dest, std::size_t destsz, const char16_t* src, std::size_t count); -int APS5_VABI wcsncpy_s_nid_postfix(char16_t* dest, std::size_t destsz, const char16_t* src, std::size_t count); -char16_t* APS5_VABI wcsncat_nid_postfix(char16_t* dest, const char16_t* src, std::size_t n); -int APS5_VABI wcsncat_s_nid_postfix(char16_t* dest, std::size_t destsz, const char16_t* src, std::size_t count); -int APS5_VABI wcscat_s_nid_postfix(char16_t* dest, std::size_t destsz, const char16_t* src); -const char16_t* APS5_VABI wcschr_nid_postfix(const char16_t* s, char16_t c); -const char16_t* APS5_VABI wcsrchr_nid_postfix(const char16_t* s, char16_t c); -const char16_t* APS5_VABI wcsstr_nid_postfix(const char16_t* haystack, const char16_t* needle); -const char16_t* APS5_VABI wcspbrk_nid_postfix(const char16_t* s, const char16_t* accept); -std::size_t APS5_VABI wcsspn_nid_postfix(const char16_t* s, const char16_t* accept); -std::size_t APS5_VABI wcscspn_nid_postfix(const char16_t* s, const char16_t* reject); -char16_t* APS5_VABI wcstok_nid_postfix(char16_t* text, const char16_t* delimiters, char16_t** last); -char16_t* APS5_VABI wmemset_nid_postfix(char16_t* s, char16_t c, std::size_t n); -double APS5_VABI wcstod_nid_postfix(const char16_t* str, char16_t** endptr); -float APS5_VABI wcstof_nid_postfix(const char16_t* str, char16_t** endptr); -long double APS5_VABI wcstold_nid_postfix(const char16_t* str, char16_t** endptr); -long long APS5_VABI wcstol_nid_postfix(const char16_t* str, char16_t** endptr, int base); -long long APS5_VABI wcstoll_nid_postfix(const char16_t* str, char16_t** endptr, int base); -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); -unsigned long long APS5_VABI strtouq_nid_postfix(const char* str, char** endptr, int base); -char* APS5_VABI rindex_nid_postfix(const char* s, int c); -int APS5_VABI flsl_nid_postfix(std::int64_t mask); -unsigned long long APS5_VABI wcstoul_nid_postfix(const char16_t* str, char16_t** endptr, int base); -unsigned long long APS5_VABI wcstoull_nid_postfix(const char16_t* str, char16_t** endptr, int base); -int APS5_VABI wcscoll_nid_postfix(const char16_t* first, const char16_t* second); -std::size_t APS5_VABI wcsxfrm_nid_postfix(char16_t* destination, const char16_t* source, std::size_t count); -} - namespace { void require(bool condition) { @@ -121,7 +73,7 @@ int main() { require(mblen_nid_postfix(nullptr, 1) == 0 && mblen_nid_postfix("ab", 2) == 1 && mblen_nid_postfix("", 1) == 0); require(wctomb_nid_postfix(nullptr, 0x41) == 0); require(wctomb_nid_postfix(out, 0x42) == 1 && out[0] == 'B'); - require(wctomb_nid_postfix(out, 0x263a) == -1); + require(wctomb_nid_postfix(out, 0x263a) == 3 && std::memcmp(out, "\xe2\x98\xba", 3) == 0); } const char16_t split[] = {u'a', u'b', 0, u'c', 0}; require(wcslen_nid_postfix(u"") == 0); @@ -148,11 +100,13 @@ int main() { char16_t buffer[8]; wmemset_nid_postfix(buffer, 0xaaaa, 8); + require(wcscpy_nid_postfix(buffer, u"wide") == buffer); require(same(buffer, u"wide", 5) && buffer[5] == 0xaaaa); char16_t concatenated[8]; wmemset_nid_postfix(concatenated, 0xaaaa, 8); + concatenated[0] = 0; require(wcscat_nid_postfix(concatenated, u"ab") == concatenated); require(wcscat_nid_postfix(concatenated, u"世") == concatenated); @@ -165,6 +119,7 @@ int main() { require(same(buffer, padded, 6)); wmemset_nid_postfix(buffer, 0xaaaa, 8); wcsncpy_nid_postfix(buffer, u"abcdef", 3); + const char16_t truncated[] = {u'a', u'b', u'c', 0xaaaa}; require(same(buffer, truncated, 4)); @@ -174,9 +129,11 @@ int main() { require(wmemcpy_s_nid_postfix(nullptr, 16, u"abc", 3) == 22); require(wmemcpy_s_nid_postfix(memBuf, 16, nullptr, 3) == 22 && memBuf[0] == 0); wmemset_nid_postfix(memBuf, u'Z', 16); + require(wmemcpy_s_nid_postfix(memBuf, 4, u"12345", 5) == 34 && memBuf[0] == 0 && memBuf[3] == 0); require(wmemcpy_s_nid_postfix(memBuf, ((SIZE_MAX >> 1) / sizeof(char16_t)) + 1, u"a", 1) == 34); wmemcpy_s_nid_postfix(memBuf, 16, u"abcdefgh", 8); + require(wmemcpy_s_nid_postfix(memBuf + 2, 8, memBuf, 4) == 22 && memBuf[2] == 0); wmemcpy_s_nid_postfix(memBuf, 16, u"0123456789", 10); @@ -203,10 +160,26 @@ int main() { require(wcsncpy_s_nid_postfix(strBuf, 5, u"hello", 5) == 34 && strBuf[0] == 0); require(wcsncpy_s_nid_postfix(strBuf, ((SIZE_MAX >> 1) / sizeof(char16_t)) + 1, u"a", 1) == 34); wcsncpy_s_nid_postfix(strBuf, 16, u"abcdef", 6); + require(wcsncpy_s_nid_postfix(strBuf + 1, 10, strBuf, 3) == 22 && strBuf[1] == 0); + char16_t cpyBuf[16]; + require(wcscpy_s_nid_postfix(reinterpret_cast(cpyBuf), 16, reinterpret_cast(u"wide")) == 0 && same(cpyBuf, u"wide", 5)); + void* previousHandler = set_constraint_handler_s_nid_postfix(reinterpret_cast(&ignore_handler_s_nid_postfix)); + require(wcscpy_s_nid_postfix(nullptr, 16, reinterpret_cast(u"wide")) == 22); + require(wcscpy_s_nid_postfix(reinterpret_cast(cpyBuf), 0, reinterpret_cast(u"wide")) == 22); + require(wcscpy_s_nid_postfix(reinterpret_cast(cpyBuf), 16, nullptr) == 22 && cpyBuf[0] == 0); + require(wcscpy_s_nid_postfix(reinterpret_cast(cpyBuf), 4, reinterpret_cast(u"wide")) == 34 && cpyBuf[0] == 0); + cpyBuf[0] = 0x1234; + require(wcscpy_s_nid_postfix(reinterpret_cast(cpyBuf), ((SIZE_MAX >> 1) / sizeof(char16_t)) + 1, reinterpret_cast(u"a")) == 34 && cpyBuf[0] == 0x1234); + wcscpy_s_nid_postfix(reinterpret_cast(cpyBuf), 16, reinterpret_cast(u"abcdef")); + + require(wcscpy_s_nid_postfix(reinterpret_cast(cpyBuf + 1), 10, reinterpret_cast(cpyBuf)) == 22 && cpyBuf[1] == 0); + set_constraint_handler_s_nid_postfix(previousHandler); + char16_t ncatBuf[16]; wmemset_nid_postfix(ncatBuf, 0xaaaa, 16); + ncatBuf[0] = 0; require(wcsncat_nid_postfix(ncatBuf, u"hello", 3) == ncatBuf); require(same(ncatBuf, u"hel", 4) && ncatBuf[4] == 0xaaaa); @@ -216,19 +189,32 @@ int main() { wcsncpy_s_nid_postfix(strBuf, 16, u"hello", 5); require(wcsncat_s_nid_postfix(strBuf, 16, u" world", 6) == 0 && same(strBuf, u"hello world", 12)); wcsncpy_s_nid_postfix(strBuf, 16, u"foo", 3); + require(wcsncat_s_nid_postfix(strBuf, 16, u"barbaz", 3) == 0 && same(strBuf, u"foobar", 7)); wcsncpy_s_nid_postfix(strBuf, 8, u"abc", 3); + require(wcsncat_s_nid_postfix(strBuf, 8, u"1234", 4) == 0 && same(strBuf, u"abc1234", 8)); wcsncpy_s_nid_postfix(strBuf, 8, u"abc", 3); + require(wcsncat_s_nid_postfix(strBuf, 8, u"12345", 5) == 34 && strBuf[0] == 0); wmemset_nid_postfix(strBuf, u'x', 4); + require(wcsncat_s_nid_postfix(strBuf, 4, u"a", 1) == 34 && strBuf[0] == 0); wcsncpy_s_nid_postfix(strBuf, 16, u"abc", 3); + + require(wcsncat_s_nid_postfix(strBuf, 16, strBuf + 1, 2) == 0 && same(strBuf, u"abcbc", 6)); + const char16_t splitSource[] = u"abc\0xyz"; + std::memcpy(strBuf, splitSource, sizeof(splitSource)); + require(wcsncat_s_nid_postfix(strBuf, 16, strBuf + 4, 3) == 22 && strBuf[0] == 0); + wcsncpy_s_nid_postfix(strBuf, 16, u"abc", 3); + require(wcscat_s_nid_postfix(strBuf, 16, u"def") == 0 && same(strBuf, u"abcdef", 7)); wcsncpy_s_nid_postfix(strBuf, 8, u"12345", 5); + require(wcscat_s_nid_postfix(strBuf, 8, u"6789") == 34 && strBuf[0] == 0); require(wcscat_s_nid_postfix(nullptr, 8, u"a") == 22); wcsncpy_s_nid_postfix(strBuf, 8, u"123", 3); + require(wcscat_s_nid_postfix(strBuf, 8, nullptr) == 22 && strBuf[0] == 0); const char16_t* text = u"a世b世c"; @@ -341,11 +327,23 @@ int main() { require(wcstod_nid_postfix(scientific, &end) == 350.0 && end == scientific + 5); require(wcstod_nid_postfix(letters, &end) == 0.0 && end == letters); require(wcstof_nid_postfix(u"0.25", nullptr) == 0.25f); + const char16_t* sign = u" -x"; + require(std::signbit(wcstof_nid_postfix(sign, &end)) && end == sign && std::signbit(wcstold_nid_postfix(sign, &end)) && !std::signbit(wcstod_nid_postfix(sign, &end))); + *__error_nid_postfix() = 0; + require(wcstof_nid_postfix(u"0x1p+200", nullptr) == HUGE_VALF && *__error_nid_postfix() == ERANGE); + *__error_nid_postfix() = 0; + require(wcstof_nid_postfix(u"1e-45", nullptr) == 0x1p-149f && *__error_nid_postfix() == ERANGE); + *__error_nid_postfix() = 0; + require(wcstof_nid_postfix(u"0x1p-149", nullptr) == 0x1p-149f && *__error_nid_postfix() == 0); + require(wcstof_nid_postfix(u"0x1.8p-150", nullptr) == 0x1p-149f && *__error_nid_postfix() == ERANGE); + const char16_t* loneSign = u" -"; + const float loneSignValue = wcstof_nid_postfix(loneSign, &end); + require(loneSignValue == 0.f && !std::signbit(loneSignValue) && end == loneSign && std::signbit(wcstold_nid_postfix(loneSign, &end))); const char16_t* half = u"-1.5é"; 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)) == 0x7fc00000u && end == wideNan + 12); + require(std::bit_cast(wcstof_nid_postfix(wideNan, &end)) == 0xffc00000u && end == wideNan + 12); const long double wideExtended = wcstold_nid_postfix(wideNan, &end); std::uint64_t significand; std::memcpy(&significand, &wideExtended, sizeof(significand)); @@ -357,8 +355,10 @@ int main() { char16_t transformed[8]; wmemset_nid_postfix(transformed, 0xaaaa, 8); + require(wcsxfrm_nid_postfix(transformed, u"wide", 8) == 4 && same(transformed, u"wide", 5) && transformed[5] == 0xaaaa); 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); @@ -382,6 +382,7 @@ int main() { TransformCase{u"\x7fff\x8000\xffff\xd83d\xde00", 6, 5, u"\x7fff\x8000\xffff\xd83d\xde00", 6} }) { wmemset_nid_postfix(transformed, 0xaaaa, 8); + require(wcsxfrm_nid_postfix(transformed, test.source, test.count) == test.length); require(same(transformed, test.expected, test.written)); for (std::size_t index = test.written; index < 8; ++index) require(transformed[index] == 0xaaaa); diff --git a/core/libs/tests/GuestWindowsDirectoryLinkTypes.cpp b/core/libs/tests/GuestWindowsDirectoryLinkTypes.cpp new file mode 100644 index 0000000000..1c5227b3f8 --- /dev/null +++ b/core/libs/tests/GuestWindowsDirectoryLinkTypes.cpp @@ -0,0 +1,197 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Directory link type check failed at line %d\n", line); + std::exit(1); + } +} + +#define Require(value) Check((value), __LINE__) + +static void Junction(const std::filesystem::path& link, const std::filesystem::path& target) { + Require(std::filesystem::create_directory(link)); + const auto printName = std::filesystem::absolute(target).make_preferred().wstring(); + const auto substituteName = L"\\??\\" + printName; + struct MountPoint { + ULONG tag; + USHORT dataLength; + USHORT reserved; + USHORT substituteOffset; + USHORT substituteLength; + USHORT printOffset; + USHORT printLength; + wchar_t names[2048]; + } data{}; + Require(substituteName.size() + printName.size() + 2 <= std::size(data.names)); + data.tag = IO_REPARSE_TAG_MOUNT_POINT; + data.substituteLength = static_cast(substituteName.size() * sizeof(wchar_t)); + data.printOffset = static_cast((substituteName.size() + 1) * sizeof(wchar_t)); + data.printLength = static_cast(printName.size() * sizeof(wchar_t)); + data.dataLength = static_cast(8 + data.printOffset + data.printLength + sizeof(wchar_t)); + std::memcpy(data.names, substituteName.c_str(), data.substituteLength + sizeof(wchar_t)); + std::memcpy(reinterpret_cast(data.names) + data.printOffset, printName.c_str(), data.printLength + sizeof(wchar_t)); + const auto handle = CreateFileW(link.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + Require(handle != INVALID_HANDLE_VALUE); + DWORD returned = 0; + const auto result = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, &data, 8 + data.dataLength, nullptr, 0, &returned, nullptr); + Require(CloseHandle(handle) != 0); + Require(result != 0); +} + +static void OutsideApp0() { + const auto root = std::filesystem::absolute("anyps5-directory-links-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(root)); + Require(std::filesystem::create_directory(root / "directory")); + { std::ofstream file(root / "regular"); file << "data"; } + std::vector links{"junction", "dangling-junction", "loop-junction"}; + Junction(root / links[0], root / "directory"); + Junction(root / links[1], root / "missing"); + Junction(root / links[2], root / links[2]); + if (CreateSymbolicLinkW((root / "file-link").c_str(), L"regular", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) { + links.push_back("file-link"); + Require(CreateSymbolicLinkW((root / "directory-link").c_str(), L"directory", + SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); + links.push_back("directory-link"); + Require(CreateSymbolicLinkW((root / "dangling-link").c_str(), L"missing", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); + links.push_back("dangling-link"); + } else { + Require(GetLastError() == ERROR_PRIVILEGE_NOT_HELD); + std::fprintf(stderr, "Symbolic-link cases require Windows developer mode or symbolic-link privilege; junction cases still run\n"); + } + const int descriptor = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); + Require(descriptor >= 0); + std::map entries; + std::array buffer{}; + for (;;) { + const int bytes = sceKernelGetdents(descriptor, buffer.data(), static_cast(buffer.size())); + Require(bytes >= 0 && bytes <= static_cast(buffer.size())); + if (bytes == 0) break; + for (int offset = 0; offset < bytes;) { + std::uint16_t length = 0; + std::memcpy(&length, buffer.data() + offset + 4, sizeof(length)); + const auto nameLength = static_cast(buffer[offset + 7]); + Require(length >= 8 + nameLength + 1 && offset + length <= bytes); + const std::string name(buffer.data() + offset + 8, nameLength); + Require(entries.emplace(name, static_cast(buffer[offset + 6])).second); + offset += length; + } + } + Require(sceKernelClose(descriptor) == 0); + Require(entries.size() == links.size() + 4); + Require(entries.at(".") == 4 && entries.at("..") == 4); + Require(entries.at("directory") == 4 && entries.at("regular") == 8); + for (const auto& name : links) { + if (entries.at(name) != 10) + std::fprintf(stderr, "%s: getdents type %u, expected DT_LNK (10)\n", name.c_str(), entries.at(name)); + Require(entries.at(name) == 10); + const auto path = root / name; + FileStat status{}; + Require(lstat_nid_postfix(path.string().c_str(), &status) == 0); + Require((status.st_mode & 0170000) == 0120000); + } + for (const auto& name : links) Require(RemoveDirectoryW((root / name).c_str()) != 0 || DeleteFileW((root / name).c_str()) != 0); + Require(std::filesystem::remove_all(root) == 3); +} + + +static std::map Types(const char* path) { + const int descriptor = sceKernelOpen(path, SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); + Require(descriptor >= 0); + std::map entries; + std::array buffer{}; + for (;;) { + const int bytes = sceKernelGetdents(descriptor, buffer.data(), static_cast(buffer.size())); + Require(bytes >= 0 && bytes <= static_cast(buffer.size())); + if (bytes == 0) break; + for (int offset = 0; offset < bytes;) { + std::uint16_t length = 0; + std::memcpy(&length, buffer.data() + offset + 4, sizeof(length)); + const auto nameLength = static_cast(buffer[offset + 7]); + Require(length >= 8 + nameLength + 1 && offset + length <= bytes); + Require(entries.emplace(std::string(buffer.data() + offset + 8, nameLength), + static_cast(buffer[offset + 6])).second); + offset += length; + } + } + Require(sceKernelClose(descriptor) == 0); + return entries; +} + +int main() { + const auto previous = std::filesystem::current_path(); + const auto root = previous / ("anyps5-app0-links-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directories(root / "app0" / "real")); + Require(std::filesystem::create_directories(root / "external")); + Require(std::filesystem::create_directories(root / "data")); + Require(std::filesystem::create_directories(root / "app0-sibling")); + std::filesystem::current_path(root); + OutsideApp0(); + std::vector links; + const auto junction = [&](const std::filesystem::path& link, const std::filesystem::path& target) { + Junction(root / link, root / target); + links.push_back(link); + }; + junction("app0/packed", "external"); + junction("app0/chain", "app0/packed"); + junction("app0/dangling", "missing"); + junction("app0/loop", "app0/loop"); + junction("app0/real/nested", "external"); + junction("external/nested", "app0/real"); + junction("data/saved", "external"); + junction("app0-sibling/saved", "external"); + { std::ofstream file(root / "external/regular"); file << "data"; } + const bool symbolic = CreateSymbolicLinkW((root / "app0/file-link").c_str(), L"..\\external\\regular", + SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0; + if (symbolic) { + links.push_back("app0/file-link"); + Require(CreateSymbolicLinkW((root / "app0/relative").c_str(), L"..\\external", + SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); + links.push_back("app0/relative"); + } else { + Require(GetLastError() == ERROR_PRIVILEGE_NOT_HELD); + std::fprintf(stderr, "app0 symbolic-link subcases skipped: privilege unavailable\n"); + } + auto app0 = Types("/app0"); + std::fprintf(stderr, "app0 packed/chain/real types: %u/%u/%u (expected 4/4/4)\n", app0.at("packed"), app0.at("chain"), app0.at("real")); + Require(app0.at("real") == 4 && app0.at("packed") == 4 && app0.at("chain") == 4); + Require(app0.at("dangling") == 10 && app0.at("loop") == 10); + if (symbolic) Require(app0.at("file-link") == 8 && app0.at("relative") == 4); + Require(Types("/app0/real").at("nested") == 4); + Require(Types("/app0/packed").at("nested") == 10); + Require(Types("/data").at("saved") == 10); + Require(Types((root / "app0-sibling").string().c_str()).at("saved") == 10); + FileStat status{}; + Require(lstat_nid_postfix("/app0/packed", &status) == 0 && (status.st_mode & 0170000) == 0120000); + for (auto it = links.rbegin(); it != links.rend(); ++it) Require(RemoveDirectoryW((root / *it).c_str()) != 0 || DeleteFileW((root / *it).c_str()) != 0); + std::filesystem::rename(root / "app0", root / "application"); + Junction(root / "app0", root / "application"); + Junction(root / "application/packed", root / "external"); + Require(Types("/app0").at("packed") == 4); + Require(std::filesystem::remove(root / "application/packed")); + Require(std::filesystem::remove(root / "app0")); + std::filesystem::current_path(previous); + Require(root.parent_path() == previous && root.filename().string().starts_with("anyps5-app0-links-")); + std::filesystem::remove_all(root); + std::puts("Windows directory link type tests passed"); +} diff --git a/core/libs/tests/GuestWorkingDirectory.cpp b/core/libs/tests/GuestWorkingDirectory.cpp index de3ea75fc1..f14f721431 100644 --- a/core/libs/tests/GuestWorkingDirectory.cpp +++ b/core/libs/tests/GuestWorkingDirectory.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestHeap.hpp" @@ -8,11 +9,7 @@ #include #include #include -extern "C" { -int APS5_VABI chdir_nid_postfix(const char*); -char* APS5_VABI getcwd_nid_postfix(char*, std::size_t); -int* APS5_VABI __error_nid_postfix(); -} + static void Require(bool value) { if (!value) std::abort(); } static int allocations = 0; static int releases = 0; diff --git a/core/libs/tests/GuestZlib.cpp b/core/libs/tests/GuestZlib.cpp index 50a9688420..9f8a857137 100644 --- a/core/libs/tests/GuestZlib.cpp +++ b/core/libs/tests/GuestZlib.cpp @@ -1,21 +1,22 @@ +#include "prx/libSceZlib/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include +#include +#include #include #include +#include -extern "C" { -std::int32_t APS5_VABI sceZlibInitialize(const void*, std::uint64_t); -std::int32_t APS5_VABI sceZlibFinalize(); -std::int32_t APS5_VABI sceZlibInflate(const void*, std::uint32_t, void*, std::uint32_t, std::uint64_t*); -std::int32_t APS5_VABI sceZlibWaitForDone(std::uint64_t*, std::uint32_t*); -std::int32_t APS5_VABI sceZlibGetResult(std::uint64_t, std::uint32_t*, std::int32_t*); +static void Require(bool value, const std::source_location& location = std::source_location::current()) { + if (value) return; + std::fprintf(stderr, "Zlib assertion failed at line %u\n", location.line()); + std::abort(); } -static void Require(bool value) { if (!value) std::abort(); } - template static void RequireThrows(TAction action) { try { action(); } catch (const std::runtime_error&) { return; } @@ -33,47 +34,110 @@ int main() { std::int32_t status = 0; Require(sceZlibFinalize() == static_cast(0x81120032)); Require(sceZlibInflate(nullptr, 0, nullptr, 0, nullptr) == static_cast(0x81120032)); - RequireThrows([&] { sceZlibInitialize(nullptr, 1); }); + Require(sceZlibInitialize(nullptr, 1) == static_cast(0x81120016)); + Require(sceZlibInitialize(output, 0) == static_cast(0x81120016)); Require(sceZlibInitialize(nullptr, 0) == 0); Require(sceZlibInitialize(nullptr, 0) == static_cast(0x81120033)); - RequireThrows([&] { sceZlibInflate(compressed.data(), compressed.size(), output + 1, expectedLength, &id); }); - RequireThrows([&] { sceZlibInflate(compressed.data(), compressed.size(), output, 65537, &id); }); - RequireThrows([&] { sceZlibInflate(nullptr, compressed.size(), output, expectedLength, &id); }); + RequireThrows([&] { sceZlibInflate(compressed.data(), compressed.size(), output + 1, 8192, &id); }); + for (const auto capacity : {0u, 1u, 2047u, 2049u, 65535u, 65537u}) { + Require(sceZlibInflate(compressed.data(), compressed.size(), output, capacity, &id) == static_cast(0x81120016)); + Require(id == 0); + } + Require(sceZlibInflate(nullptr, compressed.size(), output, 8192, &id) == static_cast(0x81120016)); + Require(sceZlibWaitForDone(nullptr, nullptr) == static_cast(0x81120016)); + Require(sceZlibGetResult(0, nullptr, &status) == static_cast(0x81120016)); + Require(sceZlibGetResult(0, &produced, nullptr) == static_cast(0x81120016)); + Require(sceZlibGetResult(0, &produced, &status) == static_cast(0x81120002)); std::uint64_t previous = 0; auto decode = [&](const void* input, std::uint32_t inputLength, std::uint32_t capacity, std::int32_t expectedStatus) { storage.fill(0xA5); Require(sceZlibInflate(input, inputLength, output, capacity, &id) == 0); Require(id > previous); previous = id; - RequireThrows([&] { sceZlibInflate(input, inputLength, output, capacity, &id); }); - RequireThrows([&] { sceZlibFinalize(); }); - std::uint32_t timeout = 0; - RequireThrows([&] { sceZlibWaitForDone(&id, &timeout); }); std::uint64_t completed; Require(sceZlibWaitForDone(&completed, nullptr) == 0); Require(completed == id); - RequireThrows([&] { sceZlibGetResult(id + 1, &produced, &status); }); + std::uint32_t timeout = 0; + Require(sceZlibWaitForDone(&completed, &timeout) == static_cast(0x81120027)); + Require(sceZlibGetResult(id + 1, &produced, &status) == static_cast(0x81120002)); + Require(sceZlibGetResult(id, nullptr, &status) == static_cast(0x81120016)); Require(sceZlibGetResult(id, &produced, &status) == 0); + if (status != expectedStatus) std::fprintf(stderr, "input=%u capacity=%u status=%x expected=%x produced=%u\n", inputLength, capacity, static_cast(status), static_cast(expectedStatus), produced); Require(status == expectedStatus && produced <= capacity); - RequireThrows([&] { sceZlibGetResult(id, &produced, &status); }); + std::uint32_t producedAgain = 0; + std::int32_t statusAgain = 0; + Require(sceZlibGetResult(id, &producedAgain, &statusAgain) == 0); + Require(producedAgain == produced && statusAgain == status); Require(std::all_of(storage.begin(), storage.begin() + 2048, [](auto byte) { return byte == 0xA5; })); Require(std::all_of(storage.begin() + 2048 + capacity, storage.end(), [](auto byte) { return byte == 0xA5; })); }; - decode(compressed.data(), compressed.size(), expectedLength, 0); + decode(compressed.data(), compressed.size(), 8192, 0); Require(produced == expectedLength); for (std::uint32_t offset = 0; offset < produced; offset += sizeof(text) - 1) Require(std::memcmp(output + offset, text, sizeof(text) - 1) == 0); - decode(compressed.data(), compressed.size(), expectedLength - 1, static_cast(0x8112001C)); + decode(compressed.data(), compressed.size(), 2048, static_cast(0x8112001C)); auto corrupt = compressed; corrupt.back() ^= 1; - decode(corrupt.data(), corrupt.size(), expectedLength, static_cast(0x811200FF)); + decode(corrupt.data(), corrupt.size(), 8192, static_cast(0x811200FF)); decode(compressed.data(), compressed.size() - 1, 65536, static_cast(0x811200FF)); - decode(compressed.data(), compressed.size() - 4, expectedLength, static_cast(0x811200FF)); - decode(compressed.data(), compressed.size() - 1, expectedLength, static_cast(0x811200FF)); + decode(compressed.data(), compressed.size() - 4, 8192, static_cast(0x811200FF)); + decode(compressed.data(), compressed.size() - 1, 8192, static_cast(0x811200FF)); + alignas(2048) std::array batch{}; + std::array ids{}; + for (std::size_t index = 0; index < ids.size(); ++index) + Require(sceZlibInflate(compressed.data(), compressed.size(), batch.data() + index * 8192, 8192, &ids[index]) == 0); + Require(ids[0] < ids[1] && ids[1] < ids[2]); + for (std::uint64_t expected : ids) { + std::uint64_t completed = 0; + Require(sceZlibWaitForDone(&completed, nullptr) == 0); + Require(completed == expected); + } + for (std::size_t index = 0; index < ids.size(); ++index) { + Require(sceZlibGetResult(ids[index], &produced, &status) == 0); + Require(status == 0 && produced == expectedLength); + Require(std::memcmp(batch.data() + index * 8192, text, sizeof(text) - 1) == 0); + } + std::uint64_t completed = 0; + std::uint32_t timeout = 0; + Require(sceZlibInflate(compressed.data(), compressed.size(), output, 8192, &id) == 0); + Require(sceZlibWaitForDone(&completed, &timeout) == 0); + Require(completed == id); + timeout = 20000; + const auto start = std::chrono::steady_clock::now(); + Require(sceZlibWaitForDone(&completed, &timeout) == static_cast(0x81120027)); + const auto waited = std::chrono::steady_clock::now() - start; + Require(waited >= std::chrono::microseconds(20000) && waited < std::chrono::seconds(1)); + std::thread waiter([&] { Require(sceZlibWaitForDone(&completed, nullptr) == 0); }); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + Require(sceZlibInflate(compressed.data(), compressed.size(), output, 8192, &id) == 0); + waiter.join(); + Require(completed == id); + Require(sceZlibInflate(compressed.data(), compressed.size(), output, 8192, &id) == 0); Require(sceZlibFinalize() == 0); + Require(sceZlibInitialize(nullptr, 0) == 0); + Require(sceZlibGetResult(id, &produced, &status) == static_cast(0x81120002)); + bool interrupted = false; + std::thread interruptedWaiter([&] { + std::uint32_t longTimeout = 5000000; + const auto waitStart = std::chrono::steady_clock::now(); + try { + interrupted = sceZlibWaitForDone(&completed, &longTimeout) == static_cast(0x81120032); + } catch (const std::runtime_error&) { + interrupted = true; + } + Require(std::chrono::steady_clock::now() - waitStart < std::chrono::seconds(1)); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + Require(sceZlibFinalize() == 0); + interruptedWaiter.join(); + Require(interrupted); Require(sceZlibGetResult(id, &produced, &status) == static_cast(0x81120032)); std::array scratch{}; - Require(sceZlibInitialize(scratch.data(), scratch.size()) == 0); - decode(compressed.data(), compressed.size(), 65536, 0); + RequireThrows([&] { sceZlibInitialize(scratch.data(), scratch.size()); }); + Require(sceZlibInitialize(nullptr, 0) == 0); + Require(sceZlibInflate(compressed.data(), compressed.size(), output, 65536, &id) == 0); + Require(sceZlibFinalize() == 0); + Require(sceZlibInitialize(nullptr, 0) == 0); + Require(sceZlibGetResult(id, &produced, &status) == static_cast(0x81120002)); Require(sceZlibFinalize() == 0); } diff --git a/core/libs/tests/HevcFixture.hpp b/core/libs/tests/HevcFixture.hpp new file mode 100644 index 0000000000..7bacee3a02 --- /dev/null +++ b/core/libs/tests/HevcFixture.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include +#include +#include +#include + +namespace HevcFixture { + +inline constexpr std::array HevcStream{ + 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, + 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, + 0x95, 0x90, 0x09, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, + 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x96, 0x59, 0x59, 0x2e, + 0x4c, 0xaf, 0x01, 0x68, 0x08, 0x00, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x40, + 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, + 0x08, 0xe0, 0xbb, 0xcb, 0xc1, 0x71, 0x26, 0x0f, 0xbf, 0x6c, 0xcb, 0x4d, 0x2b, 0x2c, 0xf4, 0x08, + 0xe0, 0x17, 0xeb, 0x16, 0xcc, 0x07, 0xd8, 0xdd, 0x11, 0xf8, 0x5e, 0x73, 0x79, 0x57, 0xcb, 0x69, + 0xa3, 0x1c, 0x9c, 0x43, 0x14, 0xdb, 0x61, 0x0e, 0xc5, 0xc6, 0x9c, 0x9c, 0xf8, 0xf2, 0x07, 0x35, + 0xf1, 0x98, 0x6c, 0x1a, 0x86, 0x53, 0x5a, 0xcd, 0x4d, 0xda, 0xff, 0xfb, 0x01, 0xda, 0xc3, 0x61, + 0xb7, 0x8c, 0xae, 0xc9, 0x76, 0xe4, 0x05, 0xd6, 0xfd, 0xe4, 0xcd, 0x93, 0x2c, 0xb1, 0x41, 0x29, + 0xce, 0x22, 0xf7, 0x77, 0x64, 0x0f, 0x1f, 0xd2, 0xc3, 0x68, 0x32, 0x2f, 0x83, 0xd7, 0x53, 0xbe, + 0x23, 0xff, 0x8c, 0x46, 0x18, 0x33, 0x90, 0x28, 0xa3, 0xef, 0xd7, 0xb1, 0x46, 0x84, 0xa0, 0xc2, + 0xd4, 0xd9, 0xce, 0xdf, 0x0a, 0x05, 0xec, 0xad, 0x63, 0xa4, 0x78, 0x27, 0x80, 0x4e, 0x3b, 0x4c, + 0xa6, 0x42, 0xbc, 0xe3, 0x39, 0x4d, 0xc4, 0xd3, 0x44, 0xf1, 0x22, 0xd0, 0x64, 0x9f, 0xd0, 0x88, + 0x7f, 0x39, 0x6d, 0x06, 0xc7, 0x79, 0x75, 0x2a, 0x44, 0x3d, 0x21, 0x41, 0x53, 0x60, 0x8d, 0x08, + 0x9f, 0xd0, 0x34, 0xa6, 0x72, 0x70, 0x3b, 0xa9, 0xfa, 0x62, 0xb1, 0x19, 0x9b, 0x0b, 0xa2, 0xfc, + 0xb4, 0xb3, 0x2c, 0x58, 0xad, 0xc6, 0xf3, 0xea, 0xc4, 0xd0, 0xf9, 0xea, 0x9e, 0xa7, 0xa4, 0x87, + 0xb8, 0x19, 0x68, 0xf1, 0xc1, 0x6b, 0x87, 0xa5, 0x99, 0xa0, 0x69, 0x24, 0x28, 0x80, 0xba, 0xd2, + 0x57, 0xc6, 0xc0, 0x1e, 0x69, 0x1b, 0xce, 0x96, 0xcd, 0x85, 0x9d, 0x59, 0x32, 0x92, 0x71, 0xe7, + 0x15, 0xf2, 0x9b, 0x71, 0x4b, 0xd4, 0xb6, 0x99, 0xb0, 0x71, 0x9b, 0x9b, 0xdb, 0x33, 0x28, 0x86, + 0x5e, 0x44, 0xf3, 0x5d, 0x5d, 0x7b, 0x87, 0x6c, 0x99, 0x8a, 0x6d, 0xaf, 0xf8, 0xdd, 0x39, 0x14, + 0xc0, 0x10, 0x85, 0xaf, 0x3a, 0x77, 0x60, 0x8b, 0xb7, 0x5f, 0x67, 0x45, 0xb4, 0xa7, 0xe7, 0x44, + 0x77, 0x0a, 0xb9, 0xe1, 0xb2, 0xbe, 0xde, 0x1b, 0x5a, 0xb8, 0xc8, 0x9b, 0x1f, 0x15, 0x37, 0xdc, + 0xeb, 0x55, 0x12, 0x37, 0xa1, 0xab, 0x9c, 0xcd, 0xdd, 0x42, 0x5d, 0x90, 0x89, 0xc5, 0x99, 0x54, + 0x6e, 0x9a, 0xd2, 0x71, 0x07, 0xe3, 0x81, 0x20, 0xad, 0xd4, 0x7c, 0xad, 0x69, 0xaf, 0xc7, 0x2d, + 0x7c, 0x84, 0xf1, 0x5d, 0x43, 0x4a, 0x3c, 0xf6, 0xf8, 0x7e, 0x6a, 0xb2, 0xdd, 0x00, 0xda, 0x30, + 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x30, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, 0x19, 0x5f, 0x84, + 0x31, 0x84, 0x50, 0x62, 0xcd, 0x44, 0xa6, 0x4e, 0xbb, 0x2e, 0x87, 0xda, 0x21, 0x25, 0x1c, 0x13, + 0xfe, 0xc2, 0x97, 0xa3, 0xd5, 0xfd, 0xe2, 0x62, 0xd6, 0xe5, 0x50, 0x7a, 0xda, 0x98, 0x8d, 0xcf, + 0xbe, 0x40, 0x5d, 0x69, 0xb6, 0x5f, 0xdc, 0x32, 0x68, 0x05, 0x67, 0x70, 0xfd, 0xa0, 0xd3, 0xcd, + 0x88, 0xeb, 0x12, 0x2e, 0xac, 0xf5, 0x57, 0xa2, 0xc2, 0x81, 0x22, 0x6b, 0x8d, 0x69, 0x6d, 0xac, + 0x41, 0x05, 0x5b, 0x0b, 0x79, 0xe0, 0x86, 0x05, 0x86, 0x8b, 0x02, 0x51, 0x7d, 0x6e, 0x13, 0x63, + 0x36, 0xe6, 0x89, 0x22, 0x75, 0xd9, 0xb0, 0x39, 0x27, 0xcd, 0xac, 0x78, 0xe0, 0x3a, 0x6a, 0x17, + 0x15, 0x05, 0x83, 0x3a, 0xe9, 0x38, 0x7c, 0x75, 0x38, 0xf3, 0xd8, 0x42, 0x57, 0xa1, 0xef, 0x68, + 0xb8, 0xb5, 0x44, 0xfe, 0x4d, 0x66, 0xa1, 0x09, 0xc5, 0x62, 0xff, 0x87, 0x7e, 0xdb, 0x41, 0x6f, + 0x4d, 0x5f, 0x16, 0x49, 0xef, 0xca, 0xda, 0x67, 0x01, 0x29, 0x0b, 0xd6, 0xd8, 0xfc, 0x0b, 0x22, + 0xc8, 0x28, 0xff, 0xb4, 0xb3, 0xa9, 0xf8, 0x9b, 0x78, 0x7d, 0x33, 0xbf, 0xa6, 0x9b, 0x39, 0x64, + 0x57, 0x41, 0x1f, 0x27, 0x05, 0xfc, 0xcd, 0x44, 0xcd, 0xb2, 0xea, 0x79, 0xe0, 0x00, 0x00, 0x00, + 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, 0x02, 0x01, 0xe0, 0x44, 0x97, 0xe1, 0x8f, 0x40, 0x3d, + 0x73, 0xde, 0xdf, 0xf5, 0x1c, 0xff, 0xf6, 0xe2, 0x12, 0x16, 0x3d, 0xb6, 0x75, 0xad, 0x9e, 0xb5, + 0x2b, 0xe7, 0x4a, 0x40, 0xe9, 0x91, 0xd6, 0x86, 0x86, 0x2d, 0xcb, 0xe4, 0xb6, 0x58, 0x4a, 0x2d, + 0x7b, 0x1b, 0xdb, 0xc5, 0x48, 0x2d, 0x09, 0x4d, 0x32, 0x30, 0xde, 0xaa, 0x61, 0xc2, 0x27, 0x64, + 0x54, 0x71, 0x89, 0x32, 0x05, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, + 0x00, 0x01, 0xe0, 0x24, 0xff, 0xfa, 0x2c, 0x6e, 0xdc, 0x78, 0x3d, 0x08, 0x6d, 0x3b, 0xcf, 0xcc, + 0xb6, 0x16, 0xeb, 0x9d, 0x5f, 0x46, 0x0b, 0x24, 0x0e, 0x97, 0x99, 0x06, 0x00, 0x00, 0x00, 0x01, + 0x46, 0x01, 0x30, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, 0x28, 0x95, 0xd7, 0xdc, 0x41, 0xc0, 0x61, + 0x14, 0xd6, 0xee, 0xd4, 0xe8, 0x24, 0x3e, 0x7f, 0x82, 0x22, 0xb0, 0x20, 0xb9, 0xf1, 0x69, 0x9b, + 0xfe, 0xcc, 0xb7, 0x2e, 0xbd, 0xdb, 0x20, 0xfa, 0xe5, 0x97, 0xd9, 0x95, 0x1d, 0x7e, 0x49, 0x35, + 0x69, 0xdb, 0x40, 0xa3, 0x5c, 0x0f, 0x74, 0x6f, 0x00, 0x3d, 0x36, 0x05, 0xc0, 0xc4, 0x37, 0x15, + 0xf9, 0x01, 0x2d, 0xdf, 0xcb, 0x70, 0x3d, 0xfa, 0x47, 0xec, 0x80, 0x07, 0x48, 0x08, 0xc8, 0x54, + 0xd3, 0xe3, 0x00, 0xa3, 0xe4, 0xce, 0x40, 0x05, 0x0b, 0x51, 0x73, 0x56, 0x33, 0x82, 0xa8, 0x0a, + 0xc0, 0xd3, 0xc5, 0xf1, 0xeb, 0x32, 0x8e, 0x4e, 0xa7, 0x6f, 0xb2, 0x64, 0x43, 0x54, 0x71, 0xd4, + 0xaf, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, 0x00, 0x01, 0xe0, 0x82, + 0x2f, 0x5f, 0xdc, 0x63, 0x70, 0x3f, 0xaf, 0x66, 0x75, 0x52, 0xe7, 0x7f, 0x62, 0xfb, 0x84, 0x9c, + 0xef, 0x60, 0x8f, 0xfa, 0x7e, 0x5f, 0x8b, 0x40, 0x15, 0x34, 0x69, 0xe2, 0xc4, 0xfc, 0xff, 0x53, + 0xb8, 0x1e, 0xc7, 0x20, 0x55, 0xdb, 0x5e, 0x05, 0xbd, 0x7a, 0x85, 0xb4, 0xb7, 0xc1, 0x8d, 0xdb, + 0x0f, 0xa6, 0xab, 0xb9, 0x00, 0x2a, 0xdb, 0x83, 0xa0, 0xb0, 0xc5, 0x76, 0x6e, 0xe1, 0xb8, 0x72, + 0xfd, 0x09, 0x61, 0x19, 0xe0, +}; + +inline constexpr std::array HevcMain10Stream{ + 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, + 0xff, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, + 0x95, 0x98, 0x09, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, + 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x93, 0x65, 0x95, 0x9a, + 0xe4, 0xca, 0xf0, 0x16, 0x80, 0x80, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x0f, 0x04, 0x00, + 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, 0x08, + 0x60, 0xf2, 0xfb, 0x04, 0xef, 0x67, 0x67, 0xdc, 0xc7, 0x8e, 0xc7, 0x42, 0x63, 0xeb, 0x7d, 0x28, + 0x73, 0x95, 0x14, 0x0c, 0x71, 0x10, 0x8b, 0x65, 0x04, 0xbd, 0x03, 0xe6, 0x04, 0x35, 0xb9, 0x9b, + 0xec, 0x60, 0x2e, 0xea, 0xcf, 0xd0, 0xdd, 0x2d, 0x3a, 0x62, 0x33, 0x9d, 0x7a, 0x74, 0xb0, 0xeb, + 0x0c, 0x06, 0xe6, 0xa5, 0x30, 0xa7, 0xea, 0xed, 0xc2, 0xa5, 0x8e, 0x7c, 0xda, 0x1b, 0x9d, 0xe4, + 0xac, 0x2b, 0x38, 0xdb, 0x9c, 0xe8, 0x51, 0x34, 0x46, 0xd2, 0x04, 0x1a, 0x77, 0x87, 0x7a, 0x19, + 0x70, 0xf9, 0x4f, 0xcb, 0x03, 0x3c, 0x74, 0x25, 0xb9, 0x92, 0x3b, 0x95, 0xe9, 0xd8, 0xaa, 0x9b, + 0x6b, 0x9b, 0x1f, 0x70, 0x25, 0x2a, 0xe7, 0x6c, 0xc3, 0x7e, 0x55, 0x39, 0xf5, 0x06, 0x5a, 0x23, + 0xb1, 0x2e, 0xf6, 0x01, 0xac, 0x71, 0xb9, 0x20, 0xb4, 0xef, 0x1d, 0x42, 0x7e, 0x0e, 0x01, 0x1d, + 0xaf, 0x0a, 0x13, 0x04, 0x8e, 0x03, 0x4d, 0x1e, 0xc4, 0xe0, +}; + +inline constexpr std::array HevcCroppedStream{ + 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, + 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0x95, 0x98, 0x09, 0x00, 0x00, 0x00, 0x01, + 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x9f, 0x79, 0x65, 0x66, 0xab, 0xca, 0xf0, 0x16, 0x80, 0x80, 0x00, + 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x0f, 0x04, 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, + 0xd1, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, 0x08, 0x59, 0xf2, 0xec, 0x80, 0xf9, 0x31, 0xa3, + 0xfa, 0x90, 0xca, 0x97, 0x42, 0xb5, 0x6f, 0xcb, 0xc8, 0xd2, 0x51, 0xa2, 0x3f, 0xe0, 0x0d, 0x82, + 0x6f, 0x1f, 0x37, 0xd4, 0x65, 0x18, 0x78, 0x60, 0x51, 0x3b, 0x98, 0xd0, 0x44, 0xfa, 0xff, 0x0b, + 0xd5, 0xdc, 0x09, 0xea, 0xf7, 0x79, 0xb4, 0xd2, 0xbc, 0x97, 0x53, 0xf2, 0x7d, 0x10, 0x5c, 0xc8, + 0x3a, 0xe0, 0xa7, 0xd8, 0xe2, 0xfc, 0x1d, 0x80, 0x09, 0xcd, 0x31, 0x09, 0xa4, 0xa9, 0x05, 0x5d, + 0x08, 0x74, 0x50, 0xf5, 0xbe, 0xf9, 0xc8, 0xa6, 0xef, 0xe4, 0xff, 0xd6, 0x86, 0x02, 0xb6, 0xa2, + 0x3f, 0x73, 0x80, 0xdb, 0x4e, 0x76, 0xa8, 0xc7, 0xac, 0x4b, 0x5b, 0xbf, 0xe1, 0x88, 0x18, 0xce, + 0x14, 0x5e, 0xf4, 0xb3, 0x57, 0xe6, 0x83, 0xe1, 0x94, 0x8f, 0x55, 0x77, 0xbb, 0x1c, 0x77, 0xd3, + 0x9e, 0xdc, 0xd5, 0x8a, 0x61, 0xb2, 0xf4, 0x77, 0xb8, 0xd1, 0x27, 0x5c, 0x41, 0x75, 0x0a, 0x82, + 0xf5, 0xe8, 0x32, 0x45, 0xae, 0x94, 0x07, 0xcb, 0xde, 0x35, 0x53, 0x3c, 0x0d, 0xf6, 0x5d, 0x1f, + 0x97, 0x0b, 0x04, 0x90, 0x4b, 0xd3, 0xf6, 0x04, 0x58, 0x26, 0xe2, 0x08, 0xe5, 0xb3, 0xa3, 0x05, + 0x5a, 0x8f, 0x6b, 0x03, 0x4b, 0x0a, 0xd4, 0x63, 0x9d, 0x2e, 0xe2, 0x19, 0xa3, 0xde, 0xb5, 0x53, + 0xf2, 0x07, 0xdc, 0xc8, 0xda, 0x2c, 0xfa, 0x2e, 0x62, 0x39, 0xf3, 0xfa, 0x2a, 0x77, 0xc2, 0xb9, + 0x76, 0x63, 0x58, 0xd3, 0xf8, 0xa6, 0xcb, 0x2b, 0xa4, 0xa8, 0xc8, 0x4a, 0xed, 0xbe, 0x8e, 0xf0, + 0xc9, 0x12, 0xf4, 0xb2, 0x91, 0xad, 0xd2, 0x18, 0xcc, 0x0c, 0x4d, 0x56, 0xd9, 0xa3, 0x09, 0x6c, + 0x01, 0x3a, 0xa4, 0xa5, 0x10, 0x98, 0x4d, 0x85, 0x92, 0xa2, 0xfa, 0x50, 0xfe, 0x2d, 0x42, 0xab, + 0xf3, 0x98, 0xe7, 0x16, 0xf1, 0x0b, 0xac, 0x74, 0x5d, 0xcd, 0xfa, 0xe3, 0x75, 0xf3, 0x66, 0x1e, + 0xf0, 0xa3, 0xad, 0x82, 0x8e, 0x25, 0x2c, 0x8f, 0xe1, 0xac, 0xcb, 0xca, 0x0b, 0x19, 0x32, 0x83, + 0x4a, 0xfd, 0x30, 0xc4, 0xe5, 0x97, 0x73, 0xfd, 0x43, 0xd0, +}; + +inline constexpr std::array HevcHashes{ + 0x7291c31ee57c5065ull, 0x37b15adc47810098ull, 0xc84737b65a65b247ull, + 0xe39bc891f8286267ull, 0x734caf1ba961b3c2ull, 0x7deb44bd1ed30531ull, +}; + +inline std::vector> hevcUnits(const std::uint8_t* stream, std::size_t size) { + std::vector starts; + for (std::size_t index = 0; index + 4 < size; ++index) { + if (stream[index] == 0 && stream[index + 1] == 0 && stream[index + 2] == 0 && stream[index + 3] == 1 && ((stream[index + 4] >> 1u) & 0x3fu) == 35) starts.push_back(index); + } + starts.push_back(size); + std::vector> units; + for (std::size_t unit = 0; unit + 1 < starts.size(); ++unit) units.emplace_back(stream + starts[unit], stream + starts[unit + 1]); + return units; +} + +} diff --git a/core/libs/tests/ImageSampleGather.cpp b/core/libs/tests/ImageSampleGather.cpp index 3d3b40d608..2182e4d5cd 100644 --- a/core/libs/tests/ImageSampleGather.cpp +++ b/core/libs/tests/ImageSampleGather.cpp @@ -62,6 +62,21 @@ static void CheckPckRejectsZeroMask(std::uint32_t encoding) { } Require(rejected); } +static void CheckGatherCompareAdjust(std::uint32_t word0, std::uint32_t opcodeId, RdnaOpcode opcode, std::uint32_t flags, std::uint32_t addressComponents) { + const std::array code{word0, 0x00801e33u}; + const RdnaInstruction instruction = DecodeRdnaMimg(0u, code, 0u); + Require(instruction.op == opcode); + Require(instruction.imageOpcodeId == opcodeId); + Require(instruction.imageSampleFlags == flags); + Require(instruction.imageAddressComponents == addressComponents); + IrProgram program; + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + TranslationContext context(program, block, 256); + context.TranslateInstruction(instruction); + Require(program.Resources().memoryInfo.size() == 1u); + Require(program.Resources().memoryInfo.back().imageSampleFlags == flags); +} static void CheckUnsupportedOpcodeDiagnostic(std::uint32_t encoding, std::string_view expected) { const std::array code{(0x3cu << 26u) | ((encoding & 0x7fu) << 18u) | (encoding >> 7u), 0u}; try { @@ -142,6 +157,8 @@ int main() { CheckPck(0x63u, RdnaOpcode::ImageGather8hPck, 0xFu, 4u); CheckPckRejectsZeroMask(0x62u); CheckPckRejectsZeroMask(0x63u); + CheckGatherCompareAdjust(0xf1200109u, 0xc8u, RdnaOpcode::ImageGather4C, RdnaImageSampleFlagCompare | RdnaImageSampleFlagAdjust, 3u); + CheckGatherCompareAdjust(0xf1600109u, 0xd8u, RdnaOpcode::ImageGather4CO, RdnaImageSampleFlagCompare | RdnaImageSampleFlagOffset | RdnaImageSampleFlagAdjust, 4u); CheckUnsupportedOpcodeDiagnostic(0x06u, "MIMG opcode 0x06"); CheckUnsupportedOpcodeDiagnostic(0xffu, "MIMG opcode 0xff"); } diff --git a/core/libs/tests/InterpolationReferenceRange.cpp b/core/libs/tests/InterpolationReferenceRange.cpp index 7bf9c80ad3..594adbd1d2 100644 --- a/core/libs/tests/InterpolationReferenceRange.cpp +++ b/core/libs/tests/InterpolationReferenceRange.cpp @@ -42,7 +42,9 @@ int main() { } }; const auto attribute = Collect(5u, 1u, 0u, 2u); - expect(attribute.find("interpolation parameter reference is out of range (attribute 5 component 1 mode 0, 2 inputs)") != std::string::npos, "an attribute past the pixel inputs", attribute); + expect(attribute.find("interpolation parameter reference") == std::string::npos, "an attribute past the pixel inputs becomes a default", attribute); + const auto empty = Collect(0u, 0u, 0u, 0u); + expect(empty.find("interpolation parameter reference") == std::string::npos, "attribute 0 with no SPI inputs becomes a default", empty); const auto component = Collect(0u, 4u, 2u, 1u); expect(component.find("(attribute 0 component 4 mode 2, 1 inputs)") != std::string::npos, "a component past w", component); const auto mode = Collect(1u, 3u, 3u, 3u); diff --git a/core/libs/tests/IrDumpMemory.cpp b/core/libs/tests/IrDumpMemory.cpp new file mode 100644 index 0000000000..e68b9567f0 --- /dev/null +++ b/core/libs/tests/IrDumpMemory.cpp @@ -0,0 +1,96 @@ +#include "Translation/TranslationContext.hpp" +#include "RdnaDecoder/RdnaMemoryOpDecoder.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +static void Require(bool value, const std::string& message) { + if (!value) throw std::runtime_error(message); +} + +static RdnaInstruction Smem(std::uint32_t pc, std::uint32_t opcode, std::uint32_t destination, std::uint32_t base, std::uint32_t offset) { + const std::array code{(0x3du << 26u) | (opcode << 18u) | (destination << 6u) | (base / 2u), (125u << 25u) | offset}; + return DecodeRdnaSmem(pc, code, 0u); +} + +static RdnaInstruction Ds(std::uint32_t pc, std::uint32_t opcode, std::uint32_t offset0, std::uint32_t offset1, std::uint32_t address, std::uint32_t destination) { + const std::array code{(0x36u << 26u) | (opcode << 18u) | (offset1 << 8u) | offset0, (destination << 24u) | address}; + return DecodeRdnaDs(pc, code, 0u); +} + +static void Translate(IrProgram& program, std::initializer_list instructions) { + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + program.BlockOrder() = {&block}; + program.Metadata().blockInfo.resize(1); + TranslationContext context(program, block, 256); + for (const auto& instruction : instructions) { + context.TranslateInstruction(instruction); + } +} + +static std::vector Lines(const std::string& dump, const std::string& opcode) { + std::vector lines; + std::istringstream stream(dump); + for (std::string line; std::getline(stream, line);) { + if (line.find(" " + opcode + " ") != std::string::npos) lines.push_back(line); + } + return lines; +} + +static void RequireLines(const std::string& dump, const std::string& opcode, std::initializer_list suffixes) { + const auto lines = Lines(dump, opcode); + Require(lines.size() == suffixes.size(), dump + "\nexpected " + std::to_string(suffixes.size()) + " " + opcode + " lines, found " + std::to_string(lines.size())); + std::size_t index = 0; + for (const std::string suffix : suffixes) { + const auto& line = lines[index++]; + Require(line.size() >= suffix.size() && line.compare(line.size() - suffix.size(), suffix.size(), suffix) == 0, dump + "\nexpected \"" + line + "\" to end with \"" + suffix + "\""); + } +} + +static void CheckOffsets() { + IrProgram program; + Translate(program, { + Smem(0x12de0u, 0x00u, 4u, 0u, 0x54u), + Smem(0x12de8u, 0x0au, 12u, 8u, 0x60u), + Ds(0x12df0u, 0x37u, 3u, 9u, 1u, 2u), + }); + const auto dump = ProgramToString(program); + RequireLines(dump, "LoadAddressU32", {" [pc=0x00012de0 off=0x54]"}); + RequireLines(dump, "ReadConstBuffer", {" [pc=0x00012de8 off=0x60]", " [pc=0x00012de8 off=0x64]", " [pc=0x00012de8 off=0x68]", " [pc=0x00012de8 off=0x6c]"}); + RequireLines(dump, "LoadSharedU32", {" [pc=0x00012df0 off=0xc]", " [pc=0x00012df0 off=0x24]"}); + Require(dump.find(" %1 = GetScalarRegister s0 (U32; uses=1)\n") != std::string::npos, dump + "\nexpected register reads without memory fields"); +} + +static void CheckMissingMemoryInfo() { + IrProgram program; + Translate(program, {Smem(0x40u, 0x00u, 4u, 0u, 0x54u)}); + program.Resources().memoryInfo.clear(); + const auto dump = ProgramToString(program); + RequireLines(dump, "LoadAddressU32", {" [pc=0x00000040 mem=?]"}); + bool rejected = false; + try { + ValidateProgram(program, false); + } catch (const std::runtime_error& error) { + rejected = std::string(error.what()) == "LoadAddressU32 has an invalid memory-info index"; + Require(rejected, std::string("unexpected validation failure: ") + error.what()); + } + Require(rejected, "validation accepted a memory instruction without memory info"); +} + +int main() { + try { + CheckOffsets(); + CheckMissingMemoryInfo(); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } + return 0; +} diff --git a/core/libs/tests/JobManagerComputeProgress.cpp b/core/libs/tests/JobManagerComputeProgress.cpp new file mode 100644 index 0000000000..fb0d90da1c --- /dev/null +++ b/core/libs/tests/JobManagerComputeProgress.cpp @@ -0,0 +1,27 @@ +#include "prx/libSceJobManager/ComputeProgress.hpp" +#include +#include +#include + +int main() { + try { + JobManager::ComputeProgress progress; + if (progress.Publish(0xfffffffd) != 0xfffffffd || progress.Collect(0xfffffffc) != 0xfffffffc) + throw std::runtime_error("partial retirement before wrap failed"); + if (progress.Publish(5) != 2 || progress.Collect(2) != 6 || progress.Collect(2) != 0) + throw std::runtime_error("batched completion across wrap failed"); + if (progress.Publish(9) != 11 || progress.Collect(5) != 3 || progress.Collect(11) != 6) + throw std::runtime_error("partial retirement after wrap failed"); + bool rejected = false; + try { + progress.Collect(12); + } catch (const std::runtime_error&) { + rejected = true; + } + if (!rejected || progress.Collect(11) != 0) throw std::runtime_error("an invalid completion changed retirement state"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/tests/JobManagerFailures.cpp b/core/libs/tests/JobManagerFailures.cpp new file mode 100644 index 0000000000..d6586fcb9b --- /dev/null +++ b/core/libs/tests/JobManagerFailures.cpp @@ -0,0 +1,156 @@ +#include "prx/libSceJobManager/libSceJobManager.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +int failure = 0; +int calls = 0; +int attributes = 0; +int threads = 0; +int queues = 0; +int registrations = 0; + +bool Fail() { return ++calls == failure; } + +void Check(bool condition) { + if (!condition) std::abort(); +} + +struct EventQueue { + std::mutex lock; + std::condition_variable ready; + bool triggered = false; +}; + +} + +extern "C" { + +int APS5_VABI scePthreadAttrInit(PthreadAttr* value) { + if (Fail()) return 12; + *value = reinterpret_cast(new int); + ++attributes; + return 0; +} + +int APS5_VABI scePthreadAttrDestroy(PthreadAttr* value) { + delete reinterpret_cast(*value); + *value = nullptr; + --attributes; + return 0; +} + +int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr*, int) { return Fail() ? 22 : 0; } +int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr*, const KernelSchedParam*) { return Fail() ? 22 : 0; } +int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr*, std::size_t) { return Fail() ? 22 : 0; } +int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr*, KernelCpumask) { return Fail() ? 22 : 0; } + +int APS5_VABI scePthreadCreate(Pthread* value, const PthreadAttr*, PthreadEntry entry, void* argument, const char*) { + if (Fail()) return 11; + *value = reinterpret_cast(new std::thread([=] { entry(argument); })); + ++threads; + return 0; +} + +int APS5_VABI scePthreadJoin(Pthread value, void**) { + auto* thread = reinterpret_cast(value); + thread->join(); + delete thread; + --threads; + return 0; +} + +int APS5_VABI sceKernelCreateEqueue(KernelEqueue* value, const char*) { + if (Fail()) return 12; + *value = reinterpret_cast(new EventQueue); + ++queues; + return 0; +} + +int APS5_VABI sceKernelDeleteEqueue(KernelEqueue value) { + delete reinterpret_cast(value); + --queues; + return 0; +} + +int APS5_VABI sceKernelWaitEqueue(KernelEqueue value, KernelEvent* event, int, int* count, const KernelUseconds*) { + auto& queue = *reinterpret_cast(value); + std::unique_lock guard(queue.lock); + queue.ready.wait(guard, [&queue] { return queue.triggered; }); + event->filter = -11; + event->ident = 0; + *count = 1; + return 0; +} + +int APS5_VABI sceKernelAddUserEventEdge(KernelEqueue, int) { return 0; } + +int APS5_VABI sceKernelTriggerUserEvent(KernelEqueue value, int, void*) { + auto& queue = *reinterpret_cast(value); + const std::lock_guard guard(queue.lock); + queue.triggered = true; + queue.ready.notify_all(); + return 0; +} + +int APS5_VABI sceKernelGetEventFilter(const KernelEvent* event) { return event->filter; } +std::uintptr_t APS5_VABI sceKernelGetEventId(const KernelEvent* event) { return event->ident; } + +int APS5_VABI sceAgcDriverAddEqEvent(KernelEqueue, int, void*) { + if (Fail()) return 22; + ++registrations; + return 0; +} + +int APS5_VABI sceAgcDriverDeleteEqEvent(KernelEqueue, int) { + --registrations; + return 0; +} + + +} + +int main() { + constexpr std::size_t size = 1024 * 1024; + void* memory = ::operator new(size, std::align_val_t{256}); + for (const std::uint32_t computeSequences : {0u, 2u}) { + int failures = 0; + for (failure = 1; failure < 40; ++failure) { + alignas(64) unsigned char manager[0x100]{}; + _ZN3sce3Job10JobManagerC1Ev(manager); + calls = 0; + bool threw = false; + try { + Check(_ZN3sce3Job10JobManager10initializeEPvmjjjmPKNS1_12ConfigParamsE(manager, memory, size, 3, 2, computeSequences, computeSequences ? 5 : 0, nullptr) == 0); + } catch (const std::runtime_error&) { + threw = true; + ++failures; + } + if (threw) { + Check(_ZN3sce3Job10JobManager27getSequenceFactoryInterfaceENS1_8PriorityE(manager, 0) == nullptr); + Check(_ZN3sce3Job10JobManager8shutdownEv(manager) == static_cast(0x81581010u)); + } else { + Check(_ZN3sce3Job10JobManager8shutdownEv(manager) == 0); + } + Check(attributes == 0 && threads == 0 && queues == 0 && registrations == 0); + if (!threw) break; + const int injected = failure; + failure = 0; + Check(_ZN3sce3Job10JobManager10initializeEPvmjjjmPKNS1_12ConfigParamsE(manager, memory, size, 3, 2, computeSequences, computeSequences ? 5 : 0, nullptr) == 0); + Check(_ZN3sce3Job10JobManager8shutdownEv(manager) == 0); + Check(attributes == 0 && threads == 0 && queues == 0 && registrations == 0); + failure = injected; + } + Check(failures == (computeSequences ? 27 : 18)); + std::printf("initialization failures recovered: %d\n", failures); + } + ::operator delete(memory, std::align_val_t{256}); +} diff --git a/core/libs/tests/JobManagerProfiler.cpp b/core/libs/tests/JobManagerProfiler.cpp new file mode 100644 index 0000000000..a2a4bebb5e --- /dev/null +++ b/core/libs/tests/JobManagerProfiler.cpp @@ -0,0 +1,394 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceJobManager/libSceJobManager.hpp" +#include "prx/libSceAgcDriver/libSceAgcDriver.hpp" +#include "prx/libSceAgc/libSceAgc.hpp" +#include "SceTypes.hpp" +#include "prx/libSceAgcDriver/tests/execution/VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace { + +enum class Kind { Sequence, Job, Dispatch, Body }; + +struct Event { + Kind kind; + const char* name; + std::uint16_t manager; + std::uint16_t sequence; + std::uint16_t dispatch; + std::uint16_t value; + std::uint16_t event; + std::uint64_t entry; + std::uint64_t argument; +}; + +std::mutex eventLock; +std::vector events; + +void Check(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "job manager profiler check failed at line %d\n", line); + std::abort(); + } +} + +#define Require(value) Check((value), __LINE__) + +void Record(Event event) { + const std::lock_guard guard(eventLock); + events.push_back(event); +} + +std::vector Snapshot() { + const std::lock_guard guard(eventLock); + return events; +} + +void Clear() { + const std::lock_guard guard(eventLock); + events.clear(); +} + +struct Fixture { + alignas(256) std::array memory{}; + alignas(64) std::array object{}; + ISequenceFactory* factory = nullptr; + std::uint16_t id = 0; + + explicit Fixture(std::uint32_t workers = 1) { + _ZN3sce3Job10JobManagerC1Ev(object.data()); + Require(_ZN3sce3Job10JobManager10initializeEPvmjjjmPKNS1_12ConfigParamsE(object.data(), memory.data(), memory.size(), workers, 2, 2, 0x122, nullptr) == 0); + factory = _ZN3sce3Job10JobManager34getUnifiedSequenceFactoryInterfaceENS1_8PriorityE(object.data(), 0); + id = static_cast(_mm_crc32_u64(0, reinterpret_cast(memory.data())) >> 16); + Clear(); + } + + void Stop() { + Require(_ZN3sce3Job10JobManager8shutdownEv(object.data()) == 0); + } + + ISequence* Create(const char* name, bool compute = false) { + ISequence* sequence = nullptr; + if (compute) Require(factory->CreateCompute(&sequence, 16, 8, 8, 4, name) == 0); + else Require(factory->Create(&sequence, 16, 8, 8, 4, name) == 0); + return sequence; + } +}; + +struct Context { + std::atomic entered{false}; + std::atomic calls{0}; + std::atomic released{true}; +}; + +void APS5_VABI Job(void* argument, std::uint32_t index) { + Record({Kind::Body, nullptr, 0, 0, 0, static_cast(index), 0, 0, reinterpret_cast(argument)}); + auto& context = *static_cast(argument); + ++context.calls; + context.entered.store(true); + while (!context.released.load()) std::this_thread::yield(); +} + +void Await(const std::atomic& flag) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + while (!flag.load() && std::chrono::steady_clock::now() < deadline) std::this_thread::yield(); + Require(flag.load()); +} + +struct Reader { + std::vector records = Snapshot(); + std::size_t next = 0; + std::uint16_t manager; + + explicit Reader(std::uint16_t id) : manager(id) {} + + Event Take(Kind kind) { + Require(next < records.size()); + const Event result = records[next++]; + Require(result.kind == kind); + if (kind != Kind::Body) Require(result.manager == manager); + return result; + } + + void Sequence(const char* name, std::uint16_t id, std::uint16_t event) { + const Event result = Take(Kind::Sequence); + Require(result.name == name && result.sequence == id && result.event == event); + } + + std::uint16_t Dispatch(const char* name, Context& context, std::uint16_t count, std::uint16_t sequence = 0) { + std::uint16_t dispatch = 0; + for (std::uint16_t index = 0; index < count; ++index) { + const Event begin = Take(Kind::Job); + if (index == 0) dispatch = begin.dispatch; + Require(begin.dispatch == dispatch && begin.value == index && begin.event == 16); + const Event body = Take(Kind::Body); + Require(body.value == index && body.argument == reinterpret_cast(&context)); + const Event end = Take(Kind::Job); + Require(end.dispatch == dispatch && end.value == index && end.event == 32); + } + const Event result = Take(Kind::Dispatch); + Require(result.name == name && result.sequence == sequence && result.dispatch == dispatch && result.value == count); + Require(result.entry == reinterpret_cast(&Job) && result.argument == reinterpret_cast(&context)); + return dispatch; + } + + void Done() const { Require(next == records.size()); } +}; + +void EmptyAndIdentityCase() { + Fixture fixture; + const char* cpuName = "cpu"; + const char* gpuName = "compute"; + ISequence* cpu0 = fixture.Create(cpuName); + ISequence* cpu1 = fixture.Create(nullptr); + ISequence* gpu0 = fixture.Create(gpuName, true); + ISequence* gpu1 = fixture.Create(nullptr, true); + ISequence* unused = nullptr; + Require(fixture.factory->Create(&unused, 1, 1, 0, 1, "overflow") == static_cast(0x81580006u)); + Require(fixture.factory->Create(nullptr, 1, 1, 0, 1, "null") == static_cast(0x81580001u)); + for (ISequence* sequence : {cpu0, cpu1, gpu0, gpu1}) { + Require(sequence->SyncPoint() == 0); + Require(sequence->Wait() == 0); + } + Require(fixture.factory->Destroy(cpu0) == 0); + Require(fixture.factory->Destroy(cpu1) == 0); + Require(fixture.factory->DestroyCompute(gpu0) == 0); + Require(fixture.factory->DestroyCompute(gpu1) == 0); + ISequence* reused = fixture.Create(nullptr); + Require(fixture.factory->Destroy(reused) == 0); + fixture.Stop(); + Reader reader(fixture.id); + reader.Sequence(cpuName, 0, 1); + reader.Sequence(nullptr, 1, 1); + reader.Sequence(gpuName, 0, 1); + reader.Sequence(nullptr, 1, 1); + reader.Sequence(cpuName, 0, 2); + reader.Sequence(nullptr, 1, 2); + reader.Sequence(gpuName, 0, 2); + reader.Sequence(nullptr, 1, 2); + reader.Sequence(nullptr, 0, 1); + reader.Sequence(nullptr, 0, 2); + reader.Done(); +} + +void BatchCase(bool sync, bool wait, bool compute = false) { + Fixture fixture; + const char* name = "sequence"; + const char* firstName = "first"; + const char* secondName = "second"; + ISequence* sequence = fixture.Create(name, compute); + const std::uint16_t id = compute ? 2 : 0; + Context first; + Context second; + first.released.store(false); + Require(sequence->Dispatch(Job, &first, 1, firstName) == 0); + Await(first.entered); + if (sync) Require(sequence->SyncPoint() == 0); + if (wait) { + first.released.store(true); + Require(sequence->Wait() == 0); + } + Require(sequence->Dispatch(Job, &second, 4, secondName) == 0); + first.released.store(true); + Require(sequence->Wait() == 0); + if (compute) Require(fixture.factory->DestroyCompute(sequence) == 0); + else Require(fixture.factory->Destroy(sequence) == 0); + fixture.Stop(); + Reader reader(fixture.id); + reader.Sequence(name, 0, 1); + reader.Sequence(name, id, 3); + const auto firstId = reader.Dispatch(firstName, first, 1, id); + if (sync || wait) { + reader.Sequence(name, id, 4); + reader.Sequence(name, id, 3); + } + const auto secondId = reader.Dispatch(secondName, second, 4, id); + if (!wait) Require(firstId != secondId); + reader.Sequence(name, id, 4); + reader.Sequence(name, 0, 2); + reader.Done(); +} + +void ConcurrentCase() { + Fixture fixture(4); + ISequence* sequence = fixture.Create(nullptr); + Context context; + context.released.store(false); + Require(sequence->Dispatch(Job, &context, 8, nullptr) == 0); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + while (context.calls.load() < 4 && std::chrono::steady_clock::now() < deadline) std::this_thread::yield(); + Require(context.calls.load() == 4); + context.released.store(true); + Require(sequence->Wait() == 0); + Require(fixture.factory->Destroy(sequence) == 0); + fixture.Stop(); + Reader reader(fixture.id); + reader.Sequence(nullptr, 0, 1); + reader.Sequence(nullptr, 0, 3); + std::array states{}; + std::uint16_t dispatch = 0; + for (std::size_t index = 0; index < states.size() * 3; ++index) { + Require(reader.next < reader.records.size()); + const Event event = reader.records[reader.next++]; + Require(event.value < states.size()); + auto& state = states[event.value]; + if (event.kind == Kind::Job) { + if (index == 0) dispatch = event.dispatch; + Require(event.manager == fixture.id && event.dispatch == dispatch); + Require((state == 0 && event.event == 16) || (state == 2 && event.event == 32)); + } else { + Require(event.kind == Kind::Body && state == 1 && event.argument == reinterpret_cast(&context)); + } + ++state; + } + for (const auto state : states) Require(state == 3); + const Event metadata = reader.Take(Kind::Dispatch); + Require(metadata.dispatch == dispatch && metadata.value == 8 && metadata.sequence == 0 && metadata.name == nullptr); + Require(metadata.entry == reinterpret_cast(&Job) && metadata.argument == reinterpret_cast(&context)); + reader.Sequence(nullptr, 0, 4); + reader.Sequence(nullptr, 0, 2); + reader.Done(); +} + +void GpuBarrierCase(bool mixed, bool cpuAfter) { + Fixture fixture; + ISequence* sequence = fixture.Create(nullptr, true); + alignas(256) static const std::array code{0xbf810000u}; + const auto address = reinterpret_cast(code.data()); + const std::array registers{{{0x20c, static_cast(address >> 8)}, {0x20d, static_cast(address >> 40) & 0xff}, {0x207, 1}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 0}}}; + const std::array specials{0, 0, 0, 0, 1, 0, 0, 0}; + const ShaderRegister* registerPointer = registers.data(); + const std::uint32_t* specialPointer = specials.data(); + std::array shader{}; + std::memcpy(shader.data() + 0x20, ®isterPointer, sizeof(registerPointer)); + std::memcpy(shader.data() + 0x28, &specialPointer, sizeof(specialPointer)); + shader[0x5c] = static_cast(registers.size()); + alignas(64) std::uint32_t gate = 0; + alignas(256) std::array commands{}; + CommandBuffer buffer{commands.data(), commands.data() + commands.size(), commands.data(), commands.data() + commands.size(), nullptr, nullptr, 0}; + Require(sceAgcAcbWaitRegMem(&buffer, 0, 3, 3, &gate, 1, 0xffffffffu, 0x190) != nullptr); + const Packet packet{commands.data(), static_cast(buffer.cursor_up - commands.data()), 0, {}}; + Require(sceAgcDriverSubmitAcb(0x21, &packet) == 0); + Context context; + if (mixed) { + context.released.store(false); + Require(sequence->Dispatch(Job, &context, 1, nullptr) == 0); + Await(context.entered); + } + Require(sequence->DispatchCompute(shader.data(), nullptr, 1, 1, 1, nullptr) == 0); + Require(sequence->SyncPoint() == 0); + if (cpuAfter) { + Require(sequence->Dispatch(Job, &context, 1, nullptr) == 0); + Require(sequence->SyncPoint() == 0); + } + Require(sequence->DispatchCompute(shader.data(), nullptr, 1, 1, 1, nullptr) == 0); + context.released.store(true); + std::atomic_ref(gate).store(1); + Require(sequence->Wait() == 0); + Require(fixture.factory->DestroyCompute(sequence) == 0); + fixture.Stop(); + Reader reader(fixture.id); + reader.Sequence(nullptr, 0, 1); + reader.Sequence(nullptr, 2, 3); + if (cpuAfter) { + reader.Sequence(nullptr, 2, 4); + reader.Sequence(nullptr, 2, 3); + } + if (mixed || cpuAfter) { + reader.Dispatch(nullptr, context, 1, 2); + reader.Sequence(nullptr, 2, 4); + reader.Sequence(nullptr, 2, 3); + } + reader.Sequence(nullptr, 2, 4); + reader.Sequence(nullptr, 0, 2); + reader.Done(); +} + +void ShutdownCase(bool queued) { + Fixture fixture; + ISequence* sequence = fixture.Create(nullptr); + Context first; + Context canceled; + first.released.store(false); + Require(sequence->Dispatch(Job, &first, 1, nullptr) == 0); + Await(first.entered); + if (queued) { + Require(sequence->SyncPoint() == 0); + Require(sequence->Dispatch(Job, &canceled, 1, "canceled") == 0); + } + std::atomic stopping{false}; + std::thread stopper([&] { + stopping.store(true); + fixture.Stop(); + }); + Await(stopping); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + first.released.store(true); + stopper.join(); + Require(!canceled.entered.load()); + Reader reader(fixture.id); + reader.Sequence(nullptr, 0, 1); + reader.Sequence(nullptr, 0, 3); + reader.Dispatch(nullptr, first, 1); + if (queued) { + reader.Sequence(nullptr, 0, 4); + reader.Sequence(nullptr, 0, 3); + } + reader.Done(); +} + +} + +extern "C" { + +int APS5_VABI sceRazorCpuJobManagerSequence(const char* name, std::uint16_t manager, std::uint16_t sequence, std::uint16_t event) { + Record({Kind::Sequence, name, manager, sequence, 0, 0, event, 0, 0}); + return -1; +} + +int APS5_VABI sceRazorCpuJobManagerJob(std::uint16_t manager, std::uint16_t dispatch, std::uint16_t index, std::uint16_t event) { + Record({Kind::Job, nullptr, manager, 0, dispatch, index, event, 0, 0}); + return -1; +} + +int APS5_VABI sceRazorCpuJobManagerDispatch(const char* name, std::uint16_t manager, std::uint16_t sequence, std::uint16_t dispatch, std::uint16_t count, std::uint64_t entry, std::uint64_t argument) { + Record({Kind::Dispatch, name, manager, sequence, dispatch, count, 0, entry, argument}); + return -1; +} + +} + +int main(int argc, char** argv) { + if (argc == 2 && std::strcmp(argv[1], "gpu") == 0) { + auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + device.reset(); + GpuBarrierCase(false, false); + GpuBarrierCase(false, true); + GpuBarrierCase(true, false); + AgcDriverShutdown_nid_postfix(); + std::puts("job manager GPU profiler tests passed"); + return 0; + } + if (argc != 1) return 2; + EmptyAndIdentityCase(); + BatchCase(false, false); + BatchCase(true, false); + BatchCase(false, true); + BatchCase(true, false, true); + ConcurrentCase(); + ShutdownCase(false); + ShutdownCase(true); + std::puts("job manager profiler tests passed"); +} diff --git a/core/libs/tests/JobManagerReference.cpp b/core/libs/tests/JobManagerReference.cpp new file mode 100644 index 0000000000..f3934c5cf6 --- /dev/null +++ b/core/libs/tests/JobManagerReference.cpp @@ -0,0 +1,97 @@ +#include "prx/libSceJobManager/libSceJobManager.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + + +int RunJobManagerCase(const char* name); + +template +static T Resolve(const char* nid) { + const bool host = std::getenv("ANYPS5_JOB_MANAGER_HOST") != nullptr; + const char* library = host ? "libSceJobManager.prx" : "libSceJobManager.prx.guest.prx"; +#ifdef _WIN32 + HMODULE module = GetModuleHandleA(library); + if (module == nullptr && host) module = LoadLibraryA("libs\\libSceJobManager.prx"); + const FARPROC symbol = module != nullptr ? GetProcAddress(module, nid) : nullptr; + if (symbol == nullptr) { + std::fprintf(stderr, "JobManager reference: missing %s in %s (Windows error %lu)\n", nid, library, GetLastError()); + std::exit(3); + } +#else + static void* module = dlopen(host ? "libs/libSceJobManager.prx" : "app0/sce_module/libSceJobManager.prx.guest.prx", RTLD_NOW | RTLD_LOCAL); + const std::string name = host ? nid : std::string(nid) + "#guest"; + void* symbol = module != nullptr ? dlsym(module, name.c_str()) : nullptr; + if (symbol == nullptr) { + std::fprintf(stderr, "JobManager reference: missing %s in %s (%s)\n", name.c_str(), library, dlerror()); + std::exit(3); + } +#endif + return reinterpret_cast(symbol); +} + +extern "C" { + +void APS5_VABI _ZN3sce3Job10JobManagerC1Ev(void* self) { + static const auto call = Resolve("cwMzzdesLlY"); + call(self); +} + +void APS5_VABI _ZN3sce3Job10JobManagerD1Ev(void* self) { + static const auto call = Resolve("4g1ee34TMH8"); + call(self); +} + +std::uint64_t APS5_VABI _ZN3sce3Job10JobManager27calculateRequiredMemorySizeEPKNS1_21MemorySizeQueryParamsE(const MemorySizeQueryParams* query) { + static const auto call = Resolve("Nm2BhAwYNFk"); + return call(query); +} + +int APS5_VABI _ZN3sce3Job10JobManager10initializeEPvmjjjmPKNS1_12ConfigParamsE(void* self, void* memory, std::uint64_t size, std::uint32_t workers, std::uint32_t sequences, std::uint32_t computeSequences, std::uint64_t mask, const ConfigParams* config) { + static const auto call = Resolve("GS9DSbzC+MY"); + return call(self, memory, size, workers, sequences, computeSequences, mask, config); +} + +int APS5_VABI _ZN3sce3Job10JobManager10initializeEPvmjjPKNS1_12ConfigParamsE(void* self, void* memory, std::uint64_t size, std::uint32_t workers, std::uint32_t sequences, const ConfigParams* config) { + static const auto call = Resolve("d205iUFMyyE"); + return call(self, memory, size, workers, sequences, config); +} + +int APS5_VABI _ZN3sce3Job10JobManager8shutdownEv(void* self) { + static const auto call = Resolve("chxnvsxxfBY"); + return call(self); +} + +ISequenceFactory* APS5_VABI _ZN3sce3Job10JobManager27getSequenceFactoryInterfaceENS1_8PriorityE(void* self, std::uint32_t priority) { + static const auto call = Resolve("W-yXgQmaozU"); + return call(self, priority); +} + +ISequenceFactory* APS5_VABI _ZN3sce3Job10JobManager34getUnifiedSequenceFactoryInterfaceENS1_8PriorityE(void* self, std::uint32_t priority) { + static const auto call = Resolve("L5jTAtk3HKA"); + return call(self, priority); +} + +int APS5_VABI _ZN3sce3Job10JobManager24setPersistentThreadCountEj(void* self, std::uint32_t count) { + static const auto call = Resolve("VTKezfYu-v4"); + return call(self, count); +} + +int APS5_VABI Run_nid_no_patch() { + const char* name = std::getenv("ANYPS5_JOB_MANAGER_CASE"); + const int result = name != nullptr ? RunJobManagerCase(name) : 2; +#ifdef _WIN32 + return result; +#else + std::exit(result); +#endif +} + +} diff --git a/core/libs/tests/JobManagerRegistration.cpp b/core/libs/tests/JobManagerRegistration.cpp new file mode 100644 index 0000000000..056b51a6b3 --- /dev/null +++ b/core/libs/tests/JobManagerRegistration.cpp @@ -0,0 +1,86 @@ +#include "prx/libSceJobManager/libSceJobManager.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +namespace { + +alignas(256) std::array workArea{}; +alignas(64) std::array manager{}; +std::uint32_t registrationCalls = 0; +std::uint32_t unregistrationCalls = 0; +std::uint32_t assignedId = 0; +bool writeId = true; +int registrationResult = 0; +std::atomic jobs{0}; + +void Require(bool condition) { + if (!condition) std::abort(); +} + +int Initialize(bool unified, void* memory) { + if (unified) return _ZN3sce3Job10JobManager10initializeEPvmjjjmPKNS1_12ConfigParamsE(manager.data(), memory, workArea.size(), 1, 1, 0, 0, nullptr); + return _ZN3sce3Job10JobManager10initializeEPvmjjPKNS1_12ConfigParamsE(manager.data(), memory, workArea.size(), 1, 1, nullptr); +} + +void APS5_VABI Count(void*, std::uint32_t) { + ++jobs; +} + +} + +extern "C" { + +int APS5_VABI _sceUlobjmgrRegisterObject_nid_postfix(std::uint64_t object, std::int32_t kind, std::uint32_t* id) { + Require(object == reinterpret_cast(workArea.data()) && kind == 0x21 && id != nullptr); + Require(workArea[0] == 0x4a && workArea[1] == 0x4d); + Require(_ZN3sce3Job10JobManager27getSequenceFactoryInterfaceENS1_8PriorityE(manager.data(), 0) != nullptr); + ++registrationCalls; + if (writeId) *id = assignedId; + return registrationResult; +} + +int APS5_VABI _sceUlobjmgrUnregisterObject_nid_postfix(std::uint32_t id) { + Require(id == (writeId ? assignedId : 0)); + Require(_ZN3sce3Job10JobManager27getSequenceFactoryInterfaceENS1_8PriorityE(manager.data(), 0) != nullptr); + ++unregistrationCalls; + return 37; +} + +} + +int main() { + for (const bool unified : {false, true}) { + for (const auto [result, assignsId] : std::array, 5>{{{0, true}, {22, true}, {static_cast(0x8002000cu), true}, {22, false}, {static_cast(0x8002000cu), false}}}) { + for (const bool destructor : {false, true}) { + _ZN3sce3Job10JobManagerC1Ev(manager.data()); + const auto before = registrationCalls; + Require(Initialize(unified, nullptr) == static_cast(0x81581001u)); + Require(registrationCalls == before); + registrationResult = result; + writeId = assignsId; + assignedId = 101 + before; + Require(Initialize(unified, workArea.data()) == result); + Require(registrationCalls == before + 1 && unregistrationCalls == before); + Require(Initialize(unified, workArea.data()) == static_cast(0x81581020u)); + Require(registrationCalls == before + 1); + auto* factory = _ZN3sce3Job10JobManager27getSequenceFactoryInterfaceENS1_8PriorityE(manager.data(), 0); + ISequence* sequence = nullptr; + Require(factory->Create(&sequence, 3, 1, 0, 1, nullptr) == 0); + jobs = 0; + Require(sequence->Dispatch(Count, nullptr, 3, nullptr) == 0); + Require(factory->Destroy(sequence) == 0 && jobs == 3); + if (destructor) _ZN3sce3Job10JobManagerD1Ev(manager.data()); + else Require(_ZN3sce3Job10JobManager8shutdownEv(manager.data()) == 0); + Require(unregistrationCalls == before + 1); + Require(_ZN3sce3Job10JobManager8shutdownEv(manager.data()) == static_cast(0x81581010u)); + _ZN3sce3Job10JobManagerD1Ev(manager.data()); + Require(unregistrationCalls == before + 1); + } + } + } + std::puts("job manager registration tests passed"); +} diff --git a/core/libs/tests/JsonCopyFailure.cpp b/core/libs/tests/JsonCopyFailure.cpp index 0fa28bad98..b8e2562656 100644 --- a/core/libs/tests/JsonCopyFailure.cpp +++ b/core/libs/tests/JsonCopyFailure.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceJson2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -5,49 +6,10 @@ #include #include #include +#include +#include -struct Value { void* node; }; -struct String { void* text; }; -struct Array { void* items; }; -struct Object { void* items; }; -struct Pair { String key; std::uint64_t reserved; Value value; }; - -extern "C" { -void APS5_VABI _ZN3sce4Json5ValueC1Ev(Value*); -void APS5_VABI _ZN3sce4Json5ValueC1EPKc(Value*, const char*); -void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6StringE(Value*, const String*); -void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(Value*, const Array*); -void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(Value*, const Object*); -void APS5_VABI _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(Value*, std::int32_t); -void APS5_VABI _ZN3sce4Json5ValueC1ERKS1_(Value*, const Value*); -void APS5_VABI _ZN3sce4Json5ValueD1Ev(Value*); -Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(Value*, const Value*); -int APS5_VABI _ZN3sce4Json5Value3setERKNS0_5ArrayE(Value*, const Array*); -int APS5_VABI _ZN3sce4Json5Value3setERKNS0_6ObjectE(Value*, const Object*); -int APS5_VABI _ZN3sce4Json5Value3setENS0_9ValueTypeE(Value*, std::int32_t); -std::int32_t APS5_VABI _ZNK3sce4Json5Value7getTypeEv(const Value*); -const Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const Value*); -const Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const Value*); -int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringE(Value*, String*); -int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value*, const char*, std::size_t); -void APS5_VABI _ZN3sce4Json6StringC1Ev(String*); -void APS5_VABI _ZN3sce4Json6StringC1EPKc(String*, const char*); -void APS5_VABI _ZN3sce4Json6StringD1Ev(String*); -const char* APS5_VABI _ZNK3sce4Json6String5c_strEv(const String*); -void APS5_VABI _ZN3sce4Json5ArrayC1Ev(Array*); -void APS5_VABI _ZN3sce4Json5ArrayC1ERKS1_(Array*, const Array*); -void APS5_VABI _ZN3sce4Json5ArrayD1Ev(Array*); -void APS5_VABI _ZN3sce4Json5Array9push_backERKNS0_5ValueE(Array*, const Value*); -std::size_t APS5_VABI _ZNK3sce4Json5Array4sizeEv(const Array*); -const Value* APS5_VABI _ZNK3sce4Json5Array5frontEv(const Array*); -void APS5_VABI _ZN3sce4Json6ObjectC1Ev(Object*); -void APS5_VABI _ZN3sce4Json6ObjectC1ERKS1_(Object*, const Object*); -void APS5_VABI _ZN3sce4Json6ObjectD1Ev(Object*); -Object* APS5_VABI _ZN3sce4Json6ObjectaSERKS1_(Object*, const Object*); -std::size_t APS5_VABI _ZNK3sce4Json6Object4sizeEv(const Object*); -void APS5_VABI _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(Pair*, const String*, const Value*); -void APS5_VABI _ZN3sce4Json6Object4PairD1Ev(Pair*); -} +using namespace SceJson2; static std::ptrdiff_t allocationBudget = -1; static std::size_t liveAllocations = 0; @@ -86,9 +48,112 @@ static void Require(bool condition, const char* message) { static void CheckValue(const Value& value, const char* expected) { String output{}; _ZN3sce4Json6StringC1Ev(&output); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(const_cast(&value), &output) == 0, "serialization failed"); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&output), expected) == 0, "value changed"); _ZN3sce4Json6StringD1Ev(&output); + +} + +struct HandlerContext { + Value fallback{}; + const Value* parent = nullptr; + unsigned calls = 0; +}; + +static const Value& APS5_VABI OnTypeMismatch(ValueType requested, const Value* value, const Value* second, void* context) { + auto& handler = *static_cast(context); + Require(requested == 2 && value == handler.parent && second == nullptr, "handler arguments changed"); + ++handler.calls; + return handler.fallback; +} + +static void CheckHandler(HandlerContext& handler) { + Require(*_ZNK3sce4Json5Value10getIntegerEv(handler.parent) == 77 && handler.calls == 1, "handler was lost"); +} + +static int APS5_VABI ReadNext(char& character, void* context) { + auto& cursor = *static_cast(context); + character = *cursor; + if (!character) return -1; + ++cursor; + return 0; +} + +static bool Parse(const char* document, bool valid, bool callback, const char* expected = nullptr) { + Value result{}; + HandlerContext handler{}; + _ZN3sce4Json5ValueC1EPKc(&result, "previous"); + _ZN3sce4Json5ValueC1El(&handler.fallback, 77); + handler.parent = &result; + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&result, OnTypeMismatch, &handler) == 0, "set handler failed"); + bool completed = false; + allocationBudget = activeBudget; + try { + const char* cursor = document; + const int status = callback ? _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&result, ReadNext, &cursor) + : _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&result, document, std::strlen(document)); + Require(status == (valid ? 0 : (callback ? -1 : static_cast(0x80848101))), "unexpected parse result"); + completed = true; + } catch (const std::bad_alloc&) { + } + allocationBudget = -1; + CheckValue(result, completed && valid ? (expected ? expected : document) : "\"previous\""); + CheckHandler(handler); + _ZN3sce4Json5ValueD1Ev(&result); + _ZN3sce4Json5ValueD1Ev(&handler.fallback); + return completed; +} + +static bool ObjectEntry(const char* keyText) { + Object object{}; + String oldKey{}, newKey{}; + HandlerContext handler{}; + _ZN3sce4Json6ObjectC1Ev(&object); + _ZN3sce4Json6StringC1EPKc(&oldKey, "kept"); + _ZN3sce4Json6StringC1EPKc(&newKey, keyText); + Value* kept = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &oldKey); + Require(_ZN3sce4Json5Value3setEPKc(kept, "previous") == 0, "prepare entry failed"); + _ZN3sce4Json5ValueC1El(&handler.fallback, 77); + handler.parent = kept; + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(kept, OnTypeMismatch, &handler) == 0, "set handler failed"); + bool completed = false; + Value* inserted = nullptr; + allocationBudget = activeBudget; + try { + inserted = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &newKey); + completed = true; + } catch (const std::bad_alloc&) { + } + allocationBudget = 0; + Require(_ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &oldKey) == kept, "existing entry changed"); + allocationBudget = -1; + Require(_ZNK3sce4Json6Object4sizeEv(&object) == (completed ? 2 : 1), "failed insertion changed object size"); + if (completed) Require(inserted && _ZNK3sce4Json5Value7getTypeEv(inserted) == 0, "new entry is not null"); + CheckValue(*kept, "\"previous\""); + CheckHandler(handler); + _ZN3sce4Json6ObjectD1Ev(&object); + _ZN3sce4Json6StringD1Ev(&newKey); + _ZN3sce4Json6StringD1Ev(&oldKey); + _ZN3sce4Json5ValueD1Ev(&handler.fallback); + return completed; +} + +template +static unsigned CheckFailures(const char* name, TOperation operation) { + activeCase = name; + bool succeeded = false; + unsigned failures = 0; + for (activeBudget = 0; activeBudget < 256 && !succeeded; ++activeBudget) { + const auto before = liveAllocations; + succeeded = operation(); + if (liveAllocations != before) std::fprintf(stderr, "expected %zu live allocations, got %zu\n", before, liveAllocations); + Require(liveAllocations == before, "operation leaked allocations"); + failures += !succeeded; + } + Require(succeeded, "operation never succeeded"); + std::printf("%s: %u allocation failures, no leaks\n", name, failures); + return failures; } enum class Operation { @@ -106,6 +171,7 @@ static bool Run(Operation operation, const Value& source, const char* expected, const bool typeSetter = operation == Operation::SetTypeArray || operation == Operation::SetTypeObject || operation == Operation::SetTypeString; if (operation == Operation::ValueAssign || operation == Operation::SetArray || operation == Operation::SetObject || typeSetter) { _ZN3sce4Json5ValueC1EPKc(&result, "previous"); + valueReady = true; } if (operation == Operation::ArrayAppend) { _ZN3sce4Json5ArrayC1Ev(&arrayResult); arrayReady = true; } @@ -124,14 +190,14 @@ static bool Run(Operation operation, const Value& source, const char* expected, case Operation::ValueObject: _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&result, &object); valueReady = true; break; case Operation::ValueString: _ZN3sce4Json5ValueC1ERKNS0_6StringE(&result, &text); valueReady = true; break; case Operation::ValueText: _ZN3sce4Json5ValueC1EPKc(&result, _ZNK3sce4Json6String5c_strEv(&text)); valueReady = true; break; - case Operation::ValueTypeString: _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&result, 5); valueReady = true; break; - case Operation::ValueTypeArray: _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&result, 6); valueReady = true; break; - case Operation::ValueTypeObject: _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&result, 7); valueReady = true; break; + case Operation::ValueTypeString: _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&result, static_cast(5)); valueReady = true; break; + case Operation::ValueTypeArray: _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&result, static_cast(6)); valueReady = true; break; + case Operation::ValueTypeObject: _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&result, static_cast(7)); valueReady = true; break; case Operation::SetArray: _ZN3sce4Json5Value3setERKNS0_5ArrayE(&result, &array); break; case Operation::SetObject: _ZN3sce4Json5Value3setERKNS0_6ObjectE(&result, &object); break; - case Operation::SetTypeArray: _ZN3sce4Json5Value3setENS0_9ValueTypeE(&result, 6); break; - case Operation::SetTypeObject: _ZN3sce4Json5Value3setENS0_9ValueTypeE(&result, 7); break; - case Operation::SetTypeString: _ZN3sce4Json5Value3setENS0_9ValueTypeE(&result, 5); break; + case Operation::SetTypeArray: _ZN3sce4Json5Value3setENS0_9ValueTypeE(&result, static_cast(6)); break; + case Operation::SetTypeObject: _ZN3sce4Json5Value3setENS0_9ValueTypeE(&result, static_cast(7)); break; + case Operation::SetTypeString: _ZN3sce4Json5Value3setENS0_9ValueTypeE(&result, static_cast(5)); break; case Operation::PairCopy: _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &text, &source); pairReady = true; break; } succeeded = true; @@ -145,16 +211,19 @@ static bool Run(Operation operation, const Value& source, const char* expected, CheckValue(result, "\"previous\""); } _ZN3sce4Json5ValueD1Ev(&result); + } if (arrayReady) { const auto count = _ZNK3sce4Json5Array4sizeEv(&arrayResult); Require(count == (succeeded ? (operation == Operation::ArrayAppend ? 1 : 3) : 0), "array contents changed"); if (succeeded && operation == Operation::ArrayAppend) CheckValue(*_ZNK3sce4Json5Array5frontEv(&arrayResult), expected); _ZN3sce4Json5ArrayD1Ev(&arrayResult); + } if (objectReady) { Require(_ZNK3sce4Json6Object4sizeEv(&objectResult) == (succeeded ? 2 : 0), "object contents changed"); _ZN3sce4Json6ObjectD1Ev(&objectResult); + } if (pairReady) { CheckValue(pair.value, expected); _ZN3sce4Json6Object4PairD1Ev(&pair); } return succeeded; @@ -169,9 +238,11 @@ int main() { String text{}; _ZN3sce4Json5ValueC1Ev(&objectValue); _ZN3sce4Json5ValueC1Ev(&arrayValue); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&objectValue, objectJson, std::strlen(objectJson)) == 0, "prepare object"); Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&arrayValue, arrayJson, std::strlen(arrayJson)) == 0, "prepare array"); _ZN3sce4Json6StringC1EPKc(&text, "a sufficiently long string to require its own allocation"); + const auto* array = _ZNK3sce4Json5Value8getArrayEv(&arrayValue); const auto* object = _ZNK3sce4Json5Value9getObjectEv(&objectValue); struct Case { const char* name; Operation operation; const Value* source; const char* expected; }; @@ -199,22 +270,97 @@ int main() { }; unsigned failures = 0; for (const auto& test : cases) { - activeCase = test.name; - bool succeeded = false; - for (activeBudget = 0; activeBudget < 256 && !succeeded; ++activeBudget) { - const auto before = liveAllocations; - succeeded = Run(test.operation, *test.source, test.expected, *array, *object, text); - if (liveAllocations != before) std::fprintf(stderr, "expected %zu live allocations, got %zu\n", before, liveAllocations); - Require(liveAllocations == before, "copy leaked allocations"); - failures += !succeeded; + failures += CheckFailures(test.name, [&] { + const bool succeeded = Run(test.operation, *test.source, test.expected, *array, *object, text); CheckValue(objectValue, objectJson); CheckValue(arrayValue, arrayJson); - } - Require(succeeded, "copy never succeeded"); + return succeeded; + }); } + const auto checkIterator = [&]() { + constexpr bool objectIterator = TObject; + using TIterator = std::conditional_t; + activeCase = objectIterator ? "object iterator copy" : "array iterator copy"; + const auto before = liveAllocations; + TIterator source{}; + if constexpr (objectIterator) _ZNK3sce4Json6Object5beginEv(&source, object); + else _ZNK3sce4Json5Array5beginEv(&source, array); + for (activeBudget = 0; activeBudget <= 1; ++activeBudget) { + TIterator copy{}; + bool succeeded = false; + allocationBudget = activeBudget; + try { + if constexpr (objectIterator) _ZN3sce4Json6Object8iteratorC1ERKS2_(©, &source); + else _ZN3sce4Json5Array8iteratorC1ERKS2_(©, &source); + succeeded = true; + } catch (const std::bad_alloc&) { + ++failures; + } + allocationBudget = -1; + Require(succeeded == (activeBudget == 1), "iterator allocation failure ignored"); + if (succeeded) Require(copy.state != source.state, "iterator copy shares owned storage"); + if constexpr (objectIterator) _ZN3sce4Json6Object8iteratorD1Ev(©); + else _ZN3sce4Json5Array8iteratorD1Ev(©); + } + if constexpr (objectIterator) _ZN3sce4Json6Object8iteratorD1Ev(&source); + else _ZN3sce4Json5Array8iteratorD1Ev(&source); + Require(liveAllocations == before, "iterator copy leaked allocations"); + }; + checkIterator.operator()(); + checkIterator.operator()(); + activeCase = "value swap"; + bool swapSucceeded = false; + for (activeBudget = 0; activeBudget < 256 && !swapSucceeded; ++activeBudget) { + const auto before = liveAllocations; + Value first{}, second{}; + _ZN3sce4Json5ValueC1ERKS1_(&first, &objectValue); + _ZN3sce4Json5ValueC1ERKS1_(&second, &arrayValue); + + allocationBudget = activeBudget; + try { + _ZN3sce4Json5Value4swapERS1_(&first, &second); + + swapSucceeded = true; + } catch (const std::bad_alloc&) { + ++failures; + } + allocationBudget = -1; + if (swapSucceeded) { + CheckValue(first, arrayJson); + CheckValue(second, objectJson); + } + _ZN3sce4Json5ValueD1Ev(&first); + _ZN3sce4Json5ValueD1Ev(&second); + + Require(liveAllocations == before, "swap leaked allocations"); + CheckValue(objectValue, objectJson); + CheckValue(arrayValue, arrayJson); + } + Require(swapSucceeded, "swap never succeeded"); + struct ParseCase { const char* name; const char* document; bool valid; bool callback; const char* expected = nullptr; }; + const ParseCase parseCases[] = { + {"parse empty array", "[]", true, false}, + {"parse empty object", "{}", true, false}, + {"parse array", "[1]", true, false}, + {"parse object", "{\"a\":1}", true, false}, + {"parse nested array", arrayJson, true, false}, + {"parse nested object", objectJson, true, false}, + {"parse long key", "{\"a sufficiently long key to require its own allocation\":{\"child\":[1,2]}}", true, false}, + {"parse invalid array", "[1,{\"a\":[true,null]}", false, false}, + {"parse invalid object", "{\"a\":[{\"b\":\"a sufficiently long string allocation\"},[true,null,17]]", false, false}, + {"parse trailing input", "{\"a\":[1]}x", true, false, "{\"a\":[1]}"}, + {"parse callback", objectJson, true, true}, + {"parse invalid callback", "[{\"a\":[1]}", false, true}, + }; + for (const auto& test : parseCases) + failures += CheckFailures(test.name, [&] { return Parse(test.document, test.valid, test.callback, test.expected); }); + failures += CheckFailures("insert short key", [] { return ObjectEntry("missing"); }); + failures += CheckFailures("insert long key", [&] { return ObjectEntry(_ZNK3sce4Json6String5c_strEv(&text)); }); _ZN3sce4Json6StringD1Ev(&text); _ZN3sce4Json5ValueD1Ev(&arrayValue); _ZN3sce4Json5ValueD1Ev(&objectValue); + Require(liveAllocations == initial, "source cleanup leaked allocations"); - std::printf("JSON copy cleanup: %zu operations, %u allocation failures, no leaks\n", sizeof(cases) / sizeof(cases[0]), failures); + std::printf("JSON allocation cleanup: %zu operations, %u allocation failures, no leaks\n", + sizeof(cases) / sizeof(cases[0]) + sizeof(parseCases) / sizeof(parseCases[0]) + 5, failures); } diff --git a/core/libs/tests/KernelError.cpp b/core/libs/tests/KernelError.cpp index 9355bd4b64..d0c1aa0bac 100644 --- a/core/libs/tests/KernelError.cpp +++ b/core/libs/tests/KernelError.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include @@ -6,21 +8,21 @@ #include #include -extern "C" std::int32_t APS5_VABI sceKernelError(std::int32_t); -extern "C" int* APS5_VABI __error_nid_postfix(); - static void Require(bool value) { if (!value) throw std::runtime_error("Kernel error conversion check failed"); } int main() { *__error_nid_postfix() = 123; - const std::array, 6> cases{{ + const std::array, 8> cases{{ {0, 0}, {1, SCE_KERNEL_ERROR_EPERM}, {2, SCE_KERNEL_ERROR_ENOENT}, - {4, SCE_KERNEL_ERROR_EINTR}, {14, SCE_KERNEL_ERROR_EFAULT}, {22, SCE_KERNEL_ERROR_EINVAL} + {4, SCE_KERNEL_ERROR_EINTR}, {14, SCE_KERNEL_ERROR_EFAULT}, {22, SCE_KERNEL_ERROR_EINVAL}, + {95, SCE_KERNEL_ERROR_ENOTRECOVERABLE}, {96, SCE_KERNEL_ERROR_EOWNERDEAD} }}; for (const auto [input, expected] : cases) { Require(sceKernelError(input) == expected); Require(*__error_nid_postfix() == 123); } + Require(SCE_KERNEL_ERROR_ENOTRECOVERABLE == static_cast(0x8002005fu)); + Require(SCE_KERNEL_ERROR_EOWNERDEAD == static_cast(0x80020060u)); Require(sceKernelError(-1) == static_cast(0x8001ffffu)); Require(sceKernelError(std::numeric_limits::max()) == 0x1ffff); Require(sceKernelError(std::numeric_limits::min()) == 0x20000); diff --git a/core/libs/tests/Mspace.cpp b/core/libs/tests/Mspace.cpp index 0741744299..a50f82f104 100644 --- a/core/libs/tests/Mspace.cpp +++ b/core/libs/tests/Mspace.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -8,42 +9,6 @@ #include #include -extern "C" { -int* APS5_VABI __error_nid_postfix(); -void* APS5_VABI sceLibcMspaceCreate_nid_postfix(const char*, void*, std::size_t, unsigned); -int APS5_VABI sceLibcMspaceDestroy_nid_postfix(void*); -void* APS5_VABI sceLibcMspaceMalloc_nid_postfix(void*, std::size_t); -void* APS5_VABI sceLibcMspaceCalloc_nid_postfix(void*, std::size_t, std::size_t); -void* APS5_VABI sceLibcMspaceRealloc_nid_postfix(void*, void*, std::size_t); -void APS5_VABI sceLibcMspaceFree_nid_postfix(void*, void*); -int APS5_VABI sceLibcMspacePosixMemalign_nid_postfix(void*, void**, std::size_t, std::size_t); -std::size_t APS5_VABI sceLibcMspaceMallocUsableSize_nid_postfix(const void*); -void* APS5_VABI sceLibcMspaceMemalign_nid_postfix(void*, std::size_t, std::size_t); -void* APS5_VABI sceLibcMspaceAlignedAlloc_nid_postfix(void*, std::size_t, std::size_t); -void* APS5_VABI sceLibcMspaceReallocalign_nid_postfix(void*, void*, std::size_t, std::size_t); -int APS5_VABI sceLibcMspaceMallocStats_nid_postfix(void*, void*); -int APS5_VABI sceLibcMspaceMallocStatsFast_nid_postfix(void*, void*); -void* APS5_VABI sceLibcPafMspaceCreate_nid_postfix(const char*, void*, std::size_t, unsigned, const void*); -int APS5_VABI sceLibcPafMspaceDestroy_nid_postfix(void*); -void* APS5_VABI sceLibcPafMspaceMalloc_nid_postfix(void*, std::size_t, const void*); -void APS5_VABI sceLibcPafMspaceFree_nid_postfix(void*, void*, const void*); -void* APS5_VABI sceLibcPafMspaceCalloc_nid_postfix(void*, std::size_t, std::size_t, const void*); -void* APS5_VABI sceLibcPafMspaceRealloc_nid_postfix(void*, void*, std::size_t, const void*); -void* APS5_VABI sceLibcPafMspaceReallocalign_nid_postfix(void*, void*, std::size_t, std::size_t, const void*); -void* APS5_VABI sceLibcPafMspaceMemalign_nid_postfix(void*, std::size_t, std::size_t, const void*); -int APS5_VABI sceLibcPafMspacePosixMemalign_nid_postfix(void*, void**, std::size_t, std::size_t, const void*); -int APS5_VABI sceLibcPafMspaceMallocStats_nid_postfix(void*, void*); -int APS5_VABI sceLibcPafMspaceMallocStatsFast_nid_postfix(void*, void*); -} -struct MallocManagedSize { - std::uint16_t size; - std::uint16_t version; - std::uint32_t reserved; - std::size_t maxSystemSize; - std::size_t currentSystemSize; - std::size_t maxInuseSize; - std::size_t currentInuseSize; -}; static void Require(bool value) { if (!value) { std::fputs("Mspace check failed\n", stderr); std::abort(); } } @@ -87,6 +52,7 @@ static void CheckPaf() { sceLibcPafMspaceFree_nid_postfix(arena, aligned, &owner); sceLibcPafMspaceFree_nid_postfix(arena, posixAligned, &owner); sceLibcPafMspaceFree_nid_postfix(arena, nullptr, &owner); + Require(sceLibcPafMspaceDestroy_nid_postfix(arena) == 0); Require(sceLibcPafMspaceDestroy_nid_postfix(arena) == -1); } @@ -205,14 +171,17 @@ int main() { Require(sceLibcMspaceCalloc_nid_postfix(arena, std::numeric_limits::max(), 2) == nullptr); sceLibcMspaceFree_nid_postfix(arena, blocker); sceLibcMspaceFree_nid_postfix(arena, aligned); + Require(sceLibcMspaceRealloc_nid_postfix(arena, grown, 0) == nullptr); void* large = sceLibcMspaceMalloc_nid_postfix(arena, storage.size() - 256); Require(large != nullptr); // freeing coalesced all the fragmented blocks sceLibcMspaceFree_nid_postfix(arena, large); + void* head = sceLibcMspaceMalloc_nid_postfix(arena, 64); Require(head != nullptr && sceLibcMspaceRealloc_nid_postfix(arena, head, 1024) == head); Require(sceLibcMspaceMallocUsableSize_nid_postfix(head) == 1024); sceLibcMspaceFree_nid_postfix(arena, head); + std::array small{}; for (auto& pointer : small) { pointer = sceLibcMspaceMalloc_nid_postfix(arena, 16); @@ -224,6 +193,7 @@ int main() { large = sceLibcMspaceMalloc_nid_postfix(arena, storage.size() - 256); Require(large != nullptr); sceLibcMspaceFree_nid_postfix(arena, large); + std::array workers; for (auto& worker : workers) worker = std::thread([&] { for (int i = 0; i < 1000; ++i) { @@ -232,6 +202,7 @@ int main() { std::memset(pointer, 42, 97); Require(sceLibcMspaceMallocUsableSize_nid_postfix(pointer) >= 97); sceLibcMspaceFree_nid_postfix(arena, pointer); + } }); for (auto& worker : workers) worker.join(); @@ -263,6 +234,7 @@ int main() { Require(rounded && (reinterpret_cast(rounded) & 127) == 0); Require(sceLibcMspaceMallocStatsFast_nid_postfix(arena, &stats) == 0 && stats.currentInuseSize >= 100); sceLibcMspaceFree_nid_postfix(arena, memaligned); + void* region = sceLibcMspaceMalloc_nid_postfix(arena, 8192); Require(region != nullptr); Require(sceLibcMspaceCreate_nid_postfix("outside", static_cast(region) + 8192, 4096, 0) == nullptr); @@ -274,6 +246,7 @@ int main() { Require(sceLibcMspaceCreate_nid_postfix("flags", storage.data(), storage.size(), 2) == nullptr); Require(sceLibcMspaceDestroy_nid_postfix(nested) == 0); sceLibcMspaceFree_nid_postfix(arena, region); + Require(sceLibcMspaceDestroy_nid_postfix(arena) == 0); Require(sceLibcMspaceMalloc_nid_postfix(arena, 8) == nullptr); Require(sceLibcMspaceCreate_nid_postfix("reuse", storage.data(), storage.size(), 0) == arena); diff --git a/core/libs/tests/NetBlockingSend.cpp b/core/libs/tests/NetBlockingSend.cpp index fb64d834ba..6dba4d4900 100644 --- a/core/libs/tests/NetBlockingSend.cpp +++ b/core/libs/tests/NetBlockingSend.cpp @@ -107,13 +107,16 @@ int main(int argc, char** argv) { } { Connection connection; +#ifndef _WIN32 + SetOption(connection.receiver, NET_SO_RCVBUF, 4096); +#endif SetOption(connection.sender, 0x1105, 100000); auto started = std::chrono::steady_clock::now(); const auto partial = Send(api, connection.sender, bytes); Require(partial > 0 && partial < static_cast(bytes.size()), "timeout must return queued bytes"); Require(std::chrono::steady_clock::now() - started >= std::chrono::milliseconds(80), "partial send returned before its timeout"); bool emptyTimeout = false; - for (int attempt = 0; attempt < 16; ++attempt) { + for (int attempt = 0; attempt < 64; ++attempt) { const auto result = Send(api, connection.sender, bytes); if (result < 0) { Require(*sceNetErrnoLoc() == NET_EAGAIN, "timeout without progress must return EAGAIN"); @@ -127,6 +130,14 @@ int main(int argc, char** argv) { { Connection connection; auto sending = std::async(std::launch::async, [&] { return Send(api, connection.sender, bytes); }); + const auto entered = std::chrono::steady_clock::now() + std::chrono::seconds(5); + char peeked = 0; + bool arrived = false; + while (!arrived && std::chrono::steady_clock::now() < entered) { + arrived = sceNetRecv(connection.receiver, &peeked, 1, NET_MSG_PEEK | NET_MSG_DONTWAIT) > 0; + if (!arrived) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + Require(arrived, "send did not reach the receiver before abort"); Require(sending.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout, "send did not wait before abort"); Require(sceNetSocketAbort(connection.sender, 0) == 0, "abort a blocked send"); Require(sending.wait_for(std::chrono::seconds(2)) == std::future_status::ready, "abort did not wake a blocked send"); diff --git a/core/libs/tests/NetDontWait.cpp b/core/libs/tests/NetDontWait.cpp index 7507f2fde5..c91b7d0bb7 100644 --- a/core/libs/tests/NetDontWait.cpp +++ b/core/libs/tests/NetDontWait.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" #include "SceTypes.hpp" #include #include @@ -12,42 +14,6 @@ #include #include -struct NetIovec { - void* base; - std::uint64_t length; -}; - -struct NetMsghdr { - void* name; - std::uint32_t nameLength; - NetIovec* iov; - int iovLength; - void* control; - std::uint32_t controlLength; - int flags; -}; - -extern "C" { -int APS5_VABI sceNetSocket(const char*, int, int, int); -int APS5_VABI sceNetSocketClose(int); -int APS5_VABI sceNetSocketAbort(int, int); -int APS5_VABI sceNetBind_nid_postfix(int, const void*, std::uint32_t); -int APS5_VABI sceNetGetsockname(int, void*, std::uint32_t*); -int APS5_VABI sceNetListen(int, int); -int APS5_VABI sceNetConnect(int, const void*, std::uint32_t); -int APS5_VABI sceNetAccept(int, void*, std::uint32_t*); -int APS5_VABI sceNetSetsockopt(int, int, int, const void*, std::uint32_t); -int APS5_VABI sceNetGetsockopt(int, int, int, void*, std::uint32_t*); -std::int64_t APS5_VABI sceNetSend(int, const void*, std::size_t, int); -std::int64_t APS5_VABI sceNetSendto(int, const void*, std::size_t, int, const void*, std::uint32_t); -std::int64_t APS5_VABI sceNetSendmsg(int, const NetMsghdr*, int); -std::int64_t APS5_VABI sceNetRecv(int, void*, std::size_t, int); -std::int64_t APS5_VABI sceNetRecvfrom(int, void*, std::size_t, int, void*, std::uint32_t*); -std::int64_t APS5_VABI sceNetRecvmsg(int, NetMsghdr*, int); -int* APS5_VABI sceNetErrnoLoc(); -int APS5_VABI fcntl_nid_postfix(int, int, ...); -} - namespace { constexpr int DontWait = 0x80; diff --git a/core/libs/tests/NetEmptyStream.cpp b/core/libs/tests/NetEmptyStream.cpp new file mode 100644 index 0000000000..5757b2bdb1 --- /dev/null +++ b/core/libs/tests/NetEmptyStream.cpp @@ -0,0 +1,145 @@ +#include "prx/libSceNet/libSceNet.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "empty stream: %s\n", message); + std::exit(1); +} + +void SetOption(int descriptor, int option, int value) { + Require(sceNetSetsockopt(descriptor, 0xffff, option, &value, sizeof(value)) == 0, "set option"); +} + +struct Pair { + int sender; + int receiver; + + explicit Pair(bool datagram) { + const int listener = sceNetSocket("empty-listener", 2, datagram ? 2 : 1, 0); + sender = sceNetSocket("empty-sender", 2, datagram ? 2 : 1, 0); + Require(listener >= 0 && sender >= 0, "create sockets"); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(sceNetBind_nid_postfix(listener, address.data(), address.size()) == 0, "bind receiver"); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(listener, address.data(), &size) == 0, "get receiver name"); + if (!datagram) Require(sceNetListen(listener, 1) == 0, "listen"); + Require(sceNetConnect(sender, address.data(), size) == 0, "connect sender"); + if (datagram) receiver = listener; + else { + receiver = sceNetAccept(listener, nullptr, nullptr); + Require(receiver >= 0, "accept"); + Require(sceNetSocketClose(listener) == 0, "close listener"); + } + SetOption(receiver, 0x1106, 2000000); + } + + ~Pair() { + Require(sceNetSocketClose(receiver) == 0 && sceNetSocketClose(sender) == 0, "close pair"); + } + + void Send() { + Require(sceNetSend(sender, "x", 1, 0) == 1, "send byte"); + } +}; + +struct Result { + std::int64_t count; + int error; +}; + +Result Receive(std::string_view api, int descriptor, int flags, bool nullBuffer, bool datagram) { + char payload = 'p'; + char control = 'c'; + std::array address; + address.fill(0xaa); + std::uint32_t addressLength = address.size(); + std::int64_t count; + if (api == "recv") count = sceNetRecv(descriptor, nullBuffer ? nullptr : &payload, 0, flags); + else if (api == "recvfrom") + count = sceNetRecvfrom(descriptor, nullBuffer ? nullptr : &payload, 0, flags, address.data(), &addressLength); + else { + NetIovec vector{&payload, 0}; + NetMsghdr message{address.data(), addressLength, nullBuffer ? nullptr : &vector, nullBuffer ? 0 : 1, &control, 1, 0x4000}; + count = sceNetRecvmsg(descriptor, &message, flags); + if (count >= 0) { + Require(message.control_length == 0, "control length is not zero"); + Require(message.flags == (datagram ? 0x10 : 0), "unexpected output flags"); + addressLength = message.name_length; + } + } + const int error = *sceNetErrnoLoc(); + Require(payload == 'p' && control == 'c', "empty receive modified payload or control storage"); + if (count >= 0 && api != "recv") { + Require(addressLength == (datagram ? 16u : 0u), "wrong sender length for an empty receive"); + if (!datagram) Require(std::all_of(address.begin(), address.end(), [](unsigned char byte) { return byte == 0xaa; }), + "empty stream receive wrote a sender address"); + } + return {count, error}; +} + +void Check(std::string_view api, std::string_view mode) { + const bool datagram = mode == "datagram"; + Pair pair(datagram); + if (mode == "errors") { + auto result = Receive(api, -1, 0, false, false); + Require(result.count < 0 && result.error == 9, "invalid descriptor became an empty success"); + result = Receive(api, pair.receiver, 1, false, false); + Require(result.count < 0 && result.error == 45, "unsupported flags became an empty success"); + const int fresh = sceNetSocket("empty-unconnected", 2, 1, 0); + Require(fresh >= 0, "create unconnected socket"); + result = Receive(api, fresh, 0, false, false); + std::fprintf(stderr, "unconnected result=%lld errno=%d\n", static_cast(result.count), result.error); + Require(result.count < 0 && result.error == 57, "fresh stream socket did not report ENOTCONN"); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(sceNetBind_nid_postfix(fresh, address.data(), address.size()) == 0, "bind unconnected socket"); + result = Receive(api, fresh, 0, false, false); + Require(result.count < 0 && result.error == 57, "bound stream socket did not report ENOTCONN"); + Require(sceNetListen(fresh, 1) == 0, "listen on unconnected socket"); + result = Receive(api, fresh, 0, false, false); + Require(result.count < 0 && result.error == 57, "listening socket did not report ENOTCONN"); + Require(sceNetSocketClose(fresh) == 0, "close unconnected socket"); + return; + } + if (mode == "queued" || datagram) pair.Send(); + if (mode == "nonblocking") SetOption(pair.receiver, 0x1200, 1); + const int flags = mode == "dontwait" ? 0x80 : mode == "peek" ? 0x82 : 0; + auto pending = std::async(std::launch::async, [&] { return Receive(api, pair.receiver, flags, mode == "nullbuf", datagram); }); + const bool ready = pending.wait_for(std::chrono::milliseconds(500)) == std::future_status::ready; + if (!ready) { + pair.Send(); + Require(pending.wait_for(std::chrono::seconds(2)) == std::future_status::ready, "cleanup receive did not finish"); + } + const auto result = pending.get(); + Require(ready, "zero-length receive waited for peer data"); + Require(result.count == 0, "zero-length receive did not return zero"); + if (mode == "queued") { + char byte = 0; + Require(sceNetRecv(pair.receiver, &byte, 1, 0) == 1 && byte == 'x', "empty stream receive consumed data"); + } + if (datagram) { + char byte = 0; + Require(sceNetRecv(pair.receiver, &byte, 1, 0x80) < 0 && *sceNetErrnoLoc() == 35, + "zero-length datagram receive did not consume its packet"); + } + std::printf("%.*s/%.*s: zero return, output sentinels and transport semantics preserved\n", + static_cast(api.size()), api.data(), static_cast(mode.size()), mode.data()); +} + +} + +int main(int argc, char** argv) { + Require(argc == 3, "expected API and mode"); + const std::string_view api(argv[1]); + Require(api == "recv" || api == "recvfrom" || api == "recvmsg", "unknown API"); + Check(api, argv[2]); +} diff --git a/core/libs/tests/NetEpollLifetime.cpp b/core/libs/tests/NetEpollLifetime.cpp new file mode 100644 index 0000000000..47d551e7e1 --- /dev/null +++ b/core/libs/tests/NetEpollLifetime.cpp @@ -0,0 +1,212 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +static int InterceptPoll(WSAPOLLFD* descriptors, ULONG count, INT timeout); +#define WSAPoll InterceptPoll +#else +#include +static int InterceptPoll(pollfd* descriptors, nfds_t count, int timeout); +#define poll InterceptPoll +#endif +#include "../prx/libSceNet/Export.cpp" +#ifdef _WIN32 +#undef WSAPoll +#else +#undef poll +#endif +#include +#include + +static void Require(bool value, const std::source_location location = std::source_location::current()) { + if (value) return; + std::fprintf(stderr, "requirement failed at %s:%u\n", location.file_name(), location.line()); + std::abort(); +} + +struct PollGate { + std::promise arrived; + std::future released; + int result = 0; +}; + +static std::atomic pollGate{nullptr}; + +#ifdef _WIN32 +static int InterceptPoll(WSAPOLLFD* descriptors, ULONG count, INT timeout) { + const int result = ::WSAPoll(descriptors, count, timeout); +#else +static int InterceptPoll(pollfd* descriptors, nfds_t count, int timeout) { + const int result = ::poll(descriptors, count, timeout); +#endif + if (auto* gate = pollGate.exchange(nullptr)) { + gate->result = result; + gate->arrived.set_value(); + gate->released.wait(); + } + return result; +} + +class ReuseSlot { +public: +#ifndef _WIN32 + ReuseSlot() : descriptor(::open("/dev/null", O_RDONLY | O_CLOEXEC)) { Require(descriptor >= 0); } + ~ReuseSlot() { if (descriptor >= 0) ::close(descriptor); } + void Release() { Require(::close(descriptor) == 0); descriptor = -1; } +private: + int descriptor; +#else + void Release() {} +#endif +}; + +static constexpr std::uint64_t OldData = 0x123456789abcdef0; +static constexpr std::uint64_t NewData = 0xfedcba9876543210; + +static int Socket() { + const int descriptor = sceNetSocket("epoll-lifetime", 2, 2, 17); + Require(descriptor >= 0); + return descriptor; +} + +static std::array Bind(int descriptor) { + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(sceNetBind_nid_postfix(descriptor, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(descriptor, address.data(), &size) == 0 && size == address.size()); + return address; +} + +static void Send(int sender, const std::array& address) { + const char payload[] = "epoll-lifetime"; + Require(sceNetSendto(sender, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload)); +} + +static void Register(int epoll, int descriptor, int operation, std::uint64_t data, std::uint32_t events = 1) { + NetEpollEvent event{}; + event.events = events; + event.ident = 0xaaaaaaaaaaaaaaaa; + event.reserved = 0x55555555; + event.data.u64 = data; + Require(sceNetEpollControl(epoll, operation, descriptor, &event) == 0); +} + +static void CheckEvent(const NetEpollEvent& event, int descriptor, std::uint64_t data, std::uint32_t events = 1) { + Require(event.ident == static_cast(descriptor)); + Require(event.data.u64 == data && event.events == events && event.reserved == 0); +} + +static void CheckReuse() { + ReuseSlot slot; + const int original = Socket(); + const auto retained = g_socks.at(original).native; + const int sender = Socket(); + std::array epolls{sceNetEpollCreate("reuse-a", 0), sceNetEpollCreate("reuse-b", 0)}; + for (const int epoll : epolls) { + Require(epoll >= 0); + Register(epoll, original, 1, OldData); + } + Require(sceNetSocketClose(original) == 0); + slot.Release(); + const int replacement = Socket(); + Require(replacement == original); + Send(sender, Bind(replacement)); + for (const int epoll : epolls) { + NetEpollEvent event{}; + Require(sceNetEpollWait(epoll, &event, 1, 0) == 0); + Register(epoll, replacement, 1, NewData); + Require(sceNetEpollWait(epoll, &event, 1, 1000000) == 1); + CheckEvent(event, replacement, NewData); + Require(sceNetEpollDestroy(epoll) == 0); + } + Require(sceNetSocketClose(replacement) == 0 && sceNetSocketClose(sender) == 0); +} + +static void CheckControl() { + const int descriptor = Socket(); + const int epoll = sceNetEpollCreate("control", 0); + Require(epoll >= 0); + Require(sceNetEpollControl(epoll, 3, descriptor, nullptr) == static_cast(0x80410102)); + Require(*sceNetErrnoLoc() == 2); + Register(epoll, descriptor, 1, OldData); + Require(sceNetSocketClose(descriptor) == 0); + NetEpollEvent event{}; + event.events = 1; + for (const int closed : {descriptor, -1}) { + for (const int operation : {3, 1, 2}) { + Require(sceNetEpollControl(epoll, operation, closed, operation == 3 ? nullptr : &event) == static_cast(0x80410109)); + Require(*sceNetErrnoLoc() == 9); + } + } + Require(sceNetEpollDestroy(epoll) == 0); +} + +struct WaitResult { + int count = -1; + int error = 0; + NetEpollEvent event{}; +}; + +static void CheckWait(const std::string& operation) { + ReuseSlot slot; + int receiver = Socket(); + const int sender = Socket(); + Send(sender, Bind(receiver)); + const int epoll = sceNetEpollCreate("inflight", 0); + Require(epoll >= 0); + Register(epoll, receiver, 1, OldData); + std::promise release; + PollGate gate{{}, release.get_future()}; + auto arrived = gate.arrived.get_future(); + pollGate.store(&gate); + auto waiter = std::async(std::launch::async, [epoll] { + WaitResult result; + result.count = sceNetEpollWait(epoll, &result.event, 1, 200000); + result.error = *sceNetErrnoLoc(); + return result; + }); + Require(arrived.wait_for(std::chrono::seconds(2)) == std::future_status::ready); + Require(gate.result == 1); + if (operation == "close") { + const int original = receiver; + Require(sceNetSocketClose(original) == 0); + slot.Release(); + receiver = Socket(); + Require(receiver == original); + Send(sender, Bind(receiver)); + Register(epoll, receiver, 1, NewData); + } else if (operation == "delete" || operation == "replace") { + Require(sceNetEpollControl(epoll, 3, receiver, nullptr) == 0); + if (operation == "replace") Register(epoll, receiver, 1, NewData); + } else if (operation == "modify") { + Register(epoll, receiver, 2, NewData, 2); + } else if (operation == "abort") { + Require(sceNetEpollAbort(epoll, 0) == 0); + } else { + Require(operation == "destroy"); + Require(sceNetEpollDestroy(epoll) == 0); + } + release.set_value(); + Require(waiter.wait_for(std::chrono::seconds(2)) == std::future_status::ready); + const auto result = waiter.get(); + if (operation == "delete") { + Require(result.count == 0); + } else if (operation == "abort" || operation == "destroy") { + Require(result.count < 0 && result.error == (operation == "abort" ? 53 : 9)); + } else { + Require(result.count == 1); + CheckEvent(result.event, receiver, NewData, operation == "modify" ? 2 : 1); + } + if (operation != "destroy") Require(sceNetEpollDestroy(epoll) == 0); + Require(sceNetSocketClose(receiver) == 0 && sceNetSocketClose(sender) == 0); +} + +int main(int argc, char** argv) { + Require(argc == 2); + Require(sceNetInit_nid_postfix() == 0); + for (int iteration = 0; iteration != 3; ++iteration) { + if (std::string(argv[1]) == "reuse") CheckReuse(); + else if (std::string(argv[1]) == "control") CheckControl(); + else CheckWait(argv[1]); + } + std::printf("epoll lifetime %s: 3 rounds passed\n", argv[1]); +} diff --git a/core/libs/tests/PthreadStart.cpp b/core/libs/tests/PthreadStart.cpp index 8750d5eb5e..8dac3f063a 100644 --- a/core/libs/tests/PthreadStart.cpp +++ b/core/libs/tests/PthreadStart.cpp @@ -1,3 +1,5 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include #include @@ -6,17 +8,6 @@ #include #endif -extern "C" int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); -extern "C" int APS5_VABI scePthreadJoin(Pthread thread, void** retval); -extern "C" int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); -extern "C" int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); -extern "C" int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr); -extern "C" int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t size); -extern "C" int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** address, std::size_t* size); -extern "C" Pthread APS5_VABI scePthreadSelf(); -extern "C" void APS5_VABI scePthreadExit(void* retval); -extern "C" int APS5_VABI scePthreadDetach(Pthread thread); - struct ThreadContext { Pthread thread = nullptr; std::promise checked; diff --git a/core/libs/tests/SaveDataMemoryGrowth.cpp b/core/libs/tests/SaveDataMemoryGrowth.cpp index 87d06e2a33..908bc48688 100644 --- a/core/libs/tests/SaveDataMemoryGrowth.cpp +++ b/core/libs/tests/SaveDataMemoryGrowth.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSaveData/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -12,12 +13,6 @@ #include #endif -extern "C" { -int APS5_VABI sceSaveDataInitialize3(const void*); -int APS5_VABI sceSaveDataTerminate(); -int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2*, SaveDataMemorySetupResult*); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Save-data memory growth check failed at line %d\n", line); diff --git a/core/libs/tests/SaveDataMemoryJournal.cpp b/core/libs/tests/SaveDataMemoryJournal.cpp index e661a1fd1f..b1af602123 100644 --- a/core/libs/tests/SaveDataMemoryJournal.cpp +++ b/core/libs/tests/SaveDataMemoryJournal.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSaveData/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -13,14 +14,6 @@ #include #include -extern "C" { -int APS5_VABI sceSaveDataInitialize3(const void*); -int APS5_VABI sceSaveDataTerminate(); -int APS5_VABI sceSaveDataGetSaveDataMemory2(SaveDataMemoryGet2*); -int APS5_VABI sceSaveDataSetSaveDataMemory2(const SaveDataMemorySet2*); -int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2*, SaveDataMemorySetupResult*); -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Save-data memory journal check failed at line %d\n", line); diff --git a/core/libs/tests/SaveDataMemoryMetadata.cpp b/core/libs/tests/SaveDataMemoryMetadata.cpp index b3acb20559..f498a26fef 100644 --- a/core/libs/tests/SaveDataMemoryMetadata.cpp +++ b/core/libs/tests/SaveDataMemoryMetadata.cpp @@ -1,3 +1,5 @@ +#include "prx/libSceSaveData/include/Api.hpp" +#include "prx/libSceSaveData.native/include/Api.hpp" #include "SceTypes.hpp" #include #include @@ -25,16 +27,6 @@ #include #endif -extern "C" { -int APS5_VABI sceSaveDataInitialize3(const void*); -int APS5_VABI sceSaveDataTerminate(); -int APS5_VABI sceSaveDataCreateTransactionResource(std::uint32_t); -int APS5_VABI sceSaveDataDeleteTransactionResource(std::int32_t); -int APS5_VABI sceSaveDataSetSaveDataMemory2(const SaveDataMemorySet2*); -int APS5_VABI sceSaveDataSyncSaveDataMemory(const void*); -int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2*, SaveDataMemorySetupResult*); -} - namespace { constexpr int kSaveDataNotInitialized = -2137063423; } diff --git a/core/libs/tests/SaveDataWriteReplacement.cpp b/core/libs/tests/SaveDataWriteReplacement.cpp index e874060c9f..ef57bfe136 100644 --- a/core/libs/tests/SaveDataWriteReplacement.cpp +++ b/core/libs/tests/SaveDataWriteReplacement.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSaveData/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -13,11 +14,9 @@ #include extern "C" { -int APS5_VABI sceSaveDataInitialize3(const void*); -int APS5_VABI sceSaveDataTerminate(void); -int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2*, SaveDataMemorySetupResult*); + #ifndef SAVEDATA_NATIVE_BACKEND -int APS5_VABI sceSaveDataSyncSaveDataMemory(const void*); + #endif } diff --git a/core/libs/tests/ScalarCall.cpp b/core/libs/tests/ScalarCall.cpp index c4d38b87c4..0faf175176 100644 --- a/core/libs/tests/ScalarCall.cpp +++ b/core/libs/tests/ScalarCall.cpp @@ -5,6 +5,7 @@ #include "RdnaDecoder/RdnaMemoryOpDecoder.hpp" #include "ControlFlow/GraphBuilder.hpp" #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include using namespace ShaderRecompiler; @@ -43,6 +45,11 @@ static RdnaInstruction Sop1(std::uint32_t pc, std::uint32_t destination, std::ui return DecodeRdnaSop1(pc, code, 0u); } +static RdnaInstruction Sop2(std::uint32_t pc, std::uint32_t destination, std::uint32_t op, std::uint32_t source0, std::uint32_t source1) { + const std::array code{0x80000000u | (op << 23u) | (destination << 16u) | (source1 << 8u) | source0}; + return DecodeRdnaSop2(pc, code, 0u); +} + static RdnaInstruction Sopp(std::uint32_t pc, std::uint32_t op, std::uint32_t immediate = 0u) { const std::array code{0xbf800000u | (op << 16u) | immediate}; return DecodeRdnaSopp(pc, code, 0u); @@ -236,6 +243,21 @@ static void CheckTableCalls() { Require(table(10u, 0xbf810000u, 12u).empty()); Require(table(5u, 0x28000008u).size() == 1u && table(5u, 0x28000008u)[0].entryOffset == 8u); Require(table(0u, 0xbe940380u).empty()); + const auto slice = [&](std::uint32_t load, std::uint32_t bufferLoad) { + auto code = caller; + code[1] = load; + code[4] = bufferLoad; + return FindTableCalls(RdnaInstructionDecoder{}.Decode(code), swappc); + }; + for (const auto& [load, bufferLoad, offset] : {std::tuple{0xf40c0606u, 0xf428020cu, 0x270u}, std::tuple{0xf40c0606u, 0xf428020eu, 0x280u}, std::tuple{0xf4100606u, 0xf4280210u, 0x290u}, std::tuple{0xf4100606u, 0xf4280212u, 0x2a0u}}) { + const auto sliced = slice(load, bufferLoad); + Require(sliced.size() == 1u && sliced[0].userDataRegister == 12u && sliced[0].descriptorOffset == offset && sliced[0].entryOffset == 0u && sliced[0].entryStride == 16u); + } + Require(slice(0xf40c0606u, 0xf4280210u).empty()); + Require(slice(0xf40c0606u, 0xf428020du).empty()); + Require(slice(0xf40c0686u, 0xf428020eu).empty()); + Require(slice(0xf40c0686u, 0xf428020cu).empty()); + Require(slice(0xf40c0586u, 0xf428020eu).empty()); auto readsLane = caller; readsLane.insert(readsLane.begin(), {0xd4c2000bu, 0x00020300u}); const auto readsLaneProgram = RdnaInstructionDecoder{}.Decode(readsLane); @@ -255,27 +277,69 @@ static void CheckTableCalls() { const std::vector> callees{{{0x1122334455ull, first}, {0x66778899aaull, second}}}; const auto linkedCalls = LinkCalls(program, {}, {}, calls, callees); const auto& linked = linkedCalls.code; - Require(linkedCalls.fallbacks == std::vector{50u * 4u}); + Require(linkedCalls.fallbacks == std::vector{53u * 4u}); ExpectThrow("is shared with another call", [&] { static_cast(BuildCode(linked, swappc)); }); SwappcInfo linkedSwappc = swappc; linkedSwappc.linkedCallFallbacks = linkedCalls.fallbacks; - Require(linked.size() == 59u && linked[9] == 0xbf820001u && linked[11] == 0xbf850010u && linked[58] == 0xbf9f0000u); - Require(linked[12] == 0xbf07ff0eu && linked[13] == 0x22334455u && linked[14] == 0xbf850004u && linked[15] == 0xbf07ff0fu && linked[16] == 0x11u && linked[17] == 0xbf850001u && linked[18] == 0xbf82001bu); - Require(linked[20] == 0x778899aau && linked[26] == 0xbf078080u && linked[27] == 0xbf820016u && linked[34] == 0xbf068080u && linked[35] == 0xbf82000au && linked[44] == 0xbf068080u && linked[45] == 0xbf820004u); - Require(linked[46] == 0xbb0e0005u && linked[47] == 0xbf82ffdau && linked[48] == 0xbb0e0006u && linked[49] == 0xbf82ffd8u && linked[50] == 0xbe8e210eu && linked[51] == 0xbf82ffd6u); - Require(std::equal(first.begin(), first.end(), linked.begin() + 52) && std::equal(second.begin(), second.end(), linked.begin() + 55)); + Require(linked.size() == 56u && linked[9] == 0xbf820001u && linked[11] == 0xbf850007u && linked[55] == 0xbf9f0000u); + Require(linked[12] == 0xbf07ff0eu && linked[13] == 0x22334455u && linked[14] == 0xbf850010u && linked[15] == 0xbf07ff0fu && linked[16] == 0x11u && linked[17] == 0xbf85000du && linked[18] == 0xbf820007u); + Require(linked[19] == 0xbf07ff0eu && linked[21] == 0xbf850010u && linked[24] == 0xbf85000du && linked[25] == 0xbf068080u && linked[26] == 0xbb0e0001u && linked[27] == 0xbf82ffeeu); + Require(linked[32] == 0x778899aau && linked[33] == 0xbf850010u && linked[37] == 0xbf820007u && linked[40] == 0xbf85000bu && linked[43] == 0xbf850008u && linked[45] == 0xbb0e0001u && linked[46] == 0xbf82ffecu); + Require(linked[50] == 0xbf078080u && linked[51] == 0xbf820001u && linked[52] == 0xbf068080u && linked[53] == 0xbe8e210eu && linked[54] == 0xbf82fff7u); + Require(std::equal(first.begin(), first.end(), linked.begin() + 28) && std::equal(second.begin(), second.end(), linked.begin() + 47)); const auto cfg = BuildCode(linked, linkedSwappc); const auto block = [&](std::uint32_t word) { return cfg.FindBlockByProgramCounter(word * 4u).id; }; - Require(cfg.FindBlockByProgramCounter(46u * 4u).terminator.trueBlock == block(52u)); - Require(cfg.FindBlockByProgramCounter(52u * 4u).terminator.trueBlock == block(47u)); - Require(cfg.FindBlockByProgramCounter(48u * 4u).terminator.trueBlock == block(55u)); - Require(cfg.FindBlockByProgramCounter(55u * 4u).terminator.trueBlock == block(49u)); - Require(cfg.unlinkedCallProgramCounters == std::vector{50u * 4u}); + Require(cfg.FindBlockByProgramCounter(26u * 4u).terminator.trueBlock == block(28u)); + Require(cfg.FindBlockByProgramCounter(28u * 4u).terminator.trueBlock == block(27u)); + Require(cfg.FindBlockByProgramCounter(45u * 4u).terminator.trueBlock == block(47u)); + Require(cfg.FindBlockByProgramCounter(47u * 4u).terminator.trueBlock == block(46u)); + Require(cfg.unlinkedCallProgramCounters == std::vector{53u * 4u}); static_cast(TranslateCode(linked, linkedSwappc)); - std::vector large(0x8000u, 0xbf800000u); - const std::vector> far{{{0x10ull, large}, {0x20ull, first}}}; - ExpectThrow("out of s_call_b64 range", [&] { static_cast(LinkCalls(program, {}, {}, calls, far)); }); + std::vector large(0x3000u, 0xbf800000u); + large.push_back(0xbe80200eu); + const std::vector> far{{{0x10ull, large}, {0x20ull, large}, {0x30ull, large}, {0x40ull, first}}}; + const auto farLinked = LinkCalls(program, {}, {}, calls, far); + const auto farCall = 12u + 3u * (16u + 0x3001u) + 14u; + Require(farLinked.code[farCall] == 0xbb0e0001u && std::equal(first.begin(), first.end(), farLinked.code.begin() + farCall + 2u)); + SwappcInfo farSwappc = swappc; + farSwappc.linkedCallFallbacks = farLinked.fallbacks; + const auto farCfg = BuildCode(farLinked.code, farSwappc); + Require(farCfg.FindBlockByProgramCounter(farCall * 4u).terminator.trueBlock == farCfg.FindBlockByProgramCounter((farCall + 2u) * 4u).id); + Require(farCfg.FindBlockByProgramCounter((farCall + 2u) * 4u).terminator.trueBlock == farCfg.FindBlockByProgramCounter((farCall + 1u) * 4u).id); + auto twoCallers = caller; + twoCallers.pop_back(); + twoCallers.insert(twoCallers.end(), caller.begin(), caller.end()); + const auto twoProgram = RdnaInstructionDecoder{}.Decode(twoCallers); + auto secondCall = calls[0]; + secondCall.programCounter += 40u; + const std::array twoCalls{calls[0], secondCall}; + const std::vector> twoCallees{far[0], {{0x50ull, first}, {0x60ull, second}}}; + const auto twoLinked = LinkCalls(twoProgram, {}, {}, twoCalls, twoCallees); + const auto& two = twoLinked.code; + const auto follow = [&](std::size_t word, std::size_t& hops) { + hops = 0; + while (two[word] >> 16u == 0xbf82u) { + word = static_cast(static_cast(word) + 1 + static_cast(two[word] & 0xffffu)); + ++hops; + } + return word; + }; + std::size_t hops = 0; + Require(follow(9u, hops) == 23u && hops == 1u && two[21] >> 16u == 0xbf82u && two[22] >> 16u == 0xbf82u); + Require(follow(39u, hops) == 10u && hops == 1u); + const auto secondChain = follow(19u, hops); + Require(hops == 6u && two[secondChain] == 0xbf850007u && two[secondChain + 1u] == 0xbf07ff0eu && two[secondChain + 2u] == 0x50u); + Require(two[secondChain + 15u] == 0xbb0e0001u && std::equal(first.begin(), first.end(), two.begin() + static_cast(secondChain + 17u))); + Require(follow(secondChain + 16u, hops) == 20u && hops == 6u); + SwappcInfo twoSwappc = swappc; + twoSwappc.linkedCallFallbacks = twoLinked.fallbacks; + const auto twoCfg = BuildCode(two, twoSwappc); + Require(twoCfg.FindBlockByProgramCounter(static_cast(secondChain + 15u) * 4u).terminator.trueBlock == twoCfg.FindBlockByProgramCounter(static_cast(secondChain + 17u) * 4u).id); + Require(twoCfg.unlinkedCallProgramCounters.size() == 2u); + std::vector oversized(0x8000u, 0xbf800000u); + const std::vector> overflows{{{0x10ull, oversized}, {0x20ull, first}}}; + ExpectThrow("out of the 16-bit branch range", [&] { static_cast(LinkCalls(program, {}, {}, calls, overflows)); }); const std::vector jumpTable{ 0xbe8c1f00u, 0x800cff0cu, 0x00000058u, 0x820d800du, 0x83928202u, 0xd4c40016u, 0x02000500u, 0x8f128212u, 0xf4000386u, 0x24000000u, @@ -290,6 +354,10 @@ static void CheckTableCalls() { ExpectThrow("escapes the constant-offset call/return model", [] { Build({Sop1(0u, 0u, 3u, 8u), Call(4u, 8u, 2u), Sopp(8u, 2u, 0xfffdu), Sopp(12u, 1u), Sop1(16u, 0u, 0x20u, 8u)}); }); + static_cast(Build({Call(0u, 8u, 3u), Sop2(4u, 8u, 0x15u, 0u, 2u), Sop2(8u, 10u, 0x1bu, 8u, 2u), Sopp(12u, 1u), Sop1(16u, 0u, 0x20u, 8u)})); + ExpectThrow("escapes the constant-offset call/return model", [] { + Build({Call(0u, 8u, 3u), Sop2(4u, 8u, 0x0eu, 0u, 2u), Sop2(8u, 10u, 0x1bu, 8u, 2u), Sopp(12u, 1u), Sop1(16u, 0u, 0x20u, 8u)}); + }); } enum class HoistedShape { InLoop, Hoisted, SecondWriterOnArm, LoadOnArmOnly, WriterAfterCall, WriterAfterLoop, CallOnArm, ConstantPointer, SkipsNullTarget, LongBranchWriter }; diff --git a/core/libs/tests/SslContextLifetime.cpp b/core/libs/tests/SslContextLifetime.cpp new file mode 100644 index 0000000000..198e1c7cbe --- /dev/null +++ b/core/libs/tests/SslContextLifetime.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceSsl/Export.cpp" + +#include +#include +#include +#include +#include +#include + +extern "C" bool KernelLookupSocketNative_nid_no_patch(int, std::shared_ptr*, std::uintptr_t*) { + std::fputs("unexpected native socket lookup in SSL lifetime test\n", stderr); + std::abort(); +} + +namespace { + +void Check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +int CreateContext() { + const int id = sceSslInit_nid_postfix(1024 * 1024); + Check(id > 0, "context creation failed"); + return id; +} + +int CreateConnection(int context) { + const int id = sceSslCreateConnection(context, -1, "localhost"); + Check(id > 0, "connection creation failed"); + return id; +} + +void DeleteBeforeTerm() { + for (int cycle = 0; cycle < 3; ++cycle) { + const int context = CreateContext(); + const int connection = CreateConnection(context); + Check(sceSslDeleteConnection(connection) == 0, "delete before term failed"); + Check(sceSslTerm_nid_postfix(context) == 0, "term after delete failed"); + Check(State().connections.empty() && State().contexts.empty(), "delete-first cycle retained records"); + } + std::puts("SSL delete-first control: 3 cycles, no retained records"); +} + +void TermBeforeDelete() { + bool clean = true; + for (int cycle = 0; cycle < 3; ++cycle) { + const auto before = State().connections.size(); + const int context = CreateContext(); + const int connection = CreateConnection(context); + const auto created = State().connections.size(); + Check(sceSslTerm_nid_postfix(context) == 0, "term before delete failed"); + const auto terminated = State().connections.size(); + Check(sceSslDeleteConnection(connection) == ErrorInvalidId, "post-term delete result changed"); + const auto deleted = State().connections.size(); + Check(State().contexts.empty(), "context survived termination"); + clean = clean && before == 0 && created == 1 && terminated == 0 && deleted == 0; + std::printf("SSL term-first cycle %d: connections %zu -> %zu -> %zu -> %zu\n", cycle + 1, before, created, terminated, deleted); + } + Check(clean, "termination retained orphan connection records"); +} + +void SeparateContexts() { + const int first = CreateContext(); + const int second = CreateContext(); + const int firstConnection = CreateConnection(first); + const int secondConnection = CreateConnection(second); + const int extraConnection = CreateConnection(first); + std::weak_ptr lifetime = State().contexts.at(first); + Check(sceSslTerm_nid_postfix(first) == 0, "selective term failed"); + Check(lifetime.expired(), "idle terminated context survived"); + Check(State().connections.size() == 1 && State().connections.contains(secondConnection), "term did not isolate connection ownership"); + Check(!State().connections.contains(firstConnection) && !State().connections.contains(extraConnection), "term retained a child connection"); + Check(sceSslTerm_nid_postfix(first) == ErrorInvalidId, "repeated term result changed"); + Check(sceSslCreateConnection(first, -1, "localhost") == ErrorInvalidId, "create accepted a terminated context"); + Check(sceSslDeleteConnection(firstConnection) == ErrorInvalidId, "delete accepted a terminated connection"); + Check(sceSslSetVerifyCallback(firstConnection, nullptr, nullptr) == ErrorInvalidId, "callback accepted a terminated connection"); + Check(sceSslConnect(firstConnection) == ErrorInvalidId && sceSslClose(firstConnection) == ErrorInvalidId, "I/O accepted a terminated connection"); + Check(sceSslSetVerifyCallback(secondConnection, nullptr, nullptr) == 0, "term invalidated another context's connection"); + Check(sceSslDeleteConnection(secondConnection) == 0 && sceSslTerm_nid_postfix(second) == 0, "unrelated context cleanup failed"); + Check(State().connections.empty() && State().contexts.empty(), "selective term retained records"); +} + +void ConcurrentLifecycle() { + for (int cycle = 0; cycle < 32; ++cycle) { + const int context = CreateContext(); + std::array connections{}; + for (auto& id : connections) id = CreateConnection(context); + std::barrier start(3); + std::atomic valid{true}; + std::thread creator([&] { + start.arrive_and_wait(); + for (int attempt = 0; attempt < 16; ++attempt) { + const int id = sceSslCreateConnection(context, -1, "localhost"); + if (id == ErrorInvalidId) continue; + if (id <= 0) { valid = false; continue; } + const int result = sceSslDeleteConnection(id); + if (result != 0 && result != ErrorInvalidId) valid = false; + } + }); + std::thread deleter([&] { + start.arrive_and_wait(); + for (const auto id : connections) { + const int result = sceSslDeleteConnection(id); + if (result != 0 && result != ErrorInvalidId) valid = false; + } + }); + start.arrive_and_wait(); + const int result = sceSslTerm_nid_postfix(context); + creator.join(); + deleter.join(); + Check(result == 0 && valid, "concurrent lifecycle returned an unexpected error"); + Check(State().connections.empty() && State().contexts.empty(), "concurrent termination retained records"); + } + std::puts("SSL concurrent create/delete/term: 32 cycles, no retained records"); +} + +struct CallbackState { + std::latch entered{1}; + std::latch resume{1}; + int context = 0; + bool valid = false; +}; + +int APS5_VABI PendingCallback(int context, unsigned int, void**, int count, void* argument) { + auto& state = *static_cast(argument); + state.valid = context == state.context && count == 0; + state.entered.count_down(); + state.resume.wait(); + return 0; +} + +void InFlightSession() { + const int context = CreateContext(); + const int connection = CreateConnection(context); + auto owner = State().contexts.at(context); + std::weak_ptr contextLifetime = owner; + auto session = std::make_shared(context, owner, std::shared_ptr{}, 0); + { + PoolScope scope(owner->pool); + session->Configure("localhost", "", InitialVerifyOptions); + } + Check(owner->pool.current > 0, "session did not allocate backend state"); + session->verified = true; + session->verifyResult = ErrorCallbackCheck; + State().connections.at(connection).session = session; + std::weak_ptr sessionLifetime = session; + CallbackState callback; + callback.context = context; + Check(sceSslSetVerifyCallback(connection, PendingCallback, &callback) == 0, "callback setup failed"); + owner.reset(); + session.reset(); + int operationResult = ErrorInvalidId; + std::thread operation([&] { operationResult = sceSslConnect(connection); }); + callback.entered.wait(); + const int result = sceSslTerm_nid_postfix(context); + const bool removed = State().connections.empty() && State().contexts.empty(); + const bool retained = !sessionLifetime.expired() && !contextLifetime.expired(); + callback.resume.count_down(); + operation.join(); + Check(result == 0 && removed, "term did not remove the in-flight connection"); + Check(retained, "term destroyed an in-flight session or its pool"); + Check(callback.valid && operationResult == 0, "in-flight verification callback did not finish"); + Check(sessionLifetime.expired() && contextLifetime.expired(), "finished operation retained its session or pool"); + Check(sceSslConnect(connection) == ErrorInvalidId && sceSslDeleteConnection(connection) == ErrorInvalidId, "post-term connection became usable"); + std::puts("SSL in-flight callback completed; session and pool released after completion"); +} + +} + +int main() { + try { + DeleteBeforeTerm(); + TermBeforeDelete(); + SeparateContexts(); + ConcurrentLifecycle(); + InFlightSession(); + std::puts("SSL context lifetime passed"); + } catch (const std::exception& error) { + std::fprintf(stderr, "SSL context lifetime failed: %s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/UltFinalizeAllocation.cpp b/core/libs/tests/UltFinalizeAllocation.cpp new file mode 100644 index 0000000000..c5730d7f4a --- /dev/null +++ b/core/libs/tests/UltFinalizeAllocation.cpp @@ -0,0 +1,72 @@ +#include "prx/libSceUlt/libSceUlt.hpp" +#include +#include +#include +#include +#include + +static int allocationBudget = -1; +static unsigned allocationAttempts = 0; + +void* operator new(std::size_t size) { + if (allocationBudget >= 0) { + ++allocationAttempts; + if (allocationBudget == 0) { + allocationBudget = -1; + throw std::bad_alloc(); + } + --allocationBudget; + } + if (void* memory = std::malloc(size ? size : 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); } + +struct alignas(8) Object { std::uint8_t bytes[256]{}; }; + +static void Require(bool passed, const char* message) { + if (!passed) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +int main() { + Require(sceUltInitialize() == ULT_OK, "initialization failed"); + unsigned failures = 0; + unsigned rejectedCases = 0; + for (int budget = 0; budget < 8; ++budget) { + Object semaphore{}; + Object mutex{}; + Require(sceUltSemaphoreCreate(&semaphore, "semaphore", 1, nullptr, nullptr, 0) == ULT_OK, "semaphore creation failed"); + Require(sceUltMutexCreate(&mutex, "mutex", nullptr, nullptr, 0) == ULT_OK, "mutex creation failed"); + allocationAttempts = 0; + allocationBudget = budget; + bool rejected = false; + int outcome = ULT_OK; + try { outcome = sceUltFinalize(); } + catch (const std::bad_alloc&) { rejected = true; } + allocationBudget = -1; + + rejectedCases += rejected; + const int semaphoreResult = sceUltSemaphoreTryAcquire(&semaphore, 1); + const int mutexResult = sceUltMutexTryLock(&mutex); + if (semaphoreResult == ULT_OK) Require(sceUltSemaphoreRelease(&semaphore, 1) == ULT_OK, "semaphore release failed"); + if (mutexResult == ULT_OK) Require(sceUltMutexUnlock(&mutex) == ULT_OK, "mutex unlock failed"); + const int expected = rejected ? ULT_OK : ULT_ERROR_STATE; + if (outcome != ULT_OK || semaphoreResult != expected || mutexResult != expected) { + std::fprintf(stderr, "budget=%d rejected=%d attempts=%u semaphore=%d mutex=%d\n", + budget, rejected, allocationAttempts, semaphoreResult, mutexResult); + ++failures; + } + if (rejected) Require(sceUltFinalize() == ULT_OK, "finalization retry failed"); + Require(sceUltSemaphoreTryAcquire(&semaphore, 1) == ULT_ERROR_STATE, "finalized semaphore is still live"); + Require(sceUltMutexTryLock(&mutex) == ULT_ERROR_STATE, "finalized mutex is still live"); + } + std::printf("finalization allocation budgets: 8 cases, %u rejections, %u incorrect lifecycles\n", rejectedCases, failures); + Require(rejectedCases > 0, "no allocation failure was injected"); + Require(rejectedCases < 8, "no finalization succeeded without a retry"); + return failures == 0 ? 0 : 1; +} diff --git a/core/libs/tests/UnwindCallbackAbi.cpp b/core/libs/tests/UnwindCallbackAbi.cpp index 18fb85df1d..c0fbcbbc2b 100644 --- a/core/libs/tests/UnwindCallbackAbi.cpp +++ b/core/libs/tests/UnwindCallbackAbi.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/exceptions/Runtime.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Unwind.hpp" #include @@ -5,11 +7,6 @@ #include #include -extern "C" _Unwind_Reason_Code APS5_VABI _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn trace, void* argument); -extern "C" _Unwind_Reason_Code APS5_VABI _Unwind_ForcedUnwind_nid_postfix(_Unwind_Exception* exception, _Unwind_Stop_Fn stop, void* argument); -extern "C" _Unwind_Ptr APS5_VABI _Unwind_GetRegionStart_nid_postfix(_Unwind_Context* context); -extern "C" void* APS5_VABI __cxa_begin_catch_nid_postfix(void* exception); -extern "C" void APS5_VABI __cxa_end_catch_nid_postfix(); extern "C" [[gnu::noinline]] void BacktraceFromHere(); extern "C" [[gnu::noinline]] void ForceUnwindFromHere(); @@ -85,6 +82,7 @@ void CheckDeleteException() { exception.exception_class = ForeignClass; exception.exception_cleanup = GuestCleanup; _Unwind_DeleteException_nid_postfix(&exception); + CheckCleanup(exception); } @@ -94,6 +92,7 @@ void CheckEndCatch(std::uint64_t exceptionClass, Cleanup function) { exception.exception_cleanup = function; __cxa_begin_catch_nid_postfix(&exception); __cxa_end_catch_nid_postfix(); + CheckCleanup(exception); } @@ -110,6 +109,7 @@ extern "C" void ForceUnwindFromHere() { _Unwind_Exception exception {}; exception.exception_class = ForeignClass; _Unwind_ForcedUnwind_nid_postfix(&exception, Stop, &stopArgument); + Require(stop.count == 1, "_Unwind_ForcedUnwind calls the stop function once"); Require(stop.version == 1, "the stop function gets version 1"); Require(stop.actions == (_UA_CLEANUP_PHASE | _UA_FORCE_UNWIND), "the stop function gets _UA_CLEANUP_PHASE | _UA_FORCE_UNWIND"); diff --git a/core/libs/tests/WindowsBacktraceReport.cpp b/core/libs/tests/WindowsBacktraceReport.cpp index d16340e2b3..b7c91f7aa4 100644 --- a/core/libs/tests/WindowsBacktraceReport.cpp +++ b/core/libs/tests/WindowsBacktraceReport.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include #include #include @@ -6,8 +7,6 @@ #include "../prx/libc/include/general/VabiMacros.hpp" #include "../prx/libc/include/FailureReport.hpp" -extern "C" int* APS5_VABI __error_nid_postfix(); - namespace { void KeptReport() noexcept { diff --git a/core/libs/tests/WindowsExceptions.cpp b/core/libs/tests/WindowsExceptions.cpp index c83acd49c2..5adb55cac4 100644 --- a/core/libs/tests/WindowsExceptions.cpp +++ b/core/libs/tests/WindowsExceptions.cpp @@ -56,6 +56,17 @@ static void TestTypeInfoVtables() { if (!upcastType(&multiple, &base, &object) || object != storage.data() + 16) throw std::runtime_error("multiple RTTI upcast failed"); object = storage.data(); if (catchType(&other, &single, &object, 0)) throw std::runtime_error("unrelated RTTI catch succeeded"); + TypeRecord shared {classTable + 2 * sizeof(void*), "6Shared"}; + SingleRecord publicPath {{singleTable + 2 * sizeof(void*), "10PublicPath"}, &shared}; + SingleRecord privatePath {{singleTable + 2 * sizeof(void*), "11PrivatePath"}, &shared}; + MultipleRecord mixed {{multipleTable + 2 * sizeof(void*), "5Mixed"}, 1, 2, {{&publicPath.type, 2}, {&privatePath.type, 16 << 8}}}; + MultipleRecord hidden {{multipleTable + 2 * sizeof(void*), "6Hidden"}, 0, 1, {{&privatePath.type, 0}, {}}}; + object = storage.data(); + if (catchType(&shared, &mixed, &object, 0) || object != storage.data()) throw std::runtime_error("RTTI catch of a base repeated through a private path succeeded"); + if (upcastType(&mixed, &shared, &object) || object != storage.data()) throw std::runtime_error("RTTI upcast to a base repeated through a private path succeeded"); + if (!catchType(&publicPath, &mixed, &object, 0) || object != storage.data()) throw std::runtime_error("RTTI catch of the public path failed"); + object = storage.data(); + if (catchType(&shared, &hidden, &object, 0)) throw std::runtime_error("RTTI catch of a privately inherited base succeeded"); } extern "C" [[noreturn]] void NotImplemented_nid_no_patch(const char*); diff --git a/core/libs/tests/WindowsSharedMapCost.cpp b/core/libs/tests/WindowsSharedMapCost.cpp new file mode 100644 index 0000000000..ce6c25b6e7 --- /dev/null +++ b/core/libs/tests/WindowsSharedMapCost.cpp @@ -0,0 +1,78 @@ +#define NOMINMAX +#include "prx/libc/include/WindowsMappings.hpp" +#include +#include +#include +#include +#include +#include + +namespace { +using Mappings = GuestArena::WindowsMappings; +constexpr std::size_t PageBytes = 16384; +constexpr int Rounds = 5; + +[[noreturn]] void Fail(const char* what) { + std::fprintf(stderr, "windows shared map cost: %s (error %lu)\n", what, GetLastError()); + std::exit(1); +} + +std::size_t MappedViews(const unsigned char* begin, std::size_t bytes) { + std::set bases; + for (auto cursor = begin; cursor < begin + bytes;) { + MEMORY_BASIC_INFORMATION info{}; + if (VirtualQuery(cursor, &info, sizeof(info)) != sizeof(info)) Fail("view query failed"); + if (info.Type == MEM_MAPPED) bases.insert(info.AllocationBase); + cursor = static_cast(info.BaseAddress) + info.RegionSize; + } + return bases.size(); +} + +struct Sample { + double mapMs = 0; + double releaseMs = 0; + std::size_t views = 0; +}; + +Sample MapOnce(std::size_t pages) { + const auto bytes = pages * PageBytes; + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, static_cast(static_cast(bytes) >> 32), static_cast(bytes), nullptr); + auto* memory = static_cast(Mappings::Get().Reserve(nullptr, bytes)); + if (section == nullptr || memory == nullptr) Fail("cannot allocate shared memory"); + const auto start = std::chrono::steady_clock::now(); + Mappings::Get().Map(memory, bytes, section, 0, PAGE_READWRITE); + Sample sample; + sample.mapMs = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); + sample.views = MappedViews(memory, bytes); + memory[0] = 1; + memory[bytes - 1] = 1; + const auto released = std::chrono::steady_clock::now(); + Mappings::Get().Reset(memory, bytes); + sample.releaseMs = std::chrono::duration(std::chrono::steady_clock::now() - released).count(); + VirtualFree(memory, 0, MEM_RELEASE); + CloseHandle(section); + return sample; +} + +double Median(std::vector values) { + std::sort(values.begin(), values.end()); + return values[values.size() / 2]; +} + +} + +int main() { + for (const std::size_t pages : {std::size_t{256}, std::size_t{16384}, std::size_t{65536}}) { + std::vector map; + std::vector release; + std::size_t views = 0; + for (int round = 0; round < Rounds; ++round) { + const auto sample = MapOnce(pages); + map.push_back(sample.mapMs); + release.push_back(sample.releaseMs); + views = sample.views; + } + std::printf("shared map of %zu pages (%zu MiB): map median %.2f ms (%.2f us per page), release median %.2f ms, %zu views\n", pages, pages * PageBytes >> 20, Median(map), Median(map) * 1000.0 / static_cast(pages), Median(release), views); + } + return 0; +} diff --git a/core/libs/tests/WindowsWriteWatch.cpp b/core/libs/tests/WindowsWriteWatch.cpp index b44c48af61..65d98f68e2 100644 --- a/core/libs/tests/WindowsWriteWatch.cpp +++ b/core/libs/tests/WindowsWriteWatch.cpp @@ -172,6 +172,149 @@ void CheckPrivate() { VirtualFree(memory, 0, MEM_RELEASE); } +MEMORY_BASIC_INFORMATION RegionAt(const void* address) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "private commit query failed"); + return info; +} + +void CheckPrivateCommitCarvesPlaceholders() { + auto& mappings = Mappings::Get(); + constexpr std::size_t Page = 16384; + constexpr std::size_t Pages = 256; + constexpr std::size_t Span = Pages * Page; + constexpr std::size_t Reserved = std::size_t{1} << 30; + auto* holder = static_cast(mappings.Reserve(nullptr, Reserved)); + Require(holder != nullptr, "cannot reserve a placeholder for private commits"); + auto* memory = holder + Page; + const auto page = [&](std::size_t index) { return memory + index * Page; }; + const auto at = [](const unsigned char* pointer) { return reinterpret_cast(pointer); }; + auto before = Mappings::MemoryQueries(); + auto created = mappings.Commit(memory, Span, PAGE_READWRITE, Page, true); + const auto queries = Mappings::MemoryQueries() - before; + Require(queries == 1, ("committing 256 fresh pages queried the address space " + std::to_string(queries) + " times").c_str()); + Require(created.size() == 1 && created[0].first == at(memory) && created[0].second == Span, "a fresh commit did not report its range as one created range"); + for (std::size_t index = 0; index < Pages; ++index) { + const auto info = RegionAt(page(index)); + Require(info.State == MEM_COMMIT && info.Type == MEM_PRIVATE && info.AllocationBase == page(index) && info.RegionSize == Page && info.Protect == PAGE_READWRITE, "a committed page is not a read-write allocation of its own"); + } + const auto prefix = RegionAt(holder); + Require(prefix.State == MEM_RESERVE && prefix.AllocationBase == holder && prefix.RegionSize == Page, "the placeholder before the commit changed"); + const auto suffix = RegionAt(page(Pages)); + Require(suffix.State == MEM_RESERVE && suffix.AllocationBase == page(Pages) && suffix.RegionSize == Reserved - Page - Span, "the placeholder after the commit changed"); + for (std::size_t index = 0; index < Pages; ++index) page(index)[8] = static_cast(index + 1); + mappings.Reset(page(11), Page); + const auto released = RegionAt(page(11)); + Require(released.State == MEM_RESERVE && released.AllocationBase == page(11) && released.RegionSize == Page, "a released page did not become a placeholder of its own"); + before = Mappings::MemoryQueries(); + created = mappings.Commit(page(11), Page, PAGE_READWRITE, Page, true); + Require(Mappings::MemoryQueries() - before <= 1 && created.size() == 1 && created[0].first == at(page(11)) && created[0].second == Page && page(11)[8] == 0, "recommitting a released page was not one fresh allocation"); + for (std::size_t index = 0; index < Pages; ++index) { + if (index != 11) Require(page(index)[8] == static_cast(index + 1), "a release or commit changed a neighbouring page"); + } + created = mappings.Commit(memory, Span, PAGE_READONLY, Page, true); + Require(created.empty() && RegionAt(page(0)).Protect == PAGE_READONLY && RegionAt(page(Pages - 1)).Protect == PAGE_READONLY, "a protection change reported fresh memory or missed a page"); + mappings.Commit(memory, Span, PAGE_READWRITE, Page, true); + std::array written{}; + std::size_t count = written.size(); + mappings.Collect(at(page(40)), 4 * Page, written.data(), &count, true); + page(41)[4096 + 3] = 1; + count = written.size(); + Require(mappings.Collect(at(page(40)), 4 * Page, written.data(), &count, true, Collection::Armed) && count == 1 && written[0] == page(41) + 4096, "a carved page lost its write watch"); + mappings.Reset(page(20), 2 * Page); + mappings.Reset(page(22), 2 * Page); + created = mappings.Commit(page(20), 4 * Page, PAGE_READWRITE, 4 * Page, true); + Require(created.size() == 1 && created[0].second == 4 * Page && RegionAt(page(23)).AllocationBase == page(20), "a commit over two placeholders was not one allocation"); + mappings.Reset(memory, Span); + Require(VirtualFree(holder, Reserved, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) && VirtualFree(holder, 0, MEM_RELEASE), "cannot release the private commit placeholder"); +} + +void CheckPrivateCommitEdgeLayouts() { + auto& mappings = Mappings::Get(); + constexpr std::size_t Page = 16384; + constexpr std::size_t Pages = 32; + auto* memory = static_cast(mappings.Reserve(nullptr, Pages * Page)); + Require(memory != nullptr, "cannot reserve a placeholder for private commit edges"); + const auto page = [&](std::size_t index) { return memory + index * Page; }; + const auto range = [&](std::size_t first, std::size_t count) { return std::make_pair(reinterpret_cast(page(first)), count * Page); }; + const auto region = [&](std::size_t first, std::size_t count, DWORD state) { + const auto info = RegionAt(page(first)); + return info.State == state && info.AllocationBase == page(first) && info.RegionSize == count * Page; + }; + const auto ownPages = [&](std::size_t first, std::size_t count) { + for (auto index = first; index < first + count; ++index) { + if (!region(index, 1, MEM_COMMIT)) return false; + } + return true; + }; + auto before = Mappings::MemoryQueries(); + auto created = mappings.Commit(page(2), 4 * Page, PAGE_READWRITE, 4 * Page, true); + Require(Mappings::MemoryQueries() - before <= 1 && created.size() == 1 && created[0] == range(2, 4), "a whole-range granule inside a placeholder was not one created range from one query"); + Require(region(0, 2, MEM_RESERVE) && region(2, 4, MEM_COMMIT) && region(6, Pages - 6, MEM_RESERVE), "a whole-range granule inside a placeholder was not one allocation between two placeholders"); + page(2)[8] = 0x5a; + before = Mappings::MemoryQueries(); + created = mappings.Commit(page(0), 8 * Page, PAGE_READWRITE, Page, true); + Require(Mappings::MemoryQueries() - before <= 3 && created.size() == 2 && created[0] == range(0, 2) && created[1] == range(6, 2), "a commit around a committed piece did not create exactly the placeholders around it"); + Require(ownPages(0, 2) && region(2, 4, MEM_COMMIT) && page(2)[8] == 0x5a && ownPages(6, 2) && region(8, Pages - 8, MEM_RESERVE), "a commit around a committed piece changed the piece or the layout"); + for (std::size_t index = 0; index < 8; ++index) page(index)[8] = static_cast(index + 1); + mappings.Reset(page(6), 2 * Page); + Require(region(6, 2, MEM_RESERVE) && ownPages(0, 2) && region(2, 4, MEM_COMMIT) && page(1)[8] == 2 && page(5)[8] == 6 && region(8, Pages - 8, MEM_RESERVE), "a partial release of carved pages changed a neighbour"); + mappings.Reset(page(2), 4 * Page); + before = Mappings::MemoryQueries(); + created = mappings.Commit(page(2), 4 * Page, PAGE_READWRITE, Page, true); + Require(Mappings::MemoryQueries() - before <= 1 && created.size() == 1 && created[0] == range(2, 4) && ownPages(2, 4) && page(2)[8] == 0 && page(5)[8] == 0, "a fixed remap was not carved into fresh pages"); + Require(page(1)[8] == 2 && region(6, 2, MEM_RESERVE), "a fixed remap changed a neighbour"); + before = Mappings::MemoryQueries(); + created = mappings.Commit(page(6), 4 * Page, PAGE_READWRITE, Page, true); + Require(Mappings::MemoryQueries() - before <= 2 && created.size() == 1 && created[0] == range(6, 4) && ownPages(6, 4) && region(10, Pages - 10, MEM_RESERVE), "a commit over adjacent placeholders was not carved page by page"); + bool refused = false; + try { + mappings.Commit(page(12), Page, PAGE_READWRITE, 0, true); + } catch (const std::invalid_argument&) { + refused = true; + } + Require(refused && region(10, Pages - 10, MEM_RESERVE), "a zero commit granule was accepted or changed the placeholder"); + mappings.Commit(page(10), 3 * Page, PAGE_READWRITE, 3 * Page, true); + mappings.Reset(page(10), 3 * Page); + created = mappings.Commit(page(10), 4 * Page, PAGE_READWRITE, 2 * Page, true); + Require(created.size() == 1 && created[0] == range(10, 4) && region(10, 2, MEM_COMMIT) && region(12, 2, MEM_COMMIT) && region(14, Pages - 14, MEM_RESERVE), "a granule crossing the end of a carved placeholder did not take the coalescing path"); + mappings.Reset(memory, 14 * Page); + Require(VirtualFree(memory, Pages * Page, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) && VirtualFree(memory, 0, MEM_RELEASE), "cannot release the private commit edge placeholder"); +} + +void CheckPrivateCommitTails() { + auto& mappings = Mappings::Get(); + constexpr std::size_t Page = 16384; + constexpr std::size_t Pages = 8; + auto* memory = static_cast(mappings.Reserve(nullptr, Pages * Page)); + Require(memory != nullptr, "cannot reserve a placeholder for private commit tails"); + const auto page = [&](std::size_t index) { return memory + index * Page; }; + const auto at = [](const unsigned char* pointer) { return reinterpret_cast(pointer); }; + const auto allocation = [](const unsigned char* base, std::size_t bytes, DWORD state) { + const auto info = RegionAt(base); + return info.State == state && info.AllocationBase == base && info.RegionSize == bytes; + }; + const auto unwatched = [](unsigned char* base) { + std::array written{}; + ULONG_PTR count = written.size(); + DWORD granularity = 0; + return GetWriteWatch(0, base, Small, written.data(), &count, &granularity) != 0; + }; + auto before = Mappings::MemoryQueries(); + auto created = mappings.Commit(page(1), 2 * Page + Small, PAGE_READWRITE, Page, false); + Require(Mappings::MemoryQueries() - before == 1 && created.size() == 1 && created[0].first == at(page(1)) && created[0].second == 2 * Page + Small, "a commit with a short last piece was not one created range from one query"); + Require(allocation(page(0), Page, MEM_RESERVE) && allocation(page(1), Page, MEM_COMMIT) && allocation(page(2), Page, MEM_COMMIT) && allocation(page(3), Small, MEM_COMMIT) && allocation(page(3) + Small, (Pages - 3) * Page - Small, MEM_RESERVE), "a short last piece was not its own allocation before the rest of the placeholder"); + page(3)[Small - 1] = 7; + Require(unwatched(page(1)) && unwatched(page(3)), "an unwatched commit carved write-watched pages"); + before = Mappings::MemoryQueries(); + created = mappings.Commit(page(5), 3 * Page, PAGE_READWRITE, Page, true); + Require(Mappings::MemoryQueries() - before == 1 && created.size() == 1 && created[0].first == at(page(5)) && created[0].second == 3 * Page, "a commit to the end of a placeholder was not one created range from one query"); + Require(allocation(page(3) + Small, 2 * Page - Small, MEM_RESERVE) && allocation(page(5), Page, MEM_COMMIT) && allocation(page(6), Page, MEM_COMMIT) && allocation(page(7), Page, MEM_COMMIT) && page(3)[Small - 1] == 7, "a commit to the end of a placeholder changed the placeholder before it or a neighbour"); + mappings.Reset(page(1), 2 * Page + Small); + mappings.Reset(page(5), 3 * Page); + Require(VirtualFree(memory, Pages * Page, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) && VirtualFree(memory, 0, MEM_RELEASE), "cannot release the private commit tail placeholder"); +} + void CheckForgottenSectionKeepsNoPages() { auto& mappings = Mappings::Get(); HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, Bytes, nullptr); @@ -216,6 +359,277 @@ void CheckConcurrentWrites() { region.Collect(Collection::Armed); Require(region.Collect(Collection::Armed).empty(), "concurrent writes did not settle"); } + +void CheckComparedWhenShared() { + auto& mappings = Mappings::Get(); + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, 2 * Bytes, nullptr); + auto* first = static_cast(mappings.Reserve(nullptr, 2 * Bytes)); + auto* second = static_cast(mappings.Reserve(nullptr, 2 * Bytes)); + Require(section && first && second, "cannot allocate compared sharing test memory"); + mappings.ForgetSection(section); + const auto collect = [&](unsigned char* address, std::size_t bytes) { + std::array pages{}; + std::size_t count = pages.size(); + return mappings.Collect(reinterpret_cast(address), bytes, pages.data(), &count, true); + }; + const auto protection = [](unsigned char* address) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "compared sharing protection query failed"); + return info; + }; + mappings.Map(first, 2 * Bytes, section, 0, PAGE_READWRITE, true, true); + Require(protection(first).Type == MEM_PRIVATE, "the compared sharing test memory is not resident"); + mappings.CompareWhenShared(reinterpret_cast(first), Bytes); + Require(collect(first, 2 * Bytes), "a resident page to be compared when shared stopped being collected"); + mappings.Map(second, 2 * Bytes, section, 0, PAGE_READWRITE, true, true); + Require(protection(first).Type == MEM_MAPPED && protection(second).Type == MEM_MAPPED, "a second mapping kept the first resident"); + Require(!collect(first, Bytes) && !collect(second, Bytes), "a page to be compared when shared was collected once shared"); + Require(!collect(first, 2 * Bytes), "a collect over a page to be compared when shared succeeded once shared"); + Require(protection(first).Protect == PAGE_READWRITE && protection(second).Protect == PAGE_READWRITE, "a page to be compared when shared was armed once shared"); + const auto before = faults.load(); + first[8] = 0x5a; + second[Small + 8] = 0xa5; + Require(faults.load() == before && second[8] == 0x5a && first[Small + 8] == 0xa5, "a store into a page to be compared when shared faulted once shared"); + Require(collect(first + Bytes, Bytes), "a shared page that was never to be compared stopped being collected"); + mappings.Reset(first, 2 * Bytes); + mappings.Reset(second, 2 * Bytes); + for (auto* reserved : {first, second}) { + VirtualFree(reserved, 2 * Bytes, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS); + VirtualFree(reserved, 0, MEM_RELEASE); + } + mappings.ForgetSection(section); + CloseHandle(section); +} + +void CheckResidentOnlyComparedWhenShared() { + auto& mappings = Mappings::Get(); + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, Bytes, nullptr); + auto* first = static_cast(mappings.Reserve(nullptr, Bytes)); + auto* second = static_cast(mappings.Reserve(nullptr, Bytes)); + Require(section && first && second, "cannot allocate resident-only sharing test memory"); + mappings.ForgetSection(section); + mappings.Map(first, Bytes, section, 0, PAGE_READWRITE, true, true); + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(first, &info, sizeof(info)) == sizeof(info) && info.Type == MEM_PRIVATE, "the resident-only sharing test memory is not resident"); + mappings.CompareWhenShared(reinterpret_cast(first), Bytes, true); + mappings.Map(second, Bytes, section, 0, PAGE_READWRITE, true, true); + std::array pages{}; + std::size_t count = pages.size(); + Require(!mappings.Collect(reinterpret_cast(first), Bytes, pages.data(), &count, true), "a resident page marked to be compared was collected once shared"); + const auto before = faults.load(); + first[8] = 0x3c; + Require(faults.load() == before && second[8] == 0x3c, "a store into a resident page marked to be compared faulted once shared"); + mappings.Reset(first, Bytes); + mappings.Reset(second, Bytes); + for (auto* reserved : {first, second}) { + VirtualFree(reserved, Bytes, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS); + VirtualFree(reserved, 0, MEM_RELEASE); + } + mappings.ForgetSection(section); + CloseHandle(section); + Region region; + mappings.CompareWhenShared(reinterpret_cast(region.first), Bytes, true); + Require(region.Collect(Collection::All, 0, 1) == Pages(0, 1), "marking only resident pages changed a shared view"); + Require(region.Protection(0) == PAGE_READONLY, "a shared view outside a resident-only mark was not armed"); +} + +void CheckComparedWhenSharedRemapped() { + auto& mappings = Mappings::Get(); + constexpr std::size_t Page = 16384; + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, Bytes, nullptr); + std::array reserved{}; + for (auto& address : reserved) address = static_cast(mappings.Reserve(nullptr, Bytes)); + Require(section && reserved[0] && reserved[1] && reserved[2], "cannot allocate remapped compared sharing test memory"); + mappings.ForgetSection(section); + const auto collect = [&](unsigned char* address) { + std::array pages{}; + std::size_t count = pages.size(); + return mappings.Collect(reinterpret_cast(address), Page, pages.data(), &count, true); + }; + const auto memory = [](unsigned char* address) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "remapped compared sharing query failed"); + return info; + }; + const auto share = [&] { + mappings.Map(reserved[1], Page, section, 0, PAGE_READWRITE, true, true); + mappings.Map(reserved[2], Page, section, 0, PAGE_READWRITE, true, true); + Require(memory(reserved[1]).Type == MEM_MAPPED && memory(reserved[2]).Type == MEM_MAPPED, "the remapped page is not shared"); + }; + const auto unmapShared = [&] { + mappings.Reset(reserved[1], Page); + mappings.Reset(reserved[2], Page); + }; + mappings.Map(reserved[0], Page, section, 0, PAGE_READWRITE, true, true); + Require(memory(reserved[0]).Type == MEM_PRIVATE, "the remapped compared sharing test memory is not resident"); + mappings.CompareWhenShared(reinterpret_cast(reserved[0]), Page); + mappings.Reset(reserved[0], Page); + share(); + Require(!collect(reserved[1]) && !collect(reserved[2]), "a page to be compared when shared was collected after every view was unmapped and it was mapped again"); + Require(memory(reserved[1]).Protect == PAGE_READWRITE && memory(reserved[2]).Protect == PAGE_READWRITE, "a page to be compared when shared was armed after every view was unmapped and it was mapped again"); + const auto before = faults.load(); + reserved[1][8] = 0x6b; + Require(faults.load() == before && reserved[2][8] == 0x6b, "a store into a remapped page to be compared faulted once shared"); + unmapShared(); + mappings.ForgetSection(section); + share(); + Require(collect(reserved[1]) && memory(reserved[2]).Protect == PAGE_READONLY, "a forgotten section's handle value kept its pages compared when shared"); + unmapShared(); + for (auto* address : reserved) { + VirtualFree(address, Bytes, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS); + VirtualFree(address, 0, MEM_RELEASE); + } + mappings.ForgetSection(section); + CloseHandle(section); +} + +void CheckComparedPagesReportedOnce() { + auto& mappings = Mappings::Get(); + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, 2 * Bytes, nullptr); + std::array reserved{}; + for (auto& address : reserved) address = static_cast(mappings.Reserve(nullptr, 2 * Bytes)); + Require(section && reserved[0] && reserved[1] && reserved[2], "cannot allocate compared page report test memory"); + mappings.ForgetSection(section); + const auto listed = [&](unsigned char* base, std::size_t begin, std::size_t count, bool unreported) { + const auto address = reinterpret_cast(base); + auto cursor = address + begin * Small; + const auto end = cursor + count * Small; + std::set result; + while (cursor < end) { + std::array pages{}; + std::size_t available = pages.size(); + mappings.ComparedPages(cursor, end - cursor, pages.data(), &available, unreported); + for (std::size_t i = 0; i < available; ++i) result.insert((reinterpret_cast(pages[i]) - address) / Small); + if (available < pages.size()) break; + cursor = reinterpret_cast(pages[available - 1]) + Small; + } + return result; + }; + auto* first = reserved[0]; + auto* second = reserved[1]; + mappings.Map(first, 2 * Bytes, section, 0, PAGE_READWRITE, true, true); + mappings.CompareWhenShared(reinterpret_cast(first), Bytes); + Require(listed(first, 0, 32, false).empty() && listed(first, 0, 32, true).empty(), "a resident page to be compared was listed before it was shared"); + mappings.Map(second, 2 * Bytes, section, 0, PAGE_READWRITE, true, true); + Require(listed(first, 0, 32, false) == Pages(0, 16) && listed(second, 0, 32, false) == Pages(0, 16), "the compared pages of both mappings were not listed once shared"); + Require(listed(first, 2, 4, true) == Pages(2, 6), "a partial take did not report exactly its compared pages"); + std::set rest = Pages(0, 2); + rest.insert(6); + for (std::size_t page = 7; page < 16; ++page) rest.insert(page); + Require(listed(first, 0, 32, true) == rest, "a compared page was reported twice or not at all"); + Require(listed(first, 0, 32, true).empty() && listed(first, 0, 32, false) == Pages(0, 16), "a compared page was reported again, or a listing consumed its mark"); + Require(listed(second, 0, 32, true) == Pages(0, 16) && listed(second, 0, 32, true).empty(), "the second mapping's compared pages were not reported exactly once"); + mappings.CompareWhenShared(reinterpret_cast(first), Bytes); + Require(listed(first, 0, 32, true).empty() && listed(second, 0, 32, true).empty(), "marking compared pages again reported them again"); + mappings.CompareWhenShared(reinterpret_cast(first + Bytes), Bytes); + Require(listed(first, 16, 16, true) == Pages(16, 32) && listed(second, 16, 16, true) == Pages(16, 32), "shared pages newly marked to be compared were not reported in every mapping"); + mappings.Map(reserved[2], 2 * Bytes, section, 0, PAGE_READWRITE, true, true); + Require(listed(reserved[2], 0, 32, true) == Pages(0, 32) && listed(first, 0, 32, true).empty(), "a new mapping of compared pages was not reported, or reported the others again"); + for (auto* address : reserved) mappings.Reset(address, 2 * Bytes); + for (auto* address : reserved) { + VirtualFree(address, 2 * Bytes, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS); + VirtualFree(address, 0, MEM_RELEASE); + } + mappings.ForgetSection(section); + CloseHandle(section); +} +} + +namespace { +void CheckSharedMapQueriesPerMapping() { + constexpr std::size_t Pages = 256; + constexpr std::size_t Span = Pages * 16384; + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, Span, nullptr); + auto* memory = static_cast(Mappings::Get().Reserve(nullptr, Span)); + Require(section && memory, "cannot allocate shared test memory"); + const auto before = Mappings::MemoryQueries(); + Mappings::Get().Map(memory, Span, section, 0, PAGE_READWRITE); + const auto queries = Mappings::MemoryQueries() - before; + for (std::size_t page = 0; page < Pages; ++page) memory[page * 16384 + 8] = static_cast(page); + for (std::size_t page = 0; page < Pages; ++page) Require(memory[page * 16384 + 8] == static_cast(page), "a shared page lost its store"); + Mappings::Get().Reset(memory, Span); + VirtualFree(memory, 0, MEM_RELEASE); + CloseHandle(section); + Require(queries < Pages, ("a shared mapping queried the address space for every page: " + std::to_string(queries) + " queries for " + std::to_string(Pages) + " pages").c_str()); +} + +std::size_t MappedViews(const unsigned char* begin, std::size_t bytes) { + std::set bases; + for (auto cursor = begin; cursor < begin + bytes;) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(cursor, &info, sizeof(info)) == sizeof(info), "view query failed"); + if (info.Type == MEM_MAPPED) bases.insert(info.AllocationBase); + cursor = static_cast(info.BaseAddress) + info.RegionSize; + } + return bases.size(); +} + +DWORD ProtectionAt(const unsigned char* address) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "protection query failed"); + return info.State == MEM_COMMIT ? info.Protect : 0; +} + +void CheckSharedRunIsOneView() { + constexpr std::size_t Gib = std::size_t{1} << 30; + HANDLE large = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, static_cast(Gib), nullptr); + auto* whole = static_cast(Mappings::Get().Reserve(nullptr, Gib)); + Require(large && whole, "cannot allocate a shared gibibyte"); + Mappings::Get().Map(whole, Gib, large, 0, PAGE_READWRITE); + const auto wholeViews = MappedViews(whole, Gib); + whole[Gib - 1] = 1; + Mappings::Get().Reset(whole, Gib); + VirtualFree(whole, 0, MEM_RELEASE); + CloseHandle(large); + Require(wholeViews == 1, ("a shared gibibyte was mapped as " + std::to_string(wholeViews) + " views instead of one").c_str()); + + constexpr std::size_t Page = 16384; + constexpr std::size_t Pages = 64; + constexpr std::size_t Span = Pages * Page; + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, Span + Page, nullptr); + auto* memory = static_cast(Mappings::Get().Reserve(nullptr, Span)); + auto* other = static_cast(Mappings::Get().Reserve(nullptr, Page)); + Require(section && memory && other, "cannot allocate shared test memory"); + Mappings::Get().Map(memory, Span, section, 0, PAGE_READWRITE); + for (std::size_t page = 0; page < Pages; ++page) memory[page * Page + 8] = static_cast(page + 1); + Require(MappedViews(memory, Span) == 1, "a shared run was not mapped as one view"); + Mappings::Get().SetProtection(reinterpret_cast(memory + 5 * Page), Page, PAGE_READONLY); + DWORD previous; + Require(VirtualProtect(memory + 5 * Page, Page, PAGE_READONLY, &previous), "guest protection of one page failed"); + Require(MappedViews(memory, Span) == 1, "a per-page protection split the run"); + Mappings::Get().Map(other, Page, section, 30 * Page, PAGE_READWRITE); + Require(MappedViews(memory, Span) == 1, "an alias of one page split the run"); + other[16] = 0x5a; + Require(memory[30 * Page + 16] == 0x5a, "an alias store did not reach the run"); + std::array collected{}; + std::size_t available = collected.size(); + Require(Mappings::Get().Collect(reinterpret_cast(memory + 50 * Page), Page, collected.data(), &available, true, Collection::All), "run collection failed"); + Require(ProtectionAt(memory + 50 * Page) == PAGE_READONLY, "collection did not arm the run's page"); + Mappings::Get().Reset(memory + 10 * Page, Page); + Require(MappedViews(memory, Span) == 2, ("a partial unmap left " + std::to_string(MappedViews(memory, Span)) + " views instead of two").c_str()); + Require(ProtectionAt(memory + 10 * Page) == 0, "the unmapped page is still mapped"); + Mappings::Get().Map(memory + 20 * Page, Page, section, Span, PAGE_READWRITE); + Require(MappedViews(memory, Span) == 4, ("a remap of one page left " + std::to_string(MappedViews(memory, Span)) + " views instead of four").c_str()); + Require(memory[20 * Page + 8] == 0, "the remapped page shows the old page"); + for (std::size_t page = 0; page < Pages; ++page) { + if (page == 10 || page == 20) continue; + Require(memory[page * Page + 8] == static_cast(page + 1), ("a split lost page " + std::to_string(page)).c_str()); + } + Require(ProtectionAt(memory + 5 * Page) == PAGE_READONLY && ProtectionAt(memory + 6 * Page) == PAGE_READWRITE, "a split lost a page protection"); + Require(ProtectionAt(memory + 50 * Page) == PAGE_READONLY, "a split dropped a write-tracking protection"); + const auto before = faults.load(); + memory[50 * Page + 8] = 0x77; + Require(faults == before + 1, "a write to an armed page of a split run did not fault"); + memory[51 * Page + 8] = 0x78; + available = collected.size(); + Require(Mappings::Get().Collect(reinterpret_cast(memory + 50 * Page), 2 * Page, collected.data(), &available, true, Collection::Armed), "armed collection failed"); + Require(available == 1 && collected[0] == memory + 50 * Page, "the split run lost a tracked write"); + Mappings::Get().Reset(memory, Span); + Mappings::Get().Reset(other, Page); + VirtualFree(memory, 0, MEM_RELEASE); + VirtualFree(other, 0, MEM_RELEASE); + CloseHandle(section); +} } int main() { @@ -230,6 +644,15 @@ int main() { CheckPrivate(); CheckConcurrentWrites(); CheckForgottenSectionKeepsNoPages(); + CheckSharedMapQueriesPerMapping(); + CheckSharedRunIsOneView(); + CheckComparedWhenShared(); + CheckResidentOnlyComparedWhenShared(); + CheckComparedWhenSharedRemapped(); + CheckComparedPagesReportedOnce(); + CheckPrivateCommitCarvesPlaceholders(); + CheckPrivateCommitEdgeLayouts(); + CheckPrivateCommitTails(); std::cout << "Windows deferred write-watch tests passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/tests/numeric-locale/comma.charmap b/core/libs/tests/numeric-locale/comma.charmap new file mode 100644 index 0000000000..cbfea3c784 --- /dev/null +++ b/core/libs/tests/numeric-locale/comma.charmap @@ -0,0 +1,135 @@ + ANYPS5_COMMA + % + / + 1 + 1 +CHARMAP + /x00 + /x01 + /x02 + /x03 + /x04 + /x05 + /x06 + /x07 + /x08 + /x09 + /x0a + /x0b + /x0c + /x0d + /x0e + /x0f + /x10 + /x11 + /x12 + /x13 + /x14 + /x15 + /x16 + /x17 + /x18 + /x19 + /x1a + /x1b + /x1c + /x1d + /x1e + /x1f + /x20 + /x21 + /x22 + /x23 + /x24 + /x25 + /x26 + /x27 + /x28 + /x29 + /x2a + /x2b + /x2c + /x2d + /x2e + /x2f + /x30 + /x31 + /x32 + /x33 + /x34 + /x35 + /x36 + /x37 + /x38 + /x39 + /x3a + /x3b + /x3c + /x3d + /x3e + /x3f + /x40 + /x41 + /x42 + /x43 + /x44 + /x45 + /x46 + /x47 + /x48 + /x49 + /x4a + /x4b + /x4c + /x4d + /x4e + /x4f + /x50 + /x51 + /x52 + /x53 + /x54 + /x55 + /x56 + /x57 + /x58 + /x59 + /x5a + /x5b + /x5c + /x5d + /x5e + /x5f + /x60 + /x61 + /x62 + /x63 + /x64 + /x65 + /x66 + /x67 + /x68 + /x69 + /x6a + /x6b + /x6c + /x6d + /x6e + /x6f + /x70 + /x71 + /x72 + /x73 + /x74 + /x75 + /x76 + /x77 + /x78 + /x79 + /x7a + /x7b + /x7c + /x7d + /x7e + /x7f +END CHARMAP diff --git a/core/libs/tests/numeric-locale/comma.locale b/core/libs/tests/numeric-locale/comma.locale new file mode 100644 index 0000000000..3207685c62 --- /dev/null +++ b/core/libs/tests/numeric-locale/comma.locale @@ -0,0 +1,5 @@ +LC_NUMERIC +decimal_point "" +thousands_sep "" +grouping -1 +END LC_NUMERIC diff --git a/core/libs/tests/test_public_headers.py b/core/libs/tests/test_public_headers.py new file mode 100644 index 0000000000..2511659765 --- /dev/null +++ b/core/libs/tests/test_public_headers.py @@ -0,0 +1,237 @@ +import argparse +import concurrent.futures +import importlib.util +import json +import re +import subprocess +import sys +import tempfile +from functools import cache +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[3] +LIBS = ROOT / "core/libs" +sys.dont_write_bytecode = True +SPEC = importlib.util.spec_from_file_location("nid_names", ROOT / "tools/nid_names.py") +NIDS = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(NIDS) +DECLARATION = re.compile(r"(?:\bAPS5_VABI\s+|^[ \t]*[A-Za-z_][\w:*&<> \t]*?[ \t]+)(\w+)\s*\([^;{}]*\)\s*(?:noexcept\s*)?;", re.M) +INCLUDE = re.compile(r'^\s*#\s*include\s*"([^"]+)"', re.M) +TYPE_TOKEN = re.compile(r"\bnamespace\s*([\w:]*)\s*\{|\bextern\s*\{|\b(?:struct|union|class)\s+(\w+)\s*([;{])|\busing\s+(\w+)\s*=|[{}]") + + +@cache +def code(text): + return NIDS.CPP_TOKEN.sub(lambda match: match.group() if match.group("code") else re.sub(r"[^\n]", " ", match.group()), text) + + +def declared_types(text): + scopes = [] + result = set() + for token in TYPE_TOKEN.finditer(text): + namespace, name, ending, alias = token.groups() + if namespace is not None: + scopes.append(namespace or None) + elif name or alias: + if None not in scopes: + result.add("::".join([scope for scope in scopes if scope] + [name or alias])) + if ending == "{": + scopes.append(name) + elif token.group().startswith("extern"): + scopes.append("") + elif token.group() == "{": + scopes.append(None) + elif scopes: + scopes.pop() + return result + + +@cache +def source_text(path): + return path.read_text(encoding="utf-8") + + +@cache +def included_headers(header): + result = [] + for name in INCLUDE.findall(source_text(header)): + candidates = {path.resolve() for path in (header.parent / name, LIBS / name) if path.is_file()} + if len(candidates) != 1: + raise RuntimeError(f"{header}: include {name} resolves to {len(candidates)} files") + result.append(candidates.pop()) + return result + + +def reachable(header, visited): + if header in visited: + return + visited.add(header) + for included in included_headers(header): + reachable(included, visited) + + +def exported_definitions(text): + text = text.replace("\\\n", "").replace("\\\r\n", "") + masked = code(text) + ranges = [] + for match in re.finditer(r"\bnamespace\s*\{", masked): + depth = 1 + end = match.end() + while depth and end < len(masked): + depth += (masked[end] == "{") - (masked[end] == "}") + end += 1 + ranges.append((match.start(), end)) + result = set() + position = 0 + parentheses = 0 + for match in NIDS.DEFINITION.finditer(masked): + prefix = masked[position:match.start()] + parentheses += prefix.count("(") - prefix.count(")") + position = match.start() + if parentheses: + continue + if any(start < match.start() < end for start, end in ranges): + continue + start = max(masked.rfind(delimiter, 0, match.start()) for delimiter in ";{}") + 1 + if re.search(r"\b(?:static|GCC_HIDDEN_FN)\b", masked[start:match.start()]): + continue + result.add(match.group(1)) + return result + + +def audit(): + umbrellas = sorted(path / f"{path.name}.hpp" for path in (LIBS / "prx").iterdir() if path.is_dir()) + if not umbrellas: + raise RuntimeError("No library umbrella headers found") + missing = [str(path.relative_to(ROOT)) for path in umbrellas if not path.is_file()] + if missing: + raise RuntimeError("Missing library umbrella headers: " + ", ".join(missing)) + exports = {} + for umbrella in umbrellas: + included = set() + reachable(umbrella.resolve(), included) + for source in NIDS.source_files(umbrella.parent): + if "tools" in source.relative_to(umbrella.parent).parts: + continue + text = source_text(source).replace("\\\n", "").replace("\\\r\n", "") + aliases = {name for nid, name in NIDS.source_aliases(text)} if "APS5_EXPORT" in text else set() + definitions = exported_definitions(text) + if any(macro in text for macro in NIDS.ASM_FUNCTION_MACROS): + definitions.update(NIDS.source_assembly_definitions(text)) + for name in definitions | aliases: + if name.endswith(("_nid_no_patch", "_nid_no_patch_cut")): + continue + exports.setdefault(name, []).append((source, included)) + declarations = {name: [] for name in exports} + for source in sorted((ROOT / "core").rglob("*")): + if source.suffix not in {".cpp", ".h", ".hpp"}: + continue + text = code(source_text(source)) + resolved = source.resolve() + for match in DECLARATION.finditer(text): + if match.group(1) not in declarations: + continue + prefix = match.group()[:match.start(1) - match.start()] + if re.search(r"\b(?:return|co_return|throw|else|case)\b|&&|\|\|", prefix): + continue + declarations[match.group(1)].append((resolved, text.count("\n", 0, match.start()) + 1)) + errors = [] + headers = set(umbrellas) + for name, definitions in exports.items(): + found = declarations[name] + if len(found) != 1 or found[0][0].suffix not in {".h", ".hpp"}: + locations = ", ".join(f"{path.relative_to(ROOT)}:{line}" for path, line in found) + errors.append(f"{name}: expected exactly one header declaration; found {len(found)}: {locations}") + continue + header = found[0][0] + headers.add(header) + for source, included in definitions: + if header not in included: + errors.append(f"{name}: {header.relative_to(ROOT)} is not included by {source.parent} library umbrella") + if errors: + raise RuntimeError("\n".join(errors)) + public_types = set() + for header in headers: + text = code(source_text(header)) + public_types.update(declared_types(text)) + for source in sorted((ROOT / "core").rglob("*.cpp")): + text = code(source_text(source)) + types = declared_types(text) + for name in sorted(public_types & types): + errors.append(f"{source.relative_to(ROOT)}: duplicate public type {name}; include its header") + if errors: + raise RuntimeError("\n".join(errors)) + return umbrellas, sorted(headers), len(exports) + + +def compile_headers(compiler, compiler_args, groups, workers=4): + macros = subprocess.run([compiler, *compiler_args, "-dM", "-E", "-x", "c++", "-"], input="", text=True, capture_output=True, check=True).stdout + warnings = ["-Werror"] + if re.search(r"^#define __clang__\b", macros, re.M): + warnings.append("-Wno-return-type-c-linkage") + + def compile_one(group): + includes = "".join(f'#include "{header.relative_to(LIBS).as_posix()}"\n' for header in group) + with tempfile.TemporaryDirectory() as directory: + dependencies = Path(directory) / "headers.d" + result = subprocess.run([compiler, *compiler_args, "-std=c++20", *warnings, "-fsyntax-only", "-MD", "-MF", str(dependencies), "-MT", "public_headers", "-x", "c++", f"-I{LIBS}", f"-I{ROOT / '3rdparty/freetype/include'}", "-"], input=includes * 2, text=True, capture_output=True) + if result.returncode: + raise RuntimeError(f"Header compilation failed: {group}\n{result.stdout}{result.stderr}") + text = dependencies.read_text(encoding="utf-8").replace("\\\n", "") + if not text.startswith("public_headers:"): + raise RuntimeError(f"Invalid compiler dependency output: {dependencies}") + names = re.findall(r"(?:\\[ \t]|[^\s])+", text.removeprefix("public_headers:")) + paths = [Path(re.sub(r"\\([ \t#\\])", r"\1", name).replace("$$", "$")).resolve() for name in names] + forbidden = [path for path in paths if path.is_relative_to((ROOT / "3rdparty").resolve())] + if forbidden: + raise RuntimeError(f"Public headers depend on 3rdparty: {group}\n" + "\n".join(map(str, forbidden))) + + errors = [] + with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool: + futures = [(group, pool.submit(compile_one, group)) for group in groups] + for group, future in futures: + try: + future.result() + except Exception as error: + errors.append(f"{group}: {error}") + if errors: + raise RuntimeError(f"{len(errors)} header compilation units failed:\n\n" + "\n\n".join(errors)) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--compiler") + parser.add_argument("--compiler-arg", action="append", default=[]) + parser.add_argument("--manifest", type=Path) + parser.add_argument("--audit-only", action="store_true") + parser.add_argument("--batch-index", type=int) + parser.add_argument("--batch-count", type=int, default=64) + args = parser.parse_args() + if args.batch_count < 1: + parser.error("--batch-count must be positive") + if args.audit_only and args.batch_index is not None: + parser.error("--audit-only cannot be combined with --batch-index") + if not args.audit_only and not args.compiler: + parser.error("--compiler is required for header compilation") + if args.batch_index is not None: + if not args.manifest or not 0 <= args.batch_index < args.batch_count: + parser.error("a batch requires --manifest and an index within --batch-count") + groups = [[LIBS / name for name in group] for group in json.loads(args.manifest.read_text(encoding="utf-8"))] + selected = groups[args.batch_index::args.batch_count] + if not selected: + raise RuntimeError("Header compilation batch is empty") + compile_headers(args.compiler, args.compiler_arg, selected, workers=1) + print(f"Batch {args.batch_index}: {len(selected)} header compilation units verified") + return + umbrellas, headers, count = audit() + groups = [[header] for header in headers] + [umbrellas] + if args.manifest: + args.manifest.write_text(json.dumps([[header.relative_to(LIBS).as_posix() for header in group] for group in groups]), encoding="utf-8") + print(f"{len(umbrellas)} libraries, {count} exports: unique declarations and umbrella coverage verified", flush=True) + if not args.audit_only: + compile_headers(args.compiler, args.compiler_arg, groups) + print("Standalone headers verified") + + +if __name__ == "__main__": + main() diff --git a/core/libs/tests/test_windows_timed_join_imports.py b/core/libs/tests/test_windows_timed_join_imports.py new file mode 100644 index 0000000000..4a5eeef09c --- /dev/null +++ b/core/libs/tests/test_windows_timed_join_imports.py @@ -0,0 +1,86 @@ +import os +import shutil +import struct +import subprocess +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'relinker' / 'relinker' / 'tests')) +from guest_import_fixtures import executable, fixture_directory + + +def call(code, slot): + code.extend(b'\xff\x15' + struct.pack(') set_tests_properties(overlapping_relocations PROPERTIES TIMEOUT 30) + add_test(NAME guest_own_tls_relocations COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_own_tls_relocations.py $) + set_tests_properties(guest_own_tls_relocations PROPERTIES TIMEOUT 30) add_test(NAME optional_plt COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_plt.py $) add_test(NAME optional_rela COMMAND ${Python3_EXECUTABLE} @@ -492,7 +558,7 @@ if(BUILD_TESTING) foreach(opcode IN ITEMS add or adc sbb and sub xor cmp) list(APPEND tlsCoverageGroups alu_${opcode}) endforeach() - list(APPEND tlsCoverageGroups gs_traps gs_traps_implicit) + list(APPEND tlsCoverageGroups gs_traps gs_traps_implicit gs_non_memory) foreach(group IN LISTS tlsCoverageGroups) add_test(NAME tls_function_coverage_${group} COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $ ${group}) @@ -508,6 +574,8 @@ if(BUILD_TESTING) set_tests_properties(macos_tls_alu PROPERTIES TIMEOUT 60) add_test(NAME linux_load_alignment COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_alignment.py $) + add_test(NAME linux_load_bounds COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_bounds.py $) add_test(NAME linux_eh_frame_header COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_eh_frame_header.py $) add_test(NAME linux_dynamic_strings COMMAND ${Python3_EXECUTABLE} @@ -520,6 +588,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_executable_exports.py $) add_test(NAME executable_tls_relocations COMMAND ${Python3_EXECUTABLE} ${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 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) @@ -560,22 +630,36 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_syscall_trap.py $) add_test(NAME guest_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_tls.py $) + add_test(NAME guest_tls_export_references COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_tls_export_references.py $) + set_tests_properties(guest_tls_export_references PROPERTIES TIMEOUT 30) add_test(NAME guest_needed_modules COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_needed_modules.py $) add_test(NAME guest_module_dir COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_dir.py $) add_test(NAME guest_module_directories COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_directories.py $) + add_test(NAME guest_trailing_note COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_trailing_note.py $) add_test(NAME relinker_executables COMMAND ${Python3_EXECUTABLE} ${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 $) + 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) + add_test(NAME patch_advisor_integration COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_patch_advisor.py $) + set_tests_properties(patch_advisor_integration PROPERTIES TIMEOUT 40) + endif() add_test(NAME sce_module_path COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_sce_module_path.py $) add_test(NAME linux_guest_stack COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_guest_stack.py $) add_test(NAME guest_symbol_names COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_symbol_names.py $) + add_test(NAME import_symbol_types COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_import_symbol_types.py $) add_test(NAME guest_registry COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_registry.py $) add_test(NAME needed_libraries COMMAND ${Python3_EXECUTABLE} @@ -595,6 +679,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $) add_test(NAME windows_named_imports COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_named_imports.py $) + add_test(NAME linux_named_imports COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_named_imports.py $) add_library(guest_module_args_host SHARED elfpatcher/tests/GuestModuleArgsHost.cpp) target_include_directories(guest_module_args_host PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../libs) set_target_properties(guest_module_args_host PROPERTIES @@ -613,6 +699,7 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_unused_import_dependencies.py $ $) set_property(TEST guest_unused_import_dependencies APPEND PROPERTY LABELS relinker) + add_asan_test_options(guest_unused_import_dependencies verify_asan_link_order=0) add_test(NAME guest_export_lookup COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_export_lookup.py $) set_tests_properties(guest_export_lookup PROPERTIES TIMEOUT 30) @@ -622,6 +709,7 @@ if(BUILD_TESTING) add_asan_test_options(guest_module_args verify_asan_link_order=0) add_asan_test_options(guest_deferred_dependencies verify_asan_link_order=0) add_asan_test_options(guest_module_fini verify_asan_link_order=0) + add_asan_test_options(guest_unused_import_dependencies verify_asan_link_order=0) add_test(NAME guest_needed_nested_modules COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_needed_nested_modules.py $) add_test(NAME guest_zero_initializer COMMAND ${Python3_EXECUTABLE} @@ -629,6 +717,10 @@ if(BUILD_TESTING) $ $) set_tests_properties(guest_zero_initializer PROPERTIES TIMEOUT 30) add_asan_test_options(guest_zero_initializer verify_asan_link_order=0) + add_test(NAME linux_guest_named_imports COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_guest_named_imports.py + $ $) + add_asan_test_options(linux_guest_named_imports verify_asan_link_order=0) set(windows_stdio_host_arguments) if(WIN32) if(MINGW) @@ -646,7 +738,7 @@ if(BUILD_TESTING) set(msvcrtImportLibrary "${CMAKE_CURRENT_BINARY_DIR}/windows_stdio_msvcrt_import.a") add_custom_command(OUTPUT "${msvcrtImportLibrary}" COMMAND "${mingwDlltoolExecutable}" -d "${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/tests/WindowsMsvcrt.def" - -l "${msvcrtImportLibrary}" -D msvcrt.dll + -l "${msvcrtImportLibrary}" -D msvcrt.dll --temp-prefix windows_stdio_msvcrt_import DEPENDS elfpatcher/tests/WindowsMsvcrt.def VERBATIM) add_custom_target(windows_stdio_msvcrt_import DEPENDS "${msvcrtImportLibrary}") @@ -666,6 +758,14 @@ if(BUILD_TESTING) add_test(NAME windows_import_modules COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_import_modules.py $ ${windows_stdio_host_arguments}) + if(WIN32) + foreach(kernelVariant IN ITEMS sys web) + add_test(NAME windows_kernel_variant_${kernelVariant} COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_kernel_variant_imports.py + $ libkernel_${kernelVariant}.prx) + set_tests_properties(windows_kernel_variant_${kernelVariant} PROPERTIES TIMEOUT 30) + endforeach() + endif() add_test(NAME elf_header COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_elf_header.py $) add_test(NAME weak_imports COMMAND ${Python3_EXECUTABLE} @@ -703,6 +803,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_call_registry.py $) add_test(NAME jmprel_table_bounds COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_jmprel_table_bounds.py $) + add_test(NAME copy_relocations COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_copy_relocations.py $) if(WIN32) add_test(NAME windows_diagnostic_fixture_isolation COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/tests/test_windows_diagnostic_fixture_isolation.py $) diff --git a/core/relinker/cli/include/Cli.hpp b/core/relinker/cli/include/Cli.hpp index cb379408f0..2625b44aac 100644 --- a/core/relinker/cli/include/Cli.hpp +++ b/core/relinker/cli/include/Cli.hpp @@ -16,6 +16,7 @@ struct Args { bool skipSceModule = false; bool toIntel = false; bool toRosetta = false; + bool frameRedZone = false; bool writeRegistry = false; bool toWindows = false; bool toMacos = false; diff --git a/core/relinker/cli/include/NativeModules.hpp b/core/relinker/cli/include/NativeModules.hpp index 66139266ca..862acfcf0c 100644 --- a/core/relinker/cli/include/NativeModules.hpp +++ b/core/relinker/cli/include/NativeModules.hpp @@ -1,11 +1,13 @@ #pragma once #include +#include namespace Cli { struct NativeModules { std::filesystem::path Root; + std::vector Directories; std::vector RuntimeDirectories; std::vector Modules; }; @@ -14,4 +16,6 @@ bool SameDirectory(const std::filesystem::path& first, const std::filesystem::pa NativeModules ReadModules(const std::filesystem::path& inputPath, const std::filesystem::path& sceModulePath, const std::set& excludedModules, const std::vector& moduleDirectories); +void ReportUnconvertedNeeded(const NativeModules& modules, const std::vector& neededLibraries, bool foldCase, std::ostream& out); + } diff --git a/core/relinker/cli/src/Autorun.cpp b/core/relinker/cli/src/Autorun.cpp index aa54150b47..e6e6defd69 100644 --- a/core/relinker/cli/src/Autorun.cpp +++ b/core/relinker/cli/src/Autorun.cpp @@ -16,11 +16,10 @@ namespace Cli { int Autorun(const std::string& absPath, bool toWindows) { if (!toWindows) { - std::filesystem::permissions(Io::NativePath(absPath), - std::filesystem::perms::owner_exec | - std::filesystem::perms::group_exec | - std::filesystem::perms::others_exec, - std::filesystem::perm_options::add); + const auto path = Io::NativePath(absPath); + constexpr auto executable = std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec; + if ((std::filesystem::status(path).permissions() & executable) != executable) + std::filesystem::permissions(path, executable, std::filesystem::perm_options::add); } int rawCode = 0; diff --git a/core/relinker/cli/src/CliArgs.cpp b/core/relinker/cli/src/CliArgs.cpp index 3fa915c6d2..6ab190a572 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] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] [--patches | --no-patches] \n" + 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" " relinker --verify-patches [--verify-output] [--skip-sce-module | --sce-module-path ] [--exclude-sce-module ]... [--module-dir ]... [--executable ]... \n" "Example: relinker input.elf output.elf"; } @@ -22,7 +22,7 @@ namespace { constexpr std::string_view kVerifyRejectedOptions[] = { "--windows", "--macos", "--windows-gui", "--windows-diagnostics", "--windows-fixed-base", "--trace-imports", "--linux-fixed-base", "--macos-fixed-base", - "--to-intel", "--to-rosetta", "--skip-syscall-check", "--trap-sdk-syscall-stubs", "unused-filter=", + "--to-intel", "--to-rosetta", "--frame-red-zone", "--skip-syscall-check", "--trap-sdk-syscall-stubs", "unused-filter=", "--rpath", "--registry", "--lazy-binding", "--autorun", "--patches", "--no-patches", }; @@ -105,6 +105,8 @@ Args ParseArgs(int argc, char* argv[]) { args.executables.push_back(argv[++i]); } else if (arg == "--to-intel") { args.toIntel = true; + } else if (arg == "--frame-red-zone") { + args.frameRedZone = true; } else if (arg == "--to-rosetta") { args.toRosetta = true; args.toIntel = true; diff --git a/core/relinker/cli/src/NativeModules.cpp b/core/relinker/cli/src/NativeModules.cpp index f01ac374d4..6163063c69 100644 --- a/core/relinker/cli/src/NativeModules.cpp +++ b/core/relinker/cli/src/NativeModules.cpp @@ -2,13 +2,59 @@ #include #include #include +#include #include +#include #include #include #include +#include +#include +#include +#include namespace Cli { +namespace { + +struct CandidateHeader { + bool Elf = false; + bool Self = false; + std::uint16_t Type = 0; +}; + +std::optional TryReadCandidateHeader(const std::filesystem::path& path) { + std::ifstream stream(path, std::ios::binary); + if (!stream) return std::nullopt; + char header[18]{}; + stream.read(header, sizeof header); + if (stream.bad()) return std::nullopt; + CandidateHeader result; + if (stream.gcount() < 4) return result; + const auto byte = [&](const std::size_t index) { return static_cast(header[index]); }; + result.Self = (byte(0) == 0x4f && byte(1) == 0x15 && byte(2) == 0x3d && byte(3) == 0x1d) || (byte(0) == 0x54 && byte(1) == 0x14 && byte(2) == 0xf5 && byte(3) == 0xee); + result.Elf = byte(0) == 0x7f && header[1] == 'E' && header[2] == 'L' && header[3] == 'F'; + if (result.Elf && stream.gcount() == sizeof header) result.Type = static_cast(byte(16) | (byte(17) << 8)); + return result; +} + +CandidateHeader ReadCandidateHeader(const std::filesystem::path& path) { + const auto header = TryReadCandidateHeader(path); + if (!header) throw Domain::RelinkerException("Cannot read guest candidate: " + Io::Utf8Path(path)); + return *header; +} + +std::string FoldedName(std::string name, const bool foldCase) { + if (foldCase) for (auto& character : name) if (character >= 'A' && character <= 'Z') character = static_cast(character + ('a' - 'A')); + return name; +} + +std::string_view ModuleStem(const std::string_view name) { + return name.substr(0, name.size() - Domain::ModuleSuffix(name).size()); +} + +} + bool SameDirectory(const std::filesystem::path& first, const std::filesystem::path& second) { if (std::filesystem::exists(first) && std::filesystem::exists(second)) return std::filesystem::equivalent(first, second); return std::filesystem::weakly_canonical(first) == std::filesystem::weakly_canonical(second); @@ -45,17 +91,6 @@ NativeModules ReadModules(const std::filesystem::path& inputPath, const std::fil } std::vector paths; std::set unmatchedExclusions = excludedModules; - const auto isElf = [](const std::filesystem::path& path, bool& self) { - std::ifstream stream(path, std::ios::binary); - if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + Io::Utf8Path(path)); - char magic[4]{}; - stream.read(magic, 4); - if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + Io::Utf8Path(path)); - if (stream.gcount() != 4) return false; - const auto byte = [&](const std::size_t index) { return static_cast(magic[index]); }; - self = (byte(0) == 0x4f && byte(1) == 0x15 && byte(2) == 0x3d && byte(3) == 0x1d) || (byte(0) == 0x54 && byte(1) == 0x14 && byte(2) == 0xf5 && byte(3) == 0xee); - return byte(0) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F'; - }; for (const auto& directory : directories) { if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + Io::Utf8Path(directory)); for (const auto& entry : std::filesystem::directory_iterator(directory)) { @@ -66,9 +101,9 @@ NativeModules ReadModules(const std::filesystem::path& inputPath, const std::fil } if (!entry.is_regular_file()) continue; if (Io::SameFile(entry.path(), inputPath)) continue; - bool self = false; - if (isElf(entry.path(), self)) paths.push_back(entry.path()); - else if (self) + const auto header = ReadCandidateHeader(entry.path()); + if (header.Elf) paths.push_back(entry.path()); + else if (header.Self) std::cout << "Skipping SELF container: " << entry.path().lexically_relative(root).generic_string() << " (unwrap it before relinking)\n"; } @@ -77,6 +112,7 @@ NativeModules ReadModules(const std::filesystem::path& inputPath, const std::fil std::sort(paths.begin(), paths.end()); NativeModules result; result.Root = root; + result.Directories = std::move(directories); result.RuntimeDirectories = std::move(runtimeDirectories); for (const auto& path : paths) result.Modules.push_back({path, Io::FileReader().Read(Io::Utf8Path(path))}); std::map sonames; @@ -107,4 +143,49 @@ NativeModules ReadModules(const std::filesystem::path& inputPath, const std::fil return result; } +void ReportUnconvertedNeeded(const NativeModules& modules, const std::vector& neededLibraries, const bool foldCase, std::ostream& out) { + std::map> stems; + for (const auto& name : neededLibraries) { + const auto folded = FoldedName(name, foldCase); + if (!Domain::ModuleSuffix(folded).empty()) stems[std::string(ModuleStem(folded))].push_back(name); + } + if (stems.empty()) return; + struct Found { + std::filesystem::path Relative; + std::string Name; + bool Self; + }; + std::vector found; + std::error_code error; + for (auto entry = std::filesystem::recursive_directory_iterator(modules.Root, std::filesystem::directory_options::skip_permission_denied, error); !error && entry != std::filesystem::recursive_directory_iterator(); entry.increment(error)) { + const bool directory = entry->is_directory(error); + if (error) { + error.clear(); + continue; + } + if (directory) { + if (std::any_of(modules.Directories.begin(), modules.Directories.end(), [&](const auto& scanned) { return std::filesystem::equivalent(scanned, entry->path(), error) && !error; })) entry.disable_recursion_pending(); + error.clear(); + continue; + } + const auto filename = FoldedName(Io::Utf8Path(entry->path().filename()), foldCase); + if (Domain::ModuleSuffix(filename).empty()) continue; + const auto stem = stems.find(std::string(ModuleStem(filename))); + if (stem == stems.end()) continue; + if (!entry->is_regular_file(error) || error) { + error.clear(); + continue; + } + const auto header = TryReadCandidateHeader(entry->path()); + if (!header || (!header->Self && !(header->Elf && header->Type == 0xfe18))) continue; + for (const auto& name : stem->second) found.push_back({entry->path().lexically_relative(modules.Root), name, header->Self}); + } + std::sort(found.begin(), found.end(), [](const auto& left, const auto& right) { return std::tie(left.Relative, left.Name) < std::tie(right.Relative, right.Name); }); + for (const auto& [relative, name, self] : found) { + const auto directory = relative.parent_path().generic_string(); + out << "Needed module not converted: " << name << " at " << relative.generic_string() << "; add --module-dir " << (directory.empty() ? "." : directory) + << (self ? " and unwrap the SELF container" : "") << '\n'; + } +} + } diff --git a/core/relinker/cli/tests/AutorunExitTests.cpp b/core/relinker/cli/tests/AutorunExitTests.cpp index c4883221a5..798fb129db 100644 --- a/core/relinker/cli/tests/AutorunExitTests.cpp +++ b/core/relinker/cli/tests/AutorunExitTests.cpp @@ -3,15 +3,38 @@ #include #include #include +#ifndef _WIN32 +#include +#endif #include +#include #include #include #include #include #include +#ifdef _WIN32 +#include +#endif + +namespace { + +std::filesystem::path ExecutablePath(const char* argument) { +#ifdef _WIN32 + static_cast(argument); + std::vector filename(32768); + const auto size = GetModuleFileNameW(nullptr, filename.data(), static_cast(filename.size())); + if (size == 0 || size >= filename.size()) throw std::runtime_error("Cannot locate the autorun test executable"); + return std::filesystem::path(filename.data()); +#else + return std::filesystem::absolute(argument); +#endif +} + +} int main(int, char* argv[]) { - const auto executable = std::filesystem::absolute(argv[0]); + const auto executable = ExecutablePath(argv[0]); const auto name = executable.stem().string(); if (name.starts_with("anyps5-autorun-child-")) { if (name.find("signal") != std::string::npos) std::raise(SIGILL); @@ -21,26 +44,43 @@ int main(int, char* argv[]) { std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + " with spaces" + executable.extension().string()); #ifndef _WIN32 const auto signaled = child.parent_path() / ("anyps5-autorun-child-signal-" + child.filename().string()); + const auto script = child.parent_path() / ("anyps5-autorun-script-" + child.filename().string()); + const auto changeTime = [](const std::filesystem::path& path) { + struct stat info {}; + if (stat(path.c_str(), &info) != 0) throw std::runtime_error("Cannot stat " + path.string()); +#ifdef __APPLE__ + return info.st_ctimespec.tv_sec * 1000000000LL + info.st_ctimespec.tv_nsec; +#else + return info.st_ctim.tv_sec * 1000000000LL + info.st_ctim.tv_nsec; +#endif + }; #endif std::istringstream input("\n\n\n"); auto* savedInput = std::cin.rdbuf(input.rdbuf()); int result = 0; + std::vector aliases; try { +#ifdef _WIN32 std::filesystem::copy_file(executable, child); +#else + std::filesystem::create_symlink(executable, child); + const auto changed = changeTime(executable); +#endif std::vector suffixes{""}; #ifndef _WIN32 suffixes.insert(suffixes.end(), {" $HOME", " $(printf substituted)", " `printf substituted`", " 'single'", " \"double\"", " \\backslash", " line\nbreak"}); #endif - const auto originalChild = child; for (const auto& suffix : suffixes) { - const auto renamed = std::filesystem::path(originalChild.string() + suffix); - if (renamed != child) std::filesystem::rename(child, renamed); - child = renamed; + const auto alias = std::filesystem::path(child.string() + suffix); + if (alias != child) { + std::filesystem::create_symlink(child, alias); + aliases.push_back(alias); + } for (const bool windows : {false, true}) { - const int code = Cli::Autorun(child.string(), windows); + const int code = Cli::Autorun(alias.string(), windows); if (code != 42) throw std::runtime_error("Autorun changed exit code 42 to " + - std::to_string(code) + " for " + child.string()); + std::to_string(code) + " for " + alias.string()); } } #ifndef _WIN32 @@ -66,18 +106,24 @@ int main(int, char* argv[]) { } if (std::signal(SIGCHLD, savedHandler) == SIG_ERR) throw std::runtime_error("Cannot restore SIGCHLD after the autorun failure test"); - std::filesystem::rename(child, signaled); + std::filesystem::create_symlink(child, signaled); const int code = Cli::Autorun(signaled.string(), false); if (code != 128 + SIGILL) throw std::runtime_error("Autorun lost the child signal"); + if (changeTime(executable) != changed) throw std::runtime_error("Autorun changed the mode of an output that was already executable"); + std::ofstream(script) << "#!/bin/sh\nexit 42\n"; + std::filesystem::permissions(script, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write); + if (Cli::Autorun(script.string(), false) != 42) throw std::runtime_error("Autorun did not make a non-executable output executable"); #endif } catch (const std::exception& error) { std::cerr << error.what() << '\n'; result = 1; } std::cin.rdbuf(savedInput); + for (const auto& alias : aliases) std::filesystem::remove(alias); std::filesystem::remove(child); #ifndef _WIN32 std::filesystem::remove(signaled); + std::filesystem::remove(script); #endif return result; } diff --git a/core/relinker/cli/tests/CliArgsTests.cpp b/core/relinker/cli/tests/CliArgsTests.cpp index 8f18c4c963..3cd3069dc1 100644 --- a/core/relinker/cli/tests/CliArgsTests.cpp +++ b/core/relinker/cli/tests/CliArgsTests.cpp @@ -322,7 +322,7 @@ void verifyMode() { expectThrows({"--verify-patches", "p.toml", "--verify-output", "out.elf", "--exclude-sce-module", "x.prx"}, noEffect); expectThrows({"--verify-patches", "p.toml", "--verify-output", "out.elf", "--skip-sce-module"}, noEffect); expectThrows({"--verify-patches", "p.toml", "in.elf", "extra"}, "unexpected argument: extra"); - for (const auto* option : {"--windows", "--macos", "--to-intel", "--to-rosetta", "--trace-imports", "--lazy-binding", "--autorun", "--registry", "--skip-syscall-check", "--no-patches"}) + for (const auto* option : {"--windows", "--macos", "--to-intel", "--to-rosetta", "--frame-red-zone", "--trace-imports", "--lazy-binding", "--autorun", "--registry", "--skip-syscall-check", "--no-patches"}) expectThrows({"--verify-patches", "p.toml", option, "in.elf"}, std::string(option) + " cannot be used with --verify-patches"); expectThrows({"--verify-patches", "p.toml", "--rpath", "x", "in.elf"}, "--rpath cannot be used with --verify-patches"); expectThrows({"--verify-patches", "p.toml", "--patches", "q.toml", "in.elf"}, "--patches cannot be used with --verify-patches"); diff --git a/core/relinker/codegen/include/codegen/CodegenTypes.hpp b/core/relinker/codegen/include/codegen/CodegenTypes.hpp index 021f2bf059..9b116f3657 100644 --- a/core/relinker/codegen/include/codegen/CodegenTypes.hpp +++ b/core/relinker/codegen/include/codegen/CodegenTypes.hpp @@ -76,6 +76,7 @@ struct DecodedInstructionInfo { bool IsTwoByteOpcode; std::uint8_t Opcode; std::uint8_t ModRmRegField; + std::size_t ModRmOffset = 0; }; } diff --git a/core/relinker/codegen/include/codegen/x86/Amd64OnlySubstitutionTable.hpp b/core/relinker/codegen/include/codegen/x86/Amd64OnlySubstitutionTable.hpp index c3df3388cc..8f71e35555 100644 --- a/core/relinker/codegen/include/codegen/x86/Amd64OnlySubstitutionTable.hpp +++ b/core/relinker/codegen/include/codegen/x86/Amd64OnlySubstitutionTable.hpp @@ -42,6 +42,7 @@ inline constexpr Entry kRdseed = {"RDSEED", nullptr, 0}; inline constexpr Entry kRdpid = {"RDPID", nullptr, 0}; inline constexpr Entry kRdtscp = {"RDTSCP", nullptr, 0}; inline constexpr Entry kClwb = {"CLWB", nullptr, 0}; +inline constexpr Entry kXsaveopt = {"XSAVEOPT", nullptr, 0}; inline constexpr Entry kWideStore = {"256-bit store", nullptr, 0}; inline constexpr Entry kScalarCompare = {"scalar compare", nullptr, 0}; inline constexpr Entry kExtrq = {"EXTRQ", nullptr, 0}; diff --git a/core/relinker/codegen/include/codegen/x86/DecodedInstruction.hpp b/core/relinker/codegen/include/codegen/x86/DecodedInstruction.hpp index 4176cc4cff..d1c86aff41 100644 --- a/core/relinker/codegen/include/codegen/x86/DecodedInstruction.hpp +++ b/core/relinker/codegen/include/codegen/x86/DecodedInstruction.hpp @@ -27,6 +27,7 @@ struct DecodedInstruction { [[nodiscard]] bool IsRdpid() const; [[nodiscard]] bool IsRdtscp() const; [[nodiscard]] bool IsClwb() const; + [[nodiscard]] bool IsXsaveopt() const; private: [[nodiscard]] std::size_t _skipPrefixesAndRex(bool* outHasOperandSizePrefix, bool* outHasRepnePrefix, bool* outHasRepPrefix) const; diff --git a/core/relinker/codegen/include/codegen/x86/IAmd64OnlyInstructionMatcher.hpp b/core/relinker/codegen/include/codegen/x86/IAmd64OnlyInstructionMatcher.hpp index 9986e79286..998c321b29 100644 --- a/core/relinker/codegen/include/codegen/x86/IAmd64OnlyInstructionMatcher.hpp +++ b/core/relinker/codegen/include/codegen/x86/IAmd64OnlyInstructionMatcher.hpp @@ -27,7 +27,8 @@ public: virtual void EmitOutOfLine( StubBodyBuilder& body, - std::span instruction + std::span instruction, + bool allowInPlace = false ) const = 0; }; diff --git a/core/relinker/codegen/include/codegen/x86/MaskedStoreLowering.hpp b/core/relinker/codegen/include/codegen/x86/MaskedStoreLowering.hpp new file mode 100644 index 0000000000..d03957a542 --- /dev/null +++ b/core/relinker/codegen/include/codegen/x86/MaskedStoreLowering.hpp @@ -0,0 +1,27 @@ +#ifndef CODEGEN_X86_MASKEDSTORELOWERING_HPP +#define CODEGEN_X86_MASKEDSTORELOWERING_HPP + +#include +#include +#include +#include +#include + +namespace Codegen { + +struct MaskedStoreOperands { + const char* Name; + std::uint8_t ElementSize; + bool Wide; + std::uint8_t Mask; + MemoryOperand Memory; + std::vector Instruction; + std::size_t DisplacementOffset; +}; + +[[nodiscard]] std::optional DecodeMaskedStore(const std::uint8_t* data, std::size_t length); +void EmitMaskedStore(StubBodyBuilder& body, const MaskedStoreOperands& operands); + +} + +#endif diff --git a/core/relinker/codegen/include/codegen/x86/RedZoneFramer.hpp b/core/relinker/codegen/include/codegen/x86/RedZoneFramer.hpp new file mode 100644 index 0000000000..a23fa23206 --- /dev/null +++ b/core/relinker/codegen/include/codegen/x86/RedZoneFramer.hpp @@ -0,0 +1,47 @@ +#ifndef CODEGEN_X86_REDZONEFRAMER_HPP +#define CODEGEN_X86_REDZONEFRAMER_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace Codegen { + +struct RedZoneFunction { + std::uint64_t Begin = 0; + std::uint64_t End = 0; +}; + +struct RedZoneFrameReport { + std::size_t Functions = 0; + std::size_t RedZoneFunctions = 0; + std::size_t Framed = 0; + std::size_t FramedWithoutFramePointer = 0; + std::size_t Exits = 0; + std::size_t MovedOperands = 0; + std::size_t OperandStubs = 0; + std::size_t StubbedOperands = 0; + std::map Skipped; +}; + +struct RedZoneDisplacement { + Domain::FileByteOffset Offset; + std::vector Bytes; +}; + +std::vector FrameRedZoneFunctions( + const std::vector& fileBytes, + const std::vector& codeSegments, + const std::vector& functions, + const std::vector& existing, + RedZoneFrameReport& report, + std::vector& displacements +); + +} + +#endif diff --git a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp index c6db778a9e..e2c581eb24 100644 --- a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp +++ b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp @@ -43,6 +43,7 @@ inline constexpr std::uint8_t OneByteArpl = 0x62; inline constexpr std::uint8_t OneByteMovsxd = 0x63; inline constexpr std::uint8_t OneByteModRmRangeIMin = 0x84; inline constexpr std::uint8_t OneByteModRmRangeIMax = 0x8F; +inline constexpr std::uint8_t OneByteLea = 0x8D; inline constexpr std::uint8_t OneByteGrp2Rm8Imm8 = 0xC0; inline constexpr std::uint8_t OneByteGrp2RmImm8 = 0xC1; inline constexpr std::uint8_t OneByteVex3 = 0xC4; diff --git a/core/relinker/codegen/src/Amd64OnlyConverter.cpp b/core/relinker/codegen/src/Amd64OnlyConverter.cpp index 8f312031fe..de510c4224 100644 --- a/core/relinker/codegen/src/Amd64OnlyConverter.cpp +++ b/core/relinker/codegen/src/Amd64OnlyConverter.cpp @@ -56,6 +56,7 @@ public: ) : _seg(seg), _matches(matches), _ph(ph), _branchTargets(branchTargets), _pending(pending), _claimed(claimed), _matcher(matcher) {} [[nodiscard]] std::optional Window(const std::size_t index) const { + std::optional fallback; for (const bool avoidSegment : {true, false}) { if (index + 1 < _matches.size() && (_pending.contains(index + 1) || _movable(index + 1, true, avoidSegment))) { if (auto placement = _relocate(index, index, index + 1)) @@ -65,8 +66,10 @@ public: if (auto placement = _relocate(index, index - 1, index)) return placement; } + if (!fallback && index + 1 < _matches.size() && _pending.contains(index + 1)) + fallback = _relocate(index, index, index + 1, true); } - return std::nullopt; + return fallback; } [[nodiscard]] std::optional Island(const std::size_t index) { @@ -130,7 +133,7 @@ private: }; } - [[nodiscard]] std::optional _relocate(const std::size_t index, const std::size_t first, const std::size_t last) const { + [[nodiscard]] std::optional _relocate(const std::size_t index, const std::size_t first, const std::size_t last, const bool allowInPlace = false) const { if (_entered(_address(first), _end(last))) return std::nullopt; StubBodyBuilder body; @@ -138,7 +141,7 @@ private: try { for (auto k = first; k <= last; ++k) { if (_pending.contains(k)) { - _matcher.EmitOutOfLine(body, _bytes(k)); + _matcher.EmitOutOfLine(body, _bytes(k), allowInPlace); if (k != index) placement.Consumed.push_back(k); } else { diff --git a/core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp b/core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp index 739a3aea7f..ecbe487147 100644 --- a/core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp +++ b/core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -172,7 +173,8 @@ public: void EmitOutOfLine( StubBodyBuilder& body, - std::span instruction + std::span instruction, + bool allowInPlace = false ) const override; private: @@ -199,6 +201,8 @@ private: Amd64OnlyMatch Amd64OnlyInstructionMatcher::_matchMovnts(const DecodedInstruction& instr, const Entry& entry) const { using namespace X64OpcodeConstants; const auto opcodeOffset = instr.OpcodeOffset(); + if (std::find(instr.Data, instr.Data + opcodeOffset, PrefixLock) != instr.Data + opcodeOffset) + throw CodegenException("MOVNTSS/MOVNTSD with a LOCK prefix"); if (opcodeOffset + 2 >= instr.Length) throw CodegenException("MOVNTSS/MOVNTSD truncated before its ModRM byte"); const auto modrm = instr.Data[opcodeOffset + 2]; @@ -258,6 +262,11 @@ std::optional Amd64OnlyInstructionMatcher::_matchRosetta(const D } if (instr.IsClwb()) return _inPlace(kClwb, instr.Length, {}); + if (instr.IsXsaveopt()) { + std::vector replacement(instr.Data, instr.Data + instr.Length); + replacement[instr.OpcodeOffset() + 2] &= static_cast(~(2u << X64OpcodeConstants::ModRmRegShift)); + return Amd64OnlyMatch{kXsaveopt.Name, instr.Length, Amd64OnlyLowering::InPlace, std::move(replacement), {}, 0}; + } if (instr.IsRdpid()) return _trampoline(kRdpid.Name, instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { body.Raw(_rdpidReplacement(instr)); }), true); if (instr.IsRdtscp()) @@ -266,6 +275,8 @@ std::optional Amd64OnlyInstructionMatcher::_matchRosetta(const D return _trampoline(kWideStore.Name, instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { _wideStoreLowering.EmitOutOfLine(body, *wide); }), false, true); if (const auto compare = DecodeScalarCompare(instr.Data, instr.Length)) return _trampoline(kScalarCompare.Name, instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { _emitScalarCompare(body, instr, *compare); }), true); + if (const auto masked = DecodeMaskedStore(instr.Data, instr.Length)) + return _trampoline(masked->Name, instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { EmitMaskedStore(body, *masked); })); return std::nullopt; } @@ -325,6 +336,10 @@ const char* Amd64OnlyInstructionMatcher::_emitOutOfLine(StubBodyBuilder& body, c _emitScalarCompare(body, instr, *compare); return kScalarCompare.Name; } + if (const auto masked = _target == Amd64OnlyTarget::Rosetta ? DecodeMaskedStore(instr.Data, instr.Length) : std::nullopt) { + EmitMaskedStore(body, *masked); + return masked->Name; + } return nullptr; } @@ -355,8 +370,12 @@ const char* Amd64OnlyInstructionMatcher::_emitInPlace(StubBodyBuilder& body, con return nullptr; } -void Amd64OnlyInstructionMatcher::EmitOutOfLine(StubBodyBuilder& body, const std::span instruction) const { - if (_emitOutOfLine(body, DecodedInstruction{instruction.data(), instruction.size()}) == nullptr) +void Amd64OnlyInstructionMatcher::EmitOutOfLine(StubBodyBuilder& body, const std::span instruction, const bool allowInPlace) const { + const DecodedInstruction instr{instruction.data(), instruction.size()}; + const auto* emitted = _emitOutOfLine(body, instr); + if (emitted == nullptr && allowInPlace) + emitted = _emitInPlace(body, instr); + if (emitted == nullptr) throw CodegenException("AMD-only instruction has no out-of-line lowering"); body.Advance(instruction.size()); } diff --git a/core/relinker/codegen/src/x86/DecodedInstruction.cpp b/core/relinker/codegen/src/x86/DecodedInstruction.cpp index d54ce541bc..fa32c87812 100644 --- a/core/relinker/codegen/src/x86/DecodedInstruction.cpp +++ b/core/relinker/codegen/src/x86/DecodedInstruction.cpp @@ -324,6 +324,21 @@ bool DecodedInstruction::IsClwb() const { ((modrm >> ModRmModShift) & ModRmModMask) != ModRmModRegister && ((modrm >> ModRmRegShift) & ModRmRegMask) == 6; } +bool DecodedInstruction::IsXsaveopt() const { + bool operandSizeOverride = false; + bool repnePrefix = false; + bool repPrefix = false; + const std::size_t pos = _skipPrefixesAndRex(&operandSizeOverride, &repnePrefix, &repPrefix); + + if (operandSizeOverride || repnePrefix || repPrefix || pos + 2 >= Length) { + return false; + } + + const auto modrm = Data[pos + 2]; + return Data[pos] == TwoByteOpcodeEscape && Data[pos + 1] == TwoByteGrp15 && + ((modrm >> ModRmModShift) & ModRmModMask) != ModRmModRegister && ((modrm >> ModRmRegShift) & ModRmRegMask) == 6; +} + bool DecodedInstruction::IsMovntss() const { bool operandSizeOverride = false; bool repnePrefix = false; diff --git a/core/relinker/codegen/src/x86/MaskedStoreLowering.cpp b/core/relinker/codegen/src/x86/MaskedStoreLowering.cpp new file mode 100644 index 0000000000..642cee81ff --- /dev/null +++ b/core/relinker/codegen/src/x86/MaskedStoreLowering.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include + +namespace Codegen { + +namespace { + +constexpr std::int32_t kFrameSize = 128 + 3 * 8; +constexpr std::uint8_t kAddress = 0; +constexpr std::uint8_t kVexMap0F38 = 0x02; +constexpr std::uint8_t kVexPp66 = 0x01; + +bool _prefix(std::uint8_t value) { + return value == 0x2E || value == 0x36 || value == 0x3E || value == 0x26 || value == 0x64 || value == 0x65 || value == 0x67; +} + +void _append32(std::vector& bytes, std::int32_t value) { + for (unsigned shift = 0; shift < 32; shift += 8) + bytes.push_back(static_cast(static_cast(value) >> shift)); +} + +void _address(StubBodyBuilder& body, const MemoryOperand& memory) { + auto displacement = static_cast(memory.Displacement); + if (memory.StackBase) displacement += kFrameSize; + const bool address32 = std::find(memory.Prefixes.begin(), memory.Prefixes.end(), 0x67) != memory.Prefixes.end(); + if (!address32 && displacement > std::numeric_limits::max()) + throw CodegenException("Masked store stack displacement does not fit after spilling"); + const auto mod = memory.StackBase ? 2 : memory.Mod; + auto bytes = memory.Prefixes; + bytes.push_back(static_cast(0x48 | memory.RexIndexBase)); + bytes.push_back(0x8D); + bytes.push_back(static_cast((mod << 6) | (kAddress << 3) | memory.Rm)); + if (memory.Rm == 4) bytes.push_back(memory.Sib); + const auto offset = bytes.size(); + if (mod == 1) bytes.push_back(static_cast(displacement)); + if (mod == 2 || (mod == 0 && (memory.RipRelative || (memory.Rm == 4 && (memory.Sib & 7) == 5)))) + _append32(bytes, static_cast(displacement)); + if (memory.RipRelative) + body.RipRelative(bytes, offset, static_cast(memory.InstructionLength) + memory.Displacement); + else + body.Raw(bytes); +} + +} + +std::optional DecodeMaskedStore(const std::uint8_t* data, const std::size_t length) { + std::size_t vex = 0; + while (vex < length && _prefix(data[vex])) ++vex; + if (vex + 5 > length || data[vex] != 0xC4 || (data[vex + 1] & 0x1F) != kVexMap0F38 || (data[vex + 2] & 3) != kVexPp66 || (data[vex + 4] >> 6) == 3) + return std::nullopt; + const bool w = (data[vex + 2] & 0x80) != 0; + const char* name = nullptr; + std::uint8_t elementSize = 4; + if (data[vex + 3] == 0x2E && !w) { + name = "VMASKMOVPS"; + } else if (data[vex + 3] == 0x2F && !w) { + name = "VMASKMOVPD"; + elementSize = 8; + } else if (data[vex + 3] == 0x8E) { + name = w ? "VPMASKMOVQ" : "VPMASKMOVD"; + elementSize = w ? 8 : 4; + } else { + return std::nullopt; + } + const auto extensions = static_cast(~data[vex + 1]); + const auto rex = static_cast(((extensions & 0x20) >> 5) | ((extensions & 0x40) >> 5)); + auto memory = DecodeMemoryOperand(data, length, vex + 4, rex, {data, data + vex}); + if (vex + 4 + memory.EncodedSize != length) + throw CodegenException("Masked store memory operand has trailing bytes"); + const auto displacementOffset = vex + 5 + (memory.Rm == 4 ? 1 : 0); + return MaskedStoreOperands{name, elementSize, (data[vex + 2] & 4) != 0, static_cast((~data[vex + 2] >> 3) & 15), + std::move(memory), std::vector(data, data + length), displacementOffset}; +} + +void EmitMaskedStore(StubBodyBuilder& body, const MaskedStoreOperands& operands) { + const auto& memory = operands.Memory; + body.Raw(std::array{0x48, 0x8D, 0x64, 0x24, 0x80, 0x9C, 0x50, 0x51}); + _address(body, memory); + body.Raw(std::array{0xC4, static_cast(operands.Mask >= 8 ? 0xC1 : 0xE1), + static_cast(0x78 | (operands.Wide ? 4 : 0) | (operands.ElementSize == 8 ? 1 : 0)), 0x50, + static_cast(0xC8 | (operands.Mask & 7))}); + const auto elements = (operands.Wide ? 32u : 16u) / operands.ElementSize; + for (unsigned element = 0; element < elements; ++element) { + std::vector probes; + for (const auto offset : {element * operands.ElementSize, element * operands.ElementSize + operands.ElementSize - 1u}) { + probes.insert(probes.end(), memory.Prefixes.begin(), memory.Prefixes.end()); + probes.insert(probes.end(), {0xF0, 0x80, 0x48, static_cast(offset), 0x00}); + } + body.Raw(std::array{0xF6, 0xC1, static_cast(1u << element), 0x74, static_cast(probes.size())}); + body.Raw(probes); + } + body.Raw(std::array{0x59, 0x58, 0x9D, 0x48, 0x8D, 0xA4, 0x24, 0x80, 0x00, 0x00, 0x00}); + if (memory.RipRelative) + body.RipRelative(operands.Instruction, operands.DisplacementOffset, static_cast(memory.InstructionLength) + memory.Displacement); + else + body.Raw(operands.Instruction); +} + +} diff --git a/core/relinker/codegen/src/x86/RedZoneFramer.cpp b/core/relinker/codegen/src/x86/RedZoneFramer.cpp new file mode 100644 index 0000000000..cfe714faef --- /dev/null +++ b/core/relinker/codegen/src/x86/RedZoneFramer.cpp @@ -0,0 +1,441 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Codegen { + +namespace { + +constexpr std::uint8_t kRegRsp = 4; +constexpr std::uint8_t kRegRbp = 5; +constexpr std::size_t kJumpBytes = 5; +constexpr std::int64_t kRedZoneBytes = 0x80; +constexpr std::int64_t kStackAlignment = 16; +constexpr std::uint8_t kJumpBack[] = {0xe9, 0x00, 0x00, 0x00, 0x00}; + +std::vector lowerRsp(const std::int64_t shift) { + return {0x48, 0x8d, 0x64, 0x24, static_cast(-shift)}; +} + +std::vector raiseRsp(const std::int64_t shift) { + const auto value = static_cast(shift); + return {0x48, 0x8d, 0xa4, 0x24, static_cast(value), static_cast(value >> 8), static_cast(value >> 16), static_cast(value >> 24)}; +} + +struct Insn { + std::size_t Offset; + std::size_t Length; + DecodedInstructionInfo Info; +}; + +std::optional pushedRegister(const std::uint8_t* code, const Insn& insn) { + if (insn.Length == 1 && code[0] >= 0x50 && code[0] <= 0x57 && code[0] != 0x50 + kRegRsp) return static_cast(code[0] - 0x50); + if (insn.Length == 2 && code[0] == 0x41 && code[1] >= 0x50 && code[1] <= 0x57) return static_cast(code[1] - 0x50 + 8); + return std::nullopt; +} + +std::optional poppedRegister(const std::uint8_t* code, const Insn& insn) { + if (insn.Length == 1 && code[0] >= 0x58 && code[0] <= 0x5f && code[0] != 0x58 + kRegRsp) return static_cast(code[0] - 0x58); + if (insn.Length == 2 && code[0] == 0x41 && code[1] >= 0x58 && code[1] <= 0x5f) return static_cast(code[1] - 0x58 + 8); + return std::nullopt; +} + +std::optional rspAdjustment(const std::uint8_t* code, const Insn& insn) { + std::int32_t value = 0; + if (insn.Length == 4 && code[0] == 0x48 && code[1] == 0x83 && (code[2] == 0xec || code[2] == 0xc4)) { + const std::int64_t amount = static_cast(code[3]); + return code[2] == 0xec ? -amount : amount; + } + if (insn.Length == 7 && code[0] == 0x48 && code[1] == 0x81 && (code[2] == 0xec || code[2] == 0xc4)) { + std::memcpy(&value, code + 3, 4); + return code[2] == 0xec ? -std::int64_t{value} : std::int64_t{value}; + } + if (insn.Length == 5 && std::memcmp(code, "\x48\x8d\x64\x24", 4) == 0) return static_cast(code[4]); + if (insn.Length == 8 && std::memcmp(code, "\x48\x8d\xa4\x24", 4) == 0) { + std::memcpy(&value, code + 4, 4); + return value; + } + return std::nullopt; +} + +struct MemoryUse { + bool UsesRsp = false; + bool RegisterRsp = false; + bool RbpBased = false; + std::int64_t RbpDisp = 0; + bool RspBased = false; + std::int64_t RspDisp = 0; + std::uint8_t Mod = 0; + std::size_t DispOffset = 0; + bool Indexed = false; + bool Address = false; +}; + +void extensionBits(const std::uint8_t* code, const Insn& insn, bool& r, bool& x, bool& b) { + const auto& info = insn.Info; + r = x = b = false; + if (info.RexPrefix != 0) { + r = (info.RexPrefix & 4) != 0; + x = (info.RexPrefix & 2) != 0; + b = (info.RexPrefix & 1) != 0; + return; + } + const auto lead = code[info.OpcodeOffset]; + if ((lead == 0xc4 || lead == 0x8f || lead == 0x62) && info.OpcodeOffset + 1 < insn.Length) { + r = (code[info.OpcodeOffset + 1] & 0x80) == 0; + x = (code[info.OpcodeOffset + 1] & 0x40) == 0; + b = (code[info.OpcodeOffset + 1] & 0x20) == 0; + } else if (lead == 0xc5 && info.OpcodeOffset + 1 < insn.Length) { + r = (code[info.OpcodeOffset + 1] & 0x80) == 0; + } +} + +bool twoByteRegisterIsGeneral(const std::uint8_t op) { + return (op >= 0x40 && op <= 0x4f) || op == 0xa3 || op == 0xa4 || op == 0xa5 || op == 0xab || op == 0xac || op == 0xad || op == 0xaf || op == 0xb0 || op == 0xb1 || op == 0xb3 || op == 0xb6 || op == 0xb7 || op == 0xb8 || (op >= 0xbb && op <= 0xbf) || op == 0xc0 || op == 0xc1 || op == 0xc3; +} + +bool twoByteRmIsGeneral(const std::uint8_t op) { + return twoByteRegisterIsGeneral(op) || op == 0x6e || op == 0x7e || op == 0xba; +} + +bool vexGeneralUsesRsp(const std::uint8_t* code, const Insn& insn, const std::uint8_t mod, const std::uint8_t reg, const std::uint8_t rm, const bool r, const bool b) { + const auto at = insn.Info.OpcodeOffset; + const auto lead = code[at]; + if ((lead != 0xc4 && lead != 0xc5) || at + 3 >= insn.Length) return false; + const auto map = lead == 0xc5 ? std::uint8_t{1} : static_cast(code[at + 1] & 0x1f); + const auto vvvv = static_cast((~code[lead == 0xc5 ? at + 1 : at + 2] >> 3) & 0xf); + const auto op = code[lead == 0xc5 ? at + 2 : at + 3]; + const bool general = (map == 2 && (op == 0xf2 || op == 0xf3 || op == 0xf5 || op == 0xf6 || op == 0xf7)) || (map == 3 && op == 0xf0); + return general && ((reg == kRegRsp && !r) || (mod == 3 && rm == kRegRsp && !b) || vvvv == kRegRsp); +} + +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; + bool r = false; + bool x = false; + bool b = false; + extensionBits(code, insn, r, x, b); + const auto modrm = info.ModRmByte; + const auto mod = static_cast(modrm >> 6); + const auto reg = static_cast((modrm >> 3) & 7); + const auto rm = static_cast(modrm & 7); + const bool vex = info.RexPrefix == 0 && (code[info.OpcodeOffset] == 0xc4 || code[info.OpcodeOffset] == 0xc5 || code[info.OpcodeOffset] == 0x62 || code[info.OpcodeOffset] == 0x8f); + if (!vex && !info.IsTwoByteOpcode && reg == kRegRsp && !r) { + const auto op = info.Opcode; + if ((op <= 0x3f && (op & 7) <= 3) || (op >= 0x84 && op <= 0x8b) || op == 0x8d) use.UsesRsp = use.RegisterRsp = true; + } + if (!vex && info.IsTwoByteOpcode && ((reg == kRegRsp && !r && twoByteRegisterIsGeneral(info.Opcode)) || (mod == 3 && rm == kRegRsp && !b && twoByteRmIsGeneral(info.Opcode)))) use.UsesRsp = use.RegisterRsp = true; + if (vex && vexGeneralUsesRsp(code, insn, mod, reg, rm, r, b)) use.UsesRsp = use.RegisterRsp = true; + if (mod == 3) { + const bool generalPurpose = !vex && !info.IsTwoByteOpcode && !(info.Opcode >= 0xd8 && info.Opcode <= 0xdf); + if (generalPurpose && rm == kRegRsp && !b) use.UsesRsp = use.RegisterRsp = true; + return use; + } + std::size_t at = info.ModRmOffset + 1; + std::uint8_t base = rm; + bool baseExtended = b; + bool indexed = false; + if (rm == 4) { + if (at >= insn.Length) return use; + const auto sib = code[at++]; + base = sib & 7; + indexed = ((sib >> 3) & 7) != 4 || x; + if (mod == 0 && base == 5) return use; + } else if (mod == 0 && rm == 5) { + return use; + } + std::int64_t disp = 0; + if (mod == 1 && at < insn.Length) disp = static_cast(code[at]); + if (mod == 2 && at + 4 <= insn.Length) { + std::int32_t value = 0; + std::memcpy(&value, code + at, 4); + disp = value; + } + if (base == kRegRsp && !baseExtended) { + use.UsesRsp = true; + use.RspBased = true; + use.RspDisp = disp; + use.Mod = mod; + use.DispOffset = at; + use.Indexed = indexed; + use.Address = !vex && !info.IsTwoByteOpcode && info.Opcode == 0x8d; + } + if (base == kRegRbp && !baseExtended && mod != 0) { + use.RbpBased = true; + use.RbpDisp = disp; + } + return use; +} + +std::optional> movedDisplacement(const MemoryUse& use, const std::int64_t shift) { + const auto moved = use.RspDisp + shift; + if (use.Mod == 1 && moved <= std::numeric_limits::max()) return std::vector{static_cast(moved)}; + if (use.Mod == 2 && moved <= std::numeric_limits::max()) { + const auto value = static_cast(static_cast(moved)); + return std::vector{static_cast(value), static_cast(value >> 8), static_cast(value >> 16), static_cast(value >> 24)}; + } + return std::nullopt; +} + +std::optional> withDisplacement(const std::uint8_t* code, const Insn& insn, const MemoryUse& use, const std::int64_t disp) { + if (disp < std::numeric_limits::min() || disp > std::numeric_limits::max()) return std::nullopt; + const std::size_t oldBytes = use.Mod == 1 ? 1 : use.Mod == 2 ? 4 : 0; + if (use.DispOffset + oldBytes > insn.Length) return std::nullopt; + const bool narrow = disp >= std::numeric_limits::min() && disp <= std::numeric_limits::max(); + std::vector bytes(code, code + use.DispOffset); + bytes[insn.Info.ModRmOffset] = static_cast((bytes[insn.Info.ModRmOffset] & 0x3f) | (narrow ? 0x40 : 0x80)); + const auto value = static_cast(static_cast(disp)); + for (std::size_t index = 0; index < (narrow ? 1u : 4u); ++index) bytes.push_back(static_cast(value >> (8 * index))); + bytes.insert(bytes.end(), code + use.DispOffset + oldBytes, code + insn.Length); + return bytes; +} + +struct OperandStub { + std::size_t First; + std::size_t Last; + std::size_t Begin; + std::size_t End; + std::vector Body; + std::size_t Operands = 0; +}; + +void frameSegment( + const std::vector& fileBytes, + const Domain::ProgramHeader& ph, + const std::vector& functions, + const std::vector& existing, + std::vector& sites, + std::vector& displacements, + RedZoneFrameReport& report +) { + const auto segOffset = static_cast(ph.Offset); + const auto segSize = static_cast(ph.FileSize); + if (segOffset > fileBytes.size() || segSize > fileBytes.size() - segOffset) return; + const std::vector seg(fileBytes.begin() + static_cast(segOffset), fileBytes.begin() + static_cast(segOffset + segSize)); + const auto scanner = MakeInstructionScanner(); + const X64InstructionDecoder decoder; + + std::set targets; + for (const auto& match : scanner->ScanCodeSection(seg, 0, seg.size())) { + const auto info = decoder.DecodeInstruction(seg.data() + match.Offset, match.Length); + if (!info.HasBranchTarget || info.HasRipRelativeDisp || info.FlowKind == ControlFlowKind::Call) continue; + const auto target = static_cast(match.Offset + match.Length) + info.BranchDisp; + if (target >= 0 && static_cast(target) < seg.size()) targets.insert(static_cast(target)); + } + std::vector> taken; + for (const auto& site : existing) { + if (site.Address < ph.MappedAddress || site.Address >= ph.MappedAddress + segSize) continue; + const auto at = static_cast(site.Address - ph.MappedAddress); + taken.emplace_back(at, at + site.Length); + } + const auto overlapsExisting = [&](const std::size_t begin, const std::size_t end) { + return std::any_of(taken.begin(), taken.end(), [&](const auto& range) { return begin < range.second && range.first < end; }); + }; + const auto targetInside = [&](const std::size_t begin, const std::size_t end) { + const auto hit = targets.upper_bound(begin); + return hit != targets.end() && *hit < end; + }; + for (const auto& function : functions) { + if (function.Begin < ph.MappedAddress || function.End <= function.Begin || function.End - ph.MappedAddress > segSize) continue; + const auto start = static_cast(function.Begin - ph.MappedAddress); + const auto limit = static_cast(function.End - ph.MappedAddress); + ++report.Functions; + const auto skip = [&](const char* reason) { ++report.Skipped[reason]; }; + + const std::vector code(seg.begin() + static_cast(start), seg.begin() + static_cast(limit)); + std::vector body; + for (const auto& match : scanner->ScanCodeSection(code, 0, code.size())) { + if (code[match.Offset] == 0xcc && match.Length == 1) break; + body.push_back({start + static_cast(match.Offset), match.Length, decoder.DecodeInstruction(code.data() + match.Offset, match.Length)}); + } + if (body.size() < 3) continue; + const auto at = [&](const std::size_t k) { return seg.data() + body[k].Offset; }; + const bool rbpPrologue = body[0].Length == 1 && at(0)[0] == 0x55 && body[1].Length == 3 && std::memcmp(at(1), "\x48\x89\xe5", 3) == 0; + std::vector pushed; + std::size_t p = rbpPrologue ? 2 : 0; + while (p < body.size()) + if (const auto reg = pushedRegister(at(p), body[p])) { pushed.push_back(*reg); ++p; } else break; + std::int64_t allocation = 0; + if (!rbpPrologue && p < body.size()) + if (const auto adjustment = rspAdjustment(at(p), body[p]); adjustment && *adjustment < 0) { allocation = -*adjustment; ++p; } + std::vector expected(pushed.begin(), pushed.end()); + if (rbpPrologue) expected.insert(expected.begin(), kRegRbp); + const auto popsBefore = [&](const std::size_t k) -> std::optional { + std::size_t q = k; + std::vector popped; + while (q > p && popped.size() < expected.size()) + if (const auto reg = poppedRegister(at(q - 1), body[q - 1])) { popped.push_back(*reg); --q; } else break; + if (popped != expected) return std::nullopt; + return q; + }; + std::set frees; + if (allocation > 0) + for (std::size_t k = p; k < body.size(); ++k) + if (body[k].Info.FlowKind == ControlFlowKind::Return) + if (const auto q = popsBefore(k); q && *q > p && rspAdjustment(at(*q - 1), body[*q - 1]) == allocation) frees.insert(*q - 1); + + bool leaf = true; + bool redZone = false; + const char* reason = nullptr; + std::vector> exits; + std::map>> moves; + std::vector stubbed; + const auto functionEnd = limit; + std::int64_t depth = 0; + bool wholeRedZone = rbpPrologue; + for (std::size_t k = p; k < body.size(); ++k) { + const auto use = memoryUse(at(k), body[k]); + if (!use.RspBased) continue; + if (use.RspDisp < 0) depth = std::max(depth, -use.RspDisp); + if (use.Indexed || (use.Address && use.RspDisp < 0)) wholeRedZone = true; + } + const auto shift = wholeRedZone ? kRedZoneBytes : std::min(kRedZoneBytes, (depth + kStackAlignment - 1) / kStackAlignment * kStackAlignment); + for (std::size_t k = p; k < body.size(); ++k) { + const auto& insn = body[k]; + const auto* code = at(k); + const auto flow = insn.Info.FlowKind; + if (flow == ControlFlowKind::Call || flow == ControlFlowKind::IndirectCall) { leaf = false; break; } + if (frees.contains(k)) continue; + const auto use = memoryUse(code, insn); + if (use.RbpBased && rbpPrologue && use.RbpDisp < -static_cast(8 * pushed.size())) redZone = true; + if (use.RspBased && use.RspDisp < 0) redZone = true; + if (reason != nullptr) continue; + if (code[insn.Info.OpcodeOffset] == 0xc9 || code[insn.Info.OpcodeOffset] == 0xc8) { reason = "leave or enter"; continue; } + if (rbpPrologue) { + if (use.UsesRsp) { reason = "other use of rsp"; continue; } + } else { + if (use.RegisterRsp) { reason = "other use of rsp"; continue; } + if (use.RspBased) { + if (code[insn.Info.OpcodeOffset] == 0x62) { reason = "rsp operand that cannot move with rsp"; continue; } + if (overlapsExisting(insn.Offset, insn.Offset + insn.Length)) { reason = "overlaps another stub"; continue; } + if (auto moved = movedDisplacement(use, shift)) moves.emplace(k, std::pair{insn.Offset + use.DispOffset, std::move(*moved)}); + else stubbed.push_back(k); + } + } + if (flow == ControlFlowKind::IndirectJump) { reason = "indirect jump"; continue; } + if ((flow == ControlFlowKind::UnconditionalJump || flow == ControlFlowKind::ConditionalBranch) && insn.Info.HasBranchTarget) { + const auto target = static_cast(insn.Offset + insn.Length) + insn.Info.BranchDisp; + if (insn.Info.HasRipRelativeDisp || target < static_cast(start) || target >= static_cast(functionEnd)) { reason = "branch leaves the function"; continue; } + } + if (flow == ControlFlowKind::Return) { + if (insn.Length != 1 || code[0] != 0xc3) { reason = "ret with an immediate"; continue; } + const auto pops = popsBefore(k); + if (!pops) { reason = "exit does not pop the prologue's registers"; continue; } + auto q = *pops; + if (allocation > 0) { + if (q == p || !frees.contains(q - 1)) { reason = "exit does not free the prologue's allocation"; continue; } + --q; + } + const auto siteStart = body[q].Offset; + const auto siteEnd = insn.Offset + 1; + if (siteEnd - siteStart < kJumpBytes) { reason = "exit shorter than a jump"; continue; } + if (targetInside(siteStart, siteEnd)) { reason = "branch into an exit"; continue; } + exits.emplace_back(siteStart, siteEnd); + } + } + if (!leaf || !redZone) continue; + ++report.RedZoneFunctions; + const auto prologueEnd = p > 0 ? body[p - 1].Offset + body[p - 1].Length : start; + if (reason == nullptr && ((pushed.empty() && allocation == 0) || prologueEnd - start < kJumpBytes)) reason = "prologue shorter than a jump"; + if (reason == nullptr && targetInside(start, prologueEnd)) reason = "branch into the prologue"; + if (reason == nullptr && exits.empty()) reason = "no ret exit"; + if (reason == nullptr && (overlapsExisting(start, prologueEnd) || std::any_of(exits.begin(), exits.end(), [&](const auto& exit) { return overlapsExisting(exit.first, exit.second); }))) reason = "overlaps another stub"; + std::vector stubs; + const auto overlapsSite = [&](const std::size_t begin, const std::size_t end) { + if (begin < prologueEnd && start < end) return true; + if (std::any_of(exits.begin(), exits.end(), [&](const auto& exit) { return begin < exit.second && exit.first < end; })) return true; + return std::any_of(stubs.begin(), stubs.end(), [&](const OperandStub& stub) { return begin < stub.End && stub.Begin < end; }); + }; + for (const auto k : stubbed) { + if (reason != nullptr) break; + if (!stubs.empty() && k < stubs.back().Last) continue; + OperandStub stub{k, k, body[k].Offset, body[k].Offset, {}}; + while (stub.Last < body.size() && stub.End - stub.Begin < kJumpBytes) { + const auto& covered = body[stub.Last]; + if (covered.Offset != stub.End || covered.Info.FlowKind != ControlFlowKind::Sequential || covered.Info.HasRipRelativeDisp || frees.contains(stub.Last)) break; + const auto use = memoryUse(at(stub.Last), covered); + const auto bytes = use.RspBased ? withDisplacement(at(stub.Last), covered, use, use.RspDisp + shift) : std::vector(at(stub.Last), at(stub.Last) + covered.Length); + if (!bytes) break; + stub.Body.insert(stub.Body.end(), bytes->begin(), bytes->end()); + if (use.RspBased) ++stub.Operands; + stub.End += covered.Length; + ++stub.Last; + } + while (stub.End - stub.Begin < kJumpBytes && stub.First > p) { + const auto j = stub.First - 1; + const auto& prior = body[j]; + if (prior.Offset + prior.Length != stub.Begin || prior.Info.FlowKind != ControlFlowKind::Sequential || prior.Info.HasRipRelativeDisp || frees.contains(j)) break; + const auto use = memoryUse(at(j), prior); + const auto bytes = use.RspBased ? withDisplacement(at(j), prior, use, use.RspDisp + shift) : std::vector(at(j), at(j) + prior.Length); + if (!bytes) break; + stub.Body.insert(stub.Body.begin(), bytes->begin(), bytes->end()); + if (use.RspBased) ++stub.Operands; + stub.Begin = prior.Offset; + stub.First = j; + } + if (stub.End - stub.Begin < kJumpBytes || targetInside(stub.Begin, stub.End) || overlapsSite(stub.Begin, stub.End) || overlapsExisting(stub.Begin, stub.End)) reason = "rsp operand that cannot move and has no room for a stub"; + else stubs.push_back(std::move(stub)); + } + if (reason != nullptr) { skip(reason); continue; } + for (const auto& stub : stubs) + for (auto k = stub.First; k < stub.Last; ++k) moves.erase(k); + + const auto addSite = [&](const std::size_t begin, const std::size_t end, std::vector code) { + TrampolineSite site; + site.Offset = ph.Offset + begin; + site.Address = ph.MappedAddress + begin; + site.Length = end - begin; + site.OriginalBytes.assign(seg.begin() + static_cast(begin), seg.begin() + static_cast(end)); + site.ReturnBranchOffset = code.size(); + code.insert(code.end(), std::begin(kJumpBack), std::end(kJumpBack)); + site.Body = std::move(code); + taken.emplace_back(begin, end); + sites.push_back(std::move(site)); + }; + const auto original = [&](const std::size_t begin, const std::size_t end) { return std::vector(seg.begin() + static_cast(begin), seg.begin() + static_cast(end)); }; + auto entry = original(start, prologueEnd); + const auto lower = lowerRsp(shift); + entry.insert(entry.end(), lower.begin(), lower.end()); + addSite(start, prologueEnd, std::move(entry)); + for (const auto& exit : exits) { + auto code = raiseRsp(shift); + const auto tail = original(exit.first, exit.second); + code.insert(code.end(), tail.begin(), tail.end()); + addSite(exit.first, exit.second, std::move(code)); + } + for (auto& stub : stubs) addSite(stub.Begin, stub.End, std::move(stub.Body)); + for (auto& [k, move] : moves) displacements.push_back({ph.Offset + move.first, std::move(move.second)}); + ++report.Framed; + if (!rbpPrologue) ++report.FramedWithoutFramePointer; + report.Exits += exits.size(); + report.MovedOperands += moves.size(); + report.OperandStubs += stubs.size(); + for (const auto& stub : stubs) report.StubbedOperands += stub.Operands; + } +} + +} + +std::vector FrameRedZoneFunctions( + const std::vector& fileBytes, + const std::vector& codeSegments, + const std::vector& functions, + const std::vector& existing, + RedZoneFrameReport& report, + std::vector& displacements +) { + std::vector sites; + for (const auto& ph : codeSegments) frameSegment(fileBytes, ph, functions, existing, sites, displacements, report); + return sites; +} + +} diff --git a/core/relinker/codegen/src/x86/Sha1Operands.cpp b/core/relinker/codegen/src/x86/Sha1Operands.cpp index 69ea10a68a..aa49ed27d5 100644 --- a/core/relinker/codegen/src/x86/Sha1Operands.cpp +++ b/core/relinker/codegen/src/x86/Sha1Operands.cpp @@ -20,16 +20,18 @@ Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { pos += 1; continue; } + if (b == PrefixLock) { + throw CodegenException("SHA-1 instruction with a LOCK prefix"); + } if (b == PrefixOperandSize || b == PrefixRepne || b == PrefixRep) { throw CodegenException("Not a SHA-1 instruction"); } - if (b != PrefixLock && b != PrefixAddressSize && + if (b != PrefixAddressSize && b != PrefixSegCs && b != PrefixSegSs && b != PrefixSegDs && b != PrefixSegEs && b != PrefixSegFs && b != PrefixSegGs) { break; } - if (b != PrefixLock) - prefixes.push_back(b); + prefixes.push_back(b); rex = 0; pos += 1; } diff --git a/core/relinker/codegen/src/x86/Sha256Operands.cpp b/core/relinker/codegen/src/x86/Sha256Operands.cpp index a090514a15..51af8ec376 100644 --- a/core/relinker/codegen/src/x86/Sha256Operands.cpp +++ b/core/relinker/codegen/src/x86/Sha256Operands.cpp @@ -20,16 +20,18 @@ Sha256Operands DecodeSha256(const std::uint8_t* data, const std::size_t length) pos += 1; continue; } + if (b == PrefixLock) { + throw CodegenException("SHA-256 instruction with a LOCK prefix"); + } if (b == PrefixOperandSize || b == PrefixRepne || b == PrefixRep) { throw CodegenException("Not a SHA-256 instruction"); } - if (b != PrefixLock && b != PrefixAddressSize && + if (b != PrefixAddressSize && b != PrefixSegCs && b != PrefixSegSs && b != PrefixSegDs && b != PrefixSegEs && b != PrefixSegFs && b != PrefixSegGs) { break; } - if (b != PrefixLock) - prefixes.push_back(b); + prefixes.push_back(b); rex = 0; pos += 1; } diff --git a/core/relinker/codegen/src/x86/Sse4aOperands.cpp b/core/relinker/codegen/src/x86/Sse4aOperands.cpp index cf492728c2..2a664f1140 100644 --- a/core/relinker/codegen/src/x86/Sse4aOperands.cpp +++ b/core/relinker/codegen/src/x86/Sse4aOperands.cpp @@ -19,11 +19,14 @@ Sse4aOperands DecodeSse4a(const std::uint8_t* data, const std::size_t length) { pos += 1; continue; } + if (b == PrefixLock) { + throw CodegenException("SSE4a instruction with a LOCK prefix"); + } if (b == PrefixOperandSize) { operandSizeOverride = true; } else if (b == PrefixRepne) { repnePrefix = true; - } else if (b != PrefixLock && b != PrefixRep && b != PrefixAddressSize && + } else if (b != PrefixRep && b != PrefixAddressSize && b != PrefixSegCs && b != PrefixSegSs && b != PrefixSegDs && b != PrefixSegEs && b != PrefixSegFs && b != PrefixSegGs) { break; diff --git a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp index b22c3f8726..78936363b4 100644 --- a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp +++ b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp @@ -393,6 +393,7 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( const std::uint8_t modrm = data[modrmPos]; info.HasModRm = true; info.ModRmByte = modrm; + info.ModRmOffset = modrmPos; info.ModRmRegField = (modrm >> ModRmRegShift) & ModRmRegMask; const std::uint8_t mod = registerOnly ? ModRmModRegister : (modrm >> ModRmModShift) & ModRmModMask; const std::uint8_t rm = modrm & ModRmRmMask; @@ -489,6 +490,7 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( std::uint8_t modrm = data[pos]; info.HasModRm = true; info.ModRmByte = modrm; + info.ModRmOffset = pos; std::uint8_t reg = (modrm >> ModRmRegShift) & ModRmRegMask; info.ModRmRegField = reg; std::uint8_t mod = (modrm >> ModRmModShift) & ModRmModMask; @@ -622,6 +624,9 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( (op >= 0xaa && op <= 0xaf) || op == 0xd7)) info.HasMemoryOperand = true; + if ((!twoByteOpcode && op == OneByteLea) || (twoByteOpcode && op == TwoByteNopModRm)) + info.HasMemoryOperand = false; + info.FlowKind = ControlFlowKind::Sequential; return info; } diff --git a/core/relinker/codegen/src/x86/X64InstructionRewriter.cpp b/core/relinker/codegen/src/x86/X64InstructionRewriter.cpp index 212bdaee22..e17dde61e9 100644 --- a/core/relinker/codegen/src/x86/X64InstructionRewriter.cpp +++ b/core/relinker/codegen/src/x86/X64InstructionRewriter.cpp @@ -54,6 +54,12 @@ std::int64_t X64InstructionRewriter::AdjustedDelta( std::int64_t sliceSize, std::int64_t delta ) { + const bool targetInsideCut = targetPosition > cutStart && targetPosition < cutEnd; + + if (targetInsideCut) { + throw CodegenException("Branch target inside rewritten instruction", static_cast(instrPosition)); + } + const bool instrShifts = instrPosition >= cutEnd && instrPosition < sliceSize; const bool targetShifts = targetPosition >= cutEnd && targetPosition < sliceSize; @@ -96,6 +102,66 @@ RewriteResult X64InstructionRewriter::Rewrite( const auto cutStart = static_cast(offset); const std::int64_t cutEnd = cutStart + oldLength; + if (delta != 0) { + const auto sectionSize = static_cast(codeSection.size()); + + for (const auto& match : matches) { + const auto matchOffset = static_cast(match.Offset); + if (matchOffset >= cutStart && matchOffset < cutEnd) + continue; + + const auto info = decoder.DecodeInstruction( + codeSection.data() + match.Offset, match.Length); + + if (!info.TruncatesBranchTarget) + continue; + + const auto opcodeLength = info.IsTwoByteOpcode ? 2u : 1u; + const auto displacementSize = + info.Length - info.OpcodeOffset - opcodeLength; + const auto displacementOffset = + match.Offset + info.Length - displacementSize; + + std::int64_t branchDisplacement = 0; + + if (displacementSize == 1) { + branchDisplacement = + static_cast(codeSection[displacementOffset]); + } else if (displacementSize == 2) { + const auto raw = static_cast( + static_cast(codeSection[displacementOffset]) | + (static_cast( + codeSection[displacementOffset + 1]) << 8)); + branchDisplacement = static_cast(raw); + } else { + throw CodegenException( + "Unexpected displacement size for truncated relative branch", + match.Offset); + } + + const auto rawTargetPosition = + matchOffset + static_cast(info.Length) + + branchDisplacement; + if (rawTargetPosition < 0 || rawTargetPosition > 0xFFFF) { + throw CodegenException( + "Cannot change code length while a truncated relative branch target wraps at 16 bits", + match.Offset); + } + const auto targetPosition = rawTargetPosition; + + const bool instrShifts = + matchOffset >= cutEnd && matchOffset < sectionSize; + const bool targetShifts = + targetPosition >= cutEnd && targetPosition < sectionSize; + + if (instrShifts != targetShifts) { + throw CodegenException( + "Cannot change code length while a truncated relative branch moves", + match.Offset); + } + } + } + std::vector rewritten; rewritten.reserve(codeSection.size() + static_cast(delta > 0 ? delta : 0)); rewritten.insert(rewritten.end(), codeSection.begin(), codeSection.begin() + offset); diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index 08d527462d..998fc11443 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -21,7 +21,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -207,6 +209,8 @@ void decoderTwoByteOpcodeLengths() { } void sse4aOperands() { + requireFailure([] { const Bytes bytes = {0xF0, 0x66, 0x0F, 0x78, 0xC0, 0x08, 0x00}; (void)Codegen::DecodeSse4a(bytes.data(), bytes.size()); }, "LOCK EXTRQ was accepted"); + requireFailure([] { const Bytes bytes = {0xF2, 0xF0, 0x0F, 0x79, 0xC1}; (void)Codegen::DecodeSse4a(bytes.data(), bytes.size()); }, "LOCK INSERTQ was accepted"); const auto check = [](const Bytes& site, const bool insertq, const int dst, const int src, const int length, const int index) { const auto operands = Codegen::DecodeSse4a(site.data(), site.size()); require(operands.Insertq == insertq && !operands.RegisterForm && operands.Destination == dst && operands.Source == src && operands.Length == length && operands.Index == index, "SSE4a operands were decoded incorrectly"); @@ -230,6 +234,8 @@ void sse4aOperands() { } void sha256Operands() { + requireFailure([] { const Bytes bytes = {0xF0, 0x0F, 0x38, 0xCB, 0xCA}; (void)Codegen::DecodeSha256(bytes.data(), bytes.size()); }, "LOCK SHA256RNDS2 was accepted"); + requireFailure([] { const Bytes bytes = {0x2E, 0xF0, 0x0F, 0x38, 0xCC, 0x08}; (void)Codegen::DecodeSha256(bytes.data(), bytes.size()); }, "LOCK SHA256MSG1 memory form was accepted"); const auto check = [](const Bytes& site, const Codegen::Sha256Operation operation, const int dst, const int src) { const auto operands = Codegen::DecodeSha256(site.data(), site.size()); require(operands.Operation == operation && operands.Destination == dst && operands.Source == src, "SHA-256 operands were decoded incorrectly"); @@ -260,6 +266,8 @@ void sha256Operands() { } void sha1Operands() { + requireFailure([] { const Bytes bytes = {0xF0, 0x0F, 0x3A, 0xCC, 0xCA, 0x00}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "LOCK SHA1RNDS4 was accepted"); + requireFailure([] { const Bytes bytes = {0x2E, 0xF0, 0x0F, 0x38, 0xC9, 0x08}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "LOCK SHA1MSG1 memory form was accepted"); const auto check = [](const Bytes& site, const Codegen::Sha1Operation operation, const int dst, const int src, const int function) { const auto operands = Codegen::DecodeSha1(site.data(), site.size()); require(operands.Operation == operation && operands.Destination == dst && operands.Source == src && operands.Function == function, "SHA-1 operands were decoded incorrectly"); @@ -321,6 +329,8 @@ void matcherSubstitutions() { const auto movntsd = match({0xF2, 0x44, 0x0F, 0x2B, 0x4C, 0x24, 0x10}); require(movntsd && movntsd->Lowering == Codegen::Amd64OnlyLowering::InPlace && movntsd->ReplacementBytes == Bytes{0xF2, 0x44, 0x0F, 0x11, 0x4C, 0x24, 0x10} && movntsd->InstructionName == "MOVNTSD", "MOVNTSD was not rewritten to MOVSD"); requireFailure([&] { (void)match({0xF3, 0x0F, 0x2B, 0xC1}); }, "MOVNTSS with a register operand was accepted"); + const auto movntssDisplacement = match({0xF3, 0x0F, 0x2B, 0x47, 0xF0}); + require(movntssDisplacement && movntssDisplacement->ReplacementBytes == Bytes{0xF3, 0x0F, 0x11, 0x47, 0xF0}, "MOVNTSS displacement byte was treated as a LOCK prefix"); const auto monitorx = match({0x0F, 0x01, 0xFA}); require(monitorx && monitorx->Lowering == Codegen::Amd64OnlyLowering::InPlace && monitorx->ReplacementBytes == Bytes{0x0F, 0x1F, 0x00} && monitorx->InstructionName == "MONITORX", "MONITORX was not replaced by a NOP"); const auto mwaitx = match({0x0F, 0x01, 0xFB}); @@ -505,6 +515,13 @@ void converterSegment() { lockedRdpru[0x211] = 0x01; lockedRdpru[0x212] = 0xFD; requireFailure([&] { (void)converter->Convert(lockedRdpru, {segmentHeader(20)}); }, "LOCK RDPRU was accepted"); + for (const Bytes& instruction : {Bytes{0xF0, 0xF3, 0x0F, 0x2B, 0x07}, Bytes{0xF3, 0xF0, 0x0F, 0x2B, 0x07}, + Bytes{0xF0, 0xF2, 0x0F, 0x2B, 0x07}, Bytes{0xF2, 0xF0, 0x0F, 0x2B, 0x07}}) { + auto lockedMovnts = file; + std::copy(instruction.begin(), instruction.end(), lockedMovnts.begin() + 0x20F); + lockedMovnts[0x214] = 0xC3; + require(failureOffset([&] { (void)converter->Convert(lockedMovnts, {segmentHeader(21)}); }, "LOCK MOVNTSS/MOVNTSD was accepted") == 0x20F, "LOCK MOVNTSS/MOVNTSD failure does not carry the file offset"); + } auto registerForm = file; const Bytes extrqRegister = {0x66, 0x0F, 0x79, 0xCA}; std::copy(extrqRegister.begin(), extrqRegister.end(), registerForm.begin() + 0x20F); @@ -560,6 +577,9 @@ void converterSha256() { 0x90, 0xC3}; std::copy(text.begin(), text.end(), file.begin() + 0x200); + auto locked = file; + locked.insert(locked.begin() + 0x200, 0xF0); + require(failureOffset([&] { (void)converter->Convert(locked, {segmentHeader(text.size() + 1)}); }, "LOCK SHA-256 was accepted") == 0x200, "LOCK SHA-256 failure does not carry the file offset"); const auto result = converter->Convert(file, {segmentHeader(text.size())}); require(result.Trampolines.size() == 2 && result.Reports.size() == 2 && result.Bytes == file, "SHA-256 sites were not lowered through stubs"); const auto& rounds = result.Trampolines[0]; @@ -595,6 +615,9 @@ void converterSha1() { 0x90, 0xC3}; std::copy(text.begin(), text.end(), file.begin() + 0x200); + auto locked = file; + locked.insert(locked.begin() + 0x200, 0xF0); + require(failureOffset([&] { (void)converter->Convert(locked, {segmentHeader(text.size() + 1)}); }, "LOCK SHA-1 was accepted") == 0x200, "LOCK SHA-1 failure does not carry the file offset"); const auto result = converter->Convert(file, {segmentHeader(text.size())}); require(result.Trampolines.size() == 3 && result.Reports.size() == 3 && result.Bytes == file, "SHA-1 sites were not lowered through stubs"); require(result.Trampolines[0].Offset == 0x200 && result.Trampolines[0].Length == 5 && result.Reports[0].InstructionName == "SHA1RNDS4", "SHA1RNDS4 was not lowered as one site"); @@ -689,8 +712,8 @@ void rosettaSubstitutions() { const auto intel = Codegen::MakeAmd64OnlyInstructionMatcher(); const auto rosetta = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta); const auto match = [](const auto& matcher, const Bytes& bytes) { return matcher->Match(bytes.data(), bytes.size()); }; - for (const auto& bytes : {Bytes{0x0F, 0xC7, 0xF8}, Bytes{0xF3, 0x0F, 0xC7, 0xF8}, Bytes{0x66, 0x0F, 0xAE, 0x30}, Bytes{0x0F, 0x01, 0xF9}}) - require(!match(intel, bytes).has_value(), "RDSEED, RDPID, CLWB or RDTSCP was converted for an Intel host"); + for (const auto& bytes : {Bytes{0x0F, 0xC7, 0xF8}, Bytes{0xF3, 0x0F, 0xC7, 0xF8}, Bytes{0x66, 0x0F, 0xAE, 0x30}, Bytes{0x0F, 0x01, 0xF9}, Bytes{0x0F, 0xAE, 0x37}}) + require(!match(intel, bytes).has_value(), "RDSEED, RDPID, CLWB, RDTSCP or XSAVEOPT was converted for an Intel host"); const auto rdseed = match(rosetta, {0x48, 0x0F, 0xC7, 0xF9}); require(rdseed && rdseed->Lowering == Amd64OnlyLowering::InPlace && rdseed->ReplacementBytes == Bytes{0x48, 0x0F, 0xC7, 0xF1} && rdseed->InstructionName == "RDSEED", "RDSEED was not rewritten to RDRAND"); const auto rdseed16 = match(rosetta, {0x66, 0x41, 0x0F, 0xC7, 0xFA}); @@ -700,6 +723,18 @@ void rosettaSubstitutions() { require(clwb && clwb->Lowering == Amd64OnlyLowering::InPlace && clwb->ReplacementBytes == Bytes{0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00} && clwb->InstructionName == "CLWB", "CLWB was not replaced by a NOP"); require(!match(rosetta, {0x66, 0x0F, 0xAE, 0xF1}).has_value(), "TPAUSE (the register form of 66 0F AE /6) was taken for CLWB"); require(!match(rosetta, {0x66, 0x0F, 0xAE, 0x38}).has_value(), "CLFLUSHOPT was taken for CLWB"); + const auto xsaveopt = match(rosetta, {0x0F, 0xAE, 0x37}); + require(xsaveopt && xsaveopt->Lowering == Amd64OnlyLowering::InPlace && xsaveopt->ReplacementBytes == Bytes{0x0F, 0xAE, 0x27} && xsaveopt->InstructionName == "XSAVEOPT", "XSAVEOPT was not rewritten to XSAVE"); + const auto xsaveopt64 = match(rosetta, {0x48, 0x0F, 0xAE, 0x74, 0x24, 0x40}); + require(xsaveopt64 && xsaveopt64->ReplacementBytes == Bytes{0x48, 0x0F, 0xAE, 0x64, 0x24, 0x40}, "XSAVEOPT64 with an RSP-relative operand was not rewritten to XSAVE64"); + const auto xsaveoptR12 = match(rosetta, {0x41, 0x0F, 0xAE, 0x34, 0x24}); + require(xsaveoptR12 && xsaveoptR12->ReplacementBytes == Bytes{0x41, 0x0F, 0xAE, 0x24, 0x24}, "XSAVEOPT with an extended base register was not rewritten to XSAVE"); + const auto xsaveoptRip = match(rosetta, {0x0F, 0xAE, 0x35, 0x00, 0x01, 0x00, 0x00}); + require(xsaveoptRip && xsaveoptRip->ReplacementBytes == Bytes{0x0F, 0xAE, 0x25, 0x00, 0x01, 0x00, 0x00}, "RIP-relative XSAVEOPT was not rewritten to XSAVE with the same displacement"); + const auto xsaveoptFs = match(rosetta, {0x64, 0x0F, 0xAE, 0x34, 0x25, 0x40, 0x00, 0x00, 0x00}); + require(xsaveoptFs && xsaveoptFs->ReplacementBytes == Bytes{0x64, 0x0F, 0xAE, 0x24, 0x25, 0x40, 0x00, 0x00, 0x00}, "FS-relative XSAVEOPT was not rewritten to XSAVE"); + for (const auto& other : {Bytes{0x0F, 0xAE, 0x27}, Bytes{0x0F, 0xAE, 0x2F}, Bytes{0x0F, 0xAE, 0x3F}, Bytes{0x0F, 0xAE, 0xF0}, Bytes{0xF3, 0x0F, 0xAE, 0xF0}, Bytes{0xF2, 0x0F, 0xAE, 0xF1}, Bytes{0xF3, 0x0F, 0xAE, 0x37}}) + require(!match(rosetta, other).has_value(), "XSAVE, XRSTOR, CLFLUSH, MFENCE, UMONITOR, UMWAIT or a prefixed 0F AE /6 was taken for XSAVEOPT"); const auto rdpid = match(rosetta, {0xF3, 0x41, 0x0F, 0xC7, 0xFB}); require(rdpid && rdpid->Lowering == Amd64OnlyLowering::Trampoline && rdpid->Optional && rdpid->InstructionName == "RDPID", "RDPID was not lowered through an optional stub"); require(rdpid->ReturnBranchOffset == 6 && Bytes(rdpid->StubBody.begin(), rdpid->StubBody.begin() + 7) == Bytes{0x41, 0xBB, 0, 0, 0, 0, 0xE9}, "RDPID r11 stub does not load 0 before returning"); @@ -836,6 +871,21 @@ void converterWideStoreWindows() { expected.insert(expected.end(), {0xC5, 0xF8, 0x11, 0x0E, 0xC4, 0xE3, 0x7D, 0x19, 0x4E, 0x10, 0x01}); require(paired.Trampolines.size() == 1 && paired.Trampolines[0].Length == 8 && body(paired.Trampolines[0]) == expected && paired.Reports.size() == 1, "A 256-bit store followed by another was not lowered with it in one stub"); + const auto withRdseed = convert(withStore({}, {0x0F, 0xC7, 0xF8, 0xC3})); + expected = halves; + expected.insert(expected.end(), {0x0F, 0xC7, 0xF0}); + require(withRdseed.Trampolines.size() == 1 && withRdseed.Trampolines[0].Length == 7 && body(withRdseed.Trampolines[0]) == expected && withRdseed.Reports.size() == 1, "A 256-bit store followed by an in-place site did not move its replacement into the stub"); + + const auto withClwb = convert(withStore({}, {0x66, 0x0F, 0xAE, 0x37, 0xC3})); + expected = halves; + expected.insert(expected.end(), {0x0F, 0x1F, 0x40, 0x00}); + require(withClwb.Trampolines.size() == 1 && withClwb.Trampolines[0].Length == 8 && body(withClwb.Trampolines[0]) == expected, "A 256-bit store followed by CLWB did not move its replacement into the stub"); + + const auto withRipStore = convert(withStore({}, {0xF3, 0x0F, 0x2B, 0x05, 0x40, 0x00, 0x00, 0x00, 0xC3})); + expected = halves; + expected.insert(expected.end(), {0xF3, 0x0F, 0x11, 0x05, 0x00, 0x00, 0x00, 0x00}); + require(withRipStore.Trampolines.size() == 1 && withRipStore.Trampolines[0].Length == 12 && body(withRipStore.Trampolines[0]) == expected && withRipStore.Trampolines[0].Relocations.size() == 1 && withRipStore.Trampolines[0].Relocations[0].SiteTarget == 0x4C && withRipStore.Trampolines[0].Relocations[0].InstructionEnd == 19, "A 256-bit store followed by a RIP-relative in-place site did not relocate its replacement"); + const Bytes push = {0x48, 0x8D, 0x64, 0x24, 0xF8, 0x50, 0x48, 0x8D, 0x05, 0, 0, 0, 0, 0x48, 0x89, 0x44, 0x24, 0x08, 0x58}; const auto ripCalled = convert(withStore({}, {0xFF, 0x15, 0x10, 0x00, 0x00, 0x00})); expected = halves; @@ -1811,7 +1861,13 @@ void sha1Execution() { else require(run.Xmm[reg][0] == xmmIn[reg][0] && run.Xmm[reg][1] == xmmIn[reg][1], "SHA-1 stub clobbered an xmm register"); } - require(run.Rax == rax && run.Rcx == 0x1122334455667788ull && (run.Flags & 0x8D5) == (0x8D7 & 0x8D5), "SHA-1 stub changed a general register or RFLAGS"); + if (run.Rax != rax || run.Rcx != 0x1122334455667788ull || (run.Flags & 0x8D5) != (0x8D7 & 0x8D5)) { + std::cerr << "SHA-1 sample=" << sample << " site=" << std::hex; + for (const auto byte : site) + std::cerr << static_cast(byte) << ' '; + std::cerr << "rax=" << run.Rax << " expected=" << rax << " rcx=" << run.Rcx << " flags=" << run.Flags << std::dec << '\n'; + throw std::runtime_error("SHA-1 stub changed a general register or RFLAGS"); + } } } } @@ -1952,6 +2008,203 @@ void mcommitExecution() { } munmap(code, 4096); } + +void wideStoreExecution() { + const auto matcher = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta); + const Bytes site = {0xC5, 0xFC, 0x11, 0x00}; + const auto match = matcher->Match(site.data(), site.size()); + require(match && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline, "256-bit store stub was not produced"); + auto body = match->StubBody; + body[match->ReturnBranchOffset] = 0xC3; + void* code = mmap(nullptr, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + require(code != MAP_FAILED, "cannot map executable memory for the stub"); + std::memcpy(code, body.data(), body.size()); + alignas(32) std::uint8_t source[32]; + for (std::size_t index = 0; index < sizeof(source); ++index) source[index] = static_cast(index + 1); + auto* pages = static_cast(mmap(nullptr, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + require(pages != MAP_FAILED, "cannot map the destination pages"); + for (const std::size_t offset : {std::size_t{0}, std::size_t{1}, std::size_t{4080}}) { + std::memset(pages, 0, 8192); + auto* destination = pages + offset; + asm volatile( + "vmovups (%[source]), %%ymm0\n\t" + "sub $128, %%rsp\n\t" + "call *%[code]\n\t" + "add $128, %%rsp\n\t" + : + : [source] "r"(source), [code] "r"(code), "a"(destination) + : "memory", "ymm0"); + require(std::memcmp(destination, source, sizeof(source)) == 0, "256-bit store stub did not write both halves"); + require(std::all_of(pages, destination, [](const std::uint8_t value) { return value == 0; }), "256-bit store stub wrote before its operand"); + require(std::all_of(destination + sizeof(source), pages + 8192, [](const std::uint8_t value) { return value == 0; }), + "256-bit store stub wrote past its operand"); + } + munmap(pages, 8192); + munmap(code, 4096); +} + +void xsaveoptExecution() { + const Bytes site = {0x48, 0x0F, 0xAE, 0x37}; + const auto match = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta)->Match(site.data(), site.size()); + require(match && match->Lowering == Codegen::Amd64OnlyLowering::InPlace, "XSAVEOPT64 was not rewritten in place"); + auto body = match->ReplacementBytes; + body.push_back(0xC3); + void* code = mmap(nullptr, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + require(code != MAP_FAILED, "cannot map executable memory for the XSAVEOPT replacement"); + std::memcpy(code, body.data(), body.size()); + alignas(64) std::uint8_t area[1024] = {}; + alignas(32) std::uint8_t pattern[32]; + alignas(32) std::uint8_t restored[32] = {}; + for (std::size_t i = 0; i < sizeof(pattern); ++i) + pattern[i] = static_cast(0xA0 + i); + asm volatile( + "vmovdqu (%[pattern]), %%ymm3\n\t" + "mov %[area], %%rdi\n\t" + "mov $7, %%eax\n\t" + "xor %%edx, %%edx\n\t" + "sub $128, %%rsp\n\t" + "call *%[code]\n\t" + "add $128, %%rsp\n\t" + "vpxor %%ymm3, %%ymm3, %%ymm3\n\t" + "mov $7, %%eax\n\t" + "xor %%edx, %%edx\n\t" + "xrstor64 (%%rdi)\n\t" + "vmovdqu %%ymm3, (%[restored])\n\t" + "vzeroupper\n\t" + : + : [pattern] "r"(pattern), [area] "r"(area), [code] "r"(code), [restored] "r"(restored) + : "rax", "rdx", "rdi", "xmm3", "memory"); + munmap(code, 4096); + std::uint64_t components = 0; + std::memcpy(&components, area + 512, sizeof(components)); + require((components & 6) == 6, "XSAVE did not mark the SSE and AVX state as saved"); + require(std::memcmp(area + 160 + 3 * 16, pattern, 16) == 0 && std::memcmp(area + 576 + 3 * 16, pattern + 16, 16) == 0, "XSAVE did not store YMM3 in the standard format"); + require(std::memcmp(restored, pattern, sizeof(pattern)) == 0, "XRSTOR did not restore YMM3 from the area XSAVE wrote"); +} + +void scalarCompareExecution() { + const auto matcher = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta); + const Bytes site = {0xF3, 0x0F, 0xC2, 0xC1, 0x05}; + const auto match = matcher->Match(site.data(), site.size()); + require(match && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline, "scalar compare stub was not produced"); + auto body = match->StubBody; + body[match->ReturnBranchOffset] = 0xC3; + void* code = mmap(nullptr, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + require(code != MAP_FAILED, "cannot map executable memory for the stub"); + std::memcpy(code, body.data(), body.size()); + struct Case { + float left; + float right; + std::uint32_t mask; + const char* name; + }; + for (const auto& item : std::vector{{309.0f, 309.0f, 0xFFFFFFFFu, "309 is not less than 309"}, + {309.0f, 310.0f, 0u, "309 is less than 310"}}) { + alignas(16) float left[4] = {item.left, 1.0f, 2.0f, 3.0f}; + alignas(16) float right[4] = {item.right, 0.0f, 0.0f, 0.0f}; + alignas(16) std::uint32_t result[4] = {}; + asm volatile( + "movaps (%[left]), %%xmm0\n\t" + "movaps (%[right]), %%xmm1\n\t" + "sub $128, %%rsp\n\t" + "call *%[code]\n\t" + "add $128, %%rsp\n\t" + "movaps %%xmm0, (%[result])\n\t" + : + : [left] "r"(left), [right] "r"(right), [result] "r"(result), [code] "r"(code) + : "memory", "xmm0", "xmm1"); + require(result[0] == item.mask, item.name); + require(result[1] == std::bit_cast(1.0f) && result[2] == std::bit_cast(2.0f) + && result[3] == std::bit_cast(3.0f), + "scalar compare stub changed the upper lanes of its destination"); + } + munmap(code, 4096); +} + +void rdpidExecution() { + const auto matcher = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta); + const Bytes site = {0xF3, 0x0F, 0xC7, 0xF8}; + const auto match = matcher->Match(site.data(), site.size()); + require(match && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline, "RDPID stub was not produced"); + auto body = match->StubBody; + body[match->ReturnBranchOffset] = 0xC3; + void* code = mmap(nullptr, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + require(code != MAP_FAILED, "cannot map executable memory for the stub"); + std::memcpy(code, body.data(), body.size()); + std::uint64_t processor = ~std::uint64_t{0}; + std::uint8_t carry = 0; + asm volatile( + "sub $128, %%rsp\n\t" + "stc\n\t" + "call *%[code]\n\t" + "setc %[carry]\n\t" + "add $128, %%rsp\n\t" + : "=&a"(processor), [carry] "=&q"(carry) + : [code] "r"(code) + : "memory", "cc"); + munmap(code, 4096); + require(processor == 0, "RDPID stub left a value other than processor 0 in RAX"); + require(carry == 1, "RDPID stub changed the flags"); +} + +void rdseedExecution() { + const auto matcher = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta); + const Bytes site = {0x0F, 0xC7, 0xF8}; + const auto match = matcher->Match(site.data(), site.size()); + require(match && match->Lowering == Codegen::Amd64OnlyLowering::InPlace, "RDSEED replacement was not produced"); + require(match->ReplacementBytes.size() == site.size(), "RDSEED replacement changed the instruction length"); + auto body = match->ReplacementBytes; + body.push_back(0xC3); + void* code = mmap(nullptr, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + require(code != MAP_FAILED, "cannot map executable memory for the replacement"); + std::memcpy(code, body.data(), body.size()); + bool produced = false; + for (unsigned attempt = 0; attempt < 16 && !produced; ++attempt) { + std::uint64_t value = 0; + std::uint8_t carry = 0; + asm volatile( + "sub $128, %%rsp\n\t" + "call *%[code]\n\t" + "setc %[carry]\n\t" + "add $128, %%rsp\n\t" + : "=&a"(value), [carry] "=&q"(carry) + : [code] "r"(code) + : "memory", "cc"); + produced = carry == 1; + } + munmap(code, 4096); + require(produced, "RDSEED replacement never reported a random value"); +} + +void clwbExecution() { + const auto matcher = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta); + const Bytes site = {0x66, 0x0F, 0xAE, 0x30}; + const auto match = matcher->Match(site.data(), site.size()); + require(match && match->Lowering == Codegen::Amd64OnlyLowering::InPlace, "CLWB replacement was not produced"); + require(match->ReplacementBytes.size() == site.size(), "CLWB replacement changed the instruction length"); + auto body = match->ReplacementBytes; + body.push_back(0xC3); + void* code = mmap(nullptr, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + require(code != MAP_FAILED, "cannot map executable memory for the replacement"); + std::memcpy(code, body.data(), body.size()); + std::uint8_t line[64] = {}; + std::uint64_t address = reinterpret_cast(line); + std::uint64_t kept = 0x5A5A5A5A5A5A5A5Aull; + std::uint8_t carry = 0; + asm volatile( + "sub $128, %%rsp\n\t" + "stc\n\t" + "call *%[code]\n\t" + "setc %[carry]\n\t" + "add $128, %%rsp\n\t" + : "+a"(address), "+b"(kept), [carry] "=&q"(carry) + : [code] "r"(code) + : "memory", "cc"); + munmap(code, 4096); + require(address == reinterpret_cast(line), "CLWB replacement changed its address register"); + require(kept == 0x5A5A5A5A5A5A5A5Aull, "CLWB replacement changed an unrelated register"); + require(carry == 1, "CLWB replacement changed the flags"); +} #else void reciprocalExecution() {} void registerFormExecution() {} @@ -1962,6 +2215,7 @@ void immediateFormExecution() {} void ripRelativeExecution() {} void rdpruExecution() {} void mcommitExecution() {} +void xsaveoptExecution() {} #endif #if defined(__x86_64__) && (defined(__linux__) || defined(__APPLE__)) @@ -1996,6 +2250,11 @@ void rdtscpExecution() { } #else void rdtscpExecution() {} +void wideStoreExecution() {} +void scalarCompareExecution() {} +void rdpidExecution() {} +void rdseedExecution() {} +void clwbExecution() {} #endif void scannerZeroTail() { @@ -2049,12 +2308,18 @@ int main() { converterInPlaceFollower(); converterClzero(); rosettaSubstitutions(); + xsaveoptExecution(); converterRosetta(); rosettaScalarCompares(); converterRosettaScalarCompare(); rosettaLegacyScalarCompares(); converterRosettaLegacyScalarCompare(); rdtscpExecution(); + wideStoreExecution(); + scalarCompareExecution(); + rdpidExecution(); + rdseedExecution(); + clwbExecution(); wideStoreSplit(); converterWideStoreWindows(); converterClaimedFollower(); diff --git a/core/relinker/codegen/tests/DecoderLengthTests.cpp b/core/relinker/codegen/tests/DecoderLengthTests.cpp index 3ad1299b0d..95c85168d7 100644 --- a/core/relinker/codegen/tests/DecoderLengthTests.cpp +++ b/core/relinker/codegen/tests/DecoderLengthTests.cpp @@ -120,6 +120,8 @@ int main() { for (const auto& testCase : std::vector>{ {"6590", false}, {"654889c8", false}, + {"658d0511111111", false}, + {"650f1f0511111111", false}, {"65488b0c2578030000", true}, {"65a4", true}, {"650fa2", false}, diff --git a/core/relinker/codegen/tests/MaskedStoreLoweringTests.cpp b/core/relinker/codegen/tests/MaskedStoreLoweringTests.cpp new file mode 100644 index 0000000000..5517011feb --- /dev/null +++ b/core/relinker/codegen/tests/MaskedStoreLoweringTests.cpp @@ -0,0 +1,368 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if (defined(__linux__) || defined(__APPLE__)) && defined(__x86_64__) +#include +#include +#include +#endif + +namespace { + +using Bytes = std::vector; + +void require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +struct Form { + std::uint8_t Opcode; + bool W; + const char* Name; + std::uint8_t ElementSize; +}; + +constexpr std::array kForms{ + Form{0x2E, false, "VMASKMOVPS", 4}, + Form{0x2F, false, "VMASKMOVPD", 8}, + Form{0x8E, false, "VPMASKMOVD", 4}, + Form{0x8E, true, "VPMASKMOVQ", 8}, +}; + +Bytes maskedStore(const Form& form, bool wide, std::uint8_t mask, std::uint8_t source, std::uint8_t rexIndexBase, Bytes memory, const Bytes& prefixes = {}) { + Bytes bytes = prefixes; + bytes.insert(bytes.end(), {0xC4, + static_cast(((source & 8) != 0 ? 0 : 0x80) | ((rexIndexBase & 2) != 0 ? 0 : 0x40) | ((rexIndexBase & 1) != 0 ? 0 : 0x20) | 0x02), + static_cast((form.W ? 0x80 : 0) | ((~mask & 15) << 3) | (wide ? 4 : 0) | 0x01), form.Opcode}); + memory.at(0) = static_cast(memory[0] | ((source & 7) << 3)); + bytes.insert(bytes.end(), memory.begin(), memory.end()); + return bytes; +} + +bool rejected(const Bytes& bytes) { + try { + (void)Codegen::DecodeMaskedStore(bytes.data(), bytes.size()); + } catch (const Codegen::CodegenException&) { + return true; + } + return false; +} + +void decoding() { + for (const auto& form : kForms) { + for (const bool wide : {false, true}) { + for (std::uint8_t mask = 0; mask < 16; ++mask) { + for (std::uint8_t source = 0; source < 16; ++source) { + const auto bytes = maskedStore(form, wide, mask, source, 0, {0x07}); + const auto operands = Codegen::DecodeMaskedStore(bytes.data(), bytes.size()); + require(operands && std::string(operands->Name) == form.Name && operands->ElementSize == form.ElementSize && operands->Wide == wide && operands->Mask == mask, "Masked store decoding changed the form, width or mask register"); + require(operands->Memory.Rm == 7 && operands->Memory.Mod == 0 && operands->Memory.RexIndexBase == 0 && operands->Instruction == bytes, "Masked store decoding changed the memory operand"); + } + } + } + } + const auto extended = maskedStore(kForms[0], true, 3, 4, 3, {0x84, 0xD1, 0x78, 0x56, 0x34, 0x12}, {0x64, 0x67}); + const auto operands = Codegen::DecodeMaskedStore(extended.data(), extended.size()); + require(operands && operands->Memory.RexIndexBase == 3 && operands->Memory.Sib == 0xD1 && operands->Memory.Displacement == 0x12345678 && operands->Memory.Prefixes == Bytes{0x64, 0x67}, "Masked store extensions, SIB or prefixes were lost"); + const auto rip = maskedStore(kForms[1], false, 1, 2, 0, {0x05, 0x10, 0x00, 0x00, 0x00}); + const auto ripOperands = Codegen::DecodeMaskedStore(rip.data(), rip.size()); + require(ripOperands && ripOperands->Memory.RipRelative && ripOperands->DisplacementOffset == 5 && ripOperands->Memory.Displacement == 0x10, "RIP-relative masked store was not recognised"); + const auto stack = maskedStore(kForms[2], false, 1, 2, 0, {0x44, 0x24, 0x08}); + const auto stackOperands = Codegen::DecodeMaskedStore(stack.data(), stack.size()); + require(stackOperands && stackOperands->Memory.StackBase && stackOperands->DisplacementOffset == 6, "RSP-based masked store was not recognised"); + for (const auto& bytes : { + Bytes{0xC4, 0xE2, 0x75, 0x2C, 0x07}, Bytes{0xC4, 0xE2, 0x75, 0x2D, 0x07}, Bytes{0xC4, 0xE2, 0x75, 0x8C, 0x07}, + Bytes{0xC4, 0xE2, 0xF5, 0x2E, 0x07}, Bytes{0xC4, 0xE2, 0xF5, 0x2F, 0x07}, Bytes{0xC4, 0xE2, 0x75, 0x2E, 0xC7}, + Bytes{0xC4, 0xE1, 0x75, 0x2E, 0x07}, Bytes{0xC4, 0xE2, 0x74, 0x2E, 0x07}, Bytes{0xC5, 0xF5, 0x2E, 0x07}, + Bytes{0xC4, 0xE2, 0x75, 0x2E}, Bytes{0x66, 0xC4, 0xE2, 0x75, 0x2E, 0x07}}) + require(!Codegen::DecodeMaskedStore(bytes.data(), bytes.size()), "A load, register form, other map or prefix matched a masked store"); + require(rejected(Bytes{0xC4, 0xE2, 0x75, 0x2E, 0x07, 0x00}), "A masked store with trailing bytes was accepted"); + require(rejected(Bytes{0xC4, 0xE2, 0x75, 0x2E, 0x47}), "A truncated masked store was accepted"); +} + +void body() { + const auto site = maskedStore(kForms[1], false, 1, 2, 0, {0x07}); + Codegen::StubBodyBuilder builder; + Codegen::EmitMaskedStore(builder, *Codegen::DecodeMaskedStore(site.data(), site.size())); + const auto lowered = builder.Finish(); + const Bytes expected{ + 0x48, 0x8D, 0x64, 0x24, 0x80, 0x9C, 0x50, 0x51, 0x48, 0x8D, 0x07, 0xC4, 0xE1, 0x79, 0x50, 0xC9, + 0xF6, 0xC1, 0x01, 0x74, 0x0A, 0xF0, 0x80, 0x48, 0x00, 0x00, 0xF0, 0x80, 0x48, 0x07, 0x00, + 0xF6, 0xC1, 0x02, 0x74, 0x0A, 0xF0, 0x80, 0x48, 0x08, 0x00, 0xF0, 0x80, 0x48, 0x0F, 0x00, + 0x59, 0x58, 0x9D, 0x48, 0x8D, 0xA4, 0x24, 0x80, 0x00, 0x00, 0x00, 0xC4, 0xE2, 0x71, 0x2F, 0x17}; + require(Bytes(lowered.Bytes.begin(), lowered.Bytes.begin() + static_cast(lowered.ReturnBranchOffset)) == expected, "VMASKMOVPD stub does not probe its elements before the store"); + const auto segment = maskedStore(kForms[0], true, 9, 2, 0, {0x44, 0x24, 0x08}, {0x64}); + Codegen::StubBodyBuilder segmentBuilder; + Codegen::EmitMaskedStore(segmentBuilder, *Codegen::DecodeMaskedStore(segment.data(), segment.size())); + const auto segmentBody = segmentBuilder.Finish().Bytes; + const Bytes lea{0x64, 0x48, 0x8D, 0x84, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xC4, 0xC1, 0x7C, 0x50, 0xC9}; + require(std::search(segmentBody.begin(), segmentBody.end(), lea.begin(), lea.end()) == segmentBody.begin() + 8, "RSP-based masked store address does not skip the spill frame"); + const Bytes probe{0x64, 0xF0, 0x80, 0x48, 0x1F, 0x00}; + require(std::search(segmentBody.begin(), segmentBody.end(), probe.begin(), probe.end()) != segmentBody.end(), "Masked store probes lost the segment prefix"); + const auto overflow = maskedStore(kForms[0], false, 1, 2, 0, {0x84, 0x24, 0xFF, 0xFF, 0xFF, 0x7F}); + bool thrown = false; + try { + Codegen::StubBodyBuilder overflowBuilder; + Codegen::EmitMaskedStore(overflowBuilder, *Codegen::DecodeMaskedStore(overflow.data(), overflow.size())); + } catch (const Codegen::CodegenException&) { + thrown = true; + } + require(thrown, "An overflowing stack displacement was silently wrapped"); +} + +void matching() { + const auto intel = Codegen::MakeAmd64OnlyInstructionMatcher(); + const auto rosetta = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta); + for (const auto& form : kForms) { + const auto bytes = maskedStore(form, true, 1, 0, 0, {0x00}); + require(!intel->Match(bytes.data(), bytes.size()), "Intel mode lowered a masked store"); + const auto match = rosetta->Match(bytes.data(), bytes.size()); + require(match && match->InstructionName == form.Name && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline && !match->Optional, "Rosetta mode did not require a masked store trampoline"); + const std::array, 2> sequence{bytes, bytes}; + require(rosetta->MatchSequence(sequence, {}).has_value(), "A masked store sequence could not move out of line"); + } + const Bytes load{0xC4, 0xE2, 0x75, 0x2C, 0x00}; + require(!rosetta->Match(load.data(), load.size()), "Rosetta mode lowered a masked load"); +} + +#if (defined(__linux__) || defined(__APPLE__)) && defined(__x86_64__) + +constexpr std::size_t kPage = 4096; +constexpr std::size_t kDataPages = 16; +constexpr std::uint8_t kFill = 0xA5; +constexpr std::uint8_t kSentinel = 0x5A; + +std::uint8_t* protectedPage = nullptr; +volatile sig_atomic_t faults = 0; + +void onFault(int number, siginfo_t* info, void*) { + const auto* address = static_cast(info->si_addr); + if (address < protectedPage || address >= protectedPage + kPage) { + signal(number, SIG_DFL); + return; + } + faults = faults + 1; + mprotect(protectedPage, kPage, PROT_READ | PROT_WRITE); +} + +struct alignas(32) Machine { + std::uint8_t Ymm[16][32]; + std::uint64_t Gpr[7]; + std::uint64_t Flags; + std::uint64_t Stack; + std::uint64_t Code; +}; + +static_assert(offsetof(Machine, Gpr) == 512 && offsetof(Machine, Flags) == 568 && offsetof(Machine, Stack) == 576 && offsetof(Machine, Code) == 584); + +void run(Machine& machine) { + asm volatile( + "mov %%rsp, %%r12\n\t" + "mov %[machine], %%r13\n\t" + "vmovdqu 0(%%r13), %%ymm0\n\t" + "vmovdqu 32(%%r13), %%ymm1\n\t" + "vmovdqu 64(%%r13), %%ymm2\n\t" + "vmovdqu 96(%%r13), %%ymm3\n\t" + "vmovdqu 128(%%r13), %%ymm4\n\t" + "vmovdqu 160(%%r13), %%ymm5\n\t" + "vmovdqu 192(%%r13), %%ymm6\n\t" + "vmovdqu 224(%%r13), %%ymm7\n\t" + "vmovdqu 256(%%r13), %%ymm8\n\t" + "vmovdqu 288(%%r13), %%ymm9\n\t" + "vmovdqu 320(%%r13), %%ymm10\n\t" + "vmovdqu 352(%%r13), %%ymm11\n\t" + "vmovdqu 384(%%r13), %%ymm12\n\t" + "vmovdqu 416(%%r13), %%ymm13\n\t" + "vmovdqu 448(%%r13), %%ymm14\n\t" + "vmovdqu 480(%%r13), %%ymm15\n\t" + "mov 512(%%r13), %%rax\n\t" + "mov 520(%%r13), %%rcx\n\t" + "mov 528(%%r13), %%rbx\n\t" + "mov 536(%%r13), %%rsi\n\t" + "mov 544(%%r13), %%rdi\n\t" + "mov 552(%%r13), %%r9\n\t" + "mov 560(%%r13), %%r10\n\t" + "mov 576(%%r13), %%rsp\n\t" + "pushq 568(%%r13)\n\t" + "popfq\n\t" + "call *584(%%r13)\n\t" + "pushfq\n\t" + "popq 568(%%r13)\n\t" + "mov %%rsp, 576(%%r13)\n\t" + "mov %%r12, %%rsp\n\t" + "mov %%rax, 512(%%r13)\n\t" + "mov %%rcx, 520(%%r13)\n\t" + "mov %%rbx, 528(%%r13)\n\t" + "mov %%rsi, 536(%%r13)\n\t" + "mov %%rdi, 544(%%r13)\n\t" + "mov %%r9, 552(%%r13)\n\t" + "mov %%r10, 560(%%r13)\n\t" + "vmovdqu %%ymm0, 0(%%r13)\n\t" + "vmovdqu %%ymm1, 32(%%r13)\n\t" + "vmovdqu %%ymm2, 64(%%r13)\n\t" + "vmovdqu %%ymm3, 96(%%r13)\n\t" + "vmovdqu %%ymm4, 128(%%r13)\n\t" + "vmovdqu %%ymm5, 160(%%r13)\n\t" + "vmovdqu %%ymm6, 192(%%r13)\n\t" + "vmovdqu %%ymm7, 224(%%r13)\n\t" + "vmovdqu %%ymm8, 256(%%r13)\n\t" + "vmovdqu %%ymm9, 288(%%r13)\n\t" + "vmovdqu %%ymm10, 320(%%r13)\n\t" + "vmovdqu %%ymm11, 352(%%r13)\n\t" + "vmovdqu %%ymm12, 384(%%r13)\n\t" + "vmovdqu %%ymm13, 416(%%r13)\n\t" + "vmovdqu %%ymm14, 448(%%r13)\n\t" + "vmovdqu %%ymm15, 480(%%r13)\n\t" + "vzeroupper\n\t" + : + : [machine] "r"(&machine) + : "rax", "rcx", "rbx", "rsi", "rdi", "r9", "r10", "r12", "r13", + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", + "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", "memory", "cc"); +} + +struct Addressing { + Bytes Memory; + std::uint8_t RexIndexBase; + std::uint8_t Mask; + std::uint8_t Source; +}; + +enum Register { Rax, Rcx, Rbx, Rsi, Rdi, R9, R10 }; + +const std::array kAddressings{ + Addressing{{0x07}, 0, 1, 2}, + Addressing{{0x44, 0x5E, 0x7F}, 0, 9, 14}, + Addressing{{0x84, 0x88, 0x00, 0xF0, 0xFF, 0xFF}, 0, 15, 0}, + Addressing{{0x04, 0xD1}, 3, 3, 8}, + Addressing{{0x44, 0x24, 0x40}, 0, 12, 5}, + Addressing{{0x05, 0x00, 0x00, 0x00, 0x00}, 0, 6, 11}, +}; + +void execution() { + const auto region = static_cast(mmap(nullptr, (kDataPages + 2) * kPage, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + require(region != MAP_FAILED, "Cannot map masked store test pages"); + auto* const code = region; + auto* const data = region + kPage; + protectedPage = data + kDataPages * kPage; + struct sigaction action {}; + action.sa_sigaction = onFault; + action.sa_flags = SA_SIGINFO; + sigemptyset(&action.sa_mask); + struct sigaction previousSegv {}; + struct sigaction previousBus {}; + sigaction(SIGSEGV, &action, &previousSegv); + sigaction(SIGBUS, &action, &previousBus); + const auto matcher = Codegen::MakeAmd64OnlyInstructionMatcher(Codegen::Amd64OnlyTarget::Rosetta); + const std::array patterns{0xFF, 0x00, 0x01, 0x80, 0x55, 0xFE}; + std::uint64_t runs = 0; + for (const auto& form : kForms) { + for (const bool wide : {false, true}) { + const auto elements = (wide ? 32u : 16u) / form.ElementSize; + for (std::size_t mode = 0; mode < kAddressings.size(); ++mode) { + const auto& addressing = kAddressings[mode]; + const auto site = maskedStore(form, wide, addressing.Mask, addressing.Source, addressing.RexIndexBase, addressing.Memory); + const auto match = matcher->Match(site.data(), site.size()); + require(match && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline, "Masked store stub was not produced"); + for (const int offset : {-40, -16, -4, -2, 0, 8}) { + auto* const target = protectedPage + offset; + const bool stackBased = mode == 4; + auto* const stack = stackBased ? target + 8 - 0x40 : data + kDataPages / 2 * kPage; + auto body = match->StubBody; + const auto siteAddress = mode == 5 ? reinterpret_cast(target) - site.size() : reinterpret_cast(code); + Codegen::ApplyStubRelocations(body, match->Relocations, siteAddress, reinterpret_cast(code), 0); + body.at(match->ReturnBranchOffset) = 0xC3; + require(body.size() <= kPage, "Masked store stub exceeds its code page"); + std::memcpy(code, body.data(), body.size()); + for (const auto pattern : patterns) { + Machine machine{}; + for (unsigned reg = 0; reg < 16; ++reg) + for (unsigned byte = 0; byte < 32; ++byte) + machine.Ymm[reg][byte] = static_cast(reg * 37 + byte * 11 + 1); + for (unsigned element = 0; element < 32u / form.ElementSize; ++element) { + const bool active = element >= elements || (pattern >> element & 1) != 0; + auto* lane = machine.Ymm[addressing.Mask] + element * form.ElementSize; + std::memset(lane, active ? 0x00 : 0xFF, form.ElementSize); + lane[0] = 0x01; + lane[form.ElementSize - 1] = active ? 0x80 : 0x7F; + } + machine.Gpr[Rax] = 0x1111111111111111ull; + machine.Gpr[Rcx] = 0x2222222222222222ull; + machine.Gpr[Rbx] = 8; + machine.Gpr[Rsi] = reinterpret_cast(target) - 0x7F - 16; + machine.Gpr[Rdi] = reinterpret_cast(target); + machine.Gpr[R9] = reinterpret_cast(target) - 5 * 8; + machine.Gpr[R10] = 5; + if (mode == 2) { + machine.Gpr[Rcx] = 3; + machine.Gpr[Rax] = reinterpret_cast(target) + 0x1000 - 12; + } + machine.Flags = (runs & 1) != 0 ? 0x8D7 : 0x002; + machine.Stack = reinterpret_cast(stack); + machine.Code = reinterpret_cast(code); + const auto input = machine; + require(mprotect(protectedPage, kPage, PROT_READ | PROT_WRITE) == 0, "Cannot unprotect the watched page"); + std::memset(target - 16, kFill, 64); + std::memset(stack - 8 - 128, kSentinel, 128); + require(mprotect(protectedPage, kPage, PROT_READ) == 0, "Cannot protect the watched page"); + faults = 0; + run(machine); + ++runs; + bool touchesProtected = false; + for (unsigned element = 0; element < elements; ++element) { + const bool active = (pattern >> element & 1) != 0; + for (unsigned byte = 0; byte < form.ElementSize; ++byte) { + const auto at = element * form.ElementSize + byte; + require(target[at] == (active ? input.Ymm[addressing.Source][at] : kFill), "Masked store wrote an inactive element or lost an active one"); + touchesProtected = touchesProtected || (active && target + at >= protectedPage); + } + } + for (int at = -16; at < 0; ++at) + require(target[at] == kFill, "Masked store wrote before its operand"); + for (unsigned at = elements * form.ElementSize; at < 48; ++at) + require(target[at] == kFill, "Masked store wrote past its operand"); + require(faults == (touchesProtected ? 1 : 0), "Masked store stub did not fault exactly once on the protected page"); + for (unsigned at = 0; at < 128; ++at) + 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"); + require(machine.Stack == input.Stack, "Masked store stub did not restore RSP"); + } + } + } + } + } + sigaction(SIGSEGV, &previousSegv, nullptr); + sigaction(SIGBUS, &previousBus, nullptr); + munmap(region, (kDataPages + 2) * kPage); + std::cout << runs << " masked store executions\n"; +} +#else +void execution() { std::cout << "Masked store execution checks require a Linux/macOS x86-64 build\n"; } +#endif + +} + +int main() { + try { + decoding(); + body(); + matching(); + execution(); + std::cout << "Masked store lowering checks passed\n"; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/relinker/codegen/tests/RedZoneFramerExecutionTests.cpp b/core/relinker/codegen/tests/RedZoneFramerExecutionTests.cpp new file mode 100644 index 0000000000..7d940524b9 --- /dev/null +++ b/core/relinker/codegen/tests/RedZoneFramerExecutionTests.cpp @@ -0,0 +1,286 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__linux__) && defined(__x86_64__) +#include +#include +#include +#endif + +#if defined(__linux__) && defined(__x86_64__) + +namespace { + +void require(const bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +constexpr std::size_t kArenaBytes = 0x10000; +constexpr std::size_t kStubBase = 0x4000; +constexpr std::size_t kRunnerBase = 0xc000; +constexpr std::uint8_t kClobber = 0xa5; +constexpr std::uint64_t kA = 0x1111, kB = 0x2222, kC = 0x3333, kD = 0x4444; + +std::uint8_t* arena = nullptr; +volatile sig_atomic_t steps = 0; + +void clobberBelowRsp(int, siginfo_t*, void* context) { + auto& registers = static_cast(context)->uc_mcontext.gregs; + const auto rip = static_cast(registers[REG_RIP]); + const auto base = reinterpret_cast(arena); + if (rip < base || rip >= base + kArenaBytes) { + registers[REG_EFL] &= ~static_cast(0x100); + return; + } + std::memset(reinterpret_cast(static_cast(registers[REG_RSP]) - 0x80), kClobber, 0x80); + steps = steps + 1; +} + +const std::vector Runner{ + 0x53, 0x4c, 0x89, 0xc3, 0x48, 0x89, 0xf8, 0x48, 0x89, 0xf7, 0x48, 0x89, 0xd6, 0x48, 0x89, 0xca, + 0x4d, 0x85, 0xc9, 0x74, 0x0a, 0x9c, 0x48, 0x81, 0x0c, 0x24, 0x00, 0x01, 0x00, 0x00, 0x9d, + 0xff, 0xd0, 0x9c, 0x48, 0x81, 0x24, 0x24, 0xff, 0xfe, 0xff, 0xff, 0x9d, 0x5b, 0xc3, +}; + +struct Leaf { + const char* Name; + std::vector Code; + std::uint64_t Expected; +}; + +const std::vector FramelessPushes{0x53, 0x41, 0x56, 0x41, 0x57}; +const std::vector FramelessPops{0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xc3}; + +std::vector frameless(const std::vector& body) { + std::vector bytes = FramelessPushes; + bytes.insert(bytes.end(), body.begin(), body.end()); + bytes.insert(bytes.end(), FramelessPops.begin(), FramelessPops.end()); + return bytes; +} + +std::vector framedLeaves() { + return { + {"frameless leaf spilling 0x10 below rsp", frameless({ + 0x48, 0x89, 0x7c, 0x24, 0xf0, 0x48, 0x89, 0x74, 0x24, 0xf8, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00, + 0x48, 0x03, 0x44, 0x24, 0xf0, 0x48, 0x03, 0x44, 0x24, 0xf8, 0xff, 0xc9, 0x75, 0xf2, + }), 8 * (kA + kB)}, + {"leaf reaching 0x28 below rsp", frameless({ + 0x48, 0x89, 0x7c, 0x24, 0xd8, 0x48, 0x89, 0x74, 0x24, 0xe8, 0x48, 0x89, 0x54, 0x24, 0xf0, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00, + 0x48, 0x03, 0x44, 0x24, 0xd8, 0x48, 0x03, 0x44, 0x24, 0xe8, 0x48, 0x03, 0x44, 0x24, 0xf0, 0xff, 0xc9, 0x75, 0xed, + }), 8 * (kA + kB + kC)}, + {"leaf reading its saved rbx at +0x10 moved in place", frameless({ + 0x48, 0x89, 0x7c, 0x24, 0xf0, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00, + 0x48, 0x03, 0x44, 0x24, 0xf0, 0x48, 0x03, 0x44, 0x24, 0x10, 0xff, 0xc9, 0x75, 0xf2, + }), 8 * (kA + kD)}, + {"leaf taking the address of a slot 0x20 below rsp", frameless({ + 0x4c, 0x8d, 0x74, 0x24, 0xe0, 0x49, 0x89, 0x3e, 0x49, 0x89, 0x76, 0x08, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00, + 0x49, 0x03, 0x06, 0x49, 0x03, 0x46, 0x08, 0xff, 0xc9, 0x75, 0xf5, + }), 8 * (kA + kB)}, + {"inflate_fast-shaped leaf allocating 0x40 after six pushes", { + 0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x40, + 0x48, 0x89, 0x7c, 0x24, 0xe0, 0x48, 0x89, 0x7c, 0x24, 0x00, 0x48, 0x89, 0x74, 0x24, 0x08, 0x48, 0x89, 0x54, 0x24, 0x38, + 0xb9, 0x04, 0x00, 0x00, 0x00, 0x31, 0xc0, 0x48, 0x03, 0x44, 0x24, 0x08, 0x48, 0x03, 0x44, 0x24, 0xe0, 0x48, 0x03, 0x44, 0x24, 0x00, + 0xff, 0xc9, 0x75, 0xed, 0x48, 0x8b, 0x54, 0x24, 0x38, + 0x48, 0x83, 0xc4, 0x40, 0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3, + }, 4 * (kB + 2 * kA)}, + }; +} + +std::vector stubbedLeaves() { + return { + {"inflate_fast-shaped leaf with operands at rsp and at 0x60 shifted by 0x20", { + 0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x70, + 0x48, 0x89, 0x7c, 0x24, 0xe0, 0x48, 0x89, 0x34, 0x24, 0x48, 0x89, 0x54, 0x24, 0x60, 0xb9, 0x04, 0x00, 0x00, 0x00, 0x31, 0xc0, + 0x48, 0x03, 0x44, 0x24, 0xe0, 0x48, 0x03, 0x04, 0x24, 0x48, 0x03, 0x44, 0x24, 0x60, 0xff, 0xc9, 0x75, 0xee, + 0x48, 0x83, 0xc4, 0x70, 0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3, + }, 4 * (kA + kB + kC)}, + {"leaf shifted by the whole red zone reading its saved rbx at +0x8", { + 0x41, 0x57, 0x53, 0x41, 0x56, 0x4c, 0x8d, 0x74, 0x24, 0xe0, 0x49, 0x89, 0x3e, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00, + 0x49, 0x03, 0x06, 0x48, 0x03, 0x44, 0x24, 0x08, 0xff, 0xc9, 0x75, 0xf4, 0x41, 0x5e, 0x5b, 0x41, 0x5f, 0xc3, + }, 8 * (kA + kD)}, + }; +} + +std::vector backwardStubbedLeaves() { + return { + {"inflate_fast-shaped leaf comparing at rsp between an add and a jne", { + 0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x40, + 0x48, 0x89, 0x7c, 0x24, 0xe0, 0x48, 0x89, 0x7c, 0x24, 0x00, 0xb9, 0x04, 0x00, 0x00, 0x00, 0x31, 0xc0, + 0x48, 0x03, 0x44, 0x24, 0xe0, 0x49, 0x01, 0xf8, 0x48, 0x39, 0x3c, 0x24, 0x75, 0x03, 0x48, 0x01, 0xf0, 0xff, 0xc9, 0x75, 0xeb, + 0x48, 0x83, 0xc4, 0x40, 0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3, + }, 4 * (kA + kB)}, + {"leaf comparing at rsp after a load below rsp that heads its loop", { + 0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x40, + 0x48, 0x89, 0x7c, 0x24, 0xe0, 0x48, 0x89, 0x7c, 0x24, 0x00, 0xb9, 0x04, 0x00, 0x00, 0x00, 0x31, 0xc0, + 0x48, 0x03, 0x44, 0x24, 0xe0, 0x48, 0x39, 0x3c, 0x24, 0x75, 0x03, 0x48, 0x01, 0xf0, 0xff, 0xc9, 0x75, 0xee, + 0x48, 0x83, 0xc4, 0x40, 0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3, + }, 4 * (kA + kB)}, + }; +} + +void writeRel32(std::uint8_t* at, const std::uint64_t target, const std::uint64_t next) { + const auto displacement = static_cast(target) - static_cast(next); + require(displacement >= std::numeric_limits::min() && displacement <= std::numeric_limits::max(), "a stub jump is out of rel32 range"); + const auto value = static_cast(static_cast(displacement)); + for (std::size_t index = 0; index < 4; ++index) at[index] = static_cast(value >> (index * 8)); +} + +std::vector callTo(const std::size_t from, const std::size_t target) { + std::vector bytes{0xe8, 0, 0, 0, 0}; + const auto displacement = static_cast(static_cast(target) - static_cast(from + 5)); + for (std::size_t index = 0; index < 4; ++index) bytes[1 + index] = static_cast(displacement >> (index * 8)); + return bytes; +} + +struct Image { + std::vector Bytes; + std::vector Starts; + std::vector Ends; +}; + +Image place(const std::vector& leaves) { + Image image; + std::size_t at = 0x40; + for (const auto& leaf : leaves) { + image.Starts.push_back(at); + at += (leaf.Code.size() + 15) / 16 * 16 + 16; + } + for (const auto start : image.Starts) { + const auto call = callTo(image.Bytes.size(), start); + image.Bytes.insert(image.Bytes.end(), call.begin(), call.end()); + } + image.Bytes.push_back(0xc3); + for (std::size_t index = 0; index < leaves.size(); ++index) { + image.Bytes.resize(image.Starts[index], 0xcc); + image.Bytes.insert(image.Bytes.end(), leaves[index].Code.begin(), leaves[index].Code.end()); + image.Ends.push_back(image.Bytes.size()); + } + image.Bytes.resize((image.Bytes.size() + 15) / 16 * 16 + 16, 0xcc); + require(image.Bytes.size() <= kStubBase, "the leaves do not fit before the stubs"); + return image; +} + +struct Framing { + Codegen::RedZoneFrameReport Report; + std::vector Sites; + std::vector Displacements; +}; + +Framing load(const Image& image, const bool frame) { + std::fill_n(arena, kArenaBytes, 0xcc); + std::copy(image.Bytes.begin(), image.Bytes.end(), arena); + std::copy(Runner.begin(), Runner.end(), arena + kRunnerBase); + Framing framing; + if (!frame) return framing; + const auto base = reinterpret_cast(arena); + const std::vector segments{{1, 5, 0, base, base, image.Bytes.size(), image.Bytes.size(), 16}}; + std::vector functions; + for (std::size_t index = 0; index < image.Starts.size(); ++index) functions.push_back({base + image.Starts[index], base + image.Ends[index]}); + framing.Sites = Codegen::FrameRedZoneFunctions(image.Bytes, segments, functions, {}, framing.Report, framing.Displacements); + for (const auto& moved : framing.Displacements) std::copy(moved.Bytes.begin(), moved.Bytes.end(), arena + moved.Offset); + std::size_t cursor = kStubBase; + for (const auto& site : framing.Sites) { + const auto at = static_cast(site.Address - base); + require(std::equal(site.OriginalBytes.begin(), site.OriginalBytes.end(), arena + at), "a framed site's bytes changed before it was patched"); + cursor = (cursor + 15) / 16 * 16; + require(cursor + site.Body.size() <= kRunnerBase, "the stubs do not fit before the runner"); + auto* stub = arena + cursor; + std::copy(site.Body.begin(), site.Body.end(), stub); + Codegen::ApplyStubRelocations(std::span(stub, site.ReturnBranchOffset), site.Relocations, site.Address, base + cursor, site.Offset); + writeRel32(stub + site.ReturnBranchOffset + 1, site.Address + site.Length, base + cursor + site.ReturnBranchOffset + 5); + std::fill_n(arena + at, site.Length, 0x90); + arena[at] = 0xe9; + writeRel32(arena + at + 1, base + cursor, site.Address + 5); + cursor += site.Body.size(); + } + return framing; +} + +struct Run { + std::uint64_t Result; + int Steps; +}; + +Run run(const std::size_t start, const bool clobber) { + using Entry = std::uint64_t (*)(std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t); + const auto entry = reinterpret_cast(arena + kRunnerBase); + steps = 0; + const auto result = entry(reinterpret_cast(arena + start), kA, kB, kC, kD, clobber ? 1 : 0); + return {result, static_cast(steps)}; +} + +std::string hex(const std::uint64_t value) { + std::ostringstream text; + text << "0x" << std::hex << value; + return text.str(); +} + +Codegen::RedZoneFrameReport runLeaves(const std::vector& leaves) { + const auto image = place(leaves); + load(image, false); + std::vector plain, clobbered; + for (std::size_t index = 0; index < leaves.size(); ++index) { + plain.push_back(run(image.Starts[index], false)); + clobbered.push_back(run(image.Starts[index], true)); + require(plain.back().Result == leaves[index].Expected, std::string(leaves[index].Name) + ": the unframed leaf returned " + hex(plain.back().Result) + " without the clobber, not " + hex(leaves[index].Expected)); + require(clobbered.back().Steps > 0, std::string(leaves[index].Name) + ": the clobber did not run"); + require(clobbered.back().Result != leaves[index].Expected, std::string(leaves[index].Name) + ": the unframed leaf survived writes below rsp, so the case proves nothing"); + } + const auto framing = load(image, true); + require(framing.Report.Framed == leaves.size(), "only " + std::to_string(framing.Report.Framed) + " of " + std::to_string(leaves.size()) + " leaves were framed"); + for (std::size_t index = 0; index < leaves.size(); ++index) { + const auto calm = run(image.Starts[index], false); + const auto framed = run(image.Starts[index], true); + require(calm.Result == leaves[index].Expected, std::string(leaves[index].Name) + ": the framed leaf returned " + hex(calm.Result) + " without the clobber, not " + hex(leaves[index].Expected)); + require(framed.Steps > 0, std::string(leaves[index].Name) + ": the clobber did not run over the framed leaf"); + require(framed.Result == leaves[index].Expected, std::string(leaves[index].Name) + ": the framed leaf returned " + hex(framed.Result) + " under writes below rsp, not " + hex(leaves[index].Expected)); + std::cout << leaves[index].Name << ": unframed " << hex(plain[index].Result) << " alone and " << hex(clobbered[index].Result) << " under the clobber (" << clobbered[index].Steps << " steps); framed " << hex(framed.Result) << " under the clobber (" << framed.Steps << " steps)\n"; + } + return framing.Report; +} + +} + +int main() { + try { + struct sigaction action {}; + action.sa_sigaction = clobberBelowRsp; + action.sa_flags = SA_SIGINFO; + sigemptyset(&action.sa_mask); + require(sigaction(SIGTRAP, &action, nullptr) == 0, "cannot install the SIGTRAP handler"); + void* mapped = mmap(nullptr, kArenaBytes, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped == MAP_FAILED) { + std::cout << "skipped: no writable and executable memory\n"; + return 77; + } + arena = static_cast(mapped); + runLeaves(framedLeaves()); + const auto stubbed = runLeaves(stubbedLeaves()); + require(stubbed.OperandStubs == 3 && stubbed.StubbedOperands == 5, "the leaves were framed with " + std::to_string(stubbed.OperandStubs) + " operand stubs carrying " + std::to_string(stubbed.StubbedOperands) + " rsp operands, not 3 carrying 5"); + const auto backward = runLeaves(backwardStubbedLeaves()); + require(backward.OperandStubs == 2 && backward.StubbedOperands == 3 && backward.MovedOperands == 5, "the leaves comparing at rsp before a branch were framed with " + std::to_string(backward.OperandStubs) + " operand stubs carrying " + std::to_string(backward.StubbedOperands) + " rsp operands and " + std::to_string(backward.MovedOperands) + " moved in place, not 2 carrying 3 and 5"); + std::cout << "red-zone framer execution tests passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << "red-zone framer execution tests failed: " << error.what() << '\n'; + return 1; + } +} + +#else + +int main() { + std::cout << "skipped: the red-zone framer execution tests single-step x86-64 code on Linux\n"; + return 77; +} + +#endif diff --git a/core/relinker/codegen/tests/RedZoneFramerTests.cpp b/core/relinker/codegen/tests/RedZoneFramerTests.cpp new file mode 100644 index 0000000000..31466fcfdb --- /dev/null +++ b/core/relinker/codegen/tests/RedZoneFramerTests.cpp @@ -0,0 +1,625 @@ +#include +#include +#include +#include +#include +#include + +namespace { + +void require(const bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +constexpr std::uint64_t Base = 0x1000; + +std::vector padTo(std::vector bytes, const std::size_t size) { + bytes.resize(size, 0xcc); + return bytes; +} + +void append(std::vector& bytes, const std::vector& more) { + bytes.insert(bytes.end(), more.begin(), more.end()); +} + +std::vector callTo(const std::size_t from, const std::size_t target) { + const auto displacement = static_cast(target) - static_cast(from + 5); + return {0xe8, static_cast(displacement), static_cast(displacement >> 8), static_cast(displacement >> 16), static_cast(displacement >> 24)}; +} + +const std::vector Prologue{0x55, 0x48, 0x89, 0xe5, 0x41, 0x57, 0x53}; +const std::vector Exit{0x5b, 0x41, 0x5f, 0x5d, 0xc3}; +const std::vector StoreBelowPushes{0x48, 0x89, 0x7d, 0xe8}; +const std::vector LoadBelowPushes{0x48, 0x8b, 0x45, 0xe8}; +const std::vector LoadAbovePushes{0x48, 0x8b, 0x45, 0x08}; + +std::vector leaf(const std::vector& body) { + std::vector bytes = Prologue; + append(bytes, body); + append(bytes, Exit); + return bytes; +} + +struct Image { + std::vector bytes; + std::vector segments; + std::vector functions; +}; + +Image image(const std::vector>& functions) { + Image result; + std::vector starts; + std::size_t at = 0x40; + for (const auto& function : functions) { + starts.push_back(at); + at += (function.size() + 15) / 16 * 16 + 16; + } + std::vector caller; + for (const auto start : starts) append(caller, callTo(caller.size(), start)); + caller.push_back(0xc3); + result.bytes = padTo(caller, 0x40); + for (std::size_t index = 0; index < functions.size(); ++index) { + result.bytes = padTo(result.bytes, starts[index]); + append(result.bytes, functions[index]); + result.functions.push_back({Base + starts[index], Base + starts[index] + functions[index].size()}); + } + result.bytes = padTo(result.bytes, (result.bytes.size() + 15) / 16 * 16 + 16); + result.segments.push_back({1, 5, 0, Base, Base, result.bytes.size(), result.bytes.size(), 16}); + return result; +} + +void framesRedZoneLeaf() { + std::vector body = StoreBelowPushes; + append(body, LoadBelowPushes); + const auto input = image({leaf(body)}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.RedZoneFunctions == 1 && report.Framed == 1 && report.Exits == 1, "a red-zone leaf was not framed"); + require(sites.size() == 2, "a framed leaf needs one prologue and one exit site"); + require(displacements.empty() && report.FramedWithoutFramePointer == 0, "a leaf with a frame pointer had rsp operands moved"); + + const auto& entry = sites[0]; + require(entry.Address == Base + 0x40 && entry.Length == Prologue.size(), "the prologue site does not cover the pushes"); + require(entry.OriginalBytes == Prologue, "the prologue site lost its bytes"); + std::vector entryBody = Prologue; + append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0x80, 0xe9, 0, 0, 0, 0}); + require(entry.Body == entryBody && entry.ReturnBranchOffset == Prologue.size() + 5, "the prologue stub does not lower rsp after the pushes"); + + const auto& exit = sites[1]; + require(exit.Address == Base + 0x40 + Prologue.size() + body.size() && exit.Length == Exit.size(), "the exit site does not cover the pops and ret"); + std::vector exitBody{0x48, 0x8d, 0xa4, 0x24, 0x80, 0, 0, 0}; + append(exitBody, Exit); + append(exitBody, {0xe9, 0, 0, 0, 0}); + require(exit.Body == exitBody && exit.ReturnBranchOffset == 8 + Exit.size(), "the exit stub does not raise rsp before the pops"); +} + +const std::vector FramelessPrologue{0x53, 0x41, 0x56, 0x41, 0x57}; +const std::vector FramelessExit{0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xc3}; +const std::vector StoreBelowRsp{0x48, 0x89, 0x7c, 0x24, 0xf0}; +const std::vector LoadBelowRsp{0x48, 0x8b, 0x44, 0x24, 0xf0}; +const std::vector LoadAboveRsp{0x48, 0x8b, 0x44, 0x24, 0x08}; +const std::vector LoadFarAboveRsp{0x48, 0x8b, 0x44, 0x24, 0x78}; +const std::vector LoadAtRsp{0x48, 0x8b, 0x04, 0x24}; +const std::vector StoreDeepBelowRsp{0x48, 0x89, 0x74, 0x24, 0xd8}; +const std::vector LoadDeepBelowRsp{0x48, 0x8b, 0x44, 0x24, 0xd8}; +const std::vector AddressBelowRsp{0x48, 0x8d, 0x44, 0x24, 0xe0}; +const std::vector AddressAboveRsp{0x48, 0x8d, 0x44, 0x24, 0x08}; +const std::vector StoreIndexedBelowRsp{0x48, 0x89, 0x7c, 0xcc, 0xf0}; +const std::vector StoreImmediateFarAboveRsp{0xc7, 0x44, 0x24, 0x7c, 0x78, 0x56, 0x34, 0x12}; +const std::vector LoadIndexedAtRsp{0x48, 0x8b, 0x04, 0xcc}; +const std::vector AddRdxToRax{0x48, 0x01, 0xd0}; +const std::vector VexStoreAtRsp{0xc5, 0xf8, 0x11, 0x04, 0x24}; +const std::vector EvexStoreBelowRsp{0x62, 0xf1, 0x7c, 0x48, 0x11, 0x44, 0x24, 0xff}; +const std::vector LoadRipRelative{0x48, 0x8b, 0x05, 0x00, 0x00, 0x00, 0x00}; + +std::vector stubBody(std::vector code) { + append(code, {0xe9, 0, 0, 0, 0}); + return code; +} + +std::vector framelessLeaf(const std::vector& prologue, const std::vector& body, const std::vector& exit) { + std::vector bytes = prologue; + append(bytes, body); + append(bytes, exit); + return bytes; +} + +void framesLeafWithoutFramePointer() { + std::vector body = StoreBelowRsp; + append(body, LoadBelowRsp); + 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.RedZoneFunctions == 1 && report.Framed == 1 && report.FramedWithoutFramePointer == 1 && report.MovedOperands == 2, "a leaf that only pushes was not framed"); + require(sites.size() == 2, "a framed leaf needs one prologue and one exit site"); + const auto& entry = sites[0]; + require(entry.Address == Base + 0x40 && entry.Length == FramelessPrologue.size(), "the prologue site does not cover the pushes"); + std::vector entryBody = FramelessPrologue; + append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0xf0, 0xe9, 0, 0, 0, 0}); + require(entry.Body == entryBody, "the prologue stub does not lower rsp by the leaf's depth below rsp after the pushes"); + const auto& exit = sites[1]; + require(exit.Address == Base + 0x40 + FramelessPrologue.size() + body.size() && exit.OriginalBytes == FramelessExit, "the exit site does not cover the pops and ret"); + std::vector exitBody{0x48, 0x8d, 0xa4, 0x24, 0x10, 0, 0, 0}; + append(exitBody, FramelessExit); + append(exitBody, {0xe9, 0, 0, 0, 0}); + require(exit.Body == exitBody, "the exit stub does not raise rsp by the leaf's depth below rsp before the pops"); + const auto storeDisplacement = 0x40 + FramelessPrologue.size() + 4; + const auto loadDisplacement = storeDisplacement + StoreBelowRsp.size(); + require(displacements.size() == 2, "both rsp operands must move"); + require(displacements[0].Offset == storeDisplacement && displacements[0].Bytes == std::vector{0x00}, "the store below rsp was not moved to the lowered rsp"); + require(displacements[1].Offset == loadDisplacement && displacements[1].Bytes == std::vector{0x00}, "the load below rsp was not moved to the lowered rsp"); +} + +void shiftsByTheDeepestOperandBelowRsp() { + std::vector body = StoreBelowRsp; + append(body, StoreDeepBelowRsp); + append(body, LoadDeepBelowRsp); + 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 && displacements.size() == 3, "a leaf reaching 0x28 bytes below rsp was not framed"); + std::vector entryBody = FramelessPrologue; + append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0xd0, 0xe9, 0, 0, 0, 0}); + require(sites[0].Body == entryBody, "rsp is not lowered by the deepest displacement rounded up to 16"); + require(std::vector(sites[1].Body.begin(), sites[1].Body.begin() + 8) == std::vector{0x48, 0x8d, 0xa4, 0x24, 0x30, 0, 0, 0}, "rsp is not raised by the shift it was lowered by"); + require(displacements[0].Bytes == std::vector{0x20} && displacements[1].Bytes == std::vector{0x08} && displacements[2].Bytes == std::vector{0x08}, "the operands below rsp were not moved by the shift"); +} + +void movesPositiveOperandsThatFitAfterTheShift() { + std::vector body = StoreBelowRsp; + append(body, LoadAboveRsp); + 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 && report.MovedOperands == 2 && sites.size() == 2, "a leaf with an 8-bit displacement above rsp that fits after the shift was not framed"); + const auto loadDisplacement = 0x40 + FramelessPrologue.size() + StoreBelowRsp.size() + 4; + require(displacements.size() == 2 && displacements[1].Offset == loadDisplacement && displacements[1].Bytes == std::vector{0x18}, "the load above rsp was not moved in place by the shift"); +} + +void shiftsByTheWholeRedZoneForAddressesBelowRsp() { + std::vector below = StoreBelowRsp; + append(below, AddressBelowRsp); + std::vector above = StoreBelowRsp; + append(above, AddressAboveRsp); + const auto input = image({framelessLeaf(FramelessPrologue, below, FramelessExit), framelessLeaf(FramelessPrologue, above, FramelessExit)}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.Framed == 2 && sites.size() == 4 && displacements.size() == 4, "leaves that take the address of a stack slot were not framed"); + require(sites[0].Body[FramelessPrologue.size() + 4] == 0x80 && displacements[0].Bytes == std::vector{0x70} && displacements[1].Bytes == std::vector{0x60}, "a leaf that takes the address of a slot below rsp was not shifted by the whole red zone"); + require(sites[2].Body[FramelessPrologue.size() + 4] == 0xf0 && displacements[2].Bytes == std::vector{0x00} && displacements[3].Bytes == std::vector{0x18}, "a leaf that only takes the address of a slot above rsp was shifted by more than its depth"); +} + +void shiftsByTheWholeRedZoneForIndexedOperands() { + std::vector body = StoreIndexedBelowRsp; + append(body, LoadBelowRsp); + 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 && displacements.size() == 2, "a leaf with an indexed operand below rsp was not framed"); + require(sites[0].Body[FramelessPrologue.size() + 4] == 0x80 && displacements[0].Bytes == std::vector{0x70} && displacements[1].Bytes == std::vector{0x70}, "a leaf whose indexed operand can reach deeper than its displacement was not shifted by the whole red zone"); +} + +void stubsOperandsWhoseDisplacementOverflows() { + std::vector body = StoreBelowRsp; + append(body, LoadFarAboveRsp); + append(body, StoreImmediateFarAboveRsp); + 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 && report.MovedOperands == 1 && report.OperandStubs == 2 && report.StubbedOperands == 2 && sites.size() == 4, "a leaf whose 8-bit displacements above rsp overflow after the shift was not framed with operand stubs"); + const auto load = Base + 0x40 + FramelessPrologue.size() + StoreBelowRsp.size(); + require(sites[2].Address == load && sites[2].Length == LoadFarAboveRsp.size() && sites[2].OriginalBytes == LoadFarAboveRsp, "the stub site does not cover the load that cannot move in place"); + require(sites[2].Body == stubBody({0x48, 0x8b, 0x84, 0x24, 0x88, 0x00, 0x00, 0x00}) && sites[2].ReturnBranchOffset == 8, "the load was not re-encoded with a 32-bit displacement moved by the shift"); + require(sites[3].Address == load + LoadFarAboveRsp.size() && sites[3].OriginalBytes == StoreImmediateFarAboveRsp, "the stub site does not cover the store of an immediate that cannot move in place"); + require(sites[3].Body == stubBody({0xc7, 0x84, 0x24, 0x8c, 0x00, 0x00, 0x00, 0x78, 0x56, 0x34, 0x12}), "the store lost its immediate when its displacement was widened"); + require(displacements.size() == 1 && displacements[0].Bytes == std::vector{0x00}, "the store below rsp was not moved in place"); +} + +void stubsOperandsAtRspWithTheInstructionsAfterThem() { + std::vector atRsp = StoreBelowRsp; + append(atRsp, LoadAtRsp); + append(atRsp, LoadAboveRsp); + std::vector indexed = StoreBelowRsp; + append(indexed, LoadIndexedAtRsp); + append(indexed, AddRdxToRax); + const auto input = image({framelessLeaf(FramelessPrologue, atRsp, FramelessExit), framelessLeaf(FramelessPrologue, indexed, FramelessExit)}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.Framed == 2 && report.MovedOperands == 2 && report.OperandStubs == 2 && report.StubbedOperands == 3 && sites.size() == 6, "leaves with operands at rsp were not framed with operand stubs"); + const auto first = 0x40 + FramelessPrologue.size() + StoreBelowRsp.size(); + require(sites[2].Address == Base + first && sites[2].Length == LoadAtRsp.size() + LoadAboveRsp.size(), "the stub site does not run on to the next instruction to make room for its jump"); + require(sites[2].Body == stubBody({0x48, 0x8b, 0x44, 0x24, 0x10, 0x48, 0x8b, 0x44, 0x24, 0x18}), "the load at rsp and the load the stub covers after it were not both moved by the shift"); + for (const auto& displacement : displacements) require(displacement.Offset < first || displacement.Offset >= first + sites[2].Length, "an rsp operand inside a stub site was also moved in place"); + require(sites[3].Body[FramelessPrologue.size() + 4] == 0x80, "a leaf with an indexed operand at rsp was not shifted by the whole red zone"); + require(sites[5].OriginalBytes.size() == LoadIndexedAtRsp.size() + AddRdxToRax.size() && sites[5].Body == stubBody({0x48, 0x8b, 0x84, 0xcc, 0x80, 0x00, 0x00, 0x00, 0x48, 0x01, 0xd0}), "the indexed load at rsp was not re-encoded with a 32-bit displacement of 0x80 ahead of the add it covers"); +} + +void stubsVexOperandsAtRsp() { + std::vector body = StoreBelowRsp; + append(body, VexStoreAtRsp); + 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 && report.OperandStubs == 1 && report.StubbedOperands == 1 && sites.size() == 3, "a leaf with a VEX store at rsp was not framed with an operand stub"); + require(sites[2].OriginalBytes == VexStoreAtRsp && sites[2].Body == stubBody({0xc5, 0xf8, 0x11, 0x44, 0x24, 0x10}), "the VEX store at rsp was not re-encoded with an 8-bit displacement moved by the shift"); +} + +void stubsOperandsAtRspWithTheInstructionsBeforeThem() { + std::vector body = StoreBelowRsp; + append(body, LoadAtRsp); + append(body, LoadRipRelative); + 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 && report.MovedOperands == 0 && report.OperandStubs == 1 && report.StubbedOperands == 2 && sites.size() == 3 && displacements.empty(), "a leaf whose short operand at rsp sits before a rip-relative instruction was not framed with a stub over the instruction before it"); + const auto store = Base + 0x40 + FramelessPrologue.size(); + require(sites[2].Address == store && sites[2].Length == StoreBelowRsp.size() + LoadAtRsp.size(), "the stub site does not start at the store before the operand at rsp"); + require(sites[2].Body == stubBody({0x48, 0x89, 0x7c, 0x24, 0x00, 0x48, 0x8b, 0x44, 0x24, 0x10}), "the store before the operand and the operand at rsp were not both moved by the shift"); +} + +void skipsLeavesWithOperandsThatCannotMove() { + std::vector evex = StoreBelowRsp; + append(evex, EvexStoreBelowRsp); + std::vector beforeExit = StoreBelowRsp; + append(beforeExit, LoadAtRsp); + std::vector beforeTarget = StoreBelowRsp; + append(beforeTarget, LoadAtRsp); + append(beforeTarget, {0xff, 0xc0, 0x3b, 0x44, 0x24, 0xf0, 0x72, 0xf8}); + const auto input = image({framelessLeaf(FramelessPrologue, evex, FramelessExit), framelessLeaf(FramelessPrologue, beforeExit, FramelessExit), framelessLeaf(FramelessPrologue, beforeTarget, FramelessExit), framelessLeaf({0x41, 0x56, 0x41, 0x57}, StoreBelowRsp, {0x41, 0x5f, 0x41, 0x5e, 0xc3})}); + 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.RedZoneFunctions == 4, "a leaf whose rsp operands can neither move nor be stubbed, or whose prologue is too short, was framed"); + require(report.Skipped.at("rsp operand that cannot move with rsp") == 1, "an EVEX operand, whose 8-bit displacement is scaled, was not refused"); + require(report.Skipped.at("rsp operand that cannot move and has no room for a stub") == 2, "an operand at rsp followed by an exit or a branch target was not refused for want of room"); + require(report.Skipped.at("prologue shorter than a jump") == 1, "the leaf with a short prologue was not reported with its reason"); +} + +void skipsLeavesThatCopyRsp() { + std::vector withCmov = StoreBelowRsp; + append(withCmov, {0x48, 0x0f, 0x45, 0xc4}); + std::vector withShlx = StoreBelowRsp; + append(withShlx, {0xc4, 0xe2, 0xf1, 0xf7, 0xc4}); + const auto input = image({framelessLeaf(FramelessPrologue, withCmov, FramelessExit), framelessLeaf(FramelessPrologue, withShlx, 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") == 2, "a leaf that copies rsp into another register was framed"); +} + +const std::vector InflatePrologue{0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x40}; +const std::vector InflatePops{0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3}; +const std::vector InflateFree{0x48, 0x83, 0xc4, 0x40}; + +std::vector inflateExit() { + std::vector bytes = InflateFree; + append(bytes, InflatePops); + return bytes; +} + +std::vector inflateBody() { + return { + 0x48, 0x89, 0x7c, 0x24, 0xe0, + 0x48, 0x89, 0x7c, 0x24, 0x00, + 0x48, 0x89, 0x74, 0x24, 0x08, + 0x48, 0x89, 0x54, 0x24, 0x38, + 0xb9, 0x04, 0x00, 0x00, 0x00, + 0x31, 0xc0, + 0x48, 0x03, 0x44, 0x24, 0x08, + 0x48, 0x03, 0x44, 0x24, 0xe0, + 0x48, 0x03, 0x44, 0x24, 0x00, + 0xff, 0xc9, + 0x75, 0xed, + 0x48, 0x8b, 0x54, 0x24, 0x38, + }; +} + +std::vector inflateCompareBody(const std::vector& prior, const std::uint8_t back) { + std::vector bytes{ + 0x48, 0x89, 0x7c, 0x24, 0xe0, + 0x48, 0x89, 0x7c, 0x24, 0x00, + 0xb9, 0x04, 0x00, 0x00, 0x00, + 0x31, 0xc0, + 0x48, 0x03, 0x44, 0x24, 0xe0, + }; + append(bytes, prior); + append(bytes, {0x48, 0x39, 0x3c, 0x24, 0x75, 0x03, 0x48, 0x01, 0xf0, 0xff, 0xc9, 0x75, back}); + return bytes; +} + +const std::vector AddRdiToR8{0x49, 0x01, 0xf8}; +const std::vector CompareRdiAtRspShifted{0x48, 0x39, 0x7c, 0x24, 0x20}; + +void stubsShortOperandsAtRspBeforeABranchWithTheInstructionsBeforeThem() { + const auto withAdd = inflateCompareBody(AddRdiToR8, 0xeb); + std::vector withLoad{ + 0x48, 0x89, 0x7c, 0x24, 0xe0, + 0x48, 0x89, 0x7c, 0x24, 0x00, + 0xb9, 0x04, 0x00, 0x00, 0x00, + 0x31, 0xc0, + 0x48, 0x03, 0x44, 0x24, 0xe0, + 0x48, 0x39, 0x3c, 0x24, 0x75, 0x03, 0x48, 0x01, 0xf0, 0xff, 0xc9, 0x75, 0xee, + }; + const auto input = image({framelessLeaf(InflatePrologue, withAdd, inflateExit()), framelessLeaf(InflatePrologue, withLoad, inflateExit())}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.Framed == 2 && report.OperandStubs == 2 && report.StubbedOperands == 3 && report.MovedOperands == 5 && sites.size() == 6 && report.Skipped.empty(), "inflate_fast's 4-byte cmp at rsp between an instruction and a branch was not framed with a stub over the instruction before it"); + const auto add = input.functions[0].Begin + InflatePrologue.size() + 22; + require(sites[2].Address == add && sites[2].Length == AddRdiToR8.size() + 4, "the stub site does not start at the add before the cmp at rsp"); + std::vector addBody = AddRdiToR8; + append(addBody, CompareRdiAtRspShifted); + require(sites[2].Body == stubBody(addBody), "the add was not copied ahead of the cmp re-encoded with a displacement of the shift"); + const auto load = input.functions[1].Begin + InflatePrologue.size() + 17; + require(sites[5].Address == load && sites[5].Length == 9, "the stub site does not start at the load before the cmp at rsp"); + std::vector loadBody{0x48, 0x03, 0x44, 0x24, 0x00}; + append(loadBody, CompareRdiAtRspShifted); + require(sites[5].Body == stubBody(loadBody), "the load before the cmp, itself an rsp operand, was not moved by the shift in the stub"); + for (const auto& displacement : displacements) { + const auto at = Base + displacement.Offset; + require((at < sites[2].Address || at >= sites[2].Address + sites[2].Length) && (at < sites[5].Address || at >= sites[5].Address + sites[5].Length), "an rsp operand inside a stub site was also moved in place"); + } +} + +void skipsShortOperandsAtRspWhoseWidenedStubCoversABranchTarget() { + std::vector body{ + 0x48, 0x89, 0x7c, 0x24, 0xe0, + 0x48, 0x89, 0x7c, 0x24, 0x00, + 0xb9, 0x04, 0x00, 0x00, 0x00, + 0x31, 0xc0, + }; + append(body, AddRdiToR8); + append(body, {0x48, 0x39, 0x3c, 0x24, 0x75, 0x03, 0x48, 0x01, 0xf0, 0xff, 0xc9, 0x75, 0xf3}); + const auto input = image({framelessLeaf(InflatePrologue, body, inflateExit())}); + 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.RedZoneFunctions == 1 && report.Skipped.at("rsp operand that cannot move and has no room for a stub") == 1, "a cmp at rsp whose loop branches back to it was framed with a stub that starts at the add before it"); +} + +void framesLeavesThatAllocateBelowTheirPushes() { + const auto body = inflateBody(); + const auto input = image({framelessLeaf(InflatePrologue, body, inflateExit())}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.RedZoneFunctions == 1 && report.Framed == 1 && report.FramedWithoutFramePointer == 1 && report.Exits == 1 && sites.size() == 2, "a leaf that allocates with sub after its pushes and spills below its allocation was not framed"); + const auto& entry = sites[0]; + require(entry.Address == Base + 0x40 && entry.OriginalBytes == InflatePrologue, "the prologue site does not cover the pushes and the sub"); + std::vector entryBody = InflatePrologue; + append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0xe0, 0xe9, 0, 0, 0, 0}); + require(entry.Body == entryBody, "the prologue stub does not lower rsp by 0x20 after the sub"); + const auto& exit = sites[1]; + require(exit.Address == Base + 0x40 + InflatePrologue.size() + body.size() && exit.OriginalBytes == inflateExit(), "the exit site does not cover the add, the pops and the ret"); + std::vector exitBody{0x48, 0x8d, 0xa4, 0x24, 0x20, 0, 0, 0}; + append(exitBody, inflateExit()); + append(exitBody, {0xe9, 0, 0, 0, 0}); + require(exit.Body == exitBody, "the exit stub does not raise rsp by 0x20 before the add"); + const std::vector moved{0x00, 0x20, 0x28, 0x58, 0x28, 0x00, 0x20, 0x58}; + require(displacements.size() == moved.size() && report.MovedOperands == moved.size(), "not every rsp operand of the allocating leaf was moved"); + for (std::size_t index = 0; index < moved.size(); ++index) + require(displacements[index].Bytes == std::vector{moved[index]}, "an rsp operand of the allocating leaf was not moved by 0x20 from the post-sub rsp"); + require(displacements[0].Offset == 0x40 + InflatePrologue.size() + 4, "the spill below the allocation was not the first operand moved"); +} + +void framesLeavesThatAllocateWithLeaOrALargeSub() { + const std::vector leaPrologue{0x53, 0x41, 0x56, 0x48, 0x8d, 0x64, 0x24, 0xc0}; + const std::vector leaExit{0x48, 0x8d, 0x64, 0x24, 0x40, 0x41, 0x5e, 0x5b, 0xc3}; + const std::vector largePrologue{0x53, 0x48, 0x81, 0xec, 0x00, 0x01, 0x00, 0x00}; + const std::vector largeExit{0x48, 0x81, 0xc4, 0x00, 0x01, 0x00, 0x00, 0x5b, 0xc3}; + std::vector largeBody = StoreBelowRsp; + append(largeBody, {0x48, 0x8b, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00}); + const auto input = image({framelessLeaf(leaPrologue, StoreBelowRsp, leaExit), framelessLeaf(largePrologue, largeBody, largeExit)}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.Framed == 2 && sites.size() == 4 && displacements.size() == 3, "leaves that allocate with lea or a 32-bit sub were not framed"); + require(sites[0].OriginalBytes == leaPrologue && sites[1].OriginalBytes == leaExit, "the lea allocation and free are not part of the prologue and exit sites"); + require(sites[2].OriginalBytes == largePrologue && sites[3].OriginalBytes == largeExit, "the 32-bit sub and add are not part of the prologue and exit sites"); + require(displacements[2].Bytes == std::vector{0x90, 0x00, 0x00, 0x00}, "the 32-bit displacement into the allocation was not moved by the shift"); +} + +void skipsAllocatingLeavesWithoutAMatchingFree() { + const std::vector otherFree{0x48, 0x83, 0xc4, 0x30}; + std::vector wrongFree = otherFree; + append(wrongFree, InflatePops); + std::vector moreRsp = StoreBelowRsp; + append(moreRsp, {0x48, 0x83, 0xec, 0x08}); + const auto input = image({framelessLeaf(InflatePrologue, StoreBelowRsp, InflatePops), framelessLeaf(InflatePrologue, StoreBelowRsp, wrongFree), framelessLeaf(InflatePrologue, moreRsp, inflateExit())}); + 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.RedZoneFunctions == 3, "an allocating leaf without a matching free, or with more rsp arithmetic, was framed"); + require(report.Skipped.at("exit does not free the prologue's allocation") == 1, "an exit that does not free the allocation was not reported with its own reason"); + require(report.Skipped.at("other use of rsp") == 2, "an add of another size before the pops, or more rsp arithmetic in the body, was not reported as another use of rsp"); +} + +Image calledThenUncalled(const std::vector& called, const std::vector& uncalled) { + Image result; + result.bytes = callTo(0, 0x40); + result.bytes.push_back(0xc3); + result.bytes = padTo(result.bytes, 0x40); + append(result.bytes, called); + result.bytes.resize(0x60, 0x90); + append(result.bytes, uncalled); + result.bytes = padTo(result.bytes, 0x80); + result.segments.push_back({1, 5, 0, Base, Base, result.bytes.size(), result.bytes.size(), 16}); + result.functions.push_back({Base + 0x40, Base + 0x40 + called.size()}); + result.functions.push_back({Base + 0x60, Base + 0x60 + uncalled.size()}); + return result; +} + +void framesEachFunctionFromItsOwnFde() { + std::vector body = StoreBelowRsp; + append(body, LoadBelowRsp); + const auto input = calledThenUncalled(framelessLeaf(FramelessPrologue, body, FramelessExit), framelessLeaf(FramelessPrologue, StoreBelowRsp, FramelessExit)); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.Functions == 2 && report.Framed == 2 && report.Exits == 2 && sites.size() == 4 && displacements.size() == 3, "two adjacent functions were not framed each by its own FDE"); + require(sites[0].Address < Base + 0x60 && sites[1].Address + sites[1].Length <= Base + 0x60 && displacements[0].Offset < 0x60 && displacements[1].Offset < 0x60, "the first function's frame runs past its FDE end"); + require(sites[2].Address == Base + 0x60 && sites[2].OriginalBytes == FramelessPrologue && displacements[2].Offset > 0x60, "the function reached without a call was not framed from its own FDE start"); +} + +void treatsATailCallPastTheFdeEndAsLeaving() { + std::vector called = FramelessPrologue; + append(called, StoreBelowRsp); + append(called, {0x85, 0xff, 0x75, 0x06}); + append(called, FramelessExit); + append(called, {0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xe9, 0x02, 0x00, 0x00, 0x00}); + const auto input = calledThenUncalled(called, framelessLeaf(FramelessPrologue, StoreBelowRsp, FramelessExit)); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.Skipped.at("branch leaves the function") == 1, "the tail call past the FDE end was not reported as leaving the function"); + require(report.Framed == 1 && sites.size() == 2 && sites[0].Address == Base + 0x60, "the tail-called function was not framed from its own FDE start"); +} + +void framesALeafAfterAJumpWithoutInt3FromItsOwnFde() { + std::vector first = FramelessPrologue; + append(first, StoreBelowRsp); + append(first, {0x31, 0xc0, 0xeb, 0x06}); + append(first, FramelessExit); + append(first, {0x48, 0x3b, 0x44, 0x24, 0xf0, 0x73, 0xf3, 0x48, 0xff, 0xc0, 0xeb, 0xf4}); + require(first.size() == 0x20, "the first leaf must end where the second starts"); + std::vector second = StoreBelowRsp; + append(second, LoadBelowRsp); + const auto input = calledThenUncalled(first, framelessLeaf(FramelessPrologue, second, FramelessExit)); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.Framed == 2 && report.Exits == 2 && sites.size() == 4 && displacements.size() == 4, "a leaf right after a jmp without int3 padding was not framed on its own"); + require(sites[1].Address + sites[1].Length <= Base + 0x60 && displacements[1].Offset < 0x60, "the leaf ending in a jmp took the next leaf into its body"); + require(sites[2].Address == Base + 0x60 && sites[2].OriginalBytes == FramelessPrologue && displacements[2].Offset > 0x60, "the leaf after the jmp was not framed from its own FDE start"); +} + +void keepsColdBlocksAfterARetInsideTheFde() { + std::vector leaf = FramelessPrologue; + append(leaf, StoreBelowRsp); + append(leaf, {0x85, 0xff, 0x74, 0x12}); + append(leaf, LoadBelowRsp); + append(leaf, FramelessExit); + append(leaf, {0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00}); + append(leaf, {0x48, 0x8b, 0x54, 0x24, 0xf0}); + append(leaf, FramelessExit); + const auto input = image({leaf}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.Framed == 1 && report.Exits == 2 && sites.size() == 3, "the exit of a cold block after the ret inside the FDE was not framed"); + require(displacements.size() == 3 && displacements[2].Offset == 0x40 + 0x20 + 4 && displacements[2].Bytes == std::vector{0x00}, "the rsp operand of a cold block after the ret inside the FDE was not moved"); +} + +void keepsLoopsAlignedAfterAJump() { + std::vector loop = FramelessPrologue; + append(loop, StoreBelowRsp); + append(loop, {0x31, 0xc0, 0xeb, 0x04, 0x66, 0x90, 0xff, 0xc0, 0x3b, 0x44, 0x24, 0xf0, 0x72, 0xf8}); + append(loop, FramelessExit); + const auto input = image({loop}); + 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 && displacements.size() == 2, "a leaf whose loop header is aligned after a jump was not framed"); +} + +void leavesOtherFunctions() { + std::vector nonLeaf = Prologue; + append(nonLeaf, StoreBelowPushes); + append(nonLeaf, {0xff, 0xd0}); + append(nonLeaf, Exit); + const auto input = image({leaf(LoadAbovePushes), nonLeaf}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(sites.empty() && report.RedZoneFunctions == 0, "a leaf without red-zone use or a function that calls was framed"); +} + +void skipsSitesTakenByOtherStubs() { + const auto input = image({leaf(StoreBelowPushes)}); + Codegen::TrampolineSite other{}; + other.Address = Base + 0x40; + other.Length = 5; + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {other}, report, displacements); + require(sites.empty() && report.Skipped.at("overlaps another stub") == 1, "a site over another stub was framed"); +} + +void framesAPointerOnlyFunctionByItsOwnBounds() { + const auto first = leaf(StoreBelowPushes); + std::vector secondBody = StoreBelowPushes; + append(secondBody, LoadBelowPushes); + const auto second = leaf(secondBody); + std::vector bytes = callTo(0, 0x40); + bytes.push_back(0xc3); + bytes = padTo(bytes, 0x40); + append(bytes, first); + const auto secondStart = bytes.size(); + append(bytes, second); + bytes = padTo(bytes, (bytes.size() + 15) / 16 * 16 + 16); + const std::vector segments{{1, 5, 0, Base, Base, bytes.size(), bytes.size(), 16}}; + const std::vector functions{{Base + 0x40, Base + 0x40 + first.size()}, {Base + secondStart, Base + secondStart + second.size()}}; + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(bytes, segments, functions, {}, report, displacements); + require(report.Functions == 2 && report.Framed == 2 && report.Exits == 2, "a function reached only through a pointer was not framed by its own bounds"); + require(sites.size() == 4, "two framed leaves need two prologue and two exit sites"); + require(sites[2].Address == Base + secondStart && sites[2].OriginalBytes == Prologue, "the second function's prologue site is not at its own start"); + require(sites[1].Address + sites[1].Length == Base + 0x40 + first.size(), "the first function's exit runs past its end"); +} + +void leavesCodeWithoutAFunctionRange() { + const auto input = image({leaf(StoreBelowPushes)}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, {}, report, displacements); + require(sites.empty() && report.Functions == 0, "code without a function range was framed"); +} + +} + +int main() { + try { + framesRedZoneLeaf(); + leavesOtherFunctions(); + skipsSitesTakenByOtherStubs(); + framesLeafWithoutFramePointer(); + shiftsByTheDeepestOperandBelowRsp(); + movesPositiveOperandsThatFitAfterTheShift(); + shiftsByTheWholeRedZoneForAddressesBelowRsp(); + shiftsByTheWholeRedZoneForIndexedOperands(); + framesLeavesThatAllocateBelowTheirPushes(); + framesLeavesThatAllocateWithLeaOrALargeSub(); + skipsAllocatingLeavesWithoutAMatchingFree(); + stubsOperandsWhoseDisplacementOverflows(); + stubsOperandsAtRspWithTheInstructionsAfterThem(); + stubsVexOperandsAtRsp(); + stubsOperandsAtRspWithTheInstructionsBeforeThem(); + stubsShortOperandsAtRspBeforeABranchWithTheInstructionsBeforeThem(); + skipsShortOperandsAtRspWhoseWidenedStubCoversABranchTarget(); + skipsLeavesWithOperandsThatCannotMove(); + skipsLeavesThatCopyRsp(); + framesEachFunctionFromItsOwnFde(); + treatsATailCallPastTheFdeEndAsLeaving(); + framesALeafAfterAJumpWithoutInt3FromItsOwnFde(); + keepsColdBlocksAfterARetInsideTheFde(); + keepsLoopsAlignedAfterAJump(); + framesAPointerOnlyFunctionByItsOwnBounds(); + leavesCodeWithoutAFunctionRange(); + std::cout << "red-zone framer tests passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << "red-zone framer tests failed: " << error.what() << '\n'; + return 1; + } +} diff --git a/core/relinker/codegen/tests/RewriteBoundaryTests.cpp b/core/relinker/codegen/tests/RewriteBoundaryTests.cpp index fa9ee00553..66a6ed2fc6 100644 --- a/core/relinker/codegen/tests/RewriteBoundaryTests.cpp +++ b/core/relinker/codegen/tests/RewriteBoundaryTests.cpp @@ -2,26 +2,124 @@ #include #include -static void Require(bool ok) { if (!ok) throw std::runtime_error("Invalid rewrite boundary accepted"); } +static void Require(bool ok) { + if (!ok) + throw std::runtime_error("Invalid rewrite boundary accepted"); +} + +static void RequireTruncatedBranchRejected(bool ok) { + if (!ok) + throw std::runtime_error( + "Length-changing rewrite accepted a section with a truncated relative branch"); +} int main() { try { const Codegen::X64InstructionRewriter rewriter; - for (const auto& code : {std::vector{0xB8, 0x90, 0x90, 0x90, 0x90, 0xC3}, - std::vector{0x48, 0x8B, 0x05, 0x90, 0x90, 0x90, 0x90, 0xC3}, - std::vector{0xE8, 0x90, 0x90, 0x90, 0x90, 0xC3}}) { + + for (const auto& code : { + std::vector{ + 0xB8, 0x90, 0x90, 0x90, 0x90, 0xC3}, + std::vector{ + 0x48, 0x8B, 0x05, 0x90, 0x90, 0x90, 0x90, 0xC3}, + std::vector{ + 0xE8, 0x90, 0x90, 0x90, 0x90, 0xC3} + }) { const auto original = code; - for (std::size_t offset = 1; offset + 1 < code.size(); ++offset) { + + for (std::size_t offset = 1; + offset + 1 < code.size(); + ++offset) { bool rejected = false; - try { (void)rewriter.Rewrite(code, {offset, {0x90}}); } - catch (const std::exception&) { rejected = true; } + + try { + (void)rewriter.Rewrite(code, {offset, {0x90}}); + } catch (const std::exception&) { + rejected = true; + } + Require(rejected && code == original); } + const auto result = rewriter.Rewrite(code, {0, {0x90}}); - Require(result.Bytes == std::vector{0x90, 0xC3}); + + Require(result.Bytes == std::vector{ + 0x90, 0xC3}); } - const auto result = rewriter.Rewrite({0x90, 0x90, 0xC3}, {1, {0x90, 0x90}}); - Require(result.Bytes == std::vector{0x90, 0x90, 0x90, 0xC3}); - std::cout << "18 rewrite boundary cases passed\n"; - } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } + + const auto result = rewriter.Rewrite( + {0x90, 0x90, 0xC3}, + {1, {0x90, 0x90}}); + + Require(result.Bytes == std::vector{ + 0x90, 0x90, 0x90, 0xC3}); + + bool targetInsideCutRejected = false; + try { + (void)rewriter.Rewrite( + {0xEB, 0x01, 0xB8, 0x11, 0x22, 0x33, 0x44, 0xC3}, + {2, {0x90}}); + } catch (const std::exception&) { + targetInsideCutRejected = true; + } + Require(targetInsideCutRejected); + + for (const auto& code : { + std::vector{ + 0x66, 0xEB, 0x01, 0x90, 0xC3}, + std::vector{ + 0x90, 0x66, 0xEB, 0xFC, 0xC3} + }) { + bool rejected = false; + + try { + const auto offset = + code[0] == 0x66 ? std::size_t{3} : std::size_t{0}; + + (void)rewriter.Rewrite(code, {offset, {0x66, 0x90}}); + } catch (const std::exception&) { + rejected = true; + } + + RequireTruncatedBranchRejected(rejected); + } + + const auto unaffected = rewriter.Rewrite( + {0x66, 0xEB, 0x00, 0x90, 0x90, 0xC3}, + {4, {0x66, 0x90}}); + + Require(unaffected.Bytes == std::vector{ + 0x66, 0xEB, 0x00, 0x90, 0x66, 0x90, 0xC3}); + + std::vector forwardWraparound(0x10004, 0x90); + forwardWraparound[0xFFFA] = 0x66; + forwardWraparound[0xFFFB] = 0xEB; + forwardWraparound[0xFFFC] = 0x03; + + bool forwardWraparoundRejected = false; + try { + (void)rewriter.Rewrite(forwardWraparound, {10, {0x66, 0x90}}); + } catch (const std::exception&) { + forwardWraparoundRejected = true; + } + RequireTruncatedBranchRejected(forwardWraparoundRejected); + + std::vector backwardWraparound(0x10004, 0x90); + backwardWraparound[0] = 0x66; + backwardWraparound[1] = 0xEB; + backwardWraparound[2] = 0xFC; + + bool backwardWraparoundRejected = false; + try { + (void)rewriter.Rewrite(backwardWraparound, {0xFFFE, {0x66, 0x90}}); + } catch (const std::exception&) { + backwardWraparoundRejected = true; + } + RequireTruncatedBranchRejected(backwardWraparoundRejected); + + std::cout << "22 rewrite boundary cases passed\n"; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } } diff --git a/core/relinker/domain/include/domain/Types.hpp b/core/relinker/domain/include/domain/Types.hpp index dd181853e0..d08f990328 100644 --- a/core/relinker/domain/include/domain/Types.hpp +++ b/core/relinker/domain/include/domain/Types.hpp @@ -69,6 +69,7 @@ struct NidReference { std::int64_t Addend; bool Weak = false; bool Function = false; + std::uint8_t SymbolType = 0; }; struct CallSiteInfo { @@ -106,6 +107,7 @@ struct SysVDynamicSection { std::vector GuestModules; std::map ImportModules; std::set FunctionImports; + std::set NamedImports; std::set UnmatchedImportModules; std::vector NeededLibraries; }; diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp index 9da009bc08..b8b6b853b6 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp @@ -139,20 +139,14 @@ inline constexpr std::int64_t DT_OS_SYMTABSZ = 0x6100003f; inline constexpr std::uint32_t R_X86_64_NONE = 0; inline constexpr std::uint32_t R_X86_64_64 = 1; inline constexpr std::uint32_t R_X86_64_PC32 = 2; -inline constexpr std::uint32_t R_X86_64_GOT32 = 3; -inline constexpr std::uint32_t R_X86_64_PLT32 = 4; inline constexpr std::uint32_t R_X86_64_COPY = 5; inline constexpr std::uint32_t R_X86_64_GLOB_DAT = 6; inline constexpr std::uint32_t R_X86_64_JUMP_SLOT = 7; inline constexpr std::uint32_t R_X86_64_RELATIVE = 8; -inline constexpr std::uint32_t R_X86_64_GOTPCREL = 9; inline constexpr std::uint32_t R_X86_64_32 = 10; -inline constexpr std::uint32_t R_X86_64_32S = 11; inline constexpr std::uint32_t R_X86_64_DTPMOD64 = 16; inline constexpr std::uint32_t R_X86_64_DTPOFF64 = 17; inline constexpr std::uint32_t R_X86_64_TPOFF64 = 18; -inline constexpr std::uint32_t R_X86_64_GOTPCRELX = 41; -inline constexpr std::uint32_t R_X86_64_REX_GOTPCRELX = 42; inline constexpr std::uint32_t R_X86_64_IRELATIVE = 37; inline constexpr std::uint8_t STB_GLOBAL = 1; @@ -170,6 +164,7 @@ inline constexpr std::uint32_t SHT_PROGBITS = 1; inline constexpr std::uint32_t SHT_SYMTAB = 2; inline constexpr std::uint32_t SHT_STRTAB = 3; inline constexpr std::uint32_t SHT_DYNAMIC = 6; +inline constexpr std::uint32_t SHT_NOBITS = 8; inline constexpr std::uint32_t SHT_DYNSYM = 11; inline constexpr std::uint32_t SHF_WRITE = 0x1; inline constexpr std::uint32_t SHF_ALLOC = 0x2; diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp index 5ca491e9bf..1dcc28bbd0 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp @@ -9,7 +9,7 @@ namespace Elfpatcher { class GuestModuleWriter { public: - std::vector WriteLinux(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath) const; + std::vector WriteLinux(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath, bool deferInitialization = false) const; std::vector WriteWindows(const Relinker::GuestImage& image, Domain::GuestRuntime& runtime, const std::function& message = {}) const; std::vector WriteMacOs(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath, const std::function& message = {}) const; }; diff --git a/core/relinker/elfpatcher/include/elfpatcher/linux/LinuxElfPatcher.hpp b/core/relinker/elfpatcher/include/elfpatcher/linux/LinuxElfPatcher.hpp index c24b3114f0..58aebbcd67 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/linux/LinuxElfPatcher.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/linux/LinuxElfPatcher.hpp @@ -14,6 +14,8 @@ namespace Elfpatcher::Linux { +inline constexpr std::uint64_t DefaultFixedBase = 0x400000; + class LinuxElfPatcher : public IElfPatcher { public: LinuxElfPatcher( diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp index 790dfdf3ae..c9436e4915 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp @@ -13,6 +13,7 @@ inline constexpr std::uint32_t SectionAlignment = 0x1000; inline constexpr std::uint32_t FileAlignment = 0x200; inline constexpr std::uint32_t LoadRva = 0x10000; inline constexpr std::uint64_t ImageBase = 0x140000000; +inline constexpr std::uint64_t ExecutableBase = 0x400000; inline constexpr std::uint32_t SectionRead = 0x40000000; inline constexpr std::uint32_t SectionWrite = 0x80000000; inline constexpr std::uint32_t SectionExecute = 0x20000000; diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeWriter.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeWriter.hpp index db44e19058..46e9e66fed 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeWriter.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeWriter.hpp @@ -2,13 +2,12 @@ #define ELFPATCHER_WINDOWS_PEWRITER_HPP #include -#include namespace Elfpatcher::Windows { class WindowsPeWriter { public: - std::vector Write(const std::vector& sections, std::uint32_t entryRva, const std::array& directories, bool windowsGui = false, std::optional fixedImageBase = std::nullopt, bool dynamicBase = true) const; + std::vector Write(const std::vector& sections, std::uint32_t entryRva, const std::array& directories, bool windowsGui = false, std::uint64_t imageBase = ImageBase, bool fixedBase = false, bool dynamicBase = true) const; }; } diff --git a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp index fc23e28dea..f53da5a100 100644 --- a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp @@ -13,6 +13,8 @@ namespace Elfpatcher { namespace { +constexpr std::size_t kExtendedProgramHeaderCount = 0xffff; + bool hasWritableLoadCoverage( const std::vector& headers, const Domain::ProgramHeader& relro @@ -152,6 +154,8 @@ std::vector ProgramHeaderLayoutBuilder::_outputHeaders(co auto kept = _keptHeaders(originalHeaders); if (std::none_of(kept.begin(), kept.end(), [](const Domain::ProgramHeader& ph) { return ph.Type == PT_GNU_STACK; })) kept.push_back(Domain::ProgramHeader{PT_GNU_STACK, PF_R | PF_W, 0, 0, 0, 0, 0, 16}); + if (kept.size() >= kExtendedProgramHeaderCount - kSyntheticProgramHeaderCount) + throw Domain::RelinkerException("Extended program header counts are not supported"); return kept; } diff --git a/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp b/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp index e07068514f..ec6ea6cb35 100644 --- a/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp @@ -103,6 +103,7 @@ void SectionHeaderTableBuilder::WriteTable( for (const char i : kShStrTabBlob) buf.push_back(static_cast(i)); + Io::AlignBuffer(buf, std::size_t{8}); const auto shOff = static_cast(buf.size()); buf.resize(buf.size() + kShdrEntrySize * kSectionHeaderCount, 0); diff --git a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp index df116a307c..c2e89787cc 100644 --- a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp +++ b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp @@ -4,10 +4,13 @@ #include #include #include +#include #include +#include #include #include #include +#include #include #include #include @@ -19,6 +22,8 @@ namespace Elfpatcher::Linux { namespace { constexpr std::uint64_t kLinuxUserAddressEnd = 0x800000000000; +constexpr std::uint64_t kLazyPltGotBytes = 24; +constexpr std::uint64_t kPltGotEntryBytes = 8; std::string Hex(const std::uint64_t value) { std::ostringstream text; @@ -78,11 +83,9 @@ std::vector RebasedRelocations(const std::vector& ta } case R_X86_64_64: case R_X86_64_PC32: - case R_X86_64_COPY: case R_X86_64_GLOB_DAT: case R_X86_64_JUMP_SLOT: case R_X86_64_32: - case R_X86_64_32S: case R_X86_64_DTPMOD64: case R_X86_64_DTPOFF64: case R_X86_64_TPOFF64: @@ -100,6 +103,19 @@ void RequireFileRange(const std::vector& buf, const Domain::Progra throw Domain::RelinkerException("PT_LOAD at " + Hex(load.MappedAddress) + " has file bytes outside the input"); } +void RequireLoaded(const std::vector& headers, const std::uint64_t address, const std::uint64_t size, const std::uint32_t flags, const std::string& what) { + for (const auto& header : headers) + if (header.Type == PT_LOAD && (header.Flags == 0 || (header.Flags & flags) == flags) && address >= header.MappedAddress && address - header.MappedAddress < header.MemorySize && size <= header.MemorySize - (address - header.MappedAddress)) + return; + const auto range = size > 1 ? " (" + std::to_string(size) + " bytes) does not fit inside " : std::string(" is not inside "); + throw Domain::RelinkerException(what + " " + Hex(address) + range + ((flags & PF_X) != 0 ? "an executable" : "a") + " PT_LOAD segment"); +} + +std::uint64_t RelocationWidth(const std::vector& relocations, const std::size_t entry) { + const auto type = static_cast(Io::ReadU64(relocations, entry + 8)); + return type == R_X86_64_PC32 || type == R_X86_64_32 ? 4 : 8; +} + void SeparateSharedLoadPages(std::vector& buf, std::vector& headers) { std::vector loads; for (std::size_t index = 0; index < headers.size(); ++index) { @@ -111,6 +127,11 @@ void SeparateSharedLoadPages(std::vector& buf, std::vector TracedImports(const Domain::SysVDynamicSection& dynSec return imports; } +Domain::SysVDynamicSection ImportsByNid(Domain::SysVDynamicSection dynSection) { + RequireWholeEntries(dynSection.DynamicSegmentData, kDynEntrySize, "Executable dependency table"); + for (std::size_t entry = 0; entry < dynSection.DynamicSegmentData.size(); entry += kDynEntrySize) { + if (static_cast(Io::ReadU64(dynSection.DynamicSegmentData, entry)) != DT_NEEDED) + continue; + const auto offset = Io::ReadU64(dynSection.DynamicSegmentData, entry + 8); + if (offset >= dynSection.DynStrData.size()) + throw Domain::RelinkerException("DT_NEEDED string offset is out of bounds", offset); + const auto name = ReadDynamicString(dynSection.DynStrData, static_cast(offset)); + const auto library = Domain::NeededLibraryName(name); + if (library == name) + continue; + Io::WriteU64(dynSection.DynamicSegmentData, entry + 8, dynSection.DynStrData.size()); + Io::AppendString(dynSection.DynStrData, library); + } + for (auto& library : dynSection.NeededLibraries) + library = Domain::NeededLibraryName(library); + + std::set symbols; + for (const auto* table : {&dynSection.RelaData, &dynSection.RelaPltData}) { + RequireWholeEntries(*table, kRelaEntrySize, "Relocation table"); + for (std::size_t entry = 0; entry < table->size(); entry += kRelaEntrySize) + if (dynSection.NamedImports.contains(Io::ReadU64(*table, entry))) + symbols.insert(Io::ReadU64(*table, entry + 8) >> 32); + } + for (const auto symbol : symbols) { + if (symbol >= dynSection.DynSymData.size() / kSymEntrySize) + throw Domain::RelinkerException("Invalid import symbol index", symbol); + const auto symbolOffset = static_cast(symbol * kSymEntrySize); + const auto name = ReadDynamicString(dynSection.DynStrData, Io::ReadU32(dynSection.DynSymData, symbolOffset)); + if (name.empty() || name.find('#') != std::string::npos) + continue; + if (dynSection.DynStrData.size() > std::numeric_limits::max()) + throw Domain::RelinkerException("Dynamic string table too large"); + Io::WriteU32(dynSection.DynSymData, symbolOffset, static_cast(dynSection.DynStrData.size())); + Io::AppendString(dynSection.DynStrData, Nid::ComputeNid(name, "")); + } + return dynSection; +} + std::vector TracePrologue(const std::uint64_t vaddr, const std::uint64_t table, const std::uint64_t tableEnd, const std::uint64_t function) { std::vector code = {0x52, 0x53, 0x41, 0x54, 0x48, 0x83, 0xec, 0x08}; const auto rip = [&](std::initializer_list opcode, const std::uint64_t target) { @@ -297,13 +358,16 @@ void LinuxElfPatcher::_appendTrampoline( std::vector LinuxElfPatcher::Patch( std::vector sourceElf, const std::vector& originalHeaders, - const Domain::SysVDynamicSection& dynSection, + const Domain::SysVDynamicSection& sourceDynSection, const std::uint64_t originalPltGotVaddr, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics, const std::vector& trampolines) { + const auto dynSection = ImportsByNid(sourceDynSection); + if (runPath.empty() || runPath.front() == ':' || runPath.back() == ':' || runPath.find("::") != std::string::npos) + throw Domain::RelinkerException("Empty Linux run path component is not supported"); if (dependencyDiagnostics) throw Domain::RelinkerException("Linux target does not support --windows-diagnostics"); if (_fixedBase && lazyBinding) @@ -314,6 +378,12 @@ std::vector LinuxElfPatcher::Patch( throw Domain::RelinkerException("--trace-imports requires libc.prx among the libraries"); if (_fixedBase) RequireOnlyDependencies(dynSection.DynamicSegmentData); + RequireLoaded(originalHeaders, Io::ReadU64(sourceElf, kEhdrEntryOffset), 1, PF_X, "e_entry"); + for (const auto& [relocationTable, name] : {std::pair{&dynSection.RelaData, "RELA r_offset"}, std::pair{&dynSection.RelaPltData, "JMPREL r_offset"}}) + for (std::size_t entry = 0; entry < relocationTable->size(); entry += kRelaEntrySize) + RequireLoaded(originalHeaders, Io::ReadU64(*relocationTable, entry), RelocationWidth(*relocationTable, entry), 0, name); + if (!dynSection.RelaPltData.empty()) + RequireLoaded(originalHeaders, originalPltGotVaddr, lazyBinding ? kLazyPltGotBytes : kPltGotEntryBytes, 0, "DT_PLTGOT"); const auto traced = _traceImports ? TracedImports(dynSection) : std::vector{}; const std::uint64_t base = _fixedBase.value_or(0); const auto address = [this, base](const std::uint64_t vaddr) { diff --git a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp index 76b91f97dc..316a1cc318 100644 --- a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp +++ b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp @@ -9,7 +9,7 @@ namespace Elfpatcher { -std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath) const { +std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath, const bool deferInitialization) const { auto bytes = image.Bytes; std::vector headers; std::uint64_t end = 0; @@ -35,7 +35,7 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma const auto extraOffset = bytes.size(); const auto extraAddress = Io::AlignUp64(end, 0x4000); const auto address = [&] { return extraAddress + bytes.size() - extraOffset; }; - const bool passesModuleArgs = image.HasInit; + const bool passesModuleArgs = image.HasInit && !deferInitialization; std::uint64_t argsSlot = 0; std::uint64_t resultSlot = 0; if (passesModuleArgs) { @@ -83,6 +83,39 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma Io::WriteU64(relaData, entry, slot); Io::WriteU64(relaData, entry + 8, (symbol << 32) | kGlobDat); }; + std::uint64_t lifecycleTable = 0; + if (deferInitialization) { + constexpr char exportName[] = "__aps5_guest_lifecycle_nid_no_patch"; + if (std::any_of(image.Symbols.begin(), image.Symbols.end(), [&](const auto& symbol) { return symbol.Name == exportName; })) + throw Domain::RelinkerException("Reserved guest lifecycle export is already present"); + Io::AlignBuffer(bytes, 8); + lifecycleTable = address(); + const auto pointer = [&](std::uint64_t target, bool relocate = true) { + const auto slot = address(); + Io::AppendU64(bytes, 0); + if (!relocate) return; + Io::AppendU64(relaData, slot); + Io::AppendU64(relaData, 8); + Io::AppendU64(relaData, target); + }; + Io::AppendU64(bytes, 0x415053354c494658ull); + pointer(0); + pointer(image.Init, image.HasInit); + pointer(image.Fini, image.Fini != 0); + Io::AppendU64(bytes, image.InitArray.size()); + Io::AppendU64(bytes, image.FiniArray.size()); + Io::AppendU64(bytes, 0); + Io::AppendU64(bytes, 0); + for (const auto slot : image.InitArray) pointer(slot); + for (const auto slot : image.FiniArray) pointer(slot); + const auto entry = symbols.size(); + symbols.resize(entry + 24, 0); + Io::WriteU32(symbols, entry, addString(exportName)); + symbols[entry + 4] = 0x11; + Io::WriteU16(symbols, entry + 6, 1); + Io::WriteU64(symbols, entry + 8, lifecycleTable); + Io::WriteU64(symbols, entry + 16, address() - lifecycleTable); + } if (passesModuleArgs) { argsImport = addUndefinedImport("__aps5_get_pending_module_args_nid_no_patch"); resultImport = addUndefinedImport("__aps5_set_module_init_result_nid_no_patch"); @@ -156,8 +189,33 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma Io::AppendU32(bytes, static_cast(distance)); return start; }; - const auto init = lifecycle(image.Init); - const auto fini = finalizer(image.Fini); + const auto init = deferInitialization ? 0 : lifecycle(image.Init); + auto fini = deferInitialization ? 0 : finalizer(image.Fini); + if (deferInitialization && (image.Fini != 0 || !image.FiniArray.empty())) { + fini = address(); + bytes.insert(bytes.end(), {0x48, 0x83, 0x3d}); + Io::AppendU32(bytes, stubDisplacement(lifecycleTable + 48, address() + 5)); + bytes.push_back(2); + bytes.insert(bytes.end(), {0x0f, 0x85}); + const auto skip = bytes.size(); + Io::AppendU32(bytes, 0); + bytes.insert(bytes.end(), {0x48, 0xc7, 0x05}); + Io::AppendU32(bytes, stubDisplacement(lifecycleTable + 48, address() + 8)); + Io::AppendU32(bytes, 4); + bytes.insert(bytes.end(), {0x48, 0x83, 0xec, 8}); + for (auto slot = image.FiniArray.rbegin(); slot != image.FiniArray.rend(); ++slot) { + bytes.insert(bytes.end(), {0x48, 0x8b, 0x05}); + Io::AppendU32(bytes, stubDisplacement(*slot, address() + 4)); + bytes.insert(bytes.end(), {0x48, 0x85, 0xc0, 0x74, 8, 0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xff, 0xd0}); + } + if (image.Fini != 0) { + bytes.insert(bytes.end(), {0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xe8}); + Io::AppendU32(bytes, stubDisplacement(image.Fini, address() + 4)); + } + bytes.insert(bytes.end(), {0x48, 0x83, 0xc4, 8}); + Io::WriteU32(bytes, skip, static_cast(bytes.size() - skip - 4)); + bytes.push_back(0xc3); + } Io::AlignBuffer(bytes, 8); const auto dynamicOffset = bytes.size(); const auto dynamicAddress = address(); @@ -181,8 +239,8 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma tag(20, 7); tag(3, image.Got); } - if (!image.InitArray.empty()) { tag(25, image.InitArray.front()); tag(27, image.InitArray.size() * 8); } - if (!image.FiniArray.empty()) { tag(26, image.FiniArray.front()); tag(28, image.FiniArray.size() * 8); } + if (!deferInitialization && !image.InitArray.empty()) { tag(25, image.InitArray.front()); tag(27, image.InitArray.size() * 8); } + if (!deferInitialization && !image.FiniArray.empty()) { tag(26, image.FiniArray.front()); tag(28, image.FiniArray.size() * 8); } if (init != 0) tag(12, init); if (fini != 0) tag(13, fini); tag(30, DF_BIND_NOW | (image.Symbolic ? DF_SYMBOLIC : 0)); diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index 43a00cd7af..b864c5640e 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -87,11 +87,13 @@ constexpr std::uint8_t EntryStub[] = { 0x4c, 0xc4, 0x08, 0x49, 0xff, 0xc0, 0xeb, 0xed, 0x48, 0xc7, 0x44, 0xfc, 0x08, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x8d, 0x54, 0xfc, 0x10, 0x45, 0x31, 0xc0, 0x49, 0x39, 0xc8, 0x74, 0x0d, 0x4e, 0x8b, 0x0c, 0xc2, 0x4f, 0x89, 0x0c, 0xc2, 0x49, 0xff, 0xc0, 0xeb, 0xee, 0x49, 0xc7, 0x04, 0xca, 0x00, - 0x00, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x48, 0x8d, 0x3d, 0x00, 0x00, 0x00, 0x00, 0xff, 0x15, 0x00, - 0x00, 0x00, 0x00, 0x0f, 0x0b, + 0x00, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x48, 0x8d, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xec, + 0x10, 0x0f, 0xae, 0x1c, 0x24, 0xc7, 0x44, 0x24, 0x04, 0xc0, 0x9f, 0x00, 0x00, 0x0f, 0xae, 0x54, + 0x24, 0x04, 0xff, 0x15, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xae, 0x14, 0x24, 0x48, 0x83, 0xc4, 0x10, + 0x0f, 0x0b, }; constexpr std::size_t EntryStubEntryDisplacement = 0x69; -constexpr std::size_t EntryStubStartDisplacement = 0x6f; +constexpr std::size_t EntryStubStartDisplacement = 0x84; struct Section { std::string Name; @@ -423,6 +425,9 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { const auto guestSegments = segments.size(); auto sections = image.BuildSections(); for (const auto& trap : tls.GsTraps) { + const auto target = tls.BranchTargets.upper_bound(trap.Rva); + if (target != tls.BranchTargets.end() && *target < trap.Rva + trap.Length) + throw Domain::RelinkerException("Branch enters a guest GS instruction", *target); if (input.Trampolines != nullptr) { for (const auto& site : *input.Trampolines) { const auto siteRva = image.GetRva(site.Address, site.Length); diff --git a/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp index 175a42f755..012c397f06 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp @@ -114,6 +114,15 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con const auto logName = addWideString(logFileName); const auto shellLibrary = addString("shell32.dll"); const auto parseArguments = addString("CommandLineToArgvW"); + const auto runtimeModule = reserve(8); + const auto runtimeIobFunction = reserve(8); + const auto runtimeFreopenFunction = reserve(8); + const auto msvcrtLibrary = addWideString(u"msvcrt.dll"); + const auto ucrtbaseLibrary = addWideString(u"ucrtbase.dll"); + const auto msvcrtIobFunctionName = addString("__iob_func"); + const auto ucrtbaseIobFunctionName = addString("__acrt_iob_func"); + const auto runtimeFreopenName = addString("_wfreopen"); + const auto runtimeAppendMode = addWideString(u"a"); const auto handles = reserve(libraries.size() * 8); const auto guestFinished = reserve(4); const WindowsGuestStartup guestStartup; @@ -157,6 +166,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con std::vector searchCounts; std::vector platformTlsResolvers; std::vector imported(libraries.size()); + std::vector executableImports(guestModules.size()); const bool unscopedImports = std::any_of(imports.begin(), imports.end(), [](const auto& import) { return import.Library.empty(); }); for (const auto& import : imports) { const auto library = Domain::NeededLibraryName(import.Library); @@ -167,11 +177,17 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con const bool guestName = index < guestModules.size() && std::find(guestModules[index].Names.begin(), guestModules[index].Names.end(), import.Library) != guestModules[index].Names.end(); if (import.Library.empty() || name == library || guestName) order.push_back(CheckedRva(index)); + if (import.TargetModule < 0 && !import.Library.empty() && index < guestModules.size() && (name == library || guestName)) executableImports[index] = true; } if (order.empty()) throw Domain::RelinkerException("Import module is not a dependency: " + import.Library); const bool hostModule = !import.Library.empty() && std::any_of(order.begin(), order.end(), [&](auto index) { return index >= guestModules.size(); }); if (hostModule) { + if (library == "libkernel_sys.prx" || library == "libkernel_web.prx") { + for (std::size_t index = guestModules.size(); index < libraries.size(); ++index) { + if (libraries[index] == "libkernel.prx") order.push_back(CheckedRva(index)); + } + } for (std::size_t index = guestModules.size(); index < libraries.size(); ++index) { if (libraries[index] == "libc.prx" && library != "libc.prx") order.push_back(CheckedRva(index)); } @@ -244,6 +260,11 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con const auto searched = addString("Searched libraries:\n"); const auto indent = addString(" "); const auto enteringElf = addString("Transferring control to ELF entry point\n"); + std::vector deferredImports; + for (std::size_t index = 0; index < guestModules.size(); ++index) { + if (guestModules[index].DeferInitialization && executableImports[index]) + deferredImports.push_back(addString("Deferred module " + guestModules[index].Path + ": the executable imports from it, but it starts only when the title loads it\n")); + } std::vector resolvedPaths; for (std::size_t index = 0; index < libraries.size(); ++index) resolvedPaths.push_back(reserve(PathCapacity)); @@ -401,7 +422,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con code.U32(units); code.Emit({0x66, 0xf3, 0xa5}); code.Rip({0x48, 0x8d, 0x0d}, modulePath); - code.Emit({0xba, 0, 0, 0, 0x40, 0x41, 0xb8, 3, 0, 0, 0, 0x45, 0x31, 0xc9}); + code.Emit({0xba, 4, 0, 0, 0, 0x41, 0xb8, 3, 0, 0, 0, 0x45, 0x31, 0xc9}); code.Emit({0x48, 0xc7, 0x44, 0x24, 0x20, 2, 0, 0, 0, 0x48, 0xc7, 0x44, 0x24, 0x28, 0x80, 0, 0, 0, 0x48, 0xc7, 0x44, 0x24, 0x30, 0, 0, 0, 0}); call("CreateFileW"); code.Emit({0x66, 0x41, 0xc7, 0x04, 0x24, 0x00, 0x00}); @@ -419,6 +440,51 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con code.PatchBranch(failed, done); }; + const auto rebindPreloadedRuntimeLog = [&](const std::uint32_t runtimeLibrary, const std::uint32_t iobFunctionName, const bool indexedIob) { + code.Rip({0x48, 0x8d, 0x0d}, runtimeLibrary); + call("GetModuleHandleW"); + code.Emit({0x48, 0x85, 0xc0}); + std::vector absent = {code.Branch({0x0f, 0x84})}; + code.Rip({0x48, 0x89, 0x05}, runtimeModule); + code.Emit({0x48, 0x89, 0xc1}); + code.Rip({0x48, 0x8d, 0x15}, iobFunctionName); + call("GetProcAddress"); + code.Emit({0x48, 0x85, 0xc0}); + absent.push_back(code.Branch({0x0f, 0x84})); + code.Rip({0x48, 0x89, 0x05}, runtimeIobFunction); + code.Rip({0x48, 0x8b, 0x0d}, runtimeModule); + code.Rip({0x48, 0x8d, 0x15}, runtimeFreopenName); + call("GetProcAddress"); + code.Emit({0x48, 0x85, 0xc0}); + absent.push_back(code.Branch({0x0f, 0x84})); + code.Rip({0x48, 0x89, 0x05}, runtimeFreopenFunction); + for (const auto& stream : {std::pair{0xfffffff5u, 1}, {0xfffffff4u, 2}}) { + code.Emit({0xb9}); + code.U32(stream.first); + call("GetStdHandle"); + code.Emit({0x48, 0x85, 0xc0}); + const auto missing = code.Branch({0x0f, 0x84}); + code.Rip({0x48, 0x3b, 0x05}, logHandle); + const auto notRedirected = code.Branch({0x0f, 0x85}); + if (indexedIob) { + code.Emit({0xb9, stream.second, 0, 0, 0}); + code.Rip({0xff, 0x15}, runtimeIobFunction); + code.Emit({0x49, 0x89, 0xc0}); + } else { + code.Rip({0xff, 0x15}, runtimeIobFunction); + code.Emit({0x4c, 0x8d, 0x40, static_cast(48 * stream.second)}); + } + code.Rip({0x48, 0x8d, 0x0d}, logName); + code.Rip({0x48, 0x8d, 0x15}, runtimeAppendMode); + code.Rip({0xff, 0x15}, runtimeFreopenFunction); + const auto next = code.GetRva(); + code.PatchBranch(missing, next); + code.PatchBranch(notRedirected, next); + } + const auto done = code.GetRva(); + for (const auto branch : absent) code.PatchBranch(branch, done); + }; + code.Emit({0x53, 0x55, 0x56, 0x57, 0x41, 0x54, 0x48, 0x83, 0xec, 0x60}); code.Rip({0x48, 0x8d, 0x0d}, parentVariable); code.Rip({0x48, 0x8d, 0x15}, parentText); @@ -493,6 +559,8 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con code.Rip({0x48, 0x8d, 0x0d}, modulePath); call("SetCurrentDirectoryW"); requireNonzero(directoryError, 0xc000000du); + rebindPreloadedRuntimeLog(msvcrtLibrary, msvcrtIobFunctionName, false); + rebindPreloadedRuntimeLog(ucrtbaseLibrary, ucrtbaseIobFunctionName, true); if (dependencyDiagnostics) { code.Rip({0x4c, 0x8d, 0x05}, programPath); writeUtf8(programText); @@ -712,6 +780,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con code.Emit({0x85, 0xc0}); unresolvedBranches.push_back(code.Branch({0x0f, 0x85})); } + for (const auto message : deferredImports) writeString(message); guestStartup.Initialize(code, guestModules, handles); writeString(enteringElf); code.Rip({0x48, 0x8d, 0x0d}, shellLibrary); diff --git a/core/relinker/elfpatcher/src/windows/WindowsImportBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsImportBuilder.cpp index e7153da217..f2d45060be 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsImportBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsImportBuilder.cpp @@ -53,10 +53,15 @@ std::vector WindowsImportBuilder::ReadLibraries(const Domain::SysVD // AnyPS5 implements the C runtime in libc.prx. Windows GetProcAddress // does not search a module's dependencies as ELF symbol lookup does. // Keep the requested module first, then make its shared runtime visible. - if ((unique.contains("libSceLibcInternal.prx") || unique.contains("libkernel.prx")) && !unique.contains("libc.prx")) - result.push_back("libc.prx"); - if (unique.contains("libkernel_web.prx") && !unique.contains("libkernel.prx")) - result.push_back("libkernel.prx"); + const auto addIfMissing = [&](const char* library) { + if (unique.insert(library).second) result.emplace_back(library); + }; + if (unique.contains("libSceLibcInternal.prx") || unique.contains("libkernel.prx")) + addIfMissing("libc.prx"); + if (unique.contains("libkernel_sys.prx") || unique.contains("libkernel_web.prx")) { + addIfMissing("libkernel.prx"); + addIfMissing("libc.prx"); + } return result; } diff --git a/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp b/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp index 063af7924b..36e88bf315 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp @@ -39,7 +39,7 @@ std::vector WindowsPePatcher::Patch(std::vector sour const auto report = [&](std::uint64_t completed) { if (progress) progress(completed, 10000); }; report(0); diagnostics.clear(); - WindowsLoadImage image(sourceElf, originalHeaders, true, fixedBase ? *fixedBase - LoadRva : ImageBase); + WindowsLoadImage image(sourceElf, originalHeaders, true, fixedBase.value_or(ExecutableBase) - LoadRva); if (originalPltGotVaddr != 0) image.GetRva(originalPltGotVaddr, 8); report(500); @@ -111,7 +111,7 @@ std::vector WindowsPePatcher::Patch(std::vector sour } directories[2] = icon ? WindowsIconResourceBuilder().Build(*icon, iconName, sections, nextRva) : PeDirectory{}; report(9500); - auto result = WindowsPeWriter().Write(sections, entryRva, directories, windowsGui, fixedBase ? std::optional(image.GetImageBase()) : std::nullopt, false); + auto result = WindowsPeWriter().Write(sections, entryRva, directories, windowsGui, image.GetImageBase(), fixedBase.has_value(), false); report(10000); return result; } diff --git a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp index 48d867f0f5..96dfbf91b3 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp @@ -5,7 +5,7 @@ namespace Elfpatcher::Windows { -std::vector WindowsPeWriter::Write(const std::vector& sections, const std::uint32_t entryRva, const std::array& directories, const bool windowsGui, const std::optional fixedImageBase, const bool dynamicBase) const { +std::vector WindowsPeWriter::Write(const std::vector& sections, const std::uint32_t entryRva, const std::array& directories, const bool windowsGui, const std::uint64_t imageBase, const bool fixedBase, const bool dynamicBase) const { constexpr std::size_t peOffset = 0x80; constexpr std::size_t optionalOffset = peOffset + 24; constexpr std::size_t sectionTable = optionalOffset + 240; @@ -18,12 +18,12 @@ std::vector WindowsPeWriter::Write(const std::vector& s Io::WriteU16(result, peOffset + 4, 0x8664); Io::WriteU16(result, peOffset + 6, static_cast(sections.size())); Io::WriteU16(result, peOffset + 20, 240); - if (fixedImageBase && (directories[5].Size != 0 || *fixedImageBase < 0x10000 || (*fixedImageBase & 0xffff) != 0)) - throw Domain::RelinkerException("Invalid fixed Windows image base or base relocations"); - Io::WriteU16(result, peOffset + 22, fixedImageBase ? 0x23 : 0x22); + if (imageBase < 0x10000 || (imageBase & 0xffff) != 0 || (fixedBase && directories[5].Size != 0)) + throw Domain::RelinkerException("Invalid Windows image base or base relocations"); + Io::WriteU16(result, peOffset + 22, fixedBase ? 0x23 : 0x22); Io::WriteU16(result, optionalOffset, 0x20b); Io::WriteU32(result, optionalOffset + 16, entryRva); - Io::WriteU64(result, optionalOffset + 24, fixedImageBase.value_or(ImageBase)); + Io::WriteU64(result, optionalOffset + 24, imageBase); Io::WriteU32(result, optionalOffset + 32, SectionAlignment); Io::WriteU32(result, optionalOffset + 36, FileAlignment); Io::WriteU16(result, optionalOffset + 40, 6); @@ -72,7 +72,7 @@ std::vector WindowsPeWriter::Write(const std::vector& s } if (!foundEntry) throw Domain::RelinkerException("PE entry point is not inside executable section data", entryRva); - if (fixedImageBase && *fixedImageBase > 0x7fffffffffff - static_cast(endRva)) + if (fixedBase && imageBase > 0x7fffffffffff - static_cast(endRva)) throw Domain::RelinkerException("Fixed Windows image exceeds the user address space"); Io::WriteU32(result, optionalOffset + 4, codeSize); Io::WriteU32(result, optionalOffset + 8, dataSize); diff --git a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp index 71b149d37f..7bd368ac26 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp @@ -86,8 +86,6 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom std::vector WindowsRelocationBuilder::BuildBaseRelocations(const std::vector& targets) const { std::map> pages; for (const auto target : targets) { - if ((target & 0xfffu) > 0x1000u - 8) - throw Domain::RelinkerException("Base relocation target crosses a page boundary", target); auto& entries = pages[target & ~0xfffu]; const auto entry = static_cast(0xa000u | (target & 0xfffu)); if (std::find(entries.begin(), entries.end(), entry) == entries.end()) diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp index b78a010b40..f923bafd63 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp @@ -221,8 +221,14 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co } report(9000); - for (const auto& [rva, length] : gsTraps) + std::sort(branchTargets.begin(), branchTargets.end()); + branchTargets.erase(std::unique(branchTargets.begin(), branchTargets.end()), branchTargets.end()); + for (const auto& [rva, length] : gsTraps) { + const auto target = std::upper_bound(branchTargets.begin(), branchTargets.end(), rva); + if (target != branchTargets.end() && *target < rva + length) + throw Domain::RelinkerException("Branch enters a guest GS instruction", *target); trapInstruction(sections, rva, length); + } if (tls == nullptr) { if (!accesses.empty()) throw Domain::RelinkerException("Guest TLS access without PT_TLS"); @@ -274,9 +280,6 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co code.PatchBranch(skipCallback, code.GetRva()); code.Emit({0xc3}); - std::sort(branchTargets.begin(), branchTargets.end()); - branchTargets.erase(std::unique(branchTargets.begin(), branchTargets.end()), branchTargets.end()); - std::uint64_t patched = 0; for (const auto& access : accesses) { if (patched % 256 == 0) report(9000 + 900 * patched / accesses.size()); diff --git a/core/relinker/elfpatcher/tests/EntryStubTests.cpp b/core/relinker/elfpatcher/tests/EntryStubTests.cpp index 5b3b8a78b1..1fe8dc32e9 100644 --- a/core/relinker/elfpatcher/tests/EntryStubTests.cpp +++ b/core/relinker/elfpatcher/tests/EntryStubTests.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #include @@ -116,6 +118,48 @@ void windowsStubRejectsAnOverlongAllocatorPath() { throw std::runtime_error("an allocator path past the startup buffer was accepted"); } +void windowsLibSceAmprObjectImportsFallBackToTheLibcProvider() { + using namespace Elfpatcher::Windows; + const auto nativeImports = WindowsImportBuilder().Build(0x1000); + const std::vector libraries{"libSceAmpr.prx", "libc.prx"}; + const std::vector amprImport{{.Name = "-mLzBSk-VGs", .TargetRva = 0x2000, .Addend = 0, .Library = "libSceAmpr.prx"}}; + const auto amprStub = WindowsEntryStubBuilder().Build(0x40000, 0x1000, nativeImports, libraries, amprImport, "$ORIGIN/libs", false, false); + const std::vector fallbackOrder{0, 0, 0, 0, 1, 0, 0, 0}; + require(std::search(amprStub.Data.Data.begin(), amprStub.Data.Data.end(), fallbackOrder.begin(), fallbackOrder.end()) != amprStub.Data.Data.end(), + "a libSceAmpr-qualified locale object import does not fall back to libc"); + + const std::vector libcImport{{.Name = "-mLzBSk-VGs", .TargetRva = 0x2000, .Addend = 0, .Library = "libc.prx"}}; + const auto libcStub = WindowsEntryStubBuilder().Build(0x40000, 0x1000, nativeImports, libraries, libcImport, "$ORIGIN/libs", false, false); + const std::vector libcOrder{1, 0, 0, 0}; + require(std::search(libcStub.Data.Data.begin(), libcStub.Data.Data.end(), libcOrder.begin(), libcOrder.end()) != libcStub.Data.Data.end(), + "a libc-qualified locale object import does not resolve through libc"); +} + +void windowsKernelVariantImportsSearchCanonicalKernelAfterTheRequestedVariant() { + using namespace Elfpatcher::Windows; + const auto nativeImports = WindowsImportBuilder().Build(0x1000); + const std::vector libraries{"libkernel_sys.prx", "libc.prx", "libkernel.prx"}; + const std::vector imports{{.Name = "getpagesize", .TargetRva = 0x2000, .Addend = 0, .Library = "libkernel_sys.prx"}}; + const auto stub = WindowsEntryStubBuilder().Build(0x40000, 0x1000, nativeImports, libraries, imports, "$ORIGIN/libs", false, false); + const std::vector expected{0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0}; + require(std::search(stub.Data.Data.begin(), stub.Data.Data.end(), expected.begin(), expected.end()) != stub.Data.Data.end(), + "a scoped libkernel_sys import does not search the requested variant before canonical libkernel and libc"); +} + +void windowsKernelVariantDependenciesLoadCanonicalKernel() { + using namespace Elfpatcher::Windows; + for (const auto& variant : {std::string("libkernel_sys.prx"), std::string("libkernel_web.prx")}) { + Domain::SysVDynamicSection dynamic; + dynamic.DynStrData.insert(dynamic.DynStrData.end(), variant.begin(), variant.end()); + dynamic.DynStrData.push_back(0); + dynamic.DynamicSegmentData.resize(16); + Io::WriteU64(dynamic.DynamicSegmentData, 0, 1); + Io::WriteU64(dynamic.DynamicSegmentData, 8, 0); + require(WindowsImportBuilder().ReadLibraries(dynamic) == std::vector{variant, "libkernel.prx", "libc.prx"}, + variant + ": canonical kernel fallback and its libc provider are not loaded after the requested module"); + } +} + } int main() { @@ -126,6 +170,9 @@ int main() { windowsStubLoadsTheAllocatorOnRequest("C:/Games/Title/libs", u"C:\\Games\\Title\\libs\\", {0x48, 0x8d, 0x0d}); windowsStubLoadsTheAllocatorOnRequest("$ORIGIN/libs", u"libs\\", {0x48, 0x8d, 0x35}); windowsStubRejectsAnOverlongAllocatorPath(); + windowsLibSceAmprObjectImportsFallBackToTheLibcProvider(); + windowsKernelVariantImportsSearchCanonicalKernelAfterTheRequestedVariant(); + windowsKernelVariantDependenciesLoadCanonicalKernel(); } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; diff --git a/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp b/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp index 18748830de..c86b61da23 100644 --- a/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp +++ b/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp @@ -149,6 +149,27 @@ int main() { bool stackSlotRejected = false; try { (void)writeSingle(6); } catch (const std::exception&) { stackSlotRejected = true; } Require(stackSlotRejected, "layout must refuse to drop the synthetic stack when its slot is missing"); - std::cout << "21 program layout cases passed\n"; + constexpr std::uint16_t extendedCount = 0xffff; + std::vector manyHeaders(extendedCount - Elfpatcher::kSyntheticProgramHeaderCount - 2); + manyHeaders.front() = singleLoad.front(); + Require(builder.RequiredProgramHeaderCount(manyHeaders) == extendedCount - 1, "largest ordinary count changed"); + const auto writeMany = [&builder](const std::vector& headers, std::vector& bytes, std::uint16_t count) { + const std::uint64_t extraOffset = 0x1000; + const std::uint64_t extraSize = static_cast(count) * 56; + Elfpatcher::ProgramHeaderLayoutRequest request{extraOffset, 56, count, headers, extraOffset, + builder.ComputeExtraBlockVaddr(headers, extraOffset), extraSize, extraOffset, 16, extraOffset + 16, 8}; + return builder.WriteLayout(bytes, request); + }; + std::vector largeOutput(0x1000 + static_cast(extendedCount) * 56, 0xA5); + Require(writeMany(manyHeaders, largeOutput, extendedCount - 1) == extendedCount - 1, "largest ordinary layout rejected"); + manyHeaders.push_back({}); + rejected = false; + try { builder.RequiredProgramHeaderCount(manyHeaders); } catch (const Domain::RelinkerException&) { rejected = true; } + Require(rejected, "extended program header count accepted"); + const auto beforeLarge = largeOutput; + rejected = false; + try { writeMany(manyHeaders, largeOutput, extendedCount); } catch (const Domain::RelinkerException&) { rejected = true; } + Require(rejected && largeOutput == beforeLarge, "extended count written without section-zero encoding"); + std::cout << "25 program layout cases passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } } diff --git a/core/relinker/elfpatcher/tests/SectionRangeTests.cpp b/core/relinker/elfpatcher/tests/SectionRangeTests.cpp index c75c4e9773..b5ff0514a0 100644 --- a/core/relinker/elfpatcher/tests/SectionRangeTests.cpp +++ b/core/relinker/elfpatcher/tests/SectionRangeTests.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include #include @@ -29,6 +31,30 @@ int main() { auto empty = valid; empty.StubOffset = 384; empty.StubSize = 0; bytes = Fixture(); builder.WriteTable(bytes, empty); Require(Io::ReadU64(bytes, Io::ReadU64(bytes, 40) + 5 * 64 + 16) == 0x1100); + for (std::size_t tail = 0; tail < 8; ++tail) { + bytes = Fixture(); + bytes.resize(512 + tail, 0xa5); + const auto original = bytes; + const auto originalSize = bytes.size(); + builder.WriteTable(bytes, valid); + const auto alignedTable = Io::ReadU64(bytes, 40); + if (alignedTable % 8 != 0) + throw std::runtime_error("ELF64 section header table is not 8-byte aligned"); + const auto stringOffset = Io::ReadU64(bytes, alignedTable + 64 + 24); + const auto stringSize = Io::ReadU64(bytes, alignedTable + 64 + 32); + const auto stringEnd = stringOffset + stringSize; + Require(stringOffset == originalSize && stringSize == sizeof(Elfpatcher::kShStrTabBlob)); + Require(stringEnd <= alignedTable && alignedTable - stringEnd < 8); + Require(bytes[stringOffset] == 0 && bytes[stringEnd - 1] == 0); + Require(std::equal(original.begin() + 64, original.end(), bytes.begin() + 64)); + Require(std::all_of(bytes.begin() + stringEnd, bytes.begin() + alignedTable, + [](const auto byte) { return byte == 0; })); + Require(bytes.size() == alignedTable + Io::ReadU16(bytes, 60) * 64); + Require(Io::ReadU64(bytes, alignedTable + 2 * 64 + 24) == valid.DynStrOffset); + Require(Io::ReadU64(bytes, alignedTable + 3 * 64 + 24) == valid.DynSymOffset); + Require(Io::ReadU64(bytes, alignedTable + 4 * 64 + 24) == valid.DynamicSegmentOffset); + Require(Io::ReadU64(bytes, alignedTable + 5 * 64 + 24) == valid.StubOffset); + } for (int kind = 0; kind < 11; ++kind) { bytes = Fixture(); auto request = valid; switch (kind) { @@ -49,6 +75,6 @@ int main() { try { builder.WriteTable(bytes, request); } catch (const std::runtime_error&) { rejected = true; } Require(rejected && bytes == before); } - std::cout << "13 section range cases passed\n"; + std::cout << "21 section range and alignment cases passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } } diff --git a/core/relinker/elfpatcher/tests/WindowsDependencyDiagnosticsTests.cpp b/core/relinker/elfpatcher/tests/WindowsDependencyDiagnosticsTests.cpp index fa6c1d4085..45ce2bf954 100644 --- a/core/relinker/elfpatcher/tests/WindowsDependencyDiagnosticsTests.cpp +++ b/core/relinker/elfpatcher/tests/WindowsDependencyDiagnosticsTests.cpp @@ -55,6 +55,8 @@ void checkRuntimeDependencies() { {"libc.prx", "libSceLibcInternal.prx"}); check({"libSceVideoOut.prx", "libkernel.prx", "libSceVideoOut.prx"}, {"libSceVideoOut.prx", "libkernel.prx", "libc.prx"}); + check({"libkernel_sys.prx"}, {"libkernel_sys.prx", "libkernel.prx", "libc.prx"}); + check({"libkernel_web.prx"}, {"libkernel_web.prx", "libkernel.prx", "libc.prx"}); check({"libSceVideoOut.prx", "libSceVideoOut.prx"}, {"libSceVideoOut.prx"}); } @@ -240,8 +242,8 @@ void expectDiagnostic(const Fs::path& runner, const std::vector& ex int main() { try { checkRuntimeDependencies(); - std::vector filename(32768); - const auto size = GetModuleFileNameA(nullptr, filename.data(), static_cast(filename.size())); + std::vector filename(32768); + const auto size = GetModuleFileNameW(nullptr, filename.data(), static_cast(filename.size())); if (size == 0 || size >= filename.size()) throw std::runtime_error("Cannot locate diagnostic tests"); const auto directory = createFixtureDirectory(Fs::path(filename.data()).parent_path()); diff --git a/core/relinker/elfpatcher/tests/WindowsImportIdentityTests.cpp b/core/relinker/elfpatcher/tests/WindowsImportIdentityTests.cpp new file mode 100644 index 0000000000..03243cea6b --- /dev/null +++ b/core/relinker/elfpatcher/tests/WindowsImportIdentityTests.cpp @@ -0,0 +1,162 @@ +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +namespace Fs = std::filesystem; +using namespace Elfpatcher::Windows; + +constexpr std::uint32_t GotRva = LoadRva + 0x1000; +constexpr std::array LocaleObjects{"-mLzBSk-VGs", "a54t8+k7KpY", "KfcTPbeaOqg"}; + +void require(const bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +Fs::path createFixtureDirectory(const Fs::path& parent) { + std::random_device random; + for (int attempt = 0; attempt < 100; ++attempt) { + auto name = Fs::path("windows-import-identity-"); + name += std::to_string(GetCurrentProcessId()) + "-" + std::to_string(random()); + const auto directory = parent / name; + if (Fs::create_directory(directory)) return directory; + } + throw std::runtime_error("Cannot create a unique import identity fixture directory"); +} + +void appendDisplacement(std::vector& code, const std::int32_t displacement) { + for (int i = 0; i < 4; ++i) code.push_back(static_cast((static_cast(displacement) >> (8 * i)) & 0xffu)); +} + +// For each pair of GOT slots: mov rax,[a]; cmp rax,[b]; jne fail. Then xor eax,eax; ret. fail: mov eax,1; ret. +std::vector compareCode(const std::vector>& pairs) { + std::vector code; + const auto failOffset = static_cast(pairs.size() * 16 + 3); + for (std::size_t i = 0; i < pairs.size(); ++i) { + const auto base = static_cast(i * 16); + code.insert(code.end(), {0x48, 0x8b, 0x05}); + appendDisplacement(code, static_cast(GotRva + 8u * static_cast(pairs[i].first)) - static_cast(LoadRva + base + 7)); + code.insert(code.end(), {0x48, 0x3b, 0x05}); + appendDisplacement(code, static_cast(GotRva + 8u * static_cast(pairs[i].second)) - static_cast(LoadRva + base + 14)); + code.push_back(0x75); + code.push_back(static_cast(failOffset - static_cast(base + 16))); + } + code.insert(code.end(), {0x31, 0xc0, 0xc3}); + code.insert(code.end(), {0xb8, 0x01, 0x00, 0x00, 0x00, 0xc3}); + return code; +} + +std::uint32_t slot(const int index) { + return GotRva + 8u * static_cast(index); +} + +void generateProbe(const Fs::path& file, const std::string& libraries, const std::vector& peImports, const std::vector>& pairs) { + const std::vector loaded{"libSceAmpr.prx", "libkernel.prx", "libc.prx"}; + PeSection entry{".guest", LoadRva, SectionRead | SectionExecute | 0x20u, compareCode(pairs)}; + PeSection got{".got", GotRva, SectionRead | SectionWrite | 0x40u, std::vector(8 * 8)}; + const auto imports = WindowsImportBuilder().Build(LoadRva + 0x2000); + const auto startup = WindowsEntryStubBuilder().Build(LoadRva + 0x3000, entry.Rva, imports, loaded, peImports, libraries, false, false); + std::array directories{}; + directories[1] = imports.Directory; + directories[3] = startup.ExceptionDirectory; + directories[12] = imports.AddressTable; + const auto image = WindowsPeWriter().Write({entry, got, imports.Section, startup.Data, startup.Code}, startup.Code.Rva, directories, false, ImageBase, true, false); + std::ofstream stream(file, std::ios::binary); + stream.write(reinterpret_cast(image.data()), static_cast(image.size())); + require(static_cast(stream), "Cannot write " + file.string()); +} + +DWORD runProbe(const Fs::path& probe, std::string& output) { + SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; + HANDLE input = nullptr; + HANDLE written = nullptr; + require(CreatePipe(&input, &written, &security, 0) && SetHandleInformation(input, HANDLE_FLAG_INHERIT, 0), "Cannot create the probe's pipe"); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + startup.dwFlags = STARTF_USESTDHANDLES; + startup.hStdOutput = written; + startup.hStdError = written; + startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + PROCESS_INFORMATION process{}; + const auto filename = probe.wstring(); + require(CreateProcessW(filename.c_str(), nullptr, nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, probe.parent_path().wstring().c_str(), &startup, &process) != 0, + "Cannot start " + probe.filename().string() + ": " + std::to_string(GetLastError())); + CloseHandle(written); + if (WaitForSingleObject(process.hProcess, 120000) != WAIT_OBJECT_0) { + TerminateProcess(process.hProcess, 1); + throw std::runtime_error(probe.filename().string() + " timed out"); + } + DWORD status = 0; + require(GetExitCodeProcess(process.hProcess, &status) != 0, "Cannot read the probe's exit code"); + char buffer[4096]; + DWORD size = 0; + while (ReadFile(input, buffer, sizeof(buffer), &size, nullptr) && size != 0) output.append(buffer, size); + CloseHandle(input); + CloseHandle(process.hThread); + CloseHandle(process.hProcess); + return status; +} + +// The generated probes load libc.prx next to the other libraries; its compiler runtime DLLs sit next to this test. +void copyRuntime(const Fs::path& directory) { + wchar_t self[MAX_PATH]; + const auto length = GetModuleFileNameW(nullptr, self, MAX_PATH); + if (length == 0 || length >= MAX_PATH) return; + const auto own = Fs::path(self).parent_path(); + for (const char* name : {"libgcc_s_seh-1.dll", "libstdc++-6.dll", "libwinpthread-1.dll"}) { + std::error_code error; + if (Fs::exists(own / name, error)) Fs::copy_file(own / name, directory / name, Fs::copy_options::overwrite_existing, error); + } +} + +} + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "usage: windows_import_identity_tests \n"; + return 2; + } + try { + const auto libraries = Fs::absolute(argv[1]).string(); + const auto directory = createFixtureDirectory(Fs::temp_directory_path()); + copyRuntime(directory); + std::vector same; + for (int i = 0; i < 3; ++i) { + same.push_back({.Name = LocaleObjects[i], .TargetRva = slot(2 * i), .Addend = 0, .Library = "libSceAmpr.prx"}); + same.push_back({.Name = LocaleObjects[i], .TargetRva = slot(2 * i + 1), .Addend = 0, .Library = "libc.prx"}); + } + generateProbe(directory / "same.exe", libraries, same, {{0, 1}, {2, 3}, {4, 5}}); + const std::vector different{ + {.Name = LocaleObjects[0], .TargetRva = slot(0), .Addend = 0, .Library = "libSceAmpr.prx"}, + {.Name = "k+AXqu2-eBc", .TargetRva = slot(6), .Addend = 0, .Library = "libkernel.prx"}, + }; + generateProbe(directory / "different.exe", libraries, different, {{0, 6}}); + std::string output; + const auto sameStatus = runProbe(directory / "same.exe", output); + require(sameStatus == 0, "A libSceAmpr.prx-qualified import of the locale objects did not resolve to libc's object (exit " + std::to_string(sameStatus) + ")\n" + output); + output.clear(); + const auto differentStatus = runProbe(directory / "different.exe", output); + require(differentStatus == 1, "The probe did not tell two different addresses apart (exit " + std::to_string(differentStatus) + ")\n" + output); + std::error_code error; + Fs::remove_all(directory, error); + std::cout << "Windows import identity tests passed\n"; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } + return 0; +} diff --git a/core/relinker/elfpatcher/tests/WindowsMsvcrt.def b/core/relinker/elfpatcher/tests/WindowsMsvcrt.def index c163db8f44..741b17b119 100644 --- a/core/relinker/elfpatcher/tests/WindowsMsvcrt.def +++ b/core/relinker/elfpatcher/tests/WindowsMsvcrt.def @@ -2,3 +2,4 @@ LIBRARY msvcrt.dll EXPORTS __iob_func fputs + fflush diff --git a/core/relinker/elfpatcher/tests/WindowsMsvcrtStdioHost.cpp b/core/relinker/elfpatcher/tests/WindowsMsvcrtStdioHost.cpp index 4c370e0924..2a77b5b41f 100644 --- a/core/relinker/elfpatcher/tests/WindowsMsvcrtStdioHost.cpp +++ b/core/relinker/elfpatcher/tests/WindowsMsvcrtStdioHost.cpp @@ -13,10 +13,13 @@ static_assert(sizeof(_iobuf) == 48); extern "C" _iobuf* __cdecl __iob_func(); extern "C" int __cdecl fputs(const char*, _iobuf*); +extern "C" int __cdecl fflush(_iobuf*); extern "C" int shared() { auto* streams = __iob_func(); fputs("Windows DLL stdout probe\n", streams + 1); fputs("Windows DLL stderr probe\n", streams + 2); + fflush(streams + 1); + fflush(streams + 2); return 11; } diff --git a/core/relinker/elfpatcher/tests/WindowsStdioHost.cpp b/core/relinker/elfpatcher/tests/WindowsStdioHost.cpp index 72a29373f4..3d996c8df4 100644 --- a/core/relinker/elfpatcher/tests/WindowsStdioHost.cpp +++ b/core/relinker/elfpatcher/tests/WindowsStdioHost.cpp @@ -3,5 +3,7 @@ extern "C" int shared() { std::fputs("Windows DLL stdout probe\n", stdout); std::fputs("Windows DLL stderr probe\n", stderr); + std::fflush(stdout); + std::fflush(stderr); return 11; } diff --git a/core/relinker/io/include/io/FileReader.hpp b/core/relinker/io/include/io/FileReader.hpp index bf5d2fa85a..1056ab33a1 100644 --- a/core/relinker/io/include/io/FileReader.hpp +++ b/core/relinker/io/include/io/FileReader.hpp @@ -2,11 +2,13 @@ #define IO_FILEREADER_HPP #include +#include namespace Io { class FileReader : public IFileReader { public: + std::vector ReadPrefix(const std::string& path, std::size_t size) const; std::vector Read(const std::string& path) override; }; diff --git a/core/relinker/io/src/FileIdentity.cpp b/core/relinker/io/src/FileIdentity.cpp index 351058935a..5f2b5cc967 100644 --- a/core/relinker/io/src/FileIdentity.cpp +++ b/core/relinker/io/src/FileIdentity.cpp @@ -47,6 +47,18 @@ bool SameFile(const std::filesystem::path& first, const std::filesystem::path& s #ifdef _WIN32 return FileIdentity(first) == FileIdentity(second); #else + for (const auto* path : {&first, &second}) { + std::error_code error; + const auto status = std::filesystem::status(*path, error); + if (error) throw std::filesystem::filesystem_error("Cannot determine file identity", *path, error); + if (!std::filesystem::exists(status)) { + throw std::filesystem::filesystem_error( + "Cannot determine file identity", + *path, + std::make_error_code(std::errc::no_such_file_or_directory) + ); + } + } return std::filesystem::equivalent(first, second); #endif } diff --git a/core/relinker/io/src/FileReader.cpp b/core/relinker/io/src/FileReader.cpp index 228903ab48..4f012529d3 100644 --- a/core/relinker/io/src/FileReader.cpp +++ b/core/relinker/io/src/FileReader.cpp @@ -3,9 +3,29 @@ #include #include #include +#include namespace Io { +std::vector FileReader::ReadPrefix(const std::string& path, const std::size_t size) const { + if (size > static_cast(std::numeric_limits::max())) + throw Domain::RelinkerException("File prefix is too large: " + path); + const auto native = NativePath(path); + std::error_code error; + if (!std::filesystem::is_regular_file(native, error)) + throw Domain::RelinkerException("Cannot open file: " + path); + std::ifstream f(native, std::ios::binary); + if (!f) + throw Domain::RelinkerException("Cannot open file: " + path); + std::vector buf(size); + f.read(reinterpret_cast(buf.data()), static_cast(size)); + const auto count = f.gcount(); + if (f.bad()) + throw Domain::RelinkerException("Cannot read file: " + path); + buf.resize(static_cast(count)); + return buf; +} + std::vector FileReader::Read(const std::string& path) { const auto native = NativePath(path); std::error_code error; diff --git a/core/relinker/io/src/FileWriter.cpp b/core/relinker/io/src/FileWriter.cpp index 49df133411..10606d09bb 100644 --- a/core/relinker/io/src/FileWriter.cpp +++ b/core/relinker/io/src/FileWriter.cpp @@ -33,11 +33,10 @@ int createTemporary(const std::filesystem::path& target, const mode_t mode, std: constexpr std::string_view characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; std::random_device device; for (int attempt = 0; attempt < 100; ++attempt) { - std::string suffix = "."; + std::string suffix = ".anyps5-"; for (int index = 0; index < 6; ++index) suffix += characters[device() % characters.size()]; - temporary = target; - temporary += suffix; + temporary = target.parent_path() / suffix; const int descriptor = open(temporary.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode); if (descriptor >= 0 || errno != EEXIST) return descriptor; diff --git a/core/relinker/io/src/NativePath.cpp b/core/relinker/io/src/NativePath.cpp index 13b11fa505..ad603ef310 100644 --- a/core/relinker/io/src/NativePath.cpp +++ b/core/relinker/io/src/NativePath.cpp @@ -1,8 +1,11 @@ #include +#include namespace Io { std::filesystem::path NativePath(const std::string& path) { + if (path.find('\0') != std::string::npos) + throw std::invalid_argument("File path contains a NUL byte"); const std::u8string utf8(path.begin(), path.end()); return std::filesystem::path(utf8); } diff --git a/core/relinker/io/tests/FileIdentityTests.cpp b/core/relinker/io/tests/FileIdentityTests.cpp index 41e6bdc5e7..763b71af1c 100644 --- a/core/relinker/io/tests/FileIdentityTests.cpp +++ b/core/relinker/io/tests/FileIdentityTests.cpp @@ -17,10 +17,16 @@ void write(const std::filesystem::path& path, const std::string& text) { require(std::filesystem::is_regular_file(path), "Cannot create " + path.string()); } -void requireMissing(const std::filesystem::path& first, const std::filesystem::path& second) { +void requireMissing( + const std::filesystem::path& first, + const std::filesystem::path& second, + const std::filesystem::path& missing +) { try { Io::SameFile(first, second); - } catch (const std::filesystem::filesystem_error&) { + } catch (const std::filesystem::filesystem_error& error) { + require(error.code() == std::errc::no_such_file_or_directory, "SameFile reported the wrong error for a missing path"); + require(error.path1() == missing, "SameFile reported the wrong missing path"); return; } throw std::runtime_error("SameFile accepted a missing file"); @@ -41,8 +47,9 @@ void run(const std::filesystem::path& directory) { require(!Io::SameFile(first, copy), "A copy with the same contents is the same file"); require(Io::SameFile(folder, directory / "folder" / "."), "Two spellings of one directory differ"); require(!Io::SameFile(first, folder), "A file is the same as a directory"); - requireMissing(directory / "missing.bin", first); - requireMissing(first, directory / "missing.bin"); + const auto missing = directory / "missing.bin"; + requireMissing(missing, first, missing); + requireMissing(first, missing, missing); } } diff --git a/core/relinker/io/tests/FileWriterTests.cpp b/core/relinker/io/tests/FileWriterTests.cpp index 8ec3b79abd..7ab1724d30 100644 --- a/core/relinker/io/tests/FileWriterTests.cpp +++ b/core/relinker/io/tests/FileWriterTests.cpp @@ -40,15 +40,15 @@ std::vector siblings(const std::filesystem::path& output) { std::vector names; for (const auto& entry : std::filesystem::directory_iterator(output.parent_path())) { const auto name = entry.path().filename().string(); - if (name.starts_with(output.filename().string() + ".")) + if (name.starts_with(output.filename().string() + ".") || name.starts_with(".anyps5-")) names.push_back(name); } return names; } template -void requireRunningReplacement(const std::filesystem::path& directory, const TValue& data, const bool leftover) { - const auto output = directory / (leftover ? "leftover" : "running"); +void requireRunningReplacement(const std::filesystem::path& directory, const TValue& data, const bool leftover, const std::string& name = "running") { + const auto output = directory / (leftover ? "leftover" : name); if (leftover) std::ofstream(directory / "leftover.tmp", std::ios::binary) << "killed"; std::filesystem::copy_file(std::filesystem::read_symlink("/proc/self/exe"), output); @@ -107,8 +107,8 @@ void requireRunningReplacement(const std::filesystem::path& directory, const TVa } template -void requireFailedReplacement(const std::filesystem::path& directory, const TValue& data) { - const auto output = directory / "limited"; +void requireFailedReplacement(const std::filesystem::path& directory, const TValue& data, const std::string& name = "limited") { + const auto output = directory / name; std::ofstream(output, std::ios::binary) << "previous"; rlimit limit{}; getrlimit(RLIMIT_FSIZE, &limit); @@ -158,6 +158,11 @@ void requireReplacement(const TValue& data) { requireRunningReplacement(directory, data, false); requireRunningReplacement(directory, data, true); requireFailedReplacement(directory, data); + const auto maximumName = pathconf(directory.c_str(), _PC_NAME_MAX); + if (maximumName <= 0) + throw std::runtime_error("Cannot determine the output filename limit"); + requireFailedReplacement(directory, data, std::string(static_cast(maximumName), 'f')); + requireRunningReplacement(directory, data, false, std::string(static_cast(maximumName), 'r')); requireInPlaceWrites(directory, data); } catch (...) { std::filesystem::remove_all(directory, error); diff --git a/core/relinker/io/tests/NativePathTests.cpp b/core/relinker/io/tests/NativePathTests.cpp new file mode 100644 index 0000000000..e40cd5f3cf --- /dev/null +++ b/core/relinker/io/tests/NativePathTests.cpp @@ -0,0 +1,74 @@ +#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); +} + +template +void RequireInvalidPath(TAction action) { + try { + action(); + } catch (const std::invalid_argument& error) { + Require(std::string(error.what()).find("NUL") != std::string::npos, "Path error does not identify the NUL byte"); + return; + } + throw std::runtime_error("A path containing a NUL byte was accepted"); +} + +void Conversions() { + for (const std::string path : {"", "relative/file.bin", "a path with spaces", "caf\xc3\xa9/\xe6\x97\xa5\xe6\x9c\xac.bin"}) + Require(Io::Utf8Path(Io::NativePath(path)) == path, "Valid path did not round-trip"); + for (const std::string prefix : {"", "file", "directory/file"}) { + for (const auto& suffix : std::vector{"", "suffix", std::string(1, '\0')}) { + const auto path = prefix + '\0' + suffix; + RequireInvalidPath([&] { Io::NativePath(path); }); + } + } +} + +void FileAccess(const std::filesystem::path& directory) { + const auto original = directory / Io::NativePath("caf\xc3\xa9-\xe6\x97\xa5\xe6\x9c\xac.bin"); + const auto originalPath = Io::Utf8Path(original); + const std::vector expected{0, 1, 10, 13, 127, 128, 255}; + Io::FileWriter().Write(originalPath, expected); + Require(Io::FileReader().Read(originalPath) == expected, "Valid UTF-8 file path did not preserve binary data"); + const auto missingPath = Io::Utf8Path(directory / "missing.bin"); + for (const auto& prefix : {originalPath, missingPath}) { + for (const std::string suffix : {"", ".other", "/other.bin"}) { + const auto path = prefix + '\0' + suffix; + RequireInvalidPath([&] { Io::FileReader().Read(path); }); + RequireInvalidPath([&] { Io::FileWriter().Write(path, std::vector{42}); }); + RequireInvalidPath([&] { Io::FileWriter().Write(path, std::string("replacement")); }); + Require(Io::FileReader().Read(originalPath) == expected, "Invalid path changed the file named before the NUL byte"); + Require(!std::filesystem::exists(Io::NativePath(missingPath)), "Invalid path created the file named before the NUL byte"); + } + } +} + +} + +int main() { + const auto directory = std::filesystem::temp_directory_path() / + ("anyps5-native-path-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + try { + Conversions(); + std::filesystem::create_directory(directory); + FileAccess(directory); + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + std::error_code ignored; + std::filesystem::remove_all(directory, ignored); + return 1; + } + std::filesystem::remove_all(directory); +} diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 33240459b0..81b6e3d359 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -35,19 +35,22 @@ struct ConvertedExecutable { std::vector Bytes; std::string Diagnostics; std::vector AppliedPatches; + std::optional LinuxBase; }; ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, const std::string& absPath, const bool mainExecutable, const std::string& targetName, const std::vector& patches, Cli::ConsoleProgress& progress) { Io::FileReader fileReader; Relinker::ConversionInput input; + Relinker::ElfReader(fileReader.ReadPrefix(inputPath, 0x40)).ValidateMagic(); input.Executable = fileReader.Read(inputPath); input.InputPath = std::filesystem::absolute(Io::NativePath(inputPath)).lexically_normal(); input.OutputPath = Io::NativePath(absPath); + Cli::NativeModules modules; if (!args.skipSceModule) { - auto modules = Cli::ReadModules(std::filesystem::absolute(Io::NativePath(args.inputPath)).lexically_normal(), Io::NativePath(args.sceModulePath), args.excludedSceModules, args.moduleDirectories); - input.ModuleRoot = std::move(modules.Root); + modules = Cli::ReadModules(std::filesystem::absolute(Io::NativePath(args.inputPath)).lexically_normal(), Io::NativePath(args.sceModulePath), args.excludedSceModules, args.moduleDirectories); + input.ModuleRoot = modules.Root; input.Modules = std::move(modules.Modules); - input.RuntimeDirectories = std::move(modules.RuntimeDirectories); + input.RuntimeDirectories = modules.RuntimeDirectories; } const auto iconPath = Io::NativePath(args.inputPath).parent_path() / "sce_sys" / "icon0.png"; if (args.toWindows && std::filesystem::exists(iconPath)) input.Icon = fileReader.Read(Io::Utf8Path(iconPath)); @@ -55,6 +58,7 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, options.Target = args.toWindows ? Relinker::TargetSystem::Windows : args.toMacos ? Relinker::TargetSystem::MacOs : Relinker::TargetSystem::Linux; options.ToIntel = args.toIntel; options.ToRosetta = args.toRosetta; + options.FrameRedZone = args.frameRedZone; options.SkipSyscallCheck = args.skipSyscallCheck; options.TrapSdkSyscallStubs = args.trapSdkSyscallStubs; options.UnusedFilterLevel = args.unusedFilterLevel; @@ -73,7 +77,8 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, progress.IncludeFile(Io::Utf8Path(input.OutputPath)); for (const auto& module : input.Modules) progress.IncludeFile(Io::Utf8Path(module.Path)); auto converted = Relinker::Conversion(progress).Convert(std::move(input), options); - return {inputPath, absPath, std::move(converted.Analysis), std::move(converted.Modules), std::move(converted.Executable), std::move(converted.WindowsDiagnostics), std::move(converted.AppliedPatches)}; + if (!args.skipSceModule) Cli::ReportUnconvertedNeeded(modules, converted.Analysis.DynamicSection.NeededLibraries, args.toWindows, std::cout); + return {inputPath, absPath, std::move(converted.Analysis), std::move(converted.Modules), std::move(converted.Executable), std::move(converted.WindowsDiagnostics), std::move(converted.AppliedPatches), converted.LinuxBase}; } class OutputIdentityDirectory { @@ -120,9 +125,10 @@ bool SameOutputFile(const std::filesystem::path& first, const std::filesystem::p return Io::SameFile(first, second); } -bool SameOutputDestination(const std::filesystem::path& first, const std::filesystem::path& second) { - const auto firstPath = std::filesystem::weakly_canonical(first); - const auto secondPath = std::filesystem::weakly_canonical(second); +bool SameOutputDestination(const std::filesystem::path& first, const std::filesystem::path& second, + std::filesystem::path& firstPath, std::filesystem::path& secondPath) { + if (firstPath.empty()) firstPath = std::filesystem::weakly_canonical(first); + if (secondPath.empty()) secondPath = std::filesystem::weakly_canonical(second); if (firstPath == secondPath) return true; if (std::filesystem::exists(first) && std::filesystem::exists(second)) return SameOutputFile(first, second); #ifdef _WIN32 @@ -332,13 +338,20 @@ int Run(const int argc, char* argv[]) { std::filesystem::path manifestPath; std::string manifestText; if (!appliedPatches.empty()) manifestPath = Io::NativePath(absPath).parent_path() / Io::NativePath(Io::Utf8Path(Io::NativePath(absPath).stem()) + Relinker::GamePatch::ManifestSuffix); - if (!manifestPath.empty()) manifestText = Relinker::GamePatch::WriteManifest(appliedPatches, mainTarget, Io::Utf8Path(patchFile)); + if (!manifestPath.empty()) manifestText = Relinker::GamePatch::WriteManifest(appliedPatches, mainTarget, Io::Utf8Path(patchFile), executables.front().LinuxBase); const Elfpatcher::Linux::LinuxDesktopEntryWriter desktopEntryWriter; - const auto desktopEntry = args.toWindows || args.toMacos ? std::nullopt : desktopEntryWriter.Prepare(Io::NativePath(args.inputPath).parent_path() / "sce_sys", Io::NativePath(absPath)); + const auto metadataRoot = Io::NativePath(args.inputPath).parent_path() / "sce_sys"; + const auto desktopEntry = args.toWindows || args.toMacos ? std::nullopt : desktopEntryWriter.Prepare(metadataRoot, Io::NativePath(absPath)); + std::vector metadataInputs; + if (args.toWindows || desktopEntry) metadataInputs.push_back(metadataRoot / "icon0.png"); + if (desktopEntry) metadataInputs.push_back(metadataRoot / "param.json"); const auto checkOutputInputs = [&](const std::filesystem::path& path, const char* kind) { if (!std::filesystem::exists(path)) return; + for (const auto& input : metadataInputs) + if (std::filesystem::exists(input) && SameOutputFile(input, path)) + throw Domain::RelinkerException(std::string(kind) + " output would overwrite input metadata: " + Io::Utf8Path(path)); for (const auto& executable : executables) if (SameOutputFile(Io::NativePath(executable.InputPath), path)) throw Domain::RelinkerException(std::string(kind) + " output would overwrite the input executable: " + Io::Utf8Path(path)); @@ -392,11 +405,12 @@ int Run(const int argc, char* argv[]) { outputPaths.push_back(desktopEntry->EntryPath); } if (args.windowsDiagnostics) outputPaths.push_back(std::filesystem::absolute("windows-diagnostics-imports.txt")); + std::vector canonicalOutputPaths(outputPaths.size()); for (std::size_t index = 0; index < outputPaths.size(); ++index) { for (std::size_t previous = 0; previous < index; ++previous) { const auto& first = outputPaths[index]; const auto& second = outputPaths[previous]; - if (SameOutputDestination(first, second)) + if (SameOutputDestination(first, second, canonicalOutputPaths[index], canonicalOutputPaths[previous])) throw Domain::RelinkerException("Output paths collide: " + Io::Utf8Path(first) + " and " + Io::Utf8Path(second)); } } diff --git a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp index 6be45157af..8fe4507a89 100644 --- a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp +++ b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp @@ -57,6 +57,7 @@ struct GuestImage { std::uint64_t Got = 0; bool UsePlatformTlsResolver = true; bool Symbolic = false; + std::vector NamedImports; }; class GuestImageReader { diff --git a/core/relinker/relinker/include/relinker/parsing/ElfReader.hpp b/core/relinker/relinker/include/relinker/parsing/ElfReader.hpp index 692f30c43d..b8fe9513c3 100644 --- a/core/relinker/relinker/include/relinker/parsing/ElfReader.hpp +++ b/core/relinker/relinker/include/relinker/parsing/ElfReader.hpp @@ -10,6 +10,7 @@ class ElfReader : public IElfReader { public: explicit ElfReader(std::vector fileBuffer); + void ValidateMagic() const; ElfHeader ReadHeader() const override; std::vector ReadProgramHeaders() const override; std::vector ReadSectionHeaders() const override; diff --git a/core/relinker/relinker/include/relinker/patching/GamePatch.hpp b/core/relinker/relinker/include/relinker/patching/GamePatch.hpp index d893a7015e..e04f9605e3 100644 --- a/core/relinker/relinker/include/relinker/patching/GamePatch.hpp +++ b/core/relinker/relinker/include/relinker/patching/GamePatch.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -40,6 +41,7 @@ struct PatchEntry { struct PatchFile { std::vector Entries; std::optional MainTarget; + std::optional LinuxBase; }; struct PatchedRange { @@ -82,6 +84,8 @@ struct WriterSites { std::span Traps; std::span RelaData; std::span RelaPltData; + std::span RedZoneSites = {}; + std::span RedZoneDisplacements = {}; }; enum class VerifyStatus { Applicable, Applied, NotChecked, Mismatch }; @@ -114,7 +118,7 @@ void MatchTargets(const std::vector& entries, const std::vector AggregateManifest(const std::vector>& records); -std::string WriteManifest(const std::vector& patches, const std::string& mainTarget, const std::string& configPath); +std::string WriteManifest(const std::vector& patches, const std::string& mainTarget, const std::string& configPath, std::optional linuxBase = std::nullopt); std::vector VerifyPatches(const PatchFile& file, const std::vector& executables, const std::vector& modules, bool outputMode); diff --git a/core/relinker/relinker/include/relinker/pipeline/Conversion.hpp b/core/relinker/relinker/include/relinker/pipeline/Conversion.hpp index 194262b78e..3a45a1ea10 100644 --- a/core/relinker/relinker/include/relinker/pipeline/Conversion.hpp +++ b/core/relinker/relinker/include/relinker/pipeline/Conversion.hpp @@ -15,6 +15,7 @@ struct ConversionOptions { std::uint32_t UnusedFilterLevel = 0; bool ToIntel = false; bool ToRosetta = false; + bool FrameRedZone = false; bool SkipSyscallCheck = false; bool TrapSdkSyscallStubs = false; bool LazyBinding = false; @@ -46,6 +47,7 @@ struct ConversionOutput { RelinkResult Analysis; std::string WindowsDiagnostics; std::vector AppliedPatches; + std::optional LinuxBase; }; class Conversion { diff --git a/core/relinker/relinker/src/analysis/CallSiteResolver.cpp b/core/relinker/relinker/src/analysis/CallSiteResolver.cpp index 91fe531f9c..a99edf14ee 100644 --- a/core/relinker/relinker/src/analysis/CallSiteResolver.cpp +++ b/core/relinker/relinker/src/analysis/CallSiteResolver.cpp @@ -26,7 +26,12 @@ std::optional ripRelativeDispOffset(const std::vector while (cur < end) { std::uint8_t b = text[cur]; if (b >= RexMin && b <= RexMax) { - if (b & RexWBit) hasRexW = true; + hasRexW = (b & RexWBit) != 0; + ++cur; + continue; + } + if (b == PrefixRepne || b == PrefixSegDs) { + hasRexW = false; ++cur; continue; } diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index 645dd46f3a..14fd038f7a 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -272,7 +273,8 @@ std::set CodeInstructionCollector::Collect(const std::ve symbols = table(6, 0x61000039, symbolCount * 24); for (std::uint64_t index = 0; index < symbolCount; ++index) { const auto offset = symbols + index * 24; - if ((bytes[offset + 4] & 15) == 2 && Io::ReadU16(bytes, offset + 6) != 0) { + const auto section = Io::ReadU16(bytes, offset + 6); + if ((bytes[offset + 4] & 15) == 2 && section != 0 && section != Elfpatcher::SHN_ABS) { const auto address = Io::ReadU64(bytes, offset + 8); addRoot(address); addFunction(address, Io::ReadU64(bytes, offset + 16), true); @@ -344,7 +346,7 @@ std::set CodeInstructionCollector::Collect(const std::ve addFunction(function.Begin, function.End - function.Begin, false); for (const auto target : function.ExtraTargets) addRoot(target); } - if (sceBase) { + if (sceBase || !functions.empty()) { const std::vector> ranges(functions.begin(), functions.end()); for (const auto& header : codeHeaders) { if (header.Type != 1 || (header.Flags & 1) == 0) continue; diff --git a/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp b/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp index 7c99111960..0171c49c9b 100644 --- a/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp +++ b/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include namespace Relinker { @@ -67,6 +69,183 @@ std::size_t TablesEnd(std::span code, std::size_t firstTable return table; } +bool IsInvalid64BitSingleByteOpcode(std::uint8_t opcode) { + switch (opcode) { + case 0x06: + case 0x07: + case 0x0e: + case 0x16: + case 0x17: + case 0x1e: + case 0x1f: + case 0x27: + case 0x2f: + case 0x37: + case 0x3f: + case 0x60: + case 0x61: + case 0x82: + case 0x9a: + case 0xce: + case 0xd4: + case 0xd5: + case 0xd6: + case 0xea: + return true; + default: + return false; + } +} + +bool IsForbiddenSyscallInstruction(std::span code, std::size_t pos, const Codegen::DecodedInstructionInfo& info) { + const auto opPos = pos + info.OpcodeOffset; + if (opPos + 1 >= pos + info.Length || opPos + 1 >= code.size()) return false; + const auto b0 = code[opPos]; + const auto b1 = code[opPos + 1]; + return (b0 == 0x0f && (b1 == 0x05 || b1 == 0x34 || b1 == 0x07)) || (b0 == 0xcd && b1 == 0x80); +} + +bool IsValid64BitInstruction(std::span code, std::size_t pos, const Codegen::DecodedInstructionInfo& info, VirtualAddress baseVAddr, VirtualAddress segmentVAddr, VirtualAddress segmentEnd) { + if (info.Length == 0 || info.Length > code.size() - pos) return false; + if (!info.IsTwoByteOpcode && IsInvalid64BitSingleByteOpcode(info.Opcode)) return false; + if (info.HasBranchTarget && !info.HasRipRelativeDisp) { + const auto target = static_cast(baseVAddr + pos + info.Length) + info.BranchDisp; + if (target < static_cast(segmentVAddr) || target >= static_cast(segmentEnd)) return false; + } + return true; +} + +bool EndsControlFlow(const Codegen::DecodedInstructionInfo& info, bool isSyscall) { + return info.FlowKind == Codegen::ControlFlowKind::Return + || info.FlowKind == Codegen::ControlFlowKind::UnconditionalJump + || info.FlowKind == Codegen::ControlFlowKind::IndirectJump + || info.FlowKind == Codegen::ControlFlowKind::Trap || isSyscall; +} + +class GapAnalysis { + enum class Trailing { Invalid, Padding, Terminator, NonTerminator }; + +public: + GapAnalysis(std::span code, VirtualAddress baseVAddr, VirtualAddress segmentVAddr, VirtualAddress segmentEnd, const Codegen::X64InstructionDecoder& decoder) + : code(code), padding(code.size() + 1), trailing(code.size() + 1), syscalls(code.size() + 1) { + padding.back() = true; + trailing.back() = Trailing::Padding; + for (std::size_t end = code.size(); end != 0;) { + const auto pos = --end; + const bool bytePadding = code[pos] == 0x00 || code[pos] == 0xcc || code[pos] == 0x90; + if (bytePadding && padding[pos + 1]) { + padding[pos] = true; + trailing[pos] = Trailing::Padding; + continue; + } + Codegen::DecodedInstructionInfo info; + try { + info = decoder.DecodeInstruction(code.data() + pos, code.size() - pos); + } catch (const Codegen::CodegenException&) { + continue; + } + if (info.Length == 0 || info.Length > code.size() - pos) continue; + const bool nop = IsNop(code, pos, info.Length); + if (nop && padding[pos + info.Length]) { + padding[pos] = true; + trailing[pos] = Trailing::Padding; + continue; + } + if (!IsValid64BitInstruction(code, pos, info, baseVAddr, segmentVAddr, segmentEnd)) continue; + const auto next = pos + info.Length; + const bool isSyscall = IsForbiddenSyscallInstruction(code, pos, info); + syscalls[pos] = isSyscall || syscalls[next]; + if (EndsControlFlow(info, isSyscall)) { + trailing[pos] = isSyscall || trailing[next] == Trailing::Padding ? Trailing::Terminator : Trailing::NonTerminator; + continue; + } + if (trailing[next] != Trailing::Padding) { + trailing[pos] = trailing[next]; + continue; + } + trailing[pos] = nop || code[pos] == 0xcc || code[pos] == 0x00 ? Trailing::Padding : Trailing::NonTerminator; + } + } + + bool OnlyPadding(std::size_t from) const { return padding[from]; } + + bool DecodesAsTrailingCodeBlock(std::size_t from, bool* outHasSyscall = nullptr) const { + if (outHasSyscall != nullptr) *outHasSyscall = false; + if (from >= code.size() || code[from] == 0x00 || code[from] == 0xcc || code[from] == 0x90) return false; + if (outHasSyscall != nullptr) *outHasSyscall = syscalls[from]; + return trailing[from] == Trailing::Terminator; + } + +private: + std::span code; + std::vector padding; + std::vector trailing; + std::vector syscalls; +}; + +std::size_t FindEmbeddedDataTableEnd(std::span code, VirtualAddress tableVAddr, VirtualAddress segmentVAddr, VirtualAddress segmentEnd, const Codegen::X64InstructionDecoder& decoder) { + const GapAnalysis analysis(code, tableVAddr, segmentVAddr, segmentEnd, decoder); + std::optional firstInvalidPos; + for (std::size_t pos = 0; pos < code.size();) { + if (analysis.OnlyPadding(pos)) break; + Codegen::DecodedInstructionInfo info; + try { + info = decoder.DecodeInstruction(code.data() + pos, code.size() - pos); + } catch (const Codegen::CodegenException&) { + firstInvalidPos = pos; + break; + } + if (!IsValid64BitInstruction(code, pos, info, tableVAddr, segmentVAddr, segmentEnd)) { + firstInvalidPos = pos; + break; + } + if (IsForbiddenSyscallInstruction(code, pos, info) || info.FlowKind != Codegen::ControlFlowKind::Sequential) return 0; + pos += info.Length; + } + if (!firstInvalidPos) return 0; + const bool duplicatedEightByteRow = code.size() >= 16 && std::memcmp(code.data(), code.data() + 8, 8) == 0; + if (*firstInvalidPos != 0 && (!duplicatedEightByteRow || *firstInvalidPos > 4)) return 0; + + std::optional firstDecodeFailure; + for (std::size_t pos = 0; pos < code.size();) { + if (analysis.OnlyPadding(pos)) break; + try { + const auto info = decoder.DecodeInstruction(code.data() + pos, code.size() - pos); + if (info.Length == 0 || info.Length > code.size() - pos) { + firstDecodeFailure = pos; + break; + } + if (IsForbiddenSyscallInstruction(code, pos, info)) return 0; + pos += info.Length; + } catch (const Codegen::CodegenException&) { + firstDecodeFailure = pos; + break; + } + } + if (!firstDecodeFailure) return 0; + + for (std::size_t pos = *firstDecodeFailure + 1; pos < code.size();) { + if (code[pos] != 0xcc && code[pos] != 0x90 && code[pos] != 0x00) { + ++pos; + continue; + } + const auto padStart = pos; + bool hasZeroPad = false; + while (pos < code.size() && (code[pos] == 0xcc || code[pos] == 0x90 || code[pos] == 0x00)) { + if (code[pos] == 0x00) hasZeroPad = true; + ++pos; + } + if (pos < code.size() && analysis.DecodesAsTrailingCodeBlock(pos)) + return hasZeroPad ? pos : padStart; + } + for (std::size_t pos = *firstDecodeFailure + 1; pos < code.size(); ++pos) { + bool hasSyscall = false; + if (analysis.DecodesAsTrailingCodeBlock(pos, &hasSyscall) && hasSyscall) + return pos; + } + return code.size(); +} + } std::size_t FindTrailingRelativeJumpTable(std::span code, VirtualAddress codeAddress, std::size_t* tableEnd, std::size_t unwindEnd) { @@ -121,6 +300,68 @@ std::size_t FindTrailingRelativeJumpTable(std::span code, Vi std::vector FindRelativeJumpTables(std::span segment, VirtualAddress segmentVAddr, const std::vector>& functions) { std::vector tables; const auto segmentEnd = segmentVAddr + segment.size(); + const Codegen::X64InstructionDecoder decoder; + std::set branchTargets; + for (const auto& [funcBegin, funcEnd] : functions) { + if (funcBegin < segmentVAddr || funcBegin >= segmentEnd || funcEnd <= funcBegin) continue; + const auto inspectFuncEnd = std::min(funcEnd, segmentEnd); + const auto funcCode = segment.subspan(funcBegin - segmentVAddr, inspectFuncEnd - funcBegin); + for (std::size_t pos = 0; pos < funcCode.size();) { + Codegen::DecodedInstructionInfo info; + try { + info = decoder.DecodeInstruction(funcCode.data() + pos, funcCode.size() - pos); + } catch (const Codegen::CodegenException&) { + break; + } + if (info.Length == 0 || info.Length > funcCode.size() - pos) break; + if (info.HasBranchTarget && !info.HasRipRelativeDisp) { + const auto target = static_cast(funcBegin + pos + info.Length) + info.BranchDisp; + if (target >= static_cast(segmentVAddr) && target < static_cast(segmentEnd)) + branchTargets.insert(static_cast(target)); + } + pos += info.Length; + } + } + auto knownFunctions = functions; + std::erase_if(knownFunctions, [](const auto& range) { return range.first >= range.second; }); + std::sort(knownFunctions.begin(), knownFunctions.end()); + std::vector> mergedFunctions; + for (const auto& range : knownFunctions) { + if (!mergedFunctions.empty() && range.first <= mergedFunctions.back().second) + mergedFunctions.back().second = std::max(mergedFunctions.back().second, range.second); + else + mergedFunctions.emplace_back(range); + } + std::vector pendingTargets(branchTargets.begin(), branchTargets.end()); + std::set exploredGaps; + while (!pendingTargets.empty()) { + const auto current = pendingTargets.back(); + pendingTargets.pop_back(); + if (current < segmentVAddr || current >= segmentEnd) continue; + const auto after = std::upper_bound(mergedFunctions.begin(), mergedFunctions.end(), current, + [](VirtualAddress value, const auto& range) { return value < range.first; }); + const bool inKnownFunction = after != mergedFunctions.begin() && current < std::prev(after)->second; + if (inKnownFunction || !exploredGaps.insert(current).second) continue; + auto pos = static_cast(current - segmentVAddr); + while (pos < segment.size()) { + Codegen::DecodedInstructionInfo info; + try { + info = decoder.DecodeInstruction(segment.data() + pos, segment.size() - pos); + } catch (const Codegen::CodegenException&) { + break; + } + if (info.Length == 0 || info.Length > segment.size() - pos) break; + if (info.HasBranchTarget && !info.HasRipRelativeDisp) { + const auto target = static_cast(segmentVAddr + pos + info.Length) + info.BranchDisp; + if (target >= static_cast(segmentVAddr) && target < static_cast(segmentEnd)) { + const auto targetVAddr = static_cast(target); + if (branchTargets.insert(targetVAddr).second) pendingTargets.push_back(targetVAddr); + } + } + if (info.FlowKind == Codegen::ControlFlowKind::Return || info.FlowKind == Codegen::ControlFlowKind::IndirectJump || info.FlowKind == Codegen::ControlFlowKind::Trap) break; + pos += info.Length; + } + } for (std::size_t index = 0; index < functions.size(); ++index) { const auto [begin, end] = functions[index]; if (begin < segmentVAddr || begin >= segmentEnd || end <= begin) continue; @@ -129,8 +370,45 @@ std::vector FindRelativeJumpTables(std::span candidateTable; + bool sawIndirectJump = false; + for (std::size_t pos = 0; pos < funcCode.size();) { + Codegen::DecodedInstructionInfo info; + try { + info = decoder.DecodeInstruction(funcCode.data() + pos, funcCode.size() - pos); + } catch (const Codegen::CodegenException&) { + break; + } + if (info.Length == 0 || info.Length > funcCode.size() - pos) break; + if (info.FlowKind == Codegen::ControlFlowKind::IndirectJump) sawIndirectJump = true; + const auto next = pos + info.Length; + if (info.HasRipRelativeDisp && !info.IsTwoByteOpcode && info.OpcodeOffset < info.Length && info.Length >= 4 && funcCode[pos + info.OpcodeOffset] == 0x8d) { + std::int32_t displacement = 0; + std::memcpy(&displacement, funcCode.data() + next - 4, sizeof(displacement)); + const auto target = static_cast(begin + next) + displacement; + if (target >= static_cast(end) && target < static_cast(inspectEnd)) { + const auto targetVAddr = static_cast(target); + candidateTable = candidateTable ? std::min(*candidateTable, targetVAddr) : targetVAddr; + } + } + pos = next; + } + if (sawIndirectJump || !candidateTable) continue; + const auto tableVAddr = *candidateTable; + const auto paddingCode = segment.subspan(end - segmentVAddr, tableVAddr - end); + if (!OnlyPadding(paddingCode, 0, paddingCode.size(), decoder)) continue; + const auto it = branchTargets.lower_bound(tableVAddr); + if (it != branchTargets.end() && *it < inspectEnd) continue; + const auto tableCode = segment.subspan(tableVAddr - segmentVAddr, inspectEnd - tableVAddr); + const auto tableSize = FindEmbeddedDataTableEnd(tableCode, tableVAddr, segmentVAddr, segmentEnd, decoder); + if (tableSize == 0) continue; + tables.push_back({begin, tableVAddr, tableVAddr + tableSize}); } return tables; } diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp index da812ba58e..d8c89bcffb 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp @@ -103,13 +103,16 @@ void buildGraph(std::span text, VirtualAddress textVaddr, Vi if (followCodeAddresses && info.HasRipRelativeDisp && text[bufOff + info.OpcodeOffset] == 0x8d && (info.RexPrefix & 8)) { std::int32_t displacement = 0; std::memcpy(&displacement, text.data() + bufOff + info.RipRelativeDispOffset, 4); - enqueue(nextVaddr + static_cast(static_cast(displacement))); + auto target = nextVaddr + static_cast(static_cast(displacement)); + if (info.AddressSize32) target = static_cast(target); + enqueue(target); } if (data != nullptr && info.HasRipRelativeDisp && !info.IsTwoByteOpcode && info.Opcode == 0x8D) { std::int32_t displacement = 0; std::memcpy(&displacement, text.data() + bufOff + info.RipRelativeDispOffset, 4); - const auto base = nextVaddr + static_cast(static_cast(displacement)); + auto base = nextVaddr + static_cast(static_cast(displacement)); + if (info.AddressSize32) base = static_cast(base); takeAddress(base); for (VirtualAddress target : ReadRelativeTableTargets(*data, base, textVaddr, text.size())) takeAddress(target); } @@ -129,6 +132,7 @@ void buildGraph(std::span text, VirtualAddress textVaddr, Vi std::int32_t disp = 0; std::memcpy(&disp, text.data() + bufOff + info.RipRelativeDispOffset, 4); VirtualAddress slotVaddr = nextVaddr + static_cast(static_cast(disp)); + if (info.AddressSize32) slotVaddr = static_cast(slotVaddr); auto resolved = relativeRelocations.TargetOfSlot(slotVaddr); if (resolved.has_value()) enqueue(*resolved); @@ -141,6 +145,7 @@ void buildGraph(std::span text, VirtualAddress textVaddr, Vi std::int32_t disp = 0; std::memcpy(&disp, text.data() + bufOff + info.RipRelativeDispOffset, 4); VirtualAddress slotVaddr = nextVaddr + static_cast(static_cast(disp)); + if (info.AddressSize32) slotVaddr = static_cast(slotVaddr); auto resolved = relativeRelocations.TargetOfSlot(slotVaddr); if (resolved.has_value()) enqueue(*resolved); diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp index 9d1e5a3c2e..18dc887f88 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp @@ -43,8 +43,10 @@ std::unique_ptr BuildGotAccessIndex( VirtualAddress nextVaddr = va + static_cast(info.Length); std::int32_t disp = 0; std::memcpy(&disp, text.data() + bufOff + info.RipRelativeDispOffset, 4); - if (disp < 0 && nextVaddr < static_cast(-static_cast(disp))) continue; - if (disp > 0 && std::numeric_limits::max() - nextVaddr < static_cast(disp)) continue; + if (!info.AddressSize32) { + if (disp < 0 && nextVaddr < static_cast(-static_cast(disp))) continue; + if (disp > 0 && std::numeric_limits::max() - nextVaddr < static_cast(disp)) continue; + } VirtualAddress target = nextVaddr + static_cast(static_cast(disp)); if (info.AddressSize32) target = static_cast(target); accessed.insert(target); diff --git a/core/relinker/relinker/src/analysis/ValidationPolicy.cpp b/core/relinker/relinker/src/analysis/ValidationPolicy.cpp index dfd0a11798..b9d67a3069 100644 --- a/core/relinker/relinker/src/analysis/ValidationPolicy.cpp +++ b/core/relinker/relinker/src/analysis/ValidationPolicy.cpp @@ -11,20 +11,13 @@ void ValidationPolicy::_initializeSupportedRelocationTypes() { R_X86_64_NONE, R_X86_64_64, R_X86_64_PC32, - R_X86_64_GOT32, - R_X86_64_PLT32, - R_X86_64_COPY, R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT, R_X86_64_RELATIVE, - R_X86_64_GOTPCREL, R_X86_64_32, - R_X86_64_32S, R_X86_64_DTPMOD64, R_X86_64_DTPOFF64, R_X86_64_TPOFF64, - R_X86_64_GOTPCRELX, - R_X86_64_REX_GOTPCRELX, }; } diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp index a630010692..66ac129902 100644 --- a/core/relinker/relinker/src/guest/GuestImageReader.cpp +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -38,8 +38,10 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector std::map loads; constexpr std::uint32_t sceComment = 0x6fffff00; constexpr std::uint32_t sceLibVersion = 0x6fffff01; + constexpr std::uint32_t note = 4; for (const auto& header : image.Headers) { if (header.Type == sceComment || header.Type == sceLibVersion) continue; + if (header.Type == note && header.MappedAddress == 0 && header.MemorySize == 0) continue; range(header.Offset, header.FileSize); if (header.Type == 1) { if (header.FileSize > header.MemorySize || header.MemorySize > std::numeric_limits::max() - header.MappedAddress || (header.Flags & ~7u) != 0) fail("Invalid load segment"); @@ -162,6 +164,8 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if (!importModules.emplace(value >> 48, string(value & 0xffffffffu)).second) fail("Duplicate import module ID"); std::vector neededLibraries; for (const auto offset : needed) neededLibraries.push_back(string(offset)); + const bool consoleFileNames = std::any_of(neededLibraries.begin(), neededLibraries.end(), + [](const std::string& name) { return Domain::NeededLibraryName(name) != name; }); std::map exports; for (std::uint64_t offset = 0; offset < symSize; offset += 24) { auto name = string(Io::ReadU32(bytes, symOffset + offset)); @@ -187,14 +191,13 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector const auto [found, inserted] = exports.emplace(name, image.Symbols.size()); if (name.empty() || (!inserted && (!windows || !IdenticalOrdinaryFunctionExport(image.Symbols[found->second], symbol)))) fail("Duplicate or empty export after stripping #: " + name); } + if (section >= 0xff00 && (section != AbsoluteSection || (info & 15) == 6)) fail("Unsupported special symbol section: " + name); if ((info & 15) == 6 && section != 0 && (tls == nullptr || value > tls->MemorySize || size > tls->MemorySize - value)) fail("TLS symbol exceeds TLS segment: " + name); - if (section != 0 && section != AbsoluteSection && (info & 15) != 6) { - if (section >= 0xff00) fail("Unsupported special symbol section: " + name); - mapped(value, std::max(size, 1), (info & 15) == 2 ? 1 : 0); - } + if (section != 0 && section != AbsoluteSection && (info & 15) != 6) mapped(value, std::max(size, 1), (info & 15) == 2 ? 1 : 0); if (image.Dynamic.DynStrData.size() > std::numeric_limits::max()) fail("String table too large"); Io::WriteU32(image.Dynamic.DynSymData, offset, static_cast(image.Dynamic.DynStrData.size())); Io::AppendString(image.Dynamic.DynStrData, name); + if (consoleFileNames && section == 0 && !name.empty() && name == qualifiedName) image.NamedImports.push_back(image.Symbols.size()); image.Symbols.push_back(symbol); } if (tags.contains(14)) { @@ -244,6 +247,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector const auto next = relocationTargets.lower_bound(target); if ((next != relocationTargets.end() && next->first < target + 8) || (next != relocationTargets.begin() && std::prev(next)->second > target)) fail("Overlapping guest relocations"); relocationTargets.emplace(target, target + 8); + bool ownTlsBlock = false; if (relocation == 8) { if ((info >> 32) != 0) { const auto& symbol = image.Symbols[info >> 32]; @@ -263,9 +267,12 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if ((info >> 32) == 0 || (symbol.Info & 15) == 6 || (relocation != 1 && addend != 0) || (symbol.Section == 0 && symbol.Name.empty())) fail("Invalid guest symbol relocation"); } else { const auto& symbol = image.Symbols[info >> 32]; - if (((info >> 32) == 0 && tls == nullptr) || ((info >> 32) != 0 && (symbol.Info & 15) != 6) || (relocation == 16 && addend != 0)) fail("Invalid guest TLS relocation"); + ownTlsBlock = (info >> 32) != 0 && (relocation == 16 || relocation == 17) && symbol.Info == 3 && symbol.Section == 0 && symbol.Value == 0 && tls != nullptr; + if (((info >> 32) == 0 && tls == nullptr) || ((info >> 32) != 0 && (symbol.Info & 15) != 6 && !ownTlsBlock) || (relocation == 16 && addend != 0)) fail("Invalid guest TLS relocation"); } + const auto position = output.size(); output.insert(output.end(), bytes.begin() + offset + index, bytes.begin() + offset + index + 24); + if (ownTlsBlock) Io::WriteU64(output, position + 8, relocation); } }; copyRelocations(relaOffset, relaSize, image.Dynamic.RelaData, false); @@ -279,8 +286,8 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if (image.HasInit) mapped(image.Init, 1, 1); if (image.Fini != 0) mapped(image.Fini, 1, 1); const auto readLifecycleArray = [&](std::uint64_t addressTag, std::uint64_t sizeTag, std::vector& entries) { - if (!tags.contains(addressTag) && !tags.contains(sizeTag)) return; - if (!tags.contains(sizeTag) || (tags.at(sizeTag) != 0 && !tags.contains(addressTag))) fail("Incomplete guest lifecycle array"); + if (!tags.contains(sizeTag)) return; + if (tags.at(sizeTag) != 0 && !tags.contains(addressTag)) fail("Incomplete guest lifecycle array"); const auto size = tags.at(sizeTag); if (size == 0) return; if (size % 8 != 0) fail("Unaligned guest lifecycle array size"); diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index 1ffd34e19d..d234bbe4db 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -6,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +44,37 @@ std::vector ReadNeededNames(const Domain::SysVDynamicSection& dynam return names; } +std::string SymbolName(const Domain::SysVDynamicSection& dynamic, const std::size_t index) { + if (dynamic.DynSymData.size() % 24 != 0 || index >= dynamic.DynSymData.size() / 24) throw Domain::RelinkerException("Invalid symbol index"); + const auto nameOffset = Io::ReadU32(dynamic.DynSymData, index * 24); + if (nameOffset >= dynamic.DynStrData.size()) throw Domain::RelinkerException("Invalid symbol name offset"); + const auto start = dynamic.DynStrData.begin() + nameOffset; + const auto end = std::find(start, dynamic.DynStrData.end(), 0); + if (end == dynamic.DynStrData.end()) throw Domain::RelinkerException("Unterminated symbol name"); + return std::string(start, end); +} + +std::set ExportedNames(const Domain::SysVDynamicSection& dynamic) { + std::set names; + for (std::size_t index = 1; index < dynamic.DynSymData.size() / 24; ++index) { + const auto info = dynamic.DynSymData[index * 24 + 4]; + const auto visibility = dynamic.DynSymData[index * 24 + 5] & 3; + if (Io::ReadU16(dynamic.DynSymData, index * 24 + 6) != 0 && (info >> 4) != 0 && visibility != 1 && visibility != 2) + names.insert(SymbolName(dynamic, index)); + } + return names; +} + +void BindNamedImportsByNid(GuestImage& image, const std::set& kept) { + for (const auto index : image.NamedImports) { + const auto name = SymbolName(image.Dynamic, index); + if (name.ends_with(GuestSymbolSuffix) || kept.contains(name)) continue; + if (image.Dynamic.DynStrData.size() > std::numeric_limits::max()) throw Domain::RelinkerException("Guest string table too large"); + Io::WriteU32(image.Dynamic.DynSymData, index * 24, static_cast(image.Dynamic.DynStrData.size())); + Io::AppendString(image.Dynamic.DynStrData, Nid::ComputeNid(name, "")); + } +} + std::string FoldFilename(std::string name) { for (auto& character : name) if (character >= 'A' && character <= 'Z') character = static_cast(character + ('a' - 'A')); return name; @@ -257,6 +290,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path } if (provider != nullptr) { if ((symbol.Info & 15) != 0 && (symbol.Info & 15) != provider->Type) throw Domain::RelinkerException("Guest import/export type mismatch: " + symbol.Name); + if (provider->Type == 6 && (symbol.Info & 15) == 0 && referencedSymbols.contains(symbolIndex)) throw Domain::RelinkerException("Non-TLS relocation against guest TLS export: " + symbol.Name); if (provider->Provider != index && referencedSymbols.contains(symbolIndex)) dependencies[index].insert(provider->Provider); } else if (windows && (symbol.Info & 15) == 6) throw Domain::RelinkerException("Windows guest TLS import requires a guest TLS export: " + symbol.Name); if (provider == nullptr) systemImports[index].insert(symbolIndex); @@ -292,12 +326,14 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path return directory == "." ? std::string("app0") : "app0/" + directory; }; if (!windows) { + const auto executableExports = macos ? std::set{} : ExportedNames(dynamic); for (auto& image : images) { for (std::size_t index = 1; index < image.Symbols.size(); ++index) { const auto& symbol = image.Symbols[index]; const bool exported = symbol.Section != 0 && (symbol.Info >> 4) != 0 && symbol.Visibility != 1 && symbol.Visibility != 2; if (((symbol.Section == 0 && !declaresHost(symbol.Library)) || exported) && exports.contains(symbol.Name)) rename(image.Dynamic.DynSymData, image.Dynamic.DynStrData, index, symbol.Name); } + if (!macos) BindNamedImportsByNid(image, executableExports); } } std::vector order; @@ -314,17 +350,18 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path std::vector hostLibraries; std::vector> moduleHostLibraries(images.size()); std::set uniqueHosts; + const auto hostLibrary = [&](const std::string& name) { return windows || macos ? name : Domain::NeededLibraryName(name); }; const auto addHost = [&](const std::string& name, const bool startup) { if (findGuest(name) != guestNames.end()) return; if (name.empty() || name.find_first_of("/\\:$") != std::string::npos) throw Domain::RelinkerException("Invalid host dependency: " + name); - if (startup && uniqueHosts.insert(name).second) hostLibraries.push_back(name); + if (const auto host = hostLibrary(name); startup && uniqueHosts.insert(host).second) hostLibraries.push_back(host); }; for (const auto& name : neededNames) addHost(name, true); for (std::size_t index = 0; index < images.size(); ++index) { auto& hosts = moduleHostLibraries[index]; for (const auto& dependency : images[index].Dependencies) { addHost(dependency, windows || !deferred(index)); - if (findGuest(dependency) == guestNames.end()) hosts.push_back(dependency); + if (findGuest(dependency) == guestNames.end()) hosts.push_back(hostLibrary(dependency)); } if (!windows && !macos && images[index].HasInit) { if (std::find(hosts.begin(), hosts.end(), "libkernel.prx") == hosts.end()) hosts.push_back("libkernel.prx"); @@ -343,7 +380,12 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path }; for (const auto index : order) if (!windows && !deferred(index)) addNeeded("$ORIGIN/" + appDirectory(images[index]) + "/" + images[index].OutputName); for (const auto& name : hostLibraries) addNeeded(name); - if (!windows && runPath != "$ORIGIN" && !runPath.starts_with("$ORIGIN/") && !runPath.starts_with("$ORIGIN:") && !runPath.starts_with('/')) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN"); + if (!windows) for (std::size_t begin = 0; begin <= runPath.size();) { + const auto end = std::min(runPath.find(':', begin), runPath.size()); + const auto component = std::string_view(runPath).substr(begin, end - begin); + if (component != "$ORIGIN" && !component.starts_with("$ORIGIN/") && !component.starts_with('/')) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN"); + begin = end + 1; + } const auto outputDirectory = outputPath.parent_path().lexically_normal(); std::vector artifacts; for (const auto index : order) { @@ -359,14 +401,26 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path } if (!image.PatchedRanges.empty()) GamePatch::CheckOverlaps(Io::Utf8Path(image.SourcePath), image.PatchedRanges, {.RelaData = image.Dynamic.RelaData, .RelaPltData = image.Dynamic.RelaPltData}); const bool sceImage = std::any_of(image.Headers.begin(), image.Headers.end(), [](const Domain::ProgramHeader& header) { return header.Type == 0x61000000; }); - const auto unwindFunctions = sceImage ? UnusedNidFilter::ReadExceptionFunctions(image.Bytes, image.Headers, {}, {}) : std::vector{}; + const bool hasEhFrame = sceImage || std::any_of(image.Headers.begin(), image.Headers.end(), [](const Domain::ProgramHeader& header) { return header.Type == 0x6474e550; }); + const auto unwindFunctions = hasEhFrame ? UnusedNidFilter::ReadExceptionFunctions(image.Bytes, image.Headers, {}, {}) : std::vector{}; + std::vector> unwindRanges; + for (const auto& function : unwindFunctions) unwindRanges.emplace_back(function.Begin, function.End); + std::sort(unwindRanges.begin(), unwindRanges.end()); for (const auto& header : codeHeaders) { std::vector code(image.Bytes.begin() + header.Offset, image.Bytes.begin() + header.Offset + header.FileSize); if (sceImage) if (const auto end = FindSceCodeEnd(code, header.MappedAddress, unwindFunctions, image.Got, image.Dynamic.RelaPltData.size())) code.resize(*end - header.MappedAddress); - const auto unwindRanges = sceImage ? SceUnwindRanges(code, header.MappedAddress, unwindFunctions) : std::vector{}; - for (const auto offset : syscallScanner.ScanCodeSectionForSyscalls(code, header.MappedAddress, code.size(), Io::Utf8Path(image.SourcePath), unwindRanges)) { + auto scanCode = code; + for (const auto& table : FindRelativeJumpTables(code, header.MappedAddress, unwindRanges)) { + if (table.Begin >= header.MappedAddress && table.End >= table.Begin) { + const auto maskEnd = std::min(table.End - header.MappedAddress, scanCode.size()); + const auto maskBegin = std::min(table.Begin - header.MappedAddress, maskEnd); + std::fill(scanCode.begin() + static_cast(maskBegin), scanCode.begin() + static_cast(maskEnd), std::uint8_t{0x90}); + } + } + const auto scanUnwindRanges = sceImage ? SceUnwindRanges(code, header.MappedAddress, unwindFunctions) : std::vector{}; + for (const auto offset : syscallScanner.ScanCodeSectionForSyscalls(scanCode, header.MappedAddress, scanCode.size(), Io::Utf8Path(image.SourcePath), scanUnwindRanges)) { if (offset > code.size() || code.size() - offset < 2u) throw Domain::RelinkerException("Syscall trap patch exceeds guest executable segment", header.MappedAddress + offset); if (!image.PatchedRanges.empty()) { @@ -404,7 +458,10 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path needed.push_back("$ORIGIN/" + dependencyPath.lexically_relative(image.SourcePath.parent_path()).generic_string()); } needed.insert(needed.end(), moduleHostLibraries[index].begin(), moduleHostLibraries[index].end()); - output = macos ? Elfpatcher::GuestModuleWriter().WriteMacOs(image, needed, guestRunPath, message) : Elfpatcher::GuestModuleWriter().WriteLinux(image, needed, guestRunPath); + const auto directory = image.SourcePath.parent_path(); + const bool deferLinuxStart = isRuntime(index) && directory != root / "sce_module" && + directory != root / "sce_modules" && directory != root / "prx"; + output = macos ? Elfpatcher::GuestModuleWriter().WriteMacOs(image, needed, guestRunPath, message) : Elfpatcher::GuestModuleWriter().WriteLinux(image, needed, guestRunPath, deferLinuxStart); } if (windows) { for (const auto& [name, provider] : guestNames) { @@ -425,7 +482,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path dynamic.GuestModules.push_back(std::move(runtime)); std::vector neededLibraries; for (const auto& name : image.Dependencies) - if (findGuest(name) == guestNames.end()) neededLibraries.push_back(name); + if (findGuest(name) == guestNames.end()) neededLibraries.push_back(hostLibrary(name)); artifacts.push_back({target, std::move(output), std::move(imports), image.SourcePath, std::move(neededLibraries)}); progress.Report({Domain::ProgressStage::Modules, Io::Utf8Path(image.SourcePath), artifacts.size(), order.size()}); } diff --git a/core/relinker/relinker/src/output/CallRegistryWriter.cpp b/core/relinker/relinker/src/output/CallRegistryWriter.cpp index 8878c6496c..f79f7fa68b 100644 --- a/core/relinker/relinker/src/output/CallRegistryWriter.cpp +++ b/core/relinker/relinker/src/output/CallRegistryWriter.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace Relinker { @@ -66,6 +67,7 @@ static std::string _jsonString(const std::string& s) { static std::string _hexOffset(FileByteOffset v) { std::ostringstream oss; + oss.imbue(std::locale::classic()); oss << "\"0x" << std::hex << std::uppercase << v << "\""; return oss.str(); } diff --git a/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp b/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp index 0d8bdd763f..5c2881d32b 100644 --- a/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp +++ b/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include namespace Relinker { @@ -77,6 +79,9 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection( _appendElfSym(result.DynSymData, 0, 0, 0, 0, 0, 0); + const bool consoleFileNames = std::any_of(neededLibraries.begin(), neededLibraries.end(), + [](const std::string& name) { return Domain::NeededLibraryName(name) != name; }); + auto stripHashSuffix = [](const std::string& value) -> std::string { const auto hashPos = value.find('#'); if (hashPos == std::string::npos) return value; @@ -87,7 +92,8 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection( if (ref.Nid.empty()) return static_cast((STB_WEAK << 4) | STT_NOTYPE); const bool tls = ref.RelocationTypeValue == R_X86_64_DTPMOD64 || ref.RelocationTypeValue == R_X86_64_DTPOFF64 || ref.RelocationTypeValue == R_X86_64_TPOFF64; - return static_cast(((ref.Weak ? STB_WEAK : STB_GLOBAL) << 4) | (tls ? STT_TLS : STT_FUNC)); + const auto type = tls ? STT_TLS : ref.SymbolType; + return static_cast(((ref.Weak ? STB_WEAK : STB_GLOBAL) << 4) | type); }; static constexpr std::size_t kRelaEntSize = 24; @@ -98,6 +104,7 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection( for (const auto& ref : nidReferences) { if (!ref.Library.empty()) result.ImportModules.emplace(ref.RelocationAddress, ref.Library); if (ref.Function) result.FunctionImports.insert(ref.RelocationAddress); + if (consoleFileNames && ref.Library.empty() && !ref.Nid.empty() && ref.Nid.find('#') == std::string::npos) result.NamedImports.insert(ref.RelocationAddress); std::uint32_t relType = ref.RelocationTypeValue; if (relType == 0) relType = R_X86_64_JUMP_SLOT; diff --git a/core/relinker/relinker/src/parsing/ElfReader.cpp b/core/relinker/relinker/src/parsing/ElfReader.cpp index 0fc55afebd..82f5e304ae 100644 --- a/core/relinker/relinker/src/parsing/ElfReader.cpp +++ b/core/relinker/relinker/src/parsing/ElfReader.cpp @@ -76,7 +76,7 @@ std::uint64_t ElfReader::_readU64At(FileByteOffset fileByteOffset) const { return value; } -ElfHeader ElfReader::ReadHeader() const { +void ElfReader::ValidateMagic() const { if (_fileBuffer.size() < 0x40) { throw RelinkerException("File too small for ELF header"); } @@ -90,6 +90,10 @@ ElfHeader ElfReader::ReadHeader() const { _fileBuffer[2] != 'L' || _fileBuffer[3] != 'F') { throw RelinkerException("Invalid ELF magic number: " + formatMagic()); } +} + +ElfHeader ElfReader::ReadHeader() const { + ValidateMagic(); if (_fileBuffer[4] != 2 || _fileBuffer[5] != 1 || _fileBuffer[6] != 1) throw RelinkerException("Expected little-endian ELF64 version 1"); if (_readU16At(0x12) != 62) @@ -216,6 +220,12 @@ std::vector ElfReader::ReadSectionHeaders() const { sh.Info = _readU32At(offset + 0x2c); sh.EntrySize = _readU64At(offset + kShdrEntSizeOffset); + if (sh.Type != SHT_NULL && sh.Type != SHT_NOBITS && sh.SectionSize != 0) { + if (!_rangeFits(sh.Offset, sh.SectionSize, _fileBuffer.size())) { + throw RelinkerException("Section offset out of bounds", sh.Offset); + } + } + sh.Name = _resolveShdrName(nameOffset, header); headers.push_back(sh); diff --git a/core/relinker/relinker/src/patching/GamePatch.cpp b/core/relinker/relinker/src/patching/GamePatch.cpp index 05d334868a..12f6451372 100644 --- a/core/relinker/relinker/src/patching/GamePatch.cpp +++ b/core/relinker/relinker/src/patching/GamePatch.cpp @@ -33,8 +33,8 @@ constexpr std::array kIntegerTypes{{ {"i8", 8, true}, {"i16", 16, true}, {"i32", 32, true}, {"i64", 64, true}, }}; -constexpr std::array kIgnoredTopLevelKeys{"relinker_version", "relinker_commit", "config", "config_sha256", "main_target", "settings", "settings_sha256"}; -constexpr std::array kReservedNames{"format", "relinker_version", "relinker_commit", "config", "config_sha256", "main_target", "settings", "settings_sha256"}; +constexpr std::array kIgnoredTopLevelKeys{"relinker_version", "relinker_commit", "config", "config_sha256", "main_target", "linux_base", "settings", "settings_sha256"}; +constexpr std::array kReservedNames{"format", "relinker_version", "relinker_commit", "config", "config_sha256", "main_target", "linux_base", "settings", "settings_sha256"}; constexpr std::array kEntryKeys{"name", "target", "va", "type", "expected", "value", "min", "max", "align", "description"}; std::string HexValue(const std::uint64_t value) { @@ -240,6 +240,15 @@ PatchEntry BuildEntry(const Table& table, const std::size_t headerLine, std::set return entry; } +std::string Describe(const PatchEntry& entry) { + return "patch " + entry.Target + " va=" + HexValue(entry.VirtualAddress) + " (line " + std::to_string(entry.Line) + ")"; +} + +void ValidateWidth(const PatchEntry& entry) { + if (entry.Expected.empty() || entry.Expected.size() != entry.Value.size()) + throw Domain::RelinkerException(Describe(entry) + ": expected and value must have the same nonzero width"); +} + struct EntryRange { std::optional Range; std::string Reason; @@ -275,14 +284,13 @@ std::vector ResolveEach(const ElfReader& reader, const std::vector

resolved; - for (const auto& entry : entries) resolved.push_back(ResolveEntry(segments, reader.GetFileSize(), header, entry)); + for (const auto& entry : entries) { + ValidateWidth(entry); + resolved.push_back(ResolveEntry(segments, reader.GetFileSize(), header, entry)); + } return resolved; } -std::string Describe(const PatchEntry& entry) { - return "patch " + entry.Target + " va=" + HexValue(entry.VirtualAddress) + " (line " + std::to_string(entry.Line) + ")"; -} - bool Overlaps(const std::uint64_t firstOffset, const std::uint64_t firstLength, const std::uint64_t secondOffset, const std::uint64_t secondLength) { return firstOffset < secondOffset + secondLength && secondOffset < firstOffset + firstLength; } @@ -300,8 +308,8 @@ void RejectEntryOverlaps(const std::vector& entries, const std::vect } } -bool IsElfDyn(const std::vector& bytes) { - return bytes.size() >= 18 && bytes[0] == 0x7f && bytes[1] == 'E' && bytes[2] == 'L' && bytes[3] == 'F' && bytes[16] == Elfpatcher::ET_DYN && bytes[17] == 0; +bool IsElfOfType(const std::vector& bytes, const std::uint16_t type) { + return bytes.size() >= 18 && bytes[0] == 0x7f && bytes[1] == 'E' && bytes[2] == 'L' && bytes[3] == 'F' && bytes[16] == (type & 0xff) && bytes[17] == (type >> 8); } VerifyResult VerifyOne(const PatchEntry& entry, const EntryRange& resolved, const std::vector& bytes, const bool outputMode) { @@ -410,6 +418,10 @@ PatchFile ParsePatchFile(const std::string_view text) { if (!mainTarget->IsString || mainTarget->Text.empty()) FailLine(mainTarget->Line, "main_target must be a nonempty string", "main_target"); file.MainTarget = mainTarget->Text; } + if (const auto* linuxBase = Find(top, "linux_base")) { + if (linuxBase->IsString || linuxBase->Number.Negative) FailLine(linuxBase->Line, "linux_base must be a non-negative integer", "linux_base"); + file.LinuxBase = linuxBase->Number.Magnitude; + } return file; } @@ -427,7 +439,10 @@ std::vector ResolveRanges(const ElfReader& reader, const std::vect std::vector ApplyPatches(std::vector& bytes, const std::string& target, const std::vector& entries, const std::vector& ranges) { if (entries.size() != ranges.size()) throw Domain::RelinkerException("Patch ranges do not match patch entries"); for (std::size_t index = 0; index < entries.size(); ++index) { + ValidateWidth(entries[index]); const auto& range = ranges[index]; + if (range.Length != entries[index].Expected.size()) + throw Domain::RelinkerException(Describe(entries[index]) + ": range length does not match the patch width", range.Offset); if (range.Offset > bytes.size() || range.Length > bytes.size() - range.Offset) throw Domain::RelinkerException(Describe(entries[index]) + ": range exceeds the image", range.Offset); const auto first = bytes.begin() + static_cast(range.Offset); if (!std::equal(entries[index].Expected.begin(), entries[index].Expected.end(), first)) @@ -466,6 +481,8 @@ void CheckOverlaps(const std::string& target, const std::vector& p for (const auto& site : writers.Trampolines) sites.push_back({site.Offset, site.Length, "Intel trampoline site"}); for (const auto& patch : writers.RelinkPatches) sites.push_back({patch.Offset, patch.Bytes.size(), "relinker patch"}); sites.insert(sites.end(), writers.Traps.begin(), writers.Traps.end()); + for (const auto& site : writers.RedZoneSites) sites.push_back({site.Offset, site.Length, "red-zone frame site"}); + for (const auto& moved : writers.RedZoneDisplacements) sites.push_back({moved.Offset, moved.Bytes.size(), "red-zone moved displacement"}); const auto relaViolation = [&](const std::span table, const char* name, const PatchedRange& range) { for (std::size_t position = 0; position + 24 <= table.size(); position += 24) { std::uint64_t relocation = 0; @@ -505,7 +522,7 @@ std::vector AggregateManifest(const std::vector& patches, const std::string& mainTarget, const std::string& configPath) { +std::string WriteManifest(const std::vector& patches, const std::string& mainTarget, const std::string& configPath, const std::optional linuxBase) { const auto holdable = [](const std::string& text) { return std::none_of(text.begin(), text.end(), [](const char character) { return character == '"' || character == '\\' || (static_cast(character) < 0x20 && character != '\t') || character == 0x7f; @@ -519,6 +536,7 @@ std::string WriteManifest(const std::vector& patches, const std::s if (!config.empty() && !holdable(config)) config = std::filesystem::path(Io::NativePath(configPath)).filename().generic_string(); std::ostringstream manifest; manifest << "format = 1\nmain_target = " << quoted(mainTarget, "main_target") << '\n'; + if (linuxBase) manifest << "linux_base = " << HexValue(*linuxBase) << '\n'; if (!config.empty()) manifest << "config = " << quoted(config, "config") << '\n'; for (const auto& patch : patches) { manifest << "\n[[patch]]\n"; @@ -553,8 +571,12 @@ std::vector VerifyPatches(const PatchFile& file, const std::vector for (std::size_t index = 0; index < executables.size(); ++index) for (std::size_t other = index + 1; other < executables.size(); ++other) if (executables[index].Name == executables[other].Name) throw Domain::RelinkerException("ambiguous patch target: " + executables[index].Name); - for (const auto& executable : executables) - if (!IsElfDyn(executable.Bytes)) throw Domain::RelinkerException(executable.Name + " is not an ET_DYN Linux ELF output (ET_EXEC from --linux-fixed-base, PE and Mach-O outputs are not supported): verify the input instead"); + for (const auto& executable : executables) { + const bool fixed = IsElfOfType(executable.Bytes, Elfpatcher::ET_EXEC); + if (!fixed && !IsElfOfType(executable.Bytes, Elfpatcher::ET_DYN)) throw Domain::RelinkerException(executable.Name + " is not a Linux ELF output (PE and Mach-O outputs are not supported): verify the input instead"); + if (fixed && !file.LinuxBase) throw Domain::RelinkerException(executable.Name + " is an ET_EXEC Linux ELF output, but the patch file has no linux_base: verify the manifest written by its conversion"); + if (!fixed && file.LinuxBase) throw Domain::RelinkerException(executable.Name + " is an ET_DYN Linux ELF output, but the patch file has linux_base = " + HexValue(*file.LinuxBase) + ": verify the output of the conversion that wrote it"); + } } else { std::vector names; for (const auto& executable : executables) names.push_back(executable.Name); @@ -583,13 +605,19 @@ std::vector VerifyPatches(const PatchFile& file, const std::vector } continue; } + const std::uint64_t base = outputMode && IsElfOfType(target->Bytes, Elfpatcher::ET_EXEC) ? *file.LinuxBase : 0; std::vector own; - for (const auto other : indices) own.push_back(file.Entries[other]); + for (const auto other : indices) { + own.push_back(file.Entries[other]); + if (own.back().VirtualAddress > std::numeric_limits::max() - base) throw Domain::RelinkerException(Describe(own.back()) + " lies outside the address space at linux_base = " + HexValue(base)); + own.back().VirtualAddress += base; + } const ElfReader reader(target->Bytes); const auto resolved = ResolveEach(reader, own); RejectEntryOverlaps(own, resolved); for (std::size_t position = 0; position < indices.size(); ++position) { results[indices[position]] = VerifyOne(own[position], resolved[position], target->Bytes, outputMode); + results[indices[position]].VirtualAddress = file.Entries[indices[position]].VirtualAddress; done[indices[position]] = true; } } diff --git a/core/relinker/relinker/src/pipeline/Conversion.cpp b/core/relinker/relinker/src/pipeline/Conversion.cpp index ad008fa002..1f371ecbca 100644 --- a/core/relinker/relinker/src/pipeline/Conversion.cpp +++ b/core/relinker/relinker/src/pipeline/Conversion.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #include #include @@ -41,6 +43,7 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti if (options.Target != TargetSystem::Linux && options.Target != TargetSystem::Windows && options.Target != TargetSystem::MacOs) throw Domain::RelinkerException("Unknown target system"); if (options.ToRosetta && (!options.ToIntel || options.Target == TargetSystem::Windows)) throw Domain::RelinkerException("Rosetta conversion requires Intel lowering and a non-Windows target"); if (options.UnusedFilterLevel > 2) throw Domain::RelinkerException("Unused NID filter level must be 0, 1 or 2"); + if (options.FrameRedZone && options.Target != TargetSystem::Windows) throw Domain::RelinkerException("Red-zone framing requires a Windows target: only Windows exception dispatch writes below RSP"); if (input.InputPath.empty() || input.OutputPath.empty()) throw Domain::RelinkerException("Input and output names are required"); if (input.InputPath.lexically_normal() == input.OutputPath.lexically_normal()) throw Domain::RelinkerException("Executable output would overwrite the input executable"); if (!input.Modules.empty() && input.ModuleRoot.empty()) throw Domain::RelinkerException("Module root is required"); @@ -105,7 +108,27 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti throw Domain::RelinkerException("Relinker patch exceeds source image", patch.Offset); for (std::size_t index = 0; index < patch.Bytes.size(); ++index) sourceBytes[patch.Offset + index] = patch.Bytes[index]; } - GamePatch::CheckOverlaps(options.TargetName, patchedRanges, {.RelinkPatches = result.Patches, .RelaData = result.DynamicSection.RelaData, .RelaPltData = result.DynamicSection.RelaPltData}); + const auto intelSites = trampolines.size(); + std::vector displacements; + if (options.FrameRedZone) { + const auto headers = Relinker::ElfReader(sourceBytes).ReadProgramHeaders(); + std::vector codeSegments; + for (const auto& header : headers) if (header.Type == 1 && (header.Flags & 1) != 0) codeSegments.push_back(header); + std::vector functions; + for (const auto& region : Relinker::UnusedNidFilter::ReadExceptionFunctions(sourceBytes, headers, {}, {})) functions.push_back({region.Begin, region.End}); + if (functions.empty()) throw Domain::RelinkerException("Red-zone framing needs function bounds from the executable's .eh_frame, and it has none"); + Codegen::RedZoneFrameReport report; + auto framed = Codegen::FrameRedZoneFunctions(sourceBytes, codeSegments, functions, trampolines, report, displacements); + for (const auto& moved : displacements) { + if (moved.Offset > sourceBytes.size() || moved.Bytes.size() > sourceBytes.size() - moved.Offset) + throw Domain::RelinkerException("Red-zone displacement exceeds source image", moved.Offset); + std::copy(moved.Bytes.begin(), moved.Bytes.end(), sourceBytes.begin() + static_cast(moved.Offset)); + } + { std::ostringstream message; message << "Red-zone framing: " << report.Functions << " functions, " << report.RedZoneFunctions << " leaf functions keep values below RSP, " << report.Framed << " framed (" << report.FramedWithoutFramePointer << " without a frame pointer, " << report.MovedOperands << " rsp operands moved, " << report.StubbedOperands << " in " << report.OperandStubs << " operand stubs; " << report.Exits << " exits)\n"; inputProgress.Message(message.str()); } + for (const auto& [reason, count] : report.Skipped) { std::ostringstream message; message << "Red-zone framing: skipped " << count << ": " << reason << '\n'; inputProgress.Message(message.str()); } + trampolines.insert(trampolines.end(), std::make_move_iterator(framed.begin()), std::make_move_iterator(framed.end())); + } + GamePatch::CheckOverlaps(options.TargetName, patchedRanges, {.RelinkPatches = result.Patches, .RelaData = result.DynamicSection.RelaData, .RelaPltData = result.DynamicSection.RelaPltData, .RedZoneSites = std::span(trampolines).subspan(intelSites), .RedZoneDisplacements = displacements}); if (!options.Patches.empty()) { for (auto& module : input.Modules) { @@ -125,6 +148,11 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti auto byteWriter = std::make_shared(); + std::optional linuxBase; + if (!windows && !macos) + linuxBase = options.LinuxFixedBase || options.LazyBinding ? options.LinuxFixedBase : std::optional(Elfpatcher::Linux::DefaultFixedBase); + if (!windows && !macos && options.LazyBinding) + inputProgress.Message("Warning: with --lazy-binding the Linux executable stays position independent and is not placed at 0x400000; a title whose own sceLibcMspaceCreate checks the 40-bit address window stops at start-up\n"); std::shared_ptr patcher; if (macos) { patcher = std::make_shared(options.MacosFixedBase, [&](std::string_view text) { inputProgress.Message(text); }); @@ -139,7 +167,7 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti ), std::make_shared(byteWriter), byteWriter, - options.LinuxFixedBase, + linuxBase, options.TraceImports ); } @@ -157,7 +185,7 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti std::string diagnostics; if (windows) diagnostics = std::static_pointer_cast(patcher)->GetDiagnostics(); inputProgress.Report({Domain::ProgressStage::Executable, Io::Utf8Path(input.InputPath), 1, 1}); - return {std::move(executableBytes), std::move(guestArtifacts), std::move(result), std::move(diagnostics), std::move(appliedPatches)}; + return {std::move(executableBytes), std::move(guestArtifacts), std::move(result), std::move(diagnostics), std::move(appliedPatches), linuxBase}; } catch (Domain::RelinkerException& error) { if (error.InputPath.empty()) error.InputPath = Io::Utf8Path(input.InputPath); throw; diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index 78ce7cd35a..d8b7ae3793 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -103,10 +103,11 @@ RelinkResult RelinkerPipeline::Relink() { for (const auto& ph : programHeaders) { if (ph.Type != PT_DYNAMIC) continue; + if (hasDynamicSegment) + throw RelinkerException("Multiple PT_DYNAMIC segments", ph.Offset); hasDynamicSegment = true; dynTags = elfReader->ReadDynamicTags(ph); - break; } if (!hasDynamicSegment) @@ -120,9 +121,6 @@ RelinkResult RelinkerPipeline::Relink() { }; auto getTagValue = [&](const std::int64_t tag) -> std::uint64_t { - for (const auto& t : dynTags) - if (t.Tag == tag) - return t.Value; const char* name = "unknown"; switch (tag) { case DT_OS_STRTAB: name = "DT_OS_STRTAB"; break; @@ -149,6 +147,16 @@ RelinkResult RelinkerPipeline::Relink() { case DT_RELAENT: name = "DT_RELAENT"; break; case DT_OS_SYMTABSZ: name = "DT_OS_SYMTABSZ"; break; } + std::optional value; + for (const auto& t : dynTags) { + if (t.Tag != tag) + continue; + if (value && *value != t.Value) + throw RelinkerException(std::string("Conflicting duplicate ") + name + " tags"); + value = t.Value; + } + if (value) + return *value; std::ostringstream message; message << "Required dynamic tag " << name << " (0x" << std::hex << tag << ") not found"; throw RelinkerException(message.str()); @@ -277,6 +285,8 @@ RelinkResult RelinkerPipeline::Relink() { for (auto& [fst, snd] : neededLibraryNamesByStrOffset) { snd = readCStr(fst); + if (snd.empty() || snd.find_first_of("\\:$\r\n") != std::string::npos) + throw RelinkerException("Invalid dependency: " + snd, dynStrTabOffset + fst); neededLibraries.push_back(snd); } @@ -312,8 +322,8 @@ RelinkResult RelinkerPipeline::Relink() { return type == R_X86_64_DTPMOD64 || type == R_X86_64_DTPOFF64 || type == R_X86_64_TPOFF64; }; - const bool hasTlsSegment = std::any_of(programHeaders.begin(), programHeaders.end(), - [](const ProgramHeader& ph) { return ph.Type == PT_TLS; }); + const bool hasNonEmptyTlsSegment = std::any_of(programHeaders.begin(), programHeaders.end(), + [](const ProgramHeader& ph) { return ph.Type == PT_TLS && ph.MemorySize != 0; }); const auto definedTlsSymbolValue = [&](const std::uint32_t symIdx, const FileByteOffset pos) -> std::optional { const FileByteOffset symOff = symbolEntryPos(symIdx); @@ -346,9 +356,14 @@ RelinkResult RelinkerPipeline::Relink() { const std::uint32_t symIdx = static_cast(rInfo >> 32); const std::uint32_t relType = static_cast(rInfo & 0xffffffff); + validationPolicy->ValidateRelocationTypeSupported(relType, pos); + if (relType == R_X86_64_NONE) continue; + if (plt && relType != R_X86_64_JUMP_SLOT) + throw RelinkerException("Unsupported PLT relocation type", pos); + if (relType == 8) { if (symIdx != 0 && !isZeroSectionSymbol(symIdx)) throw RelinkerException("RELATIVE relocation has a nonzero symbol index", pos); @@ -369,30 +384,31 @@ RelinkResult RelinkerPipeline::Relink() { [](const std::uint8_t byte) { return byte != 0; }))) throw RelinkerException("Unnamed import symbol is not a null symbol entry", symOff); const bool weak = (raw[symOff + 4] >> 4) == STB_WEAK; - const bool function = (raw[symOff + 4] & 15) == STT_FUNC; - nidRefs.push_back({name, Domain::ImportModule(name, importModules, neededLibraries), relType, pos, rOffset, rAddend, weak, function}); + const auto symbolType = static_cast(raw[symOff + 4] & 15); + const bool function = symbolType == STT_FUNC; + nidRefs.push_back({name, Domain::ImportModule(name, importModules, neededLibraries), relType, pos, rOffset, rAddend, weak, function, symbolType}); } }; extractRela(dynRelaOffset, dynRelaSize, false); extractRela(dynJmpRelOffset, dynJmpRelSize, true); - for (const auto& ref : nidRefs) - validationPolicy->ValidateRelocationTypeSupported(ref.RelocationTypeValue, ref.RelocationTableOffset); - std::vector> unwindRanges; std::vector> segmentUnwindRanges(executableSegments.size()); - if (std::any_of(programHeaders.begin(), programHeaders.end(), [](const ProgramHeader& ph) { return ph.Type == PT_SCE_DYNLIBDATA; })) { + const bool sceImage = std::any_of(programHeaders.begin(), programHeaders.end(), [](const ProgramHeader& ph) { return ph.Type == PT_SCE_DYNLIBDATA; }); + const bool hasEhFrame = sceImage || std::any_of(programHeaders.begin(), programHeaders.end(), [](const ProgramHeader& ph) { return ph.Type == PT_GNU_EH_FRAME; }); + if (hasEhFrame) { const auto functions = UnusedNidFilter::ReadExceptionFunctions(raw, programHeaders, {}, {}); for (const auto& function : functions) unwindRanges.emplace_back(function.Begin, function.End); std::sort(unwindRanges.begin(), unwindRanges.end()); - for (std::size_t index = 0; index < executableSegments.size(); ++index) { - auto& [segment, segmentVAddr, segmentFileOffset] = executableSegments[index]; - static_cast(segmentFileOffset); - if (const auto codeEnd = FindSceCodeEnd(segment, segmentVAddr, functions, gotVAddr, dynJmpRelSize)) - segment.resize(*codeEnd - segmentVAddr); - segmentUnwindRanges[index] = SceUnwindRanges(segment, segmentVAddr, functions); - } + if (sceImage) + for (std::size_t index = 0; index < executableSegments.size(); ++index) { + auto& [segment, segmentVAddr, segmentFileOffset] = executableSegments[index]; + static_cast(segmentFileOffset); + if (const auto codeEnd = FindSceCodeEnd(segment, segmentVAddr, functions, gotVAddr, dynJmpRelSize)) + segment.resize(*codeEnd - segmentVAddr); + segmentUnwindRanges[index] = SceUnwindRanges(segment, segmentVAddr, functions); + } } std::vector> scanSegments; @@ -432,13 +448,42 @@ RelinkResult RelinkerPipeline::Relink() { std::memcpy(&sourceName, symbol, sizeof(sourceName)); auto name = readCStr(sourceName); name.resize(name.find('#') == std::string::npos ? name.size() : name.find('#')); + const FileByteOffset entryOffset = dynSymTabOffset + off; + const auto type = symbol[4] & 15; + std::uint64_t value = 0; + std::uint64_t size = 0; + std::memcpy(&value, symbol + 8, sizeof(value)); + std::memcpy(&size, symbol + 16, sizeof(size)); + if (type == STT_TLS) { + if (section >= SHN_LORESERVE) + throw RelinkerException("Unsupported executable TLS export section: " + name, entryOffset); + const auto tls = std::find_if(programHeaders.begin(), programHeaders.end(), + [](const ProgramHeader& ph) { return ph.Type == PT_TLS; }); + if (tls == programHeaders.end()) + throw RelinkerException("Executable TLS export has no PT_TLS segment: " + name, entryOffset); + if (value > tls->MemorySize || size > tls->MemorySize - value) + throw RelinkerException("Executable TLS export exceeds PT_TLS: " + name, entryOffset); + } else if (section != SHN_ABS) { + if (section >= SHN_LORESERVE) + throw RelinkerException("Unsupported executable export section: " + name, entryOffset); + const std::uint32_t flags = type == STT_FUNC ? PF_X : 0; + const auto extent = std::max(size, 1); + const bool mapped = std::any_of(programHeaders.begin(), programHeaders.end(), [&](const ProgramHeader& ph) { + return ph.Type == PT_LOAD && (ph.Flags & flags) == flags && value >= ph.MappedAddress && + value - ph.MappedAddress < ph.MemorySize && extent <= ph.MemorySize - (value - ph.MappedAddress); + }); + if (!mapped) { + std::ostringstream message; + message << "Executable export " << name << " at 0x" << std::hex << value + << (size > 1 ? " (" + std::to_string(size) + " bytes) does not fit inside " : " is not inside ") + << (flags != 0 ? "an executable" : "a") << " PT_LOAD segment"; + throw RelinkerException(message.str(), entryOffset); + } + } exportedSymbols.emplace_back(off, std::move(name)); const auto binding = symbol[4] >> 4; - if ((binding == STB_GLOBAL || binding == STB_WEAK) && (symbol[4] & 15) == STT_FUNC) { - VirtualAddress address = 0; - std::memcpy(&address, symbol + 8, sizeof(address)); - exportedFunctions.push_back(address); - } + if ((binding == STB_GLOBAL || binding == STB_WEAK) && type == STT_FUNC) + exportedFunctions.push_back(value); } } @@ -543,8 +588,8 @@ RelinkResult RelinkerPipeline::Relink() { continue; offset = *value; } - if (!hasTlsSegment) - throw RelinkerException("TLS relocation in an executable without a PT_TLS segment", pos); + if (!hasNonEmptyTlsSegment) + throw RelinkerException("TLS relocation in an executable without a non-empty PT_TLS segment", pos); if (relType == R_X86_64_DTPMOD64 && rAddend != 0) throw RelinkerException("DTPMOD64 relocation has a nonzero addend", pos); const auto addend = relType == R_X86_64_DTPMOD64 ? 0 : static_cast(offset + static_cast(rAddend)); diff --git a/core/relinker/relinker/tests/CallRegistryWriterTests.cpp b/core/relinker/relinker/tests/CallRegistryWriterTests.cpp new file mode 100644 index 0000000000..542f386b46 --- /dev/null +++ b/core/relinker/relinker/tests/CallRegistryWriterTests.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +class GroupedPunctuation : public std::numpunct { +public: + explicit GroupedPunctuation(char separator) : separator(separator) {} + +protected: + char do_thousands_sep() const override { return separator; } + std::string do_grouping() const override { return "\3"; } + +private: + char separator; +}; + +class ScopedLocale { +public: + explicit ScopedLocale(const std::locale& locale) : previous(std::locale::global(locale)) {} + ~ScopedLocale() { std::locale::global(previous); } + +private: + std::locale previous; +}; + +void RegistryOffsets() { + Relinker::CallRegistryWriter writer; + Relinker::CallRegistryEntry entry; + entry.Nid = "testNid"; + entry.Library = "test.prx"; + entry.RelocationTypeString = "R_X86_64_JUMP_SLOT"; + entry.RelocationOffset = 0x12345678; + entry.TargetSection = ".text"; + entry.TargetOffset = 0xabcdef0123456789ULL; + entry.CallSites = {0, 0xfff, 0x1000, 0xffffffffffffffffULL}; + entry.CallSitesResolved = true; + entry.Weak = true; + std::string registry; + std::string imports; + std::string libraries; + { + const ScopedLocale classic(std::locale::classic()); + registry = writer.WriteCallRegistry({entry}); + imports = writer.WriteModuleImports({entry}); + libraries = writer.WriteNeededLibraries({entry.Library}); + } + Require(registry.find("\"relocationOffset\": \"0x12345678\"") != std::string::npos, "relocation offset is not canonical hexadecimal"); + Require(registry.find("\"targetOffset\": \"0xABCDEF0123456789\"") != std::string::npos, "target offset is not canonical hexadecimal"); + Require(registry.find("\"callSites\": [\"0x0\", \"0xFFF\", \"0x1000\", \"0xFFFFFFFFFFFFFFFF\"]") != std::string::npos, "call sites are not canonical hexadecimal"); + Require(imports.find("\"targetOffset\": \"0xABCDEF0123456789\"") != std::string::npos, "module target offset is not canonical hexadecimal"); + for (const char separator : {',', '.'}) { + const ScopedLocale grouped(std::locale(std::locale::classic(), new GroupedPunctuation(separator))); + std::ostringstream control; + control << std::hex << 0x12345678; + Require(control.str().find(separator) != std::string::npos, "grouping locale is not active"); + Require(writer.WriteCallRegistry({entry}) == registry, "call registry depends on the host locale"); + Require(writer.WriteModuleImports({entry}) == imports, "module imports depend on the host locale"); + Require(writer.WriteNeededLibraries({entry.Library}) == libraries, "needed libraries depend on the host locale"); + Require(std::use_facet>(std::locale()).thousands_sep() == separator, "registry writer changed the global locale"); + } +} + +} + +int main() { + try { + RegistryOffsets(); + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/relinker/relinker/tests/CallSiteResolverTests.cpp b/core/relinker/relinker/tests/CallSiteResolverTests.cpp index f06fab07fa..16ca224079 100644 --- a/core/relinker/relinker/tests/CallSiteResolverTests.cpp +++ b/core/relinker/relinker/tests/CallSiteResolverTests.cpp @@ -31,6 +31,30 @@ int main() { check("RIP-relative function pointer load", {0x48, 0x8b, 0x05, 0xf9, 0, 0, 0}, true); check("REX-prefixed indirect call", {0x48, 0xff, 0x15, 0xf9, 0, 0, 0}, true); check("multiple REX prefixes on pointer load", {0x40, 0x48, 0x8b, 0x05, 0xf8, 0, 0, 0}, true); + check("earlier REX.W does not widen a pointer load", {0x48, 0x40, 0x8b, 0x05, 0xf8, 0, 0, 0}, false); + check("final REX.W widens a pointer load", {0x48, 0x40, 0x48, 0x8b, 0x05, 0xf7, 0, 0, 0}, true); + check("BND indirect call", {0xf2, 0xff, 0x15, 0xf9, 0, 0, 0}, true); + check("BND indirect tail jump", {0xf2, 0xff, 0x25, 0xf9, 0, 0, 0}, true); + check("NOTRACK indirect call", {0x3e, 0xff, 0x15, 0xf9, 0, 0, 0}, true); + check("NOTRACK indirect tail jump", {0x3e, 0xff, 0x25, 0xf9, 0, 0, 0}, true); + check("NOTRACK BND indirect call", {0x3e, 0xf2, 0xff, 0x15, 0xf8, 0, 0, 0}, true); + check("BND NOTRACK indirect tail jump", {0xf2, 0x3e, 0xff, 0x25, 0xf8, 0, 0, 0}, true); + check("BND REX indirect call", {0xf2, 0x48, 0xff, 0x15, 0xf8, 0, 0, 0}, true); + check("NOTRACK REX indirect tail jump", {0x3e, 0x48, 0xff, 0x25, 0xf8, 0, 0, 0}, true); + check("NOTRACK-prefixed pointer load", {0x3e, 0x48, 0x8b, 0x05, 0xf8, 0, 0, 0}, true); + check("REPNE-prefixed pointer load", {0xf2, 0x48, 0x8b, 0x05, 0xf8, 0, 0, 0}, true); + check("DS after REX.W leaves a narrow load", {0x48, 0x3e, 0x8b, 0x05, 0xf8, 0, 0, 0}, false); + check("REPNE after REX.W leaves a narrow load", {0x48, 0xf2, 0x8b, 0x05, 0xf8, 0, 0, 0}, false); + check("final REX.W after DS widens a load", {0x48, 0x3e, 0x48, 0x8b, 0x05, 0xf7, 0, 0, 0}, true); + check("final REX.W after REPNE widens a load", {0x48, 0xf2, 0x48, 0x8b, 0x05, 0xf7, 0, 0, 0}, true); + check("NOTRACK preserves final narrow REX", {0x3e, 0x48, 0x40, 0x8b, 0x05, 0xf7, 0, 0, 0}, false); + check("FS call is not a flat GOT reference", {0x64, 0xf2, 0xff, 0x15, 0xf8, 0, 0, 0}, false); + check("GS jump is not a flat GOT reference", {0x3e, 0x65, 0xff, 0x25, 0xf8, 0, 0, 0}, false); + check("address-size override is not a RIP64 reference", {0x3e, 0x67, 0xff, 0x15, 0xf8, 0, 0, 0}, false); + check("BND register call has no RIP operand", {0xf2, 0xff, 0xd0}, false); + check("NOTRACK register jump has no RIP operand", {0x3e, 0xff, 0xe0}, false); + check("BND-prefixed increment is not an import access", {0xf2, 0xff, 0x05, 0xf9, 0, 0, 0}, false); + check("DS-prefixed decrement is not an import access", {0x3e, 0xff, 0x0d, 0xf9, 0, 0, 0}, false); check("pointer load without REX.W", {0x40, 0x8b, 0x05, 0xf9, 0, 0, 0}, false); check("register pointer load", {0x48, 0x8b, 0x00}, false); check("increment memory", {0xff, 0x05, 0xfa, 0, 0, 0}, false); diff --git a/core/relinker/relinker/tests/CodeDataScanningTests.cpp b/core/relinker/relinker/tests/CodeDataScanningTests.cpp new file mode 100644 index 0000000000..0b2e06c47c --- /dev/null +++ b/core/relinker/relinker/tests/CodeDataScanningTests.cpp @@ -0,0 +1,86 @@ +#include "TestProgress.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using Bytes = std::vector; + +constexpr std::size_t DataOffset = 6; +constexpr std::size_t RoutineOffset = 14; +constexpr std::array UndecodableData{0x0f, 0x78, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +Bytes Fixture(bool syscall) { + constexpr std::array entry{0xe8, 0x09, 0x00, 0x00, 0x00, 0xc3}; + Bytes code(RoutineOffset, 0x90); + std::copy(entry.begin(), entry.end(), code.begin()); + if (syscall) code.insert(code.end(), {0xb8, 0x09, 0x00, 0x00, 0x00, 0x0f, 0x05}); + code.push_back(0xc3); + return code; +} + +void CheckUndecodableData(Domain::FileByteOffset origin) { + TestProgress progress; + const auto scanner = Relinker::MakeSyscallScanner(progress); + auto code = Fixture(false); + Require(scanner->ScanCodeSectionForSyscalls(code, origin, code.size()).empty(), "Valid code across a padding gap was rejected"); + std::copy(UndecodableData.begin(), UndecodableData.end(), code.begin() + DataOffset); + try { + scanner->ScanCodeSectionForSyscalls(code, origin, code.size()); + } catch (const Codegen::CodegenException& error) { + Require(error.FailureOffset == origin + DataOffset, "Decode failure did not identify the embedded data"); + Require(std::string(error.what()).find("Cannot decode instruction: Unsupported 0F 78 opcode without SSE4A prefix") != std::string::npos, "Unexpected embedded-data decode failure"); + std::cout << "Embedded-data decode rejection reproduced at " << error.FailureOffset << '\n'; + return; + } + throw std::runtime_error("Embedded-data scanning changed; update the known-gap fixture and debt entry"); +} + +void CheckForbiddenInstruction(const Bytes& code, Domain::FileByteOffset origin, std::size_t offset, const std::string& number) { + for (const bool trap : {false, true}) { + TestProgress progress; + try { + Relinker::MakeSyscallScanner(progress, trap)->ScanCodeSectionForSyscalls(code, origin, code.size()); + } catch (const Domain::RelinkerException& error) { + Require(error.FailureOffset == origin + offset, "Forbidden instruction reported at the wrong offset"); + Require(std::string(error.what()).find("Forbidden syscall instruction at code offset 0x") != std::string::npos, "Unexpected syscall rejection"); + Require(number.empty() || std::string(error.what()).find(number) != std::string::npos, "Lost the reachable routine's syscall number"); + continue; + } + throw std::runtime_error("Forbidden instruction was not reported"); + } +} + +void CheckGapAndReachableRoutine(Domain::FileByteOffset origin) { + auto code = Fixture(false); + code[DataOffset] = 0x0f; + code[DataOffset + 1] = 0x05; + CheckForbiddenInstruction(code, origin, DataOffset, {}); + CheckForbiddenInstruction(Fixture(true), origin, RoutineOffset + 5, "syscall number 9"); +} + +} + +int main() { + try { + for (const auto origin : {std::uint64_t{0}, std::uint64_t{0x123400}, std::uint64_t{0x100000000}}) { + CheckUndecodableData(origin); + CheckGapAndReachableRoutine(origin); + } + std::cout << "Embedded-code-data known-gap and reachable-code controls passed\n"; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp index 94a34990c1..aa79bae7f0 100644 --- a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp +++ b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { @@ -142,6 +143,26 @@ void CheckRelocatedTransfers() { RequireOffsets(*unresolved, {0, 6, 7, 8}); } +void CheckAddressSizeOverride() { + constexpr auto slot = TextAddress + 0x1000; + Bytes text{0x67, 0xff, 0x15, 0, 0, 0, 0, 0x90, 0xc3, 0x67, 0xff, 0x25, 0, 0, 0, 0, 0x90, 0x90, 0xc3}; + SetDisplacement(text, 3, 7, slot); + SetDisplacement(text, 12, 16, slot + 8); + Pointers pointers; + pointers.Slots = {{static_cast(slot), TextAddress + 9}, {static_cast(slot + 8), TextAddress + 18}}; + RequireOffsets(*BuildControlFlowGraph(text, TextAddress, TextAddress, {}, pointers), {0, 7, 8, 9, 18}); + RequireOffsets(*BuildControlFlowGraph(text, TextAddress, TextAddress + 9, {}, pointers), {9, 18}); + + Bytes lea(17, 0x90); + const Bytes opcode{0x67, 0x48, 0x8d, 0x05}; + std::copy(opcode.begin(), opcode.end(), lea.begin()); + SetDisplacement(lea, 4, 8, TextAddress + 16); + lea[8] = lea[16] = 0xc3; + const Pointers missing; + RequireOffsets(*BuildControlFlowGraph(lea, TextAddress, TextAddress, {}, missing, true), {0, 8}); + RequireOffsets(*BuildAddressTakenControlFlowGraph(lea, TextAddress, TextAddress, {}, missing, {}), {0, 8}); +} + void CheckCodeAddresses() { Bytes text(17, 0x90); text[0] = 0x48; @@ -344,6 +365,257 @@ void CheckRelativeJumpTables() { Require(Relinker::FindRelativeJumpTables(cutShort, TextAddress, {{TextAddress, TextAddress + cutShort.size()}}).empty(), "Table followed by more of its function was accepted"); } +void CheckEmbeddedDataTables() { + constexpr std::size_t tableOffset = 24; + Bytes text(48, 0x90); + text[0] = 0x48; + text[1] = 0x8d; + text[2] = 0x05; + SetDisplacement(text, 3, 7, TextAddress + tableOffset); + text[7] = 0xc3; + for (std::size_t i = 8; i < tableOffset; ++i) text[i] = 0xcc; + text[tableOffset] = 0x0f; + text[tableOffset + 1] = 0x78; + text[tableOffset + 2] = 0x33; + text[tableOffset + 3] = 0x1e; + + const std::vector> functions{ + {TextAddress, TextAddress + 8}, + {TextAddress + text.size(), TextAddress + text.size() + 8} + }; + const auto tables = Relinker::FindRelativeJumpTables(text, TextAddress, functions); + Require(tables.size() == 1, "Expected one embedded data table"); + Require(tables[0].Function == TextAddress && tables[0].Begin == TextAddress + tableOffset && tables[0].End == TextAddress + text.size(), "Embedded data table range was incorrect"); + + Bytes withBranch = text; + withBranch[7] = 0xe8; + SetDisplacement(withBranch, 8, 12, TextAddress + tableOffset); + withBranch[12] = 0xc3; + const std::vector> functionsWithBranch{ + {TextAddress, TextAddress + 13}, + {TextAddress + withBranch.size(), TextAddress + withBranch.size() + 8} + }; + const auto tablesWithBranch = Relinker::FindRelativeJumpTables(withBranch, TextAddress, functionsWithBranch); + Require(tablesWithBranch.empty(), "Branch target was incorrectly recognized as a data table"); + + Bytes withValidCode = text; + withValidCode[tableOffset] = 0x48; + withValidCode[tableOffset + 1] = 0x89; + withValidCode[tableOffset + 2] = 0xc0; + withValidCode[tableOffset + 3] = 0xc3; + for (std::size_t i = tableOffset + 4; i < withValidCode.size(); ++i) withValidCode[i] = 0xcc; + const auto tablesWithValidCode = Relinker::FindRelativeJumpTables(withValidCode, TextAddress, functions); + Require(tablesWithValidCode.empty(), "Valid unannotated code in gap was incorrectly recognized as a data table"); + + Bytes withTransitiveBranch = text; + withTransitiveBranch[7] = 0xe8; + SetDisplacement(withTransitiveBranch, 8, 12, TextAddress + 16); + withTransitiveBranch[12] = 0xc3; + for (std::size_t i = 13; i < 16; ++i) withTransitiveBranch[i] = 0xcc; + withTransitiveBranch[16] = 0xe9; + SetDisplacement(withTransitiveBranch, 17, 21, TextAddress + tableOffset); + for (std::size_t i = 21; i < tableOffset; ++i) withTransitiveBranch[i] = 0xcc; + const std::vector> functionsTransitive{ + {TextAddress, TextAddress + 13}, + {TextAddress + withTransitiveBranch.size(), TextAddress + withTransitiveBranch.size() + 8} + }; + const auto tablesTransitive = Relinker::FindRelativeJumpTables(withTransitiveBranch, TextAddress, functionsTransitive); + Require(tablesTransitive.empty(), "Transitive branch target was incorrectly recognized as a data table"); + + Bytes withTrailingCode(64, 0x90); + withTrailingCode[0] = 0x48; + withTrailingCode[1] = 0x8d; + withTrailingCode[2] = 0x05; + SetDisplacement(withTrailingCode, 3, 7, TextAddress + 16); + withTrailingCode[7] = 0xc3; + for (std::size_t i = 8; i < 16; ++i) withTrailingCode[i] = 0xcc; + withTrailingCode[16] = 0x0f; + withTrailingCode[17] = 0x78; + withTrailingCode[18] = 0x11; + withTrailingCode[19] = 0x22; + withTrailingCode[20] = 0x33; + withTrailingCode[21] = 0x44; + for (std::size_t i = 22; i < 32; ++i) withTrailingCode[i] = 0xcc; + withTrailingCode[32] = 0xb8; + withTrailingCode[33] = 0x3b; + withTrailingCode[34] = 0x00; + withTrailingCode[35] = 0x00; + withTrailingCode[36] = 0x00; + withTrailingCode[37] = 0x0f; + withTrailingCode[38] = 0x05; + withTrailingCode[39] = 0xc3; + for (std::size_t i = 40; i < withTrailingCode.size(); ++i) withTrailingCode[i] = 0x90; + const std::vector> functionsTrailing{ + {TextAddress, TextAddress + 8}, + {TextAddress + withTrailingCode.size(), TextAddress + withTrailingCode.size() + 8} + }; + const auto tablesTrailing = Relinker::FindRelativeJumpTables(withTrailingCode, TextAddress, functionsTrailing); + Require(tablesTrailing.size() == 1, "Expected one bounded data table before trailing code"); + Require(tablesTrailing[0].Begin == TextAddress + 16 && tablesTrailing[0].End <= TextAddress + 32, "Mask overran into trailing code"); + + Bytes withCallbackSyscall(48, 0x90); + withCallbackSyscall[0] = 0x48; + withCallbackSyscall[1] = 0x8d; + withCallbackSyscall[2] = 0x05; + SetDisplacement(withCallbackSyscall, 3, 7, TextAddress + 16); + withCallbackSyscall[7] = 0xc3; + for (std::size_t i = 8; i < 16; ++i) withCallbackSyscall[i] = 0xcc; + withCallbackSyscall[16] = 0xb8; + withCallbackSyscall[17] = 0x3b; + withCallbackSyscall[18] = 0x00; + withCallbackSyscall[19] = 0x00; + withCallbackSyscall[20] = 0x00; + withCallbackSyscall[21] = 0x0f; + withCallbackSyscall[22] = 0x05; + withCallbackSyscall[23] = 0x0f; + withCallbackSyscall[24] = 0x78; + withCallbackSyscall[25] = 0x11; + withCallbackSyscall[26] = 0x22; + withCallbackSyscall[27] = 0x33; + withCallbackSyscall[28] = 0x44; + withCallbackSyscall[29] = 0xc3; + const std::vector> functionsCallback{ + {TextAddress, TextAddress + 8}, + {TextAddress + withCallbackSyscall.size(), TextAddress + withCallbackSyscall.size() + 8} + }; + const auto tablesCallback = Relinker::FindRelativeJumpTables(withCallbackSyscall, TextAddress, functionsCallback); + Require(tablesCallback.empty(), "Callback with syscall and undecodable tail was incorrectly recognized as a data table"); + + Bytes whirlpoolText(4224, 0x90); + whirlpoolText[0] = 0x48; + whirlpoolText[1] = 0x8d; + whirlpoolText[2] = 0x2d; + SetDisplacement(whirlpoolText, 3, 7, TextAddress + 32); + whirlpoolText[7] = 0xc3; + for (std::size_t i = 8; i < 32; ++i) whirlpoolText[i] = 0xcc; + static constexpr std::uint8_t whirlpoolPrefix[]{ + 0x18, 0x18, 0x60, 0x18, 0xc0, 0x78, 0x30, 0xd8, 0x18, 0x18, 0x60, 0x18, 0xc0, 0x78, 0x30, 0xd8, + 0x0f, 0x78, 0x33, 0x1e + }; + std::memcpy(whirlpoolText.data() + 32, whirlpoolPrefix, sizeof(whirlpoolPrefix)); + const std::vector> functionsWhirlpool{ + {TextAddress, TextAddress + 8}, + {TextAddress + whirlpoolText.size(), TextAddress + whirlpoolText.size() + 8} + }; + const auto tablesWhirlpool = Relinker::FindRelativeJumpTables(whirlpoolText, TextAddress, functionsWhirlpool); + Require(tablesWhirlpool.size() == 1, "Expected Whirlpool-style table to be recognized"); + Require(tablesWhirlpool[0].Begin == TextAddress + 32, "Whirlpool table start was incorrect"); +} + +void CheckDecodedPaddingSuffix() { + Bytes text(0x38, 0x90); + text[0] = 0x48; + text[1] = 0x8d; + text[2] = 0x05; + SetDisplacement(text, 3, 7, TextAddress + 0x10); + text[7] = 0xc3; + for (std::size_t i = 8; i < 0x10; ++i) text[i] = 0xcc; + static constexpr std::uint8_t suffix[]{0x0f, 0x78, 0xcc, 0xb8, 0x09, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x00, 0x01, 0x90}; + std::memcpy(text.data() + 0x10, suffix, sizeof(suffix)); + text[0x30] = 0xc3; + const std::vector> functions{ + {TextAddress, TextAddress + 8}, + {TextAddress + 0x30, TextAddress + 0x38} + }; + const auto tables = Relinker::FindRelativeJumpTables(text, TextAddress, functions); + Require(tables.size() == 1, "Expected one decoded data table before a padding-suffixed syscall"); + Require(tables[0].Begin == TextAddress + 0x10 && tables[0].End == TextAddress + 0x12, "Mask extended over the syscall after a decoded padding suffix"); +} + +void CheckStubBeforeUndecodableData() { + Bytes text(0x38, 0x90); + text[0] = 0x48; + text[1] = 0x8d; + text[2] = 0x05; + SetDisplacement(text, 3, 7, TextAddress + 0x10); + text[7] = 0xc3; + for (std::size_t i = 8; i < 0x10; ++i) text[i] = 0xcc; + static constexpr std::uint8_t suffix[]{ + 0x0f, 0x78, 0xcc, 0xb8, 0x09, 0x00, 0x00, 0x00, + 0x0f, 0x05, 0xc3, 0x0f, 0x78, 0x33, 0x1e + }; + std::memcpy(text.data() + 0x10, suffix, sizeof(suffix)); + const std::vector> functions{ + {TextAddress, TextAddress + 8}, + {TextAddress + text.size(), TextAddress + text.size() + 8} + }; + const auto tables = Relinker::FindRelativeJumpTables(text, TextAddress, functions); + Require(tables.size() == 1, "Expected one data table before a syscall stub followed by undecodable data"); + Require(tables[0].Begin == TextAddress + 0x10, "Data table start before a syscall stub was incorrect"); + Require(tables[0].End <= TextAddress + 0x13, "Mask covered the syscall stub before undecodable data"); +} + +void CheckTableWithPaddingAndReturnBytes() { + constexpr std::size_t gap = 0x800; + Bytes text(0x10 + gap, 0x90); + text[0] = 0x48; + text[1] = 0x8d; + text[2] = 0x05; + SetDisplacement(text, 3, 7, TextAddress + 0x10); + text[7] = 0xc3; + for (std::size_t i = 8; i < 0x10; ++i) text[i] = 0xcc; + static constexpr std::uint8_t row[]{ + 0x18, 0x18, 0x60, 0x18, 0xc0, 0x78, 0x30, 0xd8, + 0x18, 0x18, 0x60, 0x18, 0xc0, 0x78, 0x30, 0xd8, + 0x0f, 0x78, 0x33, 0x1e + }; + for (std::size_t at = 0x10; at + sizeof(row) <= text.size(); at += sizeof(row)) + std::memcpy(text.data() + at, row, sizeof(row)); + static constexpr std::uint8_t paddedChain[]{0x00, 0xcc, 0x90, 0x48, 0x89, 0xc0, 0xc3}; + static constexpr std::uint8_t alignedChain[]{0x00, 0xcc, 0x90, 0x00, 0x48, 0x89, 0xc0, 0xc3}; + std::memcpy(text.data() + 0x10 + 0x3b8, paddedChain, sizeof(paddedChain)); + std::memcpy(text.data() + 0x10 + 0x4ac, alignedChain, sizeof(alignedChain)); + const std::vector> functions{ + {TextAddress, TextAddress + 8}, + {TextAddress + text.size(), TextAddress + text.size() + 8} + }; + const auto tables = Relinker::FindRelativeJumpTables(text, TextAddress, functions); + Require(tables.size() == 1, "Expected a table with padding and return bytes to be recognized"); + Require(tables[0].Begin == TextAddress + 0x10, "Table with padding and return bytes started at the wrong address"); + Require(tables[0].End == TextAddress + text.size(), "Mask stopped inside a table with padding and return bytes"); +} + +void CheckLargeZeroGaps() { + constexpr std::size_t stride = 0x10000; + constexpr std::size_t count = 32; + constexpr std::size_t functionSize = 8; + constexpr std::size_t codeFill = 0x8000; + Bytes text(stride * count, 0x00); + std::vector> functions; + for (std::size_t index = 0; index < count; ++index) { + const auto begin = index * stride; + text[begin] = 0x48; + text[begin + 1] = 0x8d; + text[begin + 2] = 0x05; + SetDisplacement(text, begin + 3, begin + 7, TextAddress + begin + functionSize); + text[begin + 7] = 0xc3; + const auto table = begin + functionSize; + text[table] = 0x0f; + text[table + 1] = 0x78; + text[table + 2] = 0x33; + text[table + 3] = 0x1e; + for (auto at = table + 4; at + 2 < begin + codeFill; at += 3) { + text[at] = 0x48; + text[at + 1] = 0x89; + text[at + 2] = 0xc0; + } + functions.emplace_back(TextAddress + begin, TextAddress + begin + functionSize); + } + functions.emplace_back(TextAddress + stride * count, TextAddress + stride * count + functionSize); + + const auto started = std::chrono::steady_clock::now(); + const auto tables = Relinker::FindRelativeJumpTables(text, TextAddress, functions); + const auto elapsed = std::chrono::steady_clock::now() - started; + + Require(tables.size() == count, "Expected one data table per large gap"); + for (std::size_t index = 0; index < tables.size(); ++index) { + Require(tables[index].Function == TextAddress + index * stride, "Large gap table was attached to the wrong function"); + Require(tables[index].Begin == TextAddress + index * stride + functionSize, "Large gap table start was incorrect"); + Require(tables[index].End == TextAddress + (index + 1) * stride, "Large gap table end was incorrect"); + } + Require(elapsed < std::chrono::seconds(10), "Large gap analysis took too long"); +} + } int main() { @@ -354,6 +626,7 @@ int main() { CheckCallsBeforeZeroPadding(); CheckCallsThatStillFallThrough(); CheckRelocatedTransfers(); + CheckAddressSizeOverride(); CheckCodeAddresses(); CheckSpeculativeRoots(); CheckTrailingZeroRoots(); @@ -361,6 +634,11 @@ int main() { CheckAddressOverflow(); CheckHighVirtualAddress(); CheckRelativeJumpTables(); + CheckEmbeddedDataTables(); + CheckDecodedPaddingSuffix(); + CheckStubBeforeUndecodableData(); + CheckTableWithPaddingAndReturnBytes(); + CheckLargeZeroGaps(); std::cout << "Control-flow graph tests passed\n"; return 0; } catch (const std::exception& error) { diff --git a/core/relinker/relinker/tests/ConversionTests.cpp b/core/relinker/relinker/tests/ConversionTests.cpp index 468921cf4f..4b27916736 100644 --- a/core/relinker/relinker/tests/ConversionTests.cpp +++ b/core/relinker/relinker/tests/ConversionTests.cpp @@ -127,19 +127,56 @@ void convert() { require(progress.Events.back().Completed != 1 || progress.Events.back().Stage != Domain::ProgressStage::Executable, "Failed conversion reported completion"); } +void runtimeLifecyclePolicy() { + TestProgress progress; + const Relinker::Conversion converter(progress); + for (const auto* directory : {"sce_module", "sce_modules", "prx", "Media/Plugins"}) { + auto source = input(); + const auto parent = source.ModuleRoot / directory; + auto module = fixture(); + Io::WriteU64(module, 0x2080, 12); + Io::WriteU64(module, 0x2088, 0x1000); + Io::WriteU64(module, 208, 160); + Io::WriteU64(module, 216, 160); + source.Modules.push_back({parent / "plugin.prx", std::move(module)}); + source.RuntimeDirectories.push_back(parent); + const auto output = converter.Convert(std::move(source), {}); + const auto& bytes = output.Modules.at(0).Bytes; + const auto headers = Io::ReadU64(bytes, 32); + bool initializesOnMapping = false; + for (std::uint16_t index = 0; index < Io::ReadU16(bytes, 56); ++index) { + const auto header = headers + index * 56; + if (Io::ReadU32(bytes, header) != 2) continue; + const auto dynamic = Io::ReadU64(bytes, header + 8); + const auto end = dynamic + Io::ReadU64(bytes, header + 32); + for (auto offset = dynamic; offset < end; offset += 16) + if (Io::ReadU64(bytes, offset) == 12) initializesOnMapping = Io::ReadU64(bytes, offset + 8) != 0; + } + require(initializesOnMapping == (std::string_view(directory) != "Media/Plugins"), "Default SDK directories lost their mapping initializer"); + } +} + void fixedBase() { TestProgress progress; const Relinker::Conversion converter(progress); auto source = input(); source.Modules.push_back({"virtual/title/sce_module/plugin.prx", fixture()}); - const auto plain = converter.Convert(source, {}); + const auto standard = converter.Convert(source, {}); + Relinker::ConversionOptions lazy; + lazy.LazyBinding = true; + const auto independent = converter.Convert(input(), lazy); Relinker::ConversionOptions options; options.LinuxFixedBase = 0x400000000; const auto fixed = converter.Convert(source, options); - require(Io::ReadU16(plain.Executable, 16) == 3 && Io::ReadU16(fixed.Executable, 16) == 2, "Fixed base did not select ET_EXEC"); - require(Io::ReadU64(fixed.Executable, 24) == Io::ReadU64(plain.Executable, 24) + *options.LinuxFixedBase, "Fixed entry was not rebased above 4 GiB"); - require(fixed.Modules.front().Bytes == plain.Modules.front().Bytes, "Executable fixed base changed a bundled module"); + require(Io::ReadU16(standard.Executable, 16) == 2 && Io::ReadU16(fixed.Executable, 16) == 2, "Fixed base did not select ET_EXEC"); + require(Io::ReadU16(independent.Executable, 16) == 3, "Lazy binding did not keep ET_DYN"); + require(standard.LinuxBase == 0x400000 && fixed.LinuxBase == *options.LinuxFixedBase && !independent.LinuxBase, "Conversion reported the wrong Linux base"); + require(Io::ReadU64(fixed.Executable, 24) == Io::ReadU64(standard.Executable, 24) + *options.LinuxFixedBase - 0x400000, "Fixed entry was not rebased above 4 GiB"); + require(fixed.Modules.front().Bytes == standard.Modules.front().Bytes, "Executable fixed base changed a bundled module"); for (const auto target : {Relinker::TargetSystem::Windows, Relinker::TargetSystem::MacOs}) { + Relinker::ConversionOptions other; + other.Target = target; + require(!converter.Convert(source, other).LinuxBase, "A non-Linux conversion reported a Linux base"); options.Target = target; reject([&] { converter.Convert(source, options); }); } @@ -248,9 +285,9 @@ void baseRelocations() { require(Io::ReadU32(relocs, 4) == 12, "Wrong base relocation block size"); require(Io::ReadU16(relocs, 8) == 0xa000, "Wrong first relocation entry"); require(Io::ReadU16(relocs, 10) == 0xa008, "Wrong second relocation entry"); - reject([&] { (void)builder.BuildBaseRelocations({0x10ffc}); }); - reject([&] { (void)builder.BuildBaseRelocations({0x10ffd}); }); - reject([&] { (void)builder.BuildBaseRelocations({0x10fff}); }); + const auto spanning = builder.BuildBaseRelocations({0x10ffc, 0x10fff}); + require(spanning.size() == 12 && Io::ReadU32(spanning, 0) == 0x10000, "A page-spanning base relocation was not placed in its first page"); + require(Io::ReadU16(spanning, 8) == 0xaffc && Io::ReadU16(spanning, 10) == 0xafff, "Wrong page-spanning base relocation entries"); } std::size_t fileOffset(const std::vector& pe, std::uint32_t address) { @@ -292,17 +329,62 @@ void runtimeDirectories() { } } +void relocationValidation() { + TestProgress progress; + const Relinker::Conversion converter(progress); + { + auto source = input(); + Io::WriteU64(source.Executable, 0x2058, 24); + Io::WriteU64(source.Executable, 0x2700, 0x2000); + Io::WriteU64(source.Executable, 0x2708, (static_cast(0x9999) << 32) | 0x99); + Io::WriteU64(source.Executable, 0x2710, 0); + try { + converter.Convert(source, {}); + throw std::runtime_error("Unsupported relocation type was accepted"); + } catch (const Domain::RelinkerException& error) { + require(std::string(error.what()).find("Unsupported relocation type 0x99") != std::string::npos, "Wrong error for unsupported relocation"); + require(error.FailureOffset == 0x2700, "Wrong failure offset for unsupported relocation"); + } + } + { + auto source = input(); + const std::vector> tags{ + {5, 0x2600}, {10, 1}, {6, 0x2620}, {11, 24}, {7, 0x2700}, {8, 0}, {9, 24}, {4, 0x2680}, + {3, 0x2000}, {20, 7}, {23, 0x2780}, {2, 24}, {0, 0} + }; + const auto at = 64 + 2 * 56; + Io::WriteU64(source.Executable, at + 32, tags.size() * 16); + Io::WriteU64(source.Executable, at + 40, tags.size() * 16); + for (std::size_t index = 0; index < tags.size(); ++index) { + Io::WriteU64(source.Executable, 0x2000 + index * 16, tags[index].first); + Io::WriteU64(source.Executable, 0x2008 + index * 16, tags[index].second); + } + Io::WriteU64(source.Executable, 0x2780, 0x2000); + Io::WriteU64(source.Executable, 0x2788, (static_cast(1) << 32) | 8); + Io::WriteU64(source.Executable, 0x2790, 0); + try { + converter.Convert(source, {}); + throw std::runtime_error("Unsupported PLT relocation type was accepted"); + } catch (const Domain::RelinkerException& error) { + require(std::string(error.what()) == "Unsupported PLT relocation type", "Wrong error for unsupported PLT relocation"); + require(error.FailureOffset == 0x2780, "Wrong failure offset for unsupported PLT relocation"); + } + } +} + } int main() { try { convert(); + runtimeLifecyclePolicy(); fixedBase(); scanning(); diagnosticContext(); cancellation(); baseRelocations(); runtimeDirectories(); + relocationValidation(); } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; diff --git a/core/relinker/relinker/tests/ElfFixture.hpp b/core/relinker/relinker/tests/ElfFixture.hpp index daa1f322cb..2ed1a52cae 100644 --- a/core/relinker/relinker/tests/ElfFixture.hpp +++ b/core/relinker/relinker/tests/ElfFixture.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -117,12 +118,17 @@ public: const auto phOffset = Read(elf, 32); const auto phSize = Read(elf, 54); const auto phCount = Read(elf, 56); + const auto page = static_cast(sysconf(_SC_PAGESIZE)); + linkBase = std::numeric_limits::max(); + std::uint64_t last = 0; for (std::uint16_t index = 0; index < phCount; ++index) { const auto header = phOffset + index * phSize; if (Read(elf, header) != 1) continue; - size = std::max(size, Read(elf, header + 16) + Read(elf, header + 40)); + linkBase = std::min(linkBase, Read(elf, header + 16) / page * page); + last = std::max(last, Read(elf, header + 16) + Read(elf, header + 40)); } - if (size == 0) throw std::runtime_error("ELF has no PT_LOAD segment"); + if (last == 0) throw std::runtime_error("ELF has no PT_LOAD segment"); + size = static_cast(last - linkBase); base = mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (base == MAP_FAILED) throw std::runtime_error("Cannot map executable memory for the ELF image"); for (std::uint16_t index = 0; index < phCount; ++index) { @@ -131,15 +137,14 @@ public: const auto offset = Read(elf, header + 8); const auto fileSize = Read(elf, header + 32); if (offset > elf.size() || fileSize > elf.size() - offset) throw std::runtime_error("PT_LOAD exceeds the ELF file"); - std::memcpy(static_cast(base) + Read(elf, header + 16), elf.data() + offset, fileSize); + std::memcpy(static_cast(base) + (Read(elf, header + 16) - linkBase), elf.data() + offset, fileSize); } if (mprotect(base, size, PROT_NONE) != 0) throw std::runtime_error("Cannot protect the ELF image"); - const auto page = static_cast(sysconf(_SC_PAGESIZE)); for (std::uint16_t index = 0; index < phCount; ++index) { const auto header = phOffset + index * phSize; if (Read(elf, header) != 1) continue; const auto flags = Read(elf, header + 4); - const auto vaddr = Read(elf, header + 16); + const auto vaddr = Read(elf, header + 16) - linkBase; const auto start = vaddr / page * page; const auto end = (vaddr + Read(elf, header + 40) + page - 1) / page * page; const int protection = ((flags & 4) != 0 ? PROT_READ : 0) | ((flags & 2) != 0 ? PROT_WRITE : 0) | ((flags & 1) != 0 ? PROT_EXEC : 0); @@ -163,6 +168,7 @@ public: private: void* base = nullptr; std::size_t size = 0; + std::uint64_t linkBase = 0; }; } // namespace RelinkerTests diff --git a/core/relinker/relinker/tests/ElfReaderBoundsTests.cpp b/core/relinker/relinker/tests/ElfReaderBoundsTests.cpp index 154238155d..b1122ee8a7 100644 --- a/core/relinker/relinker/tests/ElfReaderBoundsTests.cpp +++ b/core/relinker/relinker/tests/ElfReaderBoundsTests.cpp @@ -233,6 +233,41 @@ int main() { bytes.resize(128 + 3 * 64 - 1); RejectSections(std::move(bytes)); }}, + {"section file range past end of file", [] { + auto bytes = Fixture(); + Write(bytes, 216, 0x2f0); + RejectSections(std::move(bytes)); + }}, + {"section file range offset overflow", [] { + auto bytes = Fixture(); + Write(bytes, 216, std::numeric_limits::max() - 15); + RejectSections(std::move(bytes)); + }}, + {"section ending at end of file", [] { + auto bytes = Fixture(); + Write(bytes, 216, 0x2e0); + Write(bytes, 224, 32); + const auto sections = Relinker::ElfReader(std::move(bytes)).ReadSectionHeaders(); + Require(sections.size() == 3 && sections[1].Offset + sections[1].SectionSize == 0x300); + }}, + {"unbacked SHT_NOBITS section with range past end of file", [] { + auto bytes = Fixture(); + Write(bytes, 196, 8); + Write(bytes, 216, 0x2f0); + Write(bytes, 224, 0x1000); + const auto sections = Relinker::ElfReader(std::move(bytes)).ReadSectionHeaders(); + Require(sections.size() == 3 && sections[1].Type == 8); + }}, + {"empty section beyond end of file", [] { + constexpr std::uint64_t offsets[] = {0x300, 0x301, std::numeric_limits::max()}; + for (const std::uint64_t offset : offsets) { + auto bytes = Fixture(); + Write(bytes, 216, offset); + Write(bytes, 224, 0); + const auto sections = Relinker::ElfReader(std::move(bytes)).ReadSectionHeaders(); + Require(sections.size() == 3 && sections[1].Offset == offset && sections[1].SectionSize == 0); + } + }}, {"translation program header stride", [] { for (const std::uint16_t stride : {0, 48, 64}) { auto bytes = Fixture(); diff --git a/core/relinker/relinker/tests/GamePatchTests.cpp b/core/relinker/relinker/tests/GamePatchTests.cpp index f81bc1f2fe..0b6ab99a13 100644 --- a/core/relinker/relinker/tests/GamePatchTests.cpp +++ b/core/relinker/relinker/tests/GamePatchTests.cpp @@ -282,6 +282,54 @@ void applyAndMismatch() { require(PatchTarget(bytes, "other", entries).Applied.empty() && bytes == original, "entry hit another target"); } +void programmaticWidths() { + auto bytes = twoSegmentFixture(); + const auto original = bytes; + const ElfReader reader(original); + const std::vector invalid{ + entry("t", 0x1000, {0}, {9, 8}), + entry("t", 0x1000, {0, 1}, {9}), + entry("t", 0x1000, {}, {}), + entry("t", 0x1000, {}, {9}), + entry("t", 0x1000, {0}, {}), + }; + for (const auto& patch : invalid) { + const std::vector entries{entry("t", 0x1100, {0x80}, {1}), patch}; + rejects([&] { ResolveRanges(reader, entries); }, "same nonzero width"); + rejects([&] { PatchTarget(bytes, "t", entries); }, "same nonzero width"); + require(bytes == original, "an invalid patch width wrote bytes"); + rejects([&] { ApplyPatches(bytes, "t", entries, {{0x2000, 1, 0x1100}, {0x1000, patch.Expected.size(), 0x1000}}); }, "same nonzero width"); + require(bytes == original, "direct application of an invalid patch width wrote bytes"); + for (const bool outputMode : {false, true}) { + PatchFile file; + file.MainTarget = "t"; + file.Entries = entries; + rejects([&] { VerifyPatches(file, {{"t", original}}, {}, outputMode); }, "same nonzero width"); + } + } +} + +void applicationRangeWidths() { + const std::vector original{0, 1, 2, 3}; + auto bytes = original; + const std::vector entries{entry("t", 0x1000, {0}, {9}), entry("t", 0x1002, {2, 3}, {8, 7})}; + for (const std::uint64_t length : {0u, 1u, 3u}) { + rejects([&] { ApplyPatches(bytes, "t", entries, {{0, 1, 0x1000}, {2, length, 0x1002}}); }, "range length does not match"); + require(bytes == original, "a mismatched range length wrote bytes"); + } + rejects([&] { ApplyPatches(bytes, "t", {entry("t", 0x1003, {3, 4}, {8, 7})}, {{3, 1, 0x1003}}); }, "range length does not match"); + require(bytes == original, "a truncated comparison range wrote bytes"); + for (const std::uint64_t offset : std::array{3, 4, 5, UINT64_MAX}) { + rejects([&] { ApplyPatches(bytes, "t", entries, {{0, 1, 0x1000}, {offset, 2, 0x1002}}); }, "range exceeds the image"); + require(bytes == original, "an out-of-bounds patch wrote bytes"); + } + rejects([&] { ApplyPatches(bytes, "t", entries, {{0, 1, 0x1000}}); }, "ranges do not match"); + require(bytes == original, "a missing range wrote bytes"); + const auto applied = ApplyPatches(bytes, "t", entries, {{0, 1, 0x1000}, {2, 2, 0x1002}}); + require(bytes == std::vector({9, 1, 8, 7}) && applied.size() == 2, "valid boundary patches failed"); + require(ApplyPatches(bytes, "t", {}, {}).empty(), "empty patch list was rejected"); +} + void overlapWithWriters() { const std::vector patched{{0x1010, 2, 0x401010}}; const auto reject = [&](const WriterSites& sites, const std::string& writer) { rejects([&] { CheckOverlaps("t", patched, sites); }, writer); }; @@ -305,6 +353,14 @@ void overlapWithWriters() { CheckOverlaps("t", patched, {.Traps = trapBefore}); const std::vector window{{0x100f, 4, "window"}}; reject({.Traps = window}, "window"); + const std::vector frame{{0x1011, 0x401011, 5, {}, {}, 0}}; + reject({.RedZoneSites = frame}, "red-zone frame site"); + const std::vector frameBefore{{0x100b, 0x40100b, 5, {}, {}, 0}}; + CheckOverlaps("t", patched, {.RedZoneSites = frameBefore}); + const std::vector moved{{0x100f, {0x10, 0, 0, 0}}}; + reject({.RedZoneDisplacements = moved}, "red-zone moved displacement"); + const std::vector movedBefore{{0x100f, {0x10}}}; + CheckOverlaps("t", patched, {.RedZoneDisplacements = movedBefore}); } void conversion() { @@ -340,11 +396,68 @@ void conversion() { rejects([&] { converter.Convert(conversionInput(executable), failing); }, "expected 07 found 00"); failing.Patches = {entry("custom-name.bin", 0x1000, {0xc3, 0}, {0, 0}), entry("custom-name.bin", 0x1001, {0}, {1})}; rejects([&] { converter.Convert(conversionInput(executable), failing); }, "overlaps"); + for (const auto target : {TargetSystem::Linux, TargetSystem::Windows, TargetSystem::MacOs}) { + failing.Target = target; + for (const char* name : {"custom-name.bin", "sce_module/plugin.prx"}) { + failing.Patches = {entry(name, 0x1001, {0}, {9, 8})}; + rejects([&] { converter.Convert(conversionInput(executable), failing); }, "same nonzero width"); + } + } const auto repeated = converter.Convert(conversionInput(executable), plain); require(repeated.Executable == baseline.Executable && repeated.Modules.front().Bytes == baseline.Modules.front().Bytes, "no entries changed the output"); } +std::vector framedLeafFixture() { + auto bytes = conversionFixture(); + Io::WriteU64(bytes, 64 + 32, 0x80); + Io::WriteU64(bytes, 64 + 40, 0x80); + std::fill(bytes.begin() + 0x1000, bytes.begin() + 0x1080, 0xcc); + const std::vector caller{0xe8, 0x3b, 0x00, 0x00, 0x00, 0xc3}; + const std::vector leaf{0x53, 0x41, 0x56, 0x41, 0x57, 0x48, 0x89, 0x7c, 0x24, 0xf0, 0x48, 0x8b, 0x04, 0x24, 0x48, 0x8b, 0x44, 0x24, 0x08, 0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xc3}; + std::copy(caller.begin(), caller.end(), bytes.begin() + 0x1000); + std::copy(leaf.begin(), leaf.end(), bytes.begin() + 0x1040); + const auto ehFrameHeader = 64 + 7 * 56; + Io::WriteU32(bytes, ehFrameHeader, 0x6474e550); + Io::WriteU32(bytes, ehFrameHeader + 4, 4); + for (const auto field : {8, 16, 24}) Io::WriteU64(bytes, ehFrameHeader + field, 0x2900); + Io::WriteU64(bytes, ehFrameHeader + 32, 32); + Io::WriteU64(bytes, ehFrameHeader + 40, 32); + Io::WriteU64(bytes, ehFrameHeader + 48, 4); + Io::WriteU32(bytes, 0x2800, 9); + const std::vector cie{1, 0, 1, 0x78, 16}; + std::copy(cie.begin(), cie.end(), bytes.begin() + 0x2808); + Io::WriteU32(bytes, 0x2840, 20); + Io::WriteU32(bytes, 0x2844, 0x44); + Io::WriteU64(bytes, 0x2848, 0x1040); + Io::WriteU64(bytes, 0x2850, leaf.size()); + bytes[0x2900] = 1; + bytes[0x2902] = 3; + Io::WriteU64(bytes, 0x2904, 0x2800); + Io::WriteU32(bytes, 0x290c, 1); + Io::WriteU64(bytes, 0x2910, 0x1040); + Io::WriteU64(bytes, 0x2918, 0x2840); + return bytes; +} + +void framedLeafPatches() { + TestProgress progress; + const Conversion converter(progress); + const auto executable = framedLeafFixture(); + ConversionOptions options; + options.TargetName = "custom-name.bin"; + options.Target = TargetSystem::Windows; + options.FrameRedZone = true; + converter.Convert(conversionInput(executable), options); + require(progress.Messages.find("1 framed (1 without a frame pointer, 1 rsp operands moved, 2 in 1 operand stubs; 1 exits)") != std::string::npos, "the fixture's leaf was not framed with a moved displacement and an operand stub: " + progress.Messages); + options.Patches = {entry("custom-name.bin", 0x1010, {0xcc}, {0x90})}; + require(converter.Convert(conversionInput(executable), options).AppliedPatches.size() == 1, "a patch outside the framed leaf was not applied"); + options.Patches = {entry("custom-name.bin", 0x1049, {0xf0}, {0xe8})}; + rejects([&] { converter.Convert(conversionInput(executable), options); }, "overlaps red-zone moved displacement at off=0x1049 length 1"); + options.Patches = {entry("custom-name.bin", 0x104c, {0x04}, {0x0c})}; + rejects([&] { converter.Convert(conversionInput(executable), options); }, "overlaps red-zone frame site at off=0x104a length 9"); +} + void targets() { const std::vector entries{entry("a.elf", 1, {0}, {1}), entry("sce_module/libc.prx", 1, {0}, {1})}; MatchTargets(entries, {"a.elf", "b.elf", "sce_module/libc.prx"}); @@ -385,7 +498,20 @@ void verify() { rejects([&] { VerifyPatches(ParsePatchFile(header("[[patch]]\ntarget = \"main.elf\"\nva = 0x1000\nexpected = \"00\"\nvalue = \"01\"\n")), outputs, {}, true); }, "no main_target"); auto exec = twoSegmentFixture(); Io::WriteU16(exec, 16, 2); - rejects([&] { VerifyPatches(file, {{"main.elf", exec}}, {}, true); }, "verify the input instead"); + rejects([&] { VerifyPatches(file, {{"main.elf", exec}}, {}, true); }, "has no linux_base"); + const auto based = ParsePatchFile(header("main_target = \"main.elf\"\nlinux_base = 0x400000\n[[patch]]\nname = \"one\"\ntarget = \"main.elf\"\nva = 0x1000\nexpected = \"0001\"\nvalue = \"0908\"\n" + "[[patch]]\ntarget = \"main.elf\"\nva = 0x1100\nexpected = \"ff\"\nvalue = \"01\"\n")); + require(based.LinuxBase == 0x400000, "linux_base was not read"); + auto rebased = patchedBytes; + Io::WriteU16(rebased, 16, 2); + for (const std::size_t field : {64 + 16, 64 + 24, 120 + 16, 120 + 24}) Io::WriteU64(rebased, field, Io::ReadU64(rebased, field) + 0x400000); + output = VerifyPatches(based, {{"main.elf", rebased}}, {}, true); + require(output.size() == 2 && output[0].Status == VerifyStatus::Applied && output[1].Status == VerifyStatus::Applied, "fixed-base output applied"); + require(output[0].VirtualAddress == 0x1000 && output[0].Offset == 0x1000 && output[1].Offset == 0x2000, "fixed-base output reported the rebased address"); + output = VerifyPatches(based, {{"main.elf", exec}}, {}, true); + require(output[0].Status == VerifyStatus::Mismatch && output[0].Detail.find("PT_LOAD") != std::string::npos, "linux_base was not applied"); + rejects([&] { VerifyPatches(based, outputs, {}, true); }, "has linux_base = 0x400000"); + rejects([&] { VerifyPatches(ParsePatchFile(header("main_target = \"main.elf\"\nlinux_base = 0x400000\n[[patch]]\ntarget = \"main.elf\"\nva = 0xffffffffffffff00\nexpected = \"00\"\nvalue = \"01\"\n")), {{"main.elf", rebased}}, {}, true); }, "lies outside the address space at linux_base"); rejects([&] { VerifyPatches(file, {{"main.elf", {'M', 'Z', 0x90, 0}}}, {}, true); }, "verify the input instead"); rejects([&] { VerifyPatches(file, {{"main.elf", {0xcf, 0xfa, 0xed, 0xfe, 7, 0, 0, 1}}}, {}, true); }, "verify the input instead"); rejects([&] { VerifyPatches(file, {{"main.elf", {}}}, {}, true); }, "verify the input instead"); @@ -424,10 +550,15 @@ void manifest() { require(parsed.Entries[1].Name.empty() && parsed.Entries[1].Target == "sce_module/libc.prx" && parsed.Entries[1].Description.empty(), "second entry"); require(text.find("name = \"alpha\"") != std::string::npos && text.find("main_target = \"main.elf\"") != std::string::npos, "manifest text"); require(WriteManifest(AggregateManifest({{plain}, {second}}), "main.elf", "") == text, "manifest is not deterministic"); + require(text.find("linux_base") == std::string::npos && !parsed.LinuxBase, "a manifest without a base wrote linux_base"); + const auto based = WriteManifest({plain}, "main.elf", "", 0x400000); + require(based.find("main_target = \"main.elf\"\nlinux_base = 0x400000\n") != std::string::npos && ParsePatchFile(based).LinuxBase == 0x400000, "linux_base did not round-trip"); + rejectsText(header("linux_base = \"0x400000\"\n"), 2, "linux_base must be a non-negative integer"); + rejectsText(header("linux_base = -1\n"), 2, "linux_base must be a non-negative integer"); } void reservedNames() { - for (const char* reserved : {"format", "relinker_version", "relinker_commit", "config", "config_sha256", "main_target", "settings", "settings_sha256"}) + for (const char* reserved : {"format", "relinker_version", "relinker_commit", "config", "config_sha256", "main_target", "linux_base", "settings", "settings_sha256"}) rejects([&] { ParsePatchFile(namedEntry(reserved)); }, "line 3: name is reserved"); ParsePatchFile(header("settings = \"a\"\nsettings_sha256 = \"b\"\n")); } @@ -511,8 +642,11 @@ int main() { ranges(); headerRange(); applyAndMismatch(); + programmaticWidths(); + applicationRangeWidths(); overlapWithWriters(); conversion(); + framedLeafPatches(); targets(); verify(); rangeFailureInVerify(); diff --git a/core/relinker/relinker/tests/test_copy_relocations.py b/core/relinker/relinker/tests/test_copy_relocations.py new file mode 100644 index 0000000000..5a1677e26e --- /dev/null +++ b/core/relinker/relinker/tests/test_copy_relocations.py @@ -0,0 +1,62 @@ +"""R_X86_64_COPY relocations in DT_RELA and in the PLT table are rejected by validation, before any +target code runs, with the same error for every output target; GLOB_DAT and JUMP_SLOT are accepted. The +Linux output gives every import symbol st_size 0, so a dynamic loader would copy MIN(st_size of the +definition, 0) = 0 bytes and the variable would stay zero.""" + +from pathlib import Path +import subprocess +import sys +import tempfile + +from test_jmprel_table_bounds import fixture + +R_X86_64_COPY = 5 +TARGETS = {(): "linux", ("--lazy-binding",): "lazy", ("--windows",): "windows", ("--macos",): "macos"} +ERROR = "Unsupported relocation type 0x5" + +CASES = [ + ("copy-rela", fixture(linux=True, data_relocations=True, relocation_type=R_X86_64_COPY), True), + ("copy-jmprel", fixture(linux=True, relocation_type=R_X86_64_COPY), True), + ("glob-dat-rela", fixture(linux=True, data_relocations=True), False), + ("jump-slot-jmprel", fixture(linux=True), False), +] + + +def relink(relinker, work, name, image, flags): + source = work / (name + ".elf") + output = work / (name + ".out") + source.write_bytes(image) + result = subprocess.run([str(relinker), "--skip-sce-module", *flags, str(source), str(output)], + capture_output=True, text=True, timeout=20) + return result, output + + +def fail_lines(result): + return [line for line in result.stderr.splitlines() if line.startswith("FAIL: ")] + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-copy-") as directory: + work = Path(directory) + for case, image, rejected in CASES: + failures = {} + for flags, target in TARGETS.items(): + name = f"{case}-{target}" + result, output = relink(relinker, work, name, image, flags) + if rejected: + assert result.returncode == 2 and ERROR in result.stderr and not output.exists(), ( + name, result.returncode, result.stdout, result.stderr) + failures[target] = fail_lines(result) + else: + assert result.returncode == 0 and output.exists(), ( + name, result.returncode, result.stdout, result.stderr) + output.unlink() + if rejected: + assert failures["linux"] and all(lines == failures["linux"] for lines in failures.values()), ( + case, failures) + print("COPY relocation test passed") + + +if __name__ == "__main__": + main() diff --git a/core/relinker/relinker/tests/test_dynamic_segment.py b/core/relinker/relinker/tests/test_dynamic_segment.py index 8822738f38..8045014d4e 100644 --- a/core/relinker/relinker/tests/test_dynamic_segment.py +++ b/core/relinker/relinker/tests/test_dynamic_segment.py @@ -22,10 +22,10 @@ def main(): with tempfile.TemporaryDirectory(prefix="anyps5-dynamic-segment-") as directory: work = Path(directory) - def convert(name, image, error=None): + def convert(name, image, error=None, modes=([], ["--windows"])): source = work / (name + ".elf") source.write_bytes(image) - for mode in ([], ["--windows"]): + for mode in modes: output = work / (name + (".exe" if mode else ".out")) result = subprocess.run([str(relinker), "--skip-sce-module", *mode, str(source), str(output)], capture_output=True, text=True, timeout=20) @@ -72,6 +72,10 @@ def main(): struct.pack_into("

+

LOCAL CONVERSION

PS5 apps to Windows/Linux converter

@@ -23,7 +23,7 @@

Memory downloads have a 256 MiB budget, checked against expanded files and output overhead. For multiple ZIPs, the limit applies to the combined bundle; larger batches require Save ZIP to disk in desktop Chrome or Edge. Executable and modules are limited to 256 MiB per app; the icon is also limited to 16 MiB and counts toward the Windows conversion limit. Use the native relinker for larger conversion inputs.

-

Conversion does not guarantee that an app runs. Check the community compatibility list for known blockers and reports.

+

Conversion does not guarantee that an app runs. Check the Game bring-up discussions for known blockers and reports.

diff --git a/demo/web/tests/app.test.mjs b/demo/web/tests/app.test.mjs new file mode 100644 index 0000000000..a97a239ad6 --- /dev/null +++ b/demo/web/tests/app.test.mjs @@ -0,0 +1,140 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { setImmediate } from "node:timers/promises"; +import { test } from "node:test"; + +class Element { + children = []; + listeners = new Map(); + value = ""; + checked = false; + disabled = false; + textContent = ""; + + constructor(hidden = false) { this.hidden = hidden; } + addEventListener(type, listener) { this.listeners.set(type, listener); } + removeAttribute(name) { delete this[name]; } + append(...children) { this.children.push(...children); } + replaceChildren(...children) { this.children = children; } +} + +async function setup(t, name) { + const html = readFileSync(new URL("../index.html", import.meta.url), "utf8"); + const elements = new Map([...html.matchAll(/<[^>]+\bid="([^"]+)"[^>]*>/gu)].map(match => [ + match[1], new Element(/\shidden(?:\s|>)/u.test(match[0])) + ])); + const get = id => { + assert.ok(elements.has(id), `Missing element: ${id}`); + return elements.get(id); + }; + const options = new Map(["disk", "zip", "memory"].map(value => [`[value="${value}"]`, new Element()])); + get("save").querySelector = selector => options.get(selector); + get("folder").webkitdirectory = true; + const workers = []; + class Worker { + messages = []; + terminated = false; + constructor() { workers.push(this); } + postMessage(message) { this.messages.push(message); } + terminate() { this.terminated = true; } + } + const globals = { + document: { + getElementById: get, + createElement: () => new Element(), + querySelector: selector => { + assert.equal(selector, 'input[name="target"]:checked'); + return { value: "linux" }; + } + }, + window: { isSecureContext: true, Worker, WebAssembly, ReadableStream, WritableStream, crypto: { subtle: {} } }, + Worker, + fetch: async () => ({ + ok: true, + json: async () => ({ tag: "test-release", platforms: { linux: { expandedBytes: 1024 }, windows: { expandedBytes: 1024 } } }) + }) + }; + for (const [key, value] of Object.entries(globals)) { + const original = Object.getOwnPropertyDescriptor(globalThis, key); + Object.defineProperty(globalThis, key, { value, configurable: true, writable: true }); + t.after(() => { + if (original) Object.defineProperty(globalThis, key, original); + else delete globalThis[key]; + }); + } + await import(new URL(`../app.mjs?${name}`, import.meta.url)); + await setImmediate(); + return { get, workers }; +} + +async function inspect(worker, count) { + for (let index = 0; index < count; index++) { + await worker.onmessage({ data: { + type: "inspected", index, + summary: { files: 1, totalBytes: 1024, modules: 0, selectedBytes: 1024, windowsSelectedBytes: 1024, warnings: ["Test warning"] } + } }); + } + await worker.onmessage({ data: { type: "inspection-complete" } }); +} + +for (const kind of ["zip", "batch", "folder"]) { + test(`Change resets an inspected ${kind} and permits a new conversion`, async t => { + const { get, workers } = await setup(t, kind); + assert.equal(get("convert").disabled, true); + if (kind === "folder") { + get("folder").files = [{ name: "eboot.bin", webkitRelativePath: "app/eboot.bin" }]; + get("folder").onchange(); + } else { + get("file").files = kind === "batch" ? [{ name: "first.zip" }, { name: "second.zip" }] : [{ name: "first.zip" }]; + get("file").onchange(); + } + const first = workers[0]; + assert.equal(first.messages[0].type, "inspect"); + assert.equal(first.messages[0].inputs.length, kind === "batch" ? 2 : 1); + get("change").onclick(); + assert.equal(first.terminated, false); + await inspect(first, first.messages[0].inputs.length); + assert.equal(get("selection").hidden, false); + assert.equal(get("drop").hidden, true); + assert.equal(get("warnings").hidden, false); + assert.equal(get("app-list").children.length, first.messages[0].inputs.length); + assert.equal(get("app-list").hidden, kind !== "batch"); + get("acknowledge").checked = true; + get("acknowledge").listeners.get("change")(); + assert.equal(get("convert").disabled, false); + + get("change").onclick(); + assert.equal(first.terminated, true); + assert.equal(get("file").value, ""); + assert.equal(get("folder").value, ""); + assert.equal(get("selection").hidden, true); + assert.equal(get("warnings").hidden, true); + assert.equal(get("app-list").hidden, true); + assert.equal(get("app-list").children.length, 0); + assert.equal(get("drop").hidden, false); + assert.equal(get("acknowledge").checked, false); + assert.equal(get("convert").disabled, true); + get("acknowledge").checked = true; + get("acknowledge").listeners.get("change")(); + assert.equal(get("convert").disabled, true); + get("change").onclick(); + + get("file").files = [{ name: "replacement.zip" }]; + get("file").onchange(); + assert.equal(workers.length, 2); + const replacement = workers[1]; + assert.deepEqual(replacement.messages[0].inputs.map(input => input.name), ["replacement.zip"]); + assert.equal(get("convert").disabled, true); + await inspect(replacement, 1); + assert.equal(get("archive-name").textContent, "replacement.zip"); + assert.equal(get("app-list").children.length, 1); + assert.equal(get("app-list").hidden, true); + assert.equal(get("convert").disabled, true); + get("acknowledge").checked = true; + get("acknowledge").listeners.get("change")(); + assert.equal(get("convert").disabled, false); + await get("convert").onclick(); + assert.equal(replacement.messages.at(-1).type, "convert"); + assert.equal(replacement.terminated, false); + }); +} diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md index 574244a6da..e63cb8cf20 100644 --- a/docs/dev/ARCHITECTURE.md +++ b/docs/dev/ARCHITECTURE.md @@ -45,6 +45,18 @@ flowchart LR - [`core/relinker/main.cpp`](../../core/relinker/main.cpp) runs the steps in this order. `--to-intel` is optional, see [USAGE.md](../user/USAGE.md). - Each library in [`core/libs/prx`](../../core/libs/prx) builds as a shared library. After the build, `nid_patcher` ([`core/libs/nid`](../../core/libs/nid)) renames every export to its NID, computed from the function name. `APS5_EXPORT("", func)` sets the NID directly when the name is unknown. Every other library keeps the names libc exports; it reads them from `libc.exports`, which `nid_patcher --write-export-list` rewrites only when that list changes. On Windows the link writes each import library with `SOURCE_DATE_EPOCH=0` and `ranlib -D` and replaces it only when its bytes change. A source edit that keeps the exports therefore relinks and re-patches only its own library. +## Main-module entry registers + +The host stubs that transfer control to the guest `e_entry` are not the same on every platform, and the console's register contract at that point is not measured here (#9048). What the stubs establish today: + +| Host | Source | `rdi` | `rsi` | `rdx` | `rbp` | Return from entry | +|---|---|---|---|---|---|---| +| Linux | [`EntryStubBuilder.cpp`](../../core/relinker/elfpatcher/src/general/EntryStubBuilder.cpp) | process stack: qword `argc`, then argv pointers, then a null | 0 | left as the host dynamic linker left it (often `rtld_fini`) | zero frame `{0, 0}` | `ud2` | +| Windows | [`WindowsEntryStubBuilder.cpp`](../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) | `VirtualAlloc` block: qword `argc`, UTF-8 argv pointers, null | address of an exit trampoline that finalizes guest modules and returns | not set by the stub (last host call is `FreeLibrary`) | zero frame in the stub's stack | `ExitProcess` with the entry's return code | +| macOS | [`MacOsImage.cpp`](../../core/relinker/elfpatcher/src/macos/MacOsImage.cpp) | guest entry address into `_Aps5StartGuest_nid_no_patch` | stack block `{argc, argv…, 0, envp…, 0}` | — | — | `Aps5StartGuest` has no implementation on `main`, so the final guest-entry registers are unset | + +On Linux the argument block is the process stack itself (`mov rdi, rsp`). On Windows it is a separate allocation the stub fills with UTF-8 argument strings. Titles that read only `argc`/`argv` through `rdi` see the same layout shape on both; titles that call through `rsi` on Windows get a host exit path that Linux does not provide (`rsi` is zero there). + ## Game patches [`GamePatch.cpp`](../../core/relinker/relinker/src/patching/GamePatch.cpp) parses the patch file, a strict subset of TOML. `Conversion::Convert` applies the entries of its own target to the executable bytes right after reading them, and to each bundled module before `GuestModuleBuilder` reads it, so every later stage sees patched bytes. Entry virtual addresses are translated through the `PT_LOAD` segments, and a range may not touch the ELF header or program header table, which the output writers rewrite. The patched file ranges are then checked against every in-place writer: the `--to-intel` reports and trampoline sites, `RelinkPatch` records and syscall trap ranges. The patched virtual address ranges are checked against the 8-byte targets of the `RelaData` and `RelaPltData` relocations, which the loader or the output writers overwrite. `main.cpp` builds the manifest text before it writes any output. `main.cpp` matches targets after the conversions, aggregates the applied records into `.patches.toml`, and runs `--verify-patches` through `GamePatch::VerifyPatches`, which reads only bytes. @@ -112,16 +124,17 @@ flowchart LR - [`Recompiler.cpp`](../../core/shader/recompiler/Recompiler.cpp) runs the stages in this order. With `ANYPS5_ENABLE_SPIRV_TOOLS`, the SPIR-V is also validated and optimized with SPIRV-Tools. - `ShaderRecompiler::Recompile` keeps compiled variants in memory, and `ShaderDiskCache` stores them on disk so later runs reuse them. Before the writable cache, a miss is also looked up in read-only prebuilt packs and loose entry folders under `shader_cache/prebuilt`; pack entries are ordinary encoded cache entries behind a sorted key-hash index, validated by magic, compiler source version and index checksum when mapped, and a miss or rejected entry falls through to the writable cache and the compiler. Cache format 35 also stores the finite stencil-reference set, its replay-safety proof and reserved push-data word in the artifact and binding layouts. Entries from formats 31 to 34 are rejected. -- The Vulkan pipelines are journaled as well ([`PipelineJournal.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp)): a graphics pipeline the store builds (`CachedPipeline`) or a compute pipeline a dispatch builds is appended to `pipelines.journal` next to the shader cache entries, as the materialized result of each stage (the specialized SPIR-V, bindings and push constants, through `ShaderDiskCache::EncodeResult`) plus the recipe the pipeline was built from (`GraphicsPipelineRecipe`: the stages' identities, vertex input layout, descriptor set layout keys and the state fields the `Pipeline` constructor reads, `CanonicalPipelineState`). When a `VulkanDevice` is created, the journal is loaded and its pipelines are built on a pool of `hardware_concurrency() / 2` threads (`APS5_PIPELINE_JOURNAL_THREADS`) while the title loads, so the first draw or dispatch of a configuration seen on an earlier run finds its pipeline resident. The pipeline stores key on `RecompileResult::Identity`, a hash of the module words, specialization constants, subgroup width and bindings that is the same on every run (`SharedSpirv::ContentHash`, computed once per shared module), rather than on the process-lifetime variant ids, and a graphics pipeline is built outside the store's mutex, so the replay threads and the queue workers build in parallel. A draw that arrives before its replayed entry builds the pipeline itself; the first inserted wins. A replayed entry enters the store at its least recently used end and a full store takes none, so the title's own pipelines are never evicted for a replayed one, and the optimized relink of a replayed pipeline (`OptimizedPipeline::Schedule`) waits for the first draw that binds it, so the relink thread serves the title's pipelines first. A device created without a window starts its replay at its first draw or dispatch rather than at creation, since `Driver::AttachWindow` usually replaces it before the title draws. The journal records a pipeline once (modules and recipes are deduplicated by content), keeps at most `APS5_PIPELINE_JOURNAL_MAX_MIB` (1024), replays the first `APS5_PIPELINE_JOURNAL_ENTRIES` recorded (1024, the pipeline store's bound, `APS5_PIPELINE_STORE_ENTRIES`; a run reaches those first), and is disabled with `APS5_NO_PIPELINE_JOURNAL=1` or without a shader cache directory. The file header names the device (`PipelineJournalDeviceFingerprint`, the properties the driver's pipeline cache file is named by), so a journal written for another GPU or driver is started over rather than replayed; appends and reads hold `pipelines.journal.lock` against another process sharing the directory, and a device cancels its replay before it is replaced (`PrepareForReplacement`) or destroyed, so a replaced headless device never inserts entries the window's device then sweeps. `APS5_PROFILE_DRAW=1` prints a `[pipeline-journal]` summary when the replay finishes and at teardown. +- The Vulkan pipelines are journaled as well ([`PipelineJournal.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp)): a graphics pipeline the store builds (`CachedPipeline`) or a compute pipeline a dispatch builds is appended to `pipelines.journal` next to the shader cache entries, as the materialized result of each stage (the specialized SPIR-V, bindings and push constants, through `ShaderDiskCache::EncodeResult`) plus the recipe the pipeline was built from (`GraphicsPipelineRecipe`: the stages' identities, vertex input layout, descriptor set layout keys and the state fields the `Pipeline` constructor reads, `CanonicalPipelineState`). When a `VulkanDevice` is created, the journal is loaded and its pipelines are built on a pool of `hardware_concurrency() / 2` threads (`APS5_PIPELINE_JOURNAL_THREADS`) while the title loads. The load parses the file a record at a time and keeps it open, and a replay thread reads an entry's modules from it when it builds that entry (a record that no longer reads back as loaded rejects the entry), so the replay holds the modules of the entries being built rather than the file, so the first draw or dispatch of a configuration seen on an earlier run finds its pipeline resident. The pipeline stores key on `RecompileResult::Identity`, a hash of the module words, specialization constants, subgroup width and bindings that is the same on every run (`SharedSpirv::ContentHash`, computed once per shared module), rather than on the process-lifetime variant ids, and a graphics pipeline is built outside the store's mutex, so the replay threads and the queue workers build in parallel. A draw that arrives before its replayed entry builds the pipeline itself; the first inserted wins. A replayed entry enters the store at its least recently used end and a full store takes none, so the title's own pipelines are never evicted for a replayed one, and the optimized relink of a replayed pipeline (`OptimizedPipeline::Schedule`) waits for the first draw that binds it, so the relink thread serves the title's pipelines first. A device created without a window starts its replay at its first draw or dispatch rather than at creation, since `Driver::AttachWindow` usually replaces it before the title draws. The journal records a pipeline once (modules and recipes are deduplicated by content), keeps at most `APS5_PIPELINE_JOURNAL_MAX_MIB` (1024), replays the last `APS5_PIPELINE_JOURNAL_ENTRIES` recorded (4096, the pipeline store's largest bound: a replayed pipeline that finds the store full grows its bound from 1024 instead of being dropped), and is disabled with `APS5_NO_PIPELINE_JOURNAL=1` or without a shader cache directory. The file header names the device (`PipelineJournalDeviceFingerprint`, the properties the driver's pipeline cache file is named by), so a journal written for another GPU or driver is started over rather than replayed; appends and reads hold `pipelines.journal.lock` against another process sharing the directory, and a device cancels its replay before it is replaced (`PrepareForReplacement`) or destroyed, so a replaced headless device never inserts entries the window's device then sweeps. `APS5_PROFILE_DRAW=1` prints a `[pipeline-journal]` summary when the replay finishes and at teardown. - A compute shader whose LDS plus its lock dword exceeds the host's `maxComputeSharedMemorySize` is not given a `Workgroup` array. `Recompile` sets `ShaderInfo::sharedMemoryBytes` for it and the SPIR-V backend addresses the LDS in a coherent storage buffer at descriptor set 1 (`WorkgroupMemoryDescriptorSet`), with a slice of `RecompileResult::workgroupMemoryDwords` per workgroup, indexed by `WorkgroupID` and `NumWorkGroups`. `VulkanDevice` keeps one such buffer, sized from the dispatch dimensions, and binds it after the resource set; programs that fit keep using shared memory. - `Driver::RegisterShader` splits shader preparation in two. `PlanRegistered` runs on the registering guest thread: it validates the registers and entry point, decodes the code and builds the recompile request, so invalid registrations still fail there. The RDNA to IR translation and resource plan (`PrepareShader`) run on a background pool of `hardware_concurrency() / 2` detached threads (at least 2, 64 MiB stacks). - A thread that needs the GPU mutex, the committer's next item or the end of the committer's queue spins before it blocks (`HandoffSpin.hpp`): `GpuMutexType::lock` retries `try_lock` for the window, the committer polls its item count, and `DrawPipeline::Drain` polls it for twice the window, so a hand-off that completes within the window costs no kernel wake-up. The window is 150 us when the process may run on more than 8 logical processors (its affinity mask, via `CpuTopology`) and 0 otherwise, where the spinning threads would compete with the holder; `APS5_HANDOFF_SPIN_US=` sets it (0 turns it off, at most 10000). Ordering and the lock's semantics are unchanged. -- Queue 0's packet worker decodes, captures and prepares a draw and hands its recording (`Graphics::Draw` under the GPU lock) to a committer thread in [`DrawPipeline.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp), so the next packet is decoded while the previous draw is recorded. Each queued item carries the guest ranges it will write (`DrawWriteRanges`: written buffers and storage images, color targets with their DCC and CMASK, depth and stencil). A later packet that reads or writes one of those ranges, or that the pipeline can't order, drains it first; labels, submission ends, buffer fills and (with `APS5_PIPELINE_DMA=1`) DMA packets are queued as items instead. With `APS5_PIPELINE_DISPATCH=1` (off by default), a `DISPATCH_DIRECT` whose dispatch-cache entry hits with a recipe is queued as an item too, carrying the dispatch's written buffers and storage images (`DispatchWriteRanges`); the capture and the cache lookup stay on the worker and the recipe is recorded on the committer. A dispatch still drains when its lookup misses, when what it reads overlaps a write still pending, when the hit is verified against data or when its writes cannot be ranged (bindless access, GDS, fault buffer). An indirect draw resolved on the CPU (its records read by the worker) drains only when its records or its count overlap a queued write; the records it draws inline drain first, and its last drawn record is queued like a direct draw (`APS5_NO_PIPELINED_INDIRECT=1` drains at every indirect draw). An exception thrown by a commit is rethrown on the worker at its next enqueue or drain, as an inline draw error would be. On by default; `APS5_PIPELINED_DRAWS=0` records every draw inline, and `APS5_EXACT_DRAW_WRITES=0` widens the color and depth ranges to estimates. +- Queue 0's packet worker decodes, captures and prepares a draw and hands its recording (`Graphics::Draw` under the GPU lock) to a committer thread in [`DrawPipeline.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp), so the next packet is decoded while the previous draw is recorded. Each queued item carries the guest ranges it will write (`DrawWriteRanges`: written buffers and storage images, color targets with their DCC and CMASK, depth and stencil). A later packet that reads or writes one of those ranges, or that the pipeline can't order, drains it first; labels, submission ends, buffer fills and DMA packets are queued as items instead. With `APS5_PIPELINE_DISPATCH=1` (off by default), a `DISPATCH_DIRECT` whose dispatch-cache entry hits with a recipe is queued as an item too, carrying the dispatch's written buffers and storage images (`DispatchWriteRanges`); the capture and the cache lookup stay on the worker and the recipe is recorded on the committer. A dispatch still drains when its lookup misses, when what it reads overlaps a write still pending, when the hit is verified against data or when its writes cannot be ranged (bindless access, GDS, fault buffer). An indirect draw resolved on the CPU (its records read by the worker) drains only when its records or its count overlap a queued write; the records it draws inline drain first, and its last drawn record is queued like a direct draw (`APS5_NO_PIPELINED_INDIRECT=1` drains at every indirect draw). An exception thrown by a commit is rethrown on the worker at its next enqueue or drain, as an inline draw error would be. On by default; `APS5_PIPELINED_DRAWS=0` records every draw inline, and `APS5_EXACT_DRAW_WRITES=0` widens the color and depth ranges to estimates. - A dispatch's resource build runs stage A (binding plan, buffers, descriptor set) without the GPU lock and stage B (image lookups) under it. Between them `VulkanDevice::presync` waits, still without the lock, for recorded work that writes a surface stage B would read on the CPU and for recorded work that writes the DCC keys of an image the dispatch binds (`ShaderResources::PresyncSurfaces`), so the locked `WaitForKeyWriters` finds nothing pending to synchronize with. A key range is waited for only when recorded work writes it, the key-store memo does not give its value (`DccKeyWritersPending`, without the mutex) and the pending stores do not give it either (`DccKeyWriterNeedsSync`, under the mutex, the same test `WaitForKeyWriters` makes before it synchronizes). Stage A's own key scan runs before that wait, and stage B compares it with the keys it reads under the lock as before. `APS5_NO_DCC_PRESYNC=1` leaves the key ranges out; `APS5_NO_PRESYNC=1` disables the whole pre-sync. +- `APS5_DRAW_PIPELINE=1` (off by default, meant with `APS5_PIPELINED_DRAWS=32 APS5_PIPELINE_DISPATCH=1`) takes more of a draw off the worker. A `DrawFinisher` thread runs the second half of a direct pipelined draw (stage materialization, the draw cache insert and the hand-off to the committer) while the worker reads the next packets; the worker drains it before the next draw's cache lookup, before packets other than register writes and draws, and before guest reads, and finishes in place a draw whose stages may write memory (`ProgramWritesMemory`) or write through the BDA page table. The committer skips the fragment-output validation and storage-image layer scans whose inputs match its last commit (`CommitTrims`), and a resource or storage image proved current keeps its proof for the rest of the collect epoch until `GuestMemory::BreakProofs` (a CPU write, a write-back, a recorded write) moves `ProofSerial`. Color metadata passes go through the pipeline with their write ranges (`PipelinedColorMetadataPassWrites`) instead of draining it. A data-only hit whose shape entry belongs to an older draw replaces that entry in place at the second sight of the same read user words. On Linux the main image's pages get a page table entry, and capture pages are pooled across threads. `APS5_VERIFY_DRAW_PIPELINE=1` runs a full check next to 1 in 64 reused proofs and prints `[epoch-proofs]` counts. - The shader registry maps a code address to every header registered for that code, in registration order. Static, graphics and rectangle ABI resolution pick the header at the bound address (or one equal to it, `SameHeader`, which also takes a hull header fused by `sceAgcFuseShaderHalves` (type 3) as the registered back half (type 7) it copies, the fused registers aside; graphics ABI resolution then uses the fused header's shader registers); draws and dispatches pick the program by the stage's binary type (`RegisteredProgram`), and a front entry matching both type 2 and type 4 throws. Within a type the last registration wins; re-registering an identical earlier header makes its snapshot current again without preparing it again, and changed code drops the older headers. - The snapshot is published as pending. `SourceHandleFor`, `InvocationFor` and `ShaderPreparationTransaction::Edit` / `Read` wait for the pending job before reading `PreparedShaders`, and rethrow its failure. `APS5_SYNC_SHADER_PREPARE=1` prepares on the registering thread instead. -- `APS5_LAZY_SHADER_PREPARE=1` retains registration validation and decoding but discards the temporary plan without queueing preparation. The snapshot is deferred, not pending; draws and dispatches prepare and cache it on first use. Explicit ABI resolution still prepares its requested stages. This opt-in mode takes precedence over synchronous registration preparation and trades background CPU/memory use for possible first-use stalls; disk caches and shader output are unchanged. -- On devices with graphics pipeline libraries (`SpirvTarget::registrationModules`), a vertex or pixel artifact is emitted in registration form: the buffer V# words, image modes, swizzles and mip ranges, depth-compare state, export mappings and vertex component selectors it used to specialize on per draw are read at run time (image `ResourceMetadata` and a few state dwords in the shader data), descriptor heaps keep every element in place (an unused one gets a null or placeholder descriptor), and the pixel stage's bindings live in descriptor set 1 so each stage's library depends only on its own set layout (`INDEPENDENT_SETS`). An artifact above `registrationWordLimit` SPIR-V words is re-emitted specialized, and `CompiledShaderArtifact::registrationModule` records which form it has. For artifacts in registration form, `RegistrationResult` materializes the module once, and `PrebuildStageLibrary` builds its pre-rasterization or fragment-shader library on a separate one-thread pool (`APS5_PREBUILD_THREADS`) with a fixed default state, pairing it with a stub stage. A later draw with that state finds both libraries in the store and only links. +- `APS5_LAZY_SHADER_PREPARE=1` retains registration validation and decoding but discards the temporary plan without queueing preparation. The snapshot is deferred, not pending; draws and dispatches prepare and cache it on first use. A compute or pixel stage's explicit ABI resolution (`ResolveShaderAbi`) plans the stage with the ABI's state, records the ABI and leaves its preparation to the shader's first use, which prepares it with that state (`PreparedShaders::deferredAbis`), so a shader that never draws or dispatches stays unprepared; a graphics-stage ABI is still prepared when it is resolved. This opt-in mode takes precedence over synchronous registration preparation and trades background CPU/memory use for possible first-use stalls; disk caches and shader output are unchanged. +- On devices with graphics pipeline libraries (`SpirvTarget::registrationModules`), a vertex or pixel artifact is emitted in registration form: the buffer V# words, image modes, swizzles and mip ranges, depth-compare state, export mappings and vertex component selectors it used to specialize on per draw are read at run time (image `ResourceMetadata` and a few state dwords in the shader data), descriptor heaps keep every element in place (an unused one gets a null or placeholder descriptor), and the pixel stage's bindings live in descriptor set 1 so each stage's library depends only on its own set layout (`INDEPENDENT_SETS`). An artifact above `registrationWordLimit` SPIR-V words is re-emitted specialized, and `CompiledShaderArtifact::registrationModule` records which form it has. For artifacts in registration form, `RegistrationResult` materializes the module once, and `PrebuildStageLibrary` builds its pre-rasterization or fragment-shader library on a separate one-thread pool (`APS5_PREBUILD_THREADS`) with a fixed default state, pairing it with a stub stage. A later draw with that state finds both libraries in the store and only links. Ahead-of-time dispatch resolution (`resolveGroupAhead` in [`DispatchAhead.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp)) scans the upcoming dispatch run up to the next wait or synchronization packet, moves still-pending registered compute shaders to the front of the registration preparation queue (`PrioritizeRegistrationPreparation`) and queues preparation (`SubmitRegistrationPreparation`) for registered compute shaders that have no prepared entries yet (such as under `APS5_LAZY_SHADER_PREPARE=1`) with the previewed register state and entry offset; when a dispatch resolved ahead misses the dispatch cache in [`Dispatch.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp), `PrebuildComputePipeline` is submitted to the same `APS5_PREBUILD_THREADS` pool so the compute `VkPipeline` is built before the worker reaches the packet. - A draw or dispatch whose state matches no artifact prepared at registration (a title that builds its static ABI state itself, without `sceAgcDriverResolve*Abi`) gets one prepared at use by `SourceHandleFor` or `InvocationFor`, logged once per artifact and kept in the snapshot for the next use. - A stage that `sceAgcDriverResolve*Abi` cannot prepare for the state the title creates its pipeline with (a compute shader that calls user-data pointers, a descriptor the resource plan rejects) is left to its first use like a stage deferred at registration: `ResolveShaderAbi` and `PrepareGraphicsStages` catch the failure inside their own preparation transaction, so the `ShaderPreparationScope` the title's call runs in still commits, log it once per shader, mark the shader deferred and record the ABI key, so a repeated call prepares nothing; `ResolvePreparedGraphics` skips a deferred stage's rectangles until a draw has prepared it. A draw or dispatch that cannot prepare the stage either fails as before. Driver failures, `ProcessShutdown` and a failure stored by the shader's registration preparation still propagate from the ABI call. - A compute shader whose `s_swappc_b64` calls a user-data pointer cannot be linked at registration, because the callee's address is only known at dispatch. `InvocationFor` in [`ShaderRegistry.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) reads each callee from guest memory, appends one copy per call after the caller (`LinkUserDataCalls`) and prepares that linked code, so a different callee gets its own prepared entry. @@ -130,9 +143,9 @@ flowchart LR - A draw whose `PA_SC_AA_CONFIG` selects 2, 4 or 8 samples renders into host-only multisampled images. [`MultisampleTarget.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) keeps one image per device, surface address, format, extent and sample count (`MultisampleTargetView`); the pipeline, render pass and library keys carry `State::samples`, and a multisampled depth target is a multisampled depth surface. The guest sample planes and FMASK are never read or written, so the registry also guards the target's guest range: a CPU read (`GuestMemory::SetReadGuard`), a DMA copy, and a sampled or storage image read of it throw. A `CB_COLOR_CONTROL` resolve mode over a multisampled CB0 runs as a render pass whose resolve attachment is CB1's resident image (`ResolveMultisampleTarget`). A zero fill of the target's CMASK (`NoteColorMetadataFill`, from fill kernels and CP DMA fills) clears the image at its next use. - A texture over a depth surface samples the depth image itself when it views one 2D slice of the plane's own format (`DepthSurfaceTexture` in [`DepthSurface.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp)). A view of several slices (a 2D array or a cube) or an integer view of a D16 plane is served by a `DepthPlaneCopy`, an image the slices are copied into at every use: from the depth surface bound at each slice's address, else from guest memory. Such a texture is never kept in the resource templates (`RefreshedPerUse`). - Guest memory reaches the GPU through host imports (`VK_EXT_external_memory_host`) where the driver accepts them, and through copies otherwise ([`GuestBufferMemory.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp)). A registered range larger than the import window (64 MiB) is imported per window for buffer and fill accesses and whole for an address-based build. Live imports stay under a budget, with the least recently used import retired first (see TechnicalDebt, Host import windows and budget). -- A guest protect or unmap over part of a plain host-pointer import (one registered range, no alias, dma-buf, chunked or joined import, no pending settle) trims the import instead of retiring it (`retireOrTrim` in [`GuestBufferMemory.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp)): its buffer is destroyed and it waits in `HostImports::trimmed` with the side of its range the change left, the larger side for a change strictly inside the range, while its `VkDeviceMemory` keeps the whole range it was made for pinned (`HostImport::memoryBase` and `pinnedBytes`, which `liveBytes` and the eviction count), also after the guest protects, unmaps or maps again the pages that were cut off. The next `refreshImports` binds a new `VkBuffer` to the same memory at the offset of the largest registered range inside the kept side (`reattachTrimmed`) when that range still has the import's backing serial, overlaps no other import, is what a new import would cover and the buffer accepts the offset; otherwise, or when an import of a range overlapping the kept side is made first, the trimmed import is released and held through the recorder (`KeepThroughHold`) as a retired import is. `GuestAllocations::Range::backingSerial` tells whether a range's pages are still the ones that were imported: a new range gets a fresh serial, the untouched pieces of a split keep theirs, and every span a mutation passes to `RequireUnpinned` (a protect or unmap, a fixed mapping over registered memory, the other mappings of remapped direct memory, and for an unmap or remap the whole 2 MiB resident runs it touches, which it may demote to shared pages) gets a fresh one when the mutation ends. +- A guest protect or unmap that overlaps a plain host-pointer import retires the whole import before changing the guest range (`WaitForLeasesAndImports` in [`GuestBufferMemory.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp)). Later accesses import surviving registered ranges again; imports never keep the pages across a cut pinned in a trimmed state. `GuestAllocations::Range::backingSerial` distinguishes the original pages from replacements: untouched pieces of a split keep their serial, newly mapped or protected pieces get a fresh one, and every span passed to `RequireUnpinned` (including the other mappings of remapped direct memory and the resident runs touched by a 2 MiB unmap or remap) gets a fresh serial when the mutation ends. - A draw binds its vertex and index ranges in place from a host import, except with NVIDIA's Windows driver, which reads a host import of guest memory far slower than a buffer it allocated (Astro Bot at Crash Site on an RTX 5070 Ti: 164 against 45 ms of draws per frame), so they are copied there (`DrawInputsInPlaceUsable`, `Context::drawInputsInPlace`). `APS5_DRAW_INPUTS_IN_PLACE=1` binds them in place and `APS5_NO_DRAW_INPUTS_IN_PLACE=1` copies them on any device; the second wins. -- On Windows, a host import of private `MEM_WRITE_WATCH` memory (singly mapped direct memory) stays on `GetWriteWatch` when `ProbeImportWriteProtection` shows the driver reports nothing written after the second GPU read and a GPU write, and a CPU store is collected (`decideImportWatch`). The driver marks every page of such an import written, asynchronously, until a GPU use of it completes (a read on the settle queue ends it even while main-queue work that used the import first is still running, and the first use of an earlier import of the same pages after that read marks nothing), so `settleImport` in [`GuestBufferMemory.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) copies 4 bytes out of the new import on a second queue of the graphics family, or on a queue of another copy-capable family when the graphics family has only one (`Context::settleQueue`, own pool and mutex) inside `ImportWatched`'s window and waits for it, for imports of 1 MiB or more; the main queue can't be used there because `importAllocation` holds the imports lock and `GpuMutex` comes before it in the lock order. A smaller import is compared when it is made, so a title that maps, imports and unmaps a small buffer around every submission does not block its mapping thread. Its 4-byte read is queued instead: the next import lookup that holds `GpuMutex` while the imports match the registry (`settleUnwatchedImports`) records the queued reads into one batch on the settle queue, and once the batch's fence has signalled a later lookup returns the import's whole 64 KiB blocks to the write watch (`GuestMemory::Rewatch` drops their marks and stamps every block as a CPU store), unless a live storage image overlaps them. Such an import stays compared: an image that took its generation before the stamp would take the stamp for a CPU store over the results it produces after it, and drop them. A settled import whose range had already left the watch returns the same way, and an import retired while its read is in flight is kept with that batch and freed once the batch has completed, without waiting for it; a guest unmap of its memory waits for the batch instead. `APS5_NO_DEFERRED_SETTLES` (any value) keeps every import under 1 MiB compared. Imports through a section alias keep tracking on the guest views, and every other Windows host import is compared. A device without such a queue keeps the comparisons. On Linux the settle queue is made when the device imports dma-buf memory, and every dma-buf import of 1 MiB or more is settled right after it is made, for another reason: the kernel maps an import's pages for the GPU at the first submission that uses it, which took about 2 s for a 3 GiB import on RADV and held up the title's frame. The import's buffer is shared with the settle queue's family instead of aliased, since a sparse import is bound to many memory objects. +- On Windows, a host import of private `MEM_WRITE_WATCH` memory (singly mapped direct memory) stays on `GetWriteWatch` when `ProbeImportWriteProtection` shows the driver reports nothing written after the second GPU read and a GPU write, and a CPU store is collected (`decideImportWatch`). The driver marks every page of such an import written, asynchronously, until a GPU use of it completes (a read on the settle queue ends it even while main-queue work that used the import first is still running, and the first use of an earlier import of the same pages after that read marks nothing), so `settleImport` in [`GuestBufferMemory.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) copies 4 bytes out of the new import on a second queue of the graphics family, or on a queue of another copy-capable family when the graphics family has only one (`Context::settleQueue`, own pool and mutex) inside `ImportWatched`'s window and waits for it, for imports of 1 MiB or more; the main queue can't be used there because `importAllocation` holds the imports lock and `GpuMutex` comes before it in the lock order. A smaller import is compared when it is made, so a title that maps, imports and unmaps a small buffer around every submission does not block its mapping thread. Its 4-byte read is queued instead: the next import lookup that holds `GpuMutex` while the imports match the registry (`settleUnwatchedImports`) records the queued reads into one batch on the main queue, with barriers ordering earlier and later writes around the reads. Submission does not wait on the CPU, and once the batch's fence has signalled a later lookup returns the import's pages to the write watch (`GuestMemory::Rewatch` drops the marks of every 64 KiB block they touch and stamps those blocks as a CPU store; the rest of a block the import only partly covers stays unwatched), unless a live storage image overlaps those blocks. Such an import stays compared: an image that took its generation before the stamp would take the stamp for a CPU store over the results it produces after it, and drop them. A settled import whose range had already left the watch returns the same way, and an import retired while its read is in flight is kept with that batch and freed once the batch has completed, without waiting for it; a guest unmap of its memory waits for the batch instead. `APS5_NO_DEFERRED_SETTLES` (any value) keeps every import under 1 MiB compared. Imports through a section alias keep tracking on the guest views, and every other Windows host import is compared. A device without such a queue keeps the comparisons. On Linux the settle queue is made when the device imports dma-buf memory, and every dma-buf import of 1 MiB or more is settled right after it is made, for another reason: the kernel maps an import's pages for the GPU at the first submission that uses it, which took about 2 s for a 3 GiB import on RADV and held up the title's frame. The import's buffer is shared with the settle queue's family instead of aliased, since a sparse import is bound to many memory objects. ### Log output diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index 4cebf79a20..377205720b 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -12,7 +12,7 @@ ctest --test-dir build-relinker --output-on-failure Python must be found during CMake configuration for Python tests to be registered. List registered tests with `ctest --test-dir build-relinker -N` and look for `input_magic`, a Python-backed relinker test. If automatic discovery fails, add `-DPython3_EXECUTABLE="C:/path/to/python.exe"` to the configure command, using the path to your Python interpreter, then list the tests again. -This mode builds the conversion tool and its tests on Linux, Windows and macOS, including Apple Silicon. macOS uses AppleClang from the Xcode command-line tools. Windows uses MinGW-w64 GCC with C++20 support and accepts distributions with static or shared runtimes; the WinLibs requirement below applies to the full build. The executable is `build-relinker/core/relinker/relinker` (`relinker.exe` on Windows with Ninja). +This mode builds the conversion tool and its tests on Linux, Windows and macOS, including Apple Silicon. macOS uses AppleClang from the Xcode command-line tools (`xcode-select --install`); CMake and Ninja come from Homebrew (`brew install cmake ninja`). Windows uses MinGW-w64 GCC with C++20 support and accepts distributions with static or shared runtimes; the full build also requires the exception support described below. The executable is `build-relinker/core/relinker/relinker` (`relinker.exe` on Windows with Ninja). The output is x86-64: Linux ELF by default, Windows PE with `--windows` or Mach-O with `--macos` ([usage](../user/USAGE.md#input-and-conversion)). Converted games need system libraries built for the target OS and a compatible x86-64 host. This mode does not build those libraries, and releases ship them for Linux and Windows only. Tests inspect all three output formats; execution checks run only on their compatible hosts. @@ -30,10 +30,15 @@ git submodule update --init --recursive - Linux display refresh queries require SDL's XRandR support (`libxrandr-dev` on Debian/Ubuntu; `libXrandr-devel` on Fedora). Bundled SDL configuration fails without it. Keep `SDL_X11_XRANDR` enabled. - Linux: GCC, G++, binutils. SDL's X11 backend requires X11 and Xext development headers (`libx11-dev` and `libxext-dev` on Debian/Ubuntu). SDL's Wayland backend requires the Wayland, xkbcommon and EGL development files (`libwayland-dev`, `libxkbcommon-dev` and `libegl-dev` on Debian/Ubuntu; `wayland-devel`, `libxkbcommon-devel` and `mesa-libEGL-devel` on Fedora), and `libdecor-0-dev` (`libdecor-devel`) for window decorations on GNOME. SDL loads both backends at run time and prefers Wayland when `WAYLAND_DISPLAY` is set; `SDL_VIDEODRIVER=x11` selects X11 (XWayland). Titles that bundle their own SDL read `SDL_VIDEODRIVER` too, and some of them fail to start with `x11`. Without the Wayland files configuration still succeeds, and the build uses X11 only. Audio output needs SDL's PipeWire, PulseAudio or ALSA backend, built from their development files (`libpipewire-0.3-dev`, `libpulse-dev` and `libasound2-dev` on Debian/Ubuntu; `pipewire-devel`, `pulseaudio-libs-devel` and `alsa-lib-devel` on Fedora). SDL loads them at run time. Without any of them SDL keeps only its silent dummy and disk drivers, so configuration fails. - Fedora: if CMake reports `Could NOT find Threads`, install `glibc-static` and `libstdc++-static` with `sudo dnf install glibc-static libstdc++-static`, then rerun configuration. The project enables static executable linking, and FFmpeg's thread check needs these libraries. -- Windows: only MinGW-w64 GCC 15.2.0 (WinLibs `x86_64-ucrt-posix-seh`, release [`15.2.0posix-14.0.0-ucrt-r7`](https://github.com/brechtsanders/winlibs_mingw/releases/tag/15.2.0posix-14.0.0-ucrt-r7), archive `winlibs-x86_64-posix-seh-gcc-15.2.0-mingw-w64ucrt-14.0.0-r7.zip`) is currently supported. Add its `mingw64/bin` directory to `PATH` before configuring. -- Windows antivirus software can quarantine the executables that the relinker tests create in the temp directory, which fails `optional_plt`, `empty_tls`, `windows_address_space` and `windows_icon`. Set `TEMP` and `TMP` to a directory the scanner excludes before running `ctest`. +- Windows: x86-64 MinGW-w64 GCC with POSIX threads. The compiler must provide DWARF exceptions with `-fno-asynchronous-unwind-tables`, or SEH exceptions for System V ABI functions; CMake checks these capabilities. WinLibs GCC 15.2.0 (`x86_64-ucrt-posix-seh`, release [`15.2.0posix-14.0.0-ucrt-r7`](https://github.com/brechtsanders/winlibs_mingw/releases/tag/15.2.0posix-14.0.0-ucrt-r7)) uses DWARF. [MSYS2 UCRT64 GCC](https://packages.msys2.org/packages/mingw-w64-ucrt-x86_64-gcc) 16.2.0-6 uses SEH. Add the selected toolchain's `bin` directory to `PATH` before configuring. +- Windows antivirus software can block or quarantine the executables that the relinker tests create and run, which fails `optional_plt`, `empty_tls`, `windows_address_space`, `windows_bss`, `windows_fixed_base` and `windows_icon`. CTest sets `TMPDIR`, `TEMP` and `TMP` of the relinker tests to `core/relinker/test-temp` in the build directory, so setting them before running `ctest` does not move these files; add that directory to the scanner's exclusions instead. - Bitdefender Advanced Threat Defense hooks Windows calls that `sceKernelRaiseException` makes while its target thread is suspended. When the target is suspended inside ntdll's exception dispatch while it holds an ntdll lock, the hook waits for that lock and `guest_raise_exception` hangs until its timeout. A scanning exception for the build directory does not prevent it; add `guest_raise_exception_tests.exe` to the Advanced Threat Defense exceptions. - FFmpeg binaries are downloaded during configuration unless `FFMPEG_PREBUILT_DIR` is set. With the WinLibs CMake, the download fails with status 60 (`SSL peer certificate or SSH remote key was not OK`) unless `SSL_CERT_FILE` names a CA bundle, for example `C:\Program Files\Git\ucrt64\etc\ssl\certs\ca-bundle.crt` from Git for Windows 2.56 and later (`mingw64` instead of `ucrt64` before 2.56), as in CI. +- Windows: libusb's `version_describe.h` is generated by `build-aux/gen-describe.ps1`, which CMake runs with `-ExecutionPolicy Bypass`. An execution policy set by Group Policy (`MachinePolicy` or `UserPolicy` in `Get-ExecutionPolicy -List`) takes precedence over that switch ([about_Execution_Policies](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-5.1)); with `Restricted` or `AllSigned` the build stops at `version_describe.h` with `running scripts is disabled on this system`. The script only starts `build-aux/gen-describe.sh` with the `bash.exe` of Git for Windows, so a `.cmd` file that runs the shell script directly can replace PowerShell for this step without changing the policy. CMake passes it the `.ps1` path as the fifth argument and the source and output paths as the sixth and seventh; configure with `-DLIBUSB_POWERSHELL_EXECUTABLE=`: + + ```bat + @"C:\Program Files\Git\bin\bash.exe" "%~dpn5.sh" %6 %7 + ``` ## Web converter releases @@ -64,6 +69,12 @@ therefore tests the previous binaries and can show no effect at all. The full build also requires Python 3 for glslang and Git for Windows with Bash for libusb's version-header generation. A minimal Git installation without Bash is insufficient for the full build. +For MSYS2, install `mingw-w64-ucrt-x86_64-gcc` with `pacman -S mingw-w64-ucrt-x86_64-gcc` and use `C:\msys64\ucrt64\bin` in PowerShell. Adjust the prefix for a Scoop installation of MSYS2. Use a separate build directory for each toolchain, and select both `gcc` and `g++` from the same installation. + +The [Windows MSYS2 workflow](../../.github/workflows/windows-msys2.yml) builds and tests the full runtime nightly at 08:23 UTC and can be started manually with `workflow_dispatch`. The regular Windows build uses WinLibs. + +Scoop's standalone GCC 15.2.0 package cannot build the runtime: disabling SEH selects SjLj exceptions, and its SEH backend cannot compile System V ABI catch handlers. Use MSYS2 UCRT64 or a DWARF-capable WinLibs distribution. This restriction does not apply to relinker-only builds. + In PowerShell, prepend the supported WinLibs toolchain and Git for Windows to the current session's `PATH`. Adjust the paths for the installed locations; a Portable Git installation can be used in place of `C:\Program Files\Git`. ```powershell @@ -76,6 +87,8 @@ If configuration reports `Could NOT find Python3` or selects an unusable interpr cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DPython3_EXECUTABLE="C:/path/to/python.exe" ``` +The documentation runs the Python tools as `python3`. With the python.org installer for Windows, which provides `python.exe` and the `py` launcher but no `python3.exe`, `python3` resolves to the Microsoft Store alias in `WindowsApps`, which prints `Python was not found` and exits with code 9009 (49 in Git Bash). Installs that provide `python3.exe`, such as the Python Install Manager, are not affected. In that case run these commands with `python` or `py -3`, for example `py -3 tools/check_conventions.py --base origin/main`. + If `gen-describe.ps1` fails while generating libusb's `version_describe.h`, check the Bash path printed in the build output. A WindowsApps or System32 `bash.exe` can launch WSL and fail with `WSL_E_DEFAULT_DISTRO_NOT_FOUND` when no distribution is installed. Install Git for Windows with Bash, or use Portable Git, and put its `cmd` and `usr\bin` directories on `PATH` before retrying the build. Use Bash and `cygpath.exe` from the same Git installation. After `cmake --build build --target libs --parallel` succeeds, copy the toolchain's runtime DLLs beside the patched libraries: @@ -119,7 +132,7 @@ Build configuration parameters: SDL and FreeType settings forced by the root `CMakeLists.txt` cannot be overridden with `-D`. -With `ANYPS5_USE_SYSTEM_DEPS=ON` those libraries come from the system (on Debian and Ubuntu: `libsdl2-dev`, `libfreetype-dev`, `libvulkan-dev`, `spirv-headers`, `glslang-dev`, `glslang-tools` and `spirv-tools`). A missing package, Vulkan-Headers older than 1.4.310 (the agc driver uses `VK_KHR_maintenance8`), or an SDL2 without the HIDAPI joystick driver (sdl2-compat over SDL3, or a build with HIDAPI disabled, which breaks the pad and disk-audio tests), fails the configure with an error and never falls back to the submodule. The forced SDL and FreeType settings from the root `CMakeLists.txt` do not apply with the flag ON: the system SDL2 comes with whatever its distribution enabled. The prx libraries are shared objects, so SDL2 is linked from the system as a shared library; the default build keeps its own static SDL2. `ffmpeg-core`, `libjpeg-turbo`, `stb`, `VulkanMemoryAllocator` and `LibAtrac9` always stay bundled and still need `git submodule update --init`. +With `ANYPS5_USE_SYSTEM_DEPS=ON` those libraries come from the system (on Debian and Ubuntu: `libsdl2-dev`, `libfreetype-dev`, `libvulkan-dev`, `spirv-headers`, `glslang-dev`, `glslang-tools` and `spirv-tools`). A missing package, Vulkan-Headers older than 1.4.310 (the agc driver uses `VK_KHR_maintenance8`), or an SDL2 without the HIDAPI joystick driver (sdl2-compat over SDL3, or a build with HIDAPI disabled, which breaks the pad and disk-audio tests), fails the configure with an error and never falls back to the submodule. The forced SDL and FreeType settings from the root `CMakeLists.txt` do not apply with the flag ON: the system SDL2 comes with whatever its distribution enabled. The prx libraries are shared objects, so SDL2 is linked from the system as a shared library; the default build keeps its own static SDL2. `ffmpeg-core`, `libjpeg-turbo`, `stb`, `VulkanMemoryAllocator`, `LibAtrac9`, `ogg` and `vorbis` always stay bundled and still need `git submodule update --init`. ## Quick syntax checks @@ -151,7 +164,7 @@ On Windows MinGW frees `thread_local` objects before their destructors run, so t ## Compute pipeline replay -Build `agc_compute_pipeline_replay` in the full build with `-DANYPS5_ENABLE_SPIRV_TOOLS=ON`; replay validates the captured module before calling Vulkan. Set `APS5_DUMP_COMPUTE_PIPELINES` to a dedicated empty directory before starting a title to save each uncached compute pipeline's final SPIR-V, descriptor layouts, push constant ranges, specialization values, subgroup size and creation flags before compilation. Files are named `compute-/capture.aps5cp`; identical records are deduplicated. Capture stops with an error at 128 records, 256 MiB total or 36 MiB per record (at most 8,388,608 SPIR-V words, or 32 MiB of shader code). Capture files contain shader code and device metadata, but no guest buffers or textures; review them before sharing. +Build `agc_compute_pipeline_replay` in the full build with `-DANYPS5_ENABLE_SPIRV_TOOLS=ON`; replay validates the captured module before calling Vulkan. Set `APS5_DUMP_COMPUTE_PIPELINES` to a dedicated empty directory before starting a title to save each uncached compute pipeline's final SPIR-V, descriptor layouts, push constant ranges, specialization values, subgroup size and creation flags before compilation. Files are named `compute-/capture.aps5cp`; identical records are deduplicated. The pipelines of the start-up f32 denormal flush probe are not captured. Capture stops with an error at 128 records, 256 MiB total or 36 MiB per record (at most 8,388,608 SPIR-V words, or 32 MiB of shader code). Capture files contain shader code and device metadata, but no guest buffers or textures; review them before sharing. Set `APS5_DUMP_COMPUTE_MIN_WORDS` to capture only modules at or above that SPIR-V word count, for example `100000` when investigating large NVIDIA compilations. The default is `0` (all modules); values outside `0` through `8388608` fail. The filter applies only when capture is enabled. @@ -180,21 +193,34 @@ build/core/libs/prx/libSceAgcDriver/agc_pm4_replay submits On Windows use `agc_pm4_replay.exe` and prepend `build/core/libs/libs/unpatched` to the process `PATH`. With `BUILD_TESTING=ON`, `agc_driver_pm4_replay` captures submissions made with the libSceAgc command builders through the driver and checks the index, the held waits, the conditional ranges, the dwords, the decode and the outcome of each wait, then checks hand-made captures: each release route; a store of another queue to the awaited bytes that entered before or after the store of the waiting queue (unknown); the label history limit, with the stores after the label on the waiting queue or on another queue; a fill the history does not keep; a store in an earlier submission overwritten later; later stores, rejected values and a missing rewind tail (unreleased); the six captures of the reviews (a two-queue deadlock, a store of 0 behind a released wait on another queue, with and without that wait held at submit, and labels pushed out of the history by stores of another queue), none of whose waits may be released except the one held at submit; five groups of waits in eight submission orders with identical outcomes; an index without the held and conditional columns; corrupt captures; then a failed write. `agc_driver_pm4_replay_tool` runs the tool on the driver capture and checks its exit codes and report. +## Frame capture and replay + +Set `APS5_FRAME_CAPTURE` to a directory to record a few frames of a title at the AGC driver boundary, then replay them with `agc_frame_replay` through any build of the driver with no title running. From the start of the process the driver keeps every shader registration and ABI resolution, video output registration and EOP flip reservation. At the frame boundary picked by `APS5_FRAME_CAPTURE_AT_FLIP` (default 1), or by the appearance of the file `APS5_FRAME_CAPTURE_TRIGGER` names (a number in it replaces the frame count), it waits for the driver to go idle and writes every committed, readable allocation of the guest allocation registry, the title's image included, as zstd chunks without their all-zero 64 KiB blocks. A frame ends at a CPU flip or at a submission that flips (a flip packet, or the EOP release of a reserved flip token); the reservation itself only records its token. For the next `APS5_FRAME_CAPTURE_FRAMES` (10) frames it records each submission (queue, command buffer address and size), flip and suspend point with the thread that made it and the accepted and completed serials, each preceded by the guest pages the write tracker saw the CPU write since the previous call that handed work to the GPU (`GuestMemory::CollectWrittenBlocks`, per 4 KiB where the tracker kept page stamps), read once the submissions already accepted have completed (a recorded submission, flip or suspend point reaches the driver before the recorder takes its next call, so that wait covers every call recorded before the delta); an allocation the registry gained after the snapshot is recorded there as a new range with its nonzero 64 KiB blocks. A submission ends a frame only when it flips itself, not when another thread's submission flipped while it was being submitted. Shader registrations and ABI resolutions only collect the blocks of the shader header and the arrays it points to, and the pages of the title's image only when their content changed. At the end it stores each display buffer the title presented, decoded, as a reference image. A capture is `calls.bin`, `memory.bin` and `capture.json` (commit, switches, sizes, warnings), and the recorder stops with a warning when its volume has less than 4 GiB free. A 20-frame capture in the Demon's Souls Nexus is 5.3 GiB of snapshot and 11.5 GiB of deltas before compression, 3.9 GiB on disk. + +`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. + +```sh +cmake --build build --target agc_frame_replay +build/core/libs/prx/libSceAgcDriver/agc_frame_replay capture +``` + +On Windows use `agc_frame_replay.exe` and prepend `build/core/libs/libs/unpatched` to the process `PATH`. With `BUILD_TESTING=ON`, `agc_frame_capture` checks the file format (round trip, zero-block elision, truncated and corrupt files), and `agc_driver_frame_capture_eop` reserves and submits six EOP flips with `APS5_FRAME_CAPTURE_FRAMES=3` and checks that the capture starts after the first flipping submission and holds three frames of one reservation and one submission each, then that a flipping submission on another thread leaves this thread's flip count unchanged, and `agc_driver_frame_replay` captures two frames of CPU writes followed by DMA copies, the second from a block committed during the capture, replays them in `agc_frame_replay` and compares the copies. + ## Pipeline statistics Set `APS5_PIPELINE_STATS=1` to capture and print driver statistics for each newly created graphics or compute pipeline. This requires `VK_KHR_pipeline_executable_properties` and `pipelineExecutableInfo`; an unsupported device fails with an error. Statistic names and units are driver-specific. Capturing statistics can increase pipeline compilation cost. The setting is disabled by default. ## Device loss reports -On a device that supports `VK_EXT_device_fault`, the first `VK_ERROR_DEVICE_LOST` prints the driver's fault description, the faulting addresses and the vendor fault codes as `[device-fault]` lines. Set `APS5_GPU_CHECKPOINTS=1` to also record a `VK_NV_device_diagnostic_checkpoints` marker with the program address before each compute dispatch; the report then lists the last markers the GPU reached. Checkpoints are disabled by default and ignored on devices without the extension. +On a device that supports `VK_EXT_device_fault`, the first `VK_ERROR_DEVICE_LOST` prints the driver's fault description, the faulting addresses and the vendor fault codes as `[device-fault]` lines. Set `APS5_GPU_CHECKPOINTS=1` to also record a `VK_NV_device_diagnostic_checkpoints` marker with the program address before each compute dispatch; the report then lists the last markers the GPU reached. On a device without that extension but with `VK_AMD_buffer_marker` (RADV and AMD's own drivers), each compute dispatch instead writes a numbered marker into a host-visible buffer when the GPU reaches it and another when it finishes; the report then names the programs of the last dispatch reached, of the last one finished and of up to eight dispatches between them. Checkpoints are disabled by default and ignored on devices without the extension. ## Worker sampler -Set `APS5_SAMPLE_WORKER=` to sample queue worker 0 every millisecond (in practice every timer tick). Every 20 s and at exit it writes ``, one `module offset self inclusive` line per sampled address, and `.collapsed.txt`, the worker's whole stacks in the collapsed format that speedscope.app and flamegraph.pl read: one `frame;...;frame count` line per distinct stack, root first, each frame as `module+0xRVA`. A walk that ends before the thread's root starts with `[truncated]`. The `#` lines give the samples, the walk depth (64 frames), the truncated walks, the mean and longest time the worker was suspended, and the size and PE timestamp of each module, to check the binary a file is symbolized against. A frame with `.pdata` is stepped by `RtlVirtualUnwind`; any other frame by its DWARF record in `.ehfram`, the unwind data the libraries are built with. Those steps read only inside the worker's stack, and a module is indexed after the worker resumes, the first time a walk meets it. +Set `APS5_SAMPLE_WORKER=` to sample queue worker 0 every millisecond (in practice every timer tick). Every 20 s and at exit it writes ``, one `module offset self inclusive` line per sampled address, and `.collapsed.txt`, the worker's whole stacks in the collapsed format that speedscope.app and flamegraph.pl read: one `frame;...;frame count` line per distinct stack, root first, each frame as `module+0xRVA`. A walk that ends before the thread's root starts with `[truncated]`. The `#` lines give the samples, the walk depth (64 frames), the truncated walks, the mean and longest time the worker was suspended, and the size and PE timestamp of each module, to check the binary a file is symbolized against. A frame with `.pdata` is stepped by `RtlVirtualUnwind`; any other frame by its DWARF record in `.ehfram`, the unwind data the libraries are built with. Those steps read only inside the worker's stack, and a module is indexed after the worker resumes, the first time a walk meets it. `python tools/symbolize_samples.py .collapsed.txt --libs ` names each frame from the COFF symbols of the module that ran, or from its export table when it has none, and leaves a module unsymbolized when its size or timestamp differs from its `# module` line or when it has none (`--unverified` symbolizes it anyway); `--since ` keeps only the samples taken after that copy, `--out` writes the named stacks and `--demangle` uses `c++filt`. Given ``, it lists functions by self and by inclusive samples, where a function's inclusive count is the sum of its addresses' and overcounts a function that is on one stack more than once. `APS5_SAMPLE_COMMITTER=` samples queue 0's draw committer the same way and writes the same two files. The committer records the draws the worker hands over (`APS5_PIPELINED_DRAWS`), so when the worker's stacks wait in `DrawPipeline::Enqueue`, its stacks show what bounds the frame. It starts with the first pipelined draw; with `APS5_PIPELINED_DRAWS=0` nothing is sampled. -`APS5_SAMPLE_THREADS=` samples every thread of the process every 2 ms and writes the 40 hottest addresses of each busy thread to ``, 12 frames deep and through `.pdata` only. The samplers exist only on Windows; the addresses are symbolized offline, for example with `nm` on the `.prx`. +`APS5_SAMPLE_THREADS=` samples every thread of the process every 2 ms and writes the 40 hottest addresses of each busy thread to ``, 12 frames deep and through `.pdata` only, under a `thread samples ` line per thread and with the same `# module` lines. The samplers exist only on Windows; the addresses are symbolized offline with `tools/symbolize_samples.py`, which adds up all threads or, with `--thread `, keeps one. ## Performance trace @@ -222,7 +248,7 @@ The switch combines with `APS5_TRACE_FRAMES` or `APS5_TRACE_SECONDS`. The interv Set `APS5_TRACE_BENCH=1` together with `APS5_TRACE_SUMMARY_EVERY` to also keep every interval's frame times for a benchmark report. At each interval the presenter hands the kept intervals to a writer thread, which formats and compresses the file and rewrites `aps5-bench--.json.gz` in `APS5_TRACE_DIR` or the working directory: one JSON object with `format` (`aps5-bench/1`), the identity fields of the summary line (`commit`, `dirty`, `env`, `gpu`, `driver`, `os`, `cpu`, `cpu_threads`, `memory_mib` and, with `param.json`, `title_id`, `title` and `content_version`), `intervals_dropped` and `intervals`. Each interval has `start_s`, `end_s`, `first_present`, `resolution`, `gpu_busy_ms` (the interval's total) and `frame_us`, the whole microseconds between consecutive presents. The file keeps the last 240 intervals; `intervals_dropped` counts older ones it no longer holds. Any other value, or the switch without `APS5_TRACE_SUMMARY_EVERY`, fails the first present. The file is written to `.tmp` and then moved over the previous one, so a crash or kill during a rewrite leaves the last complete report. Only the newest report waits for the writer: one handed over while an older one is still pending replaces it. The frame times cost 4 bytes per present in memory. -`python tools/trace_digest.py aps5-summary.jsonl` prints the interval lines of such a file as one row per interval (frame p50, p90, p99, max, GPU busy per present, the busiest queue worker and the largest phase) and a text sparkline of the frame p99 and of the GPU busy time; lines of window captures in the file are skipped, and a change of commit, environment, GPU, driver or OS between intervals is reported. `--json` prints the rows as JSON. +`python tools/trace_digest.py aps5-summary.jsonl` prints the interval lines of such a file as one row per interval (frame p50, p90, p99, max, GPU busy per present, the busiest queue worker and the largest phase) and a text sparkline of the frame p99 and of the GPU busy time; lines of window captures in the file are skipped, and changes to build, environment, host, resolution or title metadata between intervals are reported. `--json` prints the rows, the first interval's identity and the indices of identity changes as JSON. With `APS5_TRACE_SUMMARY_EVERY` set, the recorder writes a timestamp pair per batch for the whole run and every `PerformanceTimer` scope adds its time to the thread's totals, two clock reads per scope. A thread's totals are read while it runs, so a scope that straddles an interval boundary is counted in one interval or the other. diff --git a/docs/dev/CONVENTIONS.md b/docs/dev/CONVENTIONS.md index 87c674dac4..fdf0b83768 100644 --- a/docs/dev/CONVENTIONS.md +++ b/docs/dev/CONVENTIONS.md @@ -20,7 +20,7 @@ There's no need to add image files to the repository. You can add images to the Host code that writes into guest memory through the kernel or a native library must hold a `GuestArena::HostWrite` ([GuestArena.hpp](../../core/libs/prx/libc/include/GuestArena.hpp)) over the destination range for the whole write. This covers file reads (`ReadFile`, `read`, `pread`), socket receives, `DeviceIoControl` and other calls that fill a caller's buffer. -The write watch can leave guest pages read-only between collections: every watched page on macOS, and stable resident runs of at least 256 KiB on Windows (see the write-watch entries in [debt/libc.md](debt/libc.md)). A kernel-mode write into such a page doesn't fault into the watch. It fails instead, with `EFAULT` on macOS and `ERROR_NOACCESS` on Windows. `HostWrite` opens the pages and makes sure the write is recorded. +The write watch can leave guest pages read-only between collections: every watched page on macOS, and on Windows the pages of shared views (shared memory, shared file mappings, and direct memory mapped at more than one guest address), which it tracks by write protection (see the write-watch and Windows direct-memory backing entries in [debt/libc.md](debt/libc.md)). A kernel-mode write into such a page doesn't fault into the watch. It fails instead, with `EFAULT` on macOS and `ERROR_NOACCESS` on Windows. `HostWrite` opens the pages and makes sure the write is recorded. - A write that completes later on another thread (overlapped I/O, completion ports, asynchronous reads) is bracketed where the bytes land, not where the request was issued. - Ordinary CPU stores from host C++ code need no bracket: the write watch records them (through a fault where the page is protected). diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 7d6f4d537b..d54b9c8fd1 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -1,13 +1,16 @@ # Environment variables -The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetting`, `MaskFromEnvironment`, `EnvInt`, `GetEnvironmentVariableA`, `GetEnvironmentVariableW` with a wide literal, `configured`, `PipelineSwitch`, `environmentCount` and `addWideString` (the wide names the Windows entry stub reads). This table lists every `APS5_*` and `ANYPS5_*` name it reads, its kind, the value used when it is unset and what it does, one table per library. Regenerate the names with `python3 tools/env_reference.py --table`; the Conventions check fails when a name the code reads has no row here, or a row's name is no longer read. +The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetting`, `MaskFromEnvironment`, `EnvInt`, `GetEnvironmentVariableA`, `GetEnvironmentVariableW` with a wide literal, `configured`, `PipelineSwitch`, `environmentCount`, `EnvironmentNumber` and `addWideString` (the wide names the Windows entry stub reads). This table lists every `APS5_*` and `ANYPS5_*` name it reads, its kind, the value used when it is unset and what it does, one table per library. Regenerate the names with `python3 tools/env_reference.py --table`; the Conventions check fails when a name the code reads has no row here, or a row's name is no longer read. ### core/libs | Name | Kind | Source | Default | Description | |------|------|--------|---------|-------------| +| `ANYPS5_JOB_MANAGER_CASE` | string | `core/libs/tests/JobManagerReference.cpp` | | selects the CPU fixture run by `tools/run_job_manager_reference.py` | +| `ANYPS5_JOB_MANAGER_HOST` | presence | `core/libs/tests/JobManagerReference.cpp` | | runs the reference adapter against the host fallback instead of the loaded guest module | | `ANYPS5_LOADEXEC_PARENT` | string | `core/libs/tests/GuestLoadExecHost.cpp` | | variable the Windows entry stub reads; its wide name is built in `core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp` | | `ANYPS5_LOADEXEC_TEST_ROOT` | string | `core/libs/tests/GuestLoadExecHost.cpp` | | directory the load-exec host test reads its guest executable from | +| `ANYPS5_POSIX_SPAWN_ROLE` | string | `core/libs/tests/GuestPosixSpawn.cpp` | | role of the test's own copy spawned as a `posix_spawn` child, which exits or sleeps on it and otherwise fails | | `ANYPS5_TEST_QUOTED_ARGV0` | presence | `core/libs/tests/GuestLoadExecHost.cpp` | | makes the load-exec test's first argument one with quotes, a backslash and non-ASCII text | | `APS5_EBOOT` | string | `core/libs/tests/GuestSystemExit.cpp` | | host path of the executable launched for /app0/eboot.bin, defaulting to the running executable | | `APS5_SKIP_TIMING_TESTS` | presence | `core/libs/tests/PreciseWait.cpp` | | treats the timing tests' runner as preempted, so they skip without probing the clock | @@ -28,6 +31,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_BDA_BYTE_READS` | presence | `core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp` | | forces one address lookup and byte load per byte for BDA reads instead of a wide dword load | | `APS5_BDA_DWORD_STORES` | presence | `core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp` | | reverts BDA writes to one address store per dword instead of a wide multi-dword store | | `APS5_DUMP_IR` | string | `core/shader/recompiler/Recompiler.cpp` | | hex code address (or all) of the programs whose IR is printed after each front-end pass | +| `APS5_EXACT_RCP` | presence | `core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterReciprocal.cpp` | | emits `v_rcp_f32` and `v_rcp_iflag_f32` as the RDNA hardware reciprocal (bit-exact for normal inputs and results) instead of the driver's division; adds about 13k SPIR-V words to each shader that uses them | | `APS5_GATHER_CONST` | integer | `core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp` | | integer every non-depth gather returns in all four texels instead of the sampled value | | `APS5_IMAGE_TABLE_KEY_SCAN` | integer | `core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp` | 256 | number of image-table entries the key scan examines | | `APS5_IMAGE_TABLE_STRICT` | presence | `core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp` | | sets image-table materialization to its strict form; 0 leaves it off | @@ -36,6 +40,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_CODE_HASH_KEY` | presence | `core/shader/recompiler/CacheKey.hpp` | | appends every code word to the cache key instead of hashing the code | | `APS5_NO_FAILURE_MEMO` | presence | `core/shader/recompiler/Recompiler.cpp` | | rebuilds a program whose compilation failed instead of reusing the memoized failure | | `APS5_NO_IMAGE_TABLES` | presence | `core/shader/recompiler/Optimization/src/ResourceTracker.cpp` | | disables image tables, leaving table-loaded descriptors unplanned | +| `APS5_NO_MASKED_RESULT_MEMO` | presence | `core/shader/recompiler/Recompiler.cpp` | | materializes every result memo miss instead of patching the buffer descriptors, user data and flattened SRT of a cached result whose snapshot differs only in those words | | `APS5_NO_PERF_DISK_PLANS` | presence | `core/shader/recompiler/Recompiler.cpp` | | disables the disk cache of planning results | | `APS5_NO_PERF_DOM_INTERSECTION` | presence | `core/shader/recompiler/ControlFlow/src/Structurizer.cpp` | | computes dominator intersections into a fresh vector instead of intersecting in place | | `APS5_NO_PERF_ITERATION_SELECTION` | presence | `core/shader/recompiler/ControlFlow/src/Structurizer.cpp` | | recomputes the post-dominator sets when finding a loop's iteration-selection merge instead of reusing them | @@ -52,6 +57,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_TRACE_BDA` | presence | `core/shader/recompiler/Optimization/src/ResourceTracker.cpp` | | prints the first few accesses that make a program take the address-based path | | `APS5_TRACE_BUFFER_WRITTEN` | presence | `core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp` | | prints how many guest buffer elements a program may store to and how many it only loads | | `APS5_TRACE_IMAGE_TABLE` | presence | `core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp` | | traces image-table resolution | +| `APS5_VERIFY_MASKED_RESULT_MEMO` | presence | `core/shader/recompiler/Recompiler.cpp` | | also materializes every patched result memo hit, prints the first twenty whose patched result differs and counts them in the result memo report of `APS5_PROFILE_DRAW` | | `APS5_VERIFY_SRT_COMPACT` | presence | `core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp` | | walks every SRT descriptor a second time through the IR evaluator and throws when the two walks differ | | `APS5_VERIFY_SRT_TAPE` | presence | `core/shader/recompiler/Optimization/src/SrtWalker/SrtTape.cpp` | | walks a plan's runtime sources a second time through the evaluator, adopts its answers where the two differ and prints the first twenty differences | | `APS5_WAVE_LDS_SUBGROUP` | presence | `core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp` | | restores subgroup-scope LDS barriers for single-wave workgroups instead of workgroup scope | @@ -60,7 +66,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | Name | Kind | Source | Default | Description | |------|------|--------|---------|-------------| -| `APS5_TRACE_LABEL` | integer | `core/libs/prx/libSceAgc/Cb/src/Sync.cpp` | 0 | hex guest address of a RELEASE_MEM label whose packet is logged when recorded | +| `APS5_TRACE_LABEL` | integer | `core/libs/prx/libSceAgc/Cb/src/Sync.cpp`, `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp` | 0 | hex guest address; logs a matching RELEASE_MEM when recorded, and nearby RELEASE_MEM, WRITE_DATA, COPY_DATA, DMA_DATA and WAIT_REG_MEM packets when executed | ### libSceAgcDriver @@ -81,6 +87,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_BARRIER_VALIDATE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | counts the barriers a hazard-tracking validator would emit and skip, by class and hazard kind | | `APS5_BATCH_CAP` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | 64 | recorded work since the last submit at which a label batch, flush or reap is forced (0 disables the cap) | | `APS5_BDA_FLUSH` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | also flushes pending storage results over address-based shader regions, which are otherwise exempt | +| `APS5_BDA_STAGING_MIN_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 512 | smallest dispatch, in threads, whose address-based build stages its written elements device-local; an indirect dispatch always does | | `APS5_BDA_TABLE_CACHE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp` | 8 | number of recently used BDA page tables kept in the cache (1 is the previous behaviour) | | `APS5_BLOCK_WRITEBACK_EACH` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | stores the touched units only on every write-back instead of widening repeated partial stores | | `APS5_BLOCK_WRITEBACK_FRAMES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | 8 | window of presents within which repeated partial stores trigger the whole-image widening | @@ -91,6 +98,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_CAPTURE_GPU_COPIES` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | records GPU buffer copies in the capture trace (requires APS5_CAPTURE_TRACE) | | `APS5_CAPTURE_INPUTS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | records each draw's inputs in the capture trace (requires APS5_CAPTURE_TRACE) | | `APS5_CAPTURE_TRACE` | presence | `core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp` | | writes capture events to capture-trace.log | +| `APS5_CAPTURE_TRACE_PATH` | string | `core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp` | capture-trace.log | file path for the capture events log | | `APS5_CHECK_INDIRECT_ARGS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | reads GPU-side indirect draw records back on the CPU and prints the first ones | | `APS5_COMPLETION_STORE_SYNC` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | makes a store made by a completion action wait for later batches over its range | | `APS5_COPY_DESTINATION_STRICT` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | refuses a CPU copy whose destination has any pending writer, not only an unsettled one | @@ -107,17 +115,20 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_CPU_STORES` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp` | | writes packet stores on the CPU instead of recording them on the GPU | | `APS5_DCC_KEYS_EACH` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records every DCC key store at once instead of batching them | | `APS5_DEPTH_COPY_MIN_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 1024 | minimum surface size in KiB for a depth target to take the depth-copy path | +| `APS5_DEPTH_PLANE_COPY_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp` | 256 | budget in MiB of the sampled depth-plane copies kept for reuse (least recently used copies leave first); `0` keeps every copy | +| `APS5_DEPTH_PLANE_WHOLE_STORE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp` | | stores a stale depth plane whole when memory over it is read, instead of only the 64 KiB units the read overlaps | | `APS5_DISPATCH_CACHE_BYTES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp` | 268435456 | size of the dispatch cache in bytes (0 keeps the default of 256 MiB) | | `APS5_DISPATCH_CACHE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp` | 8192 | number of dispatch cache entries; 0 picks a default from the variant count | | `APS5_DISPATCH_STAMP_VALIDATE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp` | | validates dispatch cache hits by write stamps, forcing one variant and a 4096-entry cache | | `APS5_DISPATCH_VARIANTS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp` | 4 | number of dispatch variants kept per cache entry, clamped to the maximum | | `APS5_DISPMOVE_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 1024 | upper bound in KiB on the read-only buffers a dispatch-cache hit moves into its template, above which the dispatch is prepared again | | `APS5_DRAIN_ALL` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp` | | drains every packet, disabling the batched recording path | -| `APS5_DRAIN_COMPLETION_LABELS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | records completion labels immediately instead of deferring them with the batch | +| `APS5_DRAIN_COMPLETION_LABELS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | records completion labels immediately instead of deferring them with the batch, and drains a label recorded while a copied write-back is pending instead of storing it behind the write-backs | | `APS5_DRAW_CACHE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp` | 4096 | number of entries in the draw cache; 0 keeps the default 4096 | | `APS5_DRAW_DRAIN` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp` | | treats draw packets as drains, waiting for them instead of recording ahead | | `APS5_DRAW_INPUTS_IN_PLACE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | binds draw vertex and index ranges in place from host imports also with NVIDIA's Windows driver, which copies them by default | | `APS5_DRAW_KEY_ALL_USER_WORDS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp` | | includes all 32 user words in a draw's register key instead of the used count | +| `APS5_DRAW_PIPELINE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | runs the second half of a pipelined draw on a draw finisher thread and keeps proofs for the collect epoch | | `APS5_DRAW_SNAPSHOT_MAX_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 64 | upper bound in MiB on the snapshot bytes a draw's bindings may take | | `APS5_DRAW_TRANSITIONS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | keeps the per-draw upload and download barriers and layout transitions instead of the lean recorded path | | `APS5_DRAW_UPLOAD_MAX` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 4096 | largest draw snapshot in bytes copied into the per-recorder upload chunk; 0 turns the chunk off | @@ -144,8 +155,12 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_FLIP_READ_CHECK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records the in-place reads of completed batches and checks them before the present blit's fence | | `APS5_FORCE_SDR` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/HostHdr.cpp` | unset | suppresses advertised HDR availability while retaining detected host capabilities; uses the existing SDR presentation path | | `APS5_FORCE_STRUCTURIZER_FALLBACK` | presence | `core/libs/prx/libSceAgcDriver/tests/ControlFlow.cpp` | | makes the control-flow test expect the reach-flag fallback instead of no cloning | +| `APS5_FRAME_CAPTURE` | string | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | | directory in which the driver records a few frames of the title's driver calls and guest memory for `agc_frame_replay` | +| `APS5_FRAME_CAPTURE_AT_FLIP` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | 1 | frame boundary (CPU flip or submission that flips) at which the frame capture starts when no trigger file is set | +| `APS5_FRAME_CAPTURE_FRAMES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | 10 | number of frame boundaries the frame capture records | +| `APS5_FRAME_CAPTURE_TRIGGER` | string | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | | file whose appearance starts the frame capture at the next frame boundary; a number in it replaces the frame count | | `APS5_FULL_BARRIERS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records every barrier instead of merging redundant ones | -| `APS5_GPU_CHECKPOINTS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | attaches device diagnostic checkpoints through VK_NV_device_diagnostic_checkpoints | +| `APS5_GPU_CHECKPOINTS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | attaches device diagnostic checkpoints through VK_NV_device_diagnostic_checkpoints, or buffer markers through VK_AMD_buffer_marker on a device without it | | `APS5_GPU_COPY_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 1024 | size cap in KiB on a descriptor sub-range copied through the GPU path, larger ones staying on the CPU | | `APS5_GPU_NO_WRITEBACK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | skips writing GPU results back into guest memory | | `APS5_GPU_WAIT_TIMEOUT_MS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp` | 1000 | milliseconds a GPU wait may run before a warning is printed | @@ -163,7 +178,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_KEPT_DESCRIPTOR_SETS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 8 | number of freed dedicated image table descriptor sets kept for a later build with the same layout and sizes; 0 keeps none | | `APS5_KEYFLIP_DROP` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | drops a pending unit's results on a clear-code to uncompressed key flip, as before | | `APS5_KEYS_FILL_CLEAR` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | clears an image from DCC keys whose fill pattern it matches (only when set to 1) | -| `APS5_LABEL_FLUSH_US` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | 1000 | age in microseconds at which a pending label batch is recorded or submitted | +| `APS5_LABEL_FLUSH_US` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | age in microseconds at which a pending label batch is recorded or submitted; unset is 1000 with batched label submission and 250 when `APS5_NO_LABEL_BATCH_SUBMIT` or `APS5_NO_LABEL_BATCHING` turns it off | | `APS5_LABEL_GATE_ALL` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | gates a label store on any pending completion, not only one overlapping the range | | `APS5_LABEL_LOCK_EACH` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | records each label under the GpuMutex instead of deferring it | | `APS5_LABEL_OWN_LOCK` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketFlush.cpp` | | makes a label flush take the GpuMutex itself instead of relying on a self-locking packet | @@ -190,6 +205,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_ATOMIC_STAGING` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | binds atomic shader elements in place instead of staging them through a device-local buffer | | `APS5_NO_BARRIER_BATCH` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records the staging, write-back and label-run leading barriers that a covering barrier already orders, instead of leaving them out | | `APS5_NO_BARRIER_ELISION` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records every leading barrier instead of merging it into the following submission | +| `APS5_NO_BDA_STAGING` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | binds the written elements of address-based compute builds in place in the host imports instead of staging them device-local | | `APS5_NO_BDA_TABLE_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp` | | builds every BDA descriptor table instead of reusing the recent-table cache | | `APS5_NO_BLOCK_TRACKING` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | tracks array layers, or the whole surface, instead of 64 KiB blocks | | `APS5_NO_BUFFER_CLASSES` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp` | | matches exact sizes in the buffer pool instead of rounding up to size classes | @@ -204,6 +220,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_CPU_COPY` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp` | | disables the CPU copy path of the copy HLE | | `APS5_NO_DATA_HITS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp` | | disables dispatch-cache hits that differ only in ShaderData or FlattenedSrt words | | `APS5_NO_DCC_PRESYNC` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | leaves the DCC key ranges out of the surfaces a build pre-syncs, so only the surface itself waits for its producer | +| `APS5_NO_DEFERRED_DESTRUCTION` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | on MoltenVK, destroys buffers and frees device memory as soon as the driver releases them, instead of while the graphics queue is idle (KhronosGroup/MoltenVK#2876) | | `APS5_NO_DCC_WORD_SCAN` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp` | | restores the byte-by-byte DCC key scan instead of the word-at-a-time compare | | `APS5_NO_DEFERRED_EVICTION` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | destroys displaced resource-cache entries under the mutex instead of deferring them to the open batch | | `APS5_NO_DEFERRED_SETTLES` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | settles watched private host imports as they open instead of through the deferred block-settling path (Windows only) | @@ -222,6 +239,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_DRAW_RESOURCE_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | builds every draw's shader resources instead of reusing a cached identical build | | `APS5_NO_DRAW_SRT_ENTRIES` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp` | | disables the draw cache's SRT entries, so every draw decodes its programs | | `APS5_NO_DRAW_STAGE_DATA_HITS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp` | | disables draw-cache hits that differ only in a stage's ShaderData words | +| `APS5_NO_EMPTY_INDIRECT_SKIP` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp` | | builds the stages of an indirect draw whose front program last took the CPU path even when none of its records draws anything, instead of reading its records first and skipping it | | `APS5_NO_EPOCH_COLLECT_MAP` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | drops the per-epoch map of completed collects, leaving only the fixed-size thread memo to answer a repeated collect | | `APS5_NO_EPOCH_REVALIDATE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | runs every Revalidate's per-element and per-region checks instead of the epoch gate | | `APS5_NO_FAST_REVALIDATE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | always repeats the resource lookups instead of using the fast proof | @@ -254,6 +272,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_LABEL_BATCHING` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | turns off every label-batching behaviour, the label runs, queued labels and batched submit | | `APS5_NO_LABEL_BATCH_SUBMIT` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | disables batched label submission | | `APS5_NO_LABEL_PACKET_FLUSH` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketFlush.cpp` | | disables the label flush between packets | +| `APS5_NO_LABEL_READBACK_STORE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | leaves pending linear and small storage images on the GPU at a label instead of storing them before it | | `APS5_NO_LABEL_RUNS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records every store in a run of its own between a barrier pair | | `APS5_NO_LABEL_SHORTCUT` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp` | | disables the pending-label table shortcut in a memory wait | | `APS5_NO_LAYER_CLEAR` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | tracks every surface as one layer instead of per array layer | @@ -292,6 +311,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_RESIDENT_PRESENT` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | always presents through guest memory instead of the resident image | | `APS5_NO_RESIDENT_TARGETS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | copies every render target in and out again instead of keeping it resident | | `APS5_NO_RESOURCE_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | disables the shared resource cache, so every dispatch builds its resources fresh | +| `APS5_NO_RUN_MEMO` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | walks the lease for a host import's run of ranges on every lookup instead of reusing the bounds found for the same lease snapshot and import window block | | `APS5_NO_SAMPLED_FROM_STORAGE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | keeps every sampled texture a CPU snapshot instead of viewing the surface's storage image | | `APS5_NO_SAMPLER_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | creates a sampler per binding instead of caching them | | `APS5_NO_SEPARATE_QUEUED_LABELS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | lets a queued label enter the recorded table and replace a recorded entry of its dword | @@ -327,18 +347,18 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_WRITE_EVIDENCE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp` | | disables the written-element evidence that lets a read skip the flush hook | | `APS5_NO_WRITE_ONLY_PASSES` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | counts a buffer a draw only stores to as read and written, so the next draw storing to it begins a new render pass | | `APS5_NO_WRITTEN_SHADOW` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | binds written staging elements in place instead of through the written shadow | +| `APS5_NV_DIAGNOSTICS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | creates the device with VK_NV_device_diagnostics_config and VK_NV_device_diagnostic_checkpoints where supported, for Nsight Aftermath crash dumps | | `APS5_PACKET_EPOCH` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitStatistics.cpp` | | makes queue workers bump the collect epoch before every packet | | `APS5_PIPELINED_DRAWS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp` | 8 | depth of the queue-0 draw pipeline: 0 records draws inline, 1 selects the default depth of 8, and any other value is the depth capped at 256 | | `APS5_PIPELINE_DISPATCH` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp` | | records an eligible dispatch as an item in the queue-0 pipeline instead of running it inline | -| `APS5_PIPELINE_DMA` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp` | | records a queue-0 DMA data packet in the draw pipeline instead of executing it inline | | `APS5_PIPELINE_DRAIN_FILLS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp` | | runs queue-0 buffer fills through the draw pipeline to keep their order; any value stores them in place instead | | `APS5_PIPELINE_DRAIN_LABELS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp` | | answers a queue-0 wait from the draw pipeline's own pending labels while it is busy; any value drains the pipeline at the wait | | `APS5_PIPELINE_DRAIN_SUBMISSIONS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp` | | lets a submission end without submitting its recorded work, so it joins a later submit with the pipeline's queue; any value drains the pipeline and submits at the end of the submission | -| `APS5_PIPELINE_JOURNAL_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 1024 | number of journal entries loaded at most; a longer journal is truncated to this count | -| `APS5_PIPELINE_JOURNAL_MAX_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 1024 | size cap in MiB on the pipeline journal; later pipelines are not recorded once it is reached | +| `APS5_PIPELINE_JOURNAL_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 4096 | number of journal entries loaded at most; a longer journal keeps its last entries, the ones recorded last | +| `APS5_PIPELINE_JOURNAL_MAX_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 1024 | size cap in MiB on the pipeline journal; the file is rewritten down to three quarters of the cap when it is reached, keeping the newest pipelines and the modules they name and leaving room for later records | | `APS5_PIPELINE_JOURNAL_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | | threads that replay the journal's pipelines, 0 disables the replay; unset uses half the hardware threads, at least one | | `APS5_PIPELINE_STATS` | string | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | captures and prints driver statistics for each new pipeline, requiring `VK_KHR_pipeline_executable_properties`; `1` enables | -| `APS5_PIPELINE_STORE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp` | 1024 | LRU bound on the entries of the pipeline store; an entry is evicted only once no recorded draw holds it | +| `APS5_PIPELINE_STORE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp` | adaptive | fixes the LRU bound on the entries of the pipeline store, which otherwise starts at 1024 and doubles up to 4096 for a journal replay that does not fit and while evicted pipelines keep coming back; an entry is evicted only once no recorded draw holds it | | `APS5_PM4_RING` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/SubmissionRing.cpp` | 16 | number of submissions the PM4 ring keeps for dumps, from 0 to 4096; 0 disables the ring | | `APS5_POLL_REAP` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp` | | reaps completions on every poll tick | | `APS5_POLL_TRY_EACH` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp` | | tries the poll service on every tick instead of at the poll interval | @@ -362,7 +382,8 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_SKIP_INVALID_INDIRECT_REGISTERS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp` | | skips the entries whose offset dword is no register offset in a SET_*_REG_INDIRECT list read again where its packet executes, and writes its valid entries, instead of stopping | | `APS5_SKIP_REFUSED_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp` | | skips a draw whose graphics stage the recompiler refuses, reporting each refused shader once, instead of failing the queue | | `APS5_SKIP_REQUEST_DUMPS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp` | 8 | caps RecompileRequest dumps per shader-skip reason | -| `APS5_SKIP_SHADER` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp` | 0 | hex program address of a shader to skip preparing | +| `APS5_SKIP_SHADER` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp` | 0 | hex program address of a shader whose draws and dispatches are dropped | +| `APS5_SKIP_SHADER_FILE` | path | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp` | | file of whitespace-separated hex program addresses (`#` starts a comment) whose draws and dispatches are dropped; re-read within 500 ms of a change; a token that is not a whole 64-bit hex number is reported and ignored | | `APS5_SNAPSHOT_CHECK_FATAL` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | throws on the first snapshot that differs from registered memory instead of only counting them | | `APS5_SPANNED_WAVE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | spans a single-wave wave64 compute program over two full 32-lane subgroups | | `APS5_SPIRV_OPT` | string | `core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp` | | selects the SPIR-V optimization pipeline: `full` restores the performance pipeline, `none` skips optimization | @@ -375,6 +396,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_STORE_AT_LABEL` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | flushes every pending storage-image result when a label is written on the GPU | | `APS5_SUBMISSION_EPOCH_EACH` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp` | | bumps the guest-memory collect epoch on every submission instead of only when the submission is newer than the last bump | | `APS5_SUBMIT_AT_END` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp` | | submits recorded work at the end of a submission even when a worker is queued | +| `APS5_SUBMIT_PROGRESS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp` | | enables opt-in pacing for eligible queue-zero submission prefixes within a shared nominal two-millisecond acknowledgement/timeline budget; timeout retains asynchronous acceptance | | `APS5_SUSPEND_DRAIN` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp` | | drains the device at a suspend point | | `APS5_SYNC_COMPLETION_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | keeps every draw with completion work synchronous | | `APS5_SYNC_DISPATCH` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | waits for every dispatch instead of batching it | @@ -387,7 +409,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_TEMPLATE_AUDIT_MS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 1000 | milliseconds after which a cached shader-resource object takes the full walk instead of the fast proof; `0` turns the audit off | | `APS5_TEXTURE_CACHE_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 0 | forced byte budget in MiB for the storage and sampled texture caches; `0` selects the budget automatically | | `APS5_THREAD_NICE` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp` | -10 | nice value the raised queue workers and presenter ask rtkit for (clamped to -20..19) | -| `APS5_THREAD_PRIORITY` | string | `core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp` | off | raises the queue workers and presenter above the title's threads: `off`, `high` or `rt` | +| `APS5_THREAD_PRIORITY` | string | `core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp` | off | raises the queue workers and presenter above the title's threads: `off`, `high` or `rt`; on macOS `high` is the user-interactive QoS class and `rt` a Mach time-constraint policy (period 1 ms, computation 0.5 ms) | | `APS5_THREAD_RT` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp` | 10 | realtime priority the raised threads ask rtkit for (clamped to 1..99) | | `APS5_THREAD_RTTIME_US` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp` | 200000 | hard RLIMIT_RTTIME the realtime plan lowers to, in microseconds | | `APS5_TRACE_BARRIERS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | prints that many simulated hazard barriers before stopping | @@ -415,6 +437,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_TRACE_QUERY` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | prints every 16th page query with its cost | | `APS5_TRACE_RESOURCE_KEY_MISSES` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | reports shader-resource cache key misses | | `APS5_TRACE_SECONDS` | string | `core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp` | | records from the first present `` seconds after the first present for `` seconds (whole numbers, `start:length`) | +| `APS5_TRACE_SHADER_CORRELATION` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp` | | traces shader selection and preparation failure tokens through draw batches | | `APS5_TRACE_SHADER_REGS` | string | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp` | | prints a shader's registers for one hex program address, or `all` | | `APS5_TRACE_SHADOW` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp` | | prints the first n shadow publishes, the first 64 seeds, every evict and retire | | `APS5_TRACE_SKIPS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp` | | reports each distinct shader skip once | @@ -431,6 +454,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_VERIFY_DATA_HITS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp` | | recomputes a dispatch data hit and aborts on a disagreement | | `APS5_VERIFY_DRAW_DATA_HITS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp` | | recomputes a draw data hit and aborts on a disagreement | | `APS5_VERIFY_DRAW_ENTRIES` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp` | | re-decodes every draw-cache hit through the full path and checks it | +| `APS5_VERIFY_DRAW_PIPELINE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | runs a full check next to 1 in 64 reused proofs and prints `[epoch-proofs]` counts | | `APS5_VERIFY_DRAW_RECIPE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp` | | re-decodes a draw recipe instead of using the cache and checks the result | | `APS5_VERIFY_DRAW_SNAPSHOTS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | re-reads a reused draw snapshot and counts the ones that differ | | `APS5_VERIFY_KNOWN_VALUES` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp` | | serves known values through the hook too and counts mismatches | @@ -448,7 +472,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_WRITE_EVIDENCE_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp` | 16 | largest written range in KiB that the evidence may serve | | `APS5_WRITE_EVIDENCE_SAMPLE` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp` | 16 | reads one in this many pending writes to sample them | | `APS5_WRITE_WATCH_IMPORTS` | string | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | how host imports are write-tracked: `probe`, `watch` or `unwatch` | -| `APS5_WRITTEN_SHADOW_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 2048 | upper bound in KiB of the written staging window | +| `APS5_WRITTEN_SHADOW_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 2048 (8192 on a discrete GPU) | upper bound in KiB of the written staging window; a set value is used as given on both device types | | `APS5_WRITTEN_SHADOW_MIN_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 16 | lower bound in KiB of the written staging window | ### libSceAjm.native @@ -510,7 +534,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | Name | Kind | Source | Default | Description | |------|------|--------|---------|-------------| -| `ANYPS5_LANGUAGE` | integer | `core/libs/prx/libSceSystemService/Export.cpp` | 1 | console language number returned by sceSystemServiceParamGetInt, defaulting to English US | +| `ANYPS5_LANGUAGE` | integer | `core/libs/prx/libSceSystemService/SystemService.hpp` | 1 | console language number returned by sceSystemServiceParamGetInt, defaulting to English US | ### libSceVideoOut @@ -519,6 +543,8 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `ANYPS5_INPUT_CONFIG` | string | `core/libs/prx/libSceVideoOut/src/InputMapping.cpp` | | path of the input mapping file that binds host keys and mouse to guest pad actions | | `ANYPS5_REQUIRE_DISPLAY` | presence | `core/libs/prx/libSceVideoOut/tests/Flip.cpp` | | makes video-out tests fail instead of skipping when no display or Vulkan device is available | | `APS5_NO_PAD_INPUT` | presence | `core/libs/prx/libSceVideoOut/src/PadInput.cpp` | | ignores keyboard and mouse input so a measurement run is not advanced by a stray press | +| `APS5_NO_WGI` | presence | `core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp` | | turns off SDL's Windows.Gaming.Input joystick backend at default hint priority, on Windows hosts where it faults a few seconds into every run | +| `APS5_OUTPUT_RESOLUTION` | string | `core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp` | | `x` in pixels that replaces the host display's size when `sceVideoOutGetOutputStatus` and `sceVideoOutGetOutputStatusWithVrr` report the output resolution: 1 for a size below 3840 wide and 2160 high, 2 otherwise, as for the display; unset or empty follows the display, and any other value makes those calls throw | | `APS5_VIDEOOUT_TELEMETRY_DIR` | string | `core/libs/prx/libSceVideoOut/src/Telemetry.cpp` | | directory where videoout.jsonl is appended with the pad and present telemetry lines; nothing is opened when unset or empty | ### libSceVideodec2 @@ -531,17 +557,19 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | Name | Kind | Source | Default | Description | |------|------|--------|---------|-------------| -| `APS5_CRASH_INDIRECT_MEMORY` | presence | `core/libs/prx/libc/src/specifics/windows/CrashReport.cpp` | | dumps the words the faulting registers point at in the Windows crash report | +| `APS5_CRASH_INDIRECT_MEMORY` | presence | `core/libs/prx/libc/src/specifics/windows/CrashReport.cpp` | | adds to the Windows crash report up to 64 bytes from each pointer in the dump of the words the registers point at, as `[[reg+off]] @addr` lines; off when unset or empty | +| `APS5_CRASH_STACK_BYTES` | integer | `core/libs/prx/libc/src/specifics/windows/CrashReport.cpp` | 0 | bytes of the faulting thread's stack from rsp, decimal or 0x hexadecimal and at most 1048576, that the Windows crash report prints with the data at the stack words pointing outside the stack; 0 prints none | | `APS5_HANG_DUMP_SECS` | integer | `core/libs/prx/libc/src/specifics/windows/CrashReport.cpp` | | seconds after which a watchdog dumps all threads and aborts | | `APS5_HEAP_CACHE_MIB` | integer | `core/libs/prx/libc/src/GuestHeap.cpp` | 4096 | size in MiB of the cache of freed large blocks before the oldest are decommitted | | `APS5_HELPER_AFFINITY_MASK` | integer | `core/libs/prx/libc/include/CpuTopology.hpp` | | hex core mask that helper threads (deferred release, mirror refresh) are pinned to | -| `APS5_LOG_TIMESTAMPS` | presence | `core/libs/prx/libc/include/general/LogMacros.hpp` | | prefixes each log line with the milliseconds since the first log | +| `APS5_LOG_TIMESTAMPS` | presence | `core/libs/prx/libc/include/general/LogMacros.hpp` | | prefixes each log line with `[+S.UUUUUU s]`, the seconds with microsecond digits since the clock started at the first stamped line; on Windows each library has its own clock, started at that library's first stamped line | | `APS5_NO_PROCMAP_QUERY` | presence | `core/libs/prx/libc/include/specifics/linux/HostMapping.hpp` | | skips the /proc/self/maps PROCMAP_QUERY ioctl and scans the maps file instead | | `APS5_NO_SSE4A_EMULATION` | presence | `core/libs/prx/libc/src/specifics/windows/CrashReport.cpp` | | disables emulation of AMD-only SSE4a instructions, making such a fault fatal | | `APS5_NO_WRITE_WATCH` | presence | `core/libs/prx/libc/src/GuestArena.cpp` | | disables write watching so guest buffers are fully re-read on every use | | `APS5_PIN_WAIT_MS` | integer | `core/libs/prx/libc/src/GuestAllocations.cpp` | 60000 | milliseconds to wait for a pinned allocation lease before failing | | `APS5_TRACE_AFFINITY` | presence | `core/libs/prx/libc/include/CpuTopology.hpp` | | prints a line naming the role, tid and mask of every thread pinning | | `APS5_TRACE_CALLS` | string | `core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp` | | comma-separated libraries (or all) whose guest calls are traced | +| `APS5_TRACE_CALLS_FILE` | string | `core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp` | | file rewritten every 10 seconds with each traced import called so far and its number of calls | | `APS5_TRACE_EXIT` | presence | `core/libs/prx/libc/src/Process.cpp` | | prints a backtrace of the caller of exit, _Exit and the process-exit path | | `APS5_TRACE_SSE4A` | presence | `core/libs/prx/libc/src/specifics/windows/CrashReport.cpp` | | prints each emulated SSE4a instruction once per rip | | `APS5_WATCH_ADDR` | integer | `core/libs/prx/libc/src/specifics/windows/CrashReport.cpp` | | absolute hex address whose writes are reported after its page is committed | @@ -559,7 +587,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_JOB_AFFINITY` | presence | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | disables pinning of job worker threads | | `APS5_NO_SLEEP_WAKE` | presence | `core/libs/prx/libkernel/Time/SleepWake.cpp` | | keeps every sleep of 1 ms or less in full instead of ending it at a GPU batch, batch completion action or flip completion | | `APS5_NO_TIMER_RESOLUTION` | presence | `core/libs/prx/libkernel/Time/Time.cpp` | | keeps the default 15.6 ms scheduler tick instead of raising it to 0.5 ms on Windows | -| `APS5_TIME_SCALE` | integer | `core/libs/prx/libkernel/Time/Time.cpp` | 1 | factor applied to the guest monotonic clocks and TSC; sleeps still take real time | +| `APS5_TIME_SCALE` | integer | `core/libs/prx/libkernel/Time/Time.cpp` | 1 | factor applied to the guest monotonic clocks and TSC; sleeps and host JobManager worker spin intervals still take real time | | `APS5_TRACE_APR` | presence | `core/libs/prx/libkernel/Apr/src/Apr.cpp` | | logs every APR file read with its path, offset, size and destination | | `APS5_TRACE_MEMORY` | presence | `core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp` | | logs direct-memory map, remap, protect, unmap and physical allocation operations | | `APS5_TRACE_SYNC` | presence | `core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp` | | logs event-flag create, set and wait operations | diff --git a/docs/dev/GAME_TESTING.md b/docs/dev/GAME_TESTING.md index 73d6271fc2..82afb1ccfc 100644 --- a/docs/dev/GAME_TESTING.md +++ b/docs/dev/GAME_TESTING.md @@ -7,11 +7,12 @@ For user options see [USAGE](../user/USAGE.md); for internals see [ARCHITECTURE] ## Before a run - Build `main` or the PR head in a clean worktree, including the `libs` target (it is not in the default build). If a new test breaks the build, `cmake --build build --target libs relinker` is enough to convert and run a title. +- `tools/check_main_sync.py --fetch` in the worktree reports whether the branch is in step with upstream `main`, so a result isn't taken on an old base by mistake. - Convert the title from plain ELF files (`tools/unwrap_self.py` unwraps a dump's `eboot.bin` and `.prx`): - Linux: `relinker --registry src/eboot.elf app.elf` - Windows: `relinker --windows --registry src/eboot.elf app.exe`. Without `--windows` the output is an ELF file whatever its name, and Windows refuses to start it (exit code 216, "not compatible with the version of Windows"). - A dump whose `app0` holds `ampr_assets.index` and `ampr_assets-*.pak` was repacked for [ampr_emu](https://github.com/drakmor/ampr_emu): the packed files were removed from `app0`, and only ampr_emu reads the packs. Restore them first with ampr_emu's `tools/ampr_pack.py unpack --index app0/ampr_assets.index --output ` and copy the files missing from `app0`. Without them a Unity title stops at start-up on the missing `Media/Resources/unity default resources`. -- Convert every module the title loads from its own folders (`sce_module/`, `Media/Modules/`, `Media/Plugins/`). Unity titles need `--module-dir Media/Modules --module-dir Media/Plugins`. Put each printed `Guest module:` file next to its original `.prx` in `app0/`, and keep the original there. +- Convert every module the title loads from its own folders (`sce_module/`, `Media/Modules/`, `Media/Plugins/`). Unity titles need `--module-dir Media/Modules --module-dir Media/Plugins`. The relinker writes each converted module as `.guest.prx` into `app0/` beside the output, in the module's own folder, and lists it in the `Arrange the files like this` tree it prints. Keep the original `.prx` next to it. - `libs/` holds only AnyPS5's own libraries, from `build/core/libs/libs` (not `unpatched/`). A game module placed there fails to load (error 193 on Windows). A self-built `libs/` on Windows also needs `libgcc_s_seh-1.dll`, `libstdc++-6.dll` and `libwinpthread-1.dll` from the toolchain's `mingw64/bin`, or every library fails with error 126 ([USAGE](../user/USAGE.md#input-and-conversion)). - Run `tools/import_audit.py` on the registries. Exit code 1 lists imports that will fail or throw. - Run one title at a time. Titles share the GPU and the controller file. Some titles start a second process (Peace Walker's `mgspw.mgspw.elf`) that can outlive a time cap on the first: check that every process of the previous run is gone. @@ -50,7 +51,12 @@ cmd /c '.\app.exe > run.log 2>&1' - The driver's own: Mesa (`MESA_SHADER_CACHE_DIR`, or `MESA_SHADER_CACHE_DISABLE=true`), AMD on Windows (`%LOCALAPPDATA%\AMD\VkCache`). Back a driver cache up instead of deleting it; it isn't AnyPS5's. Say in the report whether the caches were empty or warm. A first run that ends in device lost (`VK_ERROR_DEVICE_LOST`) or a GPU wait timeout while large pipelines are still being built, seen on AMD, often is the cache and not a regression: run again with the caches warm and report both runs. -- **Power and display:** on a laptop, run on AC power; a run on battery can make a neutral change look like a regression. Note the display's refresh rate: a title that asks for 120 Hz output can stop at start on a 60 Hz display. +- **Power and display:** on a laptop, run on AC power; a run on battery can make a neutral change look like a regression. Note the display's refresh rate: a title that asks for 120 Hz output can stop at start on a 60 Hz display. Keep the display awake for long or unattended runs: a title started while it sleeps stops with `SDL_GetWindowDisplayIndex failed: Couldn't find any displays`. +- **Memory budgets:** two budgets come from the machine's memory and can change the frame rate several times over, so check them before comparing results from different machines. + - Host imports (`APS5_HOST_IMPORT_MIB`, see [ENVIRONMENT](ENVIRONMENT.md)): the default follows the physical memory and the host heap the driver reports, so two machines with the same RAM can get different budgets. Refused imports print `[gpu] host import budget: X MiB live of Y MiB (...); N imports (M MiB) refused so far`, at most every 10 s; without such a line nothing was refused. A refused range is copied on every later use instead, and those copies can take device memory the texture caches need. + - Texture caches (`APS5_TEXTURE_CACHE_MIB`, see [USAGE](../user/USAGE.md#texture-cache-budget)): `[gpu] sampled texture cache budget N MiB` at start and when it changes, and with `APS5_PROFILE_DRAW=1` a `[texture-cache] (10 s)` line with the evictions and the budget. Thousands of evictions per 10 s with the budget at its 2048 MiB floor mean textures are rebuilt every frame. + + Report these lines. Set either variable only for a comparison, and say so. ## What to read @@ -80,7 +86,8 @@ cmd /c '.\app.exe > run.log 2>&1' - Measure the same spot every time: load a save, wait for the scene to settle, then measure a fixed window with no input. - Compare builds on the same machine, alternating them (A, B, A, B), at least two runs each. Throw away a run that was disturbed (another program, a sleeping display) and say so. - `APS5_TRACE_SUMMARY_EVERY=10` writes one line per 10 s to `aps5-summary.jsonl` without slowing the title; `tools/trace_digest.py` turns it into a table, and `tools/performance_compare.py` compares two captures ([BUILD](BUILD.md#performance-trace)). Pull requests labelled `perf` need a capture of `main` and one with the change. -- `APS5_DUMP_FRAMES=` with `APS5_DUMP_FRAMES_EVERY=` saves frames as `frame_.bmp` to check what was on screen. A second process of the same title writes to the same names and overwrites the first one's frames. +- `APS5_DUMP_FRAMES=` with `APS5_DUMP_FRAMES_EVERY=` saves frames as `frame_.bmp` to check what was on screen. A second process of the same title writes to the same names and overwrites the first one's frames. While 64 MiB of frames wait to be written, the presenter waits for the + writer, so a slow disk or `APS5_DUMP_SCALE=1` slows the title down instead of stopping it. ## Title notes @@ -104,7 +111,7 @@ Include, in this order: 3. Host: CPU, OS (and kernel on Linux), the GPU as the `Selected GPU name=` line names it, the driver version, AC or battery, and the display refresh rate. 4. The exact command and every `APS5_*` / `ANYPS5_*` variable, the in-game settings if they differ from the defaults, and whether the caches were empty or warm. 5. How far the title got (menu, a save, a scene) and what stopped it: the exit code and the last 200 log lines, plus the kernel line (Linux) or crash report (Windows) for a crash. -6. Presents per 10 s, and storage-image lines if memory is involved. +6. Presents per 10 s, and the storage-image and budget lines (`[gpu] host import budget`, `[gpu] sampled texture cache budget`, `[texture-cache]`) if memory is involved. 7. How many runs and how long each lasted. One run is an observation, not a reproduction. 8. The date of the test. Leave unknown fields empty rather than filling them from another report. diff --git a/docs/dev/HW_ORACLE.md b/docs/dev/HW_ORACLE.md index e48f86ed1a..c675934529 100644 --- a/docs/dev/HW_ORACLE.md +++ b/docs/dev/HW_ORACLE.md @@ -10,6 +10,8 @@ the tool, but differs from gfx1013 in more places. Say which GPU you measured on ## Requirements +### Linux + - Linux with the `amdgpu` driver and read/write access to `/dev/kfd` (usually the `render` group). Root is not needed. - The ROCm HSA runtime with its headers: `libhsa-runtime-dev` (Debian, Ubuntu), `rocm-runtime-devel` (Fedora), `hsa-rocr` (Arch), or AMD's ROCm packages. A runtime under `ROCM_PATH` or `/opt/rocm` is used first. The rest of ROCm @@ -20,6 +22,17 @@ the tool, but differs from gfx1013 in more places. Say which GPU you measured on The first run builds `oracle.c` into `~/.cache/anyps5-hw-oracle/` (`HW_ORACLE_CACHE` overrides it). The target is read from the GPU (`HW_ORACLE_TARGET` overrides it). +### Windows + +- AMD's Windows driver, which installs the HIP runtime (`amdhip64_6.dll`) and the code object manager (`amd_comgr_2.dll`) + in `System32`. ROCm, a C compiler, `clang` and `ld.lld` are not needed. +- Python 3 from python.org or the Microsoft Store. + +On Windows `oracle_hip.py` replaces `oracle.c`: comgr assembles and links the kernel, and HIP runs it. The CLI, the Python +interface and the results are the same as on Linux. `HW_ORACLE_RUNTIME` picks the runtime (`hip` on Windows, `hsa` +elsewhere). `HW_ORACLE_HIP` and `HW_ORACLE_COMGR` name other DLLs. `ROCR_VISIBLE_DEVICES` selects the GPU, through +`HIP_VISIBLE_DEVICES`, unless that variable is already set. + ## Kernel `template.s` wraps the body under test. Per lane: diff --git a/docs/dev/PROGRESS.md b/docs/dev/PROGRESS.md index a1257f85a9..4e451630a3 100644 --- a/docs/dev/PROGRESS.md +++ b/docs/dev/PROGRESS.md @@ -22,6 +22,10 @@ Comparisons identify functions by library and name. When a head group becomes a Real removals and transitions back to stubs remain visible. Percentages retain the original snapshot totals, so eliminating duplicate accounting can change a percentage without changing available functionality. Older JSON snapshots without `shared_sources` are accepted. +PR reports include a table of changed libraries with implemented export counts, declared totals, percentages before and after, and changes in percentage points (`pp`). The table uses the same two JSON snapshots as the global report. A library appears when its counts, export names or shared-source owner change, including when its counts stay equal. Unchanged libraries are omitted, and the table lists at most 100 libraries in name order with a count of omitted rows. + +New or removed libraries show `Not present` on the missing side. Libraries whose snapshots contain both counts and report zero exports show `No exports`; if either count is absent, the table shows `Counts unavailable`. Wrappers show `Shared sources` with the source library's name. These rows show `n/a` for the percentage change. The table retains the scanner's counting rules; it does not measure compatibility. + Implemented definitions are also classified as behaviourally empty when their body holds only `(void)name;` discards and at most one constant return, bare `return;` or `throw`, including a return of a local `static` wrapper that is itself behaviourally empty; wrapper calls whose arguments contain calls do not qualify. Comparisons list newly implemented behaviourally empty functions without changing any count. Older JSON snapshots without `inert_names` are accepted. ## One library diff --git a/docs/dev/TRACING.md b/docs/dev/TRACING.md index 8cb84bb4a4..fa10619b68 100644 --- a/docs/dev/TRACING.md +++ b/docs/dev/TRACING.md @@ -12,6 +12,7 @@ Set a switch before starting the emulator. Unless noted, any value enables it. M | `APS5_TRACE_AUDIOOUT` | Audio output port parameters when a port is opened. | | `APS5_TRACE_AUDIOOUT2` | AudioOut2 context and port parameters, attributes, pushes, queue polls and periodic queue summaries. | | `APS5_TRACE_CALLS` | On Windows and Linux, records calls to the imports selected by this comma-separated library list (or `all`); crash and abort reports (abort only on Linux) include the last 64 calls and most-called imports. The executable must be converted with `--trace-imports`; see [Usage](../user/USAGE.md#tracing-system-library-calls). | +| `APS5_TRACE_CALLS_FILE` | With `APS5_TRACE_CALLS`, a file rewritten every 10 seconds with every traced import called so far, most called first: number of calls, library and import name. | | `APS5_TRACE_EXIT` | Guest `exit` and `_Exit` calls and process-exit backtraces. | | `APS5_TRACE_FIBER` | Fiber initialization, switches and returns. | | `APS5_TRACE_MEMORY` | Direct-memory allocation, mapping and protection operations. | @@ -45,7 +46,7 @@ Set a switch before starting the emulator. Unless noted, any value enables it. M | `APS5_TRACE_HOST_IMPORT` | Host-import mapping attempts, memory-map details, failures and retirement. | | `APS5_TRACE_IMAGE_TABLE` | Image-table resource materialization and descriptor selection. | | `APS5_TRACE_INDIRECT_DRAWS` | Indirect draw packets that pass the draw precheck. | -| `APS5_TRACE_LABEL` | `RELEASE_MEM` packets that target the address in this hexadecimal value. | +| `APS5_TRACE_LABEL` | A matching `RELEASE_MEM` when recorded, and nearby `RELEASE_MEM`, `WRITE_DATA`, `COPY_DATA`, `DMA_DATA` and `WAIT_REG_MEM` packets when executed. Set the value to the guest address in hexadecimal. | | `APS5_TRACE_LATE_LABELS` | GPU label writes observed after their waiting submission was handled. | | `APS5_TRACE_QUERY` | Every sixteenth guest-memory page query, including cost and caller address. | | `APS5_TRACE_RESOURCE_KEY_MISSES` | Resource-content key changes that cause draw or dispatch resource-cache misses. | diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index ee2a2dfd75..a9bed3b21f 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -29,3 +29,4 @@ Throughout the project, every function at every stage either **does exactly what - `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows. - [Quick syntax checks](../../tools/check_compile.py) reject unexpanded `@response` arguments rather than parse compiler-specific quoting and risk retaining output flags. Expand response files in `compile_commands.json` before checking; the Ninja fallback expands them with `ninja -t compdb -x`. +- [Quick syntax checks](../../tools/check_compile.py) do not model every compiler option's operand count. A preprocessing mode following an unrecognized option without a joined value is rejected rather than risk removing that option's operand. Compiler wrappers with their own option grammar are not covered. Forwarded frontend actions (`-Xclang`, `-Xpreprocessor` and their joined forms) are rejected. Comma-forwarded options (`-Wp,`) allow only recognized macro/include operands; response-file tokens are rejected even as operands, and other options are rejected because unmodeled actions or output operands could bypass syntax analysis or write outputs. See the [Clang option grammar](https://clang.llvm.org/docs/ClangCommandLineReference.html). diff --git a/docs/dev/debt/libSceAcm.md b/docs/dev/debt/libSceAcm.md index 59682c3221..c94c8ce717 100644 --- a/docs/dev/debt/libSceAcm.md +++ b/docs/dev/debt/libSceAcm.md @@ -2,12 +2,13 @@ ### Silent stubs -- [libSceAcm](../../../core/libs/prx/libSceAcm/Export.cpp) batches execute as silent no-ops, so `sceAcm_ConvReverb_SharedInput`, `sceAcm_FFT` and `sceAcm_Panner` return success without encoding audio commands. +- [libSceAcm](../../../core/libs/prx/libSceAcm/Export.cpp) batches execute as silent no-ops, and do not execute encoded panner and FFT commands. `sceAcm_ConvReverb_SharedInput` returns success without encoding audio commands. ### Unknown function info -- Context validation error codes (0x81940002 for bad context and 0x81940006 for bad pointer) for [libSceAcm](../../../core/libs/prx/libSceAcm/Export.cpp) follow prosper's `hle_audio_spatial.cpp`. KytyPS5 (`acm.cpp`) and prosper do not track individual batch identifiers, so `sceAcmBatchWait` returns success for valid contexts without inspecting batch IDs. Hardware ACM DSP coprocessor scheduling and console error verification remain unmeasured. +- [libSceAcm](../../../core/libs/prx/libSceAcm/Export.cpp) context creation, batch submission and waiting use host identifiers instead of the native device and ioctl requests. Batch identifiers, timeout handling, DSP scheduling and service-side errors remain unimplemented. Panner source parameter and matrix payloads are forwarded by the native library; their DSP interpretation remains unverified. - [sceAcm_ConvReverb_SharedIr, sceAcm_IFFT](../../../core/libs/prx/libSceAcm/Export.cpp) (libSceAcm) - their names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAcm.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim -- PPSA02530 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [sceAcmBatchJobPriority](../../../core/libs/prx/libSceAcm/Export.cpp) (libSceAcm) and [scePadVrControllerGetTriggerEffectStates](../../../core/libs/prx/libScePad/Export.cpp) (libScePad). - [`sceAcmBatchJobNotification`](../../../core/libs/prx/libSceAcm/Export.cpp) is a parameterless pending declaration for import resolution. Its signature and behavior are unverified; it throws through `NotImplemented_nid_no_patch`. + +- The shared-input reverb signature and descriptor fields are recovered from native accesses. Input history indices are mutable; block metadata representation, reverb command encoding and DSP interpretation remain unimplemented in [libSceAcm](../../../core/libs/prx/libSceAcm/Export.cpp). diff --git a/docs/dev/debt/libSceAgc.md b/docs/dev/debt/libSceAgc.md index 0bd4347123..43c8c621b4 100644 --- a/docs/dev/debt/libSceAgc.md +++ b/docs/dev/debt/libSceAgc.md @@ -2,14 +2,11 @@ ### Unknown function info -- [GS shader-half fusion](../../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - `SPI_SHADER_PGM_RSRC2_GS.OC_LDS_EN` is preserved if either half requests it, while user SGPR allocation comes from the front half and other state stays with the back half. Bit 18 is `OC_LDS_EN` in AMD's [GFX10 register definitions](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/include/asic_reg/gc/gc_10_1_0_sh_mask.h). Mesa's radeonsi sets it on a merged ES+GS program exactly when the ES stage is a tessellation evaluation shader ([`si_shader_gs_legacy`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/45ea3ed8ce649d6b72d1843307a29d9cc3b7704e/src/gallium/drivers/radeonsi/gfx/si_state_shaders.cpp#L631), [`gfx10_shader_ngg`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/45ea3ed8ce649d6b72d1843307a29d9cc3b7704e/src/gallium/drivers/radeonsi/gfx/si_state_shaders.cpp#L1144)), so it belongs to the front half; console fusion output has not been measured. Cocoon (PPSA08766) requests front `0x70004` and back `0xab0020`. Other unmatched hardware state still throws. -- PPSA01341 imports declared without parameters, signatures unknown: [sceAgcSetSemaphoreMemory](../../../core/libs/prx/libSceAgc/Unimplemented.cpp). Name from the shadPS4 aerolib NID list -- [sceAgcWaitRegMemPatchMask](../../../core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp) (libSceAgc) - follows `sceAgcWaitRegMemPatchReference`: the mask is taken as a 32-bit value and written to the low mask word of a 32- or 64-bit wait, as [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) does; [sharpemu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/Agc/AgcExports.PacketPatching.cs) writes both words of a 64-bit wait's mask. A mask above 32 bits throws - [sceAgcDcbSetCfRegisterDirect, sceAgcDcbSetCfRegisterRangeDirect](../../../core/libs/prx/libSceAgc/DcbState/src/Registers.cpp) (libSceAgc) - signatures follow [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Agc/SceAgc.cs), which agree; the SET_CONFIG_REG (0x68) packet is OpenAGC's. The driver does not execute SET_CONFIG_REG, so a submitted packet throws - [Command buffer cursors](../../../core/libs/prx/libSceAgc/Command/src/Packet.cpp) (libSceAgc) - a null `cursor_down` is taken as a buffer without downward allocations, whose limit is `top`. PPSA16064 builds one-packet buffers of 8 dwords that way; the console's handling is unconfirmed - [sceAgcGetRegisterDefaultsInternal](../../../core/libs/prx/libSceAgc/Misc/src/RegisterDefaults.cpp) (libSceAgc) - returns the version 0 internal table while `sceAgcGetRegisterDefaults` now returns the version 13 public one; which internal table a console returns is not known. [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) returns the table of the version passed to `sceAgcInit` instead, and null before it -- [sceAgcGetRegisterDefaults](../../../core/libs/prx/libSceAgc/Misc/src/RegisterDefaults.cpp) (libSceAgc) - returns the version 13 public table (137 keyed entries over 84 context blocks) because the console returns the running system software's table: [ps5-vulkan](https://github.com/mpereiraesaa/ps5-vulkan/blob/4bb0c8b1423698a7369efc22b829bf16a99e7128/src/color_runtime_defaults.h) measured 128 entries over 79 blocks on 10.x and 137 over 84 on 12.02 and 13.x, the shapes of the version 10 and version 11/13 tables here, and PPSA99011 refuses the version 0 table. The [type table](../../../core/libs/prx/libSceAgc/Command/src/RegisterDefaults.cpp) is handed out as 8-byte `{key, index << 2 | bank}` pairs, the layout ps5-vulkan and the homebrew sharing its `ps5_agc_registers.h` read; the compact 12-byte entries also pack the block's register count and stride above bit 10, and those bits are dropped. No console dump of the runtime type table was compared, and AnyPS5 does not choose 10.x or 12.02 by the system software a title targets +- [sceAgcGetRegisterDefaults](../../../core/libs/prx/libSceAgc/Misc/src/RegisterDefaults.cpp) (libSceAgc) - returns the version 13 public table (137 keyed entries over 84 context blocks) because the console returns the running system software's table: [ps5-vulkan](https://github.com/mpereiraesaa/ps5-vulkan/blob/4bb0c8b1423698a7369efc22b829bf16a99e7128/src/color_runtime_defaults.h) measured 128 entries over 79 blocks on 10.x and 137 over 84 on 12.02 and 13.x, the shapes of the version 10 and version 11/13 tables here, and PPSA99011 refuses the version 0 table. The [type table](../../../core/libs/prx/libSceAgc/Command/src/RegisterDefaults.cpp) is handed out as 8-byte `{key, index << 2 | bank}` pairs, the layout ps5-vulkan and the homebrew sharing its `ps5_agc_registers.h` read; the compact 12-byte entries also pack the block's register count and stride above bit 10, and those bits are dropped. `sceAgcGetRegisterDefaults2`, `sceAgcGetRegisterDefaults2Internal`, `sceAgcGetRegisterDefaultsInternal` and `sceAgcInit` hand out the compact 12-byte entries (`RegisterDefaultsTypeLayout::Compact`): SDK-built code walks the tables of the first two with a 12-byte stride, reads the key at +0 and the bank and index from the low 10 bits of the word at +4, and keeps an all-ones sentinel for a key it does not find. Which layout the legacy internal call returns on a console is not known. No console dump of the runtime type table was compared, and AnyPS5 does not choose 10.x or 12.02 by the system software a title targets - [sceAgcInit](../../../core/libs/prx/libSceAgc/DcbState/src/Init.cpp) (libSceAgc) - versions 0–13 select their register-default tables and return 0; pointer-shaped or otherwise out-of-range values (>=14) return 0 without a register-default lookup. Console probe on firmware 13.60 ([#4808](https://github.com/boykopovar/AnyPS5/issues/4808#issuecomment-6149448503)): `sceAgcInit(&buf, 8)` (NID `kW3GLb7QfPg`) returns 0 and writes nothing; scalar versions 2–13 return `0x8A6C0004` there, while AnyPS5 still returns 0 for those scalars. The probe does not show that table 13 is selected for pointer-shaped calls. [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) returns `0x80890001` above 12 for `sceAgcInit`, and falls back to version 11 in `sceAgcGetRegisterDefaults2`. PPSA99011, PPSA99995, [ps5-vulkan](https://github.com/mpereiraesaa/ps5-vulkan) and [prospero-win](https://github.com/mpereiraesaa/prospero-win) call that NID with a pointer-shaped first argument. `sceAgcInit_0090` (NID `23LRUSvYu1M`) checks the state pointer and then calls `sceAgcInit`, as OpenAGC does - [sceAgcSetSubmitMode](../../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted - [sceAgcGetGsOversubscription](../../../core/libs/prx/libSceAgc/Shader/src/GsOversubscription.cpp) (libSceAgc) - signature and occupancy formula (base and expanded vertex/export capacities, budget shift, `GE_PC_ALLOC` / `SPI_SHADER_PGM_RSRC4_GS` encoding) follow KytyPS5 only, not confirmed on hardware or a title; a negative or NaN factor that gives a negative target, a missing GS context register or a zero `GE_MAX_OUTPUT_PER_SUBGROUP` throws @@ -22,19 +19,19 @@ - [sceAgcDcbContextStateOp](../../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - delegates to `sceAgcDcbContextStateOp_0100` (same packets as KytyPS5's `AgcDcbContextStateOp`); KytyPS5 returns nullptr for an operation above 3, here it throws; the packet words were not compared with a real console - [sceAgcBranchPatchSetThenTarget_0300, sceAgcBranchPatchSetElseTarget_0300](../../../core/libs/prx/libSceAgc/Patch/src/Branch.cpp) (libSceAgc) - names from their NIDs (`7Wa3aeJgeVU`, `rP5xLdOf26k`); the `(packet, cachePolicy, target, sizeInDwords)` signature is inferred from PPSA28416's calls (a branch packet, 0, an aligned target, 4), with the policy stored where `sceAgcCbBranch` puts it - [sceAgcDcbSet{Cx,Sh,Uc}RegistersIndirectGetSize](../../../core/libs/prx/libSceAgc/DcbState/src/Registers.cpp) (libSceAgc) - the count argument is ignored: PPSA28416 calls them with leftover register contents (0xf5f5039c), so the real functions likely take none; the size is 20 bytes either way +- [sceAgcCbSetShRegistersDirect, sceAgcCbSetUcRegistersDirect](../../../core/libs/prx/libSceAgc/Cb/src/Registers.cpp) (libSceAgc) - a register list may start at any byte address, and its 8-byte `{offset, value}` entries are copied byte for byte. PPSA26344 (02.002.000) passes the `sh_registers` lists of compute shader headers that sit 1 byte past an 8-byte boundary in `core_common_subcore.core_b1_fx.sprig.xpps` ([#902](https://github.com/boykopovar/AnyPS5/pull/902)), so those lists start at 1 mod 4 (0x8021363131 in an open-world run). The title runs on the console with these lists, but how the console reads an unaligned list (at the given address, or at a rounded-down one) is not measured - [sceAgcUpdateInterpolantMapping_0100](../../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - name from its NID (`k0E7vkgqAuE`); the `(regs, vs, ps)` arguments are the declared ones, behaviour unknown - [sceAgcWriteDataPatchSetDst, sceAgcWriteDataPatchSetCachePolicy and their Async forms](../../../core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp) (libSceAgc) - the signatures `(packet, uint8 value)` are inferred from the other patch functions, as no public declaration was found; the value is taken in the encoding of the `dst` and `cachePolicy` arguments of `sceAgcDcbWriteData` (graphics) and `sceAgcAcbWriteData` (Async, compute), and the patched packet is the one those calls would write. `sceAgcAsyncWriteDataPatchSetAddressOrOffset` is assumed to patch the address like `sceAgcWriteDataPatchSetAddressOrOffset`, as the compute WRITE_DATA packet keeps it in the same dwords -- [sceAgcCreateInterpolantMapping_10000](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - name from its NID (`dbOlWdppb4o`); same arguments as `sceAgcCreateInterpolantMapping_0100`; PPSA26344 passes three pointers at each of nine observed call sites (`0x9F914E`, `0x9F91E2`, `0x9F9216`, `0x1604BBD`, `0x16122D7`, `0x1618285`, `0x161838C`, `0x16184EE`, `0x1618607`), consistent with that argument assumption, but does not read the return value there, so its return type is not established. The `SPI_PS_INPUT_CNTL` bits for `is_f16` 2 inputs follow KytyPS5, not confirmed on hardware +- [sceAgcCreateInterpolantMapping_10000](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - name from its NID (`dbOlWdppb4o`); same arguments as `sceAgcCreateInterpolantMapping_0100`; PPSA26344 passes three pointers at each of nine observed call sites (`0x9F914E`, `0x9F91E2`, `0x9F9216`, `0x1604BBD`, `0x16122D7`, `0x1618285`, `0x161838C`, `0x16184EE`, `0x1618607`), consistent with that argument assumption, but does not read the return value there, so its return type is not established. - [sceAgcGetDataPacketPayloadRange](../../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - signature, the `{base, size in bytes}` output and the type 0 range (header + 1, one dword longer than the type 1 payload) from KytyPS5 only, matching `sceAgcGetDataPacketPayloadAddress_0090`; prosper returns header + 2 for both types from that address function - [sceAgcGetDataPacketPayloadAddress](../../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - forward to `sceAgcGetDataPacketPayloadAddress_0090` and the inferred (addr, cmd, type) parameters follow SharpProspero and decrypted firmware 3.20 NID mapping; not verified on console hardware +- [sceAgcAcbMemSemaphore, sceAgcDcbMemSemaphore](../../../core/libs/prx/libSceAgc/Acb/src/Sync.cpp) (libSceAgc) - signature (cb, semaphoreAddr, waitForMailbox, signalType, operation) inferred from PPSA03396, whose wrapper passes the address second and operation 6 (signal) or 7 (wait) last; the three trailing arguments go to the packet as-is - [sceAgcCbMemSemaphore](../../../core/libs/prx/libSceAgc/Cb/src/Sync.cpp) (libSceAgc) - signature (cb, semaphoreAddr, operation, modifier, value) follows SharpProspero; writes 4-dword PM4 0x39 packet with control word 0x80100000 following console oracle measurements in prosper (cases agcbuild_CbMemSemaphore_a/b/c); operation, modifier and value are unused - [sceAgcAsyncCondExecPatchSetCommandAddress, sceAgcAsyncCondExecPatchSetEnd](../../../core/libs/prx/libSceAgc/Patch/src/CondExec.cpp) (libSceAgc) - no reference has a body for them; they are assumed to patch the COND_EXEC packet like `sceAgcCondExecPatchSetCommandAddress` and `sceAgcCondExecPatchSetEnd`, since `sceAgcAcbCondExec` writes the same packet as `sceAgcDcbCondExec`, as `sceAgcAsyncRewindPatchSetRewindState` does for REWIND -- [sceAgcAcbAtomicGds_0900](../../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - name from its NID (`gQkqkLttcpw`), signature unknown +- [sceAgcAcbAtomicGds_0900](../../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - name from its NID (`gQkqkLttcpw`) - [sceAgcDcbPrimeUtcl2](../../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime - [sceAgcDcbQueueEndOfShaderActionGetSize](../../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - the builder is inline in the SDK and not exported; the size is assumed to be that of the RELEASE_MEM packet `sceAgcCbReleaseMem` writes (32 bytes), the same as `sceAgcAcbQueueEndOfShaderActionGetSize`, as in [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) and [REmu](https://github.com/xnetcat/REmu/blob/main/crates/remu-runtime/src/agc_dcb.rs) -- [sceAgcUpdateInterpolantMapping_10000](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - name from its NID (`vieBRwlh1Lw`), not in the aerolib NID list; the `(regs, gs, ps)` arguments of `sceAgcCreateInterpolantMapping_0100` come from OpenAGC only - [sceAgcDcbSetZPassPredicationEnableGetSize, sceAgcDcbSetPredicationDisableGetSize, sceAgcDcbSetBoolPredicationEnableGetSize](../../../core/libs/prx/libSceAgc/DcbState/src/Predication.cpp) (libSceAgc) - the builders are inline in the SDK and not exported; the size is assumed to be that of the SET_PREDICATION packet `sceAgcDcbSetPredication` writes (16 bytes) -- [sceAgcUpdateInterpolantMapping_10000](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) follows [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c) only, not confirmed on hardware: the `sceAgcCreateInterpolantMapping_10000` transform without the identity tail, so it writes one register per pixel shader input and none for a pixel shader without inputs, and a null pixel shader throws. For an `is_f16` 1 or 3 input whose `default_value` and `default_value_hi` differ it throws: OpenAGC takes `DEFAULT_VAL` from `default_value_hi`, while KytyPS5, sharpemu and `sceAgcCreateInterpolantMapping_10000` here take it from `default_value` - [sceAgcAcbWaitUntilSafeForRendering](../../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - `(buffer, videoOutHandle, displayBufferIndex)`, the declared arguments, which PPSA12544 (01.000.010) passes inside `waitUntilSafeForRendering` and `waitUntilSafeForRendering VR` markers on an asynchronous compute command buffer, ignoring the result. It writes the same rendering-wait packet as `sceAgcDcbWaitUntilSafeForRendering`, which the driver now accepts on compute queues too; the wait blocks only that queue's worker. Whether the console encodes the compute variant differently is not known - [sceAgcGetDefaultCxStateFlat](../../../core/libs/prx/libSceAgc/Misc/src/RegisterDefaults.cpp) (libSceAgc) - copies up to `size` bytes (clamped to 2048 bytes, the 512-dword `AgcContextState`) from the default context state initialized from the version 8 public register default groups (CX, SH, UC), indexed by register offset, following OpenAGC `src/context_state.c`; returns `0x80890001` on null destination; version selection without `sceAgcInit` is unverified - [sceAgcDcbSetIndexIndirectArgs, sceAgcDcbSetIndexIndirectArgsGetSize](../../../core/libs/prx/libSceAgc/DcbDraw/src/IndexBuffer.cpp) (libSceAgc) - the `(buffer, attributeAddress, attributeIndex)` signature, a 64-bit address and a 32-bit index, is [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L1742-L1757)'s. The console oracle in prosper measured the packet, an INDEX_ATTRIBUTES_INDIRECT (opcode 0x91) holding the low address dword, the high address dword and the index ([measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/agcbuild.golden.tsv#L172-L174)), and a size of 16 bytes ([measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/agc.golden.tsv#L67)). The console clears the low four address bits and keeps the low 16 bits of the index; here a nonzero address that is not 16-byte aligned, or an index above 0xffff, throws, as `sceAgcDcbSetIndexBuffer` throws for a misaligned address. A null address is written as 0, as the console writes it. No title is known to call them. @@ -44,8 +41,12 @@ ### Functional - [sceAgcDebugRaiseException](../../../core/libs/prx/libSceAgc/Misc/src/Debug.cpp) (libSceAgc) - logs the exception id and returns 0, as retail hardware does nothing per [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c); the trap (`ud2`) OpenAGC describes for development hardware is not reproduced. Neither is verified on a console. The `uint32` exception id is [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/6f0cddd4404ec5203e71fea77e75fc373299ba3f/src/libs/agc.cpp)'s claim; OpenAGC declares no arguments, so the logged id may be leftover register contents -- [sceAgcSetAmmSemaphoreMemory, sceAgcGetSemaphoreLabel](../../../core/libs/prx/libSceAgc/Misc/src/Semaphore.cpp) (libSceAgc) - the `(memory, sizeInBytes)` and `(index, label)` signatures, the 16 KiB alignment of the memory and its size, the single registration, the 32-byte labels and the zeroed first 8 bytes of each are what [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L508-L546) and [sharpemu](https://github.com/sharpemu/sharpemu/blob/a2983692930564815a77f81e74c151268f5b935f/src/SharpEmu.Libs/Agc/AgcExports.AmmSemaphore.cs) agree on; KytyPS5 has neither function. OpenAGC clears the whole memory and sharpemu only those 8 bytes, so a registration whose labels hold data past them throws. The two return different errors for every rejected call (OpenAGC `0x8a6c0002`, `0x8a6c000b`, `0x8a6c0048` and `0x8a6c0049`, sharpemu generic invalid-argument and already-exists errors), so every rejected call throws, including a lookup before registration. Not measured on the console; PPSA02801 imports both (OpenAGC's import analysis), no title is shown calling them. -- [sceAgcDcbSetWorkloadsActive and sceAgcDcbSetWorkloadComplete](../../../core/libs/prx/libSceAgc/DcbState/src/Workload.cpp) (libSceAgc) and [sceAgcDriverRegisterWorkloadStream](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) (libSceAgcDriver) - the driver call records only the stream id (1 to 31; it returns 0 for any argument and keeps no stream record), and the DCB calls write the packets [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/agc.cpp) writes: an 18-dword (active) or 12-dword (complete) `IT_NOP` packet with the stream id, and for active the 64-bit mask of the workload ids, for complete the workload id and the 64-bit clear mask of that id. Both return null for a null command buffer, an unregistered or out-of-range stream, a workload id above 63, and for active a duplicate id or a count of 0 or above 63. The layouts and checks come from KytyPS5 alone; the console oracle in [prosper](https://github.com/mattias800/prosper/blob/main/prosper/tests/data/console_oracle/agcbuild.golden.tsv) measures the null return only for unregistered streams, not the packet contents of a registered one. The error codes the console driver returns for an invalid or repeated registration are unknown, so registration never fails here. PPSA29866 calls sceAgcDcbSetWorkloadsActive while building its command buffers (sceAgcDcbSetWorkloadComplete was not observed); nothing else was observed. +- [sceAgcSetAmmSemaphoreMemory, sceAgcSetSemaphoreMemory, sceAgcGetSemaphoreLabel](../../../core/libs/prx/libSceAgc/Misc/src/Semaphore.cpp) (libSceAgc) - the checks, error codes and clearing are the firmware's, but two firmware steps are not reproduced. After its checks, the firmware validates the memory with libkernel `qSleeVC6lwI`: an ioctl `0xc018ac15` taking `{memory, size, 1}`. The AMM call always does this; the non-AMM call only for memory from 8 TiB up that ends below 15 TiB. The firmware then sends `{7: memory, 8: size, 0xb: 0 for AMM, 1 otherwise}` through sceAgcDriverPassInfoDownward. Neither step happens here, so a registration that the kernel or driver would refuse succeeds. A null memory with a non-zero size and a range that wraps the address space throw where the firmware would fault +- [sceAgcDcbSetWorkloadsActive and sceAgcDcbSetWorkloadComplete](../../../core/libs/prx/libSceAgc/DcbState/src/Workload.cpp) (libSceAgc) - the DCB calls write the packets [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/agc.cpp) writes: an 18-dword (active) or 12-dword (complete) `IT_NOP` packet with the stream id, and for active the 64-bit mask of the workload ids, for complete the workload id and the 64-bit clear mask of that id. Both return null for a null command buffer, an unregistered or out-of-range stream, a workload id above 63, and for active a duplicate id or a count of 0 or above 63. The layouts and checks come from KytyPS5 alone; the console oracle in [prosper](https://github.com/mattias800/prosper/blob/main/prosper/tests/data/console_oracle/agcbuild.golden.tsv) measures the null return only for unregistered streams, not the packet contents of a registered one. PPSA29866 calls sceAgcDcbSetWorkloadsActive while building its command buffers (sceAgcDcbSetWorkloadComplete was not observed); nothing else was observed. - [sceAgcDcbSetWorkloadStreamInactive](../../../core/libs/prx/libSceAgc/DcbState/src/Workload.cpp) and [sceAgcAcbSetWorkloadsActive, sceAgcAcbSetWorkloadComplete, sceAgcAcbSetWorkloadStreamInactive](../../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - return null without writing to the buffer for a null command buffer or a stream that is not registered (out-of-range ids included), and throw for a registered stream. The console oracle in prosper measured only that null return, with no stream registered, three argument patterns each and nothing written ([ACB](https://github.com/mattias800/prosper/blob/df074498e56c0df22d74f9c6622e4709bc1c9a19/prosper/tests/data/console_oracle/agcbuild.golden.tsv#L48-L56), [DCB](https://github.com/mattias800/prosper/blob/df074498e56c0df22d74f9c6622e4709bc1c9a19/prosper/tests/data/console_oracle/agcbuild.golden.tsv#L196-L204)); the packet a registered stream gets is not measured. KytyPS5 has no body for these four, and its `IT_NOP` layout for the DCB active and complete calls differs from the SET_UCONFIG_REG, WRITE_DATA and SET_WORKLOAD sequence [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/agc_workload_packet.c) derives from the driver, so neither is taken. The `(buf, streamId)`, `(buf, streamId, workloadId)` and `(buf, streamId, workloadIds, workloadCount)` signatures are those of the DCB calls in [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L2317-L2384), which says the ACB calls use the same driver builders; no ACB declaration was found. `sceAgcDcbSetWorkloadsActive` checks the stream before it reads the workload ids, as OpenAGC does: the console returned null for stream 1 with the ids at address 2 ([measured](https://github.com/mattias800/prosper/blob/df074498e56c0df22d74f9c6622e4709bc1c9a19/prosper/tests/data/console_oracle/agcbuild.golden.tsv#L203)) without faulting +- [sceAgcDcbSetWorkload*](../../../core/libs/prx/libSceAgc/DcbState/src/Workload.cpp) and [sceAgcAcbSetWorkload*](../../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - the console's packets are not written: the executor has no ATOMIC_MEM or mask table. In `libSceAgc.sprx` (firmware 12.00, read from disassembly) all six write a SET_UCONFIG_REG to 0x342 (tag `0xCC000000 | stream`, or `0xCD000000 + workload * 32 + stream` for complete) and a WRITE_DATA to 0xC343, and active and complete add an ATOMIC_MEM (header `0xC0071E00`) on the stream's 64-bit mask. [Measured on a PS5](https://gist.github.com/orezraey/befe7632b827421988b47a6c04e89e1d) (firmware 13.60): WRITE_DATA dwords 5 to 8 are `0x06010000`, `0xC343`, 0 and the mask's high half (0 for inactive); the header low byte is 0x04 on the DCB SET_UCONFIG_REG and WRITE_DATA and 0x00 on the ACB ones; the table was at `0xFE003A000 + 8 * stream`; the pointer passed to `sceAgcDriverRegisterWorkloadStream` is never written - [sceAgcCreatePrimState](../../../core/libs/prx/libSceAgc/Shader/src/PrimState.cpp) (libSceAgc) - the `ge_user_vgpr_en` special is passed to the user configuration registers at whatever offset the shader header stores, as the SPRX does according to [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L1181-L1252), which copies all four specials verbatim (KytyPS5 stops on any other offset). PPSA99039, PPSA99505, PPSA99515 and PPSA99997 build their headers by hand with it at 0x25c (`GE_USER_VGPR1`) instead of 0x262 (`GE_USER_VGPR_EN`), value 0, and run on the console; it is not moved to 0x262, and the driver reads neither register. `ge_cntl`, `vgt_shader_stages_en` and a used `vgt_gs_out_prim_type` must still be at their own offsets, since the driver reads those registers there, so a header that stores one elsewhere throws naming the offset, although by OpenAGC the SPRX would copy it as well - [sceAgcDcbEventWrite](../../../core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp) (libSceAgc) - non-sample-dump event types (any event other than 0x38 and 0x39) emit a 2-dword PM4 packet 0x46 without address dwords; the address argument is ignored for these events. Void Terrarium (PPSA03061, #5055) passes a non-null address buffer (0x00000002d2a10000) with event type 0x16 (cache flush). + +- This pending declaration has an unverified signature and behavior; it is a parameterless import-resolution declaration that throws through `NotImplemented_nid_no_patch`: [`sceAgcAcbInitializeDefaultHardwareState`](../../../core/libs/prx/libSceAgc/Unimplemented.cpp). +- [sceAgcAcquireMemSetEngine, sceAgcCbMemsetExclusive](../../../core/libs/prx/libSceAgc/Unimplemented.cpp) (libSceAgc) - declared with canonical signatures from decrypted headers; behavior on retail consoles remains unmeasured. Both throw through `NotImplemented_nid_no_patch`. diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index c4ea080793..4976a17071 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -9,42 +9,52 @@ - [sceAgcDriverSubmitCommandBuffer, sceAgcDriverSubmitMultiCommandBuffers](../../../core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp) (libSceAgcDriver) - `queue_context` is treated as a pointer to context whose queue identifier is stored 4 bytes in, matching KytyPS5 (`src/libs/agc.cpp` `AgcDriverSubmitCommandBuffer` / `get_driver_queue`). A null context returns `EINVAL` (22). Whether the console validates other context fields before queue dispatch is unverified -- [sceAgcDriverAcquireHDRScopesACQ, sceAgcDriverGetCoredumpAddress, sceAgcDriverHp3dLockEnd, sceAgcDriverHp3dLockStart, sceAgcDriverIsEsvmProcess, sceAgcDriverNopDataWritePacket, sceAgcDriverReleaseHDRScopesACQ, sceAgcDriverSetTargetRingForDiag, sceAgcDriverSetValueToSharedGds, sceAgcDriverSetWorkloadStreamInactive, sceAgcDriverSubmitReprojectionAcb, sceAgcDriverSuspendPointSubmitCdbg, sceAgcDriverSysGetGfxAppGpuResetStatus](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - signatures and behavior are unverified; these parameterless declarations only resolve imports and throw through `NotImplemented_nid_no_patch`. +- [sceAgcDriverSubmitAcb, sceAgcDriverSubmitMultiAcbs](../../../core/libs/prx/libSceAgcDriver/Submit/src/Acb.cpp) (libSceAgcDriver) accept compute queues 0x20 to 0x57 and throw for any other. On a PS5 (firmware 13.40, measured 2026-10-19 from inside a retail title) `sceAgcDriverSubmitAcb` returned 0 and ran a label-only buffer on queues 0x20, 0x30, 0x40, 0x50, 0x57 and also 0x58, which throws here; queue 0x1F faulted inside the console's driver (SIGSEGV in libkernel on the calling thread), which throws here too. Queues above 0x58 and `sceAgcDriverSubmitMultiAcbs` were not measured. Closes when: the console's highest accepted compute queue is measured + +- [sceAgcDriverAcquireHDRScopesACQ, sceAgcDriverHp3dLockEnd, sceAgcDriverHp3dLockStart, sceAgcDriverIsEsvmProcess, sceAgcDriverReleaseHDRScopesACQ, sceAgcDriverSetValueToSharedGds, sceAgcDriverSetWorkloadStreamInactive, sceAgcDriverSubmitReprojectionAcb, sceAgcDriverSuspendPointSubmitCdbg, sceAgcDriverSysGetGfxAppGpuResetStatus](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - signatures and behavior are unverified; these parameterless declarations only resolve imports and throw through `NotImplemented_nid_no_patch`. +- [sceAgcDriverNopDataWritePacket](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - writes an IT_NOP packet with 32-bit address at dword 1 and payload at dword 2, returning 2 dwords for null data or zero bytes, and total dwords otherwise. Console behavior for null or unaligned destination remains unmeasured. +- [sceAgcDriverGetCoredumpAddress](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) returns the driver's shared coredump-area pointer, which is null when no coredump area is allocated. AnyPS5 reports coredump mode disabled and does not allocate this area; the export returns null. Enabling and populating an AGC coredump area is not implemented. +- [sceAgcDriverSetTargetRingForDiag](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) returns `0x8A6D0001` (`DRIVER_FEATURE_UNAVAILABLE`), matching the retail PS5 export. The retail diagnostic log message is not reproduced. - [Initial point context](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) - new queues and CLEAR_STATE seed PA_SU_POINT_SIZE 0x00080008 and PA_SU_POINT_MINMAX 0xffff0000, matching every public context table in libSceAgc/Command/include/RegisterDefaultsData.hpp. Cocoon (PPSA08766) reaches geometry point output without writing these registers in the observed startup. Applying the SDK context defaults to initial/CLEAR_STATE driver context is inferred; GPU power-on reset state is not measured and is not the source of these values. Explicit writes and pushed/restored contexts retain their own values. +- [Multisampled R_X and Z_X layouts](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTextureLayout.cpp) implement single-mip 2D arrays with 2, 4 or 8 equal samples and fragments, using the non-RB+ 16-pipe GFX10 equations from [AMD Addrlib](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10SwizzlePattern.h). R_X accepts 1/2/4/8/16-byte elements and Z_X accepts 1/2/4-byte elements. Tile and detile preserve element bytes and leave padding untouched; they do not synchronize resident images or publish compression metadata. Other modes, sample counts, element sizes and pipe/bank XOR bits outside the supported configuration throw. PS5 address-layout equivalence has not been measured. - [Optional BMP frame dumps](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) use checked host stdio. An open, header, pixel or close failure prints one diagnostic and disables subsequent BMP dumps for the process; queued frames drain without more file I/O. Pixel validation and allocation failures retain the existing fatal path. The CPU fixture checks identical BMP bytes and real pipe failures. VideoOut flips on an RTX 3060 continue after a first dump that cannot be opened; header, pixel and close failures during presentation are unmeasured. -- [Inactive vertex point size](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) - `PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE` is accepted for lines, triangles and rectangles with `GS_EN` and `HS_EN` clear, when polygon mode is disabled or both faces are filled. Points, patches and absent routing or rasterization registers still throw. Bit 16 selects the misc export's X component as PointSize ([AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf), PA_CL_VS_OUT_CNTL); ignoring it for non-point rasterization follows [Vulkan PointSize](https://docs.vulkan.org/refpages/latest/refpages/source/PointSize.html). This is inferred, not measured on console hardware. XashPS5 (PPSA19111 v1.3) sets `0x01210000` on a filled triangle draw. +- [Inactive vertex point size](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) - `PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE` is accepted for lines, triangles and rectangles with `GS_EN` and `HS_EN` clear, when polygon mode is disabled or both faces are filled. Patches and absent routing or rasterization registers still throw; point lists on that routing use it (next entry). Bit 16 selects the misc export's X component as PointSize ([AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf), PA_CL_VS_OUT_CNTL); ignoring it for non-point rasterization follows [Vulkan PointSize](https://docs.vulkan.org/refpages/latest/refpages/source/PointSize.html). This is inferred, not measured on console hardware. XashPS5 (PPSA19111 v1.3) sets `0x01210000` on a filled triangle draw. +- [Vertex-path point lists](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - throws for a register size of 0, a clamp maximum of 0 with `USE_VTX_POINT_SIZE`, unequal register width and height, a clamp minimum above its maximum, `USE_VTX_POINT_SIZE` without `VS_OUT_MISC_VEC_ENA`, and point sprite coordinates; sizes above one pixel need `largePoints`. Inferred, not measured: that `PA_SU_POINT_MINMAX` clamps the exported size and not the register size ([radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/gallium/drivers/radeonsi/si_state.c#L1047-1064)), and that an exported size of 0 or less covers nothing (here: the smallest point). A point list with `GS_EN` and `HS_EN` clear draws as Vulkan points, and the vertex program [writes](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) `PointSize`: the misc export's X, clamped, under `USE_VTX_POINT_SIZE`, else the `PA_SU_POINT_SIZE` height (12.4 fixed-point half sizes). +- [MEM_SEMAPHORE](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (AGC driver) - the semaphore is taken as a 64-bit word, as the 8-byte address alignment `sceAgcAcbMemSemaphore` checks suggests: a signal (operation 6) writes 1 with signal type 1 and adds 1 with signal type 0, and a wait (operation 7) polls until the word is non-zero and then subtracts 1. Signals and the decrement of a wait are atomic 64-bit operations on the guest word, so CPU threads and other queues updating it concurrently are not lost. Mailbox waits throw. The width and both signal types are inferred from the AMD PM4 field names, not measured on hardware; PPSA03396 signals with type 1 after compute dispatches. - [RELEASE_MEM of the system counter](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (AGC driver) - a `RELEASE_MEM` with `DATA_SEL = 4` throws: the frequency and epoch of the console's 64-bit system counter are not known. [amdgpu's GFX10 packet header](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/amdgpu/nvd.h) names the value "send 64bit sys counter value", beside 3, the GPU counter, which the driver writes as a 100 MHz count of the host's steady clock. An older KFD header names 4 a CP performance counter instead. A PS4 title through #6977 writes it at the end of each frame. - [Point and line polygon modes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - front and back polygon types that differ while both faces are drawn throw, as Vulkan has one `polygonMode` per draw. Points are drawn only one pixel wide (`PA_SU_POINT_SIZE = 0x00080008` inside `PA_SU_POINT_MINMAX`, the `VK_KHR_maintenance5` size of a point whose shader does not write `PointSize`); other sizes, a per-vertex size and devices without `maintenance5` throw. Lines must be one pixel wide (`PA_SU_LINE_CNTL`) and use Vulkan's default line rasterization. Depth bias with a point or line polygon mode, and such a mode on a rectangle list, throw. The fields follow the [AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf); the console's rasterization of these points and lines is not measured. - [Native multisampled depth clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accept the recognized HTILE mask kernel's direct full-range dispatch after its GPU writes complete. Every metadata byte needs tracked driver-store coverage since notification, no intervening CPU or depth write, and HTILE words proving the current clear value of zero or one. Partial, overlapping, indirect and unproven producers remain unsupported. Existing sampled views retain the native image; guest depth publication and metadata-fill transitions remain unimplemented. Windows shared imports retain write tracking through separate aliases; private host imports that lose tracking and copied or mirrored HTILE uploads remain unsupported. Clear-word encoding and console equivalence remain unmeasured on PS5 hardware. - [Native depth allocation retirement](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) covers sampling-eligible multisampled owners only. Successful release notifications must cumulatively cover both complete depth and HTILE ranges; protection changes, failed releases and partial coverage keep the original guards. Retired owners leave lookup and metadata routing, while held views and recorded work retain their images. Retirement of single-sample or sampling-ineligible depth attachments and console equivalence remain unimplemented or unmeasured. The regression uses real allocation-registry operations with a no-op host-release callback, so it does not validate the host unmap backend. - [Native MSAA depth attachment feedback](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) is not implemented. A draw sampling a native multisampled depth view while attaching its source image throws before pipeline creation or render-pass recording, including read-only attachment feedback. Sampling that image in a separate draw or dispatch remains supported. The refusal compares the held native image views and their device and attachment identity; single-sample depth behavior is retained. -- [Native multisampled depth sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accepts numeric D16 or D32, two/four/eight samples, one Z_X slice and mip, and pipe-aligned HTILE without stencil or VRS, with ITERATE_256 on or off: the initial import reads no compressed Z planes, and Mesa's HTILE clear words (`radv_get_htile_fast_clear_value`) and Addrlib's HTILE footprint do not depend on ITERATE_256, which `ac_descriptors.c` ties to TC reads of compressed MSAA HTILE. Sampling eligibility is recorded before the attachment's first host clear: every HTILE word must prove the same initial clear of zero or one, and the complete sample-aware depth and HTILE ranges must be accessible and write-watched. Unsupported initial state or device sampling support retains attachment rendering but sampling throws; eligibility is never retried. Sampling rejects later guest writes, queued external GPU writes, and unproven HTILE clear/fill transitions. Guest publication, arbitrary guest depth import, stencil/raw-bit sampling, arrays and storage access remain unimplemented. The block footprint follows main's AMD Addrlib thin-block rule; the accepted HTILE clear words and PS5 equivalence are not measured on console hardware. PPSA99902's historical resident eight-sample D32 reader is documented in [#419](https://github.com/boykopovar/AnyPS5/pull/419#issuecomment-6105597729). +- [Native multisampled depth sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accepts numeric D16 or D32, two/four/eight samples, one Z_X slice and mip, and pipe-aligned HTILE without stencil or VRS, with ITERATE_256 on or off: the initial import reads no compressed Z planes, and Mesa's HTILE clear words (`radv_get_htile_fast_clear_value`) and Addrlib's HTILE footprint do not depend on ITERATE_256, which `ac_descriptors.c` ties to TC reads of compressed MSAA HTILE. Sampling eligibility is recorded before the attachment's first host clear: every HTILE word must prove the same initial clear of zero or one, and the complete sample-aware depth and HTILE ranges must be accessible and write-watched. Unsupported initial state or device sampling support retains attachment rendering but sampling throws; eligibility is never retried. Sampling rejects later guest writes, queued external GPU writes, and unproven HTILE clear/fill transitions. A guest read over the depth or HTILE range of a surface the host has not written since import (draws with depth or stencil writes, depth/stencil copies, storage write-back; not clears after a proven HTILE clear) retires it instead of throwing; that a draw without those writes leaves the console's Z planes and HTILE unchanged is inferred, not measured. Guest publication of a written surface, arbitrary guest depth import, stencil/raw-bit sampling, arrays and storage access remain unimplemented. The block footprint follows main's AMD Addrlib thin-block rule; the accepted HTILE clear words and PS5 equivalence are not measured on console hardware. PPSA99902's historical resident eight-sample D32 reader is documented in [#419](https://github.com/boykopovar/AnyPS5/pull/419#issuecomment-6105597729). - [sceAgcDriverSetSubmitValidationMode, sceAgcDriverGetSubmitValidationMode, sceAgcDriverSetSubmitValidationConfig, sceAgcDriverGetSubmitValidationConfig, sceAgcDriverSetValidationErrorOutputFrequency, sceAgcDriverGetShaderDebuggingStatus](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - the five validation calls return `0x8A6C1000` without reading their arguments or writing caller memory, and `sceAgcDriverGetShaderDebuggingStatus` returns 1, as [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L250-L291) reads them from Sony's libSceAgcDriver.sprx: each validation export is `mov eax, 0x8a6c1000; ret` in the copy Dragon Quest VII Reimagined bundles (0x7bb0-0x7bf0), and the debugging status is 1 in the firmware 5.50 library (0x6740). The parameters (a 32-bit mode or frequency, a mode output, a configuration pointer) follow OpenAGC and are never read. Whether later retail firmware enables submit validation or shader debugging is unverified; no title in this repository is known to call them - [sceAgcDriver retail constant results](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - the debug, capture, GPU printf, register access, GPU reset, suspend point and user data exports taken from the retail `libSceAgcDriver.sprx`, and `sceAgcDriverRegisterOwnerForSystem`, `sceAgcDriverInitResourceRegistrationInterface` and `sceAgcDriverSetResourceRegistrationInfo` in [Registration.cpp](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp), are declared without parameters: the console bodies return a constant without reading any argument, so their signatures are unknown. `sceAgcDriverAllocateToolMemoryForGpuReset` and `sceAgcDriverGetAllocatedToolMemoryForGpuReset` read only the output pointer (fourth and second argument, a 12-byte range the console zeroes); the other arguments are declared as `uint64_t`. - [Indices far past every vertex fetch](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - when a draw's highest index is at least 64Ki vertices past the largest per-vertex record count, every index past that count is moved to it in a copy of the indices, so the zero-padded vertex copies are not sized to the original index (PPSA04159 draws a list of `0xffffffff` with primitive restart off, which asked for 160 GiB). Each attribute fetched by vertex index reads zeros there as at the original index (out-of-bounds select 0 or 1); a draw with another out-of-bounds select keeps its indices. The vertex index the shader sees changes for the moved indices, which a shader that reads it directly would notice +- [Depth bounds without the depthBounds feature](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - on a device without `depthBounds` (MoltenVK), a depth bounds draw with no depth test or write, a 32-bit float depth target, a pixel program that neither reads nor exports depth, and no stencil write on a failed test is drawn with a read-only depth test against one bound pinned as the viewport depth range: `GREATER_OR_EQUAL` against the upper bound when the lower one is 0, `LESS_OR_EQUAL` against the lower bound when the upper one is 1. This takes stored depth to stay in [0, 1], as it does without `VK_EXT_depth_range_unrestricted`. Both bounds inside (0, 1), depth testing, normalized depth targets and the other cases still need the feature and throw. - [Depth clear draws](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a draw with `DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLE` is taken to write `DB_DEPTH_CLEAR` to every pixel it covers whatever the depth test, depth bounds and depth bias say: it is drawn with an always-passing depth write and the viewport depth range pinned to the clear value. The draw is rasterized as submitted, so its coverage is the draw's own. Whether the DB still applies the depth test or HiZ to such a draw is not measured - [GS_FAST_LAUNCH geometry](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH = 1` with a point list and `GE_CNTL` groups of one vertex and one primitive decodes as one point per subgroup, with the primitive limit taken from `GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR` and the GS/ES VGPR counts not required. Follows how RADV drives mesh shaders on gfx10.3 (`ac_nir_lower_ngg.c`, the workgroup index comes from the vertex id of the first thread), not measured on the console. Fast launch 2, other group sizes or input primitives and indexed fast-launch draws still throw. PPSA03671's fast-launch draws decode, but the title stops earlier in a wave64 vertex program, so they have not reached the GPU yet. -- [Point-list geometry output](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a geometry program with `VGT_GS_OUT_PRIM_TYPE = 0` (point list) runs as a mesh shader with `OutputPoints` and scalar `PrimitivePointIndicesEXT`. Each primitive export emits one point whose vertex index is extracted from bits [8:0] of the packed word. A subgroup gets up to `maxVertices` points, with provoking vertex 0 and point raster primitive topology. Rectangle lists, per-stream types and point or line strips from a vertex program on the mesh path still throw. +- [Point geometry output](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a geometry program with `VGT_GS_OUT_PRIM_TYPE = 0` emits mesh points with `OutputPoints` and scalar `PrimitivePointIndicesEXT` as required by [SPV_EXT_mesh_shader](https://github.khronos.org/SPIRV-Registry/extensions/EXT/SPV_EXT_mesh_shader.html). Each packed primitive export contributes its first 9-bit vertex index and its null bit, using the same NGG primitive packing as line and triangle outputs. The subgroup reserves at least `GE_MAX_OUTPUT_PER_SUBGROUP` point slots, as the line path does; the console budget is not measured. Points use the register size or the misc-X point-size export and clamp interpreted by the vertex-point path; those register, clamp and rasterization inferences remain unmeasured on the console. Point sprites remain unsupported. Point output from a vertex workgroup still throws. Cocoon (PPSA08766) reaches this path with triangle-strip input. - [Line-strip geometry output](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a geometry program with `VGT_GS_OUT_PRIM_TYPE = 1` (line strip) runs as a mesh shader with `OutputLinesEXT`: each primitive export is one line whose two vertex indices are packed like the three of a triangle, as Mesa's NGG lowering exports line strips (`ac_nir_lower_ngg_gs.c`), and each input primitive gets `VGT_GS_MAX_VERT_OUT - 1` lines where a triangle strip gets `- 2` triangles. A subgroup gets at least `GE_MAX_OUTPUT_PER_SUBGROUP` line slots; whether the console does the same is unmeasured. A fast-launch subgroup takes its line count from `GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR`. Line output counts as lines for polygon offset decode. The lines are drawn 1 pixel wide with Vulkan default rasterization; `PA_SU_LINE_CNTL` is not read, and console line rules are not measured. -- [Alpha-to-coverage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - only `DB_ALPHA_TO_MASK = 0xaa01` is accepted: the enable bit with the four quad offsets at 2 and no `OFFSET_ROUND`, the undithered setting [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/gallium/drivers/radeonsi/si_state.c#L371-L381) programs. Draws are single-sampled, so the pixel shader [writes](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) `SampleMask` 0 when its MRT0 alpha is below one half or NaN and all ones otherwise, and runs with late fragment tests. That rule was measured on a Radeon 660M (gfx1035, RADV with `RADV_DEBUG=noatocdithering`, which programs the same register) for 0, -0, 0.25, the two floats below one half, one half, the two above, 0.75, 1, 2, -1, both infinities and both NaNs, and is pinned in [AlphaToCoverage.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp); it is not measured on gfx1013. Vulkan's `alphaToCoverageEnable` is not used: the NVIDIA driver 615.78.08 dithers single-sample coverage (an alpha of 0.25 writes half the pixels of an RTX 3050 Laptop target). Other offsets, `OFFSET_ROUND`, a sample-mask export, and an MRT0 that is missing, swizzled, packed or exported without a floating-point alpha throw. A draw without a color target on MRT0 keeps alpha-to-coverage from its MRT0 export, as [radeonsi exports MRT0 as `32_AR` for alpha-to-coverage without a color buffer](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/gallium/drivers/radeonsi/si_state_shaders.cpp#L2425-L2431). A draw that enables alpha-to-coverage with no MRT0 export still throws: what the console covers then is not measured. Default dithered `DB_ALPHA_TO_MASK = 0x18701` additionally uses a per-quad sample-mask table [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/4451#issuecomment-6124848921) for eight standard samples with FP16_ABGR or 32_ABGR exports; other sample counts, custom locations, EQAA combinations and dithered UNORM16/SNORM16 exports throw. The dithered rule is not measured on gfx1013. -- [A one-sample target under a multisampled rasterizer without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear, one-sample color targets and no depth target, or a one-sample depth target with no color target or one-sample color targets, is drawn as a one-sample draw at the pixel center. Without the bit every sample is taken to be at the pixel center (see the entry for a multisampled target rasterized without MSAA_ENABLE below), and a one-sample target is taken to keep the coverage of sample 0, so a one-sample depth target is tested and written once per pixel with the depth at the pixel center. That is the rule [`VK_AMD_mixed_attachment_samples`](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/fragops.adoc#L2510-L2516) gives a color target with fewer samples than the rasterizer, but Vulkan reaches it only beside a depth or stencil target with the rasterizer's sample count ([VUID-VkGraphicsPipelineCreateInfo-subpass-01505](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/pipelines.adoc#L3033-L3041)), so here it is an analogy, and for a one-sample depth target an extension of it. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp#L127-L128) also rasterizes with the largest sample count of the bound depth and color targets ([L416-L417](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L416-L417), [L477-L480](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L477-L480)), and reads `PA_SC_AA_CONFIG` only for a `SAMPLE_COVERAGE` input ([L174-L177](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L174-L177)). This is not measured on hardware. An `ANCILLARY` or `SAMPLE_COVERAGE` pixel input and a sample-mask export throw in a pixel program that runs, and so does `PS_ITER_SAMPLE` with one-sample color targets and no depth target, because what the console gives them under a multisampled configuration is not known; a depth-only draw that skips its pixel program is drawn with the null program, and its `PS_ITER_SAMPLE` and pixel input bits are ignored. With a one-sample depth target, a pixel program with `PS_ITER_SAMPLE` and neither input runs once per pixel; if the console runs it once per sample, its atomics and append or consume counters count differently. Points, lines and alpha-to-coverage throw as for a multisampled target. With a one-sample depth target, `DB_EQAA` Z interpolation (`INTERPOLATE_COMP_Z`, `INTERPOLATE_SRC_Z`) or overrasterization (`OVERRASTERIZATION_AMOUNT`, `ENABLE_POSTZ_OVERRASTERIZATION`) also throws. METAL GEAR SOLID: Peace Walker (PPSA18216) draws a one-sample color target under a 4x configuration this way; the first draw of its opening movie binds a one-sample depth target and no color target under the same configuration. +- [Alpha-to-coverage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `DB_ALPHA_TO_MASK = 0xaa01` is accepted: the enable bit with the four quad offsets at 2 and no `OFFSET_ROUND`, the undithered setting [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/gallium/drivers/radeonsi/si_state.c#L371-L381) programs. Draws are single-sampled, so the pixel shader [writes](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) `SampleMask` 0 when its MRT0 alpha is below one half or NaN and all ones otherwise, and runs with late fragment tests. That rule was measured on a Radeon 660M (gfx1035, RADV with `RADV_DEBUG=noatocdithering`, which programs the same register) for 0, -0, 0.25, the two floats below one half, one half, the two above, 0.75, 1, 2, -1, both infinities and both NaNs, and is pinned in [AlphaToCoverage.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp); it is not measured on gfx1013. Vulkan's `alphaToCoverageEnable` is not used: the NVIDIA driver 615.78.08 dithers single-sample coverage (an alpha of 0.25 writes half the pixels of an RTX 3050 Laptop target). Other offsets, `OFFSET_ROUND`, a sample-mask export, and an MRT0 that is missing, swizzled, packed or exported without a floating-point alpha throw. A draw without a color target on MRT0 keeps alpha-to-coverage from its MRT0 export, as [radeonsi exports MRT0 as `32_AR` for alpha-to-coverage without a color buffer](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/gallium/drivers/radeonsi/si_state_shaders.cpp#L2425-L2431). A draw that enables alpha-to-coverage with no MRT0 export still throws: what the console covers then is not measured. Default dithered `DB_ALPHA_TO_MASK = 0x18701` additionally uses a per-quad sample-mask table [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/4451#issuecomment-6124848921) for eight standard samples with FP16_ABGR or 32_ABGR exports; other sample counts, custom locations, EQAA combinations and dithered UNORM16/SNORM16 exports throw. The dithered rule is not measured on gfx1013. +- [A one-sample target under a multisampled rasterizer without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear, one-sample color targets and no depth target, or a one-sample depth target with no color target or one-sample color targets, is drawn as a one-sample draw at the pixel center. Without the bit every sample is taken to be at the pixel center (see the entry for a multisampled target rasterized without MSAA_ENABLE below), and a one-sample target is taken to keep the coverage of sample 0, so a one-sample depth target is tested and written once per pixel with the depth at the pixel center. That is the rule [`VK_AMD_mixed_attachment_samples`](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/fragops.adoc#L2510-L2516) gives a color target with fewer samples than the rasterizer, but Vulkan reaches it only beside a depth or stencil target with the rasterizer's sample count ([VUID-VkGraphicsPipelineCreateInfo-subpass-01505](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/pipelines.adoc#L3033-L3041)), so here it is an analogy, and for a one-sample depth target an extension of it. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp#L127-L128) also rasterizes with the largest sample count of the bound depth and color targets ([L416-L417](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L416-L417), [L477-L480](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L477-L480)), and reads `PA_SC_AA_CONFIG` only for a `SAMPLE_COVERAGE` input ([L174-L177](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L174-L177)). This is not measured on hardware. An `ANCILLARY` or `SAMPLE_COVERAGE` pixel input and a sample-mask export throw in a pixel program that runs (with a one-sample depth target, `ANCILLARY` only under `PS_ITER_SAMPLE`), because what the console gives them under a multisampled configuration is not known; a depth-only draw that skips its pixel program is drawn with the null program, and its `PS_ITER_SAMPLE` and pixel input bits are ignored. A pixel program with `PS_ITER_SAMPLE` and neither input runs once per pixel; if the console runs it once per sample, its atomics and append or consume counters count differently. With a one-sample depth target and without `PS_ITER_SAMPLE`, the [packed ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) gives a program that reads `ANCILLARY` sample index 0, inferred for a pixel the guest does not iterate, and that is the sample the one-sample draw keeps. Points and alpha-to-coverage throw as for a multisampled target. With a one-sample depth target, `DB_EQAA.INTERPOLATE_COMP_Z` is accepted: with every sample at the pixel center, Z interpolated to any sample position is the center's Z, which the one-sample draw tests. That rests on the same inference; neither [PAL](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9MsaaState.cpp#L117-L118) nor [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/761d5c8a54f93b3d43608f718910ed742983dd0b/src/gallium/drivers/radeonsi/si_state.c#L3314-L3316) sets the bit, and its meaning is not documented. Line and point lists (`VGT_PRIMITIVE_TYPE` 1-3, even when a geometry program turns them into triangles), `INTERPOLATE_SRC_Z` and overrasterization (`OVERRASTERIZATION_AMOUNT`, `ENABLE_POSTZ_OVERRASTERIZATION`) still throw. METAL GEAR SOLID: Peace Walker (PPSA18216) draws a one-sample color target under a 4x configuration this way; the first draw of its opening movie binds a one-sample depth target and no color target under the same configuration. Gran Turismo 7 (PPSA01317) draws its main pass with a one-sample depth target, `DB_EQAA` 0x00050000 and a 4x configuration. +- [One-fragment multisampled color target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a color target with `CB_COLOR_ATTRIB` 4 samples and 1 fragment is decoded as one sample that keeps its coverage sample count and draws sample 0's coverage. Measured only on radeonsi GL (Radeon 610M and 680M); console behaviour is not measured. It requires static `DB_EQAA` anchors at the depth target's sample count and CMASK code 0 or 0xc-0xf. More than one but fewer fragments than samples, more fragments than samples, other CMASK codes and any guest read of the FMASK of a target the host drew still throw. - [MSAA_ENABLE on a single-sample target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_MODE_CNTL_0.MSAA_ENABLE` with `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES = 0` is drawn like the same state without the bit when sample 0 of the four `PA_SC_AA_SAMPLE_LOCS_PIXEL_*_0` registers is at the pixel center and the draw rasterizes filled triangles or rectangles (`VGT_PRIMITIVE_TYPE` 7 or 17 as well as the triangle types) with alpha-to-coverage and conservative rasterization off. [RADV](https://gitlab.freedesktop.org/mesa/mesa/-/blob/745709a000482a4a24c91faef5c5a40bd653a863/src/amd/vulkan/radv_cmd_buffer.c#L13606-13609) programs this state for one-sample custom sample locations on GFX10 and later, where the bit makes the rasterizer use the programmed location of sample 0 ([061bc615](https://gitlab.freedesktop.org/mesa/mesa/-/commit/061bc6151a11597f720535f00e79a5752e24aff6)); that the bit changes nothing else for such a draw is inferred from that and is not measured on hardware. EA Sports UFC 5 (PPSA03541) reaches a rect-list draw in this state. Other locations of sample 0, absent location registers, points, lines, alpha-to-coverage and conservative rasterization throw -- [A multisampled target rasterized without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear is drawn with the target's sample count and every sample at the pixel center, as a single-sample scan into a multisampled target already is. On GFX10 and later the bit is what makes the rasterizer use the programmed sample locations ([RADV 061bc615](https://gitlab.freedesktop.org/mesa/mesa/-/commit/061bc6151a11597f720535f00e79a5752e24aff6)), and with multisampling disabled GL rasterizes at the pixel center and gives the fragment full coverage, which radeonsi programs by clearing the bit; that the hardware does the same with `MSAA_NUM_SAMPLES` > 0 is inferred from these and is not measured on hardware. A depth target with the same sample count also has every sample at the pixel center, so each depth and stencil sample is tested and written with the depth at the pixel center, as GL allows when multisampling is disabled ([L185-L190](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089ccfee87efe230ebcdd8742859813/extensions/ARB/ARB_multisample.txt#L185-L190), [L422-L425](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089ccfee87efe230ebcdd8742859813/extensions/ARB/ARB_multisample.txt#L422-L425)). GL also lets an implementation test only a centermost sample and set every sample from it ([L425-L434](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089ccfee87efe230ebcdd8742859813/extensions/ARB/ARB_multisample.txt#L425-L434)), which differs when a pixel's stored samples differ; which one the console does, and how its occlusion queries count such a draw, is not measured on hardware. `DB_EQAA` Z interpolation or overrasterization and a sample-mask export with a depth target throw. Points, lines and alpha-to-coverage throw. SONIC SUPERSTARS (PPSA06888) draws such a 2x target, and METAL GEAR SOLID: Peace Walker (PPSA18216) a 4x depth and color pair. +- [A multisampled target rasterized without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - not measured on hardware: `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear puts every color, depth and stencil sample at the pixel center, as in GL without multisampling ([ARB_multisample](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089/extensions/ARB/ARB_multisample.txt#L185), [RADV 061bc615](https://gitlab.freedesktop.org/mesa/mesa/-/commit/061bc6151a11)). Lines cover whole pixels by the host's line rule, not the console's aliased one ([D3D11.3](https://github.com/microsoft/DirectX-Specs/blob/f37b0d6033a1/d3d/archive/D3D11_3_FunctionalSpec.htm#L13705)). Depth sample tests and occlusion counts are unknown. Rasterized points, alpha-to-coverage, and Z interpolation, overrasterization or a sample-mask export with depth throw. PPSA06888 draws a 2x target, PPSA18216 a 4x depth and color pair and 4x lines. - [sceAgcDriverGetEqContextId](../../../core/libs/prx/libSceAgcDriver/Eq/src/Query.cpp) (libSceAgcDriver) - returns `ident`: an end-of-pipe interrupt is delivered only to the registration whose id equals the raising queue, so `ident` is that context's id (titles register 0 or 1 for graphics and `0x20 + 8 * pipe + queue` for compute). The console's value is not known; KytyPS5 returns the event's `data`, which it fills with the interrupt's context id, whereas `data` here is the EV_CLEAR trigger count. Other event filters throw - [User data registers](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Diagnostics.cpp) (AGC driver) - a `SPI_SHADER_USER_DATA_*` or `COMPUTE_USER_DATA_*` register the title never wrote reads as 0 when a stage's RSRC2 count covers it, as Kyty (zeroed `UserSgprInfo`) and SharpEmu (missing registers read 0) do, and as AMD PAL and Mesa initialize them; the console's value is not confirmed - [SET_PREDICATION](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (AGC driver) - boolean predication follows the AMD PM4 encoding [Mesa](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/common/sid.h) uses: operation 3 reads 64 bits and 4 reads 32 bits, and bit 8 runs predicated packets when the value is non-zero (clear: when it is zero); the bit 12 hint is ignored. The address must have its low 4 bits clear, as `sceAgcDcbSetPredication` writes it. Query predication (Z-pass, primitive count), a predicated COND_EXEC, predicated command buffer chains and flips in predicated command buffers throw - [Barycentrics without mesh shaders](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp) (AGC driver) - a pixel shader that reads barycentrics on the mesh path is emulated only on a device with `VK_EXT_mesh_shader`; a device with neither it nor `VK_KHR_fragment_shader_barycentric` (reported on Linux RADV with a GCN 5 GPU) throws that the device has neither the barycentric extension nor mesh shaders, because a geometry stage cannot follow a mesh stage and no other route is implemented. `VK_AMD_shader_explicit_vertex_parameter`, which a GCN 5 device on RADV exposes, is an unexplored option. -- [NGG vertex programs on the mesh path](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - an unregistered vertex program with `PRIMGEN_EN` that uses LDS or `s_barrier` runs on the geometry (mesh) path. Each input primitive is assumed to yield at most one output primitive, the subgroup is bounded by `GE_MAX_OUTPUT_PER_SUBGROUP` as a vertex budget, and the GS/ES VGPR count checks are skipped, because the merged inputs keep their positions. Taken from the code Mesa's ACO emits for NGG culling and from PPSA59322 and PPSA99010 draws, not measured on hardware. Registered programs keep the vertex path +- [NGG vertex programs on the mesh path](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a vertex program with `PRIMGEN_EN` that uses LDS or `s_barrier` runs on the geometry (mesh) path. Each input primitive is assumed to yield at most one output primitive, the subgroup is bounded by `GE_MAX_OUTPUT_PER_SUBGROUP` as a vertex budget, and the GS/ES VGPR count checks are skipped, because the merged inputs keep their positions. Taken from the code Mesa's ACO emits for NGG culling and from PPSA59322 and PPSA99010 draws, not measured on hardware. - [VGT_SHADER_STAGES_EN 0x02002008](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - this one value, `ES_EN` = 1 without `HS_EN` on a non-patch draw, is drawn as the vertex path. AMD's CIK register reference calls `ES_EN` = 1 `ES_STAGE_DS` (the ES stage runs the tessellation evaluation shader), which does not explain it on a non-patch draw. Seen only on PPSA29413 and not verified on the console; other vertex-path values with `ES_EN` set still throw. - [Strided buffer overhang](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a strided buffer descriptor that ends less than one stride past the end of the readable registered allocation holding its base is bound only up to that allocation's end, even when another readable allocation starts right there. This assumes the bytes past the end are the rounded-up `num_records` of the last record (RADV rounds it up), which no fetch inside the buffer reads; a guest that really keeps a vertex split across two adjacent allocations would read zeros for the cut tail. The same holds for a read-only strided buffer that starts below the render target and runs less than one stride into it: it is bound only up to the target, as a vertex fetch whose attribute descriptor starts at the attribute's offset reaches that far past the attribute's last bytes. Taken from the RetroArch Dolphin core case in #2912, not measured on hardware. - [Buffer descriptor CACHE_SWIZZLE hint](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - bit 30 of word 1 in a buffer resource descriptor (`words[1] & 0x40000000u`) is normalized to zero when binding guest buffers in `ShaderResources::addGuestBuffer`. On GFX10 (gfx1013), bit 30 of V# word 1 is `CACHE_SWIZZLE` (Mesa's `gfx10-rsrc.json`: STRIDE [29:16], CACHE_SWIZZLE [30], SWIZZLE_ENABLE [31]), a cache-bank hint with no effect on the address or data, which is dropped here. - [Buffer descriptors past the range limit](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a buffer V# whose size is over `maxStorageBufferRange` is bound only up to the end of the readable registered allocation holding its base when that allocation ends before the limit, and as the empty placeholder buffer, like a null V#, when no registered allocation holds its base; a V# over an allocation that reaches the limit still throws. Titles leave such words in descriptor slots their shaders do not reach on the path taken (PPSA10528 binds `c8010002 2c6bec20 c0053c00 04000013`, about 33 TB from an address above the user address space). What the console returns for an access past the bound range is not known; here the device's robust buffer access decides. Follows the review of #3202, not measured on hardware. - [Vertex fetch overhang](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - a zero-padded vertex fetch whose valid range (`num_records * stride`) ends less than one stride past the end of the readable memory at its base copies only the readable bytes and reads the rest as zeros, instead of failing the draw; a longer overhang still fails. This extends the strided buffer overhang rule above (the last record is the rounded-up `num_records`) to the staging copy and is not measured on hardware. The observed case is a fetch of 14858 records of 44 bytes that ends 8 bytes past the end of the 0xa0000-byte mapping holding it. -- [Zero-stride vertex fetch range](../../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - for a stride-0 V# with OOB_SELECT 2 the whole fetch reads memory unless NUM_RECORDS is 0; with OOB_SELECT 3 it reads zero when NUM_RECORDS is 0 and throws when the fetch only partly fits. Rules taken from the GFX10 OOB_SELECT table in Mesa [ac_descriptors.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/common/ac_descriptors.c), not measured on hardware. Whether OOB_SELECT 3 checks each dword of a multi-dword fetch separately is unknown. OOB_SELECT 0 and 1 keep the structured record checks. +- [Zero-stride vertex fetch range](../../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - a draw whose highest index is at or past a nonzero NUM_RECORDS of a stride-0 `OOB_SELECT` 1 V# throws: the pipeline binding has stride 0, so vertices cannot differ. Only the highest index is checked, so a draw with every index past it throws too, where the hardware reads zero. An indirect draw is not checked. Unmeasured on gfx1013 (the console), with an instruction offset or SOFFSET, with a swizzled V# and in a vertex shader. Rules from #8299, measured on an RX 6700 XT (gfx1031), same results on gfx1010, with raw ISA ([data](https://gist.github.com/thuanquang/3b25d677c1a32d79123dd2df1819dd83)). Stride 0 reads zero for `OOB_SELECT` 0 always; for 1 when index >= NUM_RECORDS (records); for 2 when NUM_RECORDS is 0; for 3 when NUM_RECORDS is under the fetch's bytes. Untyped loads check each dword under 3; only typed fetches are used here. - [Resource registration](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) (libSceAgcDriver) - registration always fails with `RESOURCE_REGISTRATION_UNAVAILABLE`, so the owner and resource queries, `sceAgcDriverSetResourceUserData`, `sceAgcDriverFindResourcesPublic` and `sceAgcDriverUnregisterAllResourcesForOwner` return the same error without reading their arguments. Their parameter lists follow the libSceGnmDriver counterparts and are unverified. `sceAgcDriverGetResourceRegistrationMaxNameLength` writes 0xfc and returns 0, as [prosper](https://github.com/mattias800/prosper) does from a title's registration configuration; the `uint32_t*` parameter matches the SDK wrapper `sce::Agc::ResourceRegistration::getMaxNameLength(unsigned int*)`. sharpemu writes 256 and KytyPS5 returns `RESOURCE_REGISTRATION_UNAVAILABLE`, so the value is unverified; a null output throws - [sceAgcDriverRegisterMultipleResources](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) uses the existing unavailable-registration state and leaves caller memory untouched. Its four arguments (uint32 handle output, uint32 owner, descriptor pointer, uint32 count) follow the original PPSA34502 / 01.010.000 codzm call. The caller builds 40-byte descriptors and explicitly accepts 0x8A6C9018 with an invalid output handle. Enabled registration and console argument-validation precedence are unverified. - [Shader headers](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - registration and ABI resolution read fixed header fields through aligned copies, so the header pointer itself may have any alignment. PPSA26344 passes headers 4 and 1 bytes past an 8-byte boundary, and PPSA01317 passes 4-byte-aligned headers to ABI resolution; console alignment requirements are unknown @@ -61,24 +71,33 @@ ### Functional +- [Written staging window on discrete GPUs](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) - a written buffer element of a dispatch that is not swept is staged device-local up to 8 MiB on a discrete GPU (`Context::discreteGpu`) and up to 2 MiB otherwise; `APS5_WRITTEN_SHADOW_MAX_KIB` replaces either bound. The 8 MiB figure comes from one title on one NVIDIA Windows system (PPSA09911: a 2.5 and a 3.3 MiB skin-cache output written in place cost 55 ms of GPU time per frame, 1.8 ms of extra staging copies once staged); the point where the two copies of a larger element cost more than its in-place stores, and whether AMD or Linux drivers move it, has not been measured. The window is per element and per use, so a kernel that streams once through an element inside the window pays the copy-in and the copy-back on every dispatch. +- [Frame capture](../../../core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp) (libSceAgcDriver) records guest memory on Windows only (a VirtualQuery walk of the registry allocations and the Windows write tracker); elsewhere a capture throws when it starts. Ranges are captured by allocation and protection, not by mapping: section views, aliases of one direct memory block and file views are snapshotted and replayed as separate private memory, so a store through one alias is not seen through another, and CPU stores to them during the captured frames are not recorded (only write-watched private memory is). Pages of the title's image are recorded during the frames only when a shader header or an array it points to lies in them and their content changed. A capture starts only at a frame boundary where the driver goes idle within 10 s, and gives up after five boundaries that do not. A capture also stops when GPU work in flight at a delta waits on a later CPU store. +- [Frame replay](../../../core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp) (libSceAgcDriver) orders the calls of different captured threads only by their start (a call starts once every earlier call has started), so the driver sees a schedule the title could have produced, not necessarily the one it did. CPU stores a title made from threads that never call the driver are known only by the next recorded call, so the replay applies them early or late when a GPU wait needs them and derives a value from the wait's reference when no recorded store satisfies it (`of_them_synthesized`). Pacing compares the number of submissions in flight, not what they hold. Prologue registrations of shaders whose memory the title reused after registering them fail and are counted (`stale_registrations`). The title's image range must be free in the replaying process, so the tool is linked at a high image base on Windows. - [Workgroup count split](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (libSceAgcDriver) - a direct dispatch over `maxComputeWorkGroupCount` runs as `vkCmdDispatch` chunks, each bound to a pipeline variant whose workgroup offset specialization constants add the chunk's base to `WorkgroupId` and `GlobalInvocationId`. A variant is built when its chunk base is first recorded, not when the dispatch is prepared. A split of a shader with device-memory LDS throws: its slot base reads the chunk's `NumWorkgroups`, not the dispatch's. Chunks are ordered by a compute barrier, which the console does not have between workgroups of one dispatch. +- [Experimental submission progress](../../../core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionProgress.hpp) (AGC driver) - the opt-in two-millisecond pacing budget is not derived from console hardware. Cross-title performance remains unvalidated; host scheduling can overshoot the timeout and pacing can reduce CPU/GPU overlap. +- [GPU allocation recovery](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - recovery may free too little and still throw: caller-held sampled images, pending storage results and recorder references stay alive. Budgets remain floors rather than hard device limits. Image and buffer allocation failures trim idle buffers, then sampled and clean storage cache entries. A held cache lock is reused only for a new image's allocation after lookup iterators are no longer used; hit refreshes and flushes skip that cache. Other cache, resource and device locks are tried without waiting. - [Scan-resistant sampled texture admission](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - while the sampled texture cache is over its budget but within a quarter over it, the first use of a texture it does not hold in a present is refused (used for its draw, then released, never cached) when its least recently used entry was used in the current or previous present, and a later use in the same present is refused when that entry was used in the current present. So a working set larger than the budget keeps a stable subset: a texture used more than once in a present still evicts an entry not yet used in that present, so a working set with interleaved multi-use textures rotates as on main, and the textures that arrive after the cache fills are created again at each use for as long as the least recently used entry is from the current present, including in the transition present after a scene change. Past a quarter over the budget the entries are evicted as before, so after the budget falls the cache can sit at up to 125% of the reduced budget until entries stop being used. Entries unused for two presents are evicted as before. A later use can evict an entry from the previous present and then be refused when the next least recently used entry is from the current present, so that entry is created again in the next present. The "current or previous present" window is an unmeasured threshold: it was chosen, not derived. Measured once, as one A-B-A series on one title (loading phase about 33000 textures per frame against a 5.4 GiB budget) on one RTX 4090, 3600 s each: 932 / 968 / 941 frames, evictions after frame 620 2.72M -> 1.17M, texture creations unchanged (about 5.8M). Nothing is measured on other titles or GPUs, and a title whose per-present working set only slightly exceeds the budget now re-creates the excess textures at every use. -- [Texture cache budgets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - the storage image cache's budget is what the reported heap budget leaves after the heap's use other than the cached storage images (at least the sampled textures alive) and an eighth of the budget, capped at a quarter of the budget and at `TextureCacheBudget` (a quarter of the heap's size, at least 2 GiB), never below 512 MiB; the sampled cache's budget reserves `TextureCacheBudget` for the storage cache and never falls below 2 GiB. The storage budget keeps no room for the part of that 2 GiB floor the sampled textures alive do not use: it gives room back as they grow, at its next read. As the heap's other use grows the sampled budget reaches its floor first, then the storage budget falls. Each cache trims only at its next insert, a storage image is allocated before its insert reads the budget, and both budgets read the heap at most once a second, so a burst between two reads, sampled textures included, can still exceed the reported budget. The eighth is the sampled formula's value reused and the 512 MiB floor and the caps the earlier ones; none was measured beyond PPSA11386 on one 10 GB GPU and PPSA16064 on one 8 GB GPU, and a title whose storage working set exceeds the shrunk budget re-creates storage images at each use. +- [Texture cache budgets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - the storage image cache gets what the reported heap budget leaves after the heap's other use, the live sampled textures and an eighth of the budget, capped at half of the budget and of the heap, at least 512 MiB; the sampled cache reserves the larger of `TextureCacheBudget` and the storage images held, and keeps at least 2 GiB; above the floors neither budget plus the other cache's bytes exceeds the budget less its eighth. Both read the heap at most once a second and trim at their next insert, so a burst can exceed the budget. The storage budget gives back the part of the sampled floor not in use. The eighth and the floors were checked only on PPSA11386 (10 GB GPU) and PPSA16064 (8 GB GPU); the halves come from PPSA25636 on one 8 GB GPU (a 3.5-4 GiB storage working set). A title whose storage working set exceeds its budget re-creates storage images at each use. - [Polygon primitive type](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `VGT_PRIMITIVE_TYPE` 21 (`PrimitiveType::Polygon`) decodes to `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN` and is treated as inactive for vertex point size, like triangle fans and strips. PPSA01317 (Gran Turismo 7) submits polygon draws; whether the console performs polygon-mode decomposition or custom vertex routing is unverified. - [Native multisampled DCC fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) import uniform 0000/0001/1110/1111 keys into a single-layer, level-zero SW_64KB_R_X color image with 2, 4 or 8 fragments. Color and DCC/CMASK ranges must stay write-watched and unchanged; expanded or mixed initial DCC, register CMASK clears, compressed payloads, color transforms, lossy modes, other metadata changes, guest publication and writes over a live owner throw. A whole-range DCC fill with one of those keys, landed or still recorded, is taken as its fast clear; a partial fill, or a CMASK changed without a whole fill, throws. Driver-mediated reads of the color and metadata stay guarded. The metadata extent follows [AMD Addrlib](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp#L347-L454) with the [PS5 Mesa model](https://github.com/mihawk-99/PS5_Mesa/blob/0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8/src/amd/vulkan/winsys/ps5/radv_ps5_winsys.c): GFX10.1, 16 pipes, 256-byte interleave, two maximum compressed fragments. Console geometry, concurrent guest mutation and transitions outside this subset are unverified. - [Initial stencil state](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) uses zero for `DB_STENCIL_CONTROL`, `DB_STENCILREFMASK` and `DB_STENCILREFMASK_BF` (context `0x10b`-`0x10d`), including after `CLEAR_STATE`. The public V0, V4, V5 and V7-V11 [register-default tables](../../../core/libs/prx/libSceAgc/Command/include/RegisterDefaultsData.hpp) supply these values. Linux's [GFX10 clear-state table](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/amdgpu/clearstate_gfx10.h) agrees on the control register but uses `0x01000000` for the reference masks; the console reset values have not been measured. Planet Coaster (PPSA01736 v01.017) enables stencil tests without writing the control register. - [Multisampled DCC comp-to-single clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (AGC driver) - RADV's multisampled comp-to-single clear dispatch ([`radv_clear_dcc_comp_to_single`](https://github.com/mihawk-99/PS5_Mesa/blob/1ba9874f478657ccf6da72ce55f0260164cc0bd0/src/amd/vulkan/meta/radv_meta_clear.c#L954)) is recognized by its code and not run. Its clear color (user data 5 to 8, which the shader stores as the four dwords of sample 0's texel) is recorded for the surface its image descriptor names, and DCC keys of 0x10 on a multisampled target of that surface, pipe-bank XOR, size and sample count import as a clear to it, for texels of 8 to 128 bits. A record is dropped when an unmap or allocation removal covers its surface's base. The guest's color memory doesn't get the color at the blocks' first pixels, and the recorded color is assumed to be the one the keys refer to: RADV writes 0x10 keys only in a fast clear that runs this dispatch for the same image right after, and initializes DCC to 0x00 or 0xff on an image's first use ([`radv_init_color_image_metadata`](https://github.com/mihawk-99/PS5_Mesa/blob/1ba9874f478657ccf6da72ce55f0260164cc0bd0/src/amd/vulkan/radv_cmd_buffer.c#L16894)). A new image on memory reused within one mapping, with the same address, XOR, size and sample count, whose clear runs a comp-to-single shader other than the recognized one, would still import the old color. -- [Type-0 and type-1 texture descriptors](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp) (`TYPE` = 0 or 1 in dword 3) with a nonzero base are bound as null images, like a zero base. TYPE 0 comes from a gfx1036 (RDNA2) measurement in issue #4139 (image sample, load and resinfo return zero, no fault). TYPE 1 comes from a gfx1010 (RDNA1) measurement in #4139 and #5828 (`image_sample_lz`, `image_sample_b`, `image_load` and `image_get_resinfo` return zero, no fault) with format 22 only; TYPE 1 is not measured on RDNA2, with other formats or with depth-compare reads (`image_sample_c*`). Neither type was measured on the console's gfx1013. -- [Multisampled color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) lose their guest-read guards and cannot be reused after a successful allocation removal or unmap covers their full guarded range. The next target, sampled or storage lookup removes retired cache owners; recorded work and sampled views keep their images alive until their last reference is released. Partial unmaps keep the guard; partial allocation reuse and guest access to live multisampled data remain unsupported. -- [Stencil reference export replay](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) on devices without `VK_EXT_shader_stencil_export` replays the low eight bits of the values proved from constant, select and phi exports, with an export proved on every kept path. An export the prover cannot bound replays all 256 eight-bit values, one pass per value per triangle (logged once per process). It records instances, then their triangles, then the reference classes of each triangle. One pipeline filters fragments at shader termination against a dynamic push value before late stencil and depth tests. The [Vulkan stencil-reference replacement rule](https://docs.vulkan.org/refpages/latest/refpages/source/FragStencilRefEXT.html) discards bits above the attachment's stencil width; supported stencil attachments have eight bits. Numeric subgroup observations throw except bounded own-lane mask plumbing and group control with kept-lane and derivative-helper coverage proved. WQM inputs must exclude helper contributions, since [non-quad subgroup operations may treat helpers as inactive](https://docs.vulkan.org/glslext/latest/glslext/khr/GL_KHR_shader_subgroup.html). Readonly guarded SRT reads may report internal host faults; these draws wait and check faults before publishing results, and loops throw. Indirect draws, restart, strips, fans, tessellation, mesh, multisampling, early tests, ordered pixel execution, guest shader memory writes, other fault-producing memory access, clocks, primitive ID inputs, attachment feedback and pipeline face/channel splits throw. An exhausted stage push range throws. Equivalence to console ordering has not been measured on console hardware. +- [Initial depth and stencil planes](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) - the console's reset values of `DB_Z_INFO` and `DB_STENCIL_INFO` (context `0x010`-`0x011`) are not measured. Both start, and return after `CLEAR_STATE`, at zero (formats `INVALID`), the value of Linux's [GFX10 clear-state table](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/amdgpu/clearstate_gfx10.h). The public [register-default tables](../../../core/libs/prx/libSceAgc/Command/include/RegisterDefaultsData.hpp) write `0x80000180` and `0x20000180` instead, also with formats `INVALID`; of their other bits, only `TILE_STENCIL_DISABLE` would change a result: the HTILE layout of a multisampled depth plane whose `DB_STENCIL_INFO` was never written. +- [Type-0 and type-1 texture descriptors](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp) (`TYPE` = 0 or 1 in dword 3) with a nonzero base are bound as null images, like a zero base. TYPE 0 comes from a gfx1036 (RDNA2) measurement in issue #4139, TYPE 1 from a gfx1010 (RDNA1) measurement in #4139 and #5828 with format 22 only. On a gfx1100 (RDNA3, RADV Mesa 25.2.8) both types answer zero with no fault from `image_sample_lz`, `image_sample_b`, `image_load`, `image_sample_c_lz` and `image_get_resinfo`, for formats 32_FLOAT, 16_SSCALED, 2_10_10_10_SNORM and 8_UNORM (GFX11 codes 22, 10, 37, 1), over a readable nonzero base and a zero base ([hardware oracle](../HW_ORACLE.md), 2D T# declared 64x32). The TYPE 9 control answers the stored texel, 64 and 32 from resinfo, and 1.0 from an ALWAYS `image_sample_c_lz` at 32_FLOAT, the only one of these formats gfx1100 compares. TYPE 1 is not measured on RDNA2, and neither type on the console's gfx1013. +- [Multisampled color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) lose their guest-read guards and cannot be reused after a successful allocation removal or unmap covers their full guarded range. The next target, sampled or storage lookup removes retired cache owners; recorded work and sampled views keep their images alive until their last reference is released. A target made from a multisampled texture that a sample reads before any draw is retired instead when a guest access overlaps it, until a draw, a dispatch's storage write or an absorbed color fill puts samples on the host only; it is rebuilt from guest memory at its next use. Partial unmaps keep the guard; partial allocation reuse and guest access to live multisampled data remain unsupported. +- [Stencil reference export replay](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) on devices without `VK_EXT_shader_stencil_export` replays the low eight bits of the values proved from constant, select and phi exports, with an export proved on every kept path. An export the prover cannot bound replays all 256 eight-bit values, one pass per value per triangle (logged once per process). It records instances, then their triangles, then the reference classes of each triangle. One pipeline filters fragments at shader termination against a dynamic push value before late stencil and depth tests. The [Vulkan stencil-reference replacement rule](https://docs.vulkan.org/refpages/latest/refpages/source/FragStencilRefEXT.html) discards bits above the attachment's stencil width; supported stencil attachments have eight bits. Numeric subgroup observations throw except bounded own-lane mask plumbing and group control with kept-lane and derivative-helper coverage proved. WQM inputs must exclude helper contributions (a ballot of the active lanes, as in an EXEC copy, covers whole quads and is accepted), since [non-quad subgroup operations may treat helpers as inactive](https://docs.vulkan.org/glslext/latest/glslext/khr/GL_KHR_shader_subgroup.html). Readonly guarded SRT reads may report internal host faults, and read-only BDA reads may report faults; these draws wait and check faults before publishing results, and loops throw. Indirect draws, restart, strips, fans, tessellation, mesh, multisampling, early tests, ordered pixel execution, guest shader memory writes, other fault-producing memory access, clocks, primitive ID inputs, attachment feedback and pipeline face/channel splits throw. An exhausted stage push range throws. Equivalence to console ordering has not been measured on console hardware. - [32-bit depth-bits reads of guest memory](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver): a sampled 32_UINT or 32_SINT T# with a depth swizzle mode that no live depth surface matches reads the guest bytes through an R32_SFLOAT image, as the recompiler's depth-bits read expects, and the shader casts the fetched value back to the stored dword. On RX 6700 XT (gfx1031, AMD Windows driver 32.0.21045.5002) the read keeps float denormals, the sign bit alone and quiet and signaling NaN payloads bit for bit. A host that canonicalizes NaNs or flushes denormals on a 32-bit float fetch would change those dwords, and other drivers are not measured. A live depth surface whose extent or base level differs from the T# is not used; the read takes the guest bytes, as the 32-bit float and 16-bit views already do. -- [Reused depth storage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp): a storage view with another extent or a format that cannot hold the cached depth plane uses guest memory. Depth rendering is not written back to those guest bytes; this admission rule does not prove allocation reuse or preserve a depth writer through an incompatible view. A 2D storage view, or a one-layer 2D array view of layer 0, of level 0 with the plane's extent and format (32-bit float or the 32-bit depth-bits layout for the depth plane, 16-bit unorm for D16, 8-bit uint for the stencil plane) is a storage image that is [filled](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) from the depth image before every use and, when the shader writes it, copied back after the work: a whole-plane copy through a staging buffer each way. A dispatch of the 15-word 8x8x1 image clear kernel ([`MatchStorageImageClear`](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp): one colour read from the V# in `user[8..11]` stored to every texel of the T# in `user[0..7]`) that covers the whole plane and stores 0 or 1 through a 16-bit unorm view, 0 or 0xffff through a 16-bit uint view, or 0 or a normal float in (0, 1] through a 32-bit float or integer view leaves out the copy-in and replaces the copy-out by a clear of the depth image to that value; the dispatch still writes the storage image. The colour is read again when the copy-out is replaced, and a changed value keeps the copy-out. Indirect dispatches, partial last groups, dispatches that queue on the draw pipeline, other values and formats, a plane the kernel covers only partly and stencil planes keep both copies. A written view is also left pending like any other storage image, so the plane's content, drawn depth included, reaches guest memory on the next flush of that range. A 2D array view of more than one slice of the depth plane, or of a later slice, is a storage image over the slices from 0 to its last (`LayeredDepthStorage`); only the slices from its base slice to its last are copied: a slice that has a depth surface at the plane's address plus a whole depth slice per layer (`DepthSliceBytes`, as for the depth plane copies below) is copied from that surface before every use and back to it after the work, every bound slice each time, through a staging buffer; the other slices hold guest memory like any storage image. These images are kept per address, extent, slice count, base slice, format and tile mode until the device goes away, outside the storage cache's budget. Such a view needs a layer pitch equal to that slice size, and another view of the same memory (the 2D view of its first slice, a view from another base slice, or a sampled copy) is a separate image, copied in before and out after each use, so views that are written before a flush and share a slice do not merge: the later copy-out wins, so their results are not merged. A matching view still throws when it is a cube, a 2D array of more than one layer of the stencil plane or from a later slice, or another level, when the surface is multisampled, when it names the depth plane of a 16-bit depth with stencil surface the device stores in a wider format, and when it was written after another view of the same plane (another format, tile mode, address or dimension) replaced it, as two such views bound in one dispatch are. Stored depth values outside 0..1 are not clamped, and Vulkan leaves that copy undefined without `VK_EXT_depth_range_unrestricted`. `agc_driver_depth_storage_dispatch` pins these paths; none is measured on the console. +- [Reused depth storage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp): a storage view with another extent or a format that cannot hold the cached depth plane uses guest memory. Depth rendering is not written back to those guest bytes; this admission rule does not prove allocation reuse or preserve a depth writer through an incompatible view. A 2D storage view, or a one-layer 2D array view of layer 0, of level 0 with the plane's extent and format (32-bit float or the 32-bit depth-bits layout for the depth plane, 16-bit unorm for D16, 8-bit uint for the stencil plane) is a storage image that is [filled](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) from the depth image before every use and, when the shader writes it, copied back after the work: a whole-plane copy through a staging buffer each way. A dispatch of the 15-word 8x8x1 image clear kernel ([`MatchStorageImageClear`](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp): one colour read from the V# in `user[8..11]` stored to every texel of the T# in `user[0..7]`) that covers the whole plane and stores 0 or 1 through a 16-bit unorm view, 0 or 0xffff through a 16-bit uint view, or 0 or a normal float in (0, 1] through a 32-bit float or integer view leaves out the copy-in and replaces the copy-out by a clear of the depth image to that value; the dispatch still writes the storage image. The colour is read again when the copy-out is replaced, and a changed value keeps the copy-out. Indirect dispatches, partial last groups, dispatches that queue on the draw pipeline, other values and formats, a plane the kernel covers only partly and stencil planes keep both copies. A written view is also left pending like any other storage image, so the plane's content, drawn depth included, reaches guest memory on the next flush of that range. A 2D array view of more than one slice of the depth plane, or of a later slice, is a storage image over the slices from 0 to its last (`LayeredDepthStorage`); only the slices from its base slice to its last are copied: a slice that has a depth surface at the plane's address plus a whole depth slice per layer (`DepthSliceBytes`, as for the depth plane copies below) is copied from that surface before every use and back to it after the work, every bound slice each time, through a staging buffer; the other slices hold guest memory like any storage image. These images are kept per address, extent, slice count, base slice, format and tile mode until the device goes away, outside the storage cache's budget. Such a view needs a layer pitch equal to that slice size, and another view of the same memory (the 2D view of its first slice, a view from another base slice, or a sampled copy) is a separate image, copied in before and out after each use, so views that are written before a flush and share a slice do not merge: the later copy-out wins, so their results are not merged. A matching view still throws when it is a cube or a 2D array of more than one layer of the stencil plane or from a later slice, when the surface is multisampled, when it names the depth plane of a 16-bit depth with stencil surface the device stores in a wider format, and when it was written after another view of the same plane (another format, tile mode, address or dimension) replaced it, as two such views bound in one dispatch are. Stored depth values outside 0..1 are not clamped, and Vulkan leaves that copy undefined without `VK_EXT_depth_range_unrestricted`. `agc_driver_depth_storage_dispatch` pins these paths; none is measured on the console. - [Collapsed color array views](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) preserve the selected slice in the XOR address equation when the view is represented as a shifted 2D surface. Native Vulkan coverage checks R_X draws, array reads, publication and re-upload for slices 1-3; Lost Soul Aside PPSA26358 validates the RGBA16F cube mip chain on Windows/NVIDIA. Other devices and console comparisons remain unverified. Full-array views retain their layer addressing. -- A [depth surface](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) is retired when a color target or a written storage image it does not serve overlaps it, or when it is sampled through a view it cannot serve and guest memory over it was written since it was last bound; lookups then skip it, so sampling reads the memory like any texture. Bound again, it keeps its own depth instead of the bytes written over it, except for the depth planes the next entry keeps in guest memory. Without write watching, a view it cannot serve still throws. A retired surface also gives up its depth and stencil planes, so another surface sharing them no longer copies from it. -- [Depth planes in guest memory](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): only single-sample `D32_SFLOAT` and `D32_SFLOAT_S8_UINT` planes with the 64 KiB Z swizzle (`SW_MODE` 24) reach guest memory; D16, other swizzles, stencil and multisampled planes keep depth in the host image only. A plane drawn since its last store is stored, uncompressed, when a buffer upload, a `GuestBuffers` V#, an image lookup or a pending-store flush overlaps it; CPU reads and reads through address-based heaps are not caught. Guest writes are ordered when collected (bind, flush, collect epoch), not when made, at 64 KiB granularity: a collected write reloads the whole plane unless an HTILE clear is pending. A store outside a recorder waits for the GPU. The console keeps HTILE-compressed tiles compressed in memory until a decompress (Mesa `ac_descriptors.h`). +- A [depth surface](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) is retired when a color target overlaps it, an unserved storage view writes a whole plane, or it is sampled through a view it cannot serve and guest memory over it was written since it was last bound; lookups then skip it, so sampling reads the memory like any texture. Bound again, it keeps its own depth instead of the bytes written over it, except for the depth planes the next entry keeps in guest memory. Without write watching, a view it cannot serve still throws. A retired surface also gives up its depth and stencil planes, so another surface sharing them no longer copies from it. +- [Depth planes in guest memory](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): only single-sample `D32_SFLOAT` and `D32_SFLOAT_S8_UINT` planes with the 64 KiB Z swizzle (`SW_MODE` 24) reach guest memory; D16, other swizzles, stencil and multisampled planes keep depth in the host image only. A plane drawn since its last store is stored, uncompressed, in 64 KiB units (128x128 tiles): a buffer upload, a `GuestBuffers` V#, an image lookup or a pending-store flush stores the units it overlaps, a color target or written storage image over the plane all of them. CPU reads and reads through address-based heaps are not caught. Guest writes are ordered when collected (bind, flush, collect epoch), not when made: a collected write reloads the units it touched unless an HTILE clear is pending. A store outside a recorder waits for the GPU. The console keeps HTILE-compressed tiles compressed in memory until a decompress (Mesa `ac_descriptors.h`). +- [Depth surfaces over compared shared memory](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): a depth surface over Windows direct memory that is compared once it is shared sees CPU writes made after the sharing no more than one over unwatched memory does. Its generations come from refused collects (0) or `TrackerGeneration()`, and nothing stamps compared memory after the one report at the sharing. +- [Unmapped depth planes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): a drawn depth plane whose guest memory the title unmapped before it was stored is not stored; its pending depth is dropped with a one-time warning. What the console does with such a store is not known. Readable planes without write permission retain the unsupported-store failure and their pending results. - [Depth plane copies](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): a depth plane sampled as a 2D array or a cube, or a D16 plane read as `R16_UINT`, is copied into an image of its own (`DepthPlaneCopy`), kept per base address, extent, format and slice count; a slice comes from the live depth surface when one is bound at the slice's address and from guest memory (detiled) otherwise. A draw or dispatch that binds the plane copies again only the slices that changed and records nothing when none did: a surface slice when its image or write serial differs from the one copied (the serial is bumped when the surface is created, when a draw binds it as its depth target, when a pending fast clear is applied and when a written storage view of the plane is copied back), a guest-memory slice when its bytes were written since they were last read (`GuestMemory::UnchangedSince`), a snapshot write overlaps them, or its address, size or tile mode changed. The surface of the latest depth draw is copied at every use until another depth target is bound, since that draw may still be recorded after the copy. A slice's address is the base plus a whole 64 KiB-block depth slice per layer (`DepthSliceBytes`), and the cube face order is the array order; both are inferred from the titles' use, not measured on hardware. A 2D-array T# whose `BASE_ARRAY` is past slice 0 reads slices `BASE_ARRAY` to `LAST_ARRAY` of the copy, which holds slices 0 to `LAST_ARRAY`, and a 2D binding of it reads slice `BASE_ARRAY`; that follows the colour 2D-array semantics and the titles that hit it (PPSA01556, PPSA03912), and was not measured on hardware for depth surfaces. Planes with mips, a cube or a 2D T# with a base slice other than 0, a `BASE_ARRAY` past `LAST_ARRAY`, a cube with other than 1 or 6 slices, and other views of the planes still throw, and so do these copies of a 16-bit plane with stencil that the device stores as D24 or D32 (16-bit depth with stencil, below). +- [New depth surface from storage writes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - `TakeStorage` discards the depth aspect (`UNDEFINED` to `TRANSFER_DST_OPTIMAL`) before copying the storage image over the whole surface. On AMD's Windows driver 32.0.31041.1004 (RX 7800 XT) a copy into the depth image after its creation clear left one or two 8x8 blocks testing as the clear value in about 1 of 10 runs, although a copy back read the copied values (#6630); not seen on a Radeon 860M or RX 6700 XT. A surface with a stencil plane keeps the clear before the copy, since its depth aspect is not discarded alone. - [Repacked format-43 copies](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver): a sampled texture of format 43 (10-bit R, 11-bit G and B unsigned floats from bit 0) is uploaded into a `B10G11R11_UFLOAT` image with its R and B fields swapped (`RepackTextureWord`) and viewed with R and B swapped back. A texture whose results a storage image still holds is copied from it again, whole, whenever the image changed (`RefreshDepthCopy`); a 3D one is flushed to guest memory and uploaded instead. Storage reads and writes of format 43 keep the packed `R32_UINT` path. - [SW_64KB_Z_X color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp) (libSceAgcDriver) use the texture equations and 64 KiB block geometry of `SW_64KB_Z_X`, whose addresses are checked against [AMD addrlib GFX10](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp) in the texture tiling tests. Any 256-byte pipe/bank XOR below the block size is accepted, as for `SW_64KB_R_X` targets and Z_X textures; which of those bits the console honours, and the render results, are not measured. - [SW_64KB_D_X color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp) (libSceAgcDriver) use the existing texture block/mip geometry and equations from [AMD addrlib GFX10 non-RB+](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp) for 16 pipes and 256-byte interleave. The address-reference tests use legal pipe/bank XOR bits for that configuration. Raw column bits outside the source configuration's pipe/bank mask 0xcf00 throw; their console meaning is unresolved. Console address equations and render results have not been measured. @@ -88,28 +107,36 @@ - [8_8 UINT color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) decode as `R8G8_UINT` with the same component-swap mapping as 8_8 UNORM/SNORM (`0xe4` / `0xec`). Follows the existing 8 and 8_8_8_8 UINT paths; not measured on the console. RetroArch (PPSA99169) uses it during menu startup. - [Quad-list primitives](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver): primitive type 19 (DI_PT_QUADLIST, 0x13) translates to Vulkan triangle lists by generating 6 indices per 4-vertex quad in a synthetic 32-bit index buffer during draw input preparation (triangles (0, 1, 2) and (0, 2, 3) for first provoking vertex; (0, 1, 3) and (1, 2, 3) for last provoking vertex). Incomplete trailing vertices (< 4) are discarded. Indirect draws, primitive restart and draw recipes are unsupported for quad-list primitives. Console behavior for incomplete quads has not been measured on hardware. - [Separate alpha blend into 8_8 SWAP_ALT targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp) (libSceAgcDriver): the second channel of a two-channel target with `COMP_SWAP` 1 holds the alpha export, but Vulkan blends it with the color equation of the R8G8 attachment, so a draw whose alpha equation differs from its color equation is recorded twice, the first channel with the color equation and the second with the alpha factors and operation moved into the color equation. The vertex stages run twice, so a vertex shader that writes memory writes twice, and occlusion counts include both passes. Draws that write depth or stencil, pixel shaders that write memory and factors of the destination alpha throw. That the hardware blends the second channel with the alpha equation is taken from Mesa's `si_translate_colorswap` and radeonsi's use of it for luminance-alpha formats, not measured on hardware. +- [Color logic operations](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver): `CB_COLOR_CONTROL.ROP3` accepts the 28 codes of the source and destination form (`pqrspqrs`) or the brush and destination form (`pqpqrsrs`, the brush is the shader color), as the R6xx 3D register reference describes the field and Mesa's `gfx10.json` `ROP3` enum lists. A code that combines source and brush, which the console emulates in software, throws. - [Unexported pixel inputs](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp) (libSceAgcDriver): a pixel input whose `SPI_PS_INPUT_CNTL` parameter the previous stage does not export is programmed as a default-value input (`OFFSET` bit 5, `DEFAULT_VAL` 0, FP16 high half too) and reads (0, 0, 0, 0), as radeonsi's `SI_PS_INPUT_CNTL_UNUSED` does and as rect-list draws already do. The console's value for an unwritten parameter cache entry is not measured on hardware. - [Per-sample pixel shading](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver): `PA_SC_MODE_CNTL_1.PS_ITER_SAMPLE` (bit 16, [PS5_Mesa GFX10.3 registers](https://github.com/mihawk-99/PS5_Mesa/blob/0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8/src/amd/registers/gfx103.json)) on a multisampled draw enables Vulkan sample shading with a minimum rate of 1.0, so the pixel program runs once per sample. The draw throws when the device lacks `sampleRateShading`. On a single-sample draw the bit has no effect and is dropped. That the console runs the pixel program once per covered sample, and what it does with the other multisample registers in that mode, is taken from the register name and Vulkan's definition, not measured on hardware. Without the bit the pixel program runs once per pixel: the recompile request carries the mode (`iteratesSamples`), the ancillary's sample number reads 0 instead of `SampleId`, whose use would make Vulkan shade per sample, and the sample barycentrics and positions are evaluated at sample 0, whose locations the request carries (`sampleLocations`, every sample's in an iterated draw). `DB_EQAA.PS_ITER_SAMPLES`, the number of samples an iterated shader covers, is not read: an iterated draw runs once per sample. - [Unused pixel programs](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver): a bound pixel program is skipped when color masks have no intersection and DB export and execution enables are clear, regardless of retained SPI_SHADER_Z_FORMAT. This follows [AMD PAL DbShaderControl](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9AbiToPipelineRegisters.h) and its [GFX10 register definitions](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/chip/gfx9_plus_merged_registers.h), not a hardware measurement. Enabled DB_ALPHA_TO_MASK conservatively retains the program even with ALPHA_TO_MASK_DISABLE, because DecodeState currently enables Vulkan alpha-to-coverage without applying that override. Console behavior for these combinations has not been measured. - [Sample masks](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `PA_SC_AA_MASK_X0Y0_X1Y0` and `PA_SC_AA_MASK_X0Y1_X1Y1` hold one 16-bit sample mask for each pixel of the aligned 2x2 quad ([Mesa gfx10.json](https://gitlab.freedesktop.org/mesa/mesa/-/blob/7b87f8ad226e/src/amd/registers/gfx10.json#L13512)); only the bits of the `MSAA_NUM_SAMPLES` rasterized samples count, and at one sample bit 0 of a pixel's field covers every sample of the target the draw is raised to. A mask equal for the four pixels is the Vulkan `pSampleMask`, the case [radv](https://gitlab.freedesktop.org/mesa/mesa/-/blob/7b87f8ad226e/src/amd/vulkan/radv_cmd_buffer.c#L13633) and [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/7b87f8ad226e/src/gallium/drivers/radeonsi/si_state.c#L4218) program; with early fragment tests it needs `earlyFragmentSampleMaskTestBeforeSampleCounting` ([radv](https://gitlab.freedesktop.org/mesa/mesa/-/blob/7b87f8ad226e/src/amd/vulkan/radv_physical_device.c#L2079) reports it). A mask that differs between the pixels is applied in the pixel shader without early fragment tests: a pixel whose mask covers all or none of its samples is demoted to a helper invocation by `gl_FragCoord` parity and killed when the program returns (the Radeon 860M's Windows driver 32.0.13058.8001 writes undefined colour for a demoted pixel whose export leaves its output unwritten), and otherwise the pixel's mask is ANDed into `gl_SampleMask` (with alpha-to-coverage and the mask export) after demoting invocations whose `SampleMask` input it leaves empty. A pixel program with early Z that writes memory under depth, stencil or depth-bounds tests is therefore refused. That XnYm is the pixel at (x mod 2, y mod 2) = (n, m) is read from the field names; that the mask removes samples before the depth and stencil tests and occlusion counting is inferred from radv implementing the Vulkan sample mask test with it; and that it takes no part in the centroid (`AA_MASK_CENTROID_DTMN` 0) is inferred from radv setting that bit only for conservative rasterization. None of it is measured on gfx1013. A mask equal for the four pixels that removes some but not all samples of a pixel is refused with active centroid inputs, since where Vulkan puts the centroid relative to `pSampleMask` is implementation-defined, and any partial mask is refused with conservative rasterization (radv sets `PREZ_AA_MASK_ENABLE` and `POSTZ_AA_MASK_ENABLE` [there](https://gitlab.freedesktop.org/mesa/mesa/-/blob/7b87f8ad226e/src/amd/vulkan/radv_cmd_buffer.c#L13653), which 0x6001 leaves clear). - [Covered-centroid raster state](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) accepts PA_SC_AA_CONFIG.COVERED_CENTROID_IS_CENTER only when the pixel program is skipped or SPI_PS_INPUT_ENA and SPI_PS_INPUT_ADDR have no active perspective or linear centroid input. Bit 29 follows [PS5_Mesa GFX10.3 registers](https://github.com/mihawk-99/PS5_Mesa/blob/0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8/src/amd/registers/gfx103.json); [RADV](https://github.com/mihawk-99/PS5_Mesa/blob/0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8/src/amd/vulkan/radv_cmd_buffer.c) sets it on GFX10.3 and later. Active centroid interpolation and unspecified input registers still throw. Console covered-pixel centroid results were not measured. +- [Buffer destruction on MoltenVK](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DeferredDestruction.cpp) (libSceAgcDriver): on MoltenVK, buffers and device memory released through the graphics context are destroyed only while the graphics queue is idle. MoltenVK makes every live `SHADER_DEVICE_ADDRESS` buffer resident, unretained, in each command buffer that uses device addresses, also in ones submitted after the release, and passes Metal a nil buffer once its memory is freed ([KhronosGroup/MoltenVK#2876](https://github.com/KhronosGroup/MoltenVK/issues/2876)). A recorder or command batch submission drains the queue under the GPU mutex once 512 objects wait or the oldest has waited 100 ms (`vkQueueWaitIdle`, then the buffers, then the memory), which stalls the CPU until the GPU is idle; memory released under pressure only returns at that drain. `VulkanDevice`'s own destroys are not deferred. `APS5_NO_DEFERRED_DESTRUCTION=1` turns this off; remove it once MoltenVK fixes #2876. - [Compute LDS beyond the device's shared-memory limit](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (libSceAgcDriver) lives in a coherent storage buffer with one slice of `LDS bytes / 4 + 1` dwords, rounded up to an even count so that 64-bit atomics stay 8-byte aligned, per workgroup (the dword after the LDS is the atomic lock on a device without `shaderBufferInt64Atomics`), shared by all such dispatches and grown in 1 MiB steps up to `maxStorageBufferRange`; a dispatch whose slices do not fit throws. It is slower than shared memory and its cost was not measured. An indirect dispatch of such a program always reads its group counts on the CPU. Its results are pinned against the in-limit semantics in `agc_driver_lds_beyond_device_limit`, not measured on hardware. -- [AGC buffer allocations](../../../core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp) retry host/device out-of-memory once after reclaiming idle pool buffers and fully unused slab blocks. Live buffers and blocks with live slots are retained. Live images and other retained resources have no global memory bound; allocation failure still throws when reclaim cannot satisfy the request. +- [AGC buffer allocations](../../../core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp) retry host/device out-of-memory once after reclaiming idle pool buffers and fully unused slab blocks. Live buffers and blocks with live slots are retained. Live images and other retained resources have no global memory bound; allocation failure still throws when reclaim cannot satisfy the request, except for the device buffers and images that the "Host placement after a device-memory refusal" entry places in host memory. +- [Storage-cache pressure and idle device buffers](../../../core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp): the one-second idle observation interval is a retention choice, not a console rule; buffers reused less often can be recreated under pressure. Before a storage-cache eviction, independently allocated device-tier buffers unused throughout that interval can be released, oldest first, for the cache's shortfall below its existing ceiling. Live buffers, host tiers and slab allocations are not reclaimed this way. Reclamation requires exactly one device-local heap because retained buffers do not record their heap. The heap budget is read again after an actual release; a delayed driver report can still cause an eviction. This is not a shared budget for all retained GPU resources, and its effect on titles with intermittent large buffer use is unmeasured. - [Host placement after a device-memory refusal](../../../core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp) (libSceAgcDriver): when the driver refuses a device-local allocation with `VK_ERROR_OUT_OF_DEVICE_MEMORY` after the pool's trim and the texture-cache release, device buffers and images (textures, storage textures, block, repacked, depth, depth plane and stencil copies, render, depth and multisampled targets and attachment proxies) are allocated in a host memory type their requirements allow. A host-placed device buffer leaves the pool on release. A host-placed image keeps that placement until it is destroyed and is never moved back to device memory, so every access to it crosses PCIe for its lifetime. The placements are counted in a rate-limited `[bufferpool]` line with the cumulative MiB, not the resident bytes. The 1x1 denormal clear probe still allocates device memory directly and throws on a refusal. -- [Pipeline journal](../../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp) (libSceAgcDriver): the journal is append-only and never compacted, so a module a later run no longer builds pipelines from stays in the file until it is deleted, and the whole file is read into memory while the replay runs (its modules reference that buffer until the last replayed pipeline was built). Only the first `APS5_PIPELINE_JOURNAL_ENTRIES` pipelines recorded are replayed (a record is never rewritten, so a pipeline past the bound stays past it), and the pipeline store evicts the least recently used entry past `APS5_PIPELINE_STORE_ENTRIES` (1024 each): a title with more configurations rebuilds the rest at the draw as before. A compute pipeline is replayed over the set layout `DescribeSetLayouts` derives from the result's bindings, which a dispatch that finds it resident checks against its `ShaderResources` layout only under `APS5_VERIFY_SET_LAYOUTS=1`. Recording and replay are best effort: a record whose write fails is lost, an entry whose build fails is skipped, and both are counted in the `[pipeline-journal]` line rather than stopping the run. A replayed pipeline that the title never asks for again costs its build time and its driver memory (its optimized relink is deferred to its first draw, so a run whose first frames bind many replayed pipelines queues their relinks then); nothing ages recipes out of the file. Two runs sharing the file take turns under its lock file, but each only knows the modules it loaded or wrote itself, so a module the other run appended meanwhile may be appended again (the loader keeps the first copy). Records a run appends after another run's load are replayed by that run only after its next start. The file header's format number is the only guard against a change of the recipe codec, `CanonicalPipelineState` or the store key that keeps the structures' sizes (the recompiler's source version does not cover the driver's sources). +- [Pipeline journal](../../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp) (libSceAgcDriver): resident graphics pipelines are bounded by count, not bytes: the store grows from 1024 to 4096 entries for a replay that does not fit and for evicted pipelines that keep coming back, and the last `APS5_PIPELINE_JOURNAL_ENTRIES` (4096) records are all replayed, so a pipeline the title never asks for again costs its memory until teardown. `agc_driver_recorder_tests --benchmark-pipeline-store-memory [count]` gives a floor with the triangle shaders: about 59 KiB host and no device memory per pipeline on an RTX 4090, 100 KiB host and 8 KiB device-local on a UHD 770; larger shaders cost more. The file is rewritten to three quarters of `APS5_PIPELINE_JOURNAL_MAX_MIB` when full. A compute pipeline is replayed over the layout `DescribeSetLayouts` derives, checked only under `APS5_VERIFY_SET_LAYOUTS=1`. The header's format number guards the recipe codec and the store key. - [Graphics pipeline libraries](../../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp) (libSceAgcDriver): a pipeline is fast-linked at the draw and relinked with `VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT` on a background thread, and binds the optimized one from the next draw on. Draws recorded before that use the fast link, whose vertex stage keeps the outputs the fragment stage does not read: on RADV (Radeon 660M, Mesa 26.2.4) 346 of 483 Astro's Playroom vertex shaders paired with a fragment stage that reads none of them used more VGPRs (median 8 more, up to 2.5 times), and on an RTX 4090 13 of 15 PPSA36082 vertex shaders up to 68 instead of 38 (#2242). The optimized link compiles to the same code as a whole pipeline +- [Fragment shader interlock pipelines](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp) (libSceAgcDriver): a pipeline whose fragment shader declares a fragment shader interlock capability is never built from graphics pipeline libraries, on every driver, so it is compiled whole at the draw with no fast link to bridge the compile. RADV's fast link ([mesa#16653](https://gitlab.freedesktop.org/mesa/mesa/-/work_items/16653), Mesa 26.2.5, RX 7900 GRE) jumps to the PS epilog without waiting for the shader's stores, and the epilog's `done` export ends the GFX11 primitive-ordered section, so the next overlapping wave reads the old value: `agc_driver_pixel_interlock` lost read-modify-writes in 108 of 120 loaded runs under `APS5_NO_PIPELINE_LTO=1`. On an RTX 4090 (NVIDIA 617.42, Windows) the fast link kept the ordering in 240 of 240 loaded runs; other drivers are not measured. - [Prebuilt pipeline libraries](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (libSceAgcDriver): each registered vertex or pixel shader in registration form gets its pre-rasterization or fragment-shader library built on one background thread, with a fixed state the draw may not use: triangle list, counter-clockwise front face, no depth clamp, depth bias or conservative rasterization, first provoking vertex, one sample. Libraries are kept until the device is torn down; on Astro's Playroom 1948 of them added 0.44 GB of peak memory on RADV. - [`APS5_THREAD_PRIORITY=rt`](../../../core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp) (libSceAgcDriver) lowers the process's hard `RLIMIT_RTTIME` when it is above `APS5_THREAD_RTTIME_US` or rtkit's `RTTimeUSecMax`, because [rtkit](https://github.com/heftig/rtkit/blob/c295fa849f52b487be6433e69e08b46251950399/rtkit-daemon.c#L592-L637) (`verify_process_rttime`) reads the hard limit from `/proc//limits` and refuses real-time scheduling when it is higher. An unprivileged process cannot raise the hard limit again, so it stays lowered for the process and its threads even when rtkit then refuses the request (burst limit, user or start-time checks) and the thread throws. rtkit's `MaxRealtimePriority` and `RTTimeUSecMax` are read first, so an unreachable rtkit or a priority above its maximum leaves the limit untouched. The lowered limit only matters for threads that later run under a real-time policy -- AMD image descriptors with `TYPE=0` are treated as null for read-only image slots even when the base and other words are nonzero: `image_load` on a gfx1035 with such a T# returns 0,0,0,0, and the same descriptors were seen in direct reads in PPSA01341 on an RTX 4070. Writable and atomic image slots and entries of an image table keep faulting because there is no hardware measurement for them, and the reserved types 2-7 also fault. `TYPE=1` is treated as null in the same read-only slots: gfx1035 reads it as zeros too, and gfx1010 returns zeros for `image_sample_lz`, `image_sample_b`, `image_load` and `image_get_resinfo` with format 22 (#5828); it is not measured on RDNA2, with other formats or with depth-compare reads (`image_sample_c*`). gfx1013 behavior is unverified - On a device without VK_EXT_robustness2's `nullDescriptor`, such as MoltenVK, a null image descriptor in a typed heap is a [placeholder image](../../../core/libs/prx/libSceAgcDriver/Graphics/src/PlaceholderImages.cpp) of that heap's type, 1×1 and cleared to zero, which every null descriptor of the heap shares. Reads give zeros, as `nullDescriptor` allows, but stores and atomics to it are kept, so a later read of a null storage image can return them, and a depth comparison compares against depth 0. A heap whose placeholder the device cannot create, for example 1D or 3D depth, 64-bit atomics without 64-bit image atomics, or multisampled storage without `shaderStorageImageMultisample`, throws when a null descriptor reaches it. - [Draw input snapshots](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver): `PrepareDrawBindings` caps read-only input buffer snapshots at 64 MiB (overridable via `APS5_DRAW_SNAPSHOT_MAX_MIB`). Larger inputs (streaming heaps, bindless pools) or draws exceeding the cumulative budget skip the per-draw snapshot and bind in place, avoiding multi-gigabyte CPU copy stalls (such as Ratchet & Clank: Rift Apart PPSA01474 stalling at frame ~380 copying 36 GB every 5 s). If the title rewrites those large ranges on the CPU before GPU completion, the in-place binding observes the modification, as verified by `drawSnapshotCapTests`. -- [Pending storage-image results](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) are stored when something reads their memory, not when the GPU work that wrote them completes. A title that waits for that work, frees the image and reuses its memory can have the new data overwritten by the late store (#1970). A title's own CPU reads are not seen either, so a title that reads the results after waiting for a label reads stale memory. Explicit-size file writes (`write`, `pwrite`, `writev`, `pwritev`, AIO and `fwrite`) synchronize their source ranges through the [host-read hook](../../../core/libs/prx/libc/src/GuestArena.cpp); other host readers are not covered. `APS5_STORE_AT_FLIP=1` stores every pending result at each flip and `APS5_STORE_AT_LABEL=1` before each label the GPU writes; both store every pending image, not only the ones the waited-for work wrote, and neither is on by default. +- [Pending storage-image results](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver), except drawn resident linear targets (next entry), are stored when something reads their memory, not when the GPU work that wrote them completes. A title that waits for that work, frees the image and reuses its memory can have the new data overwritten by the late store (#1970). A title's own CPU reads are not seen either, so a title that reads the results after waiting for a label reads stale memory. Explicit-size file writes (`write`, `pwrite`, `writev`, `pwritev`, AIO and `fwrite`) synchronize their source ranges through the [host-read hook](../../../core/libs/prx/libc/src/GuestArena.cpp); other host readers are not covered. `APS5_STORE_AT_FLIP=1` stores every pending result at each flip and `APS5_STORE_AT_LABEL=1` before each label the GPU writes; both store every pending image, not only the ones the waited-for work wrote, and neither is on by default. +- [Resident linear color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver): a CPU store inside a drawn scissor rectangle, on a pixel no primitive wrote, made before the target's store is overwritten by it, where the console keeps it. Recorded draws' results are stored before the graphics queue's next flip, label, end-of-pipe interrupt, drain or device wait, as the union of their scissor rectangles; a draw that brings a 17th rectangle that does not merge stores the target at once. A target with an attachment proxy, several mips or layers, a 3D or DCC surface, or results of a dispatch, fill or copy is stored after each draw. One that a fill or copy writes after its draws is stored whole at the next of those points, and GPU results in its CPU-written 64 KiB blocks are dropped. A store the draw pipeline's committer makes (label, end-of-pipe interrupt, device wait) skips a target whose range a queued draw still reads (`DrawPipeline::HeldReads`). +- [Direct writes over a drawn linear target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver): a CPU store into a 64 KiB block of a drawn resident linear target that lands while the completion of a direct write recorded before the draws checks and stamps that block (`StampKeepingNewerResults`) drops the draws' results in that block, where the console keeps them: the stamp's own collect records the store per page only, so the store reads the block as written by the driver. - [`APS5_SKIP_REFUSED_DRAWS`](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp) (libSceAgcDriver) drops a draw whose graphics stage the recompiler refuses instead of failing the queue, so the frame lacks that draw, and the stage is compiled and refused again at each such draw. Compute refusals still fail. -- [Pending storage-image results over memory outside the write watch](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) leave to the CPU the 64 KiB blocks whose bytes differ from the ones saved at the upload, the fill, the image copy, the previous store or the moment their range left the watch (`Unwatch`, or a host import that does not stay watched), and the blocks the watch saw the CPU write before that moment. A store made on the GPU saves its bytes through a host-visible copy that becomes the saved bytes when its batch completes; a store or a refresh that needs them earlier waits for that batch. A unit without a write generation saves the guest bytes when results land in it outside the watch. One whose range leaves the watch while its results are pending saves its bytes through the unwatch hook, which also runs when a failed collect takes a range out of the watch. A range that leaves the watch without the hook running over it (an import fallback that unwatched without saving) leaves its pending results without saved bytes; storing such results throws. A unit whose results were stored while its blocks were watched keeps only a generation; when its blocks leave the watch while nothing is pending, the hook saves nothing, and results that land in it afterwards have neither saved bytes nor a watch, so storing them throws too. +- [Pending storage-image results over memory outside the write watch](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) leave to the CPU the 64 KiB blocks whose bytes differ from the ones saved at the upload, the fill, the image copy, the previous store or the moment their range left the watch (`Unwatch`, or a host import that does not stay watched or whose page walk fails), and the blocks the watch saw the CPU write before that moment. A store made on the GPU saves its bytes through a host-visible copy that becomes the saved bytes when its batch completes; a store or a refresh that needs them earlier waits for that batch. A unit without a write generation saves the guest bytes when results land in it outside the watch. One whose range leaves the watch while its results are pending saves its bytes through the unwatch hook, which also runs when a failed collect takes a range out of the watch. A range that leaves the watch without the hook running over it leaves its pending results without saved bytes; storing such results throws. A unit whose results were stored while its blocks were watched keeps only a generation; when its blocks leave the watch while nothing is pending, the hook saves nothing, and results that land in it afterwards have neither saved bytes nor a watch, so storing them throws too. +- [Storage images over uncommitted guest memory](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) - a storage image whose guest range has no committed page when it is created throws in the `StorageTexture` constructor, which aborts the guest. Several titles reach this intermittently in runs of a few minutes, on AMD and NVIDIA alike; in two of the three logs, lines about storage images over released or rewritten memory (`memory no longer registered`, `guest memory changed while GPU results were pending`) come shortly before it. The log does not name the image's address. What the console does with a storage descriptor over memory that is not committed is unknown, and which allocation the guest recreates there was not found. - Windows host-pointer imports of the guest mapping itself are compared instead of write-watched, except writable imports of private `MEM_WRITE_WATCH` memory when the import probe keeps the write watch and a settle queue exists: those of 1 MiB or more stay watched, and smaller ones return to the watch once their settle has completed, unless a storage image overlaps them (see Pages under an import window): a driver can mark imported pages dirty after the synchronous import window. Shared memory imported through a separate writable alias retains protection-based tracking on the guest views; driver access to the import alias does not disarm those views. [GuestMemory](../../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) excludes every overlapping 64 KiB tracker block while its backing is retained. A full block replaced by fresh private backing regains tracking and gets a new generation; protection changes, partial replacements and shared remappings do not restore it. Excluded blocks keep their write-watch marks as a hint only (`UntrackedHint`): a storage image or a sampled texture snapshot over them compares its bytes again after a mark in its range, a driver store, a mapping change or a recorded GPU write instead of at every collect epoch or lookup, and a mark the driver makes without a CPU store costs one comparison. This preserves CPU edits through byte comparisons but limits snapshot reuse on excluded blocks to draws of one collect epoch and disables unit shadows for them; restoring tracking on import retirement without replacing the backing remains unimplemented. -- [Pages under an import window](../../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) (libSceAgcDriver) whose write protection was resolved while a host import that leaves its range watched was made are compared with the bytes they held before the import and count as stored over only when those differ. A CPU store of the bytes already there within the window cannot be told apart from an import that resolves the write protection without storing, as the NVIDIA driver (610.57.04) does for every imported page, so such a store does not win over pending results. Where host-pointer and dma-buf imports differ in whether they stay watched, a failed host-pointer import and the dma-buf import after it are two windows, and the pages the failed one resolved count as stored over. On Windows, the NVIDIA driver (617.42) marks every page of a host import of private `MEM_WRITE_WATCH` memory written, asynchronously, until a GPU use of the import completes; a 4-byte read of it on the settle queue ends that even while main-queue work that used the import first is still running, and an earlier import of the same pages whose first use runs after that read marks none of them (`agc_import_settle_first_use`, `agc_import_settle_earlier_import`). Such an import stays watched only after a 4-byte read of it on the settle queue has completed inside the window. Each one waits for that read, which keeps its window open for one round trip on the settle queue, so a same-value CPU store from another thread in that time counts as unchanged too. An import made where no settle queue exists is compared instead. An import of under 1 MiB is not settled inside its window, because the settle blocks the importing thread and a title that imports a 64 KiB buffer around every submission lost about 19 % of its frame rate to that wait (Dripl1x, #6060). It is compared until its read, submitted in a batch by a later import lookup under `GpuMutex` while the imports match the registry, has completed; a later lookup then returns its whole blocks to the write watch, each stamped as a CPU store, so whatever was derived from them while they were compared reads as changed once. Its blocks stay compared while storage results are pending over them, and those of an import retired before that stay compared. +- [Pages under an import window](../../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) whose protection an import resolves count as stored only when their bytes change. A same-value CPU store during that window cannot be distinguished from driver activity and does not win over pending results. On Windows, NVIDIA 617.42 marks every private MEM_WRITE_WATCH page after import until a GPU use completes. The driver behavior was measured on this driver; other drivers are unverified. Imports of at least 1 MiB wait for a four-byte read on the separate settle queue inside the window. Smaller imports stay compared until a deferred read completes; a later lookup returns their pages to write watching, stamping their blocks as CPU stores; the rest of a partly covered block stays compared. Blocks overlapping a live storage image, blocks of an import retired before rewatching, and imports without a settle queue stay compared. Shared-memory imports retain their separate-alias tracking. +- [Deferred host-import reads](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) use the main queue with barriers ordering earlier and later writes around the reads. Submission does not wait on the CPU. An initial multisampled DCC import needing a range whose import still awaits its read submits it if needed and waits under the GPU mutex (`WatchedAfterSettle`, #9724). Imports of partial 64 KiB blocks get no deferred read; only that path reads them, returning their pages as CPU stores unless a live, trimmed or parked compared import or storage image shares their blocks; one a recorder holds is unseen. The latency cost of placing batches behind main-queue work is unmeasured. The first-use test now checks that earlier main-queue reads finish before the import returns to write watching; the original two-queue canary measured driver marking while those reads were still in flight. The earlier-import canary still checks later use of another import of the same pages. - [Settled dma-buf imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver): on Linux a dma-buf import of 1 MiB or more is read by a 4-byte copy on the settle queue right after it is made (`settleImport`), so the kernel maps its pages for the GPU there and not in the first submission that uses it (about 2 s for a 3 GiB import on RADV). The import's buffer is shared with the settle queue's family. An import made at map time settles on the guest thread that maps the range, inside its map call, so that thread waits while the kernel maps the pages: 0.47 to 2.2 s for the 3 GiB range of Hades (PPSA03355) and 0.49 to 4.3 s for the 4.5 GiB range of ASTRO BOT (PPSA21567) on RADV. Both titles map these ranges at start-up; a title that maps large ranges during gameplay stalls its mapping thread for as long. An import made from a submission settles under the GPU mutex, so its frame still pays for it; a smaller import, and every import on a device with no settle queue (one without a copy-capable queue besides the main one), pays in its first submission. A range imported through a host pointer, as lavapipe imports shared memory, is not settled - [Sparse dma-buf imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver): on Linux a shared-backed range larger than one 64 MiB udmabuf buffer is imported as one sparse buffer bound to the 64 MiB buffers, on a queue of its own when the device has a second family that binds sparse memory. The path is decided before anything is imported, and a failure after that throws. A range whose offset in the backing file is not a multiple of 64 KiB starts its sparse buffer at the 64 KiB block below it (`HostImport::lead`). A range takes the single udmabuf import instead, which udmabuf's `size_limit_mb` refuses above 64 MiB by default so the range is copied, when the buffer's sparse block size is larger than 64 KiB (it is 64 KiB on RADV), when the sparse buffer does not accept every memory type a chunk import can have, when the backing file ends inside its last chunk, or when it is imported at map time on a device with no second family (the bind then needs the GPU mutex); the NVIDIA proprietary driver imports no dma-buf memory +- [Presentation under the GPU mutex](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (libSceAgcDriver) - on a device whose presentation family has no queue left after the main and settle queues, such as RADV, lavapipe and Intel's Windows driver (Arc Graphics on Meteor Lake, 101.9150), which expose a single graphics queue, `vkQueuePresentKHR` still runs on the main queue under `GuestMemory::GpuMutex`, so the draw committer and the queue workers wait for as long as the present blocks. How long that is on those drivers was not measured; on NVIDIA in FIFO mode it is about 6 ms per present. A device with a free queue in that family presents on it under its own mutex, after the GPU mutex is released. Closes when: a trace on a single-queue device shows no `GpuMutex.wait.*` behind `Vulkan.QueuePresent`. - [Unnormalized S# coordinates](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) - `FORCE_UNNORMALIZED` uses a Vulkan `unnormalizedCoordinates` sampler, so the S# LOD range, LOD bias and mip filter are not applied, which is exact only on a one-level view. - An [unnormalized S#](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) maps clamp mode 0 (repeat) on X and Y to clamp-to-edge as required by Vulkan unnormalized coordinates, and assigns the magnification filter when minification and magnification filters differ (since unnormalized coordinates sample at LOD 0). Anisotropy, TRUNC_COORD, MC_COORD_TRUNC and other clamp modes still throw. Secondary LOD bias (LOD_BIAS_SEC, bits [19:14]) is accepted and ignored, matching RDNA2 hardware measurements across explicit and implicit samples. High on Life (PPSA15388, #4736) uses secondary LOD bias and unnormalized coordinates with clamp mode 0 and differing min/mag filters during home base and city rendering. - [TRUNC_COORD](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) is not applied: the texel a point sample reads and the bilinear weights follow the host sampler's rounding. On RDNA 2 (RX 6700 XT), an S# with TRUNC_COORD 0 rounds the exact scaled coordinate to 8 fractional bits, ties to even, and one with TRUNC_COORD 1 truncates it, for point and bilinear filters. AMD's Windows driver (32.0.21045.5002) truncates for nearest samplers and rounds for linear ones. Under it, a TRUNC_COORD 0 point sample at most 1/512 texel below a texel edge reads the texel below the edge where the console reads the one above, and `k / width` computed in f32 lands there for about half of all k. A TRUNC_COORD 1 bilinear weight can be 1/256 above the console's. Other host drivers are not measured. @@ -124,43 +151,49 @@ - A [COND_EXEC range](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown. - A [COND_INDIRECT_BUFFER](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (`sceAgcCbBranch`) runs only with the always-pass compare function; a comparison or a predicated packet throws. Modes 1 and 2 run the first buffer, assumed to be the if-then and if-then-else modes of AMD's PM4 packet. [sceAgcCbBranch](../../../core/libs/prx/libSceAgc/Cb/src/Branch.cpp) does not check the compare address of an always-pass branch or the address of an empty buffer, as PPSA14632 passes an uninitialised compare address and a null else buffer of 0 dwords. - A [label or store](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (`RELEASE_MEM`, `WRITE_DATA`, `DMA_DATA`) into guest memory the device has no host import of waits for all recorded work and is then written by the CPU, so the queue worker stalls once per such label. The console's CP stores it in stream order without that stall. +- A [label](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) of 8 bytes or less recorded while a copied writer is listed, or while an earlier completion store is pending, is stored by the CPU when its batch is reaped, after the write-backs, so it becomes visible up to one reap later than on the console, also after GPU writes recorded behind it. Larger stores (DUMP_CONST_RAM, COPY_DATA, DMA_DATA) and DMA_DATA copies the GPU makes stay on the GPU unless they cover a copied writer's range or a pending completion store, so they can be visible before an unrelated earlier write-back. The order also breaks when a write-back's guest store syncs a pending storage image from inside the completion: that nested sync finishes later batches, whose completion stores land first. `APS5_DRAIN_COMPLETION_LABELS=1` drains instead (reason 2). - [WRITE_DATA to thread-trace user-data registers](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver) updates the queue's UCONFIG values for register destinations `0xc340..0xc347`, including repeated writes to one address; these are not guest-memory labels. The range follows AMD's GFX10 `SQ_THREAD_TRACE_USERDATA_0..7` definitions in [the Linux register header](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/include/asic_reg/gc/gc_10_1_0_offset.h) and the packets emitted by PPSA21564's bundled AGC driver before its EOP flip. Other register destinations throw. GPU thread-trace capture and its user-data stream are not emitted; only the final register state is retained. Console trace behavior is unverified. -- [WRITE_DATA with DST_SEL 3](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver) writes guest memory like DST_SEL 5. AMD's GFX10 header names 3 the GDS, but Killzone: Liberation (PPSA11142) writes its frame fence with `sceAgcDcbWriteData(dst 6, cache policy 3, write confirm)`, which the SPRX-confirmed encoding ([OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/cb_builders.c)) turns into DST_SEL 3 with a 40-bit label address in the title's own memory that `RELEASE_MEM` and `WAIT_REG_MEM` then use; the console must write that label or the title would wait on it forever. Whether the console routes it through the GDS or a cache is unknown +- [WRITE_DATA with DST_SEL 3](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver) writes guest memory like DST_SEL 5, which a PS5 does not: on firmware 13.40 (measured 2026-10-19 with buffers submitted through `sceAgcDriverSubmitDcb` inside a retail title) a DST_SEL 3 `WRITE_DATA` (control 0x00100300, or 0x06100300 with cache policy 3) left its label in direct memory unwritten, also after a `RELEASE_MEM` with GL2 write-back and invalidate (0x00300514), and a GDS `COPY_DATA` (SRC_SEL 3) at that address read 0. The console's own `sceAgcDcbWriteData(dst 6)` builds DST_SEL 3 (0x00110300) as here, and Killzone: Liberation (PPSA11142) fences its frame with it and waits on that label with `WAIT_REG_MEM`, so the write is kept until the path that lands the label is known. Closes when: that path is measured - [SET_*_REG_INDIRECT](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) reads its register list when the command buffer is submitted, with the packets. The console's graphics PFP reads it when it reaches the packet, so a list written after the submission (by the CPU, or by GPU work ahead of the packet behind PFP_SYNC_ME) is read here before that write. When the list read at submission holds an offset dword that is not a register offset (bits 16-27 set, or the 0xffffffff sentinel), the worker reads the list again where the packet executes and writes that copy. PPSA23566 (UFC 6) intermittently submits a SET_CONTEXT_REG_INDIRECT whose first entry is 0x038f038f or 0x03900390 in both copies; what the PFP does with such an entry is unknown, so a copy that still holds one throws unless `APS5_SKIP_INVALID_INDIRECT_REGISTERS=1`, which skips its invalid entries and writes its valid ones. -- The [shader float mode](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, their f16 forms, `v_min_f64` and `v_max_f64`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, `FP16_OVFL` by every instruction with an f16 result (see its entry), and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32. `v_min_f64` and `v_max_f64` quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0. `v_min_f16` and `v_max_f16` flush each denormal input to its signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3 preserve inputs, even when output flushing is enabled. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1, including signed zeros, NaNs and neg/abs modifiers. Unknown modes keep f16 denormals. `v_min_f64` and `v_max_f64` flush subnormal inputs to signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3, and an unknown mode, preserve inputs. The fully flushed and preserved f64 behavior was [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/2036); split-mode selection follows [LLVM's denormal-mode enumeration](https://llvm.org/docs/AMDGPUUsage.html#floating-point-denorm-mode-enumeration-values). Other f64 arithmetic does not model these denormal controls. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. +- The [shader float mode](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, their f16 forms, `v_min_f64` and `v_max_f64`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, `FP16_OVFL` by every instruction with an f16 result (see its entry), and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32. `v_min_f64` and `v_max_f64` quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0. `v_min_f16` and `v_max_f16` flush each denormal input to its signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3 preserve inputs, even when output flushing is enabled. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1, including signed zeros, NaNs and neg/abs modifiers. Unknown modes keep f16 denormals. `v_min_f64` and `v_max_f64` flush subnormal inputs to signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3, and an unknown mode, preserve inputs. The fully flushed and preserved f64 behavior was [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/2036); split-mode selection follows [LLVM's denormal-mode enumeration](https://llvm.org/docs/AMDGPUUsage.html#floating-point-denorm-mode-enumeration-values). `v_fma_f64`, `v_div_fmas_f64`, `v_div_scale_f64`, `v_ldexp_f64`, `v_rcp_f64`, `v_rsq_f64`, `v_sqrt_f64` and `v_trig_preop_f64` do not model these denormal controls. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A registered shader keeps that mode when a title rewrites RSRC1; a raw compute program takes the live RSRC1. - A [WAIT_REG_MEM](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp) (libSceAgcDriver) that a compute queue passes on another queue's recorded label store, before the GPU has executed it, is landed before the queue's next dispatch, draw, COND_EXEC or predicated packet reads guest memory: the worker polls until memory satisfies the wait or no unfinished batch writes the label. The graphics queue (queue 0) is exempt, as landing its waits costs PPSA01341 about a quarter of its frame rate; a title that fills memory a graphics command buffer reads after it submits the buffer behind such a wait reads it early there. `APS5_LAND_WAITS_ALL_QUEUES=1` lands the graphics queue's waits too. - [CPU reads behind an address-based build](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp) (libSceAgcDriver) - an address-based store (FLAT/GLOBAL, or through a descriptor the recompiler cannot resolve to a static buffer) is not recorded as a pending write: its batch holds a lease on the registered allocations instead, and the flush hook does not wait for it. A DISPATCH_INDIRECT whose arguments are read on the CPU first finishes the batches recorded with a lease (`Graphics::FinishLeaseWork`). These reads do not, so they can see guest memory from before such a build ran: the indirect draw arguments and draw count of a draw resolved on the CPU (`Pm4::ReadDrawArguments`, `Pm4::ReadDrawCount`), the count record of the [copy kernel](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp) HLE (guarded only by its shape check), and the checks in [Submission.cpp](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) that decide whether a queue awaits a label only the title writes (`awaitsTitle`, `orderReleased`). No test covers them and no title has been seen to read such memory there. - The [dispatches that follow a compute queue's wait](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp) (libSceAgcDriver), up to the next wait that memory does not satisfy, a packet that writes guest memory other than a label, or a conditional packet, are resolved (memory captured, shader recompiled) when the wait passes, as the CP reads them right after it; the graphics queue is exempt (`APS5_GROUP_CAPTURE_ALL_QUEUES=1` includes it). The device still reads guest memory when the dispatch is issued; when that memory no longer matches the capture, the dispatch is resolved again from current memory. - [PA_CL_CLIP_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `CLIP_DISABLE` (bit 16) and `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) are accepted without changing pipeline state: Vulkan clips primitives against viewport guard bands and clips `NoPerspective` outputs linearly; what clearing bit 24 or setting bit 16 changes on the console beyond standard clipping is undocumented. - [10_11_11 unorm color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export, rounded to nearest even like format-30 image stores, which is assumed to match the CB's conversion. -- [11_11_10 float color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export toward zero like format-43 image stores (overflow to the largest finite value, negatives to zero, NaN kept), which is assumed to match the CB's conversion; only the reversed component order (COMP_SWAP 2) is accepted. -- [PA_SU_SC_MODE_CNTL.PROVOKING_VTX_LAST](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp) (libSceAgcDriver) uses Vulkan's last-vertex mode for native primitives. Bit 19 follows [opengnm's `flatshade_first` mapping](https://github.com/blackbearreloaded/ps5-opengl/blob/db752da4c89380af228497b5b6852163a4e61069/src/gallium/ps5/ps5_screen.c#L2244); vertex selection follows [Vulkan](https://docs.vulkan.org/spec/latest/chapters/drawing.html#drawing-primitive-topologies), without console measurements. A [rect-list](../../../core/shader/recompiler/SpirvBackend/src/RectListShaders.cpp) draw in last mode gives all four corners of each rectangle the flat inputs of its third vertex through the generated control stage, as first mode gives them those of its first, so Vulkan's choice of provoking vertex for the tessellated triangles does not matter; which vertex the console takes for a rectangle is not measured either. A geometry program's primitives keep the vertex indices of its own primitive export on the [mesh path](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp), so last mode takes their flat inputs (from [Vulkan](https://docs.vulkan.org/spec/latest/chapters/vertexpostproc.html#vertexpostproc-flatshading)) and their [layer](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) from the last index of each primitive, while their per-vertex inputs keep the order of first mode ([Vulkan](https://docs.vulkan.org/spec/latest/chapters/primsrast.html#primsrast-barycentric)); that the console does the same for an exported primitive is not measured. Last mode throws without `VK_EXT_provoking_vertex`, except for the [generated barycentric tessellation stages](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp), which write the last vertex's flat values at every corner, for tessellation primitives, for triangle strips or fans assembled for a geometry program (whose odd-triangle order on the console is not measured), explicit per-vertex flat inputs, and triangle strips whose pixel shader reads a vertex slot itself (`v_interp_mov_f32`, or 16-bit interpolation, which is not checked yet), since the vertex order of odd strip triangles [depends on the device](https://docs.vulkan.org/refpages/latest/refpages/source/VkPhysicalDeviceFragmentShaderBarycentricPropertiesKHR.html). `v_interp_p1_f32` and `v_interp_p2_f32` read P10 and P20 against the guest's I and J, which come from `gl_BaryCoordKHR` in the same vertex order as the per-vertex values, so the order cancels out and strips with only those reads draw. A FLAT_SHADE input without pass-through that is read through `v_interp_mov_f32` P10 and not P20 [returns the provoking vertex's value](../../../core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp), like P0. ps5-opengl reads every flat input that way, and its driver passes the GL 4.6 CTS on a PS5 (#1413). The lane itself was not measured on the console, so with the first provoking vertex such a shader throws. +- [11_11_10 float color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export toward zero like format-43 image stores (overflow to the largest finite value, negatives to zero, NaN kept), which is assumed to match the CB's conversion; only the standard (COMP_SWAP 0) and reversed (COMP_SWAP 2) component orders are accepted, putting red and blue in the 10-bit low field. That follows from the format's layout and from the standard swap Mesa pairs with COLOR_10_11_11 for R11G11B10_FLOAT (ac_formats.c); it was not measured on the console. +- [PA_SU_SC_MODE_CNTL.PROVOKING_VTX_LAST](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp) (libSceAgcDriver) is not measured on the console: bit 19 follows opengnm's `flatshade_first` and Vulkan's vertex selection; rect lists take flat inputs from the third vertex (first mode: first); a geometry program gets odd strip triangle i as (i+1, i, i+2) in both modes (an RX 6700 XT, first mode: (i, i+2, i+1)); in last mode its primitives take flat inputs and layer from the last index, per-vertex ones in first-mode order; a flat input without pass-through read only via `v_interp_mov_f32` P10 (P20) is the provoking vertex's (ps5-opengl, #1413; first (last) mode throws). Barycentric tessellation stages give each corner the last vertex's flat values. Without `VK_EXT_provoking_vertex`, last mode throws only when a pixel shader input could show which vertex provokes (a flat input, a P10 flat read, a per-vertex or custom per-vertex input, or a `Flat` or `PerVertexKHR` decoration in its SPIR-V, ignoring built-ins the pixel stage generates) or another stage's SPIR-V decorates a `Layer` or `ViewportIndex` built-in that is not `PerPrimitiveEXT`; otherwise the draw uses first mode, since the provoking vertex then selects nothing it reads or writes. With the extension, last mode throws for tessellation, geometry-program fans, explicit per-vertex flat inputs, and strips whose pixel shader reads a vertex slot (`v_interp_mov_f32`; 16-bit interpolation unchecked). +- [Triangle strips through the barycentric tessellation stages](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - without `geometryShader`, a triangle strip without primitive restart is expanded on the CPU into one three-point patch per triangle in Vulkan's strip order: (i, i+1, i+2), and for odd triangles (i, i+2, i+1), or (i+1, i, i+2) with `PROVOKING_VTX_LAST`. That is the order the geometry stage receives, so both routes give the same weights and per-vertex values; which order the console uses for odd triangles is not measured. Indirect strips throw. - [Dual-source blending](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) takes the MRT1 export as the second source of the MRT0 blend, as RADV and radeonsi program it; what the CB does with other export or target layouts is unknown, so they throw. - [Blending into reversed-order color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 8_8_8_8 and 16_16_16_16 targets with `COMP_SWAP` 2 or 3 keep the exported alpha in their first channel and red or blue in their fourth, which Vulkan would blend with the color and the alpha equation respectively. The color block is taken to blend in shader export order and apply `COMP_SWAP` afterwards: the alpha equation and every source-alpha factor use the exported alpha, and the destination alpha is the first channel. This follows the `CB_TARGET_MASK` text of the AMD [SI](https://xorg.freedesktop.org/docs/AMD/old/SI_3D_registers.pdf) (p. 229) and [CIK](https://xorg.freedesktop.org/docs/AMD/old/CIK_3D_registers_v2.pdf) (p. 238) register references ("the order of components after blending and before COMP_SWAP is applied"). RADV builds `CB_BLENDn_CONTROL` without regard to the swap ([radv_pipeline_graphics.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/vulkan/radv_pipeline_graphics.c#L650-L709)) and renders R4G4B4A4, B4G4R4A4, R5G5B5A1 and B5G5R5A1 with `SWAP_STD_REV` and `SWAP_ALT_REV` ([vk_format.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/vulkan/util/vk_format.c#L43-L48), [ac_formats.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/common/ac_formats.c#L609-L624)). Mesa CI's Navi10 (gfx1010) CTS run ([job 112381553](https://gitlab.freedesktop.org/mesa/mesa/-/jobs/112381553), [112381554](https://gitlab.freedesktop.org/mesa/mesa/-/jobs/112381554)) passes all 400 `blend.format..states` cases of each of those formats, including separate alpha and destination-alpha factors. It is not measured on gfx1013 or on 8_8_8_8 and 16_16_16_16 targets. Factor pairs Vulkan applies per channel are used directly. Otherwise the pixel shader only copies values: it writes the exported alpha, each channel's own export or the constants 0 and 1 into a second color at location 0 index 1 (and 1 into a channel of the first color whose source factor is ONE, moving its export into the factor), and the blend forms every factor, `1 -` and product from those copies through `SRC1_COLOR`/`SRC1_ALPHA` factors. The blend unit therefore rounds them as it rounds a plain Vulkan blend of the same export; Vulkan does not require a second-source color to be converted like the first, but on an RTX 5080 and lavapipe every reversed-order case of the execution test is bit-identical to the same draw into a `SWAP_STD` target, which the test checks. Factor combinations that copies cannot express (for example `ONE_MINUS_SRC_ALPHA` in the alpha equation with `SRC_COLOR` in the color equation), sRGB, SNORM and integer targets, constant and second-source factors, destination-alpha and `SRC_ALPHA_SATURATE` factors in the color equation, different color and alpha operations, MIN or MAX with factors other than ONE, other color exports when the blend needs the second color, and devices without `dualSrcBlend` throw. Gran Turismo 7 (PPSA01317) blends `CB_BLEND0_CONTROL` 0x65010504 into such a target (#4782). - [DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp) (libSceAgcDriver) becomes `DepthLess`/`DepthGreater`. For a pixel shader that breaks the promise, the host driver decides whether its interpolated Z is depth-tested before shading; what the console does then was not measured. - [DB_SHADER_CONTROL.STENCIL_TEST_VAL_EXPORT_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) becomes `FragStencilRefEXT` when available, taken from the second MRTZ channel of the 32_GR and 32_ABGR formats as Mesa's AMD drivers write it ([`emit_ps_mrtz_export`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/3e32b0e1f132231b7a972c1b0b5b06c7e16cf8b9/src/amd/common/nir/ac_nir_lower_ps_late.c#L194)); the console was not measured. The host value is the test value of both faces and what `REPLACE_TEST` writes, so a stencil operation that writes all ones or the operation value throws with it, as do a stencil clear draw and the UINT16_ABGR layout. A device without the extension uses the bounded replay described above. On such a device a draw first drops the export when the reference cannot change its result (`StencilReferenceUnused`: no stencil test or attachment, or on both faces a compare that ignores it and no reachable `REPLACE` through a non-zero write mask), and its pixel shader is then compiled at its first draw, not when its ABI is resolved. A device with the extension keeps the export, so each such shader keeps one pixel variant. - [DB_SHADER_CONTROL.STENCIL_OP_VAL_EXPORT_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - the pixel shader supplies the stencil operation source value per pixel, in place of `DB_STENCILREFMASK.STENCILOPVAL`. Only the operations that take that value read it: `REPLACE_OP` and the clamped and wrapping add and subtract (codes 4, 5, 6, 8 and 9), and the logic operations, which throw already ([AMD_stencil_operation_extended](https://registry.khronos.org/OpenGL/extensions/AMD/AMD_stencil_operation_extended.txt), [AMD_shader_stencil_value_export](https://registry.khronos.org/OpenGL/extensions/AMD/AMD_shader_stencil_value_export.txt)). Vulkan has no per-pixel operation value. Without `STENCIL_TEST_VAL_EXPORT_ENABLE`, a `REPLACE_OP` reachable through a non-zero write mask (`StencilOperationValueReplaces`) becomes `REPLACE` with the exported value as `FragStencilRefEXT`, or the bounded replay without the extension; this requires a compare that ignores the test value and no `ONES` or `REPLACE_TEST` on either face, and early tests throw. The value is taken from bits 8-15 of the second MRTZ channel: the PPSA27836 pixel shader that uses it shifts it left by 8 before `exp mrtz` (`v_lshlrev_b32 v0, 8, v0`), which is the only evidence for the layout. The add and subtract operations, and `REPLACE_OP` with a test value export, throw. With the 32_GR or 32_ABGR Z format and no such operation, or without a stencil test, the export cannot change the result and is ignored. Other Z formats throw. The console was not measured. +- [Stencil logic operations](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `STENCIL_NAND` and `STENCIL_NOR` (`DB_STENCIL_CONTROL` 13 and 14, [PS5_Mesa](https://github.com/mihawk-99/PS5_Mesa/blob/0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8/src/amd/registers/gfx103.json#L581-L598)) throw. AND, OR, XOR and XNOR (10, 11, 12 and 15) apply `STENCILOPVAL` as [AMD_stencil_operation_extended](https://registry.khronos.org/OpenGL/extensions/AMD/AMD_stencil_operation_extended.txt) defines them, not measured on the console, drawn as `ZERO`, `REPLACE` with those reference bits set, or `INVERT` under the write mask narrowed to the bits they change. A face whose other operations write other bits throws, as does OR on a compared test value bit it would set, or with a stencil test value export. - [DB_SHADER_CONTROL.DEPTH_BEFORE_SHADER](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp) (libSceAgcDriver) becomes `EarlyFragmentTests`, and so does `Z_ORDER` `EARLY_Z_THEN_LATE_Z` or `EARLY_Z_THEN_RE_Z` without kill, Z export, mask export or stencil export; with alpha-to-coverage the tests stay late. The meaning of the bit and of `Z_ORDER` 3 follows Mesa (`src/amd/registers/gfx10.json`; RADV sets the bit for `early_fragment_tests` in `src/amd/vulkan/radv_shader.c`). What the console does with `DEPTH_BEFORE_SHADER` and a Z export, a mask export, a stencil export or alpha-to-coverage together was not measured. - [PA_SC_CONSERVATIVE_RASTERIZATION_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 0x6001 uses Vulkan overestimation on devices that add at most 1/256 pixel and keep degenerate triangles; the console's uncertainty region and degenerate handling are unmeasured. A value with `OVER_RAST_ENABLE` and `UNDER_RAST_ENABLE` clear is decoded as disabled even when `UNCERTAINTY_REGION_MODE` or the `OUTER_`/`INNER_UNCERTAINTY_EDGERULE_OVERRIDE` bits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured. With `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` = 0 the `PERSP_CENTROID` and `LINEAR_CENTROID` inputs get the pixel-centre barycentrics, as without conservative rasterization ([shader debt](shader.md)): the one rasterized sample sits at the pixel centre, so every centroid the rasterizer could pick for an overestimated pixel is that point; that it does not move the centroid towards the primitive when the sample is uncovered is inferred, not measured. With more rasterized samples, and when a single-sample draw is raised to a multisampled color target, centroid inputs throw. - [Descriptor pool chain](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - each pool holds two storage buffer descriptors more than a set can take, as a workaround: RADV (Mesa 26.0 and later) lets a `FREE_DESCRIPTOR_SET` pool place a set up to 32 bytes past the pool's buffer ([`util_vma_heap_init`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/vulkan/radv_descriptor_pool.c) is given `bo_size + RADV_POOL_HEAP_OFFSET` as a size), and with a page-multiple pool size those bytes are an unmapped page - [CMASK fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - each fast-cleared CMASK tile (code 0) is stored as `CB_COLOR_CLEAR_WORD` into its 8x8 texels at the first draw into the target or at its fast-clear eliminate or DCC decompress pass, not as the color block writes the tile, and the CMASK is then stored as expanded (0xF): a texture or copy read of the target after that draw and before the title's eliminate sees the clear value in tiles no draw wrote, where the console keeps their previous bytes. Tile addresses follow the pipe-aligned SW_64KB_Z_X CMASK equation for 16 pipes; other CMASK swizzle modes, a cleared tile of a DCC target and a fast clear of a 3D or array color target throw; base mip 0 of a 2D mipmapped color target is supported (offset follows AMD Addrlib `Gfx10Lib::HwlComputeCmaskInfo` reverse mip-chain ordering preceding base mip 0, unverified on PS5 hardware; mipmapped targets with 8 or 16 bpp throw until `dsMipmapHtileFix` mip-tail shrinkage is modelled). A tile with a code other than 0 and 0xF keeps its texels like an expanded tile: PPSA18391 (engine: Clickteam Fusion) fast clears a target whose CMASK it never initialized, so some of its nibbles hold leftover values such as 1 and 10; what the console does with such codes on a single-sample target is unverified on hardware. Single-sample views with `viewMip > 0` skip CMASK fast-clear decoding since CMASK metadata only covers base mip 0 on AMD hardware (Mesa `si_state.c` similarly clears `FAST_CLEAR` for level > 0). -- [CMASK clear pass on AMD without pipeline libraries](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp) (libSceAgcDriver) - the compute pass that stores a fast-cleared CMASK into a resident render target (`DispatchCmaskClear`) is unverified on AMD's Windows driver with `APS5_NO_GPL=1`: on a Radeon 860M (driver 32.0.13058.8001, Windows 11) `agc_driver_cmask_fast_clear` keeps the stale 0x5A5A5A5A in its `SW_64KB_R_X` pass instead of the clear value, while it passes with pipeline libraries on and on an NVIDIA RTX 4080 SUPER in both modes. An RX 6400 (driver 32.0.21033.3005) lost the device (read invalid at 0xbeefdeadc000) in an earlier build tree, but clean rebuilds of four main commits of 19 October pass there (three of three runs each), so that failure is not reproduced. The cause is not found (#5150). Closes when: the cause is found, or `ctest -R '^agc_driver_cmask_fast_clear$' --repeat until-fail:5` passes with `ANYPS5_GPU=Radeon` and `APS5_NO_GPL=1` on AMD. -- [HTILE depth fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - HTILE is not modeled. Only RADV's `clear_htile_mask` compute kernel (17 GFX10 words, 64x1x1 groups) is recognized, and only when it leaves ZMASK at 0 (`user[4]` and `user[5]` low nibbles both 0); the dispatch still runs, and the host depth image is cleared to `DB_DEPTH_CLEAR` the next time the surface with that `DB_HTILE_DATA_BASE` address is bound. The address match is exact, so a clear of a different HTILE range does not hit. HTILE initialization, the stencil-only update, and fast clears made by the SDK's own paths, CP DMA fills or other kernels are not covered and leave the depth image as it was. The kernel words are the ones the PR author saw RADV submit; they are not compared with Mesa's source. A second kernel, the 9-word dword fill PPSA01802 (Kena: Bridge of Spirits) clears its depth HTILE with (64x1x1 groups, one `buffer_store_format_x` per thread through the stride-4 V# in `user[0..3]`, the value read by `s_buffer_load_dword` through the V# in `user[4..7]`), is taken as a fill of that range when the store format is 32_UINT, 32_SINT or 32_FLOAT and the value V# covers the dword (NUM_RECORDS times its stride, or NUM_RECORDS bytes when the stride is 0) ([`MatchMemoryValueFill`](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp), then the whole-HTILE and ZMask rules of `NoteDepthMetadataFill`). The value is read from guest memory when the dispatch is decoded, so a value that earlier GPU work in the same submission has not written back yet is not seen. When the [fill HLE](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp) takes the dispatch instead (an output descriptor of one 32-bit value per element, a value descriptor that covers the loaded dword, a 16-byte aligned range), the value is read again when the fill is recorded, after the labels queued before it, and the HTILE fill is noted with that value. +- [HTILE depth fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - HTILE is not modeled. Only RADV's `clear_htile_mask` compute kernel (17 GFX10 words, 64x1x1 groups) is recognized, and only when it leaves ZMASK at 0 (`user[4]` and `user[5]` low nibbles both 0); the dispatch still runs, and the host depth image is cleared to `DB_DEPTH_CLEAR` the next time the surface with that `DB_HTILE_DATA_BASE` address is bound. A clear hits a surface only when its range covers that surface's whole HTILE, which need not start where the clear does. Fast clears that other writers leave in HTILE are taken at bind as the next entry describes. The kernel words are the ones the PR author saw RADV submit; they are not compared with Mesa's source. A second kernel, the 9-word dword fill PPSA01802 (Kena: Bridge of Spirits) clears its depth HTILE with (64x1x1 groups, one `buffer_store_format_x` per thread through the stride-4 V# in `user[0..3]`, the value read by `s_buffer_load_dword` through the V# in `user[4..7]`), is taken as a fill of that range when the store format is 32_UINT, 32_SINT or 32_FLOAT and the value V# covers the dword (NUM_RECORDS times its stride, or NUM_RECORDS bytes when the stride is 0) ([`MatchMemoryValueFill`](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp), then the whole-HTILE and ZMask rules of `NoteDepthMetadataFill`). The value is read from guest memory when the dispatch is decoded, so a value that earlier GPU work in the same submission has not written back yet is not seen. When the [fill HLE](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp) takes the dispatch instead (an output descriptor of one 32-bit value per element, a value descriptor that covers the loaded dword, a 16-byte aligned range), the value is read again when the fill is recorded, after the labels queued before it, and the HTILE fill is noted with that value. - [HTILE written by other kernels](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - when a depth target is bound and its HTILE was written since it was last read, the words over its whole pipe-aligned metadata (`HtileMetadataBytes`, the extent `HtileFillCovers` takes) are read, outside the surfaces mutex since the read waits for recorded work: when every one marks the depth (ZMask 0) or the stencil (SMem 0) cleared, the surface takes that fast clear and the words are stored back as expanded (depth only: ZMask 15 over the full Z range; with stencil: ZMask 15, Z range and SMem, SR1 and SR0 3), so only the title's next clear clears it again. PPSA29141 clears its depth every frame with a compute kernel the fill hooks do not recognize (`InvokeHTileClearComputeShader` in its `ClearMRT` pass); without this its depth kept every earlier frame and moving objects left black trails. PPSA25344 (Unreal Engine 5) reads its HTILE after the depth prepass in its Nanite depth export, which takes a tile with ZMask 0 as holding the clear value; without the expanded words the export put Nanite depth over the prepass and the Nanite material pass painted over the meshes in front. The word layouts are Mesa radv's for GFX10. Metadata with some tiles cleared is not modelled, and the compression state draws leave in HTILE is not tracked, so a title reading its HTILE after a draw sees the stored expanded words. A recognized HTILE fill or clear makes the next bind read the words again. A slice other than 0 of a depth array is read at its own HTILE (`DB_HTILE_DATA_BASE` plus the slice times `HtileMetadataBytes`) but the HTILE word layout of slices 1 and up is not modelled (PPSA05684's tile masks need X8 and Y8 to take Z0 and Z1 as well, slices 4 and up unmeasured), which neither the check that every word is cleared nor the store-back of each word in place needs, so such a slice takes the clear like slice 0. A depth-only HTILE whose tiles all have ZMask 0 but different Min/Max Z (per-tile clear values, PPSA05684's cascades) is taken as a whole-surface clear to `DB_DEPTH_CLEAR`, the value the DB's depth test reads for a cleared tile (`HTILE_Z_CLEAR_REG` in Mesa's `src/amd/common/ac_descriptors.h`: the clear word "only clears HiZ", "the DB register contain[s] the full clear values"); the HiZ test before it, which rejects against each tile's own Min/Max Z range (the HTILE definitions in the same file), is not modelled, so a draw that HiZ would reject over a tile cleared with Min/Max Z 0 is tested against `DB_DEPTH_CLEAR` instead. A multisampled depth target, and any target whose HTILE overlaps a resident native depth guard, skips this read and store-back and goes to the native multisample depth owner, which keeps its own refusal for a fast-clear transition. - [RADV fill-memory kernel](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp) (libSceAgcDriver) - the [fill HLE](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp) takes RADV's 16-byte `fill_memory` compute kernel (`MatchClampedGlobalFill`: 11 GFX10.3 words, 64x1x1 groups, six user SGPRs with the address in `user[2..3]`, the largest store offset in `user[4]` and the value in `user[5]`; each thread stores 16 bytes at `min(thread * 16, user[4])`) as a fill of `min(threads * 16, user[4] + 16)` bytes. RADV initializes and fast-clears DCC keys and other metadata with it, so the key and metadata tracking sees such a fill where it is in the command stream; run as a dispatch, its global stores name no range, and a key fill was only seen once it had landed, after later writes to the surface. Only a 16-byte aligned address and largest offset are taken. RADV's 4-byte variant (fills below 16 bytes), other Mesa versions' encodings and other compilers still run as dispatches. The kernel words are the ones vkQuake's PS5 RADV build (Mesa 25.2) submitted; they are not compared with the NIR builder's output for other targets. -- [Host imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) on Linux skip ranges that are not mapped read-write, which are copied instead. The NVIDIA driver (595.91.07) refuses `VK_EXT_external_memory_host` imports of read-only or executable mappings (and shared mappings of regular files) with `VK_ERROR_OUT_OF_DEVICE_MEMORY`, after which every `vkAllocateMemory` on the device fails the same way. Read-only ranges are never imported, even on drivers that accept them. Shared mappings of regular files are not checked for. On Windows, an alias of adjacent registered ranges is refused if any page is not a shared view; a range made entirely of shared views is aliased run by run, including across sections or nonconsecutive section offsets. Adjacent registered ranges are joined into one import: the largest aligned power-of-two block, from twice the import alignment up to the 8 MiB window, that holds the requested address and that they fill exactly, so a block only grows by doubling as its window fills. A block that would exceed the budget, would replace an import a build holds, or fails (a refused Windows alias, `vkAllocateMemory`) steps down to the next smaller block; a failed block is not tried again until one of its ranges changes. Ranges larger than the window are imported one at a time. A buffer bound across adjacent ranges that are each larger than the window, or bound exactly over adjacent ranges of any size (from the first one's start to the last one's end), joins them into one import when it is bound, and their own imports are retired; a buffer that crosses any other two imports is copied. A heap grown in pieces re-imports its joined run only when a buffer is bound exactly over all of its pieces, or when a sparse descriptor heap over it is bound: a heap a shader binds whole while only the pieces from its base are mapped is bound in place from the import of that run, its views ending at the import's end as the copy's do, once the previous bind of that address saw the same last mapped byte (a heap that grows between every two of its binds stays copied; one bound more often than it grows is joined again after each growth) and at most 64 times per joined run base; past that, with a message, it is not joined again: it is bound in place while an existing import covers its run and copied otherwise, until no registered range starts at that base or the device changes. Ranges larger than the window are taken to be registered whole and never grown in pieces: a title that grows a heap in pieces larger than 8 MiB re-imports the joined run each time a bound buffer crosses into a new piece. The 8 MiB window is a choice: unbounded runs were re-imported whole on every growth of a heap mapped in small pieces, and the NVIDIA driver (610.57.04, 615.78.08) then refused imports or lost the device. Not measured on RADV. +- [Host imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) on Linux skip ranges that are not mapped read-write, which are copied instead. The NVIDIA driver (595.91.07) refuses `VK_EXT_external_memory_host` imports of read-only or executable mappings (and shared mappings of regular files) with `VK_ERROR_OUT_OF_DEVICE_MEMORY`, after which every `vkAllocateMemory` on the device fails the same way. Read-only ranges are never imported, even on drivers that accept them. Shared mappings of regular files are not checked for. On Windows, an alias of adjacent registered ranges is refused if any page is not a shared view; a range made entirely of shared views is aliased run by run, including across sections or nonconsecutive section offsets. Adjacent registered ranges are joined into one import: the largest aligned power-of-two block, from twice the import alignment up to the 8 MiB window, that holds the requested address and that they fill exactly, so a block only grows by doubling as its window fills. A block that would exceed the budget, would replace an import a build holds, or fails (a refused Windows alias, `vkAllocateMemory`) steps down to the next smaller block; a failed block is not tried again until one of its ranges changes. Ranges larger than the window are imported one at a time. A buffer bound across adjacent ranges that are each larger than the window, or bound over adjacent ranges of any size from the first one's start (ending anywhere in the last range it crosses), joins the ranges it crosses into one import when it is bound, and their own imports are retired; a buffer that crosses any other two imports is copied. A heap grown in pieces re-imports its joined run only when a buffer bound from its first piece's start crosses into a piece the joined import does not cover, or when a sparse descriptor heap over it is bound: a heap a shader binds whole while only the pieces from its base are mapped is bound in place from the import of that run, its views ending at the import's end as the copy's do, once the previous bind of that address saw the same last mapped byte (a heap that grows between every two of its binds stays copied; one bound more often than it grows is joined again after each growth) and at most 64 times per joined run base; past that, with a message, it is not joined again: it is bound in place while an existing import covers its run and copied otherwise, until no registered range starts at that base or the device changes. Ranges larger than the window are taken to be registered whole and never grown in pieces: a title that grows a heap in pieces larger than 8 MiB re-imports the joined run each time a bound buffer crosses into a new piece. The 8 MiB window is a choice: unbounded runs were re-imported whole on every growth of a heap mapped in small pieces, and the NVIDIA driver (610.57.04, 615.78.08) then refused imports or lost the device. Not measured on RADV. - [Cube texture descriptors](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp) with equal `BASE_ARRAY` and `DEPTH` expose six faces starting at `BASE_ARRAY`. Measured with `image_load`, `image_sample` and `image_get_resinfo` on RX 6600 (gfx1032), using 64x64 R32_UINT faces and base slices 0, 5 and 6. Other ranges containing fewer than six faces still throw: hardware load bounds and filtered-sample addressing differ. A range of six faces or more that does not end on a whole cube keeps every slice up to `DEPTH`, the bound loads use on hardware; sampling the cube it cuts short is out-of-range cube-array sampling. Out-of-range cube-array sampling remains unimplemented. Samplers with `DISABLE_CUBE_WRAP` are created with `VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT`, the flag RADV maps to that bit (Mesa `src/amd/vulkan/radv_sampler.c`, `src/amd/common/ac_descriptors.c`); without `VK_EXT_non_seamless_cube_map` a cube texture sampled through such a sampler throws. PS5 equivalence has not been measured on console hardware. - [Array layer views](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp) (libSceAgcDriver) - `ArrayLayersServeView` lets a 2D texture at a 2D array image's base address, or a 2D array texture over its first layers, view that image (`Texture::CanCopyFrom`). Mesa-built homebrew (ps5-opengl) writes the view's last slice plus one to `CB_COLOR_ATTRIB3.MIP0_DEPTH`, so a slice-0 render target is a two-layer array image, and the title samples it as a plain 2D texture (SuperTuxKart PPSA69421 drew a black 3D scene and minimap without this). The per-layer layout of a mipmapped array is assumed equal to a standalone surface's; it is not measured on the console. A 2D texture at a later slice's address still gets an image of its own. - [Multisampled CMASK](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a multisampled color target's CMASK is decoded under CB_COLOR_INFO FAST_CLEAR (bit 13, as for a single-sample target) or COMPRESSION (bit 14). Which bit a multisampled target sets is inferred: its fast clears were seen on GTA V's multisampled targets through their CMASK fills, not in a register dump. A COMPRESSION-only target's zero CMASK is not read as cleared (inferred: zero means cleared only under FAST_CLEAR), so only a fill after its first use clears it. A slice of a multisampled array is its own image at its own address with its own CB_COLOR0_CMASK (inferred from the register layout). Its CMASK is main's whole-metablock size. -- [Multisampled CMASK state](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - a multisampled target has no guest texels, so its CMASK state comes from fills rather than from a read at each draw as a single-sample target's does. A fill of the whole CMASK with 0 clears the host image at the target's next use, one with 0xffffffff marks it expanded, and any other pattern, or a fill that covers only part of the CMASK, throws at the next use (inferred: main's rule that a mixed CMASK is unmodeled, applied to the one host image). A FAST_CLEAR target starts from its CMASK keys at its first use, and a COMPRESSION-only target starts expanded. Copies by DMA into the CMASK are not seen, and a later use with another CMASK address throws. The clear does not rewrite the CMASK bytes as expanded, so a new target over the same CMASK (a different extent, so a different image) seeds from them again and clears once more. +- [Compressed FMASK reads](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) without an exact resident native owner throw. Native reads require eight fragments, one slice and mip, pipe-aligned DCC/CMASK, Z_64KB_X FMASK and unchanged, disjoint watched planes; an FMASK import awaiting its Windows deferred read is read first (`WatchedAfterSettle`). The host stores independent samples and exposes their expanded indices (`0x76543210`, AMD PAL `c5e80007`, `Gfx9Fmask::GetPackedExpandedValue`); console equivalence is unmeasured. Other layouts, standalone compressed decoding, guest publication and recipe reuse remain unsupported. Metadata writes invalidate the native view, including same-value stores. +- [Multisampled CMASK state](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - a multisampled target has no guest texels, so its CMASK state comes from fills rather than from a read at each draw as a single-sample target's does. A fill of the whole CMASK with 0 clears the host image at the target's next use, one with 0xcccccccc, 0xdddddddd, 0xeeeeeeee or 0xffffffff marks it expanded, and any other pattern, or a fill that covers only part of the CMASK, throws at the next use (inferred: main's rule that a mixed CMASK is unmodeled, applied to the one host image). A FAST_CLEAR target starts from its CMASK keys at its first use, and a COMPRESSION-only target starts expanded. Copies by DMA into the CMASK are not seen, and a later use with another CMASK address throws. The clear does not rewrite the CMASK bytes as expanded, so a new target over the same CMASK (a different extent, so a different image) seeds from them again and clears once more. +- [Multisampled DCC CMASK without fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - the initial CMASK of a multisampled DCC target is not checked when CB_COLOR_INFO enables COMPRESSION but not FAST_CLEAR: it then holds only FMASK compression, and a uniform DCC clear sets every fragment, so the target imports as that clear. What the console reads where such an FMASK names no fragment, as an uninitialized CMASK can, is unverified. - [Multisampled color fills](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - a multisampled target's samples are on the host, so a fill of its color memory is applied at its next use: a uniform fill over the whole of it clears the host image to that value, and a pending CMASK fast clear takes precedence over the fill and over a fill that would throw. Its color memory is a single mip and slice of addrlib's block dimensions for its sample count (ComputeThinBlockDimension). A fill over part of it, a fill reaching the start of a target whose footprint is not computed (linear), and a fill value with no exact clear value in the format (sRGB channels other than 0 and 1) throw at the next use. A fill before the target's first use is not seen (the host image starts at 0), nor are copies or shader writes into its color memory. A uniform compute fill over exactly the whole color memory with an exact clear value is not stored into guest memory, since only the host image holds the samples. A target is retired only when its whole guarded range is unmapped or its allocation removed, so if a title reuses that memory for other data while it stays mapped, a read the guard does not check (a buffer read through a V# or a pointer) sees the bytes from before the fill. -- [Multisampled texture no draw rendered to](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - sampling a multisampled texture whose surface no draw rendered to creates the host target from the T# and clears it to the color a single 32-bit value repeated over the whole footprint encodes, padding up to whole tile blocks included. Inferred, not measured on the console: that the samples of such a surface are the guest color memory, as a title that clears its canvas on the CPU (ps5-opengl `ps5_clear_msaa4_color`) relies on. Memory that is not one repeated value, tile modes other than Standard4KB, Standard64KB and RenderTarget, unmapped memory and values with no exact clear color throw. CPU writes to the surface after its host target exists are not seen by the host. -- [Multisampled targets in CB passes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a CB eliminate or DCC pass over a multisampled target throws, since its texels are on the host and only a CB resolve and an FMASK decompress (`CB_COLOR_CONTROL` mode 5) are modeled. The FMASK decompress only applies the pending CMASK fast clear to the host image; it requires a CMASK address, and the target's DCC and FMASK contents are not interpreted. DCC over a multisampled target is accepted only in that pass and throws everywhere else. A CB resolve into a fast-clear destination throws: the resolve writes the destination's texels but not its CMASK, which would still read as cleared. -- [Multisampled target guest access](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - the memory a multisampled color target throws on (CPU read, index buffer read, fixed-function vertex buffer read, DMA copy, texture or storage image read) uses fragment-aware 64 KiB render-target blocks. [AMD Addrlib's GFX10 thin-block rule](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/core/addrlib2.cpp#L1680-L1712) gives the block width and height from element bytes and fragment count; its [surface-size calculation](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp#L4000-L4005) rounds both extents to those blocks and multiplies by element bytes and fragments. Thus a 1920x1080 4x RGBA8 surface occupies 0x1fe0000 bytes, leaving the next allocation at +0x2000000 outside the guard. The 2x and 8x block orientations differ from treating the fragments as a larger element. Console address-layout equivalence has not been measured. Other tile modes retain the conservative `max(CB bytes * samples, width * height * element bytes * samples)` bound, which may still reject a distinct allocation. Live targets remain cached per device, surface address, format, extent and sample count. Retired cache owners are removed on the next target, sampled or storage lookup; outstanding recorded work and sampled views retain their images. Ordinary sampled and storage bindings check the color-target guard before resource reuse, including precollected sampled lookups. Reused image tables do not invalidate on multisample color-target registration yet; their per-element fault handling and native multisample sampling require a separate reuse proof. -- [DB depth copies](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a draw with `DB_RENDER_CONTROL.DEPTH_COPY` is run as a compute pass that copies one sample of the `Z_32_FLOAT` depth surface into a single-sample R32 float color target over the whole surface. With `COPY_CENTROID` the sample is the first one the draw covers, which is sample 0 under the full `PA_SC_AA_MASK` the driver requires: [radeonsi copies one sample per draw with `COPY_CENTROID` and a one-sample mask](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/gallium/drivers/radeonsi/si_state.c#L1679-1681); the console is not measured. Stencil copies, depth tests, other depth formats, multisampled or other-format targets and copies over part of the surface throw. +- [Multisampled texture no draw rendered to](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - sampling a multisampled texture whose surface no draw rendered to creates the host target from the T# and clears it to the color a single 32-bit value repeated over the whole footprint encodes, padding up to whole tile blocks included. Inferred, not measured on the console: that the samples of such a surface are the guest color memory, as a title that clears its canvas on the CPU (ps5-opengl `ps5_clear_msaa4_color`) relies on. Memory that is not one repeated value, tile modes other than Standard4KB, Standard64KB, Z64KBX, D64KBX and RenderTarget, unmapped memory and values with no exact clear color throw. CPU writes to the surface after its host target exists are not seen by the host. +- [Sample 0 of a multisampled color target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/FirstSampleCopy.cpp) (libSceAgcDriver) - a 2D image read of a multisampled color target returns its float, UNORM, SNORM or sRGB sample 0 exactly only where the driver keeps those values through a shader and a color attachment, which Vulkan does not require, and one of a multisampled depth surface throws. The read binds a one-sample copy that a fragment pass (`texelFetch` of sample 0) draws before every such binding; integer formats copy exactly. +- [Multisampled targets in CB passes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a CB eliminate or DCC pass over a multisampled target throws, since its texels are on the host and only a CB resolve and an FMASK decompress (`CB_COLOR_CONTROL` mode 5) are modeled. The FMASK decompress only applies the pending CMASK fast clear to the host image; it requires a CMASK address, and the target's FMASK contents are not interpreted. In that pass as elsewhere, multisampled DCC is accepted only as a native multisampled DCC fast clear or comp-to-single clear. A CB resolve into a fast-clear destination throws: the resolve writes the destination's texels but not its CMASK, which would still read as cleared. +- [Multisampled target guest access](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - the memory a multisampled color target throws on (CPU read, index buffer read, fixed-function vertex buffer read, DMA copy, texture or storage image read) uses fragment-aware 64 KiB blocks for the RenderTarget, Z64KBX and D64KBX tile modes. [AMD Addrlib's GFX10 thin-block rule](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/core/addrlib2.cpp#L1680-L1712) gives the block width and height from element bytes and fragment count; its [surface-size calculation](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp#L4000-L4005) rounds both extents to those blocks and multiplies by element bytes and fragments. Thus a 1920x1080 4x RGBA8 surface occupies 0x1fe0000 bytes, leaving the next allocation at +0x2000000 outside the guard. The 2x and 8x block orientations differ from treating the fragments as a larger element. Console address-layout equivalence has not been measured. Other tile modes retain the conservative `max(CB bytes * samples, width * height * element bytes * samples)` bound, which may still reject a distinct allocation. Live targets remain cached per device, surface address, format, extent and sample count. Retired cache owners are removed on the next target, sampled or storage lookup; outstanding recorded work and sampled views retain their images. Ordinary sampled and storage bindings check the color-target guard before resource reuse, including precollected sampled lookups. Reused image tables do not invalidate on multisample color-target registration yet; their per-element fault handling and native multisample sampling require a separate reuse proof. +- [DB depth copies](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp) (libSceAgcDriver) - with `COPY_CENTROID` the copied sample is sample 0, as [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/gallium/drivers/radeonsi/si_state.c#L1679-1681) programs it under a full sample mask, so an edge pixel whose sample 0 the draw doesn't cover can differ from the console's first covered sample; not measured on the console. The copy runs as the title's draw with a driver-authored guest pixel program (hand-encoded RDNA2) that loads the sample through a driver-built T# and exports it as `32_R`. A multisampled plane is read without the native multisample import checks. Stencil copies, depth tests, planes other than `Z_32_FLOAT`, mips, a partial sample mask, blending, indirect draws and other targets throw. - [CB resolve sample count](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a draw with CB_COLOR_CONTROL in resolve mode is a CB resolve when either PA_SC_AA_CONFIG or CB_COLOR0_ATTRIB.NUM_SAMPLES is multisampled, and takes its sample count from CB0. Inferred, not from documentation: PPSA36082 resolves its 4x targets with no sample count in PA_SC_AA_CONFIG, and radeonsi sets PA_SC_AA_CONFIG to 0 for a multisampled framebuffer when rasterizer multisampling and line smoothing are off. PA_SC_AA_CONFIG is only compared with CB0 while PA_SC_MODE_CNTL_0.MSAA_ENABLE is set: PPSA08270 resolves a 4x 512x512 target with MSAA_ENABLE off and PA_SC_AA_CONFIG still at 2x (0x00108001) from earlier draws. - [Custom MSAA sample locations](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) support a uniform 1x1 pattern for 2, 4 or 8 samples through VK_EXT_sample_locations. Patterns varying across the guest's 2x2 pixel grid use a 2x2 sample location grid, anchored at the framebuffer origin like the guest's while the window offset is zero, on devices whose `maxSampleLocationGridSize` for the sample count is a multiple of 2x2. On other devices every pixel takes the pattern's pixel (0,0) locations (`PA_SC_AA_SAMPLE_LOCS_PIXEL_X0Y0_*`, its first registers and the corner the grid is anchored at), and the first such pipeline of each sample count logs `[gpu] no 2x2 sample location grid`. The RTX 5070 reports 2x4 for 2 samples, 2x2 for 4 and 1x2 for 8; an AMD Radeon integrated GPU reports 2x2 for each count, per count and for every count; an RTX 3070 Laptop GPU (driver 617.42) reports `maxSampleLocationGridSize` 1x1 for every count (`VkPhysicalDeviceSampleLocationsPropertiesEXT`), but per count (`VkMultisamplePropertiesEXT`, which the device setup reads and logs) a 2x4 grid at 2 samples, 2x2 at 4 and 1x2 at 8. So the 3070's 2x patterns, the 2x checkerboard among them, take the native grid, and the stand-in plus the exact path serve its 8-sample patterns and devices without a grid. A pattern that only reorders one set of positions between pixels, as a 2x checkerboard pattern puts its samples at x = +4/16 and -4/16 and swaps them on odd rows, is drawn exactly through that stand-in: before every render pass that draws with it each pixel's samples are put in the stand-in's order and after it back in the guest's, through a storage view of a multisampled color target and through a copy and a per-sample pass for depth (writing stencil through VK_EXT_shader_stencil_export, or one pass per bit without it), while the pixel shader reads SampleID, applies the sample mask and exports coverage in the guest's order, and the second log line names the reordering. A uniform partial sample mask that the order moves is applied in the pixel shader. With early Z, a depth or stencil test and memory writes in the pixel program, the draw throws, where #6451's stand-in alone ran it with odd rows' sample indices swapped. A fix would choose the stand-in after compilation. That costs two passes over each multisampled attachment per render pass, and on those devices every multisampled color target is created with a mutable format and storage usage for its view, which a driver may answer by not compressing it. It needs shaderStorageImageMultisample with a storage view of the color format at the sample count, sampleRateShading, and a depth format a FragDepth write gives back exactly (16-bit, or 32-bit float; not 24-bit depth, the stand-in for 16-bit depth with stencil where the device has neither); without them, and for patterns whose pixels hold different positions, the stand-in alone is used, and the log line says why. A device without shaderStorageImageMultisample takes the stand-in alone for every pass, depth-only passes included, so a depth target that a depth-only pass and a color pass both draw keeps one order. On a device that has it, a pass is still refused for a color format without a storage view at its sample count, while a depth-only pass on the same depth target is reordered, so that target's samples change order between those passes; a fix would decide the order per target. Then the top-left pixel of every 2x2 quad matches the console and the other three use its positions. A same-set pattern keeps every position and the number of samples a primitive covers in each pixel, so a resolve that averages a pixel's samples, or takes their minimum or maximum, sees the console's values, but sample i of an odd row holds what the console stores in sample 1 - i: a pass that reads the samples by index, as checkerboard reconstruction does, exchanges the two half-pixels of odd rows, which can show as combing along vertical edges, and per-sample shading, sample-mask exports and coverage inputs see the swapped indices too. A pattern whose pixels hold different positions draws pixel (0,0)'s positions in each pixel whose own differ. A device's pipelines and render pass begins take the same stand-in for a given pattern, so a sample-location-compatible depth image is not drawn with one set of locations and transitioned or tested with another. A multisampled rasterizer drawing into one-sample color targets, and a one-sample depth target if any, with MSAA_ENABLE set, no alpha-to-coverage and no coverage pixel input is drawn with one sample at sample 0's location in each pixel of the grid, as the targets keep only sample 0's coverage. Without PS_ITER_SAMPLE such a draw also refuses centroid inputs, and keeps PERSP_SAMPLE, LINEAR_SAMPLE and ANCILLARY: a pixel shaded once is taken to get sample 0's barycentrics and sample index 0. With PS_ITER_SAMPLE only sample 0's invocation reaches the targets, so its sample and centroid barycentrics are taken to be sample 0's and the draw is shaded once. Both are inferred, not measured. The pixel shader runs once per pixel with sample number 0, so ANCILLARY reads sample 0 and POS_X_FLOAT and POS_Y_FLOAT keep the position `SPI_BARYC_CNTL` names. PERSP_CENTER and LINEAR_CENTER inputs on this path are interpolated at sample 0's location on the host, where the console uses the pixel center; one-sample depth images are created with `SAMPLE_LOCATIONS_COMPATIBLE_DEPTH` when the device supports it, as multisampled ones already were; this is how VK_AMD_mixed_attachment_samples defines the case, and it is not measured on PS5 hardware. Other mixed sample counts throw. Devices without the required sample count, four subpixel bits or the full guest coordinate range refuse custom patterns. Depth images remain in GENERAL between draws, and render passes cannot continue across pattern changes. A pass that draws depth at custom sample locations ends with a transition to TRANSFER_SRC_OPTIMAL and back that names them. Guest offsets retain the existing decoder's signed four-bit, sixteenth-pixel interpretation; PS5 coverage and interpolation equivalence have not been measured on hardware. -- [Single-sample scan into a multisampled depth target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a draw with `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` = 0 and `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear into a multisampled depth target rasterizes the depth target's sample count with every sample at the pixel center, as a single-sample scan into a multisampled color target already does. radeonsi programs this `MSAA_ENABLE` and `PA_SC_AA_CONFIG` with the depth buffer's own sample count when GL multisampling is disabled on a multisampled framebuffer ([L1066-L1068](https://github.com/openbsd/xenocara/blob/1b39033df8d928ec247ca64448c4a63c72302db7/lib/mesa/src/gallium/drivers/radeonsi/si_state.c#L1066-L1068), [L3485-L3512](https://github.com/openbsd/xenocara/blob/1b39033df8d928ec247ca64448c4a63c72302db7/lib/mesa/src/gallium/drivers/radeonsi/si_state.c#L3485-L3512)), although its comment lists Z samples as at most the coverage samples ([L3439](https://github.com/openbsd/xenocara/blob/1b39033df8d928ec247ca64448c4a63c72302db7/lib/mesa/src/gallium/drivers/radeonsi/si_state.c#L3439)) and it sets `DB_EQAA.MAX_ANCHOR_SAMPLES` to the Z sample count, where PPSA18216 sets `DB_EQAA` to 0. GL then lets every sample take the single-sample depth ([L185-L190](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089ccfee87efe230ebcdd8742859813/extensions/ARB/ARB_multisample.txt#L185-L190)) and the fragment be treated as in multisampling with full coverage ([L422-L425](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089ccfee87efe230ebcdd8742859813/extensions/ARB/ARB_multisample.txt#L422-L425)), which is what this draws: each depth and stencil sample is tested and written with the depth at the pixel center. GL also lets an implementation test only a centermost sample and set every sample from it ([L425-L434](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089ccfee87efe230ebcdd8742859813/extensions/ARB/ARB_multisample.txt#L425-L434)), which differs when a pixel's stored samples differ; which one the console does, whether it instead evaluates the depth plane at each sample position, and how its occlusion queries count such a draw, is not measured on hardware. Points, lines, alpha-to-coverage, `DB_EQAA` Z interpolation or overrasterization, a pixel program reading the sample index, a sample-mask export and a single-sample color target next to the multisampled depth target throw +- [Single-sample scan into a multisampled depth target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a draw with `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` = 0 and `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear into a multisampled depth target rasterizes the depth target's sample count with every sample at the pixel center, as a single-sample scan into a multisampled color target already does. radeonsi programs this `MSAA_ENABLE` and `PA_SC_AA_CONFIG` with the depth buffer's own sample count when GL multisampling is disabled on a multisampled framebuffer ([L1066-L1068](https://github.com/openbsd/xenocara/blob/1b39033df8d928ec247ca64448c4a63c72302db7/lib/mesa/src/gallium/drivers/radeonsi/si_state.c#L1066-L1068), [L3485-L3512](https://github.com/openbsd/xenocara/blob/1b39033df8d928ec247ca64448c4a63c72302db7/lib/mesa/src/gallium/drivers/radeonsi/si_state.c#L3485-L3512)), although its comment lists Z samples as at most the coverage samples ([L3439](https://github.com/openbsd/xenocara/blob/1b39033df8d928ec247ca64448c4a63c72302db7/lib/mesa/src/gallium/drivers/radeonsi/si_state.c#L3439)) and it sets `DB_EQAA.MAX_ANCHOR_SAMPLES` to the Z sample count, where PPSA18216 sets `DB_EQAA` to 0. GL then lets every sample take the single-sample depth ([L185-L190](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089ccfee87efe230ebcdd8742859813/extensions/ARB/ARB_multisample.txt#L185-L190)) and the fragment be treated as in multisampling with full coverage ([L422-L425](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089ccfee87efe230ebcdd8742859813/extensions/ARB/ARB_multisample.txt#L422-L425)), which is what this draws: each depth and stencil sample is tested and written with the depth at the pixel center. GL also lets an implementation test only a centermost sample and set every sample from it ([L425-L434](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089ccfee87efe230ebcdd8742859813/extensions/ARB/ARB_multisample.txt#L425-L434)), which differs when a pixel's stored samples differ; which one the console does, whether it instead evaluates the depth plane at each sample position, and how its occlusion queries count such a draw, is not measured on hardware. Points, lines, alpha-to-coverage, `DB_EQAA` Z interpolation or overrasterization, a pixel program reading the sample index and a sample-mask export throw +- [One-sample color targets beside a multisampled depth target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a one-sample color target drawn with a depth target of the rasterizer's sample count is taken to keep the coverage of sample 0 after the depth and stencil tests. That is the rule of [`VK_AMD_mixed_attachment_samples`](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/fragops.adoc#L2510-L2516), and it is not measured on hardware. The draw needs `VK_AMD_mixed_attachment_samples`, or `VK_NV_framebuffer_mixed_samples` with truncated coverage reduction, for its sample count and throws without them. With `PS_ITER_SAMPLE` the color is the output of sample 0's invocation. Overrasterization and color targets of different sample counts throw. - [DCC display buffers](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) are presented only when all keys of the console's DCC extent agree on uncompressed or a fast-clear code, padding keys included; other keys, and register-clear keys over a pending image, throw. - [Storage image write-back](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) stores the GPU results over a CPU-stamped range whose guest bytes equal the copy taken when the image's layers got their generation (images up to 1 MiB whose guest bytes were settled at that point, with no unit shadow, pending image results or recorded GPU store over them; other images keep the previous rule: the CPU's bytes win). A CPU store that rewrites the baseline value after the GPU changed it is not told apart from no store, and the GPU result wins there. The 1 MiB limit is a choice, not a console value. A range a queued draw commit still has to read is left pending whatever the stamps say ([`DrawPipeline::HeldReads`](../../../core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp)), so a title that recycles one allocation between an index buffer and a storage image sees the draw's own indices; the trade is a title that reads a surface as index data and means the image gets the pre-image bytes instead, since the reservation does not compare the command-stream positions of the draw and of the image's write. `APS5_NO_QUEUED_DRAW_READ_ORDER=1` restores the old behaviour, and the `[storage]` line counts the listings the hold turned away (`held for a queued draw read`) and the `[draw] pipelined` line the stores it held. The hold covers what the commit reads of guest memory: the index range, the vertex input ranges as their descriptor extents ([`QueuedDrawReadRanges`](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp), since the bytes a draw fetches depend on the highest index, which only the commit knows) and the source of a queued DMA copy. Every other commit-time read goes through the flush hook, so it sees the bytes the hold protects. Two commit-time reads are outside the hold: the texels a register or cmask clear materialization reads from the draw's own colour target (through the hook, so ordered, but the hold does not cover a render target), and a CPU read whose store the hold turned away, which sees the pre-image bytes. Nothing retries a refused store: the image reaches guest memory with the next flush of that range (the queue worker's idle store, a `WAIT_REG_MEM` poll over it, a later read or write of it). A kept 64 KiB unit that lies outside every (slice, level) window stores nothing: the tracking grid is flat over the guest range while 3D slices taper in depth with the mip level (`HasLayer`), so the higher depth slices' higher-level blocks belong to no window. The console's tracking of such units is not measured. - [16-bit float display buffers](../../../core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp) (libSceAgcDriver, libSceVideoOut) - pixel formats `0xC001000622000000` and `0xC001000600000000` are taken as R16G16B16A16 and B16G16R16A16 float, eight bytes per texel, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/59183dbfa81ed7efdff6069177fed50de0552725/src/graphics/host_gpu/renderer/image/imageInfo.h) maps them and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/VideoOut/VideoOutTypes.cs) names them (`Rgba16Float`, `Bgra16Float`); PPSA11386 registers 3840x2160 tiled buffers of the first. The tiled layout is assumed to be the 8-byte SW_64KB_R_X equation of `TextureSwizzleEquations.hpp` applied to full coordinates, as addrlib applies it, by analogy with the 32-bit scanout; `ColorTargetLayout` applies the equations to in-block coordinates, which for 8-byte texels differs in every odd 64-row block row (address bit 11 takes y bit 6) and for 16-byte texels also differs, so a float display buffer written through `WriteColorTarget` and presented from guest memory would show those rows misplaced. The values are assumed to be linear light and are encoded with the sRGB curve for the 8-bit output. Bits 32-39 of these formats are `0x06`, as in the 10-bit formats SharpProspero lists without sRGB transfer, while its sRGB formats have `0x00`, its 10-bit PQ formats `0x04` (with `0x07` in bits 40-47) and its float BT.2100 PQ formats `0x07`: they show only that these formats are not the sRGB ones. NaN and negative values give 0, values of 1 and above give 255, alpha is linear. Neither the layout nor the transfer is verified against console or title frames, and HDR output is not modelled. DCC metadata on a float buffer throws. @@ -168,24 +201,30 @@ - [HDR output override](../../../core/libs/prx/libSceAgcDriver/Execution/src/HostHdr.cpp) - setting `APS5_FORCE_SDR` suppresses HDR availability reported to titles while retaining the detected display and surface capabilities. It selects the existing SDR presentation path; it does not add support for floating-point BT.2100 PQ buffers (`0xC001070722000000` and `0xC001070700000000`), whose presentation remains refused. The override does not change the host display settings. - [DCC key stores](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp) cover the console's DCC extent only for pipe-aligned DCC of single-level, single-layer 4-byte SW_64KB_R_X surfaces; other surfaces, and every texture and color target key read, cover one byte per 256 bytes. - [DCC key fills over other images](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) - a uniform fill at a live image's `dccAddress`, at least one key per 256 bytes long, is a key fill of that image even when other live images overlap it, but only while it is no longer than the image's key extent (`DccKeyCount`, the pipe-aligned count); a longer one is classified by its overlaps as before. The bound is inferred, not measured on a title: a longer fill also writes the bytes past the keys, which the overlapping image may own. Whether the title's bloom-target key fills stay within the extent was not checked (`APS5_TRACE_DCC_KEYS` prints the length of the fills classified as keys, not of the refused ones). +- [Mipmapped DCC color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - the keys of a 2D color target with more than one mip are modeled for its whole surface only, as for a multi-mip storage image. The resident image takes a uniform clear over all of them, then marks them all uncompressed at its first write. The per-draw handling of a single-mip target's keys does not apply. A draw through the copied (non-resident) path leaves the keys as they were, and the CB metadata passes still refuse such a target. Keys that hold a different state per mip, and `ClearRegister` keys (whose value is a CB register), are not modeled for such a target. The keys of mipmapped DCC array and 3D targets are ignored. - [Comp-to-single DCC keys](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp) (libSceAgcDriver) - key 0x10 (`GFX9_DCC_CLEAR_SINGLE` in Mesa `ac_descriptors.h`) uniform over a surface makes every texel the element at the start of its 256-byte block, the top-left texel of the block where radv's `build_clear_dcc_comp_to_single_shader` stores the clear color. Sampled textures and storage images read that expansion (a storage image's first write stores the keys uncompressed, as under a clear code), and a CB DCC decompress pass stores it over the texels. A fast-clear eliminate over such keys throws, as a metadata pass over mixed keys does; color targets that render over such keys use their stored texels, and textures read mixed keys, and comp-to-single keys of a format whose element does not divide 256 bytes, as stored. "Uniform" is judged over the key bytes the driver reads, one per 256 surface bytes (see DCC key stores above), so a pipe-aligned surface whose keys differ past that range is expanded as if all were comp-to-single, as for the other clear codes. Not verified on hardware - [8_SRGB color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) on a device that cannot attach and blend R8_SRGB render into an RGBA8_SRGB copy of the target, so the sRGB encode, decode and blend rounding are the device's; the console's are not measured. - [8_SRGB color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) on such a device throw unless they are single-mip 2D surfaces with a resident image; linear targets have none. - [5_6_5 color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R5G6B5. The field holding X is in bits 0-4, Y in 5-10 and Z in 11-15, as for the other AMD formats; the component swap picks which export fills them, following `ac_translate_colorswap` in [Mesa 26.2.2 ac_formats.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/common/ac_formats.c), which programs `SWAP_STD_REV` (2) for a surface with red in bits 11-15. With `SWAP_STD_REV` red goes to bits 11-15, R5G6B5's red, so the exports are written as they are; with `SWAP_STD` (0) red goes to bits 0-4 and the exports are written with X and Z exchanged, as for 1_5_5_5. The decoded format, component mapping and write mask are tested, and an execution test draws 32_ABGR exports into tiled and linear targets in both orders and compares every stored texel with that layout on the host device; the export direction is not measured on a PS5. Only UNORM with those two component orders is accepted; `SWAP_ALT` and `SWAP_ALT_REV` keep throwing. Destination alpha reads as 1, as Vulkan defines it for a format without alpha, which is not measured on the console. Constant blend colors with `SWAP_STD` go through the `0xc6` component-swap handling, which is not tested for this format. R5G6B5 has no entry in the DCC and multisample clear-encoding tables, so a CB DCC decompress or eliminate of DCC clear codes other than 0000 and a fast clear of a multisampled 5_6_5 target throw, as for 1_5_5_5. PPSA27305 renders into this format with `SWAP_STD_REV` (discussion #6434). - [1_5_5_5 color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into A1R5G5B5. AMD formats keep the first component in the least significant bits (X in bits 0-4, Y in 5-9, Z in 10-14, W in bit 15, as in the [fpPS4](https://github.com/red-prig/fpPS4) `kSurfaceFormat1_5_5_5` doc comment and as measured for textures in the packed 16-bit textures entry), while A1R5G5B5 keeps R in bits 10-14 and B in bits 0-4, so the pixel exports are written with X and Z exchanged, which makes the stored bits follow the guest layout. The decoded format, component mapping and write mask are tested, and an execution test draws 32_ABGR exports into tiled and linear targets and compares every stored texel with the guest layout on the host device; the export direction is not measured on a PS5. Only UNORM with the standard component swap is accepted. Constant blend colors go through the same component-swap handling as other targets with that export mapping (`0xc6`), which is not tested for this format. PPSA06699 renders into this format. -- [COPY_DATA from the GPU clock](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as the `RELEASE_MEM` timestamp, not the console's GPU clock. The `COPY_DATA` cache policy fields are accepted and have no effect. +- [4_4_4_4 partial writes, sample positions](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a partial write of a multisampled 4_4_4_4 UNORM target runs the pixel shader once per sample. For a shader the guest runs per pixel it throws on inputs that would follow the invocation's sample: the ancillary word and, unless every sample sits at the pixel centre, centroid, sample-located or pull-model barycentrics, Z, 1/W and positions at the centroid or a sample; off-centre samples make centre barycentrics throw without interpolation functions and fragment shader barycentrics. Centre barycentrics and positions, upper-left and fixed-point positions and the front face are kept, so each invocation reaches the console's colour and kill decision. A shader that writes guest memory throws, as each covered sample would repeat the writes, also when the guest iterates it itself (the request does not say). Not measured on a PS5. +- [4_4_4_4 CB resolves](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a CB resolve from a packed color target throws, so a 2-sample 4_4_4_4 target cannot be resolved by the color block. +- [4_4_4_4 partial writes, merged coverage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a partial write of a one-sample 4_4_4_4 UNORM target drawn with merged coverage (a multisampled rasterizer over a one-sample target, as radeonsi line and polygon smoothing does) throws: the merge leaves the target out of the colour attachments, so such a draw would rasterize the extra samples with no one-sample attachment for `VK_NV_framebuffer_mixed_samples` coverage reduction, a combination that is not tested. +- [4_4_4_4 partial writes, measured hardware](../../../core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444MultisamplePartialWrite.cpp) (libSceAgcDriver) - the single-sample merges and the 2-, 4- and 8-sample merges are measured only on an NVIDIA RTX 4070 SUPER, not on other host GPUs or a PS5. +- [COPY_DATA from the GPU clock](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as the `RELEASE_MEM` timestamp. On a PS5 (firmware 13.40, measured 2026-10-19 on the GPU through `sceAgcDriverSubmitDcb` from inside a retail title) `SRC_SEL` 9 and the `RELEASE_MEM` DATA_SEL 3 timestamp were the same counter (0x1C and 0x5C ticks apart within one buffer), and it ran at 98.0 and 99.8 MHz over two 1.1 s intervals timed by the CPU, i.e. 100 MHz within the submission jitter, as the 10 ns ticks here; only its starting value is the host's. The `COPY_DATA` cache policy fields are accepted and have no effect. - [COPY_DATA from a register](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 0) copies the value the queue holds for a shader, context or user config register (`COUNT_SEL` two consecutive ones) and throws for a register with no value. A dispatch records `COMPUTE_DISPATCH_INITIATOR` and, for `DISPATCH_DIRECT`, `COMPUTE_DIM_X/Y/Z` in the shader registers; `DISPATCH_INDIRECT` clears the three counts. Neither the register values the CP writes nor the copy's ordering against in-flight dispatches have been measured on hardware. - [EVENT_WRITE stream-out statistics events](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, opcode 0x46) - `SAMPLE_STREAMOUTSTATS1`-`3` and `SAMPLE_STREAMOUTSTATS` (event types 0x01-0x03 and 0x20 in [navi10_enum.h](https://github.com/torvalds/linux/blob/0fcb05b72344f40806cbe810de8872f1698bf053/drivers/gpu/drm/amd/include/navi10_enum.h#L14155-L14188)) are accepted on the graphics queue as a 2-dword packet with event index 0 and do nothing. [nvd.h](https://github.com/torvalds/linux/blob/0fcb05b72344f40806cbe810de8872f1698bf053/drivers/gpu/drm/amd/amdgpu/nvd.h#L326-L347) documents these events only with the sampling index (3, or `EVENT_INDEX__SAMPLE_STREAMOUTSTATS*` 8-11 on gfx10); the index-0 packet has no destination dwords, and what the CP does with it is neither documented nor measured on hardware. AnyPS5 does not emulate the VGT stream-out counters, so there is nothing to sample. The sampling indices throw that sampling to memory is not implemented, and other indices, other sizes and the compute queue throw. [sceAgcDcbEventWrite](../../../core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp) ignores the address for every event type except 0x38 and 0x39, so a call with one of these event types and an address builds the index-0 packet: nothing is written and nothing throws. Whether the console's builder emits a sampling packet for an address is not known. - [INDEX_ATTRIBUTES_INDIRECT](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, opcode 0x91) loads INDEX_BASE, INDEX_BUFFER_SIZE (in indices) and INDEX_TYPE from the 16-byte record at `attribute_base + attribute_index * 16`, read when the packet runs. The record layout (address low, address high, size, type) is PAL's [`IndexAttribIndirect`](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9Chip.h#L60-L67) and the packet is the one [`BuildIndexAttributesIndirect`](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9CmdUtil.cpp#L2586-L2613) writes. The console's `sceAgcDcbSetIndexIndirectArgs` writes this packet, with `indirect_mode` 0, the attribute base in the address dwords and the attribute index in bits 0-15 ([measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/agcbuild.golden.tsv#L172-L174) by the prosper console oracle); the record layout is not measured on a PS5, and neither is seen in a title. `indirect_mode` 1 (`addr_offset`, which only PAL's ExecuteIndirect path emits) throws, and so does a record whose index type is above 3, as INDEX_TYPE does. +- [Indexed indirect draws without an index limit](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, opcodes 0x25 and 0x38) - on a queue that has received neither INDEX_BUFFER_SIZE (0x13) nor INDEX_ATTRIBUTES_INDIRECT (0x91) since its last reset, the draw has no index limit: it takes the CPU record path ([IndirectDraw.cpp](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp)), and [Draw.cpp](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp) reads each record's firstIndex and indexCount range as written, without clamping, and refuses a range that overflows or is not readable. An INDEX_BUFFER_SIZE of 0 that was written still throws. The console's initial INDEX_BUFFER_SIZE and whether DRAW_INDEX_2 or DRAW_INDEX_OFFSET_2 MAX_SIZE changes it are not measured. The suspend-point queue reset also clears a written size. PPSA08338 issues DRAW_INDEX_INDIRECT_MULTI this way without ever emitting either packet. - [16-bit RGBA color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) support the standard and the alternate (`COMP_SWAP` 1) component order. Alternate swaps red and blue in shader exports, write masks and constant-color blend factors, following `ac_translate_colorswap` in [Mesa 26.2.2 ac_formats.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/common/ac_formats.c). Blending into the reverse orders (`COMP_SWAP` 2 and 3) is covered by the reversed-order blending entry, and simultaneous targets requiring conflicting blend constants throw. Console equivalence has not been measured on hardware. - [UINT16_ABGR color exports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - export format 7 is accepted only into an unsigned integer target, and each 16-bit value reaches the target unchanged; a value beyond a narrower target's range (`R8_UINT`) is undefined in Vulkan, and whether the console clamps or truncates it is not confirmed. SINT16_ABGR (8) throws - [Layered color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 3D color targets with 64 KiB render-target or 4 KiB standard tiling render several slices, and so does a `CB_COLOR_VIEW` slice range on a 2D array target through the array's resident image, or on a surface of one slice as an array of the view's last slice + 1 slices, one slice pitch apart: a layered draw into an array without its resident image, a layered draw with a depth target, and color targets of different slice counts throw. The vertex layer export (`PA_CL_VS_OUT_CNTL` USE_VTX_RENDER_TARGET_INDX) reaches the shader only for a layered draw and is otherwise ignored, as before; a layer beyond the view's last slice is undefined in Vulkan, while the console's handling (clamping to `SLICE_MAX`) is not modeled, and a view whose last slice lies past the surface is clamped to its last slice. DCC keys of a 3D color target are taken as one key per 256 bytes over the whole surface, every slice included, and only uniform keys (all uncompressed, or one fast-clear code) are interpreted, as for 2D targets; mixed keys leave the stored texels as they are. The console's DCC key layout for 3D surfaces is not checked (LEGO Horizon Adventures' (PPSA14632) lighting volumes are DCC) - [VGT_TF_PARAM](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - DISTRIBUTION_MODE is taken as scheduling only; the host tessellator decides the order of a patch's triangles. - [Local programs](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) that the hull header leaves at address 0 are prepared at their first draw, one artifact per tessellation configuration, instead of at registration. -- [Packed 16-bit textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp) (libSceAgcDriver) - `5_6_5`, `1_5_5_5` and `4_4_4_4` are viewed as R5G6B5, A1R5G5B5 and B4G4R4A4 (the 4-bit format every Vulkan device can sample; R4G4B4A4 is optional and lavapipe lacks it) with a swizzle that reads X from the low bits, and a DST_SEL of the missing W of `5_6_5` reads X. Measured on RDNA2 (gfx1035), not on the console: through `image_load` and `image_sample_lz`, X, Y, Z (and W of `1_5_5_5` and `4_4_4_4`) come from the low, middle and high fields, and W of `5_6_5` returns X (the low 5 bits), not 1. +- [Packed 16-bit textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp) (libSceAgcDriver) - `5_6_5`, `1_5_5_5` and `4_4_4_4` are viewed as R5G6B5, A1R5G5B5 and B4G4R4A4 (the 4-bit format every Vulkan device can sample; R4G4B4A4 is optional and lavapipe lacks it) with a swizzle that reads X from the low bits, and a DST_SEL of the missing W of `5_6_5` reads X. Measured on RDNA2 (gfx1035), not on the console: through `image_load` and `image_sample_lz`, X, Y, Z (and W of `1_5_5_5` and `4_4_4_4`) come from the low, middle and high fields, and W of `5_6_5` returns X (the low 5 bits), not 1. Storage images use compatible R16_UINT views with the [buffer UNORM store conversion](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp); quantization uses nearest even and maps NaN to zero. Rounding ties and NaN handling for packed stores have not been checked on AMD or PS5 hardware. - [Min/max S# reduction](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) - `FILTER_MODE` 1 and 2 use Vulkan's min and max reduction (the same `SQ_IMG_FILTER_MODE` values RADV programs). Measured on RDNA2 (gfx1035), not on the console: only texels of non-zero weight are reduced, and the weight is taken after rounding to the 8-bit subtexel grid (a sample within 1/512 texel of a centre reduces that texel alone); a driver with other `subTexelPrecisionBits` reduces a different set there. Gathers ignore the mode. - [Min/max S#](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver, shader recompiler) - throws with a linear mip filter, depth or color-texture compare, or bilinear or anisotropic filtering of an image the recompiler point-samples (sint, converted, depth-bits). With anisotropy the host sampler reduces over its own footprint; [RADV](https://github.com/openbsd/xenocara/blob/1b39033df8d928ec247ca64448c4a63c72302db7/lib/mesa/src/amd/vulkan/radv_sampler.c#L185-L232) programs the same S# for such a Vulkan sampler. Measured on RDNA2 (gfx1030 in [#5156](https://github.com/boykopovar/AnyPS5/pull/5156#issuecomment-6155265616), gfx1035 in #5821), not on the console: without derivatives (`image_sample_lz`), min or max with anisotropic bilinear returns the bilinear minimum or maximum and with anisotropic point the point result, at every ratio field 0 to 4; with derivatives (`image_sample_d`, a 4:1 footprint), anisotropic bilinear at ratio field 2 or 4 reduces over the texels of all its anisotropic taps (the minimum or maximum along the major axis), not one bilinear footprint. A host that is not AMD reduces over its own anisotropic footprint, and Vulkan leaves nearest filtering with anisotropy to the host driver. -- [FORCE_DEGAMMA](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp) (libSceAgcDriver) - not measured on the console. A sampler that forces sRGB decoding throws for an SNORM `8`, `8_8` or `8_8_8_8` texture, which RDNA2 (gfx1031) reads bit for bit as the sRGB format of the same bytes, and for a format not measured on RDNA2. The rest follows gfx1035 and gfx1031 (point and bilinear): sRGB textures sample as without the bit, as in shadPS4 (PS4); UNORM `8`, `8_8`, `8_8_8_8`, BC1, BC2, BC3 and BC7 sample through their sRGB view (decoded before filtering); `16`, `16_16` and `16_16_16_16` UNORM, SNORM and FLOAT, `32`, `32_32` and `32_32_32_32` FLOAT, `10_11_11`, `2_10_10_10`, `5_9_9_9`, `5_6_5`, `1_5_5_5`, `4_4_4_4`, BC4, BC5 and BC6H sample as without the bit, as gfx1031 does. +- [FORCE_DEGAMMA](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp) (libSceAgcDriver) - not measured on the console. A sampler that forces sRGB decoding throws for a format not measured on RDNA2. The rest follows gfx1035 and gfx1031 (point and bilinear): sRGB textures sample as without the bit, as in shadPS4 (PS4); UNORM `8`, `8_8`, `8_8_8_8`, BC1, BC2, BC3 and BC7 sample through their sRGB view (decoded before filtering), and SNORM `8`, `8_8` and `8_8_8_8` through the sRGB view of their bytes, as gfx1031 reads them; `16`, `16_16` and `16_16_16_16` UNORM, SNORM and FLOAT, `32`, `32_32` and `32_32_32_32` FLOAT, `10_11_11` FLOAT, `2_10_10_10` UNORM, `5_9_9_9`, `5_6_5`, `1_5_5_5`, `4_4_4_4`, BC4, BC5 and BC6H sample as without the bit, as gfx1031 does. - [Tessellation factor ring](../../../core/libs/prx/libSceAgcDriver/State/src/Configuration.cpp) (libSceAgcDriver) - `sceAgcDriverSetTFRing` records the ring and `sceAgcDriverGetTFRing` reports it. Before a title sets one, it reports the console's default, 0xFF0000000 and 128 KiB, as measured in [PS5_Vulkan's hardware findings](https://github.com/mihawk-99/PS5_Vulkan/blob/fde9e3798c2ec1d8fccc90d5067cd6060490607f/docs/HARDWARE_FINDINGS.md). Nothing is written to the recorded ring: tessellated draws run as Vulkan tessellation stages, which keep their factors on the host, so a title that reads tessellation factors from the ring finds none. A null base or a zero size throws; [ps5-opengl's AGC runtime](https://github.com/blackbearreloaded/ps5-opengl/blob/67c873f985eaefe0a72f713529e5632eaa01139c/src/platform/ps5_agc_native_runtime.c) calls `sceAgcDriverSetTFRing(0, 0)` in its cleanup, and what that does on the console is unknown. - [VertexBufferReadSize](../../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) and the [vertex staging copy](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - a vertex fetch whose index is at or past the descriptor's `num_records` no longer throws for `OOB_SELECT` 0 and 1. The copy still spans the whole fetch, but the bytes from `num_records * stride` onward read as zeros, so the out-of-range vertex is `(0, 0, 0, 0)`. Measured with `buffer_load_format_xyzw` (`idxen`) through the [hardware oracle](../HW_ORACLE.md) on RDNA2 gfx1036 for PPSA01325's word 3 `0x0004dfac` (`OOB_SELECT` 0) in #2241: indices 3, 4 and 5 over 3 records each return `00000000 00000000 00000000 00000000`, W included, unlike Vulkan `robustBufferAccess`, which returns `(0, 0, 0, 1)`. The zeros come from the record check, not from memory: stored values placed past the records are ignored, and a `num_records` 6 control returns them. `RESOURCE_LEVEL` 1, `OOB_SELECT` 1 and FORMAT 74 `32_32_32_FLOAT` give the same zeros, in wave32 and wave64; gfx1013 (the console) is not measured. `OOB_SELECT` 2 returns the stored values past the records and `OOB_SELECT` 3 compares `num_records` against the byte offset, both measured on the same setup in #2241; the two keep throwing past `num_records` here, since each needs its own rule. Zero-stride descriptors (`stride` 0) keep their separate byte-count check and are not covered here. Each out-of-range fetch is copied alone and outside the draw-snapshot cache, which is neither read nor written for it: a reused snapshot would carry another draw's bytes into the zero region, and a stored zero-padded copy would hand zeros to a later in-range draw at the same address. The valid prefix is still flushed for GPU writes before it is read. - [Draw snapshot buffer descriptors](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) rewrite each affected array from element zero, preserving unchanged descriptors. MoltenVK 1.4.2 writes auxiliary buffer lengths at index `i` instead of `start + i` for partial array updates ([MVKDescriptorSet.mm, BufferAuxSize](https://github.com/KhronosGroup/MoltenVK/blob/v1.4.2/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm#L1051)). Per-element writes therefore let one buffer length replace another even when Vulkan buffer handles and ranges are correct. @@ -199,16 +238,18 @@ - An [image table](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) whose record range overlaps a buffer or storage image written by its own dispatch faults (`table written by its dispatch`) when any entry is selected, even if the write misses the selected record. A selected column's range is its record and the bytes of the record before the descriptor. - An [image table](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) T# whose surface has no mapped byte faults when selected instead of becoming a zero texture as a direct T# does; what the console returns for it is not measured. - [User-data callees](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a compute shader's `s_swappc_b64` whose source pair is a user-data pair, or a copy of one made with `s_mov_b32` or `s_mov_b64` in the same block, calls the code at the address that user data holds. At dispatch the callee is read from guest memory up to its `s_setpc_b64` on the link pair, one copy of it is appended per call after the caller's last instruction, and the call is rewritten to `s_call_b64` to that copy. The words after the caller's last instruction are dropped from the recompiled code; in the shaders seen they are metadata or constants the caller reads relative to its program counter, and those reads go to the captured guest code, which linking does not change. A caller that jumps through a table among those words, which the control flow analysis reads from the recompiled code, throws. A copy runs at another program counter than the callee, so a callee that reads its program counter, makes a call, ends the program, jumps through another pair, returns before code it branches to, spans more than 64 KiB or is not readable throws. Callees are read again whenever a dispatch misses the dispatch cache; a dispatch that hits it reuses the linked program, so a title that rewrote a callee in place at the same address would keep running the old code. -- [Table callees](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a compute shader's `s_swappc_b64` whose source pair is two consecutive dwords of an `s_buffer_load_dword*`, or a copy of them made with `s_mov_b32` or `s_mov_b64` in the same block (a conditional branch forward past the call between them is crossed), calls one of the targets that buffer holds when the load's buffer descriptor comes from an `s_load_dwordx4` without SOFFSET from a user-data pointer, or from a pointer pair that two `s_mov_b32` of a literal or inline constant set, and the load's SOFFSET from an `s_lshl_b32` by 2 to 24 bits. The descriptor load may be in an earlier block, such as before a per-lane loop around the call, when it is the only write of the descriptor registers that reaches the buffer load on every path back from it, and no `s_call_b64` or statically resolved call lies on those paths; the paths may run through any `s_swappc_b64` that is not resolved statically, the table call itself included, whose callee is assumed to keep the descriptor registers. The chain compares the target each iteration actually loads, so a callee that changes them can only make a later target miss the chain and fault as an unlinked call. Paths follow fall-through, direct branches and the `s_setpc_b64` jumps whose target the control flow analysis resolves. A path that reaches, before a write of the descriptor registers, the program's first instruction, the entry of an in-program function (an `s_call_b64` target, whose callers are not followed), or an instruction that nothing modelled jumps or falls into fails the search. At dispatch the descriptor and the buffer are read from guest memory, and every non-zero 64-bit value at the loaded offset plus a multiple of the shift's stride, below the descriptor's size (stride times num_records, or num_records bytes with stride 0), is read as a user-data callee and appended once per call. The call becomes a branch to a chain that compares the source pair with each target (`s_cmp_lg_u32` with a literal and `s_cbranch_scc1` per dword) and `s_call_b64`s the copy that matches; no match runs the original `s_swappc_b64` as an unlinked call. There is one chain for each value SCC has at the call, and the chain sets SCC back to that value (`s_cmp_eq_u32 0, 0` or `s_cmp_lg_u32 0, 0`) before the callee or the unlinked call runs. The searched range is the descriptor's whole size, which covers the range the load can reach whether it is checked in bytes or in strides; which one the PS5 uses for `s_buffer_load` was not measured. More than 64 distinct targets, a buffer above 64 MiB or a copy out of `s_call_b64` range throws. As with user-data callees, the buffer is read again only when a dispatch misses the dispatch cache, so a target a title adds to the buffer later is an unlinked call when a cached program runs. +- [Table callees](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a compute shader's `s_swappc_b64` whose source pair is two consecutive dwords of an `s_buffer_load_dword*`, or a copy of them made with `s_mov_b32` or `s_mov_b64` in the same block (a conditional branch forward past the call between them is crossed), calls one of the targets that buffer holds when the load's buffer descriptor comes from an `s_load_dwordx4`, or a 4-dword-aligned slice of an `s_load_dwordx8` or `s_load_dwordx16`, without SOFFSET from a user-data pointer, or from a pointer pair that two `s_mov_b32` of a literal or inline constant set, and the load's SOFFSET from an `s_lshl_b32` by 2 to 24 bits. The descriptor load may be in an earlier block, such as before a per-lane loop around the call, when it is the only write of the descriptor registers that reaches the buffer load on every path back from it, and no `s_call_b64` or statically resolved call lies on those paths; the paths may run through any `s_swappc_b64` that is not resolved statically, the table call itself included, whose callee is assumed to keep the descriptor registers. The chain compares the target each iteration actually loads, so a callee that changes them can only make a later target miss the chain and fault as an unlinked call. Paths follow fall-through, direct branches and the `s_setpc_b64` jumps whose target the control flow analysis resolves. A path that reaches, before a write of the descriptor registers, the program's first instruction, the entry of an in-program function (an `s_call_b64` target, whose callers are not followed), or an instruction that nothing modelled jumps or falls into fails the search. At dispatch the descriptor and the buffer are read from guest memory, and every non-zero 64-bit value at the loaded offset plus a multiple of the shift's stride, below the descriptor's size (stride times num_records, or num_records bytes with stride 0), is read as a user-data callee and appended once per call. The call becomes a branch to a chain that compares the source pair with each target (`s_cmp_lg_u32` with a literal and `s_cbranch_scc1` per dword) and `s_call_b64`s the copy that matches; no match runs the original `s_swappc_b64` as an unlinked call. There is one chain for each value SCC has at the call, and the chain sets SCC back to that value (`s_cmp_eq_u32 0, 0` or `s_cmp_lg_u32 0, 0`) before the callee or the unlinked call runs. Each copy follows its own compares and call, so no branch spans more than one copy. A later call's branch to its chain and its return cross earlier calls' copies through a pair of `s_branch` relays before the first of those copies and after each one, so they don't span more than one copy either. The searched range is the descriptor's whole size, which covers the range the load can reach whether it is checked in bytes or in strides; which one the PS5 uses for `s_buffer_load` was not measured. More than 64 distinct targets, a buffer above 64 MiB, or a call more than 0x7fff dwords before the end of the program and its user-data callees throws. As with user-data callees, the buffer is read again only when a dispatch misses the dispatch cache, so a target a title adds to the buffer later is an unlinked call when a cached program runs. - [AGC FS table](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp) (libSceAgcDriver) - the driver maps 2 MiB at 0xF_E0000000 when it is created and fills the table that libSceAgc keeps at 0xF_E0040000 (six 16-byte V# slots, slot 0 describing eight 16-byte records) the way PS5 12.00 libSceAgcDriver and libSceAgc do, but every record points at one AnyPS5 routine that only returns (`s_setpc_b64` on the link pair). Sony's routines, which order work before a title's `ds_ordered_count`, are not modeled. - [DB_RENDER_CONTROL resummarize draws](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - HTILE is not modeled, so a RESUMMARIZE_ENABLE draw without depth or stencil tests and without color writes changes no modeled state and draws nothing. A pure resummarize with pass-through D16/D32 depth, no depth writes or bounds test, no stencil plane, no color writes and no running pixel stage also draws nothing; other resummarize, copy or decompress draws throw. - [Comparison-sampled R32 float and R16 unorm textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) whose surface a storage image holds are sampled from a D32_SFLOAT or D16_UNORM image that the GPU refills from the storage image whenever its version moves, because Vulkan compares only depth formats. Only surfaces of at least 1 MiB (`APS5_DEPTH_COPY_MIN_KIB`) that are not 3D take the copy; smaller ones stay CPU snapshots compared against guest memory. With copies of small surfaces, one title hit an intermittent boot-time crash that was not explained. - [Null vertex buffer V#](../../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - a V# whose four dwords are all zero is bound to a zeroed vertex buffer of the attribute's size with stride 0, so every component of its attribute reads 0. This follows the V# fields in the [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) (`NUM_RECORDS` 0, `DST_SEL` 0 selects 0), not measured on hardware. -- [Descriptor pool recycling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) avoids individually freed sets: [RADV 26.2.2](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/vulkan/radv_descriptor_pool.c) initializes its free-set heap with 32 bytes beyond the backing allocation. An allocation-only RX 6600 test reproduces acceptance of a 4,128-byte set in a 4,096-byte pool; the path without individual frees rejects it. Pools reset after their last set is released. A cached set can retain unused slots in an exhausted pool until eviction; peak gameplay pool memory remains unmeasured. +- [Descriptor pool recycling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) frees sets individually only on lavapipe: [RADV 26.2.2](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/vulkan/radv_descriptor_pool.c) initializes its free-set heap with 32 bytes beyond the backing allocation. An allocation-only RX 6600 test reproduces acceptance of a 4,128-byte set in a 4,096-byte pool; the reset-only path rejects it. Pools reset after their last set is released. A cached set can retain unused slots in an exhausted pool until eviction; peak gameplay pool memory remains unmeasured. - [16-bit depth with stencil](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) is stored as `D24_UNORM_S8_UINT`, or `D32_SFLOAT_S8_UINT`, on a device without `D16_UNORM_S8_UINT` as an attachment. Depth comparisons, clears and the depth image see the wider format's precision, so results at the 16-bit quantization boundary can differ from the console; the constant depth bias is scaled by 256 for D24, and a non-zero constant on the D32 float fallback throws. Not measured on console hardware. - [Depth surfaces created over storage writes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): a depth surface created without HTILE copies its depth plane from the storage image at the plane's address, a pending one first, when that image has the plane's extent, one level, a 2D view or a 2D array view (its layer 0 is copied) and a 16-bit format for a 16-bit plane or a 32-bit format for a 32-bit plane. A multisampled surface, a surface the device stores in another format, a surface with HTILE and a surface without such an image start from `DB_DEPTH_CLEAR`, and the stencil plane from `DB_STENCIL_CLEAR`; CPU writes to the plane's memory are not read. Not measured on the console, where the plane is that memory. - [Flip packets raise the graphics end-of-pipe event](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp) (libSceAgcDriver) - a flip delivers the queue's end-of-pipe event (`sceAgcDriverAddEqEvent`) once the work recorded before it completes, as a `RELEASE_MEM` with `INT_SEL` does, but only on a queue that has never delivered an interrupt from such a release: the queue is latched the first time a release interrupt reaches the event queue, and flips on that queue deliver nothing while its own release interrupts keep arriving. The latch is cleared when the queue has flipped five times without another interrupt from its own releases: the fifth flip delivers the event and unlatched the queue, so a title that waits on the flip event after latching recovers only if it submits further flips. The bound cannot be by time: the next release interrupt can arrive seconds later than a suppressed flip across loading stalls, and delivering an unexpected flip event on a queue that pairs flips with releases can crash; no fixed window short enough to unblock a waiting title keeps such a title alive. Whether the console raises the event from the flip itself or from an interrupt its flip sequence contains is not measured; nor is how the console behaves for a title that waits on the flip event and also raises release interrupts on the same queue. -- [Host import windows and budget](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver): a registered range larger than 64 MiB is imported one 64 MiB window at a time for buffer and fill accesses; an access across window edges imports the cells it touches, grown to the union of the range's imports it overlaps. An address-based build (`AcquireRegistered`) imports the range whole and replaces its windows. Live imports stay under a budget: `APS5_HOST_IMPORT_MIB`, else the larger of 6 GiB and half the physical memory, at most the largest host heap less the larger of 2 GiB and half that heap (imports, type-3 mirrors and buffers and textures placed in host memory share it; on a 16 GiB machine whose host heap is half the memory, a 2 GiB reserve left too little for the rest). Where the device enables `VK_EXT_memory_budget`, that host heap's reported budget less its other usage and an eighth, in 64 MiB steps, read at most once a second, also bounds the default budget once an import's `vkAllocateMemory` has failed; a set `APS5_HOST_IMPORT_MIB` is never lowered by the report. The report's other usage includes the host-visible copies and heap mirrors that stand in for refused imports, so it grows with the refusals; the first time the report would lower the budget, `[gpu] host import budget: host heap ...` prints the heap's reported budget and usage and the live imports. An import's `vkAllocateMemory` failure within an eighth of the budget, or the third in a row, lowers the budget to the live imports until the device or the configured budget changes; an isolated failure further below only marks its range failed. Live imports are not capped by `maxMemoryAllocationCount`: NVIDIA's Windows driver reports 4096, while Village held 27,167 live imports without a failure. A new import beyond it retires the least recently used imports not used in the last 256 lookups; one that would not fit even then is refused before anything is retired, and imports made when a range is mapped never evict. A heap mirror, sparse or not, whose range has left the write watch is refreshed in full at each use, every block counted as written; a build plans its range again only once the import path takes the range. A request that may not evict (an address-based build, an import when a range is mapped) remembers a refused range or block, with the live bytes and the budget at the refusal, and is answered from that memory, still counted as refused, until live imports drop below those bytes, the budget changes or one of its ranges changes. A request that may evict remembers its refusal too, answering later requests for the same run from memory with the room the refusal left (a request whose range fits in that room steps down to a smaller block without scanning the imports again, and a refusal that stepped down is remembered for each block it tried), while no import was made or retired, the budget has not grown, the import window and the run are unchanged, and no import can have become evictable since: the use count stays below the first use at which an import that was neither evictable nor held could leave the 256-lookup protection, and either the build floor has not risen or no held import could leave it yet. A join remembers its refusal the same way, apart from other requests for the same run, so a buffer bound across refused ranges does not try the join again at every upload. The shutdown line `[gpu] host imports at shutdown` gives the live, made, refused (and remembered) and failed counts per requester. The 64 MiB window, the 256-lookup protection, the 2 GiB reserve, the eighth, the 64 MiB steps and the three failures are choices, not measured. +- [Host import windows and budget](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver): a registered range larger than 64 MiB is imported one 64 MiB window at a time for buffer and fill accesses; an access across window edges imports the cells it touches, grown to the union of the range's imports it overlaps. An address-based build (`AcquireRegistered`) imports the range whole and replaces its windows. Live imports stay under a budget: `APS5_HOST_IMPORT_MIB`, else the larger of 6 GiB and half the physical memory, at most the largest host heap less the larger of 2 GiB and half that heap (imports, type-3 mirrors and buffers and textures placed in host memory share it; on a 16 GiB machine whose host heap is half the memory, a 2 GiB reserve left too little for the rest). Where the device enables `VK_EXT_memory_budget`, that host heap's reported budget less its other usage and an eighth, in 64 MiB steps, read at most once a second, also bounds the default budget once an import's `vkAllocateMemory` has failed; a set `APS5_HOST_IMPORT_MIB` is never lowered by the report. The report's other usage includes the host-visible copies and heap mirrors that stand in for refused imports, so it grows with the refusals; the first time the report would lower the budget, `[gpu] host import budget: host heap ...` prints the heap's reported budget and usage and the live imports. An import's `vkAllocateMemory` failure within an eighth of the budget, or the third in a row, lowers the budget to the live imports until the device or the configured budget changes; an isolated failure further below only marks its range failed. Live imports are not capped by `maxMemoryAllocationCount`: NVIDIA's Windows driver reports 4096, while Village held 27,167 live imports without a failure. A new import beyond it retires the least recently used imports not used in the last 256 lookups; one that would not fit even then is refused before anything is retired, and imports made when a range is mapped never evict. An address-based build does not evict either, except for a range the budget refused (or remembers refusing) and no mirror serves whose copy would take the build's copies past `APS5_ADDRESS_COPY_MAX_MIB`. A heap mirror, sparse or not, whose range has left the write watch is refreshed in full at each use, every block counted as written; a build plans its range again only once the import path takes the range. A request that may not evict (an address-based build, an import when a range is mapped) remembers a refused range or block, with the live bytes and the budget at the refusal, and is answered from that memory, still counted as refused, until live imports drop below those bytes, the budget changes or one of its ranges changes. A request that may evict remembers its refusal too, answering later requests for the same run from memory with the room the refusal left (a request whose range fits in that room steps down to a smaller block without scanning the imports again, and a refusal that stepped down is remembered for each block it tried), while no import was made or retired, the budget has not grown, the import window and the run are unchanged, and no import can have become evictable since: the use count stays below the first use at which an import that was neither evictable nor held could leave the 256-lookup protection, and either the build floor has not risen or no held import could leave it yet. A join remembers its refusal the same way, apart from other requests for the same run, so a buffer bound across refused ranges does not try the join again at every upload. The shutdown line `[gpu] host imports at shutdown` gives the live, made, refused (and remembered) and failed counts per requester. The 64 MiB window, the 256-lookup protection, the 2 GiB reserve, the eighth, the 64 MiB steps and the three failures are choices, not measured. +- [Piece-split ranges over the host import budget](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver): an address-based build imports a registered range larger than the device's largest allocation in pieces. If the budget refuses a piece, the range is mirrored or copied without the evicting retry other refused ranges get when their copy would pass `APS5_ADDRESS_COPY_MAX_MIB`, so such a copy can still exceed that cap and end the build. +- [Device-local staging](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) - a written or atomic buffer element inside a host import, including one an address-based compute build reaches through its pointers, is copied to device memory before the dispatch and copied back whole after it, so a CPU store into that range in between is rolled back. - [Periodic pipeline cache saves skip busy intervals](../../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp) (libSceAgcDriver) - the saver thread snapshots the Vulkan pipeline cache only when no pipeline creation holds it (`TryLockForSnapshot`); otherwise it skips that 10 s interval. A blocking snapshot queued behind a long creation (an NVIDIA compile of a 1.5M-word compute pipeline runs for minutes) makes the writer-preferring `shared_mutex` hold every later pipeline creation on every queue until that compile ends, which froze Yōtei's (PPSA26344) loading at frame 797-799 for the whole compile. While creations overlap without a gap, no periodic save happens, so a crash in that stretch loses the pipelines built since the last save; `SaveNow` and the teardown save still wait for the lock. How often busy stretches skip saves in practice is not measured. - [Registration preparation at submit](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) - `Driver::Submit` waits for every registration-time shader preparation queued before the call, not only those of the shaders the commands use, so a submission behind a long queue of unrelated preparations waits for all of them on the submitting thread. Waiting there instead of in the queue worker keeps the GPU side of a submission short: a title that sees a submission take seconds treats the GPU as hung (Goat Simulator 3, PPSA04159, then stops submitting and its garbage collector fails a 10 s timeout). On the console shaders are compiled ahead of time, so neither wait exists; how long a title tolerates a slow submit call is not known. - [Resource cache entries holding released textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a sampled texture the texture cache drops while something else holds it, or makes without caching (`admitTexture` refusing a first use), is marked released, and `ResourceCache::Insert` refuses descriptor sets that hold one and removes the entries that do. The removal waits for the next insert: until then `Find` still returns those entries and their textures stay alive. One process-wide flag starts the sweep, so while the sampled cache is over its budget every insert scans all entries (1,024 at start, up to 16,384 after growth, or `APS5_RESOURCE_CACHE_ENTRIES`, each with its textures); that cost is not measured. When `ReleaseTextureCaches` runs on device memory exhaustion, the sampled textures these entries hold are only marked released and are freed at the next insert, so the immediate allocation retry gets no memory back from them. A draw or dispatch whose textures the cache refused builds its descriptor sets again on each use for as long as the cache stays full; the count bounds (`APS5_RESOURCE_CACHE_ENTRIES`, `APS5_RESOURCE_CACHE_TABLE_ELEMENTS`) still bound nothing by bytes, and sampled textures made outside the texture cache (the disabled cache, depth-surface copies) are never marked. Measured on one title on one RTX 4090: with the default bounds, sampled textures held outside the cache peaked at 2,836 MiB (7,830 MiB before) and device-local heap 0 stayed under its 22.9 GB budget for the whole run (26,357 MiB before, which aborted with `VK_ERROR_OUT_OF_DEVICE_MEMORY` at the wolf scene). Whether the extra descriptor-set builds cost frame time elsewhere is not measured. @@ -217,14 +258,21 @@ - [Unclamped normalized color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - CB_COLOR_INFO targets with BLEND_CLAMP clear (bit 15) on UNORM and SNORM formats are accepted when blending is disabled (ENABLE clear or BLEND_BYPASS set). Vulkan converts unblended shader values and clamps to the normalized range automatically; blending into an unclamped normalized target remains unmodeled because Vulkan always clamps inputs prior to blending. - [Multisample image readback](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleReadback.cpp) fetches unsigned R8/R16/R32/R32G32/R32G32B32A32 or D32 depth samples through a 2D array view in GENERAL, in layer/y/x/fragment order. The caller owns producer completion, image transitions and resource lifetimes. It implements Vulkan per-sample fetches, not guest-memory publication, metadata expansion or PS5 layout equivalence; D32 exceptional-value preservation has not been measured. Other formats, sample counts and invalid ranges throw. - +- [Multisample image upload](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleUpload.cpp) does not model guest layouts, native ownership or CMASK/FMASK/DCC publication; console equivalence and large-title initialization costs remain unmeasured. It stores 1/2/4/8/16-byte elements from a storage buffer into an unsigned 2D multisample array view in layer/y/x/fragment order. It requires enabled multisample storage images and formatless image writes, 2/4/8 fragments and valid aligned buffer and dispatch ranges. The caller supplies the matching storage view in GENERAL and owns barriers, completion and resource lifetimes. - [Thick SW_4KB_S_X volumes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) use AMD addrlib's GFX10 non-RB+ 16-pipe `GFX10_SW_4K_S3_X_PATINFO` equations for 1, 2, 4, 8 and 16-byte elements, including packed mip tails. The layout follows [AMD PAL c5e80007](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp); it has not been measured on console hardware. Other thick swizzle modes outside the supported families still throw. Mipmapped 3D color targets remain refused by the color-register decoder. - [Viewport depth range remap](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - on a device without `VK_EXT_depth_range_unrestricted` a viewport depth range outside [0, 1] (Minecraft PPSA17221 draws its world with `PA_CL_VPORT_ZSCALE` 1, `ZOFFSET` 0 in negative-one-to-one clip space) is folded into the vertex shader: `RemapPositionDepth` scales clip z so z/w is the console's depth, the pipeline's depth clamp applies the console's `PA_SC_VPORT_ZMIN/ZMAX` clipped to [0, 1] (exact for a normalized depth target or none; Minecraft clamps its float depth target to [-1, 1], so a depth below 0 is stored as 0 there, which only a later test or read at exactly 0 can tell, and the first such draw is reported once), and two extra `gl_ClipDistance` elements restore the near and far planes depth clamping removes. The mode is a pipeline specialization constant (`PipelineSpecialization::DepthRemapBase`); scale and offset are read from the draw's push block (two words at byte 104, `DepthRemapPushOffsetBytes`, so a remapped draw's stage push data ends before it), so draws that differ only in them share one vertex module and one pipeline. The remap serves the vertex path only; mesh and tessellation draws with such a range still throw. Whether the console clamps before or after its depth bias and how it treats a clamp range narrower than the transform's are not measured. - [Benchmark rewrite failure test off Windows](../../../core/libs/prx/libSceAgcDriver/tests/PerformanceTrace.cpp) (libSceAgcDriver) - `ReplaceGzipFile` checks that a write failing at `.tmp` keeps the previous `aps5-bench.json.gz` only on non-Windows builds: the `std::ios_base::failure` thrown inside the MinGW ofstream did not unwind to the test's catch, so Windows CI covers only the failed move. -- [Sampled array windows](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) (libSceAgcDriver) - a sampled 2D array, 1D array or cube view whose base array slice is past 0 is built from its viewed slices only (`ViewedArrayWindow`), at the guest address of its base slice. The window is taken only when the surface is thin, has no DCC metadata, and, for a 64 KiB XOR swizzle, when the base slice keeps every slice bit of the swizzle equation at zero (R_X 1-byte elements: a base slice that is a multiple of 16). Any other view still builds slices 0 to its last slice. A window and a whole-array view of the same surface are separate images over overlapping memory, kept coherent by the existing overlap rules, at the cost of the memory and uploads of both. PPSA26344 samples a 256x256 cube array at slices 4299..7232, built at 1467 MiB instead of about 3.8 GB. +- [Sampled array windows](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) (libSceAgcDriver) - a sampled 2D array, 1D array or cube view past slice 0 is built from its viewed slices only (`ViewedArrayWindow`). The window needs a thin surface without DCC metadata, or with keys that resolve over the whole surface to Uncompressed (the window then drops the metadata; keys are rescanned at each lookup). For a 64 KiB XOR swizzle it starts at the last slice at or before the base whose swizzle slice bits are zero (R_X 1-byte and Z_X 4-byte: a multiple of 16); when that is slice 0 the view builds slices 0 to its last slice. A window and a whole-array view of the same surface are separate images over overlapping memory, kept coherent by the existing overlap rules, at the cost of both. PPSA26344 samples a cube array at slices 4299..7232 at 1467 MiB instead of about 3.8 GB; PPSA01341 samples slice 636 of a DCC 2D array at 52 MiB instead of 2.5 GiB. - [2D reads of 2D arrays](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a sampled image whose shader shape is 2D and whose T# is a 2D array is built from its base slice alone: the T# is narrowed to that slice (`FirstLayerDescriptor`) before its decode, cache key and image, in stage A records, stage B bindings, revalidation and image tables. A whole-array read of the same T# keeps its own cache entry, so both images exist when a title uses both. Storage images read or written through a 2D shape are not narrowed and still build every slice the T# names. PPSA26344 reads a 3328x1872 R_X T# of 1537 slices this way: the whole array was a 12.4 GiB image that did not fit the device-local budget of an RTX 4090. - [Sampled and storage views of 1 GiB or more](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) that stay whole are only logged (sampled views by `cachedTexture`, storage views by `cachedStorageTexture`), with the registered guest range holding their base and how many of their slices fit in it; nothing is capped to that range. PPSA26344 samples a 1467 MiB cube array window of which only 838 of 2934 slices lie inside its 3380 MiB range: the rest is read from whatever follows the range. Whether such slices are ever sampled is not measured. - [Color export type against the target format](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - the export-format check refuses a `UINT16_ABGR` export (`SPI_SHADER_COL_FORMAT` 7) into a target whose color-buffer number format is not unsigned integer, but not the reverse. A 32-bit export (`32_R`, `32_GR`, `32_AR` or `32_ABGR`) into an unsigned-integer target leaves `uintExport` clear, so the recompiler declares the fragment output `float4`; `DecodeColorBuffer` maps a number format of 4 to `R32_UINT` from the color buffer alone, independent of the export format, so the float output reaches an integer attachment. Desktop drivers keep the raw bits, but the Khronos validation layer reports `Undefined-Value-ShaderFragmentOutputMismatch` and MoltenVK's Metal compiler refuses the pipeline. Which export format a title pairs with a 32-bit integer target is not measured, so the output type is left following the export format rather than the attachment. +- [Depth mip levels](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a depth view of mip `DB_DEPTH_VIEW.MIPID` in a chain of `DB_Z_INFO.MAXMIP` levels renders into that level as a surface of its own, at the level's offset and extent from `ComputeElementMipLayout` for the 64 KiB Z_X swizzle, the layout color target mips and sampled depth already use; slices of a chain step by the size of the whole chain. Taken from LEGO Builder's Journey (PPSA05496), which renders depth mip 1 of 64 KiB Z_X surfaces without HTILE; the placement was not compared with a console capture. Multisampled depth mips, other swizzle modes, levels in the packed mip tail and HTILE on a mip chain throw - [Long Windows pipeline-cache paths](../../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp): `readHeader` and the failed-read existence check still use ordinary paths. A filename beyond MAX_PATH can prevent header reuse or be reported missing; the shared content reader and journal normalization do not cover these calls. +- [DB_RENDER_CONTROL.STENCIL_CLEAR_ENABLE without a stencil plane](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver): a stencil clear on a depth target whose `DB_STENCIL_INFO` format is `STENCIL_INVALID` is dropped; the depth clear and color writes proceed. AMD PAL ([`gfx9RsrcProcMgr.cpp`](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32/src/core/hw/gfxip/rpm/gfx9/gfx9RsrcProcMgr.cpp)) and radeonsi (`si_clear.c`) never set the bit without a stencil plane, and shadPS4 drops it the same way (`vk_rasterizer.cpp`, `StencilValid`). No source states what the hardware does with the bit then; the console was not measured. A read-only stencil plane still throws +- [Swept write-only staging](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver): on a discrete GPU a write-only element whose V# stride is wider than the widest buffer store (16 bytes) and that a direct dispatch sweeps is staged in a device-local shadow. The shadow still takes a copy-in, because the copy-back covers the whole element and the stores may leave part of each record unwritten. A sweep proven to write every byte of the element (from the offsets and widths of its stores within the record) could skip the copy-in, about 10 ms per frame in Diablo II: Resurrected; that proof is not made. Store widths are not recorded per instruction either, so a record of 16 bytes or less stays in place even when its stores are narrower. + +### Opt-in draw paths + +- [Draw finisher](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp) (`APS5_DRAW_PIPELINE`) - opt-in. The Linux main-image page table it adds matches what Windows already does by default. diff --git a/docs/dev/debt/libSceAgcVsh.md b/docs/dev/debt/libSceAgcVsh.md new file mode 100644 index 0000000000..c2fb6a18d8 --- /dev/null +++ b/docs/dev/debt/libSceAgcVsh.md @@ -0,0 +1,5 @@ +# libSceAgcVsh technical debt + +### Unknown function info + +- The 150 declarations in [Export.cpp](../../../core/libs/prx/libSceAgcVsh/Export.cpp) are present in the PS5 3.20 [export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAgcVsh.c) and the local import-stub table. Their signatures and behavior are unverified; the parameterless declarations are for import resolution only and throw through NotImplemented_nid_no_patch. diff --git a/docs/dev/debt/libSceAjm.native.md b/docs/dev/debt/libSceAjm.native.md index f8982027f0..5c693e0f85 100644 --- a/docs/dev/debt/libSceAjm.native.md +++ b/docs/dev/debt/libSceAjm.native.md @@ -3,28 +3,34 @@ ### Unknown function info - [sceAjmBatchGetLog, sceAjmInstanceAvailable](../../../core/libs/prx/libSceAjm.native/Export.cpp) (libSceAjm.native) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAjm.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. -- [sceAjmInstanceExtend, sceAjmInstanceSwitch](../../../core/libs/prx/libSceAjm.native/Export.cpp) (libSceAjm.native) - their signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/ajm/ajm.h#L19-L20) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ajm.cs#L100-L104), not measured on a console; these throwing declarations only resolve imports. +- [sceAjmInstanceExtend, sceAjmInstanceSwitch](../../../core/libs/prx/libSceAjm.native/Export.cpp) (libSceAjm.native) - their four-argument signatures come only from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ajm.cs#L98-L104), whose comments do not describe them; shadPS4 [declares both without parameters](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ajm/ajm.h#L222-L223). Neither is measured on a console; these throwing declarations only resolve imports. - [sceAjmInstanceCreate](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - a full 14-bit instance slot namespace returns `0x80930007` (`OUT_OF_RESOURCES`), as [shadPS4's InstanceCreate](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ajm/ajm_context.cpp) does for its own instance pool (PS4). The 16384 slots per codec prefix are this runtime's existing ID layout, not a measured console capacity; the PS5 capacity and exhaustion result are unverified. - [ATRAC9 vibration decoding](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native): channel configurations 6 (mono) and 7 (dual mono) use the pinned [vgmstream decoder](https://github.com/vgmstream/vgmstream/blob/7c520e8f9b078dfde2f84f1b5bcbad0c6c415806/src/coding/libs/atrac9_dec.c), including its 12/24/48 kHz profile limits. Synthetic fixtures cover interleaving, reset after malformed input, partial superframes and PCM16/PCM32/float output; console PCM rounding and physical haptic output are unverified. Ordinary channel configurations and discrete multichannel streams still use LibAtrac9. Decoder creation and decoding run under the existing AJM instance lock because the dependency rebuilds shared Huffman tables during initialization. -- [sceAjmDecWVorbisCreateHeaderPacket, sceAjmDecWVorbisCreateSetupPacket](../../../core/libs/prx/libSceAjm.native/Export.cpp) (libSceAjmWVorbis, implemented in libSceAjm.native) - unknown signatures -- [sceAjmBatchJobGetInfo](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - writes the result and format sideband of a run job without buffers with the `SIDEBAND_FORMAT` flag (0x400000000000), as [KytyPS5](https://github.com/KytyPS5/KytyPS5) does, not confirmed on a PS5 title; the format reports bitrate 0 for ATRAC9 as in KytyPS5, while [shadPS4](https://github.com/shadps4-emu/shadPS4) derives it from the superframe size; before the instance is initialized it returns the not-initialized result (1) and no format, like the other run jobs here, while KytyPS5 returns result 0 with the format of the uninitialized decoder -- [sceAjmDecMp3ParseFrame](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps; the original file length (`parse_ofl`) layout (Xing/Info + LAME tag, VBRI, Fraunhofer `0xB4` block with its CRC, encoder delay = samples per frame + LAME delay + 529) also comes from shadPS4 only, and the Fraunhofer block has no other public description -- [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts +- [sceAjmBatchJobGetInfo](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - writes the result and format sideband of a run job without buffers with the `SIDEBAND_FORMAT` flag (0x400000000000), as [KytyPS5](https://github.com/KytyPS5/KytyPS5) does, not confirmed on a PS5 title; the ATRAC9 format's bitrate is the superframe's bits per second (superframe bytes * 8 * sample rate / samples per superframe), as [shadPS4](https://github.com/shadps4-emu/shadPS4) derives it and as a PS4 reported it (168000 for 448-byte superframes at 48 kHz), where KytyPS5 reports 0; before the instance is initialized it returns the not-initialized result (1) and no format, like the other run jobs here, while KytyPS5 returns result 0 with the format of the uninitialized decoder +- [sceAjmDecMp3ParseFrame](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: Layer I/II headers are accepted when no original-file-length (`parse_ofl`) lookup is requested, which is supported only for Layer III; MPEG-2.5 stops at 64 kbps. The `parse_ofl` layout (Xing/Info + LAME tag after side information, with the standard fixed Xing/Info offset retained for encoders such as [LAME](https://github.com/lameproject/lame/blob/master/libmp3lame/VbrTag.c#L854-L862), VBRI at byte 36 or after side information, Fraunhofer `0xB4` block with its CRC, encoder delay = samples per frame + LAME delay + 529) also comes from shadPS4 only, and the Fraunhofer block has no other public description. +- [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title (the one call in PPSA04354 passes a 24-byte result buffer, the 8-byte result plus the 16-byte ATRAC9 codec info written here; the call does not show the codec or the fields); a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout and little-endian `u16` packet byte count come from The Smurfs Dreams (`2, 48000, 0`) and Lost Soul Aside (`4, 48000, 1`). The third word is interpreted as the mapping family, as in [KytyPS5's initialize structure](https://github.com/KytyPS5/KytyPS5/blob/984cfcebd141b211aa9e0a8cde63b0ebe652d07a/src/libs/ajm/opus_decoder.h#L11-L15). Family 1 uses the conventional stream counts, coupled counts and channel map from [libopus 1.5.2's surround layout](https://github.com/xiph/opus/blob/v1.5.2/src/opus_multistream_encoder.c#L47-L55), passed to FFmpeg through an [RFC 7845 section 5.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1) header with zero pre-skip and gain; gapless skipping remains controlled by AJM sideband parameters. FFmpeg returns PCM in its native speaker order. The implicit stream layout, PCM channel order and invalid family-0 channel count result are not verified on a console. NHL 27 (PPSA34063) passes `1, 48000, 255`. Family 255 with one channel is decoded as one mono stream, the same as family 0; this is assumed, since the stream count, coupled stream count and channel mapping that a family-255 Opus header carries ([RFC 7845 section 5.1.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1.1)) are not passed. Only 48000 Hz, mapping families 0 and 1, and family 255 with one channel are implemented; other rates and families, and family 255 with more channels, throw - [AJM codec 23](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - taken as interleaved `s16` PCM that only passes through the resampler, from Broken Sword - Shadow of the Templars: Reforged (PPSA21480); no source names a codec 23 ([shadPS4's PS4 header](https://github.com/shadps4-emu/shadPS4/blob/5aa7ffa3f4f21bba827d7865ed8f69f98f993542/src/core/libraries/ajm/ajm.h#L19-L25) ends its codec list with `Max = 23`) and the name `CODEC_PCM` is ours. The title decodes its Ogg Opus and Vorbis audio itself and, for every block, initializes the instance (flags 0x1002) with `0, 2, 44100`, sets the ratio 0.91875 (44100/48000) and decodes 235 stereo frames into a 256-frame output. The 12 initialize bytes are taken as input format (0 is `s16`, other values throw), channel count and sample rate; the rate is only reported in the format sideband; other sizes throw; zero or more than 8 channels or a zero rate return the invalid-parameter result as for Opus, not verified. A run consumes its whole input and emits every output frame whose position (output index × ratio) is below the input frame count, with the 4-point Catmull-Rom of the other codecs' resampler and the first and last input frames repeated past the edges, so each run restarts at position 0; `totalDecodedSamples` counts input frames; `s32` and float outputs are accepted although only `s16` was seen. Throwing because the console's behaviour is unknown: a run after a resampled run without an initialize or context clear in between (whether the console carries the resampler position across runs), a ratio change per sample, gapless counts, an empty input or one that is not a whole number of frames, an output that cannot hold the resampled input, run flags other than `SIDEBAND_STREAM` and initialization through `sceAjmBatchJobControl` -- [sceAjmBatchJobSetResampleParametersEx](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `(info, instance, float ratio_start, float ratio_change_per_sample, flags, result)` signature is inferred from the name and from `sceAjmBatchJobSetResampleParameters`; with a zero change per sample it sets the ratio as `sceAjmBatchJobSetResampleParameters` does (Watch Dogs: Legion, PPSA01500, calls it so). How a non-zero change ramps the ratio is not confirmed on a console (no source: KytyPS5 ignores it, prosper stubs it, the PS5 firmware does not export the batch builders to a payload): it is assumed to be added to the ratio after each emitted output frame, clamped at zero, and kept across batches. RIDE 4 (PPSA01599) passes one call with a change of 4.6e-7, so the exact rule matters little there. -- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET (bit 13), INITIALIZE (bit 14) and the sideband input order (gapless decode, then initialize parameters) come from [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4); other flags throw +- [sceAjmBatchJobSetResampleParametersEx](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `(info, instance, float ratio_start, float ratio_change_per_sample, flags, result)` signature is inferred from the name and from `sceAjmBatchJobSetResampleParameters`, and PPSA02225's one call agrees with that layout (`rdi`, `esi`, `xmm0` and `xmm1` as two single floats, `edx`, `rcx`) without showing `flags` or the float roles. With a zero change per sample it sets the ratio as `sceAjmBatchJobSetResampleParameters` does (Watch Dogs: Legion, PPSA01500, calls it so). How a non-zero change ramps the ratio is not confirmed on a console (no source: KytyPS5 ignores it, prosper stubs it, the PS5 firmware does not export the batch builders to a payload): it is assumed to be added to the ratio after each emitted output frame, clamped at zero, and kept across batches. RIDE 4 (PPSA01599) passes one call with a change of 4.6e-7, so the exact rule matters little there. +- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET (bit 13), INITIALIZE (bit 14) and the sideband input order (gapless decode, then initialize parameters) come from [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4); the one call in PPSA25936 passes the flags 0x4000 (bit 14 only), an 8-byte input and an 8-byte output, which agrees with INITIALIZE alone and the 8-byte parameters, without showing the codec or the meaning of the words; other flags throw - [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET is assumed to clear the context as sceAjmBatchJobClearContext does - [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - initialize parameters are 8 bytes for ATRAC9 and M4AAC, 12 for Opus and none for MP3, as one title passes them; other codecs and sizes, and an output other than the 8-byte result, throw - [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - SIDEBAND_GAPLESS_DECODE with RESET overwrites both counts as in [RPCSX](https://github.com/RPCSX/rpcsx); shadPS4 ignores a zero count when the other is nonzero. Without RESET they differ, so it throws - [sceAjmBatchJobSetResampleParameters, sceAjmBatchJobGetResampleInfo](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm) - the ratio is taken as decoded samples per output sample and num_samples as the decoded samples the resampler holds, both inferred from FMOD (Hades II); the console's interpolation is unknown, a 4-point Catmull-Rom is used. The flags argument is ignored, as in shadPS4 and Kyty +- [sceAjmBatchJobControlBufferRa, sceAjmBatchJobRunBufferRa, sceAjmBatchJobRunSplitBufferRa, sceAjmBatchStartBuffer](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm, PS4) - the batch layout (chunk kinds, 20-bit payload, flags bits 32-51 as the flags chunk's payload, the builders' chunk order) follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/854b291c63360ff24e590809aff54cf5ab2dd4c9/src/core/libraries/ajm/ajm_batch.cpp) and was confirmed byte for byte for 64-byte control and 80-byte run jobs by a PS4 trace of Knowledge is Power: Decades (CUSA12435, Wwise, 32 545 batches dumped by a GoldHEN plugin). The console's builders write only bits 0-3 (kind), bit 4 (a buffer chunk's output direction) and bits 6-25 (payload) of the header word and leave bits 5, 26, 27 and 31 as the previous batch left them, so the kind is read as 4 bits plus the direction bit, not as 6 bits +- [sceAjmBatchStartBuffer](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm, PS4) - the statistics instance 0x80000 and the inline chunk come from shadPS4 only: the traced title builds every batch with the builders and a return address, and no title was seen writing an inline chunk. Flags are kept as passed. Unknown chunk kinds, control flags other than RESET, INITIALIZE and SIDEBAND_GAPLESS_DECODE, control jobs with run buffers and run jobs with a control input throw +- [sceAjmBatchStartBuffer refusals](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm, PS4) - probe batches through the console's builders on a PS4 showed that a run buffer chunk of size zero is dropped whatever its address (the job runs as without it: 0x10 and nothing consumed for no room, 0 for no input), and that a null address with a nonzero size refuses the batch before any of its jobs runs, with 0x80930011 returned and the error record holding 0x80930016, the job chunk's address, the chunk's offset in the job and the job's return address. Control buffer chunks follow the same rule here, not measured. A size not a multiple of 8, a chunk that overruns its job or the batch, or a job without flags are refused the same way with 0x80930011 in the record; the console's report for those was not measured +- [sceAjmBatchStartBuffer null and empty batches](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm, PS4) - a null batch address returns 0x80930005 whatever the size, as the PS5 builders' sceAjmBatchInitialize does for a null buffer, and a batch of zero bytes at a non-null address is accepted, runs nothing and completes at once (a null address with size 0 is refused like any null address); neither was measured on a console (the probe that passed a null batch froze the console before a result was written, so it is not retried on hardware) +- [ATRAC9 run results](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - probe runs of a title's ATRAC9 stream through the console's builders on a PS4 showed the run result naming the side that ran out: `PARTIAL_INPUT` (0x8) when the input ran out with room left, on a superframe boundary or not; `NOT_ENOUGH_ROOM` (0x10) when the room ran out with input left, after any number of frames, and with nothing consumed for a zero room; both (0x18) when the input ended just as the output filled, which is every Wwise run job. ATRAC9 run jobs with `RUN_MULTIPLE_FRAMES` follow those rules here, vibration streams included, which were not traced; resampled runs and the other codecs keep the earlier rule (a trailing partial frame is partial input, a run that produced nothing is out of room) +- [ATRAC9 run without RUN_MULTIPLE_FRAMES](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the same PS4 probe decoded exactly one frame and reported 0 for such a run, and a job read from a PS4 batch buffer (sceAjmBatchStartBuffer) does the same here. A job from the PS5 builders still decodes its whole input, as FMOD in PPSA29141 needs (it puts two run jobs in one batch with the second's input right after the whole of the first's); whether a PS5 stops after one frame is not measured. MP3 and Opus run jobs stop after one frame on both paths. The console also rejected the tests' synthetic silent superframe after three frames (`CODEC_ERROR | FATAL`, internal result -577) where LibAtrac9 decodes four, so that vector proves job plumbing, not decoding; on the title's stream LibAtrac9 matched the console's PCM within 4 LSB, 83.5% of the samples exactly ### Functional +- [sceAjmDecWVorbisCreateHeaderPacket](../../../core/libs/prx/libSceAjm.native/Export.cpp) (libSceAjmWVorbis, implemented in libSceAjm.native) - refuses an output smaller than the whole packet (56 bytes plus the setup packet) with `0x8A6E0003`. The PS5 12.00 bit writer only checks that each write starts inside the buffer, so the console can accept a buffer a few bytes short and write past it - [AJM batch contexts](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native): start, wait and cancel reject context IDs not allocated by initialization; batches belong to their creating context, as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0ebea15c24253b60f514fc37a348dabc8d0bcb90/src/core/libraries/ajm/ajm.cpp). This is not verified on PS5. Finalization still does not invalidate contexts or release their instances and outstanding batches; jobs execute synchronously and cancellation does not interrupt them. +- [sceAjmBatchStart](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp): console results for malformed batch descriptors and corrupted jobs are unverified. A null batch buffer or an offset beyond its capacity returns the existing invalid-parameter error; a truncated or inconsistent private packed job throws before its payload is read. The packed representation belongs to this implementation. -- [sceAjmDecMp3ParseFrame](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero) for a layer other than III, for a CRC-protected frame, or for a VBRI header outside MPEG-1 stereo: shadPS4 looks for the tag after the CRC and after the side information, while LAME and FFmpeg place it without the CRC and VBRI always at byte 36. -- [ATRAC9 gapless segments](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - a run job stops after the gapless total of output samples counted from the start of the segment, leaving the rest of its input unconsumed, and the next run starts the next segment with the decoder as it is and without a second skip. FMOD in PPSA29141 sets a total shorter than a sound's frames and reads the frames after it on later runs; with the output held at the total its stream reader waited for them forever and the last buffer repeated. The console's handling of the frames after the total is not documented; a reset (`SetGaplessDecode` with the reset flag, `ClearContext`, initialization) starts a new segment. An ATRAC9 run job without `RUN_MULTIPLE_FRAMES` decodes its whole input as one with it: FMOD in PPSA29141 puts two run jobs in one batch with the second's input right after the whole of the first's. Whether the console stops after one frame there is not verified; MP3 and Opus run jobs still stop after one frame +- [ATRAC9 gapless segments](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - a run job stops after the gapless total of output samples counted from the start of the segment, leaving the rest of its input unconsumed, and the next run starts the next segment with the decoder as it is and without a second skip. FMOD in PPSA29141 sets a total shorter than a sound's frames and reads the frames after it on later runs; with the output held at the total its stream reader waited for them forever and the last buffer repeated. The console's handling of the frames after the total is not documented; a reset (`SetGaplessDecode` with the reset flag, `ClearContext`, initialization) starts a new segment. - [AJM M4AAC decoder](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 2) - Ghost of Yotei (PPSA26344) initializes it through `sceAjmBatchJobControl`. The initialize parameters (`u32` config type, 1 ADTS or 2 raw, then `u32` sampling frequency index), instance flags (channels in bits 3-6, `ENABLE_SBR_DECODE` bit 32, `ENABLE_NONDELAY_OUTPUT` bit 33, surround interleave order bits 34-35), `{heaac, reserved}` codec info and decoder error result (`CODEC_ERROR | FATAL`) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/443f022450f6857256e780daa3d5a14029a748f7/src/core/libraries/ajm/ajm_aac.cpp) (PS4, fdk-aac) and are unverified on PS5; decoding uses FFmpeg's AAC decoder. Without `ENABLE_NONDELAY_OUTPUT`, the first two frames after initialization or reset are consumed without output. Raw input takes its channel configuration from the instance (0 means stereo) and is decoded as one access unit per run job. The format reports the last frame's bitrate. Invalid config type or raw sampling frequency index above 11 returns invalid-parameter result 4. The implementation throws for ADTS without a frame header, profiles other than AAC LC, multiple raw data blocks per ADTS frame, unsupported channel configurations, more channels than the instance supports, HE-AAC without `ENABLE_SBR_DECODE`, raw input with `RUN_MULTIPLE_FRAMES`, output room smaller than the previous frame length, and unknown codec flags. - [AJM](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp), [APR](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) and [NGS2](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) state is allocated with `new` and never freed on purpose: title threads still call into these libraries while `std::exit` runs static destructors, so ordinary statics would be destroyed under them. Keep them leaked; `guest_ajm_teardown` covers the AJM case on Unix. - [AJM Opus mapping family 255 with several channels](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native): the initialization words carry no stream count or channel mapping, so the decoder opens on the first packet. It counts the self-delimited streams of that multistream packet ([RFC 6716 appendix B](https://datatracker.ietf.org/doc/html/rfc6716#appendix-B)) and takes each stream's stereo flag from its TOC byte as coupled, then decodes with an identity channel mapping. A packet whose streams do not add up to the channel count throws. The layout is inferred, not read from the console; unverified on hardware. diff --git a/docs/dev/debt/libSceAjmi.md b/docs/dev/debt/libSceAjmi.md index d3ac224c47..0a5d293464 100644 --- a/docs/dev/debt/libSceAjmi.md +++ b/docs/dev/debt/libSceAjmi.md @@ -2,4 +2,4 @@ ### Unknown function info -- [libSceAjmi pending exports](../../../core/libs/prx/libSceAjmi/Export.cpp) - 5 named callable exports were identified in the PS5 3.20 export table and cross-checked against firmware 13.60. Signatures and behavior remain unverified; declarations use the unknown-signature convention and throw through `NotImplemented_nid_no_patch`. Evidence: [3.20 export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAjmi.c). +- [libSceAjmi pending exports](../../../core/libs/prx/libSceAjmi/Export.cpp) - of the 5 named callable exports identified in the PS5 3.20 export table and cross-checked against firmware 13.60, 2 are declared here: `sceAjmBatchJobInlineBuffer` and `sceAjmBatchJobRunBufferRa`, the latter as `sceAjmBatchJobRunBufferRa_nid_disambig1`, which the NID patcher exports under the name's NID, because libSceAjm implements the same name for PS4 titles; a title importing it from libSceAjmi still gets the not-implemented error at call time. Signatures and behavior remain unverified; the declarations use the unknown-signature convention and throw through `NotImplemented_nid_no_patch`. Evidence: [3.20 export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAjmi.c). diff --git a/docs/dev/debt/libSceAmpr.md b/docs/dev/debt/libSceAmpr.md index 764ea7ec20..ae062de3f3 100644 --- a/docs/dev/debt/libSceAmpr.md +++ b/docs/dev/debt/libSceAmpr.md @@ -7,7 +7,7 @@ - [AMPR command buffer offsets](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - recording with a current offset greater than the bound memory size throws before calculating remaining capacity or writing a command. This validates the emulator's own command-buffer encoding; the console's response to a corrupted opaque buffer object is unknown. - [AMPR command sizes](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - commands use the emulator's own encoding with an 8-byte header, so `Nop(n)` takes 8 + 4n bytes where the console record is n dwords including its header (`NopWithData(n)`: (n + 1) dwords). The measure functions agree with the appended sizes, but a title that pads to an exact offset or the end of the buffer with `Nop(remaining / 4)` gets `SCE_KERNEL_ERROR_EBUSY` or a shifted offset. The at-start flag of the `_04_00` writes is ignored; commands run in order, each after the previous one completes - [APR map begin and end](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - as in ampr_emu, `MapBegin`/`MapDirectBegin` map when they run and `MapEnd` only closes the region; while a region is open a second begin and every completion write return `EPERM`, the at-start `_04_00` writes are accepted -- [AMPR markers](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - signatures taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: `sceAmprCommandBufferSetMarkerWithColor` takes the color by pointer, the push and measure variants by value. Colors are not recorded +- [AMPR markers](../../../core/libs/prx/libSceAmpr/Export.cpp): colors are not recorded and command sizes use the host encoding. Native signatures confirm that set takes a color pointer and push/measure take the color value. - [__cxa_call_unexpected](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - takes the in-flight exception's unwind header and never returns, as [libcxxabi 18.1.8](https://github.com/llvm/llvm-project/blob/3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff/libcxxabi/src/cxa_personality.cpp#L1171-L1211) does. No `std::set_unexpected` is exported and the exception header's unexpected handler is never set, so the handler is always the default one, which calls `std::terminate` ([cxa_default_handlers.cpp](https://github.com/llvm/llvm-project/blob/3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff/libcxxabi/src/cxa_default_handlers.cpp#L88-L99)); it therefore forwards to libc's `__cxa_call_terminate`, which begins the catch and runs the current terminate handler. That matches libcxxabi except for which terminate handler runs: there `std::terminate` after `__cxa_begin_catch` ([cxa_exception.cpp L470-L477](https://github.com/llvm/llvm-project/blob/3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff/libcxxabi/src/cxa_exception.cpp#L470-L477)) runs the handler saved in the caught exception's header ([cxa_handlers.cpp L72-L93](https://github.com/llvm/llvm-project/blob/3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff/libcxxabi/src/cxa_handlers.cpp#L72-L93)), which `__cxa_throw` and `__cxa_init_primary_exception` set to `std::get_terminate()` when the exception is thrown ([cxa_exception.cpp L209-L214](https://github.com/llvm/llvm-project/blob/3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff/libcxxabi/src/cxa_exception.cpp#L209-L214), [L279](https://github.com/llvm/llvm-project/blob/3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff/libcxxabi/src/cxa_exception.cpp#L279)), while here the current global handler runs, as AnyPS5's own `std::terminate` does. The two differ only if `std::set_terminate` was called between the throw and this call. A null header terminates because this emulator's libc `__cxa_begin_catch` terminates on null (libcxxabi does not define that case). Dynamic exception specifications that would let a replaced unexpected handler throw a permitted type are not modelled ### Functional diff --git a/docs/dev/debt/libSceAppContent.md b/docs/dev/debt/libSceAppContent.md index 62bdd6066f..1dbcd434cf 100644 --- a/docs/dev/debt/libSceAppContent.md +++ b/docs/dev/debt/libSceAppContent.md @@ -2,12 +2,15 @@ ### Unknown function info -- [sceAppContentDownloadDataGetAvailableSpaceKb](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - a title whose `param.json` declares `downloadDataSize` 0 gets 0 KB and OK; the console's behaviour for a title without a download data area is unverified (it may return an error instead). KytyPS5 does not special-case it, and shadPS4 stubs 1048576 KB +- [sceAppContentDownloadDataGetAvailableSpaceKb](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - a title whose `param.json` declares `downloadDataSize` 0 gets 0 KB and OK; the console's behaviour for a title without a download data area is unverified (it may return an error instead). KytyPS5 does not special-case it, and shadPS4 stubs 1048576 KB. One disassembled title (PPSA15246) calls it with the `/download0` mount point and a zeroed 64-bit output, ignores the return value and multiplies the output by 1024, so for that title an error that leaves the output untouched and an OK with 0 KB look the same - [sceAppContentDownloadDataFormat](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - empties the `/download0` directory, creating it if needed. Imported by PPSA01317. A null mount point returns `PARAMETER` and another mount point returns `NOT_MOUNTED` as in `sceAppContentTemporaryDataFormat` and `sceAppContentDownloadDataGetAvailableSpaceKb`; the console's error codes for both and its behaviour with files still open are unverified -- [libSceAppContent pending exports](../../../core/libs/prx/libSceAppContent/Export.cpp) - 17 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAppContent.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceAppContent pending exports](../../../core/libs/prx/libSceAppContent/Export.cpp) - 15 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAppContent.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. ### Functional +- [Initialization](../../../core/libs/prx/libSceAppContent/Export.cpp) - service readiness and boot attributes are not modeled; successful initialization returns a zero boot record. Initialization-state guards, SDK 0x01500000 repeated-initialization errors and reserved-field checks are recovered. SDK-dependent service-error translation remains incomplete. + - [Additional content](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries; `sceAppContentAddcontEnqueueDownload` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no download is ever queued; `sceAppContentAddcontDelete` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no additional content is ever installed. `sceAppContentGetAddcontDownloadProgress` answers `PARAMETER` for a null label or progress and `NOT_FOUND` otherwise, as [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2328-L2341) does for a label that is not installed additional content, and never writes the progress. Its 16-byte layout (`DataSize`, then `DownloadedSize`, both 64-bit) follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L45-L54) and is unused here. The signatures of `sceAppContentAddcontEnqueueDownload` and `sceAppContentGetAddcontDownloadProgress` follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L140-L148), and the signature of `sceAppContentAddcontDelete` follows [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.h#L91-L92); dlc_emu is not a console measurement and no console source gives the error codes, so the signatures and the error codes are unverified -- [Temporary app-content storage](../../../core/libs/prx/libSceAppContent/Export.cpp) tracks one `/temp0` mount, preserves its files on unmount, and rejects new path resolutions until remount. Mount, format, space-query and unmount errors follow [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/sys/sys_path.pas); the pointer ABI is also declared by [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/AppContent/AppContent.cs). Existing open host descriptors remain usable after unmount; console behaviour with open files is unverified. Host filesystem failures still throw. +- [sceAppContentGetEntitlementKey](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - the service result without an entitlement is unverified: this model returns `DRM_NO_ENTITLEMENT` and leaves the 16-byte key unchanged. Initialization, null-pointer and SDK 0x01500000 label-padding checks match the reported [firmware 13.42 module probe](https://github.com/boykopovar/AnyPS5/pull/9638#issuecomment-6390573735), run in an emulator, not on hardware. That module forwards service labels 0, 7, 8 and 0xFFFFFFFF without checking them; this library ignores the service label. The system service's own validation and errors remain unknown. +- [Temporary app-content storage](../../../core/libs/prx/libSceAppContent/Export.cpp) console behaviour and hardware return values are unverified, including behaviour with open files across unmount; host filesystem failures throw. Storage models one `/temp0` mount, preserving files on unmount while rejecting new path resolutions until remount, and leaving existing open host descriptors usable. `sceAppContentTemporaryDataMount` delegates to `sceAppContentTemporaryDataMount2` with option 1 (`TEMPORARY_DATA_OPTION_FORMAT`) to format on mount as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/app_content/app_content.cpp#L550-L553) does. Mount, format, space-query and unmount errors follow [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/sys/sys_path.pas); pointer ABI follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/AppContent/AppContent.cs). diff --git a/docs/dev/debt/libSceAppInstUtil.md b/docs/dev/debt/libSceAppInstUtil.md index ecf5fb2a82..28ed171a11 100644 --- a/docs/dev/debt/libSceAppInstUtil.md +++ b/docs/dev/debt/libSceAppInstUtil.md @@ -2,4 +2,104 @@ ### Unknown function info -- [sceAppInstUtilAppIsInUpdating, sceAppInstUtilAppGetSize, sceAppInstUtilAppInstallPkg, sceAppInstUtilAppPrepareOverwritePkg, sceAppInstUtilAppRecoverApp, sceAppInstUtilAppUnInstallAddcont, sceAppInstUtilAppUnInstallPat, sceAppInstUtilAppUnInstallTheme, sceAppInstUtilAppUnInstallTypes, sceAppInstUtilGetInstallProgressInfo, sceAppInstUtilGetInstallProgress and sceAppInstUtilGetPrimaryAppSlot](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAppInstUtil.c), but no credible prototype was found. They are declared without parameters only to resolve the known imports; this is not an ABI claim and callers must not call them until a prototype is verified. The other seven declarations follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppInstUtil/AppInstUtil.cs) +- [sceAppInstUtilAppInstallPkg](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) - the package path is followed by an opaque 64-bit value sent to the install service; its meaning and pointer-versus-integer type remain unresolved. +- [sceAppInstUtilGetInstallProgressInfo, sceAppInstUtilGetInstallProgress and sceAppInstUtilGetPrimaryAppSlot](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) - take a title-id string and an output pointer. The IPC replies contain 48, 4 and 4 bytes respectively; callers and reply producers are needed to establish the field types. +- [sceAppInstUtilAppPrepareOverwritePkg, sceAppInstUtilAppRecoverApp and sceAppInstUtilAppUnInstallTheme](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) - absent from the supplied system module and from the supplied decompiled callers; the old export inventory establishes names but not prototypes. +- [sceAppInstUtilAppGetAppInstallStatusByAppInfo](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppUnInstall2](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppUnInstallForRemote](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppUnInstallForRemote2](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetAddcontListTotalSize](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetAddcontInfo](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetAddcontInfoByContentId](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetAddcontInstalledStatus](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetInsertedDiscTitleId](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppUpdateStartTime](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppInstallSharePlayLink](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppInstallSharePlayLinkByUser](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetAddcontNum](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppCancelUnInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppDestroyMetadata](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilCheckAppSystemVer](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppConvertAppDownloadToDisc](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetMainAppTitleIdForSP](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetSubAppTitleIdForSP](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetMainAppTitleId](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetSubAppTitleId](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetAppInstallStatus](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppDestroyPkg](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilJson](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetInsertedDiscRoleType](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilRequestInstallDiscGame](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilRequestDataDiscCopy](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilCancelDataDiscCopy](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetDataDiscCopyProgress](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilSetPrimaryAppSlot](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetAppSlotInfoList](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetAppEmptySlot](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppInstallGetLinkState](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppUnInstallForMultiInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppInstallPs4CloudGame](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppStoreCompilationDiscInfo](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetCompilationDiscInfo](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetStoreCompilationDiscInfo](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetInsertedDiscTitleIdList](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppInstallRequestCompilationDiscApp](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetNoRelatedAddcontTitleList](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetIrremovableAppList](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppRequestMoveApps](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppProhibitDownloadInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppAllowDownloadInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetAppMoveProgressInfo](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppCancelMoveApps](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetMoveErrorAppList](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetAllAppSize](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppCancelGetAllAppSize](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetInsertedDiscMasterDataId](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetAppOtherSize](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppCancelGetAppOtherSize](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetInsertedDiscTotal](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppSetRemasterInstallType](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetInstallProgressInfoForMultiDisc](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilMountBdEmu](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilUnmountBdEmu](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetInstallStatus](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilResumeInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilPauseInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilRegisterDownload](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilStartInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilInstallPatch](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetPatchInstallStatus](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilResumePatchInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilPausePatchInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilCancelInstallPatch](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilCancelInstallAddcont](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilCancelInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilCancelForMultiInstall](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilInstallByPackage](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilIsRestoreState](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetRestoreState](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetAddons](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetInstallOptions](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetContentSize](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilCancelGetInformation](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetWorkspaceStatus](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilDeleteWorkspace](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetParentalControlLevel](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilCheckLatestPatch](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilCancelCheckPatch](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppInstallPsNowIcon](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetAddcontInfoList2](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetAddcontInfoList3](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilClearAddcontInfoCache](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetContentSize2](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppInstallContentPush](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetAvailableStorage](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilSwitchInstallLocation](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetConfigList](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilSwitchConfig](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilRestoreDataSource](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilGetNeededStorageSize](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilAppGetStorageDestType](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature +- [sceAppInstUtilInstallAddons](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - unknown signature diff --git a/docs/dev/debt/libSceAudio3d.md b/docs/dev/debt/libSceAudio3d.md index c7d94441a4..9c17a838c6 100644 --- a/docs/dev/debt/libSceAudio3d.md +++ b/docs/dev/debt/libSceAudio3d.md @@ -11,7 +11,7 @@ ### Unknown function info - [sceAudio3dPortOpen](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - buffer modes 0 and 1 (also selected by the 0x10 and 0x18 parameter sizes) and 3 beds depend on an SDK version check in the module ([prosper](https://github.com/mattias800/prosper)) and throw. A repeated `sceAudio3dInitialize` (`NOT_READY` in shadPS4, 0 in prosper) and `sceAudio3dPortAdvance` on a full queue (`NOT_READY` in shadPS4, unhandled in KytyPS5) throw too -- [sceAudio3dPortGetAttributesSupported](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - the supported list (late reverb level, downmix spread radius, downmix spread height aware) is inferred from the attributes `sceAudio3dPortSetAttribute` accepts; its order and the count written when the caller capacity is smaller are unverified. [shadPS4](https://github.com/shadps4-emu/shadPS4) returns object attributes (PCM, gain...) instead, prosper and KytyPS5 have no body +- [sceAudio3dPortGetAttributesSupported](../../../core/libs/prx/libSceAudio3d/Export.cpp): the native export validates the port before the count pointer, writes the total count without reading its incoming value, and writes the complete list when an array is supplied. These checks are matched. The list itself remains the host's three port attributes: the native list derives from AudioOut2 device capability masks, SDK version and platform mode, which the host does not yet model. - [sceAudio3dObjectReserve](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - object ids, the `max_objects` limit and the checks of `sceAudio3dObjectReserve` and `sceAudio3dObjectUnreserve` follow [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4), the only reference with a body: ids count up from 1 per port, skipping 0, 0xFFFFFFFF and ids in use, and a failed reserve writes 0xFFFFFFFF - [sceAudio3dStrError](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature - [sceAudio3dPortOpenEx](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature @@ -20,11 +20,7 @@ - [sceAudio3dPortGetStatus](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature - [sceAudio3dReportRegisterHandler](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature - [sceAudio3dReportUnregisterHandler](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dGetSpeakerArrayMemorySize](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dCreateSpeakerArray](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dDeleteSpeakerArray](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dGetSpeakerArrayMixCoefficients](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dGetSpeakerArrayMixCoefficients2](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature +- [sceAudio3dCreateSpeakerArray](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - the speaker-array calls return the AudioOut2 result unchanged: the console translates AudioOut2 speaker-array errors into Audio3d codes, but this project's AudioOut2 speaker-array calls throw instead of returning error codes, so there is nothing to translate yet - [Audio3d bed downmix coefficients](../../../core/libs/prx/libSceAudio3d/Mixer.hpp) (libSceAudio3d) - the centre and the surrounds fold into stereo with a 0.7071 gain and the LFE channel is dropped, which follows [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4): its `DOWNMIX_CENTER` and `DOWNMIX_SURROUND` are 0.7071 and its `DOWNMIX_LFE` is 0, so the `if constexpr` that adds the LFE is compiled out. [prosper](https://github.com/mattias800/prosper) disagrees on the LFE only: it folds it into both sides with a 0.5 gain, in its AudioOut2 main bed path, which is not an Audio3d path. KytyPS5 has no Audio3d mixing at all. The absolute level is unmeasured: fpPS4 scales the front by 1 + 3/sqrt(2) instead of 1 - [Audio3d bed channel order](../../../core/libs/prx/libSceAudio3d/Mixer.hpp) (libSceAudio3d) - the buffer is read as L, R, C, LFE, Ls, Rs, Lrs, Rrs, the standard order, which three references agree on: [shadPS4](https://github.com/shadps4-emu/shadPS4) reads the front, centre, LFE and surround samples at those indices, [prosper](https://github.com/mattias800/prosper) documents FL, FR, FC, LFE and then surround pairs, and fpPS4 names them `_FL=0 _FR=1 _FC=2 _LF=3 _SL=4 _SR=5 _BL=6 _BR=7`. fpPS4's `STD` layout swaps the side and back pairs, which this library does not model - [sceAudio3dAudioOutOutput](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - the return is the number of frames per channel in the queued block, which follows [KytyPS5](https://github.com/KytyPS5/KytyPS5) and [prosper](https://github.com/mattias800/prosper), whose `sceAudioOutOutput` returns the grain, and this project's own `sceAudioOutOutput`, which returns the port's `samplesNum` (the buffer length in frames). [shadPS4](https://github.com/shadps4-emu/shadPS4) returns the total samples instead (`len * channels`) and fpPS4 returns 0; no console measurement settles it diff --git a/docs/dev/debt/libSceAudioIn.md b/docs/dev/debt/libSceAudioIn.md index 04f555333b..abb06131ee 100644 --- a/docs/dev/debt/libSceAudioIn.md +++ b/docs/dev/debt/libSceAudioIn.md @@ -6,7 +6,6 @@ ### Unknown function info -- [sceAudioInIsPadEmbeddedMic](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - name from its NID (`X+4jdIS75P0`); signature `int32_t(int32_t handle)` from the PPSA26344 call site in #2355; the result's meaning is unconfirmed, so it still throws rather than invent one - [sceAudioInDeviceIdOpen](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInDeviceIdAsyncOpen](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInSetUsbGain](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature @@ -17,14 +16,12 @@ - [sceAudioInSetPortConnections](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInSetPortStatuses](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInGetHandleStatusInfo](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature -- [sceAudioInOpenEx](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature -- [sceAudioInHqOpenEx](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInDeviceOpenEx](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInChangeAppModuleState](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInSetSparkParam](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInSetSparkSideTone](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature -- [sceAudioInSetCompressorPreGain](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature -- [sceAudioInSetNoiseGateThreshold](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature +- [sceAudioInSetCompressorPreGain](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature; the PS5 12.00 module returns 0 without reading any argument, and so does this one +- [sceAudioInSetNoiseGateThreshold](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature; the PS5 12.00 module returns 0 without reading any argument, and so does this one - [sceAudioInSetMicLevel](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInSetDeviceMuteStatus](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInSetDevConnection](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature @@ -42,5 +39,6 @@ ### Functional -- [sceAudioInHqOpen, sceAudioInAsyncOpen](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) open the same port as `sceAudioInOpen`, so `sceAudioInInput`, `sceAudioInGetSilentState` and `sceAudioInClose` accept their handles. The signatures agree across [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/fc5d2cc219c43c8c8f354153e7b3d0c04a5d506e/src/core/libraries/audio/audioin.cpp), [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Audio/AudioIn.cs) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/c86c8b4d9333a766cc8c4495f89f893bf35e6199/src/libs/audio.cpp). HqOpen is limited to 128 samples at 48 kHz, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/c86c8b4d9333a766cc8c4495f89f893bf35e6199/src/libs/audio.cpp) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Audio/AudioIn.cs) describe the higher-quality path: a longer grain returns the invalid-size error and a lower rate the invalid-frequency error. AsyncOpen also accepts the 384-sample grain, the largest [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Audio/AudioIn.cs) documents for the low-latency path, and returns the invalid-size error for every other size; the console's shorter average return interval of an async port's blocks is not modelled, so like a synchronous port `sceAudioInInput` paces one grain at a time. Param 0x10 opens a float mono port like 0x11, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/c86c8b4d9333a766cc8c4495f89f893bf35e6199/src/libs/audio.cpp) accepts both, while SharpProspero documents only 0x11. Host capture pacing and console behavior remain unverified. -- [sceAudioInGetGain, sceAudioInGetRerouteCount](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - signatures and semantics follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Audio/AudioIn.cs#L92-L110). `sceAudioInGetGain` writes the unattenuated linear gain `1.0f` into `*gain` before validating `handle`, returning `0` for an open port and `0x80260101` (`SCE_AUDIO_IN_ERROR_INVALID_HANDLE`) otherwise; a null `gain` pointer throws because the console result is unverified, and host or system input attenuation is not modelled. `sceAudioInGetRerouteCount` returns `0` for an open port and `0x80260101` otherwise, as each port opens its SDL capture device once and capture-device hot-swapping is not modelled. +- [sceAudioInIsPadEmbeddedMic](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - returns 0 for every open port: on the console it returns 1 when the port captures from the controller's built-in microphone and 0 for another input device, but ports here capture from the host's default input device +- [sceAudioInHqOpen, sceAudioInAsyncOpen](../../../core/libs/prx/libSceAudioIn/Export.cpp) - PS5 wrapper validation requires 48000 Hz followed by exactly 128 samples for both calls; HqOpen then requires format 2. The host uses the same SDL capture path for both, so native asynchronous scheduling and high-quality processing are not reproduced. Common port validation, device selection and host capture pacing remain incomplete. +- [sceAudioInGetGain, sceAudioInGetRerouteCount](../../../core/libs/prx/libSceAudioIn/Export.cpp) - GetGain writes 1.0f before handle validation and returns 0x80260105 for a null output, as confirmed by the PS5 module. Native handle bit checks and uninitialized/inactive port errors differ from the host port table. Native reroute counters follow device reassignment; the host opens each SDL device once and reports zero. diff --git a/docs/dev/debt/libSceAudioOut.md b/docs/dev/debt/libSceAudioOut.md index f6e6901f1f..d11e3678ee 100644 --- a/docs/dev/debt/libSceAudioOut.md +++ b/docs/dev/debt/libSceAudioOut.md @@ -1,20 +1,26 @@ # libSceAudioOut technical debt +### Silent stubs + +- [sceAudioOut2LoTerminate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Low.cpp) (libSceAudioOut) - returns 0, as the PS5 12.00 module (0x248d0) does whether or not the low-latency service runs. The module disconnects from the audio server and clears the service tables; here `sceAudioOut2LoInit` starts no service and open contexts and ports are left as they are +- [sceAudioOutMasteringTerm](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - returns 0 without doing anything: there is no mastering stage to tear down, and `sceAudioOutMasteringInit` keeps no state. Whether the console fails a Term without a matching Init is unknown +- [sceAudioOutSetMainOutput](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - returns 0 and does nothing, as the console function does on firmware 12.70: [PS5-Audio-Passthrough-Research](https://github.com/sainsaji/PS5-Audio-Passthrough-Research/blob/cf3c921d79bf00db4847f31b68a7bd95a8037cd7/docs/FINDINGS.md#L25) reads that export as `xor eax,eax; ret`. The export reads no argument, so its parameters stay unknown; other firmware versions are unverified + ### Unknown function info - [sceAudioOut2ContextAdvance and sceAudioOut2ContextPush](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp) (libSceAudioOut) - with an SDL device, Advance snapshots each port's PCM and gain; Push consumes completed grains in order and a blocking Push waits for Advance when none is ready. Inferred from PPSA26358, which rewrites the same 512-frame, 12-channel float buffer on a producer thread and pushes on another thread. Console buffer ownership, snapshot timing and empty-queue errors are unverified. Completed grains remain queued until Push consumes them, so memory use grows with an unconsumed PCM backlog. The console backlog limit is unverified. A nonblocking Push without a completed grain throws; this is a runtime restriction, not a console measurement. Context destruction concurrent with active calls remains unsupported. Without an SDL device the existing wall-clock path remains. -- [sceAudioOut2UserGetSupportedAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp) (libSceAudioOut) - the user handle and two uint32 output pointers follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp), not a console measurement. The masks describe this runtime: no context attributes and port attributes PCM (bit 0) and gain (bit 1). Null outputs and unknown or destroyed user handles throw; console validation order and error values are unknown. Users are tracked for this query, but port ownership and busy-user destruction are not implemented. The other PPSA23566 import, [sceConvertKeycodeGetCharacterFromKeyboardData](../../../core/libs/prx/libSceConvertKeycode/Export.cpp), remains declared without parameters, with an unknown signature. Names from the shadPS4 aerolib NID list +- [sceAudioOut2UserGetSupportedAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp) - the PS5 module confirms a pointer-sized user handle and two optional uint32 outputs. Handles 0/-1 return 0x80268010 before checking outputs; two null outputs return 0x80268001. Host masks still describe only implemented PCM/gain support, unlike native SDK- and device-dependent masks. Unknown or destroyed host handles throw; native arbitrary handles are pointers and cannot be validated by the host registry. Port ownership and busy-user destruction remain unimplemented. - [sceAudioOutExOpen, sceAudioOutExConfigureOutput, sceAudioOutExClose](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - the HDMI bitstream calls follow [PS5-Audio-Passthrough-Research](https://github.com/sainsaji/PS5-Audio-Passthrough-Research/blob/cf3c921d79bf00db4847f31b68a7bd95a8037cd7/docs/FINDINGS.md#L31-L80), which disassembles firmware 12.70's libSceAudioOut and logs the calls on a console. `sceAudioOutExOpen(user, mode)` returns `0x80260015` for a mode above 10 or outside mask `0x61F`; otherwise it opens a port of internal type 6 in S16 stereo, 256 frames at 48000 Hz for modes 0, 1, 2 and 9 and 1024 frames at 192000 Hz for modes 3, 4 and 10. `sceAudioOutExConfigureOutput(control, flags, mode, target, option)` returns `0x80268009` when `control` is not 0, takes modes 0 to 10 and 0xFF and targets 1 to 3 and 0xFF, and returned 0 on the console; `sceAudioOutExClose` returned 0. The argument registers match [Jelly5](https://github.com/02dnot/Jelly5/blob/552b866d89b2a58b27f7f7b0484dc743ce235526/app/src/jelly5_bitstream.cpp#L26-L28) and PPSA99051's [bitstream_out.cpp](https://github.com/sainsaji/PS5-Audio-Passthrough-Research/blob/cf3c921d79bf00db4847f31b68a7bd95a8037cd7/sample-app/src/platform/ps5/bitstream_out.cpp#L29-L32). Audio reaches the host through SDL as PCM and the host has no HDMI bitstream output, so an Ex port has no host device: `sceAudioOutOutput` paces it at its own rate and drops its IEC 61937 bursts, and `sceAudioOutExConfigureOutput` changes nothing. Nonzero `flags` or `option`, other modes or targets, `sceAudioOutExClose` of a handle that is not an Ex port, and `sceAudioOutGetPortState` on an Ex port throw, since the console's answers are unknown -- [sceAudioOutExGetMonitorInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) reports a sink without audio descriptors: it zeroes the buffer and returns 0, as `sceAudioOutSysGetHdmiMonitorInfo` does, since audio reaches the host through SDL as PCM and there is no EDID to describe. The arguments `(0, 0, info, size)` and the descriptor count as a 32-bit value at +0x98, with 8-byte descriptors from +0xA0, follow [Jelly5](https://github.com/02dnot/Jelly5/blob/552b866d89b2a58b27f7f7b0484dc743ce235526/app/src/jelly5_bitstream.cpp#L62-L117) (PPSA99505), which passes a 0x400-byte buffer and read that layout from one TV; with no descriptor listed it decodes the audio itself instead of opening a bitstream port. The meaning of the first two arguments and the structure's size are unknown: nonzero first or second arguments and sizes below 0xA0, which cannot hold the count, throw, and every byte of the declared size is written as 0. A null buffer returns `0x80260004` like the other AudioOut calls; the console's code is not measured -- [sceAudioOut2Set3DLatency](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - assumed to take `(user_id, output)`: [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp) reads `(user_id, output, latency_us)`, but the known calls set just edi and esi. The validation follows KytyPS5: an output above 2 returns `0x80268001` and every user id is accepted, where before only `(0xFF, 2)` (PPSA26344) and `(0xFF, 1)` (PPSA14632) were; the latency value of the three-argument reading is never received, and there is no 3D rendering stage to apply anything to -- [sceAudioOut2MasteringInit](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - flag values unknown; only 0 is accepted. Whether the console reports an error for a repeated `sceAudioOut2MasteringInit` or for `sceAudioOut2MasteringTerm` without a matching initialization is unknown; both return 0. PPSA01289 creates the audio device of its next world before it destroys the previous one, so it calls Init twice before Term twice, and only logs a non-zero result -- [sceAudioOut2MasteringGetState and sceAudioOut2MasteringSetParam](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - the state layout, the states ids 1 (default) and 2 (V2), the descriptor ids `0x01010001`, `0x01020001` and `0x01010003` and the errors `0x80268201` and `0x80268204` follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp), not a console measurement. GetState zeroes the states and writes the two descriptors; with no mastering stage the compressor and limiter values stay zero. SetParam accepts any parameter set and records nothing, as KytyPS5 does; a null parameter returns `0x80268201`, the mastering invalid-API-parameter error KytyPS5 returns for a null state, which is an inference rather than a measurement. The meaning of `output` and `user` is unknown and both are ignored -- [sceAudioOut2SetSystemDebugState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - records the debug state id in the trace and returns 0, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp) does; the host has no system mix to apply it to. The debug state ids are unknown, so every id is accepted, and a null param returns `0x80260502` like the other AudioOut2 calls. Not measured on a console +- [sceAudioOutExGetMonitorInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) - native validation requires first argument zero, a non-null buffer and exactly 1024 bytes, in that order (errors 0x80260003/04/06); the second argument is ignored. The host still reports no audio descriptors by zeroing the record. Native content comes from monitor/device services and system audio configuration, which the SDL backend does not reproduce. +- [sceAudioOut2Set3DLatency](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - the two 32-bit arguments and selector validation match the system library: selector 0 accepts outputs 0 through 2; selector 255 accepts outputs 1 and 2. No third latency argument exists. The host has no system 3D rendering stage, so the call still does not apply latency and does not reproduce the system initialization and output-availability checks. +- [sceAudioOut2MasteringInit and sceAudioOut2MasteringInitEx](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - the initialized flag and the flag checks follow the PS5 12.00 module (0x22360): a repeated initialization returns 0, other flags return `0x80268201`, and InitEx also accepts bit 31, which the console passes to the audio server as a mastering mode. Nothing is sent to an audio server, and `sceAudioOut2MasteringInit` does not return the `0x80268006` the console returns before `sceAudioOut2Initialize`, which is a missing state check of its own. PPSA01289 calls Init twice before Term twice; by the module its second Term gets `0x80268203`, and the title only logs a non-zero result +- [sceAudioOut2MasteringGetState and sceAudioOut2MasteringSetParam](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - the state layout, the states ids 1 (default) and 2 (V2), the descriptor ids `0x01010001`, `0x01020001` and `0x01010003` and the error `0x80268204` follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp), not a console measurement. GetState zeroes the states and writes the two descriptors; with no mastering stage the compressor and limiter values stay zero. SetParam accepts any parameter set and records nothing, as KytyPS5 does. The checks before them follow the PS5 12.00 module: both return `0x80268203` before mastering is initialized, GetState returns `0x8026800C` for a null state, SetParam returns `0x80268201` for a null parameter and `0x80268205` for flags above 1. The console's checks of `output` and `user` are not reproduced; both are ignored +- [sceAudioOut2SetSystemDebugState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - records the debug state id in the trace and returns 0, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp) does; the host has no system mix to apply it to. Id `0x20` with a 4-byte param throws, and the not-initialized error is not returned. Not measured on a console - [sceAudioOut2GetSpeakerArrayCoefficients](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - validates the handle and the coefficient count like the ambisonics call, then writes the front pair at full gain and silence for every other speaker, ignoring the position, spread, height awareness and downmix spread radius: [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp) and [prosper](https://github.com/mattias800/prosper/blob/main/prosper/src/hle/audio/hle_audio.cpp) both return that pair. The console's VBAP gains over the array's directions are not measured -- [sceAudioOutSetMixLevelPadSpk](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - effect of a negative mix level unknown; only 0 to 32768 (0 dB) is accepted - [sceAudioOutSetMixLevelPadSpk](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function - [sceAudioOutGetLastOutputTime](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device +- [sceAudioOutGetSystemState](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - the 32-byte layout (`float loudness`, 4 reserved bytes, 3 reserved 64-bit words) follows shadPS4's `OrbisAudioOutSystemState` and SharpProspero's `SceAudioOutSystemState` (`Size = 32`); it is not confirmed on a console. Loudness is reported as a constant 0.0f, although SharpProspero documents it in LKFS, so the value is unconfirmed. shadPS4 returns NOT_INIT before `sceAudioOutInit`; that check is not modelled. Imported by PPSA30528, which fails to load without it - [sceAudioOutSysOpen, sceAudioOutSysConfigureOutput, sceAudioOutSysClose](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - imported by PPSA99051 and PPSA99997, which fail to load without them; the signatures follow [EVO-PLAYER-PS5](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/da0c0451333bab82e79fffd2de4718e917f9dcb0/projects/evoplayer/core/src/audio/evo_pt.cpp#L24-L29), whose audio code both titles share, and are not confirmed on a console. `sceAudioOutSysOpen(user, mode)` opens a port only for mode 5, the TrueHD MAT route: [the same project's carrier table](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/da0c0451333bab82e79fffd2de4718e917f9dcb0/projects/evoplayer/core/src/audio/iec61937.cpp#L125-L140), read from sceAudioOutExOpen's own tables in firmware 12.70, describes it as a 768 kHz port of 1024 S16 8-channel frames whose 16-byte frames are consumed at 192 kHz; every other mode throws. Audio reaches the host through SDL as PCM and the host has no HDMI bitstream output, so like an Ex port a Sys port has no host device: `sceAudioOutOutput` paces it at its own rate and drops the MAT bursts. `sceAudioOutSysConfigureOutput(type, flags, mode, target, option)` accepts only the observed `(1, 0, 5, 1, 0)` HDMI MAT switch and changes nothing; other arguments throw. `sceAudioOutSysClose` closes a Sys port and throws for any other handle. The restore path EVO-PLAYER-PS5 takes after a Sys port calls `sceAudioOutExConfigureOutput(0, 0, 0xFF, 0xFF, 0)`, so no Sys reset combination is known - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - MAIN (type 0) format validation follows PS5 firmware 13.60 measurements: bits 8-11 accept 1, 2, 8 or 12, bits 0-6 accept 0 or 1, bit 7 requires 8 channels, and bits 12-31 do not affect validation. The sample types are still read as float and int16; bit 7's layout, the PCM effect of bits 12-31 and the port `flags` are unverified. Other port types retain unverified format validation, including 6 channels and rejection of bits 12-31 - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - 12 channels are assumed to be L R C LFE Ls Rs Lb Rb Ltf Rtf Ltb Rtb as in KytyPS5's 12-to-8 fold, and 6 and 8 channels to be its first 6 and 8; no title or SDK header confirms the order @@ -22,8 +28,11 @@ - [sceAudioOut2PortSetAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - each PCM buffer set (attribute 0) is taken as one grain: buffers set between two pushes are queued and played one per grain, at most 16 queued. A grain with no new buffer repeats the last one only when the port has always been given that same buffer (rewritten in place); a port whose buffers move on plays silence instead. Inferred from PPSA12544, whose object port (type 6) is given one grain of a 64-grain ring per set, often two or three between pushes and then none for a few; the console behaviour for several sets per push and for a push without a new buffer is not documented - [Legacy vibration ports](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp): PPSA99902's [public input wrapper](https://github.com/Nazky/UnleashedRecomp-Prospero/blob/2ce067a1d51bb58670c7ee4a3aed893c504da805/UnleashedRecomp/hid/driver/sdl_hid.cpp#L316) opens type 10 with 256 stereo S16 or F32 frames at 48000 Hz. Mono duplicates its channel as AudioOut2 does; wider vibration layouts throw. Native controller output uses AudioOut2's four-channel layout, or its six-channel PulseAudio mapping. Without a controller sound card the existing RMS motor transform is applied to segments of at most 256 frames, scheduled by the legacy queue's frame count and rate; drain and close remove only that port's segments. Concurrent sources combine by the maximum per motor, and the pad consumer uses this approximation only for controllers other than DualSense. This host timing and motor model is not a console measurement or waveform reproduction; legacy native output is unmeasured on hardware, and user routing retains the single-controller limitation. AudioOut2 retains its separate 80 ms peak hold. -- [sceAudioOutDeviceIdOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - imported by PPSA01317, which fails to load without it; declared without parameters, signature unknown. It opens a port on a given audio device; which device ids exist and how they map to host outputs is unknown, so it throws -- [sceAudioOut2PortGetState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) and [sceAudioOut2GetSpeakerInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - the port state reports the channel count of the port's data format, output main, volume 127 and a zero reroute counter and flags. Taking the channel count from the data format follows [SharpEmu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs), another emulator. A payload process on a PS5 (firmware 13.60), which is not the foreground application, read output 0, volume -1 and reroute counter 5 for its main ports, and 6 channels for an 8-channel port on a 5.1 system, so a title's values are unverified, including the output of a pad port, the volume after `sceAudioOut2PortSetAttributes` and the reroute counter on a device change. The speaker info reports type 0 with available bits `0x3`, the front left speaker at +30 degrees and the front right one at -30, and -360 for the azimuth and elevation of every other slot. On that console the type followed the HDMI device type and the available bits its channels: type 0 with the device set to TV, 1 to AV amplifier and 2 to sound bar, which [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs) names television, AV receiver and sound bar (3 is headphones there), and available bits `0x3` for 2 channels and `0x3F` for 5.1 (centre at 0, surround pair at +110 and -110, -360 for the LFE). The front pair and the empty slots read the same in every setting; 7.1 on the AV amplifier reported 2 channels on that console's HDMI chain. The host output is always reported as a stereo television here. The console also returns `0x80268006` before `sceAudioOut2Initialize`, `0x80268001` for flags above 1 and `0x8026800C` for a null pointer, which are not modelled. ASTRO BOT and ASTRO's PLAYROOM reportedly stop on an assertion that depends on these values (#4317), but which field matters has not been isolated +- [sceAudioOutDeviceIdOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - the native wrapper consumes a 64-bit device ID, port type, sample count, frequency and format from argument slots 1, 2, 4, 5 and 6. Slot 3 is discarded, with the common open API index position preserved. Device enumeration and mapping to host outputs remain unimplemented; the throwing body is unchanged. +- [sceAudioOut2PortGetState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - the port state reports the channel count of the port's data format, output main, volume 127 and a zero reroute counter and flags. Taking the channel count from the data format follows [SharpEmu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs), another emulator. A payload process on a PS5 (firmware 13.60), which is not the foreground application, read output 0, volume -1 and reroute counter 5 for its main ports, and 6 channels for an 8-channel port on a 5.1 system, so a title's values are unverified, including the output of a pad port, the volume after `sceAudioOut2PortSetAttributes` and the reroute counter on a device change. +- [sceAudioOut2GetSpeakerInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - The speaker info reports type 0 with available bits `0x3`, the front left speaker at +30 degrees and the front right one at -30, and -360 for the azimuth and elevation of every other slot. On that console the type followed the HDMI device type and the available bits its channels: type 0 with the device set to TV, 1 to AV amplifier and 2 to sound bar, which [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs) names television, AV receiver and sound bar (3 is headphones there), and available bits `0x3` for 2 channels and `0x3F` for 5.1 (centre at 0, surround pair at +110 and -110, -360 for the LFE). +- [sceAudioOut2GetSpeakerInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - The front pair and the empty slots read the same in every setting; 7.1 on the AV amplifier reported 2 channels on that console's HDMI chain. The host output is always reported as a stereo television here. The console also refuses flag 1 below a per-process libkernel value whose source is unidentified; BunnyFl measured the boundary between `0x14FFFFF` and `0x1500000`, and it is not modelled here. ASTRO BOT and ASTRO's PLAYROOM reportedly stop on an assertion that depends on these values (#4317), but which field matters has not been isolated +- [sceAudioOutInit and sceAudioOut2Initialize repeats](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - a repeated `sceAudioOutInit` returns 0 here, while the firmware returns `0x8026000e` on the second call; whether a repeated `sceAudioOut2Initialize` returns 0 is unmeasured. That `sceAudioOutInit` initializes AudioOut2, and that `sceAudioOut2GetSpeakerInfo` then succeeds, follow the firmware and are covered by tests. - [sceAudioOut2UserCreateEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutOpenEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutSetSystemDebugState](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature @@ -43,17 +52,12 @@ - [sceAudioOutExGetLastOutputTime](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutGetInfoOpenNum](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutGetInfo](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature -- [sceAudioOutMasteringInit](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature -- [sceAudioOutMasteringSetParam](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature -- [sceAudioOutMasteringGetState](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature -- [sceAudioOutMasteringTerm](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutSetPadSpkVolume](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutSetPadJackVolume](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutSetUsbVolume](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutSetCaesarVolume](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutSetHeadphoneOutMode](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutGetFocusEnablePid](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature -- [sceAudioOutSetMainOutput](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutSetVolumeDown](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutSetChatBalance](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOutSetRecMode](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature @@ -91,18 +95,16 @@ - [sceAudioOut2MbusSetSparkParam](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2AllAudioMainToHeadphoneForDiag](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2LoPortGetStateEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature -- [sceAudioOut2LoTerminate](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) throws. It takes no arguments and returns an int, and stops the low-latency service ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs#L497-L498)), but what it does to open contexts and ports and what it returns when the service is not started are unknown, and its counterpart `sceAudioOut2Terminate` is unimplemented. - [sceAudioOut2ReportRegisterHandler](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2ReportUnregisterHandler](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2GetTvCorrectionInfo](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2GetIpcHandle](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2GetSystemStateEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature -- [sceAudioOut2MasteringInitEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2SetDebugHeadphoneMode](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2DebugStateCtrl](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2ContextBedWrite](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2PortCreateEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature -- [sceAudioOut2InitializeEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature +- [sceAudioOut2InitializeEx](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - the PS5 12.00 module (0x2a640) stores its 32-bit argument and runs `sceAudioOut2Initialize`; the argument's meaning is unknown and it is ignored - [sceAudioOut2Terminate](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2PortRegister](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2PortUnregister](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature @@ -111,9 +113,11 @@ ### Functional -- [sceAudioOut2EnableChat](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) takes no arguments and returns 0, as declared by [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs#L460-L464) ("Allows chat audio alongside the application's own"). The host mixes no chat audio and AudioOut2 has no chat port, so the call only records that chat audio is allowed. Whether the console fails it without a chat session, when repeated or before `sceAudioOut2Initialize` is unknown. [sceAudioOut2DisableChat](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) is the counterpart, imported by PPSA18216: it takes no arguments, returns 0 and records that chat audio is no longer allowed; its signature is not confirmed by a console measurement, and whether the console fails it without a chat session or when repeated is unknown. -- [sceAudioOut2Set3DLatency](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) and [sceAudioOut2MasteringInit, sceAudioOut2MasteringTerm and sceAudioOut2MasteringSetParam](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) have no effect: there is no 3D rendering or mastering stage. SetParam accepts the parameter set, and SetSystemDebugState records the debug state id, without anything to apply them to. +- [sceAudioOutMasteringInit, sceAudioOutMasteringTerm and sceAudioOutMasteringSetParam](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - non-zero flags, a null pointer and a params id other than 1 throw; a repeated Init, a Term without Init and a SetParam or GetState before Init are unknown. There is no mastering stage, so Init and Term keep no state and SetParam reads only the id. The signatures and the default params id 1 follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut.cs#L312-L344), not a console measurement. SharpProspero documents flag bit 0 as `SwapEqCompressor`, and shadPS4's PS4 stub [rejects any non-zero Init flags](https://github.com/shadps4-emu/shadPS4/blob/8d50471b7dc141dd700b51276b8edd1f293bce26/src/core/libraries/audio/audioout.cpp#L956-L962) with 0x80260201 +- [sceAudioOutMasteringGetState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - the states id 1 and the meter offsets 32, 128, 240, 272 and 304 in a 336-byte struct follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Audio/AudioOutMastering.cs#L97-L110), not a console measurement. With no mastering stage it zeroes the compressor input RMS and gain, and the limiter input, output and gain peaks, and leaves the other bytes, including the descriptors the AudioOut2 call writes, untouched; zero is not known to be what the console reports for an idle chain. A null pointer and a states id other than 1 throw; the console error codes are unknown +- [sceAudioOut2EnableChat and sceAudioOut2DisableChat](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) - both PS5 exports take no arguments, return 0x80268006 before AudioOut2 initialization, and invoke the system chat service with selector 3. A negative service result maps to 0x80268000, otherwise they return zero. The host only records a local enabled flag, without initialization validation or a chat service; repeated-call and no-session behavior therefore still depends on the missing service. +- [sceAudioOut2Set3DLatency](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - the two 32-bit arguments and selector validation match the system library: selector 0 accepts outputs 0 through 2; selector 255 accepts outputs 1 and 2. No third latency argument exists. The host has no system 3D rendering stage, so the call still does not apply latency and does not reproduce the system initialization and output-availability checks. - [AudioOut2 contexts](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp) (libSceAudioOut) with an SDL device and no `sceAudioOut2ContextAdvance` calls read a grain's port buffers at the next push, at least 100 us after the grain was pushed, and play the mix behind a 40 ms cushion that the queue level leaves out. When the hardware reads the buffers is not known. The timing follows a title whose mixer fills a buffer after pushing it, reuses it two pushes later, and may still be writing it when the next push follows within microseconds. The last grain pushed before a title stops pushing is never read or played. - [sceAudioOut2ContextGetQueueLevel](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp) (libSceAudioOut) with an SDL device counts the grains that `sceAudioOut2ContextAdvance` completed and no push has consumed as queued, together with the pushed grains beyond the 40 ms cushion, up to `queue_depth`. Whether the console counts them is unknown. Pushes that fill the cushion leave the level at 0, so a title that renders as many grains as the level leaves room for and pushes them on another thread rendered a full queue again after each of those pushes: PPSA23226's Wwise sink passes the available count to the sound engine as the number of frames to render, advances once per frame and pushes on a separate thread, and exceeded 16 completed grains. Counted, the completed grains keep such a title at most `queue_depth` grains ahead of its pushes. - [sceAudioOut2LoInit, sceAudioOut2LoContext* and sceAudioOut2LoPort* calls](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Low.cpp) (libSceAudioOut) have no low-latency path: each forwards to its `sceAudioOut2` counterpart, so grain and queue depth are the context's `num_grains` and `queue_depth`, and playback keeps the 40 ms cushion. [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs#L489-L545) only says each call takes the same handles and parameter blocks as its counterpart and drives the low-latency clock with a shallow queue and a small grain ([docs](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/docs/audio.md#L662-L668)). Unverified: the console's grain and depth limits, its error for a handle of the other service (accepted here) and what `sceAudioOut2LoInit` returns when repeated (0 here). -- [sceAudioOutSysGetHdmiMonitorInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) reports an HDMI sink without a monitor name or audio descriptors: it zeroes the 0x180 bytes and returns 0, since audio reaches the host through SDL as PCM and there is no EDID to describe. The arguments `(type, info, size)`, the required size 0x180 and the fields titles read (monitor name at +0x0B, 13 bytes; descriptor count as a 32-bit value at +0x94; 8-byte descriptors `[coding type, channels, rate mask, 0, extra, 0, 0, 0]` from +0x98) come from [PS5-Audio-Passthrough-Research](https://github.com/sainsaji/PS5-Audio-Passthrough-Research/blob/c3be9c2afcb8f7838784ead0fe1c12a2ceea965a/docs/FINDINGS.md) (firmware 12.70, one monitor). What a console without an HDMI audio sink returns, an error or no descriptors, is unknown. The other bytes, among them the header at +0x90 (`01 09 4f 00` on that monitor), have no known meaning and are written as 0. No LPCM descriptor is reported for the host device, as the rate mask and extra byte of one are not known. A null buffer returns `0x80260004` like the other AudioOut calls; the console's code is not measured. Monitor type 0x10, which the console also accepts, other types and sizes other than 0x180 throw: their results and error codes are unknown. +- [sceAudioOutSysGetHdmiMonitorInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) - native validation requires a non-null buffer and exactly 384 bytes, then accepts types 1 and 16. Mapped pointer/size/type errors are 0x80260004/06/0C. The host still zeroes type 1 output and throws for type 16: native monitor descriptors and system audio settings are unavailable. Driver initialization failures and service errors are not represented. diff --git a/docs/dev/debt/libSceAudioPropagation.md b/docs/dev/debt/libSceAudioPropagation.md index 30802ce4c7..66ebc2c6f3 100644 --- a/docs/dev/debt/libSceAudioPropagation.md +++ b/docs/dev/debt/libSceAudioPropagation.md @@ -2,10 +2,10 @@ ### Silent stubs -- [libSceAudioPropagation](../../../core/libs/prx/libSceAudioPropagation/Export.cpp) implements only the no-acoustic-propagation path: the system keeps a 16-byte header in the title's CPU memory (no GPU memory), rooms, portals, sources and materials are tracked handles, attributes are validated and have no effect, no rays are requested and sources report no audio paths. Ray results, path calculation and audio paths are accepted only when empty and throw otherwise; `sceAudioPropagationSourceRender` preserves each destination mix because there are no path contributions, following the additive-output contract in [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioPropagation.cs); console behaviour has not been measured. It adds no dry signal. Objects still alive when their system is destroyed become unusable, and their later destroy or unregister is a no-op: PPSA21567 releases its reference-counted room, portal and source owners just before `sceAudioPropagationSystemDestroy`, so the order for every object is not proven. Occlusion, reflections and reverb from level geometry are absent; in-game behaviour remains unverified. +- [libSceAudioPropagation](../../../core/libs/prx/libSceAudioPropagation/Export.cpp) lacks acoustic propagation: attributes have no effect, sources expose 36 initially empty, invalid path slots and no rays, rendering preserves the destination mix without adding path contributions, and allocated CPU work memory is mostly unused. Ray results and audio paths accept empty lists and throw otherwise; calculation returns initial native validation errors and throws for unsupported propagation. Room, portal, source and material handles are tracked, but destruction after their system is destroyed silently succeeds without native lifetime semantics being confirmed. Occlusion, reflections and reverb are absent. ### Unknown function info -- [sceAudioPropagationSourceSetAudioPath](../../../core/libs/prx/libSceAudioPropagation/Export.cpp) (libSceAudioPropagation, NID `tKSmk2JsMAA`) - `(uint64_t path, const void* data, float gain)` follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioPropagation.cs) and SAROS's call, which passes a handle from `sceAudioPropagationSourceGetAudioPath`, path data and gain 1.0. Path updates are unsupported and every call throws without reading or writing caller memory. The no-propagation model yields no path handles; path allocation, ownership, rendering and console error codes remain unknown. -- [libSceAudioPropagation](../../../core/libs/prx/libSceAudioPropagation/Export.cpp) (libSceAudioPropagation) - signatures and struct descriptors recovered from PPSA21567's calls (#481), not from documentation. The system options layout is unknown (only checked for non-null); the fourth argument of `sceAudioPropagationSourceCalculateAudioPaths` is unknown; `sceAudioPropagationSourceGetAudioPath` is assumed to output an 8-byte path handle. The meaning of the `RenderInfo` word at +0x28 is unknown (PPSA21567 and PPSA21564 pass 2); only 2 is accepted. Both titles pass one `RenderInfo` per call: a count of 0 throws, and larger counts are handled element by element without proof. No error code is known, so invalid input throws instead of returning one -- [libSceAudioPropagation firmware exports](../../../core/libs/prx/libSceAudioPropagation/Export.cpp) (libSceAudioPropagation) - `sceAudioPropagationPathGetNumPoints`, `sceAudioPropagationReportApi`, `sceAudioPropagationResetAttributes`, `sceAudioPropagationSourceQueryInfo`, `sceAudioPropagationSystemLock`, `sceAudioPropagationSystemQueryInfo`, `sceAudioPropagationAudioPathInit`, `sceAudioPropagationAudioPathPointInit`, `sceAudioPropagationMaterialInit`, `sceAudioPropagationPortalSettingsInit`, `sceAudioPropagationRayInit`, `sceAudioPropagationSourceRenderInfoInit`, `sceAudioPropagationSourceSetAudioPathsParamInit`, `sceAudioPropagationSystem`, `sceAudioPropagationSystemMemoryInit`, `sceAudioPropagationSystemOptionInit`, `sceAudioPropagationReportRegisterHandler`, `sceAudioPropagationReportUnregisterHandler`, `sceAudioPropagationSystemEnumHandles`, `sceAudioPropagationSystemUnlock` are declared without parameters from the firmware export database (aerolib.csv); their guest signatures are unverified on hardware and every call throws +- [sceAudioPropagationSourceSetAudioPath](../../../core/libs/prx/libSceAudioPropagation/Export.cpp): native evidence establishes a 64-bit path handle, a read-only 224-byte path description and a float gain. The four 48-byte points contain a room handle, gain and position; trailing eight-byte fields remain opaque. Updates copy the description and validate room ownership. Each source owns 36 initially invalid, empty slots, following public SharpProspero MaxPaths and SAROS's immediate lookup of slot 0. Allocation before calculation, the count of 36 and update validation precedence are inferred, not measured on a console. Native lookup error codes and their handle/null/index precedence are retained. Source/system destruction invalidates owned slots. Active nonempty path rendering still throws. +- [libSceAudioPropagation](../../../core/libs/prx/libSceAudioPropagation/Export.cpp): system options use a recovered 56-byte record; the field at byte 24 and final eight bytes remain semantically unresolved. CPU memory requirements and option limits are recovered; GPU memory queries throw because their platform-specific runtime condition is unresolved. Descriptor validation, ray ownership and path generation are unimplemented. Native rendering accepts multiple mutable records; the host accepts public formats 0/1/2/4/5 (4/9/16/8/8 float channels), preserves the output mix without path contributions and clears the returned channel count; non-format-2 behavior remains unverified on a console. Frame capacity and source-count limits follow the recovered options. Other formats throw; native validation precedence remains unimplemented. +- [Initialization exports](../../../core/libs/prx/libSceAudioPropagation/Export.cpp) ending in `Init`, plus `sceAudioPropagationSystem`, are not exported: the SDK declares the helpers `static inline` and none is in the SDK stub library or the supplied native module. Query, locking, enumeration and report callback signatures are recovered; diagnostic layouts are recovered, but source attributes 2/3 and the constant-one fields still lack semantic names. The final system word and the fourth position/direction words have no confirmed semantics. diff --git a/docs/dev/debt/libSceAudiodec.md b/docs/dev/debt/libSceAudiodec.md index 15c8c1cbe1..ac9b492ea4 100644 --- a/docs/dev/debt/libSceAudiodec.md +++ b/docs/dev/debt/libSceAudiodec.md @@ -2,6 +2,8 @@ ### Unknown function info +- [M4AAC extended parameters](../../../core/libs/prx/libSceAudiodec/Export.cpp): 28-byte and 32-byte parameter variants add fields at offsets 24 and 28. Their accepted ranges are 0..1 and 0..2; nonzero modes throw until the corresponding decoder configuration is implemented. Zero extensions use the base decoding configuration. + - [sceAudiodecDecode](../../../core/libs/prx/libSceAudiodec/src/Codecs.cpp) (libSceAudiodec) - a raw M4AAC stream carries no channel configuration, so the decoder tries the mono/stereo configuration and then configurations 3 to 7 within `ui_max_channels` until one decodes the first access unit, and keeps that one. No reference documents how the library picks the layout (shadPS4 maps the channel count straight to the configuration; SharpProspero only describes the maximum). With the stereo configuration a mono raw stream decodes as 2 channels, so 2 is reported, not 1 - [sceAudiodecDecode](../../../core/libs/prx/libSceAudiodec/src/Codecs.cpp) (libSceAudiodec) - the HE-AAC handling follows one console measurement of an HE-AAC v2 ADTS stream: SBR output at 48 kHz in one channel with `ui_enable_heaac` 1, the 24 kHz mono core with 0. Not measured: whether the console, like FFmpeg, decodes a stream without SBR data in its first access unit as AAC-LC with `ui_enable_heaac` 1, and what it reports in `ui_sampling_freq` and `ui_heaac` with `ui_enable_heaac` 0 (here the rate of the returned PCM and 0) diff --git a/docs/dev/debt/libSceAutoMounterClient.md b/docs/dev/debt/libSceAutoMounterClient.md index b7200004e3..2996a7b44b 100644 --- a/docs/dev/debt/libSceAutoMounterClient.md +++ b/docs/dev/debt/libSceAutoMounterClient.md @@ -3,3 +3,4 @@ ### Unknown function info - [sceAutoMounterClientRegisterCallback, sceAutoMounterClientTerm and sceAutoMounterClientUnregisterCallback](../../../core/libs/prx/libSceAutoMounterClient/Export.cpp) (libSceAutoMounterClient) - their names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAutoMounterClient.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceAutoMounterClientGetUsbDeviceInfo](../../../core/libs/prx/libSceAutoMounterClient/Export.cpp): the 22 metadata bytes at offset 128 remain opaque. The 944-byte output layout, 128-byte device name and three 256-byte arrays with 64-bit lengths are recovered. diff --git a/docs/dev/debt/libSceAvPlayer.md b/docs/dev/debt/libSceAvPlayer.md index 5a2f1941ed..b248df63b3 100644 --- a/docs/dev/debt/libSceAvPlayer.md +++ b/docs/dev/debt/libSceAvPlayer.md @@ -2,15 +2,14 @@ ### Silent stubs -- [libSceAvPlayer](../../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. -- [sceAvPlayerSetTrickSpeed](../../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. +- [libSceAvPlayer](../../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement; after a source is added it returns `OPERATION_FAILED`, as [prosper's reading of the module](https://github.com/mattias800/prosper/blob/8dcb5dfdb9997ede7c76783995010d6abd1222f8/prosper/src/hle/video/avplayer.cpp#L960-L972) describes. +- [sceAvPlayerSetTrickSpeed](../../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. With a source, a zero speed returns `INVALID_PARAMS` before the `OPERATION_FAILED` of a stopped, finished or failed player, the order AnyPS5 had before; that order is unmeasured. In [prosper's reading of the module](https://github.com/mattias800/prosper/blob/8dcb5dfdb9997ede7c76783995010d6abd1222f8/prosper/src/hle/video/avplayer.cpp#L929-L959), only the missing source is checked first. A zero speed is refused with `NOT_SUPPORTED`, with every other speed from -399 to 399 except 100, and no player state is checked. ### Unknown function info - [AvPlayer loop warnings](../../../core/libs/prx/libSceAvPlayer/src/Source.cpp) are queued when the first audio or video frame of a loop is delivered, following [KytyPS5's playback boundary tracking](https://github.com/KytyPS5/KytyPS5/blob/a6f4e8e7f456b8697d3102823d3f16400c3e121e/src/libs/avPlayer.cpp#L1038-L1043). The console's exact callback timing is unverified. Decode-ahead does not announce a loop while the caller is consuming the previous pass. - [sceAvPlayerAddSource](../../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) - requests up to 4 additional decode-ahead video framebuffers from the memory replacement beyond `num_output_video_framebuffers` when memory is available; the console's internal buffering behaviour is unknown -- [sceAvPlayerStartEx](../../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart` - [sceAvPlayerInit / sceAvPlayerInitEx](../../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap - [sceAvPlayerInitEx](../../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - the PS5 init data layout (`this_size` 0x230: auto start at 0x74, nine 0x30-byte thread parameter blocks from 0x78, output video frame buffer count at 0x228) follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/avPlayer.cpp), [prosper](https://github.com/mattias800/prosper/blob/main/prosper/src/hle/video/avplayer.cpp), [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Media/AvPlayer.cs) and [sharpemu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs), which agree on it. PPSA25063 fills the same fields, writes auto start 0 and starts playback with `sceAvPlayerStart`; a title that sets auto start has not been seen. A smaller `this_size` is read with the PS4 offsets. The thread parameters are ignored - [AvPlayer auto start](../../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - the auto start that `sceAvPlayerAddSource` queues for a player with auto start or without an event callback starts the player only while it is still ready: a title that starts the player itself before the auto start runs keeps its playback and the frames it holds. Not confirmed on a title. diff --git a/docs/dev/debt/libSceBgft.md b/docs/dev/debt/libSceBgft.md index 8d8e3ccbe7..95b8d7ac06 100644 --- a/docs/dev/debt/libSceBgft.md +++ b/docs/dev/debt/libSceBgft.md @@ -2,4 +2,16 @@ ### Unknown function info -- [sceBgftServiceDownloadPauseTaskAll, sceBgftServiceDownloadResumeTaskAll, sceBgftServiceDownloadStartTaskAll, sceBgftServiceDownloadStopTaskAll, sceBgftServiceDownloadFindActivePatchTask, sceBgftServiceDownloadFindActivePupTask, sceBgftServiceIntDownloadFindActiveGameAndGameAcTask, sceBgftServiceIntDownloadFindActiveTask, sceBgftServiceIntDownloadGetGameAndGameAcProgress, sceBgftServiceIntDownloadGetPatchProgress, sceBgftServiceIntDownloadGetPlayGoProgress, sceBgftServiceIntDownloadGetProgress, sceBgftServiceIntDownloadGetTaskInfo, sceBgftServiceIntDownloadPauseTask, sceBgftServiceIntDownloadRegisterTaskByStorageEx, sceBgftServiceIntDownloadResumeTask, sceBgftServiceIntDownloadStopTask, sceBgftServiceIntDownloadStartTask, sceBgftServiceIntDownloadUnregisterTask and sceBgftServiceIntInit](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. The eight task-control declarations not listed here follow [SharpProspero's DownloadService bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Platform/DownloadService.cs) +- [sceBgftServiceDownloadGetProgress](../../../core/libs/prx/libSceBgft/Export.cpp), `sceBgftServiceIntDownloadGetProgress`, `sceBgftServiceIntDownloadGetGameAndGameAcProgress` and `sceBgftServiceIntDownloadGetPlayGoProgress`: signatures and the shared 88-byte output are confirmed. The library forwards the output through IPC without accessing individual fields; their types remain unresolved. +- [sceBgftServiceIntDownloadRegisterTaskByStorageEx](../../../core/libs/prx/libSceBgft/Export.cpp): confirmed parameter pointer and 32-bit task ID output. The input contains 32-bit scalars at offsets 0, 4, 56 and 104, strings at offsets 8, 16, 32, 40, 48, 64, 72, 80 and 88, and a 64-bit scalar at offset 96. The typed layout preserves the unused eight bytes at offset 24; scalar meanings and several string roles remain unresolved. +- [sceBgftServiceIntDownloadGetPatchProgress](../../../core/libs/prx/libSceBgft/Export.cpp) and `sceBgftServiceIntDownloadGetTaskInfo`: both throw through `NotImplemented_nid_no_patch`; neither export nor a caller was found in the supplied decompiled library corpus. The `(const char* contentId, BgftProgress*)` and `(int32_t taskId, BgftTaskInfo*)` signatures follow the OpenOrbis headers, whose other Bgft prototypes match the confirmed ones here; they are not measured on a console. The task info layout is unknown. + +- [sceBgftServiceIntDebugPlayGoClearSetFreeZone](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - It is in the intersection of the export table of the console's libSceBgft (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. It is declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceBgftServiceIntDebugPlayGoIsSetFreeZone](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - It is in the intersection of the export table of the console's libSceBgft (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. It is declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceBgftServiceIntDownloadCheckPatchUpdateState and sceBgftServiceIntDownloadClearStats](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - The list is the intersection of the export table of the console's libSceBgft (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceBgftServiceIntDownloadGetApplicationDataSize](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - It is in the intersection of the export table of the console's libSceBgft (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. It is declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceBgftServiceIntDownloadGetPatchGoProgress and sceBgftServiceIntDownloadHideTask](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - The list is the intersection of the export table of the console's libSceBgft (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceBgftServiceIntDownloadSetResumeState and sceBgftServiceIntDownloadSetStartState](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - The list is the intersection of the export table of the console's libSceBgft (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceBgftServiceIntPlayGoCheckDiscIniChunksInstall and sceBgftServiceIntPlayGoStartDataDiscInstall](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - The list is the intersection of the export table of the console's libSceBgft (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceBgftServiceIntSetTaskVisible, sceBgftServiceIntUploadGetAvailableTaskCount, sceBgftServiceIntUploadGetTaskInfo and sceBgftServiceIntUploadHideTask](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - The list is the intersection of the export table of the console's libSceBgft (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceBgftServiceIntUploadSetStartState and sceBgftServiceIntUploadSetUploadError](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - The list is the intersection of the export table of the console's libSceBgft (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. diff --git a/docs/dev/debt/libSceBluetoothHid.md b/docs/dev/debt/libSceBluetoothHid.md index b8ec7bf2ab..faf41e09da 100644 --- a/docs/dev/debt/libSceBluetoothHid.md +++ b/docs/dev/debt/libSceBluetoothHid.md @@ -2,9 +2,9 @@ ### Silent stubs -- [libSceBluetoothHid](../../../core/libs/prx/libSceBluetoothHid/Export.cpp) implements only the no-device path: initialization and callback and device registration succeed, the callback is never called because no device connects, and calls that need a connected device throw +- [libSceBluetoothHid](../../../core/libs/prx/libSceBluetoothHid/Export.cpp): the host still has no device backend or event worker. Registration retains only lifecycle state and never delivers the recovered 104-byte callback events. Custom worker scheduling throws; device ioctl failures and resource exhaustion are not modeled. ### Unknown function info -- [libSceBluetoothHid](../../../core/libs/prx/libSceBluetoothHid/Export.cpp) (libSceBluetoothHid) - signatures taken from PPSA01317: `sceBluetoothHidInit()` and `sceBluetoothHidUnregisterCallback()` without arguments, `sceBluetoothHidRegisterCallback(callback, 0, param)` with a 16-byte param, and `sceBluetoothHidRegisterDevice` / `sceBluetoothHidUnregisterDevice(vendorId, productId)`; the report and device name exports are declared without parameters -- [sceBluetoothHidDebugGetVersion, sceBluetoothHidDisconnectDevice, sceBluetoothHidGetDeviceInfo, sceBluetoothHidGetFeatureReport, sceBluetoothHidGetReportDescriptor, sceBluetoothHidInterruptOutput, sceBluetoothHidParamInitialize, sceBluetoothHidSetFeatureReport, sceBluetoothHidThreadParamInitialize](../../../core/libs/prx/libSceBluetoothHid/Export.cpp) (libSceBluetoothHid) - throwing stubs declared without parameters from the firmware export database (aerolib.csv); their guest signatures are unverified on hardware +- [sceBluetoothHidGetDeviceName and sceBluetoothHidGetDeviceInfo](../../../core/libs/prx/libSceBluetoothHid/Export.cpp): output pointers are forwarded to device ioctls without accessing the buffers. Their output sizes and layouts remain unresolved. Report APIs use 64-bit device handles; device-info and disconnect APIs use 32-bit device IDs. +- [sceBluetoothHidDebugGetVersion](../../../core/libs/prx/libSceBluetoothHid/Export.cpp): returns a constant without reading arguments, so the supplied implementation does not establish whether the public prototype has unused parameters. diff --git a/docs/dev/debt/libSceCamera.md b/docs/dev/debt/libSceCamera.md new file mode 100644 index 0000000000..955b1e157c --- /dev/null +++ b/docs/dev/debt/libSceCamera.md @@ -0,0 +1,118 @@ +# libSceCamera technical debt + +### Unknown function info + +- [sceCameraOpen, sceCameraClose, sceCameraStart, sceCameraStop, sceCameraGetConfig, sceCameraSetConfig, sceCameraSetAttribute, sceCameraGetDeviceInfo, sceCameraGetFrameData, sceCameraIsValidFrameData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera) - imported by PS4 titles and added without an implementation, declared without parameters. What the console returns from them while no camera is attached is not known, so every call throws. + +### Functional + +- [sceCameraIsAttached](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera) - no camera is ever attached: index 0 returns 0 (not attached) and any other index returns `SCE_CAMERA_ERROR_PARAM` (0x802E0000), as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/9a8ae4417bbfc79b1a43eb8a2eb0bb602148261e/src/core/libraries/camera/camera.cpp#L187-L200) does without a configured camera, with the error value from its [camera_error.h](https://github.com/shadps4-emu/shadPS4/blob/9a8ae4417bbfc79b1a43eb8a2eb0bb602148261e/src/core/libraries/camera/camera_error.h). Not measured on a console. Closes when: a host camera is exposed to guests. + +### Additional PS5 3.20 imports + +- [sceCameraStopByHandle](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `+X1Kgnn3bzg`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetDeviceIDWithoutOpen](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `-H3UwGQvNZI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetAttribute](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `0btIPD5hg5A`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraInitializeRegistryCalibData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `0wnf2a60FqI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetContrast](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `1-IJHxzRJGw`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2Finalize](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `186Jz869gsc`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetConnectedDeviceAndDriverMode](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `2dBOiluwiMw`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2IsAttached](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `2v21-m4gljU`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetSharpness](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `2zCd8XDOe-Y`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetTrackerMode](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `3VJOpzKoIeM`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetContrast](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `3i5MEzrC1pg`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetCalibDataForEve](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `4IGO2LPD3h8`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetCalibDataFromDevice](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `5Oie5RArfWs`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetContrast](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `66IVWcdNHyI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraIsConnectedForSystem](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `68S3GKWhY6o`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetExposureGain](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `8MEjogxPrv0`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetRegister](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `8MjO05qk5hA`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetVideoSyncInternal](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `8WtmqmE4edw`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetConfigInternal](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `9+SNhbctk64`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetAttributeInternal](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `96F7zp1Xo+k`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetWhiteBalance](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `AfIDd+2ycTs`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetMmapConnectedCount](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `B260o9pSzM8`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetSaturation](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `DAycoVmY3Mw`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetDeviceConfig](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `DSOLCrc3Kh8`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraAudioOpen](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `DhqqFiBU+6g`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetAutoExposureGain](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `FZkD-E6MNTQ`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetCalibDataFromDeviceForEve](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `H-7tWq8gfKs`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetWhiteBalance](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `HX5524E5tMY`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetVrCaptureInfo](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `IAz2HgZQWzE`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetCalibDataForEve](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `Jkz3xxMeyIM`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetAttribute](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `JtD7ZG45rdw`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetLensCorrection](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `K7W7H4ZRwbc`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetCalibData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `LEMk5cTHKEA`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetProcessFocusByHandle](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `NVITuK83Z7o`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetConfig](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `O5x-G9Rqwx4`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetExposureGain](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `ObIste7hqdk`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetSharpness](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `P-7MVfzvpsM`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2Initialize](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `PFRl8SmSW+4`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetHue](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `QI8GVJUy2ZY`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraAccGetData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `QhjrPkRPUZQ`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetAttribute](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `Qk6kEJ0RJlM`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetCalibrationData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `RHYJ7GKOSMg`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetSaturation](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `RTDOsWWqdME`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetHue](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `T8jy0JWa210`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2Stop](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `TZWR3p6XxXk`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetSaturation](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `TzkL-nUWfaQ`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetAutoWhiteBalance](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `U8oNsRef+jI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraAudioClose](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `UFonL7xopFM`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetProductInfo](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `ULxbwqiYYuU`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetGamma](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `WVox2rwGuSc`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetLensCorrection](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `XqYRHc4aw3w`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraChangeAppModuleState](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `Y0pCDajzkVQ`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetFrameData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `YlFCMjbyzh8`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetContrast](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `a5xFueMZIMs`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetSaturation](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `bSKEi2PzzXI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetSharpness](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `c6Fp9M1EXXc`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetExposureGain](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `c9XZGDF1OcM`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraStartByHandle](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `cLxF1QtHch0`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2Open](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `dmLUJh3bVTc`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2Start](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `eGkcUia48ts`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetLensCorrectionInternal](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `eHa3vhGu2rQ`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraOpenByModuleId](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `eTywOSWsEiI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetCalibrationData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `emZBXv7cuAI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraAudioGetData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `fkZE7Hup2ro`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetHue](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `gB+OkFvkSXE`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetGamma](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `gJqqsinextg`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetCalibData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `hHA1frlMxYE`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraWaitForEveReady](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `hPVradh5wpo`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetRegistryInfo](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `hawKak+Auw4`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraAudioGetData2](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `hftC5A1C8OQ`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetAppModuleFocus](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `j5isFVIlZLk`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetDefectivePixelCancellation](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `jMv40y2A23g`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetDeviceID](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `jTJCdyv9GLU`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetForceActivate](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `jeTpU0MqKU0`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetWhiteBalance](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `k3zPIcgFNv0`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetFieldOfView](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `kVWBITsSB3o`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2IsValidFrameData](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `lQ7rWWlOArI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetProcessFocus](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `lS0tM6n+Q5E`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetGamma](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `lhEIsHzB8r4`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetConfig](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `mLRzKdIDwiU`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetWhiteBalance](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `n+R7PGJa6MI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetDeviceConfigWithoutHandle](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `n+rFeP1XXyM`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraDeviceOpen](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `nBH6i2s4Glc`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetUacModeInternal](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `nnR7KAIDPv8`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetAutoExposureGain](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `oEi6vM-3E2c`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetRegister](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `olojYZKYiYs`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetGamma](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `pfiqU2f6PQY`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraRemoveAppModuleFocus](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `py8p6kZcHmA`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetAutoWhiteBalance](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `qTPRMh4eY60`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetDefectivePixelCancellation](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `tslCukqFE+E`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2Close](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `uBRW3tEoWWM`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetVideoSync](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `ubD+FitjqhA`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraWaitForLukeReady](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `v9XTZHXoJyQ`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetDefectivePixelCancellationInternal](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `vER3cIMBHqI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2GetSharpness](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `vPi3gSzw79M`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetAutoWhiteBalance](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `vS20kteXHdw`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetDebugStop](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `vejouEusC7g`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCamera2SetAutoExposureGain](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `vx9MQYicBVQ`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraIsConfigChangeDone](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `wQfd7kfRZvo`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetExposureGain](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `wgBMXJJA6K4`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetVideoSync](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `wpeyFwJ+UEI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraAudioReset](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `wyU98EXAYxU`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetAutoExposureGain](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `yfSdswDaElo`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraSetAutoWhiteBalance](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `zIKL4kZleuc`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraGetHue](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `zrIUDKZx0iE`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` +- [sceCameraCloseByHandle](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera, NID `ztqH5qNTpTk`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCamera.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` diff --git a/docs/dev/debt/libSceCdlgG2P.md b/docs/dev/debt/libSceCdlgG2P.md index 2549e5ceee..ad18657b3d 100644 --- a/docs/dev/debt/libSceCdlgG2P.md +++ b/docs/dev/debt/libSceCdlgG2P.md @@ -2,4 +2,4 @@ ### Silent stubs -- [libSceCdlgG2P](../../../core/libs/prx/libSceCdlgG2P/Export.cpp) - the G2P dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled. The signatures (`Open` and `GetResult` take a pointer, the others none) and the leading `int32` result are assumed from the other common dialogs, not verified for G2P; PPSA01289 imports it +- [libSceCdlgG2P](../../../core/libs/prx/libSceCdlgG2P/Export.cpp) - the G2P dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled. The 128-byte open parameters, common-header validation and result field at byte 8 are recovered. Native service state transitions remain unimplemented; PPSA01289 imports it diff --git a/docs/dev/debt/libSceCdlgPlayerReview.md b/docs/dev/debt/libSceCdlgPlayerReview.md index 98ff1e48e6..0e97a3d72d 100644 --- a/docs/dev/debt/libSceCdlgPlayerReview.md +++ b/docs/dev/debt/libSceCdlgPlayerReview.md @@ -2,8 +2,7 @@ ### Unknown function info -- [scePlayerReviewDialogOpen, scePlayerReviewDialogGetResult](../../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) - the PS5-only dialog has no public reference: the parameter is not read and the result is assumed to start with the 32-bit common dialog result, as `sceWebBrowserDialogGetResult` assumes -- [scePlayerReviewDialogClose](../../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) - `scePlayerReviewDialogClose` moves an initialized dialog straight to `COMMON_DIALOG_STATUS_FINISHED` even when called before Open, as Signin, NetCtlAp and PlayerSelection do; MsgDialog.native, SaveDataDialog.native and ErrorDialog return an error there instead. Unverified against real hardware +- [scePlayerReviewDialogOpen](../../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) uses a recovered 168-byte parameter layout; the 40-byte target record at byte 60 and mode semantics remain unresolved. ### Functional diff --git a/docs/dev/debt/libSceCesCs.md b/docs/dev/debt/libSceCesCs.md index 14cef5e67e..5a561e4a4e 100644 --- a/docs/dev/debt/libSceCesCs.md +++ b/docs/dev/debt/libSceCesCs.md @@ -2,14 +2,24 @@ ### Silent stubs -- [sceCesMbcsUcsContextInit](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) stores the profile pointer in the first 8 bytes of the caller's context; the console's context layout is not known. It returns the invalid-parameter error for a null context. The signature `int sceCesMbcsUcsContextInit(union* context, const union* mbcsUcsPrf)` follows the [Orbital IDA type database](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json). `sceCesRefersUcsProfileCp1251` returns a profile accepted by `sceCesSbcToUtf8` and `sceCesUtf8ToSbc` only, with the Windows-1251 table of Microsoft's [bestfit1251.txt](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit1251.txt), where byte 0x98 (unassigned) maps to U+0098 like the unassigned Cp1252 bytes; the console behavior for that byte is not verified -- [EUC-JP (CP51932) conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs): `sceCesUcsProfileInitEucJpCp51932` marks the caller's sheet with an 8-byte tag and returns it as the profile. `sceCesUtf8StrToMbcsStr`, `sceCesUcs2StrToMbcsStr`, `sceCesMbcsStrToUtf8Str`, `sceCesMbcsStrToUcs2Str`, their `Get*Len` forms and `sceCesEucJpToUcs2` convert ASCII, the JIS X 0201 half-width katakana (`0x8E` + `0xA1`-`0xDF`, U+FF61-U+FF9F) and the two-byte rows `0xA1`-`0xFE` for that profile, and every character for the CP1252 and CP1251 profiles. The two-byte table ([JisX0208.hpp](../../../core/libs/prx/libSceCesCs/JisX0208.hpp), 7336 of 8836 cells) is Microsoft code page 932 read through the standard JIS row/cell to Shift-JIS arithmetic, so it includes NEC row 13 and the NEC-selected IBM rows 89-92 and maps the Microsoft variants (U+FF5E at `0xA1C1`, U+2225, U+FF0D, U+FFE0-U+FFE2) instead of JIS X 0208's; that the console's CP51932 table equals it is assumed from the Microsoft code page, not measured. A character with several cells encodes to the lowest cell. A well-formed but unassigned cell answers `UNASSIGNED_CODE`, a trail byte outside `0xA1`-`0xFE` `INVALID_ENCODE`, a cut pair `SRC_BUFFER_END`; the console's errors for these are not known. Three-byte JIS X 0212 (`0x8F`) characters convert through a second table ([JisX0212.hpp](../../../core/libs/prx/libSceCesCs/JisX0212.hpp), 6067 of 8836 cells) read from the Unicode consortium's [JIS0212.TXT](https://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0212.TXT); that the console's CP51932 table equals it is assumed from that mapping, not measured. A well-formed but unassigned three-byte cell answers `UNASSIGNED_CODE` and a cut triple `SRC_BUFFER_END`. A character with cells in both JIS X 0208 and JIS X 0212 encodes to the two-byte form, and one only in JIS X 0212 to the three-byte form. Signatures follow [orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json). The string forms stop at the source limit or at a zero code unit, which is neither converted nor counted, and write no terminator; `*Len` outputs count code units consumed and produced. A code point outside the BMP answers `UNASSIGNED_CODE`. Unverified on hardware. -- [Shift-JIS (CP932) conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs): `sceCesUcsProfileInitSJis1997Cp932` marks the caller's sheet with an 8-byte tag and returns it as the profile. `sceCesUtf8StrToMbcsStr`, `sceCesUcs2StrToMbcsStr`, `sceCesMbcsStrToUtf8Str`, `sceCesMbcsStrToUcs2Str`, and their `Get*Len` forms convert ASCII, JIS X 0201 half-width katakana (`0xA1`-`0xDF`, U+FF61-U+FF9F), two-byte Shift-JIS sequences (`0x81`-`0x9F` and `0xE0`-`0xFC` lead, `0x40`-`0x7E` and `0x80`-`0xFC` trail) mapped to JIS X 0208 row/cell indices against the CP932 table ([JisX0208.hpp](../../../core/libs/prx/libSceCesCs/JisX0208.hpp)), and the IBM extensions (`0xFA40`-`0xFC4B`, 388 characters) decoded to their corresponding JIS X 0208 code points. The user-defined area (`0xF040`-`0xF9FC`, which CP932 maps to Private Use Area U+E000-U+E757) and unassigned lead/trail combinations answer `UNASSIGNED_CODE`, an invalid lead or trail byte `INVALID_ENCODE`, a cut sequence `SRC_BUFFER_END`. Characters with multiple cells encode to the lowest cell. Signatures follow [orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json). Unverified on hardware. +- [sceCesMbcsUcsContextInit](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) stores the profile pointer in the first 8 bytes of the caller's context; the console's context layout is not known. It returns the invalid-parameter error for a null context and, as a payload process on a PS5 (firmware 13.60) did, for a null profile. The signature `int sceCesMbcsUcsContextInit(union* context, const union* mbcsUcsPrf)` follows the [Orbital IDA type database](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json). `sceCesRefersUcsProfileCp1251` returns a profile accepted by `sceCesSbcToUtf8`, `sceCesUtf8ToSbc` and the MBCS context conversions, with the Windows-1251 table of Microsoft's [bestfit1251.txt](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit1251.txt), except byte 0x98, which is unassigned +- [EUC-JP (CP51932) conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs): `sceCesUcsProfileInitEucJpCp51932` marks the caller's sheet with an 8-byte tag and returns it as the profile. Decoding follows the console (see the `sceCesEucJpToUtf8` entry), with the Microsoft code page 932 table read through the JIS row/cell arithmetic ([JisX0208.hpp](../../../core/libs/prx/libSceCesCs/JisX0208.hpp)); the console does not decode or encode JIS X 0212 either way (U+02D8 answered `0x805C0020`). Encoding takes the lowest cell of a character. Signatures follow [orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) +- [Shift-JIS (CP932) conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs): decoding matches a payload process on a PS5 (firmware 13.60) for every two-byte input through `sceCesMbcsStrToUcs2Str`, in result, lengths and output. `sceCesUcsProfileInitSJis1997Cp932` tags the caller's sheet and returns it as the profile. The string conversions decode ASCII, half-width katakana (`0xA1`-`0xDF`) and two-byte sequences (`0x81`-`0x9F` and `0xE0`-`0xFC` lead, `0x40`-`0x7E` and `0x80`-`0xFC` trail) through the CP932 table ([JisX0208.hpp](../../../core/libs/prx/libSceCesCs/JisX0208.hpp)), including the IBM extensions `0xFA40`-`0xFC4B`. The user-defined area `0xF040`-`0xF9FC` and unassigned cells answer `UNASSIGNED_CODE`, an invalid lead or trail `INVALID_ENCODE`, a cut sequence `SRC_BUFFER_END`. Signatures follow [orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) ### Unknown function info - [Unicode scalar conversions](../../../core/libs/prx/libSceCesCs/Unicode.cpp) (libSceCesCs) - console error precedence, optional length pointers and length units are unverified. Signatures follow the [Orbital type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json); names are in the aerolib table pinned by `tools/nid_names.py`. Encoding follows [Unicode section 3.9](https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-3/). Each call converts one scalar, including zero and U+FEFF, without a terminator or byte-order detection; lengths count bytes for UTF-8 and 16-bit units for UTF-16. Native forms use host byte order (supported hosts are little-endian). Errors follow the existing CES converters, not console measurements, and failure leaves outputs unchanged. No console or title comparison has been run -- [sceCesEucJpToUtf8](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the scalar conversion's errors and optional length pointers are unverified against the console. Its signature follows the Orbital type database linked above; it uses the existing CP51932 profile and JIS tables, with their mapping and profile-layout assumptions listed above, converts one character (including zero), and writes no terminator. Failure leaves the output and length pointers unchanged -- [sceCesRefersUcsProfileCp1250](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - returns a profile accepted by `sceCesSbcToUtf8`, `sceCesUtf8ToSbc` and the MBCS context conversions, with the Windows-1250 table of Microsoft's [bestfit1250.txt](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit1250.txt), where the unassigned bytes map to their Latin-1 control characters as the Cp1252 and Cp1251 tables do; the console behavior for those bytes and the whole profile is not verified. PPSA34501 fails to start without this export (`qYu6y1E0Qbg`) -- [libSceCesCs firmware 3.20 pending exports](../../../core/libs/prx/libSceCesCs/PendingExports.cpp) - 249 symbol names are present in [the PS5 3.20 libSceCesCs export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCesCs.c). Their signatures and console behaviour are unverified. The declarations use the existing no-parameter unknown-signature convention solely to resolve imports, and every call throws through `NotImplemented_nid_no_patch`. -- [EUC-KR, UHC and Big5 conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the tables, errors and Big5 duplicate choices are assumed, not measured. `sceCesUcsProfileInitEucKr`, `sceCesUcsProfileInitUhc` and `sceCesUcsProfileInitBig5Cp950` tag the caller's sheet like the EUC-JP profile. The tables are Microsoft's [CP949.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP949.TXT) and [CP950.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP950.TXT); EUC-KR is CP949 limited to bytes `0xA1`-`0xFE`. Bytes below `0x80` are ASCII; the KS X 1003 single-byte profile (`sceCesEucKrUcsProfileSetSbcs`) is not implemented. The user-defined areas, which Windows maps to the Private Use Area, answer `UNASSIGNED_CODE`. Big5 encoding takes the lower of two cells except for U+2550, U+255E, U+2561, U+256A, U+5341 and U+5345, as Windows code page 950 does. The `*ToUtf8` calls convert one character and leave their outputs unchanged on failure +- [sceCesEucJpToUtf8 and sceCesEucJpToUcs2](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) with the CP51932 profile follow a payload process on a PS5 (firmware 13.60): every two-byte input and every `0x8F` three-byte input gives the console's result, length and output (the test pins a hash of both sweeps). Bytes below 0xA0 other than 0x0E, 0x8E and 0x8F convert to themselves; JIS X 0212 is not decoded, so `0x8F` forms return `0x805C0020` in rows X 0212 leaves empty and `0x805C0014` elsewhere; a null profile returns `0x805C0004`, and the lengths are cleared first and then set as the console sets them. Their signatures follow the Orbital type database linked above. The console reads any other profile pointer as a sheet, where these return the invalid-parameter error. +- [Shift-JIS and EUC-JP scalar conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - errors, their precedence and the optional length pointers of `sceCesSJisTo*`, `sceCesEucJpTo{Utf16,Utf32}*` and `sceCes{Ucs2,Utf8,Utf16,Utf32}*To{SJis,EucJp}` are unverified against the console. Signatures follow the Orbital type database linked above; the CP932 and CP51932 table and profile assumptions above apply. A profile of another encoding answers `INVALID_PARAMETER`, a surrogate or a value above U+10FFFF `INVALID_ENCODE`, a code point outside the BMP `UNASSIGNED_CODE`. UTF-8 input is decoded as `sceCesUtf8ToSbc` decodes it, so overlong forms answer `0x805C0015`. `sceCesSJisCodeToJisX0208` and `sceCesJisX0208ToSJisCode` do not check that the cell is assigned; their `INVALID_ENCODE` for rows or cells outside 1-94 and `INVALID_DST_BUFFER` for a null output are not console measurements +- [Strict JIS X 0208 profiles](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the tables were measured on a PS5 (`sceCesMbcToUtf32` and `sceCesUtf32ToMbc` over every one- and two-byte sequence and every scalar); the errors follow the other JIS profiles, a missing SS2 or SS3 answering `INVALID_ENCODE`. The table is CP932's without rows 13 and 89-92 and with seven cells changed (`0x213D` U+2014 and the six of [JIS0208.TXT](https://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT)). Cell `0x2140` is U+005C in `sceCesUcsProfileInitSJis1997X0208`, whose bytes `0x5C` and `0x7E` are U+00A5 and U+203E, and U+FF3C in the EUC-JP profiles, which keep ASCII and also encode U+00A5 and U+203E as `0xA1EF` and `0xA1B1`. Per [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Text/Ces.cs#L57-L67), `X0208Ss2` adds SS2 katakana and `X0208Ss2Ss3` SS3 JIS X 0212 +- [UTF-16 and UTF-32 forms of the single-byte and MBCS string conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - errors, their precedence and the optional length pointers of `sceCesSbcTo{Ucs2,Utf16,Utf32}*`, `sceCes{Ucs2,Utf16,Utf32}*ToSbc`, `sceCesMbcsStr{To,Get}{Utf16,Utf32}*` and `sceCes{Utf16,Utf32}Str{To,Get}Mbcs*` are unverified against the console. Signatures follow the Orbital type database linked above. They follow `sceCesSbcToUtf8`, `sceCesUtf8ToSbc` and the UTF-8 MBCS string forms with the profiles and tables listed above. A surrogate or a value above U+10FFFF answers `INVALID_ENCODE`, a valid code point no profile maps `UNASSIGNED_CODE`. The string forms stop at a zero code unit, write no terminator and count code units +- [GBK (CP936) and EUC-CN conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - `sceCesUcsProfileInitGbkCp936` and `sceCesUcsProfileInitEucCnGb2312` mark the caller's sheet with an 8-byte tag; the console's profile layout is not known. Both use one table ([Cp936.hpp](../../../core/libs/prx/libSceCesCs/Cp936.hpp)) read from Microsoft's [CP936.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP936.TXT); the EUC-CN profile accepts only its `0xA1`-`0xFE` lead and trail bytes, so it includes the GBK cells inside that range and the CP936 forms of `0xA1A4` (U+00B7) and `0xA1AA` (U+2014) instead of GB 2312's. That the console's GB 2312 and CP936 tables equal it is not measured. User-defined areas and `sceCesGbUcsProfileSet*` are not handled. Errors, their precedence and the length pointers follow the Shift-JIS forms above and are unverified +- [GB 18030 profiles](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the console's tables are not measured, and the sources disagree on 26 sequences. The 2000 profile follows ICU's `gb-18030-2000.ucm` at icu-data d9d3a6ed (before ICU-8427), which matches Python's codec: `0xA8BC` is U+E7C7 and `0x8135F437` U+1E3F. The 2005 profile follows `gb-18030-2005.ucm` (91850aec), which swaps those two and maps 24 PUA cells to U+FE10-U+FE19, U+9FB4-U+9FBB and six supplementary ideographs, keeping the PUA code points as encode fallbacks. Two-byte cells are CP936 plus [Gb18030.hpp](../../../core/libs/prx/libSceCesCs/Gb18030.hpp); four-byte runs include ICU's `gb18030Ranges`. The inits tag the sheet like GBK's; the `sceCesGb*`, `sceCesMbc*` and MBCS string forms accept them. The full mapping is assumed as the default of `sceCesGbUcsProfileSetUdaMapping` and `Set4ByteCharRange`, which throw. Supplementary characters to UCS-2 answer `UNASSIGNED_CODE`; other errors follow the GBK forms +- [sceCesRefersUcsProfileCp1250](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - returns a profile accepted by `sceCesSbcToUtf8`, `sceCesUtf8ToSbc` and the MBCS context conversions, with the Windows-1250 table of Microsoft's [bestfit1250.txt](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit1250.txt), except bytes 0x81, 0x83, 0x88, 0x90 and 0x98, which are unassigned. PPSA34501 fails to start without this export (`qYu6y1E0Qbg`) +- [sceCesSbcToUtf8 and sceCesUtf8ToSbc](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) follow a payload process on a PS5 (firmware 13.60): the three tables match the console byte for byte, with Cp1252 bytes 0x81, 0x8D, 0x8F, 0x90 and 0x9D unassigned; a null profile returns `0x805C0004`; and the length and output are cleared first, as the console does. `sceCesUtf8ToSbc` decodes leads up to 0xFD (5- and 6-byte forms), returns `0x805C0015` for overlong forms and surrogates and reports the measured lengths. The console reads any other profile pointer as a table, where these return the invalid-parameter error; which of a null profile and a scalar above U+10FFFF it reports first is not measured +- [MBCS string conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - `sceCesUtf8StrToMbcsStr`, `sceCesUcs2StrToMbcsStr`, `sceCesMbcsStrToUtf8Str`, `sceCesMbcsStrToUcs2Str` and their `Get*Len` forms follow a payload process on a PS5 (firmware 13.60) with the Cp1252 and EUC-JP profiles: the lengths are cleared and a terminator is written first, then the context, the source and the output are checked in that order; a source limit of 0 reads up to the terminating zero; the output keeps room for a terminator; and on an error the characters before it stay converted, terminated and counted. They decode UTF-8 as `sceCesUtf8ToSbc` does, and a UTF-16 surrogate answers `0x805C0015`. The UCS-2 forms and the other profiles were not measured beyond these cases. A zero-filled context converted nothing and returned 0 on the console, where this returns the invalid-parameter error +- [EUC-JP and Shift-JIS encoding](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) with the CP51932 and CP932 profiles gives the result, lengths and bytes a payload process read on a PS5 (firmware 13.60) for every UCS-2 value through `sceCesUcs2StrToMbcsStr` (the test pins a hash of both sweeps): the JIS forms U+00A2, U+00A3, U+00A5, U+00AC, U+2014, U+2016, U+203E, U+2212 and U+301C encode to the cells of their Microsoft forms, U+FEFF is consumed and writes nothing, EUC-JP writes U+0080-U+009F other than U+008E and U+008F as single bytes and answers `UNASSIGNED_CODE` for U+000E, and Shift-JIS answers `0x805C0024` for U+0080-U+009F. Decoding those bytes back is unchanged +- [Generic MBCS character and Shift-JIS code helpers](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - UTF-8/UTF-16 buffer errors and size pointers are unverified. Measured on a PS5: the single-character encoders (`sceCesUtf32ToMbc` over U+0000-U+110001 per profile, surrogates, a one-byte target) give 0x805C0015 for a surrogate, 0x805C0014 past U+10FFFF, the needed length with `DST_BUFFER_END` and 0 otherwise; a rejected decoded character (`sceCesMbcTo*` and each encoding's `To{Ucs2,Utf8,Utf16,Utf32}`, not `sceCesEucJpToUcs2`/`ToUtf8`) stores its length (the bytes before a NUL after the lead, the needed length when truncated, 1 for a bad lead, the sequence for a bad or unassigned trail), a zero UCS-2/UTF-32 character and no UTF-8/UTF-16 units. `sceCesMbcTo*` and `sceCes*ToMbc` take any profile the MBCS context accepts. `sceCesSJisGetCode`/`PutCode` are unchecked; `sceCesSJisCodeToJisX0213` and back use JIS X 0213 annex 1. Signatures follow Orbital +- [Single-byte profiles](../../../core/libs/prx/libSceCesCs/SingleByte.hpp) (libSceCesCs) - the console's tables for these 27 profiles are not verified. Each returns a profile accepted by `sceCesSbcToUtf8`, `sceCesUtf8ToSbc` and the MBCS context conversions, with bytes 0x00-0x7F unchanged and the high half of a public table: ISO 8859-1, -2, -4, -5, -9, -10, -13, -14, -15 and -16 from [unicode.org](https://unicode.org/Public/MAPPINGS/ISO8859/), code pages 437, 737, 775, 850, 852, 855, 860-863, 865 and 866 from [Microsoft's PC tables](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/), KOI8-R and KOI8-U from [unicode.org](https://unicode.org/Public/MAPPINGS/VENDORS/MISC/), and 1254, 1256 and 1258 from Microsoft's [best-fit tables](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/), whose unassigned bytes map to C1 controls as in Cp1250. Profiles whose public table leaves bytes undefined or maps them to private use still throw +- [libSceCesCs firmware 3.20 pending exports](../../../core/libs/prx/libSceCesCs/PendingExports.cpp) - 62 symbol names are present in [the PS5 3.20 libSceCesCs export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCesCs.c). Their signatures and console behaviour are unverified. The declarations use the existing no-parameter unknown-signature convention solely to resolve imports, and every call throws through `NotImplemented_nid_no_patch`. +- [EUC-KR, UHC and Big5 conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the errors are assumed; the decode tables and the Big5 duplicate choices were measured on a PS5 (`sceCesMbcToUtf32` and each `ToUtf8` over every one- and two-byte sequence): UHC and Big5 match, EUC-KR apart from its single bytes. The tables are Microsoft's [CP949.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP949.TXT), plus `0xA2E8` U+327E from KS X 1001:2002, and [CP950.TXT](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP950.TXT); EUC-KR is CP949 limited to bytes `0xA1`-`0xFE`, below `0x80` ASCII; the KS X 1003 profile is not implemented. User-defined areas answer `UNASSIGNED_CODE`. Big5 encoding takes the lower of two cells except for U+5341 and U+5345, as a PS5's `sceCesUtf32ToMbc` does for every scalar (CP950 takes the higher cell of U+2550, U+255E, U+2561 and U+256A; the console does not). The profiles tag the caller's sheet like the EUC-JP profile diff --git a/docs/dev/debt/libSceCommonDialog.md b/docs/dev/debt/libSceCommonDialog.md index 0e7ac021bf..f46aaf0394 100644 --- a/docs/dev/debt/libSceCommonDialog.md +++ b/docs/dev/debt/libSceCommonDialog.md @@ -3,3 +3,7 @@ ### Silent stubs - [libSceCommonDialog](../../../core/libs/prx/libSceCommonDialog/Export.cpp) + +### Functional + +- [sceCommonDialogInitialize](../../../core/libs/prx/libSceCommonDialog/Export.cpp) (libSceCommonDialog) - returning `ALREADY_SYSTEM_INITIALIZED` (`0x80B80002`) from a repeated call is not measured on a console by the project. A third-party PS5 log ([obSCEne](https://github.com/project-oops/obSCEne/blob/67b4c529eb3c271184117e26bb54330531ef2cf6/reports/hardware/report-20261014-004800.log#L260-L261), firmware 12.40) records two calls, `0x0` and then `0x80b80002`; a third call is not covered. The SDK error names, shadPS4 (a PS4 emulator) and SharpProspero give the same value; PPSA09695 compares the result with `0x80b80002` (call site 0x5F7BD0) and PPSA06327 stops on `0x80b80004`. Closes when: measured on a PS5, including a third call. diff --git a/docs/dev/debt/libSceCompanionHttpd.md b/docs/dev/debt/libSceCompanionHttpd.md index 2738a6bd6d..3ed7d72d8c 100644 --- a/docs/dev/debt/libSceCompanionHttpd.md +++ b/docs/dev/debt/libSceCompanionHttpd.md @@ -2,4 +2,10 @@ ### Unknown function info -- [sceCompanionHttpdAddHeader, sceCompanionHttpdGet2ndScreenStatus, sceCompanionHttpdGetEvent, sceCompanionHttpdGetUserId, sceCompanionHttpdInitialize, sceCompanionHttpdInitialize2, sceCompanionHttpdOptParamInitialize, sceCompanionHttpdRegisterRequestBodyReceptionCallback, sceCompanionHttpdRegisterRequestCallback, sceCompanionHttpdRegisterRequestCallback2, sceCompanionHttpdSetBody, sceCompanionHttpdSetStatus, sceCompanionHttpdStart, sceCompanionHttpdStop, sceCompanionHttpdTerminate, sceCompanionHttpdUnregisterRequestBodyReceptionCallback and sceCompanionHttpdUnregisterRequestCallback](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCompanionHttpd.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceCompanionHttpdOptParamInitialize, sceCompanionHttpdInitialize](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - the option block is taken as 0x60 bytes: titles fill a work memory pointer at +0x08, its size at +0x10 and a temporary data mount point at +0x58 after `sceCompanionHttpdOptParamInitialize`. Its other fields and the defaults the console writes are unknown, so `sceCompanionHttpdOptParamInitialize` zeroes the 0x60 bytes and `sceCompanionHttpdInitialize` reads none of them; both reject a null block with `INVALID_PARAM` +- [sceCompanionHttpdAddHeader, sceCompanionHttpdSetStatus, sceCompanionHttpdSetBody](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - imported by PS4 titles and added without an implementation. They fill the response of a request callback, which is never called here, so every call throws +- [sceCompanionHttpdGet2ndScreenStatus, sceCompanionHttpdGetUserId, sceCompanionHttpdInitialize2, sceCompanionHttpdRegisterRequestBodyReceptionCallback, sceCompanionHttpdRegisterRequestCallback2, sceCompanionHttpdUnregisterRequestBodyReceptionCallback](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - the rest of the library's exports, declared without parameters and without an implementation; their signatures are not verified, so every call throws + +### Functional + +- [Companion server state](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - no HTTP server listens, so no companion device ever connects: `sceCompanionHttpdGetEvent` returns `NO_EVENT` without writing the event and the request callback is never called. Initialization, callback registration and start and stop are tracked with the errors of shadPS4's [companion_error.h](https://github.com/shadps4-emu/shadPS4/blob/eb9f66c349ae7c8fef780f4f55e98b8065947e14/src/core/libraries/companion/companion_error.h) (`ALREADY_REGISTERED` is 0x80E4000C, between `NOT_STARTED` and `NOT_CONNECTED`; the header has 0x80E4000, one digit short). Unregistering with no callback and terminating a started server throw, since the console's answers are unknown. Not measured on a console. Closes when: a companion server is hosted diff --git a/docs/dev/debt/libSceCompanionUtil.md b/docs/dev/debt/libSceCompanionUtil.md index 48d3c1cb47..652949f2b7 100644 --- a/docs/dev/debt/libSceCompanionUtil.md +++ b/docs/dev/debt/libSceCompanionUtil.md @@ -2,4 +2,5 @@ ### Unknown function info -- [sceCompanionUtilGetEvent, sceCompanionUtilGetRemoteOskEvent, sceCompanionUtilInitialize, sceCompanionUtilOptParamInitialize and sceCompanionUtilTerminate](../../../core/libs/prx/libSceCompanionUtil/Export.cpp) (libSceCompanionUtil) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCompanionUtil.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceCompanionUtilGetEvent](../../../core/libs/prx/libSceCompanionUtil/Export.cpp) - the PS5 argument contract, event size and alignment remain unverified for the output pointer. The supplied `libSceCompanionUtil.sprx` export `cE5Msy11WhU` at 0x100 only returns `0x80AD0008`, without reading argument registers or memory. The implementation preserves that return and does not access the buffer. +- [sceCompanionUtilGetRemoteOskEvent, sceCompanionUtilInitialize, sceCompanionUtilOptParamInitialize and sceCompanionUtilTerminate](../../../core/libs/prx/libSceCompanionUtil/Export.cpp) (libSceCompanionUtil) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCompanionUtil.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceComposite.md b/docs/dev/debt/libSceComposite.md index 45ba88b721..36d78197ce 100644 --- a/docs/dev/debt/libSceComposite.md +++ b/docs/dev/debt/libSceComposite.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceCompositorAllocateIndex, sceCompositorCheckCrash, sceCompositorCommandGpuPerfBegin, sceCompositorCommandGpuPerfEnd, sceCompositorCreateIndirectRenderTarget, sceCompositorDeleteIndirectRenderTarget, sceCompositorFlush, sceCompositorFlushWithRepeat, sceCompositorGetAnotherProcessSystemAddress, sceCompositorGetAnotherProcessSystemSize, sceCompositorGetAnotherProcessVideoAddress, sceCompositorGetAnotherProcessVideoSize, sceCompositorGetCanvasHandle, sceCompositorGetClientList, sceCompositorGetDmemOffset, sceCompositorGetDummyAddress, sceCompositorGetDummySize, sceCompositorGetRemainedCommandNum, sceCompositorGetRenderTargetResolution, sceCompositorGetSystemAddress, sceCompositorGetSystemSize, sceCompositorGetVideoAddress, sceCompositorGetVideoSize, sceCompositorHandleProcessEvents, sceCompositorInit, sceCompositorInitWithProcessOrder, sceCompositorInsertThreadTraceMarker, sceCompositorIsDebugCaptureEnabled, sceCompositorIsReady4K, sceCompositorLockCommandBuffer, sceCompositorMakeCanvasHandle, sceCompositorMapAnotherProcess, sceCompositorMemoryPoolDecommit, sceCompositorPrivacyMaskPushArea, sceCompositorQuit, sceCompositorReleaseCommandBuffer, sceCompositorReleaseIndex, sceCompositorReleaseLocalStall, sceCompositorReserveCommandBuffer, sceCompositorResetZoomCommand, sceCompositorSetAgcCompositeCanvasCommand, sceCompositorSetAgcContextCommand, sceCompositorSetCompositeCanvasCommandInC, sceCompositorSetCursorImageAddress, sceCompositorSetDebugPositionCommand, sceCompositorSetEndOfFrameCommand, sceCompositorSetEventCommand, sceCompositorSetFilterType, sceCompositorSetFlipCommand2, sceCompositorSetGameSufaceControlCommand, sceCompositorSetGnmContextCommand, sceCompositorSetIndirectCanvasCommand, sceCompositorSetIndirectCompositionCommand, sceCompositorSetIndirectRenderTargetConfigCommand, sceCompositorSetInvisibleCanvasCommand, sceCompositorSetKeepDisplayBufferCommand, sceCompositorSetLocalStallCommand, sceCompositorSetMemoryCommand, sceCompositorSetPatchCommand, sceCompositorSetPostEventCommand, sceCompositorSetRepeatCommand, sceCompositorSetResolutionCommand, sceCompositorSetScalingLayoutInfo, sceCompositorSetSeparateContextCommand, sceCompositorSetSystemConfigWithMask, sceCompositorSetVideoOutMode, sceCompositorSetVirtualCanvasAgcContextCommand, sceCompositorSetVirtualCanvasCommandFooter, sceCompositorSetVirtualCanvasPatchCommand, sceCompositorSetVrMode, sceCompositorSetZoomCommand, sceCompositorSystemConfig, sceCompositorWaitEndOfRendering, sceCompositorWaitEvent, sceCompositorWaitPostEvent, sceCompsoitorGetGpuClock, sceCompsoitorGetProcessRenderingTime and sceCompsoitorGetRenderingTime](../../../core/libs/prx/libSceComposite/Export.cpp) (libSceComposite) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceComposite.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceCompositorAllocateIndex, sceCompositorCheckCrash, sceCompositorCommandGpuPerfBegin, sceCompositorCommandGpuPerfEnd, sceCompositorFlush, sceCompositorFlushWithRepeat, sceCompositorGetAnotherProcessSystemAddress, sceCompositorGetAnotherProcessSystemSize, sceCompositorGetAnotherProcessVideoAddress, sceCompositorGetAnotherProcessVideoSize, sceCompositorGetCanvasHandle, sceCompositorGetClientList, sceCompositorGetDmemOffset, sceCompositorGetDummyAddress, sceCompositorGetDummySize, sceCompositorGetRemainedCommandNum, sceCompositorGetRenderTargetResolution, sceCompositorGetSystemAddress, sceCompositorGetSystemSize, sceCompositorGetVideoAddress, sceCompositorGetVideoSize, sceCompositorHandleProcessEvents, sceCompositorInit, sceCompositorInitWithProcessOrder, sceCompositorInsertThreadTraceMarker, sceCompositorIsDebugCaptureEnabled, sceCompositorIsReady4K, sceCompositorLockCommandBuffer, sceCompositorMakeCanvasHandle, sceCompositorMapAnotherProcess, sceCompositorMemoryPoolDecommit, sceCompositorPrivacyMaskPushArea, sceCompositorQuit, sceCompositorReleaseCommandBuffer, sceCompositorReleaseIndex, sceCompositorReleaseLocalStall, sceCompositorReserveCommandBuffer, sceCompositorResetZoomCommand, sceCompositorSetAgcCompositeCanvasCommand, sceCompositorSetAgcContextCommand, sceCompositorSetCompositeCanvasCommandInC, sceCompositorSetCursorImageAddress, sceCompositorSetDebugPositionCommand, sceCompositorSetEndOfFrameCommand, sceCompositorSetEventCommand, sceCompositorSetFilterType, sceCompositorSetFlipCommand2, sceCompositorSetGameSufaceControlCommand, sceCompositorSetGnmContextCommand, sceCompositorSetInvisibleCanvasCommand, sceCompositorSetKeepDisplayBufferCommand, sceCompositorSetLocalStallCommand, sceCompositorSetMemoryCommand, sceCompositorSetPatchCommand, sceCompositorSetPostEventCommand, sceCompositorSetRepeatCommand, sceCompositorSetResolutionCommand, sceCompositorSetScalingLayoutInfo, sceCompositorSetSeparateContextCommand, sceCompositorSetSystemConfigWithMask, sceCompositorSetVideoOutMode, sceCompositorSetVirtualCanvasAgcContextCommand, sceCompositorSetVirtualCanvasCommandFooter, sceCompositorSetVirtualCanvasPatchCommand, sceCompositorSetVrMode, sceCompositorSetZoomCommand, sceCompositorSystemConfig, sceCompositorWaitEndOfRendering, sceCompositorWaitEvent, sceCompositorWaitPostEvent, sceCompsoitorGetGpuClock, sceCompsoitorGetProcessRenderingTime and sceCompsoitorGetRenderingTime](../../../core/libs/prx/libSceComposite/Export.cpp) (libSceComposite) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceComposite.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceContentDelete.md b/docs/dev/debt/libSceContentDelete.md index a0dacf89d0..f7365210d5 100644 --- a/docs/dev/debt/libSceContentDelete.md +++ b/docs/dev/debt/libSceContentDelete.md @@ -2,8 +2,9 @@ ### Functional -- [sceContentDeleteById and sceContentDeleteByPath](../../../core/libs/prx/libSceContentDelete/Export.cpp) (libSceContentDelete) delete from a content library that is empty on the host, as nothing is exported to it, so both throw for every content id and path once the arguments and the initialization are checked: no reference gives the console's error for content that is not in the library ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Content/ContentDelete.cs#L25-L42) names only `0x809D5006`, no permission). The signatures, a 64-bit content id and a UTF-8 path, follow [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L541-L542) and SharpProspero, which agree. A null path and a call outside `sceContentDeleteInitialize` and `sceContentDeleteTerminate` throw, as the console's errors for them are unknown, and the order of the two checks is unverified. +- [sceContentDeleteById](../../../core/libs/prx/libSceContentDelete/Export.cpp) and `sceContentDeleteByPath`: content database access and deletion are not implemented. Calls with valid arguments after initialization throw; the backend result cannot be inferred from an empty host library. Null paths after initialization throw instead of reproducing the original unchecked string access. +- [sceContentDeleteInitialize](../../../core/libs/prx/libSceContentDelete/Export.cpp): validates the confirmed 16 KiB heap requirement and lifecycle errors, but does not create the original allocator, mutex and database client. Initialization-parameter fields other than the 64-bit heap size at offset 8 remain unresolved. ### Unknown function info -- [sceContentDeleteByIdListForSf, sceContentDeleteGlobalMstat, sceContentDeleteMemoryInitialize, sceContentDeleteMemoryTerminate](../../../core/libs/prx/libSceContentDelete/Export.cpp) (libSceContentDelete) - exported symbols from decrypted PS5 firmware 1.00–3.20 libSceContentDelete.sprx; parameter lists and return values are unknown/unverified and no reference (KytyPS5, shadPS4, fpPS4, SharpProspero) documents their signatures. Declared as stubs that throw via `NotImplemented_nid_no_patch` +- [sceContentDeleteGlobalMstat](../../../core/libs/prx/libSceContentDelete/Export.cpp), `sceContentDeleteMemoryInitialize` and `sceContentDeleteMemoryTerminate`: neither exports nor callers were found in the supplied decompiled library corpus; their signatures remain unresolved. diff --git a/docs/dev/debt/libSceContentExport.md b/docs/dev/debt/libSceContentExport.md index 18355bf87b..01f0d64324 100644 --- a/docs/dev/debt/libSceContentExport.md +++ b/docs/dev/debt/libSceContentExport.md @@ -2,9 +2,10 @@ ### Functional -- [Content export sessions](../../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport): `sceContentExportStart` hands out positive session ids, at most 10 live, which `sceContentExportFinish` closes. The signatures follow [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json) and the limit `MaxExecution` [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Content/ContentExport.cs); the parameter layouts follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/443f022450f6857256e780daa3d5a14029a748f7/src/core/libraries/content_export/content_export.h). The host has no media library, so `sceContentExportFromDataWithThumbnail` throws once the session is checked, before calling either data callback, and no export ever runs. `sceContentExportGetProgress` and `sceContentExportCancel` throw for a live session, as their result with no running export is unknown. An eleventh `sceContentExportStart` and `sceContentExportTerm` with live sessions throw: whether the console answers `EXECUTION_MAX` or `BUSY` there, or ends the sessions, is unverified. Session id values and error precedence are unverified +- [Content export sessions](../../../core/libs/prx/libSceContentExport/Export.cpp): the host has no media library, so `sceContentExportFromDataWithThumbnail` throws for a valid session without invoking data callbacks. Session allocation does not reproduce guest allocator calls, worker creation or their failure paths. `sceContentExportGetProgress` throws because an idle session returns either zero or an invalid-state error according to the caller's SDK compatibility mode, which is not available here. Initialization and session lookup still throw for some invalid inputs instead of reproducing the console's error precedence and legacy SDK behavior. Session IDs now use the lowest free slot in 0–9; exhaustion, termination with live sessions and cancellation before an export return the confirmed errors. ### Unknown function info -- PlayStation 5 firmware exports declared without parameters, signatures unknown: [sceContentExportAddToFavorites, sceContentExportDataProvideFunction, sceContentExportFree, sceContentExportFrom, sceContentExportFromFileForSf, sceContentExportFromFileWithContentIdList, sceContentExportFromFileWithTitleIdList, sceContentExportInit, sceContentExportMalloc, sceContentExportRemoveFromFavorites, sceContentExportRenameContent, sceContentExportValidateContents](../../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport). Names from the firmware NID database; unimplemented and throw `NotImplemented_nid_no_patch`. +- [sceContentExportDataProvideFunction, sceContentExportFree, sceContentExportFrom and sceContentExportMalloc](../../../core/libs/prx/libSceContentExport/Export.cpp): their signatures remain unresolved; these names have no matching exports in the supplied module's C or assembly listing. +- [Content export arguments](../../../core/libs/prx/libSceContentExport/Export.cpp): file/data entry points and content-ID operations have restored argument lists. The Share Factory parameter contains a size, metadata string, content-ID list, count and option; metadata format and option values remain unresolved. Protected export bodies are unchanged. diff --git a/docs/dev/debt/libSceContentSearch.md b/docs/dev/debt/libSceContentSearch.md index 56c59f62ad..6df6de62ff 100644 --- a/docs/dev/debt/libSceContentSearch.md +++ b/docs/dev/debt/libSceContentSearch.md @@ -6,4 +6,5 @@ ### Functional -- [libSceContentSearch](../../../core/libs/prx/libSceContentSearch/Export.cpp) searches a content library that is empty on the host, as nothing is exported to it. The signatures follow [AlexAltea/orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L555-L569) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Content/ContentSearch.cs#L255-L316), which agree; the filter and sort layouts and enums, the limit of 92 rows, `0x809D100A` for a larger limit and `0x809D1001` before `sceContentSearchInit` are SharpProspero's. `sceContentSearchGetNumOfContent`, `sceContentSearchGetTotalContentSize` and `sceContentSearchSearchContent` report 0 rows and 0 bytes without writing a content row, and the update id they and `sceContentSearchGetContentLastUpdateId` report is always 0; no reference gives the console's update id for a library that never changed. Filters and sort conditions are only checked against SharpProspero's enums, and a null output, a null array with a non-zero length, an empty column set, a null filter value or an unknown enum value throws, as the console's errors for them are unknown; a null column set with length 0 is taken as no filter. `sceContentSearchOpenMetadata` and `sceContentSearchOpenMetadataByContentId` throw, as no reference gives the error for a path or content id that is not in the library, so no metadata id is ever handed out and `sceContentSearchCloseMetadata`, `sceContentSearchGetMetadataFieldInfo` and `sceContentSearchGetMetadataValue` throw. Every title is taken to hold the content-search permission (SharpProspero's `0x809D1013` is never returned). The order of the initialization, argument and limit checks is unverified +- [Content queries](../../../core/libs/prx/libSceContentSearch/Export.cpp) use an empty host library and always report update id 0; the console's initial update id is unverified. No content rows are produced. Every title is assumed to have search permission. Nested filter/sort validation is incomplete: empty nested column sets, null filter values and unknown enum values throw. Guest heap allocation and the platform-dependent minimum heap size are unimplemented. Initialization and outer pointer/count/limit validation follow the supplied module. +- [Metadata access](../../../core/libs/prx/libSceContentSearch/Export.cpp) cannot open metadata by path or content id because missing-content errors remain unresolved. No metadata handles are issued. Queries reject invalid outer pointers but still throw for valid pointers because errors depend on the requested field; closing an unopened handle returns the confirmed invalid-argument error. diff --git a/docs/dev/debt/libSceConvertKeycode.md b/docs/dev/debt/libSceConvertKeycode.md index 706e31a66e..ca16750daf 100644 --- a/docs/dev/debt/libSceConvertKeycode.md +++ b/docs/dev/debt/libSceConvertKeycode.md @@ -1,9 +1,6 @@ # libSceConvertKeycode technical debt -### Unknown function info - -- [sceConvertKeycodeGetCharacter](../../../core/libs/prx/libSceConvertKeycode/Export.cpp) (libSceConvertKeycode) - the signature `(uint16_t keycode, uint32_t, uint32_t keyboardType, char16_t* character)` is inferred from PPSA12361, which passes a 16-bit keycode, 0, the type from `sceConvertKeycodeGetImeKeyboardType` and a pointer it reads one UTF-16 unit from when the result is 0. The meaning of the second argument is unknown. PPSA02530 (#5726) and PPSA27836 pass `0x4000000` as a constant: PPSA27836 calls it for every keycode from 0 to 0x8F to build a table of key names, upper-cases `a` to `z` itself and skips keycodes that fail or return 0, so that bit is ignored and the unshifted character is returned as for 0. Any other bit throws, and so does a keyboard type other than 0. The returned character for HID usage `0x64` has not been measured on a console; `\` is an unverified US-layout guess based on the Windows VK mapping, which does not establish the console result. - ### Functional -- [sceConvertKeycodeGetCharacter](../../../core/libs/prx/libSceConvertKeycode/Export.cpp) (libSceConvertKeycode) takes the keycode as a USB HID keyboard usage and returns the unshifted character of the US layout for letters, digits, punctuation, space and the keypad. A keycode without a character returns 0 with the character 0; whether the console returns an error instead is not measured. +- [sceConvertKeycodeGetVirtualKeycode](../../../core/libs/prx/libSceConvertKeycode/Export.cpp) and `sceConvertKeycodeGetCharacter`: layouts 31–39 throw until the guest platform and compiled SDK version are exposed. `GetVirtualKeycode` also throws for version-dependent Y/Z mappings in layouts 2, 3 and 12. Mapping tables and modifier behavior are recovered; no SDK version is guessed. +- [sceConvertKeycodeGetImeKeyboardType](../../../core/libs/prx/libSceConvertKeycode/Export.cpp): implemented exactly as the repository owner (boykopovar) specified the UserService keyboard type (plus the Japanese variant for type 37 and the Traditional Chinese variant for type 39) to IME keyboard type table, the SDK compatibility rules (38 becomes 4 for PS5 SDK below 0x06000000 or PS4 SDK below 0x10000000; 39 becomes 29 for PS5 SDK below 0x08000000 and for every PS4 client), the error codes and the unmatched-type result (success, IME type 0). The PS4/PS5 client rule reads the title ID prefix from `param.sfo` or `param.json` (`CUSA` is PS4); a missing, malformed or oversized ID or title, as in homebrew, is treated as PS5. Reliable detection inspects the original `eboot.bin`, which only the relinker reads, so it is not done. UserService reports the keyboard type of the configured system language, so the function returns the owner's IME type for that language (see libSceUserService.md). diff --git a/docs/dev/debt/libSceDiscId.md b/docs/dev/debt/libSceDiscId.md index fb26a7a344..17eb02c445 100644 --- a/docs/dev/debt/libSceDiscId.md +++ b/docs/dev/debt/libSceDiscId.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceDiscIdGet and sceDiscIdGet2](../../../core/libs/prx/libSceDiscId/Export.cpp) (libSceDiscId) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceDiscId.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceDiscIdGet](../../../core/libs/prx/libSceDiscId/Export.cpp) takes one output pointer; `sceDiscIdGet2` takes an output pointer and a 32-bit argument that must be zero. Both pass the pointer to a device ioctl without accessing its contents. Output structure sizes and fields require the device-side implementation or a caller and remain unresolved. diff --git a/docs/dev/debt/libSceDiscMap.md b/docs/dev/debt/libSceDiscMap.md index 4fcd7700ec..b058be4747 100644 --- a/docs/dev/debt/libSceDiscMap.md +++ b/docs/dev/debt/libSceDiscMap.md @@ -1,10 +1,9 @@ # libSceDiscMap technical debt -### Silent stubs +### Functional -- [libSceDiscMap](../../../core/libs/prx/libSceDiscMap/Export.cpp) implements only the state with no disc bitmap: `sceDiscMapIsRequestOnHDD` and `ioKMruft1ek` return `SCE_DISC_MAP_ERROR_NO_BITMAP_INFO` (0x81100004) for every request and write nothing. CUSA02636's bundled `libSceFios2.prx` imports both; what it does with the error is not known (shadPS4 returns the same, and [shadps4-game-compatibility #617](https://github.com/shadps4-compatibility/shadps4-game-compatibility/issues/617) lists CUSA02636 as in-game) +- [libSceDiscMap](../../../core/libs/prx/libSceDiscMap/Export.cpp): loading disc bitmap metadata and resolving file byte ranges to bitmap blocks are not implemented. Both exports implement the confirmed no-bitmap path: return `SCE_DISC_MAP_ERROR_NO_BITMAP_INFO` before validating any argument and leave every output unchanged. The supplied module confirms signed 64-bit byte offset/size, a 32-bit `IsRequestOnHDD` result and three 64-bit outputs for `ioKMruft1ek`. The latter outputs describe the bitmap selector, first block and block count. Its exported source name remains unavailable. ### Unknown function info - [sceDiscMapGetPackageSize](../../../core/libs/prx/libSceDiscMap/Export.cpp) (libSceDiscMap) - its name and NID are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceDiscMap.c); the `(int64_t, int*, int*)` prototype follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/disc_map/disc_map.cpp#L12), is not measured on a console, and every call throws -- [sceDiscMapIsRequestOnHDD, sceDiscMapUnknown_ioKMruft1ek](../../../core/libs/prx/libSceDiscMap/Export.cpp) (libSceDiscMap, NIDs `lbQKqsERhtE` and `ioKMruft1ek`) - `ioKMruft1ek` has no known name. The `(path, offset, size, result)` and `(path, offset, size, flags, out1, out2)` signatures follow [shadPS4 disc_map.cpp](https://github.com/shadps4-emu/shadPS4/blob/b2d7ec7d31f9ad9426db93afe079db8679b3e1e0/src/core/libraries/disc_map/disc_map.cpp); the widths of the `ioKMruft1ek` outputs are unknown (shadPS4 `int*`, [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/ps4_libscediscmap.pas) `PInt64`), so they are `void*`. The error code is the one both return; [shadPS4 #2686](https://github.com/shadps4-emu/shadPS4/pull/2686) reads the console library as checking an initialisation global first in each call and returning it while the global is clear. Not measured on a console diff --git a/docs/dev/debt/libSceFiber.md b/docs/dev/debt/libSceFiber.md index 9ad010851f..da142ae02b 100644 --- a/docs/dev/debt/libSceFiber.md +++ b/docs/dev/debt/libSceFiber.md @@ -6,7 +6,8 @@ ### Unknown function info -- [_sceFiberInitializeWithInternalOptionImpl](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - its signature follows shadPS4's [initialize implementations](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L150-L153); its export alias is mapped at [RegisterLib](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L548-L551). Not measured on a console; the throwing declaration only resolves the import. -- [_sceFiberAttachContextAndRun, _sceFiberAttachContextAndSwitch](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - signatures follow shadPS4's [run implementation](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L260-L261) and [switch implementation](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L336-L337); their export aliases are mapped at [RegisterLib](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L555-L563). Not measured on a console; the throwing declarations only resolve imports. +- [_sceFiberInitializeWithInternalOptionImpl](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - its signature and the flag values `0x1` (no ulobjmgr registration), `0x10` (context size check) and `0x100` (start with MXCSR `0x9fc0` and x87 control word `0x037f`) follow shadPS4's [initialize implementation](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L150-L153) and [flags](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.h#L27-L32). Not measured on a console. `0x1` has no effect because no ulobjmgr exists; other flag bits throw. shadPS4 also sets `0x100` for `build_version` 3.50 and later in every initialize; `opt_param` and `build_version` are ignored. +- [_sceFiberAttachContextAndRun, _sceFiberAttachContextAndSwitch](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - signatures and behaviour follow shadPS4's [run](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L260-L261), [switch](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L336-L337) and [attach](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L46-L79) implementations, not measured on a console. A context is attached only to an idle fiber initialized without one; a fiber that already has a context, or is not idle, returns `SCE_FIBER_ERROR_INVALID`. A size that is not a multiple of 16 returns `SCE_FIBER_ERROR_ALIGNMENT` as in initialize, where shadPS4 returns `SCE_FIBER_ERROR_INVALID`. +- [Fibers without a context](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - caller-stack execution and restart after yielding follow shadPS4 at commit `f68923fe807f657348e6719ce658bcbd0a2b1c72` ([run](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L238-L307), [switch](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L309-L360), [return](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L376-L401)), not measurements on a PS5. The original calling thread stack is reused even when switching from an attached fiber; contextless continuations are discarded. - [_sceFiberGetThreadFramePointerAddress](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber, NID `0dy4JtMUcMQ`) forwards to `sceFiberGetThreadFramePointerAddress`; that the argument list matches is inferred from the export without the underscore, as for the underscore Ult exports, not measured - [Fiber stacks](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - a fiber whose context is smaller than 64 KiB runs on an mmap'd host stack of its context size plus 1 MiB, so host calls made from it (21 KiB deep when PPSA01341 reaches SPIRV-Tools from a 16 KiB context) do not overwrite the contexts packed next to it; a larger context is the stack, as on the console. PPSA05684 needs the second: its job fibers have 64 KiB and 512 KiB contexts, the 512 KiB ones at 1 MiB-aligned addresses, and find their job record by masking the stack pointer (`~0xfffff` for the 512 KiB ones), which a host stack breaks (the game waits forever in GameInit, or faults later in a 64 KiB fiber). The 64 KiB limit is chosen between these two titles, not measured: a title with small contexts that also derives state from the stack pointer, or one whose host calls exceed what is left of a large context, is not covered diff --git a/docs/dev/debt/libSceFont.md b/docs/dev/debt/libSceFont.md index 5c3d5ad062..c704a42b60 100644 --- a/docs/dev/debt/libSceFont.md +++ b/docs/dev/debt/libSceFont.md @@ -2,15 +2,18 @@ ### Unknown function info -- [sceFontSetScriptLanguage, sceFontGetScriptLanguage, sceFontSetTypographicDesign, sceFontGetTypographicDesign](../../../core/libs/prx/libSceFont/src/Style.cpp) (libSceFont) - signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.cs), not confirmed for the PS5. The handle keeps the language set for each script and the feature set for each typographic design and returns them, with 0 for one never set; the console default and its valid ranges are unknown, so any value is accepted. Neither setting changes shaping or rendering yet + +- [sceFontRenderCharGlyphImage, sceFontRenderCharGlyphImageHorizontal, sceFontRenderCharGlyphImageVertical](../../../core/libs/prx/libSceFont/src/Layout.cpp) (libSceFont) accept a surface whose width or height is 0, with any row stride, and return the glyph metrics and an empty update rectangle without writing to the surface. A negative width, height or stride, a zero stride on a surface with pixels, a null buffer and a pixel size other than 1 or 4 bytes still return `SCE_FONT_ERROR_NO_SUPPORT_SURFACE`; those cases and the exact console result for a zero-extent surface are not measured. The extent-0 behaviour is inferred from the Tekken Dark Resurrection PSP emulator which, to measure a string, initialises the surface with width 0 and height 31 and an unbound stride, renders each character and sums the returned advances. + +- [sceFontCreateString](../../../core/libs/prx/libSceFont/src/Text.cpp): a string created from a default-form (0x10) text source gets the direction of its first strong character, 0x11 for right to left and 0x12 for left to right, and 0x12 when there is none; explicit 0x11 and 0x12 sources keep their form and [sceFontStringGetWritingForm](../../../core/libs/prx/libSceFont/src/Text.cpp) returns the stored form. LRM and RLM count as strong, embeddings and overrides do not, and text between an isolate initiator and its PDI is skipped. The rule follows a run of the firmware 13.42 library in a harness reported on the pull request: for non-empty text it was checked before a later glyph-stage failure and only empty text completed, so it is not full native right-to-left rendering. Which characters are strong is decided here by Unicode block ranges, not by the full bidi class tables. +- [Typographic design](../../../core/libs/prx/libSceFont/src/Style.cpp): native selector, feature, font-set eligibility and output-clearing rules are recovered. Driver typography selection and queries, feature-cache updates and glyph substitution remain unimplemented and throw instead of storing arbitrary selector/value pairs. +- [sceFontSetScriptLanguage and sceFontGetScriptLanguage](../../../core/libs/prx/libSceFont/src/Style.cpp): native script/language ranges, handle-error precedence and clearing of failed getter outputs are implemented. Valid driver queries and updates throw; the previous arbitrary-value map and fabricated zero default are no longer used. Driver selection and actual language-dependent shaping remain unimplemented. - [sceFontGetRenderScaledKerning](../../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale -- [sceFontGetPixelResolution](../../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. The value is the one the library's driver reports (64 for FreeType); the argument checks and the cleared output follow `sceFontGetLibrary` -- [sceFontDettachDeviceCacheBuffer](../../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. It detaches the cache buffer attached by `sceFontAttachDeviceCacheBuffer`, returns the buffer and its size, and reports `SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER` when no cache buffer is attached -- [sceFontGetFontGlyphsCount, sceFontGetCharGlyphCode](../../../core/libs/prx/libSceFont/src/Open.cpp) (libSceFont) - signatures taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and matching the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings, not confirmed on a title. The count is the face's glyph count and the glyph code its glyph index, as the library's driver reports them; a code with no glyph returns `NO_SUPPORT_GLYPH` and code 0 `NO_SUPPORT_CODE`, as `sceFontRenderCharGlyphImage` does. The argument checks and the cleared output follow `sceFontGetPixelResolution` -- [sceFontGetFontResolution](../../../core/libs/prx/libSceFont/src/Open.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title; the outputs are inferred from the names. The resolution is the face's units per EM and the scale pixel the driver's face scale (units per EM / 64 for FreeType), the pixel scale a font has when opened. Either output may be null, as in `sceFontGetResolutionDpi` -- [sceFontCreateWritingLine, sceFontWritingLineWritesOrder](../../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - signatures and the `SceFontWritingLineStep`/`SceFontCreateWritingLineDetail` layouts (detail id `0x0FD5`) taken from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFontStructs.cs), not confirmed on a title. The 64-bit writing attribute is read as the PS4 11.00 `libSceFont.sprx` reads it: low byte `0xFA`, embedding level in bits 8-15 (0 to 126), bit 30 and bit 31 marking space before and after the run; the PS5 library was not measured, and the names of the two bits are ours. PPSA01325 builds its attribute the same way (`0xFA | level << 8`, bits 30 and 31) and passes a null metrics pointer when its run has no metrics object. Attribute 0 is accepted and treated as `0xFA` with level 0 and no spacing bits, as before this reading; the PS4 library rejects it and what the PS5 library does with it is unknown. Any other attribute, a level above 126 and a null orderer throw: the PS4 library returns `0x80460002` for them, not confirmed for the PS5 -- [sceFontWritingRefersRenderStepCharacter](../../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - the second argument is an out pointer to the writing's letter step, as in [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1882-L1896) and the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings. Measured on PS4 firmware 11.00 `libSceFont.sprx` (not on a PS5): exactly 8 bytes are written, the address of a step stored inside the writing, and a null writing writes null; shadPS4 and prosper treat the argument as a struct, which the measurement contradicts. The letter step lives in the writing, so the pointer stays valid as long as the writing does, and its contents are rebuilt by each call of this function. On the PS4 library repeated calls on one step walk the characters it covers and then return null; here every call returns the step's one character, so the second call differs. Assumed: each step covers one character and one glyph. PPSA06699 (Hot Shots Golf 2) dereferences the returned pointer for its character counts. -- [sceFontWritingLineGetOrderingSpace](../../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - the four outputs are written in the order the PS4 11.00 `libSceFont.sprx` writes them (line advance, leading space, space between spaced runs, trailing space); the parameter names in the [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) IDA type database and the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings order them differently (head, inline, tail, advance), and the PS5 library was not measured. PPSA01325's only call uses them in the PS4 order: it computes first - second - fourth - 0.25 * third and compares that, divided by a scale, with its width limit. The PS4 library writes the trailing space through its pointer without a null check whenever the leading-space pointer is set; that call throws here +- [sceFontGetFontGlyphsCount, sceFontGetCharGlyphCode](../../../core/libs/prx/libSceFont/src/Open.cpp): signatures, error precedence, failed-output clearing and external-font glyph-code encoding are confirmed by the PS5 library. Multi-face system-font glyph counts, face-selection bits and typographic glyph substitutions are not modeled. +- [sceFontGetFontResolution](../../../core/libs/prx/libSceFont/src/Open.cpp): the 32-bit resolution output, float scale and nullable outputs are confirmed by the PS5 library and FontFt driver. Native system font sets return resolution 1000; the host still uses its selected face resolution because multi-face system font sets are not modeled. +- [Writing-line ABI and allocation](../../../core/libs/prx/libSceFont/src/WritingLine.cpp): PS5 layouts, creation forms, memory validation, allocator callbacks and error handling are recovered. The 2208-byte line and 64-byte orders use caller memory. Host registry bookkeeping still allocates separately; memory lifetime and concurrent destruction require caller synchronization. +- [sceFontWritingRefersRenderStepCharacter](../../../core/libs/prx/libSceFont/src/Text.cpp): PS5 traversal and multi-character shaping remain unverified; the host has one character and glyph per step. PS4 11.00 measurements establish an 8-byte pointer output into the writing, null for a null writing, and traversal to null after the current step is exhausted. The pointer ABI agrees with [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1882-L1896) and SharpProspero; shadPS4 and prosper instead treat the output as a struct. The internal letter step remains valid until writing destruction and is rebuilt when a character is returned. +- [sceFontWritingLineGetOrderingSpace](../../../core/libs/prx/libSceFont/src/WritingLine.cpp): PS5 confirms output order, calculations and zeroed outputs on invalid handles. A non-null head output with a null tail output causes an unchecked native write; the host throws instead. With no head output, the tail output remains untouched. - [sceFontCharacterGetSyllableStringState](../../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - the signature `(textCharacter, int* syllableStringState)` follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs). Measured on PS4 firmware 11.00 `libSceFont.sprx` (not on a PS5): the state is 0 for Latin, CJK, Hangul, Arabic, Hebrew, Devanagari and the other scripts tried, and the library sets 1, 3 or 4 (never 2 on real text) for Thai while it creates the string; a null character or a null result returns `SCE_FONT_ERROR_INVALID_PARAMETER`, and a null character writes 0 to a non-null result. This returns 0 for every character outside the Thai block (U+0E00 to U+0E7F), as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1805-L1815) does, and throws for Thai characters, whose cluster states are not modelled. SharpProspero's `SceFontSyllableString` names 0 NonDetection, 1 Starting, 2 Following, 3 Depending and 4 Connecting. PPSA06699 (Hot Shots Golf 2) calls it. - [sceFontCharactersRefersTextCodes, sceFontTextCodesStepNext and sceFontTextCodesStepBack](../../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - the signatures follow the [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L679) IDA type database and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Font/SceFont.Engine.cs#L395-L406), and the 64-byte `FontTextCodes` with the order pointer at 0 and the code at 8 follows SharpProspero's [SceFontTextCodes](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Font/SceFontStructs.cs#L216-L228). The traversal follows the PS4 11.00 `libSceFont.sprx` disassembly posted in [#4050](https://github.com/boykopovar/AnyPS5/pull/4050#issuecomment-6114504202), not a PS5 measurement: the cursor keeps the current, start and term characters at +0x28, +0x30 and +0x38; `Refers` never compares the character with the term and returns null for a clustered or synthetic character; `StepNext` returns null at the term after visiting it; `StepBack` returns null when the current character is the start; both steps skip synthetic characters. Assumed and not covered by the report, which says `StepNext` returns null only at the term: `StepNext` also returns null at the end of the chain (for example with a null term), and `StepBack` at the head of the chain. A null cursor or output returns null as in [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1696-L1740); a null text character throws, as its console result is unknown @@ -18,7 +21,26 @@ ### Functional -- [sceFontOpenFontSet](../../../core/libs/prx/libSceFont/src/SystemFont.cpp) (libSceFont) - the other script files of a set are chosen from its type, not measured on a console: Vietnamese for low-byte bit 0x10, Thai for script bit 0x10, Japanese for 0x04, simplified Chinese for 0x80 (before Japanese with 0x08), Hangul for 0x20 and Arabic for low-byte bit 0x80, as the SDK set names (`..._JP_AR`, `..._GBCJK_TH`) listed by [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/font_internal.cpp) suggest; the file order and the Hangul file name (`SCEPS4Yoongd-Medium.otf`, `SCEPS4Yoongd-Bold.otf`) also come from shadPS4. A character the primary file lacks is taken from the first of them that has it; kerning between two files is 0, and `sceFontGetCharGlyphCode` and `sceFontGetFontGlyphsCount` answer for the primary file only +- [sceFontOpenFontSet](../../../core/libs/prx/libSceFont/src/SystemFont.cpp) (libSceFont) - the other script files of a set and their priority are inferred, not measured on a console: Vietnamese for low-byte bit 0x10, Thai for script bit 0x10, Japanese for 0x04, simplified Chinese for 0x80 (before Japanese with 0x08), Hangul for 0x20 and Arabic for low-byte bit 0x80. The SDK set names (`..._JP_AR`, `..._GBCJK_TH`) and Hangul aliases (`SCEPS4Yoongd-Light.otf`, `SCEPS4Yoongd-Medium.otf`, `SCEPS4Yoongd-Bold.otf`) come from [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/b45b289761ffcb4feed44df5f51b4bb5b5c0dfdc/src/core/libraries/font/font_internal.cpp); the Vietnamese bit rule and the priority used here are our inferences, not its fallback implementation. A character the primary file lacks is taken from the first of them that has it; kerning between two files is 0, and `sceFontGetCharGlyphCode` and `sceFontGetFontGlyphsCount` answer for the primary file only - [sceFontOpenFontSet](../../../core/libs/prx/libSceFont/src/SystemFont.cpp) (libSceFont) - the console file names are those of the PS4 firmware; the names of the PS5-only JG2 sets (`0x1A......`) are unknown, so they use the SSTJpPro files. When a console file is missing, an openly licensed substitute (Noto Sans, Noto Sans Mono, Noto Sans Thai, Noto Sans Arabic, Noto Sans CJK with its Korean face for Hangul) is loaded instead; its metrics differ, so text width, line height and wrapping differ from the console. System font faces are loaded by FreeType in host memory, not in the font memory the title passes to the library: FreeType needs about 1 MiB per CJK face, which the title's font memory is not sized for. - [sceFontGetCharGlyphMetrics, sceFontGetRenderCharGlyphMetrics](../../../core/libs/prx/libSceFont/src/Layout.cpp) (libSceFont) report the vertical metrics the way the [FreeType driver](../../../core/libs/prx/libSceFontFt/src/Driver.cpp) reports them for a rendered glyph, not confirmed on a title: FreeType's vertical advance, and for a face without vertical metrics a bearing of half the horizontal advance left of the horizontal bearing and of half the advance left over by the glyph height. -- [Font writing lines](../../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - `sceFontWritingLineWritesOrder`, `sceFontWritingLineRefersRenderStep`, `sceFontWritingLineGetRenderMetrics`, `sceFontWritingLineGetOrderingSpace` and `sceFontWritingLineClear` follow the PS4 11.00 `libSceFont.sprx` (firmware module, read from its disassembly and compared call for call on random sequences): runs are linked in visual order by embedding level, the space at each spaced join is summed for the line, steps walk the visual order from the left (form `0x12`) or from the right (form `0x11`) and the render metrics grow as steps are consumed. The PS5 library was not measured. Not from that module: form `0x10` is accepted and treated as left to right and a detail with id `0x0FD5` is accepted (the PS4 library rejects both); the memory argument of `sceFontCreateWritingLine` and the rest of the detail are not read (the PS4 library validates its library object there); an unknown writing form, another detail id, a null line pointer on creation, unknown and null handles (except in `sceFontWritingLineRefersRenderStep`, which returns null for a null line) and a null output of `sceFontWritingLineGetRenderMetrics` throw instead of returning the PS4 error codes, which are not confirmed for the PS5; `sceFontDestroyWritingLine` was not compared. Lines are allocated on the host, not in the font memory the title passes to the library: FreeType needs about 1 MiB per CJK face, which the title's font memory is not sized for. +- [sceFontGetFontMetrics](../../../core/libs/prx/libSceFont/src/Layout.cpp) (libSceFont) - the 64-byte output, the form fallbacks (2 and 3 fall back to the `hhea` values when OS/2 is missing or gives an empty range, 4 builds vertical values from `vhea` or OS/2) and the raw weight deltas and slant copied from the style are not measured on a console. For a font set the values are those of its primary file; which file the console reports is not recovered. Closes when: measured on a PS5 +- [sceFontGetFontStyleInformation](../../../core/libs/prx/libSceFont/src/Layout.cpp) (libSceFont) - the 24-byte output (FreeType family and style names, italic and bold flags, OS/2 weight class when it is a multiple of 100 from 100 to 900, width class below 10, and 1 plus a right-to-left `head` direction plus 0x80 for vertical metrics) and the font-set record path are not measured on a console. A face without a `head` table, such as a BDF font, throws, as its console result is unknown. Closes when: measured on a PS5 +- [sceFontGetFontGlyphsOutlineProfile](../../../core/libs/prx/libSceFont/src/Layout.cpp) (libSceFont) - the 16-byte output (the `maxp` maximum contours, points, composite contours and composite points) and `SCE_FONT_ERROR_NO_SUPPORT_FUNCTION` for a `maxp` older than 1.0 are not measured on a console. A handle with a font-set record throws, as the maximum over the record's system font entries is not modelled. A face without a `maxp` table throws, as its console result is unknown. Closes when: measured on a PS5 +- [Font writing lines](../../../core/libs/prx/libSceFont/src/WritingLine.cpp): visual ordering and spacing originated from PS4 measurements and match the supplied PS5 structure and control flow. Exhaustive floating-point equivalence, including NaN payloads and rounding, has not been verified against a running PS5 module. + +- [Font graphics and metric signatures](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp): graphics-device/service creation takes font memory, parameters and a handle output; destruction takes a handle pointer. Attribute selectors and outputs are 32-bit values. Expansion-buffer outputs remain opaque pending field recovery. Protected implementations are unchanged. + +- [Font graphics fill descriptors](../../../core/libs/prx/libSceFont/src/Graphics.cpp): the region, fill-method, fill-plot and fill-rates initialisers and setters, and `sceFontSetGraphicsErrorCallback`, follow firmware 13.60 `libSceFont.sprx` (read from disassembly, not measured on a console): argument checks in the same order, error codes, tags and the bytes written. What the layout selectors, mapping values, region corner flags and fill effects mean is unknown, because the calls that read these descriptors (`sceFontGraphicsSetup*`, `sceFontGraphicsUpdate*`) still throw. The error codes `0x80460086` to `0x80460089` have no public names; the source names them after the argument they reject + +Frame lifecycle, drawing completion, device usage, frame policy, clipping and fill setup/update argument signatures in [Unimplemented.cpp](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) are recovered. Frame and resource layouts remain opaque; their runtime implementation is still unavailable. + +Glyph image rendering uses six pointer arguments and two scalar coordinates, with existing style, surface, metrics and output layouts. Positioning takes a mask and two floats. Control, word-character search and resource release/exchange/render signatures in [Unimplemented.cpp](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) are recovered; control payload types depend on the command and remain unmodeled. + +Canvas, design, surface texture and fill-pattern construction signatures in [Unimplemented.cpp](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) are recovered. Pattern mode/count semantics and the graphics surface descriptor fields still require reconstruction; distinct opaque types preserve the confirmed object categories. + +[Unimplemented.cpp](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) now defines the 32-byte fill method, 64-byte fill plot/rates and 96-byte device usage layouts, with size and field-offset assertions. Uninterpreted descriptor tails remain reserved; pool statistics expose capacity, current usage, available bytes, peak and signed change. + +The [glyph support registration and texture-image fill signatures](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) are recovered from native argument use. The image constructor takes a byte span and four scalar coordinates; the decompiler omits its successful path. `CreateWords` and `CreateGraphicsServiceWithEdition` consume the output pointer in the fourth general-purpose argument slot but do not read the third slot, whose source type is not established. + +- [sceFontCreateGraphicsServiceWithEdition and sceFontCreateWords](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) take their handle output in the fourth integer argument slot. The third slot is not read by the supplied module and is represented as an opaque 64-bit value; its declared source type remains unknown. diff --git a/docs/dev/debt/libSceFontFt.md b/docs/dev/debt/libSceFontFt.md index cd35391b21..c5c609e43f 100644 --- a/docs/dev/debt/libSceFontFt.md +++ b/docs/dev/debt/libSceFontFt.md @@ -1,5 +1,10 @@ # libSceFontFt technical debt +### Functional + +- [Font format support](../../../core/libs/prx/libSceFontFt/Export.cpp): format queries now take the font library, validate its magic and lock it while checking its actual FreeType modules. The host registers its default modules at creation rather than enabling formats individually, so format-selection state and guest allocator failures during format registration are not reproduced. Unsupported foreign drivers throw. +- [TrueType GX support](../../../core/libs/prx/libSceFontFt/Export.cpp): the GX query checks the bundled FreeType TrueType module, whose configuration enables GX variation support. The console's response for a FreeType build without GX variation support has not been measured. + ### Unknown function info -- [sceFontFtSupportBdf, sceFontFtSupportCid, sceFontFtSupportOpenType, sceFontFtSupportOpenTypeOtf, sceFontFtSupportOpenTypeTtf, sceFontFtSupportPcf, sceFontFtSupportPfr, sceFontFtSupportTrueType, sceFontFtSupportType1, sceFontFtSupportType42 and sceFontFtSupportWinFonts](../../../core/libs/prx/libSceFontFt/Export.cpp) (libSceFontFt) are declared without parameters, signatures unknown; [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/7da7a8e1f98eca9d8f01f84d20681f8faa0bd8fd/src/core/libraries/font/fontft.cpp), another emulator, declares them the same way and only logs. They are assumed to enable one font format in the FontFt library, which here always registers FreeType's default modules, so each call returns 0 when the host FreeType has the module (`bdf`, `t1cid`, `truetype`, `cff`, `pcf`, `pfr`, `type1`, `type42`, `winfonts`; OpenType needs `cff` and `truetype`) and throws when it does not. The mapping of Sony's format names to FreeType modules, the return value and the error codes of the console are unverified, and no title is known to call them. +- [Alias and glyph selection exports](../../../core/libs/prx/libSceFontFt/Export.cpp): alias initialization/termination take a handle pointer; alias registration takes a string and font ID or path. Glyph selection takes a 32-bit selector and returns a table pointer whose layout remains unresolved. Their protected implementations are unchanged. diff --git a/docs/dev/debt/libSceFreeType.md b/docs/dev/debt/libSceFreeType.md index 6d4f98438d..205ee0df69 100644 --- a/docs/dev/debt/libSceFreeType.md +++ b/docs/dev/debt/libSceFreeType.md @@ -1,15 +1,52 @@ # libSceFreeType technical debt ### Unknown function info +- [FT_New_Size, FT_Activate_Size and FT_Done_Size](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures, errors and generic-finalizer ordering are unverified; the LP64 guest adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Private size fields are bridge-owned. Sizes created outside the bridge throw when selected. -- [FT_Select_Charmap, FT_Set_Charmap, FT_Get_First_Char and FT_Get_Next_Char](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures and errors are unverified; the LP64 guest adapter follows the bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Character iteration uses the low 32 bits of the input code, as that version does internally, and updates the guest's selected charmap pointer without refreshing unrelated glyph or outline data. Guest charmap fields are treated as read-only copies of the native records. +- [FT_Reference_Face](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signature, reference-counter limits and finalizer behavior are unverified; the LP64 guest adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Counter overflow and reentrant finalization have not been measured on the console. + +- [FT_Get_Sfnt_Name_Count, FT_Get_Sfnt_Name](../../../core/libs/prx/libSceFreeType/src/Names.cpp) (libSceFreeType) - console signatures, record layout and errors are unverified; the adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftsnames.c) and assumes the public LP64 `FT_SfntName` layout (24 bytes, string pointer at 8, byte length at 16). Strings retain their original encoding, borrow native face storage and remain valid only while the face is alive + +- [FT_Select_Charmap, FT_Set_Charmap, FT_Get_First_Char, FT_Get_Next_Char](../../../core/libs/prx/libSceFreeType/Export.cpp), [FT_Get_Glyph_Name, FT_Get_Name_Index, FT_Get_Postscript_Name](../../../core/libs/prx/libSceFreeType/src/Names.cpp) (libSceFreeType) - console signatures, naming rules and errors are unverified; the LP64 adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Iteration uses the low 32 input bits and updates only the selected guest charmap; charmap fields are read-only copies. Naming preserves LP64 invalid-index errors on Windows. PostScript names borrow native face storage and remain valid only while the face is alive + +- [FT_Cos, FT_Sin, FT_Tan, FT_Vector_Unit and FT_Angle_Diff](../../../core/libs/prx/libSceFreeType/src/Trigonometry.cpp) (libSceFreeType) - console ABI/version and CORDIC equivalence are unverified; the LP64 adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/fttrigon.c). Tangent retains the 64-bit quotient near poles instead of Windows FT_Long truncation. Angle differences whose signed 64-bit subtraction overflows throw; the bundled source's subtraction is undefined there. + +- [FT_Outline_Copy](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - compatibility with the console FreeType build and its errors is unverified. The LP64 guest adapter follows the unsigned counts and copy contract of bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c). + +- [FT_Outline_Decompose](../../../core/libs/prx/libSceFreeType/src/OutlineDecompose.cpp) (libSceFreeType) - console callback ABI and FreeType-version equivalence are unverified; the LP64 adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c). Nonempty outlines with shifts outside 0-62 or signed 64-bit overflow in scaling, translation or midpoint sums throw; native behavior for that arithmetic is undefined. Callers must supply valid point/tag/contour arrays and the callbacks their contours use. - [FT_Init_FreeType, FT_New_Face, FT_Set_Pixel_Sizes, FT_Get_Char_Index, FT_Load_Glyph, FT_Render_Glyph](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - the PS4 library is taken to be FreeType 2 built for LP64: the face, size, charmap, glyph slot, bitmap and outline layouts follow the public headers of the bundled FreeType 2.13.3 with a 64-bit `long`, which matches the offsets the OpenOrbis samples read (`face->glyph` at 0x98, `slot->advance` at 0x80, `slot->bitmap` at 0x98, `slot->bitmap_left` at 0xc0). Which FreeType version and options the console library is built with is unknown, so the behaviour and error codes are those of FreeType 2.13.3. The OpenOrbis font, keyboard, threading, system and SDL2 samples import these six functions +- [FT_Bitmap_New, FT_Bitmap_Copy, FT_Bitmap_Convert, FT_Bitmap_Done](../../../core/libs/prx/libSceFreeType/src/Bitmap.cpp) (libSceFreeType) - console behaviour and error codes are unmeasured; these follow bundled FreeType 2.13.3. Copy and Convert throw when source and target share a non-null buffer; Convert also throws when they are the same object. Other overlapping storage remains forbidden by the native API contract + +- [FT_Request_Size](../../../core/libs/prx/libSceFreeType/src/SizeRequest.cpp) (libSceFreeType) - console signature, request layout and errors are unverified; the guest request follows the LP64 public headers of the bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/include/freetype/freetype.h). Host overflow behavior remains that of the bundled build. On Windows, native `FT_Long` is 32 bits; valid requests whose width or height exceeds that range throw before changing the size. + +- [FT_Select_Size](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures and errors are unverified; the LP64 guest adapter follows the bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Bitmap-strike selection and size metrics are checked against native FreeType with generated BDF fonts; other font drivers have not been exercised through this entry point. + - [FT_New_Memory_Face, FT_Done_Face and FT_Done_FreeType](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures, errors and generic-finalizer ordering are unverified; they follow the bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/include/freetype/freetype.h) with LP64 guest arguments. Caller-owned font bytes must remain valid until the face is freed. +- [FT_Outline_Check, FT_Outline_Get_CBox](../../../core/libs/prx/libSceFreeType/src/Outline.cpp) (libSceFreeType) - console signatures, count signedness and errors are unverified; the adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c), including its unsigned 16-bit counts and contour endpoints. Control-box coordinates use the existing guest LP64 layout and retain their full 64-bit range on Windows; contour checking does not validate point tags + +- [FT_Get_Charmap_Index, FT_Get_CMap_Language_ID, FT_Get_CMap_Format](../../../core/libs/prx/libSceFreeType/src/Charmap.cpp) (libSceFreeType) - console signatures and return values are unverified; the adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c) and the existing guest LP64 model. Charmaps must be the face's original read-only guest records; copied or otherwise unregistered records with a non-null face throw instead of reading private native cmap fields from guest memory + +- [FT_Outline_Reverse, FT_Outline_Translate](../../../core/libs/prx/libSceFreeType/src/Outline.cpp) (libSceFreeType) - console signatures, count signedness and overflow behavior are unverified; the adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) with LP64 coordinates. Translation uses 64-bit modular addition to match that build's `ADD_LONG`; reversal preserves each contour's first point and toggles the reverse-fill flag. Coordinates outside the host `FT_Pos` range still throw if the modified outline is later rendered on Windows + +- [FT_Get_X11_Font_Format, FT_Get_FSType_Flags](../../../core/libs/prx/libSceFreeType/src/FaceInfo.cpp) (libSceFreeType) - console signatures, format labels and embedding-flag behavior are unverified; the adapter follows bundled FreeType 2.13.3's [format](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftfntfmt.c) and [FSType](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftfstype.c) services. Format strings borrow native driver storage; the console's string lifetime has not been measured +- [FT_Get_Kerning](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - the name and NID `LPtlB241JSg` occur in the [PS5 3.20 libSceFreeTypeOt inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c), but the console signature and kerning results are unverified. The adapter assumes the existing LP64 guest layout and follows bundled FreeType 2.13.3 for scaling, rounding and errors + ### Functional +- [FT_Set_Transform](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console ABI, transform arithmetic and FreeType-version equivalence are unverified. The name and NID `bcM5xfX2SeA` come from the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c); the 32-byte LP64 matrix, 16-byte vector, null defaults and glyph behavior follow bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Input components outside the native `FT_Long` range throw without changing the transform; on Windows this rejects values outside signed 32 bits. Native arithmetic limits remain those of the host library. + - [libSceFreeType](../../../core/libs/prx/libSceFreeType/src/GuestObjects.cpp) (libSceFreeType) - private fields (`driver`, `memory`, `stream`, `sizes_list`, `extensions`) and `subglyphs` stay null. Face, size and glyph-slot records are copies. Only edits to outline points and flags are written back before `FT_Render_Glyph`; replacing arrays or exceeding the host `FT_Pos` range throws. `FT_New_Face` reads the whole file into memory and throws for files of 2 GiB or more on Windows. On macOS, native FreeType symbols remain exported and could resolve guest imports without adaptation; Windows and Linux hide them with `--exclude-libs`. +- [FT_List_Add, FT_List_Insert, FT_List_Find, FT_List_Remove, FT_List_Up, FT_List_Iterate and FT_List_Finalize](../../../core/libs/prx/libSceFreeType/src/List.cpp) - console signatures, callback ordering and error compatibility are unverified. Names and NIDs follow the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c); the pointer-only 16-byte list, 24-byte node and operations follow bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftutil.c). The 32-byte allocator record uses LP64 guest callbacks, and finalization calls its guest `free` callback for each node. Native list-membership and callback preconditions still apply; malformed lists and callbacks that invalidate a later node are not validated. + - [FT_New_Memory_Face](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - on Windows, the host FT_Long is 32 bits, so buffer sizes of 2 GiB or more or below -2 GiB throw before the font buffer is read + +- [FT_Outline_Get_Orientation](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console ABI/version and large-outline equivalence are unverified. The adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) with the existing unsigned guest counts and an LP64 area accumulator, including on Windows. +- [FT_Set_Char_Size](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console ABI, FreeType version and error compatibility are unverified. The name and NID `xuY99Qcb108` are in the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c); the LP64 signature and behavior follow bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c), not a console measurement. After applying its defaults and minimum-size clamp, requests whose `dimension * dpi + 36` exceeds the host's signed `FT_Long` range throw before the native call. This is a 32-bit limit on Windows; larger console requests remain unsupported. + +- [FT_Get_Gasp](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console FreeType version and query-result compatibility are unverified. The guest face adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftgasp.c) and its existing LP64 face bridge. + +- [FT_Get_CID_Registry_Ordering_Supplement, FT_Get_CID_Is_Internally_CID_Keyed and FT_Get_CID_From_Glyph_Index](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console ABI/version and CID metadata equivalence are unverified. The adapters follow bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftcid.c) through the existing face bridge; returned registry/ordering strings have the native face's lifetime. diff --git a/docs/dev/debt/libSceFreeTypeOt.md b/docs/dev/debt/libSceFreeTypeOt.md new file mode 100644 index 0000000000..6e93bdf35f --- /dev/null +++ b/docs/dev/debt/libSceFreeTypeOt.md @@ -0,0 +1,9 @@ +# libSceFreeTypeOt technical debt + +### Unknown function info + +- [FT_Outline_GetInsideBorder, FT_Outline_GetOutsideBorder](../../../core/libs/prx/libSceFreeTypeOt/CMakeLists.txt) (libSceFreeTypeOl, libSceFreeTypeOt) - console prototypes, enum/layout equivalence, FreeType configuration and module/library versions are unverified. The existing LP64 outline layout and unsigned 16-bit counts follow bundled FreeType 2.13.3; border selection follows [ftstroke.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c), with the shifted polygon area from [ftoutln.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) accumulated in 64 bits. Null positive-count arrays and out-of-range or non-increasing contour endpoints throw when reached; their console behavior is unknown. Tags and flags are not read, and input arrays remain caller-owned. + +- The 140 `sceFreeTypeOtUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. + +- The FT_Outline_Get_Orientation, FT_Request_Size, FT_Sin, FT_Tan, FT_Vector_Unit, FT_New_Size, FT_Outline_Copy and FT_Outline_Decompose NIDs in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are identified, but their provider-specific prototypes and behavior remain unverified. Their named helpers retain the explicit inventory aliases and throw; the separate libSceFreeType implementations do not supply this provider. diff --git a/docs/dev/debt/libSceFsInternalForVsh.md b/docs/dev/debt/libSceFsInternalForVsh.md index 92591afa7b..4e242ae883 100644 --- a/docs/dev/debt/libSceFsInternalForVsh.md +++ b/docs/dev/debt/libSceFsInternalForVsh.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceBdSchedCancelBackgroundCopyRequest, sceBdSchedCancelPrioritizedBackgroundCopyRequest, sceBdSchedConfigure, sceBdSchedConfigure2, sceBdSchedDeconfigure, sceBdSchedGetBackgroundCopyRequest, sceBdSchedGetExtentMap, sceBdSchedGetPrioritizedBackgroundCopyRequest, sceBdSchedGetState, sceBdSchedSetBackgroundCopyRequest, sceBdSchedSetDiscIdentity, sceBdSchedSetPrioritizedBackgroundCopyRequest, sceBdSchedStartGame, sceBdSchedStartRipping, sceBdSchedStopScheduling, sceFsBfsCheckCleanFlag, sceFsBindApplication, sceFsBindPackages, sceFsCheckFormat, sceFsCheckSingleUser, sceFsCreateExfatPartition, sceFsCreatePfsSaveDataImage, sceFsCreatePfsTrophyDataImage, sceFsCreatePprPfsTrophyDataImage, sceFsDeviceAlignedPread, sceFsDeviceAlignedPwrite, sceFsDevpfsA53IocGetBuildMetadataStatus, sceFsDevpfsCancelWaitEvent, sceFsDevpfsCreateForVSH, sceFsDevpfsDeleteForVSH, sceFsDevpfsDeregisterEvent, sceFsDevpfsEventComplete, sceFsDevpfsEventResponseComplete, sceFsDevpfsFetchMetaEntry, sceFsDevpfsFsck, sceFsDevpfsInitFetchMetaEntryCtx, sceFsDevpfsMountForFSMP, sceFsDevpfsRegisterEvent, sceFsDevpfsWaitEvent, sceFsDrfsRegisterSocket, sceFsDrfsResumeSocket, sceFsErriCtl, sceFsErriGetState, sceFsExfatMkfsExternalHdd, sceFsExfatfsMkfs, sceFsExtUSBSchedDestroy, sceFsExtUSBSchedInsert, sceFsExtUSBSchedInsertForHDD, sceFsExternalStorageDeconfigure, sceFsExternalStorageGetRawDevice, sceFsExternalStorageInitialize, sceFsExternalStorageVerifyMetadata, sceFsGetAttribute, sceFsGetClusterStatistics, sceFsGetDeviceSectorsize, sceFsGetFsMetadata, sceFsGetFsMetadataWithProgress, sceFsGetSmrInfo, sceFsGschedCtl, sceFsISSchedConfigCurrentThread, sceFsISSchedConfigDescriptor, sceFsISSchedConfigThread, sceFsISSchedMapStat, sceFsISSchedResultToPriority, sceFsISSchedResultToSlot, sceFsISSchedStatCurrentThread, sceFsISSchedStatDescriptor, sceFsISSchedStatThread, sceFsISSchedUnmapStat, sceFsInitCreatePfsSaveDataOpt, sceFsInitCreatePfsTrophyDataOpt, sceFsInitMountDownloadDataOpt, sceFsInitMountGamePkgOpt, sceFsInitMountLwfsCompatOpt, sceFsInitMountLwfsOpt, sceFsInitMountPprPkgOpt, sceFsInitMountSaveDataOpt, sceFsInitMountTrophyDataOpt, sceFsInitUmountDownloadDataOpt, sceFsInitUmountGamePkgOpt, sceFsInitUmountLwfsCompatOpt, sceFsInitUmountLwfsOpt, sceFsInitUmountPprPkgOpt, sceFsInitUmountSaveDataOpt, sceFsInitUmountTrophyDataOpt, sceFsInitWorkspaceFileMetadataParam, sceFsLvdAttach, sceFsLvdAttach2, sceFsLvdAttachPartiallyCryptedDownloadData, sceFsLvdAttachSingleDefaultImage, sceFsLvdAttachSingleImage, sceFsLvdDetach, sceFsLvdDisableLayer, sceFsLvdEnableLayer, sceFsLvdSetDiscIdentity, sceFsLvdWaitResumed, sceFsMlfsMount, sceFsMlfsMountWithFlags, sceFsMlfsUmount, sceFsMountDownloadData, sceFsMountGamePkg, sceFsMountLwfs, sceFsMountLwfsCompat, sceFsMountPartiallyCryptedDownloadData, sceFsMountPprPkg, sceFsMountSaveData, sceFsMountTrophyData, sceFsNsfsAddOverlayToNamespace, sceFsNsfsMountWithFlags, sceFsNsfsSetGlobalExclude, sceFsNsfsUmount, sceFsPartitionTrim, sceFsReserveCluster, sceFsSetAttribute, sceFsSetClusterCacheSize, sceFsSetFileReservedTotal, sceFsSetSystemReserveSize, sceFsSetWorkspaceFileHash, sceFsStatWorkspace, sceFsSysStorageGetStatus, sceFsTransactionCancel, sceFsTransactionSaveDataInitMountOpt, sceFsTransactionSaveDataMount, sceFsTransactionStart, sceFsTrophyImageError, sceFsUfsAllocateAdditionalContents, sceFsUfsAllocateAligned, sceFsUfsAllocateDownloadData, sceFsUfsAllocateGameImage, sceFsUfsAllocatePatchImage, sceFsUfsAllocateSaveData, sceFsUfsAllocateTrophyData, sceFsUfsCheckCleanFlag, sceFsUfsCheckFixedCylinderGroupSize, sceFsUfsCollectTelemetry, sceFsUfsCollectTelemetry2, sceFsUfsDirectWrite, sceFsUfsEstimateZoneCapacityForUser, sceFsUfsExtendFile, sceFsUfsFsck, sceFsUfsFsckWithMM, sceFsUfsGetExtentMap, sceFsUfsGrowLwfs, sceFsUfsGrowfs, sceFsUfsHideBlocksFromUser, sceFsUfsInitalizeSparseFile, sceFsUfsLargewriteFsck, sceFsUfsLargewriteFsckWithMM, sceFsUfsMkLwfs, sceFsUfsMkfs, sceFsUfsMkfsForExternalHDD, sceFsUfsMkfsForSystem, sceFsUfsMkfsWithFixedCylinderGroupSize, sceFsUfsReallocateSparseFile, sceFsUfsRecryptBlocks, sceFsUfsRecryptGetState, sceFsUfsRecryptIsUnfinished, sceFsUfsRecryptSyncMetadata, sceFsUfsSeekHoleInFile, sceFsUfsStopOperation, sceFsUmountDownloadData, sceFsUmountGamePkg, sceFsUmountLwfs, sceFsUmountLwfsCompat, sceFsUmountSaveData, sceFsUmountTrophyData, sceFsUmountWorkspace, sceFsUnbindApplication, sceFsUnbindPackages, sceFsWsCreate, sceFsWsDelete, sceFsWsFetchFileMetadata, sceFsWsFsck, sceFsWsGetStatus, sceFsWsMount and sceFsWsUmount](../../../core/libs/prx/libSceFsInternalForVsh/Export.cpp) (libSceFsInternalForVsh) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFsInternalForVsh.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceBdSchedCancelBackgroundCopyRequest, sceBdSchedCancelPrioritizedBackgroundCopyRequest, sceBdSchedConfigure, sceBdSchedConfigure2, sceBdSchedDeconfigure, sceBdSchedGetBackgroundCopyRequest, sceBdSchedGetExtentMap, sceBdSchedGetPrioritizedBackgroundCopyRequest, sceBdSchedGetState, sceBdSchedSetBackgroundCopyRequest, sceBdSchedSetDiscIdentity, sceBdSchedSetPrioritizedBackgroundCopyRequest, sceBdSchedStartGame, sceBdSchedStartRipping, sceBdSchedStopScheduling, sceFsBfsCheckCleanFlag, sceFsBindApplication, sceFsBindPackages, sceFsCheckFormat, sceFsCheckSingleUser, sceFsCreateExfatPartition, sceFsCreatePfsSaveDataImage, sceFsCreatePfsTrophyDataImage, sceFsCreatePprPfsTrophyDataImage, sceFsDeviceAlignedPread, sceFsDeviceAlignedPwrite, sceFsDevpfsA53IocGetBuildMetadataStatus, sceFsDevpfsCancelWaitEvent, sceFsDevpfsCreateForVSH, sceFsDevpfsDeleteForVSH, sceFsDevpfsDeregisterEvent, sceFsDevpfsEventComplete, sceFsDevpfsEventResponseComplete, sceFsDevpfsFetchMetaEntry, sceFsDevpfsFsck, sceFsDevpfsInitFetchMetaEntryCtx, sceFsDevpfsMountForFSMP, sceFsDevpfsRegisterEvent, sceFsDevpfsWaitEvent, sceFsDrfsRegisterSocket, sceFsDrfsResumeSocket, sceFsExfatMkfsExternalHdd, sceFsExfatfsMkfs, sceFsExtUSBSchedDestroy, sceFsExtUSBSchedInsert, sceFsExtUSBSchedInsertForHDD, sceFsExternalStorageDeconfigure, sceFsExternalStorageGetRawDevice, sceFsExternalStorageInitialize, sceFsExternalStorageVerifyMetadata, sceFsGetAttribute, sceFsGetClusterStatistics, sceFsGetDeviceSectorsize, sceFsGetFsMetadata, sceFsGetFsMetadataWithProgress, sceFsGetSmrInfo, sceFsGschedCtl, sceFsISSchedConfigCurrentThread, sceFsISSchedConfigDescriptor, sceFsISSchedConfigThread, sceFsISSchedMapStat, sceFsISSchedResultToPriority, sceFsISSchedResultToSlot, sceFsISSchedStatCurrentThread, sceFsISSchedStatDescriptor, sceFsISSchedStatThread, sceFsISSchedUnmapStat, sceFsInitCreatePfsSaveDataOpt, sceFsInitCreatePfsTrophyDataOpt, sceFsInitMountDownloadDataOpt, sceFsInitMountGamePkgOpt, sceFsInitMountLwfsCompatOpt, sceFsInitMountLwfsOpt, sceFsInitMountPprPkgOpt, sceFsInitMountSaveDataOpt, sceFsInitMountTrophyDataOpt, sceFsInitUmountDownloadDataOpt, sceFsInitUmountGamePkgOpt, sceFsInitUmountLwfsCompatOpt, sceFsInitUmountLwfsOpt, sceFsInitUmountPprPkgOpt, sceFsInitUmountSaveDataOpt, sceFsInitUmountTrophyDataOpt, sceFsInitWorkspaceFileMetadataParam, sceFsLvdAttach, sceFsLvdAttach2, sceFsLvdAttachPartiallyCryptedDownloadData, sceFsLvdAttachSingleDefaultImage, sceFsLvdAttachSingleImage, sceFsLvdDetach, sceFsLvdDisableLayer, sceFsLvdEnableLayer, sceFsLvdSetDiscIdentity, sceFsLvdWaitResumed, sceFsMlfsMount, sceFsMlfsMountWithFlags, sceFsMlfsUmount, sceFsMountDownloadData, sceFsMountGamePkg, sceFsMountLwfs, sceFsMountLwfsCompat, sceFsMountPartiallyCryptedDownloadData, sceFsMountPprPkg, sceFsMountSaveData, sceFsMountTrophyData, sceFsNsfsAddOverlayToNamespace, sceFsNsfsMountWithFlags, sceFsNsfsSetGlobalExclude, sceFsNsfsUmount, sceFsPartitionTrim, sceFsReserveCluster, sceFsSetAttribute, sceFsSetClusterCacheSize, sceFsSetFileReservedTotal, sceFsSetSystemReserveSize, sceFsSetWorkspaceFileHash, sceFsStatWorkspace, sceFsSysStorageGetStatus, sceFsTransactionCancel, sceFsTransactionSaveDataInitMountOpt, sceFsTransactionSaveDataMount, sceFsTransactionStart, sceFsTrophyImageError, sceFsUfsAllocateAdditionalContents, sceFsUfsAllocateAligned, sceFsUfsAllocateDownloadData, sceFsUfsAllocateGameImage, sceFsUfsAllocatePatchImage, sceFsUfsAllocateSaveData, sceFsUfsAllocateTrophyData, sceFsUfsCheckCleanFlag, sceFsUfsCheckFixedCylinderGroupSize, sceFsUfsCollectTelemetry, sceFsUfsCollectTelemetry2, sceFsUfsDirectWrite, sceFsUfsEstimateZoneCapacityForUser, sceFsUfsExtendFile, sceFsUfsFsck, sceFsUfsFsckWithMM, sceFsUfsGetExtentMap, sceFsUfsGrowLwfs, sceFsUfsGrowfs, sceFsUfsHideBlocksFromUser, sceFsUfsInitalizeSparseFile, sceFsUfsLargewriteFsck, sceFsUfsLargewriteFsckWithMM, sceFsUfsMkLwfs, sceFsUfsMkfs, sceFsUfsMkfsForExternalHDD, sceFsUfsMkfsForSystem, sceFsUfsMkfsWithFixedCylinderGroupSize, sceFsUfsReallocateSparseFile, sceFsUfsRecryptBlocks, sceFsUfsRecryptGetState, sceFsUfsRecryptIsUnfinished, sceFsUfsRecryptSyncMetadata, sceFsUfsSeekHoleInFile, sceFsUfsStopOperation, sceFsUmountDownloadData, sceFsUmountGamePkg, sceFsUmountLwfs, sceFsUmountLwfsCompat, sceFsUmountSaveData, sceFsUmountTrophyData, sceFsUmountWorkspace, sceFsUnbindApplication, sceFsUnbindPackages, sceFsWsCreate, sceFsWsDelete, sceFsWsFetchFileMetadata, sceFsWsFsck, sceFsWsGetStatus, sceFsWsMount and sceFsWsUmount](../../../core/libs/prx/libSceFsInternalForVsh/Export.cpp) (libSceFsInternalForVsh) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFsInternalForVsh.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceGameCustomDataDialog.md b/docs/dev/debt/libSceGameCustomDataDialog.md new file mode 100644 index 0000000000..09743a0781 --- /dev/null +++ b/docs/dev/debt/libSceGameCustomDataDialog.md @@ -0,0 +1,9 @@ +# libSceGameCustomDataDialog technical debt + +### Silent stubs + +- [libSceGameCustomDataDialog](../../../core/libs/prx/libSceGameCustomDataDialog/Export.cpp) - the game custom data dialog (`sceGameCustomDataDialogOpenA` and `sceGameCustomDataDialogGetResultA`) runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as there is no PSN game custom data. The signatures are from [Orbital's type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json); the result layout beyond the leading `int32_t` result code is not known, so nothing else is written. + +### Unknown function info + +- [sceGameCustomDataDialogGetResult and sceGameCustomDataDialogOpen](../../../core/libs/prx/libSceGameCustomDataDialog/Export.cpp) (libSceGameCustomDataDialog) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceGameCustomDataDialog.c), but Orbital has prototypes only for the `A` variants and no credible prototypes were found for these. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceGameLiveStreaming.md b/docs/dev/debt/libSceGameLiveStreaming.md index ae477a6346..b52bed6644 100644 --- a/docs/dev/debt/libSceGameLiveStreaming.md +++ b/docs/dev/debt/libSceGameLiveStreaming.md @@ -1,9 +1,14 @@ # libSceGameLiveStreaming technical debt +### Functional + +- [sceGameLiveStreamingEnableLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - a call outside `sceGameLiveStreamingInitialize` and `sceGameLiveStreamingTerminate` throws, as the module's error for it is not confirmed (`sceGameLiveStreamingTerminate` and `sceGameLiveStreamingGetCurrentStatus2` return `0x80A00004`; [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/ps4_libscegamelivestreaming.pas#L18) does not return its `SCE_GAME_LIVE_STREAMING_ERROR_NOT_INITIALIZED` from this call). Otherwise both values are accepted and change nothing, as no broadcast is ever on air. The signature, `int sceGameLiveStreamingEnableLiveStreaming(const bool)`, follows [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L810) and [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/ps4_libscegamelivestreaming.pas#L111) +- [libSceGameLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - native initialization requires exactly 0x4000 heap bytes before checking repeated initialization. Host lifecycle and pointer errors now match the wrapper, but service registration, allocation failures and asynchronous service readiness are not modelled. The host becomes ready immediately after initialization. +- [sceGameLiveStreamingGetCurrentStatus2](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - the supplied module confirms the 72-byte layout and inactive status (user -1, remaining bytes zero). Live service counters, active-user changes and broadcast-resolution conversion are unimplemented. +- [sceGameLiveStreamingGetProgramInfo](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - the PS5 12.00 module clears the 0x134C-byte program info, asks the system service for its first 0xFD4 bytes and returns `0x80A00002` for a null pointer and `0x80A00004` before initialization. With nothing broadcast here the info stays zero, which assumes the service reports an empty program when nothing is on air; the field layout is unknown + ### Unknown function info -- [sceGameLiveStreamingEnableLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - imported by PS4 titles, which do not load without it, and added without an implementation: declared without parameters, every call throws -- [sceGameLiveStreamingGetCurrentStatus2](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - the status layout (user id, on-air flag, spectator, text and command message counts, broadcast resolution, 48 reserved bytes) is taken from shadPS4's `OrbisGameLiveStreamingStatus2`; the call reports that no stream is on air, with an invalid user id - [sceGameLiveStreamingScreenInitialize](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingScreenInitializeSeparateModeParameter](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingScreenTerminate](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature @@ -13,7 +18,7 @@ - [sceGameLiveStreamingScreenSetMode](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingPermitLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingSetMaxBitrate](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingGetCurrentStatus](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature +- [sceGameLiveStreamingGetCurrentStatus](../../../core/libs/prx/libSceGameLiveStreaming/include/Api.hpp) - still throws; the meaning, division and SDK types of the first 12 bytes remain unknown. In the supplied `libSceGameLiveStreaming.sprx`, export `CoPMx369EqM` at 0x1810 takes one output pointer, clears 72 bytes, obtains service command 9's status, copies its first 12 bytes and clears bytes 0x0C..0x47. `GameLiveStreamingStatus` exposes this byte storage without assuming the `GetCurrentStatus2` field layout or SDK alignment. The decompiler's extra SIMD parameter is not an input: the assembly initializes the register before use. - [sceGameLiveStreamingEnableSocialFeedback](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingGetSocialFeedbackMessagesCount](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingGetSocialFeedbackMessages](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature diff --git a/docs/dev/debt/libSceGameUpdate.md b/docs/dev/debt/libSceGameUpdate.md index c67a4a14a3..75626c8e75 100644 --- a/docs/dev/debt/libSceGameUpdate.md +++ b/docs/dev/debt/libSceGameUpdate.md @@ -1,6 +1,11 @@ # libSceGameUpdate technical debt -### Unknown function info +### Silent stubs -- [sceGameUpdateGetAddcontLatestVersion](../../../core/libs/prx/libSceGameUpdate/Export.cpp) (libSceGameUpdate) - a null `entitlement_label` returns `0x80412803` as in prosper; KytyPS5 accepts it instead, and neither is confirmed on a console. Bytes after `found` are zeroed as in KytyPS5; prosper leaves them untouched -- [sceGameUpdateCreateRequest, sceGameUpdateCheck, sceGameUpdateAbortRequest, sceGameUpdateDeleteRequest](../../../core/libs/prx/libSceGameUpdate/Export.cpp) (libSceGameUpdate) - an initialized `sceGameUpdateCreateRequest` returns `0x80D50001` and creates no request, so `sceGameUpdateCheck`, `sceGameUpdateAbortRequest` and `sceGameUpdateDeleteRequest` answer `0x80412805` for every id that passes their argument checks. The PS5 3.20 module, as read by prosper, keeps 8 requests with ids from `0x20000000`, and answers "no update" when its patch-check service reports `0x80412883`; what that service returns on an offline console is unknown. Closes when: measured on a PS5 +- [sceGameUpdateCreateRequest, sceGameUpdateCheck](../../../core/libs/prx/libSceGameUpdate/Export.cpp) (libSceGameUpdate) - requests are kept in the module's 8 slots with ids from `0x20000000` (a ninth returns `0x80412806`, an aborted request's Check `0x80412807`), as [prosper](https://github.com/mattias800/prosper) reads the PS5 3.20 `libSceGameUpdate.sprx`. Check never asks a patch service: it always answers no update (`found` and the byte after it cleared, the rest of the result untouched, return 0), which is what the module returns when its service reports `0x80412883`; KytyPS5 also answers no update. What the service returns on an offline console is not measured. The answer only decides whether a title offers a patch. PPSA22408 aborted on the throw in sceGameUpdateCreateRequest at its title screen. Closes when: measured on a PS5 + +### Functional + +- [libSceGameUpdate](../../../core/libs/prx/libSceGameUpdate/Export.cpp): initialization tracks host state but does not connect the patch service. Entitlement version lookup throws. +- [sceGameUpdateCheck](../../../core/libs/prx/libSceGameUpdate/Export.cpp): option zero is supported for argument validation. The original additionally permits option one when a kernel platform predicate succeeds; that platform mode is not modeled. +- [sceGameUpdateGetAddcontLatestVersion](../../../core/libs/prx/libSceGameUpdate/Export.cpp): the supplied module confirms a 20-byte entitlement-label input and a 48-byte service output. Null pointers are rejected before initialization is checked. There is no local output-size validation or blanket clearing of the output; the host leaves it untouched when the missing service causes an exception. diff --git a/docs/dev/debt/libSceGnmDriverForNeoMode.md b/docs/dev/debt/libSceGnmDriverForNeoMode.md index 726f106af0..507eeee80f 100644 --- a/docs/dev/debt/libSceGnmDriverForNeoMode.md +++ b/docs/dev/debt/libSceGnmDriverForNeoMode.md @@ -2,6 +2,14 @@ ### Unknown function info -- [libSceGnmDriverForNeoMode pending exports](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) - 118 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceGnmDriverForNeoMode.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceGnmDriverForNeoMode pending exports](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) - 34 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceGnmDriverForNeoMode.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. -- [libSceGnmDriverForNeoMode pending exports](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) - 45 named callable exports were identified in the PS5 3.20 export table and cross-checked against firmware 13.60. Signatures and behavior remain unverified; declarations use the unknown-signature convention and throw through `NotImplemented_nid_no_patch`. Evidence: [3.20 export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceGnmDriverForNeoMode.c). +### Functional + +- [retail-only answers](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) (libSceGnmDriverForNeoMode) - the debug resets, the resource and owner registration calls, the SDMA calls and the SQ counter calls return `0x8EEE00FF`, the five `sceGnmDriverInternalRetrieveGnmInterface*` calls return `0x80000000`, `sceGnmGetDbgGcHandle` returns -1, `sceGnmDriverTraceInProgress` and `sceGnmValidateOnSubmitEnabled` return 0 and the other validation calls return `0x80D13FFF`. These are the values [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/gnmdriver/gnmdriver.cpp) returns for calls it marks as not available in retail firmware; they are not measured on a console, and the arguments are not read +- [default state packets](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) (libSceGnmDriverForNeoMode) - `sceGnmDrawInitDefaultHardwareState`, `sceGnmDrawInitDefaultHardwareState200`, `sceGnmDrawInitDefaultHardwareState350`, `sceGnmDrawInitToDefaultContextState` and `sceGnmDrawInitToDefaultContextState400` write the sequences in [InitSequences.hpp](../../../core/libs/prx/libSceGnmDriverForNeoMode/include/InitSequences.hpp), taken from [shadPS4's gnmdriver_init.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/gnmdriver/gnmdriver_init.h): its Neo sequences, except the plain hardware state, which shadPS4 also writes unchanged in Neo mode. The size checks and returned sizes are shadPS4's. Not compared with console output, and nothing in AnyPS5 consumes a GNM command buffer yet +- [draw, dispatch, wait and VGT packets](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) (libSceGnmDriverForNeoMode) - the signatures, size checks, PM4 words and refusals (-1) of the 17 command writers follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/gnmdriver/gnmdriver.cpp) on its Neo-mode path, since this module is the Neo-mode driver: draw initiators keep flag bits 29 to 31, and `sceGnmSetVgtControl` and `sceGnmResetVgtControl` write `IA_MULTI_VGT_PARAM` through `SET_UCONFIG_REG` (shadPS4's `UseNeoCompatSequences` is false). The packets are not compared with console output, and nothing in AnyPS5 consumes a GNM command buffer yet +- [embedded shaders](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) (libSceGnmDriverForNeoMode) - `sceGnmSetEmbeddedVsShader` (id 0, a fullscreen triangle) and `sceGnmSetEmbeddedPsShader` (id 0 writes black, id 1 exports depth only) write the register packets of [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/gnmdriver/gnmdriver.cpp), pointing at the shader code in [EmbeddedShaders.hpp](../../../core/libs/prx/libSceGnmDriverForNeoMode/include/EmbeddedShaders.hpp) in this module's memory instead of the console's `0xfe000f00` area. That code is shadPS4's instruction listing assembled with LLVM 19 `llvm-mc -arch=amdgcn -mcpu=bonaire`, without the binary header and register block that follow it on the console, since nothing here reads them; another id returns `0x8EEE00FF`. The size and modifier arguments are not checked, as in shadPS4. Not compared with console output, and nothing in AnyPS5 consumes a GNM command buffer yet +- [shader register packets](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) (libSceGnmDriverForNeoMode) - `sceGnmUpdateVsShader`, `sceGnmUpdatePsShader`, `sceGnmUpdatePsShader350`, `sceGnmSetPsShader350`, `sceGnmUpdateGsShader`, `sceGnmUpdateHsShader` and `sceGnmSetCsShaderWithModifier` write the packets, size checks and -1 refusals of [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/gnmdriver/gnmdriver.cpp); the update forms carry context registers inside NOP packets as there. Not compared with console output, and nothing in AnyPS5 consumes a GNM command buffer yet +- [workloads](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) (libSceGnmDriverForNeoMode) - `sceGnmCreateWorkloadStream`, `sceGnmBeginWorkload` and `sceGnmEndWorkload` return shadPS4's values (3 for a null output or name, 1 for a stream above 15, 2 for workload 0 or one with a top byte above 15) and keep no state; not measured on a console +- [sceGnmGetGpuCoreClockFrequency](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) (libSceGnmDriverForNeoMode) - returns 911 MHz, shadPS4's Neo-mode value, which it derives from Neo mode instead of the console's ioctl. `sceGnmGetOffChipTessellationBufferSize` returns shadPS4's `0x800000` diff --git a/docs/dev/debt/libSceHmd.md b/docs/dev/debt/libSceHmd.md index ee1012aafa..cf169b2329 100644 --- a/docs/dev/debt/libSceHmd.md +++ b/docs/dev/debt/libSceHmd.md @@ -6,153 +6,21 @@ ### Unknown function info +- [libSceHmd internal and capture exports](../../../core/libs/prx/libSceHmd/Export.cpp) - input/output directions and argument registers are recovered for shared VR2D data, device binding, optical and serial queries, screen status and capture operations. VR2D records are 128 bytes, optical output 16, serial output 16, PU position output 20, crash-dump output 68, audio/video status 32, and reprojection debug outputs 216 or 1136. Payload field meanings and capture callback contracts remain incomplete; these structures use forward declarations. + - [libSceHmd queries without a headset](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - the signatures, the distortion work memory (`0x20000`, aligned to `0x400`) and reprojection buffer sizes (garlic `0x100000`, onion `0x810`, both aligned to `0x100`), and the results of `sceHmdGetFieldOfView`, `sceHmdGet2DEyeOffset`, `sceHmdGetInertialSensorData` (`HANDLE_INVALID` once initialized; field of view checks its output for null first) and `sceHmdGetAssyError` (null output, then `DEVICE_DISCONNECTED` once initialized) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/hmd/hmd.cpp) (PS4) with no headset, not a PS5 measurement. Before initialization `sceHmdGetFieldOfView` returns `NOT_INITIALIZED` for a non-null output, where shadPS4 checks the handle first and returns `HANDLE_INVALID`. The third argument of `sceHmdGetInertialSensorData` is unnamed there and taken as a count -- [sceHmdInternalGetPUVersion](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalEventInitialize](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetVirtualDisplaySize](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSocialScreenSetOutput](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetVirtualDisplayDepth](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetEyeStatus](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalAnotherScreenOpen](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionUnsetCallback](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetCrashReportCommand](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetDebugTextMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalRegisterEventCallback](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStartWideNear](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetPUSerialNumber](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetDebugSocialScreenMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetM2LedBrightness](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetIPD](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalMirroringModeSetAspect](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuGetStatus](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalAnotherScreenSendVideo](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetRequiredPUPVersion](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionSetCallback](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuCheckAfterPvt](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalResetInertialSensor](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionSetUserEventStart](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdDistortionSetOutputMinColor](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetVirtualDisplayDepth](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdGetDistortionWorkMemoryAlign](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStartMultilayer](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalPushVr2dData](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionClearUserEventEnd](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetIPD](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetVRMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalCrashReportReadDataSize](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetVr2dData](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalAnotherScreenSendAudio](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetPuPositionParam](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionInitializeCapture](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetDebugSocialScreenMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetHmuPowerStatusForDebug](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdGetDistortionWorkMemorySize](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionSetDisplayBuffers](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalAnotherScreenSetFadeAndSwitch](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalCreateSharedMemory](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalMirroringModeSetAspectDebug](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetPuBuildNumber](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdDistortionGetCompoundEyeCorrectionCommand](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetS3dPassMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStopCapture](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalIsCommonDlgMiniAppVr2d](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalMmapGetSensorCalibrationData](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdDistortionGetCorrectionCommand](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetDebugMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalIsMiniAppVr2d](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalIsCommonDlgVr2d](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionSetOutputMinColor](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetPortConnection](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionDebugGetLastInfo](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetM2LedOn](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionAddDisplayBuffer](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetIpdSettingEnableForSystemService](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetLedOn](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternal3dAudioSendData](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionInitialize](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetDeviceInformationByHandle](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStopLiveCapture](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalAnotherScreenClose](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalCrashReportReadData](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetDeviceInformation](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStartCapture](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetDefaultLedData](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuClose](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetHmuPowerControl](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalBindDeviceWithUserId](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetHmuSerialNumber](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdDistortionGetWideNearCorrectionCommand](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalCheckS3dPassModeAvailable](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalAnotherScreenGetAudioStatus](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSeparateModeGetAudioStatus](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStartLiveCapture](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalIsGameVr2d](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionFinalize](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSocialScreenSetFadeAndSwitch](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuStart](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetVirtualDisplayHeight](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdDistortionInitialize](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuSetMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetForcedCrash](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuCheckPartialUpdateAvailable](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSeparateModeClose](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetDebugTextMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalCrashReportClose](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetHmuPowerControlForDebug](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetVirtualDisplayHeight](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStart](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalResetLedForVsh](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuOpen](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetDebugMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalCrashReportCancel](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSeparateModeGetVideoStatus](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalCheckDeviceModelMk3](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdSetVr2dEnable](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetSidetone](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSeparateModeOpen](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalMmapGetModeId](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdDistortionGet2dVrCommand](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuSendSize](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdGetWideNearDistortionCorrectionCommand](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStartMultilayer2](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdGetDistortionCorrectionCommand](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetDeviceConnection](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetVirtualDisplaySize](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionUnsetDisplayBuffers](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetHmuOpticalParam](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSeparateModeSendVideo](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalResetLedForVrTracker](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStartWithOverlay](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionSetUserEventEnd](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalCrashReportOpen](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetDemoMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStartWideNearWithOverlay](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdGetDistortionParams](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuSend](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionClearUserEventStart](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetUserType](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetDemoMode](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalMapSharedMemory](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetCrashDumpInfo](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetTv4kCapability](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalDfuReset](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStart2dVr](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalMmapGetCount](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetDefaultLedData](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternal3dAudioClose](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetBrightness](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSocialScreenGetFadeState](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSeparateModeSendAudio](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetStatusReport](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetBrightness](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionFinalizeCapture](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetPortStatus](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionStop](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalAnotherScreenGetVideoStatus](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternal3dAudioOpen](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalAnotherScreenGetFadeState](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalSetDebugGpo](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetIpdSettingEnableForSystemService](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdReprojectionDebugGetLastInfoMultilayer](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdInternalGetPuRevision](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature -- [sceHmdGet2dVrCommand](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - unknown signature +- [libSceHmd VR2D queries](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - `sceHmdInternalIsGameVr2d`, `sceHmdInternalIsMiniAppVr2d`, `sceHmdInternalIsCommonDlgVr2d` and `sceHmdInternalIsCommonDlgMiniAppVr2d` return false: nothing runs in VR2D without a headset, and the console answers 0 to the same four queries in libSceHmd2 (`Hmd2BeforeInitializeZero` in [libSceHmd2](libSceHmd2.md)). The libSceHmd answers themselves are not measured. +- [sceHmdDistortionGetCompoundEyeCorrectionCommand](../../../core/libs/prx/libSceHmd/Export.cpp) returns a constant error without consuming arguments in the supplied module; unused argument types remain unresolved. +- [sceHmdInternalGetDefaultLedData](../../../core/libs/prx/libSceHmd/Export.cpp) takes an unused handle, a 32-bit mask output and a nine-byte LED output. Output sizes match the paired setter; mask bits and LED payload semantics remain unresolved. The protected body is unchanged. + +[Device, field-of-view, assembly-error, distortion, sensor and DFU query signatures](../../../core/libs/prx/libSceHmd/Export.cpp) are recovered from native register use. Field of view and assembly errors each output four floats; device information occupies 64 bytes and distortion output spans 76 bytes. The remaining device/sensor/report field semantics and DFU status bit meanings are unresolved. Assembly confirms integer error returns where decompilation incorrectly inferred void. Protected bodies are unchanged. + +[Reprojection initialization and display-buffer signatures](../../../core/libs/prx/libSceHmd/Export.cpp) are recovered: initialization takes a parameter pointer, 32-bit mode and null reserved pointer; display-buffer setup takes a VideoOut handle, two buffer indices and reserved pointer, while adding a buffer takes an index and reserved pointer. Parameter fields remain incomplete. Protected bodies are unchanged. + +[Work-memory queries, capture start, separate-mode open and crash-report cancellation](../../../core/libs/prx/libSceHmd/Export.cpp) consume no arguments in the supplied native implementation. The mirroring aspect debug setter takes five 16-bit values followed by a byte mode; the individual scalar meanings remain unresolved. These argument lists are confirmed; protected bodies remain unimplemented. + +[Reprojection start signatures](../../../core/libs/prx/libSceHmd/Export.cpp) are recovered, including the five arguments of `StartMultilayer2` hidden by an incomplete decompilation. Standard and wide-near starts take frame parameters, pose, 64-bit flip argument and reserved pointer; overlay variants add an overlay descriptor. The 2D variant takes parameters, flip argument and reserved pointer. Multilayer takes a layer array/count, submission parameters, pose, flip argument and reserved pointer. Frame, pose, overlay and submission field semantics remain incomplete. Protected bodies are unchanged. + +[Distortion and 2D VR command queries](../../../core/libs/prx/libSceHmd/Export.cpp) take a 32-bit HMD handle, input descriptor and command-buffer output. The output contains a command pointer at offset zero and a 32-bit dword count at offset eight. Descriptor fields and GPU resource types remain incomplete; the standard, wide-near and 2D descriptors are distinct. Without a headset `sceHmdOpen` never issues a handle, so the stored handle stays 0. As firmware 13.42's libSceHmd does when its exports are run under an emulator ([#9577](https://github.com/boykopovar/AnyPS5/pull/9577#issuecomment-6336871370)), the distortion queries return `HANDLE_INVALID` for any other handle before checking initialization, and `NOT_INITIALIZED` for handle 0 before it when the title's compiled SDK version is below 4.50. The 2D VR query first returns `PARAMETER_NULL` for a null descriptor or output or a null pointer at descriptor offset 0, 8 or 0x20, and `PARAMETER_INVALID` for a non-zero field at 0x34 (32-bit) or 0x38 (64-bit). None writes the output. From SDK 4.50 the firmware first checks state the library sets up when it starts, which is not measured ([#9577](https://github.com/boykopovar/AnyPS5/pull/9577#issuecomment-6343495321)), and what handle 0 gets after initialization is not measured either, so both throw. + +The ELF export table aliases the [Distortion correction/2D command and work-memory queries](../../../core/libs/prx/libSceHmd/Export.cpp) to their corresponding HMD queries, confirming identical signatures. Distortion initialization consumes only a work-memory pointer. No shader code or calibration constants are copied; each aliased command query answers as its twin, and distortion initialization keeps its protected body. diff --git a/docs/dev/debt/libSceHmd2.md b/docs/dev/debt/libSceHmd2.md index 1dacc21ad2..25f1bc36a6 100644 --- a/docs/dev/debt/libSceHmd2.md +++ b/docs/dev/debt/libSceHmd2.md @@ -2,16 +2,13 @@ ### Silent stubs -- [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) has no PS VR2 runtime: `sceHmd2Initialize` returns libSceHmd's `UNSUPPORTED_FEATURE` (0x81110016) for any param, so the other calls answer as before initialization and `sceHmd2Open` returns `NotInitialized` (0x8a720002). These results are restored from the behaviour before #7323 and are not measured on hardware: firmware 13.20's libSceHmd2, with the `/dev/hmd2_cmd` state measured on a retail PS5 with no headset connected (connected flag 0, status 2, user -1), initializes, returns a handle from `sceHmd2Open` and reports `NotDetected`, which the code past the initialization check models but no title reaches. PPSA01317 passes a pointer to 16 zeroed bytes, only tests the result for non-zero and then skips its VR set-up; with initialization succeeding it stopped on `sceHmd2ReprojectionQueryBufferSizeAlign` (#6350). That export, `sceHmd2ReprojectionQueryDisplayBufferSizeAlign` and `sceHmd2ReprojectionQuerySeeThroughBufferSizeAlign` stay unimplemented and throw pending console measurements of their sizes and alignments. The other reprojection and gaze exports throw, and [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) never initializes +- [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) has no PS VR2 runtime: Initialize returns unsupported-feature, so normal calls never reach initialized device behavior. Reprojection and gaze operations retain their throwing bodies. Four size/alignment queries return measured constants; QueryBufferSizeAlign still throws. Device setup, headset tracking and rendering are not implemented. ### Unknown function info -- PPSA09030 imports from its `Media/Plugins/psvr2.prx` declared without parameters, signatures unknown, added without an implementation because the title fails to load the plugin without them: [sceHmd2ReprojectionGetStatus](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - [sceHmd2Open](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - the handle is the value the console's driver returned to the first open; titles built with an SDK before 7.00 may open for any user, and the firmware's `sceMbusAddHandleByUserId` result for a user other than the initial one is unknown, so that case throws. 0x8a720006 for a second open is named `AlreadyOpened` from its use - [sceHmd2SetVibration](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - the second parameter is checked against 25 only while a headset is connected; its name and unit are unknown. The field of view member names are assumed: of the firmware's default tangents the first is the widest and the last two are equal -- PPSA01317 imports declared without parameters, signatures unknown: the [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) exports that throw and the remaining [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) exports. Names from the aerolib NID list -- PPSA15878 imports declared without parameters, signatures unknown: the [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) reprojection and gaze exports, `sceVrTracker2Finalize`, `sceVrTracker2GetPlayAreaOrientedBoundingBox` and `sceVrTracker2LocateCoordinateSystem` in [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp), and the `scePadVrController` trigger effect exports in [libScePad](../../../core/libs/prx/libScePad/Export.cpp). Names from the aerolib NID list -- [libSceHmd2 gaze and reprojection exports](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - `sceHmd2ReprojectionSeeThroughDraw`, `sceHmd2ReprojectionSeeThroughIC`, `sceHmd2ReprojectionVideoOutEq`, `sceHmd2ReprojectionWaitShader`, `sceHmd2SeeThroughCallbackGetPoseDataFunc`, `sceHmd2ReprojectionClear`, `sceHmd2ReprojectionSet`, `sceHmd2ReprojectionSetEventGpuEnd`, `sceHmd2ReprojectionSetEventGpuStart`, `sceHmd2RpeorjectionSetParam` are declared without parameters from the firmware export database (aerolib.csv); their guest signatures are unverified on hardware and every call throws +- [libSceHmd2 legacy exports](../../../core/libs/prx/libSceHmd2/Export.cpp) - SeeThroughDraw, SeeThroughIC, VideoOutEq, WaitShader, SeeThroughCallbackGetPoseDataFunc, ReprojectionClear, ReprojectionSet and RpeorjectionSetParam have no matching symbols or callers in the supplied system modules. Their signatures remain unresolved. ClearUserEventEnd/Start, GazeStart/StopUserCalibration and GazeObjectSelectionTerminate are confirmed to take no arguments; their behavior after initialization remains unimplemented. - [sceHmd2InternalSetPortStatus](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - unknown signature - [sceHmd2InternalLedDetectorStartBySlot](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - unknown signature - [sceHmd2InternalImageStartBySlot](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - unknown signature @@ -35,7 +32,7 @@ ### Functional - [sceHmd2Initialize](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - the firmware also fails with 0x8a72001f when `sceKernelGetAppInfo` fails and with 0x8a72000a for some applications, depending on app info fields at 0x2c, 0x4b and 0x4d whose meaning is unknown, and then calls `sceMbusInit`; none of this is modelled -- [sceHmd2ReprojectionQueryDisplayBufferSizeAlign, sceHmd2ReprojectionQuerySeeThroughBufferSizeAlign, sceHmd2ReprojectionGetMirroringDisplayBufferSizeAlign and sceHmd2ReprojectionGetMirroringWorkMemorySizeAlign](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - each returns a `{uint64 size, uint64 alignment}` pair in RAX:RDX; the values were measured on a retail PS5 (firmware 13.60) with no PS VR2 headset from a homebrew payload that loaded `libSceHmd2.sprx` without initializing it, and stayed the same for every argument tried; `sceHmd2ReprojectionQueryBufferSizeAlign` measured there returns 0x100828 and 0x100 for every argument, including the 2, 0xAF and 1 that PPSA05666 passes. They are assumed not to depend on initialization or on a connected headset, and their parameters are unknown +- [Reprojection size/alignment queries](../../../core/libs/prx/libSceHmd2/Export.cpp) - all five exports consume no arguments and return two uint64 values in RAX:RDX. Four host queries return values measured without a headset; native display sizes are calculated through the graphics layout service and see-through size follows internal configuration. QueryBufferSizeAlign changes calculation with runtime state; its native pair return type is restored, while its protected body remains unchanged and throws. - [Hmd2BeforeInitializeZero](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers 0 to these 45 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `vDgpdS2N9Rw` (sceHmd2InternalRegisterVibrationCallback), `Ri35gKTZpvE` (sceHmd2InternalUnregisterVibrationCallback), `9oyW8puiKeI`, `1HZNE6jhBnQ` (sceHmd2InternalPowerEventInitialize), `cdwAKBZLXjs`, `Yub1MF58jfY`, `JYXAuQFnPyw` (sceHmd2InternalDfuOpen), `VuPKqCu3wzw` (sceHmd2InternalDfuClose), `GSThagVyW5U` (sceHmd2InternalCrashReportOpen), `J4qE5N937MY` (sceHmd2InternalCrashReportClose), `9KnNj-dyASg`, `Qta2bkstbFk`, `5ZcObnDRBXE`, `AvPNn0qx4WQ`, `jp0apqNGgi8` (sceHmd2SetIpdOffset), `-kZT7X7OMg0` (sceHmd2InternalRelocalizerOpen), `1ewLtng-SVk` (sceHmd2InternalRelocalizerClose), `B0b8DeIUGes` (sceHmd2InternalTrackingOpen), `HclLg4Oj5yw` (sceHmd2InternalTrackingClose), `VbEvrQXsZyY` (sceHmd2InternalLedDetectorOpen), `bB6MKZi5UCQ` (sceHmd2InternalLedDetectorClose), `OXQ1EDf02XU` (sceHmd2InternalImageOpen), `IAxfMTvFm0M` (sceHmd2InternalImageClose), `qTJtAtaLeno` (sceHmd2InternalGenDataOpen), `j7Q16UX6l34` (sceHmd2InternalGenDataClose), `pmmNM06rLqU`, `zdQfyDK9Ods` (sceHmd2InternalSlamTraceOpen), `aPRwp9GnljM` (sceHmd2InternalSlamTraceClose), `MjY1gBSaed0` (sceHmd2InternalGazeOpen), `evAMjJYHqW4` (sceHmd2InternalGazeClose), `r93FLEZ3BBk` (sceHmd2InternalGazeStart), `IdYURdEdNFo` (sceHmd2InternalGazeStop), `CVcL2Cu2AEI` (sceHmd2GazeActivateGazeTracking), `NmKIfVxr2s0`, `KeDLkkp62Gg`, `+aadu6-ra+A`, `FgnTiwwGSzk`, `PWAUwOTHbbo` (sceHmd2InternalCreateSharedMemory), `WzaEApHnsP8` (sceHmd2InternalIsGameVr2d), `dXzQI8QVY7Y` (sceHmd2InternalIsMiniAppVr2d), `cCvFKGqRh1k` (sceHmd2InternalIsCommonDlgVr2d), `uj4-2VEAFPU` (sceHmd2InternalIsCommonDlgMiniAppVr2d), `sIaGStI-iLM` (sceHmd2InternalMapSharedMemory), `JzwpZOemDO8`, `IsC4R4bZFVU`. Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2BeforeInitializeNotInitialized](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720002` to these 9 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `sXaPEPY-fLA` (sceHmd2InternalOpen), `3F5inHhKzXg` (sceHmd2InternalGetDeviceInformation), `sJAiB-6p55U`, `E8hDydLjsSs`, `xEQkSb2WJ7Y`, `aaP4dnR4q-0`, `lEU0TeUoGwI`, `cFVyjHBpUUU`, `zROtb46jTj4`. Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2BeforeInitializeError0004](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720004` to these 15 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `kAMsjYHBbNQ` (sceHmd2InternalSetVolume), `5m73zmLpsQ0` (sceHmd2InternalResetViewPosition), `EPeH4xuQ2XQ` (sceHmd2InternalGetStatus), `TJ8lyRsjFY4` (sceHmd2InternalClearTrackerFts), `-XQVcLnyX68` (sceHmd2InternalSetSlamCommand), `giQwQoHhYYA` (sceHmd2InternalDfuStart), `zNc9IkgawMI`, `-tnG5UJPvAc` (sceHmd2GazeStartUserCalibration), `FPhFmG+jXuQ` (sceHmd2GazeStopUserCalibration), `CKlkvNngofQ`, `yL1CZaciFWE` (sceHmd2GazeSetUserCalibrationPoint), `qlzgyG3s0RY`, `kTIDrDLzMv0`, `9Gee0ef8a+U`, `ecZVjQdwmBI` (sceHmd2GazeSetEnabledEye). Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded @@ -48,7 +45,6 @@ - [Hmd2BeforeInitializeMinusOne](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers -1 to these 12 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `EAkv8Mqt33E` (sceHmd2InternalRelocalizerStart), `Rv7bkmKvmz0` (sceHmd2InternalRelocalizerStop), `1oPZizn0SNA` (sceHmd2InternalTrackingStart), `TwCpE-nNrlo` (sceHmd2InternalTrackingStop), `jkqAeQfdtxw` (sceHmd2InternalLedDetectorStart), `+l0RQph81Z8` (sceHmd2InternalLedDetectorStop), `qsmAtGw1Ncc` (sceHmd2InternalImageStart), `uKhwYoMsIsA` (sceHmd2InternalImageStop), `WNdaigr94dI` (sceHmd2InternalGenDataStart), `P6S5ZVWf-H0` (sceHmd2InternalGenDataStop), `sun6NWE1sdg` (sceHmd2InternalSlamTraceStart), `--TFsJYXo04` (sceHmd2InternalSlamTraceStop). Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2BeforeInitializeValue0400](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x400` to these 1 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `BWqcwjCozzI`. Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2BeforeInitializeValue20000](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x20000` to these 1 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `PRz7TdBGUI0`. Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded -- [libSceHmd2 pre-initialization sweep](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - the names in the entries above are the aerolib names of the NIDs where one exists; the error names `Error0004`, `Error002C`, `Error1002` and `Error1003` are placeholders for codes whose meaning is unknown, and `ReprojectionNotInitialized` is ours because every export answering `0x8a72000b` is a reprojection one. Exports that answered differently to null and to zeroed arguments, wrote output, returned a pointer-like value, answered `0x8a72000a` (which `sceHmd2Initialize` also returns for some applications) or crashed the probe are left as they were - [Hmd2CheckFirstThenError0004](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720008` when argument 0 is null and `0x8a720004` when every argument points at a zeroed 16 KiB buffer, writing nothing, for these 20 exports: `9WeZr4kYfzk` (sceHmd2InternalGetFwVersion), `N+5h1hCOFAg` (sceHmd2InternalGetRequiredPUPVersion), `w2vEs2y5ZmE` (sceHmd2InternalGetOpticalParam), `L5gat1pe0y0` (sceHmd2InternalSetCameraSeeThroughParam), `4GnLHR9NJ1w`, `AHk7nGSzef0` (sceHmd2InternalGetTrackerFtsData), `ncjX9d3mc0M`, `l63QYAUuogc` (sceHmd2InternalGetSlamCommand), `mrcBySCdXSo` (sceHmd2InternalDfuGetStatus), `ieBra+td8sU` (sceHmd2InternalDfuGetUpdateStatus), `s+rzBnewgGs` (sceHmd2InternalDfuCheckAfterPvt), `Nfcm0GBdzZI` (sceHmd2InternalRelocalizerGetVersion), `Tc9rasWfrm4` (sceHmd2InternalSetHomeCalibration), `ckTbKTX4KZg`, `D7576PUbUjk` (sceHmd2InternalGazeReadStatus), `tTJ04SrBT8U`, `sDP7hZ-P6OI` (sceHmd2GazeGetDeviceStatus), `J+eVNbN9ndY` (sceHmd2GazeGetUserCalibrationStatus), `kozvHBNyxf0`, `638Tmg5y6U4`. Each argument was nulled on its own with the others present (probe `hmd2n`, payload process, firmware 13.60); what the present arguments mean, and the behaviour after initialization, are not measured, so the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2CheckFirstThenZero](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720008` when argument 0 is null and 0 when every argument points at a zeroed 16 KiB buffer, writing nothing, for these 8 exports: `8ETtFtp5zqY` (sceHmd2InternalGetVrMode), `CWPkBm3EXQA`, `R62liQQIAaA` (sceHmd2InternalGetFakeLensSeparationDistance), `-bcWLHsdzo0`, `JngJzgNbu8U` (sceHmd2SetFieldOfView), `xNBSz8xrGdk`, `yUVPfN-jRIU`, `BKPR3RZ5aO4`. Each argument was nulled on its own with the others present (probe `hmd2n`, payload process, firmware 13.60); what the present arguments mean, and the behaviour after initialization, are not measured, so the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2CheckFirstThenError1002](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720008` when argument 0 is null and `0x8a721002` when every argument points at a zeroed 16 KiB buffer, writing nothing, for these 8 exports: `IG4y+drWAWw` (sceHmd2InternalRelocalizerReadData), `rhYUml9vppE` (sceHmd2InternalRelocalizerReadDataAsync), `6574xHS+jZk` (sceHmd2InternalRelocalizerReadData2), `mXzaY6xVoxg` (sceHmd2InternalTrackingReadData), `6af-HIZQQn4` (sceHmd2InternalTrackingReadData2), `6qWtdTHQsTk`, `pUm8t8xWhFg`, `A0XKI-WjxK8` (sceHmd2InternalImageReadData). Each argument was nulled on its own with the others present (probe `hmd2n`, payload process, firmware 13.60); what the present arguments mean, and the behaviour after initialization, are not measured, so the handler throws once `sceHmd2Initialize` has succeeded @@ -67,7 +63,6 @@ - [Hmd2CheckFirstAndThirdThenError1003](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720008` when argument 0 and argument 2 is null and `0x8a721003` when every argument points at a zeroed 16 KiB buffer, writing nothing, for these 1 exports: `zn7PHGzzEyI`. Each argument was nulled on its own with the others present (probe `hmd2n`, payload process, firmware 13.60); what the present arguments mean, and the behaviour after initialization, are not measured, so the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2CheckSecondThenInvalidHandle](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720008` when argument 1 is null and `0x8a720003` when every argument points at a zeroed 16 KiB buffer, writing nothing, for these 1 exports: `wEHILc7TP04` (sceHmd2GazeInsertRawDataWithHandle). Each argument was nulled on its own with the others present (probe `hmd2n`, payload process, firmware 13.60); what the present arguments mean, and the behaviour after initialization, are not measured, so the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2CheckSecondThenError1002](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720008` when argument 1 is null and `0x8a721002` when every argument points at a zeroed 16 KiB buffer, writing nothing, for these 1 exports: `W-ZIlUXcrNA`. Each argument was nulled on its own with the others present (probe `hmd2n`, payload process, firmware 13.60); what the present arguments mean, and the behaviour after initialization, are not measured, so the handler throws once `sceHmd2Initialize` has succeeded -- [libSceHmd2 null-argument sweep](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - `Error1010` and `Error800d0203` are placeholder names. Left as they were: the exports that write output (`omimgG5itSs`, `FBUEhH9tQ1A`, `6PeHcAaKYg8`, `yur+xPdWPAg`, `RnieDDp5ge4`, `jM3NEJA11yw`, `b0H-n-tUk0U`) and those whose answer with a single null argument is neither `0x8a720008` nor their all-present answer, which take a handle or slot number rather than a pointer first (`MXhloJ6Iw3w`, `19wrIIiqFgM`, `Z389vAm47Ys`, `cDcrvEG8j8k`, `tuqJO6O6cb4`, `fd4fu1cziaI`, `UxMg7iL4nRs`, `K1GELwlaaGc`, `RFn39VCHjGY`, `FRoSjceDv10`, `wiv8+7ogrq0`, `wQ44gBwBw44`) - [Hmd2WriteStateTwo](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console writes the five bytes `02 00 00 00 00` at the start of its first argument and returns 0; a null first argument returns `0x8a720008`: `omimgG5itSs`. Measured from a payload process on firmware 13.60 (probes `hmd2n` and `hmd2x`); the meaning of the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2WriteSize0x2000Align0x100](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console writes the 64-bit values `0x2000` through its first argument and `0x100` through its second and returns 0; a null first or second argument returns `0x8a720008`: `6PeHcAaKYg8`. Measured from a payload process on firmware 13.60 (probes `hmd2n` and `hmd2x`); the meaning of the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2WriteFourConstants](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console writes the 16 bytes `e1 f0 74 3c d8 f0 74 3c 1f f1 74 3c d8 f0 74 3c` (four floats near 0.01495) at the start of its first argument and returns 0; a null first argument returns `0x8a720008`: `yur+xPdWPAg`. Measured from a payload process on firmware 13.60 (probes `hmd2n` and `hmd2x`); the meaning of the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded @@ -83,3 +78,9 @@ - [Hmd2SlotUpTo2CheckSecondThenError002C](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console returns `0x8a72002c` for a first argument of 0 to 2 and `0x8a720009` for 3 to 5, 8, 16, 255 and -1; a null second argument returns `0x8a720008` even with an invalid first argument: `wQ44gBwBw44`. Measured from a payload process on firmware 13.60 (probes `hmd2n` and `hmd2x`); the meaning of the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2IndexZeroCheckSecondThenZero](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console returns 0 for a first argument of 0 and `0x8a72100f` for 1 to 5, 8, 16, 255 and -1; a null second argument returns `0x8a720008` even with a nonzero first argument. Its third to sixth arguments answered `0x8a72100f` when null with a nonzero first argument, which does not separate them from the first-argument check, so they are not looked at: `wiv8+7ogrq0`. Measured from a payload process on firmware 13.60 (probes `hmd2n` and `hmd2x`); the meaning of the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [FBUEhH9tQ1A](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - left unimplemented: before initialization it writes the 32-bit value 9 through its second argument and returns 0 when its first argument points at zeroed memory, but returns `0x8a720001` without writing when that memory holds 0xAA bytes, and its fourth to sixth arguments answer `0x8a720001` when null, so the first argument is an in-out structure whose fields are not known + +- [libSceHmd2 gaze exports](../../../core/libs/prx/libSceHmd2/Export.cpp) - gaze argument registers and input/output directions are recovered. Result queries take a 16-byte size/handle parameter. The 24-byte foveated result contains size, status and a three-float direction; its trailing four bytes are untouched. The 56-byte full result still needs its eye-field semantics. Object selection uses a 120-byte parameter and 56-byte result whose fields remain incomplete. Its initialization and memory-query structures are recovered, including the 32-bit object count and memory size, memory pointer and 64-bit alignment. Calibration data descriptors contain a pointer and signed 32-bit size; calibration status contains two 32-bit selectors and three floats. The underlying calibration blob formats remain incomplete. Raw gaze input is mutable. CalibrationPoint is three floats passed by value in two SIMD registers; SetIpdOffset takes a byte and a float. + +- [libSceHmd2 reprojection exports](../../../core/libs/prx/libSceHmd2/Export.cpp) - frame identifiers and predicted display times are 64-bit. Event setters accept a kernel event queue and 32-bit event ID, consumed by sceKernelTriggerUserEvent. Mirroring uses two memory pointers and a separate 32-byte mode/reserved option. Status and current-output queries write 104 and 32 bytes; those field layouts and the reprojection initialization fields remain incomplete. Positional-reprojection mode is a byte with values 0 through 5, not a boolean. + +- [Reprojection parameter submission](../../../core/libs/prx/libSceHmd2/Export.cpp) - SetParam and SetParamWithBuffer take a 32-bit buffer-set ID, 64-bit frame ID, parameter pointer and reserved register slot. The parameter contains a layer pointer, signed count and optional 128-byte flags/reserved structure; plain and buffered layer strides are 328 and 472 bytes. Layer union fields remain incomplete. SetRenderConfig consumes three 64-bit slots; only bits 0 and 32 of the first can be nonzero. These exports remain unimplemented. diff --git a/docs/dev/debt/libSceHmdSetupDialog.md b/docs/dev/debt/libSceHmdSetupDialog.md index cd9e1a5ae2..1566cbc62a 100644 --- a/docs/dev/debt/libSceHmdSetupDialog.md +++ b/docs/dev/debt/libSceHmdSetupDialog.md @@ -1,5 +1,5 @@ # libSceHmdSetupDialog technical debt -### Unknown function info +### Silent stubs -- [sceHmdSetupDialogClose, sceHmdSetupDialogGetResult, sceHmdSetupDialogGetStatus, sceHmdSetupDialogInitialize, sceHmdSetupDialogOpen, sceHmdSetupDialogTerminate and sceHmdSetupDialogUpdateStatus](../../../core/libs/prx/libSceHmdSetupDialog/Export.cpp) (libSceHmdSetupDialog) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceHmdSetupDialog.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [libSceHmdSetupDialog](../../../core/libs/prx/libSceHmdSetupDialog/Export.cpp) - the HMD setup dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as no headset is supported (see libSceHmd2). The signatures are from [Orbital's type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json); the result layout beyond the leading `int32_t` result code is not known, so nothing else is written. diff --git a/docs/dev/debt/libSceHttp.md b/docs/dev/debt/libSceHttp.md index 7119fff249..e58f4e6694 100644 --- a/docs/dev/debt/libSceHttp.md +++ b/docs/dev/debt/libSceHttp.md @@ -4,64 +4,36 @@ - [libSceHttp](../../../core/libs/prx/libSceHttp/Export.cpp) - no request reaches the network, so `sceHttpSetResponseHeaderMaxSize` has no response header to limit, `sceHttpSetRecvBlockSize` (`(int id, u32 blockSize)` as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.h)) no response body to read in blocks and `sceHttpRedirectCacheFlush` no redirect to forget; `sceHttpsUnloadCert` returns success like `sceHttpsLoadCert`, which keeps no certificate; `sceHttpSetRedirectCallback` and `sceHttpSetCookieRecvCallback` accept callbacks that are never called, `sceHttpsSetSslVersion` has no TLS connection to configure and `sceHttpsGetSslError` reports error 0 with detail 0; `sceHttpSetAuthInfoCallback` accepts a callback that is never called, as no server asks for authentication; `sceHttpsGetCaList` and `sceHttpsFreeCaList` report an empty CA list (`certs` null, `certsNum` 0) and return 0, as no CA store is kept, and a null list returns `ERROR_INVALID_VALUE`; unlike shadPS4 they do not check that `http_ctx_id` is a live context nor that the library is initialised, and `sceHttpsFreeCaList` also nulls `certs`, which shadPS4 leaves untouched. The signatures and the `{ void* certs; int certsNum; }` layout follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.h) (PS4), not confirmed on a PS5 title - [HTTP options without a network](../../../core/libs/prx/libSceHttp/src/Options.cpp) (libSceHttp) - `sceHttpCacheRedirectedConnectionEnabled`, `sceHttpsEnableOptionPrivate` and `sceHttpsDisableOptionPrivate` return 0 for a live template, connection or request and keep nothing, since no request reaches the network; the private options refuse flags outside `0x2dff` with `ERROR_INVALID_VALUE` before they look at the id, and `sceHttpAbortRequestForce` does what `sceHttpAbortRequest` does. Signatures, the mask and the order of the checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/network/http.cpp) (PS4), not measured on a PS5 +- [sceHttpSetProxy](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - returns 0 for a live template, connection or request and keeps nothing, since no request reaches the network. The signature `(id, HTTP proxy conf, WLAN proxy conf, host, port)` and the order of the checks (`ERROR_BEFORE_INIT`, then `ERROR_INVALID_VALUE` for a null host, then `ERROR_INVALID_ID`) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/network/http.cpp) (PS4), which stores the proxy; not measured on a PS5 ### Unknown function info - [HTTP object options](../../../core/libs/prx/libSceHttp/src/Options.cpp) (libSceHttp) - a template starts with redirects followed, blocking calls and no `Accept-Encoding: gzip`, and a connection or request copies its parent's options when it is created. The signatures of the option calls, these defaults, the copying and the order of the checks (`BEFORE_INIT` first; the last `sceHttpTerm` ends every object's options) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/network/http.cpp) (PS4) and are not measured on a PS5. `sceHttpSetDefaultAcceptEncodingGZIPEnabled` sets the gzip value for templates created later in that context, where shadPS4 keeps one value for the library, and `sceHttpUnsetEpoll` takes a template or connection id, where shadPS4 looks only at requests. The options change no request, as none reaches the network -- [sceHttpUriBuild](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) retains `//` for a non-opaque URI independently of the selected fields, following [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a8a70da595da2a96e0410309472ba4a7eb558144/src/core/libraries/network/http.cpp#L3551-L3558). Black Myth: Wukong builds its `Scheme` header with option 1 and expects `http://` or `https://`. Other partial field combinations follow the PS4 reference and remain unverified on PS5 hardware. +- [sceHttpGetConnectionStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - connection and request diagnostic signatures are recovered; their outputs contain four and three 32-bit words, respectively. Field semantics remain incomplete. Epoll setters/getters distinguish a 32-bit epoll ID from a pointer-sized handle, and use a pointer-sized user argument. The supplied firmware getter dispatch does not handle the pointer-handle selector used by GetEpoll, so its intended successful behavior is not recoverable from this implementation. + - [sceHttpSetCookieEnabled](../../../core/libs/prx/libSceHttp/Export.cpp) - the two-argument signature and 0/1 enable values follow [VitaSDK](https://github.com/vitasdk/vita-headers/blob/master/include/psp2/net/http.h) and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp); PPSA01521 passes 0 to disable cookies. Disabling succeeds because the offline HTTP backend has no cookie engine; enabling throws. Invalid enable values use the same error as `sceHttpSetInflateGZIPEnabled`. Like the existing offline HTTP setters, the handle is not validated; requests still fail with the network error. Console behavior is unverified - [sceHttpParseResponseHeader](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); the result, value bounds and error codes follow a payload process on a PS5 (firmware 13.60). Unmeasured there: a bare carriage return, and vertical tab or form feed around the value, which are skipped as blanks before it as before. A value end above `INT_MAX` throws instead of wrapping -- [sceHttpParseStatusLine](../../../core/libs/prx/libSceHttp/src/Parse.cpp) (libSceHttp) - behaviour and error codes follow the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title; unlike it, no byte past `lineLen` is read -- [sceHttpUriMerge](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); validation, size formula, absolute/relative split and path join follow the disassembly-like body in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp), not confirmed on a PS5 title or hardware. The base URL goes through our `sceHttpUriParse`, which removes dot segments from the base path, while the relative part is appended unswept; base query and fragment are dropped. The relative reference is only classified by its scheme and a leading `//`, and only references with `//` are validated; as in shadPS4, `//host/x` is returned unchanged instead of taking the base scheme, a reference with a scheme but no `//` (`mailto:x`) is joined onto the base directory, and `?q` and `#f` are appended to the base directory instead of the base path. -- [sceHttpUriUnescape](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); decoding and error codes follow a payload process on a PS5 (firmware 13.60). A buffer one byte short of the decoded size got the decoded bytes without a terminator and left `require` alone; a shorter buffer is assumed to get the first `prepare` bytes the same way +- [sceHttpParseStatusLine](../../../core/libs/prx/libSceHttp/src/Parse.cpp) (libSceHttp) - follows a payload process on a PS5 (firmware 13.60) for 124 lines and lengths. As on the console, `lineLen` is only checked to be at least 8 and to leave the 3 code digits (`lineLen - index < 3` in unsigned arithmetic): the version digits and the `.` and space after them are read past `lineLen`, and the line feed is searched up to and including the byte at `lineLen`, so a length that stops right before the line feed still parses. When the version ends past `lineLen` both subtractions wrap and the search goes on until a line feed; what the console does when none follows in readable memory was not measured +- [sceHttpUriMerge](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); results, errors and `require` (the base's `sceHttpUriParse` size plus 2 and twice the two string lengths, also for a reference copied unchanged) follow a payload process on a PS5 (firmware 13.60). A reference with an authority (`//` after an optional scheme) is copied unchanged; any other, including `mailto:x`, `http:d`, `?q` and `#f`, is appended unswept to the base path up to its last `/`, and the base query and fragment are dropped. A reference that `sceHttpUriParse` rejects returns that error, which was not measured. +- [sceHttpUriParse](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - follows a payload process on a PS5 (firmware 13.60) for every ASCII byte inside a scheme, userinfo and hostname, and for letters, digits, `+`, `-` and `.` at the start of a scheme, where a digit ends it; other bytes at the start of a scheme are taken as they are inside one. The userinfo ends at the first `@` after bytes from the RFC 3986 userinfo set; a hostname is letters, digits, `-`, `_` and `.` without a leading `.`, and the first byte outside it starts the path instead of failing (`http://bad host/` has the host `bad` and the path ` host/`). A port is up to 5 digits that end at `/` or the end, at most 65536, which reads as 0; an empty port takes 443 for a scheme starting with `https`, 80 for one starting with `http` (case-insensitive) and 0 otherwise. The path is swept as `sceHttpUriSweepPath` does, while the pool keeps room for the unswept path. With a pool too small the console returns `ERROR_OUT_OF_MEMORY`, leaves `require` alone and has already written part of the pool; how much is not known, so nothing is written here. - [sceHttpUriSweepPath](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - follows a payload process on a PS5 (firmware 13.60), which agrees with the [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) reimplementation of the PS4 library: it is not RFC 3986 5.2.4, a path that doesn't start with `/` is copied unchanged and a trailing `.` or `..` is kept. Like the console, the byte at `srcSize - 1` is read as the `/` that ends a `..` segment, and `srcSize == 1` with a leading `/` writes `/`; whether it also ends a `.` segment was not measured, so a `.` there is still kept as before - [sceHttpSetResponseHeaderMaxSize, sceHttpRedirectCacheFlush, sceHttpsUnloadCert](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) (PS4); no PS5 reference has them - [sceHttpSetRequestStatusCallback](../../../core/libs/prx/libSceHttp/Export.cpp) - the three-argument ABI follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp). Registrations and user data are retained and removed on request deletion; the offline backend never dispatches callbacks. Invalid ids return `0x80431100`, an uncited assumption, not a verified console error. Handle hierarchy, inheritance and deleted-handle validation remain incomplete; callback statuses and dispatch timing are unknown. - [sceHttpSetRedirectCallback, sceHttpSetCookieRecvCallback, sceHttpsSetSslVersion, sceHttpsGetSslError](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) (PS4), which only logs and returns 0; a null `errNum` or `detail` in `sceHttpsGetSslError` is assumed to return `SCE_HTTP_ERROR_INVALID_VALUE` like `sceHttpGetLastErrno` - [sceHttpSetAuthInfoCallback](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signature and callback type follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.h) (PS4); no PS5 reference has them. The `id` is not checked, as in the other libSceHttp setters - [sceHttpWaitRequest](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the timeout is assumed to be in microseconds and a negative timeout to wait without limit; the invalid-value error for a null handle or event array or a non-positive event count is assumed, not measured. No request is ever pending, so a wait returns 0 events when its timeout passes -- [sceHttpUriCopy](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - read from the disassembly of libSceHttp.sprx on firmware 13.60, not measured on a console and not compared with other emulators. The arguments are (out, src, pool, prepare, require), `prepare` before `require`, the reverse of `sceHttpUriParse`. A null source returns `ERROR_INVALID_URL`; without an output or a pool, a null `require` returns `ERROR_INVALID_VALUE`, otherwise the size is only reported. Only the scheme, hostname, path, username and password are copied, in that order, each with its terminator (a missing one becomes an empty string and takes one byte), and the port; the query, the fragment and the opaque flag stay 0. A pool that is too small clears the output and the pool and returns `ERROR_OUT_OF_MEMORY` (0x80431022, the console's out-of-size error) -- [sceHttpAuthCacheImport](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpAuthCacheExport](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpDbgEnableProfile](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpDbgSetPrintf](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpDbgShowStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpGetMemoryPoolStats](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpDbgShowConnectionStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpDbgShowRequestStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpDbgShowMemoryPoolStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpDbgGetConnectionStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpDbgGetRequestStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpAddRequestHeaderRaw](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpRemoveRequestHeader](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpGetAuthEnabled](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetEpollId](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpGetEpollId](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpGetCookie](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpGetCookieEnabled](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpAddCookie](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetCookieSendCallback](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpCookieExport](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpCookieImport](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetCookieTotalMaxSize](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetCookieMaxSize](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetCookieMaxNum](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetCookieMaxNumPerDomain](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpGetCookieStats](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetPriorityOption](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetPolicyOption](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpRequestGetAllHeaders](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetProxy](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetSocketCreationCallback](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetHttp09Enabled](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpGetRegisteredCtxIds](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpGetConnectionStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpAddQuery](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature -- [sceHttpSetDelayBuildRequestEnabled](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature +- [sceHttpUriCopy](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - unverified: whether the strings are written before the size check, which a short pool hides by ending cleared. The rest follows a payload process on a PS5 (firmware 13.60): the arguments are (out, src, pool, prepare, require) as in the disassembly; the whole output and all `prepare` pool bytes are cleared before the source is read, so a source that is the output or lies in the pool copies as empty; the scheme, hostname, path, username and password are copied in that order with terminators, a null one as empty, and the port, while the query, fragment and opaque flag stay 0; a short pool returns 0x80431022 and leaves `require` alone; a null source returns 0x80433060, and no output or pool with a null `require` 0x804311FE +- [sceHttpDbgEnableProfile](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the export is absent from the supplied native module; its signature remains unresolved. +- [sceHttpDbgSetPrintf](../../../core/libs/prx/libSceHttp/Export.cpp) returns constant success without consuming arguments in the supplied module; the callback contract remains unresolved. +- [sceHttpDbgShowStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. +- [sceHttpDbgShowConnectionStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. +- [sceHttpDbgShowRequestStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. +- [sceHttpDbgShowMemoryPoolStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. +- [sceHttpGetMemoryPoolStats](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - not measured on a console. The library takes nothing from the pool given to `sceHttpInit`, so it reports that size with peak and current use 0, where a console already holds bytes for a live context (0x1640 for Http2, see libSceNet). No live context returns `BEFORE_INIT` (0x80431001) and an unknown or terminated id `INVALID_ID` (0x80431100), as in `sceHttpCookieFlush`, with the output untouched; both codes are unmeasured. A null output throws, as its error code is unknown. Only the 24 bytes of the declared layout are written; [SharpProspero's struct](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs#L398-L413) adds a 32-bit reserved field after them ### Functional -- [sceHttpUriParse, sceHttpUriBuild, sceHttpUriMerge](../../../core/libs/prx/libSceHttp/src/Uri.cpp) - console behavior for non-ASCII scheme and hostname bytes remains unmeasured. ASCII character classification and scheme case folding follow [RFC 3986 sections 3.1 and 3.2](https://www.rfc-editor.org/rfc/rfc3986#section-3.1), independently of the host locale; parsing retains the existing C-locale rejection of those bytes. +- [sceHttpUriParse, sceHttpUriBuild, sceHttpUriMerge](../../../core/libs/prx/libSceHttp/src/Uri.cpp) - bytes 0x80 to 0xFF were measured only as 0x80, 0xC3 and 0xFF in a hostname; the others, and any of them in a scheme or userinfo, are taken as the measured ASCII bytes outside those sets are. ASCII classification and scheme case folding are independent of the host locale. A bracketed host such as `[::1]` is parsed, merged and built without its brackets, as a PS5 (firmware 13.60) does; a PS4 (firmware 10.71) takes no bracketed host (`http://[::1]x/` has an empty host and the path `[::1]x/`) and keeps `[::1]:80` in a merge, as measured in #9058, so this follows the PS5 only. - [sceHttpUriEscape](../../../core/libs/prx/libSceHttp/src/Uri.cpp) escapes every byte except the ASCII unreserved set in [RFC 3986 section 2.3](https://www.rfc-editor.org/rfc/rfc3986#section-2.3), independently of the host locale. Console behaviour for non-ASCII input remains unmeasured. diff --git a/docs/dev/debt/libSceHttp2.md b/docs/dev/debt/libSceHttp2.md index 2fa52cfbc5..73186b8a90 100644 --- a/docs/dev/debt/libSceHttp2.md +++ b/docs/dev/debt/libSceHttp2.md @@ -2,19 +2,18 @@ ### Silent stubs -- [libSceHttp2](../../../core/libs/prx/libSceHttp2/Export.cpp) - no request reaches the network, so `sceHttp2SetMinSslVersion` has no TLS connection to restrict and its value is ignored, as for `sceHttp2SslEnableOption` and `sceHttp2SslDisableOption`. The three setters validate `id` against the live handle registry and return `SCE_HTTP2_ERROR_INVALID_ID` (`0x817B1100`) for a handle that was never allocated, was deleted, or belonged to a terminated context, following [prosper](https://github.com/mattias800/prosper). Whether the console restricts the object kind these setters accept, and what it does with an id from a previous init/term cycle, is unverified, so any live handle kind is accepted and ids are never reused. `sceHttp2Term`, `sceHttp2DeleteTemplate`, `sceHttp2DeleteRequest`, `sceHttp2CreateTemplate`, `sceHttp2CreateRequestWithURL`, `sceHttp2CreateCookieBox` and `sceHttp2SendRequestAsync` also return the invalid-id error for a handle of the wrong kind or no longer live, following prosper (not measured on a console) +- [libSceHttp2](../../../core/libs/prx/libSceHttp2/Export.cpp) - no request reaches the network, so `sceHttp2SetMinSslVersion` has no TLS connection to restrict and its value is ignored, as for `sceHttp2SslEnableOption` and `sceHttp2SslDisableOption`. The three setters validate `id` against the live handle registry and return `SCE_HTTP2_ERROR_INVALID_ID` (`0x817B1100`) for an invalid handle, following [prosper](https://github.com/mattias800/prosper). Whether the console restricts the object kind these setters accept, and what it does with an id from a previous init/term cycle, is unverified, so any live handle kind is accepted and ids are never reused. `sceHttp2Term`, `sceHttp2DeleteTemplate`, `sceHttp2DeleteRequest`, `sceHttp2CreateTemplate`, `sceHttp2CreateRequestWithURL` and `sceHttp2SendRequestAsync` also return the invalid-id error for a handle of the wrong kind or no longer live, following prosper (not measured on a console) ### Unknown function info -- [sceHttp2WebSocketSetPingInterval](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2, NID `YfaXPnc8PJE`) - imported by `libdiscord_partner_sdk.prx` of PPSA23126 (issue 2648). The parameters, the unit of the interval and the console errors are unknown. Every call throws. -- [sceHttp2WebSocketSetPingTimeout](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2, NID `-MU9L6S3Fj4`) - imported by PPSA27836 01.000.001, whose loader stopped on the missing export; not called during its start-up. The parameters and the console errors are unknown. Every call throws. - [sceHttp2DeleteCookieBox](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - imported by PPSA35943's Unity player. The `(int cookieBoxId)` signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http2.cs#L215-L217). A box `sceHttp2CreateCookieBox` returned is discarded; an unknown or already deleted id returns the library's invalid-id error (0x817B1100), as [prosper](https://github.com/mattias800/prosper/blob/b1bec57186a0/prosper/src/hle/net/hle_http2.cpp#L512-L523) does; not measured on a console - [sceHttp2SetResolveRetry](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - signature `(int id, int32_t retry)` from libSceHttp's `sceHttpSetResolveRetry`; the value is accepted and ignored like the other Http2 settings, since no request reaches the network -- [sceHttp2WebSocketCreateRequest, sceHttp2WebSocketCloseAsync, sceHttp2WebSocketSendTextMessageAsync, sceHttp2WebSocketSendDataMessageAsync](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - imported by PPSA16064 and not called during its start-up; unknown signatures -- [libSceHttp2 cookie boxes](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - `sceHttp2CreateCookieBox` is taken to take the library context id first, as in the [prosper](https://github.com/mattias800/prosper) reimplementation; further arguments are unknown and ignored. `sceHttp2SetRequestNoContentLength` is assumed to take only the request id. Cookies are never stored, so `sceHttp2CookieFlush` has nothing to discard +- [libSceHttp2 cookie boxes](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - `sceHttp2CreateCookieBox` takes a machine-sized pool size, requires at least 64 KiB and allocates independently of HTTP contexts. The host reserves the pool with 16 KiB rounding but does not reproduce native cookie-heap bookkeeping, identifier ranges or retained request references. Its delete signature and invalid-ID error are confirmed. `sceHttp2SetRequestNoContentLength` takes only the request id. Cookies are never stored, so `sceHttp2CookieFlush` has nothing to discard - [libSceHttp2 pending exports](../../../core/libs/prx/libSceHttp2/Export.cpp) - 18 named callable exports were identified in the PS5 3.20 export table and cross-checked against firmware 13.60. Signatures and behavior remain unverified; declarations use the unknown-signature convention and throw through `NotImplemented_nid_no_patch`. Evidence: [3.20 export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceHttp2.c). ### Functional - [libSceHttp2 asynchronous requests](../../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) never reach the network: `sceHttp2SendRequestAsync` accepts the request, queues a completion whose result is the library's network error and triggers the user event named by the kqueue option (equeue at offset 0, event id at 8, user data at 16, layout inferred from PPSA17221); `sceHttp2WaitAsync` returns that completion. The completion's `event_type` value is unknown and left 0. PPSA17221 completes its request object only after `sceHttp2SendRequestAsync` returns, and its handler for a synchronous failure calls `shared_from_this` before that, so a send must not fail synchronously. PPSA15508 passes null kqueue and extra options at original VA 0xb63931, then calls `sceHttp2WaitAsync` with a 24-byte result and a 2,000,000 timeout, and checks the result at offset 8. A null kqueue option therefore retains the same offline completion for direct waiting without an event notification. The timeout is not used because the offline result is already complete. This path follows the original title call and caller control flow, not a console measurement. A supplied kqueue with a missing user event, an extra option, and a wait with no completed operation still throw + +- [WebSocket ping settings](../../../core/libs/prx/libSceHttp2/Export.cpp): both setters take a 32-bit request ID and a 32-bit interval/timeout in microseconds, confirmed by the timer consumer and its clock conversion. The protected implementations remain unchanged. diff --git a/docs/dev/debt/libSceHttpCache.md b/docs/dev/debt/libSceHttpCache.md index 9ee1af9ece..265fb144aa 100644 --- a/docs/dev/debt/libSceHttpCache.md +++ b/docs/dev/debt/libSceHttpCache.md @@ -2,6 +2,5 @@ ### Unknown function info -- [sceHttpCacheClear, sceHttpCacheClearAll, sceHttpCacheCompleteRequest, sceHttpCacheCreateRequest, sceHttpCacheCreateRequestWithTag, sceHttpCacheDeleteRequest, sceHttpCacheInit, sceHttpCacheReadData, sceHttpCacheRetrieve, sceHttpCacheRetrieveWithMemoryPool, sceHttpCacheRevalidate, sceHttpCacheRevalidateWithMemoryPool, sceHttpCacheSetCacheSharing, sceHttpCacheSetData, sceHttpCacheSetQuota, sceHttpCacheSetResponseHeader, sceHttpCacheSystemClearAll, sceHttpCacheSystemInit, sceHttpCacheSystemSendStatistics, sceHttpCacheSystemShutdown, sceHttpCacheSystemTerm and sceHttpCacheTerm](../../../core/libs/prx/libSceHttpCache/Export.cpp) (libSceHttpCache) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceHttpCache.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified - -- The following pending declarations have unverified signatures and behavior; they are parameterless import-resolution declarations and throw through `NotImplemented_nid_no_patch`: [`sceHttpCacheClearAllUserTags`](../../../core/libs/prx/libSceHttpCache/Export.cpp), [`sceHttpCacheClearCacheInfo`](../../../core/libs/prx/libSceHttpCache/Export.cpp), [`sceHttpCacheDebugSetUrl`](../../../core/libs/prx/libSceHttpCache/Export.cpp), [`sceHttpCacheRegisterCacheInfoUrl`](../../../core/libs/prx/libSceHttpCache/Export.cpp), [`sceHttpCacheRemoveTag`](../../../core/libs/prx/libSceHttpCache/Export.cpp). +- [sceHttpCacheDebugSetUrl, sceHttpCacheSystemSendStatistics and sceHttpCacheSystemTerm](../../../core/libs/prx/libSceHttpCache/Export.cpp): the supplied implementations return zero without reading arguments. No callers have yet established whether their public prototypes include unused arguments. +- The following pending declarations have unverified signatures and behavior; they are parameterless import-resolution declarations and throw through `NotImplemented_nid_no_patch`: [`sceHttpCacheClearAllUserTags`](../../../core/libs/prx/libSceHttpCache/Export.cpp), [`sceHttpCacheClearCacheInfo`](../../../core/libs/prx/libSceHttpCache/Export.cpp), [`sceHttpCacheRegisterCacheInfoUrl`](../../../core/libs/prx/libSceHttpCache/Export.cpp), [`sceHttpCacheRemoveTag`](../../../core/libs/prx/libSceHttpCache/Export.cpp). diff --git a/docs/dev/debt/libSceIme.md b/docs/dev/debt/libSceIme.md index 465e86b085..b26437726a 100644 --- a/docs/dev/debt/libSceIme.md +++ b/docs/dev/debt/libSceIme.md @@ -6,53 +6,13 @@ ### Unknown function info -- [sceImeSetConsecutiveClauseConversionEnabled](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - name from its NID (`row9sfnYNx4`) in [sce_symbols](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); neither [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0ebea15c24253b60f514fc37a348dabc8d0bcb90/src/core/libraries/ime/ime.cpp) nor [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/e5f0ee7fc2fb4ce88c6abb2fa02296404e2fe2a6/src/libs/ime.cpp) implements it, so it throws. PPSA24191 calls it with 1 as its only argument before each `sceImeOpen` and opens the panel only when it returns 0; the argument type, the result while a panel is open and the error codes are unknown -- [sceImeGetPanelPositionAndForm](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - the output is taken to be the `PositionAndForm` that `sceImeDialogGetPanelPositionAndForm` fills. While a panel is open it reports the type, position and alignments passed to `sceImeOpen` and the size `sceImeGetPanelSize` gives; the console's values for a panel moved by its own position adjustment are unknown. That a closed panel is reported before a null output is assumed by analogy with `sceImeSetCaret`, not confirmed on a PS5 title +- [sceImeVshOpen](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - VSH context, UTF-16 text and dictionary argument registers are recovered. The VSH parameter is copied as 256 bytes; preedit metadata is eight bytes. Dictionary operations transfer 224-byte records, with a fixed 11200-byte output for GetWords; the 64-bit dictionary selector and record fields still need receiver-side identification. These payload types remain incomplete. UpdateTextInfo and RemoteEventParam use the existing 64-byte ImeEditText and event payload at offset eight; their byte-sized repair flag permits correcting invalid text ranges and caret positions. + +- [sceImeGetPanelPositionAndForm](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - the output is taken to be the `PositionAndForm` that `sceImeDialogGetPanelPositionAndForm` fills. While a panel is open it reports the type, position and alignments passed to `sceImeOpen` and the size `sceImeGetPanelSize` gives; the console's values for a panel moved by its own position adjustment are unknown. - [sceImeFilterText](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - the behaviour follows the disassembly of `libSceIme.sprx` from firmware 13.60 and is differential-tested against that function in an emulator, not measured on a console. [ImeFilterCodepages.hpp](../../../core/libs/prx/libSceIme/include/ImeFilterCodepages.hpp) holds the two codepage checks (helpers at 0x4850 and 0x48f0), rebuilt from Unicode's public [CP932](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT) and [CP936](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP936.TXT) tables plus the Latin-1, CJK Extension A and a few other units the firmware also accepts (listed in the PR). The parameter layout beyond `type` is inferred: `mask` is the allowed-class bitmask, and `version` and `variant` derive it when it is 0. [sceImeCheckFilterText](../../../core/libs/prx/libSceIme/Export.cpp) throws, as its contract is unknown - [sceImeOpen, sceImeUpdate, sceImeSetText, sceImeSetCaret, sceImeClose](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - the event ids (Open 0, UpdateText 1, UpdateCaret 2, PressClose 4, PressEnter 5), the caret directions (left 1, right 2), the error codes and the text event payload follow the PS4 reimplementation in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/b72489d8c24e63fc56a29fe2c90120ab768a210d/src/core/libraries/ime/ime.cpp); none is measured on a PS5. Events are queued and delivered by `sceImeUpdate` only when it is passed the handler given to `sceImeOpen`, with that call's `arg`. A text event points `str` at a copy of the text kept in `work`, and `inputTextBuffer` holds the same text; both are terminated, `caret_index` is the caret after the edit and the one edit area has the edit position and the change in UTF-16 units (0 for Enter and Close). An edit sends one UpdateText; a caret move sends one UpdateCaret per UTF-16 unit, as shadPS4 does. Enter inserts a line break with the multiline option and sends PressEnter otherwise; Escape sends PressClose. Typed text is cut to `maxTextLength` without splitting a surrogate pair, line breaks are dropped without the multiline option, and the BasicLatin, URL, mail and number types do not filter characters. `sceImeOpen` checks, in shadPS4's order, the user, type, enter label, input method, option bits (the `sceImeGetPanelSize` mask), `maxTextLength` (1 to 2048), the text buffer, the position (below 1920x1080, or 3840x2160 with the over-2K option), the alignments, the extended priority and reserved bytes, `work` (non-null, 4-byte aligned), the handler, the reserved bytes and the initial text (no unpaired surrogate, no line break without the multiline option); shadPS4's SDK-dependent option, language and extended-option masks and its work-overlap check are not applied, and only the text copy is written to `work`. `sceImeSetText` keeps the caret unless the text became shorter, `sceImeSetCaret` rejects an index past the text with `0x80bc0030`, and neither sends an event. `sceImeSetTextGeometry` accepts any geometry while a panel is open, since no panel is drawn -- [sceImeVshOpen](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeKeyboardUpdate](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshUpdate](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshUpdateContext](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshClearPreedit](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshNotifySetText](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshSetCaretGeometry](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshConfirmPreedit](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshSetCaretIndexInPreedit](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshSetPreeditGeometry](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshSetSelectGeometry](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshClose](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshGetPanelPositionAndForm](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeSetCandidateIndex](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeConfirmCandidate](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeDisableController](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeSetVoiceDictationEnabled](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeSetEditEventWithRemotePlayEnabled](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeForTestFunction](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshInformConfirmdString2](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshSetSelectionText](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeCheckUpdateTextInfo](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeCheckRemoteEventParam](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeKeyboardOpenInternal](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshSetParam](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshUpdateContext2](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshSendTextInfo](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshInformConfirmdString](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshNotifyCurrentTextInfo](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshNotifyCurrentTextInfoForReconvertText](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshNotifyDeleteText](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshNotifyUpdateCaretText](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshCheckAutoReConvertingPreeditText](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshSetPanelPosition](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeVshNotifyPreeditMetaInfo](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeConfigSet](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeConfigGet](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeDicGetWords](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeDicAddWord](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeDicDeleteWord](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeDicReplaceWord](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeDicDeleteLearnDics](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature -- [sceImeDicDeleteUserDics](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - unknown signature +- [sceImeForTestFunction](../../../core/libs/prx/libSceIme/Export.cpp) returns a constant error without consuming arguments in the supplied module; unused argument types cannot be recovered from that body. +- [sceImeConfigSet and sceImeConfigGet](../../../core/libs/prx/libSceIme/Export.cpp) preserve the native argument slots: an unused first slot, a 64-bit configuration value, a 32-bit key and, for set, a 32-bit value. The service receives a 24-byte request; configuration/key semantics and unused argument types remain unresolved. The getter advertises one output transfer but does not initialize its descriptor in this module. Protected bodies remain unchanged. - [libSceIme pending exports](../../../core/libs/prx/libSceIme/Export.cpp) - 2 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIme.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. @@ -62,3 +22,5 @@ - [sceImeKeyboardGetInfo](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) returns `0x80bc0023` (no resource id) for every id while a keyboard is open, since the IME keyboard never reports a connected device. [sceImeKeyboardSetMode](../../../core/libs/prx/libSceIme/Export.cpp) validates the mode bits but the mode has no effect, as no IME keyboard events are delivered. - [sceImeKeyboardGetResourceId](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) reports that no USB keyboard is connected (`0x80bc0004`, as [shadPS4](https://github.com/shadps4-emu/shadPS4) does) because IME keyboard events are not delivered to the handler, although [libSceKeyboard](../../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) exposes the host keyboard - [sceImeOpen text filter](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) runs on the text edits of host input - typing, backspace, forward delete and a multiline Enter - following [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/1211958b4eb38256cc0c5c7ba92d668820c05389/src/libs/ime.cpp), not confirmed on a PS5. The host input thread only queues the edit under the IME mutex; `sceImeUpdate` applies it on the title's thread, where the filter also runs with the mutex released - like the event handler, so a filter that calls back into `sceIme*` cannot deadlock - and the structural edit and the filtered commit are serialized under the mutex, with a commit after the session closed or reopened dropped by a session id. The filter is guest code and `ImeTextFilter` and `ImeExtKeyboardFilter` are declared `APS5_VABI` like `EventHandler`. The filter is called with the edited text, a buffer of `maxTextLength` UTF-16 units and its length; a nonzero return, an output longer than the capacity, an unpaired surrogate or, without the multiline option, a line break keeps the edit unfiltered. An accepted output replaces the text, the caret stays where the edit put it and is clamped to the text and normalized past a surrogate pair, and one UpdateText is sent with the minimal edit delta of the whole old and new text, as KytyPS5 computes it; an output equal to the previous text sends no event. Caret moves, PressEnter without multiline, PressClose, `sceImeSetText` and `sceImeSetCaret` do not run the filter, as in KytyPS5; a `sceImeSetText` or `sceImeSetCaret` that lands while the filter runs bumps a revision beside the session id, and the filtered commit is dropped when either changed, as KytyPS5 drops the commit when its revision changed, so the title's own text or caret setting wins, the edit is discarded and sends no event. An external keyboard filter is accepted and stored on the session: it is called only for keyboard events that carry key data, which the host input path does not produce - it delivers text and actions, as KytyPS5's host API queues external inputs with a zeroed key and skips the filter for them - so it is never called + +- [sceImeVshNotifyCurrentTextInfo and sceImeVshNotifyCurrentTextInfoForReconvertText](../../../core/libs/prx/libSceIme/Export.cpp): assembly confirms seven and eight integer/pointer arguments, including the stack arguments, with no incoming SIMD parameter. Both take a context, UTF-16 string, signed 32-bit caret index and two pairs of 32-bit preedit/selection values. Pair endpoint-versus-length semantics and the reconversion option remain unresolved. Protected bodies are unchanged. diff --git a/docs/dev/debt/libSceImeDialog.md b/docs/dev/debt/libSceImeDialog.md index 58f32f8d68..84726f50bc 100644 --- a/docs/dev/debt/libSceImeDialog.md +++ b/docs/dev/debt/libSceImeDialog.md @@ -6,7 +6,8 @@ ### Unknown function info -- [sceImeDialogForTestFunction, sceImeDialogGetCurrentStarState, sceImeDialogInitInternal, sceImeDialogSetPanelPosition](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - their signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/ime/ime_dialog.h#L34-L51), not measured on a console. They are declared solely to resolve known imports, `sceImeDialogForTestFunction` without parameters and the others with shadPS4's parameters; this is not an ABI claim, and every call throws. +- [sceImeDialogForTestFunction](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - declared without parameters to resolve a known import, following [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/ime/ime_dialog.h#L34-L51); not measured on a console and not an ABI claim. +- [sceImeDialogGetCurrentStarState, sceImeDialogInitInternal, sceImeDialogSetPanelPosition](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - not measured on a console; signatures and checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/ime/ime_dialog.cpp). `sceImeDialogInitInternal` runs `sceImeDialogInit`, as shadPS4's two share one path apart from user flags this library does not keep. There is no movable panel, so `sceImeDialogSetPanelPosition` never succeeds: `DIALOG_NOT_IN_USE` without a dialog and `IME_SUSPENDING` while one is running or finished, the states in which shadPS4 refuses it too. The star state is always 0 - [sceImeDialogForceClose](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - not measured on a PS5: its `int (void)` signature and behaviour follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/ime/ime_dialog.cpp#L571-L601) (PS4). It closes a running or finished dialog, as `sceImeDialogTerm` does, without writing the typed text to the title's buffer, which shadPS4 updates while typing and this library only on Enter, and returns `0x80bc0107` with no dialog open, the `NotInUse` code of [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Dialog/ImeDialog.cs#L224-L225), while this library's `sceImeDialogTerm` returns `0x80bc0002` in the same state. Closes when: measured on a PS5 - [sceImeDialogInit, sceImeDialogGetStatus, sceImeDialogGetResult, sceImeDialogAbort, sceImeDialogTerm](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - the statuses (none 0, running 1, finished 2), the end statuses (ok 0, user canceled 1, aborted 2) and the errors of `sceImeDialogInit` for an open dialog (`0x80bc0001`), a null parameter (`0x80bc0031`), a maximum text length outside 1 to 2048 (`0x80bc0016`) and a null text buffer (`0x80bc0017`) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/b2d7ec7d31f9ad9426db93afe079db8679b3e1e0/src/core/libraries/ime/ime_dialog.cpp); none is measured on a PS5. A result, abort or term on a dialog that is not finished, running or open returns `0x80bc0002`, where shadPS4 returns `0x80bc0105` to `0x80bc0107`, and `sceImeDialogTerm` also closes a running dialog, which shadPS4 refuses. The text reaches the title's buffer only when Enter accepts it diff --git a/docs/dev/debt/libSceInvitationDialog.md b/docs/dev/debt/libSceInvitationDialog.md new file mode 100644 index 0000000000..9038d9da62 --- /dev/null +++ b/docs/dev/debt/libSceInvitationDialog.md @@ -0,0 +1,6 @@ +# libSceInvitationDialog technical debt + +### Unknown function info + +- [sceInvitationDialogGetResult](../../../core/libs/prx/libSceInvitationDialog/include/Api.hpp) - still throws; SDK field names and types at offsets 0x00 and 0x0C are unknown. In the supplied `libSceInvitationDialog.sprx`, export `8XKR6wa64iQ` at 0xFF0 takes one result pointer, checks reserved bytes 0x18..0x37 and writes the returned status at 0x08. The helper at 0x410 copies eight bytes from the saved open parameter to 0x00 and eight result bytes to 0x08, then copies 20-byte online IDs through the pointer at 0x10, after a four-byte count. Validation at 0xC20 limits recipients to 1..16. `InvitationDialogRecipients` provides the accessed 324-byte storage; any unused SDK tail is unverified. `InvitationDialogResult` provides the accessed 56 bytes, with unknown fields kept opaque; this is not a recovered SDK source declaration. +- [sceInvitationDialogClose](../../../core/libs/prx/libSceInvitationDialog/Export.cpp) (libSceInvitationDialog) - imported by PS4 titles, which do not load without it, and added without an implementation: declared without parameters, every call throws diff --git a/docs/dev/debt/libSceIpmi.md b/docs/dev/debt/libSceIpmi.md index 09625b0ec9..e1d0461397 100644 --- a/docs/dev/debt/libSceIpmi.md +++ b/docs/dev/debt/libSceIpmi.md @@ -2,4 +2,6 @@ ### Unknown function info +- [libSceIpmi pending exports](../../../core/libs/prx/libSceIpmi/PendingExports.cpp) - 49 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIpmi.c). Their signatures and behavior are unverified; the no-parameter declarations exist for import resolution and throw through `NotImplemented_nid_no_patch`. + - [libSceIpmi pending exports](../../../core/libs/prx/libSceIpmi/Export.cpp) - 5 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIpmi.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. diff --git a/docs/dev/debt/libSceJemspace.md b/docs/dev/debt/libSceJemspace.md new file mode 100644 index 0000000000..c0baf800a1 --- /dev/null +++ b/docs/dev/debt/libSceJemspace.md @@ -0,0 +1,5 @@ +# libSceJemspace technical debt + +### Unknown function info + +- The thirteen exports in [Export.cpp](../../../core/libs/prx/libSceJemspace/Export.cpp) resolve imports only and throw through `NotImplemented_nid_no_patch`. Each declared name hashes to a defined function NID in the `libSceJemspace` ELF export table. The module firmware version is unverified. The no-parameter declarations do not establish argument layouts or return types. No allocator behavior, memory ownership, allocation failure handling, statistics layout, or console compatibility is implemented or verified. diff --git a/docs/dev/debt/libSceJobManager.md b/docs/dev/debt/libSceJobManager.md index 123977e935..93d8f3ec8a 100644 --- a/docs/dev/debt/libSceJobManager.md +++ b/docs/dev/debt/libSceJobManager.md @@ -2,4 +2,7 @@ ### Functional -- [sce::Job::JobManager](../../../core/libs/prx/libSceJobManager/Export.cpp) (libSceJobManager) is written from the disassembly of the copies PPSA01474 and PPSA36082 ship in `sce_module`, and `guest_job_manager_tests` passes against both of those modules as well: the memory size formula, the error codes, sync point ordering, the per-sequence limits and the priority order are the original's. Compute sequences submit through `sceAgcDriverSubmitAcb` on the queue their mask bit selects and are checked the same way by `agc_driver_job_manager_compute_tests`: results, ordering against CPU dispatches and sync points, limits and sizes match the two modules. The command stream is simpler than the original's: every compute dispatch is submitted with its own label write and interrupt, where the original batches the label write and rewinds commands it encoded ahead, and the command buffers and labels live on the host heap. The per-queue command size in `calculateRequiredMemorySize` is a constant (104, 148 and 32 bytes per dispatch, sync point and wait), which is what the original measures by running the builders of this project's libSceAgc. A compute dispatch on a CPU sequence throws (the original does not check), and a failing `sceAgcDriverAddEqEvent` throws. `shutdown` throws while a compute sequence has work in flight, a sequence's label total is written as 32 bits, and since the label is host memory no GPU import covers, the driver drains recorded work before each label write. The manager's state lives on the host heap, with only the block header written to the title's memory; the original keeps everything in that block, and a block smaller than 0x2d8 bytes throws here. The worker sleep policy, the spin count and the persistent thread count are validated and stored but change nothing, a factory priority above 1 throws, `shutdown` throws while a thread is blocked in a sequence wait, and using a sequence after its `destroy` throws. `initialize` returns 0 where the original returns the result of its last import (`BG26hBGiNlw`, not resolved to a name), the `sceRazorCpuJobManager*` profiler calls are not made, and a worker thread that cannot be created or configured throws where the original does not check. The order of the sequence and factory virtual functions is read from the modules' vtables only, since no title that calls them has been run +- [Compute sequences](../../../core/libs/prx/libSceJobManager/Export.cpp) encode ahead across GPU barriers using explicit release packets instead of the native rewind protocol and completion-packet patching recovered from PPSA06888's module (offsets 0x2210 and 0x2650). Commands are recycled after a completed wait. Command capacity is a separate bound from the dispatch count: the native size query measures one register and a 31-character marker, then its exhaustion callback at 0x29d0 traps instead of growing storage. The fallback throws. Original-module execution with the host AGC builders accepts two dispatches with 255 registers and 65,535-character markers, then traps on a third. Console timing equivalence remains unverified. Compute dispatch on a CPU sequence throws. +- [Scheduling](../../../core/libs/prx/libSceJobManager/Export.cpp) uses host mutexes and condition variables instead of native atomic queues and semaphores; spin intervals deliberately use unscaled host TSC, while the original module reads scaled guest TSC under the host-only `APS5_TIME_SCALE` diagnostic. Blocks below 0x2d8 bytes, factory priorities above one, use of destroyed sequences and shutdown from a worker throw. New calls concurrent with shutdown are unverified. +- [Pool allocation](../../../core/libs/prx/libSceJobManager/Export.cpp) reuses freed nodes in a different order; profiler dispatch IDs can differ from native allocation. The fallback Wait deliberately allocates no pool node; native Wait can stall on global pool exhaustion. Resource-creation failures throw after cleanup, where the original does not check every return. Vtable order was checked, but no calling title or PS5 timing equivalence has been tested. +- Console behavior for [worker waits](../../../core/libs/prx/libSceJobManager/Export.cpp) is unverified. The owned original module stalls on the zero-spin, zero-persistence `worker_wait` fixture with native Windows and Linux host kernel services; the fallback wakes a spare worker and completes the child. The reference runner keeps this case selectable but excludes it from the shared-behavior parity suite. diff --git a/docs/dev/debt/libSceJpegDec.md b/docs/dev/debt/libSceJpegDec.md index e5b7252fa0..174718fdd4 100644 --- a/docs/dev/debt/libSceJpegDec.md +++ b/docs/dev/debt/libSceJpegDec.md @@ -3,8 +3,8 @@ ### Unknown function info - [sceJpegDecDecodeWithInputControl](../../../core/libs/prx/libSceJpegDec/Export.cpp) (libSceJpegDec) - its name and NID are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceJpegDec.c), but no credible prototype was found. It is declared without parameters solely to resolve known imports; this is not an ABI claim -- [libSceJpegDec](../../../core/libs/prx/libSceJpegDec/Export.cpp) (libSceJpegDec) - structure layouts follow [OpenOrbis `jpeg.h`](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9dd4a92695538bdeb09fb056d06dd11725/include/orbis/_types/jpeg.h#L13-L72) and [SharpProspero `JpegDec.cs`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Image/JpegDec.cs#L20-L117); attributes 1 and 2 are the values [SharpProspero's `JpegImage.cs`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Graphics/JpegImage.cs#L64-L74) passes. The error codes are libScePngDec's: no reference lists the JpegDec ones, and SharpProspero raises 0x80650020 on its JpegDec path where it raises libScePngDec's 0x80690020 on its PngDec path. Image info always reports color space 3, suitable CSC attribute 1 and zero component ids and sampling factors; the console's values, including which images it reports attribute 2 for, are unknown. The coefficient memory size is 0 because the host decoder needs no coefficient buffer; the size the console reports for progressive images is unknown. `max_image_width` is not checked, and the packed `width << 16 | height` decode result is libScePngDec's ### Functional -- [libSceJpegDec](../../../core/libs/prx/libSceJpegDec/Export.cpp) (libSceJpegDec) - decode modes other than 0 and down scales other than 1, 2, 4 and 8 throw, as does a down scale of 2, 4 or 8 on a CMYK or YCCK image, which libjpeg-turbo cannot decode to RGB. The down scale factors are the ones [SharpProspero `JpegDec.cs`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Image/JpegDec.cs#L57-L58) documents; the output size is the source size divided by the factor and rounded up, and the pixels come from libjpeg-turbo's scaled inverse DCT. The console's rounding, pixel values and the packed decode result for a down-scaled image (the output size here) are unverified. In `sceJpegDecDecode`, rejecting an output `image_mem_addr` not aligned to 4 bytes and rejecting an `alpha_value` above 0xFF follow libScePngDec validation and are not verified from a libSceJpegDec binary disassembly +- [libSceJpegDec](../../../core/libs/prx/libSceJpegDec/Export.cpp): work-memory size, alignment, creation checks, image-info fields, progressive coefficient-buffer capacity, row-buffer capacity and scaled dimensions are recovered from the native module. The host still allocates its decoder storage separately rather than using the guest work and coefficient regions. Malformed-header diagnostics and output mutation on failure are not fully matched. +- [sceJpegDecDecode](../../../core/libs/prx/libSceJpegDec/Export.cpp): decode mode 1 and pixel formats 9, 10 and 11 remain unimplemented and throw. Exact decoded pixel values and color conversion are not verified against the native decoder. diff --git a/docs/dev/debt/libSceJpegEnc.md b/docs/dev/debt/libSceJpegEnc.md index 2bff53d167..28c35b84a2 100644 --- a/docs/dev/debt/libSceJpegEnc.md +++ b/docs/dev/debt/libSceJpegEnc.md @@ -1,5 +1,5 @@ # libSceJpegEnc technical debt -### Unknown function info +### Unimplemented functionality -- [sceJpegEncEncode](../../../core/libs/prx/libSceJpegEnc/Export.cpp) (libSceJpegEnc) - `restart_interval` follows SharpProspero's field doc (0 none, -1 every row of blocks, positive a block count); a positive value is taken as the JPEG DRI interval in MCUs and -1 as one restart per MCU row, which no reference confirms. Other negative values throw +- [sceJpegEncEncode](../../../core/libs/prx/libSceJpegEnc/Export.cpp) (libSceJpegEnc) - MJPEG mode throws; its encoding path is not implemented. Restart intervals are confirmed by the native validator, MCU counter and DRI writer: 0 disables restart markers, -1 selects one MCU row, and 1 through 65535 specify an MCU count. Other values return the invalid-parameter error. A successful call returns the JPEG's size, as a PS4 Pro (firmware 10.71) does; this is not confirmed on a PS5; the size itself comes from this encoder, so it differs from the console's for the same image (the console 633 bytes, this encoder 650, for an 8x8 RGBA image at 4:2:0). diff --git a/docs/dev/debt/libSceJson2.md b/docs/dev/debt/libSceJson2.md index 3a89284e01..bb2d163782 100644 --- a/docs/dev/debt/libSceJson2.md +++ b/docs/dev/debt/libSceJson2.md @@ -2,16 +2,83 @@ ### Unknown function info -- [sce::Json::Parser::parse with a read callback](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the callback's contract is taken from PPSA04540's: it returns 0 with the next character, and at the end of its data writes `'\0'` and returns -1. Characters are read until the callback returns non-zero or gives `'\0'`, and the text read is then parsed as by the `(text, size)` overload, so trailing characters after the value are rejected as there. Whether the console stops reading at the end of the top-level value, and what it returns when the callback fails before the end, are unknown -- [sce::Json::Parser::parse](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - a null text pointer or zero size returns `JsonErrorParse` (0x80920101) without modifying the destination Value; whether the console distinguishes a null pointer from a syntax error with a separate parameter error code is unknown. A number that overflows a double (e.g. `1e309`) throws: whether the console rejects the document or stores an infinite real is unknown -- [sce::Json::InitParameter2, InitParameterRtti and InitParameterRtti2](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - layouts inferred, not known. `InitParameter2` is taken as the 40-byte object of [SharpEmu](https://github.com/sharpemu/sharpemu/blob/1811cef94435db7655209fb39f65ddf258b93fd6/src/SharpEmu.Libs/Json/JsonExports.cs) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/eda6ba37ece127ab0396378404d99182e3e8a530/src/libs/libJson2.cpp): the allocator, its user data, the file buffer size, the 32-bit special float format type at 0x18 and 12 bytes both zero, and both constructors zero all of it. `InitParameterRtti2::setAllocatorRtti` writes the allocation parameters and user data where `setAllocator` writes the allocator, as both references do; whether the console also marks the object as using RTTI allocation is unknown. `InitParameterRtti`'s constructor writes the allocation parameters, user data and file buffer size to the first 24 bytes, an order inferred from its mangled arguments. The values of `SpecialFloatFormatType` and what they select are unknown, so `setSpecialFloatFormatType` stores any value and nothing reads it: `Initializer::initialize(InitParameter2)` ignores every field as `initialize(InitParameter)` does, serializing a non-finite real throws whatever the type, and whether the type lets the parser accept NaN or infinity is unknown. The setters return nothing; whether the console's return `this` is unknown -- [sce::Json::Object::Pair, sce::Json::Array::iterator and sce::Json::Object::iterator members](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the signatures follow the mangled names, with `Array::iterator::operator++(int)` and `Object::iterator::operator++(int)` returning their copy through a result pointer before `this` as `begin` does, since the iterators have non-trivial destructors. `Pair(const String&, const Value&)` and default `Pair()` deep-copy or initialize into the layout `Object` iteration already hands out (key at 0, value at 0x10) and zeroes the 8 bytes between them; what the console keeps there is unknown. Default-constructed iterators are singular +- [JSON real-number conversion](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) uses locale-independent `to_chars` with 17 significant digits and `from_chars`, following JSON's decimal-point grammar in RFC 8259 section 6. Out-of-range input falls back to a classic-locale stream so underflow retains the prior signed-zero behavior and overflow throws. Console rounding and formatting are unverified. +- [sce::Json::Array::pop_front](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - removes and destroys the first element; on an empty array it throws `std::out_of_range`, as `front` and `back` do here. The console's result for an empty array is unverified. +- [sce::Json::Parser::parse](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - a null text pointer with nonzero size throws instead of being dereferenced. A number that overflows a double (e.g. `1e309`) throws: whether the console rejects the document or stores an infinite real is unknown. Syntax validation for number tokens and destination state after a failed parse still differ from the system parser +- [sce::Json::InitParameter2, InitParameterRtti and InitParameterRtti2](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - RTTI allocator adaptation and the `InitParameterRtti` layout remain unverified. `setAllocatorRtti` stores the raw allocation-parameter pointer as the allocator; `initialize` ignores allocator, buffer-size and special-float settings. Special-float enum meanings and parser support for non-finite numbers remain unresolved; serialization of non-finite values throws. Setter return types remain unverified. +- [sce::Json::Object::Pair, sce::Json::Array::iterator and sce::Json::Object::iterator members](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the signatures follow the mangled names, with `Array::iterator::operator++(int)` and `Object::iterator::operator++(int)` returning their copy through a result pointer before `this` as `begin` does, since the iterators have non-trivial destructors. `Pair(const String&, const Value&)` and default `Pair()` deep-copy or initialize into the layout `Object` iteration already hands out (key at 0, value at 0x10) and zeroes the 8 bytes between them; what the console keeps there is unknown. - [sce::Json::Value::setTypeMismatchHandler, setElementAccessFailureHandler and setSpecialFloatHandler](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - members of `Value` that return an int, inferred from a caller that registers them on its root `Value` before parsing into it, with the handler and its context, and accepts 0 or 0x80848104 as success; they return 0. The PS Vita library's `Value::setNullAccessCallback` is such a member too ([vitasdk](https://github.com/vitasdk/vita-headers/blob/15605617cbbb23d266f88af28899fdb573e2355e/include/psp2/json.h#L638)). The handler signatures follow the mangled names. A handler applies to the `Value` it is set on and to the values that `Value` holds then, and `Parser::parse` gives the values it creates the handlers of the `Value` it parses into, since the title reads the errors its handlers record after accessing parsed members; copies, `push_back` and `Object::operator[]` do not carry them, and how the console finds the handler of an accessed value is unknown. A typed getter on a non-null value of another type calls the type mismatch handler with the requested type, the value, null and the context, and returns the value the handler returns if it has the requested type, else the getter's default; passing null as the third handler argument (the second `Value` pointer), and the order of the two `Value` pointers, remain unverified against the console. The element access failure handler is not called: a failed `operator[]` or `getValue` on a value carrying it throws, as the error code the console passes, which the title records as its error, is unknown; without one the access still reaches the null-access callback. The special float handler is not called: serializing a non-finite real throws with or without it, as the `FunctionType` values and the `bool*` output are unknown, and the title's handler returns a different replacement for each of its two types - [sce::Json::MemAllocator::notifyError](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the base class's virtual does nothing. A title's allocator that does not override it calls it through its own vtable; the library never calls the title's allocator, so it never reports an allocation failure. Whether the console's base implementation logs anything is unknown -- [sce::Json::String size, capacity, resize, compare and operator==(const String&), append(const char*, size_t), operator+=; Value::swap; Array::iterator and Object::iterator copy constructor, advance and operator==](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the signatures follow the mangled names and the behaviour is that of the matching `std::string` or container operation, with no cited source in KytyPS5, shadPS4, SharpProspero or disassembly. `append(text, length)` throws on a null text with a non-zero length; `operator+=` returns the String; `Value::swap` exchanges the node pointers; `advance` moves the iterator in place, returns it, and does not check the distance against the end. Whether the console returns the iterator from `advance` and how it bounds it is unverified -- [sce::Json::Value::operator bool](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - member of `Value` returning `bool` as a conversion operator, with no cited source in KytyPS5, shadPS4, SharpProspero, or disassembly: evaluates to `false` for `TypeNull` and boolean `false`, and `true` for boolean `true`. For all other value types it currently returns `true`; whether the console converts them, throws, or checks container emptiness is unverified -- [sce::Json::Object::insert and sce::Json::Object::erase](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - signatures and behaviors have no cited source in KytyPS5, shadPS4, SharpProspero, or disassembly: `insert` returns an iterator through a result pointer before `this` as `Object::begin` does, and takes a const Pair reference. If the key exists, it updates the existing value and returns an iterator to it; otherwise it appends a deep copy of the pair and returns an iterator to the new element; whether the console updates existing keys, appends duplicates, or ignores duplicate insertion is unverified. `erase` removes the item with matching key and returns 1 if found and removed, or 0 if not; whether the console returns size_t, bool, or an iterator is unverified +- [sce::Json::String size, capacity, resize, append(const char*, size_t), operator+=](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - storage uses `std::string`; capacity and allocation growth do not match the system 32-bit capacity and allocator settings. Appending null text with a nonzero length throws instead of dereferencing it. +- [sce::Json::String at, find and rfind](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the return types follow the PS Vita library's `String` ([vitasdk](https://github.com/vitasdk/vita-headers/blob/15605617cbbb23d266f88af28899fdb573e2355e/include/psp2/json.h#L338)) and the search follows `std::string`. `at` past the end and a null text or `String` throw; what the console returns or does there is unverified. +- [sce::Json::Array::iterator::advance and Object::iterator members](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the return type of `advance` remains unverified. Object iterator advancement past the end throws instead of asserting. Dereferencing an empty array iterator throws instead of returning the system null-value pointer or an invalid address. +- [sce::Json::Value::operator bool](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - callback dispatch before conversion is not implemented. The system library invokes the applicable type mismatch handler before converting a non-boolean value, with SDK-dependent global callback handling. -### Functional +### Unknown 3.20 import declarations -- [sce::Json::InitParameter2 base-object constructor](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - forwards to the complete-object constructor and uses its inferred layout and default field values; constructor equivalence and initialization bytes remain unverified on the console +- [_ZN3sce4Json11Initializer24setAllocatorInfoCallBackEPFviNS0_9ValueTypeEPvES3_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `R996H0YIhiQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json11Initializer28setGlobalSpecialFloatHandlerEPFKNS0_5ValueENS0_12FunctionTypeEdPS3_PvPbES6_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `i1393UBWu1U` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json11Initializer28setGlobalTypeMismatchHandlerEPFRKNS0_5ValueENS0_9ValueTypeEPS3_S6_PvES7_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `tck9g-yOqPg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json11Initializer36setGlobalElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKNS0_5ValueES9_PvESA_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `j8Q7SBgMm28` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json11InitializerC2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `6qFqND4iwPA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json11InitializerD2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `qQZGdYkEytk` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json12MemAllocatorD0Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `ypazoTrKYhE` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json12MemAllocatorD1Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `O1uwoqazS2A` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json17InitParameterRttiC2EPNS0_14AllocParamRttiEPvm](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `Mp8DPb3M4I0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json19InternalInitializer19terminate_staticlibEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `dvp2zGSQzMs` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `BPSPc3pUMJo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json19InternalInitializerC1Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `WntRKhYTmZw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json19InternalInitializerC2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `2n+fxNdSZfs` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json19InternalInitializerD1Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `yudT4-bglko` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json19InternalInitializerD2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `caP-y4fH-zQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json4FreeEPv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `bnk+edDbqMk` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Array10push_frontERKNS0_5ValueE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `MQxDxBzWz40` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Array5eraseERKNS1_8iteratorE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `2mbVAtshWRI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Array6insertERKNS1_8iteratorERKNS0_5ValueE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `ixJAz92H1uE` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Array8iteratorC2ERKS2_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `Hdc3DSIsG2I` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Array8iteratorC2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `JSg1MSo7NKE` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Array8iteratorD2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `9niNrXsF3Bc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Array8pop_backEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `woec1toi4Xw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Array9pop_frontEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `pPs3qZTZQvQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ArrayC2ERKS1_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `6NlbpyIfUuY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ArrayC2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `t6AHhkWu4mY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ArrayD2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `MIyMPw7YTGM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Value11referStringEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `m5J28iw-QPs` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Value12referBooleanEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `QxjfcfBhEdc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Value12referIntegerEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `R0ac5QOdlpo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Value13referUIntegerEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `Nx5tez4siL4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Value9referRealEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `t7UEJrhojhk` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `PyMbL9F2oBY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `l1URiQJlSoU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2ENS0_9ValueTypeE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `PWm9MyJJVqU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2EPKc](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `+hHa4QYtLY8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2ERKNS0_5ArrayE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `VH9dsKpQTmk` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2ERKNS0_6ObjectE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `JXlrCLLCLmc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2ERKNS0_6StringE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `veNviXt6wC0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2ERKS1_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `TZyuFeGuw9Y` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2Eb](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `OK4Ot0ue7J0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2Ed](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `23nm4oXHlfI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2El](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `EjxUpzR2Yx8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2Em](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `cqucBPSeVDA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueC2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `-wa17B7TGnw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json5ValueD2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `0eUrW9JAxM0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6MallocEm](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `Xzy0onDzSAc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `lt3J4dPvEtM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6Object4PairC2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `Wu-urs6MMpQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6Object4PairD2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `TM0Sj+Ebins` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6Object8iteratorC2ERKS2_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `r5CShmcua+M` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6Object8iteratorC2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `OxO+V0ouoS4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6Object8iteratorD2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `patTzc7zBjw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6ObjectC2ERKS1_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `tSRqQ-+Y4PE` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6ObjectC2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `SkKczWHdklc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6ObjectD2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `1siRBursJjw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6Parser5parseERNS0_5ValueEPKc](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `LB3jxppxyKU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6StringC2EPKc](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `N78gkQzXRL4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6StringC2ERKS1_](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `gALFgNfF+9I` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6StringC2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `eG9E9M6XvTM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZN3sce4Json6StringD2Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `Ui7YFnSTCBw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNK3sce4Json14InitParameter211getUserDataEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `GWtzpTKbZ+0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNK3sce4Json14InitParameter212getAllocatorEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `BykPm1ajcM0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNK3sce4Json14InitParameter217getFileBufferSizeEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `Pcg6xQUhq9E` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNK3sce4Json14InitParameter225getSpecialFloatFormatTypeEv](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `PDkib0zyABg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNK3sce4Json5ValueixERKNS0_6StringE](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `clF7J7N9xXE` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 20faabeefb..0fbc29eb6c 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -2,4 +2,8 @@ ### Unknown function info -- [libSceJxr pending exports](../../../core/libs/prx/libSceJxr/Export.cpp) - 4 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceJxr.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceJxr pending exports](../../../core/libs/prx/libSceJxr/Export.cpp) - 4 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceJxr.c) and were cross-checked against the same module in PS5 firmware 13.60. `PixelFormatLookup`, `readIS` and `writeIS` have unverified signatures and behaviour; their declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. + +### Functional + +- [PKFree](../../../core/libs/prx/libSceJxr/Export.cpp) (libSceJxr) follows Microsoft's jxrlib [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L53-L62), whose four exported names the module shares: a non-null `ppv` has `*ppv` released with the guest `free` and set to null, and the result is always `WMP_errSuccess` (0) as a 64-bit `ERR` (`long`). That the console's module is built from jxrlib, and that its `PKFree` releases through libc's `free`, is inferred from the export names and not checked against the console's code. Closes when: the console's `PKFree` is disassembled or measured on a PS5 diff --git a/docs/dev/debt/libSceKeyboard.md b/docs/dev/debt/libSceKeyboard.md index a0a894689e..12c1ff0c79 100644 --- a/docs/dev/debt/libSceKeyboard.md +++ b/docs/dev/debt/libSceKeyboard.md @@ -2,9 +2,11 @@ ### Functional -- [sceKeyboardGetKey2Char](../../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates the 101-key (US) arrangement and the alphanumeric layer of the 106-key (Japanese) one, which follows the JIS layout of the HID usages: the Yen (0x89) and Ro (0x87) keys return `\` (0x5C) unshifted, as JIS X 0201 places the yen sign there; whether the console returns U+00A5 instead is unverified. With the Kana LED (bit 4, the HID position) set the 106-key arrangement throws, as the kana the console returns are unknown. Ctrl and Alt do not change the character. +- [sceKeyboardGetKey2Char](../../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp): initialization, output length and reserved bytes remain unverified on a console; unconverted keys retain the host's length of 0. One title (PPSA15246) disassembled for this entry calls it after clearing the first 20 bytes of its output buffer (16-byte store 0x69942d, 32-bit store 0x699435; no size argument), uses a character only when the result is 0 and the first output byte is nonzero, and then reads the 16-bit value at offset 8. Which other output fields it reads and what its other arguments mean are not shown. ### Unknown function info - [sceDbgKeyboardClose, sceDbgKeyboardGetKey2Char, sceDbgKeyboardInit, sceDbgKeyboardOpen, sceDbgKeyboardRead, sceDbgKeyboardReadState](../../../core/libs/prx/libSceKeyboard/Export.cpp) (libSceKeyboard) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceKeyboard.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. -- [sceKeyboardConnectPort, sceKeyboardDebugGetDeviceId, sceKeyboardDeviceOpen, sceKeyboardDisconnectAll, sceKeyboardDisconnectDevice, sceKeyboardDisconnectPort, sceKeyboardGetConnection, sceKeyboardGetDeviceInfo, sceKeyboardGetHandle, sceKeyboardMbusInit, sceKeyboardPadEmulateClose, sceKeyboardPadEmulateOpen, sceKeyboardSetProcessFocus, sceKeyboardSetProcessPrivilege](../../../core/libs/prx/libSceKeyboard/Export.cpp) (libSceKeyboard) - throwing stubs declared without parameters from the firmware export database (aerolib.csv); their guest signatures are unverified on hardware +- [sceKeyboardGetDeviceInfo and sceKeyboardSetProcessFocus](../../../core/libs/prx/libSceKeyboard/Export.cpp): pointer arguments are passed to device ioctls. Device-info storage is 168 bytes in the connection-query caller, but its fields and the process-focus layout remain unresolved. +- [sceKeyboardConnectPort, sceKeyboardDisconnectPort and sceKeyboardDeviceOpen](../../../core/libs/prx/libSceKeyboard/Export.cpp): argument widths are confirmed. The meaning/order of the two 32-bit port arguments and the final 32-bit device-open argument still require the device-service backend. +- [sceKeyboardPadEmulateClose and sceKeyboardPadEmulateOpen](../../../core/libs/prx/libSceKeyboard/Export.cpp): the supplied keyboard library has neither export; their signatures remain unresolved. diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index ff8c271f1f..3ac4b14e2d 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -7,13 +7,16 @@ ### Functional +- [std::filesystem::_Xfilesystem_error](../../../core/libs/prx/libSceLibcInternal/Filesystem.cpp) (libSceLibcInternal, NIDs `B0CeIhQty7Y` and `C6-7Mo5WbwU`) - both overloads still throw `NotImplemented`: on the console they throw `std::filesystem::filesystem_error`, whose guest type information and layout this library does not provide yet. The filesystem primitives beside them follow the PS5 13.60 module - [makecontext](../../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) follows FreeBSD 11's `lib/libc/amd64/gen/makecontext.c`: at most six arguments, a stack of at least `MINSIGSTKSZ` (2048) bytes, and `exit(0)` when the function returns with no `uc_link`. The console's version is not verified - [makecontext](../../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) on Windows pins its stack writable until a later `makecontext` stack overlaps it, since nothing tells the library when the guest frees the stack - [Thread exit destructors](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped - [fdopendir](../../../core/libs/prx/libSceLibcInternal/Directory.cpp) (libSceLibcInternal) follows FreeBSD 11's [`fdopendir`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/lib/libc/gen/opendir.c#L67-L74): it sets `FD_CLOEXEC` on the descriptor first, and the stream starts at the descriptor's current position instead of the first entry. On Windows the stream skips the entries the descriptor's cursor (`lseek` or `getdirentries`) has passed, which assumes that the descriptor and the stream list a directory in the same host order; on Linux the stream is opened on a duplicate of the descriptor, which shares its position. The Linux path is not run by the tests here. As in FreeBSD, a descriptor that is not open fails with `EBADF`, one that is not a directory with `ENOTDIR` (both leave it open, and `FD_CLOEXEC` stays set on the latter), and `closedir` closes it - [fstatvfs](../../../core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp) (libSceLibcInternal) converts `_fstatfs` to the 88-byte `struct statvfs` as FreeBSD 9.2's `sfs2svfs` does (`f_bsize` from the transfer size, `f_frsize` from the block size, `f_favail` equal to `f_ffree`, `f_fsid` 0, `ST_RDONLY` and `ST_NOSUID` only). Differences: `f_namemax` is the `statfs` value, not `fpathconf(_PC_NAME_MAX)`; negative `f_bavail` and `f_ffree` wrap to unsigned without `EOVERFLOW`, as the same-width copy in FreeBSD does; inode counts are 0 on Windows, inherited from `_fstatfs`; a null output returns `EFAULT` after the descriptor is validated, where FreeBSD would fault. Layout and conversion are unverified against the PS5 library (PS5MC imports `fstatvfs` but was not run) -- [posix_spawn and posix_spawnp](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) (libSceLibcInternal) create no process. FreeBSD 11.4 returns the error the child's exec leaves ([`_posix_spawn_thr`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/posix_spawn.c#L227-L231), [`do_posix_spawn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/posix_spawn.c#L295-L303)), and `posix_spawnp` searches `PATH`, or `_PATH_DEFPATH` when it is unset, ending with `ENOENT` when no directory holds the program ([`_execvpe`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/exec.c#L272-L281), [`execvPe`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/exec.c#L257-L260)). So when the program is not in the guest file system both return `ENOENT` and leave the process ID unwritten; when it is there they throw. PPSA99305 (Dusklight) reaches this when SDL looks for `zenity` to show a file dialog. The attributes and file actions are not applied, so one that would fail in the child is not reported, and whether the console's application sandbox lets the child be created at all (FreeBSD returns that error first) is unverified. The attribute and file action calls keep FreeBSD 11.4's objects. Null or uninitialized objects, which FreeBSD dereferences, throw. +- [posix_spawn and posix_spawnp](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) (libSceLibcInternal) create a native child running the requested program's converted image, which must be under `/app0/` (`/app0/eboot.bin` maps to the running image); `posix_spawnp` searches `PATH` as FreeBSD 11's `_execvpe`, and the FreeBSD 11.4 file actions and attributes are applied to the child. A null `argv` returns `EFAULT`; a null `envp` inherits the parent environment; guest process IDs are the host's. Limits: the child executes the conversion made at relink time; Windows redirects only descriptors 0 to 2 and starts the child on the parent's std handles; Linux applies only `POSIX_SPAWN_RESETIDS` and `POSIX_SPAWN_SETPGROUP`, other flags or a nonzero set on Windows throw. The console sandbox's effect on child creation is unverified, and null or uninitialized objects, which FreeBSD dereferences, throw. PPSA99305 (Dusklight) reaches this when SDL looks for `zenity` - [getopt, getopt_long](../../../core/libs/prx/libSceLibcInternal/Getopt.cpp) (libSceLibcInternal) follow FreeBSD 11 [getopt.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/getopt.c) and [getopt_long.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/getopt_long.c) with `GNU_COMPATIBLE`: `getopt` does not permute, `getopt_long` permutes unless `POSIXLY_CORRECT` is set in the guest environment or the option string starts with `+`, and the exported `opterr`, `optind`, `optopt`, `optreset` and `optarg` start at 1, 1, 0, 0 and null. Warnings go to the guest's current `stderr` prefixed with `__progname`, where FreeBSD's `warnx` keeps the `stderr` of its first call. A null `getopt` option string, a null argument vector or argument, a negative `optind` and an `optind` moved inside a pending permutation throw where FreeBSD faults. `getopt_long_only` is not exported. That the console's `libSceLibcInternal` uses this FreeBSD code is unverified - [posix_spawnattr_getflags, getpgroup, getschedparam, getschedpolicy, getsigdefault, getsigmask, setpgroup, setschedparam, setschedpolicy and setsigmask](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) (libSceLibcInternal) read and write the 0x30-byte attribute object at the offsets of the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x8D860 to 0x8D910: flags at 0, process group at 4, scheduling parameter at 8, policy at 0xC, default and masked signal sets at 0x10 and 0x20), which never fail there and return 0; the layout matched on a PS5 (firmware 13.60). Uninitialized attributes and null pointers, which the console dereferences, throw as the existing setters do - [sys_siglist and sys_nsig](../../../core/libs/prx/libSceLibcInternal/SignalList.cpp) (libSceLibcInternal) hold the 32 descriptions and the count of [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/siglist.c#L73-L107) `siglist.c` (`NSIG` 32, signal 0 to `SIGUSR2`); the console's table was not read +- [_err, _warn](../../../core/libs/prx/libSceLibcInternal/Err.cpp) and [_getprogname](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) are assumed to be the same functions as `err`, `warn` and `getprogname` and run their code; the console's module was not read. FreeBSD 11.4 defines the underscore names and makes the public names weak references to them ([`_err`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/err.c#L70-L79), [`_warn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/err.c#L135-L144), [`_getprogname`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/getprogname.c#L10-L17)) - [inet_aton, inet_addr, inet_ntoa and inet_ntoa_r](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal) follow the FreeBSD code in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x8ADE0 to 0x8B3D0) and matched it on a PS5 (firmware 13.60) for 20 `inet_aton` inputs; a null text, which the console dereferences, throws. `inet_ntoa_r` formats through libkernel's `__inet_ntop`, so a null buffer with room for the text throws and a null buffer that is too small sets `ENOSPC`, as `__inet_ntop` does for a null destination; the console's `inet_ntoa_r` was not measured with a null buffer +- [__inet_ntoa_r](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal) was not measured on the console: it runs `inet_ntoa_r`, as FreeBSD 11.4 names the function `__inet_ntoa_r` ([arpa/inet.h](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/arpa/inet.h#L129)) and makes `inet_ntoa_r` a weak alias of it ([inet_ntoa.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_ntoa.c#L62-L76)) diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index b8330484a5..bd201c0765 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,4 +2,19 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 399 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 342 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw +- [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults +- [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. +- [RAND_add, RAND_bytes, RAND_status, RAND_load_file, RAND_file_name, CRYPTO_malloc, CRYPTO_free, CRYPTO_set_mem_functions](../../../core/libs/prx/libSceLibreSsl/src/Runtime.cpp) (libSceLibreSsl) - follow LibreSSL's [rand_lib.c, randfile.c](https://github.com/libressl/openbsd/tree/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/rand) and [malloc-wrapper.c](https://github.com/libressl/openbsd/blob/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/malloc-wrapper.c), which are the same in 2.6.5 (3.4.3 linked); nothing is checked on the console. `RAND_bytes` reads host entropy and `CRYPTO_malloc` and `CRYPTO_free` use the guest heap. `CRYPTO_malloc` returns null for a size of zero or less, as LibreSSL 2.5.4 to 3.4 do; later versions take a `size_t` size and call `malloc`. A null buffer with a positive length throws +- [AES_set_encrypt_key, AES_set_decrypt_key, AES_encrypt, AES_decrypt, AES_ecb_encrypt, AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt](../../../core/libs/prx/libSceLibreSsl/src/Aes.cpp) (libSceLibreSsl) - follow LibreSSL's [aes.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/aes/aes.h) and [mode code](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/modes). Nothing is checked on the console. Key setup follows [LibreSSL 2.5.4's aes_core.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/aes_core.c), the version the `X509_verify_cert_error_string` entry names: a null pointer returns -1 and a key length other than 128, 192 or 256 bits returns -2 before the key is written, and only the 44, 52 or 60 words the key length needs are written. The `AES_KEY` schedule uses that file's generic C layout (big-endian words, `rounds` 10, 12 or 14). The 2.5.4 amd64 build uses [aes-x86_64](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/asm/aes-x86_64.pl) instead, which copies the key bytes without swapping, so each word is byte-reversed compared with the C layout (OpenSSL 3.0.13's x86-64 code gives the same schedules byte-reversed); the console's build options are unknown, so a schedule the title reads or fills is unverified. CTR leaves the last keystream block in `ecount_buf` even at a block boundary. Null pointers in the cipher and mode functions, other `rounds` values and a CFB or CTR position outside 0-15 throw +- [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names +- [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)` + +- [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. + +### Functional + +- [ASN1_INTEGER_get](../../../core/libs/prx/libSceLibreSsl/src/Asn1.cpp) has no console verification for nonpositive lengths. It follows [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/a_int.c#L363-L389): valid integer types return zero without reading data when length is zero or negative; invalid types return -1 first. Later LibreSSL versions reject negative lengths through `ASN1_INTEGER_get_int64`; that later contract is not used for this library. + +- [ASN1_INTEGER_get and ASN1_STRING_data/length/type/to_UTF8](../../../core/libs/prx/libSceLibreSsl/src/Asn1.cpp) are unverified on PS5. Signatures, the 24-byte guest string layout and conversions follow [LibreSSL 2.5.4's ASN.1 sources](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1), the version reported for firmware 13.40 in [discussion #8981](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Null accessor inputs, null conversion outputs, missing nonempty data, oversized converted output and unrepresentable signed integer values throw where the source has undefined behaviour. Malformed encodings and allocation failure throw until ASN.1 error-queue reporting is implemented. Conversion buffers use the guest libc allocator and can be released with guest `free`. diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index 3fbd00e901..c98952a914 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -2,4 +2,9 @@ ### Unknown function info -- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 87 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 84 named callable exports, excluding `CRYPTO_memcmp`, `MD5` and `SHA1`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. + +### Functional + +- [CRYPTO_memcmp](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) follows the public [LibreSSL signature and equality contract](https://man.openbsd.org/CRYPTO_memcmp.3), without PS5 verification. The exact nonzero result for unequal buffers is unspecified by that contract. Content-independent execution of its volatile byte reads still depends on the compiler; inspect optimized code when changing toolchains. Closes when: measured on a PS5 and the supported toolchains' generated code is checked. +- [MD5 and SHA1](../../../core/libs/prx/libSceLibreSsl3/src/Digest.cpp) (libSceLibreSsl3) - follow LibreSSL's [md5.c, sha1.c](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto) and hash through libSceLibreSsl's `MD5_*` and `SHA1_*`. A null digest buffer, which LibreSSL 4.0.0 does not check, throws. Nothing is checked on the console diff --git a/docs/dev/debt/libSceLoginDialog.md b/docs/dev/debt/libSceLoginDialog.md index 07a38ff414..c9408a3d58 100644 --- a/docs/dev/debt/libSceLoginDialog.md +++ b/docs/dev/debt/libSceLoginDialog.md @@ -7,4 +7,4 @@ ### Unknown function info -- [sceLoginDialogOpen, sceLoginDialogGetResult](../../../core/libs/prx/libSceLoginDialog/Export.cpp) - the 0x8134xxxx codes, the Open param checks (size 0x40, word 1 below 2, words 0x2C to 0x3C zero) and the `{1, 0x81340007}` result words are the ones [prosper](https://github.com/mattias800/prosper/blob/ed42e499888d5a86792c3e1b2df3aebd8ca1a681/prosper/src/hle/service/hle_service.cpp#L805-L874) reads from the shipped module. The order of the param and state checks follows prosper's handlers and is not stated separately, the meaning of word 1 and of the second result word is unknown, words 2 to 10 are not checked, and Open's busy result is never reached because the dialog never runs. Closes when: the module's check order is read or measured on a PS5 +- [sceLoginDialogOpen, sceLoginDialogGetResult](../../../core/libs/prx/libSceLoginDialog/Export.cpp) - the 64-byte input and 16-byte result layouts are recovered. The roles of the nine user-ID slots and the eight trailing result bytes require service-side analysis. The UI stub returns the native disconnected-service cancellation result and does not populate successful login data. diff --git a/docs/dev/debt/libSceLoginService.md b/docs/dev/debt/libSceLoginService.md index f33713e90d..db043fd9c7 100644 --- a/docs/dev/debt/libSceLoginService.md +++ b/docs/dev/debt/libSceLoginService.md @@ -6,4 +6,4 @@ ### Unknown function info -- [sceLoginServiceRequestDevices](../../../core/libs/prx/libSceLoginService/Export.cpp) (libSceLoginService) - PPSA34015 passes one pointer to a stack buffer (`lea rdi, [rbp-0x80]`) then calls `sceLoginServiceTerminate`. The buffer layout, other arguments and console errors are unknown, so every call throws without reading or writing caller memory +- [sceLoginServiceRequestDevices](../../../core/libs/prx/libSceLoginService/Export.cpp): the native wrapper confirms one read-only request pointer and transfers exactly 64 bytes to the login service. Null input returns 0x813a0003 before the initialization check; an uninitialized service returns 0x813a0001. The request fields are interpreted by the service backend and remain unresolved. The protected export body is unchanged. diff --git a/docs/dev/debt/libSceMbus.md b/docs/dev/debt/libSceMbus.md index 1ae13da251..9078d4ebc6 100644 --- a/docs/dev/debt/libSceMbus.md +++ b/docs/dev/debt/libSceMbus.md @@ -6,6 +6,22 @@ ### Unknown function info -- [libSceMbus](../../../core/libs/prx/libSceMbus/Export.cpp) (`libSceDeviceService`) - signatures taken from PPSA01317, which calls `sceDeviceServiceInitialize(3, 0)`, polls `sceDeviceServiceGetEventState(1)` and queries again when bit 0 or 1 is set, and calls `sceDeviceServiceQueryDeviceInfo_(0x7001, 0, 0, infos, 1, &count, &unknown, 0x70)`, treating a negative result as an error and `count` as the number of entries. The host has no devices: the event state is 0 and a query returns 0 with `count` 0 +- [sceDeviceServiceQueryDeviceInfo_](../../../core/libs/prx/libSceMbus/Export.cpp): the 112-byte device-info layout and the third argument's meaning remain unresolved. The second argument is a six-byte HID usage filter pointer, not an integer. Initialization takes an optional 16-byte parameter structure with an optional thread configuration; custom scheduling throws because the host has no event worker. `sceDeviceServiceGetEventState` takes a clear flag, not a device type. -- [libSceMbus firmware 3.20 pending exports](../../../core/libs/prx/libSceMbus/PendingExports.cpp) - 115 symbol names are present in [the PS5 3.20 libSceMbus export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceMbus.c). Their signatures and console behaviour are unverified. The declarations use the existing no-parameter unknown-signature convention solely to resolve imports, and every call throws through `NotImplemented_nid_no_patch`. +- [sceMbus remaining declarations](../../../core/libs/prx/libSceMbus/PendingExports.cpp): SetDeviceFunctionState takes a 64-bit device identifier and a 180-byte aggregate by value on the stack. Its service-side fields and source alignment remain unresolved; the declaration preserves the observed stack ABI with opaque storage. AcquireControlList and Term return constants without reading incoming arguments, so their original parameter lists cannot be established from the callees. All protected stub bodies remain unchanged. + +- [sceMbusAddHandleByDeviceId](../../../core/libs/prx/libSceMbus/PendingExports.cpp): the six-argument ABI is confirmed, including the 64-bit device ID. The meanings of the last three 32-bit fields require the device-service backend. + +- [sceMbus control and handle registration](../../../core/libs/prx/libSceMbus/PendingExports.cpp): argument positions and transmitted widths are recovered for control acquisition, handle registration, process attachment, user binding and device-ID conversion. Several scalar field meanings still depend on the service protocol; the 64-bit control value and unnamed registration fields must not be interpreted as pointers. AcquireControlList returns a constant error without reading its incoming arguments, so its source signature remains unresolved. + +- [sceMbus debug process and device queries](../../../core/libs/prx/libSceMbus/PendingExports.cpp): process information is transmitted as 16 bytes, startup input/output as eight bytes, control status as 24 bytes, internal information as 2832 bytes and priority information as 3088 bytes. Device enumeration writes a 32-bit count followed by 396-byte entries. These pointee fields and the semantics of unnamed process flags remain unresolved; declarations use distinct incomplete types instead of guessing their fields. + +- [sceMbus event subscriptions](../../../core/libs/prx/libSceMbus/PendingExports.cpp): the subscription transports two 32-bit and two 64-bit fields whose service-side meanings remain unresolved. EventCreate uses 80-byte queue records and copies a 16-byte callback table; event fields still require the dispatch path; the overflow callback takes an event handle and user data, and the event callback additionally takes a pointer to the queued event. EventCallbackFuncsInit only clears memory and has no defined return value; its protected stub retains the existing integer return. Eight dump/BGM wrappers have confirmed zero-argument ABIs. EventBusStatusChangeUnsubscribe ignores its first incoming argument and consumes a 32-bit second argument; its first parameter type remains unresolved. + +- [sceMbus device and driver payloads](../../../core/libs/prx/libSceMbus/PendingExports.cpp): device-info records occupy 256 bytes, descriptions 264 bytes, simulated-handle status and Spark state 16 bytes each. Their service-side fields remain unresolved. Driver registration copies four callback pointers; driver-event notification transports two 64-bit values without dereferencing them. Init, IsBgmProhibited, ReenableBgmForShellUi and RemoveCameraAppModuleFocus consume no incoming arguments. + +- [sceMbus settings](../../../core/libs/prx/libSceMbus/PendingExports.cpp): USB power-list entries occupy 12 bytes, with an 8-bit count limited to 100; their fields remain unresolved. Several routing, scratch-data and proxy scalar meanings still require the service handler. Start/StopAudioOutAuxBroadcast and StopAudioOutSharePlay have confirmed zero-argument ABIs. + +- [sceMbusGetDeviceInfoByCondition_](../../../core/libs/prx/libSceMbus/PendingExports.cpp): its first parameter points to a 256-byte query condition; the second is a 32-bit scalar. Two optional 32-bit outputs and the sized 256-byte record array are confirmed, while condition fields and output meanings remain unresolved. + +- [sceMbusGetDeviceInfoByConditionForDeviceService](../../../core/libs/prx/libSceMbus/PendingExports.cpp): assembly and its DeviceService caller confirm five register arguments followed by a 256-byte condition passed by value on the stack. The condition keeps opaque storage because its service-side fields remain unresolved; byte 228 contains the 16-bit device class. Output records occupy 112 bytes. The two optional outputs are 32-bit values whose meanings remain unresolved. diff --git a/docs/dev/debt/libSceMouse.md b/docs/dev/debt/libSceMouse.md index 19fe9ab3b5..afca5f0db7 100644 --- a/docs/dev/debt/libSceMouse.md +++ b/docs/dev/debt/libSceMouse.md @@ -6,4 +6,4 @@ ### Functional -- [sceMouseResetAbsolutePosition](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - signature taken from a call site (a handle, then x and y as 16-bit values: PPSA30601 passes the centre of its display size); behaviour not measured on hardware. Validates initialization and the handle like `sceMouseRead` and returns `MOUSE_OK`; the position is not stored because the library reports relative motion only +- [sceMouseResetAbsolutePosition](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - the position is not stored because the host reports relative motion only. Native input widths and validation are confirmed, including the default 1920 by 1080 bounds; other native coordinate modes and absolute reports remain unimplemented diff --git a/docs/dev/debt/libSceMove.md b/docs/dev/debt/libSceMove.md index d1660178ce..bc12dfcafb 100644 --- a/docs/dev/debt/libSceMove.md +++ b/docs/dev/debt/libSceMove.md @@ -1,10 +1,7 @@ # libSceMove technical debt -### Silent stubs +### Functional -- [libSceMove](../../../core/libs/prx/libSceMove/Export.cpp) implements only the no-controller path: initialization and termination succeed, `sceMoveOpen` returns a new handle, `sceMoveClose` and `sceMoveResetLightSphere` succeed for an open handle, and the device info, state, extension port, vibration and `sceMoveSetLightSphere` calls return 1 (no controller connected) and write nothing - -### Unknown function info - -- [libSceMove](../../../core/libs/prx/libSceMove/Export.cpp) (libSceMove) - behaviour, error codes and the handle values (`0x30B0100`, then every `0x100`) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/move/move.cpp) (PS4), which checks no handle or open arguments; none is measured on a PS5. Signatures follow [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), except `sceMoveGetExtensionPortInfo`, which follows shadPS4. Before `sceMoveInit` and after `sceMoveTerm` every other call returns `NOT_INIT` (`0x80EE0001`); a null output or a negative timestamp returns `INVALID_ARG` (`0x80EE0003`). Closes when: measured on a PS5 -- [libSceMove](../../../core/libs/prx/libSceMove/Export.cpp) (libSceMove) - a handle that was never opened, is closed or was opened before `sceMoveTerm` returns `INVALID_HANDLE` (`0x80EE0004`), checked before `INVALID_ARG`, and `sceMoveOpen` returns `ALREADY_OPENED` (`0x80EE0007`) for a type and index that are already open; both codes are named in [shadPS4's move_error.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/move/move_error.h), which never returns them, so the results and their order are not measured. `sceMoveOpen` throws for a user other than the initial user, whose console result is not known, and accepts any type and index. Closes when: measured on a PS5 +- [libSceMove](../../../core/libs/prx/libSceMove/Export.cpp): the host implements only disconnected controllers. The native module confirms initialization errors, disconnected return 1 without output writes, invalid-handle error 0x80EE0004, and initialization then argument then handle validation for queries. Reset-light-sphere still omits the device ioctl and its failure mapping. +- [sceMoveOpen and sceMoveClose](../../../core/libs/prx/libSceMove/Export.cpp): protected bodies remain unchanged. Native opening resolves a user/device through the input service, rejects duplicate device handles with 0x80EE0007, limits storage to eight slots with 0x80EE0005, and obtains handles from the device ioctl. The host generates monotonically increasing handles, accepts arbitrary type/index values and returns 0x80EE0007 for duplicate slots and throws for other users. Closing an unknown handle returns the confirmed 0x80EE0004. +- [sceMoveReadStateRecent](../../../core/libs/prx/libSceMove/Export.cpp): the native module permits a null data array for SDK versions through 0x03ffffff while still requiring a count pointer and nonnegative timestamp. The host requires the data array for every SDK version. Connected state records and device-service failures remain unimplemented. diff --git a/docs/dev/debt/libSceMoveTracker.md b/docs/dev/debt/libSceMoveTracker.md index 156c1dfb0a..c261f9fcd9 100644 --- a/docs/dev/debt/libSceMoveTracker.md +++ b/docs/dev/debt/libSceMoveTracker.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceMoveTrackerCalibrateReset, sceMoveTrackerCalibrateYaw, sceMoveTrackerCameraUpdate, sceMoveTrackerCaptureInit, sceMoveTrackerCaptureTerm, sceMoveTrackerControllersUpdate, sceMoveTrackerGetState, sceMoveTrackerGetVersion, sceMoveTrackerGetWorkingMemorySize, sceMoveTrackerInit, sceMoveTrackerPlayGetImages, sceMoveTrackerPlayGetStates, sceMoveTrackerPlayStart, sceMoveTrackerPlayStep, sceMoveTrackerRecordSave, sceMoveTrackerRecordStart, sceMoveTrackerRecordStop and sceMoveTrackerTerm](../../../core/libs/prx/libSceMoveTracker/Export.cpp) (libSceMoveTracker) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceMoveTracker.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [libSceMoveTracker](../../../core/libs/prx/libSceMoveTracker/Export.cpp) remains unimplemented. Calibration direction, camera/controller input, capture initialization, state query, memory-size query and playback output signatures are recovered. Camera and controller records occupy 24 bytes, state records 160 bytes, and playback metadata 16 bytes; their complete field meanings still require reconstruction. Initialization consumes a work-memory pointer, a required nonzero 64-bit argument and two 32-bit GPU queue selectors below eight. The second argument is only checked for zero; its declared type remains unresolved. Capture/playback helper signatures need further checking against caller register use. diff --git a/docs/dev/debt/libSceMsgDialog.md b/docs/dev/debt/libSceMsgDialog.md new file mode 100644 index 0000000000..71acb51911 --- /dev/null +++ b/docs/dev/debt/libSceMsgDialog.md @@ -0,0 +1,9 @@ +# libSceMsgDialog technical debt + +### Silent stubs + +- [libSceMsgDialog](../../../core/libs/prx/libSceMsgDialog/Export.cpp) - message dialogs show no UI. `sceMsgDialogOpen` opens one without `sceMsgDialogInitialize` as well, and a user message or system message dialog finishes on the second `sceMsgDialogGetStatus` or `sceMsgDialogUpdateStatus` call; the first 160 characters of a user message are only logged. The button it picks (2 for `YES_NO_FOCUS_NO`, otherwise 1) is kept but not returned, as `sceMsgDialogGetResult` is exported by libSceMsgDialog.native. A progress bar dialog runs until `sceMsgDialogClose` or for 30 seconds, and `sceMsgDialogProgressBarInc`, `sceMsgDialogProgressBarSetMsg` and `sceMsgDialogProgressBarSetValue` ignore their arguments + +### Unknown function info + +- [sceMsgDialogOpen](../../../core/libs/prx/libSceMsgDialog/Export.cpp) (libSceMsgDialog) - the `SceMsgDialogParam` layout it reads is not measured on a PS5: the mode at 0x38 (1 user message, 2 progress bar, 3 system message), the user message parameter pointer at 0x40, and in that parameter the button type at 0 and the message pointer at 8. A mode outside 1 to 3, or a parameter that cannot be read, is treated as a user message; only Windows checks that the memory is readable, so on Linux any non-null pointer is read. The errors `0x80b80003` (not initialized), `0x80b80007` (busy), `0x80b8000b` (not running) and `0x80b8000d` (null argument) are not measured either. Closes when: measured on a PS5 diff --git a/docs/dev/debt/libSceNKWeb.md b/docs/dev/debt/libSceNKWeb.md index 4191fc13a3..5b311c43ed 100644 --- a/docs/dev/debt/libSceNKWeb.md +++ b/docs/dev/debt/libSceNKWeb.md @@ -1,5 +1,5 @@ # libSceNKWeb technical debt -### Unknown function info +### Functional -- [sceNKWebInitialize and sceNKWebTerminate](../../../core/libs/prx/libSceNKWeb/Export.cpp) (libSceNKWeb) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNKWeb.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceNKWebInitialize and sceNKWebTerminate](../../../core/libs/prx/libSceNKWeb/Export.cpp) remain throwing stubs. Both native exports take no input arguments and return a 32-bit status; decompiler SIMD parameters are unused register propagation through initialization and cleanup helpers. diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md new file mode 100644 index 0000000000..e515ec10a7 --- /dev/null +++ b/docs/dev/debt/libSceNKWebKit.md @@ -0,0 +1,5 @@ +# libSceNKWebKit technical debt + +### Known export names; ABI prototypes unverified + +- The 150 `sceNKWebKitUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp) are present in the PS5 3.20 `libSceNKWebKit` export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their NIDs are derived from exported mangled symbols. Prototypes and behavior are unverified; each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. These declarations make no ABI or behavior claim. diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index fa35cc1780..f66599bc68 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -2,195 +2,59 @@ ### Silent stubs -- [sceNetShowIfconfig, sceNetShowNetstat, sceNetShowRoute, sceNetShowRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - return success without printing anything: the `()` signatures and the success return follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/net.cpp), which stubs each the same way. PPSA01530 fails to load without the `sceNetShowIfconfig` export (#3177); PPSA11386 imports `sceNetShowNetstat` but does not call it during start-up. What the console prints and where is unverified +- [sceNetShowRouteWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - validates memory pool ID and returns success without printing anything: the `(poolId)` signature and pool validation follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp) and `sceNetShowNetstatWithMemory`. What the console prints and where is unverified +- [sceNetShowIfconfig, sceNetShowNetstat](../../../core/libs/prx/libSceNet/Export.cpp) return the native not-initialized error and set errno before initialization. Initialized calls throw because native diagnostic collection and formatting are not implemented. +- [sceNetShowPolicy, sceNetShowNetstatEx, sceNetShowRoute, sceNetShowRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - return success without printing anything: the `()` signatures and the success return follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/net.cpp), which stubs each the same way. PPSA01530 fails to load without the `sceNetShowIfconfig` export (#3177); PPSA11386 imports `sceNetShowNetstat` but does not call it during start-up. What the console prints and where is unverified ### Unknown function info -- [sceNetEtherStrton](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - its signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs#L737-L913) and its [Net bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs#L19), not measured on a console; the throwing declaration only resolves the import. +- [sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) - `SO_PROTOCOL` on raw sockets throws: FreeBSD's raw protocol-switch selection has not been mapped to host raw sockets. For stream and datagram sockets, the effective creation protocol and accepted-socket inheritance follow FreeBSD 11.4's [protocol tables](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/in_proto.c) and [sogetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c); the `0x1016` option and truncated integer output are not measured on PS5. Existing null-pointer validation still returns `EINVAL`. +- [sceNetRecv, sceNetRecvfrom and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - zero-length TCP receives while connecting, after disconnect, or with a pending host error still use the host receive path. Error and output precedence in those states is unmeasured on a console. The established, error-free case follows FreeBSD 11.4's [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1568-L1583). + +- [sceNetConfigAddIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - disassembly confirms the same interface/address/broadcast/netmask arguments as SetIfaddr, entering its shared handler with add mode selected. GetIfaddr copies up to two 16-byte interface-address records. GetRouteInfo uses a family, record buffer, signed capacity and byte flags; internal statistics output is 16 bytes. The address-record and internal-statistics fields remain incomplete. + +- [Resolver-connect, interface and diagnostic queries, WLAN configuration and DHCP queries](../../../core/libs/prx/libSceNet/Export.cpp) retain unsupported implementations. Their restored argument lists still reference incomplete connection, interface, route, WLAN and statistics types. Kernel ioctl payloads need reconstruction from their handlers or consumers; buffer lengths alone do not establish field types. +- [sceNetThreadCreate, sceNetThreadExit, sceNetThreadJoin, sceNetEmulationGet and sceNetEmulationSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the signatures, the `{ entry, arg, reserved, flags }` parameter block and the `EOPNOTSUPP` of the two emulation calls ("not available on retail units") follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/net/net.cpp) (PS4), not a PS5 measurement. The block is read on the new thread, so it must stay valid until the thread starts, `flags` is ignored (shadPS4 ends the thread with `pthread_exit` when bit 0 is set, which ends it the same way), and a failing `scePthreadCreate` or `scePthreadJoin` result is returned as it is, where shadPS4 also sets the net errno from it - [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - the signatures `(int id, stats*)`, the layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) and the error codes were measured on a PS5 (firmware 13.60). The pool size is the one given at init or pool creation. Http2 and Net allocate nothing from these pools. Ssl counts its certificate backend allocations and returned data against its pool; C++ bookkeeping is outside that budget. All three initially report zero use, where the console already reports 0x15C0 bytes for Ssl, 0x1640 for Http2 and 4 for a Net pool. Ssl's allocation sizes and peak usage do not reproduce the console allocator -- [sceNetClearDnsCache](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) returns 0 without doing anything: every `sceNetResolverStart*` lookup goes to the host resolver, so the library keeps no DNS cache to drop, and the host's own cache is left alone. The `(int flags)` signature and its meaning come from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs#L928-L930); nonzero flags throw because their meaning is unknown -- [sceNetGetStatisticsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetInterfaceStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSetDnsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowIfconfigWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowPolicy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowPolicyWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowRouteWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetResolverConnectAbort](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetResolverConnectCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetResolverConnect](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetResolverConnectDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetSockInfo6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSetDns6Info](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowRoute6WithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetAllocateAllRouteInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetAllocateAllRouteInfo6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetControl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDbgInit](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetEventCallbackCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetEventCallbackDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetEventCallbackGetError](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetEventCallbackWaitCB](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetFreeAllRouteInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetArpInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetAppMemInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetDnsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(info, int flags)` signature, the two IPv4 server slots in network byte order and the server count as the return value follow the PS4 [OpenOrbis musl resolver](https://github.com/OpenOrbis/musl/blob/2b0bb8ce2fa64ac255cb656785729d159a5f074c/src/network/resolvconf.c) and [OpenOrbis Net.h](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9dd4a92695538bdeb09fb056d06dd11725/include/orbis/Net.h); unverified on PS5 hardware. It reports the host DNS servers of the interface `sceNetCtlGetInfo` reports, never servers set through `sceNetSetDnsInfo`. Zeroing the unused slots and returning 0 without a host IPv4 interface are unverified. Nonzero flags and a null info throw because their console results are unknown -- [sceNetGetIfList](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetIfListEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetIfListOnce](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetIfName](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(uint32_t index)` signature and the table of ten interface names (`lo0`, `eth0`, `eth1`, `dbg0`, `wlan0`, `wlan1`, `gbe0`, `bt0`, `phone0`, `pppoe0`) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/d5ba5fd06c49af29b8ec9200cad70bca676a9cd4/src/core/libraries/net/net.cpp); an index past the table sets `EINVAL` and returns an empty string. Whether the console has more names, or returns null, is unverified on hardware -- [sceNetGetIfnameNumList](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetNameToIndex](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetRandom](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(uint32_t* output)` signature and the `EINVAL` failure for a null output follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/d5ba5fd06c49af29b8ec9200cad70bca676a9cd4/src/core/libraries/net/net.cpp); the value comes from the host's `std::random_device`, as `kern.rng_pseudo` in libkernel does. Unverified on hardware -- [sceNetGetRouteInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetsockoptEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetSystemTime](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unverified on PS5 hardware: the `void(uint64_t*)` signature, process-relative microseconds and ignored null output follow [shadPS4 net.cpp](https://github.com/shadps4-emu/shadPS4/blob/d615b3887ba29025a59242e1a26f5b68bb176158/src/core/libraries/net/net.cpp#L1654-L1659). The clock is the same as `sceKernelGetProcessTime`, including `APS5_TIME_SCALE` -- [sceNetGetSystemMemInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetInitParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetInetPtonEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(int af, const char* src, void* dst, int flags)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp); IPv4 conversion and the ignored flags follow PS4 firmware 10.71, not yet confirmed on PS5. There IPv4 text is read as `inet_aton` reads it (`a`, `a.b`, `a.b.c`, hex and octal parts, values wrapping at 32 bits, the text ending at the first space, tab or VT), except that a part starting with `09` is decimal (`"0989"` is 989) and an octal part with `8` or `9` fails. Flags 0, each single bit up to 0x100, 0x10000, 0x80000000 and -1 gave the same results; other values were not measured. LF, FF and CR as terminators, and `NET_EINVAL` for a null `src` or `dst`, follow `sceNetInetPton` and were not measured for this function. IPv6 converts as `sceNetInetPton` does. -- [sceNetIoctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetMemoryAllocate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(int64_t size, int flags)` signature, flag 2 as zero fill and the null result for a size of 0 or less follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/d5ba5fd06c49af29b8ec9200cad70bca676a9cd4/src/core/libraries/net/net.cpp). The block comes from the guest heap, and an allocation failure returns null without setting an errno. Any other flag bit throws, since its meaning is not known. Unverified on hardware -- [sceNetMemoryFree](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(void* memory)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/d5ba5fd06c49af29b8ec9200cad70bca676a9cd4/src/core/libraries/net/net.cpp); the block returns to the guest heap and a null pointer is ignored. Unverified on hardware -- [sceNetSetDnsInfoToKernel](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSetsockoptEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSocketInternal](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSyncCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSyncDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSyncGet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSyncSignal](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSyncWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSysctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetThreadCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetThreadExit](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetThreadJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetUsleep](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(int microseconds)` signature and the `EINVAL` failure for a negative count follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp); other counts sleep through libkernel's `sceKernelUsleep`, a cancellation point, and return 0. Whether the console takes the count as signed, and what it returns, is unverified on hardware -- [sceNetShowIfconfigForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowRouteForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowNetstatForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowNetstatEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowNetstatExForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetInetNtopWithScopeId](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetInetPtonWithScopeId](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetDns6Info](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetSetDns6InfoToKernel](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetShowRoute6ForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigGetIfFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetIfFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigUnsetIfFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDownInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetDefaultRouteDev](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetIfLinkLocalAddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigUpInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigUpInterfaceWithFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigCleanUpAllInterfaces](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigFlushRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigFlushRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigAddIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigGetIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetIfmtu](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigAddArp](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigAddArpWithInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigAddMRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigAddRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigAddRouteWithInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelArp](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelArpWithInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelDefaultRouteDev](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelMRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigGetDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDhcpGetAutoipInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDhcpGetInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDhcpGetInfoEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDhcpStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDhcpStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigAddIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelDefaultRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetIfaddr6WithFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetDefaultRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigGetIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigGetDefaultRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigDelRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigAddRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetAddrConfig6Start](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetAddrConfig6Stop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetAddrConfig6GetInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetInfoDumpStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetInfoDumpStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDhcpdStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDhcpdStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDuplicateIpStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDuplicateIpStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocClearWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocGetWakeOnWlanInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocLeave](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocPspEmuClearWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocPspEmuSetWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocScanJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocSetExtInfoElement](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanAdhocSetWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanApStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanApStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanBackgroundScanQuery](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanBackgroundScanStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanBackgroundScanStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanDiagGetDeviceInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanDiagSetAntenna](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanDiagSetTxFixedRate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanGetDeviceConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanInfraGetRssiInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanInfraLeave](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanInfraScanJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanScan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigWlanSetDeviceConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigEtherGetLinkMode](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigEtherPostPlugInOutEvent](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigEtherSetLinkMode](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetPppoeStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetPppoeStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetBandwidthControlSetDefaultParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetBandwidthControlGetDefaultParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetBandwidthControlGetDataTraffic](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigSetDefaultScope](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigRoutingStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigRoutingStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigRoutingShowRoutingConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetConfigRoutingShowtCtlVar](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetGetStatisticsInfoInternal](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDumpAbort](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDumpCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDumpSetFilterParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDumpDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetDumpRead](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetEmulationDebugSettingsSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetEmulationDebugSettingsGet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetEmulationSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetEmulationGet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetClearDnsCache](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the library has no DNS cache to clear because lookups use the host resolver. The host resolver cache is left unchanged. +- [sceNetEtherStrton](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - strings shorter than 17 bytes return EINVAL without changing the output, rather than reading past the terminating NUL as in the [firmware 13.60 probe](https://gist.github.com/orezraey/789290577123af3c45b340d6db6f395c/71e0a6215c7ee86f49fdb31de989dd2b50720b7a). Non-ASCII digit bytes throw because their decoding is unmeasured; other firmware versions remain unverified. Fixed character pairs, ignored separators, alphabetic values beyond f, non-alphanumeric ASCII values of 0x7f, and null-pointer EINVAL follow those measurements, not the strict [shadPS4 parser](https://github.com/shadps4-emu/shadPS4/blob/d615b3887ba29025a59242e1a26f5b68bb176158/src/core/libraries/net/net.cpp#L1274-L1297) +- [sceNetGetDnsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - host-interface selection, zeroing unused slots and returning 0 without a host IPv4 interface remain unverified on PS5 hardware. It reports the host interface's DNS servers until a guest override is set. The two network-order IPv4 slots and returned count follow the PS4 [OpenOrbis musl resolver](https://github.com/OpenOrbis/musl/blob/2b0bb8ce2fa64ac255cb656785729d159a5f074c/src/network/resolvconf.c) and [Net.h](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9dd4a92695538bdeb09fb056d06dd11725/include/orbis/Net.h). +- [sceNetGetRandom](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - generates values with the host random device; the system library maintains its own seeded PRNG state. +- [sceNetInetPtonEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - for IPv6 a null `dst` returns `NET_EINVAL` here, where it crashed a payload process on a PS5 (firmware 13.60). The `(int af, const char* src, void* dst, int flags)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp). On PS4 firmware 10.71 and on that PS5, IPv4 text is read as `inet_aton` reads it (`a`, `a.b`, `a.b.c`, hex and octal parts, values wrapping at 32 bits, the text ending at a space, tab or VT), except that a part starting with `09` is decimal (`"0989"` is 989) and an octal part with `8` or `9` fails; flags were ignored (PS4: 0, single bits up to 0x100, 0x10000, 0x80000000, -1; PS5: 1, 2, 4, 0x100, 0x10000, -1). On the PS5 only: LF, FF and CR also end the text, a null `src` returns `NET_EINVAL`, and a null `dst` returns 1 or 0 without writing or setting errno. IPv6 converts as `sceNetInetPton` does +- [sceNetMemoryAllocate and sceNetMemoryFree](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - allocations use the guest heap instead of the system network pool. Pool limits, its allocation accounting and free-time fill are not reproduced: a reused block allocated without flag 2 holds 0xdd bytes on the console and its previous contents here. Flag 4 bypasses the system pool reserve threshold; with no pool there is no threshold, so it allocates as other flags do. What the console returns once the pool is exhausted is unmeasured; AnyPS5 returns null without setting an errno. Measured on a PS5 (firmware 13.60): a size of 0 or less returns null and leaves `sceNetErrnoLoc` unchanged, flag 2 zero-fills, flags 1, 4, 8, 0x10, 0x100 and 0x80000000 had no visible effect, and every block of 1 to 65536 bytes was 64-byte aligned +- [sceNetConfigCleanUpAllInterfaces](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the native routine consumes no arguments; the throwing implementation remains unchanged. +- [sceNetDhcpdStart](../../../core/libs/prx/libSceNet/Export.cpp): six arguments and the three-argument address callback are confirmed. The 24-byte parameter contains five network-order addresses and a client limit. Individual address roles and the two unused thread-option meanings remain unresolved. +- [sceNetPppoeStart](../../../core/libs/prx/libSceNet/Export.cpp): seven arguments are confirmed, including the parameter pointer on the stack. The 272-byte parameter contains two DNS addresses, two 128-byte credential arrays, MTU and flags. Callback arguments are user data, event and error; the two unused thread-option meanings remain unresolved. +- [sceNetConfigRoutingStart](../../../core/libs/prx/libSceNet/Export.cpp): five pointer/scalar arguments are confirmed. Input address data is eight bytes; the mutable server parameter is 20 bytes and duplicates its first address when its count is one. Optional rules contain a count and pointer to 20-byte records with protocol, two address/port pairs and flags. Layouts are defined; rule flag meanings and protocol-specific port interpretation remain unresolved. +- [sceNetConfigRoutingShowRoutingConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the native routine consumes no arguments; the throwing implementation remains unchanged. ### Functional +- [sceNetRecv/sceNetRecvfrom](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) translate Windows WSAEMSGSIZE into the copied byte count for datagram sockets, including MSG_PEEK, and retain the sender address. This follows FreeBSD 11.4 recv(2) and Winsock's documented truncated-datagram behavior; the console measurement covered only libkernel_sys `recv` and `recvfrom`, not libSceNet. +- [sceNetUsleep](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - whether the console prints its `[LIBNET] usleep ` line for a negative count before `sceNetInit` is unmeasured; nothing is printed here. What an interrupted sleep returns is unmeasured: AnyPS5 returns the result of libkernel's `sceKernelUsleep`, a cancellation point. The signed 32-bit count (the upper half of the register is ignored) and the 0 result were measured on a PS5 (firmware 13.60) +- [sceNetIoctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - on a `sceNetSocket` socket only `FIONBIO` and `FIONREAD` are handled and other requests fail with `ENOTTY`, as in libkernel's `ioctl`, though FreeBSD 11.4's [`soo_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_socket.c#L156-L262) handles more; not measured on a console. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/60c42b925c3d1e00008621b8587819999260ec31/src/core/libraries/net/net.cpp#L1839) (PS4) returns `EINVAL` for other requests. `FIONBIO` sets `SO_NBIO`, assumed to be `soo_ioctl`'s `SS_NBIO`. `FIONREAD` asks the host, which on Linux counts only the next datagram where `soo_ioctl` counts all. Libkernel's own sockets go to its `ioctl`; other descriptors get `EBADF`. - [sceNetResolverStartAton6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reverse-resolves a 16-byte IPv6 address through the host resolver (`getnameinfo` with `NI_NAMEREQD`) into the caller's buffer of `len` bytes, as `sceNetResolverStartAton` does for IPv4. The `(rid, addr, hostname, len, timeout, retry, flags)` signature comes from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs#L992-L997). An address with no name, or a name longer than `len - 1` bytes, fails with `RESOLVER_ENODNS` (`0x804101E1`) and leaves the buffer untouched; whether the console truncates instead is not verified. Abort flag 2 (ATON preservation) fails the next call with `EINTR` as it does `sceNetResolverStartAton`. Timeout and retry are ignored, and any nonzero flags throw because their meaning is unknown -- [sceNetAccept](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) inherits the listener's non-blocking mode through the existing `SO_NBIO` setter, as the libkernel socket path does and FreeBSD's [`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L571) copies the socket state. The mode is sampled when accept starts; changes between connection creation and accept are not modelled. Not measured on a console. +- [sceNetAccept](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) inherits the listener's non-blocking mode through the existing `SO_NBIO` setter, as the libkernel socket path does and FreeBSD's [`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L571) copies the socket state. The mode is read after the host accept returns; changes between connection creation and accept are not modelled. Not measured on a console. -- [sceNetConnect to AF_UNSPEC](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux a socket bound to an explicit port keeps its local address as well (`127.0.0.1:` stays `127.0.0.1:`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had -- [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) sets `SO_RCVTIMEO` and `SO_SNDTIMEO` as the host socket's timeouts. Windows sockets stay non-blocking and the library waits for readiness until the stored timeout. A stream receive that times out still reports `ETIMEDOUT` rather than FreeBSD's `EAGAIN`; a datagram receive reports `EAGAIN`. Blocking stream sends return the queued byte count on timeout, or `EAGAIN` when no bytes were queued. +- [sceNetConnect to AF_UNSPEC](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux and on Windows a socket bound to an explicit port keeps its local address as well (`127.0.0.1:` stays `127.0.0.1:`), because a bound socket cannot be bound again, and `sceNetGetsockname` reports that host address. On Windows a `sendto` on such a host socket to a non-loopback address fails with Winsock error 10051 (measured on the host socket, not through `sceNetSendto`), where FreeBSD sends it from the wildcard address. One bound to port 0 is bound again to the wildcard address with the port it had +- [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) sets `SO_RCVTIMEO` and `SO_SNDTIMEO` as the host socket's timeouts. Windows sockets stay non-blocking and the library waits for readiness until the stored timeout. Blocking stream sends return the queued byte count on timeout, or `EAGAIN` when no bytes were queued. - [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) take `SO_SNDTIMEO` and `SO_RCVTIMEO` (0x1005, 0x1006) as a 16-byte `struct timeval`, as FreeBSD 11.4's [`sosetopt` and `sogetopt`](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/uipc_socket.c#L2657-L2690) do, and Sony's 0x1105 and 0x1106 as an `int` of microseconds. A [console log of ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight/issues/49) (firmware 12.20) shows receives timing out every 100 ms after it gave 0x1006 a `{0, 100000}` timeval. The errors for a value shorter than a timeval (`EINVAL`) or out of range (`EDOM`), and `sceNetGetsockopt` cutting the timeval to the caller's length, follow FreeBSD's `sooptcopyin` and `sooptcopyout` and are not measured on a console - [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) stores `SO_ONESBCAST` (`0x10000`, [shadPS4's `ORBIS_NET_SO_ONESBCAST`](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net_types.h)) per socket and `sceNetGetsockopt` reads back the value that was set, as shadPS4 does; PPSA34501 sets it and crashes later when it fails with `EOPNOTSUPP`. Sends ignore it. FreeBSD 11.4's [ip(4)](https://man.freebsd.org/cgi/man.cgi?manpath=FreeBSD+11.4-RELEASE&query=ip&sektion=4) documents the like-named `IPPROTO_IP` option `IP_ONESBCAST`: a datagram sent to an interface's directed broadcast address, such as 192.168.2.255, leaves through that interface with its destination rewritten to 255.255.255.255, and only on a socket allowed to set `SO_BROADCAST`. Here host routing selects the outgoing interface and the destination is not rewritten. shadPS4 also leaves sends unchanged, and its [TODO](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp#L2467-L2470) reads the option the other way round: a send to 255.255.255.255 would be converted to the interface's broadcast address. Whether the console's `SOL_SOCKET` option behaves like `IP_ONESBCAST`, and what it reads back, is not verified on hardware. FreeBSD 11.4's `sogetopt()` reads several boolean socket options back as their flag bit (`so_options & optname`, `sys/kern/uipc_socket.c`), not as the value set; whether the console does so for `0x10000` is unknown - [sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reads `SO_ERROR` from the host socket. Winsock does not clear a socket's pending error when it is read, so on Windows the library reports a pending error once and shows it again only after a new `sceNetConnect`; a second failure with the same code on the same socket without a `sceNetConnect` in between would be hidden. -- [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) set `TCP_NODELAY` (`IPPROTO_TCP`, 1) on the host socket of a stream socket and read it back as `TF_NODELAY` (4) or 0, as FreeBSD 11.4's [`tcp_default_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1626-L1650) does. Every other `IPPROTO_TCP` option (`TCP_MAXSEG`, `TCP_NOPUSH` and `TCP_KEEPIDLE` among them, which FreeBSD implements) fails with `ENOPROTOOPT`, and the `IPPROTO_TCP` level on a socket that is not a stream socket fails with `EINVAL`, as [`ip_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/ip_output.c#L943-L944) answers it. A set on a connection that FreeBSD's [`tcp_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1498-L1502) refuses with `ECONNRESET` (refused, reset or in `TIME_WAIT`) succeeds here because the host accepts it. A null value with a nonzero length fails with `EINVAL`, where FreeBSD's `kern_setsockopt` returns `EFAULT`, and a get into less than an `int` fails with `EINVAL`, where `sooptcopyout` truncates. None of this was measured on a console. +- [SO_KEEPALIVE](../../../core/libs/prx/libSceNet/Export.cpp) uses the host TCP keepalive timers and probe count, which can differ from the console. The boolean flag, bit-valued readback and truncated output follow FreeBSD 11.4's [sosetopt and sogetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c), without PS5 hardware verification. Non-stream sockets retain the flag without sending probes, as FreeBSD does. Null pointers retain the existing libSceNet `EINVAL` result instead of FreeBSD's `EFAULT`. +- [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) - `TCP_MAXSEG`, `TCP_NOPUSH`, `TCP_KEEPIDLE` and other TCP options remain unsupported (`ENOPROTOOPT`). `TCP_NODELAY` sets on refused, reset or `TIME_WAIT` sockets can succeed through the host where FreeBSD 11.4's [tcp_ctloutput](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet/tcp_usrreq.c#L1498-L1502) returns `ECONNRESET`. A null setter value returns `EINVAL` rather than `kern_setsockopt`'s `EFAULT`. The non-stream `IPPROTO_TCP` error (`EINVAL`) and `TF_NODELAY` flag readback follow FreeBSD, not PS5 measurements. - [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reports 0 for the name, pid, policy, priority, queue lengths, virtual ports, state, bandwidth, drops and waits, sets no status flag except `SELF` (0x1) and `NONBLOCK` (0x200000), and reports the host's buffer sizes where none was set. An IPv6 socket fails with `EAFNOSUPPORT` and is not counted or listed; shadPS4 lists it with zero addresses. `flags` bits other than 0x40 fail with `EINVAL`, where shadPS4 ignores `flags`; 0x40 adds sockets in TIME_WAIT ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs)), but closed sockets are not kept. Otherwise the `s` below 0 count and list, the return value and `EINVAL` for a null `info` or an `n` below 1 follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/06e813ff8c8e2a6d81b02cabfcc9543c3debf78e/src/core/libraries/net/net.cpp#L1498-L1522). Not measured on a console. +- [SO_SNDBUF and SO_RCVBUF validation](../../../core/libs/prx/libSceNet/Export.cpp) remains unverified on PS5, and positive sizes remain subject to host buffer limits. Nonpositive sizes fail with `EINVAL` before changing the native socket, following FreeBSD 11.4 [sosetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c#L2494-L2509). - [sceNetSendmsg and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported +- [sceNetGetSockInfo6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) is not measured on a console; shadPS4 stubs it. It lists only IPv6 sockets and `flags` 0x1000 adds IPv4 sockets, reported with the IPv4 addresses, all-zero IPv6 addresses and no `F_IPV6` (0x400000), which every IPv6 socket sets ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs#L251-L343), [flags](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs#L698-L705), [docs](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/docs/net.md#L301-L304)). The default of leaving IPv4 sockets out, the zero IPv6 addresses of an IPv4 socket and accepting 0x40 are inferred from those names, not documented. An IPv4 socket without 0x1000 fails with `EAFNOSUPPORT`. The other gaps are those of `sceNetGetSockInfo`. +- [sceNetSocketClose, sceNetEpollControl and sceNetEpollWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - close and concurrent registration ordering are unmeasured on a console. Closed descriptors retire their registrations, and a wait discards snapshots invalidated by close, DEL or MOD; descriptor identity follows [Linux epoll(7)](https://man7.org/linux/man-pages/man7/epoll.7.html). ADD, MOD and DEL reject closed or invalid socket descriptors with `EBADF`, as [Linux epoll_ctl(2)](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) does; this error ordering is unmeasured on a console. Duplicate-descriptor alias lifetime is not modeled by this epoll backend. - [MSG_DONTWAIT](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - console ordering between competing readers and writers is unmeasured. The per-call behavior follows FreeBSD 11.4's `soreceive_generic` and `sosend_generic` (`sys/kern/uipc_socket.c`); `0x80` is FreeBSD's flag value (`sys/sys/socket.h`). On Windows native sockets stay non-blocking. `MSG_DONTWAIT` and guest `SO_NBIO` return one host transfer's result. Blocking stream sends queue the whole buffer with one deadline and return progress after a timeout or host error. SSL uses this path through the native owner, retaining the socket, its mode and timeouts. Readiness waits poll at most every 10 ms for local close, so idle blocked operations wake periodically. -- [sceNetRecv, sceNetRecvfrom and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) on a datagram socket with no local address wait on Windows until a bind, connect or send gives it one, checking the host socket every 10 ms. Winsock fails such a receive with `WSAEINVAL`, where FreeBSD 11.4's `soreceive_dgram` waits for a datagram (`sys/kern/uipc_socket.c`). A non-blocking socket fails with `EAGAIN` at once, and the receive timeout ends the wait with `EAGAIN`; when the address comes first, the call waits for a datagram only for what is left of the timeout. A `sceNetShutdown` of the receive side ends the wait and the receive returns 0, and a zero-length receive returns 0 without waiting, as `soreceive_dgram` does when the socket can't receive more or nothing was asked for. An abort or close ends the wait, and the host receive then fails as it did before +- [sceNetRecv, sceNetRecvfrom and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) on a datagram socket with no local address wait on Windows and macOS until a bind, connect or send gives it one, checking the host socket every 10 ms. Winsock fails such a receive with `WSAEINVAL`, where FreeBSD 11.4's `soreceive_dgram` waits for a datagram (`sys/kern/uipc_socket.c`). On macOS the host receive waits, but a receive-side `shutdown` does not wake it. A non-blocking socket fails with `EAGAIN` at once, and the receive timeout ends the wait with `EAGAIN`; when the address comes first, the call waits for a datagram only for what is left of the timeout. A `sceNetShutdown` of the receive side ends the wait and the receive returns 0, and a zero-length receive returns 0 without waiting, as `soreceive_dgram` does when the socket can't receive more or nothing was asked for. An abort or close ends the wait, and the host receive then fails as it did before - [sceNetResolverAbort and sceNetResolverStartNtoaMultipleRecords](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) follow shadPS4 ([net.cpp](https://github.com/shadps4-emu/shadPS4/blob/811bfcd7d17ed8ae975c477d9ff28abc744e1ca8/src/core/libraries/net/net.cpp), [net_resolver.cpp](https://github.com/shadps4-emu/shadPS4/blob/811bfcd7d17ed8ae975c477d9ff28abc744e1ca8/src/core/libraries/net/net_resolver.cpp)); the `(int rid, int flags)` signature of the abort also matches [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs). Unverified on hardware. Lookups run synchronously over the host resolver, which cannot be interrupted: an abort while a lookup runs on another thread returns at once, and that lookup fails with `EINTR` without writing its output only when the host call returns; the abort flags are then ignored, as shadPS4 does for a running lookup. With nothing running, abort flag 1 (NTOA preservation) or 2 (ATON preservation) makes the next `sceNetResolverStartNtoa*` or `sceNetResolverStartAton` call on that resolver fail with `EINTR`, reported by `sceNetResolverGetError`, after its arguments are checked. Unlike shadPS4, which ignores them, other abort flag bits throw, and `sceNetResolverStartNtoaMultipleRecords` throws for any flag, including async (1) and 0x10000 (IP address literals disabled), as `sceNetResolverStartNtoaMultipleRecordsEx` does - [sceNetResolverStartNtoa6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) resolves a host name to its first IPv6 address through the host resolver (`getaddrinfo` with `AF_INET6`) and writes the 16-byte address; the `(rid, hostname, addr, timeout, retry, flags)` signature follows `sceNetResolverStartNtoa`. A name with no IPv6 address fails with `RESOLVER_ENODNS` (`0x804101E1`) like a failed lookup; whether the console returns IPv4-mapped addresses instead is not verified. Timeout and retry are ignored as in `sceNetResolverStartNtoa`, and any nonzero flags throw because their meaning for this call is not known. Unverified on hardware. -- [sceNetInetPton](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) ends an IPv4 address at the first space, tab, LF, VT, FF or CR and ignores the rest (`"1.2.3.4 x"` converts), leaves the destination and `sceNetErrnoLoc` unchanged when a conversion fails, as PS4 firmware 10.71 does; not yet confirmed on PS5. Leading whitespace, any other trailing byte (`0x01`, `0xa0`) and IPv6 text with whitespace fail. +- [sceNetInetPton](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - an IPv6 conversion with a null `dst` returns `NET_EINVAL` here, where it crashed a payload process on a PS5 (firmware 13.60). On that PS5 and on PS4 firmware 10.71 an IPv4 address ends at the first space, tab, LF, VT, FF or CR and the rest is ignored (`"1.2.3.4 x"` converts), and a failed conversion leaves the destination and `sceNetErrnoLoc` unchanged. On the PS5 the dotted IPv4 part of IPv6 text is read the same way (`"::ffff:1.2.3.4 x"` converts, `"::ffff:01.2.3.4"` fails), and an IPv4 conversion with a null `dst` returns 1 or 0 without writing. Leading whitespace, any other trailing byte (`0x01`, `0xa0`) and whitespace after hexadecimal IPv6 text fail +- [sceNetInetPton, sceNetInetNtop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) convert IPv6 text with libkernel's [inet_pton and inet_ntop](../../../core/libs/prx/libkernel/Socket/src/AddressConversion.cpp), so they take the same FreeBSD 11.4 text forms; beyond the PS5 measurements in the entry above, these are unverified on a console. IPv4 text keeps the host conversion. The checks and errors before the conversion are libSceNet's own and unchanged +- [sceNetResolverStartNtoa](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) ignores flag bits 0x1 and 0x10000 and does not apply the timeout or retry count: every lookup runs once, synchronously, through the host resolver. What those two bits do on a console is not known; the sceNetResolverAbort entry above gives shadPS4's names for them. A negative timeout or retry and any other flag bit fail with `EINVAL` before anything else is checked, as [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L579-L587) gives + +- [sceNetDhcpStart, sceNetAddrConfig6Start, sceNetDuplicateIpStart and sceNetInfoDumpStart](../../../core/libs/prx/libSceNet/Export.cpp) - argument widths and callback ABI are recovered. Address callbacks receive user data, a 32-bit event and a 32-bit error. DHCP configuration is mutable; the 512-byte IPv6 configuration is read-only. Their complete field layouts remain unknown. The two 32-bit thread parameters are stored but not consumed by this firmware thread wrapper, so their original names and units are unconfirmed. Information-dump options contain flags, an interval in seconds and a 64-byte path. All four exports remain unimplemented. +- [sceNetSetDnsInfo, sceNetSetDns6Info and sceNetGetDns6Info](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the resolver still uses the host's DNS servers; setters only store servers for the getters. Without an IPv6 override, the getter reports none. Counts follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0918faa0255e9816b795b62c9b09eaea51a1bc5e/src/core/libraries/net/net.cpp), not hardware measurements. All four DNS calls' initialization and argument errors match a reported [firmware 13.42 module probe](https://github.com/boykopovar/AnyPS5/pull/9506#issuecomment-6390574180): ENOTINIT first, then EINVAL for nonzero flags, null getter outputs or zero primary servers; null setters clear the override. Setters use a checked guest read and return EFAULT for unreadable non-null inputs, an AnyPS5 safety measure absent from the probed module. diff --git a/docs/dev/debt/libSceNetCtl.md b/docs/dev/debt/libSceNetCtl.md index f9fb92ef5c..add05f78df 100644 --- a/docs/dev/debt/libSceNetCtl.md +++ b/docs/dev/debt/libSceNetCtl.md @@ -1,87 +1,24 @@ # libSceNetCtl technical debt -### Silent stubs - -- [sceNetCtlGetNatInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - `sceNetCtlGetNatInfo` validates null pointers and returns `SCE_NET_CTL_ERROR_NOT_CONNECTED` (`0x80412108`) without performing NAT discovery. - -### Unknown function info - -- [sceNetCtlGetInfoV6](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl, NID `Jy1EO5GdlcM`) - imported by PPSA08587. `(int code, SceNetCtlInfoV6* info)` with codes 1-5 (IP address, link-local address, default route, primary and secondary DNS) comes only from [SharpProspero](https://github.com/SvenGDK/SharpProspero), whose source is unknown; shadPS4 and fpPS4 only stub it. A null `info` answers `INVALID_ADDR`, a code outside 1-5 `INVALID_CODE`, and a host without an IPv6 address `NOT_CONNECTED`, as `sceNetCtlGetStateV6` reports it. The layout of each answer is unverified, so every code throws on a host with an IPv6 address -- [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - answers from the host interface that holds an IPv4 address, preferring the one that carries the default route: its address, netmask, gateway, MTU and first two IPv4 DNS servers. The device is reported as wired, the link as connected, the IP configuration as DHCP and the HTTP proxy as off, whatever the host uses, and the Ethernet address is the one `sceNetGetMacAddress` reports. A gateway or DNS server the interface does not have is reported as `0.0.0.0`; what a console reports then is unknown. The DHCP host name (12), the PPPoE user name (13), the HTTP proxy server (20) and the Wi-Fi BSSID and SSID (5, 6) are empty strings, as that configuration sets none of them; the remaining Wi-Fi codes (7-10) and the HTTP proxy port (21) report 0; this follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/network.cpp) and is not measured on hardware -- [sceNetCtlRegisterCallbackForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlRegisterCallbackIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlRegisterCallbackForLibIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlUnregisterCallbackForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlUnregisterCallbackIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlUnregisterCallbackForLibIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlCheckCallbackForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlCheckCallbackForLibIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlClearEventIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlClearEventForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlClearEventForLibIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetResultIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetStateIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetState2IpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetInfoIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetEtherLinkMode](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetWifiType](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlConnectIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlConnectWithRetryIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlConnectConfIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlDisconnectIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlScanIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetScanInfoBssidIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetScanInfoByBssidIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetScanInfoForSsidScanIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetScanInfoBssidForSsidListScanIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetScanInfoForSsidListScanIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlSetErrorNotificationEnabledIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlRegisterCallbackV6IpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlUnregisterCallbackV6IpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetResultV6](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl, NID `H5yARg37U5g`) - `(int eventType, int* errorCode)` comes only from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/NetCtl.cs#L246-L251), whose source is unknown; shadPS4 only stubs it. Callbacks registered with `sceNetCtlRegisterCallbackV6` receive no event, so event types 1-3 write 0 to `errorCode`; what a console writes before the first event is unmeasured. A null `errorCode` answers `INVALID_ADDR`, as `sceNetCtlGetResult` does, and other event types throw, as their error code on a console is unknown -- [sceNetCtlGetResultV6IpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetStateV6IpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetInfoV6IpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetNatInfoIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlSetStunWithPaddingFlagIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlUnsetStunWithPaddingFlagIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlIsBandwidthManagementEnabledIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlDisableBandwidthManagementIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlEnableBandwidthManagementIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetBandwidthInfoIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetNetEvConfigInfoIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetConnectionElapsedTimeIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpCheckCallback](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpClearEvent](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpRegisterCallback](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpUnregisterCallback](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpGetResult](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpGetState](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpStart](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApCpStart](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApAppStartWithRetryPid](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApAppStartWithRetry](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpStartWithRetry](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpStartConf](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRpStop](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApCpStop](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApAppInitWpaKey](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApAppInitWpaKeyForQa](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlApRestart](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetBweCheckCallbackIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetBweClearEventIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetBweRegisterCallbackIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetBweUnregisterCallbackIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetBweGetInfoIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetBweStartInternetConnectionTestIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetBweFinishInternetConnectionTestIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetBweStartInternetConnectionTestBandwidthTestIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetBweSetInternetConnectionTestResultIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlGetWifiInfoIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature - ### Functional -- [sceNetCtlCheckCallback](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - the state comes from the host interfaces, as in `sceNetCtlGetState`: `IPOBTAINED` (3) while the host has a non-loopback IPv4 or IPv6 address, `DISCONNECTED` (1) otherwise; the event values follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/network/net_ctl_codes.h#L27-L29), and no other event is delivered. Each callback registered with `sceNetCtlRegisterCallback` is called on the checking thread with its registration argument when the state differs from the last one it received, so a new registration receives the current state at the next check. This edge-triggered model follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L3648-L3661); [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/network/net_ctl_obj.cpp#L47-L57) delivers the state on every check instead, and neither is measured on hardware. KytyPS5 calls the callbacks under its lock; here they are called after the lock is released, from a copy of their slots, so a `sceNetCtlUnregisterCallback` or `sceNetCtlTerm` on another thread that returns while a check is in flight does not stop that check from calling the callback once more with its old argument. Callbacks registered with `sceNetCtlRegisterCallbackV6` keep their argument but receive no event, because no source says which call delivers them +- [sceNetCtlGetNatInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) - NAT discovery is not implemented and throws after native initialization and argument checks. NAT information is 16 bytes with a 32-bit size prefix; the remaining fields are unresolved. + +### Unknown function info + +- [sceNetCtlScanIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - argument registers and IPC transfer directions are recovered for the info, connection, scan, NAT, bandwidth and Wi-Fi queries. The connection configuration is 1464 bytes, scan options 88, BSSID output 96, scan info 112, IPv6 info 256, internal NAT info 196, bandwidth info 32, bandwidth-estimation info 28 and Wi-Fi info 48. Their field semantics still require the receiving service; incomplete declarations preserve this gap. Scan queries transfer a six-byte BSSID and use a 64-bit in/out buffer size. + +- [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - answers from the host interface that holds an IPv4 address, preferring the one that carries the default route: its address, netmask, gateway, MTU and first two IPv4 DNS servers. The device is reported as wired, the link as connected, the IP configuration as DHCP and the HTTP proxy as off, whatever the host uses, and the Ethernet address is the one `sceNetGetMacAddress` reports. A gateway or DNS server the interface does not have is reported as `0.0.0.0`; what a console reports then is unknown. The DHCP host name (12), the PPPoE user name (13), the HTTP proxy server (20) and the Wi-Fi BSSID and SSID (5, 6) are empty strings, as that configuration sets none of them; the remaining Wi-Fi codes (7-10) and the HTTP proxy port (21) report 0; this follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/network.cpp) and is not measured on hardware +- [sceNetCtlGetInfoV6](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl, NID `Jy1EO5GdlcM`) - imported by PPSA08587. `(int code, SceNetCtlInfoV6* info)` with codes 1-5 (IP address, link-local address, default route, primary and secondary DNS) comes only from [SharpProspero](https://github.com/SvenGDK/SharpProspero), whose source is unknown; shadPS4 and fpPS4 only stub it. A null `info` answers `INVALID_ADDR`, a code outside 1-5 `INVALID_CODE`, and a host without an IPv6 address `NOT_CONNECTED`, as `sceNetCtlGetStateV6` reports it. The layout of each answer is unverified, so every code throws on a host with an IPv6 address +- [sceNetCtlGetResult](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - `(int eventType, int* errorCode)` reports no error: event types 1-3 (disconnected to IP obtained, the numbering `sceNetCtlCheckCallback` delivers) write 0 to `errorCode`, as `sceNetCtlGetResultV6` does. The error code a console reports for each event, a disconnection's cause in particular, is unconfirmed. Callers found: PPSA20052 (Worms Armageddon: Anniversary Edition 01.003.000) calls it 2 s into boot, and PPSA24191 (Monster Hunter Wilds) without a network, with event 1 (disconnected) as its first call. A null `errorCode` answers `INVALID_ADDR` and other event types throw, as their error code on a console is unknown +- [sceNetCtlGetResultV6](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl, NID `H5yARg37U5g`) - `(int eventType, int* errorCode)` comes only from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/NetCtl.cs#L246-L251), whose source is unknown; shadPS4 only stubs it. Callbacks registered with `sceNetCtlRegisterCallbackV6` receive no event, so event types 1-3 write 0 to `errorCode`; what a console writes before the first event is unmeasured. A null `errorCode` answers `INVALID_ADDR`, as `sceNetCtlGetResult` does, and other event types throw, as their error code on a console is unknown +- [sceNetCtlRegisterCallbackForNpToolkit, sceNetCtlUnregisterCallbackForNpToolkit, sceNetCtlCheckCallbackForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - a table of eight callbacks apart from the IPv4 and IPv6 ones, with the `(int eventType, void* arg)` callback, follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/net_ctl_obj.h), whose NpToolkit check delivers the same events to that table. The three share the register, unregister and dispatch of `sceNetCtlRegisterCallback`, `sceNetCtlUnregisterCallback` and `sceNetCtlCheckCallback`, so they return the same initialization, address, slot-limit and ID errors, and the check reports the state `sceNetCtlCheckCallback` does. Whether the console keeps this table apart, and what `sceNetCtlCheckCallback` does with it, is unverified on hardware +- [ApRp exports](../../../core/libs/prx/libSceNetCtl/Export.cpp) are constant-success functions in the supplied native module and consume no arguments. No callers were found in the supplied system-library decompilations, so unused callback, output and configuration argument types remain unresolved. Protected stub bodies are unchanged. + +### Functional + +- [sceNetCtlCheckCallback](../../../core/libs/prx/libSceNetCtl/Export.cpp) - host address changes stand in for the native service event queues. IPv4 callbacks receive connected/disconnected transitions and an initial event; intermediate connection states and errors are not represented. Native CheckCallback drains both IPv4 and IPv6 queues, but the host backend never delivers IPv6 events. Dispatch copies slots before invoking callbacks, so unregistering concurrently may leave one callback in flight; native dispatch holds its registration lock. Slot limits, invalid/deleted IDs and the no-op Term follow the supplied module. +- [libSceNetCtl initialization](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - the library starts initialized, as titles find it: it is the internal sysmodule `0x80000009`, and PPSA11386, PPSA20049, PPSA02530, PPSA14632 and PPSA15878 register and check NetCtl callbacks without importing `sceNetCtlInit`. PPSA11386 executes `int 0x42` at start-up unless `sceNetCtlRegisterCallback` returns 0. So the supplied module's `SCE_NET_CTL_ERROR_NOT_INITIALIZED` answers are unreachable, since Term is a no-op. Whether the console's process start or the module's start sets that state is not measured. - [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) finds the host's default route in `/proc/net/route` on Linux and, on macOS, in the routing table read with `sysctl(NET_RT_FLAGS, RTF_GATEWAY)`, where an interface-scoped default route ranks after the unscoped one - [sceNetCtlGetIfStat](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) throws on macOS: an unprivileged process reads the interface byte counters (`getifaddrs` and `sysctl(NET_RT_IFLIST2)` alike) reduced to their low 32 bits, so a total would wrap every 4 GiB +- [Host interface cache](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - on Windows, `sceNetCtlGetState`, `sceNetCtlGetStateV6`, `sceNetCtlCheckCallback`, `sceNetCtlGetIfStat` and `sceNetCtlGetInfo` reuse the last host interface query until `NotifyUnicastIpAddressChange`, `NotifyIpInterfaceChange` or `NotifyRouteChange2` reports a change, instead of calling `GetAdaptersAddresses` on every call. A DNS server change with no address, interface or route change is seen only at the next such change. If the notifications cannot be registered, every call queries the host as before. diff --git a/docs/dev/debt/libSceNetCtlAp.md b/docs/dev/debt/libSceNetCtlAp.md index 012a82c476..59dab4dfd7 100644 --- a/docs/dev/debt/libSceNetCtlAp.md +++ b/docs/dev/debt/libSceNetCtlAp.md @@ -6,7 +6,8 @@ ### Unknown function info +- [sceNetCtlApRegisterCallback](../../../core/libs/prx/libSceNetCtlAp/Export.cpp) (libSceNetCtlAp) - writing `InvalidCid` (`-1`) to `cid` when all eight slots are taken (`CALLBACK_MAX`) is unmeasured and interprets [SharpProspero's comment](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/NetCtlAp.cs#L117-L118) that it is the value written into an unclaimed callback id, which does not name this path. [`libSceNetCtl`](https://github.com/boykopovar/AnyPS5/blob/aeeaefad98613c63a49ea2daae4d4558304b14cd/core/libs/prx/libSceNetCtl/Export.cpp#L103-L115) and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/network/netctl.cpp#L381-L390) leave `cid` unchanged on a full table + - [sceNetCtlApInit, sceNetCtlApTerm](../../../core/libs/prx/libSceNetCtlAp/Export.cpp) (libSceNetCtlAp) - every other call returns `NOT_INITIALIZED` (`0x80412994`) before `sceNetCtlApInit` and after `sceNetCtlApTerm`, which also drops the registered callbacks. A second `sceNetCtlApInit` throws, as no error for it is documented -- [sceNetCtlApGetInfo, sceNetCtlApGetConnectInfo, sceNetCtlApStop](../../../core/libs/prx/libSceNetCtlAp/Export.cpp) (libSceNetCtlAp) - checks run in this order, which is not verified: a null structure returns `INVALID_ADDR` (`0x80412991`), a size field other than 152 or 184 `INVALID_SIZE` (`0x80412998`), then `NOT_STARTED`. The option string is not read, since the accepted strings (`INVALID_OPT_STR`, `0x80412901`) are unknown. Whether `sceNetCtlApStop` of a stopped access point returns `NOT_STARTED` or 0 is unverified -- [sceNetCtlApClearEvent, sceNetCtlApGetResult](../../../core/libs/prx/libSceNetCtlAp/Export.cpp) (libSceNetCtlAp) - event types 1 to 4 are accepted and others return `INVALID_TYPE` (`0x80412990`), checked before the null `errorCode` (`INVALID_ADDR`). With no event ever delivered, `sceNetCtlApGetResult` writes 0; what the console writes before the first event is unverified -- [sceNetCtlApRegisterCallback, sceNetCtlApUnregisterCallback](../../../core/libs/prx/libSceNetCtlAp/Export.cpp) (libSceNetCtlAp) - a null function or id pointer returns `INVALID_ADDR`, a ninth registration `CALLBACK_MAX` (`0x80412995`), an id outside 0 to 7 `INVALID_ID` (`0x80412996`) and an unregistered id `ID_NOT_FOUND` (`0x80412997`). Ids are the lowest free slot, as in libSceNetCtl; the console's id assignment is unverified +- [sceNetCtlApGetInfo, sceNetCtlApGetConnectInfo, sceNetCtlApStop](../../../core/libs/prx/libSceNetCtlAp/Export.cpp) - stopped-state results come from a service on the console and remain unverified. Wrapper validation is recovered: initialization precedes arguments, the optional connection string is at most eight bytes, then output pointers and exact 152/184-byte sizes are checked. The service interpretation of the option string remains unresolved. +- [sceNetCtlApGetResult](../../../core/libs/prx/libSceNetCtlAp/Export.cpp) - with no event delivered, the host writes zero; the service result before the first event remains unknown. Wrapper validation checks initialization, output pointer and event type in that order. Event types 1 through 4 and lowest-free callback IDs 0 through 7 are confirmed. diff --git a/docs/dev/debt/libSceNetCtlApDialog.md b/docs/dev/debt/libSceNetCtlApDialog.md index aefc800699..faeb9b3cb6 100644 --- a/docs/dev/debt/libSceNetCtlApDialog.md +++ b/docs/dev/debt/libSceNetCtlApDialog.md @@ -2,4 +2,4 @@ ### Silent stubs -- [libSceNetCtlApDialog](../../../core/libs/prx/libSceNetCtlApDialog/Export.cpp) - the access-point dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as no access-point connection is brought up. `Open` takes the 104-byte parameter block and `GetResult` writes a 40-byte result (int32 result and 36 zeroed reserved bytes), following [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/NetCtlApDialog.cs); the parameter block is not validated and the 0x80B8xxxx error codes follow the other common dialogs, neither verified on hardware. Deathloop imports it +- [libSceNetCtlApDialog](../../../core/libs/prx/libSceNetCtlApDialog/Export.cpp) - the access-point dialog shows no UI and reports user cancellation without bringing up a connection. Parameter size, reserved-byte checks, option-string limit and lifecycle errors are recovered from the supplied module. The meaning of the input word at offset 56 and service-side connection behavior remain unresolved. diff --git a/docs/dev/debt/libSceNgs2.md b/docs/dev/debt/libSceNgs2.md index 50b1203e89..f25b3c941d 100644 --- a/docs/dev/debt/libSceNgs2.md +++ b/docs/dev/debt/libSceNgs2.md @@ -3,4 +3,4 @@ ### Unknown function info - [sceNgs2FftInit, sceNgs2FftProcess, sceNgs2FftQuerySize](../../../core/libs/prx/libSceNgs2/PendingExports.cpp) (libSceNgs2) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNgs2.c), but no credible prototypes were found in the cited sources. They are declared without parameters solely to resolve known imports and make no ABI claim. -- [sceNgs2CustomRackGetModuleInfo, sceNgs2GetWaveformFrameInfo, sceNgs2ParseWaveformUser, sceNgs2SystemEnumHandles, sceNgs2SystemEnumRackHandles, sceNgs2VoiceGetMatrixInfo](../../../core/libs/prx/libSceNgs2/PendingExports.cpp) (libSceNgs2) - names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNgs2.c); signatures follow [SharpProspero's Ngs2 bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs), not measured on a console. Opaque structure pointers use `void*`; these stubs throw for import resolution. +- [sceNgs2ParseWaveformUser, sceNgs2SystemEnumHandles, sceNgs2SystemEnumRackHandles](../../../core/libs/prx/libSceNgs2/PendingExports.cpp) (libSceNgs2) - names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNgs2.c); signatures follow [SharpProspero's Ngs2 bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs), not measured on a console. Opaque structure pointers use `void*`; these stubs throw for import resolution. diff --git a/docs/dev/debt/libSceNgs2.native.md b/docs/dev/debt/libSceNgs2.native.md index f5a4bd1856..53f3ab0df1 100644 --- a/docs/dev/debt/libSceNgs2.native.md +++ b/docs/dev/debt/libSceNgs2.native.md @@ -2,41 +2,60 @@ ### Unknown function info +- [NGS2 PCM sampler playback](../../../core/libs/prx/libSceNgs2.native/src/Pcm.cpp) is not console-verified. Types 0x11, 0x12, 0x14, 0x16, 0x18 and 0x1a follow [prosper's waveform parser analysis](https://github.com/mattias800/prosper/blob/30572df43ae20ff28f995db56149f7af42235741/prosper/src/hle/audio/ngs2_waveform.cpp#L194-L223) and the WAVE representations it selects: unsigned 8-bit, signed little-endian 16/24/32-bit and little-endian IEEE float/double. Integer samples use the existing signed-16 normalization generalized to their width; double samples narrow to host float. Console rounding, float clipping, nonfinite and subnormal handling are unknown: floating input outside -1..1, nonfinite values and nonzero magnitudes below the smallest normal float throw. Other types and nonzero configuration words on the new formats throw. Incomplete streamed blocks and dataless silence blocks remain supported only for signed-16 PCM; the new formats require complete blocks. + - [NGS2 voice validation](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) returns `0x804A8203` for an invalid voice in Control, RunCommands, GetState, GetStateFlags, GetPortInfo and QueryInfo, following the type-4 handle error in [prosper's PS5 module analysis](https://github.com/mattias800/prosper/blob/d61eb2c0ad2de879ef74df9c4aa050c512804a18/prosper/src/hle/audio/hle_audio.cpp#L4478). Control checks a null parameter list first (`0x804A8360`); GetState resolves the voice before output validation, and GetState rejects sizes below 8 before a null output. Existing rack-specific exact state sizes, other argument precedence and command layouts are retained; arbitrary-sized state copies and full console behavior remain unverified. These values follow module analysis, not new console execution. - [NGS2 system and rack handle errors](../../../core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp) use `0x804A8201` and `0x804A8202`, following the native handle-type error helper recorded in [prosper's PS5 module analysis](https://github.com/mattias800/prosper/blob/d61eb2c0ad2de879ef74df9c4aa050c512804a18/prosper/src/hle/audio/hle_audio.cpp). [sceNgs2RackGetVoiceHandle](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) resolves the rack before checking the output pointer, matching the export in firmware 1.00 through 12.70 reported in [#5916](https://github.com/boykopovar/AnyPS5/pull/5916#issuecomment-6172832590). Other exports retain their existing validation order; complete invalid-input precedence and console execution remain unverified. -- [NGS2 render buffer capacity](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) is treated as available storage for one configured grain. PPSA01880 passes 303104 bytes for 256 stereo float frames (2048 bytes); rendering writes or clears only those frames and preserves the remaining capacity. Non-null silent outputs now undergo the same supported-format and minimum-size checks as audible outputs, with channel counts limited to 1 through 8. Unused null outputs remain ignored. The console's handling of surplus capacity and invalid silent output descriptors has not been measured; invalid descriptors throw rather than return guessed SDK errors. -- [NGS2 ATRAC9 waveform loops](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) accepts one integer forward `smpl` loop. Inclusive RIFF endpoints and encoder-delay adjustment follow [vgmstream riff.c](https://github.com/vgmstream/vgmstream/blob/7dc938fa2f210943b37c7b6511852b516ef432ab/src/meta/riff.c). PPSA03747 supplies a 60-byte sampler chunk with one loop, `samplerData` 24, start 256, inclusive end 299224 and delay 256 for 298969 audible samples. The chunk size and loop count bound the records; `samplerData` is not interpreted. Output positions are assumed to use audible samples and an exclusive end. Blocks split into an optional intro, a repeated range and an optional tail; zero play count means infinite repeats, positive play count includes the first pass. ATRAC9 ranges include a preceding codec frame for decoder history. The output block layout, repeat-count mapping and error codes are unverified on hardware. Multiple, reverse, alternating and fractional loops throw. +- [NGS2 system option flags](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native) - `sceNgs2SystemQueryBufferSize`, `sceNgs2SystemCreate` and `sceNgs2SystemCreateWithAllocator` accept `flags` 2 (bit 1) and ignore it. PPSA04540 (Rise of the Ronin) passes it when creating its system. The firmware's shared option validator, as read in [prosper](https://github.com/mattias800/prosper/blob/9b0698b1f51258a52dc2c0d9640e02caa9fcc0fa/prosper/src/hle/audio/hle_audio.cpp#L4155-L4188), checks the size and the fields from +0x6c on but not `flags` at +0x68. What bit 1 selects is unknown. Every other bit still throws, including bit 31, which shadPS4 reads. + +- [NGS2 render buffer capacity](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) is treated as available storage for one configured grain. PPSA01880 passes 303104 bytes for 256 stereo float frames (2048 bytes); rendering writes or clears only those frames and preserves the remaining capacity. Non-null silent outputs now undergo the same supported-format and minimum-size checks as audible outputs, with channel counts limited to 1 through 8. A zero buffer count throws: the module accepts it, but whether it renders voices is unknown. Before rendering, over 31 buffers return `0x804A830B`, a type other than 0x12, 0x13, 0x18 or 0x19 `0x804A8050` and a NULL buffer `0x804A8251`, per [prosper's reading](https://github.com/mattias800/prosper/blob/8dcb5dfdb9997ede7c76783995010d6abd1222f8/prosper/src/hle/audio/hle_audio.cpp#L4582-L4606); 0x13 and 0x19 then throw. Surplus capacity is unmeasured; channel and size refusals throw. +- [NGS2 ATRAC9 waveform loops](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - unmeasured on a console. A loop starting inside the decoder delay or ending past the last sample, a loop block past the `data` chunk, a `smpl` chunk too short for its first loop and a looped file at a rate with no module frame size throw: the module wraps or does not bound these, and what it then decodes is unknown. As [prosper reads the module](https://github.com/mattias800/prosper/blob/8dcb5dfdb9997ede7c76783995010d6abd1222f8/prosper/src/hle/audio/ngs2_waveform.cpp#L249-L346), only the first loop's start and inclusive end are used, less the one-frame decoder delay, and type, fraction, play count and other loops are ignored; an end not after the start keeps one block, else an intro, the loop repeating forever and a tail. PPSA03747 (start 256, inclusive end 299224, 298969 samples) gets one block repeating forever +- [NGS2 ATRAC9 parsed delay](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - `sceNgs2ParseWaveformData` reports one codec frame as `num_delay_samples` whatever the `fact` chunk's delay word holds, as [prosper's reading of `libSceNgs2.native.sprx`](https://github.com/mattias800/prosper/blob/30572df43ae20ff28f995db56149f7af42235741/prosper/src/hle/audio/ngs2_waveform.cpp#L224-L231) sets it for ATRAC9 (its [test](https://github.com/mattias800/prosper/blob/30572df43ae20ff28f995db56149f7af42235741/prosper/tests/hle/test_ngs2_waveform.cpp#L326-L342) expects 256 for a `fact` of 900 and 0). Not measured on a console. The block skip still comes from the `fact` chunk's third word, where prosper uses the same one-frame delay - [NGS2 submixer UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (control `0x20000004`) invokes the title's callback after input mixing on every playing grain, including silence. The 40-byte control and 48-byte process context follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/libraries/ngs2/ngs2_submixer.h). PPSA03747 installs a peak-meter callback; its code confirms the channel pointers, user data 0, channel count, grain size and sample-rate offsets and returns 0. Other user data words are preserved. Flags are assumed to be 1 after registration/restart and 0 thereafter, following UserFx2's first-process convention; this callback does not inspect them. Null handlers disable the effect, setup clears it, and nonzero callback results throw. These lifecycle and error semantics are unverified on console hardware. - [NGS2 voice callback after setup](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - a sampler or mixer setup clears the voice callback with the rest of the voice state. PPSA18216 registers a block-end callback for a streamed music voice, later sets the same voice up again for one-shot ATRAC9 blocks with user data 0, and registers the callback again before it streams again; its callback dereferences the block's user data, so it must not run for the one-shot blocks. A setup made from inside the voice's own callback clears that callback as well, so a callback that sets its voice up again and queues the next block runs once instead of for every block; PPSA18216 never sets a voice up from inside its callback, so that ordering is unverified. Unverified on console hardware. - [NGS2 ATRAC9 streaming](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) keeps waveform duration and skip counts across compressed input blocks. The shared `APPEND` flag (2), called `ONLY_DATA` in the compressed-data reference, ignores an ATRAC9 continuation block's sample, skip and repeat counts, following [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/bb98f26433fa9e711c219aac1711fe8ecba5bf3a/src/libs/ngs2.cpp); PPSA03747 confirms a 262044-byte initial buffer declaring 11310532 samples, followed by 16384-byte buffers with flags 3. Incomplete superframes are retained until more input arrives. Input-block completion callbacks allow refills; the final callback of a logical waveform waits for its buffered PCM to drain, preserving the existing complete-block repeat behaviour. This buffering, callback timing, decoded-byte accounting and starvation silence are unverified on hardware. A closed ATRAC9 stream ending before its declared samples throws; fragmented ATRAC9 waveforms with repeats are unsupported. PCM streaming retains its existing append validation and early-close behaviour. -- [NGS2 mastering voice gain](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - the param id 0x30000004 is taken from PPSA22520 and the layout from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `OrbisNgs2MasteringVoiceGainParam`; no reference gives the units or range. The levels are assumed to be linear factors applied when the mastering voice is mixed into the render buffer, so the voice's own samples stay unscaled; the LFE level applies to channel 3 of 6- and 8-channel voices, assuming the L R C LFE order of the AudioOut2 entries above, and the full-band level to the rest. Negative levels are accepted, and a new setup resets both levels to 1 +- [NGS2 mastering voice gain](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - a NaN sample in a PCM_I16L render buffer throws, because PS5 firmware 3.20 and 4.03 were measured only into float buffers, where a NaN gain gives NaN samples. Measured there: param 0x30000004 (id seen in PPSA22520) sets linear full-band and LFE factors on the mastering output, applied from the next render, flat for the first gain a voice receives and ramped over the next grain in eight lanes after that, which this change does not implement; each is clamped to [-32, 32] with NaN kept, and every value returns 0. The LFE level acts on channel 3 of 6- and 8-channel voices and the full-band level on every other channel, 1- and 2-channel voices included; a mastering setup resets both to 1, and STOP_IMM plus PLAY keeps them - [NGS2 stereo mastering voice into a surround buffer](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) (libSceNgs2.native) - PPSA21402 renders a 2-channel mastering voice into a 6- or 8-channel render buffer, which the mixing stage rejected. The console's conversion is unknown: the left and right channels are assumed to go to the first two buffer channels (front left and right) and the remaining channels stay silent, with no centre, LFE or surround content. Any other channel count mismatch still throws - [NGS2 voices without a format](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - a sampler voice whose setup has no format (all zero) accepts an add of no waveform blocks without `APPEND`, which only applies `RESET` and the `CONTINUE` state, and matrix levels with no levels, which clear that matrix. PPSA36591 sets such a voice up, adds no blocks and sets no levels when a button is pressed at its title screen. Whether the console returns success or an error there is not measured; blocks with data, `APPEND` and an empty matrix on a voice with a format are still refused +- [NGS2 play without setup](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - differs from PS5 firmware 3.20 and 4.03 `libSceNgs2.native.sprx` in two cases: a set-up voice given an add of no waveform blocks and played keeps state flags 1 there for as long as it was watched and only until the next render here; an add of no blocks to a voice that already played and ended returns `0x804A8054` there and succeeds here. A `PLAY` on a voice with no channels (never set up; an all-zero setup leaves an earlier setup untouched) returns `0x804A8351` (`SCE_NGS2_ERROR_INVALID_VOICE_ACCESS`) from Control and RunCommands, leaves it idle and skips the rest of the list, as that module does for every rack; PPSA03061 (Void Terrarium) frees its sound slots that way - [NGS2 silent waveform blocks](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native): flag `0x10` (named `SILENCE` here; no public reference names it) lets a waveform block declare samples without data. PPSA04867 adds one block with flags `0x11`, `data_size` 0 and `num_samples` 16385, then another with flags `0x11` and a fully specified 0x800-byte, 1024-sample mono block, which takes the ordinary path. A dataless block under the flag outputs `num_samples` frames of silence, reports them through `decoded_data_size`, and finishes like a complete block; a dataless block without the flag still throws as not fitting its data. The flag's real name, its interaction with ATRAC9 and repeats, and the console's `waveform_data` for silent blocks are unverified on hardware - [NGS2 streaming PCM waveforms](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - PPSA21402 streams its music by adding one block whose `num_samples` is the whole track and whose `data_size` is a 16 KiB first piece, with `CONTINUE`, and then adds each following piece with flag `0x2` (called `APPEND` here) and the same `num_samples`. No reference describes flag `0x2`. A 16-bit PCM block without skipped samples or repeats whose `num_samples` exceed its data is assumed to start such a stream; flag `0x2` is assumed to append the block's data to the last streaming block, ignoring its `num_samples`; the voice is assumed to output silence without advancing while it has played all appended data and `CONTINUE` is set; and the waveform is assumed to end at `num_samples` or, once `CONTINUE` is cleared, at the end of the appended data. `sceNgs2VoiceGetState` reports the bytes played as `decoded_data_size`, which the title uses to release its queued data, and the read position as `waveform_data`. Appending to a block that is not a stream, or other formats, skipped samples, repeats and partial frames, throw - [NGS2 matrix levels](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - levels are clamped to -4..4 and NaN is kept, as the PS4 firmware 11.00 `libSceNgs2.sprx` stores them; not yet confirmed on the PS5 library. That library rejects a level count of 0 and keeps the matrix; the PS5 error code is unknown, so a count of 0 throws, except on a sampler voice without a format, where it clears the matrix (see NGS2 voices without a format). `sceNgs2VoiceRunCommands` is not in that library, so its matrix levels command is assumed to clamp the same way - [NGS2 sampler voice state flag EMPTY](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) (libSceNgs2.native) - a playing sampler voice that still accepts blocks (`CONTINUE`) and has played all queued blocks reports `INUSE|PLAYING|EMPTY` (0x20) and keeps playing, so blocks added later play on. PPSA18391 (engine: Clickteam Fusion) reads `state_flags & 0x22 != 2` from `sceNgs2VoiceGetState` as the end of a sound and only then reuses the voice. When the console sets and clears EMPTY, and whether it also reports it for other voice types, is unverified on hardware. +- [NGS2 reverb channel conversion](../../../core/libs/prx/libSceNgs2.native/src/Reverb.cpp) (libSceNgs2.native) - a reverb voice whose `num_input_channels` differs from `num_output_channels` (PPSA03747 sets up 2 inputs and 8 outputs) mixes its ports in the input layout and folds that to mono with the input count's gains, as an equal-count voice does, and runs the comb and allpass stages per output channel. The PS4 library's handling of the dry signal in this case is not checked: here it reaches the output layout through the default channel map, so a stereo input keeps its dry signal on the front left and right and the other outputs carry only the reverb - [NGS2 port matrix](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) (libSceNgs2.native) - a port without a matrix, or with a selected matrix whose levels were never set, mixes through a default channel map: mono reaches both stereo channels at -3 dB and the 5.1 and 7.1 centre, stereo folds to mono at -3 dB per side, and wider sources fold down with 0.5 and -3 dB gains. The map and the equivalence of an unset matrix with no matrix were measured from the PS4 firmware 11.00 `libSceNgs2.sprx`, not yet from the PS5 library. That library only accepts submixer and mastering voices with 1, 2, 6 or 8 channels, so the map into 3, 4, 5 or 7 channels throws. A matrix set with fewer levels than the destination needs still mixes only the rows it has -- [NGS2 custom submixer rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - structure layouts from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_custom.h`; module id 0x1f for UserFx2 and 0x1d for UserFx, voice parameter ids `0x40001f00 | module index` and `0x40001d00 | module index`, the process flags (1 on the first process call after setup, 2 after a parameter change, 0 otherwise) and the call order (setup per voice at rack creation, process after the inputs are mixed, cleanup at destruction) come only from [KytyPS5](https://github.com/KytyPS5/KytyPS5) and shadPS4. Only UserFx and UserFx2 modules on a single buffer are accepted: other module ids, more buffers, a module control handler, different input and output channel counts, setup flags and default rack options throw. Each module's state is a separate buffer, not the `state_offset` slice of the voice state, and `sceNgs2VoiceGetState` still throws for this rack. As in KytyPS5, UserFx2 process runs only on grains where an input produced samples, so an effect tail stops with the input and a pending flag 2 waits for the next audible grain -- [NGS2 custom module ids](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - a custom rack with a module id other than 0x13 (sampler rack), 0x1d and 0x1f (submixer rack) throws. Trails in the Sky 2nd Chapter (PPSA35169) creates a rack with module id 0x20 (`NGS2: custom module id 0x20 is not implemented`, #4246); which module it is, and its structure, are unknown. +- [NGS2 custom submixer rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - UserFx2 process flags and setup/process/cleanup order follow only [KytyPS5](https://github.com/KytyPS5/KytyPS5), not a console measurement; layouts follow [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_custom.h`. A module control handler, unequal input and output channels, setup flags and default rack options throw. Module state is a separate buffer, not the `state_offset` slice of voice state; `sceNgs2VoiceGetState` throws for this rack. UserFx2 runs only on grains with input samples, so an effect tail stops with the input +- [NGS2 custom UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - the 48-byte process context, process flags, callback error handling and null-handler behavior retain the existing submixer callback model and are not console-verified. Non-null module options throw because their console error is unknown. UserFx on custom sampler rack 0x4001 throws: its parameter indexing and routing are not implemented. Module id 0x19, the 40-byte voice parameter (handler and three user-data words), parameter id `0x40001900 | index`, and lack of option and module state follow [fma-rc's PS5 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943), including parameter dispatch at 0x25ec4. +- [NGS2 custom Delay](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, 0x1d) - processing and defaults remain unverified on PS5. The [agreed compatibility path](https://github.com/boykopovar/AnyPS5/pull/9337#issuecomment-6351205140) preserves main's PCM pass-through for Void Terrarium (PPSA03061) until any Delay parameter is set; every `0x40001dxx` parameter throws. Only one-buffer custom submixers with no module state and a 20-byte option are accepted. Option type 0/1, taps 1..8, length 1..20000 and reserved 0 follow [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943). Other shapes and values throw because their console errors are unknown. This preserves host behavior, not console-equivalent Delay processing +- [NGS2 custom module ids](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - Reverb (0x1b), PitchShift (0x20) and Convolution (0x23) are not processed: a rack that passes their creation checks throws. Gain (0x15) on custom sampler racks also throws. Per [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6294323150), creation returns `0x804A8302` for a wrong non-zero state size, and `0x804A8013`, `0x804A8099` and `0x804A8098` for a bad Reverb option size or word and PitchShift word. Unknown: check order, errors for a Gain option, missing Reverb or PitchShift options, other PitchShift and Convolution option sizes, and Convolution's state (64 bytes from SDK 3.0). PPSA04540 uses 0x15, 0x19, 0x1b and 0x23 (#8039), PPSA35169 0x20 (#4246) and PPSA33655 0x15, 0x19 and 0x1b +- [NGS2 custom Gain module](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - follows [fma-rc's reading of the 12.70 module](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943): parameter `0x40001500 | module index` holds 37 linear levels, one per channel, default 0, and the module writes source × level into dest, ramping linearly over the grain from the previous level. Unverified: where the ramp starts and ends (here the grain's first sample is one step past the previous level and its last sample is at the new level), whether stopping or setting up the voice again resets the levels, and what the library does with a level outside -32 to 32 (here it throws). On a custom sampler rack, whose parameter index is one higher per fma-rc, Gain throws. A Gain module with an option throws: fma-rc reads Gain as taking none, and the error the library returns for one is unknown. The extra buffer is ignored, as fma-rc reads it +- [NGS2 custom mastering rack](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native, rack 0x4003) - follows [fma-rc's reading of the 12.70 module](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943): option size is 0x508, matching the custom submixer rack layout. Creation returns `0x804A8052` above 37 channels; channels 9 to 37 throw because voices hold at most 8 channels. Mastering racks with modules throw: attached modules and non-default option fields are unverified on PS5. Voice control parameters 0x40030000 (setup), 0x40030004 (gain) and 0x40030005 (output) configure channels, levels and destination buffer. `sceNgs2VoiceGetState` fills common voice state flags. PPSA10278 and PPSA04540 create rack 0x4003 without modules (#8039) +- [NGS2 custom rack buffers and channels](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, racks 0x4001 and 0x4002) - racks with 2 to 4 buffers, custom submixers with 9 to 37 channels and submixer `max_inputs` other than 1 or 2 throw: how modules and ports route several buffers is unknown, and voices hold at most 8 channels. Per [fma-rc's reading of the 12.70 module](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6294323150), creation returns `0x804A830B` for a buffer count outside 1 to 4 or below the submixer's `max_inputs`, `0x804A8306` for a module source, extra or dest buffer id at or above the count, and `0x804A8052` for more than 37 submixer channels; the check order is not known. `sceNgs2RackQueryBufferSize` throws for these options, as no source says whether it checks them. A port buffer id other than 0 throws. PPSA04540 creates racks with up to 4 buffers and a 16-channel custom submixer (#8039) - [NGS2 reverb rack](../../../core/libs/prx/libSceNgs2.native/src/Reverb.cpp) (libSceNgs2.native, rack 0x2001) - follows the PS4 firmware 11.00 `libSceNgs2.sprx`, read and checked against its output at 11.025 to 48 kHz, including parameter changes during playback (differences at float rounding level); not yet compared with the PS5 library. Structure layouts come from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_reverb.h` and the param ids 0x20010000 (setup) and 0x20010001 (I3DL2) from PPSA22520. Only the default `reverb_size` of 1 is handled, with its 14400-sample reflection buffer; other sizes and the library's half-rate mode throw. System sample rates below about 7.5 kHz, where an allpass delay would have no samples, throw. Setup only accepts equal input and output channel counts - [sceNgs2ReportRegisterHandler, sceNgs2ReportUnregisterHandler](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native, NIDs `uBIN24Tv2MI`, `nPzb7Ly-VjE`) keep a registry of report handlers. Register rejects a null handler with an invalid argument and returns `INVALID_OUT_ADDRESS` for a null out handle, then stores the report type, handler and user data under a freshly allocated opaque handle; unregister removes and frees the matching record and throws for a handle that is not registered. The signatures (report type, handler, user data, out handle) and (report handle) follow [SharpProspero](https://github.com/SvenGDK/SharpProspero) and [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_report.h`, and the 24-byte `Ngs2ReportDataHeader` passed to a handler is its `OrbisNgs2ReportDataHeader`. No report is produced yet, so no registered handler is ever invoked and PPSA04540, which imports both and registers a CPU load handler (type 5), still receives none. -- [sceNgs2SystemSetSampleRate, sceNgs2SystemSetUserData, sceNgs2SystemGetUserData, sceNgs2SystemLock, sceNgs2SystemUnlock](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native) - shadPS4 only checks the system handle (`INVALID_SYSTEM_HANDLE`) and SharpProspero names what each call does; neither gives the other limits. A sample rate of 0 and a null user data pointer throw, any other rate is accepted, and changing the rate while a sampler filter is enabled throws because the filter coefficients were computed for the old rate. `sceNgs2SystemLock` and `sceNgs2SystemUnlock` follow [prosper's notes](https://github.com/mattias800/prosper/blob/d61eb2c0ad2de879ef74df9c4aa050c512804a18/prosper/src/hle/audio/hle_audio.cpp#L4367-L4384) on the PS5 module: Lock takes a per-system lock and counts nested holds, Unlock releases one hold from any thread and returns 0 without a hold, and `sceNgs2SystemRender` only tries the lock: while another thread holds it, Render clears one grain of each output, leaves the render count and voices unchanged and returns 0. The other calls do not wait for the system lock. Peace Walker (PPSA18216) holds it while it spins on a flag that its audio thread clears only after `sceNgs2SystemRender` and eight `sceNgs2VoiceGetStateFlags` calls, so on the console neither can wait for it. Whether the console's voice and rack calls take the lock at all is not known. `sceNgs2RackLock` still holds the library's lock until `sceNgs2RackUnlock`, as before +- [sceNgs2SystemSetUserData, sceNgs2SystemGetUserData, sceNgs2SystemLock, sceNgs2SystemUnlock](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native) - shadPS4 only checks the system handle (`INVALID_SYSTEM_HANDLE`) and SharpProspero names what each call does; neither gives the other limits. A null user data pointer throws. `sceNgs2SystemLock` and `sceNgs2SystemUnlock` follow [prosper's notes](https://github.com/mattias800/prosper/blob/d61eb2c0ad2de879ef74df9c4aa050c512804a18/prosper/src/hle/audio/hle_audio.cpp#L4367-L4384) on the PS5 module: Lock takes a per-system lock and counts nested holds, Unlock releases one hold from any thread and returns 0 without a hold, and `sceNgs2SystemRender` only tries the lock: while another thread holds it, Render clears one grain of each output, leaves the render count and voices unchanged and returns 0. The other calls do not wait for the system lock. Peace Walker (PPSA18216) holds it while it spins on a flag that its audio thread clears only after `sceNgs2SystemRender` and eight `sceNgs2VoiceGetStateFlags` calls, so on the console neither can wait for it. Whether the console's voice and rack calls take the lock at all is not known. `sceNgs2RackLock` still holds the library's lock until `sceNgs2RackUnlock`, as before +- [sceNgs2GetWaveformFrameInfo](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - not measured on a console; shadPS4 only stubs it, and prosper does not cover the call. The outputs are taken to be the frame fields of the parsed waveform info, which [prosper's reading of `libSceNgs2.native.sprx`](https://github.com/mattias800/prosper/blob/30572df43ae20ff28f995db56149f7af42235741/prosper/src/hle/audio/ngs2_waveform.cpp#L224-L231) fills for ATRAC9 with the superframe bytes and samples, the frames per superframe and a one-frame delay, whatever the `fact` chunk says. `PCM_I16L` answers channels * 2 bytes, 1 sample, 1 unit and no delay, as for parsed 16-bit PCM, itself assumed. Other types throw. Null outputs return `INVALID_OUT_ADDRESS` and an ATRAC9 format the codec does not accept `INVALID_WAVEFORM_FORMAT`; the codes are unverified - [sceNgs2RackSetUserData, sceNgs2RackGetUserData](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native) - the signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs#L418-L421) and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ngs2/ngs2.cpp#L86-L111), which only logs them; neither gives an error. A handle that is not a live rack answers `INVALID_RACK_HANDLE` before a null user data pointer is checked, and the null pointer throws, as in `sceNgs2SystemGetUserData`. A new rack starts with 0. Not measured on a console - [sceNgs2RackGetInfo](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native) - field order from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `OrbisNgs2RackInfo` with the 64-byte name of the other info structures (216 bytes); `type` is assumed to be the rack id divided by 0x1000, `min_grain_samples` 64 (the system minimum) and `state_flags` 1; `uid` comes from a per-process rack counter, `render_count` is the owning system's render count and `active_voice_count` counts voices that are not empty (paused and stopped voices included). The last process ratio and tick and `active_channel_work_count` are left zero +- [NGS2 system option checks](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native) - not verified on a console. `sceNgs2SystemQueryBufferSize`, `sceNgs2SystemCreate` and `sceNgs2SystemCreateWithAllocator` return `0x804A8013` (size), `0x804A8311` (max grain, a multiple of 64 up to 2048), `0x804A8310` (grain, a multiple of 64 up to the max), `0x804A8051` (one of ten sample rates) and `0x804A8052` (1 to 37 channels) in that order, following [prosper's PS5 module analysis](https://github.com/mattias800/prosper/blob/8dcb5dfdb9997ede7c76783995010d6abd1222f8/prosper/src/hle/audio/hle_audio.cpp#L4151-L4226). The four job scheduler pointers are neither inspected nor used. +- [sceNgs2SystemSetGrainSamples, sceNgs2SystemSetSampleRate](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native) - a grain that is not a multiple of 64 is accepted, where the module returns `0x804A8310`, because the NGS2 tests render 8-sample grains. Changing the rate while a sampler filter is enabled throws, as the filter coefficients were computed for the old rate. After the handle check, a grain of 0 or above the system's maximum returns `0x804A8310`, and a rate other than 11025, 12000, 22050, 24000, 44100, 48000, 88200, 96000, 176400 or 192000 returns `0x804A8051`, per [prosper's reading of the module's command validators](https://github.com/mattias800/prosper/blob/8dcb5dfdb9997ede7c76783995010d6abd1222f8/prosper/src/hle/audio/hle_audio.cpp#L4355-L4399); not measured on a console - [sceNgs2VoiceQueryInfo](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - only info id 0x4001 is known: PPSA22520 passes an 8-byte buffer and branches on the first word being 1 or 2, so it is assumed to be the voice channel count (0 before the voice is set up); the second word is zeroed and other ids throw +- [sceNgs2SystemCreateWithAllocator free handler](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native) - an allocator with an `alloc_handler` but no `free_handler` throws after the option checks: the module only checks the `alloc_handler`, and what it does without a free handler is unknown. Otherwise `sceNgs2SystemCreate` returns `0x804A8020` for a null buffer info or host buffer, and `sceNgs2SystemCreateWithAllocator` `0x804A8023` for a null allocator or `alloc_handler`, before the null-handle `0x804A8010` and the option checks, per [prosper's reading of the module](https://github.com/mattias800/prosper/blob/8dcb5dfdb9997ede7c76783995010d6abd1222f8/prosper/src/hle/audio/hle_audio.cpp#L4246-L4334); not measured on a console - [sceNgs2VoiceGetOwner](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - the signature `(voiceHandle, outOwnerRack, outVoiceIndex)` follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs#L439-L440) and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ngs2/ngs2.cpp#L322-L326), which only logs it; neither gives an error. The errors are unmeasured: a handle that is not a live voice answers `INVALID_VOICE_HANDLE` before the outputs are checked, as in `sceNgs2RackGetVoiceHandle`, and either output pointer null answers `INVALID_OUT_ADDRESS` without writing the other +- [sceNgs2VoiceGetMatrixInfo](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - a matrix id past the rack's `max_matrices` throws, as the PS5 code for shadPS4's PS4 [`INVALID_MATRIX_INDEX`](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ngs2/ngs2_error.h#L92) is unknown. A matrix without levels reports 0 levels, though it mixes through the default channel map. Signature from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs#L442), layout (a count and 64 levels) from [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ngs2/ngs2.h#L263-L266); levels are the stored, clamped ones. Unmeasured: a null info answers `INVALID_OUT_ADDRESS` and a size other than 260 `INVALID_OUT_SIZE` before the voice is checked, as in `sceNgs2VoiceGetPortInfo` +- [sceNgs2CustomRackGetModuleInfo](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native) - a size over 32 bytes throws, as the longer per-module info in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ngs2/ngs2_custom.h#L419-L442) is not kept. A size under 32 and an index past the rack's modules (any on a non-custom rack) also throw: the PS5 codes for shadPS4's PS4 [`INVALID_MODULE_INDEX` and `INVALID_MODULE_INFO_SIZE`](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ngs2/ngs2_error.h#L117-L118) are unknown. It repeats the ids, state offset and size from rack creation, reserved words 0, as [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs#L323-L341) lays it out. Unmeasured: a null info gives `INVALID_OUT_ADDRESS`, then a bad handle `INVALID_RACK_HANDLE` - [sceNgs2VoiceControl patch](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - a destination rack handle with input id 0 resolves to that rack's first voice; this matches the Dead Cells call in #4334, but selection for racks with multiple voices has not been checked against a console. Nonzero destination input ids still throw. `sceNgs2VoiceGetPortInfo` reports the resolved voice handle and zero delay. +- [NGS2 PCM waveform parsing](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - `sceNgs2ParseWaveformData` reads integer PCM (tag 1, or `WAVE_FORMAT_EXTENSIBLE` with the PCM GUID) of 8, 16, 24 and 32 bits as types 0x11, 0x12, 0x14 and 0x16, and float (tag 3) of 32 and 64 bits as 0x18 and 0x1a, and returns 0x804A8432 for other depths, after [prosper's reading of `libSceNgs2.native.sprx`](https://github.com/mattias800/prosper/blob/30572df43ae20ff28f995db56149f7af42235741/prosper/src/hle/audio/ngs2_waveform.cpp#L194-L223); not measured on a console. Every depth keeps the checks of the 16-bit path (channels, rate, block align, whole frames, a looped `smpl` chunk throws), which that reading does not apply. Voices still play only 16-bit PCM and ATRAC9 - [sceNgs2ParseWaveformFile](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - the signature `(path, offset, info)` is taken from the call site of PPSA21402, which sets only three argument registers; the file is read from `offset` to its end, the data and block offsets are taken to be relative to the start of the file, not of `offset`, and an unreadable file throws. The audio unit fields of a 16-bit PCM waveform are assumed to be one sample frame per unit and per frame; `INVALID_WAVEFORM_DATA` for a `data` chunk that runs past the buffer or ends inside a frame is assumed, and looped PCM and other PCM formats throw - [sceNgs2VoiceControl sampler filter](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - param `0x1000000a`, its layout and meaning follow KytyPS5 only: location 1 type 1 is an RBJ low-pass biquad at the system sample rate after resampling, scaled by `level`, a set `channel_mask` bit bypasses that channel, type 0 turns the filter off. Other locations and types throw. The voice keeps playing a filter tail after its last block until the history is zero, with outputs below 1e-20 flushed to zero (a KytyPS5 choice); the console's tail length is unknown -- [sceNgs2PanInit / sceNgs2PanGetVolumeMatrix](../../../core/libs/prx/libSceNgs2.native/src/Pan.cpp) (libSceNgs2.native) - follow the PS4 firmware 11.00 `libSceNgs2.sprx`, read and checked against its output for 2, 4, 5 and 7 speakers into 1, 2, 6 and 8 channels; not yet confirmed on the PS5 library. Speakers are taken in the order L R, L R Ls Rs, L R C Ls Rs or L R C Ls Rs Lb Rb and kept in a fixed circular order, not sorted; without angles the defaults are 90/270, 40/110/250/320, 0/40/110/250/320 and 0/40/110/150/210/250/320 degrees. As in that library, defaults converted to radians keep the wrap-around entry in degrees, so angles past the last speaker pan almost fully to it. Error codes are not known for the PS5, so invalid arguments throw, including a speaker angle equal to the unit angle, on which that library loops forever. Angles so large that adding or subtracting a turn leaves them unchanged throw for the same reason +- [sceNgs2PanInit / sceNgs2PanGetVolumeMatrix](../../../core/libs/prx/libSceNgs2.native/src/Pan.cpp) (libSceNgs2.native) - inputs on which the PS5 `libSceNgs2.native.sprx` of firmware 3.20 and 4.03 did not return within 60 s throw; whether it returns at all for them is unknown. They are an infinite speaker or param angle, an angle too large for a turn to change it, a speaker angle that wraps to exactly the unit angle, and a param angle that wraps onto the first speaker plus one turn, such as one float below the first speaker in radians - [sceNgs2GeomResetListenerParam, sceNgs2GeomResetSourceParam](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) (libSceNgs2.native) - the structures (12, 16, 16, 60, 96, 108, 32 and 308 bytes) come from [KytyPS5](https://github.com/KytyPS5/KytyPS5)'s `ngs2.cpp` and match [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_geom.h` field for field, but only KytyPS5 gives the behaviour - shadPS4 logs and returns success without writing the output. The defaults (orient front +Z, orient up +Y, sound speed 343, cone 1/360/1/360, rolloff 0/1000000/1/1, doppler factor, full-band, LFE and maximum level 1, two speakers and matrix format 2) are unverified on a PS5 title; KytyPS5 and iStark/PS5PCEM agree on them. PPSA15552 (Dead Cells) calls `sceNgs2GeomResetSourceParam` after creating its racks. A null output address returns `INVALID_OUT_ADDRESS` as elsewhere in the library rather than throwing as KytyPS5 does. `sceNgs2GeomApply` is now a minimal pass-through that follows [KytyPS5](https://github.com/KytyPS5/KytyPS5)'s `Ngs2GeomApply` (shadPS4 only stubs it): pitch 1, source position, volume the larger of the source minimum and maximum level, and a diagonal of the source maximum level (1 when it is not positive) over `matrix_format` channels (0 is two, at most 8). The listener and `flags` are ignored, so source attenuation, distance rolloff, panning and Doppler stay unimplemented; neither KytyPS5 nor this has been measured on a PS5, and a null output address returns `INVALID_OUT_ADDRESS` where KytyPS5 exits -- [sceNgs2GeomCalcListener](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) builds an orthonormal world-to-listener transform. The row-major matrix, front-axis priority and flag 1 for right-handed input follow [PS5PCEM](https://github.com/iStark/PS5PCEM/blob/d2534bac6325a2c61c0fa88977e5321074304484/src/hle/ngs2_geom.zig); they have not been measured on PS5 hardware. Velocity stays in world coordinates and positive finite sound speed is preserved. Orientation vectors are normalised and up is made perpendicular to front, so non-unit and slightly tilted vectors are accepted. When front and up are parallel, up is replaced by `(0,1,0)` or `(1,0,0)` so the basis stays defined: PPSA15552 (Dead Cells) passes `orient_front={0,0,1}` with `orient_up={0,0,1}` after moving the listener, which PS5PCEM rejects; the console error code is unknown. Unknown flags, zero orientation vectors, nonfinite input and an unrepresentable translation still throw without changing the output; console error codes are unknown, and a nonpositive sound speed throws where PS5PCEM and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/ngs2.cpp) default it to 343. KytyPS5 always writes the identity matrix, so the transform rests on PS5PCEM plus that Dead Cells case. Dead Cells (PPSA15552) reaches this call after the custom sampler rack in #4334. This does not implement source attenuation, panning or Doppler in `sceNgs2GeomApply`. +- [sceNgs2GeomCalcListener](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) builds an orthonormal world-to-listener transform. The row-major matrix, front-axis priority and flag 1 for right-handed input follow [PS5PCEM](https://github.com/iStark/PS5PCEM/blob/d2534bac6325a2c61c0fa88977e5321074304484/src/hle/ngs2_geom.zig); they have not been measured on PS5 hardware. Velocity stays in world coordinates and positive finite sound speed is preserved. Orientation vectors are normalised and up is made perpendicular to front, so non-unit and slightly tilted vectors are accepted. When front and up are parallel, up is replaced by `(0,1,0)` or `(1,0,0)` so the basis stays defined: PPSA15552 (Dead Cells) passes `orient_front={0,0,1}` with `orient_up={0,0,1}` after moving the listener, which PS5PCEM rejects; the console error code is unknown. Zero orientation vectors, nonfinite input and an unrepresentable translation still throw without changing the output; console error codes are unknown, and a nonpositive sound speed throws where PS5PCEM and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/ngs2.cpp) default it to 343. KytyPS5 always writes the identity matrix, so the transform rests on PS5PCEM plus that Dead Cells case. Dead Cells (PPSA15552) reaches this call after the custom sampler rack in #4334. This does not implement source attenuation, panning or Doppler in `sceNgs2GeomApply`. - [NGS2 custom sampler rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4001) - implements bounded support for the custom sampler rack and its built-in module 0x13 (Custom Voice Filter), unblocking PPSA15552 (Dead Cells) startup. Structure layouts come from [shadPS4's ngs2_custom.h](https://raw.githubusercontent.com/shadps4-emu/shadPS4/main/src/core/libraries/ngs2/ngs2_custom.h) (OrbisNgs2CustomVoiceFilterParam, 40 bytes) and live trace correlation from [prosper's hle_audio.cpp](https://raw.githubusercontent.com/mattias800/prosper/main/prosper/src/hle/audio/hle_audio.cpp) (0x40001300 command and parameters like 10000.0f and 0.707f). Routes parameter updates via the 0x40001300 command family into the Biquad filter engine. Filter type 0 clears the destination filter; type 1 configures the RBJ low-pass biquad frequency, Q and level. Other filter types and unmodeled configurations throw. - [sceNgs2VoiceGetState custom sampler](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native, rack 0x4001) - only the size and one field come from a title. PPSA15552 (Dead Cells) calls it on custom sampler voices with a 48-byte buffer at two sites (eboot `0x1740f90`, `0x1741009`) and reads only the 64-bit value at offset 16: the first site keeps it and the second subtracts the kept value, so it is filled with the voice's decoded sample count. Other sizes return `INVALID_OUT_SIZE` as for the other racks. The state flags fill the common voice state; the remaining fields stay zero. shadPS4's `OrbisNgs2CustomSamplerVoiceState` is about 80 bytes and does not match the size this title passes, so the field names and the console's value are unverified -- [sceNgs2CalcWaveformBlock](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - VAG (0x1c) throws. ATRAC9 keeps AnyPS5's own mapping: it checks the config against the channels and rate (0x804A8431), leaves out the decoder delay from the skip and size, leaves both 0 for zero samples and computes the size in 64 bits, while [prosper reads the PS5 module](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/src/hle/audio/ngs2_waveform.cpp#L348-L396) without the check, with the delay, filling both and modulo 2^32. Linear PCM and float (0x10 to 0x1b), the null checks and the 0x804A8432 of other types follow that reading and [its entry point](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/src/hle/audio/ngs2_waveform.cpp#L444-L466). Closes when: VAG is mapped and ATRAC9 follows the module +- [sceNgs2CalcWaveformBlock](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - an ATRAC9 config whose sampling-rate index (bits 20 to 23) is not 1, 4 or 7 throws, where the module reads 0 frame samples from its table and divides by zero. Every type follows [prosper's reading of the PS5 module](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/src/hle/audio/ngs2_waveform.cpp#L348-L396) and [its entry point](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/src/hle/audio/ngs2_waveform.cpp#L444-L466): linear PCM and float (0x10 to 0x1b), VAG (0x1c) and ATRAC9 (whole superframes from the config alone, a skip of one frame of decoder delay plus the position in the superframe, modulo 2^32), the null checks and the 0x804A8432 of other types. Closes when: the other sampling rates are known ### Functional diff --git a/docs/dev/debt/libSceNotification.md b/docs/dev/debt/libSceNotification.md new file mode 100644 index 0000000000..6245484e54 --- /dev/null +++ b/docs/dev/debt/libSceNotification.md @@ -0,0 +1,6 @@ +# libSceNotification technical debt + +### Unknown function info + +- [sceNotificationSend, sceNotificationSendById, sceNotificationShowPsButtonPersistentBanner, sceNotificationHidePsButtonPersistentBanner](../../../core/libs/prx/libSceNotification/Export.cpp) (libSceNotification) - notification sending and banner presentation interfaces throw through `NotImplemented_nid_no_patch`. Native notification service IPC and parameter structures remain unimplemented. +- [sceNotificationSend](../../../core/libs/prx/libSceNotification/Export.cpp) (libSceNotification) - what the console shows, checks and returns is not measured. The parameters are the ones homebrew built with the [ps5-payload-dev SDK](https://github.com/ps5-payload-dev/sdk) declares, `int sceNotificationSend(int userId, bool isLogged, const char* payload)`: its `notify` sample passes the system user (`0xFE`), `true` and a JSON document whose `rawData` names a `viewTemplateType` such as `InteractiveToastTemplateB` and holds the `viewData` to show, and exits with the result. Closes when: measured on a PS5. diff --git a/docs/dev/debt/libSceNpAuth.md b/docs/dev/debt/libSceNpAuth.md index 2ad98e9bdd..e918766151 100644 --- a/docs/dev/debt/libSceNpAuth.md +++ b/docs/dev/debt/libSceNpAuth.md @@ -2,6 +2,12 @@ ### Unknown function info -- [sceNpAuthGetAuthorizedAppCode](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - declared without parameters and returns `SCE_NP_ERROR_SIGNED_OUT` without reading them, as there is no PSN sign-in; the request parameter and code output are not known. Minecraft (PPSA17221) imports it, fails to load without it, and after its sign-in dialog treats the signed-out code as offline play. -- [sceNpAuthAbortRequest, sceNpAuthGetAuthorizationCode, sceNpAuthGetAuthorizationCodeA, sceNpAuthGetAuthorizationCodeV3, sceNpAuthGetIdToken, sceNpAuthGetIdTokenA, sceNpAuthGetIdTokenV3, sceNpAuthPollAsync, sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - request tracking, `ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND` (0x80550306) on unknown IDs, `ORBIS_NP_AUTH_ERROR_INVALID_ID` (0x80550307) on non-async or unprocessed requests in Poll/Wait, `ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT` (0x80550301) on null arguments, and `ORBIS_NP_AUTH_ERROR_ABORTED` (0x80550304) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp) (PS4); the A calls forward to their V3 counterparts, which in shadPS4 ([AuthorizationCode A](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L189-L193), [V3](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L195-L199); [IdToken A](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L375-L378), [V3](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L380-L383)) call the same helper and differ only in a version flag; async requests defer `SCE_NP_ERROR_SIGNED_OUT` to Poll/Wait while sync requests fail immediately; PSN is not contacted. Parameter size validation and PS5 behavior are unverified -- [sceNpAuthSetTimeout](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - imported by PPSA01317, which fails to load without it. The signature `(reqId, resolveRetry, resolveTimeout, connTimeout, sendTimeout, recvTimeout)` follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/np/np_auth.cpp) (PS4), which only stubs it. An unknown request ID returns `ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND` like the other request calls; otherwise it returns OK and keeps nothing, as no request reaches the network the timeouts would bound. Argument checks of the console are unverified +- [sceNpAuthAbortRequest, sceNpAuthGetAuthorizationCode, sceNpAuthGetAuthorizationCodeA, sceNpAuthGetAuthorizationCodeV3, sceNpAuthGetIdToken, sceNpAuthGetIdTokenA, sceNpAuthGetIdTokenV3, sceNpAuthPollAsync, sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - request tracking, `ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND` (0x80550306) on unknown IDs, `ORBIS_NP_AUTH_ERROR_INVALID_ID` (0x80550307) on non-async or unprocessed requests in Poll/Wait, `ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT` (0x80550301) on null arguments, and `ORBIS_NP_AUTH_ERROR_ABORTED` (0x80550304) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp) (PS4); the A calls forward to their V3 counterparts, which in shadPS4 ([AuthorizationCode A](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L189-L193), [V3](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L195-L199); [IdToken A](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L375-L378), [V3](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp#L380-L383)) call the same helper and differ only in a version flag; async requests defer `SCE_NP_ERROR_SIGNED_OUT` to Poll/Wait while sync requests fail immediately; PSN is not contacted. Call sites in three disassembled titles (PPSA02225, PPSA15246, PPSA30601; static reading only) show the V3 parameter layouts: the code request parameter is 0x20 bytes (size, a 32-bit value that the titles take from their user object, a pointer to a client-ID-like string, a pointer to a constant string) and the call is `(reqId, param, code[128], int* issuerId)`; the ID token request parameter is 0x28 bytes (size, the same kind of 32-bit value, a pointer to a client-ID-like string, a pointer to a second buffer (256 bytes in one title) and a pointer to a constant string) and the call is `(reqId, param, token)`. The client-ID-like string is 36 characters plus a terminator in one title and a 128-byte buffer in two. In two titles the calls follow `sceNpAuthCreateAsyncRequest` (whose parameter holds only a size of 0x18) and are polled with `sceNpAuthPollAsync` (one title once per frame, one in a blocking loop of up to 40 polls 100 ms apart); the third title is a synchronous caller that takes the request ID from an argument-less call. All three call `sceNpAuthDeleteRequest` once the request has finished or failed (immediately after the call in the synchronous title, after a poll return of 0 in the per-frame title, on every path in the blocking one). The titles treat a poll return of 0 as finished and any negative result as a failure without looking at the specific code; one title, after 40 polls that still return non-zero, treats a return of exactly 1 as a timeout and calls `sceNpAuthAbortRequest`, and reports 0x80550301 and 0x80550306 from Poll as an invalid result pointer and an unknown request ID. The size fields are not checked here; the console's size checks and the behaviour of the calls on a console are unverified + +- [sceNpAuthPollAsync and sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - PS5 abort-before-start behavior is unmeasured. Poll/Wait keep returning `ORBIS_NP_AUTH_ERROR_INVALID_ID` without changing output after a pre-start abort, including subsequent rejected getter calls, following [shadPS4 StartRequest and FindAsyncRequest](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_auth/np_auth.cpp). + +### Functional + +- [sceNpAuthSetTimeout](../../../core/libs/prx/libSceNpAuth/Export.cpp): native validation and storage are implemented: retry must be nonnegative, the all-zero tuple is invalid, resolve timeout is zero or at least 1000000, and other timeouts are zero or at least 10000000. Invalid arguments precede request lookup. Stored settings currently have no transport to configure because authentication remains offline. + +- [sceNpAuthGetAuthorizedAppCode](../../../core/libs/prx/libSceNpAuth/Export.cpp): the five-argument ABI and 40-byte request parameter are recovered. Pointer, size, user, option and request-ID validation replaces the unconditional signed-out result. Valid calls throw because authorization and asynchronous result delivery are not implemented. Client-ID storage is copied as 136 bytes by the firmware; its internal fields and the complete output-code format remain unresolved. diff --git a/docs/dev/debt/libSceNpAuthAuthorizedAppDialog.md b/docs/dev/debt/libSceNpAuthAuthorizedAppDialog.md index e6b5fa19a6..6f98749d82 100644 --- a/docs/dev/debt/libSceNpAuthAuthorizedAppDialog.md +++ b/docs/dev/debt/libSceNpAuthAuthorizedAppDialog.md @@ -2,5 +2,4 @@ ### Silent stubs -- [libSceNpAuthAuthorizedAppDialog](../../../core/libs/prx/libSceNpAuthAuthorizedAppDialog/Export.cpp) - the authorized-app dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as there is no PSN sign-in to authorize an app against. The signatures follow the other common dialogs (`Open` takes a parameter block, `GetResult` writes a 32-bit result); Minecraft (PPSA17221) imports the seven functions and fails to load without them. -- [sceNpAuthAuthorizedAppDialogClose](../../../core/libs/prx/libSceNpAuthAuthorizedAppDialog/Export.cpp) - `sceNpAuthAuthorizedAppDialogClose` moves an initialized dialog straight to `COMMON_DIALOG_STATUS_FINISHED`, also when called before `Open`, following the Signin / NetCtlAp dialogs; MsgDialog / SaveDataDialog / ErrorDialog return NOT_RUNNING or INVALID_STATE for that case, and which one the real library does is unverified. +- [libSceNpAuthAuthorizedAppDialog](../../../core/libs/prx/libSceNpAuthAuthorizedAppDialog/Export.cpp) - the authorized-app dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as there is no PSN sign-in to authorize an app against. Parameter fields at offsets 64 and 72 feed a 136-byte application record and a text value of at most 1024 bytes; their service-side meaning remains incomplete. diff --git a/docs/dev/debt/libSceNpCommerce.md b/docs/dev/debt/libSceNpCommerce.md index d9ac7be185..762eed1207 100644 --- a/docs/dev/debt/libSceNpCommerce.md +++ b/docs/dev/debt/libSceNpCommerce.md @@ -6,5 +6,7 @@ ### Unknown function info -- [sceNpCommerceDialogInitializeInternal](../../../core/libs/prx/libSceNpCommerce/Export.cpp) (libSceNpCommerce) - its name and NID are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpCommerce.c); the no-parameter prototype follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_commerce/np_commerce.cpp#L61) and is not measured on a console. It is declared without parameters solely to resolve known imports; this is not an ABI claim, and every call throws. -- [sceNpCommerceDialogOpen2](../../../core/libs/prx/libSceNpCommerce/Export.cpp) (libSceNpCommerce) - declared without parameters; follows [prosper](https://github.com/mattias800/prosper/blob/73413cb445b0364bb49c4d2478e9ee1614b9f7d9/prosper/src/hle/np/np.cpp#L500-L504), which serves it with its `sceNpCommerceDialogOpen` handler: every argument is ignored, nothing is validated, it returns 0 and the dialog finishes at once, so `sceNpCommerceDialogGetResult` reports it cancelled. The parameters and the console's error results are unknown. PPSA34501 calls it at Zombies > Online +- [sceNpCommerceDialogInitializeInternal](../../../core/libs/prx/libSceNpCommerce/Export.cpp) (libSceNpCommerce) - its name and NID are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpCommerce.c); the no-parameter prototype follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_commerce/np_commerce.cpp#L61) and delegates to `sceNpCommerceDialogInitialize()`, sharing dialog status and returning `COMMON_DIALOG_ERROR_ALREADY_INITIALIZED` (0x80B80004) when already initialized. Any internal options, parameter structs, or console error returns remain unmeasured on hardware. +- [sceNpCommerceDialogOpen2](../../../core/libs/prx/libSceNpCommerce/Export.cpp): extended content combinations outside content mode 0 and content mode 1 with dialog mode 1 throw when otherwise valid. The native restriction reads process sysctl field 68; its relation to host process metadata is unresolved. Mode-specific meanings of the option and 64-bit argument remain unresolved. + +- [sceNpCommerceDialogOpen](../../../core/libs/prx/libSceNpCommerce/Export.cpp): Product copying and service submission remain part of the cancelled UI stub. diff --git a/docs/dev/debt/libSceNpCommon.md b/docs/dev/debt/libSceNpCommon.md index 2a8900021d..be7ac29c21 100644 --- a/docs/dev/debt/libSceNpCommon.md +++ b/docs/dev/debt/libSceNpCommon.md @@ -2,134 +2,27 @@ ### Unknown function info -The 150 declarations below use names mapped from the local `libSceNpCommon.sprx` export table. Each NID was verified against that table and recomputed from its name with `tools/nid_names.py`. The public [PS5 3.20 libSceNpCommon export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/main/libSceNpCommon.c) contains all 150 symbol names. No prototype or behavior is claimed. Each declaration uses the no-parameter unknown-signature convention solely for import resolution and throws if called. +The 34 declarations below use names mapped from the local `libSceNpCommon.sprx` export table. Each NID was verified against that table and recomputed from its name with `tools/nid_names.py`. The public [PS5 3.20 libSceNpCommon export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/main/libSceNpCommon.c) contains all 150 symbol names. No prototype or behavior is claimed. Each declaration uses the no-parameter unknown-signature convention solely for import resolution and throws if called. -- [sceNpAllocateKernelMemoryNoAlignment](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `MWPOkqzYss0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpAllocateKernelMemoryWithAlignment](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `gMlY6eewr-c`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpArchInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `jGF+MaB4b-M`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpArchTerm](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `UskWpVWxSvg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpAtomicCas32](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `+9+kKMY9YIw`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpAtomicDec32](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Yohe0MMDfj0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpAtomicInc32](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `pfJgSA4jO3M`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpBase64Decoder](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `l67qBmMmKP4`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpBase64Encoder](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `pu39pU8UgCo`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpBase64GetDecodeSize](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `a5IfPlpchXI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpBase64UrlDecoder](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `moGcgMNTHvQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpBase64UrlEncoder](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `IeNj+OcWgU8`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpBase64UrlGetDecodeSize](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `7BjZKcN+oZ4`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCalloutInitCtx](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `9+m5nRdJ-wQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCalloutStartOnCtx](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `fClnlkZmA6k`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCalloutStartOnCtx64](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `lpr66Gby8dQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCalloutStopOnCtx](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `in19gH7G040`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCalloutTermCtx](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `AqJ4xkWsV+I`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCancelEventFlag](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `kb2thTuS8t8`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpClearEventFlag](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `9pLoHoPMxeg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCloseEventFlag](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `+nmn+Z0nWDo`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpCloseSema](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `8hPzfjZzV88`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCmpNpIdInOrder](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `TcwEFnakiSc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCmpOnlineId](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `dj+O5aD2a0Q`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCondDestroy](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `1a+iY5YUJcI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCondInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `q2tsVO3lM4A`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCondSignal](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `uMJFOA62mVU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCondSignalAll](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `bsjWg59A7aE`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCondSignalTo](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `bAHIOyNnx5Y`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCondTimedwait](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `ss2xO9IJxKQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCondWait](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `fZShld2PQ7w`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCreateEventFlag](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `6jFWpAfqAcc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCreateSema](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `LHZtCT2W1Pw`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpCreateThread](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `fhJ5uKzcn0w`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpDbgAssignDebugId](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `90pmGqDK4BI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpDbgDumpBinary](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Etq15-l9yko`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpDbgDumpText](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `ZaKa5x61hGA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpDeleteEventFlag](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `sjnIeFCuTD0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpDeleteSema](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `xPrF2nGPBXQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpFreeKernelMemory](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `vjwlDmsGtME`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpGetNavSdkVersion](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `QmDEFikd3VA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGetPlatformType](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `sXVQUIGmk2U`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGetProcessId](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Z3mnqcGmf8E`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGetRandom](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `pJlGhXEt5CU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGetSdkVersion](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Pglk7zFj0DI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGetSdkVersionUInt](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `ljqnF0hmLjo`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGetSystemClockUsec](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `PVVsRmMkO1g`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGlobalHeapGetAllocator](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `-gN6uE+zWng`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGlobalHeapGetAllocatorEx](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `VUHUasztbUY`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGlobalHeapGetAllocatorExPtr](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `P4YpPziLBd4`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpGlobalHeapGetAllocatorPtr](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `DI5n4aOdxmk`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpHeapDestroy](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `wVdn78HKc30`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpHeapGetAllocator](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `lvek8w7yqyE`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpHeapGetStat](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `2jdHoPpS+W0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpHeapInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `B+yGIX1+BTI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpHeapShowStat](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `evz0-93ucJc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpHexToInt](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Hvpr+otU4bo`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpInt32ToStr](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `5y0wMPQkaeU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpInt64ToStr](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `HoPC33siDD4`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpIntGetPlatformType](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `G6qytFoBJ-w`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpIntIsOnlineIdString](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `fY4XQoA20i8`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpIntIsValidOnlineId](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `hkeX9iuCwlI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpIntSetPlatformType](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `X6emt+LbSEI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpIntToHex](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `TWPY1x1Atys`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpIpc2ClientInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `kgDwlmy78k0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpIpc2ClientTerm](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `CI2p6Viee9w`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpJoinThread](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `EjMsfO3GCIA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpJsonParse](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `vJGDnNh4I0g`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpJsonParseBuf](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `RgfCYkjW7As`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpJsonParseBufInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `SnAdybtBK3o`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpJsonParseEx](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `p5ZkSMRR7AU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpJsonParseExInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `nhgjiwPUIzI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpJsonParseInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `teVnFAL6GNY`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwCondDestroy](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `zNb6IxegrCE`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwCondInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `++eqYdzB8Go`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwCondSignal](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Xkn6VoN-wuQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwCondSignalAll](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `FJ4DCt8VzVE`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwCondSignalTo](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Bwi+EP8VQ+g`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwCondWait](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `ExeLuE3EQCQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwMutexDestroy](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `4zxevggtYrQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwMutexInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `1CiXI-MyEKs`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwMutexLock](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `18j+qk6dRwk`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwMutexTryLock](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `hp0kVgu5Fxw`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpLwMutexUnlock](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `CQG2oyx1-nM`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpMemoryHeapDestroy](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `dfXSH2Tsjkw`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpMemoryHeapGetAllocator](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `FaMNvjMA6to`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpMemoryHeapGetAllocatorEx](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `xHAiSVEEjSI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpMemoryHeapInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `kZizwrFvWZY`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpMutexDestroy](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `lQ11BpMM4LU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpMutexInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `uEwag-0YZPc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpMutexLock](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `r9Bet+s6fKc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpMutexTryLock](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `DuslmoqQ+nk`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpMutexUnlock](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `oZyb9ktuCpA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpOpenEventFlag](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `5DkyduAF2rs`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpOpenSema](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `-blITIdtUd0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpPanic](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `ZoXUrTiwKNw`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpPollEventFlag](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `9YmBJ8KF9eI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpPollSema](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `xmF0yIF4iXc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpRtcConvertToPosixTime](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `VMjIo2Z-aW0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpRtcFormatRFC3339](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `W0YWLVDndx0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpRtcParseRFC3339](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `LtkeQwMIEWY`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpServerErrorJsonGetErrorCode](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `0lZHbA-HRD0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpServerErrorJsonMultiGetErrorCode](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `cRabutqUG7c`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpServerErrorJsonParse](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `WSQxnAVLKgw`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpServerErrorJsonParseInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `UbStlMKTBeU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpServerErrorJsonParseMultiInit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `hbe+DdooIi4`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpSetEventFlag](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `29ftOGIrUCo`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpSetPlatformType](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `m9JzZSoDVFY`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpSignalSema](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `-W28+9p1CKI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpStrBuildHex](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `i5TP5NLmkoQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpStrParseHex](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `DUHzVPNlugg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpStrToInt32](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `fElyBSn-l24`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpStrToInt64](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `CwqYdG4TrjA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpStrToUInt32](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `uj86YxCYid0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpStrToUInt64](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Ted2YU9lv94`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpStrcpyToBuf](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `ivnnssCwjGI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpStrnParseHex](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `h1SWCcBdImo`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpStrncpyToBuf](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `PHrpHMSU8Cs`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpThreadGetId](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `yvaNTRiKXmo`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUInt32ToStr](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `rRN89jBArEM`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUInt64ToStr](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `QjNUYQbGoHA`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUserGetUserIdList](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Gh74vNl06sg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilBuildTitleId](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `N3tAHlBnowE`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilCanonicalizeNpIdForPs4](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `4mEAk-UKVNw`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilCanonicalizeNpIdForPsp2](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `N3FB4r8JoRE`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilCmpAccountId](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `xPRHNaD3kTc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetDateSetAuto](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `owm52JoZ8uc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetDbgCommerce](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `1Gfhi+tZ9IE`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetEnv](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `kBON3bAtfGs`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. @@ -137,20 +30,29 @@ The 150 declarations below use names mapped from the local `libSceNpCommon.sprx` - [sceNpUtilGetFakeRateLimit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `O86rgZ2azfg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetIgnoreNpTitleId](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `FrxliFYAO8Y`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetNpDebug](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `GRvK1ZE+FEQ`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilGetNpLanguageCode](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `OFiFmfsADas`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilGetNpLanguageCode2](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `X9CqyP164Hc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilGetNpLanguageCode2Str](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `Fxux7Ob+Ynk`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilGetNpLanguageCodeStr](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `RfiA17kV+xs`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetNpTestPatch](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `OA8f3KF9JsM`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilGetNthChar](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `KCk4OGu8+sc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetShareTitleCheck](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `fB5hE65pzbU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetSystemLanguage](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `SXUNKr9Zkv0`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetTrcNotify](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `AjzLvR0g5Zs`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetWebApi2FakeRateLimit](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `pmHBFJyju9E`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetWebApi2FakeRateLimitTarget](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `ZRxKp9vjcNc`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilGetWebTraceSetting](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `4CqfNm3pisU`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilHttpUrlEncode](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `ajoqGz0D9Dw`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. - [sceNpUtilJidToNpId](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `458yjI+OECI`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilJsonEscape](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `EftEB4kmkSg`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilJsonGetOneChar](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `vj04qzp7uKY`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. -- [sceNpUtilJsonUnescape](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) (NID `4YJ5gYtRAAE`) - the signature and behavior are unknown. The no-parameter declaration only resolves the import; it makes no ABI claim. Calls throw through `NotImplemented_nid_no_patch`. + +### Functional + +- [Online ID and NpId utilities](../../../core/libs/prx/libSceNpCommon/Export.cpp) (libSceNpCommon) - `sceNpCmpOnlineId`, `sceNpCmpNpIdInOrder`, `sceNpGetPlatformType` and `sceNpIntIsValidOnlineId` are not measured on a console; their signatures, checks and results follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_common.cpp), including the platform tags read from `opt[4..7]` and the 3 to 16 character online ID rule. +- [Mutex, condition and thread wrappers](../../../core/libs/prx/libSceNpCommon/Export.cpp) (libSceNpCommon) - the `sceNpMutex*`, `sceNpLwMutex*`, `sceNpCond*` and `sceNpJoinThread` wrappers forward to the libkernel `scePthread*` calls as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_common.cpp) and are not measured on a console. Only `EBUSY` from try-lock and `ETIMEDOUT` from a timed wait are mapped to NP errors; every other kernel error is returned unchanged. The `flags` argument of `sceNpCondInit` is ignored. +- [sceNpGetSdkVersion](../../../core/libs/prx/libSceNpCommon/Export.cpp) (libSceNpCommon) - writes the libkernel system software version as `major.minor` into an 8-byte buffer without checking it, as shadPS4 does; that version is the fixed `1.00`, not the SDK version a title was built with. +- [The exports in Runtime.cpp](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) and [Strings.cpp](../../../core/libs/prx/libSceNpCommon/Strings.cpp) follow `libSceNpCommon.sprx` from firmware 13.60, read from disassembly and not measured on a console. +- [sceNpCreateThread](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) always applies an explicit priority when one is given; the firmware picks between explicit and inherited scheduling from the title's compiled SDK version, which is not available here. +- [sceNpCalloutStartOnCtx and sceNpCalloutStartOnCtx64](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) look for the callout in the queue before writing it and return `0x80558006` with the entry and the queue unchanged. Firmware 13.60, read from disassembly and not measured on a console, clears and fills the entry before it takes the lock and checks the queue, so restarting a queued callout returns the same error but drops every callout queued after it. +- [sceNpArchInit and sceNpArchTerm](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) keep a reference count under a lock and create the global heap once. Firmware 13.60, read from disassembly and not measured on a console, uses no lock and never sets its count on the first call, so every call made while the count is zero creates the heap again, and a call to sceNpArchTerm at zero destroys the heap and leaves the count negative. +- [sceNpGetRandom](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) draws from a 32-bit Mersenne Twister seeded once per process; the console uses SFMT-19937 with a seed set inside the module, so the sequences differ. +- [sceNpHeapShowStat](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) and [sceNpDbgDumpBinary](../../../core/libs/prx/libSceNpCommon/Runtime.cpp) print the same information as the console in a different layout. +- [The string-to-integer exports](../../../core/libs/prx/libSceNpCommon/Strings.cpp) clear `errno` before parsing; the firmware reads whatever `errno` held before the call when the result saturates. +- [The integer-to-string exports](../../../core/libs/prx/libSceNpCommon/Strings.cpp) return `0x80558008` when the hexadecimal prefix does not fit; the firmware writes the prefix without checking the size. [The base64 decoders](../../../core/libs/prx/libSceNpCommon/Strings.cpp) treat bytes above 0x7F as invalid; the firmware indexes its table with them as signed values. [sceNpUtilJsonGetOneChar](../../../core/libs/prx/libSceNpCommon/Strings.cpp) checks that a `\u` escape and a surrogate pair fit in the input; the firmware reads past it. +- [sceNpUtilGetNpDebug, sceNpUtilGetNpTestPatch, sceNpUtilGetTrcNotify, sceNpUtilGetIgnoreNpTitleId, sceNpUtilGetFakeDisplayNameMode, sceNpUtilGetWebTraceSetting, sceNpUtilGetWebApi2FakeRateLimit, sceNpUtilGetWebApi2FakeRateLimitTarget, sceNpUtilGetFakeRateLimit, sceNpUtilGetDbgCommerce, sceNpUtilGetSystemLanguage, sceNpUtilGetEnv, sceNpUtilGetDateSetAuto and sceNpUtilGetShareTitleCheck](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) read system and debug registry keys through libSceRegMgr, whose `sceRegMgrNonSysGetInt` and string reads still throw; they throw until those exist. +- [sceNpOpenSema and sceNpCloseSema](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) forward to the libkernel open and close calls for named semaphores, which still throw. +- [sceNpSetPlatformType, sceNpIntGetPlatformType, sceNpIntSetPlatformType, sceNpUtilCanonicalizeNpIdForPs4, sceNpUtilCanonicalizeNpIdForPsp2, sceNpUtilJidToNpId and sceNpGetNavSdkVersion](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) depend on console platform tags, network domain names or a version string of the console; they throw. +- [sceNpJsonParse, sceNpJsonParseInit, sceNpJsonParseBuf, sceNpJsonParseBufInit, sceNpJsonParseEx, sceNpJsonParseExInit and the sceNpServerErrorJson exports](../../../core/libs/prx/libSceNpCommon/PendingExports.cpp) are a streaming, schema-checked JSON parser whose event and error rules are not yet specified; they throw. diff --git a/docs/dev/debt/libSceNpCppWebApi.md b/docs/dev/debt/libSceNpCppWebApi.md index be7f650ac6..ee3994c483 100644 --- a/docs/dev/debt/libSceNpCppWebApi.md +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -2,16 +2,19 @@ ### Silent stubs -- [sce::Np::CppWebApi::Common](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the `finish` overrides of `TransactionBase<...>` (8 instantiations), `DownStreamTransaction<...>` (2 instantiations) and `Vector>::pushBack` return success without doing work; a failed request never produces a transaction to finish +- [sce::Np::CppWebApi::Common](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the `finish` overrides of `TransactionBase<...>` (13 instantiations), `DownStreamTransaction<...>` (2 instantiations) and `Vector>::pushBack` return success without doing work; a failed request never produces a transaction to finish. `Transaction<...>::setOnFinishedCallback` and `TransactionBase<...>::setResponseInformationOption` (5 instantiations each, the `UserProfile::V1`, `ProfanityFilter::V2` and `CommunicationRestrictionStatus::V3` responses) return success without storing the callback or the option: `start` fails, so neither is ever used. The constructors and destructors of the same five `Transaction<...>` instantiations succeed without keeping state - [sce::Np::CppWebApi::Leaderboards::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - request-body and parameter bookkeeping succeeds without storing state: `GetRankingRequestBody::set{StartSerialRank,Group,Limit,Users,Offset}`, `RecordScoreRequestBody::set{Comment,SmallData,NeedsTmpRank,ComparedDateTime,PcId}`, `User::{setAccountId,setPcId}`, `BoardsApi::ParameterToGetBoardDefinition::{initialize,terminate}`, `RecordApi::ParameterToRecordLargeData::{initialize,terminate}`, `RecordApi::ParameterToRecordScore::{initialize,setxPsnAtomicOperation,terminate}`, `ViewApi::ParameterToGetLargeDataByObjectId::{initialize,terminate}`, `ViewApi::ParameterToGetRanking::{initialize,setgetRankingRequestBody,terminate}` - [sce::Np::CppWebApi::SessionManager::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - parameter bookkeeping succeeds without storing state: `PlayerSessionsApi::ParameterToCreatePlayerSessions::{ctor,dtor,initialize}`, `PlayerSessionsApi::ParameterToGetPlayerSessionInvitations::{ctor,dtor,initialize,setfields,setinvitationInvalidFilter}`, `PlayerSessionsApi::ParameterToJoinPlayerSessionAsPlayer::{ctor,dtor,initialize,terminate}`, `PlayerSessionsApi::ParameterToLeavePlayerSession::{ctor,dtor,initialize}`, `RequestPlayerSession::set{MaxSpectators,InvitableUserType,JoinableUserType,ExclusiveLeaderPrivileges}`, `RequestPlayerSessionPlayer::setPushContexts` - [sce::Np::CppWebApi::TitleCloudStorage::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `DataApi::ParameterToDownloadData::{initialize,terminate}` succeed without storing state +- [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - parameter bookkeeping succeeds without storing state: `FriendsApi::ParameterToGetFriends`, `PresenceApi::ParameterToGetBasicPresences`, `BlocksApi::ParameterToGetBlockingUsers`, `ProfanityApi::ParameterToFilterProfanity` and `CommunicationRestrictionStatusApi::ParameterToGetCommunicationRestrictionStatus`, each `{ctor,dtor,initialize,terminate}` ### Unknown function info - [sce::Np::CppWebApi](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - every request entry point (API calls, transaction `start`/`getResponse`/`readData`, the `*Factory::create`s) returns `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN` offline. The constant's name and value follow the reference tables in shadPS4 `np_error.h` and fpPS4 `np_error.pas`, where `0x80552907` is `ORBIS_NP_WEBAPI_ERROR_NOT_SIGNED_IN` and `0x80552901` is `OUT_OF_MEMORY` (no `UNAVAILABLE` exists in the `0x80552901`-`0x8055291F` range); it is the code a signed-out console returns from these entry points. The earlier `0x80552901` value came from the module the previous implementation shipped, which used the same incorrect constant - [sce::Np::CppWebApi::Common::IntrusivePtr](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented - [sce::Np::CppWebApi::Common::String](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. `append` grows the buffer with the host allocator and the destructor frees it, instead of going through the `LibContext` allocator. `pushBack` into `Vector` stores a deep copy, so a destroyed temporary cannot invalidate the stored element; a title that byte-copies a `String` with a live buffer would still double-free, as nothing increments the reference count -- [sce::Np::CppWebApi::Common::Vector](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671, whose dump ships no `libSceNpCppWebApi.prx`. A `Vector` handle is an `IntrusivePtr` over a shared body; bodies created by `pushBack` use a host-side layout with the reference count at 0x30, matching the adopted-body convention measured on PPSA21564. The pointer/deleter constructor adopts a title-provided body released through the stored deleter; `pushBack` on such a body throws, since its element layout is unknown. `begin`, `end`, iterators and `operator[]` throw: the only `Vector`s the title reads back come from failed transactions, which never produce one +- [sce::Np::CppWebApi::Common::Vector](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671, whose dump ships no `libSceNpCppWebApi.prx`. A `Vector` handle is an `IntrusivePtr` over a shared body; bodies created by `pushBack` use a host-side layout with the reference count at 0x30, matching the adopted-body convention measured on PPSA21564. The pointer/deleter constructor adopts a title-provided body released through the stored deleter; `pushBack` on such a body throws, since its element layout is unknown. `begin`, `end`, iterators and `copyFrom` throw: the only `Vector`s the title reads back come from failed transactions, which never produce one. `empty`, `size` and `operator[]` read the host-created body honestly and throw on title-provided bodies - [sce::Np::CppWebApi::SessionManager2V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671. Offline, the factories, `PlayerSessionsApi` calls and transaction `start`/`getResponse`/`readData` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter setters, `initialize`, `terminate`, constructors and destructors succeed without keeping state, as no request is ever sent. Response-body accessors (`getSessionId`, `getPlayerSessions`, `getInvitations`, the `IsSet` queries) throw: a failed request never produces a response object +- [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by an Epic Online Services module whose dump ships no `libSceNpCppWebApi.prx`. The reference-count offset 0x00 of `ResponseHeaderBase` and `WebApiFilterRequest` is assumed from the other objects, not measured. Offline, the five API calls, `WebApiFilterRequestFactory::create` and the transactions' `start`/`getResponseHeaders` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. Response accessors, `ResponseHeaderBase::getHeaderValue` and the `Vector`/`Iterator` members over `IntrusivePtr` and `unsigned long` throw: a failed request never produces a response - [libSceNpCppWebApi](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - unimplemented stubs have their parameter lists, const qualifiers and return types aligned with the demangled symbols confirmed by NID matching in [#8143](https://github.com/boykopovar/AnyPS5/issues/8143). They cover response-body accessors, iterators, and uninstantiated transactions in Leaderboards::V1, SessionManager::V1, InGameCatalog::V5, TitleCloudStorage::V1, and Common templates: each mangled name gives the method and parameter types, but the runtime layout of response objects and errors are unverified on console, so each call continues to throw NotImplemented_nid_no_patch. No call site is recorded for them +- [sce::Np::CppWebApi::SessionManager2V1 and sce::Np::CppWebApi::InGameCatalog2V5](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - 198 further exports imported by PPSA20397, named from aerolib. API calls, factories and transaction `start`/`getResponse*` fail `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter bookkeeping succeeds without state; `IntrusivePtr`/`Vector` helpers follow the layouts above, with `Vector::empty`/`size`/`operator[]` reading host-created bodies and throwing on title-provided ones. Response accessors, iterators, `Vector::begin`/`end`/`copyFrom` and transaction `abort`/`getId`/`operator=` throw: a failed request produces no response object, and aborting or renumbering a transaction that never ran has no defined outcome diff --git a/docs/dev/debt/libSceNpEntitlementAccess.md b/docs/dev/debt/libSceNpEntitlementAccess.md index 2a9d2f3089..7d54ea4fe1 100644 --- a/docs/dev/debt/libSceNpEntitlementAccess.md +++ b/docs/dev/debt/libSceNpEntitlementAccess.md @@ -4,7 +4,7 @@ - [sceNpEntitlementAccessGetAddcontEntitlementInfoIndividual, sceNpEntitlementAccessPollConsumableEntitlementInfo, sceNpEntitlementAccessRequestConsumableEntitlementInfo, sceNpEntitlementAccessRequestConsumeEntitlement](../../../core/libs/prx/libSceNpEntitlementAccess/Export.cpp) (libSceNpEntitlementAccess) - their names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpEntitlementAccess.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim - [sceNpEntitlementAccessGetEntitlementKey](../../../core/libs/prx/libSceNpEntitlementAccess/Export.cpp) - the three-argument signature, 16-byte key and errors `0x817D0002`/`0x817D0007` follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libNet.cpp); PPSA01521 passes a service label, entitlement label and output pointer. Only the no-key path is implemented: local entitlement labels carry no keys, and the output is untouched on failure. Console error precedence and key provisioning are unverified -- [sceNpEntitlementAccess{Request,Poll}{Unified,Service}EntitlementInfo{,List}](../../../core/libs/prx/libSceNpEntitlementAccess/Export.cpp) - signatures unknown; no reference implements them, so they are declared without parameters. The single-item variants copy the behaviour of the list ones, with no source of their own. Like the consume requests in the same library, they report the signed-out NP state with `SCE_NP_ERROR_SIGNED_OUT` (`0x80550006`) and write nothing. PPSA15657 calls the unified request during start-up and continues past the error. Whether the console fails at the request or at the poll when signed out is unverified +- [Entitlement requests and polls](../../../core/libs/prx/libSceNpEntitlementAccess/Export.cpp): native argument lists are restored for single-item, list and consume operations, plus abort/delete. Request IDs are 64-bit, labels occupy 20 bytes and transaction IDs occupy 72 bytes. List options occupy 32 bytes and results have a 64-byte stride; their field semantics and three list-result scalars remain unresolved. The existing signed-out responses do not reproduce native initialization, validation or asynchronous request handling; abort/delete throw until request tracking is implemented. ### Functional diff --git a/docs/dev/debt/libSceNpFriendListDialog.md b/docs/dev/debt/libSceNpFriendListDialog.md index 1c3824ba59..2d5b73e230 100644 --- a/docs/dev/debt/libSceNpFriendListDialog.md +++ b/docs/dev/debt/libSceNpFriendListDialog.md @@ -1,5 +1,9 @@ # libSceNpFriendListDialog technical debt +### Silent stubs + +- [libSceNpFriendListDialog](../../../core/libs/prx/libSceNpFriendListDialog/Export.cpp) - the friend list dialog (`sceNpFriendListDialogOpenA` and `sceNpFriendListDialogGetResultA`) runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as there is no PSN friend list. The signatures are from [Orbital's type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json); the result layout beyond the leading `int32_t` result code is not known, so nothing else is written. + ### Unknown function info -- [sceNpFriendListDialogClose, sceNpFriendListDialogGetResult, sceNpFriendListDialogGetResultA, sceNpFriendListDialogGetStatus, sceNpFriendListDialogInitialize, sceNpFriendListDialogOpen, sceNpFriendListDialogOpenA, sceNpFriendListDialogTerminate and sceNpFriendListDialogUpdateStatus](../../../core/libs/prx/libSceNpFriendListDialog/Export.cpp) (libSceNpFriendListDialog) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpFriendListDialog.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceNpFriendListDialogGetResult and sceNpFriendListDialogOpen](../../../core/libs/prx/libSceNpFriendListDialog/Export.cpp) (libSceNpFriendListDialog) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpFriendListDialog.c), but Orbital has prototypes only for the `A` variants and no credible prototypes were found for these. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceNpGameIntent.md b/docs/dev/debt/libSceNpGameIntent.md index 622b40a2c2..cc0daa50c5 100644 --- a/docs/dev/debt/libSceNpGameIntent.md +++ b/docs/dev/debt/libSceNpGameIntent.md @@ -2,11 +2,12 @@ ### Silent stubs -- [sceNpGameIntentVshInitialize, sceNpGameIntentVshTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - VSH initialization and termination return 0 without performing shell operations, matching `sceNpGameIntentInitialize` and `sceNpGameIntentTerminate` -- [sceNpAppLauncherInitialize, sceNpAppLauncherTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - app-launcher lifecycle returns success without tracking launch state; `sceNpAppLauncherInitialize` accepts its init parameter without validation, following the silent-stub precedent of `sceNpGameIntentInitialize` — a title calls it early in boot, and returning success lets it continue +- [sceNpGameIntentVshInitialize, sceNpGameIntentVshTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - VSH initialization and termination return 0 without performing shell operations +- [sceNpAppLauncherInitialize, sceNpAppLauncherTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - app-launcher lifecycle returns success without tracking launch state; `sceNpAppLauncherInitialize` accepts its init parameter without validation — a title calls it early in boot, and returning success lets it continue ### Unknown function info +- [sceNpGameIntentReceiveIntent, sceNpGameIntentGetPropertyValueString](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - firmware 13.42 traces report `0x80553802` before `sceNpGameIntentInitialize` (precedence over argument validity), and `0x80553804` for invalid arguments (null pointer, empty key, or zero buffer size); `sceNpGameIntentTerminate` resetting initialized state is modeled lifecycle behavior rather than hardware-verified. `sceNpGameIntentReceiveIntent` reportedly requires `info->size == 0x4140` on firmware 13.42, while AnyPS5 `NpGameIntentInfo` is `0x560` bytes (matching fpPS4) and set by `sceNpGameIntentInfoInit`, so initialized objects are rejected by the receiver's measured size guard. Complete retail layout, output field offsets, and retail behavior of `sceNpGameIntentInfoInit` are unverified; expanding the type with unverified reserved bytes is not an evidence-backed solution. - [sceNpAppLauncherLaunchApp, sceNpGameIntentGetUdsNpComId, sceNpGameIntentLaunchApp, sceNpGameIntentLaunchApp2, sceNpGameIntentNotifyAppLaunched, sceNpSessionServiceGetJoinedPartySession, sceNpSessionServiceGetRunningGamePlayerSession and sceNpSessionServiceSendPlayerSessionInvitations](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpGameIntent.c). Call sites in a title's executable give a partial prototype: `sceNpAppLauncherLaunchApp` takes `(user_id, uri struct, context)` and returns int — throws because no launcher-service semantics are derivable. The rest are declared without parameters solely to resolve known imports and make no ABI claim. - [sceNpGameIntentInitParamInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L17) `SceNpGameIntentInitParam`; setting `size`, zeroing reserved bytes, and throwing on null pointer follow Sony SDK `*ParamInit` conventions but are unverified on retail hardware -- [sceNpGameIntentInfoInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L28) `SceNpGameIntentInfo`; setting `size`, `user_id = -1`, zeroing fields, and throwing on null pointer match `sceNpGameIntentReceiveIntent` defaults but are unverified on retail hardware +- [sceNpGameIntentInfoInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L28) `SceNpGameIntentInfo`; setting `size = 0x560`, `user_id = -1`, zeroing fields, and throwing on null pointer follow fpPS4 defaults, but reported firmware 13.42 behavior requires `0x4140` for `sceNpGameIntentReceiveIntent`, so the helper's `0x560` size does not match retail receiver defaults; the complete retail structure layout and retail behavior of `sceNpGameIntentInfoInit` remain unverified diff --git a/docs/dev/debt/libSceNpManager.md b/docs/dev/debt/libSceNpManager.md index 9cf1c6accd..88f66d3920 100644 --- a/docs/dev/debt/libSceNpManager.md +++ b/docs/dev/debt/libSceNpManager.md @@ -6,11 +6,11 @@ ### Unknown function info -- [libSceNpManager pending exports](../../../core/libs/prx/libSceNpManager/Export.cpp) - 150 named callable exports are present in the local `libSceNpManager.sprx` export table and the public PS5 3.20 symbol list. Their signatures and behavior are unverified; each declaration uses the no-parameter unknown-signature convention and throws through `NotImplemented_nid_no_patch`. +- [libSceNpManager pending exports](../../../core/libs/prx/libSceNpManager/Export.cpp) - 143 named callable exports are present in the local `libSceNpManager.sprx` export table and the public PS5 3.20 symbol list. Their signatures and behavior are unverified; each declaration uses the no-parameter unknown-signature convention and throws through `NotImplemented_nid_no_patch`. - [sceNpGetSandboxInfo](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager, NID `ZVZ++68nNI8`) - the name hashes to the imported NID (found with nidhunt, confirmed with `tools/nid_names.py`). STAR WARS: Galactic Racer (PPSA28416) imports it; its OnlineSubsystemPS5 calls it with one pointer to a 0x50-byte stack buffer and reads the buffer as a string (discussions/3086). The returned string, the exact parameter struct and console errors are unknown, so every call throws without reading or writing caller memory. A working implementation belongs with that title's bring-up. -- [sceNpUnknown_8weiQyIdI3A](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager, NID `8weiQyIdI3A`) - the name is not in aerolib. EA SPORTS FC 27 (PPSA34015) fails to load `libSceNpManager.prx` without it (`unresolved ELF import` / `GetLastError: 127`). Its only call is `mov edi, r14d` (a user id) and `lea rsi, [rbp-0x40]` after a 17-byte copy into that buffer (`mov edx, 0x11`); `test eax, eax` follows. The C name, the type of the 17-byte object and the console return are unknown, so every call throws without reading or writing caller memory. -- [sceNpUnknown_WxLcN76q19o](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager, NID `WxLcN76q19o`) - the name is not in aerolib. PPSA34015 imports it from `libSceNpManager.prx` as a strong `JUMP_SLOT` with no call found in the eboot; the loader still requires the export. Arguments, result and console errors are unknown, so every call throws. +- [sceNpUnknown_8weiQyIdI3A](../../../core/libs/prx/libSceNpManager/Export.cpp): the original name remains unknown. The native wrapper takes a user ID and a read-only 20-byte online-ID record, validates its first byte and sends the record through IPC. The implementation remains a throwing stub. +- [sceNpUnknown_WxLcN76q19o](../../../core/libs/prx/libSceNpManager/Export.cpp): the original name remains unknown. The native wrapper has no input arguments and returns a 32-bit status from an IPC request after initialization checks. The implementation remains a throwing stub. - [sceNpSetTimeout](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the signature follows [orbital](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json), [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/np/np_manager.cpp) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Np/NpAccount.cs); the rejected values (all five 0, negative retries, a resolve timeout under 1 s or another timeout under 10 s, 0 meaning the default) are shadPS4's, not checked on a console. An id that was never created or was deleted returns `REQUEST_NOT_FOUND`, as in shadPS4; there is no network, so the timeouts are not stored - [sceNpGetUserIdByAccountId](../../../core/libs/prx/libSceNpManager/Export.cpp) - the `(account id, user id output)` signature and its errors follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/np/np_manager.cpp): account id 0 or a null output returns `SCE_NP_ERROR_INVALID_ARGUMENT`, and a signed-out NP returns `SCE_NP_ERROR_SIGNED_OUT` without writing the output. NP is always signed out here, so no account id resolves to a user - [sceNpNotifyPlusFeature](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - PS4 titles import it; its parameter block and what the console does with it for a signed-out user are unknown ([shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp#L1170) returns 0 without reading it), so every call throws. @@ -18,12 +18,20 @@ ### Functional - [sceNpGetAccountCountry, sceNpGetAccountDateOfBirth, sceNpGetAccountLanguage, sceNpGetParentalControlInfo](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - PS4 forms taking an online id, not measured on a console. Signatures and checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp): a null pointer is `INVALID_ARGUMENT`; no user is signed in, so no online id matches one and the rest return `USER_NOT_FOUND` without writing, like `sceNpCheckNpAvailability`. Request ids are not checked +- [sceNpCheckNpAvailabilityA, sceNpGetAccountDateOfBirthA, sceNpGetAccountLanguageA, sceNpGetGamePresenceStatus, sceNpGetGamePresenceStatusA, sceNpGetParentalControlInfoA](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - answer as if no PSN account is signed in; the signed-in behaviour is not implemented and none of it is measured on a console. Checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c2814041e546bf75c12756dbec9da911c721ce1c/src/core/libraries/np/np_manager.cpp): a null pointer or user id -1 is `INVALID_ARGUMENT`, then request id 0 is `INVALID_ARGUMENT` and an unknown one `REQUEST_NOT_FOUND`. Other users get `SIGNED_OUT` with nothing written, as from `sceNpGetAccountCountryA`; shadPS4's `sceNpGetAccountDateOfBirthA` returns `USER_NOT_FOUND` for a user it does not know. An async request gets `SIGNED_OUT` from the call; shadPS4 returns 0 and leaves it to `sceNpPollAsync`. The presence queries write `OFFLINE` (0) and return 0 - [sceNpGetUserIdByOnlineId](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the `(online id, user id output)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp#L703): a null online id or output returns `SCE_NP_ERROR_INVALID_ARGUMENT`, and a signed-out NP returns `SCE_NP_ERROR_SIGNED_OUT` without writing the output, as `sceNpGetUserIdByAccountId` does - [sceNpCheckPlus](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the `(request id, parameter, result)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp#L246): request id 0 or a null parameter or result returns `SCE_NP_ERROR_INVALID_ARGUMENT`, and every other call returns `SCE_NP_ERROR_SIGNED_OUT` without writing the result, as `sceNpCheckPremium` does. The parameter's size and feature fields, which shadPS4 checks, are not read: the errors the console returns for them are not known - [sceNpGetAccountId, sceNpSetGamePresenceOnline](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - PS4 forms; the `(online id, account id output)` and `(request id, online id, presence)` signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp#L1092-L1096). A null online id or output, or request id 0 for `sceNpSetGamePresenceOnline`, returns `SCE_NP_ERROR_INVALID_ARGUMENT`. `sceNpSetGamePresenceOnline` then returns `SCE_NP_ERROR_REQUEST_NOT_FOUND` for a request that was never created or was deleted, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c1b26558352b863f80142256e4df38585aa7d727/src/core/libraries/np/np_manager.cpp#L1098-L1104) does. Otherwise both return `SCE_NP_ERROR_SIGNED_OUT` without writing, as `sceNpGetAccountIdA` does. The console's result for a signed-out user is not measured - [NP reachability callbacks](../../../core/libs/prx/libSceNpManager/Export.cpp) retain one callback and its user data; duplicate registration and unregistering an absent callback return the errors used by [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/libraries/np/np_manager.cpp). No callback is delivered because the existing NP model remains signed out with reachability unavailable. -- [sceNpCheckNpAvailability](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the `(req_id, online_id)` signature and the errors follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/np/np_manager.cpp) with no user signed in to PSN: a null online id is `SCE_NP_ERROR_INVALID_ARGUMENT`, and any other online id matches no user, `SCE_NP_ERROR_USER_NOT_FOUND`; the request id is not checked, as in the other NpManager requests -- [NP state callbacks](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the registration rules are [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L857-L901)'s: `sceNpRegisterStateCallbackA` keeps up to 8 callbacks and returns the slot plus 1, a null callback returns `INVALID_ARGUMENT`, a callback already registered `CALLBACK_ALREADY_REGISTERED` and a ninth one `CALLBACK_MAX` (0x8055001D, [np_error.h](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_error.h#L38)); `sceNpUnregisterStateCallbackA` returns `INVALID_ARGUMENT` for an id outside 1..8 and `CALLBACK_NOT_REGISTERED` for an empty slot; the legacy [sceNpRegisterStateCallback](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L981-L1005) keeps one callback with the same errors. Each registration gets `SIGNED_OUT` for the initial user (0x10000000) once, from the next `sceNpCheckCallback` on the calling thread. For the A form this follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L3959-L3974), whose [sceNpRegisterStateCallbackA](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L4021-L4034) marks the registration pending. KytyPS5 never calls the legacy callback ([sceNpRegisterStateCallback](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L4009-L4019) stores nothing); it is called as `(user, SIGNED_OUT, nullptr, userdata)`, as in shadPS4, which passes an NpId only for `SIGNED_IN` ([L823](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L823), [L938-L940](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L938-L940)). shadPS4 instead queues `SIGNED_OUT` from the user service login event and delivers it from `sceNpCheckCallback` ([DispatchPendingNpStateCallbacks](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L903-L962)); the reachability callback is not given an initial `UNAVAILABLE`, unlike that function ([L922-L961](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L922-L961)). A callback being delivered when another thread unregisters it is still called once. When and how often the console calls the state callbacks, and the errors, are not measured +- [sceNpCheckNpAvailability](../../../core/libs/prx/libSceNpManager/Export.cpp): native NP initialization and online-ID lookup are not modeled. The offline host returns user-not-found for valid arguments after clearing the optional one-byte availability output; request ID 0 and a null online-ID pointer return invalid-argument before writing the output. +- [NP state callbacks](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the registration rules are [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L857-L901)'s: `sceNpRegisterStateCallbackA` keeps up to 8 callbacks and returns the slot plus 1, a null callback returns `INVALID_ARGUMENT`, a callback already registered `CALLBACK_ALREADY_REGISTERED` and a ninth one `CALLBACK_MAX` (0x8055001D, [np_error.h](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_error.h#L38)); the unregister errors are in the next entry; the legacy [sceNpRegisterStateCallback](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L981-L1005) keeps one callback with the same errors. Each registration gets `SIGNED_OUT` for the initial user (0x10000000) once, from the next `sceNpCheckCallback` on the calling thread. For the A form this follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L3959-L3974), whose [sceNpRegisterStateCallbackA](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L4021-L4034) marks the registration pending. KytyPS5 never calls the legacy callback ([sceNpRegisterStateCallback](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L4009-L4019) stores nothing); it is called as `(user, SIGNED_OUT, nullptr, userdata)`, as in shadPS4, which passes an NpId only for `SIGNED_IN` ([L823](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L823), [L938-L940](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L938-L940)). shadPS4 instead queues `SIGNED_OUT` from the user service login event and delivers it from `sceNpCheckCallback` ([DispatchPendingNpStateCallbacks](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L903-L962)); the reachability callback is not given an initial `UNAVAILABLE`, unlike that function ([L922-L961](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L922-L961)). A callback being delivered when another thread unregisters it is still called once. When and how often the console calls the state callbacks, and the errors, are not measured +- [sceNpRegisterStateCallbackA and sceNpUnregisterStateCallbackA](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - never return the module's `0x80550002` for a failed [readiness check](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/np/state_callbacks.cpp#L152-L153), whose input is not known, and are not measured on a PS5. An unregister id outside 1..8, compared unsigned so 0 and negative ids are included, returns `INVALID_ID` (0x80550015) and an empty slot `CALLBACK_NOT_REGISTERED`, as [prosper](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/np/state_callbacks.cpp#L176-L188) reads `libSceNpManager.sprx` at RVA 0x15b10, without recording its firmware version; the same reading gives the registration rules above ([L162-L175](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/np/state_callbacks.cpp#L162-L175)) - [sceNpRegisterGamePresenceCallback](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - accepts any callback, null included, and returns nothing; no game presence event is delivered because the NP model remains signed out. [Kyty](https://github.com/InoriRus/Kyty/blob/4733b7e1c91b10554a52007903d74dc76c39a230/source/emulator/src/Network.cpp#L1549-L1552) ignores the callback the same way, and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/8d50471b7dc141dd700b51276b8edd1f293bce26/src/core/libraries/np/np_manager.cpp#L1121-L1126) keeps the last one without calling it. Not measured on a PS5 +- [sceNpIsPlusMember and sceNpSetContentRestriction](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the signatures and checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_manager.cpp): a null output or restriction is `INVALID_ARGUMENT`, a restriction size other than 0x18 is `INVALID_SIZE` (0x80550011), and a negative default age, more than 256 age restrictions or a count without an array is `INVALID_ARGUMENT`. Any user is reported as not a Plus member, and a valid restriction is accepted and not stored. Not measured on a PS5 -- The following pending declarations have unverified signatures and behavior; they are parameterless import-resolution declarations and throw through `NotImplemented_nid_no_patch`: [`sceNpCheckCallbackForLib`](../../../core/libs/prx/libSceNpManager/Export.cpp). +- [sceNpCheckCallbackForLib](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - not measured on a PS5: it delivers the pending state callbacks like `sceNpCheckCallback`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_manager.cpp) does with its pending state events; shadPS4's `sceNpCheckCallback` also runs other registered NP callbacks, which this library does not keep + +- [sceNpAbortRequest](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - a request id that was never created or was deleted returns `SCE_NP_ERROR_REQUEST_NOT_FOUND` and a live request returns 0, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp) does in `sceNpAbortRequest`; shadPS4 there also moves the request into an `Aborted` state that its later requests read as `ABORTED`, and whether aborting on the console changes a live request's completion is not measured on a PS5 ([#9277](https://github.com/boykopovar/AnyPS5/issues/9277)), so nothing in this library reads an abort state and the id stays live +- [sceNpDeleteRequest](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - a request id that was never created or was deleted returns `SCE_NP_ERROR_REQUEST_NOT_FOUND`, and a live request is removed and returns 0, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp) does in `sceNpDeleteRequest` ([#9277](https://github.com/boykopovar/AnyPS5/issues/9277)) +- [sceNpPollAsync](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - a null result pointer returns `SCE_NP_ERROR_INVALID_ARGUMENT` before the id is read, a request id that was never created or was deleted returns `SCE_NP_ERROR_REQUEST_NOT_FOUND` without writing the caller's result, and a live request keeps its signed-out completion `0` with `SCE_NP_ERROR_SIGNED_OUT`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp) does in `sceNpPollAsync` ([#9277](https://github.com/boykopovar/AnyPS5/issues/9277)). The parts of that model beyond the validation are not implemented: shadPS4 keeps a per-request state machine decorated by `sceNpCheckNpAvailability` and neighbors and refuses `InvalidId` for a ready async request in wait/poll, whose completion publication, poll-versus-wait precedence and already-complete errors remain unattributable to the PS5 +- [sceNpCreateAsyncRequest](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the parameter is not read ([NpCreateAsyncRequestParameter](../../../core/libs/SceTypes.hpp) is a 0x40-byte opaque blob, so shadPS4's nonnull check and `size == sizeof` reject ([81be949167d0060b08f26dc3e055a0b640d129c8](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp)) cannot be applied without inventing a layout) and no async kind or completion state is stored until the console's contract for them is evidenced ([#9277](https://github.com/boykopovar/AnyPS5/issues/9277)); `sceNpWaitAsync` (`jyi5p9XWUSs`) has no export here yet and stays with [PR #9282](https://github.com/boykopovar/AnyPS5/pull/9282) diff --git a/docs/dev/debt/libSceNpMatching2.md b/docs/dev/debt/libSceNpMatching2.md new file mode 100644 index 0000000000..edbc279625 --- /dev/null +++ b/docs/dev/debt/libSceNpMatching2.md @@ -0,0 +1,6 @@ +# libSceNpMatching2 technical debt + +### Functional + +- [sceNpMatching2Initialize, sceNpMatching2Terminate](../../../core/libs/prx/libSceNpMatching2/Export.cpp) (libSceNpMatching2) - only the library state is kept: initialization checks the parameter's `size` field (0x28, or 0x30 with an SSL pool) as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_matching2/np_matching2.cpp) does and starts no thread; a second initialization returns `ALREADY_INITIALIZED` and a termination without one `NOT_INITIALIZED`. The pool, thread and SSL settings are not used: no matching server is reached. Not measured on a console +- [sceNpMatching2CreateContext and the context calls](../../../core/libs/prx/libSceNpMatching2/Export.cpp) (libSceNpMatching2) - offline only, not measured on a PS5: the checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_matching2/np_matching2.cpp#L26-L349) and its [context manager](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_matching2/np_matching2_internal.cpp#L1348-L1457) (a `0x28`-byte parameter, ten ids handed out round-robin). `sceNpMatching2ContextStart` returns `0x804101E2` (`SCE_NET_ERROR_RESOLVER_ETIMEDOUT`), which shadPS4 says was confirmed on a console disconnected from the internet, so no context starts and `sceNpMatching2ContextStop` answers `CONTEXT_NOT_STARTED`. Callbacks and the default request option are checked but never used. The room, user, world, server-id and signaling-info calls still throw diff --git a/docs/dev/debt/libSceNpParty.md b/docs/dev/debt/libSceNpParty.md index 45d68abf16..8d6176415e 100644 --- a/docs/dev/debt/libSceNpParty.md +++ b/docs/dev/debt/libSceNpParty.md @@ -2,4 +2,8 @@ ### Unknown function info -- [sceNpPartyGetState](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - the 16-bit state, its `NOT_IN_PARTY` value 2 and the `INVALID_ARGUMENT` (`0x80552504`) error follow shadPS4's [np_party.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_party.h) and [np_error.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_error.h) (PS4); shadPS4 does not check for a null state, and whether the console needs an initialized party library first is not known. Not measured on a PS5 +- [sceNpPartyGetState and sceNpPartyGetStateAsUserA](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - the 16-bit state, its `NOT_IN_PARTY` value 2 and the `INVALID_ARGUMENT` (`0x80552504`) error follow shadPS4's [np_party.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_party.h) and [np_error.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_error.h) (PS4); shadPS4 does not check for a null state, and whether the console needs an initialized party library first is not known. Not measured on a PS5 +- [sceNpPartyGetId, sceNpPartyGetMembersA, sceNpPartyGetMemberInfoA, sceNpPartyGetMemberSessionInfo, sceNpPartyGetMemberVoiceInfo and sceNpPartySendBinaryMessage](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - the user is never in a party, so these return `NOT_IN_PARTY` (`0x80552506`, shadPS4's np_error.h) and write nothing, or `INVALID_ARGUMENT` for a null pointer; the signatures are from [Orbital's type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json). Like `sceNpPartyGetState` they do not require `sceNpPartyInitialize`, and the order of the null and party checks on the console is not known. Not measured on a PS5 +- [sceNpPartyInitialize, sceNpPartyTerminate, sceNpPartyCheckCallback and sceNpPartyRegisterHandlerA](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - an initialized flag with `ALREADY_INITIALIZED` (`0x80552502`) and `NOT_INITIALIZED` (`0x80552503`) from shadPS4's np_error.h; the handlers are accepted but never called, since no party event can happen. Whether the console refuses a null initialize parameter, and which check comes first, is not known +- [sceNpPartyCreate, sceNpPartyCreateA, sceNpPartyGetStateAsUser, sceNpPartyGetVoiceChatPriority, sceNpPartyJoin, sceNpPartyLeave, sceNpPartyRegisterPrivateHandler, sceNpPartySetVoiceChatPriority, sceNpPartyShowInvitationList, sceNpPartyShowInvitationListA and sceNpPartyUnregisterPrivateHandler](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - Orbital has no prototypes for these and no credible ones were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceNpPartyGetMembers, sceNpPartyGetMemberInfo and sceNpPartyRegisterHandler](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - the signatures are from [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/np/ps4_libscenpparty.pas) (PS4) and match their `A` variants, so they behave like them: no party, `NOT_IN_PARTY` from [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_party.cpp), and handlers accepted but never called. How the console's non-`A` member structures differ from the `A` ones is not known, and nothing is written to them. Not measured on a PS5 diff --git a/docs/dev/debt/libSceNpProfileDialog.md b/docs/dev/debt/libSceNpProfileDialog.md index 2755456503..54a1f1b591 100644 --- a/docs/dev/debt/libSceNpProfileDialog.md +++ b/docs/dev/debt/libSceNpProfileDialog.md @@ -1,5 +1,14 @@ # libSceNpProfileDialog technical debt +### Silent stubs + +- [sceNpProfileDialogOpen, sceNpProfileDialogOpenA, sceNpProfileDialogGetResult](../../../core/libs/prx/libSceNpProfileDialog/Export.cpp) (libSceNpProfileDialog) - no profile is shown: `Open` and `OpenA` move the dialog straight to `FINISHED`, and `GetResult` reports `result` 0, `userAction` `USER_CANCELED` (1) and the caller's `userData`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c2814041e546bf75c12756dbec9da911c721ce1c/src/core/libraries/np/np_profile_dialog/np_profile_dialog.cpp#L21-L180) does when its window is dismissed. Not measured on a console. + ### Unknown function info -- [sceNpProfileDialogClose, sceNpProfileDialogGetResult, sceNpProfileDialogGetStatus, sceNpProfileDialogInitialize, sceNpProfileDialogOpen, sceNpProfileDialogOpenA, sceNpProfileDialogTerminate and sceNpProfileDialogUpdateStatus](../../../core/libs/prx/libSceNpProfileDialog/Export.cpp) (libSceNpProfileDialog) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpProfileDialog.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceNpProfileDialogOpen, sceNpProfileDialogOpenA, sceNpProfileDialogGetResult](../../../core/libs/prx/libSceNpProfileDialog/Export.cpp) (libSceNpProfileDialog) - the layouts come from the [shadPS4 header](https://github.com/shadps4-emu/shadPS4/blob/c2814041e546bf75c12756dbec9da911c721ce1c/src/core/libraries/np/np_profile_dialog/np_profile_dialog.h#L31-L66) alone: `SceNpProfileDialogParam` 128 bytes with `userData` at 88, `SceNpProfileDialogParamA` 120 bytes with 4 unnamed bytes after `targetAccountId` and `userData` at 80, the result 48 bytes. A param whose `size` is not that size, or whose `mode` is not 1 to 4, throws. `baseParam` (size, magic), `userId` and the target ID are not checked. +- [sceNpProfileDialogOpen, sceNpProfileDialogOpenA, sceNpProfileDialogClose, sceNpProfileDialogGetResult](../../../core/libs/prx/libSceNpProfileDialog/Export.cpp) (libSceNpProfileDialog) - these paths throw because shadPS4 and the other common dialogs here return different errors: `Open` or `OpenA` before `Initialize` (shadPS4 `INVALID_STATE`, the Signin family `NOT_INITIALIZED`); `Close` on a dialog that is not running, which is every `Close` after `Initialize` here (shadPS4 `NOT_RUNNING`, the Signin family 0 and `FINISHED`); `GetResult` with a null result before the dialog finishes (shadPS4 `ARG_NULL`, Signin and NetCtlAp `NOT_FINISHED`). + +### Functional + +- [sceNpProfileDialogInitialize](../../../core/libs/prx/libSceNpProfileDialog/Export.cpp) (libSceNpProfileDialog) - never returns shadPS4's `NOT_SYSTEM_INITIALIZED` (`sceCommonDialogInitialize` not called) or `BUSY` (another common dialog in use): libSceCommonDialog shares neither state here. diff --git a/docs/dev/debt/libSceNpScoreRanking.md b/docs/dev/debt/libSceNpScoreRanking.md new file mode 100644 index 0000000000..d373e91695 --- /dev/null +++ b/docs/dev/debt/libSceNpScoreRanking.md @@ -0,0 +1,5 @@ +# libSceNpScoreRanking technical debt + +### Functional + +- [libSceNpScoreRanking](../../../core/libs/prx/libSceNpScoreRanking/Export.cpp) (libSceNpScoreRanking) - offline only, not measured on a PS5: the argument checks, their order, the limit of 32 title contexts and 32 requests and the array sizes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_score/np_score.cpp#L64-L1210), and a valid call then returns `SCE_NP_ERROR_SIGNED_OUT` and finishes its request with it, as shadPS4 does when the user [has no connection](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_handler/np_handler_score.cpp#L18-L26). No output is written, where shadPS4 first clears the rank, comment and info arrays. A null `boardInfo` throws: shadPS4 answers it by the title's SDK version diff --git a/docs/dev/debt/libSceNpSessionSignaling.md b/docs/dev/debt/libSceNpSessionSignaling.md index f5f1396a51..33ed1372ba 100644 --- a/docs/dev/debt/libSceNpSessionSignaling.md +++ b/docs/dev/debt/libSceNpSessionSignaling.md @@ -2,9 +2,11 @@ ### Unknown function info -- [sceNpSessionSignalingGetConnectionFromPeerAddress](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - imported by PPSA01289 and added without an implementation; unknown signature -- [sceNpSessionSignalingGetMemoryInfo](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature -- [sceNpSessionSignalingGetConnectionStatistics](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature -- [sceNpSessionSignalingGetConnectionStatus](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - `(int32 context id, int32 connection id, int32* status, SceNetInAddr* peer address, uint16* peer port)` is ASTRO BOT's (PPSA21564 01.007.000) call at 0x30622B: the context id from a 32-bit field, the connection id, three stack outputs, a negative result taken as an error, then the address passed to `sceNetInetNtop(AF_INET, ...)` and the port to `sceNetNtohs`. It matches PS4's `sceNpSignalingGetConnectionStatus`. The title reaches it only for a connection id from the signaling library, which offline never hands one out. No session ever activates offline, so every connection id answers `CONN_NOT_FOUND` (0x8055330C, from [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/64d198190fa05d37cbb1fa29c50f967c376fce55/src/core/libraries/np/np_error.h)) without writing the outputs, and a null status answers `INVALID_ARGUMENT`. The context id is not checked, as contexts are not tracked. The console's answer for an unknown context or connection is unverified -- PPSA31327 imports declared without parameters, signatures unknown: [sceNpSessionSignalingGetGroupInfo, sceNpSessionSignalingGetConnectionFromNetAddress2](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling), imported by Yakuza 0 Director's Cut and not called before the main menu. -- [sceNpSessionSignalingCreateJoinActivate, sceNpSessionSignalingGetConnectionFromNetAddress, sceNpSessionSignalingGetCustomProperty, sceNpSessionSignalingGetGroupFromPeerAddress, sceNpSessionSignalingInitializeWithPort, sceNpSessionSignalingJoinActivate and sceNpSessionSignalingSetCustomProperty](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpSessionSignaling.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. +- [sceNpSessionSignalingInitialize, sceNpSessionSignalingCreateContext and sceNpSessionSignalingCreateContext2](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) - the initialization layout is 40 bytes; both context layouts are 56 bytes with callback and user-data pairs. The second context variant adds a group identifier to its connection callback. Pool allocation, callback delivery and underlying NP account validation are not implemented; priority and affinity settings do not affect host scheduling. + +- [sceNpSessionSignalingGetConnectionStatus, sceNpSessionSignalingActivateSession and sceNpSessionSignalingActivateUser](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) - initialization, context lookup and output validation follow the system library. Connections never activate in the offline runtime; activation returns unavailable and a connection query on a valid context returns connection-not-found without writing outputs. SDK-dependent peer-address validation and local-network discovery remain incomplete. +- [sceNpSessionSignalingCreateJoinActivate, sceNpSessionSignalingGetCustomProperty, sceNpSessionSignalingGetGroupFromPeerAddress, sceNpSessionSignalingInitializeWithPort, sceNpSessionSignalingJoinActivate and sceNpSessionSignalingSetCustomProperty](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpSessionSignaling.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. + +### Functional + +- [sceNpSessionSignalingGetGroupInfo, sceNpSessionSignalingGetGroupFromSessionId, sceNpSessionSignalingGetConnectionFromNetAddress, sceNpSessionSignalingGetConnectionFromNetAddress2, sceNpSessionSignalingGetConnectionFromPeerAddress, sceNpSessionSignalingGetConnectionFromPeerAddress2](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - no session ever activates here, so every lookup on a valid context ends at not-found (group 0x8055330A, connection 0x8055330C). The signatures and the checks before it are the PS5 12.00 module's, read from disassembly: 0x80553301 before initialization, 0x80553303 for a null pointer, then 0x80553308 for an unknown context diff --git a/docs/dev/debt/libSceNpSignaling.md b/docs/dev/debt/libSceNpSignaling.md new file mode 100644 index 0000000000..1adbc6177b --- /dev/null +++ b/docs/dev/debt/libSceNpSignaling.md @@ -0,0 +1,7 @@ +# libSceNpSignaling technical debt + +### Functional + +- [libSceNpSignaling](../../../core/libs/prx/libSceNpSignaling/Export.cpp) (libSceNpSignaling) - offline only, not measured on a PS5: the checks, their order, context ids 1 to 8 and the NpId rule (`term` 0, 3 to 16 characters, the first an ASCII letter or digit, the rest ASCII letters, digits, `_` or `-`) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_signaling/np_signaling.cpp#L118-L170) and its [state helpers](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_signaling/np_signaling_state.cpp#L120-L170). `sceNpSignalingInitialize` reserves no heap and starts no thread, so its arguments are not checked, and callbacks are never called. `sceNpSignalingActivateConnection` returns `SCE_NP_ERROR_SIGNED_OUT` after shadPS4's checks up to `OWN_NP_ID`, without its rate limit, and creates no connection, so the connection calls answer `CONN_NOT_FOUND` + +- [sceNpSignalingDeactivateConnection](../../../core/libs/prx/libSceNpSignaling/Export.cpp) (libSceNpSignaling) - not measured on a PS5: it has the same checks as `sceNpSignalingTerminateConnection` (not initialized, unknown context, then `CONN_NOT_FOUND`), after shadPS4's [call](https://github.com/shadps4-emu/shadPS4/blob/12a4564543ccec527f9723676d2180580d1d7a94/src/core/libraries/np/np_signaling/np_signaling.cpp#L378-L400). No connection is ever created, so none can be deactivated. diff --git a/docs/dev/debt/libSceNpSns.md b/docs/dev/debt/libSceNpSns.md index 9f14bbcc3a..29b552135c 100644 --- a/docs/dev/debt/libSceNpSns.md +++ b/docs/dev/debt/libSceNpSns.md @@ -2,7 +2,7 @@ ### Unknown function info -- [sceNpSnsFacebookAbortRequest, sceNpSnsFacebookCreateRequest, sceNpSnsFacebookDeleteRequest, sceNpSnsFacebookGetAccessToken, sceNpSnsIntAbortRequest, sceNpSnsIntCreateRequest, sceNpSnsIntDeleteRequest, sceNpSnsIntFbGetGameAccessToken, sceNpSnsIntFbGetGameAccessTokenAllowed, sceNpSnsIntFbGetSystemAccessToken, sceNpSnsIntFbGetTitleInfo, sceNpSnsIntLinkedStatus, sceNpSnsIntTwGetSystemAccessToken, sceNpSnsIntUnlink, sceNpSnsIntYtGetAccessToken, sceNpSnsIntYtRefreshMasterToken, sceNpSnsTwitchAbortRequest, sceNpSnsTwitchCreateRequest, sceNpSnsTwitchDeleteRequest, sceNpSnsTwitchGetAccessToken, sceNpSnsYouTubeAbortRequest, sceNpSnsYouTubeCreateRequest, sceNpSnsYouTubeDeleteRequest and sceNpSnsYouTubeGetAccessToken](../../../core/libs/prx/libSceNpSns/Export.cpp) (libSceNpSns) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpSns.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceNpSnsFacebookAbortRequest, sceNpSnsIntAbortRequest, sceNpSnsIntCreateRequest, sceNpSnsIntDeleteRequest, sceNpSnsIntFbGetGameAccessToken, sceNpSnsIntFbGetGameAccessTokenAllowed, sceNpSnsIntFbGetSystemAccessToken, sceNpSnsIntFbGetTitleInfo, sceNpSnsIntLinkedStatus, sceNpSnsIntTwGetSystemAccessToken, sceNpSnsIntUnlink, sceNpSnsIntYtGetAccessToken, sceNpSnsIntYtRefreshMasterToken, sceNpSnsTwitchAbortRequest, sceNpSnsTwitchCreateRequest, sceNpSnsTwitchDeleteRequest, sceNpSnsTwitchGetAccessToken, sceNpSnsYouTubeAbortRequest, sceNpSnsYouTubeCreateRequest, sceNpSnsYouTubeDeleteRequest and sceNpSnsYouTubeGetAccessToken](../../../core/libs/prx/libSceNpSns/Export.cpp) (libSceNpSns) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpSns.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified ### Inventory exports declared elsewhere - sceNpSnsFacebookCreateRequest, sceNpSnsFacebookDeleteRequest and sceNpSnsFacebookGetAccessToken are also 3.20 exports of this module but are currently declared by #6977 in libSceNpSnsFacebookDialog, so they are omitted here to respect the duplicate-export rule; re-add if that PR's module attribution is rejected. + sceNpSnsFacebookCreateRequest, sceNpSnsFacebookDeleteRequest and sceNpSnsFacebookGetAccessToken are also 3.20 exports of this module but are declared in `libSceNpManager` ([`Export.cpp`](../../../core/libs/prx/libSceNpManager/Export.cpp)), so they are omitted here to respect the duplicate-export rule; re-add them here if that module attribution is corrected. diff --git a/docs/dev/debt/libSceNpSnsFacebookDialog.md b/docs/dev/debt/libSceNpSnsFacebookDialog.md index 3009c1d373..5ec4954b21 100644 --- a/docs/dev/debt/libSceNpSnsFacebookDialog.md +++ b/docs/dev/debt/libSceNpSnsFacebookDialog.md @@ -1,5 +1,5 @@ # libSceNpSnsFacebookDialog technical debt -### Unknown function info +### Silent stubs -- The following pending declarations have unverified signatures and behavior; they are parameterless import-resolution declarations and throw through `NotImplemented_nid_no_patch`: [`sceNpSnsFacebookDialogClose`](../../../core/libs/prx/libSceNpSnsFacebookDialog/Export.cpp), [`sceNpSnsFacebookDialogGetStatus`](../../../core/libs/prx/libSceNpSnsFacebookDialog/Export.cpp). +- [libSceNpSnsFacebookDialog](../../../core/libs/prx/libSceNpSnsFacebookDialog/Export.cpp) - the Facebook dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as there is no Facebook account link. The signatures are from [Orbital's type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json); the result layout beyond the leading `int32_t` result code is not known, so nothing else is written. diff --git a/docs/dev/debt/libSceNpTrophy.md b/docs/dev/debt/libSceNpTrophy.md new file mode 100644 index 0000000000..82ca475ea5 --- /dev/null +++ b/docs/dev/debt/libSceNpTrophy.md @@ -0,0 +1,11 @@ +# libSceNpTrophy technical debt + +### Unknown function info + +- [sceNpTrophyRegisterContext, sceNpTrophyGetTrophyUnlockState, sceNpTrophyUnlockTrophy](../../../core/libs/prx/libSceNpTrophy/Export.cpp) (libSceNpTrophy, PS4) - the trophy definitions in `TROPCONF.ESFM` are encrypted, so the trophy count is the number of consecutive `TROPnnn.PNG` icons in the title's `/app0/sce_sys/trophy/trophyNN.trp`. Unlocks are kept per user and service label in `_trophy/trophyNN.` under the working directory, no platinum trophy is reported (`platinumId` is -1), and a locked trophy's icon fails with `TROPHY_NOT_UNLOCKED`. None of it is checked on a console. + +### Functional + +- [sceNpTrophyGetGameInfo](../../../core/libs/prx/libSceNpTrophy/Export.cpp) (libSceNpTrophy) - the trophy configuration `TROPCONF.ESFM` is encrypted and not read, so the game details report the trophy count taken from the `TROPnnn.PNG` icons and the group count taken from the `GRnnn.PNG` icons of the trophy package; the per-grade counts are 0, the title and description are empty, and the progress percentage is unlocked trophies over all trophies instead of the console's grade-weighted points +- [sceNpTrophyGetGroupInfo](../../../core/libs/prx/libSceNpTrophy/Export.cpp) (libSceNpTrophy) - only the base game group (id -1) is answered, with the same counts as the game; an id past the `GRnnn.PNG` icons returns `INVALID_GROUP_ID`, and an existing group throws because the trophies of a group are listed only in the encrypted configuration +- [sceNpTrophyGetTrophyInfo](../../../core/libs/prx/libSceNpTrophy/Export.cpp) (libSceNpTrophy) - the grade is reported as unknown, the trophy is never hidden, the name and description are empty and the unlock timestamp is 0; a package with groups throws, since a trophy's group is listed only in the encrypted configuration diff --git a/docs/dev/debt/libSceNpTrophy2.md b/docs/dev/debt/libSceNpTrophy2.md index 4576f12de6..8552c9e0de 100644 --- a/docs/dev/debt/libSceNpTrophy2.md +++ b/docs/dev/debt/libSceNpTrophy2.md @@ -2,20 +2,20 @@ ### Silent stubs +- [sceNpTrophy2GetRewardIcon](../../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - zeroes size and returns `SCE_NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND` (-2141898479): the `(int context, int handle, int reward_id, void* buffer, size_t* size)` signature and two-stage query pattern follow the bundled `Media/Plugins/PSNCore.prx` of PPSA06888 and SharpProspero; returning -2141898479 (0x80553911) when icons are unavailable is inferred from sibling calls (sceNpTrophy2GetGameIcon, sceNpTrophy2GetGroupIcon, sceNpTrophy2GetTrophyIcon) in KytyPS5 (libTrophy.cpp:464/483/502) - [libSceNpTrophy2](../../../core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp): `sceNpTrophy2RegisterUnlockCallback` accepts a callback that is never called, as the callback's signature and when the console calls it are not known, `sceNpTrophy2UnregisterUnlockCallback` only returns success, and `sceNpTrophy2ShowTrophyList` returns success without showing a list (there is no system trophy UI; seen on PPSA15552). ### Unknown function info - [Query structures](../../../core/libs/prx/libSceNpTrophy2/include/NpTrophy2Types.hpp): a progress value is a 16-byte `{int32 type, 4 reserved bytes, uint64 value}` and the game, group and trophy structures are 152, 24, 152, 32, 1312 and 32 bytes, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/libTrophy.cpp) asserts; no other public source confirms the layout. Progress type 1 marks a progressive trophy's target and progress, as in KytyPS5. Trophies without a `groupId` belong to group -1, KytyPS5's base game group. -- [sceNpTrophy2GetRewardIcon](../../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) takes `(int context, int handle, int reward_id, void* buffer, size_t* size)`, from the call sites in the bundled `Media/Plugins/PSNCore.prx` of PPSA06888, the same shape as `sceNpTrophy2GetTrophyIcon`. The title calls it twice: first with a null buffer to read the size, then with a buffer of that size allocated with `malloc`. The meaning of the third argument is unverified, and the reward icon data is unknown, so every call throws - - - [libSceNpTrophy2 pending exports](../../../core/libs/prx/libSceNpTrophy2/Export.cpp) - 23 named callable exports were identified in the PS5 3.20 export table and cross-checked against firmware 13.60. Signatures and behavior remain unverified; declarations use the unknown-signature convention and throw through `NotImplemented_nid_no_patch`. Evidence: [3.20 export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpTrophy2.c). + ### Functional -- [Trophy queries](../../../core/libs/prx/libSceNpTrophy2/src/TrophyInfo.cpp) report the title's [trophy data](../../../core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp) (`sce_sys/trophy2/trophyNN.ucp`) and the unlocks in the [trophy store](../../../core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp). Without the file, or when it is malformed, they report the old placeholders and `sceNpTrophy2GetTrophyInfoArray` lists only trophy 0. Icons are not read. Hidden trophies report their names and descriptions; whether the console blanks them before the unlock is not known. `progress_percentage` weighs gold 6, silver 2 and bronze 1 and leaves the platinum out, as KytyPS5 does (the ratio of the published PSN trophy points); the console's formula is not known. A trophy or group that is not in the file throws. A context that was not created or was destroyed reports placeholders and no unlocks and returns success; the console's errors for it are unknown. +- [Trophy queries](../../../core/libs/prx/libSceNpTrophy2/src/TrophyInfo.cpp) - not confirmed on the console: `sceNpTrophy2GetTrophyInfo` of a trophy id that is not in the title's [trophy data](../../../core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp) (`sce_sys/trophy2/trophyNN.ucp`) returns `0x8055390A` (KytyPS5's `NP_TROPHY2_ERROR_INVALID_TROPHY_ID`) and writes nothing; a context not created or destroyed reports placeholders and no unlocks and returns success; hidden trophies report their names and descriptions before the unlock; `progress_percentage` weighs gold 6, silver 2 and bronze 1 and leaves the platinum out, as KytyPS5 does (the ratio of the published PSN trophy points). A group that is not in the data throws. Without the file, or when it is malformed, the queries report the old placeholders and `sceNpTrophy2GetTrophyInfoArray` lists only trophy 0. Icons are not read. Unlocks come from the [trophy store](../../../core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp). - [Trophy data limits](../../../core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp): a `trophyNN.ucp` or `udsNN.ucp` larger than 256 MiB or listing more than 4096 files, and a JSON file in it that nests deeper than 64 levels or holds more than 1048576 values, are not used, and the title runs as without that file. The limits keep the memory the reader takes to the file and about 140 MiB of parsed JSON; the console's limits are not known. - [Text language](../../../core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp): texts come from `tropmeta_.json` for the console language, then `en-US`, then the file's `defaultLanguage`. The tags for languages 0 to 29 are KytyPS5's; `uk-UA` for 30 (Ukrainian) is an assumption. +- [Trophy progress statistics](../../../core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp): `max` (the largest value posted, starting from `initialValue`, or `minValue` without one), `int32` (0 to 2147483647) and a rule condition of an `eventName` and one `property` whose `comparator` is `==` and `value` an unsigned integer follow the rules a title ships for its progressive trophies, not a console measurement. Borderlands 4 (PPSA01494) feeds each one's `udsStatId` from `$._trophy_progress` of `_UpdateTrophyProgress`, through an `int32` `max` statistic and a rule on `$._trophy_id`. `latest` and the unsigned types follow KytyPS5. - [Platinum](../../../core/libs/prx/libSceNpTrophy2/src/TitleTrophies.cpp): an unlock through any path unlocks the platinum that the trophy names in `platinumTrophyId` once every trophy naming that platinum is unlocked, as KytyPS5 does; trophies without `platinumTrophyId` are not required. With the title's trophy data loaded, a posted `_UnlockTrophy` of a trophy that is not in the data, of a platinum or of a progressive trophy is not stored and prints a `[trophy] ignored` line, as KytyPS5 refuses them, so a progressive trophy unlocks only through its statistic; without trophy data any id of 0 or more is stored, including ids the data would make the platinum, a progressive trophy or no trophy, and the platinum is not unlocked when the trophies it needs are. The console's behaviour is not known. - [Damaged records](../../../core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp): a line in `_trophy/_.txt` or `.stats.txt` that is not a new ` ` pair (a blank line, a carriage return, a repeated or negative id, a value that is not a number) makes `readRecord` throw a `std::runtime_error` that names the file and the line. Every call that reads that record then throws it instead of returning, which stops the title as any throwing export does, so the title gets no return value; main returned 0 from these calls. The failed read is not kept, so each later call reads the file again and throws again until the file is fixed or deleted. The unlock record is read by `sceNpTrophy2GetTrophyInfo`, `sceNpTrophy2GetTrophyInfoArray` and `sceNpTrophy2GetGameInfo` for a created context, by `sceNpTrophy2GetGroupInfo` and `sceNpTrophy2GetGroupInfoArray` when the title's trophy data is loaded, by a `sceNpUniversalDataSystemPostEvent` of an `_UnlockTrophy` that the trophy data does not refuse, and by a statistic change when the trophy data is loaded; the stat record by a `sceNpUniversalDataSystemPostEvent` of an event that a statistics rule copies and by the trophy queries of a progressive trophy with a statistic. What the console does with a damaged record is not known. diff --git a/docs/dev/debt/libSceNpTus.md b/docs/dev/debt/libSceNpTus.md index 141e50a1cc..142eda2d11 100644 --- a/docs/dev/debt/libSceNpTus.md +++ b/docs/dev/debt/libSceNpTus.md @@ -4,88 +4,18 @@ - [TUS variable outputs](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - the layouts of the variable arrays of the `sceNpTusGetMultiSlotVariable*` calls and of `outVariable` in `sceNpTusAddAndGetVariableAAsync` are not verified, so the output is never written and `variableArraySize` / `outVariableSize` are not checked; shadPS4, and RPCS3 in its PS3 `sceNpTusGetMultiSlotVariable*` and `sceNpTusAddAndGetVariable*` calls, return `INVALID_ALIGNMENT` unless the size is `arrayLen` variables of the call's type, or one variable for `outVariable`. The signatures and the community error codes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/tree/main/src/core/libraries/np/np_tus), which fpPS4 agrees with for the context and request calls; no console measurement. - [Multi-slot variable calls](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - the argument checks of the multi-slot get, set and delete calls are not measured on a console; they follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c1b26558352b863f80142256e4df38585aa7d727/src/core/libraries/np/np_tus/np_tus.cpp), where they differ between functions: `arrayLen < 1` is `INSUFFICIENT_ARGUMENT` in the NpId and virtual-user delete calls and in `sceNpTusGetMultiSlotVariableVUserAsync`, and `INVALID_ARGUMENT` in the others; a null NpId is accepted except in delete; an empty virtual user ID is `INVALID_ARGUMENT`. RPCS3's [PS3 module](https://github.com/RPCS3/rpcs3/blob/1a9dca3a3a84a18df866e3021918322bd295160a/rpcs3/Emu/Cell/Modules/sceNpTus.cpp) has only the NpId and virtual-user calls; it returns `INVALID_ARGUMENT` for an empty array in all of them, rejects a null target in get and set and does not check for an empty virtual user ID. +- [Variable, data, data status and TSS requests](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - the argument checks of the 82 add-and-get, try-and-set, data, data status, multi-user, friends and `sceNpTssGetSmallStorage` calls are not measured on a console; their signatures, checks and order follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_tus/np_tus.cpp), including its differences between forms: the NpId try-and-set calls return `INVALID_ARGUMENT` for an operation type outside 1-6 where the others return `INVALID_TYPE`, and the NpId and virtual-user multi-slot data status calls return `TOO_MANY_NPID` for more than 64 slots. As for the calls above, the output and structure sizes are not checked, and every request finishes with `SCE_NP_ERROR_SIGNED_OUT` - [sceNpTusSetThreadParam](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTssCreateNpTitleCtx](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTssCreateNpTitleCtxA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusCreateTitleCtx](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusSetTimeout](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusChangeModeForOtherSaveDataOwners](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariable](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetFriendsVariable](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetFriendsVariableAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariable](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariable](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusDeleteMultiSlotData](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusDeleteMultiSlotDataVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusDeleteMultiSlotDataAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusDeleteMultiSlotDataVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusSetDataAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusSetDataVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetDataAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetDataVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatus](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatus](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetFriendsDataStatus](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetFriendsDataStatusAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTssGetSmallStorage](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTssGetSmallStorageAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTssGetStorage](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTssGetStorageAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTssGetDataAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableAVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableAVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableForCrossSave](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableForCrossSaveAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableForCrossSaveVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusAddAndGetVariableForCrossSaveVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusAAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusAVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusAVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusForCrossSave](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusForCrossSaveAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusForCrossSaveVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetDataA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetDataAAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetDataAVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetDataAVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetDataForCrossSave](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetDataForCrossSaveAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusGetDataForCrossSaveVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusGetDataForCrossSaveVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableAAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableAVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableAVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableForCrossSave](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableForCrossSaveAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableForCrossSaveVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserVariableForCrossSaveVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusAAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusAVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusAVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusForCrossSave](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusForCrossSaveAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusForCrossSaveVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusGetFriendsVariableA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusGetFriendsVariableAAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusGetFriendsVariableForCrossSave](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature @@ -94,23 +24,11 @@ - [sceNpTusGetFriendsDataStatusAAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusGetFriendsDataStatusForCrossSave](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusGetFriendsDataStatusForCrossSaveAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusSetDataA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusSetDataAAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusSetDataAVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusSetDataAVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusDeleteMultiSlotDataA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusDeleteMultiSlotDataAAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableAAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableAVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableAVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableForCrossSave](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableForCrossSaveAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableForCrossSaveVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTusTryAndSetVariableForCrossSaveVUserAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusDeleteNpTitleCtx](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - the PS5 signature, error codes and child-request lifetime have not been measured on a console. Deletion invalidates the context and all its requests, following [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e7626e3c7de8d0f0fda6a91f620b42f6c762/src/core/libraries/np/np_tus/np_tus.cpp#L158-L180). ### Functional - [sceNpTusCreateNpTitleCtxA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - creates a title context without checking sign-in, up to 32 contexts (`TOO_MANY_OBJECTS` beyond); shadPS4 returns the `sceNpGetNpId` error instead. Requests are limited to 256, and the limits are not measured on a console. +- [sceNpTssCreateNpTitleCtx and sceNpTssCreateNpTitleCtxA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - that TSS title contexts share the TUS context table and its 32-context limit is inferred from [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/core/libraries/np/np_tus/np_tus.cpp#L146-L168), which redirects both to the TUS creators, and from TSS requests coming from `sceNpTusCreateRequest`; nothing is measured on a console. The `A` form is `sceNpTusCreateNpTitleCtxA`. The `NpId` form returns `INSUFFICIENT_ARGUMENT` for a null `npId` and `INVALID_ARGUMENT` for service label `0xFFFFFFFF` before the limit, as shadPS4's [sceNpTusCreateNpTitleCtx](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/core/libraries/np/np_tus/np_tus.cpp#L122-L143) does; the signatures are shadPS4's and fpPS4's, and Orbital's for the `A` form. - [sceNpTusPollAsync, sceNpTusWaitAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - there is no PSN, so every implemented `*Async` call whose arguments pass the checks starts a request that finishes at once with `SCE_NP_ERROR_SIGNED_OUT`, which poll and wait report and the blocking calls return; shadPS4 returns `SIGNED_OUT` from the `*Async` call itself when no shadNet client is signed in, and RPCS3 returns `INVALID_ONLINE_ID` while offline, marked there as probable behaviour. An abort stays set, so every later operation on the request, blocking ones included, finishes with `ABORTED`; in shadPS4 an abort only ends the operation in progress. Until an operation has started, poll returns 1 and wait returns `INVALID_ARGUMENT` as in shadPS4; RPCS3 [blocks until a result is set](https://github.com/RPCS3/rpcs3/blob/1a9dca3a3a84a18df866e3021918322bd295160a/rpcs3/Emu/NP/np_contexts.cpp#L40-L52). +- [sceNpTusGetData, sceNpTusGetDataVUser, sceNpTusGetMultiSlotVariable, sceNpTusGetMultiSlotVariableVUser, sceNpTusGetMultiUserVariableForCrossSave, sceNpTusSetData, sceNpTusSetDataVUser, sceNpTusSetMultiSlotVariable and sceNpTusSetMultiSlotVariableVUser](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - block on their `*Async` forms with the same parameters, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/np/np_tus/np_tus.cpp) does, so they inherit those forms' unmeasured checks and the offline `SIGNED_OUT` result diff --git a/docs/dev/debt/libSceNpUniversalDataSystem.md b/docs/dev/debt/libSceNpUniversalDataSystem.md index 6c8a996278..cbc943be5e 100644 --- a/docs/dev/debt/libSceNpUniversalDataSystem.md +++ b/docs/dev/debt/libSceNpUniversalDataSystem.md @@ -4,7 +4,20 @@ - [Events](../../../core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp): `sceNpUniversalDataSystemCreateEvent` copies `prop` and returns the event's own property object through `prop_ptr`, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/libTrophy.cpp) does; `sceNpUniversalDataSystemEventPropertyObjectSetObject`, `SetArray` and the array setters for objects and arrays store a copy of `value` and return the stored copy through `value_ptr`. Destroying a property object or array that its Create function did not return (an event's property object or a stored copy) does nothing, as its owner frees it. These ownership rules are not verified on a console, nor are the output of `sceNpUniversalDataSystemEventToString` (still `{}`) and `sceNpUniversalDataSystemEventEstimateSize` (still 3). +- [sceNpUniversalDataSystemIntCreateRecordArray, sceNpUniversalDataSystemIntCreateRecordData and sceNpUniversalDataSystemIntCreateRecordObject](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntDeleteAllRecord](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - It is in the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. It is declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntDestroyRecordArray and sceNpUniversalDataSystemIntDestroyRecordData](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntDestroyRecordObject and sceNpUniversalDataSystemIntGetMemoryStat](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntPostRecordData, sceNpUniversalDataSystemIntRecordArraySetArray and sceNpUniversalDataSystemIntRecordArraySetBinary](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntRecordArraySetBool, sceNpUniversalDataSystemIntRecordArraySetFloat32 and sceNpUniversalDataSystemIntRecordArraySetFloat64](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntRecordArraySetInt32, sceNpUniversalDataSystemIntRecordArraySetInt64 and sceNpUniversalDataSystemIntRecordArraySetObject](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntRecordArraySetString, sceNpUniversalDataSystemIntRecordArraySetUInt32 and sceNpUniversalDataSystemIntRecordArraySetUInt64](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntRecordDataEstimateSize, sceNpUniversalDataSystemIntRecordObjectSetArray and sceNpUniversalDataSystemIntRecordObjectSetBinary](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntRecordObjectSetBool, sceNpUniversalDataSystemIntRecordObjectSetFloat32 and sceNpUniversalDataSystemIntRecordObjectSetFloat64](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntRecordObjectSetInt32, sceNpUniversalDataSystemIntRecordObjectSetInt64 and sceNpUniversalDataSystemIntRecordObjectSetObject](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUniversalDataSystemIntRecordObjectSetString, sceNpUniversalDataSystemIntRecordObjectSetUInt32 and sceNpUniversalDataSystemIntRecordObjectSetUInt64](../../../core/libs/prx/libSceNpUniversalDataSystem/PendingExports.cpp) (libSceNpUniversalDataSystem) - The list is the intersection of the export table of the console's libSceNpUniversalDataSystem (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUniversalDataSystem.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. + ### Functional -- [Trophy events](../../../core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp): `sceNpUniversalDataSystemPostEvent` stores an `_UnlockTrophy` event with an int32 `_trophy_id` of 0 or more as an unlock for the user and service label of its context, as KytyPS5 does, except an id that [libSceNpTrophy2](../../../core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp) refuses when the title's trophy data is loaded (one not in the data, a platinum or a progressive trophy). `_UpdateTrophyProgress`, named only by a third-party unlocker, is logged once per trophy and not interpreted. A context need not be registered, and every post of an event that `sceNpUniversalDataSystemCreateEvent` returned and that is not yet destroyed returns success (a null or any other event pointer stops the game): KytyPS5's errors for an unknown or unregistered context and for a malformed unlock are not verified. The storage statistics stay zero. -- [Statistics](../../../core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp): every other event goes through the title's rules in `sce_sys/uds/udsNN.ucp`, which [libSceNpTrophy2](../../../core/libs/prx/libSceNpTrophy2/src/TitleTrophies.cpp) reads. Only statistics with `"aggregation":"latest"` and `dataType` `uint32` or `uint64` are kept, as in KytyPS5; no public source documents the other aggregations or types, so their definitions and rules are skipped. Only rules whose condition is an `eventName` alone and whose input is a `$.name` path (dotted for nested objects) are applied. A value is used when it is an integer of any width that is not negative and fits the statistic's type and `minValue`/`maxValue`; KytyPS5 requires the statistic's exact width, and whether the console converts, clamps or rejects other values is not known. Statistics are stored per user and service label, and every trophy's `unlockCondition` is compared after each event that sets one. +- [Trophy events](../../../core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp): `sceNpUniversalDataSystemPostEvent` stores an `_UnlockTrophy` event with an int32 `_trophy_id` of 0 or more as an unlock for the user and service label of its context, as KytyPS5 does, except an id that [libSceNpTrophy2](../../../core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp) refuses when the title's trophy data is loaded (one not in the data, a platinum or a progressive trophy). `_UpdateTrophyProgress` goes through the title's statistic rules like any other event (below). A context need not be registered, and every post of an event that `sceNpUniversalDataSystemCreateEvent` returned and that is not yet destroyed returns success (a null or any other event pointer stops the game): KytyPS5's errors for an unknown or unregistered context and for a malformed unlock are not verified. The storage statistics stay zero. +- [Statistics](../../../core/libs/prx/libSceNpUniversalDataSystem/src/Event.cpp): every other event goes through the title's rules in `sce_sys/uds/udsNN.ucp`, which [libSceNpTrophy2](../../../core/libs/prx/libSceNpTrophy2/src/TitleTrophies.cpp) reads. Only `"aggregation":"latest"` and `"max"` statistics of `dataType` `uint32`, `uint64` or `int32` are kept; no public source documents the other aggregations (such as `count`, `collection` and `eachLatest`) or types, so their definitions and rules are skipped. Only rules whose input is a `$.name` path (dotted for nested objects) are applied. A value is used when it is an integer of any width that is not negative and fits the statistic's type and `minValue`/`maxValue`; KytyPS5 requires the statistic's exact width, and whether the console converts, clamps or rejects other values is not known. Statistics are stored per user and service label, and every trophy's `unlockCondition` is compared after each event that sets one. diff --git a/docs/dev/debt/libSceNpUtility.md b/docs/dev/debt/libSceNpUtility.md new file mode 100644 index 0000000000..f163571610 --- /dev/null +++ b/docs/dev/debt/libSceNpUtility.md @@ -0,0 +1,22 @@ +# libSceNpUtility technical debt + +### Unknown function info + +- [sceNpAppInfoIntAbortRequest, sceNpAppInfoIntCheckAvailability, sceNpAppInfoIntCheckAvailabilityA, sceNpAppInfoIntCheckAvailabilityAll, sceNpAppInfoIntCheckAvailabilityAllA and sceNpAppInfoIntCheckServiceAvailability](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpAppInfoIntCheckServiceAvailabilityA, sceNpAppInfoIntCheckServiceAvailabilityAll, sceNpAppInfoIntCheckServiceAvailabilityAllA, sceNpAppInfoIntCreateRequest, sceNpAppInfoIntDestroyRequest and sceNpAppInfoIntFinalize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpAppInfoIntInitialize, sceNpAppLaunchLink2IntAbortRequest, sceNpAppLaunchLink2IntCreateRequest, sceNpAppLaunchLink2IntDestroyRequest, sceNpAppLaunchLink2IntFinalize and sceNpAppLaunchLink2IntGetCompatibleTitleIdList](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpAppLaunchLink2IntGetCompatibleTitleIdNum, sceNpAppLaunchLink2IntInitialize, sceNpAppLaunchLinkIntAbortRequest, sceNpAppLaunchLinkIntCreateRequest, sceNpAppLaunchLinkIntDestroyRequest and sceNpAppLaunchLinkIntFinalize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpAppLaunchLinkIntGetCompatibleTitleIdList, sceNpAppLaunchLinkIntGetCompatibleTitleIdNum, sceNpAppLaunchLinkIntInitialize and sceNpBandwidthTestShutdownWithDetailedInfo](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpLookupCreateTitleCtxA, sceNpLookupNetAbortRequest and sceNpLookupNetCensorComment](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpLookupNetConvertJidToNpId, sceNpLookupNetConvertNpIdToJid, sceNpLookupNetCreateRequest, sceNpLookupNetCreateTitleCtx, sceNpLookupNetDeleteRequest, sceNpLookupNetDeleteTitleCtx and sceNpLookupNetInit](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpLookupNetInitWithFunctionPointer, sceNpLookupNetInitWithMemoryPool, sceNpLookupNetIsInit, sceNpLookupNetNpId, sceNpLookupNetSanitizeComment, sceNpLookupNetSetTimeout and sceNpLookupNetTerm](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpServiceChecker2IntAbortRequest, sceNpServiceChecker2IntCreateRequest, sceNpServiceChecker2IntDestroyRequest and sceNpServiceChecker2IntFinalize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpServiceChecker2IntGetServiceAvailability, sceNpServiceChecker2IntGetServiceAvailabilityA, sceNpServiceChecker2IntInitialize, sceNpServiceChecker2IntIsSetServiceType and sceNpServiceCheckerIntAbortRequest](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpServiceCheckerIntCreateRequest, sceNpServiceCheckerIntDestroyRequest, sceNpServiceCheckerIntFinalize, sceNpServiceCheckerIntGetAvailability, sceNpServiceCheckerIntGetAvailabilityList and sceNpServiceCheckerIntInitialize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpServiceCheckerIntIsCached, sceNpTitleMetadataIntAbortRequest, sceNpTitleMetadataIntCreateRequest, sceNpTitleMetadataIntDeleteRequest, sceNpTitleMetadataIntGetInfo, sceNpTitleMetadataIntGetNpTitleId and sceNpUtilityInit](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceNpUtilityTerm and sceNpWordFilterCreateTitleCtxA](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. + +### Functional + +- [NpLookup and NpWordFilter](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - offline only, not measured on a PS5: the argument checks and their order, the limit of 32 title contexts and 32 requests, the word filter id bases `0x01000001` and `0x02000001`, the 0x400-byte comment limit and single-use requests (`INVALID_TYPE`) follow shadPS4's [lookup](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L41-L222) and [word filter](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L705-L909) calls. `sceNpLookupNpId`, `sceNpWordFilterCensorComment` and `sceNpWordFilterSanitizeComment` then finish with `SCE_NP_ERROR_SIGNED_OUT` and write no NpId or comment; shadPS4's word filter accepts every comment as a stand-in for the server, so its result is not used +- [NpLookup and NpWordFilter waits and timeouts](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - IDs here carry no service: on the console, measured on firmware 13.42's module in an emulator (#10640), each ID holds its service in its high bits (Lookup requests `0x2xxxxxxx` and title contexts `0x4xxxxxxx`, word filter `0x02xxxxxx` and `0x04xxxxxx`), and `SetTimeout`, `AbortRequest`, `PollAsync` and `WaitAsync` find the object by those bits whichever service's call is made; here each call finds only its own service's IDs, which keep the ranges above. The timeouts are checked as measured and then have no effect, since no request reaches a server; the console stores them. A wait on a request deleted meanwhile returns `INVALID_ID`, unmeasured. The measured checks are followed: a null result first (`0x80550704`), a synchronous request (`0x80550307` for Lookup, `INVALID_ID` for the word filter), the timeout bounds and the 0x18-byte async parameter diff --git a/docs/dev/debt/libSceNpWebApi.md b/docs/dev/debt/libSceNpWebApi.md new file mode 100644 index 0000000000..af76876d59 --- /dev/null +++ b/docs/dev/debt/libSceNpWebApi.md @@ -0,0 +1,8 @@ +# libSceNpWebApi technical debt + +### Unknown function info + +- [sceNpWebApiInitialize, sceNpWebApiTerminate, sceNpWebApiCreateHandle, sceNpWebApiDeleteHandle, sceNpWebApiCreatePushEventFilter, sceNpWebApiDeletePushEventFilter](../../../core/libs/prx/libSceNpWebApi/Export.cpp) (libSceNpWebApi, PS4) - signatures, id ranges (library contexts 1 to 0x7fff, handles and filters below 0xf0000000) and errors (`LIB_CONTEXT_NOT_FOUND`, `HANDLE_NOT_FOUND`, `PUSH_EVENT_FILTER_NOT_FOUND`, `INVALID_ARGUMENT` for a missing filter parameter) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/np/np_web_api/np_web_api_internal.cpp); they are not checked on a console. The libHttp context and pool size are kept but not used, since no request is ever sent. Limbo (CUSA01664) creates a context, a handle and filters when its game starts. +- [sceNpWebApiCreateServicePushEventFilter](../../../core/libs/prx/libSceNpWebApi/Export.cpp) (libSceNpWebApi, PS4) - a filter for a PSN service needs the signed-in user, and no user is signed in to PSN, so a valid call returns `SIGNED_IN_USER_NOT_FOUND` after the context and handle checks, as shadPS4 does with its network disabled. `sceNpWebApiDeleteServicePushEventFilter` therefore never finds a filter. +- [sceNpWebApiCreateContext, sceNpWebApiDeleteContext, sceNpWebApiCreateRequest, sceNpWebApiDeleteRequest, sceNpWebApiAbortRequest, sceNpWebApiSendRequest](../../../core/libs/prx/libSceNpWebApi/Export.cpp) (libSceNpWebApi, PS4) - the online id is not looked up: as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/np/np_web_api/np_web_api_internal.cpp), a user context belongs to the initial user, so a library context holds at most one. With no user signed in to PSN, `sceNpWebApiSendRequest` returns `NOT_SIGNED_IN` unless the request is aborted. Id layouts and the delete, abort and not-found results match an offline PS4 Pro probe on firmware 10.71 (#9797); other checks follow shadPS4. That homebrew probe, with a non-retail SDK version, accepted method 99, so `INVALID_ARGUMENT` for a method after DELETE from SDK 2.50 is unverified. `sceNpWebApiTerminate` also deletes user contexts and requests; shadPS4 stops after the first user context. +- [sceNpWebApiRegisterPushEventCallback, sceNpWebApiUnregisterPushEventCallback, sceNpWebApiRegisterServicePushEventCallback, sceNpWebApiUnregisterServicePushEventCallback](../../../core/libs/prx/libSceNpWebApi/Export.cpp) (libSceNpWebApi, PS4) - registered callbacks are never called, since no push event arrives. A null function is rejected from SDK 1.00 and the filter must exist in the library context from SDK 2.50, as in shadPS4; this is not checked on a console. No service filter can be created, so from SDK 2.50 a service callback returns `SERVICE_PUSH_EVENT_FILTER_NOT_FOUND`. Callback ids come from the counter of handles and filters. diff --git a/docs/dev/debt/libSceNpWebApi2.md b/docs/dev/debt/libSceNpWebApi2.md index cc65efef68..7c73e05da4 100644 --- a/docs/dev/debt/libSceNpWebApi2.md +++ b/docs/dev/debt/libSceNpWebApi2.md @@ -11,6 +11,6 @@ - [sceNpWebApi2InitializeForPresence, sceNpWebApi2IntInitialize and sceNpWebApi2IntInitialize2](../../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2) - create a library context like `sceNpWebApi2Initialize`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/8d50471b7dc141dd700b51276b8edd1f293bce26/src/core/libraries/np/np_web_api2/np_web_api2.cpp#L174-L222) does on the PS4: the argument structures (32 and 40 bytes) and the `struct_size` check that returns `SCE_NP_WEBAPI2_ERROR_INVALID_ARGUMENT` follow its [declarations](https://github.com/shadps4-emu/shadPS4/blob/8d50471b7dc141dd700b51276b8edd1f293bce26/src/core/libraries/np/np_web_api2/np_web_api2.h#L15-L31). The context type shadPS4 records (2 for the internal initializers, 3 for presence), the context name and the push configuration group are not kept, and the 0x8000 context limit is not applied. Not measured on a PS5 - [sceNpWebApi2CreateMultipartRequest and sceNpWebApi2SendMultipartRequest](../../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpWebApi2.c). The signatures follow the PS4 implementations in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_web_api2/np_web_api2.cpp#L52-L221) and [its multipart sender](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/np/np_web_api2/np_web_api2.cpp#L388-L399); opaque struct pointers use `const void*` or `void*` to preserve source constness. These prototypes are not measured on a console. - [sceNpWebApi2IntPushEventCreateDestPs4CtxIndFilter](../../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2) - no credible prototype was found; it is declared without parameters solely to resolve known imports and makes no ABI claim. -- [sceNpWebApi2PushEventAbortHandle](../../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2, NID `1OLgvahaSco`) - the two signed 32-bit handle arguments and return type follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/443f022450f6857256e780daa3d5a14029a748f7/src/core/libraries/np/np_web_api2/np_web_api2.cpp) (PS4); the PS5 signature and abort behavior are unverified. PPSA01736 imports it. An unknown library context throws, as in `sceNpWebApi2PushEventSetHandleTimeout`. The handle is not checked and nothing is aborted, as push event handles are not tracked and nothing waits on a handle offline -- [sceNpWebApi2GetMemoryPoolStats](../../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2) - imported by PPSA21703's Unity PSN plugin. The signature `(int libCtxId, stats*)` and the layout of the Http2 statistics above (measured on a PS5 for Http2, Ssl and Net) are assumed for this library, not measured. The pool size is the one given to the initializer that created the context, and the peak and current use are 0. The error codes are unknown, so a null output or an unknown context throws -- [sceNpWebApi2PushEventSetHandleTimeout](../../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2) - imported by PPSA01317, which fails to load without it. The signature `(int libCtxId, int handleId, uint32_t timeout)` follows `sceNpWebApiSetHandleTimeout` of the PS4 libSceNpWebApi in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/np/np_web_api.cpp) and is unverified on the PS5. An unknown library context throws, as in `sceNpWebApi2GetMemoryPoolStats`, since its error code is unknown. The handle is not checked, as push event handles are not tracked (`sceNpWebApi2PushEventDeleteHandle` accepts any handle), and the timeout is not kept, as nothing waits on a handle offline +- [Push handle lifecycle](../../../core/libs/prx/libSceNpWebApi2/Export.cpp): PS5 create/delete/abort signatures and invalid-context/invalid-handle errors are confirmed. Handles track ownership, timeout and abort state; deletion and context termination remove them. Network push operations and cancellation of active requests remain unimplemented. +- [sceNpWebApi2GetMemoryPoolStats](../../../core/libs/prx/libSceNpWebApi2/Export.cpp): PS5 confirms the context/statistics arguments and 32-byte output. An invalid context returns `0x80553404` without changing output; a valid context with null output succeeds. Allocator accounting remains absent: peak and current usage still report zero instead of native pool allocations. +- [sceNpWebApi2PushEventSetHandleTimeout](../../../core/libs/prx/libSceNpWebApi2/Export.cpp): PS5 confirms three 32-bit arguments, ownership validation and storing the entire timeout value without range checks. The stored timeout is not consumed because asynchronous push operations are unimplemented. diff --git a/docs/dev/debt/libSceOpusCeltDec.md b/docs/dev/debt/libSceOpusCeltDec.md index 66de7596b2..4ba1fc6999 100644 --- a/docs/dev/debt/libSceOpusCeltDec.md +++ b/docs/dev/debt/libSceOpusCeltDec.md @@ -3,8 +3,9 @@ ### Unknown function info - [sceOpusCeltDecCreate, sceOpusCeltDecCtl, sceOpusCeltDecDecodeFloat, sceOpusCeltDecDecodeFloatWithPriority, sceOpusCeltDecDecodeWithPriority](../../../core/libs/prx/libSceOpusCeltDec/Export.cpp) (libSceOpusCeltDec) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceOpusCeltDec.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. -- [sceOpusCeltDecCreateEx, sceOpusCeltDecDecode, sceOpusCeltDecDestroy, sceOpusCeltDecGetSize, sceOpusCeltDecInitialize, sceOpusCeltDecTerminate](../../../core/libs/prx/libSceOpusCeltDec/Export.cpp) (libSceOpusCeltDec) - imported by PPSA99001. Their signatures are assumed to match libSceOpusDec's in [ProsperoRadio](https://github.com/blackbearreloaded/ProsperoRadio/blob/2c4029c3a8f43e9e1dfef98374d94d99b14d745d/src/opus_decoder.cpp), and the 640-byte state size, context handling and byte-count return are taken from [libSceOpusDec](../../../core/libs/prx/libSceOpusDec/Export.cpp); none of them was measured on a console. PPSA99001 opens this decoder only as a fallback when libSceOpusDec reports a retryable error for a CELT packet, which libSceOpusDec here never returns, so the fallback is not reached +- [sceOpusCeltDecTerminate](../../../core/libs/prx/libSceOpusCeltDec/Export.cpp): the non-null path forwards the AJM finalization result, but the null path returns without setting the return register. Its source return type cannot be established from this callee alone. The existing integer return is retained. ### Functional -- [sceOpusCeltDecDecode](../../../core/libs/prx/libSceOpusCeltDec/Export.cpp) (libSceOpusCeltDec) decodes with the FFmpeg decoder of [libSceOpusDec](../../../core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp) and shares its limits. Packets whose TOC configuration is below 16 (SILK and hybrid) throw: whether the console decodes them or returns an error code is unknown. Contexts and decoder states are separate from libSceOpusDec's +- [libSceOpusCeltDec](../../../core/libs/prx/libSceOpusCeltDec/Export.cpp): the supplied module confirms the six exported argument lists, read-only context inputs, 640-byte state allocation, mono/stereo channels, five sample rates, byte-sized decode capacity and byte-count return. The implementation still keeps decoder state outside guest memory and uses host contexts instead of AJM contexts. AJM error propagation, resource ownership and termination with live decoders are not implemented. Wrapper validation errors and their precedence are recovered. +- [sceOpusCeltDecDecode](../../../core/libs/prx/libSceOpusCeltDec/Export.cpp): the original forwards packets to AJM codec 16 without a local CELT-only check. The backend response to SILK/hybrid packets is unresolved; packets whose TOC configuration is below 16 still throw. Decoding uses the bundled libopus decoder of [OpusDecoderLibrary](../../../core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp) and shares its limits. Packet loss concealment and asynchronous AJM execution are not implemented. diff --git a/docs/dev/debt/libSceOpusCeltEnc.md b/docs/dev/debt/libSceOpusCeltEnc.md index 6f232c9cee..6ef2fceb48 100644 --- a/docs/dev/debt/libSceOpusCeltEnc.md +++ b/docs/dev/debt/libSceOpusCeltEnc.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceOpusCeltEncCreate, sceOpusCeltEncCreateEx, sceOpusCeltEncCtl, sceOpusCeltEncDestroy, sceOpusCeltEncEncode, sceOpusCeltEncEncodeFloat, sceOpusCeltEncEncodeFloatWithPriority, sceOpusCeltEncEncodeWithPriority, sceOpusCeltEncGetSize, sceOpusCeltEncInitialize and sceOpusCeltEncTerminate](../../../core/libs/prx/libSceOpusCeltEnc/Export.cpp) (libSceOpusCeltEnc) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceOpusCeltEnc.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceOpusCeltEncTerminate](../../../core/libs/prx/libSceOpusCeltEnc/Export.cpp): the context-ID pointer is confirmed. The null-input path does not define the return register, so an integer return contract is not established. The current integer return declaration is retained because changing it to `void` would require altering the protected stub body, which contains `return 0`. diff --git a/docs/dev/debt/libSceOpusDec.md b/docs/dev/debt/libSceOpusDec.md index fa6aa1dfd1..d98513d94b 100644 --- a/docs/dev/debt/libSceOpusDec.md +++ b/docs/dev/debt/libSceOpusDec.md @@ -6,6 +6,4 @@ ### Functional -- [libSceOpusDec](../../../core/libs/prx/libSceOpusDec/Export.cpp) uses FFmpeg for mono/stereo signed-16 decoding at the creation rates the research lists: 8000, 12000, 16000, 24000 and 48000 Hz. The six signatures, 640-byte opaque state allocation and byte-count return follow [public PS5 decoder research](https://github.com/blackbearreloaded/ps5-hardware-audio-decoding/blob/26be5a12f92b2cdc67f8a9e082fd69ae0e9a35a2/docs/DECODING.md#direct-opus-abi) and [ProsperoRadio](https://github.com/blackbearreloaded/ProsperoRadio/blob/2c4029c3a8f43e9e1dfef98374d94d99b14d745d/src/opus_decoder.cpp). Console PCM equivalence and error codes are unverified. Other sample rates, packet loss concealment, invalid inputs and termination with live decoders throw. Context identifiers and host decoder state are private registries; the firmware's opaque state layout and error recovery are unknown. -- [sceOpusDecDecode](../../../core/libs/prx/libSceOpusDec/Export.cpp) (libSceOpusDec) returns exactly the packet's duration, as libopus does. FFmpeg resamples narrowband SILK from 8 kHz and holds back 24 samples of the first narrowband packet after the decoder starts or after a packet of another bandwidth, releasing them with the next packet that is not narrowband. The decoder keeps FFmpeg's output in order and pads the start with silence, so such a packet begins with 24 samples (0.5 ms) of silence and the output after it is 24 samples late; libopus has no such gap -- [sceOpusDecDecode](../../../core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp) (libSceOpusDec, libSceOpusCeltDec) decodes at 48000 Hz and converts to a lower creation rate with FFmpeg's resampler, where libopus decodes at that rate directly. The resampler's filter delay is padded with silence at the start of the stream like the narrowband delay above, so the output is late by that delay and is not sample-identical to libopus; which of the two the console matches is not measured +- [libSceOpusDec](../../../core/libs/prx/libSceOpusDec/Export.cpp) uses bundled libopus 1.6.1 for mono/stereo signed-16 decoding at the creation rates the research lists: 8000, 12000, 16000, 24000 and 48000 Hz. The supplied module confirms the exported arguments, read-only context inputs, 640-byte state size and creation rates. Validation errors and their precedence are recovered. Console PCM equivalence, AJM failures, context ownership and termination with live decoders remain unimplemented. Packet loss concealment still throws; decoder state is kept in host registries instead of the native guest state layout. diff --git a/docs/dev/debt/libSceOpusSilkEnc.md b/docs/dev/debt/libSceOpusSilkEnc.md index 3e129c1e1f..266dfe90fd 100644 --- a/docs/dev/debt/libSceOpusSilkEnc.md +++ b/docs/dev/debt/libSceOpusSilkEnc.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceOpusSilkEncCancel, sceOpusSilkEncCreate, sceOpusSilkEncCreateEx, sceOpusSilkEncCtl, sceOpusSilkEncDestroy, sceOpusSilkEncEncode, sceOpusSilkEncEncodeFloat, sceOpusSilkEncEncodeFloatWithPriority, sceOpusSilkEncEncodeFloatWithPriorityAndTimeout, sceOpusSilkEncEncodeWithPriority, sceOpusSilkEncEncodeWithPriorityAndTimeout, sceOpusSilkEncGetSize, sceOpusSilkEncInitialize and sceOpusSilkEncTerminate](../../../core/libs/prx/libSceOpusSilkEnc/Export.cpp) (libSceOpusSilkEnc) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceOpusSilkEnc.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceOpusSilkEncTerminate](../../../core/libs/prx/libSceOpusSilkEnc/Export.cpp): the context-ID pointer is confirmed. The null-input path does not define the return register, so an integer return contract is not established. The current integer return declaration is retained because changing it to `void` would require altering the protected stub body, which contains `return 0`. diff --git a/docs/dev/debt/libScePad.md b/docs/dev/debt/libScePad.md index c52607d8ee..911bec0271 100644 --- a/docs/dev/debt/libScePad.md +++ b/docs/dev/debt/libScePad.md @@ -1,166 +1,33 @@ # libScePad technical debt +### Silent stubs + +- [scePadEnableExtensionPort](../../../core/libs/prx/libScePad/Export.cpp) returns success without changing extension-port state. The boolean argument is recovered; connected-device behavior is not modeled. + ### Unknown function info +- [libScePad profile and device-setting exports](../../../core/libs/prx/libScePad/Export.cpp) - profile getters, tuning, saved settings, USB state, shared output and virtual-device setting arguments are recovered. Hunt and Leonardo reset each consume only a handle; physical VR USB status consumes only an output pointer. Remote settings output is 64 bytes and license-controller information is 1224 bytes. Kernel ioctl payload fields remain incomplete. + +- [libScePad report and tracking exports](../../../core/libs/prx/libScePad/Export.cpp) - read, extended-read, tracker, UI, VR-controller and report argument registers are recovered. Feature reports use a byte report ID and 64-bit byte count, capped at 64. VR trigger effects consume a 56-byte record; the dual-controller call uses two handles and a mask with records at offsets 8 and 64. Remaining payload field layouts use incomplete declarations, including extended samples and kernel-owned extension reports. + - [scePadUnknown_n3kSX62fgNo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad, NID `n3kSX62fgNo`) - the name is unknown, and the 0 returned after `scePadInit` is inferred, not measured. In the OS 10.01 system module the function takes no arguments, returns `0x80920005` before `scePadInit`, and otherwise sends a per-process value of 1 to ShellCore over Mbus, returning `0x809200ff` if that call fails and 0 if it succeeds, with or without an open handle. The #3177 probe measured `0x809200ff` in a process that is not a game; a game process is assumed to get 0. The ShellCore setting it changes is unknown and not modelled -- [scePadSetAngularVelocityDeadbandState](../../../core/libs/prx/libScePad/src/PadState.cpp) (libScePad) - with the deadband on, each angular velocity axis whose magnitude is below 0.017453292 rad/s (1 degree per second) reads as 0, before the orientation is integrated; it starts off. Threshold, per-axis test and default follow [duaLib](https://github.com/WujekFoliarz/duaLib/blob/03bad1bea2a36561b520846776f2a07ced6773e0/src/source/duaLib.cpp#L44), which reimplements the PC `libScePad.dll`; the console's threshold is not measured, and the deadband is kept per library rather than per handle since one pad handle exists -- [scePadOpen and scePadOpenExt](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - special ports (type 2) return `DEVICE_NOT_CONNECTED` (0x80920007) because the host has no special controller, and `scePadGetHandle` has no handle for them; whether the console opens a handle and reports the device as disconnected later is unknown. scePadOpenExt handles only special ports. PPSA01317 passes a 16-byte param that names the device by vendor and product id (`b7 0e 04 0e ...` for 0x0eb7:0x0e04) and skips the user on any negative result. Other port types throw -- [scePadVrControllerReadState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad, NID `iA-DdobUen8`) - the name hashes to the imported NID; the `(int handle, void* state)` signature follows [PS5VR](https://github.com/theghostonline/PS5VR/blob/0e408909ce7a39891a2e06eee3438d07a09e11e3/app/src/app_vr.c#L326-L369) (PPSA99177), which passes a PS VR2 Sense controller handle from `scePadOpen` type 3 or 4 and a 0x68-byte state it reads as buttons at 0, stick bytes at 4 and a connected byte at 0x40. The title calls it only after `sceHmd2Initialize` succeeds, which it does not here, so every call throws; the state layout is otherwise unknown -- PPSA07411 PS VR2 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [scePadVrControllerSetVibration, scePadVrControllerSetVibrationMode and scePadVrControllerReadCombinedState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) and [sceVrTracker2ResetLocalCoordinate](../../../core/libs/prx/libSceVrTracker2/Export.cpp) (libSceVrTracker2) +- [scePadSetAngularVelocityDeadbandState](../../../core/libs/prx/libScePad/src/PadState.cpp) (libScePad) - with the deadband on, each angular velocity axis whose magnitude is below 0.017453292 rad/s (1 degree per second) reads as 0, before the orientation is integrated; it starts off. Threshold, per-axis test and default follow [duaLib](https://github.com/WujekFoliarz/duaLib/blob/03bad1bea2a36561b520846776f2a07ced6773e0/src/source/duaLib.cpp#L44), which reimplements the PC `libScePad.dll`; the console's threshold is not measured, and the deadband is kept per port slot +- [PadQueueLegacyAudioHaptics, PadClearLegacyAudioHaptics](../../../core/libs/prx/libScePad/src/PadState.cpp) (libScePad) - PadQueueLegacyAudioHaptics and PadClearLegacyAudioHaptics route legacy audio haptics only to port 0; multi-port queueing is not modelled +- [scePadGetControllerInformation](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - scePadGetControllerInformation always reports connectedCount = 1 and connected = true; behaviour across multiple open controller handles is not measured on hardware +- [scePadOpen and scePadOpenExt](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - the special port (type 2) opens a handle per user (`PAD_SPECIAL_HANDLE` + slot, 16 slots for the console's 16 accounts, a 17th user gets the unknown-user code 0x809b0081) that every pad call accepts: reads give the rest record (sticks 0x80, orientation w 1, acceleration y 1, `connected` false), information is zeroed, extended information has device class -1, setters succeed without effect. Not measured: the console's own handle values (fresh per open), a PS5, and the calls the PS4 Pro survey (firmware 10.71, no special controller) did not cover; it measured open, `ALREADY_OPENED`, `scePadGetHandle`, both reads, both informations, the five setters above, close, and that two logged-in users get independent handles. `scePadOpenExt` returns `DEVICE_NOT_CONNECTED` (0x80920007) for special ports, unmeasured; PPSA01317 skips a user on any negative result. `scePadOpenExt` throws for other port types +- [scePadOpen, scePadGetHandle, scePadRead and scePadReadState for the remote control port](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - whether a console without a media remote opens the system user's remote control port (type 16), returns `DEVICE_NOT_CONNECTED` or fills other fields is not measured. The host has no remote, so the port opens its own handle (`PAD_REMOTE_HANDLE`) that reads one zeroed entry with no buttons and `connected` false; the other pad calls reject it with `INVALID_HANDLE`. SDL's PS5 video driver maps this port's buttons to keys, so a remote that read the DualSense turned Cross into Return (Raptor PPSA94002). +- [scePadOpen](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - port types 3 and 4 throw: a payload process on a PS5 (firmware 13.60) let them past the type check, but it cannot open a port for its own logged-in user (0x809b0081), so what they open is unknown. The argument checks it did reach are followed: types 1, 5, 6, 255 and -1, and 16 for a user other than the system user, fail with `INVALID_ARG` before the user is looked at, user -1 with 0x809b0001 on types 0, 2, 3 and 4 and on index 1, and index 1 of a logged-in user with 0x809b0003; whether index 1 of types 3 and 4 fails the same way was not measured; the payload never tried type 16 with the system user (255), which opens the remote control port as below +- [scePadVrControllerReadState](../../../core/libs/prx/libScePad/Export.cpp) - the 104-byte output layout is recovered, including buttons, stick, triggers, touch fields, motion, connection state and timestamp. Touch-value semantics and kernel report production remain incomplete; the export still throws. +- [scePadVrControllerReadCombinedState](../../../core/libs/prx/libScePad/Export.cpp) - the two handle arguments and 128-byte output are recovered; output field semantics remain incomplete. - [scePadSetVibrationMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - modes 1 to 3 and 5 are accepted and stored. On a PS5 (firmware 13.60) the call returns `0x80920005` before `scePadInit`, then rejects a mode of 0 or below with `INVALID_ARG` before it checks the handle, and returns `INVALID_HANDLE` for a bad handle with any higher mode; whether an open handle accepts modes above 3 was not measured, since the payload could not open a pad; 5 is accepted because PPSA25286 passes it on its open handle at start-up; the meanings of modes 3 and 5 are unknown (PPSA21480 calls it with 3 right after `scePadOpen` and aborts on failure; [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Pad/Pad.cs) names only 1 and 2). The stored mode has no other effect - [scePadIsRemoteController](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - the name hashes to NID `fCWdlnmB1Ks`; the signature `(int handle, bool* remote)` comes from PPSA16064, which zeroes a byte, passes its address after `scePadGetControllerInformation` and ignores the result. The error codes follow the other pad calls. Controllers are local devices, so the answer is always false +- [scePadIsDS4Connected, scePadIsBlasterConnected and scePadIsMoveConnected](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - the `bool (int handle)` signatures are recovered; the library reports one DualSense to every title (see Touchpad resolution below), so no DualShock 4, Blaster or Move controller is ever behind a handle and each call answers false for any handle. The console's answer for an invalid handle is not measured - [scePadSetAngularVelocityBiasCorrectionState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - inferred signature `(int handle, bool enabled)` follows `scePadSetTiltCorrectionState` and `scePadSetAngularVelocityDeadbandState`; an invalid handle returns `PAD_ERROR_INVALID_HANDLE`, disabling is a no-op, and enabling throws because bias correction is not modelled - [scePadGetAccessibilityControllerState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - the value a console reports for a DualSense is not measured; the emulated pad, which is not an Access controller, reports 0. The name hashes to NID `zBsgF0q8DIM`. In the OS 10.01 system module the `(int handle, uint32_t* state)` function returns `0x80920005` before `scePadInit`, then `INVALID_ARG` for a NULL `state` and `INVALID_HANDLE` for a bad handle, and otherwise asks the pad driver for a per-device value of the device behind the handle: 0 when no device is behind it, 1 when the driver has no answer, else the driver's value. What the values mean is unknown -- [scePadSetTriggerEffectExt](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadLastLotusButtonData](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadReadStateExt](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetExtensionReport](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetExtensionUnitInfo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadEnableExtensionPort](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadEnableSpecificDeviceClass](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadOpenExt2](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDeviceOpen](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetDataInternal](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetUserColor](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDisableVibration](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetVibrationStrength](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetLightBarBaseBrightness](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetLightBarBlinking](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetIdleCount](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetControllerIdleCount](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetPowerSavingState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetAutoPowerOffCount](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadEnableUsbConnection](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadEnableAutoDetect](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadEnablePlayPauseEmulation](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDisableTrackingLedBatterySaver](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetLoginUserNumber](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetCapability](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadShareOutputData](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadShareOutputDataWithFunctionType](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetVibrationForce](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetVersionInfo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetControllerType](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDisableDataConversion](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetButtonRemappingInfo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetProcessPrivilegeOfButtonRemapping](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetForceIntercepted](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadIsLightBarBaseBrightnessControllable](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSwitchConnection](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDisableTriggerEffect](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetTriggerEffectStrength](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetTriggerEffectInternalState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetUserNumber](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDisableTransportButtons](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetLotusIrCodeSetStart](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetLotusIrCodeSetData](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetLotusIrCodeSetEnd](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetLotusIrCodeSetResult](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetLotusEnableIrKey](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetLotusDisableIrKey](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetUsbDetectionState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadUiReadState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadUiReadStateForTuning](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetStickTriggerTuningMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetStickTuningParam](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetTriggerTuningParam](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetProfileHunt](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetProfileHunt](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadResetHunt](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetProfileInfo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetActiveProfileInfo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDisableActiveProfileChange](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDisableActiveProfileChangeByDeviceId](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetActiveProfileSlot](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetSavedSettingInfo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetSavedSettingInfo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadVrControllerGetPhysicalUsbConnectionStatus](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadParseUniqueDataLeonardo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetControllerTypeLeonardo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetControllerTypeLeonardo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetProfileLeonardo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetProfileLeonardo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadResetLeonardo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetProfileEditMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetProfileEditMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetProfileEditModeLeonardo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetSelectButtonCustomizeMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetSelectButtonCustomizeMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetToggleAllReleaseLeonardo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadEnableControllerIndicator](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadEnableProfileIndicator](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetProfileIndicatorAnimation](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadIsCoPilot](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetIsCoPilot](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadRegisterCollaboInputMgrProcess](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetTelemetryInfo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetActiveTime](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetDeviceId](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetInfoByPortType](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadResetLightBarAll](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadResetLightBarAllByPortType](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetLightBarForTracker](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadReadForTracker](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadReadBlasterForTracker](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadResetOrientationForTracker](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetMotionTimerUnit](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetVrTrackingMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetMotionSensorPosition](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetSphereRadius](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadIsValidHandle](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadIsDS4Connected](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadIsBlasterConnected](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadIsMoveConnected](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetBluetoothAddress](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadIsMoveReproductionModel](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadReadAstonForTracker](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetAstonModel](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadMbusInit](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadMbusTerm](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadConnectPort](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDisconnectPort](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadDisconnectDevice](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetDeviceInfo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetProcessFocus](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadVertualDeviceAddDevice](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadVirtualDeviceAddDevice](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadVirtualDeviceDeleteDevice](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadVirtualDeviceInsertData](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadVirtualDeviceInsertTriggerInternalState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadVirtualDeviceGetRemoteSetting](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadVirtualDeviceSetTriggerEffectSetting](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadVirtualDeviceDisableButtonRemapping](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetShareButtonMaskForRemotePlay](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetPS4BcVibrationMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetVirtualDeviceComponentInformation](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadEnablePs4CompatibleMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetLicenseControllerInformation](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadReadHistory](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadStartRecording](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadStopRecording](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadRemoteControlInsertData](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadRemoteControlInsertDataForAston](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadGetFeatureReport2](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetFeatureReport2](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadSetMuteLed](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadBondUpdateInit](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadBondUpdateIsReady](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadBondUpdateStart](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadBondUpdateGetStatus](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadBondUpdateSendData](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadBondUpdateResetBond](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadBondUpdateGetReconnectStatus](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadBondUpdateTerm](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadControllerUpdateInit](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadControllerUpdateIsUpdateNeeded2](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadControllerUpdateIsReady](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadControllerUpdateStart](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadControllerUpdateGetStatus](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadControllerUpdateSendData](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadControllerUpdateResetController](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadControllerUpdateGetReconnectStatus](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature -- [scePadControllerUpdateTerm](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - unknown signature +- [scePadGetControllerType](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - the value a console reports for a DualSense is not measured; the emulated pad reports `PAD_CONTROLLER_TYPE_DUAL_SENSE` (2), following [duaLib](https://github.com/WujekFoliarz/duaLib/blob/master/src/include/duaLib.h)'s `s_SceControllerType` enum where `UNKNOWN = 0` and `DUALSENSE = 2`; special handles report 0 (`UNKNOWN`). The signature `(int handle, int32_t* type)` follows duaLib, while the validation order (checking initialization, null pointer, then handle) follows the library's other pad queries; duaLib does not validate null pointers and returns `PAD_ERROR_DEVICE_NOT_CONNECTED` for an invalid pad. +- [libScePad internal data and device control](../../../core/libs/prx/libScePad/Export.cpp) - kernel-owned version, telemetry, Lotus-button and update payload fields remain unresolved. Lotus-button parameter units and the three blinking byte meanings are unknown. Port information occupies 152 bytes; history records occupy 72 bytes; Leonardo unique data occupies 24 bytes. Their complete field semantics remain unresolved. +- [libScePad profile writes and virtual devices](../../../core/libs/prx/libScePad/Export.cpp) - profile writes take a handle, profile ID, profile pointer and a 32-bit result pointer; the profile payload direction and fields remain unresolved. Virtual-device creation takes an input record and an output ID pointer, whose width is not exposed by the wrapper. Component information copies 16 bytes; field meanings remain unknown. Virtual trigger state contains two 32-bit states and three control bytes, but the control semantics remain incomplete. ### Functional -- [Touchpad resolution](../../../core/libs/prx/libScePad/include/PadState.hpp) (libScePad) - the library reports a DualSense to every title, so the controller information reports its touchpad, 1920 by 1080 at 44.86 pixels per cm, and the host controller's fingers and the keyboard touch contacts are placed in that range. [duaLib](https://github.com/WujekFoliarz/duaLib/blob/0b39f92261e2ff0e77eb0dac00943be6970e0dbf/src/source/duaLib.cpp#L847-L849) reports these values for a DualSense and 1920 by 943 at 44 for a DualShock 4, and the Linux kernel's [hid-playstation](https://github.com/torvalds/linux/blob/cd4f494ba535e848e2d299707d363de84e61a518/drivers/hid/hid-playstation.c#L177-L178) holds 1920 by 1080 against 1920 by 942 for the DualShock 4. A DualShock 4 on the host reports through the DualSense range too, so its last touchpad rows are never reached. The console's values are not measured +- [Touchpad resolution](../../../core/libs/prx/libScePad/include/PadState.hpp) (libScePad) - the library reports a DualSense to every title, so the controller information reports its touchpad, 1920 by 1080 at 44.86 pixels per cm, and the host controller's fingers and the keyboard touch contacts are placed in that range. [duaLib](https://github.com/WujekFoliarz/duaLib/blob/0b39f92261e2ff0e77eb0dac00943be6970e0dbf/src/source/duaLib.cpp#L847-L849) reports these values for a DualSense and 1920 by 943 at 44 for a DualShock 4, and the Linux kernel's [hid-playstation](https://github.com/torvalds/linux/blob/cd4f494ba535e848e2d299707d363de84e61a518/drivers/hid/hid-playstation.c#L177-L178) holds 1920 by 1080 against 1920 by 942 for the DualShock 4. A DualShock 4 on the host reports through the DualSense range too, so its last touchpad rows are never reached. A PS5 on firmware 13.40 reported 1920 by 1080 at 44.86 and stick dead zones of 13 to a retail title diff --git a/docs/dev/debt/libScePlayGo.md b/docs/dev/debt/libScePlayGo.md index cfd6b6de0d..89f271596b 100644 --- a/docs/dev/debt/libScePlayGo.md +++ b/docs/dev/debt/libScePlayGo.md @@ -4,5 +4,5 @@ - [scePlayGoRequestNextChunk, scePlayGoSnapshot](../../../core/libs/prx/libScePlayGo/Export.cpp) (libScePlayGo) - their names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePlayGo.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim - [scePlayGoSetLanguageMask](../../../core/libs/prx/libScePlayGo/Export.cpp) (libScePlayGo) - stores the mask that `scePlayGoGetLanguageMask` then reports, and returns `SCE_PLAYGO_ERROR_BAD_HANDLE` for another handle, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/8d50471b7dc141dd700b51276b8edd1f293bce26/src/core/libraries/playgo/playgo.cpp#L346-L359) does on the PS4. shadPS4 also returns `SCE_PLAYGO_ERROR_NOT_INITIALIZED` before `scePlayGoInitialize` and starts from the system language's mask; here the mask starts with every language and initialization is not tracked. Not measured on a PS5 -- [libScePlayGo](../../../core/libs/prx/libScePlayGo/Export.cpp) - the chunk set comes from `playgo-chunkdefs.xml` (every listed chunk plus chunks 0 through the default chunk), otherwise from `sce_sys/playgo-chunk.dat`, whose `plgx` header is read as holding the chunk count as a 16-bit value at offset 0x0A and ids 0 to count - 1 are taken as valid. The field was identified on four dumps, where exactly that many 0x20-byte entries follow from offset 0x100 (RIDE 4 PPSA01599: 35, Persona 5 Royal PPSA05110: 25, LEGO Horizon Adventures PPSA14632 and Kena PPSA01802: 1); the rest of the format is not decoded and ids are assumed to be contiguous. Every listed chunk is reported as installed (`LOCAL_FAST`). Without either file, a package with `sce_sys/playgo-scenario.json` (PPSA01748, whose dump lists no chunks) accepts every chunk id as installed, and `scePlayGoGetChunkId` lists ids 0 to 999. A title that builds its set of installed chunks from that list skips any chunk it does not see. The console's chunk limit is not known here, and a package with more chunks would see ids from 1000 up as not listed. A package with no `playgo-*` file in `sce_sys` (PPSA03001, PPSA03490 and PPSA01285 here) accepts chunk 0; querying any other id throws with a message naming the missing `playgo-chunk.dat` and `playgo-scenario.json`, since the incomplete dump cannot establish whether that id exists; `APS5_PLAYGO_UNLISTED_CHUNKS=installed` answers every such id as installed (`LOCAL_FAST`, which PPSA15388 needs to load its saves) and `=absent` answers `SCE_PLAYGO_ERROR_BAD_CHUNK_ID` (which ends PPSA03001's id walk), as the user's choice for that dump. A `sce_sys` that holds other `playgo-*` files (`playgo-ficm.dat`, `playgo-hash-table.dat`) without `playgo-chunk.dat` or `playgo-scenario.json`, or a `playgo-chunk.dat` without a `plgx` header and a nonzero count, throws naming the file, because no such dump is known. These answers are unverified on a console, as is the console's answer for an id outside a package's chunk table. -- [libScePlayGo](../../../core/libs/prx/libScePlayGo/Export.cpp) - `scePlayGoGetSupportedOptionalChunk` and `scePlayGoGetOptionalChunk` report every bit set for any `type`, i.e. every optional chunk (language pack) supported and installed, like `scePlayGoGetLanguageMask`. The bit layout and the values of `type` are not known; PPSA01748 calls both with type 0 and hides every text language listed in `playgo-scenario.json` `chunkSupportedLanguages` when the bitmask is 0. The console's answer for a package that ships no optional chunks is unverified. +- [libScePlayGo](../../../core/libs/prx/libScePlayGo/Export.cpp) - the chunk set comes from `playgo-chunkdefs.xml` (every listed chunk plus chunks 0 through the default chunk), otherwise from `sce_sys/playgo-chunk.dat`, whose `plgx` header is read as holding the chunk count as a 16-bit value at offset 0x0A and ids 0 to count - 1 are taken as valid. The field was identified on four dumps, where exactly that many 0x20-byte entries follow from offset 0x100 (RIDE 4 PPSA01599: 35, Persona 5 Royal PPSA05110: 25, LEGO Horizon Adventures PPSA14632 and Kena PPSA01802: 1); the rest of the format is not decoded and ids are assumed to be contiguous. Every listed chunk is reported as installed (`LOCAL_FAST`). Without either file, a package with `sce_sys/playgo-scenario.json` (PPSA01748, whose dump lists no chunks) accepts ids 0 to 999 as installed, and `scePlayGoGetChunkId` lists the same ids. A title that builds its set of installed chunks from that list skips any chunk it does not see. The console's chunk limit is not known here, so an id from 1000 up is answered like an unlisted id of a package with no `playgo-*` file: it throws unless `APS5_PLAYGO_UNLISTED_CHUNKS` answers it (`=absent` ends PPSA02630's id walk, which otherwise asks for every id until one is rejected and wraps the 16-bit id). A package with no `playgo-*` file in `sce_sys` (PPSA03001, PPSA03490 and PPSA01285 here) accepts chunk 0; querying any other id throws with a message naming the missing `playgo-chunk.dat` and `playgo-scenario.json`, since the incomplete dump cannot establish whether that id exists; `APS5_PLAYGO_UNLISTED_CHUNKS=installed` answers every such id as installed (`LOCAL_FAST`, which PPSA15388 needs to load its saves) and `=absent` answers `SCE_PLAYGO_ERROR_BAD_CHUNK_ID` (which ends PPSA03001's id walk), as the user's choice for that dump. A `sce_sys` that holds other `playgo-*` files (`playgo-ficm.dat`, `playgo-hash-table.dat`) without `playgo-chunk.dat` or `playgo-scenario.json`, or a `playgo-chunk.dat` without a `plgx` header and a nonzero count, throws naming the file, because no such dump is known. These answers are unverified on a console, as is the console's answer for an id outside a package's chunk table. +- [libScePlayGo](../../../core/libs/prx/libScePlayGo/Export.cpp) - `scePlayGoGetSupportedOptionalChunk` and `scePlayGoGetOptionalChunk` report every bit set for `type` 0, 1 or 2, i.e. every optional chunk (language pack) supported and installed, like `scePlayGoGetLanguageMask`. Native requests confirm types 0 through 2 and an eight-byte result; their meanings and bit layout are still determined by the external service, not this module; PPSA01748 calls both with type 0 and hides every text language listed in `playgo-scenario.json` `chunkSupportedLanguages` when the bitmask is 0. The console's answer for a package that ships no optional chunks is unverified. diff --git a/docs/dev/debt/libScePlayGoDialog.md b/docs/dev/debt/libScePlayGoDialog.md index 7e06e1309c..53b4cd2147 100644 --- a/docs/dev/debt/libScePlayGoDialog.md +++ b/docs/dev/debt/libScePlayGoDialog.md @@ -2,4 +2,4 @@ ### Unknown function info -- [scePlayGoDialogGetResult](../../../core/libs/prx/libScePlayGoDialog/Export.cpp) (libScePlayGoDialog) - checks that the dialog has reached STATUS_FINISHED and returns 0x80B80005 (COMMON_DIALOG_ERROR_NOT_FINISHED) when called before the dialog completes, following the other common dialog modules in the tree (MsgDialog, SigninDialog, PlayerSelectionDialog, SaveDataDialog). shadPS4's scePlayGoDialogGetResult is a dummy that always returns OK and provides no evidence for either value; the exact return code and intermediate state check have not been verified against a real hardware trace or title measurement. +- [scePlayGoDialogOpen](../../../core/libs/prx/libScePlayGoDialog/Export.cpp) does not validate or copy the native nested parameters or query download progress. The host UI stub immediately reports completion; service-driven failures and transitions remain unimplemented. diff --git a/docs/dev/debt/libScePlayReady.md b/docs/dev/debt/libScePlayReady.md index f3b7a6a60c..c7677e9529 100644 --- a/docs/dev/debt/libScePlayReady.md +++ b/docs/dev/debt/libScePlayReady.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceBufferAlloc, sceBufferDup, sceBufferFree, sceBufferInit, sceBufferRealloc, scePlayReadyApiInitializeInitParams, scePlayReadyApiIsStarted, scePlayReadyApiStart, scePlayReadyApiStop, scePlayReadyApiVersion, scePlayReadyApiVersionString, scePlayReadyBufferFree, scePlayReadyBufferInit, scePlayReadyBufferMalloc, scePlayReadyBufferRealloc, scePlayReadyContentGetProperty, scePlayReadyContentSetProperty, scePlayReadyDomainCertEnumFini, scePlayReadyDomainCertEnumInit, scePlayReadyDomainCertEnumNext, scePlayReadyDomainCertFind, scePlayReadyDomainJoinGenerateChallenge, scePlayReadyDomainJoinProcessResponse, scePlayReadyDomainLeaveGenerateChallenge, scePlayReadyDomainLeaveProcessResponse, scePlayReadyEnvelopeClose, scePlayReadyEnvelopeGetSize, scePlayReadyEnvelopeInitializeRead, scePlayReadyEnvelopeOpen, scePlayReadyEnvelopeRead, scePlayReadyEnvelopeSeek, scePlayReadyFinalize, scePlayReadyFreeDecryptContext, scePlayReadyInitialize, scePlayReadyInitializeInitParams, scePlayReadyLicenseAcqGenerateAck, scePlayReadyLicenseAcqGenerateChallenge, scePlayReadyLicenseAcqGetAdditionalResponseData, scePlayReadyLicenseAcqProcessAckResponse, scePlayReadyLicenseAcqProcessResponse, scePlayReadyMemAlloc, scePlayReadyMemFree, scePlayReadyMemGetStats, scePlayReadyMemRealloc, scePlayReadyNewDecryptContext, scePlayReadyReaderBind, scePlayReadyReaderCommit, scePlayReadyReaderDecrypt, scePlayReadyReset and scePlayReadyStoreCleanup](../../../core/libs/prx/libScePlayReady/Export.cpp) (libScePlayReady) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePlayReady.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceBufferAlloc, sceBufferDup, sceBufferFree, sceBufferInit, sceBufferRealloc, scePlayReadyApiInitializeInitParams, scePlayReadyApiIsStarted, scePlayReadyApiStart, scePlayReadyApiStop, scePlayReadyApiVersion, scePlayReadyApiVersionString, scePlayReadyBufferFree, scePlayReadyBufferInit, scePlayReadyBufferMalloc, scePlayReadyBufferRealloc, scePlayReadyContentGetProperty, scePlayReadyContentSetProperty, scePlayReadyDomainCertEnumFini, scePlayReadyDomainCertEnumInit, scePlayReadyDomainCertEnumNext, scePlayReadyDomainCertFind, scePlayReadyDomainJoinGenerateChallenge, scePlayReadyDomainJoinProcessResponse, scePlayReadyDomainLeaveGenerateChallenge, scePlayReadyDomainLeaveProcessResponse, scePlayReadyEnvelopeClose, scePlayReadyEnvelopeGetSize, scePlayReadyEnvelopeInitializeRead, scePlayReadyEnvelopeOpen, scePlayReadyEnvelopeRead, scePlayReadyEnvelopeSeek, scePlayReadyFinalize, scePlayReadyFreeDecryptContext, scePlayReadyInitialize, scePlayReadyInitializeInitParams, scePlayReadyLicenseAcqGenerateAck, scePlayReadyLicenseAcqGenerateChallenge, scePlayReadyLicenseAcqGetAdditionalResponseData, scePlayReadyLicenseAcqProcessAckResponse, scePlayReadyLicenseAcqProcessResponse, scePlayReadyMemAlloc, scePlayReadyMemFree, scePlayReadyMemRealloc, scePlayReadyNewDecryptContext, scePlayReadyReaderBind, scePlayReadyReaderCommit, scePlayReadyReaderDecrypt, scePlayReadyReset and scePlayReadyStoreCleanup](../../../core/libs/prx/libScePlayReady/Export.cpp) (libScePlayReady) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePlayReady.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libScePlayerSelectionDialog.md b/docs/dev/debt/libScePlayerSelectionDialog.md index c4936f8740..96b3ef7efc 100644 --- a/docs/dev/debt/libScePlayerSelectionDialog.md +++ b/docs/dev/debt/libScePlayerSelectionDialog.md @@ -2,7 +2,7 @@ ### Silent stubs -- [libScePlayerSelectionDialog](../../../core/libs/prx/libScePlayerSelectionDialog/Export.cpp) - the player-selection dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled. `GetResult` writes only the first `int32` (`USER_CANCELED`); the rest of `ScePlayerSelectionDialogResult` (player list pointer and length) is not filled. EA SPORTS FC 27 (PPSA34015) fails to load `libScePlayerSelectionDialog.prx` without `Initialize`, `Open`, `Close`, `GetStatus`, `UpdateStatus` and `GetResult` (`unresolved ELF import` / `GetLastError: 127`) +- [libScePlayerSelectionDialog](../../../core/libs/prx/libScePlayerSelectionDialog/Export.cpp) shows no selection UI and immediately reports cancellation. Account discovery, selection and successful-result list delivery are not implemented. ### Unknown function info diff --git a/docs/dev/debt/libScePngDec.md b/docs/dev/debt/libScePngDec.md index 2eca53adb5..260202e2bf 100644 --- a/docs/dev/debt/libScePngDec.md +++ b/docs/dev/debt/libScePngDec.md @@ -1,6 +1,10 @@ # libScePngDec technical debt +### Functional + +- [libScePngDec](../../../core/libs/prx/libScePngDec/Export.cpp): the supplied module confirms the work-memory size formula, 16-byte handle alignment, the first 24 bytes of its context, and Create/QueryMemorySize validation order. The host now exposes these values but still allocates decoder working storage on the host instead of using the guest work region. +- [scePngDecDecode](../../../core/libs/prx/libScePngDec/Export.cpp): the original's accepted pixel format 132 remains unsupported and throws. The input bit-depth limit and 16-to-8-bit conversion follow [Prosper's PS5 3.20 module analysis](https://github.com/mattias800/prosper/blob/550886e3677e4a2ef81ad1603bf313f6a9696de2/prosper/src/hle/util/hle_pngdec.cpp#L412-L414), and the large-dimension image-info requirement and its validation order follow [Prosper's PS5 3.20 module analysis](https://github.com/mattias800/prosper/blob/550886e3677e4a2ef81ad1603bf313f6a9696de2/prosper/src/hle/util/hle_pngdec.cpp#L385-L396), not console measurements here. Malformed-image diagnostics and decoder scratch-memory behavior are not fully matched to the supplied module. + ### Unknown function info -- [scePngDecQueryMemorySize, scePngDecCreate](../../../core/libs/prx/libScePngDec/Export.cpp) (libScePngDec) - the work memory is a fixed 0x20 bytes, because decoding uses host memory; the PS5 module asks for `((max_image_width + 0x47) & ~7) * (8 << attribute)` plus a constant, as read from the 3.20 module by prosper. A null parameter returns `0x80690003`; prosper returns `0x80690001` when it cannot read the parameter, which was not measured. Closes when: measured on a PS5 - [scePngDecDecodeWithInputControl](../../../core/libs/prx/libScePngDec/Export.cpp) (libScePngDec) - its name and NID are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePngDec.c); the no-parameter prototype follows [shadPS4's pngdec.h](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/libpng/pngdec.h#L76) and is not measured on a console. It is declared without parameters solely to resolve known imports; this is not an ABI claim. diff --git a/docs/dev/debt/libScePngEnc.md b/docs/dev/debt/libScePngEnc.md index 0ba7edc2bf..88f5089fbc 100644 --- a/docs/dev/debt/libScePngEnc.md +++ b/docs/dev/debt/libScePngEnc.md @@ -1,5 +1,5 @@ # libScePngEnc technical debt -### Unknown function info +### Functional -- [scePngEncEncode](../../../core/libs/prx/libScePngEnc/Export.cpp) (libScePngEnc) - whether rows may stay unfiltered when `filter_type` names a subset of the filters is unknown; as in shadPS4, they may only for 0 and for the all-filters mask +- [scePngEncEncode](../../../core/libs/prx/libScePngEnc/Export.cpp): native filter setup always includes the unfiltered candidate, including for partial masks. The host now permits it too; exact filter-selection ties, compressed bytes and partial-output behavior are not matched to the native encoder. Creation validation, work-memory size, 16-byte handle alignment and the context prefix now match the supplied module. Input-buffer alignment, pitch, dimensions, buffer capacity and selected-filter count now follow the native checks. Host scratch storage remains separately allocated. diff --git a/docs/dev/debt/libScePosixForWebKit.md b/docs/dev/debt/libScePosixForWebKit.md index 681a24a3d5..9c664a7afc 100644 --- a/docs/dev/debt/libScePosixForWebKit.md +++ b/docs/dev/debt/libScePosixForWebKit.md @@ -3,11 +3,15 @@ ### Unknown function info - [gethostbyname](../../../core/libs/prx/libScePosixForWebKit/Resolver.cpp) (libScePosixForWebKit) - numeric IPv4 forms, trailing whitespace and 64-bit accumulation follow FreeBSD 11.4 [inet_aton](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/inet/inet_addr.c); clearing the resolver error on numeric success follows [fakeaddr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/net/gethostnamadr.c). Exact PS5 behavior is unverified; hostname lookup still uses the host resolver -- [__wrap_access, __wrap_clock_gettime, __wrap_fchown, __wrap_getcwd, __wrap_getenv, __wrap_link, __wrap_lstat, __wrap_mmap, __wrap_readlink, __wrap_statvfs, __wrap_symlink, clock_gettime_np, getenv_np, gethostbyname_r (`scePosixForWebKit_gethostbyname_r`), mmap_np, msetname, pthread_setschedparam_np, setenv_np, sysinfo (`scePosixForWebKit_sysinfo`)](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) - names/NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePosixForWebKit.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); credible prototypes are unknown. These exports use renamed helpers; defining `gethostbyname_r` would interpose on the host function this library calls itself. All are parameterless import stubs, not ABI claims; callers must not use them until signatures are verified. +- [clock_gettime_np, getenv_np, gethostbyname_r (`scePosixForWebKit_gethostbyname_r`), mmap_np, msetname, pthread_setschedparam_np, setenv_np, sysinfo (`scePosixForWebKit_sysinfo`)](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) - names/NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePosixForWebKit.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); credible prototypes are unknown. These exports use renamed helpers; defining `gethostbyname_r` would interpose on the host function this library calls itself. All are parameterless import stubs, not ABI claims; callers must not use them until signatures are verified. +- [__wrap_fchown, __wrap_link, __wrap_readlink, __wrap_statvfs, __wrap_symlink](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) - parameterless throwing stubs. Under the `--wrap` convention assumed for the forwarding wrappers (see Functional) each would take the POSIX signature of the function it wraps, but the targets are in libkernel_sys (`fchown`, `link`, `readlink`, `symlink`) and libSceLibcInternal (`statvfs`), which no prx links; libkernel_sys's `fchown`, `readlink` and `symlink` also still throw for some arguments ### Functional +- [strcasestr](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) - console locale behavior is unmeasured. It follows FreeBSD 11.4's [strcasestr.c](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/string/strcasestr.c) in the guest's supported C locale; other guest locales are not implemented. - [mkstemp and isatty](../../../core/libs/prx/libScePosixForWebKit/Filesystem.cpp) (libScePosixForWebKit) follow FreeBSD's [temporary-file API](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/stdio/mktemp.c) and [terminal check](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/gen/isatty.c), without console verification. `mkstemp` replaces all trailing `X` characters, accepts a fixed name, and creates files through the guest path resolver with exclusive read/write access and mode 0600. Windows file permissions follow the existing kernel file implementation. `isatty` tests Windows console handles rather than all character devices; files, pipes and guest sockets return `ENOTTY`, closed descriptors return `EBADF`. - [arc4random](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) returns four bytes of host OS entropy (`getentropy`, `rand_s` on Windows), as `arc4random_buf` does, uniform over the 32-bit range like FreeBSD 11.4's RC4 keystream in [arc4random.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/arc4random.c). The console's generator and its reseeding are unknown; `arc4random_uniform`, `arc4random_stir` and `arc4random_addrandom` are not in the module's export set and are absent +- [pthread_setname_np](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) stores the guest thread name in libkernel's thread object, so `pthread_getname_np` returns it, and follows libkernel's `pthread_rename_np`: `EINVAL` (22) for a null thread or name, `ENAMETOOLONG` (63) from 32 bytes on, `0` otherwise, with the name truncated to 31 bytes when read back. It does not apply the Windows thread description or the job affinity that `scePthreadRename` sets, because those helpers are private to libkernel; the console's exact error numbers for this module are unverified - [fnmatch](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) follows FreeBSD 11 [fnmatch.c](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/gen/fnmatch.c) in the C locale: each byte is one character, `FNM_CASEFOLD` folds ASCII letters only, and bracket ranges compare byte values. `[:class:]` names are not character classes: as in FreeBSD 11 through 14, `[[:digit:]]` is the set of `[`, `:`, `d`, `i`, `g`, `t` followed by a literal `]` (classes were added to `fnmatch.c` after 14). Multibyte characters and locale collation are not modelled. A null pattern or string throws - [getservbyname](../../../core/libs/prx/libScePosixForWebKit/Resolver.cpp) (libScePosixForWebKit) searches a built-in copy of 37 lines of FreeBSD 11.4's [etc/services](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/etc/services) (ftp-data, ftp, ssh, telnet, smtp, domain, tftp, http, pop3, ntp, imap, https, rtsp, submission, imaps, pop3s with their aliases and protocols, in file order) the way [getservent.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/net/getservent.c) searches the file: case-sensitive name or alias, a null protocol takes the first match, the port in network byte order, the result in per-thread storage. The console's `/etc/services` is unknown, so any other service returns null without setting `errno`, as for a name the file lacks. `getaddrinfo` still resolves service names through the host's service database +- [__wrap_access, __wrap_clock_gettime, __wrap_getcwd, __wrap_getenv, __wrap_lstat and __wrap_mmap](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) assume, from the export names alone, the GNU ld [`--wrap`](https://sourceware.org/binutils/docs/ld/Options.html) convention (a reference to `` resolves to `__wrap_`): each takes its target's POSIX signature and forwards to libc's (`access`, `getcwd`, `getenv`) or libkernel's (`clock_gettime`, `lstat`, `mmap`) implementation, which sets `errno`. No PS5 module or WebKit call site was read to confirm that WebKit imports `__wrap_` in place of `` or that the console wrappers only forward. Any sandbox or path handling the console module adds is not modelled, and how the wrappers relate to `getenv_np`, `setenv_np`, `mmap_np` and `clock_gettime_np` (such as a separate environment that `setenv_np` fills) is unknown diff --git a/docs/dev/debt/libScePsml_debug.md b/docs/dev/debt/libScePsml_debug.md index a4cd5f3070..b8bdedf383 100644 --- a/docs/dev/debt/libScePsml_debug.md +++ b/docs/dev/debt/libScePsml_debug.md @@ -2,14 +2,13 @@ ### Unknown function info -- [scePsmlMfsrGetSharedResourcesInitRequirement, scePsmlMfsrCreateSharedResources, scePsmlMfsrReleaseSharedResources, scePsmlMfsrGetContextBufferRequirement800M3_2, scePsmlMfsrCreateContext800M3_2, scePsmlMfsrReleaseContext](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml) - signatures from the calls in PPSA08330 (0x5724b6-0x5727e8, 0xc8697) and PPSA03016 (0x43dcf4d-0x43dd69a): a `{block bytes, alignment, block count}` requirement, a 32-byte shared-resources parameter with the type at 0 and the `{address, bytes}` block list at 8 and 16, a context parameter with the block list at 8 and 16 and the shared-resources handle at 0x38, and 8-byte handles written through the first argument and passed back by value. The descriptor pointer at 0x28 is not read. The requirement of four 2 MiB blocks, 2 MiB aligned, is what PPSA08330 v06.006 allocates regardless of the returned count; Sony's values are unknown ([KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/libPsml.cpp) derives 52 blocks for type 0; its layout differs from these calls: it writes a 0x30-byte object through the first argument, where PPSA08330 passes one of two adjacent 8-byte handle globals, and reads the shared resources at context parameter offset 8, where both titles pass the block list). Only type 0 is accepted. The error codes for null pointers, short block lists and foreign handles are unknown, so those calls throw -- [scePsmlMfsr2CreateContext, scePsmlMfsr2CreateSharedResources, scePsmlMfsr2GetContextInitRequirement, scePsmlMfsr2GetDispatchPackets, scePsmlMfsr2GetDispatchPacketsSizeInDwords, scePsmlMfsr2GetSharedResourcesInitRequirement, scePsmlMfsr2Init, scePsmlMfsr2ReleaseContext, scePsmlMfsr2ReleaseSharedResources](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_mfsr2) - unknown signatures, imported by PPSA23566; declared without parameters +- [scePsmlMfsrGetSharedResourcesInitRequirement, scePsmlMfsrCreateSharedResources, scePsmlMfsrReleaseSharedResources, scePsmlMfsrGetContextBufferRequirement800M3_2, scePsmlMfsrCreateContext800M3_2, scePsmlMfsrReleaseContext](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml) - only resource type 0 is supported. The context requirement reports four 2 MiB blocks regardless of its parameters, while the system calculation depends on the context configuration. The descriptor pointer at 0x28 is not read. Validation throws for null pointers, short block lists and foreign handles instead of reproducing the system error and fault behaviour. +- [scePsmlMfsr2Init and the other MFSR2 calls](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml) - only a console without PSSR is modelled. In firmware 13.60 `libScePsml.sprx` (read from disassembly, not measured on a console), `scePsmlMfsr2Init` returns 0x8A8A0012 when `sceKernelIsTrinityMode` returns 0, and until `Init` succeeds every other MFSR2 call returns 0x8A8A0001 without touching its arguments. AnyPS5 reports 0, so `Init` throws only if that changes: the initialization after the check, the shared resource and context requirements, and the 0x800-dword size that `GetDispatchPacketsSizeInDwords` writes through its second argument are not implemented - [scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100, scePsmlMfsrGetDispatchMfsrPacket1100](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_debug) - argument layouts unknown; PPSA26344 imports but does not call the first two and at `0x87EFA` passes `(rdi, rsi=&stack, rdx)` as three pointers to `GetDispatchMfsrPacket1100`; before `scePsmlMfsrInit` they return 0x8A810001 (not initialized) and after it they throw. `scePsmlMfsrCreateContext1300` and `scePsmlMfsrGetDispatchMfsrPacket900`, `1000` and `1300` take the same shapes (context slot and parameter; context, command buffer and parameter) and behave the same way. PPSA08330 and PPSA03016 pass `GetDispatchMfsrPacket900`/`1000` the context handle, a `{cursor, size in dwords}` command buffer and the dispatch parameters, and advance their cursor by the size `scePsmlMfsrGetDispatchMfsrPacketSizeInDwords` returned. Whether the 1100 and 1300 versions take these arguments, and the console's error, are unverified - [scePsmlMfsrGetDispatchMfsrPacket1000](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml) - imported by PPSA25344. libScePsml and libScePsml_debug are built from the same source - [scePsmlMfsrCreateContext1300, scePsmlMfsrGetDispatchMfsrPacket1300](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml) - imported by PPSA28416 next to `scePsmlMfsrGetContextBufferRequirement1100`. The names are not in the aerolib NID list; they hash to the imported NIDs `kLF7WtQ1-Dg` and `DDVUyPyYHTA` (`tools/nid_names.py`) -- [scePsmlMfsrEnableSpecularAA, scePsmlMfsrEnableQuality8K](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_specularAA and libScePsml_quality8K) - imported by PPSA01317, which fails to load without them; signatures unknown, so they throw. The names are not in the aerolib NID list; they hash to the imported NIDs `xNZM9Y5lf-M` and `lcZD9tJraAw` (`tools/nid_names.py`) ### Functional - [scePsmlMfsrGetDispatchMfsrPacketSizeInDwords, scePsmlMfsrGetMipmapBias, scePsmlMfsrSelectConfig, scePsmlMfsrRequestCapture, scePsmlMfsrIsCaptureInProgress](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml) - semantics from the APR-EMU `fakelib/libScePsml.sprx`. Guards: `0x8A810001` uninitialized, `0x8A810005` null/foreign handle (fakelib checks an engine id `0xA9C4`/`0x9231`; our map check is the equivalent), `0x8A810009` null output. `GetDispatchMfsrPacketSizeInDwords` writes `0x600`; `GetMipmapBias(context, bias, dimension)` writes `log2f(dimension / context->inputWidth) - 1.0f`, width recorded at `CreateContext800M3_2` from the parameter pair at 0x18 or `descriptor0 + 0x20` for callers that pass a descriptor pointer there (indirection inferred; zero width returns `0x8A810005`). `SelectConfig`, `RequestCapture`, `IsCaptureInProgress` validate then return 0. The `GetDispatchMfsrPacket*` calls still throw: the 0x158-byte template's packet routine is not understood -- [scePsmlMfsrInit](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml) - succeeds and only sets the initialized state; the shared resources and contexts are bookkeeping objects that use none of the guest blocks. Whether Sony's library fails on hardware without PSSR is unknown. PPSA08330 calls the setup unconditionally and only uses a context when its cached `sceKernelIsTrinityMode` result is set (0x3c643d, 0x567f97; otherwise it stores a -0.25 mip bias itself at 0x567fe2), which AnyPS5 reports as 0 +- [scePsmlMfsrInit](../../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml) - succeeds and only sets the initialized state; the shared resources and contexts are bookkeeping objects that use none of the guest blocks. The console's library fails there instead: in firmware 13.60 (read from disassembly, not measured on a console) `scePsmlMfsrInit` returns 0x8A810012 when `sceKernelIsTrinityMode` returns 0, so its later MFSR calls return 0x8A810001. PPSA08330 calls the setup unconditionally and only uses a context when its cached `sceKernelIsTrinityMode` result is set (0x3c643d, 0x567f97; otherwise it stores a -0.25 mip bias itself at 0x567fe2), which AnyPS5 reports as 0 diff --git a/docs/dev/debt/libSceRazorCpu.md b/docs/dev/debt/libSceRazorCpu.md index f6f5690d75..092aa68738 100644 --- a/docs/dev/debt/libSceRazorCpu.md +++ b/docs/dev/debt/libSceRazorCpu.md @@ -2,17 +2,11 @@ ### Silent stubs -- [sceRazorCpuPushMarker](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signature (label, color, flags) from [SharpProspero](https://github.com/SvenGDK/SharpProspero), the same as sceRazorCpuPushMarkerStatic's except that the label is copied; it does nothing without a capture and returns 0 -- [sceRazorCpuSync](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - the `int(void)` signature and its meaning, an unnamed frame-boundary event, come from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Debug/RazorCpu.cs#L119-L122). Without a capture there is no event to record, so it does nothing and returns 0, like the other `sceRazorCpu*` markers here and the [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/razor_cpu/razor_cpu.cpp#L143-L146) stub. PRAGMATA (PPSA02530) continues on 0; the console's result with no capture attached is unverified -- [sceRazorCpuDisableFiberUserMarkers, sceRazorCpuBeginLogicalFileAccess and sceRazorCpuEndLogicalFileAccess](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures and meanings from SharpProspero ([fiber markers](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Debug/RazorCpu.cs#L73-L79), [logical file access](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Debug/RazorCpu.cs#L189-L209)): the first makes later markers carry the thread instead of the fiber, the other two attribute the thread's file reads and writes to a named logical file. Both only change what a capture records, so without one they do nothing, and the two file-access calls return 0 for any arguments. Whether the console checks the operation (0 read, 1 write) or an unmatched end without a capture is unverified -- [sceRazorCpuInitDataTags, sceRazorCpuShutdownDataTags, sceRazorCpuTagBuffer, sceRazorCpuTagArray, sceRazorCpuResizeTaggedBuffer and sceRazorCpuUnTagBuffer](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Debug/RazorCpu.cs#L130-L187), which documents them as the data sampler's buffer-tag table and the tags in it. Without a capture there is no data sampler, so they neither read the caller's storage nor keep the tags, and return 0 for any arguments; the console's errors for a tag before `sceRazorCpuInitDataTags`, an unknown buffer or a full table are unverified +- [libSceRazorCpu](../../../core/libs/prx/libSceRazorCpu/Export.cpp): capture-controller registration, thread-local capture storage, marker serialization and flushing are not implemented. The host implements only the confirmed uninitialized-profiler path: markers return zero without reading labels, and `sceRazorCpuFlushOccurred` returns false without writing its optional output. ### Unknown function info - [sceRazorCpuFiberLogNameChange, sceRazorCpuFiberSwitch, sceRazorCpuGpuMarkerSync, sceRazorCpuInitializeGpuMarkerContext, sceRazorCpuInitializeInternal, sceRazorCpuSetPopMarkerCallback](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - the list is the intersection of the export table of the console's [libSceRazorCpu](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRazorCpu.c) (function symbols only), the pinned [aerolib names](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) and the functions not yet defined anywhere in the repo. No credible prototypes were found; they are declared without parameters solely to resolve known imports, not as an ABI claim, and callers must not call them until signatures are verified. - [sceRazorCpuSetPushMarkerCallback, sceRazorCpuSetPushMarkerStaticCallback, sceRazorCpuStartCaptureInternal, sceRazorCpuStopCaptureInternal, sceRazorCpuWorkloadRunBegin, sceRazorCpuWorkloadRunEnd, sceRazorCpuWorkloadSubmit](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - the list is the intersection of the export table of the console's [libSceRazorCpu](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRazorCpu.c) (function symbols only), the pinned [aerolib names](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) and the functions not yet defined anywhere in the repo. No credible prototypes were found; they are declared without parameters solely to resolve known imports, not as an ABI claim, and callers must not call them until signatures are verified. -- [sceRazorCpuFlushOccurred](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signature and return values (1 = flush since the last call, 0 = none) from [SharpProspero](https://github.com/SvenGDK/SharpProspero); without a capture nothing is flushed, so it returns 0 and writes 0 cycles to the optional out pointer (whether the real library writes it when nothing was flushed is unknown) -- [sceRazorCpuPushMarkerStatic, sceRazorCpuPopMarker](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures from PPSA26344's calls; (name, color, flags) assumed for the push; both do nothing without a capture -- [sceRazorCpuPlotValue, sceRazorCpuWriteBookmark](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures (series, float value) and (label, optional description) from [SharpProspero](https://github.com/SvenGDK/SharpProspero), matching PPSA06320's calls; both do nothing without a capture and return 0 -- [sceRazorCpuNamedSync](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - NID `XCuZoBSVFG8` is named by [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/razor_cpu/razor_cpu.cpp); the NID and a stub returning 0 come from there, but its stub declares no arguments; the `const char* label` parameter (NUL-terminated, up to 16384 bytes) comes from [SharpProspero](https://github.com/SvenGDK/SharpProspero), which documents it as the frame-boundary label. Without a capture it does nothing and returns 0; the label is not read, and the capture behavior and the real return values are unverified +- [sceRazorCpuJobManagerDispatch, sceRazorCpuJobManagerJob and sceRazorCpuJobManagerSequence](../../../core/libs/prx/libSceRazorCpu/Export.cpp): argument positions and serialized widths are recovered from machine instructions. Dispatch takes seven arguments, including two 64-bit payloads whose upper 16 bits are replaced by the count fields. The payloads are not dereferenced, so their original pointer-versus-integer types and the semantic names of the event identifiers remain unresolved. Job and Sequence serialize 16-bit identifiers and an event selector; their source enum names are unavailable. - [sceRazorCpuGetDataTagStorageSize](../../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - throws: [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Debug/RazorCpu.cs#L142-L144) documents it as the byte size a tag table needs for a count of entries, but not the size of an entry diff --git a/docs/dev/debt/libSceRegMgr.md b/docs/dev/debt/libSceRegMgr.md index 9b6dbd6441..939ecae49d 100644 --- a/docs/dev/debt/libSceRegMgr.md +++ b/docs/dev/debt/libSceRegMgr.md @@ -1,9 +1,12 @@ # libSceRegMgr technical debt +### Functional + +- [Registry store](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr) - entries are queried through an in-memory process-local store without persistence. Callable prototypes for `sceRegMgrGetInt`, `sceRegMgrGetStr`, and `sceRegMgrGetBin` derive from [regmgr.h](https://github.com/ps5-payload-dev/regdump/blob/883524cc48c8497267ba7fc94ff0ac58880c4227/regmgr.h#L3713-L3729). Implemented getters return error codes for invalid parameters (`0x80820001`), missing keys (`0x80820002`), buffer sizing (`0x80820004`), and type mismatches (`0x80820006`) as unverified host implementation assumptions, as the cited regmgr.h does not establish error codes or validation behavior. Setters (`sceRegMgrSetInt`, `sceRegMgrSetStr`, `sceRegMgrSetBin`, `sceRegMgrSetbin`), reset (`sceRegMgrResetVal`), and zero-length getter paths remain unimplemented throwing `NotImplemented_nid_no_patch` until callable contracts and error codes are supported by evidence. +- [Unverified registry APIs](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr) - export names and NIDs for initial-value getters, version queries, region queries and conversions, error checks, control/backup routines, and `sceRegMgrNonSys*` variants are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/cf6eede3f6588d779664e01874638f09484324b7/libSceRegMgr.c) and [NID database](https://github.com/zecoxao/sce_symbols/blob/9620e03b2964dbf0cfc1cb143ab24a65125091de/aerolib.csv), but their callable contracts and runtime behavior remain unverified and retain `NotImplemented_nid_no_patch`. + ### Unknown function info -- [sceRegMgrNonSysGetInt](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr, NID `dKeshzt29G4`) - observed as an import needed for module loading (issue 4720); its `(int64_t key, int32_t unk, int32_t* data)` signature follows [ChonkyStation4](https://github.com/liuk7071/ChonkyStation4/blob/c101a23ecbc15e56c6e30d5dd24d3ed8880a957a/ChonkyStation4/OS/Libraries/SceRegMgr/SceRegMgr.cpp#L38-L47), not measured on a console, so every call throws -- [sceRegMgrGetBin and sceRegMgrSetBin](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRegMgr.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. The string and integer prototypes follow [SharpProspero's SystemSettings bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Platform/SystemSettings.cs) - [sceRegMgrCheckError](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr) - unknown signature - [sceRegMgrCheckIntVal](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr) - unknown signature - [sceRegMgrGetBinInitVal](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr) - unknown signature diff --git a/docs/dev/debt/libSceRnpsAppMgr.md b/docs/dev/debt/libSceRnpsAppMgr.md new file mode 100644 index 0000000000..b430381f91 --- /dev/null +++ b/docs/dev/debt/libSceRnpsAppMgr.md @@ -0,0 +1,5 @@ +# libSceRnpsAppMgr technical debt + +### Unknown function info + +- [sceRnpsAppMgrBlockAppInstall, sceRnpsAppMgrCreateUpdateRequest, sceRnpsAppMgrCreateUrlRequest, sceRnpsAppMgrDestroyBundle, sceRnpsAppMgrDestroyUpdateRequest, sceRnpsAppMgrFinishUrlRequest, sceRnpsAppMgrGetAppInfo, sceRnpsAppMgrGetAppInfoDebugString, sceRnpsAppMgrGetAppInfoList, sceRnpsAppMgrGetEventForShellUI, sceRnpsAppMgrGetUpdateProgress, sceRnpsAppMgrHasEventForShellUI, sceRnpsAppMgrInitialize, sceRnpsAppMgrPushEventForShellUI, sceRnpsAppMgrRecoverUfsImage, sceRnpsAppMgrRegisterShellUI, sceRnpsAppMgrRemoveApp, sceRnpsAppMgrRemoveAppOnSystemShutdown, sceRnpsAppMgrRemoveUfsImageOnSystemShutdown, sceRnpsAppMgrRequestUpdateCheck, sceRnpsAppMgrSetCrlUrl, sceRnpsAppMgrSetEventFlagForClients, sceRnpsAppMgrSetUpdateUrl, sceRnpsAppMgrStartService, sceRnpsAppMgrTerminate, sceRnpsAppMgrUnblockAppInstall and sceRnpsAppMgrUnregisterShellUI](../../../core/libs/prx/libSceRnpsAppMgr/Export.cpp) (libSceRnpsAppMgr) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRnpsAppMgr.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceRtc.md b/docs/dev/debt/libSceRtc.md index cf5399b601..12645457b4 100644 --- a/docs/dev/debt/libSceRtc.md +++ b/docs/dev/debt/libSceRtc.md @@ -6,10 +6,11 @@ ### Unknown function info -- [sceRtcParseDateTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the zone name table matches every name of 1 to 3 uppercase letters as a payload process on PS5 firmware 13.60 parsed them in the RFC 2822 form; of the longer names only real ones were tried (ACDT, AEST, CEST, NZDT, MESZ and others). Any other name of 4 or more letters is assumed to take the military letter offset of its first character, so a longer name the console's table holds but nobody measured gets the wrong offset - [sceRtcParseDateTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - a blank zone after seconds is accepted. With fully allocated input storage on PS5 firmware 13.42 and again on 13.60, the same NUL-terminated text returned `BAD_PARSE` when the byte after its terminator was zero and succeeded when that byte was uppercase `T`. This dependence on storage after the terminator is not modeled; controlled backing-byte measurements in a title process are unavailable. -- [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick formats the current time, as shadPS4 does and as these three and `sceRtcFormatRFC3339` did in a payload process on a PS5 (firmware 13.60); offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`, also with a null tick there; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset) +- [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - measured on PS4 firmware 10.71, not on a PS5: the RFC 2822 text, `INVALID_YEAR` when the zone-shifted tick is past 9999 as a signed value, otherwise `INVALID_VALUE` when the source tick or the zone-shifted tick is negative as a signed value, and the local-time variants using the offset in effect at the given instant (+01:00 in January, +02:00 in July on a Central European console). A null tick formatting the current time and the ±1439 minute offset limit come from the PS5 measurement in the next entry and are not measured on the PS4 +- [sceRtcFormatRFC3339, sceRtcGetDayOfWeek, sceRtcSetTime_t](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - measured on PS4 firmware 10.71 from processes compiled with SDK versions 0x01000051, 0x02ffffff, 0x03000000 and 0x05050001, not on a PS5. From 0x03000000 on: `FormatRFC3339` returns `INVALID_VALUE` when adding the zone shift to the tick overflows or underflows 64 bits (earlier SDKs wrap); `GetDayOfWeek` checks the month before the year and rejects a year past 9999 (earlier SDKs check the year first, with no upper bound); `SetTime_t` rejects negative seconds with `INVALID_VALUE` and converts all 64 bits (earlier SDKs take the low 32 bits). 0x02ffffff still behaves the earlier way and 0x03000000 the later way. The earlier rules apply only to a PS4 title (a CUSA title ID): a PS5 title has its own SDK numbering, so it always gets the later rules, the behaviour main had before; those are not measured on a PS5. - [sceRtcGetDosTime, sceRtcSetDosTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - bit layout from shadPS4 (PS4, the standard FAT date/time). A year outside 1980-2107 makes `GetDosTime` return `INVALID_YEAR` after writing 0 for an earlier one and `0xff9fbf7d` (2107-12-31 23:59:58, the largest encodable time) for a later one, and `SetDosTime` decodes the year as the unsigned 7-bit FAT field (1980-2107) without validating the fields, as the disassembly-derived [fpPS4](https://github.com/red-prig/fpPS4) reimplementation of the PS4 library does; not confirmed on a PS5 title, and shadPS4 instead masks out-of-range years into the packed value and sign-extends the 16-bit date +- [sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../../core/libs/prx/libSceRtc/Export.cpp): the native wrappers obtain the zone from a kernel timezone query, while the host uses the offset at the supplied instant. The kernel timezone configuration and its failure paths are not modeled. Native formatting confirms that null tick pointers select current time, RFC3339 uses two fractional digits, and explicit offsets are limited to -1439 through 1439 minutes. - [sceRtcGetCurrentRawNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console reads clock 0x13 and returns its time plus the Unix epoch in ticks, or `0x80B50001` when the clock is unsupported (errno 5). The raw network clock is not modelled; this returns the host clock. Read from the disassembly of libSceRtc.sprx on firmware 13.60, not measured on a console - [sceRtcGetCurrentAdNetworkTick, sceRtcGetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console reads clocks 0x12 and 0x11 and falls back to network clock 0x10 when either clock is unsupported. These getters use their process-local clock when it was set, otherwise they follow the process-local network clock. The firmware's unsupported-clock result is not modelled. Read from the disassembly of libSceRtc.sprx on firmware 13.60, not measured on a console - [sceRtcSetCurrentTick, sceRtcSetCurrentNetworkTick, sceRtcSetCurrentAdNetworkTick, sceRtcSetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - a valid tick sets a process-local clock that advances from a monotonic host clock; the host system clock is not changed. The console sets clock 0, 0x10, 0x12 or 0x11. Null pointers return `0x80B50002` and ticks before the Unix epoch return `0x80B50003`, from disassembly of libSceRtc.sprx on firmware 13.60, not measured on a console. sceRtcSetConf passes its third and fourth arguments to the same clock call with clock 0 and throws; what the pair selects is unknown diff --git a/docs/dev/debt/libSceRudp.md b/docs/dev/debt/libSceRudp.md index f8abf0989f..6b667bd48d 100644 --- a/docs/dev/debt/libSceRudp.md +++ b/docs/dev/debt/libSceRudp.md @@ -2,15 +2,14 @@ ### Silent stubs -- [sceRudpProcessEvents, sceRudpNetFlush and sceRudpNetReceived](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - there is no reliable-UDP stack on the host, and every `sceRudpWrite`, `sceRudpRead` and `sceRudpGetStatus` call here reports a reset or idle context without queueing data, so no context ever holds pending data to process, send or receive; the three calls return success after `sceRudpInit` and `RUDP_ERROR_NOT_INITIALIZED` before it, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/rudp/rudp.cpp) returns OK from its stubs. What the console's `sceRudpNetReceived` returns is unverified. PPSA03490 calls `sceRudpProcessEvents(0)` followed by `sceRudpNetFlush()` from its P2P service thread and ignores the first result; whether the real calls take parameters is unknown +- [sceRudpProcessEvents, sceRudpNetFlush and sceRudpNetReceived](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - there is no reliable-UDP stack on the host, and every `sceRudpWrite`, `sceRudpRead` and `sceRudpGetStatus` call here reports a reset or idle context without queueing data, so no context ever holds pending data to process, send or receive; the three calls return success after `sceRudpInit` and `RUDP_ERROR_NOT_INITIALIZED` before it, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/rudp/rudp.cpp) returns OK from its stubs. What the console's `sceRudpNetReceived` returns is unverified. PPSA03490 calls `sceRudpProcessEvents(0)` followed by `sceRudpNetFlush()` from its P2P service thread and ignores the first result. - [sceRudpEnableInternalIOThread2](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) takes `(uint32_t stack_size, uint32_t priority, uint64_t affinity)`, taken from the managed `SceRudp.Rudp.EnableInternalIOThread(uint stackSize, uint priority, ulong affinity)` in PPSA06888's Unity metadata; the first two match `sceRudpEnableInternalIOThread`. It returns success after `sceRudpInit` and `RUDP_ERROR_NOT_INITIALIZED` before it, as `sceRudpEnableInternalIOThread` does, without starting any thread. Whether the console reports other errors, and whether it applies the affinity, is unverified. -- [sceRudpActivate](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - there is no reliable-UDP stack on the host to activate, so it returns success without doing anything, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/rudp/rudp.cpp) does for its own `sceRudpActivate`. PPSA01325 calls it with no arguments during start-up; what the console activates, and whether it reports an error before `sceRudpInit`, is unverified +- [sceRudpActivate](../../../core/libs/prx/libSceRudp/Export.cpp) takes a context ID, socket address and 32-bit address length. Initialization and initial argument checks match the native implementation; transport mode checks, address conversion and connection activation remain unimplemented and throw. ### Unknown function info - [sceRudpGetStatus](../../../core/libs/prx/libSceRudp/Export.cpp) - PPSA01521 passes an output pointer and byte count `0xF8`. Measured on a PS5 (firmware 13.60): the status is 0xF8 bytes; before `sceRudpInit` every call returns `RUDP_ERROR_NOT_INITIALIZED`, and after it a null pointer or a byte count of 0 or above 0xF8 returns `RUDP_ERROR_INVALID_ARGUMENT`. The layout matches `SceRudpStatus` in [Vita3K](https://github.com/AndnixSH/Vita3K-PS-Vita-OS-Fork/blob/master/vita3k/modules/SceRudp/SceLibRudp.cpp) with `currentContexts` at 0x64 and `allocs`, `frees`, `memCurrent` and `memPeak` at 0xC8 to 0xD4. Only `currentContexts` is reported: the traffic counters stay 0 because nothing is sent, and the allocator counters stay 0 because the library does not allocate from the title's pool (the console reports 6 allocations and 0x1260 bytes right after `sceRudpInit`, and 4 more allocations and 0x3F0 bytes per context). The console also writes `currentContexts` and the allocator counters when the byte count is smaller than their offset; only the requested bytes are written here. -- [sceRudpGetMaxSegmentSize](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - measured on a PS5 (firmware 13.60) in #3073: before `sceRudpInit` it returns `RUDP_ERROR_NOT_INITIALIZED` and leaves the value untouched, also for a null pointer; after it a null pointer returns `RUDP_ERROR_INVALID_ARGUMENT` and a valid one gets 1410 with result 0. The library keeps no configurable segment size (there is no `sceRudpSetMaxSegmentSize`), so 1410 is always reported. The `uint16_t*` parameter is inferred: the measurement does not show how many bytes the console writes, PPSA03490 passes a pointer to a 16-bit value and [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellRudp.cpp) declares the PS3 `cellRudpSetMaxSegmentSize(u16 mss)`, so only 16 bits are written +- [sceRudpGetMaxSegmentSize](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - measured on a PS5 (firmware 13.60) in #3073: before `sceRudpInit` it returns `RUDP_ERROR_NOT_INITIALIZED` and leaves the value untouched, also for a null pointer; after it a null pointer returns `RUDP_ERROR_INVALID_ARGUMENT` and a valid one gets 1410 with result 0. The library keeps no configurable segment size (there is no `sceRudpSetMaxSegmentSize`), so 1410 is always reported. - [sceRudpGetOption, sceRudpSetOption](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - full PS5 option contracts are unverified; defaults and validation follow [Vita3K](https://github.com/AndnixSH/Vita3K-PS-Vita-OS-Fork/blob/c86ac732bc811057e690bed280c5076bdf95385e/vita3k/modules/SceRudp/SceLibRudp.cpp). Options 12 and 13 have unknown meanings; last-error option 14 is not maintained. Their setters store all 32 bits per context and return 0 for valid four-byte buffers, preserving acceptance on `main`; their getters throw. Static inspection of PPSA06888 v01.001.008's bundled Unity transport shows its context-set constructor configuring IDs 2, 3, 5 and 6; runtime usage and other paths remain unverified. The plugin wrappers pass four-byte values. No context can connect, so stored settings cannot affect traffic. Closes when: PS5 option contracts are verified and the remaining behavior is implemented. -- PPSA06888 imports with unknown signatures: [sceRudpGetRemoteInfo](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp), imported by its bundled `Media/Plugins/SceRudp.prx` and not called during start-up. The third argument is a pointer to a 4-byte buffer seeded from the byte its wrapper reads through the second argument, which does not show what the arguments mean. The managed `SceRudp.Context.GetRemoteInfo()` in the title's Unity metadata takes no parameters and returns a `SceRudp.AddrAndVport`; its native arguments are not visible there. Names from the NID table of [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/rudp/rudp.cpp), which declares them without parameters too -- [sceRudpPollCreate, sceRudpPollControl, sceRudpPollWait, sceRudpPollDestroy](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - unverified on a PS5; imported by PPSA31327 and not called before the main menu. The signatures, the 8-byte `SceRudpPollEvent`, the operations (1 add, 2 modify, 3 remove) and the argument checks follow the PS Vita library in [Vita3K](https://github.com/AndnixSH/Vita3K-PS-Vita-OS-Fork/blob/master/vita3k/modules/SceRudp/SceLibRudp.cpp), and the error codes match [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellRudp.h). Vita3K leaves `sceRudpPollDestroy` unimplemented, so `int sceRudpPollDestroy(int pollId)` and its checks are inferred. No context can connect, so no event is ever reported: `sceRudpPollWait` returns 0 when its timeout elapses, also for an empty poll, where Vita3K returns at once +- [sceRudpPollCreate, sceRudpPollControl, sceRudpPollWait, sceRudpPollDestroy](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - unverified on a PS5; imported by PPSA31327 and not called before the main menu. Signatures and the 8-byte event layout are recovered from the supplied module. The operations (1 add, 2 modify, 3 remove) and the argument checks follow the PS Vita library in [Vita3K](https://github.com/AndnixSH/Vita3K-PS-Vita-OS-Fork/blob/master/vita3k/modules/SceRudp/SceLibRudp.cpp), and the error codes match [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellRudp.h). Vita3K leaves `sceRudpPollDestroy` unimplemented, so `int sceRudpPollDestroy(int pollId)` and its checks are inferred. No context can connect, so no event is ever reported: `sceRudpPollWait` returns 0 when its timeout elapses, also for an empty poll, where Vita3K returns at once - [sceRudpAccept, sceRudpFlush, sceRudpGetLocalInfo, sceRudpListen, sceRudpPollCancel and sceRudpSetMaxSegmentSize](../../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRudp.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. diff --git a/docs/dev/debt/libSceS3da.md b/docs/dev/debt/libSceS3da.md index c941424735..cc6345ecaa 100644 --- a/docs/dev/debt/libSceS3da.md +++ b/docs/dev/debt/libSceS3da.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceS3daClose, sceS3daGetInfo, sceS3daOpen and sceS3daSendData](../../../core/libs/prx/libSceS3da/Export.cpp) (libSceS3da) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceS3da.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceS3daClose, sceS3daGetInfo, sceS3daOpen and sceS3daSendData](../../../core/libs/prx/libSceS3da/Export.cpp) remain unimplemented. Port arguments and transfer buffer/length are recovered from the native module. `GetInfo` forwards its output pointer to a kernel ioctl without accessing its fields; the information layout requires the kernel handler or a caller that interprets its output. diff --git a/docs/dev/debt/libSceSaveData.native.md b/docs/dev/debt/libSceSaveData.native.md index db7aeb7660..73a245aab1 100644 --- a/docs/dev/debt/libSceSaveData.native.md +++ b/docs/dev/debt/libSceSaveData.native.md @@ -3,11 +3,11 @@ ### Unknown function info - [sceSaveDataCreateTransactionResource, sceSaveDataDeleteTransactionResource](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - transaction resources return synthetic positive identifiers and validate initialization state; resource parameter validation and transactional rollback semantics on host filesystem storage are unknown -- [sceSaveDataPrepareForTransferring](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native, NID `RvID8BVeg+g`) - declared without parameters as a throwing stub; Minecraft (PPSA17221) imports it beside `sceSaveDataTransferringMountPs4` and `sceSaveDataDirNameSearchPs4` and fails to load without it. The name hashes to the NID but is not in aerolib; it was found by combining words from the libSceSaveData names in aerolib. Its arguments and behaviour are unknown. +- [sceSaveDataPrepareForTransferring](../../../core/libs/prx/libSceSaveData.native/Export.cpp): the native wrapper confirms the same 16-byte mount point and 40-byte preparation parameter transport as `sceSaveDataPrepare`. Transfer preparation still requires the save-data service backend; its protected stub is unchanged. - [sceSaveDataTransferringMountPs4, sceSaveDataDirNameSearchPs4](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - arguments assumed to be those of `sceSaveDataTransferringMount` and `sceSaveDataDirNameSearch`: KytyPS5 binds both NIDs to the PS5 functions, and [prosper](https://github.com/mattias800/prosper) captured the same result layout on a title for the search. There is no PS4 save area, so the mount returns `NOT_FOUND` and the search reports zero hits -- [sceSaveDataConvert](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - the parameter block (user id at `+0x00`, `SceSaveDataDirName` pointer at `+0x08`, 48 opaque bytes after it) is read from PPSA28416's single call, not confirmed by KytyPS5, shadPS4, fpPS4 or SharpProspero, which have no body for it. Host saves have no size limit, so an existing save returns `SCE_OK` without touching its files and no event is queued; what the console returns for a save that is mounted, already converted or needs no conversion is unknown. The directory name is validated as `sceSaveDataDelete` does +- [sceSaveDataConvert](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - the parameter block (user id at `+0x00`, `SceSaveDataDirName` pointer at `+0x08`, 48 opaque bytes after it) is read from PPSA28416's single call, not confirmed by KytyPS5, shadPS4, fpPS4 or SharpProspero, which have no body for it. Host saves have no size limit, so an existing save returns `SCE_OK` without touching its files and queues a successful type-5 completion event with the user and directory; PPSA03671 polls for this event after conversion. What the console returns for a save that is mounted, already converted or needs no conversion is unknown. The directory name is validated as `sceSaveDataDelete` does - [sceSaveDataGetConvertProgress](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - imported by PPSA16064; signature and error codes follow the [SDK 12.000 reference](https://github.com/peterpuppy/bm/blob/3f9d1ba6b896a53080460c903f5c15af8affb80d/features/ps5/document/output/psn_12/System/Save_Data/SaveData-Reference/sce-save-data-get-convert-progress.md) (`float* progress`). Save data conversion on host filesystems completes synchronously, so the progress query returns `SAVE_DATA_OK` with the last conversion's progress (`0.0f` before any conversion or when conversion fails, `1.0f` after successful conversion), `SAVE_DATA_ERROR_PARAMETER` if `progress` is null, or `SAVE_DATA_ERROR_NOT_INITIALIZED` when the library is uninitialized; intermediate asynchronous conversion progress on console is unmeasured -- [sceSaveDataCancel](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - imported by PPSA23126, which fails to load without it; added without an implementation because its signature is unknown. Every save data call here finishes before it returns, so nothing is ever in flight to cancel, but what the console returns then is unknown +- [sceSaveDataCancel](../../../core/libs/prx/libSceSaveData.native/Export.cpp): the native wrapper forwards one 32-bit argument to the save-data service. Its meaning and cancellation results require the service handler; the protected stub is unchanged. - [sceSaveDataDirNameSearch](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - the sort keys (0 directory name, 1 user parameter, 2 blocks, 3 modification time, 5 free blocks) and orders (0 ascending, 1 descending) follow shadPS4, not a hardware run; saves with equal keys keep directory-name order before a descending reverse. The search infos (blocks, free blocks and 32 reserved bytes per returned name) follow shadPS4's `OrbisSaveDataSearchInfo`. Keys other than 0, 1, 2, 3 and 5 (including 4) and orders above 1 return `SCE_SAVE_DATA_ERROR_PARAMETER`; shadPS4 and KytyPS5 only reject keys above 5, so key 4 is rejected here because no source names it. In the directory name pattern `%` matches any run of characters, none included, and `_` exactly one, as the comments of shadPS4's matcher describe; also not confirmed on hardware - [sceSaveDataDirNameSearch, sceSaveDataGetMountInfo, sceSaveDataMount3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - the 64 KiB block of [SharpEmu](https://github.com/sharpemu/sharpemu/blob/c6d304cc3328bad62cb671327ace181110cf2ddb/src/SharpEmu.Libs/SaveData/SaveDataExports.cs#L82-L84) and the 48 to 16384-block range of SharpEmu and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/569ec04d1a77127c601eed522ad5bba187173f9d/src/libs/libSaveData.cpp#L30-L31) are not measured on a console, and file system overhead is not counted. What the console does with a `blocks` outside that range is unknown: KytyPS5 rejects the creating mount and SharpEmu clamps it; here a larger one is kept as 16384 and a smaller non-zero one as given. A save's size is the `blocks` of the mount that created it, kept in `.blocks`, else that of its next mount that names one, else 16384; its free blocks are that size less its files, each rounded up to 64 KiB blocks - [sceSaveDataInitialize3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - a repeated initialize succeeds: PPSA12544's executable and its Unity SaveData plugin both initialize, and the plugin fails on any error. Initializations are counted and `sceSaveDataTerminate` ends the session at the last one; how the console pairs them is unknown @@ -19,6 +19,6 @@ - [sceSaveDataDirNameSearch2](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) runs the same search as `sceSaveDataDirNameSearch` with the same condition and result structures, as [SharpEmu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/SaveData/SaveDataExports.cs) does, its only source; what distinguishes it on the console is unknown. - [sceSaveDataMount3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - a failed mount (BUSY, EXISTS, NOT_FOUND, MOUNT_FULL) leaves the result as it was, as shadPS4 does; not checked on hardware. Animal Well (PPSA22520) depends on it: it checks for its save with a mount it never unmounts, then saves with a second mount of the same directory, ignores its BUSY and keeps using the first mount point for `sceSaveDataPrepare`, its file and `sceSaveDataUmount2` - [sceSaveDataMount3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - an unterminated or empty directory name, `.`, `..`, or a name containing `/`, `\` or `:` returns `SAVE_DATA_ERROR_PARAMETER`, which the [SDK 12.000 reference](https://github.com/peterpuppy/bm/blob/3f9d1ba6b896a53080460c903f5c15af8affb80d/features/ps5/document/output/psn_12/System/Save_Data/SaveData-Reference/sce-save-data-mount3.md#L196-L204) lists for an invalid parameter, and leaves the result as it was, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/569ec04d1a77127c601eed522ad5bba187173f9d/src/libs/libSaveData.cpp#L652-L658) does. The [SDK](https://github.com/peterpuppy/bm/blob/3f9d1ba6b896a53080460c903f5c15af8affb80d/features/ps5/document/output/psn_12/System/Save_Data/SaveData-Reference/sce-save-data-dir-name.md#L32-L36) lets an application use only letters, digits, `-`, `.` and `@` in a name, and KytyPS5 also accepts `_` ([`valid_path_component`](https://github.com/KytyPS5/KytyPS5/blob/569ec04d1a77127c601eed522ad5bba187173f9d/src/libs/libSaveData.cpp#L276-L287)); which other names the console's mount rejects is not checked on hardware -- [sceSaveDataGetMountInfo, sceSaveDataGetParam, sceSaveDataLoadIcon, sceSaveDataSaveIcon, sceSaveDataSetParam, sceSaveDataUmount2](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - null pointer arguments return `SAVE_DATA_ERROR_PARAMETER` instead of throwing C++ exceptions; unmounted targets return `SAVE_DATA_ERROR_NOT_MOUNTED`, and parameter type or buffer size violations return `SAVE_DATA_ERROR_PARAMETER` after slot lookup +- [sceSaveDataBackup, sceSaveDataDelete, sceSaveDataDirNameSearch, sceSaveDataGetEventResult, sceSaveDataGetMountInfo, sceSaveDataGetParam, sceSaveDataLoadIcon, sceSaveDataMount3, sceSaveDataSaveIcon, sceSaveDataSetParam, sceSaveDataUmount2](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - null pointer arguments return `SAVE_DATA_ERROR_PARAMETER` instead of throwing C++ exceptions, an implementation assumption matching guest error expectations not checked on hardware; unmounted targets return `SAVE_DATA_ERROR_NOT_MOUNTED`, and parameter type or buffer size violations return `SAVE_DATA_ERROR_PARAMETER` after slot lookup - [sceSaveDataSyncSaveDataMemory](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - validates library initialization and parameters; since in-memory saves flush synchronously on write, sync confirms existence and readiness of the memory blob diff --git a/docs/dev/debt/libSceScreenShot.md b/docs/dev/debt/libSceScreenShot.md index 568cb899b6..a109ca5bb9 100644 --- a/docs/dev/debt/libSceScreenShot.md +++ b/docs/dev/debt/libSceScreenShot.md @@ -1,6 +1,11 @@ # libSceScreenShot technical debt +### Silent stubs + +- [sceScreenShotEnableNotification and sceScreenShotDisableNotification](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - return 0 without effect: on the console they ask the system software to show or hide the capture notification, which this project does not display +- [sceScreenShotSetOverlayImage and sceScreenShotSetOverlayImageWithOrigin](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - check the arguments and the file as the PS5 module does, then return 0 without keeping the image, since this project takes no screenshots to overlay +- [sceScreenShotIsDisabled](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - reports one process-wide flag set by sceScreenShotDisable and cleared by sceScreenShotEnable for any id other than -1; on the console the system software answers, and what the id selects is unknown + ### Unknown function info -- [sceScreenShotCapture, sceScreenShotDisable, sceScreenShotDisableNotification, sceScreenShotEnable, sceScreenShotEnableNotification, sceScreenShotGetAppInfo, sceScreenShotGetDrcParam, sceScreenShotIsDisabled, sceScreenShotIsVshScreenCaptureDisabled, sceScreenShotSetDrcParam and sceScreenShotSetParam](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceScreenShot.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. -- [sceScreenShotSetOverlayImage and sceScreenShotSetOverlayImageWithOrigin](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceScreenShot.c). The PS4 compatibility implementation also exposes the names but declares no parameters; no PS5 prototype was found. The declarations have no parameters solely to resolve known imports and make no ABI claim +- [sceScreenShotCapture, sceScreenShotGetAppInfo, sceScreenShotGetDrcParam, sceScreenShotIsVshScreenCaptureDisabled, sceScreenShotSetDrcParam and sceScreenShotSetParam](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceScreenShot.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. diff --git a/docs/dev/debt/libSceSdma.md b/docs/dev/debt/libSceSdma.md index 78b6855431..bef40fd068 100644 --- a/docs/dev/debt/libSceSdma.md +++ b/docs/dev/debt/libSceSdma.md @@ -2,4 +2,4 @@ ### Unknown function info -- [libSceSdma pending exports](../../../core/libs/prx/libSceSdma/Export.cpp) - 26 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSdma.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceSdma pending exports](../../../core/libs/prx/libSceSdma/Export.cpp) - 21 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSdma.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. diff --git a/docs/dev/debt/libSceShare.md b/docs/dev/debt/libSceShare.md index 8ad04d2333..e9062c9e03 100644 --- a/docs/dev/debt/libSceShare.md +++ b/docs/dev/debt/libSceShare.md @@ -3,9 +3,9 @@ ### Unknown function info - [sceShareGetCurrentStatus](../../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - validation (feature flag non-zero, status non-null) and the all-zero 16-byte status taken from the [KytyPS5](https://github.com/KytyPS5/KytyPS5) reimplementation, not confirmed on a PS5 title; the meaning of the recording status values is unknown -- [sceShareGetRunningStatus](../../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - only the first 32-bit word of the status is known: PPSA01317 treats bits 0x3c of it as a running share feature and gives the call a 40-byte buffer. The host never records or broadcasts, so it stores 0 in that word and leaves the rest untouched; a null status returns `INVALID_PARAM` by analogy with `sceShareGetCurrentStatus` -- [sceShareSetContentParamForApplicationTitle](../../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - imported by PPSA12544's Unity plugins; the signature follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libShare.cpp), not confirmed on a PS5 title. The application title is only checked for null, as KytyPS5 does, since nothing is captured +- [sceShareGetRunningStatus](../../../core/libs/prx/libSceShare/Export.cpp) - the native wrapper writes only the first 32-bit word, populated from a kernel event flag. The host reports zero because capture and broadcast services are absent; native initialization checks and event-flag failures are not modeled. +- [sceShareSetContentParamForApplicationTitle](../../../core/libs/prx/libSceShare/Export.cpp) - the recovered string length limit is 1 through 5119 bytes. The title is validated but not sent to the capture service; native initialization and service failures are not modeled. ### Functional -- [sceShareCaptureVideoClipExtended](../../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - imported by PPSA12544's Share plugin; the signature (extended parameter, request id) and the answer follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/147188b143f67c27d8da396e759c19336f64c105/src/libs/libShare.cpp), not confirmed on a PS5 title. Nothing is captured, so, like `sceShareCaptureScreenshotExtended`, it stores the invalid request id -1 when the pointer is not null and returns `NOT_SUPPORTED` without reading the extended parameter, whose layout is unknown +- [sceShareCaptureVideoClipExtended, sceShareCaptureScreenshotExtended](../../../core/libs/prx/libSceShare/Export.cpp) - the 40-byte video and 24-byte screenshot parameters and request-ID write precedence are recovered. Video values at offsets 8 and 16 and screenshot mode semantics remain incomplete. Valid requests still return NOT_SUPPORTED without capture; screenshot mode 2 platform checks, throttling, initialization and the backend text limit are not modeled. diff --git a/docs/dev/debt/libSceSharePlay.md b/docs/dev/debt/libSceSharePlay.md index 167653b1ff..915f67c4c2 100644 --- a/docs/dev/debt/libSceSharePlay.md +++ b/docs/dev/debt/libSceSharePlay.md @@ -2,11 +2,12 @@ ### Unknown function info -- [sceSharePlayCrashDaemon, sceSharePlayGetCurrentConnectionInfo, sceSharePlayGetCurrentInfo, sceSharePlayGetEvent, sceSharePlayNotifyDialogOpen, sceSharePlayNotifyForceCloseForCdlg, sceSharePlayResumeScreenForCdlg, sceSharePlayServerLock](../../../core/libs/prx/libSceSharePlay/Export.cpp) (libSceSharePlay) - the list is the intersection of the export table of the console's [libSceSharePlay](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSharePlay.c) (function symbols only), the pinned [aerolib names](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) and the functions not yet defined anywhere in the repo. No credible prototypes were found; they are declared without parameters solely to resolve known imports, not as an ABI claim, and callers must not call them until signatures are verified. +- [sceSharePlayCrashDaemon, sceSharePlayGetCurrentInfo, sceSharePlayGetEvent, sceSharePlayNotifyDialogOpen, sceSharePlayNotifyForceCloseForCdlg, sceSharePlayResumeScreenForCdlg, sceSharePlayServerLock](../../../core/libs/prx/libSceSharePlay/Export.cpp) (libSceSharePlay) - the list is the intersection of the export table of the console's [libSceSharePlay](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSharePlay.c) (function symbols only), the pinned [aerolib names](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) and the functions not yet defined anywhere in the repo. No credible prototypes were found; they are declared without parameters solely to resolve known imports, not as an ABI claim, and callers must not call them until signatures are verified. - [sceSharePlayServerUnLock, sceSharePlaySetMode, sceSharePlaySetProhibition, sceSharePlaySetProhibitionModeWithAppId, sceSharePlayStartStandby, sceSharePlayStartStreaming, sceSharePlayStopStandby, sceSharePlayStopStreaming](../../../core/libs/prx/libSceSharePlay/Export.cpp) (libSceSharePlay) - the list is the intersection of the export table of the console's [libSceSharePlay](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSharePlay.c) (function symbols only), the pinned [aerolib names](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) and the functions not yet defined anywhere in the repo. No credible prototypes were found; they are declared without parameters solely to resolve known imports, not as an ABI claim, and callers must not call them until signatures are verified. -- [sceSharePlayInitialize](../../../core/libs/prx/libSceSharePlay/Export.cpp) (libSceSharePlay) - the library keeps no state, so the heap is not used. A null heap is accepted: PPSA11386 passes a null heap and 0x1800 bytes and stops on any non-zero result. The error for a zero size is unknown, so it throws ### Functional -- [sceSharePlayGetCurrentConnectionInfoA](../../../core/libs/prx/libSceSharePlay/Export.cpp) (libSceSharePlay) - reports no Share Play session, as the library keeps none: status 0 (DORMANT), mode 0, empty online IDs, account IDs 0 and both user IDs -1 (`SCE_USER_SERVICE_USER_ID_INVALID`). A null pointer returns `SCE_SHARE_PLAY_ERROR_INVALID_ARGS` (0x810E0001). The 72-byte `SceSharePlayConnectionInfoA` layout and the error code follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libNet.cpp) and [fpPS4](https://github.com/red-prig/fpPS4/blob/trunk/src/ps4_libsceshareplay.pas), which agree on both; KytyPS5 sets the user IDs to -1 and fpPS4 leaves them 0, and which one the console returns is not verified. KytyPS5 also returns `SCE_SHARE_PLAY_ERROR_NOT_INITIALIZED` before `sceSharePlayInitialize`; this library keeps no initialization state and does not. -- [sceSharePlayProhibitController](../../../core/libs/prx/libSceSharePlay/Export.cpp) (libSceSharePlay) - accepts 0 (allow) and 1 (prohibit) and returns 0, following [sharpemu](https://github.com/sharpemu/sharpemu/blob/a2983692930564815a77f81e74c151268f5b935f/src/SharpEmu.Libs/Share/SharePlayExports.cs#L9-L31). The setting is not stored, as no Share Play session or visitor controller exists and no export reads it back. sharpemu rejects any other value with its synthetic `0x80020003`, not a Share Play code, so other values throw. Like the other exports here it does not require `sceSharePlayInitialize`. The `int` argument type and the console's error for other values are unverified +- [sceSharePlayInitialize and sceSharePlayTerminate](../../../core/libs/prx/libSceSharePlay/Export.cpp) - native heap validation and initialization state are recovered: a null heap ignores the supplied size, a supplied heap requires at least 0x1800 bytes, repeated initialization returns 0x810E0003 and termination before initialization returns 0x810E0004. The host does not allocate the native internal heap, connect to the Share Play service or reproduce its failures. +- [sceSharePlayGetCurrentConnectionInfoA](../../../core/libs/prx/libSceSharePlay/Export.cpp) - the native module confirms a 72-byte result, initial zero fields and both user IDs -1. Initialization is checked before the output pointer. The host retains this initial disconnected state; native event draining, cached updates and service reconnection are unimplemented. +- [sceSharePlayGetCurrentConnectionInfo](../../../core/libs/prx/libSceSharePlay/Export.cpp) - the 56-byte layout without account IDs is from [shadPS4's shareplay.h](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/share_play/shareplay.h); the checks and values copy `sceSharePlayGetCurrentConnectionInfoA`, while shadPS4 leaves both user IDs 0 and checks nothing. Not measured on a console. +- [sceSharePlayProhibitController](../../../core/libs/prx/libSceSharePlay/Export.cpp) - the argument is one byte, forwarded unchanged to the service. The host checks initialization, accepts 0/1 without a visitor controller, and returns 0x810E0001 (SCE_SHARE_PLAY_ERROR_INVALID_ARGS) for other values; dynamic service communication and controller state remain unimplemented. diff --git a/docs/dev/debt/libSceShareUtility.md b/docs/dev/debt/libSceShareUtility.md new file mode 100644 index 0000000000..c953b5f180 --- /dev/null +++ b/docs/dev/debt/libSceShareUtility.md @@ -0,0 +1,5 @@ +# libSceShareUtility technical debt + +### Unknown function info + +- [sceShareUtilityAdvanceFromFileWithContentIdList, sceShareUtilityAdvanceFromFileWithTitleIdList, sceShareUtilityAdvanceGetServiceInfo, sceShareUtilityAdvanceInitialize, sceShareUtilityAdvanceQueryServiceInfo, sceShareUtilityAdvanceQueryServiceInfoA, sceShareUtilityAdvanceResetPlayingContent, sceShareUtilityAdvanceSetPlayingContent, sceShareUtilityAdvanceSetUploadContentData, sceShareUtilityAdvanceTerminate, sceShareUtilityAdvanceValidateContents, sceShareUtilityAdvanceWakeUpShareMenu, sceShareUtilityAdvanceWakeUpShareMenuBroadcast, sceShareUtilityAdvanceWakeUpShareMenuDefault, sceShareUtilityAdvanceWakeUpShareMenuForShareContent, sceShareUtilityInitialize, sceShareUtilityInitializeEx2, sceShareUtilityOpenShareMenu, sceShareUtilityOpenShareMenuBroadcast, sceShareUtilityOpenShareMenuDefault, sceShareUtilityOpenShareMenuForShareContent and sceShareUtilityTerminate](../../../core/libs/prx/libSceShareUtility/Export.cpp) (libSceShareUtility) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceShareUtility.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceShellUIUtil.md b/docs/dev/debt/libSceShellUIUtil.md index f07f584e14..12414a8ba7 100644 --- a/docs/dev/debt/libSceShellUIUtil.md +++ b/docs/dev/debt/libSceShellUIUtil.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceShellUIUtilCreateTaskToken, sceShellUIUtilGetAppUrl, sceShellUIUtilInitialize, sceShellUIUtilLaunchByUri, sceShellUIUtilLaunchTask and sceShellUIUtilTerminate](../../../core/libs/prx/libSceShellUIUtil/Export.cpp) (libSceShellUIUtil) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceShellUIUtil.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [libSceShellUIUtil](../../../core/libs/prx/libSceShellUIUtil/Export.cpp) remains unimplemented. Native wrappers confirm the export signatures, including two input strings, output buffer, 32-bit capacity and optional 32-bit output size for `GetAppUrl`. The precise string meanings and the second word of the 8-byte `LaunchByUri` parameter require the receiving IPC service; the wrapper only checks its leading size. Task completion, IPC errors and lifecycle behavior are not implemented. diff --git a/docs/dev/debt/libSceSocialScreenDialog.md b/docs/dev/debt/libSceSocialScreenDialog.md index a1b39f0af9..b1b16a216d 100644 --- a/docs/dev/debt/libSceSocialScreenDialog.md +++ b/docs/dev/debt/libSceSocialScreenDialog.md @@ -1,5 +1,5 @@ # libSceSocialScreenDialog technical debt -### Unknown function info +### Silent stubs -- [sceSocialScreenDialogClose, sceSocialScreenDialogGetResult, sceSocialScreenDialogGetStatus, sceSocialScreenDialogInitialize, sceSocialScreenDialogOpen, sceSocialScreenDialogTerminate and sceSocialScreenDialogUpdateStatus](../../../core/libs/prx/libSceSocialScreenDialog/Export.cpp) (libSceSocialScreenDialog) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSocialScreenDialog.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [libSceSocialScreenDialog](../../../core/libs/prx/libSceSocialScreenDialog/Export.cpp) - the social screen dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as there is no second-screen viewer. The signatures are from [Orbital's type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json); the result layout beyond the leading `int32_t` result code is not known, so nothing else is written. diff --git a/docs/dev/debt/libSceSpSysCallWrapper.md b/docs/dev/debt/libSceSpSysCallWrapper.md index ea6d5fbf4e..1a664e18fd 100644 --- a/docs/dev/debt/libSceSpSysCallWrapper.md +++ b/docs/dev/debt/libSceSpSysCallWrapper.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceSpGetPid, sceSpKernelGettimeofday, sceSpKernelNanosleep, sceSpNetConnect, sceSpNetEpollAbort, sceSpNetEpollControl, sceSpNetEpollCreate, sceSpNetEpollDestroy, sceSpNetEpollWait, sceSpNetErrnoLoc, sceSpNetPoolCreate, sceSpNetPoolDestroy, sceSpNetRecv, sceSpNetResolverAbort, sceSpNetResolverCreate, sceSpNetResolverDestroy, sceSpNetResolverGetError, sceSpNetResolverStartNtoa, sceSpNetSend, sceSpNetSetsockopt, sceSpNetSocket, sceSpNetSocketClose, sceSpPthreadAttrDestroy, sceSpPthreadAttrInit, sceSpPthreadAttrSetinheritsched, sceSpPthreadAttrSetschedparam, sceSpPthreadAttrSetstacksize, sceSpPthreadCondDestroy, sceSpPthreadCondInit, sceSpPthreadCondSignal, sceSpPthreadCondWait, sceSpPthreadCondattrDestroy, sceSpPthreadCondattrInit, sceSpPthreadCreate, sceSpPthreadDetach, sceSpPthreadExit, sceSpPthreadJoin, sceSpPthreadMutexDestroy, sceSpPthreadMutexInit, sceSpPthreadMutexLock, sceSpPthreadMutexUnlock, sceSpPthreadMutexattrDestroy, sceSpPthreadMutexattrInit, sceSpPthreadMutexattrSetprioceiling, sceSpPthreadMutexattrSetprotocol, sceSpPthreadMutexattrSettype, sceSpPthreadSetprio and sceSpSelect](../../../core/libs/prx/libSceSpSysCallWrapper/Export.cpp) (libSceSpSysCallWrapper) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSpSysCallWrapper.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceSpPthreadMutexattrSetprioceiling](../../../core/libs/prx/libSceSpSysCallWrapper/Export.cpp) (libSceSpSysCallWrapper) - still throws. In firmware 13.60's `libSceSpSysCallWrapper.sprx` it only jumps to libkernel's `scePthreadMutexattrSetprioceiling`, which AnyPS5 does not implement yet (read from disassembly, not measured on a console) diff --git a/docs/dev/debt/libSceSsl.md b/docs/dev/debt/libSceSsl.md index 71ef1ff74e..cbd359521e 100644 --- a/docs/dev/debt/libSceSsl.md +++ b/docs/dev/debt/libSceSsl.md @@ -2,13 +2,15 @@ ### Unknown function info -- [CA_MGMT_extractKeyBlobEx, CA_MGMT_extractPublicKeyInfo, CA_MGMT_freeKeyBlob, CRYPTO_initAsymmetricKey, CRYPTO_uninitAsymmetricKey, RSA_verifySignature, VLONG_freeVlongQueue, sceSslCheckRecvPending, sceSslCreateSslConnection and sceSslDeleteSslConnection](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - the list is the intersection of the export table of the console's libSceSsl (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSsl.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); no credible prototypes were found. They are declared without parameters solely to resolve known imports, not as an ABI claim; callers must not call them until signatures are verified. +- [CA_MGMT_extractKeyBlobEx, CA_MGMT_extractPublicKeyInfo, CA_MGMT_freeKeyBlob, CRYPTO_initAsymmetricKey, RSA_verifySignature, sceSslCheckRecvPending, sceSslCreateSslConnection and sceSslDeleteSslConnection](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - the list is the intersection of the export table of the console's libSceSsl (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSsl.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); no credible prototypes were found. They are declared without parameters solely to resolve known imports, not as an ABI claim; callers must not call them until signatures are verified. - [sceSslDisableOption, sceSslDisableOptionInternal, sceSslDisableOptionInternalInsecure, sceSslEnableOption, sceSslEnableOptionInternal, sceSslGetNanoSSLModuleId, sceSslGetPeerCert, sceSslGetSslError, sceSslLoadRootCACert and sceSslSetSslVersion](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - the list is the intersection of the export table of the console's libSceSsl (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSsl.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); no credible prototypes were found. They are declared without parameters solely to resolve known imports, not as an ABI claim; callers must not call them until signatures are verified. -- [sceSslRecv, sceSslReuseConnection and sceSslSend](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - the list is the intersection of the export table of the console's libSceSsl (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSsl.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). Their parameter shapes follow [SharpProspero's pinned bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs#L222-L262), and are not measured on a PS5. +- [sceSslReuseConnection](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - its name and NID appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSsl.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); its parameter shape follows [SharpProspero's pinned binding](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs#L2231-L2235). No console observation or reliable implementation source for session reuse was found. -- [sceSslCreateConnection, sceSslDeleteConnection, sceSslSetVerifyCallback, sceSslConnect, sceSslRead, sceSslWrite](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - imported by `ffmpeg.prx`. Signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/Http.cs#L195-L252), which also says the socket stays open after `sceSslDeleteConnection`. The behaviour below was measured on a PS5 on system software 13.40 with a ps5-payload-dev payload (every call printed with its return, a TLS 1.2 test server on the LAN presenting leaves issued by the test fixture root loaded with `sceSslLoadCert`, from an elfldr process rather than a title); what differs for a title process is unknown. An unknown context returns `SSL_ERROR_INVALID_ID` (0x8095F006) from `sceSslCreateConnection`, and every connection call on an unknown or deleted connection id or on a connection whose context was terminated returns `SSL_ERROR_INVALID_ID`. `sceSslCreateConnection` returns `SSL_ERROR_INVALID_VALUE` (0x8095F007) for a null host and does not check the socket: `sceSslConnect` on a descriptor that is not a socket returns `SCE_NET_ERROR_EBADF` (0x80410109). `sceSslConnect` runs a TLS 1.2 client handshake with Mbed TLS 3.6.7 over the host socket that the guest number maps to in the libkernel or libSceNet socket table, and `sceSslRead` and `sceSslWrite` carry application data over it. TLS 1.3 is compiled out: the console offers TLS 1.2 only (its ClientHello carries 12 suites and no supported_versions extension) and a TLS 1.3-only server gets `SSL_ERROR_FATAL_ALERT` (0x8095F010) from it, as from this build. Connection state is allocated from the context's pool and returned to it by `sceSslDeleteConnection`; a connection keeps the pool alive after `sceSslTerm`, whose connections return `SSL_ERROR_INVALID_ID` from then on. The console's pool use for one connection with a two-certificate chain peaked at 80,544 bytes in a 256 KiB pool (5,568 after `sceSslInit`, 12,000 after loading one CA); a 16 KiB pool fails the connect with `SSL_ERROR_OUT_OF_MEMORY` (0x809517D5) there and here. The server's chain is checked against the CAs loaded with `sceSslLoadCert`, the host name the connection was created with (case-insensitively; an IP literal sends no SNI and matches an IP subject alternative name; an empty host sends an empty SNI and fails `CN_CHECK`) and, when enabled, its validity dates, and `sceSslConnect` returns the first failure the connection's verify options select (the verify option entry below lists the bits, codes and initial options); no root bundle is shipped and the console's built-in roots are unknown, so with no CA loaded a connection with the initial options returns `SSL_ERROR_UNKNOWN_CA` (0x8095F00C), as on the console. A client certificate loaded with `sceSslLoadCert` is sent when the server asks for one. A failed verification is final: close_notify is sent, later `sceSslConnect` calls return the same code (also after the `WANT_POLLIN` rounds of a non-blocking socket), and `sceSslRead` and `sceSslWrite` throw, as the console's codes for them were not measured. With a verify callback set it is called once after the handshake, outside the connection's lock, with the context id, the bits of the enabled checks that failed (`KNOWN_CA_CHECK` 0x20, `CN_CHECK` 0x4, the date checks when enabled, 0 when the chain passed), handles to a copy of the peer chain as received (the leaf first, the root last when the server sent it) that the certificate readers of the connection's context accept until the connection is deleted (the handles stay registered in the context until it is terminated; the console frees them at the delete), the chain length and the user argument. A chain fault that no option names (`SSL_ERROR_INVALID_CERT` without a callback) throws when a callback is set, as the bits the console passes for it were not measured. A non-negative return accepts the connection whatever the bits were; a negative return fails the connect with `SSL_ERROR_APPLICATION_VERIFICATION_CALLBACK_CHECK` (0x8095F0FD) and the callback runs again, with the same chain, from every later `sceSslConnect`, `sceSslRead` and `sceSslWrite` of the connection, which return 0x8095F0FD while it keeps refusing and proceed once it accepts. A `sceSslConnect` from inside the callback, or from another thread while it runs, throws. `sceSslRead` and `sceSslWrite` before `sceSslConnect` run the handshake and verification themselves and return its code when it fails. A non-blocking socket that would block returns `SSL_ERROR_WANT_POLLIN` (0x8095F013) or `SSL_ERROR_WANT_POLLOUT` (0x8095F014) from `sceSslConnect`, `sceSslRead` and `sceSslWrite`; the next `sceSslConnect` resumes the handshake, and `sceSslWrite` returns the part it sent before blocking (the console returned 32 KiB of a 64 KiB write). A blocking libSceNet socket whose `SO_RCVTIMEO` expires returns `SSL_ERROR_WANT_POLLIN` as well, and the call can be repeated; `SSL_ERROR_READ_TIMEOUT` (0x80951710) is never returned. libkernel keeps its timeouts above the host socket, so they do not bound SSL reads and writes on a libkernel socket. `sceSslRead` with flags 0 reads until `len` bytes arrived or the stream ended and with any other flags (`MsgFlagPartialRead` 1 measured, 2 behaved the same) returns what the first record delivers; a non-blocking read that got some bytes returns them. It returns 0 after the server's close_notify and after a transport that ends without one, every later read 0 again, and `SSL_ERROR_EOF` (0x8095177C) and `SSL_ERROR_SOCKET_CLOSED` (0x8095170D) never from a read. A reset (`ECONNRESET`, `EPIPE`, `WSAECONNRESET`, `WSAECONNABORTED`, `WSAENETRESET`) returns `SCE_NET_ERROR_ECONNRESET` (0x80410136) from `sceSslRead` and `SCE_NET_ERROR_EPIPE` (0x80410120) from `sceSslWrite`, on every later call too; a record that fails its MAC returns 0x80951D55 from `sceSslRead` after the fatal alert goes out. `sceSslWrite` accepts flags 0 only, `SSL_ERROR_INVALID_VALUE` otherwise, sends all of `len`, capped at `INT_MAX`, and returns the count; it keeps writing after the peer's close_notify or a stream that ended without one, as the console does. A fatal alert during the handshake returns `SSL_ERROR_FATAL_ALERT` and ends the transport: later `sceSslConnect` calls return 0, `sceSslRead` 0 and `sceSslWrite` `SSL_ERROR_SOCKET_CLOSED`, as measured. A transport that ends without close_notify or a reset during the handshake returns `SSL_ERROR_SOCKET_CLOSED`, the session is dropped and the next `sceSslConnect` starts over; those two handshake cases and `SSL_ERROR_INVALID_CERT` (0x8095F00A) for a verification failure no option names were not measured. Other handshake and socket failures, a read or write after a failed verification, a write after `sceSslClose`, a null buffer and a read or write from inside the callback throw, as their console codes are unknown. A connection serializes its reads and writes, so a read blocked in one thread holds back a write on the same connection from another. `sceSslDeleteConnection` sends no close_notify and returns `SSL_ERROR_INVALID_ID` when repeated. `sceSslTerm` leaves the context's connections to `sceSslDeleteConnection`, which then returns `SSL_ERROR_INVALID_ID`; a second `sceSslTerm` returns `SSL_ERROR_INVALID_ID` and `sceSslInit` with a pool size of 0 returns `SCE_KERNEL_ERROR_EINVAL` (0x80020016). `sceSslSetVerifyCallback` accepts any callback, null included, and returns 0. The console's first `sceSslConnect` in a process took 23.8 s before its ClientHello left, a one-time initialisation not emulated. `SSL_ERROR_BEFORE_INIT` (0x8095F001) is never returned, as the library keeps no state apart from its contexts +- [sceSslCreateConnection, sceSslDeleteConnection, sceSslSetVerifyCallback, sceSslConnect, sceSslRead, sceSslWrite](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - imported by `ffmpeg.prx`. Signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/Http.cs#L195-L252), which also says the socket stays open after `sceSslDeleteConnection`. The behaviour below was measured on a PS5 on system software 13.40 with a ps5-payload-dev payload (every call printed with its return, a TLS 1.2 test server on the LAN presenting leaves issued by the test fixture root loaded with `sceSslLoadCert`, from an elfldr process rather than a title); what differs for a title process is unknown. An unknown context returns `SSL_ERROR_INVALID_ID` (0x8095F006) from `sceSslCreateConnection`, and every connection call on an unknown or deleted connection id or on a connection whose context was terminated returns `SSL_ERROR_INVALID_ID`. `sceSslCreateConnection` returns `SSL_ERROR_INVALID_VALUE` (0x8095F007) for a null host and does not check the socket: `sceSslConnect` on a descriptor that is not a socket returns `SCE_NET_ERROR_EBADF` (0x80410109). `sceSslConnect` runs a TLS 1.2 client handshake with Mbed TLS 3.6.7 over the host socket that the guest number maps to in the libkernel or libSceNet socket table, and `sceSslRead` and `sceSslWrite` carry application data over it. TLS 1.3 is compiled out: the console offers TLS 1.2 only (its ClientHello carries 12 suites and no supported_versions extension) and a TLS 1.3-only server gets `SSL_ERROR_FATAL_ALERT` (0x8095F010) from it, as from this build. Connection state is allocated from the context's pool and returned to it by `sceSslDeleteConnection`. The console's pool use for one connection with a two-certificate chain peaked at 80,544 bytes in a 256 KiB pool (5,568 after `sceSslInit`, 12,000 after loading one CA); a 16 KiB pool fails the connect with `SSL_ERROR_OUT_OF_MEMORY` (0x809517D5) there and here. The server's chain is checked against the CAs loaded with `sceSslLoadCert`, the host name the connection was created with (case-insensitively; an IP literal sends no SNI and matches an IP subject alternative name; an empty host sends an empty SNI and fails `CN_CHECK`) and, when enabled, its validity dates, and `sceSslConnect` returns the first failure the connection's verify options select (the verify option entry below lists the bits, codes and initial options); no root bundle is shipped and the console's built-in roots are unknown, so with no CA loaded a connection with the initial options returns `SSL_ERROR_UNKNOWN_CA` (0x8095F00C), as on the console. A client certificate loaded with `sceSslLoadCert` is sent when the server asks for one. A failed verification is final: close_notify is sent, later `sceSslConnect` calls return the same code (also after the `WANT_POLLIN` rounds of a non-blocking socket), and `sceSslRead` and `sceSslWrite` throw, as the console's codes for them were not measured. With a verify callback set it is called once after the handshake, outside the connection's lock, with the context id, the bits of the enabled checks that failed (`KNOWN_CA_CHECK` 0x20, `CN_CHECK` 0x4, the date checks when enabled, 0 when the chain passed), handles to a copy of the peer chain as received (the leaf first, the root last when the server sent it) that the certificate readers of the connection's context accept until the connection is deleted (the handles stay registered in the context until it is terminated; the console frees them at the delete), the chain length and the user argument. A chain fault that no option names (`SSL_ERROR_INVALID_CERT` without a callback) throws when a callback is set, as the bits the console passes for it were not measured. A non-negative return accepts the connection whatever the bits were; a negative return fails the connect with `SSL_ERROR_APPLICATION_VERIFICATION_CALLBACK_CHECK` (0x8095F0FD) and the callback runs again, with the same chain, from every later `sceSslConnect`, `sceSslRead` and `sceSslWrite` of the connection, which return 0x8095F0FD while it keeps refusing and proceed once it accepts. A `sceSslConnect` from inside the callback, or from another thread while it runs, throws. `sceSslRead` and `sceSslWrite` before `sceSslConnect` run the handshake and verification themselves and return its code when it fails. A non-blocking socket that would block returns `SSL_ERROR_WANT_POLLIN` (0x8095F013) or `SSL_ERROR_WANT_POLLOUT` (0x8095F014) from `sceSslConnect`, `sceSslRead` and `sceSslWrite`; the next `sceSslConnect` resumes the handshake, and `sceSslWrite` returns the part it sent before blocking (the console returned 32 KiB of a 64 KiB write). A blocking libSceNet socket whose `SO_RCVTIMEO` expires returns `SSL_ERROR_WANT_POLLIN` as well, and the call can be repeated; `SSL_ERROR_READ_TIMEOUT` (0x80951710) is never returned. libkernel keeps its timeouts above the host socket, so they do not bound SSL reads and writes on a libkernel socket. `sceSslRead` with flags 0 reads until `len` bytes arrived or the stream ended and with flags 1 or 2 returns what the first record delivers. Native flags 3 peek without consuming data; values outside 0 through 3 return `SSL_ERROR_INVALID_VALUE` before connection lookup. A non-blocking read that got some bytes returns them. It returns 0 after the server's close_notify and after a transport that ends without one, every later read 0 again, and `SSL_ERROR_EOF` (0x8095177C) and `SSL_ERROR_SOCKET_CLOSED` (0x8095170D) never from a read. A reset (`ECONNRESET`, `EPIPE`, `WSAECONNRESET`, `WSAECONNABORTED`, `WSAENETRESET`) returns `SCE_NET_ERROR_ECONNRESET` (0x80410136) from `sceSslRead` and `SCE_NET_ERROR_EPIPE` (0x80410120) from `sceSslWrite`, on every later call too; a record that fails its MAC returns 0x80951D55 from `sceSslRead` after the fatal alert goes out. `sceSslWrite` accepts flags 0 only, `SSL_ERROR_INVALID_VALUE` otherwise, sends all of `len`, capped at `INT_MAX`, and returns the count; it keeps writing after the peer's close_notify or a stream that ended without one, as the console does. A fatal alert during the handshake returns `SSL_ERROR_FATAL_ALERT` and ends the transport: later `sceSslConnect` calls return 0, `sceSslRead` 0 and `sceSslWrite` `SSL_ERROR_SOCKET_CLOSED`, as measured. A transport that ends without close_notify or a reset during the handshake returns `SSL_ERROR_SOCKET_CLOSED`, the session is dropped and the next `sceSslConnect` starts over; those two handshake cases and `SSL_ERROR_INVALID_CERT` (0x8095F00A) for a verification failure no option names were not measured. Other handshake and socket failures, a read or write after a failed verification, a write after `sceSslClose`, a null buffer and a read or write from inside the callback throw, as their console codes are unknown. A connection serializes its reads and writes, so a read blocked in one thread holds back a write on the same connection from another. `sceSslDeleteConnection` sends no close_notify and returns `SSL_ERROR_INVALID_ID` when repeated. A second `sceSslTerm` returns `SSL_ERROR_INVALID_ID` and `sceSslInit` with a pool size of 0 returns `SCE_KERNEL_ERROR_EINVAL` (0x80020016). `sceSslSetVerifyCallback` accepts any callback, null included, and returns 0. The console's first `sceSslConnect` in a process took 23.8 s before its ClientHello left, a one-time initialisation not emulated. `SSL_ERROR_BEFORE_INIT` (0x8095F001) is never returned, as the library keeps no state apart from its contexts - [sceSslClose](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - takes the connection id, as in [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/Http.cs#L195-L252). Measured on 13.40 as the connection entry describes: it sends close_notify once if the handshake completed and returns 0, 0 again when repeated and 0 on a connection that never connected, already failed verification or lost its transport to a fatal alert; a close before the handshake changes nothing, so the connection can still connect. After the close `sceSslConnect` returns 0 and `sceSslRead` 0, as on the console; `sceSslWrite` after it throws, as the console's code was not measured. A close_notify that a non-blocking socket cannot take at once returns `SSL_ERROR_WANT_POLLOUT` (0x8095F014) and the close can be repeated; after a reset it returns `SCE_NET_ERROR_ECONNRESET` (0x80410136) every time. The socket is left to the title; an unknown connection returns `SSL_ERROR_INVALID_ID` (0x8095F006). Whether the console closes the socket is unknown -- [sceSslGetSerialNumber, sceSslGetSubjectName, sceSslGetIssuerName, sceSslGetNameEntryCount, sceSslGetNameEntryInfo, sceSslFreeSslCertName, sceSslGetNotBefore, sceSslGetNotAfter, sceSslGetFingerprint](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - signatures and context ownership follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/Http.cs#L293-L350), with hash types matching its `SceSslFingerprintHashAlgorithm` enum. Operations take a context handle as their first argument and operate on certificate and name handles scoped to that context. `sceSslGetSerialNumber` copies into a caller-allocated output buffer or queries the required length when the buffer is null or capacity is zero; a buffer smaller than the serial number returns `SSL_ERROR_INVALID_ARG` (0x8095177a). Mbed TLS parses the certificate's serial number, validity period (converted to microseconds since Gregorian year 0001 epoch), fingerprint digests and X.509 subject and issuer distinguished names, writing the attribute long names the console writes (`commonName`, `organizationName`, `countryName`, measured on 13.40 through the verify callback's chain handles; numeric strings for attributes Mbed TLS cannot name) and value strings with `valueLen` set to the value's length. An unknown context, unknown or expired certificate handle or invalid certificate name handle throws; null output pointers, unsupported hash types or out-of-range entry indices return `SSL_ERROR_INVALID_ARG` (0x8095177a). `sceSslGetSerialNumber` with a buffer large enough leaves `*sboLen` as the caller set it, as measured on 13.40 with a 32-byte buffer. The error codes, the zero-capacity length query, the NUL-terminated truncation of name entry strings, and the lifetime of name handles (released only by `sceSslFreeSslCertName` or when the context is terminated) have not been measured on a PS5 -- [sceSslLoadCert, sceSslGetCaList, sceSslFreeCaList, sceSslGetCaCerts, sceSslFreeCaCerts, sceSslUnloadCert](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - signatures and layouts follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs). PPSA15508/01.002.000 calls `sceSslLoadCert` with one 1,408-byte PEM CA and null client certificate/key arguments; this confirms that call shape, not console error behavior. Mbed TLS 3.6.7 parses DER/PEM chains and checks an optional client certificate/key pair. Imports are retained per context and committed together; a failed import preserves existing credentials. CA queries return independently owned snapshots; unload clears active credentials while outstanding snapshots remain valid. Appending repeated CA imports, snapshot lifetime across unload, memory overhead and error precedence have not been measured on a PS5. Invalid loads, malformed material, exhausted pools and unsupported keys throw rather than inventing console error codes. No root bundle is shipped; an empty context still returns `NOT_FOUND`. The existing null-list error, unknown-context behavior and legacy clearing of foreign raw CA lists remain unchanged. Certificate reader queries reflect active certificates or outstanding snapshots. Loading credentials does not start a TLS connection; `sceSslConnect` verifies the server against the loaded CAs and offers the loaded client certificate -- [sceSslEnableVerifyOption, sceSslDisableVerifyOption, sceSslSetMinSslVersion, sceSslSetAlpn, sceSslGetAlpnSelected](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/Http.cs), the source of the other connection signatures; the behaviour was measured on a PS5 on system software 13.40 with a ps5-payload-dev payload (every call printed with its return, a TLS 1.2 test server on the LAN presenting leaves issued by the test fixture root loaded with `sceSslLoadCert`, from an elfldr process rather than a title). The verify options select which verification failures `sceSslConnect` reports (see the connection entry above), with the bit values SharpProspero's `SceSslVerifyOption` gives: `SERVER_VERIFY` 0x1, `CN_CHECK` 0x4, `NOT_AFTER_CHECK` 0x8, `NOT_BEFORE_CHECK` 0x10, `KNOWN_CA_CHECK` 0x20. The initial options are `SERVER_VERIFY`, `CN_CHECK` and `KNOWN_CA_CHECK` (0x25): the console accepted an expired and a not-yet-valid server certificate until 0x8 or 0x10 was enabled, which then returned `SSL_ERROR_NOT_AFTER_CHECK` (0x8095F00D) and `SSL_ERROR_NOT_BEFORE_CHECK` (0x8095F00E); whether 0x2 (`CLIENT_VERIFY`) starts enabled was not determined and it is kept off. A check that is disabled is not reported, whatever the chain. Bits outside 0x3F return `SSL_ERROR_INVALID_VALUE` (0x8095F007) from both calls. Disabling 0x1, 0x4 or 0x20 from the payload stopped its process through the kernel's debug exception path (`mDBG: Sending signal ... signo: 0xa0020101`, 6 of 6 attempts; disabling 0x2, 0x8 and 0x10 returned 0), so whether a title may clear them is unknown; this build clears them, as titles import the call. `sceSslSetMinSslVersion` accepts the `SceSslVersion` values 2 (SSL 3.0) to 5 (TLS 1.2) and returns `SSL_ERROR_INVALID_VALUE` for 0, 1 and anything above 5; the console has no TLS 1.3 value. Every accepted value gives the same handshake, as Mbed TLS 3 speaks TLS 1.2 and nothing older and TLS 1.3 is compiled out, which matches the console offering TLS 1.2 only. `sceSslSetAlpn` stores the string up to its terminator, split on commas into the protocols offered in the handshake (the console sent `h2,http/1.1` as the two names and a wire-format string as one name), a second call replaces the list, an empty string returns `SSL_ERROR_BAD_LENGTH` (0x80951773), and a null list throws (not measured). `sceSslGetAlpnSelected` returns the protocol the server selected, still after `sceSslClose`, or null before the handshake and when none was selected; the pointer is owned by the connection. An unknown connection and a connection whose context was terminated return `SSL_ERROR_INVALID_ID` (0x8095F006) from the four setters and throw from `sceSslGetAlpnSelected`, whose console return was not measured -- [sceSslGetPem](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - static inspection of PPSA28002 confirms the context-first four-argument call with a caller-owned buffer and an in/out 64-bit length. Mbed TLS serializes PEM including its trailing NUL. Buffers smaller than the required encoding throw; an unknown context or foreign or expired certificate handle throws. The exact console PEM length policy, error precedence and truncation behavior have not been measured on a PS5. Existing serial/name, validity and fingerprint readers and their documented uncertainties remain unchanged +- [Certificate readers](../../../core/libs/prx/libSceSsl/Export.cpp): foreign and expired certificate/name handles throw instead of following native unchecked pointers. Name views track certificate lifetime and preserve identity; the native no-op name release is implemented. View storage remains allocated until context destruction. Numeric names for attributes Mbed TLS cannot identify may differ from native names. Fingerprint stack-space validation and backend digest failures are not reproduced. Signatures, valid-context/null-argument precedence, serial truncation, name copy bounds, validity timestamps and supported hash selectors are confirmed by the supplied module. +- [sceSslLoadCert, sceSslGetCaList, sceSslFreeCaList, sceSslGetCaCerts, sceSslFreeCaCerts, sceSslUnloadCert](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - signatures and layouts follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs). PPSA15508/01.002.000 calls `sceSslLoadCert` with one 1,408-byte PEM CA and null client certificate/key arguments; this confirms that call shape, not console error behavior. Mbed TLS parses DER/PEM chains. CA imports still commit together instead of preserving successful entries before a later failure. Client certificate replacement clears the previous identity; raw private keys load independently and are parsed when configuring TLS. Partial client chains on decode failure, native key parse errors and incomplete identities during TLS setup remain unsupported. CA queries return independently owned snapshots; unload clears active credentials while outstanding snapshots remain valid. Appending repeated CA imports, snapshot lifetime across unload, memory overhead and error precedence have not been measured on a PS5. Invalid loads, malformed material, exhausted pools and unsupported keys throw rather than inventing console error codes. Native CA queries include built-in roots; the host still queries loaded authorities and returns `NOT_FOUND` for an empty context. No native root bundle is copied. Null-output and invalid-context precedence, output preservation on invalid contexts and the SDK 7.50 unload error boundary are recovered. Legacy clearing of foreign raw CA lists remains unchanged. Certificate reader queries reflect active certificates or outstanding snapshots. Loading credentials does not start a TLS connection; `sceSslConnect` verifies the server against the loaded CAs and offers the loaded client certificate +- [sceSslEnableVerifyOption, sceSslDisableVerifyOption, sceSslSetMinSslVersion, sceSslSetAlpn, sceSslGetAlpnSelected](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/Http.cs), the source of the other connection signatures; the behaviour was measured on a PS5 on system software 13.40 with a ps5-payload-dev payload (every call printed with its return, a TLS 1.2 test server on the LAN presenting leaves issued by the test fixture root loaded with `sceSslLoadCert`, from an elfldr process rather than a title). The verify options select which verification failures `sceSslConnect` reports (see the connection entry above), with the bit values SharpProspero's `SceSslVerifyOption` gives: `SERVER_VERIFY` 0x1, `CN_CHECK` 0x4, `NOT_AFTER_CHECK` 0x8, `NOT_BEFORE_CHECK` 0x10, `KNOWN_CA_CHECK` 0x20. The initial options are `SERVER_VERIFY`, `CN_CHECK` and `KNOWN_CA_CHECK` (0x25): the console accepted an expired and a not-yet-valid server certificate until 0x8 or 0x10 was enabled, which then returned `SSL_ERROR_NOT_AFTER_CHECK` (0x8095F00D) and `SSL_ERROR_NOT_BEFORE_CHECK` (0x8095F00E); whether 0x2 (`CLIENT_VERIFY`) starts enabled was not determined and it is kept off. A check that is disabled is not reported, whatever the chain. Bits outside 0x3F return `SSL_ERROR_INVALID_VALUE` (0x8095F007) from both calls. Disabling 0x1, 0x4 or 0x20 from the payload stopped its process through the kernel's debug exception path (`mDBG: Sending signal ... signo: 0xa0020101`, 6 of 6 attempts; disabling 0x2, 0x8 and 0x10 returned 0), so whether a title may clear them is unknown; this build clears them, as titles import the call. `sceSslSetMinSslVersion` accepts the `SceSslVersion` values 2 (SSL 3.0) to 5 (TLS 1.2) and returns `SSL_ERROR_INVALID_VALUE` for 0, 1 and anything above 5; the console has no TLS 1.3 value. Every accepted value gives the same handshake, as Mbed TLS 3 speaks TLS 1.2 and nothing older and TLS 1.3 is compiled out, which matches the console offering TLS 1.2 only. `sceSslSetAlpn` stores the string up to its terminator, split on commas into the protocols offered in the handshake (the console sent `h2,http/1.1` as the two names and a wire-format string as one name), a second call replaces the list, an empty string returns `SSL_ERROR_BAD_LENGTH` (0x80951773), and a null list clears the configured protocols. Native inspection confirms that replacement clears the old list even on failure, that each comma-separated name must contain 1-255 bytes and that the whole string must be shorter than 65535 bytes. `sceSslGetAlpnSelected` returns the protocol the server selected, still after `sceSslClose`, or null before the handshake and when none was selected; the pointer is owned by the connection. An unknown connection and a connection whose context was terminated return `SSL_ERROR_INVALID_ID` (0x8095F006) from the four setters and null from `sceSslGetAlpnSelected`, as confirmed by the supplied native module +- [sceSslGetPem](../../../core/libs/prx/libSceSsl/Export.cpp): native argument validation, CRLF line endings, 64-character Base64 lines, NUL-inclusive length and buffer clearing on insufficient capacity are recovered. Foreign and expired certificate handles throw instead of performing native unchecked pointer access. DER reserialization differences for noncanonical certificates remain unverified. + +- This pending declaration has an unverified signature and behavior; it is a parameterless import-resolution declaration and throws through `NotImplemented_nid_no_patch`: [`sceSslSetServerName`](../../../core/libs/prx/libSceSsl/Export.cpp). diff --git a/docs/dev/debt/libSceSulphaDrv.md b/docs/dev/debt/libSceSulphaDrv.md index 88b6f9091f..990eaa1951 100644 --- a/docs/dev/debt/libSceSulphaDrv.md +++ b/docs/dev/debt/libSceSulphaDrv.md @@ -1,5 +1,5 @@ # libSceSulphaDrv technical debt -### Inventory exports declared elsewhere +### Unknown function info -sceSulphaGetClientInfo, sceSulphaGetConnectedClientCount, sceSulphaGetDefaultConfig, sceSulphaGetNeededMemory, sceSulphaInit, sceSulphaMessage, sceSulphaSetBookmark and sceSulphaShutdown are the complete PS5 3.20 export table of this module per the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSulphaDrv.c), but all eight are currently declared by #7416 in libSceSulpha, so this module keeps its `APS5_DUMMY_FUN` skeleton to respect the duplicate-export rule. Declare them here (with no parameters, matching the `1b5004d77` unknown-prototype shape) if that PR's module attribution is rejected. +- [libSceSulphaDrv](../../../core/libs/prx/libSceSulphaDrv/Export.cpp) - signatures and behavior remain unknown for `sceSulphaGetClientInfo`, `sceSulphaGetConnectedClientCount`, `sceSulphaGetNeededMemory`, `sceSulphaInit`, `sceSulphaMessage`, `sceSulphaSetBookmark` and `sceSulphaShutdown`. Their no-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch`; they make no ABI claim. diff --git a/docs/dev/debt/libSceSysCore.md b/docs/dev/debt/libSceSysCore.md index 0ebd352ebd..386b64d98d 100644 --- a/docs/dev/debt/libSceSysCore.md +++ b/docs/dev/debt/libSceSysCore.md @@ -2,4 +2,16 @@ ### Unknown function info -- [sceApplicationGetAppInfoByAppId](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - the name and NID are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c), but no credible prototype was found. The declaration has no parameters solely to resolve a known import and makes no ABI claim +- [sceApplicationGetAppInfoByAppId](../../../core/libs/prx/libSceSysCore/Export.cpp) takes a 32-bit application ID and a writable 152-byte record. The service fills the record through IPC; its complete field layout remains unresolved. The SystemService caller consumes a subrecord beginning at byte 48. + +- [_sceApplicationGetAppId, sceApplicationAddProcess2, sceApplicationBeginSclkChange, sceApplicationBlockingKill2, sceApplicationBlockingKill3, sceApplicationContinue, sceApplicationCrashSyscore, sceApplicationCrashSystemProcess](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationEndSclkChange, sceApplicationExitSpawn, sceApplicationExitSpawn2, sceApplicationFinalize, sceApplicationGetAppPackageInfoForCoredump, sceApplicationGetCompiledSdkVersionByAppId and sceApplicationGetCoredumpDirAndFileName](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationGetCoredumpState, sceApplicationGetCpuUsage, sceApplicationGetCpuUsage2, sceApplicationGetDbgExecutablePath, sceApplicationGetMode, sceApplicationGetProcs and sceApplicationGetShellCoreAppId](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationGetStateForDebugger, sceApplicationInitialize, sceApplicationInitializeForShellCore, sceApplicationIsCrashReportBeingProcessed, sceApplicationIsPrimaryProcess, sceApplicationIsResumable and sceApplicationIsSuspendable](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationKickCoredump, sceApplicationKickCoredump2, sceApplicationKill, sceApplicationLocalProcessKill, sceApplicationLocalProcessKill2, sceApplicationLocalProcessResume and sceApplicationLocalProcessSuspend](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationNotifyCoredumpRequestEnd, sceApplicationNotifyCoredumpRequestProgress, sceApplicationNotifyVshMainOnStandby, sceApplicationNotifyVshReady, sceApplicationOnAllResourceReleased and sceApplicationRaiseException](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationRaiseExceptionToLocalPid, sceApplicationRaiseExceptionToPid, sceApplicationRequestToChangeRenderingMode, sceApplicationRestore, sceApplicationResume, sceApplicationSave and sceApplicationSendDebugSpawnResult2](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationSendResultOfDebuggerKillRequest, sceApplicationSendResultOfDebuggerResumeRequest, sceApplicationSendResultOfDebuggerSuspendRequest, sceApplicationSendResultOfDebuggerTitleIdLaunchRequest and sceApplicationSetApplicationFocus](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationSetControllerFocus, sceApplicationSetControllerFocusPermissionToSubProcess, sceApplicationSetMemoryPstate, sceApplicationSignalShellCoreHeartBeat, sceApplicationSpawn2 and sceApplicationSuspend](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationSuspendSystemForDebugger, sceApplicationSwitchToBaseMode, sceApplicationSwitchToNeoMode, sceApplicationSwitchToNeoMode2, sceApplicationSystemReboot and sceApplicationSystemShutdown2](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceApplicationSystemSuspend, sceApplicationSystemSuspend2, sceApplictionGetStateForDebugger, sceSysCoreGetEventCount and sceSysCoreReceiveEvent](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - The list is the intersection of the export table of the console's libSceSysCore (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. diff --git a/docs/dev/debt/libSceSystemGesture.md b/docs/dev/debt/libSceSystemGesture.md index e0bc2e09c2..5d9acec690 100644 --- a/docs/dev/debt/libSceSystemGesture.md +++ b/docs/dev/debt/libSceSystemGesture.md @@ -2,9 +2,9 @@ ### Silent stubs -- [System gesture recognizers](../../../core/libs/prx/libSceSystemGesture/Export.cpp) (libSceSystemGesture) - `sceSystemGestureGetTouchEvents`, `GetTouchEventsCount`, `GetTouchEventByIndex`, `GetTouchEventByEventID` and the recognizer append, remove, reset and update calls report that no recognizer ever matches, so a title receives primitive touch events but no tap, drag, hold, pinch, rotation or flick event. Handle and pointer arguments are still checked across all query entry points. Error codes follow the 0x80D1 values main already uses in this file; KytyPS5 uses 0x8089 (INVALID_ARGUMENT 0x80890001, INVALID_HANDLE 0x80890006), and which one the console returns is not verified. `sceSystemGestureGetTouchRecognizerInformation` returns the gesture type and rectangle given to `sceSystemGestureCreateTouchRecognizer` or `UpdateTouchRecognizerRectangle`, stored in the title's recognizer buffer in a layout private to this file, and always reports `updated_time` 0 because no recognizer is ever updated with input. It throws for a recognizer that was not created by `sceSystemGestureCreateTouchRecognizer` or has no rectangle, since the console's result in those cases is not known +- [System gesture recognizers](../../../core/libs/prx/libSceSystemGesture/Export.cpp) (libSceSystemGesture) - `sceSystemGestureGetTouchEvents`, `GetTouchEventsCount`, `GetTouchEventByIndex`, `GetTouchEventByEventID` and the recognizer append, remove, reset and update calls report that no recognizer ever matches, so a title receives primitive touch events but no tap, drag, hold, pinch, rotation or flick event. Handle and pointer arguments are still checked across all query entry points. `sceSystemGestureGetTouchRecognizerInformation` returns the gesture type and rectangle given to `sceSystemGestureCreateTouchRecognizer` or `UpdateTouchRecognizerRectangle`, stored in the title's recognizer buffer in a layout private to this file, and always reports `updated_time` 0 because no recognizer is ever updated with input. It throws for a recognizer that was not created by `sceSystemGestureCreateTouchRecognizer` or has no rectangle, since the console's result in those cases is not known ### Unknown function info -- [Primitive touch events](../../../core/libs/prx/libSceSystemGesture/Export.cpp) (libSceSystemGesture) - `sceSystemGestureUpdatePrimitiveTouchRecognizer` reads the pad reports the title passes in, `{int32 padHandle, int32 reportNumber, const ScePadData* padDataBuffer, 8 reserved bytes}`, as [fpPS4](https://github.com/red-prig/fpPS4/blob/585d06fa7c04698c41272b95a696a14c4180e13e/src/ps4_libscesystemgesture.pas) records `SceSystemGestureTouchPadData`; `padHandle` is not checked and a report of a pad other than the one the title opened is not told apart. The touch states 0 inactive, 1 begin, 2 active and 3 end are the Vita ones in [RPCS3](https://github.com/RPCS3/rpcs3/blob/38c444cfa1dbe152daa63b1703c12172078cd056/rpcs3/Emu/PSP2/Modules/sceSystemGesture.h), whose recognizer, rectangle and primitive event layouts are the ones main already uses, and no public source states the PS5 values. A primitive keeps the touch id the pad reports, its positions are the pad's touchpad coordinates unscaled, `delta_time` and `elapsed_time` come from the reports' own `timestamp`, and `is_updated` is 1 for a primitive whose state or position changed in the update; the console's units, scaling and `is_updated` rule are unverified. An ended touch stays readable as one `end` event until the next report sampled drops it. A null `pInputData`, a null `padDataBuffer` or a `reportNumber` of 0 or less leaves the events unchanged and returns success, an event buffer shorter than the event count reports as many events as it holds, and `sceSystemGestureGetPrimitiveTouchEventByPrimitiveID` throws for an id that no tracked touch has; what the console answers in those cases is not known +- [Primitive touch events](../../../core/libs/prx/libSceSystemGesture/Export.cpp): pad handles are not validated against native controller information. Begin, active and end states are confirmed, but cancellation state 4, motion thresholds and SDK-dependent duplicate-report handling remain unimplemented. Timing, coordinate conversion and update flags still use the host approximation. Recognizer allocation and native descriptor layouts remain incomplete. - [sceSystemGestureDebugGetVersion](../../../core/libs/prx/libSceSystemGesture/Export.cpp) (libSceSystemGesture) - its name and NID are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSystemGesture.c), but no credible prototype was found. It is declared without parameters solely to resolve known imports; this is not an ABI claim diff --git a/docs/dev/debt/libSceSystemLogger2.md b/docs/dev/debt/libSceSystemLogger2.md new file mode 100644 index 0000000000..3d17ac6069 --- /dev/null +++ b/docs/dev/debt/libSceSystemLogger2.md @@ -0,0 +1,5 @@ +# libSceSystemLogger2 technical debt + +### Unknown function info + +- [sceSystemLogger2GetTimestamp, sceSystemLogger2Initialize, sceSystemLogger2SetEventAsync, sceSystemLogger2SetEventAsyncEx and sceSystemLogger2Terminate](../../../core/libs/prx/libSceSystemLogger2/Export.cpp) (libSceSystemLogger2) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSystemLogger2.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceSystemService.md b/docs/dev/debt/libSceSystemService.md index 2e396200ca..40dfa92cfa 100644 --- a/docs/dev/debt/libSceSystemService.md +++ b/docs/dev/debt/libSceSystemService.md @@ -2,6 +2,8 @@ ### Silent stubs +- [sceShellCoreUtilActivateAbort, sceShellCoreUtilActivateStart, sceShellCoreUtilActivateStartAsync, sceShellCoreUtilActivateTerm](../../../core/libs/prx/libSceSystemService/PendingExports.cpp) return zero with the recovered handle argument. Activation service state is not modeled. + - [libSceSystemService](../../../core/libs/prx/libSceSystemService/Export.cpp) - `sceSystemServicePowerTick`, `sceSystemServiceReportAbnormalTermination`, `sceSystemServiceDisableMusicPlayer`, `sceSystemServiceReenableMusicPlayer`, `sceSystemServiceDisableMediaPlay` and `sceSystemServiceReenableMediaPlay` return success and do nothing: the host has no auto power-off timer to reset, no system crash reporter to notify, no system music player to pause or resume and no system media playback to disable. PPSA01521 calls `sceSystemServiceDisableMediaPlay` during start-up, and its wrappers for both pass no arguments - [sceSystemServiceLaunchWebBrowser](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - AnyPS5 launches no web browser, so the call fails and nothing is shown. RommPS PPSA76677 then tells the player to scan its QR code instead - [sceSystemServiceShowControllerSettings](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - returns success without showing the controller settings: AnyPS5 has no system UI to open. Cat Quest III (PPSA15065) imports it @@ -12,14 +14,16 @@ ### Unknown function info - [sceSystemServiceAddLocalProcessForPsmKit, sceSystemServiceKillLocalProcessForPsmKit](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - AnyPS5 runs one process and starts or stops no other, so both throw without reading their arguments. The parameters follow Forever Launcher's own firmware 5.50 probes (`ps5/probes/localproc` in [bizkut/ps5-forever](https://github.com/bizkut/ps5-forever)): a path, a null-terminated argv and a 0x90-byte parameter block in, a handle out; the kill takes that handle. Their results on the console are unknown. Forever Launcher (PPSA99030) imports both and stops at load without them. +- [sceSystemServiceGetAppId, sceSystemServiceGetAppTitleId, sceLncUtilGetAppTitleId](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the console's results for a title ID or app ID other than the running title are unknown, and so are its errors for null pointers, so those calls throw. `sceSystemServiceGetAppId` compares the title ID exactly with the one in `param.json` or `param.sfo`. The title ID buffer is 10 bytes, so a running title ID of 10 or more characters throws too. The signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/SystemService/SystemService.cs#L236-L260), except that `sceLncUtilGetAppTitleId` returns an `int`, as declared in the pending exports, and 0 on success like `sceSystemServiceGetAppTitleId`; SharpProspero names `ErrorNoAppInfo` (0x80A10009) only for `sceSystemServiceGetAppFocusedAppStatus`. - [sceSystemServiceLaunchApp](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - AnyPS5 runs one title and cannot start another, so every call throws without reading its arguments. The (title ID, null-terminated argv, launch parameters) signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/SystemService/SystemService.cs#L141-L148), whose `AppLauncher` Prospero Explorer (PPSA99109) and Prospero Multi Tools (PPSA99110) import it through and which passes a 0x20-byte launch context or null; the console's argument checks and errors are unknown -- [sceSystemServiceLaunchWebBrowser](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the console's error for an unavailable web browser is unknown; it returns the kernel's `EOPNOTSUPP` (0x8002002D) for any arguments. The (uri, param) signature follows [RommPS](https://github.com/s0liton/RommPS/blob/754d4d60f25938a5136b9e2efcd074313c4386c5/platform/ps5/app/rommps/examples/rommps/kit/credits.cpp#L86-L95), which passes a URL and a null param and only tests the result for zero; the param layout and the console's argument checks are unknown +- [sceSystemServiceLaunchWebBrowser](../../../core/libs/prx/libSceSystemService/Export.cpp): the native export takes one URI argument. Null input, the 6015-byte length limit and supported scheme checks are recovered. The host still returns kernel `EOPNOTSUPP` for valid input; a missing browser service's native error is unresolved. - [sceSystemServiceParamGetString](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the system name (parameter 6) is the fixed `PS5`, not the console's name. As in a payload process on a PS5 (firmware 13.60), the name is written NUL-padded to the whole buffer, and a buffer under 65 bytes, a null buffer or any other parameter returns `SYSTEM_SERVICE_ERROR_PARAMETER` without writing. Parameters 0 to 40, 100 to 110, 1000 to 1010, -1 and `INT_MAX` were tried there; the others are assumed to fail the same way. PPSA26344 at `0xA87935` passes parameter 6 with a 65-byte buffer -- [sceSystemServiceParamGetInt](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - console language numbers 0 to 29 follow the PS4 SDK enum in [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/06fcf7fd108fa080035cdd3259ed22eb07e0508c/include/orbis/_types/sys_service.h#L54-L83) (21 Arabic). 30 (Ukrainian) is not in it and follows PPSA99505 ([Jelly5](https://github.com/02dnot/Jelly5/blob/552b866d89b2a58b27f7f7b0484dc743ce235526/app/src/app/i18n.cpp#L29-L60)) and PPSA99002 ([ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight/blob/b708bbde3457bef1073a9ab6959957cb691e8e2f/include/i18n.hpp#L252-L309)), whose numbers agree with the enum. Not confirmed on a console, and neither title starts on `main` yet. Parameter 400 returns the same number as parameter 1: PPSA01325 asks for 400 first, asks for 1 when that fails, and uses either answer as an index into the same 31 languages; in the PS5 10.01 and 12.60 `libSceSystemService` modules parameter 400 reads the same registry value as parameter 1, without parameter 1's mapping of newer language numbers by SDK version, and returns 0x80a10003 when an imported check that is not identified returns 1; the 4.03 module has no parameter 400. Neither the mapping nor that error is modelled, and the parameter's name is not known. Without `ANYPS5_LANGUAGE` the language is English (United States); the date format, time format and enter button stay fixed +- [sceSystemServiceParamGetInt](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - console language numbers 0 to 29 follow the PS4 SDK enum in [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/06fcf7fd108fa080035cdd3259ed22eb07e0508c/include/orbis/_types/sys_service.h#L54-L83) (21 Arabic). 30 (Ukrainian) is not in it and follows PPSA99505 ([Jelly5](https://github.com/02dnot/Jelly5/blob/552b866d89b2a58b27f7f7b0484dc743ce235526/app/src/app/i18n.cpp#L29-L60)) and PPSA99002 ([ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight/blob/b708bbde3457bef1073a9ab6959957cb691e8e2f/include/i18n.hpp#L252-L309)), whose numbers agree with the enum. Not confirmed on a console, and neither title starts on `main` yet. Parameter 400 returns the same number as parameter 1: PPSA01325 asks for 400 first, asks for 1 when that fails, and uses either answer as an index into the same 31 languages; in the PS5 10.01 and 12.60 `libSceSystemService` modules parameter 400 reads the same registry value as parameter 1, without parameter 1's mapping of newer language numbers by SDK version, and returns 0x80a10003 when an imported check that is not identified returns 1; the 4.03 module has no parameter 400. Neither the mapping nor that error is modelled, and the parameter's name is not known. Without `ANYPS5_LANGUAGE` the language is English (United States); the date format and time format stay fixed +- [sceSystemServiceParamGetInt](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - parameters 1 to 5, 100 to 111, 200 to 213, 300 and 400 answer (see the integer system parameters entry); every other parameter, and a null value pointer, returns 0x80A10003 without writing the value. Inside a retail title on a PS5 (firmware 13.40, measured 2026-10-19 through ptrace remote calls in the game process) parameters 0, 6, 7 (game parental level), 8 to 20, 401, 999, 1000 (enter button assignment) and 1001 to 1008 returned 0x80A10003, as did a null value pointer; 1 to 5 and 400 answered, and 100 answered the value 0. A sweep of parameters 0 to 2048 in the same way on 2026-10-23 also found 101 to 111, 200 to 213 and 300 answering, each writing 4 bytes; every other parameter, and a null value pointer for 100 to 111, returned 0x80A10003 and left the value as it was - [sceSystemServiceParamGetInt](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the time zone (parameter 4) and summer time (parameter 5) follow the host's local time zone at the moment of the call. Titles compute the local time as UTC plus the time zone in minutes plus 60 minutes when summer time is 1 ([Apollo](https://github.com/bucanero/apollo-ps4/blob/252a9d5a187c463994263009851a179f4f910c49/source/exec_cmd.c#L70-L92), [GoldHEN's frame logger](https://github.com/GoldHEN/GoldHEN_Plugins_Repository/blob/6c3a06f53d9caebadfb0aeed9bb94119012010a7/plugin_src/frame_logger/source/main.c#L60-L82), and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/413d2e3518fb435f40971d238fe0ecae7ee551da/src/SharpProspero/Platform/SystemParameters.cs#L75-L79) calls them the offset from UTC in minutes and whether summer time is in effect), so the time zone is the host's current offset east of UTC less the summer time shift while summer time is in effect: 60 minutes on Linux, and on Windows the C runtime's own summer time bias (`_get_dstbias`), which is also what shifted its local time. For a host zone whose summer time is not one hour, Linux keeps the sum right and the standard offset wrong, and Windows the opposite; how the console reports such a zone is unknown. libkernel's `sceKernelConvertUtcToLocaltime`, `sceKernelConvertLocaltimeToUtc` and `sceKernelGettimezone` still use UTC - [sceSystemServiceInitializePlayerDialogParam](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - a null param throws: what the console does with it is not measured. The console run in [prosper](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/tests/data/console_oracle/nethttp.golden.tsv#L46) shows only the 0x40 bytes written (size 0x40, user ID -1 at 0xC, zeros), so the field names and the mode and target account fields follow [SharpProspero's struct](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/SystemService/SystemService.cs#L637-L655), and the `void` return [its declaration](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/SystemService/SystemService.cs#L419-L426); the console left 0x800693020 in `rax`, not an SCE error code. Closes when: the null case is measured on a PS5 -- [libSceSystemService firmware 3.20 pending exports](../../../core/libs/prx/libSceSystemService/PendingExports.cpp) - 475 symbol names are present in [the PS5 3.20 libSceSystemService export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSystemService.c). Their signatures and console behaviour are unverified. The declarations use the existing no-parameter unknown-signature convention solely to resolve imports, and every call throws through `NotImplemented_nid_no_patch`. +- [libSceSystemService pending exports](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): unreviewed argument lists and service behavior remain unresolved. The C decompilation contains 381 of the 477 pending names; additional exports exist as direct assembly jumps, so absence from that listing does not imply absence from the module. Confirmed argument lists are being restored independently of their protected throwing bodies; remaining empty declarations must not be treated as proof of a zero-argument ABI. - [sceSystemServiceLaunchSystemDeeplink](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceShellCoreUtilLaunchByUriParamInit](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceSystemStateMgrTickVideoPlayback](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature @@ -105,3 +109,49 @@ - [Running application ID](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - `sceSystemServiceGetAppIdOfRunningBigApp` reports 1 as the running title's app ID. The value is AnyPS5's own: the console's shell assigns app IDs and their values are not known here, so other calls that take an app ID must use the same one. `sceSystemServiceKillApp` with that ID and the arguments -1, 0 and 0, which [SwanStationPS5](https://github.com/darkxex/SwanStationPS5/blob/4cdc981d1a64ffb7f2af73f7a27a9e91a3cb5eb4/src/main.c#L1050-L1058) passes to close itself and [ps5-payload-dev's hbldr](https://github.com/ps5-payload-dev/websrv/blob/46374edee6627885abac717de24da7a1d73af6f1/src/ps5/hbldr.c#L425-L435) passes to close the running game, ends the title as its own `exit(0)` would: the guest's shutdown and `atexit` handlers run and the process exits with status 0. On the console the call returns and the shell then ends the title, and whether those handlers run there is unknown; here there is no shell, so the call does not return. Another app ID or other arguments throw. - [sceLncUtilKillApp, sceLncUtilKillAppWithReason](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the running title's app ID ends the title as `sceSystemServiceKillApp` does, which [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/SystemService/SystemService.cs#L219-L227) describes as a wrapper of `sceLncUtilKillApp`; the signatures follow its [launcher utility bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/SystemService/SystemService.cs#L262-L279). Another app ID throws, and so does a reason other than 0, whose meaning and effect on the console are not documented - [LoadExec descriptors](../../../core/libs/prx/libSceSystemService/LoadExec.cpp) (libSceSystemService) - on Linux, a call with an argument list replaces the process with `fexecve`, which first closes every descriptor above 2, and on macOS with `posix_spawn`, which closes them through `POSIX_SPAWN_CLOEXEC_DEFAULT`; a call without one uses `execv`, and the guest's descriptors stay open across it. On both paths descriptor 2 first gets back the host standard error a guest redirect kept, so the runtime's output does not follow the title's redirect (RetroArch's trace file, VLC's log) into the next executable. Which descriptors the console passes to the new process is not known + + +- [LncUtil process and status payloads](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): local-process creation transmits a 72-byte parameter block whose fields remain unresolved. Title-ID output occupies 16 bytes; category output is a four-byte string, while category type and launched-user outputs are 32-bit scalars. The argument lists are recovered through the indirect dispatch targets rather than the decompiler's apparent zero-argument wrappers. + +- [LncUtil event and diagnostic payloads](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): Daemon and shell-UI events are distinct output structures. GetDbgExecutablePath writes up to a 1024-byte protocol output and forwards a 64-bit selector whose meaning is unresolved. Boolean outputs and the two direct boolean budget/mode queries are confirmed. + +- [LncUtil launch and lifecycle payloads](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): Flag values and service-side focus/resume/suspend/exception behavior remain unresolved. Launch and load-exec use null-terminated argument vectors; asynchronous launch/kill write a 32-bit request ID. The argument positions and transported widths are confirmed, but the service operations remain protected stubs. + +- [ShellCoreUtil application data](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): GetAppLaunchTypeInfo writes eight bytes whose fields are unresolved. Application-data and launched-parameter keys are null-terminated strings, with 32-bit capacities/outputs. Format/fsck paths and mini-app return values are strings. File-formatting and service implementations remain protected stubs. + +- [ShellCoreUtil queries](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): launched-parameter strings have signed 32-bit capacities; checker strings use machine-sized capacities and one or two string keys. Cloud status and device-index outputs are 32-bit scalars; the timeout variant consumes one byte. Partition-size output and the direct temporary-content free-size return are 64-bit. The service-side checker protocol remains unresolved. Device-index scene output uses a 32-bit native store despite the managed wrapper using a byte-backed scene enum; the native output width is preserved. + +- [ShellCoreUtil storage and HID queries](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): partition free sizes and required application-content size are 64-bit outputs; fsck progress takes a title ID and a 32-bit progress output. HID string queries transport a 64-bit capacity and 32-bit selector, while output-vector lengths sign-extend the capacity's low 32 bits. HID selectors are configuration indices; the wrapper declares 32-bit capacities while the native code transports 64 bits, so their declared width remains unresolved. IDU mode byte values remain unresolved. Streaming status and the compositor-profiler return are boolean; the compositor-profiler and current GPU-load queries consume no caller arguments. + +- [ShellCoreUtil account and privacy payloads](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): Pbtc user information occupies 800 bytes; privacy-event data occupies eight bytes after two string keys, each limited to 32 characters. The five account-information outputs occupy 8, 104, 136, 1024 and 1032 bytes; their fields remain unresolved. Category-default list output has a byte capacity and unresolved element format. Remote-play/share-play/social-screen outputs are boolean; application display-state queries take a 32-bit application ID. Only the support-site query signature is restored; no endpoint content is included. + +- [ShellCoreUtil telemetry and user queries](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): telemetry-token output is 4112 bytes, including an eight-byte service result at offset 4104; its meaning remains unresolved. UI status is a 64-bit event mask; VM statistics and workaround-file strings use machine-sized capacities. Eye-distance adjustment writes one byte. + +- [ShellCoreUtil launch and status arguments](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): Navigation consumes one 32-bit value, not the phantom vector/register arguments shown by decompilation. Workaround checks and mount requests take strings. Logout-state queries transmit two 32-bit values and return a byte output; selector meanings remain unresolved. + +- [ShellCoreUtil notifications](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): IDU notifications use a signed 64-bit identifier, optional text and a 32-bit type. PSM events carry a 32-bit event ID and a string. + +- [ShellCoreUtil state setters](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): UI updates take a 64-bit status and mask; app data takes two strings. Display-state setters mask their low bits, so source enum names and declared widths cannot be inferred from those masks alone. Streaming/impose/profiler setters test all 32 bits. User-focus reads take an application ID and return its user ID; writes apply a user ID to the current application. + +- [ShellCoreUtil dialog parameters](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): the 24-byte error-dialog parameter contains a message pointer at offset zero, a null-terminated vector of string arguments at offset eight and a byte flag at offset 16. Field offsets and size are asserted; the flag meaning remains unresolved. Start/stop streaming-game and unmount requests take strings. Bus-transfer testing writes one byte after a string input. + +- [SystemService activation and process signatures](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): activation wrappers validate a machine-sized handle and expose 32-bit status/result outputs or a byte activation flag. Shell activation initialization also receives a type selector; managed callers confirm the machine-sized handle consumed by constant-return start/abort/terminate exports. SystemService local-process parameters differ from LncUtil parameters and are kept as distinct types; general creation reads through offset 71, JVM/PS2 creation reads two 32-bit fields at offsets four and eight. Install status writes 12 bytes and application status writes 16 bytes; their remaining field meanings are unresolved. + +- [SystemService query payloads](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): daemon events write 12 bytes; local-process entries occupy 36 bytes, consisting of a 32-bit value and 32-byte name. PS-button extraction reads a 16-byte event and rearranges its fields into a distinct 16-byte output; semantic names remain unresolved. Privacy definition/version queries take string keys and distinct output types; their layouts remain unresolved. Workaround information writes four bytes at offset zero and 16 bytes at offset eight. + +- [SystemService direct aliases](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): assembly-only jumps now share the confirmed signatures of their LncUtil/ShellCoreUtil targets for application-ID lists, remote-play/UI status, navigation, controller-focus permissions and boolean budget/status queries. Aliases of unresolved targets still require target or caller analysis. + +- [LncUtil collapsed dispatch stubs](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): assembly table targets confirm application-ID lookup takes a title-ID string, current/focused status outputs occupy 64 bytes, GPU-crash status occupies 1320 bytes, and dialog activation writes one byte. Shared-memory registration takes a string. The protected service implementations remain unchanged; collapsed assembly procedures are not missing exports. + +- [SystemService launch and telemetry arguments](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): store-launch parameters contain only a four-byte size; web-app launch takes one string argument. Process lifecycle wrappers forward the verified LncUtil argument lists. TelemetrySetData takes a string key, binary payload and machine-sized length; TelemetrySetData2 takes two strings. Token storage consumes a 32-bit identifier and string; only its signature is restored, with no token values included. + +- [SystemStateMgr and modal signatures](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): keep-main-on-standby takes a string, reboot timer consumes a 32-bit mode and wake-up consumes a 16-bit trigger. Suspend/resume progress forwards a string to kernel logging. Disc readiness, shell shutdown progress and standby enablement return booleans. Modal open takes a string and close takes a 32-bit dialog ID. Platform privacy setting writes a 32-bit value; the kernel platform query it calls consumes no caller arguments. + +- [USB storage aliases](../../../core/libs/prx/libSceSystemService/PendingExports.cpp) and [USB exports](../../../core/libs/prx/libSceUsbStorage/Export.cpp): direct-jump signatures are matched to the supplied USB module. RequestMap/RequestMapWSB take six arguments; the two extra helper arguments are internal. IsExist takes a device, directory and byte output, with a separate boolean failure result. Callbacks receive an event pointer and user data. Device information writes 824 bytes, device lists hold eight 32-bit IDs, and mount-point outputs occupy 128 bytes. Event/device field meanings and initialization attributes remain unresolved. + +- [Assembly-only shell and clock queries](../../../core/libs/prx/libSceSystemService/PendingExports.cpp): format/fsck progress returns a float in XMM0. Impose-menu, accessibility-zoom and suspend-confirmation queries take an application ID and return a boolean. Suspend-blacklist lookup forwards a 32-bit ID to SysCore. Clock changes take a string and 32-bit value; CPU pstate takes a 32-byte input structure whose fields remain unresolved, and memory pstate takes one 32-bit value. + +- [Integer system parameters](../../../core/libs/prx/libSceSystemService/Export.cpp): registry-backed parameters 100–111, 200–213 and 300 answer the values a PS5 (firmware 13.40, measured 2026-10-23 inside a retail title) returned with its settings at the time: 101 is 1, 102 is 2, 103, 107 and 109 are 100, 104 and 110 are 0x20202, 209 is 1, 210 is 71 and the others are 0. What each parameter means, and which of them follow a user setting, is not known, so every title gets that one console's values. Language results omit the console SDK-version compatibility remapping, and parameter 400 does not reproduce platform-dependent availability. + +- [Tournament launch parameters](../../../core/libs/prx/libSceSystemService/Export.cpp): occurrence parameters occupy 32 bytes and contain two string pointers and a user ID. The final string semantics are unresolved. Event-details and team-profile launches retain an unused first argument slot of unknown declared type. The daemon blocking-event receive argument is passed unchanged as 64 bits to IPC; its meaning remains unresolved. Protected bodies in [PendingExports.cpp](../../../core/libs/prx/libSceSystemService/PendingExports.cpp) remain unchanged. +- [sceSystemServiceIsBgmPlaying, sceShellCoreUtilIsBgmPlaying](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - not measured on a PS5: both return 0. In [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/system/systemservice.cpp) they report whether its libSceMusicPlayerService is playing; there is no libSceMusicPlayerService here, so no background music can be playing diff --git a/docs/dev/debt/libSceTextToSpeech2.md b/docs/dev/debt/libSceTextToSpeech2.md index fb8dc582dc..58ba1298f6 100644 --- a/docs/dev/debt/libSceTextToSpeech2.md +++ b/docs/dev/debt/libSceTextToSpeech2.md @@ -6,5 +6,4 @@ ### Unknown function info -- [sceTextToSpeech2Initialize](../../../core/libs/prx/libSceTextToSpeech2/Export.cpp) (libSceTextToSpeech2) - the console's error for an unavailable speech engine is unknown; it returns the kernel's `EOPNOTSUPP` (0x8002002D). PPSA01317 passes a param that starts with 0x2000000 and only tests the result for non-zero; the other exports it imports are declared without parameters and return the same error -- [sceTextToSpeech2GetSystemStatus](../../../core/libs/prx/libSceTextToSpeech2/Export.cpp) (libSceTextToSpeech2) - unknown signature; returns `EOPNOTSUPP` like `sceTextToSpeech2Initialize` +- [sceTextToSpeech2Initialize](../../../core/libs/prx/libSceTextToSpeech2/Export.cpp) (libSceTextToSpeech2) - the console's error for an unavailable speech engine is unknown; it returns the kernel's `EOPNOTSUPP` (0x8002002D). PPSA01317 passes a param that starts with 0x2000000 and only tests the result for non-zero. diff --git a/docs/dev/debt/libSceUlt.md b/docs/dev/debt/libSceUlt.md index 0ddb633b28..0e314d8440 100644 --- a/docs/dev/debt/libSceUlt.md +++ b/docs/dev/debt/libSceUlt.md @@ -4,17 +4,15 @@ - [Bound condition variables](../../../core/libs/prx/libSceUlt/Export.cpp) use the creation-time mutex, inferred from PPSA28061 callers of `_sceUltConditionVariableCreate`; the wait caller reloads only the condition argument. Error precedence, busy/state errors, FIFO signal selection and unsupported recursive waits have not been measured on PS5 hardware. Non-null options and waits at recursion depth above one throw. A live condition prevents destroying its bound mutex. Finalization follows the existing host mutex lifecycle: it invalidates conditions and wakes queued or reacquiring waiters with a state error. Scheduling priority and build-version validation are not implemented. -- [libSceUlt ulthreads](../../../core/libs/prx/libSceUlt/Export.cpp) - `_sceUltUlthreadRuntimeOptParamInitialize`, `_sceUltUlthreadRuntimeCreate` and `_sceUltUlthreadCreate` are assumed to take the same arguments as the existing exports without the underscore, including the trailing build version. `sceUltUlthreadTryJoin` is assumed to return `ULT_ERROR_BUSY` while the ulthread runs, and `sceUltUlthreadRuntimeDestroy` to return `ULT_ERROR_BUSY` while an ulthread created on the runtime is not joined. Neither error has been confirmed on PS5 hardware -- [libSceUlt queues](../../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. -- [sceUltQueueCreate, sceUltQueueDataResourcePoolCreate](../../../core/libs/prx/libSceUlt/Export.cpp) - a non-null option parameter is accepted and ignored, as the other Ult create calls already do. [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/00d087b2323bf377fd39b7163a256977383cb232/include/orbis/Ult.h) declares `sceUltQueueOptParamInitialize` and `sceUltQueueDataResourcePoolOptParamInitialize` without their structures; the PS Vita libult in [RPCS3](https://github.com/RPCS3/rpcs3/blob/13aa88c3c4d66ea8799e987f7d762d90943ab17b/rpcs3/Emu/PSP2/Modules/sceUlt.h#L25-L78) defines both as a 16-byte reserved header followed by reserved fields, 128 bytes, with no settings, and [fpPS4](https://github.com/red-prig/fpPS4/blob/5ffbdbbd76b4da6e4b8721743ea491f494b28913/src/ps4_libsceult.pas#L142-L172) keeps them as 128 opaque bytes. That the PS5 structures carry no settings either is assumed from these, not verified -- [_sceUltWaitingQueueResourcePoolCreate, _sceUltSemaphoreCreate, _sceUltMutexOptParamInitialize, _sceUltMutexCreate, _sceUltQueueDataResourcePoolCreate and _sceUltQueueCreate](../../../core/libs/prx/libSceUlt/Export.cpp) (libSceUlt) forward to the exports without the underscore; that their argument lists match is inferred from the other underscore Ult exports, not measured -- [sceUltConditionVariableSignalAll, sceUltGetConditionVariableInfo, sceUltGetMutexInfo, sceUltGetQueueDataResourcePoolInfo, sceUltGetQueueInfo, sceUltGetReaderWriterLockInfo, sceUltGetSemaphoreInfo, sceUltGetUlthreadInfo, sceUltGetUlthreadRuntimeInfo, sceUltGetWaitingQueueResourcePoolInfo, sceUltAbstractThreadFactoryCreateThread, sceUltAbstractThreadFactoryGetNumMaxThreads, sceUltAbstractThreadFactoryJoinThread, sceUltKernelThreadFactoryFinalize, sceUltKernelThreadFactoryGetFactoryPointer, sceUltKernelThreadFactoryInitialize, sceUltModuleFinalize, sceUltModuleInitialize, sceUltUlthreadFactoryFinalize, sceUltUlthreadFactoryGetFactoryPointer, sceUltUlthreadFactoryGetWorkAreaSize, sceUltUlthreadFactoryInitialize, sceUltConditionVariableCreate, sceUltConditionVariableOptParamInitialize](../../../core/libs/prx/libSceUlt/Export.cpp) (libSceUlt) - throwing stubs declared without parameters from the firmware export database (aerolib.csv); their guest signatures are unverified on hardware +- [libSceUlt ulthreads](../../../core/libs/prx/libSceUlt/Export.cpp) - Runtime option defaults still omit the native header, stack size, worker affinity and priority fields; affinity depends on unresolved process configuration queries. Concurrent join ownership is not modeled: the native runtime distinguishes an ordinary running thread from one with another joiner. Host threads do not use the guest context or worker scheduler. +- [libSceUlt queues](../../../core/libs/prx/libSceUlt/Export.cpp) - Queue-data pool destruction and shared slot limits still require comparison with the native node allocator. +- [`sceUltConditionVariableCreate` and `sceUltConditionVariableOptParamInitialize`](../../../core/libs/prx/libSceUlt/Export.cpp) (libSceUlt) are absent from the console export list, where only the underscored variants `_sceUltConditionVariableCreate` and `_sceUltConditionVariableOptParamInitialize` appear; their non-underscored signatures are assumed. Condition variable option parameter initialization only zeroes 128 bytes, and its layout without the common header remains unverified on hardware. +- [Ult factory and creation exports](../../../core/libs/prx/libSceUlt/Export.cpp) without a leading underscore are absent from the supplied module; their signatures remain unresolved. Diagnostic signatures and synchronization argument lists are recovered. + - [sceUltMutexTryLock, the reader-writer lock and sceUltUlthreadGetSelf, sceUltUlthreadYield and sceUltUlthreadExit](../../../core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp) (libSceUlt) - the signatures follow the PS Vita libult in [RPCS3](https://github.com/RPCS3/rpcs3/blob/13aa88c3c4d66ea8799e987f7d762d90943ab17b/rpcs3/Emu/PSP2/Modules/sceUlt.cpp) (a 256-byte lock and 128-byte option structures), not a PS5 binary. The rules are assumed: readers may take the lock while a writer waits, a thread that holds the write lock gets `ULT_ERROR_STATE` for any further lock instead of waiting on itself, a reader asking for the write lock waits on its own read lock, and the try forms return `ULT_ERROR_BUSY`. Outside an ulthread, `sceUltUlthreadGetSelf`, `sceUltUlthreadYield` and `sceUltUlthreadExit` return `ULT_ERROR_STATE`; inside one they act on the guest thread it runs on, since ulthreads are not scheduled cooperatively here - [_sceUltWaitingQueueResourcePoolOptParamInitialize, _sceUltSemaphoreOptParamInitialize, _sceUltQueueDataResourcePoolOptParamInitialize, _sceUltQueueOptParamInitialize and _sceUltUlthreadOptParamInitialize](../../../core/libs/prx/libSceUlt/Export.cpp) and [_sceUltReaderWriterLockOptParamInitialize and _sceUltReaderWriterLockCreate](../../../core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp) (libSceUlt) forward to the exports without the underscore; that their argument lists match is inferred from the other underscore Ult exports, not measured ### Functional -- [libSceUlt queues](../../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. -- [sceUltWaitingQueueResourcePoolDestroy](../../../core/libs/prx/libSceUlt/Export.cpp) refuses with `ULT_ERROR_BUSY` only when a thread is currently waiting on a pool-backed mutex, semaphore or queue, matching the PS5 libSceUlt disassembly (0x473a in or55417wcDk); bound but idle objects do not block pool destruction. Waiting-queue pool guest work area node accounting is not implemented. -- [sceUltMutexDestroy](../../../core/libs/prx/libSceUlt/Export.cpp) returns `ULT_ERROR_BUSY` when the mutex is held or has waiting threads, tracked via owner and waiter counts in `UltMutexState`. Prosper (`hle_ult.cpp`) logs a destroy of a held mutex and returns `ULT_OK`; AnyPS5 returns `ULT_ERROR_BUSY` to prevent destroying active mutexes. - -- The following pending declarations have unverified signatures and behavior; they are parameterless import-resolution declarations and throw through `NotImplemented_nid_no_patch`: [`_sceUltConditionVariableOptParamInitialize`](../../../core/libs/prx/libSceUlt/Export.cpp). +- [sceUltFinalize](../../../core/libs/prx/libSceUlt/Export.cpp) - allocator-failure behavior is unverified on a PS5. The host may throw `std::bad_alloc` while reserving temporary wake lists; guest waiting-pool exhaustion and native finalization errors are not modeled. +- [libSceUlt queues](../../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; the guest object layouts remain incomplete. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. +- [sceUltWaitingQueueResourcePoolDestroy](../../../core/libs/prx/libSceUlt/Export.cpp) refuses with `ULT_ERROR_STATE` only when a thread is currently waiting on a pool-backed mutex, semaphore or queue, matching the PS5 libSceUlt disassembly (0x473a in or55417wcDk); bound but idle objects do not block pool destruction. Waiting-queue pool guest work area node accounting is not implemented. diff --git a/docs/dev/debt/libSceUsbStorage.md b/docs/dev/debt/libSceUsbStorage.md index e1bc7fc86b..c901faca35 100644 --- a/docs/dev/debt/libSceUsbStorage.md +++ b/docs/dev/debt/libSceUsbStorage.md @@ -1,6 +1,19 @@ # libSceUsbStorage technical debt +### Silent stubs + +- [sceUsbStorageInit, sceUsbStorageTerm](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - no USB mass-storage device is ever attached, so there is nothing to initialize or tear down: both accept every call and keep no state, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/usbd/usbstorage.cpp#L91-L99) does with its plain ORBIS_OK returns. A passthrough that lists devices must build its list in `Init` and free it in `Term`. + ### Unknown function info +- [sceUsbStorageRegisterCallback, sceUsbStorageUnregisterCallback, sceUsbStorageRegisterCallbackForMapAvailable, sceUsbStorageUnregisterCallbackForMapAvailable, sceUsbStorageRegisterCallbackForMapUnavailable, sceUsbStorageUnregisterCallbackForMapUnavailable](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - what a successful registration stores, what unregistering a never-registered callback answers, and when any callback fires is unverified: no device is ever attached, and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/usbd/usbstorage.cpp#L110-L174) logs all six as `(STUBBED)` and returns 0 without storing anything, the success-without-a-reference pattern #6288 was closed for. The argument validation is implemented (0x80f40002 for a null callback, an event type outside 1 and 2, or a null directory); past it they keep the marker until a reference or a measurement says what success does. +- [sceUsbStorageRequestUnmap, sceUsbStorageGetdentsClose](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - what the console answers for a directory that was never mapped and a getdents descriptor that was never opened is unknown: nothing can be mapped or opened in this model, shadPS4's [unmap](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/usbd/usbstorage.cpp#L281-L288) only logs without keeping state and its [close](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/usbd/usbstorage.cpp#L378-L383) succeeds only because `erase` ignores a missing key, and the no-device libraries throw on this path (`sceMoveClose` for a handle that was never opened, `sceContentSearchCloseMetadata` because no metadata id is ever handed out). Both keep the marker after their argument validation. - [sceUsbStorageClearFakeWriteProtection, sceUsbStorageSetFakeMapLockForDebug, sceUsbStorageSetFakeWriteProtection](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceUsbStorage.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. -- [sceUsbStorageRequestMapWSB](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - its prototype follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/usbd/usbstorage.cpp#L245-L246), not measured on a console; the throwing declaration only resolves imports. +- [sceUsbStorageGetMountPointOfShellCore](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - the prototype is known, but no reference says what a console without a mapped device writes into the mount-point buffer or returns; it keeps the marker until a reference or a console measurement answers it. + +### Functional + +- [the argument validation](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - the null-pointer, null-callback, event-type and null-directory refusals with 0x80f40002, and the getdents read of a never-opened descriptor answering 0x80f40005 with zero bytes, follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/usbd/usbstorage.cpp#L176-L383); the validation runs before the markers the register, unregister, unmap and getdents-close exports keep above. None of this is measured on a console. +- [the no-device answers](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - AnyPS5 has no USB mass-storage passthrough, so no device is ever attached: the device list is empty, `sceUsbStorageIsExist` succeeds and reports non-existence for every device id and directory, and the device-info query and the map and getdents-open requests answer 0x80f40004 for every device id. A passthrough that lists devices follows shadPS4's virtual-device model at the same commit for the filled paths, including its packed dirent. +- [the device-info record](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - the 0x338-byte record a passthrough fills for `sceUsbStorageGetDeviceInfo` is unverified on hardware; its layout follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Platform/UsbStorage.cs#L34-L45), which recovered it from the module's own export and was checked against #6288's "volume size at 0x0": the u64 volume size at offset 0, the vendor, product and serial strings at 0x16, 0x120 and 0x228, and the flags field the service masks with 0xd0 at 0x330. shadPS4's record starts with a u32 device id at offset 0; filling it that way puts a device id where a title reads the volume size. +- [the prototypes of sceUsbStorageRequestMap and sceUsbStorageGetDeviceList](../../../core/libs/prx/libSceUsbStorage/Export.cpp) (libSceUsbStorage) - the signatures follow the ABI restored on `main` (the firmware-database sweep of 2026-10-21), which matches [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/usbd/usbstorage.cpp#L176-L383): `RequestMap`'s six parameters, the typed `UsbStorageCallback` of the register/unregister family, `IsExist`'s three-argument bool form and `GetDeviceList`'s `std::int32_t*` count (the count also follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Platform/UsbStorage.cs#L233-L235), whose earlier eight-parameter `RequestMap` reading carried the trailing reserved pair the restored ABI drops). diff --git a/docs/dev/debt/libSceUsbStorageDialog.md b/docs/dev/debt/libSceUsbStorageDialog.md index 93445b0d4a..58151aaf70 100644 --- a/docs/dev/debt/libSceUsbStorageDialog.md +++ b/docs/dev/debt/libSceUsbStorageDialog.md @@ -6,5 +6,4 @@ ### Unknown function info -- [sceUsbStorageDialogGetResult](../../../core/libs/prx/libSceUsbStorageDialog/Export.cpp) (libSceUsbStorageDialog) - `SceUsbStorageDialogResult` full layout, size and additional fields are unverified across open references (KytyPS5, shadPS4, fpPS4, SharpProspero); only the 4-byte `result` field (`COMMON_DIALOG_RESULT_USER_CANCELED = 1`) is written to avoid guest memory corruption -- [sceUsbStorageDialogOpen, sceUsbStorageDialogClose](../../../core/libs/prx/libSceUsbStorageDialog/Export.cpp) (libSceUsbStorageDialog) - the relative validation precedence between uninitialized state, busy/running state, and null parameter checks is inferred from standard common dialog conventions and unverified against real console firmware +- [libSceUsbStorageDialog](../../../core/libs/prx/libSceUsbStorageDialog/Export.cpp) - the service-specific result status at offset 12 is not produced because the UI always cancels. The 80-byte result header and SDK-dependent validation are recovered; the remaining 64 bytes are untouched by the native wrapper. The purpose of the Open parameter size field remains unverified; the native wrapper does not validate it. diff --git a/docs/dev/debt/libSceUsbd.md b/docs/dev/debt/libSceUsbd.md index f23c18fd72..e7e535f68b 100644 --- a/docs/dev/debt/libSceUsbd.md +++ b/docs/dev/debt/libSceUsbd.md @@ -6,4 +6,10 @@ ### Unknown function info -- [sceUsbdClearHalt, sceUsbdDetachKernelDriver, sceUsbdEventHandlerActive, sceUsbdGetConfigDescriptorByValue, sceUsbdGetConfiguration, sceUsbdGetDescriptor, sceUsbdGetDevice, sceUsbdGetDeviceSpeed, sceUsbdGetMaxIsoPacketSize, sceUsbdGetMaxPacketSize, sceUsbdGetStringDescriptorAscii, sceUsbdHandleEvents, sceUsbdHandleEventsLocked, sceUsbdLockEventWaiters, sceUsbdLockEvents, sceUsbdOpenDeviceWithVidPid, sceUsbdSetInterfaceAltSetting, sceUsbdTryLockEvents, sceUsbdUnlockEventWaiters, sceUsbdUnlockEvents, sceUsbdWaitForEvent](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - signatures follow the console type database in [orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L2268-L2322), but their behavior is unknown and each call throws; not measured on a console +- [sceUsbdEventHandlerActive, sceUsbdHandleEvents, sceUsbdHandleEventsLocked, sceUsbdLockEventWaiters, sceUsbdLockEvents, sceUsbdTryLockEvents, sceUsbdUnlockEventWaiters, sceUsbdUnlockEvents, sceUsbdWaitForEvent](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - signatures follow the console type database in [orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L2268-L2322), but their behavior is unknown and each call throws; not measured on a console + +### Functional + +- [sceUsbdGetConfiguration, sceUsbdGetConfigDescriptorByValue, sceUsbdGetDescriptor, sceUsbdGetStringDescriptorAscii](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - not measured on a console; they follow FreeBSD 11.4 ([libusb10_desc.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb10_desc.c), [libusb20.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb20.c)). The configuration and descriptor queries return host libusb errors. `sceUsbdGetStringDescriptorAscii` rebuilds `libusb20_dev_req_string_simple_sync` with only its 255-byte read: libusb's Windows HID backend reports the requested length as `bLength`, cutting a 4-byte header read to one character. Unlike the host `libusb_get_string_descriptor_ascii` it accepts index 0, drops non-printable characters and `<>` instead of writing `?`, and fails with `0x802400FF`. Closes when: measured on a PS5 +- [sceUsbdGetDevice, sceUsbdGetDeviceSpeed, sceUsbdGetMaxPacketSize, sceUsbdGetMaxIsoPacketSize, sceUsbdOpenDeviceWithVidPid](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - not measured on a console; they follow FreeBSD 11.4 [libusb10.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb10.c) over the host libusb. The packet sizes are searched in the active configuration and the iso size is scaled as FreeBSD does, not through the host's versions, which differ for an unreadable configuration and for SuperSpeed endpoints. A host SuperSpeed+ device reports SuperSpeed, the fastest speed FreeBSD 11.4 has. A null device in `sceUsbdGetMaxIsoPacketSize` throws, as FreeBSD dereferences it. A device stays known while a handle to it is open, so the device of a handle from `sceUsbdOpenDeviceWithVidPid` can be used until that handle is closed. Closes when: measured on a PS5 +- [sceUsbdSetInterfaceAltSetting, sceUsbdClearHalt, sceUsbdDetachKernelDriver](../../../core/libs/prx/libSceUsbd/Export.cpp) (libSceUsbd) - not measured on a console; they follow FreeBSD 11.4 [libusb10.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb10.c) over the host libusb. `sceUsbdSetInterfaceAltSetting` checks the interface range and this library's record of claimed interfaces, and returns `0x802400FF` for any other failure; it throws while the device has transfers in flight, which FreeBSD cancels first. `sceUsbdClearHalt` returns 0, or `0x802400FF` for an endpoint the host does not find; FreeBSD ignores the result of the clear itself, so the host's other errors throw, and so does a null handle, which FreeBSD dereferences. `sceUsbdDetachKernelDriver` returns `0x802400FF` for any failure, which on Windows is every call, as libusb cannot detach drivers there. Closes when: measured on a PS5 diff --git a/docs/dev/debt/libSceUserService.md b/docs/dev/debt/libSceUserService.md index 4fca560df4..f1cb7b914e 100644 --- a/docs/dev/debt/libSceUserService.md +++ b/docs/dev/debt/libSceUserService.md @@ -3,9 +3,10 @@ ### Unknown function info - [sceUserServiceGetNpAccountId](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the account id for a user with no linked PSN account is assumed to be 0 with success; libSceNpManager's `sceNpGetAccountIdA` answers the same question with `SCE_NP_ERROR_SIGNED_OUT` -- [sceUserServiceInitialize, sceUserServiceTerminate](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - not measured: whether an invalid thread priority leaves an uninitialized service uninitialized (it does here), the parameters after their first `int`, and any second argument of `sceUserServiceInitialize2`. Measured on a PS5: before initialization and after `sceUserServiceTerminate`, `GetLoginUserIdList`, `GetEvent` and `Terminate` return `0x80960002`; a second initialization returns `0x80960003` and a new one reports the initial user's login again; a priority outside 0x100 to 0x2FF, from the first `int` of the parameters or the `sceUserServiceInitialize2` argument, returns `0x80960005` before the initialized check. The other calls do not check initialization -- [sceUserServiceGetUserColor](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the default profile colour is assumed to be blue (0) -- [sceUserServiceGetUserName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - `0x80960105`, which a PS5 (firmware 13.60) returns for a user id it does not know, has no known name; it is `USER_SERVICE_ERROR_UNKNOWN_USER` here. On the console a registered user who is not logged in still gets their name; here only the initial user exists +- [sceUserServiceInitialize and sceUserServiceInitialize2](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - event delivery has no background service thread, so the confirmed priority and Initialize2 CPU affinity mask do not affect host scheduling. +- [sceUserServiceGetUserColor](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the profile colour corresponds to user index (0: blue, 1: red, 2: green, 3: pink); colours 1, 2 and 3 are assumed, not measured +- [sceUserServiceGetUserName](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - `0x80960105`, which a PS5 (firmware 13.60) returns for a user id it does not know, has no known name; it is `USER_SERVICE_ERROR_UNKNOWN_USER` here. On the console a registered user who is not logged in still gets their name; here up to 4 users ("Player", "Player2", "Player3", "Player4") are supported; user names "Player2", "Player3" and "Player4" are assumed, not measured +- [sceUserServiceGetEvent](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - login and logout events queued dynamically on controller hotplug have no source and are assumed, not measured - [sceUserServiceGetAgeLevel](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - on a PS5 (firmware 13.60) the value is the age set in the user's parental restriction (0, 7, 12 and 15 were measured) and `0xFFFFFFFF` without one; a user who is not logged in returns `0x80960009` with or without an output. Parental restrictions are not modelled, so the initial user is always reported as unrestricted. PPSA12544 (Unity) only stores the value for its user profile - [sceUserServiceGetAccessibilityChatTranscription, PressAndHoldDelay, TriggerEffect, Vibration, ZoomEnabled, ZoomFollowFocus](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - report 0, 0, 1, 1, 0 and 1, what a PS5 (firmware 13.60) reports for a user whose accessibility settings are all at their defaults; the console reports the user's own settings (a changed vibration intensity read 3), which are not modelled. What each number means is unknown. A user who is not logged in returns `0x80960009` with or without an output -- [sceUserServiceGetAccessibilityKeyremapEnable, sceUserServiceGetAccessibilityZoom](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - declared without parameters as throwing stubs calling `NotImplemented_nid_no_patch`. Their signatures and values remain unverified on hardware +- [sceUserServiceGetKeyboardType, sceUserServiceGetJapaneseInputType, sceUserServiceGetTraditionalChineseInputType](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - AnyPS5 stores no per-user keyboard setting, so the initial user reports the keyboard type of the configured system language (`ANYPS5_LANGUAGE`, English US by default) and 0 for the two input variants, and any other user id returns `USER_SERVICE_ERROR_UNKNOWN_USER`. The language-to-keyboard-type table is the repository owner's (languages 0 to 29); a valid language outside it, such as Ukrainian (30), reports 20 (US English). A value that is not a console language number is rejected by `sceSystemServiceParamGetInt` with an error naming `ANYPS5_LANGUAGE`; the call throws it and leaves the output untouched. The input variant is 0 because the owner's table gives 0 for Japanese and Traditional Chinese, the only languages needing one. `sceConvertKeycodeGetImeKeyboardType` consumes these values. diff --git a/docs/dev/debt/libSceVdecCore.md b/docs/dev/debt/libSceVdecCore.md new file mode 100644 index 0000000000..a6803142e3 --- /dev/null +++ b/docs/dev/debt/libSceVdecCore.md @@ -0,0 +1,5 @@ +# libSceVdecCore technical debt + +### Unknown function info + +- [sceVdecCoreAnalyzeStream, sceVdecCoreCreateDecoder, sceVdecCoreDeleteDecoder, sceVdecCoreFinalizeComputeResource, sceVdecCoreFlushDecodeOutput, sceVdecCoreGetDecodeOutput, sceVdecCoreInitializeComputeResource, sceVdecCoreMapMemoryBlock, sceVdecCoreQueryComputeResourceInfo, sceVdecCoreQueryFrameBufferInfo, sceVdecCoreQueryInstanceSize, sceVdecCoreResetDecoder, sceVdecCoreSetDecodeInput, sceVdecCoreSetDecodeOutputSw, sceVdecCoreSyncDecode, sceVdecCoreSyncDecodeOutputSw, sceVdecCoreSyncDecodeWptr, sceVdecCoreTrySyncDecode, sceVdecCoreTrySyncDecodeOutputSw and sceVdecCoreTrySyncDecodeWptr](../../../core/libs/prx/libSceVdecCore/Export.cpp) (libSceVdecCore) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVdecCore.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceVdecsw.md b/docs/dev/debt/libSceVdecsw.md index 0bf65cb433..ca1399ba19 100644 --- a/docs/dev/debt/libSceVdecsw.md +++ b/docs/dev/debt/libSceVdecsw.md @@ -2,10 +2,8 @@ ### Unknown function info -- [libSceVdecsw](../../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - structure layouts follow shadPS4's `vdecsw.h` and were checked against PPSA05684's movie decoder (`movie-decoder-vdecsw.cpp`), which asserts `pictureCount == 1` at offset 0xb of `OutputInfo` and reads `InputResult` offset 0x10 as the number of frames an access unit produced. The compute, decoder memory and decode input structures and the first 0x28 bytes of the 0x50-byte decoder config are those of libSceVideodec2, and decoding goes through it. The 0x78-byte AVC picture info is filled with Videodec2's leading fields and the cropping fields only. OUTPUT_PENDING (0x81510115), INPUT_QUEUE_EMPTY (0x81510116) and DECODE_PENDING (0x81510117) follow from the codes each call's assert accepts. At most four decoded pictures are queued and the last one is held until `sceVdecswFinalizeDecodeSequence`; the console's queue depth is unknown. `sceVdecswSyncDecodeInput` / `sceVdecswSyncDecodeOutput` retry the TrySync variants until they stop returning DECODE_PENDING / OUTPUT_PENDING; they do not sleep beyond a yield, so another thread must drain outputs when the picture queue is full. `sceVdecswGetHevcPictureInfo` still throws: the 0xb8-byte HEVC picture layout is in shadPS4's `vdecsw.h`, but this wrapper only keeps AVC picture info from Videodec2 +- [libSceVdecsw](../../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - structure layouts follow shadPS4's `vdecsw.h` and were checked against PPSA05684's movie decoder (`movie-decoder-vdecsw.cpp`), which asserts `pictureCount == 1` at offset 0xb of `OutputInfo` and reads `InputResult` offset 0x10 as the number of frames an access unit produced. The compute, decoder memory and decode input structures and the first 0x28 bytes of the 0x50-byte decoder config are those of libSceVideodec2, and decoding goes through it. The 0x78-byte AVC picture info is filled with Videodec2's leading fields and the cropping fields only. OUTPUT_PENDING (0x81510115), INPUT_QUEUE_EMPTY (0x81510116) and DECODE_PENDING (0x81510117) follow from the codes each call's assert accepts. At most four decoded pictures are queued and the last one is held until `sceVdecswFinalizeDecodeSequence`; the console's queue depth is unknown. `sceVdecswSyncDecodeInput` / `sceVdecswSyncDecodeOutput` retry the TrySync variants until they stop returning DECODE_PENDING / OUTPUT_PENDING; they do not sleep beyond a yield, so another thread must drain outputs when the picture queue is full. The 0xb8-byte HEVC picture info (shadPS4's `vdecsw.h`) carries only the timestamps, coded size, bit depth, IDR and IRAP flags, profile, level and conformance window (in chroma samples, repeated in the frame cropping fields, whose console unit is unknown) - [sceVdecswGetVp9PictureInfo](../../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - no console behaviour is recorded; the signature and 0x58-byte layout come from [SharpProspero's Vdecsw.cs](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/Vdecsw.cs#L892-L952), and shadPS4 has no VP9 getter. It copies the leading `thisSize - 8` bytes of the picture's info, as [shadPS4's `sceVdecswGetPictureInfo`](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/videodec/vdecsw.cpp#L303-L334) does for AVC and HEVC, from Videodec2's VP9 info (`level` is 0 there). An output with no picture returns 0 and writes nothing. Null pointers, `thisSize` below 8, two pictures and a frame buffer no VP9 decoder returned (an H.264 picture too) throw; console error codes are unknown. VP9 decoding goes through Videodec2 and rejects pictures deeper than 10 bits -### Functional - -- [sceVdecswGetHevcPictureInfo](../../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - throws until HEVC picture info is filled from the decoder +- [sceVdecswGetPictureInfo](../../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - console behaviour and errors are unmeasured. The signature, 0x30/0x38 output descriptor sizes, optional first output, unchanged size field and empty-picture handling follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/videodec/vdecsw.cpp#L303-L334). Metadata comes from the existing AVC, HEVC or VP9 delivered-frame records, not the guest frame-buffer tail; copying is capped at the known record size, as in the typed getters. Invalid descriptor pointers/sizes, result sizes below 8 and unknown frame buffers throw instead of returning unverified console error codes. Two-picture field output remains unimplemented and throws before writing; the second output is untouched for zero or one picture. Metadata completeness and decoder limitations above still apply. diff --git a/docs/dev/debt/libSceVenc.md b/docs/dev/debt/libSceVenc.md new file mode 100644 index 0000000000..92c6460425 --- /dev/null +++ b/docs/dev/debt/libSceVenc.md @@ -0,0 +1,5 @@ +# libSceVenc technical debt + +### Unknown function info + +- [sceVencCreateEncoder, sceVencDeleteEncoder, sceVencGetConfig, sceVencMapMemory, sceVencQueryHeader, sceVencQueryMemorySize, sceVencQueryPreset, sceVencSetConfig, sceVencSetDecodedFrame, sceVencSetPictureConfig, sceVencSetRateControlConfig, sceVencSetReferenceFrameInvalidationConfig, sceVencStartEncode and sceVencStopEncode](../../../core/libs/prx/libSceVenc/Export.cpp) (libSceVenc) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVenc.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceVideoOut.md b/docs/dev/debt/libSceVideoOut.md index 8b62870d99..97a7439d7e 100644 --- a/docs/dev/debt/libSceVideoOut.md +++ b/docs/dev/debt/libSceVideoOut.md @@ -13,16 +13,18 @@ - [sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_, sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_, sceVideoOutAdjustColorSpaceConversion_, sceVideoOutSetGlobalBlendSpace](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - PPSA30630 imports these to configure color space conversion and global blend space. Color space conversion settings are accepted with size validation (32 bytes) but conversion blocks and blend space parameters are not applied to the presentation swapchain - [sceVideoOutDeleteVrrActiveStatusEvent](../../../core/libs/prx/libSceVideoOut/src/Event.cpp) (libSceVideoOut, NID `-q-vbO859Tw`) - the name hashes to the NID PPSA02530 imports and is listed in [sce_symbols](https://github.com/zecoxao/sce_symbols) `aerolib.csv` at `fe2cc2948e`. The signature `(eq, handle)` and the behaviour, removing the handle's VRR status registration from the queue as the other `Delete*Event` calls do, follow sceVideoOutAddVrrActiveStatusEvent; no title has been observed calling it - [sceVideoOutCursorEnable, sceVideoOutCursorDisable, sceVideoOutCursorSetImageAddress, sceVideoOutCursorSetPosition](../../../core/libs/prx/libSceVideoOut/Unimplemented.cpp) (libSceVideoOut, NIDs `UPZWCH8qTM4`, `fEPEwROdCwM`, `u-9bhWQApq8`, `N17AK88NdD0`) - the names hash to the NIDs Prospero Win (PPSA99995) imports, and the parameter lists `(handle, index, address)`, `(handle, index)`, `(handle, index, address)` and `(handle, index, x, y)` follow its [declarations](https://github.com/mpereiraesaa/prospero-win/blob/644e32e400c555e340d507383398c5d21b39b0b0/wine/ps5/pw_vulkan_radv.c#L65-L68). The cursor image's format and size and the console's return values are not known; the functions report that they are not implemented. -- [sceVideoOutGetOutputStatusWithVrr](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut, NID `gWT7X8H0bYs`) - `(int handle, void* status)`, the 80-byte layout and the return codes follow the clean-room description in #2746. Bytes 0-47 are `sceVideoOutGetOutputStatus`'s; bytes 48-53, which the console copies from an internal query whose fields are not established, are written as zero, as are the rest, so the 16-bit value at byte 52 never adjusts `refreshRate`. A null `status` returns `0x80290002` and an unknown handle `0x8029000B`. The console's earlier global-flag check (`0x8029001C`) and its object-field check (`0x8029001B`) test states that are not modelled and are never reported. +- [sceVideoOutGetOutputStatusWithVrr](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut, NID `gWT7X8H0bYs`) - bytes 48-53 are written as zero, where a PS5 (firmware 13.40, measured 2026-10-19 inside a retail title) with a 4K TV that takes 120 Hz returned the 16-bit values 48, 60 and 60 (0x30, 0x3C, 0x3C); their meaning is not established, so the value at byte 52 never adjusts `refreshRate`. `(int handle, void* status)`, the 80-byte layout and the return codes follow the clean-room description in #2746: bytes 0-47 are `sceVideoOutGetOutputStatus`'s and the rest 0, a null `status` returns `0x80290002` and an unknown handle `0x8029000B`, all as that console answered. The console's earlier global-flag check (`0x8029001C`) and its object-field check (`0x8029001B`) test states that are not modelled and are never reported. - [sceVideoOutRegisterBuffers2](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - buffer option 8 (STRICT_COLORIMETRY) is presented as option 0, with the stored values unchanged; what the console changes for it is unknown. Only options 0 and 8 are accepted - [sceVideoOutAddBufferAttributeOption](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut, NID `MBlv8YWoyOk`) - the signature `(VideoOutBufferAttribute2*, uint64_t option)` comes from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/VideoOut/VideoOut.cs#L174-L176) and the call site in PPSA25380, which passes 0x20 and ignores the result. The option is ORed into `attribute->option`; whether the console validates the bits, keeps them elsewhere or fails on an unknown option is unknown. A null attribute throws. Option 0x20 makes `sceVideoOutRegisterBuffers2` throw, as every option other than 0 and 8; what it selects for an overlay buffer is unknown - [sceVideoOutRegisterBuffers2](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - DCC buffers (category 1): `dcc_control` is taken as the CB_DCC_CONTROL block layout, only bits 0x10026c are accepted; `dcc_cb_register_clear_color` as the register-clear texel, only 32-bit values are presented -- [sceVideoOutIsOutputSupported](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - mode 1 is supported; mode 15 is supported when the current host display runs at 120 Hz or above. Configuring mode 15 requires attribute3 bit 0x40; this single-bit mask is inferred from the measured full value 0x80040, not isolated by a console measurement. Vblanks run at 119.88 Hz until mode 1 restores 59.94 Hz. Display modes are read through SDL; a move to a slower display falls back the configured output mode to mode 1, fires the output-mode event and continues at 59.94 Hz. The console's exact fallback behaviour when a 120 Hz display is lost has not been measured; this fallback avoids the abort in #7877 and matches the HDMI output mode renegotiation that a display change requires. Selecting another host display mode and VRR are not implemented. The console answers per display and per title: with a 120 Hz TV it also answers 1 for mode 15 (119.88 Hz), and [PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan/blob/5b5e4fc2d80fdb67a4f61ba9e6a8a424026bb8a5/docs/HARDWARE_FINDINGS.md) measured that `sceVideoOutConfigureOutput` accepts 15 only from a title whose param.json declares high-frame-rate output (attribute3 0x80040). Modes 4, 7, 8, 12-14, 16-17 and 19 return `VIDEO_OUT_ERROR_UNSUPPORTED_OUTPUT_MODE` here, as they did on the measured display. Mode 18 throws, because one console answered 0x80290016 and another 0x8029001E. The name `VIDEO_OUT_ERROR_UNKNOWN_OUTPUT_MODE` for 0x8029001E is ours. Word 3 of the output options is accepted with any value, as on the console, and its meaning is unknown. PPSA12544 probes `0xd000000a`, which the console answers with 0x8029001E, so the title selects 3840 pixels; PPSA26344 at `0xCD0946` passes `(handle, 0xf, nullptr, nullptr, 0)` and branches on the result equalling 1 -- [sceVideoOutGetResolutionStatus](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the 48-byte layout follows the declarations in ps5-opengl and EVO-PLAYER-PS5 (#1578), which agree; not confirmed on a console. `screenSizeInInch` is 0, the value EDID uses for an undefined size, since the host display's size is not read; `flags` is 0, its bits are unknown. The pane equals the full size because `sceVideoOutSetWindowModeMargins` applies no margins +- [sceVideoOutIsOutputSupported](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - mode 1 is supported; mode 15 is supported when the current host display runs at 120 Hz or above. Configuring mode 15 requires attribute3 bit 0x40; this single-bit mask is inferred from the measured full value 0x80040, not isolated by a console measurement. Vblanks run at 119.88 Hz until mode 1 restores 59.94 Hz. Display modes are read through SDL; a move to a slower display falls back the configured output mode to mode 1, fires the output-mode event and continues at 59.94 Hz. The console's exact fallback behaviour when a 120 Hz display is lost has not been measured; this fallback avoids the abort in #7877 and matches the HDMI output mode renegotiation that a display change requires. Selecting another host display mode and VRR are not implemented. The console answers per display and per title: with a 120 Hz TV it also answers 1 for mode 15 (119.88 Hz), and [PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan/blob/5b5e4fc2d80fdb67a4f61ba9e6a8a424026bb8a5/docs/HARDWARE_FINDINGS.md) measured that `sceVideoOutConfigureOutput` accepts 15 only from a title whose param.json declares high-frame-rate output (attribute3 0x80040). Modes 4, 7, 8, 12-14, 16-17 and 19 return `VIDEO_OUT_ERROR_UNSUPPORTED_OUTPUT_MODE` here, as they did on the measured display. Mode 18 throws, because one console answered 0x80290016 and two others 0x8029001E, the last a PS5 on 13.40 with a 120 Hz TV (measured 2026-10-19 inside a retail title), whose answers for every other mode match the ones here. The name `VIDEO_OUT_ERROR_UNKNOWN_OUTPUT_MODE` for 0x8029001E is ours. Word 3 of the output options is accepted with any value, as on the console, and its meaning is unknown. PPSA12544 probes `0xd000000a`, which the console answers with 0x8029001E, so the title selects 3840 pixels; PPSA26344 at `0xCD0946` passes `(handle, 0xf, nullptr, nullptr, 0)` and branches on the result equalling 1 +- [sceVideoOutGetResolutionStatus](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - `screenSizeInInch` is 0, the value EDID uses for an undefined size, since the host display's size is not read; a PS5 (firmware 13.40, measured 2026-10-19 inside a retail title) reported its TV's 60.0f. The 48-byte layout, from the declarations in ps5-opengl and EVO-PLAYER-PS5 (#1578), is confirmed there: full size and pane 3840 x 2160, `refreshRate` 3, `flags` 0 and the rest 0 on the title's handle, 0x8029000B for handles 0 to 8. `flags` is 0 here too; its bits are unknown. The pane equals the full size because `sceVideoOutSetWindowModeMargins` applies no margins +- [sceVideoOutGetOutputStatus](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the status after a buffer is registered, a 1440p output and a display below 4K with the console's resolution on Automatic were not measured. A payload process on a PS5 (firmware 12.70) with no buffer registered reported `resolution` 2 right after `sceVideoOutOpen(0xff, 0, 0, NULL)` with the console's resolution on Automatic on a 4K display and 1 with it set to 1080p, and `dynamicRange` 2 with `flags` 0 on an HDR display with HDR on, while this code sets flag bit 0 whenever it reports dynamic range 2. `resolution` is 2 when the current display mode SDL reports for the window's display is at least 3840 pixels wide or 2160 high and 1 otherwise, read with the refresh rate and independent of the registered buffers; on Wayland SDL's mode is the output's logical size (from SDL's source, not run), so a scaled 3840x2160 output reports 1. `APS5_OUTPUT_RESOLUTION` replaces that size - [sceVideoOutOpen](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. PPSA26344's only call at `0xCCF5EA` passes `(0xFF, 0, 0, nullptr)`. Whether a non-null param continues past offset 24 is also unknown -- [sceVideoOutGetBufferLabelAddress](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) exposes a port-owned contiguous array of 16 aligned 64-bit labels and returns its count. Astro Bot PPSA21564's bundled AGC driver passes a pointer output and addresses each label at `base + index * 8`. The count, zero initialization on registration, and release of the previous displayed buffer when its replacement completes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/23c41333822fe8eca4c609b43119180af63ae4ac/src/core/libraries/videoout/video_out.cpp) and its driver; PS5 console equivalence has not been measured. Labels live until the port closes, and reopening creates a new array. Error precedence follows the reference for null output and the existing open-port policy for handles. Exact scanout release timing, repeated same-buffer flips and concurrent guest label stores remain unverified. EOP-flip registration and interrupt tokens remain unimplemented; exposing these labels does not implement that separate API. +- [sceVideoOutGetBufferLabelAddress](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) exposes a port-owned contiguous array of 16 aligned 64-bit labels and returns its count. Astro Bot PPSA21564's bundled AGC driver passes a pointer output and addresses each label at `base + index * 8`. The count, zero initialization on registration, and release of the previous displayed buffer when its replacement completes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/23c41333822fe8eca4c609b43119180af63ae4ac/src/core/libraries/videoout/video_out.cpp) and its driver; PS5 console equivalence has not been measured. Labels live until the port closes, and reopening creates a new array. Error precedence follows the reference for null output and the existing open-port policy for handles. Exact scanout release timing, repeated same-buffer flips and concurrent guest label stores remain unverified. EOP-flip registration and interrupt tokens are the separate `sceVideoOutSubmitEopFlip` API. - [GPU flips](../../../core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp) (libSceVideoOut) - a command-buffer flip of a buffer index with no registered buffer when the GPU reaches it presents black and completes like any flip (flip event, count, `flipPendingNum`). The console's behaviour is unknown; PPSA05684 requires that it completes: its renderer submits a flip of buffer 0 while another thread waits for `sceVideoOutIsFlipPending` to reach 0 before registering the buffers, and `flipPendingNum` counts that flip from its submission as in shadPS4 - [sceVideoOutSubmitFlip](../../../core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp) (libSceVideoOut, libSceAgcDriver) - in the [AGC driver](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp), a CPU flip waits for the graphics-queue submissions ahead of it when the calling thread has submitted graphics-queue work itself: a title that submits a frame and flips it without waiting relies on that order, since emulated GPU work finishes later than the console's. A flip from a thread that has never submitted graphics-queue work is presented without waiting for queued submissions, unless a command-buffer or EOP flip ahead of it has not been reached. Such a thread can only know its frame is finished by waiting for it, as the PS5 RADV WSI's flip thread waits on the frame's fence. Whether the console ever orders a CPU flip behind the graphics queue is not measured; `APS5_CPU_FLIP_IN_ORDER=1` orders every CPU flip +- [sceVideoOutGetVblankStatus](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the third 64-bit word of the PS5 status (`reserved` in [SceTypes.hpp](../../../core/libs/SceTypes.hpp)) is written 0. On a PS5 (firmware 13.40, measured 2026-10-19) a retail title's own call, caught inside it, got count 0xb16b, `processTime` 0x381a8790 (941 s), third word 0x1c485ffaadc, `processTimeCounter` 0x15dd63c364b, flags 0x04 and phase 0: the third word is a running counter, about 970 s at ~2 GHz, consistent with a TSC, not zero. Its clock and what it counts from are not established. Closes when: the third word's source is measured and written - [sceVideoOutAddPreVblankStartEvent](../../../core/libs/prx/libSceVideoOut/src/Event.cpp) (libSceVideoOut) - how long before the vblank the console signals pre-vblank start is unknown. The event is triggered on each emulated vblank tick immediately before the vblank event, with its own count as the event data - [sceVideoOutRegisterBuffers, sceVideoOutGetFlipStatus, sceVideoOutGetVblankStatus](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - PS4 callers. A handle is treated as PS4 once buffers are registered through the PS4 `sceVideoOutRegisterBuffers`; from then on its flip and vblank status are written in the PS4 64- and 32-byte layouts, with `tsc` and `submitTsc` 0. A PS4 title that reads a status before it registers buffers gets the larger PS5 layout. The PS4 function accepts only linear A8R8G8B8_SRGB (0x80000000) and A8B8G8R8_SRGB (0x80002200) buffers without options; tiled PS4 buffers throw, as their layout differs from the PS5 tiling. Linear PS4 buffers may be 4 KiB aligned: the OpenOrbis graphics sample registers its second 1920x1080 buffer 8,294,400 bytes after the first. The PS4 alignment rule is not known diff --git a/docs/dev/debt/libSceVideoRecordingP.md b/docs/dev/debt/libSceVideoRecordingP.md index 50c98e9fc0..835fa5eb21 100644 --- a/docs/dev/debt/libSceVideoRecordingP.md +++ b/docs/dev/debt/libSceVideoRecordingP.md @@ -2,19 +2,17 @@ ### Unknown function info -- [_sceVideoRecordingQueryParam_nid_postfix](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - declared with `void(void* param)` signature following [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/VideoRecording.cs#L67-L69); unimplemented and throws `NotImplemented_nid_no_patch` -- [sceVideoRecordingCloseByHandle, sceVideoRecordingGetInfoByHandle](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - inferred handle-based recording control signatures taking an integer recording handle; unimplemented and throw `NotImplemented_nid_no_patch` -- [sceVideoRecordingCopyBGRAtoNV12](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - inferred pixel buffer conversion signature `(void* dst, const void* src, uint32_t width, uint32_t height)`; unimplemented and throws `NotImplemented_nid_no_patch` -- [sceVideoRecordingGetSupportInfo, sceVideoRecordingGetVideoOutMode](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - query signatures taking an output pointer; exact structure layouts and return values are unverified and throw `NotImplemented_nid_no_patch` -- [sceVideoRecordingInit, sceVideoRecordingTerm](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - parameterless initialization and termination stubs; unimplemented and throw `NotImplemented_nid_no_patch` -- [sceVideoRecordingInitialize](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - initialization stub taking an optional parameter structure pointer; unverified and throws `NotImplemented_nid_no_patch` -- [sceVideoRecordingOpen2](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - extended recording open signature; parameters inferred from `sceVideoRecordingOpen` and [fpPS4](https://github.com/red-prig/fpPS4); unimplemented and throws `NotImplemented_nid_no_patch` -- [sceVideoRecordingQueryHddSize2, sceVideoRecordingQueryMemSize2](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - memory and disk size query signatures taking parameter pointer; unverified and throw `NotImplemented_nid_no_patch` -- [sceVideoRecordingQueryMemSize](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - signature `int(const SceVideoRecordingParam*)` from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/VideoRecording.cs#L75-L77), which says only that it reports the heap bytes needed for the settings in the parameter; how the size follows from `ringSec` and the format is not documented. [`MaxMemSize` (4096)](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/VideoRecording.cs#L64-L65) is the largest heap the recorder needs, an upper bound and not the size for given settings, so returning it would be invented. Throws `NotImplemented_nid_no_patch` -- [sceVideoRecordingOpen, sceVideoRecordingStart, sceVideoRecordingStop, sceVideoRecordingClose](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - signatures from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/VideoRecording.cs#L79-L101). The recorder is not modelled, so these throw `NotImplemented_nid_no_patch` instead of pretending to record. SharpProspero documents no error code for an unavailable recorder; [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/video_recording/video_recording_error.h#L9-L18) lists codes such as `0x80A80008` (UNSUPPORTED), but nothing shows the console returns one here -- [sceVideoRecordingRestModeProcessGetStatus, sceVideoRecordingRestModeProcessStart](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - rest mode recording interfaces; unverified and throw `NotImplemented_nid_no_patch` -- [sceVideoRecordingSendCommand](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - command dispatch signature taking integer command ID and parameter pointer; unverified and throws `NotImplemented_nid_no_patch` +- [sceVideoRecordingGetInfo, sceVideoRecordingSetInfo](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) support native validation and errors before a recording context exists. Open and initialization remain protected unimplemented exports; live recording queries, control and volume changes are unavailable. + +- [_sceVideoRecordingQueryParam_nid_postfix](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp): the supplied module reads a 64-bit size and writes a 32-bit mode at offset 12 for sizes at least 16, plus a 32-bit option at offset 28 for sizes at least 64. Mode/option meanings remain unresolved; the protected implementation is unchanged. +- [sceVideoRecordingCloseByHandle, sceVideoRecordingGetInfoByHandle](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) - native handles are context pointers. Recording state and per-handle queries remain unimplemented. +- [sceVideoRecordingCopyBGRAtoNV12](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) - the destination, source, 32-bit width and height arguments are confirmed. Pixel conversion remains unimplemented. +- [sceVideoRecordingGetSupportInfo, sceVideoRecordingGetVideoOutMode](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) - GetSupportInfo writes an 18-byte payload whose fields remain unresolved. GetVideoOutMode takes a four-byte output confirmed by the native recording assembly and Avcap2 IPC descriptor; the mode values remain unresolved. Both exports remain unimplemented. +- [sceVideoRecordingInitialize](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp): the native initializer consumes no caller arguments; the former optional parameter is not read by the Avcap2 initializer or recording setup. Its unused parameter slot is retained because the protected body references it. +- [sceVideoRecordingOpen, sceVideoRecordingOpen2](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) - the 32-byte and 40-byte parameter variants, four argument registers and 32-bit heap size are recovered. Mode, output mode, option and format semantics remain incomplete. QueryHddSize2 returns a signed 64-bit size or error; recording and sizing exports remain unimplemented. + +- [Handle and support queries](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) - native recording handles are context pointers, and GetSupportInfo writes 18 bytes. Support payload field semantics remain incomplete. RestModeProcessStart, RestModeProcessGetStatus and SendCommand consume no caller arguments in the supplied native module; their existing unused parameter slots remain because the protected bodies reference those parameter names. ### Functional -- [sceVideoRecordingGetStatus](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) always returns 0, `None` ("The recorder is not open") in [SharpProspero's `VideoRecordingStatus`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/VideoRecording.cs#L18-L29), also listed in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/video_recording/video_recording.h#L32-L36). It is true here because `sceVideoRecordingOpen` throws, so no recorder is ever open. Unverified: whether the console returns 0 or an error before `sceVideoRecordingInit`/`sceVideoRecordingInitialize`, which throw, and when it reports `Running` (1) or `Paused` (2) +- [sceVideoRecordingGetStatus](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) always returns 0, `None` ("The recorder is not open") in [SharpProspero's `VideoRecordingStatus`](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/VideoRecording.cs#L18-L29), also listed in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/video_recording/video_recording.h#L32-L36). It is true here because `sceVideoRecordingOpen` throws, so no recorder is ever open. Unverified: whether the console returns 0 or an error before `sceVideoRecordingInit`/`sceVideoRecordingInitialize`, and when it reports `Running` (1) or `Paused` (2) diff --git a/docs/dev/debt/libSceVideodec.md b/docs/dev/debt/libSceVideodec.md index c24087ab78..8abac004cf 100644 --- a/docs/dev/debt/libSceVideodec.md +++ b/docs/dev/debt/libSceVideodec.md @@ -2,14 +2,14 @@ ### Unknown function info -- [sceVideodecCreateDecoder, sceVideodecDecode, sceVideodecDeleteDecoder, sceVideodecFlush and sceVideodecReset](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the structure layouts and the error codes follow [shadPS4's PS4 library](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/videodec/videodec.h) and its [implementation](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/videodec/videodec.cpp), not measured on a PS5: the console's error for a rejected argument and its own version of the structures are unknown. The decoder runs through libSceVideodec2, which decodes H.264 with FFmpeg, and the picture is copied into the caller's frame buffer with the pitch aligned to 64 and the coded height, as the reference writes it - -- [libSceVideodec](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the module's other two exports, `sceVideodecMapMemory` and `sceVideodecQueryResourceInfo`, are not provided, so a title that imports them fails to start +- [sceVideodecCreateDecoder, sceVideodecDecode, sceVideodecDeleteDecoder, sceVideodecFlush, sceVideodecQueryResourceInfo and sceVideodecReset](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the structure layouts and the error codes follow [shadPS4's PS4 library](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/videodec/videodec.h) and its [implementation](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/videodec/videodec.cpp), not measured on a PS5: the console's error for a rejected argument and its own version of the structures are unknown. The decoder runs through libSceVideodec2, which decodes H.264 with FFmpeg, and the picture is copied into the caller's frame buffer with the pitch aligned to 64 and the coded height, as the reference writes it ### Functional - [sceVideodecCreateDecoder](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the codec type is not checked and the decoder always decodes H.264, as the PS4 library does; the console's codec ids are not documented. A configuration without a maximum frame size is refused with the configuration error, and the resource information the caller passes is not used +- [sceVideodecQueryResourceInfo](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the sizes it reports are libSceVideodec2's, not the console's: the CPU memory, CPU-GPU memory and frame buffer sizes and the frame buffer alignment that `sceVideodec2QueryDecoderMemoryInfo` gives for the configuration mapped as `sceVideodecCreateDecoder` maps it. The GPU memory size that query also gives has no field in this structure and is dropped. The memory pointers and `thisSize` are left as the caller passed them, where [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c2814041e546bf75c12756dbec9da911c721ce1c/src/core/libraries/videodec/videodec.cpp#L137-L187) clears the pointers; what the console writes there is not known. A larger structure is accepted, as by the module's other exports, where shadPS4 requires the exact size, and a configuration without a maximum frame size is refused with the configuration error, where shadPS4 assumes 1920x1080 or 3840x2160 + - [sceVideodecDecode and sceVideodecFlush](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the picture information's video usability information (the tick rate, the sample aspect ratio and the colour description) stays zero, as the decoder does not report it, and the crop offsets are the decoded picture's crop halved, the unit the H.264 offsets use. The frame buffer holds the coded picture the decoder reports, so the rows the crop covers are in it and the caller crops them -- [sceVideodecMapMemory and sceVideodecQueryResourceInfo](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - their names are listed in the PS5 3.20 export table and cross-checked against PS5 13.60. Their signatures and behaviour are unverified; these no-parameter declarations only resolve imports and throw through NotImplemented_nid_no_patch. +- [sceVideodecMapMemory](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - its name is listed in the PS5 3.20 export table and cross-checked against PS5 13.60. Its signature and behaviour are unverified; this no-parameter declaration only resolves the import and throws through NotImplemented_nid_no_patch. diff --git a/docs/dev/debt/libSceVideodec2.md b/docs/dev/debt/libSceVideodec2.md index 3706171e00..74a8f81c0f 100644 --- a/docs/dev/debt/libSceVideodec2.md +++ b/docs/dev/debt/libSceVideodec2.md @@ -2,6 +2,7 @@ ### Unknown function info +- [sceVideodec2InitLibrary](../../../core/libs/prx/libSceVideodec2/Unimplemented.cpp) (libSceVideodec2) throws: the FFmpeg decoders this module creates need no library-level initialisation, and neither SharpProspero nor EVO-PLAYER-PS5 declares this call, so its arguments and result are unknown. A title imports it from this module - [sceVideodec2GetAvcPictureInfo](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) - runs the `sceVideodec2GetPictureInfo` body (KytyPS5 binds both NIDs to one function; SharpProspero gives both the same `outputInfo, first, second` signature); the second (bottom field) picture info is never written, since only progressive H.264 output is modelled. prosper calls the AVC layout unestablished (no title observed calling it) - [sceVideodec2TermLibrary and twelve other exports](../../../core/libs/prx/libSceVideodec2/Unimplemented.cpp) (libSceVideodec2) - report that they are not implemented; what they do on the console is not known. The others are `QueryLibraryInfo`, `QueryDecoderInfo`, `MapMemory`, `MapDirectMemory`, `PresetDecoderConfigInfo` (plain, `Avc`, `Hevc`, `Vp9`), `CreateDecoderBid`, `CreateHevcDecoder`, `QueryHevcDecoderMemoryInfo` and `GetHevcPictureInfo`. They are exported so that a title which imports them with `sceVideodec2InitLibrary` and calls none of them directly loads. Only `sceVideodec2MapDirectMemory` has a known parameter list, `(decoder, memory)`, from [EVO-PLAYER-PS5's `sce_videodec2.h`](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/9715f6ee9bf9b53461d6fdf9a2e11b4c48357783/projects/evoplayer/media/include/sce/sce_videodec2.h#L173); the others take none here @@ -13,5 +14,6 @@ - [sceVideodec2CreateDecoder](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) decodes HEVC (codec type 974921) and VP9 (2382845) through FFmpeg, besides H.264. The tags come from SharpProspero's `Videodec2CodecType` and EVO-PLAYER-PS5's `sce_videodec2.h`, which records them from hardware runs. HEVC input is converted from length-prefixed NAL units to Annex B as for H.264, and VP9 frames are passed through. Output is NV12 for 8-bit pictures. A 10-bit picture is written as 16-bit little-endian words with the sample in the low 10 bits, Y rows then interleaved UV rows at `framePitchInBytes` times the picture height, with `framePitch` in samples and `framePitchInBytes` in bytes (twice the width, aligned to 256), and `QueryDecoderMemoryInfo` sizes a frame of an HEVC or VP9 profile 2 configuration with two bytes per sample. Evidence is from [EVO-PLAYER-PS5](https://github.com/sainsaji/EVO-PLAYER-PS5/tree/9715f6ee9bf9b53461d6fdf9a2e11b4c48357783), which decodes and presents 1080p HEVC Main10 on hardware through this path (`docs/evo-pro/on-demand-decoder-handoff.md`): it takes a picture as 10-bit when `pitch_bytes >= pitch * 2`, puts UV at `pitch_bytes` times the height and samples with a low-bit factor (`projects/evoplayer/media/src/evo_vdec_native.c`, `ro_harvest`), and its hardware logs give 6128 KB and 24496 KB frame buffers for 1920x1088 and 3840x2176 Main10 decoders after rounding to 16 KiB. The 1080p size is what this formula gives; at 3840x2176 it gives 16 KiB less, so the console's size there has another term that is not known. Unverified: `frameFormat` (still 0; no consumer found reads it), VP9 profile 2 output (no hardware evidence), and the layout of 12-bit pictures and of decoded formats other than yuv420p10le, which throw. `sceVideodec2GetPictureInfo` and `sceVideodec2GetAvcPictureInfo` throw for HEVC pictures (`SceVideodec2HevcPictureInfo` is not filled), and `sceVideodec2GetHevcPictureInfo` reports that it is not implemented - [sceVideodec2GetVp9PictureInfo](../../../core/libs/prx/libSceVideodec2/Export.cpp) uses the 88-byte layout and two-pointer signature from [SharpProspero Videodec2.cs](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/Videodec2.cs). Frame metadata follows the [VP9 specification](https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.7-20170222-draft.pdf), sections 5.26 and 6.2: profile, key/intra-only flags, bit depth, color space/range, coded/render dimensions and superframe count are retained per emitted buffer. Level is 0 because the elementary stream does not signal a level; the console value is unverified. Null pointers, undersized structures and querying another codec throw; console error codes and support for other structure sizes are unknown. Pictures deeper than 10 bits are rejected. Superframes are submitted as individual coded frames to keep each output associated with its input metadata. `sceVideodec2GetPictureInfo` writes this layout for a VP9 picture, as shadPS4's `videodec2.cpp` writes the decoder's own layout; Mortal Shell 2 (PPSA34008) calls it with `thisSize` 88. -- [sceVideodec2Decode after sceVideodec2Flush](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) without `sceVideodec2Reset` restarts FFmpeg's decoder (`avcodec_flush_buffers`) and decodes the unit, where it threw before. TEKKEN DARK RESURRECTION (PPSA31371), which runs in Sony's PSP emulator, decodes after a flush without a reset (#7118), so the console accepts the call. Pictures the flush decoded but has not returned yet are returned before the new ones. What the console returns for them, and for a stream that continues across the flush without a new IDR picture, is not measured. +- [sceVideodec2Decode after sceVideodec2Flush](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) without `sceVideodec2Reset` restarts FFmpeg's decoder (`avcodec_flush_buffers`) and decodes the unit, where it threw before. TEKKEN DARK RESURRECTION (PPSA31371), which runs in Sony's PSP emulator, decodes after a flush without a reset (#7118), so the console accepts the call. Pictures the flush decoded but has not returned yet are returned before the new ones. A flush with no picture pending (each decoded unit has returned its picture) leaves the decoder and its reference pictures as they are, so a stream that continues without a new IDR picture keeps decoding: Daxter (PPSA09695), also in the PSP emulator, flushes after every access unit of its H.264 movies, and restarting the decoder there lost every P picture until the next keyframe. What the console returns for pending pictures, and for a stream that continues across a flush that had pictures pending, is not measured. - [sceVideodec2Decode](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) and `sceVideodec2Flush` output the coded picture, with the chroma plane after its luma rows, as the console does for 1080p H.264 and HEVC streams coded at 1920x1088. An HEVC or VP9 picture whose size is not a multiple of 16, such as HEVC coded at 1920x1080, is output at that size; whether the console pads it is unverified. `sceVideodec2GetPictureInfo` reports no frame cropping +- [sceVideodec2Decode of an empty access unit](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) returns `0x811D010E` for a null `auData` and `0x811D010D` for an `auSize` of 0, with no picture, where it threw before. The values are `ACCESS_UNIT_POINTER` and `ACCESS_UNIT_SIZE` in the error table of [shadPS4's videodec_error.h](https://github.com/shadps4-emu/shadPS4/blob/f3f0c14c1350a768558dfd1fd058d259ccecaca2/src/core/libraries/videodec/videodec_error.h), whose libSceVdecsw codes this repository already uses; they are not measured on a PS5, and neither is whether the console leaves the decoder's state alone on that call, as this does. Media Player Classic PS5 (PPSA99015) submits empty units: its own converter reads a 4-byte length prefix of `00 00 01 xx` as a start code and drops the rest as a non-base HEVC layer. It goes on after the error. diff --git a/docs/dev/debt/libSceVisionManager.md b/docs/dev/debt/libSceVisionManager.md index 6e8c187895..f8f41d421e 100644 --- a/docs/dev/debt/libSceVisionManager.md +++ b/docs/dev/debt/libSceVisionManager.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceVisionManagerApplyCalibrateHmdResult, sceVisionManagerCancelRequest, sceVisionManagerClose, sceVisionManagerFinalize, sceVisionManagerGenerateRegisterUserData, sceVisionManagerGenerateRegisterUserDataFinalize, sceVisionManagerGenerateRegisterUserDataInitialize, sceVisionManagerGetCalibrateTrackingLedInfo, sceVisionManagerGetCalibrateTrackingLedNecessity, sceVisionManagerGetHitCountOfRegisterUserData, sceVisionManagerGetLibraryVersion, sceVisionManagerGetNumOfRegisterUserDataSlot, sceVisionManagerGetRegisterUserDataSize, sceVisionManagerGetRegisterUserDataVersion, sceVisionManagerGetResultOfCalibrateHmd, sceVisionManagerGetResultOfCalibrateStereoCamera, sceVisionManagerGetResultOfCalibrateTrackingLed, sceVisionManagerGetResultOfFindFace, sceVisionManagerGetResultOfFindPad, sceVisionManagerGetResultOfFindPadCorrespondRegion, sceVisionManagerGetResultOfFindUser, sceVisionManagerGetResultOfFindUserInRegion, sceVisionManagerGetResultOfFindUserWithoutIdentify, sceVisionManagerGetResultOfRegisterUserInRegion, sceVisionManagerGetStoreCameraImgYPtr, sceVisionManagerGetWorkingMemorySize, sceVisionManagerInitialize, sceVisionManagerLoadSettings, sceVisionManagerOpen, sceVisionManagerRegistryClear, sceVisionManagerRequestCalibrateHmd, sceVisionManagerRequestCalibrateStereoCamera, sceVisionManagerRequestCalibrateTrackingLed, sceVisionManagerRequestClose, sceVisionManagerRequestFindFace, sceVisionManagerRequestFindPad, sceVisionManagerRequestFindPadCorrespondRegion, sceVisionManagerRequestFindUser, sceVisionManagerRequestFindUserInRegion, sceVisionManagerRequestFindUserWithoutIdentify, sceVisionManagerRequestHeadDetection, sceVisionManagerRequestInitializeRegisterUserData, sceVisionManagerRequestOpen, sceVisionManagerRequestPadTracking, sceVisionManagerRequestRecogUtility, sceVisionManagerRequestRegisterUserInRegion, sceVisionManagerRequestStoreCameraImgY, sceVisionManagerSetCallbackForUpdateCalibrateHmdResult, sceVisionManagerSetCallbackForUpdateCalibrateStereoCameraResult, sceVisionManagerSetCallbackForUpdateCalibrateTrackingLedResult, sceVisionManagerSetCallbackForUpdateCameraFrame, sceVisionManagerSetCallbackForUpdateCtrlCore, sceVisionManagerSetCallbackForUpdateRecog, sceVisionManagerSetCallbackForUpdateRunningStatus, sceVisionManagerSetCallbackForUpdateStoreFrame and sceVisionManagerWaitResponse](../../../core/libs/prx/libSceVisionManager/Export.cpp) (libSceVisionManager) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVisionManager.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceVisionManagerApplyCalibrateHmdResult, sceVisionManagerCancelRequest, sceVisionManagerFinalize, sceVisionManagerGenerateRegisterUserData, sceVisionManagerGenerateRegisterUserDataFinalize, sceVisionManagerGenerateRegisterUserDataInitialize, sceVisionManagerGetCalibrateTrackingLedInfo, sceVisionManagerGetCalibrateTrackingLedNecessity, sceVisionManagerGetHitCountOfRegisterUserData, sceVisionManagerGetLibraryVersion, sceVisionManagerGetNumOfRegisterUserDataSlot, sceVisionManagerGetRegisterUserDataSize, sceVisionManagerGetRegisterUserDataVersion, sceVisionManagerGetResultOfCalibrateHmd, sceVisionManagerGetResultOfCalibrateStereoCamera, sceVisionManagerGetResultOfCalibrateTrackingLed, sceVisionManagerGetResultOfFindFace, sceVisionManagerGetResultOfFindPad, sceVisionManagerGetResultOfFindPadCorrespondRegion, sceVisionManagerGetStoreCameraImgYPtr, sceVisionManagerGetWorkingMemorySize, sceVisionManagerInitialize, sceVisionManagerRegistryClear, sceVisionManagerRequestCalibrateHmd, sceVisionManagerRequestCalibrateStereoCamera, sceVisionManagerRequestCalibrateTrackingLed, sceVisionManagerRequestClose, sceVisionManagerRequestFindFace, sceVisionManagerRequestFindPad, sceVisionManagerRequestFindPadCorrespondRegion, sceVisionManagerRequestHeadDetection, sceVisionManagerRequestInitializeRegisterUserData, sceVisionManagerRequestOpen, sceVisionManagerRequestPadTracking, sceVisionManagerRequestRecogUtility, sceVisionManagerRequestStoreCameraImgY, sceVisionManagerSetCallbackForUpdateCalibrateHmdResult, sceVisionManagerSetCallbackForUpdateCalibrateStereoCameraResult, sceVisionManagerSetCallbackForUpdateCalibrateTrackingLedResult, sceVisionManagerSetCallbackForUpdateCameraFrame, sceVisionManagerSetCallbackForUpdateCtrlCore, sceVisionManagerSetCallbackForUpdateRecog, sceVisionManagerSetCallbackForUpdateRunningStatus, sceVisionManagerSetCallbackForUpdateStoreFrame and sceVisionManagerWaitResponse](../../../core/libs/prx/libSceVisionManager/Export.cpp) (libSceVisionManager) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVisionManager.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceVoice.md b/docs/dev/debt/libSceVoice.md index c6fee962c1..32d3ca5475 100644 --- a/docs/dev/debt/libSceVoice.md +++ b/docs/dev/debt/libSceVoice.md @@ -1,24 +1,23 @@ # libSceVoice technical debt -### Silent stubs +### Functional -- [sceVoiceGetPortAttr](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - attribute 1001 writes one zero byte and returns 0 for an existing port; the backend processes no voice audio. PPSA34501 requests size 1 and reads one byte on success, and this fallback avoids its startup exception. The name follows attribute 1001 (`CELLVOICE_ATTR_VAD`) (Voice Activity Detection) in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellVoice.h), not confirmed for the PS5. The correct result, eligible port types and behaviour are unknown. +- [Voice resource and port state](../../../core/libs/prx/libSceVoice/Export.cpp): resource information is eight bytes containing four 16-bit limits selected by initialization mode. Reset, pause and resume use confirmed initialization, range and missing-port errors; repeated pause/resume succeed. GetPortInfo preserves unused output fields and reports native frame sizes, but still reports a constant ready state and does not reproduce stopped-state or snapshot-slot errors. The host has no buffered voice processing or device reset backend, so reset only covers empty software ports and pause flags currently affect no audio. Initialization validates native modes and version 100, but still lacks event callback delivery and backend creation. Port creation enforces recovered format and resource limits, but does not allocate device or codec backends. +- [sceVoiceEnableChat and sceVoiceDisableChat](../../../core/libs/prx/libSceVoice/Export.cpp): zero-argument signatures and the not-initialized error are confirmed. Initialized calls throw because the required audio chat backend is not implemented; they no longer report success for a local flag change. ### Unknown function info -- [sceVoiceResetPort](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - the `(uint32_t portId)` signature is the PS Vita one in [VitaSDK](https://github.com/vitasdk/vita-headers/blob/ae5808f3105c42fa3373ba4ab64ab54e2e3ca848/include/psp2/voice.h#L503-L514) and the PS3 `cellVoiceResetPort` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/be69c054eeac57e2e671697a463473d37fe5a7b2/rpcs3/Emu/Cell/Modules/cellVoice.cpp#L493-L510); that the PS5 library takes the same argument is assumed. A reset clears a port's buffered data, and ports here buffer nothing (`sceVoiceReadFromOPort` returns 0 bytes and `sceVoiceWriteToIPort` throws), so it only checks that the library is initialized and the port exists, as RPCS3 does, and keeps the port's connections, volume and mute flag. An uninitialized library or an unknown port throws like the other port calls; the console's error codes are not measured. PPSA23566 imports it -- [sceVoiceGetResourceInfo](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - the `(VoiceResourceInfo* info)` signature and port limits (7 voice inputs, 7 voice outputs, 1 device input, 1 device output, 64 total ports) are the PS Vita ones in [VitaSDK](https://github.com/vitasdk/vita-headers/blob/ae5808f3105c42fa3373ba4ab64ab54e2e3ca848/include/psp2/voice.h#L150-L157); that the PS5 library uses the same layout and limits is assumed. An uninitialized library or a null output pointer throws like the other Voice calls; console limits and error codes are not measured. PPSA23566 imports it -- [sceVoiceEnableChat, sceVoiceDisableChat](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) take no arguments and return 0, following [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs#L460-L464) and the `sceAudioOut2EnableChat` model. The host mixes no chat audio, so the calls only record whether chat audio is enabled and fail when the library is not initialized. Console error codes and chat session requirements are unverified. PPSA23566 imports them +- [sceVoiceVADAdjustment](../../../core/libs/prx/libSceVoice/Export.cpp) - throws through `NotImplemented_nid_no_patch`; behaviour and the PS5 prototypes are unmeasured. The only source of the parameter lists is shadPS4's PS4-derived prototypes (GPLv2, used as a reference only), with no second reference; structure pointers are `void*` because no layout is known. - [sceVoiceInit, sceVoiceStart, sceVoiceCreatePort, sceVoiceGetPortInfo](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - a second `sceVoiceInit` returns 0x804E0802, and Start, CreatePort and GetPortInfo return 0x804E0801 before checking any argument while the library is not initialized, as [prosper](https://github.com/mattias800/prosper/blob/ed42e499888d5a86792c3e1b2df3aebd8ca1a681/prosper/src/hle/audio/hle_audio.cpp#L4697-L4713) reads them from the shipped module. The other port and lifecycle calls still throw when the library is not initialized, since prosper gives no module address for them. Whether `sceVoiceInit` checks a null param before its state is unknown, so a null param still throws first, and that `sceVoiceEnd` returns the library to the not-initialized state is assumed. Closes when: the other entry points are read from the module or measured on a PS5 -- [sceVoicePausePort, sceVoiceResumePort](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - not measured on a PS5: the `(uint32_t portId)` signature follows [VitaSDK](https://github.com/vitasdk/vita-headers/blob/ae5808f3105c42fa3373ba4ab64ab54e2e3ca848/include/psp2/voice.h#L516-L538) and the PS3 calls in [RPCS3](https://github.com/RPCS3/rpcs3/blob/be69c054eeac57e2e671697a463473d37fe5a7b2/rpcs3/Emu/Cell/Modules/cellVoice.cpp#L443-L529), and the console's error codes and its result for pausing a paused port or resuming a running one are unknown (0 here, as in RPCS3). Both only check that the library is initialized and the port exists: VitaSDK documents that a paused port keeps its data and reported state, and nothing here processes port data. PPSA24191 imports them +- [sceVoiceInitHQ, sceVoiceVADSetVersion](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - not measured on a PS5: the signatures and rules follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/voice/voice.cpp). `sceVoiceInitHQ` runs `sceVoiceInit`, as shadPS4's two share one path. `sceVoiceVADSetVersion` accepts versions 0 and 1 and returns `0x804E0805` for any other; nothing uses the version, as no voice activity detection runs - [sceVoicePausePortAll, sceVoiceResumePortAll](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - not measured on a PS5: both return 0x804E0801 while the library is not initialized and 0 otherwise, as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c1b26558352b863f80142256e4df38585aa7d727/src/core/libraries/voice/voice.cpp#L356-L437); prosper reads the same code for `sceVoiceStart`, and the PS3 calls in [RPCS3](https://github.com/RPCS3/rpcs3/blob/be69c054eeac57e2e671697a463473d37fe5a7b2/rpcs3/Emu/Cell/Modules/cellVoice.cpp#L462-L543) make the same check. Like `sceVoicePausePort` they change nothing that is reported: [VitaSDK](https://github.com/vitasdk/vita-headers/blob/ae5808f3105c42fa3373ba4ab64ab54e2e3ca848/include/psp2/voice.h#L540-L557) documents that pausing every port keeps the buffered data and the reported state, and nothing here processes port data -- [sceVoiceInitHQ](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [sceVoiceGetMemorySize](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [sceVoiceDeviceInCustomerData](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [sceVoiceQAEnableRecording](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [sceVoiceVADAdjustment](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [sceVoiceVADPVTAdjustment](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [sceVoiceVADSetVersion](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [sceVoiceSetMuteFlagAll](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [sceVoiceSetBitRate](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [sceVoiceUpdatePort](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature +- [sceVoiceSetMuteFlagAll, sceVoiceSetBitRate, sceVoiceUpdatePort](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - not measured on a PS5; signatures follow [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), behaviour the Vita's [VitaSDK](https://github.com/vitasdk/vita-headers/blob/ae5808f3105c42fa3373ba4ab64ab54e2e3ca848/include/psp2/voice.h#L250-L557). They return the not-initialized, port-range and missing-port codes the other port calls return. The mute-all flag makes `sceVoiceGetMuteFlag` report every port muted without changing port flags. A non-voice port, a bitrate `sceVoiceCreatePort` rejects, a null param or a different port type throw, since the console's codes for them are unknown +- [sceVoiceGetPortAttr](../../../core/libs/prx/libSceVoice/Export.cpp): native initialization, port-range, pointer, attribute-size and port-type validation is implemented. Supported device attributes throw because the host has no device state or voice-activity processing. The former constant-zero voice-activity fallback is removed; failed calls leave output untouched. + +- [sceVoiceSetThreadsParams](../../../core/libs/prx/libSceVoice/Export.cpp): the priorities and affinity masks that mode 1 sets reach no host scheduling, because no voice worker threads run (see sceVoiceStart below). Mode 1 returns 0 for priorities 256-767 with non-zero masks within 0x1fff, the values a PS5 thread takes; the native result for other values is unverified, so they throw. The 32-byte event callback union is recovered, but callback delivery and device-state byte semantics remain unimplemented. + +- [sceVoiceStart, sceVoiceStop and sceVoiceEnd](../../../core/libs/prx/libSceVoice/Export.cpp) validate initialization, container presence and native mode-dependent memory sizes, but do not partition guest working memory or run the audio workers. Start/stop currently change software state only; backend failures and active-stream shutdown are not reproduced. diff --git a/docs/dev/debt/libSceVoiceQoS.md b/docs/dev/debt/libSceVoiceQoS.md index fa6732187f..9b322456c0 100644 --- a/docs/dev/debt/libSceVoiceQoS.md +++ b/docs/dev/debt/libSceVoiceQoS.md @@ -7,11 +7,11 @@ ### Unknown function info -- [sceVoiceQoSSetConnectionAttribute](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS, NID `KCGOxg8iX7s`) is a throwing stub declared without parameters, like the other endpoint calls of the library: PPSA04159 fails to load without it and its signature is unknown. The name hashes to the imported NID (`tools/nid_names.py`) -- [sceVoiceQoSInit](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `0x804E0905` for a second initialization comes from [prosper](https://github.com/mattias800/prosper)'s reading of the module and was not measured: on a PS5 a valid call blocks inside a homebrew payload, so it never returns there. PPSA26344's two call sites (`0xE307BF`, `0x151C45D`) pass an allocated pointer, a 0x40000-byte block and app type 0x10000000; the app type and block are not used. `sceVoiceQoSCreateLocalEndpoint` takes four arguments (`int* id, userId, deviceInId, deviceOutId`) as declared in fpPS4 (`src/ps4_libscevoice.pas`, `ps4_sceVoiceQoSCreateLocalEndpoint`); signatures for `DeleteLocalEndpoint(id)`, `CreateRemoteEndpoint(int* id)`, `DeleteRemoteEndpoint(id)`, `Connect(int* id, local, remote)`, `Disconnect(connection)`, `SetLocalEndpointAttribute(id, attribute, value, size)`, `SetRemoteEndpointAttribute(id, attribute, value, size)`, and `GetLocalEndpointAttribute(id, attribute, value, size)` were verified against call sites in Aliens: Fireteam Elite (PPSA03393, e.g. `0x76D132`, `0x76D16E`, `0x76CECB`). Endpoints and connections are tracked by allocated id; duplicate connections and deleting an endpoint while referenced in a connection are permitted as the console is not known to reject them; endpoint attributes are stored as given (including empty or non-positive sizes), as their ids and defaults are unknown. The not-initialized error 0x804E0901 is assumed from the 0x804E09xx numbering, and unknown ids return `ARGUMENT_INVALID`. Aliens: Fireteam Elite (PPSA03393) uses all implemented calls on its load path; it stops with `not implemented` when any unimplemented call throws -- [libSceVoiceQoS firmware exports](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `sceVoiceQoSGetRemoteEndpointAttribute`, `sceVoiceQoSGetConnectionAttribute` and `sceVoiceQoSGetStatus` follow public PS4 SDK prototypes in [Orbital](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json#L2436-L2441) and remain throwing stubs as endpoint and connection attribute storage is unverified on hardware. `sceVoiceQoSEnableChat`, `sceVoiceQoSDisableChat`, `sceVoiceQoSDebugGetStatus`, `sceVoiceQoSInitHQ`, `sceVoiceQoSSetMode`, and `sceVoiceQoSTerminate` are declared without parameters from the firmware export database (aerolib.csv); their guest signatures are unverified on hardware and every call throws +- [sceVoiceQoSSetMode and sceVoiceQoSTerminate](../../../core/libs/prx/libSceVoiceQoS/Export.cpp): these exports are absent from the supplied module. Their argument declarations still require another firmware version or callers. ### Functional -- [sceVoiceQoSEnd](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `0x804E0901` (not initialized) before any initialization was measured on a PS5 (firmware 13.60, #1407); ending an initialized library was not, because a valid `sceVoiceQoSInit` never returns inside a homebrew payload. PPSA14632 calls it at shutdown and PPSA26344 after a nonzero `sceVoiceQoSInit`. After an initialization it returns 0 and clears the init flag, so the library can be initialized again. Whether the console also ends the libSceVoice instance that its `sceVoiceQoSInit` starts ([prosper](https://github.com/mattias800/prosper)'s reading of the module), and what it answers while endpoints exist, is unknown; here it drops every endpoint and connection -- [sceVoiceQoSGetLocalEndpoint, sceVoiceQoSGetRemoteEndpoint](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - signatures follow [Orbital](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json#L2437-L2439) (`GetLocalEndpoint(connectionId, int* localId)` and `GetRemoteEndpoint(connectionId, int* remoteId)`). Operations look up the connection by allocated id and return the connected local or remote endpoint id; an unknown connection id or null output pointer returns `ARGUMENT_INVALID` (0x804E0902), and calling before initialization returns `0x804E0901` +- [libSceVoiceQoS](../../../core/libs/prx/libSceVoiceQoS/Export.cpp): initialization arguments, initialization errors, endpoint and connection argument lists, and the no-argument End path are confirmed by the supplied module. The host still tracks endpoints without initializing, starting or stopping libSceVoice or creating its ports. Device-dependent port creation, deletion and connection errors remain unimplemented; connecting an uncreated port throws. End clears host state but does not execute the confirmed voice stop/end sequence or propagate its result. +- [sceVoiceQoSCreateLocalEndpoint, sceVoiceQoSCreateRemoteEndpoint and sceVoiceQoSConnect](../../../core/libs/prx/libSceVoiceQoS/Export.cpp): local ID zero and its single-slot limit, remote/connection slot allocation from zero, seven/fifteen slot capacities, exhaustion outputs, and reuse of released slots are recovered. Remote endpoint deletion clears the first matching connection. Shared encoder ownership, codec parameters and packet accounting are still missing. +- [sceVoiceQoSSetLocalEndpointAttribute and sceVoiceQoSSetRemoteEndpointAttribute](../../../core/libs/prx/libSceVoiceQoS/Export.cpp): local selectors 0/2 accept four-byte values and 1/3 one-byte values; remote selector 7 accepts one byte. Unsupported selectors and sizes return the confirmed invalid-argument error. Valid requests throw until voice-port volume/mute forwarding is implemented; they no longer silently store arbitrary bytes. +- [sceVoiceQoSGetLocalEndpoint and sceVoiceQoSGetRemoteEndpoint](../../../core/libs/prx/libSceVoiceQoS/Export.cpp): all in-range connection slots are queryable, including unused slots returning endpoint ID -1. Disconnecting an unused in-range slot succeeds. These state rules are confirmed; the packet, status and attribute getter bodies containing `NotImplemented_nid_no_patch` remain unchanged. diff --git a/docs/dev/debt/libSceVorbisDec.md b/docs/dev/debt/libSceVorbisDec.md index f0c2ddc877..45ad9e2d0e 100644 --- a/docs/dev/debt/libSceVorbisDec.md +++ b/docs/dev/debt/libSceVorbisDec.md @@ -2,37 +2,13 @@ ### Unknown function info -- [sceVorbisDecBitrate](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecBitrateInstant](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecClear](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecComment](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecCrossLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database +- [sceVorbisDecOpenCallbacks, sceVorbisDecRead and the other libvorbisfile exports](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - the 31 exports named after libvorbisfile functions are taken to be the libvorbisfile API with a `sceVorbisDec` prefix (`Read` is `ov_read`, `CrossLap` is `ov_crosslap`, `HalfrateP` is `ov_halfrate_p`). `ReadFilter`, `ReadFloat` and `Halfrate` exist in libvorbis (`ov_read_filter` since 1.2.2, with the same `vorbisfile.h` signatures through 1.3.7 apart from `const`) but not in Tremor, so times are `double` and `ov_read` takes `bigendianp`, `word` and `sgned`. Guest `long` arguments and results are 64-bit. They run the bundled libvorbis 1.3.7 and libogg 1.3.6; the console's libvorbis version, any Sony changes to its behaviour or error codes, and the signatures themselves are unverified on a PS5. - [sceVorbisDecGetDecodePriority](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database - [sceVorbisDecGetLastDecodeError](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecHalfrate](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecHalfrateP](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecInfo](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecOpenCallbacks](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmSeek](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmSeekLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmSeekPage](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmSeekPageLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmTell](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmTotal](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRawSeek](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRawSeekLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRawTell](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRawTotal](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRead](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecReadFilter](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecReadFloat](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecSeekable](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecSerialNumber](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database - [sceVorbisDecSetDecodePriority](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecStreams](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeSeek](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeSeekLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeSeekPage](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeSeekPageLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeTell](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeTotal](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database + +### Functional + +- [libSceVorbisDec](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - the guest `OggVorbis_File` is never written: the decoder state lives on the host, keyed by the struct's address, so fields a title reads directly keep what it stored, and a struct never opened, cleared or failed to open behaves as libvorbisfile's zeroed struct. `sceVorbisDecInfo` returns a copy of each link's `vorbis_info` with 64-bit `long` fields, refreshed after every call on the struct and valid until `sceVorbisDecClear`. Reopening a struct that is still open frees its old state without calling its close callback; libvorbisfile leaks it. +- [sceVorbisDecClear and sceVorbisDecCrossLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - a null `vf` throws, as libvorbisfile dereferences it, except that `sceVorbisDecClear` and `sceVorbisDecCrossLap` with the same pointer twice return 0, and `sceVorbisDecCrossLap` with a null second pointer after a first that is not open returns `OV_EINVAL`, as libvorbisfile does. `sceVorbisDecClear` frees the state before it calls the close callback, as `ov_clear` does, so when that callback throws the struct is already cleared, where libvorbisfile leaves it freed but not zeroed. +- [sceVorbisDecOpenCallbacks and sceVorbisDecBitrate](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - on Windows the host `long` is 32-bit: a tell result outside it, or an initial byte count outside it with a non-null `initial`, throws; a sample rate of 2^31 Hz or more is rejected as a bad header; and `sceVorbisDecBitrate` rounds halves up instead of to even. diff --git a/docs/dev/debt/libSceVrServiceDialog.md b/docs/dev/debt/libSceVrServiceDialog.md index f6be543e6d..fe48267534 100644 --- a/docs/dev/debt/libSceVrServiceDialog.md +++ b/docs/dev/debt/libSceVrServiceDialog.md @@ -1,5 +1,5 @@ # libSceVrServiceDialog technical debt -### Unknown function info +### Silent stubs -- [sceVrServiceDialogClose, sceVrServiceDialogGetResult, sceVrServiceDialogGetStatus, sceVrServiceDialogInitialize, sceVrServiceDialogOpen, sceVrServiceDialogTerminate and sceVrServiceDialogUpdateStatus](../../../core/libs/prx/libSceVrServiceDialog/Export.cpp) (libSceVrServiceDialog) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrServiceDialog.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [libSceVrServiceDialog](../../../core/libs/prx/libSceVrServiceDialog/Export.cpp) - the VR service dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as no headset is supported (see libSceHmd2). The signatures are from [Orbital's type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json); the result layout beyond the leading `int32_t` result code is not known, so nothing else is written. diff --git a/docs/dev/debt/libSceVrSetupDialog.md b/docs/dev/debt/libSceVrSetupDialog.md index 336e3361d1..6890166ca4 100644 --- a/docs/dev/debt/libSceVrSetupDialog.md +++ b/docs/dev/debt/libSceVrSetupDialog.md @@ -2,4 +2,4 @@ ### Silent stubs -- [libSceVrSetupDialog](../../../core/libs/prx/libSceVrSetupDialog/Export.cpp) - the VR setup dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as no headset is supported (see libSceHmd2). The signatures are assumed from the other common dialogs; PPSA12544 imports the library. `sceVrSetupDialogGetStatus` (NID `qRUACYyUNFA`) is present in the firmware export database (aerolib), but direct import by PPSA12544 has not been explicitly confirmed. +- [libSceVrSetupDialog](../../../core/libs/prx/libSceVrSetupDialog/Export.cpp) - the VR setup dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as no headset is supported (see libSceHmd2). Initialization does not check the native process eligibility record, and the 46-byte setup payload remains opaque. Open validates the recovered 56-byte parameter header. GetResult writes one 32-bit result after initialization, including when returning the not-finished error; its error codes and validation order are recovered. diff --git a/docs/dev/debt/libSceVrTracker.md b/docs/dev/debt/libSceVrTracker.md index 7180a455a7..a3e50959cb 100644 --- a/docs/dev/debt/libSceVrTracker.md +++ b/docs/dev/debt/libSceVrTracker.md @@ -2,8 +2,12 @@ ### Unknown function info -- [sceVrTrackerCpuProcess, sceVrTrackerGetResult, sceVrTrackerGetTime, sceVrTrackerGpuSubmit, sceVrTrackerGpuWait and sceVrTrackerInit](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified -- [sceVrTrackerCpuPopMarker, sceVrTrackerCpuPushMarker, sceVrTrackerGetLiveCaptureId, sceVrTrackerGpuWaitAndCpuProcess, sceVrTrackerResetAll, sceVrTrackerSaveInternalBuffers and sceVrTrackerSetDeviceRejection](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - the list is the intersection of the export table of the console's libSceVrTracker (function symbols only), pinned aerolib names and functions not yet defined anywhere in the repo. [Inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c); [aerolib](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). No credible prototypes were found; these no-parameter declarations only resolve imports, make no ABI claim and must not be called until signatures are verified. -- [sceVrTrackerSetExtendedMode, sceVrTrackerSetLEDBrightness, sceVrTrackerSetRestingMode, sceVrTrackerStartLiveCapture, sceVrTrackerStopLiveCapture and sceVrTrackerTerm](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - the list is the intersection of the export table of the console's libSceVrTracker (function symbols only), pinned aerolib names and functions not yet defined anywhere in the repo. [Inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c); [aerolib](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). No credible prototypes were found; these no-parameter declarations only resolve imports, make no ABI claim and must not be called until signatures are verified. -- [sceVrTrackerGetPlayAreaWarningInfo, sceVrTrackerNotifyEndOfCpuProcess, sceVrTrackerQueryMemory, sceVrTrackerRecalibrate, sceVrTrackerRegisterDevice and sceVrTrackerRegisterDevice2](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - the list is the intersection of the export table of the console's libSceVrTracker (function symbols only), pinned aerolib names and functions not yet defined anywhere in the repo. [Inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c); [aerolib](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). Parameter shapes follow the [pinned shadPS4 declarations](https://github.com/shadps4-emu/shadPS4/blob/f9d07487dd85bb7dc522df6de109b8ad4b9b7175/src/core/libraries/vr_tracker/vr_tracker.h), not measured on a PS5. -- [sceVrTrackerRegisterDeviceInternal, sceVrTrackerResetOrientationRelative, sceVrTrackerSetDurationUntilStatusNotTracking, sceVrTrackerUnregisterDevice and sceVrTrackerUpdateMotionSensorData](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - the list is the intersection of the export table of the console's libSceVrTracker (function symbols only), pinned aerolib names and functions not yet defined anywhere in the repo. [Inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c); [aerolib](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). Parameter shapes follow the [pinned shadPS4 declarations](https://github.com/shadps4-emu/shadPS4/blob/f9d07487dd85bb7dc522df6de109b8ad4b9b7175/src/core/libraries/vr_tracker/vr_tracker.h), not measured on a PS5. +- [VrTracker parameters](../../../core/libs/prx/libSceVrTracker/Export.cpp): argument slots are recovered. CPU processing and GPU waiting consume 32-byte records, result queries consume 56-byte records, and time queries write 64 bits. Initialization accepts 128-byte and 144-byte versions; the 144-byte layout includes three address/size/alignment records, but configuration selector semantics and the older field layout remain unresolved. GPU submission accepts 552-byte and 648-byte versions; submission and result records still require field recovery; result storage has 1520-byte and 3640-byte variants. Result-query records have a signed handle, 64-bit time, 32-bit selectors and an extended-result flag; selector meanings and the four-byte value copied unchanged to result offset 448 remain unresolved. CPU mode/argument semantics are unresolved. Protected bodies remain unchanged. +- [sceVrTrackerCpuPopMarker, sceVrTrackerCpuPushMarker, sceVrTrackerGetLiveCaptureId, sceVrTrackerResetAll, sceVrTrackerSaveInternalBuffers and sceVrTrackerSetDeviceRejection](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - the list is the intersection of the export table of the console's libSceVrTracker (function symbols only), pinned aerolib names and functions not yet defined anywhere in the repo. [Inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c); [aerolib](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). No credible prototypes were found; these no-parameter declarations only resolve imports, make no ABI claim and must not be called until signatures are verified. +- [sceVrTrackerSetExtendedMode, sceVrTrackerSetLEDBrightness, sceVrTrackerSetRestingMode, sceVrTrackerStartLiveCapture and sceVrTrackerStopLiveCapture](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - the list is the intersection of the export table of the console's libSceVrTracker (function symbols only), pinned aerolib names and functions not yet defined anywhere in the repo. [Inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c); [aerolib](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). No credible prototypes were found; these no-parameter declarations only resolve imports, make no ABI claim and must not be called until signatures are verified. +- [sceVrTrackerGetPlayAreaWarningInfo and sceVrTrackerNotifyEndOfCpuProcess](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - the list is the intersection of the export table of the console's libSceVrTracker (function symbols only), pinned aerolib names and functions not yet defined anywhere in the repo. [Inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c); [aerolib](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). Parameter shapes follow the [pinned shadPS4 declarations](https://github.com/shadps4-emu/shadPS4/blob/f9d07487dd85bb7dc522df6de109b8ad4b9b7175/src/core/libraries/vr_tracker/vr_tracker.h), not measured on a PS5. +- [sceVrTrackerResetOrientationRelative and sceVrTrackerUpdateMotionSensorData](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - the list is the intersection of the export table of the console's libSceVrTracker (function symbols only), pinned aerolib names and functions not yet defined anywhere in the repo. [Inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c); [aerolib](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). Parameter shapes follow the [pinned shadPS4 declarations](https://github.com/shadps4-emu/shadPS4/blob/f9d07487dd85bb7dc522df6de109b8ad4b9b7175/src/core/libraries/vr_tracker/vr_tracker.h), not measured on a PS5. + +### Functional + +- [tracker lifecycle and devices](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - `sceVrTrackerQueryMemory`, `sceVrTrackerInit`, `sceVrTrackerTerm`, the three `sceVrTrackerRegisterDevice*` calls, `sceVrTrackerUnregisterDevice`, `sceVrTrackerRecalibrate`, `sceVrTrackerSetDurationUntilStatusNotTracking` and `sceVrTrackerGpuWaitAndCpuProcess` follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/vr_tracker/vr_tracker.cpp), a PS4 implementation: its struct layouts, memory sizes, argument checks, one handle per device type and `DEVICE_NOT_REGISTERED` for an HMD recalibration. Only the 128-byte init parameter is read; the 144-byte one throws. Unlike shadPS4, the init memory ranges are not looked up, a null init parameter and negative device types are refused. Terminating keeps the registered handles, as there. With no camera there is no GPU submission, so the GPU wait returns `NOT_EXECUTE_GPU_SUBMIT`. Not measured on a PS5 diff --git a/docs/dev/debt/libSceVrTracker2.md b/docs/dev/debt/libSceVrTracker2.md index 42938b9e61..5bb664cbf1 100644 --- a/docs/dev/debt/libSceVrTracker2.md +++ b/docs/dev/debt/libSceVrTracker2.md @@ -2,10 +2,27 @@ ### Silent stubs -- [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) never initializes, so its exports answer as the console's module does before initialization, measured on a retail PS5 (firmware 13.60) with no PS VR2 headset by calling each of the 115 NIDs with null arguments, zeroed buffers and buffers filled with 0xAA and 0x01: 99 return 0x8A740001 and write nothing; `XknY0IDcOwU` and `ark13GkiskA` return 0x8A740006 for a null first argument and otherwise clear its first 24 and 16 bytes and return 0x8A740001; `-YvjOUph8B0` returns 0x8A740006 for a null second argument and otherwise 0x8A740001; `QyZvFyf-QEk` and `fTYZOZepkqE` return 0 and write nothing; `TZIUIk9id3c` writes 0xC000, 0, 0x40 and 0x40 as `uint32`, `uint32`, `uint16` and `uint16` at offsets 4 to 15 of its first argument and returns 0; `IB0qcDVu8vU` clears the `uint32` at its first argument and returns 0. The last two crash on the console for a null first argument and throw here. Signatures and what the tracker returns once initialized are unknown; the twelve exports with aerolib names keep them and the others carry this library's own names +- [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) never initializes; initialized tracking behavior is absent. Its uninitialized responses were measured without a headset: most exports return 0x8A740001 without writes, while specific output/reset helpers clear buffers or write defaults. These helpers still lack complete signatures. Named export signatures and play-area output layouts are recovered; initialization option semantics and result fields need reconstruction. Anonymous exports remain grouped by their observed response rather than their individual contracts. ### Unknown function info -- [sceVrTracker2QueryMemory](../../../core/libs/prx/libSceVrTracker2/Export.cpp) (libSceVrTracker2, NID `TwqZnaIjWv4`) - accepts only a parameter whose first `uint32` is 0x20 and whose bytes 4 to 31 are zero, found by searching sizes 8 to 0x400 and setting each field and byte; it then writes 32 bytes, `uint32` 0x20, 32 MiB and 64 KiB followed by zeros, and returns 0, and otherwise returns 0x8A740006 without writing, as it does for a null argument. The meaning of the parameter's reserved bytes and of the written fields is assumed from their values -- [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) - initialization (`6Jy73SRfG-o`) takes one parameter of 0x48 bytes. PPSA05666 passes the size 0x48 at offset 0, `uint32` 0x100 and 0x2BC at 0x8 and 0xC, `uint64` 0x1FFF at 0x10 and 0x18, a pointer to its work memory at 0x20, and the memory query's 32 MiB and 64 KiB as `uint32` at 0x28 and 0x2C, with the other bytes zero; the priority and CPU mask meanings of 0x8 to 0x1F are assumed from the values. Called with that parameter and 32 MiB of anonymous memory from a homebrew payload on a PS5 (firmware 13.60) with no PS VR2 headset, it did not return within a minute, so what it returns without a headset is unknown and it throws -- [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) - eight exports throw. `6Jy73SRfG-o` (initialization) and `9R6W33Au4B4` return 0x8A740006 on the console for null arguments, for zeroed, 0xAA- and 0x01-filled buffers and for a size field from 8 to 0x400 in any of the first three arguments; initialization also returned it for the memory query's parameter with 32 MiB aligned to 64 KiB passed inside the parameter or as further arguments, so the parameter layout it accepts is unknown. `NBhWy+Gl0tY` returns 0x8A740006 for those buffers but crashes for null arguments; `jRmuXjsknsI` returns 0x8A740006 for null arguments and crashes for buffers, also when every 8-byte slot points at readable memory; `O8ZkTFtvUL4`, `940WPg+WwIk`, `oZDm1bynCaA` and `U1VYczwqukY` crash for both +- [sceVrTracker2Initialize](../../../core/libs/prx/libSceVrTracker2/Export.cpp) - never initializes: after validation it returns the first non-zero libSceHmd2 tracking or LED-detector open/start result and resets to uninitialized, and throws if all four succeed or for modes 1000/1001, whose application category the host lacks. Unverified: the mode 0 category query is assumed to succeed, the `sceKernelGetAppInfo2` attribute checks are skipped (the host has no such record; a zeroed one passes), the daemon IPC client and work-memory heap are not created, rollback only undoes what the call opened, and the meanings of the options at 0x30, 0x38 and 0x39 remain unknown (0x39 is only range-checked). The behaviour is not measured on a console; the -1 a no-headset host gets relies on libSceHmd2's measured pre-initialization answers. PPSA05666 calls it with no headset and, with #7463, #7690 and #7658 applied, stopped on the NotImplemented. +- [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) - the seven anonymous throwing exports now have their consumed argument slots restored. Calibration output (`jRmuXjsknsI`) uses a 32-bit capacity with 608/1216-byte variants; its fields remain unresolved. Mesh requirements (`NBhWy+Gl0tY`) contain four 32-bit counts/sizes. The mode/buffer/capacity/size export (`9R6W33Au4B4`) uses 64-bit sizes. The 32/48-byte result parameters are recovered; result output and pose parameters still need full layouts; the telemetry input (`oZDm1bynCaA`) spans 7024 bytes. Their bodies remain unimplemented. + +- [Anonymous tracking queries](../../../core/libs/prx/libSceVrTracker2/Export.cpp) still lack initialized behavior. The paired option setter/getter consume 28/40-byte records with three integer options, a reset byte and one/two float vectors; option meanings remain unresolved. The scalar query uses an 8-byte size/float record. Diagnostic output spans 4096 bytes, extended output 0xD02E8 bytes, and tracking configuration input 1024 bytes; their fields remain unresolved. The relative-pose query uses two coordinate-system selectors and a 64-byte output containing a pose and 32 cleared bytes; the counter query uses a 64-bit output and a 32-bit selector whose meaning remains unresolved. Remaining aliases still need individual signatures. + +- [Anonymous diagnostic exports](../../../core/libs/prx/libSceVrTracker2/Export.cpp) have separate pointer contracts for composite poses, configuration, camera images, sensor frames, tracking status and boundary parameters. The camera-image query takes a 32-bit byte count and an in/out frame identifier; diagnostics use six 512-byte text sections, and the mode setter uses a 12-byte size/mode/float record. Composite pose, sensor/status, configuration and boundary fields still require reconstruction; none of these exports supports initialized operation. + +- [Camera-frame exports](../../../core/libs/prx/libSceVrTracker2/Export.cpp) do not acquire or release native buffers. Their descriptor has a selector, two identifiers, dimensions, pitch, pixel pointer and buffer index; native acquisition writes the first 36 bytes, leaving natural tail padding untouched. The optional 48-byte metadata record and identifier meanings remain unresolved. The scalar-option setter/getter use a float value/output, but its units and meaning remain unresolved. + +- [Device and spatial queries](../../../core/libs/prx/libSceVrTracker2/Export.cpp) retain uninitialized responses. Position queries use a three-float input or a device handle and a 36-byte size/status/two-vector/scalar result; the vector and scalar meanings remain unresolved. The bounds query uses the recovered 80-byte oriented box. Device-boundary operations consume a handle and selector; their internal parameter layout differs from the public boundary record and remains unresolved, as do the 28-byte device-calibration status and reset-pose input. + +- [Map and flag queries](../../../core/libs/prx/libSceVrTracker2/Export.cpp) retain uninitialized responses. Map initialization takes a device handle and a pointer to one float; map data retrieval takes an output and a 32-bit size constrained to 2048. Map data and status field meanings remain unresolved. Tracking flags use a required boolean output and an optional byte output, rather than an argument-free alias. + +- [Diagnostic records](../../../core/libs/prx/libSceVrTracker2/Export.cpp) have individual output-pointer signatures, but field interpretation remains incomplete. Native runtime status clears 88 bytes; diagnostic state writes 120 bytes, sensor diagnostics reach byte 339, device diagnostics reach byte 6983, timing diagnostics write 80 bytes, and sensor summary writes 32 bytes. The map-configuration getter/setter exchange the same 72-byte record. These exports still return only the uninitialized error. + +- [Extended result and boundary fitting](../../../core/libs/prx/libSceVrTracker2/Export.cpp) retain uninitialized responses. The extended result spans 816 bytes; its coordinate-specific parameter prefixes the 48-byte query with a coordinate selector and four-byte padding. Boundary fitting takes the recovered point list, optionally returns two 80-byte bounds, and writes four byte flags plus four floats; flag and dimension semantics remain unresolved. The device-center output is a 16-byte size/three-float record. + +- [Mesh and image-pose utilities](../../../core/libs/prx/libSceVrTracker2/Export.cpp) retain uninitialized responses. Mesh generation takes vertex and 16-bit index outputs plus a 32-bit format. Timed-pose queries take an in/out result, timestamp and user tag. Image submission consumes a record with format at byte 16 and a data pointer at byte 80; image-pose retrieval uses an in/out record with a buffer pointer and capacity. Record fields and initialized algorithms remain incomplete. Scalar queries use float outputs; the map-scalar query also consumes map data. + +- [Legacy unsupported operations](../../../core/libs/prx/libSceVrTracker2/Export.cpp) have separate input-pointer signatures. Two consume only the size prefix of 12/20-byte records, leaving their other fields unrecoverable from these bodies. The coordinate operation reads a selector at byte 4 but always returns unsupported after initialization; the remaining record layout is unresolved. diff --git a/docs/dev/debt/libSceWebBrowserDialog.md b/docs/dev/debt/libSceWebBrowserDialog.md index 3d44927711..c057cc3710 100644 --- a/docs/dev/debt/libSceWebBrowserDialog.md +++ b/docs/dev/debt/libSceWebBrowserDialog.md @@ -2,9 +2,8 @@ ### Silent stubs -- [libSceWebBrowserDialog](../../../core/libs/prx/libSceWebBrowserDialog/Export.cpp) - `sceWebBrowserDialogOpen` and `sceWebBrowserDialogOpenForPredeterminedContent` run the common-dialog state machine without displaying an interactive browser, and `sceWebBrowserDialogGetResult` reports that the user cancelled. Minecraft Bedrock (PPSA17221) imports `sceWebBrowserDialogOpenForPredeterminedContent` (#5744). The real export takes a second argument, the allow-list of up to twenty addresses (SharpProspero `docs/dialogs.md`); it is not read here, so no address filtering is modelled, and the exact validation order and codes with a null second argument are unverified. -- [libSceWebBrowserDialog](../../../core/libs/prx/libSceWebBrowserDialog/Export.cpp) - `sceWebBrowserDialogClose` transitions an initialized dialog directly to `COMMON_DIALOG_STATUS_FINISHED` even when called before `Open`, following the Signin / NetCtlAp / PlayerSelection convention; this behavior is unverified against real hardware. - +- [libSceWebBrowserDialog](../../../core/libs/prx/libSceWebBrowserDialog/Export.cpp) - `sceWebBrowserDialogOpen` and `sceWebBrowserDialogOpenForPredeterminedContent` run the common-dialog state machine without displaying an interactive browser, and `sceWebBrowserDialogGetResult` reports that the user cancelled. Minecraft Bedrock (PPSA17221) imports `sceWebBrowserDialogOpenForPredeterminedContent` (#5744). The 424-byte allow-list parameter and its twenty optional address pointers are validated, but address filtering is not modelled without an interactive browser. Main open-parameter validation remains incomplete. +- [sceWebBrowserDialogSetCookie, sceWebBrowserDialogResetCookie](../../../core/libs/prx/libSceWebBrowserDialog/Export.cpp) (libSceWebBrowserDialog) - there is no browser cookie store. Both calls check their parameters in the same order and with the same codes as the PS5 12.00 module, then return 0 without storing or clearing anything. On the console they then forward the request to the browser service. While an opened dialog has not been updated to finished, closed or terminated, they return `0x80B80006` (the console's open-dialog flag). ### Unknown function info - [sceWebBrowserDialogGetEvent, sceWebBrowserDialogNavigate, sceWebBrowserDialogSetZoom](../../../core/libs/prx/libSceWebBrowserDialog/Export.cpp) (libSceWebBrowserDialog) - their names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceWebBrowserDialog.c); the no-parameter prototypes follow [shadPS4 `webbrowserdialog.cpp`](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/web_browser_dialog/webbrowserdialog.cpp#L23-L85) and are not measured on a console. They are declared without parameters solely to resolve known imports; this is not an ABI claim, and every call throws. diff --git a/docs/dev/debt/libSceZlib.md b/docs/dev/debt/libSceZlib.md index 5d289d575b..85bd381724 100644 --- a/docs/dev/debt/libSceZlib.md +++ b/docs/dev/debt/libSceZlib.md @@ -2,4 +2,4 @@ ### Functional -- [libSceZlib](../../../core/libs/prx/libSceZlib/Export.cpp) completes RFC1950 decompression during submission and supports one outstanding request, null-timeout waits and one result retrieval. Timed waits, multiple outstanding requests, repeated retrieval and finalization with an unread result throw. The 64-bit initialization length and 64 KiB output limit with 2 KiB destination alignment follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Compression/Zlib.cs); the PS4 initialization length is 32 bits. shadPS4 also requires the output length to be a multiple of 2 KiB and returns `0x81120016` for invalid arguments; this implementation accepts any length up to 64 KiB (SharpProspero documents only the pointer alignment) and throws on invalid arguments. Scratch memory is not used, and its firmware size/alignment requirements are unknown. The initialization errors follow SharpProspero; malformed/checksum/truncated streams report `0x811200FF` and insufficient output `0x8112001C` as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/zlib/zlib.cpp). PS5 error precedence, scheduling, completion selection, result consumption and cancellation are unverified. The payload SDK's FreeBSD kernel `z_stream` and host errno are not this library's ABI. +- [libSceZlib](../../../core/libs/prx/libSceZlib/Export.cpp) is unverified on PS5 for several outstanding requests in submission order, repeated `sceZlibGetResult`, `0x81120002` for an unknown ID, `0x81120027` on timeout and results dropped by `sceZlibFinalize`: they follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/zlib/zlib.cpp) (PS4), and the microsecond timeout follows SharpProspero. `0x8112000B` (not ready) is never returned: requests finish at submission. A wait interrupted by `sceZlibFinalize`, non-null work memory and an unaligned destination throw. Native validation confirms the 64-bit initialization length, 2 KiB output granularity up to 64 KiB, null-argument errors and initialization precedence. Driver scheduling, cancellation and scratch memory are unverified; malformed streams map to `0x811200FF`, exhausted output to `0x8112001C`. diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 581f589299..2db74b5e99 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -1,50 +1,66 @@ # libc technical debt +### Silent stubs + +- [_Mtx_current_owns](../../../core/libs/prx/libc/src/Threads.cpp) (libSceLibcInternal, implemented in libc, NID `VYQwFs4CC4Y`) returns 1 and ignores its argument. This follows the PS5 12.00 body reading reported in [PR8859](https://github.com/boykopovar/AnyPS5/pull/8859), rather than inspecting host mutex ownership. The firmware observation is attributed to that contributor; other firmware versions and the argument's console type remain unverified. + ### Unknown function info -- [strtonum](../../../core/libs/prx/libc/src/StringExtensions.cpp) (libc) is unverified on the console. Its signature, decimal parsing, inclusive bounds, error strings, error precedence and errno preservation follow [FreeBSD 11.4 strtonum.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/strtonum.c). Parsing uses the host `strtoll` with base 10. +- [strtonum](../../../core/libs/prx/libc/src/StringExtensions.cpp) (libc) is unverified on the console. Its signature, decimal parsing, inclusive bounds, error strings, error precedence and errno preservation follow [FreeBSD 11.4 strtonum.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/strtonum.c). Parsing uses the host `strtoll` with base 10. It is not exported by `libSceLibcInternal.sprx` on a PS5 (firmware 13.60), so a payload process cannot call it there +- [fegetexcept](../../../core/libs/prx/libc/src/FloatingPoint.cpp) (libSceLibcInternal, implemented in libc) has not been read from the PS5 library or measured on a console. It follows FreeBSD 11.4 amd64 [fenv.h](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/x86/fenv.h#L121-L133), reporting the six unmasked x87 exceptions independently of MXCSR; whether the PS5 uses the same rule when the units disagree is unverified. - [sceLibcBacktraceGetBufferSize, sceLibcBacktraceSelf](../../../core/libs/prx/libc/src/Backtrace.cpp) (libc) - signatures and frame record layout inferred from a title (next pointer, u32 module offset, module name at +0x10); the u32 at +0xC is unknown and written as 0. Module names are host file names without the `.guest.prx` suffix, so their case may differ from the PS5 module name. On Windows, unwinding uses `_Unwind_Backtrace` and stops at a frame without `.ehmeta` or `.ehfram` tables whose PE unwind data has an exception or termination handler, leaving the trace empty or cut short while still returning 0. Implemented only on Linux and Windows; other platforms throw - [ApplicationGuestHeapApi](../../../core/libs/prx/libc/src/ApplicationProcessParameters.cpp) (libc) - on Linux, HLE application allocation uses a loaded guest SDK's ten allocator exports when the title's replacement table has an allocator entry and the exports belong to one native module. With an empty table it keeps the default heap, since a bundled `libc.prx` may export the allocator NIDs while forwarding them to HLE. Its CRT owns application allocator initialization and finalization; HLE retains the module until process exit. Partial or mixed providers throw. This avoids reinitializing an allocator already used by SDK startup; console behavior and other host platforms remain unverified. - [fdopen](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) validates descriptor access modes as FreeBSD 11.4 [fdopen.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/fdopen.c) does: a requested mode incompatible with a descriptor not opened for both reading and writing returns null with `EINVAL`, leaving the descriptor open. Windows reads the handle's granted access through the same host helper as `fcntl(F_GETFL)`; a closed descriptor returns null with `EBADF`. Console behavior is unverified. - [scriptingGetMem, scriptingFreeMem](../../../core/libs/prx/libc/src/ApplicationHeap.cpp) (libc) - runtime allocator hooks for Unity titles. The allocation signature `(alignment, size)`, minimum alignment 16 and null result for a non-power-of-two alignment follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libKernel.cpp); the free hook's one-pointer signature follows [Raeen's recorded contract](https://github.com/Whoisraeen/Raeen/blob/2db56923204b89719916b95e29c2f3c4e1d9f90b/docs/unity-dlsym-blocker.md). Both use libc's own guest heap, not the title's registered allocator. A title that ships its own `libc.prx` (Metal Gear Solid Master Collection, PPSA18194) has already initialized that allocator, and reaching it from here would run its initializer a second time. That title's IL2CPP runtime looks up only `scriptingGetMem`, by name from `Il2cppUserAssemblies.prx`, and asks it for 256 KiB blocks aligned to 4 KiB. Which console library exports the hooks is unknown, and there is no console measurement. Allocation failure and sizes that overflow return null. -- [_Mtx_trylock](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `k6pGNMwJB08`) - returns busy for a non-recursive mutex already owned by the caller, assumed to match `_Mtx_lock`'s ownership handling; exact console behavior is unverified. -- [nearbyintf](../../../core/libs/prx/libc/src/Math.cpp) (libc, NID `c+4r-T-tEIc`) uses the host rounding mode, not the console's; console rounding mode state remains unverified. +- [_Mtx_trylock](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `k6pGNMwJB08`) - returns busy for a non-recursive mutex already owned by the caller. The `libc.prx` that titles ship (read from disassembly of PPSA13934's copy, 0x5cf0, not measured on a console) passes its argument to `scePthreadMutexTrylock` and returns 0 for success, `_Thrd_busy` (3) for `SCE_KERNEL_ERROR_EBUSY` and `_Thrd_error` (4) for any other error, so busy for an owned non-recursive mutex comes from the kernel mutex there; this libc keeps its own owner and count instead, and its result for a kernel error other than busy is not compared. +- [nearbyintf](../../../core/libs/prx/libc/src/Math.cpp) (libc, NID `c+4r-T-tEIc`) rounds with the MXCSR rounding mode, as the console's `libSceLibcInternal.sprx` does in a payload process on a PS5 (firmware 13.60): with the x87 mode set to upward and MXCSR to nearest it rounded 2.5 to 2, and with MXCSR upward to 3, and the four modes gave the results the host gives. The console also raised `FE_INEXACT` for those inexact inputs, which the host's `nearbyintf` does not; whether it does for exact inputs was not measured - [__std_find_trivial_unsized_N](../../../core/libs/prx/libc/src/VectorAlgorithms.cpp) (libc) - matches the `libc.prx` bundled with PPSA08427 (disassembly, and that binary run side by side on random inputs): when nothing matches between `first` and the end of its 32-byte-aligned block, the console rescans that whole block without masking and so can return a match up to 31 bytes before `first` - [_Thrd_sleep](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `jfRI3snge3o`) follows the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`xtime.c`) and C11 Annex K, without console verification. It reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest `CLOCK_REALTIME`, normalizes the nanoseconds and sleeps through `nanosleep` on POSIX, retrying on `EINTR`, or high-resolution waitable timers on Windows; a null xtime or one whose normalization overflows throws. Guest thread cancellation is not integrated with this wait. The console's `_Thrd_sleep` (disassembled from a title's `libc.prx`) also takes an optional `xtime*` of remaining time and returns `int`; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched. +- [setjmp, longjmp](../../../core/libs/prx/libc/src/JumpBuffer.cpp) (libc) save the signal mask at 72 in the jump buffer and restore it, as FreeBSD 11.4's [`setjmp` and `longjmp`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/amd64/gen/setjmp.S#L51-L85) do, which is unverified on a console. They go through the `sigprocmask` libkernel registers when it loads and mark the buffer at 88 as `sigsetjmp` does, so a buffer saved before then is jumped to without changing the mask - [_Mtx_init_with_name](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `tgioGpKtmbE`) forwards to `_Mtx_init` and ignores the mutex name parameter. - [_Cnd_timedwait](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `McaImWKXong`) follows the console's `_Cnd_timedwait` (disassembled from a title's `libc.prx`), where xtime is an absolute deadline converted against the current time: it returns `_Thrd_timedout` (2) on expiration and `_Thrd_success` (0) on notification. The console returns 4 for other internal pthread errors while this implementation throws. -- [fegetround, fesetround, fetestexcept, feclearexcept, fegetexceptflag and fesetexceptflag](../../../core/libs/prx/libc/src/FloatingPoint.cpp) (libSceLibcInternal, implemented in libc) follow FreeBSD 11.4 amd64 [fenv.h](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/x86/fenv.h): rounding modes use x87 control bits 10-11, exception flags include denormals (`0x3f`), and `fexcept_t` is 16 bits. They access the current host thread's x87 and MXCSR state. This console ABI is assumed, as for `fegetenv`, not measured on hardware. Null flag pointers throw instead of faulting. +- [fegetround, fesetround, fetestexcept, feclearexcept, fegetexceptflag and fesetexceptflag](../../../core/libs/prx/libc/src/FloatingPoint.cpp) (libSceLibcInternal, implemented in libc) follow FreeBSD 11.4 amd64 [fenv.h](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/x86/fenv.h): rounding modes use x87 control bits 10-11, exception flags include denormals (`0x3f`), and `fexcept_t` is 16 bits. They access the current host thread's x87 and MXCSR state. Under Rosetta 2 the six MXCSR exception masks always read back set, so an SSE exception the guest unmasks stays masked; the x87 masks and the other MXCSR bits are kept. This console ABI is assumed, as for `fegetenv`, not measured on hardware. Null flag pointers throw instead of faulting. +- [feenableexcept, fedisableexcept, __feenableexcept, __fedisableexcept, fegettrapenable and fesettrapenable](../../../core/libs/prx/libc/src/FloatingPoint.cpp) (libc) change only the x87 masks under Rosetta 2: MXCSR keeps its six exception masks set there, so no SSE trap is enabled, and the enable and disable results, which report the traps enabled on both units, report none - [fegetenv](../../../core/libs/prx/libc/src/Math.cpp) (libSceLibcInternal, implemented in libc, NID `psx0YiAKm7k`) follows FreeBSD 11 amd64 [fenv.c](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/amd64/fenv.c) with the 32-byte `fenv_t` of [x86/fenv.h](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/x86/fenv.h): `fnstenv` fills the 28-byte x87 part, MXCSR follows at 0x1c, and the x87 control word is reloaded because `fnstenv` masks every x87 exception. That the console's `libSceLibcInternal` has the same code and layout is assumed, not read from its binary. A null pointer throws instead of faulting -- [Guest arena](../../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws +- [feholdexcept](../../../core/libs/prx/libc/src/Math.cpp) (libSceLibcInternal, implemented in libc, NID `1kduKXMqx7k`) - console equivalence and module/library versions are unverified. The signature and 32-byte amd64 environment layout follow the public SDK [fenv.h](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/include/freebsd/fenv.h); saving both units, masking exceptions and clearing pending flags follows FreeBSD 11 amd64 [fenv.c](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/amd64/fenv.c). The NID comes from the public 3.20 stub inventory, not a module examined here. Null storage throws before modifying the environment, where the reference dereferences it +- [feupdateenv](../../../core/libs/prx/libc/src/Math.cpp) (libSceLibcInternal, implemented in libc, NID `YXQ4gXamCrY`) follows FreeBSD 11.4 amd64 [fenv.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/msun/amd64/fenv.c): capture the old x87/MXCSR exception union (`0x3f`), restore the saved 32-byte environment, raise those flags in both units, then `fwait`. The console's layout, behavior and module versions are unmeasured; state belongs to the current host thread. A null input or MXCSR bits unsupported by the host's `FXSAVE` mask throw before changing floating-point state; the mask's architectural zero fallback is `0xffbf`. Console fault behavior for malformed environments is unknown. The separate `__fe_dfl_env` object remains unimplemented; no host `FE_DFL_ENV` sentinel is substituted. +- [Guest arena](../../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws. The console's low-address layout is not measured. Linux and Windows reserve free addresses from 64 KiB, except host mappings and low chunks the Linux address policy refuses; null hints still allocate from 8 GiB. macOS and ASan builds still start the arena at 8 GiB and cannot supply low 32-bit addresses. Guest thread creation ignores a stack address set in its attributes. A host-loaded executable is not moved, so a PIE above the arena gets no memory within 2 GiB of its code ([#5217](https://github.com/boykopovar/AnyPS5/issues/5217)) - [sceLibcInternalBacktraceForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - `(const char* heapName)` and its return follow the `libc.prx` ASTRO BOT (PPSA21564 01.007.000) bundles: its four calls (0x6801, 0x7F46, 0xE75E, 0xEE02) are in the `malloc_stats` and `sceLibcMspaceMallocStats` paths, each after a `[%s] ...` line printed with the same heap name buffer, and the caller ignores the result and continues. The console's backtrace output is not reproduced: the heap name and the guest return address are written to the host standard error -- [sceLibcInternalHeapErrorReportForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - `(void* heap, void* block, uint32 error)` follows the same `libc.prx` (call at 0xF634): it passes the heap, the block and the nonzero result of its block check, and then calls a function that never returns, so the report only has to return. The meaning of the error values is unknown; the three values and the guest return address are written to the host standard error +- [sceLibcInternalHeapErrorReportForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - `(void* heap, void* block, uint32 error)` writes to the guest's standard output what a payload process on a PS5 (firmware 13.60) wrote: `[SceLibc] A heap error is detected.`, `[SceLibc] -- Mspace Name --`, then the C string at heap+0x3B0 followed by ` (%2u)` of the 32-bit value at heap+0x3F4 when it is not 0, the reason `segment out of range.` for error 1 only (38 other measured values print none), and `pid:<0x..> tid:<0x..>`, and returns. The pid and tid are the host's, where the console prints its own process and kernel thread ids. The block's contents did not change the console's output and it is not read here; the values and the guest return address also go to the host's standard error. A null heap faulted on the console and throws here. What the other heap fields and error values mean is unknown - [sceLibcPafMspaceCreate, Destroy, Malloc, Memalign, PosixMemalign, Free, Calloc, Realloc, Reallocalign, MallocStats and MallocStatsFast](../../../core/libs/prx/libc/src/Mspace.cpp) - PAF exports delegate to `sceLibcMspace` without malloc hooks; `Memalign` and `PosixMemalign` forward to their `Mspace` counterparts with owner ignored and are unverified on console. `Create`'s options block (version, type 1 or 2 at +2, field at +0x10) throws when non-null as its meaning is unknown. Block footers are not modelled, so owners are ignored: on PS5 firmware 13.60 mismatched owners in `Reallocalign` or in-place `Realloc` kill the process and `MallocStats` returns 1 if non-heap-owned blocks exist, which is not reproduced. `MallocUsableSize`, `Trim`, `IsHeapEmpty`, `CheckMemoryBounds`, `ReportMemoryBlocks` and `GetFooterValue` are not implemented. - [malloc_usable_size](../../../core/libs/prx/libc/src/ApplicationHeap.cpp) (libc) - with the default allocator it returns the size the block was requested with (the range registered for it), not the rounded-up chunk size Sony's allocator reports; both satisfy "at least the requested size", and bytes past the request would not be write-watched. With an application allocator it calls entry 9 of the replacement table (`malloc_usable_size` in KytyPS5's `MallocReplace` order) and throws when the table leaves it null, like `malloc_stats_fast` - [malloc_stats_fast](../../../core/libs/prx/libc/src/GuestHeap.cpp) (libc) - with the default allocator it fills the 0x28-byte managed-size block as `sceLibcMspaceMallocStatsFast` does, from the default heap's own accounting, not Sony's allocator: the system sizes count the 1 MiB spans of its small-block classes and its committed large blocks, cached free ones included, and the in-use sizes count each handed-out block at its size-class or 64 KiB-granule size, header included. A block size below 0x28 or a null pointer returns 22, as in the mspace version; the version field is not checked. Ship of Harkinian (PPSA99320) calls it about 10 s after start-up. -- [malloc, calloc, realloc, memalign, aligned_alloc and reallocalign](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) - follow FreeBSD 11.4 `contrib/jemalloc/src/jemalloc.c` for failures, not measured on the console: a size the default heap rejects or the guest arena cannot place (including a `calloc` product that overflows) returns null with `errno` `ENOMEM`, and `memalign` and `aligned_alloc` return null with `EINVAL` for an alignment that is zero or not a power of two. FreeBSD's libc has no `memalign`, so setting `errno` there is assumed. A host commit failure for a block that fits in the arena (Windows `ERROR_NOT_ENOUGH_MEMORY`, `ERROR_OUTOFMEMORY` or `ERROR_COMMITMENT_LIMIT`, Linux `ENOMEM`) resets and releases the reserved range and returns null with `ENOMEM`; any other commit failure releases the range and throws. `reallocalign` returns null with `EINVAL` for an invalid alignment and `ENOMEM` like the others, which is an assumption as it is not in FreeBSD. `realloc(ptr, 0)` frees the block and returns null, where FreeBSD 11.4's jemalloc 4.5 resizes it to one byte and returns a block -- [reallocf](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libSceLibcInternal, implemented in libc) - follows FreeBSD 11 `lib/libc/stdlib/reallocf.c` on top of this libc's `realloc`, not measured on the console: size 0 frees the block and returns null as `realloc` does here, so the block is not freed twice; when `realloc` fails because the application allocator returns null or the default heap rejects the size, the block is freed through the same allocator, `errno` is `ENOMEM` and null is returned. Other failures (a commit failure that is not out of memory, an unknown pointer) throw, as they do for `realloc` +- [malloc, calloc, realloc, memalign, aligned_alloc, reallocalign and posix_memalign](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) - the console's own heap was measured in a payload process on firmware 13.60, not in a title. A heap a title replaces was not measured and keeps the FreeBSD jemalloc behaviour: `ENOMEM` on failure, `EINVAL` for an alignment that is zero or not a power of two, and `realloc(ptr, 0)` frees the block. With the console's heap, failures set `EINVAL`, and `errno` is left alone for a size above `SIZE_MAX - 0x1008f` (`SIZE_MAX - (A + 0x100af)` for an alignment over 32 rounded up to A), a `calloc` overflow or an alignment of 2^63 or more. Alignments round up to a power of two, and `realloc(ptr, 0)` keeps the block with 0 usable bytes. `reallocalign(ptr, 0, alignment)` for a block without that alignment was not measured and throws +- [reallocf](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libSceLibcInternal, implemented in libc) - follows FreeBSD 11 `lib/libc/stdlib/reallocf.c` on top of this libc's `realloc`, as a payload process on a PS5 (firmware 13.60) does with the default heap (null and 0, size 0, 2^40 with `errno` 22 and the block freed, `SIZE_MAX` with `errno` untouched): size 0 does what `realloc` does here, keeping the block with 0 usable bytes with the default heap and freeing it and returning null with an application allocator, so the block is not freed twice; when `realloc` fails, the block is freed through the same allocator and null is returned with the `errno` that `realloc` left. Other failures (a commit failure that is not out of memory, an unknown pointer) throw, as they do for `realloc` - [sceLibcMspaceReallocalign](../../../core/libs/prx/libc/src/Mspace.cpp) (libSceLibcInternal, implemented in libc) takes `(void* heap, void* block, size_t alignment, size_t size)`, the order of the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x13880, whose internal routine at 0x12390 treats the third argument as the alignment and, for a null block, passes it to the routine `sceLibcMspaceMemalign` uses). Measured on a PS5 (firmware 13.60) from a homebrew payload: a null block with alignment 4096 and size 100 returns a 4096-aligned block; an alignment of 0 or 3 succeeds and 100 gives a 128-aligned block, as the routine raises it to at least 32 and to the next power of two; an alignment of 2^63 or more returns null, keeps the block and leaves `errno` alone; and a size of 0 returns a block (the same one when it is already suitable) instead of freeing it. A block that is aligned and large enough is kept where it is, without giving back the bytes past the new size as the console's in-place resize does - [acosl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, like the other libc math functions here, not by the console's (FreeBSD msun ld80 code in Sony's `libc.prx`): results can differ from the console's in the last bit, and on Linux glibc sets `errno` to `EDOM` for `acosl` outside [-1, 1] where msun only raises the floating-point exception. `long double` is the x87 80-bit format, as Sony's `frexpl` uses it (`fldt 0x8(%rsp)`, result in `st(0)`) - [atanl, sinl, cosl, tanl, sinhl, coshl, tanhl, expl, exp2l, logl, log10l and powl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, as `acosl` is, not by the console's FreeBSD msun ld80 code: results can differ from the console's in the last bit (glibc's `log10l(1000)` is not exactly 3), and on Linux glibc sets `errno` to `EDOM` or `ERANGE` where msun only raises the floating-point exception. `ldexpl` and `truncl` are exact operations and give the console's result; `logb` and `remainder` give the console's value too, but on Linux glibc sets `errno` (`ERANGE` for `logb(0)`, `EDOM` for `remainder` by zero or of an infinity) where msun only raises the floating-point exception - [Dinkumware atomics](../../../core/libs/prx/libc/src/DinkumwareAtomics.cpp) (libc) - every operation is sequentially consistent whatever memory order it is given, which is never weaker than the console. Matching the `libc.prx` bundled with PPSA08427 (that binary run side by side on every order from 0 to 7), `_Atomic_load_8` returns 0 for a release, acq_rel or invalid order and the 8-byte exchange and fetch operations return 0 without touching memory for an order above `memory_order_seq_cst`; the generic `_Atomic_copy`, `_Atomic_exchange` and `_Atomic_compare_exchange_*` lock the byte flag passed first, as the console does +- [_Fetch_{and,or,xor}_seq_cst_{1,2,4}](../../../core/libs/prx/libc/src/DinkumwareAtomics.cpp) (libc) follow the two-argument unsigned 8-, 16- and 32-bit helpers in Dinkumware [VC12 xatomic.h](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/xatomic.h), using sequentially consistent atomic read-modify-write operations and returning the original value. The firmware 3.20 export inventory supplies the names and NIDs; the reference defines inline namespace helpers, so the equivalence to the console exports and their calling convention are inferred, not console-measured. A live, suitably aligned object is required as in the reference; invalid-pointer and misalignment fault behavior on the console is unverified. Windows tests cover raw and patched exports, values, neighboring storage and concurrent updates; a finite ordering litmus cannot prove the memory model. - [std_execute_once](../../../core/libs/prx/libc/Export.cpp) (libc) - assumed to be the plain-named twin of `std::_Execute_once` (`_ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1_`, the callee of Dinkumware `call_once`): it delegates there and gets the [thread.once.callonce] semantics already verified for that symbol (one invocation per flag, concurrent callers block until it completes, and a failing or throwing invocation leaves the flag unready so the next caller runs it). The name equivalence is not confirmed on a title, and the callback convention (called with (null, arg, null), non-zero return meaning success) is the twin's, unverified for this symbol - [__atomic_load, __atomic_store, __atomic_exchange and __atomic_compare_exchange](../../../core/libs/prx/libc/src/AtomicBuiltins.cpp) (libc) - the generic, size-parameterised libatomic entry points are a single `ud2` in the `libc.prx` bundled with PPSA08427 (disassembly), so the console traps on any call; these throw instead +- [Sized __atomic calls](../../../core/libs/prx/libc/src/DinkumwareAtomics.cpp) (libc) - the 36 `__atomic_load_N`, `__atomic_store_N`, `__atomic_exchange_N`, `__atomic_compare_exchange_N` and `__atomic_fetch_{add,sub,and,or,xor}_N` calls (N = 1, 2, 4, 8) are sequentially consistent whatever order they are given. The `libc.prx` bundled with PPSA08427, run side by side on random values and on every order from -1 to 7, switches on the order but always uses a locked instruction or CAS loop, except that a store with an order other than `memory_order_seq_cst` is a plain store, so these are never weaker than the console. Unlike `_Atomic_load_8` and the 8-byte `_Atomic_*` exchange and fetch calls above, the console accepts every order here, including release, acq_rel and invalid ones, and these do the same - [Dinkumware mutexes and conditions](../../../core/libs/prx/libc/src/Threads.cpp) (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in [#3079](https://github.com/boykopovar/AnyPS5/pull/3079). The title's `_Mtx_lock` maps `0` to `_Thrd_success` (0), `0x8002000b` to `_Thrd_busy` (3), and other failures to `_Thrd_error` (4); its internal mutex init recognizes recursive flag `0x100`, while every other type uses default attributes. The observed relock failure for a plain mutex suggests those defaults are error-checking, but that has not been confirmed on hardware. The title's `_Mtx_unlock` and `_Cnd_wait` return success regardless of their internal result. The host implementation treats invalid ownership preconditions as no-ops, and throws for invalid handles, host wait errors and destroying a locked mutex as debug guards; those cases are not confirmed against the console. +- [_Mtx_timedlock](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `hPzYSd5Nasc`) follows the console's `libc.prx` at `0x5d50` (PPSA02530): it takes an absolute xtime and returns `_Thrd_timedout` (2) on expiry (`0x8002003c`) or 5 on other failures. On non-Windows hosts the native mutex and condition are `std::timed_mutex` and `std::condition_variable_any`; Windows keeps the `CRITICAL_SECTION`/`CONDITION_VARIABLE` pair because `std::condition_variable_any` failed `guest_libc_cnd_timed_wait` on the Windows runner, and its `_Mtx_timedlock` polls `TryEnterCriticalSection` until the deadline. Windows timing is not measured on the console. - [Floating-point compiler-rt builtins](../../../core/libs/prx/libc/src/FloatBuiltins.cpp) (libc) follow the `libc.prx` bundled with PPSA08427 (disassembly, and that binary run side by side on random inputs) where it differs from current compiler-rt: the soft-float `__add`, `__sub`, `__mul` and `__div` (`sf3`, `df3`) round to nearest, raise no exception flags and ignore MXCSR, here done with SSE under a default MXCSR that is restored afterwards; division flushes a quotient below the smallest normal to a signed zero; `__extendsfdf2` and `__truncdfsf2` copy NaN payloads unshifted; float-to-integer conversions do not saturate but shift the significand by a count masked to the shift width. `__mulsc3`, `__muldc3`, `__divsc3` and `__divdc3` fuse the same products as the console with `fma` and scale through the x87 like its `scalbn`, so their values match, but NaN payloads and exception flags can differ, as the host `fma` and `logb` are not the console's VEX FMA code and libm -- [random, srandom, initstate, setstate](../../../core/libs/prx/libc/src/Random.cpp) (libc) follow FreeBSD 11 [random.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdlib/random.c): the additive feedback generator on a default 128-byte `TYPE_3` table, `good_rand` seeding, state sizes rounded down to 8, 32, 64, 128 or 256 bytes, and `setstate` returning the previous state or null for a rear position past the degree. `initstate` below 8 bytes returns null without setting errno, as that file and its manual do (glibc sets `EINVAL`). FreeBSD does not lock the generator; here every call is serialised by one lock, so concurrent callers draw the serial sequence between them. A null state throws, where FreeBSD faults. That the console's `libSceLibcInternal` uses this FreeBSD code and not another generator is unverified +- [random, srandom, initstate, setstate](../../../core/libs/prx/libc/src/Random.cpp) (libc) - not measured: the state `setstate` returns and its null return for a rear position past the degree, `errno` after `initstate` below 8 bytes, a null state (throws here), an `initstate` seed other than 1, concurrent callers (one lock serialises every call here) and a title's own libc. Measured in a payload process on a PS5 (firmware 12.70): a fresh process draws the `srandom(1)` sequence; `srandom` seeds an additive feedback generator through a `good_rand` that reads its argument as an int32, turns 0 into 123459876 and applies `x = 16807 * (x % 127773) - 2836 * (x / 127773)`, adding 0x7fffffff to a negative result, which reproduces seeds 0, 1, 2, 12345, 0xffffffff and 0x80000000; `initstate` returns null at 0 and 7 bytes and selects types 0 to 4 by size as measured at 8, 15, 31, 32, 63, 64, 127, 128, 129, 255, 256, 257 and 1000 bytes. +- [srandomdev](../../../core/libs/prx/libc/src/Random.cpp) (libSceLibcInternal, implemented in libc) is not verified against the console. The firmware 3.20 [export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.c) names NID `a2MOZf++Wjg`; it follows FreeBSD 11 [random.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdlib/random.c), filling every active generator word from host OS entropy instead of `KERN_ARND`, then resetting additive feedback positions without warming up. An entropy failure throws rather than leaving `sysctl`'s failure unchecked. Calls share the generator lock. - [Integer compiler-rt builtins](../../../core/libs/prx/libc/src/IntegerBuiltins.cpp) (libc) - the console traps in `__divsi3`, `__modsi3` and the 64- and 128-bit divisions on a zero divisor (`div`) and in `__negv*`, `__absv*` and `__mulv*` on overflow (an abort helper, then `ud2`); these throw instead. As in the `libc.prx` bundled with PPSA08427 (disassembly), `__addv*` and `__subv*` wrap without an overflow check, and `__udivsi3`, `__umodsi3` and `__udivmodsi4` divide in software, giving a quotient of 0 and the dividend as remainder for a zero divisor; `__clzdi2` and `__ctzdi2` of 0 return 32 and `__clzti2` and `__ctzti2` of 0 return 64 - [qsort](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) follows FreeBSD 11 [qsort.c](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/lib/libc/stdlib/qsort.c): Bentley and McIlroy's quicksort, with insertion sort below 7 elements and after a partition pass that swaps nothing, so elements that compare equal end in FreeBSD's order and the comparator is called in the same sequence. That the console's libc uses this FreeBSD code is unverified - [memcpy_s, memmove_s, strcpy_s, strncpy_s, strcat_s, strncat_s, wmemcpy_s, wmemmove_s, wcsncpy_s, wcscat_s, wcsncat_s](../../../core/libs/prx/libc/src/Strings.cpp) (libc) use the C11 Annex K destination size constraints, with RSIZE_MAX taken as (SIZE_MAX >> 1) / sizeof(char16_t) for wide operations and SIZE_MAX >> 1 for byte operations. An oversized destination size returns ERANGE (34) without touching the destination; the console's RSIZE_MAX and exact error value are unverified. Constraint violations return an error without invoking the registered constraint handler, as before. -- [snwprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` -- [swprintf_s and vswprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follow C11 K.3.9.1.4 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`: the complete output, including the terminating null, must fit in the buffer, otherwise it is a runtime-constraint violation that sets `buffer[0]` to zero and returns a negative value, and the buffer is not partially written. Argument and encoding checks match `snwprintf_s` +- [wcsncat_s and wcscat_s](../../../core/libs/prx/libc/src/Strings.cpp) (libc) refuse a copy as overlapping only when the written range `[dest + len1, dest + len1 + len2)` meets the read range `[src, src + len2)`. The rule comes from a static reading of the console's `wcsncat_s` in `libc.prx` (not run on a console); `wcscat_s` gets it through `wcsncat_s`, and its own console code was not read. The narrow `strncat_s` keeps a wider rule, `[dest, dest + used + length + 1)` against `[src, src + length + 1)`, whose console code was not read either, so it refuses some copies whose wide equivalent proceeds. +- [snwprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: the handler installed with `set_constraint_handler_s` is not invoked, and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` +- [swprintf_s and vswprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follow C11 K.3.9.1.4 with `RSIZE_MAX` assumed to be `(SIZE_MAX >> 1) / sizeof(char16_t)`: the complete output, including the terminating null, must fit in the buffer, otherwise it is a runtime-constraint violation that sets `buffer[0]` to zero and returns a negative value, and the buffer is not partially written. Argument and encoding checks match `snwprintf_s` +- [strerror](../../../core/libs/prx/libc/src/Errors.cpp) (libc) - follows the `libc.prx` that CUSA04865 and CUSA12435 ship, loaded on a PS4 Pro (firmware 10.71): 0 gives "No error", 1 to 94 the FreeBSD texts, any other number "error_code=0x%08x", and errno is left alone. The console's system libc, which homebrew without a bundled `libc.prx` uses, answers differently ("No error information" for 0 and unknown numbers, 95 and 96 named). `strerror_r` and `perror` keep "No error: 0" and "Unknown error: %d"; they are not measured. - [strerror_s and strerrorlen_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc) follow [C11 K.3.7.4.2 and K.3.7.4.3](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf), using the existing FreeBSD error messages and unknown-error formatting. Truncation writes an ellipsis when capacity exceeds 3; constraint failures leave the destination untouched and invoke the registered guest-ABI handler. The console's `RSIZE_MAX` (`SIZE_MAX >> 1` here), nonzero returns (`EINVAL` 22 for constraints, `ERANGE` 34 for truncation), handler arguments and default abort behavior are unverified. Messages remain in the C locale, and guest errno is preserved. -- [getw and putw](../../../core/libs/prx/libc/src/BinaryWordIo.cpp) (libc) follow FreeBSD 11.4 [getw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/getw.c) and [putw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/putw.c): one 32-bit little-endian word, `getw` returns -1 for EOF or an error, and `putw` returns 0 on success or -1 on error while holding the stream lock. They use the existing host-backed streams, so buffering and error timing follow the host CRT. Null streams throw and closed streams return -1 with `EBADF`, as existing byte I/O does. Console behavior is unverified. +- [getw and putw](../../../core/libs/prx/libc/src/BinaryWordIo.cpp) (libc) follow FreeBSD 11.4 [getw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/getw.c) and [putw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/putw.c): one 32-bit little-endian word, `getw` returns -1 for EOF or an error, and `putw` returns 0 on success or -1 on error while holding the stream lock. They use the existing host-backed streams, so buffering and error timing follow the host CRT. Null streams throw and closed streams return -1 with `EBADF`, as existing byte I/O does. Console behavior is unverified. A payload process on a PS5 (firmware 13.60) gave the same results: two words written little-endian, -1 with end of file after them and for a 2-byte file, and -1 with the error flag for `putw` on a read-only stream - [vsnwprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - `va_list` counterpart of `snwprintf_s` (C11 K.3.9.1.3): truncates to the buffer and returns the full length, with the same constraints and argument checks -- [strndup and strdup](../../../core/libs/prx/libc/src/AllocatingStrings.cpp) (libc) return null with `errno` set to `EINVAL` (22) when `source` is null, preventing a host segfault on invalid guest input. POSIX.1-2008 and FreeBSD 11.4 libc leave `strndup(NULL)` and `strdup(NULL)` undefined (faulting); exact console behavior on hardware is unverified. +- [strndup and strdup](../../../core/libs/prx/libc/src/AllocatingStrings.cpp) (libc) return null with `errno` set to `EINVAL` (22) when `source` is null, preventing a host segfault on invalid guest input. POSIX.1-2008 and FreeBSD 11.4 libc leave `strndup(NULL)` and `strdup(NULL)` undefined (faulting). The `libc.prx` that titles ship has no null check either (read from disassembly of PPSA13934's copy, not measured on a console): `strdup` (0x6b440) calls `strlen` and `strndup` (0x6b650) calls `strnlen` on the source before allocating, so both fault on a null source there, except `strndup(NULL, 0)`, whose `strnlen` reads nothing and which returns an empty copy; here it returns null with `EINVAL`. - [snprintf_s and vsnprintf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - follow C11 K.3.5.3.5 and K.3.5.3.12 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`, as `snwprintf_s` does: a null buffer or format, `size` of 0 or above `RSIZE_MAX`, any `%n` and a null `%s`, `%ls` or `%S` argument return a negative value and set `buffer[0]` to zero when the buffer and size allow it; otherwise the output is truncated to the buffer and the full length is returned. A violation only returns: the constraint handler is not invoked, and the console's error value is unverified. Not confirmed on a PS5 title - [vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - returns the count of items converted before an undersized `%s`/`%c`/`%[` destination (0 when it is the first), not EOF. Not confirmed on a PS5 title +- [setlocale](../../../core/libs/prx/libc/src/StdioExtras.cpp) (libc) knows only the "C" locale, as the `libc.prx` that titles ship does: it ignores the category, returns "C" for a null, empty or "C" name and a null pointer for any other, "POSIX" included, without setting `errno`. Checked by running the copies bundled with PPSA07923 and PPSA13934 side by side for categories -40 to 79 and 2^30, not measured on a console. - [localeconv](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, re-exported by libSceAmpr) - the 0x90-byte Dinkumware struct (empty monetary strings, `CHAR_MAX` for the 14 `char` fields, `decimal_point` ".", `_False`/`_True` "false"/"true") is read from the `libc.prx` that Astro's Playroom ships. It is a fixed C-locale table: whether the console changes it with `setlocale` is not known, and `_Frac_grouping`, `_Frac_sep`, `_No`, `_Yes` are empty strings as in that dump - [set_constraint_handler_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc, re-exported by libSceAmpr) - the default handler is assumed to abort; each module keeps its own handler state, so a handler set through one library is not seen by the other. Within a module every `_s` function calls the installed handler, with the guest's calling convention. `ctime_s` and `asctime_s` follow the Dinkumware 26-byte format with `EINVAL` for a buffer under 26 bytes, a null pointer or an out-of-range weekday/month; `ctime_s` uses the guest time zone of `localtime_s`, and the console's exact checks are not verified - [qsort_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) sorts as `qsort` does and passes its context as the comparator's third argument, as FreeBSD 13's [`qsort_s`](https://github.com/freebsd/freebsd-src/blob/release/13.0.0/lib/libc/stdlib/qsort.c#L97-L127) (C11 Annex K) does. It returns `EINVAL` after calling the constraint handler when `n` or `es` exceeds `RSIZE_MAX`, or when `n` is not 0 and `a` or `cmp` is null; FreeBSD 11, which the rest of libc follows, has no `qsort_s`. An element size of 0 sorts nothing, where FreeBSD loops over the same address. The console's behaviour is unverified; the Quake homebrew (#6698) imports it from its `libOSMesa.so.8` @@ -52,40 +68,64 @@ - [__umodti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - the hardware traps on a zero divisor; this throws instead - [__modti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - the hardware traps on a zero divisor; this throws instead +- [snprintf and vsnprintf](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) accept any size up to `SIZE_MAX`, as a payload process on a PS5 (firmware 13.60) does. A null buffer with a nonzero size throws here, where the console writes through it, and a result longer than `INT_MAX` throws, which was not measured + ### Functional -- [_FSinh and _FCosh](../../../core/libs/prx/libc/src/Math.cpp) (libc) finite results are not verified bit for bit against the console: the scaled product uses the host's binary64 hyperbolic function before rounding to binary32, rather than the float rational and `_FExp` sequence in PRAGMATA's bundled `libc.prx`. NaN preference and payloads, signed zeros, infinite operands and the range-error thresholds follow that binary's instructions; the system `libSceLibcInternal` implementation and nondefault flush-to-zero modes are unverified. +- [_Cosh](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the two-argument Dinkumware CRT helper used by `std::complex`, including a scaled large-finite-input path; the signature, exact result and floating-point exception behavior are not verified against the console. +- [std::thread::hardware_concurrency](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `CHrhwd8QSBs`) returns the host's `std::thread::hardware_concurrency()` (which may be 0 when unknown) instead of the console's processor count; no reference implements it and the value a title expects (PPSA34015 imports it) is unverified. +- [fmodf](../../../core/libs/prx/libc/src/Math.cpp) (libc) - console `errno`, exception flags and NaN payloads remain unverified. The wrapper preserves `errno` like the existing double `fmod`, following FreeBSD 11.4 [`e_fmodf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_fmodf.c); host `std::fmod` still supplies the result and exception flags. +- [_FSinh and _FCosh](../../../core/libs/prx/libc/src/Math.cpp) (libc) finite results are not verified bit for bit against the console: the scaled product uses the host's binary64 hyperbolic function before rounding to binary32, rather than the float rational and `_FExp` sequence in PRAGMATA's bundled `libc.prx`. NaN preference and payloads, signed zeros, infinite operands and the range-error thresholds follow that binary's instructions; the system `libSceLibcInternal` implementation and nondefault flush-to-zero modes are unverified. Against `libSceLibcInternal.sprx` on a PS5 (firmware 13.60), 12 of 36 measured results differ by one unit in the last place (`_FSinh(0.5, y)` and `_FCosh(1, y)`, `_FCosh(10, y)` for y of 0.5, 1 and 2); zeros, the overflow to infinity at 89 and 90 and the tiny-argument results matched +- [_Divide, _FDivide, _Recip and _FRecip](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libc) follow the Dinkumware CRT division and reciprocal contracts with IEEE-754 signed-zero, infinity and NaN behavior. The exact console results, signaling-NaN handling and floating-point exception behavior are not verified. - [sscanf_s and vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) return `EOF` when a reached conversion has a null destination, stopping before further assignments, following C11 K.3.5.3.7 and K.3.5.3.14. Earlier assignments are retained. The constraint handler is not invoked, and console behaviour is unverified. - [memmem and memccpy](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `memmem` searches for needle occurrences in memory buffers and `memccpy` copies buffers until a character match, following FreeBSD and POSIX specifications; behavior is unverified on console. +- [strtof and wcstof](../../../core/libs/prx/libc/src/Strings.cpp) (libc) round to the nearest float, where the console's `_Stof` in `libSceLibcInternal.sprx` (0x734a0) does not always: about a third of decimal inputs within a relative 2^-51 of a point halfway between two floats, and some hexadecimal inputs with more than 24 significant bits, come out one unit apart in the last place (`1e-40` is `0x1.16c3p-133` there and `0x1.16c2p-133` here). The console's own rounding is not reproduced +- [strtof and wcstof](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `ERANGE` follows the `libc.prx` bundled with PPSA08427 (J03547016) and PPSA03671 (J03848509), both run side by side with this libc on a Linux host: a decimal input whose result is a nonzero subnormal sets it only when inexact, a hexadecimal one only when it lies below the smallest subnormal and rounds up to it, and underflow to zero and overflow set it. Both builds also take a `NAN(...)` payload in `strtof` from the sequence's leading digits (`nan(0x123)` is `0x7fc00123`, `-nan(5)` is `0xffc00005`); the sign is kept here, the payload is not. `libSceLibcInternal`'s `_Stof` differs: no `ERANGE` for hexadecimal input, a positive `nan`, and after a minus sign a failed parse is `-0.0` for a lone sign and `+0.0` before an `inf`/`nan` spelling, the reverse of `libc.prx`. Decimal `strtod` sets `ERANGE` for a nonzero subnormal in J03547016 but not in J03848509 or `libSceLibcInternal`, whose result is kept. -- [Guest time conversions](../../../core/libs/prx/libc/src/Time.cpp) rewrite the host process `TZ` when a guest changes its own `TZ`, so other host consumers of that variable (including libSceRtc local time) shift too; code outside libc does not take the zone lock. Zone syntax follows the host C runtime, not FreeBSD: Windows only accepts the `std offset [dst]` form, treats an empty guest `TZ` as `UTC0` and reads `:Europe/Berlin` as an abbreviation (for example `rop`); only Linux honours zone file names. If the guest accepts a `TZ` value that the host rejects (on Windows, for example, one longer than the host environment limit), conversions throw `std::runtime_error`. A `tm_zone` saved before a `TZ` change can refer to storage now holding the new zone name, changing `%Z` output. -- [__isfinitel](../../../core/libs/prx/libc/src/Math.cpp) - classifies the x87 80-bit exponent without narrowing to double or performing floating-point comparisons, following [FreeBSD msun](https://github.com/freebsd/freebsd-src/blob/main/lib/msun/src/s_isfinite.c). The signature and classification follow FreeBSD; console behaviour is unmeasured. Requires little-endian x87 long double stored in 16 bytes, as the existing guest long-double ABI does. +- [Guest time conversions](../../../core/libs/prx/libc/src/Time.cpp) rewrite the host process `TZ` when a guest changes its own `TZ`, so other host consumers of that variable (including libSceRtc local time) shift too; code outside libc does not take the zone lock. Zone syntax follows the host C runtime, not FreeBSD: Windows only accepts the `std offset [dst]` form, treats an empty guest `TZ` as `UTC0` and reads `:Europe/Berlin` as an abbreviation (for example `rop`); only Linux honours zone file names. If the guest accepts a `TZ` value that the host rejects (on Windows, for example, one longer than the host environment limit), conversions throw `std::runtime_error`. A `tm_zone` saved before a `TZ` change can refer to storage now holding the new zone name; `strftime` does not read it. +- [cbrtf](../../../core/libs/prx/libc/src/Math.cpp) (libc) is FreeBSD msun's [`s_cbrtf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_cbrtf.c): a 5-bit first guess from the exponent bits, then two Newton steps in double precision. It sets no `errno`, as the `libc.prx` that titles ship does not. Floating-point exceptions are not compared. It gives the same bits as the `cbrtf` of the `libc.prx` bundled with PPSA07923, PPSA05916 and PPSA13934 for all 2^32 arguments, run on a Windows host. +- [__isfinitel](../../../core/libs/prx/libc/src/Math.cpp) - classifies the x87 80-bit exponent without narrowing to double or performing floating-point comparisons, following [FreeBSD msun](https://github.com/freebsd/freebsd-src/blob/main/lib/msun/src/s_isfinite.c). The signature and classification follow FreeBSD; console behaviour is unmeasured. Requires little-endian x87 long double stored in 16 bytes, as the existing guest long-double ABI does. On a PS5 (firmware 13.60) `__isfinitel` returned 1 for 0, -0, 1, a denormal and ±1e4000, and 0 for a NaN and for the maximum exponent without the integer bit, which this classification gives too +- [logf](../../../core/libs/prx/libc/src/Math.cpp) (libc) is FreeBSD msun's [`e_logf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_logf.c), with `vzero` read at run time as msun's `volatile` read does. As in the `libc.prx` that titles ship, `errno` is set to `ERANGE` for ±0 and to `EDOM` for every argument with the sign bit set, negative NaNs and -inf included; positive NaNs and +inf leave it unchanged. Floating-point exceptions are not compared. It gives the same bits and `errno` as the `logf` of the `libc.prx` bundled with PPSA07923 and PPSA05916 for all 2^32 arguments, run on a Windows host. The FMA-contracted build bundled with PPSA13934 differs from it by 1 ulp on 961,881 arguments, so no single port matches every title's last bit. - Windows direct-memory backing reserves a paging-file section. A [direct memory page](../../../core/libs/prx/libc/include/WindowsMappings.hpp) mapped at one guest address is private write-watched memory at that address and is not committed in the section; a page mapped at a second address moves its whole resident run into the section (committed there) and every address becomes a view of it, tracked by write protection. An unmapped resident page that is not all zeros is kept in a private reservation until it is mapped again. Committed section contents remain across unmap/remap and are released when the backing and its remaining views are destroyed. A host import alias over part of a resident run is refused. Linux retains its existing backing. Peak gameplay memory and Windows versions other than the tested host remain unverified. -- Stable Windows resident runs of at least 256 KiB use temporary read-only protection for clean write-watch proofs. The first CPU write restores the logical protection of the whole run; that run remains on native write watching until it is remapped, so repeatedly rewritten runs stay writable. Native kernel writes into guest buffers must hold [GuestArena::HostWrite](../../../core/libs/prx/libc/include/GuestArena.hpp), as shared views already require: an unbracketed WinAPI ReadFile can fail with ERROR_NOACCESS while the resident proof is armed. Guest read, pread and socket receive paths already bracket their destinations. GPU host-import access suppresses resident rearming without inventing shared-page stores. This is a host integration requirement, not a change to guest logical protection. - [getc_unlocked, putc_unlocked, getchar_unlocked, putchar_unlocked](../../../core/libs/prx/libc/src/StandardStreams.cpp) (libc) - byte I/O uses the host unlocked operations; guest EOF/error flags and macro counters follow `FileStream`. Linux status queries use `feof_unlocked`/`ferror_unlocked` because [glibc `feof`](https://github.com/bminor/glibc/blob/d2097651cc57834dbfcaa102ddfacae0d86cfb66/libio/feof.c) and [`ferror`](https://github.com/bminor/glibc/blob/d2097651cc57834dbfcaa102ddfacae0d86cfb66/libio/ferror.c) can acquire the stream lock. Windows uses [the CRT no-lock byte operations](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fgetc-nolock-fgetwc-nolock) and `feof`/`ferror`, verified under a held stream lock on UCRT. Console behavior and other host CRTs remain unverified. +- [strcasecmp and strncasecmp](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - console locale behavior is unmeasured. They use the guest's C-locale case conversion and return -1, 0 or 1, the sign of the folded unsigned-byte difference, as the `libc.prx` bundled with eleven titles does (FreeBSD 11.4's [strcasecmp.c](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/string/strcasecmp.c) returns the difference itself); other guest locales are not implemented. - [feof_unlocked and ferror_unlocked](../../../core/libs/prx/libc/src/StandardStreams.cpp) (libSceLibcInternal, implemented in libc) have no console measurement. The firmware 3.20 [export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.c) names NIDs `NuydofHcR1w` and `yxbGzBQC5xA`. They follow FreeBSD 11 [feof.c](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/lib/libc/stdio/feof.c) and [ferror.c](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/lib/libc/stdio/ferror.c), returning 0 or 1 from the stream status without acquiring the stream lock. They inherit the host CRT limitations of the unlocked byte operations above. -- [fgetws, getwc, getwchar, putwc, putwchar](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - wide stream I/O encodes and decodes wide characters as UTF-8 on the underlying byte stream; `fgetws` reads characters into the destination buffer until newline or EOF and null-terminates, returning null on stream error. Console wide stream buffering and encoding details remain unverified. -- [Closed standard descriptors](../../../core/libs/prx/libc/src/StandardStreams.cpp) (libc) - on Linux and macOS, a descriptor 0, 1 or 2 that is closed when libc loads is opened on `/dev/null` (read-only for 0, write-only for 1 and 2) by a static initializer of libc, which runs before the libraries that depend on libc initialize and before the title starts. Within libc this relies on no static initializer opening a descriptor before it, which `guest_closed_standard_descriptors` checks. glibc does the same for set-user-ID programs ([`check_fds.c`](https://sourceware.org/git/?p=glibc.git;a=blob;f=csu/check_fds.c)). Without it the first descriptor a library opens, the write watch's `userfaultfd` for one, becomes the guest's standard input or output. What the console gives a title as its standard descriptors is not measured, and on Windows a process started without standard handles is not handled. +- [gets](../../../core/libs/prx/libc/src/StandardStreams.cpp) (libSceLibcInternal, implemented in libc, NID `Ok+SYcoL19Y`) follows [FreeBSD 11.4 gets.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libc/stdio/gets.c) for byte input, newline removal and EOF, without PS5 verification or known module/library versions. It reads the guest stdin under its stream lock and preserves the destination when EOF or a read error precedes any byte; a partial line is terminated and returned. A null destination throws; its console behavior is unverified. It emits no diagnostic; FreeBSD writes a warning once, and the console's diagnostic behavior and stream orientation are unverified. +- [fgetws, getwc, getwchar, putwc, putwchar](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - wide stream I/O encodes and decodes wide characters as UTF-8 on the underlying byte stream; `fgetws` follows the `libc.prx` that titles ship (read from disassembly of three titles' copies, PPSA13934, PPSA07923 and PPSA05916, not measured on a console): it returns null for a size below 2 without writing the buffer, `errno` or the stream, stores each `fgetwc` result as its low 16 bits until a newline (compared on all 32 bits), `-1` or a full buffer, and null-terminates and returns the buffer whenever it stored a character, also after a read error. A null buffer or stream, which the console dereferences, sets `errno` to 22 here. The calls the console makes before and after reading, and the one it makes when bits 0xC of the stream's second flag byte are set, are not reproduced. Console wide stream buffering and `fgetwc`'s decoding remain unverified. +- [Closed standard descriptors](../../../core/libs/prx/libc/src/StandardStreams.cpp) (libc) - console startup descriptors remain unmeasured. [Halo: Combat Evolved's PS5 host](https://github.com/thelinkin3000/halo-ce-universal/blob/201e0f18011a30ea6d098ad249bf17eba005d653/port/ps5/host/host_main.c#L407-L429) expects 0 to 2 closed and opens its eboot on them; here it leaves them open. Missing descriptors are opened on `/dev/null` on Linux/macOS or `NUL` on Windows, read-only for 0 and write-only for 1 and 2. Windows also reattaches the UCRT streams before any guest `FileStream` captures their descriptor. Initialization runs before libraries depending on libc; no earlier libc initializer may consume a missing standard descriptor. The `guest_closed_standard_descriptors` and `guest_closed_standard_descriptors_windows` tests cover startup. - [Guest path names](../../../core/libs/prx/libc/src/General.cpp) (libc) are checked against FreeBSD 11.4's `NAME_MAX` (255 bytes per component) and `MAXPATHLEN` (1024 bytes with the terminating NUL) before they are resolved, and a longer name fails with `ENAMETOOLONG`, as `namei` does. The host's own limit still applies below that: on Windows a path whose host form exceeds `MAX_PATH` fails with `ENOENT`. A backslash counts as a path separator for the component length, because the guest path resolver turns it into `/` before the host call, while FreeBSD treats it as an ordinary name character, so a 301-byte name that contains one is accepted here. `sceNgs2ParseWaveformFile` resolves its path without a try block, so a long component ends in an uncaught `filesystem_error` there where a missing file ended in a `runtime_error` (both end the title). A path through a regular file (`file/x`, `file/`) is not always `ENOTDIR` on the calls that resolve it. None of this was measured on a console. +- [Guest path names](../../../core/libs/prx/libc/src/General.cpp) (libc) fail on macOS with `ENAMETOOLONG` once their host form, the root directory followed by the guest path, reaches 1024 bytes, so a guest path of 1023 bytes, which FreeBSD accepts, fails there under any root directory - [ftrylockfile](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - tries the host CRT stream lock that `flockfile`, `funlockfile` and each locking host stdio call on the stream take, so it returns -1 while another thread holds the stream through `flockfile` or is inside such a call, and 0 when it acquires the lock or already owns it (recursively, released by as many `funlockfile` calls), as FreeBSD 11 [`_ftrylockfile`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/_flock_stub.c#L89-L104) does. A libc export holds the lock only for each host call it makes, not across several host calls or while it updates the guest `FILE` prefix. Linux uses glibc `ftrylockfile`. UCRT has no try variant of `_lock_file`, so Windows calls `TryEnterCriticalSection` on the `CRITICAL_SECTION` that `_lock_file` enters, found through the private UCRT stream layout (`__crt_stdio_stream_data` in `ucrt/inc/corecrt_internal_stdio.h`, unchanged from Windows SDK 10.0.19041 to 10.0.26100); another CRT or a change of that layout breaks it. A null or closed stream throws where FreeBSD dereferences it +- [sinf, cosf, tanf and sincosf](../../../core/libs/prx/libc/src/Math.cpp) (libc) are FreeBSD msun's [`s_sinf.c`, `s_cosf.c` and `s_tanf.c`](https://github.com/freebsd/freebsd-src/tree/release/11.4.0/lib/msun/src) over its double-precision kernels and `e_rem_pio2f.c`, with `k_rem_pio2.c` (for `|x| >= 2^28 * pi/2`) ported only for one input word and `prec` 0; `sincosf` returns the two. As in the `libc.prx` that titles ship, only an infinite argument sets `errno` (`EDOM`); NaN leaves it unchanged. Floating-point exceptions are not compared. All four give the same bits and `errno` as the copies bundled with PPSA07923, PPSA05916 and PPSA13934 for all 2^32 arguments, run on a Windows host. - [fgetln](../../../core/libs/prx/libc/src/StandardStreams.cpp) (libSceLibcInternal, implemented in libc) throws: in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x5FE80) the export is a `hlt` instruction, which faults in user mode and ends the process -- [Wide-character conversions](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. [`wcsrtombs`](../../../core/libs/prx/libc/src/Multibyte.cpp) uses the guest's single-byte C locale through `wcrtomb`; other encodings and shift states are not supported +- [Wide-character conversions](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. The multibyte conversions follow the C locale of the console's `libSceLibcInternal.sprx`: UTF-8 of at most three bytes, so `_Getmbcurmax` returns 3, overlong forms and encoded surrogates decode, and four-byte forms are invalid. `__mb_cur_max` stays 1 like the console's variable; `___mb_cur_max` is not in that module and also stays 1, unverified. Other locales are not supported. With a null destination, `wcstombs` returns the byte count; the console's count is wrong when the capacity argument equals the running count, because it reads past the terminator - [realpath](../../../core/libs/prx/libc/src/RealPath.cpp) (libc) follows FreeBSD 11's [`realpath.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/stdlib/realpath.c) except for symlinks. The console's guest filesystem has none, so a host symlink under the guest root is followed for existence and type but not expanded, and `..` drops the previous component lexically, as `Resolve` does for every guest path. Through a host link `linkdir -> sub/deep`, `linkdir/../x` gives `x` beside the link, not beside its target. - [strtod, strtof, strtold](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `strtod` and `wcstod` (and `atof`, which calls `strtod` here) read a `NAN(...)` sequence as FreeBSD 11's gdtoa [`hexnan`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/contrib/gdtoa/hexnan.c) does: hexadecimal digits with an optional `0x`, blanks starting a new 32-bit word, the first 16 digits kept and the payload cut to 52 bits, a zero payload made 1, any other character giving the default NaN but consuming up to the closing parenthesis, and an unterminated sequence keeping its payload while consuming only `nan`. The payload goes under the quiet bit with the sign, as in gdtoa's [`strtod`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/contrib/gdtoa/strtod.c#L316-L328). `strtof`, `wcstof`, `strtold` and `wcstold` return the positive default quiet NaN (`0x7fc00000`; exponent `0x7fff`, significand `0xc000000000000000`) whatever the sign and the sequence, and consume a sequence of letters, digits and underscores up to its closing parenthesis. The console's `libSceLibcInternal.sprx`, called from a payload process on a PS5 (firmware 12.70), gives these results for the 64 NaN inputs tried through `strtod` and `strtof`, 50 of them also through `strtold` and `wcstod`, and 18 through `wcstof` and `wcstold`, and, through `strtod`, for `nan(1 2)` and for `nan( ` followed by NUL and `)`; that a title's own `libc.prx` does the same is assumed. In `hexnan`, a blank after a payload digit, while a 32-bit word of the payload is still free, starts a blank skip that has no NUL check, so when only blanks follow up to the terminating NUL the console's `strtod` reads past it: `nan(1 ` followed by NUL and `)` consumes the `)` after the NUL, so the result depends on the bytes after the string, and here that case throws. A blank right after `nan(` is skipped with a NUL check, and the console stopped at the NUL there - [remquol](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) leaves `*quo` unchanged when the result is NaN; the `libc.prx` bundled with PPSA08427 writes a value that depends on earlier x87 state (`remquol(1, 0)` stored 0 on a first call and 4 on later ones) +- [_Stod, _Stodx, _Stofx, _Stoldx, _Stof and _Stold](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libSceLibcInternal, implemented in libc) throw for hexadecimal text with a non-zero power-of-ten scale. The console's result there does not follow a simple rule: `0x3` with a scale of -1 gives 0.29999999999999999. A nonzero subnormal result sets neither `errno` nor the error argument, as measured in firmware 13.60's `libSceLibcInternal.sprx` run in a Linux host process, not on a console; there about a fifth of decimal inputs with a subnormal result come out one unit apart in the last place (`_Stodx("2.2250738585072014e-308", 0)` is the largest subnormal), which is not reproduced +- [_LDivide, _LRecip and _LLog](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libc) follow the public Dinkumware CRT contracts: long-double division and reciprocal, and the `_LLog` base flag (`0` natural, positive base 10, negative base 2). The exact console results, signaling-NaN behavior and floating-point exception behavior are not verified. - [Long double math in MathExact.cpp](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) was compared with the `libc.prx` bundled with PPSA08427 only for canonical 80-bit encodings; unnormals, pseudo-denormals and pseudo-infinities passed to arithmetic functions (not the classifiers) follow the host x87 instead of the console -- [rename, sceKernelRename](../../../core/libs/prx/libc/include/RenameDot.hpp) (libc, libkernel) fail a last path component of `.` or `..` with `EINVAL`, after FreeBSD 11.4's [`lookup`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/vfs_lookup.c), which refuses `.` and, through its `ISDOTDOT` and `ISLASTCN` flags, `..` as the last component for the `DELETE` and `RENAME` operations. `rmdir("dir/.")`, `unlink("dir/.")` and the same calls with `..` follow the same lookup rule in FreeBSD but are not changed here: the guest still resolves the dot component away and acts on `dir` or its parent. Both arguments are checked before any host access, so `rename` of a missing source onto `dir/.` answers `EINVAL`, where FreeBSD's [`kern_renameat`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/vfs_syscalls.c) looks the source up first and answers `ENOENT`. A backslash counts as a path separator here (the guest path resolver turns it into `/`); FreeBSD treats it as an ordinary name character. None of this was measured on a console. -- [nan and nanf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return fixed quiet NaN values (`0x7fc00000` and `0x7ff8000000000000`), ignoring the tag argument as observed in the console's `libc.prx` (both exports load a global constant via `mov rax,[rip+..]; vmovsd xmm0,[rax]; ret`); VLC (PPSA85300) stops on nanf at start-up after strsep. +- [atanf](../../../core/libs/prx/libc/src/Math.cpp) (libc) is FreeBSD msun's [`s_atanf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_atanf.c), with `atanlo[3]` read at run time as msun's `volatile` does. Tiny arguments return `x` without raising `FE_INEXACT`. It gives the same bits as the `atanf` of the `libc.prx` bundled with PPSA07923, PPSA05916 and PPSA13934 for all 2^32 arguments, run on a Windows host, and none of those sets `errno`. Whether other titles' builds differ is unverified. +- [rename, sceKernelRename](../../../core/libs/prx/libc/include/RenameDot.hpp) (libc, libkernel) fail a last path component of `.` or `..` with `EINVAL`, after FreeBSD 11.4's [`lookup`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/vfs_lookup.c), which refuses `.` and, through its `ISDOTDOT` and `ISLASTCN` flags, `..` as the last component for the `DELETE` and `RENAME` operations. `unlink`, `rmdir`, `sceKernelUnlink` and `sceKernelRmdir` follow the same rule. Both arguments are checked before any host access, so `rename` of a missing source onto `dir/.` answers `EINVAL`, where FreeBSD's [`kern_renameat`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/vfs_syscalls.c) looks the source up first and answers `ENOENT`. A backslash counts as a path separator here (the guest path resolver turns it into `/`); FreeBSD treats it as an ordinary name character. None of this was measured on a console. +- [nan and nanf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return fixed quiet NaN values (`0x7fc00000` and `0x7ff8000000000000`), ignoring the tag argument as observed in the console's `libc.prx` (both exports load a global constant via `mov rax,[rip+..]; vmovsd xmm0,[rax]; ret`); VLC (PPSA85300) imports nanf. - [jrand48 and mrand48](../../../core/libs/prx/libc/src/Random.cpp) (libSceLibcInternal, implemented in libc) return the high 32 bits of the new 48-bit state zero-extended to 64 bits, as the console's `libSceLibcInternal.sprx` does when called from a homebrew payload process on a PS5 (firmware 12.70): a result with bit 31 set comes back between 0x80000000 and 0xFFFFFFFF, as FreeBSD 11.0's [`jrand48`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/lib/libc/gen/jrand48.c#L23) and [`mrand48`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/lib/libc/gen/mrand48.c#L25) return it; FreeBSD 11.1 and later cast it to `int32_t` ([`jrand48`](https://github.com/freebsd/freebsd-src/blob/release/11.1.0/lib/libc/gen/jrand48.c#L26), [`mrand48`](https://github.com/freebsd/freebsd-src/blob/release/11.1.0/lib/libc/gen/mrand48.c#L28)), which makes it negative. `lrand48` and `nrand48` matched FreeBSD there. Whether a title's own libc does the same is unverified - [seed48 and lcong48](../../../core/libs/prx/libc/src/Random.cpp) (libSceLibcInternal, implemented in libc) follow the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x6F460 and 0x6F420): the multiplier and addend are shared by the whole `rand48` family, including `erand48`, `nrand48` and `jrand48` on a caller's seed; `lcong48` sets them with the seed, and `srand48` and `seed48` restore 0x5DEECE66D and 0xB. Unlike FreeBSD, `seed48` returns a static copy of the new seed rather than the previous one. A null argument, which the console dereferences, throws +- [drand48 and erand48](../../../core/libs/prx/libc/src/Random.cpp) (libc) return the upper 32 bits of the new 48-bit state over 2^32, as the `libc.prx` bundled with eleven titles does on 10,000 values per function; FreeBSD returns all 48 bits. Whether the console's `libSceLibcInternal.sprx` copy does the same is unmeasured. - [lgamma and signgam](../../../core/libs/prx/libc/src/Math.cpp) (libc) - `lgamma` returns the host's `lgamma` and stores the sign of Γ(x) in the exported `signgam` (NID `5gOOC0kzW0c`, starting at 0 as in FreeBSD 11's [`s_signgam.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/s_signgam.c)) by the rules of [`e_lgamma_r.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/e_lgamma_r.c): -1 for -0 and for a negative non-integer whose floor is odd, 1 otherwise, including poles, infinities and NaN. Taken from the FreeBSD source, not measured on the console. The NID is in the aerolib symbol list; no title is known to read `signgam`. PPSA99004 imports `lgamma` +- [coshf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows FreeBSD msun's [`e_coshf.c`](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_coshf.c), using the `expm1f` and `expf` paths already inlined for `sinhf` below; the Windows UCRT differed from the `libc.prx` copies in PPSA07923 and PPSA13934 on 500,554 and 500,614 of 20 million inputs in the #9707 survey. The exact system `libSceLibcInternal` behavior and the FMA-contracted `libc.prx` results remain unverified +- [expm1](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_expm1.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_expm1.c) with `huge` read at run time, and sets `ERANGE` only when a finite argument overflows to infinity, as the titles' `libc.prx` does. It gives the same bits and `errno` as the `expm1` bundled with PPSA07923 and PPSA05916 on 320 million random and 47,222 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 43,433 of the random ones. +- [log1p](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_log1p.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_log1p.c). As the titles' `libc.prx` does, it sets `EDOM` at msun's NaN return (arguments below -1, `-inf` and negative NaNs) and leaves `errno` alone for `log1p(-1)`, where it returns `-inf`. It gives the same bits, NaN payloads included, and `errno` as the `log1p` bundled with PPSA07923 and PPSA05916 on 320 million random and 68,216 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 73,682 of the random ones. +- [nextafter and nextafterf](../../../core/libs/prx/libc/src/Math.cpp) (libc) do not compare floating-point exceptions, and their inputs are checked by sampling, not exhaustively. They are FreeBSD msun's [`s_nextafter.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_nextafter.c) and [`s_nextafterf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_nextafterf.c), with `ERANGE` at msun's overflow and underflow returns as the titles' `libc.prx` sets it; a step away from zero returns early and sets none. The copies bundled with eleven titles give the same bits and `errno` on 4,000,784 sampled pairs per function on a Windows host. +- [tanh](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_tanh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_tanh.c) over this file's msun `Expm1`, with `tiny` read at run time; it sets no `errno`, as the titles' `libc.prx` does not. It gives the same bits as the `tanh` bundled with PPSA07923 and PPSA05916 on 16 million random and 68,211 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 1,054 of them. - [sinhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `sinhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_sinhf.c`](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_sinhf.c) from before `__ldexp_expf`, which squares `expf(|x| / 2)` between `logf(FLT_MAX)` and the overflow threshold, calling msun's [`expm1f`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_expm1f.c) and [`expf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_expf.c), whose paths for the arguments it passes are inlined here. Past the threshold Sony's error helper sets `errno` to `ERANGE` before returning `x * 1e37`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits as this implementation for every finite argument up to the threshold, of both signs; it was not run on Linux. That the system `libSceLibcInternal`, which Ruffle (PPSA68091) and RetroArch (PPSA99169) import while bundling a zero-return `libc.prx`, has the same code is assumed +- [acoshf](../../../core/libs/prx/libc/src/Math.cpp) (libc) exact system `libSceLibcInternal` behavior and FMA-contracted copies remain unverified. Positive finite inputs use [msun](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_acoshf.c) range handling and this file's `logf`; the middle range divides -1 before addition under directed rounding. Bits, errno and SSE/x87 status and controls match the supplied PPSA03766/PPSA07923 copies on 236,592 positive finite calls (4,929 inputs, four rounding modes, DAZ/FTZ combinations and sticky flags); these samples are not exhaustive. Invalid-domain errno and SSE/x87 invalid flags are checked against two supplied bundled copies; those samples are not exhaustive either. - [acoshl, asinhl, atanhl, atan2l, cbrtl, erfl, erfcl, expm1l, hypotl, log1pl, log2l, lgammal, tgammal, j0f, j1f, jnf, y0f, y1f and ynf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return the host C library's results (the float Bessel functions through its double ones), not those of the console's own code, so the last bits can differ (`y0f`, `y1f`, `ynf` follow the console at `+-0`). As in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x56410), `lgammal` does not store the sign in `signgam`; `hypotl` returns infinity for an infinite argument even when the other is a NaN, as C11 F.10.4.3 requires. [hypot3, hypot3f and hypot3l](../../../core/libs/prx/libc/src/Math.cpp) follow that module (0x51D30, 0x520D0, 0x52420): the first NaN of x, y and z is returned unchanged, any infinity gives positive infinity, a zero argument gives the two-argument hypot of the other two, and otherwise the largest magnitude is combined with the hypot of the other two - [asinl](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `asinl` of the `libc.prx` bundled with PPSA28319 (disassembly), FreeBSD msun's [`e_asinl.c`](https://github.com/freebsd/freebsd-src/blob/9f0432041b8fb69832f10c32da50a67f34feeb61/lib/msun/src/e_asinl.c) with the ld80 [`invtrig.c`](https://github.com/freebsd/freebsd-src/blob/9f0432041b8fb69832f10c32da50a67f34feeb61/lib/msun/ld80/invtrig.c) coefficients, which the binary holds as the same 80-bit constants. For |x| > 1, the infinities and NaN, Sony's error helper raises `FE_INVALID` and sets `errno` to `EDOM` before returning `(x - x) / (x - x)`, so a NaN argument also sets `EDOM`; msun itself sets no `errno`. 20 million random arguments in (-1, 1) give the same bits as that code run on a Windows host. The result needs the x87 precision control at 64 bits, as FreeBSD amd64 starts threads (control word `0x37f`), but guest threads do not set it: on Windows a thread started by `_beginthreadex` runs at 53 bits (`0x27f`), where `asinl(0.75)` returns mantissa `0xd91a98ae3406e000` instead of `0xd91a98ae3406e041`. The system `libSceLibcInternal` that RetroArch (PPSA99169) imports is assumed to have the same code +- [acosf](../../../core/libs/prx/libc/src/Math.cpp) (libc) - for |x| > 1 and NaN the console stores `EDOM` and raises `FE_INVALID` before returning `(x - x) / (x - x)`; here those return the same NaNs and leave `errno` as it was (#3688). For |x| <= 1 it follows the `acosf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_acosf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_acosf.c), with the same constants, `pio2_lo` read at run time and the `0xfffff000` mask on `sqrtf(z)`, which the binary holds. Titles ship different builds of that `libc.prx`: PPSA13934's is FMA-contracted and differs from this one by 1 ulp on 129,170 inputs (#9668), so no single port matches every title's last bit. - [atanhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `atanhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_atanhf.c`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_atanhf.c) calling msun's [`log1pf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_log1pf.c), whose paths for the positive arguments it passes are inlined here; the exported `log1pf` is still the host's. For |x| > 1, the infinities and NaN, Sony's error helper raises `FE_INVALID` and sets `errno` to `EDOM` before returning `(x - x) / (x - x)`, and for |x| = 1 it raises `FE_DIVBYZERO` and sets `errno` to `ERANGE` before returning `x / 0`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits, `errno` and floating-point exceptions as this implementation for all 2^32 arguments; it was not run on Linux. That the system `libSceLibcInternal`, which Dusklight (PPSA99305) imports while bundling a zero-return `libc.prx`, has the same code is assumed -- [j0, j1, jn, y0, y1 and yn](../../../core/libs/prx/libc/src/Bessel.cpp) (libc) call the host's `sin`, `cos`, `sincos` and `log`, so the last bits can differ from the console. The rest follows the system `libc.prx` built as `J03093718` (disassembly), which given the console's own `sin`, `cos`, `sincos` and `log` gave the same bits and exception flags for 90 million arguments: msun's [`e_j0.c`, `e_j1.c` and `e_jn.c`](https://github.com/freebsd/freebsd-src/tree/release/10.0.0/lib/msun/src) with each multiply-add the console fuses done as one FMA. As in that release, before FreeBSD commit `186f620727`, `y0`, `y1` and `yn` return `-inf` for ±0 and `0x7ff8000000000000` for a negative argument without raising an exception. The console's `y0f`, `y1f` and `ynf` raise none either, unlike [ours](../../../core/libs/prx/libc/src/Math.cpp), which raise `FE_DIVBYZERO` at ±0. The order wraps at 32 bits as on the console: `jn(1 << 30, x)` never returns for 2^-29 <= |x| < 2^30 +- [j0, j1, jn, y0, y1 and yn](../../../core/libs/prx/libc/src/Bessel.cpp) (libc) call the host's `sin`, `cos`, `sincos` and `log`, so the last bits can differ from the console. The rest follows the system `libc.prx` built as `J03093718` (disassembly), which given the console's own `sin`, `cos`, `sincos` and `log` gave the same bits and exception flags for 90 million arguments: msun's [`e_j0.c`, `e_j1.c` and `e_jn.c`](https://github.com/freebsd/freebsd-src/tree/release/10.0.0/lib/msun/src) with each multiply-add the console fuses done as one FMA. As in that release, before FreeBSD commit `186f620727`, `y0`, `y1` and `yn` return `-inf` for ±0 and `0x7ff8000000000000` for a negative argument without raising an exception. The order wraps at 32 bits as on the console: `jn(1 << 30, x)` never returns for 2^-29 <= |x| < 2^30 - [getenv, setenv, unsetenv, putenv](../../../core/libs/prx/libc/src/Environment.cpp) (libc) keep one guest environment that starts empty, like the `environ` libkernel exports, and never reads or changes the host's. A title sees only the variables it sets itself; host variables such as `HOME` and `PATH` name host paths a guest cannot use (PS5MC, PPSA99420, exits with `XBMC_Run returned -1` when it gets the host's `HOME`). Whether the console starts a title with any variables set is not measured - [mergesort](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libSceLibcInternal, implemented in libc) checks its arguments like the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x762D0): an element size under 4 sets `errno` to `EINVAL` (22) and returns -1, an empty array returns 0, and a failed `malloc` of the count times the size plus 8 bytes returns -1. The sort is a stable bottom-up merge, not FreeBSD's natural merge with its insertion sort for short runs, so the comparator is called in a different order and number of times; the result is the same for a consistent comparator. A null base or comparator and a size product that overflows, which the console does not check, throw - On macOS the [write watch](../../../core/libs/prx/libc/src/GuestWriteWatch.cpp) write-protects watched pages and records the first write to each from a SIGBUS/SIGSEGV handler, because macOS has no counterpart to `userfaultfd` write-protect or `MEM_WRITE_WATCH`. The protection is real, so: @@ -95,42 +135,59 @@ - On macOS the [guest arena](../../../core/libs/prx/libc/src/GuestArena.cpp) finds host regions with `mach_vm_region` and reserves the rest with a fixed `mach_vm_map`, which fails instead of replacing, as macOS has no `/proc/self/maps` or `MAP_FIXED_NOREPLACE`. The kernel keeps the range up to 0x70_0000_0000 unmappable for every process (max protection none). It starts at 0x10_0000_0000 on macOS 26.5 (M1 Pro, arm64 and x86_64 under Rosetta) and at 0xF_C000_0000 on macOS 27 (27.0.1 on an M4 Pro, arm64 and x86_64 under Rosetta; 27.2 on an M2 Max, arm64). With dyld and the Rosetta runtime near 0x2_0200_0000 and 0x3_0400_0000, a guest mapping fixed inside those ranges fails where the console places it; mappings without a fixed address are placed around them. - [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) flushes only the host files and directories recorded through `RecordWrittenPath_nid_no_patch` (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by `sceKernelSync` - [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. On Linux, it skips a recorded file it cannot open for reading. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way -- [`fopen` and `freopen`](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) on Windows fail to open a directory in a read mode with `EACCES`, where FreeBSD opens it; directory streams are not emulated +- [`fopen` and `freopen`](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) on Windows: `freopen` onto a directory in a read mode fails with `EACCES`, where FreeBSD opens it. Reading a directory stream from `fopen` fails with `EISDIR` and sets the error indicator on every host, as FreeBSD 11 tmpfs does; FreeBSD 11 FFS returns the directory's raw blocks instead, and what the console's filesystems return is unverified - [readdir and readdir_r](../../../core/libs/prx/libc/src/Directory.cpp) (libc) on Windows report, on a file system whose directory listing carries no file ID (the entry's ID is 0, or the volume refuses `FileIdBothDirectoryInfo`), a nonzero `d_fileno` hashed from the entry name and the directory's file index, which differs from `lstat`'s `st_ino`. Not reproduced: NTFS lists an ID for every entry - [Writes on an update stream](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) on Windows go through the UCRT, which drops a write that directly follows a read without `fflush`, `fseek`, `fsetpos` or `rewind` unless the read reached end of file: `fwrite` returns 0, and `fputc` and `fprintf` report success, without the error indicator. FreeBSD 11.4's [`__swsetup`](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libc/stdio/wsetup.c#L59-L74) discards the read buffer and writes at the descriptor's offset, past a read-ahead of `st_blksize` bytes for a regular file, so where the console writes depends on its block size. Reads directly after writes follow FreeBSD's `__srefill` - [`fopen` of /dev/random and /dev/urandom](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) in a read mode returns a stream over the host null device whose `fread` fills the buffer with host OS entropy, as the libkernel [random device](../../../core/libs/prx/libkernel/File/src/RandomDevice.cpp) descriptors do. Only the exact paths are recognised, the stream has no descriptor, `setvbuf`, `feof`, `ferror`, `clearerr`, `flockfile`, `ftrylockfile`, `funlockfile` and `fclose` work on it, `fflush` returns 0 as on a FreeBSD read-only stream, and every other stream function throws, as does opening the paths for writing. `freopen` onto these paths still fails with `ENOENT`. ProsperoTV (PPSA99003) reads its phone pairing code this way - [RemoveTree](../../../core/libs/prx/libc/include/RemoveTree.hpp) (libc) removes a host tree itself on Windows instead of calling `std::filesystem::remove_all`: MinGW libstdc++ up to GCC 16 does not treat a reparse point as a link, so `remove_all` recurses through it and never terminates, and any tree that holds a symlink hangs. It builds the entry list from `GetFileAttributesW`, removes a reparse point as the link it is rather than its target, clears the read-only attribute and retries as `remove_all` does, and reports the first native error. The libstdc++ fix is in GCC 17 (commit 88856ad855ae); other hosts call `remove_all`. - The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Only register reads and writes (`validReg`, `getReg`, `setReg`, with `UNW_REG_IP` and `UNW_REG_SP`), `validFloatReg` (always false) and `isSignalFrame` are answered; the other slots throw. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked -- The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) steps through frames of modules without the project's `.ehmeta` and `.ehfram` tables (system DLLs, and WinLibs GCC 15.2's `libstdc++-6.dll` and `libwinpthread-1.dll`, whose `.eh_frame` sections are empty) with their PE unwind data through `RtlVirtualUnwind`, but does not run Windows exception or termination handlers: it stops at a frame whose unwind data has one, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), and the exception is then reported as unhandled +- The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) cannot run arbitrary Windows exception or termination handlers. It accepts GCC C and C++ handlers identified by a project's `.sehgcc` section and interprets their LSDA with the guest personalities. A handler in an unmarked module, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), still stops unwinding and the exception is reported as unhandled. SjLj exceptions are unsupported; Scoop GCC 15.2.0 selects them when SEH is disabled and lacks the System V ABI SEH compiler fix carried by MSYS2. - The libc [SSE4a trap emulation](../../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. -- [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists host interfaces and IPv4/IPv6 addresses in FreeBSD's layouts. Windows omits per-interface AF_LINK entries and interfaces without an address; its flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST` (PPP/tunnel adapters omit broadcast and point-to-point, and an unspecified prefix length gets a host mask). On Linux, `AF_LINK` `if_data` maps non-Ethernet/loopback/PPP types to `IFT_OTHER` (1) and leaves `ifi_omcasts`, `ifi_noproto`, `ifi_hwassist`, `ifi_metric`, `ifi_epoch`, `ifi_lastchange`, unknown baud rates and address-entry `ifa_data` as 0/null; a point-to-point address without a peer loses `IFF_POINTOPOINT`, and an omitted IPv4 broadcast is derived from the netmask. Interface names are the host's (`if` on Windows above 15 bytes). -- [gethostname](../../../core/libs/prx/libc/src/Hostname.cpp) (libc) reports the fixed guest name `AnyPS5` without querying the host machine name. Buffer copying, null queries and `ENAMETOOLONG` follow [FreeBSD gethostname](https://github.com/freebsd/freebsd-src/blob/main/lib/libc/gen/gethostname.c); the console's configurable hostname and its integration with `sysctl` and name resolution are unverified and not implemented. +- [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists host interfaces and IPv4/IPv6 addresses in FreeBSD's layouts. Windows and macOS omit per-interface AF_LINK entries and interfaces without an address; on Windows the flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST` (PPP/tunnel adapters omit broadcast and point-to-point, and an unspecified prefix length gets a host mask). On Linux, `AF_LINK` `if_data` maps non-Ethernet/loopback/PPP types to `IFT_OTHER` (1) and leaves `ifi_omcasts`, `ifi_noproto`, `ifi_hwassist`, `ifi_metric`, `ifi_epoch`, `ifi_lastchange`, unknown baud rates and address-entry `ifa_data` as 0/null; a point-to-point address without a peer loses `IFF_POINTOPOINT`, and an omitted IPv4 broadcast is derived from the netmask. Interface names are the host's (`if` on Windows above 15 bytes). +- [gethostname](../../../core/libs/prx/libc/src/Hostname.cpp) (libc) reports an empty name without querying the host machine name, as a payload process read on a PS5 (firmware 13.60): 0 with only the terminator for a 256-byte and a 1-byte buffer. Buffer copying, null queries and `ENAMETOOLONG` follow [FreeBSD gethostname](https://github.com/freebsd/freebsd-src/blob/main/lib/libc/gen/gethostname.c); whether a title process or a console with a configured name reads another name, and the integration with `sysctl` and name resolution, are unverified and not implemented. - [wctomb_s, wcrtomb_s, mbstowcs_s and mbsrtowcs_s](../../../core/libs/prx/libc/src/BoundsChecking.cpp) (libc) follow the console's `libc.prx`, but convert in the single-byte C locale of `wctomb`, `wcrtomb` and `mbrtowc` (`MB_CUR_MAX` 1): a character above 0xFF is `EILSEQ` in `wctomb_s` and `wcrtomb_s`, and each byte is one wide character in `mbstowcs_s` and `mbsrtowcs_s`. The locale of a title's `libc.prx` was not measured. The `wcrtomb_s` path where a character does not fit `smax` and the `EILSEQ` paths of `mbstowcs_s` and `mbsrtowcs_s` are not reached - [wcsrtombs_s](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) follows the console's `libc.prx`, but a runtime-constraint violation only returns its error: the default handler there writes the message and a newline to `stderr` before returning, which is not done here. Conversion uses the C locale of `wcrtomb` (`MB_CUR_MAX` 1), so the path where a multibyte character does not fit the remaining space is not reached -- [c16rtomb, c32rtomb, mbrtoc16 and mbrtoc32](../../../core/libs/prx/libc/src/Multibyte.cpp) (libSceLibcInternal, implemented in libc) follow the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x7D480 to 0x7D920), which converts to and from UTF-8 whatever the locale, with the original sequences of up to six bytes and no check for surrogates or for values above 0x10FFFF when encoding. The conversion state keeps the value in its first eight bytes and the count of bytes still expected (or 0x10 for a pending low surrogate, 1 for a stored high surrogate) as 16 bits at offset 0xA; a null state, which the console dereferences, throws. As on the console, `mbrtoc16` does not store the complete code point when it returns a high surrogate, so the low surrogate of the next call is built from the value the state last held, which is the partial value of an earlier incomplete call or whatever the caller initialised it to, `c16rtomb` returns -1 without setting `errno` when a high surrogate is not followed by a low one, and `mbrtoc16` and `mbrtoc32` report `EILSEQ` as 86. Measured on a PS5 (firmware 13.60) from a homebrew payload, the system `libSceLibcInternal`'s default locale also converts UTF-8 in `wcrtomb`, `wctomb`, `mbrtowc` and `mblen` (`wctomb` of U+263A gives `E2 98 BA`, `mblen` of a lone 0xE9 gives -1); this libc keeps its single-byte C locale, so [wctomb and mblen](../../../core/libs/prx/libc/src/Multibyte.cpp), which call `wcrtomb` and `mbtowc` here as they call the same internal routines on the console, share that difference. Titles bundle their own `libc.prx`, whose locale was not measured -- [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) converts 16-bit guest wide arguments to UTF-8 on both platforms, independently of the host locale. It does not use the guest's single-byte `wcrtomb`; the console's treatment of invalid UTF-16 and locale-specific encoding remains unverified. Linux retains host formatting for formats without wide arguments, `%m` or `%p`, so a NaN there still follows the host (glibc prints its sign, `-nan`, and honors the `+` and space flags, where FreeBSD prints `nan`); argument positions above 99 (FreeBSD's `NL_ARGMAX`) throw when a format requires the guest formatter +- [c16rtomb, c32rtomb, mbrtoc16 and mbrtoc32](../../../core/libs/prx/libc/src/Multibyte.cpp) (libSceLibcInternal, implemented in libc) - a null state, which the console dereferences, throws. Otherwise they follow the console's `libSceLibcInternal.sprx` (0x7D480 to 0x7D920), read from disassembly: UTF-8 whatever the locale, up to six bytes per sequence, with no surrogate or 0x10FFFF check when encoding. The state holds the value in its first eight bytes and the expected byte count (0x10 for a pending low surrogate, 1 for a stored high one) as 16 bits at 0xA. `mbrtoc16` builds a low surrogate from the value the state last held, `c16rtomb` returns -1 without `errno` for an unpaired high surrogate, and `EILSEQ` is 86. On a PS5 (13.60) the system library's default locale converts UTF-8 in `wcrtomb`, `wctomb`, `mbrtowc` and `mblen`; a title's bundled `libc.prx` does too until `setlocale` installs a table ([disassembly](https://github.com/boykopovar/AnyPS5/pull/8855#issuecomment-6335267758)) +- [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) follows a payload process on a PS5 (firmware 13.60): each 16-bit unit of a `%ls` or `%lc` argument is encoded alone, so a surrogate pair gives two 3-byte sequences; a NaN keeps its sign and the `+` and space flags, and `0` pads it with spaces; argument positions above 99 work (up to 400 measured; above 65535 throw here); an unknown conversion such as `%m` or `%k` prints `%` and its letter, padded to the width, without taking an argument. The guest's `wcrtomb` and locales are not used. On Linux the formats without those conversions or a floating-point conversion go to glibc, which prints NaN the same way. The `_s` forms throw for an unknown conversion, `%m` included, which was not measured +- [%a and %A](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) format a finite double as the `libc.prx` that titles ship does, instead of through the host: a subnormal is normalised (`0x1p-1074`), a shorter precision rounds half up on the dropped hexadecimal digits and a carry stays in the leading digit (`%.3a` of `0x0.fffffffffffffp-1022` is `0x2.000p-1023`), and `-` overrides `0`. Checked by running the copies of PPSA13934, PPSA07923 and PPSA05916 side by side on 7 million random formats, not measured on a console. Infinities and `long double` still go to the host; NaN keeps its sign and flags through the formatter on current main. +- [%e, %f and %g](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) format a finite `double` or `long double` as the `libc.prx` that titles ship does, instead of through the host. It keeps the exact decimal digits down to position `4 * floor(k / 4) - 20`, where `k` is the `frexp` exponent times 30103 / 100000 truncated, and prints zeros after them; a shorter precision rounds half up on the kept digits, except that an exact tie rounds down when `k` is at least 8 (`%.0f` of 67108865.5 is `67108865`). The console computes the kept digits in extended precision, so about 7% of outputs longer than 17 significant digits end one unit lower than here (`%f` of 1e22 is `9999999999999999999999.000000`). Checked by running the copies of PPSA07923, PPSA05916 and PPSA13934 side by side, not measured on a console. The wide printf family still formats through the host. - [%p and %lp](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) follow the console's `libSceLibcInternal.sprx` measured from a homebrew payload on firmware 12.70 ([#7549](https://github.com/boykopovar/AnyPS5/issues/7549)), not FreeBSD: bare lowercase hex with no `0x` prefix even under `#`, `0` for a null pointer, `+` and the space flag ignored, the width counting only the digits, `-` padding on the right, `0` padding with zeros when there is no precision, the precision as the minimum number of digits, nothing for a null pointer under `%.0p`, and `l` accepted. Only `snprintf` and `swprintf` of that module were measured, from a payload process rather than a title process, so a width beside `%.0p`, the `0` flag beside a precision (which C17 ignores) and `%.0p` of a non-null pointer follow this formatter's own rules and are not measured; `swprintf` was not run with `%lp`, so the wide family still refuses that conversion and returns -1. Linux routes `%p` and `%lp` to this formatter because glibc prints the FreeBSD form and `(nil)` for a null pointer, which means a format that combines `%p` with a conversion this formatter refuses, such as an argument position above 99, throws there instead of reaching glibc - [collate and collate _Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) build the facet for the C locale only and throw for any other locale name, the wide entry point following the narrow one it sits next to. The wide facet carries its own vtable, whose `compare`, `transform` and `hash` take the guest's 16-bit characters, so the length of a range is a count of characters: `compare` orders by 16-bit value (the C locale, as Dinkumware's `char_traits::compare` does), `transform` copies the characters into the result and records size and capacity in 16-bit elements (inline capacity 7, heap capacity `length | 7`, `(capacity + 1) * 2` bytes including the terminator), following Dinkumware's [VC12 `basic_string`](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/xstring#L500-L531), and `hash` mixes one whole character per round, so wide text below 0x100 hashes like its narrow spelling and a character above 0xff hashes as one value instead of the two bytes it holds. The console's `libc.prx` exports both `_Getcat` (NIDs BSVJqITGCyI and 2wz4rthdiy8) with identical 0x19d-byte bodies, believed to build the same 0x20-byte facet; the console bodies themselves are unreadable (obfuscated dynsym, and the text of the relinked `libc.prx` is zeroed), so the wide entries follow the narrow algorithm and the C locale instead of the console's wide code, and no console run checks the two. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size. The narrow result capacity (`length | 15`) and the over-aligned storage of results of 4080 characters or more follow the console's `libc.prx` code and are not checked on hardware. No module of the PPSA59322, PPSA76677 or PPSA97358 dumps in this tree imports either `_Getcat`, so neither has been exercised by a title. +- [codecvt, numpunct, num_put and time_put _Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) build facets matching a bundled console `libc.prx` (dynsym decoded by NID, vtables disassembled): categories 2, 4, 4 and 5, sizes 0x10, 0x30, 0x10 and 0xb8. `codecvt` reproduces the console virtuals (`noconv` conversions, `length` the shorter range); `numpunct` copies its C-locale table (`""`, `.`, `,`, `false`, `true`). `num_put`/`time_put` formatting virtuals throw rather than fabricate output, and `time_put`'s `_Locinfo` table is unpopulated — the console's `do_put` bodies were not mirrored. The facets share the no-op `destroy`/`deleteObject`/`retain`/`release` base used for `collate`, so `numpunct`'s static C-locale strings are never freed. Unlike `collate`, these `_Getcat` entries ignore the locale argument and always build the C-locale facet; the console's behaviour for a non-C name is unverified. `std::clog` and `std::wclog` are 0x400-byte zeroed objects like the rest of the `NSTDSTR` stream set (the console's `libc.prx` sizes `std::clog` at 96 bytes, measured from its dynsym `st_size`), and the `numpunct::id` and `time_put::id` objects are 8-byte zeros like the other facet ids. - [std::__throw_bad_variant_access](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) throws `std::bad_variant_access` with the `what()` string `bad_variant_access`, the one [LLVM libc++](https://github.com/llvm/llvm-project/blob/main/libcxx/src/variant.cpp) returns, since the export carries libc++'s `__throw_bad_variant_access` name; the console's string is unverified. Onimusha: Way of the Sword (PPSA27836 01.000.001) imports it from `libc`, and its bundled `libc.prx` does not export it. -- [std::bad_optional_access and std::_Throw_bad_optional_access](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) provide the exception type, constructor, destructor, and `what()` exports alongside `_Throw_bad_optional_access`, returning the `what()` string `bad_optional_access` matching Dinkumware and libc++ conventions; the console libc.prx string is unverified. PPSA26578 (issue #7624) and PPSA34015 import `_ZSt26_Throw_bad_optional_accessv` (`KwzEip7m6TM`) from `libc`, and fail to load on main with unresolved import (0xC0000139). +- [std::bad_optional_access and std::_Throw_bad_optional_access](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) provide the exception type, constructor, destructor, and `what()` exports alongside `_Throw_bad_optional_access`, returning the `what()` string `bad_optional_access` matching Dinkumware and libc++ conventions; the console libc.prx string is unverified. PPSA26578 (issue #7624) and PPSA34015 import `_ZSt26_Throw_bad_optional_accessv` (`KwzEip7m6TM`) from `libc`. - [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc), the `_s` forms included, decodes the input of the 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`) on Linux as UTF-8, mirroring the narrow printf family, where FreeBSD uses the locale's `mbrtowc` (one byte per character in the C locale); the console's locale encoding is unverified. Input that is not UTF-8, a character outside the BMP, a scanset member outside ASCII, positional arguments next to a wide conversion and wide conversions in `fscanf` throw. Each character is stored as one 16-bit unit, widths and `_s` sizes count characters, and a `%lc` field cut short by the end of the input keeps what it read, as FreeBSD's `convert_wchar` does. Windows hands `%ls`, `%lc` and `%l[` to the host, whose `wchar_t` is 16 bits - [The scanf family](../../../core/libs/prx/libc/include/WindowsScanning.hpp) (libc) on Windows returns 0, as C17 and FreeBSD do, when input fails after only suppressed conversions have completed (`sscanf("12", "%*d%d", &value)`). Linux hands the format to glibc, which returns `EOF` there. On Windows the host stops a `%x`, `%X`, `%i` or `%p` after the `x` of a `0x` that no hex digit follows (or that the width cuts off), and a float conversion (`%e`, `%f`, `%g`, `%a`) after the `e` of an exponent with no digits (`1e`), where C17 and FreeBSD stop before the `x` or the `e` +- [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) on Linux reads a `q` length as `ll`, as glibc does. On Windows a `q` directive ends the scan with nothing stored, as the console's own libc does for `%qd`; FreeBSD 11.4's source reads it as `ll`. - [Guest path resolution](../../../core/libs/prx/libc/src/General.cpp) falls back to ASCII case-insensitive component lookup, following [shadPS4's host filesystem](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/file_sys/backends/host_fs.cpp). PPSA03747 requests `Skyrim.INI`, `Data/Video` and lowercase archive/plugin names that differ from the dump's filenames. Exact host names take precedence; multiple fallback matches throw instead of selecting an arbitrary file. Existing parent names are resolved when creating a new file, and guest mount aliases use the same ASCII comparison. Results are not cached, so renames and newly created files remain visible. Unicode case folding and PS5 behaviour for case-colliding host entries are unverified; host roots are used as supplied. Explicit native Windows drive and UNC paths bypass guest parent-component validation.. A relinked module's `.guest.prx` is matched the same way when the original module is not beside it: Borderlands 4 (PPSA01494) loads `/app0/prx/libRenoirCore.PS5.prx`, which it ships as `librenoircore.ps5.prx`. A relinked module request that misses both the exact and the case-insensitive `.guest.prx` takes the sibling whose stem (the name without its last extension, ASCII case ignored) equals the requested stem and that ends with `.guest.prx`, shortest name first: Unity IL2CPP titles load `Media/Modules/.prx` and `Media/Plugins/.prx`, and the relinker names the converted module after the ELF it was built from, `.elf.guest.prx`. The console's module namespace is assumed to ignore case and the extension; several stem matches pick the shortest name instead of throwing, which is not measured on a console +- [Guest file names on Windows](../../../core/libs/prx/libc/src/General.cpp) (libc) are not escaped or mapped before the host call, so names Windows treats specially differ from FreeBSD, where only `/` and NUL are special. `a*b`, `a?b`, `ac`, `a|b` and `a"b` fail with `EINVAL` (22) where FreeBSD creates them. `trail.` and `trail ` both create the host file `trail`, so three FreeBSD names are one file here. `NUL` and a reopen of it succeed, but no file is created. `con`, `aux.txt` and `COM1` create ordinary files. Colons are under Colons in guest names. Measured on Windows 11 (10.0.26200), MinGW-w64 GCC 15.2.0 r7, with `open` (`O_CREAT|O_EXCL|O_WRONLY`), a one-byte write and a listing of the host directory; not measured on Linux or a console. A fix needs a guest-to-host name mapping. - [Colons in guest names](../../../core/libs/prx/libc/src/General.cpp) (libc) - on Windows (NTFS) a guest name containing `:` is not a plain file name: `name:stream` opens or creates the alternate data stream `stream` of the file `name`. Creating `/savedata0/C:file` makes an empty file `C` whose stream `file` holds the data, and `stat` of `/savedata0/C` then reports a regular file. `mkdir` of a name containing `:`, such as `C:` or `dir:x`, fails with EINVAL. `name:stream` already behaved this way before drive-letter components were kept under their mount -- [tzname](../../../core/libs/prx/libc/src/Time.cpp) (libc) points at the host's zone names, read at load and again by `tzset`: the abbreviations a `TZ` string gives (`EST5EDT` is `EST` and `EDT` on both hosts), but on Windows without `TZ` the CRT's full names such as `GMT Standard Time`, which ICU's [uprv_tzname](https://github.com/unicode-org/icu/blob/457157a92aa053e632cc7fcfd0e12f8a943b2d11/icu4c/source/common/putil.cpp#L1244-L1264) (ES-DE in PPSA99169) cannot map to a zone ID. A zone with no daylight-saving time has `tzname[1]` empty on Windows and equal to `tzname[0]` on Linux, where FreeBSD leaves three spaces. The console's names are not known -- [Trailing slash on a mount alias](../../../core/libs/prx/libc/src/General.cpp) (libc) - on Windows a trailing slash after a file name under a mount alias such as `/savedata0` is still dropped, so `unlink`, `rename`, `open` and the other path calls act on the file where FreeBSD 11.4's `lookup()` fails with `ENOTDIR`; Linux and macOS pass the slash to the host, as for paths outside an alias +- [tzname](../../../core/libs/prx/libc/src/Time.cpp) (libc) points at the host's zone names, read at load and again by `tzset`: the abbreviations a `TZ` string gives (`EST5EDT` is `EST` and `EDT` on both hosts), but on Windows without `TZ` the CRT's full names such as `GMT Standard Time`, which ICU's [uprv_tzname](https://github.com/unicode-org/icu/blob/457157a92aa053e632cc7fcfd0e12f8a943b2d11/icu4c/source/common/putil.cpp#L1244-L1264) (ES-DE in PPSA99169) cannot map to a zone ID. A payload process on a PS5 (firmware 13.60) read both names empty with `TZ` unset, and its `%Z` printed nothing whatever `tm_zone` held; the names a title process sees are not known - [localtime, mktime](../../../core/libs/prx/libc/src/Time.cpp) (libc) on Windows convert through the CRT's `localtime_s` and `mktime`, which accept about 1970 to January 3001 UTC and apply the CRT's daylight-saving rule. A time outside that range is converted a whole number of 400-year Gregorian cycles away, where the calendar repeats, so it gets the rule the CRT applies to that year and not the zone's historical one. On macOS `mktime` converts through the host's `mktime`, which refuses every year before 1900. A local time before 1900 is instead solved for directly: the offset `localtime_r` gives for a candidate time, which follows the zone's history (local mean time before its first transition), is subtracted from the local fields, and the step is repeated until the offset holds. `tm_isdst` is not consulted there, so a time with `tm_isdst` set where the zone had no daylight offset is taken as standard time. FreeBSD's [`time1`](https://github.com/freebsd/freebsd-src/blob/stable/11/contrib/tzcode/stdtime/localtime.c#L2036) would instead shift it by the difference between one of the zone's daylight types and a standard one. `gmtime` converts every time whose year fits an `int` on both hosts. -- [strftime and asctime](../../../core/libs/prx/libc/src/Time.cpp) (libc) follow FreeBSD 11 [strftime.c](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/lib/libc/stdtime/strftime.c) and [asctime.c](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/contrib/tzcode/stdtime/asctime.c) with the C locale on every host, not measured on the console; other locales are not implemented. `%s` follows FreeBSD 9 and 11 and is the local-time `mktime` of the broken-down time (current FreeBSD uses `tm_gmtoff`), which is -1 on Windows outside the range above. `%Z` is `tm_zone`, or `tzname[tm_isdst != 0]` when it is null and `tm_isdst` is not negative. The two-digit year warning FreeBSD writes to `stderr` when `CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS` is set is not written -- [wcsftime](../../../core/libs/prx/libc/src/Time.cpp) (libc) follows FreeBSD 11.4's [wcsftime.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/locale/wcsftime.c) over this library's `wcsrtombs`, `strftime` and `mbsrtowcs` in the single-byte C locale of [none.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/locale/none.c), with the guest's 16-bit `wchar_t`, without console verification. It has the `strftime` gaps above. A format unit above 255 returns 0 with `EILSEQ`; an empty result, a result that does not fit and a null buffer return 0 with `errno` unchanged, discarding the `ERANGE` or `EINVAL` that the Windows CRT's `strftime` sets. A null format or `tm` throws, where FreeBSD dereferences it (a null `tm` is read there only when the format has a conversion that needs it) +- [strftime and asctime](../../../core/libs/prx/libc/src/Time.cpp) (libc) follow a payload process on a PS5 (firmware 13.60) in the C locale. `%G`, `%g` and `%V` throw for a negative `tm_yday`, a `tm_yday` past the year or a `tm_wday` below -6, where no rule fits the console: `%V` is 01, 00, 00 and 52 for `tm_yday` -25, -13, -12 and -1 (`tm_wday` 5), `tm_yday` 400 of 2026 is week 01 of 2027 but 415 of 1997 is week 59 and 365 of 2013 week 53, and `tm_wday` -12 with `tm_yday` 18 is week 04. `%k`, `%l`, `%P`, `%s`, `%+`, `%v`, flags and widths are copied as written, `E` or `O` before an unknown conversion is dropped, and a format ending in either throws, where the console crashed. Negative fields print 0 and fields wrap at 100 (`%j` at 1000, `%u` and `%w` at 10); `%Y` and `%G` are the year mod 10000, `%C` is `tm_year / 100 + 19`. A negative weekday or month gives `Sun` or `Jan`. `%z` and `%Z` ignore `tm_gmtoff` and `tm_zone` and use the guest zone and `tzname` +- [wcsftime](../../../core/libs/prx/libc/src/Time.cpp) (libc) converts the format to UTF-8, runs this library's `strftime` and converts the result back, where the console's `libSceLibcInternal.sprx` (0x854e0) copies literal wide characters and expands each conversion straight into the buffer; the results match for the formats tried, and it has the `strftime` gaps above. A result that does not fit returns 0 with the buffer untouched, where the console leaves the characters written so far. A null buffer returns 0, where the console writes through it, and a size too large for the narrow buffer returns 0 with `EINVAL`, where the console formats as usual. A null format or `tm` throws - [syslog](../../../core/libs/prx/libc/src/Syslog.cpp) (libc) writes each message to `stderr` as `[syslog:] `, with a newline added when missing, instead of sending it to the console's system log. Priority and facility handling follow FreeBSD's `vsyslog`: `%m` expands to the guest errno's message, unknown bits are reported (as priority 0x23, since FreeBSD passes `LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID` as the priority) and masked, a missing facility becomes `LOG_USER` or the facility set by `openlog`, and errno is preserved. `openlog` applies its identity and `LOG_PID`; a null identity retains the previous one, and a valid nonzero facility replaces the default. `closelog` clears the identity but retains the options and facility, as in FreeBSD 11; when no identity is set, FreeBSD falls back to the program name, which this backend does not emit. `LOG_PERROR` has no extra effect because stderr is the emulated log sink; `LOG_NDELAY`/`LOG_ODELAY`, `LOG_NOWAIT` and `LOG_CONS` have no daemon, child or console backend to control. `setlogmask` follows [FreeBSD 11](https://github.com/freebsd/freebsd-src/blob/8d2708de5d6112a492744d367cfdfb8fd219227a/lib/libc/gen/syslog.c): the process-wide mask defaults to 0xff, zero queries it, and nonzero values replace it; masked messages are skipped before formatting, including the unknown-priority diagnostic. The console's mask behaviour is unverified - [fwscanf, vfwscanf, wscanf, vwscanf and their `_s` forms](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) run the `swscanf` scanner on characters read with `fgetwc`, so everything listed for `swscanf` in this file applies to them. As in the console's `libc.prx`, a field takes characters while they can still begin a valid item, so a failed field such as `0x` for `%x` stays consumed, and only the character that ended the scan goes back to the stream. That character is pushed back as its UTF-8 bytes with `ungetc`, because `ungetwc` takes single bytes only; the Windows C runtime guarantees one byte of push-back, so a non-ASCII lookahead can be lost there. A null stream or format returns `EOF`; the console checks them only in the `_s` forms. - [daemon](../../../core/libs/prx/libc/src/Process.cpp) (libc) throws: forking into the background is not supported. PPSA85300 imports it and fails to load without it. VLC 3.0 calls it only when its `daemon` option is set ([libvlc.c](https://github.com/videolan/vlc/blob/6de05adcbaf2e8b85fe86aad4169393098628119/src/libvlc.c#L190-L197)), and the title starts libVLC with `--ignore-config` and without `--daemon` ([player.cc](https://github.com/ZiZc3/VLC/blob/f3253020a64368153233ede29183014c0b6c8979/src/player.cc#L960-L981)) -- [_WStoul](../../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../../core/libs/prx/libc/src/RuneLocale.cpp), [swscanf_s and swscanf](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal mismatch is a matching failure and a field that is only a prefix, such as `0x` or `1e`, does not convert, as C11 requires; FreeBSD's `vfwscanf` reports an input failure for the first and converts the second. Integers out of range are truncated from `strtoll`/`strtoull`. `%p` follows FreeBSD 11's [vfwscanf.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdio/vfwscanf.c#L610-L615): a hexadecimal integer with an optional `0x`, converted as unsigned and stored as a pointer; a length modifier on it throws, where FreeBSD ignores it. It throws for `%S`, `%C`, unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert. `swscanf` runs the same scanner without the size arguments of `%s`, `%c` and `%[`, as FreeBSD's [swscanf.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libc/stdio/swscanf.c) passes its arguments to `vswscanf`, so everything above except the sizes applies to it; it also keeps the null buffer, format and receiving pointer checks returning `EOF`, where C leaves those undefined. Neither variant is confirmed on a PS5 title +- [_WStoul](../../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../../core/libs/prx/libc/src/RuneLocale.cpp), [swscanf_s and swscanf](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal at end of input returns the number of assignments, including zero, rather than `EOF` as FreeBSD does, as reported from the console scanner in #8479; `%%` at end of input still returns `EOF`. A literal mismatch is a matching failure, where FreeBSD's `vfwscanf` reports an input failure. An incomplete floating exponent such as `1e+` converts its mantissa, as reported from the console scanner in #8479; other incomplete exponent forms are not separately verified. A short `%c` field is currently a matching failure without storing its available prefix, while #9321 reports that the console stores a short `%3lc` field; its return count, partial-output extent and precedence are unverified. An integer field that is only a prefix, such as `0x`, does not convert; FreeBSD's `vfwscanf` pushes back the `x` and converts the `0`. Integers out of range are truncated from `strtoll`/`strtoull`. `%p` follows FreeBSD 11's [vfwscanf.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdio/vfwscanf.c#L610-L615): a hexadecimal integer with an optional `0x`, converted as unsigned and stored as a pointer; a length modifier on it throws, where FreeBSD ignores it. `%S` and `%C`, with any length modifier or `*`, end the scan as a matching failure, as the console's Dinkumware scanner in PPSA21564's `libc.prx` treats them as invalid conversions; this was read from the module, not run on a console, and whether input ending before them is an input failure instead is unverified. It throws for unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert. `swscanf` runs the same scanner without the size arguments of `%s`, `%c` and `%[`, as FreeBSD's [swscanf.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libc/stdio/swscanf.c) passes its arguments to `vswscanf`, so everything above except the sizes applies to it; it also keeps the null buffer, format and receiving pointer checks returning `EOF`, where C leaves those undefined. Neither variant is confirmed on a PS5 title - [flock](../../../core/libs/prx/libc/src/FileLock.cpp) (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's [flock(2)](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/sys/flock.2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The lock is a host [LockFileEx](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) lock on one byte at offset 2^63 - 2, which no file reaches, so it excludes other `flock` calls and leaves reads, writes and mapped views alone, like FreeBSD's advisory locks. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified. - [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) follows Dinkumware `thread0.cpp` ([VC12](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/thread0.cpp), [STL](https://github.com/microsoft/STL/blob/da52dc0f/stl/src/thread0.cpp)), without console verification. Codes outside 0..6 throw `std::out_of_range`, the deleting destructor throws, and STREAMS errnos absent from FreeBSD 11.4 [`errno.h`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/sys/errno.h) (`ENODATA`, `ENOSR`, `ENOSTR`, `ETIME`) are omitted. Codes 0..6 throw a 32-byte [VC12 `system_error`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/system_error) with FreeBSD errnos (16, 22, 3, 12, 1, 11, 35), a static `generic_category` (not `_Generic_object`), and unverified 40-byte [GuestLocale::String](../../../core/libs/prx/libc/include/GuestLocale.hpp) messages from [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/syserror.cpp) - [Call tracing](../../../core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp) (`--trace-imports`, `APS5_TRACE_CALLS`) is missing on macOS and records only the entry of a call, not its arguments or result. One process-wide ring keeps the last 65536 calls, so a busy thread pushes the others out; the reporting thread's own calls are listed only while they are still in that ring. Calls are counted per traced target; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and `xmm0` to `xmm7`; wider vector arguments survive only while the recording code avoids AVX, which is untested. Linux has no crash report, so it prints the trace only on `SIGABRT` (guest or host `abort()`), not on `SIGSEGV`; a caller in a module loaded after the last traced import prints without a module name, and a thread aborting while another writes the report waits for it at most 2 seconds - [_Dtest](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows Dinkumware double classification matching _FDtest: finite values return -1, subnormals -2, zeros 0, infinities 1, and NaNs 2. +- [_Exp and _FExp](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libc) follow PRAGMATA PPSA02530's bundled `libc.prx` (static disassembly): both operands are classified first and only finite/denormal pairs take the finite path. The strict x-only cutoffs at ±229/±1842 store `0.0*y` without an error below and `y*inf` with overflow above, so an offset does not compensate them. A zero multiplier keeps its sign, a NaN multiplier leaves `*px` unchanged, `+inf` with zero or `-inf` with infinity is invalid, and finite multipliers with `+inf`/`-inf` return signed infinity/zero without flags. Inside the cutoffs subnormals are `Finite` without flags, a zero from scaling raises underflow, and the negative cutoff itself raises nothing. `_FExp` uses the measured `c1`, `c2`, `invln2`, `Q` and `P`; `_Exp` finite constants, exact finite bits, signaling NaNs, directed rounding and flush-to-zero modes remain unverified. +- [exp2f](../../../core/libs/prx/libc/src/Math.cpp) (libc) is FreeBSD msun's [`s_exp2f.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_exp2f.c), with `huge` and `twom100` read at run time and `x + redux` rounded to float, as msun's `volatile` reads and `STRICT_ASSIGN` do. As in the `libc.prx` that titles ship, `errno` is set to `ERANGE` only in msun's overflow (`x >= 128`) and underflow (`x <= -150`) branches, so a subnormal result leaves it unchanged. Floating-point exceptions are not compared. It gives the same bits and `errno` as the `exp2f` of the `libc.prx` bundled with PPSA07923, PPSA05916 and PPSA13934 for all 2^32 arguments, run on a Windows host. - [app0.zar](../../../core/libs/prx/libc/src/AppArchive.cpp) (libc) - a [ZArchive](https://github.com/Exzap/ZArchive) beside `app0/` is a read-only layer under it: a path missing from `app0/` is looked up in the archive, case-insensitively. It is not a console format; titles ship their files in a PFS image instead. Resolving an archive-only path through any call other than the archive-aware libkernel ones throws, so `fopen`, `stat`, `access`, `opendir` and the rest do not read the archive yet, and `opendir` of a directory that exists in both places, `app0/` itself included, and `fdopen` of an archived descriptor throw instead of missing its entries or reading the null device. A damaged compressed block throws instead of returning `EIO` - [fopen_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) - returns the errno of the failed open, as C11 K.3.5.2.1 leaves the nonzero value open and Microsoft's CRT returns errno; the console's Dinkumware `fopen_s` is not verified +- [codecvt vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `H-TDszhsYuY`) is thirteen zeroed `uintptr_t` slots matching `st_size` 104 on title `libc.prx` from PPSA26344 and on `libSceLibcInternal.sprx` (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and seven conversion virtuals). Facet calls through the object fault until those methods are wired. +- [num_put vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `59oywaaZbJk`) is fourteen zeroed `uintptr_t` slots matching `st_size` 112 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and eight `do_put` virtuals). Facet calls through the object fault until those methods are wired. +- [time_get vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `o4kt51-uO48`) is thirteen zeroed `uintptr_t` slots matching `st_size` 104 on console `libSceLibcInternal.sprx` (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and seven `do_*` virtuals). Facet calls through the object fault until those methods are wired. - [ios_base standard streams](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) `_Addstd` queues the stream in the standard stream table of Dinkumware [ios.cpp](https://github.com/ganboing/crt.vc12/blob/master/src/ios.cpp) (`stdstr`, `stdopens`, `_Addstd`, `_Ios_base_dtor`): it takes the first of slots 1 to 7 that is free or already holds the stream, and slot 8 (`NSTDSTR`) when none is, so slot 8 is shared by every stream past the first seven and registration never fails. It stores the 1-based slot index in the guest `IosBase::standardStream` (Dinkumware's `_Stdstr`, where 0 means not a standard stream) and counts the stream objects registered per slot; registering the same stream again reuses its slot and raises its count. Measured on a PS5 (firmware 13.60) from a homebrew payload calling the system `libSceLibcInternal` export: in a process whose own standard streams already hold slots 1 to 7, twelve distinct streams and a repeated one all get 8, written as a 64-bit value at offset 0x8 with no other byte of the object touched; the assignment of slots 1 to 7 to fresh streams is not observable there and follows VC12. The counts are `int`, not VC12's `char`, and the registration and the destructor lock the table where VC12 locks only `_Addstd`; a slot index past the table throws in the destructor instead of indexing out of bounds. The destructor decrements its slot's count and suppresses the rest of the destruction while the count stays positive, as `_Ios_base_dtor` does, and the slots of destroyed streams keep their pointer and stay reserved for that stream, as `stdstr` does. The streams themselves are destroyed by the title's own code as it registers them through `__cxa_atexit`; when the last reference closes, the unsupported stream storage and callback cleanup is still the throw it was and the locale is released as for a non-standard stream. -- [std::_Pad, _Thrd_join, _Thrd_detach, _Thrd_id](../../../core/libs/prx/libc/src/Threads.cpp) (libc) follow Dinkumware V6.00 `thr/xthread` and `thr/threadcall.cpp` ([crt.vc12 `f1d47126`](https://github.com/ganboing/crt.vc12/tree/f1d47126/src/thr)), without console verification. `_Thrd_id` throws because whether the console returns `pthread_t` or a kernel thread ID and at what width is unknown; `_Launch` omits `_Cnd_do_broadcast_at_thread_exit` and calls `pthread_create` registered by libkernel with default attributes. The 32-byte pad layout (`_Go()` vtable at 0x0, `_Cnd_t` at 0x8, `_Mtx_t` at 0x10, `bool _Started` at 0x18) is assumed from VC12, unverified. Failing primitives or thread start throw the guest `std::system_error`; null arguments throw. `_Thrd_join` returns `_Thrd_error` (4) on `pthread_join` failure and truncates the exit code to `int`; `_Thrd_detach` returns `_Thrd_error` (4) on `pthread_detach` failure and throws when none is registered +- [std::_Pad, _Thrd_join, _Thrd_detach, _Thrd_id](../../../core/libs/prx/libc/src/Threads.cpp) (libc) follow Dinkumware V6.00 `thr/xthread` and `thr/threadcall.cpp` ([crt.vc12 `f1d47126`](https://github.com/ganboing/crt.vc12/tree/f1d47126/src/thr)), otherwise without console verification. `_Thrd_id` returns `scePthreadSelf()` unchanged, as measured on a PS5 (firmware 13.40) in two threads (#4164, #8711); `_Launch` omits `_Cnd_do_broadcast_at_thread_exit`; the two-argument form starts with default attributes and the three-argument form forwards the caller's attribute pointer, as PPSA02530's `libc.prx` does at `0x18090`. The 32-byte pad layout is assumed from VC12, unverified. Failing primitives or thread start throw the guest `std::system_error`; null arguments throw. `_Thrd_join` returns `_Thrd_error` (4) on `pthread_join` failure and truncates the exit code to `int`; `_Thrd_detach` returns `_Thrd_error` (4) on `pthread_detach` failure and throws when none is registered +- [siginterrupt](../../../core/libs/prx/libc/src/Process.cpp) (libc) runs FreeBSD 11.4's `siginterrupt.c` sequence through the `sigaction` and the `_sigintr` set that libkernel registers at load: it reads the signal's disposition, sets or clears the signal's bit and `SA_RESTART`, and installs the disposition again. The set is written outside libkernel's registration lock, as FreeBSD's libc writes it without one; a call before libkernel has registered throws. +- [collate vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `ruZtIwbCFjk`) is nine zeroed `uintptr_t` slots matching `st_size` 72 on the title `libc.prx` from PPSA16833 (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and three `do_*` virtuals). Facet calls through the object fault until those methods are wired; `_Getcat` still installs the host `g_collateVtable` for facets it builds. +- [num_get::id, time_get::id and the num_get vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) are data objects, as the public SDK's libSceLibcInternal table declares them and as the other facet ids and the `num_put` and `codecvt` vtables are kept: the ids start at zero and the vtable is 17 zeroed slots, the 136-byte size of `KfcTPbeaOqg` in the console's libSceLibcInternal, whose relocations fill the offset-to-top and typeinfo entries, the two destructors, `locale::facet::_Incref` and `_Decref`, and the eleven `do_get` of Dinkumware's crt.vc12 `xlocnum`, so a guest resolves the symbols and reads data, and a facet call through the vtable faults. libSceAmpr exported the three as throwing functions, which a data consumer reads as code; its qualified imports fall back to libc. +- [printf family conversions](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - Windows `FormatWindows` follows the title `libc.prx` `_Printf` measured from PPSA13934, PPSA07923, PPSA05916 and PPSA04158: `%q`, `%D`, `%O` and `%U` are unknown conversions; an unknown character is printed with its leading `%`, and width, `-` and discarded length modifiers behave as measured (`%5y` is ` %y`, `%hhz` is `%z`). `%%` takes a width. A trailing `%` reads past the format's NUL and is not reproduced; it throws. `#`, precision and a `0` without a width are ignored, as a payload process on a PS5 (firmware 13.60) printed `%#k` and `%.2k` as `%k`; `+`, space, `'` and `0` with a width on an unknown conversion are unverified and throw. On Linux a format with an unknown conversion also goes to this formatter +- [child process registry](../../../core/libs/prx/libc/src/Process.cpp) (libc) - `RegisterChildProcess_nid_no_patch` and `WaitChildProcess_nid_no_patch` track the native handles of children created by `posix_spawn` ([libSceLibcInternal](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp)) so libkernel can reap them on Windows, where the host has no `waitpid`. The registry lives in libc because libSceLibcInternal imports libc but not libkernel. `RegisterChildProcess_nid_no_patch` stores the process ID and handle; only reaped handles are closed, and a process that exits without waiting leaks them. Registration without a live child is not validated, and a duplicate process ID is accepted. The decoder maps recognized structured-exception codes to guest signals, keeping the guest `exit` byte layout for anything else. These exports are internal glue for libkernel, not console ABI: their behavior is unverified against any PS5 library and a title importing their NIDs directly is not expected. +- [fprintf_s, vfprintf_s, vprintf_s, fwprintf_s, wprintf_s, vfwprintf_s and vwprintf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) follow C11 K.3.5.3.1, K.3.5.3.10 and K.3.9.1.1 with the constraints of `snprintf_s` and `swprintf_s`: a null stream, format or argument list, any `%n`, and a null `%s`, `%ls` or `%S` argument return -1 and write nothing, because the output is formatted completely before it is written. A violation only returns: the constraint handler is not invoked, `errno` is untouched, and the value is the one the narrow `snprintf_s` returns on this branch, not a result measured for a stream function on a PS5. A closed stream sets `EBADF` (9) as `fprintf` does. `vprintf_s` and `vwprintf_s` write to the guest standard output stream, and the wide forms count characters while the narrow ones count bytes, as their unchecked siblings do +- [scanf_s, fscanf_s, vscanf_s and vfscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) run the scanner of `sscanf_s` over the stream, taking the size of `%s`, `%c` and `%[` as `unsigned int` after the pointer, so everything listed for `sscanf_s` applies, including the count of items converted before an undersized destination, whose first element is set to zero. Each directive is read from the stream with the host `fscanf`, with one character of look-ahead pushed back, so the stream stops after the last character a conversion used. A null stream, null format or null destination returns `EOF`, a closed stream also sets `EBADF`, and the constraint handler is not invoked. The 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`) throw on Linux, as in `fscanf`. None of the stream forms were measured on a PS5, and the size type follows `sscanf_s` rather than `rsize_t` +- [freopen_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) follows C11 K.3.5.2.2 through `freopen`: a null result, mode or stream returns `EINVAL` (22) with `*result` set to null when it exists, and a null name is passed to `freopen`, which reports mode-only reopening as unsupported (`ENOTSUP`, 45) with `*result` null, and the constraint handler is not invoked, as in `fopen_s`. A successful reopen stores the stream in `*result` and returns 0, and a failed one stores null and returns the errno `freopen` left, closing the stream as `freopen` does. The console's return values are unverified diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 381e509889..7a7a1524a1 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -2,19 +2,24 @@ ### Silent stubs +- [SO_TIMESTAMP, SO_BINTIME](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - set and read back as their bits, as a payload process on a PS5 (firmware 13.60) answered them, but `recvmsg` adds no `SCM_TIMESTAMP` or `SCM_BINTIME` control message, which FreeBSD 11.4's `ip_savecontrol` and `ip6_savecontrol` add to a received datagram while the option is set; what the console's `recvmsg` returns was not measured - [sceKernelGetSocPowerConsumption](../../../core/libs/prx/libkernel/System/src/ThermalSensors.cpp) (libkernel) - returns `EINVAL` and writes nothing, as `sceKernelGetSocSensorTemperature` does when the host has no sensor: the host's package power is not read (Linux RAPL `energy_uj` is readable by root only, and Windows has no general source). The prototype is not documented. PS5LM Plus (PPSA99581) passes a zeroed 128-byte buffer and, on success, reads its first 32-bit word as milliwatts, accepting 1000 to 350000; on failure it shows 0 W -- [scePthreadMutexattrSetprotocol](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) - accepts 0 to 2 as a PS5 (firmware 13.60) does and returns `EINVAL` otherwise, but the protocol is not stored or applied: priority inheritance and protection are not modelled. On the console a null or uninitialized mutex attribute returns `EINVAL` from the SCE attribute calls, `scePthreadMutexInit` takes an uninitialized attribute as the default one, and a destroyed mutex returns `EINVAL` from `scePthreadMutexDestroy`; a null mutex pointer crashed the payload and still throws +- [scePthreadMutexattrSetprotocol, scePthreadMutexattrGetprotocol, scePthreadMutexattrSetprioceiling, scePthreadMutexattrGetprioceiling](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) - `scePthreadMutexattrSetprotocol` accepts 0 to 2 as a PS5 (firmware 13.60) does and returns `EINVAL` otherwise, but the protocol is not applied: priority inheritance and protection are not modelled. On the console a null or uninitialized mutex attribute returns `EINVAL` from the SCE attribute calls, `scePthreadMutexInit` takes an uninitialized attribute as the default one, and a destroyed mutex returns `EINVAL` from `scePthreadMutexDestroy`; a null mutex pointer crashed the payload and still throws - [pthread_mutexattr_setprotocol, pthread_mutexattr_getprotocol, pthread_mutexattr_setprioceiling, pthread_mutexattr_getprioceiling](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) - the protocol and the priority ceiling are stored and reported as in [FreeBSD 11 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.0/lib/libthr/thread/thr_mutexattr.c#L192-L245) (the ceiling is set or read only under `PTHREAD_PRIO_PROTECT`, and setting a protocol resets it to the highest `SCHED_RR` priority). A null, uninitialized or destroyed attribute returns `EINVAL` before the other arguments are checked; this error behavior is unverified on the console. Mutexes do not apply the protocol or ceiling: a priority-inherit or priority-protect mutex locks like a normal one and does not change its owner's priority +- [sceCoredumpSetUserDataType](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel, NID `Uxqkdta7wEg`) - `(int type)` follows the PS5 12.00 `libkernel.sprx` at 0x138B0: the sign-extended `type` goes to syscall 601 (command 29, third argument 0), and the raw result comes back with no SCE code mapping, so a kernel error is -1 with `errno` set. Which types the kernel accepts is not measured. No dump is produced here, so every type returns 0. Palworld (PPSA23126, #2598) and Hello Neighbor (PPSA35154, discussion #6991) import it; #4144 left it throwing because no signature was known - [sceKernelGetMainSocId and sceKernelGetCpumode](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) both return 0, and [sceKernelGetCpuFrequency](../../../core/libs/prx/libkernel/Time/Time.cpp) returns the host TSC frequency measured for `sceKernelGetTscFrequency`, not the console CPU clock. The PS5's SoC id and CPU mode are unknown; shadPS4's PS4 values are [0x710f10/0x740f30 and 0, 2 or 5 from the param.sfo CPU-mode flags](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1a/src/core/libraries/kernel/process.cpp). PPSA99203 only logs them (`[console] main SoC id: result=%#x out=%#x`, `CPU: %llu Hz, cpumode %d`), passing a pointer that `sceKernelGetMainSocId` leaves untouched - [sceKernelEnableDmemAliasing](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) returns 0 and changes nothing: direct memory can always be mapped more than once here, as `DoMapDirect` in [DirectMemory.cpp](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) only checks that the physical pages are allocated, so there is no aliasing to turn on. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/memory.cpp#L39-L43) (PS4) sets a flag there that lifts the `EBUSY` its [`sceKernelMapDirectMemory2`](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/memory.cpp#L299-L301), and [`sceKernelMapDirectMemory` for SDK 2.50 and later](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/memory.cpp#L237-L242), return for direct memory that is [already mapped](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/memory.cpp#L548-L553); that check is not applied here. The no-argument `int` signature follows shadPS4 and ProsperoEden (PPSA99008), which calls it before mapping the same direct memory again. Whether the PS5 refuses such a mapping without the call is unverified -- [sceKernelInternalMemoryGetAvailableSize](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) reports 0 bytes and returns 0: what the console's internal memory pool holds and how large it is are unknown. The `int (size_t*)` signature is from [xUtilty](https://github.com/Backporter/xUtilty/blob/328a856af1ef/source/SystemWrapper.cpp#L85) (PS4). PPSA99203 calls it once beside `sceKernelAvailableFlexibleMemorySize` while starting its GS renderer and never reads the result ([GS.cpp](https://github.com/Swordpdf/PS5SX2/blob/b4cf55e87c34/pcsx2/GS/GS.cpp#L233-L247)) -- [sceKernelIsThreadSanitizerEnabled](../../../core/libs/prx/libkernel/System/src/Sanitizer.cpp) (libkernel, NID `LzoM-wVLJDE`) returns 0, as `sceKernelIsAddressSanitizerEnabled` does: a converted title never runs under the thread sanitizer, so a title that checks it skips the [libSceDbgThreadSanitizer](../../../core/libs/prx/libSceDbgThreadSanitizer/Export.cpp) annotation calls, which throw. PPSA23226's bundled `libSceJobManager_nosubmission.prx` makes those calls only when it reports nonzero +- [sceKernelInternalMemoryGetAvailableSize](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - internal pool allocation and statistics are not implemented, so a non-null query throws without changing its output. Native null output returns -1; an absent pool returns -2, a failed statistics query returns -3, and success writes total pool bytes minus used bytes. +- [sceKernelIsThreadSanitizerEnabled](../../../core/libs/prx/libkernel/System/src/Sanitizer.cpp) - the native zero-argument query returns bit 3 of the process sanitizer flags loaded from sysctl `{1, 14, 41}`. The host always reports disabled because guest sanitizer activation is not modeled. ### Unknown function info +- [sceCoredumpRegisterCoredumpHandler, sceCoredumpUnregisterCoredumpHandler](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) retain registration metadata but do not create the native report thread or deliver callbacks. Native registration-state precedence, stack bounds and SDK capture are recovered; thread-creation failures and active-report synchronization remain unimplemented. + - [sceKernelError](../../../core/libs/prx/libkernel/System/src/Error.cpp) follows [shadPS4's kernel implementation](https://github.com/shadps4-emu/shadPS4/blob/23c41333822fe8eca4c609b43119180af63ae4ac/src/core/libraries/kernel/kernel.cpp): zero returns zero; other guest POSIX error values receive the `0x80020000` bias without modifying errno. Arithmetic is defined modulo 32 bits to avoid signed overflow for inputs outside the errno domain. Console behavior for those inputs and PS5 equivalence have not been measured. - [physhm_open, physhm_unlink](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel, NIDs `n371J5cP+uo` and `AUqJNkobQ1c`) - declared as throwing stubs: the PS5 physical shared memory API is not documented, and [shadPS4's module generator](https://github.com/shadps4-emu/moduleGenerator/blob/main/libraries/kernel.cpp) also declares both with no arguments and returns `ORBIS_OK`, so the signature, the handle a success would return and the console's behaviour are unmeasured. The no-argument `int` signature follows shadPS4. Forever Launcher (PPSA99030) imports both and stops at load on `physhm_open`, and the relinker resolves no call site for either in that title, so no handle is fabricated and both throw - [getlogin_r](../../../core/libs/prx/libkernel/System/src/Login.cpp) (libkernel) returns the host effective account name: `GetUserNameW` as UTF-8 on Windows and `getpwuid_r(geteuid())` on POSIX. A valid account returns a name even without a login session; host `getlogin_r`, `USER` and `LOGNAME` are not consulted. The console's login name and behaviour without a session are unknown. Account lookup or encoding failures, a missing or empty account name, and POSIX account records exceeding the 16 KiB lookup buffer throw; there is no replacement name or retry. A short guest buffer returns `ERANGE` without modifying it, and a null output returns `EFAULT`. +- [getlogin](../../../core/libs/prx/libkernel/System/src/Getlogin.cpp) (libkernel) follows FreeBSD 11 `lib/libc/gen/getlogin.c` and returns a pointer to one static 33-byte buffer filled by `getlogin_r`; on an error it sets `errno` to `getlogin_r`'s code and returns NULL. These semantics are not checked against a PS5. It exports the test seam `LoginNameForTest_nid_no_patch`, which replaces the host account name for tests. - [sceKernelGetSystemSwVersion](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports baseline system software version `0x01000000` ("01.000.000") for ProsperoStore (PPSA99000); the console's firmware version is not modeled. Measured on PS5 firmware 13.42: record values 0, 24, 39, 40, 41, 48 and 64 in the leading 64-bit field are accepted and preserved, the 28-byte string at offset 8 and packed version at 0x24 are filled, and bytes from 0x28 onward remain untouched. This API reports "13.590.001" / `0x13590001` in that environment, while the Prospero variant reports "13.420.000" / `0x13420000`; their values must not be assumed identical. A null pointer still returns `SCE_KERNEL_ERROR_EFAULT`, which was not measured; other firmware versions and record values remain unverified. - [kern.rng_pseudo](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` draws a new block of 64 bytes from the host's `std::random_device` for each read (no new value) that passes a buffer and a length, and sizes its answers as a payload process on a PS5 (firmware 12.70) sees it: a size query reports 64; a buffer of 64 bytes or more gets 64 bytes and a length of 64 (tried up to 65536); a smaller buffer gets as many bytes as its length, which is left as given, and a null length gets none, both with -1 and `ENOMEM`; a null buffer with a null length returns 0. `errno` is kept when the call succeeds. A write returns -1 with `EPERM`, which was not tried on the console. The only caller seen is mbedTLS's `mbedtls_hardware_poll` in the catalog's ProsperoLight boilerplate, which asks for 64-byte blocks and fails if fewer come back. The console's generator is unknown, and a title process was not measured. On the console `sysctlnametomib` gives MIB {1, 50} for the name, and `sysctl` with it answers reads of 0, 1, 63, 64, 65 and 128 bytes, a size query, a null length and a null buffer with a null length as the name does; here `sysctlnametomib` maps the name to {1, 50} and `sysctl` {1, 50} answers as the name does; a write through the MIB was not measured and throws - [sysconf](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `_SC_CLK_TCK` returns 128, FreeBSD's `CLK_TCK` as its [`sysconf`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L100-L101) returns it ([`time.h`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/time.h#L48-L54)); the console's value is not checked, and no `times` reports ticks at that rate. CPython's `posix` module stops its import with "cannot read ticks_per_second" when the value is below 1, as the Python in PS5MC (PPSA99420) does. `_SC_ARG_MAX` returns 262144 (FreeBSD's `NCARGS`), `_SC_OPEN_MAX` returns the `RLIMIT_NOFILE` soft limit via `getrlimit` ([`sysconf.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L106-L115)), and `_SC_IOV_MAX` returns 1024 (FreeBSD's `UIO_MAXIOV`). Names other than `_SC_ARG_MAX`, `_SC_CLK_TCK`, `_SC_OPEN_MAX`, `_SC_PAGESIZE`, `_SC_IOV_MAX`, `_SC_NPROCESSORS_CONF`, `_SC_NPROCESSORS_ONLN` and `_SC_PHYS_PAGES` fail with `EINVAL` @@ -23,73 +28,81 @@ - [kern.sdk_version](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` reports the system software version as the 4-byte value `0x01000000` (1.00), the value `sceKernelGetSystemSwVersion` reports; a write returns -1 with `EPERM`. The major version is byte 3 and the minor version byte 2, in hex digits that read as decimal (13.60 is `0x1360xxxx`), as [Capy Mobile](https://github.com/khalifa007/CapyMobile/blob/2758c016/src/ps5/platform.c#L64-L71) (PPSA92280), [remote_lua_loader](https://github.com/n0llptr/remote_lua_loader/blob/7c2840e475/savedata/misc.lua#L195-L209) and the [lapse payload](https://github.com/NetflixNHack/Netflix-N-Hack/blob/605eb93ba5/PS4/payloads/lapse.js#L436-L450) read it. Capy Mobile only displays it. The console's firmware version is not modeled and the low two bytes are unknown - [kern.ostype, kern.osrelease, kern.version, kern.hostname, hw.machine](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctl` (1.1, 1.2, 1.4, 1.10, 6.1), `sysctlbyname` and `sysctlnametomib` serve the names `uname` reads as read-only strings: "FreeBSD", "11.4-RELEASE" and "FreeBSD 11.4-RELEASE\n", the values FreeBSD 11.4-RELEASE builds without source control information ([newvers.sh](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/conf/newvers.sh#L45-L52), [L293-L294](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/conf/newvers.sh#L293-L294)), the host name `gethostname` reports ("AnyPS5") and "amd64"; a short buffer returns `ENOMEM` and a write `EPERM`. The console's strings are not measured - [kern.osreldate](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - reports MIB `{CTL_KERN, KERN_OSRELDATE}` as `1104001`, FreeBSD 11.4's `__FreeBSD_version` in `sys/sys/param.h`; this is consistent with `kern.osrelease` and `kern.version` above, but the value reported by a PS5 title has not been measured. +- [kern.boottime](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctl` {`CTL_KERN`, `KERN_BOOTTIME`} and `sysctlbyname` report a read-only 16-byte `struct timeval`, as FreeBSD 11.4's [`sysctl_kern_boottime`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_tc.c#L144-L163) does: the guest's `CLOCK_REALTIME` minus its `CLOCK_UPTIME`, taken at the first read and kept, so a later step of the host clock does not move it as FreeBSD's `tc_setclock` would. The value a PS5 title gets has not been measured. - [sceKernelGetProsperoSystemSwVersion](../../../core/libs/prx/libkernel/System/src/SoftwareVersion.cpp) (libkernel) reports system software version `0x01000000` ("01.000.000"); the console's firmware version is not modeled. The 0x28-byte block (a 64-bit size at 0, the version string at 8 and the packed version at 0x24) follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Kernel/KernelSystem.cs), which Prospero Multi Tools (PPSA99110) and Prospero Explorer (PPSA99109) are built with and which sets the size to 40, and the same fields in [etaHEN](https://github.com/etaHEN/etaHEN/blob/main/Source%20Code/daemon/include/ipc.hpp) and [shsrv](https://github.com/ps5-payload-dev/shsrv/blob/master/sh.c). A size other than 0x28 returns `SCE_KERNEL_ERROR_EINVAL` without writing; whether the console checks the size is unverified, since etaHEN and shsrv declare a 0x30-byte block and do not set it - [sceKernelGetAllowedSdkVersionOnSystem](../../../core/libs/prx/libkernel/System/src/SoftwareVersion.cpp) (libkernel) reports the system software version with the 12 patch bits set, `0x01000fff`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/98fd0689/src/core/libraries/kernel/kernel.cpp) does on the PS4 and as [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Kernel/KernelSystem.cs) describes it, which Prospero Explorer (PPSA99109) reads it through; a null pointer returns `SCE_KERNEL_ERROR_EINVAL`. The console's value and its errors are unverified - [sceKernelConvertUtcToLocaltime, sceKernelConvertLocaltimeToUtc](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel) convert with the host time zone at the given instant, as libc `localtime` does, instead of the console's time zone setting. `west_sec` holds the standard-time offset in seconds east of UTC, despite its name, and `dst_sec` the daylight saving seconds in effect. This follows PPSA99203 ([PS5SX2](https://github.com/Swordpdf/PS5SX2/blob/b4cf55e87c34e8a2a1cd9302df4373e5a94d28f4/ps5/frontend/fe_ps5.cpp#L54-L63)), whose author saw a console write 7200 and 3600 into the 16-byte block; which zone that console was set to is not recorded, so whether `west_sec` includes daylight saving is not settled. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/23c41333/src/core/libraries/kernel/time.cpp) also stores seconds east. PPSA99203 declares the last argument of `sceKernelConvertUtcToLocaltime` as `uint64_t*`; 32 bits are written. On Windows the standard offset and the daylight saving amount are the C runtime's current `_get_timezone` and `_get_dstbias` for every instant, as libc `localtime_s` uses them, so instants under a zone's earlier rules get today's offsets; a `TZ` variable does not set `_dstbias`, which keeps the value an earlier `_tzset` read from the system (0 when the system zone has no daylight saving), and summer time then adds nothing. In a repeated or skipped local hour, `sceKernelConvertLocaltimeToUtc` takes the offset in effect one standard offset earlier. Its last argument receives the zone's daylight saving amount whether or not it is in effect, found by sampling the host zone over the year around the result, while `dst_sec` is the amount in effect: the libc `mktime` that PPSA99109 bundles (text 0x70ebb-0x70ee5) subtracts that amount when `tm_isdst` is set outside summer time. That `mktime` passes `tm_isdst` as the second argument, which is ignored, so it does not choose the offset in a repeated hour. `sceKernelGettimezone` and the `gettimeofday` time zone still report no offset and no daylight saving. A host time the C runtime cannot convert throws -- [getuid, geteuid, getgid, getegid and issetugid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) report the root identity (0) and a process that never changed credentials (`issetugid` 0), as a jailbroken process has; the host user and group are not exposed to the guest. The values a title sees on a console are unverified +- [getuid, geteuid, getgid, getegid, getresuid, getresgid and issetugid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) report the root identity (0) and a process that never changed credentials (`issetugid` 0), as a jailbroken process has; the host user and group are not exposed to the guest. `getresuid` and `getresgid` follow FreeBSD 11.4's `sys_getresuid` and `sys_getresgid`, writing 0 to non-null pointers and leaving errno untouched on success. The values a title sees on a console are unverified - [sceKernelGetAppInfo](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - only `app_id` and the title ID (offset 0x10, up to 9 characters plus a terminator) are written; callers declare the structure with different sizes, so the rest is left alone. `app_id` is 1, the running title's id in libSceSystemService, which is a guess, not something measured. The error codes follow shadPS4: `EPERM` for another process, and 0 without writing anything for a null pointer. - [AMPR object layout and compact gathers](../../../core/libs/prx/libkernel/Apr/include/AprCommandBuffer.hpp) - the 24-byte object and offsets (type 0, cursor 4, count 8, capacity 12, pointer 16) follow [ampr_emu assertions](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr_emu_command_buffer_types.h#L138-L143) and its [C declaration](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr.h#L41-L47). AnyPS5 represents the 32-bit type word as low 16-bit type and high 16-bit recording flags. Command encoding is AnyPS5 internal: opcode 29 and three 32-bit words encode the high offset byte, length minus one and low offset word. The 12-byte size reduces HFW's 1 MiB buffer pressure; it is not a claim about console opcode encoding. Existing 40-bit offsets and lengths 1 through 2^32 are retained; console behavior outside those limits is unverified. - [APR resolve ForEach result](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - `sceKernelAprResolveFilepathsToIdsAndFileSizesForEach` returns the number of paths that resolved, with the per-path status in `results` and the failed ids/sizes set to the invalid id and 0. The [ampr_emu](https://github.com/drakmor/ampr_emu) bridge passes the count of its resolver through and does not show what is counted; counting successes is not confirmed on a title. The guest errno of the failing non-`ForEach` resolves is set through `__error_nid_postfix` - [APR reads past the end of a file](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - a read that reaches the current end of the file on disk copies the bytes the file has and fills the rest of the destination with zeros; a read that comes back short before that end still throws. PPSA18605 resolves its settings file once, rewrites it one byte shorter and reads it again with the size that resolve returned. The console's behaviour for a read past the end of a file is unverified: it may zero-fill, leave the destination untouched or fail the command - [AMPR counter access](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and checks of `WaitOnCounter_04_00`, `WriteCounter_04_00`, `ConstructNop` and `ConstructMarker` taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, which hands counter commands to the console and does not show their effect. The counters are taken as 32 bits each: the 8-byte access of counter n spans n (low) and n + 1 (high), the 2- and 1-byte accesses select a half or a byte of the counter by its offset, waits compare at the access width (signed and wrapped compares included), the AND mask applies to both sides and the atomic writes wrap within the access. The `ConstructNop` type is not recorded - [APR gather and scatter reads](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures, argument checks and the read cursor taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: each read leaves the file, the next file offset and the next destination; a gather reads that file at a new offset into the next destination, a scatter continues in the file into a new destination, a gather-scatter takes both. Recording one needs a read file since the last `ResetGatherScatterState`; like ampr_emu, `sceAmprCommandBufferReset` keeps that state, and a gather or scatter with no read before it in the submitted buffer throws instead of returning `SCE_AMPR_ERROR_APR_INVALIDGATHERSCATTERSTATE` -- [AMM map, map direct and unmap](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and the protection mask (CPU, GPU, AMPR and ACP read and write) taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, which hands the remaining checks and the mapping to the console kernel; the mapper model taken from [PS5PCEM](https://github.com/iStark/PS5PCEM): `sceAmprAmmGiveDirectMemory` usage 1 gives the allocated direct memory to the pool plain maps take pages from, usage 0 only allocates it. Not confirmed on a title. The AMM virtual address ranges are a 256 GiB range (PPSA02015 places its heaps 64 GiB above the start of the range, so 32 GiB is too small, and reached 84 GiB above it in its first 159 s; KytyPS5 reports 512 GiB, but every 64 GiB-aligned 544 GiB window in the guest arena covers 448 to 608 GiB, and on Windows one host thread stack there is enough to block it) and a 32 GiB multimap range reserved on first use at the top of the guest arena with the start on a 64 GiB boundary (PPSA02015 tells its pool pointers from its heap pointers by address bits 28 to 35, which needs such a start for its layout), above the addresses that maps without `MAP_FIXED` are given lowest first and that titles derive their next hints from; the console's addresses are not known (ampr_emu takes them from `sceKernelMapperGetParam`), and a reservation that cannot be made there throws. Maps outside them throw. Addresses, sizes and direct offsets must be 16 KiB aligned. The memory type and the GPU mask are ignored, AMPR and ACP access maps as CPU access, unmapped pages go back to the pool and stay reserved. Submission runs the buffer before returning, the submit result is always 0 and a failing command throws; the AMM measures return errors sign-extended, as ampr_emu does. A map returns its target to the reservation before releasing the old pages, so pool exhaustion leaves that target reserved; mapping failure also restores the reservation before returning newly taken pages to the pool. The console's map failure and rollback behavior is unverified. `sceAmprAmmGetVirtualAddressRanges` throws for a null output +- [AMM map, map direct and unmap](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and the protection mask (CPU, GPU, AMPR and ACP read and write) taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, which hands the remaining checks and the mapping to the console kernel; the mapper model taken from [PS5PCEM](https://github.com/iStark/PS5PCEM): `sceAmprAmmGiveDirectMemory` usage 1 gives the allocated direct memory to the pool plain maps take pages from, usage 0 only allocates it. Not confirmed on a title. The AMM virtual address ranges are a 256 GiB range (PPSA02015 places its heaps 64 GiB above the start of the range, so 32 GiB is too small, and reached 84 GiB above it in its first 159 s; KytyPS5 reports 512 GiB, but every 64 GiB-aligned 544 GiB window in the guest arena covers 448 to 608 GiB, and on Windows one host thread stack there is enough to block it) and a 32 GiB multimap range reserved on first use at the top of the guest arena with the start on a 64 GiB boundary (PPSA02015 tells its pool pointers from its heap pointers by address bits 28 to 35, which needs such a start for its layout), above the addresses that maps without `MAP_FIXED` are given lowest first and that titles derive their next hints from; the console's addresses are not known (ampr_emu takes them from `sceKernelMapperGetParam`), and a reservation that cannot be made there throws. Maps outside them throw. Addresses, sizes and direct offsets must be 16 KiB aligned. The memory type and the GPU mask are ignored, AMPR and ACP access maps as CPU access, unmapped pages go back to the pool and stay reserved. A buffer may run after submission returns, the submit result is always 0 and a failing command throws; the AMM measures return errors sign-extended, as ampr_emu does. A map returns its target to the reservation before releasing the old pages, so pool exhaustion leaves that target reserved; mapping failure also restores the reservation before returning newly taken pages to the pool. The console's map failure and rollback behavior is unverified. `sceAmprAmmGetVirtualAddressRanges` throws for a null output - [AMM remap, multimap and protection changes](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and the protection and mask checks taken from ampr_emu; the effects are inferred from the names, not confirmed on a title. A remap moves every page of a fully mapped range to the new address with the given protection and leaves the old range reserved; a multimap maps the same pages at a second address, anywhere in the AMM ranges, and a pool page goes back to the pool once its last mapping is unmapped; a multimap onto its own source range throws. A protection change sets the masked bits on every page of a fully mapped range; the memory type is recorded and ignored. A command on a range that is not fully mapped throws - [AMM PRT ranges](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures, checks and their order taken from ampr_emu, where `MapAsPrt` is a map with the PRT flag and protection 0 and `AllocatePaForPrt` a memory type and protection change with mask 1019, both checking the buffer memory after its size; the effects are inferred from the names, not confirmed on a title. Unbacked PRT pages are zero-filled pages with GPU read access, so CPU reads see zeros instead of faulting and GPU writes fault instead of being dropped. `AllocatePaForPrt` backs each unbacked page with a pool page and sets the type and protection of backed ones; `RemapIntoPrt` moves a fully mapped range into a PRT range, its opcode argument (1011 when 0, as in ampr_emu) is recorded and ignored; `UnmapToPrt` returns the pages to the pool and zero-fills them again. A plain map, unmap or remap over a PRT range ends it; `AllocatePaForPrt`, `RemapIntoPrt` or `UnmapToPrt` outside a PRT range throws - [AMM kernel write commands](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - signatures of `sceKernelWriteModifyMtypeProtectCommand` and `sceKernelWriteRemapIntoPrtCommand` taken from [ampr_emu's libkernel hook targets](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr_libkernel_hook_targets.h), which forward to the real kernel; the measure mode (`dst` null reports the size, `outSize` null skips the report) follows ampr_emu's measure-then-record calls, not confirmed on a title. The range, protection and mask checks mirror the `libSceAmpr` siblings, and the recorded bytes match them exactly so the submitted buffer executes on the existing paths - [sceCoredumpWriteUserString](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel, NID `32KQRUK13kI`) - `(uint32_t, const char* text, int)` follows the console's `libkernel.sprx` (SDK 4.5x build of `J03950001`) at 0x13050: a null `text` returns `0x81180000`; otherwise the three arguments go to syscall 601 (command 10), whose `EPERM` becomes `SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER` (0x81180003), `EINVAL` `0x81180000`, and a success returns the value the kernel wrote back. Measured on a PS5 (firmware 13.60) from a homebrew payload with no core dump handler: the call succeeds with 0, the kernel rejects a first argument above 15 or a third outside 0 to 256 with `0x81180000`, and a 2047-character text is accepted. Those checks are reproduced and anything else returns 0; nothing is recorded, as there is no dump, and what the two numbers mean is unknown. PPSA16064 imports it - [sceCoredumpConfigDumpMode](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel, NID `gzLt9Qrauk0`) - `(int mode)` follows the same `libkernel.sprx` at 0x134D0: a mode other than 1 returns `0x81180000`; mode 1 issues syscall 601 (command 16, arguments 0 and 1) and returns 0 whether or not it fails, since only a raw return of 22 would map to `0x81180000`. No dump is produced here, so mode 1 returns 0 without the syscall. PPSA16833 imports it - [sceCoredumpGetStopInfoGpu_Agc](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel, NID `Jrs7UUkGOFo`) - `(void* info, size_t size)` follows the same `libkernel.sprx` at 0x137E0: a null `info` returns `0x81180000`; syscall 601 (command 21) failing with `EPERM` returns `NOT_IN_COREDUMP_HANDLER`; otherwise two `libSceAgcDriver` imports (NIDs `+NIAY3DWpOw` and `LgctzJSGy7s`, names unknown) decide between `0x81180005` and, for a `size` of 8, storing the second one's 64-bit result and returning 0, with any other size returning `0x81180000`. The handler is never invoked here, so a non-null `info` returns `NOT_IN_COREDUMP_HANDLER` without writing, assuming `EPERM` outside the handler as above; the AGC branch is not reached -- [sceKernelInternalMemoryGetModuleSegmentInfo](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - the `(InternalSegmentInfo* result)` signature, the 16-byte `{address, size}` result and the choice of the main program's first segment follow [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_libkernel.pas#L343-L355) only, which fills it through its `sceKernelGetLibkernelTextLocation` from the first segment of `eboot.bin`; whether the console reports that segment, the text of libkernel itself or the calling module's is unknown. The segment is the first one `sceKernelGetModuleInfo` reports for the main program, and 0 is returned. A null result throws. 44 audited titles that bundle their own `libc.prx` import it; no call has been observed +- [sceKernelInternalMemoryGetModuleSegmentInfo](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - returns the main program's first segment, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_libkernel.pas#L343-L355) does through its `sceKernelGetLibkernelTextLocation`. In a payload process on a PS5 (firmware 13.60) both returned 0 with address and size 0, and a null result faulted (here it throws); what a title process gets is not known. 44 audited titles that bundle their own `libc.prx` import it; no call has been observed - [sceKernelGetModuleInfoForUnwind](../../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - on Linux the tables come from `sceKernelGetModuleInfoFromAddr`, whose `eh_frame_size` includes the zero terminator, as measured on firmware 13.60 (reported in [#2419](https://github.com/boykopovar/AnyPS5/issues/2419), not reproduced here); on Windows it is assumed to stop before the terminator. LLVM libunwind's `CFI_Parser::findFDE` stops at the terminator or the length, whichever comes first, so it finds the same FDEs either way. Only the main executable and `.guest.prx` images report tables; other host images report none, as on Windows - [sceKernelGetModuleInfoForUnwind](../../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - on Windows the `.ehmeta` tables and the image file name are cached per allocation base and dropped after a loader free (`dlclose`, `sceKernelStopUnloadModule`, the thread atexit release) when the module is no longer loaded. A table changed in place, or an image freed by anything other than the loader, is not noticed. An address inside an image of the Windows loader's module list whose tables are cached is resolved with `RtlPcToFileHeader` and no `VirtualQuery`; other addresses, and images without tables, still use `VirtualQuery`. The `.eh_frame` walk that finds its end is done once per image instead of once per call -- [sceKernelGetModuleInfoFromAddr](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - the 0x1A8-byte info layout follows the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation; a title's `libc.prx` reads only `id` (PPSA14632). The meaning of `flags` is unknown; only 2 is accepted. The input `st_size` is ignored, because PPSA21703's `libc.prx` passes an uninitialized 0x1A8-byte buffer; the full size is written back. The id is the `sceKernelLoadStartModule` handle of an image opened through the loader and otherwise a stable id from the same range. The name is the host file name without `.guest.prx`. An image with more than 4 load segments (the converted executable has 7) reports its first 4. `tls_offset` is 0 and `ref_count` is 1, as the host loader exposes neither. On Windows the module is the PE image holding the address: a relinked image reports its `.elf` sections as segments and the eh_frame_hdr recorded in `.ehmeta`, any other image its first 4 sections with memory access and the `.ehfram` section without an eh_frame_hdr; the TLS fields and `init_proc_addr`/`fini_proc_addr` are 0 +- [sceKernelGetModuleInfoFromAddr](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - as in a payload process on a PS5 (firmware 13.60), flags 1 finds any module and flags 2 refuses the system ones (`ESRCH`), here the host libraries; other flags clear the 0x1A8 bytes and return `EINVAL`, any failure clears them, `st_size` is left 0, and a null info returns `EFAULT` (with other flags the console faulted; here it throws). Flags 2 finding a title module was not measured. The layout follows [shadPS4](https://github.com/shadps4-emu/shadPS4). The id is the loader handle or a stable id from that range, and the name is the host file name without `.guest.prx`, where the console keeps `.sprx`. Only the first 4 load segments are reported. `tls_offset` is 0 and `ref_count` 1 (the console gave 5 for system modules). On Windows a relinked image reports its `.elf` sections and the `.ehmeta` eh_frame_hdr; the TLS fields and init/fini are 0 - [sceKernelGetModuleList](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - lists the main program and every loaded `.prx` image in load order; host runtime libraries are left out. The handles are the ids `sceKernelGetModuleInfoFromAddr` reports, not the console's. The `(int32_t* handles, size_t count, size_t* actual)` signature and returning `ENOMEM` without writing `actual` after filling the first `count` handles when more images are loaded follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/kernel/process.cpp#L244-L264); whether the console writes any handles in that case is unknown - [sceKernelGetModuleInfo](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - the 0x160-byte layout (`st_size`, `name[256]`, 4 segments, `segment_count`, `fingerprint[20]`) follows [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/sys/sys_types.pas#L319-L337) and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/module.h#L15-L31). Only that `st_size` is accepted, as in shadPS4; other sizes return `EINVAL`, a null pointer `EFAULT` and a handle `sceKernelGetModuleList` does not list `ESRCH`. Name and segments are the ones `sceKernelGetModuleInfoFromAddr` reports, on Windows from the same PE sections, so `prot` uses the read/write/execute bits 1/2/4, while fpPS4 and shadPS4 store the ELF `p_flags`. The fingerprint is left zero, as the host image does not carry it - [sceKernelGetModuleList2 and sceKernelGetModuleInfo2](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - the title-module variants follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/process.cpp): `sceKernelGetModuleList2` lists what `sceKernelGetModuleList` lists without system libraries, and `sceKernelGetModuleInfo2` answers `EPERM` for a system library after the `EFAULT`, `EINVAL` and `ESRCH` checks of `sceKernelGetModuleInfo`. A system library here is a loaded `.prx` image that is not a relinked title module (`.guest.prx`); the main program and the title's own modules are listed. Not verified on the console. PPSA34063 imports both and fails to load without them - [sceKernelGetLibkernelTextLocation](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - the `(void** address, std::size_t* size)` signature and the choice of the main program's first segment follow [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_libkernel.pas#L338-L348); whether the console reports that segment, the text of libkernel itself or the calling module's is unverified. The segment is the first one `sceKernelGetModuleInfo` reports for the main program, and 0 is returned. A null argument throws. -- [sceKernelReleaseFlexibleMemory](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - unmaps the range like sceKernelMunmap, as fpPS4 and prosper do; whether the console keeps the virtual range reserved after releasing its pages is unverified +- [sceKernelReleaseFlexibleMemory](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - on a PS5 (firmware 13.60) the released range stays mapped, with its protection and contents (64 MiB checked after 0.5 s), and a fixed no-overwrite map over it fails with `ENOMEM`, as here. What it does to the flexible budget was not measured, because a payload process has none (`sceKernelAvailableFlexibleMemorySize` did not change on map or release); here the released bytes return to the budget while the pages stay mapped, and a later map over them charges them again. The host pages behind a released range stay committed until `sceKernelMunmap`, as `madvise` keeps them for every advice; freeing them would lose the contents the console still returns. - [sceKernelAvailableFlexibleMemorySize](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - the segments of the title's executable and of its `.guest.prx` modules count against the configured flexible-memory budget (448 MiB by default) while loaded, for this size and for flexible maps. A title may declare a larger budget through `kernel.flexibleMemorySize` in `param.json`; this does not reduce the 13.5 GiB direct-memory size. Rejection of zero or a value not aligned to 16 KiB has not been measured on a console. This segment-accounting model follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/module.cpp#L165), which maps every segment of a module outside its system module directory as flexible memory on the PS4. [Counted](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) are the segments the title's ELF gives permissions, each rounded to 16 KiB pages; not counted are segments without permissions, the relinker's own blocks (on Linux both are the read/write/execute `PT_LOAD`s, plus the header block at file offset 0; on Windows everything outside the `.elf` sections) and the host system libraries. The Linux image carries no marker for the relinker's blocks, so they are told apart by those flags and that offset only: a title segment that is writable and executable, or starts at file offset 0, is not counted. A module whose segments exceed what is left still loads, and the available size then reads 0; shadPS4 fails the segment map instead ([memory.cpp](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/memory.cpp#L519-L524) returns `EINVAL`, [module.cpp](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/module.cpp#L168-L169) asserts). Which segments the PS5 charges, its rounding, whether system modules use the title's budget and when an unload releases the charge are not measured. PPSA15508 subtracts the reported size and a 10 MiB module allowance from its own 448 MiB budget at start-up and stops when the result exceeds its 146 MiB code and data limit ([#2883](https://github.com/boykopovar/AnyPS5/issues/2883)) -- [sceKernelAvailableDirectMemorySize](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - how the console handles invalid arguments is not measured. A negative `search_start`, a `search_end` at or below `search_start` and an alignment that overflows when rounded up to 16 KiB return `EAGAIN` (`0x80020023`); bounds that are not multiples of 16 KiB are rounded inward, the alignment is raised to a multiple of 16 KiB and need not be a power of two, and a `search_end` past the pool is cut to it. fpPS4's HLE wrapper on its trunk branch refuses all of these with `EINVAL` ([ps4_map_mm.pas](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_map_mm.pas#L558-L570)) +- [sceKernelAvailableDirectMemorySize](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - its arguments follow a payload process on a PS5 (firmware 13.60) ([probes](https://gist.github.com/orezraey/42a06a7ea0d9fb57ac9c06eaf9e6756c)). One answer is not modelled: in a process that had not yet called `sceKernelGetDirectMemorySize` or otherwise touched direct memory, the console returned `EBADF` (`0x80020009`); here the call always searches the pool. Titles open the direct-memory device during start-up, so they are not expected to see it +- [sceKernelReleaseDirectMemory](../../../core/libs/prx/libkernel/DirectMemory/MemoryPool.cpp) (libkernel) - releases the allocated pages of the range and skips pages that are not allocated, returning 0, while `sceKernelCheckedReleaseDirectMemory` returns `ENOENT` for them, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c206f35a546230d9c9808b823c0f36f5850cd4af/src/core/memory.cpp#L295-L313) does; not measured on a console. Absolum (PPSA28311) releases such a range when it disposes of its video logos - [Memory protection bits 0x100 and 0x200](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - taken as audio coprocessor (ACP) read and write, as in the [KytyPS5](https://github.com/KytyPS5/KytyPS5) and [etaHEN](https://github.com/etaHEN/etaHEN) headers; the coprocessor work runs on the host CPU, so they grant host read and read/write access like the GPU bits - [Protecting a host thread's stack](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - on Windows `sceKernelIsStack` reports a host thread's whole stack reservation, which Windows commits on demand from the top down behind a guard page. A protect over that range changes only the pages above the guard; the guard page and the reserved pages below it are left as they are, so the stack still grows and an overflow still raises a stack overflow. A protect that starts at the bottom of the reservation and stops short of the guard page, as HotSpot makes its stack guard zones, is committed and protected. Windows moves the guard page down by committing the pages below it as read/write guard pages, whether they were reserved or committed, so those pages come in read/write as the stack grows to within one guard region of them (three pages on GitHub's runners). A protection other than read/write on them, `PROT_NONE` included, does not hold once the stack is that close, where on the console every page of the reported range takes the protection. Holding it would mean removing the thread's guard page and keeping the stack limit in its TEB by hand. On Linux the reported range of the main thread is mostly unmapped; the unmapped pages from the bottom of a protect up to the top of the stack are mapped read/write in place, then the protect is applied, so the stack never has to grow down to the protected pages, which Linux refuses within its stack guard gap (1 MiB by default) above a readable mapping. The pages below a protect stay unmapped and out of the stack's reach, where on the console the stack goes on below them - [Foreign guest module image protection on Windows](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - `sceKernelMprotect` on Windows allows protection mutations on mapped PE images containing a `.elf0` section (as relinked guest modules produce, such as NVIDIA Real-time Denoisers `libNRD.prx` bundled in Dying Light 2: Stay Human PPSA02262), registering their sections into `GuestAllocations`. Host libraries and system DLLs (such as `libkernel.prx` and `kernel32.dll`) without a `.elf0` section continue to be rejected - [_umtx_op](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the mutex, condition variable, read-write lock, legacy lock and `SEM2` operations throw, and so does a misaligned object. Implemented as FreeBSD 11's [kern_umtx.c](https://github.com/freebsd/freebsd-src/blob/stable/11/sys/kern/kern_umtx.c): `UMTX_OP_WAIT`, `UMTX_OP_WAKE`, `UMTX_OP_WAIT_UINT`, `UMTX_OP_WAIT_UINT_PRIVATE`, `UMTX_OP_WAKE_PRIVATE`, `UMTX_OP_NWAKE_PRIVATE`, `UMTX_OP_SEM_WAIT` and `UMTX_OP_SEM_WAKE` (the last two built under `COMPAT_FREEBSD9`/`10`). Operations 0 and 1 fail with `EOPNOTSUPP`, as measured on a PS5 (firmware 12.70). The `SEM2` operations, 23 and 24, were measured on the console and returned `0` for a count of 0 where FreeBSD would time out, so they stay unimplemented. +- [sceKernelCreateSema, sceKernelPollSema, sceKernelSignalSema, sceKernelDeleteSema](../../../core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp) (libkernel) - a poll, a signal or a second delete of a deleted semaphore reads freed memory here, where the console returns `0x80020003` (ESRCH) ([measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/kernel.golden.tsv#L17-L19)). A null output pointer to `sceKernelCreateSema`, an attribute above 2, a negative maximum and a negative signal count throw, as they were not measured. `SCE_KERNEL_ERROR_EINVAL` for an initial count above the maximum or negative, a maximum of 0, a null name, a poll of 0 or above the maximum and a signal of 0 is [measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/kernel.golden.tsv#L10-L23). - [sceKernelSyncOnAddressWait](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored - [sceKernelSyncOnAddressWait8 / sceKernelSyncOnAddressWait16 / sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_8/16/32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and the 2- and 8-byte alignment of Wait16 and Wait64 are assumed - [scePthreadRwlockDestroy, pthread_rwlock_destroy](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) - measured on a PS5 (firmware 13.60): destroying a rwlock returns 0 whether it is free or held for reading or writing, and leaves 1 in the handle; every call on that handle then returns `EINVAL` until it is initialized again, and a zero handle is initialized on first use by the `scePthreadRwlock` calls as by the POSIX ones. A rwlock destroyed while held is not freed, so its memory leaks. Destroying a rwlock while another thread is acquiring it is not guarded -- [scePthreadCondWait / scePthreadCondTimedwait](../../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) - before waiting, FreeBSD libthr's [`cond_wait_common`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_cond.c#L1992-L2029) checks ownership through [`_mutex_owned`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_mutex.c#L1166-L1178): an unowned mutex returns `EPERM`, while its destroyed marker returns `EINVAL`. An untouched adaptive static initializer (slot value 1) therefore returns `EPERM`; the test covers this, an uninitialized slot, a mutex owned by another thread, and the destroyed marker for both timed and untimed sce and POSIX waits. A recursive mutex locked several times is released completely for the wait and relocked to the same depth afterwards, as FreeBSD libthr does (`_mutex_cv_unlock`, `_mutex_cv_lock`). These FreeBSD-derived behaviors remain unverified on console hardware. Cancellation follows the [POSIX condition wait contract](https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_cond_clockwait.html): the original mutex and recursive depth are restored before cleanup, and a canceled waiter forwards a wake to the remaining waiters. The host condition state is retained across mutex reacquisition so the guest can destroy and free its condition after notification; cancellation ordering and signal replacement are not measured on a console -- [sceKernelSetEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - multi-waiter satisfaction follows fpPS4 in-order evaluation: registered waiters are evaluated in FIFO order and each satisfied waiter's clear mode is applied immediately to the pattern before evaluating subsequent waiters; this ordering is unverified on PS5 console hardware -- [sceKernelOpenEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - returns the handle of an undeleted event flag this process created with the same name, and `SCE_KERNEL_ERROR_ENOENT` otherwise. Flags owned by other processes, such as the shell's `SceShellCoreUtilAppFocus`, do not exist here, and which attribute makes a flag openable on the console is not modelled. The error code for a missing name and the choice between flags sharing a name are unverified. -- [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioPollRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - behaviour from shadPS4 (one id per request for the Multiple submits, cancel turns any request aborted and id 0 reports processing, wait mode 2 returns once one request completed, poll copies every state without waiting); the 128 requests or ids per batch limit is from SharpProspero, with an unknown error, so larger batches throw; wait modes other than 1 (and) and 2 (or) throw; null pointers, negative counts and invalid ids return the codes the single-request siblings use, checked before any state is written +- [scePthreadMutexTimedlock](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp), [scePthreadRwlockTimedrdlock, scePthreadRwlockTimedwrlock](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) - a timed lock of a mutex the caller holds, or of a rwlock it holds for writing, sleeps until its timeout before returning `ETIMEDOUT` (`0x8002003c`), as FreeBSD 11.4 libthr does ([mutex](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_mutex.c#L878-L928)); whether the console waits is not measured. The prosper oracle measured only the result, for a default mutex and a rwlock with a 1000 us timeout ([kernelx.golden.tsv](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/kernelx.golden.tsv#L16-L47)); the other mutex types and the POSIX timed locks follow the same rule unmeasured. +- [scePthreadCondWait / scePthreadCondTimedwait](../../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) - the mutex's owner is checked before waiting, as in FreeBSD libthr's [`cond_wait_common`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_cond.c#L1992-L2029). Measured in a payload process on a PS5 (firmware 13.60): `EPERM` (`0x80020001`) for an unowned mutex of every type, the adaptive initializer (slot 1), an uninitialized slot and a mutex owned by another thread, `EINVAL` for the destroyed mutex (slot 2), and a recursive mutex is released completely and relocked to its depth. `scePthreadCondDestroy` stores 1, which every condition call refuses with `EINVAL`, and leaves an uninitialized slot alone. Cancellation restores the mutex and depth before cleanup and a canceled waiter forwards a wake, per POSIX; its ordering is not measured +- [sceKernelOpenEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - opens only flags this process created with attribute 0x100. On the console, other processes' 0x100 flags, such as the shell's `SceShellCoreUtilAppFocus`, can be opened too. They do not exist here, so opening them returns `SCE_KERNEL_ERROR_ESRCH`. A null output with the name of an openable flag returns `SCE_KERNEL_ERROR_EFAULT`, the host's answer to the console's fault there, not a measured code. With a plain flag's name it returns `SCE_KERNEL_ERROR_ESRCH`, as measured; with a missing name it also returns `SCE_KERNEL_ERROR_ESRCH`, which was not measured. +- [sceKernelCreateEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - when a call has both an invalid attribute and a name of 32 bytes or more, `SCE_KERNEL_ERROR_EINVAL` is returned, because the attribute checks run before the name length check. The console was only measured with long names and valid attributes (0x20 and 0x120), so which error it returns first is not known. Event flag handles are host addresses here, while the console returns small IDs. +- [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioPollRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - one id per request for the Multiple submits and wait mode 2 returning once one request completed follow shadPS4. Requests run synchronously on submit, so cancel never finds one processing. The console's ids are not the small numbers used here (it returned 33, 0x2021, 0x4021) +- [sceKernelWaitSema, sceKernelPollSema, sceKernelSignalSema, sceKernelCancelSema, sceKernelDeleteSema](../../../core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp) (libkernel) - `SCE_KERNEL_ERROR_ESRCH` for a null handle follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/60c42b925c3d1e00008621b8587819999260ec31/src/core/libraries/kernel/threads/semaphore.cpp#L329-L366) (PS4), unverified on PS5. Handles are pointers, so a non-null handle that is not a live semaphore is not detected; shadPS4 returns `ESRCH` for any unallocated handle. - [scePthreadAttr setters and getters](../../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) (libkernel) - measured on a PS5 (firmware 13.60): a null or uninitialized attribute returns `EINVAL`, as does a null output of the stack size, detach state and scheduling parameter getters; a policy other than 1 and 3, an inheritance other than 0 and 4, or a priority outside 256 to 767 (or a null parameter) returns `ENOTSUP` (`0x8002002D`); a detach state other than 0 and 1 and a stack size below 16384 return `EINVAL`; any affinity mask is accepted. A null attribute pointer to `scePthreadAttrInit` and a null output of `scePthreadAttrGetaffinity` crashed the payload, so they still throw. A new attribute holds a 64 KiB stack size on the console and 1 MiB here, which the host code that runs on guest threads needs. `scePthreadAttrGetschedpolicy` and `scePthreadAttrGetinheritsched` were not measured and follow [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_attr.c#L216-L262); a null output of `scePthreadAttrGetinheritsched` throws, since libthr writes through it unchecked - [scePthreadAttrGet](../../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) and [pthread_attr_get_np](../../../core/libs/prx/libkernel/Pthread/Posix/Attr.cpp) (libkernel) - the returned scheduling policy is unmeasured on a console. Both report the recorded guest policy, following [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_attr.c#L134-L164) - [pthread_getprio](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) is unmeasured on the console. NID `aO+WS2xWuo4` is named in the firmware 3.20 [export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libkernel.c). Its return and errno contract follows FreeBSD 11 [thr_getprio.c](https://github.com/freebsd/freebsd-src/blob/671fb7df97631020c42ce55527dc49fffe0656b7/lib/libthr/thread/thr_getprio.c): return the recorded guest priority, or -1 with `ESRCH` for an unknown or finished thread. The live-thread registry lock protects the read from a concurrent join. This reports guest scheduler state, not the host scheduling priority. +- [pthread_rwlock_reltimedrdlock_np, pthread_rwlock_reltimedwrlock_np](../../../core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp) (libkernel) take `(rwlock, usec)`, a relative timeout in microseconds, and are `scePthreadRwlockTimedrdlock` and `scePthreadRwlockTimedwrlock` with POSIX error numbers, so they return what those return (`ETIMEDOUT` when the time runs out, at once for 0 while another thread holds the lock, and `EINVAL` for a destroyed lock); a zero lock is initialized as by `pthread_rwlock_rdlock`. FreeBSD 11.4 libthr has no such functions; the signature comes only from shadPS4, which registers one function for each NID and its `scePthreadRwlockTimed*` twin ([`rwlock.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/rwlock.cpp#L167-L179), 32-bit `usec`). Unverified on the console - [sceKernelGetAvailableCpumask](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - returns the default thread affinity, 0x1FFF (CPUs 0 to 12), as KytyPS5 does. Titles pin threads within it (PPSA02664 passes 0x1FFB, Hades II PPSA36082 0x3 and 0x3F); whether CPU 13 is available to a title is not confirmed - [sceKernelGetCurrentCpu](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - guest affinities are not applied to host threads, so the host CPU index is mapped into the calling thread's affinity clipped to `sceKernelGetAvailableCpumask` (the available mask when they do not overlap): host CPU n reports the (n modulo the CPU count)-th CPU of that mask, so a thread pinned to one CPU reports that CPU, as a FreeBSD `cpuset` guarantees on the console. Which CPU the console reports for a thread allowed on several is not known; shadPS4 returns 0 - [pthread_attr_setsolosched_np](../../../core/libs/prx/libkernel/Pthread/Posix/Attr.cpp) (libkernel) takes `(attr, int)` and stores the value through `scePthreadAttrSetsolosched`, returning `EINVAL` (22) for a null or uninitialized attribute like the other POSIX attribute setters. Only the name is public (aerolib `2+pVfgiEd7A`); the signature and behaviour follow the SCE twin, which [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/947627d10d4ef4b68ba3c0189aab6c368e70a8d2/src/kernel/pthread.cpp#L2188-L2199) and [sharpemu](https://github.com/sharpemu/sharpemu/blob/a2983692930564815a77f81e74c151268f5b935f/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs#L891-L913) store as an `int` without a range check, and [PS5PCEM](https://github.com/iStark/PS5PCEM/blob/71de7681f06c5db59a1120a3bb554c86780c3f5f/src/hle/libs/kernel_threading.zig#L1396-L1398) implements this alias over it. On a PS5 (firmware 13.60) only 0 and 16 are accepted; every other value returns `ENOTSUP` (45, `0x8002002D` from the SCE call) and keeps the stored one, and the SCE calls return `EINVAL` for a null or uninitialized attribute. PPSA15878 passes 16. Threads created from the attribute are not scheduled differently; what solo scheduling does on the console is unknown. Returnal (PPSA01285 v03.000.005) stopped on it with `what(): pthread_attr_setsolosched_np_nid_postfix not implemented` (#2872, #1155); LEGO Horizon Adventures (PPSA14632, #734) and Palworld (PPSA23126, #2598) import it +- [pthread_main_np and scePthreadMain](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel_sys) - console behaviour is unmeasured. Linux identifies the process initial thread by kernel TID/PID equality, including worker-first loading. Other hosts identify the thread that initialized libkernel; loading it first from a worker can misidentify the initial thread. The pre-initialization -1 state is not exposed. - [sceKernelMapFlexibleMemory / sceKernelMapNamedFlexibleMemoryInternal](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 is accepted and ignored; its meaning is not established. KytyPS5 (`GUEST_MAP_UNKNOWN_8000`) and sharpemu accept it without reading it. Every known caller is an SDK 9 `libc.prx` heap, which passes it with read/write protection and uses the mapping as an older `libc.prx` uses `sceKernelMapNamedFlexibleMemory` with flags 0: through the Internal variant in PPSA21564, through `sceKernelMapFlexibleMemory` in PPSA22520 01.000.011 - [sceKernelMtypeprotect](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - sets the memory type of the direct mappings in the page-rounded range and of the direct memory behind them, as shadPS4 and prosper do (KytyPS5 ignores the type); the type is not validated, flexible pages in the range only get the protection, and a later map of the retyped direct memory reports the type it was allocated with. `sceKernelBatchMap2` operation 4 takes the same path with the entry type - [Map hints inside a reservation](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - `sceKernelMapDirectMemory` and the flexible and anonymous maps place a mapping without `MAP_FIXED` at its hint address when the whole range lies in a range still reserved by `sceKernelReserveVirtualRange`, as if `MAP_FIXED` were set. Taken from PPSA01474, which reserves 64 GiB and then maps its heaps inside it with hints and no `MAP_FIXED`, and uses the hint addresses afterwards; not measured on the console. Hints outside a reservation, or over a range that is partly mapped, stay hints -- [Fixed anonymous mmap](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - `MAP_PRIVATE | MAP_ANON | MAP_FIXED` replaces the requested range through the existing guest mapping machinery and zeroes the new memory. `MAP_EXCL` is checked under the allocation-registry mutation lock, including virtual reservations, and returns `ENOMEM` without changing an occupied range; without `MAP_FIXED` it is `EINVAL`. Lengths are rounded to 16 KiB and fixed addresses must be aligned. The complete rounded range must lie inside the guest arena or, for POSIX fixed anonymous `mmap` on Windows, one current or registered native thread stack; other mapping APIs do not opt into stack replacement. Native stack replacement keeps the reservation, decommits and recommits fresh pages, and rejects foreign allocations, ranges spanning reservations and host `PAGE_GUARD` pages before mutation. Current-stack ranges must end at least 64 KiB below the active host frame; callers replacing another thread's stack must coordinate with that thread. If recommitting fails, the call returns `ENOMEM` with the range still reserved; thread cleanup attempts to recommit it before restoring write access. This follows FreeBSD 11.4 [argument validation and rounding](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_mmap.c#L226-L299), [fixed replacement](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_map.c#L1455-L1464), and [collision errno](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_mmap.c#L1604-L1613). The console behaviour has not been measured. AnyPS5 starts the guest arena at 0x200000000, so the fixed path rejects the low 0x400000 address requested by PICO-8 PPSA99808. The console address-space layout is unmeasured; executable placement outside the host arena is tracked in #5217. Static catalog disassembly finds `0x1012` in PPSA98001/PPSA99808 and `0x5012` in PPSA99808; these titles were not run. +- [Fixed anonymous mmap](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - `MAP_PRIVATE | MAP_ANON | MAP_FIXED` replaces the requested range through the existing guest mapping machinery and zeroes the new memory. `MAP_EXCL` is checked under the allocation-registry mutation lock, including virtual reservations, and returns `ENOMEM` without changing an occupied range; without `MAP_FIXED` it is `EINVAL`. Lengths are rounded to 16 KiB and fixed addresses must be aligned. The complete rounded range must lie inside the guest arena or, for POSIX fixed anonymous `mmap` on Windows, one current or registered native thread stack; other mapping APIs do not opt into stack replacement. Native stack replacement keeps the reservation, decommits and recommits fresh pages, and rejects foreign allocations, ranges spanning reservations and host `PAGE_GUARD` pages before mutation. Current-stack ranges must end at least 64 KiB below the active host frame; callers replacing another thread's stack must coordinate with that thread. If recommitting fails, the call returns `ENOMEM` with the range still reserved; thread cleanup attempts to recommit it before restoring write access. This follows FreeBSD 11.4 [argument validation and rounding](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_mmap.c#L226-L299), [fixed replacement](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_map.c#L1455-L1464), and [collision errno](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_mmap.c#L1604-L1613). The console behaviour has not been measured. Linux and Windows accept addresses from 64 KiB that no host mapping occupies, such as the 0x400000 imported by PICO-8 PPSA99808; macOS keeps the 0x200000000 start. Non-fixed private anonymous mappings keep their hint; null hints still use the 8 GiB area. On Windows the host's `KUSER_SHARED_DATA` always occupies 0x7ffe0000, so Wine's shared user data page (Forever Launcher PPSA99030) cannot be placed there. The console address-space layout is unmeasured; executable placement outside the host arena is tracked in #5217. Static catalog disassembly finds `0x1012` in PPSA98001/PPSA99808 and `0x5012` in PPSA99808; these titles were not run. - [Mapping hints with no room above them](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - a mapping without `MAP_FIXED` whose hint lies above the guest address space, or has no free range at or above it, is placed from the bottom of the guest arena, as FreeBSD 11.4's [`vm_mmap_object`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_mmap.c#L1569) retries through [`vm_map_find_min`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_map.c#L1555-L1584) from its minimum address. FreeBSD's minimum is the end of the largest possible data segment rather than the start of the arena, and the console's address-space layout is not measured. PS5CEMU-HAR (PPSA99360) gives `sceKernelReserveVirtualRange` a hint near its own code, which is a host address here because the relinked executable lies outside the guest arena, so its JIT gets memory more than 2 GiB from its code and does not start -- [_sceKernelRtldThreadAtexitIncrement, _sceKernelRtldThreadAtexitDecrement](../../../core/libs/prx/libkernel/System/src/Rtld.cpp) (libkernel) - the argument is the `dso` address a title's `libc.prx` passes to `__cxa_thread_atexit` (PPSA14632), which fails when Increment returns nonzero. As in the [fpPS4](https://github.com/red-prig/fpPS4) reimplementation, Increment takes a reference on the loaded image containing the address and Decrement releases one; the variants without the leading underscore are assumed to match +- [_sceKernelRtldThreadAtexitIncrement, _sceKernelRtldThreadAtexitDecrement](../../../core/libs/prx/libkernel/System/src/Rtld.cpp) (libkernel) - the argument is the `dso` address a title's `libc.prx` passes to `__cxa_thread_atexit` (PPSA14632), which fails when Increment returns nonzero. As in a payload process on a PS5 (firmware 13.60), Increment returns -1 for an address outside every module (null, stack, heap) and 0 otherwise, and Decrement never fails on an address outside every module or after a matching Increment; the console's Decrement returns nothing, here 0. A Decrement on a module's address without a matching Increment was not measured and throws here. The module's `ref_count` did not change on the console. As in [fpPS4](https://github.com/red-prig/fpPS4), Increment here takes a reference on the image and Decrement releases one. The variants without the leading underscore are not exported by firmware 13.60's `libkernel_sys`; here they match +- [pthread_cleanup_push, pthread_cleanup_pop](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel, NIDs `4ZeZWcMsAV0`, `RVxb0Ssa5t0`) share the caller-record cleanup list with `__pthread_cleanup_push_imp` and `__pthread_cleanup_pop_imp`, following [FreeBSD 11.4 thr_clean.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libthr/thread/thr_clean.c). The firmware body reading reported by [PR8428](https://github.com/boykopovar/AnyPS5/pull/8428) also sets the thread's unwind-disabled flag in the heap form; that flag is not modeled here, and runtime-created guest threads skip guest-frame unwinding in these exit paths. The firmware observation is attributed to that contributor, not independently reproduced. Heap records use C++ `new` here, whereas the FreeBSD reference leaves the cleanup list unchanged if `malloc` fails; console allocation-failure behavior and its interaction with unwinding remain unverified. - [tcgetattr, tcsetattr](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) - every file and pipe descriptor is taken as not a terminal (`ENOTTY`), and a socket gets `EADDRNOTAVAIL` for `AF_INET` and `EOPNOTSUPP` otherwise, as FreeBSD's `in_control` and `pru_control_notsupp` answer a termios ioctl; what descriptors 0 to 2 of a title are on the console, and whether any of them is a terminal there, is unknown - [cfsetspeed](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel, NID `C1MQ6OaXRjo`) - console behavior and module/library versions are unverified. The prototype, uint32 speed and 44-byte termios layout (input speed at 36, output speed at 40) follow the public PS5 payload SDK [termios.h](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/include/freebsd/termios.h) and [sys/_termios.h](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/include/freebsd/sys/_termios.h). The module association follows its libkernel stub table and the NID rehashes to the aerolib name. Both speed fields accept every uint32 value and no flags or control characters change, as [FreeBSD 11.4 termios.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libc/gen/termios.c) does. No terminal I/O is performed. Null pointers throw where the reference dereferences them. -- [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - descriptor 0 is never reported readable and descriptors 1 and 2 are always writable, as for a console title with no input; reads from descriptor 0 still go to the host stdin. `/dev/random` and `/dev/urandom` follow `randomdev_poll` for a seeded device under [`selscan`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_generic.c#L1178-L1236), which asks once with the flags of every set the descriptor is in: in the read set they are readable and reported in no other set, and otherwise they are reported in the write and exception sets they were asked in. A regular file, a directory or a file read from app0.zar is always readable and writable and never in the exception set, as `vop_stdpoll` answers, on every host. Guest sockets and libSceNet sockets are polled. A kqueue is readable while one of its events is triggered and is never writable or in the exception set, as FreeBSD 11.4's [`kqueue_poll`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_event.c#L1900-L1924) answers, on every host. Other descriptors are polled through the host on Linux, while on Windows pipes are checked every millisecond with the readiness test `poll` uses and any other descriptor throws. Dungeons (PPSA16064) selects on descriptor 0, and RetroArch (PPSA99169) selects on its web server's wake-up pipe next to its listening socket -- [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - a descriptor the socket poller reports as unknown that is below `GuestSockets::FirstDescriptor` is polled on the host instead of throwing (on Windows only a pipe, as above); an invalid host descriptor fails with `EBADF`, and mixed host and socket sets are polled in short alternating slices so neither side waits out the full slice alone. A descriptor that stops being a socket, a kqueue or a pipe during the wait fails the call with `EBADF` when it is closed and is selected again with the rest of the timeout when its number was reused, as FreeBSD 11.4's [`kern_select`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_generic.c#L1129-L1140) looks every descriptor up again in each pass. A close that has already removed the descriptor from the guest's kqueue, pipe or file tables but not yet closed its host descriptor is seen by that new look-up as a descriptor of unknown kind, which throws on Windows and is polled through the host on Linux. Wobbly Life (PPSA29413) selects on file descriptors while streaming its bundles +- [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - console standard-stream behavior is unverified. Regular files, directories and archived files at descriptors 0 to 2 use vnode readiness, including host redirection (`< file`, `> log.txt` or `2> log.txt`): they are readable and writable even at EOF. Other standard descriptors reaching the fallback use the console model: descriptor 0 is never reported readable and descriptors 1 and 2 are writable; reads from descriptor 0 still go to host stdin. `/dev/random` and `/dev/urandom` follow `randomdev_poll` for a seeded device under [`selscan`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_generic.c#L1178-L1236), which asks once with the flags of every set the descriptor is in: in the read set they are readable and reported in no other set, and otherwise they are reported in the write and exception sets they were asked in. A regular file, a directory or a file read from app0.zar is always readable and writable and never in the exception set, as `vop_stdpoll` answers, on every host. Guest sockets and libSceNet sockets are polled. A kqueue is readable while one of its events is triggered and is never writable or in the exception set, as FreeBSD 11.4's [`kqueue_poll`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_event.c#L1900-L1924) answers, on every host. Other descriptors are polled through the host on Linux, while on Windows pipes are checked every millisecond with the readiness test `poll` uses and any other descriptor throws. Dungeons (PPSA16064) selects on descriptor 0, and RetroArch (PPSA99169) selects on its web server's wake-up pipe next to its listening socket +- [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - a descriptor the socket poller reports as unknown is polled on the host instead of throwing (on Windows only a pipe, as above); an invalid host descriptor fails with `EBADF`, and mixed host and socket sets are polled in short alternating slices so neither side waits out the full slice alone. A descriptor that stops being a socket, a kqueue or a pipe during the wait fails the call with `EBADF` when it is closed and is selected again with the rest of the timeout when its number was reused, as FreeBSD 11.4's [`kern_select`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_generic.c#L1129-L1140) looks every descriptor up again in each pass. A close that has already removed the descriptor from the guest's kqueue, pipe or file tables but not yet closed its host descriptor is seen by that new look-up as a descriptor of unknown kind, which throws on Windows and is polled through the host on Linux. Wobbly Life (PPSA29413) selects on file descriptors while streaming its bundles - [__progname](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - [environ](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - data export pointing to a fixed empty environment so titles that import it load; it is not the environment libc's `getenv` reads, so it lists none of those variables and `setenv`/`putenv` changes do not appear in it -- [seteuid, setegid, setuid, setgid, setreuid, setregid, setgroups, getgroups](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the guest process is assumed to run as root, with real, effective and saved user and group ids 0. As in FreeBSD's `sys_seteuid`, `sys_setegid`, `sys_setuid`, `sys_setgid`, `sys_setreuid` and `sys_setregid`, id 0 (or -1 for an unchanged `setreuid`/`setregid` id) is then accepted without a privilege check and changes no credential, so 0 is returned; any other id would change an id, which is not modelled, and throws. `setgroups` keeps the supplementary group list as `kern_setgroups` does (`NGROUPS_MAX` 1023, an empty list leaves the effective group as the only entry, a first entry other than 0 would change the effective group and throws) and `getgroups` reports it. The ids and privileges a title has on a console are unverified +- [seteuid, setegid, setuid, setgid, setreuid, setregid, setresuid, setresgid, setgroups, getgroups](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the guest process is assumed to run as root, with real, effective and saved user and group ids 0. As in FreeBSD's `sys_seteuid`, `sys_setegid`, `sys_setuid`, `sys_setgid`, `sys_setreuid`, `sys_setregid`, `sys_setresuid` and `sys_setresgid`, id 0 (or -1 for an unchanged `setreuid`/`setregid`/`setresuid`/`setresgid` id) is then accepted without a privilege check and changes no credential, so 0 is returned; any other id would change an id, which is not modelled, and throws. `setgroups` keeps the supplementary group list as `kern_setgroups` does (`NGROUPS_MAX` 1023, an empty list leaves the effective group as the only entry, a first entry other than 0 would change the effective group and throws) and `getgroups` reports it. The ids and privileges a title has on a console are unverified - [pthread_getname_np](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - a thread that has already been joined returns `ESRCH` (3) and its output buffer is left untouched, as measured on a PS5 (firmware 13.60) by the author of this entry; since [#4481](https://github.com/boykopovar/AnyPS5/pull/4481) the joined thread's object is retained, so the posix wrapper answers `ESRCH` for it instead of reading the retained name. A null name buffer is answered with `EFAULT` before that, an order the console table does not measure. The SCE sibling `scePthreadGetname` has no measured joined-thread behaviour on the console and still reads the retained object's name, so it is unchanged, as is the pre-existing dangling case of a thread that ended detached (its object is released at exit; see the scePthreadJoin entry) - [sceKernelGetOperationMode](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the signature `(int* mode, int* submode)` comes from PPSA12544, which logs both values after the call and ignores the result. A homebrew payload on a PS5 (firmware 13.60) reads mode 2 and submode 0, nothing else is written, and the result register holds no status, so the function is taken to return nothing. Whether a title process reads the same values is unverified. Null outputs throw -- [sceCoredumpGetStopInfoCpu](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) takes `(void* info, size_t size)` as in the [Orbital type database](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json); the 16-byte, 8-byte-aligned block follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Kernel/KernelSystem.cs), which places coredump reads inside the handler. The handler is never invoked here, so a well-formed request returns `NOT_IN_COREDUMP_HANDLER` without reading or writing the block. Its value `0x81180003` follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/coredump/coredump_error.h) and the NHL 27 (PPSA34063) call site reported in [#4275](https://github.com/boykopovar/AnyPS5/discussions/4275), not a PS5 measurement. The console validation order is unverified: null, unaligned or wrapping buffers and sizes other than 16 throw instead of guessing an argument error. Information from an active coredump handler remains unsupported -- [sceCoredumpGetThreadContextInfo](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel, NID `ShChva57wIM`) takes `(void* threadContextInfo, size_t threadContextInfoSize)` as in the [Orbital type database](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json); the 144-byte block of 18 64-bit registers follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Kernel/KernelSystem.cs#L120-L141). It answers as `sceCoredumpGetStopInfoCpu`: the handler is never invoked here, so an aligned 144-byte request returns `NOT_IN_COREDUMP_HANDLER` without reading or writing the block, and null, unaligned or wrapping buffers and other sizes throw, as the console validation order is unverified. Planet Coaster (PPSA01736) imports it -- [sceCoredumpAttachMemoryRegion, sceCoredumpAttachUserMemoryFile](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - both take `(uint32_t userValue, const void* mem, size_t size)`: for the first, the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Kernel/KernelSystem.cs) bindings agree; the second has only SharpProspero (the PS4 5.05 firmware does not export it, so Orbital has no entry) and is described there as attaching the region as an unnamed user file. Not confirmed on a title; the RED engine's PS4 crash handler ([Vangaurd](https://github.com/vurimad/Vangaurd/blob/main/source/imported/common/redSystem/src/errorHandlerImplErrorHooksOrbis.cpp)) calls the sibling `sceCoredumpAttachMemoryRegionAsUserFile(999, buffer, bytes, name)` from inside its handler. Attaching belongs inside the core dump handler, which the system runs on its own thread after the process stops (SharpProspero's remark, the RED engine's usage and `SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER`, "function was not called from registered core dump handler", in the Vita's SceCoredump error table, [RivetDB](https://github.com/CreepNT/RivetDB/blob/master/rivetdb.json)). The handler is never invoked here, so every call is outside it and returns that error; nothing is recorded, as there is no dump to attach to. Its value `0x81180003` comes from shadPS4 (PS4), not confirmed on a PS5: [fpPS4](https://github.com/red-prig/fpPS4/blob/master/kernel/ps4_libkernel.pas) has `NOT_REGISTERED` 0x81180001 and `ALREADY_REGISTERED` 0x81180002, shadPS4's `coredump_error.h` has `NOT_IN_COREDUMP_HANDLER` = 0x81180003, and the Vita table numbers `PARAM` 0, `NOT_REGISTERED` 1, `ALREADY_REGISTERED` 2 and `NOT_IN_COREDUMP_HANDLER` 3. Whether the console checks the region before the calling context is unknown, so a null, empty or wrapping region throws. `userValue` is not interpreted; SharpProspero names its bits 31 and 30 as truncation flags for the user file variants -- [sceCoredumpWriteUserData, sceCoredumpDebugTextOut](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - the signatures `ssize_t (const void* data, size_t size)` and `void (const char* str, int len)` follow the [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json) IDA type database and the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Kernel/KernelSystem.cs) bindings ("Repeated calls append", "The bytes written, or a negative error code"). User data is written from inside the core dump handler: the Vita's [`sceCoredumpWriteUserData`](https://github.com/vitasdk/vita-headers/blob/e66ebe90b73d1fa4cce005a5b2072cec27322544/include/psp2/coredump_nounlink.h) returns `0x800A0003` ("No user-data capture is active") outside it, and the RED engine's PS4 crash handler ([Vangaurd](https://github.com/vurimad/Vangaurd/blob/4f86d2b0e1a6c0d50526152c517a0292b1a68af4/source/imported/common/redSystem/src/errorHandlerImplErrorHooksOrbis.cpp)) makes all its coredump calls, `sceCoredumpDebugTextOut` included, from its handler. The handler is never invoked here, so `sceCoredumpWriteUserData` returns `SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER` (0x81180003 in shadPS4's [`coredump_error.h`](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/coredump/coredump_error.h), PS4 numbering) sign-extended to 64 bits and keeps nothing; whether the console checks the buffer before the calling context is unknown, so a null, empty or wrapping buffer throws. `sceCoredumpDebugTextOut` returns nothing; its text, which goes into the report on the console, is printed to stderr with the `[coredump]` prefix of `sceKernelDebugWriteCppExceptionInfo`, and a negative length or a null text with a nonzero length throws. No title is seen calling either: PPSA11386 imports `sceCoredumpWriteUserData` and does not call it before its stop (#2523) -- [sceCoredumpAttachUserFile, sceCoredumpAttachMemoryRegionAsUserFile](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - the signatures `int (uint32_t userValue, const char* path)` and `int (uint32_t userValue, const void* mem, size_t size, const char* name)` follow the [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json) IDA type database and the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Kernel/KernelSystem.cs) bindings. The crash handler calls both from inside its core dump handler, `sceCoredumpAttachUserFile(priority, absolutePath)` and `sceCoredumpAttachMemoryRegionAsUserFile(999, buffer, bytes, "LoggerQueueCrashDump.txt")`. The handler is never invoked here, so both return `SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER` (0x81180003, PS4 numbering from shadPS4) and attach nothing; whether the console checks the arguments before the calling context is unknown, so a null path or name and a null, empty or wrapping region throw. An empty path or name is passed through to that error, and `userValue` is not interpreted. No title is seen calling either +- [sceCoredumpGetStopInfoCpu](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) has no active coredump context. Native null/size validation is recovered, including both 8-byte and 16-byte records; valid requests return `NOT_IN_COREDUMP_HANDLER` without accessing the output. Active-handler data retrieval remains unimplemented. +- [sceCoredumpGetThreadContextInfo](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) validates a nonnull 144-byte output using the native checks. Active-handler register capture remains unimplemented; valid requests return `NOT_IN_COREDUMP_HANDLER` without accessing the output. +- [sceCoredumpAttachMemoryRegion, sceCoredumpAttachUserMemoryFile](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) cannot attach memory to an active coredump. The native signed 32-bit length is recovered: negative lengths are ignored successfully; other calls return the outside-handler error without accessing memory. Capture and kernel-side record limits remain unimplemented. +- [sceCoredumpWriteUserData, sceCoredumpDebugTextOut](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) have no active coredump report. WriteUserData validates a nonnull buffer and size at most `INT32_MAX`, including zero; accepted requests return the sign-extended outside-handler error. DebugTextOut still prints to stderr instead of a report; its validation and report integration remain unverified. +- [sceCoredumpAttachUserFile, sceCoredumpAttachMemoryRegionAsUserFile](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) cannot write files to an active coredump. Native null-name checks, the signed 32-bit memory length and SDK 6.50 flag validation are recovered. Nonnegative requests return the outside-handler error; negative memory lengths are ignored after name/flag validation. Kernel-side path handling and capture limits remain unimplemented. - [sceKernelAddTimerEvent](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - `usec` is taken as the period in microseconds; re-adding an existing timer keeps its pending expiration count and restarts the period from the time of the call, where FreeBSD 12+ `filt_timertouch` clears pending expirations on `EV_ADD`; `usec == 0` fires on every wait. None of this is confirmed on a PS5 title -- [sceKernelWaitEqueue](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - a level-triggered user event (`sceKernelAddUserEvent`) is reported at most once per call, as in FreeBSD `kqueue_scan` (`sys/kern/kern_event.c`), which re-queues a reported knote without `EV_CLEAR` or `EV_DISPATCH` behind the scan marker, and kevent(2), where several triggers do not produce several kevents. Not measured on a console. Events are scanned in list order and the list is not rotated, so when `num` is smaller than the number of ready level events the later ones can starve, where FreeBSD moves a reported level knote to the tail. Several completions of one AMPR or VideoOut event within one call are returned as separate entries, where FreeBSD would aggregate them; the Orbis behaviour is unconfirmed +- [sceKernelWaitEqueue](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - a level-triggered user event (`sceKernelAddUserEvent`) is reported at most once per call, as in FreeBSD `kqueue_scan` (`sys/kern/kern_event.c`), which re-queues a reported knote without `EV_CLEAR` or `EV_DISPATCH` behind the scan marker, and kevent(2), where several triggers do not produce several kevents. Not measured on a console. A reported event moves to the end of the list, so events are reported in the order they were last reported, and those never reported come first in registration order. FreeBSD `knote_enqueue` queues them in the order they fire. Several completions of one AMPR or VideoOut event within one call are returned as separate entries, where FreeBSD would aggregate them; the Orbis behaviour is unconfirmed - [AC1FtjqMCL0](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) - unknown name, signature - [0iEKNAvT600](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) - unknown name, signature - [2cELEBPdYQ0](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) - unknown name, signature @@ -108,138 +121,183 @@ - [qk043QPsrZU](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) - unknown name, signature - [uDGXwUKqi8s](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) - unknown name, signature - [sceKernelGetCpuTemperature, sceKernelGetSocSensorTemperature](../../../core/libs/prx/libkernel/System/src/ThermalSensors.cpp) (libkernel) - read the host's thermal sensors: on Linux from `/sys/class/hwmon` and `/sys/class/thermal`, on Windows from the ACPI thermal zones (`MSAcpi_ThermalZoneTemperature` over WMI) and the storage temperature property of the first physical drive. Sensor 0 is the CPU/package value, 1 the first other value and 2 an NVMe value; a host that exposes none reports every sensor as unsupported (`EINVAL`). The console's SoC channel count and meanings are hardware-dependent and differ from the host's, the host sensors are not the console's, the ACPI reading is unavailable on many machines, and the storage temperature property is numbered 52 on Windows 10 2004 and later and 22 before it, so the mapping, the values and the unsupported-input error are unverified; the storage channel was checked against the drive's own SMART composite temperature on one NVMe host (`StorageDeviceTemperatureProperty`, 36 °C on a WD_BLACK SN7100). `sceKernelGetCpuTemperature` reports sensor 0. PPSA99058 and PPSA99420 call both -- [LinuxProtFromSce](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - protection bits 0x100 and 0x200 are accepted and assumed not to affect host CPU access; host protection is derived only from the read, write and execute bits -- [sceKernelIsThreadSanitizerEnabled](../../../core/libs/prx/libkernel/System/src/Sanitizer.cpp) (libkernel, NID `LzoM-wVLJDE`) - no source gives the signature; `int (void)` is assumed from `sceKernelIsAddressSanitizerEnabled` and from PPSA23226's bundled `libSceJobManager_nosubmission.prx`, which passes no arguments and tests only the 32-bit result -- [getdtablesize](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - returns the smaller of the `RLIMIT_NOFILE` soft limit of `getrlimit` and the host's `getdtablesize()`, capped at `INT_MAX`: FreeBSD returns the smaller of the `RLIMIT_NOFILE` soft limit and `maxfilesperproc` ([`sys_getdtablesize`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L338-L355)), and the host call supplies that bound on macOS, where the soft limit alone is not one (`setrlimit` accepts soft limits above `maxfilesperproc`, `RLIM_INFINITY` included); `maxfilesperproc` is not measured on the console. The guest limit is the host's soft limit, raised to the hard limit when libkernel loads only on Linux: `core/libs/prx/libkernel/File/src/OpenFileLimit.cpp` is `__linux__`-only, so on macOS the guest gets whatever soft limit the host process inherited. VLC (PPSA85300) imports it for a `closefrom` loop in the child of its spawn helper ([libc_stubs.c](https://github.com/ZiZc3/VLC/blob/d46be6a68f4244466b041c2dc1b640915ac1a983/ps5/compat/libc_stubs.c#L66-L72)), which never runs because its `fork` fails with `ENOSYS` -- [close, sceKernelClose](../../../core/libs/prx/libkernel/File/src/HostStreams.cpp) (libkernel) - descriptors 0 to 2 that are open when libkernel loads belong to the host process: closing one fails with `EBADF` and leaves it open. The console's result is unknown. PPSA21703 calls `sceKernelClose(0)` repeatedly; closing the host stream let the host C runtime give descriptor 0 to the next file opened, which the next call then closed +- [getdtablesize](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - returns the smaller of the `RLIMIT_NOFILE` soft limit of `getrlimit` and the host's `getdtablesize()`, capped at `INT_MAX`: FreeBSD returns the smaller of the `RLIMIT_NOFILE` soft limit and `maxfilesperproc` ([`sys_getdtablesize`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L338-L355)), and the host call supplies that bound on macOS, where the soft limit alone is not one (`setrlimit` accepts soft limits above `maxfilesperproc`, `RLIM_INFINITY` included); `maxfilesperproc` is not measured on the console. On Linux and macOS the guest limit is the host's soft limit. VLC (PPSA85300) imports it for a `closefrom` loop in the child of its spawn helper ([libc_stubs.c](https://github.com/ZiZc3/VLC/blob/d46be6a68f4244466b041c2dc1b640915ac1a983/ps5/compat/libc_stubs.c#L66-L72)), which never runs because its `fork` fails with `ENOSYS` +- [close, sceKernelClose](../../../core/libs/prx/libkernel/File/src/HostStreams.cpp) (libkernel) - descriptors 0 to 2 that are open when libkernel loads belong to the host process: closing one fails with `EPERM` (`sceKernelClose`: `0x80020001`) and leaves it open, as closing the open descriptors 0 to 2 of a payload process did on a PS5 (firmware 13.60); whether a title process can close them is unverified. PPSA21703 calls `sceKernelClose(0)` repeatedly; closing the host stream let the host C runtime give descriptor 0 to the next file opened, which the next call then closed - [sceKernelPrintBacktraceWithModuleInfo](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) - imported by PS4 titles, which do not load without it, and added without an implementation: declared without parameters, every call throws +- [pthread_timedjoin_np](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel, NID `PkS44IGrDkM`) - the signature follows [ps5-payload-dev/sdk](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/include/freebsd/pthread_np.h#L71); deadline validation, absolute realtime deadlines, error precedence and join ownership follow [FreeBSD libthr](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libthr/thread/thr_join.c). The [public 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libkernel.c) lists this NID, but PS5 behavior and module/library versions have not been measured. Realtime is rechecked at most two seconds apart while waiting. After guest cleanup completes, final native thread teardown uses the existing unbounded join and may finish after the deadline; host teardown is not bounded by this implementation. + ### Functional - [scePthreadYield](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) on Windows calls Sleep(1) after 512 consecutive yields that find no other thread ready on the current processor and arrive less than ~10,000 TSC ticks apart (so the span varies with host CPU TSC rate); FreeBSD sched_yield never sleeps. +- [__tls_get_addr](../../../core/libs/prx/libkernel/Module/src/Tls.cpp) (libkernel, NID `vNe1w4diLCs`) throws on macOS, where the relinker binds guest modules to `Aps5GuestTlsGetAddr_nid_no_patch`, which no library defines yet; it also throws for a null index, module 0 and a Windows thread without a block for the module. It reads the module word as the relinker writes it for `R_X86_64_DTPMOD64`: on Windows the address of the module's PE `_tls_index`, resolved through the thread's `ThreadLocalStoragePointer` as in [WindowsGuestStartup.cpp](../../../core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp); on Linux the glibc module ID, passed to glibc's `__tls_get_addr`. Guest modules' imports are bound to the host resolver, so this export is reached only through a lookup by NID. +- [sceKernelGetTscFrequency](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel) pins its thread to the CPU it runs on for the 20 ms measurement, so that both TSC reads come from one counter. If the pin can't be set (`sched_getcpu` or `pthread_getaffinity_np` fails on Linux, `SetThreadAffinityMask` fails on Windows), the measurement runs unpinned. On a host whose CPU TSCs differ, the result can then be wrong as before. Neither call is known to fail for the calling thread's own CPU, which is always in its allowed set. macOS never pins. - [Guest thread scheduling](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - the policies and priorities that `sched_get_priority_max`, `sched_get_priority_min`, `pthread_setschedparam` and `scePthreadSetschedparam` accept, and those a created thread takes, follow a PS5 (firmware 13.60), but they are only recorded and reported: they are not applied to host threads, so a title's priority order has no effect on host scheduling +- [Linux runtime module initialization](../../../core/libs/prx/libkernel/Module/src/DynamicLoader.cpp) (libkernel) - modules in explicit runtime directories can be mapped for bindings before an explicit guest load runs their entry and init array once with the caller arguments. Finalizers run only after initialization completed. Console dependency-start and finalization ordering are unmeasured. Windows and macOS use their existing lifecycle paths. + - [sceKernelMprotect, sceKernelMtypeprotect](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) over a range with a gap that is not registered in the allocation tracker protect the registered parts and skip the gap, and return 0, as FreeBSD 11.4's [`vm_map_protect`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/vm/vm_map.c) does (it has no check for holes); a range whose start is unmapped (also when later pages are mapped) returns `SCE_KERNEL_ERROR_EINVAL` (`mprotect`: `EINVAL`) because an existing test pins that for an unmapped start; FreeBSD answers 0 there too (`vm_map_protect` takes the next entry), so this errno is not taken from the source and was not changed. `sceKernelMprotect` threw for a gap before, which ended the title. Every part is checked for a GPU or image pin before the first one is protected; the parts are then protected one after the other, so a host protection failure in a later part leaves the earlier parts changed (FreeBSD's `vm_map_protect` cannot fail in its second pass). The behaviour on the console is not known. None of this was measured on a console. -- [sceKernelMprotect protection bits](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - bits outside `0x3F7` are dropped instead of refused, as a PS5 (firmware 13.60) drops them: Onimusha: Way of the Sword (PPSA27836 01.000.001) maps direct memory with `0xf2` and then protects it with `0xfffffff2`, which the console reports as `0xf3` and then `0x3f3`. In a payload process the console also drops the CPU execute bit (`0x4`) of flexible, direct and JIT memory, and POSIX `mprotect` with write and execute reports write only (`0x2`). A title process may keep execute, so it is kept here. The map calls and `sceKernelMtypeprotect` refuse undefined bits on the console with `EINVAL` (`0x80020016`), and the direct map calls refuse execute; here both throw for undefined bits, and the direct map calls map execute +- [sceKernelMprotect protection bits](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - bits outside `0x3F7` are dropped instead of refused, in the POSIX `mprotect` as well (FreeBSD 11.4 masks the argument, `sys/vm/vm_mmap.c:613`; the console was measured only through `sceKernelMprotect`), as a PS5 (firmware 13.60) drops them: Onimusha: Way of the Sword (PPSA27836 01.000.001) maps direct memory with `0xf2` and then protects it with `0xfffffff2`, which the console reports as `0xf3` and then `0x3f3`. In a payload process the console also drops the CPU execute bit (`0x4`) of flexible, direct and JIT memory, and POSIX `mprotect` with write and execute reports write only (`0x2`). A title process may keep execute, so it is kept here. The map calls and `sceKernelMtypeprotect` refuse undefined bits on the console with `EINVAL` (`0x80020016`), and the direct map calls refuse execute; here both throw for undefined bits, and the direct map calls map execute +- [JIT shared memory on macOS](../../../core/libs/prx/libkernel/DirectMemory/Jit.cpp) (libkernel) - the object is an unlinked `mkstemp` file in the temporary directory, as `shm_open` makes its anonymous objects. macOS refuses an executable shared file mapping (`EPERM`), so an executable view is mapped without execute and then given it with `mprotect`. Under Rosetta 2 (Apple M2, macOS 26), code rewritten through one view after it ran from another keeps its old translation until the executable view's protection changes; first writes and a single read-write-execute view, as XPSemu PPSA97358 and VLC PPSA85300 map, are seen. `mmap` of a JIT descriptor is not supported, as file-backed guest `mmap` throws on macOS. - [JIT shared memory](../../../core/libs/prx/libkernel/DirectMemory/Jit.cpp) (libkernel) - `sceKernelJitCreateSharedMemory`, `sceKernelJitCreateAliasOfSharedMemory` and `sceKernelJitMapSharedMemory` take the signatures of [Orbital](https://github.com/AlexAltea/orbital)'s `tools/ida/db_types.json` (name, length, maximum protection, descriptor out; descriptor, maximum protection, descriptor out; descriptor, protection, address in and out), which ps4sdk, ps5-jar-loader and the call sites in RetroArch PPSA99169, XPSemu PPSA97358 and VLC PPSA85300 match; SharpProspero declares the alias as returning the descriptor. The object is a host anonymous file shared by its descriptors (a `memfd` on Linux, a delete-on-close temporary file mapped as an executable section on Windows), the alias is a duplicate descriptor, and every map maps the whole object, so writes through one view show through the others; a non-null address is a hint, as in `mmap` without `MAP_FIXED`. Not measured on the console: a length that is zero or not a multiple of 16 KiB and protection bits other than CPU read, write and execute are `EINVAL`; an alias or a map asking for more than the descriptor's maximum protection (write and execute counting as read) is `EACCES`, as FreeBSD 11's `vm_map_protect` treats a protection past `max_protection`; a descriptor that is not a JIT object is `EBADF`. The name is ignored, the objects do not count against the flexible memory budget, `mprotect` of a view is not limited to the maximum protection, and `mmap` of the descriptors maps the object as a [regular file](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp), so its views and the JIT views see each other's writes. On Linux the `memfd` is created without `MFD_EXEC`, which Linux 6.3 added and older kernels reject with `EINVAL`, and relies on the protection of the mapping: the flag only sets the file's execute permission bits, and the direct memory `memfd` is mapped executable the same way; a host with `vm.memfd_noexec` set to 2 refuses the creation and the call throws -- [kqueue and kevent](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) follow FreeBSD 11's [`kern_event.c`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/kern_event.c) (`kqueue_kevent`, `kqueue_register`, `kqueue_scan`, the timer and user filters) over the event queue; nothing is measured on a console. The descriptor is a host null device registered as the kqueue: `close` and `sceKernelClose` close the queue and `dup`/`dup2` of it throw, but `read`, `write` and `fstat` act on the null device instead of failing with `EOPNOTSUPP` or reporting `S_IFIFO`. Read and write events are supported on guest sockets only, plus `EVFILT_READ` on the read end and `EVFILT_WRITE` on the write end of a pipe (see the kevent on a pipe entry) and the timer, user and [vnode](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) filters and a read event on the host stream on descriptor 0, which never triggers, as `select` never reports descriptor 0 readable; PPSA34015 registers descriptor 0 for reading in a new kqueue at start-up and does not check the result; other filters, `EV_CLEAR` on a socket or a pipe off Linux (see the kevent EV_CLEAR entry), a `NOTE_NSECONDS` period above one second with a fraction (11.0 converts the fraction with the microsecond macro) and any change to an existing timer (11.0 keeps the armed expiry and applies the new period after it, 11.4's `filt_timertouch` restarts it) throw. `EV_DISABLE`, `EV_DISPATCH` and `EV_FORCEONESHOT` follow kern_event.c: a disabled knote stays registered, stops polling or firing and is not reported, a trigger raised while it is disabled is kept and reported after `EV_ENABLE`, where `knote` would drop it, `EV_DISPATCH` reports once and then disables the knote, and `EV_FORCEONESHOT` is ignored on a new knote while on an existing one it sets `EV_ONESHOT` and fires the knote; a negative `nevents` fails with `EINVAL`. A new user event keeps its `fflags` unmasked and is not triggered by `NOTE_TRIGGER`, because `filt_userattach` reads the already cleared `kn_fflags`. The timer limit `kq_calloutmax` (`ENOMEM`) is not enforced, and closing a kqueue wakes a thread waiting on it in `kevent` with `EBADF`, where `kqueue_drain` makes the close wait for that thread. A level-triggered event is reported once per call and moved behind the other events, as in `kqueue_scan`; the `sceKernel*Equeue` calls keep their own collection loop. `NOTE_LOWAT` on a socket sets the low-water mark as FreeBSD 11's [`filt_soread` and `filt_sowrite`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/uipc_socket.c) do, and a later change of the knote replaces it as `kqueue_register` replaces `kn_sdata`: a read event fires once the buffered bytes reach the mark, or at end of file or on a listening socket. A read mark above 1 is taken on stream sockets only: on a datagram socket Linux's `FIONREAD` reports the next datagram where FreeBSD's `sbavail` counts every queued byte, so it throws. A write event fires once the send buffer's free space reaches the mark, as `filt_sowrite` does with `sbspace`; the host reports that space as `SO_SNDBUF` less `SIOCOUTQ` on Linux and as `SO_SNDBUF` alone elsewhere, so off Linux it does not shrink while data is queued. Marks below 1, which make FreeBSD's read filter always fire, throw +- [kqueue and kevent](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) follow FreeBSD 11's [`kern_event.c`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/kern_event.c) (`kqueue_kevent`, `kqueue_register`, `kqueue_scan`, the timer and user filters) over the event queue; nothing here is taken from a console except what the kevent measurements entry lists. The descriptor is a host null device registered as the kqueue: `close` and `sceKernelClose` close the queue and `dup`/`dup2` of it throw, but `read`, `write` and `fstat` act on the null device instead of failing with `EOPNOTSUPP` or reporting `S_IFIFO`. Read and write events are supported on guest sockets only, plus `EVFILT_READ` on the read end and `EVFILT_WRITE` on the write end of a pipe (see the kevent on a pipe entry) and the timer, user and [vnode](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) filters and a read event on the host stream on descriptor 0, which never triggers, as `select` never reports descriptor 0 readable; PPSA34015 registers descriptor 0 for reading in a new kqueue at start-up and does not check the result; other filters except `EVFILT_AIO`, `EVFILT_LIO`, `EVFILT_SENDFILE` and `EVFILT_PROCDESC` (see the kevent measurements entry), `EV_CLEAR` on a socket or a pipe off Linux (see the kevent EV_CLEAR entry) and every change to an existing timer except `EV_DELETE` and the `EV_ADD` re-adds that the kevent measurements entry describes throw. A `NOTE_NSECONDS` period above one second with a fraction is kept to the nanosecond, where 11.0 converts the fraction with the microsecond macro. `EV_DISABLE`, `EV_DISPATCH` and `EV_FORCEONESHOT` follow kern_event.c: a disabled knote stays registered, stops polling or firing and is not reported, a trigger raised while it is disabled is kept and reported after `EV_ENABLE`, where `knote` would drop it, `EV_DISPATCH` reports once and then disables the knote, and `EV_FORCEONESHOT` is ignored on a new knote while on an existing one it sets `EV_ONESHOT` and fires the knote; a negative `nevents` fails with `EINVAL`. A new user event keeps its `fflags` unmasked and is not triggered by `NOTE_TRIGGER`, because `filt_userattach` reads the already cleared `kn_fflags`. The timer limit `kq_calloutmax` (`ENOMEM`) is not enforced, and closing a kqueue wakes a thread waiting on it in `kevent` with `EBADF`, where `kqueue_drain` makes the close wait for that thread. A level-triggered event is reported once per call and moved behind the other events, as in `kqueue_scan`; the `sceKernel*Equeue` calls keep their own collection loop. `NOTE_LOWAT` on a socket sets the low-water mark as FreeBSD 11's [`filt_soread` and `filt_sowrite`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/uipc_socket.c) do, and a later change of the knote replaces it as `kqueue_register` replaces `kn_sdata`: a read event fires once the buffered bytes reach the mark, or at end of file or on a listening socket. A read mark above 1 is taken on stream sockets only: on a datagram socket Linux's `FIONREAD` reports the next datagram where FreeBSD's `sbavail` counts every queued byte, so it throws. A write event fires once the send buffer's free space reaches the mark, as `filt_sowrite` does with `sbspace`; the host reports that space as `SO_SNDBUF` less `SIOCOUTQ` on Linux and as `SO_SNDBUF` alone elsewhere, so off Linux it does not shrink while data is queued. Marks below 1, which make FreeBSD's read filter always fire, throw - [EVFILT_VNODE, sceKernelAddFileEvent and sceKernelDeleteFileEvent](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) (libkernel) watch the host file or directory behind the descriptor and turn host changes into FreeBSD 11 vnode hints, which [`filt_vfsvnode`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/vfs_subr.c#L5032) adds whole when they share a bit with the requested `fflags`: a write that grows a file is `NOTE_WRITE | NOTE_EXTEND`, another write `NOTE_WRITE`, a shrinking truncate or an attribute change `NOTE_ATTRIB`, a higher link count `NOTE_LINK`, a lower one or a pending delete `NOTE_DELETE`, a new path `NOTE_RENAME`; in a directory, an entry created, removed or renamed is `NOTE_WRITE`, with `NOTE_LINK` for a subdirectory, and on Linux an entry moved from or to another directory adds `NOTE_EXTEND`; inotify queues the two halves of a rename one after the other, so a watcher woken by the first can read it alone, and a half read without the other is classified only after a `getdents64` on the watched directory, which takes the directory lock that `rename` holds until both halves are queued ([`do_renameat2`](https://github.com/torvalds/linux/blob/v6.8/fs/namei.c#L4975) around `vfs_rename` and its `fsnotify_move`, [`iterate_dir`](https://github.com/torvalds/linux/blob/v6.8/fs/readdir.c#L103)), an ordering taken from the kernel source, not from inotify(7), which promises none; a directory registered with `NOTE_EXTEND` on Windows is accepted, as `vfs_kqfilter` does not check it, but never receives it, because the directory handle cannot tell a move between directories from a removal plus an addition. FreeBSD's hints depend on the filesystem and the console's filesystems are not known: a growing `ftruncate` reports `NOTE_WRITE | NOTE_EXTEND` where `vop_setattr_post` reports `NOTE_ATTRIB`, and on Windows `utimes` on a file reports `NOTE_WRITE`. The three Windows notifications are told apart in the error, which names the `parent`, `entries` or `subdirectories` watch, the path it was given and `GetLastError`, but a `FindFirstChangeNotification` that fails still throws rather than returning an error to the guest: the EVFILT_VNODE attach path is [`vfs_kqfilter`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/vfs_subr.c#L4936), which returns `EINVAL`, `ENOMEM` or 0 after `fget` has turned a descriptor that is not open into `EBADF`, and consults no directory permission, so no FreeBSD errno stands for a host that refuses to notify, and what the console does on such a host is not measured. Only the `parent` watch can fail this way in practice: `entries` and `subdirectories` need the `FILE_LIST_DIRECTORY` that opening the watched directory already took. The parent handle produces no hint of its own, as `RunHostWatch` only calls `FindNextChangeNotification` on it, so a later change may leave the watch on its 50 ms poll instead of throwing; that is not done here. Windows uses `FindFirstChangeNotification` on the parent directory (and on a watched directory itself) and compares `GetFileInformationByHandleEx` snapshots at least every 50 ms, because a write through an open handle is reported only when the handle is flushed or closed, so file hints can arrive 50 ms late, and a file deleted while watched, which the POSIX delete of Windows 10 1903 moves into `$Extend\$Deleted` until its last handle closes, reports `NOTE_DELETE` without `NOTE_RENAME` and keeps its handle without a parent directory watch, as that directory cannot be watched; Linux uses inotify on the file and is not run. `NOTE_REVOKE`, `NOTE_OPEN`, `NOTE_CLOSE`, `NOTE_CLOSE_WRITE`, `NOTE_READ`, unknown bits and descriptors other than files, directories, pipes and sockets throw. `sceKernelAddFileEvent(eq, fd, watch, udata)` and `sceKernelDeleteFileEvent(eq, fd)` take the [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9d/include/orbis/libkernel.h#L35) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0/src/SharpProspero/Interop/Kernel/KernelEqueue.cs#L164) signatures; `watch` is taken as the `fflags` and the event is added with `EV_CLEAR`, neither confirmed on a title (feat(libkernel): implement kevent EV_DISABLE, EV_DISPATCH and EV_FORCEONESHOT) (fix(libkernel): follow kqueue_register for EV_FORCEONESHOT and modify flags) +- [sem_open, sem_close, sem_unlink](../../../core/libs/prx/libkernel/Pthread/Posix/Sem.cpp) (libkernel) keep named semaphores in a table inside the process; FreeBSD 11.4's [`sem_new.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sem_new.c#L138-L308) backs each with a file `/tmp/SEMD`. A semaphore lasts until `sem_unlink` and its last `sem_close` but is not shared with other processes, and the mode is ignored (no permission errors, as for a root caller). Whether a title can create files under `/tmp` on the console is unverified. A name with a further `/` throws, as it would resolve below `/tmp`, and so does an initial value above `SEM_VALUE_MAX`, which FreeBSD stores unchecked. - [sem_timedwait](../../../core/libs/prx/libkernel/Pthread/Posix/Sem.cpp) (libkernel) consumes an available count before checking the timeout and waits indefinitely for a null timeout, following FreeBSD 11's `sem_timedwait`. Console behavior for null and invalid timeouts when a count is available is unverified. +- [kevent measurements](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) - in payload processes on a PS5 (firmware 12.70), re-adding an existing periodic `EVFILT_TIMER` with `EV_ADD`, `fflags` 0 and a positive period kept the armed expiry and the expirations already counted and applied the new period after that expiry, as FreeBSD 11.0 does, where 11.4's `filt_timertouch` restarts the timer: a 50 ms timer re-added with 10 ms fired first 46 ms after the re-add and then every 10 ms, a 10 ms timer re-added with 100 ms fired first 5 ms after the re-add and then every 100 ms, the 12 expirations counted before a re-add were reported after it, and `EV_ONESHOT` in the re-add left the timer periodic. Here such a re-add does the same; re-adding a one-shot or disabled timer, a re-add with nonzero `fflags` (`NOTE_MSECONDS` included), a period of 0 or less, a period too large to add to the armed expiry or flags other than `EV_ONESHOT` were not measured and throw here, and the other changes to an existing timer except `EV_DELETE` throw here too. There `EV_ADD` failed with `EPERM` for `EVFILT_AIO`, `EVFILT_LIO` and `EVFILT_SENDFILE` at every ident tried (0, 5, 999, 2^40 and an open pipe) and, at ident 5, also with `EV_CLEAR`, `EV_ONESHOT` or `EV_DISABLE` added, and `EV_DELETE` and `EV_ENABLE` without `EV_ADD` failed with `ENOENT`. `EVFILT_PROCDESC` failed with `EBADF` on a descriptor that is not open and on an ident above `INT_MAX`, and with `EINVAL` on every open descriptor tried: Unix-domain stream and datagram sockets, descriptor 0 (a socket), an `AF_INET` UDP socket, both ends of a pipe, a regular file, a directory, another kqueue and the kqueue itself; `EV_DELETE` on a socket failed with `ENOENT`. Here these four filters answer the same way. `EV_ADD` of `EVFILT_PROCDESC` fails with `EINVAL` on the kinds of descriptor tried, since there are no process descriptors here, and also on shared memory and JIT shared memory, which are host files here: FreeBSD 11.0 gives `shm_open` objects [`invfo_kqfilter`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/uipc_shm.c#L136), which answers `EINVAL`, but neither was measured. It throws on any other kind, such as `/dev/random` and `/dev/urandom`, and on descriptors 0 to 2 that are not sockets or kqueues, whatever they hold: the host's standard streams, a pipe or file that `dup2` put on one of them, or a descriptor opened on a number that was free when libkernel loaded. A change of any of the four without `EV_ADD` fails with `ENOENT`, for `EVFILT_PROCDESC` after the descriptor check, as `kqueue_register` finds no knote. When `EV_ADD` of one of the four fails with `EPERM`, or with `EINVAL` on a descriptor other than the kqueue itself, the error event has `fflags` 0 and the errno in `data`, as FreeBSD 11.0's [`kqueue_register`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/kern_event.c#L1302-L1316) clears `fflags` and `data` before the attach and [`kqueue_kevent`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/kern_event.c#L1005-L1006) then stores the error in `data`; a descriptor that is not open, the kqueue itself and `EV_ENABLE` with `EV_DISABLE` fail before that and keep their `fflags`. That error event was not measured. `EVFILT_PROC` on the process itself with `NOTE_EXIT`, `EVFILT_SIGNAL` for `SIGUSR1` and `EVFILT_FS` at ident 0 were accepted there and still throw here. Also measured there on Unix-domain socketpairs, and still throwing here: a `NOTE_LOWAT` mark of 0 on an empty stream socket gave a read event at once (`data` 0), and so did re-adding that knote with a mark of -1 once the socket was drained; a write mark of 2 on the other end of that stream socketpair gave an event (`data` 8192), and re-adding that knote with 2^30 gave none; a mark of 5 on a datagram socket gave no read event after one 1-byte datagram and a read event (`data` 11) after a second, 10-byte datagram. `EVFILT_READ` on another kqueue reported the number of events pending in it there (1, then 2) and throws here; `EVFILT_WRITE` on a kqueue and a kqueue registered on itself failed with `EINVAL` there, as they do here - [tcsendbreak, tcdrain, tcflush, tcflow, tcgetpgrp, tcsetpgrp](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) follow FreeBSD 11.4's [`termios.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/termios.c): `tcflush` answers `EINVAL` for a queue other than `TCIFLUSH`, `TCOFLUSH` or `TCIOFLUSH` and `tcflow` for an action outside `TCOOFF`..`TCION` before the descriptor is looked at, and every other call fails as its terminal ioctl does on a descriptor that is not a terminal, with the errors `tcgetattr` gives, so `tcsendbreak` never reaches its 0.4 s pause. FreeBSD's `pipe_ioctl` answers `TIOCGPGRP` and `TIOCSPGRP` on a pipe or FIFO with the descriptor's SIGIO owner, which is not modelled, so `tcgetpgrp` and `tcsetpgrp` throw on a host pipe or FIFO above descriptor 2; descriptors 0 to 2 are taken as non-terminals that are not pipes, whatever the host attaches to them, so a pipe the guest creates on a freed descriptor 0 to 2 gets `ENOTTY` there. The PS5 `libkernel` is assumed to use this FreeBSD code, and `tcdrain` is not a cancellation point here -- [Windows file timestamps](../../../core/libs/prx/libkernel/File/include/WindowsFileTime.hpp) use the host filesystem's timestamp range and resolution. `utimes`, `futimes`, and `lutimes` reject timestamps outside nonnegative signed 64-bit Windows file-time ticks with `EINVAL`, including exactly 1601-01-01 00:00:00 UTC, whose zero value means "leave unchanged" to `SetFileTime`. Directory timestamp updates resolve the current path of the opened handle. Filesystems with coarser timestamp resolution cannot retain every guest microsecond +- [Windows file timestamps](../../../core/libs/prx/libkernel/File/include/WindowsFileTime.hpp) use the host filesystem's timestamp range and resolution. `utimes`, `futimes`, and `lutimes` reject timestamps outside nonnegative signed 64-bit Windows file-time ticks with `EINVAL`, including exactly 1601-01-01 00:00:00 UTC, whose zero value means "leave unchanged" to `SetFileTime`. Directory timestamp updates resolve the current path of the opened handle. Filesystems with coarser timestamp resolution cannot retain every guest microsecond. Birth-time clamping is not atomic with concurrent host timestamp updates and has not been measured on a console - [EVFILT_VNODE on macOS](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) (libkernel) registers `EVFILT_VNODE` on a duplicate of the descriptor in a host kqueue, whose notes have FreeBSD's values, and drops the `NOTE_LINK` that XNU adds to `NOTE_DELETE` when the watched file or directory is removed, since FreeBSD's `vop_remove_post` and `vop_rmdir_post` report `NOTE_DELETE` alone. As on Windows, a directory registered with `NOTE_EXTEND` is accepted but never receives it: XNU reports a move between directories as `NOTE_WRITE` alone, with `NOTE_LINK` for a subdirectory - [Windows file identity](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) - `stat` and `fstat` report the volume serial number as `st_dev` and the 64-bit file index folded into the guest's 32-bit `st_ino` (never 0), so two different files can in principle share an `st_ino`; the fold is not taken from a reference. `st_nlink` is clamped to 65535, the width of the guest field. The call keeps the `_wstat64` values when `GetFileInformationByHandle` fails -- [lstat of a Windows symbolic link](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) reports a link whose target is relative, as `symlink` creates them, with the link's own times, `st_dev` and `st_ino`, mode `S_IFLNK | 0777` and the length of the target `readlink` returns as `st_size`. FreeBSD gives a new link `0777` less the creating process's umask, which the host does not keep, so the permission bits are not the console's. A link with an absolute target is reported the same way, using its print name, or its substitute name when the print name is empty, without a `\??\` or `\\?\` prefix. A directory junction is reported the same way when the name left after that prefix is a drive path (`X:\...`): the print name when that name is a drive path, otherwise the substitute name. A mounted volume, whose substitute name is `\??\Volume{GUID}\`, and any other reparse point throw, as for `readlink`. `open` with `O_NOFOLLOW` returns `EMLINK` for that junction and does not open the target +- [lstat of a Windows symbolic link](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) reports a link whose target is relative, as `symlink` creates them, with the link's own times, `st_dev` and `st_ino`, mode `S_IFLNK | 0777` and the length of the target `readlink` returns as `st_size`. FreeBSD gives a new link `0777` less the creating process's umask, which the host does not keep, so the permission bits are not the console's. A link with an absolute target is reported the same way, using its print name, or its substitute name when the print name is empty, without a `\??\` or `\\?\` prefix. A directory junction is reported the same way when the name left after that prefix is a drive path (`X:\...`): the print name when that name is a drive path, otherwise the substitute name. `open` with `O_NOFOLLOW` returns `EMLINK` for that junction and does not open the target +- [lstat of a Windows reparse point that is not a link](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) reports a mounted volume (`\??\Volume{GUID}\`) or another name surrogate as the reparse point itself, not followed, with the mode of its attributes, as CPython's `os.lstat` does, and a reparse point without the name surrogate bit (deduplication, cloud files) as `stat` reports it - [setitimer](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follows FreeBSD's `sys_setitimer` and `kern_setitimer`: `EINVAL` for timers other than `ITIMER_REAL`, `ITIMER_VIRTUAL` and `ITIMER_PROF`, and for a value, or the interval of a non-zero value, with a negative field, `tv_usec` of 1000000 or more, or more than `INT32_MAX / 2` seconds; a null value reads the timer as `getitimer` does, with `EFAULT` when the output is null too. `ITIMER_REAL` shares the timer thread of `alarm`, which clears its interval: a zero value disarms it, the previous value is returned, and each expiry re-arms it by `it_interval`. With `SIG_IGN` an expiry is dropped. With `SIGALRM` blocked it is left on the pending set of `sigtimedwait`, as `kern_psignal` does, where `sigwait`, `sigtimedwait` and `sigwaitinfo` take it; unblocking it runs a guest handler on the unblocking thread, and with the default disposition exits the process with status 142. Unblocked, the default disposition exits the process with status 142 and a handler throws, since guest `SIGALRM` handlers are never run from the timer thread. The value is not raised to `tick / 16` as `itimerfix` does, and an interval below 1 ms throws, since the console's `hz`, and so that rounding, is unknown (1 ms covers any `hz` of 63 or more). `ITIMER_VIRTUAL` and `ITIMER_PROF` throw when armed, as process CPU time is not accounted, and read and disarm as zero. PPSA99420 imports it and fails to load without it - On Linux, [direct memory](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) lives in one sparse memfd at file offset = physical address, so the [GPU imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) can share one udmabuf buffer object per 64 MiB of it. Released direct memory is not punched out of the file, because a live import of its chunk pins the pages and the GPU would keep reading the old ones: its pages stay committed (the process keeps the high-water mark of the direct memory it used) and are zeroed when the range is allocated again. - [system](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) runs no command. It follows FreeBSD 11's `lib/libc/stdlib/system.c` for a sandbox without `/bin/sh`: a null command returns 1 unconditionally, as that version does, and any command returns the wait status of a child that could not exec the shell and called `_exit(127)` (`0x7f00`). Whether the console's application sandbox has no shell, and whether `vfork` succeeds there (FreeBSD returns -1 when it fails), is unverified - [sceKernelWriteThrottlingStatus](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) reports no write throttling: the host SSD has no write budget, so the first qword of the 32-byte status is set to `UINT32_MAX` and the rest is zeroed; returns `EFAULT` (`0x8002000e`) when passed a null status buffer pointer. PPSA17221 reads only that qword: it multiplies it by 64 KiB and compares the product with byte thresholds up to 0x4AF00000 to pick one of five tiers. When the call fails the title assumes 0x4B000001 bytes, which selects the same top tier as `UINT32_MAX`. What the value measures on hardware (a remaining budget or a rate) and the meaning of the other three qwords are unknown -- [Windows directory descriptors](../../../core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp) (libkernel) take one snapshot of the directory, on the first `getdents`, `getdirentries` or non-zero `lseek` (`SEEK_SET` or `SEEK_CUR`), and keep it until it is discarded; `lseek` to offset 0 only discards the snapshot, and the next read lists the directory again, so every rewind costs O(entries) on the next read. `lseek` takes the offset as an entry index and `SEEK_CUR` with offset 0 only reports the index. `SEEK_END` throws because FreeBSD defines it relative to the directory size, which is not modelled. A negative result returns `EINVAL` and a `SEEK_CUR` offset that overflows returns `EOVERFLOW`, as FreeBSD `vn_seek` reports them. Names whose UTF-8 encoding exceeds the one-byte `d_namlen` limit are not representable in this guest directory-record layout: `getdents` returns `ENAMETOOLONG` without consuming the entry. Repeated calls therefore report the same error and entries after that name cannot be reached through this descriptor; FreeBSD's `getdents` behavior for host filenames that exceed its `MAXNAMLEN` is not verified. `getdirentries` still reports 0 through `basep` instead of the position of the block it read, so only 0 and values returned by `lseek` are valid offsets; FreeBSD offsets are filesystem cookies. Descriptors retain the opened directory's native handle across renames. Enumeration reloads and `*at` operations resolve its current path, so a concurrent rename between resolution and the operation is not atomic. Enumeration of a removed directory and network-share path queries have not been verified +- [Windows directory descriptors](../../../core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp) (libkernel) take one snapshot of the directory, on the first `getdents`, `getdirentries` or non-zero `lseek` (`SEEK_SET` or `SEEK_CUR`), and keep it until it is discarded; `lseek` to offset 0 only discards the snapshot, and the next read lists the directory again, so every rewind costs O(entries) on the next read. `lseek` takes the offset as an entry index and `SEEK_CUR` with offset 0 only reports the index. `SEEK_END` throws because FreeBSD defines it relative to the directory size, which is not modelled. A negative result returns `EINVAL` and a `SEEK_CUR` offset that overflows returns `EOVERFLOW`, as FreeBSD `vn_seek` reports them. Names whose UTF-8 encoding exceeds the one-byte `d_namlen` limit are not representable in this guest directory-record layout, so `getdents` and `getdirentries` throw when they reach one; FreeBSD's behavior for host filenames that exceed its `MAXNAMLEN` is not verified. `getdirentries` reports through `basep` the entry index its block starts at, so only 0 and the values from `basep` or `lseek` are valid offsets; FreeBSD offsets are filesystem cookies. Descriptors retain the opened directory's native handle across renames. Enumeration reloads and `*at` operations resolve its current path, so a concurrent rename between resolution and the operation is not atomic. Enumeration of a removed directory and network-share path queries have not been verified +- [Linux directory entry types](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) - `sceKernelGetdirentries` reports a host symlink inside the app0 tree as the type of its target (directory or regular file), since a console app0 has no links and links there come from how the title was unpacked; a link elsewhere, or a dangling one, stays a link. A directory opened through an app0 link resolves to its target's host path, so links nested inside that target keep the link type. - [ftruncate, truncate, sceKernelFtruncate](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) extend a file on Windows with `SetFileInformationByHandle` (`FileEndOfFileInfo`), which reserves the clusters but writes no zero bytes, and report `EFBIG` for a length the volume cannot hold (`ERROR_INVALID_PARAMETER` on a disk file; seen with a length of `INT64_MAX`, the size from which NTFS refuses it was not measured) and `ENOSPC` when the free space is smaller. FreeBSD 11.4 makes the extension a hole on a file system that has them ([`ffs_truncate`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/ufs/ffs/ffs_inode.c) writes only the last block), so a length above the free space succeeds there. A write behind the end of the file still makes NTFS write zero bytes over the gap, as the file is not sparse. None of this was measured on a console. +- [Windows directory entry types](../../../core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp) (libkernel) - types other than regular files, directories and symbolic links, and reparse points unsupported by `lstat`, throw. If the host cannot stat an entry, its type is `DT_UNKNOWN`. Console directory-entry types have not been measured; the host `lstat` modes map to `DT_REG`, `DT_DIR` and `DT_LNK` following [FreeBSD 11.4 dirent.h](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/sys/dirent.h). A host link inside canonical app0 is reported as its target's type (directory or regular file); a link elsewhere, or a dangling one, stays a link. A directory opened through an app0 link resolves to its target's host path, so links nested inside a target outside app0 keep the link type. - [readv, writev, preadv, pwritev and their sceKernel forms](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) - unreadable iovec-table errors follow FreeBSD 11.4's [`copyinuio`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/subr_uio.c#L401-L434), not console measurements. The table is copied before inspecting its entries; payload-buffer validation is unchanged. A Linux `process_vm_readv` failure other than `EFAULT` throws, including when the host denies that API. - [/dev/random and /dev/urandom](../../../core/libs/prx/libkernel/File/src/RandomDevice.cpp) (libkernel) opened for reading return a descriptor backed by the host null device whose reads are filled with host OS entropy (`getentropy`, `rand_s` on Windows); both paths behave the same, never block, and positioned reads ignore the offset (FreeBSD `random(4)`). Only the exact paths are recognised, `fstat`, `lseek` and `mmap` see the null device, and opening them for writing throws - [pthread_attr_getinheritsched, pthread_attr_setinheritsched, pthread_attr_getscope, pthread_attr_setscope, pthread_attr_getstackaddr](../../../core/libs/prx/libkernel/Pthread/Posix/Attr.cpp) (libkernel) follow FreeBSD 11 libthr ([thr_attr.c](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/lib/libthr/thread/thr_attr.c), [thr_create.c](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/lib/libthr/thread/thr_create.c)), not measured on the console: the scope defaults to `PTHREAD_SCOPE_SYSTEM` (2), only it and `PTHREAD_SCOPE_PROCESS` (0) are accepted and other values return `EINVAL`, and an inherit value other than `PTHREAD_INHERIT_SCHED` (4) or `PTHREAD_EXPLICIT_SCHED` (0) returns `ENOTSUP`. `pthread_attr_getstackaddr` returns the configured stack address pointer (null by default). A null output of `pthread_attr_getinheritsched` throws, since libthr writes through it unchecked. A thread keeps the scope it was created with, taken from the creating thread under `PTHREAD_INHERIT_SCHED`, and `pthread_attr_get_np` and `scePthreadAttrGet` report it, but every guest thread is a host thread at system scope, so process scope changes nothing -- [Shared memory](../../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel) on Linux implements anonymous and named objects, bounded descriptor writes and seeks, and `shm_unlink` with effective read/write permission checks from FreeBSD 11. Named paths up to 1023 bytes use `/anyps5-shm-` plus SHA-1; collisions are not detected, and the namespace is shared across processes in the host mount namespace. Named creation applies the guest umask without changing the host mask. Linux procfs and `/dev/shm` are required. Operations use an advisory directory lock; external host operations bypass it, unrelated objects serialize, and descriptor operations require two spare host descriptors and compare device/inode with the initial descriptor identity on both the pinned-object and ordinary-file fallback paths. [Guest mappings](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) support shared and private mappings of these descriptors through the allocation tracker and pin a duplicate descriptor during validation and mapping; mapping requires one spare host descriptor; file-backed mappings use the write-watch scan fallback because aliases and descriptor writes bypass per-mapping tracking. On Windows, anonymous objects are delete-on-close temporary files and `mmap` maps them as [regular files](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp); named objects remain unsupported. Invalid non-null name pointers, console behaviour and title compatibility remain unverified. +- [Shared memory](../../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel) on Linux implements anonymous and named objects, bounded descriptor writes and seeks, and `shm_unlink` with effective read/write permission checks from FreeBSD 11. Named paths up to 1023 bytes use `/anyps5-shm-` plus SHA-1; collisions are not detected, and the namespace is shared across processes in the host mount namespace. Named creation applies the guest umask without changing the host mask. Linux procfs and `/dev/shm` are required. Operations use an advisory directory lock; external host operations bypass it, unrelated objects serialize, and descriptor operations require two spare host descriptors and compare device/inode with the initial descriptor identity on both the pinned-object and ordinary-file fallback paths. [Guest mappings](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) support shared and private mappings of these descriptors through the allocation tracker and pin a duplicate descriptor during validation and mapping; mapping requires one spare host descriptor; file-backed mappings use the write-watch scan fallback because aliases and descriptor writes bypass per-mapping tracking. On Windows, anonymous objects are delete-on-close temporary files and `mmap` maps them as [regular files](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp); named objects are described in the next entry. Invalid non-null name pointers, console behaviour and title compatibility remain unverified. +- [shm_open, shm_unlink](../../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel, Windows) - named objects are regular files in the current user's temporary directory under `AnyPS5/shm`, identified by SHA-1 of the exact guest name. The namespace is per host user and shared by every AnyPS5 process using that temporary root, so two titles using the same name share the object. Namespace operations use a cross-session named mutex, while file-size changes and writes use a mutex keyed by the host file identity; unlink renames the visible object to a unique internal name so already-open handles remain usable while the guest name can be recreated. Orphaned files are reaped when the last tracked guest descriptor closes or opportunistically during a later open. Closing through host stdio wrappers does not update this bookkeeping or reap immediately. The namespace directory has a protected ACL for the owner, SYSTEM and Administrators; newly created objects map the guest owner read/write bits after applying the guest creation mask. `fchmod` updates those owner permissions on the descriptor's file object. Guest group/other permission classes are not represented independently on Windows. Mapping a named object uses the same Windows file mapper as anonymous objects and no test covers it, and no behavior here has been measured on PS5. Lifecycle, cross-process resize/write, and mode behavior are exercised by Windows tests. See Microsoft's [file sharing modes](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea), [DeleteFile](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-deletefilew) and [mutex objects](https://learn.microsoft.com/en-us/windows/win32/sync/mutex-objects). - [sceKernelAddReadEvent and sceKernelAddWriteEvent](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel): a read event on a listening socket, here and in `kevent`, reports the number of completed connections waiting to be accepted in `data`, as FreeBSD 11.4's [`filt_solisten`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L3384-L3392) reports `so_qlen`: on Linux a TCP listener's accept queue from `TCP_INFO` (`tcpi_unacked`, which Linux fills with the accept backlog for a listening socket), and elsewhere, or for a listener that is not TCP, 1 while a connection is waiting, as no host call reports that count. A write event reports the free space of the socket's send buffer in `data`, as FreeBSD 11.4's [`filt_sowrite`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L3358-L3382) reports `sbspace`: on Linux the host's `SO_SNDBUF` less the bytes `SIOCOUTQ` counts as queued, which include the host's per-packet overhead, so the space shrinks by somewhat more than the bytes sent. Windows has no query for that space and macOS is not wired up, so both report the whole `SO_SNDBUF`, which does not shrink as data is queued. Only guest sockets are supported; other descriptors and a low watermark above 1 throw -- [sceKernelAddReadEvent on pipes](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) is the one exception to the sockets-only rule above: it also takes the read end of a pipe, as `kevent` does through FreeBSD's `filt_piperead`, and ignores the size argument there. Host streams, other descriptors and a write event on a pipe throw -- [close](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) and [setsockopt](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) reach a [libSceNet](../../../core/libs/prx/libSceNet/Export.cpp) socket through hooks libSceNet registers, as `select` polls them; the other libkernel socket calls do not. libSceNet numbers its sockets from 32 in its own table, so a CRT file descriptor and a socket can share a number; `close` then closes the socket. On the console both are one kernel descriptor space -- [sceKernelPread, sceKernelPwrite, sceKernelPreadv, sceKernelPwritev](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on Windows transfer at the given offset with an overlapped `ReadFile`/`WriteFile` and restore the descriptor's file pointer afterwards. Guest scalar transfers, positioned transfers, vectored transfers and seeks share a position lock across duplicated descriptors; a vector holds it for the whole operation. Independently opened descriptors and other processes do not share that lock, so vector transfers over their overlapping file ranges are not coordinated. Handles that are not disk files return `ESPIPE`. A pipe or console `readv` can return a short read when no more input is available; each iovec is transferred separately, so a failure after the first transfer returns the bytes already done +- [sceKernelAddReadEvent on pipes](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) is an exception to the sockets-only rule above: it also takes the read end of a pipe, as `kevent` does through FreeBSD's `filt_piperead`, and ignores the size argument there. Host streams, other descriptors and a write event on a pipe throw +- [sceKernelAddReadEvent and sceKernelAddWriteEvent on regular files](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) are the other exception: a size argument above 1, directories and other vnode types throw, and whether the console's file systems answer as `vfs_kqfilter` does was not measured. A wait holding one of these events polls it every millisecond, as no host call reports every file change, so a write through another descriptor shows up to 1 ms late. Unlike FreeBSD, a seek can make a read event fire again: FreeBSD 11.4's [`kqueue_scan`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_event.c) deactivates a level-triggered read knote once `filt_vfsread` reports `data` 0, and only a vnode `KNOTE` (a write, an extend or a truncate) activates it again; `lseek` posts none, so after a file read to its end is seeked back, the 1 ms poll here reports the new `data` (10 for a 10-byte file seeked to 0) where FreeBSD keeps waiting until the file changes. Otherwise they follow FreeBSD 11.4's [`filt_vfsread` and `filt_vfswrite`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/vfs_subr.c#L5122-L5168): a read event reports the size less the descriptor's offset in `data` and fires while that is not 0; a write event always fires with `data` 0 +- [close](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) and [setsockopt](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) reach a [libSceNet](../../../core/libs/prx/libSceNet/Export.cpp) socket through hooks libSceNet registers, as `select` and `poll` poll them and `fcntl` answers `F_GETFL` and `F_SETFL`, failing other commands with `EINVAL`; the other libkernel socket calls do not. libSceNet numbers its sockets from 32 in its own table, so a CRT file descriptor and a socket can share a number; `close` then closes the socket. On the console both are one kernel descriptor space +- [sceKernelPread, sceKernelPwrite, sceKernelPreadv, sceKernelPwritev](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on Windows transfer with an overlapped `ReadFile`/`WriteFile` and restore the descriptor's file pointer afterwards. Guest scalar transfers, positioned transfers, vectored transfers and seeks share a position lock across duplicated descriptors; a vector holds it for the whole operation. Independently opened descriptors and other processes do not share that lock, so vector transfers over their overlapping file ranges are not coordinated. Handles that are not disk files return `ESPIPE`. A pipe or console `readv` can return a short read when no more input is available; each iovec is transferred separately, so a failure after the first transfer returns the bytes already done - [sceKernelAioSubmitReadCommands, sceKernelAioSubmitWriteCommands and their Multiple variants](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) on Windows transfer through the same positioned path as `sceKernelPread`/`sceKernelPwrite` and restore the descriptor's file pointer afterwards; positioned transfers, plain transfers and seeks of one descriptor are serialized per descriptor (shared across duplicated descriptors), so a concurrent transfer observes a stable file pointer -- [sceKernelAioSetParam](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) writes the five values into the 20-byte scheduling struct the first argument points to, following the declarations in [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json), [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Kernel/KernelAio.cs) and [ampr_emu](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/ps5/include/sce/_fs.h). Whether the console checks the values is unknown, so a null struct, a scheduling window or delayed count limit outside 0..128, a split flag other than 0 or 1 and a split size or chunk size above 0x1000000 (SharpProspero's maximums) throw. The scheduling settings are not used: `sceKernelAioInitializeImpl` ignores the struct and requests run synchronously on submit +- [sceKernelAioSetParam](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - the limits and `sceKernelAioInitializeParam`'s defaults (three 20-byte scheduling structs, the last without split) are measured on a PS5 (firmware 13.60), where a null struct crashes both calls; here both throw. The scheduling settings are not used: `sceKernelAioInitializeImpl` ignores the struct and requests run synchronously on submit - [sceKernelGetFsSandboxRandomWord](../../../core/libs/prx/libkernel/System/src/Sandbox.cpp) (libkernel) returns a fixed directory name, `gkbHtXc3Wq`. On the console the name is the process's randomized path (syscall 602, `randomized_path`), and the sandbox exposes system files under it, such as `common/lib` and `common/cert`. Nothing is mounted under the name here, so `/gkbHtXc3Wq/common/...` resolves under the guest root like any other path and does not exist, except `/gkbHtXc3Wq/common/cert/CA_LIST.cer`, which [SandboxCertificates.cpp](../../../core/libs/prx/libkernel/File/src/SandboxCertificates.cpp) serves from the host trust store +- [ktimer_create, ktimer_settime, ktimer_gettime, ktimer_getoverrun, ktimer_delete](../../../core/libs/prx/libkernel/Time/Ktimer.cpp) (libkernel) - a `SIGEV_SIGNAL` or `SIGEV_THREAD_ID` timer, and one created with a null `sigevent` (FreeBSD defaults it to `SIGEV_SIGNAL`), throws, because delivering a guest signal from a timer is not modelled. A non-zero value under one tick rounds up to 1 ms, as FreeBSD 11 does at hz 1000; the console's hz is not measured (`kern.clockrate` is unanswered). `SIGEV_NONE` timers on `CLOCK_REALTIME` and `CLOCK_MONOTONIC` follow `kern_time.c`: ids 3 to 31 (`EAGAIN` when full), `EINVAL` for another clock, a bad notify type or signal, an id that is not live or a bad `itimerspec` field, and `TIMER_ABSTIME` (1). Expiry is worked out from the guest clock when the timer is read, and `ktimer_getoverrun` returns 0, as FreeBSD only updates it when a signal is accepted - [The sandbox CA list](../../../core/libs/prx/libkernel/File/src/SandboxCertificates.cpp) (libkernel), `//common/cert/CA_LIST.cer`, is a PEM bundle of the host's trust anchors, written once per process to `cert_cache/CA_LIST.cer` in the run directory with the owner write bit cleared: on Windows the current user's `ROOT` store, filtered through Disallowed and the combined EKU extension/property for TLS server authentication (Windows fetches missing roots on demand, so a root that was never fetched is absent), elsewhere `SSL_CERT_FILE` or the first of `/etc/ssl/certs/ca-certificates.crt`, `/etc/pki/tls/certs/ca-bundle.crt`, `/etc/ssl/ca-bundle.pem` and `/etc/ssl/cert.pem` that exists. On macOS, `/etc/ssl/cert.pem` is a static snapshot and does not reflect roots added to or distrusted in the Keychain. Existing guest files, explicit aliases and blocked mounts take precedence over the provider. The PEM snapshot does not carry Windows CTL restrictions beyond EKU, native revocation policy or later trust-store changes. Without any certificate the path does not exist. The console file's format is unverified: only that bundled OpenSSL accepts a PEM bundle there is shown (ProsperoRadio PPSA99001 and SwanStation PPSA98510, #5001). The other entries of the console's `common/cert` directory, the directory itself (it does not exist here) and the error a write open gets (`EACCES` from the host here) are unverified +- [pselect](../../../core/libs/prx/libkernel/Socket/src/Pselect.cpp) (libkernel) sets the signal mask, calls `select` and restores the mask, as FreeBSD 11 `kern_pselect` does. A pending signal that the new mask unblocks has its handler run during the swap, and `pselect` then returns `EINTR` unless a descriptor is already ready, the timeout is zero or an argument is invalid, as FreeBSD's `kern_select` decides before it sleeps. A signal raised during the wait, including one sent by another thread, does not interrupt it, because `select` has no `EINTR` path; and the mask is the process-wide one, so it applies to every thread for the duration of the call - [getcontext and setcontext](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) and [makecontext](../../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) use FreeBSD 11's amd64 `ucontext_t` (`mcontext` at 0x10, `mc_len` 800, `uc_link` at 0x330, `uc_stack` at 0x338, 0x370 bytes), the layout homebrew built against FreeBSD's headers passes: PPSA99039's OpenSSL `ASYNC` (`crypto/async/arch/async_posix.c`) captures into a 0x370-byte stack buffer. `getcontext` writes the 0x330 bytes before `uc_link`, as FreeBSD's `UC_COPY_SIZE`. The console's own layout for these functions is not verified; the exception handler's context is a different one (see `sceKernelRaiseException`) - [getcontext and setcontext](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) keep the 512-byte FXSAVE image in `mc_fpstate` (x87, MXCSR, XMM0-15), so the upper halves of YMM0-15 are neither saved nor restored, and `getcontext` leaves `mc_xfpustate` and `mc_xfpustate_len` 0. `getcontext` sets `mc_fpformat` and `mc_ownedfp` to FreeBSD's `_MC_FPFMT_XMM` and `_MC_FPOWNED_FPU`, `mc_cs` and `mc_ss` to the host selectors, and `mc_flags`, `mc_fsbase` and `mc_gsbase` to 0. `setcontext` loads `mc_fpstate` whatever `mc_fpformat` and `mc_ownedfp` say, and restores neither the segment selectors, `mc_fsbase` and `mc_gsbase` nor the extended state, which FreeBSD restores when `mc_flags` asks for them - [setcontext](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) and [longjmp](../../../core/libs/prx/libc/src/JumpBuffer.cpp) (libc) on Windows set the thread's stack bounds in the TEB to the stack they switch to: a `makecontext` stack, or else the committed region around the target stack pointer. A switch to any other guest stack, such as a context built by hand, gets the bounds of that region only -- [scePthreadAttrGet](../../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) (libkernel) - the answer for a thread that has ended is not measured on the console, and [`pthread_attr_get_np`](../../../core/libs/prx/libkernel/Pthread/Posix/Attr.cpp) still returns 0 for such a thread, reading a detached one's object after it is freed. `scePthreadAttrGet` returns `ESRCH` (`0x80020003`) for a thread that has ended, joined or not and detached or not, as FreeBSD 11.4's [`_pthread_attr_get_np`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_attr.c#L139-L172) does through `_thr_find_thread`; a thread counts as ended once its thread-specific data destructors have run, where FreeBSD marks it `PS_DEAD`. On Windows a host thread adopted by `scePthreadSelf` that ends without `scePthreadExit` keeps answering 0. Monster Hunter Wilds (PPSA24191) reuses a slot of its thread table only when `scePthreadAttrGet` returns `0x80020003` +- [scePthreadAttrGet](../../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) and [pthread_attr_get_np](../../../core/libs/prx/libkernel/Pthread/Posix/Attr.cpp) (libkernel) - the answer for a thread that has ended is not measured on the console; the implementation follows FreeBSD 11.4's [`_pthread_attr_get_np`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_attr.c#L139-L172) through `_thr_find_thread`. On Windows a host thread adopted by `scePthreadSelf` that ends without `scePthreadExit` keeps answering 0. Monster Hunter Wilds (PPSA24191) reuses a slot of its thread table only when `scePthreadAttrGet` returns `0x80020003` - [scePthreadAttrGet, pthread_attr_get_np and sceKernelIsStack](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) report the stack of a host thread the library did not create, on Windows, from one allocation granule (64 KiB) above the bottom of its reservation when everything above that is committed read-write, as in a relinked executable's main thread and its default-size host threads (0x200000 committed of 0x210000 reserved), so the guest can protect the lowest reported pages, as HotSpot does for its stack guard pages in Java 25 LAN / DNS (PPSA99869). Any other such thread, and every such thread on Linux, gets its whole host stack: on Windows `mprotect` commits its reserved lower pages before protecting them and drops the guard flag of Windows' guard pages, and on Linux it cannot change the unmapped lower pages - [sceKernelInstallExceptionHandler](../../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler, but a host fault still ends the process: only signals raised with `sceKernelRaiseException` are delivered. Unity's PS5Util module installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection -- [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) on Linux sends the host SIGUSR1, which the library takes over, to the target thread, so the handler runs wherever the thread was interrupted, emulator code included. On the console kernel objects are system calls and libthr defers signals while a thread holds its internal locks; here a handler that needs a host lock the interrupted code holds deadlocks, and a thread that the handler keeps waiting keeps its host locks. Host waits resume after the handler. The host calls Linux does not restart end early: sleeps that do not retry return before their time, and the poll of `sceNetEpollWait` and socket calls with a send or receive timeout return `EINTR` to the guest -- [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) on Windows suspends the target and redirects it to the handler. A target running host code (any module other than the executable and the `*.guest.prx` modules, so libkernel, libc and the other HLE modules count too) is resumed and sampled again until it is back in guest code, since a handler that waits there would keep the host locks that code holds; a target blocked in an ntdll wait is redirected in place, as are NtContinue and winpthreads after their retry limit. A target that stays in host code without waiting for 50 ms after the raise began, such as a long HLE or driver call, is then redirected in host code anyway, with the same hazard. A target seen right after a `syscall` instruction in ntdll, inside a system call that is not a wait such as the `NtWriteFile` of an `fwrite` holding its stream lock, restarts those 50 ms, so a raise can cost the raising thread up to the 1 s retry limit. While the target is suspended only `VirtualQuery` and a lock-free cache of image bases are consulted; an image not yet in the cache is classified with `GetModuleFileNameW` after the target is resumed. An APC queued at the first host-code sample, which an alertable guest wait would run sooner, is not implemented +- [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) on Linux sends the host SIGUSR1, which the library takes over, to the target thread, so the handler runs wherever the thread was interrupted, emulator code included. On the console kernel objects are system calls and libthr defers signals while a thread holds its internal locks; here a handler that needs a host lock the interrupted code holds deadlocks, and a thread that the handler keeps waiting keeps its host locks. A thread holding the address-wait lock (`sceKernelSyncOnAddressWait`, `sceKernelSyncOnAddressWake`, `_umtx_op`) records the signal and runs the handler once it releases the lock or while it waits; other emulator locks are not deferred. Host waits resume after the handler. The host calls Linux does not restart end early: sleeps that do not retry return before their time, and the poll of `sceNetEpollWait` and socket calls with a send or receive timeout return `EINTR` to the guest +- [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) on Windows suspends the target and redirects it to the handler. A target running host code (any module other than the executable and the `*.guest.prx` modules, so libkernel, libc and the other HLE modules count too) is resumed and sampled again until it is back in guest code, since a handler that waits there would keep the host locks that code holds. The raising thread spins for 20 µs before each new host-code sample: suspended again at once, the target could stay at the same instruction for every sample until the 50 ms below ran out (on a 16-core Ryzen 9 5950X, `guest_raise_in_host_code` then raised into `fwrite` and deadlocked on its stream lock), and the 20 µs is a measured choice, not a console value; a target blocked in an ntdll wait is redirected in place, as are NtContinue and winpthreads after their retry limit. A target that stays in host code without waiting for 50 ms after the raise began, such as a long HLE or driver call, is then redirected in host code anyway, with the same hazard. A target seen right after a `syscall` instruction in ntdll, inside a system call that is not a wait such as the `NtWriteFile` of an `fwrite` holding its stream lock, restarts those 50 ms, so a raise can cost the raising thread up to the 1 s retry limit. While the target is suspended only `VirtualQuery` and a lock-free cache of image bases are consulted; an image not yet in the cache is classified with `GetModuleFileNameW` after the target is resumed. An APC queued at the first host-code sample, which an alertable guest wait would run sooner, is not implemented - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) builds the handler's `ucontext` with FreeBSD's amd64 layout (16-byte sigmask, `mcontext` at 0x40, `mc_rsp` at 0xf8); the console's layout is not verified beyond the fields PS5Util reads - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) does not consult the target thread's signal mask -- [pthread_sigmask](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) shares the process-wide mask of `sigprocmask` and `_sigprocmask`, which keeps only the first 32 signals; FreeBSD keeps a mask per thread +- [pthread_sigmask](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) shares the process-wide mask of `sigprocmask` and `_sigprocmask`; FreeBSD keeps a mask per thread - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) reaches a thread that waits in an alertable `TimedWait` wait through an APC. A thread blocked anywhere else, including all waits under `APS5_COARSE_TIMED_WAITS` and host waits inside the emulator, is redirected and runs the handler only when that wait returns - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) on Windows runs the handler wherever the target thread was interrupted. It waits only while the thread is inside winpthreads or restoring a context through `NtContinue`, and throws if that lasts 1 s, so a handler that calls into other host code the thread was interrupted in, such as the C runtime's heap or a lock of the emulator, can deadlock on it or reenter it. A thread that holds the address-wait lock of `sceKernelSyncOnAddressWait`, `sceKernelSyncOnAddressWake` and `_umtx_op`, or the park queue lock of the guest mutexes, gets the raise as an APC instead, which runs once it has released the lock or while it waits; the other locks of the emulator are not deferred this way +- [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) on Windows throws for a target thread that was created suspended and is not resumed yet. FreeBSD 11.4 libthr starts such a thread with every signal but `SIGTRAP` blocked ([`thr_create.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_create.c#L177-L183)) and restores its mask only once it is resumed ([`thread_start`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_create.c#L252-L294)), so a signal sent to it stays pending until then. The console's behaviour is unverified - [sigaltstack](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records each thread's alternate signal stack and reports it back, following FreeBSD's `kern_sigaltstack` (`EINVAL` for flags other than `SS_DISABLE`, `ENOMEM` below `MINSIGSTKSZ` 2048), but handlers never run on it, so `SS_ONSTACK` and `EPERM` are never reported -- [sigpending](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) reports the intersection of the process-wide pending set of `sigtimedwait` and the blocked mask of `_sigprocmask`, as FreeBSD's `sys_sigpending` intersects the pending list with `td_sigmask`, and returns -1 with `EFAULT` for a null set. Because `_sigprocmask` keeps only the first 32 signals, signals 33 to 128 are never blocked and never appear in the set, and setting `SIG_IGN` (or `SIG_DFL` on a signal whose default action is to ignore) does not clear a signal that was already marked pending. PPSA99420 imports it -- [scePthreadJoin, scePthreadCreate](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - on a PS5 (firmware 13.60) joining a thread a second time returns `ESRCH` (`0x80020003`), as it does here, but a joined thread is kept until `scePthreadCreate` publishes over the same handle storage, so one host object and its handle are retained per distinct guest handle storage location for the lifetime of the process; a guest that joins into a fresh storage location every time keeps every one of them. A null entry crashed the payload and still throws. Joining the calling thread returns `EDEADLK` and a zeroed attribute creates a default thread, as on the console. `scePthreadDetach` on a joined thread answers `EINVAL`, as FreeBSD does on a thread that is not joinable; the console answer for a detach after a join is unverified. Detaching a thread while another thread waits to join it returns `EINVAL`, and a second concurrent join returns `EOPNOTSUPP`, as FreeBSD 11.4's [`thr_detach.c`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_detach.c) and [`thr_join.c`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_join.c) check `joiner`; before this PPSA34015 stopped once with `Joining guest thread: Access is denied` when a thread it was joining was released under the join, and which of the two calls released it was not captured. A detached thread is still released when it ends, so joining it after it ended reads freed memory. A handle value copied to a second storage and joined there retains the object under the original storage key, so a later `scePthreadCreate` over that original storage releases an object the copy still points at, and the copy reads freed memory +- [sigpending](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) reports the intersection of the process-wide pending set of `sigtimedwait` and the blocked mask of `_sigprocmask`, as FreeBSD's `sys_sigpending` intersects the pending list with `td_sigmask`, and returns -1 with `EFAULT` for a null set. PPSA99420 imports it +- [scePthreadJoin, scePthreadCreate](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - a joined thread is kept until `scePthreadCreate` publishes over the same handle storage, so one host object and its handle are retained per distinct guest storage location for the life of the process; a guest that joins into a fresh location every time keeps every one. A handle copied to a second storage and joined there retains the object under the original storage key, so a later `scePthreadCreate` over the original storage releases an object the copy still points at, and the copy reads freed memory. A detached thread is released when it ends, so joining it after it ended reads freed memory. A null entry crashed the payload and throws. Joining the calling thread returns `EDEADLK` and a zeroed attribute creates a default thread, as on the console. PPSA34015 once stopped with `Joining guest thread: Access is denied` before the join and detach checks followed the console +- [scePthreadCreate and scePthreadDetach, measured](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - as in a payload process on a PS5 (firmware 13.60): a name of 32 bytes or more returns `ENAMETOOLONG` (`0x8002003f`) without creating a thread, 31 is kept whole, and a null name gives an empty one; detaching a joined thread returns `ESRCH`; detaching a thread another thread is joining, or a detached one, returns `EINVAL`, as does joining a detached one; a second concurrent join returns `EOPNOTSUPP`; a null thread returns `EINVAL` from both; a null handle pointer to `scePthreadCreate` faulted and throws here. Whether the handle is written before the name check was not measured; here it is left alone - [Thread cancellation](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) follows FreeBSD libthr (states and types 0/1 and 0/2, `PTHREAD_CANCELED` 1, the `_pthread_cleanup_info` node layout), not measured on the console. A request is acted on only in `scePthreadTestcancel`, the condition variable, `sem` and `scePthreadSem` waits, `scePthreadJoin` and the sleeps, and their POSIX names; file and socket calls are not cancellation points. An enabled cancellation request wakes POSIX `sleep` and exits the thread. `usleep`, `sceKernelUsleep`, `nanosleep`, `_nanosleep`, `sceKernelNanosleep` and `sceKernelSleep` check cancellation only before and after the host sleep, so an enabled request during those waits is delivered when the sleep ends. The asynchronous type acts like the deferred one, except at the two set calls. - [cpuset_setaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - the calling thread, another live guest thread selected by its guest TID, or the calling process is handled; other levels (ROOT/CPUSET) and other scopes (CPUSET/JAIL) throw as unmodelled, invalid levels or scopes return `EINVAL` and unknown thread or process IDs return `ESRCH`, following [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/kern_cpuset.c#L1281-L1286) thread selection. Bits outside `sceKernelGetAvailableCpumask` are dropped, and a mask with none of those bits fails with `EDEADLK`, as FreeBSD does when the new set has no CPU in common with the parent set. Guest-TID lookup and the update hold the thread registry lock so a join cannot release the target during access. Affinity is recorded for guest CPU reporting and child inheritance, not applied to host scheduling; PS5 behaviour has not been measured - [cpuset_getaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - the calling thread, another live guest thread selected by its guest TID, or the calling process is handled; other levels (ROOT/CPUSET) and other scopes (CPUSET/JAIL) throw as unmodelled, invalid levels or scopes return `EINVAL` and unknown thread or process IDs return `ESRCH`, following [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/kern_cpuset.c#L426-L437) guest-TID selection, not measured on PS5. For the process the mask is the union of the affinities of the live guest threads, as [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/kern_cpuset.c#L1150-L1156) ORs the mask of every thread in the process; threads the guest did not create through libkernel are not included, and the console result is unverified - [pthread_setaffinity_np](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - follows [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_affinity.c#L41-L62) libthr over `kern_cpuset_setaffinity` for the calling thread and any live guest thread: a size other than the 32-byte `cpuset_t` returns `ERANGE`, an unknown or finished thread `ESRCH`. Bits outside `sceKernelGetAvailableCpumask` are dropped and a mask with none of those bits returns `EDEADLK`, as `cpuset_setaffinity` does; the affinity is recorded on the guest thread and not applied to the host thread, and the console result is unverified -- [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` of a guest socket onto descriptors 0 to 2, whose host streams libc writes without the socket table, and on Windows duplicating a directory descriptor. Guest sockets share one socket object between the duplicates; the numbers come from the host descriptor table like those of files. The errno values for descriptor exhaustion are not verified against the console. On Windows a guest `dup2` onto descriptor 1 or 2 keeps host logging on an `_fdopen` of a duplicate, and the guest `FILE` stays on the descriptor that was replaced; opening that duplicate throws (#7791) -- [waitpid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns -1 with `ECHILD` (`EINVAL` for unknown options): nothing here creates a child process; child processes would need a real `waitpid`. +- [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` of a guest socket onto descriptors 0 to 2, whose host streams libc writes without the socket table. Guest sockets share one socket object between the duplicates; the numbers come from the host descriptor table like those of files. The errno values for descriptor exhaustion are not verified against the console. On Windows a guest `dup2` onto descriptor 1 or 2 keeps host logging on an `_fdopen` of a duplicate, and the guest `FILE` stays on the descriptor that was replaced; opening that duplicate throws (#7791) +- [waitpid and wait](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) reaps [posix_spawn](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) children, whose guest process IDs are the host's. On Linux the host call runs: the guest signal numbering applies in bits 8-15 for a stopped child and bits 0-6 for a killed one (7, 10, 12, 17, 18, 19, 20, 23, 29, 31 to 10, 30, 31, 20, 19, 17, 18, 16, 23, 12; Linux 16 and 30 are unmapped and throw), a continued status becomes 0x13, and guest `WCONTINUED` 4 becomes Linux 8. On Windows an unrecognized exit code keeps the guest `exit` byte layout, so `exit(-1)` reads as 255, and a structured-exception code maps to `SIGSEGV`, `SIGILL`, `SIGTRAP`, `SIGFPE` or `SIGABRT` (access violation/stack overflow, illegal instruction, breakpoint, division, fastfail). Windows has no stop/continue status; unknown `options` bits fail with `EINVAL`. Without an unreaped matching child, even after another thread reaps it, the call gives `ECHILD`. - [execv](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: replacing the process image is not supported, the same as `execvp`. PPSA97358 imports it and fails to load without it. - [execve](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws like `execvp`: replacing the process image is not supported. The console behavior for invalid arguments and executable path lookups is not modeled. - [sysarch](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - `AMD64_GET_FSBASE`, `AMD64_GET_GSBASE` and `AMD64_SET_GSBASE` follow FreeBSD 11's amd64 [`sysarch`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c) on Linux x86-64 hosts, through the host's `arch_prctl`. The GS base belongs to the calling thread, a base at or above `0x800000000000` (`VM_MAXUSER_ADDRESS`) returns `EINVAL`, and a null argument returns `EFAULT`. `AMD64_SET_FSBASE` throws, since guest and host code share the thread's FS base for their TLS. In a payload process on a PS5 (firmware 12.70), `I386_GET_FSBASE` (7) and `I386_GET_GSBASE` (9) wrote 4 bytes, the low 32 bits of the FS and GS bases, and failed with `EFAULT` for a null argument; here they do the same on Linux x86-64 hosts. There a GS base set by one thread left the main thread's unchanged, and threads made with `pthread_create` and `scePthreadCreate` started with their creator's GS base; here a guest thread's host thread is created by the creating thread, and Linux's [`copy_thread`](https://github.com/torvalds/linux/blob/v6.6/arch/x86/kernel/process.c#L182-L186) gives a new thread its creator's GS base. The operation numbers FreeBSD 11's amd64 `sysarch` has no case for returned `EINVAL` there for the nine tried, each with a null argument (2, 5, 6, 12, 127, 133, 134, 1000 and -1; 6 is `I386_VM86`, which its header names), as its [default case](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c#L324-L325) does; here every such number returns `EINVAL` on every host; the numbers other than those nine follow that default case and were not measured. `AMD64_GET_XFPUSTATE` (132) returned 0 there for lengths 0, 64, 128, 256 and 512 (512 was also the largest length a search accepted) and failed with `EINVAL` for 1024, 2048, 4096 and 8192 and with `EFAULT` for a null argument or address, and `I386_GET_XFPUSTATE` (11) failed with `EFAULT` for a null argument; both throw here. The other operations FreeBSD 11's amd64 `sysarch` handles, and every operation it handles on other hosts (Windows keeps its TEB in GS), throw. - [kern.proc.pathname](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns `/app0/eboot.bin` for pid -1 and the calling process, following FreeBSD's `sysctl_kern_proc_pathname`; the exact pathname on retail consoles remains unverified. +- [kern.proc.env](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - which variables, if any, the console starts an application with is unverified; `sysctl` {`CTL_KERN`, `KERN_PROC`, `KERN_PROC_ENV`, pid} returns 0 bytes, the empty start-up environment `environ` and libc's `getenv` also begin with, as FreeBSD 11's [`sysctl_kern_proc_env`](https://github.com/freebsd/freebsd-src/blob/releng/11.0/sys/kern/kern_proc.c) copies a process's start-up strings, not later `setenv` changes. Only the calling process's pid is accepted (`ESRCH` otherwise, -1 included); a write returns `EPERM`. PS5VR (PPSA99177) reads it for its own pid at start-up. - [kern.proc.vmmap](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctl` {`CTL_KERN`, `KERN_PROC`, `KERN_PROC_VMMAP`, pid} returns packed FreeBSD 11 `kinfo_vmentry` records (144 bytes each, an empty path) for the used ranges of the guest address-space arena, in address order, as [`kinfo_getvmmap`](https://github.com/freebsd/freebsd-src/blob/releng/11.0/lib/libutil/kinfo_getvmmap.c) reads them. A range is one record: the type is always `KVME_TYPE_DEFAULT`, the protection is `sceKernelVirtualQuery`'s for the range's first page (0 where it reports none), and offset, object, resident page counts, flags and path are 0. Only the calling process's pid is accepted (`ESRCH` otherwise, -1 included, as FreeBSD's `pget` gives); a write returns `EPERM` and a short buffer `ENOMEM`. Ranges outside the arena, which guest code cannot map, are not reported. The walk takes the arena lock once per range, not for the whole walk as FreeBSD holds the map lock, so an `mmap` or `munmap` on another thread can change the list between two records. Whether a PS5 application may read its own map, and what types and paths the console reports, is unverified -- [sysctl names a PS5 payload process gets ENOENT for](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` returns -1 with `ENOENT` for `hw.byteorder`, `hw.cacheline_size`, `hw.machine_arch`, `hw.usermem`, `kern.clockrate`, `kern.maxfiles`, `kern.maxfilesperproc`, `kern.maxproc`, `kern.ps_strings`, `kern.sched.name`, `kern.smp.maxcpus`, `kern.stackprot`, `kern.timecounter.hardware` and `machdep.cacheline_size`, and `sysctl` does the same for the MIBs {1, 6}, {1, 7}, {1, 12}, {1, 27}, {1, 32}, {6, 4} and {6, 11}. The buffer and the length are left alone. A payload process on a PS5 (firmware 12.70) gets this for a read, a size query, a null length and a null buffer with a null length, by name and by MIB. `sysctlnametomib` gives those seven MIBs, and returns `ENOENT` for `hw.cacheline_size`, `hw.usermem` and `machdep.cacheline_size` without touching the count or the first three words of the MIB, as the console does. A write to these names and MIBs throws, except for `machdep.cacheline_size`, which returns `ENOENT` as FreeBSD amd64 does for an OID it does not define; the console's answer to a write was not tried. The console's `sysctlnametomib` gives `kern.smp.maxcpus`, `kern.stackprot`, `kern.sched.name` and `kern.timecounter.hardware` the MIBs {1, 2147482816, 2147482814}, {1, 2147483151}, {1, 2147483182, 2147483181} and {1, 2147482838, 2147482834}, and its `sysctl` returns `ENOENT` for them; here `sysctlnametomib` throws for them. `hw.physmem`, `hw.realmem` and `vm.overcommit` return `ENOENT` on the console too but report values here, as the `hw.realmem` and `vm.overcommit` entries say. A payload process also gets `ENOENT` for `kern.anyps5_no_such_name`, which its `sysctlnametomib` does not resolve; other names still throw here. A title process was not measured +- [sysctl names measured absent on a PS5 payload](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - On firmware 12.70, a payload gets `ENOENT` for `hw.byteorder`, `hw.cacheline_size`, `hw.machine_arch`, `hw.usermem`, `kern.clockrate`, `kern.maxfiles`, `kern.maxfilesperproc`, `kern.maxproc`, `kern.ps_strings`, `kern.sched.name`, `kern.smp.maxcpus`, `kern.stackprot`, `kern.timecounter.hardware` and `machdep.cacheline_size`. `sysctl` returns `ENOENT` for MIBs {1, 6}, {1, 7}, {1, 12}, {1, 27}, {1, 32}, {6, 4} and {6, 11}; reads and size queries leave output buffers and lengths untouched. `sysctlnametomib` resolves the seven corresponding MIB names and returns `ENOENT` for `hw.cacheline_size`, `hw.usermem` and `machdep.cacheline_size`. Writes and title behavior are unmeasured. +- [Unknown sysctl names](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - A payload process on firmware 12.70 gets `ENOENT` for `kern.anyps5_no_such_name` through `sysctlbyname` and `sysctlnametomib`; here both name-based calls return `ENOENT` for unrecognized names, following FreeBSD name lookup. This is one payload measurement; title behavior for other unknown names is unverified. Numeric MIBs with no implementation still throw. - [hw.pagesize](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports the system page size (16384 bytes, PS5_PAGE_SIZE) as an integer through sysctl MIB {6, 7} (CTL_HW, HW_PAGESIZE) and the "hw.pagesize" sysctlbyname lookup, following FreeBSD's [`hw.pagesize`](https://github.com/freebsd/freebsd-src/blob/main/sys/kern/kern_mib.c#L177), a read-only `SYSCTL_INT` of `PAGE_SIZE`. The value on a PS5 follows the 16 KiB page size the rest of libkernel uses and is unmeasured. A write returns -1 with EPERM. - [hw.model](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports the [host CPU's name](../../../core/libs/prx/libkernel/System/src/HostCpu.cpp), built as FreeBSD 11 builds `hw.model`: the CPUID brand string (leaves 0x80000002 to 0x80000004) without leading spaces ([identcpu.c](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/x86/x86/identcpu.c#L683-L691)). Trailing padding stays, as it does there, so an AMD host's name ends in spaces. The name a PS5 reports is unknown. A write returns -1 with `EPERM`. A host without an x86 CPUID brand string, such as an Apple Silicon Mac, throws. PPSA99203 only prints it (`[console] hw.model: %s`) - [Host CPU check](../../../core/libs/prx/libkernel/System/src/HostCpu.cpp) (libkernel) - when libkernel loads on a Linux or Windows x86-64 host, it logs which of the PS5 CPU's user-mode extensions CPUID reports and throws, naming the host CPU, if any extension is missing that LLVM selects from plain C/C++ for the PS5's Zen 2 (`-march=znver2`, [feature list](https://github.com/llvm/llvm-project/blob/llvmorg-19.1.7/llvm/lib/Target/X86/X86.td#L1459-L1513)): SSE to SSE4.2, SSSE3, POPCNT, LZCNT, BMI1, BMI2, MOVBE, CMPXCHG16B, LAHF/SAHF, AVX, AVX2, FMA, F16C, and the operating system's AVX state in XCR0 (#3719). SSE4A, CLZERO and SHA, which `--to-intel` converts, and AES, PCLMULQDQ, RDRAND, RDSEED, ADX, CLFLUSHOPT, CLWB, RDPID, XSAVEOPT, XSAVEC and PREFETCHW, which compilers emit only for intrinsics or explicit prefetches, are logged but not required: a title that executes one of them on a host without it still stops with an illegal instruction. macOS hosts are not checked: some Rosetta versions execute extensions without reporting them through CPUID, but support differs by host. ANDN and RDRAND fault on an M1 with macOS 15.3.1; see the [Rosetta conversion limits](relinker.md) +- [sceKernelGetSystemExVersion, sysKernelGetLowerLimitSysexVersion, sysKernelGetLowerLimitUpdVersion](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) and [sceKernelGetTraceMemoryStats](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - follow libkernel.sprx, libkernel_sys.sprx and libkernel_web.sprx of firmware 13.42, read from disassembly and not measured on a console. None of them asks the kernel: the first writes an empty string to both outputs and returns a plain 22, not `SCE_KERNEL_ERROR_EINVAL`, when either is null; the lower limits write an empty string and a 32-bit 0; the trace memory statistics write 0 to each output that is not null. The meaning of the outputs is not known - [machdep.tsc_freq](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports the host TSC frequency that `sceKernelGetTscFrequency` measures, as a 64-bit value like FreeBSD 11's [`sysctl_machdep_tsc_freq`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/x86/x86/tsc.c#L658-L676). FreeBSD lets root write it to change the TSC timecounter's frequency; a write here returns -1 with `EPERM`. PPSA99203 reads it at start-up and measures the TSC itself when the value is 100 MHz or less - [getrlimit](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - `RLIMIT_CPU`, `RLIMIT_FSIZE`, `RLIMIT_CORE`, `RLIMIT_SBSIZE`, `RLIMIT_VMEM`/`AS`, `RLIMIT_NPTS`, `RLIMIT_SWAP`, `RLIMIT_KQUEUES` and `RLIMIT_UMTXP` report infinity as in FreeBSD's process-0 defaults, and `RLIMIT_DATA` mirrors the host limit; a payload process on a PS5 (firmware 12.70) reads 0x800000000 for both limits. `RLIMIT_NOFILE` reports, as both soft and hard limit, the number of descriptors after which AnyPS5 itself fails with `EMFILE`: on Windows the C runtime's 8192 low-level descriptors, which guest files, pipes and sockets all take, and as many stdio streams, which libkernel raises from 512 at load with [`_setmaxstdio`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio), on Linux the host's `RLIMIT_NOFILE`, whose soft limit libkernel raises to the hard limit at load, and on macOS the smaller of the host's hard limit and `kern.maxfilesperproc`, which libkernel sets as both limits at load, since macOS fails with `EMFILE` at `kern.maxfilesperproc` whatever the soft limit. A payload process on a PS5 (firmware 12.70) reads 13952 for both limits, while `getdtablesize` returns 12555 and the process can open descriptors up to 12554. XPSemu (PPSA97358) reads `RLIMIT_NOFILE` in QEMU's `os_setup_limits` and raises the soft limit to the hard one when they differ. `RLIMIT_STACK` (0x200000), `RLIMIT_RSS` and `RLIMIT_MEMLOCK` (infinity) and `RLIMIT_NPROC` (1604) report the soft and hard limits a payload process reads on a PS5 (firmware 12.70). Guest locking still follows the host: `mlock` and `sceKernelMlock` fail at the host's hard `RLIMIT_MEMLOCK` on Linux, and on Windows when the process working set cannot grow, although `RLIMIT_MEMLOCK` reports infinity. There the main thread's stack is 0x200000 as well (`pthread_attr_get_np`); here the main thread runs on the host thread's stack, whose size can differ. Whether a title process reads the same limits is unverified. -- [setrlimit](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) validates the resource and input in FreeBSD order, maps negative limits to infinity, and lowers a soft limit above the hard limit. A request other than `RLIMIT_NPROC` succeeds when its normalized values match `getrlimit`, or, for `RLIMIT_NOFILE`, when `rlim_max` does not exceed the current hard limit and `rlim_cur` does not exceed `rlim_max`, storing the updated limits for subsequent `getrlimit` queries; `RLIMIT_NPROC` always throws; descriptor allocation enforcement of the lowered soft limit is not implemented, and changing other limits throws because guest limit state is not stored. In a payload process on a PS5 (firmware 12.70), `setrlimit(RLIMIT_NOFILE)` lowers a requested 13952 to 12555, as the hard and as the soft limit; here a request within the current hard limit is stored as given and one above it throws; setting `RLIMIT_STACK`, `RLIMIT_RSS` and `RLIMIT_MEMLOCK` to their current values succeeds and changes nothing, as here; setting `RLIMIT_NPROC` to its current 1604 also succeeds but leaves 1443, as FreeBSD 11 would if the console's `maxproc` were 1604: process 0's limit starts at `maxproc` ([`init_main.c`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/init_main.c#L550-L551)), `maxprocperuid` starts at nine tenths of it ([`subr_param.c`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/subr_param.c#L258)), and `setrlimit` lowers an `RLIMIT_NPROC` limit above `maxprocperuid` to it ([`kern_resource.c`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/kern_resource.c#L714-L718)); 1604 × 9 / 10 gives 1443. Neither value is measured: `kern.maxproc` fails with `ENOENT` in the payload process. Here that call throws, because the limit is not stored. Java 25 LAN / DNS (PPSA99869) sets `rlim_cur = MIN(0x100000, rlim_max)` during VM initialization +- [EVFILT_TIMER modify with data 0](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) - a timer change with `EV_ADD`, `data` and `fflags` unset keeps the note's period instead of taking FreeBSD's zero default, which would fire without delay; whether the console keeps or zeroes the period is unverified. `EV_ENABLE` on a disabled timer reports the expiries it missed while disabled, as FreeBSD 11's `filt_timerexpire` counts a disabled note and keeps its phase: the missed count is summed in one step at the first wait and the next expiry keeps the note's phase, which `TimerDisableEnableRearm` pins; not measured on a console. A change that sets `EV_ADD`, `data` or `fflags` still throws, waiting for the measured re-add rules: FreeBSD 11's `kqueue_register` copies `data` and `fflags` onto the note and the new period applies at the next expiry. +- [setrlimit](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) validates the resource and input in FreeBSD order, maps negative limits to infinity, and lowers a soft limit above the hard limit. A request other than `RLIMIT_NPROC` succeeds when its normalized values match `getrlimit`, or, for `RLIMIT_NOFILE`, when `rlim_max` does not exceed the current hard limit and `rlim_cur` does not exceed `rlim_max`, storing the updated limits for subsequent `getrlimit` queries; descriptor allocation enforcement of the lowered soft limit is not implemented, and changing other limits throws because guest limit state is not stored. In a payload process on a PS5 (firmware 12.70), `setrlimit(RLIMIT_NOFILE)` lowers a requested 13952 to 12555, as the hard and as the soft limit; here a request within the current hard limit is stored as given and one above it throws; setting `RLIMIT_STACK`, `RLIMIT_RSS` and `RLIMIT_MEMLOCK` to their current values succeeds and changes nothing, as here; setting `RLIMIT_NPROC` to its current 1604 also succeeds but leaves 1443. `RLIMIT_NPROC` follows FreeBSD 11 by clamping both values to 1 through `maxprocperuid`, stores the result and returns it from `getrlimit`. The initial 1604 and `maxprocperuid` 1443 follow that measurement: process 0's limit starts at `maxproc` ([`init_main.c`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/init_main.c#L550-L551)), `maxprocperuid` starts at nine tenths of it ([`subr_param.c`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/subr_param.c#L258)), and `setrlimit` applies that bound ([`kern_resource.c`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/kern_resource.c#L714-L718)). `kern.maxproc` fails with `ENOENT` in the payload process, so the values cannot be queried independently, and process creation does not enforce the stored limit. Java 25 LAN / DNS (PPSA99869) sets `rlim_cur = MIN(0x100000, rlim_max)` during VM initialization +- [socket](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) turns off Winsock's report of an ICMP port-unreachable reply on datagram sockets (`SIO_UDP_CONNRESET`). Winsock fails the next receive on any datagram socket with `WSAECONNRESET`, where FreeBSD's [`udp_common_ctlinput`](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/netinet/udp_usrreq.c#L774-L798) reports the reply only to a socket connected to the sender. So on Windows a datagram socket connected to a port nobody listens on does not fail its next send or receive with `ECONNREFUSED` either - [execvp](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: replacing the process image is not supported. PPSA85300 imports it and fails to load without it; its `fork` and `vfork` fail with `ENOSYS` ([libc_missing.c](https://github.com/ZiZc3/VLC/blob/f3253020a64368153233ede29183014c0b6c8979/ps5/compat/libc_missing.c#L290-L292)), so the child process that would call it is never created. - [nmount](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throws: mounting file systems is not supported. Prospero Themes (PPSA90210) imports it and fails to load without it; it opens its theme library without calling it, and its strings (`/dev/ssd0.system_ex`, `/system_ex`, `exfatfs`, `fstype`, `fspath`, `from`, `ignoreacl`) point to remounting the console's `/system_ex` partition to write theme files there. -- [socketpair](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - the pair is the host's `AF_UNIX` pair on Linux and macOS and, on Windows, a loopback TCP connection for `SOCK_STREAM` or two connected UDP sockets for `SOCK_DGRAM`; `SOCK_SEQPACKET` throws on Windows and macOS, which has no such `AF_UNIX` pair. A datagram sent to a full receiver gets `ENOBUFS` on the console, waits or gets `EAGAIN` on Linux and is dropped on Windows, and on Windows a datagram `recv` after the peer closed reports `ECONNRESET` instead of the console's `ENOTCONN`, and a stream `send` after the peer closed reports `ECONNABORTED` instead of the console's `EPIPE`. `read` and `write` on a pair end go to the host like on every guest socket. `getpeername` on a pair end succeeds after the peer closed, where the console returns `ENOTCONN`, and `connect`, `bind` and `sendto` with an address on a pair end report an address-family error instead of the console's `EISCONN`. On Windows `poll` on a stream end reports `POLLHUP` without `POLLIN` after the peer's `shutdown(SHUT_WR)` or `close`, where the console reports `POLLIN`, with `POLLHUP` only after `close` +- [socketpair](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - the pair is the host's `AF_UNIX` pair on Linux and macOS and, on Windows, a loopback TCP connection for `SOCK_STREAM` or two connected UDP sockets for `SOCK_DGRAM`; `SOCK_SEQPACKET` throws on Windows and macOS, which has no such `AF_UNIX` pair. A datagram sent to a full receiver gets `ENOBUFS` on the console, waits or gets `EAGAIN` on Linux and is dropped on Windows, and on Windows a datagram `recv` after the peer closed reports `ECONNRESET` instead of the console's `ENOTCONN`, and the first stream `send` after the peer closed succeeds on the loopback connection, where the console returns `EPIPE`; the sends after it report `EPIPE`. `read` and `write` on a pair end go to the host as on every guest socket. `connect`, `bind` and `sendto` with an address on a pair end report an address-family error instead of the console's `EISCONN`. On Windows `poll` on a stream end reports `POLLIN` after the peer's `shutdown(SHUT_WR)` or `close`, as the console does after a `shutdown(SHUT_WR)`; after the peer's `close` the console reports `POLLIN` with `POLLHUP`, because `unp_drop` disconnects the other end, and the loopback connection cannot tell that from a shutdown, so no `POLLHUP` is reported - Guest sockets on macOS ([the equeue wait, kevent, fstat and ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp), [gethostbyname](../../../core/libs/prx/libScePosixForWebKit/Resolver.cpp)): macOS has no `ppoll`, so the wait uses `poll` with the timeout rounded up to a millisecond; it has no `POLLRDHUP`, so a stream that the peer closed is found by peeking one byte when it is readable; `FIONREAD` on a datagram socket, and the byte count that `fstat` and `kevent` report for it, is `SO_NREAD`, the size of the next datagram, because macOS adds the source address to `FIONREAD`; and `gethostbyname` replaces `gethostbyname_r`, which macOS lacks. -- [UDP recv/recvfrom](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) and [sceNetRecv/sceNetRecvfrom](../../../core/libs/prx/libSceNet/Export.cpp) translate Windows WSAEMSGSIZE into the copied byte count for datagram sockets, including MSG_PEEK, and retain the sender address. This follows FreeBSD 11.4 recv(2) and Winsock's documented truncated-datagram behavior; console behavior and existing title impact are unverified. - [recvmsg](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) loses received stream data on an output copy fault: outputs are checked after the host receive. FreeBSD 11.4 leaves unread stream data queued, though it also drops a datagram on a copy fault ([`soreceive_dgram`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L2308-L2312), [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1758-L1774)). A Linux `process_vm_readv` or `process_vm_writev` failure other than `EFAULT` throws - [MSG_DONTWAIT](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) in `send`, `sendto`, `sendmsg`, `recv`, `recvfrom` and `recvmsg` makes that one call non-blocking through the host's `MSG_DONTWAIT`, as FreeBSD 11.4's `soreceive_generic` returns `EWOULDBLOCK` for it (`sys/kern/uipc_socket.c`). Winsock has no per-call non-blocking flag, so on Windows the call first asks `WSAPoll` with a zero timeout whether the socket is ready and fails with `EAGAIN` when it is not. The check and the transfer are two host calls: a second thread that takes the data in between can leave a `recv` blocked, and a `send` of more bytes than the socket buffer holds can still block until it has sent them, where FreeBSD's `sosend_generic` returns a partial count +- [MSG_WAITALL](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) in `recv`, `recvfrom` and `recvmsg` on a stream socket is passed to the host, which returns when the buffer is full, the peer closes or an error occurs, as FreeBSD 11.4's [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1855-L1860) does; a datagram socket ignores it. It is not measured on a console, a nonblocking socket and a call that also passes `MSG_PEEK` follow the host's rules, and `SO_RCVTIMEO` bounds only the wait for the first data. Any other flag still fails with `EOPNOTSUPP`. +- [recv and recvfrom](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) answer some cases without the host. A zero-length receive on a stream socket returns 0, or `ENOTCONN` when it is not connected ([L1566-L1578](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1566-L1578)), as on a PS5 (firmware 13.60). On an empty datagram socket it returns 0 at once; the console was measured only with `MSG_DONTWAIT`, and the blocking case follows FreeBSD 11.4's `soreceive_dgram`, which returns 0 before waiting. `recvmsg` answers the empty datagram socket the same way but asks the host in the stream cases. A datagram another thread takes after the check makes a zero-length receive wait +- [recv and recvfrom after `shutdown(SHUT_RD)`](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) return 0 on Windows ([`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1556-L1562)) where Winsock fails with `EPIPE`; Linux and macOS still return data buffered before it, which the console discards. - [send, sendto](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - send and sendto without an address on an unconnected datagram socket map Windows `WSAENOTCONN` to FreeBSD's `EDESTADDRREQ` (39). -- [bind, connect, sendto](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) check a socket address length as FreeBSD 11.4's [`getsockaddr`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_syscalls.c) and the `sa_len` test of `tcp_usr_connect` and `in_pcbconnect` do: below 2 or an `AF_INET` address below 16 or an `AF_INET6` address below 28 bytes fails with `EINVAL`, above 255 with `ENAMETOOLONG`. An address longer than `sockaddr_in` or `sockaddr_in6` is still accepted where FreeBSD answers `EINVAL`, and an address of another family still fails with `EAFNOSUPPORT`, as the console's behaviour for these is not known. None of this was measured on a console. +- [bind, connect, sendto](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) check a socket address as a payload process on a PS5 (firmware 13.60) did on datagram sockets: a length below 2 fails with `EINVAL` and above 255 with `ENAMETOOLONG`; an `AF_INET` socket needs exactly 16 bytes and an `AF_INET6` socket 28, or `EINVAL`; another family fails with `EAFNOSUPPORT`; `sendto` on an `AF_INET` socket, and all three on an `AF_INET6` socket, check the length first. Unmeasured: `bind` and `connect` on an `AF_INET` socket check the family first, where FreeBSD 11.4's `tcp_usr_bind` and `tcp_usr_connect` check the length first; stream sockets follow the datagram order; `AF_UNSPEC` with `INADDR_ANY`, which FreeBSD's `bind` accepts, fails; `connect` on a listening stream socket fails with `EOPNOTSUPP` first, as `soconnect` does. A connected stream socket's `sendto` ignores the address once its length passes, as `tcp_usr_send` does +- [bind, connect](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) on an `AF_INET6` datagram socket with `IPV6_V6ONLY` cleared accept an IPv4-mapped address longer than 28 bytes and use its first 28, as FreeBSD 11.4's `udp6_bind` and `udp6_connect` hand it to `in_pcbbind` and `in_pcbconnect` as a `sockaddr_in` without the length test of `in6_pcbbind`; `sendto` still fails it with `EINVAL`, as `udp6_send` tests the length first. The console was measured only with `::1` and `IPV6_V6ONLY` at its default, so this case is not measured - [read, write and vector I/O](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on a guest socket use `recv` and `send` without flags; `sceKernelReadv` and `sceKernelWritev` gather or scatter through one socket operation, preserving stream short reads and datagram boundaries. The positioned `pread`, `pwrite`, `preadv` and `pwritev` forms, and their `sceKernel` forms, return `ESPIPE` for an open guest socket and `EBADF` for a closed one. `EPIPE` from a write raises the host's `SIGPIPE` on Linux unless the socket has `SO_NOSIGPIPE` set, and the guest cannot ignore it: `signal(SIGPIPE, SIG_IGN)` and `sigaction` record the disposition, as they do for every number from 1 to 128, but only the six signals with host equivalents (2, 4, 6, 8, 11 and 15) take effect on the host or are delivered, so the host `SIGPIPE` is not stopped; nothing is raised on Windows; a `write` longer than `INT_MAX` bytes is `EMSGSIZE` where the console sends it. AIO on a guest socket still goes to the host, which has no such descriptor -- [Socket descriptor numbers](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel, libSceNet) - `socket`, `accept`, `socketpair`, `dup`, `sceNetSocket` and `sceNetAccept` take the lowest free number of the host descriptor table that files use, as FreeBSD 11.4 `fdalloc` takes the lowest free entry of the one process table (`kern_descrip.c`), so every number stays below `FD_SETSIZE` (1024) while fewer than 1024 descriptors are open. The number holds a zero-access `NUL` handle on Windows and a duplicate of the host socket on Linux, and the socket tables map it to the socket. The host table also holds the runtime's own files, so the numbers differ from the console's; the first number `socket` and `sceNetSocket` return on a console and its `getdtablesize` are not measured. Paths that bypass the socket tables see the placeholder: libc `fdopen` on a socket opens it, and libkernel `close` of a libSceNet socket releases the number but leaves the libSceNet socket open. Palworld (PPSA23126), The Mound (PPSA26223) and Dying Light 2 (PPSA02262) set socket bits in a 1024-bit `fd_set` +- [Socket descriptor numbers](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel, libSceNet) - `socket`, `accept`, `socketpair`, `dup`, `sceNetSocket` and `sceNetAccept` take the lowest free number of the host descriptor table that files use, as FreeBSD 11.4 `fdalloc` takes the lowest free entry of the one process table (`kern_descrip.c`), so every number stays below `FD_SETSIZE` (1024) while fewer than 1024 descriptors are open. The number holds a zero-access `NUL` handle on Windows and a duplicate of the host socket on Linux, and the socket tables map it to the socket. The host table also holds the runtime's own files, so the numbers differ from the console's; the first number `socket` and `sceNetSocket` return on a console and its `getdtablesize` are not measured. Paths that bypass the socket tables see the placeholder: libc `fdopen` on a socket opens it, and libkernel `sceKernelClose` of a libSceNet socket releases the number but leaves the libSceNet socket open. Palworld (PPSA23126), The Mound (PPSA26223) and Dying Light 2 (PPSA02262) set socket bits in a 1024-bit `fd_set` - [fstat on a guest socket](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) fills the fields FreeBSD 11's soo_stat and pru_sense_null fill and zeroes the rest: S_IFSOCK, the read bits while the receive side is open or data is queued, the write bits while the send side is open, st_size and st_blksize. Only this process's shutdown closes a side here; a peer's shutdown, close or reset does not clear the bits as FreeBSD's socantrcvmore and socantsendmore do. st_size is the host's FIONREAD, which for a Linux datagram socket is the size of the next datagram where FreeBSD counts every queued byte, and st_blksize is the host's SO_SNDBUF, which Linux reports as twice the size that was set. A socketpair end reports st_dev and st_ino 0, where FreeBSD's uipc_sense reports NODEV and a per-socket number -- [poll](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits on guest sockets and pipes, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. A libSceNet socket joins the same host wait through the native socket libSceNet lends to libkernel. `WSAPoll` rejects `POLLPRI`, so on Windows a socket's `POLLPRI` is asked as `POLLRDBAND` and `POLLIN` as `POLLRDNORM`; Winsock reports urgent data until it is read with `MSG_OOB`, where FreeBSD 11.4's `sopoll_generic` stops once the normal stream passes the mark. Guest `send` and `recv` refuse `MSG_OOB` with `EOPNOTSUPP`, so urgent data can only come from an outside peer and Windows keeps reporting `POLLPRI` after the stream passes the mark. A pipe follows FreeBSD's `pipe_poll`: the read end is readable when data is buffered, either end reports `POLLHUP` once the other end is closed, with the requested `POLLIN` and `POLLRDNORM` bits and without `POLLOUT`, which `pollscan` clears, and the write end is writable when `PIPE_BUF` (512) bytes are free on Windows, or when the host kernel says so on Linux. `POLLOUT` on a read end throws: FreeBSD opens both ends for reading and writing and reports it writable, and host pipes are one-way. Windows pipes cannot be waited on, so while a pipe is polled `poll` checks it with `NtQueryInformationFile` and waits on the sockets in slices of 1 ms. A regular file, a directory or a file read from app0.zar is always ready for `POLLIN`, `POLLRDNORM` and `POLLOUT` and never for `POLLPRI`, `POLLRDBAND` or `POLLWRBAND`, as FreeBSD 11.4's [`vop_stdpoll`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/vfs_default.c#L563-L574) and `vop_nopoll` answer every event `poll` accepts; which of the two the console's filesystems use is not known, and they agree on these events. `/dev/random` and `/dev/urandom` follow FreeBSD 11.4's [`randomdev_poll`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/dev/random/randomdev.c#L274-L285) for a seeded device: a request with `POLLIN` or `POLLRDNORM` gets only those bits back, and any other request gets every requested bit. Open descriptors 0 to 2 follow the console model `select` uses: descriptor 0 never reports an event and descriptors 1 and 2 report `POLLOUT`, whatever the host attaches to them. A closed descriptor reports `POLLNVAL`, and an open descriptor that is neither a guest socket, a libSceNet socket, a pipe, a file, a directory, the random device nor descriptor 0 to 2 (other devices, a kqueue) throws, as do event bits other than `POLLIN`, `POLLPRI`, `POLLOUT`, `POLLRDNORM`, `POLLRDBAND` and `POLLWRBAND` (the `POLLERR`, `POLLHUP` and `POLLNVAL` bits are ignored in `events`), an infinite timeout with no socket or pipe to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked +- [poll](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits on guest sockets and pipes, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. A libSceNet socket joins the same host wait through the native socket libSceNet lends to libkernel. `WSAPoll` rejects `POLLPRI`, so on Windows a socket's `POLLPRI` is asked as `POLLRDBAND` and `POLLIN` as `POLLRDNORM`; Winsock reports urgent data until it is read with `MSG_OOB`, where FreeBSD 11.4's `sopoll_generic` stops once the normal stream passes the mark. Guest `send` and `recv` refuse `MSG_OOB` with `EOPNOTSUPP`, so urgent data can only come from an outside peer and Windows keeps reporting `POLLPRI` after the stream passes the mark. A pipe follows FreeBSD's `pipe_poll`: the read end is readable when data is buffered, either end reports `POLLHUP` once the other end is closed, with the requested `POLLIN` and `POLLRDNORM` bits and without `POLLOUT`, which `pollscan` clears, and the write end is writable when `PIPE_BUF` (512) bytes are free on Windows, or when the host kernel says so on Linux. `POLLOUT` on a read end throws: FreeBSD opens both ends for reading and writing and reports it writable, and host pipes are one-way. Windows pipes cannot be waited on, so while a pipe is polled `poll` checks it with `NtQueryInformationFile` and waits on the sockets in slices of 1 ms. A kqueue is ready for `POLLIN` and `POLLRDNORM` while one of its events is triggered and for no other event, as FreeBSD 11.4's [`kqueue_poll`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_event.c#L1900-L1924) answers. A kqueue cannot be waited on either, so on every host `poll` checks it every millisecond and waits on the other descriptors in slices of 1 ms. A kqueue closed during the wait reports `POLLNVAL`, and one whose number was reused is polled again with the rest of the timeout, as FreeBSD 11.4's [`pollrescan`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_generic.c#L1499-L1519) looks the descriptor up again. A close that has already removed the kqueue but not yet closed its host descriptor is seen as a descriptor of unknown kind, which throws. A regular file, a directory or a file read from app0.zar is always ready for `POLLIN`, `POLLRDNORM` and `POLLOUT` and never for `POLLPRI`, `POLLRDBAND` or `POLLWRBAND`, as FreeBSD 11.4's [`vop_stdpoll`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/vfs_default.c#L563-L574) and `vop_nopoll` answer every event `poll` accepts; which of the two the console's filesystems use is not known, and they agree on these events. `/dev/random` and `/dev/urandom` follow FreeBSD 11.4's [`randomdev_poll`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/dev/random/randomdev.c#L274-L285) for a seeded device: a request with `POLLIN` or `POLLRDNORM` gets only those bits back, and any other request gets every requested bit. At descriptors 0 to 2, regular files, directories and archived files use vnode readiness too, including host redirection (`< file`, `> log.txt` or `2> log.txt`). A standard descriptor that is not a socket, a kqueue, a random device or a vnode still takes the console fallback: descriptor 0 never reports an event and descriptors 1 and 2 report `POLLOUT`. A closed descriptor reports `POLLNVAL`, and an open descriptor that is neither a guest socket, a libSceNet socket, a pipe, a file, a directory, the random device nor descriptor 0 to 2 (other devices) throws, as do event bits other than `POLLIN`, `POLLPRI`, `POLLOUT`, `POLLRDNORM`, `POLLRDBAND` and `POLLWRBAND` (the `POLLERR`, `POLLHUP` and `POLLNVAL` bits are ignored in `events`), an infinite timeout with no socket, pipe or kqueue to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked +- [poll](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) on Windows reports a guest stream socket whose peer closed as readable and adds `POLLHUP` only once the socket has shut its own sending side, as FreeBSD 11.4's [`sopoll_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L3080-L3086) does; `WSAPoll` reports `POLLHUP` alone in both cases. A reset or refused connection (`POLLHUP` with `POLLERR`) keeps `WSAPoll`'s flags, a libSceNet socket keeps them too, and a call that asks for no event still returns early on the peer's close with nothing reported. Not measured on a console. +- [poll on a stream socket](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - on Linux a socket that reports `POLLHUP` reports it without `POLLOUT`, as FreeBSD 11.4's `pollscan` clears `POLLOUT` whenever it reports `POLLHUP`; Linux, like FreeBSD, reports `POLLHUP` only once both of the socket's directions are shut. Windows keeps the flags `WSAPoll` reports. Not measured on a console - [kevent EV_CLEAR on a socket or a pipe](../../../core/libs/prx/libkernel/Equeue/src/EdgeWatch.cpp) (libkernel) - FreeBSD 11 activates an `EV_CLEAR` knote when `kqueue_register` finds it ready, which every `EV_ADD` or `EV_ENABLE` of an enabled knote does again, and on each `KNOTE` the socket or pipe raises (`sorwakeup`, `sowwakeup`, `pipeselwakeup`); [`kqueue_scan`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_event.c) checks the filter again before reporting, drops a knote that is no longer ready and deactivates a reported one until the next activation. On Linux each such knote registers the host socket or pipe with its own epoll instance in edge-triggered mode (`EPOLLIN | EPOLLRDHUP` for `EVFILT_READ`, `EPOLLOUT` for `EVFILT_WRITE`), and a wakeup of the host descriptor with that mask stands for a `KNOTE`: the knote is then checked with the same readiness test as a level-triggered one, `NOTE_LOWAT` mark included, and a wait blocks on the epoll descriptor instead of the socket, so a socket that stays readable does not wake it. The host's wakeups are not FreeBSD's: FreeBSD raises `sowwakeup` whenever send buffer space is freed, while Linux TCP wakes `EPOLLOUT` waiters only once the socket is writable again after a write found it full (`SOCK_NOSPACE`, [`tcp_check_space`](https://github.com/torvalds/linux/blob/v6.8/net/ipv4/tcp_input.c#L5639)), so a TCP writer that never fills its buffer sees fewer `EVFILT_WRITE` events than on FreeBSD; a host stack may also coalesce data wakeups, so the number of times a busy socket fires can differ. Windows and macOS throw, as the host there has no edge-triggered wait over these descriptors that is wired up; `EV_CLEAR` on the timer, user and vnode filters does not use this and works everywhere. - [kevent on a pipe](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) - host pipes are one-way, where FreeBSD's [`kern_pipe`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/sys_pipe.c#L422) opens both ends for reading and writing, so `EVFILT_READ` on the write end and `EVFILT_WRITE` on the read end throw; on macOS `EVFILT_WRITE` on a pipe throws, as the host has no call for a pipe's capacity. `EVFILT_WRITE` reports the free space of the host buffer, not of FreeBSD's 16 KiB one, and on Linux fires when the host says the pipe is writable rather than at `PIPE_BUF` (512) free bytes. Windows pipes cannot be waited on, so a kqueue waiting on one checks it every 1 ms. Otherwise [`filt_piperead`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/sys_pipe.c#L1787-L1828) and `filt_pipewrite` are followed: `EV_EOF` once the other end closes, `EPIPE` for a write event added after the read end closed, and `NOTE_LOWAT` ignored. On Windows `fcntl` cannot make a pipe write end non-blocking, so the full-pipe and drain case of `EVFILT_WRITE` is tested on Linux only and the `NtQueryInformationFile` write quota is checked there only for the free space, `EV_EOF` and `EPIPE`. - [kevent on a kqueue](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) - `EVFILT_READ` on a kqueue counts the watched queue's enabled notes that are ready when it is evaluated, where [`filt_kqueue`](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/sys/kern/kern_event.c#L363-L370) reports `kq_count`, the queued notes: a queued note counts until that kqueue is scanned, even once disabled or no longer ready (a socket or pipe read empty, a user note cleared, a watched kqueue drained), so FreeBSD can report it readable where this reports nothing. A queued `EV_ONESHOT` note is reported there with its last count and deleted; here it is evaluated again and, if the watched queue is empty by then, neither reported nor deleted. `EV_CLEAR` throws, as FreeBSD re-arms it only when the watched kqueue queues a note. A cycle of watches throws; a note on the queue itself is `EINVAL`. Not measured on a console -- [Socket error numbers](../../../core/libs/prx/libkernel/Socket/include/SocketError.hpp) (libkernel) - a host socket error becomes the FreeBSD 11 `errno.h` number of the same name; the numbers are not checked on the console. Winsock `WSAESHUTDOWN` becomes `EPIPE`, as a send after `shutdown` does on FreeBSD, and a host error with no FreeBSD socket counterpart becomes `EIO`. A nonblocking `connect` in progress reports `EINPROGRESS` on Linux but `EAGAIN` on Windows (`WSAEWOULDBLOCK`), where FreeBSD reports `EINPROGRESS`. On Windows a refused nonblocking `connect` is never reported (`SO_ERROR` is unsupported), so a repeated one keeps reporting `EALREADY` where FreeBSD reports `ECONNREFUSED`. A receive after `shutdown(SHUT_RD)` reports `EPIPE` on Windows where FreeBSD returns 0 -- [getsockopt, setsockopt](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) on a guest socket handle the SOL_SOCKET level, `TCP_NODELAY` and `IPV6_V6ONLY`, after FreeBSD 11.4's [`sosetopt` and `sogetopt`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c): `SO_REUSEADDR`, `SO_KEEPALIVE`, `SO_DONTROUTE`, `SO_BROADCAST`, `SO_OOBINLINE` and `SO_LINGER` act on the host socket, and `SO_ERROR`, `SO_TYPE`, `SO_ACCEPTCONN`, `SO_SNDBUF` and `SO_RCVBUF` are read. A boolean option reads back as its own bit, as FreeBSD returns `so_options & option`. Winsock does not clear a socket's pending error when it is read, so on Windows the library reports a pending error once and shows it again only after a new `connect`; a second failure with the same code on the same socket without a `connect` in between would be hidden. `SO_SNDBUF` and `SO_RCVBUF` read the host's value, which is not the size FreeBSD reports (Linux doubles it). `SO_NOSIGPIPE` is kept per socket and reads back as its bit. On Linux a `send`, `sendto`, `sendmsg` or `write` on that socket passes `MSG_NOSIGNAL` to the host, as [`kern_sendit`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_syscalls.c#L890-L895) and [`soo_write`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_socket.c#L147-L151) leave out the signal. An accepted socket takes it from its listener, as [`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L603) copies the options. `SO_NBIO` (`0x1200`) is the console's own option, not FreeBSD's. It switches the socket between blocking and non-blocking as `FIONBIO` does and reads back as 0 or 1, as `sceNetSetsockopt` and `sceNetGetsockopt` treat it here; what the console reads back for it is not measured. `TCP_NODELAY` (`IPPROTO_TCP`, 1) is set on the host socket and reads back as `TF_NODELAY` (4), as [`tcp_default_ctloutput`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/netinet/tcp_usrreq.c#L1860-L1861) returns it. The `IPPROTO_TCP` level on a datagram or `AF_UNIX` socket fails with `EINVAL`, as [`ip_ctloutput`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/netinet/ip_output.c#L943-L944) and [`uipc_ctloutput`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_usrreq.c#L1228-L1229) answer it, and an accepted socket takes `TCP_NODELAY` through the host. `SO_SNDTIMEO` and `SO_RCVTIMEO` follow [`sosetopt`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L2657-L2689) and [`sogetopt`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L2846-L2850). A 16-byte `timeval` with a negative field or 1000000 microseconds or more fails with `EDOM`, and more than `INT32_MAX` seconds becomes the longest wait. The value is kept and read back through FreeBSD's 32.32 fixed-point conversion ([`tvtosbt`, `sbttotv`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/sys/time.h#L178-L286)), so half a second reads back as 499999 microseconds and one microsecond as 0. A `recv`, `recvfrom`, `recvmsg`, `read` or `readv`, and a `send`, `sendto`, `sendmsg`, `write` or `writev`, on a blocking socket then waits that long for the host to report the socket readable or writable, and fails with `EWOULDBLOCK`; a call with `MSG_DONTWAIT` does not wait. An accepted socket takes both from its listener ([`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L629-L630)). The wait covers only the first transfer: once the socket is writable the host send runs without a limit, where FreeBSD stops a send that cannot finish and returns the bytes sent so far, and a receive whose data another thread takes between the wait and the host call blocks again. `sendfile` ignores the send timeout. The host wait has millisecond steps, so a shorter timeout lasts up to a millisecond longer. A null value fails with `EINVAL`, where FreeBSD's [`kern_setsockopt`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_syscalls.c#L1414-L1415) returns `EFAULT`. `IPV6_V6ONLY` (`IPPROTO_IPV6`, 27) on an `AF_INET6` socket is set on the host socket and reads back as 0 or 1, after FreeBSD's [`ip6_ctloutput`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/netinet6/ip6_output.c#L1716-L1732): a value that is not exactly an `int` fails with `EINVAL` ([L1572](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/netinet6/ip6_output.c#L1572)), as does setting it on a bound socket, which the host refuses too. `socket` turns it on for every `AF_INET6` socket, as FreeBSD's default `net.inet6.ip6.v6only` does, and Java turns it off on the sockets it opens: Java 25 Advanced Network (PPSA99871) stops at `java.net.SocketException: Protocol not available` without it. The `IPPROTO_IPV6` level on an `AF_INET` or `AF_UNIX` socket fails with `EINVAL`, as `ip_ctloutput` and `uipc_ctloutput` answer it. Every other SOL_SOCKET option (`SO_DEBUG`, `SO_USELOOPBACK`, `SO_REUSEPORT` and `SO_TIMESTAMP`, which FreeBSD stores, the low-water marks among them; PPSA99871 sets `SO_REUSEPORT` and carries on when it fails), every other `IPPROTO_TCP` option (`TCP_MAXSEG`, `TCP_KEEPIDLE`, `TCP_KEEPINTVL` and `TCP_KEEPCNT` among them), every other `IPPROTO_IPV6` option ([L1928-L1929](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/netinet6/ip6_output.c#L1928-L1929), [L2158-L2159](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/netinet6/ip6_output.c#L2158-L2159)) and the `IPPROTO_IP` level fail with `ENOPROTOOPT`. None of this was measured on a console. -- [connect to AF_UNSPEC](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux a socket bound to an explicit port keeps its local address as well (`127.0.0.1:` stays `127.0.0.1:`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had -- [connect](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - `connect` to port 0 on an unconnected `AF_INET` or `AF_INET6` stream or datagram socket fails with `EADDRNOTAVAIL` (49), as FreeBSD 11.4's `in_pcbconnect_setup()` and `in6_pcbconnect()` do. A connected stream socket keeps its host `EISCONN` answer. Known gaps: on an already connected datagram socket `connect` to port 0 keeps the old peer, where FreeBSD's `soconnect()` disconnects first and then fails with 49; `sendto` and `sendmsg` to port 0 are sent, where `udp_output()` fails with 49 on the unbound path but FreeBSD can send to port 0 from a socket bound to a specific address and port. The answers are taken from the FreeBSD source and are not measured on a console. [sceNetConnect](../../../core/libs/prx/libSceNet/Export.cpp) is not changed -- [msync](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) follows FreeBSD 11's `kern_msync` and `vm_map_sync`, not measured on the console: the address is rounded down to the page, a range that wraps is `EINVAL`, as is `MS_ASYNC` with `MS_INVALIDATE`, and a range whose start or any page is not mapped (as `sceKernelVirtualQuery` reports it, reservations included) is `ENOMEM`; a page unmapped from inside a larger mapping still counts as mapped there, since `sceKernelVirtualQuery` keeps reporting the whole mapping. The guest has no file mappings, so a successful call writes nothing back. `MS_INVALIDATE` on a mapped range throws: FreeBSD returns `EBUSY` when the range holds `mlock`ed pages, and locked pages are not tracked +- [Socket error numbers](../../../core/libs/prx/libkernel/Socket/include/SocketError.hpp) (libkernel) - a host socket error becomes the FreeBSD 11 `errno.h` number of the same name; the numbers are not checked on the console. Winsock `WSAESHUTDOWN` becomes `EPIPE`, as a send after `shutdown` does on FreeBSD, and a host error with no FreeBSD socket counterpart becomes `EIO`. On Windows a refused nonblocking `connect` is never reported (`SO_ERROR` is unsupported), so a repeated one keeps reporting `EALREADY` where FreeBSD reports `ECONNREFUSED`. A receive after `shutdown(SHUT_RD)` reports `EPIPE` on Windows where FreeBSD returns 0 +- [getsockopt, setsockopt](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - a guest socket has the SOL_SOCKET level, eight `IPPROTO_TCP` options, `IPV6_V6ONLY` and ten `IPPROTO_IP` options. `SO_ACCEPTFILTER` (0x1000) fails with `ENOPROTOOPT` (42), where a payload process on a PS5 (firmware 13.60) failed it with `EINVAL` for a set and a get on a non-listening socket, as FreeBSD 11.4 does; the other options of these levels fail with `ENOPROTOOPT` and were not all measured. `SO_KEEPALIVE`, `SO_DONTROUTE`, `SO_BROADCAST`, `SO_OOBINLINE` and `SO_LINGER` act on the host socket, and a boolean one reads back as its own bit, as there. Winsock does not clear a pending error when `SO_ERROR` reads it, so on Windows a pending error is reported once and again only after a new `connect`; a second failure with the same code without a `connect` in between is hidden +- [TCP_NOPUSH, TCP_NOOPT, TCP option 0x80, IP_RECVDSTADDR, IP_RECVIF, IP_RECVTTL](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - kept and read back as a payload process on a PS5 (firmware 13.60) did: the TCP ones as the bits 0x1000, 0x8 and 0x10000 (so 0x80 is not FreeBSD's `TCP_KEEPINIT` there), the IP ones as 0 or 1. None of them changes what the socket does: `TCP_NOPUSH` does not hold back sends, and the IP ones add no control message to `recvmsg`. `IP_RECVTTL` was only read, never set, on the console, and an accepted socket takes `TCP_NOPUSH` from its listener as FreeBSD 11.4's `syncache_socket` does, which was not measured +- [SO_DEBUG, SO_REUSEADDR, SO_USELOOPBACK, SO_REUSEPORT, SO_TIMESTAMP, SO_BINTIME and option 0x10000](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - kept as bits that read back as on a PS5 (firmware 13.60, payload process), and options 0x4000 and 0x8000 are accepted and cannot be read, as there; only `SO_REUSEADDR` and `SO_REUSEPORT` change what the socket does. Both are set on the host, `SO_REUSEPORT` as `SO_REUSEADDR` on Windows, which has no `SO_REUSEPORT` and there also lets the socket bind over one that did not set it; Linux spreads unicast datagrams across the sockets, where FreeBSD gives each to one. Option 0x10000 is only kept: if the console treats it as FreeBSD 12's `SO_REUSEPORT_LB`, two sockets that set it share a port there, where the second `bind` here fails with `EADDRINUSE`. An accepted socket takes the bits from its listener, as FreeBSD 11.4's `sonewconn` copies `so_options`; that was not measured +- [IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_MULTICAST_IF](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - set on the host socket after the checks a PS5 (firmware 13.60, payload process) showed: `EOPNOTSUPP` on a stream socket, `EINVAL` for a short value or a group outside 224.0.0.0/4, `EADDRINUSE` for a group joined twice and `EADDRNOTAVAIL` for one not joined, tracked per socket. An interface the host lacks fails with the host's error (`EADDRNOTAVAIL` on the console). A group joined through `INADDR_ANY` and through that interface's address counts as two here. Not measured, after FreeBSD 11.4: a drop through `INADDR_ANY` leaves the first such group on any interface (`inp_leave_group`), and the other multicast options fail with `EOPNOTSUPP` on a stream socket (`inp_setmoptions`). An `ip_mreqn` with a non-zero index or address throws, as only a zeroed one was measured, and so does reading `IP_MULTICAST_IF` into 12 bytes, where FreeBSD returns an `ip_mreqn` +- [TCP_MAXSEG](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - a connected socket reads 536 (1220 for `AF_INET6`) until a segment is set, where a payload process on PS5 firmware 13.60 read the negotiated segment (16344 over loopback, lo0's MTU less 40) on both ends; the segment is not set on the host socket, and a listening socket was not measured. Otherwise it follows that console: a fresh socket reads 536, or 1220 for `AF_INET6`, and fails every set with `EINVAL` (175, 176, 200, 400, 536, 537, 1460 tried); a connected one fails a segment above the current one or below 176, and stores the rest raised to 216 (176 and 200 both read back 216), as FreeBSD 11.4's `tcp_default_ctloutput` with `V_tcp_minmss` does +- [TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - kept per stream socket and read back as a payload process on a PS5 (firmware 13.60) did: 7200, 75 and 8 at first, 0 restoring a keepalive default, and a count stored as an unsigned value. They are not set on the host socket, so the host's keepalive timers are its own while `SO_KEEPALIVE` is applied there. An interval above 4294967 seconds fails with `EINVAL`, after FreeBSD 11.4's `tcp_default_ctloutput` with `hz` taken as 1000, and an accepted socket takes the keepalive values from its listener as `syncache_socket` copies them; neither was measured +- [IP_TTL, IP_TOS, IP_MULTICAST_TTL, IP_MULTICAST_LOOP](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - kept per `AF_INET` socket with the defaults (64, 0, 1, 1), the low-byte storage of `IP_TTL` and `IP_TOS`, the one-byte forms and the `EINVAL` above 255 of the multicast options that a payload process on a PS5 (firmware 13.60) showed. `IP_TTL` and the multicast options are also set on the host socket, except a time to live of 0, which hosts refuse; `IP_TOS` is only kept, so the host's packets carry its own type of service, and the host's default time to live (128 on Windows) is not changed to 64. An accepted socket takes `IP_TTL` and `IP_TOS` from its listener, as FreeBSD's `syncache_socket` does, and an `int` `IP_MULTICAST_LOOP` keeps only its low byte, as FreeBSD 11.4's `inp_setmoptions` does; neither was measured. Winsock applies `IP_MULTICAST_LOOP` on the receiving socket, not the sender, so on Windows the receiver's setting decides loopback +- [SO_SNDBUF, SO_RCVBUF, SO_SNDLOWAT, SO_RCVLOWAT](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - kept per socket with the defaults, the 524288-byte limit, the `ENOBUFS` above 5592405 bytes and the low-water clipping of a payload process on a PS5 (firmware 13.60); the host socket gets the size asked for, so a request above the limit still enlarges the host's buffer. The host's real buffering, FreeBSD's automatic growth of a connected TCP socket's buffers, and a low-water mark's effect on `select`, `poll` and a receive are not reproduced. An accepted socket takes all four from its listener, as FreeBSD's [`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L603) does; that was not measured +- [SO_NOSIGPIPE](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - kept per socket and read back as its bit. On Linux a `send`, `sendto`, `sendmsg` or `write` on that socket passes `MSG_NOSIGNAL` to the host, as [`kern_sendit`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_syscalls.c#L890-L895) and [`soo_write`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_socket.c#L147-L151) leave out the signal, and an accepted socket takes it from its listener, as [`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L603) copies the options. Not measured on a console +- [SO_SNDTIMEO, SO_RCVTIMEO](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - the value checks and the 32.32 fixed-point read-back follow [`sosetopt`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L2657-L2689), as a payload process on a PS5 (firmware 13.60) answered them. A receive or send on a blocking socket waits that long for the host to report the socket readable or writable and fails with `EWOULDBLOCK`, but only before the first transfer: once writable the host send has no limit, where FreeBSD returns the bytes sent so far, and a receive whose data another thread takes after the wait blocks again. `sendfile` ignores the send timeout, the host wait has millisecond steps, and an accepted socket takes both from its listener +- [IPV6_V6ONLY](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) - the `IPPROTO_IPV6` option 27 is set on the host socket and reads back as 0 or 1, after FreeBSD's [`ip6_ctloutput`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/netinet6/ip6_output.c#L1716-L1732): a value that is not exactly an `int` fails with `EINVAL`, as does setting it on a bound socket, which the host refuses too. `socket` turns it on for every `AF_INET6` socket, as a payload process on a PS5 (firmware 13.60) reported, and Java turns it off on the sockets it opens: Java 25 Advanced Network (PPSA99871) stops at `java.net.SocketException: Protocol not available` without it. The value checks and the bound-socket answer were not measured +- [connect to AF_UNSPEC](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux and on Windows a socket bound to an explicit port keeps its local address as well (`127.0.0.1:` stays `127.0.0.1:`), because a bound socket cannot be bound again, and `getsockname` reports that host address. On Windows a `sendto` to a non-loopback address from such a socket then fails with `ENETUNREACH` (Winsock 10051, measured on Windows 10), where FreeBSD sends it from the wildcard address. One bound to port 0 is bound again to the wildcard address with the port it had +- [connect](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - `connect` to port 0 on an `AF_INET` or `AF_INET6` stream or datagram socket fails with `EADDRNOTAVAIL` (49), and on a connected datagram socket it disconnects first, as on a PS5 (firmware 13.60) and in FreeBSD 11.4's `soconnect()`. A connected stream socket keeps its host `EISCONN` answer. Known gap: `sendto` and `sendmsg` to port 0 on an `AF_INET` datagram socket fail with 49 (measured for an unbound socket) unless the socket is bound to a specific address and port and the destination is neither `INADDR_ANY` nor `INADDR_BROADCAST`, which is the path where `udp_output()` skips `in_pcbconnect_setup()` and FreeBSD sends to port 0. There the call returns the length without the host, as Linux's `udp_sendmsg` refuses port 0 with `EINVAL`; an `AF_INET6` socket still goes to the host. [sceNetConnect](../../../core/libs/prx/libSceNet/Export.cpp) is not changed +- [msync](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) follows FreeBSD 11's `kern_msync` and `vm_map_sync`: the address is rounded down to the page, a range that wraps is `EINVAL`, as is `MS_ASYNC` with `MS_INVALIDATE`, and a range whose start or any page is not mapped (as `sceKernelVirtualQuery` reports it, reservations included) is `ENOMEM`; a page unmapped from inside a larger mapping still counts as mapped there, since `sceKernelVirtualQuery` keeps reporting the whole mapping. The guest has no file mappings, so a successful call writes nothing back. `MS_INVALIDATE` on a mapped range fails with `EBUSY`, as it did in a payload process on a PS5 (firmware 13.60) for anonymous memory, locked or not, also with a hole unmapped inside it; a title process, reservations, direct memory and flexible memory were not measured - [madvise](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) accepts `MADV_PROTECT` (10) as a no-op hint without modifying process protection. FreeBSD 11's `kern_madvise` (`sys/vm/vm_mmap.c`) handles it before the address check, ignores the address and length, and calls `kern_procctl(..., PROC_SPROTECT, PPROT_SET)`, which requires `PRIV_VM_MADV_PROTECT` and fails with `EPERM` without it. Returning 0 unconditionally assumes the calling process is privileged. Console behaviour is unverified - [mincore](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) follows FreeBSD 11's `sys_mincore` (`sys/vm/vm_mmap.c`), not measured on the console: the address is rounded down to the page, length 0 returns 0, a null vector over a mapped range returns `EFAULT`, an overflowing range or any unmapped page in the range (as reported by `sceKernelVirtualQuery`) returns `ENOMEM`. For valid ranges, a page reports `MINCORE_INCORE` (0x1) when `sceKernelVirtualQuery` reports it committed and 0 when it is only reserved; actual residency, referenced and modified bits are not modelled. Length 0 returns 0 when the start page is mapped and `ENOMEM` otherwise, and the range and mapping checks come before the null vector `EFAULT`, as in `kern_mincore` Java 25 LAN / DNS (PPSA99869) imports it -- [mlockall, munlockall](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - `mlockall` returns `EINVAL` for zero or unknown flags. Valid `MCL_CURRENT` throws because it would lock host stacks, heaps, modules and driver mappings along with guest memory; `MCL_FUTURE` throws because later mappings are not tracked for locking. FreeBSD 11.4's `sys_munlockall` (`sys/vm/vm_mmap.c`:1133-1165) checks `priv_check(td, PRIV_VM_MUNLOCK)` first (returning `EPERM` without it) and unwires the user map (`vm_map_unwire` over `vm_map_min`...`vm_map_max` with `VM_MAP_WIRE_HOLESOK`). Returning 0 unconditionally assumes the calling process is privileged. `munlockall` unlocks memory locked by `mlock` (`VirtualUnlock` on Windows, tolerating `ERROR_NOT_LOCKED` for pages never locked, and `munlockall` on Linux). On main, XPSemu (PPSA97358) fails to load at these imports (mlockall #4386, munlockall); their calls in QEMU `os-posix.c` execute only when `-overcommit mem-lock=on` is enabled. The call has not been observed in a run with this implementation. -- [socket](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) creates only `SOCK_STREAM` and `SOCK_DGRAM` sockets in `AF_INET` and `AF_INET6`. A type that FreeBSD 11.4's inet domain has no protocol for in any configuration ([`in_proto.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/netinet/in_proto.c) has no `SOCK_RDM` entry and no unknown type) fails with `EPROTOTYPE` when the protocol is 0 and `EPROTONOSUPPORT` otherwise, as [`socreate`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c) does. `SOCK_RAW`, which `in_proto.c` always has (`socreate` creates it for a privileged process), and `SOCK_SEQPACKET`, which it has only in a kernel built with `SCTP`, fail with `EPROTONOSUPPORT`; whether the console allows either is not known. `SOCK_DGRAM` with `IPPROTO_UDPLITE` and `AF_UNIX` (`socketpair` supports it) fail with `EPROTONOSUPPORT` and `EAFNOSUPPORT` where FreeBSD creates the socket +- [mlockall, munlockall](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - `mlockall` returns `EINVAL` for zero or unknown flags. Valid `MCL_CURRENT` throws because it would lock host stacks, heaps, modules and driver mappings along with guest memory; `MCL_FUTURE` throws because later mappings are not tracked for locking. FreeBSD 11.4's `sys_munlockall` (`sys/vm/vm_mmap.c`:1133-1165) checks `priv_check(td, PRIV_VM_MUNLOCK)` first (returning `EPERM` without it) and unwires the user map (`vm_map_unwire` over `vm_map_min`...`vm_map_max` with `VM_MAP_WIRE_HOLESOK`). Returning 0 unconditionally assumes the calling process is privileged. `munlockall` unlocks memory locked by `mlock` (`VirtualUnlock` on Windows, tolerating `ERROR_NOT_LOCKED` for pages never locked, and `munlockall` on Linux). XPSemu (PPSA97358) imports `mlockall` and `munlockall`; their calls in QEMU `os-posix.c` execute only when `-overcommit mem-lock=on` is enabled. The call has not been observed in a run with this implementation. +- [socket](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) creates only `SOCK_STREAM` and `SOCK_DGRAM` sockets in `AF_INET` and `AF_INET6`. In a payload process on a PS5 (firmware 13.60) `SOCK_RDM`, `SOCK_SEQPACKET` and an unknown type failed with `EPROTOTYPE` when the protocol was 0 and `EPROTONOSUPPORT` otherwise, as here, and a family other than `AF_INET`, `AF_INET6` and `AF_UNIX` with `EAFNOSUPPORT`. The console also created `SOCK_RAW` (for that payload process, which may hold privileges a title does not), `SOCK_DGRAM` with `IPPROTO_UDPLITE`, and `AF_UNIX` stream, datagram and seqpacket sockets; these fail here with `EPROTONOSUPPORT` and `EAFNOSUPPORT` (`socketpair` supports `AF_UNIX`) - [sigtimedwait](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) waits on a process-wide pending set instead of throwing: a signal raised while blocked stays pending and is taken by `sigwait`, `sigwaitinfo` or `sigtimedwait`, delivered to the handler when `_sigprocmask` unblocks it, or delivered by `pause`. The set covers all 128 signals and the lowest number that is pending and in the set is selected (FreeBSD specifies the lowest number only among real-time signals). Selection, `EAGAIN` on timeout, `EINVAL` for a `tv_nsec` outside 0 to 1000000000 while nothing is pending and `NULL` timeout blocking indefinitely follow FreeBSD's [sigtimedwait(2)](https://man.freebsd.org/cgi/man.cgi?query=sigtimedwait). `sigwait` returns 0 with the number in its output, as POSIX specifies, and `sigwaitinfo` and `sigtimedwait` return the number, as FreeBSD does. The guest `siginfo_t` follows FreeBSD 11's `sys/sys/siginfo.h`: 80 bytes, `si_signo` at 0, `si_code` at 8, `si_pid` at 12, `si_uid` at 16, `si_addr` at 24, `si_value` at 32 and `si_band` at 40; only `si_signo`, `si_code` (`SI_USER`) and `si_pid` (the guest pid) are written, `si_uid` stays 0 because no uid is modelled, and the console's layout is not verified. PPSA85300 imports `sigtimedwait` and fails to load without it; its only call site is VLC's `vlc_writev` ([filesystem.c](https://github.com/videolan/vlc/blob/6de05adcbaf2e8b85fe86aad4169393098628119/src/posix/filesystem.c#L243-L278)), which polls with a zero timeout to drain a `SIGPIPE` only after `writev` fails with `EPIPE`. The waits are not cancellation points and an unblocked caught signal does not interrupt them with `EINTR`, where FreeBSD's `sigwaitinfo` and `sigtimedwait` may return it. `SIGPIPE` has no host equivalent and is never raised by a host write here (see `read, write and vector I/O`), so VLC's drain finds nothing - [pause, sigsuspend, sigwait, sigwaitinfo](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) take the pending set of `sigtimedwait` instead of throwing. `pause` waits for a pending signal that is not blocked and whose disposition can be delivered, runs its handler on the calling thread and returns -1 with `EINTR`; an ignored signal is discarded and the wait continues, and it never returns for a signal whose handler cannot run. A signal whose registered handler has `SA_SIGINFO` still throws, and one whose disposition is the default action and that has no host equivalent (every signal except 2, 4, 6, 8, 11 and 15) is neither delivered nor discarded: it stays pending until a wait takes it, where FreeBSD runs the default action, which terminates the process for most of them. A pending `SIGALRM` with the default disposition exits the process with status 142 once it is unblocked, as FreeBSD's default action does. PPSA68091, PPSA99169, PPSA99420 and PPSA97358 import them and fail to load without them. PPSA68091 calls `pause` at one site (0x8F7266), in a loop that waits while a global holds another thread's `errno` address. PPSA99169 only stores the address of `pause` in a table of symbol addresses (0x30DB1). PPSA99420 calls `pause` at 0x3BD80F6 and 0x3C9E220, `sigwait` at 0x3C9E4C7 and `sigwaitinfo` at 0x3C9E588. PPSA97358 calls `sigwait` from QEMU's `sigwait_compat` thread ([compatfd.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/compatfd.c#L28-L53)), which `qemu_signal_init` ([main-loop.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/main-loop.c#L110-L147)) starts for `SIGIO`, `SIGALRM` and `SIGBUS` when the main loop is created, so it reaches the wait at start-up, and from the vCPU thread of the dummy accelerator ([dummy-cpus.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/accel/dummy-cpus.c#L48-L56)). `sigsuspend` swaps the process-wide mask of `_sigprocmask` for its argument, waits as `pause` does through the same helper, then restores the mask through `_sigprocmask`, and returns -1 with `EINTR`; a null set throws, as the console faults in libthr instead of returning `EFAULT`. All four words of the argument replace the mask and are restored afterwards, as in `_sigprocmask`. Java 25 LAN / DNS (PPSA99869, #5501) imports it and fails to load without it, after `sysctlnametomib`, `fegetenv` and `mincore`. None of the five waits is a cancellation point -- [getsid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns the calling process ID for pid 0 and the calling process, and `ESRCH` for other process IDs, assuming the single guest process is its own session leader. PPSA74126 imports it and fails to load without it -- [sigaction](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records the handler, flags and mask of every signal from 1 to 128 and reports them back, following FreeBSD's `sigaction` (`EINVAL` for other signals, and for a handler on `SIGKILL` or `SIGSTOP`); `signal` records its handler with `SA_RESTART`, as FreeBSD's libc does. Only handlers without `SA_SIGINFO` on the signals `signal` already delivers (`SIGINT`, `SIGILL`, `SIGABRT`, `SIGFPE`, `SIGSEGV`, `SIGTERM`) run, the same way `signal` handlers do; their mask and flags are not applied. `SA_SIGINFO` handlers are recorded but never called, since that needs FreeBSD's `siginfo_t` and `ucontext_t`, and neither are the handlers of other signals (`SIGBUS`, `SIGHUP`, ...), whether they are set by `sigaction` or by `signal`, which records them too (every number from 1 to 128, with `SA_RESTART` and an empty mask replacing what `sigaction` stored; `SIGKILL` and `SIGSTOP` accept only the default handler in both calls, as in FreeBSD's `kern_sigaction`). `raise` and `kill` accept every number from 1 to 128 now: a blocked one stays pending on the set of `sigtimedwait`, an unblocked one whose handler is registered runs it on the calling thread, and an unblocked one that has no host equivalent and no handler still fails with `EINVAL`, where FreeBSD runs its default action. `raise` of an `SA_SIGINFO` handler still throws instead of terminating +- [getsid, setpgid, setpgrp](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - getsid returns the calling process ID for pid 0 and the calling process, and `ESRCH` for other process IDs, assuming the single guest process is its own session leader. setpgid and setpgrp follow FreeBSD 11.4's `sys_setpgid` for the only guest process: a negative group ID fails with `EINVAL`, a process ID other than 0 or the caller's PID (including a negative PID) fails with `ESRCH`, and because the single guest process is modeled as its own session leader, `SESS_LEADER(targp)` fails with `EPERM` before normalizing the process group or reaching the same-process-group no-op. PPSA74126 imports `getsid` and fails to load without it +- [sigaction](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records the handler, flags and mask of every signal from 1 to 128 and reports them back, following FreeBSD's `sigaction` (`EINVAL` for other signals, and for a handler on `SIGKILL` or `SIGSTOP`); `signal` records its handler with `SA_RESTART`, as FreeBSD's libc does. Only handlers without `SA_SIGINFO` on the signals `signal` already delivers (`SIGINT`, `SIGILL`, `SIGABRT`, `SIGFPE`, `SIGSEGV`, `SIGTERM`) run, the same way `signal` handlers do. While a handler runs, its own signal (unless `SA_NODEFER`) and the signals of its mask other than `SIGKILL` and `SIGSTOP` are blocked, and `SA_RESETHAND` resets the disposition to the default before it runs, as FreeBSD's `postsig_done` does. Those blocks are held apart from the process-wide mask that `sigprocmask` sets, which reports them, and are released when the handler returns, so a block another thread adds meanwhile is kept, for the same signal too, and so is one the handler adds itself, which FreeBSD's `sigreturn` would undo. `SIG_UNBLOCK` of a held signal releases its hold and `SIG_SETMASK` releases them all. When the handler's own thread released one, that signal gets back its bit of the process mask from before the handler when the handler returns, as FreeBSD's `sigreturn` puts back the mask from before it, so a handler that saves and restores the mask with `SIG_SETMASK`, or calls `pselect`, leaves its signal unblocked. A `sigsuspend` inside a handler keeps the holds, where FreeBSD replaces the whole thread mask. A handler left with `longjmp`, or with `siglongjmp` to a buffer that saved the mask, gets back the mask that buffer saved, as on FreeBSD; a signal raised meanwhile stays pending and is delivered when the handler returns to `raise`, but one left pending by a handler that a host signal ran asynchronously waits for the next `raise` or `_sigprocmask`. `SA_ONSTACK` is not applied. `SA_SIGINFO` handlers are recorded but never called, since that needs FreeBSD's `siginfo_t` and `ucontext_t`, and neither are the handlers of other signals (`SIGBUS`, `SIGHUP`, ...), whether they are set by `sigaction` or by `signal`, which records them too (every number from 1 to 128, with `SA_RESTART` and an empty mask replacing what `sigaction` stored; `SIGKILL` and `SIGSTOP` accept only the default handler in both calls, as in FreeBSD's `kern_sigaction`). `raise` and `kill` accept every number from 1 to 128 now: a blocked one stays pending on the set of `sigtimedwait`, an unblocked one whose handler is registered runs it on the calling thread, and an unblocked one that has no host equivalent and no handler still fails with `EINVAL`, where FreeBSD runs its default action. `raise` of an `SA_SIGINFO` handler still throws instead of terminating +- [signal](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records its handler with `SA_RESTART` unless the signal is in the exported `_sigintr` set, as FreeBSD's libc does (`lib/libc/gen/signal.c`). `siginterrupt`, which fills that set, is a libSceLibcInternal export that libc serves through the `sigaction` and the set libkernel registers at load (see libc). - [kill, killpg](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follow FreeBSD's `kern_kill` and `killpg1` for the only guest process: `EINVAL` for signals outside 0 to 128 and for negative `killpg` process groups, signal 0 only checks that the target exists, and its own pid, 0 and minus its own pid (the guest is taken to lead its own process group) signal it through `raise`, so they share `raise`'s limits above. Any other pid, and -1, which skips the caller, report `ESRCH`, so `EPERM` is never reported - [sceKernelFcntl](../../../core/libs/prx/libkernel/File/src/Open.cpp) (libkernel) runs the POSIX `fcntl` below and returns its guest errno as `SCE_KERNEL_ERROR_*`, the form the other `sceKernel` file exports use. The return values and error codes are unverified on a console -- [fcntl](../../../core/libs/prx/libkernel/File/src/DescriptorControl.cpp) (libkernel) follows FreeBSD 11.4's [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L479-L799) and [`kern_dup`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L812) on file, directory, pipe and character device descriptors; a socket keeps the libSceNet path (`F_DUPFD`, `F_DUP2FD`, `F_DUPFD_CLOEXEC`, `F_DUP2FD_CLOEXEC`, `F_GETFL`, `F_SETFL` with `O_NONBLOCK`, `F_GETFD` and `F_SETFD`, `EINVAL` for every other command, see the next entry). On Linux and macOS a host socket that is not a guest socket, such as a standard descriptor a service manager connected to its journal, takes the flag handling of a pipe end and `FIONREAD` asks the host for its queued bytes (for a datagram socket Linux counts only the next datagram, where FreeBSD's [`soo_ioctl`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_socket.c#L202-L205) counts every buffered byte); its record locks and read-ahead hints fail with `EBADF`, as FreeBSD fails them on a descriptor without a vnode, and `O_ASYNC` on it throws. The close-on-exec flag and the status flags live in a guest table, the status flags shared by `dup`, `dup2` and `F_DUPFD` as in an open file description; a descriptor the table has not seen, such as 0 to 2, starts from the host's flags, since the console's are not known. `F_GETFL` takes the access mode from the host (the granted access of the handle on Windows), so pipe ends report `O_RDONLY` and `O_WRONLY` where FreeBSD's bidirectional pipes report `O_RDWR`. `F_DUPFD` and `F_DUPFD_CLOEXEC` return the lowest free host descriptor at or above the start, so the bound is the host table (`EMFILE` on Windows, `EINVAL` past `RLIMIT_NOFILE` on Linux), not the console's unknown `RLIMIT_NOFILE` and `kern.maxfilesperproc`. `O_NONBLOCK` switches a host pipe (`PIPE_NOWAIT` on Windows, where an empty read and a full write then report `EAGAIN`) and is only recorded on files, directories and the random device, as their reads never block; on a Windows console or other character device it throws. Changing `O_APPEND` on a Windows file, changing `O_FSYNC` on a file, `O_ASYNC` on a pipe or character device (`SIGIO` is not delivered), `F_GETOWN`, `F_SETOWN` and `F_SETLK_REMOTE` throw; `O_DIRECT`, `F_RDAHEAD` and `F_READAHEAD` are recorded as the hints they are. Record locks (`F_GETLK`, `F_SETLK`, `F_SETLKW` and their forms without `l_sysid`) use the host's POSIX locks on Linux and macOS. `F_GETLK` with `F_UNLCK` asks the host with `F_RDLCK`: as in [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) only another owner's overlapping write lock conflicts with either request. On Windows they follow [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L607-L752) and [`lf_advlockasync`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L433-L487) for the one guest process, which owns every lock: `EOVERFLOW` for an `SEEK_CUR` start past `OFF_MAX`, then `EBADF` for `F_RDLCK` without read access or `F_WRLCK` without write access and `EINVAL` for another type, then `EINVAL` and `EOVERFLOW` for the range; as [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) never lets locks of one owner conflict, whichever descriptor or thread asks, `F_SETLK` and `F_SETLKW` then succeed without waiting and `F_GETLK` reports `F_UNLCK`, so no lock list is kept and merging, splitting and the release on close have nothing to change. Nothing is held on the host, since Windows byte-range locks are mandatory and would fail the guest's own reads and writes through other descriptors, so other host processes neither see these locks nor are seen; a lock from the end of a directory throws. The return values and error codes are unverified on a console +- [Creating through a trailing slash](../../../core/libs/prx/libkernel/File/src/Open.cpp) (libkernel) - `open` and `sceKernelOpen` with `O_CREAT` on a missing name followed by a slash (`new/`) fail with `EINVAL` on Windows and with the host's `EISDIR` on Linux, where FreeBSD 11.4's `lookup()` (sys/kern/vfs_lookup.c) fails with `ENOENT` because only a directory may be created through a trailing slash +- [fcntl](../../../core/libs/prx/libkernel/File/src/DescriptorControl.cpp) (libkernel) follows FreeBSD 11.4's [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L479-L799) and [`kern_dup`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L812) on file, directory, pipe and character device descriptors; a socket keeps the libSceNet path (`F_DUPFD`, `F_DUP2FD`, `F_DUPFD_CLOEXEC`, `F_DUP2FD_CLOEXEC`, `F_GETFL`, `F_SETFL` with `O_NONBLOCK`, `F_GETFD` and `F_SETFD`, `EINVAL` for every other command, see the next entry). On Linux and macOS a host socket that is not a guest socket, such as a standard descriptor a service manager connected to its journal, takes the flag handling of a pipe end and `FIONREAD` asks the host for its queued bytes (for a datagram socket Linux counts only the next datagram, where FreeBSD's [`soo_ioctl`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_socket.c#L202-L205) counts every buffered byte); its record locks and read-ahead hints fail with `EBADF`, as FreeBSD fails them on a descriptor without a vnode, and `O_ASYNC` on it throws. The close-on-exec flag and the status flags live in a guest table, the status flags shared by `dup`, `dup2` and `F_DUPFD` as in an open file description; a descriptor the table has not seen, such as 0 to 2, starts from the host's flags, since the console's are not known. `F_DUPFD` and `F_DUPFD_CLOEXEC` return the lowest free host descriptor at or above the start, so the bound is the host table (`EMFILE` on Windows, `EINVAL` past `RLIMIT_NOFILE` on Linux), not the console's unknown `RLIMIT_NOFILE` and `kern.maxfilesperproc`. `O_NONBLOCK` switches a host pipe (`PIPE_NOWAIT` on Windows, where an empty read and a full write then report `EAGAIN`) and is only recorded on files, directories and the random device, as their reads never block; on a Windows console or other character device it throws. `O_ASYNC` on a pipe or character device (`SIGIO` is not delivered), `F_GETOWN`, `F_SETOWN` and `F_SETLK_REMOTE` throw; `O_FSYNC` set by `open` or `F_SETFL` makes each `write`, `pwrite`, `writev` and `pwritev` on a file sync it before returning, as `vn_write` adds `IO_SYNC`; the `sceKernelAioSubmitWriteCommands` writes are not synced, as whether the console's AIO honours the flag is unknown; on Linux a file opened with it keeps the host `O_SYNC` after `F_SETFL` clears it. `O_DIRECT`, `F_RDAHEAD` and `F_READAHEAD` are recorded as the hints they are. Record locks (`F_GETLK`, `F_SETLK`, `F_SETLKW` and their forms without `l_sysid`) use the host's POSIX locks on Linux and macOS. `F_GETLK` with `F_UNLCK` asks the host with `F_RDLCK`: as in [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) only another owner's overlapping write lock conflicts with either request. On Windows they follow [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L607-L752) and [`lf_advlockasync`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L433-L487) for the one guest process, which owns every lock: `EOVERFLOW` for an `SEEK_CUR` start past `OFF_MAX`, then `EBADF` for `F_RDLCK` without read access or `F_WRLCK` without write access and `EINVAL` for another type, then `EINVAL` and `EOVERFLOW` for the range; as [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) never lets locks of one owner conflict, whichever descriptor or thread asks, `F_SETLK` and `F_SETLKW` then succeed without waiting and `F_GETLK` reports `F_UNLCK`, so no lock list is kept and merging, splitting and the release on close have nothing to change. Nothing is held on the host, since Windows byte-range locks are mandatory and would fail the guest's own reads and writes through other descriptors, so other host processes neither see these locks nor are seen; a lock from the end of a directory throws. The return values and error codes are unverified on a console - [fcntl on a guest socket](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) answers `F_DUPFD`, `F_DUP2FD`, `F_DUPFD_CLOEXEC`, `F_DUP2FD_CLOEXEC`, `F_GETFD`, `F_SETFD`, `F_GETFL` and `F_SETFL` and fails every other command with `EINVAL`; FreeBSD 11.4's `kern_fcntl` also handles `F_GETOWN` and `F_SETOWN` on a socket. `FD_CLOEXEC` is only recorded (there is no `exec`): `dup`, `dup2`, `F_DUPFD`, `F_DUP2FD` onto another descriptor and an accepted socket start without it, `F_DUP2FD` onto the same descriptor keeps it, and `SOCK_CLOEXEC`, `F_DUPFD_CLOEXEC`, `F_DUP2FD_CLOEXEC` and `F_SETFD` set it. None of this was measured on a console. - [ioctl, _ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) handle only `FIONBIO` and `FIONREAD` on a socket, and any other request there fails with `ENOTTY`. On the other descriptors they follow FreeBSD's [`kern_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_generic.c#L758), [`vn_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_vnops.c#L1488) and [`pipe_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_pipe.c#L1346): `FIONBIO` sets `O_NONBLOCK` as `fcntl` does, `FIOCLEX` and `FIONCLEX` set and clear the close-on-exec flag `F_GETFD` reports, and `FIONREAD` reports the size past the offset of a file, the queued bytes of a pipe's read end, 0 on its write end and `ENOTTY` on the random device, while it throws on a directory or another character device. Every termios request (group `t`, `TIOCGWINSZ` included) fails with `ENOTTY`, since those descriptors are taken as not a terminal, as in `tcgetattr`, and any other well-formed request throws. An input request with length 0 is not rejected as malformed: [`sys_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_generic.c#L703-L718) rejects it only in a kernel built without `COMPAT_FREEBSD4`, `COMPAT_FREEBSD5` and `COMPAT_43`, and which of these the PS5 kernel has is unknown. Stremio Plus (PPSA74126) imports `_ioctl` for its statically linked `tc*` functions, which send `TIOCGETA`, `TIOCSETA`, `TIOCSPGRP` and the other termios requests +- [ioctl, _ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) do not pass a `sceNetSocket` socket to libSceNet as `fcntl` does. On Linux its number duplicates the host socket, so `FIONBIO` switches the host `O_NONBLOCK` but not the library's `SO_NBIO` state; on Windows it is a `NUL` placeholder, so `FIONREAD` and enabling `FIONBIO` throw as on a character device. `sceNetIoctl` handles both requests on such a socket. +- [_accept, _bind, _connect, _getpeername, _getsockname, _getsockopt, _listen, _recvfrom, _recvmsg, _sendmsg, _sendto, _setsockopt](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp), [_getdirentries](../../../core/libs/prx/libkernel/File/src/Stdio.cpp), [_sigaction, _sigsuspend](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) are not measured on the console. They forward to the calls without the underscore, which have no libthr layer here, as FreeBSD 11.4 makes the underscore names the plain system calls. `_sigaction` and `sigaction` share one handler table, where FreeBSD's `__thr_sigaction` hands its own wrapper to the system call `_sigaction` reads back, and rejects `SIGCANCEL` (32). A null set throws in `_sigsuspend` as in `sigsuspend`, while the system call FreeBSD 11.4 exports as `_sigsuspend` ([`Symbol.map`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/sys/Symbol.map#L961)) returns `EFAULT` - [Regular file mappings](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - on Linux `mmap` maps a regular file's descriptor shared or private like a shared memory object, with the same access checks; SQLite's WAL index (`-shm`) in Dusklight PPSA99305 needs it. FreeBSD 11 `mmap(2)` and shadPS4's `MemoryManager::MapFile` map regular files; the console's behaviour is not measured. Other descriptors (pipes, sockets, directories, devices) still fail with `EOPNOTSUPP`. On Windows a shared mapping is a view of a file section (`CreateFileMappingW` on the descriptor's handle, read-write when the descriptor is, otherwise read-only), so writes through the descriptor and the mapping see each other. This also maps anonymous `shm_open` objects and JIT shared memory descriptors, which are temporary files on Windows. A shared mapping may end inside the 16 KiB page that holds the end of the file, as POSIX [`mmap`](https://pubs.opengroup.org/onlinepubs/9799919799/functions/mmap.html) allows (llama.cpp in PS5LM Plus PPSA99581 maps a whole model file this way): the section keeps the file's size, so the file never grows, the view ends with the 4 KiB host page that holds the end of the file, which Windows fills with zeros past it, and the rest of the guest page is private zero pages. As POSIX requires, the bytes past the end read zero and are never written to the file; bytes written to those private pages are seen only through that mapping, not through other mappings of the file, and a file that grows later does not show its new bytes there. A shared mapping that reaches a whole page past the end of the file fails with `EOPNOTSUPP`, where POSIX maps it and delivers `SIGBUS` on access, as do `PROT_EXEC` and a write-only descriptor. A read-write alias (`GuestArenaMapAlias`) of a mapped page that holds the end of the file throws. `mprotect` above the view's protection (writing through a read-only descriptor, or adding `PROT_EXEC`) fails with `EACCES` before anything changes; FreeBSD allows `PROT_EXEC` on a readable descriptor. Shrinking the file with `ftruncate` fails with `EINVAL` while any shared mapping of it exists, even one that does not cover the removed part, because the section keeps the file's size at `mmap`; FreeBSD truncates. A read-write alias of a view (`GuestArenaMapAlias`, used when the GPU imports guest memory) of a read-only file section fails, as the alias is mapped `PAGE_READWRITE`. Views of the same file made by separate `mmap` calls are separate sections, so write tracking does not treat them as aliases of each other. A private mapping on Windows is a copy of the file read at `mmap`, which POSIX allows (later changes to the file need not show); pages wholly past the end of the file fail with `EOPNOTSUPP` instead of faulting on access, and a write-only descriptor fails with `EOPNOTSUPP`. - [File mappings on macOS](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - `mmap` of a regular file, of an anonymous `shm_open` object (an unlinked temporary file on macOS) or of a JIT shared memory descriptor maps the descriptor shared or private as on Linux, with the same access checks. macOS refuses to create an executable file mapping (`EPERM`), so an executable mapping is made without execute and then given it with `mprotect`. Under Rosetta 2 (Apple M2, macOS 26), code rewritten through one view of a file after it ran from another view keeps its old translation until the executable view's protection changes. Named `shm_open` objects still throw on macOS. -- [sendfile](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) follows FreeBSD 11's `sendfile` for a connected stream socket: headers, the file from `offset` (to its end when `nbytes` is 0) and trailers, the file offset left unchanged, `EAGAIN` with `*sbytes` set after a partial send on a non-blocking socket. It reads the file through `sceKernelPread` in 64 KiB blocks and sends each with `send`. A socket as the file fails with `EINVAL`, as on FreeBSD, but any other descriptor `sceKernelPread` reads, such as `/dev/urandom`, is sent where FreeBSD requires a regular file or shared memory, and a file descriptor as the socket fails with `EBADF`, as for `send`, where FreeBSD answers `ENOTSOCK`. `flags` are ignored: reads complete before sending, so `SF_NODISKIO` never fails with `EBUSY`, and `SF_SYNC` returns once the data is queued on the socket rather than sent. PS5MC (PPSA99420) imports it +- [sendfile](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) follows FreeBSD 11's `sendfile` for a connected stream socket: headers, the file from `offset` (to its end when `nbytes` is 0) and trailers, the file offset left unchanged, `EAGAIN` with `*sbytes` set after a partial send on a non-blocking socket. It reads the file through `sceKernelPread` in 64 KiB blocks and sends each with `send`. A file that is not a regular file or shared memory, such as a socket, `/dev/urandom` or a kqueue, fails with `EINVAL` and `*sbytes` 0, as FreeBSD's [`sendfile_getobj`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_sendfile.c#L421-L463) refuses it. `flags` are ignored: reads complete before sending, so `SF_NODISKIO` never fails with `EBUSY`, and `SF_SYNC` returns once the data is queued on the socket rather than sent. PS5MC (PPSA99420) imports it - [pthread_kill](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) returns the error number like FreeBSD's `pthread_kill`: `EINVAL` for signals outside 0 to 128 and `ESRCH` for a null thread. A signal to the calling thread goes through `raise`, so it shares `raise`'s limits; a signal to another thread is left on the pending set for whichever thread waits for or unblocks it, since it is not delivered to the chosen thread, and a handle that is not a live guest thread returns `ESRCH` without touching it - [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) validates `tv_usec` but saturates a valid timeout beyond the remaining `steady_clock` range at `time_point::max()`, so the wait can continue effectively indefinitely. The console's behavior for a valid timeout outside the host clock's range is unknown. - [inet_ntop, inet_pton](../../../core/libs/prx/libkernel/Socket/src/AddressConversion.cpp) (libkernel) - an `AF_INET6` result of `inet_ntop` is followed by zero bytes up to `size`. In a payload process on a PS5 (firmware 12.70), the text forms, the order of the checks and this padding were measured through both exports. The padding was sampled at sizes from 3 to 65536, above 64 only at 65, 128, 256, 1024, 4096 and 65536; the sizes that were not sampled, sizes above 65536 and a title process are assumed to behave the same -- [_fstatfs](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) fills the FreeBSD 11 `struct statfs` of the PS5 payload SDK with the host volume that holds the file: block size, transfer size, total, free and available blocks, maximum name length and the read-only, `nosuid` and `noexec` flags (on Windows only read-only, and `f_files` and `f_ffree` are 0, as NTFS has no fixed number of files). The filesystem type number and name, mount paths, owner, filesystem id and I/O counters are left 0 or empty, since they would describe a host mount, not a console one. Sockets and pipes return `EINVAL`, as FreeBSD's `getvnode` does; a Windows descriptor that is neither a file nor a pipe throws. Stremio Plus (PPSA74126) reads the free space before a download and only logs the other fields +- [_fstatfs](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) reports the host volume of a file, not a console mount: block sizes, block counts, name length and the read-only, `nosuid` and `noexec` flags (on Windows only read-only, and `f_files` and `f_ffree` are 0). The type, mount names, owner, filesystem id and I/O counters are 0 or empty. Sockets, pipes and kqueues return `EINVAL`, as FreeBSD's `getvnode` does. A character device, including `/dev/random` and `/dev/urandom`, reports FreeBSD 11.4's [`devfs_statfs`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/fs/devfs/devfs_vfsops.c#L225-L237) values on every host: no flags, 512-byte blocks, 2 blocks, nothing free and a 255-byte name limit. A Windows descriptor that is none of these throws. Stremio Plus (PPSA74126) reads the free space before a download and only logs the other fields - [pthread_spin_lock](../../../core/libs/prx/libkernel/Pthread/Posix/Spinlock.cpp) (libkernel) - a lock taken again by the thread that holds it throws here. On a PS5 (firmware 12.70) that call had not returned after 2 s, as FreeBSD 11.4 libthr [`thr_pspinlock.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_pspinlock.c) spins forever - [pthread_mutex_getspinloops_np, pthread_mutex_setspinloops_np, pthread_mutex_getyieldloops_np, pthread_mutex_setyieldloops_np and their scePthreadMutex counterparts](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) store and report the counts, but host mutexes do not spin or yield by them. The defaults follow FreeBSD 11.4's [`mutex_init_body`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_mutex.c#L236-L272) without `LIBPTHREAD_SPINLOOPS` and `LIBPTHREAD_YIELDLOOPS`: 2000 spins for an adaptive mutex, 0 otherwise; the console's defaults are unverified. Setting the yield count of a destroyed mutex returns 0 as FreeBSD's [`__pthread_mutex_setyieldloops_np`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_mutex.c#L1125-L1135) does; the console's result is unverified - [pthread_rwlockattr_getpshared, pthread_rwlockattr_setpshared](../../../core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp) (libkernel) - `pthread_rwlockattr_destroy` clears the attribute here, so a later get, or a set of 0, throws. On a PS5 (firmware 12.70) destroy leaves the attribute set; a get afterwards returned 0 with a stale value and a set of 0 returned 0 -- [sceKernelGetEventError](../../../core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp) (libkernel) returns the event's `data` as an `int` when `EV_ERROR` (0x4000) is set in `flags` and 0 otherwise, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/947627d10d4ef4b68ba3c0189aab6c368e70a8d2/src/kernel/eventQueue.cpp#L663) does and as FreeBSD 11's [kevent](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/kern_event.c#L1005) reports a failed change (`flags = EV_ERROR`, errno in `data`). [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_queue.pas#L643) instead returns `fflags` plus `0x80020000` when `fflags` is nonzero. Whether the console reads `data` or `fflags` and returns the raw errno or an SCE error code is unverified. No event delivered by the equeue here carries `EV_ERROR`, so the call returns 0 for every event `sceKernelWaitEqueue` reports +- [pthread_mutexattr_setkind_np](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) throws for a kind outside 1 to 4. FreeBSD 11.4's [`_pthread_mutexattr_setkind_np`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_mutexattr.c#L108-L120) stores any kind and [`mutex_check_attr`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_mutex.c#L207-L218) then makes `pthread_mutex_init` fail with `EINVAL`, but `pthread_mutex_init` here does not check the type +- [pthread_cond_signalto_np, scePthreadCondSignalto](../../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) wake every waiter of the condition variable, not only the given thread, and do not check the thread; the other waiters see a spurious wakeup. Which waiters the console wakes, and its result for a thread that is not waiting, are unverified +- [pthread_rwlockattr_gettype_np, scePthreadRwlockattrGettype](../../../core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp) (libkernel) report the type stored by `scePthreadRwlockattrSettype`, which accepts any value and which read-write locks do not apply; the console's valid types and their effect are unverified. A null or uninitialized attribute throws, as for `pthread_rwlockattr_getpshared` +- [sceKernelGetEventError](../../../core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp) (libkernel) throws for `fflags` above 0xFFFF, where OR and addition of 0x80020000 differ and the console was not measured. Otherwise it returns 0 for `fflags` 0 and `0x80020000 | fflags`, whatever `flags` and `data` hold, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_queue.pas#L643) does and a PS5 (firmware 13.40, measured 2026-10-19 in a payload process) answered: `{flags 0x4000, fflags 0, data 22}`, `{0x4000, 0, -1}`, `{0x4000, 0, 0x80020016}` and `{0, 0, 0}` gave 0, `{0x4000, 5, 0}` and `{0x4000, 5, 22}` 0x80020005, `{0, 7, 22}` 0x80020007, `{0x8000, 3, 9}` 0x80020003. Video-out events carry their count in `fflags`, so they get 0x80020001 and up; only events built by the caller were measured. Closes when: `fflags` above 0xFFFF is measured - [pthread_cond_reltimedwait_np](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) takes `(cond, mutex, usec)`, a relative timeout in microseconds, and is `scePthreadCondTimedwait` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadCondTimedwait` ([`condvar.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/condvar.cpp#L229-L234)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `sem_reltimedwait_np`. Unverified on the console - [pthread_mutex_reltimedlock_np](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) takes `(mutex, usec)`, a relative timeout in microseconds, and is `scePthreadMutexTimedlock` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadMutexTimedlock` ([`mutex.cpp`](https://github.com/shadps4-emu/shadPS4/blob/7c960eae136b956cf4c1d72aacd2426b650330df/src/core/libraries/kernel/threads/mutex.cpp#L284-L287)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `pthread_cond_reltimedwait_np`, so a count of 2^32 microseconds or more would be truncated if the console reads 64 bits. Unverified on the console - [sysctl kern.arandom](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) serves FreeBSD 11's MIB {1, 37} (CTL_KERN, KERN_ARANDOM) and the "kern.arandom" sysctlbyname lookup through std::random_device, returning min(*oldLength, 256) random bytes. Following FreeBSD's sysctl_kern_arnd, a null buffer reports min(*oldLength, 256) without writing, a null length returns 0 without writing, and requests with a new value return EPERM (1) as the node is read-only. Cryptographic quality depends on the host's random device implementation. -- [pthread_atfork](../../../core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp) (libkernel) appends the three handlers to a process-wide list and returns 0, or `ENOMEM` when the entry cannot be allocated, as FreeBSD 11.4's [`_pthread_atfork`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_fork.c#L78-L100) does; null handlers are accepted. The handlers never run: `fork` throws and `vfork` is absent, so no child process is created. Handlers of a module that is unloaded stay in the list, where FreeBSD's `__pthread_cxa_finalize` removes them. PPSA97358 has a source-level call from the constructor `rcu_init` ([rcu.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/rcu.c#L488-L495)), but current-main runtime reachability is unverified; XPSemu currently stops first at the unresolved `mlockall` import (#4386) +- [pthread_atfork](../../../core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp) (libkernel) appends the three handlers to a process-wide list and returns 0, or `ENOMEM` when the entry cannot be allocated, as FreeBSD 11.4's [`_pthread_atfork`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_fork.c#L78-L100) does; null handlers are accepted. The handlers never run: `fork` throws and `vfork` is absent, so no child process is created. Handlers of a module that is unloaded stay in the list, where FreeBSD's `__pthread_cxa_finalize` removes them. PPSA97358 has a source-level call from the constructor `rcu_init` ([rcu.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/rcu.c#L488-L495)), but current-main runtime reachability is unverified - [sceKernelGetPageTableStats](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) reports 8192 entries on each side and counts them as a PS5 (firmware 13.60) counted them for a payload process: one cpu entry for each 2 MiB granule that holds a committed mapping, whatever its protection (none included), and one gpu entry for each 1 GiB region that holds a GPU-accessible mapping, plus one for each 2 MiB granule of such a mapping that is not a whole granule of direct memory at a 2 MiB-aligned physical address. A granule or region shared by several mappings counts once, and reservations do not count. On the console the first GPU mapping of a process also takes three gpu entries and one cpu entry that stay after it is unmapped, which is not modelled, and only the ranges whose protection is recorded here are counted. A null pointer returns `SCE_KERNEL_ERROR_EFAULT`, where the console faults. Resonance: A Plague Tale Legacy (PPSA30601) calls it at start-up (#4067) +- [settimeofday](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel_sys) checks like FreeBSD 11.4's [`kern_settimeofday`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_time.c#L688-L707) for a root caller: `EINVAL` for a negative second count or a microsecond count outside 0 to 999999, before anything is stored. A valid time throws, naming it, since the host clock is never set, and its time zone is not stored, as FreeBSD stores the zone only after setting the clock. A time zone without a time is kept for the process and reported by `gettimeofday`; `sceKernelGettimezone` still reports 0 and 0, since whether the console reads the same value is not known. FreeBSD checks `PRIV_SETTIMEOFDAY` first, which a jailed process fails with `EPERM`; whether a title holds it on the console is not known. - [clock_settime](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel) validates like FreeBSD 11.4's [`kern_clock_settime`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_time.c#L396-L412) for a root caller: `EFAULT` for a null time, `EINVAL` for any clock but `CLOCK_REALTIME`, a negative second count or a nanosecond count outside 0 to 999999999. A valid `CLOCK_REALTIME` request throws, naming the requested time, since the host clock is never set and no guest clock offset is kept. FreeBSD checks `PRIV_CLOCK_SETTIME` first, which a jailed process fails with `EPERM`; whether a title holds it on the console is unverified. PS5MC (PPSA99420) imports it; no call site is known - [Short sleeps ended by GPU and flip completions](../../../core/libs/prx/libkernel/Time/SleepWake.cpp) (libkernel) - a `sceKernelUsleep`, `usleep`, `nanosleep` or `sceKernelNanosleep` of 1 ms or less returns before the requested time, which FreeBSD and the console do not do: it ends once the AGC driver reports a completed GPU batch or batch completion action, or VideoOut completes a flip, so a title that polls a GPU label or the flip status between sleeps does not sleep the rest. A title that paces frames with such a sleep can wake up to 1 ms early. A thread returns early at most 8 times in a run of sleeps; the run restarts when the thread submits AGC work or did not sleep the requested time since its last sleep, so a loop that counts sleeps to time out still sleeps all but 8 in full. `APS5_NO_SLEEP_WAKE=1` keeps every sleep in full - [chflags](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) follows FreeBSD 11.4's [`kern_chflagsat`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2366-L2383) and UFS's [`ufs_setattr`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/ufs/ufs/ufs_vnops.c#L509-L556) for a root caller: the path lookup reports `EFAULT`, `ENOENT` and `ENOTDIR`, a flag outside the 17 UFS stores (`VNOVAL` included) `EOPNOTSUPP`, and `SF_SNAPSHOT` `EPERM`. Neither host keeps BSD file flags, so every file is taken to have none (`stat` reports `st_flags` 0): flags 0 succeed without touching the file, and any other flag throws, naming the flags. `UF_READONLY`, `UF_HIDDEN`, `UF_SYSTEM` and `UF_ARCHIVE` are FreeBSD's names for the Windows attributes but are not mapped to them: UFS stores them without enforcing them, `FILE_ATTRIBUTE_READONLY` already carries the owner write bit of `chmod` on Windows, and Linux has no equivalent a process can set without `CAP_LINUX_IMMUTABLE`. The console's filesystem types, `EROFS` on read-only mounts and the change-time update are not modelled - [fchflags](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) has the gaps of `chflags`: a valid non-zero flag throws, and the console's filesystem types, `EROFS` and the change-time update are not modelled. Character devices, the random device, other host descriptors and files read from app0.zar throw, since their console filesystems and the privilege check for devices are not modelled. On Windows, anonymous shared memory objects are temporary files that are not told apart from regular files, so they are treated as files instead of reporting `EINVAL` as FreeBSD 11.4's [`getvnode`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L3850-L3877) does. - [getpriority](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) follows FreeBSD 11.4's [`sys_getpriority`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_resource.c#L93-L165) for the only guest process, which is taken to run at nice 0: `PRIO_PROCESS` and `PRIO_PGRP` with 0 or its own pid (the guest is taken to lead its own process group, see `kill`) and `PRIO_USER` with 0 or its user id 0 return 0, any other id fails with `ESRCH` and any other `which` with `EINVAL`. As in FreeBSD's libc, where `getpriority` is the bare system call, errno is left unchanged on success. The nice value of a title process on the console is unverified, and the host nice of the process or of the threads `APS5_THREAD_PRIORITY=high` raises is not reported. `setpriority` accepts the same target ids (any other id fails with `ESRCH`, any other `which` with `EINVAL`), clamps the value to FreeBSD's `PRIO_MIN`-`PRIO_MAX` (-20 to 20) and stores it as the guest process's nice value, which `getpriority` returns; the host scheduling class is not changed, and whether a title process may lower the value without privilege is unverified -- [rtprio_thread, pthread_setconcurrency, pthread_getconcurrency](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - `rtprio_thread` follows FreeBSD 11.4's `rtprio_thread(2)`. Every guest thread here runs under `SCHED_FIFO` or `SCHED_RR` (priority 256-767), whose rtprio class is real-time; the console's mapping from that priority to the rtprio `prio` range (0-31) is unmeasured, so `RTP_LOOKUP` and `RTP_SET` throw for such a thread rather than report an impossible priority. A thread that is not real-time reports `RTP_PRIO_NORMAL` (3) with 0, and `RTP_SET` accepts only that value. Another function fails with `EINVAL`, a null `rtprio` with `EFAULT`, a thread other than the calling one throws, and an id that is not a live guest thread with `ESRCH`. `pthread_setconcurrency` stores 0 or more and returns `EINVAL` for a negative level (the error itself, as FreeBSD's libthr does, with errno untouched); `pthread_getconcurrency` returns the level, which is a hint and changes nothing about the host's concurrency. Morrowind (`mshivam019/morrowind-ps5`) imports all three at start-up +- [rtprio_thread, pthread_setconcurrency, pthread_getconcurrency](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - `RTP_SET` throws for a real-time thread (sce attributes allow only `SCHED_FIFO`/`SCHED_RR`) and for another thread: a payload process on a PS5 (firmware 12.70) answered type 2, prio 0/15/31 on the calling thread with `EINVAL`; no accepted value or set by another thread's id measured. A non-real-time thread (POSIX attributes store `SCHED_OTHER`), unmeasured, reports 3 with 0, and `RTP_SET` accepts only that. `RTP_LOOKUP` reports the policy's type (10 or 2) and the scheduling priority, as measured, not FreeBSD's 0-31; another function fails with `ESRCH` (2 measured), a null `rtprio` with `EFAULT`, an id of no live thread with `ESRCH` (unknown ids measured). `pthread_setconcurrency` stores 0 or more, returns `EINVAL` (not errno) below 0, as libthr does; `pthread_getconcurrency` returns it, a hint. Morrowind (`mshivam019/morrowind-ps5`) imports them - [sceKernelVirtualQuery](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) returns [VirtualQueryInfo](../../../core/libs/SceTypes.hpp) with a one-byte flag allocation unit so Windows and SysV hosts return the same 72-byte representation, with the name at byte 33 and GPU mask at byte 65. This preserves the existing SysV representation; the console offsets beyond the address, protection and size fields observed in Astro Bot's AGC calls are not hardware-measured. Size and offset assertions plus a raw-byte named-mapping regression guard the host ABI. - [app0.zar files](../../../core/libs/prx/libkernel/File/src/ArchivedFile.cpp) (libkernel) - `sceKernelOpen`, `open`, `sceKernelStat` and `sceKernelCheckReachability` resolve a path missing from `app0/` in `app0.zar`. An archived file is a read-only host null device whose reads, positioned and vectored reads, AIO reads, seeks, `fstat` and `FIONREAD` are served from the archive, with the position shared by `dup`, `dup2` and `F_DUPFD`; writes fail with `EBADF` as on a read-only descriptor, opening one for writing or truncation fails with `EROFS` and `O_CREAT | O_EXCL` with `EEXIST`. `fstat` and `sceKernelStat` report mode `0444` (`0555` for a directory), the node index as inode and the archive file's device and times. Opening an archived directory or a directory present both on disk and in the archive throws, as `getdents` would miss entries. On an archived descriptor `mmap`, `flock`, record locks, `fstatfs`, `ftruncate`, `fsync`, `fdatasync`, `getdirentries`, `fchmod` and `futimes` throw instead of acting on the null device; `mmap` is `noexcept`, so its refusal terminates the process. `lstat` and the other path calls on archived files are not implemented - [sceKernelLoadStartModule](../../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) loads a system library named by its console path, `/system/common/lib/.sprx` or the sandboxed `//common/lib/.sprx` that `sceKernelGetFsSandboxRandomWord` names, from the host `libs/.prx`, as libSceSysmodule loads modules by ID; a host library has no `module_start`, so `res` is 0. Other system directories, such as `/system/priv/lib`, are not mapped. The OpenOrbis piglet sample loads `libScePigletv2VSH.sprx` and `libScePrecompiledShaders.sprx` this way - [param.sfo](../../../core/libs/prx/libkernel/AppMetadata/src/ParamSfoParser.cpp) (libkernel) is read when `param.json` is absent, for `TITLE`, `TITLE_ID`, `DOWNLOAD_DATA_SIZE` and `USER_DEFINED_PARAM_1` to `_4`, and mounts `/data` and `/download0` like `param.json`; the localized `TITLE_00` to `TITLE_30` are not selected by the console language. - [sysctl MIBs for kern.smp.maxcpus, kern.stackprot, kern.sched.name and kern.timecounter.hardware](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - on a PS5 (firmware 12.70), a payload process resolves these names to {1, 2147482816, 2147482814}, {1, 2147483151}, {1, 2147483182, 2147483181} and {1, 2147482838, 2147482834}; `sysctl` returns -1 with `ENOENT` for reads and size queries by those exact MIBs, leaving the buffer and length unchanged, including when either or both output pointers are null. Writes were not measured and throw. `sysctlnametomib` still throws for these four names; #7541 implements the mappings for nine of the other names. - [sceKernelGetProcessAbiType](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) throws: no measured value, prototype or documented consumer of the process ABI type was found, and PS5_MihawkOS (`mihawk-99/PS5_MihawkOS`, PPSA99170) is the only title known to import it. Returning a guessed constant would let a title branch on a value the console never answered with, so the declaration only resolves the import until the console value is measured +- [flock on a directory descriptor](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) returns 0 on Windows without taking a lock, as FreeBSD 11.4's `sys_flock` accepts any vnode descriptor and so succeeds on a directory; Windows `LockFileEx` rejects a directory handle, which used to abort the process. Two descriptors of the same directory therefore never conflict: a second `LOCK_EX | LOCK_NB` succeeds where FreeBSD fails with `EWOULDBLOCK`. Linux and macOS use the host `flock`. Not measured on a console diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 25499ae3d1..b2868bb494 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -3,18 +3,40 @@ ### Silent stubs - [fchmodat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - `AT_SYMLINK_NOFOLLOW` on a symbolic link (on Windows and Linux) +### Unknown function info + +- [sceKernelGetCompiledSdkVersion](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - which SDK version the console returns is unmeasured. The [PS5 payload SDK](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/crt/kernel.c#L257-L275) declares the process parameters with a 32-bit PS4 SDK version at offset `0x10` and a 32-bit PS5 SDK version at `0x14`; this export returns the field at `0x10`, the one [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) reads and the one [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/emulator.cpp#L110-L138) (PS4, a single 64-bit `sdk_version` there) reads from the eboot. The `int (int32_t*)` signature and `EINVAL` (`0x80020016`) for a null output follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/core/libraries/kernel/process.cpp#L56-L62). Closes when: measured on a PS5 +- [scePthreadCondattrGetclock](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - the `int (const PthreadCondattr*, KernelClockid*)` signature and checks follow FreeBSD 11.4 libthr's [`_pthread_condattr_getclock`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_condattr.c#L82-L90) with the error code of libkernel's `scePthreadCondattrSetclock`: a null or destroyed attribute returns `SCE_KERNEL_ERROR_EINVAL`, and a null clock pointer throws, since libthr writes through it unchecked. Console behaviour is unverified + ### Functional -- [fchdir](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) changes the guest working directory to the descriptor's directory: on Windows through the path recorded when the descriptor was opened, on Linux through the `/proc/self/fd` link target. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Outside Windows it checks search permission on the directory with `faccessat`, which fails with `EACCES` as FreeBSD `change_dir` does; on Windows that check is not modelled. A directory renamed after it was opened is still reached through the recorded path. Console behaviour is unverified -- [openat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `open`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. As with `open`, `O_NOFOLLOW` and `O_NONBLOCK` are ignored. PPSA99003 opens directory entries this way. Console behaviour is unverified +- [sched_rr_get_interval](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) accepts pid 0 or the host-backed guest pid and reports the FreeBSD 11 ULE default time slice for the usual AMD64 clock rates (94 ms); other pids return `ESRCH`, and a null interval returns `EFAULT`. AnyPS5 does not run a guest ULE scheduler or track a per-process configured quantum, so this reports the modeled default and does not reflect runtime scheduling. Console-specific clock rates and quantum configuration are unverified +- [fchdir](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) changes the guest working directory to the descriptor's directory: on Windows through the path recorded when the descriptor was opened, on Linux through the `/proc/self/fd` link target. It returns `EBADF` for a descriptor that is not open, `EINVAL` for a pipe or socket, as FreeBSD `getvnode` does for a descriptor without a vnode, and `ENOTDIR` for any other descriptor that is not a directory. Outside Windows it checks search permission on the directory with `faccessat`, which fails with `EACCES` as FreeBSD `change_dir` does; on Windows that check is not modelled. A directory renamed after it was opened is still reached through the recorded path. Console behaviour is unverified +- [openat, _openat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) pass `AT_FDCWD` (-100) and absolute paths to `open`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. They return `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. As with `open`, `O_NONBLOCK` is ignored. PPSA99003 opens directory entries through `openat`. Console behaviour is unverified - [fchmodat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `chmod`; any other relative path is joined to the descriptor's host directory the same way as for `openat`, so guest path aliases and case-insensitive name matching do not apply to it and a `..` component is not kept inside the guest root. A flag other than `0` or `AT_SYMLINK_NOFOLLOW` (0x200) returns `EINVAL`; the mode is masked to `07777`. With `AT_SYMLINK_NOFOLLOW` a symbolic link returns success silently on Windows and Linux, since the host cannot change a link's own mode, while a regular file is changed either way. The mode maps to the host file mode, which on Windows is only the CRT's read-only bit. PPSA99621 calls it through `std::filesystem::permissions`. Console behaviour is unverified -- [pathconf](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) looks the path up as FreeBSD 11's `kern_pathconf` does (links followed, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255) and returns the constants of FreeBSD 11's UFS `ufs_pathconf` for files and directories alike: `_PC_LINK_MAX` 32767, `_PC_NAME_MAX` 255, `_PC_PATH_MAX` and `_PC_SYMLINK_MAX` 1024, `_PC_PIPE_BUF` 512, `_PC_CHOWN_RESTRICTED` and `_PC_NO_TRUNC` 1, `_PC_FILESIZEBITS` 64, `_PC_REC_MAX_XFER_SIZE` -1, `_PC_PRIO_IO` and `_PC_SYNC_IO` 0. Whether the console's filesystems report the same is not verified. It throws for the names whose UFS value depends on the mount (`_PC_ALLOC_SIZE_MIN`, `_PC_REC_INCR_XFER_SIZE`, `_PC_REC_MIN_XFER_SIZE`, `_PC_MIN_HOLE_SIZE`), the kernel options (`_PC_ACL_EXTENDED`, `_PC_ACL_PATH_MAX`, `_PC_ACL_NFS4`, `_PC_MAC_PRESENT`) or the page size (`_PC_REC_XFER_ALIGN`), and on which UFS and `vop_stdpathconf` disagree (`_PC_MAX_CANON`, `_PC_MAX_INPUT`, `_PC_VDISABLE`, `_PC_ASYNC_IO`); other names fail with `EINVAL` +- [lchmod](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws for a symbolic link on Windows and Linux, whose hosts cannot change a link's own mode; on macOS the link's mode is changed. Anything else goes to `fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW)`, as FreeBSD 11.4's [`sys_lchmod`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2490-L2496) does, so a file or directory is changed as by `chmod`, with `fchmodat`'s path lookup and Windows mode mapping. On Windows any reparse point counts as a link. Console behaviour is unverified +- [pathconf, fpathconf, _fpathconf](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) report FreeBSD 11's `ufs_pathconf` constants for files and directories alike (`_PC_LINK_MAX` 32767, `_PC_NAME_MAX` 255, `_PC_PATH_MAX` and `_PC_SYMLINK_MAX` 1024, `_PC_PIPE_BUF` 512, `_PC_CHOWN_RESTRICTED` and `_PC_NO_TRUNC` 1, `_PC_FILESIZEBITS` 64, `_PC_REC_MAX_XFER_SIZE` -1, `_PC_PRIO_IO` and `_PC_SYNC_IO` 0); whether the console's filesystems report the same is not verified. They throw for names whose UFS value depends on the mount, the kernel options or the page size, and where UFS and `vop_stdpathconf` disagree (`_PC_MAX_CANON`, `_PC_MAX_INPUT`, `_PC_VDISABLE`, and `_PC_ASYNC_IO` in `pathconf`); other names fail with `EINVAL`. `fpathconf` and `_fpathconf` answer `_PC_ASYNC_IO` 200112 and, for a pipe, `_PC_PIPE_BUF` 512 and `EINVAL` otherwise, as FreeBSD 11's `kern_fpathconf` does; a guest socket is not a host descriptor, so the `fstat` check reports it as `EBADF`, as in `fchown` - [mkdirat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `mkdir`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. On Windows, `mode` is ignored. Console behaviour is unverified -- [readlink](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows reads a symbolic link's target from its reparse data and returns it with `/` separators, only when the target is relative, as `symlink` creates them. A symbolic link with an absolute target returns its print name, or its substitute name when the print name is empty, without a `\??\` or `\\?\` prefix. A directory junction returns the same kind of name when it is a drive path (`X:\...`), using the print name when that is a drive path and the substitute name otherwise. A mounted volume, whose substitute name is `\??\Volume{GUID}\`, stays a throw, because the name left after the prefix is not a drive path. Any other reparse point throws too, where FreeBSD reports `EINVAL` for a file that is not a link +- [readlink](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows reads a symbolic link's target from its reparse data and returns it with `/` separators, only when the target is relative, as `symlink` creates them. A symbolic link with an absolute target returns its print name, or its substitute name when the print name is empty, without a `\??\` or `\\?\` prefix. A directory junction returns the same kind of name when it is a drive path (`X:\...`), using the print name when that is a drive path and the substitute name otherwise. A mounted volume (`\??\Volume{GUID}\`), any other name surrogate and every reparse point without the name surrogate bit (WOF compression, deduplication, cloud files) return `EINVAL`, as FreeBSD does for a file that is not a link and as CPython's `os.readlink`, which reads only symbolic links and junctions, does +- [aio_read, aio_write, aio_fsync, aio_error, aio_return, aio_suspend, aio_waitcomplete, aio_cancel](../../../core/libs/prx/libkernel_sys/Aio.cpp) (libkernel_sys) - a request with `SIGEV_SIGNAL`, `SIGEV_THREAD_ID` or `SIGEV_KEVENT` completion, or on a socket, throws, because delivering a guest signal or kevent and socket aio are not modelled. One worker thread runs the process's requests one at a time in submission order, so a request is cancellable only until that thread takes it. A queued request keeps the descriptor number, not the open file: closing or reusing the descriptor before it runs changes what it reaches. Pipes fail with `EOPNOTSUPP`, as FreeBSD 11 does with `vfs.aio.enable_unsafe` 0; the console's setting is unverified. Waits are not interrupted by signals (`EINTR`), and timeouts under 1 ms round up to 1 ms (hz 1000). Otherwise the rules follow FreeBSD 11 `vfs_aio.c` - [readlinkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `readlink`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Console behaviour is unverified +- [link](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows resolves a symbolic link source through the host, which follows longer chains than FreeBSD: a chain of 40 links is linked, where FreeBSD 11.4 returns `ELOOP` after 32 (`MAXSYMLINKS`). A loop returns `ELOOP` as on FreeBSD +- [pthread_attr_setcreatesuspend_np, pthread_resume_np, pthread_resume_all_np, pthread_multi_np, scePthreadAttrSetcreatesuspend, scePthreadResume, scePthreadResumeAll and scePthreadMulti](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - when a thread created suspended stops relative to its signal mask is unmeasured on a console. Return values and errors follow FreeBSD 11.4 libthr ([`thr_attr.c`](https://github.com/freebsd/freebsd-src/blob/8c639945/lib/libthr/thread/thr_attr.c#L369-L381), [`thr_resume_np.c`](https://github.com/freebsd/freebsd-src/blob/8c639945/lib/libthr/thread/thr_resume_np.c#L47-L97), [`thr_multi_np.c`](https://github.com/freebsd/freebsd-src/blob/8c639945/lib/libthr/thread/thr_multi_np.c#L40-L51)) and the code of firmware 13.42's `libkernel_sys.sprx` ([#9573](https://github.com/boykopovar/AnyPS5/pull/9573#issuecomment-6393877843)), where the sce forms add `0x80020000` to a non-zero errno - [fstatat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `stat`, or to `lstat` with `AT_SYMLINK_NOFOLLOW` (0x200). Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory, and flags other than `AT_SYMLINK_NOFOLLOW` return `EINVAL`. PPSA74126 imports it. Console behaviour is unverified - [unlinkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rmdir` (with `AT_REMOVEDIR`, 0x800) or `unlink`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Removing a directory without `AT_REMOVEDIR` fails with `EPERM`, removing `.` or `..` with `AT_REMOVEDIR` fails with `EINVAL`, and flags other than `AT_REMOVEDIR` fail with `EINVAL`. Console behaviour is unverified - [renameat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rename`. Any other relative path is joined to its descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to relative descriptors, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Console behaviour is unverified - [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask +- [sceKernelGetModuleInfoInternal, sceKernelGetModuleListInternal and sceKernelLoadStartModuleForSysmodule](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) answer for every loaded module, system modules included. `sceKernelGetModuleInfoInternal` fills the 0x1A8-byte `SceKernelModuleInfoEx` of a module handle the way `sceKernelGetModuleInfoFromAddr` does for an address, with [shadPS4's checks](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/kernel/process.cpp#L243-L258) in their order: `EFAULT` for a null pointer, `EINVAL` when `st_size` is not 0x1A8, `ESRCH` for an unknown handle. `sceKernelGetModuleListInternal` returns the same list as `sceKernelGetModuleList`, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd4/kernel/ps4_libkernel.pas#L852-L858) does; fpPS4 marks that as a guess, and neither is checked on a PS5. `sceKernelLoadStartModuleForSysmodule` forwards to `sceKernelLoadStartModule` - [chroot](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L776-L807) and [`pwd_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L3059-L3085) for a root caller: `EFAULT` for a null path, `ENOENT` for an empty or missing one, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255, and `ENOTDIR` for a path that is not a directory or passes through a file. A directory that is the guest root (`/`, `/..`, or `.` after `fchdir` to a descriptor of `/`) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with `EPERM` (`kern.chroot_allow_open_directories` 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (`/app0`, `/savedata0`) that do not move with it. On the console a title starts in a sandbox root, where `pwd_chroot` refuses with `EPERM` while a directory is open; whether that holds for titles, and whether a title holds `PRIV_VFS_CHROOT`, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as `fchdir` to its saved descriptor of `/` then `chroot(".")`, to put its sandbox root back after a jailbreak daemon replaced it ([`put_root_back`](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/blob/2ed1ab5a06995aa198f7bccd4da0f90c3fc29d39/src/porpoise_jailbreak.cpp#L303-L331)) +- [Console identity](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - `get_authinfo` errors and capability bits remain unverified on PS5; they follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/kernel.cpp), leaving the authority ID and other capability, attribute and credential words zero. The host models a retail unit (`IsCEX` 1, `IsDevKit` 0). The system OpenPSID reuses the ordinary ID; firmware 13.42 reads a separate `machdep.openpsid_for_sys`, whose value is unknown. Null returns positive 22 per the [module report](https://github.com/boykopovar/AnyPS5/pull/10524#issuecomment-6393566914). - [fchown](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled: the guest's own owner and group (0) and the unchanged value (-1) are accepted, a descriptor that is not open fails with `EBADF`, and any other owner or group throws, naming it. The check reads the descriptor with `fstat`, ignoring the CRT's invalid-parameter handler on Windows as `DescriptorControl` does. The console's ownership rules are unverified. Morrowind (`mshivam019/morrowind-ps5`) imports it +- [chown, lchown and fchownat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled, as for `fchown`: owner and group 0 and the unchanged value (-1) are accepted, and any other owner or group throws, naming it. The path is looked up first, as in FreeBSD 11's `kern_fchownat`, with `stat`, with `lstat` for `lchown`, and with `fstatat` for `fchownat`, so a lookup error is returned before the owner is checked and nothing is changed on the host. `fchownat` flags other than `AT_SYMLINK_NOFOLLOW` (0x200) return `EINVAL`, as in FreeBSD 11. The console's ownership rules are unverified +- [linkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) resolves `AT_FDCWD` (-100) and absolute paths as `link` does, and flags other than `AT_SYMLINK_FOLLOW` (0x400) return `EINVAL`, as in FreeBSD 11. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. A symbolic link as the source without `AT_SYMLINK_FOLLOW` throws, where FreeBSD links the symbolic link itself. Console behaviour is unverified +- [pthread_set_defaultstacksize_np and scePthreadSetDefaultstacksize](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) set the stack size that `scePthreadAttrInit` and a thread created without attributes use, as [fpPS4's `pthread_set_defaultstacksize_np`](https://github.com/red-prig/fpPS4/blob/04cefd4/kernel/ps4_pthread_attr.pas#L89-L102) does: a size of 16384 bytes or less fails with `EINVAL` (`0x80020016` from the sce form) and there is no upper limit, so a default the host cannot allocate fails only when a thread is created. Attributes initialised earlier keep their size. Not checked on a PS5 +- [sceKernelTitleWorkaroundIsEnabled](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - not measured on a console; the `{version, align, ids[2]}` layout, the bit test and the limit of 0x3A bits follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/kernel/kernel.cpp), which notes that the limit changes between firmwares. A negative bit is `EINVAL` here; shadPS4 compares it signed and reads outside `ids`. Titles usually pass the `title_workaround` block of `sceKernelGetAppInfo`, which does not write that block here, so the bits read are whatever the caller's memory held +- [sceKernelGetProcessType](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - not measured on a console; as in shadPS4, the title's own pid, -1 and 0 are type 0 (big application) and any other pid is `ESRCH` + +### Unknown function info + +- [scePthreadBarrier*, scePthreadBarrierattr*, scePthreadGetcpuclockid, scePthreadGetconcurrency and sceKernelMsync](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) call libkernel's `pthread_barrier*`, `pthread_getcpuclockid`, `pthread_getconcurrency` and `msync` and turn a POSIX error into `0x80020000 | errno`; that the console's `sce` forms do the same is not measured. Signatures follow the [Orbital type database](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json); `scePthreadBarrierInit` ignores its name, and `scePthreadBarrierWait` returns -1 to the serial thread, both as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/barrier.cpp) (PS4). `scePthreadGetconcurrency` returns the level unchanged +- [scePthreadAttrGetscope, scePthreadAttrSetscope, scePthreadAtfork and scePthreadSetconcurrency](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) call libkernel's `pthread_attr_getscope`, `pthread_attr_setscope`, `pthread_atfork` and `pthread_setconcurrency` and turn a POSIX error into `0x80020000 | errno`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/kernel/threads/pthread_attr.cpp#L392) exports `scePthreadAttrGetscope` as `ORBIS(posix_pthread_attr_getscope)`; shadPS4 has no export for the other three, whose names and NIDs are in the [symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) and whose signatures follow the same POSIX forms. Not measured on a console. As in libkernel, the scope is 0 (process) or 2 (system), and `scePthreadAtfork` handlers are only recorded, as no process forks diff --git a/docs/dev/debt/libmdbg_syscore.md b/docs/dev/debt/libmdbg_syscore.md index cb843c4f05..0201062426 100644 --- a/docs/dev/debt/libmdbg_syscore.md +++ b/docs/dev/debt/libmdbg_syscore.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceDebugAcquireAndUpdateDebugRegister, sceDebugAttachProcess, sceDebugCancelCoredump, sceDebugClearStepThread, sceDebugCreatePerfScratchDataArea, sceDebugCreatePerfScratchExecutableArea, sceDebugCreateScratchDataArea, sceDebugCreateScratchDataAreaForPrx, sceDebugCreateScratchExecutableArea, sceDebugCreateScratchExecutableAreaForPrx, sceDebugDestroyPerfScratchDataArea, sceDebugDestroyPerfScratchExecutableArea, sceDebugDestroyScratchDataArea, sceDebugDestroyScratchExecutableArea, sceDebugDetachProcess, sceDebugGetApplicationIdByTitleId, sceDebugGetApplicationInfo, sceDebugGetApplicationList, sceDebugGetCrashInfoDetailForCoredump, sceDebugGetCrashInfoForCoredump, sceDebugGetDLLoadFlag, sceDebugGetDataListOfUltQueue, sceDebugGetDebugRegisterStatusMap, sceDebugGetEventList, sceDebugGetEventListForEQueueFd, sceDebugGetEventSubscriptionList, sceDebugGetEventSubscriptionListForEQueueFd, sceDebugGetFiberInfo, sceDebugGetFileInfo, sceDebugGetFileInfoForCoredump, sceDebugGetFileList, sceDebugGetFileListForCoredump, sceDebugGetGpuInfoArea, sceDebugGetJobManagerInfo, sceDebugGetJobManagerSequenceInfo, sceDebugGetJobManagerSequenceList, sceDebugGetModuleInfo, sceDebugGetModuleList, sceDebugGetModuleMetaData, sceDebugGetMonoVMInfo, sceDebugGetMonoVMList, sceDebugGetProcessCoredumpHandlerEventBlob, sceDebugGetProcessCoredumpHandlerEventInfo, sceDebugGetProcessCoredumpHandlerResult, sceDebugGetProcessEventCntlFlag, sceDebugGetProcessInfo, sceDebugGetProcessList, sceDebugGetProcessPropertyForCoredump, sceDebugGetProcessResourceStatCount, sceDebugGetProcessResourceStatData, sceDebugGetProcessTime, sceDebugGetProcessTimeCounter, sceDebugGetSyncExclusiveWaiterList, sceDebugGetSyncObjectData, sceDebugGetSyncObjectList, sceDebugGetSyncWaiterList, sceDebugGetSyncWaiterListForEQueueFd, sceDebugGetSystemStatusBlob, sceDebugGetSystemStatusCount, sceDebugGetThreadInfo, sceDebugGetThreadInfoByIdent, sceDebugGetThreadList, sceDebugGetThreadListAsIdent, sceDebugGetUlObjectList, sceDebugGetUltCondvarInfo, sceDebugGetUltInfo, sceDebugGetUltListOfUltRuntime, sceDebugGetUltMutexInfo, sceDebugGetUltQueueDataResourcePoolInfo, sceDebugGetUltQueueInfo, sceDebugGetUltRuntimeInfo, sceDebugGetUltRwlockInfo, sceDebugGetUltSemaphoreInfo, sceDebugGetUltWaitingQueueResourcePoolInfo, sceDebugGetVirtualMemoryDetailInfoForCoredump, sceDebugGetVirtualMemoryInfo, sceDebugGetVirtualMemoryInfoForCoredump, sceDebugGetWaitingListOfUltCondvar, sceDebugGetWaitingListOfUltMutex, sceDebugGetWaitingListOfUltQueue, sceDebugGetWaitingListOfUltRwlock, sceDebugGetWaitingListOfUltSemaphore, sceDebugGetWorkerThreadListOfUltRuntime, sceDebugInit, sceDebugInitForCoredump, sceDebugInitForTest, sceDebugIpmiGetBlockTimeInfoList, sceDebugIpmiGetBlockedIpcInfo, sceDebugIpmiGetChannelInfo, sceDebugIpmiGetChannelKidList, sceDebugIpmiGetChannelWaitingThreadList, sceDebugIpmiGetClientInfo, sceDebugIpmiGetClientKidList, sceDebugIpmiGetClientKidListByDump, sceDebugIpmiGetClientKidListByServerKid, sceDebugIpmiGetConnectRequestInfoList, sceDebugIpmiGetConnectionInfoList, sceDebugIpmiGetConnectionWaitingThreadListByClientKid, sceDebugIpmiGetConnectionWaitingThreadListBySessionKid, sceDebugIpmiGetDump, sceDebugIpmiGetEncryptedInfoAllForCoredump, sceDebugIpmiGetKidInfoListForCoredump, sceDebugIpmiGetServerDispatchInfo, sceDebugIpmiGetServerInfo, sceDebugIpmiGetServerKidList, sceDebugIpmiGetServerKidListByDump, sceDebugIpmiGetServerWaitingThreadList, sceDebugIpmiGetSessionInfo, sceDebugIpmiGetSessionKidList, sceDebugIpmiGetSessionKidListByServerKid, sceDebugIpmiGetTidListByDump, sceDebugKillApplication, sceDebugKillProcess, sceDebugNoStopChildProcesses, sceDebugNoStopOnDLLoad, sceDebugProcessSpawn, sceDebugReadEvent, sceDebugReadEventForTest, sceDebugReadProcessMemory, sceDebugReadProcessMemoryForSDBGP, sceDebugReadProcessRegister, sceDebugReadProcessRegisterForSDBGP, sceDebugReadProcessResourceStatData, sceDebugReadThreadRegister, sceDebugReadThreadRegisterForSDBGP, sceDebugReleaseDebugRegister, sceDebugResumeApplication, sceDebugResumeProcess, sceDebugResumeThread, sceDebugSetProcessEventCntlFlag, sceDebugSetStepThread, sceDebugSpawnApplication, sceDebugStopChildProcesses, sceDebugStopOnDLLoad, sceDebugSuspendApplication, sceDebugSuspendProcess, sceDebugSuspendThread, sceDebugTriggerCoredump, sceDebugTriggerCoredumpForSystem, sceDebugTriggerVrCaptureDump, sceDebugWriteProcessMemory, sceDebugWriteProcessRegister and sceDebugWriteThreadRegister](../../../core/libs/prx/libmdbg_syscore/Export.cpp) (libmdbg_syscore) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libmdbg_syscore.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified +- [sceDebugAcquireAndUpdateDebugRegister, sceDebugAttachProcess, sceDebugCancelCoredump, sceDebugClearStepThread, sceDebugCreatePerfScratchDataArea, sceDebugCreatePerfScratchExecutableArea, sceDebugCreateScratchDataArea, sceDebugCreateScratchDataAreaForPrx, sceDebugCreateScratchExecutableArea, sceDebugCreateScratchExecutableAreaForPrx, sceDebugDestroyPerfScratchDataArea, sceDebugDestroyPerfScratchExecutableArea, sceDebugDestroyScratchDataArea, sceDebugDestroyScratchExecutableArea, sceDebugDetachProcess, sceDebugGetApplicationIdByTitleId, sceDebugGetApplicationInfo, sceDebugGetApplicationList, sceDebugGetCrashInfoDetailForCoredump, sceDebugGetCrashInfoForCoredump, sceDebugGetDLLoadFlag, sceDebugGetDataListOfUltQueue, sceDebugGetDebugRegisterStatusMap, sceDebugGetEventList, sceDebugGetEventListForEQueueFd, sceDebugGetEventSubscriptionList, sceDebugGetEventSubscriptionListForEQueueFd, sceDebugGetFiberInfo, sceDebugGetFileInfo, sceDebugGetFileInfoForCoredump, sceDebugGetFileList, sceDebugGetFileListForCoredump, sceDebugGetGpuInfoArea, sceDebugGetJobManagerInfo, sceDebugGetJobManagerSequenceInfo, sceDebugGetJobManagerSequenceList, sceDebugGetModuleInfo, sceDebugGetModuleList, sceDebugGetModuleMetaData, sceDebugGetMonoVMInfo, sceDebugGetMonoVMList, sceDebugGetProcessCoredumpHandlerEventBlob, sceDebugGetProcessCoredumpHandlerEventInfo, sceDebugGetProcessCoredumpHandlerResult, sceDebugGetProcessInfo, sceDebugGetProcessList, sceDebugGetProcessPropertyForCoredump, sceDebugGetProcessResourceStatCount, sceDebugGetProcessResourceStatData, sceDebugGetProcessTime, sceDebugGetProcessTimeCounter, sceDebugGetSyncExclusiveWaiterList, sceDebugGetSyncObjectData, sceDebugGetSyncObjectList, sceDebugGetSyncWaiterList, sceDebugGetSyncWaiterListForEQueueFd, sceDebugGetSystemStatusBlob, sceDebugGetSystemStatusCount, sceDebugGetThreadInfo, sceDebugGetThreadInfoByIdent, sceDebugGetThreadList, sceDebugGetThreadListAsIdent, sceDebugGetUlObjectList, sceDebugGetUltCondvarInfo, sceDebugGetUltInfo, sceDebugGetUltListOfUltRuntime, sceDebugGetUltMutexInfo, sceDebugGetUltQueueDataResourcePoolInfo, sceDebugGetUltQueueInfo, sceDebugGetUltRuntimeInfo, sceDebugGetUltRwlockInfo, sceDebugGetUltSemaphoreInfo, sceDebugGetUltWaitingQueueResourcePoolInfo, sceDebugGetVirtualMemoryDetailInfoForCoredump, sceDebugGetVirtualMemoryInfo, sceDebugGetVirtualMemoryInfoForCoredump, sceDebugGetWaitingListOfUltCondvar, sceDebugGetWaitingListOfUltMutex, sceDebugGetWaitingListOfUltQueue, sceDebugGetWaitingListOfUltRwlock, sceDebugGetWaitingListOfUltSemaphore, sceDebugGetWorkerThreadListOfUltRuntime, sceDebugInit, sceDebugInitForCoredump, sceDebugInitForTest, sceDebugIpmiGetBlockTimeInfoList, sceDebugIpmiGetBlockedIpcInfo, sceDebugIpmiGetChannelInfo, sceDebugIpmiGetChannelKidList, sceDebugIpmiGetChannelWaitingThreadList, sceDebugIpmiGetClientInfo, sceDebugIpmiGetClientKidList, sceDebugIpmiGetClientKidListByDump, sceDebugIpmiGetClientKidListByServerKid, sceDebugIpmiGetConnectRequestInfoList, sceDebugIpmiGetConnectionInfoList, sceDebugIpmiGetConnectionWaitingThreadListByClientKid, sceDebugIpmiGetConnectionWaitingThreadListBySessionKid, sceDebugIpmiGetDump, sceDebugIpmiGetEncryptedInfoAllForCoredump, sceDebugIpmiGetKidInfoListForCoredump, sceDebugIpmiGetServerDispatchInfo, sceDebugIpmiGetServerInfo, sceDebugIpmiGetServerKidList, sceDebugIpmiGetServerKidListByDump, sceDebugIpmiGetServerWaitingThreadList, sceDebugIpmiGetSessionInfo, sceDebugIpmiGetSessionKidList, sceDebugIpmiGetSessionKidListByServerKid, sceDebugIpmiGetTidListByDump, sceDebugKillApplication, sceDebugKillProcess, sceDebugNoStopChildProcesses, sceDebugNoStopOnDLLoad, sceDebugProcessSpawn, sceDebugReadEvent, sceDebugReadEventForTest, sceDebugReadProcessMemory, sceDebugReadProcessMemoryForSDBGP, sceDebugReadProcessRegister, sceDebugReadProcessRegisterForSDBGP, sceDebugReadProcessResourceStatData, sceDebugReadThreadRegister, sceDebugReadThreadRegisterForSDBGP, sceDebugReleaseDebugRegister, sceDebugResumeApplication, sceDebugResumeProcess, sceDebugResumeThread, sceDebugSetStepThread, sceDebugSpawnApplication, sceDebugStopChildProcesses, sceDebugStopOnDLLoad, sceDebugSuspendApplication, sceDebugSuspendProcess, sceDebugSuspendThread, sceDebugTriggerCoredump, sceDebugTriggerCoredumpForSystem, sceDebugTriggerVrCaptureDump, sceDebugWriteProcessMemory, sceDebugWriteProcessRegister and sceDebugWriteThreadRegister](../../../core/libs/prx/libmdbg_syscore/Export.cpp) (libmdbg_syscore) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libmdbg_syscore.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/relinker.md b/docs/dev/debt/relinker.md index 43ff7062d0..918c5348fb 100644 --- a/docs/dev/debt/relinker.md +++ b/docs/dev/debt/relinker.md @@ -2,18 +2,27 @@ ### Functional +- The [syscall scanner](../../../core/relinker/relinker/src/analysis/SyscallScanner.cpp) decodes executable ranges linearly without code/data boundaries. Embedded data between routines can fail decoding or be reported as a forbidden syscall ([#7547](https://github.com/boykopovar/AnyPS5/issues/7547)). Unwind ranges alone omit real code. The [synthetic fixture](../../../core/relinker/relinker/tests/CodeDataScanningTests.cpp) characterizes these rejections and checks a reachable routine beyond the gap; it does not resolve the boundary problem. + - [Executable guest imports](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) - on Linux and macOS, a relocated executable import resolved to one guest provider requires that module and its dependencies at startup, even when it was discovered through `--module-dir` without a DT_NEEDED entry. Unreferenced symbols and no-op relocations do not require an otherwise deferred module. Console module-start ordering is unmeasured. -- [Runtime-loaded modules](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) that another module needs are initialized at startup with no start arguments, before the module that needs them is loaded; the console is assumed to start them when that module is loaded. -- [Runtime-loaded modules the executable needs](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) are mapped at startup on Windows and started when the title loads them with `sceKernelLoadStartModule`, with their start arguments, as a Unity player loads `mono-ps4.sprx`. A title that calls such a module before loading it finds its initializers not run; whether the console starts it earlier is not measured. A module the executable only imports from and the title never loads itself gets no `module_start` and no initializers at all under this deferral, and nothing reports it. Linux and macOS still start it at startup. +- [Runtime-loaded modules](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) can be mapped early for bindings. Linux defers their initialization to an explicit load of each module; standard SDK folders keep their mapping initializer, including through the browser conversion API. On Windows and macOS, a module another module needs is initialized at startup without start arguments; console dependency-start ordering is unmeasured. +- [Runtime-loaded modules the executable needs](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) are mapped at startup on Windows and started when the title loads them with `sceKernelLoadStartModule`, with their start arguments, as a Unity player loads `mono-ps4.sprx`. A title that calls such a module before loading it finds its initializers not run; whether the console starts it earlier is not measured. A module the executable only imports from and the title never loads itself gets no `module_start` and no initializers at all under this deferral. The [entry stub](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) prints `Deferred module ` at startup for each such module that an import of the executable names, whether or not the title loads it later; an import without a module name is not reported. macOS still starts it at startup. - The executable file that [relinker](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens a console when launched, unless it is relinked with `--windows-gui`, which selects the GUI subsystem. A GUI build started from Explorer has no standard output or error handle; the [entry stub](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) gives each missing one `anyps5.log` beside the executable, replacing the previous run's file (#3282). Errors about the executable path or about waiting for the previous LoadExec process are written before the log is opened and are lost when the standard error handle is missing. If the log cannot be created, output to the missing handles is lost. - [Guest import registries](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) - declared host imports of bundled modules are recorded with their module name and relocation target, even when another guest module exports the same stripped NID. No-op relocations are omitted. Registries describe import bindings, not implementation correctness or runtime reachability. -- Converted Windows main executables [omit `DYNAMIC_BASE`](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) by default, not only with `--windows-fixed-base`: on the console the executable lies inside the 40-bit window that the title's own `sceLibcMspaceCreate` checks (#3043). Relocations are kept, so if `0x140000000` is taken Windows relocates the image, possibly above that window. Verified only on Windows 10. +- Registry [call-site analysis](../../../core/relinker/relinker/src/analysis/CallSiteResolver.cpp) does not classify instructions with `F3`, `66`, `67`, `F0`, `2E`, `26`, `36`, FS/GS prefixes, or `8B` loads without REX.W, so missing call sites do not establish that an import is unused. +- Converted Windows main executables [omit `DYNAMIC_BASE`](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) and [prefer `0x400000`](../../../core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp) for their lowest ELF load page by default, not only with `--windows-fixed-base`: on the console the executable lies inside the 40-bit window that the title's own `sceLibcMspaceCreate` checks (#3043). Where the console loads an executable that does not require an address is not measured; `0x400000` is the base that executables reading their GOT through `app0/imports.txt` assume (#4013). Relocations are kept, so if that range is taken Windows can relocate the image, possibly above that window; no run has observed this, and from this base an image cannot reach the shared user data page at `0x7ffe0000`, since Windows refuses a `SizeOfImage` above `0x77000000` (#9332). Verified on Windows 11 only. - [SDK memory-management syscall traps](../../../core/relinker/relinker/src/analysis/SyscallScanner.cpp) under `--trap-sdk-syscall-stubs` recognize three exact instruction sequences from [ps5-payload-dev SDK libc/mman.c](https://github.com/ps5-payload-dev/sdk/blob/12cf3dda40/libc/mman.c), observed in Nuvio Maxd 0.5.15 (PPSA99288): syscall 74 and the two syscall 477 paths, with their argument preparation and carry-flag error handling. Matching requires a decoded instruction boundary and the complete sequence inside the scanned code range. Other SDK/compiler output remains rejected. The matched syscall becomes `UD2`; this does not implement mmap or mprotect and a call still faults. - The [instruction decoder](../../../core/relinker/codegen/src/x86/X64InstructionDecoder.cpp) reads a 16-bit displacement for `66 E8`, `66 E9` and `66 0F 80`-`8F` without REX.W, as AMD processors, the console included, do (binutils `objdump -M amd64`; REX.W selects the 32-bit form). AMD also truncates the target of every near relative branch with that prefix, rel8, LOOP and JRCXZ forms included, to 16 bits. Instruction metadata marks these branches as truncated and reports no branch target for them, and stubs refuse to move them. Compilers do not emit these forms. +- Converted Linux main executables are [placed at `0x400000`](../../../core/relinker/relinker/src/pipeline/Conversion.cpp) by default, as `ET_EXEC` without ASLR, not only with `--linux-fixed-base`: the console executable lies inside the 40-bit window that the title's own `sceLibcMspaceCreate` checks (#3043). Where the console loads an executable that does not require an address is not measured; `0x400000` is the base that executables reading their GOT through `app0/imports.txt` assume (#4013). The relinker refuses an image that spans 2 GiB or more, so the executable ends below the guest arena at `0x200000000`. With `--lazy-binding` the executable stays position independent. +- `--frame-red-zone` frames only leaf functions that start with a chain of pushes of at least five bytes (`push rbp; mov rbp, rsp; push ...`, or pushes alone, which may be followed by `sub $n, %rsp` or `lea -n(%rsp), %rsp`) and leave through exits of at least five bytes that undo that allocation with `add` or `lea`, pop the same registers and `ret` ([framer](../../../core/relinker/codegen/src/x86/RedZoneFramer.cpp)). Leaves with a frame pointer that allocate, leaves with neither pushes nor an allocation, shorter prologues and exits, exits that free another size, and functions with indirect jumps or other RSP arithmetic keep their red zone. +- In a red-zone leaf without a frame pointer, `--frame-red-zone` lowers RSP after the prologue by the deepest displacement below RSP, rounded up to 16, and moves every RSP operand by the same amount ([framer](../../../core/relinker/codegen/src/x86/RedZoneFramer.cpp)). A leaf that takes the address of a slot below RSP, or uses an RSP operand with an index register, is lowered by the whole 128 bytes, since a pointer or an index can reach deeper than any displacement; values below RSP reached through any other register are not seen. Leaves with a frame pointer are lowered by 128 bytes. Inside a moved frame the CFA rules of the guest's unwind tables are off by the shift. +- An RSP operand that `--frame-red-zone` cannot move in place, an 8-bit displacement the shift takes past 0x7f or one without a displacement, moves to a stub with the instructions after it up to five bytes and is re-encoded there with the moved displacement ([framer](../../../core/relinker/codegen/src/x86/RedZoneFramer.cpp)). The leaf is skipped when that site would take in a branch target, a branch, ret or RIP-relative instruction, or another site, and when the operand is EVEX, whose 8-bit displacement is scaled. A game patch over a framed site or a moved displacement is refused. +- In `--frame-red-zone`, function bounds come from the executable's `.eh_frame` FDEs: each function is decoded from its FDE start to its FDE end, or to int3 padding before it, and the relinker stops with an error when the executable has none. Code outside every FDE, such as a function without unwind information, is not framed ([framer](../../../core/relinker/codegen/src/x86/RedZoneFramer.cpp)). - `--to-intel` lowers MCOMMIT to STC plus NOP padding and RDPRU through a runtime-checked stub in the [matcher](../../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp): AMD APM Vol. 3 defines RDPRU ECX 0 as MPERF and ECX 1 as APERF, neither readable on Intel hosts, so both selectors return the host TSC as a disclosed monotonic-counter approximation (their ratio always reads 1.0) while other selectors yield zero EDX:EAX. STC asserts CF=1 because under x86-TSO prior stores are already ordered and guest memory is host-DRAM-backed, leaving the commit-success case as the only observable one. Non-zero RDPRU selectors, the TSC-for-MPERF/APERF substitution, and any console path reporting MCOMMIT CF=0 are unverified. -- [Unscoped imports on Windows](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) - an import without a module that no searched library exports under its name is searched again under the NID of that name, because homebrew built with the [ps5-payload-dev SDK](https://github.com/ps5-payload-dev/sdk) imports by symbol name (Quake, from the websrv homebrew launcher; Mari0, #6239) while the libraries in `libs/` export NIDs. A dependency named with `.sprx`, `.suprx` or `.debug_prx` loads the `.prx` library of the same stem from `libs/`, and `libkernel_web` also loads `libkernel.prx`, as `libSceLibcInternal` already loads `libc.prx`: AnyPS5's `libkernel_web.prx` exports only two functions, where the console's `libkernel_web.sprx` is a libkernel variant. A name that matches a library's export as written still binds to that export first. Linux and macOS resolve these imports through the host loader by name and are unchanged. -- The length-changing path of the [instruction rewriter](../../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. +- [Unscoped imports on Windows](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) - an import without a module that no searched library exports under its name is searched again under the NID of that name, because homebrew built with the [ps5-payload-dev SDK](https://github.com/ps5-payload-dev/sdk) imports by symbol name (Quake, from the websrv homebrew launcher; Mari0, #6239) while the libraries in `libs/` export NIDs. A dependency named with `.sprx`, `.suprx` or `.debug_prx` loads the `.prx` library of the same stem from `libs/`, and `libkernel_web` also loads `libkernel.prx`, as `libSceLibcInternal` already loads `libc.prx`: AnyPS5's `libkernel_web.prx` exports only two functions, where the console's `libkernel_web.sprx` is a libkernel variant. A name that matches a library's export as written still binds to that export first. macOS resolves these imports through the host loader by name and is unchanged. +- [Unscoped imports on Linux](../../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) - in an executable or bundled module that names a dependency with `.sprx`, `.suprx` or `.debug_prx`, as homebrew built with the [ps5-payload-dev SDK](https://github.com/ps5-payload-dev/sdk) does, an import without a module that is named by symbol is bound to the NID of that name only. Unlike Windows, the name as written is not tried first, because the host loader also searches the host C library, which exports `malloc` or `pthread_create` under their names. A name that a bundled module exports still binds to that module, as does a bundled module's import of a name the executable exports, and such a dependency loads the `.prx` library of the same stem. Unqualified imports of other executables and bundled modules are still bound by name. +- The length-changing path of the [instruction rewriter](../../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. The rewriter conservatively rejects length-changing edits when a surviving truncated relative branch would move relative to its target (only one of the two lies after the cut) or when its section-relative target falls outside the 16-bit range, because the runtime address base needed to resolve a wrapped destination is unavailable; truncated branches that keep their distance and stay in range are accepted. - The [converter](../../../core/relinker/codegen/src/Amd64OnlyConverter.cpp) takes the branch targets that keep an instruction in place from a linear scan of the code segment, so data in that segment decodes as branches too: OpenSSL's x86-64 assembly keeps strings such as "SHA1 block transform for x86_64, CRYPTOGAMS by " in its code, and in SuperTuxKart (PPSA69421) and Mortalfin (PPSA99141) the `jae` read from "sf" points into the VEX prefix of a 256-bit store. A direct branch target inside a scanned instruction counts only where every byte before it is a legacy prefix or REX, which a real branch can skip (a `lock` prefix, for example); a target in a VEX prefix, opcode or operand is ignored, as a jump-table entry that starts no instruction already is. A real branch to such a target, overlapping instructions as hand-written code can, is not seen, and the instruction it enters can be moved. - A `PT_SCE_RELRO` segment that no `PT_LOAD` covers, the PS4 layout, is [loaded](../../../core/relinker/relinker/src/parsing/ElfReader.cpp) as a read-write `PT_LOAD` and stays writable after relocation; FreeBSD rtld makes `PT_GNU_RELRO` read-only once relocation is done, and what the console does with `PT_SCE_RELRO` is not checked. - In Windows output, a guest instruction that addresses memory through GS becomes `UD2` and NOP padding of its own length ([TLS builder](../../../core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp)): the host owns GS, and the guest GS base that FreeBSD's `sysarch(AMD64_SET_GSBASE)` would set is not emulated. A converted module runs until such an instruction is reached, which then faults. Prospero Win (PPSA99995) has six in its Wine `ntdll`. @@ -22,12 +31,16 @@ - In the same executables and modules, the bytes between unwind functions are 16-byte slots of function tails and padding, and a tail can begin inside an instruction. The [instruction scanner](../../../core/relinker/codegen/src/InstructionScanner.cpp) of the syscall and call-site scans therefore resumes at the next byte when bytes outside every unwind function do not decode, and ends a decoding outside them at the start of the next unwind function. Code without an unwind entry that does not decode from its first byte is skipped by these scans instead of failing the relink; inside an unwind function a decode failure still fails it. - Read-only data can also sit between the unwind functions of such images. There, a `syscall`, `sysenter` or `int 0x80` pattern fails the [syscall scan](../../../core/relinker/relinker/src/analysis/SyscallScanner.cpp) only when a constant syscall number is set before it; the others, and every `sysret`, are skipped and counted in one message. The number is looked for in the decoded instructions and, whatever alignment the decoding took, in the bytes before the pattern: a `mov eax, imm32`, `mov rax, imm32`, `mov rax, imm64` or `xor eax, eax`, optionally followed by `mov al, imm8` or `mov ax, imm16`, then by any number of register moves or zeroings that leave `rax` alone. A real syscall without an unwind entry whose number is set otherwise is not refused and runs on the host: from memory or another register (`mov eax, ecx`), by `push`/`pop` or arithmetic (`or eax, imm32`), or with any other instruction between the load and the pattern. - In the [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp) of the Windows and macOS outputs for such images, a relocation or `lea` target outside every unwind function is followed as code only when its linear decoding, up to the first jump, return or trap, the next unwind function or 256 instructions, holds an `fs:[disp32]` access, or when it is 16-byte aligned, starts less than 256 bytes after an unwind function and ends in a jump, return or trap. Every other such target is taken as data and counted in one message, and the Windows TLS rewrite fails instead of overwriting one. A TLS access of another form, such as `mov rax, fs:[rax]`, or a `gs` instruction in code reached only through such a target is therefore not rewritten or trapped. Other fs and gs instructions do not tell code from data there: string bytes decode as these prefixes, and in two measured titles about 2,500 of about 5,500 such targets decode to one before their first jump. +- [Embedded data tables and relative jump tables in inter-FDE gaps](../../../core/relinker/relinker/src/analysis/RelativeJumpTables.cpp) - in modules with `PT_GNU_EH_FRAME` or `PT_SCE_DYNLIBDATA`, a constant table of undecodable bytes that a RIP-relative `LEA` references, and a trailing relative jump table, are masked with `0x90` in the scan buffer before syscall scanning and kept out of the code roots of [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp), so `0F 78` data in `ubiservices.prx` (PPSA28155, #7547) is no longer refused as an illegal opcode. Masking starts at the `LEA` target, stops at the first byte whose linear decoding reaches a jump, return or trap, keeps gap code being scanned, and needs padding only from the function end to the table; A gap that decodes as valid instructions or that a direct branch targets is left alone. The gap properties are computed once in a backward pass. Output bytes are unchanged apart from syscall traps. - In executables and guest modules with a `PT_SCE_DYNLIBDATA` segment, the [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp) of the Windows and macOS outputs follows code only from the end of `PT_INTERP`, or of the `/libexec/ld-elf.so.1` string that begins the segment when no `PT_INTERP` covers it, as in OpenOrbis modules, to the end of the PLT after the last function of the unwind table, or to the end of that function when the PLT comes before the code, as in the `libSceFios2.prx` and `libc.prx` that CUSA02636 bundles (PLT at 0xc8); the pipeline's scans of executables above stop at this same end, also when the PLT comes before the code. A guest module with PLT relocations and no PLT before or after its unwind functions, or with a PLT that overlaps one, fails as the executable does. Code after that end is not analyzed, such as the `DT_FINI` routine without an unwind entry that follows the last unwind function in both modules and that the Windows and macOS outputs call when the program exits: a TLS access in it is not rewritten, the ELF entry point, `DT_INIT`, `DT_FINI`, a branch or a pointer into it is ignored, and a function symbol or init/fini array entry pointing into it fails the conversion. +- Under Rosetta (M5, macOS 27.0.1), a VMASKMOVPS, VMASKMOVPD, VPMASKMOVD or VPMASKMOVQ store or load that faults on an element other than an active first one ends the process (`unexpectedly need to EmulateForward on a synchronous exception`) instead of raising SIGSEGV or SIGBUS, so a masked store into a page the macOS write watch keeps read-only ends the title. `--to-rosetta` moves each masked store to a [stub](../../../core/relinker/codegen/src/x86/MaskedStoreLowering.cpp) that first writes to the first and last byte of every active element with `LOCK OR` of 0, which faults normally and changes nothing; each store then costs up to 16 locked byte writes. A page made read-only again by another thread between those writes and the store still ends the process. A stack-relative displacement that exceeds signed disp32 after the spill frame throws. Masked loads are not converted. - A relocation against a dynamic symbol entry that is all zero, like the null symbol, takes the value 0 plus its addend, as the gABI defines for `STN_UNDEF` ([pipeline](../../../core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp)). The Linux output imports such an entry as an unnamed weak symbol, which the host loader resolves to 0; the Windows and macOS outputs write the value into the slot ([relocation builder](../../../core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp)). What the console's loader does with such an entry is not measured. OliSe Player (PPSA01153) has seven of them, and its code calls through those slots only when they are not 0 ([#4437](https://github.com/boykopovar/AnyPS5/issues/4437)). - In macOS output, a guest instruction that addresses memory through GS becomes `UD2` and NOP padding of its own length ([macOS image writer](../../../core/relinker/elfpatcher/src/macos/MacOsImage.cpp)): the host owns GS, which holds the pthread TSD base the guest TLS stubs read, and the guest GS base that FreeBSD's `sysarch(AMD64_SET_GSBASE)` would set is not emulated. A converted module runs until such an instruction is reached, which then faults. Prospero Win (PPSA99995) has six in its Wine `ntdll`. - The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test. - `--to-rosetta` lowers what Rosetta lacks on top of `--to-intel` ([matcher](../../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp)). AVX2, FMA, BMI1/2, F16C and MOVBE were observed to execute on an M5 Max without being reported through CPUID; this is not a guarantee for other Rosetta hosts. ANDN and RDRAND fault on an M1 running macOS 15.3.1 and execute on an M1 running macOS 26.6.2, so on the M1 the difference follows the macOS version; on that host BEXTR, BLSI, BLSMSK, BLSR, TZCNT, LZCNT, POPCNT, BZHI, MULX, PDEP, PEXT, RORX, SARX, SHLX, SHRX, MOVBE, FMA, AVX2 and F16C execute as well, its CPUID leaf 1 reports none of MOVBE, FMA, AVX or F16C, and RDSEED, RDPID, CLWB, ADCX and ADOX raise SIGILL. They also execute on an M5 running macOS 26.4, where the rest of BMI1 and BMI2, LZCNT, PREFETCHW, CLFLUSHOPT, AVX, AVX2, FMA, F16C and MOVBE execute as well although CPUID leaf 7 reads zero and leaves 1 and 0x80000001 report none of them, and where RDSEED, CLWB, RDPID, ADCX and ADOX raise an illegal instruction. ANDN is lowered, but RDRAND and other BMI instructions still depend on the host. RDSEED becomes RDRAND and CLWB a NOP. RDPID returns processor 0, and RDTSCP runs unchanged with ECX set to 0 after it, because Rosetta's TSC_AUX is the constant 0xAAAAAAAB (also measured on an M5, macOS 27.0.1). An RDPID or RDTSCP site with no movable instruction after it for the jump is kept; a kept RDTSCP reports 0xAAAAAAAB. ADCX/ADOX fault under Rosetta and are not converted: compilers do not emit them, and code that checks CPUID sees no ADX. The conversions are covered by the converter tests. ANDN lowering is also executed by an x86-64 test build under Rosetta on the M1; the other conversions were originally checked on an M5 Max, outside this repository's CI. +- `--to-rosetta` output faults on an M2 running macOS 14.8.9 when it reaches AVX, AVX2, FMA, F16C, RDRAND or a BMI1/BMI2 instruction other than ANDN, TZCNT and LZCNT: Rosetta on that host raises SIGILL for them and for RDSEED, RDPID, CLWB, ADCX and ADOX ([matcher](../../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp)). Only TZCNT, LZCNT, POPCNT, MOVBE, CLFLUSHOPT, PREFETCHW and RDTSCP execute, and RDTSCP sets ECX to 0xAAAAAAAB. CPUID leaf 1 reports POPCNT and none of MOVBE, AVX, FMA, F16C or RDRAND, leaf 7 reads zero, and leaf 0x80000001 reports RDTSCP but neither LZCNT nor PREFETCHW. An M2 Max on macOS 27.2 reports the same CPUID but executes everything except RDSEED, RDPID, CLWB, ADCX and ADOX, so the faults follow the macOS version, not the chip. - `--to-intel` replaces the approximate legacy SSE and VEX.128 register forms of RSQRTPS/RCPPS and VRSQRTPS/VRCPPS with [correctly rounded](../../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). Legacy SSE forms preserve the destination's upper YMM bits; VEX forms clear them. The 256-bit, scalar and memory forms keep the native instruction, as does a site shorter than 5 bytes with no movable instruction after it for the jump (5 sites in PPSA21564, counted before a following RIP-relative instruction could move into the stub; not recounted). +- XSAVEC raises an illegal instruction under Rosetta and is not converted, because it writes the compacted format. XSAVEOPT, which also faults there, is rewritten to XSAVE in place by the [matcher](../../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) under `--to-rosetta` (`0F AE /6` becomes `/4`; prefixes and operand are kept). The two differ only in the init and modified optimizations, which let XSAVEOPT skip writing unchanged or initial state ([Intel SDM volume 1](https://cdrdv2-public.intel.com/812382/253665-sdm-vol-1.pdf), chapter 13), so XRSTOR restores the same state from either area. Measured on one M5 with macOS 27.0.1: XSAVE and XRSTOR handle x87, SSE and AVX state in the standard format, although CPUID leaf 1 reports neither XSAVE nor OSXSAVE and leaf 0xD sub-leaf 1 reports neither XSAVEOPT nor XSAVEC. The converter tests' x86-64 build executes the replacement XSAVE under Rosetta on that host. - `--trace-imports` ([Windows entry stub](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp), [Linux entry stub](../../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp)) exists for Windows and Linux output, not macOS. Only imports bound through a PLT slot, or through a GOT slot whose symbol is a function, are traced; a function reached through any other relocation, or looked up at run time with `sceKernelDlsym`, is not, and neither are weak imports or the imports of modules loaded at run time. On Linux the entry stub makes the page of every traced slot readable, writable and executable with `mprotect` before it stores the thunk, so a slot inside `PT_GNU_RELRO` can be replaced; those pages stay writable, and data pages become executable, while tracing is on - [ANDN lowering](../../../core/relinker/codegen/src/x86/AndnLowering.cpp) uses scalar NOT and AND for 32- and 64-bit register and memory operands under `--to-rosetta`, following the ANDN entry in [Intel SDM volume 2A](https://cdrdv2-public.intel.com/812383/253666-sdm-vol-2a.pdf). SF and ZF follow the result, CF and OF clear, and AF and PF are undefined. The scratch register is saved below the 128-byte red zone. A 64-bit stack-relative displacement that exceeds signed disp32 after reserving the spill frame throws; other BMI instructions are not lowered. The M1 test covers register overlap, RSP operands, memory addressing, RIP relocation, flags and red-zone preservation; precise fault delivery inside a spill sequence has not been verified. diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index d76932d773..e7aba24b21 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -2,6 +2,8 @@ ### Build +- [GuestContext memory](../../../core/shader/recompiler/Recompiler.hpp) rejects direct construction from temporary owning ranges. The view does not own the region array or its referenced bytes. That storage must outlive the request and its consumers, including when supplied through named containers, existing spans or pointers. + - winpthreads initializes a statically initialized rwlock on its first lock and fails a concurrent first lock with `EINVAL`, which `std::shared_mutex` and `std::shared_timed_mutex` ignore, so that lock does not exclude. The host `std::shared_mutex` objects in [Recompiler.cpp](../../../core/shader/recompiler/Recompiler.cpp) are locked once when created. A new host `std::shared_mutex` used on Windows needs the same. - The bundled [SPIRV-Tools build](../../../core/shader/recompiler/PatchSpirvTools.cmake) replaces the redundancy-elimination source with a build-directory copy. A depth-first traversal keeps one value map and removes each subtree's added values on return, avoiding growing map copies and preventing sibling values from leaking into one another. This extends the processed-map release fix in [SPIRV-Tools #6962](https://github.com/KhronosGroup/SPIRV-Tools/pull/6962). Remove the patch when the pinned dependency has equivalent copy-free traversal; #6962 alone still copies maps for sibling branches. System-dependency builds need an equivalent fix in their installed package. Optimization passes and validation remain unchanged; wider shader and game performance measurement is needed. @@ -12,25 +14,32 @@ - [MSAA-address image loads on 2D textures](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) - 32-bit `image_load` with DIM 2D_MSAA reads a non-multisampled 2D T# as the same DIM 2D load, ignoring the sample index. Measured on RX 6650 XT (gfx1032), wave32 and wave64, for R32_UINT, R32_FLOAT, RGBA8_UNORM and RGBA8_SRGB, sample indices 0/1/2/7/31/0xffffffff and in-range/out-of-range coordinates; IEEE 0, DX10 clamp 1, denorm32 0, denorm16 3, round32/16 0, fp16 overflow 0. Pinned in ImageMsaaAddressOnPlane.cpp. Not measured on gfx1013. Stores, atomics, mip-bearing or A16 addresses, packed accesses, converted formats, depth-bit views, array-MSAA addresses and image-table accesses are not enabled by this compatibility mode. - [MUBUF/MTBUF descriptors ending in VCC](../../../core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp): the four dword descriptor at s104 is s104, s105, vcc_lo and vcc_hi, as LLVM's [VCCTuple/SReg_128](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/SIRegisterInfo.td#L422) and [buffer resource operands](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/BUFInstructions.td) describe. Existing translation maps scalar codes106/107 to the raw VCC words. Recompiled loads/stores, both words, bounds, formats and wave32/wave64 execute on NVIDIA and AMD Vulkan; raw gfx10/gfx1013 hardware is not measured. Higher descriptor bases remain refused; the normal SGPR bound is unchanged. -- [Packed pixel ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295. The sample index is the SampleId builtin in a pixel shader the guest iterates per sample (`PS_ITER_SAMPLE` on a multisampled draw), an equality inferred, not measured. In any other pixel shader it is 0, the iterated sample number of a pixel the hardware does not iterate (also inferred), so reading it does not make the host shade per sample. The render target index is the Layer builtin when the draw exports it (a layered draw, `PA_CL_VS_OUT_CNTL` bit 18; the pipeline specialization constant `LayerExported`) and 0 otherwise, the first layer Vulkan renders when no pre-rasterization stage writes Layer. Reading the unwritten Layer builtin instead makes AMD's Windows driver drop the fragment's writes (#4800); with the constant at 0 the load is folded away. Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on ([ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c#L342-L368)). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant ([RasterPrimitiveOf](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp)) in the shaders that read it, plus the sample field, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding ([LowerGlCompatibility.cpp](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/llpc/lowering/LowerGlCompatibility.cpp#L830-L836)) and without a GFX-level check ([getPrimType](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/LowerInOut.cpp#L5307-L5315)). Nobody has checked it on gfx1013. In the whole word only bits 0-1 and 8-11 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId only in iterated shaders. The whole word has the same 0 at bits 16-28. A change that makes draws export a layer must restore the Layer read in both places +- [Packed pixel ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295. The sample index is the SampleId builtin in a pixel shader the guest iterates per sample (`PS_ITER_SAMPLE` on a multisampled draw), an equality inferred, not measured. In any other pixel shader it is 0, the iterated sample number of a pixel the hardware does not iterate (also inferred), so reading it does not make the host shade per sample. The render target index is the Layer builtin when the draw exports it (a layered draw, `PA_CL_VS_OUT_CNTL` bit 18; the pipeline specialization constant `LayerExported`) and 0 otherwise, the first layer Vulkan renders when no pre-rasterization stage writes Layer. Reading the unwritten Layer builtin instead makes AMD's Windows driver drop the fragment's writes (#4800); with the constant at 0 the load is folded away. Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on ([ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c#L342-L368)). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant ([RasterPrimitiveOf](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp)) in the shaders that read it, plus the two fields, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding ([LowerGlCompatibility.cpp](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/llpc/lowering/LowerGlCompatibility.cpp#L830-L836)) and without a GFX-level check ([getPrimType](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/LowerInOut.cpp#L5307-L5315)). Nobody has checked it on gfx1013. In the whole word only bits 0-1, 8-11 and 16-28 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId only in iterated shaders. The whole word has the same render target index at bits 16-28 - [Front-face input with `FRONT_FACE_ALL_BITS`](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (shader recompiler) - with `SPI_BARYC_CNTL.FRONT_FACE_ALL_BITS` set, a back face reads 0 and a front face 0xffffffff. Mesa 22.0 RADV sets the bit ([radv_pipeline.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/vulkan/radv_pipeline.c#L5953)) and ACO tests the input with `v_cmp_lg_u32 0` ([aco_instruction_selection.cpp](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/compiler/aco_instruction_selection.cpp#L8123-L8128)), so only back = 0 and front ≠ 0 are sourced; PPSA05684's two-sided cloth shaders test it with `v_cmp_lt_u32 0`. The exact front value is not measured. Without the bit the input is ±1.0, as current Mesa reads it (`fgt 0`, `fsign`; [ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c)) -- [Color comparison descriptor forms](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp): emulated color comparison supports UNORM, SNORM and float formats for 2D, 2D-array and cube views. 8-bit sRGB and UNORM (`Format8Srgb`, `Format8_8Srgb`, `Format8_8_8_8Srgb`, `Format8UNorm`, `Format8_8UNorm`, `Format8_8_8_8UNorm`) return 0 for every compare function, measured with `image_sample_c_lz` on gfx1035 over references -0.5..1.5 and texel codes 0..255. Anisotropic filters are refused, except on those 8-bit formats, where every texel comparison is 0 (not measured with an anisotropic S#). Bilinear cube comparisons of float, snorm and the other unorm formats without `DISABLE_CUBE_WRAP` are refused; on that hardware a 4x4 R32 float cube blends across faces (46 of 90 edge rows differ from a per-face clamp; face 0, ref 0.1, u=3.9 v=2.5 is 0.6016 and u=1.5 v=0.1 is 0.3984), and with `DISABLE_CUBE_WRAP` the per-face clamp matches. An 8-bit sRGB or UNORM bilinear cube without that bit returns 0, since every texel comparison is already 0 for every compare function and the selected face cannot change the result. Cube-edge sampling behavior and console coverage on PS5's gfx1013 remain unverified. +- [Color comparison descriptor forms](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp): for formats AnyPS5 can sample as floating-point from GFX10 codes 1–77, `image_sample_c*` returns 0 for every comparison function except codes 7 (`16_UNORM`) and 22 (`32_FLOAT`), which use the native comparison path. The split was measured with point sampling on RDNA2 (gfx1031 and gfx1036) and with point and bilinear sampling on RDNA3 (gfx1100); the PS5's gfx1013 and 2D-array/cube comparisons across these formats remain unverified. Codes 128–130 (8-bit sRGB) also return 0, measured on gfx1035 over references -0.5..1.5 and texel codes 0..255. Scaled, integer and formats requiring converted sampling remain unsupported, although the tested hardware formats also return 0 for them. Anisotropic comparison of the remaining formats has not been measured. - [s_setkill](../../../core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp) (core/shader/recompiler) - decoded as `s_endpgm` for any immediate: measured on RDNA2 hardware (gfx1035), a store before it is visible and nothing after it runs ([#771](https://github.com/boykopovar/AnyPS5/pull/771)); whether the KILL status bit it sets is observable differently from `s_endpgm` is unverified - [s_sethalt](../../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; measured on a gfx1035 (RDNA 2) wave: `s_sethalt 0` followed by a VALU op completes and gives the normal result. Only SIMM16[0] is read; with it set the instruction throws -- [s_subvector_loop_begin and s_subvector_loop_end](../../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (core/shader/recompiler) - follow AMD's RDNA 2 instruction set reference and a gfx1036 (wave64) measurement, pinned in [SubvectorLoop.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/SubvectorLoop.cpp); `S0` in the end instruction's `elif(S0 == 0)` is read as its SGPR operand. A wave32 shader and a destination other than an SGPR throw because neither is measured. Measured on PC RDNA2 (Radeon 610M), not on a PS5. The [specialization pass](../../../core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp) keeps a constant-condition branch whose untaken target is a loop header, since folding it removes the loop's back edge, which SPIR-V requires exactly once per loop header (SPIRV-Tools rejects the module and the NVIDIA driver 615.78.08 crashes compiling it); a loop whose back-edge block becomes unreachable in another way is not handled +- [s_subvector_loop_begin and s_subvector_loop_end](../../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (core/shader/recompiler) - follow AMD's RDNA 2 instruction set reference and a gfx1036 (wave64) measurement, pinned in [SubvectorLoop.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/SubvectorLoop.cpp); `S0` in the end instruction's `elif(S0 == 0)` is read as its SGPR operand. A wave32 shader and a destination other than an SGPR throw because neither is measured. The begin instruction tests for an empty mask as `(exec_lo | exec_hi) == 0`: AMD's Windows driver (32.0.21045.5002 and 32.0.21042.62) compiled `exec_lo == 0 && exec_hi == 0` as a test of the whole 64-bit mask when guest loads use selects, as they do with `robustBufferAccess2` (#7376, #7623). Measured on PC RDNA2 (Radeon 610M), not on a PS5. The [specialization pass](../../../core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp) keeps a constant-condition branch whose untaken target is a loop header, since folding it removes the loop's back edge, which SPIR-V requires exactly once per loop header (SPIRV-Tools rejects the module and the NVIDIA driver 615.78.08 crashes compiling it); a loop whose back-edge block becomes unreachable in another way is not handled - [Vertex entry merged wave info](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (recompiler) - `s3` of every vertex-path shader is set to `waveSize | waveSize << 8` (vertex and primitive count of a full wave, wave index 0), or to `32 | 32 << 8` for a wave64 program on a 32-lane host subgroup, which runs it at one lane per invocation and holds no lanes 32-63. On a device whose `subgroupSupportedStages` lacks the vertex stage (lavapipe, MoltenVK), vertex and local programs run as one-lane waves: `s3` is `1 | 1 << 8`, the lane id is 0, a ballot is the invocation's own bit, a shuffle or broadcast returns the invocation's value, an elect is true and a subgroup reduction is the value itself, so no `GroupNonUniform` capability is declared. Layout taken from the prologue Mesa's ACO emits for NGG vertex shaders (`s_bfe_u64 exec` from `s3`), not measured on hardware. The driver gives every graphics program user data from `s8`, so the condition does not tell NGG from legacy vertex shaders: they just never read `s3`. A draw whose last wave is partial still reports a full wave, and so does a host wave with idle invocations (an RTX 5070 ran every vertex warp of a non-indexed triangle list with 30 of its 32 invocations). - [s_barrier in vertex-path programs](../../../core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp) (recompiler) - emits no instruction when the program has no LDS or GDS access and no memory write or atomic, so no wave of the subgroup can observe another, or when the barrier runs before every memory access of the program (it is on the entry's straight-line path with no load, store, atomic, image or LDS access ahead of it), so it orders nothing; the `GS_ALLOC_REQ` such a barrier orders on NGG passthrough programs is already dropped on the vertex path. The Vertex execution model has no workgroup barrier. Other programs with LDS, GDS or memory writes throw. Not measured on hardware -- [Vertex clip and cull distances](../../../core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp) (libSceAgcDriver) - `PA_CL_VS_OUT_CNTL` enable bit `s` of `CLIP_DIST_ENA`/`CULL_DIST_ENA` is taken to select component `s` of the `pos1`/`pos2` exports, with the Vulkan array index counted by the enabled bits below it, and `VS_OUT_CCDIST0_VEC_ENA` as bit 22; this follows the recompiler's existing position-export mapping, not hardware or documentation. The guest clip distances share one `gl_ClipDistance` array with the all-zero clip position distance, which takes the index after them. A vertex program whose own code has no `pos1`..`pos3` export is compiled and prepared without these bits (`PositionExportLayout`), so registration and draws agree on its artifact; an export in a subroutine it calls is not seen by that scan +- [Vertex clip and cull distances](../../../core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp) (libSceAgcDriver) - `PA_CL_VS_OUT_CNTL` enable bit `s` of `CLIP_DIST_ENA`/`CULL_DIST_ENA` is taken to select component `s` of the `pos1`/`pos2` exports, with the Vulkan array index counted by the enabled bits below it, and `VS_OUT_CCDIST0_VEC_ENA` as bit 22; this follows the recompiler's existing position-export mapping, not hardware or documentation. The guest clip distances share one `gl_ClipDistance` array with the all-zero clip position distance, which takes the index after them. A vertex program whose own code has no `pos1`..`pos3` export is compiled and prepared without these bits (`PositionExportLayout`), so registration and draws agree on its artifact; an export in a subroutine it calls is not seen by that scan. A device without `shaderCullDistance` (MoltenVK) throws on a draw that enables a cull distance; none is emulated. - [32_AR color exports](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - with `SPI_SHADER_COL_FORMAT` 3 the red value is export channel 0 and the alpha is export channel 1, the GFX10 layout of Mesa's [`emit_ps_color_export`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/3e32b0e1f132231b7a972c1b0b5b06c7e16cf8b9/src/amd/common/nir/ac_nir_lower_ps_late.c#L400-L411), ACO and radeonsi in Mesa 22.0 ([aco_instruction_selection.cpp](https://gitlab.freedesktop.org/mesa/mesa/-/blob/a82920af6efa4a7a66919885c6477fbd8fdc7b9d/src/amd/compiler/aco_instruction_selection.cpp#L11087-L11096), [si_shader_llvm_ps.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/a82920af6efa4a7a66919885c6477fbd8fdc7b9d/src/gallium/drivers/radeonsi/si_shader_llvm_ps.c#L323-L333)) and AMD's [LLPC](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/FragmentColorExport.cpp#L157-L168); before GFX10 the alpha was channel 3. Channels 2 and 3, which none of these compilers enables, are ignored; green and blue are 0 and the alpha of an export without channel 1 is 1.0, the defaults of the other formats, which no source gives. Not measured on hardware, neither on gfx1013 nor on a PC RDNA 2 GPU; pinned in [Pixel32ArExport.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/Pixel32ArExport.cpp) +- [GCN instructions](../../../core/shader/recompiler/RdnaDecoder/src/GcnInstructionDecoder.cpp) (shader, PS4) - PS4 (Sea Islands) instructions are re-encoded to their RDNA encodings with opcode tables built from shadPS4's `opcodes.h` and decoded by the RDNA decoder; an encoding without an RDNA counterpart throws. MIMG has no DIM field on GCN: the dimension is taken as 2D, or 2D array when DA is set, and the [materializer](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) rejects a T# whose type needs another address count. MIMG word 1 bits [31:26] are reserved on GCN (A16 and D16 on RDNA). +- [GCN buffer and image descriptors](../../../core/shader/recompiler/RdnaDecoder/src/RdnaDescriptorFormat.cpp) (shader, PS4) - a GCN V# word 3 (DATA_FORMAT [18:15], NUM_FORMAT [14:12], names read from the most significant field) is rewritten to the unified format of the PS5 layout with OOB_SELECT 1 for a strided buffer and 3 otherwise, following the GFX6-7 NUM_RECORDS rules in Mesa's `ac_descriptors`; ATC, HASH, HEAP and MTYPE are dropped. The V# base is 44 bits, as in shadPS4's `AmdGpu::Buffer`: Limbo leaves 0x7 in word 1 bits [15:12]. A GCN T# is rewritten to the PS5 layout with SW_MODE linear, keeping TILING_INDEX [4:0], POW2_PAD [5] and PITCH - 1 [19:6] in word 7. None of it is checked on a console. +- [PS4 legacy vertex shaders](../../../core/shader/recompiler/Recompiler.cpp) (shader, PS4) - a PS4 vertex program at the VS slot takes its user data from s0, its vertex index in v0 and its instance index in v1-v3, and a fetch shader it calls through the user pair (`s_swappc_b64 s[0:1], s[0:1]`) returns through the same pair. These rules apply only while GCN shaders are decoded. +- [4_4_4_4 partial writes, refused draws](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp) (shader recompiler) - a merge throws when its shader exports depth, stencil or a sample mask, uses alpha-to-coverage or a quad sample mask, kills pixels under late tests in a draw that writes depth or stencil (a depth write, or a stencil test with a nonzero write mask and a non-keep operation), writes guest memory (stores, atomics, GDS writes) under late tests in a draw with a depth, stencil or depth-bounds test, as the forced early tests would drop the writes of rejected pixels, or kills pixels under late tests after the game has read an occlusion counter, as the early tests would count them. On an RTX 4070 SUPER (driver 610.88) a fragment killed after early tests still wrote depth, and `FragDepth` was ignored with `EarlyFragmentTests`, so the depth write cannot move after the shader there. ### Functional +- [Pixel interpolators past `SPI_PS_IN_CONTROL.NUM_INTERP`](../../../core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp) (shader recompiler) - when the IR reads an attribute at or past the guest interpolator count, the missing `SPI_PS_INPUT_CNTL` slots are filled as DEFAULT (`OFFSET` 0x20, constant 0/1) before info collection, and SPIR-V emission of `GetInterpolationParameter` for a missing binding returns the same defaults instead of throwing, matching Mesa's treatment of a PS input with no VS output. Component and mode bounds still throw. A retail title prepares a pixel shader with `NUM_INTERP` 0 while it still issues `v_interp` on attribute 0 (`undefined attribute` after the collector fill alone). Console behaviour for that pairing is not measured. - [Windows cache housekeeping](../../../core/shader/recompiler/ShaderDiskCache.cpp): eviction of old versions and temporary files is skipped for `\\?\` and `\\.\` cache roots because the supported WinLibs directory iterator can enumerate the process working directory for long prefixed paths. The active version's `last-used` stamp update still runs; it fails for long `\\.\` aliases with the supported toolchain. Ordinary Windows directory enumeration remains subject to MAX_PATH. - [f64 compares](../../../core/shader/recompiler/Translation/src/CompareInstructions.cpp) flush denormal inputs to signed zero when bit 0 of the `FLOAT_MODE` f16/f64 denormal field is clear (modes 0 and 2); modes 1 and 3 and an unknown mode keep them. This follows the RDNA2 ISA section 6.4 and LLVM's sign-preserving AMDGPU denormal modes. The gfx1036 audit in [#981](https://github.com/boykopovar/AnyPS5/issues/981#issuecomment-6070046381) reports mode-sensitive `VopcCompareF64` results. Class compares still inspect the original bits. The full four-mode matrix has not been newly measured on AMD hardware or the PS5's gfx1013. - +- [Shader disk cache source version](../../../core/shader/recompiler/ShaderCacheVersion.cmake) - the version that names `shader_cache/` and stamps `pipelines.journal` hashes every file under `core/shader/recompiler` (line endings normalized), `HostThreadLocal.hpp`, the SPIR-V and glslang submodule commits, and the compiler, build type and flags. Any edit there changes it and empties the cache, including edits that cannot change a translation (logging, the disk cache's own I/O, the recompiler's tests), and so does a rebuild with another compiler or flags. Driver sources outside the recompiler are not hashed: their recompiler calls are only checked against `shaderCacheReviewedNames`, so a driver change that alters a `RecompileRequest` field's meaning without changing its value keeps a stale cache. - [Unlinked calls](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) - the `s_swappc_b64` a table call's compare chain falls back to, when its target matches no linked copy, records the `UnlinkedCall` fault with the target and the program counter and stops the invocation, or continues after the call in programs whose faults do not stop invocations (see `bdaStopsInvocations`). The driver lists those calls in the request (`linkedCallFallbacks`), and it reports the fault like a BDA fault, naming the target: a dispatch that checks its faults when it completes throws, one whose check runs in a deferred write-back logs it and continues. The hardware would jump to the target, which is not modelled. Any other `s_swappc_b64` whose target is not resolved still throws when the shader is compiled. - [Cached source IR](../../../core/shader/recompiler/Recompiler.cpp) releases lazily allocated SSA construction tables after resource preparation and reclaims values unreachable from instructions, metadata and resource plans when retaining a pending source program. Immediate shader preparation skips reclamation because compilation consumes the program. Reclamation must account for every new IR pointer owner; a remaining external use throws. Live IDs are retained. Source and variant entries remain cached without an eviction bound. A pending source program is released after its first variant is obtained; memory beyond the measured title checkpoint and Linux performance remain unmeasured. + +- Cycles through non-phi instructions are not removed by [unobserved phi elimination](../../../core/shader/recompiler/Optimization/src/UnobservedPhiEliminator.cpp). The pass removes phi-only cycles before resource planning when no transitive user is a non-phi instruction. - [Vertex user-data tail jumps](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) link a linear raw vertex prolog's terminal `s_setpc_b64` through an unchanged user-data pair to its continuation. The instruction semantics follow AMD RDNA2 ISA section 12.3; the prolog convention follows Mesa `aco_select_vs_prolog.cpp` at `0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8`. Console hardware has not been measured. Branching prologs, computed low pointers and continuations reading `s_getpc_b64` remain unsupported - [Raw fragment user-data tails](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) also link a terminal `s_setpc_b64` when the low half is unchanged user data and the last high-half writer is an unconditional immediate `s_mov_b32`. Other computed high halves remain unsupported. This follows AMD RDNA2 ISA section 12.3 and Mesa `aco_select_nir.cpp` (`create_fs_jump_to_epilog`), `aco_isel_helpers.cpp` (`convert_pointer_to_64_bit`) and `aco_lower_to_hw_instr.cpp` (`p_jump_to_epilog`) at `0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8`; console hardware has not been measured @@ -41,10 +50,11 @@ - An [image table](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) snapshot holds at most 65536 keys per table, 65536 distinct images and 256 S# entries per shader; a key past a longer table's snapshot, or an entry over the budget, faults when selected. A selected column (a T# the SRT walker evaluates) holds its one record wherever it lies. - [Image tables](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) read by `s_load` through a 64-bit pointer hold the keys the key's known bits allow, at most 65536; a key past them, or an entry at a wrapping, unaligned or unmapped address, faults when selected. - An [image table](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) whose key is neither a V#-relative scalar read nor a value the SRT walker evaluates (for example a shifted or masked runtime-offset V# read, a per-lane value made uniform by `v_readfirstlane`, or a loop-carried phi) binds every record of the snapshot domain instead of the selected ones. So does a table read at `(key << n) & mask` with such a key: a key domain would select records by the unmasked key, so that key is not narrowed. A key the walker evaluates selects its one record instead, unless the shader may write the memory the key is read from at run time (the same buffer, or any memory when the read or a write goes through a pointer or a GPU-only descriptor); then the column binds the whole domain too. A write to the key's memory through another V# over the same bytes is not detected. A shader whose selected records would pass 64 images or 64 sampled image/sampler pairs binds, for the heaps with the most such reads, the union of their keys in one table instead. -- [Image table pointer indexing](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) propagates 32-bit value bounds through `ReadFirstLane` and `SelectU32` so waterfall key masks keep their bit domain. The SRT walker validates runtime select conditions whose active lane mask implies the condition through `LogicalAnd` conjunctions, assuming unselected inactive lanes need not be uniform across the full wave. +- [Image table pointer indexing](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) propagates 32-bit value bounds through `ReadFirstLane` and `SelectU32` so waterfall key masks keep their bit domain. The SRT walker validates runtime select conditions whose active lane mask implies the condition through `LogicalAnd` conjunctions, assuming unselected inactive lanes need not be uniform across the full wave. The same assumption lets the two `s_load`s of one T# record match when one offset is added before `v_readfirstlane` and the other after (`ReadFirstLane(Select(mask, (key << 5) + 16, old), mask)` against `ReadFirstLane(key, mask) << 5`): with no active lane the hardware reads lane 0 of the inactive arm, and the table still binds the whole record. - An [image table](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) T# of an MSAA or FMASK image, or of a color format under a depth compare, faults when selected: tables have no multisampled descriptors and no emulated color compare. -- Direct [FMASK image loads](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) read uncompressed `Fmask32_S8_F8` through a single-mip 2D or 2D-array `R32_UINT` snapshot with `Z_64KB_X` addressing. Destination selectors may name X, zero or one. Other FMASK formats, CMASK compression, packed accesses and other layouts throw. The layout follows PAL's [FMASK-to-surface conversion](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/core/addrlib2.cpp#L877) and [32-bit view selectors](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9Device.cpp#L4973). Native RDNA/PS5 image-load results and title rendering are unmeasured; compressed color sampling remains unsupported. -- `image_store`, image atomics, PCK2/PCK4 and MIMG BY2/BY4 through an [image table](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) T# fault (`unsupported operation`) when an active lane executes them; the store is dropped and the load reads zeros. A T# whose words the SRT walker evaluates is bound directly instead, for every operation of the shader on it (`DirectImageHandles`), so only T#s keyed per lane or by a value the walker cannot evaluate reach a table with these operations; such a direct T# is read when the work is recorded, so a key its own dispatch writes is read before that write. +- Direct [FMASK image loads](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) of 16 samples, CMASK compression (bar the 8-fragment native owner path), packed accesses and other layouts throw. Uncompressed FMASK of up to 8 samples reads a single-mip 2D or 2D-array 8-, 16- or 32-bit `UINT` snapshot with `Z_64KB_X` addressing; selectors may name X, zero or one. `Fmask32_S8_F8` returns the stored word; others expand to 4 bits per sample, an index at or past the fragment count reading 8 (unknown), samples past the count 0. Layouts follow PAL's [expanded values](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9MaskRam.cpp#L5005) and [FMASK surfaces](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/core/addrlib2.cpp#L877). Console results, unused nibbles included, are unmeasured; compressed color sampling is unsupported. +- A [DIM 2D read of a 2D MSAA T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) returns sample 0, which is not measured on the console's gfx1013, at an implicit LOD above 0, at an explicit one for texels over 16 bits except formats 22 and 56 in SW_64KB_R_X, or for other formats and swizzle modes; 64- and 128-bit SW_64KB_Z_X texels are not covered: gfx1031 does not address their samples. Such a read is an `image_load` without a mip, `image_sample`, `image_sample_lz` or `image_sample_l` of a direct sampled image; other accesses (gathers, bias, gradients, offsets, LOD clamps, depth compares, `image_load_mip`, `image_get_resinfo`, `image_get_lod`, PCK, BY2/BY4) throw, as does a depth-bits T# (16_UINT, 32_UINT or 32_SINT in a Z swizzle mode). On gfx1031 (RX 6700 XT, HIP), such reads return sample 0 at 2x, 4x and 8x for 8- to 128-bit texels in SW_64KB_R_X and 8- to 32-bit ones in SW_64KB_Z_X (formats 1, 5, 7, 11, 13, 14, 18, 22, 29, 56, 60, 64, 71, 77, 133). +- `image_store_mip`, `image_store_pck`, PCK2/PCK4 and MIMG BY2/BY4 through an [image table](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) T# fault (`unsupported operation`) when an active lane executes them; the store is dropped and the load reads zeros. 32-bit and 64-bit image atomics through a table lower to `OpAtomic*` on the table `AtomicView`/`Atomic64View` (`Device` scope, relaxed, `AcquireRelease`/`UniformMemory` barrier as the direct path) instead of faulting; the host needs `VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT` for the view format. Packed formats and BY element counts still fault. - An [image table](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) S# with a min or max reduction or FORCE_UNNORMALIZED faults when selected, as does an integer, converted or depth-bits table T# paired with a direct min or max S# that filters between texels. - Uint and Sint [image table](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvDescriptors.cpp) entries and direct T#s under a table S# sample through a point-filtered copy of the S#: Vulkan leaves linear filtering of integer formats undefined; the console is assumed to point-sample. - Comparison sampling of a color texture through an [image table](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) S# throws at the first dispatch or draw that binds it. @@ -57,16 +67,18 @@ - [MIMG `tfe` and `lwe`](../../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, the LOD warning (texture-streaming feedback, like the T# `MIN_LOD_WARN` itself) is never reported, and `lwe` on `image_get_lod` throws. - [Packed horizontal gathers](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_gather4h_pck`, MIMG opcode 98): the four window texels are read raw and concatenated into a little-endian bitstream, with DMASK bit i writing stream word i to the next destination register; measured on RDNA2 (gfx1031) at every edge: the anchor is floor(x-0.5) with elements at -1, 0, +1 and +2 clamped horizontally into the width, the row is floor(y), negative rows clamp to row 0, rows at or past the height return zeros, sampler addressing modes do not affect the window, and formats wider than 32 bits per texel write zeros in every register. The emitter fetches each window texel at LOD 0 and packs its bits like `image_load_pck`, so the sampler's filter, addressing and LOD state are not used; formats whose bits are not recoverable from the view and D16 data throw. The anchor and row multiply the normalized coordinate by the image size without contraction. The stream rule was measured for R8_UNORM, RG8_UNORM and RGBA8; only RGBA8 has an execution test. `image_gather8h_pck` (opcode 99) never returns texel data on this hardware - across dmasks 0x1 to 0xFF including the upper nibble, RGBA8, RGBA16 and RGBA32 formats, 1D, 2D and 3D descriptors and normalized and unnormalized sampling it only zero-fills popcount(dmask) registers (sentinel-prefilled destinations) - so it decodes but throws at translation; its eight-texel semantics remain unmeasured. - [MIMG BY2/BY4 loads](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_load[_mip]_by2/by4`, opcodes 0x42, 0x43, 0x4a and 0x4b) were measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md) for 2D images with identity swizzle. They read N texels (BY2: 2, BY4: 4) from `x & ~(N - 1)` on row y, texel by texel and component by component within each texel, with each texel converted like `image_load`. Every register is zero when the mip level is past the last level, `x + N - 1` is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when the N texels fit in one dword: R8 and R16 (DMASK 0x3) or RG8 (0xf) for BY2, and R8 (0xf) for BY4. With 32-bit texels (R32, RG32, RG16, RGBA8) the hardware returns zeros. Other formats, masks, dimensions, swizzles, D16/A16 and indirect descriptors throw. The stores (0x52, 0x53, 0x5a and 0x5b) decode but throw at translation. Measured on hardware, they write from the unaligned x up to the end of its dword, which is not implemented. -- [2D stores to 1D storage images](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp): an `image_store` with DIM 2D through a 1D T# writes texel x and ignores y, as measured on RDNA2 (gfx1035) for y from 0 to 4 and 0xffffffff, single- and four-component formats and a 1x1 8_8_8_8_UNORM T#: the T# type decides the addressing, DIM only how many address VGPRs are read. Storage loads, atomics and `image_store_mip` through such a pair still throw +- [2D stores to 1D storage images](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp): an `image_store` with DIM 2D through a 1D T# writes texel x and ignores y, as measured on RDNA2 (gfx1035) for y from 0 to 4 and 0xffffffff, single- and four-component formats and a 1x1 8_8_8_8_UNORM T#: the T# type decides the addressing, DIM only how many address VGPRs are read. An `image_store` with DIM 2D_ARRAY through a 1D T# writes texel x, ignoring y, only when the slice is 0, and is dropped for any other slice: measured on RDNA2 (gfx1036) with the [hardware oracle](../HW_ORACLE.md) through the AMD Windows driver's comgr and HIP runtime (wave32, IEEE off, DX10 clamp on, denormals preserved, round to nearest even) on a 64-texel linear 32_UINT T# with DEPTH 0 and 3, for x of 0, 5 and 63, y of 0, 1, 3 and 0xffffffff and slices 0, 1, 2, 5 and 0xffffffff; the PS5's gfx1013 is not measured. Storage loads, atomics and `image_store_mip` through such a pair still throw - [MIMG PCK2/PCK4 loads](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_load[_mip]_pck2/pck4`, opcodes 0x70, 0x71, 0x73 and 0x74) were measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md) for 2D images with identity swizzle. With data mask 0x1, they write one dword: the raw bits of the N texels (PCK2: 2, PCK4: 4) from `x & ~(N - 1)` on row y, concatenated (element k at bit `k * texel bits`) and zero-extended. The dword is zero when the mip level is past the last level, `x + N - 1` is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when N texels fit in 32 bits: R8, R16 or RG8 for PCK2, R8 for PCK4. Wider texels (R32, RG16, RGBA8) and masks other than 0x1 write zeros on hardware. Those masks throw at decode, and formats outside UNORM/UINT/SINT R8, R16 and RG8 throw. #1314's earlier measurement saw only zeros because it used masks 0x3 and 0xf. The stores (0x76, 0x77, 0x79 and 0x7a) decode but throw at translation. They are not measured, and opcode 0x76 on 32_32_32_32_FLOAT hung the GPU (#1314). +- [`image_get_resinfo` through DIM 1D on a cube, 2D-array or 3D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) follows thuanquang's measurement on RDNA2 (RX 6700 XT, gfx1031, #7550), not the PS5's gfx1013: width, height and the level count come from the T#, and the third component is the 3D T#'s depth, or 0 for a cube or 2D array, as for every DIM without layers (1D, 2D, 2D MSAA). Such a query on a 1D-array T# still reads it as its first layer, whose height is not measured. On a 2D MSAA or 2D MSAA-array T# it throws: gfx1031 was measured only with one fragment, which the driver cannot bind, and the level component for 2, 4 or 8 fragments is not measured. - [3D image accesses bound to a 2D or 2D-array T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) follow CouchRiv's [hardware oracle](../HW_ORACLE.md) measurement on RDNA2 (gfx1036, #2023) and adriadam10's (gfx1035, #2140), not the PS5's gfx1013. On a 2D T#, `image_sample_lz` ignores r and reads the (u,v) texel, `image_load` and `image_load_mip` read the (x,y) texel when z is 0 and return zero otherwise, and `image_get_resinfo` returns (width, height, 1, levels). On a 2D-array T#, these accesses behave like dim 2D-array: r or z is the layer, and `image_get_resinfo` returns the layer count as its third component. Every other access of such an image, including samples with an LOD, bias, derivatives, offset or comparison, gathers, `image_get_lod` and stores, keeps throwing because it is not measured. -- [2D-array samples through a 3D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (`image_sample`, `image_sample_l` and `image_sample_lz` with DIM 2D_ARRAY) sample the volume as DIM 3D does: the third address VGPR is the normalized r coordinate, addressed by the sampler's Z mode (only wrap was measured), not a layer index, and a plain sample in a pixel shader takes its LOD from the derivatives of all three coordinates. Measured on RDNA2 (RX 6700 XT, gfx1031, driver 32.0.21045.5002, Windows 11) with the [hardware oracle](../HW_ORACLE.md) template run through the driver's comgr and HIP runtime, not on the PS5's gfx1013: on 16x16x4 R32_FLOAT 3D T#s with one and three levels, the three instructions with DIM 2D_ARRAY returned the same value as with DIM 3D in all 256 lanes of 66 cases (point, bilinear and z-linear filters, wrap addressing, r from -0.125 to 3, quad steps of 1 to 4 texels in u and v and of 0 to 4 slices in r, LOD 1). Other samples of such a pair (bias, LOD clamp, derivatives, offsets, comparison, 16-bit addresses), gathers, `image_get_lod` and a sample mixed with a load of the same image keep throwing because they are not measured. The host sampler ignores `Z_FILTER`, as for every 3D sample, so r is filtered with the XY filter. +- [2D-array samples through a 3D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (`image_sample`, `image_sample_l` and `image_sample_lz` with DIM 2D_ARRAY) are measured only on RDNA2 (gfx1031, [hardware oracle](../HW_ORACLE.md)), not on the PS5's gfx1013, and only with wrap addressing. They sample the volume as DIM 3D does: the third address VGPR is the normalized r coordinate, addressed by the S#'s `CLAMP_Z`, not a layer index. The host sampler ignores `Z_FILTER`, as for every 3D sample, so r is filtered with the XY filter. Other samples through such a pair (bias, LOD clamp, derivatives, offsets, comparison, `_a`, 16-bit addresses), gathers, `image_get_lod` and `image_get_resinfo` throw, as do the three samples of a T# selected from an image table, of a T# the program also loads with DIM 2D_ARRAY, and of an 11_11_10_UNORM, 11_11_10_UINT or depth-bits T#. +- AMD image descriptors with `TYPE=0` are treated as null for [read-only image slots](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) even when the base and other words are nonzero: `image_load` on a gfx1035 with such a T# returns 0,0,0,0, and the same descriptors were seen in direct reads in PPSA01341 on an RTX 4070. Writable and atomic image slots and entries of an image table keep faulting because there is no hardware measurement for them, and the reserved types 2-7 also fault. `TYPE=1` is treated as null in the same read-only slots: gfx1035 reads it as zeros too, and gfx1010 returns zeros for `image_sample_lz`, `image_sample_b`, `image_load` and `image_get_resinfo` with format 22 (#5828); it is not measured on RDNA2, with other formats or with depth-compare reads (`image_sample_c*`). gfx1013 behavior is unverified - [2D accesses through a cube T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (#4139) read the T#'s `BASE_ARRAY` face as a cube access with face coordinate 0 does, measured on RDNA1 (gfx1010, RX 5700 XT) with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime on 8x8 and 32x32 32_FLOAT cubes (`DEPTH` 5 and 11, `BASE_ARRAY` 0 and 6): `image_sample_lz`, plain `image_sample` (one level, and six levels in 2x2 compute quads) and `image_sample_l` (the third address VGPR is the LOD, integer and fractional) with DIM 2D equal DIM CUBE with face 0 on every row, with point and bilinear filters, wrap, clamp and mirror addressing and `DISABLE_CUBE_WRAP` 0 and 1, and the VGPR after the two coordinates is not read; `image_load` with DIM 2D reads texel (x, y) of the `BASE_ARRAY` face. So the face coordinates are cube coordinates in [1, 2]: the texel is `floor((u - 1) * width)`. They are translated through the cube mode DIM CUBE accesses use, which samples the face of the 2D-array view at (u - 1, v - 1) with the S# addressing. That matches the hardware inside the face, except within half a texel of its edges with a bilinear filter and `DISABLE_CUBE_WRAP` 0, where the hardware blends texels of the neighbouring faces (also measured on RDNA2, gfx1035, 8x8 cubes with `BASE_ARRAY` 0 and 6). Outside it the hardware clamps a coordinate below 1 to the first texel under every filter, wrap mode and `DISABLE_CUBE_WRAP` setting measured, clamps a coordinate of 2 or more to the last texel when `DISABLE_CUBE_WRAP` is 0 (point filter) and applies the S# addressing to it when it is 1; the translation does none of this, as for DIM CUBE. Only `image_sample_lz`, plain `image_sample`, `image_sample_l`, mip-less `image_load`, and mip-less `image_store` to an image the program only stores to take this mode. Such a store writes texel (x, y) of the `BASE_ARRAY` face, measured on RDNA2 (gfx1036, [by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6217609589), not the PS5's gfx1013) with the [hardware oracle](../HW_ORACLE.md) on 8x8 mip-less cube arrays (`DEPTH` 11) of 32_UINT, 32_FLOAT and 16_16_16_16_FLOAT ([the last also by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6224023050), with exactly representable half values) with `BASE_ARRAY` 0 and 6, wave32 and wave64: the 64 stored texels land on that face and every other byte of the 64 KiB backing is unchanged. Other formats, and Crimson Desert's own 256x256 tiled descriptor, are not measured. Other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, atomics, stores to an image the program also reads, stores of formats written through a conversion such as 32_SINT, mip loads and stores) still throw. -- [Unnormalized S#s](../../../core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp) are accepted only when direct and used only by explicit-LOD `image_sample` without comparison, derivatives or `_a`, on a single-level, single-layer 1D or 2D view of mip 0, even when the image is also sampled through a normalized S#. +- [Unnormalized S#s](../../../core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp) are accepted only when direct and used only by `image_sample` without comparison or `_a`, on a single-layer 1D or 2D view that is one level at mip 0 (even when the image is also sampled through a normalized S#), or that only unnormalized S#s sample, each without a mip filter or only by `image_sample_lz` without minimum LOD or bias. - [Unnormalized texel offsets](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) are added to the f32 coordinates: exact on RDNA2 (gfx1035), which applies the offset before the clamp ([measured by adriadam10](https://github.com/boykopovar/AnyPS5/pull/822#issuecomment-6033192725)). - [Cube image gradients](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (shader recompiler) - `image_sample_d` and `image_sample_cd` on a cube pass their gradients to the host sample without the -1.0 that moves the face coordinates from [1, 2] to [0, 1], as Mesa's AMD lowering builds them (`src/amd/common/nir/ac_nir_lower_tex_coords.c`, `prepare_cube_coords`: face-space derivatives with no offset, coordinates offset by 1.5). Measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md): on a 16x16 32_FLOAT cube with five levels, point mip filter, `image_sample_d` reads the level of `round(log2(16 * max(|ddx|, |ddy|)))` for gradients of 2^(level ± 0.3) texels on each of the four components, both signs, mixed components, every face and two coordinates (1032 rows). `image_sample_cd` takes one level per quad of lanes, from the gradients of its first lane; the host sample computes it per lane. - [Image atomics](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) on 32_SINT and 32_FLOAT surfaces act on the raw dword through an R32_UINT storage view. On gfx1100 (RDNA3, [hardware oracle](../HW_ORACLE.md), wave32 and wave64), all 13 integer image atomics, `swap` to `dec`, return the old dword and store the raw-dword result identically on 32_UINT, 32_SINT and 32_FLOAT T#s, over 121 old/data pairs per op, among them 0x80000000, 0xffffffff, 1.0, -1.0 and NaN: `add` adds the bits and `umin` is unsigned on every format. A 16_UINT control differs, so the format is read. gfx1031 (RDNA2) gives the same rows, and its `fmin`, `fmax` and `fcmpswap`, which gfx1100 lacks, also match on the three formats ([measured by thuanquang](https://github.com/boykopovar/AnyPS5/pull/7939#issuecomment-6275099482)). The console's gfx1013 is unmeasured. Other formats throw. -- [`s_memtime`](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) on RADV with a GFX10.3 to GFX11.7 GPU (RDNA 2, RDNA 3, RDNA 3.5) reads the Vulkan device clock instead of the subgroup clock, which [ACO](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/compiler/instruction_selection/aco_select_nir_intrinsics.cpp) reads from the 20-bit `SHADER_CYCLES` register there and which wraps every 2^20 cycles. The device clock is a monotonic 64-bit counter, but it counts the 100 MHz reference clock (the same one as `s_memrealtime`), not shader cycles, so `s_memtime` deltas are about 20 times smaller than on hardware. The families are matched by the name RADV reports ([`NarrowSubgroupClock`](../../../core/libs/prx/libSceAgcDriver/Execution/src/SubgroupClock.cpp)); a family added to Mesa later reads the subgroup clock until it is listed. AMD's proprietary Windows driver also exposes a 20-bit subgroup clock on device 0x1114 (Radeon 860M, measured in #1615), device 0x744c (Radeon RX 7900 XT, measured in #1632), and device 0x747e (Radeon RX 7800 XT, measured in #1632); other AMD proprietary devices remain unverified. +- [`s_memtime`](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) on RADV with a GFX10.3 to GFX11.7 GPU (RDNA 2, RDNA 3, RDNA 3.5) reads the Vulkan device clock instead of the subgroup clock, which [ACO](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/compiler/instruction_selection/aco_select_nir_intrinsics.cpp) reads from the 20-bit `SHADER_CYCLES` register there and which wraps every 2^20 cycles. The device clock is a monotonic 64-bit counter, but it counts the 100 MHz reference clock (the same one as `s_memrealtime`), not shader cycles, so `s_memtime` deltas are 22.35 times smaller than on a PS5 (13.40, measured 2026-10-19 by a gfx1013 kernel run from inside a retail title), where it counts the ~2.23 GHz core clock (44,041,632 against 1,970,792 `s_memrealtime` ticks). The families are matched by the name RADV reports ([`NarrowSubgroupClock`](../../../core/libs/prx/libSceAgcDriver/Execution/src/SubgroupClock.cpp)); a family added to Mesa later reads the subgroup clock until it is listed. AMD's proprietary Windows driver also exposes a 20-bit subgroup clock on device 0x1114 (Radeon 860M, measured in #1615), device 0x744c (Radeon RX 7900 XT, measured in #1632), and device 0x747e (Radeon RX 7800 XT, measured in #1632); other AMD proprietary devices remain unverified. - [`s_memtime` and `s_memrealtime`](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) on llvmpipe read only the low 32 bits of the host's nanosecond clock at either scope: Mesa's [`emit_clock`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/gallium/auxiliary/gallivm/lp_bld_nir_soa.c) builds the high dword with a left shift by 32, so it is always 0 and both counters wrap every 2^32 ns (about 4.3 s). The [shader clock test](../../../core/libs/prx/libSceAgcDriver/tests/execution/ShaderClock.cpp) compares them modulo 2^32 there - [`ds_wrxchg2_rtn_b32`, `ds_wrxchg2st64_rtn_b32`, `ds_wrxchg2_rtn_b64` and `ds_wrxchg2st64_rtn_b64`](../../../core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp) are translated as two separate 32-bit or 64-bit atomic swaps per invocation. RDNA2 hardware (measured on gfx1035) performs every lane's first exchange before any lane's second one, so when lanes of the same wave or of different waves overlap addresses, the result can differ from hardware. `offset0 == offset1` throws, although on hardware it is two exchanges in order. - [`ds_condxchg32_rtn_b64`](../../../core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp) uses two 32-bit atomic conditional exchanges. RDNA2 (gfx1035) measurements confirmed aligned return values, conditional bit clearing and 16-bit offsets; out-of-range accesses return zero without modifying LDS. Misaligned accesses fault the wave on gfx1035; the recompiler does not model this fault. Radeon 780M (gfx1103, RDNA3) measurements confirmed aligned return values, conditional bit clearing, EXEC masking and operand overlap in wave32 and wave64; contended cases passed per-dword conservation checks. Contended lane ordering and gfx1013 remain unmeasured; pair-wide indivisibility is not assumed, and concurrent 64-bit LDS atomics use a separate lock. GDS throws. VGPR pairs extending beyond v255 throw; allocated-VGPR EXEC nullification is not emulated. @@ -74,8 +86,9 @@ - [8_SRGB and 8_8_SRGB loads](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) on a device that cannot sample R8_SRGB or R8G8_SRGB return the sRGB EOTF rounded to nearest even at bf16 precision, as adriadam10 measured on RDNA2 (gfx1035) in #866; the PS5's gfx1013 is not measured. This is the path on a device that cannot blit R8 or RG8 to RGBA8, and with `APS5_SRGB_SHADER_DECODE=1`; otherwise a load of a sampled texture reads the RGBA8 sRGB copy and gets the device's own decode. - [Samples and gathers](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) of 8_SRGB and 8_8_SRGB textures read an RGBA8 sRGB copy of the texture (libSceAgcDriver, [Texture.cpp](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp)) on a device that cannot sample R8_SRGB or R8G8_SRGB but can blit R8 or RG8 to RGBA8, so they use that device's own decode. They still throw on a device that cannot blit, and with `APS5_SRGB_SHADER_DECODE=1`. `APS5_SRGB_SHADER_DECODE=copy` also takes the copy on a device that samples these formats natively, so the copy can be tested on any device that can blit R8 or RG8 to RGBA8; on a device that cannot blit, it behaves like `APS5_SRGB_SHADER_DECODE=1`. A multisampled or depth view of these formats gets no copy. - [Bindless image table](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) entries of 8_SRGB and 8_8_SRGB textures read zeros, like converted formats, on a device that cannot sample R8_SRGB or R8G8_SRGB. -- [Shader MODE register access](../../../core/shader/recompiler/Translation/src/RoundingMode.cpp) starts from the rounding fields of `GuestContext::floatMode` and follows immediate rounding writes (`s_round_mode`, `s_setreg_imm32_b32` on MODE bits 0-3) through the control flow graph; `s_getreg_b32` of those bits returns the tracked value. Only `v_cvt_f32_i32`, `v_cvt_f32_u32`, `v_ldexp_f32`, the f16 conversions and `v_ldexp_f16` (with f16 denormals kept) and the instructions that already read `FLOAT_MODE` follow a rounding mode set at run time; any other float instruction that runs while a directed rounding mode differs from the initial one throws at that instruction. Rounding bits written from an SGPR (`s_setreg_b32`) or that differ between the paths reaching an instruction throw where they are read or used. A shader without `FLOAT_MODE` metadata throws on any rounding change. Non-rounding register reads still throw; `s_setreg_b32` writes to other hardware registers are still ignored. Tests follow the public RDNA2/LLVM contract, not console measurements. +- [Shader MODE register access](../../../core/shader/recompiler/Translation/src/RoundingMode.cpp) starts from the rounding fields of `GuestContext::floatMode` and follows immediate rounding writes (`s_round_mode`, `s_setreg_imm32_b32` on MODE bits 0-3) through the control flow graph; `s_getreg_b32` of those bits returns the tracked value. Only `v_cvt_f32_i32`, `v_cvt_f32_u32`, `v_ldexp_f32`, `v_cvt_pk_u8_f32`, the f16 conversions and `v_ldexp_f16` (with f16 denormals kept) and the instructions that already read `FLOAT_MODE` follow a rounding mode set at run time; any other float instruction that runs while a directed rounding mode differs from the initial one throws at that instruction. Rounding bits written from an SGPR (`s_setreg_b32`) or that differ between the paths reaching an instruction throw where they are read or used. A shader without `FLOAT_MODE` metadata throws on any rounding change. Non-rounding register reads still throw; `s_setreg_b32` writes to other hardware registers are still ignored. Tests follow the public RDNA2/LLVM contract, not console measurements. - [Flat scalar-read reachability](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) keeps both successors active for non-clean predicates and loop headers, so an inaccessible direct root on such a path still refuses capture. Skipping a read under a clean, evaluable scalar branch follows RDNA2 conditional PC semantics and synthetic fixtures; console execution and title behavior are unverified. +- [v_cvt_pk_u8_f32](../../../core/shader/recompiler/Translation/src/ConvertInstructions.cpp): the PS5's gfx1013 is not measured. It rounds its source in the `FLOAT_MODE` f32 round mode (ties to even, toward +infinity, -infinity or zero), clamps it to 0..255 (a NaN gives 0) and flushes a denormal source when f32 denormals are flushed. A kept positive denormal gives 1 toward +infinity, decided on the bits, as a driver without `DenormPreserve 32` may flush it. Measured on RDNA2 (gfx1030, RX 6800, AMD Windows driver 32.0.21045.5002) over 2,133 sources (k/8 for k = -16 to 2,099, values next to 0.5 and 255.5, ±1e10, denormals, infinities, NaNs) at IEEE 0 and 1, f32 denormal modes 0 and 3 and f32 round modes 0 to 3, DX10_CLAMP=1, f16 denormals kept, f16 round to nearest even, FP16_OVFL=0. A round mode set by `s_round_mode` gives the results of the same mode in `FLOAT_MODE`, measured on RDNA2 (gfx1036) over 2,148 such sources in every f32 denormal and round mode. - A [flat SRT read](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) through a null or unmapped pointer loaded from memory raises a [BDA fault](../../../core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp) only if the program executes it, then stops or reads zero like a BDA read. - Repeated `s_load`s of one [flat SRT](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) dword through a null or unmapped pointer report one of their pcs, and an `s_load` whose inaccessible dwords are all unused, or only address repeated loads, reports no fault of its own. - A value read through a null or unmapped pointer still throws at record time if a [uniform fill or table base](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) needs it, or if the pointer comes from user data and is not null, even if the console skips the read. @@ -84,7 +97,7 @@ - [2D accesses through a 1D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) read the texel the 1D addressing selects and ignore the second coordinate, as simonbbby measured on RDNA2 (gfx1036) in #2237 for `image_sample_lz` and mip-less `image_load` with point filtering and wrap, clamp and border addressing; `image_sample_l` was measured the same way on gfx1036 against `image_sample_l` with DIM 1D through the same T#, for u inside, on and past the edges, v of 0, 0.5, 1, -0.25 and 3, LOD -1 to 6 on a 5-level R32_FLOAT texture, point, linear and linear-mip filtering, wrap, clamp and border addressing and HEIGHT or DEPTH 3: all 4725 rows matched. The PS5's gfx1013 is not measured. A plain `image_sample` (implicit LOD) through such a pair takes its LOD from the derivatives of the first coordinate alone, measured on RDNA2 (gfx1036) with the [hardware oracle](../HW_ORACLE.md) on the quads of a compute kernel (wave32 and wave64, IEEE off, DX10 clamp on, denormals preserved, round to nearest even): on a 16-texel, 5-mip 32_UINT T# with point filters it picks the same mip and texel as DIM 1D for first-coordinate steps of 0, 1, 2, 4 and 8 texels per lane along x or along y, whatever the second coordinate does (normalised steps up to 16 per lane), while the same rows on a 16x16 2D T# move the mip with the second coordinate. Outside the pixel stage a plain `image_sample` is emitted with LOD 0 for every image type, so this pair reads mip 0 there, where the kernel measurement shows the quad's derivatives being used. `image_sample_c_lz` and plain `image_sample_c` through such a pair compare against the texel the DIM 1D comparison with the same reference and first coordinate reads, measured on RDNA1 (gfx1010, RX 5700 XT) with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime (wave32 and wave64, IEEE off, DX10 clamp on, denormals preserved, round to nearest even): 8-texel 32_FLOAT and 16_UNORM T#s, also with HEIGHT 3, point and bilinear filters, wrap, clamp and opaque-white border addressing, less and gequal functions, second coordinates 0, 0.5, 1, 1.5, -0.25 and 3; on a 64-texel, 7-mip 32_FLOAT T# `image_sample_c` picks its LOD from the first coordinate's steps alone (1, 4 and 16 texels per lane give mips 0, 2 and 4; second-coordinate steps up to 64 texels leave it unchanged). Only comparisons the runtime does natively (32_FLOAT, 16_UNORM) reach the 1D view; comparisons the runtime emulates for other color formats still throw through such a pair. 2D-array `image_sample_l` and `image_sample_lz` through a 1D T# read as DIM 1D too, ignoring v and the slice: measured on gfx1036 with the hardware oracle (template assembled by the AMD Windows driver's `amd_comgr_2.dll` and launched through its `amdhip64_6.dll`) against the same sample with DIM 1D on a 16-texel 8_8_8_8 UNORM T# with 1 or 5 levels, HEIGHT or DEPTH 3, for u inside, on and past the edges, v of 0, 0.5, 1, -0.25 and 3, slices 0, 1, 2.7, -1 and 5, LOD -1 to 6, and the five filter and address cases above: all 48000 rows matched. A 2D-array `image_load` through a 1D T# is not the same: on gfx1036 it returns the DIM 1D texel only at slice 0, and zeros at slices 1, 2, 5 and -1 even with DEPTH 3, so it, the plain `image_sample` and the other 2D-array accesses still throw. Other 2D accesses through a 1D T# (storage, depth, packed, BY loads, mips other than explicit LOD, explicit LOD with comparison, bias, offsets, derivatives) still throw. - [3D image accesses bound to a 1D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) follow a [hardware oracle](../HW_ORACLE.md) measurement on RDNA1 (RX 5700 XT, gfx1010; wave32 and wave64, IEEE off, DX10 clamp on, denormals preserved, round to nearest even), not the PS5's gfx1013, on 8x1 R8G8B8A8_UNORM linear 1D T#s with HEIGHT and DEPTH 0 or 3 and point filters. `image_sample_lz` reads the texel u selects for v and r in {0, 0.5, 1, 1.5, -0.25, 3}, with wrap, clamp and border addressing, and v and r never produce the border; u wraps, clamps and borders as with DIM 1D. Mip-less `image_load` reads the x texel for any y in {0, 1, 2, 3, -1} when z is 0, returns zero for z in {1, 2, 3, -1}, whatever DEPTH says, and returns zero for x past the width. A plain `image_sample` (implicit LOD) on the quads of a compute kernel takes its texel and LOD from u alone: on a 64-texel, 7-mip 1D T# steps of 1, 4 and 16 texels per lane pick mips 0, 2 and 4, as DIM 1D `image_sample_l` does, and steps in v or r alone stay at mip 0. Outside the pixel stage a plain `image_sample` is emitted with LOD 0, as for 2D accesses through a 1D T#. Every other 3D access of such an image (`image_load` with mips, storage, explicit LOD, bias, derivatives, offsets, comparison, gathers, queries) keeps throwing. SONIC SUPERSTARS (PPSA06888) samples a 1x1 1D T# this way with plain `image_sample` and `image_load`. - [Emulated comparison of a 2D-array access through a 2D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) samples the 2D image and ignores the slice, as the native modes of a 2D-array access through a 2D T# already do. This follows the T# type deciding the addressing; it is not measured on hardware for comparison samples -- [Flat aperture bases](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (core/shader/recompiler) - the layout follows the gfx9+ one in the AMD KFD driver's `kfd_flat_memory.c` (`MAKE_LDS_APP_BASE_V9`, `MAKE_LDS_APP_LIMIT`): each aperture is a 4 GB window whose base is the high dword, so a flat address is routed by its whole high dword, as measured on RDNA2 (gfx1035: `src_shared_base` 0x00010000_00000000, `src_shared_limit` 0x00010000_ffffffff). The PS5's base values, high dwords 0x80000000 (shared, `SharedApertureHigh`) and 0x70000000 (private, `PrivateApertureHigh`), are inferred from PPSA04263's traversal addresses, not measured on a console or read from SH_MEM_BASES. The scalar aperture sources read the same two values +- [Flat aperture bases](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (core/shader/recompiler) - the layout follows the gfx9+ one in the AMD KFD driver's `kfd_flat_memory.c` (`MAKE_LDS_APP_BASE_V9`, `MAKE_LDS_APP_LIMIT`): each aperture is a 4 GB window whose base is the high dword, so a flat address is routed by its whole high dword, as measured on RDNA2 (gfx1035). How the console routes a flat address was not measured. Its bases, high dwords 0x80000000 (shared, `SharedApertureHigh`) and 0x70000000 (private, `PrivateApertureHigh`), first inferred from PPSA04263's traversal addresses, are measured: on a PS5 (13.40, measured 2026-10-19 by a gfx1013 kernel run from inside a retail title) `src_shared_base` / `src_shared_limit` read 0x80000000_00000000 / 0x80000000_FFFFFFFF and `src_private_base` / `src_private_limit` 0x70000000_00000000 / 0x70000000_FFFFFFFF. The scalar aperture sources read the same two values - [Flat accesses outside the aperture routing](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (core/shader/recompiler) - only compute stages route flat accesses, so graphics-stage flat accesses to the shared or private aperture still go to BDA. In compute, a 64-bit flat atomic whose address is in the shared aperture takes the workgroup lock (`ldsLock`) for its two-dword update, and every such atomic requests that lock, so a shader with one pays for it even on global addresses (the path is exercised by `agc_driver_global_atomics_float`). Inferred, not measured on LDS or scratch: an aperture access that is not inside one dword is done byte by byte, an unaligned aperture atomic records the BDA unaligned fault, and LDS or scratch accesses past their end are dropped or read zero. - On a host subgroup narrower than 32 lanes, [`threadBit`](../../../core/shader/recompiler/Translation/src/TranslationContext/ExecMask.cpp) reads an EXEC written as a value (`s_mov_b32`/`s_mov_b64`) at bit (subgroup invocation id) of EXEC_LO, so lanes at or above the subgroup size read a lower lane's bit. - [EXEC read as a value](../../../core/shader/recompiler/Translation/src/TranslationContext/ExecMask.cpp) (`s_mov_b64 sN, exec`, the `s_*_saveexec` copy, ALU sources) is limited to host lanes. Pixel reads retain helper bits in quads with an active fragment, preserving whole-quad masks across copies. Quad expansion follows the [RDNA 2 ISA, section 12.3, S_WQM_B32/B64, page 111](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf#page=119); save/restore followed by implicit-LOD sampling is checked on an RX 6700 XT, not on a console. The console also holds lanes the host lacks, e.g. 32-63 of a wave64 on 32 host lanes. @@ -95,16 +108,16 @@ - [Mesh shader grid size](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - every mesh shader loads `NumWorkgroups.x` and declares no vertices when it is 0, which no dispatch produces. The read is a workaround for RADV (Mesa 26.2.4, seen on an RX 9070 XT, GFX12): binding a mesh pipeline whose shader reads neither `NumWorkgroups` nor `DrawIndex` keeps the user SGPR count and draw-id flag of the vertex pipeline bound before it in the same command buffer (`radv_bind_pre_rast_shader` updates them only when the shader has the `AC_UD_VS_BASE_VERTEX_START_INSTANCE` slot), so `radv_emit_userdata_mesh` opens a 3-register `SET_SH_REG` and writes one value, the CP takes the following `DISPATCH_MESH_DIRECT` as register data and the GPU hangs (gfx ring timeout, MODE1 reset). Kena: Bridge of Spirits (PPSA01802) hits it on its layered volume draws. Reading `NumWorkgroups` gives every mesh shader the grid-size slot, so the count and the values match; drop the read once a fixed Mesa is the minimum - [Mesh layer output](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - a mesh shader writes each primitive's layer through a one-member `Block` struct array whose member is decorated `BuiltIn Layer` and `PerPrimitiveEXT`, as glslang declares `gl_Layer` inside `gl_MeshPerPrimitiveEXT`, not through a standalone `PerPrimitiveEXT` `Layer` array. Both forms pass `spirv-val`, but AMD's Windows driver (32.0.21045.5002, RX 6700 XT) crashes in `amdvlk64.dll` creating a pipeline whose mesh shader writes the standalone form, whether the layer is signed or unsigned and whatever the target, while the block form draws, also with a pixel shader that reads the layer. - [Shader recompilation](../../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../../core/shader/recompiler) was written completely independently from [libs/prx](../../../core/libs/prx). -- [v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16](../../../core/shader/recompiler/Translation/src/AttributeInstructions.cpp) read the 16-bit halves of each vertex's parameter through `VK_KHR_fragment_shader_barycentric`, as fixed-function interpolation cannot split a 32-bit attribute. Without the extension the pixel shader takes the [barycentric emulation](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp), whose generated geometry stage passes each vertex's parameter to it as a flat input, so the halves arrive bit for bit; a device without geometry shaders takes the tessellation stages of the same emulation instead, which no 16-bit interpolation test has run through yet. P10 and P20 are the vertex differences rounded to f16 to nearest even, which is not confirmed on hardware. v_interp_p1ll_f16 and v_interp_p1lv_f16 round the exact `P10 * I + P0` once to f32 and v_interp_p2_f16 rounds the exact `P20 * J + S2` once to f16, as measured on RDNA2 (#697); the exact sums are computed in f64 or, on a device without the Float64 capability, in 64-bit integers: the exact product is added with a sticky bit and rounded once. They follow the shader's float mode, measured on RDNA2 (gfx1035) in all eight combinations of `IEEE_MODE` and kept/flushed f32 and f16 denormals over 768 special and 1,024 random rows. With IEEE=1 a NaN operand is returned quieted; with IEEE=0 it is returned unquieted, except that v_interp_p2_f16's f16 result is always quiet. Flushed f32 denormals flush I/J, v_interp_p2_f16's f32 S2 and the f32 result; flushed f16 denormals flush P0, P10/P20, v_interp_p1lv_f16's f16 S2 and the f16 result. Denormal modes 1 and 2 (flush only inputs or only outputs) and rounding other than to nearest even are not measured and throw. An unknown mode follows IEEE=0 with denormals kept. Lavapipe has no barycentrics, so the draw test is skipped in CI; `agc_driver_interpolation_modes` checks the integer arithmetic on every device, and the f64 arithmetic on devices with Float64. It checks both against the hardware rows in every mode, and against a CPU reference on random rows in every mode and f16 rounding. -- Without `VK_KHR_fragment_shader_barycentric`, a pixel shader whose I/J VGPRs [only feed `v_interp_p1_f32`/`v_interp_p2_f32` of their own pair](../../../core/shader/recompiler/Optimization/src/HostInterpolationChecker.cpp) reads the attribute Vulkan interpolated. Any other read of them or of a raw vertex parameter takes the weights and the three vertex values from a [generated geometry stage](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp), which needs `geometryShader` and a vertex shader before it. On a device with `tessellationShader` but no `geometryShader` (MoltenVK), a triangle list without primitive restart takes them from [generated tessellation stages](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp) instead: each triangle is one three-point patch at tessellation level 1, the evaluation stage writes the corner `gl_TessCoord` points at as the weights, and the vertex clip distances are forwarded: the control stage reads them as the separate `ClipDistance` array the vertex shader writes, so the vertex-to-control interfaces match, and passes them on inside its per-vertex block, which the evaluation stage reads and writes. MoltenVK 1.4.2 (SPIRV-Cross) cannot translate a separate `ClipDistance` array in a tessellation evaluation stage (`no member named 'gl_ClipDistance' in 'main0_out'`), so the two generated stages keep it in their blocks. A flat parameter takes the provoking vertex's value there, the first or with `PROVOKING_VTX_LAST` the last, and every corner of the patch writes it, so the host's own provoking vertex does not matter and `VK_EXT_provoking_vertex` is not needed; a flat per-vertex parameter with the last provoking vertex still throws. The patch keeps the triangle's facing with `VertexOrderCw`, checked with face culling on MoltenVK 1.4.2 (Apple M2) only. Tessellation, mesh and rect-list pipelines, primitives other than triangles, triangle strips and fans without `geometryShader`, more than 32 fragment input locations and interfaces beyond the device's geometry or tessellation limits throw. -- [v_fma_f64 and v_div_fmas_f64](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) with a subnormal operand or result round once, as IEEE 754 fusedMultiplyAdd does with f64 denormals kept, measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1, `v_div_fmas_f64` with VCC clear and set. The f16/f64 denormal field of `FLOAT_MODE` (bits 7:6) is not read: when it flushes, the hardware flushes the operands and the result of `v_fma_f64` (tininess before rounding) and only the result of `v_div_fmas_f64`, while these keep them. Shaders with f64 instructions declare `DenormPreserve` for 64-bit floats only when `shaderDenormPreserveFloat64` is true and Vulkan reports `denormBehaviorIndependence` as `ALL`; other independence modes are not handled by the current backend because they require coupled execution modes. See [VkShaderFloatControlsIndependence](https://docs.vulkan.org/refpages/latest/refpages/source/VkShaderFloatControlsIndependence.html). Elsewhere (including lavapipe), the driver may flush f64 subnormals, and `agc_driver_f64_fma_subnormals` is skipped. -- [v_floor_f64, v_ceil_f64, v_fract_f64, v_frexp_mant_f64 and v_frexp_exp_i32_f64](../../../core/shader/recompiler/Translation/src/Float64Instructions.cpp) follow the `FLOAT_MODE` f16/f64 denormal field: a denormal input is flushed to a zero of its sign when bit 0 is clear (0 and 2), and `v_fract_f64` flushes a denormal result, the fraction of a positive denormal input, to +0 when bit 1 is clear (0 and 1). `v_trunc_f64` and `v_rndne_f64` give the same results either way and are unchanged. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with denorm16 0..3 over 23 f64 values (zeros, denormals, minimum normals, ±0.5, ±1, values next to 1, the largest finite value, infinities and quiet and signaling NaNs) and 16 of them with neg and abs modifiers. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured. +- [v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16](../../../core/shader/recompiler/Translation/src/AttributeInstructions.cpp) read the 16-bit halves of each vertex's parameter through `VK_KHR_fragment_shader_barycentric`, as fixed-function interpolation cannot split a 32-bit attribute. Without the extension the pixel shader takes the [barycentric emulation](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp), whose generated geometry stage passes each vertex's parameter to it as a flat input, so the halves arrive bit for bit; a device without geometry shaders takes the tessellation stages of the same emulation instead, which no 16-bit interpolation test has run through yet. P10 and P20 are the vertex differences rounded to f16 to nearest even, which is not confirmed on hardware. v_interp_p1ll_f16 and v_interp_p1lv_f16 round the exact `P10 * I + P0` once to f32 and v_interp_p2_f16 rounds the exact `P20 * J + S2` once to f16, as measured on RDNA2 (#697); the exact sums are computed in f64 or, on a device without the Float64 capability, in 64-bit integers: the exact product is added with a sticky bit and rounded once. They follow the shader's float mode, measured on RDNA2 (gfx1035) in all eight combinations of `IEEE_MODE` and kept/flushed f32 and f16 denormals over 768 special and 1,024 random rows. With IEEE=1 a NaN operand is returned quieted; with IEEE=0 it is returned unquieted, except that v_interp_p2_f16's f16 result is always quiet. Flushed f32 denormals flush I/J, v_interp_p2_f16's f32 S2 and the f32 result; flushed f16 denormals flush P0, P10/P20, v_interp_p1lv_f16's f16 S2 and the f16 result. Denormal modes 1 and 2 (flush only inputs or only outputs) and rounding other than to nearest even are not measured and throw. An unknown mode follows IEEE=0 with denormals kept. `agc_driver_interpolation_modes` checks the integer arithmetic on every device, and the f64 arithmetic on devices with Float64. It checks both against the hardware rows in every mode, and against a CPU reference on random rows in every mode and f16 rounding. +- Without `VK_KHR_fragment_shader_barycentric`, a pixel shader whose I/J VGPRs [only feed `v_interp_p1_f32`/`v_interp_p2_f32` of their own pair](../../../core/shader/recompiler/Optimization/src/HostInterpolationChecker.cpp) reads the attribute Vulkan interpolated. Any other read of them or of a raw vertex parameter takes the weights and the three vertex values from a [generated geometry stage](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp), which needs `geometryShader` and a vertex shader before it. On a device with `tessellationShader` but no `geometryShader` (MoltenVK), a triangle list without primitive restart takes them from [generated tessellation stages](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp) instead: each triangle is one three-point patch at tessellation level 1, the evaluation stage writes the corner `gl_TessCoord` points at as the weights, and the vertex clip distances are forwarded: the control stage reads them as the separate `ClipDistance` array the vertex shader writes, so the vertex-to-control interfaces match, and passes them on inside its per-vertex block, which the evaluation stage reads and writes. MoltenVK 1.4.2 (SPIRV-Cross) cannot translate a separate `ClipDistance` array in a tessellation evaluation stage (`no member named 'gl_ClipDistance' in 'main0_out'`), so the two generated stages keep it in their blocks. A flat parameter takes the provoking vertex's value there, the first or with `PROVOKING_VTX_LAST` the last, and every corner of the patch writes it, so the host's own provoking vertex does not matter and `VK_EXT_provoking_vertex` is not needed; a flat per-vertex parameter with the last provoking vertex still throws. The patch keeps the triangle's facing with `VertexOrderCw`, checked with face culling on MoltenVK 1.4.2 (Apple M2) only. A rect-list draw takes them from its own generated tessellation stages; its test passes on MoltenVK 1.4.2 (Apple M2), RADV (Radeon RX 7900 XT, Mesa 25.2.8), lavapipe, and on Windows on an RTX 3080 (NVIDIA 617.42) and Radeon RDNA 2 iGPUs (AMD 32.0.21045 and 32.0.21030); no title was run through it except Ruffle PPSA68091 on the M2. Tessellation and mesh pipelines, primitives other than triangles, triangle fans and primitive restart without `geometryShader`, more than 32 fragment input locations and interfaces beyond the device's geometry or tessellation limits throw. +- [v_fma_f64 and v_div_fmas_f64](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) with a subnormal operand or result round once, as IEEE 754 fusedMultiplyAdd does with f64 denormals kept, measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1, `v_div_fmas_f64` with VCC clear and set. The f16/f64 denormal field of `FLOAT_MODE` (bits 7:6) is not read: when it flushes, the hardware flushes the operands and the result of `v_fma_f64` (tininess before rounding) and only the result of `v_div_fmas_f64`, while these keep them. Shaders with f64 instructions declare `DenormPreserve` for 64-bit floats only when `shaderDenormPreserveFloat64` is true and Vulkan reports `denormBehaviorIndependence` as `ALL`; other independence modes are not handled by the current backend because they require coupled execution modes. See [VkShaderFloatControlsIndependence](https://docs.vulkan.org/refpages/latest/refpages/source/VkShaderFloatControlsIndependence.html). Elsewhere the driver may flush f64 subnormals, and `agc_driver_f64_fma_subnormals` skips if it does. +- [v_floor_f64, v_ceil_f64, v_fract_f64, v_frexp_mant_f64 and v_frexp_exp_i32_f64](../../../core/shader/recompiler/Translation/src/Float64Instructions.cpp) follow the `FLOAT_MODE` f16/f64 denormal field: a denormal input is flushed to a zero of its sign when bit 0 is clear (0 and 2), and `v_fract_f64` flushes a denormal result, the fraction of a positive denormal input, to +0 when bit 1 is clear (0 and 1). `v_trunc_f64` and `v_rndne_f64` give the same results either way and are unchanged. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with denorm16 0..3 over 23 f64 values (zeros, denormals, minimum normals, ±0.5, ±1, values next to 1, the largest finite value, infinities and quiet and signaling NaNs), and with neg and abs modifiers over 16 values (13 of those, -1.5, 2.75 and 1 - 2^-53). Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured. - [v_add_f64 and v_mul_f64 denormals](../../../core/shader/recompiler/Translation/src/Float64Instructions.cpp) follow the `FLOAT_MODE` f16/f64 denormal field; the PS5's gfx1013 is not measured. Inputs flush to a zero of their sign when bit 0 is clear (0 and 2). When bit 1 is clear (0 and 1), a sum below 2^-1022, or a product whose exact value rounded to 53 significant bits with an unbounded exponent is below 2^-1022, flushes to a zero of its sign, so `0x2000000000000001 * 0x1ffffffffffffffe` (exactly 2^-1022 - 2^-1126) rounds to 2^-1022 and is kept. Measured on RDNA2 (gfx1031, RX 6700 XT, AMD Windows driver 32.0.21045.5002, HIP) at IEEE=0 and 1, denorm16 0..3, over 576 pairs of 24 f64 values (zeros, denormals, minimum normals, values next to 1 and to 2^-1022, products just below 2^-1022, the largest finite value, infinities), with neg, abs and clamp. `v_fma_f64`, `v_div_fmas_f64` and shaders without a known float mode keep the previous lowering. - [SINT storage images](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) of 8- and 16-bit components saturate each stored 32-bit value to the component range, as adriadam10 measured for image stores on RDNA2 (gfx1035) in #944 (448 rows match); the PS5's gfx1013 is not measured. A D16 store sign-extends each 16-bit half before that saturation, as measured on RDNA2 (gfx1035) for `image_store d16` to all eight SINT formats with DMASK 0xf, 0x7, 0x5, 0x3, 0x2, 0xa and 0x1 (1792 rows match). - [`StoreTexel`](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) writes channel 1 of a two-channel format from the data slot whose DST_SEL is Y, for SINT and UINT alike. With DST_SEL X,Y,Z,W, gfx1035 writes it from the W slot, as adriadam10 measured in #944. - [v_ldexp_f16](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the `FLOAT_MODE` f16 denormal field: bit 0 preserves the half input and bit 1 the half result. Otherwise a denormal input is flushed to its signed zero, and a result whose exact scaled value is below the minimum normal (2^-14) is flushed to its signed zero before rounding, so `v_ldexp_f16 0x07ff, -1` gives +0 instead of 0x0400. Measured on RDNA2 (gfx1036) with round to nearest, IEEE 0 and 1 and denorm16 0..3, over every half magnitude 0x0000..0x0fff of both signs scaled by 2^-1 and 2^0 (16384 rows per mode): no mismatch. NaN and infinity handling and destination high-half preservation are unchanged. Shaders without a known float mode keep the previous lowering. - [v_div_fixup_f64](../../../core/shader/recompiler/Translation/src/Float64Instructions.cpp) flushes its src1 and src2 to a zero of the same sign when bit 0 of the `FLOAT_MODE` f16/f64 denormal field is clear (0 and 2) and keeps a denormal src0 and result in every mode, as `v_div_fixup_f32` does with the f32 field. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) over 1728 triples of 12 f64 values (zeros, denormals, the minimum normal, ±1, the largest finite value, infinity and quiet and signaling NaNs) at IEEE=0 and IEEE=1 with denorm16 0..3. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured. -- [v_mullit_f32](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted only with IEEE=1, as measured at IEEE=0 too; a positive denormal src2 counts as positive); with f32 denormals flushed it follows the f32 denormals entry below, and denormal modes 1 and 2 throw. +- [v_mullit_f32](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted only with IEEE=1, as measured at IEEE=0 too; a positive denormal src2 counts as positive); with f32 denormals flushed it follows the f32 denormals entry below. Denormal mode 2 flushes only the sources, and mode 1 only the result: a result below 2^-126 after rounding becomes a zero of its sign, also when a preserved denormal source produced it. Measured on RDNA2 (gfx1030) over 13,824 operand sets, and 1,632 with an inline or literal constant factor, in all four modes at IEEE=0 and IEEE=1 with f32 round to nearest even. Modes 1 and 2 throw when the module cannot declare `DenormPreserve 32` (no `shaderDenormPreserveFloat32`, or the hardware f32 flush in use), and mode 1 also with another f32 rounding. - [v_mad_legacy_f32 and v_mac_legacy_f32](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140, VOP2 0x06) round the product to f32 before the add, give a +0 product for a zero factor, and flush their f32 denormal sources, their product and their result to a zero of the same sign in every f32 denormal mode, an exception to the `FLOAT_MODE` model in [f32DenormalFlushFor](../../../core/shader/recompiler/Translation/src/VectorInstructions.cpp). Measured on gfx1010 (RX 5700 XT) with `tools/hw-oracle/template.s`, assembled by the AMD Windows driver's `amd_comgr_2.dll` and launched through its `amdhip64_6.dll` instead of `oracle.c` and ROCr, over the 32 rows of the five `FmaLegacy.cpp` and the four `MacLegacy.cpp` encodings: identical in wave32 and wave64 at IEEE 0 and 1 with f32 denormal modes 0 to 3, except that the two signaling NaN rows are quieted only with IEEE=1 (#1281, #1290). A sum of two zeros takes its sign on the bits (+0 unless both are -0), as lavapipe returned -0 for +0 + -0. The PS5's gfx1013 is not measured; LLVM decodes the encoding as `v_mad_legacy_f32` there, as on gfx1010. RDNA2 differs: on gfx1031 (RX 6700 XT, measured by pandather in #1281) the encoding is `v_fma_legacy_f32` and fused, with the same zero rule and denormal-source flush, so an RDNA2 measurement is not evidence for the console. - [v_mad_f32, v_mac_f32, v_madak_f32 and v_madmk_f32](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (gfx10.1 only) leave f32 denormals and NaN bits to the driver, unlike the console. Measured on gfx1010 (RX 5700 XT, #5782): the product is rounded before the add; f32 sources, product and result are flushed to a zero of the same sign in every f32 denormal mode; the product is flushed when tiny after rounding to 24 bits with an unbounded exponent; NaN results follow the fma rule, an invalid product beating a NaN src2; in round mode 2 a sum of zeros of opposite signs is -0. Translating this cost 15× `main` on a `v_mac_f32` chain on the RX 5700 XT and 23× on an RTX 3070, and a never-taken branch around it 6×, so it waits for the SPIR-V float controls of #981. - [v_fma_mixlo_f16 and v_fma_mixhi_f16](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) round the exact product-sum once to f16 with the arithmetic of v_interp_p2_f16, in f64 or, without the Float64 capability, in 64-bit integers. On RDNA2 (gfx1036) they follow the `FLOAT_MODE` f16 round and denormal fields, not the f32 round field: -1.5 * 2^-24 + 2^-126 from f32 sources (0xbfc00000, 0x33800000, 0x00800000) gives 0x8001 with f16 denormals kept and round to nearest even, 0x8000 with f16 denormals flushed and 0x8002 with f16 round toward -infinity. With f16 denormals kept, every f16 rounding mode is translated: the exact product-sum is rounded once in the mode's direction. All four modes match RDNA2 (gfx1036) on the 2,233 rows of the #2631 sweep, including `FLOAT_MODE` 0xcc (toward zero, used by ps5-opengl) and the exact cancellations toward -infinity; the PS5's gfx1013 is not measured. A result beyond 65504 becomes infinity or 65504 as IEEE 754 gives for the mode. Flushed f16 denormals still throw, and packed f16 throws with any f16 rounding but nearest even. `FP16_OVFL` set is not measured. @@ -113,13 +126,18 @@ - [f16 unary, frexp and conversion denormals](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follow the `FLOAT_MODE` f16 denormal field: `v_ceil_f16`, `v_floor_f16`, `v_fract_f16`, `v_rcp_f16`, `v_rsq_f16`, `v_sqrt_f16`, `v_log_f16`, `v_exp_f16`, `v_sin_f16`, `v_frexp_mant_f16`, `v_frexp_exp_i16_f16`, `v_cvt_norm_i16_f16`, `v_cvt_norm_u16_f16` and `v_cvt_f32_f16` flush a denormal half input to its signed zero when bit 0 is clear (0 and 2), and the f16-result ones flush a denormal half result to its signed zero when bit 1 is clear (0 and 1), after the host rounds it to half. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with denorm16 0..3 over every non-NaN f16 input (63,490 rows per mode and instruction): the only mismatches are the 1-ULP driver differences of `v_exp_f16` (2 inputs) and `v_sin_f16` (14), which are the same with denormals kept. NaN inputs are not compared. `v_ldexp_f16` shares the input rule. `v_cvt_pknorm_i16_f16` and `v_cvt_pknorm_u16_f16` flush both half inputs by the same bit, measured on RDNA2 (gfx1030) over every f16 input (each half also negated in the other source) at IEEE=0 and IEEE=1 with denorm16 0..3 and at f16 rounding 1..3: every row matches. Shaders without a known float mode keep the previous lowering. - [v_div_fixup_f16](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) flushes its src1 and src2 to a zero of the same sign when bit 0 of the `FLOAT_MODE` f16 denormal field is clear (0 and 2) and keeps a denormal src0 and result in every mode, as `v_div_fixup_f32` does with the f32 field. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) over 6859 triples of 19 halves (zeros, denormals, minimum normals, ±0.5, ±1, the largest finite value, infinities and quiet and signaling NaNs) at IEEE=0 and IEEE=1 with denorm16 0..3 and FP16_OVFL 0 and 1, also with neg and abs source modifiers and with op_sel high halves. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured. - [Packed f16 NaN results](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (`v_pk_add/mul/fma/min/max_f16`) follow the hardware measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1 with f16 round-to-nearest-even and f16 denormals kept: with IEEE=1 a signaling NaN input is quieted and min/max return the other operand only for a quiet NaN; with IEEE=0 NaN inputs pass through unquieted and min/max ignore signaling. In `v_pk_fma_f16` an invalid product (inf * 0) gives the default NaN even when src2 is a NaN. A shader without a known float mode is translated as IEEE=0, the mode titles use; a mode with another f16 rounding throws. In the f16 denormal modes that flush, each denormal input lane is flushed to a zero of its sign when bit 0 of the field is clear (0 and 2), so `inf * 0x0001` is an invalid product there, and when bit 1 is clear (0 and 1) a `v_pk_add_f16` result below 2^-14, or a `v_pk_mul_f16`, `v_pk_fma_f16` or `v_pk_fmac_f16` result whose exact value rounded to 11 significant bits with an unbounded exponent is below 2^-14, is flushed to a zero of its sign; `v_pk_min/max_f16` flush only their inputs. The native f16x2 add and multiply is not used while results are flushed. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with denorm16 0..3, over 19,192 to 162,204 lanes per instruction with op_sel, op_sel_hi, neg_lo, neg_hi and clamp variants and products and sums at the 2^-14 boundary. -- [f32 denormals](../../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follow the shader's `FLOAT_MODE` f32 denormal field (bits 5:4) when it flushes them (0, and shaders without a known float mode, as in the 0xc0 most titles use), measured on RDNA2 (gfx1035) over 1444 rows per instruction at IEEE=0 and IEEE=1. `v_add/sub/subrev/mul/mul_legacy/mullit/fma/fmac/fmamk/fmaak/min/max/min3/max3/med3/fract/floor/ceil/frexp_mant/sin/cos_f32` flush their f32 inputs and their result to a zero of the same sign (`v_mullit_f32` measured on RDNA2 (gfx1036) over 6907 rows per mode at IEEE=0 and IEEE=1, so a positive denormal src2 counts as zero); the f32 compares, `v_cvt_i32/u32/rpi_i32/flr_i32_f32`, `v_frexp_exp_i32_f32`, `v_cvt_f64_f32`, `v_cvt_f16_f32`, `v_cvt_pkrtz_f16_f32`, `v_cvt_pknorm_i16/u16_f32` and `v_cvt_pk_u8_f32` flush their f32 inputs. `v_div_scale_f32` flushes all three inputs to signed zeros, measured on RDNA2 (gfx1036) over 2744 operand triples per mode at IEEE=0 and IEEE=1 with f32 denormals flushed and kept; its scale-down behaviour and host denormal preservation remain incomplete. `v_div_fixup_f32` flushes its src1 and src2 and keeps a denormal src0 and result, measured on RDNA2 (gfx1036) over 6859 rows per mode at IEEE=0 and IEEE=1. Tininess is detected after rounding, measured on RDNA2 (gfx1036) for `v_mul_f32` and `v_mullit_f32` at IEEE=0 and IEEE=1: a product is flushed when its value rounded to 24 bits with an unbounded exponent is below 2^-126, so `0x3f7ffffe * 0x00800001` (2^-126 * (1 - 2^-46)) gives `0x00800000` and `0x3f7fffff * 0x00800000` (2^-126 * (1 - 2^-24)) gives zero. The check rounds in the f32 round mode of `FLOAT_MODE` (bits 1:0), measured in all four modes, while the products themselves are still rounded to nearest even. This applies to `v_mul_f32`, `v_mul_legacy_f32`, `v_mullit_f32`, and to `v_fma_f32`, `v_fmac_f32`, `v_fmamk_f32`, `v_fmaak_f32` and `v_fma_mix_f32` when the addend is zero; with a nonzero addend the fused result is kept, so an exact result just below 2^-126 that rounds to it is not flushed. In the three directed modes the check is computed exactly from the significands. To nearest even (0, the mode of shaders without a known float mode) one factor is multiplied by 2^64, which is exact below 2^64, and the product with the other factor is compared with 2^-62. That multiply is a plain `OpFMul` like the product, and no f32 rounding mode is requested in the SPIR-V, so the check relies on the driver rounding f32 to nearest even by default, as the products do, and a product that rounds up to 2^-126 is the emitted product. `v_fma_mix_f32` follows this rule with mixed f16/f32 sources too, measured on RDNA2 (gfx1036) with f16 in either multiplicand slot, low and high half selection, and an f32 or f16 zero addend: f32 `0x00801002` times f16 `0x3bff` gives `0x00800000` to nearest at IEEE=0 and IEEE=1, while the neighbours `0x00801001` and `0x00801003` give zero and `0x00800001`. The tiny-product boundary was also measured for both signs in all four f32 round modes at IEEE=0; Its f16 sources are flushed to a zero of their sign when bit 0 of the f16/f64 denormal field is clear (0 and 2), whatever the f32 field, measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with f32 denormal modes 0 and 3 and f16 denormal modes 0 to 3, over 2744 triples of 14 halves with all three sources f16 and 1960 rows with an f32 multiplicand. `v_fma_mixlo_f16` and `v_fma_mixhi_f16` flush their f32 sources too and keep a product below 2^-126, measured on RDNA2 (gfx1036). `v_mad_legacy_f32` and `v_mac_legacy_f32` flush their inputs, their product and their result in every mode, see its entry; their product is still checked for tininess on its exact value, so one below 2^-126 gives zero even when it rounds to 2^-126, as the console's separately rounded encoding is not measured at this boundary. Denormal modes 1 and 2 throw for the other instructions. Not covered, and not measurable on RDNA2 (no such opcodes) or not measured: `v_mac_f32`, `v_mad_f32`, `v_madak_f32` and `v_madmk_f32` (rounded separately), `v_dot2_f32_f16` and `v_div_fmas_f32` (on hardware `v_div_fmas_f32` flushes only its result), f32 interpolation and `ds_add_f32` keep the previous lowering; `ds_min/max/cmpst_f32` flush both operands by the field, see the LDS float atomics entry. With f32 denormals kept (3) the results depend on the driver's default denormal behaviour, as no `DenormPreserve` execution mode is requested; `v_ldexp_f32` and the f32 compares are the exceptions. `v_cmp_X_f32` and `v_cmpx_X_f32` compare the operands as sign-magnitude integers with the NaN cases handled apart, so a denormal is not read as zero, measured on RDNA2 (gfx1036) over 256 operand pairs per opcode at IEEE=0 and IEEE=1 with f32 denormals kept and flushed. `v_ldexp_f32` preserves subnormal inputs and rounds a subnormal result in the f32 round mode of `FLOAT_MODE`, measured on RDNA2 (gfx1036) over 2163 rows in each of the four round modes at IEEE=0 and IEEE=1. An overflow rounds like any other result (infinity, or the largest finite value when the mode rounds that sign toward zero). With f32 denormals flushed it flushes a subnormal input and result, measured over the same rows in each round mode at IEEE=0 only. A device whose driver splits `Fma` into a multiply and an add flushes a product below 2^-126 before adding a non-zero addend in `v_fma_f32` and `v_fmac_f32`: `0x1fffffff * 0x1fffffff + 0x00800000` gives `0x00800000` instead of the fused `0x00ffffff` on RADV RENOIR (Mesa 26.2.4), whose compiled shader has `v_mad_f32` and no `v_fma_f32`. [F32DenormalFlush](../../../core/libs/prx/libSceAgcDriver/tests/execution/F32DenormalFlush.cpp) accepts that sum on such a device. +- [f32 denormals](../../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follow the shader's `FLOAT_MODE` f32 denormal field (bits 5:4) when it flushes them (0, and shaders without a known float mode, as in the 0xc0 most titles use), measured on RDNA2 (gfx1035) over 1444 rows per instruction at IEEE=0 and IEEE=1. `v_add/sub/subrev/mul/mul_legacy/mullit/fma/fmac/fmamk/fmaak/min/max/min3/max3/med3/fract/floor/ceil/frexp_mant/sin/cos_f32` flush their f32 inputs and their result to a zero of the same sign (`v_mullit_f32` measured on RDNA2 (gfx1036) over 6907 rows per mode at IEEE=0 and IEEE=1, so a positive denormal src2 counts as zero); the f32 compares, `v_cvt_i32/u32/rpi_i32/flr_i32_f32`, `v_frexp_exp_i32_f32`, `v_cvt_f64_f32`, `v_cvt_f16_f32`, `v_cvt_pkrtz_f16_f32`, `v_cvt_pknorm_i16/u16_f32` and `v_cvt_pk_u8_f32` flush their f32 inputs. `v_div_scale_f32` flushes all three inputs to signed zeros, measured on RDNA2 (gfx1036) over 2744 operand triples per mode at IEEE=0 and IEEE=1 with f32 denormals flushed and kept; its scale-down behaviour and host denormal preservation remain incomplete. `v_div_fixup_f32` flushes its src1 and src2 and keeps a denormal src0 and result, measured on RDNA2 (gfx1036) over 6859 rows per mode at IEEE=0 and IEEE=1. Tininess is detected after rounding, measured on RDNA2 (gfx1036) for `v_mul_f32` and `v_mullit_f32` at IEEE=0 and IEEE=1: a product is flushed when its value rounded to 24 bits with an unbounded exponent is below 2^-126, so `0x3f7ffffe * 0x00800001` (2^-126 * (1 - 2^-46)) gives `0x00800000` and `0x3f7fffff * 0x00800000` (2^-126 * (1 - 2^-24)) gives zero. The check rounds in the f32 round mode of `FLOAT_MODE` (bits 1:0), measured in all four modes, while the products themselves are still rounded to nearest even. This applies to `v_mul_f32`, `v_mul_legacy_f32`, `v_mullit_f32`, and to `v_fma_f32`, `v_fmac_f32`, `v_fmamk_f32`, `v_fmaak_f32` and `v_fma_mix_f32` when the addend is zero; with a nonzero addend the fused result is kept, so an exact result just below 2^-126 that rounds to it is not flushed. In the three directed modes the check is computed exactly from the significands. To nearest even (0, the mode of shaders without a known float mode) one factor is multiplied by 2^64, which is exact below 2^64, and the product with the other factor is compared with 2^-62. That multiply is a plain `OpFMul` like the product, and no f32 rounding mode is requested in the SPIR-V, so the check relies on the driver rounding f32 to nearest even by default, as the products do, and a product that rounds up to 2^-126 is the emitted product. `v_fma_mix_f32` follows this rule with mixed f16/f32 sources too, measured on RDNA2 (gfx1036) with f16 in either multiplicand slot, low and high half selection, and an f32 or f16 zero addend: f32 `0x00801002` times f16 `0x3bff` gives `0x00800000` to nearest at IEEE=0 and IEEE=1, while the neighbours `0x00801001` and `0x00801003` give zero and `0x00800001`. The tiny-product boundary was also measured for both signs in all four f32 round modes at IEEE=0; Its f16 sources are flushed to a zero of their sign when bit 0 of the f16/f64 denormal field is clear (0 and 2), whatever the f32 field, measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with f32 denormal modes 0 and 3 and f16 denormal modes 0 to 3, over 2744 triples of 14 halves with all three sources f16 and 1960 rows with an f32 multiplicand. `v_fma_mixlo_f16` and `v_fma_mixhi_f16` flush their f32 sources too and keep a product below 2^-126, measured on RDNA2 (gfx1036). `v_mad_legacy_f32` and `v_mac_legacy_f32` flush their inputs, their product and their result in every mode, see its entry; their product is still checked for tininess on its exact value, so one below 2^-126 gives zero even when it rounds to 2^-126, as the console's separately rounded encoding is not measured at this boundary. Denormal modes 1 and 2 throw for the other instructions except the f32 compares and the f32 conversions in their entries. Not covered, and not measurable on RDNA2 (no such opcodes) or not measured: `v_mac_f32`, `v_mad_f32`, `v_madak_f32` and `v_madmk_f32` (rounded separately), `v_dot2_f32_f16` and `v_div_fmas_f32` (on hardware `v_div_fmas_f32` flushes only its result), f32 interpolation and `ds_add_f32` keep the previous lowering; `ds_min/max/cmpst_f32` and the image, buffer, global and flat `fmin/fmax/fcmpswap` flush both operands by the field, see the float atomics entries. With f32 denormals kept (3) the results depend on the driver's default denormal behaviour, as no `DenormPreserve` execution mode is requested; `v_ldexp_f32` and the f32 compares are the exceptions. `v_cmp_X_f32` and `v_cmpx_X_f32` compare the operands as sign-magnitude integers with the NaN cases handled apart, so a denormal is not read as zero, measured on RDNA2 (gfx1036) over 256 operand pairs per opcode at IEEE=0 and IEEE=1 with f32 denormals kept and flushed. `v_ldexp_f32` preserves subnormal inputs and rounds a subnormal result in the f32 round mode of `FLOAT_MODE`, measured on RDNA2 (gfx1036) over 2163 rows in each of the four round modes at IEEE=0 and IEEE=1. An overflow rounds like any other result (infinity, or the largest finite value when the mode rounds that sign toward zero). With f32 denormals flushed it flushes a subnormal input and result, measured over the same rows in each round mode at IEEE=0 only. A device whose driver splits `Fma` into a multiply and an add flushes a product below 2^-126 before adding a non-zero addend in `v_fma_f32` and `v_fmac_f32`: `0x1fffffff * 0x1fffffff + 0x00800000` gives `0x00800000` instead of the fused `0x00ffffff` on RADV RENOIR (Mesa 26.2.4), whose compiled shader has `v_mad_f32` and no `v_fma_f32`. [F32DenormalFlush](../../../core/libs/prx/libSceAgcDriver/tests/execution/F32DenormalFlush.cpp) accepts that sum on such a device. - [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. Measured on RDNA2 (gfx1036) over every f16 input and 32,832 f32 inputs: RADV returns the hardware's bits for every input of rcp, rsq, sqrt and log and all but one of exp (f16 0x11c5, 1 ULP); NVIDIA (615.71.09) stays within 2 ULP except `v_log_f32` near 1.0 (next entry). [v_sin and v_cos](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) take the driver's sine or cosine of the radians of the exact cycle, except within 2^-12 of a turn from a zero of the function, where the result is the linear term of the exact distance to the zero: the hardware's constant 6.2831826 (0x40c90fd5) at the integers, which the hardware applies exactly to inputs below 2^-12 of a turn, and the f32 2π at the half and quarter turns, where the hardware is within 7 ULP of it. Outside those bands, a driver result more than 4 ULP from a Taylor series in turns of the exact cycle is replaced by the series, which is within 2 ULP of the true sine and cosine; the hardware is up to 9 ULP from them (gfx1035, 243,092 f32 inputs), so results stay within 13 ULP of the hardware on any driver. RADV returns the hardware's bits for every f16 input and for 32,422 sine and 32,486 cosine results of the 32,832 f32 inputs; the rest are 1 ULP off, except up to 14 ULP on either side of non-zero integers and up to 15 ULP beyond ±256 cycles, where the hardware's own reduction differs from the exact fraction, and up to 6 ULP inside the half- and quarter-turn bands. NVIDIA's own sine is within 1 ULP on 93% of the f32 inputs and up to 1,166 ULP off just outside the bands, its cosine within 163 ULP; the series replaces those results. A denormal f32 input with f32 denormals kept gives the hardware the input times 6.2831826; both hosts flush it. +- [v_rcp_f32 and v_rcp_iflag_f32 with `APS5_EXACT_RCP`](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterReciprocal.cpp) return the hardware reciprocal for normal inputs with normal results: a cubic over 32 mantissa segments, the linear term rounded to nearest even, plus 2,761 one-ULP corrections. It matches all 2^23 mantissas of an RDNA2 (gfx1031) sweep; inputs or results that are not normal keep the driver's 1/x. It is off by default because the coefficient and correction tables add about 13k SPIR-V words to each shader that uses it. The switch also reaches v_rcp_f16 (rounded from the f32 reciprocal) and the pixel shader's position W input. - [v_ldexp_f32 in denormal modes 1 and 2](../../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the `FLOAT_MODE` f32 denormal field: mode 1 keeps a subnormal input and flushes a result below 2^-126 to a zero of its sign in every f32 round mode, also one that rounds to 2^-126 at subnormal precision (`0x3fffffff` scaled by 2^-127); mode 2 flushes a subnormal input to a zero of its sign and rounds a subnormal result in the f32 round mode of `FLOAT_MODE`, as mode 3 does. Measured on RDNA2 (gfx1030, Radeon RX 6800, AMD Windows driver 32.0.21045.5002 through HIP) over 10,260 rows (54 inputs of both signs: zeros, subnormals, normals near 2^-126 and 1.0, the largest finite value, infinities and NaNs, each scaled by 190 exponents from -2^31 to 2^31-1) at IEEE=0 and IEEE=1 with each f32 denormal mode 0 to 3 and f32 round mode 0 to 3, DX10_CLAMP=1, f16 denormals kept, f16 round to nearest even and FP16_OVFL=0; with the `clamp` modifier the result is clamped as in the other modes. The PS5's gfx1013 is not measured. +- [32-bit denormal execution mode](../../../core/shader/recompiler/Recompiler.cpp) (shader recompiler) - the hardware f32 flush that the startup probe chooses (`DenormFlushToZero 32`) is declared only in shaders whose `FLOAT_MODE` flushes f32 denormals (field 0, or no known float mode). A shader that keeps them on either side (field 1, 2 or 3) declares `DenormPreserve 32` when the device has `shaderDenormPreserveFloat32` with a `denormBehaviorIndependence` other than NONE; on other devices it gets the driver's default f32 denormal handling, which flushes the denormals it keeps on MoltenVK. - [v_log_f32 and v_log_f16](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) keep the driver's log2 unless, for inputs within 1/8 of 1.0, it is more than 4 ULP from an eight-term series in x - 1, which then replaces it. `v_log_f16` goes through the same f32 lowering and is rounded to half. The series is evaluated for every `v_log` and chosen with `OpSelect` rather than a branch, so its seven fused multiply-adds and two multiplies are an unconditional cost. Measured on RDNA2 over all 3,145,728 f32 inputs within 1/8 of 1.0 (gfx1035, IEEE 0, f32 denormals flushed), the hardware is within 1 ULP of the correctly rounded log2 and the series within 2 ULP of it and within 3 ULP of the hardware (2 except at 0x3f752e47, 0x3f7baab0 and 0x3f7bc4fb), so a kept driver value is within 7 ULP of the hardware and a driver that returns the hardware's bits never triggers the gate; RADV (Mesa 26.2.4) returns the hardware's bits on the 9,280 such inputs measured on gfx1036; NVIDIA (615.71.09) was up to 23,947,836 ULP off at 1 - 2^-24 and is within 6 ULP inside the band after the change; outside it its own log2 stays up to 9 ULP off (measured up to 1.375). Over every f16 input, the change moves one `v_log_f16` result on NVIDIA (0x3bed, 1 ULP, onto the hardware's bits) and none on RADV. +- [v_add_f32, v_sub_f32, v_subrev_f32, v_mul_f32 and v_mul_legacy_f32 in denormal modes 1 and 2](../../../core/shader/recompiler/Translation/src/VectorInstructions.cpp) throw with a directed f32 round mode, as the host rounds these sums and products to nearest even (a difference that modes 0 and 3 also have then), and when the module cannot declare `DenormPreserve 32` (no `shaderDenormPreserveFloat32`, or the hardware f32 flush in use); the PS5's gfx1013 is not measured. Otherwise mode 1 keeps denormal inputs and flushes a result below 2^-126 to a zero of its sign (a product only when it stays below 2^-126 after rounding to 24 bits with an unbounded exponent), and mode 2 flushes denormal inputs and keeps a denormal result. Measured on RDNA2 (gfx1030, RX 6800) over 1,296 operand pairs, 896 products around 2^-126 and 1,632 with a constant factor, at IEEE 0 and 1 in every f32 denormal mode, with DX10_CLAMP=1, f16 denormals kept and FP16_OVFL=0. +- [f32 min and max in denormal modes 1 and 2](../../../core/shader/recompiler/Translation/src/VectorInstructions.cpp) (`v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32`, `v_med3_f32`) throw in mode 1 when the module cannot declare `DenormPreserve 32` (no `shaderDenormPreserveFloat32`, or the hardware f32 flush in use), and mode 3 keeps depending on the driver there; the PS5's gfx1013 is not measured. They follow only bit 0 of the `FLOAT_MODE` f32 denormal field: mode 2 flushes denormal inputs to zeros of their sign as mode 0 does, and mode 1 keeps them and returns a selected denormal unflushed as mode 3 does. Measured on RDNA2 (gfx1030, RX 6800) over the 2,744 operand triples of ±0, ±2^-149, ±0x007fffff, ±2^-126, ±1, ±infinity, a quiet and a signaling NaN at IEEE 0 and 1 in each f32 denormal mode, round to nearest even, DX10_CLAMP=1, f16 denormals kept, FP16_OVFL=0: modes 1 and 3 give identical results, and so do modes 2 and 0. - [Ordered not-equal, less-than and greater-than compares](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) (shader recompiler) - `FPOrdNotEqual32` is emitted as `OpFUnordNotEqual`, and `FPOrdLessThan32` and `FPOrdGreaterThan32` as their ordered forms, each with both operands checked for NaN on their bits; the results are the same for every input. The AMD Windows driver 32.0.13058.8001 (Radeon 860M, RDNA 3.5) returns 1 for a NaN operand when any float form of not-equal feeds a ballot on a 32-wide subgroup (#825), and so does a less-than or greater-than against an infinity the driver sees as a constant (#3536), presumably rewritten into a not-equal by its compiler - [16-bit integer multiplies](../../../core/shader/recompiler/Translation/src/IntegerInstructions.cpp) (`v_mul_lo_u16`, `v_pk_mul_lo_u16`) (shader recompiler) - a factor taken from the low half of its source is read as the low 24 bits of the register, not the low 16; the low 16 bits of the product are the same for every input. The AMD Windows driver 32.0.21045.5002 (RX 6700 XT, RDNA 2) stores the full 32-bit product when two values it knows fit in 16 bits are multiplied, the product is reduced to 16 bits and nothing else is merged into the stored value: `v_mul_lo_u16` into a register whose high half is zero, also with factors masked to 16 bits by the program, and `v_pk_mul_lo_u16` whose high lanes are zero wrote 0xfffe0001 for 0xffff * 0xffff where the GPU writes 0x00000001. Reducing the product with `v_and_b32`, `v_bfe_u32` or a shift pair loses the same bits; factors read as 24-bit fields do not. The [constant folder](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) keeps a field read that a multiply uses, even when the program already masked the factor to 16 bits, since folding it away brings the full product back (#8271). Measured against the same instructions run through the [hardware oracle](../HW_ORACLE.md) on that GPU; other drivers are not checked - [f32 compares in denormal modes 1 and 2](../../../core/shader/recompiler/Translation/src/VectorInstructions.cpp) (`v_cmp_X_f32` and `v_cmpx_X_f32` with X = eq, ge, gt, le, lg, lt, neq, nge, ngt, nle, nlg, nlt) flush their f32 denormal inputs by bit 0 of the `FLOAT_MODE` f32 denormal field: mode 1 keeps them as mode 3 does, and mode 2 flushes them as mode 0 does. Measured on RDNA2 (gfx1030, Radeon RX 6800, AMD Windows driver 32.0.21045.5002 through HIP) over 484 operand pairs of zeros, denormals, minimum normals, ones, the largest finite value, infinities and quiet and signaling NaNs of both signs, in VOPC form, at IEEE=0 and IEEE=1 with each f32 denormal mode 0 to 3, f32 round to nearest even, DX10_CLAMP=1, f16 denormals kept, f16 round to nearest even and FP16_OVFL=0. The PS5's gfx1013 is not measured. +- [f32 conversions in denormal modes 1 and 2](../../../core/shader/recompiler/Translation/src/VectorInstructions.cpp) (`v_cvt_i32/u32/rpi_i32/flr_i32_f32`, `v_frexp_exp_i32_f32`, `v_cvt_f64_f32`, `v_cvt_pknorm_i16/u16_f32`, `v_cvt_pk_u8_f32`, `v_cvt_pkrtz_f16_f32` and `v_cvt_f16_f32`) flush their f32 denormal inputs by bit 0 of the `FLOAT_MODE` f32 denormal field: mode 1 keeps them as mode 3 does, and mode 2 flushes them as mode 0 does. Measured on RDNA2 (gfx1036, ROCm 7.1.1, Linux 7.2) over 148 sources and 484 operand pairs (zeros, denormals, minimum normals, f16 limits, the largest finite value, infinities, NaNs) at IEEE=0 and IEEE=1 in every f32 denormal and round mode, and for the f16 and f64 results in every f16/f64 denormal and round mode. `v_div_scale_f32` and `v_div_fixup_f32` do not follow this rule and still throw in modes 1 and 2, as do `v_mad_mixlo/mixhi_f16`, which RDNA2 lacks. The PS5's gfx1013 is not measured. - [f16 compares](../../../core/shader/recompiler/Translation/src/CompareInstructions.cpp) (`v_cmp_X_f16` and `v_cmpx_X_f16` with X = eq, ge, gt, le, lg, lt, neq, nge, ngt, nle, nlg, nlt) flush their f16 denormal inputs to a zero of the same sign by bit 0 of the `FLOAT_MODE` f16 denormal field: modes 0 and 2 flush them, 1 and 3 keep them. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) over 256 operand pairs of zeros, denormals, minimum normals, ones, the largest finite value, infinities and quiet and signaling NaNs of both signs, in VOPC form, at IEEE=0 and IEEE=1 with each f16 denormal mode 0 to 3, f16 round to nearest even, DX10_CLAMP=1, f32 denormals kept and FP16_OVFL=0. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured. - [DB_SHADER_CONTROL.PRIMITIVE_ORDERED_PIXEL_SHADER](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - the host pixel interlock spans the whole shader, not only up to `s_sendmsg` ORDERED_PS_DONE. - [POPS collision SGPRs](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - the collision wave ID and intrawave collision mask read 0, so a title skips its POPS wait and runs its per-collision loop once. @@ -139,15 +157,16 @@ - [FP16_OVFL](../../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follows the shader's float mode as measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f16 denormals kept and flushed: with `FP16_OVFL`=1 an f16 result that rounds to ±infinity is written as ±0x7bff unless a source operand is infinite, so `v_rcp_f16`, `v_rsq_f16` and `v_log_f16` of ±0 and `v_exp_f16` of 16 or more are clamped while an infinite input passes through, and `v_div_fixup_f16` keeps the infinity it writes for a zero denominator or an infinite numerator and clamps any other infinite quotient, a NaN one included. `v_cvt_pkrtz_f16_f32` rounds toward zero and never overflows. The sources are checked as the f32 values the translation computes with; the rule is applied to `v_fma_mix*_f16` with f32 sources and to `v_interp_p2_f16` without a measurement of infinite f32 sources or parameters. Round modes other than nearest even are not modelled, so the overflow threshold is the nearest-even one (65520). - [Directed f16 rounding](../../../core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp) for `v_cvt_f16_f32`, `v_cvt_f16_i16`, `v_cvt_f16_u16` and `v_ldexp_f16` follows `FLOAT_MODE` bits 3:2 in every f16 denormal mode, measured on RDNA2 (gfx1036 with f16 denormals preserved, gfx1030 in all sixteen combinations of f16 rounding and denormal mode). When bit 7 flushes f16 results, `v_cvt_f16_f32`, `v_cvt_f16_i16` and `v_cvt_f16_u16` write a zero of the result's sign for a value that, rounded in that mode with an unbounded exponent, is below 2^-14, so a value just below 2^-14 that rounds up to it stays 0x0400; `v_ldexp_f16` keeps its rule above. The `clamp` modifier is applied before the rounding. On gfx1030 every row matches: 8,996 f32 inputs around the half denormal, normal and overflow boundaries with and without `clamp`, every 16-bit integer, and 2,470 `v_ldexp_f16` operands. - [v_cvt_pkrtz_f16_f32 and v_pack_b32_f16 denormals](../../../core/shader/recompiler/Translation/src/ConvertInstructions.cpp) follow the `FLOAT_MODE` f16 denormal field: `v_cvt_pkrtz_f16_f32` flushes each denormal half result to its signed zero when bit 1 is clear (0 and 1), and `v_pack_b32_f16` flushes each denormal half input to its signed zero when bit 0 is clear (0 and 2). Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with denorm16 0..3 and f32 denormals kept, over 256 pairs of 16 f32 values around the f16 denormal range for `v_cvt_pkrtz_f16_f32` and 256 pairs of 16 halves (zeros, denormals, minimum normals, ±1, the largest finite value, infinities and quiet and signaling NaNs) for `v_pack_b32_f16`. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured. -- [Comparison sampling of a color texture](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_sample_c`/`image_sample_c_lz` on a texture that is not R32 float or R16 unorm) is emulated in the shader, since Vulkan compares only depth formats: the red channel is fetched, compared and, for bilinear filtering, blended (compare, then filter). Only 2D, 2D array and cube views, float/unorm/snorm formats, wrap, clamp-to-edge and clamp-to-border addressing (border red 0 for the black borders, 1 for opaque white) and point or bilinear filtering at one level are implemented, with texel offsets added to the texel indices; LOD biases and clamps are accepted, as they cannot select another level of a single-level sample. Half-border addressing, the border color table, gradients, gathers, other dimensions, anisotropy (except on the 8-bit formats below) and mip filtering throw. Under bilinear filtering, taps past a clamp-to-border edge compare the border value and blend with the texel taps, so results near the edge depend on the filter weights. AMD documentation does not say how a color format's reference is handled: it is clamped to [0, 1] for unorm and [-1, 1] for snorm, as Vulkan does for unorm depth, and not clamped for float. 8-bit sRGB and UNORM (`Format8Srgb`, `Format8_8Srgb`, `Format8_8_8_8Srgb`, `Format8UNorm`, `Format8_8UNorm`, `Format8_8_8_8UNorm`) return 0 for every compare function, measured with `image_sample_c_lz` on gfx1035 over references -0.5..1.5 and texel codes 0..255. A bilinear cube of those 8-bit formats without `DISABLE_CUBE_WRAP`, and an anisotropic S# on them, return 0 for the same reason; float, snorm and the other unorm bilinear cubes without that bit still throw. Bilinear weights are full float, not the hardware's fixed-point subtexel weights +- [Comparison sampling of a color texture](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_sample_c`/`image_sample_c_lz` on a texture that is not R32 float or R16 unorm) returns 0 for every comparison function on formats AnyPS5 can sample as floating-point measured on GFX10 (codes 1–77 except native comparison codes 7 and 22) and on the corresponding GFX11 formats measured on gfx1100. The current Vulkan path still lowers these formats through the shader; the compare result is constant zero. Scaled, integer and formats requiring converted sampling remain refused. Only 2D, 2D-array and cube views are accepted; image tables, min/max reduction, unnormalized coordinates and unsupported addressing remain refused. Point and bilinear results match on gfx1100. The PS5's gfx1013, array/cube behavior for the measured formats, and anisotropic comparisons remain unverified. Bilinear weights are full float, not the hardware's fixed-point subtexel weights - [Negative color-texture comparison wrap indices](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (libSceAgcDriver) use explicit `OpSRem` normalization to implement modulo; the negative-offset point-wrap regression fails with direct `OpSMod` and passes with normalization on Mesa 22.3.6 llvmpipe (LLVM 15.0.6), but negative-index wrap has not been measured on PS5 hardware. - [Converted unorm images](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (10_11_11_UNORM, read and written through an R32_UINT view) are sampled in the shader, the way color comparisons are emulated: `image_sample` of a 2D or 2D array view fetches the one or four texels at LOD 0, decodes them and blends them, for point or bilinear filtering with equal minification and magnification filters and wrap or clamp-to-edge addressing. A shader gets these sample modes only once a converted descriptor reaches one of its sampled images: that capture throws `ConvertedSampleRequired`, the driver records the program and prepares it again with `GuestContext::convertedSamples`, so such a shader is prepared twice per run and its first capture is repeated. The record lives with the registered program and is not kept across runs. Bilinear weights are rounded to the hardware's 8-bit grid (`roundEven(fraction * 256) / 256`, ties to even), as measured with `image_sample_lz` on RDNA2 (gfx1035) for 10_11_11 UNORM texels by adriadam10 in #5958; point sampling, decoding, swizzles and wrap and clamp-to-edge addressing matched the same measurement. A sample that is not `_lz` needs an image with one mip level; gathers, LOD queries, clamp-to-border and mirrored addressing, other filters, min/max reduction, unnormalized coordinates, images selected at run time, 16-bit data and a DST_SEL of the missing W channel throw. -- [Converted unorm image loads](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) divide by 2^n - 1 with the buffer format path's `OpFDiv`, which Vulkan allows to be up to 2.5 ULP off, so some drivers are not bit-exact. -- [64-bit LDS atomics](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. On RDNA2 (gfx1036) the LDS serialises a 32-bit atomic on a slot's high dword and a 64-bit atomic on that slot: 128 lanes of `ds_add_u32` and 128 lanes of `ds_add_u64` on one slot sum exactly. Only that pair was measured. Every 32-bit LDS atomic of a program that contains a 64-bit LDS atomic therefore takes the same lock, including a 32-bit `flat_atomic_*` whose address lands in the LDS aperture (a 64-bit one takes it already); the flat forms were not measured against a 64-bit atomic either (the other 32-bit LDS atomics, `ds_min/max`, `ds_cmpst`, the float forms, `ds_inc/dec`, `ds_mskor` and `ds_wrxchg`, are extrapolated from `ds_add_u32` and were not measured against a 64-bit atomic); a program without a 64-bit LDS atomic keeps its native 32-bit atomics. `ds_condxchg32_rtn_b64` is two `ds_mskor` halves, each under the lock but not atomic as a pair, so a 64-bit atomic between the two halves is not excluded. Sub-dword writes (`ds_write_b8`, `ds_write_b16` and their `_d16_hi` forms) and plain `ds_read_*`, `ds_write_*`, `ds_append_u32` and `ds_consume_u32` are not taken under the lock, so a plain access racing a 64-bit atomic on the same dwords can see or overwrite one half of it; whether the hardware orders them was not measured. The lock serialises the workgroup's LDS atomics of such a program; the throughput cost was not measured. `VK_KHR_workgroup_memory_explicit_layout` with `shaderSharedInt64Atomics` would make both widths native and is not used. A 64-bit atomic at an address that is 4-byte but not 8-byte aligned faults the wave on gfx1036, as on gfx1035. When the LDS lives in device memory (compute LDS beyond the device's shared-memory limit) and the device has `shaderBufferInt64Atomics`, no lock is used: a 64-bit atomic is a compare-exchange loop on a 64-bit view of the storage buffer and a 32-bit atomic is a native buffer atomic, a misaligned 64-bit atomic records the `Unaligned` shader fault with the LDS byte address instead of updating, so a mixed-width pair on one slot relies on the device ordering both widths on one buffer address; only `ds_add_u32` against `ds_add_u64` was measured, on gfx1036 (AMD Windows driver 32.0.21042.62) and an RTX 4070 SUPER. The lock is not used there because AMD's Windows driver compiles an acquire or release on a storage buffer, at workgroup scope or wider, to `s_barrier`, and those barriers inside the lock's divergent loop pair with the program's own `s_barrier`s. The fence after each LDS atomic compiles to `s_barrier` on that driver as well, so a wave that skips an LDS atomic that other waves of its workgroup execute misses a barrier they reach; its effect was not measured. On that path a 64-bit atomic at an address that is 4-byte but not 8-byte aligned is skipped and returns 0. -- [LDS float atomics](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_min/max/cmpst_f32`, `ds_min/max/cmpst_f64` and their `_rtn` forms) follow the hardware measured on RDNA2 (gfx1036) over 14 x 14 operand sets (zeros, ones, infinities, denormals, quiet and signalling NaNs of both signs and with payloads) in IEEE 0 and 1 with every value 0-3 of the type's denormal field: both operands are flushed to a signed zero when bit 0 of the field is clear (0 and 2; 1 and 3 keep them; the f32 field for the f32 forms, the f16/f64 field for the f64 forms; a shader without a known float mode flushes f32 and keeps f64), then a signalling-NaN data operand is stored quieted, else a signalling-NaN old value is stored quieted, else a quiet-NaN old value is replaced by the data, else a quiet-NaN data operand loses, else the ordered compare with -0 below +0 decides, and the flushed value is what is written, also when the old value wins. `cmpst` compares the flushed bits (both zeros equal, NaN never equal) and leaves memory unchanged when they differ. The returned value is always the old value as it was in memory. `IEEE_MODE` has no effect. The same bit rule serves the buffer float atomics (`AtomicFloatBits`). Not measured: a denormal or signalling-NaN `data1` of `cmpst` (stored as given). `ds_add_f32` and `ds_add_rtn_f32` follow RDNA1 (gfx1010, RX 5700 XT), measured with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime over 1574 operand pairs (signed zeros, denormals, the smallest normals, infinities, quiet and signalling NaNs with payloads, the largest normals, random pairs near the denormal range and pairs that nearly cancel) in all 16 combinations of the f32 denormal field and the f32 round mode, and at IEEE 1 and DX10 clamp 0: both operands are flushed to a signed zero when bit 0 of the field is clear and the result when bit 1 is clear, the sum is rounded to nearest even whatever the round mode, a NaN data operand is stored quieted, else a NaN old value is stored quieted, `+inf + -inf` stores 0xffc00000, and `IEEE_MODE` and `DX10_CLAMP` have no effect. The sum is the host's `OpFAdd` between those flushes, so where the field keeps f32 denormals the result also depends on the host preserving them, which the module does not request. thuanquang measured the same rule on RDNA2 (gfx1031, RX 6700 XT) in #5492, with the same operand classes and 18 modes in wave32 and wave64; the PS5's gfx1013 is not measured. -- [LDS in pixel, vertex-path and fetch programs](../../../core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp) (shader recompiler) - is an array private to each host invocation, in `Function` storage, as only compute and mesh programs get workgroup memory; on the console one LDS region is shared by the wave (RDNA 2 ISA sections 3.6.6 and 10.4), so a lane does not see another lane's LDS writes. Sub-dword writes (`ds_write_b8`, `ds_write_b16` and their `_d16_hi` forms), the 32-bit LDS atomics, `ds_append` and `ds_consume` load, update and store the invocation's own dword without an atomic, as Vulkan allows atomics only on `Uniform`, `Workgroup`, `Image`, `StorageBuffer`, `PhysicalStorageBuffer` and `TaskPayloadWorkgroupEXT` pointers (VUID-StandaloneSpirv-None-04686). `ds_append` and `ds_consume` update only the first active lane's copy and return its old value to every lane, so a second append after the first active lane changes, or a read of the counter by another lane, differs from the hardware. Not measured on hardware. -- [V# bases off a DWORD boundary](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (shader recompiler) - raw accesses use the exact bytes and `s_buffer_load` reads `(base & ~3) + (offset & ~3)`, as measured on gfx1035 (Linux, alignment mode UNALIGNED; the console's mode is unknown). Formatted accesses off their element's alignment throw. Atomics use the byte address `base + offset`, base's low two bits included: measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) with `buffer_atomic_add`, `buffer_atomic_swap` and `buffer_atomic_cmpswap` and bases 0 to 3 bytes past a DWORD boundary with offsets 0 to 7, an address on a DWORD boundary updates that DWORD and returns its old value, and any other address stops the dispatch, which the recompiler reports as an unaligned access fault. -- [Unaligned 32-bit buffer atomics](../../../core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp) (shader recompiler) act on the dword that holds their address, as section 8.1.7 of the [RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) describes for alignment mode DWORD ("the two LSBs of the byte-address are ignored, thus forcing Dword alignment"). The mode comes from `SH_MEM_CONFIG.alignment_mode`, and the console's mode is unknown. MXGP 26 (PPSA30839) sets bits with `buffer_atomic_or` at odd byte offsets, so the console is not in a strict mode, where section 3.11 reports a memory violation for a misaligned atomic. In mode UNALIGNED, which Linux KFD sets, a misaligned atomic stops the dispatch (measured on gfx1031 and gfx1036), so the console is not in that mode either. A 64-bit buffer atomic off an 8-byte boundary still records an unaligned BDA fault: mode DWORD would align it only to 4 bytes, which a host 64-bit atomic cannot address. When the V# base itself is off a DWORD boundary and the containing dword lies before the first aligned dword the buffer view binds to, that atomic is out of bounds and returns 0 without writing. Covered by [BufferAtomicUnaligned.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicUnaligned.cpp). +- [64-bit LDS atomics](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. On RDNA2 (gfx1036) the LDS serialises a 32-bit atomic on a slot's high dword and a 64-bit atomic on that slot: 128 lanes of `ds_add_u32` and 128 lanes of `ds_add_u64` on one slot sum exactly. Only that pair was measured. Every 32-bit LDS atomic of a program that contains a 64-bit LDS atomic therefore takes the same lock, including a 32-bit `flat_atomic_*` whose address lands in the LDS aperture (a 64-bit one takes it already); the flat forms were not measured against a 64-bit atomic either (the other 32-bit LDS atomics, `ds_min/max`, `ds_cmpst`, the float forms, `ds_inc/dec`, `ds_mskor` and `ds_wrxchg`, are extrapolated from `ds_add_u32` and were not measured against a 64-bit atomic); a program without a 64-bit LDS atomic keeps its native 32-bit atomics. `ds_condxchg32_rtn_b64` is two `ds_mskor` halves, each under the lock but not atomic as a pair, so a 64-bit atomic between the two halves is not excluded. Sub-dword writes (`ds_write_b8`, `ds_write_b16` and their `_d16_hi` forms) and plain `ds_read_*`, `ds_write_*`, `ds_append_u32` and `ds_consume_u32` are not taken under the lock, so a plain access racing a 64-bit atomic on the same dwords can see or overwrite one half of it; whether the hardware orders them was not measured. The lock serialises the workgroup's LDS atomics of such a program; the throughput cost was not measured. `VK_KHR_workgroup_memory_explicit_layout` with `shaderSharedInt64Atomics` would make both widths native and is not used. A 64-bit atomic at an address that is 4-byte but not 8-byte aligned faults the wave on gfx1036, as on gfx1035; with the workgroup lock, in device memory and in the invocation-private LDS of other stages alike, the recompiler records the `Unaligned` shader fault with the LDS byte address, leaves both dwords unchanged and returns 0, but the lane and the rest of its wave keep running. When the LDS lives in device memory (compute LDS beyond the device's shared-memory limit) and the device has `shaderBufferInt64Atomics`, no lock is used: a 64-bit atomic is a compare-exchange loop on a 64-bit view of the storage buffer and a 32-bit atomic is a native buffer atomic, so a mixed-width pair on one slot relies on the device ordering both widths on one buffer address; only `ds_add_u32` against `ds_add_u64` was measured, on gfx1036 (AMD Windows driver 32.0.21042.62) and an RTX 4070 SUPER. The lock is not used there because AMD's Windows driver compiles an acquire or release on a storage buffer, at workgroup scope or wider, to `s_barrier`, and those barriers inside the lock's divergent loop pair with the program's own `s_barrier`s. The fence after each LDS atomic compiles to `s_barrier` on that driver as well, so a wave that skips an LDS atomic that other waves of its workgroup execute misses a barrier they reach; its effect was not measured. +- [Image, buffer and global float atomics](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`image_atomic_fmin/fmax/fcmpswap`, the same three `buffer_atomic_*` and `global_atomic_*` forms and their `_x2` forms) follow the rule of the LDS float atomics below, including its operand flush: the 32-bit forms flush both operands to a signed zero when bit 0 of the f32 denormal field is clear, the `_x2` forms by the f16/f64 field, and `fcmpswap` stores a denormal source as given. The `flat_atomic_*` forms take the same path and are not measured. Measured on RDNA2 (gfx1031, RX 6700 XT) with the [hardware oracle](../HW_ORACLE.md) over 20 x 20 f32 and 18 x 18 f64 operand pairs in every value of both fields, at IEEE 0 and 1, in wave32 and wave64, and for the image forms on 32_UINT, 32_SINT and 32_FLOAT surfaces. The console's gfx1013 is not measured. +- [LDS float atomics](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_min/max/cmpst_f32`, `ds_min/max/cmpst_f64` and their `_rtn` forms) follow the hardware measured on RDNA2 (gfx1036) over 14 x 14 operand sets (zeros, ones, infinities, denormals, quiet and signalling NaNs of both signs and with payloads) in IEEE 0 and 1 with every value 0-3 of the type's denormal field: both operands are flushed to a signed zero when bit 0 of the field is clear (0 and 2; 1 and 3 keep them; the f32 field for the f32 forms, the f16/f64 field for the f64 forms; a shader without a known float mode flushes f32 and keeps f64), then a signalling-NaN data operand is stored quieted, else a signalling-NaN old value is stored quieted, else a quiet-NaN old value is replaced by the data, else a quiet-NaN data operand loses, else the ordered compare with -0 below +0 decides, and the flushed value is what is written, also when the old value wins. `cmpst` compares the flushed bits (both zeros equal, NaN never equal) and leaves memory unchanged when they differ. The returned value is always the old value as it was in memory. `IEEE_MODE` has no effect. Not measured: a denormal or signalling-NaN `data1` of `cmpst` (stored as given). `ds_add_f32` and `ds_add_rtn_f32` follow RDNA1 (gfx1010, RX 5700 XT), measured with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime over 1574 operand pairs (signed zeros, denormals, the smallest normals, infinities, quiet and signalling NaNs with payloads, the largest normals, random pairs near the denormal range and pairs that nearly cancel) in all 16 combinations of the f32 denormal field and the f32 round mode, and at IEEE 1 and DX10 clamp 0: both operands are flushed to a signed zero when bit 0 of the field is clear and the result when bit 1 is clear, the sum is rounded to nearest even whatever the round mode, a NaN data operand is stored quieted, else a NaN old value is stored quieted, `+inf + -inf` stores 0xffc00000, and `IEEE_MODE` and `DX10_CLAMP` have no effect. The sum is the host's `OpFAdd` between those flushes, so where the field keeps f32 denormals the result also depends on the host preserving them, which the module does not request. thuanquang measured the same rule on RDNA2 (gfx1031, RX 6700 XT) in #5492, with the same operand classes and 18 modes in wave32 and wave64; the PS5's gfx1013 is not measured. +- [LDS in pixel, vertex-path and fetch programs](../../../core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp) (shader recompiler) - console waves share one LDS region; the host uses a private `Function` array (8192 dwords when unbounded). On NVIDIA proprietary, past about 4 GiB of private memory over every resident invocation that array fails (4090 on 617.42: 5456 ok, 5472 fault at 128×48×32 from `VK_NV_shader_sm_builtins`; 5090: 4096 ok, 6144 timeout). Above `min(4096, 4 GiB / (SMs × warps/SM × subgroup × 4))` it is a storage buffer per lane; other drivers keep `Function`. Only the 4090 sizes are measured. The NVIDIA-only pool fits subgroups in min(2 GiB, storage range) until teardown; excess slots sticky-flag and `WaitIdle` throws (no recycle). Header mapped host-visible/coherent (prefer device-local). Sub-dword writes, 32-bit atomics, `ds_append` and `ds_consume` are non-atomic on the lane's copy; append/consume update only the first active lane. Not measured on hardware. + +- [V# bases off a DWORD boundary](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (shader recompiler) - raw accesses use the exact bytes, as measured on gfx1035 (Linux, alignment mode UNALIGNED), which differs from the console's mode DWORD: on a PS5 (13.40, measured 2026-10-19 by a gfx1013 kernel run from inside a retail title) a raw `buffer_load_dword` with the V# base 1 byte past a dword, or an aligned base and `voffset` 1, returned the aligned dword (0x44332211, not 0x55443322). `s_buffer_load` reads `(base & ~3) + (offset & ~3)`, as on the console. Formatted accesses off their element's alignment throw. Atomics use the byte address `base + offset`, base's low two bits included: measured on RDNA2 (gfx1031, AMD Windows driver 32.0.21045.5002, HIP) with `buffer_atomic_add`, `_swap` and `_cmpswap`, bases 0 to 3 bytes past a DWORD boundary and offsets 0 to 7, an aligned address updates that DWORD and any other stops the dispatch, which the recompiler reports as an unaligned access fault. Closes when: raw buffer accesses drop the two low address bits as on the console +- [Unaligned 32-bit buffer atomics](../../../core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp) (shader recompiler) act on the dword that holds their address, as section 8.1.7 of the [RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) describes for alignment mode DWORD, the mode `SH_MEM_CONFIG.alignment_mode` selects on the console: on a PS5 (13.40, measured 2026-10-19 by a gfx1013 kernel run from inside a retail title) a `buffer_atomic_add` of 1 at byte offset 0x0D incremented the dword at 0x0C and left its neighbours unchanged. A 64-bit buffer atomic off an 8-byte boundary still records an unaligned BDA fault: mode DWORD would align it only to 4 bytes, which a host 64-bit atomic cannot address. When the V# base itself is off a DWORD boundary and the containing dword lies before the first aligned dword the buffer view binds to, that atomic is out of bounds and returns 0 without writing. Covered by [BufferAtomicUnaligned.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicUnaligned.cpp). - [32-bit bitfield masks](../../../core/shader/recompiler/Translation/src/IntegerInstructions.cpp) (`S_BFM_B32`, `V_BFM_B32`) use the truncating 32-bit shift specified by the [RDNA 1.0 ISA](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/instruction-set-architectures/rdna-shader-instruction-set-architecture.pdf), sections 12.1 (opcode 36) and 12.12 (opcode 867). Width and offset use their low five bits. This matches gfx1035 (Linux) for every width/offset pair, with and without upper operand bits set, and both leave SCC unchanged; not measured on the console. - [Vector buffer offsets that are not a multiple of 4](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (shader recompiler) - dword accesses count whole dwords from the V# base, where the hardware reads the exact bytes at base + offset. - [Raw `buffer_load_dwordx2`](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) is one 64-bit access on the hardware (with or without glc), so it never sees half of a `buffer_atomic_swap_x2`. Only a glc load that is 8-byte aligned and in bounds, in a program that has the 64-bit storage view (it uses 64-bit buffer atomics), is read with one 64-bit atomic load; every other dwordx2 load is two dword loads and can tear against a concurrent 64-bit write: loads without glc, and loads in a program without 64-bit buffer atomics while another shader writes the pair with `swap_x2`. @@ -158,7 +177,7 @@ - [2D biased sampling through a one-slice, single-level 3D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) supplies a zero third coordinate and retains the bias operation, with normalized point minification and magnification and no linear mip filtering. Measured on RDNA2 (AMD integrated gfx1036, driver 32.0.21036.18, Windows 11) by running an OpenCL-loaded native kernel with `image_sample_b` DIM 2D against 16x16x1 R32_UINT and RGBA8_UNORM 3D images: biases -16, -0.5, 0, 0.5 and 16 read the (u,v) texel in slice zero, including wrapped coordinates, point min/max reduction and the title's sampler with Z border addressing. Wave32, IEEE off, DX10 clamp on, denormals preserved, round to nearest even, FP16 overflow off; wave64 and the PS5's gfx1013 are not measured. Pixel-stage, direct, non-depth, unpacked samples whose only use is the plain 32-bit bias form are accepted; extra slices, any nonzero mip range, linear or anisotropic filtering and unnormalized coordinates throw. Vulkan readback is checked on NVIDIA RTX 5070 and AMD integrated graphics for positive and negative bias and point weighted-average/min/max reduction. - [FORCE_DEGAMMA on RGBA8_UINT](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) materializes a direct image sampled only by plain biased pixel accesses as RGBA8_SRGB when every paired sampler forces degamma. Measured on AMD integrated RDNA2 gfx1036 (driver 32.0.21036.18, Windows 11, wave32, IEEE off, DX10 clamp on, denormals preserved, round to nearest even, FP16 overflow off): a native `image_sample_b` DIM 2D on a 16x16x1 RGBA8_UINT 3D T# returns integer channels without the bit, but the same floating-point decoded values as RGBA8_UNORM with it, including point min/max reduction and the title's exact sampler. The metadata and image view must both become floating-point sRGB; ignoring the sampler bit or retaining a UINT heap is incorrect. The accepted case requires one slice, mip zero, normalized point sampling and uniform FORCE_DEGAMMA across its samplers. Other image accesses, mixed sampler states, indirect descriptors and other integer formats remain refused. Wave64, other filtering modes and the PS5's gfx1013 are not measured. - The [s_swappc_b64 fetch-shader call at vertex/local/tessellation/mesh entry](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) assumes the called fetch-shader blob ends in `s_setpc_b64` to the link pair (the GNM fetch-shader shape shadPS4's `EmitFetch` copies); the blob is not part of the shader program, so its return closes the pairing by assumption and any in-program reader of the link pair throws. Attribute fetches are inlined from the request's parsed fetch-shader plan through the same `GetAttribute` path as the embedded fetch analyzer, honoring the buffer resource `dst_sel`; an attribute writing more than four registers, or an entry call without a parsed plan, throws. The vertex/instance offset accumulator inside the fetch blob is not replayed for external blobs. -- The scalar aperture sources [src_shared_base, src_shared_limit, src_private_base, src_private_limit](../../../core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp) (core/shader/recompiler, scalar source codes 235 to 238) read as the 64-bit apertures the flat routing uses: a base is the aperture's high dword (`SharedApertureHigh`, `PrivateApertureHigh`) over a low dword of 0, a limit is the same high dword over 0xffffffff. A 64-bit read (`s_mov_b64`, a 64-bit VALU source, a wave64 lane mask) gets the whole value and a 32-bit read gets the low dword. This follows LLVM's `SIRegisterInfo.td` (aperture registers are 64-bit, their low 32 bits are always 0 for a base and -1 for a limit) and `getSegmentAperture` (`AMDGPULegalizerInfo.cpp`, `SIISelLowering.cpp`: a 64-bit read whose high half is the aperture), the KFD's `MAKE_LDS_APP_LIMIT` (limit = base | 0xffffffff) and the gfx1030 and gfx1035 readouts on [#1190](https://github.com/boykopovar/AnyPS5/pull/1190) and [#3803](https://github.com/boykopovar/AnyPS5/issues/3803). Not measured on a console: the PS5's high dwords are the inferred values of the flat aperture bases entry, and the wave is taken to be in 64-bit address mode (PTR32 clear) +- The scalar aperture sources [src_shared_base, src_shared_limit, src_private_base, src_private_limit](../../../core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp) (core/shader/recompiler, scalar source codes 235 to 238) read as the 64-bit apertures the flat routing uses: a base is the aperture's high dword (`SharedApertureHigh`, `PrivateApertureHigh`) over a low dword of 0, a limit is the same high dword over 0xffffffff, as a PS5 (13.40, measured 2026-10-19 by a gfx1013 kernel run from inside a retail title) read them (0x80000000_00000000 / 0x80000000_FFFFFFFF shared, 0x70000000_00000000 / 0x70000000_FFFFFFFF private). A 64-bit read (`s_mov_b64`, a 64-bit VALU source, a wave64 lane mask) gets the whole value and a 32-bit read the low dword, after LLVM's `SIRegisterInfo.td` and `getSegmentAperture` and the gfx1030 and gfx1035 readouts on [#1190](https://github.com/boykopovar/AnyPS5/pull/1190) and [#3803](https://github.com/boykopovar/AnyPS5/issues/3803). The 32-bit read and the wave's address mode (taken to be 64-bit, PTR32 clear) were not measured on the console - [EmitSetAttribute](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - a primitive with a vertex whose clip position is (0,0,0,0) is clipped away through an extra `gl_ClipDistance` element on vertex and tessellation evaluation shaders, matching what a Radeon 610M (RDNA2, RADV) and llvmpipe rasterise; NVIDIA 615.78.08 without it draws the vertex at the viewport centre. Measured for triangles (strip and list) and for a point at (0,0,0,0), which the Radeon 610M does not draw, NVIDIA draws at the viewport centre and llvmpipe at the top-left corner; lines with such a vertex are clipped by the same element and were not measured. Mesh, geometry and the generated barycentric geometry stage do not write it, the generated rect-list and barycentric tessellation stages forward the vertex shader's array, and a vertex or tessellation evaluation shader whose guest clip and cull distances already take all eight does not write it either, so NVIDIA 615.78.08 draws such a vertex at the viewport centre; a device without `shaderClipDistance` cannot compile vertex shaders that write it (the compile throws). The PS5 rasteriser was not measured. - [PERSP_SAMPLE and LINEAR_SAMPLE](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (recompiler) are the barycentrics at the iterated sample, which in a pixel shader the guest does not iterate is taken to be sample 0, inferred from the ancillary's iterated sample number and not measured. They are interpolated with `InterpolateAtOffset`, which [GLSL_EXT_fragment_shader_barycentric](https://github.com/KhronosGroup/GLSL/blob/0099bf83b0288469917fc2e2972cb0a2b74d6711/extensions/ext/GLSL_EXT_fragment_shader_barycentric.txt#L286-L299) names for barycentrics away from the default position, at the sample's offset from the pixel centre, read per pixel of the 2x2 quad from `PA_SC_AA_SAMPLE_LOCS` (the standard pattern unless the draw sets its own): sample 0's in a shader the guest does not iterate, and in an iterated shader the offset of the host's `SampleId` from a table of the guest's locations, because Vulkan leaves the position of plain barycentrics under sample shading implementation-defined (lavapipe uses the pixel centre). That needs the device's `sampleRateShading`; without it, and when every location is the pixel centre (a single-sample draw), they keep the host's default position, the pixel centre once per pixel and an implementation-defined position under sample shading. PERSP_CENTER and LINEAR_CENTER are interpolated at offset 0 in an iterated shader, so they stay at the pixel centre as on the console; the centroid pairs keep the host's default position. With barycentric emulation the offsets apply to the emulated barycentrics. Devices that interpolate attributes on the host instead (no usable fragment-shader barycentrics, as on MoltenVK) keep the default position for all four pairs. - [Pixel positions](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (recompiler) - POS_X_FLOAT and POS_Y_FLOAT follow `SPI_BARYC_CNTL.POS_FLOAT_LOCATION` (bits 17:16) and `POS_FLOAT_ULC` (bit 20), whose positions come from [PAL's GFX9+ register header](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/chip/gfx9_plus_merged_registers.h) and whose meanings from [LLPC](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/RegisterMetadataBuilder.cpp#L767-L779): the pixel centre (0), the iterated sample (2), or the upper-left corner (ULC). Under host sample shading the centre is `floor(FragCoord) + 0.5`, and a non-iterated shader's sample is the centre plus sample 0's offset. The centroid (1) keeps `FragCoord`, the pixel centre or under sample shading the sample, not the centroid of the covered samples. A missing `SPI_BARYC_CNTL` reads as the pixel centre and the reserved location 3 throws. POS_Z_FLOAT and POS_W_FLOAT keep `FragCoord`'s depth and 1/w at the host's position. `PERSP_CENTER_CNTL`, `PERSP_CENTROID_CNTL`, `LINEAR_CENTER_CNTL` and `LINEAR_CENTROID_CNTL` (bits 0, 4, 8 and 12) are not read: PAL and LLPC never set them. @@ -166,13 +185,17 @@ - [Static scalar calls](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) (`s_call_b64` and `s_swappc_b64`) require an ordinary even-aligned SGPR link pair (s0:s1 through s104:s105) and a later `s_setpc_b64` return reading that exact pair. Outside every region of its calls the pair is free to use wherever no return address can reach: a dataflow over the decoded instructions follows each dword of the pair from the calls that write it until a scalar load, SALU or VALU result is known to overwrite it, and an instruction there that may read a dword still holding a return address throws; SOPC compares are exempt, as they only set SCC. A VALU mask covers one SGPR in wave32 and a pair in wave64. When the graph is built without the wave size, the dataflow runs for both: a program that escapes under both throws there, and one that escapes under only one throws when it is translated at that size. - [Static scalar call link pairs](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) each serve one call, or several calls whose regions are disjoint, each call closed by the first return reading the pair after its target. Regions must nest or be disjoint; recursion, other shared links, escaping link values (including overlapping tuples and resource descriptors), invalid targets, interleaved regions, cross-region branches or fall-through, relative SGPR reads and dynamic `s_setpc_b64` in a program with calls throw. The analysis checks all decoded instructions, including unreachable code. Arbitrary indirect calls and general call frames are not implemented. - [Relative vector addressing](../../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (`v_movrels_b32`, `v_movreld_b32`, `v_movrelsd_b32`, `v_movrelsd_2_b32`, `v_swaprel_b32`) keeps every vector register from the lowest base register the program addresses relatively up to the last register the program names in zero-initialised function-scope storage per invocation (`LoadVectorWindow`/`StoreVectorWindow`, one element per register), so an indexed access is one load or store instead of a compare-and-select chain over the window. [`IndexedWindowSlots`](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp) bounds each relative index through the IR (bit-field extracts, masks, shifts, adds, selects, phis, `ReadFirstLane` and the window stores that reach a load); the storage is an array of the slots below the largest bound that is under half the window, and one function variable per slot after it. A constant index uses the array element or the slot's variable; a relative access whose index has no such bound, or lands past the array at run time, indexes the array below its size and otherwise branches through an `OpSwitch` over the slot variables, so the bound only decides speed. When no index has a bound under half the window, the whole window is the array, as with `APS5_NO_WINDOW_NARROWING=1` (a recompiler switch in the shader cache key). Measured on an NVIDIA GeForce RTX 3060 (driver 615.78.08, Linux) with a compute program whose 167-register window an ALU loop reads and writes in full and whose relative accesses take `m0 = s8 & 3`: a 2048-group dispatch took 70.2 ms in wave32 and 123.3 ms in wave64 with the whole window in the array (as before narrowing) and 1.9-2.1 ms and 7.8 ms narrowed; with a second relative access whose index has no bound and lands past the array at run time (index 50 or 166), 113 ms and 5.8-6.0 ms in wave32. In the PPSA08427 (Killing Floor 3) hub it made no measurable difference to presents or to the GPU time of the eight most expensive programs. Whether the host compiler keeps the array and the slot variables in registers or spills them to scratch is not measured. A register in the window the program never wrote reads 0, as an unwritten SSA register does; the console's value is undefined. M0 is unsigned: an index at or past the window reads `v0` and drops its write, and `v_swaprel_b32` writes neither side when either is out of range, as measured on RDNA2 (gfx1035, `agc_driver_relative_indexing`). The window ends at the last register the program names rather than at the shader's VGPR allocation, which the recompiler does not receive, so a relative write past the named registers but inside the allocation is dropped where the hardware keeps it for a later relative read. Killzone: Liberation's PSP vertex program (PPSA11142, 3176 ISA words, 104 relative accesses over 154 registers) went from 649,515 to 392,333 SPIR-V words with this. Pixel programs whose relative span contains the packed ancillary input instead keep that span in SSA and implement indexed operations with compare/select chains. This keeps ancillary definitions visible to field lowering and masked-write proofs; storing the unmodelled whole word in a private array would require composing unknown bits. A fold after masked-select elimination lowers fields whose consumers become visible only after redundant copies are removed. A whole-word use that the masked-select pass keeps gets the composed word described under the packed pixel ancillary word. Both representations include implicit entry inputs in the inferred register limit and retain unsigned M0, inactive-write and DPP update rules; wave32/wave64 native Vulkan readbacks cover relative reads, writes, split-offset swaps, inactive writes, DPP masks and out-of-range accesses, but console comparison and the performance cost of selecting over this span are unverified. +- [Lane spills read back with `v_readlane_b32`](../../../core/shader/recompiler/Optimization/src/ReadLaneEliminator.cpp) (shader recompiler) - not verified for the guest compiler: a relative write (`v_movreld_b32`, `v_movrelsd_b32`, `v_movrelsd_2_b32`, `v_swaprel_b32`) with a computed index is taken to miss a register between a `v_writelane_b32` of a lane and a later `v_readlane_b32` of it, so the read becomes the spilled scalar. That holds when the compiler keeps spill registers out of its relatively indexed arrays. Each such write that a replaced read depends on is checked at run time: one that changes that lane of that register records a fault, and the dispatch or draw fails with `which holds a scalar spilled with v_writelane_b32`, where the console would read the new value ([m0 = 1 counterexample](https://github.com/boykopovar/AnyPS5/pull/6277#issuecomment-6215244790)). An inactive or out-of-range lane writes nothing. A constant-index write of the register still ends the spill. - [s_call_b64](../../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) saves `PC + 4` over `GetShaderBase` and branches to `PC + 4 + signext(SIMM16) * 4`, following [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) sections 3.6.3, 12.2 and 12.3. Forward callees after `s_endpgm` are decoded. Measured on RDNA2 (gfx1036) in wave32 and wave64 with SIMM16 = 3: the link pair receives `PC + 4`, the branch goes to `PC + 4 + signext(SIMM16) * 4`, and `s_setpc_b64` of the link returns to the instruction after the call. Execution on the PS5 (gfx1013) and title compatibility for these bounded scalar calls remain unverified; `s_rfe_b64` still throws in translation. - [BDA anchor halfword reads](../../../core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp) retain volatile byte loads even for noncoherent memory. On RX 6650 XT / RADV Mesa 26.2.4, the valid SPIR-V for two adjacent nonvolatile byte loads returned `0x80a0` for bytes `0xa0, 0xa1`, where lookup-based reads returned `0xa1a0`. Adding the volatile operand makes the same GPU exactness fixture pass, including mixed direct/fallback lanes. The driver transformation responsible has not been isolated; removing this restriction needs equivalent readback coverage on the affected driver. Single-byte reads keep their original access mask. - [BDA span reads](../../../core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp) (shader recompiler) - `read_bda_span` is one selection: one arm calls `load_bda_span`, a `DontInline` function that holds the in-place dword loads and calls nothing, and the other arm is the per-dword byte fallback. Two other shapes of the same reads each hang a group of NVIDIA drivers, where a dispatch never finishes and the device is lost. With the loads emitted in the arm itself, a dispatch whose lanes took both arms hung on an RTX 3060 and an RTX 4060 Laptop GPU (Windows, driver 591.86) and an RTX 5060 (Linux, driver 590.48.01) ([#3291](https://github.com/boykopovar/AnyPS5/issues/3291)). With the loads and the fallback as two selections, the first under the span's condition and the second under its negation, it hung on an RTX 4070 Laptop GPU (Windows, drivers 595.97 and 596.36), an RTX 5080 (Windows, driver 610.62) and an RTX 4090 (Linux, driver 610.57.04) ([#4975](https://github.com/boykopovar/AnyPS5/issues/4975)). The current shape is measured on the RTX 4090 with driver 610.57.04 only, where it does not hang with or without SPIRV-Tools; a shape with the loads in their own `DontInline` function was reported passing on driver 591.86 in #3291. The cause inside the driver is not known. -- [SDWA source selects on `v_cvt_f32_ubyte1`, `v_cvt_f32_ubyte2` and `v_cvt_f32_ubyte3`](../../../core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp) (shader recompiler) - byte N is taken from source 0 after `SRC0_SEL` and `SRC0_SEXT` select and extend it, since SDWA supplies source 0 (RDNA 2 ISA guide 6.8 and 13.3.7) and the opcode reads `S0.u[8N+7:8N]` (12.8). Not measured on hardware. +- [GFX10 scratch address operands](../../../core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp) (shader recompiler) - `core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp` and `core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp` use the SV/SS/ST scratch operand widths described by LLVM FLATInstructions.td at 3eb867f9b63ae2881bbe3c25d3c12842f2bdc55e (operand pseudos L557-L630, GFX10 encoding L2973-L3038). Decoding and IR tests cover the one-dword VGPR offset; PS5 hardware address-result validation remains outstanding. Unsupported scalar registers still throw. +- [SDWA source selects on `v_cvt_f32_ubyte0` to `v_cvt_f32_ubyte3`](../../../core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp) (shader recompiler) - the PS5's gfx1013 is not measured. Byte N is taken from source 0 after `SRC0_SEL` and `SRC0_SEXT` select and extend it, as measured on RDNA2 (gfx1036, Ryzen 7 9800X3D integrated graphics, AMD Windows driver 32.0.21030.31) for all seven source selects with and without `SRC0_SEXT` over 16 sources (zero, all ones, each byte's and word's sign bit set alone, mixed patterns), wave32, IEEE 0, DX10_CLAMP 1, f32 denormals flushed, f16 denormals kept, round to nearest even, FP16_OVFL 0. - [Flag structurization](../../../core/shader/recompiler/ControlFlow/src/Structurizer.cpp) (shader recompiler) - a graph that every structurizer attempt refuses, irreducible and non-terminating graphs included, is rebuilt with one reach flag per block and one again flag per loop (`structurizeWithFlags`): each block keeps one copy and runs behind a guard on its flag, and each strongly connected component becomes a loop whose single latch repeats it while a back edge set its flag. The output has about 8 times the input's blocks and one route variable per block, and ends in an empty Return that never runs. Only blocks that start the top-level order unconditionally run unguarded, so resource planning also sees the static paths that skip a guarded block and can plan extra descriptor candidates for them. The attempts still run first. Shared merge splitting refuses an attempt once the graph has grown past 16 times its block count at the start of splitting (at least 64 blocks); succeeding attempts grew at most 10.6 times on random graphs of up to 128 blocks, and an attempt that would succeed only beyond the budget leaves the graph to a later attempt or to this construction. Splitting that adds one block per round stays within that budget until its 4 rounds per block run out, so the attempts can still take more than 10 s (9 of 2,000 random graphs of up to 128 blocks, 63 s at most). `APS5_FORCE_STRUCTURIZER_FALLBACK=1` builds every graph this way, for tests +- [Shared loop latches](../../../core/shader/recompiler/ControlFlow/src/Structurizer.cpp) (shader recompiler) - a loop whose single latch holds instructions and is reached by one edge gets an empty continue block, so its continue construct is that one block. A latch that several `continue` paths share keeps its instructions in the continue construct, since isolating it would clone them into each path. When such a latch emits more than one SPIR-V block, as a memory or image access does, SPIRV-Cross (MoltenVK) folds the construct into a `for` increment and drops the header's `OpPhi` updates, so the loop never advances on Metal. No title is known to reach this case. - [Scalar buffer descriptors](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) use only the base address, stride and record count for resources accessed exclusively by scalar loads; their fourth word is normalized to zero, following AMD RDNA2 ISA section 7.2.2. Vector and mixed accesses retain descriptor validation. Native Vulkan readbacks cover ignored fourth-word fields on Windows/NVIDIA; their console behavior has not been measured. - [Spanned wave64 compute](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp) (shader recompiler) - `APS5_SPANNED_WAVE=1`, off by default, runs a single-wave wave64 compute program at one lane per invocation on a 32-lane host subgroup and exchanges ballots, shuffles and reductions between the two host subgroups through workgroup memory and barriers. The driver turns it on only where compute subgroups can be pinned at 32 full lanes (`subgroupSizeControl` and `computeFullSubgroups`), passes it as `SpirvTarget::spannedWaves` (part of the cache key and the serialized request), and creates the pipelines of spanned programs with `REQUIRE_FULL_SUBGROUPS`, since the lane number `SubgroupId * 32 + SubgroupLocalInvocationId` needs two full subgroups. Those barriers are correct only where every invocation of the workgroup reaches them, which holds for the guest's wave-uniform control flow. The emulated 64-bit LDS atomics ([LockedLdsUpdate](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp)) run only on the lanes the exec mask keeps, so their lock loop ballots within its own host subgroup, as its elect already does. Any other cross-lane exchange emitted inside per-lane control flow would hang a spanned wave in the same way, and the emitter does not reject one: there is no uniformity check. The only check so far is manual: disassemble the module (`spirv-dis`), list the selection and loop constructs that enclose each `OpControlBarrier`, and read the condition of each enclosing selection. In Ghost of Yōtei's program 0x80003af600 (PPSA26344), 108 of 111 barriers sat only in loops on wave-uniform conditions and 3 inside per-invocation conditionals, all in the lock loop. Other programs and titles have not been scanned. An earlier, cheaper ballot (one barrier instead of three) also lost the device for a reason that is not understood, which may be another barrier placement problem. Measured on an RTX 4090 (Windows): an offline replay of that program and two Yōtei runs that previously lost the device (NVIDIA TDR) now run past it, and [LdsMaskWaterfall.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/LdsMaskWaterfall.cpp) runs both modes and covers a half-inactive wave. Stray (PPSA02101) on Intel ANV and a Demon's Souls (PPSA01342) request replay showed no fault, before this fix -- [Guest memory load guards](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (shader recompiler) - a guest load whose lane is inactive or whose address is out of bounds is still performed and its result replaced by 0 with `OpSelect`, instead of a selection per guard, when the load can be pointed at a location nothing writes: scratch reads its own element 0; storage buffers (direct buffers, GDS) read element 0xfffffffe, which `robustBufferAccess2` turns into a zero load with no memory access, so the extra read cannot race with a write the guarded program would not have raced with ([Vulkan memory model](https://docs.vulkan.org/spec/latest/appendices/memorymodel.html#data-race)). Without `robustBufferAccess2` ([VulkanDevice.cpp](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) enables it when the device has it) those loads keep their selections; LDS loads, stores, atomics, formatted and physical-address (BDA) accesses always do. LDS keeps the branch because NVIDIA's compiler predicates a branched LDS load at no cost, while the select form costs about 2.5 instructions per dword and splits a `ds_read_b128` into four loads (RTX 4060 measurements by @karanvora2599 in #4048); Intel's static counts favour the select only for wide reads, and run time was not measured on either. The aligned high word of a misaligned-capable buffer read re-reads the low word's own element. A selection guard costs four blocks per lane half and the host compiler's time grows with the block count: PPSA11142's merged PSP vertex program (3176 ISA words, wave64) emitted 16,983 blocks and 1,710 conditional branches, 5,907 and 868 with the selects. `agc_guarded_memory_loads` traces the emitted loads in both device modes. AMD's proprietary driver does not get `robustBufferAccess2` ([RobustBufferAccess.hpp](../../../core/libs/prx/libSceAgcDriver/Execution/include/RobustBufferAccess.hpp)), so its loads keep their selections. On an RX 6700 XT with driver 32.0.21045.5002, enabling the feature broke `agc_driver_layered_volume_draw` and `agc_driver_layered_volume_vertex`: the vertex draw left the volume untouched even with the selections kept. `agc_driver_subvector_loop` failed too, with the selected loads: the driver compiled the begin instruction's `exec_lo == 0` as a test of the whole 64-bit mask, so the high half of a wave64 skipped its loop. The begin instruction now tests for an empty mask as `(exec_lo | exec_hi) == 0` rather than `exec_lo == 0 && exec_hi == 0`, and that driver compiles it correctly. The cause of the layered-volume failures was not found. +- [Guest memory load guards](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (shader recompiler) - a guest load whose lane is inactive or whose address is out of bounds is still performed and its result replaced by 0 with `OpSelect`, instead of a selection per guard, when the load can be pointed at a location nothing writes: scratch reads its own element 0; storage buffers (direct buffers, GDS) read element 0xfffffffe, which `robustBufferAccess2` turns into a zero load with no memory access, so the extra read cannot race with a write the guarded program would not have raced with ([Vulkan memory model](https://docs.vulkan.org/spec/latest/appendices/memorymodel.html#data-race)). Without `robustBufferAccess2` ([VulkanDevice.cpp](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) enables it when the device has it) those loads keep their selections; LDS loads, stores, atomics, formatted and physical-address (BDA) accesses always do. LDS keeps the branch because NVIDIA's compiler predicates a branched LDS load at no cost, while the select form costs about 2.5 instructions per dword and splits a `ds_read_b128` into four loads (RTX 4060 measurements by @karanvora2599 in #4048); Intel's static counts favour the select only for wide reads, and run time was not measured on either. The aligned high word of a misaligned-capable buffer read re-reads the low word's own element. A selection guard costs four blocks per lane half and the host compiler's time grows with the block count: PPSA11142's merged PSP vertex program (3176 ISA words, wave64) emitted 16,983 blocks and 1,710 conditional branches, 5,907 and 868 with the selects. `agc_guarded_memory_loads` traces the emitted loads in both device modes. - [Merged hull programs](../../../core/shader/recompiler/Optimization/src/TessellationLowering.cpp) (registered binary type 3) run whole in the hull stage, one invocation per output control point. On the console hull lane i keeps local lane i's registers, which with fewer input than output control points stop matching the patch's control points from i = inputs on; here invocation i holds control point min(i, inputs - 1) of its own patch. A wave-mask branch (`s_cbranch_exec[n]z`, `s_cbranch_vcc[n]z`) tests the invocation's own lane, so it can skip a block, scalar instructions included, that the console runs for the wave; `v_readfirstlane_b32` returns the invocation's own value, not the first active lane's, and another lane's register throws. The register layout (`s3`, `v0`-`v5`, as for the separate halves) is not measured on hardware. s[0:1] hold the hull user-data address (SH 0x102/0x103) when a draw sets it and zero otherwise, so a program reading it unset faults at the null address. - [Wave LDS ordering on AMD's proprietary driver](../../../core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp) (shader recompiler) - there ([SubgroupBarrier.hpp](../../../core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp)) one guest wave's LDS accesses are ordered by a subgroup-scope `OpMemoryBarrier` with no control barrier, relying on a 32-lane subgroup being one hardware wave whose lanes run in lockstep and issue LDS instructions in order, which Vulkan does not guarantee; for LDS kept in device memory the same is assumed of global loads and stores, unmeasured. Driver 32.0.21042.62 (Radeon 0x13c0, RDNA 2) compiled the subgroup-scope `OpControlBarrier` to `s_barrier`, which waves in a branch paired with other waves' barriers (PS5LM Plus). Measured on that driver only ([LdsTreeReduction.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/LdsTreeReduction.cpp)); a Radeon 860M on 32.0.13058.8001 reportedly did not show the fault. +- [LDS atomic fences on AMD's proprietary driver](../../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp) (shader recompiler) - there ([SubgroupBarrier.hpp](../../../core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp)) the fence after an LDS atomic or a sub-dword or misaligned LDS store is a device-scope `OpMemoryBarrier`, which that driver compiles to GL0 and GL1 cache invalidations; the cost is unmeasured. Driver 32.0.21042.62 (Radeon 0x13c0, RDNA 2) compiled the workgroup-scope fence to `s_barrier`, which waves in a branch paired with other waves' barriers (PS5LM Plus with flash attention). Measured on that driver only ([LdsSubwordStoreBranch.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/LdsSubwordStoreBranch.cpp)); a Radeon 860M (RDNA 3.5, 32.0.13058.8001) reportedly gave correct flash-attention output, but the gate covers every version. diff --git a/docs/user/INPUT_MAPPING.md b/docs/user/INPUT_MAPPING.md index 7ab80c3d4f..05c49f2559 100644 --- a/docs/user/INPUT_MAPPING.md +++ b/docs/user/INPUT_MAPPING.md @@ -1,6 +1,6 @@ # Controller, keyboard and mouse input -AnyPS5 accepts SDL-mapped game controllers as well as its built-in keyboard and mouse bindings. Xbox-style controllers and PlayStation controllers recognized by SDL use the standard PS button layout; sticks and analog triggers are passed through, and controllers can be connected or disconnected while the game is running. The first recognized controller is used. AnyPS5 uses its built-in keyboard and mouse bindings when no configuration file is present. To change selected keyboard and mouse bindings, create `anyps5-input.ini` beside the generated game executable. Set `ANYPS5_INPUT_CONFIG` to use a file at another path. Both the game directory and that path may contain any characters, such as `ü` or `日本`. +AnyPS5 accepts SDL-mapped game controllers as well as its built-in keyboard and mouse bindings. Up to 4 controllers are supported simultaneously. The first connected controller is assigned to Player 1 and composed with keyboard and mouse input; additional controllers are sequentially assigned to Players 2, 3, and 4 (matching `libSceUserService` users). Xbox-style controllers and PlayStation controllers recognized by SDL use the standard PS button layout; sticks and analog triggers are passed through, and controllers can be connected or disconnected dynamically while the game is running. AnyPS5 uses its built-in keyboard and mouse bindings when no configuration file is present. To change selected keyboard and mouse bindings, create `anyps5-input.ini` beside the generated game executable. Set `ANYPS5_INPUT_CONFIG` to use a file at another path. Both the game directory and that path may contain any characters, such as `ü` or `日本`. For an Xbox-layout controller, A/B/X/Y map to Cross/Circle/Square/Triangle, LB/RB map to L1/R1, and LT/RT map to analog L2/R2. Start maps to Options. D-pad and stick-click buttons are supported. On a PlayStation controller, touchpad clicks and the positions of up to two fingers on the touchpad are forwarded. On other controllers, View (Back) presses the touchpad at its center. @@ -42,6 +42,7 @@ Without an `anyps5-input.ini`, or for actions the file does not mention, these b | `Shake` | X (reports a swinging acceleration and angular velocity while held) | | `ToggleMouse` | Middle mouse button, F7 | | `ToggleFullscreen` | F11 | +| `TogglePerformanceOverlay` | F10 | | `Screenshot` | F12 | For example, this changes Cross to F or Space, moves the left stick to IJKL, uses the mouse buttons for Square and R2, and keeps all other built-in bindings: @@ -130,3 +131,27 @@ MouseCurve = 1.3 Earlier versions sampled the mouse every 33 ms and tilted the stick at least halfway for any movement. `MouseDeadzoneOffset = 0.5`, `MouseSmoothingMs = 0` and `MouseDecayMs = 33` come close to that behavior. An invalid line reports the file and line number and stops input initialization. If the configured file does not exist or cannot be read, AnyPS5 reports an error. With no `anyps5-input.ini` and no `ANYPS5_INPUT_CONFIG`, the built-in mapping is used. + +## Web configurator + +The [input mapper](../../tools/input-mapper/index.html) edits keyboard, mouse and wheel overrides in the browser. It supports alternate bindings, physical-key capture, search, INI import, preview, copy and download. Invalid imports preserve the current configuration. Invalid bindings block export; duplicate and shared inputs, including retained built-in bindings, produce warnings. Removing every binding for an action restores its built-in inputs. + +From the repository root, with Node.js 22 or later: + +```sh +npm --prefix tools/input-mapper start +``` + +Open `http://127.0.0.1:4173`. Stop the server with Ctrl+C. No package installation is required. Set `PORT` to use another port. Run the tests with: + +```sh +npm --prefix tools/input-mapper test +``` + +Controls, Configuration and Help are separate views; switching views preserves bindings. Search covers all control groups. The group selector supports arrow keys, Home and End. Input and language menus support search, arrow keys and Enter; Escape cancels selection. Menus open beside their fields on larger screens and as bottom sheets on phones. The interface uses system fonts and respects reduced-motion preferences. + +The translation button selects one of 22 interface and FAQ languages. First visits use English; a saved language choice takes priority. Egyptian Arabic appears first in the language list. Arabic, Urdu and Persian use right-to-left layouts. Only the language preference is persisted; copy or download input changes before reloading. + +Keyboard capture converts physical browser key codes to SDL scancode names. Escape cancels capture; select Escape manually to assign it. Composition, ambiguous keys and some browser or system shortcuts require manual selection. Names containing `#` or `;` cannot be exported because those characters start INI comments. The key catalogue follows SDL commit `4b69833bc54abf3dd3288d4aa7afbba527775e5b`. The source-contract tests compare actions, restrictions and defaults with the current native input code. + +For static hosting, serve `index.html`, `styles.css`, `app.js`, `mapping.js`, `keys.js`, `capture.js`, `validation.js`, `i18n.js` and `dropdown.js` from one directory. The configurator is not published on the project's GitHub Pages site yet. diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 672e35bbbf..4a95d67cd3 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -12,6 +12,8 @@ Select or drop multiple app ZIP archives to convert them into one ZIP bundle. Th ## Input and conversion +The SELF unwrapper refuses containers requiring more than 64 MiB of total alignment padding before allocating the output buffer. This limit counts padding across file-backed and empty segments, not segment data; a refusal preserves the input and any existing output file. + Use a clean ELF executable. Place its bundled ELF modules in `sce_module/`, `sce_modules/`, or `prx/` beside the input executable, or under the parent directory selected with `--sce-module-path `. The option accepts absolute paths and paths relative to the current working directory; do not include the `sce_module` directory itself. Only files directly inside the module directories are processed. SELF/fSELF containers are skipped with a warning; unwrap them before relinking if they need to be included. `tools/unwrap_self.py ` does that for a decrypted container: it places the segments at the offsets of the SELF segment records and restores `p_offset % p_align == p_vaddr % p_align`, and it refuses a container whose load segments all look encrypted. The command-line relinker skips an ELF module as a backup copy, with a message, when its file name extends the name of another ELF module in the same directory and both have the same SONAME, such as a dumper's `libX.prx.esbak` or `libX.prxbak` beside an ELF `libX.prx`; a single backup beside a SELF container is still relinked. `prx/` can coexist with either `sce_module/` or `sce_modules/`. Both `sce_module/` and `sce_modules/` present is an error. All three absent is an error unless an explicit `--module-dir` is supplied. ```text @@ -27,6 +29,8 @@ relinker [options] An explicit `--module-dir` also works when the module parent has no `sce_module`, `sce_modules` or `prx` directory. For a homebrew bundle with shared libraries beside its executable, use `--module-dir .`; no empty standard directory is required. Without an explicit module directory, the standard-directory requirement remains. +A module the executable needs that is not converted is looked for under the module parent directory, outside the module directories. Each SELF container or `ET_SCE_DYNAMIC` ELF found there whose name matches, up to the `.prx`, `.sprx`, `.suprx` or `.debug_prx` suffix (ignoring case with `--windows`, as the guest module match does), is reported after the conversion with the `--module-dir` that would include it; the output still refers to a host library of that name. A file that cannot be read is not reported. + Linux output: ```sh @@ -62,20 +66,20 @@ All switches are disabled by default. `unused-filter` defaults to `0`; `--rpath` | `--macos` | Produce an x86-64 Mach-O executable, with bundled modules as Mach-O libraries in `app0/sce_module`. Conflicts with `--windows`. | | `--windows-diagnostics` | Include startup dependency diagnostics. Requires `--windows`. | | `--windows-gui` | Select the Windows GUI subsystem instead of the console subsystem. Requires `--windows`. | -| `--windows-fixed-base
` | Map the lowest ELF load page at this fixed Windows address, given in decimal or hexadecimal (`0x`). Requires `--windows`, a 64 KiB-aligned address from `0x20000` through `0x7fffffff0000`, and a complete image fitting in the user address space. The main executable has no ASLR or base relocations; an occupied address fails to load. Bundled modules keep their normal placement. | +| `--windows-fixed-base
` | Map the lowest ELF load page at this fixed Windows address instead of the preferred `0x400000`, given in decimal or hexadecimal (`0x`). Requires `--windows`, a 64 KiB-aligned address from `0x20000` through `0x7fffffff0000`, and a complete image fitting in the user address space. The main executable has no ASLR or base relocations; an occupied address fails to load. Bundled modules keep their normal placement. | | `--trace-imports` | Pass each function import through the libc.prx call tracer, see [tracing system library calls](#tracing-system-library-calls). Windows and Linux output only; conflicts with `--lazy-binding`. | -| `--linux-fixed-base
` | Load the Linux executable at this fixed address, given in decimal or hexadecimal (`0x`): every virtual address of the image is offset by it, so virtual address 0 is placed at the address, as the console places an executable. Requires a 64 KiB-aligned address from `0x10000` through `0x7fffffff0000` that is a multiple of every `PT_LOAD` alignment, and an image that ends in the user address space. The executable is written as `ET_EXEC`, without ASLR. Bundled modules keep their normal placement. Conflicts with `--windows`, `--macos` and `--lazy-binding`. | +| `--linux-fixed-base
` | Load the Linux executable at this fixed address instead of the default `0x400000`, given in decimal or hexadecimal (`0x`): every virtual address of the image is offset by it, so virtual address 0 is placed at the address, as the console places an executable. Requires a 64 KiB-aligned address from `0x10000` through `0x7fffffff0000` that is a multiple of every `PT_LOAD` alignment, and an image that ends in the user address space. The executable is written as `ET_EXEC`, without ASLR, as it is at the default address. Bundled modules keep their normal placement. Conflicts with `--windows`, `--macos` and `--lazy-binding`. | | `--macos-fixed-base
` | Map the lowest ELF load page of the macOS executable at this fixed address, given in decimal or hexadecimal (`0x`). Requires `--macos`, a 64 KiB-aligned address from `0x20000` through `0x7fffffff0000`, and an image that ends in the macOS user address space. `__PAGEZERO` ends 64 KiB below the address and the Mach-O header occupies those 64 KiB. The executable has no ASLR; an address the process cannot map fails to load. Bundled modules keep their normal placement. | | `--to-intel` | Convert supported AMD-only instructions in the executable and bundled modules. Unsupported instructions or unreachable conversion stubs cause an error. | -| `--to-rosetta` | Convert as `--to-intel` does and also lower what Rosetta lacks: ANDN becomes scalar NOT/AND, RDSEED becomes RDRAND, CLWB a NOP, and RDPID and the ECX of RDTSCP return processor 0. Implies `--to-intel`. Independent of `--macos`: `--macos` alone leaves these instructions as they are. Conflicts with `--windows`. | +| `--to-rosetta` | Convert as `--to-intel` does and also lower what Rosetta lacks: ANDN becomes scalar NOT/AND, RDSEED becomes RDRAND, CLWB a NOP, XSAVEOPT becomes XSAVE, and RDPID and the ECX of RDTSCP return processor 0. Implies `--to-intel`. Independent of `--macos`: `--macos` alone leaves these instructions as they are. Conflicts with `--windows`. | | `unused-filter=0` | Keep all imported NID references. | | `unused-filter=1` | Filter unused non-PLT imports using control-flow and GOT access analysis, treating address-taken code as reachable; preserve PLT imports. | | `unused-filter=2` | Apply strict unused-import analysis and compact the PLT. Unsupported analysis cases cause an error. | | `--registry` | Write `.registry.json` beside the output executable, and `..registry.json` for each converted bundled module. | -| `--rpath ` | Set the system library search path. Quote `$ORIGIN` to prevent shell expansion, for example `--rpath '$ORIGIN/libs'` in Bash or PowerShell. Linux guest modules require an absolute path or a path beginning with `$ORIGIN`. Windows requires a nonempty ASCII path and supports `$ORIGIN` as the executable directory. | +| `--rpath ` | Set the system library search path. Quote `$ORIGIN` to prevent shell expansion, for example `--rpath '$ORIGIN/libs'` in Bash or PowerShell. On Linux every colon-separated entry must be nonempty, and with guest modules each must be absolute or begin with `$ORIGIN`. Windows requires a nonempty ASCII path and supports `$ORIGIN` as the executable directory. | | `--autorun` | Run the output after conversion, print its exit code, and wait for Enter. Adds executable permissions for Linux output. Requires the target OS and prepared runtime layout. | | `--sce-module-path ` | Parent of the module directories, not the module directory itself. Default: input ELF parent; relative input paths resolve from the process working directory. Explicit absolute path: used directly. Explicit relative path (including `.` and `..`): relative to the process working directory. Output path has no effect. Requires one nonempty value; repeats, nonexistent or nondirectory paths, access failures, and combination with `--skip-sce-module` are errors. | -| `--module-dir ` | Relink the ELF modules of a directory under the module parent directory, besides `sce_module`/`sce_modules`/`prx`; outputs keep the relative path. A module another module needs is initialized at startup, and on Linux and macOS also one the executable needs; any other when the title loads it with `sceKernelLoadStartModule`, with its start arguments (for example Unity plugins in `Media/Plugins`, or `mono-ps4.sprx` on Windows). Repeatable; `.` is the parent itself, without the input ELF. Conflicts with `--skip-sce-module`. | +| `--module-dir ` | Relink the ELF modules of a directory under the module parent directory, besides `sce_module`/`sce_modules`/`prx`; outputs keep the relative path. Needed modules are mapped at startup. On Linux, modules in these extra directories initialize only when explicitly loaded, with the arguments from `sceKernelLoadStartModule` (for example Unity plugins in `Media/Plugins`). A module another module needs initializes at startup on Windows and macOS; one only the executable needs initializes when explicitly loaded on Windows and at startup on macOS. Other modules initialize when loaded. Repeatable; `.` is the parent itself, without the input ELF. Conflicts with `--skip-sce-module`. | | `--executable ` | Also convert another executable of the title, which it starts with `sceSystemServiceLoadExec`; the path is relative to the module parent directory, for example `picker/picker.bin`. It is written beside the output, named after the path with `/` replaced by `.` and given the output's extension (`picker.picker.bin.exe`), and uses the same `libs/`, `app0/` and guest modules. It is an error if a guest module converts differently for it. Repeatable. Conflicts with `--skip-sce-module`. | | `--patches ` | Apply the byte patches of this file during conversion, see [game patches](#game-patches). Overrides `relinker-patches.toml` discovery. Conflicts with `--no-patches`. | | `--no-patches` | Do not apply patches, and do not load `relinker-patches.toml` from the input directory. | @@ -85,7 +89,8 @@ All switches are disabled by default. `unused-filter` defaults to `0`; `--rpath` | `--exclude-sce-module ` | Deprecated. Exclude a bundled module by exact filename, not path. Repeat for multiple files; a missing filename is an error. Conflicts with `--skip-sce-module`. | | `--skip-syscall-check` | Deprecated. Disable syscall scanning in the executable and bundled modules. | | `--trap-sdk-syscall-stubs` | Replace the exact ps5-payload-dev generic and numbered syscall wrapper stubs and the recognized inline mmap/mprotect sequences from SDK libc/mman.c with `UD2`, so unused wrappers do not block relinking. Other syscall instructions still cause an error. If a trapped wrapper runs, it raises an illegal-instruction exception. For debugging only. | -| `--lazy-binding` | Deprecated. Enable lazy symbol binding instead of eager binding. Incompatible with bundled ELF modules. | +| `--frame-red-zone` | Windows only: give leaf functions that keep values below RSP (the System V red zone) a frame, so an exception Windows delivers on a guest thread cannot overwrite them. Covers leaves with a `push rbp; mov rbp, rsp; push ...` prologue and leaves that only push registers, with or without a `sub` allocation after the pushes; in those, RSP is lowered only as far as the leaf reaches below it and their RSP operands are moved with it, in place or, when their encoding cannot hold the moved displacement, in a stub. Function bounds come from the executable's `.eh_frame`, and the relinker stops with an error when it has none. The relinker prints how many it framed and why it skipped the others, and refuses a game patch over a framed site. | +| `--lazy-binding` | Deprecated. Enable lazy symbol binding instead of eager binding. Incompatible with bundled ELF modules. The Linux executable stays position independent (`ET_DYN`) instead of being placed at `0x400000`. | Specify `unused-filter=0|1|2` without `--`, at most once. Unknown options and extra positional arguments are errors. Use `relinker --help` to print the usage syntax to stdout and exit with code `0` without converting files. Invoking `relinker` without arguments prints the usage syntax to stderr and exits with code `1`. @@ -97,6 +102,34 @@ The `--skip-sce-module`, `--exclude-sce-module `, `--skip-syscall-check`, A patch file changes bytes of the input executable or of a bundled module before conversion, for a title that hard-codes a value the host cannot meet. The relinker checks the bytes it replaces, so a patch for another version of the title fails instead of corrupting it. With no patch file the output is unchanged. +The advisor requires Python 3.11 or newer. To check a local patch catalog, run: + +```sh +python3 tools/suggest_patches.py --catalog --param-json --input --relinker [--sce-module-path ] [--module-dir ]... [--executable ]... [--skip-sce-module] [--timeout-seconds ] [--list-verified] +``` + +The advisor reads `titleId` and `contentVersion` from the specified `param.json`, checks the SHA-256 of every file targeted by a candidate patch, then calls the relinker specified by `--relinker` to run `--verify-patches`. A relative path such as `--relinker ./relinker` resolves to that local file. The explicit metadata path also works when `param.json` and the unwrapped ELF files are stored separately. The catalog directory must contain an `index.json` with `format: 1` and an `entries` array. Each entry has `titleId`, `contentVersion`, a relative `patch` path, and `targets` mapping every patch target to its SHA-256. Hexadecimal digests are case-insensitive. For example: + +```json +{ + "format": 1, + "entries": [{ + "titleId": "TEST00001", + "contentVersion": "01.000.000", + "patch": "patch.toml", + "targets": {"Media/Modules/module.prx": ""} + }] +} +``` + +The advisor validates patch files only for entries matching the requested title and version. It reports and skips a malformed matching entry or a relinker verification error, so another valid entry can still be recommended. Verification times out after 120 seconds per entry by default; use `--timeout-seconds ` to change the limit. For synthetic inputs without system modules, use `--skip-sce-module` if conversion also uses it; this option conflicts with `--sce-module-path`. No matching entry or no verified patch exits `1`; invalid input or no verified patch after a relinker error exits `2`. If multiple entries verify, the advisor recommends none because it cannot choose or combine them, and points to `--list-verified` to inspect the matches. The tool does not fetch or apply patches. A recommendation prints `--patches` and the module and executable options to use when converting. Keep catalog patch files outside the input executable's directory to avoid automatic `relinker-patches.toml` discovery. + +With `--list-verified`, the advisor prints every entry that passes metadata, hash and relinker checks, in catalog order. Each result includes its catalog number, patch file, target hashes and conversion options. It exits `0` when at least one entry verifies, even if another entry was rejected; with no verified entries, the existing `1` or `2` exit status still applies. Without this option, a single verified entry is recommended and multiple verified entries remain ambiguous. The listing tells users to choose one patch file. It does not combine patch files or apply them during conversion; review the chosen patch before applying it. + +The target hashes identify the exact unwrapped files changed by a patch, which might not include the main executable. Different dump tools could produce different bytes for the same title version; the advisor then rejects a patch even if it would work. It has not been confirmed that all dump tools produce identical files. The hashes do not authenticate a catalog or establish that a patch is safe or that the title is fully playable. Use catalogs from sources you trust and review the patch before applying it. + +If the patch targets another title executable, pass `--executable ` to the advisor for each additional executable, using paths relative to the module parent directory, such as `picker/picker.bin`. The advisor hashes those target files and forwards the same options to `relinker --verify-patches`. Use the same `--executable` options when converting with `--patches`. + `relinker-patches.toml` in the directory of the input executable is loaded automatically and announced as `Patches: `. `--patches ` names another file, and `--no-patches` loads none. Each applied patch is reported: ``` @@ -120,11 +153,11 @@ align = 0x1000 description = "pool size" ``` -`format = 1` is required. The top-level keys `relinker_version`, `relinker_commit`, `config`, `config_sha256`, `main_target`, `settings` and `settings_sha256` are accepted in any file; only `main_target` is used, by `--verify-output`. Any other top-level key is an error. +`format = 1` is required. The top-level keys `relinker_version`, `relinker_commit`, `config`, `config_sha256`, `main_target`, `linux_base`, `settings` and `settings_sha256` are accepted in any file; only `main_target` and `linux_base` are used, by `--verify-output`. Any other top-level key is an error. | Entry key | Meaning | |-----------|---------| -| `name` | Optional. A setting name of `[a-z0-9_]`, unique within the file and not one of the reserved top-level key names (`format` and the seven keys above), used by a later `--set` option. Shown in the progress line, the manifest and the verify report. | +| `name` | Optional. A setting name of `[a-z0-9_]`, unique within the file and not one of the reserved top-level key names (`format` and the eight keys above), used by a later `--set` option. Shown in the progress line, the manifest and the verify report. | | `target` | Required. The main executable's file name, or for `--executable` files and bundled modules the path relative to the module parent directory, for example `sce_module/libc.prx`. | | `va` | Required. The virtual address of the ELF as a disassembler shows it. | | `type` | Optional: `u8 u16 u32 u64 i8 i16 i32 i64`. Then `expected`, `value`, `min`, `max` and `align` are integers, range-checked and written little-endian. Without it, `expected` and `value` are hexadecimal byte strings of the same length. | @@ -137,19 +170,19 @@ x86 `mov r/m64, imm32` and `add`/`sub`/`cmp r/m64, imm32` sign-extend their imme Entries apply in file order. A patch range must lie in the file bytes of one `PT_LOAD` and outside the ELF header and program header table. The conversion fails before writing any output if bytes differ from `expected`, a range is not valid, two entries overlap, a target matches no file or more than one file, or a range overlaps bytes the relinker itself rewrites (`--to-intel` replacements and trampolines, relinker patches, syscall traps) or the 8-byte target of a relocation of the file. -When at least one patch is applied, `.patches.toml` is written beside the output. It lists every applied entry for every converted file, with `main_target` and `config` (the patch file path in forward-slash form, reduced to its file name when the path holds a `"`, backslash or control character; the conversion fails before writing any output when even that cannot be held); typed entries keep their `type`, integers and `min`, `max` and `align`, and `--patches` accepts it to reproduce the conversion. The manifest must not replace the patch file it was read from. +When at least one patch is applied, `.patches.toml` is written beside the output. It lists every applied entry for every converted file, with `main_target`, `linux_base` (the address a Linux `ET_EXEC` output is placed at) and `config` (the patch file path in forward-slash form, reduced to its file name when the path holds a `"`, backslash or control character; the conversion fails before writing any output when even that cannot be held); typed entries keep their `type`, integers and `min`, `max` and `align`, and `--patches` accepts it to reproduce the conversion. The manifest must not replace the patch file it was read from. `--verify-patches ` writes nothing and exits `0` when no entry is a mismatch. Without `--verify-output`, `` is the input executable of any type, modules are found as in a conversion (module options and `--executable` apply), and each entry is `applicable` (the input holds `expected`) or `mismatch`. A target that matches nothing is an error that suggests `--verify-output`. -With `--verify-output`, `` is a converted Linux ELF (`ET_DYN`); `ET_EXEC` output from `--linux-fixed-base`, PE and Mach-O outputs are refused. The file must have `main_target`, so verify the generated manifest, not a hand-written file. The entry whose target is `main_target` is checked against ``, and entries for the `--executable` strings given are checked against the output file derived as in a conversion; an entry holding `value` is `applied`. Every other entry is `not-checked (module, or executable not named with --executable)`, including a mistyped module target. Two targets with the same name, such as an `--executable` equal to `main_target`, are an `ambiguous patch target` error. Module discovery options are rejected with `--verify-output`. +With `--verify-output`, `` is a converted Linux ELF; PE and Mach-O outputs are refused. The file must have `main_target`, so verify the generated manifest, not a hand-written file. Entries are checked at their `va` plus the manifest's `linux_base` in an `ET_EXEC` output, which requires `linux_base`; an `ET_DYN` output, from `--lazy-binding`, requires a manifest without it. The entry whose target is `main_target` is checked against ``, and entries for the `--executable` strings given are checked against the output file derived as in a conversion; an entry holding `value` is `applied`. Every other entry is `not-checked (module, or executable not named with --executable)`, including a mistyped module target. Two targets with the same name, such as an `--executable` equal to `main_target`, are an `ambiguous patch target` error. Module discovery options are rejected with `--verify-output`. Verify mode rejects the conversion options (`--windows`, `--macos`, `--to-intel`, `--rpath`, `--patches`, `--no-patches` and the like) and a second path. ## Runtime layout -For an executable that assumes its image starts at `0x400000`, use `relinker --windows --windows-fixed-base 0x400000 source/input.elf app.exe`. This maps its lowest ELF load page there; the PE headers occupy the preceding 64 KiB. Use this option only when the executable requires an exact address. Without it, the Windows executable has no `DYNAMIC_BASE` and loads at its preferred base `0x140000000`; relocations are kept, so Windows relocates it if that range is taken. Modules keep `DYNAMIC_BASE`. This option does not change macOS output. +The Windows executable has no `DYNAMIC_BASE` and prefers to load with its lowest ELF load page at `0x400000`, where Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) expect their image when they read their `app0/imports.txt`; the PE headers occupy the preceding 64 KiB (preferred base `0x3f0000`). Relocations are kept, so Windows can relocate it if that range is taken. When the executable requires an exact address, use `relinker --windows --windows-fixed-base
source/input.elf app.exe`, which maps the lowest ELF load page at that address without relocations, so an occupied address fails to load. Modules keep `DYNAMIC_BASE`. This option does not change macOS output. -For an executable that assumes its image starts at `0x400000`, as Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) do when they read their `app0/imports.txt`, use `relinker --linux-fixed-base 0x400000 source/input.elf app.elf` on Linux, or `relinker --macos --macos-fixed-base 0x400000 source/input.elf app` on macOS. Use these options only when the executable requires a fixed address. Without them, Linux output is a position-independent executable as before, and macOS output places the lowest ELF load page at `0x140010000`. +For an executable that assumes its image starts at `0x400000`, as Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) do when they read their `app0/imports.txt`, use `relinker --macos --macos-fixed-base 0x400000 source/input.elf app` on macOS. Use this option only when the executable requires a fixed address; without it, macOS output places the lowest ELF load page at `0x140010000`. Linux output places virtual address 0 at `0x400000` without an option, inside the 40-bit window that a title's own `sceLibcMspaceCreate` checks; `--linux-fixed-base` selects another address, and with `--lazy-binding` the executable stays position independent. The same base is applied to executables selected with `--executable`; bundled PRX modules are not rebased. @@ -166,15 +199,15 @@ app0/ ``` -Use `sce_modules/` or `prx/` instead of `sce_module/` if that is the input directory name. Relinker preserves each module's directory under `app0/` and prints its exact path. Place app resources in `app0/` separately, including the title's original `eboot.bin`: titles test `/app0/eboot.bin` to find their own directory, as it is always present on the console (ProsperoTV, ProsperoRadio, ProsperoLight and ProsperoLichess fall back to an empty `/data/homebrew/` and fail to load their fonts without it). Only the converted executable runs. On Linux, `app0/` must not contain symbolic links: a directory listing gives the title the link's own type, not the type of the file or directory it points to, so a title that walks its directories skips a linked directory. Use copies or hard links. Copy the built system libraries from `build/core/libs/libs/*.prx` into `libs/`; use libraries built for the target OS. A custom `--rpath` changes the system library location. +Use `sce_modules/` or `prx/` instead of `sce_module/` if that is the input directory name. Relinker preserves each module's directory under `app0/` and prints its exact path. Place app resources in `app0/` separately, including the title's original `eboot.bin`: titles test `/app0/eboot.bin` to find their own directory, as it is always present on the console (ProsperoTV, ProsperoRadio, ProsperoLight and ProsperoLichess fall back to an empty `/data/homebrew/<title id>` and fail to load their fonts without it). Only the converted executable runs. On Linux, a symbolic link inside the `app0/` tree is listed with the type of the file or directory it points to, so a linked directory is walked like a real one. Links inside a linked directory whose target is outside `app0/` keep the link type; for nested trees, link only the top-level entries or use copies or hard links. Copy the built system libraries from `build/core/libs/libs/*.prx` into `libs/`; use libraries built for the target OS. A custom `--rpath` changes the system library location. Some applications also need their original `app0/sce_sys/param.json` at runtime. AnyPS5 reads this file for the title ID and display name; it must contain a non-empty `titleId` string and a non-empty `localizedParameters` object with a language entry that has a non-empty `titleName`. It selects the entry named by `defaultLanguage` (inside `localizedParameters`, or at the top level); if that entry is absent, it tries `en-US`, then the first object-valued language entry. Preserve the values from the title's own metadata and place the file under the same `app0/` directory as the other app resources. At startup, its presence in the working directory creates `data/`; a non-zero `downloadDataSize` also creates `download0/`. Without the file, neither directory is created. If it is missing or invalid, metadata calls such as title/window setup can fail with a `param.json` error. Resources can also be packed into `app0.zar` beside `app0/`, a [ZArchive](https://github.com/Exzap/ZArchive) whose root is the `app0/` directory. A file present in `app0/` is read from there and any other file from the archive; the executable, `sce_module/` and `sce_sys/` stay in `app0/`. The archive is read only through `sceKernelOpen`/`open`, the read, seek and stat calls on its descriptors and `sceKernelStat`; other calls on an archived path, listing a directory that the archive also contains and opening a file in it for writing fail as described in [technical debt](../dev/debt/libkernel.md). An archive that cannot be opened stops the title at its first file access. -Use the generated files printed as `Guest module:` for bundled title modules. `libs/` is for AnyPS5 system libraries, not the original PS5 `.prx` files. Placing an original PS5 module in `libs/` on Windows makes Windows try to load it as a DLL and can fail with error 193 (not a valid Win32 application). +For bundled title modules, use the `.guest.prx` files the relinker writes into `app0/` and lists in the layout it prints. `libs/` is for AnyPS5 system libraries, not the original PS5 `.prx` files. Placing an original PS5 module in `libs/` on Windows makes Windows try to load it as a DLL and can fail with error 193 (not a valid Win32 application). A module the title loads while it runs that was not relinked, such as one in a directory not given to `--module-dir`, is not loaded either: the title gets an error and continues, and the runtime prints `[module] dlopen: <path> was not relinked: ...` once for each file. Relink that module's directory with `--module-dir <dir>`. -On Windows, a self-built `libs/` also needs `libgcc_s_seh-1.dll`, `libstdc++-6.dll` and `libwinpthread-1.dll` from the `mingw64/bin` directory of the toolchain that built the libraries; the release archives already contain them. The libraries are loaded without searching `PATH`, so a copy elsewhere on the system is not used, and a library that needs one of them fails with error 126 (the specified module could not be found) although its `.prx` file is present. +On Windows, a self-built `libs/` also needs `libgcc_s_seh-1.dll`, `libstdc++-6.dll` and `libwinpthread-1.dll` from the `mingw64/bin` directory of the toolchain that built the libraries; the release archives already contain them. The libraries are loaded without searching `PATH`, so a copy elsewhere on the system is not used, and a library that needs one of them fails with error 126 (the specified module could not be found) although its `.prx` file is present. With `APS5_MIMALLOC` set, the title loads `mimalloc.dll` and `mimalloc-redirect.dll` from `libs/` and allocates through mimalloc. Copy both from `build/core/libs/libs`; the release archives contain them. Without them the title keeps the C runtime's heap and prints nothing. On Windows, direct memory (`sceKernelAllocateDirectMemory`, up to 13824 MiB per title) is committed when the title maps it, not when the title allocates it or first uses its pages. The system commit limit (installed memory plus page file size, the second value of Committed in Task Manager) must cover the mapped direct memory together with all other committed memory. Otherwise `sceKernelMapDirectMemory` throws `replace guest placeholder with resident direct memory` with the Windows error (`commit direct memory pages` when a page is mapped at a second address, `keep unmapped direct memory` when it is unmapped); enlarge the page file or close other applications. @@ -216,11 +249,12 @@ python3 tools/pad_input.py /tmp/anyps5-pad release python3 tools/pad_input.py /tmp/anyps5-pad release --left-stick 128 0 --hold-ms 2000 python3 tools/pad_input.py /tmp/anyps5-pad cross --left-stick 255 128 --right-stick 0 128 python3 tools/pad_input.py /tmp/anyps5-pad touch-right +python3 tools/pad_input.py /tmp/anyps5-pad l1+r1 ``` For timed button sequences, see [Controller timelines](INPUT_MAPPING.md#controller-timelines). -Run the helper in a second terminal. Commands are polled at most every 100 ms during controller reads, and their buttons add to physical controller input. A press lasts 300 ms by default, starting when the application reads it, and releases automatically; `--hold-ms` accepts 1–5000 ms. `--left-stick X Y` and `--right-stick X Y` hold the sticks for the same time: each axis is 0-255, 128 is the centre, X grows to the right and Y grows downward. While such a command is active both sticks come from it instead of the physical controller, and a stick that was not named stays centred. `touch-left` and `touch-right` press the touchpad with a finger on its left or right half, as the `TouchLeft` and `TouchRight` keys do; in the command file they are bits `0x200000` and `0x400000` of the button field. Wait for the application to observe a release before sending another press. The file holds only the latest command; rapid commands can replace one another. On POSIX, replacing an existing command file retains its permission mode; a privileged helper also retains its owner and group so the game user can still read it. Commands left over when the application first reads the controller are ignored. Without the environment variable, file input is disabled. +Run the helper in a second terminal. Commands are polled at most every 100 ms during controller reads, and their buttons add to physical controller input. A press lasts 300 ms by default, starting when the application reads it, and releases automatically; `--hold-ms` accepts 1–5000 ms. `--left-stick X Y` and `--right-stick X Y` hold the sticks for the same time: each axis is 0-255, 128 is the centre, X grows to the right and Y grows downward. While such a command is active both sticks come from it instead of the physical controller, and a stick that was not named stays centred. `touch-left` and `touch-right` press the touchpad with a finger on its left or right half, as the `TouchLeft` and `TouchRight` keys do; in the command file they are bits `0x200000` and `0x400000` of the button field. Button names joined by `+`, such as `l1+r1`, are pressed together in one command; `release` cannot be joined. Wait for the application to observe a release before sending another press. The file holds only the latest command; rapid commands can replace one another. On POSIX, replacing an existing command file retains its permission mode; a privileged helper also retains its owner and group so the game user can still read it. Commands left over when the application first reads the controller are ignored. Without the environment variable, file input is disabled. ### Video-out and pad telemetry @@ -240,9 +274,9 @@ A game unlocks a trophy by posting a `_UnlockTrophy` event with an int32 `_troph The trophy data comes from the game's own `app0/sce_sys/trophy2/trophyNN.ucp`, where `NN` is the service label (`00` for most games). The trophy queries report its names, descriptions and rewards in the console language (`ANYPS5_LANGUAGE`, see above), or in English (United States) or the data's default language when it has no text in that language, and its grades, groups, hidden flags, number of trophies and grade counts. A platinum trophy unlocks as soon as every trophy that names it in `platinumTrophyId` is unlocked. The first trophy query or event prints what was read, such as `[trophy] /app0/sce_sys/trophy2/trophy00.ucp: 52 trophies, 1 group, texts in en-US`. -Many games unlock trophies through statistics instead: `app0/sce_sys/uds/udsNN.ucp` names, for each event, the property whose value becomes a statistic, and a trophy's `unlockCondition` compares a statistic with a target. The latest value of each statistic is stored in `_trophy/<user id>_<service label>.stats.txt`, one `<stat id> <value>` line per statistic, and is kept across runs; delete it together with the unlock file to start over. A progressive trophy reports its statistic as progress. Only statistics that keep the latest value (`"aggregation":"latest"`) as `uint32` or `uint64`, and rules that copy one property of an event chosen by its name (`$.name` or `$.object.name`), are applied; the others are listed in `[trophy]` lines when the file is read. +Many games unlock trophies through statistics instead: `app0/sce_sys/uds/udsNN.ucp` names, for each event, the property whose value becomes a statistic, and a trophy's `unlockCondition` compares a statistic with a target. The latest value of each statistic is stored in `_trophy/<user id>_<service label>.stats.txt`, one `<stat id> <value>` line per statistic, and is kept across runs; delete it together with the unlock file to start over. A progressive trophy reports its statistic as progress. Only statistics that keep the latest (`"aggregation":"latest"`) or the largest value (`"max"`) as `uint32`, `uint64` or `int32`, and rules that copy one property of an event chosen by its name (`$.name` or `$.object.name`), optionally only when one property equals an integer (`_UpdateTrophyProgress` by its `_trophy_id`), are applied; the others are listed in `[trophy]` lines when the file is read. -`APS5_TRACE_UDS=1` prints every posted event, such as `[uds] context 1 posted _UnlockTrophy {"_trophy_id":12}`, every statistic an event sets, such as `[uds] stat 10 of user 268435456 is 10 (COLLECT $.counter)`, and a `[uds]` line for a missing `trophyNN.ucp` or `udsNN.ucp`. An event that is not used prints the reason in a `[trophy] ignored` line: an `_UnlockTrophy` event without an int32 `_trophy_id` of 0 or more, or posted to a context that does not exist; the first `_UpdateTrophyProgress` event of each trophy, because that event is not interpreted; and, once per rule, an event whose property is missing, negative, not an integer or outside the statistic's range. +`APS5_TRACE_UDS=1` prints every posted event, such as `[uds] context 1 posted _UnlockTrophy {"_trophy_id":12}`, every statistic an event sets, such as `[uds] stat 10 of user 268435456 is 10 (COLLECT $.counter)`, and a `[uds]` line for a missing `trophyNN.ucp` or `udsNN.ucp`. An event that is not used prints the reason in a `[trophy] ignored` line: an `_UnlockTrophy` event without an int32 `_trophy_id` of 0 or more, or posted to a context that does not exist; and, once per rule, an event whose property is missing, negative, not an integer or outside the statistic's range. A missing or malformed `trophyNN.ucp` or `udsNN.ucp` turns off only what it provides, and a malformed one prints the reason once: without trophy data the queries report placeholders and statistics unlock nothing, and without statistics rules events change no statistic. Icons are not read: `sceNpTrophy2GetGameIcon`, `sceNpTrophy2GetGroupIcon`, `sceNpTrophy2GetTrophyIcon` and `sceNpTrophy2GetRewardIcon` stop the game with an error. So does an unlock or statistics file in `_trophy/` that is damaged (a line that is not a new `<id> <value>` pair, such as a line an edit left blank or ended with a carriage return) or cannot be read or written. No notification is shown, and callbacks registered with `sceNpTrophy2RegisterUnlockCallback` are not called. @@ -288,10 +322,16 @@ Recompiled shaders and the Vulkan pipeline cache are kept in a `shader_cache/` d ### Pipeline journal -Besides the recompiled shaders, the shader cache directory holds `pipelines.journal`: every Vulkan graphics pipeline the pipeline store kept and every compute pipeline a run built, with the specialized shader modules they were built from. The next run of the title builds those pipelines on background threads while it loads, so a draw or dispatch the earlier run already made does not compile its pipeline when it is first reached. The journal belongs to the GPU and driver that wrote it, like the driver's pipeline cache file next to it: a run on another GPU or driver version starts it over, and the driver's own pipeline cache decides how fast each pipeline is rebuilt. Two runs sharing the directory take turns on `pipelines.journal.lock`; a run that cannot lock it records nothing. `APS5_NO_PIPELINE_JOURNAL=1` disables recording and replay; `APS5_PIPELINE_JOURNAL_THREADS` sets the replay threads (half the CPU threads by default; `0` records without replaying), `APS5_PIPELINE_JOURNAL_ENTRIES` how many pipelines are replayed (the first recorded, which a run reaches first) and `APS5_PIPELINE_STORE_ENTRIES` how many graphics pipelines stay resident (1024 each), and `APS5_PIPELINE_JOURNAL_MAX_MIB` (1024) the file size at which recording stops. Deleting the file loses nothing but the head start. With `APS5_PROFILE_DRAW=1` a `[pipeline-journal]` line reports what was loaded, replayed and recorded. +Besides the recompiled shaders, the shader cache directory holds `pipelines.journal`: every Vulkan graphics pipeline the pipeline store kept and every compute pipeline a run built, with the specialized shader modules they were built from. The next run of the title builds those pipelines on background threads while it loads, so a draw or dispatch the earlier run already made does not compile its pipeline when it is first reached. The journal belongs to the GPU and driver that wrote it, like the driver's pipeline cache file next to it: a run on another GPU or driver version starts it over, and the driver's own pipeline cache decides how fast each pipeline is rebuilt. Two runs sharing the directory take turns on `pipelines.journal.lock`; a run that cannot lock it records nothing. `APS5_NO_PIPELINE_JOURNAL=1` disables recording and replay; `APS5_PIPELINE_JOURNAL_THREADS` sets the replay threads (half the CPU threads by default; `0` records without replaying), `APS5_PIPELINE_JOURNAL_ENTRIES` how many pipelines are replayed (4096, the last recorded) and `APS5_PIPELINE_STORE_ENTRIES` how many graphics pipelines stay resident (by default the store grows from 1024 up to 4096 to hold the replay and the pipelines that keep coming back), and `APS5_PIPELINE_JOURNAL_MAX_MIB` (1024) the file size at which recording stops. Deleting the file loses nothing but the head start. With `APS5_PROFILE_DRAW=1` a `[pipeline-journal]` line reports what was loaded, replayed and recorded. + +### Draw pipeline + +`APS5_DRAW_PIPELINE=1` (off by default) moves more of each draw off the thread that reads the title's command buffers: a second thread finishes each draw while the next one is read, the thread that records draws skips work whose inputs did not change, and checks that a draw's resources are still current are reused until the title or the GPU writes memory. It is meant with `APS5_PIPELINED_DRAWS=32 APS5_PIPELINE_DISPATCH=1`. The picture does not change; if it does, that is a bug, and a run without the switch shows whether it is the cause. `APS5_VERIFY_DRAW_PIPELINE=1` runs the full checks next to the reused ones, at a cost in speed, and prints `[epoch-proofs]` lines with how many differed (any nonzero count is a bug). `APS5_PROFILE_DRAW=1` finishes every draw on the reading thread, so a run with it does not measure this switch. To see the hand-off without that, set `APS5_PIPELINE_STATS` (any value; `1` also turns on Vulkan pipeline statistics, which cost time): every 10 s a `[finisher]` line counts the draws handed to the second thread and those finished on the reading thread, and `[finisher-held]` lines count the draws held back by why their writes are not known. ### Texture cache budget +Image and buffer memory allocations retry after releasing unused pooled buffers and reclaimable texture cache entries. Recovery preserves storage images with pending GPU results and resources retained by recorded work. `[bufferpool]` recovery and failure lines report the requested allocation, memory released and retry count. An allocation still fails if recovery cannot free enough memory. + Sampled textures and storage images stay in device memory in two caches that evict their least recently used entries when they grow past their budgets. The storage image budget is what the `VK_EXT_memory_budget` budget of the largest device-local heap leaves after the heap's use other than the cached storage images themselves and an eighth of the budget; it is capped at a quarter of that budget, at most a quarter of the heap's size or 2 GiB, whichever is larger, and never below 512 MiB. The sampled texture budget is what the reported budget leaves after the heap's use other than cached textures, a quarter of the heap's size (at least 2 GiB) and an eighth of the budget, and never below 2 GiB. As the heap's other use grows the sampled budget falls first, to that floor, then the storage budget; the storage budget also gives back what the sampled textures alive take as they grow. Each cache trims at its next insert. Both budgets are read again every second; `[gpu] sampled texture cache budget` lines report them at start-up and when either moves by more than a tenth, at most every 10 s. `APS5_TEXTURE_CACHE_MIB=<n>` fixes both budgets at `n` MiB, and the lines are then not printed. A fixed budget above the automatic one can exceed what the card holds next to the rest of the frame. Allocations then go to host memory (`[gpu] ... placed in host memory` lines), where the GPU reads them over PCIe. PPSA16064 on an RTX 4060 8 GB, whose automatic storage image budget was 1797 MiB under the earlier rule, placed 868 and 878 MiB in host memory with `APS5_TEXTURE_CACHE_MIB=3072` and walked through its first level at 4.46 and 4.83 FPS; with the variable unset it placed none and ran the same walk at 6.48 and 6.07 FPS. Leave it unset unless a measurement on the card shows otherwise. @@ -319,6 +359,13 @@ most called traced imports (1 calls in total): 1 libkernel.prx k+AXqu2-eBc ``` +To list every import a title calls during a run that ends without a crash, also set `APS5_TRACE_CALLS_FILE` to a file path. Every 10 seconds the file is rewritten with one line per import called so far, most called first: the number of calls, the library and the import name. It survives a run that is killed, up to its last rewrite. + +``` +245557 libc.prx gQX+4GDQjpM#guest +3866 libSceAgc.prx 57labkp+rSQ +``` + Limits are listed in the [libc](../dev/debt/libc.md) and [relinker](../dev/debt/relinker.md) technical debt. ### Performance overlay @@ -380,6 +427,8 @@ python3 tools/import_audit.py app.registry.json app.libc.prx.guest.prx.registry. | `absent` | No built library exports the NID: the loader fails. | | `module` | The import names a file found in `--modules` (the title's own library), which is trusted to provide it. | +An import that names a library requires the literal export name. If its implementation is found only by hashing the import name, it still counts as `implemented` or `stub`, but the report lists the name mismatch, sets `symbol_mismatch` on the JSON import record and counts it in the summary, and exits with code 1. A strong import with a name mismatch is a current stop. Imports without a library retain the audit's name-to-NID fallback. + Module registries mark ELF weak imports with `"weak": true`, allowing the audit's current-stop selection to skip them. Strong imports omit the field. Older module registries do not preserve weak bindings; regenerate them to distinguish optional imports. ELF modules in `--modules` are audited regardless of filename: their undefined symbols are classified against the built libraries and the other modules' exports, with the module file named in the report, and the libraries they need must have a file in `--libs` or `--modules`. Ordinary non-module sidecars are ignored; malformed ELF, PE/SELF containers and non-ELF files with module extensions remain errors. The audit reads dynamic tables from ELF section headers or, for sectionless files, from `PT_DYNAMIC` and a SysV or GNU hash table. @@ -403,3 +452,13 @@ Registry `nid` fields may contain either encoded NIDs or ordinary ELF symbol nam Exit codes: `0` nothing blocks loading, `1` there are `absent` imports or needed libraries without a file, `2` an input could not be read; the message names the file and the value. A module registry lists `nid`, `library` and `targetOffset` for each relocation against a function that no other bundled module exports, without the `#` suffix of the executable's NIDs. + +## Patching imports of native applications + +`nid_patcher --exec <file> [file2 ...]` patches imports in place and preserves the application's exports. It detects PE, ELF64 and Mach-O64 from file contents. No library name is required. Build a separate executable for each target platform. + +By default, imports use the library export naming rules: `_nid_no_patch` and `SDL_*` stay unchanged; `_nid_no_patch_cut` is removed without hashing; `_nid_postfix` and numeric `_nid_disambig` suffixes are removed before computing the NID. When both `foo` and `foo_nid_postfix` are imported, `foo` stays unchanged. These rules also apply to host imports unless they are explicitly preserved. + +`nid_patcher --exec --only-sce <file>` restricts renaming to names starting with `sce` (case-insensitive) or ending with `_nid_postfix`. Both modes accept `--preserve-exports <unpatched-library>` or `--preserve-export-list <export-list>` for explicit exclusions. The existing `nid_patcher <library_name> <file>` mode is unchanged. + +Unsupported layouts fail before the input file is written. diff --git a/tools/build_web.py b/tools/build_web.py index 66798820d2..4127eaf9a0 100644 --- a/tools/build_web.py +++ b/tools/build_web.py @@ -4,6 +4,7 @@ import io import json import os from pathlib import Path +import sys import re import shutil import tempfile @@ -70,7 +71,8 @@ def publish_libraries(output, release=None): raise ValueError("Invalid library archive") names = {} for file in files: - if not file.filename.startswith("libs/") or any(part in ("..", ".", "") for part in file.filename.rstrip("/").split("/")) or "\\" in file.filename: + if (file.orig_filename != file.filename or not file.filename.startswith("libs/") + or any(part in ("..", ".", "") for part in file.filename.rstrip("/").split("/")) or "\\" in file.filename): raise ValueError("Invalid library archive layout") name = file.filename.rstrip("/").lower() if name in names: @@ -89,6 +91,8 @@ def publish_libraries(output, release=None): path = f"libraries/{platform}-{checksum}.zip" temporary.replace(output / path) manifest["platforms"][platform] = {"path": path, "size": size, "expandedBytes": expanded, "sha256": checksum} + except zipfile.BadZipFile as error: + raise ValueError(f"Invalid release archive {asset['name']}: {error}") from error finally: temporary.unlink(missing_ok=True) (output / "release.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") @@ -141,7 +145,7 @@ def publish_release(output): target.parent.mkdir(parents=True, exist_ok=True) target.write_bytes(archive.read(file)) publish_libraries(staging, release) - manifest = json.loads((staging / "release.json").read_text(encoding="utf-8")) + manifest = json.loads((staging / "release.json").read_text(encoding="utf-8-sig")) manifest["revision"] = metadata["revision"] (staging / "release.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") (staging / ".nojekyll").touch() @@ -157,29 +161,38 @@ def publish_release(output): raise RuntimeError(f"Could not restore release output. Previous site is preserved at {previous}") from error raise published = True + except zipfile.BadZipFile as error: + raise ValueError(f"Invalid release archive {asset['name']}: {error}") from error finally: if published or not previous.exists(): shutil.rmtree(temporary) print(f"Published web converter and libraries from {tag} ({metadata['revision']})") -def main(): +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser() parser.add_argument("--build", type=Path, required=True) parser.add_argument("--release", "--libraries", dest="release", action="store_true", help="Download the matching converter and libraries from the latest release") - args = parser.parse_args() + args = parser.parse_args(argv) output = args.build.resolve() / "web" - if args.release: - publish_release(output) + try: + if args.release: + publish_release(output) + print(output) + return 0 + for name in ("relinker_wasm.mjs", "relinker_wasm.wasm"): + if not (output / "native" / name).is_file(): + parser.error(f"Build relinker_wasm first: missing {name}") + copy_sources(output) print(output) - return - for name in ("relinker_wasm.mjs", "relinker_wasm.wasm"): - if not (output / "native" / name).is_file(): - raise ValueError(f"Build relinker_wasm first: missing {name}") - copy_sources(output) - print(output) + return 0 + except (OSError, ValueError) as error: + parser.error(str(error)) if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/tools/check_compile.py b/tools/check_compile.py index a251673abf..24daf7321a 100644 --- a/tools/check_compile.py +++ b/tools/check_compile.py @@ -11,8 +11,19 @@ from contextlib import nullcontext from pathlib import Path CPP = (".cpp", ".cc", ".cxx", ".c") -DROP = ("-c", "-MD", "-MMD", "-MP") +DROP = ("-c", "-MD", "-MMD", "-MP", "-fsyntax-only", "-fno-syntax-only", "-save-temps", "--save-temps", + "-save-temps=cwd", "-save-temps=obj", "--save-temps=cwd", "--save-temps=obj") DROP_WITH_VALUE = ("-o", "-MT", "-MF", "-MQ", "-MJ") +PREPROCESSING = ("-E", "--preprocess", "-M", "--dependencies", "-MM", "--user-dependencies", + "-MG", "--print-missing-file-dependencies", "-P", "--no-line-commands", + "-C", "-CC", "-dM", "-dD", "-dI") +WITH_VALUE = ("-I", "-L", "-F", "-B", "-D", "-U", "-x", "-include", "--include", "-imacros", "--imacros", + "--define-macro", "--undefine-macro", "-iquote", "-isystem", "-idirafter", "-iprefix", + "-iwithprefix", "-iwithprefixbefore", "-isysroot", "--sysroot", "-target", "--target", + "-arch", "-std", "-T", "-u", "-e", "--param", "-Xclang", "-Xpreprocessor", "-Xassembler", "-Xlinker") +WITHOUT_VALUE = (*DROP, *PREPROCESSING, "-S", "-fsyntax-only", "-Werror", "-Wall", "-Wextra", + "-pedantic", "-pedantic-errors", "-pthread", "-pipe", "-v") +JOINED_OPTIONS = ("-I", "-L", "-F", "-B", "-D", "-U", "-O", "-g", "-W") def capture(command, cwd=None, encoding="utf-8"): @@ -40,19 +51,53 @@ def repository(): return Path(result.stdout.strip()) +def validate_arguments(arguments): + if not isinstance(arguments, list) or not arguments or any(not isinstance(argument, str) for argument in arguments) or not arguments[0]: + raise ValueError("arguments must be a nonempty list of strings with an executable") + + +def compilation_database(text, source, skip_empty_commands=False): + database = json.loads(text) + if not isinstance(database, list): + raise ValueError(f"{source}: expected an array of command objects") + commands = [] + for index, entry in enumerate(database): + context = f"{source}: entry {index}" + if not isinstance(entry, dict): + raise ValueError(f"{context}: expected a command object") + for field in ("directory", "file"): + if field not in entry: + raise ValueError(f"{context}: missing {field}") + if not isinstance(entry[field], str) or not entry[field]: + raise ValueError(f"{context}: {field} must be a nonempty string") + if skip_empty_commands and "arguments" not in entry and entry.get("command") == "": + continue + if "arguments" in entry: + try: + validate_arguments(entry["arguments"]) + except ValueError as error: + raise ValueError(f"{context}: {error}") from error + elif "command" not in entry: + raise ValueError(f"{context}: expected arguments or command") + elif not isinstance(entry["command"], str) or not entry["command"].strip(): + raise ValueError(f"{context}: command must be a nonempty string") + commands.append(entry) + return commands + + def entries(build): if not build.is_dir(): raise ValueError(f"{build} is not a directory; configure the build first") manifest = build / "compile_commands.json" if manifest.is_file(): - return json.loads(manifest.read_text(encoding="utf-8")) + return compilation_database(manifest.read_text(encoding="utf-8-sig"), manifest) ninja = shutil.which("ninja") if ninja is None: raise ValueError(f"no {manifest} and ninja is not on PATH") result = capture([ninja, "-C", str(build), "-t", "compdb", "-x"], encoding=ninja_encoding(ninja)) if result.returncode != 0: raise ValueError(f"ninja -t compdb: {result.stderr.strip()}") - return json.loads(result.stdout) + return compilation_database(result.stdout, "ninja -t compdb", skip_empty_commands=True) def entry_key(entry, root): @@ -115,33 +160,88 @@ def syntax_only(entry, source): return syntax_arguments(arguments, directory, source) +def preprocessor_arguments(argument): + forwarded = argument[4:].split(",") + if any(option.startswith("@") for option in forwarded): + raise ValueError(f"unsupported forwarded compiler option response file in {argument}") + options = iter(forwarded) + kept = [] + for option in options: + if option in ("-MD", "-MMD", "-MF", "-MT", "-MQ", "--write-dependencies", "--write-user-dependencies"): + if not next(options, None): + raise ValueError(f"missing value for {option} in {argument}") + continue + if option in ("-M", "-MM", "-MG", "-MP", "--dependencies", "--user-dependencies", + "--print-missing-file-dependencies") or option.startswith(("-MF", "-MT", "-MQ")): + continue + kept.append(option) + if option in ("-D", "-U", "-I", "-F", "-include", "--include", "-imacros", "--imacros", + "-isystem", "-iquote", "-idirafter", "-isysroot"): + value = next(options, None) + if not value: + raise ValueError(f"missing operand for {option} in {argument}") + kept.append(value) + elif not (option.startswith(("-D", "-U", "-I", "-F")) and len(option) > 2): + raise ValueError(f"unsupported forwarded compiler option {option} in {argument}") + return "-Wp," + ",".join(kept) if kept else None + + def syntax_arguments(arguments, directory, source): - if not isinstance(arguments, list) or not arguments or any(not isinstance(argument, str) for argument in arguments) or not arguments[0]: - raise ValueError("arguments must be a nonempty list of strings with an executable") + validate_arguments(arguments) for argument in arguments[1:]: if argument.startswith("@"): raise ValueError(f"unexpanded response file {argument[1:]}; provide expanded compile arguments before checking syntax") wanted = source.resolve() - trimmed = [] - index = 0 + trimmed = [arguments[0]] + index = 1 + positional = False + syntax_added = False while index < len(arguments): argument = arguments[index] - if argument in DROP_WITH_VALUE: - index += 2 - continue - if argument in DROP or argument.startswith(("-MT", "-MF", "-MQ", "-MJ")): + if not syntax_added and argument.startswith("-"): + trimmed.append("-fsyntax-only") + syntax_added = True + if not positional and argument == "--": + positional = True + trimmed.append(argument) index += 1 continue - if not argument.startswith("-"): + if not positional and (argument in ("-Xclang", "-Xpreprocessor") or argument.startswith(("-Xclang=", "-Xpreprocessor="))): + raise ValueError(f"unsupported forwarded compiler option {argument}") + if not positional and argument in (*DROP_WITH_VALUE, *WITH_VALUE): + if index + 1 == len(arguments): + raise ValueError(f"missing operand for {argument}") + if argument in WITH_VALUE: + trimmed.extend(arguments[index:index + 2]) + index += 2 + continue + if not positional and argument.startswith("-Wp,"): + if forwarded := preprocessor_arguments(argument): + trimmed.append(forwarded) + index += 1 + continue + if not positional and (argument in (*DROP, *PREPROCESSING) or argument.startswith(("-MT", "-MF", "-MQ", "-MJ"))): + index += 1 + continue + if positional or not argument.startswith("-"): candidate = Path(argument) if not candidate.is_absolute(): candidate = directory / candidate if candidate.resolve() == wanted: index += 1 continue + if positional: + trimmed.append(str(candidate.resolve())) + index += 1 + continue + elif (argument not in WITHOUT_VALUE and not argument.startswith(JOINED_OPTIONS) and "=" not in argument and index + 1 < len(arguments) + and arguments[index + 1] in PREPROCESSING): + raise ValueError(f"unknown operand count for {argument} before {arguments[index + 1]}") trimmed.append(argument) index += 1 - trimmed += ["-fsyntax-only", str(wanted)] + if not syntax_added: + trimmed.append("-fsyntax-only") + trimmed.append(str(wanted)) return trimmed diff --git a/tools/check_conventions.py b/tools/check_conventions.py index 132f275710..8503faf71d 100644 --- a/tools/check_conventions.py +++ b/tools/check_conventions.py @@ -1,5 +1,6 @@ import argparse import io +import itertools import json import os import posixpath @@ -24,6 +25,7 @@ RULES = { "system-dependency": ("error", "CONTRIBUTING.md#code", "Third-party code is a submodule under 3rdparty/ built from source, not found on the system"), "title-specific": ("error", "CONTRIBUTING.md#code", "Implement the general behaviour, not what one title needs; title-specific code belongs in its sce_module"), "extension": ("error", "CONTRIBUTING.md#code", "Avoid non-standard extensions where standard C++ is enough"), + "alignment": ("error", "CONTRIBUTING.md#code", "MinGW GCC rejects static objects aligned above 8192 bytes; align a larger block at run time inside a bigger buffer"), "notes-file": ("error", "CONTRIBUTING.md#branches-and-pull-requests", "Notes, investigation and agent files go in the pull request, not in the repository; only the root AGENTS.md is allowed"), "binary": ("error", "docs/dev/CONVENTIONS.md", "Only UTF-8 text files in the repository; images go in the gist comments"), "ci-gate": ("error", "CONTRIBUTING.md#build-and-test", "A job that runs for pull requests needs a file filter: paths on the pull_request trigger, or needs eligibility and an if on its outputs"), @@ -59,6 +61,12 @@ STUB_BODY = re.compile(r"^(?:\(void\)\s*\w+\s*;|static_cast<void>\(\s*\w+\s*\)\s SYSTEM_DEPENDENCY = re.compile(r"\b(find_package\s*\((?!\s*(Python3|Threads|Git)\b)|pkg_check_modules\b|pkg_search_module\b|find_(library|path|program|file)\b)", re.I) TITLE = re.compile(r"\b(PPSA|CUSA)\d{5}\b") EXTENSION = re.compile(r"__attribute__|__declspec|#\s*pragma\s+(?!once\b)") +INTEGER = r"(0[xX][0-9a-fA-F']+|0[bB][01']+|[0-9][0-9']*)[uUlLzZ]*" +ALIGNAS = re.compile(rf"\balignas\s*\(\s*(?:{INTEGER}|([A-Za-z_][A-Za-z0-9_]*))\s*\)") +CONSTANT = re.compile(rf"\bconstexpr\b[^;=]*?\b([A-Za-z_][A-Za-z0-9_]*)\s*(?:=\s*|\{{\s*){INTEGER}\s*[;}}]") +NAMESPACE = re.compile(r"\bnamespace\s+((?:[A-Za-z_][A-Za-z0-9_]*\s*::\s*(?:inline\s+)?)*[A-Za-z_][A-Za-z0-9_]*)\s*$|\bnamespace\s*$") +LINKAGE = re.compile(r'\bextern\s*""\s*$') +ALIGNMENT_LIMIT = 8192 LINK = re.compile(r"\[[^\]]+\]\(([^)\s]+)") FENCE = re.compile(r"^ {0,3}(```|~~~).*?^ {0,3}\1[ \t]*\r?$|^ {0,3}(?:```|~~~).*\Z", re.S | re.M) INLINE = re.compile(r"`[^`\n]*`") @@ -93,6 +101,7 @@ def git(*args, text=True): class Check: def __init__(self, base, head): self.head = head + self.target = base self.base = git("merge-base", base, head).strip() self.findings = [] self.added = defaultdict(list) @@ -170,17 +179,27 @@ class Check: core = path.startswith("core/") and "tests" not in PurePosixPath(path).parts source = self.show(path).split("\n") masked = cpp_text("\n".join(source), preserve_comments=True).split("\n") + assembly = asm_exports("\n".join(source)) if core and path.startswith("core/libs/prx/") else {} + constants, starts = None, None for number, text in lines: bare = masked[number - 1] if ("//" in bare or "/*" in bare) and not ALLOWED_COMMENT.match(bare): self.report("comment", path, number) if EXTENSION.search(bare): self.report("extension", path, number, EXTENSION.search(bare).group(0)) + if "alignas" in bare: + if constants is None: + constants = named_constants(cpp_text("\n".join(source))) + starts = list(itertools.accumulate((len(line) + 1 for line in masked), initial=0)) + alignment = over_aligned(bare, constants, starts[number - 1]) + if alignment: + self.report("alignment", path, number, alignment) if core and TITLE.search(text): self.report("title-specific", path, number, TITLE.search(text).group(0)) if core and path.startswith("core/libs/prx/"): unknowns.extend((nid, name, path, number) for nid, name in source_aliases(text) if "unknown" in name.lower()) symbols.extend((symbol, path, number) for symbol in nid_symbols(text)) + exports.update((name, (path, number)) for name in assembly.get(number, ()) if not name.endswith(("_nid_no_patch", "_nid_no_patch_cut"))) match = EXPORT.search(bare) if match and core and path.startswith("core/libs/prx/") and not match.group(1).endswith(("_nid_no_patch", "_nid_no_patch_cut")): source = source or self.show(path).splitlines() @@ -206,21 +225,23 @@ class Check: def duplicates(self, exports): if not exports: return - result = subprocess.run(["git", "-c", "core.quotePath=false", "grep", "-nF", "--full-name", "-f", "-", self.head, "--", ":(top)core/libs/prx"], input="\n".join(exports) + "\n", capture_output=True, encoding="utf-8", errors="replace") - hits = [line.split(":", 3)[1:] for line in filter(None, result.stdout.split("\n"))] - self.read(path for path, _, _ in hits) - sources = {} - for path, number, text in hits: - if path not in sources: - sources[path] = cpp_text(self.show(path)).split("\n") - source = sources[path] - match = next((match for match in EXPORT.finditer(source[int(number) - 1]) if match.group(1) in exports), None) - if match is None: + paths = sorted(path for path in self.objects if path.startswith("core/libs/prx/") and + "tests" not in PurePosixPath(path).parts) + self.read(paths) + for path in paths: + text = self.show(path) + if "APS5_VABI" not in text and "ASM_FUNCTION" not in text: continue - name = match.group(1) - own, own_number = exports[name] - if library(path) != library(own) and "tests" not in PurePosixPath(path).parts and function_body(source, int(number) - 1) is not None: - self.report("duplicate-export", own, own_number, f"{name} is also in {library(path)}") + source = cpp_text(text).split("\n") + assembly = asm_exports(text) + for number, line in enumerate(source): + names = [match.group(1) for match in EXPORT.finditer(line) + if match.group(1) in exports and library(path) != library(exports[match.group(1)][0]) + and function_body(source, number) is not None] + names += [name for name in assembly.get(number + 1, ()) if name in exports and library(path) != library(exports[name][0])] + for name in names: + own, own_number = exports[name] + self.report("duplicate-export", own, own_number, f"{name} is also in {library(path)}") def unknown_nids(self, unknowns, symbols): if not unknowns and not symbols: @@ -338,7 +359,7 @@ class Check: doc = "docs/dev/ENVIRONMENT.md" added, removed, dropped = {}, set(), set() path, number, in_hunk = "", 0, False - for line in git("diff", "-U0", self.base, self.head, "--", "core", doc).splitlines(): + for line in git("diff", "-U0", self.base, self.head, "--", ":(top)core", f":(top){doc}").splitlines(): if line.startswith("diff --git"): path, in_hunk = "", False elif line.startswith("@@"): @@ -356,12 +377,15 @@ class Check: removed.update(match.group("name") for _, match in env_reference.line_reads(line[1:])) elif line.startswith("+++ b/"): path = line[6:] - result = subprocess.run(["git", "grep", "-nE", env_reference.GREP, self.head, "--", "core"], + result = subprocess.run(["git", "grep", "-nE", "--full-name", env_reference.GREP, self.head, "--", ":(top)core"], capture_output=True, encoding="utf-8", errors="replace") reads = set() for line in result.stdout.splitlines(): reads.update(match.group("name") for _, match in env_reference.line_reads(line)) - documented = set(env_reference.documented_pattern(env_reference.PREFIXES).findall(self.show(doc))) if doc in self.tree else set() + pattern = env_reference.documented_pattern(env_reference.PREFIXES) + documented = set(pattern.findall(self.show(doc))) if doc in self.tree else set() + if git("ls-tree", "--full-tree", "--name-only", self.target, "--", doc).strip() == doc: + documented |= set(pattern.findall(git("show", f"{self.target}:{doc}"))) - dropped for name, (source, line) in sorted(added.items()): if name not in documented: self.report("env-reference", source, line, f"{name} is read here but has no row in {doc}") @@ -456,13 +480,44 @@ def block(lines, start): return lines[start + 1:end] +def flow_mapping(value): + tokens = re.findall(r'''"(?:\\.|[^"\\])*"|'(?:''|[^'])*'|[^{}\[\],:\s]+|[{}\[\],:]''', value) + if not tokens or tokens[0] != "{" or tokens[-1] != "}": + return {} + result = {} + stack = [] + start, colon = 1, None + for index, token in enumerate(tokens): + if token in ("{", "["): + stack.append(token) + elif token in ("}", "]"): + if not stack or stack.pop() != ("{" if token == "}" else "["): + return {} + if not stack and index != len(tokens) - 1: + return {} + if len(stack) == 1 and token == ":" and colon is None: + colon = index + if (len(stack) == 1 and token == ",") or (not stack and token == "}"): + if colon == start + 1: + result[tokens[start].strip("'\"")] = " ".join(tokens[colon + 1:index]) + elif start != index: + return {} + start, colon = index + 1, None + return result if not stack else {} + + def pull_request_filtered(lines, trigger): _, _, _, value = trigger if value: + if value.lstrip().startswith("{"): + triggers = flow_mapping(value) + return "pull_request" not in triggers or bool({"paths", "paths-ignore"} & flow_mapping(triggers["pull_request"]).keys()) return "pull_request" not in re.findall(r"[A-Za-z_]+", value) triggers = block(lines, lines.index(trigger)) for index, (_, indent, name, value) in enumerate(triggers): if name == "pull_request" and indent == triggers[0][1]: + if value: + return bool({"paths", "paths-ignore"} & flow_mapping(value).keys()) return any(entry[2] in ("paths", "paths-ignore") for entry in block(triggers, index)) return True @@ -512,6 +567,16 @@ def symbol_nid(name): return compute_nid(base) if base else None +def asm_exports(text): + tokens = [match for match in CPP_TOKEN.finditer(text) if match.group("comment") is None] + exports = defaultdict(list) + for index, match in enumerate(tokens[:-2]): + name = tokens[index + 2].group()[1:-1] + if match.group() in ("APS5_ASM_FUNCTION", "APS5_SIGNAL_ASM_FUNCTION") and tokens[index + 1].group() == "(" and tokens[index + 2].group("string") is not None and IDENTIFIER.fullmatch(name): + exports[text.count("\n", 0, tokens[index + 2].start()) + 1].append(name) + return exports + + def nid_symbols(text): tokens = [match for match in CPP_TOKEN.finditer(text) if match.group("comment") is None] names = [] @@ -562,6 +627,68 @@ def cpp_text(text, preserve_comments=False): return CPP_TOKEN.sub(mask, text) +def integer_literal(text): + digits = text.replace("'", "").lower() + if digits.startswith(("0x", "0b")): + return int(digits, 0) + return int(digits, 8 if len(digits) > 1 and digits[0] == "0" else 10) + + +def brace_scopes(text): + stack, scopes, boundary = [], [], -1 + for index, character in enumerate(text): + if character not in "{};": + continue + if character == "{": + head = text[boundary + 1:index] + outer = next((key[1] for _, key in reversed(stack) if key and key[0] == "namespace"), "") + match = NAMESPACE.search(head) + if match and re.search(r"\binline\s+namespace\b", head): + key = ("namespace", outer) + elif match: + key = ("namespace", outer + "::" + re.sub(r"\binline\b|\s+", "", match.group(1) or "{anonymous}")) + else: + key = None if LINKAGE.search(head) else ("block", index) + stack.append((index, key)) + elif character == "}" and stack: + start, key = stack.pop() + scopes.append((start, index, key)) + boundary = index + return scopes + + +def scope_chain(scopes, position): + chain = [] + for _, _, key in sorted((scope for scope in scopes if scope[0] < position < scope[1]), reverse=True): + if key and key[0] == "namespace": + parts = key[1].split("::") + chain.extend(prefix for prefix in (("namespace", "::".join(parts[:count])) for count in range(len(parts), 1, -1)) if prefix not in chain) + elif key: + chain.append(key) + return chain + [("namespace", "")] + + +def named_constants(text): + scopes = brace_scopes(text) + definitions = [(match.group(1), integer_literal(match.group(2)), match.start(), scope_chain(scopes, match.start())[0]) for match in CONSTANT.finditer(text)] + return definitions, scopes + + +def constant_value(constants, name, position): + definitions, scopes = constants + chain = scope_chain(scopes, position) + visible = [(-chain.index(scope), start, value) for found, value, start, scope in definitions if found == name and start < position and scope in chain] + return max(visible)[2] if visible else 0 + + +def over_aligned(text, constants=((), ()), offset=0): + for match in ALIGNAS.finditer(text): + value = integer_literal(match.group(1)) if match.group(1) else constant_value(constants, match.group(2), offset + match.start()) + if value > ALIGNMENT_LIMIT: + return match.group(0) + return None + + def function_body(lines, start): text = cpp_text("\n".join(lines[start:start + 400])) brace, semicolon = text.find("{"), text.find(";") diff --git a/tools/check_main_sync.py b/tools/check_main_sync.py index e1f41677d7..a8be7392f2 100644 --- a/tools/check_main_sync.py +++ b/tools/check_main_sync.py @@ -4,7 +4,7 @@ import subprocess import sys from pathlib import Path -PULL_REQUEST = re.compile(r"Merge pull request #(\d+)") +PULL_REQUEST = re.compile(r"Merge pull request #(\d+)|\(#(\d+)\)$") BASES = ("upstream/main", "origin/main", "main") @@ -47,14 +47,17 @@ def merged_pull_requests(repo, mergebase, base, paths): for line in lines(repo, "-c", "core.quotepath=off", "log", "--first-parent", "--format=%h %s", f"{mergebase}..{base}", "--", *paths): match = PULL_REQUEST.search(line) if match: - found[int(match.group(1))] = line.split(" ", 1)[0] + found[int(match.group(1) or match.group(2))] = line.split(" ", 1)[0] return found def report(repo, base, head): + repo = git(repo, "rev-parse", "--show-toplevel").stdout.strip() found = git(repo, "merge-base", base, head, check=False) - if found.returncode: + if found.returncode == 1: raise RuntimeError(f"{head} and {base} have no common ancestor") + if found.returncode: + raise RuntimeError(f"git merge-base failed: {found.stderr.strip()}") mergebase = found.stdout.strip() behind = int(git(repo, "rev-list", "--count", f"{head}..{base}").stdout) ahead = int(git(repo, "rev-list", "--count", f"{base}..{head}").stdout) @@ -88,14 +91,17 @@ def render(data): return "\n".join(out) -if __name__ == "__main__": +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(errors="backslashreplace") parser = argparse.ArgumentParser(description="report whether a branch is in step with the upstream main before you push or test") parser.add_argument("--base", help="the main ref to compare against (default: upstream/main, then origin/main, then main)") parser.add_argument("--head", default="HEAD", help="the branch or commit to check (default: HEAD)") parser.add_argument("--repo", type=Path, default=Path("."), help="the repository to check (default: the current directory)") parser.add_argument("--fetch", action="store_true", help="fetch the base's remote first") parser.add_argument("--strict", action="store_true", help="also fail when the branch is behind the base") - args = parser.parse_args() + args = parser.parse_args(argv) try: def fetch(ref): remote, _, branch = ref.partition("/") @@ -117,9 +123,11 @@ if __name__ == "__main__": data = report(args.repo, base, args.head) except RuntimeError as error: parser.error(str(error)) - sys.stdout.reconfigure(errors="backslashreplace") print(render(data)) - if data["conflicts"]: - raise SystemExit(1) - if args.strict and data["behind"]: - raise SystemExit(1) + if data["conflicts"] or (args.strict and data["behind"]): + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/check_skips.py b/tools/check_skips.py index 99628018e3..936570e00e 100644 --- a/tools/check_skips.py +++ b/tools/check_skips.py @@ -1,3 +1,4 @@ +import sys import argparse import re import xml.etree.ElementTree as ET @@ -10,23 +11,34 @@ NO_DISPLAY = re.compile(r"skipped,.*no display", re.IGNORECASE) NO_VULKAN_DEVICE = re.compile(r"skipped,.*no usable Vulkan device", re.IGNORECASE) HIGH_REFRESH_LIMITATION = re.compile(r"current display does not provide 120 Hz", re.IGNORECASE) +CTEST_SKIP_STATUS = re.compile(r"SKIP_RETURN_CODE=\d+|SKIP_REGULAR_EXPRESSION_MATCHED", re.IGNORECASE) WINDOWS_PREFIX = "agc" +def device_limitations(output, patterns): + reasons = [match.group(0) for line in output.splitlines() if (match := re.search(r"skipped,.*", line, re.IGNORECASE))] + return bool(reasons) and all(any(pattern.search(reason) for pattern in patterns) for reason in reasons) + + def high_refresh_limitation(output): reasons = [] for line in output.splitlines(): reason = line.strip() - if reason.lower().startswith("skipped,"): - reason = reason[len("skipped,"):].strip() + if match := re.search(r"skipped,", reason, re.IGNORECASE): + reason = reason[match.end():].strip() if reason: reasons.append(reason) return bool(reasons) and all(HIGH_REFRESH_LIMITATION.fullmatch(reason) for reason in reasons) -def skips(path): - for case in ET.parse(path).getroot().iter("testcase"): +def skips(path, require_tests=False): + root = ET.parse(path).getroot() + if root.tag not in ("testsuite", "testsuites"): + raise ValueError(f"{path}: expected a JUnit testsuite or testsuites root, got {root.tag!r}") + found = False + for case in root.iter("testcase"): + found = True if case.get("status") == "disabled": raise ValueError(f"{path}: testcase {case.get('name')!r} is disabled and was not run") skipped = case.find("skipped") @@ -35,35 +47,48 @@ def skips(path): name = case.get("name") if not name: raise ValueError(f"{path}: skipped testcase has no name") - output = ["skipped, " + reason.strip() for reason in (skipped.get("message", ""), skipped.text or "") if reason.strip()] + output = ["skipped, " + reason.strip() for reason in (skipped.get("message", ""), skipped.text or "") + if reason.strip() and not CTEST_SKIP_STATUS.fullmatch(reason.strip())] output += [case.findtext(channel) or "" for channel in ("system-out", "system-err")] yield name, "\n".join(output) + if require_tests and not found: + raise ValueError(f"{path}: report contains no testcases") -if __name__ == "__main__": +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="fail when a test is skipped that the job's device is not expected to skip") parser.add_argument("junit", type=Path, help="the file written by ctest --output-junit") parser.add_argument("--job", choices=("linux", "windows"), default="linux") parser.add_argument("--list", action="store_true", help="print the skipped tests and why the file says they skipped") - args = parser.parse_args() + parser.add_argument("--require-tests", action="store_true", help="fail if the report contains no testcases") + args = parser.parse_args(argv) try: - found = sorted(skips(args.junit)) + found = sorted(skips(args.junit, require_tests=args.require_tests)) except (OSError, ET.ParseError, ValueError) as error: parser.error(str(error)) if args.list: for name, output in found: - reason = next((line.strip() for line in output.splitlines() if line.strip().lower().startswith("skipped")), "") + reason = next((match.group(0) for line in output.splitlines() + if (match := re.search(r"skipped,.*", line, re.IGNORECASE))), "") print(f"{name}: {reason}") - raise SystemExit + return 0 if args.job == "windows": - unexpected = [name for name, output in found if not name.startswith(WINDOWS_PREFIX) and not NO_DISPLAY.search(output) and not NO_VULKAN_DEVICE.search(output) and not high_refresh_limitation(output)] + unexpected = [name for name, output in found if not name.startswith(WINDOWS_PREFIX) and not device_limitations(output, (NO_DISPLAY, NO_VULKAN_DEVICE)) and not high_refresh_limitation(output)] expected = f"any {WINDOWS_PREFIX}* test, which the runner has no Vulkan driver for, or a test that reports no display or Vulkan device or a display without a 120 Hz mode" else: - unexpected = [name for name, output in found if not DEVICE_LIMITATION.search(output) and not high_refresh_limitation(output)] + unexpected = [name for name, output in found if not device_limitations(output, (DEVICE_LIMITATION,)) and not high_refresh_limitation(output)] expected = "any test that reports the device limitation it needs, such as narrow subgroups, a missing extension, no display for a Vulkan window or a display without a 120 Hz mode" for name in unexpected: print(f"error: {name} skipped") if unexpected: print(f"{len(unexpected)} test(s) skipped for a reason that is not expected: the job lost that coverage and ctest still reports every test as passed") - raise SystemExit(1) + return 1 print(f"{len(found)} test(s) skipped, all expected ({expected})") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/check_test_registration.py b/tools/check_test_registration.py index fcce86cbd1..518a885bd4 100644 --- a/tools/check_test_registration.py +++ b/tools/check_test_registration.py @@ -11,12 +11,12 @@ LEXICAL = re.compile(r'\\.|"(?:\\.|[^"\\])*"|\[(?P<literal_equals>=*)\[.*?\](?P= r'|(?P<comment>\#\[(?P<comment_equals>=*)\[.*?\](?P=comment_equals)\]|\#[^\n]*)', re.S) KNOWN_UNRUN = set() -SCRIPT = re.compile(r"(?:[\w.-]+/)*test_\w+\.py") +SCRIPT = re.compile(r"(?<![\w./-])(?:[\w.-]+/)*test_\w+\.py(?![\w./-])") def built(build): program, generator = None, None - for line in (Path(build) / "CMakeCache.txt").read_text(encoding="utf-8").splitlines(): + for line in (Path(build) / "CMakeCache.txt").read_text(encoding="utf-8-sig").splitlines(): if line.startswith("CMAKE_MAKE_PROGRAM:"): program = line.partition("=")[2] elif line.startswith("CMAKE_GENERATOR:"): @@ -78,10 +78,12 @@ def commands(content): raise ValueError(f"invalid CTest argument at position {position}") argument = token.group(0) if argument.startswith('"'): - argument = re.sub(r"\\(.)", lambda match: {"n": "\n", "r": "\r", "t": "\t"}.get(match[1], match[1]), argument[1:-1], flags=re.S) + argument = re.sub(r"\\(\r?\n|.)", + lambda match: {"\n": "", "\r\n": "", "n": "\n", "r": "\r", "t": "\t"}.get(match[1], match[1]), + argument[1:-1], flags=re.S) elif token.group("equals") is not None: width = len(token.group("equals")) + 2 - argument = argument[width:-width] + argument = argument[width:-width].removeprefix("\n") arguments.append(argument) position = token.end() @@ -157,21 +159,24 @@ def unrun_scripts(root): return scripts, [name for name in scripts if not any(name == path or name.endswith("/" + path) for path in run)] -if __name__ == "__main__": +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="fail when a test executable is built that no ctest test runs") parser.add_argument("build", type=Path, nargs="?", default=Path("build"), help="configured build directory") parser.add_argument("--list", action="store_true", help="print every test executable and whether ctest runs it") parser.add_argument("--scripts", action="store_true", help="instead check that an add_test or a workflow runs every test_*.py of the source tree") - args = parser.parse_args() + args = parser.parse_args(argv) if args.scripts: scripts, unrun = unrun_scripts(Path(__file__).resolve().parents[1]) for name in unrun: print(f"error: {name} is a test script that no add_test or workflow runs") if unrun: print(f"{len(unrun)} test script(s) would never run: register each with add_test, or run it from a workflow") - raise SystemExit(1) + return 1 print(f"{len(scripts)} test script(s), each run by an add_test or a workflow") - raise SystemExit + return 0 try: every = sorted(built(args.build)) run = registered(args.build) @@ -180,11 +185,11 @@ if __name__ == "__main__": if args.list: for name in every: print(f"{'run' if name in run else 'NOT RUN'} {name}") - raise SystemExit + return 0 unrun = sorted(set(every) - run) if not every: print(f"no test executables found under {args.build}; is it configured with -DBUILD_TESTING=ON?") - raise SystemExit(1) + return 1 for name in sorted(set(unrun) & KNOWN_UNRUN): print(f"note: {name} is built and not run, as recorded") stale = sorted(KNOWN_UNRUN & run) @@ -196,5 +201,10 @@ if __name__ == "__main__": if fresh: print(f"{len(fresh)} test executable(s) would never run: ctest reports a full pass over what it knows, so a test nobody registered is invisible in CI") if stale or fresh: - raise SystemExit(1) + return 1 print(f"{len(every)} test executable(s), {len(unrun)} built and not run as recorded, none new") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/configure_seed.py b/tools/configure_seed.py index d1936ae21a..1dd9e52f29 100644 --- a/tools/configure_seed.py +++ b/tools/configure_seed.py @@ -4,7 +4,7 @@ import re import sys from pathlib import Path -CHECK = re.compile(r"^//(Have (include|includes|function|symbol|library) |Test |Result of (TRY_COMPILE|TRY_RUN|TEST_BIG_ENDIAN|check_))") +CHECK = re.compile(r"^//(Have (include|includes|function|symbol|library) |Test |Result of (TRY_COMPILE|TRY_RUN|TEST_BIG_ENDIAN|check_)|CHECK_TYPE_SIZE: )") ENTRY = re.compile(r"^([^#/:][^:]*):([A-Z]+)=(.*)$") @@ -25,7 +25,10 @@ def quote(value): return '"' + value.replace("\\", "\\\\").replace('"', '\\"').replace("$", "\\$") + '"' -def main(): +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="Write the configure check results of a CMake cache as an initial cache file") parser.add_argument("cache", help="CMakeCache.txt of a configured tree") parser.add_argument("seed", help="file to pass to cmake -C") @@ -33,7 +36,7 @@ def main(): help="seed matching check names only; repeat to include several patterns") parser.add_argument("--exclude", action="append", default=[], metavar="GLOB", help="omit matching check names after inclusion; patterns are case-sensitive") - args = parser.parse_args() + args = parser.parse_args(argv) cache, seed = Path(args.cache), Path(args.seed) if cache.resolve() == seed.resolve() or (cache.exists() and seed.exists() and cache.samefile(seed)): parser.error("seed output would overwrite the input cache") @@ -44,9 +47,12 @@ def main(): found = {name: entry for name, entry in results.items() if (not args.include or any(fnmatch.fnmatchcase(name, pattern) for pattern in args.include)) and not any(fnmatch.fnmatchcase(name, pattern) for pattern in args.exclude)} - seed.parent.mkdir(parents=True, exist_ok=True) - seed.write_text("".join(f"set({quote(name)} {quote(value)} CACHE INTERNAL {quote(doc)})\n" - for name, (value, doc) in sorted(found.items())), encoding="utf-8") + try: + seed.parent.mkdir(parents=True, exist_ok=True) + seed.write_text("".join(f"set({quote(name)} {quote(value)} CACHE INTERNAL {quote(doc)})\n" + for name, (value, doc) in sorted(found.items())), encoding="utf-8") + except OSError as error: + parser.error(f"{seed}: {error}") print(f"{len(found)} check results") return 0 diff --git a/tools/env_reference.py b/tools/env_reference.py index 683b1e9168..d7f80595ff 100644 --- a/tools/env_reference.py +++ b/tools/env_reference.py @@ -10,13 +10,14 @@ ROOTS = ("core",) EXTENSIONS = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx", ".inl", ".ipp", ".py"} SKIP = {"build", "build-linux", "build-relinker", "3rdparty", ".git"} -INTEGER_READERS = {"kibSetting", "MaskFromEnvironment", "EnvInt", "configured", "environmentCount"} +INTEGER_READERS = {"kibSetting", "MaskFromEnvironment", "EnvInt", "configured", "environmentCount", "EnvironmentNumber"} STRING_READERS = {"GetEnvironmentVariableA", "GetEnvironmentVariableW", "addWideString"} -NULL_TEST = re.compile(r"[!=]=\s*(nullptr|NULL|0)\b|!\s*(?:std::)?_?w?getenv\(") +NULL_TEST = re.compile(r"[!=]=\s*(nullptr|NULL|0)\b|!\s*(?:std::)?_?w?getenv\s*\(") INTEGER_TEST = re.compile(r"\b(atoi|strtol|strtoul|strtoll|strtoull|stoi|stol|stoul|from_chars|atoi64|_wtoi|_wtol|_wtoi64)\s*\(") GREP = (r'(getenv|_wgetenv|wgetenv|IsEnvironmentSet|kibSetting|MaskFromEnvironment|EnvInt|GetEnvironmentVariable[AW]' - r'|configured|PipelineSwitch|environmentCount|addWideString)' + r'|configured|PipelineSwitch|environmentCount|EnvironmentNumber|addWideString)' r'\([[:space:]]*(u|L)?"(APS5|ANYPS5)_[A-Za-z0-9_]+"') +GREP = GREP.replace(r"\(", r"[[:space:]]*\(") def _name(prefixes): @@ -26,7 +27,7 @@ def _name(prefixes): @functools.lru_cache(maxsize=None) def forms(prefixes=PREFIXES): name = _name(prefixes) - return ( + readers = ( ("getenv", re.compile(r'(?:std::)?getenv\(\s*"(?P<name>' + name + r')"\s*\)')), ("_wgetenv", re.compile(r'(?:std::)?_?wgetenv\(\s*L"(?P<name>' + name + r')"\s*\)')), ("IsEnvironmentSet", re.compile(r'IsEnvironmentSet\(\s*"(?P<name>' + name + r')"\s*\)')), @@ -36,10 +37,12 @@ def forms(prefixes=PREFIXES): ("configured", re.compile(r'\b(?:configured|PipelineSwitch)\(\s*"(?P<name>' + name + r')"' r'(?:\s*,\s*(?P<default>[0-9]+)[uUlL]*)?')), ("environmentCount", re.compile(r'\benvironmentCount\(\s*"(?P<name>' + name + r')"\s*,\s*(?P<default>[0-9]+)')), + ("EnvironmentNumber", re.compile(r'\bEnvironmentNumber\(\s*"(?P<name>' + name + r')"\s*\)')), ("GetEnvironmentVariableA", re.compile(r'GetEnvironmentVariableA\(\s*"(?P<name>' + name + r')"')), ("GetEnvironmentVariableW", re.compile(r'GetEnvironmentVariableW\(\s*L"(?P<name>' + name + r')"')), ("addWideString", re.compile(r'\baddWideString\(\s*u"(?P<name>' + name + r')"\s*\)')), ) + return tuple((reader, re.compile(form.pattern.replace(r"\(", r"\s*\(", 1), form.flags)) for reader, form in readers) def line_reads(line, prefixes=PREFIXES): @@ -88,14 +91,16 @@ def reads(roots, prefixes=PREFIXES): for path in source_files(root): try: text = path.read_text(encoding="utf-8") - except (OSError, UnicodeDecodeError): - continue + except (OSError, UnicodeDecodeError) as error: + raise ValueError(f"cannot read {path.as_posix()}: {error}") from error lines = text.splitlines() for number, line in enumerate(lines, 1): - if not any(prefix in line for prefix in prefixes): - continue window = "\n".join(lines[number - 1:number + 2]) - for reader, match in line_reads(line, prefixes): + if not any(prefix in window for prefix in prefixes): + continue + for reader, match in line_reads(window, prefixes): + if match.start() >= len(line): + continue found.setdefault(match.group("name"), []).append( (path.as_posix(), number, kind_of(reader, window), default_of(match, window))) for sites in found.values(): @@ -158,6 +163,9 @@ def check(found, doc, prefixes, out): def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser( description="list the environment names the code reads, or check them against a reference document") parser.add_argument("--check", action="store_true", @@ -176,7 +184,11 @@ def main(argv=None): if not Path(root).is_dir(): print(f"error: scan root {root} is not a directory", file=sys.stderr) return 1 - found = reads(roots, prefixes) + try: + found = reads(roots, prefixes) + except ValueError as error: + print(f"error: {error}", file=sys.stderr) + return 1 if args.check: return check(found, args.doc, prefixes, sys.stdout) if args.table: diff --git a/tools/hw-oracle/hw_oracle.py b/tools/hw-oracle/hw_oracle.py index ffac30f5c7..f1dc2da856 100644 --- a/tools/hw-oracle/hw_oracle.py +++ b/tools/hw-oracle/hw_oracle.py @@ -1,5 +1,6 @@ import argparse import hashlib +import importlib.util import json import os import operator @@ -17,6 +18,22 @@ CACHE = Path(os.environ.get("HW_ORACLE_CACHE") or Path(os.environ.get("XDG_CACHE ROWS_PER_DISPATCH = 1024 DEFAULT_LDS = 4096 MAX_LDS = 65536 +RUNTIMES = ("hsa", "hip") + + +def runtime(): + selected = os.environ.get("HW_ORACLE_RUNTIME") or ("hip" if os.name == "nt" else "hsa") + if selected not in RUNTIMES: + raise ValueError(f"HW_ORACLE_RUNTIME must be one of {', '.join(RUNTIMES)}") + return selected + + +@cache +def hip_runtime(): + spec = importlib.util.spec_from_file_location("oracle_hip", HERE / "oracle_hip.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module def rocm_root(): @@ -78,7 +95,11 @@ def detected_target(binary, visible_devices): def target(): - return os.environ.get("HW_ORACLE_TARGET") or detected_target(oracle(), os.environ.get("ROCR_VISIBLE_DEVICES")) + if os.environ.get("HW_ORACLE_TARGET"): + return os.environ["HW_ORACLE_TARGET"] + if runtime() == "hip": + return hip_runtime().target() + return detected_target(oracle(), os.environ.get("ROCR_VISIBLE_DEVICES")) def assemble(body, work, wave64, *, ieee, denorm32, denorm16, dx10_clamp, round32, round16, fp16_overflow, lds=DEFAULT_LDS): @@ -97,6 +118,9 @@ def assemble(body, work, wave64, *, ieee, denorm32, denorm16, dx10_clamp, round3 ("@FP16_OVERFLOW@", str(int(fp16_overflow))), ("@LDS@", str(lds)), ("@BODY@", body)): text = text.replace(key, value) (work / "k.s").write_text(text) + if runtime() == "hip": + (work / "k.co").write_bytes(hip_runtime().assemble(text.encode("utf-8"), target(), wave64)) + return work / "k.co" subprocess.run([tool("clang"), "-x", "assembler", "-target", "amdgcn-amd-amdhsa", f"-mcpu={target()}", *(["-mwavefrontsize64"] if wave64 else []), "-c", str(work / "k.s"), "-o", str(work / "k.o")], check=True, stdout=sys.stderr) subprocess.run([tool("ld.lld"), "-m", "elf64_amdgpu", "-shared", str(work / "k.o"), "-o", str(work / "k.co")], check=True, stdout=sys.stderr) return work / "k.co" @@ -131,10 +155,14 @@ def run(body, rows, extra=b"", wave64=False, coarse=False, *, ieee, denorm32, de rows_per_dispatch = ROWS_PER_DISPATCH // 2 if wave64 else ROWS_PER_DISPATCH for start in range(0, len(padded), rows_per_dispatch): chunk = padded[start:start + rows_per_dispatch] - (work / "in.bin").write_bytes(b"".join(struct.pack("<4I", *r) for r in chunk) + extra) - (work / "out.bin").unlink(missing_ok=True) - subprocess.run([oracle(), str(code), str(len(chunk)), str(work / "in.bin"), str(work / "out.bin"), "16"], check=True, env=env, stdout=sys.stderr) - data = (work / "out.bin").read_bytes() + payload = b"".join(struct.pack("<4I", *r) for r in chunk) + extra + if runtime() == "hip": + data = hip_runtime().dispatch(Path(code).read_bytes(), len(chunk), payload, 16, coarse) + else: + (work / "in.bin").write_bytes(payload) + (work / "out.bin").unlink(missing_ok=True) + subprocess.run([oracle(), str(code), str(len(chunk)), str(work / "in.bin"), str(work / "out.bin"), "16"], check=True, env=env, stdout=sys.stderr) + data = (work / "out.bin").read_bytes() expected = len(chunk) * 64 if len(data) != expected: raise ValueError(f"Oracle output size: expected {expected} bytes, got {len(data)}") diff --git a/tools/hw-oracle/oracle_hip.py b/tools/hw-oracle/oracle_hip.py new file mode 100644 index 0000000000..596b82a8f7 --- /dev/null +++ b/tools/hw-oracle/oracle_hip.py @@ -0,0 +1,235 @@ +import argparse +import ctypes +import os +import sys +from functools import cache +from pathlib import Path + +HIP_LIBRARIES = ("amdhip64_7.dll", "amdhip64_6.dll", "amdhip64.dll") +COMGR_LIBRARIES = ("amd_comgr_3.dll", "amd_comgr_2.dll", "amd_comgr.dll") +DATA_SOURCE = 0x1 +DATA_LOG = 0x5 +DATA_RELOCATABLE = 0x7 +DATA_EXECUTABLE = 0x8 +ASSEMBLE_ACTIONS = (0x8, 0xA) +LINK_ACTIONS = (0x7, 0x9) +HIP_MEMCPY_HOST_TO_DEVICE = 1 +HIP_MEMCPY_DEVICE_TO_HOST = 2 +DEVICE_PROPERTIES_SIZE = 8192 +DEVICE_NAME_SIZE = 256 + + +class OracleError(Exception): + pass + + +class Handle(ctypes.Structure): + _fields_ = [("handle", ctypes.c_uint64)] + + +def check(status, what): + if status != 0: + raise OracleError(f"{what} failed with status {status}") + + +def load(variable, names): + candidates = (os.environ[variable],) if os.environ.get(variable) else names + errors = [] + for name in candidates: + try: + return ctypes.WinDLL(name) + except OSError as error: + errors.append(f"{name}: {error}") + raise OracleError(f"no library found ({variable} overrides it): " + "; ".join(errors)) + + +@cache +def hip(): + if os.environ.get("ROCR_VISIBLE_DEVICES") is not None and "HIP_VISIBLE_DEVICES" not in os.environ: + os.environ["HIP_VISIBLE_DEVICES"] = os.environ["ROCR_VISIBLE_DEVICES"] + library = load("HW_ORACLE_HIP", HIP_LIBRARIES) + library.hipModuleLoadData.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_char_p] + library.hipModuleGetFunction.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_void_p, ctypes.c_char_p] + library.hipMemcpy.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int] + library.hipMemset.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_size_t] + library.hipModuleLaunchKernel.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, + ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] + library.hipModuleUnload.argtypes = [ctypes.c_void_p] + library.hipFree.argtypes = [ctypes.c_void_p] + library.hipHostFree.argtypes = [ctypes.c_void_p] + check(library.hipInit(0), "hipInit") + count = ctypes.c_int() + check(library.hipGetDeviceCount(ctypes.byref(count)), "hipGetDeviceCount") + if count.value == 0: + raise OracleError("no HIP device") + return library + + +@cache +def comgr(): + library = load("HW_ORACLE_COMGR", COMGR_LIBRARIES) + library.amd_comgr_create_data.argtypes = [ctypes.c_int, ctypes.POINTER(Handle)] + library.amd_comgr_create_data_set.argtypes = [ctypes.POINTER(Handle)] + library.amd_comgr_create_action_info.argtypes = [ctypes.POINTER(Handle)] + library.amd_comgr_set_data.argtypes = [Handle, ctypes.c_size_t, ctypes.c_char_p] + library.amd_comgr_set_data_name.argtypes = [Handle, ctypes.c_char_p] + library.amd_comgr_data_set_add.argtypes = [Handle, Handle] + library.amd_comgr_action_info_set_isa_name.argtypes = [Handle, ctypes.c_char_p] + library.amd_comgr_action_info_set_option_list.argtypes = [Handle, ctypes.POINTER(ctypes.c_char_p), ctypes.c_size_t] + library.amd_comgr_action_info_set_logging.argtypes = [Handle, ctypes.c_bool] + library.amd_comgr_do_action.argtypes = [ctypes.c_int, Handle, Handle, Handle] + library.amd_comgr_action_data_count.argtypes = [Handle, ctypes.c_int, ctypes.POINTER(ctypes.c_size_t)] + library.amd_comgr_action_data_get_data.argtypes = [Handle, ctypes.c_int, ctypes.c_size_t, ctypes.POINTER(Handle)] + library.amd_comgr_get_data.argtypes = [Handle, ctypes.POINTER(ctypes.c_size_t), ctypes.c_char_p] + library.amd_comgr_release_data.argtypes = [Handle] + library.amd_comgr_destroy_data_set.argtypes = [Handle] + library.amd_comgr_destroy_action_info.argtypes = [Handle] + return library + + +@cache +def target(): + properties = ctypes.create_string_buffer(DEVICE_PROPERTIES_SIZE) + library = hip() + getter = getattr(library, "hipGetDevicePropertiesR0600", None) or library.hipGetDeviceProperties + check(getter(properties, 0), "hipGetDeviceProperties") + start = properties.raw.find(b"gfx", DEVICE_NAME_SIZE) + if start < 0: + raise OracleError("the HIP device properties name no gfx target") + return properties.raw[start:properties.raw.index(b"\0", start)].decode("ascii").split(":")[0] + + +def data_bytes(data_set, kind): + library = comgr() + count = ctypes.c_size_t() + check(library.amd_comgr_action_data_count(data_set, kind, ctypes.byref(count)), "amd_comgr_action_data_count") + chunks = [] + for index in range(count.value): + data = Handle() + check(library.amd_comgr_action_data_get_data(data_set, kind, index, ctypes.byref(data)), "amd_comgr_action_data_get_data") + size = ctypes.c_size_t() + check(library.amd_comgr_get_data(data, ctypes.byref(size), None), "amd_comgr_get_data") + buffer = ctypes.create_string_buffer(size.value) + check(library.amd_comgr_get_data(data, ctypes.byref(size), buffer), "amd_comgr_get_data") + chunks.append(buffer.raw[:size.value]) + library.amd_comgr_release_data(data) + return chunks + + +def data_set(kind, name, payload): + library = comgr() + data = Handle() + check(library.amd_comgr_create_data(kind, ctypes.byref(data)), "amd_comgr_create_data") + check(library.amd_comgr_set_data(data, len(payload), payload), "amd_comgr_set_data") + check(library.amd_comgr_set_data_name(data, name), "amd_comgr_set_data_name") + result = Handle() + check(library.amd_comgr_create_data_set(ctypes.byref(result)), "amd_comgr_create_data_set") + check(library.amd_comgr_data_set_add(result, data), "amd_comgr_data_set_add") + library.amd_comgr_release_data(data) + return result + + +def comgr_step(actions, isa, options, inputs, kind): + library = comgr() + logs = [] + for action in actions: + info = Handle() + check(library.amd_comgr_create_action_info(ctypes.byref(info)), "amd_comgr_create_action_info") + outputs = Handle() + try: + check(library.amd_comgr_action_info_set_isa_name(info, isa.encode("ascii")), "amd_comgr_action_info_set_isa_name") + check(library.amd_comgr_action_info_set_logging(info, True), "amd_comgr_action_info_set_logging") + if options: + check(library.amd_comgr_action_info_set_option_list(info, (ctypes.c_char_p * len(options))(*options), len(options)), + "amd_comgr_action_info_set_option_list") + check(library.amd_comgr_create_data_set(ctypes.byref(outputs)), "amd_comgr_create_data_set") + status = library.amd_comgr_do_action(action, info, inputs, outputs) + result = data_bytes(outputs, kind) + log = b"".join(data_bytes(outputs, DATA_LOG)).decode("utf-8", errors="replace").strip() + library.amd_comgr_destroy_data_set(outputs) + finally: + library.amd_comgr_destroy_action_info(info) + if status == 0 and len(result) == 1: + return result[0] + logs.append(f"action {action:#x} status {status}" + (f": {log}" if log else "")) + raise OracleError("comgr produced no output\n" + "\n".join(logs)) + + +def assemble(source, isa_target, wave64): + isa = f"amdgcn-amd-amdhsa--{isa_target}" + inputs = data_set(DATA_SOURCE, b"k.s", source) + try: + relocatable = comgr_step(ASSEMBLE_ACTIONS, isa, [b"-mwavefrontsize64"] if wave64 else [], inputs, DATA_RELOCATABLE) + finally: + comgr().amd_comgr_destroy_data_set(inputs) + objects = data_set(DATA_RELOCATABLE, b"k.o", relocatable) + try: + return comgr_step(LINK_ACTIONS, isa, [], objects, DATA_EXECUTABLE) + finally: + comgr().amd_comgr_destroy_data_set(objects) + + +def dispatch(code, threads, payload, out_dwords, coarse): + library = hip() + module = ctypes.c_void_p() + check(library.hipModuleLoadData(ctypes.byref(module), code), "hipModuleLoadData") + try: + function = ctypes.c_void_p() + check(library.hipModuleGetFunction(ctypes.byref(function), module, b"probe"), "hipModuleGetFunction") + out_size = threads * out_dwords * 4 + buffers = [] + try: + for size in (len(payload), out_size): + pointer = ctypes.c_void_p() + if coarse: + check(library.hipMalloc(ctypes.byref(pointer), ctypes.c_size_t(size)), "hipMalloc") + else: + check(library.hipHostMalloc(ctypes.byref(pointer), ctypes.c_size_t(size), 0), "hipHostMalloc") + buffers.append(pointer) + device_in, device_out = buffers + check(library.hipMemcpy(device_in, payload, len(payload), HIP_MEMCPY_HOST_TO_DEVICE), "hipMemcpy") + check(library.hipMemset(device_out, 0xee, out_size), "hipMemset") + arguments = (ctypes.c_void_p * 2)(ctypes.addressof(device_in), ctypes.addressof(device_out)) + check(library.hipModuleLaunchKernel(function, 1, 1, 1, min(threads, 1024), 1, 1, 0, None, arguments, None), "hipModuleLaunchKernel") + check(library.hipDeviceSynchronize(), "hipDeviceSynchronize") + output = ctypes.create_string_buffer(out_size) + check(library.hipMemcpy(output, device_out, out_size, HIP_MEMCPY_DEVICE_TO_HOST), "hipMemcpy") + return output.raw + finally: + for pointer in buffers: + (library.hipFree if coarse else library.hipHostFree)(pointer) + finally: + library.hipModuleUnload(module) + + +def main(argv=None): + parser = argparse.ArgumentParser(description="Assemble and run hw-oracle kernels through the HIP runtime and comgr of AMD's Windows driver.") + parser.add_argument("--target", action="store_true", help="print the gfx target of the HIP device") + parser.add_argument("--assemble", nargs=3, metavar=("SOURCE", "OUTPUT", "TARGET"), help="assemble and link SOURCE for TARGET into OUTPUT") + parser.add_argument("--wave64", action="store_true", help="assemble for wave64") + parser.add_argument("dispatch", nargs="*", metavar="CODE THREADS IN OUT OUT_DWORDS", help="run CODE as oracle does") + args = parser.parse_args(argv) + try: + if args.target and not args.assemble and not args.dispatch: + print(target()) + return 0 + if args.assemble and not args.target and not args.dispatch: + source, output, isa_target = args.assemble + Path(output).write_bytes(assemble(Path(source).read_bytes(), isa_target, args.wave64)) + return 0 + if len(args.dispatch) != 5 or args.target or args.assemble or args.wave64: + parser.error("give --target, --assemble SOURCE OUTPUT TARGET, or CODE THREADS IN OUT OUT_DWORDS") + code, threads, inputs, output, out_dwords = args.dispatch + threads, out_dwords = int(threads), int(out_dwords) + if threads not in range(1, 1025) or out_dwords not in range(1, 17): + parser.error("THREADS must be 1 to 1024 and OUT_DWORDS 1 to 16") + result = dispatch(Path(code).read_bytes(), threads, Path(inputs).read_bytes(), out_dwords, os.environ.get("COARSE") is not None) + Path(output).write_bytes(result) + return 0 + except (OSError, OracleError) as error: + print(error, file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/hw-oracle/test_hw_oracle.py b/tools/hw-oracle/test_hw_oracle.py index 58f66cf38c..e234145299 100644 --- a/tools/hw-oracle/test_hw_oracle.py +++ b/tools/hw-oracle/test_hw_oracle.py @@ -10,6 +10,7 @@ import sys import tempfile import unittest from pathlib import Path +import unittest.mock from unittest.mock import patch import hw_oracle @@ -18,6 +19,10 @@ import hw_oracle MODES = dict(ieee=0, denorm32=0, denorm16=3, dx10_clamp=1, round32=0, round16=0, fp16_overflow=0) +def setUpModule(): + os.environ["HW_ORACLE_RUNTIME"] = "hsa" + + class OracleCacheTests(unittest.TestCase): def test_failed_build_is_not_cached_and_can_be_retried(self): for previous in (None, b"old executable"): @@ -642,5 +647,136 @@ class AssemblyTests(unittest.TestCase): self.assertTrue(code.is_file()) +class HipRuntimeTests(unittest.TestCase): + def test_runtime_defaults_to_hip_on_windows_only(self): + environment = {key: value for key, value in os.environ.items() if key != "HW_ORACLE_RUNTIME"} + with patch.dict(os.environ, environment, clear=True): + self.assertEqual(hw_oracle.runtime(), "hip" if os.name == "nt" else "hsa") + for selected in hw_oracle.RUNTIMES: + with patch.dict(os.environ, {"HW_ORACLE_RUNTIME": selected}): + self.assertEqual(hw_oracle.runtime(), selected) + with patch.dict(os.environ, {"HW_ORACLE_RUNTIME": "rocm"}), self.assertRaises(ValueError): + hw_oracle.runtime() + + def test_hip_run_assembles_and_dispatches_in_process(self): + calls = [] + + class Runtime: + @staticmethod + def target(): + calls.append(("target",)) + return "gfx1010" + + @staticmethod + def assemble(source, isa_target, wave64): + calls.append(("assemble", isa_target, wave64, b"v_mov_b32 v10, v4" in source)) + return b"code" + + @staticmethod + def dispatch(code, threads, payload, out_dwords, coarse): + calls.append(("dispatch", code, threads, len(payload), out_dwords, coarse)) + return bytes(threads * out_dwords * 4) + + environment = {key: value for key, value in os.environ.items() if key != "HW_ORACLE_TARGET"} + for wave64, coarse in ((False, False), (True, True)): + calls.clear() + with self.subTest(wave64=wave64), patch.dict(os.environ, {**environment, "HW_ORACLE_RUNTIME": "hip"}, clear=True), patch.object(hw_oracle, "hip_runtime", return_value=Runtime), patch.object(hw_oracle.subprocess, "run", side_effect=AssertionError("subprocess called")), patch.object(hw_oracle, "oracle", side_effect=AssertionError("oracle built")): + results = hw_oracle.run(" v_mov_b32 v10, v4", [(1, 2, 3, 4)], b"xy", wave64, coarse, **MODES) + wave = 64 if wave64 else 32 + self.assertEqual(results, [(0,) * 16]) + self.assertIn(("target",), calls) + self.assertEqual([call for call in calls if call != ("target",)], + [("assemble", "gfx1010", wave64, True), ("dispatch", b"code", wave, wave * 16 + 2, 16, coarse)]) + + def test_hip_output_must_match_the_padded_dispatch_size(self): + class Runtime: + @staticmethod + def assemble(source, isa_target, wave64): + return b"code" + + @staticmethod + def dispatch(code, threads, payload, out_dwords, coarse): + return bytes(threads * out_dwords * 4 - 4) + + with patch.dict(os.environ, {"HW_ORACLE_RUNTIME": "hip", "HW_ORACLE_TARGET": "gfx1010"}), patch.object(hw_oracle, "hip_runtime", return_value=Runtime), self.assertRaises(ValueError): + hw_oracle.run(" s_nop 0", [(0, 0, 0, 0)], **MODES) + + def test_hip_runtime_loads_the_script_next_to_the_tool(self): + self.assertEqual(Path(hw_oracle.hip_runtime().__file__), hw_oracle.HERE / "oracle_hip.py") + + +class HipOracleTests(unittest.TestCase): + def setUp(self): + import oracle_hip + self.oracle_hip = oracle_hip + + def test_cli_rejects_mixed_or_incomplete_requests(self): + for argv in ([], ["--target", "code", "32", "in", "out", "16"], ["--wave64", "code", "32", "in", "out", "16"], + ["code", "32", "in", "out"], ["code", "0", "in", "out", "16"], ["code", "1025", "in", "out", "16"], + ["code", "32", "in", "out", "17"]): + with self.subTest(argv=argv), contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as exit: + self.oracle_hip.main(argv) + self.assertEqual(exit.exception.code, 2) + + def test_comgr_step_tries_each_action_until_one_produces_output(self): + library = unittest.mock.MagicMock() + for name in ("amd_comgr_create_action_info", "amd_comgr_create_data_set", "amd_comgr_action_info_set_isa_name", + "amd_comgr_action_info_set_logging", "amd_comgr_action_info_set_option_list"): + getattr(library, name).return_value = 0 + outputs = {0x8: [], 0xA: [b"object"]} + actions = [] + library.amd_comgr_do_action.side_effect = lambda action, *args: actions.append(action) or (0 if action == 0xA else 1) + + def data_bytes(data_set, kind): + return [b"log"] if kind == self.oracle_hip.DATA_LOG else outputs[actions[-1]] + + isa = "amdgcn-amd-amdhsa--gfx1010" + with patch.object(self.oracle_hip, "comgr", return_value=library), patch.object(self.oracle_hip, "data_bytes", side_effect=data_bytes): + self.assertEqual(self.oracle_hip.comgr_step((0x8, 0xA), isa, [b"-mwavefrontsize64"], self.oracle_hip.Handle(), + self.oracle_hip.DATA_RELOCATABLE), b"object") + self.assertEqual(actions, [0x8, 0xA]) + outputs[0xA] = [] + with self.assertRaises(self.oracle_hip.OracleError) as error: + self.oracle_hip.comgr_step((0x8, 0xA), isa, [], self.oracle_hip.Handle(), self.oracle_hip.DATA_RELOCATABLE) + self.assertIn("action 0x8 status 1: log", str(error.exception)) + self.assertIn("action 0xa status 0: log", str(error.exception)) + + def test_device_selection_follows_rocr_visible_devices(self): + library = unittest.mock.MagicMock() + library.hipInit.return_value = 0 + + def count(pointer): + pointer._obj.value = 1 + return 0 + + library.hipGetDeviceCount.side_effect = count + base = {key: value for key, value in os.environ.items() if key not in ("ROCR_VISIBLE_DEVICES", "HIP_VISIBLE_DEVICES")} + for environment, expected in (({"ROCR_VISIBLE_DEVICES": "1"}, "1"), ({"ROCR_VISIBLE_DEVICES": "1", "HIP_VISIBLE_DEVICES": "0"}, "0")): + self.oracle_hip.hip.cache_clear() + with self.subTest(environment=environment), patch.dict(os.environ, {**base, **environment}, clear=True), \ + patch.object(self.oracle_hip, "load", return_value=library): + self.oracle_hip.hip() + self.assertEqual(os.environ["HIP_VISIBLE_DEVICES"], expected) + self.oracle_hip.hip.cache_clear() + + def test_assembles_both_wave_sizes_with_the_driver_comgr(self): + if os.name != "nt": + self.skipTest("comgr from AMD's Windows driver") + try: + self.oracle_hip.comgr() + except self.oracle_hip.OracleError as error: + self.skipTest(str(error)) + template = (hw_oracle.HERE / "template.s").read_text() + for wave64, mask in ((False, "s8"), (True, "s[8:9]")): + text = template + for key, value in (("@TARGET@", "gfx1010"), ("@WAVE32@", "0" if wave64 else "1"), ("@WAVESIZE@", "64" if wave64 else "32"), + ("@DENORM@", "0"), ("@DENORM16@", "3"), ("@IEEE@", "0"), ("@DX10_CLAMP@", "1"), ("@ROUND32@", "0"), + ("@ROUND16@", "0"), ("@FP16_OVERFLOW@", "0"), ("@LDS@", "4096"), + ("@BODY@", f" v_cmp_lt_f32 {mask}, v4, v5\n v_cndmask_b32 v10, v6, v7, {mask}\n")): + text = text.replace(key, value) + with self.subTest(wave64=wave64): + self.assertEqual(self.oracle_hip.assemble(text.encode(), "gfx1010", wave64)[:4], b"\x7fELF") + + if __name__ == "__main__": unittest.main() diff --git a/tools/import_audit.py b/tools/import_audit.py index 6ee58ad673..83ad3722d4 100644 --- a/tools/import_audit.py +++ b/tools/import_audit.py @@ -2,6 +2,7 @@ import argparse import hashlib import json import os +import re import struct import subprocess import sys @@ -15,7 +16,12 @@ import progress ROOT = Path(__file__).resolve().parent.parent PRX = ROOT / "core" / "libs" / "prx" MODULE_SUFFIX = "#" -CLASSES = ("implemented", "stub", "absent", "module") +CLASSES = ("implemented", "partial", "stub", "absent", "module") +CONDITIONAL = re.compile(r"(?:if|else|for|while|do|switch|case|default|try|catch|co_await|co_yield)\b") +BRANCHING = re.compile(r"\?|&&|\|\||\b(?:and|or)\b") +EXIT = re.compile(r"\b(?:return|co_return|goto)\b") +PREPROCESSOR_LINE = re.compile(r"^[ \t]*#[ \t]*(ifdef|ifndef|if|elifdef|elifndef|elif|else|endif)\b[^\n]*$", re.M) +BRANCH_VARIANT_LIMIT = 256 PE_DIRECTORY_OFFSET = {0x20b: 112, 0x10b: 96} @@ -84,16 +90,21 @@ def elf_program_dynamic_symbols(path, data): _, _, dynamic_offset, _, _, dynamic_size, _, _ = dynamic_segments[0] if dynamic_size % 16: raise AuditError(f"{path}: dynamic segment size {dynamic_size} is not a multiple of 16") + take(data, dynamic_offset, dynamic_size, path, "dynamic segment") dynamic = {} needed_offsets = [] + terminated = False for position in range(0, dynamic_size, 16): tag, value = unpack("<qQ", data, dynamic_offset + position, path, "dynamic entry") if tag == 0: + terminated = True break if tag == 1: needed_offsets.append(value) else: dynamic[tag] = value + if not terminated: + raise AuditError(f"{path}: dynamic segment is not terminated by DT_NULL") string_address = dynamic.get(5) string_size = dynamic.get(10) symbol_address = dynamic.get(6) @@ -134,7 +145,7 @@ def elf_program_dynamic_symbols(path, data): if symbol_count is None: raise AuditError(f"{path}: dynamic symbol table has no supported hash table") symbol_offset = file_offset(symbol_address, symbol_count * symbol_size, "dynamic symbol table") - imports = set() + imports = {} exports = set() for index in range(symbol_count): name, info, other, section_index, _, _ = unpack("<IBBHQQ", data, symbol_offset + index * symbol_size, path, "dynamic symbol") @@ -142,7 +153,7 @@ def elf_program_dynamic_symbols(path, data): continue symbol = cstring(data, string_offset + name, string_offset + string_size, path, "dynamic symbol name").split(MODULE_SUFFIX, 1)[0] if section_index == 0: - imports.add(symbol) + imports[symbol] = imports.get(symbol, True) and info >> 4 == 2 elif info >> 4 in (1, 2, 10) and other & 3 in (0, 3): exports.add(symbol) needed = {cstring(data, string_offset + value, string_offset + string_size, path, "needed library name") for value in needed_offsets} @@ -190,31 +201,38 @@ def elf_imports(path, data): imports, _, needed = elf_program_dynamic_symbols(path, data) return imports, needed sections = elf_sections(path, data) - symbols = set() + symbols = {} needed = set() for section in sections: kind, offset, size, link, entry = section[1], section[4], section[5], section[6], section[9] if kind == 11: if entry != 24 or size % 24 or link >= len(sections) or sections[link][1] != 3: raise AuditError(f"{path}: invalid dynamic symbol table (entry size {entry}, size {size}, string table section {link})") + take(data, offset, size, path, "dynamic symbol table") strings_offset, strings_size = sections[link][4], sections[link][5] take(data, strings_offset, strings_size, path, "dynamic string table") for position in range(0, size, 24): - name, _, _, index, _, _ = unpack("<IBBHQQ", data, offset + position, path, "dynamic symbol") + name, info, _, index, _, _ = unpack("<IBBHQQ", data, offset + position, path, "dynamic symbol") if name and index == 0: symbol = cstring(data, strings_offset + name, strings_offset + strings_size, path, "import name") - symbols.add(symbol.split(MODULE_SUFFIX, 1)[0]) + symbol = symbol.split(MODULE_SUFFIX, 1)[0] + symbols[symbol] = symbols.get(symbol, True) and info >> 4 == 2 elif kind == 6: if entry != 16 or size % 16 or link >= len(sections) or sections[link][1] != 3: raise AuditError(f"{path}: invalid dynamic section (entry size {entry}, size {size}, string table section {link})") + take(data, offset, size, path, "dynamic section") strings_offset, strings_size = sections[link][4], sections[link][5] take(data, strings_offset, strings_size, path, "dynamic string table") + terminated = False for position in range(0, size, 16): tag, value = unpack("<qQ", data, offset + position, path, "dynamic entry") if tag == 0: + terminated = True break if tag == 1: needed.add(cstring(data, strings_offset + value, strings_offset + strings_size, path, "needed library name")) + if not terminated: + raise AuditError(f"{path}: dynamic section is not terminated by DT_NULL") return symbols, needed @@ -258,6 +276,8 @@ def pe_headers(path, data): def pe_exports(path, data): to_offset, directory, _ = pe_headers(path, data) rva, size = unpack("<II", data, directory, path, "export data directory") + if not rva and not size: + return set() if not rva or size < 40: raise AuditError(f"{path}: PE file has no export table") @@ -449,7 +469,7 @@ def read_modules(directories): names = pe_exports(file, data) else: symbols, needed = elf_imports(file, data) - imports[file.name] = (sorted(symbols), sorted(needed)) + imports[file.name] = (sorted(symbols.items()), sorted(needed)) names = elf_exports(file, data) for name in names: exports[name.split(MODULE_SUFFIX, 1)[0]].add(file.name) @@ -462,23 +482,144 @@ def nids_of(name, aliases): return {nid_names.compute_nid(nid_names.strip_nid_postfix(name))} +def top_level_statements(body): + braces, parens, start, parts = 0, 0, 1, [] + for i, character in enumerate(body[1:-1], 1): + if character in "([": + parens += 1 + elif character in ")]": + parens -= 1 + elif character == "{": + braces += 1 + elif character == "}": + braces -= 1 + if not braces and not parens: + parts.append((start, body[start:i + 1])) + start = i + 1 + elif character == ";" and not braces and not parens: + parts.append((start, body[start:i + 1])) + start = i + 1 + parts.append((start, body[start:-1])) + return parts + + +def branch_variants(body): + root = [] + lists, groups = [root], [] + position = 0 + for directive in PREPROCESSOR_LINE.finditer(body): + lists[-1].append(body[position:directive.start()]) + position = directive.end() + kind = directive.group(1) + if kind in ("if", "ifdef", "ifndef"): + group = {"alternatives": [[]], "default": False} + lists[-1].append(group) + groups.append(group) + lists.append(group["alternatives"][-1]) + continue + if not groups: + raise AuditError(f"#{kind} without a matching #if") + group = groups[-1] + lists.pop() + if kind == "endif": + if not group["default"]: + group["alternatives"].append([]) + groups.pop() + continue + group["default"] = group["default"] or kind == "else" + group["alternatives"].append([]) + lists.append(group["alternatives"][-1]) + if groups: + raise AuditError("#if without a matching #endif") + root.append(body[position:]) + + def expand(items): + variants = [""] + for item in items: + if isinstance(item, str): + variants = [variant + item for variant in variants] + continue + choices = [choice for alternative in item["alternatives"] for choice in expand(alternative)] + variants = [variant + choice for variant in variants for choice in choices] + if len(variants) > BRANCH_VARIANT_LIMIT: + raise AuditError(f"more than {BRANCH_VARIANT_LIMIT} preprocessor branch combinations in one function") + return variants + + return expand(root) + + +def variant_calls(body): + return [unconditional_calls(variant) for variant in branch_variants(body)] + + +def unconditional_calls(body): + names = set() + for offset, statement in top_level_statements(body): + block = statement.strip() + if block.startswith("{") and block.endswith("}"): + names |= unconditional_calls(block) + elif not (CONDITIONAL.match(block) or BRANCHING.search(statement)): + for call in progress.CALL.finditer(statement): + before = statement[:call.start()] + if before.count("{") == before.count("}") and not progress.QUALIFIED.search(body, 0, offset + call.start()): + names.add(call.group(1)) + if EXIT.search(statement): + break + return names + + +def always_stub_calls(text): + helpers = {} + for match in progress.STUB_WRAPPER.finditer(text): + body = text[match.end() - 1:progress.body_end(text, match.end() - 1) + 1] + helpers.setdefault(match.group(1), []).extend(variant_calls(body)) + calls = {progress.STUB} + while True: + found = {name for name, bodies in helpers.items() if name not in calls and all(targets & calls for targets in bodies)} + if not found: + return calls + calls |= found + + +def conditional_stubs(library): + conditional = set() + for source in library.rglob("*.cpp"): + if "tests" in source.relative_to(library).parts: + continue + text = progress.code(source.read_text(errors="ignore")) + calls = always_stub_calls(text) + for match in progress.DEFINITION.finditer(text): + body = text[match.end() - 1:progress.body_end(text, match.end() - 1) + 1] + if not all(targets & calls for targets in variant_calls(body)): + conditional.add(match.group(1)) + return conditional + + def source_stubs(source): + return source_stub_classes(source)[0] + + +def source_stub_classes(source): if not Path(source).is_dir(): raise AuditError(f"{source}: not a directory") stubs = {} + partial = set() shared = {} for library in sorted(item for item in Path(source).iterdir() if item.is_dir()): aliases = {} for file in nid_names.source_files(library): - for nid, name in nid_names.source_aliases(file.read_text(errors="ignore")): + for nid, name in nid_names.source_aliases(file.read_text(encoding="utf-8-sig", errors="ignore")): aliases.setdefault(name, set()).add(nid) group = progress.scan_library(library) if "shared_sources" in group: shared[library.name] = group["shared_sources"] done = {nid for name in group["done_names"] for nid in nids_of(name, aliases)} + conditional = conditional_stubs(library) if group["todo_names"] else set() for name in group["todo_names"]: for nid in nids_of(name, aliases) - done: stubs.setdefault((library.name + ".prx", nid), name) + if name in conditional: + partial.add((library.name + ".prx", nid)) for library, owner in shared.items(): chain = [library] while owner in shared: @@ -489,7 +630,9 @@ def source_stubs(source): for (provider, nid), name in list(stubs.items()): if provider == owner + ".prx": stubs[(library + ".prx", nid)] = name - return stubs + if (provider, nid) in partial: + partial.add((library + ".prx", nid)) + return stubs, partial def read_registry(path): @@ -538,11 +681,13 @@ MODULE_FILE_SUFFIXES = (".debug_prx", ".suprx", ".sprx", ".prx") def module_stem(name, fold): + if fold: + name = name.lower() for suffix in MODULE_FILE_SUFFIXES: if len(name) > len(suffix) and name.endswith(suffix): name = name[:-len(suffix)] break - return name.lower() if fold else name + return name class ModuleNames(set): @@ -558,55 +703,77 @@ class ModuleNames(set): return isinstance(library, str) and module_stem(library, self.fold) in self.stems -def classify(nid, library, providers, stubs, modules): +def classify(nid, library, providers, stubs, modules, partial=frozenset()): if library and library in modules: return "module" if not providers: return "absent" if all((provider, nid) in stubs for provider in providers): - return "stub" + return "partial" if any((provider, nid) in partial for provider in providers) else "stub" return "implemented" -def audit(imports, exports, stubs, libraries, modules, module_imports=None, module_exports=None, *, needed_libraries=()): +def audit(imports, exports, stubs, libraries, modules, module_imports=None, module_exports=None, *, needed_libraries=(), windows_libraries=(), partial=frozenset()): + windows_names = {} + for library in windows_libraries: + windows_names.setdefault(library.casefold(), set()).add(library) + + def system_library(name): + if name: + for suffix in (".sprx", ".suprx", ".debug_prx"): + if name.endswith(suffix): + normalized = name.removesuffix(suffix) + ".prx" + candidates = windows_names.get(normalized.casefold(), ()) + if normalized in candidates: + return normalized + if len(candidates) == 1: + return next(iter(candidates)) + return name + + def available(name): + return name in modules or system_library(name) in libraries + records = {} for nid, library, weak in imports: record = records.setdefault((nid, library), {"nid": nid, "library": library, "references": 0, "weak": weak}) record["references"] += 1 record["weak"] = record["weak"] and weak for module, (symbols, needed) in (module_imports or {}).items(): - for nid in symbols: - record = records.setdefault((nid, None, module), {"nid": nid, "library": None, "module": module, "references": 0, "weak": False}) + for nid, weak in symbols: + record = records.setdefault((nid, None, module), {"nid": nid, "library": None, "module": module, "references": 0, "weak": weak}) record["references"] += 1 + record["weak"] = record["weak"] and weak for record in records.values(): nid, library = record["nid"], record["library"] providers = exports.get(nid, []) if not library: providers = sorted(set(providers) | set((module_exports or {}).get(nid, []))) resolved_nid = nid + record["symbol_mismatch"] = False if not providers: resolved_nid = nid_names.compute_nid(nid) providers = exports.get(resolved_nid, []) + record["symbol_mismatch"] = bool(library) and library not in modules and bool(providers) and resolved_nid != nid if not library: providers = sorted(set(providers) | set((module_exports or {}).get(resolved_nid, []))) - kind = classify(resolved_nid, library, providers, stubs, modules) + kind = classify(resolved_nid, library, providers, stubs, modules, partial) record["class"] = kind record["providers"] = providers - record["library_mismatch"] = kind in ("implemented", "stub") and bool(library) and library not in providers and "libc.prx" not in providers - record["name"] = next((stubs[(provider, resolved_nid)] for provider in providers if (provider, resolved_nid) in stubs), None) if kind == "stub" else None + record["library_mismatch"] = kind in ("implemented", "partial", "stub") and bool(library) and system_library(library) not in providers and "libc.prx" not in providers + record["name"] = next((stubs[(provider, resolved_nid)] for provider in providers if (provider, resolved_nid) in stubs), None) if kind in ("partial", "stub") else None ordered = sorted(records.values(), key=lambda record: (record["class"], record["library"] or "", record.get("module") or "", record["nid"])) check_conservation(len(imports) + sum(len(symbols) for symbols, _ in (module_imports or {}).values()), ordered) missing = defaultdict(int) for library in needed_libraries: - if library not in libraries and library not in modules: + if not available(library): missing[library] = 0 for record in ordered: library = record["library"] - if library and library not in libraries and library not in modules: + if library and not available(library): missing[library] += 1 for _, needed in (module_imports or {}).values(): for library in needed: - if library not in libraries and library not in modules: + if not available(library): missing[library] += 1 return ordered, dict(sorted(missing.items())) @@ -618,7 +785,7 @@ def current_stop(imports, records): if weak: continue record = classes.get((nid, library)) - if record is not None and record["class"] == "absent": + if record is not None and (record["class"] == "absent" or record["symbol_mismatch"]): return record return None @@ -677,6 +844,7 @@ def summarize(records, references, missing, missing_hosts=()): "missing_libraries": missing, "missing_host_dependencies": [{"importer": entry["importer"], "chain": list(entry["chain"])} for entry in missing_hosts], "library_mismatch": sum(1 for record in records if record["library_mismatch"]), + "symbol_mismatch": sum(1 for record in records if record["symbol_mismatch"]), } @@ -686,6 +854,8 @@ def label(record): else: mark = "" if record.get("name_verified", True) else " (name does not hash to this NID)" text = f"{record['nid']} {record['name']}{mark}" + if record["weak"]: + text += " (weak import)" return f"{text} (imported by {record['module']})" if record.get("module") else text @@ -711,7 +881,8 @@ def render(summary, records, missing, stops, missing_hosts=(), located=None): lines = [f"{summary['references']} references, {summary['unique_imports']} unique imports"] for kind in CLASSES: lines.append(f" {kind:<12}{summary['unique_by_class'][kind]:>6} imports {summary['references_by_class'][kind]:>6} references") - for title, kind in (("Absent: no built library exports these, so the loader fails", "absent"), ("Stub: exported but throws when called", "stub")): + for title, kind in (("Absent: no built library exports these", "absent"), ("Stub: exported but throws when called", "stub"), + ("Partial: exported, throws only for inputs whose behaviour is unknown", "partial")): selected = [record for record in records if record["class"] == kind] if selected: lines.append("") @@ -722,6 +893,10 @@ def render(summary, records, missing, stops, missing_hosts=(), located=None): current = record["library"] lines.append(f" [{current or 'library not named'}]") lines.append(f" {label(record)}") + mismatched = [record for record in records if record["symbol_mismatch"]] + if mismatched: + lines.extend(["", f"Qualified import names exported only as their NID ({len(mismatched)}): the loader cannot resolve these names"]) + lines.extend(f" {label(record)} -> {nid_names.compute_nid(record['nid'])}" for record in mismatched) if stops: lines.append("") lines.append("Current stop (first unresolved import, in registry order)") @@ -747,6 +922,10 @@ def render(summary, records, missing, stops, missing_hosts=(), located=None): def main(argv=None): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="classify the imports of a converted game against the built system libraries") parser.add_argument("registries", nargs="+", type=Path, help="JSON written by 'relinker --registry'") parser.add_argument("--libs", action="append", required=True, help="directory of built .prx files; repeat for several") @@ -766,6 +945,7 @@ def main(argv=None): inputs += [file for directory in args.libs for file in directory_files(directory, "*.prx")] inputs += [file for directory in args.modules for file in directory_files(directory, "*")] inputs += list(Path(args.source).rglob("*.cpp")) + inputs += [file for file in Path(args.source).glob("*/CMakeLists.txt") if file.is_file()] output = args.json.resolve() if any(output == path.resolve() or (args.json.exists() and args.json.samefile(path)) for path in inputs): raise AuditError(f"{args.json}: JSON output would overwrite an input file") @@ -778,7 +958,8 @@ def main(argv=None): modules = set(module_imports) modules |= {name.removesuffix(".guest.prx") for name in modules if name.endswith(".guest.prx")} modules = ModuleNames(modules, fold=bool(windows)) - records, missing = audit(imports, exports, source_stubs(args.source), libraries, modules, module_imports, module_exports, needed_libraries=needed) + stubs, partial = source_stub_classes(args.source) + records, missing = audit(imports, exports, stubs, libraries, modules, module_imports, module_exports, needed_libraries=needed, windows_libraries=windows, partial=partial) missing_hosts = audit_host_dependencies(host_dependencies, libraries, modules, windows) if args.names: attach_names(records, load_names(args.names)) @@ -800,11 +981,8 @@ def main(argv=None): return 2 located = locate_missing(missing, {path.resolve().parent for path in args.registries}) if missing else None print(render(summary, records, missing, stops, missing_hosts, located)) - return 1 if summary["unique_by_class"]["absent"] or missing or missing_hosts or (args.strict_providers and summary["library_mismatch"]) or (args.strict_stubs and summary["unique_by_class"]["stub"]) else 0 + return 1 if summary["unique_by_class"]["absent"] or summary["symbol_mismatch"] or missing or missing_hosts or (args.strict_providers and summary["library_mismatch"]) or (args.strict_stubs and summary["unique_by_class"]["stub"]) else 0 if __name__ == "__main__": - for stream in (sys.stdout, sys.stderr): - if hasattr(stream, "reconfigure"): - stream.reconfigure(encoding="utf-8", errors="replace") raise SystemExit(main()) diff --git a/tools/input-mapper/app.js b/tools/input-mapper/app.js new file mode 100644 index 0000000000..4703578fe0 --- /dev/null +++ b/tools/input-mapper/app.js @@ -0,0 +1,446 @@ +import {actions, defaults, groups, mouseValues, typesForAction, wheelValues} from './mapping.js'; +import {exportableKeys} from './keys.js'; +import {captureKey} from './capture.js'; +import {parseIni, serialize, validate} from './validation.js'; +import {diagnostic, initialLocale, locales, translate} from './i18n.js'; +import {createDropdown} from './dropdown.js'; + +const byId = id => document.getElementById(id); +let entries = []; +let savedConfiguration = '[]'; +let captureTarget; +let captureButton; +let selectedGroup = groups[0].name; +let locale = initialLocale(); +let languageControl; +const bindingControls = new WeakMap(); +let activeView = ['controls', 'configuration', 'help'].includes(location.hash.slice(1)) ? location.hash.slice(1) : 'controls'; +let currentStatus; +let importReport; +const dialog = byId('capture-dialog'); +try { + const savedLocale = localStorage.getItem('anyps5-controls-language'); + locale = initialLocale(savedLocale); +} catch {} +const t = (key, values) => translate(locale, key, values); +const groupLabel = name => t(`group${groups.findIndex(group => group.name === name)}`); + +function setStatus(key, values = {}) { + currentStatus = {key, values}; + byId('status').textContent = t(key, values); +} + +function showView(view, focus = true, updateHash = true) { + activeView = view; + const controls = view === 'controls'; + byId('categories').hidden = !controls; + byId('toolbar').hidden = !controls; + byId('mappings').hidden = !controls; + byId('configuration').hidden = view !== 'configuration'; + byId('help').hidden = view !== 'help'; + byId('validation').hidden = view === 'help'; + byId('status').hidden = view === 'help'; + byId('import-report').hidden = view === 'help' || !importReport; + byId('view-intro').textContent = t(controls ? 'intro' : view === 'help' ? 'helpIntro' : 'configIntro'); + if (controls) filterActions(); + else byId('group-title').textContent = t(view); + for (const link of document.querySelectorAll('[data-view]')) { + const current = link.dataset.view === view; + link.classList.toggle('current', current); + if (current) link.setAttribute('aria-current', 'page'); + else link.removeAttribute('aria-current'); + } + if (updateHash && location.hash !== `#${view}`) history.pushState(null, '', `#${view}`); + if (focus) { + byId('content').focus({preventScroll: true}); + window.scrollTo({top: 0, behavior: 'auto'}); + } +} + +function renderHelp() { + const opened = [...byId('help').querySelectorAll('details')].map(item => item.open); + byId('help').replaceChildren(); + for (let index = 0; index < 4; index++) { + const question = element('details', undefined, 'faq'); + question.open = opened[index] || false; + question.append(element('summary', t(`faq${index}Question`)), element('p', t(`faq${index}Answer`))); + byId('help').append(question); + } +} + +function renderImportReport() { + if (!importReport) return; + const report = byId('import-report'); + report.replaceChildren(element('h2', t(importReport.errors.length ? 'importRejected' : 'importRead'))); + const messages = [...importReport.errors, ...importReport.warnings]; + if (messages.length) { + const list = element('ul'); + for (const item of messages) list.append(element('li', t('line', {line: item.line, message: diagnostic(locale, item.message)}))); + report.append(list); + } + if (importReport.errors.length) report.append(element('p', t('preserved'), 'error')); + report.hidden = false; +} + +function applyLocale() { + document.documentElement.lang = locale; + document.documentElement.dir = locales.find(item => item.code === locale).direction; + for (const node of document.querySelectorAll('[data-i18n]')) node.textContent = t(node.dataset.i18n); + for (const node of document.querySelectorAll('[data-i18n-label]')) node.setAttribute('aria-label', t(node.dataset.i18nLabel)); + for (const node of document.querySelectorAll('[data-i18n-placeholder]')) node.placeholder = t(node.dataset.i18nPlaceholder); + languageControl.label = t('language'); + languageControl.direction = document.documentElement.dir; + languageControl.text = dropdownText(); + languageControl.setValue(locale); + byId('settings').title = t('language'); + byId('categories').replaceChildren(); + renderCategories(); + renderHelp(); + render(); + renderImportReport(); + if (currentStatus) setStatus(currentStatus.key, currentStatus.values); + showView(activeView, false, false); + updateClock(); +} + +function element(tag, text, className) { + const node = document.createElement(tag); + if (text !== undefined) node.textContent = text; + if (className) node.className = className; + return node; +} + +function isDirty() { + return JSON.stringify(entries) !== savedConfiguration; +} + +async function confirmReplacement() { + if (!isDirty()) return true; + const confirmation = byId('replace-dialog'); + confirmation.returnValue = 'cancel'; + return new Promise(resolve => { + confirmation.addEventListener('close', () => resolve(confirmation.returnValue === 'discard'), {once: true}); + confirmation.showModal(); + }); +} + +function valueChoices(type) { + return type === 'KEY' ? exportableKeys : type === 'MOUSE' ? mouseValues : type === 'WHEEL' ? wheelValues : []; +} + +function dropdownText() { + return {search: t('searchOptions'), close: t('close'), empty: t('noOptions')}; +} + +function selectControl(label, choices, value) { + return createDropdown({label, choices: [{value: '', label: t('select')}, ...choices], value, placeholder: t('select'), text: dropdownText()}); +} + +function button(text, label, click) { + const control = element('button', text); + control.type = 'button'; + control.setAttribute('aria-label', label); + control.addEventListener('click', click); + return control; +} + +function renderCategories() { + const icons = ['face-icon', 'shoulder-icon', 'direction-icon', 'stick-icon', 'stick-icon', 'touch-icon']; + const symbols = ['△ ○ × □', 'L2 R2', '', 'L', 'R', '']; + groups.forEach((group, index) => { + const category = button('', groupLabel(group.name), () => { + selectedGroup = group.name; + byId('search').value = ''; + filterActions(); + }); + category.className = 'category'; + category.dataset.group = group.name; + const icon = element('span', undefined, 'category-icon'); + icon.setAttribute('aria-hidden', 'true'); + const mark = element('span', undefined, icons[index]); + if (index === 0) { + for (const symbol of symbols[index].split(' ')) mark.append(element('span', symbol)); + } else mark.textContent = symbols[index]; + icon.append(mark); + category.append(icon, element('span', t(`group${index}`), 'category-label')); + category.addEventListener('keydown', event => { + const direction = document.documentElement.dir === 'rtl' ? -1 : 1; + const offset = event.key === 'ArrowRight' ? direction : event.key === 'ArrowLeft' ? -direction : 0; + if (!offset && event.key !== 'Home' && event.key !== 'End') return; + event.preventDefault(); + const next = event.key === 'Home' ? 0 : event.key === 'End' ? groups.length - 1 : (index + offset + groups.length) % groups.length; + const target = byId('categories').children[next]; + target.click(); + target.focus(); + target.scrollIntoView({block: 'nearest', inline: 'nearest'}); + }); + byId('categories').append(category); + }); +} + +function renderBinding(entry, position) { + const row = element('div', undefined, 'binding'); + const label = t('bindingLabel', {action: entry.action, number: position + 1}); + const type = selectControl(t('typeLabel', {binding: label}), typesForAction(entry.action), entry.type); + const value = selectControl(t('valueLabel', {binding: label}), valueChoices(entry.type), entry.value); + type.element.dir = 'ltr'; + value.element.dir = 'ltr'; + const capture = button(t('capture'), t('captureLabel', {binding: label}), () => { + captureTarget = entry; + captureButton = capture; + byId('capture-title').textContent = t('captureTitle', {action: entry.action}); + byId('capture-status').textContent = ''; + dialog.showModal(); + }); + capture.disabled = entry.type !== 'KEY'; + const remove = button(t('remove'), t('removeLabel', {binding: label}), () => { + entries = entries.filter(binding => binding !== entry); + render(); + const remaining = document.querySelector(`[data-action="${entry.action}"] .dropdown-trigger`); + (remaining || document.querySelector(`[data-action="${entry.action}"] .add-binding`)).focus(); + }); + type.select.addEventListener('change', () => { + entry.type = type.select.value; + entry.value = ''; + value.setChoices([{value: '', label: t('select')}, ...valueChoices(entry.type)]); + value.setValue(''); + capture.disabled = entry.type !== 'KEY'; + updateOutput(); + }); + value.select.addEventListener('change', () => { + entry.value = value.select.value; + updateOutput(); + }); + row.append(type.element, value.element, capture, remove); + bindingControls.set(entry, value); + return row; +} + +function render() { + const container = byId('mappings'); + container.replaceChildren(); + for (const group of groups) { + const section = element('section', undefined, 'group'); + section.dataset.group = group.name; + section.append(element('h2', groupLabel(group.name))); + for (const action of group.actions) { + const article = element('div', undefined, 'action'); + article.dataset.action = action; + const heading = element('div', undefined, 'action-heading'); + const add = button(t('add'), t('addLabel', {action}), () => { + entries.push({action, type: 'KEY', value: ''}); + render(); + const rows = document.querySelectorAll(`[data-action="${action}"] .binding`); + rows[rows.length - 1].querySelectorAll('.dropdown-trigger')[1].focus(); + }); + add.className = 'add-binding'; + const title = element('div', undefined, 'action-title'); + const symbols = {Cross: '×', Circle: '○', Triangle: '△', Square: '□', Up: '↑', Right: '→', Down: '↓', Left: '←'}; + if (symbols[action]) { + const symbol = element('span', symbols[action], 'action-symbol'); + symbol.setAttribute('aria-hidden', 'true'); + title.append(symbol); + } + const actionName = element('h3', action); + actionName.dir = 'ltr'; + title.append(actionName); + heading.append(title, add); + article.append(heading); + const configured = entries.filter(entry => entry.action === action); + article.append(element('p', configured.length ? t('custom') : t('builtin', {inputs: defaults[action].join(', ') || t('noDefault')}), 'hint')); + configured.forEach((entry, position) => article.append(renderBinding(entry, position))); + section.append(article); + } + container.append(section); + } + filterActions(); + updateOutput(); +} + +function filterActions() { + const search = byId('search').value.trim().toLowerCase(); + if (activeView === 'controls') byId('group-title').textContent = search ? t('search') : groupLabel(selectedGroup); + for (const category of byId('categories').children) { + category.setAttribute('aria-pressed', String(!search && category.dataset.group === selectedGroup)); + } + let visible = 0; + for (const section of document.querySelectorAll('.group')) { + let matches = 0; + for (const action of section.querySelectorAll('.action')) { + action.hidden = search ? !`${section.dataset.group} ${groupLabel(section.dataset.group)} ${action.dataset.action}`.toLowerCase().includes(search) : section.dataset.group !== selectedGroup; + if (!action.hidden) matches++; + } + section.hidden = matches === 0; + visible += matches; + } + let empty = byId('no-results'); + if (!empty) { + empty = element('p', t('noResults'), 'hint'); + empty.id = 'no-results'; + byId('mappings').append(empty); + } + empty.hidden = visible !== 0; +} + +function updateOutput() { + const result = validate(entries); + const valid = result.errors.length === 0; + byId('preview').value = valid ? serialize(entries) : ''; + byId('count').textContent = `${new Set(result.bindings.map(binding => binding.action)).size} / ${actions.length}`; + byId('download').disabled = !valid; + byId('copy').disabled = !valid; + const report = byId('validation'); + report.replaceChildren(); + for (const [name, messages] of [['error', result.errors], ['warning', result.warnings]]) { + if (!messages.length) continue; + const list = element('ul', undefined, name); + for (const item of messages) list.append(element('li', diagnostic(locale, item.message))); + report.append(list); + } + const rows = [...document.querySelectorAll('.binding')]; + const renderedEntries = groups.flatMap(group => group.actions.flatMap(action => entries.filter(entry => entry.action === action))); + rows.forEach((row, index) => { + const invalid = result.errors.some(error => entries[error.index] === renderedEntries[index]); + for (const control of row.querySelectorAll('.dropdown-trigger')) control.setAttribute('aria-invalid', String(invalid)); + }); +} + +function cancelCapture(message) { + captureTarget = undefined; + dialog.close(); + captureButton?.focus(); + if (message) setStatus(message); +} + +byId('cancel-capture').addEventListener('click', () => cancelCapture('captureCancelled')); +dialog.addEventListener('cancel', event => { + event.preventDefault(); + cancelCapture('captureCancelled'); +}); +window.addEventListener('blur', () => { + if (captureTarget) cancelCapture('captureBlur'); +}); +window.addEventListener('keydown', event => { + if (!captureTarget) return; + event.preventDefault(); + event.stopImmediatePropagation(); + if (event.code === 'Escape' || event.key === 'Escape') { + cancelCapture('captureCancelled'); + return; + } + if (event.repeat) return; + const result = captureKey(event); + if (result.error) { + byId('capture-status').textContent = diagnostic(locale, result.error); + return; + } + const target = captureTarget; + target.value = result.value; + cancelCapture(); + bindingControls.get(target)?.setValue(result.value); + updateOutput(); + setStatus('captured', {value: result.value, action: target.action}); +}, true); + +byId('search').addEventListener('input', filterActions); +byId('reset').addEventListener('click', async () => { + if (!await confirmReplacement()) return; + entries = []; + savedConfiguration = '[]'; + importReport = undefined; + byId('import-report').hidden = true; + render(); + setStatus('resetDone'); +}); +byId('import').addEventListener('click', () => byId('file').click()); +byId('file').addEventListener('change', async event => { + const file = event.target.files[0]; + if (!file) return; + event.target.value = ''; + try { + if (file.size > 1024 * 1024) throw new Error(t('fileTooLarge')); + const text = new TextDecoder('utf-8', {fatal: true}).decode(await file.arrayBuffer()); + const result = parseIni(text); + const report = byId('import-report'); + importReport = result; + renderImportReport(); + if (result.errors.length) { + report.focus(); + return; + } + if (!await confirmReplacement()) return; + entries = result.entries; + savedConfiguration = JSON.stringify(entries); + render(); + setStatus('imported', {name: file.name}); + } catch (error) { + setStatus('importFailed', {message: error instanceof TypeError ? t('invalidUtf8') : error.message}); + } +}); +byId('download').addEventListener('click', () => { + const content = serialize(entries); + const url = URL.createObjectURL(new Blob([content], {type: 'text/plain;charset=utf-8'})); + const link = element('a'); + link.href = url; + link.download = 'anyps5-input.ini'; + document.body.append(link); + link.click(); + link.remove(); + setTimeout(() => URL.revokeObjectURL(url), 1000); + savedConfiguration = JSON.stringify(entries); + setStatus('downloaded'); +}); +byId('copy').addEventListener('click', async () => { + try { + const content = serialize(entries); + const copiedConfiguration = JSON.stringify(entries); + await navigator.clipboard.writeText(content); + savedConfiguration = copiedConfiguration; + setStatus('copied'); + } catch { + byId('preview').focus(); + byId('preview').select(); + setStatus('clipboardFailed'); + } +}); +window.addEventListener('beforeunload', event => { + if (!isDirty()) return; + event.preventDefault(); + event.returnValue = ''; +}); +byId('focus-search').addEventListener('click', () => { + showView('controls'); + byId('search').focus(); +}); +for (const link of document.querySelectorAll('[data-view]')) { + link.addEventListener('click', event => { + event.preventDefault(); + showView(link.dataset.view); + }); +} +byId('back-controls').addEventListener('click', () => showView('controls')); +function restoreView() { + const view = location.hash.slice(1); + if (['controls', 'configuration', 'help'].includes(view)) showView(view, false, false); + else if (!view) showView('controls', false, false); +} +window.addEventListener('popstate', restoreView); +window.addEventListener('hashchange', restoreView); +languageControl = createDropdown({ + label: t('language'), choices: locales.map(item => ({value: item.code, label: item.name, lang: item.code, direction: item.direction})), + value: locale, placeholder: t('language'), text: dropdownText(), trigger: byId('settings'), direction: locales.find(item => item.code === locale).direction +}); +byId('language-control').append(languageControl.element); +languageControl.select.addEventListener('change', event => { + locale = event.target.value; + try { localStorage.setItem('anyps5-controls-language', locale); } catch {} + applyLocale(); +}); +function updateClock() { + const now = new Date(); + byId('clock').textContent = new Intl.DateTimeFormat(locale, {hour: '2-digit', minute: '2-digit'}).format(now); + byId('clock').dateTime = now.toISOString(); +} +updateClock(); +setInterval(updateClock, 60000); +applyLocale(); diff --git a/tools/input-mapper/capture.js b/tools/input-mapper/capture.js new file mode 100644 index 0000000000..05bbce0c36 --- /dev/null +++ b/tools/input-mapper/capture.js @@ -0,0 +1,30 @@ +import {exportableKeys} from './keys.js'; + +const codes = { + Enter: 'Return', Escape: 'Escape', Space: 'Space', Backspace: 'Backspace', Tab: 'Tab', + Minus: '-', Equal: '=', BracketLeft: '[', BracketRight: ']', Backslash: '\\', + Semicolon: ';', Quote: "'", Backquote: '`', Comma: ',', Period: '.', Slash: '/', + CapsLock: 'CapsLock', PrintScreen: 'PrintScreen', ScrollLock: 'ScrollLock', Pause: 'Pause', + Insert: 'Insert', Home: 'Home', PageUp: 'PageUp', Delete: 'Delete', End: 'End', PageDown: 'PageDown', + ArrowRight: 'Right', ArrowLeft: 'Left', ArrowDown: 'Down', ArrowUp: 'Up', NumLock: 'Numlock', + NumpadDivide: 'Keypad /', NumpadMultiply: 'Keypad *', NumpadSubtract: 'Keypad -', + NumpadAdd: 'Keypad +', NumpadEnter: 'Keypad Enter', NumpadDecimal: 'Keypad .', + NumpadEqual: 'Keypad =', NumpadComma: 'Keypad ,', ContextMenu: 'Application', + ControlLeft: 'Left Ctrl', ControlRight: 'Right Ctrl', ShiftLeft: 'Left Shift', ShiftRight: 'Right Shift', + AltLeft: 'Left Alt', AltRight: 'Right Alt', MetaLeft: 'Left GUI', MetaRight: 'Right GUI' +}; + +export function captureKey(event) { + if (event.isComposing || event.key === 'Dead' || event.key === 'Process') { + return {error: 'Composition and dead keys are ambiguous. Select an SDL key from the list.'}; + } + let value = codes[event.code]; + if (/^Key[A-Z]$/u.test(event.code)) value = event.code.slice(3); + else if (/^Digit[0-9]$/u.test(event.code)) value = event.code.slice(5); + else if (/^Numpad[0-9]$/u.test(event.code)) value = `Keypad ${event.code.slice(6)}`; + else if (/^F(?:[1-9]|1[0-9]|2[0-4])$/u.test(event.code)) value = event.code; + if (!value || !exportableKeys.includes(value)) { + return {error: 'This physical key cannot be captured in the current INI format. Select an SDL key from the list.'}; + } + return {value}; +} diff --git a/tools/input-mapper/dropdown.js b/tools/input-mapper/dropdown.js new file mode 100644 index 0000000000..63a962e2ea --- /dev/null +++ b/tools/input-mapper/dropdown.js @@ -0,0 +1,167 @@ +const dialog = document.getElementById('picker-dialog'); +const title = document.getElementById('picker-title'); +const search = document.getElementById('picker-search'); +const list = document.getElementById('picker-options'); +const empty = document.getElementById('picker-empty'); +const close = document.getElementById('picker-close'); +let active; +let visible = []; +let focused = 0; + +function renderOptions() { + const query = search.value.trim().toLocaleLowerCase(); + visible = active.choices.filter(item => `${item.label} ${item.value}`.toLocaleLowerCase().includes(query)); + list.replaceChildren(); + const exactMatch = query ? visible.findIndex(item => item.label.toLocaleLowerCase() === query || item.value.toLocaleLowerCase() === query) : -1; + focused = Math.max(0, exactMatch >= 0 ? exactMatch : visible.findIndex(item => item.value === active.select.value)); + visible.forEach((item, index) => { + const option = document.createElement('button'); + option.type = 'button'; + option.className = 'picker-option'; + option.setAttribute('role', 'option'); + option.setAttribute('aria-selected', String(item.value === active.select.value)); + option.tabIndex = index === focused ? 0 : -1; + option.textContent = item.label; + if (item.lang) option.lang = item.lang; + option.dir = item.direction || active.direction; + option.addEventListener('click', () => choose(item)); + option.addEventListener('focus', () => { focused = index; }); + list.append(option); + }); + empty.hidden = visible.length !== 0; + if (dialog.open) list.children[focused]?.scrollIntoView({block: 'nearest'}); +} + +function choose(item) { + const current = active; + current.setValue(item.value); + dialog.close(); + current.select.dispatchEvent(new Event('change', {bubbles: true})); +} + +function focusOption(index) { + if (!visible.length) return; + focused = (index + visible.length) % visible.length; + [...list.children].forEach((option, position) => { option.tabIndex = position === focused ? 0 : -1; }); + list.children[focused].focus({preventScroll: true}); + list.children[focused].scrollIntoView({block: 'nearest'}); +} + +function positionPicker() { + if (!dialog.open || !active) return; + if (matchMedia('(max-width: 600px)').matches) { + for (const name of ['top', 'left', 'width']) dialog.style.removeProperty(name); + return; + } + const rect = active.trigger.getBoundingClientRect(); + const width = Math.min(Math.max(rect.width, 320), innerWidth - 32); + dialog.style.width = `${width}px`; + const height = dialog.getBoundingClientRect().height; + const preferred = rect.bottom + 10; + const top = preferred + height <= innerHeight - 16 ? preferred : Math.max(16, rect.top - height - 10); + const left = active.direction === 'rtl' ? rect.right - width : rect.left; + dialog.style.top = `${Math.min(top, Math.max(16, innerHeight - height - 16))}px`; + dialog.style.left = `${Math.max(16, Math.min(left, innerWidth - width - 16))}px`; +} + +function openPicker(control) { + active = control; + title.textContent = control.label; + list.setAttribute('aria-label', control.label); + search.value = ''; + search.placeholder = control.text.search; + search.setAttribute('aria-label', control.text.search); + close.textContent = control.text.close; + empty.textContent = control.text.empty; + dialog.style.setProperty('--picker-height', `${Math.min(480, 136 + Math.min(control.choices.length, 8) * 46)}px`); + renderOptions(); + control.trigger.setAttribute('aria-expanded', 'true'); + dialog.showModal(); + positionPicker(); + list.children[focused]?.scrollIntoView({block: 'nearest'}); + search.focus(); +} + +search.addEventListener('input', renderOptions); +dialog.addEventListener('keydown', event => { + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + event.preventDefault(); + const inSearch = event.target === search; + focusOption(inSearch ? focused : focused + (event.key === 'ArrowDown' ? 1 : -1)); + } else if (event.target !== search && (event.key === 'Home' || event.key === 'End')) { + event.preventDefault(); + focusOption(event.key === 'Home' ? 0 : visible.length - 1); + } else if (event.target === search && event.key === 'Enter' && visible.length) { + event.preventDefault(); + choose(visible[focused]); + } +}); +close.addEventListener('click', () => dialog.close()); +dialog.addEventListener('click', event => { + if (event.target !== dialog) return; + const rect = dialog.getBoundingClientRect(); + if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) dialog.close(); +}); +dialog.addEventListener('close', () => { + const current = active; + active = undefined; + current?.trigger.setAttribute('aria-expanded', 'false'); + if (current?.trigger.isConnected) current.trigger.focus({preventScroll: true}); +}); +window.addEventListener('resize', positionPicker); + +export function createDropdown({label, choices, value, placeholder, text, trigger, direction = 'ltr'}) { + const wrapper = document.createElement('div'); + wrapper.className = 'dropdown'; + const select = document.createElement('select'); + select.hidden = true; + const providedTrigger = Boolean(trigger); + const control = trigger || document.createElement('button'); + control.type = 'button'; + if (!providedTrigger) { + control.className = 'dropdown-trigger'; + control.setAttribute('role', 'combobox'); + } + control.setAttribute('aria-label', label); + control.setAttribute('aria-haspopup', 'dialog'); + control.setAttribute('aria-controls', 'picker-dialog'); + control.setAttribute('aria-expanded', 'false'); + const selected = document.createElement('span'); + selected.className = 'dropdown-value'; + if (!providedTrigger) control.append(selected); + const api = { + element: wrapper, select, trigger: control, label, direction, text, choices: [], + setValue(next) { + select.value = next; + const choice = api.choices.find(item => item.value === select.value); + if (!providedTrigger) selected.textContent = choice?.label || placeholder; + if (!providedTrigger) control.title = choice?.label || placeholder; + }, + setChoices(next) { + const previous = select.value; + api.choices = next.map(item => typeof item === 'string' ? {value: item, label: item} : item); + select.replaceChildren(); + for (const item of api.choices) { + const option = document.createElement('option'); + option.value = item.value; + option.textContent = item.label; + select.append(option); + } + api.setValue(previous); + }, + focus() { control.focus(); } + }; + api.setChoices(choices); + api.setValue(value); + select.addEventListener('change', () => api.setValue(select.value)); + control.addEventListener('click', () => openPicker(api)); + control.addEventListener('keydown', event => { + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + event.preventDefault(); + openPicker(api); + } + }); + wrapper.append(select); + if (!providedTrigger) wrapper.append(control); + return api; +} diff --git a/tools/input-mapper/i18n.js b/tools/input-mapper/i18n.js new file mode 100644 index 0000000000..3f6b3f0d86 --- /dev/null +++ b/tools/input-mapper/i18n.js @@ -0,0 +1,1263 @@ +export const locales = [ + { + "code": "ar-EG", + "name": "العربية (المصري)", + "direction": "rtl" + }, + { + "code": "en", + "name": "English", + "direction": "ltr" + }, + { + "code": "es", + "name": "Español", + "direction": "ltr" + }, + { + "code": "fr", + "name": "Français", + "direction": "ltr" + }, + { + "code": "de", + "name": "Deutsch", + "direction": "ltr" + }, + { + "code": "it", + "name": "Italiano", + "direction": "ltr" + }, + { + "code": "pt", + "name": "Português", + "direction": "ltr" + }, + { + "code": "ru", + "name": "Русский", + "direction": "ltr" + }, + { + "code": "tr", + "name": "Türkçe", + "direction": "ltr" + }, + { + "code": "zh-Hans", + "name": "简体中文", + "direction": "ltr" + }, + { + "code": "ja", + "name": "日本語", + "direction": "ltr" + }, + { + "code": "ko", + "name": "한국어", + "direction": "ltr" + }, + { + "code": "hi", + "name": "हिन्दी", + "direction": "ltr" + }, + { + "code": "bn", + "name": "বাংলা", + "direction": "ltr" + }, + { + "code": "ur", + "name": "اردو", + "direction": "rtl" + }, + { + "code": "fa", + "name": "فارسی", + "direction": "rtl" + }, + { + "code": "id", + "name": "Bahasa Indonesia", + "direction": "ltr" + }, + { + "code": "vi", + "name": "Tiếng Việt", + "direction": "ltr" + }, + { + "code": "th", + "name": "ไทย", + "direction": "ltr" + }, + { + "code": "sw", + "name": "Kiswahili", + "direction": "ltr" + }, + { + "code": "nl", + "name": "Nederlands", + "direction": "ltr" + }, + { + "code": "pl", + "name": "Polski", + "direction": "ltr" + } +]; + +export const messages = { + "en": { + "controls": "Controls", + "configuration": "Configuration", + "help": "Help", + "settings": "Settings", + "language": "Language", + "close": "Close", + "eyebrow": "Accessories / Custom controls", + "intro": "Choose a control, then assign your keyboard or mouse inputs.", + "search": "Search controls", + "import": "Import INI", + "reset": "Reset", + "group0": "Face buttons", + "group1": "Shoulders", + "group2": "D-pad", + "group3": "Left stick", + "group4": "Right stick", + "group5": "Touch & utility", + "select": "Select…", + "add": "+ Add binding", + "capture": "Capture", + "remove": "Remove", + "builtin": "Built-in: {inputs}", + "custom": "Custom inputs replace built-in inputs.", + "noResults": "No controls match your search.", + "download": "Download INI", + "copy": "Copy configuration", + "previewEmpty": "Add a binding to begin.", + "backControls": "Back to controls", + "switchGroup": "Switch group", + "moveFocus": "Move focus", + "helpIntro": "A few answers to help you get started.", + "configIntro": "Review your inputs, then copy or download your file.", + "captureTitle": "Press a key for {action}", + "cancelCapture": "Cancel capture", + "keepEditing": "Keep editing", + "discard": "Discard changes", + "faq0Question": "How do I assign a key?", + "faq0Answer": "Choose a group, select Add binding, then Capture or choose a key from the list. Multiple bindings are alternatives.", + "faq1Question": "Where does my INI file go?", + "faq1Answer": "Download anyps5-input.ini and put it next to the generated game executable. For another location, set ANYPS5_INPUT_CONFIG to the file path.", + "faq2Question": "Why does a key not get captured?", + "faq2Answer": "Browser or system shortcuts may intercept it. Choose it manually from the list. Escape cancels capture; bind Escape using the list.", + "faq3Question": "Are my edits saved automatically?", + "faq3Answer": "Input edits stay in memory: copy or download before reloading. Only your language choice is saved in this browser. Unconfigured actions keep their defaults.", + "skip": "Skip to content", + "mainNav": "Main navigation", + "groups": "Control groups", + "find": "Find an action", + "clock": "Local time", + "output": "Configuration output", + "captureHelp": "Capture uses physical key positions. Escape cancels capture; choose Escape from the list to assign it. Some browser and system shortcuts require manual selection.", + "replaceTitle": "Discard unsaved changes?", + "replaceHelp": "Copy or download your configuration first if you want to keep it.", + "captureCancelled": "Capture cancelled.", + "captureBlur": "Capture cancelled because the window lost focus.", + "captured": "Captured {value} for {action}.", + "resetDone": "Configuration reset.", + "importRejected": "Import rejected", + "importRead": "Import read successfully", + "line": "Line {line}: {message}", + "preserved": "Your current configuration has been preserved.", + "imported": "Imported {name}.", + "importFailed": "Import failed: {message}", + "fileTooLarge": "INI file must be smaller than 1 MiB.", + "invalidUtf8": "The file must be valid UTF-8 text.", + "downloaded": "Configuration download requested.", + "copied": "Configuration copied.", + "clipboardFailed": "Clipboard unavailable. Copy the selected preview with Ctrl+C.", + "noScript": "Enable JavaScript to use this configurator.", + "noDefault": "no keyboard or mouse input", + "bindingLabel": "{action} binding {number}", + "typeLabel": "{binding} input type", + "valueLabel": "{binding} input value", + "addLabel": "Add binding to {action}", + "captureLabel": "Capture key for {binding}", + "removeLabel": "Remove {binding}", + "searchOptions": "Search options", + "noOptions": "No matching options." + }, + "ar-EG": { + "controls": "التحكم", + "configuration": "ملف الإعدادات", + "help": "المساعدة", + "settings": "الإعدادات", + "language": "اللغة", + "close": "قفل", + "eyebrow": "الملحقات / تخصيص التحكم", + "intro": "اختار زرار، وبعدين حدد له زرار من الكيبورد أو الماوس.", + "search": "دور على زرار", + "import": "استيراد INI", + "reset": "إعادة ضبط", + "group0": "أزرار التحكم", + "group1": "الأزرار العلوية", + "group2": "الاتجاهات", + "group3": "الانالوج الشمال", + "group4": "الانالوج اليمين", + "group5": "اللمس وأدوات تانية", + "select": "اختار…", + "add": "+ ضيف زرار", + "capture": "تسجيل زرار", + "remove": "إزالة", + "builtin": "الافتراضي: {inputs}", + "custom": "الزرار اللي اخترته بيستبدل التحكم الافتراضي.", + "noResults": "مفيش أزرار مطابقة للبحث.", + "download": "تنزيل INI", + "copy": "نسخ الإعدادات", + "previewEmpty": "ضيف زرار عشان تبدأ.", + "backControls": "ارجع للتحكم", + "switchGroup": "بدّل المجموعة", + "moveFocus": "انقل التركيز", + "helpIntro": "إجابات سريعة تساعدك تبدأ.", + "configIntro": "راجع اختياراتك، وبعدين انسخ الملف أو نزّله.", + "captureTitle": "اضغط زرار لـ {action}", + "cancelCapture": "إلغاء التسجيل", + "keepEditing": "كمّل التعديل", + "discard": "تجاهل التعديلات", + "faq0Question": "أربط زرار إزاي؟", + "faq0Answer": "اختار المجموعة واضغط ضيف زرار. بعدها اضغط تسجيل زرار أو اختاره من القائمة. تقدر تضيف أكتر من زرار لنفس الحركة.", + "faq1Question": "أحط ملف INI فين؟", + "faq1Answer": "نزّل anyps5-input.ini وحطه جنب ملف تشغيل اللعبة اللي اتولّد. لو عايزه في مكان تاني، حط مساره في ANYPS5_INPUT_CONFIG.", + "faq2Question": "ليه فيه زرار مش بيتسجل؟", + "faq2Answer": "ممكن المتصفح أو النظام يكون بيستخدمه كاختصار. اختاره من القائمة يدويًا. Escape بيلغي التسجيل؛ لو عايز تربطه اختاره من القائمة.", + "faq3Question": "تعديلاتي بتتحفظ لوحدها؟", + "faq3Answer": "التعديلات بتفضل في الذاكرة بس: انسخها أو نزّلها قبل تحديث الصفحة. اللغة بس هي اللي بتتحفظ في المتصفح. الأزرار اللي ماعدّلتهاش بتفضل على الافتراضي.", + "skip": "روح للمحتوى", + "mainNav": "التنقل الرئيسي", + "groups": "مجموعات التحكم", + "find": "دور على زرار", + "clock": "الوقت الحالي", + "output": "ملف الإعدادات", + "captureHelp": "التسجيل بيستخدم مكان الزرار على الكيبورد. Escape بيلغي التسجيل؛ عشان تربطه اختاره من القائمة. بعض اختصارات المتصفح والنظام لازم تختارها يدويًا.", + "replaceTitle": "تتجاهل التعديلات اللي لسه ماحفظتهاش؟", + "replaceHelp": "انسخ الإعدادات أو نزّلها الأول لو عايز تحتفظ بيها.", + "captureCancelled": "تم إلغاء تسجيل الزرار.", + "captureBlur": "التسجيل اتلغى عشان خرجت من النافذة.", + "captured": "تم تسجيل {value} لـ {action}.", + "resetDone": "الإعدادات رجعت للافتراضي.", + "importRejected": "الملف مااتقبلش", + "importRead": "الملف اتقري بنجاح", + "line": "سطر {line}: {message}", + "preserved": "إعداداتك الحالية لسه زي ما هي.", + "imported": "تم استيراد {name}.", + "importFailed": "الاستيراد ماكملش: {message}", + "fileTooLarge": "ملف INI لازم يكون أصغر من 1 MiB.", + "invalidUtf8": "الملف لازم يكون نص بترميز UTF-8 سليم.", + "downloaded": "تم طلب تنزيل الإعدادات.", + "copied": "الإعدادات اتنسخت.", + "clipboardFailed": "النسخ مش متاح. انسخ النص المحدد بـ Ctrl+C.", + "noScript": "فعّل JavaScript عشان تستخدم الصفحة.", + "noDefault": "مفيش زرار كيبورد أو ماوس افتراضي", + "bindingLabel": "{action}، الربط {number}", + "typeLabel": "نوع الإدخال لـ {binding}", + "valueLabel": "الزرار لـ {binding}", + "addLabel": "ضيف زرار لـ {action}", + "captureLabel": "سجّل زرار لـ {binding}", + "removeLabel": "شيل {binding}", + "searchOptions": "دور في القائمة", + "noOptions": "مفيش اختيار مطابق للبحث." + }, + "es": { + "controls": "Controles", + "configuration": "Configuración", + "help": "Ayuda", + "settings": "Ajustes", + "language": "Idioma", + "close": "Cerrar", + "eyebrow": "Accesorios / Controles personalizados", + "intro": "Elige un control y asigna entradas del teclado o ratón.", + "search": "Buscar controles", + "import": "Importar INI", + "reset": "Restablecer", + "group0": "Botones frontales", + "group1": "Botones superiores", + "group2": "Cruceta", + "group3": "Stick izquierdo", + "group4": "Stick derecho", + "group5": "Táctil y utilidades", + "select": "Seleccionar…", + "add": "+ Añadir entrada", + "capture": "Capturar", + "remove": "Eliminar", + "builtin": "Predeterminado: {inputs}", + "custom": "Las entradas personalizadas sustituyen las predeterminadas.", + "noResults": "No se encontraron controles.", + "download": "Descargar INI", + "copy": "Copiar configuración", + "previewEmpty": "Añade una entrada para empezar.", + "backControls": "Volver a controles", + "switchGroup": "Cambiar grupo", + "moveFocus": "Mover el foco", + "helpIntro": "Respuestas rápidas para empezar.", + "configIntro": "Revisa las entradas y copia o descarga el archivo.", + "captureTitle": "Pulsa una tecla para {action}", + "cancelCapture": "Cancelar captura", + "keepEditing": "Seguir editando", + "discard": "Descartar cambios", + "faq0Question": "¿Cómo asigno una tecla?", + "faq0Answer": "Elige un grupo y Añadir entrada. Usa Capturar o la lista. Puedes asignar varias entradas a una acción.", + "faq1Question": "¿Dónde guardo el archivo INI?", + "faq1Answer": "Pon anyps5-input.ini junto al ejecutable generado del juego. Para otra ruta, usa ANYPS5_INPUT_CONFIG.", + "faq2Question": "¿Por qué no se captura una tecla?", + "faq2Answer": "El navegador o sistema puede interceptarla. Elígela en la lista. Escape cancela la captura; asígnala desde la lista.", + "faq3Question": "¿Se guardan mis cambios solos?", + "faq3Answer": "Los cambios están en memoria: cópialos o descárgalos antes de recargar. Solo se guarda el idioma. Las acciones no configuradas conservan sus valores predeterminados.", + "searchOptions": "Buscar opciones", + "noOptions": "No hay opciones coincidentes." + }, + "fr": { + "controls": "Commandes", + "configuration": "Configuration", + "help": "Aide", + "settings": "Paramètres", + "language": "Langue", + "close": "Fermer", + "eyebrow": "Accessoires / Commandes personnalisées", + "intro": "Choisissez une commande, puis une touche ou un bouton de souris.", + "search": "Rechercher une commande", + "import": "Importer INI", + "reset": "Réinitialiser", + "group0": "Boutons principaux", + "group1": "Boutons supérieurs", + "group2": "Croix directionnelle", + "group3": "Stick gauche", + "group4": "Stick droit", + "group5": "Tactile et utilitaires", + "select": "Choisir…", + "add": "+ Ajouter une entrée", + "capture": "Capturer", + "remove": "Supprimer", + "builtin": "Par défaut : {inputs}", + "custom": "Les entrées personnalisées remplacent celles par défaut.", + "noResults": "Aucune commande trouvée.", + "download": "Télécharger INI", + "copy": "Copier la configuration", + "previewEmpty": "Ajoutez une entrée pour commencer.", + "backControls": "Retour aux commandes", + "switchGroup": "Changer de groupe", + "moveFocus": "Déplacer le focus", + "helpIntro": "Quelques réponses pour bien commencer.", + "configIntro": "Vérifiez les entrées, puis copiez ou téléchargez le fichier.", + "captureTitle": "Appuyez sur une touche pour {action}", + "cancelCapture": "Annuler la capture", + "keepEditing": "Continuer la modification", + "discard": "Ignorer les modifications", + "faq0Question": "Comment attribuer une touche ?", + "faq0Answer": "Choisissez un groupe et Ajouter une entrée. Utilisez Capturer ou la liste. Plusieurs entrées peuvent déclencher la même action.", + "faq1Question": "Où placer le fichier INI ?", + "faq1Answer": "Placez anyps5-input.ini près de l’exécutable généré du jeu. Pour un autre chemin, utilisez ANYPS5_INPUT_CONFIG.", + "faq2Question": "Pourquoi une touche n’est-elle pas capturée ?", + "faq2Answer": "Un raccourci du navigateur ou du système peut l’intercepter. Choisissez-la dans la liste. Escape annule la capture ; attribuez-la via la liste.", + "faq3Question": "Mes modifications sont-elles enregistrées ?", + "faq3Answer": "Les modifications restent en mémoire : copiez-les ou téléchargez-les avant de recharger. Seule la langue est mémorisée. Les actions non configurées gardent leurs valeurs par défaut.", + "searchOptions": "Rechercher une option", + "noOptions": "Aucune option trouvée." + }, + "de": { + "controls": "Steuerung", + "configuration": "Konfiguration", + "help": "Hilfe", + "settings": "Einstellungen", + "language": "Sprache", + "close": "Schließen", + "eyebrow": "Zubehör / Eigene Steuerung", + "intro": "Wähle eine Steuerung und weise Tastatur- oder Mauseingaben zu.", + "search": "Steuerung suchen", + "import": "INI importieren", + "reset": "Zurücksetzen", + "group0": "Aktionstasten", + "group1": "Schultertasten", + "group2": "Steuerkreuz", + "group3": "Linker Stick", + "group4": "Rechter Stick", + "group5": "Touch und Werkzeuge", + "select": "Auswählen…", + "add": "+ Eingabe hinzufügen", + "capture": "Erfassen", + "remove": "Entfernen", + "builtin": "Standard: {inputs}", + "custom": "Eigene Eingaben ersetzen die Standardeingaben.", + "noResults": "Keine passende Steuerung gefunden.", + "download": "INI herunterladen", + "copy": "Konfiguration kopieren", + "previewEmpty": "Füge eine Eingabe hinzu, um zu beginnen.", + "backControls": "Zurück zur Steuerung", + "switchGroup": "Gruppe wechseln", + "moveFocus": "Fokus verschieben", + "helpIntro": "Kurze Antworten für den Einstieg.", + "configIntro": "Prüfe die Eingaben und kopiere oder lade die Datei herunter.", + "captureTitle": "Drücke eine Taste für {action}", + "cancelCapture": "Erfassung abbrechen", + "keepEditing": "Weiter bearbeiten", + "discard": "Änderungen verwerfen", + "faq0Question": "Wie weise ich eine Taste zu?", + "faq0Answer": "Wähle eine Gruppe und Eingabe hinzufügen. Nutze Erfassen oder die Liste. Mehrere Eingaben können dieselbe Aktion auslösen.", + "faq1Question": "Wohin gehört die INI-Datei?", + "faq1Answer": "Lege anyps5-input.ini neben die erzeugte Spielprogrammdatei. Für einen anderen Pfad nutze ANYPS5_INPUT_CONFIG.", + "faq2Question": "Warum wird eine Taste nicht erfasst?", + "faq2Answer": "Browser- oder Systemkürzel können sie abfangen. Wähle sie in der Liste. Escape bricht die Erfassung ab; weise Escape über die Liste zu.", + "faq3Question": "Werden Änderungen automatisch gespeichert?", + "faq3Answer": "Änderungen bleiben im Speicher: vor dem Neuladen kopieren oder herunterladen. Nur die Sprache wird gespeichert. Nicht konfigurierte Aktionen behalten ihre Standardwerte.", + "searchOptions": "Optionen suchen", + "noOptions": "Keine passenden Optionen." + }, + "it": { + "controls": "Controlli", + "configuration": "Configurazione", + "help": "Aiuto", + "settings": "Impostazioni", + "language": "Lingua", + "close": "Chiudi", + "eyebrow": "Accessori / Controlli personalizzati", + "intro": "Scegli un controllo e assegna un tasto o un pulsante del mouse.", + "search": "Cerca controlli", + "import": "Importa INI", + "reset": "Reimposta", + "group0": "Pulsanti principali", + "group1": "Pulsanti superiori", + "group2": "Croce direzionale", + "group3": "Stick sinistro", + "group4": "Stick destro", + "group5": "Touch e utilità", + "select": "Seleziona…", + "add": "+ Aggiungi input", + "capture": "Acquisisci", + "remove": "Rimuovi", + "builtin": "Predefinito: {inputs}", + "custom": "Gli input personalizzati sostituiscono quelli predefiniti.", + "noResults": "Nessun controllo trovato.", + "download": "Scarica INI", + "copy": "Copia configurazione", + "previewEmpty": "Aggiungi un input per iniziare.", + "backControls": "Torna ai controlli", + "switchGroup": "Cambia gruppo", + "moveFocus": "Sposta il focus", + "helpIntro": "Risposte rapide per iniziare.", + "configIntro": "Controlla gli input e copia o scarica il file.", + "captureTitle": "Premi un tasto per {action}", + "cancelCapture": "Annulla acquisizione", + "keepEditing": "Continua a modificare", + "discard": "Scarta modifiche", + "faq0Question": "Come assegno un tasto?", + "faq0Answer": "Scegli un gruppo e Aggiungi input. Usa Acquisisci o l’elenco. Puoi assegnare più input alla stessa azione.", + "faq1Question": "Dove metto il file INI?", + "faq1Answer": "Metti anyps5-input.ini accanto all’eseguibile generato del gioco. Per un altro percorso, usa ANYPS5_INPUT_CONFIG.", + "faq2Question": "Perché un tasto non viene acquisito?", + "faq2Answer": "Il browser o il sistema potrebbe intercettarlo. Sceglilo dall’elenco. Escape annulla l’acquisizione; assegnalo dall’elenco.", + "faq3Question": "Le modifiche si salvano automaticamente?", + "faq3Answer": "Le modifiche restano in memoria: copiale o scaricale prima di ricaricare. Si salva solo la lingua. Le azioni non configurate mantengono i valori predefiniti.", + "searchOptions": "Cerca opzioni", + "noOptions": "Nessuna opzione trovata." + }, + "pt": { + "controls": "Controles", + "configuration": "Configuração", + "help": "Ajuda", + "settings": "Configurações", + "language": "Idioma", + "close": "Fechar", + "eyebrow": "Acessórios / Controles personalizados", + "intro": "Escolha um controle e atribua entradas do teclado ou mouse.", + "search": "Buscar controles", + "import": "Importar INI", + "reset": "Redefinir", + "group0": "Botões principais", + "group1": "Botões superiores", + "group2": "Direcional", + "group3": "Analógico esquerdo", + "group4": "Analógico direito", + "group5": "Toque e utilidades", + "select": "Selecionar…", + "add": "+ Adicionar entrada", + "capture": "Capturar", + "remove": "Remover", + "builtin": "Padrão: {inputs}", + "custom": "As entradas personalizadas substituem as entradas padrão.", + "noResults": "Nenhum controle encontrado.", + "download": "Baixar INI", + "copy": "Copiar configuração", + "previewEmpty": "Adicione uma entrada para começar.", + "backControls": "Voltar aos controles", + "switchGroup": "Trocar grupo", + "moveFocus": "Mover o foco", + "helpIntro": "Respostas rápidas para começar.", + "configIntro": "Confira as entradas e copie ou baixe o arquivo.", + "captureTitle": "Pressione uma tecla para {action}", + "cancelCapture": "Cancelar captura", + "keepEditing": "Continuar editando", + "discard": "Descartar alterações", + "faq0Question": "Como atribuo uma tecla?", + "faq0Answer": "Escolha um grupo e Adicionar entrada. Use Capturar ou a lista. Uma ação pode ter várias entradas alternativas.", + "faq1Question": "Onde coloco o arquivo INI?", + "faq1Answer": "Coloque anyps5-input.ini junto do executável gerado do jogo. Para outro caminho, use ANYPS5_INPUT_CONFIG.", + "faq2Question": "Por que uma tecla não é capturada?", + "faq2Answer": "O navegador ou sistema pode interceptá-la. Escolha na lista. Escape cancela a captura; atribua Escape pela lista.", + "faq3Question": "As alterações são salvas sozinhas?", + "faq3Answer": "As alterações ficam na memória: copie ou baixe antes de recarregar. Só o idioma é salvo. Ações não configuradas mantêm os padrões.", + "searchOptions": "Buscar opções", + "noOptions": "Nenhuma opção encontrada." + }, + "ru": { + "controls": "Управление", + "configuration": "Конфигурация", + "help": "Помощь", + "settings": "Настройки", + "language": "Язык", + "close": "Закрыть", + "eyebrow": "Аксессуары / Настройка управления", + "intro": "Выберите действие и назначьте клавишу или кнопку мыши.", + "search": "Поиск действий", + "import": "Импорт INI", + "reset": "Сбросить", + "group0": "Основные кнопки", + "group1": "Верхние кнопки", + "group2": "Крестовина", + "group3": "Левый стик", + "group4": "Правый стик", + "group5": "Сенсор и инструменты", + "select": "Выбрать…", + "add": "+ Добавить ввод", + "capture": "Записать", + "remove": "Удалить", + "builtin": "По умолчанию: {inputs}", + "custom": "Новые назначения заменяют стандартные.", + "noResults": "Действия не найдены.", + "download": "Скачать INI", + "copy": "Копировать конфигурацию", + "previewEmpty": "Добавьте назначение, чтобы начать.", + "backControls": "Назад к управлению", + "switchGroup": "Сменить группу", + "moveFocus": "Переместить фокус", + "helpIntro": "Краткие ответы для начала работы.", + "configIntro": "Проверьте назначения и скопируйте или скачайте файл.", + "captureTitle": "Нажмите клавишу для {action}", + "cancelCapture": "Отменить запись", + "keepEditing": "Продолжить изменение", + "discard": "Отменить изменения", + "faq0Question": "Как назначить клавишу?", + "faq0Answer": "Выберите группу и Добавить ввод. Используйте Записать или список. Одно действие может иметь несколько назначений.", + "faq1Question": "Куда поместить INI-файл?", + "faq1Answer": "Поместите anyps5-input.ini рядом с созданным исполняемым файлом игры. Для другого пути используйте ANYPS5_INPUT_CONFIG.", + "faq2Question": "Почему клавиша не записывается?", + "faq2Answer": "Браузер или система может перехватывать её. Выберите клавишу в списке. Escape отменяет запись; назначайте Escape через список.", + "faq3Question": "Изменения сохраняются автоматически?", + "faq3Answer": "Изменения хранятся в памяти: копируйте или скачивайте их перед перезагрузкой. Сохраняется только язык. Ненастроенные действия используют значения по умолчанию.", + "searchOptions": "Поиск вариантов", + "noOptions": "Совпадений нет." + }, + "tr": { + "controls": "Kontroller", + "configuration": "Yapılandırma", + "help": "Yardım", + "settings": "Ayarlar", + "language": "Dil", + "close": "Kapat", + "eyebrow": "Aksesuarlar / Özel kontroller", + "intro": "Bir kontrol seçip klavye veya fare girdisi atayın.", + "search": "Kontrol ara", + "import": "INI içe aktar", + "reset": "Sıfırla", + "group0": "Ana düğmeler", + "group1": "Üst düğmeler", + "group2": "Yön tuşları", + "group3": "Sol çubuk", + "group4": "Sağ çubuk", + "group5": "Dokunma ve araçlar", + "select": "Seç…", + "add": "+ Girdi ekle", + "capture": "Yakala", + "remove": "Kaldır", + "builtin": "Varsayılan: {inputs}", + "custom": "Özel girdiler varsayılanların yerini alır.", + "noResults": "Kontrol bulunamadı.", + "download": "INI indir", + "copy": "Yapılandırmayı kopyala", + "previewEmpty": "Başlamak için bir girdi ekleyin.", + "backControls": "Kontrollere dön", + "switchGroup": "Grubu değiştir", + "moveFocus": "Odağı taşı", + "helpIntro": "Başlamanıza yardımcı olacak kısa yanıtlar.", + "configIntro": "Girdileri kontrol edip dosyayı kopyalayın veya indirin.", + "captureTitle": "{action} için bir tuşa basın", + "cancelCapture": "Yakalamayı iptal et", + "keepEditing": "Düzenlemeye devam et", + "discard": "Değişiklikleri sil", + "faq0Question": "Nasıl tuş atarım?", + "faq0Answer": "Bir grup seçip Girdi ekle düğmesine basın. Yakala veya listeyi kullanın. Bir eyleme birden fazla girdi atanabilir.", + "faq1Question": "INI dosyasını nereye koyarım?", + "faq1Answer": "anyps5-input.ini dosyasını oluşturulan oyun yürütülebilir dosyasının yanına koyun. Başka yol için ANYPS5_INPUT_CONFIG kullanın.", + "faq2Question": "Neden bir tuş yakalanmıyor?", + "faq2Answer": "Tarayıcı veya sistem kısayolu engelleyebilir. Listeden seçin. Escape yakalamayı iptal eder; Escape tuşunu listeden atayın.", + "faq3Question": "Değişiklikler otomatik kaydedilir mi?", + "faq3Answer": "Değişiklikler bellekte kalır: yenilemeden önce kopyalayın veya indirin. Yalnızca dil kaydedilir. Ayarlanmayan eylemler varsayılanlarını korur.", + "searchOptions": "Seçenek ara", + "noOptions": "Eşleşen seçenek yok." + }, + "zh-Hans": { + "controls": "控制", + "configuration": "配置", + "help": "帮助", + "settings": "设置", + "language": "语言", + "close": "关闭", + "eyebrow": "配件 / 自定义控制", + "intro": "选择一个控制,然后分配键盘或鼠标输入。", + "search": "搜索控制", + "import": "导入 INI", + "reset": "重置", + "group0": "主要按钮", + "group1": "肩部按钮", + "group2": "方向键", + "group3": "左摇杆", + "group4": "右摇杆", + "group5": "触摸和工具", + "select": "选择…", + "add": "+ 添加输入", + "capture": "录入", + "remove": "移除", + "builtin": "默认:{inputs}", + "custom": "自定义输入会替换默认输入。", + "noResults": "没有找到匹配的控制。", + "download": "下载 INI", + "copy": "复制配置", + "previewEmpty": "添加输入以开始。", + "backControls": "返回控制", + "switchGroup": "切换分组", + "moveFocus": "移动焦点", + "helpIntro": "帮助您入门的简短解答。", + "configIntro": "检查输入,然后复制或下载文件。", + "captureTitle": "为 {action} 按下一个键", + "cancelCapture": "取消录入", + "keepEditing": "继续编辑", + "discard": "放弃更改", + "faq0Question": "如何分配按键?", + "faq0Answer": "选择分组和添加输入,然后使用录入或列表。同一操作可以使用多个输入。", + "faq1Question": "INI 文件放在哪里?", + "faq1Answer": "将 anyps5-input.ini 放在生成的游戏可执行文件旁。其他位置可使用 ANYPS5_INPUT_CONFIG 指定路径。", + "faq2Question": "为什么无法录入某个键?", + "faq2Answer": "浏览器或系统快捷键可能会拦截它。请从列表选择。Escape 会取消录入;要分配 Escape,请使用列表。", + "faq3Question": "修改会自动保存吗?", + "faq3Answer": "输入修改只保留在内存中,刷新前请复制或下载。浏览器只保存语言选择。未配置的操作保留默认输入。", + "searchOptions": "搜索选项", + "noOptions": "没有匹配选项。" + }, + "ja": { + "controls": "操作", + "configuration": "構成", + "help": "ヘルプ", + "settings": "設定", + "language": "言語", + "close": "閉じる", + "eyebrow": "アクセサリー / 操作のカスタマイズ", + "intro": "操作を選び、キーボードやマウスの入力を割り当てます。", + "search": "操作を検索", + "import": "INI を読み込む", + "reset": "リセット", + "group0": "メインボタン", + "group1": "ショルダーボタン", + "group2": "方向キー", + "group3": "左スティック", + "group4": "右スティック", + "group5": "タッチとツール", + "select": "選択…", + "add": "+ 入力を追加", + "capture": "記録", + "remove": "削除", + "builtin": "初期設定:{inputs}", + "custom": "カスタム入力は初期設定を置き換えます。", + "noResults": "一致する操作がありません。", + "download": "INI をダウンロード", + "copy": "構成をコピー", + "previewEmpty": "入力を追加して開始します。", + "backControls": "操作に戻る", + "switchGroup": "グループを切り替え", + "moveFocus": "フォーカスを移動", + "helpIntro": "使い始めるための簡単な説明です。", + "configIntro": "入力を確認してファイルをコピーまたはダウンロードします。", + "captureTitle": "{action} のキーを押してください", + "cancelCapture": "記録をキャンセル", + "keepEditing": "編集を続ける", + "discard": "変更を破棄", + "faq0Question": "キーを割り当てるには?", + "faq0Answer": "グループと入力を追加を選び、記録または一覧を使います。同じ操作に複数の入力を割り当てられます。", + "faq1Question": "INI ファイルはどこに置きますか?", + "faq1Answer": "anyps5-input.ini を生成されたゲーム実行ファイルの隣に置きます。別の場所には ANYPS5_INPUT_CONFIG でパスを指定します。", + "faq2Question": "キーが記録されないのはなぜですか?", + "faq2Answer": "ブラウザーやシステムのショートカットが優先される場合があります。一覧から選んでください。Escape は記録をキャンセルするため、一覧から割り当てます。", + "faq3Question": "変更は自動保存されますか?", + "faq3Answer": "入力の変更はメモリ内だけです。再読み込み前にコピーかダウンロードをしてください。言語のみ保存されます。未設定の操作は初期設定を使います。", + "searchOptions": "選択肢を検索", + "noOptions": "一致する選択肢がありません。" + }, + "ko": { + "controls": "컨트롤", + "configuration": "구성", + "help": "도움말", + "settings": "설정", + "language": "언어", + "close": "닫기", + "eyebrow": "액세서리 / 컨트롤 맞춤 설정", + "intro": "컨트롤을 선택하고 키보드 또는 마우스 입력을 지정하세요.", + "search": "컨트롤 검색", + "import": "INI 가져오기", + "reset": "초기화", + "group0": "주요 버튼", + "group1": "상단 버튼", + "group2": "방향키", + "group3": "왼쪽 스틱", + "group4": "오른쪽 스틱", + "group5": "터치 및 도구", + "select": "선택…", + "add": "+ 입력 추가", + "capture": "기록", + "remove": "제거", + "builtin": "기본값: {inputs}", + "custom": "사용자 입력이 기본 입력을 대체합니다.", + "noResults": "일치하는 컨트롤이 없습니다.", + "download": "INI 다운로드", + "copy": "구성 복사", + "previewEmpty": "입력을 추가하여 시작하세요.", + "backControls": "컨트롤로 돌아가기", + "switchGroup": "그룹 전환", + "moveFocus": "포커스 이동", + "helpIntro": "시작을 돕는 간단한 답변입니다.", + "configIntro": "입력을 확인하고 파일을 복사하거나 다운로드하세요.", + "captureTitle": "{action}에 사용할 키를 누르세요", + "cancelCapture": "기록 취소", + "keepEditing": "계속 편집", + "discard": "변경 사항 삭제", + "faq0Question": "키는 어떻게 지정하나요?", + "faq0Answer": "그룹과 입력 추가를 선택한 뒤 기록 또는 목록을 사용하세요. 같은 동작에 여러 입력을 지정할 수 있습니다.", + "faq1Question": "INI 파일은 어디에 두나요?", + "faq1Answer": "생성된 게임 실행 파일 옆에 anyps5-input.ini를 두세요. 다른 경로는 ANYPS5_INPUT_CONFIG로 지정하세요.", + "faq2Question": "키가 기록되지 않는 이유는 무엇인가요?", + "faq2Answer": "브라우저나 시스템 단축키가 가로챌 수 있습니다. 목록에서 선택하세요. Escape는 기록을 취소하므로 목록에서 지정하세요.", + "faq3Question": "변경 사항이 자동 저장되나요?", + "faq3Answer": "입력 변경은 메모리에만 있습니다. 새로고침 전에 복사하거나 다운로드하세요. 언어 선택만 저장됩니다. 설정하지 않은 동작은 기본값을 유지합니다.", + "searchOptions": "옵션 검색", + "noOptions": "일치하는 옵션이 없습니다." + }, + "hi": { + "controls": "नियंत्रण", + "configuration": "कॉन्फ़िगरेशन", + "help": "मदद", + "settings": "सेटिंग्स", + "language": "भाषा", + "close": "बंद करें", + "eyebrow": "सहायक उपकरण / कस्टम नियंत्रण", + "intro": "नियंत्रण चुनें और कीबोर्ड या माउस इनपुट जोड़ें।", + "search": "नियंत्रण खोजें", + "import": "INI आयात करें", + "reset": "रीसेट", + "group0": "मुख्य बटन", + "group1": "ऊपरी बटन", + "group2": "दिशा बटन", + "group3": "बायाँ स्टिक", + "group4": "दायाँ स्टिक", + "group5": "टच और उपकरण", + "select": "चुनें…", + "add": "+ इनपुट जोड़ें", + "capture": "रिकॉर्ड करें", + "remove": "हटाएँ", + "builtin": "डिफ़ॉल्ट: {inputs}", + "custom": "कस्टम इनपुट डिफ़ॉल्ट इनपुट की जगह लेते हैं।", + "noResults": "कोई नियंत्रण नहीं मिला।", + "download": "INI डाउनलोड करें", + "copy": "कॉन्फ़िगरेशन कॉपी करें", + "previewEmpty": "शुरू करने के लिए इनपुट जोड़ें।", + "backControls": "नियंत्रण पर वापस जाएँ", + "switchGroup": "समूह बदलें", + "moveFocus": "फ़ोकस बदलें", + "helpIntro": "शुरू करने के लिए कुछ आसान जवाब।", + "configIntro": "इनपुट जाँचें और फ़ाइल कॉपी या डाउनलोड करें।", + "captureTitle": "{action} के लिए कोई कुंजी दबाएँ", + "cancelCapture": "रिकॉर्डिंग रद्द करें", + "keepEditing": "संपादन जारी रखें", + "discard": "बदलाव हटाएँ", + "faq0Question": "कुंजी कैसे जोड़ूँ?", + "faq0Answer": "समूह और इनपुट जोड़ें चुनें, फिर रिकॉर्ड करें या सूची का उपयोग करें। एक काम के लिए कई इनपुट जोड़ सकते हैं।", + "faq1Question": "INI फ़ाइल कहाँ रखें?", + "faq1Answer": "anyps5-input.ini को बनाए गए गेम प्रोग्राम के पास रखें। दूसरे स्थान के लिए ANYPS5_INPUT_CONFIG में पथ दें।", + "faq2Question": "कुंजी रिकॉर्ड क्यों नहीं होती?", + "faq2Answer": "ब्राउज़र या सिस्टम का शॉर्टकट उसे रोक सकता है। सूची से चुनें। Escape रिकॉर्डिंग रद्द करता है; इसे सूची से जोड़ें।", + "faq3Question": "क्या बदलाव अपने आप सेव होते हैं?", + "faq3Answer": "बदलाव केवल मेमोरी में रहते हैं: रीलोड से पहले कॉपी या डाउनलोड करें। केवल भाषा सेव होती है। बिना बदले काम अपने डिफ़ॉल्ट रखते हैं।", + "searchOptions": "विकल्प खोजें", + "noOptions": "कोई विकल्प नहीं मिला।" + }, + "bn": { + "controls": "নিয়ন্ত্রণ", + "configuration": "কনফিগারেশন", + "help": "সাহায্য", + "settings": "সেটিংস", + "language": "ভাষা", + "close": "বন্ধ করুন", + "eyebrow": "আনুষঙ্গিক / নিজস্ব নিয়ন্ত্রণ", + "intro": "একটি নিয়ন্ত্রণ বেছে কিবোর্ড বা মাউস ইনপুট দিন।", + "search": "নিয়ন্ত্রণ খুঁজুন", + "import": "INI আমদানি", + "reset": "রিসেট", + "group0": "প্রধান বোতাম", + "group1": "ওপরের বোতাম", + "group2": "দিক বোতাম", + "group3": "বাম স্টিক", + "group4": "ডান স্টিক", + "group5": "টাচ ও সরঞ্জাম", + "select": "বাছুন…", + "add": "+ ইনপুট যোগ", + "capture": "রেকর্ড", + "remove": "সরান", + "builtin": "ডিফল্ট: {inputs}", + "custom": "নিজস্ব ইনপুট ডিফল্ট ইনপুটের বদলে কাজ করে।", + "noResults": "কোনো নিয়ন্ত্রণ পাওয়া যায়নি।", + "download": "INI ডাউনলোড", + "copy": "কনফিগারেশন কপি", + "previewEmpty": "শুরু করতে একটি ইনপুট যোগ করুন।", + "backControls": "নিয়ন্ত্রণে ফিরে যান", + "switchGroup": "গ্রুপ বদলান", + "moveFocus": "ফোকাস সরান", + "helpIntro": "শুরু করার জন্য কিছু সহজ উত্তর।", + "configIntro": "ইনপুট দেখে ফাইল কপি বা ডাউনলোড করুন।", + "captureTitle": "{action}-এর জন্য একটি কী চাপুন", + "cancelCapture": "রেকর্ড বাতিল", + "keepEditing": "সম্পাদনা চালান", + "discard": "পরিবর্তন বাদ দিন", + "faq0Question": "কী কীভাবে যোগ করব?", + "faq0Answer": "গ্রুপ ও ইনপুট যোগ বেছে রেকর্ড বা তালিকা ব্যবহার করুন। এক কাজের জন্য একাধিক ইনপুট দিতে পারেন।", + "faq1Question": "INI ফাইল কোথায় রাখব?", + "faq1Answer": "তৈরি হওয়া গেম চালানোর ফাইলের পাশে anyps5-input.ini রাখুন। অন্য পথের জন্য ANYPS5_INPUT_CONFIG ব্যবহার করুন।", + "faq2Question": "কোনো কী রেকর্ড হচ্ছে না কেন?", + "faq2Answer": "ব্রাউজার বা সিস্টেম শর্টকাট সেটি আটকাতে পারে। তালিকা থেকে বাছুন। Escape রেকর্ড বাতিল করে; তালিকা থেকে যোগ করুন।", + "faq3Question": "পরিবর্তন কি নিজে থেকে সেভ হয়?", + "faq3Answer": "পরিবর্তন শুধু মেমোরিতে থাকে: রিলোডের আগে কপি বা ডাউনলোড করুন। শুধু ভাষা সেভ হয়। না বদলানো কাজগুলো ডিফল্ট রাখে।", + "searchOptions": "বিকল্প খুঁজুন", + "noOptions": "কোনো বিকল্প মেলেনি।" + }, + "ur": { + "controls": "کنٹرول", + "configuration": "ترتیب", + "help": "مدد", + "settings": "سیٹنگز", + "language": "زبان", + "close": "بند کریں", + "eyebrow": "لوازمات / حسب ضرورت کنٹرول", + "intro": "کنٹرول منتخب کرکے کی بورڈ یا ماؤس ان پٹ مقرر کریں۔", + "search": "کنٹرول تلاش کریں", + "import": "INI درآمد کریں", + "reset": "ری سیٹ", + "group0": "بنیادی بٹن", + "group1": "اوپری بٹن", + "group2": "سمت کے بٹن", + "group3": "بائیں اسٹک", + "group4": "دائیں اسٹک", + "group5": "ٹچ اور اوزار", + "select": "منتخب کریں…", + "add": "+ ان پٹ شامل کریں", + "capture": "ریکارڈ کریں", + "remove": "ہٹائیں", + "builtin": "پہلے سے طے شدہ: {inputs}", + "custom": "حسب ضرورت ان پٹ پہلے سے طے شدہ ان پٹ کی جگہ لیتے ہیں۔", + "noResults": "کوئی کنٹرول نہیں ملا۔", + "download": "INI ڈاؤن لوڈ کریں", + "copy": "ترتیب کاپی کریں", + "previewEmpty": "شروع کرنے کے لیے ان پٹ شامل کریں۔", + "backControls": "کنٹرول پر واپس جائیں", + "switchGroup": "گروپ بدلیں", + "moveFocus": "فوکس بدلیں", + "helpIntro": "شروع کرنے کے لیے مختصر جوابات۔", + "configIntro": "ان پٹ دیکھ کر فائل کاپی یا ڈاؤن لوڈ کریں۔", + "captureTitle": "{action} کے لیے کوئی کلید دبائیں", + "cancelCapture": "ریکارڈنگ منسوخ کریں", + "keepEditing": "ترمیم جاری رکھیں", + "discard": "تبدیلیاں چھوڑ دیں", + "faq0Question": "کلید کیسے مقرر کروں؟", + "faq0Answer": "گروپ اور ان پٹ شامل کریں منتخب کریں، پھر ریکارڈ یا فہرست استعمال کریں۔ ایک عمل کے لیے کئی ان پٹ رکھ سکتے ہیں۔", + "faq1Question": "INI فائل کہاں رکھوں؟", + "faq1Answer": "anyps5-input.ini کو بنائی گئی گیم کی قابل اجرا فائل کے ساتھ رکھیں۔ دوسرے راستے کے لیے ANYPS5_INPUT_CONFIG استعمال کریں۔", + "faq2Question": "کلید ریکارڈ کیوں نہیں ہوتی؟", + "faq2Answer": "براؤزر یا نظام کا شارٹ کٹ روک سکتا ہے۔ فہرست سے منتخب کریں۔ Escape ریکارڈنگ منسوخ کرتا ہے؛ اسے فہرست سے مقرر کریں۔", + "faq3Question": "کیا تبدیلیاں خود محفوظ ہوتی ہیں؟", + "faq3Answer": "تبدیلیاں صرف میموری میں رہتی ہیں: ری لوڈ سے پہلے کاپی یا ڈاؤن لوڈ کریں۔ صرف زبان محفوظ ہوتی ہے۔ غیر تبدیل شدہ اعمال اپنی طے شدہ ترتیب رکھتے ہیں۔", + "searchOptions": "اختیارات تلاش کریں", + "noOptions": "کوئی اختیار نہیں ملا۔" + }, + "fa": { + "controls": "کنترل‌ها", + "configuration": "پیکربندی", + "help": "راهنما", + "settings": "تنظیمات", + "language": "زبان", + "close": "بستن", + "eyebrow": "لوازم جانبی / کنترل سفارشی", + "intro": "یک کنترل را انتخاب کنید و ورودی صفحه‌کلید یا ماوس بدهید.", + "search": "جستجوی کنترل", + "import": "وارد کردن INI", + "reset": "بازنشانی", + "group0": "دکمه‌های اصلی", + "group1": "دکمه‌های بالایی", + "group2": "دکمه‌های جهت", + "group3": "دسته چپ", + "group4": "دسته راست", + "group5": "لمس و ابزارها", + "select": "انتخاب…", + "add": "+ افزودن ورودی", + "capture": "ثبت", + "remove": "حذف", + "builtin": "پیش‌فرض: {inputs}", + "custom": "ورودی سفارشی جای ورودی پیش‌فرض را می‌گیرد.", + "noResults": "کنترلی پیدا نشد.", + "download": "دانلود INI", + "copy": "کپی پیکربندی", + "previewEmpty": "برای شروع یک ورودی اضافه کنید.", + "backControls": "بازگشت به کنترل‌ها", + "switchGroup": "تغییر گروه", + "moveFocus": "جابجایی تمرکز", + "helpIntro": "پاسخ‌های کوتاه برای شروع.", + "configIntro": "ورودی‌ها را بررسی و فایل را کپی یا دانلود کنید.", + "captureTitle": "برای {action} کلیدی فشار دهید", + "cancelCapture": "لغو ثبت", + "keepEditing": "ادامه ویرایش", + "discard": "کنار گذاشتن تغییرات", + "faq0Question": "چطور کلید تعیین کنم؟", + "faq0Answer": "گروه و افزودن ورودی را انتخاب کنید، سپس ثبت یا فهرست را به کار ببرید. یک عمل می‌تواند چند ورودی داشته باشد.", + "faq1Question": "فایل INI را کجا بگذارم؟", + "faq1Answer": "anyps5-input.ini را کنار فایل اجرایی ساخته‌شده بازی قرار دهید. برای مسیر دیگر از ANYPS5_INPUT_CONFIG استفاده کنید.", + "faq2Question": "چرا کلید ثبت نمی‌شود؟", + "faq2Answer": "میانبر مرورگر یا سیستم ممکن است آن را بگیرد. از فهرست انتخاب کنید. Escape ثبت را لغو می‌کند؛ آن را از فهرست تعیین کنید.", + "faq3Question": "آیا تغییرات خودکار ذخیره می‌شوند؟", + "faq3Answer": "تغییرات فقط در حافظه‌اند: قبل از بارگذاری دوباره کپی یا دانلود کنید. تنها زبان ذخیره می‌شود. اعمال تنظیم‌نشده پیش‌فرض خود را حفظ می‌کنند.", + "searchOptions": "جستجوی گزینه‌ها", + "noOptions": "گزینه‌ای پیدا نشد." + }, + "id": { + "controls": "Kontrol", + "configuration": "Konfigurasi", + "help": "Bantuan", + "settings": "Pengaturan", + "language": "Bahasa", + "close": "Tutup", + "eyebrow": "Aksesori / Kontrol khusus", + "intro": "Pilih kontrol, lalu tetapkan input keyboard atau mouse.", + "search": "Cari kontrol", + "import": "Impor INI", + "reset": "Atur ulang", + "group0": "Tombol utama", + "group1": "Tombol atas", + "group2": "Tombol arah", + "group3": "Stik kiri", + "group4": "Stik kanan", + "group5": "Sentuh dan alat", + "select": "Pilih…", + "add": "+ Tambah input", + "capture": "Rekam", + "remove": "Hapus", + "builtin": "Bawaan: {inputs}", + "custom": "Input khusus menggantikan input bawaan.", + "noResults": "Kontrol tidak ditemukan.", + "download": "Unduh INI", + "copy": "Salin konfigurasi", + "previewEmpty": "Tambahkan input untuk memulai.", + "backControls": "Kembali ke kontrol", + "switchGroup": "Ganti grup", + "moveFocus": "Pindahkan fokus", + "helpIntro": "Jawaban singkat untuk memulai.", + "configIntro": "Periksa input, lalu salin atau unduh berkas.", + "captureTitle": "Tekan tombol untuk {action}", + "cancelCapture": "Batalkan rekaman", + "keepEditing": "Lanjutkan mengedit", + "discard": "Buang perubahan", + "faq0Question": "Bagaimana menetapkan tombol?", + "faq0Answer": "Pilih grup dan Tambah input, lalu Rekam atau gunakan daftar. Satu aksi dapat memiliki beberapa input.", + "faq1Question": "Di mana berkas INI diletakkan?", + "faq1Answer": "Letakkan anyps5-input.ini di samping berkas program game yang dihasilkan. Untuk lokasi lain, gunakan ANYPS5_INPUT_CONFIG.", + "faq2Question": "Mengapa tombol tidak direkam?", + "faq2Answer": "Pintasan browser atau sistem mungkin menangkapnya. Pilih dari daftar. Escape membatalkan rekaman; tetapkan Escape melalui daftar.", + "faq3Question": "Apakah perubahan disimpan otomatis?", + "faq3Answer": "Perubahan hanya di memori: salin atau unduh sebelum memuat ulang. Hanya bahasa yang disimpan. Aksi yang tidak diatur tetap memakai bawaan.", + "searchOptions": "Cari opsi", + "noOptions": "Tidak ada opsi yang cocok." + }, + "vi": { + "controls": "Điều khiển", + "configuration": "Cấu hình", + "help": "Trợ giúp", + "settings": "Cài đặt", + "language": "Ngôn ngữ", + "close": "Đóng", + "eyebrow": "Phụ kiện / Điều khiển tùy chỉnh", + "intro": "Chọn điều khiển rồi gán phím hoặc nút chuột.", + "search": "Tìm điều khiển", + "import": "Nhập INI", + "reset": "Đặt lại", + "group0": "Nút chính", + "group1": "Nút phía trên", + "group2": "Phím hướng", + "group3": "Cần trái", + "group4": "Cần phải", + "group5": "Cảm ứng và công cụ", + "select": "Chọn…", + "add": "+ Thêm đầu vào", + "capture": "Ghi phím", + "remove": "Xóa", + "builtin": "Mặc định: {inputs}", + "custom": "Đầu vào tùy chỉnh thay thế đầu vào mặc định.", + "noResults": "Không tìm thấy điều khiển.", + "download": "Tải INI", + "copy": "Sao chép cấu hình", + "previewEmpty": "Thêm đầu vào để bắt đầu.", + "backControls": "Trở về điều khiển", + "switchGroup": "Đổi nhóm", + "moveFocus": "Di chuyển tiêu điểm", + "helpIntro": "Câu trả lời nhanh để bắt đầu.", + "configIntro": "Kiểm tra đầu vào rồi sao chép hoặc tải tệp.", + "captureTitle": "Nhấn phím cho {action}", + "cancelCapture": "Hủy ghi phím", + "keepEditing": "Tiếp tục chỉnh sửa", + "discard": "Bỏ thay đổi", + "faq0Question": "Làm sao gán phím?", + "faq0Answer": "Chọn nhóm và Thêm đầu vào, rồi dùng Ghi phím hoặc danh sách. Một hành động có thể có nhiều đầu vào.", + "faq1Question": "Đặt tệp INI ở đâu?", + "faq1Answer": "Đặt anyps5-input.ini cạnh tệp chạy trò chơi được tạo. Dùng ANYPS5_INPUT_CONFIG cho đường dẫn khác.", + "faq2Question": "Tại sao không ghi được phím?", + "faq2Answer": "Trình duyệt hoặc hệ thống có thể chặn phím tắt. Chọn từ danh sách. Escape hủy ghi phím; hãy gán Escape từ danh sách.", + "faq3Question": "Thay đổi có tự lưu không?", + "faq3Answer": "Thay đổi chỉ ở trong bộ nhớ: sao chép hoặc tải xuống trước khi tải lại. Chỉ ngôn ngữ được lưu. Hành động chưa cấu hình giữ mặc định.", + "searchOptions": "Tìm tùy chọn", + "noOptions": "Không có tùy chọn phù hợp." + }, + "th": { + "controls": "การควบคุม", + "configuration": "การกำหนดค่า", + "help": "ช่วยเหลือ", + "settings": "การตั้งค่า", + "language": "ภาษา", + "close": "ปิด", + "eyebrow": "อุปกรณ์เสริม / ปรับแต่งการควบคุม", + "intro": "เลือกการควบคุม แล้วกำหนดปุ่มแป้นพิมพ์หรือเมาส์", + "search": "ค้นหาการควบคุม", + "import": "นำเข้า INI", + "reset": "รีเซ็ต", + "group0": "ปุ่มหลัก", + "group1": "ปุ่มด้านบน", + "group2": "ปุ่มทิศทาง", + "group3": "สติ๊กซ้าย", + "group4": "สติ๊กขวา", + "group5": "สัมผัสและเครื่องมือ", + "select": "เลือก…", + "add": "+ เพิ่มอินพุต", + "capture": "บันทึกปุ่ม", + "remove": "ลบ", + "builtin": "ค่าเริ่มต้น: {inputs}", + "custom": "อินพุตที่กำหนดเองจะแทนที่ค่าเริ่มต้น", + "noResults": "ไม่พบการควบคุม", + "download": "ดาวน์โหลด INI", + "copy": "คัดลอกการกำหนดค่า", + "previewEmpty": "เพิ่มอินพุตเพื่อเริ่มต้น", + "backControls": "กลับไปการควบคุม", + "switchGroup": "เปลี่ยนกลุ่ม", + "moveFocus": "ย้ายโฟกัส", + "helpIntro": "คำตอบสั้นๆ เพื่อช่วยเริ่มต้น", + "configIntro": "ตรวจสอบอินพุต แล้วคัดลอกหรือดาวน์โหลดไฟล์", + "captureTitle": "กดปุ่มสำหรับ {action}", + "cancelCapture": "ยกเลิกการบันทึก", + "keepEditing": "แก้ไขต่อ", + "discard": "ทิ้งการเปลี่ยนแปลง", + "faq0Question": "กำหนดปุ่มอย่างไร?", + "faq0Answer": "เลือกกลุ่มและเพิ่มอินพุต แล้วใช้บันทึกปุ่มหรือรายการ หนึ่งการกระทำมีได้หลายอินพุต", + "faq1Question": "วางไฟล์ INI ที่ไหน?", + "faq1Answer": "วาง anyps5-input.ini ข้างไฟล์โปรแกรมเกมที่สร้างขึ้น ใช้ ANYPS5_INPUT_CONFIG สำหรับตำแหน่งอื่น", + "faq2Question": "ทำไมบางปุ่มจึงบันทึกไม่ได้?", + "faq2Answer": "ทางลัดของเบราว์เซอร์หรือระบบอาจดักปุ่มไว้ เลือกจากรายการแทน Escape ยกเลิกการบันทึก จึงต้องกำหนดจากรายการ", + "faq3Question": "บันทึกการเปลี่ยนแปลงอัตโนมัติไหม?", + "faq3Answer": "การเปลี่ยนแปลงอยู่ในหน่วยความจำเท่านั้น คัดลอกหรือดาวน์โหลดก่อนโหลดใหม่ บันทึกเฉพาะภาษา การกระทำที่ยังไม่ตั้งค่าจะใช้ค่าเริ่มต้น", + "searchOptions": "ค้นหาตัวเลือก", + "noOptions": "ไม่พบตัวเลือกที่ตรงกัน" + }, + "sw": { + "controls": "Vidhibiti", + "configuration": "Usanidi", + "help": "Msaada", + "settings": "Mipangilio", + "language": "Lugha", + "close": "Funga", + "eyebrow": "Vifaa / Vidhibiti maalum", + "intro": "Chagua kidhibiti kisha weka ingizo la kibodi au kipanya.", + "search": "Tafuta vidhibiti", + "import": "Ingiza INI", + "reset": "Weka upya", + "group0": "Vibonye vikuu", + "group1": "Vibonye vya juu", + "group2": "Vibonye vya mwelekeo", + "group3": "Kijiti cha kushoto", + "group4": "Kijiti cha kulia", + "group5": "Mguso na zana", + "select": "Chagua…", + "add": "+ Ongeza ingizo", + "capture": "Rekodi", + "remove": "Ondoa", + "builtin": "Chaguo-msingi: {inputs}", + "custom": "Ingizo maalum linachukua nafasi ya ingizo la kawaida.", + "noResults": "Hakuna vidhibiti vilivyopatikana.", + "download": "Pakua INI", + "copy": "Nakili usanidi", + "previewEmpty": "Ongeza ingizo ili kuanza.", + "backControls": "Rudi kwenye vidhibiti", + "switchGroup": "Badili kundi", + "moveFocus": "Hamisha fokasi", + "helpIntro": "Majibu mafupi ya kukusaidia kuanza.", + "configIntro": "Kagua ingizo kisha nakili au pakua faili.", + "captureTitle": "Bonyeza kitufe kwa {action}", + "cancelCapture": "Ghairi kurekodi", + "keepEditing": "Endelea kuhariri", + "discard": "Tupa mabadiliko", + "faq0Question": "Ninawekaje kitufe?", + "faq0Answer": "Chagua kundi na Ongeza ingizo, kisha Rekodi au tumia orodha. Kitendo kimoja kinaweza kuwa na ingizo nyingi.", + "faq1Question": "Ninaweka wapi faili la INI?", + "faq1Answer": "Weka anyps5-input.ini karibu na faili la programu ya mchezo lililozalishwa. Tumia ANYPS5_INPUT_CONFIG kwa njia nyingine.", + "faq2Question": "Kwa nini kitufe hakirekodiwi?", + "faq2Answer": "Njia ya mkato ya kivinjari au mfumo inaweza kukizuia. Chagua kwenye orodha. Escape hughairi kurekodi; kiweke kupitia orodha.", + "faq3Question": "Mabadiliko huhifadhiwa kiotomatiki?", + "faq3Answer": "Mabadiliko hubaki kwenye kumbukumbu: nakili au pakua kabla ya kupakia upya. Lugha pekee huhifadhiwa. Vitendo visivyosanidiwa hutumia chaguo-msingi.", + "searchOptions": "Tafuta chaguo", + "noOptions": "Hakuna chaguo linalolingana." + }, + "nl": { + "controls": "Bediening", + "configuration": "Configuratie", + "help": "Hulp", + "settings": "Instellingen", + "language": "Taal", + "close": "Sluiten", + "eyebrow": "Accessoires / Eigen bediening", + "intro": "Kies een actie en wijs toetsenbord- of muisinvoer toe.", + "search": "Bediening zoeken", + "import": "INI importeren", + "reset": "Herstellen", + "group0": "Actieknoppen", + "group1": "Schouderknoppen", + "group2": "Richtingsknoppen", + "group3": "Linker stick", + "group4": "Rechter stick", + "group5": "Aanraakvlak en hulpmiddelen", + "select": "Selecteren…", + "add": "+ Invoer toevoegen", + "capture": "Vastleggen", + "remove": "Verwijderen", + "builtin": "Standaard: {inputs}", + "custom": "Eigen invoer vervangt de standaardinvoer.", + "noResults": "Geen passende bediening gevonden.", + "download": "INI downloaden", + "copy": "Configuratie kopiëren", + "previewEmpty": "Voeg invoer toe om te beginnen.", + "backControls": "Terug naar bediening", + "switchGroup": "Groep wisselen", + "moveFocus": "Focus verplaatsen", + "helpIntro": "Korte antwoorden om te beginnen.", + "configIntro": "Controleer de invoer en kopieer of download het bestand.", + "captureTitle": "Druk op een toets voor {action}", + "cancelCapture": "Vastleggen annuleren", + "keepEditing": "Verder bewerken", + "discard": "Wijzigingen weggooien", + "faq0Question": "Hoe wijs ik een toets toe?", + "faq0Answer": "Kies een groep en Invoer toevoegen, dan Vastleggen of de lijst. Eén actie kan meerdere invoeren hebben.", + "faq1Question": "Waar plaats ik het INI-bestand?", + "faq1Answer": "Zet anyps5-input.ini naast het gegenereerde spelprogramma. Gebruik ANYPS5_INPUT_CONFIG voor een ander pad.", + "faq2Question": "Waarom wordt een toets niet vastgelegd?", + "faq2Answer": "Een browser- of systeemsneltoets kan hem onderscheppen. Kies uit de lijst. Escape annuleert vastleggen; wijs Escape via de lijst toe.", + "faq3Question": "Worden wijzigingen automatisch opgeslagen?", + "faq3Answer": "Wijzigingen staan alleen in het geheugen: kopieer of download vóór herladen. Alleen de taal wordt opgeslagen. Ongewijzigde acties houden hun standaardwaarden.", + "searchOptions": "Opties zoeken", + "noOptions": "Geen passende opties." + }, + "pl": { + "controls": "Sterowanie", + "configuration": "Konfiguracja", + "help": "Pomoc", + "settings": "Ustawienia", + "language": "Język", + "close": "Zamknij", + "eyebrow": "Akcesoria / Własne sterowanie", + "intro": "Wybierz akcję i przypisz klawisz lub przycisk myszy.", + "search": "Szukaj sterowania", + "import": "Importuj INI", + "reset": "Resetuj", + "group0": "Główne przyciski", + "group1": "Górne przyciski", + "group2": "Przyciski kierunkowe", + "group3": "Lewy drążek", + "group4": "Prawy drążek", + "group5": "Dotyk i narzędzia", + "select": "Wybierz…", + "add": "+ Dodaj wejście", + "capture": "Zarejestruj", + "remove": "Usuń", + "builtin": "Domyślne: {inputs}", + "custom": "Własne wejścia zastępują domyślne.", + "noResults": "Nie znaleziono pasującej akcji.", + "download": "Pobierz INI", + "copy": "Kopiuj konfigurację", + "previewEmpty": "Dodaj wejście, aby zacząć.", + "backControls": "Wróć do sterowania", + "switchGroup": "Zmień grupę", + "moveFocus": "Przenieś fokus", + "helpIntro": "Krótkie odpowiedzi na początek.", + "configIntro": "Sprawdź wejścia i skopiuj lub pobierz plik.", + "captureTitle": "Naciśnij klawisz dla {action}", + "cancelCapture": "Anuluj rejestrację", + "keepEditing": "Edytuj dalej", + "discard": "Odrzuć zmiany", + "faq0Question": "Jak przypisać klawisz?", + "faq0Answer": "Wybierz grupę i Dodaj wejście, potem Zarejestruj lub listę. Jedna akcja może mieć kilka wejść.", + "faq1Question": "Gdzie umieścić plik INI?", + "faq1Answer": "Umieść anyps5-input.ini obok wygenerowanego pliku wykonywalnego gry. Inny adres podaj w ANYPS5_INPUT_CONFIG.", + "faq2Question": "Dlaczego klawisz nie jest rejestrowany?", + "faq2Answer": "Skrót przeglądarki lub systemu może go przechwycić. Wybierz z listy. Escape anuluje rejestrację; przypisz go z listy.", + "faq3Question": "Czy zmiany zapisują się automatycznie?", + "faq3Answer": "Zmiany są tylko w pamięci: skopiuj lub pobierz przed odświeżeniem. Zapisywany jest tylko język. Nieustawione akcje zachowują wartości domyślne.", + "searchOptions": "Szukaj opcji", + "noOptions": "Brak pasujących opcji." + } +}; + +export function translate(locale, key, values = {}) { + const text = messages[locale]?.[key] ?? messages.en[key] ?? key; + return text.replace(/\{(\w+)\}/gu, (match, name) => String(values[name] ?? match)); +} + +export function diagnostic(locale, message) { + if (locale !== 'ar-EG') return message; + return message + .replace('Configuration must be a list of bindings.', 'الإعدادات لازم تكون قائمة روابط أزرار.') + .replace('Unknown or empty action.', 'الحركة مش معروفة أو فاضية.') + .replace('Input type must be KEY, MOUSE or WHEEL.', 'نوع الإدخال لازم يكون KEY أو MOUSE أو WHEEL.') + .replace(/(\w+) cannot use (\w+)\./gu, '$1 مينفعش يستخدم $2.') + .replace(/Unknown or empty (\w+) value\./gu, 'قيمة $1 مش معروفة أو فاضية.') + .replace('This SDL key name cannot be represented in the current INI comment syntax.', 'اسم الزرار ده مينفعش يتحفظ بالصيغة الحالية لملف INI.') + .replace(/duplicate (.+) will be omitted\./gu, 'الربط $1 متكرر وهيتحفظ مرة واحدة.') + .replace(' controls ', ' بيشغّل ') + .replaceAll(' (built-in)', ' (افتراضي)') + .replaceAll(' and ', ' و ') + .replace('Expected Action = TYPE:VALUE.', 'الصيغة المطلوبة: Action = TYPE:VALUE.') + .replace('Duplicate binding ignored.', 'الربط المتكرر تم تجاهله.') + .replace('This physical key cannot be captured in the current INI format. Select an SDL key from the list.', 'الزرار ده مينفعش يتسجل بالصيغة الحالية لملف INI. اختار زرار SDL من القائمة.') + .replace('Composition and dead keys are ambiguous. Select an SDL key from the list.', 'زرار تركيب الحروف مش واضح في التسجيل. اختار زرار SDL من القائمة.'); +} + +export function initialLocale(savedLocale) { + return locales.some(item => item.code === savedLocale) ? savedLocale : 'en'; +} diff --git a/tools/input-mapper/index.html b/tools/input-mapper/index.html new file mode 100644 index 0000000000..3b340b95e6 --- /dev/null +++ b/tools/input-mapper/index.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="color-scheme" content="dark"> + <title>AnyPS5 · Input mapper + + + + + + +
+ AnyPS5 + +
+ + + +
+
+
+ +
+

Accessories / Custom controls

+

Face buttons

+

Choose a control, then assign your keyboard or mouse inputs.

+
+
+ +
+ + + +
+
+
+ + +
+

+ +
+
© Amr Yasser←→ Switch groupTab Move focus
+ +

Press a key

+

Capture uses physical key positions. Escape cancels capture; choose Escape from the list to assign it. Some browser and system shortcuts require manual selection.

+

+ +
+ +

Discard unsaved changes?

+

Copy or download your configuration first if you want to keep it.

+
+ + +
+
+ + +

+ +
+ +
+ + + diff --git a/tools/input-mapper/keys.js b/tools/input-mapper/keys.js new file mode 100644 index 0000000000..5c44d49036 --- /dev/null +++ b/tools/input-mapper/keys.js @@ -0,0 +1,230 @@ +export const keyNames = [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + "Return", + "Escape", + "Backspace", + "Tab", + "Space", + "-", + "=", + "[", + "]", + "\\", + "#", + ";", + "'", + "`", + ",", + ".", + "/", + "CapsLock", + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "PrintScreen", + "ScrollLock", + "Pause", + "Insert", + "Home", + "PageUp", + "Delete", + "End", + "PageDown", + "Right", + "Left", + "Down", + "Up", + "Numlock", + "Keypad /", + "Keypad *", + "Keypad -", + "Keypad +", + "Keypad Enter", + "Keypad 1", + "Keypad 2", + "Keypad 3", + "Keypad 4", + "Keypad 5", + "Keypad 6", + "Keypad 7", + "Keypad 8", + "Keypad 9", + "Keypad 0", + "Keypad .", + "Application", + "Power", + "Keypad =", + "F13", + "F14", + "F15", + "F16", + "F17", + "F18", + "F19", + "F20", + "F21", + "F22", + "F23", + "F24", + "Execute", + "Help", + "Menu", + "Select", + "Stop", + "Again", + "Undo", + "Cut", + "Copy", + "Paste", + "Find", + "Mute", + "VolumeUp", + "VolumeDown", + "Keypad ,", + "Keypad = (AS400)", + "AltErase", + "SysReq", + "Cancel", + "Clear", + "Prior", + "Separator", + "Out", + "Oper", + "Clear / Again", + "CrSel", + "ExSel", + "Keypad 00", + "Keypad 000", + "ThousandsSeparator", + "DecimalSeparator", + "CurrencyUnit", + "CurrencySubUnit", + "Keypad (", + "Keypad )", + "Keypad {", + "Keypad }", + "Keypad Tab", + "Keypad Backspace", + "Keypad A", + "Keypad B", + "Keypad C", + "Keypad D", + "Keypad E", + "Keypad F", + "Keypad XOR", + "Keypad ^", + "Keypad %", + "Keypad <", + "Keypad >", + "Keypad &", + "Keypad &&", + "Keypad |", + "Keypad ||", + "Keypad :", + "Keypad #", + "Keypad Space", + "Keypad @", + "Keypad !", + "Keypad MemStore", + "Keypad MemRecall", + "Keypad MemClear", + "Keypad MemAdd", + "Keypad MemSubtract", + "Keypad MemMultiply", + "Keypad MemDivide", + "Keypad +/-", + "Keypad Clear", + "Keypad ClearEntry", + "Keypad Binary", + "Keypad Octal", + "Keypad Decimal", + "Keypad Hexadecimal", + "Left Ctrl", + "Left Shift", + "Left Alt", + "Left GUI", + "Right Ctrl", + "Right Shift", + "Right Alt", + "Right GUI", + "ModeSwitch", + "AudioNext", + "AudioPrev", + "AudioStop", + "AudioPlay", + "AudioMute", + "MediaSelect", + "WWW", + "Mail", + "Calculator", + "Computer", + "AC Search", + "AC Home", + "AC Back", + "AC Forward", + "AC Stop", + "AC Refresh", + "AC Bookmarks", + "BrightnessDown", + "BrightnessUp", + "DisplaySwitch", + "KBDIllumToggle", + "KBDIllumDown", + "KBDIllumUp", + "Eject", + "Sleep", + "App1", + "App2", + "AudioRewind", + "AudioFastForward", + "SoftLeft", + "SoftRight", + "Call", + "EndCall" +]; + +export const exportableKeys = keyNames.filter(name => !/[#;]/u.test(name)); diff --git a/tools/input-mapper/mapping.js b/tools/input-mapper/mapping.js new file mode 100644 index 0000000000..fe476f10f5 --- /dev/null +++ b/tools/input-mapper/mapping.js @@ -0,0 +1,35 @@ +export const groups = [ + {name: 'Face buttons', actions: ['Cross', 'Circle', 'Triangle', 'Square']}, + {name: 'Shoulders, triggers and stick clicks', actions: ['L1', 'R1', 'L2', 'R2', 'L3', 'R3']}, + {name: 'Directional controls', actions: ['Up', 'Right', 'Down', 'Left']}, + {name: 'Left analog stick', actions: ['LeftStickLeft', 'LeftStickRight', 'LeftStickUp', 'LeftStickDown']}, + {name: 'Right analog stick', actions: ['RightStickLeft', 'RightStickRight', 'RightStickUp', 'RightStickDown']}, + {name: 'Touch and utility', actions: ['Options', 'TouchLeft', 'TouchRight', 'StickModifier', 'Shake', 'ToggleMouse', 'ToggleFullscreen', 'TogglePerformanceOverlay', 'Screenshot']} +]; + +export const actions = groups.flatMap(group => group.actions); +export const padButtons = new Set(['Cross', 'Circle', 'Triangle', 'Square', 'L1', 'R1', 'L2', 'R2', 'L3', 'R3', 'Options', 'Up', 'Right', 'Down', 'Left']); +export const mouseValues = ['Left', 'Middle', 'Right', 'X1', 'X2']; +export const wheelValues = ['Up', 'Down']; + +export const defaults = { + Cross: ['KEY:Return', 'KEY:Space'], Circle: ['KEY:C'], Triangle: ['KEY:I'], Square: ['MOUSE:Left'], + L1: ['KEY:Q'], R1: ['KEY:E', 'KEY:Left Alt', 'KEY:Right Alt'], L2: ['KEY:R', 'MOUSE:X1'], R2: ['MOUSE:Right'], + L3: ['KEY:Left Shift', 'KEY:Right Shift'], R3: ['KEY:Left Ctrl', 'KEY:Right Ctrl'], Options: ['KEY:Escape'], + Up: ['KEY:Up', 'WHEEL:Up'], Right: ['KEY:Right'], Down: ['KEY:Down', 'WHEEL:Down'], Left: ['KEY:Left'], + LeftStickLeft: ['KEY:A'], LeftStickRight: ['KEY:D'], LeftStickUp: ['KEY:W'], LeftStickDown: ['KEY:S'], + RightStickLeft: ['KEY:F'], RightStickRight: ['KEY:H'], RightStickUp: ['KEY:T'], RightStickDown: ['KEY:G'], + TouchLeft: ['KEY:Backspace'], TouchRight: ['KEY:Tab'], StickModifier: [], Shake: ['KEY:X'], + ToggleMouse: ['KEY:F7', 'MOUSE:Middle'], ToggleFullscreen: ['KEY:F11'], TogglePerformanceOverlay: ['KEY:F10'], Screenshot: ['KEY:F12'] +}; + +export function typesForAction(action) { + if (!actions.includes(action)) return []; + if (['ToggleFullscreen', 'TogglePerformanceOverlay', 'Screenshot'].includes(action)) return ['KEY']; + return padButtons.has(action) ? ['KEY', 'MOUSE', 'WHEEL'] : ['KEY', 'MOUSE']; +} + +export function canonicalValue(value, choices) { + if (typeof value !== 'string') return undefined; + return choices.find(choice => choice.toUpperCase() === value.trim().toUpperCase()); +} diff --git a/tools/input-mapper/package.json b/tools/input-mapper/package.json new file mode 100644 index 0000000000..064ea01892 --- /dev/null +++ b/tools/input-mapper/package.json @@ -0,0 +1,15 @@ +{ + "name": "anyps5-input-mapper", + "version": "1.0.0", + "private": true, + "description": "Browser-based keyboard and mouse input configurator for AnyPS5", + "type": "module", + "license": "GPL-2.0", + "engines": { + "node": ">=22" + }, + "scripts": { + "start": "node serve.js", + "test": "node --test tests/*.test.js" + } +} diff --git a/tools/input-mapper/serve.js b/tools/input-mapper/serve.js new file mode 100644 index 0000000000..ccdb20e26c --- /dev/null +++ b/tools/input-mapper/serve.js @@ -0,0 +1,30 @@ +import {createServer} from 'node:http'; +import {readFile} from 'node:fs/promises'; + +const files = new Set(['index.html', 'styles.css', 'app.js', 'mapping.js', 'keys.js', 'capture.js', 'validation.js', 'i18n.js', 'dropdown.js']); +const mime = {html: 'text/html', css: 'text/css', js: 'text/javascript'}; +const port = Number(process.env.PORT || 4173); +const server = createServer(async (request, response) => { + const path = new URL(request.url, 'http://127.0.0.1').pathname; + const file = path === '/' ? 'index.html' : path.slice(1); + if (!['GET', 'HEAD'].includes(request.method)) { + response.writeHead(405, {Allow: 'GET, HEAD'}).end(); + return; + } + if (!files.has(file)) { + response.writeHead(404).end('Not found'); + return; + } + try { + const content = await readFile(new URL(file, import.meta.url)); + response.writeHead(200, {'Content-Type': `${mime[file.split('.').pop()]}; charset=utf-8`, 'Cache-Control': 'no-store'}); + response.end(request.method === 'HEAD' ? undefined : content); + } catch { + response.writeHead(500).end('Cannot read static file'); + } +}); +server.on('error', error => { + console.error(error.message); + process.exitCode = 1; +}); +server.listen(port, '127.0.0.1', () => console.log(`Input mapper: http://127.0.0.1:${port}`)); diff --git a/tools/input-mapper/styles.css b/tools/input-mapper/styles.css new file mode 100644 index 0000000000..1be7b42096 --- /dev/null +++ b/tools/input-mapper/styles.css @@ -0,0 +1,203 @@ +:root { color-scheme: dark; font-family: "Segoe UI", Helvetica, Arial, sans-serif; font-weight: 400; color: #f4f5f7; background: #101116; --muted: #c1c4ce; --line: #ffffff1c; --ease: cubic-bezier(.2,.75,.25,1); } +* { box-sizing: border-box; } +* { scrollbar-width: thin; scrollbar-color: #dbe0ea70 transparent; } +::-webkit-scrollbar { width: 4px; height: 4px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: #dbe0ea70; border-radius: 20px; } +::-webkit-scrollbar-thumb:hover { background: #eef1f8c0; } +::-webkit-scrollbar-button { display: none; width: 0; height: 0; } +::-webkit-scrollbar-corner { background: transparent; } +@supports selector(::-webkit-scrollbar) { * { scrollbar-width: auto; scrollbar-color: auto; } } +[hidden] { display: none !important; } +html { scroll-behavior: smooth; scroll-padding-top: 30px; } +body { margin: 0; min-height: 100vh; isolation: isolate; } +a { color: inherit; } +button, input, select, textarea { font: inherit; } +button, input, select { min-height: 42px; border: 1px solid #ffffff24; border-radius: 4px; color: inherit; background: #ffffff09; padding: 9px 14px; transition: color .2s, background .2s, border-color .2s, box-shadow .25s, transform .25s var(--ease); } +button { cursor: pointer; font-weight: 400; } +button:hover:not(:disabled) { background: #ffffff16; border-color: #ffffff70; } +button:active:not(:disabled) { transform: scale(.97); } +button:disabled { cursor: not-allowed; opacity: .42; } +:focus-visible { outline: none; box-shadow: inset 0 -2px 0 #7d9ac9; } +button:focus-visible:not(:disabled) { background: #31415b80; } +input:focus-visible, textarea:focus-visible { border-color: #788da9; } +select option { color: #f4f5f7; background: #22232a; } +.ambient { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; background: radial-gradient(ellipse at 80% 100%, #16385278, transparent 65%), radial-gradient(ellipse at 20% 18%, #4c484b50, transparent 60%), linear-gradient(140deg, #18191e, #101116 50%, #111820); } +.ambient-glow { position: absolute; width: 110%; height: 75%; top: -45%; left: -20%; border-radius: 50%; background: radial-gradient(ellipse, #c9b99a33, #b3bfd614 38%, transparent 68%); filter: blur(36px); animation: atmosphere 24s ease-in-out infinite alternate; } +.particles { position: absolute; inset: -12%; opacity: .7; background-image: radial-gradient(circle at 12% 29%, #e3d5b5d0 0 1px, transparent 2px), radial-gradient(circle at 32% 68%, #e0d5b9a0 0 1px, transparent 3px), radial-gradient(circle at 68% 23%, #e8d8b6b0 0 1px, transparent 3px), radial-gradient(circle at 88% 59%, #f4ead2a0 0 1px, transparent 2px), radial-gradient(circle at 54% 48%, #a2bfda90 0 1px, transparent 2px), radial-gradient(circle at 76% 78%, #ebd9ada0 0 1px, transparent 3px); background-size: 410px 370px, 590px 530px, 670px 490px, 710px 630px, 830px 730px, 970px 810px; animation: particles 38s linear infinite alternate; } +.particles.distant { opacity: .25; filter: blur(1px); animation-duration: 58s; animation-direction: alternate-reverse; } +header { max-width: 1640px; margin: auto; display: flex; align-items: center; gap: 42px; padding: 34px 5.5%; } +.brand { font-size: 20px; text-decoration: none; font-weight: 300; letter-spacing: -.5px; } +.system-nav { display: flex; gap: 32px; } +.system-nav a { text-decoration: none; font-size: 23px; font-weight: 300; color: #b8bac1; transition: color .2s; } +.system-nav .current, .system-nav a:hover { color: white; } +.system-tools { display: flex; align-items: center; gap: 24px; margin-inline-start: auto; } +.icon-button { display: grid; place-items: center; width: 38px; min-height: 38px; border: 0; border-radius: 50%; padding: 0; background: transparent; text-decoration: none; font-size: 26px; } +.search-icon { width: 15px; height: 15px; border: 2px solid currentColor; border-radius: 50%; position: relative; } +.search-icon::after { content: ''; position: absolute; height: 7px; width: 2px; background: currentColor; right: -4px; bottom: -5px; transform: rotate(-45deg); } +.translation-icon { display: flex; align-items: center; gap: 2px; direction: ltr; font-size: 18px; line-height: 1; } +.translation-icon span { display: grid; place-items: center; width: 21px; height: 23px; border: 1px solid #aab7cb70; border-radius: 4px; } +.translation-icon span:last-child { margin-left: -7px; margin-top: 11px; background: #252a34; } +#clock { font-size: 22px; font-weight: 300; min-width: 58px; font-variant-numeric: tabular-nums; white-space: nowrap; } +main { max-width: 1440px; min-height: calc(100vh - 210px); margin: auto; padding: 6px 5% 35px; } +main:focus { outline: none; box-shadow: none; } +#categories { display: flex; align-items: flex-start; gap: 20px; padding: 16px 8px 22px; margin-inline-start: -8px; min-height: 155px; overflow-x: auto; } +.category { background: transparent; border: 0; padding: 0; width: 108px; flex: 0 0 108px; text-align: center; } +.category:hover:not(:disabled), .category:focus-visible:not(:disabled) { background: transparent; color: white; } +.category:focus-visible { outline: none; box-shadow: none; } +.category-icon { display: grid; place-items: center; height: 92px; width: 92px; margin: 0 auto 13px; border-radius: 16px; border: 1px solid #ffffff24; background: linear-gradient(145deg, #565860a0, #272b33c0); color: #eeeeef; box-shadow: 0 8px 20px #0002; transition: transform .3s var(--ease), box-shadow .3s, background .3s; } +.category[aria-pressed="true"] .category-icon, .category:focus-visible .category-icon { transform: scale(1.06); background: linear-gradient(145deg, #6a778db0, #354359c0); border-color: #91a4c366; box-shadow: 0 12px 30px #0004; } +.category:hover .category-icon { transform: translateY(-3px); } +.category[aria-pressed="true"]:hover .category-icon { transform: scale(1.06) translateY(-2px); } +.category-label { display: block; font-size: 12px; color: #babdc6; line-height: 1.4; } +.category[aria-pressed="true"] .category-label { color: white; } +.face-icon { position: relative; width: 60px; height: 60px; font-size: 24px; line-height: 1; font-weight: 300; } +.face-icon span { position: absolute; display: grid; place-items: center; width: 24px; height: 24px; } +.face-icon span:nth-child(1) { top: 0; left: 18px; } +.face-icon span:nth-child(2) { right: 0; top: 18px; } +.face-icon span:nth-child(3) { bottom: 0; left: 18px; } +.face-icon span:nth-child(4) { left: 0; top: 18px; } +.shoulder-icon { border: 2px solid currentColor; border-radius: 6px 6px 13px 13px; padding: 7px 10px; font-size: 17px; } +.direction-icon { position: relative; width: 42px; height: 42px; background: currentColor; clip-path: polygon(34% 0,66% 0,66% 34%,100% 34%,100% 66%,66% 66%,66% 100%,34% 100%,34% 66%,0 66%,0 34%,34% 34%); } +.stick-icon { display: grid; place-items: center; border: 2px solid currentColor; border-radius: 50%; width: 43px; height: 43px; font-size: 18px; box-shadow: 0 4px 0 1px #ffffff45; } +.touch-icon { width: 50px; height: 33px; border: 2px solid currentColor; border-radius: 7px; background: radial-gradient(circle, #ffffff60 1px, transparent 1.5px) 0 0 / 7px 7px; } +.intro { margin: 8px 0 26px; animation: enter .6s var(--ease) both; } +.eyebrow { color: #b9bbc2; font-size: 13px; margin: 0 0 13px; } +h1 { font-size: clamp(34px, 4vw, 48px); font-weight: 300; letter-spacing: -.9px; line-height: 1.15; margin: 0; } +.intro > p:last-child { color: var(--muted); font-size: 15px; margin: 13px 0 0; } +.toolbar, .tools, .output-heading { display: flex; gap: 12px; align-items: center; } +.toolbar { justify-content: space-between; margin-bottom: 22px; } +.search { width: min(350px, 55%); } +.search input { width: 100%; border-radius: 30px; padding: 10px 20px; background: #ffffff08; } +input::placeholder { color: #b6b9c4; } +.tools button { border-radius: 30px; padding: 9px 22px; } +.group, .configuration-panel, .faq { background: #181a22b0; border: 1px solid var(--line); border-radius: 6px; backdrop-filter: blur(20px); box-shadow: 0 18px 45px #0002; } +.group { margin-bottom: 20px; animation: enter .35s var(--ease); } +.group h2 { font-size: 12px; font-weight: 400; color: #bdc2ce; margin: 0; padding: 18px 24px; border-bottom: 1px solid var(--line); } +.action { padding: 19px 24px; border-bottom: 1px solid #ffffff10; transition: background .25s; } +.action:last-child { border-bottom: 0; } +.action:hover { background: #ffffff06; } +.action:focus-within { background: #ffffff0c; } +.action-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; } +.action-title { display: flex; align-items: center; gap: 15px; } +.action-symbol { display: grid; place-items: center; width: 30px; height: 30px; font-size: 29px; line-height: 1; font-weight: 300; color: #eaedf5; } +.action h3 { margin: 0; font-size: 18px; font-weight: 300; } +.action-heading button { border: 0; min-height: 34px; font-size: 12px; border-radius: 30px; padding: 7px 14px; background: #ffffff0a; } +.hint { font-size: 12px; color: var(--muted); line-height: 1.65; overflow-wrap: anywhere; } +.action > .hint { margin: 7px 0 0; margin-inline-start: 45px; } +.binding { display: grid; grid-template-columns: 110px minmax(0, 1fr) auto auto; gap: 10px; margin-top: 17px; animation: enter .25s var(--ease); } +.dropdown { min-width: 0; } +.dropdown-trigger { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 10px 13px; font-size: 13px; text-align: start; border-radius: 9px; background: #0e131e66; } +.dropdown-trigger::after { content: ''; width: 6px; height: 6px; flex-shrink: 0; border-right: 1.5px solid #a7b2c5; border-bottom: 1.5px solid #a7b2c5; transform: rotate(45deg); margin-inline-start: auto; margin-top: -4px; } +.dropdown-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.dropdown-trigger[aria-expanded="true"] { background: #27344999; border-color: #677c9a; } +.binding button { font-size: 12px; padding: 8px 11px; } +[aria-invalid="true"] { border-color: #ff9a9a; } +.configuration-panel { padding: 32px; max-width: 960px; margin: auto; animation: enter .35s var(--ease); } +.output-heading { justify-content: space-between; } +.output-heading h2 { font-size: 22px; font-weight: 300; margin: 0; } +.tag { font-size: 11px; color: #b6bcc9; white-space: nowrap; } +.configuration-panel label { display: block; margin: 20px 0 10px; color: #d0d3db; font-size: 12px; direction: ltr; } +textarea { width: 100%; height: 340px; resize: vertical; padding: 18px; border: 1px solid #ffffff18; border-radius: 4px; background: #090b1290; color: #edf0f8; font: 14px/1.9 Consolas, ui-monospace, monospace; text-align: left; } +.export-actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin-top: 20px; } +.export-actions button { border-radius: 30px; } +.primary { background: #f0f1f5; color: #171920; border-color: #f0f1f5; } +.primary:hover:not(:disabled), .primary:focus-visible:not(:disabled) { background: #d8e0ee; color: #171920; box-shadow: none; } +.error { color: #ffb5b5; } +.warning { color: #e8cf9b; } +#validation { font-size: 12px; line-height: 1.6; } +#validation ul { padding-inline-start: 18px; } +#count { direction: ltr; unicode-bidi: isolate; } +#help { max-width: 960px; margin: auto; } +.faq { margin-bottom: 14px; } +.faq summary { cursor: pointer; padding: 22px 26px; font-size: 18px; font-weight: 300; } +.faq[open] summary { border-bottom: 1px solid var(--line); } +.faq p { padding: 0 26px 12px; color: var(--muted); line-height: 1.9; } +#picker-dialog { position: fixed; inset: auto; margin: 0; padding: 18px; border: 1px solid #70809c45; border-radius: 16px; background: linear-gradient(145deg, #242a36fa, #131a27fc); max-width: calc(100% - 32px); height: min(var(--picker-height, 480px), calc(100dvh - 32px)); max-height: calc(100dvh - 32px); box-shadow: 0 24px 80px #0008; } +#picker-dialog[open] { display: grid; grid-template-rows: auto auto minmax(0, 1fr); gap: 14px; } +#picker-dialog::backdrop { background: #080b1540; backdrop-filter: blur(3px); } +.picker-heading { display: flex; gap: 12px; align-items: center; justify-content: space-between; min-width: 0; } +.picker-heading h2 { font-size: 17px; line-height: 1.5; overflow-wrap: anywhere; } +.picker-heading button { min-height: 36px; margin: 0; padding: 6px 12px; border: 0; background: #ffffff08; font-size: 12px; flex-shrink: 0; } +#picker-search { width: 100%; min-width: 0; border-radius: 10px; padding: 11px 14px; font-size: 16px; } +#picker-options { min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 2px 3px; } +.picker-option { display: block; width: 100%; min-height: 44px; margin: 0 0 3px; border: 0; border-radius: 8px; padding: 12px 14px; background: transparent; text-align: start; font-size: 14px; line-height: 1.5; overflow-wrap: anywhere; } +.picker-option[aria-selected="true"] { background: #3547629c; color: #e4edff; } +.picker-option:focus-visible { background: #3b506fa6; box-shadow: none; } +#picker-empty { position: absolute; top: 160px; inset-inline: 18px; text-align: center; } +html[dir="rtl"] h1 { letter-spacing: 0; } +html[dir="rtl"] .intro > p:last-child { line-height: 1.9; } +html[dir="rtl"] .brand, footer > span:first-child { direction: ltr; unicode-bidi: isolate; } +#status { color: #dae3f6; font-size: 13px; line-height: 1.6; } +#import-report { margin-top: 22px; padding: 24px; border: 1px solid var(--line); background: #171b25c0; border-radius: 6px; overflow-wrap: anywhere; } +#import-report h2 { font-size: 23px; font-weight: 300; } +dialog { width: min(520px, calc(100% - 32px)); max-height: calc(100dvh - 32px); overflow-y: auto; color: inherit; background: linear-gradient(140deg, #30323bef, #181d28f5); border: 1px solid #ffffff3c; border-radius: 6px; padding: 32px; box-shadow: 0 20px 100px #0008; } +dialog[open] { animation: dialog-enter .3s var(--ease); } +dialog h2 { font-size: 28px; font-weight: 300; margin: 0; } +dialog p { line-height: 1.7; font-size: 14px; color: var(--muted); } +dialog button { border-radius: 30px; margin-top: 12px; } +dialog form { display: flex; gap: 12px; flex-wrap: wrap; } +dialog::backdrop { background: #080b15a0; backdrop-filter: blur(12px); } +footer { display: flex; justify-content: space-between; align-items: center; gap: 18px; padding: 20px 5.5% 30px; max-width: 1640px; margin: auto; color: #b1b6c2; font-size: 12px; } +.navigation-hint { display: flex; gap: 7px; align-items: center; } +kbd { border: 1px solid #ffffff50; border-radius: 4px; padding: 1px 5px; font: inherit; color: #e3e5eb; } +.footer-divider { width: 1px; height: 12px; background: #ffffff30; margin: 0 8px; } +.skip-link { position: fixed; top: -80px; left: 16px; padding: 12px; z-index: 2; background: #242836; } +.skip-link:focus { top: 12px; } +.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; } +@keyframes atmosphere { to { transform: translate(15%, 20%) scale(1.15); opacity: .7; } } +@keyframes particles { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(-35px, -65px, 0); } } +@keyframes enter { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } +@keyframes dialog-enter { from { opacity: 0; transform: translateY(16px) scale(.98); } to { opacity: 1; transform: translateY(0) scale(1); } } +@media (max-width: 1100px) { header { gap: 26px; } #categories { gap: 14px; } .category { width: 96px; flex-basis: 96px; } .category-icon { width: 82px; height: 82px; } } +@media (max-width: 760px) { + header { display: grid; grid-template-columns: 1fr auto; gap: 18px 12px; padding: 22px 5% 14px; } + .brand { font-size: 18px; } + .system-tools { margin: 0; gap: 12px; } + .system-nav { grid-column: 1 / -1; grid-row: 2; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 3px; background: #ffffff04; border-radius: 12px; padding: 4px; } + .system-nav a { min-width: 0; padding: 10px 6px; font-size: clamp(12px, 3.7vw, 16px); text-align: center; border-radius: 8px; overflow-wrap: anywhere; } + .system-nav .current { background: #3b4b6233; } + #clock { font-size: 15px; } + main { min-height: calc(100vh - 225px); padding-inline: 5%; } + #categories { gap: 10px; padding: 12px 8px 16px; min-height: 119px; scroll-snap-type: x proximity; } + .category { flex-basis: 80px; width: 80px; scroll-snap-align: center; } + .category-icon { width: 66px; height: 66px; border-radius: 13px; margin-bottom: 10px; } + .category-label { font-size: 10px; min-height: 28px; } + .face-icon { transform: scale(.8); } + .intro { margin: 9px 0 22px; } + h1 { font-size: clamp(28px, 7.5vw, 36px); } + .intro > p:last-child { font-size: 13px; line-height: 1.7; } + .eyebrow { font-size: 11px; margin-bottom: 10px; } + .toolbar { flex-wrap: wrap; gap: 10px; } + .search, .tools { width: 100%; } + .search input { font-size: 16px; min-height: 44px; } + .tools button { flex: 1; min-height: 44px; padding: 10px 14px; font-size: 13px; } + .group h2 { padding: 15px 16px; font-size: 11px; } + .action { padding: 17px 16px; } + .action-heading { flex-wrap: wrap; gap: 10px; } + .action-title { gap: 10px; min-width: 0; flex: 1 1 140px; } + .action h3 { font-size: 16px; overflow-wrap: anywhere; } + .action-heading button { min-height: 38px; max-width: 100%; } + .action-symbol { width: 25px; flex-shrink: 0; } + .action > .hint { margin-inline-start: 0; font-size: 11px; } + .binding { grid-template-columns: 104px minmax(0, 1fr); gap: 10px; margin-top: 14px; } + .binding > button { min-height: 44px; border-radius: 9px; } + .dropdown-trigger { font-size: 14px; } + .configuration-panel { padding: 18px; } + textarea { height: clamp(220px, 40dvh, 340px); font-size: 13px; } + .export-actions { grid-template-columns: 1fr; gap: 10px; } + .export-actions button { min-height: 44px; } + .faq summary { padding: 18px; font-size: 16px; } + .faq p { padding-inline: 18px; font-size: 14px; } + dialog { padding: 22px; border-radius: 16px; } + dialog h2 { font-size: 23px; } + footer { justify-content: center; padding-block: 22px; } + .navigation-hint { display: none; } +} +@media (max-width: 600px) { + #picker-dialog { inset-inline: 8px; top: auto; bottom: 8px; width: calc(100% - 16px); max-width: calc(100% - 16px); height: min(var(--picker-height, 480px), 76dvh); max-height: calc(100dvh - 16px); padding: 18px 16px calc(16px + env(safe-area-inset-bottom)); border-radius: 22px; } + #picker-dialog::backdrop { background: #05081080; backdrop-filter: blur(7px); } + .picker-option { min-height: 46px; font-size: 15px; } +} +@media (max-width: 360px) { #clock { display: none; } .system-tools { gap: 16px; } .binding { grid-template-columns: 94px minmax(0, 1fr); } .action-title { flex-basis: 120px; } } +@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { animation: none !important; transition: none !important; } } diff --git a/tools/input-mapper/tests/i18n.test.js b/tools/input-mapper/tests/i18n.test.js new file mode 100644 index 0000000000..65d98f0140 --- /dev/null +++ b/tools/input-mapper/tests/i18n.test.js @@ -0,0 +1,39 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {diagnostic, initialLocale, locales, messages, translate} from '../i18n.js'; +import {parseIni, serialize} from '../validation.js'; + +test('every selectable language supplies the main interface and every FAQ', () => { + assert.equal(locales[0].code, 'ar-EG'); + assert.equal(new Set(locales.map(item => item.code)).size, locales.length); + const required = ['controls', 'configuration', 'help', 'settings', 'language', 'close', 'intro', 'search', 'import', 'reset', 'select', 'add', 'capture', 'remove', 'download', 'copy', 'backControls', 'helpIntro', 'configIntro']; + for (let index = 0; index < 6; index++) required.push(`group${index}`); + for (let index = 0; index < 4; index++) required.push(`faq${index}Question`, `faq${index}Answer`); + for (const locale of locales) { + assert.doesNotThrow(() => new Intl.DateTimeFormat(locale.code)); + assert.equal(locale.direction, ['ar-EG', 'ur', 'fa'].includes(locale.code) ? 'rtl' : 'ltr'); + for (const key of required) assert.ok(messages[locale.code][key], `${locale.code}: ${key}`); + } +}); + +test('first visits use English while valid saved language preferences are retained', () => { + for (const saved of [undefined, null, '', 'unsupported']) assert.equal(initialLocale(saved), 'en'); + for (const locale of locales) assert.equal(initialLocale(locale.code), locale.code); +}); + +test('language substitution preserves canonical inputs and exported INI bytes', () => { + const source = 'Cross = KEY:F\nCross = KEY:Space\nToggleMouse = MOUSE:X1\n'; + const entries = parseIni(source).entries; + for (const locale of locales) { + assert.ok(translate(locale.code, 'builtin', {inputs: 'KEY:F, KEY:Space'}).includes('KEY:F, KEY:Space')); + assert.equal(serialize(entries), source); + } + assert.equal(translate('ar-EG', 'captured', {value: 'F', action: 'Cross'}), 'تم تسجيل F لـ Cross.'); + assert.equal(translate('unsupported', 'help'), 'Help'); +}); + +test('Egyptian diagnostics retain action tokens and unknown technical details', () => { + assert.equal(diagnostic('ar-EG', 'Cross: Unknown or empty KEY value.'), 'Cross: قيمة KEY مش معروفة أو فاضية.'); + assert.equal(diagnostic('ar-EG', 'Unexpected diagnostic'), 'Unexpected diagnostic'); + assert.equal(diagnostic('fr', 'Cross: Unknown or empty KEY value.'), 'Cross: Unknown or empty KEY value.'); +}); diff --git a/tools/input-mapper/tests/mapping.test.js b/tools/input-mapper/tests/mapping.test.js new file mode 100644 index 0000000000..8c34cceb70 --- /dev/null +++ b/tools/input-mapper/tests/mapping.test.js @@ -0,0 +1,154 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import {actions, defaults, mouseValues, padButtons, typesForAction} from '../mapping.js'; +import {exportableKeys, keyNames} from '../keys.js'; +import {captureKey} from '../capture.js'; +import {parseIni, serialize, validate} from '../validation.js'; + +const binding = (action, type = 'KEY', value = 'F') => ({action, type, value}); + +test('action names and pad button categories agree with the current native parser', () => { + const source = readFileSync(new URL('../../../core/libs/prx/libSceVideoOut/src/InputMapping.cpp', import.meta.url), 'utf8'); + const definitions = [...source.matchAll(/InputAction\{"(\w+)", Pad::InputControl::(\w+),/gu)]; + assert.deepEqual([...actions].sort(), definitions.map(match => match[1]).sort()); + assert.deepEqual([...padButtons].sort(), definitions.filter(match => match[2] === 'Button').map(match => match[1]).sort()); + assert.match(source, /SDL_GetScancodeFromName\(keyName\.c_str\(\)\)/u); + const keyboardOnly = [...source.matchAll(/if \(action\.control == Pad::InputControl::(\w+)\) \{\s*throw std::runtime_error\("\w+ can only use a keyboard key"\);/gu)].map(match => match[1]); + assert.deepEqual(actions.filter(action => typesForAction(action).length === 1).sort(), keyboardOnly.sort()); + assert.match(source, /if \(action\.control != Pad::InputControl::Button\)/u); +}); + +test('built-in conflict catalogue agrees with the current native bindings', () => { + const source = readFileSync(new URL('../../../core/libs/prx/libScePad/include/InputMapping.hpp', import.meta.url), 'utf8'); + const special = {RETURN: 'Return', SPACE: 'Space', ESCAPE: 'Escape', BACKSPACE: 'Backspace', TAB: 'Tab', UP: 'Up', RIGHT: 'Right', DOWN: 'Down', LEFT: 'Left', LALT: 'Left Alt', RALT: 'Right Alt', LSHIFT: 'Left Shift', RSHIFT: 'Right Shift', LCTRL: 'Left Ctrl', RCTRL: 'Right Ctrl'}; + const native = Object.fromEntries(actions.map(action => [action, []])); + for (const match of source.matchAll(/InputBinding\{SDL_SCANCODE_(\w+), MouseButton::(\w+), InputControl::(\w+)(?:, PadButton::(\w+))?(?:, (-?\d+))?\}/gu)) { + const [, key, mouse, control, button, wheel] = match; + const action = control === 'Button' ? button : control; + const value = key !== 'UNKNOWN' ? `KEY:${special[key] || key}` : mouse !== 'None' ? `MOUSE:${mouse}` : `WHEEL:${wheel === '1' ? 'Up' : 'Down'}`; + native[action].push(value); + } + assert.deepEqual(native, defaults); +}); + +test('every action serializes with a supported key', () => { + const input = actions.map(action => binding(action)); + assert.equal(serialize(input), actions.map(action => `${action} = KEY:F\n`).join('')); +}); + +test('multiple inputs are alternatives and omitted actions stay omitted', () => { + assert.equal(serialize([binding('Cross'), binding('Cross', 'KEY', 'Space')]), 'Cross = KEY:F\nCross = KEY:Space\n'); + assert.equal(serialize([]), ''); + assert.equal(serialize([binding('R2', 'MOUSE', 'Right')]), 'R2 = MOUSE:Right\n'); +}); + +test('case and whitespace normalize; duplicate bindings warn and serialize once', () => { + const input = [binding(' cross ', ' key ', ' f '), binding('Cross')]; + assert.equal(serialize(input), 'Cross = KEY:F\n'); + assert.equal(validate(input).warnings.filter(warning => warning.message.includes('duplicate')).length, 1); +}); + +test('invalid and incomplete bindings block export', () => { + for (const input of [binding('Unknown'), binding('Cross', 'GAMEPAD'), binding('Cross', 'KEY', 'Enter'), binding('Cross', 'KEY', 'KeyF'), binding('Cross', 'KEY', ''), binding('Cross', 'KEY', 'F\nR2 = MOUSE:Left'), null, {}]) { + assert.ok(validate([input]).errors.length); + assert.throws(() => serialize([input])); + } + assert.ok(validate({}).errors.length); +}); + +test('all exportable SDL names validate and unsafe comment names are rejected', () => { + for (const key of exportableKeys) assert.equal(validate([binding('Cross', 'KEY', key)]).errors.length, 0, key); + for (const key of [';', '#', 'Keypad #']) assert.ok(validate([binding('Cross', 'KEY', key)]).errors.length, key); + assert.equal(new Set(keyNames).size, keyNames.length); +}); + +test('mouse values and action restrictions match the current INI format', () => { + for (const action of actions) { + for (const value of mouseValues) { + assert.equal(validate([binding(action, 'MOUSE', value)]).errors.length === 0, !['ToggleFullscreen', 'TogglePerformanceOverlay', 'Screenshot'].includes(action)); + } + } + assert.ok(validate([binding('Cross', 'MOUSE', 'Button1')]).errors.length); +}); + +test('wheel directions only map to pad buttons', () => { + for (const action of actions) { + for (const value of ['Up', 'Down']) { + assert.equal(validate([binding(action, 'WHEEL', value)]).errors.length === 0, padButtons.has(action)); + } + assert.deepEqual(typesForAction(action).includes('WHEEL'), padButtons.has(action)); + } + assert.ok(validate([binding('Cross', 'WHEEL', 'Left')]).errors.length); +}); + +test('new utility actions import and round trip without accepting unsupported sources', () => { + const source = 'StickModifier = MOUSE:X2\nShake = MOUSE:Left\nTogglePerformanceOverlay = KEY:F9\nScreenshot = KEY:F8\n'; + const imported = parseIni(source); + assert.deepEqual(imported.errors, []); + assert.equal(serialize(imported.entries), source); + for (const action of ['StickModifier', 'Shake', 'TogglePerformanceOverlay', 'Screenshot']) { + assert.ok(validate([binding(action, 'WHEEL', 'Up')]).errors.length); + } + for (const action of ['TogglePerformanceOverlay', 'Screenshot']) { + assert.deepEqual(parseIni(`${action} = MOUSE:Left`).errors.map(error => error.line), [1]); + } +}); + +test('updated built-in inputs warn until their owning action is overridden', () => { + for (const [action, type, value] of [['L2', 'KEY', 'R'], ['L2', 'MOUSE', 'X1'], ['ToggleMouse', 'KEY', 'F7'], ['Shake', 'KEY', 'X'], ['Screenshot', 'KEY', 'F12'], ['TogglePerformanceOverlay', 'KEY', 'F10']]) { + const input = binding('Cross', type, value); + assert.ok(validate([input]).warnings.some(warning => warning.message.includes(`${action} (built-in)`))); + assert.equal(validate([input, binding(action, 'KEY', 'J')]).warnings.length, 0); + } +}); + +test('conflicts warn without blocking, including omitted built-in actions', () => { + const input = [binding('Cross'), binding('Square')]; + const result = validate(input); + assert.equal(result.errors.length, 0); + assert.ok(result.warnings.some(warning => warning.message.includes('Cross and Square and RightStickLeft (built-in)'))); + assert.doesNotThrow(() => serialize(input)); + const overridden = validate([binding('Cross'), binding('RightStickLeft', 'KEY', 'J')]); + assert.equal(overridden.warnings.length, 0); +}); + +test('serialization is deterministic in action order and preserves alternative order', () => { + const input = [binding('ToggleMouse', 'MOUSE', 'X1'), binding('Cross', 'KEY', 'Space'), binding('Cross')]; + assert.equal(serialize(input), 'Cross = KEY:Space\nCross = KEY:F\nToggleMouse = MOUSE:X1\n'); + assert.deepEqual(input, [binding('ToggleMouse', 'MOUSE', 'X1'), binding('Cross', 'KEY', 'Space'), binding('Cross')]); +}); + +test('import supports BOM, CRLF, inline comments, extra equals and colons in key names', () => { + const result = parseIni('\uFEFF# config\r\n cross = key : space ; note\r\nCross=KEY:Space\r\nR2=mouse:right\r\nL1=KEY:=\r\nL2=KEY:Keypad :\r\n'); + assert.deepEqual(result.errors, []); + assert.equal(result.warnings.length, 1); + assert.deepEqual(result.entries, [binding('Cross', 'KEY', 'Space'), binding('R2', 'MOUSE', 'Right'), binding('L1', 'KEY', '='), binding('L2', 'KEY', 'Keypad :')]); +}); + +test('import rejects sections, unsupported extensions and malformed lines with line numbers', () => { + const result = parseIni('[Bindings]\nCross=NONE\nCross=KEY:4\nToggleFullscreen=MOUSE:Left\nLeftStickUp=WHEEL:Up\nCross=KEY:;\nCross=KEY:KeyF\nCross=KEY:\n'); + assert.deepEqual(result.errors.map(error => error.line), [1, 2, 4, 5, 6, 7, 8]); + assert.deepEqual(result.entries, [binding('Cross', 'KEY', '4')]); + assert.throws(() => parseIni(null), TypeError); +}); + +test('round trip is stable for every safe key and supported mouse/wheel source', () => { + const input = [...exportableKeys.map(value => binding('Cross', 'KEY', value)), ...mouseValues.map(value => binding('Square', 'MOUSE', value)), binding('R2', 'WHEEL', 'Down')]; + const text = serialize(input); + const imported = parseIni(text); + assert.equal(imported.errors.length, 0); + assert.equal(serialize(imported.entries), text); +}); + +test('capture maps physical positions and distinguishes modifiers and keypad', () => { + const cases = {KeyA: 'A', Digit1: '1', Enter: 'Return', NumpadEnter: 'Keypad Enter', Numpad1: 'Keypad 1', ControlLeft: 'Left Ctrl', AltRight: 'Right Alt', ArrowUp: 'Up', F24: 'F24', Equal: '=', Backslash: '\\'}; + for (const [code, value] of Object.entries(cases)) assert.deepEqual(captureKey({code, key: 'ignored'}), {value}); + assert.equal(captureKey({code: 'KeyQ', key: 'a'}).value, 'Q'); +}); + +test('capture rejects missing identifiers, composition and unsafe punctuation', () => { + for (const event of [{code: 'Unidentified'}, {key: 'a'}, {code: 'IntlBackslash'}, {code: 'Semicolon'}, {code: 'KeyA', isComposing: true}, {code: 'Quote', key: 'Dead'}, {code: 'KeyA', key: 'Process'}]) { + assert.ok(captureKey(event).error); + } +}); diff --git a/tools/input-mapper/validation.js b/tools/input-mapper/validation.js new file mode 100644 index 0000000000..ce4c7a8bb9 --- /dev/null +++ b/tools/input-mapper/validation.js @@ -0,0 +1,93 @@ +import {actions, canonicalValue, defaults, mouseValues, typesForAction, wheelValues} from './mapping.js'; +import {keyNames} from './keys.js'; + +export function validate(entries) { + const errors = []; + const warnings = []; + const bindings = []; + const seen = new Set(); + if (!Array.isArray(entries)) return {errors: [{message: 'Configuration must be a list of bindings.'}], warnings, bindings}; + entries.forEach((entry, index) => { + const action = canonicalValue(entry?.action, actions); + const type = canonicalValue(entry?.type, ['KEY', 'MOUSE', 'WHEEL']); + let message; + if (!action) message = 'Unknown or empty action.'; + else if (!type) message = 'Input type must be KEY, MOUSE or WHEEL.'; + else if (!typesForAction(action).includes(type)) message = `${action} cannot use ${type}.`; + const choices = type === 'KEY' ? keyNames : type === 'MOUSE' ? mouseValues : wheelValues; + const value = canonicalValue(entry?.value, choices); + if (!message && !value) message = `Unknown or empty ${type} value.`; + if (!message && /[#;\r\n]/u.test(value)) message = 'This SDL key name cannot be represented in the current INI comment syntax.'; + if (message) { + errors.push({index, message: `${entry?.action || 'Binding'}: ${message}`}); + return; + } + const binding = {action, type, value}; + const identity = `${action}:${type}:${value}`; + if (seen.has(identity)) warnings.push({index, message: `${action}: duplicate ${type}:${value} will be omitted.`}); + else bindings.push(binding); + seen.add(identity); + }); + const configured = new Set(bindings.map(binding => binding.action)); + const sources = new Map(); + const effective = bindings.map(binding => ({action: binding.action, source: `${binding.type}:${binding.value}`, builtin: false})); + for (const action of actions) { + if (!configured.has(action)) { + for (const source of defaults[action]) effective.push({action, source, builtin: true}); + } + } + for (const binding of effective) { + const owners = sources.get(binding.source) || []; + owners.push(binding); + sources.set(binding.source, owners); + } + for (const [source, owners] of sources) { + if (owners.length > 1 && owners.some(owner => !owner.builtin)) { + warnings.push({message: `${source} controls ${owners.map(owner => `${owner.action}${owner.builtin ? ' (built-in)' : ''}`).join(' and ')}.`}); + } + } + return {errors, warnings, bindings}; +} + +export function serialize(entries) { + const result = validate(entries); + if (result.errors.length) throw new Error(result.errors.map(error => error.message).join('\n')); + const lines = []; + for (const action of actions) { + for (const binding of result.bindings.filter(binding => binding.action === action)) { + lines.push(`${action} = ${binding.type}:${binding.value}`); + } + } + return lines.length ? `${lines.join('\n')}\n` : ''; +} + +export function parseIni(text) { + if (typeof text !== 'string') throw new TypeError('INI content must be text.'); + const entries = []; + const errors = []; + const warnings = []; + const identities = new Set(); + text.replace(/^\uFEFF/u, '').split('\n').forEach((line, index) => { + const content = line.split(/[#;]/u, 1)[0].trim(); + if (!content) return; + const separator = content.indexOf('='); + const source = content.slice(separator + 1); + const colon = source.indexOf(':'); + if (separator === -1 || colon === -1) { + errors.push({line: index + 1, message: 'Expected Action = TYPE:VALUE.'}); + return; + } + const entry = {action: content.slice(0, separator), type: source.slice(0, colon), value: source.slice(colon + 1)}; + const result = validate([entry]); + if (result.errors.length) { + errors.push(...result.errors.map(error => ({line: index + 1, message: error.message}))); + return; + } + const binding = result.bindings[0]; + const identity = JSON.stringify(binding); + if (identities.has(identity)) warnings.push({line: index + 1, message: 'Duplicate binding ignored.'}); + else entries.push(binding); + identities.add(identity); + }); + return {entries, errors, warnings}; +} diff --git a/tools/nid_names.py b/tools/nid_names.py index 1818f593c9..9c32236fe8 100644 --- a/tools/nid_names.py +++ b/tools/nid_names.py @@ -51,7 +51,7 @@ def load_db(path): if not path.exists(): path.parent.mkdir(parents=True, exist_ok=True) print("downloading NID database to %s ..." % path, file=sys.stderr) - with tempfile.NamedTemporaryFile(dir=path.parent, prefix=path.name + ".", suffix=".tmp", delete=False) as download: + with tempfile.NamedTemporaryFile(dir=path.parent, prefix=".nid-db-", suffix=".tmp", delete=False) as download: temporary = Path(download.name) try: urllib.request.urlretrieve(DB_URL, str(temporary)) @@ -181,10 +181,13 @@ def symbol_file(path): def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser( description="Suggest real names for Unknown NID stubs") parser.add_argument("--db", default=os.environ.get("ANYPS5_NID_DB"), - help="NID database CSV (downloaded to a cache dir by default)") + help="NID database CSV; defaults to $ANYPS5_NID_DB, then to a copy downloaded to a cache dir") parser.add_argument("--nid", action="append", default=None, help="resolve only these NIDs instead of scanning the tree") parser.add_argument("--name", "--symbol", dest="name", action="append", default=None, @@ -222,7 +225,7 @@ def main(argv=None): db_path = Path(args.db) if args.db else DEFAULT_CACHE try: - db = load_db(db_path) + db = {} if requested_nids and not args.nid else load_db(db_path) if requested_nids: unknowns = {nid: "" for nid in (args.nid or [])} else: @@ -261,7 +264,4 @@ def main(argv=None): if __name__ == "__main__": - for stream in (sys.stdout, sys.stderr): - if hasattr(stream, "reconfigure"): - stream.reconfigure(encoding="utf-8") sys.exit(main()) diff --git a/tools/package_release.py b/tools/package_release.py index 88558908a6..c32336f6d8 100644 --- a/tools/package_release.py +++ b/tools/package_release.py @@ -4,6 +4,7 @@ import os import re import shutil import stat +import sys import tarfile import tempfile import zipfile @@ -15,12 +16,15 @@ WEB_FILES = ("index.html", "app.css", "app.mjs", "archive.mjs", "batch.mjs", "ou def windows_runtime(build): - for line in (build / "CMakeCache.txt").read_text(encoding="utf-8-sig").splitlines(): + cache = build / "CMakeCache.txt" + if not cache.is_file(): + raise RuntimeError(f"Missing CMakeCache.txt in build directory: {build}") + for line in cache.read_text(encoding="utf-8-sig").splitlines(): if line.startswith("CMAKE_CXX_COMPILER:"): compiler = line.partition("=")[2] if compiler and not compiler.endswith("-NOTFOUND"): return Path(compiler).parent - raise RuntimeError(f"CMAKE_CXX_COMPILER is not configured in {build / 'CMakeCache.txt'}") + raise RuntimeError(f"CMAKE_CXX_COMPILER is not configured in {cache}") def tracy_runtime(platform, build): @@ -52,6 +56,11 @@ def package(platform, build, output, version): licenses = [] if "libSceAjm.native.prx" in expected: licenses.append((ROOT / "3rdparty/vgmstream/COPYING", "libs/licenses/vgmstream.txt")) + if "libSceVorbisDec.prx" in expected: + licenses.append((ROOT / "3rdparty/ogg/COPYING", "libs/licenses/ogg.txt")) + licenses.append((ROOT / "3rdparty/vorbis/COPYING", "libs/licenses/vorbis.txt")) + if expected & {"libSceOpusDec.prx", "libSceOpusCeltDec.prx"}: + licenses.append((ROOT / "3rdparty/opus/COPYING", "libs/licenses/opus.txt")) if platform == "windows": runtime = windows_runtime(build) files.extend(runtime / name for name in ("libgcc_s_seh-1.dll", "libstdc++-6.dll", "libwinpthread-1.dll")) @@ -92,6 +101,8 @@ def package(platform, build, output, version): def collect_docs(source, output): + if not source.is_dir(): + raise RuntimeError(f"Documentation source is not a directory: {source}") documents = sorted(document for document in source.rglob("*.md") if document.is_file()) if not documents: raise RuntimeError(f"No Markdown documents found in {source}") @@ -129,7 +140,11 @@ def package_web(build, output, version, revision): asset.replace(destination) -def main(): +def main(argv=None): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser() parser.add_argument("--platform", choices=("linux", "windows", "web")) parser.add_argument("--build", type=Path) @@ -137,23 +152,27 @@ def main(): parser.add_argument("--revision") parser.add_argument("--docs", type=Path) parser.add_argument("--output", required=True, type=Path) - args = parser.parse_args() - if args.docs is not None: - if args.platform is not None or args.build is not None or args.version is not None or args.revision is not None: - parser.error("--docs cannot be combined with --platform, --build, --version or --revision") - collect_docs(args.docs, args.output) - else: - if args.platform is None or args.build is None or args.version is None: - parser.error("--platform, --build and --version are required when --docs is not specified") - if args.platform == "web": - if args.revision is None: - parser.error("--revision is required for --platform web") - package_web(args.build, args.output, args.version, args.revision) + args = parser.parse_args(argv) + try: + if args.docs is not None: + if args.platform is not None or args.build is not None or args.version is not None or args.revision is not None: + parser.error("--docs cannot be combined with --platform, --build, --version or --revision") + collect_docs(args.docs, args.output) else: - if args.revision is not None: - parser.error("--revision requires --platform web") - package(args.platform, args.build, args.output, args.version) + if args.platform is None or args.build is None or args.version is None: + parser.error("--platform, --build and --version are required when --docs is not specified") + if args.platform == "web": + if args.revision is None: + parser.error("--revision is required for --platform web") + package_web(args.build, args.output, args.version, args.revision) + else: + if args.revision is not None: + parser.error("--revision requires --platform web") + package(args.platform, args.build, args.output, args.version) + except (RuntimeError, ValueError, OSError) as error: + parser.error(str(error)) + return 0 if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/tools/pad_input.py b/tools/pad_input.py index ab93227072..69b1d64579 100644 --- a/tools/pad_input.py +++ b/tools/pad_input.py @@ -52,14 +52,28 @@ def next_sequence(path): return sequence +def button_mask(value): + names = value.split("+") + if any(name not in BUTTONS for name in names) or ("release" in names and len(names) > 1) or len(set(names)) != len(names): + raise argparse.ArgumentTypeError(f"invalid button {value!r}: use one of {', '.join(BUTTONS)}, or several joined by '+'") + mask = 0 + for name in names: + mask |= BUTTONS[name] + return mask + + def main(): parser = argparse.ArgumentParser(description="Queue a controller pulse for APS5_PAD_INPUT_FILE.") parser.add_argument("path", type=Path) - parser.add_argument("button", choices=BUTTONS) + parser.add_argument("button", help=f"a button name ({', '.join(BUTTONS)}), or several joined by '+' to press them together (for example l1+r1); release must be used alone") parser.add_argument("--hold-ms", type=int, default=300) parser.add_argument("--left-stick", type=int, nargs=2, metavar=("X", "Y")) parser.add_argument("--right-stick", type=int, nargs=2, metavar=("X", "Y")) args = parser.parse_args() + try: + mask = button_mask(args.button) + except argparse.ArgumentTypeError as error: + parser.error(str(error)) if not 1 <= args.hold_ms <= 5000: parser.error("--hold-ms must be between 1 and 5000") sticks = "" @@ -86,7 +100,7 @@ def main(): if os.geteuid() == 0: os.fchown(output.fileno(), metadata.st_uid, metadata.st_gid) os.fchmod(output.fileno(), stat.S_IMODE(metadata.st_mode)) - output.write(f"{sequence} {BUTTONS[args.button]} {args.hold_ms}{sticks}\n") + output.write(f"{sequence} {mask} {args.hold_ms}{sticks}\n") os.replace(temporary, args.path) finally: if temporary is not None: diff --git a/tools/performance_compare.py b/tools/performance_compare.py index 4642a983ff..6a6c599351 100644 --- a/tools/performance_compare.py +++ b/tools/performance_compare.py @@ -166,7 +166,7 @@ def markdown(report): def main(argv=None): for stream in (sys.stdout, sys.stderr): if hasattr(stream, "reconfigure"): - stream.reconfigure(encoding="utf-8") + stream.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="Compare two aps5-summary.jsonl captures. " "Reports numerical differences, not statistical significance or an automatic pass/fail verdict.") parser.add_argument("baseline", type=Path, help="Baseline summary JSONL file") diff --git a/tools/pr_labels.py b/tools/pr_labels.py index 4ca5c70a9b..f6f0039090 100644 --- a/tools/pr_labels.py +++ b/tools/pr_labels.py @@ -121,18 +121,25 @@ def process(repo, pr, dry_run, comment=True): return add, remove, post -def main(): +def main(argv=None): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="Label pull requests from their title and template fields.") which = parser.add_mutually_exclusive_group(required=True) which.add_argument("--pr", type=int, help="label one pull request") which.add_argument("--all", action="store_true", help="label every open pull request") parser.add_argument("--dry-run", action="store_true", help="print the planned changes and write nothing") - args = parser.parse_args() - repo = repository() - if args.pr is not None: - prs = [pull(json.loads(gh("api", f"repos/{repo}/pulls/{args.pr}")))] - else: - prs = [pull(data) for data in decode_pages(gh("api", "--paginate", f"repos/{repo}/pulls?state=open&base=main&per_page=100"))] + args = parser.parse_args(argv) + try: + repo = repository() + if args.pr is not None: + prs = [pull(json.loads(gh("api", f"repos/{repo}/pulls/{args.pr}")))] + else: + prs = [pull(data) for data in decode_pages(gh("api", "--paginate", f"repos/{repo}/pulls?state=open&base=main&per_page=100"))] + except subprocess.CalledProcessError as error: + parser.error((error.stderr or str(error)).strip()) failed = [] for pr in prs: try: @@ -148,7 +155,8 @@ def main(): time.sleep(WRITE_PAUSE) if failed: sys.exit(f"{len(failed)} pull requests not labeled: {', '.join(f'#{number}' for number in failed)}") + return 0 if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/tools/pr_overlap.py b/tools/pr_overlap.py index ca1bc134e5..992a1e4b2e 100644 --- a/tools/pr_overlap.py +++ b/tools/pr_overlap.py @@ -1,13 +1,18 @@ import argparse +import io import json import os import re import subprocess +import sys from concurrent.futures import ThreadPoolExecutor from itertools import combinations from pathlib import Path +import tarfile from urllib.parse import quote, urlencode +import nid_names + PARENT = 'if .parent then "\\(.parent.owner.login)/\\(.parent.name)" else .nameWithOwner end' EXPORT = re.compile(r"^\+.*\bAPS5_VABI\s+(\w+)\s*\(") NID_DISAMBIG = re.compile(r"_nid_disambig[0-9]+$") @@ -16,10 +21,33 @@ PLAIN_DEPENDS = re.compile(r'Depends on[ \t]+(#\d+\b(?:(?:[ \t]*,[ \t]*(?:and[ \ HUNK = re.compile(r"^@@ -(\d+)(?:,(\d+))?", re.M) MARKER = "" FETCH_REFS = 100 +_REPO_ROOTS = {} def git(*args): - result = subprocess.run(["git", "-c", "core.quotePath=false", *args], capture_output=True) + cwd = Path.cwd() + root = _REPO_ROOTS.get(cwd) + if root is None: + root = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], cwd=cwd, capture_output=True, check=True + ).stdout.decode("utf-8", errors="replace").strip() + _REPO_ROOTS[cwd] = root + args = list(args) + if "--" in args: + separator = args.index("--") + for index in range(separator + 1, len(args)): + pathspec = args[index] + if pathspec.startswith(("../", "..\\")): + absolute = (cwd / pathspec).resolve() + try: + args[index] = absolute.relative_to(Path(root)).as_posix() + except ValueError: + pass + result = subprocess.run( + ["git", "-c", "core.quotePath=false", *args], + cwd=root, + capture_output=True, + ) result.stdout = result.stdout.decode("utf-8", errors="replace") result.stderr = result.stderr.decode("utf-8", errors="replace") result.check_returncode() @@ -41,10 +69,15 @@ def upstream(): def depends_on(body): body = re.sub(r"", "", body or "", flags=re.S) + repo = re.escape(globals().get("REPO", "boykopovar/AnyPS5")) + repo_pattern = rf"(?:{repo}|boykopovar/AnyPS5)" + body = re.sub(rf"\[([^\]]*)\]\(https?://(?:www\.)?github\.com/{repo_pattern}/pull/(\d+)(?:[#/?][^)\s]*)?\)", r"#\2", body, flags=re.IGNORECASE) + body = re.sub(rf"\[([^\]]*)\]\(https?://(?:www\.)?github\.com/(?!{repo_pattern}/)[^/\s]+/[^/\s]+/pull/\d+[^)\s]*\)", "", body, flags=re.IGNORECASE) + body = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", body) + body = re.sub(rf"https?://(?:www\.)?github\.com/{repo_pattern}/pull/(\d+)\b(?:/[^\s]*)?", r"#\1", body, flags=re.IGNORECASE) match = next((match for match in DEPENDS.finditer(body) if match.group(1).strip()), None) or PLAIN_DEPENDS.search(body) return {int(n) for n in re.findall(r"#(\d+)", match.group(1))} if match else set() - def open_prs(base): query = urlencode({"state": "open", "base": base, "per_page": 100}) out = gh("api", "--paginate", f"repos/{REPO}/pulls?{query}") @@ -83,8 +116,15 @@ def fetch(base, prs): def merge(*args): + cwd = Path.cwd() + root = _REPO_ROOTS.get(cwd) + if root is None: + root = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], cwd=cwd, capture_output=True, check=True + ).stdout.decode("utf-8", errors="replace").strip() + _REPO_ROOTS[cwd] = root result = subprocess.run(["git", "-c", "core.quotePath=false", "merge-tree", "--write-tree", "--name-only", "--no-messages", "-z", *args], - capture_output=True) + cwd=root, capture_output=True) lines = result.stdout.decode("utf-8", errors="replace").rstrip("\0").split("\0") if result.returncode not in (0, 1) or not lines[0]: error = result.stderr.decode("utf-8", errors="replace").strip() @@ -97,16 +137,18 @@ def commit(tree): "-p", "refs/pr/base", "-m", "pr_overlap").strip() -def exports(diff): +def exports(diff, prefix="+"): found = set() lines = diff.splitlines() for i, line in enumerate(lines): - m = EXPORT.match(line) + if not line.startswith(prefix) or line.startswith(prefix * 3): + continue + m = EXPORT.match(prefix + line[1:]) if not m or m.group(1).endswith(("_nid_no_patch", "_nid_no_patch_cut")): continue text = line[1:] for nxt in lines[i + 1:]: - if ";" in text or "{" in text or not nxt.startswith("+") or nxt.startswith("+++"): + if ";" in text or "{" in text or not nxt.startswith(prefix) or nxt.startswith(prefix * 3): break text += nxt[1:] semi, brace = text.find(";"), text.find("{") @@ -116,6 +158,73 @@ def exports(diff): return found +def changed_source(diff, prefix): + files, current, path = [], [], None + for line in diff.splitlines(): + if line.startswith("diff --git "): + if current and path: + files.append((path, "\n".join(current))) + path = line.split(" b/", 1)[1] if " b/" in line else None + current = [] + elif line.startswith(prefix) and not line.startswith(prefix * 3): + current.append(line[1:]) + if current and path: + files.append((path, "\n".join(current))) + return files + + +def library_for_path(path): + parts = Path(path).parts + return "/".join(parts[:4]) if len(parts) > 3 and parts[:3] == ("core", "libs", "prx") else "" + + +def source_exports(text, library=""): + found = {} + for match in nid_names.source_definitions(text): + name = nid_names.strip_nid_postfix(match.group(1)) + if not name.endswith(("_nid_no_patch", "_nid_no_patch_cut")): + found.setdefault((library, nid_names.compute_nid(name)), set()).add(name) + for name in nid_names.source_assembly_definitions(text): + name = nid_names.strip_nid_postfix(name) + if not name.endswith(("_nid_no_patch", "_nid_no_patch_cut")): + found.setdefault((library, nid_names.compute_nid(name)), set()).add(name) + for nid, name in nid_names.source_aliases(text): + found.setdefault((library, nid), set()).add(name) + return found + + +def source_changed_exports(diff, prefix): + found = {} + for path, text in changed_source(diff, prefix): + library = library_for_path(path) + for key, names in source_exports(text, library).items(): + found.setdefault(key, set()).update(names) + file_diff = "\n".join(prefix + line for line in text.splitlines()) + for name in exports(file_diff, prefix): + canonical = nid_names.strip_nid_postfix(name) + found.setdefault((library, nid_names.compute_nid(canonical)), set()).add(canonical) + return found + + +def base_exports(ref): + root = git("rev-parse", "--show-toplevel").strip() + archive = subprocess.run( + ["git", "-C", root, "archive", "--format=tar", ref, "core/libs"], capture_output=True, check=True + ).stdout + found = {} + with tarfile.open(fileobj=io.BytesIO(archive), mode="r:") as source: + for entry in source: + path = Path(entry.name) + if not entry.isfile() or path.suffix != ".cpp" or any( + part.lower() in {"test", "tests"} for part in path.parts[2:-1]): + continue + text = source.extractfile(entry).read().decode("utf-8", errors="replace") + library = library_for_path(entry.name) + for key, names in source_exports(text, library).items(): + found.setdefault(key, set()).update(names) + return found + + def scan(pr): tree, conflicted = merge("refs/pr/base", pr["ref"]) pr["tree"] = None if conflicted else commit(tree) @@ -128,6 +237,8 @@ def scan(pr): pr["added"].add(path) diff = git("diff", "-U0", "--no-color", old, new, "--", "core/libs/prx/*.cpp") pr["exports"] = exports(diff) + pr["added_exports"] = source_changed_exports(diff, "+") + pr["removed_exports"] = source_changed_exports(diff, "-") def scan_all(prs, jobs): @@ -203,9 +314,10 @@ def pull(blob, number): return f'[PR {number}]({blob.split("/blob/", 1)[0].replace("https://github.com/", "https://www.github.com/", 1)}/pull/{number})' -def report(rows, blob): +def report(rows, blob, main_exports=None): if not rows: - return "" + if not main_exports: + return "" confirmed = {number: found for number, found in rows.items() if found.get("conflicts") or found.get("exports") or found["added"]} uncompared = {number: found["uncompared"] for number, found in rows.items() if found.get("uncompared")} @@ -220,6 +332,12 @@ def report(rows, blob): ]) for number, found in sorted(confirmed.items()): lines.append(f'| {pull(blob, number)} | {spans(found.get("conflicts"), blob)} | {cell(found.get("exports"))} | {cell(found["added"])} |') + if main_exports: + if confirmed: + lines.append("") + lines.append("Exports introduced here that are already present in `main`:") + lines.append("") + lines.append(f"- {cell(main_exports)}") if uncompared: if confirmed: lines.append("") @@ -235,10 +353,24 @@ def report(rows, blob): return "\n".join(lines) + "\n" -def write_reports(out, rows, blob): +def write_reports(out, rows, blob, main_exports=None): + main_exports = main_exports or {} out.mkdir(parents=True, exist_ok=True) for number, found in rows.items(): - (out / f"{number}.md").write_text(report(found, blob), encoding="utf-8") + (out / f"{number}.md").write_text(report(found, blob, main_exports.get(number)), encoding="utf-8") + + +def main_export_findings(prs, current_exports): + findings = {} + for number, pr in prs.items(): + candidates = set(pr.get("added_exports", {})) - set(pr.get("removed_exports", {})) + names = set() + for key in candidates & set(current_exports): + names.update(pr["added_exports"][key]) + names.update(current_exports[key]) + if names: + findings[number] = names + return findings def describe_failure(error): @@ -247,18 +379,23 @@ def describe_failure(error): return f"pr_overlap: {command} failed with exit code {error.returncode}: {message}" -if __name__ == "__main__": +def main(argv=None): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="report open pull requests that overlap each other") parser.add_argument("--base", default="main") parser.add_argument("--out", type=Path, help="write .md for every open pull request (empty when clean)") parser.add_argument("--branch", help="check a local branch against the open pull requests instead") parser.add_argument("--repo", help="owner/name; defaults to the upstream of the current checkout") parser.add_argument("--jobs", type=int, default=4, help="number of pull request comparisons to run at once") - args = parser.parse_args() + args = parser.parse_args(argv) if bool(args.out) == bool(args.branch): parser.error("pass exactly one of --out or --branch") if args.jobs < 1: parser.error("--jobs must be at least 1") + global REPO try: REPO = args.repo or upstream() prs = open_prs(args.base) @@ -268,7 +405,9 @@ if __name__ == "__main__": prs = own(prs, sha) prs[0] = {"ref": sha, "author": None, "depends": set()} scan_all(prs, args.jobs) + current_exports = base_exports("refs/pr/base") rows = {n: {} for n in prs} + main_exports = main_export_findings(prs, current_exports) pairs = [(0, n) for n in prs if n] if args.branch else combinations(prs, 2) for a, b in pairs: if found := overlaps(prs, a, b): @@ -277,6 +416,11 @@ if __name__ == "__main__": except subprocess.CalledProcessError as error: raise SystemExit(describe_failure(error)) if args.branch: - print(report(rows[0], blob) or "No overlap with open pull requests.", end="") - raise SystemExit - write_reports(args.out, rows, blob) + print(report(rows[0], blob, main_exports.get(0)) or "No overlap with open pull requests.", end="") + return 0 + write_reports(args.out, rows, blob, main_exports) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/prepare_release_notes.py b/tools/prepare_release_notes.py index 0163999059..0b8c6c72db 100644 --- a/tools/prepare_release_notes.py +++ b/tools/prepare_release_notes.py @@ -3,6 +3,7 @@ import json import os import re import stat +import sys from pathlib import Path BODY_LIMIT = 125000 @@ -26,7 +27,7 @@ def prepare_notes(source, body, full): for second in paths[index + 1:]: if first.resolve() == second.resolve() or (first.exists() and second.exists() and first.samefile(second)): raise ValueError("Release note input and outputs must be distinct files") - notes = json.loads(source.read_text(encoding="utf-8"))["body"] + notes = json.loads(source.read_text(encoding="utf-8-sig"))["body"] if not isinstance(notes, str): raise ValueError("Generated release notes body must be a string") for destination in (body, full): @@ -40,13 +41,21 @@ def prepare_notes(source, body, full): body.write_bytes(release_body(notes).encode("utf-8")) -def main(): +def main(argv=None): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="Prepare size-safe GitHub release notes") parser.add_argument("--input", type=Path, required=True) parser.add_argument("--body", type=Path, required=True) parser.add_argument("--full", type=Path, required=True) - args = parser.parse_args() - prepare_notes(args.input, args.body, args.full) + args = parser.parse_args(argv) + try: + prepare_notes(args.input, args.body, args.full) + except (OSError, ValueError) as error: + parser.error(str(error)) + return 0 if __name__ == "__main__": diff --git a/tools/progress.py b/tools/progress.py index 944ba4170b..aaef552f7b 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -441,8 +441,46 @@ def compare(title, column, unit, base, head): return lines + [""] +def library_progress(group): + if group is None: + return "Not present", None + if "shared_sources" in group: + return f'Shared sources: `{group["shared_sources"]}`', None + if "done" not in group or "todo" not in group: + return "Counts unavailable", None + total = group["done"] + group["todo"] + if not total: + return "No exports", None + percent = 100 * group["done"] / total + return f'{group["done"]}/{total} ({percent:.2f}%)', percent + + +def library_report(base, head): + before, after = ({group["name"]: group for group in data["groups"]} for data in (base, head)) + fields = ("done", "todo", "done_names", "todo_names", "shared_sources") + rows = [] + for name in sorted(before.keys() | after.keys(), key=str.casefold): + old, new = before.get(name), after.get(name) + if old is not None and new is not None and all(old.get(field) == new.get(field) for field in fields): + continue + old_text, old_percent = library_progress(old) + new_text, new_percent = library_progress(new) + delta = "n/a" + if old_percent is not None and new_percent is not None: + delta = f'{round(new_percent - old_percent, 2) or 0:+.2f} pp' + rows.append(f"| {name} | {old_text} | {new_text} | {delta} |") + if not rows: + return [] + lines = ["#### Changed libraries (implemented exports)", "", + "| Library | Before | After | Change |", "| - | - | - | - |"] + rows[:REPORT_ROWS] + if len(rows) > REPORT_ROWS: + lines.append(f"| ... | {len(rows) - REPORT_ROWS} more libraries | | |") + return lines + [""] + + def report(base, head): lines = compare("System libraries", "Library", "functions", base["libraries"], head["libraries"]) + lines += library_report(base["libraries"], head["libraries"]) lines += compare("GPU shader instructions", "Encoding", "instructions", base["shaders"], head["shaders"]) return "\n".join(lines) @@ -482,6 +520,10 @@ def validate_snapshot(snapshot): def main(argv=None): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace") global PRX, OPCODES previous = PRX, OPCODES parser = argparse.ArgumentParser() @@ -501,7 +543,7 @@ def main(argv=None): snapshots = [] for path in args.compare: try: - snapshots.append(json.loads(path.read_text(encoding="utf-8"))) + snapshots.append(json.loads(path.read_text(encoding="utf-8-sig"))) except (OSError, ValueError) as error: parser.error(f"{path}: {error}") for path, snapshot in zip(args.compare, snapshots): @@ -526,7 +568,10 @@ def main(argv=None): except ValueError as error: parser.error(str(error)) if args.output: - write_site(args.output, libraries, shaders) + try: + write_site(args.output, libraries, shaders) + except OSError as error: + parser.error(f"{args.output}: {error.strerror or error}") if args.json: print(json.dumps({"libraries": libraries, "shaders": shaders}, indent=2)) else: @@ -538,6 +583,4 @@ def main(argv=None): if __name__ == "__main__": - if hasattr(sys.stdout, "reconfigure"): - sys.stdout.reconfigure(encoding="utf-8") raise SystemExit(main()) diff --git a/tools/progress_history.py b/tools/progress_history.py index ca8460652a..e98eaa5df8 100644 --- a/tools/progress_history.py +++ b/tools/progress_history.py @@ -25,8 +25,13 @@ def git(*args): def daily_commits(ref): + try: + log = git("log", "--first-parent", "--format=%H %ct", ref) + except subprocess.CalledProcessError as error: + reason = error.stderr.decode("utf-8", errors="replace").strip() + raise ValueError(f"cannot read the history of {ref}: {reason}") from error seen = {} - for line in git("log", "--first-parent", "--format=%H %ct", ref).decode().splitlines(): + for line in log.decode("utf-8").splitlines(): sha, timestamp = line.split() seen.setdefault(datetime.fromtimestamp(int(timestamp), timezone.utc).date().isoformat(), sha) return sorted(seen.items()) @@ -221,12 +226,12 @@ def page(points): ]) + "\n" -if __name__ == "__main__": +def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument("output", type=Path) parser.add_argument("--ref", default="HEAD", help="branch or commit whose first-parent history is measured") parser.add_argument("--cache", type=Path, help="reuse per-commit counts while the scanner, ISA list and Python version match") - args = parser.parse_args() + args = parser.parse_args(argv) try: if args.cache: outputs = [args.output / name for name in ("history.json", "history.svg", "history.html")] @@ -239,7 +244,11 @@ if __name__ == "__main__": if not points: parser.error("no commit with a measurable tree") args.output.mkdir(parents=True, exist_ok=True) - (args.output / "history.json").write_text(json.dumps(points, indent=2)) - (args.output / "history.svg").write_text(chart(points) + "\n") - (args.output / "history.html").write_text(page(points)) + (args.output / "history.json").write_text(json.dumps(points, indent=2), encoding="utf-8") + (args.output / "history.svg").write_text(chart(points) + "\n", encoding="utf-8") + (args.output / "history.html").write_text(page(points), encoding="utf-8") print(f"{len(points)} days, {points[0]['date']} .. {points[-1]['date']}") + + +if __name__ == "__main__": + main() diff --git a/tools/run_job_manager_reference.py b/tools/run_job_manager_reference.py new file mode 100644 index 0000000000..705d0e1228 --- /dev/null +++ b/tools/run_job_manager_reference.py @@ -0,0 +1,155 @@ +import argparse +import hashlib +import json +import os +import shutil +import struct +import subprocess +import sys +import tempfile +from pathlib import Path + +from import_audit import read_host_imports +from unwrap_self import Refused, unwrap + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "core/relinker/relinker/tests")) +from guest_import_fixtures import executable + +CASES = ("memory", "owned_state", "concurrent_wait", "validation", "dispatch", "ordering", "nested", "limits", "node_pool", "priority", "shutdown", "worker_policy", "worker_wake", "worker_wait", "worker_count", "worker_spin") +PARITY_CASES = tuple(case for case in CASES if case not in ("node_pool", "worker_wait")) + + +def stage_libraries(directory, build, toolchain, dependencies): + providers = {path.name.lower(): path for path in (build / "core/libs/libs").glob("*.prx")} + if toolchain is not None: + providers.update({path.name.lower(): path for path in toolchain.glob("*.dll")}) + pending = list(dependencies) + staged = set() + while pending: + name = pending.pop().lower() + if name in staged: + continue + staged.add(name) + source = providers.get(name) + if source is None: + if name.endswith(".prx"): + raise RuntimeError(f"Missing patched runtime {name}; build core/libs/libs/{name}") + continue + shutil.copy2(source, directory / source.name) + pending.extend(read_host_imports(source)) + + +def run(args, directory): + libraries = directory / "libs" + libraries.mkdir() + helper = args.build / "tests/job_manager_reference.prx" + if not helper.is_file(): + raise RuntimeError("Build the job_manager_reference target first") + shutil.copy2(helper, libraries / "jobref.prx") + suffix = ".exe" if sys.platform == "win32" else "" + subprocess.run([str(args.build / f"core/libs/nid_patcher{suffix}"), "jobref", "--preserve-export-list", + str(args.build / "core/libs/libc.exports"), str(libraries / "jobref.prx")], check=True, capture_output=True) + module_hash = None + if args.module: + module_data = args.module.read_bytes() + module_hash = hashlib.sha256(module_data).hexdigest() + modules = directory / "sce_module" + modules.mkdir() + target = modules / "libSceJobManager.prx" + if module_data[:4] == b"\x7fELF": + shutil.copy2(args.module, target) + else: + unwrap(args.module, target) + source = directory / "input.elf" + entry = executable("jobref.prx", symbol="Run_nid_no_patch") + entry[0x4000:0x400f] = b"\x48\x83\xec\x08\xff\x15" + struct.pack(" 30: + parser.error("--timeout must be greater than zero and at most 30 seconds") + try: + for name in ("build", "relinker", "toolchain", "module"): + value = getattr(args, name) + if value is not None: + setattr(args, name, value.resolve(strict=True)) + directory = args.output.resolve() if args.output else Path(tempfile.mkdtemp(prefix="anyps5-jobmanager-")) + if args.output: + directory.mkdir(parents=True, exist_ok=False) + print(f"Artifacts: {directory}", flush=True) + return run(args, directory) + except (OSError, Refused, RuntimeError, subprocess.SubprocessError) as error: + print(str(error), file=sys.stderr) + if isinstance(error, subprocess.CalledProcessError) and error.stderr: + print(error.stderr.decode(errors="replace"), file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/select_tests.py b/tools/select_tests.py index efb972d0a1..8d3563274a 100644 --- a/tools/select_tests.py +++ b/tools/select_tests.py @@ -1,5 +1,6 @@ import argparse import collections +import importlib.util import json import os import re @@ -7,6 +8,10 @@ import subprocess import sys from pathlib import Path +REGISTRATION_SPEC = importlib.util.spec_from_file_location("check_test_registration", Path(__file__).with_name("check_test_registration.py")) +REGISTRATION = importlib.util.module_from_spec(REGISTRATION_SPEC) +REGISTRATION_SPEC.loader.exec_module(REGISTRATION) + FULL = re.compile(r"^CMakeLists\.txt$|^core/CMakeLists\.txt$|^core/libs/CMakeLists\.txt$|^\.github/workflows/build\.yml$" r"|^3rdparty/|^cmake/|^\.gitmodules$|^tools/select_tests\.py$") LOCAL = re.compile(r"(^|/)CMakeLists\.txt$|\.cmake$") @@ -16,7 +21,7 @@ BATCH = 20000 class Build: def __init__(self, path): cache = {} - for line in (Path(path) / "CMakeCache.txt").read_text(encoding="utf-8").splitlines(): + for line in (Path(path) / "CMakeCache.txt").read_text(encoding="utf-8-sig").splitlines(): name, _, value = line.partition("=") cache[name.partition(":")[0]] = value self.path = os.path.normpath(cache["CMAKE_CACHEFILE_DIR"]) @@ -84,11 +89,43 @@ class Build: leaves.add(name) return sorted(leaves) + def outputs(self, paths): + built = {line.rpartition(": ")[0] for line in self.run("-t", "targets", "all").splitlines()} + return [path for path in paths if path in built] + def tests(self, *arguments): listing = json.loads(subprocess.run(["ctest", "--test-dir", self.path, "--show-only=json-v1", *arguments], capture_output=True, text=True, check=True).stdout) + missing = {test["name"] for test in listing["tests"] if not test.get("command")} + if missing: + commands = self.test_commands(missing) + for test in listing["tests"]: + candidates = commands.get(test["name"], set()) + if test["name"] in missing and len(candidates) == 1: + command = next(iter(candidates)) + if command and os.path.isabs(command[0]): + test["command"] = list(command) return listing, listing["tests"] + def test_commands(self, names): + found, visited, pending = collections.defaultdict(set), set(), [Path(self.path)] + while pending: + directory = pending.pop().resolve() + file = directory / "CTestTestfile.cmake" + if directory in visited or not file.is_file(): + continue + visited.add(directory) + try: + commands = list(REGISTRATION.commands(file.read_text(encoding="utf-8-sig"))) + except (OSError, UnicodeError, ValueError): + continue + for command, arguments in commands: + if command == "subdirs": + pending.extend(directory / argument for argument in arguments) + elif command == "add_test" and arguments and arguments[0] in names: + found[arguments[0]].add(tuple(arguments[1:])) + return found + def output(self, path): absolute = self.absolute(path) if not inside(absolute, self.path): @@ -110,14 +147,24 @@ def relative(path, root): return path[root_len + 1:].replace(os.sep, "/") +def paths(arguments): + for argument in arguments: + value = argument.partition("=")[2] if argument.startswith("-D") else argument + if os.path.isabs(value): + yield value + + def chunks(names): batch, size = [], 0 for name in names: - if batch and size + len(name) + 1 > BATCH: + length = len(name) + 1 + if sys.platform == "win32": + length = len(subprocess.list2cmdline([name]).encode("utf-16-le", "surrogatepass")) // 2 + 1 + if batch and size + length > BATCH: yield batch batch, size = [], 0 batch.append(name) - size += len(name) + 1 + size += length if batch: yield batch @@ -125,11 +172,12 @@ def chunks(names): def dump(build, out): deps = collections.defaultdict(set) current = None - for line in build.run("-t", "deps").splitlines(): - if not line.startswith(" ") and ": #deps" in line: - current = build.key(line.split(": #deps")[0]) + recorded = subprocess.run([build.program, "-C", build.path, "-t", "deps"], capture_output=True, check=True).stdout + for line in recorded.splitlines(): + if not line.startswith(b" ") and b": #deps" in line: + current = build.key(line.split(b": #deps")[0].decode(build.encoding)) elif line.strip() and current: - path = build.repo(line.strip()) + path = build.repo(line.strip().decode("utf-8")) if path: deps[current].add(path) listing, listed = build.tests() @@ -157,7 +205,7 @@ def dump(build, out): registered.add(build.repo(graph["files"][graph["nodes"][node]["file"]])) node = graph["nodes"][node].get("parent") props = {p["name"]: p["value"] for p in test.get("properties", [])} - arguments = sorted({build.repo(a) for a in (test.get("command") or [])[1:] if os.path.isabs(a)} - {None}) + arguments = sorted({build.repo(a) for a in paths((test.get("command") or [])[1:])} - {None}) tests[test["name"]] = {"registered": sorted(registered - {None}), "arguments": arguments, "requires": props.get("FIXTURES_REQUIRED", []), "sets": props.get("FIXTURES_SETUP", []), "depends": props.get("DEPENDS", []), "target": build.target(test)} @@ -216,16 +264,22 @@ def plan(build, graph, picked): _, selected = build.tests("-I", "0,0,0," + ",".join(map(str, run))) names = {t["name"] for t in selected} run = [i for i, t in enumerate(listed, 1) if t["name"] in names] - keep = {build.output(a) for t in listed if t["name"] in names for a in t.get("command") or [] if os.path.isabs(a)} + if any(not t.get("command") for t in listed if t["name"] in names): + return run, build.leaves(), len(listed) + keep = {build.output(a) for t in listed if t["name"] in names for a in paths(t.get("command") or [])} skip = {build.target(t) for t in listed if t["name"] not in names} - keep - {None} - return run, [t for t in build.leaves() if t not in skip], len(listed) + targets = [t for t in build.leaves() if t not in skip] + return run, targets + build.outputs(sorted(keep - set(targets) - {None})), len(listed) def escape(name): return name.replace("$", "$$").replace(":", "$:").replace(" ", "$ ") -def main(): +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="Select the tests whose build inputs a change touches") sub = parser.add_subparsers(dest="command", required=True) d = sub.add_parser("dump", help="write the test graph of a built tree") @@ -241,15 +295,15 @@ def main(): c = sub.add_parser("ctest", help="run ctest on a tree, limited to the selected tests if select wrote any") c.add_argument("build") c.add_argument("arguments", nargs=argparse.REMAINDER, help="ctest arguments") - args = parser.parse_args() + args = parser.parse_args(argv) if args.command == "ctest": selected = Path(args.build, "select.tests") - extra = ["-I", selected.read_text(encoding="utf-8").strip()] if selected.exists() else [] + extra = ["-I", selected.read_text(encoding="utf-8-sig").strip()] if selected.exists() else [] return subprocess.run(["ctest", "--test-dir", args.build, *args.arguments, *extra]).returncode if args.command == "explain": try: - graph = json.loads(args.graph.read_text(encoding="utf-8")) - changed = [line.strip() for line in args.changed.read_text(encoding="utf-8").splitlines() if line.strip()] + graph = json.loads(args.graph.read_text(encoding="utf-8-sig")) + changed = [line.strip() for line in args.changed.read_text(encoding="utf-8-sig").splitlines() if line.strip()] reasons = {} picked, full = select(graph, changed, reasons) except (OSError, ValueError) as error: @@ -270,7 +324,7 @@ def main(): if not os.path.exists(args.graph): print("full run: no test graph") return 0 - graph = json.loads(Path(args.graph).read_text(encoding="utf-8")) + graph = json.loads(Path(args.graph).read_text(encoding="utf-8-sig")) changed = [line.strip() for line in Path(args.changed).read_text(encoding="utf-8-sig").splitlines() if line.strip()] picked, full = select(graph, changed) if picked is None: diff --git a/tools/stubgen.py b/tools/stubgen.py index a701cb5472..436233cb23 100644 --- a/tools/stubgen.py +++ b/tools/stubgen.py @@ -187,7 +187,7 @@ def write_outputs(outputs): path = Path(filename).resolve() if path.is_dir(): raise IsADirectoryError(str(path)) - with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", newline="\n", dir=path.parent, prefix=path.name + ".", suffix=".tmp", delete=False) as handle: + with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", newline="\n", dir=path.parent, prefix=".stubgen-", suffix=".tmp", delete=False) as handle: temporary = Path(handle.name) staged.append((temporary, path)) handle.write(content) @@ -211,6 +211,9 @@ def write_outputs(outputs): def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace", newline="\n") ap = argparse.ArgumentParser(description="Generate AnyPS5 Export.cpp skeletons and TechnicalDebt lines") ap.add_argument("--module", required=True, help="target module name, e.g. libSceAmpr, used for UnknownNN prefix and default TechnicalDebt path core/libs/prx/MODULE/Export.cpp") ap.add_argument("--nids", required=True, help="path to missing-NIDs file, one 11-char NID per line") @@ -221,9 +224,6 @@ def main(argv=None): ap.add_argument("--td-out", required=False, default=None, help="output file for TechnicalDebt lines, which go in docs/dev/debt/MODULE.md, default stderr when code uses stdout, otherwise stdout") ap.add_argument("--td-only", action="store_true", help="print only TechnicalDebt lines, no code") args = ap.parse_args(argv) - for stream in (sys.stdout, sys.stderr): - if hasattr(stream, "reconfigure"): - stream.reconfigure(encoding="utf-8", newline="\n") try: if not args.td_only and args.out and args.td_out and same_file(args.out, args.td_out): ap.error("--out and --td-out must name different files") diff --git a/tools/suggest_patches.py b/tools/suggest_patches.py new file mode 100644 index 0000000000..f927d31dc6 --- /dev/null +++ b/tools/suggest_patches.py @@ -0,0 +1,209 @@ +import argparse +import hashlib +import json +import os +import re +import shlex +import subprocess +import sys +import tomllib +from pathlib import Path, PurePosixPath + + +SHA256 = re.compile(r"[0-9a-fA-F]{64}\Z") +VERIFY_SUMMARY = re.compile(r"^Verified (\d+) patches: (\d+) ok, (\d+) not-checked, (\d+) mismatch$", re.MULTILINE) + + +def confined(root, name): + if not isinstance(name, str) or not name or "\\" in name: + raise ValueError(f"invalid relative path: {name!r}") + path = PurePosixPath(name) + if path.is_absolute() or any(part in (".", "..") for part in name.split("/")): + raise ValueError(f"invalid relative path: {name!r}") + resolved = (root / Path(*path.parts)).resolve() + if not resolved.is_relative_to(root.resolve()) or not resolved.is_file(): + raise ValueError(f"file missing or outside directory: {name}") + return resolved + + +def sha256(path): + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def command_line(arguments): + return subprocess.list2cmdline(arguments) if os.name == "nt" else shlex.join(arguments) + + +def conversion_options(args, patch): + options = ["--patches", str(patch)] + if args.skip_sce_module: + options.append("--skip-sce-module") + if args.sce_module_path: + options.extend(("--sce-module-path", str(args.sce_module_path))) + for module_dir in args.module_dir: + options.extend(("--module-dir", module_dir)) + for executable in args.executable: + options.extend(("--executable", executable)) + return command_line(options) + + +def load_catalog(directory, title, version): + data = json.loads(confined(directory, "index.json").read_text(encoding="utf-8")) + if not isinstance(data, dict) or set(data) != {"format", "entries"} or type(data["format"]) is not int or data["format"] != 1 or not isinstance(data["entries"], list): + raise ValueError("catalog must have format 1 and an entries array") + entries = [] + for number, entry in enumerate(data["entries"], 1): + if not isinstance(entry, dict) or (entry.get("titleId"), entry.get("contentVersion")) != (title, version): + continue + entries.append((number, entry)) + return entries + + +def validate_entry(directory, entry): + if set(entry) != {"titleId", "contentVersion", "patch", "targets"}: + raise ValueError("invalid fields") + patch = confined(directory, entry["patch"]) + targets = entry["targets"] + if not isinstance(targets, dict) or not targets: + raise ValueError("targets must be a nonempty object") + if any(not isinstance(name, str) or not isinstance(digest, str) or not SHA256.fullmatch(digest) for name, digest in targets.items()): + raise ValueError("invalid target digest") + parsed = tomllib.loads(patch.read_text(encoding="utf-8")) + patches = parsed.get("patch") + if not isinstance(patches, list) or not patches or any(not isinstance(item, dict) or not isinstance(item.get("target"), str) for item in patches): + raise ValueError("patch file has no valid targets") + if set(targets) != {item["target"] for item in patches}: + raise ValueError("target hashes must cover exactly the patch targets") + return patch, {name: digest.lower() for name, digest in targets.items()} + + +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") + parser = argparse.ArgumentParser(description="Recommend a local patch only after metadata, hashes and relinker verification match") + parser.add_argument("--catalog", required=True, type=Path, help="directory containing index.json and patch files") + parser.add_argument("--param-json", required=True, type=Path, help="title metadata file") + parser.add_argument("--input", required=True, type=Path, help="unwrapped main ELF") + parser.add_argument("--relinker", required=True, type=Path, help="relinker executable to run") + parser.add_argument("--sce-module-path", type=Path, help="parent directory of bundled modules") + parser.add_argument("--module-dir", action="append", default=[], help="module directory relative to the module parent; repeatable") + parser.add_argument("--executable", action="append", default=[], help="additional title executable relative to the module parent; repeatable") + parser.add_argument("--skip-sce-module", action="store_true", help="skip bundled modules during verification") + parser.add_argument("--timeout-seconds", type=int, default=120, help="maximum seconds per relinker verification (default: 120)") + parser.add_argument("--list-verified", action="store_true", help="list every verified catalog entry without choosing one") + args = parser.parse_args(argv) + prefix = "No verified catalog entries" if args.list_verified else "No recommendation" + try: + metadata = json.loads(args.param_json.read_text(encoding="utf-8")) + if not isinstance(metadata, dict): + raise ValueError("param.json must be an object") + title = metadata.get("titleId") + version = metadata.get("contentVersion") + if not isinstance(title, str) or not title or not isinstance(version, str) or not version: + raise ValueError("param.json needs nonempty titleId and contentVersion") + if not args.input.is_file() or not args.relinker.is_file(): + raise ValueError("input and relinker must be files") + if args.skip_sce_module and args.sce_module_path: + raise ValueError("--skip-sce-module conflicts with --sce-module-path") + if args.timeout_seconds <= 0: + raise ValueError("timeout-seconds must be positive") + root = (args.sce_module_path or args.input.parent).resolve() + if not root.is_dir(): + raise ValueError("module parent must be a directory") + for executable in args.executable: + confined(root, executable) + matches = [] + entries = load_catalog(args.catalog.resolve(), title, version) + if not entries: + print(f"{prefix}: no catalog entry for {title} {version}.") + return 1 + relinker_errors = False + for number, entry in entries: + label = f"Catalog entry {number} ({entry.get('patch')!r})" + try: + patch, targets = validate_entry(args.catalog.resolve(), entry) + except (OSError, ValueError, tomllib.TOMLDecodeError) as error: + print(f"{label}: rejected: {error}") + continue + errors = [] + for name, expected in targets.items(): + try: + source = args.input.resolve() if name == args.input.name else confined(root, name) + actual = sha256(source) + if actual != expected: + errors.append(f"{name}: SHA-256 mismatch ({actual})") + except (OSError, ValueError) as error: + errors.append(f"{name}: {error}") + if errors: + print(f"{label}: rejected: {'; '.join(errors)}") + continue + command = [str(args.relinker.resolve()), "--verify-patches", str(patch)] + if args.skip_sce_module: + command.append("--skip-sce-module") + if args.sce_module_path: + command.extend(("--sce-module-path", str(args.sce_module_path))) + for module_dir in args.module_dir: + command.extend(("--module-dir", module_dir)) + for executable in args.executable: + command.extend(("--executable", executable)) + command.append(str(args.input)) + try: + result = subprocess.run(command, capture_output=True, text=True, encoding="utf-8", errors="replace", + timeout=args.timeout_seconds) + except subprocess.TimeoutExpired: + print(f"{label}: rejected: relinker verification exceeded {args.timeout_seconds} seconds") + continue + summary = VERIFY_SUMMARY.search(result.stdout) + verified = tuple(map(int, summary.groups())) if summary else None + if result.returncode: + detail = (result.stdout + result.stderr).strip() + if result.returncode == 1 and verified and verified[3] > 0: + print(f"{label}: rejected by relinker: {detail}") + continue + if result.returncode == 2: + print(f"{label}: relinker error (exit 2): {detail}") + relinker_errors = True + continue + raise ValueError(f"{label}: relinker error (exit {result.returncode}): {detail}") + if not verified: + raise ValueError(f"{label}: relinker produced no verification summary") + if verified[0] == 0 or verified[1] != verified[0] or verified[2] or verified[3]: + print(f"{label}: rejected: relinker did not confirm every patch") + continue + matches.append((number, entry, patch, targets)) + if args.list_verified and matches: + print(f"Verified catalog entries: {len(matches)}") + print("Choose one patch file; listed entries are not combined.") + for number, entry, patch, targets in matches: + print(f"Catalog entry {number}: {patch}") + for name, digest in targets.items(): + print(f" {name} SHA-256 {digest}") + print(f"Use these options when converting:\n {conversion_options(args, patch)}") + return 0 + if len(matches) == 1: + number, entry, patch, targets = matches[0] + print(f"Recommended: {patch} (catalog entry {number})") + for name, digest in targets.items(): + print(f" {name} SHA-256 {digest}") + print(f"Use these options when converting:\n {conversion_options(args, patch)}") + return 0 + if len(matches) > 1: + print("No recommendation: multiple verified catalog entries match; use --list-verified to see them.") + return 1 + if relinker_errors: + print(f"{prefix}: relinker errors occurred; see catalog entries above.", file=sys.stderr) + return 2 + print(f"{prefix}: no catalog entry passed all checks.") + return 1 + except (OSError, ValueError, json.JSONDecodeError, tomllib.TOMLDecodeError) as error: + print(f"Patch advisor: {error}", file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/symbolize_samples.py b/tools/symbolize_samples.py new file mode 100644 index 0000000000..f61b586e53 --- /dev/null +++ b/tools/symbolize_samples.py @@ -0,0 +1,300 @@ +import argparse +import bisect +import collections +import shutil +import struct +import subprocess +import sys +from pathlib import Path + +DESCRIPTION = """Symbolize the output of the Windows samplers (APS5_SAMPLE_WORKER, APS5_SAMPLE_COMMITTER and +APS5_SAMPLE_THREADS) against the modules that ran. Each sampled frame is written as module+0xRVA; this reads the +COFF symbol table of each module found in --libs (the MinGW-built .prx and .dll files keep one), or its export +table when it has none, and names every frame module!function. + +The '# module name size timestamp' header lines the samplers write are compared with the SizeOfImage and +TimeDateStamp of the file that is found; a module whose file does not match is left unsymbolized, since its +addresses would name the wrong functions. + +The sampler rewrites its files every 20 s with totals since start-up. --since takes an earlier copy of the same +file and subtracts it, which keeps only the samples taken between the two copies (a steady-state window). + +A module with no such record is also left unsymbolized, since nothing shows which build it was; --unverified +symbolizes it anyway against the file found. + +Input: a collapsed-stack file (.collapsed.txt, 'frame;...;frame count' lines) or an address file +('module offset self inclusive' lines, under 'thread samples ' headings for APS5_SAMPLE_THREADS; --thread +keeps one thread). Output: the hottest functions by self and by inclusive samples, and with --out a symbolized +collapsed-stack file for speedscope.app or flamegraph.pl. For an address file, a function's inclusive count is the +sum of its addresses' counts, which overcounts the samples in which it is on the stack more than once.""" + +IMAGE_SCN_CNT_CODE = 0x20 +IMAGE_SYM_CLASS_EXTERNAL = 2 +IMAGE_SYM_CLASS_STATIC = 3 + + +class PeModule: + def __init__(self, path): + data = Path(path).read_bytes() + if data[:2] != b"MZ": + raise ValueError(f"{path}: not a PE file") + pe = struct.unpack_from("> 4) != 2 and value == 0: + continue + if name[:4] == b"\0\0\0\0": + at = strings + struct.unpack_from(" samples '") + samples.stacks[frames] += int(count) + return samples + + +def subtract(later, earlier): + result = collections.Counter(later) + result.subtract(earlier) + return collections.Counter({key: count for key, count in result.items() if count > 0}) + + +def window(later, earlier): + result = Samples() + result.headers = {**earlier.headers, **later.headers} + for name in ("stacks", "self_counts", "inclusive", "total"): + setattr(result, name, subtract(getattr(later, name), getattr(earlier, name))) + return result + + +def summarize(symbolizer, samples, top): + self_counts = collections.Counter() + inclusive = collections.Counter() + total = 0 + if samples.stacks: + for frames, count in samples.stacks.items(): + names = [symbolizer.frame(frame) for frame in frames.split(";")] + total += count + self_counts[names[-1]] += count + for name in set(names): + inclusive[name] += count + else: + for (module, offset), count in samples.self_counts.items(): + self_counts[symbolizer.frame(f"{module}+{offset}")] += count + for (module, offset), count in samples.inclusive.items(): + inclusive[symbolizer.frame(f"{module}+{offset}")] += count + total = samples.total["threads"] or samples.total["samples"] or sum(samples.self_counts.values()) + lines = [f"{total} samples"] + for title, counter in (("self", self_counts), ("inclusive", inclusive)): + if not counter: + continue + lines.append(f"hottest by {title}:") + for name, count in counter.most_common(top): + lines.append(f" {count:8d} {100.0 * count / total if total else 0.0:5.1f}% {name}") + return lines + + +def main(argv=None): + parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("samples", help="collapsed-stack file or address file written by a Windows sampler") + parser.add_argument("--libs", action="append", required=True, help="directory holding the modules that ran (repeatable)") + parser.add_argument("--since", help="an earlier copy of the same file, subtracted to keep the samples taken after it") + parser.add_argument("--out", help="write the symbolized collapsed stacks to this file") + parser.add_argument("--top", type=int, default=30, help="functions listed per table (default 30)") + parser.add_argument("--demangle", action="store_true", help="demangle C++ names with c++filt when it is on PATH") + parser.add_argument("--thread", help="keep only this thread of an APS5_SAMPLE_THREADS file") + parser.add_argument("--unverified", action="store_true", help="also symbolize modules the samples have no '# module' record for") + args = parser.parse_args(argv) + samples = load(args.samples, args.thread) + if args.since: + samples = window(samples, load(args.since, args.thread)) + symbolizer = Symbolizer(args.libs, samples.headers, args.demangle, warn=lambda text: print("warning: " + text, file=sys.stderr), unverified=args.unverified) + for line in summarize(symbolizer, samples, args.top): + print(line) + if args.out: + if not samples.stacks: + parser.error("--out needs a collapsed-stack file") + with open(args.out, "w", encoding="utf-8") as output: + for frames, count in samples.stacks.items(): + output.write(";".join(symbolizer.frame(frame) for frame in frames.split(";")) + f" {count}\n") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/test_web.py b/tools/test_web.py index 245e6b4eac..03fe29a0c7 100644 --- a/tools/test_web.py +++ b/tools/test_web.py @@ -5,6 +5,7 @@ import re import subprocess import sys import tempfile +import urllib.error import urllib.request ROOT = Path(__file__).resolve().parent.parent @@ -13,25 +14,41 @@ from test_guest_intel_trampolines import main_fixture from test_guest_module_directories import module_with_symbol -def main(): +def main(argv=None): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser() parser.add_argument("--build", type=Path, required=True) - args = parser.parse_args() - subprocess.run(["node", "--test", str(ROOT / "demo/web/tests/output-directory.test.mjs")], check=True) - with tempfile.TemporaryDirectory(prefix="anyps5-web-") as directory: - fixture = Path(directory) - (fixture / "main.elf").write_bytes(main_fixture()) - (fixture / "module.elf").write_bytes(module_with_symbol(True)) - source = (args.build / "web/archive.mjs").read_text(encoding="utf-8") - url = re.search(r'from "(https://cdn.jsdelivr.net/[^"\n]+)"', source).group(1) - library = fixture / "zip.mjs" - with urllib.request.urlopen(url, timeout=30) as response: - library.write_bytes(response.read()) - environment = dict(os.environ, ANYPS5_TEST_ZIP_URL=url, ANYPS5_TEST_ZIP_PATH=library.as_uri()) - subprocess.run(["node", str(ROOT / "demo/web/tests/archive-root.test.mjs"), str(args.build.resolve() / "web")], env=environment, check=True) - subprocess.run(["node", str(ROOT / "demo/web/tests/archive-windows.test.mjs"), str(args.build.resolve() / "web")], env=environment, check=True) - subprocess.run(["node", str(ROOT / "demo/web/tests/archive.test.mjs"), str(args.build.resolve() / "web"), str(fixture)], env=environment, check=True) + args = parser.parse_args(argv) + archive = args.build / "web/archive.mjs" + if not archive.is_file(): + parser.error(f"archive module does not exist: {archive}") + try: + subprocess.run(["node", "--test", *(str(ROOT / "demo/web/tests" / name) for name in ( + "app.test.mjs", "batch.test.mjs", "output-directory.test.mjs" + ))], check=True) + with tempfile.TemporaryDirectory(prefix="anyps5-web-") as directory: + fixture = Path(directory) + (fixture / "main.elf").write_bytes(main_fixture()) + (fixture / "module.elf").write_bytes(module_with_symbol(True)) + source = archive.read_text(encoding="utf-8-sig") + match = re.search(r'from "(https://cdn.jsdelivr.net/[^"\n]+)"', source) + if not match: + parser.error(f"zip library URL not found in {archive}") + url = match.group(1) + library = fixture / "zip.mjs" + with urllib.request.urlopen(url, timeout=30) as response: + library.write_bytes(response.read()) + environment = dict(os.environ, ANYPS5_TEST_ZIP_URL=url, ANYPS5_TEST_ZIP_PATH=library.as_uri()) + subprocess.run(["node", str(ROOT / "demo/web/tests/archive-root.test.mjs"), str(args.build.resolve() / "web")], env=environment, check=True) + subprocess.run(["node", str(ROOT / "demo/web/tests/archive-windows.test.mjs"), str(args.build.resolve() / "web")], env=environment, check=True) + subprocess.run(["node", str(ROOT / "demo/web/tests/archive.test.mjs"), str(args.build.resolve() / "web"), str(fixture)], env=environment, check=True) + except (subprocess.CalledProcessError, urllib.error.URLError, OSError) as error: + parser.error(str(error)) + return 0 if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/tools/tests/test_build_web.py b/tools/tests/test_build_web.py index 3a56e49429..78cdb95d0e 100644 --- a/tools/tests/test_build_web.py +++ b/tools/tests/test_build_web.py @@ -1,14 +1,18 @@ +import os import contextlib import hashlib import io import json from pathlib import Path +import subprocess +import struct import sys import tempfile import unittest from unittest.mock import patch import warnings import zipfile +import zlib sys.path.insert(0, str(Path(__file__).resolve().parents[1])) import build_web @@ -24,6 +28,60 @@ def library_zip(entries): class LibraryPublicationTests(unittest.TestCase): + def test_unicode_path_rewrite_is_rejected_before_decompression(self): + original = "libs/../libc.prx" + rewritten = "libs/libc.prx" + entry = zipfile.ZipInfo(original) + unicode_path = b"\x01" + struct.pack(" Texels{};", "alignas(65536)"), + ("alignas(16384) static char block[16];", "alignas(16384)"), + ("alignas( 0x10000 ) char block[16];", "alignas( 0x10000 )"), + ("alignas(0X4000u) char block[16];", "alignas(0X4000u)"), + ("alignas(65'536) char block[16];", "alignas(65'536)"), + ("alignas(040000) char block[16];", "alignas(040000)"), + ("alignas(0b100000000000000) char block[16];", "alignas(0b100000000000000)"), + ("alignas(256) char small[4]; alignas(32768) char large[4];", "alignas(32768)"), + ): + with self.subTest(line=line): + self.assertEqual(over_aligned(line), match) + + def test_alignments_within_the_limit(self): + for line in ( + "alignas(8192) char block[16];", + "alignas(0x2000) char block[16];", + "alignas(256) std::array Input{};", + "alignas(4096) char page[4096];", + "alignas(std::max_align_t) char storage[16];", + "alignas(Shader) char storage[sizeof(Shader)];", + "realignas(65536)", + "constexpr std::size_t TexelAlignment = 65536;", + ): + with self.subTest(line=line): + self.assertIsNone(over_aligned(line)) + + def test_named_constants_of_the_file(self): + self.assertEqual(reported("constexpr std::size_t BlockBytes = 65536;\n" + "static constexpr std::uint32_t PageBytes{0x1000u};\n" + "constexpr auto Large = 0x10000;\n" + "constexpr std::size_t Texels = 4u << 20u;\n" + "alignas(BlockBytes) std::array Block{};\n" + "alignas( Large ) char block[16];\n" + "alignas(PageBytes) char page[4096];\n" + "alignas(Texels) char texels[16];\n" + "alignas(inotify_event) char buffer[16384];\n"), [5, 6]) + self.assertEqual(reported("alignas(BlockBytes) char block[16];\n" + "constexpr std::size_t BlockBytes = 65536;\n"), []) + + def test_constants_in_separate_scopes(self): + self.assertEqual(reported(SMALL + LARGE), [7]) + self.assertEqual(reported(LARGE + SMALL), [3]) + + def test_constants_in_nested_scopes(self): + for outer, inner, expected in ((64, 16384, [6]), (16384, 64, [3, 8])): + text = ("void Nested() {\n" + f" constexpr std::size_t Alignment = {outer};\n" + " alignas(Alignment) static char before[4];\n" + " {\n" + f" constexpr std::size_t Alignment = {inner};\n" + " alignas(Alignment) static char inside[4];\n" + " }\n" + " alignas(Alignment) static char after[4];\n" + "}\n") + with self.subTest(outer=outer, inner=inner): + self.assertEqual(reported(text), expected) + + def test_constants_in_reopened_namespaces(self): + large = "constexpr std::size_t Alignment = 16384;" + use = "alignas(Alignment) static char data[4];" + for text, expected in ( + (f"namespace N {{ {large} }}\nnamespace N {{ {use} }}\n", [2]), + (f"namespace N {{ {large} }}\nnamespace M {{ {use} }}\n", []), + (f"namespace {{ {large} }}\nnamespace {{ {use} }}\n", [2]), + (f"namespace A::B {{ {large} }}\nnamespace A {{ namespace B {{ {use} }} }}\n", [2]), + (f"namespace A {{ {large} }}\nnamespace A::B {{ {use} }}\n", [2]), + (f"namespace A {{ inline namespace V1 {{ {large} }} }}\nnamespace A {{ {use} }}\n", [2]), + (f"namespace A {{ inline namespace V1 {{ {large} }} }}\nnamespace B {{ {use} }}\n", []), + (f"namespace N {{ {large} }}\nnamespace N {{ void F() {{ constexpr std::size_t Alignment = 64; {use} }} }}\n", []), + (f'extern "C" {{ {large} }}\n{use}\n', [2]), + ): + with self.subTest(text=text): + self.assertEqual(reported(text), expected) + + +class AlignmentRuleTests(unittest.TestCase): + def findings(self, after): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + + def git(*args): + return subprocess.run(["git", "-c", f"safe.directory={root}", "-c", "user.name=Conventions Test", + "-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", *args], + cwd=root, capture_output=True, check=True, encoding="utf-8").stdout + + source = root / SOURCE + source.parent.mkdir(parents=True) + source.write_text("alignas(65536) char existing[16];\n", encoding="utf-8") + git("init", "-q") + git("add", ".") + git("commit", "-qm", "test: add fixture") + source.write_text("alignas(65536) char existing[16];\n" + after, encoding="utf-8") + git("commit", "-qam", "test: add over-aligned blocks") + original = Path.cwd() + try: + os.chdir(root) + check = Check("HEAD~1", "HEAD") + check.code() + finally: + os.chdir(original) + return check.findings + + def test_only_added_code_is_reported(self): + self.assertEqual(self.findings("alignas(256) char small[16];\n" + "const char* text = \"alignas(65536)\";\n" + "alignas(0x10000) char added[16];\n" + "constexpr std::size_t BlockBytes = 65536;\n" + "alignas(BlockBytes) std::array Block{};\n"), + [("alignment", SOURCE, 4, "alignas(0x10000)"), ("alignment", SOURCE, 6, "alignas(BlockBytes)")]) + + def test_constants_are_resolved_in_their_scope(self): + self.assertEqual(self.findings("namespace First {\n" + SMALL + LARGE + "}\nnamespace Second {\n" + LARGE + SMALL + "}\n"), + [("alignment", SOURCE, 9, "alignas(Alignment)"), ("alignment", SOURCE, 15, "alignas(Alignment)")]) + + def test_reopened_namespace_keeps_its_constants(self): + self.assertEqual(self.findings("namespace N {\n" + " constexpr std::size_t Alignment = 16384;\n" + "}\n" + "namespace N {\n" + " alignas(Alignment) static char data[4];\n" + "}\n"), + [("alignment", SOURCE, 6, "alignas(Alignment)")]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_check_conventions_ci_gate.py b/tools/tests/test_check_conventions_ci_gate.py index 200d32f39b..ca942cd702 100644 --- a/tools/tests/test_check_conventions_ci_gate.py +++ b/tools/tests/test_check_conventions_ci_gate.py @@ -86,6 +86,47 @@ class CiGateTests(unittest.TestCase): def test_paths_of_another_trigger_do_not_count(self): self.assertUngated(workflow(" push:\n paths:\n - 'core/**'\n pull_request:\n branches: [main]\n", " tests:\n runs-on: ubuntu-latest\n"), "tests") + def test_flow_mapping_paths_passes(self): + self.assertGated(workflow(" pull_request: {paths: ['core/**']}\n", " tests:\n runs-on: ubuntu-latest\n")) + + def test_flow_mapping_paths_ignore_passes(self): + self.assertGated(workflow(" pull_request: {paths-ignore: ['docs/**']}\n", " tests:\n runs-on: ubuntu-latest\n")) + + def test_flow_mapping_without_paths_fails(self): + self.assertUngated(workflow(" pull_request: {branches: [main]}\n", " tests:\n runs-on: ubuntu-latest\n"), "tests") + + def test_flow_mapping_quoted_values_do_not_count(self): + for filters in ( + "{branches-ignore: ['paths:unused']}", + '{branches-ignore: ["paths-ignore:unused"]}', + "{branches-ignore: ['it''s, paths:unused']}", + r'{branches-ignore: ["escaped\", paths:unused"]}', + "{branches-ignore: ['}, paths: [core/**], {']}", + ): + with self.subTest(filters=filters): + self.assertUngated(workflow(f" pull_request: {filters}\n", " tests:\n runs-on: ubuntu-latest\n"), "tests") + self.assertUngated(f"name: Test\non: {{pull_request: {filters}}}\njobs:\n tests:\n runs-on: ubuntu-latest\n", "tests") + + def test_flow_mapping_nested_paths_do_not_count(self): + self.assertUngated(workflow(" pull_request: {branches: [{paths: ['core/**']}]}\n", " tests:\n runs-on: ubuntu-latest\n"), "tests") + self.assertUngated("name: Test\non: {push: {paths: ['core/**']}, pull_request: {branches: [{paths-ignore: ['docs/**']}]}}\njobs:\n tests:\n runs-on: ubuntu-latest\n", "tests") + + def test_flow_mapping_quoted_keys_and_delimiters_pass(self): + for filters in ( + "{branches: ['release,}'], paths: ['core/**']}", + "{'paths': ['core/**']}", + '{"paths-ignore": ["docs/**"]}', + ): + with self.subTest(filters=filters): + self.assertGated(workflow(f" pull_request: {filters}\n", " tests:\n runs-on: ubuntu-latest\n")) + self.assertGated(f"name: Test\non: {{'pull_request': {filters}, push: {{branches: [main]}}}}\njobs:\n tests:\n runs-on: ubuntu-latest\n") + + def test_top_level_flow_mapping_paths_passes(self): + self.assertGated(f"name: Test\n\non: {{pull_request: {{paths: ['core/**']}}}}\njobs:\n{GATE} tests:\n runs-on: ubuntu-latest\n") + + def test_top_level_flow_paths_of_another_trigger_do_not_count(self): + self.assertUngated("name: Test\n\non: {push: {paths: ['core/**']}, pull_request: {branches: [main]}}\njobs:\n" + GATE + " tests:\n runs-on: ubuntu-latest\n", "tests") + def test_inline_trigger_list_fails(self): self.assertUngated("name: Test\non: [push, pull_request]\njobs:\n tests:\n runs-on: ubuntu-latest\n", "tests") diff --git a/tools/tests/test_check_conventions_comments.py b/tools/tests/test_check_conventions_comments.py index f0d3d223fb..6bc843e5d2 100644 --- a/tools/tests/test_check_conventions_comments.py +++ b/tools/tests/test_check_conventions_comments.py @@ -399,7 +399,7 @@ class SilentStubSectionTests(unittest.TestCase): class TestFixtureExportTests(unittest.TestCase): - def check(self, path, text=None): + def check(self, path, text=None, base=None): files = path if text is None else {path: text} checker = Path(__file__).resolve().parents[1] / "check_conventions.py" with tempfile.TemporaryDirectory() as directory: @@ -420,6 +420,8 @@ class TestFixtureExportTests(unittest.TestCase): git("init", "-q") write("core/libs/prx/libkernel/src/Time.cpp", "std::uint64_t APS5_VABI sceKernelGetProcessTime() { return now(); }\n") write("core/libs/prx/libSceNgs2/tests/Render.cpp", "static int APS5_VABI Process(int value) { return value; }\n") + for relative, content in (base or {}).items(): + write(relative, content) git("add", ".") git("commit", "-qm", "feat: add libraries") for relative, content in files.items(): @@ -451,6 +453,63 @@ class TestFixtureExportTests(unittest.TestCase): self.assertIn("core/libs/prx/libSceAudioOut/src/Clock.cpp:1: error: [duplicate-export]", result.stdout) self.assertIn("sceKernelGetProcessTime is also in libkernel", result.stdout) + def test_large_export_set_uses_a_file_backed_pattern(self): + names = [f"ConventionExport{index:08d}LongName" for index in range(1100)] + self.assertGreater(len("|".join(names)), 32767) + source = "\n".join(f"int APS5_VABI {name}(void) {{ return 0; }}" for name in names) + source += "\nstd::uint64_t APS5_VABI sceKernelGetProcessTime() { return now(); }\n" + result = self.check("core/libs/prx/libSceAudioOut/src/Exports.cpp", source) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceAudioOut/src/Exports.cpp:1101: error: [duplicate-export]", result.stdout) + self.assertIn("sceKernelGetProcessTime is also in libkernel", result.stdout) + + def test_export_shadowing_an_assembly_export_is_a_duplicate(self): + result = self.check("core/libs/prx/libSceLibcInternal/Export.cpp", + "int APS5_VABI setjmp_nid_postfix(void) {\n NotImplemented_nid_no_patch(__func__);\n return 0;\n}\n", + base={"core/libs/prx/libc/src/JumpBuffer.cpp": 'asm(".text\\n"\n APS5_ASM_FUNCTION("setjmp_nid_postfix")\n " ret\\n");\n'}) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceLibcInternal/Export.cpp:1: error: [duplicate-export]", result.stdout) + self.assertIn("setjmp_nid_postfix is also in libc", result.stdout) + + def test_export_shadowing_a_spaced_or_split_assembly_export_is_a_duplicate(self): + for macro, call in (("APS5_ASM_FUNCTION", ' ("setjmp_nid_postfix")'), + ("APS5_ASM_FUNCTION", '(\n "setjmp_nid_postfix")'), + ("APS5_SIGNAL_ASM_FUNCTION", ' (\n "setjmp_nid_postfix")')): + with self.subTest(macro=macro, call=call): + result = self.check("core/libs/prx/libSceLibcInternal/Export.cpp", + "int APS5_VABI setjmp_nid_postfix(void) {\n NotImplemented_nid_no_patch(__func__);\n return 0;\n}\n", + base={"core/libs/prx/libc/src/JumpBuffer.cpp": 'asm(".text\\n"\n ' + macro + call + '\n " ret\\n");\n'}) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceLibcInternal/Export.cpp:1: error: [duplicate-export]", result.stdout) + self.assertIn("setjmp_nid_postfix is also in libc", result.stdout) + + def test_assembly_export_in_another_library_is_a_duplicate(self): + result = self.check("core/libs/prx/libSceAudioOut/src/Clock.cpp", + 'asm(".text\\n"\n APS5_SIGNAL_ASM_FUNCTION("sceKernelGetProcessTime")\n " ret\\n");\n') + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceAudioOut/src/Clock.cpp:2: error: [duplicate-export]", result.stdout) + self.assertIn("sceKernelGetProcessTime is also in libkernel", result.stdout) + + def test_assembly_export_after_a_raw_string_is_a_duplicate(self): + result = self.check("core/libs/prx/libSceAudioOut/src/Jump.cpp", + 'asm(R"(\n .text\n)" APS5_ASM_FUNCTION("setjmp_nid_postfix") R"(\n ret\n)");\n', + base={"core/libs/prx/libc/src/JumpBuffer.cpp": 'asm(".text\\n"\n APS5_ASM_FUNCTION("setjmp_nid_postfix")\n " ret\\n");\n'}) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceAudioOut/src/Jump.cpp:3: error: [duplicate-export]", result.stdout) + self.assertIn("setjmp_nid_postfix is also in libc", result.stdout) + + def test_assembly_export_in_a_block_comment_is_not_an_export(self): + result = self.check("core/libs/prx/libSceLibcInternal/Export.cpp", + "int APS5_VABI setjmp_nid_postfix(void) {\n NotImplemented_nid_no_patch(__func__);\n return 0;\n}\n", + base={"core/libs/prx/libc/src/JumpBuffer.cpp": '/*\n APS5_ASM_FUNCTION("setjmp_nid_postfix")\n*/\n'}) + self.assertNotIn("duplicate-export", result.stdout, result.stdout + result.stderr) + + def test_assembly_name_that_is_not_an_identifier_keeps_the_check(self): + result = self.check("core/libs/prx/libSceAudioOut/src/Clock.cpp", + 'asm(".text\\n"\n APS5_ASM_FUNCTION("x(y")\n " ret\\n");\nstd::uint64_t APS5_VABI sceKernelGetProcessTime() {\n return 1;\n}\n') + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("sceKernelGetProcessTime is also in libkernel", result.stdout) + def test_comment_in_a_shader_is_reported(self): shaders = {} for path in ("core/libs/prx/libSceAgcDriver/Graphics/shaders/Fill.comp", "core/libs/prx/libSceAgcDriver/tests/shaders/Fill.frag", "core/libs/prx/libSceAgcDriver/Graphics/shaders/Fill.glsl"): @@ -706,6 +765,20 @@ class RepositoryChecksTests(unittest.TestCase): check.code() self.assertEqual(check.findings, [("comment", name, 2, "")]) + def test_large_export_set_checks_duplicates_without_command_line_limit(self): + first = self.root / "core/libs/prx/libSceA/Export.cpp" + first.parent.mkdir(parents=True) + first.write_text("int APS5_VABI sceLargeExportSet1999(int value) { return value; }\n", encoding="utf-8") + self.commit() + second = self.root / "core/libs/prx/libSceB/Export.cpp" + second.parent.mkdir(parents=True) + second.write_text("".join(f"int APS5_VABI sceLargeExportSet{index}(int value) {{ return value; }}\n" for index in range(2000)), encoding="utf-8") + self.commit() + first.write_text("", encoding="utf-8") + check = Check("HEAD~1", "HEAD") + check.code() + self.assertEqual(check.findings, [("duplicate-export", "core/libs/prx/libSceB/Export.cpp", 2000, "sceLargeExportSet1999 is also in libSceA")]) + def test_checks_from_a_subdirectory_see_the_whole_repository(self): definition = "int APS5_VABI sceFooBar(int a, int b) { return a + b; }\n" first = self.root / "core/libs/prx/libSceA/Export.cpp" diff --git a/tools/tests/test_check_conventions_env_reference.py b/tools/tests/test_check_conventions_env_reference.py index 0ccd9bf93c..ce341ce439 100644 --- a/tools/tests/test_check_conventions_env_reference.py +++ b/tools/tests/test_check_conventions_env_reference.py @@ -14,7 +14,7 @@ SAMPLE = "core/libs/prx/libSceExample/Sample.cpp" DOC_TEXT = "| `APS5_DOCUMENTED` | presence |\n| `APS5_GONE` | presence |\n" -def repository(root, base, head): +def repository(root, base, head, target=None): def git(*args): subprocess.run(["git", "-c", f"safe.directory={root}", "-c", "user.name=Conventions Test", "-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", *args], @@ -34,16 +34,23 @@ def repository(root, base, head): git("add", ".") git("commit", "-qm", "docs: add base") git("tag", "base") + if target is not None: + git("branch", "pull") + apply(target) + git("add", "-A") + git("commit", "-qm", "docs: advance the target") + git("tag", "target") + git("checkout", "-q", "pull") apply(head) git("add", "-A") git("commit", "-qm", "feat: read a new name") -def findings_of(root, base, head): +def findings_of(root, base, head, ref="base", directory="."): previous = os.getcwd() - os.chdir(root) + os.chdir(root / directory) try: - check = Check("base", "HEAD") + check = Check(ref, "HEAD") check.env_reference() return check.findings finally: @@ -51,12 +58,57 @@ def findings_of(root, base, head): class EnvReferenceDiffTests(unittest.TestCase): - def run_case(self, base, head): - directory = tempfile.TemporaryDirectory() - self.addCleanup(directory.cleanup) - root = Path(directory.name) - repository(root, base, head) - return findings_of(root, base, head) + def create_repository(self, base, head, target=None): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + root = Path(temporary.name) + repository(root, base, head, target) + return root + + def run_case(self, base, head, target=None, directory="."): + root = self.create_repository(base, head, target) + return findings_of(root, base, head, "base" if target is None else "target", directory) + + def test_a_check_from_a_subdirectory_reports_the_same_findings(self): + other = "core/libs/prx/libSceExample/Other.cpp" + base = { + DOC: DOC_TEXT, + SAMPLE: 'static bool a = std::getenv("APS5_DOCUMENTED") != nullptr;\n', + other: 'static bool b = std::getenv("APS5_DOCUMENTED") != nullptr;\n', + } + target = {DOC: DOC_TEXT + "| `APS5_TARGET_ROW` | presence |\n"} + head = { + DOC: "| `APS5_DOCUMENTED` | presence |\n", + SAMPLE: base[SAMPLE] + 'static bool c = std::getenv("APS5_NEW_READ") != nullptr;\n' + 'static bool d = std::getenv("APS5_TARGET_ROW") != nullptr;\n', + other: None, + } + expected = [("env-reference", SAMPLE, 2, f"APS5_NEW_READ is read here but has no row in {DOC}")] + root = self.create_repository(base, head, target) + for directory in (".", "core", "core/libs/prx/libSceExample", "docs/dev"): + with self.subTest(directory=directory): + self.assertEqual(findings_of(root, base, head, "target", directory), expected) + + def test_a_new_spaced_read_is_reported_at_its_line(self): + base = {DOC: DOC_TEXT, SAMPLE: "int unrelated;\n"} + head = {SAMPLE: base[SAMPLE] + 'auto value = !std::getenv \t("APS5_NEW_READ");\n'} + self.assertEqual(self.run_case(base, head), [("env-reference", SAMPLE, 2, + f"APS5_NEW_READ is read here but has no row in {DOC}")]) + + def test_removing_a_compact_read_keeps_a_row_still_used_by_a_spaced_read(self): + other = "core/libs/prx/libSceExample/Other.cpp" + base = { + DOC: DOC_TEXT, + SAMPLE: 'auto a = std::getenv("APS5_DOCUMENTED");\n', + other: 'auto b = _wgetenv \t(L"APS5_DOCUMENTED");\n', + } + self.assertEqual(self.run_case(base, {SAMPLE: "int unrelated;\n"}), []) + + def test_a_removed_row_is_reported_when_a_spaced_read_remains(self): + base = {DOC: DOC_TEXT, SAMPLE: 'auto value = std::getenv \t("APS5_DOCUMENTED");\n'} + head = {DOC: "| `APS5_GONE` | presence |\n"} + self.assertEqual(self.run_case(base, head), [("env-reference", DOC, 0, + "APS5_DOCUMENTED is still read by the code but this pull request removes its row")]) def test_only_a_name_the_pull_request_adds_is_reported_at_its_line(self): base = { @@ -143,6 +195,29 @@ class EnvReferenceDiffTests(unittest.TestCase): self.assertEqual(self.run_case(base, head), [("env-reference", DOC, 0, "APS5_GONE has a row but the code no longer reads it")]) + def test_a_row_the_target_added_after_the_branch_point_documents_the_read(self): + base = {SAMPLE: "int unrelated;\n"} + target = {DOC: "| `APS5_NEW_READ` | presence |\n"} + head = {SAMPLE: 'static bool a = std::getenv("APS5_NEW_READ") != nullptr;\n'} + self.assertEqual(self.run_case(base, head, target), []) + + def test_a_read_the_target_does_not_document_is_still_reported(self): + base = {SAMPLE: "int unrelated;\n"} + target = {DOC: DOC_TEXT} + head = {SAMPLE: 'static bool a = std::getenv("APS5_NEW_READ") != nullptr;\n'} + self.assertEqual(self.run_case(base, head, target), [("env-reference", SAMPLE, 1, + f"APS5_NEW_READ is read here but has no row in {DOC}")]) + + def test_a_row_the_pull_request_removes_is_reported_although_the_target_keeps_it(self): + base = { + DOC: DOC_TEXT, + SAMPLE: 'static bool a = std::getenv("APS5_DOCUMENTED") != nullptr;\n', + } + target = {"core/libs/prx/libSceExample/Other.cpp": "int other;\n"} + head = {DOC: "| `APS5_GONE` | presence |\n"} + self.assertEqual(self.run_case(base, head, target), [("env-reference", DOC, 0, + "APS5_DOCUMENTED is still read by the code but this pull request removes its row")]) + if __name__ == "__main__": unittest.main() diff --git a/tools/tests/test_check_main_sync.py b/tools/tests/test_check_main_sync.py index a4590a60e5..85292d1d51 100644 --- a/tools/tests/test_check_main_sync.py +++ b/tools/tests/test_check_main_sync.py @@ -1,4 +1,6 @@ from pathlib import Path +import contextlib +import io import os import subprocess import sys @@ -65,6 +67,14 @@ class CheckMainSyncTests(unittest.TestCase): self.assertEqual((data["behind"], data["ahead"], data["conflicts"], data["shared"]), (0, 1, [], [])) self.assertEqual(run_tool(self.repo.path, "--head", "feature", "--strict").returncode, 0) + def test_main_with_explicit_argv(self): + self.branch("feature") + self.repo.write("other.txt", "y\n") + self.repo.commit("feature change") + with contextlib.redirect_stdout(io.StringIO()): + code = check_main_sync.main(["--repo", str(self.repo.path), "--base", "main", "--head", "feature"]) + self.assertEqual(code, 0) + def test_behind_main_is_reported_and_only_fails_when_strict(self): self.branch("feature") self.repo.write("other.txt", "y\n") @@ -111,6 +121,71 @@ class CheckMainSyncTests(unittest.TestCase): self.assertIn("1 file(s) changed on both sides", result.stdout) self.assertIn("#77", result.stdout) + def check_squash_merge(self, conflict): + self.branch("feature") + self.repo.write("shared.txt", "one\nmine\nthree\n" if conflict else "zero\none\ntwo\nthree\n") + self.repo.commit("feature edit") + self.repo.git("checkout", "-q", "main") + self.repo.git("checkout", "-q", "-b", "theirs") + self.repo.write("shared.txt", "one\ntheirs\nthree\n" if conflict else "one\ntwo\nthree\nfour\n") + self.repo.commit("their edit") + self.repo.git("checkout", "-q", "main") + self.repo.git("merge", "--squash", "-q", "theirs") + self.repo.commit("fix(test): change the shared file (#4242)") + revision = check_main_sync.git(self.repo.path, "rev-parse", "--short", "main").stdout.strip() + data = check_main_sync.report(self.repo.path, "main", "feature") + self.assertEqual(data["pulls"], {4242: revision}) + self.assertEqual(data["shared"], ["shared.txt"]) + self.assertEqual(data["conflicts"], ["shared.txt"] if conflict else []) + for strict in ((False,) if conflict else (False, True)): + with self.subTest(strict=strict): + result = run_tool(self.repo.path, "--head", "feature", *(["--strict"] if strict else [])) + self.assertEqual(result.returncode, 1 if conflict or strict else 0, result.stderr) + self.assertIn("merge: CONFLICT" if conflict else "merge: clean", result.stdout) + self.assertIn("merged pull requests that touched them: #4242", result.stdout) + self.assertNotIn("Traceback", result.stderr) + + def test_clean_squash_merge_names_the_pull_request(self): + self.check_squash_merge(False) + + def test_conflicting_squash_merge_names_the_pull_request(self): + self.check_squash_merge(True) + + def test_squash_numbers_must_end_the_subject_and_touch_shared_paths(self): + self.branch("feature") + self.repo.write("shared.txt", "zero\none\ntwo\nthree\n") + self.repo.commit("feature edit") + self.repo.git("checkout", "-q", "main") + self.repo.write("shared.txt", "one\ntwo\nthree\nfour\n") + self.repo.commit("fix(test): reference (#13) without merging it") + self.assertEqual(check_main_sync.report(self.repo.path, "main", "feature")["pulls"], {}) + self.repo.write("shared.txt", "one\ntwo\nthree\nfour\nfive\n") + self.repo.commit("fix(test): mention (#13) but merge (#4242)") + revision = check_main_sync.git(self.repo.path, "rev-parse", "--short", "main").stdout.strip() + self.repo.write("other.txt", "unrelated\n") + self.repo.commit("fix(test): an unrelated pull request (#9000)") + self.assertEqual(check_main_sync.report(self.repo.path, "main", "feature")["pulls"], {4242: revision}) + + def test_a_subdirectory_reports_the_same_conflicts_and_pull_requests(self): + (self.repo.path / "docs").mkdir() + self.repo.write("docs/notes.txt", "x\n") + self.repo.commit("add docs") + self.branch("feature") + self.repo.write("shared.txt", "one\nmine\nthree\n") + self.repo.commit("feature edit") + self.repo.git("checkout", "-q", "main") + self.repo.git("checkout", "-q", "-b", "theirs") + self.repo.write("shared.txt", "one\ntheirs\nthree\n") + self.repo.commit("their edit") + self.repo.git("checkout", "-q", "main") + self.repo.merge_pull(77, "theirs") + from_root = check_main_sync.report(self.repo.path, "main", "feature") + self.assertEqual((from_root["conflicts"], from_root["pulls"].keys()), (["shared.txt"], {77})) + self.assertEqual(check_main_sync.report(self.repo.path / "docs", "main", "feature"), from_root) + result = run_tool(self.repo.path / "docs", "--head", "feature") + self.assertEqual(result.returncode, 1, result.stderr) + self.assertEqual(result.stdout, run_tool(self.repo.path, "--head", "feature").stdout) + def test_paths_with_spaces_and_unicode_are_reported_verbatim(self): name = "sp ace \u00e9.txt" self.repo.write(name, "one\ntwo\n") @@ -136,6 +211,15 @@ class CheckMainSyncTests(unittest.TestCase): result = run_tool(self.repo.path, "--head", "island") self.assertEqual(result.returncode, 2) self.assertIn("no common ancestor", result.stderr) + result = run_tool(self.repo.path, "--head", "missing") + expected = subprocess.run( + ["git", "-C", str(self.repo.path), "merge-base", "main", "missing"], + capture_output=True, + encoding="utf-8", + ).stderr.strip() + self.assertEqual(result.returncode, 2) + self.assertIn(f"git merge-base failed: {expected}", result.stderr) + self.assertNotIn("no common ancestor", result.stderr) def check_unicode_output(self, conflict): name = "sp ace \u00e9 \u6e2c \U0001f680.txt" diff --git a/tools/tests/test_check_skips.py b/tools/tests/test_check_skips.py index f4de797f05..567a04612c 100644 --- a/tools/tests/test_check_skips.py +++ b/tools/tests/test_check_skips.py @@ -1,8 +1,11 @@ from pathlib import Path +import contextlib +import io import subprocess import sys import tempfile import unittest +from unittest import mock sys.path.insert(0, str(Path(__file__).resolve().parents[1])) import check_skips @@ -34,6 +37,117 @@ OTHER = [ class CheckSkipsTests(unittest.TestCase): + def test_ctest_status_and_prefixed_device_reasons_are_expected(self): + reports = ( + ('skipped, the device lacks geometryShader', "linux"), + ('f16 native: skipped, the device has no native f16 with RTE rounding and kept denormals', "linux"), + ('video: skipped, no display or Vulkan device: ErrorInitializationFailed', "windows"), + ('skipped, no usable Vulkan device: none', "windows"), + ) + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "junit.xml" + for report, job in reports: + with self.subTest(job=job, report=report): + junit.write_text(f'{report}', encoding="utf-8") + result = subprocess.run([sys.executable, "-I", str(TOOL), str(junit), "--job", job], + capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn("all expected", result.stdout) + + def test_ctest_status_does_not_allow_missing_or_unexpected_reasons(self): + reports = ( + '', + '', + 'skipped, flaky\nskipped, the device lacks geometryShader', + ) + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "junit.xml" + for report in reports: + for job in ("linux", "windows"): + with self.subTest(job=job, report=report): + junit.write_text(f'{report}', encoding="utf-8") + result = subprocess.run([sys.executable, "-I", str(TOOL), str(junit), "--job", job], + capture_output=True, text=True) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("error: guest_probe skipped", result.stdout) + + def test_ctest_status_preserves_high_refresh_policy(self): + reason = "skipped, current display does not provide 120 Hz" + reports = ( + (f'{reason}', 0), + (f'{reason}', 0), + (f'{reason}\nunexpected failure detail', 1), + (f'{reason}\nskipped, flaky', 1), + (f'skipped, no display\n{reason}', 1), + ) + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "junit.xml" + for report, expected_code in reports: + for job in ("linux", "windows"): + with self.subTest(job=job, report=report): + junit.write_text(f'{report}', encoding="utf-8") + result = subprocess.run([sys.executable, "-I", str(TOOL), str(junit), "--job", job], + capture_output=True, text=True) + self.assertEqual(result.returncode, expected_code, result.stdout + result.stderr) + + def test_list_reports_device_reason_after_ctest_status(self): + xml = ('' + 'f16 native: skipped, the device has no native f16') + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "junit.xml" + junit.write_text(xml, encoding="utf-8") + result = subprocess.run([sys.executable, "-I", str(TOOL), str(junit), "--list"], + capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn("guest_probe: skipped, the device has no native f16", result.stdout) + self.assertNotIn("SKIP_RETURN_CODE", result.stdout) + + def test_unexpected_skip_reason_cannot_hide_behind_a_device_limitation(self): + reports = ( + ('skipped, the device lacks geometryShader', "linux"), + ('skipped, no display', "windows"), + ('flakyskipped, no usable Vulkan device', "windows"), + ('skipped, flaky\nskipped, the device lacks geometryShader', "linux"), + ) + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "junit.xml" + for report, job in reports: + with self.subTest(job=job, report=report): + junit.write_text(f'{report}', encoding="utf-8") + result = subprocess.run([sys.executable, "-I", str(TOOL), str(junit), "--job", job], + capture_output=True, text=True) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("error: guest_probe skipped", result.stdout) + + def test_multiple_allowed_skip_reasons_remain_expected(self): + reports = ( + ('skipped, no display', "linux"), + ('skipped, no usable Vulkan device', "windows"), + ) + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "junit.xml" + for report, job in reports: + with self.subTest(job=job, report=report): + junit.write_text(f'{report}', encoding="utf-8") + result = subprocess.run([sys.executable, "-I", str(TOOL), str(junit), "--job", job], + capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_non_junit_xml_fails_in_every_cli_mode(self): + reports = ('', 'coverage') + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "ctest.xml" + for report in reports: + junit.write_text(report, encoding="utf-8") + for arguments in (("--job", "linux"), ("--job", "windows"), ("--list",)): + with self.subTest(report=report, arguments=arguments): + result = subprocess.run([sys.executable, "-I", str(TOOL), str(junit), *arguments], + capture_output=True, text=True, timeout=5) + self.assertEqual(result.returncode, 2, result.stdout + result.stderr) + self.assertIn("expected a JUnit", result.stderr) + self.assertNotIn("Traceback", result.stderr) + self.assertNotIn("all expected", result.stdout) + def test_missing_high_refresh_display_is_expected_in_both_jobs(self): reason = "current display does not provide 120 Hz" reports = ( @@ -192,7 +306,77 @@ class CheckSkipsTests(unittest.TestCase): self.assertIn("indented: skipped, host imports are unavailable", result.stdout) self.assertIn("capitalized: Skipped, no display", result.stdout) + def test_main_with_explicit_argv(self): + xml = '' + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "junit.xml" + junit.write_text(xml, encoding="utf-8") + with contextlib.redirect_stdout(io.StringIO()): + code = check_skips.main([str(junit), "--job", "linux"]) + self.assertEqual(code, 0) + def test_main_reconfigures_streams(self): + class Stream: + def __init__(self): + self.encoding = None + self.errors = None + + def reconfigure(self, encoding=None, errors=None): + self.encoding = encoding + self.errors = errors + + def write(self, text): + pass + + def flush(self): + pass + + out, err = Stream(), Stream() + with mock.patch.object(check_skips.sys, "stdout", out), mock.patch.object(check_skips.sys, "stderr", err): + try: + check_skips.main([]) + except SystemExit: + pass + self.assertEqual(out.encoding, "utf-8") + self.assertEqual(out.errors, "backslashreplace") + self.assertEqual(err.encoding, "utf-8") + self.assertEqual(err.errors, "backslashreplace") + + + + +class RequiredTestsTests(unittest.TestCase): + def test_required_reports_without_testcases_fail_in_both_jobs(self): + for xml in ('', ''): + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "junit.xml" + junit.write_text(xml, encoding="utf-8") + for job in ("linux", "windows"): + for options in ([], ["--list"]): + with self.subTest(xml=xml, job=job, options=options): + result = subprocess.run( + [sys.executable, "-I", str(TOOL), str(junit), "--job", job, "--require-tests", *options], + capture_output=True, text=True, timeout=5) + self.assertEqual(result.returncode, 2, result.stdout + result.stderr) + self.assertIn("no testcases", result.stderr) + result = subprocess.run( + [sys.executable, "-I", str(TOOL), str(junit), "--job", job, *options], + capture_output=True, text=True, timeout=5) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_executed_tests_without_skips_pass_in_both_jobs(self): + for xml in ('', + ''): + with tempfile.TemporaryDirectory() as directory: + junit = Path(directory) / "junit.xml" + junit.write_text(xml, encoding="utf-8") + for job in ("linux", "windows"): + with self.subTest(xml=xml, job=job): + result = subprocess.run( + [sys.executable, "-I", str(TOOL), str(junit), "--job", job, "--require-tests"], + capture_output=True, text=True, timeout=5) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn("0 test(s) skipped, all expected", result.stdout) class SkipReportTests(unittest.TestCase): @@ -202,6 +386,18 @@ class SkipReportTests(unittest.TestCase): path.write_text(content, encoding="utf-8") return list(check_skips.skips(path)) + def test_non_junit_roots_are_rejected_even_with_testcases(self): + for content in ('', '', + '', + ''): + with self.subTest(content=content), self.assertRaisesRegex(ValueError, "expected a JUnit"): + self.skipped(content) + + def test_empty_junit_suites_remain_valid(self): + for content in ('', '', ''): + with self.subTest(content=content): + self.assertEqual(self.skipped(content), []) + def test_ctest_disabled_status_is_not_silently_ignored(self): with self.assertRaisesRegex(ValueError, "disabled.*not run"): self.skipped('') diff --git a/tools/tests/test_configure_seed.py b/tools/tests/test_configure_seed.py index 2cff72cd94..36d6c6a66d 100644 --- a/tools/tests/test_configure_seed.py +++ b/tools/tests/test_configure_seed.py @@ -1,9 +1,11 @@ +import ctypes import os import shutil import subprocess import sys import tempfile import unittest +from unittest import mock from pathlib import Path TOOL = Path(__file__).resolve().parents[1] / "configure_seed.py" @@ -16,11 +18,20 @@ project(mini C) include(CheckIncludeFile) include(CheckSymbolExists) include(CheckCSourceCompiles) +include(CheckTypeSize) check_include_file(stdio.h MINI_HAVE_STDIO_H) check_include_file(mini_missing_header.h MINI_HAVE_MISSING_H) check_symbol_exists(printf stdio.h MINI_HAVE_PRINTF) check_c_source_compiles("int main(void) { return 0; }" MINI_COMPILES) check_c_source_compiles("int main(void) { return undefined_name; }" MINI_FAILS) +check_type_size(int MINI_SIZEOF_INT) +check_type_size(mini_no_such_type_t MINI_SIZEOF_NONE) +if(NOT HAVE_MINI_SIZEOF_INT OR NOT MINI_SIZEOF_INT) + message(FATAL_ERROR "Unsupported integer size: int") +endif() +if(HAVE_MINI_SIZEOF_NONE OR NOT "${MINI_SIZEOF_NONE}" STREQUAL "") + message(FATAL_ERROR "A missing type has a size") +endif() set(MINI_OPTION "value" CACHE STRING "An option, not a check result") """ @@ -62,6 +73,14 @@ class ConfigureSeedPathTests(unittest.TestCase): self.assertEqual(result.returncode, 0, result.stderr) self.assertIn("HAVE_FEATURE", seed.read_text(encoding="utf-8")) + def test_unwritable_seed_is_reported_without_a_traceback(self): + seed_dir = self.root / "seed_dir" + seed_dir.mkdir() + result = self.run_seed(seed_dir) + self.assertEqual(result.returncode, 2, result.stderr) + self.assertIn(str(seed_dir), result.stderr) + self.assertNotIn("Traceback", result.stderr) + def test_same_path_preserves_cache(self): self.assert_rejected(self.cache) @@ -149,8 +168,9 @@ class ConfigureSeedProject(unittest.TestCase): class ConfigureSeedTests(ConfigureSeedProject): def test_only_check_results_are_seeded(self): found = configure_seed.checks(self.root / "fresh" / "CMakeCache.txt") - self.assertEqual({n for n in found if n.startswith("MINI_")}, - {"MINI_HAVE_STDIO_H", "MINI_HAVE_MISSING_H", "MINI_HAVE_PRINTF", "MINI_COMPILES", "MINI_FAILS"}) + self.assertEqual({n for n in found if "MINI_" in n}, + {"MINI_HAVE_STDIO_H", "MINI_HAVE_MISSING_H", "MINI_HAVE_PRINTF", "MINI_COMPILES", "MINI_FAILS", + "HAVE_MINI_SIZEOF_INT", "MINI_SIZEOF_INT", "HAVE_MINI_SIZEOF_NONE", "MINI_SIZEOF_NONE"}) self.assertNotIn("CMAKE_C_COMPILER", found) def test_cache_without_checks_writes_an_empty_seed(self): @@ -193,13 +213,17 @@ class ConfigureSeedRoundTripTests(ConfigureSeedProject): self.assertNotIn("HAVE:INTERNAL=", seeded.read_text(encoding="utf-8")) def test_seeded_configure_runs_no_check_and_gets_the_same_results(self): - self.assertRegex(self.fresh, r"Looking for|Performing Test") + self.assertRegex(self.fresh, r"Looking for|Performing Test|Check size of") output = self.configure("seeded", "-C", str(self.seed(self.root / "fresh" / "CMakeCache.txt", "seed.cmake"))) - self.assertNotRegex(output, r"Looking for|Performing Test") + self.assertNotRegex(output, r"Looking for|Performing Test|Check size of") fresh = configure_seed.checks(self.root / "fresh" / "CMakeCache.txt") self.assertEqual(configure_seed.checks(self.root / "seeded" / "CMakeCache.txt"), fresh) self.assertEqual(fresh["MINI_HAVE_STDIO_H"][0], "1") self.assertEqual(fresh["MINI_FAILS"][0], "") + self.assertEqual(fresh["HAVE_MINI_SIZEOF_INT"][0], "TRUE") + self.assertEqual(fresh["MINI_SIZEOF_INT"][0], str(ctypes.sizeof(ctypes.c_int))) + self.assertEqual(fresh["HAVE_MINI_SIZEOF_NONE"][0], "FALSE") + self.assertEqual(fresh["MINI_SIZEOF_NONE"][0], "") def test_values_with_cmake_syntax_round_trip(self): cache = self.root / "odd" / "CMakeCache.txt" @@ -221,6 +245,33 @@ class ChecksParsingTests(unittest.TestCase): found = configure_seed.checks(cache) self.assertEqual(found, {"FIRST_CHECK": ("1", "Test FIRST_CHECK")}) + def test_main_reconfigures_streams(self): + class Stream: + def __init__(self): + self.encoding = None + self.errors = None + + def reconfigure(self, encoding=None, errors=None): + self.encoding = encoding + self.errors = errors + + def write(self, text): + pass + + def flush(self): + pass + + out, err = Stream(), Stream() + with mock.patch.object(configure_seed.sys, "stdout", out), mock.patch.object(configure_seed.sys, "stderr", err): + try: + configure_seed.main([]) + except SystemExit: + pass + self.assertEqual(out.encoding, "utf-8") + self.assertEqual(out.errors, "backslashreplace") + self.assertEqual(err.encoding, "utf-8") + self.assertEqual(err.errors, "backslashreplace") + if __name__ == "__main__": unittest.main() diff --git a/tools/tests/test_debt_split.py b/tools/tests/test_debt_split.py index 1f2cf31775..ae68754774 100644 --- a/tools/tests/test_debt_split.py +++ b/tools/tests/test_debt_split.py @@ -44,15 +44,14 @@ class DebtSplitTests(unittest.TestCase): self.root = Path(self.directory.name) self.git("init", "-q", "-b", "main") self.write(INDEX, ORIGINAL) - self.git("add", ".") - self.git("commit", "-qm", "docs: record technical debt") def tearDown(self): self.directory.cleanup() def git(self, *args, check=True): return subprocess.run( - ["git", "-c", "user.name=Debt Test", "-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", *args], + ["git", "-c", "user.name=Debt Test", "-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", + "-c", "maintenance.auto=false", *args], cwd=self.root, capture_output=True, text=True, check=check, env={**os.environ, "GIT_EDITOR": "true"}, ) @@ -68,10 +67,19 @@ class DebtSplitTests(unittest.TestCase): return subprocess.run([sys.executable, str(TOOL), command], cwd=self.root, capture_output=True, text=True, timeout=20) def split(self): - result = self.tool("split") - self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + directory, streams = os.getcwd(), (sys.stdout, sys.stderr) + os.chdir(self.root) + try: + with open(os.devnull, "w", encoding="utf-8") as sink: + sys.stdout = sys.stderr = sink + debt_split.main(["split"]) + finally: + os.chdir(directory) + sys.stdout, sys.stderr = streams + + def commit(self, message): self.git("add", "-A") - self.git("commit", "-qm", "docs: split technical debt") + self.git("commit", "-qm", message) def test_split_preserves_external_urls_and_rewrites_local_links(self): links = ["HTTPS://example.test/guide#intro", "//example.test/guide#intro", @@ -114,11 +122,13 @@ class DebtSplitTests(unittest.TestCase): self.assertEqual(self.read(INDEX).count("[debt](debt/)"), 1) def rebase(self, stubs, functional): + self.commit("docs: record technical debt") self.git("checkout", "-qb", "change") self.write(INDEX, index(stubs, functional)) self.git("commit", "-qam", "feat: change debt") self.git("checkout", "-q", "main") self.split() + self.commit("docs: split technical debt") self.git("checkout", "-q", "change") rebase = self.git("rebase", "main", check=False) self.assertNotEqual(rebase.returncode, 0, rebase.stdout + rebase.stderr) @@ -170,11 +180,13 @@ class DebtSplitTests(unittest.TestCase): self.assertIn("<<<<<<<", self.read(INDEX)) def test_resolve_refuses_an_entry_main_changed_too(self): + self.commit("docs: record technical debt") self.git("checkout", "-qb", "change") self.write(INDEX, index([A_STUB, B_STUB + " here", C_STUB], [A_FUNCTIONAL, SHADER, SHARED])) self.git("commit", "-qam", "feat: change b") self.git("checkout", "-q", "main") self.split() + self.commit("docs: split technical debt") path = "docs/dev/debt/libSceB.md" self.write(path, self.read(path).replace("returns 0", "returns 1")) self.git("commit", "-qam", "docs: change b") @@ -185,6 +197,7 @@ class DebtSplitTests(unittest.TestCase): self.assertIn("entry not found", result.stderr) def test_resolve_needs_a_conflict(self): + self.commit("docs: record technical debt") result = self.tool("resolve") self.assertNotEqual(result.returncode, 0) self.assertIn("not in a three-way conflict", result.stderr) diff --git a/tools/tests/test_env_reference.py b/tools/tests/test_env_reference.py index c414e314c0..3b43421864 100644 --- a/tools/tests/test_env_reference.py +++ b/tools/tests/test_env_reference.py @@ -3,7 +3,9 @@ import io import sys import tempfile import unittest +from unittest import mock from pathlib import Path +from unittest.mock import patch sys.path.insert(0, str(Path(__file__).resolve().parents[1])) import env_reference @@ -30,6 +32,8 @@ static bool q = _wgetenv(L"APS5_WIDE_PRESENT") != nullptr; static int r = _wtoi(_wgetenv(L"APS5_WIDE_INT")); static const wchar_t* s = _wgetenv(L"ANYPS5_WIDE_CONFIG"); static const char* t = std::getenv("NOT_AN_ENV"); +static std::uint64_t u = std::min(EnvironmentNumber("APS5_CRASH_STACK_BYTES"), std::uint64_t{1} << 20); +static std::uint64_t v = MyEnvironmentNumber("APS5_NOT_A_NUMBER_READ"); """ @@ -52,7 +56,8 @@ class EnvReferenceTests(unittest.TestCase): "APS5_CRASH_INDIRECT_MEMORY", "APS5_PIPELINE_JOURNAL_MAX_MIB", "APS5_PIPELINE_STORE_ENTRIES", "APS5_PIPELINE_JOURNAL_THREADS", "APS5_PM4_RING", "ANYPS5_LOADEXEC_ARGV_SKIP", "ANYPS5_LOADEXEC_PARENT", - "ANYPS5_WIDE_CONFIG", "APS5_WIDE_PRESENT", "APS5_WIDE_INT"}) + "ANYPS5_WIDE_CONFIG", "APS5_WIDE_PRESENT", "APS5_WIDE_INT", + "APS5_CRASH_STACK_BYTES"}) self.assertEqual(found["APS5_PROFILE_DRAW"][0][1], 1) self.assertEqual(found["APS5_THREAD_NICE"][0][2:], ("integer", "-10")) self.assertEqual(found["APS5_HOST_IMPORT_MIB"][0][2:], ("integer", "6144")) @@ -65,6 +70,97 @@ class EnvReferenceTests(unittest.TestCase): self.assertEqual(found["ANYPS5_WIDE_CONFIG"][0][2:], ("string", "")) self.assertEqual(found["APS5_WIDE_PRESENT"][0][2:], ("presence", "")) self.assertEqual(found["APS5_WIDE_INT"][0][2:], ("integer", "")) + self.assertEqual(found["APS5_CRASH_STACK_BYTES"][0][2:], ("integer", "")) + + def test_reads_environmental_variable_with_multiline_getenv(self): + root = self.fixture() + source = root / "core" / "Sample.cpp" + source.write_text( + 'static const char* value = std::getenv(\n' + ' "APS5_MULTILINE");\n', + encoding="utf-8" + ) + + found = env_reference.reads([root / "core"]) + self.assertIn("APS5_MULTILINE", found) + + def test_whitespace_before_parentheses_preserves_names_kinds_and_defaults(self): + root = self.fixture() + source = root / "core" / "Sample.cpp" + expected = env_reference.reads([root / "core"]) + for separator in (" ", "\t", " \t "): + with self.subTest(separator=separator): + source.write_text(CODE.replace("(", separator + "("), encoding="utf-8") + self.assertEqual(env_reference.reads([root / "core"]), expected) + + def test_reader_and_parenthesis_on_separate_lines_keep_line_numbers_and_kinds(self): + root = self.fixture() + source = root / "core" / "Sample.cpp" + source.write_text('auto a = !std::getenv\n ("APS5_PRESENT");\n' + 'auto b = _wtoi(_wgetenv\n (L"ANYPS5_INTEGER"));\n', encoding="utf-8") + found = env_reference.reads([root / "core"]) + self.assertEqual(set(found), {"APS5_PRESENT", "ANYPS5_INTEGER"}) + self.assertEqual(found["APS5_PRESENT"], [(source.as_posix(), 1, "presence", "")]) + self.assertEqual(found["ANYPS5_INTEGER"], [(source.as_posix(), 3, "integer", "")]) + + def test_main_check_rejects_an_undocumented_spaced_read(self): + root = self.fixture() + (root / "core" / "Sample.cpp").write_text('auto value = !std::getenv \t("APS5_UNDOCUMENTED");\n', encoding="utf-8") + doc = root / "ENVIRONMENT.md" + doc.write_text("", encoding="utf-8") + with contextlib.redirect_stdout(io.StringIO()) as out: + code = env_reference.main(["--check", "--root", str(root / "core"), "--doc", str(doc)]) + self.assertEqual(code, 1) + self.assertIn("APS5_UNDOCUMENTED is read", out.getvalue()) + self.assertNotIn("all documented", out.getvalue()) + + def test_negated_spaced_getenv_is_a_presence_read(self): + root = self.fixture() + (root / "core" / "Sample.cpp").write_text('auto a = !std::getenv ("APS5_PRESENT");\n' + 'auto b = !_wgetenv\t(L"ANYPS5_PRESENT");\n', encoding="utf-8") + found = env_reference.reads([root / "core"]) + self.assertEqual(set(found), {"APS5_PRESENT", "ANYPS5_PRESENT"}) + self.assertEqual([sites[0][2] for sites in found.values()], ["presence", "presence"]) + + def test_spacing_does_not_change_literal_parentheses_in_a_custom_prefix(self): + for separator in ("", " ", "\t"): + with self.subTest(separator=separator): + source = f'getenv{separator}("CUSTOM(NAME"); getenv{separator}("CUSTOM (WRONG");' + found = env_reference.line_reads(source, ("CUSTOM(",)) + self.assertEqual([match.group("name") for _, match in found], ["CUSTOM(NAME"]) + + def test_multiline_reads_keep_line_numbers_and_do_not_duplicate(self): + root = self.fixture() + source = root / "core" / "Sample.cpp" + source.write_text( + 'auto a = std::getenv("APS5_SINGLE");\n' + 'auto b = std::getenv(\n' + ' "APS5_MULTI");\n' + 'auto c = EnvInt(\n' + '\n' + ' "APS5_INT", 42);\n' + 'auto d = std::getenv("APS5_SINGLE");\n', + encoding="utf-8" + ) + + found = env_reference.reads([root / "core"]) + + self.assertEqual( + set(found), + {"APS5_SINGLE", "APS5_MULTI", "APS5_INT"} + ) + self.assertEqual( + [site[1] for site in found["APS5_SINGLE"]], [1, 7] + ) + self.assertEqual( + [site[1] for site in found["APS5_MULTI"]], [2] + ) + self.assertEqual( + [site[1] for site in found["APS5_INT"]], [4] + ) + self.assertEqual( + found["APS5_INT"][0][2:], ("integer", "42") + ) def test_the_default_prefixes_cover_both(self): root = self.fixture() @@ -73,6 +169,19 @@ class EnvReferenceTests(unittest.TestCase): self.assertTrue([name for name in found if name.startswith("APS5_")]) self.assertTrue([name for name in found if name.startswith("ANYPS5_")]) + def test_reads_fails_on_a_source_file_it_cannot_decode(self): + root = self.fixture() + (root / "core" / "Latin.cpp").write_bytes(b'static const char* s = "caf\xe9";\n') + with self.assertRaises(ValueError) as raised: + env_reference.reads([root / "core"], BOTH) + self.assertIn("Latin.cpp", str(raised.exception)) + stderr = io.StringIO() + with contextlib.redirect_stderr(stderr): + code = env_reference.main(["--check", "--doc", str(root / "missing.md"), "--root", str(root / "core")]) + self.assertEqual(code, 1) + self.assertIn("cannot read", stderr.getvalue()) + self.assertIn("Latin.cpp", stderr.getvalue()) + def test_check_passes_when_every_read_name_has_a_row(self): root = self.fixture() found = env_reference.reads([root / "core"]) @@ -119,6 +228,64 @@ class EnvReferenceTests(unittest.TestCase): (core / "3rdparty" / "Ignored.cpp").write_text('static bool b = IsEnvironmentSet("APS5_IGNORED");\n', encoding="utf-8") self.assertEqual(set(env_reference.reads([core], BOTH)), {"APS5_TRACE_GPU"}) + def test_main_reconfigures_streams_for_unicode_report_paths(self): + class Stream: + def __init__(self): + self.encoding = "ascii" + self.errors = "strict" + self.text = "" + + def reconfigure(self, encoding=None, errors=None): + self.encoding = encoding + self.errors = errors + + def write(self, text): + text.encode(self.encoding, self.errors) + self.text += text + return len(text) + + def flush(self): + pass + + root = self.fixture() / "source-Ж" + root.mkdir() + core = root / "core" + core.mkdir() + (core / "Sample.cpp").write_text('static bool a = std::getenv("APS5_TRACE_GPU") != nullptr;\n', encoding="utf-8") + out, err = Stream(), Stream() + with mock.patch.object(env_reference.sys, "stdout", out), mock.patch.object(env_reference.sys, "stderr", err): + self.assertEqual(env_reference.main(["--root", str(core)]), 0) + self.assertEqual((out.encoding, out.errors), ("utf-8", "backslashreplace")) + self.assertEqual((err.encoding, err.errors), ("utf-8", "backslashreplace")) + self.assertIn((core / "Sample.cpp").as_posix(), out.text) + + def test_main_reports_unicode_scan_root_with_non_utf8_stderr(self): + class Stream: + def __init__(self): + self.encoding = "ascii" + self.errors = "strict" + self.text = "" + + def reconfigure(self, encoding=None, errors=None): + self.encoding = encoding + self.errors = errors + + def write(self, text): + text.encode(self.encoding, self.errors) + self.text += text + return len(text) + + def flush(self): + pass + + root = Path(tempfile.gettempdir()) / "missing-source-Ж" + out, err = Stream(), Stream() + with mock.patch.object(env_reference.sys, "stdout", out), mock.patch.object(env_reference.sys, "stderr", err): + self.assertEqual(env_reference.main(["--root", str(root)]), 1) + self.assertEqual((out.encoding, out.errors), ("utf-8", "backslashreplace")) + self.assertEqual((err.encoding, err.errors), ("utf-8", "backslashreplace")) + self.assertIn(str(root), err.text) + def test_main_rejects_a_scan_root_that_is_not_a_directory(self): root = self.fixture() for name in ("missing", "core/Sample.cpp"): @@ -131,5 +298,33 @@ class EnvReferenceTests(unittest.TestCase): self.assertNotIn("no longer reads", out.getvalue()) + def test_main_reconfigures_streams(self): + class Stream: + def __init__(self): + self.encoding = None + self.errors = None + + def reconfigure(self, encoding=None, errors=None): + self.encoding = encoding + self.errors = errors + + def write(self, text): + pass + + def flush(self): + pass + + out, err = Stream(), Stream() + with patch.object(env_reference.sys, "stdout", out), patch.object(env_reference.sys, "stderr", err): + try: + env_reference.main(["--root", "nonexistent"]) + except SystemExit: + pass + self.assertEqual(out.encoding, "utf-8") + self.assertEqual(out.errors, "backslashreplace") + self.assertEqual(err.encoding, "utf-8") + self.assertEqual(err.errors, "backslashreplace") + + if __name__ == "__main__": unittest.main() diff --git a/tools/tests/test_import_audit.py b/tools/tests/test_import_audit.py index a29775c13b..5554152a68 100644 --- a/tools/tests/test_import_audit.py +++ b/tools/tests/test_import_audit.py @@ -104,6 +104,13 @@ def exporting(*names): return build_elf([(name, 1, GLOBAL_FUNCTION) for name in names]) +def elf_dynamic_range_fields(image): + sections = struct.unpack_from(" {nid}", out) + + def test_name_mismatch_requires_a_resolved_host_fallback(self): + name, library = "strlen", "libc.prx" + exports = {nid_names.compute_nid(name): [library]} + for providers, modules, kind, mismatch in (({}, set(), "absent", False), + (exports, {library}, "module", False), + (exports, set(), "implemented", True)): + with self.subTest(kind=kind): + records, _ = import_audit.audit([(name, library, False)], providers, {}, {library}, modules) + self.assertEqual(records[0]["class"], kind) + self.assertEqual(records[0]["symbol_mismatch"], mismatch) + + def test_literal_exports_resolve_without_a_name_mismatch(self): + work = self.work + name, nid = "strlen", nid_names.compute_nid("strlen") + registry, report = work.root / "sdk.registry.json", work.root / "audit.json" + for image in (exporting(name, nid), build_pe(sorted([name, nid]))): + with self.subTest(pe=image.startswith(b"MZ")): + (work.libs / "libc.prx").write_bytes(image) + write_registry(registry, [(name, "libc.prx"), (nid, "libc.prx")]) + code, out, err = work.run(registry, "--json", str(report)) + self.assertEqual((code, err), (0, "")) + data = json.loads(report.read_text(encoding="utf-8")) + self.assertEqual(data["symbol_mismatch"], 0) + self.assertTrue(all(not record["symbol_mismatch"] for record in data["imports"])) + self.assertIsNone(data["stops"][0]["nid"]) + self.assertNotIn("Qualified import names exported only as their NID", out) + + def test_unscoped_windows_imports_keep_the_loaders_nid_fallback(self): + work = self.work + name, nid = "strlen", nid_names.compute_nid("strlen") + registry, report = work.root / "sdk.registry.json", work.root / "audit.json" + write_registry(registry, [(name, "")]) + (work.libs / "libc.prx").write_bytes(build_pe([nid])) + code, out, err = work.run(registry, "--json", str(report)) + self.assertEqual((code, err), (0, "")) + data = json.loads(report.read_text(encoding="utf-8")) + self.assertEqual(data["symbol_mismatch"], 0) + self.assertIsNone(data["stops"][0]["nid"]) + def test_named_nid_fallback_preserves_stub_and_library_classification(self): nid = nid_names.compute_nid(MESSAGE_NAME) imports = [(MESSAGE_NAME, "libSceOther.prx", False)] @@ -309,7 +435,7 @@ class ImportAuditTests(unittest.TestCase): records, _ = import_audit.audit(imports, exports, stubs, set(exports), set()) self.assertEqual(records[0]["class"], "stub") self.assertEqual(records[0]["providers"], ["literal.prx"]) - module_imports = {"guest.so": (["strlen"], [])} + module_imports = {"guest.so": ([("strlen", False)], [])} records, _ = import_audit.audit([], {"j4ViWNHEgww": ["libc.prx"]}, {}, {"libc.prx"}, set(module_imports), module_imports, {"strlen": ["other.so"]}) @@ -495,8 +621,8 @@ class ImportAuditTests(unittest.TestCase): data = json.loads(result.read_text()) self.assertEqual(data["references"], 10) self.assertEqual(data["unique_imports"], 9) - self.assertEqual(data["unique_by_class"], {"implemented": 3, "stub": 2, "absent": 3, "module": 1}) - self.assertEqual(data["references_by_class"], {"implemented": 3, "stub": 3, "absent": 3, "module": 1}) + self.assertEqual(data["unique_by_class"], {"implemented": 3, "partial": 0, "stub": 2, "absent": 3, "module": 1}) + self.assertEqual(data["references_by_class"], {"implemented": 3, "partial": 0, "stub": 3, "absent": 3, "module": 1}) self.assertEqual(data["missing_libraries"], {"libSceFoo.prx": 1, "libSceOther.prx": 1}) self.assertEqual(data["library_mismatch"], 1) kinds = {(record["nid"], record["library"]): record["class"] for record in data["imports"]} @@ -525,7 +651,7 @@ class ImportAuditTests(unittest.TestCase): self.assertEqual((code, err), (1, "")) data = json.loads(result.read_text()) self.assertEqual(data["references"], 4) - self.assertEqual(data["unique_by_class"], {"implemented": 2, "stub": 1, "absent": 1, "module": 0}) + self.assertEqual(data["unique_by_class"], {"implemented": 2, "partial": 0, "stub": 1, "absent": 1, "module": 0}) self.assertEqual(data["missing_libraries"], {}) records = {(record["nid"], record["module"]): record for record in data["imports"]} self.assertEqual(records[("AAAAAAAAAAA", "main.prx")]["class"], "implemented") @@ -538,6 +664,28 @@ class ImportAuditTests(unittest.TestCase): self.assertIn("[library not named]", out) self.assertIn("EEEEEEEEEEE (imported by main.prx)", out) + def test_module_import_bindings_survive_the_audit(self): + work = self.work + (work.libs / "libc.prx").write_bytes(exporting()) + registry, result = work.root / "game.json", work.root / "audit.json" + write_registry(registry, []) + symbols = [("optional", 0, 0x22), ("required", 0, GLOBAL_FUNCTION), + ("mixed#first", 0, 0x22), ("mixed#second", 0, GLOBAL_FUNCTION)] + images = [build_elf(symbols), build_sectionless_elf(symbols), + build_sectionless_elf(symbols, gnu_hash=True)] + for index, image in enumerate(images): + with self.subTest(image=index): + (work.modules / "guest.so").write_bytes(image) + code, out, err = work.run(registry, "--json", str(result)) + self.assertEqual((code, err), (1, "")) + data = json.loads(result.read_text()) + records = {record["nid"]: record for record in data["imports"]} + self.assertTrue(records["optional"]["weak"]) + self.assertFalse(records["required"]["weak"]) + self.assertFalse(records["mixed"]["weak"]) + self.assertEqual(data["references"], 3) + self.assertIn("optional (weak import)", out) + def test_module_sidecars_do_not_change_the_audit(self): work = self.work (work.libs / "libSceA.prx").write_bytes(exporting("AAAAAAAAAAA")) @@ -579,7 +727,7 @@ class ImportAuditTests(unittest.TestCase): self.assertEqual((code, err), (0, "")) data = json.loads(result.read_text()) self.assertEqual(data["references"], 3) - self.assertEqual(data["unique_by_class"], {"implemented": 2, "stub": 0, "absent": 0, "module": 1}) + self.assertEqual(data["unique_by_class"], {"implemented": 2, "partial": 0, "stub": 0, "absent": 0, "module": 1}) self.assertEqual(data["missing_libraries"], {}) records = {(record["nid"], record.get("module")): record for record in data["imports"]} self.assertEqual(records[("AAAAAAAAAAA", "provider.data")]["providers"], ["libSceA.prx"]) @@ -605,6 +753,46 @@ class ImportAuditTests(unittest.TestCase): records, missing = import_audit.audit(imports, {}, {}, set(), import_audit.ModuleNames({"sdk-shipping.prx"})) self.assertEqual((records[0]["class"], missing), ("absent", {"SDK-Shipping.debug_prx": 1})) + def test_windows_module_suffix_case_does_not_report_existing_modules_as_missing(self): + work = self.work + (work.libs / "libc.prx").write_bytes(build_pe([])) + registry, report = work.root / "game.registry.json", work.root / "audit.json" + cases = (("sdk-shipping.prx", "SDK-Shipping.DEBUG_PRX"), + ("SDK-SHIPPING.PRX", "sdk-shipping.sprx"), + ("Sdk-Shipping.SuPrX", "SDK-SHIPPING.debug_prx"), + ("SDK-SHIPPING.DEBUG_PRX", "sdk-shipping.PRX")) + for filename, needed in cases: + with self.subTest(filename=filename, needed=needed): + module = work.modules / filename + module.write_bytes(exporting("AAAAAAAAAAA")) + try: + write_registry(registry, [("AAAAAAAAAAA", needed)]) + import_audit.needed_registry_path(registry).write_text(json.dumps([needed]), encoding="utf-8") + code, _, err = work.run(registry, "--json", str(report), "--strict-providers") + self.assertEqual((code, err), (0, "")) + data = json.loads(report.read_text(encoding="utf-8")) + self.assertEqual(data["missing_libraries"], {}) + self.assertEqual(data["unique_by_class"], {"implemented": 0, "partial": 0, "stub": 0, "absent": 0, "module": 1}) + self.assertEqual(data["references"], 1) + self.assertEqual(data["imports"][0]["library"], needed) + finally: + module.unlink() + + def test_elf_module_suffix_case_stays_significant(self): + work = self.work + (work.libs / "libc.prx").write_bytes(exporting()) + (work.modules / "sdk-shipping.prx").write_bytes(exporting("AAAAAAAAAAA")) + registry, report = work.root / "game.registry.json", work.root / "audit.json" + for suffix in ("PRX", "SPRX", "SUPRX", "DEBUG_PRX"): + needed = "sdk-shipping." + suffix + with self.subTest(needed=needed): + write_registry(registry, [("AAAAAAAAAAA", needed)]) + code, _, err = work.run(registry, "--json", str(report)) + self.assertEqual((code, err), (1, "")) + data = json.loads(report.read_text(encoding="utf-8")) + self.assertEqual(data["missing_libraries"], {needed: 1}) + self.assertEqual(data["unique_by_class"], {"implemented": 0, "partial": 0, "stub": 0, "absent": 1, "module": 0}) + def test_converted_module_matches_imports_of_its_base_name(self): work = self.work (work.libs / "libc.prx").write_bytes(exporting()) @@ -751,7 +939,7 @@ class ImportAuditTests(unittest.TestCase): self.assertEqual((code, err), (1, "")) data = json.loads(result.read_text()) self.assertEqual((data["references"], data["unique_imports"]), (2, 2)) - self.assertEqual(data["unique_by_class"], {"implemented": 1, "stub": 0, "absent": 1, "module": 0}) + self.assertEqual(data["unique_by_class"], {"implemented": 1, "partial": 0, "stub": 0, "absent": 1, "module": 0}) self.assertEqual(data["references_by_class"], data["unique_by_class"]) self.assertEqual(data["missing_libraries"], {"libSceMissing.prx": 1}) records = {record["nid"]: record for record in data["imports"]} @@ -792,7 +980,7 @@ class ImportAuditTests(unittest.TestCase): ("AAAAAAAAAAA", 0, GLOBAL_FUNCTION), ("BBBBBBBBBBB", 1, GLOBAL_FUNCTION)], ["libSceA.prx"]) expected_imports = import_audit.elf_imports(path, image) expected_exports = import_audit.elf_exports(path, image) - self.assertEqual(expected_imports, ({"AAAAAAAAAAA"}, {"libSceA.prx"})) + self.assertEqual(expected_imports, ({"AAAAAAAAAAA": False}, {"libSceA.prx"})) self.assertEqual(expected_exports, {"BBBBBBBBBBB"}) for section_offset in (len(image), len(image) + 64): for section_count in (1, 0): @@ -852,12 +1040,94 @@ class ImportAuditTests(unittest.TestCase): self.assertIn(str(path), err) self.assertIn("lies outside the file", err) + def test_elf_import_symbol_tables_reject_ranges_outside_the_file(self): + original = build_elf([("alpha", 0, GLOBAL_FUNCTION)]) + for offset, size in ((len(original) + 1, 0), (2**64 - 1, 0), + (len(original) - 23, 24), (len(original), 24)): + with self.subTest(offset=offset, size=size): + with self.assertRaisesRegex(import_audit.AuditError, "dynamic symbol table.*outside the file"): + import_audit.elf_imports(Path("invalid-imports.prx"), with_elf_symbol_table_range(original, offset, size)) + + def test_elf_import_symbol_tables_can_end_at_eof(self): + original = build_elf([("alpha", 0, GLOBAL_FUNCTION)]) + empty = with_elf_symbol_table_range(original, len(original), 0) + symbols, needed = import_audit.elf_imports(Path("empty.prx"), empty) + self.assertFalse(symbols) + self.assertFalse(needed) + sections = struct.unpack_from(" 1) NotImplemented_nid_no_patch(__func__); return value; }\n" + "int APS5_VABI sceBlock(int value) { if (value) { NotImplemented_nid_no_patch(__func__); } return 0; }\n" + "int APS5_VABI sceElse(int value) { if (value) return 1; else Fail(); return 0; }\n" + "int APS5_VABI sceGuarded(int value) { Check(value); return value; }\n" + "int APS5_VABI sceLoop(int value) { for (int i = 0; i < value; ++i) Fail(); return 0; }\n" + "int APS5_VABI sceSwitch(int value) { switch (value) { case 0: return 0; default: Fail(); } return 1; }\n" + "int APS5_VABI sceTernary(int value) { return value ? value : NotImplemented_nid_no_patch(__func__); }\n" + "int APS5_VABI sceShort(int value) { return value && NotImplemented_nid_no_patch(__func__); }\n" + "int APS5_VABI sceLambda(int value) { auto f = [&]() { Fail(); }; return value; }\n" + "int APS5_VABI sceMember(int value) { if (value) Fail(); other.Fail(); return value; }\n" + "int APS5_VABI sceHost(int value) {\n#if defined(_WIN32)\n return value;\n#elif defined(__linux__)\n" + " return value + 1;\n#else\n NotImplemented_nid_no_patch(__func__);\n return 0;\n#endif\n}\n" + "int APS5_VABI sceHostHelper(int value) {\n#ifdef HOST_WITHOUT_IT\n Fail();\n#endif\n return value;\n}\n" + "#ifdef ONLY_HERE\nint APS5_VABI sceOuter(int value) { NotImplemented_nid_no_patch(__func__); return 0; }\n#endif\n" + "int APS5_VABI sceEveryHost(int value) {\n#if defined(_WIN32)\n NotImplemented_nid_no_patch(__func__);\n#else\n" + " NotImplemented_nid_no_patch(__func__);\n#endif\n return 0;\n}\n" + "int APS5_VABI sceEveryElif(int value) {\n#if defined(HOST_A)\n Fail();\n#elif defined(HOST_B)\n" + " NotImplemented_nid_no_patch(__func__);\n#else\n Fail();\n#endif\n return 0;\n}\n" + "int APS5_VABI sceNested(int value) {\n#if defined(HOST_A)\n#ifdef FEATURE\n return value;\n#else\n Fail();\n#endif\n" + "#else\n Fail();\n#endif\n return 0;\n}\n" + "void SoftFail(int value) { if (value == 0) return; NotImplemented_nid_no_patch(__func__); }\n" + "void HardFail(int value) { if (value == 0) throw std::invalid_argument(\"zero\"); NotImplemented_nid_no_patch(__func__); }\n" + "int APS5_VABI sceGuardReturn(int value) { if (value == 0) return 0; NotImplemented_nid_no_patch(__func__); return 1; }\n" + "int APS5_VABI sceGuardThrow(int value) { if (value == 0) throw std::invalid_argument(\"zero\"); " + "NotImplemented_nid_no_patch(__func__); return 1; }\n" + "int APS5_VABI sceGuardBlock(int value) { { if (value == 0) return 0; } Fail(); return 1; }\n" + "int APS5_VABI sceGuardGoto(int value) { if (value == 0) goto done; Fail(); done: return 0; }\n" + "int APS5_VABI sceGuardHost(int value) {\n#ifdef HOST_A\n if (value == 0) return 0;\n#endif\n Fail();\n return 1;\n}\n" + "int APS5_VABI sceSoftHelper(int value) { SoftFail(value); return 0; }\n" + "int APS5_VABI sceHardHelper(int value) { HardFail(value); return 0; }\n" + "int APS5_VABI scePlainBlock(int value) { { NotImplemented_nid_no_patch(__func__); } return 0; }\n" + "int APS5_VABI sceNestedBlock(int value) { value += 1; { { Fail(); } } return value; }\n" + "int APS5_VABI sceBlockThenGuard(int value) { { if (value == 0) return 0; NotImplemented_nid_no_patch(__func__); } return 1; }\n") + write_source(source, "libShared", '#include "prx/libGuard/Export.cpp"\n') + stubs, partial = import_audit.source_stub_classes(source) + names = ("sceDirect", "sceReturned", "sceWrapped", "sceIf", "sceBlock", "sceElse", "sceGuarded", + "sceLoop", "sceSwitch", "sceTernary", "sceShort", "sceLambda", "sceMember", "sceHost", "sceHostHelper", + "sceOuter", "sceEveryHost", "sceEveryElif", "sceNested", "sceGuardReturn", "sceGuardThrow", + "sceGuardBlock", "sceGuardGoto", "sceGuardHost", "sceSoftHelper", "sceHardHelper", + "scePlainBlock", "sceNestedBlock", "sceBlockThenGuard") + kinds = {name: "partial" if ("libGuard.prx", nid_names.compute_nid(name)) in partial else "stub" for name in names} + self.assertEqual({name for name, kind in kinds.items() if kind == "stub"}, + {"sceDirect", "sceReturned", "sceWrapped", "sceOuter", "sceEveryHost", "sceEveryElif", + "sceGuardThrow", "sceHardHelper", "scePlainBlock", "sceNestedBlock"}) + self.assertEqual(set(stubs), {(library, nid_names.compute_nid(name)) for library in ("libGuard.prx", "libShared.prx") for name in names}) + self.assertEqual(partial, {(library, nid_names.compute_nid(name)) for library in ("libGuard.prx", "libShared.prx") + for name, kind in kinds.items() if kind == "partial"}) + + def test_partial_import_is_its_own_class(self): + direct, guarded = nid_names.compute_nid("sceDirect"), nid_names.compute_nid("sceGuarded") + stubs = {("libA.prx", direct): "sceDirect", ("libA.prx", guarded): "sceGuarded", ("libB.prx", guarded): "sceGuarded"} + partial = {("libB.prx", guarded)} + exports = {direct: ["libA.prx"], guarded: ["libA.prx", "libB.prx"]} + imports = [(direct, "libA.prx", False), (guarded, "libA.prx", False), (guarded, "libA.prx", False)] + records, _ = import_audit.audit(imports, exports, stubs, {"libA.prx", "libB.prx"}, set(), partial=partial) + self.assertEqual({record["nid"]: (record["class"], record["name"]) for record in records}, + {direct: ("stub", "sceDirect"), guarded: ("partial", "sceGuarded")}) + summary = import_audit.summarize(records, 3, {}) + self.assertEqual(summary["references_by_class"], {"implemented": 0, "partial": 2, "stub": 1, "absent": 0, "module": 0}) + text = import_audit.render(summary, records, {}, []) + self.assertIn("Stub: exported but throws when called (1)", text) + self.assertIn("Partial: exported, throws only for inputs whose behaviour is unknown (1)", text) + def test_shared_source_cycle_fails(self): source = self.work.source for library, owner in (("libA", "libB"), ("libB", "libA")): @@ -1314,8 +1676,8 @@ class ImportAuditTests(unittest.TestCase): def test_elf_imports_reader(self): image = build_elf([("one", 1, GLOBAL_FUNCTION), ("two", 0, GLOBAL_FUNCTION), ("three", 0, 0x22)], ["libc.prx", "libSceZip.prx"]) path = Path("mod.prx") - self.assertEqual(import_audit.elf_imports(path, image), ({"two", "three"}, {"libc.prx", "libSceZip.prx"})) - self.assertEqual(import_audit.elf_imports(path, exporting()), (set(), set())) + self.assertEqual(import_audit.elf_imports(path, image), ({"two": False, "three": True}, {"libc.prx", "libSceZip.prx"})) + self.assertEqual(import_audit.elf_imports(path, exporting()), ({}, set())) for cut in (10, 64, len(image) - 5): with self.assertRaisesRegex(import_audit.AuditError, "mod.prx"): import_audit.elf_imports(path, image[:cut]) @@ -1358,6 +1720,61 @@ class ImportAuditTests(unittest.TestCase): with self.assertRaisesRegex(import_audit.AuditError, "lib.prx"): import_audit.pe_exports(path, image[:cut]) + def test_pe_without_an_export_directory_has_no_exports(self): + for magic, directory in ((0x10b, 96), (0x20b, 112)): + image = bytearray(build_pe([])) + struct.pack_into("{label}<", svg) + def test_main_writes_utf8_output_files(self): + point = {"date": "2026-01-01", "commit": "abc1234", + "libraries": {"done": 1, "total": 2, "percent": 50.0}, + "shaders": {"done": 3, "total": 4, "percent": 75.0}} + with tempfile.TemporaryDirectory() as directory: + output = Path(directory) / "site" + with patch.object(progress_history, "collect", return_value=[point]): + progress_history.main([str(output)]) + svg = (output / "history.svg").read_text(encoding="utf-8") + self.assertIn("\u25cf", svg) + html = (output / "history.html").read_text(encoding="utf-8") + self.assertIn("", html) + data = json.loads((output / "history.json").read_text(encoding="utf-8")) + self.assertEqual(data, [point]) + if __name__ == "__main__": diff --git a/tools/tests/test_select_tests.py b/tools/tests/test_select_tests.py index e86b16ccad..515dc16930 100644 --- a/tools/tests/test_select_tests.py +++ b/tools/tests/test_select_tests.py @@ -119,13 +119,108 @@ class SelectTests(unittest.TestCase): self.assertEqual(self.select("alpha/alpha.h", graph=older)[1], {"alpha", "alpha_data", "beta", "beta_setup", "script"}) def test_selected_target_skips_unselected_executables(self): - self.select("alpha/main.c") subprocess.run(["ninja", "-C", str(self.build), "-t", "clean"], check=True, capture_output=True) + self.select("alpha/main.c") subprocess.run(["ninja", "-C", str(self.build), "-f", "select.ninja", "selected"], check=True, capture_output=True) built = {p.stem for p in self.build.rglob("*_tests*") if p.is_file() and os.access(p, os.X_OK)} subprocess.run(["cmake", "--build", str(self.build)], check=True, capture_output=True) self.assertEqual(built, {"alpha_tests"}) + def test_fresh_build_keeps_a_shared_selected_executable(self): + with tempfile.TemporaryDirectory() as directory: + build = Path(directory) / "fresh" + compiler = shutil.which("gcc") or shutil.which("cc") + subprocess.run(["cmake", "-S", str(self.source), "-B", str(build), "-G", "Ninja", f"-DCMAKE_C_COMPILER={compiler}"], + check=True, capture_output=True) + changed = build / "changed.txt" + changed.write_text("data/input.txt\n", encoding="utf-8") + self.tool("select", build, self.graph, changed) + subprocess.run(["ninja", "-C", str(build), "-f", "select.ninja", "selected"], check=True, capture_output=True) + built = {path.stem for path in build.rglob("*_tests*") if path.is_file() and os.access(path, os.X_OK)} + self.assertEqual(built, {"alpha_tests"}) + output = self.tool("ctest", build, "--output-on-failure") + self.assertEqual(set(re.findall(r"Test +#\d+: (\S+)", output)), {"alpha_data", "script"}) + + def test_empty_selection_does_not_build_unselected_executables(self): + cmake = self.source / "CMakeLists.txt" + original = cmake.read_text(encoding="utf-8") + try: + cmake.write_text(original.replace('add_test(NAME script COMMAND ${CMAKE_COMMAND} -E true)\n', ''), encoding="utf-8") + subprocess.run(["cmake", "-S", str(self.source), "-B", str(self.build)], check=True, capture_output=True) + subprocess.run(["ninja", "-C", str(self.build), "-t", "clean"], check=True, capture_output=True) + self.assertEqual(self.select("README.md")[1], set()) + subprocess.run(["ninja", "-C", str(self.build), "-f", "select.ninja", "selected"], check=True, capture_output=True) + self.assertFalse(any(path.is_file() and os.access(path, os.X_OK) for path in self.build.rglob("*_tests*"))) + finally: + cmake.write_text(original, encoding="utf-8") + subprocess.run(["cmake", "-S", str(self.source), "-B", str(self.build)], check=True, capture_output=True) + subprocess.run(["cmake", "--build", str(self.build)], check=True, capture_output=True) + + def test_script_test_builds_the_executable_it_gets_as_a_definition(self): + cmake, script = self.source / "alpha/CMakeLists.txt", self.source / "data/run.cmake" + original = cmake.read_text(encoding="utf-8") + try: + script.write_text('execute_process(COMMAND "${FIXTURE}" RESULT_VARIABLE result)\n' + 'if(NOT result STREQUAL "0")\n message(FATAL_ERROR "fixture: ${result}")\nendif()\n', + encoding="utf-8") + cmake.write_text(original + "add_test(NAME alpha_run COMMAND ${CMAKE_COMMAND} -DFIXTURE=$" + " -P ${CMAKE_SOURCE_DIR}/data/run.cmake)\n", encoding="utf-8") + subprocess.run(["cmake", "-S", str(self.source), "-B", str(self.build)], check=True, capture_output=True) + subprocess.run(["ninja", "-C", str(self.build), "-t", "clean"], check=True, capture_output=True) + graph = self.build / "run-graph.json" + self.tool("dump", self.build, graph) + self.assertEqual(self.select("beta/main.c", graph=graph)[1], {"alpha_run", "beta", "beta_setup", "script"}) + subprocess.run(["ninja", "-C", str(self.build), "-f", "select.ninja", "selected"], check=True, capture_output=True) + output = self.tool("ctest", self.build, "-R", "alpha_run", "--output-on-failure") + self.assertIn("100% tests passed", output) + finally: + cmake.write_text(original, encoding="utf-8") + script.unlink(missing_ok=True) + subprocess.run(["cmake", "-S", str(self.source), "-B", str(self.build)], check=True, capture_output=True) + subprocess.run(["cmake", "--build", str(self.build)], check=True, capture_output=True) + + def test_script_test_builds_an_excluded_executable_it_gets_as_a_definition(self): + cmake, script = self.source / "alpha/CMakeLists.txt", self.source / "data/run.cmake" + original = cmake.read_text(encoding="utf-8") + try: + script.write_text('execute_process(COMMAND "${TOOL}" RESULT_VARIABLE result)\n' + 'if(NOT result STREQUAL "0")\n message(FATAL_ERROR "tool: ${result}")\nendif()\n', + encoding="utf-8") + cmake.write_text(original + "add_executable(alpha_tool EXCLUDE_FROM_ALL main.c)\n" + "add_test(NAME alpha_run COMMAND ${CMAKE_COMMAND} -DTOOL=$" + " -P ${CMAKE_SOURCE_DIR}/data/run.cmake)\n", encoding="utf-8") + subprocess.run(["cmake", "-S", str(self.source), "-B", str(self.build)], check=True, capture_output=True) + subprocess.run(["ninja", "-C", str(self.build), "-t", "clean"], check=True, capture_output=True) + graph = self.build / "run-graph.json" + self.tool("dump", self.build, graph) + self.assertEqual(self.select("beta/main.c", graph=graph)[1], {"alpha_run", "beta", "beta_setup", "script"}) + subprocess.run(["ninja", "-C", str(self.build), "-f", "select.ninja", "selected"], check=True, capture_output=True) + output = self.tool("ctest", self.build, "-R", "alpha_run", "--output-on-failure") + self.assertIn("100% tests passed", output) + finally: + cmake.write_text(original, encoding="utf-8") + script.unlink(missing_ok=True) + subprocess.run(["cmake", "-S", str(self.source), "-B", str(self.build)], check=True, capture_output=True) + subprocess.run(["cmake", "--build", str(self.build)], check=True, capture_output=True) + + def test_retargeted_test_does_not_prune_its_previous_executable(self): + cmake = self.source / "alpha/CMakeLists.txt" + original = cmake.read_text(encoding="utf-8") + try: + cmake.write_text(original.replace("add_executable(alpha_tests main.c)", + "add_executable(alpha_tests main.c)\nadd_executable(replacement_tests main.c)") + .replace("COMMAND alpha_tests", "COMMAND replacement_tests"), encoding="utf-8") + subprocess.run(["cmake", "-S", str(self.source), "-B", str(self.build)], check=True, capture_output=True) + subprocess.run(["ninja", "-C", str(self.build), "-t", "clean"], check=True, capture_output=True) + self.select("beta/main.c") + subprocess.run(["ninja", "-C", str(self.build), "-f", "select.ninja", "selected"], check=True, capture_output=True) + built = {path.stem for path in self.build.rglob("*_tests*") if path.is_file() and os.access(path, os.X_OK)} + self.assertEqual(built, {"alpha_tests", "beta_tests", "setup_tests"}) + finally: + cmake.write_text(original, encoding="utf-8") + subprocess.run(["cmake", "-S", str(self.source), "-B", str(self.build)], check=True, capture_output=True) + subprocess.run(["cmake", "--build", str(self.build)], check=True, capture_output=True) + def test_explain_reports_input_and_fixture_reasons_without_a_build_cache(self): changed = self.build / "explain-changed.txt" cache = self.build / "CMakeCache.txt" @@ -182,8 +277,8 @@ class FixtureBuildTests(unittest.TestCase): marker.unlink(missing_ok=True) listed = self.build / "changed.txt" listed.write_text(changed + "\n", encoding="utf-8") - SelectTests.tool("select", self.build, graph, listed) subprocess.run(["ninja", "-C", str(self.build), "-t", "clean"], check=True, capture_output=True) + SelectTests.tool("select", self.build, graph, listed) subprocess.run(["ninja", "-C", str(self.build), "-f", "select.ninja", "selected"], check=True, capture_output=True) result = subprocess.run([sys.executable, str(TOOL), "ctest", str(self.build), "--output-on-failure"], capture_output=True, text=True) @@ -207,6 +302,31 @@ class FixtureBuildTests(unittest.TestCase): self.assertIn(name, output) +@unittest.skipUnless(shutil.which("ninja") and (shutil.which("gcc") or shutil.which("cc")), "needs ninja and a C compiler") +class NonAsciiDirectoryTests(unittest.TestCase): + def test_header_under_a_non_ascii_directory_is_an_input_of_its_tests(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) / "ação" + if os.name == "nt": + try: + str(root).encode("mbcs", "strict") + except UnicodeEncodeError: + self.skipTest("the ANSI code page cannot represent the fixture directory") + source, build, graph = root / "source", root / "build", root / "graph.json" + for name, content in PROJECT.items(): + (source / name).parent.mkdir(parents=True, exist_ok=True) + (source / name).write_text(content, encoding="utf-8") + compiler = shutil.which("gcc") or shutil.which("cc") + subprocess.run(["cmake", "-S", str(source), "-B", str(build), "-G", "Ninja", f"-DCMAKE_C_COMPILER={compiler}"], + check=True, capture_output=True) + subprocess.run(["cmake", "--build", str(build)], check=True, capture_output=True) + SelectTests.tool("dump", build, graph) + reasons = {} + select_tests.select(json.loads(graph.read_text(encoding="utf-8")), ["alpha/alpha.h"], reasons) + self.assertIn("input: alpha/alpha.h", reasons["alpha"]) + self.assertIn("input: alpha/alpha.h", reasons["alpha_data"]) + + class BuildErrorTests(unittest.TestCase): def run_tool(self, *args): return subprocess.run([sys.executable, "-I", str(TOOL), *map(str, args)], capture_output=True, text=True, timeout=60) @@ -230,6 +350,40 @@ class BuildErrorTests(unittest.TestCase): self.assertIn("CMAKE_CACHEFILE_DIR", result.stderr) self.assertNotIn("Traceback", result.stderr) + def test_cache_with_utf8_bom_is_read(self): + with tempfile.TemporaryDirectory() as directory: + cache = Path(directory) / "CMakeCache.txt" + cache.write_text("\ufeffCMAKE_CACHEFILE_DIR:INTERNAL=C:/work/build\nCMAKE_HOME_DIRECTORY:INTERNAL=C:/work/source\n", encoding="utf-8") + with mock.patch.object(select_tests.Build, "run", return_value="Build file encoding: UTF-8"): + build = select_tests.Build(directory) + self.assertEqual(build.path, os.path.normpath("C:/work/build")) + self.assertEqual(build.source, os.path.normpath("C:/work/source")) + + def test_explain_accepts_utf8_bom_in_graph_and_changed(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + graph_path = root / "graph.json" + graph = { + "tests": { + "unit": { + "target": "tests/unit", + "arguments": [], + "registered": [], + "sets": [], + "requires": [], + "depends": [], + } + }, + "closures": {"tests/unit": ["src/main.c"]}, + } + graph_path.write_text("\ufeff" + json.dumps(graph), encoding="utf-8") + changed_path = root / "changed.txt" + changed_path.write_text("\ufeffsrc/main.c\n", encoding="utf-8") + result = self.run_tool("explain", graph_path, changed_path) + self.assertEqual(result.returncode, 0, result.stderr) + data = json.loads(result.stdout) + self.assertEqual(data["tests"], ["unit"]) + class CaseInsensitivePathTests(unittest.TestCase): def build(self): @@ -257,7 +411,61 @@ class CaseInsensitivePathTests(unittest.TestCase): self.assertIsNone(build.repo(os.path.join(os.sep, "WORK", "SOURCE", "BUILD", "Generated.h"))) +class ChunkTests(unittest.TestCase): + def test_windows_batches_fit_the_serialized_utf16_budget(self): + cases = ( + (20000, ["\U0001f600" * 50 + f"/{index}.obj" for index in range(400)]), + (64, ['space name\\', 'embedded"quote', "\t", "", 'both \\" slash\\'] * 20), + (64, ["\ud800" * 10, "\U0001f600" * 10] * 20), + ) + for budget, names in cases: + with self.subTest(budget=budget, first=names[0]), mock.patch.object(select_tests.sys, "platform", "win32"), \ + mock.patch.object(select_tests, "BATCH", budget): + batches = list(select_tests.chunks(iter(names))) + self.assertEqual([name for batch in batches for name in batch], names) + self.assertGreater(len(batches), 1) + for batch in batches: + self.assertTrue(batch) + command = subprocess.list2cmdline(batch) + self.assertLessEqual(len(command.encode("utf-16-le", "surrogatepass")) // 2 + 1, budget) + + def test_windows_batch_keeps_an_exact_boundary(self): + with mock.patch.object(select_tests.sys, "platform", "win32"), mock.patch.object(select_tests, "BATCH", 8): + self.assertEqual(list(select_tests.chunks(["a b", "c", "d"])), [["a b", "c"], ["d"]]) + + def test_other_platforms_keep_the_existing_character_budget(self): + names = ["\U0001f600" * 3, "abc", "\udcff"] + for platform in ("linux", "darwin"): + with self.subTest(platform=platform), mock.patch.object(select_tests.sys, "platform", platform), \ + mock.patch.object(select_tests, "BATCH", 8): + self.assertEqual(list(select_tests.chunks(iter(names))), [names[:2], names[2:]]) + + def test_empty_input_and_an_oversized_single_argument_are_preserved(self): + for platform in ("win32", "linux"): + with self.subTest(platform=platform), mock.patch.object(select_tests.sys, "platform", platform), \ + mock.patch.object(select_tests, "BATCH", 4): + self.assertEqual(list(select_tests.chunks(iter([]))), []) + self.assertEqual(list(select_tests.chunks(["long target", "x"])), [["long target"], ["x"]]) + + @unittest.skipUnless(sys.platform == "win32", "needs the Windows command line limit") + def test_windows_batches_launch_without_losing_arguments(self): + names = ["\U0001f600" * 50 + f"/{index}.obj" for index in range(400)] + for batch in select_tests.chunks(names): + result = subprocess.run([sys.executable, "-I", "-c", "import json,sys; print(json.dumps(sys.argv[1:]))", *batch], + capture_output=True, text=True, check=True, timeout=5) + self.assertEqual(json.loads(result.stdout), batch) + + class PlanTests(unittest.TestCase): + def test_unresolved_selected_command_keeps_unselected_build_targets(self): + build = mock.Mock() + listed = [{"name": "selected"}, {"name": "other", "command": ["/build/tests/shared_tests"]}] + build.tests.side_effect = [({}, listed), ({}, [listed[0]])] + build.leaves.return_value = ["tests/shared_tests", "helper"] + build.target.side_effect = lambda test: "tests/shared_tests" if test.get("command") else None + graph = {"tests": {"selected": {}, "other": {}}} + self.assertEqual(select_tests.plan(build, graph, {"selected"}), ([1], ["tests/shared_tests", "helper"], 2)) + def test_plan_empty_run_avoids_invoking_ctest(self): build = mock.Mock() build.tests.return_value = ({}, [{"name": "alpha", "command": ["/build/alpha_tests"]}]) @@ -271,5 +479,63 @@ class PlanTests(unittest.TestCase): build.tests.assert_called_once_with() +class MissingCommandTests(unittest.TestCase): + def test_only_unambiguous_absolute_commands_in_reachable_files_are_recovered(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + active, stale = root / "active tests", root / "stale" + active.mkdir() + stale.mkdir() + executable = (root / "tests/unit_tests").as_posix() + helper = (root / "tests/helper_tests").as_posix() + (root / "CTestTestfile.cmake").write_text('subdirs("active tests")\n', encoding="utf-8") + (active / "CTestTestfile.cmake").write_text( + f'add_test([=[absolute]=] "{executable}" "{helper}")\n' + 'add_test(relative tests/relative_tests)\n' + 'add_test(path unit_tests)\n' + f'if(CONFIG)\nadd_test(ambiguous "{executable}")\nelse()\nadd_test(ambiguous "{helper}")\nendif()\n' + f'add_test(existing "{executable}")\n' + 'subdirs("..")\n', encoding="utf-8") + (stale / "CTestTestfile.cmake").write_text(f'add_test(stale "{executable}")\n', encoding="utf-8") + names = ("absolute", "relative", "path", "ambiguous", "existing", "stale") + listed = [{"name": name} for name in names] + listed[4]["command"] = [helper] + build = object.__new__(select_tests.Build) + build.path = str(root) + with mock.patch.object(select_tests.subprocess, "run", return_value=mock.Mock(stdout=json.dumps({"tests": listed}))): + _, recovered = build.tests() + commands = {test["name"]: test.get("command") for test in recovered} + self.assertEqual(commands, {"absolute": [executable, helper], "relative": None, "path": None, + "ambiguous": None, "existing": [helper], "stale": None}) + + + def test_main_reconfigures_streams(self): + class Stream: + def __init__(self): + self.encoding = None + self.errors = None + + def reconfigure(self, encoding=None, errors=None): + self.encoding = encoding + self.errors = errors + + def write(self, text): + pass + + def flush(self): + pass + + out, err = Stream(), Stream() + with mock.patch.object(select_tests.sys, "stdout", out), mock.patch.object(select_tests.sys, "stderr", err): + try: + select_tests.main([]) + except SystemExit: + pass + self.assertEqual(out.encoding, "utf-8") + self.assertEqual(out.errors, "backslashreplace") + self.assertEqual(err.encoding, "utf-8") + self.assertEqual(err.errors, "backslashreplace") + + if __name__ == "__main__": unittest.main() diff --git a/tools/tests/test_shader_cache_runtime_sources.py b/tools/tests/test_shader_cache_runtime_sources.py index 0c462bb7ea..eec57f2fb3 100644 --- a/tools/tests/test_shader_cache_runtime_sources.py +++ b/tools/tests/test_shader_cache_runtime_sources.py @@ -11,8 +11,13 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1])) import select_tests -SOURCE = Path(sys.argv.pop(1)) -CMAKE = sys.argv.pop(1) +EXPLICIT_ARGS = len(sys.argv) >= 3 and Path(sys.argv[1]).is_dir() +if EXPLICIT_ARGS: + SOURCE = Path(sys.argv.pop(1)) + CMAKE = sys.argv.pop(1) +else: + SOURCE = Path(__file__).resolve().parents[2] / 'core/shader/recompiler' + CMAKE = shutil.which('cmake') or 'cmake' class Fixture: @@ -29,6 +34,9 @@ class Fixture: header = root / 'core/libs/prx/libc/include/HostThreadLocal.hpp' header.parent.mkdir(parents=True) header.write_text('struct FixtureThreadLocal {};\n', encoding='utf-8') + shared = root / 'core/libs/prx/libc/include/general/AtomicSharedPtr.hpp' + shared.parent.mkdir(parents=True) + shared.write_text('struct FixtureAtomicSharedPtr {};\n', encoding='utf-8') self.guard = root / 'driver.cpp' self.guard.write_text('int fixture_driver;\n', encoding='utf-8') compiler = root / ('compiler.cmd' if os.name == 'nt' else 'compiler') @@ -70,7 +78,7 @@ file(WRITE "${{CMAKE_CURRENT_BINARY_DIR}}/runtime-sources.txt" "${{runtimeSource def configure(self, flags=''): result = subprocess.run([CMAKE, '-S', str(self.root), '-B', str(self.build), '-G', 'Ninja', f'-DCMAKE_CXX_FLAGS={flags}'], - capture_output=True, encoding='utf-8', errors='replace', timeout=15) + capture_output=True, encoding='utf-8', errors='replace', timeout=120) if result.returncode: raise AssertionError(result.stdout + result.stderr) if (self.root / 'compiler-invoked').exists(): @@ -93,7 +101,7 @@ file(WRITE "${{CMAKE_CURRENT_BINARY_DIR}}/runtime-sources.txt" "${{runtimeSource f'-DRECOMPILER_DIR={self.recompiler.as_posix()}', f'-DLIST_FILE={self.listing.as_posix()}', f'-DOUTPUT={output.as_posix()}', f'-DEXTRA={self.extra}', '-P', str(self.recompiler / 'ShaderCacheVersion.cmake')], - capture_output=True, encoding='utf-8', errors='replace', timeout=10) + capture_output=True, encoding='utf-8', errors='replace', timeout=60) if not success: if result.returncode == 0 or output.exists(): raise AssertionError('The generator accepted an unkeyed dependency') @@ -106,6 +114,7 @@ file(WRITE "${{CMAKE_CURRENT_BINARY_DIR}}/runtime-sources.txt" "${{runtimeSource return {Path(line.split(' ', 1)[1]) for line in self.listing.read_text(encoding='utf-8').splitlines() if line.startswith('sources ')} +@unittest.skipUnless(EXPLICIT_ARGS or (shutil.which('ninja') and shutil.which(CMAKE) and SOURCE.is_dir()), 'ninja, cmake, and shader recompiler source required') class ShaderCacheRuntimeSourceTests(unittest.TestCase): def fixture(self, spirv_tools=False): temporary = tempfile.TemporaryDirectory(prefix='shader_cache_sources_') @@ -138,7 +147,7 @@ class ShaderCacheRuntimeSourceTests(unittest.TestCase): 'if(NOT result EQUAL 99)', 'message(FATAL_ERROR "Unexpected compiler arguments were accepted")', 'endif()'] script.write_text('\n'.join(lines) + '\n', encoding='utf-8') result = subprocess.run([CMAKE, '-P', str(script)], capture_output=True, - encoding='utf-8', errors='replace', timeout=10) + encoding='utf-8', errors='replace', timeout=60) self.assertEqual(result.returncode, 0, result.stdout + result.stderr) self.assertEqual(len(marker.read_text(encoding='utf-8').splitlines()), len(rejected)) @@ -167,17 +176,33 @@ class ShaderCacheRuntimeSourceTests(unittest.TestCase): fixture.configure() self.assertEqual(fixture.version(), previous) - def test_runtime_sources_headers_and_cmake_change_version(self): + def assertEditChangesVersion(self, name): fixture = self.fixture() previous = fixture.version() - for name in ('Recompiler.cpp', 'tests/DummyShaders.cpp', 'tests/DummyShaders.hpp', 'BdaAbi.hpp', 'CMakeLists.txt', 'ShaderCacheVersion.cmake'): - with self.subTest(source=name): - path = fixture.recompiler / name - before = path.read_bytes() - path.write_bytes(before + b'\n') - self.assertNotEqual(fixture.version(), previous) - path.write_bytes(before) - self.assertEqual(fixture.version(), previous) + path = fixture.recompiler / name + before = path.read_bytes() + path.write_bytes(before + b'\n') + self.assertNotEqual(fixture.version(), previous) + path.write_bytes(before) + self.assertEqual(fixture.version(), previous) + + def test_runtime_source_changes_version(self): + self.assertEditChangesVersion('Recompiler.cpp') + + def test_compiled_test_source_changes_version(self): + self.assertEditChangesVersion('tests/DummyShaders.cpp') + + def test_compiled_test_header_changes_version(self): + self.assertEditChangesVersion('tests/DummyShaders.hpp') + + def test_runtime_header_changes_version(self): + self.assertEditChangesVersion('BdaAbi.hpp') + + def test_cmake_list_changes_version(self): + self.assertEditChangesVersion('CMakeLists.txt') + + def test_version_script_changes_version(self): + self.assertEditChangesVersion('ShaderCacheVersion.cmake') def test_flags_change_version(self): fixture = self.fixture() @@ -213,4 +238,45 @@ class ShaderCacheRuntimeSourceTests(unittest.TestCase): self.assertIn('has not been reviewed', fixture.version(False)) -unittest.main() +def iter_test_cases(suite): + for test in suite: + if isinstance(test, unittest.TestSuite): + yield from iter_test_cases(test) + else: + yield test + + +def shard_suite(index, count): + if count <= 0 or index < 0 or index >= count: + raise ValueError('shard index must be within the shard count') + tests = sorted(iter_test_cases(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])), key=lambda test: test.id()) + return unittest.TestSuite(test for position, test in enumerate(tests) if position % count == index) + + +class ShardTests(unittest.TestCase): + def test_shards_run_each_case_once(self): + discovered = sorted(test.id() for test in iter_test_cases(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]))) + for count in range(1, len(discovered) + 2): + with self.subTest(count=count): + sharded = [test.id() for index in range(count) for test in iter_test_cases(shard_suite(index, count))] + self.assertEqual(sorted(sharded), discovered) + + def test_invalid_shard_is_rejected(self): + for index, count in ((0, 0), (-1, 2), (2, 2)): + with self.subTest(index=index, count=count), self.assertRaises(ValueError): + shard_suite(index, count) + + +if __name__ == '__main__': + if len(sys.argv) > 1 and sys.argv[1] == '--shard': + if len(sys.argv) != 3 or '/' not in sys.argv[2]: + print('usage: test_shader_cache_runtime_sources.py [SOURCE CMAKE] --shard INDEX/COUNT', file=sys.stderr) + raise SystemExit(2) + try: + shard_index, shard_count = (int(part) for part in sys.argv[2].split('/', 1)) + suite = shard_suite(shard_index, shard_count) + except ValueError as error: + print(f'invalid shard: {error}', file=sys.stderr) + raise SystemExit(2) + raise SystemExit(0 if unittest.TextTestRunner().run(suite).wasSuccessful() else 1) + unittest.main() diff --git a/tools/tests/test_stubgen.py b/tools/tests/test_stubgen.py index bc4bf37488..41fab273fb 100644 --- a/tools/tests/test_stubgen.py +++ b/tools/tests/test_stubgen.py @@ -6,6 +6,7 @@ import tempfile from pathlib import Path import sys import unittest +from unittest.mock import patch sys.path.insert(0, str(Path(__file__).resolve().parents[1])) import check_conventions @@ -41,6 +42,34 @@ class StubgenTests(unittest.TestCase): class StubGeneratorTests(unittest.TestCase): + def test_long_output_names(self): + import subprocess + with tempfile.TemporaryDirectory() as directory: + root = Path(directory).resolve() + if os.name == "nt": + root = Path("\\\\?\\" + str(root)) + limit = os.pathconf(root, "PC_NAME_MAX") if os.name == "posix" else 255 + nids = root / "nids.txt" + nids.write_text("AAAAAAAAAAA\n", encoding="utf-8") + expected = stubgen.generate("libSceExample", ["AAAAAAAAAAA"], {}) + for option, extra, content in (("--out", [], expected[0]), + ("--td-out", ["--td-only"], expected[1])): + for existing in (False, True): + with self.subTest(option=option, existing=existing): + output = root / ("a" * (limit - 4) + ".txt") + output.write_bytes(b"previous output\n") + if not existing: + output.unlink() + result = subprocess.run( + [sys.executable, str(Path(stubgen.__file__)), "--module", "libSceExample", + "--nids", str(nids), option, str(output), *extra], + capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(output.read_text(encoding="utf-8"), content) + self.assertEqual(nids.read_text(encoding="utf-8"), "AAAAAAAAAAA\n") + self.assertEqual(set(root.iterdir()), {nids, output}) + output.unlink() + @unittest.skipUnless(os.name == "nt", "Windows denies replacing read-only output files") def test_readonly_second_output_preserves_both_outputs(self): import subprocess @@ -200,5 +229,37 @@ class ReadOnlyOutputTests(unittest.TestCase): self.assertEqual(sorted(path.name for path in root.iterdir()), ["Export.cpp", "nids.txt"]) + def test_main_reconfigures_streams(self): + class Stream: + def __init__(self): + self.encoding = None + self.errors = None + self.newline = None + + def reconfigure(self, encoding=None, errors=None, newline=None): + self.encoding = encoding + self.errors = errors + self.newline = newline + + def write(self, text): + pass + + def flush(self): + pass + + out, err = Stream(), Stream() + with patch.object(stubgen.sys, "stdout", out), patch.object(stubgen.sys, "stderr", err): + try: + stubgen.main([]) + except SystemExit: + pass + self.assertEqual(out.encoding, "utf-8") + self.assertEqual(out.errors, "backslashreplace") + self.assertEqual(out.newline, "\n") + self.assertEqual(err.encoding, "utf-8") + self.assertEqual(err.errors, "backslashreplace") + self.assertEqual(err.newline, "\n") + + if __name__ == "__main__": unittest.main() diff --git a/tools/tests/test_suggest_patches.py b/tools/tests/test_suggest_patches.py new file mode 100644 index 0000000000..f24f84b86d --- /dev/null +++ b/tools/tests/test_suggest_patches.py @@ -0,0 +1,374 @@ +import contextlib +import hashlib +import io +import json +import os +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +import suggest_patches + +VERIFIED = "Verified 1 patches: 1 ok, 0 not-checked, 0 mismatch\n" + + +class PatchAdvisorTests(unittest.TestCase): + def setUp(self): + self.directory = tempfile.TemporaryDirectory() + self.addCleanup(self.directory.cleanup) + self.root = Path(self.directory.name) + self.catalog = self.root / "catalog" + self.catalog.mkdir() + self.input = self.root / "eboot.bin" + self.input.write_bytes(b"main executable") + self.module = self.root / "Media" / "Modules" / "module.prx" + self.module.parent.mkdir(parents=True) + self.module.write_bytes(b"module version one") + self.param = self.root / "param.json" + self.param.write_text(json.dumps({"titleId": "TEST00001", "contentVersion": "01.000.000"})) + self.relinker = self.root / "relinker" + self.relinker.write_bytes(b"fixture") + (self.catalog / "patch.toml").write_text('format = 1\n[[patch]]\ntarget = "Media/Modules/module.prx"\nva = 0x1000\nexpected = "00"\nvalue = "01"\n') + self.entry = {"titleId": "TEST00001", "contentVersion": "01.000.000", "patch": "patch.toml", + "targets": {"Media/Modules/module.prx": hashlib.sha256(self.module.read_bytes()).hexdigest()}} + self.write_index() + + def write_index(self, entries=None): + (self.catalog / "index.json").write_text(json.dumps({"format": 1, "entries": entries if entries is not None else [self.entry]})) + + def advise(self, *extra): + return suggest_patches.main(["--catalog", str(self.catalog), "--param-json", str(self.param), + "--input", str(self.input), "--relinker", str(self.relinker), + "--module-dir", "Media/Modules", *extra]) + + def additional_executable(self): + executable = self.root / "picker" / "picker.bin" + executable.parent.mkdir() + executable.write_bytes(b"secondary executable") + (self.catalog / "patch.toml").write_text('format = 1\n[[patch]]\ntarget = "picker/picker.bin"\nva = 0x1000\nexpected = "00"\nvalue = "01"\n') + self.entry["targets"] = {"picker/picker.bin": hashlib.sha256(executable.read_bytes()).hexdigest()} + self.write_index() + return executable + + @patch.object(suggest_patches.subprocess, "run") + def test_verified_match_requires_explicit_application(self, run): + run.return_value.returncode = 0 + run.return_value.stdout = VERIFIED + self.assertEqual(self.advise(), 0) + self.assertEqual(run.call_args.args[0], [str(self.relinker), "--verify-patches", + str(self.catalog / "patch.toml"), "--module-dir", "Media/Modules", + str(self.input)]) + self.assertFalse((self.root / "relinker-patches.toml").exists()) + + def test_relinker_output_is_utf8_independent_of_host_encoding(self): + actual_run = subprocess.run + for diagnostic in ("caf\u00e9 \u65e5\u672c\u8a9e".encode(), b"invalid byte \xff"): + for status in (0, 2): + with self.subTest(diagnostic=diagnostic, status=status): + stdout = VERIFIED.encode() + diagnostic + b"\n" + stderr = diagnostic + b"\n" + captured = [] + output, errors = io.StringIO(), io.StringIO() + + def verify(command, **options): + script = f"import os; os.write(1, {stdout!r}); os.write(2, {stderr!r}); raise SystemExit({status})" + options.setdefault("encoding", "ascii") + result = actual_run([sys.executable, "-c", script], **options) + captured.append(result) + return result + + with contextlib.redirect_stdout(output), contextlib.redirect_stderr(errors), \ + patch.object(suggest_patches.subprocess, "run", side_effect=verify): + self.assertEqual(self.advise(), status) + self.assertEqual(captured[0].stdout, stdout.decode("utf-8", errors="replace")) + self.assertEqual(captured[0].stderr, stderr.decode("utf-8", errors="replace")) + if status == 2: + self.assertIn("relinker error (exit 2)", output.getvalue()) + self.assertIn((stdout + stderr).decode("utf-8", errors="replace").strip(), output.getvalue()) + self.assertIn("No recommendation: relinker errors occurred", errors.getvalue()) + + @patch.object(suggest_patches.subprocess, "run") + def test_output_streams_use_utf8(self, run): + catalog = self.root / "caf\u00e9" + self.catalog.rename(catalog) + self.catalog = catalog + self.param = self.root / "caf\u00e9.json" + self.param.write_text(json.dumps({"titleId": "TEST00001", "contentVersion": "01.000.000"})) + stdout = io.TextIOWrapper(io.BytesIO(), encoding="ascii") + stderr = io.TextIOWrapper(io.BytesIO(), encoding="ascii") + self.addCleanup(stdout.close) + self.addCleanup(stderr.close) + run.return_value = subprocess.CompletedProcess([], 0, VERIFIED, "") + with patch.object(suggest_patches.sys, "stdout", stdout), patch.object(suggest_patches.sys, "stderr", stderr): + self.assertEqual(self.advise(), 0) + stdout.flush() + self.assertIn(b"Recommended:", stdout.buffer.getvalue()) + self.assertIn(str(self.catalog).encode("utf-8"), stdout.buffer.getvalue()) + self.param.unlink() + self.assertEqual(self.advise(), 2) + stderr.flush() + self.assertIn(self.param.name.encode("utf-8"), stderr.buffer.getvalue()) + for stream in (stdout, stderr): + self.assertEqual(stream.encoding, "utf-8") + self.assertEqual(stream.errors, "backslashreplace") + + @patch.object(suggest_patches.subprocess, "run") + def test_changed_module_is_not_sent_to_relinker(self, run): + self.module.write_bytes(b"module version two") + self.assertEqual(self.advise(), 1) + run.assert_not_called() + + @patch.object(suggest_patches.subprocess, "run") + def test_relinker_rejection_is_not_a_recommendation(self, run): + run.return_value.returncode = 1 + run.return_value.stdout = "Mismatch: patch\nVerified 1 patches: 0 ok, 0 not-checked, 1 mismatch\n" + run.return_value.stderr = "" + self.assertEqual(self.advise(), 1) + + @patch.object(suggest_patches.subprocess, "run") + def test_catalog_cannot_escape_directory(self, run): + self.entry["patch"] = "../outside.toml" + self.write_index() + self.assertEqual(self.advise(), 1) + run.assert_not_called() + + @patch.object(suggest_patches.subprocess, "run") + def test_hashes_cover_all_patch_targets(self, run): + self.entry["targets"] = {"eboot.bin": hashlib.sha256(self.input.read_bytes()).hexdigest()} + self.write_index() + self.assertEqual(self.advise(), 1) + run.assert_not_called() + + @patch.object(suggest_patches.subprocess, "run") + def test_additional_executable_is_hashed_and_verified(self, run): + self.additional_executable() + run.return_value.returncode = 0 + run.return_value.stdout = VERIFIED + self.assertEqual(self.advise("--executable", "picker/picker.bin"), 0) + self.assertEqual(run.call_args.args[0], [str(self.relinker), "--verify-patches", + str(self.catalog / "patch.toml"), "--module-dir", "Media/Modules", + "--executable", "picker/picker.bin", str(self.input)]) + + @patch.object(suggest_patches.subprocess, "run") + def test_changed_additional_executable_is_not_verified(self, run): + executable = self.additional_executable() + executable.write_bytes(b"different executable") + self.assertEqual(self.advise("--executable", "picker/picker.bin"), 1) + run.assert_not_called() + + @patch.object(suggest_patches.subprocess, "run") + def test_additional_executable_cannot_escape_module_parent(self, run): + self.assertEqual(self.advise("--executable", "../outside.bin"), 2) + run.assert_not_called() + + @patch.object(suggest_patches.subprocess, "run") + def test_unrelated_malformed_patch_does_not_block_recommendation(self, run): + bad = {"titleId": "OTHER0001", "contentVersion": "01.000.000", "patch": "missing.toml", "targets": {}} + (self.catalog / "index.json").write_text(json.dumps({"format": 1, "entries": [bad, self.entry]})) + run.return_value.returncode = 0 + run.return_value.stdout = VERIFIED + self.assertEqual(self.advise(), 0) + run.assert_called_once() + + @patch.object(suggest_patches.subprocess, "run") + def test_matching_malformed_patch_is_rejected(self, run): + self.entry["patch"] = "missing.toml" + self.write_index() + self.assertEqual(self.advise(), 1) + run.assert_not_called() + + @patch.object(suggest_patches.subprocess, "run") + def test_relinker_timeout_is_not_a_recommendation(self, run): + run.side_effect = subprocess.TimeoutExpired([str(self.relinker)], 7) + self.assertEqual(self.advise("--timeout-seconds", "7"), 1) + self.assertEqual(run.call_args.kwargs["timeout"], 7) + + @patch.object(suggest_patches.subprocess, "run") + def test_broken_matching_entry_does_not_block_valid_entry(self, run): + bad = dict(self.entry, patch="broken.toml") + (self.catalog / "broken.toml").write_text("format = @") + self.write_index([bad, self.entry]) + run.return_value.returncode = 0 + run.return_value.stdout = VERIFIED + output = io.StringIO() + with contextlib.redirect_stdout(output): + self.assertEqual(self.advise(), 0) + self.assertIn("Catalog entry 1 ('broken.toml'): rejected", output.getvalue()) + self.assertIn("Recommended:", output.getvalue()) + run.assert_called_once() + + @patch.object(suggest_patches.subprocess, "run") + def test_timeout_does_not_block_valid_entry(self, run): + self.write_index([self.entry, self.entry]) + run.side_effect = [subprocess.TimeoutExpired([str(self.relinker)], 7), subprocess.CompletedProcess([], 0, VERIFIED, "")] + self.assertEqual(self.advise("--timeout-seconds", "7"), 0) + self.assertEqual(run.call_count, 2) + + @patch.object(suggest_patches.subprocess, "run") + def test_relinker_error_without_verified_entries_exits_two(self, run): + run.return_value.returncode = 2 + run.return_value.stdout = "" + run.return_value.stderr = "FAIL: broken input" + self.assertEqual(self.advise(), 2) + + @patch.object(suggest_patches.subprocess, "run") + def test_relinker_error_does_not_block_verified_entry(self, run): + self.write_index([self.entry, self.entry]) + run.side_effect = [subprocess.CompletedProcess([], 2, "", "FAIL: invalid patch"), + subprocess.CompletedProcess([], 0, VERIFIED, "")] + output = io.StringIO() + with contextlib.redirect_stdout(output): + self.assertEqual(self.advise(), 0) + self.assertIn("Catalog entry 1 ('patch.toml'): relinker error (exit 2)", output.getvalue()) + self.assertIn("Recommended:", output.getvalue()) + self.assertEqual(run.call_count, 2) + + @patch.object(suggest_patches.subprocess, "run") + def test_relinker_error_and_mismatch_exit_two(self, run): + self.write_index([self.entry, self.entry]) + run.side_effect = [subprocess.CompletedProcess([], 2, "", "FAIL: invalid patch"), + subprocess.CompletedProcess([], 1, "Verified 1 patches: 0 ok, 0 not-checked, 1 mismatch\n", "")] + self.assertEqual(self.advise(), 2) + + @patch.object(suggest_patches.subprocess, "run") + def test_multiple_verified_entries_are_ambiguous(self, run): + self.write_index([self.entry, self.entry]) + run.return_value.returncode = 0 + run.return_value.stdout = VERIFIED + output = io.StringIO() + with contextlib.redirect_stdout(output): + self.assertEqual(self.advise(), 1) + self.assertIn("multiple verified catalog entries match; use --list-verified to see them.", output.getvalue()) + self.assertEqual(run.call_count, 2) + + @patch.object(suggest_patches.subprocess, "run") + def test_list_verified_shows_each_match_and_quoted_patch_path(self, run): + second = self.catalog / "second patch.toml" + second.write_bytes((self.catalog / "patch.toml").read_bytes()) + self.write_index([self.entry, dict(self.entry, patch=second.name)]) + run.return_value.returncode = 0 + run.return_value.stdout = VERIFIED + output = io.StringIO() + with contextlib.redirect_stdout(output): + self.assertEqual(self.advise("--list-verified"), 0) + self.assertIn("Verified catalog entries: 2", output.getvalue()) + self.assertIn("Choose one patch file; listed entries are not combined.", output.getvalue()) + self.assertIn(f"Catalog entry 1: {self.catalog / 'patch.toml'}", output.getvalue()) + self.assertIn(f"Catalog entry 2: {second}", output.getvalue()) + quoted = f'"{second}"' if os.name == "nt" else f"'{second}'" + self.assertIn(f"--patches {quoted} --module-dir Media/Modules", output.getvalue()) + self.assertEqual(output.getvalue().count("SHA-256"), 2) + self.assertNotIn("Recommended:", output.getvalue()) + self.assertEqual(run.call_count, 2) + + @patch.object(suggest_patches.subprocess, "run") + def test_list_verified_single_match_preserves_conversion_options(self, run): + self.additional_executable() + run.return_value.returncode = 0 + run.return_value.stdout = VERIFIED + output = io.StringIO() + with contextlib.redirect_stdout(output): + self.assertEqual(self.advise("--list-verified", "--executable", "picker/picker.bin", + "--sce-module-path", str(self.root)), 0) + expected = suggest_patches.command_line(["--patches", str(self.catalog / "patch.toml"), + "--sce-module-path", str(self.root), "--module-dir", + "Media/Modules", "--executable", "picker/picker.bin"]) + self.assertIn(expected, output.getvalue()) + self.assertIn("Verified catalog entries: 1", output.getvalue()) + self.assertIn("Choose one patch file; listed entries are not combined.", output.getvalue()) + self.assertNotIn("Recommended:", output.getvalue()) + + @patch.object(suggest_patches.subprocess, "run") + def test_list_verified_skips_individual_errors(self, run): + bad = dict(self.entry, patch="missing.toml") + wrong_hash = dict(self.entry, targets={"Media/Modules/module.prx": "0" * 64}) + self.write_index([bad, wrong_hash, self.entry, self.entry, self.entry]) + run.side_effect = [subprocess.CompletedProcess([], 2, "", "FAIL: invalid patch"), + subprocess.CompletedProcess([], 1, "Verified 1 patches: 0 ok, 0 not-checked, 1 mismatch\n", ""), + subprocess.CompletedProcess([], 0, VERIFIED, "")] + output = io.StringIO() + with contextlib.redirect_stdout(output): + self.assertEqual(self.advise("--list-verified"), 0) + self.assertIn("Catalog entry 1 ('missing.toml'): rejected", output.getvalue()) + self.assertIn("Catalog entry 2 ('patch.toml'): rejected: Media/Modules/module.prx: SHA-256 mismatch", output.getvalue()) + self.assertIn("Catalog entry 3 ('patch.toml'): relinker error (exit 2)", output.getvalue()) + self.assertIn("Catalog entry 4 ('patch.toml'): rejected by relinker", output.getvalue()) + self.assertIn("Verified catalog entries: 1", output.getvalue()) + self.assertIn(f"Catalog entry 5: {self.catalog / 'patch.toml'}", output.getvalue()) + self.assertEqual(run.call_count, 3) + + @patch.object(suggest_patches.subprocess, "run") + def test_list_verified_no_match_keeps_exit_statuses(self, run): + self.write_index([]) + output = io.StringIO() + with contextlib.redirect_stdout(output): + self.assertEqual(self.advise("--list-verified"), 1) + self.assertIn("No verified catalog entries: no catalog entry", output.getvalue()) + run.assert_not_called() + self.write_index() + run.return_value = subprocess.CompletedProcess([], 2, "", "FAIL: invalid patch") + errors = io.StringIO() + with contextlib.redirect_stderr(errors): + self.assertEqual(self.advise("--list-verified"), 2) + self.assertIn("No verified catalog entries: relinker errors occurred", errors.getvalue()) + + @patch.object(suggest_patches.subprocess, "run") + def test_not_checked_patch_is_not_recommended(self, run): + run.return_value.returncode = 0 + run.return_value.stdout = "Verified 1 patches: 0 ok, 1 not-checked, 0 mismatch\n" + self.assertEqual(self.advise(), 1) + + @patch.object(suggest_patches.subprocess, "run") + def test_success_without_verification_summary_is_an_error(self, run): + run.return_value.returncode = 0 + run.return_value.stdout = "unrecognized verifier" + self.assertEqual(self.advise(), 2) + + @patch.object(suggest_patches.subprocess, "run") + def test_relinker_argument_error_is_not_a_patch_rejection(self, run): + run.return_value.returncode = 1 + run.return_value.stdout = "" + run.return_value.stderr = "FAIL: invalid option" + self.assertEqual(self.advise(), 2) + + @patch.object(suggest_patches.subprocess, "run") + def test_conflicting_module_options_are_rejected_before_relinker(self, run): + self.assertEqual(self.advise("--skip-sce-module", "--sce-module-path", str(self.root)), 2) + run.assert_not_called() + + @patch.object(suggest_patches.subprocess, "run") + def test_uppercase_digest_and_conversion_options(self, run): + self.additional_executable() + self.entry["targets"]["picker/picker.bin"] = self.entry["targets"]["picker/picker.bin"].upper() + self.write_index() + run.return_value.returncode = 0 + run.return_value.stdout = VERIFIED + output = io.StringIO() + with contextlib.redirect_stdout(output): + self.assertEqual(self.advise("--executable", "picker/picker.bin", "--sce-module-path", str(self.root)), 0) + self.assertIn("--patches", output.getvalue()) + self.assertIn("--module-dir Media/Modules", output.getvalue()) + self.assertIn("--executable picker/picker.bin", output.getvalue()) + self.assertIn(f"--sce-module-path {self.root}", output.getvalue()) + + @patch.object(suggest_patches.subprocess, "run") + def test_no_entries_for_title_is_distinct(self, run): + self.write_index([]) + output = io.StringIO() + with contextlib.redirect_stdout(output): + self.assertEqual(self.advise(), 1) + self.assertIn("no catalog entry for TEST00001 01.000.000", output.getvalue()) + run.assert_not_called() + + @patch.object(suggest_patches.subprocess, "run") + def test_invalid_timeout_is_rejected_before_relinker(self, run): + self.assertEqual(self.advise("--timeout-seconds", "0"), 2) + run.assert_not_called() + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_symbolize_samples.py b/tools/tests/test_symbolize_samples.py new file mode 100644 index 0000000000..5e000527dc --- /dev/null +++ b/tools/tests/test_symbolize_samples.py @@ -0,0 +1,202 @@ +import contextlib +import io +import struct +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +import symbolize_samples + +TIMESTAMP = 0x6AD92AC4 +SIZE_OF_IMAGE = 0x3000 + + +def symbol(name, value, section, kind, storage, aux=0): + return struct.pack("<8sIhHBB", name, value, section, kind, storage, aux) + + +def build_pe(path, with_symbols=True, with_exports=False, second_code=False): + image = bytearray(0xC00) + image[0:2] = b"MZ" + struct.pack_into(" [--verbose] """ import argparse +import io import math +import secrets import struct import sys from collections import Counter @@ -43,7 +45,7 @@ RECORD_SIZE = 0x20 RECORD_HAS_SEGMENT = 0x800 RECORD_SEGMENT_SHIFT = 20 FIRST_REGION = 0x400 -MAX_EMPTY_LOAD_PADDING = 64 * 1024 * 1024 +MAX_ALIGNMENT_PADDING = 64 * 1024 * 1024 PT_LOAD = 1 PT_NOTE = 4 ENTROPY_LIMIT = 7.5 @@ -109,6 +111,8 @@ def parse_elf(data): count = struct.unpack_from(" len(data): raise Refused("ELF program header table does not fit the file") headers = [] @@ -182,6 +186,12 @@ def place_segments(data, headers, records): if header["type"] == PT_LOAD: continue if header["index"] in placed: + host = load_for(header["vaddr"], header["size"]) + if host is not None: + offset = header["vaddr"] - host["vaddr"] + if (host["size"] and offset <= host["size"] and header["size"] <= host["size"] - offset + and header["source"] == host["source"] + offset): + header["host"] = host if header["size"] and (tail is None or header["offset"] > tail[0]): tail = (header["offset"], header["source"] - header["offset"]) continue @@ -214,7 +224,7 @@ def place_segments(data, headers, records): for header in headers: if header["type"] == PT_LOAD and header["size"] == 0: continue - if header["source"] > len(data) or header["size"] > len(data) - header["source"]: + if header["source"] < 0 or header["source"] > len(data) or header["size"] > len(data) - header["source"]: raise Refused("segment %d does not fit the file" % header["index"]) @@ -233,8 +243,8 @@ def check_decrypted(data, headers): def unwrap(path, output, verbose=False): - source, destination = Path(path), Path(output) - if source.resolve() == destination.resolve() or (destination.exists() and source.samefile(destination)): + source, destination = Path(path), Path(output).resolve() + if source.resolve() == destination or (destination.exists() and source.samefile(destination)): raise Refused("output aliases the input container") data = bytes(source.read_bytes()) if len(data) < FIRST_REGION: @@ -273,9 +283,11 @@ def unwrap(path, output, verbose=False): regions.sort(key=lambda region: region["old"]) program_offset = struct.unpack_from(" MAX_EMPTY_LOAD_PADDING: - raise Refused("empty load alignment requires more than %d bytes of output padding" % MAX_EMPTY_LOAD_PADDING) + padding += empty_end - cursor + if padding > MAX_ALIGNMENT_PADDING: + raise Refused("segment alignment requires more than %d bytes of output padding" % MAX_ALIGNMENT_PADDING) cursor = empty_end result = bytearray(cursor) @@ -321,19 +334,29 @@ def unwrap(path, output, verbose=False): print(" [%2d] %-18s vaddr=%#011x filesz=%#010x offset=%#010x%s" % (item["index"], name, item["vaddr"], item["size"], item["new"], moved)) - destination = Path(output) - if destination.parent != Path(""): - destination.parent.mkdir(parents=True, exist_ok=True) - destination.write_bytes(result) + destination.parent.mkdir(parents=True, exist_ok=True) + temporary_path = destination.parent / (".unwrap-self-" + secrets.token_hex(16)) + temporary = io.open(temporary_path, "xb") + try: + with temporary: + temporary.write(result) + if sys.platform != "win32" and destination.exists(): + temporary_path.chmod(destination.stat().st_mode) + temporary_path.replace(destination) + finally: + temporary_path.unlink(missing_ok=True) return len(result) -def main(): +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="unwrap a decrypted SELF or fSELF container into a clean ELF") parser.add_argument("input", help="SELF container to read") parser.add_argument("output", help="ELF executable to write") parser.add_argument("--verbose", action="store_true", help="print the program header mapping") - arguments = parser.parse_args() + arguments = parser.parse_args(argv) try: size = unwrap(arguments.input, arguments.output, arguments.verbose) except Refused as error: @@ -347,6 +370,4 @@ def main(): if __name__ == "__main__": - if hasattr(sys.stdout, "reconfigure"): - sys.stdout.reconfigure(encoding="utf-8") sys.exit(main())